From d8eb7147663d28c5cff3249a6df7d98e692741cb Mon Sep 17 00:00:00 2001 From: thug1src Date: Sun, 14 Feb 2016 08:39:12 +1100 Subject: [PATCH] thug1src --- Code/Core/Debug.h | 145 + Code/Core/Debug/Assert.cpp | 314 + Code/Core/Debug/Checks.h | 319 + Code/Core/Debug/Debug.cpp | 454 + Code/Core/Debug/Mem_stat.h | 102 + Code/Core/Debug/Messages.h | 199 + Code/Core/Debug/Module.h | 183 + Code/Core/Debug/NGPS/P_debug.cpp | 341 + Code/Core/Debug/Project.h | 159 + Code/Core/Debug/Signatrs.h | 124 + Code/Core/Debug/Wn32/P_debug.cpp | 155 + Code/Core/Debug/XBox/p_debug.cpp | 341 + Code/Core/Debug/log.cpp | 205 + Code/Core/Debug/ngc/P_debug.cpp | 328 + Code/Core/Defines.h | 559 + Code/Core/DynamicTable.cpp | 87 + Code/Core/DynamicTable.h | 274 + Code/Core/HashTable.h | 515 + Code/Core/List.h | 64 + Code/Core/List/Head.h | 419 + Code/Core/List/Node.h | 456 + Code/Core/List/Search.h | 192 + Code/Core/List/list.cpp | 73 + Code/Core/LookupTable.cpp | 88 + Code/Core/LookupTable.h | 426 + Code/Core/Math/Xbox/sse.h | 158 + Code/Core/Math/geometry.cpp | 562 + Code/Core/Math/geometry.h | 354 + Code/Core/Math/math.cpp | 374 + Code/Core/Math/math.h | 473 + Code/Core/Math/matrix.cpp | 1049 ++ Code/Core/Math/matrix.h | 191 + Code/Core/Math/matrix.inl | 1053 ++ Code/Core/Math/quat.h | 143 + Code/Core/Math/quat.inl | 815 + Code/Core/Math/rect.h | 235 + Code/Core/Math/rot90.cpp | 105 + Code/Core/Math/rot90.h | 26 + Code/Core/Math/slerp.cpp | 274 + Code/Core/Math/slerp.h | 102 + Code/Core/Math/vector.cpp | 432 + Code/Core/Math/vector.h | 242 + Code/Core/Math/vector.inl | 1205 ++ Code/Core/String/CString.cpp | 346 + Code/Core/String/CString.h | 113 + Code/Core/String/stringutils.cpp | 247 + Code/Core/String/stringutils.h | 80 + Code/Core/StringHashTable.h | 127 + Code/Core/Support/Lock.h | 149 + Code/Core/Support/Ref.h | 150 + Code/Core/Support/class.cpp | 272 + Code/Core/Support/class.h | 63 + Code/Core/Support/support.h | 76 + Code/Core/Task.h | 67 + Code/Core/Task/Hook.h | 158 + Code/Core/Task/List.h | 108 + Code/Core/Task/Stack.h | 112 + Code/Core/Task/Task.cpp | 162 + Code/Core/Task/Task.h | 206 + Code/Core/Task/Tlist.cpp | 291 + Code/Core/Task/Tstack.cpp | 377 + Code/Core/Thread/ngc/t_thread.cpp | 70 + Code/Core/Thread/ngc/t_thread.h | 70 + Code/Core/Thread/ngps/t_thread.cpp | 71 + Code/Core/Thread/ngps/t_thread.h | 67 + Code/Core/Thread/wn32/t_thread.cpp | 36 + Code/Core/Thread/wn32/t_thread.h | 36 + Code/Core/TimestampedFlag.h | 173 + Code/Core/compress.cpp | 392 + Code/Core/compress.h | 12 + Code/Core/crc.cpp | 338 + Code/Core/crc.h | 73 + Code/Core/flags.h | 334 + Code/Core/glue.h | 5 + Code/Core/log.h | 11 + Code/Core/macros.h | 61 + Code/Core/math.h | 68 + Code/Core/singleton.h | 365 + Code/Core/support.h | 40 + Code/Core/thread.h | 32 + Code/GameFlow.txt | 147 + Code/Gel/AssMan/AssMan.cpp | 898 + Code/Gel/AssMan/AssMan.h | 124 + Code/Gel/AssMan/NodeArrayAsset.cpp | 111 + Code/Gel/AssMan/NodeArrayAsset.h | 36 + Code/Gel/AssMan/animasset.cpp | 168 + Code/Gel/AssMan/animasset.h | 40 + Code/Gel/AssMan/asset.cpp | 113 + Code/Gel/AssMan/asset.h | 102 + Code/Gel/AssMan/assettypes.h | 24 + Code/Gel/AssMan/cutsceneasset.cpp | 118 + Code/Gel/AssMan/cutsceneasset.h | 46 + Code/Gel/AssMan/refasset.cpp | 156 + Code/Gel/AssMan/refasset.h | 53 + Code/Gel/AssMan/skeletonasset.cpp | 137 + Code/Gel/AssMan/skeletonasset.h | 47 + Code/Gel/AssMan/skinasset.cpp | 161 + Code/Gel/AssMan/skinasset.h | 59 + Code/Gel/Collision/BatchTriColl.cpp | 482 + Code/Gel/Collision/BatchTriColl.h | 163 + Code/Gel/Collision/CollCache.cpp | 333 + Code/Gel/Collision/CollCache.h | 212 + Code/Gel/Collision/CollEnums.h | 46 + Code/Gel/Collision/CollTriData.cpp | 2239 +++ Code/Gel/Collision/CollTriData.h | 721 + Code/Gel/Collision/Collision.cpp | 2698 +++ Code/Gel/Collision/Collision.h | 729 + Code/Gel/Collision/MovCollMan.cpp | 78 + Code/Gel/Collision/MovCollMan.h | 93 + Code/Gel/Components/BouncyComponent.h | 116 + Code/Gel/Components/CameraComponent.cpp | 239 + Code/Gel/Components/CameraComponent.h | 66 + .../Components/CameraLookAroundComponent.cpp | 322 + .../Components/CameraLookAroundComponent.h | 120 + Code/Gel/Components/CameraUtil.cpp | 165 + Code/Gel/Components/CameraUtil.h | 52 + .../Gel/Components/CollideAndDieComponent.cpp | 297 + Code/Gel/Components/CollideAndDieComponent.h | 70 + .../Gel/Components/FloatingLabelComponent.cpp | 155 + Code/Gel/Components/FloatingLabelComponent.h | 60 + .../GunslingerCameraLookAroundComponent.cpp | 452 + .../GunslingerWalkCameraComponent.cpp | 569 + .../Components/GunslingerWalkComponent.cpp | 2351 +++ Code/Gel/Components/HorseCameraComponent.cpp | 559 + Code/Gel/Components/HorseCameraComponent.h | 106 + Code/Gel/Components/HorseComponent.cpp | 2330 +++ Code/Gel/Components/HorseComponent.h | 439 + Code/Gel/Components/InputComponent.cpp | 489 + Code/Gel/Components/InputComponent.h | 96 + .../Components/ModelLightUpdateComponent.cpp | 188 + .../Components/ModelLightUpdateComponent.h | 54 + .../Components/MovableContactComponent.cpp | 270 + Code/Gel/Components/MovableContactComponent.h | 128 + Code/Gel/Components/NearComponent.cpp | 701 + Code/Gel/Components/NearComponent.h | 121 + Code/Gel/Components/NodeArrayComponent.cpp | 221 + Code/Gel/Components/NodeArrayComponent.h | 54 + .../Components/ObjectHookManagerComponent.cpp | 209 + .../Components/ObjectHookManagerComponent.h | 55 + Code/Gel/Components/ParticleComponent.cpp | 697 + Code/Gel/Components/ParticleComponent.h | 63 + Code/Gel/Components/PedLogicComponent.cpp | 2497 +++ Code/Gel/Components/PedLogicComponent.h | 210 + .../Gel/Components/ProximTriggerComponent.cpp | 156 + Code/Gel/Components/ProximTriggerComponent.h | 74 + Code/Gel/Components/RailManagerComponent.cpp | 222 + Code/Gel/Components/RailManagerComponent.h | 58 + Code/Gel/Components/RibbonComponent.cpp | 284 + Code/Gel/Components/RibbonComponent.h | 71 + Code/Gel/Components/RiderComponent.cpp | 757 + Code/Gel/Components/RiderComponent.h | 491 + .../Components/SetDisplayMatrixComponent.cpp | 104 + .../Components/SetDisplayMatrixComponent.h | 49 + Code/Gel/Components/SkaterCameraComponent.cpp | 1544 ++ Code/Gel/Components/SkaterCameraComponent.h | 192 + Code/Gel/Components/SkitchComponent.cpp | 275 + Code/Gel/Components/SkitchComponent.h | 61 + .../Gel/Components/StaticVehicleComponent.cpp | 186 + Code/Gel/Components/StaticVehicleComponent.h | 55 + Code/Gel/Components/StatsManagerComponent.cpp | 1252 ++ Code/Gel/Components/StatsManagerComponent.h | 143 + Code/Gel/Components/StreamComponent.cpp | 338 + Code/Gel/Components/StreamComponent.h | 87 + Code/Gel/Components/SuspendComponent.cpp | 602 + Code/Gel/Components/SuspendComponent.h | 113 + Code/Gel/Components/TriggerComponent.cpp | 288 + Code/Gel/Components/TriggerComponent.h | 85 + Code/Gel/Components/VehicleSoundComponent.cpp | 553 + Code/Gel/Components/VehicleSoundComponent.h | 100 + Code/Gel/Components/VelocityComponent.cpp | 205 + Code/Gel/Components/VelocityComponent.h | 58 + Code/Gel/Components/VibrationComponent.cpp | 339 + Code/Gel/Components/VibrationComponent.h | 77 + Code/Gel/Components/WalkCameraComponent.cpp | 589 + Code/Gel/Components/WalkCameraComponent.h | 147 + Code/Gel/Components/WalkComponent.cpp | 3310 ++++ Code/Gel/Components/WalkComponent.h | 643 + Code/Gel/Components/WalkHangUtil.cpp | 1450 ++ Code/Gel/Components/WalkLadderUtil.cpp | 582 + Code/Gel/Components/animationcomponent.cpp | 2356 +++ Code/Gel/Components/animationcomponent.h | 188 + Code/Gel/Components/avoidcomponent.cpp | 697 + Code/Gel/Components/avoidcomponent.h | 69 + Code/Gel/Components/bouncycomponent.cpp | 734 + Code/Gel/Components/carphysicscomponent.cpp | 176 + Code/Gel/Components/carphysicscomponent.h | 69 + Code/Gel/Components/collisioncomponent.cpp | 228 + Code/Gel/Components/collisioncomponent.h | 63 + Code/Gel/Components/emptycomponent.cpp | 218 + Code/Gel/Components/emptycomponent.h | 53 + Code/Gel/Components/lockobjcomponent.cpp | 489 + Code/Gel/Components/lockobjcomponent.h | 103 + Code/Gel/Components/modelcomponent.cpp | 1840 ++ Code/Gel/Components/modelcomponent.h | 197 + Code/Gel/Components/motioncomponent.cpp | 2731 +++ Code/Gel/Components/motioncomponent.h | 275 + Code/Gel/Components/rigidbodycomponent.cpp | 1667 ++ Code/Gel/Components/rigidbodycomponent.h | 304 + Code/Gel/Components/shadowcomponent.cpp | 542 + Code/Gel/Components/shadowcomponent.h | 84 + Code/Gel/Components/skeletoncomponent.cpp | 431 + Code/Gel/Components/skeletoncomponent.h | 80 + Code/Gel/Components/soundcomponent.cpp | 607 + Code/Gel/Components/soundcomponent.h | 100 + Code/Gel/Components/specialitemcomponent.cpp | 429 + Code/Gel/Components/specialitemcomponent.h | 61 + Code/Gel/Components/trickcomponent.cpp | 4069 +++++ Code/Gel/Components/trickcomponent.h | 371 + .../Gel/Components/vehiclecameracomponent.cpp | 314 + Code/Gel/Components/vehiclecameracomponent.h | 76 + Code/Gel/Components/vehiclecomponent.cpp | 3321 ++++ Code/Gel/Components/vehiclecomponent.h | 762 + Code/Gel/Components/weaponcomponent.cpp | 494 + Code/Gel/Components/weaponcomponent.h | 71 + Code/Gel/Environment/terrain.cpp | 935 + Code/Gel/Environment/terrain.h | 134 + Code/Gel/Event.h | 221 + Code/Gel/Input/InpMan.cpp | 389 + Code/Gel/Input/inpserv.cpp | 592 + Code/Gel/MainLoop/Mainloop.cpp | 426 + Code/Gel/Module/modman.cpp | 395 + Code/Gel/Module/module.cpp | 111 + Code/Gel/Movies/Movies.cpp | 57 + Code/Gel/Movies/Movies.h | 46 + Code/Gel/Movies/Ngps/audiodec.cpp | 483 + Code/Gel/Movies/Ngps/audiodec.h | 112 + Code/Gel/Movies/Ngps/defs.h | 55 + Code/Gel/Movies/Ngps/disp.cpp | 328 + Code/Gel/Movies/Ngps/disp.h | 29 + Code/Gel/Movies/Ngps/p_movies.cpp | 763 + Code/Gel/Movies/Ngps/p_movies.h | 129 + Code/Gel/Movies/Ngps/read.cpp | 132 + Code/Gel/Movies/Ngps/readbuf.cpp | 81 + Code/Gel/Movies/Ngps/readbuf.h | 46 + Code/Gel/Movies/Ngps/strfile.cpp | 180 + Code/Gel/Movies/Ngps/strfile.h | 38 + Code/Gel/Movies/Ngps/vibuf.cpp | 639 + Code/Gel/Movies/Ngps/vibuf.h | 84 + Code/Gel/Movies/Ngps/videodec.cpp | 432 + Code/Gel/Movies/Ngps/videodec.h | 71 + Code/Gel/Movies/Ngps/vobuf.cpp | 90 + Code/Gel/Movies/Ngps/vobuf.h | 90 + Code/Gel/Movies/Xbox/p_movies.cpp | 457 + Code/Gel/Movies/Xbox/p_movies.h | 43 + Code/Gel/Movies/ngc/p_movies.cpp | 2839 +++ Code/Gel/Movies/ngc/p_movies.h | 43 + Code/Gel/Music/Ngps/Bgm/PathDefs | 16 + Code/Gel/Music/Ngps/Bgm/bgm_com.c | 144 + Code/Gel/Music/Ngps/Bgm/bgm_entr.c | 49 + Code/Gel/Music/Ngps/Bgm/bgm_i.h | 58 + Code/Gel/Music/Ngps/Bgm/bgm_play.c | 797 + Code/Gel/Music/Ngps/Bgm/bgm_r2s.s | 129 + Code/Gel/Music/Ngps/Bgm/bgm_r2sm.c | 20 + Code/Gel/Music/Ngps/Bgm/makefile | 33 + Code/Gel/Music/Ngps/Pcm/Makefile | 39 + Code/Gel/Music/Ngps/Pcm/PathDefs | 16 + Code/Gel/Music/Ngps/Pcm/pcm.h | 310 + Code/Gel/Music/Ngps/Pcm/pcm_com.c | 1821 ++ Code/Gel/Music/Ngps/Pcm/pcm_ent.c | 59 + Code/Gel/Music/Ngps/Pcm/pcm_sound.c | 430 + Code/Gel/Music/Ngps/Pcm/pcmiop.h | 76 + Code/Gel/Music/Ngps/p_music.cpp | 1426 ++ Code/Gel/Music/Ngps/p_music.h | 189 + Code/Gel/Music/Xbox/p_adpcmfilestream.cpp | 1134 ++ Code/Gel/Music/Xbox/p_adpcmfilestream.h | 174 + Code/Gel/Music/Xbox/p_music.cpp | 1324 ++ Code/Gel/Music/Xbox/p_music.h | 82 + Code/Gel/Music/Xbox/p_soundtrack.cpp | 222 + Code/Gel/Music/Xbox/p_soundtrack.h | 80 + Code/Gel/Music/Xbox/p_wmafilestream.cpp | 1219 ++ Code/Gel/Music/Xbox/p_wmafilestream.h | 162 + Code/Gel/Music/music.cpp | 2539 +++ Code/Gel/Music/music.h | 272 + Code/Gel/Music/ngc/bgm/bgm_com.c | 144 + Code/Gel/Music/ngc/bgm/bgm_entr.c | 49 + Code/Gel/Music/ngc/bgm/bgm_i.h | 58 + Code/Gel/Music/ngc/bgm/bgm_play.c | 797 + Code/Gel/Music/ngc/bgm/bgm_r2s.s | 129 + Code/Gel/Music/ngc/bgm/bgm_r2sm.c | 20 + Code/Gel/Music/ngc/divx/AUDSimpleAudio.cpp | 790 + Code/Gel/Music/ngc/divx/AUDSimpleAudio.h | 75 + Code/Gel/Music/ngc/divx/AUDSimplePlayer.cpp | 1146 ++ Code/Gel/Music/ngc/divx/AUDSimplePlayer.h | 124 + Code/Gel/Music/ngc/p_music.cpp | 2121 +++ Code/Gel/Music/ngc/p_music.h | 85 + Code/Gel/Music/ngc/pcm/pcm.h | 167 + Code/Gel/Music/ngc/pcm/pcm_com.c | 1655 ++ Code/Gel/Music/ngc/pcm/pcm_ent.c | 46 + Code/Gel/Music/ngc/pcm/pcmiop.h | 13 + Code/Gel/Net/App/netapp.cpp | 2817 +++ Code/Gel/Net/Client/netclnt.cpp | 611 + Code/Gel/Net/Client/netclnt.h | 79 + Code/Gel/Net/Dispatch/netdsptch.cpp | 334 + Code/Gel/Net/Handler/nethndlr.cpp | 745 + Code/Gel/Net/Server/netserv.cpp | 570 + Code/Gel/Net/Server/netserv.h | 77 + Code/Gel/Net/net.cpp | 3220 ++++ Code/Gel/Net/net.h | 1381 ++ Code/Gel/Net/netconn.cpp | 688 + Code/Gel/ObjPtr.h | 204 + Code/Gel/Object/Event.cpp | 1092 ++ Code/Gel/Object/ObjPtr.cpp | 11 + Code/Gel/Object/RefCounted.cpp | 105 + Code/Gel/Object/basecomponent.cpp | 127 + Code/Gel/Object/basecomponent.h | 94 + Code/Gel/Object/compositeobject.cpp | 1096 ++ Code/Gel/Object/compositeobject.h | 168 + Code/Gel/Object/compositeobjectmanager.cpp | 512 + Code/Gel/Object/compositeobjectmanager.h | 72 + Code/Gel/Object/object.cpp | 1189 ++ Code/Gel/Object/objman.cpp | 459 + Code/Gel/Object/objsearch.cpp | 134 + Code/Gel/Object/objtrack.cpp | 1249 ++ Code/Gel/Prefs/Prefs.cpp | 370 + Code/Gel/Prefs/Prefs.h | 118 + Code/Gel/RefCounted.h | 32 + Code/Gel/Scripting/array.cpp | 559 + Code/Gel/Scripting/array.h | 131 + Code/Gel/Scripting/checksum.cpp | 274 + Code/Gel/Scripting/checksum.h | 21 + Code/Gel/Scripting/component.cpp | 61 + Code/Gel/Scripting/component.h | 93 + Code/Gel/Scripting/debugger.cpp | 1628 ++ Code/Gel/Scripting/debugger.h | 308 + Code/Gel/Scripting/eval.cpp | 996 ++ Code/Gel/Scripting/eval.h | 86 + Code/Gel/Scripting/file.cpp | 159 + Code/Gel/Scripting/file.h | 19 + Code/Gel/Scripting/init.cpp | 193 + Code/Gel/Scripting/init.h | 31 + Code/Gel/Scripting/parse.cpp | 3415 ++++ Code/Gel/Scripting/parse.h | 159 + Code/Gel/Scripting/script.cpp | 3835 ++++ Code/Gel/Scripting/script.h | 508 + Code/Gel/Scripting/scriptcache.cpp | 574 + Code/Gel/Scripting/scriptcache.h | 139 + Code/Gel/Scripting/scriptdefs.h | 32 + Code/Gel/Scripting/skiptoken.cpp | 134 + Code/Gel/Scripting/string.cpp | 250 + Code/Gel/Scripting/string.h | 25 + Code/Gel/Scripting/struct.cpp | 2080 +++ Code/Gel/Scripting/struct.h | 286 + Code/Gel/Scripting/symboltable.cpp | 762 + Code/Gel/Scripting/symboltable.h | 169 + Code/Gel/Scripting/symboltype.cpp | 75 + Code/Gel/Scripting/symboltype.h | 76 + Code/Gel/Scripting/tokens.cpp | 237 + Code/Gel/Scripting/tokens.h | 140 + Code/Gel/Scripting/utils.cpp | 1775 ++ Code/Gel/Scripting/utils.h | 36 + Code/Gel/Scripting/vecpair.cpp | 37 + Code/Gel/Scripting/vecpair.h | 58 + Code/Gel/Scripting/win32functions.cpp | 125 + Code/Gel/SoundFX/NGPS/p_sfx.cpp | 1512 ++ Code/Gel/SoundFX/NGPS/p_sfx.h | 141 + Code/Gel/SoundFX/Xbox/p_sfx.cpp | 1363 ++ Code/Gel/SoundFX/Xbox/p_sfx.h | 135 + Code/Gel/SoundFX/Xbox/skate5fx.h | 61 + Code/Gel/SoundFX/ngc/p_sfx.cpp | 961 + Code/Gel/SoundFX/ngc/p_sfx.h | 82 + Code/Gel/SoundFX/soundfx.cpp | 1833 ++ Code/Gel/SoundFX/soundfx.h | 325 + Code/Gel/inpman.h | 531 + Code/Gel/mainloop.h | 411 + Code/Gel/modman.h | 120 + Code/Gel/module.h | 160 + Code/Gel/object.h | 377 + Code/Gel/objman.h | 295 + Code/Gel/objsearch.h | 82 + Code/Gel/objserv.h | 116 + Code/Gel/objtrack.h | 272 + Code/Gfx/2D/BlurEffect.cpp | 131 + Code/Gfx/2D/BlurEffect.h | 64 + Code/Gfx/2D/Element3d.cpp | 945 + Code/Gfx/2D/Element3d.h | 136 + Code/Gfx/2D/Menu2.cpp | 587 + Code/Gfx/2D/Menu2.h | 87 + Code/Gfx/2D/ScreenElemMan.cpp | 1453 ++ Code/Gfx/2D/ScreenElemMan.h | 138 + Code/Gfx/2D/ScreenElement2.cpp | 1410 ++ Code/Gfx/2D/ScreenElement2.h | 457 + Code/Gfx/2D/ScrollingMenu.cpp | 385 + Code/Gfx/2D/ScrollingMenu.h | 78 + Code/Gfx/2D/SpriteElement.cpp | 182 + Code/Gfx/2D/SpriteElement.h | 45 + Code/Gfx/2D/TextElement.cpp | 1374 ++ Code/Gfx/2D/TextElement.h | 164 + Code/Gfx/2D/Window.cpp | 51 + Code/Gfx/2D/Window.h | 31 + Code/Gfx/AnimController.cpp | 474 + Code/Gfx/AnimController.h | 95 + Code/Gfx/BonedAnim.cpp | 2647 +++ Code/Gfx/BonedAnim.h | 206 + Code/Gfx/BonedAnimTypes.h | 143 + Code/Gfx/CasUtils.cpp | 358 + Code/Gfx/CasUtils.h | 45 + Code/Gfx/CustomAnimKey.cpp | 692 + Code/Gfx/CustomAnimKey.h | 137 + Code/Gfx/FaceMassage.cpp | 644 + Code/Gfx/FaceMassage.h | 103 + Code/Gfx/FaceTexture.cpp | 324 + Code/Gfx/FaceTexture.h | 82 + Code/Gfx/Image/ImageBasic.h | 107 + Code/Gfx/ModelAppearance.cpp | 804 + Code/Gfx/ModelAppearance.h | 126 + Code/Gfx/ModelBuilder.cpp | 1130 ++ Code/Gfx/ModelBuilder.h | 108 + Code/Gfx/NGC/NX/anim.cpp | 268 + Code/Gfx/NGC/NX/anim.h | 16 + Code/Gfx/NGC/NX/chars.cpp | 689 + Code/Gfx/NGC/NX/chars.h | 101 + Code/Gfx/NGC/NX/geomnode.cpp | 358 + Code/Gfx/NGC/NX/geomnode.h | 185 + Code/Gfx/NGC/NX/grass.cpp | 1413 ++ Code/Gfx/NGC/NX/grass.h | 151 + Code/Gfx/NGC/NX/import.cpp | 224 + Code/Gfx/NGC/NX/import.h | 17 + Code/Gfx/NGC/NX/instance.cpp | 1190 ++ Code/Gfx/NGC/NX/instance.h | 88 + Code/Gfx/NGC/NX/light.cpp | 326 + Code/Gfx/NGC/NX/light.h | 122 + Code/Gfx/NGC/NX/line.cpp | 132 + Code/Gfx/NGC/NX/line.h | 21 + Code/Gfx/NGC/NX/material.cpp | 1619 ++ Code/Gfx/NGC/NX/material.h | 118 + Code/Gfx/NGC/NX/mesh.cpp | 1470 ++ Code/Gfx/NGC/NX/mesh.h | 195 + Code/Gfx/NGC/NX/nx_init.cpp | 159 + Code/Gfx/NGC/NX/nx_init.h | 99 + Code/Gfx/NGC/NX/occlude.cpp | 491 + Code/Gfx/NGC/NX/occlude.h | 15 + Code/Gfx/NGC/NX/particles.cpp | 372 + Code/Gfx/NGC/NX/particles.h | 45 + Code/Gfx/NGC/NX/render.cpp | 2830 +++ Code/Gfx/NGC/NX/render.h | 96 + Code/Gfx/NGC/NX/scene.cpp | 1150 ++ Code/Gfx/NGC/NX/scene.h | 133 + Code/Gfx/NGC/NX/sprite.cpp | 528 + Code/Gfx/NGC/NX/sprite.h | 76 + Code/Gfx/NGC/NX/texture.cpp | 255 + Code/Gfx/NGC/NX/texture.h | 66 + Code/Gfx/NGC/NX/types.h | 16 + Code/Gfx/NGC/blur.cpp | 221 + Code/Gfx/NGC/p_NxGeom.cpp | 2125 +++ Code/Gfx/NGC/p_NxGeom.h | 125 + Code/Gfx/NGC/p_NxImposter.cpp | 69 + Code/Gfx/NGC/p_NxImposter.h | 30 + Code/Gfx/NGC/p_NxLightMan.cpp | 250 + Code/Gfx/NGC/p_NxMesh.cpp | 231 + Code/Gfx/NGC/p_NxMesh.h | 66 + Code/Gfx/NGC/p_NxSprite.cpp | 139 + Code/Gfx/NGC/p_NxSprite.h | 37 + Code/Gfx/NGC/p_NxTextured3dPoly.cpp | 27 + Code/Gfx/NGC/p_NxTextured3dPoly.h | 23 + Code/Gfx/NGC/p_NxViewport.cpp | 47 + Code/Gfx/NGC/p_NxViewport.h | 31 + Code/Gfx/NGC/p_NxWin2D.cpp | 82 + Code/Gfx/NGC/p_gfxman.cpp | 215 + Code/Gfx/NGC/p_loadscreen.cpp | 648 + Code/Gfx/NGC/p_memview.cpp | 1494 ++ Code/Gfx/NGC/p_memview.h | 19 + Code/Gfx/NGC/p_nx.cpp | 4734 +++++ Code/Gfx/NGC/p_nxanimcache.cpp | 47 + Code/Gfx/NGC/p_nxanimcache.h | 37 + Code/Gfx/NGC/p_nxfont.cpp | 264 + Code/Gfx/NGC/p_nxfont.h | 77 + Code/Gfx/NGC/p_nxfontman.cpp | 106 + Code/Gfx/NGC/p_nxlight.cpp | 307 + Code/Gfx/NGC/p_nxlight.h | 56 + Code/Gfx/NGC/p_nxloadscreen.cpp | 569 + Code/Gfx/NGC/p_nxmiscfx.cpp | 1658 ++ Code/Gfx/NGC/p_nxmodel.cpp | 369 + Code/Gfx/NGC/p_nxmodel.h | 64 + Code/Gfx/NGC/p_nxnewparticle.cpp | 791 + Code/Gfx/NGC/p_nxnewparticle.h | 130 + Code/Gfx/NGC/p_nxnewparticlemgr.cpp | 83 + Code/Gfx/NGC/p_nxnewparticlemgr.h | 77 + Code/Gfx/NGC/p_nxparticle.cpp | 168 + Code/Gfx/NGC/p_nxparticle.h | 31 + Code/Gfx/NGC/p_nxparticleflat.cpp | 298 + Code/Gfx/NGC/p_nxparticleflat.h | 82 + Code/Gfx/NGC/p_nxparticleglow.cpp | 319 + Code/Gfx/NGC/p_nxparticleglow.h | 87 + Code/Gfx/NGC/p_nxparticleglowribbontrail.cpp | 424 + Code/Gfx/NGC/p_nxparticleglowribbontrail.h | 86 + Code/Gfx/NGC/p_nxparticleline.cpp | 272 + Code/Gfx/NGC/p_nxparticleline.h | 84 + Code/Gfx/NGC/p_nxparticleribbon.cpp | 292 + Code/Gfx/NGC/p_nxparticleribbon.h | 84 + Code/Gfx/NGC/p_nxparticleribbontrail.cpp | 322 + Code/Gfx/NGC/p_nxparticleribbontrail.h | 84 + Code/Gfx/NGC/p_nxparticleshaded.cpp | 305 + Code/Gfx/NGC/p_nxparticleshaded.h | 83 + Code/Gfx/NGC/p_nxparticlesmooth.cpp | 314 + Code/Gfx/NGC/p_nxparticlesmooth.h | 84 + Code/Gfx/NGC/p_nxparticlesmoothribbon.cpp | 298 + Code/Gfx/NGC/p_nxparticlesmoothribbon.h | 84 + Code/Gfx/NGC/p_nxparticlesmoothstar.cpp | 321 + Code/Gfx/NGC/p_nxparticlesmoothstar.h | 86 + Code/Gfx/NGC/p_nxparticlestar.cpp | 311 + Code/Gfx/NGC/p_nxparticlestar.h | 86 + Code/Gfx/NGC/p_nxscene.cpp | 138 + Code/Gfx/NGC/p_nxscene.h | 41 + Code/Gfx/NGC/p_nxsector.cpp | 980 + Code/Gfx/NGC/p_nxsector.h | 55 + Code/Gfx/NGC/p_nxtexman.cpp | 69 + Code/Gfx/NGC/p_nxtexture.cpp | 773 + Code/Gfx/NGC/p_nxtexture.h | 106 + Code/Gfx/NGC/p_nxviewman.cpp | 32 + Code/Gfx/NGC/p_nxweather.cpp | 1279 ++ Code/Gfx/NGC/p_nxweather.h | 105 + Code/Gfx/NGC/p_nxwin2D.h | 51 + Code/Gfx/NGPS/NX/Makefile | 91 + Code/Gfx/NGPS/NX/asmdma.dsm | 276 + Code/Gfx/NGPS/NX/asmdma.h | 12 + Code/Gfx/NGPS/NX/chars.cpp | 755 + Code/Gfx/NGPS/NX/chars.h | 94 + Code/Gfx/NGPS/NX/dma.cpp | 1267 ++ Code/Gfx/NGPS/NX/dma.h | 321 + Code/Gfx/NGPS/NX/dmacalls.cpp | 472 + Code/Gfx/NGPS/NX/dmacalls.h | 37 + Code/Gfx/NGPS/NX/fx.cpp | 27 + Code/Gfx/NGPS/NX/fx.h | 17 + Code/Gfx/NGPS/NX/geomnode.cpp | 4177 +++++ Code/Gfx/NGPS/NX/geomnode.h | 411 + Code/Gfx/NGPS/NX/gif.cpp | 202 + Code/Gfx/NGPS/NX/gif.h | 45 + Code/Gfx/NGPS/NX/group.cpp | 46 + Code/Gfx/NGPS/NX/group.h | 58 + Code/Gfx/NGPS/NX/gs.cpp | 101 + Code/Gfx/NGPS/NX/gs.h | 639 + Code/Gfx/NGPS/NX/immediate.cpp | 534 + Code/Gfx/NGPS/NX/immediate.h | 69 + Code/Gfx/NGPS/NX/instance.cpp | 277 + Code/Gfx/NGPS/NX/instance.h | 123 + Code/Gfx/NGPS/NX/interrupts.cpp | 361 + Code/Gfx/NGPS/NX/interrupts.h | 112 + Code/Gfx/NGPS/NX/light.cpp | 59 + Code/Gfx/NGPS/NX/light.h | 417 + Code/Gfx/NGPS/NX/line.cpp | 184 + Code/Gfx/NGPS/NX/line.h | 21 + Code/Gfx/NGPS/NX/loadscreen.cpp | 219 + Code/Gfx/NGPS/NX/loadscreen.h | 30 + Code/Gfx/NGPS/NX/maintest.cpp | 253 + Code/Gfx/NGPS/NX/material.cpp | 217 + Code/Gfx/NGPS/NX/material.h | 45 + Code/Gfx/NGPS/NX/mesh.cpp | 1219 ++ Code/Gfx/NGPS/NX/mesh.h | 109 + Code/Gfx/NGPS/NX/mikemath.cpp | 414 + Code/Gfx/NGPS/NX/mikemath.h | 64 + Code/Gfx/NGPS/NX/nx_init.cpp | 434 + Code/Gfx/NGPS/NX/nx_init.h | 16 + Code/Gfx/NGPS/NX/occlude.cpp | 569 + Code/Gfx/NGPS/NX/occlude.h | 68 + Code/Gfx/NGPS/NX/pcrtc.cpp | 109 + Code/Gfx/NGPS/NX/pcrtc.h | 12 + Code/Gfx/NGPS/NX/render.cpp | 2346 +++ Code/Gfx/NGPS/NX/render.h | 144 + Code/Gfx/NGPS/NX/resource.cpp | 61 + Code/Gfx/NGPS/NX/resource.h | 42 + Code/Gfx/NGPS/NX/scene.cpp | 295 + Code/Gfx/NGPS/NX/scene.h | 91 + Code/Gfx/NGPS/NX/sprite.cpp | 1569 ++ Code/Gfx/NGPS/NX/sprite.h | 267 + Code/Gfx/NGPS/NX/switches.h | 60 + Code/Gfx/NGPS/NX/texture.cpp | 1135 ++ Code/Gfx/NGPS/NX/texture.h | 86 + Code/Gfx/NGPS/NX/texturemem.cpp | 1250 ++ Code/Gfx/NGPS/NX/texturemem.h | 109 + Code/Gfx/NGPS/NX/types.h | 16 + Code/Gfx/NGPS/NX/vif.cpp | 95 + Code/Gfx/NGPS/NX/vif.h | 623 + Code/Gfx/NGPS/NX/vu0code.dsm | 613 + Code/Gfx/NGPS/NX/vu0code.h | 17 + Code/Gfx/NGPS/NX/vu1.cpp | 136 + Code/Gfx/NGPS/NX/vu1.h | 171 + Code/Gfx/NGPS/NX/vu1/defs.vsm | 5 + Code/Gfx/NGPS/NX/vu1/jumptab.vsm | 23 + Code/Gfx/NGPS/NX/vu1/main.vsm | 156 + Code/Gfx/NGPS/NX/vu1/newvu1code.dsm | 27 + Code/Gfx/NGPS/NX/vu1/newvu1code.h | 12 + Code/Gfx/NGPS/NX/vu1/particle.vsm | 299 + Code/Gfx/NGPS/NX/vu1code.dsm | 5809 ++++++ Code/Gfx/NGPS/NX/vu1code.h | 111 + Code/Gfx/NGPS/NX/vu1context.cpp | 455 + Code/Gfx/NGPS/NX/vu1context.h | 184 + Code/Gfx/NGPS/PaletteGen.cpp | 979 + Code/Gfx/NGPS/PaletteGen.h | 85 + Code/Gfx/NGPS/p_NxAnimCache.cpp | 46 + Code/Gfx/NGPS/p_NxAnimCache.h | 36 + Code/Gfx/NGPS/p_NxFont.cpp | 257 + Code/Gfx/NGPS/p_NxFont.h | 73 + Code/Gfx/NGPS/p_NxFontMan.cpp | 48 + Code/Gfx/NGPS/p_NxGeom.cpp | 1111 ++ Code/Gfx/NGPS/p_NxGeom.h | 154 + Code/Gfx/NGPS/p_NxImposter.cpp | 69 + Code/Gfx/NGPS/p_NxImposter.h | 30 + Code/Gfx/NGPS/p_NxLight.cpp | 252 + Code/Gfx/NGPS/p_NxLight.h | 77 + Code/Gfx/NGPS/p_NxLightMan.cpp | 276 + Code/Gfx/NGPS/p_NxLightMan.h | 50 + Code/Gfx/NGPS/p_NxLoadScreen.cpp | 233 + Code/Gfx/NGPS/p_NxMesh.cpp | 373 + Code/Gfx/NGPS/p_NxMesh.h | 64 + Code/Gfx/NGPS/p_NxModel.cpp | 148 + Code/Gfx/NGPS/p_NxModel.h | 56 + Code/Gfx/NGPS/p_NxScene.cpp | 387 + Code/Gfx/NGPS/p_NxScene.h | 59 + Code/Gfx/NGPS/p_NxSector.cpp | 292 + Code/Gfx/NGPS/p_NxSector.h | 96 + Code/Gfx/NGPS/p_NxSprite.cpp | 160 + Code/Gfx/NGPS/p_NxSprite.h | 37 + Code/Gfx/NGPS/p_NxTexMan.cpp | 80 + Code/Gfx/NGPS/p_NxTexture.cpp | 2265 +++ Code/Gfx/NGPS/p_NxTexture.h | 244 + Code/Gfx/NGPS/p_NxTextured3dPoly.cpp | 39 + Code/Gfx/NGPS/p_NxTextured3dPoly.h | 26 + Code/Gfx/NGPS/p_NxViewMan.cpp | 31 + Code/Gfx/NGPS/p_NxViewport.cpp | 154 + Code/Gfx/NGPS/p_NxViewport.h | 33 + Code/Gfx/NGPS/p_NxWin2D.cpp | 80 + Code/Gfx/NGPS/p_NxWin2D.h | 49 + Code/Gfx/NGPS/p_gfxman.cpp | 784 + Code/Gfx/NGPS/p_memview.cpp | 1716 ++ Code/Gfx/NGPS/p_memview.h | 21 + Code/Gfx/NGPS/p_nx.cpp | 891 + Code/Gfx/NGPS/p_nxmiscfx.cpp | 1483 ++ Code/Gfx/NGPS/p_nxnewparticle.cpp | 546 + Code/Gfx/NGPS/p_nxnewparticle.h | 127 + Code/Gfx/NGPS/p_nxnewparticlemgr.cpp | 85 + Code/Gfx/NGPS/p_nxnewparticlemgr.h | 77 + Code/Gfx/NGPS/p_nxparticle.cpp | 138 + Code/Gfx/NGPS/p_nxparticle.h | 23 + Code/Gfx/NGPS/p_nxparticleflat.cpp | 397 + Code/Gfx/NGPS/p_nxparticleflat.h | 80 + Code/Gfx/NGPS/p_nxparticleglow.cpp | 290 + Code/Gfx/NGPS/p_nxparticleglow.h | 84 + Code/Gfx/NGPS/p_nxparticleglowribbontrail.cpp | 398 + Code/Gfx/NGPS/p_nxparticleglowribbontrail.h | 84 + Code/Gfx/NGPS/p_nxparticleline.cpp | 254 + Code/Gfx/NGPS/p_nxparticleline.h | 82 + Code/Gfx/NGPS/p_nxparticlenewflat.cpp | 547 + Code/Gfx/NGPS/p_nxparticlenewflat.h | 131 + Code/Gfx/NGPS/p_nxparticleribbon.cpp | 284 + Code/Gfx/NGPS/p_nxparticleribbon.h | 82 + Code/Gfx/NGPS/p_nxparticleribbontrail.cpp | 312 + Code/Gfx/NGPS/p_nxparticleribbontrail.h | 82 + Code/Gfx/NGPS/p_nxparticleshaded.cpp | 271 + Code/Gfx/NGPS/p_nxparticleshaded.h | 81 + Code/Gfx/NGPS/p_nxparticlesmooth.cpp | 270 + Code/Gfx/NGPS/p_nxparticlesmooth.h | 82 + Code/Gfx/NGPS/p_nxparticlesmoothribbon.cpp | 285 + Code/Gfx/NGPS/p_nxparticlesmoothribbon.h | 82 + Code/Gfx/NGPS/p_nxparticlesmoothstar.cpp | 290 + Code/Gfx/NGPS/p_nxparticlesmoothstar.h | 84 + Code/Gfx/NGPS/p_nxparticlestar.cpp | 286 + Code/Gfx/NGPS/p_nxparticlestar.h | 84 + Code/Gfx/NGPS/p_nxweather.cpp | 1189 ++ Code/Gfx/NGPS/p_nxweather.h | 100 + Code/Gfx/NxAnimCache.cpp | 60 + Code/Gfx/NxAnimCache.h | 26 + Code/Gfx/NxFont.cpp | 573 + Code/Gfx/NxFont.h | 229 + Code/Gfx/NxFontMan.cpp | 213 + Code/Gfx/NxFontMan.h | 69 + Code/Gfx/NxGeom.cpp | 1093 ++ Code/Gfx/NxGeom.h | 213 + Code/Gfx/NxHierarchy.h | 136 + Code/Gfx/NxImposter.cpp | 445 + Code/Gfx/NxImposter.h | 109 + Code/Gfx/NxLight.cpp | 381 + Code/Gfx/NxLight.h | 101 + Code/Gfx/NxLightMan.cpp | 1362 ++ Code/Gfx/NxLightMan.h | 233 + Code/Gfx/NxLoadScreen.cpp | 316 + Code/Gfx/NxLoadScreen.h | 64 + Code/Gfx/NxMesh.cpp | 216 + Code/Gfx/NxMesh.h | 72 + Code/Gfx/NxMiscFX.cpp | 831 + Code/Gfx/NxMiscFX.h | 205 + Code/Gfx/NxModel.cpp | 1656 ++ Code/Gfx/NxModel.h | 220 + Code/Gfx/NxNewParticle.cpp | 517 + Code/Gfx/NxNewParticle.h | 176 + Code/Gfx/NxNewParticleMgr.cpp | 197 + Code/Gfx/NxNewParticleMgr.h | 93 + Code/Gfx/NxQuickAnim.cpp | 464 + Code/Gfx/NxQuickAnim.h | 116 + Code/Gfx/NxScene.cpp | 1282 ++ Code/Gfx/NxScene.h | 322 + Code/Gfx/NxSector.cpp | 919 + Code/Gfx/NxSector.h | 219 + Code/Gfx/NxSkinComponent.cpp | 143 + Code/Gfx/NxSkinComponent.h | 47 + Code/Gfx/NxSprite.cpp | 320 + Code/Gfx/NxSprite.h | 220 + Code/Gfx/NxTexMan.cpp | 808 + Code/Gfx/NxTexMan.h | 84 + Code/Gfx/NxTexture.cpp | 1187 ++ Code/Gfx/NxTexture.h | 268 + Code/Gfx/NxTextured3dPoly.cpp | 63 + Code/Gfx/NxTextured3dPoly.h | 47 + Code/Gfx/NxViewMan.cpp | 658 + Code/Gfx/NxViewMan.h | 174 + Code/Gfx/NxViewport.cpp | 190 + Code/Gfx/NxViewport.h | 217 + Code/Gfx/NxWin2D.cpp | 126 + Code/Gfx/NxWin2D.h | 107 + Code/Gfx/Pose.h | 71 + Code/Gfx/Skeleton.cpp | 1781 ++ Code/Gfx/Skeleton.h | 179 + Code/Gfx/XBox/NX/BillboardScreenAlignedVS.vsh | 53 + Code/Gfx/XBox/NX/ParticleFlatVS.vsh | 66 + .../XBox/NX/ParticleNewFlatPointSpriteVS.vsh | 59 + Code/Gfx/XBox/NX/ParticleNewFlatVS.vsh | 75 + Code/Gfx/XBox/NX/PixelShader0.psh | 8 + Code/Gfx/XBox/NX/PixelShader0IVA.psh | 8 + Code/Gfx/XBox/NX/PixelShader1.psh | 3 + Code/Gfx/XBox/NX/PixelShader2.psh | 3 + Code/Gfx/XBox/NX/PixelShader3.psh | 2 + Code/Gfx/XBox/NX/PixelShader4.psh | 3 + Code/Gfx/XBox/NX/PixelShader5.psh | 2 + Code/Gfx/XBox/NX/PixelShaderBrighten.psh | 10 + Code/Gfx/XBox/NX/PixelShaderBrightenIVA.psh | 10 + Code/Gfx/XBox/NX/PixelShaderBumpWater.psh | 9 + Code/Gfx/XBox/NX/PixelShaderFocusBlur.psh | 33 + .../Gfx/XBox/NX/PixelShaderFocusIntegrate.psh | 32 + .../NX/PixelShaderFocusLookupIntegrate.psh | 22 + Code/Gfx/XBox/NX/PixelShaderNULL.psh | 3 + Code/Gfx/XBox/NX/PixelShaderPointSprite.psh | 3 + Code/Gfx/XBox/NX/PixelShader_ShadowBuffer.psh | 11 + Code/Gfx/XBox/NX/ShadowBufferStaticGeomPS.psh | 15 + Code/Gfx/XBox/NX/ShadowBufferStaticGeomVS.vsh | 42 + Code/Gfx/XBox/NX/WeightedMeshVS_1Weight.vsh | 102 + Code/Gfx/XBox/NX/WeightedMeshVS_2Weight.vsh | 108 + Code/Gfx/XBox/NX/WeightedMeshVS_3Weight.vsh | 118 + .../XBox/NX/WeightedMeshVS_VXC_1Weight.vsh | 74 + .../WeightedMeshVS_VXC_1Weight_SBPassThru.vsh | 53 + ...WeightedMeshVS_VXC_1Weight_UVTransform.vsh | 78 + .../XBox/NX/WeightedMeshVS_VXC_2Weight.vsh | 86 + .../WeightedMeshVS_VXC_2Weight_SBPassThru.vsh | 69 + ...WeightedMeshVS_VXC_2Weight_UVTransform.vsh | 91 + .../XBox/NX/WeightedMeshVS_VXC_3Weight.vsh | 96 + .../WeightedMeshVS_VXC_3Weight_SBPassThru.vsh | 77 + ...WeightedMeshVS_VXC_3Weight_UVTransform.vsh | 101 + .../XBox/NX/WeightedMeshVS_VXC_4Weight.vsh | 117 + .../WeightedMeshVS_VXC_4Weight_SBPassThru.vsh | 89 + .../WeightedMeshVS_VXC_Specular_1Weight.vsh | 108 + .../WeightedMeshVS_VXC_Specular_2Weight.vsh | 121 + .../WeightedMeshVS_VXC_Specular_3Weight.vsh | 131 + .../WeightedMeshVS_VXC_Specular_4Weight.vsh | 151 + .../Gfx/XBox/NX/WeightedMeshVertexShader0.vsh | 142 + .../Gfx/XBox/NX/WeightedMeshVertexShader1.vsh | 142 + .../NX/WeightedMeshVertexShader_SBWrite.vsh | 60 + Code/Gfx/XBox/NX/anim.cpp | 498 + Code/Gfx/XBox/NX/anim.h | 36 + Code/Gfx/XBox/NX/anim_vertdefs.h | 97 + Code/Gfx/XBox/NX/billboard.cpp | 687 + Code/Gfx/XBox/NX/billboard.h | 98 + Code/Gfx/XBox/NX/chars.cpp | 919 + Code/Gfx/XBox/NX/chars.h | 93 + Code/Gfx/XBox/NX/gamma.cpp | 170 + Code/Gfx/XBox/NX/gamma.h | 11 + Code/Gfx/XBox/NX/grass.cpp | 405 + Code/Gfx/XBox/NX/grass.h | 154 + Code/Gfx/XBox/NX/instance.cpp | 753 + Code/Gfx/XBox/NX/instance.h | 62 + Code/Gfx/XBox/NX/material.cpp | 1180 ++ Code/Gfx/XBox/NX/material.h | 136 + Code/Gfx/XBox/NX/mesh.cpp | 1605 ++ Code/Gfx/XBox/NX/mesh.h | 159 + Code/Gfx/XBox/NX/mipmap.inl | 218 + Code/Gfx/XBox/NX/nx_init.cpp | 472 + Code/Gfx/XBox/NX/nx_init.h | 122 + Code/Gfx/XBox/NX/occlude.cpp | 504 + Code/Gfx/XBox/NX/occlude.h | 14 + Code/Gfx/XBox/NX/particles.cpp | 122 + Code/Gfx/XBox/NX/particles.h | 38 + Code/Gfx/XBox/NX/render.cpp | 2955 +++ Code/Gfx/XBox/NX/render.h | 129 + Code/Gfx/XBox/NX/scene.cpp | 492 + Code/Gfx/XBox/NX/scene.h | 87 + Code/Gfx/XBox/NX/screenfx.cpp | 1410 ++ Code/Gfx/XBox/NX/screenfx.h | 16 + Code/Gfx/XBox/NX/sprite.cpp | 497 + Code/Gfx/XBox/NX/sprite.h | 76 + Code/Gfx/XBox/NX/swizzleformat.h | 71 + Code/Gfx/XBox/NX/texture.cpp | 305 + Code/Gfx/XBox/NX/texture.h | 36 + Code/Gfx/XBox/NX/verlet.cpp | 273 + Code/Gfx/XBox/NX/verlet.h | 70 + Code/Gfx/XBox/NX/xbmemfnt.h | 5251 ++++++ Code/Gfx/XBox/p_NxGeom.cpp | 1496 ++ Code/Gfx/XBox/p_NxGeom.h | 122 + Code/Gfx/XBox/p_NxImposter.cpp | 565 + Code/Gfx/XBox/p_NxImposter.h | 62 + Code/Gfx/XBox/p_NxLight.cpp | 269 + Code/Gfx/XBox/p_NxLight.h | 55 + Code/Gfx/XBox/p_NxLightMan.cpp | 186 + Code/Gfx/XBox/p_NxLoadScreen.cpp | 416 + Code/Gfx/XBox/p_NxMesh.cpp | 274 + Code/Gfx/XBox/p_NxMesh.h | 64 + Code/Gfx/XBox/p_NxModel.cpp | 317 + Code/Gfx/XBox/p_NxModel.h | 50 + Code/Gfx/XBox/p_NxParticleRibbonTrail.cpp | 361 + Code/Gfx/XBox/p_NxParticleRibbonTrail.h | 76 + Code/Gfx/XBox/p_NxParticleShaded.cpp | 348 + Code/Gfx/XBox/p_NxParticleShaded.h | 64 + Code/Gfx/XBox/p_NxParticleStar.cpp | 342 + Code/Gfx/XBox/p_NxParticleStar.h | 70 + Code/Gfx/XBox/p_NxSprite.cpp | 137 + Code/Gfx/XBox/p_NxSprite.h | 36 + Code/Gfx/XBox/p_NxTextured3dPoly.cpp | 23 + Code/Gfx/XBox/p_NxTextured3dPoly.h | 22 + Code/Gfx/XBox/p_NxViewMan.cpp | 28 + Code/Gfx/XBox/p_NxViewport.cpp | 102 + Code/Gfx/XBox/p_NxViewport.h | 31 + Code/Gfx/XBox/p_NxWin2D.cpp | 60 + Code/Gfx/XBox/p_NxWin2D.h | 33 + Code/Gfx/XBox/p_gfxman.cpp | 160 + Code/Gfx/XBox/p_loadscreen.cpp | 392 + Code/Gfx/XBox/p_memview.cpp | 1130 ++ Code/Gfx/XBox/p_memview.h | 20 + Code/Gfx/XBox/p_nx.cpp | 1245 ++ Code/Gfx/XBox/p_nxfont.cpp | 299 + Code/Gfx/XBox/p_nxfont.h | 76 + Code/Gfx/XBox/p_nxfontman.cpp | 105 + Code/Gfx/XBox/p_nxmiscfx.cpp | 1279 ++ Code/Gfx/XBox/p_nxnewparticle.cpp | 864 + Code/Gfx/XBox/p_nxnewparticle.h | 125 + Code/Gfx/XBox/p_nxnewparticlemgr.cpp | 85 + Code/Gfx/XBox/p_nxnewparticlemgr.h | 77 + Code/Gfx/XBox/p_nxparticle.cpp | 120 + Code/Gfx/XBox/p_nxparticle.h | 27 + Code/Gfx/XBox/p_nxparticleflat.cpp | 522 + Code/Gfx/XBox/p_nxparticleflat.h | 66 + Code/Gfx/XBox/p_nxparticleglow.cpp | 375 + Code/Gfx/XBox/p_nxparticleglow.h | 74 + Code/Gfx/XBox/p_nxparticleglowribbontrail.cpp | 492 + Code/Gfx/XBox/p_nxparticleglowribbontrail.h | 71 + Code/Gfx/XBox/p_nxparticleline.cpp | 294 + Code/Gfx/XBox/p_nxparticleline.h | 71 + Code/Gfx/XBox/p_nxparticleribbon.cpp | 315 + Code/Gfx/XBox/p_nxparticleribbon.h | 68 + Code/Gfx/XBox/p_nxparticlesmooth.cpp | 405 + Code/Gfx/XBox/p_nxparticlesmooth.h | 73 + Code/Gfx/XBox/p_nxparticlesmoothribbon.cpp | 357 + Code/Gfx/XBox/p_nxparticlesmoothribbon.h | 73 + Code/Gfx/XBox/p_nxparticlesmoothstar.cpp | 367 + Code/Gfx/XBox/p_nxparticlesmoothstar.h | 70 + Code/Gfx/XBox/p_nxscene.cpp | 138 + Code/Gfx/XBox/p_nxscene.h | 40 + Code/Gfx/XBox/p_nxsector.cpp | 821 + Code/Gfx/XBox/p_nxsector.h | 49 + Code/Gfx/XBox/p_nxtexman.cpp | 67 + Code/Gfx/XBox/p_nxtexture.cpp | 698 + Code/Gfx/XBox/p_nxtexture.h | 107 + Code/Gfx/XBox/p_nxweather.cpp | 1086 ++ Code/Gfx/XBox/p_nxweather.h | 95 + Code/Gfx/baseanimcontroller.cpp | 122 + Code/Gfx/baseanimcontroller.h | 157 + Code/Gfx/bbox.cpp | 202 + Code/Gfx/bbox.h | 75 + Code/Gfx/blendchannel.cpp | 727 + Code/Gfx/blendchannel.h | 129 + Code/Gfx/camera.cpp | 325 + Code/Gfx/camera.h | 177 + Code/Gfx/debuggfx.cpp | 675 + Code/Gfx/debuggfx.h | 104 + Code/Gfx/gfxman.cpp | 264 + Code/Gfx/gfxman.h | 181 + Code/Gfx/gfxutils.cpp | 439 + Code/Gfx/gfxutils.h | 177 + Code/Gfx/nx.cpp | 1324 ++ Code/Gfx/nx.h | 310 + Code/Gfx/nxflags.h | 125 + Code/Gfx/nxparticle.cpp | 946 + Code/Gfx/nxparticle.h | 187 + Code/Gfx/nxparticlemgr.cpp | 119 + Code/Gfx/nxparticlemgr.h | 34 + Code/Gfx/nxweather.cpp | 509 + Code/Gfx/nxweather.h | 93 + Code/Gfx/shadow.cpp | 223 + Code/Gfx/shadow.h | 130 + Code/Gfx/stdafx.h | 3 + Code/Gfx/subanimcontroller.cpp | 1652 ++ Code/Gfx/subanimcontroller.h | 307 + Code/Gfx/vecfont.cpp | 449 + Code/Gfx/vecfont.h | 111 + Code/Sk/.DS_Store | Bin 0 -> 6148 bytes Code/Sk/Components/EditorCameraComponent.cpp | 900 + Code/Sk/Components/EditorCameraComponent.h | 121 + Code/Sk/Components/GoalEditorComponent.cpp | 3040 ++++ Code/Sk/Components/GoalEditorComponent.h | 362 + .../ProjectileCollisionComponent.cpp | 380 + .../Components/ProjectileCollisionComponent.h | 70 + Code/Sk/Components/RailEditorComponent.cpp | 2898 +++ Code/Sk/Components/RailEditorComponent.h | 326 + .../SkaterAdjustPhysicsComponent.cpp | 365 + .../Components/SkaterAdjustPhysicsComponent.h | 79 + .../SkaterBalanceTrickComponent.cpp | 572 + .../Components/SkaterBalanceTrickComponent.h | 97 + .../SkaterCleanupStateComponent.cpp | 137 + .../Components/SkaterCleanupStateComponent.h | 58 + .../Components/SkaterCorePhysicsComponent.cpp | 8396 +++++++++ .../Components/SkaterCorePhysicsComponent.h | 621 + Code/Sk/Components/SkaterEndRunComponent.cpp | 307 + Code/Sk/Components/SkaterEndRunComponent.h | 78 + .../SkaterFinalizePhysicsComponent.cpp | 190 + .../SkaterFinalizePhysicsComponent.h | 59 + .../SkaterFlipAndRotateComponent.cpp | 387 + .../Components/SkaterFlipAndRotateComponent.h | 80 + .../SkaterFloatingNameComponent.cpp | 196 + .../Components/SkaterFloatingNameComponent.h | 56 + Code/Sk/Components/SkaterGapComponent.cpp | 974 + Code/Sk/Components/SkaterGapComponent.h | 99 + .../SkaterLocalNetLogicComponent.cpp | 530 + .../Components/SkaterLocalNetLogicComponent.h | 79 + .../SkaterLoopingSoundComponent.cpp | 385 + .../Components/SkaterLoopingSoundComponent.h | 194 + .../SkaterMatrixQueriesComponent.cpp | 260 + .../Components/SkaterMatrixQueriesComponent.h | 63 + .../SkaterNonLocalNetLogicComponent.cpp | 1542 ++ .../SkaterNonLocalNetLogicComponent.h | 110 + .../SkaterPhysicsControlComponent.cpp | 500 + .../SkaterPhysicsControlComponent.h | 195 + .../Components/SkaterProximityComponent.cpp | 374 + Code/Sk/Components/SkaterProximityComponent.h | 75 + Code/Sk/Components/SkaterRotateComponent.cpp | 289 + Code/Sk/Components/SkaterRotateComponent.h | 79 + .../Sk/Components/SkaterRunTimerComponent.cpp | 215 + Code/Sk/Components/SkaterRunTimerComponent.h | 162 + Code/Sk/Components/SkaterScoreComponent.cpp | 290 + Code/Sk/Components/SkaterScoreComponent.h | 59 + Code/Sk/Components/SkaterSoundComponent.cpp | 350 + Code/Sk/Components/SkaterSoundComponent.h | 83 + .../Components/SkaterStancePanelComponent.cpp | 204 + .../Components/SkaterStancePanelComponent.h | 132 + Code/Sk/Components/SkaterStateComponent.cpp | 278 + Code/Sk/Components/SkaterStateComponent.h | 122 + .../SkaterStateHistoryComponent.cpp | 837 + .../Components/SkaterStateHistoryComponent.h | 173 + Code/Sk/Engine/RectFeeler.cpp | 219 + Code/Sk/Engine/RectFeeler.h | 116 + Code/Sk/Engine/SuperSector.cpp | 876 + Code/Sk/Engine/SuperSector.h | 149 + Code/Sk/Engine/contact.cpp | 112 + Code/Sk/Engine/contact.h | 54 + Code/Sk/Engine/feeler.cpp | 414 + Code/Sk/Engine/feeler.h | 197 + Code/Sk/Engine/sounds.cpp | 215 + Code/Sk/Engine/sounds.h | 103 + Code/Sk/GameNet/ExportMsg.h | 105 + Code/Sk/GameNet/GameHandler.cpp | 3624 ++++ Code/Sk/GameNet/GameMsg.h | 813 + Code/Sk/GameNet/GameNet.cpp | 8283 +++++++++ Code/Sk/GameNet/GameNet.h | 1100 ++ Code/Sk/GameNet/Lobby.cpp | 2410 +++ Code/Sk/GameNet/Lobby.h | 231 + Code/Sk/GameNet/Ngps/dnas.cpp | 699 + Code/Sk/GameNet/Ngps/p_buddy.cpp | 1243 ++ Code/Sk/GameNet/Ngps/p_buddy.h | 176 + Code/Sk/GameNet/Ngps/p_content.cpp | 1667 ++ Code/Sk/GameNet/Ngps/p_content.h | 260 + Code/Sk/GameNet/Ngps/p_ezcommon.h | 175 + Code/Sk/GameNet/Ngps/p_ezconfig.h | 86 + Code/Sk/GameNet/Ngps/p_ezmain.h | 48 + Code/Sk/GameNet/Ngps/p_libezcnf.cpp | 232 + Code/Sk/GameNet/Ngps/p_libezcnf.h | 24 + Code/Sk/GameNet/Ngps/p_netcnfif.h | 236 + Code/Sk/GameNet/Ngps/p_netconfig.cpp | 596 + Code/Sk/GameNet/Ngps/p_stats.cpp | 1453 ++ Code/Sk/GameNet/Ngps/p_stats.h | 218 + Code/Sk/GameNet/Ngps/snglue.cpp | 1387 ++ Code/Sk/GameNet/Player.cpp | 1451 ++ Code/Sk/GameNet/ServerList.cpp | 2109 +++ Code/Sk/GameNet/XBox/p_auth.cpp | 787 + Code/Sk/GameNet/XBox/p_auth.h | 169 + Code/Sk/GameNet/XBox/p_buddy.cpp | 206 + Code/Sk/GameNet/XBox/p_buddy.h | 110 + Code/Sk/GameNet/XBox/p_match.cpp | 77 + Code/Sk/GameNet/XBox/p_match.h | 72 + Code/Sk/GameNet/XBox/p_voice.cpp | 104 + Code/Sk/GameNet/XBox/p_voice.h | 86 + Code/Sk/GameNet/scriptdebugger.h | 74 + Code/Sk/Main.cpp | 848 + Code/Sk/Modules/FrontEnd/FrontEnd.cpp | 1056 ++ Code/Sk/Modules/FrontEnd/FrontEnd.h | 200 + Code/Sk/Modules/Skate/BettingGuy.cpp | 575 + Code/Sk/Modules/Skate/BettingGuy.h | 60 + Code/Sk/Modules/Skate/CATGoal.cpp | 49 + Code/Sk/Modules/Skate/CATGoal.h | 29 + Code/Sk/Modules/Skate/CompetitionGoal.cpp | 370 + Code/Sk/Modules/Skate/CompetitionGoal.h | 51 + Code/Sk/Modules/Skate/CreateATrick.cpp | 795 + Code/Sk/Modules/Skate/CreateATrick.h | 84 + Code/Sk/Modules/Skate/FilmGoal.cpp | 203 + Code/Sk/Modules/Skate/FilmGoal.h | 45 + Code/Sk/Modules/Skate/FindGapsGoal.cpp | 106 + Code/Sk/Modules/Skate/FindGapsGoal.h | 33 + Code/Sk/Modules/Skate/GameFlow.cpp | 206 + Code/Sk/Modules/Skate/GameFlow.h | 94 + Code/Sk/Modules/Skate/GameMode.cpp | 1022 ++ Code/Sk/Modules/Skate/GameMode.h | 177 + Code/Sk/Modules/Skate/Goal.cpp | 3554 ++++ Code/Sk/Modules/Skate/Goal.h | 265 + Code/Sk/Modules/Skate/GoalManager.cpp | 7132 ++++++++ Code/Sk/Modules/Skate/GoalManager.h | 521 + Code/Sk/Modules/Skate/GoalPed.cpp | 1139 ++ Code/Sk/Modules/Skate/GoalPed.h | 61 + Code/Sk/Modules/Skate/HorseGoal.cpp | 135 + Code/Sk/Modules/Skate/HorseGoal.h | 30 + Code/Sk/Modules/Skate/Minigame.cpp | 269 + Code/Sk/Modules/Skate/Minigame.h | 48 + Code/Sk/Modules/Skate/NetGoal.cpp | 163 + Code/Sk/Modules/Skate/NetGoal.h | 33 + Code/Sk/Modules/Skate/RaceGoal.cpp | 113 + Code/Sk/Modules/Skate/RaceGoal.h | 29 + Code/Sk/Modules/Skate/SkatetrisGoal.cpp | 1134 ++ Code/Sk/Modules/Skate/SkatetrisGoal.h | 71 + Code/Sk/Modules/Skate/VictoryCond.cpp | 621 + Code/Sk/Modules/Skate/VictoryCond.h | 147 + Code/Sk/Modules/Skate/competition.cpp | 842 + Code/Sk/Modules/Skate/competition.h | 156 + Code/Sk/Modules/Skate/horse.cpp | 559 + Code/Sk/Modules/Skate/horse.h | 121 + Code/Sk/Modules/Skate/score.cpp | 2855 +++ Code/Sk/Modules/Skate/score.h | 303 + Code/Sk/Modules/Skate/skate.cpp | 3525 ++++ Code/Sk/Modules/Skate/skate.h | 524 + Code/Sk/Modules/Skate/skatenet.cpp | 3649 ++++ Code/Sk/Modules/Viewer/Viewer.cpp | 961 + Code/Sk/Modules/Viewer/Viewer.h | 178 + Code/Sk/Ngps/crt0.s | 275 + Code/Sk/Objects/FollowOb.h | 92 + Code/Sk/Objects/GameObj.cpp | 223 + Code/Sk/Objects/GameObj.h | 43 + Code/Sk/Objects/MovingObject.cpp | 871 + Code/Sk/Objects/MovingObject.h | 159 + Code/Sk/Objects/PathMan.cpp | 269 + Code/Sk/Objects/PathMan.h | 93 + Code/Sk/Objects/PathOb.cpp | 1046 ++ Code/Sk/Objects/PathOb.h | 166 + Code/Sk/Objects/PlayerProfileManager.cpp | 769 + Code/Sk/Objects/PlayerProfileManager.h | 128 + Code/Sk/Objects/SkaterButton.cpp | 142 + Code/Sk/Objects/SkaterButton.h | 81 + Code/Sk/Objects/SkaterPad.h | 112 + Code/Sk/Objects/SkaterProfile.cpp | 1044 ++ Code/Sk/Objects/SkaterProfile.h | 181 + Code/Sk/Objects/SkaterTricks.cpp | 240 + Code/Sk/Objects/SkaterTricks.h | 103 + Code/Sk/Objects/TrickObject.cpp | 1578 ++ Code/Sk/Objects/TrickObject.h | 206 + Code/Sk/Objects/ViewerObj.cpp | 876 + Code/Sk/Objects/ViewerObj.h | 128 + Code/Sk/Objects/car.cpp | 576 + Code/Sk/Objects/car.h | 97 + Code/Sk/Objects/crown.cpp | 242 + Code/Sk/Objects/crown.h | 107 + Code/Sk/Objects/cutscenedetails.cpp | 5461 ++++++ Code/Sk/Objects/cutscenedetails.h | 217 + Code/Sk/Objects/emitter.cpp | 442 + Code/Sk/Objects/emitter.h | 113 + Code/Sk/Objects/followob.cpp | 159 + Code/Sk/Objects/gap.cpp | 726 + Code/Sk/Objects/gap.h | 211 + Code/Sk/Objects/manual.cpp | 616 + Code/Sk/Objects/manual.h | 145 + Code/Sk/Objects/moviecam.cpp | 759 + Code/Sk/Objects/moviecam.h | 121 + Code/Sk/Objects/moviedetails.cpp | 959 + Code/Sk/Objects/moviedetails.h | 216 + Code/Sk/Objects/navigation.cpp | 756 + Code/Sk/Objects/navigation.h | 154 + Code/Sk/Objects/objecthook.cpp | 204 + Code/Sk/Objects/objecthook.h | 108 + Code/Sk/Objects/ped.cpp | 140 + Code/Sk/Objects/ped.h | 44 + Code/Sk/Objects/proxim.cpp | 619 + Code/Sk/Objects/proxim.h | 135 + Code/Sk/Objects/rail.cpp | 3154 ++++ Code/Sk/Objects/rail.h | 209 + Code/Sk/Objects/records.cpp | 639 + Code/Sk/Objects/records.h | 225 + Code/Sk/Objects/restart.cpp | 237 + Code/Sk/Objects/restart.h | 76 + Code/Sk/Objects/skater.cpp | 2638 +++ Code/Sk/Objects/skater.h | 612 + Code/Sk/Objects/skatercam.cpp | 1646 ++ Code/Sk/Objects/skatercam.h | 229 + Code/Sk/Objects/skatercareer.cpp | 832 + Code/Sk/Objects/skatercareer.h | 163 + Code/Sk/Objects/skaterflags.h | 61 + Code/Sk/Objects/skaterpad.cpp | 520 + Code/Sk/ParkEditor/EdRail.cpp | 395 + Code/Sk/ParkEditor/EdRail.h | 174 + Code/Sk/ParkEditor/LoadPath.txt | 157 + Code/Sk/ParkEditor2/EdMap.cpp | 5184 ++++++ Code/Sk/ParkEditor2/EdMap.h | 745 + Code/Sk/ParkEditor2/GapManager.cpp | 410 + Code/Sk/ParkEditor2/GapManager.h | 107 + Code/Sk/ParkEditor2/ParkEd.cpp | 3814 ++++ Code/Sk/ParkEditor2/ParkEd.h | 454 + Code/Sk/ParkEditor2/ParkGen.cpp | 2959 +++ Code/Sk/ParkEditor2/ParkGen.h | 497 + Code/Sk/ParkEditor2/clipboard.cpp | 997 ++ Code/Sk/ParkEditor2/clipboard.h | 105 + Code/Sk/Scripting/cfuncs.cpp | 14850 ++++++++++++++++ Code/Sk/Scripting/cfuncs.h | 576 + Code/Sk/Scripting/ftables.cpp | 2188 +++ Code/Sk/Scripting/ftables.h | 74 + Code/Sk/Scripting/gs_file.cpp | 263 + Code/Sk/Scripting/gs_file.h | 22 + Code/Sk/Scripting/gs_init.cpp | 128 + Code/Sk/Scripting/gs_init.h | 12 + Code/Sk/Scripting/mcfuncs.cpp | 4897 +++++ Code/Sk/Scripting/mcfuncs.h | 107 + Code/Sk/Scripting/nodearray.cpp | 1298 ++ Code/Sk/Scripting/nodearray.h | 64 + Code/Sk/Scripting/skfuncs.cpp | 7494 ++++++++ Code/Sk/Scripting/skfuncs.h | 305 + Code/Sk/heap_sizes.h | 138 + Code/Sk/language.h | 47 + Code/Sk/ngc/crt0.s | 175 + Code/Sk/ngc/defs.s | 226 + Code/Sk/product_codes.h | 20 + Code/Sk/template.h | 58 + Code/Sys/Config/NGC/p_config.cpp | 78 + Code/Sys/Config/NGPS/p_config.cpp | 226 + Code/Sys/Config/Win32/p_config.cpp | 15 + Code/Sys/Config/XBox/p_config.cpp | 70 + Code/Sys/Config/config.cpp | 265 + Code/Sys/Config/config.h | 125 + Code/Sys/File/AsyncFilesys.cpp | 854 + Code/Sys/File/AsyncFilesys.h | 324 + Code/Sys/File/AsyncTypes.h | 91 + Code/Sys/File/FileLibrary.cpp | 610 + Code/Sys/File/FileLibrary.h | 117 + Code/Sys/File/PRE.cpp | 1620 ++ Code/Sys/File/PRE.h | 239 + Code/Sys/File/XBox/hed.cpp | 133 + Code/Sys/File/XBox/hed.h | 30 + Code/Sys/File/XBox/p_AsyncFilesys.cpp | 662 + Code/Sys/File/XBox/p_AsyncFilesys.h | 135 + Code/Sys/File/XBox/p_filesys.cpp | 591 + Code/Sys/File/XBox/p_pre.cpp | 1157 ++ Code/Sys/File/XBox/p_streamer.cpp | 770 + Code/Sys/File/XBox/p_streamer.h | 398 + Code/Sys/File/filesys.h | 104 + Code/Sys/File/memfile.h | 29 + Code/Sys/File/ngc/hed.cpp | 319 + Code/Sys/File/ngc/hed.h | 47 + Code/Sys/File/ngc/p_AsyncFilesys.cpp | 78 + Code/Sys/File/ngc/p_asyncFilesys.h | 65 + Code/Sys/File/ngc/p_filesys.cpp | 572 + Code/Sys/File/ngc/p_pre.cpp | 1038 ++ Code/Sys/File/ngps/FileIO/FIleIO_IOP.h | 137 + Code/Sys/File/ngps/FileIO/FileIO.h | 192 + Code/Sys/File/ngps/FileIO/Makefile | 37 + Code/Sys/File/ngps/FileIO/PathDefs | 16 + Code/Sys/File/ngps/FileIO/command.c | 1617 ++ Code/Sys/File/ngps/FileIO/start.c | 47 + Code/Sys/File/ngps/hed.cpp | 392 + Code/Sys/File/ngps/hed.h | 58 + Code/Sys/File/ngps/p_AsyncFilesys.cpp | 1290 ++ Code/Sys/File/ngps/p_AsyncFilesys.h | 256 + Code/Sys/File/ngps/p_filesys.cpp | 1878 ++ Code/Sys/File/pip.cpp | 1022 ++ Code/Sys/File/pip.h | 40 + Code/Sys/McMan.h | 297 + Code/Sys/Mem/CompactPool.cpp | 195 + Code/Sys/Mem/CompactPool.h | 137 + Code/Sys/Mem/PoolManager.cpp | 91 + Code/Sys/Mem/PoolManager.h | 45 + Code/Sys/Mem/Poolable.cpp | 31 + Code/Sys/Mem/Poolable.h | 243 + Code/Sys/Mem/alloc.cpp | 441 + Code/Sys/Mem/alloc.h | 478 + Code/Sys/Mem/handle.h | 187 + Code/Sys/Mem/heap.cpp | 1155 ++ Code/Sys/Mem/heap.h | 100 + Code/Sys/Mem/memdbg.h | 79 + Code/Sys/Mem/memman.cpp | 1722 ++ Code/Sys/Mem/memman.h | 361 + Code/Sys/Mem/memptr.h | 591 + Code/Sys/Mem/memtest.cpp | 263 + Code/Sys/Mem/memtest.h | 67 + Code/Sys/Mem/pile.cpp | 203 + Code/Sys/Mem/pile.h | 102 + Code/Sys/Mem/pool.cpp | 209 + Code/Sys/Mem/pool.h | 95 + Code/Sys/Mem/region.cpp | 310 + Code/Sys/Mem/region.h | 144 + Code/Sys/MemCard/NGPS/p_McMan.cpp | 768 + Code/Sys/MemCard/XBox/p_McMan.cpp | 1031 ++ Code/Sys/MemCard/ngc/p_McMan.cpp | 1072 ++ Code/Sys/Profiler.cpp | 434 + Code/Sys/Profiler.h | 192 + Code/Sys/Replay/NGC/p_replay.cpp | 147 + Code/Sys/Replay/NGPS/p_replay.cpp | 81 + Code/Sys/Replay/Win32/p_replay.cpp | 79 + Code/Sys/Replay/XBox/p_replay.cpp | 82 + Code/Sys/Replay/replay.cpp | 4606 +++++ Code/Sys/Replay/replay.h | 402 + Code/Sys/SIO/NGPS/p_keyboard.cpp | 463 + Code/Sys/SIO/XBox/p_keyboard.cpp | 115 + Code/Sys/SIO/XBox/p_siodev.cpp | 760 + Code/Sys/SIO/XBox/p_sioman.cpp | 310 + Code/Sys/SIO/keyboard.h | 94 + Code/Sys/SIO/ngc/p_keyboard.cpp | 390 + Code/Sys/SIO/ngc/siodev.cpp | 719 + Code/Sys/SIO/ngc/sioman.cpp | 309 + Code/Sys/SIO/siodev.cpp | 703 + Code/Sys/SIO/sioman.cpp | 558 + Code/Sys/Win32/p_timer.cpp | 120 + Code/Sys/XBox/p_timer.cpp | 498 + Code/Sys/demo.cpp | 221 + Code/Sys/demo.h | 55 + Code/Sys/ngc/p_anim.cpp | 533 + Code/Sys/ngc/p_anim.h | 141 + Code/Sys/ngc/p_anim_core.s | 3160 ++++ Code/Sys/ngc/p_aram.cpp | 479 + Code/Sys/ngc/p_aram.h | 32 + Code/Sys/ngc/p_assert.h | 3 + Code/Sys/ngc/p_atomic.cpp | 620 + Code/Sys/ngc/p_atomic.h | 113 + Code/Sys/ngc/p_bbox.cpp | 43 + Code/Sys/ngc/p_bbox.h | 14 + Code/Sys/ngc/p_buffer.cpp | 194 + Code/Sys/ngc/p_buffer.h | 26 + Code/Sys/ngc/p_camera.cpp | 455 + Code/Sys/ngc/p_camera.h | 64 + Code/Sys/ngc/p_clump.cpp | 1065 ++ Code/Sys/ngc/p_clump.h | 113 + Code/Sys/ngc/p_collision.cpp | 573 + Code/Sys/ngc/p_collision.h | 66 + Code/Sys/ngc/p_debugfont.cpp | 291 + Code/Sys/ngc/p_debugfont.h | 27 + Code/Sys/ngc/p_display.cpp | 2051 +++ Code/Sys/ngc/p_display.h | 55 + Code/Sys/ngc/p_dl.cpp | 261 + Code/Sys/ngc/p_dl.h | 79 + Code/Sys/ngc/p_dlman.cpp | 118 + Code/Sys/ngc/p_dlman.h | 27 + Code/Sys/ngc/p_dma.cpp | 167 + Code/Sys/ngc/p_dma.h | 13 + Code/Sys/ngc/p_dvd.cpp | 1359 ++ Code/Sys/ngc/p_dvd.h | 67 + Code/Sys/ngc/p_file.cpp | 125 + Code/Sys/ngc/p_file.h | 88 + Code/Sys/ngc/p_font.cpp | 521 + Code/Sys/ngc/p_frame.cpp | 56 + Code/Sys/ngc/p_frame.h | 23 + Code/Sys/ngc/p_gx.cpp | 4823 +++++ Code/Sys/ngc/p_gx.h | 884 + Code/Sys/ngc/p_hashid.cpp | 170 + Code/Sys/ngc/p_hashid.h | 14 + Code/Sys/ngc/p_hw.h | 197 + Code/Sys/ngc/p_hwinit.cpp | 1704 ++ Code/Sys/ngc/p_hwpad.cpp | 441 + Code/Sys/ngc/p_hwpad.h | 124 + Code/Sys/ngc/p_light.cpp | 170 + Code/Sys/ngc/p_light.h | 53 + Code/Sys/ngc/p_material.cpp | 781 + Code/Sys/ngc/p_material.h | 92 + Code/Sys/ngc/p_matman.cpp | 495 + Code/Sys/ngc/p_matman.h | 53 + Code/Sys/ngc/p_matrix.cpp | 351 + Code/Sys/ngc/p_matrix.h | 95 + Code/Sys/ngc/p_model.cpp | 716 + Code/Sys/ngc/p_model.h | 105 + Code/Sys/ngc/p_particle.cpp | 679 + Code/Sys/ngc/p_particle.h | 147 + Code/Sys/ngc/p_ppcwgpipe.h | 93 + Code/Sys/ngc/p_prim.cpp | 761 + Code/Sys/ngc/p_prim.h | 49 + Code/Sys/ngc/p_profile.cpp | 287 + Code/Sys/ngc/p_profile.h | 40 + Code/Sys/ngc/p_quat.cpp | 92 + Code/Sys/ngc/p_quat.h | 27 + Code/Sys/ngc/p_reftypes.h | 213 + Code/Sys/ngc/p_render.cpp | 646 + Code/Sys/ngc/p_render.h | 62 + Code/Sys/ngc/p_scene.cpp | 248 + Code/Sys/ngc/p_scene.h | 94 + Code/Sys/ngc/p_screenshot.cpp | 347 + Code/Sys/ngc/p_screenshot.h | 49 + Code/Sys/ngc/p_slerp.cpp | 159 + Code/Sys/ngc/p_slerp.h | 27 + Code/Sys/ngc/p_tex.cpp | 170 + Code/Sys/ngc/p_tex.h | 40 + Code/Sys/ngc/p_texman.cpp | 508 + Code/Sys/ngc/p_texman.h | 40 + Code/Sys/ngc/p_timer.cpp | 606 + Code/Sys/ngc/p_triangle.cpp | 157 + Code/Sys/ngc/p_triangle.h | 40 + Code/Sys/ngc/p_vector.cpp | 133 + Code/Sys/ngc/p_vector.h | 38 + Code/Sys/ngps/p_timer.cpp | 637 + Code/Sys/siodev.h | 331 + Code/Sys/sioman.h | 150 + Code/Sys/sys.cpp | 80 + Code/Sys/sys.h | 85 + Code/Sys/timer.cpp | 72 + Code/Sys/timer.h | 229 + Code/standard.cpp | 147 + Code/template.cpp | 77 + Code/template.h | 72 + 1309 files changed, 588460 insertions(+) create mode 100644 Code/Core/Debug.h create mode 100644 Code/Core/Debug/Assert.cpp create mode 100644 Code/Core/Debug/Checks.h create mode 100644 Code/Core/Debug/Debug.cpp create mode 100644 Code/Core/Debug/Mem_stat.h create mode 100644 Code/Core/Debug/Messages.h create mode 100644 Code/Core/Debug/Module.h create mode 100644 Code/Core/Debug/NGPS/P_debug.cpp create mode 100644 Code/Core/Debug/Project.h create mode 100644 Code/Core/Debug/Signatrs.h create mode 100644 Code/Core/Debug/Wn32/P_debug.cpp create mode 100644 Code/Core/Debug/XBox/p_debug.cpp create mode 100644 Code/Core/Debug/log.cpp create mode 100644 Code/Core/Debug/ngc/P_debug.cpp create mode 100644 Code/Core/Defines.h create mode 100644 Code/Core/DynamicTable.cpp create mode 100644 Code/Core/DynamicTable.h create mode 100644 Code/Core/HashTable.h create mode 100644 Code/Core/List.h create mode 100644 Code/Core/List/Head.h create mode 100644 Code/Core/List/Node.h create mode 100644 Code/Core/List/Search.h create mode 100644 Code/Core/List/list.cpp create mode 100644 Code/Core/LookupTable.cpp create mode 100644 Code/Core/LookupTable.h create mode 100644 Code/Core/Math/Xbox/sse.h create mode 100644 Code/Core/Math/geometry.cpp create mode 100644 Code/Core/Math/geometry.h create mode 100644 Code/Core/Math/math.cpp create mode 100644 Code/Core/Math/math.h create mode 100644 Code/Core/Math/matrix.cpp create mode 100644 Code/Core/Math/matrix.h create mode 100644 Code/Core/Math/matrix.inl create mode 100644 Code/Core/Math/quat.h create mode 100644 Code/Core/Math/quat.inl create mode 100644 Code/Core/Math/rect.h create mode 100644 Code/Core/Math/rot90.cpp create mode 100644 Code/Core/Math/rot90.h create mode 100644 Code/Core/Math/slerp.cpp create mode 100644 Code/Core/Math/slerp.h create mode 100644 Code/Core/Math/vector.cpp create mode 100644 Code/Core/Math/vector.h create mode 100644 Code/Core/Math/vector.inl create mode 100644 Code/Core/String/CString.cpp create mode 100644 Code/Core/String/CString.h create mode 100644 Code/Core/String/stringutils.cpp create mode 100644 Code/Core/String/stringutils.h create mode 100644 Code/Core/StringHashTable.h create mode 100644 Code/Core/Support/Lock.h create mode 100644 Code/Core/Support/Ref.h create mode 100644 Code/Core/Support/class.cpp create mode 100644 Code/Core/Support/class.h create mode 100644 Code/Core/Support/support.h create mode 100644 Code/Core/Task.h create mode 100644 Code/Core/Task/Hook.h create mode 100644 Code/Core/Task/List.h create mode 100644 Code/Core/Task/Stack.h create mode 100644 Code/Core/Task/Task.cpp create mode 100644 Code/Core/Task/Task.h create mode 100644 Code/Core/Task/Tlist.cpp create mode 100644 Code/Core/Task/Tstack.cpp create mode 100644 Code/Core/Thread/ngc/t_thread.cpp create mode 100644 Code/Core/Thread/ngc/t_thread.h create mode 100644 Code/Core/Thread/ngps/t_thread.cpp create mode 100644 Code/Core/Thread/ngps/t_thread.h create mode 100644 Code/Core/Thread/wn32/t_thread.cpp create mode 100644 Code/Core/Thread/wn32/t_thread.h create mode 100644 Code/Core/TimestampedFlag.h create mode 100644 Code/Core/compress.cpp create mode 100644 Code/Core/compress.h create mode 100644 Code/Core/crc.cpp create mode 100644 Code/Core/crc.h create mode 100644 Code/Core/flags.h create mode 100644 Code/Core/glue.h create mode 100644 Code/Core/log.h create mode 100644 Code/Core/macros.h create mode 100644 Code/Core/math.h create mode 100644 Code/Core/singleton.h create mode 100644 Code/Core/support.h create mode 100644 Code/Core/thread.h create mode 100644 Code/GameFlow.txt create mode 100644 Code/Gel/AssMan/AssMan.cpp create mode 100644 Code/Gel/AssMan/AssMan.h create mode 100644 Code/Gel/AssMan/NodeArrayAsset.cpp create mode 100644 Code/Gel/AssMan/NodeArrayAsset.h create mode 100644 Code/Gel/AssMan/animasset.cpp create mode 100644 Code/Gel/AssMan/animasset.h create mode 100644 Code/Gel/AssMan/asset.cpp create mode 100644 Code/Gel/AssMan/asset.h create mode 100644 Code/Gel/AssMan/assettypes.h create mode 100644 Code/Gel/AssMan/cutsceneasset.cpp create mode 100644 Code/Gel/AssMan/cutsceneasset.h create mode 100644 Code/Gel/AssMan/refasset.cpp create mode 100644 Code/Gel/AssMan/refasset.h create mode 100644 Code/Gel/AssMan/skeletonasset.cpp create mode 100644 Code/Gel/AssMan/skeletonasset.h create mode 100644 Code/Gel/AssMan/skinasset.cpp create mode 100644 Code/Gel/AssMan/skinasset.h create mode 100644 Code/Gel/Collision/BatchTriColl.cpp create mode 100644 Code/Gel/Collision/BatchTriColl.h create mode 100644 Code/Gel/Collision/CollCache.cpp create mode 100644 Code/Gel/Collision/CollCache.h create mode 100644 Code/Gel/Collision/CollEnums.h create mode 100644 Code/Gel/Collision/CollTriData.cpp create mode 100644 Code/Gel/Collision/CollTriData.h create mode 100644 Code/Gel/Collision/Collision.cpp create mode 100644 Code/Gel/Collision/Collision.h create mode 100644 Code/Gel/Collision/MovCollMan.cpp create mode 100644 Code/Gel/Collision/MovCollMan.h create mode 100644 Code/Gel/Components/BouncyComponent.h create mode 100644 Code/Gel/Components/CameraComponent.cpp create mode 100644 Code/Gel/Components/CameraComponent.h create mode 100644 Code/Gel/Components/CameraLookAroundComponent.cpp create mode 100644 Code/Gel/Components/CameraLookAroundComponent.h create mode 100644 Code/Gel/Components/CameraUtil.cpp create mode 100644 Code/Gel/Components/CameraUtil.h create mode 100644 Code/Gel/Components/CollideAndDieComponent.cpp create mode 100644 Code/Gel/Components/CollideAndDieComponent.h create mode 100644 Code/Gel/Components/FloatingLabelComponent.cpp create mode 100644 Code/Gel/Components/FloatingLabelComponent.h create mode 100644 Code/Gel/Components/GunslingerCameraLookAroundComponent.cpp create mode 100644 Code/Gel/Components/GunslingerWalkCameraComponent.cpp create mode 100644 Code/Gel/Components/GunslingerWalkComponent.cpp create mode 100644 Code/Gel/Components/HorseCameraComponent.cpp create mode 100644 Code/Gel/Components/HorseCameraComponent.h create mode 100644 Code/Gel/Components/HorseComponent.cpp create mode 100644 Code/Gel/Components/HorseComponent.h create mode 100644 Code/Gel/Components/InputComponent.cpp create mode 100644 Code/Gel/Components/InputComponent.h create mode 100644 Code/Gel/Components/ModelLightUpdateComponent.cpp create mode 100644 Code/Gel/Components/ModelLightUpdateComponent.h create mode 100644 Code/Gel/Components/MovableContactComponent.cpp create mode 100644 Code/Gel/Components/MovableContactComponent.h create mode 100644 Code/Gel/Components/NearComponent.cpp create mode 100644 Code/Gel/Components/NearComponent.h create mode 100644 Code/Gel/Components/NodeArrayComponent.cpp create mode 100644 Code/Gel/Components/NodeArrayComponent.h create mode 100644 Code/Gel/Components/ObjectHookManagerComponent.cpp create mode 100644 Code/Gel/Components/ObjectHookManagerComponent.h create mode 100644 Code/Gel/Components/ParticleComponent.cpp create mode 100644 Code/Gel/Components/ParticleComponent.h create mode 100644 Code/Gel/Components/PedLogicComponent.cpp create mode 100644 Code/Gel/Components/PedLogicComponent.h create mode 100644 Code/Gel/Components/ProximTriggerComponent.cpp create mode 100644 Code/Gel/Components/ProximTriggerComponent.h create mode 100644 Code/Gel/Components/RailManagerComponent.cpp create mode 100644 Code/Gel/Components/RailManagerComponent.h create mode 100644 Code/Gel/Components/RibbonComponent.cpp create mode 100644 Code/Gel/Components/RibbonComponent.h create mode 100644 Code/Gel/Components/RiderComponent.cpp create mode 100644 Code/Gel/Components/RiderComponent.h create mode 100644 Code/Gel/Components/SetDisplayMatrixComponent.cpp create mode 100644 Code/Gel/Components/SetDisplayMatrixComponent.h create mode 100644 Code/Gel/Components/SkaterCameraComponent.cpp create mode 100644 Code/Gel/Components/SkaterCameraComponent.h create mode 100644 Code/Gel/Components/SkitchComponent.cpp create mode 100644 Code/Gel/Components/SkitchComponent.h create mode 100644 Code/Gel/Components/StaticVehicleComponent.cpp create mode 100644 Code/Gel/Components/StaticVehicleComponent.h create mode 100644 Code/Gel/Components/StatsManagerComponent.cpp create mode 100644 Code/Gel/Components/StatsManagerComponent.h create mode 100644 Code/Gel/Components/StreamComponent.cpp create mode 100644 Code/Gel/Components/StreamComponent.h create mode 100644 Code/Gel/Components/SuspendComponent.cpp create mode 100644 Code/Gel/Components/SuspendComponent.h create mode 100644 Code/Gel/Components/TriggerComponent.cpp create mode 100644 Code/Gel/Components/TriggerComponent.h create mode 100644 Code/Gel/Components/VehicleSoundComponent.cpp create mode 100644 Code/Gel/Components/VehicleSoundComponent.h create mode 100644 Code/Gel/Components/VelocityComponent.cpp create mode 100644 Code/Gel/Components/VelocityComponent.h create mode 100644 Code/Gel/Components/VibrationComponent.cpp create mode 100644 Code/Gel/Components/VibrationComponent.h create mode 100644 Code/Gel/Components/WalkCameraComponent.cpp create mode 100644 Code/Gel/Components/WalkCameraComponent.h create mode 100644 Code/Gel/Components/WalkComponent.cpp create mode 100644 Code/Gel/Components/WalkComponent.h create mode 100644 Code/Gel/Components/WalkHangUtil.cpp create mode 100644 Code/Gel/Components/WalkLadderUtil.cpp create mode 100644 Code/Gel/Components/animationcomponent.cpp create mode 100644 Code/Gel/Components/animationcomponent.h create mode 100644 Code/Gel/Components/avoidcomponent.cpp create mode 100644 Code/Gel/Components/avoidcomponent.h create mode 100644 Code/Gel/Components/bouncycomponent.cpp create mode 100644 Code/Gel/Components/carphysicscomponent.cpp create mode 100644 Code/Gel/Components/carphysicscomponent.h create mode 100644 Code/Gel/Components/collisioncomponent.cpp create mode 100644 Code/Gel/Components/collisioncomponent.h create mode 100644 Code/Gel/Components/emptycomponent.cpp create mode 100644 Code/Gel/Components/emptycomponent.h create mode 100644 Code/Gel/Components/lockobjcomponent.cpp create mode 100644 Code/Gel/Components/lockobjcomponent.h create mode 100644 Code/Gel/Components/modelcomponent.cpp create mode 100644 Code/Gel/Components/modelcomponent.h create mode 100644 Code/Gel/Components/motioncomponent.cpp create mode 100644 Code/Gel/Components/motioncomponent.h create mode 100644 Code/Gel/Components/rigidbodycomponent.cpp create mode 100644 Code/Gel/Components/rigidbodycomponent.h create mode 100644 Code/Gel/Components/shadowcomponent.cpp create mode 100644 Code/Gel/Components/shadowcomponent.h create mode 100644 Code/Gel/Components/skeletoncomponent.cpp create mode 100644 Code/Gel/Components/skeletoncomponent.h create mode 100644 Code/Gel/Components/soundcomponent.cpp create mode 100644 Code/Gel/Components/soundcomponent.h create mode 100644 Code/Gel/Components/specialitemcomponent.cpp create mode 100644 Code/Gel/Components/specialitemcomponent.h create mode 100644 Code/Gel/Components/trickcomponent.cpp create mode 100644 Code/Gel/Components/trickcomponent.h create mode 100644 Code/Gel/Components/vehiclecameracomponent.cpp create mode 100644 Code/Gel/Components/vehiclecameracomponent.h create mode 100644 Code/Gel/Components/vehiclecomponent.cpp create mode 100644 Code/Gel/Components/vehiclecomponent.h create mode 100644 Code/Gel/Components/weaponcomponent.cpp create mode 100644 Code/Gel/Components/weaponcomponent.h create mode 100644 Code/Gel/Environment/terrain.cpp create mode 100644 Code/Gel/Environment/terrain.h create mode 100644 Code/Gel/Event.h create mode 100644 Code/Gel/Input/InpMan.cpp create mode 100644 Code/Gel/Input/inpserv.cpp create mode 100644 Code/Gel/MainLoop/Mainloop.cpp create mode 100644 Code/Gel/Module/modman.cpp create mode 100644 Code/Gel/Module/module.cpp create mode 100644 Code/Gel/Movies/Movies.cpp create mode 100644 Code/Gel/Movies/Movies.h create mode 100644 Code/Gel/Movies/Ngps/audiodec.cpp create mode 100644 Code/Gel/Movies/Ngps/audiodec.h create mode 100644 Code/Gel/Movies/Ngps/defs.h create mode 100644 Code/Gel/Movies/Ngps/disp.cpp create mode 100644 Code/Gel/Movies/Ngps/disp.h create mode 100644 Code/Gel/Movies/Ngps/p_movies.cpp create mode 100644 Code/Gel/Movies/Ngps/p_movies.h create mode 100644 Code/Gel/Movies/Ngps/read.cpp create mode 100644 Code/Gel/Movies/Ngps/readbuf.cpp create mode 100644 Code/Gel/Movies/Ngps/readbuf.h create mode 100644 Code/Gel/Movies/Ngps/strfile.cpp create mode 100644 Code/Gel/Movies/Ngps/strfile.h create mode 100644 Code/Gel/Movies/Ngps/vibuf.cpp create mode 100644 Code/Gel/Movies/Ngps/vibuf.h create mode 100644 Code/Gel/Movies/Ngps/videodec.cpp create mode 100644 Code/Gel/Movies/Ngps/videodec.h create mode 100644 Code/Gel/Movies/Ngps/vobuf.cpp create mode 100644 Code/Gel/Movies/Ngps/vobuf.h create mode 100644 Code/Gel/Movies/Xbox/p_movies.cpp create mode 100644 Code/Gel/Movies/Xbox/p_movies.h create mode 100644 Code/Gel/Movies/ngc/p_movies.cpp create mode 100644 Code/Gel/Movies/ngc/p_movies.h create mode 100644 Code/Gel/Music/Ngps/Bgm/PathDefs create mode 100644 Code/Gel/Music/Ngps/Bgm/bgm_com.c create mode 100644 Code/Gel/Music/Ngps/Bgm/bgm_entr.c create mode 100644 Code/Gel/Music/Ngps/Bgm/bgm_i.h create mode 100644 Code/Gel/Music/Ngps/Bgm/bgm_play.c create mode 100644 Code/Gel/Music/Ngps/Bgm/bgm_r2s.s create mode 100644 Code/Gel/Music/Ngps/Bgm/bgm_r2sm.c create mode 100644 Code/Gel/Music/Ngps/Bgm/makefile create mode 100644 Code/Gel/Music/Ngps/Pcm/Makefile create mode 100644 Code/Gel/Music/Ngps/Pcm/PathDefs create mode 100644 Code/Gel/Music/Ngps/Pcm/pcm.h create mode 100644 Code/Gel/Music/Ngps/Pcm/pcm_com.c create mode 100644 Code/Gel/Music/Ngps/Pcm/pcm_ent.c create mode 100644 Code/Gel/Music/Ngps/Pcm/pcm_sound.c create mode 100644 Code/Gel/Music/Ngps/Pcm/pcmiop.h create mode 100644 Code/Gel/Music/Ngps/p_music.cpp create mode 100644 Code/Gel/Music/Ngps/p_music.h create mode 100644 Code/Gel/Music/Xbox/p_adpcmfilestream.cpp create mode 100644 Code/Gel/Music/Xbox/p_adpcmfilestream.h create mode 100644 Code/Gel/Music/Xbox/p_music.cpp create mode 100644 Code/Gel/Music/Xbox/p_music.h create mode 100644 Code/Gel/Music/Xbox/p_soundtrack.cpp create mode 100644 Code/Gel/Music/Xbox/p_soundtrack.h create mode 100644 Code/Gel/Music/Xbox/p_wmafilestream.cpp create mode 100644 Code/Gel/Music/Xbox/p_wmafilestream.h create mode 100644 Code/Gel/Music/music.cpp create mode 100644 Code/Gel/Music/music.h create mode 100644 Code/Gel/Music/ngc/bgm/bgm_com.c create mode 100644 Code/Gel/Music/ngc/bgm/bgm_entr.c create mode 100644 Code/Gel/Music/ngc/bgm/bgm_i.h create mode 100644 Code/Gel/Music/ngc/bgm/bgm_play.c create mode 100644 Code/Gel/Music/ngc/bgm/bgm_r2s.s create mode 100644 Code/Gel/Music/ngc/bgm/bgm_r2sm.c create mode 100644 Code/Gel/Music/ngc/divx/AUDSimpleAudio.cpp create mode 100644 Code/Gel/Music/ngc/divx/AUDSimpleAudio.h create mode 100644 Code/Gel/Music/ngc/divx/AUDSimplePlayer.cpp create mode 100644 Code/Gel/Music/ngc/divx/AUDSimplePlayer.h create mode 100644 Code/Gel/Music/ngc/p_music.cpp create mode 100644 Code/Gel/Music/ngc/p_music.h create mode 100644 Code/Gel/Music/ngc/pcm/pcm.h create mode 100644 Code/Gel/Music/ngc/pcm/pcm_com.c create mode 100644 Code/Gel/Music/ngc/pcm/pcm_ent.c create mode 100644 Code/Gel/Music/ngc/pcm/pcmiop.h create mode 100644 Code/Gel/Net/App/netapp.cpp create mode 100644 Code/Gel/Net/Client/netclnt.cpp create mode 100644 Code/Gel/Net/Client/netclnt.h create mode 100644 Code/Gel/Net/Dispatch/netdsptch.cpp create mode 100644 Code/Gel/Net/Handler/nethndlr.cpp create mode 100644 Code/Gel/Net/Server/netserv.cpp create mode 100644 Code/Gel/Net/Server/netserv.h create mode 100644 Code/Gel/Net/net.cpp create mode 100644 Code/Gel/Net/net.h create mode 100644 Code/Gel/Net/netconn.cpp create mode 100644 Code/Gel/ObjPtr.h create mode 100644 Code/Gel/Object/Event.cpp create mode 100644 Code/Gel/Object/ObjPtr.cpp create mode 100644 Code/Gel/Object/RefCounted.cpp create mode 100644 Code/Gel/Object/basecomponent.cpp create mode 100644 Code/Gel/Object/basecomponent.h create mode 100644 Code/Gel/Object/compositeobject.cpp create mode 100644 Code/Gel/Object/compositeobject.h create mode 100644 Code/Gel/Object/compositeobjectmanager.cpp create mode 100644 Code/Gel/Object/compositeobjectmanager.h create mode 100644 Code/Gel/Object/object.cpp create mode 100644 Code/Gel/Object/objman.cpp create mode 100644 Code/Gel/Object/objsearch.cpp create mode 100644 Code/Gel/Object/objtrack.cpp create mode 100644 Code/Gel/Prefs/Prefs.cpp create mode 100644 Code/Gel/Prefs/Prefs.h create mode 100644 Code/Gel/RefCounted.h create mode 100644 Code/Gel/Scripting/array.cpp create mode 100644 Code/Gel/Scripting/array.h create mode 100644 Code/Gel/Scripting/checksum.cpp create mode 100644 Code/Gel/Scripting/checksum.h create mode 100644 Code/Gel/Scripting/component.cpp create mode 100644 Code/Gel/Scripting/component.h create mode 100644 Code/Gel/Scripting/debugger.cpp create mode 100644 Code/Gel/Scripting/debugger.h create mode 100644 Code/Gel/Scripting/eval.cpp create mode 100644 Code/Gel/Scripting/eval.h create mode 100644 Code/Gel/Scripting/file.cpp create mode 100644 Code/Gel/Scripting/file.h create mode 100644 Code/Gel/Scripting/init.cpp create mode 100644 Code/Gel/Scripting/init.h create mode 100644 Code/Gel/Scripting/parse.cpp create mode 100644 Code/Gel/Scripting/parse.h create mode 100644 Code/Gel/Scripting/script.cpp create mode 100644 Code/Gel/Scripting/script.h create mode 100644 Code/Gel/Scripting/scriptcache.cpp create mode 100644 Code/Gel/Scripting/scriptcache.h create mode 100644 Code/Gel/Scripting/scriptdefs.h create mode 100644 Code/Gel/Scripting/skiptoken.cpp create mode 100644 Code/Gel/Scripting/string.cpp create mode 100644 Code/Gel/Scripting/string.h create mode 100644 Code/Gel/Scripting/struct.cpp create mode 100644 Code/Gel/Scripting/struct.h create mode 100644 Code/Gel/Scripting/symboltable.cpp create mode 100644 Code/Gel/Scripting/symboltable.h create mode 100644 Code/Gel/Scripting/symboltype.cpp create mode 100644 Code/Gel/Scripting/symboltype.h create mode 100644 Code/Gel/Scripting/tokens.cpp create mode 100644 Code/Gel/Scripting/tokens.h create mode 100644 Code/Gel/Scripting/utils.cpp create mode 100644 Code/Gel/Scripting/utils.h create mode 100644 Code/Gel/Scripting/vecpair.cpp create mode 100644 Code/Gel/Scripting/vecpair.h create mode 100644 Code/Gel/Scripting/win32functions.cpp create mode 100644 Code/Gel/SoundFX/NGPS/p_sfx.cpp create mode 100644 Code/Gel/SoundFX/NGPS/p_sfx.h create mode 100644 Code/Gel/SoundFX/Xbox/p_sfx.cpp create mode 100644 Code/Gel/SoundFX/Xbox/p_sfx.h create mode 100644 Code/Gel/SoundFX/Xbox/skate5fx.h create mode 100644 Code/Gel/SoundFX/ngc/p_sfx.cpp create mode 100644 Code/Gel/SoundFX/ngc/p_sfx.h create mode 100644 Code/Gel/SoundFX/soundfx.cpp create mode 100644 Code/Gel/SoundFX/soundfx.h create mode 100644 Code/Gel/inpman.h create mode 100644 Code/Gel/mainloop.h create mode 100644 Code/Gel/modman.h create mode 100644 Code/Gel/module.h create mode 100644 Code/Gel/object.h create mode 100644 Code/Gel/objman.h create mode 100644 Code/Gel/objsearch.h create mode 100644 Code/Gel/objserv.h create mode 100644 Code/Gel/objtrack.h create mode 100644 Code/Gfx/2D/BlurEffect.cpp create mode 100644 Code/Gfx/2D/BlurEffect.h create mode 100644 Code/Gfx/2D/Element3d.cpp create mode 100644 Code/Gfx/2D/Element3d.h create mode 100644 Code/Gfx/2D/Menu2.cpp create mode 100644 Code/Gfx/2D/Menu2.h create mode 100644 Code/Gfx/2D/ScreenElemMan.cpp create mode 100644 Code/Gfx/2D/ScreenElemMan.h create mode 100644 Code/Gfx/2D/ScreenElement2.cpp create mode 100644 Code/Gfx/2D/ScreenElement2.h create mode 100644 Code/Gfx/2D/ScrollingMenu.cpp create mode 100644 Code/Gfx/2D/ScrollingMenu.h create mode 100644 Code/Gfx/2D/SpriteElement.cpp create mode 100644 Code/Gfx/2D/SpriteElement.h create mode 100644 Code/Gfx/2D/TextElement.cpp create mode 100644 Code/Gfx/2D/TextElement.h create mode 100644 Code/Gfx/2D/Window.cpp create mode 100644 Code/Gfx/2D/Window.h create mode 100644 Code/Gfx/AnimController.cpp create mode 100644 Code/Gfx/AnimController.h create mode 100644 Code/Gfx/BonedAnim.cpp create mode 100644 Code/Gfx/BonedAnim.h create mode 100644 Code/Gfx/BonedAnimTypes.h create mode 100644 Code/Gfx/CasUtils.cpp create mode 100644 Code/Gfx/CasUtils.h create mode 100644 Code/Gfx/CustomAnimKey.cpp create mode 100644 Code/Gfx/CustomAnimKey.h create mode 100644 Code/Gfx/FaceMassage.cpp create mode 100644 Code/Gfx/FaceMassage.h create mode 100644 Code/Gfx/FaceTexture.cpp create mode 100644 Code/Gfx/FaceTexture.h create mode 100644 Code/Gfx/Image/ImageBasic.h create mode 100644 Code/Gfx/ModelAppearance.cpp create mode 100644 Code/Gfx/ModelAppearance.h create mode 100644 Code/Gfx/ModelBuilder.cpp create mode 100644 Code/Gfx/ModelBuilder.h create mode 100644 Code/Gfx/NGC/NX/anim.cpp create mode 100644 Code/Gfx/NGC/NX/anim.h create mode 100644 Code/Gfx/NGC/NX/chars.cpp create mode 100644 Code/Gfx/NGC/NX/chars.h create mode 100644 Code/Gfx/NGC/NX/geomnode.cpp create mode 100644 Code/Gfx/NGC/NX/geomnode.h create mode 100644 Code/Gfx/NGC/NX/grass.cpp create mode 100644 Code/Gfx/NGC/NX/grass.h create mode 100644 Code/Gfx/NGC/NX/import.cpp create mode 100644 Code/Gfx/NGC/NX/import.h create mode 100644 Code/Gfx/NGC/NX/instance.cpp create mode 100644 Code/Gfx/NGC/NX/instance.h create mode 100644 Code/Gfx/NGC/NX/light.cpp create mode 100644 Code/Gfx/NGC/NX/light.h create mode 100644 Code/Gfx/NGC/NX/line.cpp create mode 100644 Code/Gfx/NGC/NX/line.h create mode 100644 Code/Gfx/NGC/NX/material.cpp create mode 100644 Code/Gfx/NGC/NX/material.h create mode 100644 Code/Gfx/NGC/NX/mesh.cpp create mode 100644 Code/Gfx/NGC/NX/mesh.h create mode 100644 Code/Gfx/NGC/NX/nx_init.cpp create mode 100644 Code/Gfx/NGC/NX/nx_init.h create mode 100644 Code/Gfx/NGC/NX/occlude.cpp create mode 100644 Code/Gfx/NGC/NX/occlude.h create mode 100644 Code/Gfx/NGC/NX/particles.cpp create mode 100644 Code/Gfx/NGC/NX/particles.h create mode 100644 Code/Gfx/NGC/NX/render.cpp create mode 100644 Code/Gfx/NGC/NX/render.h create mode 100644 Code/Gfx/NGC/NX/scene.cpp create mode 100644 Code/Gfx/NGC/NX/scene.h create mode 100644 Code/Gfx/NGC/NX/sprite.cpp create mode 100644 Code/Gfx/NGC/NX/sprite.h create mode 100644 Code/Gfx/NGC/NX/texture.cpp create mode 100644 Code/Gfx/NGC/NX/texture.h create mode 100644 Code/Gfx/NGC/NX/types.h create mode 100644 Code/Gfx/NGC/blur.cpp create mode 100644 Code/Gfx/NGC/p_NxGeom.cpp create mode 100644 Code/Gfx/NGC/p_NxGeom.h create mode 100644 Code/Gfx/NGC/p_NxImposter.cpp create mode 100644 Code/Gfx/NGC/p_NxImposter.h create mode 100644 Code/Gfx/NGC/p_NxLightMan.cpp create mode 100644 Code/Gfx/NGC/p_NxMesh.cpp create mode 100644 Code/Gfx/NGC/p_NxMesh.h create mode 100644 Code/Gfx/NGC/p_NxSprite.cpp create mode 100644 Code/Gfx/NGC/p_NxSprite.h create mode 100644 Code/Gfx/NGC/p_NxTextured3dPoly.cpp create mode 100644 Code/Gfx/NGC/p_NxTextured3dPoly.h create mode 100644 Code/Gfx/NGC/p_NxViewport.cpp create mode 100644 Code/Gfx/NGC/p_NxViewport.h create mode 100644 Code/Gfx/NGC/p_NxWin2D.cpp create mode 100644 Code/Gfx/NGC/p_gfxman.cpp create mode 100644 Code/Gfx/NGC/p_loadscreen.cpp create mode 100644 Code/Gfx/NGC/p_memview.cpp create mode 100644 Code/Gfx/NGC/p_memview.h create mode 100644 Code/Gfx/NGC/p_nx.cpp create mode 100644 Code/Gfx/NGC/p_nxanimcache.cpp create mode 100644 Code/Gfx/NGC/p_nxanimcache.h create mode 100644 Code/Gfx/NGC/p_nxfont.cpp create mode 100644 Code/Gfx/NGC/p_nxfont.h create mode 100644 Code/Gfx/NGC/p_nxfontman.cpp create mode 100644 Code/Gfx/NGC/p_nxlight.cpp create mode 100644 Code/Gfx/NGC/p_nxlight.h create mode 100644 Code/Gfx/NGC/p_nxloadscreen.cpp create mode 100644 Code/Gfx/NGC/p_nxmiscfx.cpp create mode 100644 Code/Gfx/NGC/p_nxmodel.cpp create mode 100644 Code/Gfx/NGC/p_nxmodel.h create mode 100644 Code/Gfx/NGC/p_nxnewparticle.cpp create mode 100644 Code/Gfx/NGC/p_nxnewparticle.h create mode 100644 Code/Gfx/NGC/p_nxnewparticlemgr.cpp create mode 100644 Code/Gfx/NGC/p_nxnewparticlemgr.h create mode 100644 Code/Gfx/NGC/p_nxparticle.cpp create mode 100644 Code/Gfx/NGC/p_nxparticle.h create mode 100644 Code/Gfx/NGC/p_nxparticleflat.cpp create mode 100644 Code/Gfx/NGC/p_nxparticleflat.h create mode 100644 Code/Gfx/NGC/p_nxparticleglow.cpp create mode 100644 Code/Gfx/NGC/p_nxparticleglow.h create mode 100644 Code/Gfx/NGC/p_nxparticleglowribbontrail.cpp create mode 100644 Code/Gfx/NGC/p_nxparticleglowribbontrail.h create mode 100644 Code/Gfx/NGC/p_nxparticleline.cpp create mode 100644 Code/Gfx/NGC/p_nxparticleline.h create mode 100644 Code/Gfx/NGC/p_nxparticleribbon.cpp create mode 100644 Code/Gfx/NGC/p_nxparticleribbon.h create mode 100644 Code/Gfx/NGC/p_nxparticleribbontrail.cpp create mode 100644 Code/Gfx/NGC/p_nxparticleribbontrail.h create mode 100644 Code/Gfx/NGC/p_nxparticleshaded.cpp create mode 100644 Code/Gfx/NGC/p_nxparticleshaded.h create mode 100644 Code/Gfx/NGC/p_nxparticlesmooth.cpp create mode 100644 Code/Gfx/NGC/p_nxparticlesmooth.h create mode 100644 Code/Gfx/NGC/p_nxparticlesmoothribbon.cpp create mode 100644 Code/Gfx/NGC/p_nxparticlesmoothribbon.h create mode 100644 Code/Gfx/NGC/p_nxparticlesmoothstar.cpp create mode 100644 Code/Gfx/NGC/p_nxparticlesmoothstar.h create mode 100644 Code/Gfx/NGC/p_nxparticlestar.cpp create mode 100644 Code/Gfx/NGC/p_nxparticlestar.h create mode 100644 Code/Gfx/NGC/p_nxscene.cpp create mode 100644 Code/Gfx/NGC/p_nxscene.h create mode 100644 Code/Gfx/NGC/p_nxsector.cpp create mode 100644 Code/Gfx/NGC/p_nxsector.h create mode 100644 Code/Gfx/NGC/p_nxtexman.cpp create mode 100644 Code/Gfx/NGC/p_nxtexture.cpp create mode 100644 Code/Gfx/NGC/p_nxtexture.h create mode 100644 Code/Gfx/NGC/p_nxviewman.cpp create mode 100644 Code/Gfx/NGC/p_nxweather.cpp create mode 100644 Code/Gfx/NGC/p_nxweather.h create mode 100644 Code/Gfx/NGC/p_nxwin2D.h create mode 100644 Code/Gfx/NGPS/NX/Makefile create mode 100644 Code/Gfx/NGPS/NX/asmdma.dsm create mode 100644 Code/Gfx/NGPS/NX/asmdma.h create mode 100644 Code/Gfx/NGPS/NX/chars.cpp create mode 100644 Code/Gfx/NGPS/NX/chars.h create mode 100644 Code/Gfx/NGPS/NX/dma.cpp create mode 100644 Code/Gfx/NGPS/NX/dma.h create mode 100644 Code/Gfx/NGPS/NX/dmacalls.cpp create mode 100644 Code/Gfx/NGPS/NX/dmacalls.h create mode 100644 Code/Gfx/NGPS/NX/fx.cpp create mode 100644 Code/Gfx/NGPS/NX/fx.h create mode 100644 Code/Gfx/NGPS/NX/geomnode.cpp create mode 100644 Code/Gfx/NGPS/NX/geomnode.h create mode 100644 Code/Gfx/NGPS/NX/gif.cpp create mode 100644 Code/Gfx/NGPS/NX/gif.h create mode 100644 Code/Gfx/NGPS/NX/group.cpp create mode 100644 Code/Gfx/NGPS/NX/group.h create mode 100644 Code/Gfx/NGPS/NX/gs.cpp create mode 100644 Code/Gfx/NGPS/NX/gs.h create mode 100644 Code/Gfx/NGPS/NX/immediate.cpp create mode 100644 Code/Gfx/NGPS/NX/immediate.h create mode 100644 Code/Gfx/NGPS/NX/instance.cpp create mode 100644 Code/Gfx/NGPS/NX/instance.h create mode 100644 Code/Gfx/NGPS/NX/interrupts.cpp create mode 100644 Code/Gfx/NGPS/NX/interrupts.h create mode 100644 Code/Gfx/NGPS/NX/light.cpp create mode 100644 Code/Gfx/NGPS/NX/light.h create mode 100644 Code/Gfx/NGPS/NX/line.cpp create mode 100644 Code/Gfx/NGPS/NX/line.h create mode 100644 Code/Gfx/NGPS/NX/loadscreen.cpp create mode 100644 Code/Gfx/NGPS/NX/loadscreen.h create mode 100644 Code/Gfx/NGPS/NX/maintest.cpp create mode 100644 Code/Gfx/NGPS/NX/material.cpp create mode 100644 Code/Gfx/NGPS/NX/material.h create mode 100644 Code/Gfx/NGPS/NX/mesh.cpp create mode 100644 Code/Gfx/NGPS/NX/mesh.h create mode 100644 Code/Gfx/NGPS/NX/mikemath.cpp create mode 100644 Code/Gfx/NGPS/NX/mikemath.h create mode 100644 Code/Gfx/NGPS/NX/nx_init.cpp create mode 100644 Code/Gfx/NGPS/NX/nx_init.h create mode 100644 Code/Gfx/NGPS/NX/occlude.cpp create mode 100644 Code/Gfx/NGPS/NX/occlude.h create mode 100644 Code/Gfx/NGPS/NX/pcrtc.cpp create mode 100644 Code/Gfx/NGPS/NX/pcrtc.h create mode 100644 Code/Gfx/NGPS/NX/render.cpp create mode 100644 Code/Gfx/NGPS/NX/render.h create mode 100644 Code/Gfx/NGPS/NX/resource.cpp create mode 100644 Code/Gfx/NGPS/NX/resource.h create mode 100644 Code/Gfx/NGPS/NX/scene.cpp create mode 100644 Code/Gfx/NGPS/NX/scene.h create mode 100644 Code/Gfx/NGPS/NX/sprite.cpp create mode 100644 Code/Gfx/NGPS/NX/sprite.h create mode 100644 Code/Gfx/NGPS/NX/switches.h create mode 100644 Code/Gfx/NGPS/NX/texture.cpp create mode 100644 Code/Gfx/NGPS/NX/texture.h create mode 100644 Code/Gfx/NGPS/NX/texturemem.cpp create mode 100644 Code/Gfx/NGPS/NX/texturemem.h create mode 100644 Code/Gfx/NGPS/NX/types.h create mode 100644 Code/Gfx/NGPS/NX/vif.cpp create mode 100644 Code/Gfx/NGPS/NX/vif.h create mode 100644 Code/Gfx/NGPS/NX/vu0code.dsm create mode 100644 Code/Gfx/NGPS/NX/vu0code.h create mode 100644 Code/Gfx/NGPS/NX/vu1.cpp create mode 100644 Code/Gfx/NGPS/NX/vu1.h create mode 100644 Code/Gfx/NGPS/NX/vu1/defs.vsm create mode 100644 Code/Gfx/NGPS/NX/vu1/jumptab.vsm create mode 100644 Code/Gfx/NGPS/NX/vu1/main.vsm create mode 100644 Code/Gfx/NGPS/NX/vu1/newvu1code.dsm create mode 100644 Code/Gfx/NGPS/NX/vu1/newvu1code.h create mode 100644 Code/Gfx/NGPS/NX/vu1/particle.vsm create mode 100644 Code/Gfx/NGPS/NX/vu1code.dsm create mode 100644 Code/Gfx/NGPS/NX/vu1code.h create mode 100644 Code/Gfx/NGPS/NX/vu1context.cpp create mode 100644 Code/Gfx/NGPS/NX/vu1context.h create mode 100644 Code/Gfx/NGPS/PaletteGen.cpp create mode 100644 Code/Gfx/NGPS/PaletteGen.h create mode 100644 Code/Gfx/NGPS/p_NxAnimCache.cpp create mode 100644 Code/Gfx/NGPS/p_NxAnimCache.h create mode 100644 Code/Gfx/NGPS/p_NxFont.cpp create mode 100644 Code/Gfx/NGPS/p_NxFont.h create mode 100644 Code/Gfx/NGPS/p_NxFontMan.cpp create mode 100644 Code/Gfx/NGPS/p_NxGeom.cpp create mode 100644 Code/Gfx/NGPS/p_NxGeom.h create mode 100644 Code/Gfx/NGPS/p_NxImposter.cpp create mode 100644 Code/Gfx/NGPS/p_NxImposter.h create mode 100644 Code/Gfx/NGPS/p_NxLight.cpp create mode 100644 Code/Gfx/NGPS/p_NxLight.h create mode 100644 Code/Gfx/NGPS/p_NxLightMan.cpp create mode 100644 Code/Gfx/NGPS/p_NxLightMan.h create mode 100644 Code/Gfx/NGPS/p_NxLoadScreen.cpp create mode 100644 Code/Gfx/NGPS/p_NxMesh.cpp create mode 100644 Code/Gfx/NGPS/p_NxMesh.h create mode 100644 Code/Gfx/NGPS/p_NxModel.cpp create mode 100644 Code/Gfx/NGPS/p_NxModel.h create mode 100644 Code/Gfx/NGPS/p_NxScene.cpp create mode 100644 Code/Gfx/NGPS/p_NxScene.h create mode 100644 Code/Gfx/NGPS/p_NxSector.cpp create mode 100644 Code/Gfx/NGPS/p_NxSector.h create mode 100644 Code/Gfx/NGPS/p_NxSprite.cpp create mode 100644 Code/Gfx/NGPS/p_NxSprite.h create mode 100644 Code/Gfx/NGPS/p_NxTexMan.cpp create mode 100644 Code/Gfx/NGPS/p_NxTexture.cpp create mode 100644 Code/Gfx/NGPS/p_NxTexture.h create mode 100644 Code/Gfx/NGPS/p_NxTextured3dPoly.cpp create mode 100644 Code/Gfx/NGPS/p_NxTextured3dPoly.h create mode 100644 Code/Gfx/NGPS/p_NxViewMan.cpp create mode 100644 Code/Gfx/NGPS/p_NxViewport.cpp create mode 100644 Code/Gfx/NGPS/p_NxViewport.h create mode 100644 Code/Gfx/NGPS/p_NxWin2D.cpp create mode 100644 Code/Gfx/NGPS/p_NxWin2D.h create mode 100644 Code/Gfx/NGPS/p_gfxman.cpp create mode 100644 Code/Gfx/NGPS/p_memview.cpp create mode 100644 Code/Gfx/NGPS/p_memview.h create mode 100644 Code/Gfx/NGPS/p_nx.cpp create mode 100644 Code/Gfx/NGPS/p_nxmiscfx.cpp create mode 100644 Code/Gfx/NGPS/p_nxnewparticle.cpp create mode 100644 Code/Gfx/NGPS/p_nxnewparticle.h create mode 100644 Code/Gfx/NGPS/p_nxnewparticlemgr.cpp create mode 100644 Code/Gfx/NGPS/p_nxnewparticlemgr.h create mode 100644 Code/Gfx/NGPS/p_nxparticle.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticle.h create mode 100644 Code/Gfx/NGPS/p_nxparticleflat.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticleflat.h create mode 100644 Code/Gfx/NGPS/p_nxparticleglow.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticleglow.h create mode 100644 Code/Gfx/NGPS/p_nxparticleglowribbontrail.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticleglowribbontrail.h create mode 100644 Code/Gfx/NGPS/p_nxparticleline.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticleline.h create mode 100644 Code/Gfx/NGPS/p_nxparticlenewflat.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticlenewflat.h create mode 100644 Code/Gfx/NGPS/p_nxparticleribbon.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticleribbon.h create mode 100644 Code/Gfx/NGPS/p_nxparticleribbontrail.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticleribbontrail.h create mode 100644 Code/Gfx/NGPS/p_nxparticleshaded.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticleshaded.h create mode 100644 Code/Gfx/NGPS/p_nxparticlesmooth.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticlesmooth.h create mode 100644 Code/Gfx/NGPS/p_nxparticlesmoothribbon.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticlesmoothribbon.h create mode 100644 Code/Gfx/NGPS/p_nxparticlesmoothstar.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticlesmoothstar.h create mode 100644 Code/Gfx/NGPS/p_nxparticlestar.cpp create mode 100644 Code/Gfx/NGPS/p_nxparticlestar.h create mode 100644 Code/Gfx/NGPS/p_nxweather.cpp create mode 100644 Code/Gfx/NGPS/p_nxweather.h create mode 100644 Code/Gfx/NxAnimCache.cpp create mode 100644 Code/Gfx/NxAnimCache.h create mode 100644 Code/Gfx/NxFont.cpp create mode 100644 Code/Gfx/NxFont.h create mode 100644 Code/Gfx/NxFontMan.cpp create mode 100644 Code/Gfx/NxFontMan.h create mode 100644 Code/Gfx/NxGeom.cpp create mode 100644 Code/Gfx/NxGeom.h create mode 100644 Code/Gfx/NxHierarchy.h create mode 100644 Code/Gfx/NxImposter.cpp create mode 100644 Code/Gfx/NxImposter.h create mode 100644 Code/Gfx/NxLight.cpp create mode 100644 Code/Gfx/NxLight.h create mode 100644 Code/Gfx/NxLightMan.cpp create mode 100644 Code/Gfx/NxLightMan.h create mode 100644 Code/Gfx/NxLoadScreen.cpp create mode 100644 Code/Gfx/NxLoadScreen.h create mode 100644 Code/Gfx/NxMesh.cpp create mode 100644 Code/Gfx/NxMesh.h create mode 100644 Code/Gfx/NxMiscFX.cpp create mode 100644 Code/Gfx/NxMiscFX.h create mode 100644 Code/Gfx/NxModel.cpp create mode 100644 Code/Gfx/NxModel.h create mode 100644 Code/Gfx/NxNewParticle.cpp create mode 100644 Code/Gfx/NxNewParticle.h create mode 100644 Code/Gfx/NxNewParticleMgr.cpp create mode 100644 Code/Gfx/NxNewParticleMgr.h create mode 100644 Code/Gfx/NxQuickAnim.cpp create mode 100644 Code/Gfx/NxQuickAnim.h create mode 100644 Code/Gfx/NxScene.cpp create mode 100644 Code/Gfx/NxScene.h create mode 100644 Code/Gfx/NxSector.cpp create mode 100644 Code/Gfx/NxSector.h create mode 100644 Code/Gfx/NxSkinComponent.cpp create mode 100644 Code/Gfx/NxSkinComponent.h create mode 100644 Code/Gfx/NxSprite.cpp create mode 100644 Code/Gfx/NxSprite.h create mode 100644 Code/Gfx/NxTexMan.cpp create mode 100644 Code/Gfx/NxTexMan.h create mode 100644 Code/Gfx/NxTexture.cpp create mode 100644 Code/Gfx/NxTexture.h create mode 100644 Code/Gfx/NxTextured3dPoly.cpp create mode 100644 Code/Gfx/NxTextured3dPoly.h create mode 100644 Code/Gfx/NxViewMan.cpp create mode 100644 Code/Gfx/NxViewMan.h create mode 100644 Code/Gfx/NxViewport.cpp create mode 100644 Code/Gfx/NxViewport.h create mode 100644 Code/Gfx/NxWin2D.cpp create mode 100644 Code/Gfx/NxWin2D.h create mode 100644 Code/Gfx/Pose.h create mode 100644 Code/Gfx/Skeleton.cpp create mode 100644 Code/Gfx/Skeleton.h create mode 100644 Code/Gfx/XBox/NX/BillboardScreenAlignedVS.vsh create mode 100644 Code/Gfx/XBox/NX/ParticleFlatVS.vsh create mode 100644 Code/Gfx/XBox/NX/ParticleNewFlatPointSpriteVS.vsh create mode 100644 Code/Gfx/XBox/NX/ParticleNewFlatVS.vsh create mode 100644 Code/Gfx/XBox/NX/PixelShader0.psh create mode 100644 Code/Gfx/XBox/NX/PixelShader0IVA.psh create mode 100644 Code/Gfx/XBox/NX/PixelShader1.psh create mode 100644 Code/Gfx/XBox/NX/PixelShader2.psh create mode 100644 Code/Gfx/XBox/NX/PixelShader3.psh create mode 100644 Code/Gfx/XBox/NX/PixelShader4.psh create mode 100644 Code/Gfx/XBox/NX/PixelShader5.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderBrighten.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderBrightenIVA.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderBumpWater.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderFocusBlur.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderFocusIntegrate.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderFocusLookupIntegrate.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderNULL.psh create mode 100644 Code/Gfx/XBox/NX/PixelShaderPointSprite.psh create mode 100644 Code/Gfx/XBox/NX/PixelShader_ShadowBuffer.psh create mode 100644 Code/Gfx/XBox/NX/ShadowBufferStaticGeomPS.psh create mode 100644 Code/Gfx/XBox/NX/ShadowBufferStaticGeomVS.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_1Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_2Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_3Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_SBPassThru.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_UVTransform.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_SBPassThru.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_UVTransform.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_SBPassThru.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_UVTransform.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight_SBPassThru.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_1Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_2Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_3Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_4Weight.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVertexShader0.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVertexShader1.vsh create mode 100644 Code/Gfx/XBox/NX/WeightedMeshVertexShader_SBWrite.vsh create mode 100644 Code/Gfx/XBox/NX/anim.cpp create mode 100644 Code/Gfx/XBox/NX/anim.h create mode 100644 Code/Gfx/XBox/NX/anim_vertdefs.h create mode 100644 Code/Gfx/XBox/NX/billboard.cpp create mode 100644 Code/Gfx/XBox/NX/billboard.h create mode 100644 Code/Gfx/XBox/NX/chars.cpp create mode 100644 Code/Gfx/XBox/NX/chars.h create mode 100644 Code/Gfx/XBox/NX/gamma.cpp create mode 100644 Code/Gfx/XBox/NX/gamma.h create mode 100644 Code/Gfx/XBox/NX/grass.cpp create mode 100644 Code/Gfx/XBox/NX/grass.h create mode 100644 Code/Gfx/XBox/NX/instance.cpp create mode 100644 Code/Gfx/XBox/NX/instance.h create mode 100644 Code/Gfx/XBox/NX/material.cpp create mode 100644 Code/Gfx/XBox/NX/material.h create mode 100644 Code/Gfx/XBox/NX/mesh.cpp create mode 100644 Code/Gfx/XBox/NX/mesh.h create mode 100644 Code/Gfx/XBox/NX/mipmap.inl create mode 100644 Code/Gfx/XBox/NX/nx_init.cpp create mode 100644 Code/Gfx/XBox/NX/nx_init.h create mode 100644 Code/Gfx/XBox/NX/occlude.cpp create mode 100644 Code/Gfx/XBox/NX/occlude.h create mode 100644 Code/Gfx/XBox/NX/particles.cpp create mode 100644 Code/Gfx/XBox/NX/particles.h create mode 100644 Code/Gfx/XBox/NX/render.cpp create mode 100644 Code/Gfx/XBox/NX/render.h create mode 100644 Code/Gfx/XBox/NX/scene.cpp create mode 100644 Code/Gfx/XBox/NX/scene.h create mode 100644 Code/Gfx/XBox/NX/screenfx.cpp create mode 100644 Code/Gfx/XBox/NX/screenfx.h create mode 100644 Code/Gfx/XBox/NX/sprite.cpp create mode 100644 Code/Gfx/XBox/NX/sprite.h create mode 100644 Code/Gfx/XBox/NX/swizzleformat.h create mode 100644 Code/Gfx/XBox/NX/texture.cpp create mode 100644 Code/Gfx/XBox/NX/texture.h create mode 100644 Code/Gfx/XBox/NX/verlet.cpp create mode 100644 Code/Gfx/XBox/NX/verlet.h create mode 100644 Code/Gfx/XBox/NX/xbmemfnt.h create mode 100644 Code/Gfx/XBox/p_NxGeom.cpp create mode 100644 Code/Gfx/XBox/p_NxGeom.h create mode 100644 Code/Gfx/XBox/p_NxImposter.cpp create mode 100644 Code/Gfx/XBox/p_NxImposter.h create mode 100644 Code/Gfx/XBox/p_NxLight.cpp create mode 100644 Code/Gfx/XBox/p_NxLight.h create mode 100644 Code/Gfx/XBox/p_NxLightMan.cpp create mode 100644 Code/Gfx/XBox/p_NxLoadScreen.cpp create mode 100644 Code/Gfx/XBox/p_NxMesh.cpp create mode 100644 Code/Gfx/XBox/p_NxMesh.h create mode 100644 Code/Gfx/XBox/p_NxModel.cpp create mode 100644 Code/Gfx/XBox/p_NxModel.h create mode 100644 Code/Gfx/XBox/p_NxParticleRibbonTrail.cpp create mode 100644 Code/Gfx/XBox/p_NxParticleRibbonTrail.h create mode 100644 Code/Gfx/XBox/p_NxParticleShaded.cpp create mode 100644 Code/Gfx/XBox/p_NxParticleShaded.h create mode 100644 Code/Gfx/XBox/p_NxParticleStar.cpp create mode 100644 Code/Gfx/XBox/p_NxParticleStar.h create mode 100644 Code/Gfx/XBox/p_NxSprite.cpp create mode 100644 Code/Gfx/XBox/p_NxSprite.h create mode 100644 Code/Gfx/XBox/p_NxTextured3dPoly.cpp create mode 100644 Code/Gfx/XBox/p_NxTextured3dPoly.h create mode 100644 Code/Gfx/XBox/p_NxViewMan.cpp create mode 100644 Code/Gfx/XBox/p_NxViewport.cpp create mode 100644 Code/Gfx/XBox/p_NxViewport.h create mode 100644 Code/Gfx/XBox/p_NxWin2D.cpp create mode 100644 Code/Gfx/XBox/p_NxWin2D.h create mode 100644 Code/Gfx/XBox/p_gfxman.cpp create mode 100644 Code/Gfx/XBox/p_loadscreen.cpp create mode 100644 Code/Gfx/XBox/p_memview.cpp create mode 100644 Code/Gfx/XBox/p_memview.h create mode 100644 Code/Gfx/XBox/p_nx.cpp create mode 100644 Code/Gfx/XBox/p_nxfont.cpp create mode 100644 Code/Gfx/XBox/p_nxfont.h create mode 100644 Code/Gfx/XBox/p_nxfontman.cpp create mode 100644 Code/Gfx/XBox/p_nxmiscfx.cpp create mode 100644 Code/Gfx/XBox/p_nxnewparticle.cpp create mode 100644 Code/Gfx/XBox/p_nxnewparticle.h create mode 100644 Code/Gfx/XBox/p_nxnewparticlemgr.cpp create mode 100644 Code/Gfx/XBox/p_nxnewparticlemgr.h create mode 100644 Code/Gfx/XBox/p_nxparticle.cpp create mode 100644 Code/Gfx/XBox/p_nxparticle.h create mode 100644 Code/Gfx/XBox/p_nxparticleflat.cpp create mode 100644 Code/Gfx/XBox/p_nxparticleflat.h create mode 100644 Code/Gfx/XBox/p_nxparticleglow.cpp create mode 100644 Code/Gfx/XBox/p_nxparticleglow.h create mode 100644 Code/Gfx/XBox/p_nxparticleglowribbontrail.cpp create mode 100644 Code/Gfx/XBox/p_nxparticleglowribbontrail.h create mode 100644 Code/Gfx/XBox/p_nxparticleline.cpp create mode 100644 Code/Gfx/XBox/p_nxparticleline.h create mode 100644 Code/Gfx/XBox/p_nxparticleribbon.cpp create mode 100644 Code/Gfx/XBox/p_nxparticleribbon.h create mode 100644 Code/Gfx/XBox/p_nxparticlesmooth.cpp create mode 100644 Code/Gfx/XBox/p_nxparticlesmooth.h create mode 100644 Code/Gfx/XBox/p_nxparticlesmoothribbon.cpp create mode 100644 Code/Gfx/XBox/p_nxparticlesmoothribbon.h create mode 100644 Code/Gfx/XBox/p_nxparticlesmoothstar.cpp create mode 100644 Code/Gfx/XBox/p_nxparticlesmoothstar.h create mode 100644 Code/Gfx/XBox/p_nxscene.cpp create mode 100644 Code/Gfx/XBox/p_nxscene.h create mode 100644 Code/Gfx/XBox/p_nxsector.cpp create mode 100644 Code/Gfx/XBox/p_nxsector.h create mode 100644 Code/Gfx/XBox/p_nxtexman.cpp create mode 100644 Code/Gfx/XBox/p_nxtexture.cpp create mode 100644 Code/Gfx/XBox/p_nxtexture.h create mode 100644 Code/Gfx/XBox/p_nxweather.cpp create mode 100644 Code/Gfx/XBox/p_nxweather.h create mode 100644 Code/Gfx/baseanimcontroller.cpp create mode 100644 Code/Gfx/baseanimcontroller.h create mode 100644 Code/Gfx/bbox.cpp create mode 100644 Code/Gfx/bbox.h create mode 100644 Code/Gfx/blendchannel.cpp create mode 100644 Code/Gfx/blendchannel.h create mode 100644 Code/Gfx/camera.cpp create mode 100644 Code/Gfx/camera.h create mode 100644 Code/Gfx/debuggfx.cpp create mode 100644 Code/Gfx/debuggfx.h create mode 100644 Code/Gfx/gfxman.cpp create mode 100644 Code/Gfx/gfxman.h create mode 100644 Code/Gfx/gfxutils.cpp create mode 100644 Code/Gfx/gfxutils.h create mode 100644 Code/Gfx/nx.cpp create mode 100644 Code/Gfx/nx.h create mode 100644 Code/Gfx/nxflags.h create mode 100644 Code/Gfx/nxparticle.cpp create mode 100644 Code/Gfx/nxparticle.h create mode 100644 Code/Gfx/nxparticlemgr.cpp create mode 100644 Code/Gfx/nxparticlemgr.h create mode 100644 Code/Gfx/nxweather.cpp create mode 100644 Code/Gfx/nxweather.h create mode 100644 Code/Gfx/shadow.cpp create mode 100644 Code/Gfx/shadow.h create mode 100644 Code/Gfx/stdafx.h create mode 100644 Code/Gfx/subanimcontroller.cpp create mode 100644 Code/Gfx/subanimcontroller.h create mode 100644 Code/Gfx/vecfont.cpp create mode 100644 Code/Gfx/vecfont.h create mode 100644 Code/Sk/.DS_Store create mode 100644 Code/Sk/Components/EditorCameraComponent.cpp create mode 100644 Code/Sk/Components/EditorCameraComponent.h create mode 100644 Code/Sk/Components/GoalEditorComponent.cpp create mode 100644 Code/Sk/Components/GoalEditorComponent.h create mode 100644 Code/Sk/Components/ProjectileCollisionComponent.cpp create mode 100644 Code/Sk/Components/ProjectileCollisionComponent.h create mode 100644 Code/Sk/Components/RailEditorComponent.cpp create mode 100644 Code/Sk/Components/RailEditorComponent.h create mode 100644 Code/Sk/Components/SkaterAdjustPhysicsComponent.cpp create mode 100644 Code/Sk/Components/SkaterAdjustPhysicsComponent.h create mode 100644 Code/Sk/Components/SkaterBalanceTrickComponent.cpp create mode 100644 Code/Sk/Components/SkaterBalanceTrickComponent.h create mode 100644 Code/Sk/Components/SkaterCleanupStateComponent.cpp create mode 100644 Code/Sk/Components/SkaterCleanupStateComponent.h create mode 100644 Code/Sk/Components/SkaterCorePhysicsComponent.cpp create mode 100644 Code/Sk/Components/SkaterCorePhysicsComponent.h create mode 100644 Code/Sk/Components/SkaterEndRunComponent.cpp create mode 100644 Code/Sk/Components/SkaterEndRunComponent.h create mode 100644 Code/Sk/Components/SkaterFinalizePhysicsComponent.cpp create mode 100644 Code/Sk/Components/SkaterFinalizePhysicsComponent.h create mode 100644 Code/Sk/Components/SkaterFlipAndRotateComponent.cpp create mode 100644 Code/Sk/Components/SkaterFlipAndRotateComponent.h create mode 100644 Code/Sk/Components/SkaterFloatingNameComponent.cpp create mode 100644 Code/Sk/Components/SkaterFloatingNameComponent.h create mode 100644 Code/Sk/Components/SkaterGapComponent.cpp create mode 100644 Code/Sk/Components/SkaterGapComponent.h create mode 100644 Code/Sk/Components/SkaterLocalNetLogicComponent.cpp create mode 100644 Code/Sk/Components/SkaterLocalNetLogicComponent.h create mode 100644 Code/Sk/Components/SkaterLoopingSoundComponent.cpp create mode 100644 Code/Sk/Components/SkaterLoopingSoundComponent.h create mode 100644 Code/Sk/Components/SkaterMatrixQueriesComponent.cpp create mode 100644 Code/Sk/Components/SkaterMatrixQueriesComponent.h create mode 100644 Code/Sk/Components/SkaterNonLocalNetLogicComponent.cpp create mode 100644 Code/Sk/Components/SkaterNonLocalNetLogicComponent.h create mode 100644 Code/Sk/Components/SkaterPhysicsControlComponent.cpp create mode 100644 Code/Sk/Components/SkaterPhysicsControlComponent.h create mode 100644 Code/Sk/Components/SkaterProximityComponent.cpp create mode 100644 Code/Sk/Components/SkaterProximityComponent.h create mode 100644 Code/Sk/Components/SkaterRotateComponent.cpp create mode 100644 Code/Sk/Components/SkaterRotateComponent.h create mode 100644 Code/Sk/Components/SkaterRunTimerComponent.cpp create mode 100644 Code/Sk/Components/SkaterRunTimerComponent.h create mode 100644 Code/Sk/Components/SkaterScoreComponent.cpp create mode 100644 Code/Sk/Components/SkaterScoreComponent.h create mode 100644 Code/Sk/Components/SkaterSoundComponent.cpp create mode 100644 Code/Sk/Components/SkaterSoundComponent.h create mode 100644 Code/Sk/Components/SkaterStancePanelComponent.cpp create mode 100644 Code/Sk/Components/SkaterStancePanelComponent.h create mode 100644 Code/Sk/Components/SkaterStateComponent.cpp create mode 100644 Code/Sk/Components/SkaterStateComponent.h create mode 100644 Code/Sk/Components/SkaterStateHistoryComponent.cpp create mode 100644 Code/Sk/Components/SkaterStateHistoryComponent.h create mode 100644 Code/Sk/Engine/RectFeeler.cpp create mode 100644 Code/Sk/Engine/RectFeeler.h create mode 100644 Code/Sk/Engine/SuperSector.cpp create mode 100644 Code/Sk/Engine/SuperSector.h create mode 100644 Code/Sk/Engine/contact.cpp create mode 100644 Code/Sk/Engine/contact.h create mode 100644 Code/Sk/Engine/feeler.cpp create mode 100644 Code/Sk/Engine/feeler.h create mode 100644 Code/Sk/Engine/sounds.cpp create mode 100644 Code/Sk/Engine/sounds.h create mode 100644 Code/Sk/GameNet/ExportMsg.h create mode 100644 Code/Sk/GameNet/GameHandler.cpp create mode 100644 Code/Sk/GameNet/GameMsg.h create mode 100644 Code/Sk/GameNet/GameNet.cpp create mode 100644 Code/Sk/GameNet/GameNet.h create mode 100644 Code/Sk/GameNet/Lobby.cpp create mode 100644 Code/Sk/GameNet/Lobby.h create mode 100644 Code/Sk/GameNet/Ngps/dnas.cpp create mode 100644 Code/Sk/GameNet/Ngps/p_buddy.cpp create mode 100644 Code/Sk/GameNet/Ngps/p_buddy.h create mode 100644 Code/Sk/GameNet/Ngps/p_content.cpp create mode 100644 Code/Sk/GameNet/Ngps/p_content.h create mode 100644 Code/Sk/GameNet/Ngps/p_ezcommon.h create mode 100644 Code/Sk/GameNet/Ngps/p_ezconfig.h create mode 100644 Code/Sk/GameNet/Ngps/p_ezmain.h create mode 100644 Code/Sk/GameNet/Ngps/p_libezcnf.cpp create mode 100644 Code/Sk/GameNet/Ngps/p_libezcnf.h create mode 100644 Code/Sk/GameNet/Ngps/p_netcnfif.h create mode 100644 Code/Sk/GameNet/Ngps/p_netconfig.cpp create mode 100644 Code/Sk/GameNet/Ngps/p_stats.cpp create mode 100644 Code/Sk/GameNet/Ngps/p_stats.h create mode 100644 Code/Sk/GameNet/Ngps/snglue.cpp create mode 100644 Code/Sk/GameNet/Player.cpp create mode 100644 Code/Sk/GameNet/ServerList.cpp create mode 100644 Code/Sk/GameNet/XBox/p_auth.cpp create mode 100644 Code/Sk/GameNet/XBox/p_auth.h create mode 100644 Code/Sk/GameNet/XBox/p_buddy.cpp create mode 100644 Code/Sk/GameNet/XBox/p_buddy.h create mode 100644 Code/Sk/GameNet/XBox/p_match.cpp create mode 100644 Code/Sk/GameNet/XBox/p_match.h create mode 100644 Code/Sk/GameNet/XBox/p_voice.cpp create mode 100644 Code/Sk/GameNet/XBox/p_voice.h create mode 100644 Code/Sk/GameNet/scriptdebugger.h create mode 100644 Code/Sk/Main.cpp create mode 100644 Code/Sk/Modules/FrontEnd/FrontEnd.cpp create mode 100644 Code/Sk/Modules/FrontEnd/FrontEnd.h create mode 100644 Code/Sk/Modules/Skate/BettingGuy.cpp create mode 100644 Code/Sk/Modules/Skate/BettingGuy.h create mode 100644 Code/Sk/Modules/Skate/CATGoal.cpp create mode 100644 Code/Sk/Modules/Skate/CATGoal.h create mode 100644 Code/Sk/Modules/Skate/CompetitionGoal.cpp create mode 100644 Code/Sk/Modules/Skate/CompetitionGoal.h create mode 100644 Code/Sk/Modules/Skate/CreateATrick.cpp create mode 100644 Code/Sk/Modules/Skate/CreateATrick.h create mode 100644 Code/Sk/Modules/Skate/FilmGoal.cpp create mode 100644 Code/Sk/Modules/Skate/FilmGoal.h create mode 100644 Code/Sk/Modules/Skate/FindGapsGoal.cpp create mode 100644 Code/Sk/Modules/Skate/FindGapsGoal.h create mode 100644 Code/Sk/Modules/Skate/GameFlow.cpp create mode 100644 Code/Sk/Modules/Skate/GameFlow.h create mode 100644 Code/Sk/Modules/Skate/GameMode.cpp create mode 100644 Code/Sk/Modules/Skate/GameMode.h create mode 100644 Code/Sk/Modules/Skate/Goal.cpp create mode 100644 Code/Sk/Modules/Skate/Goal.h create mode 100644 Code/Sk/Modules/Skate/GoalManager.cpp create mode 100644 Code/Sk/Modules/Skate/GoalManager.h create mode 100644 Code/Sk/Modules/Skate/GoalPed.cpp create mode 100644 Code/Sk/Modules/Skate/GoalPed.h create mode 100644 Code/Sk/Modules/Skate/HorseGoal.cpp create mode 100644 Code/Sk/Modules/Skate/HorseGoal.h create mode 100644 Code/Sk/Modules/Skate/Minigame.cpp create mode 100644 Code/Sk/Modules/Skate/Minigame.h create mode 100644 Code/Sk/Modules/Skate/NetGoal.cpp create mode 100644 Code/Sk/Modules/Skate/NetGoal.h create mode 100644 Code/Sk/Modules/Skate/RaceGoal.cpp create mode 100644 Code/Sk/Modules/Skate/RaceGoal.h create mode 100644 Code/Sk/Modules/Skate/SkatetrisGoal.cpp create mode 100644 Code/Sk/Modules/Skate/SkatetrisGoal.h create mode 100644 Code/Sk/Modules/Skate/VictoryCond.cpp create mode 100644 Code/Sk/Modules/Skate/VictoryCond.h create mode 100644 Code/Sk/Modules/Skate/competition.cpp create mode 100644 Code/Sk/Modules/Skate/competition.h create mode 100644 Code/Sk/Modules/Skate/horse.cpp create mode 100644 Code/Sk/Modules/Skate/horse.h create mode 100644 Code/Sk/Modules/Skate/score.cpp create mode 100644 Code/Sk/Modules/Skate/score.h create mode 100644 Code/Sk/Modules/Skate/skate.cpp create mode 100644 Code/Sk/Modules/Skate/skate.h create mode 100644 Code/Sk/Modules/Skate/skatenet.cpp create mode 100644 Code/Sk/Modules/Viewer/Viewer.cpp create mode 100644 Code/Sk/Modules/Viewer/Viewer.h create mode 100644 Code/Sk/Ngps/crt0.s create mode 100644 Code/Sk/Objects/FollowOb.h create mode 100644 Code/Sk/Objects/GameObj.cpp create mode 100644 Code/Sk/Objects/GameObj.h create mode 100644 Code/Sk/Objects/MovingObject.cpp create mode 100644 Code/Sk/Objects/MovingObject.h create mode 100644 Code/Sk/Objects/PathMan.cpp create mode 100644 Code/Sk/Objects/PathMan.h create mode 100644 Code/Sk/Objects/PathOb.cpp create mode 100644 Code/Sk/Objects/PathOb.h create mode 100644 Code/Sk/Objects/PlayerProfileManager.cpp create mode 100644 Code/Sk/Objects/PlayerProfileManager.h create mode 100644 Code/Sk/Objects/SkaterButton.cpp create mode 100644 Code/Sk/Objects/SkaterButton.h create mode 100644 Code/Sk/Objects/SkaterPad.h create mode 100644 Code/Sk/Objects/SkaterProfile.cpp create mode 100644 Code/Sk/Objects/SkaterProfile.h create mode 100644 Code/Sk/Objects/SkaterTricks.cpp create mode 100644 Code/Sk/Objects/SkaterTricks.h create mode 100644 Code/Sk/Objects/TrickObject.cpp create mode 100644 Code/Sk/Objects/TrickObject.h create mode 100644 Code/Sk/Objects/ViewerObj.cpp create mode 100644 Code/Sk/Objects/ViewerObj.h create mode 100644 Code/Sk/Objects/car.cpp create mode 100644 Code/Sk/Objects/car.h create mode 100644 Code/Sk/Objects/crown.cpp create mode 100644 Code/Sk/Objects/crown.h create mode 100644 Code/Sk/Objects/cutscenedetails.cpp create mode 100644 Code/Sk/Objects/cutscenedetails.h create mode 100644 Code/Sk/Objects/emitter.cpp create mode 100644 Code/Sk/Objects/emitter.h create mode 100644 Code/Sk/Objects/followob.cpp create mode 100644 Code/Sk/Objects/gap.cpp create mode 100644 Code/Sk/Objects/gap.h create mode 100644 Code/Sk/Objects/manual.cpp create mode 100644 Code/Sk/Objects/manual.h create mode 100644 Code/Sk/Objects/moviecam.cpp create mode 100644 Code/Sk/Objects/moviecam.h create mode 100644 Code/Sk/Objects/moviedetails.cpp create mode 100644 Code/Sk/Objects/moviedetails.h create mode 100644 Code/Sk/Objects/navigation.cpp create mode 100644 Code/Sk/Objects/navigation.h create mode 100644 Code/Sk/Objects/objecthook.cpp create mode 100644 Code/Sk/Objects/objecthook.h create mode 100644 Code/Sk/Objects/ped.cpp create mode 100644 Code/Sk/Objects/ped.h create mode 100644 Code/Sk/Objects/proxim.cpp create mode 100644 Code/Sk/Objects/proxim.h create mode 100644 Code/Sk/Objects/rail.cpp create mode 100644 Code/Sk/Objects/rail.h create mode 100644 Code/Sk/Objects/records.cpp create mode 100644 Code/Sk/Objects/records.h create mode 100644 Code/Sk/Objects/restart.cpp create mode 100644 Code/Sk/Objects/restart.h create mode 100644 Code/Sk/Objects/skater.cpp create mode 100644 Code/Sk/Objects/skater.h create mode 100644 Code/Sk/Objects/skatercam.cpp create mode 100644 Code/Sk/Objects/skatercam.h create mode 100644 Code/Sk/Objects/skatercareer.cpp create mode 100644 Code/Sk/Objects/skatercareer.h create mode 100644 Code/Sk/Objects/skaterflags.h create mode 100644 Code/Sk/Objects/skaterpad.cpp create mode 100644 Code/Sk/ParkEditor/EdRail.cpp create mode 100644 Code/Sk/ParkEditor/EdRail.h create mode 100644 Code/Sk/ParkEditor/LoadPath.txt create mode 100644 Code/Sk/ParkEditor2/EdMap.cpp create mode 100644 Code/Sk/ParkEditor2/EdMap.h create mode 100644 Code/Sk/ParkEditor2/GapManager.cpp create mode 100644 Code/Sk/ParkEditor2/GapManager.h create mode 100644 Code/Sk/ParkEditor2/ParkEd.cpp create mode 100644 Code/Sk/ParkEditor2/ParkEd.h create mode 100644 Code/Sk/ParkEditor2/ParkGen.cpp create mode 100644 Code/Sk/ParkEditor2/ParkGen.h create mode 100644 Code/Sk/ParkEditor2/clipboard.cpp create mode 100644 Code/Sk/ParkEditor2/clipboard.h create mode 100644 Code/Sk/Scripting/cfuncs.cpp create mode 100644 Code/Sk/Scripting/cfuncs.h create mode 100644 Code/Sk/Scripting/ftables.cpp create mode 100644 Code/Sk/Scripting/ftables.h create mode 100644 Code/Sk/Scripting/gs_file.cpp create mode 100644 Code/Sk/Scripting/gs_file.h create mode 100644 Code/Sk/Scripting/gs_init.cpp create mode 100644 Code/Sk/Scripting/gs_init.h create mode 100644 Code/Sk/Scripting/mcfuncs.cpp create mode 100644 Code/Sk/Scripting/mcfuncs.h create mode 100644 Code/Sk/Scripting/nodearray.cpp create mode 100644 Code/Sk/Scripting/nodearray.h create mode 100644 Code/Sk/Scripting/skfuncs.cpp create mode 100644 Code/Sk/Scripting/skfuncs.h create mode 100644 Code/Sk/heap_sizes.h create mode 100644 Code/Sk/language.h create mode 100644 Code/Sk/ngc/crt0.s create mode 100644 Code/Sk/ngc/defs.s create mode 100644 Code/Sk/product_codes.h create mode 100644 Code/Sk/template.h create mode 100644 Code/Sys/Config/NGC/p_config.cpp create mode 100644 Code/Sys/Config/NGPS/p_config.cpp create mode 100644 Code/Sys/Config/Win32/p_config.cpp create mode 100644 Code/Sys/Config/XBox/p_config.cpp create mode 100644 Code/Sys/Config/config.cpp create mode 100644 Code/Sys/Config/config.h create mode 100644 Code/Sys/File/AsyncFilesys.cpp create mode 100644 Code/Sys/File/AsyncFilesys.h create mode 100644 Code/Sys/File/AsyncTypes.h create mode 100644 Code/Sys/File/FileLibrary.cpp create mode 100644 Code/Sys/File/FileLibrary.h create mode 100644 Code/Sys/File/PRE.cpp create mode 100644 Code/Sys/File/PRE.h create mode 100644 Code/Sys/File/XBox/hed.cpp create mode 100644 Code/Sys/File/XBox/hed.h create mode 100644 Code/Sys/File/XBox/p_AsyncFilesys.cpp create mode 100644 Code/Sys/File/XBox/p_AsyncFilesys.h create mode 100644 Code/Sys/File/XBox/p_filesys.cpp create mode 100644 Code/Sys/File/XBox/p_pre.cpp create mode 100644 Code/Sys/File/XBox/p_streamer.cpp create mode 100644 Code/Sys/File/XBox/p_streamer.h create mode 100644 Code/Sys/File/filesys.h create mode 100644 Code/Sys/File/memfile.h create mode 100644 Code/Sys/File/ngc/hed.cpp create mode 100644 Code/Sys/File/ngc/hed.h create mode 100644 Code/Sys/File/ngc/p_AsyncFilesys.cpp create mode 100644 Code/Sys/File/ngc/p_asyncFilesys.h create mode 100644 Code/Sys/File/ngc/p_filesys.cpp create mode 100644 Code/Sys/File/ngc/p_pre.cpp create mode 100644 Code/Sys/File/ngps/FileIO/FIleIO_IOP.h create mode 100644 Code/Sys/File/ngps/FileIO/FileIO.h create mode 100644 Code/Sys/File/ngps/FileIO/Makefile create mode 100644 Code/Sys/File/ngps/FileIO/PathDefs create mode 100644 Code/Sys/File/ngps/FileIO/command.c create mode 100644 Code/Sys/File/ngps/FileIO/start.c create mode 100644 Code/Sys/File/ngps/hed.cpp create mode 100644 Code/Sys/File/ngps/hed.h create mode 100644 Code/Sys/File/ngps/p_AsyncFilesys.cpp create mode 100644 Code/Sys/File/ngps/p_AsyncFilesys.h create mode 100644 Code/Sys/File/ngps/p_filesys.cpp create mode 100644 Code/Sys/File/pip.cpp create mode 100644 Code/Sys/File/pip.h create mode 100644 Code/Sys/McMan.h create mode 100644 Code/Sys/Mem/CompactPool.cpp create mode 100644 Code/Sys/Mem/CompactPool.h create mode 100644 Code/Sys/Mem/PoolManager.cpp create mode 100644 Code/Sys/Mem/PoolManager.h create mode 100644 Code/Sys/Mem/Poolable.cpp create mode 100644 Code/Sys/Mem/Poolable.h create mode 100644 Code/Sys/Mem/alloc.cpp create mode 100644 Code/Sys/Mem/alloc.h create mode 100644 Code/Sys/Mem/handle.h create mode 100644 Code/Sys/Mem/heap.cpp create mode 100644 Code/Sys/Mem/heap.h create mode 100644 Code/Sys/Mem/memdbg.h create mode 100644 Code/Sys/Mem/memman.cpp create mode 100644 Code/Sys/Mem/memman.h create mode 100644 Code/Sys/Mem/memptr.h create mode 100644 Code/Sys/Mem/memtest.cpp create mode 100644 Code/Sys/Mem/memtest.h create mode 100644 Code/Sys/Mem/pile.cpp create mode 100644 Code/Sys/Mem/pile.h create mode 100644 Code/Sys/Mem/pool.cpp create mode 100644 Code/Sys/Mem/pool.h create mode 100644 Code/Sys/Mem/region.cpp create mode 100644 Code/Sys/Mem/region.h create mode 100644 Code/Sys/MemCard/NGPS/p_McMan.cpp create mode 100644 Code/Sys/MemCard/XBox/p_McMan.cpp create mode 100644 Code/Sys/MemCard/ngc/p_McMan.cpp create mode 100644 Code/Sys/Profiler.cpp create mode 100644 Code/Sys/Profiler.h create mode 100644 Code/Sys/Replay/NGC/p_replay.cpp create mode 100644 Code/Sys/Replay/NGPS/p_replay.cpp create mode 100644 Code/Sys/Replay/Win32/p_replay.cpp create mode 100644 Code/Sys/Replay/XBox/p_replay.cpp create mode 100644 Code/Sys/Replay/replay.cpp create mode 100644 Code/Sys/Replay/replay.h create mode 100644 Code/Sys/SIO/NGPS/p_keyboard.cpp create mode 100644 Code/Sys/SIO/XBox/p_keyboard.cpp create mode 100644 Code/Sys/SIO/XBox/p_siodev.cpp create mode 100644 Code/Sys/SIO/XBox/p_sioman.cpp create mode 100644 Code/Sys/SIO/keyboard.h create mode 100644 Code/Sys/SIO/ngc/p_keyboard.cpp create mode 100644 Code/Sys/SIO/ngc/siodev.cpp create mode 100644 Code/Sys/SIO/ngc/sioman.cpp create mode 100644 Code/Sys/SIO/siodev.cpp create mode 100644 Code/Sys/SIO/sioman.cpp create mode 100644 Code/Sys/Win32/p_timer.cpp create mode 100644 Code/Sys/XBox/p_timer.cpp create mode 100644 Code/Sys/demo.cpp create mode 100644 Code/Sys/demo.h create mode 100644 Code/Sys/ngc/p_anim.cpp create mode 100644 Code/Sys/ngc/p_anim.h create mode 100644 Code/Sys/ngc/p_anim_core.s create mode 100644 Code/Sys/ngc/p_aram.cpp create mode 100644 Code/Sys/ngc/p_aram.h create mode 100644 Code/Sys/ngc/p_assert.h create mode 100644 Code/Sys/ngc/p_atomic.cpp create mode 100644 Code/Sys/ngc/p_atomic.h create mode 100644 Code/Sys/ngc/p_bbox.cpp create mode 100644 Code/Sys/ngc/p_bbox.h create mode 100644 Code/Sys/ngc/p_buffer.cpp create mode 100644 Code/Sys/ngc/p_buffer.h create mode 100644 Code/Sys/ngc/p_camera.cpp create mode 100644 Code/Sys/ngc/p_camera.h create mode 100644 Code/Sys/ngc/p_clump.cpp create mode 100644 Code/Sys/ngc/p_clump.h create mode 100644 Code/Sys/ngc/p_collision.cpp create mode 100644 Code/Sys/ngc/p_collision.h create mode 100644 Code/Sys/ngc/p_debugfont.cpp create mode 100644 Code/Sys/ngc/p_debugfont.h create mode 100644 Code/Sys/ngc/p_display.cpp create mode 100644 Code/Sys/ngc/p_display.h create mode 100644 Code/Sys/ngc/p_dl.cpp create mode 100644 Code/Sys/ngc/p_dl.h create mode 100644 Code/Sys/ngc/p_dlman.cpp create mode 100644 Code/Sys/ngc/p_dlman.h create mode 100644 Code/Sys/ngc/p_dma.cpp create mode 100644 Code/Sys/ngc/p_dma.h create mode 100644 Code/Sys/ngc/p_dvd.cpp create mode 100644 Code/Sys/ngc/p_dvd.h create mode 100644 Code/Sys/ngc/p_file.cpp create mode 100644 Code/Sys/ngc/p_file.h create mode 100644 Code/Sys/ngc/p_font.cpp create mode 100644 Code/Sys/ngc/p_frame.cpp create mode 100644 Code/Sys/ngc/p_frame.h create mode 100644 Code/Sys/ngc/p_gx.cpp create mode 100644 Code/Sys/ngc/p_gx.h create mode 100644 Code/Sys/ngc/p_hashid.cpp create mode 100644 Code/Sys/ngc/p_hashid.h create mode 100644 Code/Sys/ngc/p_hw.h create mode 100644 Code/Sys/ngc/p_hwinit.cpp create mode 100644 Code/Sys/ngc/p_hwpad.cpp create mode 100644 Code/Sys/ngc/p_hwpad.h create mode 100644 Code/Sys/ngc/p_light.cpp create mode 100644 Code/Sys/ngc/p_light.h create mode 100644 Code/Sys/ngc/p_material.cpp create mode 100644 Code/Sys/ngc/p_material.h create mode 100644 Code/Sys/ngc/p_matman.cpp create mode 100644 Code/Sys/ngc/p_matman.h create mode 100644 Code/Sys/ngc/p_matrix.cpp create mode 100644 Code/Sys/ngc/p_matrix.h create mode 100644 Code/Sys/ngc/p_model.cpp create mode 100644 Code/Sys/ngc/p_model.h create mode 100644 Code/Sys/ngc/p_particle.cpp create mode 100644 Code/Sys/ngc/p_particle.h create mode 100644 Code/Sys/ngc/p_ppcwgpipe.h create mode 100644 Code/Sys/ngc/p_prim.cpp create mode 100644 Code/Sys/ngc/p_prim.h create mode 100644 Code/Sys/ngc/p_profile.cpp create mode 100644 Code/Sys/ngc/p_profile.h create mode 100644 Code/Sys/ngc/p_quat.cpp create mode 100644 Code/Sys/ngc/p_quat.h create mode 100644 Code/Sys/ngc/p_reftypes.h create mode 100644 Code/Sys/ngc/p_render.cpp create mode 100644 Code/Sys/ngc/p_render.h create mode 100644 Code/Sys/ngc/p_scene.cpp create mode 100644 Code/Sys/ngc/p_scene.h create mode 100644 Code/Sys/ngc/p_screenshot.cpp create mode 100644 Code/Sys/ngc/p_screenshot.h create mode 100644 Code/Sys/ngc/p_slerp.cpp create mode 100644 Code/Sys/ngc/p_slerp.h create mode 100644 Code/Sys/ngc/p_tex.cpp create mode 100644 Code/Sys/ngc/p_tex.h create mode 100644 Code/Sys/ngc/p_texman.cpp create mode 100644 Code/Sys/ngc/p_texman.h create mode 100644 Code/Sys/ngc/p_timer.cpp create mode 100644 Code/Sys/ngc/p_triangle.cpp create mode 100644 Code/Sys/ngc/p_triangle.h create mode 100644 Code/Sys/ngc/p_vector.cpp create mode 100644 Code/Sys/ngc/p_vector.h create mode 100644 Code/Sys/ngps/p_timer.cpp create mode 100644 Code/Sys/siodev.h create mode 100644 Code/Sys/sioman.h create mode 100644 Code/Sys/sys.cpp create mode 100644 Code/Sys/sys.h create mode 100644 Code/Sys/timer.cpp create mode 100644 Code/Sys/timer.h create mode 100644 Code/standard.cpp create mode 100644 Code/template.cpp create mode 100644 Code/template.h diff --git a/Code/Core/Debug.h b/Code/Core/Debug.h new file mode 100644 index 0000000..a8a95de --- /dev/null +++ b/Code/Core/Debug.h @@ -0,0 +1,145 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** Created: 05/27/99 mjb ** +** ** +** File name: core/debug.h ** +** ** +*****************************************************************************/ + +#ifndef __CORE_DEBUG_H +#define __CORE_DEBUG_H + + +#ifdef __NOPT_DEBUG__ + +// for now - always turn on messages and assertion +#define __NOPT_MESSAGES__ +#define __NOPT_ASSERT__ + +#else + +// Mick - added assertions always on, regardless of debug mode. +#define __NOPT_ASSERT__ + +// Gary - ... unless you're working on the tools, +// in which case the asserts prevent the code from compiling... +#ifdef __PLAT_WN32__ +#ifndef _CONSOLE // Ken: Asserts now compile for console apps. (see levelassetlister project) + #undef __NOPT_ASSERT__ +#endif +#endif + +#endif // __NOPT_DEBUG__ + +#ifdef __NOPT_CDROM__OLD +#undef __NOPT_ASSERT__ +#endif + +// Temporary switch off assertions flag +#ifdef __NOPT_NOASSERTIONS__ +#undef __NOPT_ASSERT__ +#endif + +// no assertions on final build ("final=") +#ifdef __NOPT_FINAL__ +#undef __NOPT_ASSERT__ +#endif + + +#ifdef __NOPT_ASSERT__ +#define __DEBUG_CODE__ +#endif + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +#include "debug/messages.h" +#include "debug/checks.h" + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Dbg +{ + + + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Type Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +void SetUp( void ); +void CloseDown( void ); + +#ifdef __NOPT_FULL_DEBUG__ +void* NewClassNode( size_t size ); +void DeleteClassNode( void* pMem ); +void* NewInstanceNode( size_t size ); +void DeleteInstanceNode( void* pMem ); +#endif // __NOPT_FULL_DEBUG__ + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + +#define Dbg_Code(X) X + +/***************************************************************************** +** Stubs ** +*****************************************************************************/ + +#else // __NOPT_DEBUG__ + +#ifndef __NOPT_ASSERT__ +inline void SetUp( void ) {}; +inline void CloseDown( void ) {}; +#else +void SetUp( void ); +void CloseDown( void ); +#endif // __NOPT_ASSERT + +#define Dbg_Code(X) + + +#endif // __NOPT_DEBUG__ + +} // namespace Dbg + +#endif // __CORE_DEBUG_H diff --git a/Code/Core/Debug/Assert.cpp b/Code/Core/Debug/Assert.cpp new file mode 100644 index 0000000..a119de7 --- /dev/null +++ b/Code/Core/Debug/Assert.cpp @@ -0,0 +1,314 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** File name: assert.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Assert support code ** +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include +#include + +#include + +#ifndef __PLAT_WN32__ +#include +#endif // __PLAT_WN32__ + +#ifdef __PLAT_NGPS__ +int DumpUnwindStack( int iMaxDepth, int *pDest ); +#include +#endif + +#ifdef __PLAT_NGC__ +#include +#define _output OSReport +#else +#define _output printf +#endif + +#ifdef __NOPT_ASSERT__ + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + + +namespace Dbg +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +static const int vASSERT_BUFFER_SIZE = 512; + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +static AssertTrap* assert_trap_handler = NULL; +static bool screen_assert_active = false; + +// made public for Dbg_ Macros +char* msg_null_pointer = "Null Pointer"; +char* msg_misaligned_pointer = "Pointer not aligned"; +char* msg_pointer_to_free_mem = "Pointer to free mem"; +char* msg_unknown_reason = "No reason supplied"; +char* msg_type_mismatch = "Type Mismatch: \"%s\" is of type \"%s\" - not a valid \"%s\""; + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +void set_trap( AssertTrap* trap ) +{ + assert_trap_handler = trap; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void screen_assert( bool on ) +{ + screen_assert_active = on; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifdef __NOPT_DEBUG__ +void Assert( char* file, uint line, Signature& sig, char* reason ) +#else +void Assert( char* file, uint line, char* reason ) +#endif +{ + static char assert_buffer1[vASSERT_BUFFER_SIZE]; +#ifdef __NOPT_DEBUG + static char assert_buffer2[vASSERT_BUFFER_SIZE]; +#endif + static char assert_buffer3[vASSERT_BUFFER_SIZE]; + static char* tmp1 = assert_buffer1; +#ifdef __NOPT_DEBUG + static char* tmp2 = assert_buffer2; +#endif + static char* tmp3 = assert_buffer3; + + +#if !( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ )) + + static int again = 0; + if (again) + { + _output ("MEM CONTEXT: %s\n",Mem::Manager::sHandle().GetContextName()); + + _output( "LOOPED ASSERTION: %s(%d)\n%s\n\n", + file, line, reason ); + _output ("Real Assertion: %s\n%s\n",tmp1,tmp3); + while (1); // and hang... + } + again = 1; + + _output ("\n--------------------------------------------------\nPLEASE COPY FROM A FEW LINES ABOVE HERE\n\nCURRENT MEM CONTEXT: %s\n\n" + ,Mem::Manager::sHandle().GetContextName()); + Mem::Manager& mem_man = Mem::Manager::sHandle(); +// Mem::Heap* heap = mem_man.TopDownHeap(); +// Mem::Region* region = heap->ParentRegion(); +// _output ("TopDown Fragmentation %7dK, in %5d Blocks\n",heap->mFreeMem.m_count / 1024, heap->mFreeBlocks.m_count); +// _output (" used %7dK, in %5d Blocks\n",heap->mUsedMem.m_count / 1024, heap->mUsedBlocks.m_count); +// heap = mem_man.BottomUpHeap(); +// _output ("BottomUp Fragmentation %7dK, in %5d Blocks\n",heap->mFreeMem.m_count / 1024, heap->mFreeBlocks.m_count); +// _output (" used %7dK, in %5d Blocks\n",heap->mUsedMem.m_count / 1024, heap->mUsedBlocks.m_count); +// _output ("Shared Region %dK free out of %d K available\n", region->MemAvailable() / 1024, region->TotalSize() / 1024 ); + + _output("Name Used Frag Free Min Blocks\n"); + _output("--------------- ----- ----- ---- ------ ------\n"); + Mem::Heap* heap; + for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap)) + { + Mem::Region* region = heap->ParentRegion(); + _output( "%12s: %5dK %4dK %4dK %4dK %5d \n", + heap->GetName(), + heap->mUsedMem.m_count / 1024, + heap->mFreeMem.m_count / 1024, + region->MemAvailable() / 1024, + region->MinMemAvailable() / 1024, + heap->mUsedBlocks.m_count + ); + } + + _output( "FILE: %s(%d)\nASSERTION: %s\n\n", + file, line, reason ); + _output( tmp1, "FILE: %s(%d) ", file, line ); + _output( tmp3, "ASSERTION: %s", reason ); + // attempt to dump the call stack + // requires that you have the correct .map file, along with the executable + #ifndef __PLAT_WN32__ + MemView_FindLeaks(); + #endif + + _output( "\nCALL STACK ..........................\n\n"); + + #ifdef __PLAT_NGPS__ + DumpUnwindStack( 40, NULL ); + #endif + + +#endif + + sprintf( tmp1, "FILE: %s(%d) ", file, line ); + sprintf( tmp3, "ASSERTION: %s", reason ); + +#ifndef __PLAT_NGC__ +#ifndef __PLAT_WN32__ +// Mick: Attempt to save a screenshot +// Dbg_Printf("Attempting to save screenshot 'screens\\Assert???.bmp'\n"); + if (!Config::CD()) + { + Gfx::Manager * gfx_manager = Gfx::Manager::Instance(); + gfx_manager->ScreenShot( "Assert" ); + } +#endif + + if ( screen_assert_active ) + { + screen_assert_active = false; + +#ifndef __PLAT_WN32__ + Gfx::Manager* gfx_man = Gfx::Manager::Instance(); + + gfx_man->AssertText ( 0, tmp1 ); + +#ifdef __NOPT_DEBUG + sprintf( tmp2, "%s", &sig.GetName() ); + gfx_man->AssertText ( 1, tmp2 ); +#endif + gfx_man->AssertText ( 2, tmp3 ); + + gfx_man->AssertFlush(); +#endif // __PLAT_WN32__ + } +#endif // __PLAT_NGC__ + +#ifdef __NOPT_DEBUG + sprintf( tmp1, "ASSERTION: %s(%d) %s\n%s\n\n", + file, line, &sig.GetName(), reason ); +#else + sprintf( tmp1, "FILE: %s(%d)\nASSERTION: %s\n\n", + file, line, reason ); +#endif + + +#ifdef __PLAT_NGPS__ +#if 0 // does not seem to output anything... + sceDevConsInit(); // Initialise screen console output + int console = sceDevConsOpen( + (2048 - 640/2 + 20) << 4, // top left GS X primitive coord + (2048 - 448/2 + 20) << 4, // top left GS Y primitive coord + 80, // Number of chars (X) + 5); // Number of chars (Y) + sceDevConsAttribute(console, 7); // Output in white (default colours can be + // redefined by sceDevConsSetColor) + sceDevConsClear(console); + sceDevConsPrintf(console, tmp1); + sceDevConsPrintf(console, "blah \n"); + sceDevConsPrintf(console, "blah2 \n"); + sceDevConsPrintf(console, "blah3 \n"); + sceDevConsDraw(console); +#endif +#endif + + + if ( assert_trap_handler != NULL ) + { + _output ("MEM CONTEXT: %s\n",Mem::Manager::sHandle().GetContextName()); + + Dbg_Printf( "%s\n", tmp1 ); + assert_trap_handler( tmp1 ); + } + else + { + Dbg_Printf( "%s\n", tmp1 ); + Dbg_Printf( "!!NO TRAP HANDLER SET!!\n" ); + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ )) +void assert_vcc( char* file, uint line, char* reason ) +{ + static char assert_buffer[vASSERT_BUFFER_SIZE]; + static char* tmp = assert_buffer; + + sprintf( tmp, "ASSERTION: %s(%d)\n%s\n\n", + file, line, reason ); + + if ( assert_trap_handler != NULL ) + { + Dbg_Printf( "%s\n", tmp ); + assert_trap_handler( tmp ); + } + else + { + Dbg_Printf( "%s\n", tmp ); + Dbg_Printf( "!!NO TRAP HANDLER SET!!\n" ); + } +} +#endif // #ifdef __PLAT_XBOX__ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +} // namespace Dbg + +#endif //__NOPT_DEBUG__ diff --git a/Code/Core/Debug/Checks.h b/Code/Core/Debug/Checks.h new file mode 100644 index 0000000..5f3f418 --- /dev/null +++ b/Code/Core/Debug/Checks.h @@ -0,0 +1,319 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (Dbg_) ** +** ** +** Created: 05/27/99 mjb ** +** ** +** File name: core/debug/checks.h ** +** ** +*****************************************************************************/ + +#ifndef __CORE_DEBUG_CHECKS_H +#define __CORE_DEBUG_CHECKS_H + +#ifndef __CORE_DEBUG_LOG_H +#include +#endif + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include "signatrs.h" + +#ifdef __NOPT_ASSERT__ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Dbg +{ + + + +/***************************************************************************** +** Type Defines ** +*****************************************************************************/ + +typedef void ( AssertTrap ) ( char* message ); + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +extern char* msg_unknown_reason; // message strings +extern char* msg_null_pointer; +extern char* msg_misaligned_pointer; +extern char* msg_pointer_to_free_mem; +extern char* msg_type_mismatch; + +extern AssertTrap default_trap; + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +#ifdef __NOPT_DEBUG__ +void Assert ( char* file, uint line, Signature& sig, char* reason = msg_unknown_reason ); +#else +void Assert ( char* file, uint line, char* reason = msg_unknown_reason ); +#endif + +void pad_printf ( const char* text, ... ); +void set_trap( AssertTrap* trap = default_trap ); +void screen_assert( bool on = false ); + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Dbg + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + +#define Dbg_SetTrap(A) { Dbg::set_trap(A); } +#define Dbg_SetScreenAssert(A) { Dbg::screen_assert(A); } + +/******************************************************************/ +/* Assertions */ +/* */ +/******************************************************************/ + +#ifdef __NOPT_DEBUG__ + + +#define Dbg_Assert(_c) \ + \ +if ( !(_c)) \ +{ \ + Dbg::Assert( __FILE__, __LINE__, Dbg_signature ); \ +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#define Dbg_MsgAssert( _c, _params ) \ + \ +if( !( _c )) \ +{ \ + Dbg::pad_printf _params; \ + Dbg::Assert( __FILE__, __LINE__, Dbg_signature, \ + Dbg::sprintf_pad ); \ +} + +#else // __NOPT_DEBUG__ + +// Mick: If we have assertions, but are not in DEBUG mode +// then we don not have the Dbg_signature thing +// so we just pass NULL to assert() + +#define Dbg_Assert(_c) \ + \ +if ( !(_c)) \ +{ \ + Dbg::Assert ( __FILE__, __LINE__, NULL ); \ +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifdef __PLAT_WN32__ + +#ifdef _CONSOLE +#define Dbg_MsgAssert( _c, _params ) \ + \ +if( !( _c )) \ +{ \ + printf("\nGame code assert!\n"); \ + printf("File %s, line %d\n",__FILE__,__LINE__); \ + printf _params; \ + printf("\nPress CTRL-C to quit ... "); \ + while(1); \ +} +#else +#define Dbg_MsgAssert( _c, _params ) +#endif + +#else + +#define Dbg_MsgAssert( _c, _params ) \ + \ +if( !( _c )) \ +{ \ + Dbg::pad_printf _params; \ + Dbg::Assert( __FILE__, __LINE__, Dbg::sprintf_pad ); \ +} +#endif + +#endif // __NOPT_DEBUG__ + +#ifdef __PLAT_NGPS__ +#define Dbg_MsgLog( _params ) \ +{ \ + Dbg::pad_printf _params; \ + Log::AddEntry(__FILE__,__LINE__,__PRETTY_FUNCTION__,Dbg::sprintf_pad); \ +} + +#define Dbg_Log( ) \ +{ \ + Log::AddEntry(__FILE__,__LINE__,__PRETTY_FUNCTION__); \ +} +#else +#define Dbg_MsgLog( _params ) \ +{ \ + Dbg::pad_printf _params; \ + Log::AddEntry(__FILE__,__LINE__,__FUNCTION__,Dbg::sprintf_pad); \ +} + +#define Dbg_Log( ) \ +{ \ + Log::AddEntry(__FILE__,__LINE__,__FUNCTION__); \ +} +#endif +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifdef __NOPT_MEM_DEBUG__ + +#define Dbg_AssertPtr(_p) \ +{ \ + if ((_p) == NULL ) \ + { \ + Dbg::Assert ( __FILE__, __LINE__, \ + Dbg_signature, Dbg::msg_null_pointer ); \ + } \ + else if ( *((uint64*)(nAlignDown(_p))) == Mem::vFREE_BLOCK ) \ + { \ + Dbg::Assert ( __FILE__, __LINE__, \ + Dbg_signature, Dbg::msg_pointer_to_free_mem ); \ + } \ +} + + +#else // __NOPT_MEM_DEBUG__ + +#ifdef __NOPT_DEBUG__ + +#define Dbg_AssertPtr(_p) \ +{ \ + if ((_p) == NULL ) \ + { \ + Dbg::Assert ( __FILE__, __LINE__, \ + Dbg_signature, Dbg::msg_null_pointer ); \ + } \ +} + +#else + + +#define Dbg_AssertPtr(_p) \ +{ \ + if ((_p) == NULL ) \ + { \ + Dbg::Assert ( __FILE__, __LINE__, \ + Dbg::msg_null_pointer ); \ + } \ +} + +#endif //__NOPT_DEBUG__ + +#endif // __NOPT_MEM_DEBUG__ + +/******************************************************************/ +/* Type Checking */ +/* */ +/******************************************************************/ + +#ifdef __NOPT_FULL_DEBUG__ + +#define Dbg_AssertType(_c,_t) \ +{ \ + Dbg_AssertPtr(_c); \ + Dbg_MsgAssert( nAligned(_c), Dbg::msg_misaligned_pointer ); \ + Dbg_MsgAssert(((_c)->classStamp != Spt::Class::vDELETED_CLASS ),"Deleted Class" ); \ + Dbg_MsgAssert(((_c)->classStamp == Spt::Class::vREGISTERED_CLASS ), "Corrupt Class" ); \ + Dbg_MsgAssert(((_c)->vClassNode()->IsDerivedOrSame(*_t::sClassNode())), \ + Dbg::msg_type_mismatch,#_c,(_c)->vClassNode()->GetName(),#_t); \ +} + +#define Dbg_AssertThis \ +{ \ + Dbg_AssertPtr(this); \ + Dbg_MsgAssert( nAligned(this), Dbg::msg_misaligned_pointer ); \ + Dbg_MsgAssert((classStamp != Spt::Class::vDELETED_CLASS ),"Deleted Class" ); \ + Dbg_MsgAssert((classStamp == Spt::Class::vREGISTERED_CLASS ), "Corrupt Class" ); \ + Dbg_MsgAssert((vClassNode()->IsDerivedOrSame(*sClassNode())), \ + Dbg::msg_type_mismatch,"this", vClassNode()->GetName(),sClassNode()->GetName()); \ +} + +#else // __NOPT_FULL_DEBUG__ + +#define Dbg_AssertType(_c,_t) Dbg_AssertPtr(_c) +#define Dbg_AssertThis Dbg_AssertPtr(this) + +#endif // __NOPT_FULL_DEBUG__ + + +/***************************************************************************** +** Stubs ** +*****************************************************************************/ + +#else + +#define Dbg_MsgAssert( _c, _params ) + +#define Dbg_SetTrap(_f) + +#ifdef __NOPT_ASSERT__ +#define Dbg_SetScreenAssert(A) { Dbg::screen_assert(A); } +#else +#define Dbg_SetScreenAssert(_b) +#endif + +#define Dbg_Assert(_c) +#define Dbg_AssertPtr(_p) +#define Dbg_AssertType(_c,_t) +#define Dbg_AssertThis + +#define Dbg_MsgLog( _params ) +#define Dbg_Log( ) + + +#endif // __NOPT_ASSERT__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#endif // __CORE_DEBUG_CHECKS_H + + diff --git a/Code/Core/Debug/Debug.cpp b/Code/Core/Debug/Debug.cpp new file mode 100644 index 0000000..e4db55b --- /dev/null +++ b/Code/Core/Debug/Debug.cpp @@ -0,0 +1,454 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** File name: debug.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Debug message support. ** +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#define __ERROR_STRINGS__ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __PLAT_NGC__ +#include +#endif __PLAT_NGC__ + +#ifdef __NOPT_ASSERT__ + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +Dbg_DefineProject( Core, "Core Library") + + + +namespace Dbg +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +extern void set_up ( void ); +extern void close_down ( void ); +extern OutputCode default_print; +extern AssertTrap default_trap; + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +static const int vBUFFER_SIZE = 512; + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +static char s_sprintf_buffer[vBUFFER_SIZE]; +static char s_printf_buffer[vBUFFER_SIZE]; +static Flags< Level > s_level_mask = mNONE; + +#ifdef __NOPT_DEBUG__ +static char* s_typename[] = +{ + "ERROR!!", + "WARNING", + "Notify ", + "Message" +}; +#endif + +static OutputCode* output; + + +// public for Dbg_ Macros +char* sprintf_pad = s_sprintf_buffer; +char* printf_pad = s_printf_buffer; +#ifdef __NOPT_DEBUG__ +Signature* current_sig; +#endif + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +#ifdef __NOPT_DEBUG__ +Project* RegisteredProjects = NULL; +Module* RegisteredModules = NULL; +#endif +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +void print( char* text ); + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +static void s_prefixed_output( Level level, char* text, va_list args ) +{ + + + Dbg_AssertPtr( text ); + Dbg_Assert( level >= vERROR ); + Dbg_Assert( level <= vMESSAGE ); + + if ( s_level_mask.Test( level )) + { + return; + } + +#ifdef __NOPT_DEBUG__ + printf( "[%s] %s - ", + s_typename[level], + ¤t_sig->GetName()); +#endif + + vsprintf( printf_pad, text, args); + + Dbg::print( printf_pad ); + Dbg::print( "\n" ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void pad_printf( const char* text, ... ) +{ + + + Dbg_AssertPtr( text ); + + va_list args; + + va_start( args, text ); + + vsprintf( sprintf_pad, text, args); + + va_end( args ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void print( char* text ) +{ + + + if ( output ) + { + output( text ); + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void message ( char *text, ... ) +{ + + + Dbg_AssertPtr( text ); + + + if ( s_level_mask.TestMask ( mMESSAGE )) + { + return; + } + + va_list args; + + va_start( args, text ); + s_prefixed_output( vMESSAGE, text, args ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void notify( char *text, ... ) +{ + + + Dbg_AssertPtr( text ); + + if ( s_level_mask.TestMask ( mNOTIFY )) + { + return; + } + + va_list args; + + va_start( args, text ); + s_prefixed_output( vNOTIFY, text, args ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void warning( char *text, ... ) +{ + + + Dbg_AssertPtr( text ); + + if ( s_level_mask.TestMask ( mWARNING )) + { + return; + } + + va_list args; + + va_start( args, text ); + s_prefixed_output( vWARNING, text, args ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void error( char *text, ... ) +{ + + + Dbg_AssertPtr( text ); + + if ( s_level_mask.TestMask ( mERROR )) + { + return; + } + + va_list args; + + va_start( args, text ); + s_prefixed_output( vERROR, text, args ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void set_output( OutputCode* handler ) +{ + + + Dbg_AssertPtr( handler ); + + output = handler; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void level_mask( Flags< Mask > mask ) +{ + + + s_level_mask.SetMask( mask ); +} + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +void SetUp( void ) +{ + + + set_trap( default_trap ); + set_output( Dbg::default_print ); +#ifdef __NOPT_DEBUG__ + set_up(); // platform specific setup +#endif +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void CloseDown( void ) +{ + + +#ifdef __NOPT_DEBUG__ + close_down(); // platform specific closedown +#endif +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + + +} // namespace Dbg + +#endif // __NOPT_DEBUG__ + +#ifdef __PLAT_NGPS__ +extern "C" +{ +int snputs(const char* pszStr); +} +#endif + +// Need to do this, cos otherwise the printf inside OurPrintf will get converted to +// an OurPrintf, & it will infinitely recurse. +#undef printf + +int OurPrintf ( const char* fmt, ... ) +{ + // Don't want printf's in some builds. +# if defined( __PLAT_XBOX__ ) && !defined( __NOPT_ASSERT__ ) + return 0; +# endif + + int ret=-1; + + char p_buffer[1024]; + + char *p_buf = p_buffer; + + #if 1 + p_buf[0]=0; + #else + sprintf (p_buf,"%6d: ",(int)Tmr::GetRenderFrame()); + p_buf+=strlen(p_buf); + #endif + + + va_list args; + va_start( args, fmt ); + vsprintf(p_buf,fmt,args); + va_end( args ); + + switch (Config::GetHardware()) + { + #ifdef __PLAT_NGPS__ + case Config::HARDWARE_PS2_PROVIEW: + ret=snputs(p_buffer); + break; + case Config::HARDWARE_PS2_DEVSYSTEM: + case Config::HARDWARE_PS2: + // If we are capturing to a file, then redirect there + if (dumping_printfs == 1) + { + dump_printf(p_buffer); + } + + printf("%s",p_buffer); + + break; + #endif + + #ifdef __PLAT_NGC__ + case Config::HARDWARE_NGC: + OSReport("%s",p_buffer); + break; + #endif + + #ifdef __PLAT_XBOX__ + case Config::HARDWARE_XBOX: + OutputDebugString(p_buffer); + break; + #endif + + default: + break; + } + + return ret; +} + + +uint32 quick_check_checksum(uint32 _i, const char *_s, const char *f, int line); + +uint32 check_checksum(uint32 _i, const char *_s, const char *f, int line) +{ + #ifdef __PLAT_NGPS__ + uint32* ra; + asm ( "daddu %0, $31, $0" : "=r" (ra) ); + + Dbg_MsgAssert(_i == Crc::GenerateCRCFromString(_s),("%s:%d: Checksum 0x%x does not match %s, should be 0x%x",f,line,_i,_s,Crc::GenerateCRCFromString(_s))); + + // After the assert has been run once, there is no need to run it again, so + // patch up the calling code to call quick_check_checksum instead + // we just need to inspect the code just before ra, + // make sure it contains the instructions for calling "check_checksum" + // and replace it with the equivalent for "quick_check_checksum" + // This will still all get compiled out for the release build + // but this method lets us validata all usages of CRCD that get called. + + // Note, due to I-Cache, this code might still be executed several times + // as the routine will continue to be called until the cache entry for the + // call is overwritten + + + uint32 JAL_check_checksum = 0x0c000000 + ((uint32)check_checksum >> 2); + uint32 JAL_quick_check_checksum = 0x0c000000 + ((uint32)quick_check_checksum >> 2); + + if (ra[-2] == JAL_check_checksum) + { + ra[-2] = JAL_quick_check_checksum; + } + + return quick_check_checksum(_i,_s,f,line); + #else + return _i; + #endif +} + +uint32 quick_check_checksum(uint32 _i, const char *_s, const char *f, int line) +{ + return _i; +} + + diff --git a/Code/Core/Debug/Mem_stat.h b/Code/Core/Debug/Mem_stat.h new file mode 100644 index 0000000..5cd98fc --- /dev/null +++ b/Code/Core/Debug/Mem_stat.h @@ -0,0 +1,102 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Host (HOST_) ** +** ** +** File name: core/host.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_DEBUG_MEM_STAT_H +#define __CORE_DEBUG_MEM_STAT_H + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Type Defines ** +*****************************************************************************/ + +class Dbg_MEMORY_STATS +{ + +public : + + Dbg_MEMORY_STATS ( uint total = 0, uint spike = 0, + uint system = 0, uint fixed = 0 ); + +private : + + uint total; // total memory currently allocated on behalf of this module/project + uint spike; // peak total memory allocated on behalf of this module/project + uint system; // current module/project private memory that is adjustable by the user + uint fixed; // current module/project private memory that is a fixed overhead + +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +inline Dbg_MEMORY_STATS::Dbg_MEMORY_STATS ( uint total, uint spike, + uint system, uint fixed ) + : total ( total ), spike ( spike ), + system ( system ), fixed ( fixed ) +{ +} + +#endif // __NOPT_DEBUG__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#endif // __CORE_DEBUG_SIGNATRS_H + + + + diff --git a/Code/Core/Debug/Messages.h b/Code/Core/Debug/Messages.h new file mode 100644 index 0000000..4343d3b --- /dev/null +++ b/Code/Core/Debug/Messages.h @@ -0,0 +1,199 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (Dbg_) ** +** ** +** File name: core/debug/messages.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_DEBUG_MESSAGES_H +#define __CORE_DEBUG_MESSAGES_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +#include +#include +#include + +#ifdef __NOPT_ASSERT__ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ +namespace Dbg +{ + + +enum Level +{ + vERROR, + vWARNING, + vNOTIFY, + vMESSAGE, + vPRINTF +}; + +enum Mask +{ + mERROR = (1< mask ); +void message( char* text, ...); +void notify ( char* text, ...); +void warning( char* text, ...); +void error ( char* text, ...); + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +} // namespace Dbg + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + +#if (defined ( __PLAT_XBOX__ ) || defined( __PLAT_WN32__ )) + +inline void Dbg_SetOutput( const char* A ... ) {}; +#define Dbg_LevelMask(A) { Dbg::level_mask(A); } + +inline void Dbg_Printf( const char* A ... ) {}; +inline void Dbg_Message( const char* A ... ) {}; +inline void Dbg_Notify( const char* A ... ) {}; +inline void Dbg_Warning( const char* A ... ) {}; +inline void Dbg_Error( const char* A ... ) {}; + +#else + +#define Dbg_SetOutput(A...) { Dbg::set_output(##A); } +#define Dbg_LevelMask(A) { Dbg::level_mask(A); } + + +#ifdef __NOPT_DEBUG__ +#define Dbg_Printf(A...) { printf(##A); } +#define Dbg_Message(A...) { Dbg::current_sig = &Dbg_signature; Dbg::message(##A); } +#define Dbg_Notify(A...) { Dbg::current_sig = &Dbg_signature; Dbg::notify(##A); } +#define Dbg_Warning(A...) { Dbg::current_sig = &Dbg_signature; Dbg::warning(##A); } +#define Dbg_Error(A...) { Dbg::current_sig = &Dbg_signature; Dbg::error(##A); } +#else +#define Dbg_Printf(A...) { printf(##A); } +#define Dbg_Message(A...) { Dbg::message(##A); } +#define Dbg_Notify(A...) { Dbg::notify(##A); } +#define Dbg_Warning(A...) { Dbg::warning(##A); } +#define Dbg_Error(A...) { Dbg::error(##A); } +#endif + +#endif // __PLAT_XBOX__ + +/***************************************************************************** +** Stubs ** +*****************************************************************************/ + +#else + +#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ )) +inline void Dbg_SetOutput( const char* A ... ) {}; +#define Dbg_LevelMask(A) +inline void Dbg_Printf( const char* A ... ) {}; +inline void Dbg_Message( const char* A ... ) {}; +inline void Dbg_Notify( const char* A ... ) {}; +inline void Dbg_Warning( const char* A ... ) {}; +inline void Dbg_Error( const char* A ... ) {}; +#else +#define Dbg_SetOutput(A...) +#define Dbg_LevelMask(A) +#define Dbg_Printf(A...) +#define Dbg_Message(A...) +#define Dbg_Notify(A...) +#define Dbg_Warning(A...) +#define Dbg_Error(A...) +#endif + +#endif // __NOPT_MESSAGES__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +// A special printf function that only works for Ryan +// (since I love them so much) + +#if defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ ) +inline void Ryan(const char* A ...) {}; +inline void Ken(const char* A ...) {}; +inline void Matt(const char* A ...) {}; +#else + +#ifdef __USER_RYAN__ +#define Ryan(A...) printf(##A) +#else +#define Ryan(A...) +#endif + +#if defined(__USER_KEN__) && defined(__NOPT_DEBUG__) +#define Ken(A...) printf(##A) +#else +#define Ken(A...) +#endif + +#if defined(__USER_MATT__) && defined(__NOPT_DEBUG__) +#define Matt(A...) printf(##A) +#else +#define Matt(A...) +#endif + +#endif + +#endif // __CORE_DEBUG_MESSAGES_H diff --git a/Code/Core/Debug/Module.h b/Code/Core/Debug/Module.h new file mode 100644 index 0000000..4a67c16 --- /dev/null +++ b/Code/Core/Debug/Module.h @@ -0,0 +1,183 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Confidential Information ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (Dbg_) ** +** ** +** File name: core/debug/module.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_DEBUG_MODULE_H +#define __CORE_DEBUG_MODULE_H + + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include "mem_stat.h" + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +// Unfortunately, Visual C++ does not support the __PRETTY_FUNCTION__ predefined +// name. The most information we can get at is __FILE__. +#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ )) +#define __PRETTY_FUNCTION__ __FILE__ +#endif + +/***************************************************************************** +** Type Defines ** +*****************************************************************************/ + +namespace Dbg +{ + +class Project; + +class Module +{ + +public : + + Module ( Project& proj, const char& prefix, const char& description ); + + void SetNext ( const Module* next ); + void SetSibling ( const Module* sibling ); + + const Module* GetNext ( void ) const; + const Module* GetSibling ( void ) const; + +private : + + const Module* m_next; // pointer to next registered module + const Module* m_sibling; // pointer to next module in same project + + const char& m_prefix; // module prefix + const char& m_description; // module description + + Project& m_project; // project that this modules belongs to + Dbg_MEMORY_STATS m_stats; +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +extern Module* RegisteredModules; + +} // namespace Dbg + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +namespace Dbg +{ + +inline Module::Module( Project& proj, const char& pref, const char& desc ) +: m_prefix ( pref ), + m_description ( desc ), + m_project ( proj ) + +{ + m_next = Dbg::RegisteredModules; // add module to main registration list + Dbg::RegisteredModules = this; + + m_sibling = m_project.GetChildren(); // add module to its project parent + m_project.SetChildren( this ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Module::SetNext( const Module* next ) +{ + m_next = next; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Module::SetSibling( const Module* sibling ) +{ + m_sibling = sibling; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline const Module* Module::GetNext( void ) const +{ + return m_next; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline const Module* Module::GetSibling( void ) const +{ + return m_sibling; +} + +} // namespace Dbg + +/***************************************************************************** +** Stubs ** +*****************************************************************************/ + +#else + +#endif //__NOPT_DEBUG__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#endif // __CORE_DEBUG_MODULE_H + + + + diff --git a/Code/Core/Debug/NGPS/P_debug.cpp b/Code/Core/Debug/NGPS/P_debug.cpp new file mode 100644 index 0000000..a2dd675 --- /dev/null +++ b/Code/Core/Debug/NGPS/P_debug.cpp @@ -0,0 +1,341 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** File name: p_debug.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Platform specific debug code ** +** ** +*****************************************************************************/ + +#include +#include +#include +#include + + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +extern char _dbg_start[]; +extern char _dbg_end[]; +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Dbg +{ + + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +#ifdef __NOPT_FULL_DEBUG__ + +enum +{ + vMAX_CLASS_NODES = 28000, + vMAX_INSTANCE_NODES = 100000 +}; + +static const int vFREE_CLASS_BLOCK_ID = 0x02030405; +static const int vFREE_INST_BLOCK_ID = 0x27354351; + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +struct FreeNode +{ + FreeNode* next; + int id; + +} ; + +union ClassNodeBlock +{ + char pad[sizeof(Spt::ClassNode)]; + FreeNode free_node; + +}; + +union InstanceNodeBlock +{ + char pad[sizeof(Spt::ClassNode::InstanceNode)]; + FreeNode free_node; + +}; + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +static ClassNodeBlock* spClassNodeFreeList = NULL; +static InstanceNodeBlock* spInstanceNodeFreeList = NULL; + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +void set_up( void ) +{ + + + spClassNodeFreeList = (ClassNodeBlock*)_dbg_start; + + ClassNodeBlock* p_class_node = spClassNodeFreeList; + + for( int i = 0; i < ( vMAX_CLASS_NODES - 1 ); i++ ) + { + p_class_node->free_node.id = vFREE_CLASS_BLOCK_ID; + p_class_node->free_node.next = (FreeNode*)(++p_class_node); + } + + p_class_node->free_node.id = vFREE_CLASS_BLOCK_ID; + (p_class_node++)->free_node.next = (FreeNode*)spClassNodeFreeList; + + + spInstanceNodeFreeList = (InstanceNodeBlock*)(++p_class_node); + InstanceNodeBlock* p_inst_node = spInstanceNodeFreeList; + + for( int i = 0; i < ( vMAX_INSTANCE_NODES - 1 ) ; i++ ) + { + p_inst_node->free_node.id = vFREE_INST_BLOCK_ID; + p_inst_node->free_node.next = (FreeNode*)(++p_inst_node); + } + p_inst_node->free_node.id = vFREE_INST_BLOCK_ID; + (p_inst_node++)->free_node.next = (FreeNode*)spInstanceNodeFreeList; + + + Dbg_MsgAssert( (int)p_inst_node <= (int)_dbg_end, + "Dbg Mem Block not big enough (%d bytes too small)", (int)p_inst_node - (int)_dbg_end ); + + if ( (int)p_inst_node < ((int)_dbg_end)) + { + Dbg_Warning ( "%dbytes unused in Dbg mem block",((int)_dbg_end - (int)p_inst_node)); + } + + Dbg_Notify ( "Dbg Mem Block Allocated successfully" ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void close_down( void ) +{ + + + int count = 0; + ClassNodeBlock* p_class_node = spClassNodeFreeList; + InstanceNodeBlock* p_inst_node = spInstanceNodeFreeList; + + do + { + Dbg_MsgAssert ( p_class_node->free_node.id == vFREE_CLASS_BLOCK_ID, "Block not free" ); + p_class_node = (ClassNodeBlock*)p_class_node->free_node.next; + count++; + } while ( p_class_node != spClassNodeFreeList ); + + Dbg_MsgAssert ( count == vMAX_CLASS_NODES, + "%d Class Nodes still registered", vMAX_CLASS_NODES - count ); + + count = 0; + + do + { + Dbg_MsgAssert ( p_inst_node->free_node.id == vFREE_INST_BLOCK_ID, "Block not free" ); + p_inst_node = (InstanceNodeBlock*)p_inst_node->free_node.next; + count++; + } while ( p_inst_node != spInstanceNodeFreeList ); + + Dbg_MsgAssert ( count == vMAX_INSTANCE_NODES, + "%d Instance Nodes still registered", vMAX_INSTANCE_NODES - count ); + + Dbg_Notify ( "Dbg Mem Block Released successfully" ); +} + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + + +void* NewClassNode( size_t size ) +{ + + + void* p_ret = (void*)spClassNodeFreeList; + + Dbg_MsgAssert ( (int)spClassNodeFreeList != (int)(spClassNodeFreeList->free_node.next), + "ClassNode pool full" ); + + Dbg_MsgAssert ( spClassNodeFreeList->free_node.id == vFREE_CLASS_BLOCK_ID, "Not a Free Class Node" ); + + spClassNodeFreeList = (ClassNodeBlock*)spClassNodeFreeList->free_node.next; +// printf("NewClassNode %p next %p\n",p_ret, spClassNodeFreeList); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void DeleteClassNode( void* pMem ) +{ + + + Dbg_MsgAssert ( (((int)pMem >= (int)_dbg_start ) && (((int)pMem < (int)_dbg_end ))), + "Memory not in Debug block (%p)",pMem ); + + ClassNodeBlock* p_freeblock = (ClassNodeBlock*)pMem; + p_freeblock->free_node.next = (FreeNode*)spClassNodeFreeList; + spClassNodeFreeList = (ClassNodeBlock*)p_freeblock; + + spClassNodeFreeList->free_node.id = vFREE_CLASS_BLOCK_ID; + + +// printf("DeleteClassNode %p next %p\n",spClassNodeFreeList, p_freeblock->free_node.next); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* NewInstanceNode( size_t size ) +{ + + + void* p_ret = (void*)spInstanceNodeFreeList; + + Dbg_MsgAssert ( (int)spInstanceNodeFreeList != (int)(spInstanceNodeFreeList->free_node.next), + "InstanceNode pool full" ); + + Dbg_MsgAssert ( spInstanceNodeFreeList->free_node.id == vFREE_INST_BLOCK_ID, "Not a Free Instance Node" ); + + spInstanceNodeFreeList = (InstanceNodeBlock*)spInstanceNodeFreeList->free_node.next; + + + +// printf("NewInstanceNode %p next %p\n",p_ret, spInstanceNodeFreeList); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void DeleteInstanceNode( void* pMem ) +{ + + + Dbg_MsgAssert ( (((int)pMem >= (int)_dbg_start ) && (((int)pMem < (int)_dbg_end ))), + "Memory not in Debug block (%p)",pMem ); + + InstanceNodeBlock* p_freeblock = (InstanceNodeBlock*)pMem; + p_freeblock->free_node.next = (FreeNode*)spInstanceNodeFreeList; + spInstanceNodeFreeList = (InstanceNodeBlock*)p_freeblock; + + spInstanceNodeFreeList->free_node.id = vFREE_INST_BLOCK_ID; + + +// printf("DeleteInstanceNode %p next %p\n",spInstanceNodeFreeList, p_freeblock->free_node.next); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#else // __NOPT_FULL_DEBUG__ + +void set_up( void ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void close_down( void ) +{ + +} + +#endif // __NOPT_FULL_DEBUG__ + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Dbg + +#endif // __NOPT_DEBUG__ + +#ifdef __NOPT_ASSERT__ + +namespace Dbg +{ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_print( char *text ) +{ + std::printf( text ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_trap( char* mess ) +{ + uint* ptr = reinterpret_cast< uint* >( 0x00000001 ); + + *ptr = 0; +} +} // namespace Dbg + +#endif diff --git a/Code/Core/Debug/Project.h b/Code/Core/Debug/Project.h new file mode 100644 index 0000000..4a096c4 --- /dev/null +++ b/Code/Core/Debug/Project.h @@ -0,0 +1,159 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Confidential Information ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (Dbg_) ** +** ** +** File name: core/debug/project.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + + +#ifndef __CORE_DEBUG_PROJECT_H +#define __CORE_DEBUG_PROJECT_H + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include "mem_stat.h" + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Type Defines ** +*****************************************************************************/ + +namespace Dbg +{ + +class Project +{ + +public : + + Project ( const char& name, const char& description ); + + void SetChildren ( Dbg::Module* children ); + Dbg::Module* GetChildren ( void ) const; + +private : + + const char& name; + const char& description; + + Project* next; // pointer to next registered project + Dbg::Module* children; // pointer to children modules + + Dbg_MEMORY_STATS stats; + +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +extern Dbg::Project* RegisteredProjects; + +} // namespace Dbg + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + +#define Dbg_DefineProject(proj,des) \ + \ +Dbg::Project& Dbg_project_##proj ( void ) \ +{ \ + static Dbg::Project project ( *#proj, *des ); \ + \ + return project; \ +} + \ +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +namespace Dbg +{ + +inline Project::Project ( const char& name, const char& description ) +: name ( name ), description ( description ) +{ + next = RegisteredProjects; + RegisteredProjects = this; + children = NULL; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Project::SetChildren ( Dbg::Module* children_in ) +{ + children = children_in; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Dbg::Module* Project::GetChildren ( void ) const +{ + return children; +} + +} // namespace Dbg + + +/***************************************************************************** +** Stubs ** +*****************************************************************************/ + +#else + +#define Dbg_DefineProject(proj,des) + +#endif //__NOPT_DEBUG__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#endif // __CORE_DEBUG_PROJECT_H + + + + diff --git a/Code/Core/Debug/Signatrs.h b/Code/Core/Debug/Signatrs.h new file mode 100644 index 0000000..54009d1 --- /dev/null +++ b/Code/Core/Debug/Signatrs.h @@ -0,0 +1,124 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (Dbg_) ** +** ** +** File name: core/debug/signatrs.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_DEBUG_SIGNATRS_H +#define __CORE_DEBUG_SIGNATRS_H + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Dbg +{ + +/***************************************************************************** +** Type Defines ** +*****************************************************************************/ + +class Module; + +class Signature +{ + +public : + Signature( char* name, const Module& module ); + Signature( const char* cl, char* name, const Module& module ); + + const char& GetName( void ) const ; + +private : + + char* m_name; // function's name + const Module& m_module; // function's module +}; + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +inline Signature::Signature( char* name, const Module& module ) +: m_name ( name ), + m_module ( module ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline const char& Signature::GetName( void ) const +{ + + return *m_name; +} + +} // namespace Dbg + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +#ifdef __PLAT_NGC__ +extern Dbg::Module& Dbg_module; +extern Dbg::Signature Dbg_signature; +#endif + +#ifndef __NOPT_STRICT_SIGNATURES__ + +extern Dbg::Module& Dbg_module; +extern Dbg::Signature Dbg_signature; + +#endif + +#endif // __NOPT_DEBUG__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#endif // __CORE_DEBUG_SIGNATRS_H diff --git a/Code/Core/Debug/Wn32/P_debug.cpp b/Code/Core/Debug/Wn32/P_debug.cpp new file mode 100644 index 0000000..f7fcebb --- /dev/null +++ b/Code/Core/Debug/Wn32/P_debug.cpp @@ -0,0 +1,155 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** File name: p_debug.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Platform specific debug code ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Dbg +{ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ +#ifdef __NOPT_DEBUG__ + +void set_up ( void ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void close_down ( void ) +{ + +} +#endif // __NOPT_DEBUG__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_print ( char *text ) +{ + +#ifdef __PLAT_WN32__ + + OutputDebugString ( text ); + +#else + + printf ( text ); + +#endif // __CC_VISUALC__ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_trap ( char* message ) +{ + +#ifdef __CC_VISUALC__ +#ifdef __ALLOW_CONTINUE__ + + switch ( MessageBox ( GetActiveWindow(), message, "Assertion Failure - Trigger Debugger ?", + MB_DEFBUTTON1 | MB_YESNO | MB_ICONEXCLAMATION )) + { + case IDYES: + + __asm int 3; // trigger the debugger + + break; + + } + +#else // __ALLOW_CONTINUE__ + + MessageBox ( GetActiveWindow(), message, "Assertion Failure", + MB_DEFBUTTON1 | MB_ICONEXCLAMATION ); + + __asm int 3; + +#endif // __ALLOW_CONTINUE__ + + + +#else // __CC_VISUALC__ + + exit(-10); + +#endif // __CC_VISUALC__ +} + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ +} + diff --git a/Code/Core/Debug/XBox/p_debug.cpp b/Code/Core/Debug/XBox/p_debug.cpp new file mode 100644 index 0000000..d0b31e2 --- /dev/null +++ b/Code/Core/Debug/XBox/p_debug.cpp @@ -0,0 +1,341 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** File name: p_debug.cpp ** +** ** +** Created by: 09/25/00 - dc ** +** ** +** Description: Platform specific debug code ** +** ** +*****************************************************************************/ + +#include +#include +#include +#include + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +extern char _dbg_start[]; +extern char _dbg_end[]; +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Dbg +{ + +Dbg_Module // module this code belongs to + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +#ifdef __NOPT_FULL_DEBUG__ + +enum +{ + vMAX_CLASS_NODES = 28000, + vMAX_INSTANCE_NODES = 100000 +}; + +static const int vFREE_CLASS_BLOCK_ID = 0x02030405; +static const int vFREE_INST_BLOCK_ID = 0x27354351; + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +struct FreeNode +{ + FreeNode* next; + int id; + +} ; + +union ClassNodeBlock +{ + char pad[sizeof(Spt::ClassNode)]; + FreeNode free_node; + +}; + +union InstanceNodeBlock +{ + char pad[sizeof(Spt::ClassNode::InstanceNode)]; + FreeNode free_node; + +}; + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +static ClassNodeBlock* spClassNodeFreeList = NULL; +static InstanceNodeBlock* spInstanceNodeFreeList = NULL; + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +void set_up( void ) +{ + Dbg_Function; + + spClassNodeFreeList = (ClassNodeBlock*)_dbg_start; + + ClassNodeBlock* p_class_node = spClassNodeFreeList; + + for( int i = 0; i < ( vMAX_CLASS_NODES - 1 ); i++ ) + { + p_class_node->free_node.id = vFREE_CLASS_BLOCK_ID; + p_class_node->free_node.next = (FreeNode*)(++p_class_node); + } + + p_class_node->free_node.id = vFREE_CLASS_BLOCK_ID; + (p_class_node++)->free_node.next = (FreeNode*)spClassNodeFreeList; + + + spInstanceNodeFreeList = (InstanceNodeBlock*)(++p_class_node); + InstanceNodeBlock* p_inst_node = spInstanceNodeFreeList; + + for( int i = 0; i < ( vMAX_INSTANCE_NODES - 1 ) ; i++ ) + { + p_inst_node->free_node.id = vFREE_INST_BLOCK_ID; + p_inst_node->free_node.next = (FreeNode*)(++p_inst_node); + } + p_inst_node->free_node.id = vFREE_INST_BLOCK_ID; + (p_inst_node++)->free_node.next = (FreeNode*)spInstanceNodeFreeList; + + + Dbg_MsgAssert( (int)p_inst_node <= (int)_dbg_end, + "Dbg Mem Block not big enough (%d bytes too small)", (int)p_inst_node - (int)_dbg_end ); + + if ( (int)p_inst_node < ((int)_dbg_end)) + { + Dbg_Warning ( "%dbytes unused in Dbg mem block",((int)_dbg_end - (int)p_inst_node)); + } + + Dbg_Notify ( "Dbg Mem Block Allocated successfully" ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void close_down( void ) +{ + Dbg_Function; + + int count = 0; + ClassNodeBlock* p_class_node = spClassNodeFreeList; + InstanceNodeBlock* p_inst_node = spInstanceNodeFreeList; + + do + { + Dbg_MsgAssert ( p_class_node->free_node.id == vFREE_CLASS_BLOCK_ID, "Block not free" ); + p_class_node = (ClassNodeBlock*)p_class_node->free_node.next; + count++; + } while ( p_class_node != spClassNodeFreeList ); + + Dbg_MsgAssert ( count == vMAX_CLASS_NODES, + "%d Class Nodes still registered", vMAX_CLASS_NODES - count ); + + count = 0; + + do + { + Dbg_MsgAssert ( p_inst_node->free_node.id == vFREE_INST_BLOCK_ID, "Block not free" ); + p_inst_node = (InstanceNodeBlock*)p_inst_node->free_node.next; + count++; + } while ( p_inst_node != spInstanceNodeFreeList ); + + Dbg_MsgAssert ( count == vMAX_INSTANCE_NODES, + "%d Instance Nodes still registered", vMAX_INSTANCE_NODES - count ); + + Dbg_Notify ( "Dbg Mem Block Released successfully" ); +} + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + + +void* NewClassNode( size_t size ) +{ + Dbg_Function; + + void* p_ret = (void*)spClassNodeFreeList; + + Dbg_MsgAssert ( (int)spClassNodeFreeList != (int)(spClassNodeFreeList->free_node.next), + "ClassNode pool full" ); + + Dbg_MsgAssert ( spClassNodeFreeList->free_node.id == vFREE_CLASS_BLOCK_ID, "Not a Free Class Node" ); + + spClassNodeFreeList = (ClassNodeBlock*)spClassNodeFreeList->free_node.next; +// printf("NewClassNode %p next %p\n",p_ret, spClassNodeFreeList); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void DeleteClassNode( void* pMem ) +{ + Dbg_Function; + + Dbg_MsgAssert ( (((int)pMem >= (int)_dbg_start ) && (((int)pMem < (int)_dbg_end ))), + "Memory not in Debug block (%p)",pMem ); + + ClassNodeBlock* p_freeblock = (ClassNodeBlock*)pMem; + p_freeblock->free_node.next = (FreeNode*)spClassNodeFreeList; + spClassNodeFreeList = (ClassNodeBlock*)p_freeblock; + + spClassNodeFreeList->free_node.id = vFREE_CLASS_BLOCK_ID; + + +// printf("DeleteClassNode %p next %p\n",spClassNodeFreeList, p_freeblock->free_node.next); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* NewInstanceNode( size_t size ) +{ + Dbg_Function; + + void* p_ret = (void*)spInstanceNodeFreeList; + + Dbg_MsgAssert ( (int)spInstanceNodeFreeList != (int)(spInstanceNodeFreeList->free_node.next), + "InstanceNode pool full" ); + + Dbg_MsgAssert ( spInstanceNodeFreeList->free_node.id == vFREE_INST_BLOCK_ID, "Not a Free Instance Node" ); + + spInstanceNodeFreeList = (InstanceNodeBlock*)spInstanceNodeFreeList->free_node.next; + + + +// printf("NewInstanceNode %p next %p\n",p_ret, spInstanceNodeFreeList); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void DeleteInstanceNode( void* pMem ) +{ + Dbg_Function; + + Dbg_MsgAssert ( (((int)pMem >= (int)_dbg_start ) && (((int)pMem < (int)_dbg_end ))), + "Memory not in Debug block (%p)",pMem ); + + InstanceNodeBlock* p_freeblock = (InstanceNodeBlock*)pMem; + p_freeblock->free_node.next = (FreeNode*)spInstanceNodeFreeList; + spInstanceNodeFreeList = (InstanceNodeBlock*)p_freeblock; + + spInstanceNodeFreeList->free_node.id = vFREE_INST_BLOCK_ID; + + +// printf("DeleteInstanceNode %p next %p\n",spInstanceNodeFreeList, p_freeblock->free_node.next); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#else // __NOPT_FULL_DEBUG__ + +void set_up( void ) +{ + Dbg_Function; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void close_down( void ) +{ + Dbg_Function; +} + +#endif // __NOPT_FULL_DEBUG__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Dbg + +#endif // __NOPT_DEBUG__ + + +#ifdef __NOPT_ASSERT__ + +namespace Dbg +{ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_print( char *text ) +{ + OutputDebugString( text ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_trap( char* message ) +{ + OutputDebugString( message ); + uint* ptr = reinterpret_cast< uint* >( 0x00000001 ); + *ptr = NULL; +} +} // namespace Dbg + +#endif + diff --git a/Code/Core/Debug/log.cpp b/Code/Core/Debug/log.cpp new file mode 100644 index 0000000..03cfd13 --- /dev/null +++ b/Code/Core/Debug/log.cpp @@ -0,0 +1,205 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** File name: log.cpp ** +** ** +** Created by: 09/25/02 - ksh ** +** ** +** Description: Logging code ** +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include +#include + + +#include + +#ifndef __PLAT_WN32__ +#include +#endif // __PLAT_WN32__ + +#ifdef __PLAT_NGC__ +#include +#define _output OSReport +#else +#define _output printf +#endif + +#ifdef __NOPT_ASSERT__ + +#ifdef __PLAT_NGPS__ + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ +// 128 bytes each (sizeof(SLogEntry)) and they come off the debug heap. +#define MAX_LOG_ENTRIES 20000 + +extern char _log_info_start[]; +extern char _log_info_end[]; + +namespace Log +{ +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +// Chosen just to make sizeof(SLogEntry) a nice round 128 +#define MAX_MESSAGE_LENGTH 107 +struct SLogEntry +{ + Tmr::CPUCycles mCPUTime; + const char *mpSourceFileName; + const char *mpFunctionName; + int mLineNumber; + char mpMessage[MAX_MESSAGE_LENGTH+1]; +}; +//char p_foo[sizeof(SLogEntry)/0]; + +struct SLogBufferInfo +{ + // Pointer to the start of the big log buffer, which will be in the debug heap somewhere. + SLogEntry *mpBuffer; + // The total number of entries in the buffer. + int mTotalEntries; + // The number of entries written to so far. This starts at 0 when the game starts, + // and increases till it hits mTotalEntries, then stays at that value. + int mNumEntries; + + // Points to just after the last entry that got added to the buffer. + // So it may not point to a valid SLogEntry, for example when the buffer is filling + // up, or when pTop points to the end of pBuffer. + SLogEntry *mpTop; +}; + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ +static bool sInitialised=false; +static SLogBufferInfo *spLogInfo=NULL; +static SLogEntry *spNotALeak=NULL; + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +void Init() +{ + + if (Config::GotExtraMemory()) + { + Dbg_MsgAssert(!sInitialised,("Tried to call Log::Init twice")); + + Dbg_MsgAssert((uint32)(_log_info_end-_log_info_start) >= sizeof(SLogBufferInfo),("log_info section too small, got=%d, required=%d",(uint32)(_log_info_end-_log_info_start),sizeof(SLogBufferInfo))); + spLogInfo=(SLogBufferInfo*)_log_info_start; + + Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap()); + + spLogInfo->mNumEntries=0; + spLogInfo->mTotalEntries=MAX_LOG_ENTRIES; + spLogInfo->mpBuffer=(SLogEntry*)Mem::Malloc(MAX_LOG_ENTRIES*sizeof(SLogEntry)); + spNotALeak=spLogInfo->mpBuffer; + spLogInfo->mpTop=spLogInfo->mpBuffer; + + uint32 *p_long=(uint32*)spLogInfo->mpBuffer; + Dbg_MsgAssert((sizeof(SLogEntry)&3)==0,("sizeof(SLogEntry) not a multiple of 4")); + for (uint32 i=0; imNumEntries < MAX_LOG_ENTRIES) + { + p_new=spLogInfo->mpTop++; + ++spLogInfo->mNumEntries; + } + else + { + if (spLogInfo->mpTop >= spLogInfo->mpBuffer+MAX_LOG_ENTRIES) + { + spLogInfo->mpTop=spLogInfo->mpBuffer; + } + p_new=spLogInfo->mpTop++; + } + + p_new->mCPUTime=Tmr::GetTimeInCPUCycles(); + p_new->mLineNumber=lineNumber; + p_new->mpFunctionName=p_functionName; + p_new->mpSourceFileName=p_fileName; + if (p_message) + { + Dbg_MsgAssert(strlen(p_message)<=MAX_MESSAGE_LENGTH,("Log message '%s' too long",p_message)); + strcpy(p_new->mpMessage,p_message); + } + else + { + p_new->mpMessage[0]=0; + } + } +} + +} // namespace Log + +#else +namespace Log +{ +void Init() +{ +} + +void AddEntry(char *p_fileName, int lineNumber, char *p_functionName, char *p_message) +{ +} +} // namespace Log +#endif // #ifdef __PLAT_NGPS__ + +#endif //__NOPT_ASSERT__ + diff --git a/Code/Core/Debug/ngc/P_debug.cpp b/Code/Core/Debug/ngc/P_debug.cpp new file mode 100644 index 0000000..3c3d0f3 --- /dev/null +++ b/Code/Core/Debug/ngc/P_debug.cpp @@ -0,0 +1,328 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Debug (DBG) ** +** ** +** File name: p_debug.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Platform specific debug code ** +** ** +*****************************************************************************/ + +#include +#include +#include +#include +#include + + +#ifdef __NOPT_DEBUG__ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +extern char _dbg_start[]; +extern char _dbg_end[]; +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Dbg +{ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +#ifdef __NOPT_FULL_DEBUG__ + +enum +{ + vMAX_CLASS_NODES = 28000, + vMAX_INSTANCE_NODES = 100000 +}; + +static const int vFREE_CLASS_BLOCK_ID = 0x02030405; +static const int vFREE_INST_BLOCK_ID = 0x27354351; + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +struct FreeNode +{ + FreeNode* next; + int id; + +} ; + +union ClassNodeBlock +{ + char pad[sizeof(Spt::ClassNode)]; + FreeNode free_node; + +}; + +union InstanceNodeBlock +{ + char pad[sizeof(Spt::ClassNode::InstanceNode)]; + FreeNode free_node; + +}; + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +static ClassNodeBlock* spClassNodeFreeList = NULL; +static InstanceNodeBlock* spInstanceNodeFreeList = NULL; + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +void set_up( void ) +{ + spClassNodeFreeList = (ClassNodeBlock*)_dbg_start; + + ClassNodeBlock* p_class_node = spClassNodeFreeList; + + for( int i = 0; i < ( vMAX_CLASS_NODES - 1 ); i++ ) + { + p_class_node->free_node.id = vFREE_CLASS_BLOCK_ID; + p_class_node->free_node.next = (FreeNode*)(++p_class_node); + } + + p_class_node->free_node.id = vFREE_CLASS_BLOCK_ID; + (p_class_node++)->free_node.next = (FreeNode*)spClassNodeFreeList; + + + spInstanceNodeFreeList = (InstanceNodeBlock*)(++p_class_node); + InstanceNodeBlock* p_inst_node = spInstanceNodeFreeList; + + for( int i = 0; i < ( vMAX_INSTANCE_NODES - 1 ) ; i++ ) + { + p_inst_node->free_node.id = vFREE_INST_BLOCK_ID; + p_inst_node->free_node.next = (FreeNode*)(++p_inst_node); + } + p_inst_node->free_node.id = vFREE_INST_BLOCK_ID; + (p_inst_node++)->free_node.next = (FreeNode*)spInstanceNodeFreeList; + + + Dbg_MsgAssert( (int)p_inst_node <= (int)_dbg_end, + "Dbg Mem Block not big enough (%d bytes too small)", (int)p_inst_node - (int)_dbg_end ); + + if ( (int)p_inst_node < ((int)_dbg_end)) + { + Dbg_Warning ( "%dbytes unused in Dbg mem block",((int)_dbg_end - (int)p_inst_node)); + } + + Dbg_Notify ( "Dbg Mem Block Allocated successfully" ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void close_down( void ) +{ + int count = 0; + ClassNodeBlock* p_class_node = spClassNodeFreeList; + InstanceNodeBlock* p_inst_node = spInstanceNodeFreeList; + + do + { + Dbg_MsgAssert ( p_class_node->free_node.id == vFREE_CLASS_BLOCK_ID, "Block not free" ); + p_class_node = (ClassNodeBlock*)p_class_node->free_node.next; + count++; + } while ( p_class_node != spClassNodeFreeList ); + + Dbg_MsgAssert ( count == vMAX_CLASS_NODES, + "%d Class Nodes still registered", vMAX_CLASS_NODES - count ); + + count = 0; + + do + { + Dbg_MsgAssert ( p_inst_node->free_node.id == vFREE_INST_BLOCK_ID, "Block not free" ); + p_inst_node = (InstanceNodeBlock*)p_inst_node->free_node.next; + count++; + } while ( p_inst_node != spInstanceNodeFreeList ); + + Dbg_MsgAssert ( count == vMAX_INSTANCE_NODES, + "%d Instance Nodes still registered", vMAX_INSTANCE_NODES - count ); + + Dbg_Notify ( "Dbg Mem Block Released successfully" ); +} + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + + +void* NewClassNode( size_t size ) +{ + void* p_ret = (void*)spClassNodeFreeList; + + Dbg_MsgAssert ( (int)spClassNodeFreeList != (int)(spClassNodeFreeList->free_node.next), + "ClassNode pool full" ); + + Dbg_MsgAssert ( spClassNodeFreeList->free_node.id == vFREE_CLASS_BLOCK_ID, "Not a Free Class Node" ); + + spClassNodeFreeList = (ClassNodeBlock*)spClassNodeFreeList->free_node.next; +// printf("NewClassNode %p next %p\n",p_ret, spClassNodeFreeList); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void DeleteClassNode( void* pMem ) +{ + Dbg_MsgAssert ( (((int)pMem >= (int)_dbg_start ) && (((int)pMem < (int)_dbg_end ))), + "Memory not in Debug block (%p)",pMem ); + + ClassNodeBlock* p_freeblock = (ClassNodeBlock*)pMem; + p_freeblock->free_node.next = (FreeNode*)spClassNodeFreeList; + spClassNodeFreeList = (ClassNodeBlock*)p_freeblock; + + spClassNodeFreeList->free_node.id = vFREE_CLASS_BLOCK_ID; + + +// printf("DeleteClassNode %p next %p\n",spClassNodeFreeList, p_freeblock->free_node.next); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* NewInstanceNode( size_t size ) +{ + void* p_ret = (void*)spInstanceNodeFreeList; + + Dbg_MsgAssert ( (int)spInstanceNodeFreeList != (int)(spInstanceNodeFreeList->free_node.next), + "InstanceNode pool full" ); + + Dbg_MsgAssert ( spInstanceNodeFreeList->free_node.id == vFREE_INST_BLOCK_ID, "Not a Free Instance Node" ); + + spInstanceNodeFreeList = (InstanceNodeBlock*)spInstanceNodeFreeList->free_node.next; + + + +// printf("NewInstanceNode %p next %p\n",p_ret, spInstanceNodeFreeList); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void DeleteInstanceNode( void* pMem ) +{ + Dbg_MsgAssert ( (((int)pMem >= (int)_dbg_start ) && (((int)pMem < (int)_dbg_end ))), + "Memory not in Debug block (%p)",pMem ); + + InstanceNodeBlock* p_freeblock = (InstanceNodeBlock*)pMem; + p_freeblock->free_node.next = (FreeNode*)spInstanceNodeFreeList; + spInstanceNodeFreeList = (InstanceNodeBlock*)p_freeblock; + + spInstanceNodeFreeList->free_node.id = vFREE_INST_BLOCK_ID; + + +// printf("DeleteInstanceNode %p next %p\n",spInstanceNodeFreeList, p_freeblock->free_node.next); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#else // __NOPT_FULL_DEBUG__ + +void set_up( void ) +{ +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void close_down( void ) +{ +} + +#endif // __NOPT_FULL_DEBUG__ + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Dbg + +#endif // __NOPT_DEBUG__ + +#ifdef __NOPT_ASSERT__ + +namespace Dbg +{ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_print( char *text ) +{ + std::printf( text ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void default_trap( char* message ) +{ +// uint* ptr = reinterpret_cast< uint* >( 0x00000001 ); +// +// *ptr = NULL; + + snPause(); +} +} // namespace Dbg + +#endif diff --git a/Code/Core/Defines.h b/Code/Core/Defines.h new file mode 100644 index 0000000..9841693 --- /dev/null +++ b/Code/Core/Defines.h @@ -0,0 +1,559 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Standard Header ** +** ** +** File name: core/defines.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +** All code depends on the definitions in this files ** +** It should be included in every file ** +** ** +*****************************************************************************/ + +#ifndef __CORE_DEFINES_H +#define __CORE_DEFINES_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +#ifdef __PLAT_WN32__ +//#include +//#include +#ifdef __USE_OLD_STREAMS__ +#include +#else +#include +using namespace std; +#endif +#include +#include + +#pragma warning( disable : 4800 ) +#pragma warning( disable : 4355 ) // 'this' : used in base member initializer list +#pragma warning( disable : 4291 ) // no matching operator delete found + + + +#else +#ifdef __PLAT_NGPS__ +#include +#include +//#include +#include +#include +#else +#ifdef __PLAT_NGC__ +#include +#include +#include +#include +#else +#ifdef __PLAT_XBOX__ +#include +//#include +#include +#include + +#pragma warning( disable : 4800 ) +#pragma warning( disable : 4291 ) // no matching operator delete found +#pragma warning( disable : 4355 ) // 'this' : used in base member initializer list +#pragma warning( disable : 4995 ) // name was marked as #pragma deprecated + +#endif +#endif +#endif +#endif + +#ifndef __PLAT_WN32__ +//#ifdef __NOPT_ASSERT__ +#ifdef __PLAT_NGC__ +#ifdef __NOPT_FINAL__ + #define printf(A...) +#else + int OurPrintf(const char *fmt, ...); + #define printf OurPrintf +#endif +#else + int OurPrintf(const char *fmt, ...); + #define printf OurPrintf +#endif + +//#else +// inline void NullPrintf(const char *fmt, ...){} +// #define printf NullPrintf +//#endif +#endif + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +#define vINT8_MAX 0x7F +#define vINT8_MIN 0x81 +#define vINT16_MAX 0x7FFF +#define vINT16_MIN 0x8001 +#define vINT32_MAX 0x7FFFFFFF +#define vINT32_MIN 0x80000001 +#define vINT64_MAX 0x7FFFFFFFFFFFFFFF +#define vINT64_MIN 0x8000000000000001 + +#define vUINT8_MAX 0xFF +#define vUINT16_MAX 0xFFFF +#define vUINT32_MAX 0xFFFFFFFF +#define vUINT64_MAX 0xFFFFFFFFFFFFFFFF + +#ifndef TRUE +#define FALSE 0 +#define TRUE (!FALSE) +#endif + +#ifndef NULL +#define NULL 0 +#endif + +/***************************************************************************** +** Type Defines ** +*****************************************************************************/ + +typedef char int8; +typedef short int16; + +typedef unsigned int uint; +typedef unsigned char uint8; +typedef unsigned short uint16; + +typedef signed int sint; +typedef signed char sint8; +typedef signed short sint16; + +#define vINT_MAX vINT32_MAX +#define vINT_MIN vINT32_MIN +#define vUINT_MAX vUINT32_MAX + +#ifdef __PLAT_WN32__ +typedef long int32; +typedef unsigned long uint32; +typedef signed long sint32; +typedef __int64 int64; +typedef unsigned __int64 uint64; +typedef signed __int64 sint64; + +#endif + +#ifdef __PLAT_NGPS__ +typedef int int32; +typedef unsigned int uint32; +typedef signed int sint32; +typedef long int64; +typedef unsigned long uint64; +typedef signed long sint64; +typedef long128 int128; +typedef u_long128 uint128; +typedef long128 sint128; + +#endif + +#ifdef __PLAT_XBOX__ +typedef long int32; +typedef unsigned long uint32; +typedef signed long sint32; +typedef __int64 int64; +typedef unsigned __int64 uint64; +typedef signed __int64 sint64; + +#endif + +#ifdef __PLAT_NGC__ +typedef int int32; +typedef unsigned int uint32; +typedef signed int sint32; +typedef long long int64; +typedef unsigned long long uint64; +typedef signed long long sint64; +// Paul: No GameCube 128-bit types. +//typedef long128 int128; +//typedef u_long128 uint128; +//typedef long128 sint128; +typedef long long int128; +typedef unsigned long long uint128; +typedef signed long long sint128; + +#endif + +#if defined(__PLAT_NGPS__) || defined(__PLAT_XBOX__) || defined(__PLAT_NGC__) + +class ostream +{ +public: + ostream& operator<< ( char* str ) { printf ( str ); return *this; } + ostream& operator<< ( const char* str ) { printf ( str ); return *this; } + ostream& operator<< ( sint i ) { printf ( "%d", i ); return *this; } + ostream& operator<< ( uint i ) { printf ( "%u", i ); return *this; } + ostream& operator<< ( float f ) { printf ( "%f", f ); return *this; } + ostream& operator<< ( void* p ) { printf ( "%p", p ); return *this; } + ostream& operator<< ( const void* p ) { printf ( "%p", p ); return *this; } +}; + +#endif + +#define vINT_BITS 32 +#define vPTR_BITS 32 + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +// Alignment macros + + +#ifdef __PLAT_NGPS__ +#define nAlign(bits) __attribute__((aligned((bits>>3)))) +#else +#define nAlign(bits) +#endif + +#define nPad64(X) uint64 _pad##X; +#define nPad32(X) uint32 _pad##X; +#define nPad16(X) uint16 _pad##X; +#define nPad8(X) uint8 _pad##X; + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +// version stamping + +#define __nTIME__ __TIME__ +#define __nDATE__ __DATE__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#define nBit(b) ( 1 << (b) ) + +typedef sint nID; +typedef sint8 nID8; +typedef sint16 nID16; +typedef sint32 nID32; +typedef sint64 nID64; + +#define nMakeID(a,b,c,d) ( (nID) ( ( (nID) (a) ) << 24 | ( (nID) (b) ) << 16 | \ + ( (nID) (c) ) << 8 | ( (nID) (d) ) ) ) + + +// nMakeStructID() differs from nMakeID in that struct IDs are always +// readable from a memory dump, where as IDs would be reversed on little +// endian machines + +#if __CPU_BIG_ENDIAN__ +#define nMakeStructID(a,b,c,d) ( (nID) ( ((nID)(a))<<24 | ((nID)(b))<<16 | \ + ((nID)(c))<<8 | ((nID)(d)) )) +#else +#define nMakeStructID(a,b,c,d) ( (ID) ( ((nID)(d))<<24 | ((nID)(c))<<16 | \ + ((nID)(b))<<8 | ((nID)(a)) )) +#endif + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + +#define nReverse32(L) ( (( (L)>>24 ) & 0xff) | (((L)>>8) &0xff00) | (((L)<<8)&0xff0000) | (((L)<<24)&0xff000000)) +#define nReverse16(L) ( (( (L)>>8 ) & 0xff) | (((L)<<8)&0xff00) ) + +#if __CPU_BIG_ENDIAN__ + +#define nBgEn32(L) (L) +#define nBgEn16(L) (L) + +#define nLtEn32(L) nReverse32(L) +#define nLtEn16(L) nReverse16(L) + +#else + +#define nBgEn32(L) nReverse32(L) +#define nBgEn16(L) nReverse16(L) + +#define nLtEn32(L) (L) +#define nLtEn16(L) (L) + +#endif + +#ifdef __PLAT_XBOX__ +#define __PRETTY_FUNCTION__ "?" + +#define isnanf _isnan +#define isinff _isnan + +#endif + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#define __CPU_WORD_BALIGN__ 4 // Memory word byte alignment + +#define PTR_ALIGNMASK ( vUINT_MAX << __CPU_WORD_BALIGN__) + +// The alignment macros align elements for fastest access + +#define nAligned(P) ( !( (uint) (P) & (~PTR_ALIGNMASK) ) ) +#define nAlignDown(P) (void*)( (uint) (P) & PTR_ALIGNMASK ) +#define nAlignUp(P) (void*)( ( (uint) (P) + ( 1 << __CPU_WORD_BALIGN__ ) - 1 ) & PTR_ALIGNMASK ) +#define nAlignedBy(P,A) ( !( (uint) (P) & ( ~(vUINT_MAX << (A) ) ) ) ) +#define nAlignDownBy(P,A) (void*)( (uint) (P) & (vUINT_MAX << (A) ) ) +#define nAlignUpBy(P,A) (void*)( ( (uint) (P) + ( 1 << (A) ) - 1 ) & ( vUINT_MAX <<( A ) ) ) +#define nStorage(X) nAlignUp ( (X) + 1 ) + +/****************************************************************************/ + +#define nAddPointer(P,X) (void*) ( (uint) (P) + (uint) (X) ) +#define nSubPointer(P,X) (void*) ( (uint) (P) - (uint) (X) ) + +/****************************************************************************/ + +// Converts a string into a checksum. This macro can be used for readability. +// Later, for speed, some application can scan all .cpp and .h files, and +// replace all instances of CRCX(...) with corresponding CRCD(...) instances +// +// Example: CRCX("object_id") +#define CRCX(_s) Script::GenerateCRC(_s) +// This macro exists simply for readability. Whenever you see a CRCD(...) instance, +// you will know what string the checksum maps to. CRCD(...) instances in the code +// can be generated from CRCX(...), and can be reverse mapped if desired. +// +// Example: CRCD(0xdcd2a9d4, "object_id") + +#include + + + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + + + +#include +#include + +#include + +#ifdef __PLAT_NGPS__ +#include +#include "libsn.h" +#elif defined( __PLAT_NGC__ ) +#include +//#include "libsn.h" +#elif defined( __PLAT_XBOX__ ) +#include +#endif + + +// Mick: This check slows the game down quite a bit +#if 1 && defined( __NOPT_ASSERT__ ) + +extern uint32 check_checksum(uint32 _i, const char *_s, const char *f, int line); + +#define CRCD(_i, _s) check_checksum(_i, _s, __FILE__, __LINE__) +#else +#define CRCD(_i, _s) _i +#endif + +// CRC-C, for use only in switch statements, where you want to use the same syntax as CRCD +#define CRCC(_i, _s) _i + + + +//#ifdef __PLAT_NGC__ +//class TCPPInit +//{ +//public: +// static bool IsHeapInitialized ; +//} ; +// +//// these definitions override the new and delete operators. +// +#ifdef __PLAT_NGC__ +static inline void* operator new ( size_t blocksize ) ; + +static inline void* operator new[] ( size_t blocksize ) ; + +static inline void operator delete ( void* block ) ; + +static inline void operator delete[] ( void* block ) ; +#endif // __PLAT_NGC__ +// +//#else + +#ifdef __PLAT_WN32__ +inline void* operator new( size_t size, bool assert_on_fail ) +{ + return new char[size]; +} + +#else +#ifndef __PLAT_NGC__ +/******************************************************************/ +/* Global new/delete operators */ +/* */ +/******************************************************************/ +inline void* operator new( size_t size ) +{ + return Mem::Manager::sHandle().New( size, true ); +} +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void* operator new[] ( size_t size ) +{ + return Mem::Manager::sHandle().New( size, true ); +} +#endif // __PLAT_NGC__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void* operator new( size_t size, bool assert_on_fail ) +{ + return Mem::Manager::sHandle().New( size, assert_on_fail ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void* operator new[] ( size_t size, bool assert_on_fail ) +{ + return Mem::Manager::sHandle().New( size, assert_on_fail ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void* operator new( size_t size, Mem::Allocator* pAlloc, bool assert_on_fail = true ) +{ + return Mem::Manager::sHandle().New( size, assert_on_fail, pAlloc ); +} +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void* operator new[]( size_t size, Mem::Allocator* pAlloc, bool assert_on_fail = true ) +{ + return Mem::Manager::sHandle().New( size, assert_on_fail, pAlloc ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void* operator new( size_t size, void* pLocation ) +{ + return pLocation; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void* operator new[]( size_t size, void* pLocation ) +{ + return pLocation; +} + +#ifndef __PLAT_NGC__ +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void operator delete( void* pAddr ) +{ + Mem::Manager::sHandle().Delete( pAddr ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void operator delete[]( void* pAddr ) +{ + Mem::Manager::sHandle().Delete( pAddr ); +} +#endif // __PLAT_NGC__ + +//#ifdef __PLAT_NGC__ +/******************************************************************/ +/* only used when exception is thrown in constructor */ +/* */ +/****************************************************************** + +inline void operator delete( void* pAddr, Mem::Allocator* pAlloc ) +{ + Mem::Manager * mem_man = Mem::Manager::Instance(); + Mem::Manager::sHandle().Delete( pAddr ); +} + +/******************************************************************/ +/* */ +/* */ +/****************************************************************** + +inline void operator delete[]( void* pAddr, Mem::Allocator* pAlloc ) +{ + Mem::Manager * mem_man = Mem::Manager::Instance(); + Mem::Manager::sHandle().Delete( pAddr ); +} + +/******************************************************************/ +/* */ +/* */ +/****************************************************************** + +inline void operator delete( void*, void* pLocation ) +{ + return; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +//#endif // __PLAT_NGC__ +#endif // __PLAT_WN32__ +#endif // __CORE_DEFINES_H + + + + diff --git a/Code/Core/DynamicTable.cpp b/Code/Core/DynamicTable.cpp new file mode 100644 index 0000000..589586e --- /dev/null +++ b/Code/Core/DynamicTable.cpp @@ -0,0 +1,87 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: List ** +** ** +** File name: ** +** ** +** Created by: ** +** ** +** Description: ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +// + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Lst +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +/****************************************************************************** + * + * Function: + * + * Description: Default constructor + * + * Parameters: + * + *****************************************************************************/ + + +} // namespace Lst + + + + + diff --git a/Code/Core/DynamicTable.h b/Code/Core/DynamicTable.h new file mode 100644 index 0000000..83bfa5d --- /dev/null +++ b/Code/Core/DynamicTable.h @@ -0,0 +1,274 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: DynamicTable ** +** ** +** File name: Core\DynamicTable.h ** +** ** +** Created by: 12/14/2000 - rjm ** +** ** +** Description: A handy DynamicTable class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_DYNAMICTABLE_H +#define __CORE_LIST_DYNAMICTABLE_H + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +namespace Lst +{ + + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +nTemplateBaseClass(_V, DynamicTable) +{ + Dbg_TemplateBaseClass(_V, DynamicTable); + +public: + DynamicTable(int size, int grow_increment = 8); + ~DynamicTable(); + + // it goes on the end of the table + void Add(_V *pItem); + int GetSize(); + void Reset(); + + _V &Last(); + + void Replace(int index, _V *pNewItem); + void Remove(int index); + +/* + bool IsEmpty(int index); +*/ + + _V &operator[](int index); + +private: + _V ** mpp_table; + int m_size; + int m_entriesFilled; + int m_growIncrement; +}; + + + +template +class DynamicTableDestroyer +{ +public: + DynamicTableDestroyer(DynamicTable<_V> *pTable); + + void DeleteTableContents(); + +private: + DynamicTable<_V> * mp_table; +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template +DynamicTable<_V>::DynamicTable(int size, int grow_increment) +{ + m_size = size; + m_growIncrement = grow_increment; + mpp_table = (_V**) Mem::Malloc(sizeof(_V *) * m_size); + //mpp_table = new _V*[m_size]; + for (int i = 0; i < m_size; i++) + mpp_table[i] = NULL; + + m_entriesFilled = 0; +} + + + +template +DynamicTable<_V>::~DynamicTable() +{ + Mem::Free(mpp_table); + //delete [] mpp_table; +} + + + +template +void DynamicTable<_V>::Add(_V* pItem) +{ + + Dbg_MsgAssert(pItem,( "can't add NULL item to table")); + + // do we need to create a new table? + if (m_size <= m_entriesFilled) + { + // create new table + int new_size = m_size + m_growIncrement; + _V **ppTemp = (_V**) Mem::Malloc(sizeof(_V *) * new_size); + //_V **ppTemp = new _V*[new_size]; + + // copy contents of old one + int i; + for (i = 0; i < m_size; i++) + ppTemp[i] = mpp_table[i]; + for (i = m_size; i < new_size; i++) + ppTemp[i] = NULL; + + Mem::Free(mpp_table); + //delete [] mpp_table; + mpp_table = ppTemp; + m_size = new_size; + } + + mpp_table[m_entriesFilled++] = pItem; +} + + + +template +int DynamicTable<_V>::GetSize() +{ + return m_entriesFilled; +} + + + +template +void DynamicTable<_V>::Reset() +{ + m_entriesFilled = 0; +} + + + +template +void DynamicTable<_V>::Replace(int index, _V *pNewItem) +{ + + Dbg_MsgAssert(index >= 0 && index < m_entriesFilled,( "index out of bounds in DynamicTable")); + Dbg_AssertPtr(pNewItem); + mpp_table[index] = pNewItem; +} + + +template +void DynamicTable<_V>::Remove(int index) +{ + Dbg_MsgAssert(index >= 0 && index < m_entriesFilled,( "index out of bounds in DynamicTable")); + + delete mpp_table[index]; + + for ( int i = index; i < m_entriesFilled-1; i++ ) + { + mpp_table[i] = mpp_table[i+1]; + } + mpp_table[m_entriesFilled-1] = NULL; + m_entriesFilled--; +} + +/* +template +bool DynamicTable<_V>::IsEmpty(int index) +{ + + Dbg_MsgAssert(index >= 0 && index < m_entriesFilled,( "index out of bounds in DynamicTable")); + if (mpp_table[index]) return false; + return true; +} +*/ + + + +template +_V &DynamicTable<_V>::operator[](int index) +{ + + Dbg_MsgAssert(index >= 0 && index < m_entriesFilled,( "index %d out of bounds in DynamicTable", index)); + Dbg_MsgAssert(mpp_table[index],( "table entry is NULL")); + Dbg_AssertPtr(mpp_table[index]); + + return *mpp_table[index]; +} + + + +template +_V &DynamicTable<_V>::Last() +{ + + Dbg_MsgAssert(m_entriesFilled > 0,( "empty list")); + + return *mpp_table[m_entriesFilled-1]; +} + + + +template +DynamicTableDestroyer<_V>::DynamicTableDestroyer(DynamicTable<_V> *pTable) +{ + mp_table = pTable; +} + + + +template +void DynamicTableDestroyer<_V>::DeleteTableContents() +{ + int size = mp_table->GetSize(); + for (int i = 0; i < size; i++) + { + //if (!mp_table->IsEmpty()) + delete &(*mp_table)[i]; + } + mp_table->Reset(); +} + + + +} // namespace Lst + +#endif // __CORE_LIST_DYNAMICTABLE_H + + diff --git a/Code/Core/HashTable.h b/Code/Core/HashTable.h new file mode 100644 index 0000000..33fc12f --- /dev/null +++ b/Code/Core/HashTable.h @@ -0,0 +1,515 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: HashTable ** +** ** +** File name: Core\HashTable.h ** +** ** +** Created by: 9/22/2000 - rjm ** +** ** +** Description: A handy Hashtable class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_HASHTABLE_H +#define __CORE_LIST_HASHTABLE_H + +#ifndef __CORE_DEFINES_H + #include +#endif + +#ifndef __PLAT_WN32__ + #include +#endif + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Lst +{ + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + + +template< class _V > class HashTable; + + +template< class _V > class HashItem +{ + friend class HashTable<_V>; + +private: + HashItem(); + void Init(); + + uint32 m_key; + _V * mp_value; + HashItem<_V> * mp_next; +}; + + +template class HashTable +{ + typedef void (*HashCallback)(_V*, void*); + +public: + HashTable(uint32 numBits); + ~HashTable(); + + // if any item exists with the same key, replace it + bool PutItem(const uint32 &key, _V *item); + // delete the item, and remove it from the table + void FlushItem(const uint32 &key); + // print all instances of an item (debugging) + void PrintItem(const uint32 &key); + // gets a pointer to requested item, returns NULL if item not in table + _V * GetItem(const uint32 &key, bool assert_if_clash = true); + _V * GetNextItemWithSameKey(const uint32 &key, _V *p_item); + void FlushAllItems(void); + void HandleCallback(HashCallback, void* pData); + int GetSize(){return m_size;} + + void IterateStart(); + _V * IterateNext(uint32 *pRetKey = NULL); + +#ifdef __NOPT_ASSERT__ + void PrintContents(); +#endif + + void AllowDuplicateKeys(bool allowed) + { + m_allowDuplicateKeys = allowed; + } + +protected: + uint32 m_numBits; // resolution of hash table + HashItem<_V> * mp_hash_table; + int m_size; // number of entries in the table + + int m_iterator_index; + HashItem<_V> * mp_iterator_item; + bool m_allowDuplicateKeys; +}; + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template //inline +HashTable<_V>::HashTable(uint32 numBits) +{ + //Ryan("Creating HashTable"); + + m_numBits = numBits; + mp_hash_table = new HashItem<_V>[1< //inline +HashTable<_V>::~HashTable() +{ + + //Ryan("Destroying HashTable"); + + Dbg_AssertPtr( mp_hash_table ); + if (!mp_hash_table) + return; + + FlushAllItems(); + + // Remove the table. +// Mem::Free(mp_hash_table); + delete[] mp_hash_table; + mp_hash_table=NULL; +} + +template //inline +bool HashTable<_V>::PutItem(const uint32 &key, _V *item) +{ + Dbg_AssertPtr(item); + //Ryan("putting item in Hash table\n"); + + Dbg_AssertPtr( mp_hash_table ); + + // sometimes, we want to allow checksum conflicts + // (for instance, ConvertAssets' file database) + if ( !m_allowDuplicateKeys ) + { + Dbg_MsgAssert(!GetItem(key), ("item 0x%x already in hash table", key)); + } + + // can't add an item of 0,NULL, as that is used to indicate an empty head slot + Dbg_MsgAssert(key || item, ("Both key and item are 0 (NULL) in hash table")); + // can have a value of NULL either, as the the test below uses pEntry->mp_value == NULL + // to indicate an an empty head slot + // We could just change it to ( pEntry->mp_value || pEntry->m_key ) if NULL values are desirable + Dbg_MsgAssert( item, ("NULL item added to hash table")); + + HashItem<_V> *pEntry=&mp_hash_table[key&((1<mp_value ) + { + // The main table entry is already occupied, so create a new HashEntry and + // link it in between the first and the rest. +#ifndef __PLAT_WN32__ + HashItem<_V> *pNew = new (Mem::PoolManager::SCreateItem(Mem::PoolManager::vHASH_ITEM_POOL)) HashItem<_V>(); +#else + HashItem<_V> *pNew = new HashItem<_V>; +#endif + pNew->m_key = key; + pNew->mp_value = item; + pNew->mp_next = pEntry->mp_next; + pEntry->mp_next = pNew; + } + else + { + // Main table entry is not occupied, so wack it in there. + pEntry->m_key = key; + pEntry->mp_value = item; + // leave pEntry->mp_next untouched + } + + m_size++; + return true; +} + + +template //inline +_V *HashTable<_V>::GetNextItemWithSameKey(const uint32 &key, _V *p_item) +{ + Dbg_AssertPtr( mp_hash_table ); + + // Jump to the linked list of all entries with similar checksums. + HashItem<_V> *pEntry=&mp_hash_table[key&((1<m_key == key && pEntry->mp_value==p_item) + { + break; + } + pEntry=pEntry->mp_next; + } + if (!pEntry) + { + // p_item was not found. + return NULL; + } + + // Found p_item, so search the rest of the list for the next element with the same key. + pEntry=pEntry->mp_next; + while (pEntry) + { + if (pEntry->m_key == key && pEntry->mp_value) + { + return pEntry->mp_value; + } + pEntry=pEntry->mp_next; + } + + return NULL; +} + +template //inline +_V *HashTable<_V>::GetItem(const uint32 &key, bool assert_if_clash) +{ + Dbg_AssertPtr( mp_hash_table ); + + // Jump to the linked list of all entries with similar checksums. + HashItem<_V> *pEntry=&mp_hash_table[key&((1<m_key == key && pEntry->mp_value) + { + return (_V *) pEntry->mp_value; + } + pEntry=pEntry->mp_next; + } + + return NULL; +} + + +template //inline +void HashTable<_V>::PrintItem(const uint32 &key) +{ + Dbg_AssertPtr( mp_hash_table ); + + // Jump to the linked list of all entries with similar checksums. + HashItem<_V> *pEntry=&mp_hash_table[key&((1<m_key == key && pEntry->mp_value) + { + printf ("%d: Entry for 0x%x at %p\n", n ,key,(_V *) pEntry->mp_value); + n++; + } + pEntry=pEntry->mp_next; + } + +} + + + + +template //inline +void HashTable<_V>::FlushItem(const uint32 &key) +{ + + Dbg_AssertPtr( mp_hash_table ); + if (!mp_hash_table) + return; + + // Jump to the linked list of all entries with similar checksums. + HashItem<_V> *pEntry=&mp_hash_table[key&((1< *pLast = NULL; + + // Scan through the small list until the matching entry is found. + while (pEntry) + { + HashItem<_V> *p_next_entry = pEntry->mp_next; + if (pEntry->m_key==key && pEntry->mp_value) // to allow keys of value 0, we have to skip head nodes that are 0,NULL + { + if (pLast) + { + // this is not a main table entry; this is a linked entry + pLast->mp_next = pEntry->mp_next; +#ifndef __PLAT_WN32__ + Mem::PoolManager::SFreeItem(Mem::PoolManager::vHASH_ITEM_POOL, pEntry); +#else + delete pEntry; +#endif + } + else + { + // this is a main table entry, it still might be linked to something + // clear the entry to 0,NULL (see comment above about keys of value 0) + pEntry->m_key = 0; + pEntry->mp_value = NULL; + } + m_size--; + return; + } + pLast = pEntry; + pEntry = p_next_entry; + } + return; +} + + + + +template //inline +void HashTable<_V>::FlushAllItems() +{ + + + Dbg_AssertPtr( mp_hash_table ); + if (!mp_hash_table) + return; + + // Run through the table and delete any of the extra + // HashItem<_V>s. + HashItem<_V> *pMainEntry = mp_hash_table; + uint32 hashTableSize = (1< *pLinkedEntry = pMainEntry->mp_next; + while (pLinkedEntry) + { + HashItem<_V> *pNext = pLinkedEntry->mp_next; +#ifndef __PLAT_WN32__ + Mem::PoolManager::SFreeItem(Mem::PoolManager::vHASH_ITEM_POOL, pLinkedEntry); +#else + delete pLinkedEntry; +#endif + pLinkedEntry = pNext; + } + pMainEntry->Init(); + ++pMainEntry; + } + m_size = 0; +} + + + + +template //inline +void HashTable<_V>::HandleCallback(HashCallback hashCallback, void* pData) +{ + HashItem<_V> *pMainEntry = mp_hash_table; + uint32 hashTableSize=(1< *pLinkedEntry = pMainEntry->mp_next; + while (pLinkedEntry) + { + HashItem<_V> *pNext = pLinkedEntry->mp_next; +// Mem::Free(pLinkedEntry); + if (pLinkedEntry->mp_value) + (hashCallback)((_V *) pLinkedEntry->mp_value, pData); + pLinkedEntry = pNext; + } + if (pMainEntry->mp_value) + (hashCallback)((_V *) pMainEntry->mp_value, pData); + ++pMainEntry; + } + +} + + + + +template //inline +void HashTable<_V>::IterateStart() +{ + m_iterator_index = -1; + mp_iterator_item = NULL; +} + + + + +template //inline +_V * HashTable<_V>::IterateNext(uint32 *pRetKey) +{ + uint32 hashTableSize=(1<mp_next; + else if (m_iterator_index >= 0) + // we've exhausted all the lists + return NULL; + + if (!mp_iterator_item) + { + // no entry in list, move on to next list + do + { + m_iterator_index++; + if (m_iterator_index >= (int) hashTableSize) + return NULL; + mp_iterator_item = mp_hash_table + m_iterator_index; + } // main entry has to contain something, or be part of a list + while (!mp_iterator_item->mp_value && !mp_iterator_item->mp_next); + if (!mp_iterator_item->mp_value) + // this must be an empty main entry, skip ahead + mp_iterator_item = mp_iterator_item->mp_next; + } + + // Ken: Added this because it was hanging here once when loading the junkyard + // off CD. It was trying to dereference mp_iterator_item. Added the printf + // so we can at least see when it happens without having to go into the debugger. + if (!mp_iterator_item) + { + #ifdef __NOPT_ASSERT__ + printf("Error!! NULL mp_iterator_item in IterateNext()\n"); + #endif + return NULL; + } + if (pRetKey) + *pRetKey = mp_iterator_item->m_key; + return mp_iterator_item->mp_value; +} + + + + +#ifdef __NOPT_ASSERT__ +template //inline +void HashTable<_V>::PrintContents() +{ + printf("Items in Hash Table:\n"); + uint32 hashTableSize=(1< *pEntry = mp_hash_table + i; + while(pEntry) + { + if (pEntry->mp_value) + printf(" 0x%x [%d]\n", pEntry->m_key, i); + pEntry = pEntry->mp_next; + } + } +} +#endif + + + + +template //inline +HashItem<_V>::HashItem<_V>() +{ + Init(); +} + + + + +template //inline +void HashItem<_V>::Init() +{ + m_key = 0; + mp_value = NULL; + mp_next = NULL; +} + +} // namespace Lst + +#endif // __CORE_LIST_HASHTABLE_H + + diff --git a/Code/Core/List.h b/Code/Core/List.h new file mode 100644 index 0000000..16e1181 --- /dev/null +++ b/Code/Core/List.h @@ -0,0 +1,64 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: List (Lst) ** +** ** +** File name: core/list.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_H +#define __CORE_LIST_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +#endif // __CORE_LIST_H diff --git a/Code/Core/List/Head.h b/Code/Core/List/Head.h new file mode 100644 index 0000000..22e50ba --- /dev/null +++ b/Code/Core/List/Head.h @@ -0,0 +1,419 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: List (LST_) ** +** ** +** File name: core/list/head.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_HEAD_H +#define __CORE_LIST_HEAD_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Lst +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/*********************************************************************** + * + * Class: Head + * + * Description: Linked-list head node. + * + ***********************************************************************/ + +nTemplateSubClass( _T, Head, Node< _T > ) +{ + +public: + Head( void ); + virtual ~Head( void ); + + void Merge( Head< _T >* dest ); // Source list will be empty after merge + uint CountItems( void ); + Node< _T >* GetItem( uint number ); // Zero-based ( 0 will return first node ) + + void AddNode( Node< _T >* node ); // Using priority + void AddNodeFromTail( Node< _T >* node ); // Using priority, search backwards from tail (i.e same-priorties are appended rather than pre-pended) + bool AddUniqueSequence( Node< _T >* node ); // Only add if priority is unique + // and priority decreases + void AddToTail( Node< _T >* node ); + void AddToHead( Node< _T >* node ); + + void RemoveAllNodes( void ); + void DestroyAllNodes( void ); // ONLY USE FOR INHERITED LISTS + bool IsEmpty( void ); + + Node< _T >* FirstItem(); // get first node, or NULL if none + + +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +template < class _T > inline +Head< _T >::Head( void ) +: Node< _T > ( reinterpret_cast < _T* >( vHEAD_NODE ) ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Head< _T >::~Head( void ) +{ + + + Dbg_MsgAssert( IsEmpty(),( "List is not empty" )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Head< _T >::AddNode( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + Dbg_MsgAssert( !node->InList (),( "Object is already in a list" )); + + Node< _T >* node_ptr = this; + Priority new_pri = node->GetPri(); + + while (( node_ptr = node_ptr->GetNext() )) + { + if ( node_ptr->GetPri() <= new_pri ) + { + node_ptr->Insert( node ); + return; + } + } + + Insert( node ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Head< _T >::AddNodeFromTail( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + Dbg_MsgAssert( !node->InList (),( "Object is already in a list" )); + + Node< _T >* node_ptr = this; + Priority new_pri = node->GetPri(); + + while (( node_ptr = node_ptr->GetPrev() )) + { + if ( node_ptr->GetPri() >= new_pri ) + { + node_ptr->Append( node ); + return; + } + } + + Append( node ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +bool Head< _T >::AddUniqueSequence( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + Dbg_MsgAssert( !node->InList (),( "Object is already in a list" )); + + Node< _T >* node_ptr = this; + Priority new_pri = node->GetPri(); + + while (( node_ptr = node_ptr->GetNext() )) + { + if ( node_ptr->GetPri() == new_pri ) + { + return false; + } + else if ( node_ptr->GetPri() > new_pri ) + { + node_ptr->Insert( node ); + return true; + } + } + + Insert( node ); + return true; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Head< _T >::Merge( Head< _T >* dest ) +{ + + + Dbg_AssertType( dest, Head< _T > ); + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + Dbg_MsgAssert( dest->is_head (),( "Object is not a list" )); + + Node< _T >* first = next; + Node< _T >* last = prev; + Node< _T >* node = dest->GetPrev(); + + if ( this == first ) // source list is empty + { + return; + } + + node->SetNext( first ); + first->SetPrev( node ); + + last->SetNext( dest ); + dest->SetPrev( last ); + + node_init(); // make the source list empty +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Node< _T >* Head< _T >::GetItem( uint number ) +{ + + + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + + Node< _T >* node = GetNext(); + + while ( node ) + { + if ( number-- == 0 ) + { + return node; + } + + node = node->GetNext(); + } + + Dbg_Warning( "Item requested (%d) out of range (%d)", number, CountItems() ); + + return NULL; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +// Return the firs node in the list that this is the head of + +template < class _T > inline +Node< _T >* Head< _T >::FirstItem( ) +{ + + + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + + return GetNext(); +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +uint Head< _T >::CountItems( void ) +{ + + + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + + uint count = 0; + Node< _T >* node = GetNext(); + + while ( node ) + { + count++; + node = node->GetNext(); + } + + return count; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +void Head< _T >::RemoveAllNodes( void ) +{ + + + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + + Node< _T >* next_nd; + Node< _T >* node = GetNext(); + + while ( node ) + { + next_nd = node->GetNext(); + node->Remove(); + node = next_nd; + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +void Head< _T >::DestroyAllNodes( void ) +{ + + + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + + Node< _T >* next_nd; + Node< _T >* node = GetNext(); + + while ( node ) + { + next_nd = node->GetNext(); +// node->Remove(); + delete node; + node = next_nd; + } +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +template inline +void Head< _T >::AddToTail( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + Dbg_MsgAssert( this->is_head (),( "Object is not a list" )); + Dbg_MsgAssert( !node->InList (),( "Node is already in a list" )); + + Insert ( node ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +void Head< _T >::AddToHead ( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + Dbg_MsgAssert( this->is_head(),(( "Object is not a list" ))); + Dbg_MsgAssert( !node->InList(),(( "Node is already in a list" ))); + + Append( node ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +bool Head< _T >::IsEmpty( void ) +{ + + + Dbg_MsgAssert ( this->is_head(),( "Object is not a list" )); + + return ( !InList() ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Lst + +#endif // __CORE_LIST_HEAD_H diff --git a/Code/Core/List/Node.h b/Code/Core/List/Node.h new file mode 100644 index 0000000..7f38eba --- /dev/null +++ b/Code/Core/List/Node.h @@ -0,0 +1,456 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: List (LST_) ** +** ** +** File name: core/list/node.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_Node_H +#define __CORE_LIST_Node_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +namespace Lst +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/*********************************************************************** + * + * Class: Lst::Node + * + * Description: Linked-list node. + * + ***********************************************************************/ + +nTemplateBaseClass( _T, Node ) +{ + + +public: + + // GJ: Note we have to cast vINT_MIN to an sint + // ( ((sint)vINT_MIN) / 2 ) ends up positive. + + enum + { + vNORMAL_PRIORITY = 0, + vHEAD_NODE = vUINT32_MAX + }; + + enum + { + vSYSTEM_TASK_PRIORITY_PROCESS_MODULES = -1000, + vSYSTEM_TASK_PRIORITY_FLUSH_DEAD_OBJECTS = 1000, + }; + + enum + { + vLOGIC_TASK_PRIORITY_REPLAY_END_FRAME = -3000, + vLOGIC_TASK_PRIORITY_PROCESS_NETWORK_METRICS = -2500, + vLOGIC_TASK_PRIORITY_SERVER_SEND_NETWORK_DATA = -2000, + vLOGIC_TASK_PRIORITY_CLIENT_SEND_NETWORK_DATA = -2000, + vLOGIC_TASK_PRIORITY_TIMEOUT_CONNECTIONS = -2000, + vLOGIC_TASK_PRIORITY_CLIENT_ADD_NEW_PLAYERS = -2000, + vLOGIC_TASK_PRIORITY_SERVER_ADD_NEW_PLAYERS = -2000, + vLOGIC_TASK_PRIORITY_FRONTEND = -2000, + vLOGIC_TASK_PRIORITY_SCRIPT_DEBUGGER = -1500, + vLOGIC_TASK_PRIORITY_LOCKED_OBJECT_MANAGER_LOGIC = -1100, + vLOGIC_TASK_PRIORITY_PARTICLE_MANAGER_LOGIC = -1000, + vLOGIC_TASK_PRIORITY_HANDLE_KEYBOARD = -1000, + vLOGIC_TASK_PRIORITY_OBJECT_UPDATE = -1000, + vLOGIC_TASK_PRIORITY_SCORE_UPDATE = -1000, + vLOGIC_TASK_PRIORITY_COMPOSITE_MANAGER = -900, + vLOGIC_TASK_PRIORITY_PROCESS_NETWORK_DATA = 0, + vLOGIC_TASK_PRIORITY_SKATER_SERVER = 1000, + vLOGIC_TASK_PRIORITY_TRANSFER_NETWORK_DATA = 1000, + vLOGIC_TASK_PRIORITY_RECEIVE_NETWORK_DATA = 1000, + vLOGIC_TASK_PRIORITY_PROCESS_HANDLERS = 2000, + vLOGIC_TASK_PRIORITY_REPLAY_START_FRAME = 3000, + }; + + enum + { + vDISPLAY_TASK_PRIORITY_PARK_EDITOR_DISPLAY = -1000, + vDISPLAY_TASK_PRIORITY_CPARKEDITOR_DISPLAY = -1000, + vDISPLAY_TASK_PRIORITY_IMAGE_VIEWER_DISPLAY = -1000, + vDISPLAY_TASK_PRIORITY_PANEL_DISPLAY = -500, + }; + + enum + { + vHANDLER_PRIORITY_OBSERVER_INPUT_LOGIC = -1000, + vHANDLER_PRIORITY_FRONTEND_INPUT_LOGIC0 = -1000, + vHANDLER_PRIORITY_FRONTEND_INPUT_LOGIC1 = -1000, + vHANDLER_PRIORITY_IMAGE_VIEWER_INPUT_LOGIC = 1000, + vHANDLER_PRIORITY_VIEWER_SHIFT_INPUT_LOGIC = 1000, + }; + + typedef sint Priority; + + Node ( _T* d, Priority p = vNORMAL_PRIORITY ); + virtual ~Node ( void ); + + void Insert ( Node< _T >* node ); + void Append ( Node< _T >* node ); + void Remove ( void ); + + void SetPri ( const Priority priority ); + void SetNext ( Node< _T >* node ); + void SetPrev ( Node< _T >* node ); + + Priority GetPri ( void ) const; + Node< _T >* GetNext ( void ) const; + Node< _T >* GetPrev ( void ) const; + _T* GetData ( void ) const; + + Node< _T >* LoopNext ( void ) const; + Node< _T >* LoopPrev ( void ) const; + + bool InList ( void ) const; + +protected: + + bool is_head ( void ) const; + void node_init ( void ); + +private: + + _T* data; + Priority pri; + Node< _T >* next; + Node< _T >* prev; +}; + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Inline Functions ** +*****************************************************************************/ + +template < class _T > inline +void Node< _T >::node_init( void ) +{ + + + next = this; + prev = this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +bool Node< _T >::is_head( void ) const +{ + + + return ( data == reinterpret_cast( vHEAD_NODE )); +} + +/***************************************************************************** +** Public Inline Functions ** +*****************************************************************************/ + +template < class _T > inline +Node< _T >::Node( _T* d, Priority p ) +: data( d ), pri( p ) +{ + + + node_init(); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +// Must define inline functions before they are used +template < class _T > inline +void Node< _T >::Remove( void ) +{ + + + prev->next = next; + next->prev = prev; + + node_init(); // so we know that the node is not in a list +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Node< _T >::~Node( void ) +{ + + + Remove(); +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +// Mick: Moved this here from the end of the file +// as it's intended to b inline +// yet it is used by other functions below + +template < class _T > inline +bool Node< _T >::InList( void ) const +{ + + + return ( prev != this ); +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Node< _T >::Insert ( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + Dbg_MsgAssert( !node->InList(),("node is already in a list" )); + + node->prev = prev; + node->next = this; + prev->next = node; + prev = node; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Node< _T >::Append( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + Dbg_MsgAssert( !node->InList(),( "node is already in a list" )); + + node->prev = this; + node->next = next; + next->prev = node; + next = node; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Node< _T >::SetPri( const Priority priority ) +{ + + + pri = priority; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Node< _T >::SetNext( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + + next = node; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Node< _T >::SetPrev( Node< _T >* node ) +{ + + + Dbg_AssertType( node, Node< _T > ); + + prev = node; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +typename Node< _T >::Priority Node< _T >::GetPri( void ) const +{ + return pri; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Node< _T >* Node< _T >::GetNext( void ) const +{ + + + if ( next->is_head() ) + { + return NULL; + } + + return next; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Node< _T >* Node< _T >::GetPrev( void ) const +{ + + + if ( prev->is_head() ) + { + return NULL; + } + + return prev; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +_T* Node< _T >::GetData( void ) const +{ + + + return data; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Node< _T >* Node< _T >::LoopNext( void ) const +{ + + + Node< _T >* next_node = next; + + if ( next_node->is_head() ) + { + next_node = next_node->next; // skip head node + + if ( next_node->is_head() ) + { + return NULL; // list is empty + } + } + + return next_node; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Node< _T >* Node< _T >::LoopPrev( void ) const +{ + + + Node< _T >* prev_node = prev; + + if ( prev_node->is_head() ) + { + prev_node = prev_node->prev; // skip head node + + if ( prev_node->is_head() ) + { + return NULL; // list is empty + } + } + + return prev_node; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Lst + +#endif // __CORE_LIST_Node_H diff --git a/Code/Core/List/Search.h b/Code/Core/List/Search.h new file mode 100644 index 0000000..3a91395 --- /dev/null +++ b/Code/Core/List/Search.h @@ -0,0 +1,192 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: List (LST_) ** +** ** +** File name: core/list/search.h ** +** ** +** Created: 04/02/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_SEARCH_H +#define __CORE_LIST_SEARCH_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Lst +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +nTemplateBaseClass ( _T, Search ) +{ + + +public: + Search ( void ); + virtual ~Search ( void ); + + + _T* FirstItem ( Head<_T>& head ); + _T* LastItem ( Head<_T>& head ); + _T* NextItem ( void ); + _T* PrevItem ( void ); + +private: + + Node< _T >* node; + +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +template inline +Search<_T>::Search ( void ) +: node ( NULL ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +Search<_T>::~Search ( void ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +_T* Search<_T>::FirstItem ( Head<_T>& head ) +{ + + + Dbg_AssertType ( &head, Head<_T> ); + + node = &head; + + return ( NextItem () ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +_T* Search<_T>::LastItem ( Head<_T>& head ) +{ + + + Dbg_AssertType ( &head, Head<_T> ); + + node = &head; + + return ( PrevItem () ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +_T* Search<_T>::NextItem ( void ) +{ + + + Dbg_AssertType ( node, Node< _T > ); + + node = node->GetNext(); + + if ( !node ) + { + return NULL; + } + + Dbg_AssertType ( node, Node<_T> ); + + return ( node->GetData() ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template inline +_T* Search<_T>::PrevItem ( void ) +{ + + + Dbg_AssertType ( node, Node< _T > ); + + node = node->GetPrev(); + + if ( !node ) + { + return NULL; + } + + Dbg_AssertType ( node, Node<_T> ); + + return ( node->GetData() ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Lst + +#endif // __CORE_LIST_SEARCH_H diff --git a/Code/Core/List/list.cpp b/Code/Core/List/list.cpp new file mode 100644 index 0000000..9fd142c --- /dev/null +++ b/Code/Core/List/list.cpp @@ -0,0 +1,73 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: List (Lst) ** +** ** +** File name: list.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: List management code ** +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + + + diff --git a/Code/Core/LookupTable.cpp b/Code/Core/LookupTable.cpp new file mode 100644 index 0000000..6ad1e37 --- /dev/null +++ b/Code/Core/LookupTable.cpp @@ -0,0 +1,88 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: List ** +** ** +** File name: Core\List\LookupTable.cpp ** +** ** +** Created by: 9/22/2000 - rjm ** +** ** +** Description: A Lookuptable ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include + +// + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Lst +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +/****************************************************************************** + * + * Function: + * + * Description: Default constructor + * + * Parameters: + * + *****************************************************************************/ + + +} // namespace Lst + + + + + diff --git a/Code/Core/LookupTable.h b/Code/Core/LookupTable.h new file mode 100644 index 0000000..d0ddccd --- /dev/null +++ b/Code/Core/LookupTable.h @@ -0,0 +1,426 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: LookupTable ** +** ** +** File name: Core\LookupTable.h ** +** ** +** Created by: 9/22/2000 - rjm ** +** ** +** Description: A handy Lookuptable class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_LOOKUPTABLE_H +#define __CORE_LIST_LOOKUPTABLE_H + +#ifndef __CORE_CRC_H +#include +#endif + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +namespace Lst +{ + + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/**************************************************************************** + * + * Class: LookupItem + * + * Description: Used to represent an item in a Lookup table. + * + * Usage: + * + * + * + ****************************************************************************/ + +// forward declaration +template< class _V > class LookupTable; + +//nTemplateBaseClass2(_K, _V, LookupItem) +template< class _V > class LookupItem +{ +// Dbg_TemplateBaseClass2(_K, _V, LookupItem); + + friend class LookupTable<_V>; + +private: + LookupItem(); + + int m_key; + _V * mp_value; + LookupItem<_V> * mp_next; +}; + + + +nTemplateBaseClass(_V, LookupTable) +{ + Dbg_TemplateBaseClass(_V, LookupTable); + +public: + LookupTable(int size=0); // Mick, size is not used, so give it default until we get rid of it + ~LookupTable(); + + // if any item exists with the same key, replace it + bool PutItem(const int &key, _V *item); + // gets a pointer to requested item, returns NULL if item not in table + _V *GetItem(const int &key); + // gets a pointer to requested item, returns NULL if item not in table + _V *GetItemByIndex(const int &index, int *pKey = NULL); + int getSize() {return m_size;} + + // removes item from table, calls its destructor if requested + void FlushItem(const int &key); + void flushAllItems(); + +private: + int m_size; + LookupItem<_V> * mp_list; // first item in list + LookupItem<_V> * mp_last; // last item in list + LookupItem<_V> * mp_current; // Pointer to current item in this table, NULL if invalid + int m_currentIndex; // the index of this item. Only valid if mp_current is not NULL +}; + + + +template +class StringLookupTable : public LookupTable<_V> +{ + //Dbg_TemplateBaseClass2(_K, _V, LookupItem); + +public: + StringLookupTable(int size); + + bool PutItem(const char *stringKey, _V *item); + _V *GetItem(const char *stringKey); + void FlushItem(const char *stringKey); +}; + + + +template +class LookupTableDestroyer +{ +public: + LookupTableDestroyer(LookupTable<_V> *pTable); + + void DeleteTableContents(); + +private: + LookupTable<_V> * mp_table; +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template //inline +LookupItem<_V>::LookupItem() +{ + mp_value = NULL; + mp_next = NULL; +} + + + +template //inline +LookupTable<_V>::LookupTable(int size=0) +{ + m_size = 0; + mp_list = NULL; + mp_current = NULL; // initialized invalid, so we don't try to use it +} + + + +template //inline +LookupTable<_V>::~LookupTable() +{ + flushAllItems(); +} + + + +template //inline +bool LookupTable<_V>::PutItem(const int &key, _V *item) +{ + + Dbg_AssertPtr(item); + //Ryan("putting item in lookup table\n"); + +# ifdef __NOPT_DEBUG__ + if (GetItem(key)) return false; +# endif + + LookupItem<_V> *pItem = new LookupItem<_V>; + pItem->mp_value = item; + pItem->m_key = key; + + if (!mp_list) + mp_list = pItem; + else + mp_last->mp_next = pItem; + mp_last = pItem; + m_size++; + mp_current = NULL; // no longer valid + + return true; +} + + + +template //inline +_V *LookupTable<_V>::GetItem(const int &key) +{ + + + LookupItem<_V> *pItem = mp_list; + mp_current = NULL; // set invalid now, so if not found, then it will be correctly invalid + m_currentIndex = 0; // index should be 0, for the first item + while(pItem) + { + if (pItem->m_key == key) + { + mp_current = pItem; // we have a valid current, and hence index + return pItem->mp_value; + } + m_currentIndex++; // update index, in case we find the item + pItem = pItem->mp_next; + } + + //printf("returning NULL from GetItem()\n"); + + // warning should be given by calling function, if necessary + //Dbg_Warning("Item not found in lookup table"); + return NULL; +} + + + +template //inline +_V *LookupTable<_V>::GetItemByIndex(const int &index, int *pKey) +{ + + // size must be at least 1 + Dbg_MsgAssert(index >= 0 && index < m_size,( "bad index %d", index)); + + // if we have a valid mp_current, then check if we can use that (for speed) + if (mp_current) + { + // is it the same as last time? + if (m_currentIndex == index) + { + // It's this one, so just leave it alone + } + else if (m_currentIndex < index) + { + // need to step forward until we find the correct one + // this is the most likely scenario if + // we are using GetItemByIndex in a for loop + // really we need an interator access class + // but this should be similar, in terms of code speed + while (m_currentIndex < index) + { + m_currentIndex++; + mp_current = mp_current->mp_next; + } + } + else + { + // otherwise, we need to start again, so invalidate mp_current + mp_current = NULL; + } + } + + // if we don't have a valid mp_current at this point, then we need + // to start searching from the start again + if (!mp_current) + { + LookupItem<_V> *pItem = mp_list; + for (int i = 0; i < index; i++) + { + pItem = pItem->mp_next; + } + m_currentIndex = index; + mp_current = pItem; + } + + // if they want us to returnt he key, then poke it into the supplied destination + if ( pKey ) + *pKey = mp_current->m_key; + + // the current value is correct + return mp_current->mp_value; +} + + + +template //inline +void LookupTable<_V>::FlushItem(const int &key) +{ + + + mp_current = NULL; // No longer valid + + LookupItem<_V> *pItem = mp_list; + LookupItem<_V> *prev = NULL; + while(pItem) + { + if (pItem->m_key == key) + { + if (prev) + prev->mp_next = pItem->mp_next; + else + mp_list = pItem->mp_next; + + if (mp_last == pItem) + mp_last = prev; + + delete pItem; + m_size--; + + return; + } + prev = pItem; + pItem = pItem->mp_next; + } +} + + + +template //inline +void LookupTable<_V>::flushAllItems() +{ + + + LookupItem<_V> *pItem = mp_list; + while(pItem) + { + LookupItem<_V> *pNext = pItem->mp_next; + delete pItem; + pItem = pNext; + } + mp_list = NULL; + mp_current = NULL; + m_size = 0; +} + +/* +int m_size; +LookupItem<_K, _V> *m_array; + +_K m_key; +_V *mp_value; +*/ + + + +template //inline +StringLookupTable<_V>::StringLookupTable(int size) : + LookupTable<_V>(size) +{ +} + + + +template //inline +bool StringLookupTable<_V>::PutItem(const char *stringKey, _V *item) +{ + int key = Crc::GenerateCRCFromString(stringKey); + return LookupTable<_V>::PutItem(key, item); +} + + + +template //inline +_V *StringLookupTable<_V>::GetItem(const char *stringKey) +{ + int key = Crc::GenerateCRCFromString(stringKey); + return LookupTable<_V>::GetItem(key); +} + + +template //inline +void StringLookupTable<_V>::FlushItem(const char *stringKey) +{ + int key = Crc::GenerateCRCFromString(stringKey); + LookupTable<_V>::FlushItem(key); +} + + + +template //inline +LookupTableDestroyer<_V>::LookupTableDestroyer(LookupTable<_V> *pTable) +{ + mp_table = pTable; +} + + + +template inline +void LookupTableDestroyer<_V>::DeleteTableContents() +{ + int num_items = mp_table->getSize(); + + for (int i = 0; i < num_items; i++) + { + int key; + // since we are flushing items out of the table as we go, + // we should always grab the first one + _V *pItem = mp_table->GetItemByIndex(0, &key); + mp_table->FlushItem(key); + if (pItem) delete pItem; + } +} + + + +} // namespace Lst + +#endif // __CORE_LIST_LOOKUPTABLE_H + + diff --git a/Code/Core/Math/Xbox/sse.h b/Code/Core/Math/Xbox/sse.h new file mode 100644 index 0000000..c223f5f --- /dev/null +++ b/Code/Core/Math/Xbox/sse.h @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// File: SSE.h +// +// Desc: P3 SSE conversions and estimates +// +// Hist: 1.7.03 - Created +// +// Copyright (c) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +#ifndef P3_SSE +#define P3_SSE + + +//----------------------------------------------------------------------------- +// Name: Ftoi_ASM +// Desc: SSE float to int conversion. Note that no control word needs to be +// set to round down +//----------------------------------------------------------------------------- +__forceinline int Ftoi_ASM( const float f ) +{ + __asm cvttss2si eax, f // return int(f) +} + + +//----------------------------------------------------------------------------- +// Name: ReciprocalEstimate_ASM +// Desc: SSE reciprocal estimate, accurate to 12 significant bits of +// the mantissa +//----------------------------------------------------------------------------- +__forceinline float ReciprocalEstimate_ASM( const float f ) +{ + float rec; + __asm rcpss xmm0, f // xmm0 = rcpss(f) + __asm movss rec , xmm0 // return xmm0 + return rec; +} + + +//----------------------------------------------------------------------------- +// Name: ReciprocalSqrtEstimate_ASM +// Desc: SSE reciprocal square root estimate, accurate to 12 significant +// bits of the mantissa +//----------------------------------------------------------------------------- +__forceinline float ReciprocalSqrtEstimate_ASM( const float f ) +{ + float recsqrt; + __asm rsqrtss xmm0, f // xmm0 = rsqrtss(f) + __asm movss recsqrt, xmm0 // return xmm0 + return recsqrt; +} + + +//----------------------------------------------------------------------------- +// Name: SqrtEstimae_ASM +// Desc: SSE square root estimate, accurate to 12 significant bits of +// the mantissa. Note that a check for zero must be made since +// sqrt(0) == 0 but 1/sqrt(0) = inf +//----------------------------------------------------------------------------- +__forceinline float SqrtEstimate_ASM( const float f ) +{ + float recsqrt; + __asm movss xmm0,f // xmm0 = f + __asm rsqrtss xmm1, xmm0 // xmm1 = rsqrtss(f) + __asm mulss xmm1, xmm0 // xmm1 = rsqrtss(f) * f = sqrt(f) + __asm xorps xmm2, xmm2 // xmm2 = 0 + __asm cmpneqss xmm2, xmm0 // xmm2 = (f != 0 ? 1s : 0s) + __asm andps xmm1, xmm2 // xmm1 = xmm1 & xmm2 + __asm movss recsqrt, xmm1 // return xmm1 + return recsqrt; +} + + +//----------------------------------------------------------------------------- +// Name: ReciprocalEstimateNR_ASM +// Desc: SSE Newton-Raphson reciprocal estimate, accurate to 23 significant +// bits of the mantissa +// One Newtown-Raphson Iteration: +// f(i+1) = 2 * rcpss(f) - f * rcpss(f) * rcpss(f) +//----------------------------------------------------------------------------- +__forceinline float ReciprocalEstimateNR_ASM( const float f ) +{ + float rec; + __asm rcpss xmm0, f // xmm0 = rcpss(f) + __asm movss xmm1, f // xmm1 = f + __asm mulss xmm1, xmm0 // xmm1 = f * rcpss(f) + __asm mulss xmm1, xmm0 // xmm2 = f * rcpss(f) * rcpss(f) + __asm addss xmm0, xmm0 // xmm0 = 2 * rcpss(f) + __asm subss xmm0, xmm1 // xmm0 = 2 * rcpss(f) + // - f * rcpss(f) * rcpss(f) + __asm movss rec, xmm0 // return xmm0 + return rec; +} + +//----------------------------------------------------------------------------- +// Newton-Rapson square root iteration constants +//----------------------------------------------------------------------------- +const float g_SqrtNRConst[2] = {0.5f, 3.0f}; + + +//----------------------------------------------------------------------------- +// Name: ReciprocalSqrtEstimateNR_ASM +// Desc: SSE Newton-Raphson reciprocal square root estimate, accurate to 23 +// significant bits of the mantissa +// One Newtown-Raphson Iteration: +// f(i+1) = 0.5 * rsqrtss(f) * (3.0 - (f * rsqrtss(f) * rsqrtss(f)) +// NOTE: rsqrtss(f) * rsqrtss(f) != rcpss(f) (presision is not maintained) +//----------------------------------------------------------------------------- +__forceinline float ReciprocalSqrtEstimateNR_ASM( const float f ) +{ + float recsqrt; + __asm rsqrtss xmm0, f // xmm0 = rsqrtss(f) + __asm movss xmm1, f // xmm1 = f + __asm mulss xmm1, xmm0 // xmm1 = f * rsqrtss(f) + __asm movss xmm2, g_SqrtNRConst+4 // xmm2 = 3.0f + __asm mulss xmm1, xmm0 // xmm1 = f * rsqrtss(f) * rsqrtss(f) + __asm mulss xmm0, g_SqrtNRConst // xmm0 = 0.5f * rsqrtss(f) + __asm subss xmm2, xmm1 // xmm2 = 3.0f - + // f * rsqrtss(f) * rsqrtss(f) + __asm mulss xmm0, xmm2 // xmm0 = 0.5 * rsqrtss(f) + // * (3.0 - (f * rsqrtss(f) * rsqrtss(f)) + __asm movss recsqrt, xmm0 // return xmm0 + return recsqrt; +} + + +//----------------------------------------------------------------------------- +// Name: SqrtEstimateNR_ASM +// Desc: SSE Newton-Raphson square root estimate, accurate to 23 significant +// bits of the mantissa +// NOTE: x/sqrt(x) = sqrt(x) +// One Newtown-Raphson Iteration (for 1/sqrt(x)) : +// f(i+1) = 0.5 * rsqrtss(f) * (3.0 - (f * rsqrtss(f) * rsqrtss(f)) +// NOTE: rsqrtss(f) * rsqrtss(f) != rcpss(f) (presision is not maintained) +//----------------------------------------------------------------------------- +__forceinline float SqrtEstimateNR_ASM( const float f ) +{ + float recsqrt; + __asm rsqrtss xmm0, f // xmm0 = rsqrtss(f) + __asm movss xmm1, f // xmm1 = f + __asm mulss xmm1, xmm0 // xmm1 = f * rsqrtss(f) + __asm movss xmm2, g_SqrtNRConst+4 // xmm2 = 3.0f + __asm mulss xmm1, xmm0 // xmm1 = f * rsqrtss(f) * rsqrtss(f) + __asm mulss xmm0, g_SqrtNRConst // xmm0 = 0.5f * rsqrtss(f) + __asm subss xmm2, xmm1 // xmm2 = 3.0f - + // f * rsqrtss(f) * rsqrtss(f) + __asm mulss xmm0, xmm2 // xmm0 = 0.5 * rsqrtss(f) + // * (3.0 - (f * rsqrtss(f) * rsqrtss(f)) + __asm xorps xmm1, xmm1 // xmm1 = 0 + __asm mulss xmm0, f // xmm0 = sqrt(f) + __asm cmpneqss xmm1, f // xmm1 = (f != 0 ? 1s : 0s) + __asm andps xmm0, xmm1 // xmm0 = xmm1 & xmm2 + __asm movss recsqrt, xmm0 // return xmm0 + return recsqrt; +} + + +#endif // P3_SSE diff --git a/Code/Core/Math/geometry.cpp b/Code/Core/Math/geometry.cpp new file mode 100644 index 0000000..fbba5f8 --- /dev/null +++ b/Code/Core/Math/geometry.cpp @@ -0,0 +1,562 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Geometry (MTH) ** +** ** +** File name: Geometry.cpp ** +** ** +** Created by: 11/14/00 - Mick ** +** ** +** Description: Math ** +// This module handles the representation and manipulation of line segements +// and planes +// +// a line segment (Mth::Line) is defined by two points, m_start and m_end +// +// a plane is defined by a point and a normal + +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include + +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Mth +{ + + + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +// + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +Line::Line() +{ +} + +Line::Line ( const Vector &start, const Vector &end ) +{ + m_start = start; + m_end = end; +} + +Plane::Plane() +{ +} + +Plane::Plane (const Vector &point, const Vector &normal) +{ + m_point = point; + m_normal = normal; +} + +Rectangle::Rectangle() +{ +} + +Rectangle::Rectangle ( const Vector& corner, const Vector& first_edge, const Vector& second_edge ) +{ + m_corner = corner; + m_first_edge = first_edge; + m_second_edge = second_edge; +} + +/* + Calculate the line segment PaPb that is the shortest route between + two lines P1P2 and P3P4. Calculate also the values of mua and mub where + Pa = P1 + mua (P2 - P1) + Pb = P3 + mub (P4 - P3) + Return FALSE if no solution exists. + original algorithm from http://www.swin.edu.au/astronomy/pbourke/geometry/lineline3d/ + note that I (Mick) modified it to clamp the points to within the line segments + if you remove the "Clamp" calls below, then the lines will be assumed to be + of infinite length +*/ + +#define EPS 0.00001f + +bool LineLineIntersect( Line & l1, Line & l2, Vector *pa, Vector *pb, float *mua, float *mub, bool clamp ) +{ + + Vector &p1 = l1.m_start; + Vector &p2 = l1.m_end; + Vector &p3 = l2.m_start; + Vector &p4 = l2.m_end; + + Vector p13,p43,p21; + + float d1343,d4321,d1321,d4343,d2121; + float numer,denom; + + p13 = p1 - p3; + p43 = p4 - p3; + + if (Abs(p43[X]) < EPS && Abs(p43[Y]) < EPS && Abs(p43[Z]) < EPS) + return(false); + + p21 = p2 - p1; + + if (Abs(p21[X]) < EPS && Abs(p21[Y]) < EPS && Abs(p21[Z]) < EPS) + return(false); + + d1343 = DotProduct(p13,p43); + d4321 = DotProduct(p43,p21); + d1321 = DotProduct(p13,p21); + d4343 = DotProduct(p43,p43); + d2121 = DotProduct(p21,p21); + + denom = d2121 * d4343 - d4321 * d4321; + if (Abs(denom) < EPS) + return(false); + numer = d1343 * d4321 - d1321 * d4343; + + *mua = numer / denom; + if( clamp ) + { + *mua = Clamp(*mua,0.0f,1.0f); + } + *mub = (d1343 + d4321 * (*mua)) / d4343; + + if( clamp ) + { + *mub = Clamp(*mub,0.0f,1.0f); + } + + *pa = p1 + (*mua * p21); + *pb = p3 + (*mub * p43); + + return(true); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +CBBox::CBBox() +{ + Reset(); +} + +CBBox::CBBox(const Vector &point) +{ + Set(point, point); +} + +CBBox::CBBox(const Vector &min, const Vector &max) +{ + Set(min, max); +} + +void CBBox::AddPoint(const Vector &point) +{ + // Adjust min/max points + if (point[X] < m_min[X]) + m_min[X] = point[X]; + if (point[Y] < m_min[Y]) + m_min[Y] = point[Y]; + if (point[Z] < m_min[Z]) + m_min[Z] = point[Z]; + + if (point[X] > m_max[X]) + m_max[X] = point[X]; + if (point[Y] > m_max[Y]) + m_max[Y] = point[Y]; + if (point[Z] > m_max[Z]) + m_max[Z] = point[Z]; +} + +// Returns number of axes within and flags for which ones +int CBBox::WithinAxes(const Vector &point, uint32 &axis_flags) const +{ + int number_of_axes = 0; + axis_flags = 0; + + if ((point[X] >= m_min[X]) && (point[X] <= m_max[X])) + { + number_of_axes++; + axis_flags |= 1 << X; + } + + if ((point[Y] >= m_min[Y]) && (point[Y] <= m_max[Y])) + { + number_of_axes++; + axis_flags |= 1 << Y; + } + + if ((point[Z] >= m_min[Z]) && (point[Z] <= m_max[Z])) + { + number_of_axes++; + axis_flags |= 1 << Z; + } + + return number_of_axes; +} + +void CBBox::DebugRender(uint32 rgba, int frames) const +{ + // Min YZ square + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_min[Y],m_min[Z]),Mth::Vector(m_min[X],m_min[Y],m_max[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_min[Y],m_max[Z]),Mth::Vector(m_min[X],m_max[Y],m_max[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_max[Y],m_max[Z]),Mth::Vector(m_min[X],m_max[Y],m_min[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_max[Y],m_min[Z]),Mth::Vector(m_min[X],m_min[Y],m_min[Z]),rgba,rgba,frames); + + // Max YZ square + Gfx::AddDebugLine(Mth::Vector(m_max[X],m_min[Y],m_min[Z]),Mth::Vector(m_max[X],m_min[Y],m_max[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_max[X],m_min[Y],m_max[Z]),Mth::Vector(m_max[X],m_max[Y],m_max[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_max[X],m_max[Y],m_max[Z]),Mth::Vector(m_max[X],m_max[Y],m_min[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_max[X],m_max[Y],m_min[Z]),Mth::Vector(m_max[X],m_min[Y],m_min[Z]),rgba,rgba,frames); + + // lines joining corners + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_min[Y],m_min[Z]),Mth::Vector(m_max[X],m_min[Y],m_min[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_min[Y],m_max[Z]),Mth::Vector(m_max[X],m_min[Y],m_max[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_max[Y],m_max[Z]),Mth::Vector(m_max[X],m_max[Y],m_max[Z]),rgba,rgba,frames); + Gfx::AddDebugLine(Mth::Vector(m_min[X],m_max[Y],m_min[Z]),Mth::Vector(m_max[X],m_max[Y],m_min[Z]),rgba,rgba,frames); +} + +void CBBox::DebugRender(const Mth::Matrix & transform, uint32 rgba, int frames) const +{ + uint32 rgba_min = 0xFF000080; + uint32 rgba_max = 0x0000FF80; + + Mth::Vector min_yz_1(m_min[X], m_min[Y], m_min[Z]); + Mth::Vector min_yz_2(m_min[X], m_min[Y], m_max[Z]); + Mth::Vector min_yz_3(m_min[X], m_max[Y], m_max[Z]); + Mth::Vector min_yz_4(m_min[X], m_max[Y], m_min[Z]); + + Mth::Vector max_yz_1(m_max[X], m_min[Y], m_min[Z]); + Mth::Vector max_yz_2(m_max[X], m_min[Y], m_max[Z]); + Mth::Vector max_yz_3(m_max[X], m_max[Y], m_max[Z]); + Mth::Vector max_yz_4(m_max[X], m_max[Y], m_min[Z]); + + // Transform + min_yz_1 = transform.Transform(min_yz_1); + min_yz_2 = transform.Transform(min_yz_2); + min_yz_3 = transform.Transform(min_yz_3); + min_yz_4 = transform.Transform(min_yz_4); + + max_yz_1 = transform.Transform(max_yz_1); + max_yz_2 = transform.Transform(max_yz_2); + max_yz_3 = transform.Transform(max_yz_3); + max_yz_4 = transform.Transform(max_yz_4); + + // Min YZ square + Gfx::AddDebugLine(min_yz_1, min_yz_2,rgba_min,rgba_min,frames); + Gfx::AddDebugLine(min_yz_2, min_yz_3,rgba_min,rgba_min,frames); + Gfx::AddDebugLine(min_yz_3, min_yz_4,rgba_min,rgba_min,frames); + Gfx::AddDebugLine(min_yz_4, min_yz_1,rgba_min,rgba_min,frames); + + // Max YZ square + Gfx::AddDebugLine(max_yz_1, max_yz_2,rgba_max,rgba_max,frames); + Gfx::AddDebugLine(max_yz_2, max_yz_3,rgba_max,rgba_max,frames); + Gfx::AddDebugLine(max_yz_3, max_yz_4,rgba_max,rgba_max,frames); + Gfx::AddDebugLine(max_yz_4, max_yz_1,rgba_max,rgba_max,frames); + + // lines joining corners + Gfx::AddDebugLine(min_yz_1, max_yz_1,rgba,rgba,frames); + Gfx::AddDebugLine(min_yz_2, max_yz_2,rgba,rgba,frames); + Gfx::AddDebugLine(min_yz_3, max_yz_3,rgba,rgba,frames); + Gfx::AddDebugLine(min_yz_4, max_yz_4,rgba,rgba,frames); +} + +bool CBBox::LineIntersect( const Mth::Line &line, Mth::Vector &point, Mth::Vector &normal ) const +{ + bool start_point_within = Within(line.m_start); + bool end_point_within = Within(line.m_end); + + // Doesn't intersect side if both points within + if (start_point_within && end_point_within) + { + return false; + } + + // Trivial rejection. + if ((line.m_start[Y] > m_max[Y]) && (line.m_end[Y] > m_max[Y])) return false; + if ((line.m_start[Y] < m_min[Y]) && (line.m_end[Y] < m_min[Y])) return false; + if ((line.m_start[X] > m_max[X]) && (line.m_end[X] > m_max[X])) return false; + if ((line.m_start[X] < m_min[X]) && (line.m_end[X] < m_min[X])) return false; + if ((line.m_start[Z] > m_max[Z]) && (line.m_end[Z] > m_max[Z])) return false; + if ((line.m_start[Z] < m_min[Z]) && (line.m_end[Z] < m_min[Z])) return false; + + float dx = line.m_end[X] - line.m_start[X]; + float dy = line.m_end[Y] - line.m_start[Y]; + float dz = line.m_end[Z] - line.m_start[Z]; + + // avoid divide by zeros. + if ( !dx ) + { + dx = ( 0.000001f ); + } + if ( !dy ) + { + dy = ( 0.000001f ); + } + if ( !dz ) + { + dz = ( 0.000001f ); + } + + // + // Garrett: I back-face cull 3 of the sides to reduce the calculations and keep + // the collisions down to one side. + + // Check the max-x face. + if (line.m_start[X] > m_max[X] && line.m_end[X] < m_max[X] && (dx < 0.0f)) + { + // It crosses the plane of the face, so calculate the y & z coords + // of the intersection and see if they are in the face, + float d=m_max[X] - line.m_start[X]; + float y=d*dy/dx + line.m_start[Y]; + float z=d*dz/dx + line.m_start[Z]; + if (y < m_max[Y] && y > m_min[Y] && z < m_max[Z] && z > m_min[Z]) + { + // It does collide! + point = Mth::Vector(m_max[X], y, z, 1.0f); + normal = Mth::Vector(1.0f, 0.0f, 0.0f, 0.0f); + return true; + } + } + + // Check the min-x face. + if (line.m_start[X] < m_min[X] && line.m_end[X] > m_min[X] && (dx > 0.0f)) + { + // It crosses the plane of the face, so calculate the y & z coords + // of the intersection and see if they are in the face, + float d=m_min[X] - line.m_start[X]; + float y=d*dy/dx + line.m_start[Y]; + float z=d*dz/dx + line.m_start[Z]; + if (y < m_max[Y] && y > m_min[Y] && z < m_max[Z] && z > m_min[Z]) + { + // It does collide! + point = Mth::Vector(m_min[X], y, z, 1.0f); + normal = Mth::Vector(-1.0f, 0.0f, 0.0f, 0.0f); + return true; + } + } + + // Check the max-y face. + if (line.m_start[Y] > m_max[Y] && line.m_end[Y] < m_max[Y] && (dy < 0.0f)) + { + // It crosses the plane of the face, so calculate the x & z coords + // of the intersection and see if they are in the face, + float d=m_max[Y] - line.m_start[Y]; + float x=d*dx/dy + line.m_start[X]; + float z=d*dz/dy + line.m_start[Z]; + if (x < m_max[X] && x > m_min[X] && z < m_max[Z] && z > m_min[Z]) + { + // It does collide! + point = Mth::Vector(x, m_max[Y], z, 1.0f); + normal = Mth::Vector(0.0f, 1.0f, 0.0f, 0.0f); + return true; + } + } + + // Check the min-y face. + if (line.m_start[Y] < m_min[Y] && line.m_end[Y] > m_min[Y] && (dy > 0.0f)) + { + // It crosses the plane of the face, so calculate the x & z coords + // of the intersection and see if they are in the face, + float d=m_min[Y] - line.m_start[Y]; + float x=d*dx/dy + line.m_start[X]; + float z=d*dz/dy + line.m_start[Z]; + if (x < m_max[X] && x > m_min[X] && z < m_max[Z] && z > m_min[Z]) + { + // It does collide! + point = Mth::Vector(x, m_min[Y], z, 1.0f); + normal = Mth::Vector(0.0f, -1.0f, 0.0f, 0.0f); + return true; + } + } + + // Check the max-z face. + if (line.m_start[Z] > m_max[Z] && line.m_end[Z] < m_max[Z] && (dz < 0.0f)) + { + // It crosses the plane of the face, so calculate the x & y coords + // of the intersection and see if they are in the face, + float d=m_max[Z] - line.m_start[Z]; + float x=d*dx/dz + line.m_start[X]; + float y=d*dy/dz + line.m_start[Y]; + if (x < m_max[X] && x > m_min[X] && y < m_max[Y] && y > m_min[Y]) + { + // It does collide! + point = Mth::Vector(x, y, m_max[Z], 1.0f); + normal = Mth::Vector(0.0f, 0.0f, 1.0f, 0.0f); + return true; + } + } + + + // Check the min-z face. + if (line.m_start[Z] < m_min[Z] && line.m_end[Z] > m_min[Z] && (dz > 0.0f)) + { + // It crosses the plane of the face, so calculate the x & y coords + // of the intersection and see if they are in the face, + float d=m_min[Z] - line.m_start[Z]; + float x=d*dx/dz + line.m_start[X]; + float y=d*dy/dz + line.m_start[Y]; + if (x < m_max[X] && x > m_min[X] && y < m_max[Y] && y > m_min[Y]) + { + // It does collide! + point = Mth::Vector(x, y, m_min[Z], 1.0f); + normal = Mth::Vector(0.0f, 0.0f, -1.0f, 0.0f); + return true; + } + } + + return false; +} + +void CBBox::GetClosestIntersectPoint(const Mth::Vector &pos, Mth::Vector &point) const +{ + uint32 axes_within_flags; + int axes_within = WithinAxes(pos, axes_within_flags); + + if (axes_within == 3) + { + float closest_dist = -1.0f; + int closest_axis = -1; + float closest_axis_coord = 0.0f; // Value of the minimum axis coordinate + + // Find the closest rectangle and move the point to that rectangle to find the intersection + // point. + for (int axis = X; axis <= Z; axis++) + { + float min_to_pos_dist = pos[axis] - m_min[axis]; + float max_to_pos_dist = m_max[axis] - pos[axis]; + + Dbg_Assert(min_to_pos_dist >= 0.0f); + Dbg_Assert(max_to_pos_dist >= 0.0f); + + float min_dist; + float min_coord; + + // Figure out the closest distance + if (min_to_pos_dist < max_to_pos_dist) + { + min_dist = min_to_pos_dist; + min_coord = m_min[axis]; + } + else + { + min_dist = max_to_pos_dist; + min_coord = m_max[axis]; + } + + // See if this is the first axis or if this one is closer + if ((axis == X) || (min_dist < closest_dist)) + { + closest_axis = axis; + closest_dist = min_dist; + closest_axis_coord = min_coord; + } + } + + Dbg_Assert((closest_axis >= X) && (closest_axis <= Z)); + Dbg_Assert(closest_dist >= 0.0f); + + // Calc the intersection point + point = pos; // Start with position + point[closest_axis] = closest_axis_coord; + +#if 0 + static int print_now; + if ((print_now++ % 60) == 0) + { + Dbg_Message("Closest axis %d, (min - max) pos (%f - %f) %f, closest dist %f, closest coord %f", closest_axis, + m_min[closest_axis], m_max[closest_axis], pos[closest_axis], closest_dist, closest_axis_coord); + } +#endif + } + else + { + point[W] = 1.0f; // Homogeneous + + // Go through each axis. If the pos coordinate is between the min and max, use that + // coordinate. Otherwise, use the min or max coordinate that is closest. + for (int axis = X; axis <= Z; axis++) + { + if (axes_within_flags & (1 << axis)) + { + // Within, use pos coord + point[axis] = pos[axis]; + } + else + { + // Figure out which end we need + if (pos[axis] <= m_min[axis]) + { + point[axis] = m_min[axis]; + } + else + { + point[axis] = m_max[axis]; + } + } + } + } +} + + +} + + + diff --git a/Code/Core/Math/geometry.h b/Code/Core/Math/geometry.h new file mode 100644 index 0000000..1627637 --- /dev/null +++ b/Code/Core/Math/geometry.h @@ -0,0 +1,354 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/geometry.h ** +** ** +** Created: 11/29/99 - mjb ** +** ** +** Description: Vector Math Class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_GEOMETRY_H +#define __CORE_MATH_GEOMETRY_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +namespace Mth +{ + + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + + +/**************************************************************************** + * + * Class: Vector + * + * Description: Vector math class + * + ****************************************************************************/ + +class Plane; + +class Line +{ + + + friend class Plane; + +public: + + Line(); + Line ( const Vector &start, const Vector &end ); +// Line& operator+= ( const Vector& v ); + + inline void MirrorAboutStart(); + inline void FlipDirection(); + + bool operator== ( const Line& l ) const; + bool operator!= ( const Line& l ) const; + Line& operator+= ( const Vector& v ); + Line& operator-= ( const Vector& v ); + + float Length() const {return (m_end-m_start).Length();} + + Vector m_start, m_end; +private: +}; + + +class Plane +{ + + + friend class Line; + +public: + Plane(); + Plane (const Vector &point, const Vector &normal); + Vector m_point, m_normal; +private: +}; + +class Rectangle +{ +public: + Rectangle ( ); + Rectangle ( const Vector& corner, const Vector& first_edge, const Vector& second_edge ); + Vector m_corner, m_first_edge, m_second_edge; +private: +}; + + +/**************************************************************************** + * + * Class: CBBox + * + * Description: Axis-aligned Bounding Box + * + ****************************************************************************/ + +class CBBox +{ +public: + // Constructors + CBBox(); + CBBox(const Vector &point); + CBBox(const Vector &min, const Vector &max); + + // + inline void Set(const Vector &min, const Vector &max); + inline void Reset(); + + // + inline const Vector &GetMin() const; + inline const Vector &GetMax() const; + inline void SetMin(const Vector &min); + inline void SetMax(const Vector &max); + + // + void AddPoint(const Vector &point); + inline bool Intersect(const CBBox &bbox) const; + inline bool CouldIntersect(const Vector &v0, const Vector &v1, const Vector &v2) const; // Intersect w/ triangle + inline bool Within(const Vector &point) const; + inline bool Within(const CBBox &bbox) const; + int WithinAxes(const Vector &point, uint32 &axis_flags) const; // Returns number of axes within and flags for which ones + + // Checks to see if the line intersects any of the sides + bool LineIntersect( const Mth::Line &line, Mth::Vector &point, Mth::Vector &normal ) const; + + // Finds the closest point on the bounding box to a position + void GetClosestIntersectPoint(const Mth::Vector &pos, Mth::Vector &point) const; + + void DebugRender(uint32 rgba, int frames = 0) const; + void DebugRender(const Mth::Matrix & transform, uint32 rgba, int frames = 0) const; + +private: + // + Vector m_min, m_max; +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +bool LineLineIntersect( Line & l1, Line & l2, Vector *pa, Vector *pb, float *mua, float *mub, bool clamp = true ); + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Line::operator== ( const Line& l ) const +{ + + + return ( m_start == l.m_start) && ( m_end == l.m_end ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Line::operator!= ( const Line& l ) const +{ + + + return !( *this == l ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Line& Line::operator+= ( const Vector& v ) +{ + + + m_start += v; + m_end += v; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Line& Line::operator-= ( const Vector& v ) +{ + + + m_start -= v; + m_end -= v; + + return *this; +} + + + +// A->B becomes B<-A, where A is unchanged +inline void Line::MirrorAboutStart() +{ + m_end = m_start - 2.0f * ( m_end - m_start ); +} + +// A->B becores B->A (A and B swap positions) +inline void Line::FlipDirection() +{ + Mth::Vector temp = m_end; + m_end = m_start; + m_start = temp; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void CBBox::Reset() +{ +// m_min = Vector( (float)HUGE_VAL, (float)HUGE_VAL, (float)HUGE_VAL); +// m_max = Vector((float)-HUGE_VAL, (float)-HUGE_VAL, (float)-HUGE_VAL); + m_min = Vector( 10000000.0f, 10000000.0f, 10000000.0f); + m_max = Vector(-10000000.0f, -10000000.0f, -10000000.0f); +} + +inline void CBBox::Set(const Vector &min, const Vector &max) +{ + m_min = min; + m_max = max; +} + +inline const Vector &CBBox::GetMin() const +{ + return m_min; +} + +inline const Vector &CBBox::GetMax() const +{ + return m_max; +} + +inline void CBBox::SetMin(const Vector &min) +{ + m_min = min; +} + +inline void CBBox::SetMax(const Vector &max) +{ + m_max = max; +} + +// for intersection test, we do X and Z first, +// as we war aer more likely to be intersecting in the Y +// as everyhting is about the same height +// so this eliminates thigns quicker +inline bool CBBox::Intersect(const CBBox &bbox) const +{ + if ((m_min[X] > bbox.m_max[X]) || (bbox.m_min[X] > m_max[X]) || + (m_min[Z] > bbox.m_max[Z]) || (bbox.m_min[Z] > m_max[Z]) || + (m_min[Y] > bbox.m_max[Y]) || (bbox.m_min[Y] > m_max[Y])) + { + return false; + } + + return true; +} + +inline bool CBBox::Within(const Vector &point) const +{ + return ((point[X] >= m_min[X]) && (point[Y] >= m_min[Y]) && (point[Z] >= m_min[Z]) && + (point[X] <= m_max[X]) && (point[Y] <= m_max[Y]) && (point[Z] <= m_max[Z])); +} + +inline bool CBBox::Within(const CBBox &bbox) const +{ + if ((bbox.m_min[X] >= m_min[X]) && (bbox.m_max[X] <= m_max[X]) && + (bbox.m_min[Y] >= m_min[Y]) && (bbox.m_max[Y] <= m_max[Y]) && + (bbox.m_min[Z] >= m_min[Z]) && (bbox.m_max[Z] <= m_max[Z])) + { + return true; + } + + return false; +} + +// Not So Quick & Dirty Intersection w/ Triangle +inline bool CBBox::CouldIntersect(const Vector &v0, const Vector &v1, const Vector &v2) const +{ + if ( ( v0[X] > m_max[X] ) && + ( v1[X] > m_max[X] ) && + ( v2[X] > m_max[X] ) ) return false; + if ( ( v0[X] < m_min[X] ) && + ( v1[X] < m_min[X] ) && + ( v2[X] < m_min[X] ) ) return false; + if ( ( v0[Y] > m_max[Y] ) && + ( v1[Y] > m_max[Y] ) && + ( v2[Y] > m_max[Y] ) ) return false; + if ( ( v0[Y] < m_min[Y] ) && + ( v1[Y] < m_min[Y] ) && + ( v2[Y] < m_min[Y] ) ) return false; + if ( ( v0[Z] > m_max[Z] ) && + ( v1[Z] > m_max[Z] ) && + ( v2[Z] > m_max[Z] ) ) return false; + if ( ( v0[Z] < m_min[Z] ) && + ( v1[Z] < m_min[Z] ) && + ( v2[Z] < m_min[Z] ) ) return false; + + return true; + +} + +} // namespace Mth + +#endif // __CORE_MATH_GEOMETRY_H + diff --git a/Code/Core/Math/math.cpp b/Code/Core/Math/math.cpp new file mode 100644 index 0000000..1606eda --- /dev/null +++ b/Code/Core/Math/math.cpp @@ -0,0 +1,374 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: math.cpp ** +** ** +** Created by: 11/24/99 - mjb ** +** ** +** Description: Math Library code ** +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#ifdef __PLAT_WN32__ +#include +#endif +#ifdef __PLAT_XBOX__ +#include +#endif + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +#include + +#if DEBUGGING_REPLAY_RND + +#include + +#include + +extern int *gReplayTestRndLine; +extern uint64 *gReplayTestRndFunc; +static int gRndIndex = 0; +static int gTestMode = 0; +#define MAX_RND_TEST_INDEX 3666 +#endif + + + +namespace Mth +{ + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +static int RandSeed; +static int RandA; +static int RandB; + +static int RandSeed2; +static int RandC; +static int RandD; + +void InitialRand(int a) +{ + RandSeed = a; + RandA = 314159265; + RandB = 178453311; + RandSeed2 = a; + RandC = 314159265; + RandD = 178453311; +} + +#if DEBUGGING_REPLAY_RND + +static bool gFuckedUp = false; + +bool RndFuckedUp( void ) +{ + if ( gTestMode != 2 ) + return ( false ); + return ( gFuckedUp ); +} + +int Rnd_DbgVersion(int n, int line, char *file) +{ + + if ( gRndIndex < MAX_RND_TEST_INDEX ) + { + if ( gTestMode == 1 ) + { + // record: + gReplayTestRndLine[ gRndIndex ] = line; + strncpy( ( char * )( &gReplayTestRndFunc[ gRndIndex ] ), file, 8 ); + gRndIndex++; + } + else if ( gTestMode == 2 ) + { + // compare: + if ( line != gReplayTestRndLine[ gRndIndex ] ) + { + char temp[ 9 ]; + strncpy( temp, ( char * )( &gReplayTestRndFunc[ gRndIndex ] ), 8 ); + temp[ 8 ] = '\0'; + Dbg_Message( "********Rnd Fuckup!********* num %d time %d\ncurrent line %d file %s conflicts with prev line %d file %s", + Tmr::GetTime( ), gRndIndex, line, file, gReplayTestRndLine[ gRndIndex ], temp ); + gFuckedUp = true; + } + else + { + gFuckedUp = false; + } + gRndIndex++; + } + } + RandSeed=RandSeed*RandA+RandB; + RandA = (RandA ^ RandSeed) + (RandSeed>>4); + RandB += (RandSeed>>3) - 0x10101010L; + return (int)((RandSeed&0xffff) * n)>>16; +} + +void SetRndTestMode( int mode ) +{ + gTestMode = mode; + gRndIndex = 0; +} + +#else +int Rnd(int n) +{ + RandSeed=RandSeed*RandA+RandB; + RandA = (RandA ^ RandSeed) + (RandSeed>>4); + RandB += (RandSeed>>3) - 0x10101010L; + return (int)((RandSeed&0xffff) * n)>>16; +} +#endif + +int Rnd2(int n) +{ + RandSeed2=RandSeed2*RandC+RandD; + RandC = (RandC ^ RandSeed2) + (RandSeed2>>4); + RandD += (RandSeed2>>3) - 0x10101010L; + return (int)((RandSeed2&0xffff) * n)>>16; +} + +// Return a random number in the range +/- n +float PlusOrMinus(float n) +{ + float range = (float)(Rnd(10000)); + range -= 5000.0f; + return n * range / 5000.0f; +} + +#if 0 +// 8192 makes Kensinf and sinf match to all 6 decimal places, (when x is in the range -2pi to 2pi) +// apart from very occassionally where there might be a .000001 difference. +// The table size must be a power of two. +#define SINF_NUM_DIVISIONS 8192 +static float pSinLookup[SINF_NUM_DIVISIONS+5]; // +5 for good measure, since sometimes we look one beyond the end. +void InitSinLookupTable() +{ + // The lookup table covers a whole period (0 to 2pi) of the sin function. + // This saves a few if's in the sin function. + for (int i=0; i fabsf( delta ) ) + { + return ( current - fabsf( delta ) ); + } + return ( target ); + } + if ( ( target - current ) > fabsf( delta ) ) + { + return ( current + fabsf( delta ) ); + } + return ( target ); +} + +int RunFilter( int target, int current, int delta ) +{ + if ( target < current ) + { + if ( current - target > abs( delta ) ) + return ( target ); + return ( current - abs( delta ) ); + } + if ( target - current > abs( delta ) ) + return ( target ); + return ( current + abs( delta ) ); +} + + +// returns atan(y/x) with appropiate sign determinations +// although it does not work, as we cant; find atan ..... + +/* +float Atan2 (float y, float x) +{ + + if (x == 0.0f) + { + if (y < 0.0f) return(-PI/2.0f); + + else return( PI/2.0f); + + } + else + { + + + if (x < 0.0f) { + if (y < 0.0f) + return(atan(y/x)-PI); + else + return(atan(y/x)+PI); + } + else + { + return(atan(y/x)); + } + + } + +} +*/ + +} // namespace Mth diff --git a/Code/Core/Math/math.h b/Code/Core/Math/math.h new file mode 100644 index 0000000..60e9b6a --- /dev/null +++ b/Code/Core/Math/math.h @@ -0,0 +1,473 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/math.h ** +** ** +** Created: 11/23/99 - mjb ** +** ** +** Description: Math Library ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_MATH_H +#define __CORE_MATH_MATH_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#ifdef __PLAT_XBOX__ +#include // Required for fabsf(). +#include +#endif + +#ifdef __PLAT_WN32__ +#include // Required for fabsf(). +#endif + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +#ifdef __USER_MATT__ +#define DEBUGGING_REPLAY_RND 0 +#else +#define DEBUGGING_REPLAY_RND 0 +#endif + +#if DEBUGGING_REPLAY_RND + +#define Rnd( x ) Rnd_DbgVersion( ( x ), __LINE__, __FILE__ ) + +#else +#define Rnd( x ) Rnd( x ) +#endif + +namespace Mth +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/***************************************************************************** +** Type Definitions ** +*****************************************************************************/ + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +void InitialRand(int a); + +#if DEBUGGING_REPLAY_RND + +int Rnd_DbgVersion(int n, int line, char *file); +void SetRndTestMode( int mode ); +bool RndFuckedUp( void ); + +#else + +int Rnd(int n); + +#endif + +// use for non-deterministic things +// especially for CD-timing reliant stuff that would throw off our random number dealy whopper. +int Rnd2(int n); + +float PlusOrMinus(float n); +void InitSinLookupTable(); +float Kensinf(float x); +float Kencosf(float x); +float Kenacosf(float x); +float Atan2 (float y, float x); // replaces atan2, much faster + + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +inline int Abs ( int x ) +{ + return ( x>=0?x:-x); +} + + +inline float Abs ( float x ) +{ + return float ( fabsf ( x )); +} + +// round down to nearest whole number +inline float Whole ( float x ) +{ + return (float) ( (int) ( x )); +} + +// round to nearest whole number +inline float Round ( float x ) +{ + return (float) ( (int) ( x + 0.499999f)); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// Calculate the "Parabolic Lerp" value for a set of parameters +// (Note: made-up name, by Mick) +// +// A lerp (Linear intERPalotion) value is always in the range 0.0 to 1.0 +// and is used to move one value (A) towards another (B) +// usually the lerp value is fixed. +// +// A new value C is calculated as C = A + lerp * (B - A) +// +// a "ParaLerp" is a lerp value that varies based on the distance apart of the two values +// +// The purpose of this function is to be able to smoothly change a value +// that really only needs lerping when the values are within a small range +// The result of this equation is a parabolic curve that quickly tends +// towards lerp = 1.0, as x increases +// The valeu "Rate" is the value of x for which lerp half way between Min and 1.0 +// Min is the value of lerp when x is 0 +inline float ParaLerp(float x, float Rate = 1.0f, float Minimum = 0.0f) +{ + float lerp = Minimum + (1 - 1 / (1 + x / Rate) ) * (1 - Minimum); + return lerp; + +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float ClampMin ( float v, float min ) +{ + if ( v < min ) + { + return min; + } + else + { + return v; + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float ClampMax ( float v, float max ) +{ + if ( v > max ) + { + return max; + } + else + { + return v; + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Clamp ( float v, float min, float max ) +{ + return ClampMin ( ClampMax ( v, max ), min ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Lerp ( float a, float b, float value ) +{ + return a + ( b - a ) * value; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float LinearMap ( float a, float b, float value, float value_min, float value_max ) +{ + return Lerp(a, b, (value - value_min) / (value_max - value_min)); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float SmoothStep ( float value ) +{ + // interpolates from zero to one with a zero derivative at the end-points + return -2.0f * value * value * ( value - (3.0f / 2.0f) ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float SmoothInterp ( float a, float b, float value ) +{ + return Lerp(a, b, SmoothStep(value)); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float SmoothMap ( float a, float b, float value, float value_min, float value_max ) +{ + return Lerp(a, b, SmoothStep((value - value_min) / (value_max - value_min))); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Max ( float a, float b ) +{ + return ( a > b ) ? a : b; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Min ( float a, float b ) +{ + return ( a < b ) ? a : b; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Sgn ( float x ) +{ + if ( x < 0.f ) + { + return -1.0f; + } + else + { + return 1.0f; + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Sqr ( float x ) +{ + return ( x * x ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Swap ( float& a, float& b ) +{ + float t = a; + a = b; + b = t; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Equal ( const float a, const float b, const float perc ) +{ + return ( Abs ( a - b ) <= (( Abs ( a ) + Abs ( b )) * perc )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline sint Max ( sint a, sint b ) +{ + return ( a > b ) ? a : b; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline sint Min ( sint a, sint b ) +{ + return ( a < b ) ? a : b; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Max3 ( float a, float b, float c ) +{ + return Max(a, Max(b,c)); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Min3 ( float a, float b, float c ) +{ + return Min(a, Min(b,c)); +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Determinant ( float a, float b, float c, float d ) +{ + return ( a * d ) - ( b * c ); + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Determinant3 ( float a1, float a2, float a3, float b1, float b2, float b3, float c1, float c2, float c3 ) +{ + return a1 * (b2 * c3 - b3 * c2) - + b1 * (a2 * c3 - a3 * c2) + + c1 * (a2 * b3 - a3 * b2); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline int WeightedRnd ( float min_val, float max_val ) +{ + // This random function uses the West distribution + // to return values that favor the midpoint. + + // For example, WeightedRnd( 10, 20 ) returns: + // Bucket[10] = 2.12 percent + // Bucket[11] = 5.72 percent + // Bucket[12] = 9.44 percent + // Bucket[13] = 13.87 percent + // Bucket[14] = 18.16 percent + // Bucket[15] = 15.70 percent + // Bucket[16] = 14.52 percent + // Bucket[17] = 10.16 percent + // Bucket[18] = 6.05 percent + // Bucket[19] = 2.26 percent + // Bucket[20] = 2.00 percent + + float range = ( max_val - min_val ) / 2; + float midpoint = min_val + range; + + float random_val = range - (float)sqrtf((float)Rnd( (int)(range * range) ) ); + + // negate it half of the time + if ( Rnd(2) ) + { + random_val = -random_val; + } + + return (int)( random_val + midpoint ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + + +// Trig + +const float EPSILON = 0.000001f; +const float PI = 3.141592654f; + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float DegToRad ( float degrees ) +{ + return degrees * ( PI / 180.0f); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float RadToDeg ( float radians ) +{ + return radians * ( 180.0f / PI ); +} + + +int RunFilter( int target, int current, int delta ); +float FRunFilter( float target, float current, float delta ); + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_MATH_H diff --git a/Code/Core/Math/matrix.cpp b/Code/Core/Math/matrix.cpp new file mode 100644 index 0000000..b0eb649 --- /dev/null +++ b/Code/Core/Math/matrix.cpp @@ -0,0 +1,1049 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: matrix.cpp ** +** ** +** Created by: 10/03/2000 - mjb ** +** ** +** Description: Matrix Math Library code ** +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Mth +{ + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Matrix::Matrix( float p, float h, float r) +{ + + // Algorithm from http://www.flipcode.com/documents/matrfaq.html#Q37 + float A,B,C,D,E,F,AD,BD; + + A = cosf( p ); + B = sinf( p ); + C = cosf( h ); + D = sinf( h ); + E = cosf( r ); + F = sinf( r ); + + AD = A * D; + BD = B * D; + + row[0][0] = C * E; + row[0][1] = -C * F; + row[0][2] = -D; + row[1][0] = -BD * E + A * F; + row[1][1] = BD * F + A * E; + row[1][2] = -B * C; + row[2][0] = AD * E + B * F; + row[2][1] = -AD * F + B * E; + row[2][2] = A * C; + + row[0][3] = row[1][3] = row[2][3] = row[3][0] = row[3][1] = row[3][2] = 0.0f; + row[3][3] = 1.0f; +} + +#ifdef __PLAT_NGPS__ +void xsceVu0MulMatrix(Mth::Matrix* m0, Mth::Matrix* m1, const Mth::Matrix* m2) +{ + + asm __volatile__(" + lqc2 vf4,0x0(%2) + lqc2 vf5,0x10(%2) + lqc2 vf6,0x20(%2) + lqc2 vf7,0x30(%2) + li $7,4 +_loopMulMatrix: + lqc2 vf8,0x0(%1) + vmulax.xyzw ACC, vf4,vf8 + vmadday.xyzw ACC, vf5,vf8 + vmaddaz.xyzw ACC, vf6,vf8 + vmaddw.xyzw vf9,vf7,vf8 + sqc2 vf9,0x0(%0) + addi $7,-1 + addi %1,0x10 + addi %0,0x10 + bne $0,$7,_loopMulMatrix + ": : "r" (m0), "r" (m2), "r" (m1) : "$7"); +} +#endif + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +// Ken: Faster version of atan2f, using the same algorithm used by VU1 +// (Coeefficients got from the VU manual, from documentation of EATANxy function, p130 +// SPEEDOPT: This could probably be made even faster using a less accurate approx with few coeffs. +float katan(float y, float x) +{ + if (fabs(y)>fabs(x)) + { + // The approximation only works for y<=x, bummer! + return atan2f(y,x); + } + register bool x_negative=false; + register bool y_negative=false; + if (x<0.0f) + { + x=-x; + x_negative=true; + } + if (y<=-0.0f) + { + y=-y; + y_negative=true; + } + + // Use the non-ratio version, because the ratio version goes + // all innaccurate for some values ... don't know why ... + y=y/x; + + register float t1=0.999999344348907f; + register float t2=-0.333298563957214f; + register float t3=0.199465364217758f; + register float t4=-0.139085337519646f; + register float t5=0.096420042216778f; + register float t6=-0.055909886956215f; + register float t7=0.021861229091883f; + register float t8=-0.004054057877511f; + + register float t=(y-1.0f)/(y+1.0f); + register float tt=t*t; + register float s=t8*t; + s=s*tt+t7*t; + s=s*tt+t6*t; + s=s*tt+t5*t; + s=s*tt+t4*t; + s=s*tt+t3*t; + s=s*tt+t2*t; + s=s*tt+t1*t; + if (x_negative) + { + if (y_negative) + { + return s+0.785398185253143f-3.141592653589793f; + } + else + { + return 3.141592653589793f-0.785398185253143f-s; + } + } + else + { + if (y_negative) + { + return -0.785398185253143f-s; + } + else + { + return s+0.785398185253143f; + } + } +} + +void Matrix::GetEulers( Vector& euler ) const +{ + + + // Algorithm from http://www.flipcode.com/documents/matrfaq.html#Q37 + float C, D; + float tr_x, tr_y; + +#ifdef __PLAT_NGC__ + float ang = row[0][2]; + if ( ang > 1.0f ) ang = 1.0f; + if ( ang < -1.0f ) ang = -1.0f; + euler[Y] = D = -asinf( ang ); /* Calculate Y-axis angle */ +#else + float ang = row[0][2]; + if ( ang > 1.0f ) ang = 1.0f; + if ( ang < -1.0f ) ang = -1.0f; + euler[Y] = D = -asinf( ang ); /* Calculate Y-axis angle */ +// euler[Y] = D = -asinf( row[0][2]); /* Calculate Y-axis angle */ +#endif // __PLAT_NGC__ + C = cosf( euler[Y] ); + + if( fabsf( C ) > 0.005f ) /* Gimball lock? */ + { + tr_x = row[2][2] / C; /* No, so get X-axis angle */ + tr_y = -row[1][2] / C; + + euler[X] = katan( tr_y, tr_x ); + + tr_x = row[0][0] / C; /* Get Z-axis angle */ + tr_y = -row[0][1] / C; + + euler[Z] = katan( tr_y, tr_x ); + } + else /* Gimball lock has occurred */ + { + euler[X] = 0; /* Set X-axis angle to zero */ + + tr_x = row[1][1]; /* And calculate Z-axis angle */ + tr_y = row[1][0]; + + euler[Z] = katan( tr_y, tr_x ); + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Matrix::PrintContents() const +{ + printf( "-----------------\n" ); + printf( "%f %f %f %f\n", row[RIGHT][X], row[RIGHT][Y], row[RIGHT][Z], row[RIGHT][W] ); + printf( "%f %f %f %f\n", row[UP][X], row[UP][Y], row[UP][Z], row[UP][W] ); + printf( "%f %f %f %f\n", row[AT][X], row[AT][Y], row[AT][Z], row[AT][W] ); + printf( "%f %f %f %f\n", row[POS][X], row[POS][Y], row[POS][Z], row[POS][W] ); + printf( "-----------------\n" ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +/* +// Original source of the optimized fromToRoation function + +#include + +#define EPSILON 0.000001 + +#define CROSS(dest, v1, v2){ \ + dest[0] = v1[1] * v2[2] - v1[2] * v2[1]; \ + dest[1] = v1[2] * v2[0] - v1[0] * v2[2]; \ + dest[2] = v1[0] * v2[1] - v1[1] * v2[0];} + +#define DOT(v1, v2) (v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]) + +#define SUB(dest, v1, v2){ \ + dest[0] = v1[0] - v2[0]; \ + dest[1] = v1[1] - v2[1]; \ + dest[2] = v1[2] - v2[2];} + +// +// * A function for creating a rotation matrix that rotates a vector called +// * "from" into another vector called "to". +// * Input : from[3], to[3] which both must be *normalized* non-zero vectors +// * Output: mtx[3][3] -- a 3x3 matrix in colum-major form +// * Authors: Tomas Möller, John Hughes 1999 +// +void fromToRotation(float from[3], float to[3], float mtx[3][3]) +{ + float v[3]; + float e, h, f; + + CROSS(v, from, to); + e = DOT(from, to); + f = (e < 0)? -e:e; + if (f > 1.0 - EPSILON) // "from" and "to"-vector almost parallel + { + float u[3], v[3]; // temporary storage vectors + float x[3]; // vector most nearly orthogonal to "from" + float c1, c2, c3; // coefficients for later use + int i, j; + + x[0] = (from[0] > 0.0)? from[0] : -from[0]; + x[1] = (from[1] > 0.0)? from[1] : -from[1]; + x[2] = (from[2] > 0.0)? from[2] : -from[2]; + + if (x[0] < x[1]) + { + if (x[0] < x[2]) + { + x[0] = 1.0; x[1] = x[2] = 0.0; + } + else + { + x[2] = 1.0; x[0] = x[1] = 0.0; + } + } + else + { + if (x[1] < x[2]) + { + x[1] = 1.0; x[0] = x[2] = 0.0; + } + else + { + x[2] = 1.0; x[0] = x[1] = 0.0; + } + } + + u[0] = x[0] - from[0]; u[1] = x[1] - from[1]; u[2] = x[2] - from[2]; + v[0] = x[0] - to[0]; v[1] = x[1] - to[1]; v[2] = x[2] - to[2]; + + c1 = 2.0 / DOT(u, u); + c2 = 2.0 / DOT(v, v); + c3 = c1 * c2 * DOT(u, v); + + for (i = 0; i < 3; i++) { + for (j = 0; j < 3; j++) { + mtx[i][j] = - c1 * u[i] * u[j] + - c2 * v[i] * v[j] + + c3 * v[i] * u[j]; + } + mtx[i][i] += 1.0; + } + } + else // the most common case, unless "from"="to", or "from"=-"to" + { +#if 0 + // unoptimized version - a good compiler will optimize this. + h = (1.0 - e)/DOT(v, v); + mtx[0][0] = e + h * v[0] * v[0]; + mtx[0][1] = h * v[0] * v[1] - v[2]; + mtx[0][2] = h * v[0] * v[2] + v[1]; + + mtx[1][0] = h * v[0] * v[1] + v[2]; + mtx[1][1] = e + h * v[1] * v[1]; + mtx[1][2] = h * v[1] * v[2] - v[0]; + + mtx[2][0] = h * v[0] * v[2] - v[1]; + mtx[2][1] = h * v[1] * v[2] + v[0]; + mtx[2][2] = e + h * v[2] * v[2]; +#else + // ...otherwise use this hand optimized version (9 mults less) + float hvx, hvz, hvxy, hvxz, hvyz; + h = (1.0 - e)/DOT(v, v); + hvx = h * v[0]; + hvz = h * v[2]; + hvxy = hvx * v[1]; + hvxz = hvx * v[2]; + hvyz = hvz * v[1]; + mtx[0][0] = e + hvx * v[0]; + mtx[0][1] = hvxy - v[2]; + mtx[0][2] = hvxz + v[1]; + + mtx[1][0] = hvxy + v[2]; + mtx[1][1] = e + h * v[1] * v[1]; + mtx[1][2] = hvyz - v[0]; + + mtx[2][0] = hvxz - v[1]; + mtx[2][1] = hvyz + v[0]; + mtx[2][2] = e + hvz * v[2]; +#endif + } +} +*/ + + +#define FROMTO_EPSILON 0.000001f + + +//////////////////////////////////////////////////////////////////////////////// +//Matrix& CreateFromToMatrix( Matrix &mtx, Vector from, Vector to ) +// +// * A function for creating a rotation matrix that rotates a vector called +// * "from" into another vector called "to". +// * Input : from[3], to[3] which both must be *normalized* non-zero vectors +// * Output: mtx[3][3] -- a 3x3 matrix in colum-major form +// * Authors: Tomas Möller, John Hughes 1999 +// +// +// Micknote .. on testing this, it seems like it produces the inverse of what we want +// possibly just need to swap the from/to vectors, but for now I'm just inverting it +// at the end of the function, before it returns the matrix +// +// Sample usage: +// Mth::Matrix rotate; +// Mth::CreateFromToMatrix(rotate,m_matrix[Y],m_normal); +// m_matrix *= rotate; +// +// will rotate m_matrix, so the Y component is coincident with m_normal +// (this is used to align the skater to a surface, given the surface normal) +// + +Matrix& CreateFromToMatrix( Matrix &mtx, Vector from, Vector to ) +{ + Vector cross; + float e, h, f; + + + mtx.Ident(); // clean up W rows and cols + + + cross = CrossProduct(from,to); // CROSS(v, from, to); + e = DotProduct(from,to); // e = DOT(from, to); + f = (e < 0.0f)? -e:e; + if (f > 1.0f - FROMTO_EPSILON) // "from" and "to"-vector almost parallel + { + Vector u, v; // temporary storage vectors + Vector x; // vector most nearly orthogonal to "from" + float c1, c2, c3; // coefficients for later use + int i, j; + + x[0] = (from[0] >= 0.0f)? from[0] : -from[0]; + x[1] = (from[1] >= 0.0f)? from[1] : -from[1]; + x[2] = (from[2] >= 0.0f)? from[2] : -from[2]; + + if (x[0] < x[1]) + { + if (x[0] < x[2]) + { + x[0] = 1.0f; x[1] = x[2] = 0.0f; + } + else + { + x[2] = 1.0f; x[0] = x[1] = 0.0f; + } + } + else + { + if (x[1] < x[2]) + { + x[1] = 1.0f; x[0] = x[2] = 0.0f; + } + else + { + x[2] = 1.0f; x[0] = x[1] = 0.0f; + } + } + + u[0] = x[0] - from[0]; u[1] = x[1] - from[1]; u[2] = x[2] - from[2]; + v[0] = x[0] - to[0]; v[1] = x[1] - to[1]; v[2] = x[2] - to[2]; + + c1 = 2.0f / DotProduct(u, u); + c2 = 2.0f / DotProduct(v, v); + c3 = c1 * c2 * DotProduct(u, v); + + for (i = 0; i < 3; i++) + { + for (j = 0; j < 3; j++) + { + mtx[i][j] = - c1 * u[i] * u[j] + - c2 * v[i] * v[j] + + c3 * v[i] * u[j]; + } + mtx[i][i] += 1.0f; + } + } + else // the most common case, unless "from"="to", or "from"=-"to" + { +#if 0 + // unoptimized version - a good compiler will optimize this. + h = (1.0f - e)/DOT(v, v); + mtx[0][0] = e + h * v[0] * v[0]; + mtx[0][1] = h * v[0] * v[1] - v[2]; + mtx[0][2] = h * v[0] * v[2] + v[1]; + + mtx[1][0] = h * v[0] * v[1] + v[2]; + mtx[1][1] = e + h * v[1] * v[1]; + mtx[1][2] = h * v[1] * v[2] - v[0]; + + mtx[2][0] = h * v[0] * v[2] - v[1]; + mtx[2][1] = h * v[1] * v[2] + v[0]; + mtx[2][2] = e + h * v[2] * v[2]; +#else + // ...otherwise use this hand optimized version (9 mults less) + float hvx, hvz, hvxy, hvxz, hvyz; + h = (1.0f - e)/DotProduct(cross, cross); + hvx = h * cross[0]; + hvz = h * cross[2]; + hvxy = hvx * cross[1]; + hvxz = hvx * cross[2]; + hvyz = hvz * cross[1]; + mtx[0][0] = e + hvx * cross[0]; + mtx[0][1] = hvxy - cross[2]; + mtx[0][2] = hvxz + cross[1]; + + mtx[1][0] = hvxy + cross[2]; + mtx[1][1] = e + h * cross[1] * cross[1]; + mtx[1][2] = hvyz - cross[0]; + + mtx[2][0] = hvxz - cross[1]; + mtx[2][1] = hvyz + cross[0]; + mtx[2][2] = e + hvz * cross[2]; +#endif + } + + + mtx.Invert(); // Micknote: not sure why we need to invert it, but let it be for now + + + return mtx; +} + + +Matrix& CreateRotateMatrix ( Matrix& mat, const Vector& axis, const float angle ) +{ + + + Vector unitAxis = axis; + unitAxis.Normalize(); + + float oneMinusCosine = 1.0f - cosf( angle ); + + Vector leading; + + leading[X] = 1.0f - ( unitAxis[X] * unitAxis[X] ); + leading[Y] = 1.0f - ( unitAxis[Y] * unitAxis[Y] ); + leading[Z] = 1.0f - ( unitAxis[Z] * unitAxis[Z] ); + leading *= oneMinusCosine; + + Vector crossed; + + crossed[X] = ( unitAxis[Y] * unitAxis[Z] ); + crossed[Y] = ( unitAxis[Z] * unitAxis[X] ); + crossed[Z] = ( unitAxis[X] * unitAxis[Y] ); + crossed *= oneMinusCosine; + + unitAxis *= sinf( angle ); + + mat[RIGHT][X] = 1.0f - leading[X]; + mat[RIGHT][Y] = crossed[Z] + unitAxis[Z]; + mat[RIGHT][Z] = crossed[Y] - unitAxis[Y]; + mat[RIGHT][W] = 0.0f; + + mat[UP][X] = crossed[Z] - unitAxis[Z]; + mat[UP][Y] = 1.0f - leading[Y]; + mat[UP][Z] = crossed[X] + unitAxis[X]; + mat[UP][W] = 0.0f; + + mat[AT][X] = crossed[Y] + unitAxis[Y]; + mat[AT][Y] = crossed[X] - unitAxis[X]; + mat[AT][Z] = 1.0f - leading[Z]; + mat[AT][W] = 0.0f; + + mat[POS][X] = 0.0f; + mat[POS][Y] = 0.0f; + mat[POS][Z] = 0.0f; + mat[POS][W] = 1.0f; + + return mat; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Matrix& CreateRotateXMatrix( Matrix& rotX, float angle ) +{ + + + float s = sinf( angle ); + float c = cosf( angle ); + + rotX[RIGHT][X] = 1.0f; + rotX[RIGHT][Y] = 0.0f; + rotX[RIGHT][Z] = 0.0f; + rotX[RIGHT][W] = 0.0f; + + rotX[UP][X] = 0.0f; + rotX[UP][Y] = c; + rotX[UP][Z] = s; + rotX[UP][W] = 0.0f; + + rotX[AT][X] = 0.0f; + rotX[AT][Y] = -s; + rotX[AT][Z] = c; + rotX[AT][W] = 0.0f; + + rotX[POS][X] = 0.0f; + rotX[POS][Y] = 0.0f; + rotX[POS][Z] = 0.0f; + rotX[POS][W] = 1.0f; + + return rotX; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Matrix& CreateRotateYMatrix( Matrix& rotY, float angle ) +{ + + + float s = sinf( angle ); + float c = cosf( angle ); + + rotY[RIGHT][X] = c; + rotY[RIGHT][Y] = 0.0f; + rotY[RIGHT][Z] = -s; + rotY[RIGHT][W] = 0.0f; + + rotY[UP][X] = 0.0f; + rotY[UP][Y] = 1.0f; + rotY[UP][Z] = 0.0f; + rotY[UP][W] = 0.0f; + + rotY[AT][X] = s; + rotY[AT][Y] = 0.0f; + rotY[AT][Z] = c; + rotY[AT][W] = 0.0f; + + rotY[POS][X] = 0.0f; + rotY[POS][Y] = 0.0f; + rotY[POS][Z] = 0.0f; + rotY[POS][W] = 1.0f; + + return rotY; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Matrix& CreateRotateZMatrix( Matrix& rotZ, float angle ) +{ + + + float s = sinf( angle ); + float c = cosf( angle ); + + rotZ[RIGHT][X] = c; + rotZ[RIGHT][Y] = s; + rotZ[RIGHT][Z] = 0.0f; + rotZ[RIGHT][W] = 0.0f; + + rotZ[UP][X] = -s; + rotZ[UP][Y] = c; + rotZ[UP][Z] = 0; + rotZ[UP][W] = 0.0f; + + rotZ[AT][X] = 0.0f; + rotZ[AT][Y] = 0.0f; + rotZ[AT][Z] = 1.0f; + rotZ[AT][W] = 0.0f; + + rotZ[POS][X] = 0.0f; + rotZ[POS][Y] = 0.0f; + rotZ[POS][Z] = 0.0f; + rotZ[POS][W] = 1.0f; + + return rotZ; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Matrix& CreateRotateMatrix( Matrix &rot, int axis, const float angle ) +{ + int a = (axis + 1) % (Z + 1); + int b = (axis + 2) % (Z + 1); + + float s = sinf( angle ); + float c = cosf( angle ); + + rot.Ident(); + + rot[a][a] = c; + rot[a][b] = s; + rot[b][a] = -s; + rot[b][b] = c; + + return rot; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Matrix& CreateMatrixLookAt( Matrix& mat, const Vector& pos, const Vector& lookat, const Vector& up ) +{ + // Create Z axis + Mth::Vector z_axis(lookat - pos); + z_axis.Normalize(); + //mat[Z] = z_axis; + + // Create X from Z and up (up assumed to be normalized) + Mth::Vector x_axis(Mth::CrossProduct(z_axis, up)); + //mat[X] = x_axis; + + // Create Y from Z and X + Mth::Vector y_axis(Mth::CrossProduct(x_axis, z_axis)); + //mat[Y] = y_axis; + + // Orientation needs transposing (since we want the inverse orientation) + //mat.Transpose(); + mat[X][X] = x_axis[X]; + mat[Y][X] = x_axis[Y]; + mat[Z][X] = x_axis[Z]; + + mat[X][Y] = y_axis[X]; + mat[Y][Y] = y_axis[Y]; + mat[Z][Y] = y_axis[Z]; + + mat[X][Z] = z_axis[X]; + mat[Y][Z] = z_axis[Y]; + mat[Z][Z] = z_axis[Z]; + + // These may not be zero, but should be + mat[X][W] = 0.0f; + mat[Y][W] = 0.0f; + mat[Z][W] = 0.0f; + + // Create inverse translation + mat[POS][X] = -Mth::DotProduct(x_axis, pos); + mat[POS][Y] = -Mth::DotProduct(y_axis, pos); + mat[POS][Z] = -Mth::DotProduct(z_axis, pos); + mat[POS][W] = 1.0f; + + //Dbg_Message("LookAt matrix:"); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[X][X], mat[X][Y], mat[X][Z], mat[X][W]); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[Y][X], mat[Y][Y], mat[Y][Z], mat[Y][W]); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[Z][X], mat[Z][Y], mat[Z][Z], mat[Z][W]); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[W][X], mat[W][Y], mat[W][Z], mat[W][W]); + + return mat; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Matrix& CreateMatrixOrtho( Matrix& mat, float width, float height, float f_near, float f_far ) +{ + mat[X] = Mth::Vector(2.0f / width, 0.0f, 0.0f, 0.0f); + mat[Y] = Mth::Vector(0.0f, 2.0f / height, 0.0f, 0.0f); + mat[Z] = Mth::Vector(0.0f, 0.0f, 2.0f / (f_far - f_near), 0.0f); + mat[W] = Mth::Vector(0.0f, 0.0f, -(f_far + f_near) / (f_far - f_near), 1.0f); + + //Dbg_Message("Orthographic matrix:"); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[X][X], mat[X][Y], mat[X][Z], mat[X][W]); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[Y][X], mat[Y][Y], mat[Y][Z], mat[Y][W]); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[Z][X], mat[Z][Y], mat[Z][Z], mat[Z][W]); + //Dbg_Message("[ %f, %f, %f, %f ]", mat[W][X], mat[W][Y], mat[W][Z], mat[W][W]); + + return mat; +} + +// Orthonormalize a matrix keeping one row r0 unchanged +// (Mick accepts responsibility for this). +Matrix& Matrix::OrthoNormalizeAbout(int r0) +{ + int r1, r2; + r1 = r0+1; + if (r1 == 3) + { + r1 = 0; + } + r2 = r1+1; + if (r2 == 3) + { + r2 = 0; + } + // Now regarding Rows r0,r1,r2 + // r0 = r1 x r2 (implied) + // r1 = r2 x r0 (calculate this) + // r2 = r0 x r1 (and this) + // + // We need to recalculate rows r1 and r2 using the above cross produces + // however if r0 is close to r2, then the calculation of r1 will be off + // so it's better to calulate r2 and then r1 + // the first pair to do will be whichever has the smaller dot product + + if (Abs(DotProduct(*(Vector*)(row[r2]),*(Vector*)(row[r0]))) < Abs(DotProduct(*(Vector*)(row[r0]),*(Vector*)(row[r1])))) + { + *(Vector*)(row[r1]) = Mth::CrossProduct(*(Vector*)(row[r2]),*(Vector*)(row[r0])); + (*(Vector*)(row[r1])).Normalize(); + *(Vector*)(row[r2]) = Mth::CrossProduct(*(Vector*)(row[r0]),*(Vector*)(row[r1])); + (*(Vector*)(row[r2])).Normalize(); + } + else + { + *(Vector*)(row[r2]) = Mth::CrossProduct(*(Vector*)(row[r0]),*(Vector*)(row[r1])); + (*(Vector*)(row[r2])).Normalize(); + *(Vector*)(row[r1]) = Mth::CrossProduct(*(Vector*)(row[r2]),*(Vector*)(row[r0])); + (*(Vector*)(row[r1])).Normalize(); + } + + return *this; + +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Matrix::GetRotationAxisAndAngle( Vector* pAxis, float* pRadians ) +{ + // Given a transform matrix (end = start * xform) + // this will return its rotation axis and angle + + Dbg_Assert( pAxis ); + Dbg_Assert( pRadians ); + + float nTwoSinTheta, nTwoCosTheta; + Mth::Vector vTwoSinThetaAxis; + + nTwoCosTheta = row[Mth::RIGHT][X] + row[Mth::UP][Y] + row[Mth::AT][Z] - 1.0f; + + vTwoSinThetaAxis[X] = row[Mth::UP][Z] - row[Mth::AT][Y]; + vTwoSinThetaAxis[Y] = row[Mth::AT][X] - row[Mth::RIGHT][Z]; + vTwoSinThetaAxis[Z] = row[Mth::RIGHT][Y] - row[Mth::UP][X]; + vTwoSinThetaAxis[W] = 1.0f; + + // Gary: There used to be a magic patch added by Dave + // (basically negating the axis) which made it work with + // the RW-based Slerp object. This doesn't seem to be + // necessary any more, but I'm going to leave it + // in the code just in case our Slerp stops working... +// vTwoSinThetaAxis[X] = row[Mth::AT][Y] - row[Mth::UP][Z]; +// vTwoSinThetaAxis[Y] = row[Mth::RIGHT][Z] - row[Mth::AT][X]; +// vTwoSinThetaAxis[Z] = row[Mth::UP][X] - row[Mth::RIGHT][Y]; +// vTwoSinThetaAxis[W] = 1.0f; + + nTwoSinTheta = vTwoSinThetaAxis.Length(); + + if (nTwoSinTheta > 0.0f) + { + float recipLength = (1.0f / (nTwoSinTheta)); + + *pAxis = vTwoSinThetaAxis; + pAxis->Scale( recipLength ); + } + else + { + pAxis->Set( 0.0f, 0.0f, 0.0f ); + } + + (*pRadians) = (float)atan2(nTwoSinTheta, nTwoCosTheta); + if ((nTwoSinTheta <= 0.01f) && (nTwoCosTheta <= 0.0f)) + { + /* + * sin theta is 0; cos theta is -1; theta is 180 degrees + * vTwoSinThetaAxis was degenerate + * axis will have to be found another way. + */ + + //Vector vTwoSinThetaAxis; + + /* + * Matrix is: + * [ [ 2 a_x^2 - 1, 2 a_x a_y, 2 a_x a_z, 0 ] + * [ 2 a_x a_y, 2 a_y^2 - 1, 2 a_y a_z, 0 ] + * [ 2 a_x a_z, 2 a_y a_z, 2 a_z^2 - 1, 0 ] + * [ 0, 0, 0, 1 ] ] + * Build axis scaled by 4 * component of maximum absolute value + */ + if (row[Mth::RIGHT][X] > row[Mth::UP][Y]) + { + if (row[Mth::RIGHT][X] > row[Mth::AT][Z]) + { + vTwoSinThetaAxis[X] = 1.0f + row[Mth::RIGHT][X]; + vTwoSinThetaAxis[X] = vTwoSinThetaAxis[X] + vTwoSinThetaAxis[X]; + vTwoSinThetaAxis[Y] = row[Mth::RIGHT][Y] + row[Mth::UP][X]; + vTwoSinThetaAxis[Z] = row[Mth::RIGHT][Z] + row[Mth::AT][X]; + } + else + { + vTwoSinThetaAxis[Z] = 1.0f + row[Mth::AT][Z]; + vTwoSinThetaAxis[Z] = vTwoSinThetaAxis[Z] + vTwoSinThetaAxis[Z]; + vTwoSinThetaAxis[X] = row[Mth::AT][X] + row[Mth::RIGHT][Z]; + vTwoSinThetaAxis[Y] = row[Mth::AT][Y] + row[Mth::UP][Z]; + } + } + else + { + if (row[Mth::UP][Y] > row[Mth::AT][Z]) + { + vTwoSinThetaAxis[Y] = 1.0f + row[Mth::UP][Y]; + vTwoSinThetaAxis[Y] = vTwoSinThetaAxis[Y] + vTwoSinThetaAxis[Y]; + vTwoSinThetaAxis[Z] = row[Mth::UP][Z] + row[Mth::AT][Y]; + vTwoSinThetaAxis[X] = row[Mth::UP][X] + row[Mth::RIGHT][Y]; + } + else + { + vTwoSinThetaAxis[Z] = 1.0f + row[Mth::AT][Z]; + vTwoSinThetaAxis[Z] = vTwoSinThetaAxis[Z] + vTwoSinThetaAxis[Z]; + vTwoSinThetaAxis[X] = row[Mth::AT][X] + row[Mth::RIGHT][Z]; + vTwoSinThetaAxis[Y] = row[Mth::AT][Y] + row[Mth::UP][Z]; + } + } + + /* + * and normalize the axis + */ + + *pAxis = vTwoSinThetaAxis; + pAxis->Normalize(); + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Matrix::RotateLocal( const Vector &rot ) +{ + if ( rot[ X ] ) + { + RotateXLocal( rot[ X ] ); + } + if ( rot[ Y ] ) + { + RotateYLocal( rot[ Y ] ); + } + if ( rot[ Z ] ) + { + RotateZLocal( rot[ Z ] ); + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +bool Matrix::PatchOrthogonality ( ) +{ + // Given a matrix m which is assumed to be orthonormal, check to see if it is, and if not then fix it up + // returns true if any patching was done + + float lx,ly,lz; + lx = (*(Vector*)(row[X])).Length(); + ly = (*(Vector*)(row[Y])).Length(); + lz = (*(Vector*)(row[Z])).Length(); + + const float near1 = 0.99; + + if (lx < near1) + { + if (ly < near1) + { + if (lz < near1) + { + // everything collapsing to a point!!! + // just reset to the identity matrix + Ident(); + } + else + { + // x and y have collapsed, but Z is okay + Ident(); + } + } + else + { + if (lz < near1) + { + // x and z have collapsed, y is okay (most likely situation) + Ident(); + } + else + { + // just x has collapsed, y and z are okay + *(Vector*)(row[X]) = Mth::CrossProduct(*(Vector*)(row[Y]),*(Vector*)(row[Z])); + (*(Vector*)(row[X])).Normalize(); + } + } + } + else + { + if (ly < near1) + { + if (lz < near1) + { + // y and z collapsed, x is okay + Ident(); + } + else + { + // y has collapsed, x and Z are okay + *(Vector*)(row[Y]) = Mth::CrossProduct(*(Vector*)(row[Z]),*(Vector*)(row[X])); + (*(Vector*)(row[Y])).Normalize(); + } + } + else + { + if (lz < near1) + { + // just z has collapsed, x and y is okay + *(Vector*)(row[Z]) = Mth::CrossProduct(*(Vector*)(row[X]),*(Vector*)(row[Y])); + (*(Vector*)(row[Z])).Normalize(); + } + else + { + // nothing has collapsed + return false; + } + } + } + + return true; +} + +} // namespace Mth + + diff --git a/Code/Core/Math/matrix.h b/Code/Core/Math/matrix.h new file mode 100644 index 0000000..50ab58a --- /dev/null +++ b/Code/Core/Math/matrix.h @@ -0,0 +1,191 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/Matrix.h ** +** ** +** Created: 11/29/99 - mjb ** +** ** +** Description: 4x4 Matrix Math Class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_MATRIX_H +#define __CORE_MATH_MATRIX_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Mth +{ + + + + +enum +{ + RIGHT, UP, AT, POS +}; + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/**************************************************************************** + * + * Class: Matrix + * + * Description: 4x4 Matrix math class + * + ****************************************************************************/ + +class Matrix +{ + + +public: + Matrix ( void ); + Matrix ( const Matrix& src ); + Matrix ( const Vector& axis, const float angle ); + Matrix ( int axis, const float angle ); + Matrix ( float p, float h, float r ); // Euler to Matrix + Matrix ( const Mth::Quat& orientaion ); + + Matrix& operator= ( const Matrix& src ); + Matrix& operator+= ( const Matrix& n ); + Matrix& operator*= ( const Matrix& n ); + Matrix& operator*= ( const float f ); + bool operator== ( const Matrix& n ) const; + bool operator!= ( const Matrix& n ) const; + + float Determinant ( void ) const; + bool IsIdent ( void ) const; + Matrix& Ident ( void ); // set to 4x4 identity + Matrix& Identity ( void ); // same, but better name + Matrix& Zero ( void ); // set to all 0's + Matrix& Adjoint ( const Matrix& src ); + Matrix& Invert ( const Matrix& src ); // this = Invert ( src ) + Matrix& Invert ( void ); // this = Invert ( this ) + Matrix& InvertUniform ( void ); + Matrix& Transpose ( const Matrix& src ); + Matrix& Transpose ( void ); + + Vector Transform ( const Vector& src ) const; + Vector TransformAsPos ( const Vector& src ) const; + Vector Rotate ( const Vector& src ) const; + + Matrix& OrthoNormalize ( const Matrix& src ); // this = OrthoNornalize ( src ) + Matrix& OrthoNormalize ( void ); // this = OrthoNormalize ( this ) + + void RotateLocal ( const Vector& src ); + Matrix& Translate ( const Vector& trans ); + Matrix& TranslateLocal ( const Vector& trans ); + + Matrix& Rotate ( const Vector& axis, const float angle ); + Matrix& RotateLocal ( const Vector& axis, const float angle ); + Matrix& RotateLocal ( int axis, float angle ); + Matrix& RotateX ( const float angle ); + Matrix& RotateXLocal ( const float angle ); + Matrix& RotateY ( const float angle ); + Matrix& RotateYLocal ( const float angle ); + Matrix& RotateZ ( const float angle ); + Matrix& RotateZLocal ( const float angle ); + + Matrix& Scale ( const Vector& scale ); + Matrix& ScaleLocal ( const Vector& scale ); + + Vector& GetRight ( void ) { return *(Vector*)(&row[RIGHT]); } + Vector& GetUp ( void ) { return *(Vector*)(&row[UP]); } + Vector& GetAt ( void ) { return *(Vector*)(&row[AT]); } + Vector& GetPos ( void ) { return *(Vector*)(&row[POS]); } + void SetPos ( const Vector& trans ); + + + Matrix& SetColumn ( sint i, const Vector& v ); + Vector GetColumn ( sint i ) const; + const Vector& operator[] ( sint i ) const; + Vector& operator[] ( sint i ); + + Matrix& OrthoNormalizeAbout(int r0); + void PrintContents() const; + + void GetEulers( Vector& euler ) const; + void GetRotationAxisAndAngle( Vector* pAxis, float* pRadians ); + + bool PatchOrthogonality ( ); + + Matrix& SetFromAngles ( const Vector& angles ); + +private: + + enum + { + NUM_ELEMENTS = 4 + }; + +// Vector row[NUM_ELEMENTS]; // Old style, an array of vectors + float row[NUM_ELEMENTS][4]; // New style, an array of float, for constructor optimization + +} nAlign(128); + +/***************************************************************************** +** Private Inline Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +#ifdef __PLAT_NGPS__ +void xsceVu0MulMatrix(Mth::Matrix* m0, Mth::Matrix* m1, const Mth::Matrix* m2); +#endif + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +void Swap ( Matrix& a, Matrix& b ); +void Slerp ( Matrix& result, const Matrix& s1, const Matrix& s2, float t ); +Matrix operator* ( const Matrix& m1, const Matrix& m2 ); +ostream& operator<< ( ostream& os, const Matrix& m ); + +Matrix& CreateRotateMatrix ( Matrix& mat, const Vector& axis, const float angle ); +Matrix& CreateRotateMatrix ( Matrix& mat, int axis, const float angle ); +Matrix& CreateRotateXMatrix ( Matrix& mat, const float angle ); +Matrix& CreateRotateYMatrix ( Matrix& mat, const float angle ); +Matrix& CreateRotateZMatrix ( Matrix& mat, const float angle ); +Matrix& CreateFromToMatrix( Matrix &mtx, Vector from, Vector to ); + +Matrix& CreateMatrixLookAt( Matrix& mat, const Vector& pos, const Vector& lookat, const Vector& up ); +Matrix& CreateMatrixOrtho( Matrix& mat, float width, float height, float f_near, float f_far ); + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_MATRIX_H + diff --git a/Code/Core/Math/matrix.inl b/Code/Core/Math/matrix.inl new file mode 100644 index 0000000..cbaefc3 --- /dev/null +++ b/Code/Core/Math/matrix.inl @@ -0,0 +1,1053 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (Mth) ** +** ** +** File name: core/math/Matrix.inl ** +** ** +** Created: 11/23/99 - mjb ** +** ** +** Description: 4x4 Matrix Math Class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_MATRIX_INL +#define __CORE_MATH_MATRIX_INL + +namespace Mth +{ + + + +/***************************************************************************** +** Private Inline Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Inline Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Ident ( void ) +{ + row[X][X] = 1.0f; + row[X][Y] = 0.0f; + row[X][Z] = 0.0f; + row[X][W] = 0.0f; + + row[Y][X] = 0.0f; + row[Y][Y] = 1.0f; + row[Y][Z] = 0.0f; + row[Y][W] = 0.0f; + + row[Z][X] = 0.0f; + row[Z][Y] = 0.0f; + row[Z][Z] = 1.0f; + row[Z][W] = 0.0f; + + row[W][X] = 0.0f; + row[W][Y] = 0.0f; + row[W][Z] = 0.0f; + row[W][W] = 1.0f; + + return *this; +} + + +inline Matrix::Matrix( void ) +{ + +#ifdef DEBUG_UNINIT_VECTORS +// For debugging uninitialized vectors, we set them to a magic value +// and then the vector code should assert when they are accessed +// via the [] operator, which casts a row to a vector + *((uint32*)(&row[X][X])) = 0x7F800010; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[X][Y])) = 0x7F800011; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[X][Z])) = 0x7F800012; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[X][W])) = 0x7F800013; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Y][X])) = 0x7F800014; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Y][Y])) = 0x7F800015; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Y][Z])) = 0x7F800016; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Y][W])) = 0x7F800017; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Z][X])) = 0x7F800018; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Z][Y])) = 0x7F800019; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Z][Z])) = 0x7F80001a; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[Z][W])) = 0x7F80001b; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[W][X])) = 0x7F80001c; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[W][Y])) = 0x7F80001d; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[W][Z])) = 0x7F80001e; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&row[W][W])) = 0x7F80001f; // Positive NANS (Not A Number - Signaling) +#else +#ifdef __INITIALIZE_VECTORS__ + Ident(); + +#endif +#endif +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix::Matrix( const Vector& axis, const float angle ) +{ + + + CreateRotateMatrix( *this, axis, angle ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix::Matrix( int axis, const float angle ) +{ + + + CreateRotateMatrix( *this, axis, angle ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix::Matrix( const Mth::Quat &orientation ) +{ + orientation.GetMatrix(*this); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline const Vector& Matrix::operator[] ( sint i ) const +{ +// Dbg_MsgAssert (( i >= X && i < NUM_ELEMENTS ),( "index out of range" )); + return *(Vector*)(row[i]); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Matrix::operator[] ( sint i ) +{ +// Dbg_MsgAssert (( i >= X && i < NUM_ELEMENTS ),( "index out of range" )); + return *(Mth::Vector*)(&row[i]); +} + + +/* +#ifdef __USE_VU0__ +inline void asm_copy(void* m0, void* m1) +{ + asm __volatile__(" + lq $6,0x0(%1) + lq $7,0x10(%1) + lq $8,0x20(%1) + lq $9,0x30(%1) + sq $6,0x0(%0) + sq $7,0x10(%0) + sq $8,0x20(%0) + sq $9,0x30(%0) + ": : "r" (m0) , "r" (m1):"$6","$7","$8","$9"); +} +#endif +*/ + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::operator= ( const Matrix& src ) +{ +#ifdef __USE_VU0__ +// Mick: This actually is faster than the asm_copy method as +// the compiler can optimize it better + *((uint128 *)&row[0])=*((uint128 *)&src[0]); + *((uint128 *)&row[1])=*((uint128 *)&src[1]); + *((uint128 *)&row[2])=*((uint128 *)&src[2]); + *((uint128 *)&row[3])=*((uint128 *)&src[3]); + // asm_copy(this,(void*)&src[0]); +#else + *(Vector*)row[X] = *(Vector*)src.row[X]; + *(Vector*)row[Y] = *(Vector*)src.row[Y]; + *(Vector*)row[Z] = *(Vector*)src.row[Z]; + *(Vector*)row[W] = *(Vector*)src.row[W]; +#endif + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix::Matrix( const Matrix& src ) +{ + *this = src; +} + + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Swap ( Matrix& a, Matrix& b ) +{ + Matrix t = a; + a = b; + b = t; +} + + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::operator*= ( const Matrix& n ) +{ +# ifdef __PLAT_XBOX__ + D3DXMatrixMultiply((D3DXMATRIX*)this, (D3DXMATRIX*)this, (D3DXMATRIX*)&n ); +# else +#ifdef __USE_VU0__ + sceVu0MulMatrix((sceVu0FVECTOR*)this,(sceVu0FVECTOR*)&n,(sceVu0FVECTOR*)this); +# else + + Vector vec; + + vec[X] = row[X][X] * n[X][X] + row[X][Y] * n[Y][X] + row[X][Z] * n[Z][X] + row[X][W] * n[W][X]; + vec[Y] = row[X][X] * n[X][Y] + row[X][Y] * n[Y][Y] + row[X][Z] * n[Z][Y] + row[X][W] * n[W][Y]; + vec[Z] = row[X][X] * n[X][Z] + row[X][Y] * n[Y][Z] + row[X][Z] * n[Z][Z] + row[X][W] * n[W][Z]; + vec[W] = row[X][X] * n[X][W] + row[X][Y] * n[Y][W] + row[X][Z] * n[Z][W] + row[X][W] * n[W][W]; + *(Vector*)row[X] = vec; + + vec[X] = row[Y][X] * n[X][X] + row[Y][Y] * n[Y][X] + row[Y][Z] * n[Z][X] + row[Y][W] * n[W][X]; + vec[Y] = row[Y][X] * n[X][Y] + row[Y][Y] * n[Y][Y] + row[Y][Z] * n[Z][Y] + row[Y][W] * n[W][Y]; + vec[Z] = row[Y][X] * n[X][Z] + row[Y][Y] * n[Y][Z] + row[Y][Z] * n[Z][Z] + row[Y][W] * n[W][Z]; + vec[W] = row[Y][X] * n[X][W] + row[Y][Y] * n[Y][W] + row[Y][Z] * n[Z][W] + row[Y][W] * n[W][W]; + *(Vector*)row[Y] = vec; + + vec[X] = row[Z][X] * n[X][X] + row[Z][Y] * n[Y][X] + row[Z][Z] * n[Z][X] + row[Z][W] * n[W][X]; + vec[Y] = row[Z][X] * n[X][Y] + row[Z][Y] * n[Y][Y] + row[Z][Z] * n[Z][Y] + row[Z][W] * n[W][Y]; + vec[Z] = row[Z][X] * n[X][Z] + row[Z][Y] * n[Y][Z] + row[Z][Z] * n[Z][Z] + row[Z][W] * n[W][Z]; + vec[W] = row[Z][X] * n[X][W] + row[Z][Y] * n[Y][W] + row[Z][Z] * n[Z][W] + row[Z][W] * n[W][W]; + *(Vector*)row[Z] = vec; + + vec[X] = row[W][X] * n[X][X] + row[W][Y] * n[Y][X] + row[W][Z] * n[Z][X] + row[W][W] * n[W][X]; + vec[Y] = row[W][X] * n[X][Y] + row[W][Y] * n[Y][Y] + row[W][Z] * n[Z][Y] + row[W][W] * n[W][Y]; + vec[Z] = row[W][X] * n[X][Z] + row[W][Y] * n[Y][Z] + row[W][Z] * n[Z][Z] + row[W][W] * n[W][Z]; + vec[W] = row[W][X] * n[X][W] + row[W][Y] * n[Y][W] + row[W][Z] * n[Z][W] + row[W][W] * n[W][W]; + *(Vector*)row[W] = vec; +# endif +# endif + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix operator* ( const Matrix& m1, const Matrix& m2 ) +{ + + + Matrix result = m1; + result *= m2; + + return result; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::operator*= ( const float s ) +{ + + + if ( s != 1.0f ) + { + *(Vector*)(row[X]) *= s; + *(Vector*)(row[Y]) *= s; + *(Vector*)(row[Z]) *= s; + *(Vector*)(row[W]) *= s; + } + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::operator+= ( const Matrix& n ) +{ + row[X][X] += n[X][X]; + row[X][Y] += n[X][Y]; + row[X][Z] += n[X][Z]; + row[X][W] += n[X][W]; + + row[Y][X] += n[Y][X]; + row[Y][Y] += n[Y][Y]; + row[Y][Z] += n[Y][Z]; + row[Y][W] += n[Y][W]; + + row[Z][X] += n[Z][X]; + row[Z][Y] += n[Z][Y]; + row[Z][Z] += n[Z][Z]; + row[Z][W] += n[Z][W]; + + row[W][X] += n[W][X]; + row[W][Y] += n[W][Y]; + row[W][Z] += n[W][Z]; + row[W][W] += n[W][W]; + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Matrix::operator== ( const Matrix& n ) const +{ + + + for ( sint i = 0; i < NUM_ELEMENTS; i++ ) + { + for ( sint j = 0; j < NUM_ELEMENTS; j++ ) + { + if ( row[i][j] != n[i][j] ) + { + return false; + } + } + } + + return true; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Matrix::operator!= ( const Matrix& n ) const +{ + + + return !( *this == n ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Matrix::Determinant ( void ) const +{ + + + return row[X][X] * Determinant3 ( row[Y][Y], row[Z][Y], row[W][Y], row[Y][Z], row[Z][Z], row[W][Z], row[Y][W], row[Z][W], row[W][W] ) + - row[X][Y] * Determinant3 ( row[Y][X], row[Z][X], row[W][X], row[Y][Z], row[Z][Z], row[W][Z], row[Y][W], row[Z][W], row[W][W] ) + + row[X][Z] * Determinant3 ( row[Y][X], row[Z][X], row[W][X], row[Y][Y], row[Z][Y], row[W][Y], row[Y][W], row[Z][W], row[W][W] ) + - row[X][W] * Determinant3 ( row[Y][X], row[Z][X], row[W][X], row[Y][Y], row[Z][Y], row[W][Y], row[Y][Z], row[Z][Z], row[W][Z] ); +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Matrix::IsIdent ( void ) const +{ + + + return ( row[X][X] == 1.0f && row[X][Y] == 0.0f && row[X][Z] == 0.0f && row[X][W] == 0.0f && + row[Y][X] == 0.0f && row[Y][Y] == 1.0f && row[Y][Z] == 0.0f && row[Y][W] == 0.0f && + row[Z][X] == 0.0f && row[Z][Y] == 0.0f && row[Z][Z] == 1.0f && row[Z][W] == 0.0f && + row[W][X] == 0.0f && row[W][Y] == 0.0f && row[W][Z] == 0.0f && row[W][W] == 1.0f ); +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Identity ( void ) +{ + row[X][X] = 1.0f; + row[X][Y] = 0.0f; + row[X][Z] = 0.0f; + row[X][W] = 0.0f; + + row[Y][X] = 0.0f; + row[Y][Y] = 1.0f; + row[Y][Z] = 0.0f; + row[Y][W] = 0.0f; + + row[Z][X] = 0.0f; + row[Z][Y] = 0.0f; + row[Z][Z] = 1.0f; + row[Z][W] = 0.0f; + + row[W][X] = 0.0f; + row[W][Y] = 0.0f; + row[W][Z] = 0.0f; + row[W][W] = 1.0f; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Zero ( void ) +{ + row[X][X] = 0.0f; + row[X][Y] = 0.0f; + row[X][Z] = 0.0f; + row[X][W] = 0.0f; + + row[Y][X] = 0.0f; + row[Y][Y] = 0.0f; + row[Y][Z] = 0.0f; + row[Y][W] = 0.0f; + + row[Z][X] = 0.0f; + row[Z][Y] = 0.0f; + row[Z][Z] = 0.0f; + row[Z][W] = 0.0f; + + row[W][X] = 0.0f; + row[W][Y] = 0.0f; + row[W][Z] = 0.0f; + row[W][W] = 0.0f; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Adjoint ( const Matrix& src ) +{ + + + row[X][X] = Determinant3 ( src[Y][Y], src[Z][Y], src[W][Y], src[Y][Z], src[Z][Z], src[W][Z], src[Y][W], src[Z][W], src[W][W] ); + row[Y][X] = -Determinant3 ( src[Y][X], src[Z][X], src[W][X], src[Y][Z], src[Z][Z], src[W][Z], src[Y][W], src[Z][W], src[W][W] ); + row[Z][X] = Determinant3 ( src[Y][X], src[Z][X], src[W][X], src[Y][Y], src[Z][Y], src[W][Y], src[Y][W], src[Z][W], src[W][W] ); + row[W][X] = -Determinant3 ( src[Y][X], src[Z][X], src[W][X], src[Y][Y], src[Z][Y], src[W][Y], src[Y][Z], src[Z][Z], src[W][Z] ); + + row[X][Y] = -Determinant3 ( src[X][Y], src[Z][Y], src[W][Y], src[X][Z], src[Z][Z], src[W][Z], src[X][W], src[Z][W], src[W][W] ); + row[Y][Y] = Determinant3 ( src[X][X], src[Z][X], src[W][X], src[X][Z], src[Z][Z], src[W][Z], src[X][W], src[Z][W], src[W][W] ); + row[Z][Y] = -Determinant3 ( src[X][X], src[Z][X], src[W][X], src[X][Y], src[Z][Y], src[W][Y], src[X][W], src[Z][W], src[W][W] ); + row[W][Y] = Determinant3 ( src[X][X], src[Z][X], src[W][X], src[X][Y], src[Z][Y], src[W][Y], src[X][Z], src[Z][Z], src[W][Z] ); + + row[X][Z] = Determinant3 ( src[X][Y], src[Y][Y], src[W][Y], src[X][Z], src[Y][Z], src[W][Z], src[X][W], src[Y][W], src[W][W] ); + row[Y][Z] = -Determinant3 ( src[X][X], src[Y][X], src[W][X], src[X][Z], src[Y][Z], src[W][Z], src[X][W], src[Y][W], src[W][W] ); + row[Z][Z] = Determinant3 ( src[X][X], src[Y][X], src[W][X], src[X][Y], src[Y][Y], src[W][Y], src[X][W], src[Y][W], src[W][W] ); + row[W][Z] = -Determinant3 ( src[X][X], src[Y][X], src[W][X], src[X][Y], src[Y][Y], src[W][Y], src[X][Z], src[Y][Z], src[W][Z] ); + + row[X][W] = -Determinant3 ( src[X][Y], src[Y][Y], src[Z][Y], src[X][Z], src[Y][Z], src[Z][Z], src[X][W], src[Y][W], src[Z][W] ); + row[Y][W] = Determinant3 ( src[X][X], src[Y][X], src[Z][X], src[X][Z], src[Y][Z], src[Z][Z], src[X][W], src[Y][W], src[Z][W] ); + row[Z][W] = -Determinant3 ( src[X][X], src[Y][X], src[Z][X], src[X][Y], src[Y][Y], src[Z][Y], src[X][W], src[Y][W], src[Z][W] ); + row[W][W] = Determinant3 ( src[X][X], src[Y][X], src[Z][X], src[X][Y], src[Y][Y], src[Z][Y], src[X][Z], src[Y][Z], src[Z][Z] ); + + return *this; + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Invert ( const Matrix& src ) +{ + + + Adjoint (src); + + float det = src.Determinant(); + + if ( det != 1.0f ) + { + *this *= ( 1.0f / det ); + } + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Invert ( void ) +{ + + + Matrix d; + + d.Invert( *this ); + + *this = d; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifdef __USE_VU0__ +// K: Copied from sceVu0InversMatrix, just tweaked to take one param & be inline. +// This is about 18% faster than in C. (1000000 calls take .121 s instead of .148) +inline void Vu0Invert(sceVu0FMATRIX m0) +{ + asm __volatile__(" + lq $8,0x0000(%0) + lq $9,0x0010(%0) + lq $10,0x0020(%0) + lqc2 vf4,0x0030(%0) + + vmove.xyzw vf5,vf4 + vsub.xyz vf4,vf4,vf4 #vf4.xyz=0; + vmove.xyzw vf9,vf4 + qmfc2 $11,vf4 + + #“]’u + pextlw $12,$9,$8 + pextuw $13,$9,$8 + pextlw $14,$11,$10 + pextuw $15,$11,$10 + pcpyld $8,$14,$12 + pcpyud $9,$12,$14 + pcpyld $10,$15,$13 + + qmtc2 $8,vf6 + qmtc2 $9,vf7 + qmtc2 $10,vf8 + + #“àÏ + vmulax.xyz ACC, vf6,vf5 + vmadday.xyz ACC, vf7,vf5 + vmaddz.xyz vf4,vf8,vf5 + vsub.xyz vf4,vf9,vf4 + + sq $8,0x0000(%0) + sq $9,0x0010(%0) + sq $10,0x0020(%0) + sqc2 vf4,0x0030(%0) + ": : "r" (m0) :"$8","$9","$10","$11","$12","$13","$14","$15"); +} +#endif + +inline Matrix& Matrix::InvertUniform () +{ + + + // Only works for orthonormal Matrix + + #if 0 // (Mike) this version is buggy! + + Transpose ( src ); + + row[POS][X] = -DotProduct ( src[POS], src[RIGHT] ); + row[POS][Y] = -DotProduct ( src[POS], src[UP] ); + row[POS][Z] = -DotProduct ( src[POS], src[AT] ); + + return *this; + + #else // (Mike) try this one instead... + + // (Dan) actually, don't use the vu0; it returns before the correct values are safely stored in memory; if you access the results too + // quickly, you get the wrong answers; this is because the compiler may reorder instructions + #if 0 + // (Ken) or try this one, uses vu0 + Vu0Invert((sceVu0FMATRIX)this); + return *this; + #else + + // need to copy the old row, + // or else it will change on us as we're doing + // our dot products... + + Mth::Vector oldPos = *(Vector*)row[POS]; + + row[POS][X] = -DotProduct ( oldPos, *(Vector*)row[RIGHT] ); + row[POS][Y] = -DotProduct ( oldPos, *(Vector*)row[UP] ); + row[POS][Z] = -DotProduct ( oldPos, *(Vector*)row[AT] ); + + Swap ( row[X][Y], row[Y][X] ); + Swap ( row[X][Z], row[Z][X] ); + Swap ( row[Y][Z], row[Z][Y] ); + + return *this; + + #endif + + #endif +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Transpose ( void ) +{ + + + Swap ( row[X][Y], row[Y][X] ); + Swap ( row[X][Z], row[Z][X] ); + Swap ( row[X][W], row[W][X] ); + Swap ( row[Y][Z], row[Z][Y] ); + Swap ( row[Y][W], row[W][Y] ); + Swap ( row[Z][W], row[W][Z] ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Transpose ( const Matrix& src ) +{ + + + if ( &src == this ) + { + return Transpose(); + } + + row[X][X] = src[X][X]; + row[X][Y] = src[Y][X]; + row[X][Z] = src[Z][X]; + row[X][W] = src[W][X]; + row[Y][X] = src[X][Y]; + row[Y][Y] = src[Y][Y]; + row[Y][Z] = src[Z][Y]; + row[Y][W] = src[W][Y]; + row[Z][X] = src[X][Z]; + row[Z][Y] = src[Y][Z]; + row[Z][Z] = src[Z][Z]; + row[Z][W] = src[W][Z]; + row[W][X] = src[X][W]; + row[W][Y] = src[Y][W]; + row[W][Z] = src[Z][W]; + row[W][W] = src[W][W]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Matrix::Transform ( const Vector& src ) const +{ + + + Vector v; + + #ifdef __USE_VU0__ + sceVu0ApplyMatrix((float*)&v,(sceVu0FVECTOR*)this,(float*)&src); + #else + + v[X] = src[X] * row[RIGHT][X] + src[Y] * row[UP][X] + src[Z] * row[AT][X] + src[W] * row[POS][X]; + v[Y] = src[X] * row[RIGHT][Y] + src[Y] * row[UP][Y] + src[Z] * row[AT][Y] + src[W] * row[POS][Y]; + v[Z] = src[X] * row[RIGHT][Z] + src[Y] * row[UP][Z] + src[Z] * row[AT][Z] + src[W] * row[POS][Z]; + v[W] = src[X] * row[RIGHT][W] + src[Y] * row[UP][W] + src[Z] * row[AT][W] + src[W] * row[POS][W]; + + #endif + + return v; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Matrix::TransformAsPos ( const Vector& src ) const +{ + + Vector v; + + v[X] = src[X] * row[RIGHT][X] + src[Y] * row[UP][X] + src[Z] * row[AT][X] + row[POS][X]; + v[Y] = src[X] * row[RIGHT][Y] + src[Y] * row[UP][Y] + src[Z] * row[AT][Y] + row[POS][Y]; + v[Z] = src[X] * row[RIGHT][Z] + src[Y] * row[UP][Z] + src[Z] * row[AT][Z] + row[POS][Z]; + v[W] = src[X] * row[RIGHT][W] + src[Y] * row[UP][W] + src[Z] * row[AT][W] + row[POS][W]; + + return v; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Matrix::Rotate ( const Vector& src ) const +{ + + + Vector v; + + v[X] = src[X] * row[RIGHT][X] + src[Y] * row[UP][X] + src[Z] * row[AT][X]; + v[Y] = src[X] * row[RIGHT][Y] + src[Y] * row[UP][Y] + src[Z] * row[AT][Y]; + v[Z] = src[X] * row[RIGHT][Z] + src[Y] * row[UP][Z] + src[Z] * row[AT][Z]; + v[W] = src[W]; + + return v; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Translate( const Vector& trans ) +{ + + + *(Vector*)(row[POS]) += trans; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +// Ken: Added cos I needed it. +inline void Matrix::SetPos( const Vector& trans ) +{ + *(Vector*)(row[POS]) = trans; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::TranslateLocal( const Vector& trans ) +{ + + + Vector& vec = *(Vector*)(row[POS]); + + vec[X] += (( trans[X] * row[RIGHT][X] ) + + ( trans[Y] * row[UP][X] ) + + ( trans[Z] * row[AT][X] )); + + vec[Y] += (( trans[X] * row[RIGHT][Y] ) + + ( trans[Y] * row[UP][Y] ) + + ( trans[Z] * row[AT][Y] )); + + vec[Z] += (( trans[X] * row[RIGHT][Z] ) + + ( trans[Y] * row[UP][Z] ) + + ( trans[Z] * row[AT][Z] )); + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Rotate( const Vector& axis, const float angle ) +{ + + + Matrix rot_mat( axis, angle ); + + *this *= rot_mat; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateLocal( const Vector& axis, const float angle ) +{ + + + Matrix rot_mat( axis, angle ); + + *this = rot_mat * *this; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateLocal( int axis, const float angle ) +{ + + + Matrix rot_mat( axis, angle ); + + *this = rot_mat * *this; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateX( const float angle ) +{ + + Matrix rotX; + + *this *= CreateRotateXMatrix( rotX, angle ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateXLocal( const float angle ) +{ + + Matrix rotX; + + *this = CreateRotateXMatrix( rotX, angle ) * *this; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateY( const float angle ) +{ + + Matrix rotY; + + *this *= CreateRotateYMatrix( rotY, angle ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateYLocal( const float angle ) +{ + + #ifdef __USE_VU0__ + + // Use the end of SCache for a quick and easy speed increase. + // (Mick, changed to use the end, so start can be used for permanent stuff) + Matrix *p_rot_y=(Matrix*)(0x70004000-4*4*4); + + *this = CreateRotateYMatrix( *p_rot_y, angle ) * *this; + + #else + + Matrix rotY; + *this = CreateRotateYMatrix( rotY, angle ) * *this; + + #endif + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateZ( const float angle ) +{ + + + Matrix rotZ; + + *this *= CreateRotateZMatrix( rotZ, angle ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::RotateZLocal( const float angle ) +{ + + + Matrix rotZ; + + *this = CreateRotateZMatrix( rotZ, angle ) * *this; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::Scale( const Vector& scale ) +{ + + + *(Vector*)(row[RIGHT]) *= scale; + *(Vector*)(row[UP]) *= scale; + *(Vector*)(row[AT]) *= scale; + *(Vector*)(row[POS]) *= scale; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::ScaleLocal( const Vector& scale ) +{ + + + *(Vector*)(row[RIGHT]) *= scale[X]; + *(Vector*)(row[UP]) *= scale[Y]; + *(Vector*)(row[AT]) *= scale[Z]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::SetColumn ( sint i, const Vector& v ) +{ + + +// Dbg_MsgAssert (( i >= X && i < NUM_ELEMENTS ),( "index out of range" )); + + row[X][i] = v[X]; + row[Y][i] = v[Y]; + row[Z][i] = v[Z]; + row[W][i] = v[W]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Matrix::GetColumn ( sint i ) const +{ + + +// Dbg_MsgAssert (( i >= X && i < NUM_ELEMENTS ),( "index out of range" )); + + return Vector ( row[X][i], row[Y][i], row[Z][i], row[W][i] ); +} + + + + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline ostream& operator<< ( ostream& os, const Matrix& m ) +{ + return os << "{ " << m[X] << ",\n " << m[Y] << ",\n " << m[Z] << ",\n " << m[W] << " }"; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Matrix& Matrix::SetFromAngles ( const Vector& angles ) +{ + Identity(); + if (angles[X] != 0.0f || angles[Y] != 0.0f || angles[Z] != 0.0f) + { + RotateX(angles[X]); + RotateY(angles[Y]); + RotateZ(angles[Z]); + } + return *this; +} + +} // namespace Mth + +#endif // __CORE_MATH_MATRIX_INL + diff --git a/Code/Core/Math/quat.h b/Code/Core/Math/quat.h new file mode 100644 index 0000000..4464fbd --- /dev/null +++ b/Code/Core/Math/quat.h @@ -0,0 +1,143 @@ +/***************************************************************************** +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/Quat.h ** +** ** +** Created: 11/23/99 - mjb ** +** ** +** Description: Quaternion Math Class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_QUAT_H +#define __CORE_MATH_QUAT_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Mth +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +class Matrix; // forward declaration + +/**************************************************************************** + * + * Class: Quat + * + * Description: Quaternion math class + * + ****************************************************************************/ + +class Quat //: public Spt::Class +{ + + +public: + + friend bool quat_equal ( const Quat& q1, const Quat& q2, const float tol ); + + Quat ( float cx = 0.0f, float cy = 0.0f, float cz = 0.0f, float cw = 1.0f ); + + Quat ( const Vector& axis, float angle ); + Quat ( const Matrix& mat ); + + Quat& Invert ( void ); + Quat& Invert ( const Quat& src ); + float Modulus ( void ); + float ModulusSqr ( void ) const; + Quat& Normalize ( float len = 1.0f ); + Vector Rotate ( const Vector& vec ) const; + + void SetScalar ( const float w ); + void SetVector ( const float x, const float y, const float z ); + void SetVector ( const Vector& v ); + const float& GetScalar ( void ) const; + const Vector& GetVector ( void ) const { return quat; } + + void GetMatrix ( Matrix& mat ) const; + + Quat& operator= ( const Quat& q ); + Quat& operator+= ( const Quat& q ); + Quat& operator-= ( const Quat& q ); + Quat& operator*= ( const Quat& q ); + Quat& operator*= ( float s ); + Quat& operator/= ( float s ); + const float& operator[] ( sint i ) const; + float& operator[] ( sint i ); + + +private: + + Vector quat; // X,Y,Z : Imaginary (Vector) component + // W : Real (Scalar) component +}; + + +/***************************************************************************** +** Private Inline Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +Quat operator+ ( const Quat& q1, const Quat& q2 ); +Quat operator- ( const Quat& q1, const Quat& q2 ); +Quat operator* ( const Quat& q1, const Quat& q2 ); +Quat operator* ( const Quat& q, const float s ); +Quat operator* ( const float s, const Quat& q ); +Quat operator- ( const Quat& q ); // negate all elements -> equilavent rotation +float DotProduct ( const Quat& v1, const Quat& v2 ); +Quat Slerp ( const Quat& q1, const Quat& q2, const float t ); +Quat FastSlerp( Quat& qIn1, Quat& qIn2, const float t ); +bool Equal ( const Quat& q1, const Quat& q2, const float tol = 0.0001f ); +ostream& operator<< ( ostream& os, const Quat& v ); +Quat EulerToQuat( const Vector& v ); + +// converts a quat+vector xform into a matrix xform +void QuatVecToMatrix( Mth::Quat* pQ, Mth::Vector* pT, Mth::Matrix* pMatrix ); +void SCacheQuatVecToMatrix( Mth::Quat* pQ, Mth::Vector* pT, Mth::Matrix* pMatrix ); + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_QUAT_H + + diff --git a/Code/Core/Math/quat.inl b/Code/Core/Math/quat.inl new file mode 100644 index 0000000..7cd454b --- /dev/null +++ b/Code/Core/Math/quat.inl @@ -0,0 +1,815 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (Mth) ** +** ** +** File name: core/math/Quat.inl ** +** ** +** Created: 11/23/99 - mjb ** +** ** +** Description: Quaternion Math Class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_QUAT_INL +#define __CORE_MATH_QUAT_INL + +namespace Mth +{ + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline const float& Quat::operator[] ( sint i ) const +{ + + + return quat[i]; +} + + +/***************************************************************************** +** Private Inline Functions ** +*****************************************************************************/ + +inline bool quat_equal( const Quat& q1, const Quat& q2, const float tol ) +{ + + + return ( Equal( q1.quat, q2.quat, tol )); +} + +/***************************************************************************** +** Public Inline Functions ** +*****************************************************************************/ + +inline Quat::Quat( float cx, float cy, float cz, float cw ) +: quat( cx, cy, cz, cw ) +{ + + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat::Quat( const Vector& axis, float angle ) +{ + + + float mod = axis.Length(); + float ang = angle / 2.0f; + + mod = ( mod > 0.0f ) ? ( 1.0f / mod ) : 0.0f; + mod *= sinf( ang ); + + SetVector( mod * axis[X], mod * axis[Y], mod * axis[Z] ); + SetScalar( cosf ( ang )); +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float& Quat::operator[] ( sint i ) +{ + + + return quat[i]; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat::Quat( const Matrix& m ) +{ + + + float t = m[X][X] + m[Y][Y] + m[Z][Z]; + float trace; + + if ( t > 0.0f ) + { + trace = sqrtf( t + 1.0f ); + + SetScalar( trace * 0.5f ); + trace = 0.5f / trace; + + SetVector(( m[Z][Y] - m[Y][Z] ) * trace, + ( m[X][Z] - m[Z][X] ) * trace, + ( m[Y][X] - m[X][Y] ) * trace ); + } + else + { + // find greatest element in Matrix diagonal + sint i = X; + if ( m[Y][Y] > m[X][X] ) i = Y; + if ( m[Z][Z] > m[i][i] ) i = Z; + + sint j = ( i + 1 ) % W; + sint k = ( j + 1 ) % W; + + trace = sqrtf(( m[i][i] - (m[j][j] + m[k][k] )) + 1.0f ); + + quat[i] = ( trace * 0.5f ); + trace = 0.5f / trace; + quat[j] = ( m[j][i] + m[i][j] ) * trace; + quat[k] = ( m[k][i] + m[i][k] ) * trace; + quat[W] = ( m[k][j] - m[j][k] ) * trace; + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::Invert( void ) // this = Invert ( this ) +{ + + + quat.Negate(); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::Invert( const Quat& src ) // this = Invert ( src ) +{ + + + quat.Negate( src.quat ); + quat[W] = src.quat[W]; + + return *this; +} + + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Quat::Modulus( void ) +{ + + + return sqrtf( ModulusSqr () ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Quat::ModulusSqr ( void ) const +{ + + + return (( quat[X] * quat[X] ) + + ( quat[Y] * quat[Y] ) + + ( quat[Z] * quat[Z] ) + + ( quat[W] * quat[W] )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::Normalize( float len ) +{ + + + float mod = Modulus(); + + if ( mod > 0.0f ) + { + mod = len / mod; + + quat *= mod; + } + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Quat::Rotate( const Vector& vec ) const +{ + + + Quat inv; +// Quat pt( vec[X], vec[Y], vec[Z], vec[W] ); + Quat pt( vec[X], vec[Y], vec[Z], 1.0f ); // Mick: Setting W to sensible value, otherwise can cause overflow + Quat res = *this; + + inv.Invert( *this ); + res *= pt; + res *= inv; + + return Vector( res[X], res[Y], res[Z], res[W] ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Quat::SetScalar( const float w ) +{ + + + quat[W] = w; +}; + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Quat::SetVector( const float x, const float y, const float z ) +{ + + + quat[X] = x; + quat[Y] = y; + quat[Z] = z; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Quat::SetVector( const Vector& v ) +{ + + + quat[X] = v[X]; + quat[Y] = v[Y]; + quat[Z] = v[Z]; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::operator= ( const Quat& q ) +{ + + + quat = q.quat; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::operator+= ( const Quat& q ) +{ + + + quat += q.quat; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::operator-= ( const Quat& q ) +{ + + + quat -= q.quat; + + return *this; +} + + +inline const float& Quat::GetScalar ( void ) const +{ + return quat[W]; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::operator*= ( const Quat& q ) +{ + + + float s1 = GetScalar(); + float s2 = q.GetScalar(); + + Vector v1 = GetVector(); + Vector v2 = q.GetVector(); + + SetVector (( v2 * s1 ) + ( v1 * s2 ) + CrossProduct ( v1, v2 )); + SetScalar (( s1 * s2 ) - DotProduct ( v1, v2 )); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::operator*= ( float s ) +{ + + + quat *= s; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat& Quat::operator/= ( float s ) +{ + + + quat /= s; + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Quat::GetMatrix ( Matrix& mat ) const +{ + + + float xs, ys, zs, + wx, wy, wz, + xx, xy, xz, + yy, yz, zz, ss; + + +#if 0 + // our original version. Seems essentially broken, as LengthSqr ignores W + ss = 2.0f / quat.LengthSqr(); +#else + // version suggested by Andre at Left Field + // uses proper Modulus, and clamps 2.0/0.0f to zero + ss = ModulusSqr(); + ss = ( ss>0.0f ? 2.0f/ss : 0.0f); +#endif + + xs = quat[X] * ss; + ys = quat[Y] * ss; + zs = quat[Z] * ss; + + wx = quat[W] * xs; + wy = quat[W] * ys; + wz = quat[W] * zs; + + xx = quat[X] * xs; + xy = quat[X] * ys; + xz = quat[X] * zs; + + yy = quat[Y] * ys; + yz = quat[Y] * zs; + + zz = quat[Z] * zs; + + mat[X][X] = 1.0f - (yy + zz); + mat[Y][X] = xy + wz; + mat[Z][X] = xz - wy; + + mat[X][Y] = xy - wz; + mat[Y][Y] = 1.0f - (xx + zz); + mat[Z][Y] = yz + wx; + + mat[X][Z] = xz + wy; + mat[Y][Z] = yz - wx; + mat[Z][Z] = 1.0f - (xx + yy ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat operator+ ( const Quat& q1, const Quat& q2 ) +{ + + + Quat sum = q1; + + sum += q2; + + return sum; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat operator- ( const Quat& q1, const Quat& q2 ) +{ + + + Quat diff = q1; + + diff -= q2; + + return diff; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat operator* ( const Quat& q1, const Quat& q2 ) +{ + + + Quat prod = q1; + + prod *= q2; + + return prod; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat operator* ( const Quat& q, const float s ) +{ + Quat prod = q; + + prod *= s; + + return prod; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat operator* ( const float s, const Quat& q ) +{ + return q * s; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat operator- ( const Quat& q ) +{ + + + return Quat ( -q[X], -q[Y], -q[Z], -q[W] ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float DotProduct ( const Quat& v1, const Quat& v2 ) +{ + + + return ( v1[X] * v2[X] ) + ( v1[Y] * v2[Y] ) + ( v1[Z] * v2[Z] ) + ( v1[W] * v2[W] ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Quat Slerp ( const Quat& q1, const Quat& q3, float t ) +{ + float sclp, sclq; + + // GJ: Need to possibly negate the second quaternion, + // to avoid spinning in the wrong direction. + Quat q2 = q3; + if ( DotProduct( q1, q3 ) < 0.0f ) + { + q2 = -q2; + } + + float coso = q1.GetScalar() * q2.GetScalar() + + DotProduct ( q1.GetVector(), q2.GetVector()); + + Quat q; + + if (( 1.0f + coso + 1.0f ) > EPSILON ) + { + if (( 1.0f - coso ) > EPSILON ) // slerp + { + float omega = acosf ( coso ); + float sino = sinf ( omega ); + + sclp = sinf (( 1.0f - t ) * omega ) / sino; + sclq = sinf ( t * omega ) / sino; + } + else // lerp ( angle tends to 0 ) + { + sclp = 1.0f - t; + sclq = t; + } + + q = ( sclp * q1 ) + ( sclq * q2 ); + } + else // angle tends to 2*PI + { + q.SetVector ( -q1[Y], q1[X], -q1[W] ); + q.SetScalar ( q1[Z] ); + + sclp = sinf (( 1.0f - t ) * PI / 2.0f ); + sclq = sinf ( t * PI / 2.0f ); + + q.SetVector (( sclp * q1.GetVector()) + ( sclq * q.GetVector())); + } + + return q; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Equal ( const Quat& q1, const Quat& q2, const float tol ) +{ + + + if (( quat_equal ( q1, q2, tol )) || + ( quat_equal ( -q1, q2, tol ))) + { + return true; + } + + return false; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline ostream& operator<< ( ostream& os, const Quat& q ) +{ + return os << "(( " << q[X] << ", " << q[Y] << ", " << q[Z] << ") , " << q[W] << " )"; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void QuatVecToMatrix( Mth::Quat* pQ, Mth::Vector* pT, Mth::Matrix* pMatrix ) +{ + Dbg_Assert( pQ ); + Dbg_Assert( pT ); + Dbg_Assert( pMatrix ); + + pQ->Invert(); + + Mth::Vector square; + Mth::Vector cross; + Mth::Vector wimag; + + square[X] = (*pQ)[X] * (*pQ)[X]; + square[Y] = (*pQ)[Y] * (*pQ)[Y]; + square[Z] = (*pQ)[Z] * (*pQ)[Z]; + + cross[X] = (*pQ)[Y] * (*pQ)[Z]; + cross[Y] = (*pQ)[Z] * (*pQ)[X]; + cross[Z] = (*pQ)[X] * (*pQ)[Y]; + + wimag[X] = (*pQ)[W] * (*pQ)[X]; + wimag[Y] = (*pQ)[W] * (*pQ)[Y]; + wimag[Z] = (*pQ)[W] * (*pQ)[Z]; + + (*pMatrix)[Mth::RIGHT][X] = 1 - 2 * (square[Y] + square[Z]); + (*pMatrix)[Mth::RIGHT][Y] = 2 * (cross[Z] + wimag[Z]); + (*pMatrix)[Mth::RIGHT][Z] = 2 * (cross[Y] - wimag[Y]); + (*pMatrix)[Mth::RIGHT][W] = 0.0f; + + (*pMatrix)[Mth::UP][X] = 2 * (cross[Z] - wimag[Z]); + (*pMatrix)[Mth::UP][Y] = 1 - 2 * (square[X] + square[Z]); + (*pMatrix)[Mth::UP][Z] = 2 * (cross[X] + wimag[X]); + (*pMatrix)[Mth::UP][W] = 0.0f; + + (*pMatrix)[Mth::AT][X] = 2 * (cross[Y] + wimag[Y]); + (*pMatrix)[Mth::AT][Y] = 2 * (cross[X] - wimag[X]); + (*pMatrix)[Mth::AT][Z] = 1 - 2 * (square[X] + square[Y]); + (*pMatrix)[Mth::AT][W] = 0.0f; + + (*pMatrix)[Mth::POS] = *pT; + (*pMatrix)[Mth::POS][W] = 1.0f; +} + +inline void SCacheQuatVecToMatrix( Mth::Quat* pQ, Mth::Vector* pT, Mth::Matrix* pMatrix ) +{ + Dbg_Assert( pQ ); + Dbg_Assert( pT ); + Dbg_Assert( pMatrix ); + + pQ->Invert(); + + Mth::Vector *p_square = ((Mth::Vector*)0x7000000)+0; + Mth::Vector *p_cross = ((Mth::Vector*)0x7000000)+1; + Mth::Vector *p_wimag = ((Mth::Vector*)0x7000000)+2; + + (*p_square)[X] = (*pQ)[X] * (*pQ)[X]; + (*p_square)[Y] = (*pQ)[Y] * (*pQ)[Y]; + (*p_square)[Z] = (*pQ)[Z] * (*pQ)[Z]; + + (*p_cross)[X] = (*pQ)[Y] * (*pQ)[Z]; + (*p_cross)[Y] = (*pQ)[Z] * (*pQ)[X]; + (*p_cross)[Z] = (*pQ)[X] * (*pQ)[Y]; + + (*p_wimag)[X] = (*pQ)[W] * (*pQ)[X]; + (*p_wimag)[Y] = (*pQ)[W] * (*pQ)[Y]; + (*p_wimag)[Z] = (*pQ)[W] * (*pQ)[Z]; + + (*pMatrix)[Mth::RIGHT][X] = 1 - 2 * ((*p_square)[Y] + (*p_square)[Z]); + (*pMatrix)[Mth::RIGHT][Y] = 2 * ((*p_cross)[Z] + (*p_wimag)[Z]); + (*pMatrix)[Mth::RIGHT][Z] = 2 * ((*p_cross)[Y] - (*p_wimag)[Y]); + (*pMatrix)[Mth::RIGHT][W] = 0.0f; + + (*pMatrix)[Mth::UP][X] = 2 * ((*p_cross)[Z] - (*p_wimag)[Z]); + (*pMatrix)[Mth::UP][Y] = 1 - 2 * ((*p_square)[X] + (*p_square)[Z]); + (*pMatrix)[Mth::UP][Z] = 2 * ((*p_cross)[X] + (*p_wimag)[X]); + (*pMatrix)[Mth::UP][W] = 0.0f; + + (*pMatrix)[Mth::AT][X] = 2 * ((*p_cross)[Y] + (*p_wimag)[Y]); + (*pMatrix)[Mth::AT][Y] = 2 * ((*p_cross)[X] - (*p_wimag)[X]); + (*pMatrix)[Mth::AT][Z] = 1 - 2 * ((*p_square)[X] + (*p_square)[Y]); + (*pMatrix)[Mth::AT][W] = 0.0f; + + (*pMatrix)[Mth::POS] = *pT; + (*pMatrix)[Mth::POS][W] = 1.0f; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Slerp ( Matrix& result, const Matrix& s1, const Matrix& s2, float t ) +{ + Slerp ( Quat(s1), Quat(s2), t).GetMatrix ( result ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float counter_warp(float t, float cos_alpha) +{ + const float ATTENUATION = 0.82279687f; + const float WORST_CASE_SLOPE = 0.58549219f; + + float factor = 1.0f - ATTENUATION * cos_alpha; + factor *= factor; + float k = WORST_CASE_SLOPE * factor; + + return t*(k*t*(2.0f*t - 3.0f) + 1.0f + k); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Mth::Quat FastSlerp( Mth::Quat& qIn1, Mth::Quat& qIn2, const float t ) +{ + if ( Mth::DotProduct( qIn1, qIn2 ) < 0.0f ) + { + qIn2 = -qIn2; + } + +# if 0 + float cos_a = v1[X]*v2[X] + v1[Y]*v2[Y] + v1[Z]*v2[Z] + v1[W]*v2[W]; + if (t <= 0.5f) + { + t = counter_warp(t, cos_a); + } + else + { + t = 1.0f - counter_warp(1.0f - t, cos_a); + } +# endif + + float xxx_x = qIn1[X] + ( qIn2[X] - qIn1[X] ) * t; + float xxx_y = qIn1[Y] + ( qIn2[Y] - qIn1[Y] ) * t; + float xxx_z = qIn1[Z] + ( qIn2[Z] - qIn1[Z] ) * t; + float xxx_w = qIn1[W] + ( qIn2[W] - qIn1[W] ) * t; + + float len = 1.0f / sqrtf( xxx_x * xxx_x + xxx_y * xxx_y + xxx_z * xxx_z + xxx_w * xxx_w ); + + qIn2.SetVector( xxx_x * len, xxx_y * len, xxx_z * len ); + qIn2.SetScalar( xxx_w * len ); + + return qIn2; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Mth::Quat EulerToQuat( const Mth::Vector& euler ) +{ + // This code is a modified version of Left Field's + // MakeQuatFromEuler() function. + + Mth::Quat q; + + float roll = euler[X]; + float pitch = euler[Y]; + float yaw = euler[Z]; + + float cyaw, cpitch, croll, syaw, spitch, sroll; + float cyawcpitch, syawspitch, cyawspitch, syawcpitch; + + cyaw = cosf(0.5f * yaw); + cpitch = cosf(0.5f * pitch); + croll = cosf(0.5f * roll); + + syaw = sinf(0.5f * yaw); + spitch = sinf(0.5f * pitch); + sroll = sinf(0.5f * roll); + + cyawcpitch = cyaw * cpitch; + syawspitch = syaw * spitch; + cyawspitch = cyaw * spitch; + syawcpitch = syaw * cpitch; + + q[W] = cyawcpitch * croll + syawspitch * sroll; + q[X] = cyawcpitch * sroll - syawspitch * croll; + q[Y] = cyawspitch * croll + syawcpitch * sroll; + q[Z] = syawcpitch * croll - cyawspitch * sroll; + + return q; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_QUAT_INL + diff --git a/Code/Core/Math/rect.h b/Code/Core/Math/rect.h new file mode 100644 index 0000000..0db8d49 --- /dev/null +++ b/Code/Core/Math/rect.h @@ -0,0 +1,235 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/Rect.h ** +** ** +** Created: 01/31/00 - mjb ** +** ** +** Description: Math Library Rectangle class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_RECT_H +#define __CORE_MATH_RECT_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Mth +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +nTemplateBaseClass ( _T, _Rect ) +{ + + +public: + _Rect ( const _T x, const _T y, const _T w, const _T h ); + ~_Rect ( ); + + void SetOriginX ( const _T ) ; + void SetOriginY ( const _T ) ; + void SetWidth ( const _T ) ; + void SetHeight ( const _T ) ; + + const _T GetOriginX ( void ) const; + const _T GetOriginY ( void ) const; + const _T GetWidth ( void ) const; + const _T GetHeight ( void ) const; + +private: + + _T x, y, w, h; + +}; + +/***************************************************************************** +** Type Definitions ** +*****************************************************************************/ + +typedef _Rect < float > Rect; +typedef _Rect < sint > IRect; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +template < class _T > inline +_Rect< _T >::_Rect ( const _T _x, const _T _y, const _T _w, const _T _h ) +: x (_x), y (_y), w(_w), h(_h) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +_Rect< _T >::~_Rect ( void ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void _Rect< _T >::SetOriginX ( const _T xval ) +{ + + + x = xval; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void _Rect< _T >::SetOriginY ( const _T yval ) +{ + + + y = yval; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void _Rect< _T >::SetWidth ( const _T width ) +{ + + + w = width; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void _Rect< _T >::SetHeight ( const _T height ) +{ + + + h = height; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +const _T _Rect< _T >::GetOriginX ( void ) const +{ + + + return x; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +const _T _Rect< _T >::GetOriginY ( void ) const +{ + + + return y; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +const _T _Rect< _T >::GetWidth ( void ) const +{ + + + return w; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +const _T _Rect< _T >::GetHeight ( void ) const +{ + + + return h; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +ostream& operator<< ( ostream& str, const _Rect< _T >& r ) +{ + str << "(( " << r.GetOriginX() << ", " << r.GetOriginY() << " ),( " + << r.GetWidth() << ", " << r.GetHeight() << " ))"; + return str; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_RECT_H diff --git a/Code/Core/Math/rot90.cpp b/Code/Core/Math/rot90.cpp new file mode 100644 index 0000000..d6da056 --- /dev/null +++ b/Code/Core/Math/rot90.cpp @@ -0,0 +1,105 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/rot90.cpp ** +** ** +** Created: 07/17/02 - grj ** +** ** +** Description: Rotation 90 Math Class ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +namespace Mth +{ + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** DBG Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void RotateY90( ERot90 angle, int32& x, int32& y, int32& z ) +{ + int32 temp; + + switch (angle) + { + case ROT_0: + break; + + case ROT_90: + temp = x; + x = z; + z = -temp; + break; + + case ROT_180: + x = -x; + z = -z; + break; + + case ROT_270: + temp = x; + x = -z; + z = temp; + break; + + default: + Dbg_MsgAssert(0, ("RotateY90() out of range: %d", angle)); + break; + } +} + +} // namespace Mth + diff --git a/Code/Core/Math/rot90.h b/Code/Core/Math/rot90.h new file mode 100644 index 0000000..17aee7e --- /dev/null +++ b/Code/Core/Math/rot90.h @@ -0,0 +1,26 @@ +/////////////////////////////////////////////////////////////////////////////////////// +// rot90.h +// + +#ifndef __CORE_ROT90_H +#define __CORE_ROT90_H + +namespace Mth +{ + +// Rotation values for 90 degree increment Rotation (uses no multiplication) +enum ERot90 { + ROT_0 = 0, + ROT_90, + ROT_180, + ROT_270, + NUM_ROTS +}; + +// Integer rotate +void RotateY90( ERot90 angle, int32& x, int32& y, int32& z ); + +} + +#endif + diff --git a/Code/Core/Math/slerp.cpp b/Code/Core/Math/slerp.cpp new file mode 100644 index 0000000..480969b --- /dev/null +++ b/Code/Core/Math/slerp.cpp @@ -0,0 +1,274 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/slerp.cpp ** +** ** +** Created: 12/20/01 - gj ** +** ** +** Description: Slerp Interpolator Math Class ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +namespace Mth +{ + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +const float USE_LERP_INSTEAD_DEGREES = 2.0f; +const float USE_LERP_INSTEAD_RADIANS = USE_LERP_INSTEAD_DEGREES * PI / 180.0f; + +/***************************************************************************** +** DBG Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +SlerpInterpolator::SlerpInterpolator() +{ +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +SlerpInterpolator::SlerpInterpolator( const Matrix * start, const Matrix * end ) +{ + setMatrices( start, end ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void SlerpInterpolator::setMatrices( const Matrix * start, const Matrix * end ) +{ + Matrix inv; + + m_start = *start; + m_end = *end; + + // Calculate the inverse transformation. + inv = m_start; + inv.Invert(); + inv = inv * m_end; + + // Get the axis and angle. + inv.GetRotationAxisAndAngle( &m_axis, &m_radians ); + +#if 0 + // Debugging stuff + static int print_x_times = 50; + + if ( print_x_times > 0 ) + { + print_x_times--; + printf("Start mat\n"); + m_start.PrintContents(); + printf("Inv mat\n"); + inv.PrintContents(); + printf("End mat\n"); + m_end.PrintContents(); + printf("Start * Inv mat\n"); + Mth::Matrix siMat = m_start; + siMat = siMat * inv; + siMat.PrintContents(); + { + printf("Rotated mat w=0\n"); + Mth::Matrix tempMatrix = m_start; + m_axis[W] = 0.0f; + tempMatrix.Rotate( m_axis, m_radians ); + tempMatrix.PrintContents(); + } + { + printf("Rotated mat w=1\n"); + Mth::Matrix tempMatrix2 = m_start; + m_axis[W] = 1.0f; + tempMatrix2.Rotate( m_axis, m_radians ); + tempMatrix2.PrintContents(); + } + { + printf("Rotated local mat w=0\n"); + Mth::Matrix tempMatrix = m_start; + m_axis[W] = 0.0f; + tempMatrix.RotateLocal( m_axis, m_radians ); + tempMatrix.PrintContents(); + } + { + printf("Rotated local mat w=1\n"); + Mth::Matrix tempMatrix2 = m_start; + m_axis[W] = 1.0f; + tempMatrix2.RotateLocal( m_axis, m_radians ); + tempMatrix2.PrintContents(); + } + printf( "Slerp axis=(%f %f %f %f) angle=%f\n", m_axis[X], m_axis[Y], m_axis[Z], m_axis[W], m_radians ); + } + else + { + Dbg_Assert( 0 ); + } +#endif + + // If angle is too small, use lerp. + m_useLerp = m_radians < USE_LERP_INSTEAD_RADIANS; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void SlerpInterpolator::getMatrix( Matrix * result, float delta ) +{ + /* Cap and floor the delta */ + /* If we are at one end the solution is easy */ + if (delta <= 0.0f) + { + // delta = 0.0f; + *result = m_start; + return; + } + else if (delta >= 1.0f) + { + // delta = 1.0f; + *result = m_end; + return; + } + +#if 0 + // GJ: always lerp, used while slerp was being debugged +// m_useLerp = true; +#endif + + /* Do the lerp if we are, else... */ + if ( m_useLerp ) + { + /* Get the lerp matrix */ + Matrix lerp; + Vector lpos; + Vector spos; + Vector epos; + Vector rpos; + + lerp.Ident(); + + spos = m_start[Mth::POS]; + epos = m_end[Mth::POS]; + + lerp[Mth::RIGHT] = m_end[Mth::RIGHT] - m_start[Mth::RIGHT]; + lerp[Mth::UP] = m_end[Mth::UP] - m_start[Mth::UP]; + lerp[Mth::AT] = m_end[Mth::AT] - m_start[Mth::AT]; + lpos = epos - spos; + + /* Do lerp */ + lerp[Mth::RIGHT].Scale( delta ); + lerp[Mth::UP].Scale( delta ); + lerp[Mth::AT].Scale( delta ); + lpos.Scale( delta ); + + (*result)[Mth::RIGHT] = m_start[Mth::RIGHT] + lerp[Mth::RIGHT]; + (*result)[Mth::UP] = m_start[Mth::UP] + lerp[Mth::UP]; + (*result)[Mth::AT] = m_start[Mth::AT] + lerp[Mth::AT]; + rpos = spos + lpos; + + (*result)[Mth::RIGHT].Normalize(); + (*result)[Mth::UP].Normalize(); + (*result)[Mth::AT].Normalize(); + + (*result)[Mth::POS] = rpos; + } + else + { + Vector rpos; + Vector spos; + Vector epos; + Vector lpos; + + spos = m_start[Mth::POS]; + epos = m_end[Mth::POS]; + + /* Remove the translation for now */ + *result = m_start; + (*result)[Mth::POS] = Mth::Vector( 0.0f, 0.0f, 0.0f ); + + /* Rotate the new matrix */ +// m_axis[W] = 0.0f; + result->Rotate( m_axis, m_radians * delta ); + + /* Do linear interpolation on position */ + lpos = epos - spos; + lpos.Scale( delta ); + rpos = spos + lpos; + + (*result)[Mth::POS] = rpos; + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void SlerpInterpolator::invertDirection ( ) +{ + m_axis = -m_axis; + m_radians = (2.0f * Mth::PI) - m_radians; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Obj + diff --git a/Code/Core/Math/slerp.h b/Code/Core/Math/slerp.h new file mode 100644 index 0000000..60b4cb6 --- /dev/null +++ b/Code/Core/Math/slerp.h @@ -0,0 +1,102 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math/slerp.h ** +** ** +** Created: 12/20/01 - gj ** +** ** +** Description: Slerp Interpolator Math Class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_SLERP_H +#define __CORE_MATH_SLERP_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#ifndef __CORE_DEFINES_H +#include +#endif + +#include +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Mth +{ + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/**************************************************************************** + * + * Class: Slerp Interpolator + * + * Description: Slerp Interpolator math class + * + ****************************************************************************/ + +class SlerpInterpolator +{ +public: + SlerpInterpolator(); + SlerpInterpolator( const Matrix* pStart, const Matrix* pEnd ); + +public: + void setMatrices( const Matrix* pStart, const Matrix* pEnd ); + void getMatrix( Matrix* result, float delta ); + + void invertDirection ( ); + + float getRadians ( ) { return m_radians; } + const Mth::Vector& getAxis ( ) { return m_axis; } + +protected: + Matrix m_start; + Matrix m_end; + Vector m_axis; + float m_radians; + bool m_useLerp; // do linear-interpolation, rather than slerping +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_SLERP_H diff --git a/Code/Core/Math/vector.cpp b/Code/Core/Math/vector.cpp new file mode 100644 index 0000000..b664b57 --- /dev/null +++ b/Code/Core/Math/vector.cpp @@ -0,0 +1,432 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Vector (MTH) ** +** ** +** File name: vector.cpp ** +** ** +** Created by: 11/24/99 - Mick ** +** ** +** Description: Math Library code ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +#include +#include +//#include +#include +#include +#include +#include + +#include +#include + + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Mth +{ + + + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +// + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +Vector& Vector::RotateY90( ERot90 angle ) +{ + float temp; + + switch (angle) + { + case ROT_0: + break; + + case ROT_90: + temp = col[X]; + col[X] = col[Z]; + col[Z] = -temp; + break; + + case ROT_180: + col[X] = -col[X]; + col[Z] = -col[Z]; + break; + + case ROT_270: + temp = col[X]; + col[X] = -col[Z]; + col[Z] = temp; + break; + + default: + Dbg_MsgAssert(0, ("Vector::RotateY90() out of range: %d", angle)); + break; + } + + return *this; +} + +/***************************************************************************** +// Project the vector onto the plane define by a normal +*****************************************************************************/ + +Vector& Vector::ProjectToPlane(const Vector& normal) +{ + + + float perp_component = Mth::DotProduct(*this,normal); + + *this -= normal * perp_component; + + return *this; + +} + +Vector& Vector::RotateToPlane(const Vector& normal) +{ + // get the length of the vector + float length = Length(); + + // Project the vector onto the plane + ProjectToPlane(normal); + + // now we've projected it ontot he plane + // we need to handle the case where it dissapears into a point + // which would indicate that we were perpendicular to the plane + // so need to get an arbitary vector in the plane + float projected_length = Length(); + if (projected_length == 0.0f) // is this a valid comparision? + { + // Rotate vector through -90 degrees about Y then +90 about X + col[X] = -normal[Z]; + col[Y] = normal[X]; + col[Z] = -normal[Y]; + } + + // get unit vector in this direction + Normalize(); + + // multiply by original speed to rotate velocity onto plane + *this *= length; + + col[W] = 0.0f; // clean up W, otherwise multilications will accumelate over time... + + return *this; +} + +Vector& Vector::RotateToNormal(const Vector& normal) +{ + + *this = normal*Length(); + + return *this; +} + +Vector& Vector::ProjectToNormal(const Vector& normal) +{ + + float dot = Mth::DotProduct(*this,normal); + *this = normal * dot; + + return *this; +} + +/* Finds the largest contributor to the length of the vector... + If you pass in whichAxis, it will fill it in with which axis + is the max... +*/ +float Vector::GetMaxAxis( int *whichAxis ) +{ + int which_axis; + if ( !whichAxis ) + { + whichAxis = &which_axis; + } + + *whichAxis = X; + + if ( Abs( this->col[ Y ] ) > Abs( this->col[ X ] ) ) + { + if ( Abs( this->col[ Z ] ) > Abs( this->col[ Y ] ) ) + *whichAxis = Z; + else + *whichAxis = Y; + } + else if ( Abs( this->col[ Z ] ) > Abs( this->col[ X ] ) ) + { + *whichAxis = Z; + } + return ( this->col[ *whichAxis ] ); +} + +void Vector::DegreesToRadians( void ) +{ + this->col[ X ] = DEGREES_TO_RADIANS( this->col[ X ] ); + this->col[ Y ] = DEGREES_TO_RADIANS( this->col[ Y ] ); + this->col[ Z ] = DEGREES_TO_RADIANS( this->col[ Z ] ); +} + +void Vector::RadiansToDegrees( void ) +{ + this->col[ X ] = RADIANS_TO_DEGREES( this->col[ X ] ); + this->col[ Y ] = RADIANS_TO_DEGREES( this->col[ Y ] ); + this->col[ Z ] = RADIANS_TO_DEGREES( this->col[ Z ] ); +} + +void Vector::FeetToInches( void ) +{ + this->col[ X ] = FEET_TO_INCHES( this->col[ X ] ); + this->col[ Y ] = FEET_TO_INCHES( this->col[ Y ] ); + this->col[ Z ] = FEET_TO_INCHES( this->col[ Z ] ); +} + +// convert to 3d studio max coords ( Z+ up, Y- forward, X to the right... ) +void Vector::ConvertToMaxCoords( void ) +{ + this->col[ X ] = -this->col[ X ]; + float temp = this->col[ Y ]; + this->col[ Y ] = -this->col[ Z ]; + this->col[ Z ] = temp; +} + +// convert to 3d studio max coords ( Z+ up, Y- forward, X to the right... ) +void Vector::ConvertFromMaxCoords( void ) +{ + this->col[ X ] = -this->col[ X ]; + float temp = this->col[ Y ]; + this->col[ Y ] = this->col[ Z ]; + this->col[ Z ] = -temp; +} + +float GetAngle( const Matrix ¤tOrientation, const Vector &desiredHeading, int headingAxis, int rotAxis ) +{ + + + Dbg_MsgAssert( headingAxis == X || headingAxis == Y || headingAxis == Z,( "Improper heading axis." )); + Dbg_MsgAssert( rotAxis == X || rotAxis == Y || rotAxis == Z,( "Improper rot axis." )); + Dbg_MsgAssert( rotAxis != headingAxis,( "Can't use heading axis as rot axis." )); + + Mth::Vector tempHeading = desiredHeading; + tempHeading.ProjectToPlane( currentOrientation[ rotAxis ] ); + if ( !tempHeading.Length( ) ) + { + // If our rot axis is along Y, for example, the + // target heading is right above us... Can't pick + // a rotation! + return ( 0.0f ); + } + tempHeading.Normalize( ); + + // while we have these two vectors, find the angle between them... + float angCos = DotProduct( currentOrientation[ headingAxis ], tempHeading ); + + // Mick: contrain Dot product to range -1.0f to +1.0f, since FP innacuracies might + // make it go outside this range + // (previously this was done only on NGC. Not sure why it has started failing now + // but it seems logical that it would fail occasionally, so this check is necessary) + float ang = angCos; + if ( ang > 1.0f ) ang = 1.0f; + if ( ang < -1.0f ) ang = -1.0f; + float retVal = -acosf( ang ); + + Mth::Vector temp = Mth::CrossProduct( currentOrientation[ headingAxis ], tempHeading ); + + // check to see if the cross product is in the same quatrant as our rot axis... + // if so, gots to rotate the other way and shit like that... + int whichAxis; + float tempMax = temp.GetMaxAxis( &whichAxis ); + if ( ( tempMax > 0 ) == ( currentOrientation[ rotAxis ][ whichAxis ] > 0 ) ) + { + return ( -retVal ); + } + return ( retVal ); +} + +float GetAngle(Vector &v1, Vector &v2) +{ + Vector a=v1; + Vector b=v2; + a.Normalize(); + b.Normalize(); + float dot = Mth::DotProduct(a,b); + if ( dot > 1.0f ) dot = 1.0f; + if ( dot < -1.0f ) dot = -1.0f; + return Mth::RadToDeg(acosf(dot)); +} + +// Returns true if the angle between a and b is greater than the passed Angle. +bool AngleBetweenGreaterThan(const Vector& a, const Vector& b, float Angle) +{ + float dot=Mth::DotProduct(a,b); + float len=a.Length(); + if (len<0.5f) + { + return false; + } + dot/=len; + + len=b.Length(); + if (len<0.5f) + { + return false; + } + dot/=len; + + float TestAngleCosine=cosf(Mth::DegToRad(Angle)); + + if (dot>=0.0f) + { + return dot=0.0f) + { + return true; + } + else + { + return dot + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +enum +{ + X, Y, Z, W +}; + +namespace Mth +{ + + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +class Quat; // forward refs +class Matrix; + +/**************************************************************************** + * + * Class: Vector + * + * Description: Vector math class + * + ****************************************************************************/ + +class Vector +{ + + +public: + // Made special enum so we can have a constructor that does not initialize + enum ENoInitType + { + NO_INIT + }; + + Vector ( ); + Vector ( ENoInitType ); /// A constructor that doesn't init the members. + Vector ( float cx, float cy, float cz ); + Vector ( float cx, float cy, float cz, float cw ); +// Vector ( float cx = 0.0f, float cy = 0.0f, float cz = 0.0f, float cw = 1.0f ); + Vector ( const Vector& v ); + + + Vector& operator= ( const Vector& v ); + bool operator== ( const Vector& v ) const; + bool operator!= ( const Vector& v ) const; + Vector& operator+= ( const Vector& v ); + Vector& operator-= ( const Vector& v ); + Vector& operator*= ( const float s ); + Vector& operator/= ( float s ); + Vector& operator*= ( const Vector& v ); + Vector& operator/= ( const Vector& v ); + Vector& operator*= ( const Matrix& mat ); + const float& operator[] ( sint i ) const; + float& operator[] ( sint i ); + + Vector& Set ( float cx = 0.0f, float cy = 0.0f, float cz = 0.0f, float cw = 1.0f ); + float Length ( void ) const; + float LengthSqr ( void ) const; + Vector& Negate ( void ); // this = -this + Vector& Negate ( const Vector& src ); // this = -src + Vector& Normalize ( ); // faster version normlaizes to 1 + Vector& Normalize ( float len); + + Vector& ShearX ( float shy, float shz ); + Vector& ShearY ( float shx, float shz ); + Vector& ShearZ ( float shx, float shy ); + + Vector& RotateX ( float s, float c ); + Vector& RotateY ( float s, float c ); + Vector& RotateZ ( float s, float c ); + + Vector& RotateX ( float angle ); + Vector& RotateY ( float angle ); + Vector& RotateZ ( float angle ); + + Vector& Rotate ( const Quat& quat ); + Vector& Rotate ( const Matrix& matrix ); + Vector& Rotate ( const Vector& axis, const float angle ); + + // 90 degree increment Rotation (uses no multiplication) + Vector& RotateY90 ( ERot90 angle ); + + Vector& Scale ( float scale ); + Vector& Scale ( const Vector& scale ); + + Vector& RotateToPlane(const Vector& normal); + Vector& RotateToNormal(const Vector& normal); + Vector& ProjectToPlane(const Vector& normal); + Vector& ProjectToNormal(const Vector& normal); + + Vector& ZeroIfShorterThan ( float length ); + + Vector& ClampMin ( float min ); + Vector& ClampMax ( float max ); + Vector& Clamp ( float min = 0.0f, float max = 1.0f ); + Vector& ClampMin ( Vector& min ); + Vector& ClampMax ( Vector& max ); + Vector& Clamp ( Vector& min, Vector& max ); + + float GetX ( void ) const; + float GetY ( void ) const; + float GetZ ( void ) const; + float GetW ( void ) const; + float GetMaxAxis ( int *whichAxis = NULL ); + + void DegreesToRadians( void ); + void RadiansToDegrees( void ); + void FeetToInches( void ); + // convert to 3d studio max coords ( Z+ up, Y- forward, X to the right... ) + void ConvertToMaxCoords( void ); + void ConvertFromMaxCoords( void ); + + // debug info + void PrintContents() const; + +private: + + enum + { + NUM_ELEMENTS = 4 + }; + + float col[NUM_ELEMENTS]; +} nAlign(128); + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +Vector operator+ ( const Vector& v1, const Vector& v2 ); +Vector operator- ( const Vector& v1, const Vector& v2 ); +Vector operator* ( const Vector& v, const float s ); +Vector operator* ( const float s, const Vector& v ); +Vector operator* ( const Vector& v, const Matrix& m ); +Vector operator/ ( const Vector& v, const float s ); +Vector operator- ( const Vector& v ); +float DotProduct ( const Vector& v1, const Vector& v2 ); +Vector CrossProduct ( const Vector& v1, const Vector& v2 ); +Vector Clamp ( const Vector& v, float min = 0.0f, float max = 1.0f ); +Vector Clamp ( const Vector& v, Vector& min, Vector& max ); +float Distance ( const Vector& v1, const Vector& v2 ); +float DistanceSqr ( const Vector& v1, const Vector& v2 ); +float GetAngle ( const Matrix ¤tOrientation, const Vector &desiredHeading, int headingAxis = Z, int rotAxis = Y ); +float GetAngle(Vector &v1, Vector &v2); +bool AngleBetweenGreaterThan(const Vector& a, const Vector& b, float Angle); +float GetAngleAbout(Vector &v1, Vector &v2, Vector &Axis); +bool Equal ( const Vector& v1, const Vector& v2, const float tol = 0.0001f ); +Vector Lerp ( const Vector& v1, const Vector& v2, const float val ); +Vector Min ( const Vector& v1, const Vector& v2 ); +Vector Max ( const Vector& v1, const Vector& v2 ); +Vector Min ( const Vector& v, float c ); +Vector Max ( const Vector& v, float c ); +void Swap ( Vector& a, Vector& b ); +ostream& operator<< ( ostream& os, const Vector& v ); + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_VECTOR_H + diff --git a/Code/Core/Math/vector.inl b/Code/Core/Math/vector.inl new file mode 100644 index 0000000..73a0e8b --- /dev/null +++ b/Code/Core/Math/vector.inl @@ -0,0 +1,1205 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (Mth) ** +** ** +** File name: core/math/Vector.h ** +** ** +** Created: 11/29/99 - mjb ** +** ** +** Description: Vector Math Class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_VECTOR_INL +#define __CORE_MATH_VECTOR_INL + +#ifdef __PLAT_NGPS__ +#include + + +#define __USE_VU0__ + +#ifdef DEBUG_UNINIT_VECTORS +#undef __USE_VU0__ +#endif + +#ifdef DEBUG_OVERFLOW_VECTORS +#undef __USE_VU0__ +#endif + +#endif + + +/***************************************************************************** +** Private Inline Functions ** +*****************************************************************************/ + +namespace Mth +{ + + + +/***************************************************************************** +** Public Inline Functions ** +*****************************************************************************/ + +// Unitialized vectors always need overwriting +// for now we debug this by setting them to a NaN value +// which we can check for later (and some platforms will check automatically) + + +#ifdef __INITIALIZE_VECTORS__ +inline Vector::Vector ( ) +{ + col[X] = 0.0f; + col[Y] = 0.0f; + col[Z] = 0.0f; + col[W] = 1.0f; +} +#else +#ifdef DEBUG_UNINIT_VECTORS +inline Vector::Vector ( ) +{ + *((uint32*)(&col[X])) = 0x7F800001; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&col[Y])) = 0x7F800002; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&col[Z])) = 0x7F800003; // Positive NANS (Not A Number - Signaling) + *((uint32*)(&col[W])) = 0x7F800004; // Positive NANS (Not A Number - Signaling) +} +#else +inline Vector::Vector ( ) +{ +} + +#endif +#endif + + + +inline Vector::Vector ( ENoInitType ) +{ + // DO NOTHING +} + +inline Vector::Vector ( float cx, float cy, float cz) +{ + col[X] = cx; + col[Y] = cy; + col[Z] = cz; + col[W] = 1.0f; +} + + +inline Vector::Vector ( float cx, float cy, float cz, float cw ) +{ + col[X] = cx; + col[Y] = cy; + col[Z] = cz; + col[W] = cw; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifndef DEBUG_UNINIT_VECTORS +#ifndef DEBUG_OVERFLOW_VECTORS + +inline const float& Vector::operator[] ( sint i ) const +{ + return col[i]; +} + +#endif +#endif + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float& Vector::operator[] ( sint i ) +{ + + +// Dbg_MsgAssert (( i >= X && i < NUM_ELEMENTS ),( "index out of range" )); + + return col[i]; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifndef DEBUG_UNINIT_VECTORS + +inline Vector& Vector::operator= ( const Vector& v ) +{ + +#ifdef __USE_VU0__ + // Not really VU0, but it's hardware specific + *(uint128*)(this) = *(uint128*)(&v); +#else + + col[X] = v[X]; + col[Y] = v[Y]; + col[Z] = v[Z]; + col[W] = v[W]; +#endif + return *this; +} + +#endif + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector::Vector ( const Vector& v ) +{ + + + *this = v; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Vector::operator== ( const Vector& v ) const +{ + + + return (( col[X] == v[X] ) && + ( col[Y] == v[Y] ) && + ( col[Z] == v[Z] ) && + ( col[W] == v[W] )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Vector::operator!= ( const Vector& v ) const +{ + + + return !( *this == v ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::operator+= ( const Vector& v ) +{ + + + col[X] += v[X]; + col[Y] += v[Y]; + col[Z] += v[Z]; + col[W] += v[W]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::operator-= ( const Vector& v ) +{ + + + col[X] -= v[X]; + col[Y] -= v[Y]; + col[Z] -= v[Z]; + col[W] -= v[W]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::operator*= ( const float s ) +{ + + + col[X] *= s; + col[Y] *= s; + col[Z] *= s; + col[W] *= s; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::operator/= ( float s ) +{ + + + Dbg_MsgAssert( s != 0,( "Divide by zero" )); + + s = 1.0f / s; + + col[X] *= s; + col[Y] *= s; + col[Z] *= s; + col[W] *= s; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifdef __USE_VU0__ +inline void xsceVu0MulVector(sceVu0FVECTOR v0, sceVu0FVECTOR v1, sceVu0FVECTOR v2) +{ + asm __volatile__(" + .set noreorder + lqc2 vf4,0x0(%1) + lqc2 vf5,0x0(%2) + vmul.xyzw vf6,vf4,vf5 + sqc2 vf6,0x0(%0) + .set reorder + ": : "r" (v0) , "r" (v1), "r" (v2)); +} +#endif + +inline Vector& Vector::operator*= ( const Vector& v ) +{ +#ifndef __USE_VU0__ + + col[X] *= v[X]; + col[Y] *= v[Y]; + col[Z] *= v[Z]; + col[W] *= v[W]; +# else + xsceVu0MulVector(col,(float*)v.col,col); +# endif + return *this; + + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::operator/= ( const Vector& v ) +{ + + + + Dbg_MsgAssert( v[X] != 0,( "Divide by zero X" )); + Dbg_MsgAssert( v[Y] != 0,( "Divide by zero Y" )); + Dbg_MsgAssert( v[Z] != 0,( "Divide by zero Z" )); + Dbg_MsgAssert( v[W] != 0,( "Divide by zero W" )); + + col[X] /= v[X]; + col[Y] /= v[Y]; + col[Z] /= v[Z]; + col[W] /= v[W]; + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::operator*= ( const Matrix& mat ) +{ + + #ifdef __USE_VU0__ + sceVu0ApplyMatrix((sceVu0FVECTOR)this,(sceVu0FMATRIX)&mat,(sceVu0FVECTOR)this); + #else + + Vector v=*this; + + col[X] = v[X] * mat[X][X] + v[Y] * mat[Y][X] + v[Z] * mat[Z][X] + v[W] * mat[W][X]; + col[Y] = v[X] * mat[X][Y] + v[Y] * mat[Y][Y] + v[Z] * mat[Z][Y] + v[W] * mat[W][Y]; + col[Z] = v[X] * mat[X][Z] + v[Y] * mat[Y][Z] + v[Z] * mat[Z][Z] + v[W] * mat[W][Z]; + col[W] = v[X] * mat[X][W] + v[Y] * mat[Y][W] + v[Z] * mat[Z][W] + v[W] * mat[W][W]; + + #endif + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Set ( float cx, float cy, float cz, float cw ) +{ + + + col[X] = cx; + col[Y] = cy; + col[Z] = cz; + col[W] = cw; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Vector::LengthSqr ( void ) const +{ + + + return (( col[X] * col[X] ) + + ( col[Y] * col[Y] ) + + ( col[Z] * col[Z] )); +} +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Vector::Length ( void ) const +{ + + + return sqrtf ( LengthSqr ()); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Negate ( void ) +{ + + + col[X] = -col[X]; + col[Y] = -col[Y]; + col[Z] = -col[Z]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Negate ( const Vector& src ) +{ + + + col[X] = -src[X]; + col[Y] = -src[Y]; + col[Z] = -src[Z]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#ifdef __USE_VU0__ +// currenntly this version seems to have problems with instrcutionre-ordering, +// so don't use it unless you figure it out. +// Note: it was originally a non-inline library function, so those problems would not occur +inline void xsceVu0Normalize(sceVu0FVECTOR v0, sceVu0FVECTOR v1) +{ + asm __volatile__(" + .set noreorder + lqc2 vf4,0x0(%1) + vmul.xyz vf5,vf4,vf4 + vaddy.x vf5,vf5,vf5 + vaddz.x vf5,vf5,vf5 + + vsqrt Q,vf5x + vwaitq + vaddq.x vf5x,vf0x,Q + vdiv Q,vf0w,vf5x + vsub.xyzw vf6,vf0,vf0 #vf6.xyzw=0; + vwaitq + + vmulq.xyz vf6,vf4,Q + sqc2 vf6,0x0(%0) + .set reorder + ": : "r" (v0) , "r" (v1)); +} +#endif + +inline Vector& Vector::Normalize( ) +{ + + +//# if 0 +#ifdef __USE_VU0__ +// currenntly inline (xsce) version seems to have problems with instrcutionre-ordering, +// so don't use it unless you figure it out. + sceVu0Normalize(col,col); // NOTE::: using the non-inline version (not xsce....) +# else + float l = Length(); + if ( l > 0.0f ) + { + l = 1.0f / l; + + col[X] *= l; + col[Y] *= l; + col[Z] *= l; + } + else + { +// Dbg_MsgAssert(0,("Normalizing vector of zero length")); + } +# endif + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Normalize ( float len ) +{ + + + float l = Length(); + if ( l > 0.0f ) + { + l = len / l; + + col[X] *= l; + col[Y] *= l; + col[Z] *= l; + } + else + { +// Dbg_MsgAssert(0,("Normalizing vector of zero length")); + } + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ShearX ( float shy, float shz ) +{ + + + col[X] += (( col[Y] * shy ) + ( col[Z] * shz )); + + return *this; + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ShearY ( float shx, float shz ) +{ + + + col[Y] += (( col[X] * shx ) + ( col[Z] * shz )); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ShearZ ( float shx, float shy ) +{ + + + col[Z] += (( col[X] * shx ) + ( col[Y] * shy )); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::RotateX ( float s, float c ) +{ + + + float y = ( c * col[Y] ) - ( s * col[Z] ); + float z = ( s * col[Y] ) + ( c * col[Z] ); + + col[Y] = y; + col[Z] = z; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::RotateY ( float s, float c ) +{ + + + float x = ( c * col[X] ) + ( s * col[Z] ); + float z = ( -s * col[X] ) + ( c * col[Z] ); + + col[X] = x; + col[Z] = z; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::RotateZ ( float s, float c ) +{ + + + float x = ( c * col[X] ) - ( s * col[Y] ); + float y = ( s * col[X] ) + ( c * col[Y] ); + + col[X] = x; + col[Y] = y; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::RotateX ( float angle ) +{ + + + return RotateX ( sinf ( angle ), cosf ( angle )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::RotateY ( float angle ) +{ + + + return RotateY ( sinf ( angle ), cosf ( angle )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::RotateZ ( float angle ) +{ + + + return RotateZ ( sinf ( angle ), cosf ( angle )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Rotate ( const Quat& quat ) +{ + + + *this = quat.Rotate ( *this ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Rotate ( const Matrix& mat ) +{ + + + *this = mat.Rotate ( *this ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Rotate ( const Vector& axis, const float angle ) +{ + +#if 0 + Matrix mat( axis, angle ); + *this = mat.Rotate( *this ); +#else + Quat quat( axis, angle ); + *this = quat.Rotate( *this ); +#endif + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Scale ( float scale ) +{ + col[X] *= scale; + col[Y] *= scale; + col[Z] *= scale; + col[W] *= scale; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Scale ( const Vector& scale ) +{ + col[X] *= scale[X]; + col[Y] *= scale[Y]; + col[Z] *= scale[Z]; + col[W] *= scale[W]; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ClampMin ( float min ) +{ + + + col[X] = Mth::ClampMin ( col[X], min ); + col[Y] = Mth::ClampMin ( col[Y], min ); + col[Z] = Mth::ClampMin ( col[Z], min ); + col[W] = Mth::ClampMin ( col[W], min ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ClampMax ( float max ) +{ + + + col[X] = Mth::ClampMax ( col[X], max ); + col[Y] = Mth::ClampMax ( col[Y], max ); + col[Z] = Mth::ClampMax ( col[Z], max ); + col[W] = Mth::ClampMax ( col[W], max ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Clamp ( float min, float max ) +{ + + + col[X] = Mth::ClampMin ( Mth::ClampMax ( col[X], max ), min ); + col[Y] = Mth::ClampMin ( Mth::ClampMax ( col[Y], max ), min ); + col[Z] = Mth::ClampMin ( Mth::ClampMax ( col[Z], max ), min ); + col[W] = Mth::ClampMin ( Mth::ClampMax ( col[W], max ), min ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ClampMin ( Vector& min ) +{ + + + col[X] = Mth::ClampMin ( col[X], min[X] ); + col[Y] = Mth::ClampMin ( col[Y], min[Y] ); + col[Z] = Mth::ClampMin ( col[Z], min[Z] ); + col[W] = Mth::ClampMin ( col[W], min[W] ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ClampMax ( Vector& max ) +{ + + + col[X] = Mth::ClampMax ( col[X], max[X] ); + col[Y] = Mth::ClampMax ( col[Y], max[Y] ); + col[Z] = Mth::ClampMax ( col[Z], max[Z] ); + col[W] = Mth::ClampMax ( col[W], max[W] ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::Clamp ( Vector& min, Vector& max ) +{ + + + col[X] = Mth::ClampMin ( Mth::ClampMax ( col[X], max[X] ), min[X] ); + col[Y] = Mth::ClampMin ( Mth::ClampMax ( col[Y], max[Y] ), min[Y] ); + col[Z] = Mth::ClampMin ( Mth::ClampMax ( col[Z], max[Z] ), min[Z] ); + col[W] = Mth::ClampMin ( Mth::ClampMax ( col[W], max[W] ), min[W] ); + + return *this; +} + + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Vector::GetX( void ) const +{ + + + return col[X]; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Vector::GetY( void ) const +{ + + + return col[Y]; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Vector::GetZ( void ) const +{ + + + return col[Z]; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Vector::GetW( void ) const +{ + + + return col[W]; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector operator+ ( const Vector& v1, const Vector& v2 ) +{ + + + return Vector ( v1[X] + v2[X], v1[Y] + v2[Y], v1[Z] + v2[Z], v1[W] + v2[W] ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector operator- ( const Vector& v1, const Vector& v2 ) +{ + + + return Vector ( v1[X] - v2[X], v1[Y] - v2[Y], v1[Z] - v2[Z], v1[W] - v2[W] ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector operator* ( const Vector& v, const float s ) +{ + + + return Vector ( v[X] * s, v[Y] * s, v[Z] * s, v[W] * s ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector operator* ( const float s, const Vector& v ) +{ + + + return v * s; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector operator* ( const Vector& v, const Matrix& m ) +{ + + #ifdef __USE_VU0__ + // Mick: + Mth::Vector result; + sceVu0ApplyMatrix((sceVu0FVECTOR)&result,(sceVu0FMATRIX)&m,(sceVu0FVECTOR)&v); + return result; + #else + // Mick: This is REALLY SLOW, as it calls Mth::Matrix operator[] + return Vector ( v[X] * m[X][X] + v[Y] * m[Y][X] + v[Z] * m[Z][X] + v[W] * m[W][X], + v[X] * m[X][Y] + v[Y] * m[Y][Y] + v[Z] * m[Z][Y] + v[W] * m[W][Y], + v[X] * m[X][Z] + v[Y] * m[Y][Z] + v[Z] * m[Z][Z] + v[W] * m[W][Z], + v[X] * m[X][W] + v[Y] * m[Y][W] + v[Z] * m[Z][W] + v[W] * m[W][W]); + #endif +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector operator/ ( const Vector& v, const float s ) +{ + + + Dbg_MsgAssert (( s != 0.0f ),( "Divide by Zero" )); + + float r = 1.0f / s; + + return v * r; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector operator- ( const Vector& v ) +{ + + + return Vector ( -v[X], -v[Y], -v[Z], -v[W] ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float DotProduct ( const Vector& v1, const Vector& v2 ) +{ + + + return ( v1[X] * v2[X] ) + ( v1[Y] * v2[Y] ) + ( v1[Z] * v2[Z] ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector CrossProduct ( const Vector& v1, const Vector& v2 ) +{ + return Vector (( v1[Y] * v2[Z] ) - ( v1[Z] * v2[Y] ), + ( v1[Z] * v2[X] ) - ( v1[X] * v2[Z] ), + ( v1[X] * v2[Y] ) - ( v1[Y] * v2[X] ), + 0.0f ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Clamp ( const Vector& v, float min, float max ) +{ + + + Vector f = v; + + f.Clamp ( min, max ); + + return f; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Clamp ( const Vector& v, Vector& min, Vector& max ) +{ + + + Vector f = v; + + f.Clamp ( min, max ); + + return f; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float Distance ( const Vector& v1, const Vector& v2 ) +{ + + + Vector d = v1 - v2; + + return sqrtf ( DotProduct ( d, d )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline float DistanceSqr ( const Vector& v1, const Vector& v2 ) +{ + + + Vector d = v1 - v2; + + return ( DotProduct ( d, d )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Equal ( const Vector& v1, const Vector& v2, const float tol ) +{ + + + return ( Equal ( v1[X], v2[X], tol ) && + Equal ( v1[Y], v2[Y], tol ) && + Equal ( v1[Z], v2[Z], tol )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Lerp ( const Vector& v1, const Vector& v2, const float val ) +{ + + + return Vector ( v1 + ( v2 - v1 ) * val ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector LinearMap ( const Vector& v1, const Vector& v2, float value, float value_min, float value_max ) +{ + return Vector ( v1 + ( v2 - v1 ) * ( (value - value_min) / (value_max - value_min) ) ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Min ( const Vector& v1, const Vector& v2 ) +{ + + + return Vector ( Min ( v1[X], v2[X] ), + Min ( v1[Y], v2[Y] ), + Min ( v1[Z], v2[Z] )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Max ( const Vector& v1, const Vector& v2 ) +{ + + + return Vector ( Max ( v1[X], v2[X] ), + Max ( v1[Y], v2[Y] ), + Max ( v1[Z], v2[Z] )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Min ( const Vector& v, float c ) +{ + + + return Min ( v, Vector ( c, c, c, 1.0f )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector Max ( const Vector& v, float c ) +{ + + + return Max ( v, Vector ( c, c, c, 0.0f )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Swap ( Vector& a, Vector& b ) +{ + + + Vector t = a; + + a = b; + b = t; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Vector& Vector::ZeroIfShorterThan ( float length ) +{ + + + if (LengthSqr() < length *length) + { + Set(0.0f,0.0f,0.0f,0.0f); + } + + return *this; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline ostream& operator<< ( ostream& os, const Vector& v ) +{ + return os << "( " << v[X] << ", " << v[Y] << ", " << v[Z] << ", " << v[W] << " )"; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Mth + +#endif // __CORE_MATH_VECTOR_INL + diff --git a/Code/Core/String/CString.cpp b/Code/Core/String/CString.cpp new file mode 100644 index 0000000..2768984 --- /dev/null +++ b/Code/Core/String/CString.cpp @@ -0,0 +1,346 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: String ** +** ** +** File name: Core\String\CString.cpp ** +** ** +** Created by: 9/20/2000 - rjm ** +** ** +** Description: A handy, safe class to represent a string ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include + + + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Str +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +const int String::s_max_size = 256; + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +int String::sDumbCount = 0; + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +/****************************************************************************** + * + * Function: + * + * Description: Default constructor + * + * Parameters: + * + *****************************************************************************/ + + +String::String( void ) +: mp_string( NULL ) +{ +// + + + m_dumbNum = sDumbCount; + sDumbCount++; +} + + + +/****************************************************************************** + * + * Description: First copy constructor + * (called by: String x = "blah";) + * + *****************************************************************************/ + +String::String( const char* string ) +: mp_string ( NULL ) +{ +// + + + + m_dumbNum = sDumbCount; + sDumbCount++; + + // call operator= function + *this = string; + + //Ryan("copy successful\n"); +} + + + +/****************************************************************************** + * + * Description: Second copy constructor + * + *****************************************************************************/ + +String::String( const String& string ) +: mp_string ( NULL ) +{ +// + + + // call operator= function + *this = string; + + m_dumbNum = sDumbCount; + sDumbCount++; +} + + + +/****************************************************************************** + * + * Description: destructor + * + *****************************************************************************/ + +String::~String( void ) +{ +// + + + //Ryan("Am deleting string"); + + if (mp_string) + { + delete [] mp_string; + mp_string = NULL; + } + + //Ryan("\n"); + sDumbCount--; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +String & String::operator= (const char * string) +{ +// + + + int last_char = 0; + if ( string ) + { + last_char = strlen(string) - 1; + + // Fix the case where "string" is the empty string + if( last_char < 0 ) + { + last_char = 0; + } + } + copy(string, 0, last_char); + + //Ryan("Copying %s to String\n", mp_string); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +String & String::operator= (const String & string) +{ +// + + + Dbg_MsgAssert(this != &string,( "can't assign a String to itself")); + + // call other operator= function + *this = string.mp_string; + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +bool String::operator== (const char * string) +{ +// + + + //Ryan("comparing String to string: %s to %s\n", mp_string, string); + if (mp_string && string) + { + return (strcmp(mp_string, string) == 0); + } + + return (!mp_string && !string); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +bool String::operator== (const String & string) +{ +// + + + //Ryan("comparing String to String: %s to %s\n", mp_string, string.mp_string); + return (*this == string.mp_string); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +bool String::operator!() +{ + return (mp_string == NULL); +} + +/****************************************************************************** + * + * Description: Returns pointer to internal char * array. Maybe should be const. + * + *****************************************************************************/ + +const char * String::getString() const +{ +// + + + return mp_string; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void String::copy(const char *pChar, int first_char, int last_char) +{ + + + if ( pChar && last_char >= first_char) + { +// GJ: The following handles 1-byte long strings incorrectly: +// if ( first_char == last_char ) + + if ( pChar[first_char] == 0 ) + { + if (mp_string) + { + delete [] mp_string; + } + mp_string = new char[1]; + mp_string[0] = 0; + m_length = 2; // MICK-GJ: Set length to 2, so it matches what we have below + } + else + { + int length = last_char - first_char + 2; + if (length < 4) length = 4; + Dbg_MsgAssert (length <= s_max_size,( "string too long for String object")); + + // if current string exists and is smaller than new string, delete it + if ( mp_string && ( length > m_length )) + { + delete [] mp_string; + mp_string = NULL; + } + + // if current string doesn't exist, or has been deleted, make a new one + if ( !mp_string ) + { + m_length = length; + mp_string = new char[m_length]; + } + + // perform string copy + int i = 0; + int j; + for (j = first_char; j <= last_char; j++) + { + mp_string[i] = pChar[j]; + i++; + } + mp_string[i] = '\0'; + } + } + else + { + if ( mp_string ) + { + delete [] mp_string; + mp_string = NULL; + } + } + + //Ryan("copied string %s\n", pChar); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace String + + + + diff --git a/Code/Core/String/CString.h b/Code/Core/String/CString.h new file mode 100644 index 0000000..b8efcd3 --- /dev/null +++ b/Code/Core/String/CString.h @@ -0,0 +1,113 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: String ** +** ** +** File name: Core\String\CString.h ** +** ** +** Created by: 9/20/2000 - rjm ** +** ** +** Description: A handy, safe class to represent a string ** +** ** +*****************************************************************************/ + +#ifndef __CORE_STRING_STRING_H +#define __CORE_STRING_STRING_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#ifndef __CORE_SUPPORT_CLASS_H + #include +#endif + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Str +{ + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/**************************************************************************** + * + * Class: String + * + * Description: A handy, safe class for representing a string. + * + * Usage: String x = "try our new diet of pinecones"; + * String y = x; + * printf("%s\n", y.getString()); + * + ****************************************************************************/ + +class String : public Spt::Class // fix bug delete[] !!! +{ + +public: + String(); + String(const char *string); + String(const String &string); + ~String(); + + String& operator= (const char * string); + String& operator= (const String & string); + bool operator== (const char * string); + bool operator== (const String & string); + bool operator!(); + + const char* getString() const; + void copy(const char *pChar, int first_char, int last_char); + + int m_dumbNum; + static int sDumbCount; + +private: + char* mp_string; + int m_length; + + + static const int s_max_size; +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace String + +#endif // __CORE_STRING_STRING_H + + diff --git a/Code/Core/String/stringutils.cpp b/Code/Core/String/stringutils.cpp new file mode 100644 index 0000000..1208d2b --- /dev/null +++ b/Code/Core/String/stringutils.cpp @@ -0,0 +1,247 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: ** +** ** +** Module: String ** +** ** +** File name: stringutils.cpp ** +** ** +** Created by: 05/31/01 - Mick ** +** ** +** Description: useful string (char *) utilities ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Str +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +#define Caseless(letter) (((letter)>='A' && (letter)<='Z')?((letter)+'a'-'A'):letter) + +// Returns location of Needle in the Haystack +const char * StrStr (const char *pHay, const char *pNeedle) +{ + register char First = *pNeedle++; // get first letter in needle + if (!First) return 0; // if end of needle, then return NULL + First = Caseless(First); + + while (1) + { + while (1) // scan Haystack one letter at a time + { + char FirstHay = *pHay++; // get first letter of haystack + FirstHay = Caseless(FirstHay); + if (FirstHay == First) break; // first letter matches, so drop into other code + if (!FirstHay) return 0; // if end of haystack, then return NULL + } + + const char *pH = pHay; // second letter of hay + const char *pN = pNeedle; // second letter of needle + while (1) + { + char n = *pN++; // get a needle letter + if (!n) return pHay-1; // end of needle, so return pointer to start of string in haystack + char h = *pH++; // get a hay letter + if (Caseless(n) != Caseless(h)) break; // difference, so stop looping (could be end of haystack, which is valid) + } + } +} + +void LowerCase(char *p) +{ + while (*p) + { + char letter = *p; + if (letter>='A' && letter<='Z') + letter+='a'-'A'; + *p++ = letter; + } +} + +void UpperCase(char *p) +{ + while (*p) + { + char letter = *p; + if (letter>='a' && letter<='z') + letter-='a'-'A'; + *p++ = letter; + } +} + + +int WhiteSpace(char *p) +{ + if (*p == 0x20 || *p== 0x09) + return 1; + else + return 0; +} + +char *PrintThousands(int n, char c) +{ + static char buffer[32]; // enough for any int + char normal[32]; // printf in here without commas + char *p = buffer; + + if (n<0) + { + *p++ = '-'; + n = -n; + } + sprintf (normal, "%d", n); + int digits = strlen(normal); + char *p_digit = normal; + + // Since other countries deliminate their successive kilos differently + switch (Config::GetLanguage()) + { + case Config::LANGUAGE_FRENCH: + c=' '; + break; + case Config::LANGUAGE_SPANISH: + c='.'; + break; + case Config::LANGUAGE_GERMAN: + c='.'; + break; + case Config::LANGUAGE_ITALIAN: + c='.'; + break; + default: + c=','; + break; + } + + while (digits) + { + *p++ = *p_digit++; + digits--; + if ( digits && ( digits % 3) == 0) + { + *p++ = c; + } + } + *p++ = 0; +// printf ("%s\n",buffer); + return buffer; +} + + +// helper function for SText::Draw(), SFont::QueryString() +uint DehexifyDigit(const char *pLetter) +{ + uint digit = 0; + if (*pLetter >= '0' && *pLetter <= '9') + digit = *pLetter - '0'; + else if (*pLetter >= 'a' && *pLetter <= 'v') + digit = *pLetter - 'a' + 10; + else if (*pLetter >= 'A' && *pLetter <= 'V') + digit = *pLetter - 'A' + 10; + else + { + Dbg_MsgAssert(0, ("Non Hex digit")); + } + return digit; +} + +// Returns +// -1 if ab +// +// +// if +int Compare(const char *p_a, const char *p_b) +{ + for (;;) + { + char a = *p_a++; + char b = *p_b++; + a = Caseless(a); + b = Caseless(b); + if (a < b) + { + return -1; + } + if (a > b) + { + return 1; + } + if (!a && !b) + { + // strings are identical + return 0; + } + if (!a) + { + // a is shorter, so a < b + return -1; + } + if (!b) + { + // b is shorter, so b < a + return 1; + } + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Str + + + + diff --git a/Code/Core/String/stringutils.h b/Code/Core/String/stringutils.h new file mode 100644 index 0000000..c8dc895 --- /dev/null +++ b/Code/Core/String/stringutils.h @@ -0,0 +1,80 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: ** +** ** +** Module: String ** +** ** +** File name: stringutils.h ** +** ** +** Created by: 05/31/01 - Mick ** +** ** +** Description: useful string (char *) utilities ** +** ** +*****************************************************************************/ + +#ifndef __CORE_STRING_STRINGUTILS_H +#define __CORE_STRING_STRINGUTILS_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +namespace Str +{ + + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +const char* StrStr(const char* pHay, const char* pNeedle); +void LowerCase(char* p); +void UpperCase(char* p); +int WhiteSpace(char* p); +char * PrintThousands(int n, char c = ','); +uint DehexifyDigit(const char *pLetter); +int Compare(const char *p_a, const char *p_b); + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Str + +#endif // __DIR_SUBDIR_FILE_H + + diff --git a/Code/Core/StringHashTable.h b/Code/Core/StringHashTable.h new file mode 100644 index 0000000..d0f99bf --- /dev/null +++ b/Code/Core/StringHashTable.h @@ -0,0 +1,127 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core library ** +** ** +** Module: HashTable ** +** ** +** File name: Core\HashTable.h ** +** ** +** Created by: 9/22/2000 - rjm ** +** ** +** Description: A handy Hashtable class ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LIST_STRINGHASHTABLE_H +#define __CORE_LIST_STRINGHASHTABLE_H + +#ifndef __PLAT_WN32__ + #include +#endif + +#include "HashTable.h" + +namespace Crc +{ + uint32 GenerateCRCFromString( const char *pName ); +} + +namespace Script +{ + // So as not to require inclusion of checksum.h +#ifdef __NOPT_ASSERT__ + const char *FindChecksumName(uint32 checksum); +#endif +} + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +namespace Lst +{ + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +template +class StringHashTable : public HashTable<_V> +{ + +public: + StringHashTable(int numBits); + + bool PutItem(const char *stringKey, _V *item); + _V * GetItem(const char *stringKey, bool assert_if_clash = true); + void FlushItem(const char *stringKey); +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +template //inline +StringHashTable<_V>::StringHashTable(int numBits) : + HashTable<_V>(numBits) +{ +} + + + +template //inline +bool StringHashTable<_V>::PutItem(const char *stringKey, _V *item) +{ + uint32 key = Crc::GenerateCRCFromString(stringKey); + return HashTable<_V>::PutItem(key, item); +} + + + +template //inline +_V *StringHashTable<_V>::GetItem(const char *stringKey, bool assert_if_clash) +{ + uint32 key = Crc::GenerateCRCFromString(stringKey); + return HashTable<_V>::GetItem(key, assert_if_clash); +} + + +template //inline +void StringHashTable<_V>::FlushItem(const char *stringKey) +{ + uint32 key = Crc::GenerateCRCFromString(stringKey); + HashTable<_V>::FlushItem(key); +} + +} // namespace Lst + +#endif // __CORE_LIST_STRINGHASHTABLE_H + + diff --git a/Code/Core/Support/Lock.h b/Code/Core/Support/Lock.h new file mode 100644 index 0000000..e8a3184 --- /dev/null +++ b/Code/Core/Support/Lock.h @@ -0,0 +1,149 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Support (SPT) ** +** ** +** File name: core/support/lock.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_LOCK_H +#define __CORE_LOCK_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +namespace Spt +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +class Access : public Spt::Class +{ + + +public : + + Access(); + + void Forbid( void ); + void Permit( void ); + void Enable( void ); + bool HaveAccess( void ); + +private : + + sint m_count; +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +inline Access::Access() +: m_count( 0 ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Access::Forbid( void ) +{ + + + m_count++; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Access::Permit( void ) +{ + + + m_count--; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Access::Enable( void ) +{ + + + m_count = 0; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Access::HaveAccess ( void ) +{ + + + return( m_count == 0 ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Spt + +#endif // __CORE_LOCK_H + diff --git a/Code/Core/Support/Ref.h b/Code/Core/Support/Ref.h new file mode 100644 index 0000000..3952054 --- /dev/null +++ b/Code/Core/Support/Ref.h @@ -0,0 +1,150 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Support (SPT) ** +** ** +** File name: core/support/lock.h ** +** ** +** Created: 03/16/00 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_REF_H +#define __CORE_REF_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Spt +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +class Ref : public Spt::Class +{ + + +public : + + Ref(); + Ref( const Ref& rhs ); + + void Acquire( void ); + void Release( void ); + bool InUse( void ); + +private : + + sint m_count; +}; + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +inline Ref::Ref () +: m_count( 0 ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + + +inline Ref::Ref( const Ref& rhs ) +: m_count( 0 ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Ref::Acquire ( void ) +{ + + + m_count++; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void Ref::Release ( void ) +{ + + + Dbg_MsgAssert(m_count>0,("Tried to call Release when m_count was already zero")); + m_count--; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool Ref::InUse ( void ) +{ + + + return ( m_count != 0 ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Spt + +#endif // __CORE_REF_H + diff --git a/Code/Core/Support/class.cpp b/Code/Core/Support/class.cpp new file mode 100644 index 0000000..cda9547 --- /dev/null +++ b/Code/Core/Support/class.cpp @@ -0,0 +1,272 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Support (SPT) ** +** ** +** File name: class.cpp ** +** ** +** Created by: 06/10/99 - mjb ** +** ** +** Description: Base class from which all other classes are derived. ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +#include +#include +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +#define NUM_ADDRESS 0 // Change this to 8 to have a handy list of addresses to check. + // If set to 0, no extra code/data will be generated. + // Remember to change to 8, compile, then get the addresses in question + // as the act of adding this code/data will change your addresses. +#if NUM_ADDRESS > 0 +uint32 gAddress[NUM_ADDRESS] = +{ + 0x81701061, + 0x81700ee1, + 0x81700d61, + 0x81700be1, + 0x81700a61, + 0x817008e1, + 0x81700761, + 0x817005e1 +}; + +static void check_address( void * p, int size ) +{ + for ( int lp = 0; lp < 8; lp++ ) + { + if ( gAddress[lp] == ((uint32)p) ) { + Dbg_Message( "We found the address we're looing for: 0x%08x (%d)\n", (uint32)p, size ); + } + } +} + +#else +#define check_address(a,b) +#endif + +#ifdef __PLAT_NGC__ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +static inline void* operator new( size_t size ) +{ +// Dbg_SetPlacementNew( false ); + void * rv = Mem::Manager::sHandle().New( size, true ); + check_address( rv, size ); + return rv; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +static inline void* operator new[] ( size_t size ) +{ +// Dbg_SetPlacementNew( false ); + void * rv = Mem::Manager::sHandle().New( size, true ); + check_address( rv, size ); + return rv; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +static inline void operator delete( void* pAddr ) +{ + Mem::Manager::sHandle().Delete( pAddr ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +static inline void operator delete[]( void* pAddr ) +{ + Mem::Manager::sHandle().Delete( pAddr ); +} + +#endif // __PLAT_NGC__ + + + +namespace Spt +{ + +/******************************************************************/ +/* new operators - same as global + zero-ed memory */ +/* */ +/******************************************************************/ + +#if (defined ( ZERO_CLASS_MEM ) && !defined ( __PLAT_WN32__ )) + +void* Class::operator new( size_t size ) +{ + void* p_ret = Mem::Manager::sHandle().New( size ); + + if ( p_ret ) + { + memset ( p_ret, 0, size ); + } + + check_address( p_ret, size ); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* Class::operator new[] ( size_t size ) +{ + void* p_ret = Mem::Manager::sHandle().New( size ); + + if ( p_ret ) + { + memset ( p_ret, 0, size ); + } + + + check_address( p_ret, size ); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* Class::operator new( size_t size, bool assert_on_fail ) +{ + void* p_ret = Mem::Manager::sHandle().New( size, assert_on_fail ); + + if ( p_ret ) + { + memset ( p_ret, 0, size ); + } + + + check_address( p_ret, size ); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* Class::operator new[] ( size_t size, bool assert_on_fail ) +{ + void* p_ret = Mem::Manager::sHandle().New( size, assert_on_fail ); + + if ( p_ret ) + { + memset ( p_ret, 0, size ); + } + + + check_address( p_ret, size ); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* Class::operator new( size_t size, Mem::Allocator* pAlloc, bool assert_on_fail ) +{ + void* p_ret = Mem::Manager::sHandle().New( size, assert_on_fail, pAlloc ); + + if ( p_ret ) + { + memset ( p_ret, 0, size ); + } + + + check_address( p_ret, size ); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* Class::operator new[]( size_t size, Mem::Allocator* pAlloc, bool assert_on_fail ) +{ + void* p_ret = Mem::Manager::sHandle().New( size, assert_on_fail, pAlloc ); + + if ( p_ret ) + { + memset ( p_ret, 0, size ); + } + + check_address( p_ret, size ); + return p_ret; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* Class::operator new( size_t size, void* pLocation ) +{ + + if ( pLocation ) + { + memset ( pLocation, 0, size ); + } + + + return pLocation; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void* Class::operator new[]( size_t size, void* pLocation ) +{ + + if ( pLocation ) + { + memset ( pLocation, 0, size ); + } + + + return pLocation; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +#endif +} // namespace Spt + diff --git a/Code/Core/Support/class.h b/Code/Core/Support/class.h new file mode 100644 index 0000000..196d519 --- /dev/null +++ b/Code/Core/Support/class.h @@ -0,0 +1,63 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Support (SPT) ** +** ** +** Created: 06/10/99 mjb ** +** ** +** File name: core/support/class.h ** +** ** +** Description: Base class from which classes are derived if they want ** +** their memory zeroed when instantiated (via new) ** +** ** +*****************************************************************************/ + +#ifndef __CORE_SUPPORT_CLASS_H +#define __CORE_SUPPORT_CLASS_H + +#ifndef __CORE_DEFINES_H + #include +#endif + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +#define ZERO_CLASS_MEM TRUE + +namespace Mem +{ + class Allocator; +} + +namespace Spt +{ + +class Class +{ + public: + + #if (defined ( ZERO_CLASS_MEM ) && !defined ( __PLAT_WN32__ )) + void* operator new( size_t size ); + void* operator new[] ( size_t size ); + void* operator new( size_t size, bool assert_on_fail ); + void* operator new[] ( size_t size, bool assert_on_fail ); + void* operator new( size_t size, Mem::Allocator* pAlloc, bool assert_on_fail = true ); + void* operator new[]( size_t size, Mem::Allocator* pAlloc, bool assert_on_fail = true ); + void* operator new( size_t size, void* pLocation ); + void* operator new[]( size_t size, void* pLocation ); + + #endif // ZERO_CLASS_MEM +}; + +} // namespace Spt + +#endif // __CORE_SUPPORT_CLASS_H + diff --git a/Code/Core/Support/support.h b/Code/Core/Support/support.h new file mode 100644 index 0000000..db3855b --- /dev/null +++ b/Code/Core/Support/support.h @@ -0,0 +1,76 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Support (SPT) ** +** ** +** Created: 06/10/99 mjb ** +** ** +** File name: core/support/support.h ** +** ** +** Description: template class for tracking class hierarchy ** +** ** +*****************************************************************************/ + +#ifndef __CORE_SUPPORT_SUPPORT_H +#define __CORE_SUPPORT_SUPPORT_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ +#ifdef __PLAT_XBOX__ +#include +#else +#include +#endif +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +//#define class _c : public Spt::Class class _c : public Spt::Class +//#define class _c : public _b class _c : public _b + +#define nTemplateBaseClass(_t,_c) \ + template< class _t > \ + class _c : public Spt::Class \ + +#define nTemplateBaseClass2(_t1,_t2,_c) \ + template< class _t1, class _t2 > \ + class _c : public Spt::Class \ + +#define nTemplateBaseClass3(_t1,_t2,_t3,_c) \ + template< class _t1, class _t2, class _t3 > \ + class _c : public Spt::Class \ + +#define nTemplateSubClass(_t,_c,_b) \ + template< class _t > \ + class _c : public _b \ + +#define nTemplateSubClass2(_t1,_t2,_c,_b) \ + template< class _t1, class _t2 > \ + class _c : public _b \ + +#define nTemplateSubClass3(_t1,_t2,_t3,_c,_b) \ + template< class _t1, class _t2, class _t3 > \ + class _c : public _b \ + + +#define Dbg_TemplateBaseClass(_t1,_c) +#define Dbg_TemplateSubClass(_t1,_c,_b) + +#define Dbg_TemplateBaseClass(_t1,_c) +#define Dbg_TemplateSubClass(_t1,_c,_b) + +#define Dbg_TemplateBaseClass2(_t1,_t2,_c) +#define Dbg_TemplateSubClass2(_t1,_t2,_c,_b) + + +#endif // __CORE_SUPPORT_SUPPORT_H diff --git a/Code/Core/Task.h b/Code/Core/Task.h new file mode 100644 index 0000000..52a5bde --- /dev/null +++ b/Code/Core/Task.h @@ -0,0 +1,67 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task (TSK) ** +** ** +** File name: core/task.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_TASK_H +#define __CORE_TASK_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +#endif // __CORE_TASK_H + + diff --git a/Code/Core/Task/Hook.h b/Code/Core/Task/Hook.h new file mode 100644 index 0000000..610a21b --- /dev/null +++ b/Code/Core/Task/Hook.h @@ -0,0 +1,158 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task (TSK_) ** +** ** +** File name: core/task/hook.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_TASK_HOOK_H +#define __CORE_TASK_HOOK_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Tsk +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/*********************************************************************** + * + * Class: BaseHook + * + * Description: abstract base class for code hook + * defines minimal interface + * + ***********************************************************************/ + +class BaseHook : public Spt::Class +{ + + +public : + + virtual void Call( void ) const = 0; + +protected : + + BaseHook( void ) {} + +}; + +/*********************************************************************** + * + * Class: Hook< _T > + * + * Description: derived template class for hook with typed data field + * + ***********************************************************************/ + +nTemplateSubClass( _T, Hook, BaseHook ) +{ + + +public : + + typedef void ( Code )( const Hook< _T >& ); + + Hook( Code* code, _T& data ); + + void Call( void ) const; + _T& GetData( void ) const; + +private : + + Code* const code; + _T& data; + +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +template < class _T > inline +Hook< _T >::Hook( Code* _code, _T& _data ) +: code( _code ), data( _data ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Hook< _T >::Call( void ) const +{ + + + Dbg_AssertPtr( code ); + code( *this ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +_T& Hook< _T >::GetData( void ) const +{ + + + Dbg_AssertType( &data, _T ); + return data; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Tsk + +#endif // __CORE_TASK_HOOK_H + + diff --git a/Code/Core/Task/List.h b/Code/Core/Task/List.h new file mode 100644 index 0000000..77313f0 --- /dev/null +++ b/Code/Core/Task/List.h @@ -0,0 +1,108 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task (TSK_) ** +** ** +** File name: core/task/list.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_TASK_LIST_H +#define __CORE_TASK_LIST_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#ifndef __CORE_DEFINES_H +#include +#endif +#include +#include "core/support/lock.h" + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Tsk +{ + + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +class BaseTask; // forward declaration + +class List : public Spt::Access +{ + + +public : + List ( void ); + ~List ( void ); + + void RemoveAllTasks ( void ); + void Process ( bool time, uint mask ); + void Dump ( void ); + void AddTask ( BaseTask& task ); + bool IsEmpty ( void ); + void SignalListChange( void ); + +private : + + Lst::Head list; + uint stamp; + bool list_changed; + +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +inline bool List::IsEmpty ( void ) +{ + + + return list.IsEmpty(); +} + + +} // namespace Tsk + +#endif // __CORE_TASK_LIST_H + + diff --git a/Code/Core/Task/Stack.h b/Code/Core/Task/Stack.h new file mode 100644 index 0000000..1559ab9 --- /dev/null +++ b/Code/Core/Task/Stack.h @@ -0,0 +1,112 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task (TSK_) ** +** ** +** File name: core/task/stack.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_TASK_STACK_H +#define __CORE_TASK_STACK_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Tsk +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +class Stack : public Spt::Access +{ + + +public : + Stack ( void ); + ~Stack ( void ); + + void Push ( void ); + void Pop ( void ); + void Process ( bool time = false); + void Dump ( void ); + void AddTask ( BaseTask& task ); + void AddPushTask ( BaseTask& task ); + void RemoveAllPushTasks ( void ); + void RemoveAllTasks ( void ); + void SetMask(uint mask); + +private : + + uint m_mask_off; // mask to supress execution of tasks + + void remove_dead_entries( void ); + + class StackElement; // forward declaration + + Lst::Head< StackElement > m_stack; + Lst::Head< StackElement > m_dead; // dead element list + StackElement* m_run; // current run task list + StackElement* m_add; // current add task list + + // these get reset when Process() is called + bool m_pushedList; + bool m_poppedList; +}; + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +inline void Stack::SetMask(uint mask) +{ + m_mask_off = mask; +} + +} // namespace Tsk + +#endif // __CORE_TASK_STACK_H + + diff --git a/Code/Core/Task/Task.cpp b/Code/Core/Task/Task.cpp new file mode 100644 index 0000000..8742729 --- /dev/null +++ b/Code/Core/Task/Task.cpp @@ -0,0 +1,162 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task Manager (TSK) ** +** ** +** File name: task.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Task Support ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + + + +namespace Tsk +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +BaseTask::BaseTask( Node::Priority priority ) +: tlist(NULL), stamp(0) +{ + + + m_mask = 0; // default to "No type of thing", so it will always run + node = new Node( this, priority ); + Dbg_AssertType( node, Node ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +BaseTask::~BaseTask () +{ + + + if ( tlist ) + { + Remove(); + } + + Dbg_AssertType( node, Node ); + delete node; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void BaseTask::Remove( void ) +{ + + + if ( tlist ) + { + tlist->Forbid(); + + tlist->SignalListChange(); + node->Remove(); + + tlist->Permit(); + tlist = NULL; + } + else + { + Dbg_Warning( "Task is not in a List" ); + } + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +bool BaseTask::InList( void ) const +{ + + + return node->InList(); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void BaseTask::SetPriority( Node::Priority pri ) const +{ + + + node->SetPri( pri ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Tsk diff --git a/Code/Core/Task/Task.h b/Code/Core/Task/Task.h new file mode 100644 index 0000000..58f2176 --- /dev/null +++ b/Code/Core/Task/Task.h @@ -0,0 +1,206 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task (TSK_) ** +** ** +** File name: core/task/task.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_TASK_TASK_H +#define __CORE_TASK_TASK_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#ifndef __CORE_DEFINES_H +#include +#endif +#include +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Tsk +{ + + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + + +class BaseTask : public Spt::Access +{ + + + friend class List; + + +public: + + typedef Lst::Node< BaseTask > Node; + + bool InList( void ) const; + void Remove( void ); + + virtual void vCall( void ) const = 0; + virtual void * GetCode(void) const = 0; + + void SetPriority( Node::Priority pri ) const; + virtual ~BaseTask( void ); + void SetMask(uint mask); + uint GetMask(); + +protected: + BaseTask( Node::Priority pri ); + + Node* node; // link node + +private: + + List* tlist; // task list to which we belong + uint stamp; // process stamp + uint m_mask; // mask indicating what types of thing this is +}; + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +nTemplateSubClass( _T, Task, BaseTask ) +{ + + +public: + + typedef void (Code)( const Task< _T >& ); + + Task( Code* const code, _T& data, Node::Priority pri = Lst::Node< BaseTask >::vNORMAL_PRIORITY ); + + virtual void vCall( void ) const; + _T& GetData( void ) const; + virtual void * GetCode(void) const; + +private : + Code* const code; // tasks entry point + _T& data; // task defined data +}; + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +template < class _T > inline +Task< _T >::Task ( Code* const _code, _T& _data, Node::Priority pri ) +: BaseTask( pri ), code( _code ), data( _data ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Task< _T >::vCall( void ) const +{ + + + Dbg_AssertPtr( code ); + code( *this ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +_T& Task< _T >::GetData( void ) const +{ + + + Dbg_AssertType( &data, _T ); + return data; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + + + +template < class _T > inline +void * Task< _T >::GetCode( void ) const +{ + + + return (void*)code; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void BaseTask::SetMask( uint mask ) +{ + + m_mask = mask; +} + +inline uint BaseTask::GetMask( void ) +{ + + return m_mask; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Tsk + +#endif // __CORE_TASK_TASK_H + + diff --git a/Code/Core/Task/Tlist.cpp b/Code/Core/Task/Tlist.cpp new file mode 100644 index 0000000..7ae00b6 --- /dev/null +++ b/Code/Core/Task/Tlist.cpp @@ -0,0 +1,291 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task Manager (TSK) ** +** ** +** File name: tlist.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Task List Support ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +#include // Including for debugging +#include // Including for debugging + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Tsk +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +List* TSK_BkGndTasks = NULL; + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +List::List( void ) +: stamp( 0 ), list_changed( false ) +{ + + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +List::~List ( void ) +{ + + + Dbg_MsgAssert( list.IsEmpty(),( "Task List Not Empty" )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void List::Process( bool time, uint mask ) +{ + + + if ( !HaveAccess ()) + { + return; + } + + stamp++; + + do + { + Lst::Search iterator; + BaseTask* next = iterator.FirstItem( list ); + + list_changed = false; + + while ( next ) + { + BaseTask* task = next; + + Dbg_AssertType( task, BaseTask ); + Dbg_AssertPtr( task ); + Dbg_AssertPtr( task->node ); + Dbg_MsgAssert( task->node->InList(),( "Task was removed from list by previous task" )); + + next = iterator.NextItem(); // get next task before we execute the current + // one as the task might remove itself + + Dbg_MsgAssert(!list_changed,( "list changed, you fucker")); + + if ( task->HaveAccess() ) + { + if ( task->stamp != stamp ) + { + task->stamp = stamp; + +#ifdef __USE_PROFILER__ + Tmr::MicroSeconds start_time=0; +#ifdef __PLAT_NGPS__ + Tmr::MicroSeconds end_time=0; +#endif + if (time) + { + start_time = Tmr::GetTimeInUSeconds(); + } +#endif + // only call the task if it is not masked off + if (! (task->GetMask() & mask)) + { + task->vCall(); + } +#ifdef __USE_PROFILER__ +#ifdef __PLAT_NGPS__ + if (time) + { + end_time = Tmr::GetTimeInUSeconds(); + + int length = (int)(end_time - start_time); + int size; + printf ("%6d %s\n",length,MemView_GetFunctionName((int)task->GetCode(), &size)); + } +#endif +#endif + } + } + + if ( list_changed ) + { + break; + } + } + + } while ( list_changed ); + +} + + +/******************************************************************/ +// Mick Debugging: Dump out the contents of the task list +// +/******************************************************************/ + +void List::Dump( void ) +{ + + + Lst::Search iterator; + BaseTask* next = iterator.FirstItem( list ); + + list_changed = false; + + while ( next ) + { + BaseTask* task = next; + + Dbg_AssertType( task, BaseTask ); + Dbg_AssertPtr( task ); + Dbg_AssertPtr( task->node ); + + next = iterator.NextItem(); // get next task before we execute the current + // one as the task might remove itself +#ifdef __PLAT_NGPS__ + if ( task->HaveAccess() ) + { +// char *MemView_GetFunctionName(int pc, int *p_size); +// task->vCall(); + int size; + printf ("Task %p %s\n",task->GetCode(), + MemView_GetFunctionName((int)task->GetCode(), &size)); + } +#elif defined( __PLAT_NGC__ ) + if ( task->HaveAccess() ) + { +// char *MemView_GetFunctionName(int pc, int *p_size); +// task->vCall(); + Dbg_Printf ("Task %p\n",task->GetCode() ); +// MemView_GetFunctionName((int)task->GetCode(), &size)); + } +#endif + } +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void List::AddTask( BaseTask& task ) +{ + + + Dbg_AssertType( &task, BaseTask ); + Dbg_MsgAssert( !task.node->InList(),( "Task is already in a list" )); + + Forbid (); // stop any processing while list is modified + + list.AddNode( task.node ); + task.tlist = this; + task.stamp = stamp - 1; + list_changed = true; + + Permit(); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void List::RemoveAllTasks( void ) +{ + + + Lst::Search iterator; + BaseTask* next; + + Forbid(); + + while (( next = iterator.FirstItem( list ) )) + { + next->Remove(); + list_changed = true; + } + + Permit(); + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void List::SignalListChange( void ) +{ + list_changed = true; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +} // namespace Tsk + diff --git a/Code/Core/Task/Tstack.cpp b/Code/Core/Task/Tstack.cpp new file mode 100644 index 0000000..f425392 --- /dev/null +++ b/Code/Core/Task/Tstack.cpp @@ -0,0 +1,377 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Task Manager (TSK) ** +** ** +** File name: tstack.cpp ** +** ** +** Created by: 05/27/99 - mjb ** +** ** +** Description: Task Stack Support ** +** ** +*****************************************************************************/ + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + +namespace Tsk +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +class Stack::StackElement : public List +{ + + + friend class Stack; + +public : + + StackElement( void ); + ~StackElement( void ); + +private: + + Lst::Node< StackElement >* m_node; + +}; + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + + +void Stack::remove_dead_entries( void ) +{ + + + Lst::Search< StackElement > iterator; + StackElement* dead_entry; + + Dbg_MsgAssert( HaveAccess(),( "List currently being processed" )); + + while (( dead_entry = iterator.FirstItem( m_dead ))) + { + delete dead_entry; + } +} + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +Stack::StackElement::StackElement( void ) +{ + + + m_node = new Lst::Node< StackElement >( this ); + Dbg_AssertType( m_node, Lst::Node< StackElement > ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Stack::StackElement::~StackElement ( void ) +{ + + + Dbg_AssertType( m_node, Lst::Node< StackElement > ); + delete m_node; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Stack::Stack ( void ) +{ + + + m_pushedList = false; + m_poppedList = false; + + Push(); // create initial list + m_add = m_run; + + m_pushedList = false; + m_poppedList = false; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +Stack::~Stack ( void ) +{ + + + Dbg_MsgAssert( HaveAccess(),( "List currently being processed" )); + + Dbg_Code + ( + if ( m_stack.CountItems() != 1 ) + { + Dbg_Warning( "Task Stack contains %d Task Lists", m_stack.CountItems()); + } + ) + + while ( m_stack.CountItems() ) + { + Pop (); + } + + remove_dead_entries(); + + Dbg_MsgAssert (( m_run == NULL ),( "Task Stack not empty" )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Stack::Push( void ) +{ + /* + On entry: + m_add -> top list on stack (the one being processed) + m_run -> probably the same + + On exit: + m_run -> new top list + m_add -> old top list (the one being processed) + */ + + + Dbg_MsgAssert(!m_pushedList,( "push already called this frame")); + + m_run = new StackElement; + + Dbg_AssertType( m_run, StackElement ); + + m_stack.AddToHead( m_run->m_node ); // deferred push + m_pushedList = true; + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Stack::Pop( void ) +{ + /* + On entry: + m_add -> top list on stack (the one being processed) + m_run -> top list on stack + + On exit: + m_run and m_add -> the new top list (originally second to top) + m_dead -> list to be destroyed (still being processed) + */ + + + Dbg_AssertType( m_run, StackElement ); + // this assert is problematic because the task system does not call Process() on the input + // task list stacks for inactive devices, though Push() and Pop() are called + //Dbg_MsgAssert(!m_poppedList,( "pop already called this frame")); + + // remove item + + Lst::Search< StackElement > iterator; + iterator.FirstItem( m_stack ); + + // must use this test, m_pushedList is not reliable + if (m_add == m_run) + m_add = iterator.NextItem(); + else + { + // otherwise, current list is retained, m_add stays the same + m_pushedList = false; + } + + + // m_run may have been just added this frame, but that shouldn't matter + Dbg_MsgAssert( m_run->IsEmpty(),( "Tasks List not Empty" )); + if( HaveAccess() ) + { + delete m_run; + } + else + { + m_run->m_node->Remove(); + m_dead.AddToHead( m_run->m_node ); // deferred kill + } + + m_run = m_add; + m_poppedList = true; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Stack::Process( bool time ) +{ + + + Forbid(); + + Dbg_AssertType( m_run, StackElement ); + + m_add = m_run; // push now in effect + m_run->Process( time, m_mask_off ); + + Permit(); + + remove_dead_entries(); + + m_pushedList = false; + m_poppedList = false; +} + +void Stack::Dump( void ) +{ + + + Forbid(); + + Dbg_AssertType( m_run, StackElement ); + + m_add = m_run; // push now in effect + m_run->Dump(); + + Permit(); + + remove_dead_entries(); + + m_pushedList = false; + m_poppedList = false; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Stack::AddTask( BaseTask& task ) +{ + + + Dbg_AssertType( &task, BaseTask ); + Dbg_AssertType( m_add, StackElement ); + + m_add->AddTask( task ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Stack::AddPushTask( BaseTask& task ) +{ + + + Dbg_AssertType( &task, BaseTask ); + Dbg_AssertType( m_run, StackElement ); + + m_run->AddTask( task ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Stack::RemoveAllTasks( void ) +{ + + + // we may really be removing all tasks in list about to be pushed + if (m_pushedList) + { + RemoveAllPushTasks(); + Dbg_MsgAssert( m_run->IsEmpty(),( "should now be empty" )); + return; + } + + Dbg_AssertType( m_add, StackElement ); + + m_add->RemoveAllTasks(); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +void Stack::RemoveAllPushTasks( void ) +{ + + + Dbg_AssertType( m_run, StackElement ); + + m_run->RemoveAllTasks(); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Tsk + diff --git a/Code/Core/Thread/ngc/t_thread.cpp b/Code/Core/Thread/ngc/t_thread.cpp new file mode 100644 index 0000000..8a58610 --- /dev/null +++ b/Code/Core/Thread/ngc/t_thread.cpp @@ -0,0 +1,70 @@ +///****************************************************************** +//* +//* DESCRIPTION: thread/ngps/t_thread.cpp +//* +//* AUTHOR: JAB +//* +//* HISTORY: +//* +//* DATE:9/1/2000 +//* +//*******************************************************************/ +// +///** include files **/ +// +//#include +//#include +//#include +////#include +// +//#include "t_thread.h" +// +////#ifdef __NOPT_STD_MEM__ +//extern "C" int _std_stack[1]; +//static int* _stack = _std_stack; +////#else +////extern "C" int _dbg_stack[1]; +////static int* _stack = _dbg_stack; +////#endif +// +//extern "C" int _stack_size[1]; +//extern "C" int _start(void); +// +//Thread::PerThreadStruct _rootThreadStruct = +//{ +// "ROOT", +// 0, +// 0, +// _start, // m_pEntry +// _stack, // m_pStackBase +// (int)_stack_size, // m_iStackSize +// 0, +// 0 +//}; +// +//int Thread::dummy = -1; +// +// +//namespace Thread +//{ +// Dbg_Module +// +//// PJR - Commented out. +//// HTHREAD CreateThread( PerThreadStruct* pPTS ) +//// { +//// ThreadParam sParam; +//// sParam.entry = pPTS->m_pEntry; +//// sParam.stack = pPTS->m_pStackBase; +//// sParam.stackSize = pPTS->m_iStackSize; +//// //ps2memset( pPTS->m_pStackBase, 0xca, pPTS->m_iStackSize); +//// sParam.initPriority = pPTS->m_iInitialPriority; +//// sParam.gpReg = pPTS; +////#ifdef __THREAD_DEBUGGING_PRINTS__ +//// Dbg_Message( "T::CrThread: %s, entry 0x%x, stack 0x%x, size 0x%x\n",pPTS->m_cID, (unsigned int) pPTS->m_pEntry, (unsigned int) pPTS->m_pStackBase, (unsigned int) pPTS->m_iStackSize ); +////#endif +//// pPTS->m_osId = ::CreateThread( &sParam ); +//// return (HTHREAD) pPTS; +//// } +//} + + diff --git a/Code/Core/Thread/ngc/t_thread.h b/Code/Core/Thread/ngc/t_thread.h new file mode 100644 index 0000000..e1f0336 --- /dev/null +++ b/Code/Core/Thread/ngc/t_thread.h @@ -0,0 +1,70 @@ +/******************************************************************* +* +* DESCRIPTION: thread/ngc/t_thread.h +* +* AUTHOR: JAB +* +* HISTORY: +* +* DATE:9/1/2000 +* +*******************************************************************/ + +/** include files **/ +#ifndef __CORE_THREAD_NGC_T_THREAD_H__ +#define __CORE_THREAD_NGC_T_THREAD_H__ + +#include +//#include + +namespace Thread { + typedef int THREADID; // Id for use and assigned by application + typedef int OSTHREADID; // Id assigned by the OS. + typedef unsigned int HTHREAD; // A value that is easy to determine from the OS + + class PerThreadStruct { + public: + char m_cID[16]; // a text name so its easy to + THREADID m_utid; // an id that the use can assign + OSTHREADID m_osId; // an id that the OS assigns + void* m_pEntry; + void* m_pStackBase; + int m_iStackSize; + int m_iInitialPriority; + void* m_pRpcData; // ptr to rpc specific data. + }; + +// PJR - Not sure... +// register PerThreadStruct* g_pCurrThread asm ("gp"); // Gotta fucking love that, eh? + +// inline THREADID GetCurrentThreadId() { return (g_pCurrThread) ? g_pCurrThread->m_utid : 0; } +// inline HTHREAD GetCurrentHandle() { return (HTHREAD) g_pCurrThread; } // This is fastest, so use it if you want to get data fast; + //inline void StartThread( HTHREAD hthr, void* arg = NULL ) { ::StartThread( ((PerThreadStruct*)hthr)->m_osId, arg ); } + // So you must set up the stack, and the perthreadstruct. + HTHREAD CreateThread( PerThreadStruct* pPTS ); + + /* + Commentary on data for submodules (eg m_pRpcData): + Yes there are less data dependent ways of doing this. For example, + by having an array of pointers and the subsystem requests an index + that it can use. But the extra work (small tho it is) seems a bit + pointless when the penalty for hardcoding it is having to recompile + lots of things if it changes. Not a big deal. + */ + extern int dummy; +}; + +struct PTS{ + char m_cID[16]; // a text name so its easy to + Thread::THREADID m_utid; // an id that the use can assign + Thread::OSTHREADID m_osId; // an id that the OS assigns + void* m_pEntry; + void* m_pStackBase; + int m_iStackSize; + int m_iInitialPriority; + void* m_pRpcData; // ptr to rpc specific data. +}; + +extern "C" Thread::PerThreadStruct _rootThreadStruct; + +#endif // __CORE_THREAD_NGC_T_THREAD_H__ diff --git a/Code/Core/Thread/ngps/t_thread.cpp b/Code/Core/Thread/ngps/t_thread.cpp new file mode 100644 index 0000000..6d980e6 --- /dev/null +++ b/Code/Core/Thread/ngps/t_thread.cpp @@ -0,0 +1,71 @@ +/******************************************************************* +* +* DESCRIPTION: thread/ngps/t_thread.cpp +* +* AUTHOR: JAB +* +* HISTORY: +* +* DATE:9/1/2000 +* +*******************************************************************/ + +/** include files **/ + +#include +#include +#include + +#include "t_thread.h" + +extern "C" int _std_stack[8]; +static int* _stack = _std_stack; + +extern "C" int _stack_size[8]; +extern "C" int _start(void); + +Thread::PerThreadStruct _rootThreadStruct = +{ + "ROOT", + 0, + 0, + _start, // m_pEntry + _stack, // m_pStackBase + (int)_stack_size, // m_iStackSize + 0, + 0 +}; + +int Thread::dummy = -1; + + + +typedef void (*VOID_CB)(void*); // pointer to function that takes a void pointer + + + +namespace Thread +{ + + + + HTHREAD CreateThread( PerThreadStruct* pPTS ) + { + + ThreadParam sParam; + /*sParam.entry = pPTS->m_pEntry;*/ + sParam.entry = (VOID_CB) (pPTS->m_pEntry); + sParam.stack = pPTS->m_pStackBase; + sParam.stackSize = pPTS->m_iStackSize; + //ps2memset( pPTS->m_pStackBase, 0xca, pPTS->m_iStackSize); + sParam.initPriority = pPTS->m_iInitialPriority; + sParam.gpReg = &_gp; +#ifdef __THREAD_DEBUGGING_PRINTS__ + Dbg_Message( "T::CrThread: %s, entry 0x%x, stack 0x%x, size 0x%x\n",pPTS->m_cID, (unsigned int) pPTS->m_pEntry, (unsigned int) pPTS->m_pStackBase, (unsigned int) pPTS->m_iStackSize ); +#endif + pPTS->m_osId = ::CreateThread( &sParam ); + return (HTHREAD) pPTS; + } +} + + diff --git a/Code/Core/Thread/ngps/t_thread.h b/Code/Core/Thread/ngps/t_thread.h new file mode 100644 index 0000000..e0076cb --- /dev/null +++ b/Code/Core/Thread/ngps/t_thread.h @@ -0,0 +1,67 @@ +/******************************************************************* +* +* DESCRIPTION: thread/ngps/t_thread.h +* +* AUTHOR: JAB +* +* HISTORY: +* +* DATE:9/1/2000 +* +*******************************************************************/ + +/** include files **/ +#ifndef __CORE_THREAD_NGPS_T_THREAD_H__ +#define __CORE_THREAD_NGPS_T_THREAD_H__ + +#include +#include + +namespace Thread { + + + typedef int THREADID; // Id for use and assigned by application + typedef int OSTHREADID; // Id assigned by the OS. + typedef unsigned int HTHREAD; // A value that is easy to determine from the OS + + class PerThreadStruct { + public: + char m_cID[16]; // a text name so its easy to + THREADID m_utid; // an id that the use can assign + OSTHREADID m_osId; // an id that the OS assigns + void* m_pEntry; + void* m_pStackBase; + int m_iStackSize; + int m_iInitialPriority; + void* m_pRpcData; // ptr to rpc specific data. + }; + + //inline void StartThread( HTHREAD hthr, void* arg = NULL ) { ::StartThread( ((PerThreadStruct*)hthr)->m_osId, arg ); } + // So you must set up the stack, and the perthreadstruct. + HTHREAD CreateThread( PerThreadStruct* pPTS ); + + /* + Commentary on data for submodules (eg m_pRpcData): + Yes there are less data dependent ways of doing this. For example, + by having an array of pointers and the subsystem requests an index + that it can use. But the extra work (small tho it is) seems a bit + pointless when the penalty for hardcoding it is having to recompile + lots of things if it changes. Not a big deal. + */ + extern int dummy; +}; + +struct PTS{ + char m_cID[16]; // a text name so its easy to + Thread::THREADID m_utid; // an id that the use can assign + Thread::OSTHREADID m_osId; // an id that the OS assigns + void* m_pEntry; + void* m_pStackBase; + int m_iStackSize; + int m_iInitialPriority; + void* m_pRpcData; // ptr to rpc specific data. +}; + +extern "C" Thread::PerThreadStruct _rootThreadStruct; + +#endif // __CORE_THREAD_NGPS_T_THREAD_H__ diff --git a/Code/Core/Thread/wn32/t_thread.cpp b/Code/Core/Thread/wn32/t_thread.cpp new file mode 100644 index 0000000..4238be4 --- /dev/null +++ b/Code/Core/Thread/wn32/t_thread.cpp @@ -0,0 +1,36 @@ +/******************************************************************* +* +* DESCRIPTION: t_target.h +* +* AUTHOR: JAB +* +* HISTORY: +* +* DATE:9/5/2000 +* + + This is a dummy file, because its in the NGPS version + it has to be here too. + +*******************************************************************/ + +/** include files **/ + +/** local definitions **/ + +/* default settings */ + +/** external functions **/ + +/** external data **/ + +/** internal functions **/ + +/** public data **/ + +/** private data **/ + +/** public functions **/ + +/** private functions **/ + diff --git a/Code/Core/Thread/wn32/t_thread.h b/Code/Core/Thread/wn32/t_thread.h new file mode 100644 index 0000000..4238be4 --- /dev/null +++ b/Code/Core/Thread/wn32/t_thread.h @@ -0,0 +1,36 @@ +/******************************************************************* +* +* DESCRIPTION: t_target.h +* +* AUTHOR: JAB +* +* HISTORY: +* +* DATE:9/5/2000 +* + + This is a dummy file, because its in the NGPS version + it has to be here too. + +*******************************************************************/ + +/** include files **/ + +/** local definitions **/ + +/* default settings */ + +/** external functions **/ + +/** external data **/ + +/** internal functions **/ + +/** public data **/ + +/** private data **/ + +/** public functions **/ + +/** private functions **/ + diff --git a/Code/Core/TimestampedFlag.h b/Code/Core/TimestampedFlag.h new file mode 100644 index 0000000..6d427b8 --- /dev/null +++ b/Code/Core/TimestampedFlag.h @@ -0,0 +1,173 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Standard Header ** +** ** +** File name: core/TimestampedFlag.h ** +** ** +** Created: 3/3/3 - Dan ** +** ** +*****************************************************************************/ + +#ifndef __CORE_TIMESTAMPEDFLAG_H +#define __CORE_TIMESTAMPEDFLAG_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/************************************************************************ + * * + * Class: TimestampedFlag * + * * + * Description: Flag which remembers the time of its last * + * modification * + * Extracted from skater.h * + * * + * * + ***********************************************************************/ + +class CTimestampedFlag : public Spt::Class +{ +public: + void Set ( bool state ); + void SetTrue ( ); + void SetFalse ( ); + void Toggle ( ); + + bool Get ( ); + Tmr::Time GetTime ( ); + Tmr::Time GetElapsedTime ( ); + + operator bool ( ) { return Get(); } + +private: + bool m_state; // flag state + Tmr::Time m_time; // time of last state change +}; + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void CTimestampedFlag::Set ( bool state ) +{ + if (m_state == state) + { + return; + } + + m_state = state; + m_time = Tmr::GetTime(); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void CTimestampedFlag::SetTrue ( ) +{ + Set(true); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void CTimestampedFlag::SetFalse ( ) +{ + Set(false); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline void CTimestampedFlag::Toggle ( ) +{ + Set(!m_state); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline bool CTimestampedFlag::Get ( ) +{ + return m_state; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Tmr::Time CTimestampedFlag::GetTime ( ) +{ + return m_time; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +inline Tmr::Time CTimestampedFlag::GetElapsedTime ( ) +{ + return Tmr::GetTime() - GetTime(); + +} + +#endif // __CORE_TIMESTAMPEDFLAG_H + + + + + diff --git a/Code/Core/compress.cpp b/Code/Core/compress.cpp new file mode 100644 index 0000000..9227035 --- /dev/null +++ b/Code/Core/compress.cpp @@ -0,0 +1,392 @@ +#include + +#define N 4096 /* size of ring buffer */ +#define F 18 /* upper limit for match_length */ +#define THRESHOLD 2 /* encode string into position and length + if match_length is greater than this */ +#define NIL N /* index for root of binary search trees */ + +unsigned long int +textsize = 0, /* text size counter */ +codesize = 0, /* code size counter */ +printcount = 0; /* counter for reporting progress every 1K bytes */ + + +//unsigned char text_buf[N + F - 1]; //ring buffer of size N, with extra F-1 bytes to facilitate string comparison +int match_position, match_length; // of longest match. These are set by the InsertNode() procedure. +//int lson[N + 1], rson[N + 257], dad[N + 1]; // left & right children & parents -- These constitute binary search trees. + + +unsigned char *text_buf; +int *lson; +int *rson; +int *dad; + + +#define readc() *pIn++ +#define writec(x) *pOut++ = x + + +void InitTree(void) /* initialize trees */ +{ + int i; + + Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap()); + text_buf = new unsigned char[N + F - 1]; + lson = new int[N+1]; + rson = new int[N+257]; + dad = new int[N+1]; + Mem::Manager::sHandle().PopContext(); //Mem::Manager::sHandle().TopDownHeap()); + + + + /* For i = 0 to N - 1, rson[i] and lson[i] will be the right and + left children of node i. These nodes need not be initialized. + Also, dad[i] is the parent of node i. These are initialized to + NIL (= N), which stands for 'not used.' + For i = 0 to 255, rson[N + i + 1] is the root of the tree + for strings that begin with character i. These are initialized + to NIL. Note there are 256 trees. */ + + for ( i = N + 1; i <= N + 256; i++ ) rson[i] = NIL; + for ( i = 0; i < N; i++ ) dad[i] = NIL; +} + +void DeInitTree(void) /* free up the memory */ +{ + delete [] text_buf; + delete [] lson; + delete [] rson; + delete [] dad; + +} + +void InsertNode(int r) +/* Inserts string of length F, text_buf[r..r+F-1], into one of the + trees (text_buf[r]'th tree) and returns the longest-match position + and length via the global variables match_position and match_length. + If match_length = F, then removes the old node in favor of the new + one, because the old one will be deleted sooner. + Note r plays double role, as tree node and position in buffer. */ +{ + int i, p, cmp; + unsigned char *key; + + cmp = 1; key = &text_buf[r]; p = N + 1 + key[0]; + rson[r] = lson[r] = NIL; match_length = 0; + for ( ; ; ) + { + if ( cmp >= 0 ) + { + if ( rson[p] != NIL ) + { + p = rson[p]; + } + else + { + rson[p] = r; dad[r] = p; return; + } + } + else + { + if ( lson[p] != NIL ) + { + p = lson[p]; + } + else + { + lson[p] = r; dad[r] = p; return; + } + } + + for ( i = 1; i < F; i++ ) + { + if ( (cmp = key[i] - text_buf[p + i]) != 0 ) break; + } + + if ( i > match_length ) + { + match_position = p; + if ( (match_length = i) >= F ) + { + break; + } + } + } + dad[r] = dad[p]; lson[r] = lson[p]; rson[r] = rson[p]; + dad[lson[p]] = r; dad[rson[p]] = r; + if ( rson[dad[p]] == p ) + { + rson[dad[p]] = r; + } + else + { + lson[dad[p]] = r; + } + dad[p] = NIL; /* remove p */ +} + +void DeleteNode(int p) /* deletes node p from tree */ +{ + int q; + + if ( dad[p] == NIL ) return; /* not in tree */ + if ( rson[p] == NIL ) q = lson[p]; + else if ( lson[p] == NIL ) q = rson[p]; + else + { + q = lson[p]; + if ( rson[q] != NIL ) + { + do + { + q = rson[q]; + } while ( rson[q] != NIL ); + rson[dad[q]] = lson[q]; dad[lson[q]] = dad[q]; + lson[q] = lson[p]; dad[lson[p]] = q; + } + rson[q] = rson[p]; dad[rson[p]] = q; + } + dad[q] = dad[p]; + if ( rson[dad[p]] == p ) rson[dad[p]] = q; + else lson[dad[p]] = q; + dad[p] = NIL; +} + +int Encode(char *pIn, char *pOut, int bytes_to_read, bool print_progress) +{ + int i, c, len, r, s, last_match_length, code_buf_ptr; + unsigned char code_buf[17], mask; + + textsize = 0; /* text size counter */ + codesize = 0; /* code size counter */ + printcount = 0; /* counter for reporting progress every 1K bytes */ + + InitTree(); /* initialize trees */ + code_buf[0] = 0; /* code_buf[1..16] saves eight units of code, and + code_buf[0] works as eight flags, "1" representing that the unit + is an unencoded letter (1 byte), "0" a position-and-length pair + (2 bytes). Thus, eight units require at most 16 bytes of code. */ + code_buf_ptr = mask = 1; + s = 0; r = N - F; + for ( i = s; i < r; i++ ) text_buf[i] = ' '; /* Clear the buffer with + any character that will appear often. */ + for ( len = 0; len < F && bytes_to_read; len++ ) + { + c = readc(); + bytes_to_read--; + text_buf[r + len] = c; /* Read F bytes into the last F bytes of + the buffer */ + } + if ( (textsize = len) == 0 ) + { + DeInitTree(); + return 0; /* text of size zero */ + } + for ( i = 1; i <= F; i++ ) InsertNode(r - i); /* Insert the F strings, + each of which begins with one or more 'space' characters. Note + the order in which these strings are inserted. This way, + degenerate trees will be less likely to occur. */ + InsertNode(r); /* Finally, insert the whole string just read. The + global variables match_length and match_position are set. */ + do + { + if ( match_length > len ) match_length = len; /* match_length + may be spuriously long near the end of text. */ + if ( match_length <= THRESHOLD ) + { + match_length = 1; /* Not long enough match. Send one byte. */ + code_buf[0] |= mask; /* 'send one byte' flag */ + code_buf[code_buf_ptr++] = text_buf[r]; /* Send uncoded. */ + } + else + { + code_buf[code_buf_ptr++] = (unsigned char) match_position; + code_buf[code_buf_ptr++] = (unsigned char) + (((match_position >> 4) & 0xf0) + | (match_length - (THRESHOLD + 1))); /* Send position and + length pair. Note match_length > THRESHOLD. */ + } + if ( (mask <<= 1) == 0 ) + { /* Shift mask left one bit. */ + for ( i = 0; i < code_buf_ptr; i++ ) /* Send at most 8 units of */ + writec(code_buf[i]); /* code together */ + codesize += code_buf_ptr; + code_buf[0] = 0; code_buf_ptr = mask = 1; + } + last_match_length = match_length; + for ( i = 0; i < last_match_length && + bytes_to_read; i++ ) + { + c = readc(); + bytes_to_read--; + DeleteNode(s); /* Delete old strings and */ + text_buf[s] = c; /* read new bytes */ + if ( s < F - 1 ) text_buf[s + N] = c; /* If the position is + near the end of buffer, extend the buffer to make + string comparison easier. */ + s = (s + 1) & (N - 1); r = (r + 1) & (N - 1); + /* Since this is a ring buffer, increment the position + modulo N. */ + InsertNode(r); /* Register the string in text_buf[r..r+F-1] */ + } + if ( (textsize += i) > printcount ) + { +// printf("%12ld\r", textsize); printcount += 1024; + /* Reports progress each time the textsize exceeds + multiples of 1024. */ + } + while ( i++ < last_match_length ) + { /* After the end of text, */ + DeleteNode(s); /* no need to read, but */ + s = (s + 1) & (N - 1); r = (r + 1) & (N - 1); + if ( --len ) InsertNode(r); /* buffer may not be empty. */ + } + } while ( len > 0 ); /* until length of string to be processed is zero */ + if ( code_buf_ptr > 1 ) + { /* Send remaining code. */ + for ( i = 0; i < code_buf_ptr; i++ ) writec(code_buf[i]); + codesize += code_buf_ptr; + } + + if ( print_progress ) + { + printf(" In : %ld bytes\n", textsize); /* Encoding is done. */ + printf(" Out: %ld bytes\n", codesize); + printf(" Out/In: %.3f\n", (double)codesize / textsize); + } + + DeInitTree(); + return codesize; +} + + +/////////////////////////////////////////////////////////////////////////////////////// +// +// Decompression code, cut & pasted from pre.cpp +// +/////////////////////////////////////////////////////////////////////////////////////// + +#define RINGBUFFERSIZE 4096 /* N size of ring buffer */ +#define MATCHLIMIT 18 /* F upper limit for match_length */ +#define THRESHOLD 2 /* encode string into position and length */ + +//#define WriteOut(x) {Dbg_MsgAssert(pOut>= 1) & 256) == 0 ) + { + ReadInto(c); + flags = c | 0xff00; /* uses higher byte cleverly */ + } /* to count eight */ + if ( flags & 1 ) + { + ReadInto(c); + // putc(c, outfile); + WriteOut(c); +#if USE_BUFFER + sTextBuf[r++] = c; + r &= (RINGBUFFERSIZE - 1); +#else + r++; +// r &= (RINGBUFFERSIZE - 1); // don't need to wrap r until it is used +#endif + } + else + { + ReadInto(i); + ReadInto2(j); // note, don't need to check len on this one.... + + i |= ((j & 0xf0) << 4); // i is 12 bit offset + +#if !USE_BUFFER + j = (j & 0x0f) + THRESHOLD+1; // j is 4 bit length (above the threshold) + unsigned char *pStream; + r &= (RINGBUFFERSIZE - 1); // wrap r around before it is used + pStream = pOut - r; // get base of block + if ( i>=r ) // if offset > r, then + pStream -= RINGBUFFERSIZE; // it's the previous block + pStream += i; // add in the offset to the base + r+=j; // add size to r + while ( j-- ) // copy j bytes + WriteOut(*pStream++); +#else + + j = (j & 0x0f) + THRESHOLD; // j is 4 bit length (above the threshold) + for ( k = 0; k <= j; k++ ) // just copy the bytes + { + c = sTextBuf[(i+k) & (RINGBUFFERSIZE - 1)]; + WriteOut(c); + sTextBuf[r++] = c; + r &= (RINGBUFFERSIZE - 1); + } +#endif + } + } + +// int Time = (int) Tmr::ElapsedTime(basetime); +// if (Time > 5) +// { +// printf("decomp time is %d ms, for %d bytes, %d bytes/second\n", Time,len, len * 1000 /Time ); +// } + return pOut; +} + diff --git a/Code/Core/compress.h b/Code/Core/compress.h new file mode 100644 index 0000000..a3ea5aa --- /dev/null +++ b/Code/Core/compress.h @@ -0,0 +1,12 @@ +#ifndef __CORE_COMPRESS_H +#define __CORE_COMPRESS_H + +#ifndef __CORE_DEFINES_H +#include +#endif + +int Encode(char *pIn, char *pOut, int bytes_to_read, bool print_progress); +unsigned char *DecodeLZSS(unsigned char *pIn, unsigned char *pOut, int Len); + +#endif + diff --git a/Code/Core/crc.cpp b/Code/Core/crc.cpp new file mode 100644 index 0000000..a6302bb --- /dev/null +++ b/Code/Core/crc.cpp @@ -0,0 +1,338 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment. ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: PS2 ** +** ** +** Module: Core ** +** ** +** File name: Core/crc.cpp ** +** ** +** Created by: 03/05/01 - spg ** +** ** +** Description: Crc functionality ** +** ** +*****************************************************************************/ + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + +/***************************************************************************** +** DBG Information ** +*****************************************************************************/ + + + +namespace Crc +{ + + + +/***************************************************************************** +** Externals ** +*****************************************************************************/ + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +/***************************************************************************** +** Private Types ** +*****************************************************************************/ + +/***************************************************************************** +** Private Data ** +*****************************************************************************/ + +static uint32 CRCTable[256] = // CRC polynomial 0xedb88320 +{ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +/***************************************************************************** +** Public Data ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Private Functions ** +*****************************************************************************/ + +/***************************************************************************** +** Public Functions ** +*****************************************************************************/ + +//////////////////////////////////////////////////////////////////////////////// +// Generates a checksum from raw data, given pointer to that data, and the size +// +// input: +// const char *stream = pointer to data +// int size = number of bytes to CRC +// +// output: +// returns uint32 = checksum of the data +// if NULL pointer passed in, or zero size is given, then will return 0 +// +uint32 GenerateCRCCaseSensitive( const char *stream, int size ) +{ + + + // A checksum of zero is used to mean no name. + if(( !stream ) || ( size <= 0 )) + { + return 0; + } + + // Initializing the CRC to all one bits avoids failure of detection + // should entire data stream get cyclically bit-shifted by one position. + // The calculation of the probability of this happening is left as + // an exercise for the reader. + uint32 rc = 0xffffffff; + for (int i=0; i>8) & 0x00ffffff); + } + + return rc; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +// For use when calculating a total checksum of a bunch of chunks of data. +// Currently used when saving out the replay buffer to mem card. +uint32 UpdateCRC( const char *p_stream, int size, uint32 rc ) +{ + Dbg_MsgAssert(p_stream,("NULL p_stream")); + + for (int i=0; i>8) & 0x00ffffff); + } + + return rc; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +// Generates a checksum from a name, case insensitive. +uint32 GenerateCRC( const char *stream, int size ) +{ + + + // A checksum of zero is used to mean no name. + if(( !stream ) || ( size <= 0 )) + { + return 0; + } + + // Initializing the CRC to all one bits avoids failure of detection + // should entire data stream get cyclically bit-shifted by one position. + // The calculation of the probability of this happening is left as + // an exercise for the reader. + uint32 rc = 0xffffffff; + for (int i=0; i='A' && ch<='Z') + { + ch='a'+ch-'A'; + } + // Convert forward slashes to backslashes, otherwise two filenames which are + // effectively the same but with different slashes will give different checksums. + if (ch=='/') + { + ch='\\'; + } + + rc = CRCTable[(rc^ch) & 0xff] ^ ((rc>>8) & 0x00ffffff); + } + + return rc; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +uint32 GenerateCRCFromString( const char *pName ) +{ + + // A checksum of zero is used to mean no name. + if (!pName) + { + return 0; + } + + // Initializing the CRC to all one bits avoids failure of detection + // should entire data stream get cyclically bit-shifted by one position. + // The calculation of the probability of this happening is left as + // an exercise for the reader. + uint32 rc = 0xffffffff; + const char *pCh=pName; + while (true) + { + char ch=*pCh++; + if (!ch) + { + break; + } + + // Convert to lower case. + if (ch>='A' && ch<='Z') + { + ch='a'+ch-'A'; + } + // Convert forward slashes to backslashes, otherwise two filenames which are + // effectively the same but with different slashes will give different checksums. + if (ch=='/') + { + ch='\\'; + } + + rc = CRCTable[(rc^ch) & 0xff] ^ ((rc>>8) & 0x00ffffff); + } + + return rc; +} + + +// Same as above, but it's calculating the CRC of two strings added together +// but the first string is already CRCed +// essentially it's like jumping into GenerateGRCFromString halfway through +uint32 ExtendCRCWithString( uint32 rc, const char *pName ) +{ + + if (!pName) + { + return rc; + } + + const char *pCh=pName; + while (true) + { + char ch=*pCh++; + if (!ch) + { + break; + } + + // Convert to lower case. + if (ch>='A' && ch<='Z') + { + ch='a'+ch-'A'; + } + // Convert forward slashes to backslashes, otherwise two filenames which are + // effectively the same but with different slashes will give different checksums. + if (ch=='/') + { + ch='\\'; + } + + rc = CRCTable[(rc^ch) & 0xff] ^ ((rc>>8) & 0x00ffffff); + } + + return rc; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +uint32 GetCRCTableEntry( int entry ) +{ + + + Dbg_Assert(( entry < 256 ) && ( entry >= 0 )); + + return CRCTable[entry]; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +} // namespace Crc + diff --git a/Code/Core/crc.h b/Code/Core/crc.h new file mode 100644 index 0000000..f128e8e --- /dev/null +++ b/Code/Core/crc.h @@ -0,0 +1,73 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Crc (CRC) ** +** ** +** Created: 03/05/01 spg ** +** ** +** File name: core/crc.h ** +** ** +** Description: Crc Functionality ** +** ** +*****************************************************************************/ + +#ifndef __CORE_CRC_H +#define __CORE_CRC_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#ifndef __CORE_DEFINES_H + #include +#endif + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Crc +{ + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + +uint32 GenerateCRC( const char *stream, int size ); +uint32 GenerateCRCCaseSensitive( const char *stream, int size ); +uint32 UpdateCRC( const char *p_stream, int size, uint32 rc=0xffffffff ); +uint32 GenerateCRCFromString( const char *pName ); +uint32 ExtendCRCWithString( uint32 rc, const char *pName ); +uint32 GetCRCTableEntry( int entry ); + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +} // namespace Crc + +#endif // __CORE_CRC_H + diff --git a/Code/Core/flags.h b/Code/Core/flags.h new file mode 100644 index 0000000..76acc38 --- /dev/null +++ b/Code/Core/flags.h @@ -0,0 +1,334 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Standard Header ** +** ** +** File name: core/flags.h ** +** ** +** Created: 10/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_FLAGS_H +#define __CORE_FLAGS_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + +/************************************************************************ + * * + * Class: flags * + * * + * Description: Template class for flags. * + * * + * * + ***********************************************************************/ + +template < class _T > +class Flags +{ + +public: + + Flags ( void ); + Flags ( const uint val ); + + void ClearAll ( void ); + void Clear ( _T flag_index ); + void ClearMask ( const uint& f ); + void Set ( _T flag_index, bool on = true ); + void SetAll ( void ); + void SetMask ( const uint& mask ); + void SetVal ( const uint& f ); + bool TestAny ( void ) const; + bool Test ( _T flag_index ) const; + bool TestMask ( const uint& mask ) const; + bool TestMaskAny ( const uint& mask ) const; + bool TestMaskAll ( const uint& mask ) const; + void Toggle ( _T flag_index ); + + operator uint (void) const; + Flags& operator= ( const Flags& src ); + Flags& operator= ( const uint& val ); + +private: + + uint flag; +}; + + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + +template < class _T > inline +Flags< _T >::Flags ( void ) +: flag ( 0 ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + + +template < class _T > inline +Flags< _T >::Flags ( const uint val ) +: flag ( val ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::ClearAll ( void ) +{ + flag = 0; +} + + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::Clear ( _T flag_index ) +{ + flag &= ~( 1 << static_cast< uint >( flag_index )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::ClearMask ( const uint& f ) +{ + flag &= ~f; +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::Set ( _T flag_index, bool on ) +{ + if ( on ) + { + flag |= ( 1 << static_cast< uint >( flag_index )); + } + else + { + flag &= ~( 1 << static_cast< uint >( flag_index )); + } +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::SetAll ( void ) +{ + flag = vUINT_MAX; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::SetMask ( const uint& mask ) +{ + flag |= mask; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::SetVal ( const uint& f ) +{ + flag = f; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +bool Flags< _T >::TestAny ( void ) const +{ + return ( flag != 0 ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +bool Flags< _T >::Test ( _T flag_index ) const +{ + return (( flag & ( 1 << static_cast< uint >( flag_index ))) != 0 ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +bool Flags< _T >::TestMask ( const uint& mask ) const +{ + return TestMaskAny(mask); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +bool Flags< _T >::TestMaskAny ( const uint& mask ) const +{ + return (( flag & mask ) != 0 ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +bool Flags< _T >::TestMaskAll ( const uint& mask ) const +{ + return (( flag & mask ) == mask ); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +void Flags< _T >::Toggle ( _T flag_index ) +{ + flag ^= ( 1 << static_cast< uint >( flag_index )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Flags< _T >::operator uint (void) const +{ + return flag; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Flags< _T >& Flags< _T >::operator= ( const Flags< _T >& src ) +{ + flag = static_cast< uint >( src.flag ); + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +Flags< _T >& Flags< _T >::operator= ( const uint& val ) +{ + flag = val; + + return *this; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +/* +template < class _T > inline +ostream& operator<< ( ostream& os, const Flags< _T >& src ) +{ + return os << static_cast< uint >( src ); +} +*/ + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ +#endif // __CORE_FLAGS_H + + + + + diff --git a/Code/Core/glue.h b/Code/Core/glue.h new file mode 100644 index 0000000..68ac05d --- /dev/null +++ b/Code/Core/glue.h @@ -0,0 +1,5 @@ +#ifndef __GLUE_H__ +#define __GLUE_H__ + + +#endif // __GLUE_H__ diff --git a/Code/Core/log.h b/Code/Core/log.h new file mode 100644 index 0000000..9f75e26 --- /dev/null +++ b/Code/Core/log.h @@ -0,0 +1,11 @@ +#ifndef __CORE_DEBUG_LOG_H +#define __CORE_DEBUG_LOG_H + +namespace Log +{ +void Init(); +void AddEntry(char *p_fileName, int lineNumber, char *p_functionName, char *p_message=NULL); +} + +#endif // #ifndef __CORE_DEBUG_LOG_H + diff --git a/Code/Core/macros.h b/Code/Core/macros.h new file mode 100644 index 0000000..517e314 --- /dev/null +++ b/Code/Core/macros.h @@ -0,0 +1,61 @@ +/* Useful little macros... + + Add any macros you want into this: +*/ + +#ifndef __USEFUL_LITTLE_MACROS_H__ +#define __USEFUL_LITTLE_MACROS_H__ + +#ifndef __CORE_DEFINES_H +#include +#endif + +#include + +#define PERCENT_MULT ( ( 1.0f ) / 100.0f ) +#define PERCENT( x, percent ) ( ( ( ( ( float )( x ) ) * ( ( float )( percent ) ) ) * PERCENT_MULT ) ) + +#define IPU (1.0f) // Inches per unit + +#define INCHES(x) ((float)(x)) +#define INCHES_PER_SECOND(x) ((float)(x)) +#define INCHES_PER_SECOND_SQUARED(x) ((float)(x)) + +#define FEET_TO_INCHES( x ) ( x * 12.0f ) +#define FEET(x) ((float)(x*12.0f)) + +#define FEET_PER_MILE 5280.0f +#define MPH_TO_INCHES_PER_SECOND( x ) ( ( ( x ) * FEET_PER_MILE * 12.0f ) / ( 60.0f * 60.0f ) ) +#define MPH_TO_IPS( x ) MPH_TO_INCHES_PER_SECOND( x ) +#define INCHES_PER_SECOND_TO_MPH( x ) ( ( x ) / 12.0f / FEET_PER_MILE * 60.0f * 60.0f ) +#define IPS_TO_MPH( x ) INCHES_PER_SECOND_TO_MPH( x ) + +#define RADIANS_PER_SECOND_TO_RPM( x ) (9.5496f * ( x )) +#define RPM_TO_RADIANS_PER_SECOND( x ) (0.10472f * ( x )) + +#define DEGREES_TO_RADIANS( x ) ( ( x ) * ( 2.0f * Mth::PI / 360.0f ) ) +#define RADIANS_TO_DEGREES( x ) ( ( x ) / ( DEGREES_TO_RADIANS( 1.0f ) ) ) +#define THE_NUMBER_OF_THE_BEAST 666 + + + +// Takes screen coordinates in 'default' screen space - 640x448 - and converts them based on PAL and system defines. + +// Convert from logical to SCREEN coordinates +#ifdef __PLAT_XBOX__ +// Unfortunately, this has to be a platform-specific macro, since it references Xbox specific global structures to determine +// the width and height of the back buffer. +#include +#define SCREEN_CONV_X( x ) ((( x ) * NxXbox::EngineGlobals.screen_conv_x_multiplier ) + NxXbox::EngineGlobals.screen_conv_x_offset ) +#define SCREEN_CONV_Y( y ) ((( y ) * NxXbox::EngineGlobals.screen_conv_y_multiplier ) + NxXbox::EngineGlobals.screen_conv_y_offset ) +#else +#define SCREEN_CONV_X( x ) (Config::GetHardware()==Config::HARDWARE_XBOX ? (int)((( x ) * ( 640.0f / 704.0f )) + 32 ) : Config::PAL() ? (( x ) * 512 ) / 640 : (x) ) +#define SCREEN_CONV_Y( y ) (Config::GetHardware()==Config::HARDWARE_XBOX ? (( y ) + 16 ) : Config::PAL() ? (( y ) * 512 ) / 448 : (y) ) +#endif // __PLAT_XBOX__ + +// Convert from screen to LOGICAL coordinates +#define LOGICAL_CONV_X( x ) (Config::GetHardware()==Config::HARDWARE_XBOX ? (x) : Config::PAL() ? (( x ) * 640 ) / 512 : (x) ) +#define LOGICAL_CONV_Y( y ) (Config::GetHardware()==Config::HARDWARE_XBOX ? (y) : Config::PAL() ? (( y ) * 448 ) / 512 : (y) ) + +#endif // __USEFUL_LITTLE_MACROS_H__ + diff --git a/Code/Core/math.h b/Code/Core/math.h new file mode 100644 index 0000000..f4f6c55 --- /dev/null +++ b/Code/Core/math.h @@ -0,0 +1,68 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Math (MTH) ** +** ** +** File name: core/math.h ** +** ** +** Created: 11/23/99 - mjb ** +** ** +** Description: Math Library ** +** ** +*****************************************************************************/ + +#ifndef __CORE_MATH_H +#define __CORE_MATH_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include + + +// The following overlaoded functions will make sure that if you accidently call a double version of +// a trig function with a float argument, then it will simply call the float version +// (double precision is very very slow on the PS2) +#ifndef __PLAT_XBOX__ +inline float acos( float x ) {return acosf( x );} +inline float asin( float x ) {return asinf( x );} +inline float atan( float x ) {return atanf( x );} +inline float atan2( float x, float y){return atan2f( x , y );} +inline float cos( float x ) {return cosf( x );} +inline float sin( float x ) {return sinf( x );} +inline float tan( float x ) {return tanf( x );} +inline float fabs( float x ) {return fabsf( x );} +#endif // ifndef __PLAT_XBOX__ + + + +#ifndef __CORE_DEFINES_H +#include +#endif +#include +#include +#include +#include +#include // need quat.h before matrix, but after vector (for the GetScalar function) + +#include +#include +#include +#include + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + + +#endif // __CORE_MATH_H diff --git a/Code/Core/singleton.h b/Code/Core/singleton.h new file mode 100644 index 0000000..74640ed --- /dev/null +++ b/Code/Core/singleton.h @@ -0,0 +1,365 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 2000 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Template Singleton class ** +** ** +** File name: core/singleton.h ** +** ** +** Created: 10/17/00 - mjb ** +** ** +** ** +*****************************************************************************/ + +#ifndef __CORE_SINGLETON_H +#define __CORE_SINGLETON_H + +/* + +Singletons +(Documentation by Mick) + +A singleton is a single instance of a class which is dynamically +allocated just once. + +To make a class such as CStats a singleton class, you do this: + + + class CStats + { + // Normal Class definition goes here... + ... + + // Constructor and destructor MUST BE PRIVATE, for it to be a singleton + private: + CStats( void ); + ~CStats( void ); + + // Macro to include the singleton code + DeclareSingletonClass( CStats ); + } + +This macro just places some static functions in the class, and a static pointer to +the single instance of class itself (initially NULL) + +You can then initialize the singleton by the line + + Spt::SingletonPtr< CStats > p_stats( true ); + +This is somewhere at a high level in the code (currently they are all in main()) + +The original design of our singletons had in mind that there would be reference counting +of the references tot he singleton, and when it was no longer referenced, it would +delete itself. So, in many places in the code, the singletons are reference by creating a +"Spt::SingletonPtr", which is a smart pointer to the instance of the singleton. + +For example: + + Spt::SingletonPtr< CStats > p_stats; + p_stats->Update(); + +In the ultimate version of this idea, the game would be comprised of many modules, +systems and subsystems. Each would be a singleton. Each could be dependent on +other singletons, but more that one module could depend on a particular singleton, +so you coudl not rely on a module to de-initialize the singletons it needed +as they might be needed elsewhere. + +So, each module would include a Spt::SingletonPtr for each of the modules that it +needed, so when a module was created it would increase the reference on the singletons +and create any that did not exists. Then when it shut down, the pointers would automatically +go out of scope, and any singleton that was not referenced would automatically be deleted, +so there would never be more systems active than there needed to be. + +However, it turned out that this was generally overkill. Most systems were initialized at the start, +and those that were not, we wanted to have control over the initialization of them. +The use of smart pointers here also obfuscated the code, and introduced a significant +execution overhead that was totally unnecessary. + +So, the prefered method to access a singleton now is: + + CStats::Instance()->Update(); + +This simply references the instance directly (as it's inline), and so +does no addition processing. It's easier to type as well. + + +*/ + + + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#ifndef __CORE_DEFINES_H +#include +#endif +#include + +#ifndef __SYS_MEM_MEMPTR_H +#include +#endif + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +namespace Spt +{ + +/***************************************************************************** +** Class Definitions ** +*****************************************************************************/ + + +template< class _T > +class SingletonPtr : public Spt::Class +{ + +public: + + SingletonPtr( bool create = false ); + virtual ~SingletonPtr( void ); + + +#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ )) + SingletonPtr( const SingletonPtr< _T > & rhs ); + SingletonPtr< _T >& operator= ( const SingletonPtr< _T >& rhs ); + +#else + +template < class _NewT > // template copy contructor + SingletonPtr( const SingletonPtr< _NewT >& rhs ); // needed to support inheritance correctly + +template < class _NewT > + SingletonPtr< _T >& operator= ( const SingletonPtr< _NewT >& rhs ); // template assignment operator + +#endif + + _T* operator-> () const; + _T& operator* () const; + +private: + + Mem::Ptr< _T > mp_instance; +}; + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +SingletonPtr< _T >::SingletonPtr( bool create ) +: mp_instance ( _T::sSgltnInstance( create ) ) +{ + +#ifdef __NOPT_FULL_DEBUG__ + SetName( const_cast< char* >( sClassNode()->GetName() )); +#endif +} + + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +SingletonPtr< _T >::~SingletonPtr() +{ + + + mp_instance->sSgltnDelete(); +} + +#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ )) + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +SingletonPtr< _T >::SingletonPtr ( const SingletonPtr< _T >& rhs ) +: mp_instance ( _T::sSgltnInstance() ) +{ + +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +SingletonPtr< _T >& SingletonPtr< _T >::operator= ( const SingletonPtr< _T >& rhs ) +{ + + + return *this; +} + +#else + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > template < class _NewT > inline +SingletonPtr< _T >::SingletonPtr< _T >( const SingletonPtr< _NewT >& rhs ) +: mp_instance ( _NewT::sSgltnInstance() ) +{ + + + Dbg_MsgAssert( false,( "Microsoft VC++ sucks - don't do this (yet)" )); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > template < class _NewT > inline +SingletonPtr< _T >& SingletonPtr< _T >::operator= ( const SingletonPtr< _NewT >& rhs ) +{ + + + Dbg_MsgAssert( false,( "Microsoft VC++ sucks - don't do this (yet)" )); + + return *this; +} + +#endif + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +_T* SingletonPtr< _T >::operator-> () const +{ + + + return mp_instance.Addr(); +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +template < class _T > inline +_T& SingletonPtr< _T >::operator* () const +{ + + + return *mp_instance; +} + +/******************************************************************/ +/* */ +/* */ +/******************************************************************/ + +#define DeclareSingletonClass(_T) \ +private: \ + static _T* sSgltnInstance( bool create = false ); \ + static void sSgltnDelete( void ); \ + \ + static _T* sp_sgltn_instance; \ + static uint s_sgltn_count; \ +public: \ + inline static void Create(void) {sp_sgltn_instance = new _T;} \ + inline static _T* Instance(void) {return sp_sgltn_instance;} \ + inline bool Initilized(void) {return (sp_sgltn_instance!=NULL);} \ + \ + friend class Spt::SingletonPtr< _T >; \ + + +#define DefinePlacementSingletonClass(_T, _P, _N ) \ + \ +_T* _T::sp_sgltn_instance = NULL; \ +uint _T::s_sgltn_count = 0; \ + \ +_T* _T::sSgltnInstance( bool create ) \ +{ \ + \ + \ + if ( !sp_sgltn_instance && create ) \ + { \ + sp_sgltn_instance = new (_P) _T; \ + } \ + \ + Dbg_AssertType( sp_sgltn_instance, _T ); \ + \ + ++s_sgltn_count; \ + return sp_sgltn_instance; \ +} \ + \ +void _T::sSgltnDelete( void ) \ +{ \ + \ + \ + Dbg_MsgAssert(( s_sgltn_count > 0 ),( "Reference count imbalance" )); \ + \ + if ( s_sgltn_count > 1 ) \ + { \ + s_sgltn_count--; \ + return; \ + } \ + \ + delete sp_sgltn_instance; \ + sp_sgltn_instance = NULL; \ + s_sgltn_count = 0; \ +} \ + + +#define DefineSingletonClass(_T,_N) \ + DefinePlacementSingletonClass(_T, (Mem::Allocator*)NULL, _N ) + +/***************************************************************************** +** Private Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Private Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Declarations ** +*****************************************************************************/ + + +/***************************************************************************** +** Public Prototypes ** +*****************************************************************************/ + + +/***************************************************************************** +** Inline Functions ** +*****************************************************************************/ + + +/***************************************************************************** +** Macros ** +*****************************************************************************/ + +} // namespace Spt + +#endif // __CORE_SINGLETON_H + + + + + diff --git a/Code/Core/support.h b/Code/Core/support.h new file mode 100644 index 0000000..1169ffe --- /dev/null +++ b/Code/Core/support.h @@ -0,0 +1,40 @@ +/***************************************************************************** +** ** +** Neversoft Entertainment ** +** ** +** Copyright (C) 1999 - All Rights Reserved ** +** ** +****************************************************************************** +** ** +** Project: Core Library ** +** ** +** Module: Support (SPT) ** +** ** +** File name: core/support.h ** +** ** +** Created: 05/27/99 - mjb ** +** ** +*****************************************************************************/ + +#ifndef __CORE_SUPPORT_H +#define __CORE_SUPPORT_H + +/***************************************************************************** +** Includes ** +*****************************************************************************/ + +#include +#include +#include "sk/language.h" + +/***************************************************************************** +** Defines ** +*****************************************************************************/ + +// #define PAL for PAL mode build. +#define PALx + + +#endif // __CORE_TASK_H + + diff --git a/Code/Core/thread.h b/Code/Core/thread.h new file mode 100644 index 0000000..ba5f9c2 --- /dev/null +++ b/Code/Core/thread.h @@ -0,0 +1,32 @@ +/******************************************************************* +* +* DESCRIPTION: thread.h +* +* AUTHOR: JAB +* +* HISTORY: +* +* DATE:9/1/2000 +* +*******************************************************************/ + +/** include files **/ +#ifndef __CORE_DEFINES_H +#include +#endif + +#ifdef __PLAT_WN32__ +#else +# ifdef __PLAT_XBOX__ +# else +# ifdef __PLAT_NGPS__ +# include "thread/ngps/t_thread.h" +# elif defined( __PLAT_NGC__ ) +# include "thread/ngc/t_thread.h" +# else +# error Unsupported Platform +# endif +# endif +#endif + + diff --git a/Code/GameFlow.txt b/Code/GameFlow.txt new file mode 100644 index 0000000..57eff59 --- /dev/null +++ b/Code/GameFlow.txt @@ -0,0 +1,147 @@ +Flow of control +===================================== + +"autolaunch level=Load_Sch game=career" + +script autolaunch + script request_level level= + ScriptRequestLevel + -set Skate::m_requested_level + script cleanup_before_loading_level + ScriptCleanup + -skate_mod->Cleanup( ) + -skate_mod->GetGoalManager()->RemoveAllGoals() + -particle_manager->DestroyAllSystems(); + -ParticleFX::BloodSplatDestroy(); + -SkateScript::ClearNodeNameHashTable(); + -ParticleFX::DestroyTerrainEffectProperties(); + -gfx_man->DisableFog(); + -Script::KillStoppedScripts(); + -sk3_sfx_manager->Reset( ); + + -sfx_manager->CleanUp( ); + + -ass_manager->UnloadAllTables( ); + + -Script::CSymbolTableEntry *p_sym=Script::LookUpSymbol("NodeArray"); + -if (p_sym) + SkateScript::UnloadQB(p_sym->mSourceFileNameChecksum); + + -SkateScript::UnloadQB(s_level_specific_qb); + -ScriptUnloadAllLevelGeometry(NULL,NULL); + + -skate_mod->UnloadSkaters(); + -Mem::Manager::sHandle().DeleteSkaterHeaps(); + + script load_requested_level level=load_sch + script LoadLevel + script PreLevelLoad + -does nothing + ScriptLaunchLevel level=load_sch + -Skate::OpenLevel() + -Script::RunScript(level_script); + script Load_Sch + script load_level level_sch + -see below.... + PostLevelLoad + -does nothing + gameflow StandardGameFlow + skate_mod->mp_gameFlow->Reset( ScriptChecksum ) + -set m_requestedScript + +Later... + +CGameFlow::Update() + GameFlow_Startup + -reset panel, sound stuff + -maybe enter free skate in network game + -choose game mode + -Put skaters in world + -Set screen mode + -Run intro script + -repeat: + GameFlow_StartRun + GameFlow_PlayRun + GameFlow_WaitEnd + -and other shit + +===================================== + +----------------------- +"load_level level_sch" + +Level_Sch = { + loading_screen = "loadscrn_generic" + pre = "sch.pre" + scnpre = "schscn.pre" + Level = "sch" + Sky = "sf2_Sky" + qb = "levels\sch\sch.qb" + colpre = "schcol.pre" + level_qb = "levels\sch\sch_scripts.qb" + startup_script = sch_Startup + default_stats = 5 +} + +Level_Sk4Ed = { + loading_screen = "loadscrn_generic" + pre = "sk4ed.pre" + scnpre = "sk4edscn.pre" + Level = "sk4ed" + Sky = "can_Sky" + qb = "levels\sk4ed\sk4ed.qb" + colpre = "sk4edcol.pre" + level_qb = "levels\sk4ed\sk4ed_scripts.qb" + startup_script = Sk4Ed_Startup + default_stats = 8 + level_number = LevelNum_Lon + park_editor +} +----------------------- + + +script load_level level_sch + DisplayLoadingScreen + + *** Park editor yes *** + script LoadLevelPreFile + ScriptLoadPreFile + *** Park editor, need special flag *** + ScriptLoadScene scene= (for sky, then world) + Nx::CTexDictManager::sLoadTextureDictionary(p_scene_name) + Nx::CEngine::sLoadScene(p_scene_name, p_tex_dict); + ScriptUnloadPreFile + + *** Park editor maybe *** + script LoadLevelPreFile + ScriptLoadCollision scene= + p_scene->LoadCollision(p_scene_name) + ScriptUnloadPreFile + + *** Park editor yes *** + script LoadLevelPreFile
+	ScriptLoadQB 
+		SkateScript::LoadQB
+			Script::LoadQB
+				-load it into memory
+				ParseQB()
+				-restart all scripts referring to symbols
+			-do symbol table processing
+				-do following just for NodeArray symbol
+				- can reference nodes by name or prefix
+				CreateNodeNameHashTab
+				GeneratePrefixInfo();
+	*** don't call ScriptParseNodeArray for park editor, I don't think ***
+	*** duplicate some functionality ***
+	ScriptLoadNodeArray 
+		SkateScript::LoadQB (see above)
+		ScriptParseNodeArray
+			-does a lot of stuff...
+	LoadTerrain
+		-doesn't seem to exist
+	CC_Startup
+		-loads some test camera stuff
+	run  (sch_startup)       
+	ScriptUnloadPreFile
+
+
diff --git a/Code/Gel/AssMan/AssMan.cpp b/Code/Gel/AssMan/AssMan.cpp
new file mode 100644
index 0000000..2c78f0f
--- /dev/null
+++ b/Code/Gel/AssMan/AssMan.cpp
@@ -0,0 +1,898 @@
+//****************************************************************************
+//* MODULE:         Gel/AssMan
+//* FILENAME:       AssMan.cpp
+//* OWNER:          Matt Duncan
+//* CREATION DATE:  11/17/2000
+//****************************************************************************
+
+// start autoduck documentation
+// @DOC Assman
+// @module Assman | None
+// @subindex Scripting Database
+// @index script | Assman
+
+/*
+
+	Assets are generally binary files that are processed in some way when loaded
+	(although they might simply be loaded).
+	
+	They are always referenced by the 32-bit checksum of the name of the file
+
+	Design and art guidelines already require that all filenames in the project 
+	are unique names.
+
+	The asset manager should handle generic assets...  (it shouldn't need
+	to know anything about texture dictionaries or clumps or anims...)
+*/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 							
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Ass
+{
+
+DefineSingletonClass( CAssMan, "Shared Asset Manager" );
+
+struct SAssetLookup
+{
+   	char*		p_extension;
+	EAssetType	type;
+};
+
+static SAssetLookup s_asset_lookup[] = 
+{
+	{"BIN",		ASSET_BINARY},
+	{"CLD",		ASSET_COLLISION},
+	{"SCN",		ASSET_SCENE},
+	{"MDL",		ASSET_SKIN},
+	{"SKA",		ASSET_ANIM},
+	{"FAM",		ASSET_ANIM}, 	// facial anims
+    {"SKE",		ASSET_SKELETON},
+	{"SKIN",	ASSET_SKIN},
+	{"TEX",		ASSET_TEXTURES},
+	{"CUT",		ASSET_CUTSCENE},
+	{"QB",		ASSET_NODEARRAY},
+	
+// Insert new types above this line	
+	{NULL,		ASSET_UNKNOWN}		   				// terminator;
+};
+	
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAssMan::CAssMan()
+{
+	mp_asset_table = new Lst::HashTable( 10 );
+
+	// for debugging
+	mp_assetlist_head = new CAsset;
+	mp_assetlist_head->SetText("Asset List Head (Possible error here?)");	
+	
+	mp_assetlist_head->mp_next = mp_assetlist_head;
+	mp_assetlist_head->mp_prev = mp_assetlist_head;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAssMan::~CAssMan()
+{	
+	UnloadAllTables( true );
+
+	delete mp_asset_table;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAssetType CAssMan::FindAssetType( const char *p_assetName )
+{
+	// given a file name, then find the type of asset that this represents
+	
+	// the asset type is determined by the file extension
+	// so first, find the last . in the assetName
+	const char *p_ext = p_assetName + strlen(p_assetName);
+	while ( p_ext > p_assetName && *p_ext != '.' )
+	{
+		p_ext--;
+	}
+	
+	if ( *p_ext != '.' )
+	{
+		return ASSET_UNKNOWN;
+	}
+	
+	p_ext++;
+	// p_ext now points to the extension for the file name
+
+	SAssetLookup *p_lookup = &s_asset_lookup[0];
+	while ( p_lookup->p_extension != NULL )
+	{
+		// note, ignoring case
+		if ( strcmpi( p_lookup->p_extension, p_ext ) == 0 )	
+		{
+			return p_lookup->type;
+		}
+		p_lookup++;
+	}
+	
+	return ASSET_UNKNOWN;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void* CAssMan::LoadAssetFromStream(uint32 asset_name, uint32 asset_type, uint32* p_data, int data_size, bool permanent, uint32 group)
+{
+	// Note:  There's no reason to support async loads when
+	// loading an asset from a data stream...
+
+	// load an asset, and return a pointer to the asset data
+
+	// given an asset name checksum, find the type of the asset, and then load it
+
+	Dbg_MsgAssert(!AssetAllocated(asset_name),("Asset %08x already loaded",asset_name));
+
+	CAsset* p_asset = NULL;	
+		
+	// based on the asset type, create an asset of the correct type	
+	switch (asset_type)
+	{
+		case ASSET_ANIM:
+			p_asset = new CAnimAsset;
+			break;
+
+		case ASSET_SKIN:
+			p_asset = new CSkinAsset;
+			break;
+
+		default:
+			// right now, the only file type that's supported
+			// for data streams is the "cutscene model"
+			Dbg_MsgAssert(0,("Asset %08x is of unsupported type %d",asset_name,asset_type));   		
+	}
+
+	p_asset->m_permanent = permanent;
+	p_asset->m_group = group;
+	p_asset->m_dead = false;	  			// it is not dead	
+
+	// fake an asset name for debugging
+	char pDebugAssetString[256];
+	sprintf( pDebugAssetString, "%08x from data stream", asset_name );
+	p_asset->SetText(pDebugAssetString);			
+	
+	// finally, call the asset's Load member function to load it
+	int error = p_asset->Load(p_data, data_size);	
+	if ( error != 0 )
+	{
+		if ( Script::GetInteger( CRCD(0x25dc7904,"AssertOnMissingAssets") ) )
+		{
+			Dbg_MsgAssert(0,("Loading asset %08x returned error %d",asset_name,error));
+		}
+		else
+		{
+			delete p_asset;
+			p_asset = NULL;
+		}
+		return NULL;
+	}
+	else
+	{
+		// only add it to the table once we're sure there's no error
+		uint32 checksum = asset_name;
+		Dbg_MsgAssert(!mp_asset_table->GetItem( checksum ),("Asset %08x already in table",asset_name));	
+		mp_asset_table->PutItem( checksum,  p_asset);
+		p_asset->SetID(checksum);
+
+		// link in at the end, so things are added in order
+		p_asset->mp_next = mp_assetlist_head;
+		p_asset->mp_prev = mp_assetlist_head->mp_prev;
+		mp_assetlist_head->mp_prev->mp_next = p_asset;
+		mp_assetlist_head->mp_prev = p_asset;
+
+		Dbg_MsgAssert(mp_asset_table->GetItem( checksum ) == p_asset,("Asset does not match entry in table"));
+		Dbg_Assert( p_asset );
+		return p_asset->GetData();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void* CAssMan::LoadAsset(const char *p_assetName, bool async_load, bool use_pip, bool permanent, uint32 group, void* pExtraData, Script::CStruct *pParams)
+{
+	// load an asset, and return a pointer to the asset data
+
+	// given an asset filename, find the type of the asset, and then load it
+
+	Dbg_MsgAssert(!AssetAllocated(p_assetName),("Asset %s already loaded",p_assetName));
+
+	CAsset	* p_asset = NULL;	
+	
+// Find the asset type	
+	EAssetType	asset_type = FindAssetType(p_assetName); 
+	Dbg_MsgAssert(asset_type != ASSET_UNKNOWN,("Asset %s is of unknown type",p_assetName));
+	
+// based on that, create an asset of the correct type	
+	switch (asset_type)
+	{
+		case ASSET_ANIM:
+			p_asset = new CAnimAsset;
+			break;
+
+        case ASSET_SKELETON:
+            p_asset = new CSkeletonAsset;
+            break;
+
+		case ASSET_SKIN:
+			p_asset = new CSkinAsset;
+			break;
+
+		case ASSET_CUTSCENE:
+			p_asset = new CCutsceneAsset;
+			break;
+			
+		case ASSET_NODEARRAY:
+			p_asset = new CNodeArrayAsset;
+			break;
+			
+		case ASSET_SCENE:
+		case ASSET_TEXTURES:
+		case ASSET_COLLISION:
+		case ASSET_BINARY:
+		default:
+			Dbg_MsgAssert(0,("Asset %s is of unsupported type %d",p_assetName,asset_type));   		
+	}
+
+	p_asset->m_permanent = permanent;
+	p_asset->m_group = group;
+	p_asset->m_dead = false;	  			// it is not dead	
+
+	p_asset->SetText(p_assetName);			// record asset name for debugging
+	
+	// Garrett: Make sure that we only set async on the things that can handle them
+	if (async_load)
+	{
+		Dbg_MsgAssert(asset_type == ASSET_ANIM, ("Can't load this asset type asynchronously: %d", asset_type));
+	}
+
+	// finally, call the asset's Load member function to load it
+	int	error = p_asset->Load(p_assetName, async_load, use_pip, pExtraData, pParams);	
+	if ( error != 0 )
+	{
+		if ( Script::GetInteger( CRCD(0x25dc7904,"AssertOnMissingAssets") ) )
+		{
+			Dbg_MsgAssert(0,("Loading asset %s returned error %d",p_assetName,error));
+		}
+		else
+		{
+			delete p_asset;
+			p_asset = NULL;
+		}
+		return NULL;
+	}
+	else
+	{
+		// only add it to the table once we're sure there's no error
+		uint32 checksum = Script::GenerateCRC( p_assetName );
+		Dbg_MsgAssert(!mp_asset_table->GetItem( checksum ),("Asset %s already in table",p_assetName));	
+		mp_asset_table->PutItem( checksum,  p_asset);
+		p_asset->SetID(checksum);
+
+		// link in at the end, so things are added in order
+		p_asset->mp_next = mp_assetlist_head;
+		p_asset->mp_prev = mp_assetlist_head->mp_prev;
+		mp_assetlist_head->mp_prev->mp_next = p_asset;
+		mp_assetlist_head->mp_prev = p_asset;
+
+		Dbg_MsgAssert(mp_asset_table->GetItem( Script::GenerateCRC( p_assetName )) == p_asset,("Asset does not match entry in table"));
+		Dbg_Assert( p_asset );
+		return p_asset->GetData();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void* CAssMan::GetFirstInGroup( uint32 group )
+{
+	// return the first asset in the asset manager that is in this group
+	// will return NULL if there are none of teh specified group
+	
+	return GetNthInGroup(group,0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void* CAssMan::GetNthInGroup( uint32 group, int n )
+{
+	// return the nth asset in the asset manager that is in this group
+	// will return NULL if there are none of the specified group
+	
+	CAsset* p_asset = mp_assetlist_head->mp_next;
+	while (p_asset != mp_assetlist_head)
+	{
+//		printf ("%d: Checkingfor %x, group = %x\n",n,group,p_asset->m_group); 
+		CAsset *p_next = p_asset->mp_next;	
+		if (p_asset->m_group == group)
+		{
+//			printf ("%d: GROUP MATCH %x, group = %x\n",n,group,p_asset->m_group); 
+			if (n==0)
+			{
+//				printf ("%d: COUNT MATCH %x, group = %x\n",n,group,p_asset->m_group); 
+				return	p_asset->GetData();				
+			}
+			n--;
+		}			
+		p_asset = p_next;		
+	}
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CAssMan::GetGroup(uint32 checksum)
+{
+	CAsset* p_actual = mp_asset_table->GetItem(checksum);
+	Dbg_MsgAssert(p_actual, ("GetIndexInGroup with asset not found\n")); 
+	return p_actual->GetGroup();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CAssMan::GetIndexInGroup(uint32 checksum)
+{
+	// find this entry, and see which number it is in the group
+	// i.e, returns the intex into the group
+	
+	CAsset* p_actual = mp_asset_table->GetItem(checksum);
+	Dbg_MsgAssert(p_actual, ("GetIndexInGroup with asset not found\n")); 
+	
+	int n = 0;										  	// index starts at 0
+	CAsset* p_asset = mp_assetlist_head->mp_next;
+	while (p_asset != mp_assetlist_head)
+	{
+		CAsset *p_next = p_asset->mp_next;	
+		if (p_asset == p_actual)
+		{
+			return n;						  			// if we've found it, then return the asset
+		}
+		if (p_asset->m_group == p_actual->m_group)	 	// if in same group
+		{
+			n++;										// increment index
+		}
+		p_asset = p_next;		
+	}
+	Dbg_MsgAssert(0, ("ERROR IN GetIndexInGroup\n")); 
+	return 0;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CAssMan::CountGroup(uint32 group)
+{
+	int n = 0;										  	// index starts at 0
+	CAsset* p_asset = mp_assetlist_head->mp_next;
+	while (p_asset != mp_assetlist_head)
+	{
+		CAsset *p_next = p_asset->mp_next;	
+		if (p_asset->m_group == group)	 	// if in same group
+		{
+			n++;										// increment index
+		}
+		p_asset = p_next;		
+	}
+	return n;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CAssMan::CountSameGroup(uint32 checksum)
+{
+	// a utility function to count the assets in the same group as another asset
+	
+	return CountGroup(GetGroup(checksum));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+					   
+void CAssMan::DestroyReferences( CAsset* pAsset )
+{
+	// Destroys an asset's references
+	
+	Dbg_Assert( pAsset );
+		
+	while (pAsset->mp_ref_asset)
+	{
+		// destroying a reference is similar to an asset, except we
+		// do not need to call unload
+
+//		printf ("Deleting Reference %p of type %d, <%s>, <%s>\n",p_asset->mp_ref_asset,p_asset->mp_ref_asset->GetType(),p_asset->mp_ref_asset->Name(), p_asset->mp_ref_asset->GetText());
+		uint32 id = pAsset->mp_ref_asset->m_id;
+		pAsset->mp_ref_asset->Unlink();
+		delete pAsset->mp_ref_asset; 
+		mp_asset_table->FlushItem(id);		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAssMan::UnloadAsset( CAsset* pAsset )
+{
+	// Unload an asset and delete it
+	// note the current pairing of create/load,  and unload/delete
+	// in the future, we might need to seperate them out into two distinct stages 
+	
+	Dbg_Assert( pAsset );
+
+	uint32 id = pAsset->m_id;
+
+	Dbg_MsgAssert(pAsset->LoadFinished(), ("UnloadAsset(): Asset not finished loading"));
+
+	pAsset->Unlink();
+	pAsset->Unload();
+	delete pAsset;				
+	mp_asset_table->FlushItem(id);		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAssMan::ReloadAsset( uint32 assetID, const char* pFileName, bool assertOnFail )
+{
+	Ass::CAsset* pAsset;
+
+	pAsset = this->GetAssetNode( assetID, assertOnFail );
+
+	if ( pAsset )
+	{
+		Dbg_MsgAssert(pAsset->LoadFinished(), ("ReloadAsset(): Asset not finished loading"));
+
+		return pAsset->Reload( pFileName );
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAsset*	CAssMan::GetAssetNode( uint32 assetID, bool assertOnFail )
+{
+	CAsset *p_asset = mp_asset_table->GetItem(assetID);
+		
+	if ( assertOnFail )
+	{
+		Dbg_MsgAssert(p_asset, ("Asset Not found <%s>", Script::FindChecksumName(assetID)));
+		//Dbg_MsgAssert(p_asset->LoadFinished(), ("Asset not finished loading (%s)", Script::FindChecksumName(assetID)));
+	}
+
+	// Don't return pointer if it isn't done loading
+	if ( p_asset && !p_asset->LoadFinished() )
+	{
+		//p_asset = NULL;
+
+		// Wait if not done loading
+		while (!p_asset->LoadFinished())
+		{
+			Dbg_Message("Waiting for async asset load to finish...");
+			File::CAsyncFileLoader::sWaitForIOEvent(false);
+		}
+	}
+	
+	return p_asset;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void* CAssMan::GetAsset(uint32 assetID, bool assertOnFail )	
+{
+	// Return a pointer to the asset data, or NULL if not found
+	
+	CAsset *p_asset = mp_asset_table->GetItem(assetID);
+
+	if (p_asset /*&& p_asset->LoadFinished()*/)
+	{
+		// Wait if not done loading
+		while (!p_asset->LoadFinished())
+		{
+			Dbg_Message("Waiting for async asset load to finish...");
+			File::CAsyncFileLoader::sWaitForIOEvent(false);
+		}
+
+		void *p_asset_data = p_asset->GetData();
+		Dbg_MsgAssert(p_asset_data,("Asset has no data"));
+		return	p_asset_data;
+	}
+	else
+	{
+		if ( assertOnFail )
+		{
+			Dbg_MsgAssert(p_asset, ("Asset 0x%x Not found (%s)",assetID,Script::FindChecksumName(assetID)));
+			//Dbg_MsgAssert(p_asset->LoadFinished(), ("Asset 0x%x not finished loading (%s)",assetID,Script::FindChecksumName(assetID)));
+		}
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void* CAssMan::GetAsset(const char *p_assetName, bool assertOnFail)
+{
+	//returns a pointer to the asset data, does not load it if not found
+	
+	if ( assertOnFail )
+	{
+		Dbg_MsgAssert(AssetAllocated(p_assetName),("asset %s is not loaded in GetAsset",p_assetName));
+	}
+	void *p_asset_data = GetAsset(Script::GenerateCRC( p_assetName ), assertOnFail );
+	
+	if ( assertOnFail )
+	{
+		Dbg_MsgAssert(p_asset_data,("Asset has NULL data %s",p_assetName));
+	}
+	return p_asset_data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void* CAssMan::LoadOrGetAsset(const char *p_assetName, bool async_load, bool use_pip, bool permanent, uint32 group, void* pExtraData, Script::CStruct * pParams)
+{
+	//returns a pointer to the asset data, and loads the asset if not found
+	
+	void *p_asset_data;
+	CAsset *p_asset = mp_asset_table->GetItem(Script::GenerateCRC( p_assetName ));
+	if (p_asset)
+	{
+		p_asset_data = p_asset->GetData();
+		Dbg_MsgAssert(p_asset_data,("Asset has NULL data %s",p_assetName));
+		//Dbg_MsgAssert(p_asset->LoadFinished(), ("Asset not finished loading (%s)", p_assetName));
+
+		// Wait if not done loading
+		while (!p_asset->LoadFinished())
+		{
+			Dbg_Message("Waiting for async asset load to finish...");
+			File::CAsyncFileLoader::sWaitForIOEvent(false);
+		}
+	}
+	else
+	{
+		p_asset_data = LoadAsset(p_assetName, async_load, use_pip, permanent, 0, pExtraData,pParams);	
+	}
+	
+	if ( Script::GetInteger( CRCD(0x25dc7904,"AssertOnMissingAssets") ) )
+	{
+		// do some extra checks here if we're asserting on missing assets
+		Dbg_MsgAssert(p_asset_data,("Could not load asset %s",p_assetName));
+		Dbg_MsgAssert(AssetAllocated(p_assetName),("asset %s is not loaded after LoadOrGetAsset",p_assetName));
+	}
+
+	return p_asset_data;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAssMan::AssetAllocated(uint32 assetID)
+{
+	//  Return true if the asset has at least started loading
+	
+	return (NULL != mp_asset_table->GetItem(assetID));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAssMan::AssetAllocated(const char *p_assetName)
+{
+	//  Return true if the asset has at least started loading
+	
+	return (AssetAllocated(Script::GenerateCRC( p_assetName )));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAssMan::AssetLoaded(uint32 assetID)
+{
+	//  Return true if the asset is done loading
+	CAsset *p_asset = mp_asset_table->GetItem(assetID);
+
+	if (p_asset && p_asset->LoadFinished())
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAssMan::AssetLoaded(const char *p_assetName)
+{
+	//  Return true if the asset is done loading
+	
+	return (AssetLoaded(Script::GenerateCRC( p_assetName )));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAssMan::AddRef(uint32 asset_id, uint32 ref_id, uint32 group)
+{
+	// Add a reference to an asset, so we can access it via a new id
+
+	Dbg_MsgAssert(AssetAllocated(asset_id),("Adding ref to unloaded asset\n"));
+	CAsset *p_asset = new CRefAsset(mp_asset_table->GetItem(asset_id));
+	Dbg_MsgAssert(!mp_asset_table->GetItem( ref_id ),("ref already in table/n"));	
+	mp_asset_table->PutItem( ref_id,  p_asset);
+	p_asset->SetID(ref_id);
+	p_asset->SetGroup(group);
+
+	// link it to the end of the list
+	p_asset->mp_next = mp_assetlist_head;
+	p_asset->mp_prev = mp_assetlist_head->mp_prev;
+	mp_assetlist_head->mp_prev->mp_next = p_asset;
+	mp_assetlist_head->mp_prev = p_asset;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAssMan::AddRef(const char *p_assetName, uint32 ref_id, uint32 group)
+{
+	Dbg_MsgAssert(!mp_asset_table->GetItem( ref_id ),("ref %lx already in table\nwhen adding to %s\n",ref_id,p_assetName));	
+	Dbg_MsgAssert(AssetAllocated(p_assetName),("Adding ref to unloaded asset %s\n",p_assetName));
+	uint32	checksum = Script::GenerateCRC( p_assetName );
+	AddRef(checksum,ref_id, group);	
+}
+												 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAssMan::UnloadAllTables( bool destroy_permanent )
+{
+//	printf ( "Unloading all tables" );
+	
+	CAsset* p_asset;
+	 
+	p_asset = mp_assetlist_head->mp_next;
+	while (p_asset != mp_assetlist_head)
+	{
+		CAsset *p_next = p_asset->mp_next;	
+		if ( destroy_permanent || !p_asset->m_permanent )
+		{
+			// First destroy any references to this asset
+			DestroyReferences( p_asset );
+			
+			// Need to update p_next, in case it was change by destroying above code
+			p_next = p_asset->mp_next;	
+	
+			UnloadAsset( p_asset );
+		}
+		p_asset = p_next;		
+	}
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAssMan::SetDefaultPermanent(bool permanent)
+{
+	// set the default permanent state of loaded assets
+	// (generally this will be false)
+	
+	m_default_permanent = permanent;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAssMan::GetDefaultPermanent() const
+{
+	return m_default_permanent;
+}
+
+// The following functions are kind of kludge for referencing anims
+// Ideally, I'd like to remove these kludges and simplify the
+// asset manager
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAssMan::LoadAnim(const char* pFileName, uint32 animName, uint32 referenceChecksum, bool async_load, bool use_pip)
+{
+	// If the asset was previously loaded, then we are just creating a reference to it
+	// so the m_permanent flag should be set based on m_default_permanent, and NOT INHERITED
+			
+	if (AssetAllocated(pFileName))
+	{
+		// if descChecksum was supplied, then adding a reference
+		if (animName && !AssetAllocated(animName + referenceChecksum))
+		{
+			// adding a reference
+			AddRef(pFileName, animName + referenceChecksum, referenceChecksum);	 
+			// the m_permanent will have been inherited from the parent asset
+			// Now get the reference asset, and set the permanent flag to m_defualt_permanet
+			CAsset *p_asset = mp_asset_table->GetItem(animName + referenceChecksum);
+			Dbg_MsgAssert(p_asset,("(Mick) Error re-getting asset ref for %s\n",pFileName));
+			//p_asset->SetPermanent(m_default_permanent);  // <<<< this would also set the perm flag on the parent
+
+			p_asset->m_permanent = m_default_permanent;		// just in the reference!!!
+			return true;
+		}
+		else
+		{
+			//Dbg_MsgAssert( 0, ( "This file was already loaded %s", pFileName ) );
+			return false;
+		}
+	}
+	else
+	{
+		if (!LoadOrGetAsset(pFileName, async_load, use_pip, m_default_permanent, 0))
+		{
+			Dbg_MsgAssert(0,("Failed to load anim %s",pFileName));
+			return false;
+		}
+
+		// Now add the reference to it, if one was requested
+		// this gets combined with the "reference checksum"
+		if (animName && !AssetAllocated(animName + referenceChecksum))
+		{
+			AddRef(pFileName, animName + referenceChecksum, referenceChecksum);	 
+		}
+
+		return true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAssMan::SetReferenceChecksum(uint32 reference_checksum)	   
+{
+	// kind of a kludge for anims
+
+	m_reference_checksum = reference_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CAssMan::GetReferenceChecksum() const
+{
+	return m_reference_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Ass
+
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAssMan::DumpAssets()
+{
+	CAsset * p_asset = mp_assetlist_head->mp_next;
+	int i=0;
+	while (p_asset != mp_assetlist_head)
+	{
+		CAsset *p_next = p_asset->mp_next;	
+		printf ("Asset %3d: perm=%d %s\n",i,p_asset->m_permanent, p_asset->GetText());
+		i++;
+		p_asset = p_next;		
+	}	
+}
+#endif
diff --git a/Code/Gel/AssMan/AssMan.h b/Code/Gel/AssMan/AssMan.h
new file mode 100644
index 0000000..aa171e1
--- /dev/null
+++ b/Code/Gel/AssMan/AssMan.h
@@ -0,0 +1,124 @@
+//****************************************************************************
+//* MODULE:         Gel/AssMan
+//* FILENAME:       AssMan.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/17/2000
+//****************************************************************************
+
+#ifndef __GEL_ASSMAN_H
+#define __GEL_ASSMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Lst
+{
+	template class HashTable;
+}
+
+namespace Script
+{
+	class CStruct;
+}
+
+namespace Ass
+{
+	class CAsset;
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+   
+class CAssMan : public Spt::Class 
+{	
+	DeclareSingletonClass( CAssMan );
+
+public:
+    
+	CAssMan( void );
+    ~CAssMan( void );
+
+	// New generic functions
+	EAssetType	FindAssetType(const char *p_assetName);
+
+	void*		LoadAssetFromStream(uint32 asset_name, uint32 asset_type, uint32* p_data, int data_size, bool permanent, uint32 group);
+	void*		LoadAsset(const char *p_assetName, bool async_load, bool use_pip = false, bool permanent = false, uint32 group = 0, void* pExtraData = NULL, Script::CStruct * pParams = NULL);
+	bool		ReloadAsset( uint32 assetID, const char* pFileName, bool assertOnFail );
+	void*		GetAsset(const char *p_assetName, bool assertOnFail = true);	
+	void*		GetAsset(uint32	assetID, bool assertOnFail = true);	
+	void*		LoadOrGetAsset(const char *p_assetName, bool async_load, bool use_pip, bool permanent = false, uint32 group = 0, void* pExtraData = NULL, Script::CStruct *pParams = NULL);	
+	
+	bool		AssetAllocated(uint32	assetID);
+	bool		AssetAllocated(const char *p_assetName);
+
+	bool		AssetLoaded(uint32	assetID);
+	bool		AssetLoaded(const char *p_assetName);
+	
+	void		AddRef(uint32 asset_id, uint32 ref_id, uint32 group = 0);
+	void		AddRef(const char *p_assetName, uint32 ref_id, uint32 group = 0);
+	
+	void		SetReferenceChecksum(uint32 reference_checksum);	   
+	uint32		GetReferenceChecksum() const;
+	
+	void		SetDefaultPermanent(bool permanent);
+	bool		GetDefaultPermanent() const;
+
+	void* 		GetFirstInGroup(uint32 group);
+	void* 		GetNthInGroup(uint32 group, int n);
+
+	int 		GetIndexInGroup(uint32 checksum);
+	int 		GetGroup(uint32 checksum);
+	int 		CountGroup(uint32 group);
+	int 		CountSameGroup(uint32 checksum);
+
+	void		UnloadAllTables( bool destroy_permanent = false );
+	void		DestroyReferences( CAsset* pAsset );
+	void		UnloadAsset( CAsset* pAsset );
+	CAsset*		GetAssetNode( uint32 assetID, bool assertOnFail );
+	
+	// ideally, the LoadAnim function should be made more generic
+	bool		LoadAnim(const char* pFileName, uint32 animName, uint32 referenceChecksum, bool async_load, bool use_pip);
+	
+private:
+
+	bool							m_default_permanent;
+	uint32 							m_reference_checksum;	// used for anims, bit of a patch
+	Lst::HashTable*	mp_asset_table;
+	CAsset*							mp_assetlist_head;		   
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Ass
+
+#endif	// __GEL_ASSMAN_H
+
diff --git a/Code/Gel/AssMan/NodeArrayAsset.cpp b/Code/Gel/AssMan/NodeArrayAsset.cpp
new file mode 100644
index 0000000..36de65e
--- /dev/null
+++ b/Code/Gel/AssMan/NodeArrayAsset.cpp
@@ -0,0 +1,111 @@
+///////////////////////////////////////////////////////////////////////////
+// NodeArrayAsset.cpp
+//
+// Asset depended code for loading, unloading and reloading a node array
+//
+// Dave
+//
+
+
+#include	
+#include	
+#include	
+#include	
+#include	
+#include	
+
+namespace Ass
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CNodeArrayAsset::Load( const char* p_file, bool async_load, bool use_pip, void* pExtraData , Script::CStruct *pStruct)
+{
+	SkateScript::LoadQB( p_file, Script::ASSERT_IF_DUPLICATE_SYMBOLS);
+	m_qb_checksum = Script::GenerateCRC( p_file );
+
+	char nodearrayname[128];
+
+	// Skip back to the backslash.
+	const char *p = &p_file[strlen( p_file ) - 1];
+	while( *p != '\\' && *p != '/') p--;
+	p++;
+
+	strcpy( nodearrayname, p );												// nodearrayname is now "name.qb"
+	sprintf( &nodearrayname[strlen( nodearrayname ) - 3], "_nodearray" );	// nodearrayname is now "name_nodearray"
+	void *p_data = Script::GetArray( nodearrayname );
+
+	// Set the array as the data returned when queried,
+	SetData( p_data );
+
+	Dbg_MsgAssert( p_data, ( "Could not find nodearray %s in %s", nodearrayname, p_file ));
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CNodeArrayAsset::Unload()                     
+{
+	// Unload the asset.
+	SkateScript::UnloadQB( m_qb_checksum );
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CNodeArrayAsset::Reload( const char* p_file )
+{
+	Dbg_Message( "Reloading %s", p_file );
+	
+	Unload();
+
+	return( Load( p_file, false, 0, NULL, NULL ) == 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CNodeArrayAsset::LoadFinished()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* CNodeArrayAsset::Name()            
+{
+	// Printable name, for debugging.
+	return "Node Array";	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAssetType CNodeArrayAsset::GetType()         
+{
+	// type is hard wired into asset class 
+	return ASSET_NODEARRAY;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Gel/AssMan/NodeArrayAsset.h b/Code/Gel/AssMan/NodeArrayAsset.h
new file mode 100644
index 0000000..274ffa2
--- /dev/null
+++ b/Code/Gel/AssMan/NodeArrayAsset.h
@@ -0,0 +1,36 @@
+////////////////////////////////////////////////////////////////////////////
+// animasset.h - interface between asset manager, and the actual animation
+// provides a common interface to the 
+#ifndef	__GEL_NODEARRAYASSET_H__
+#define	__GEL_NODEARRAYASSET_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include	
+
+namespace Ass
+{
+
+class 	CNodeArrayAsset : public CAsset
+{
+
+public:
+	virtual int 				Load( const char *p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct );	// create or load the asset
+	virtual int 				Unload();										// Unload the asset
+	virtual int 				Reload( const char *p_file);
+	virtual bool				LoadFinished();									// Check to make sure asset is actually there
+	virtual const char *  		Name();											// printable name, for debugging
+	virtual EAssetType 			GetType();										// type is hard wired into asset class
+private:
+
+
+	uint32						m_qb_checksum;
+};
+
+
+} // end namespace Ass
+
+#endif			// #ifndef	__GEL_ASSET_H__
+
diff --git a/Code/Gel/AssMan/animasset.cpp b/Code/Gel/AssMan/animasset.cpp
new file mode 100644
index 0000000..d5df479
--- /dev/null
+++ b/Code/Gel/AssMan/animasset.cpp
@@ -0,0 +1,168 @@
+///////////////////////////////////////////////////////////////////////////
+// AnimAsset.cpp
+//
+// Asset depended code for loading, unloading and reloading an animation
+//
+// Mick
+//
+
+
+#include	
+
+#include	
+
+#include	
+#include	
+
+#include	
+
+
+namespace Ass
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CAnimAsset::Load(const char* p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct)     // create or load the asset
+{
+	int errorCode = -1;
+
+	Mem::PushMemProfile((char*)p_file);
+	
+	Gfx::CBonedAnimFrameData* pSeq = new Gfx::CBonedAnimFrameData;
+
+	char fullName[256];
+	
+	// add extension to create name of platform-specific SKA file
+	sprintf( fullName, "%s.%s", p_file, Nx::CEngine::sGetPlatformExtension() );
+	
+	// Load the data
+	if ( !pSeq->Load(fullName, true, async_load, use_pip) )
+	{
+		if ( Script::GetInteger( CRCD(0x25dc7904,"AssertOnMissingAssets") ) )
+		{
+			Dbg_MsgAssert( 0,( "Anim %s doesn't exist.", fullName ) );
+		}
+		delete pSeq;
+		pSeq = NULL;
+		goto failure;
+	}
+
+	// if we get this far, then it's successful
+	errorCode = 0;
+
+failure:
+	// Add it to the list:
+	SetData( (void*)pSeq );
+	Mem::PopMemProfile(/*(char*)p_file*/);
+	return errorCode;
+}
+
+int CAnimAsset::Load(uint32* p_data, int data_size)     // create or load the asset
+{
+	int errorCode = -1;
+
+	char pDebugAssetString[256];
+	sprintf( pDebugAssetString, "anim from data stream" );
+	
+	Mem::PushMemProfile((char*)pDebugAssetString);
+
+	Gfx::CBonedAnimFrameData* pSeq = new Gfx::CBonedAnimFrameData;
+
+	// Load the data
+	if ( !pSeq->Load(p_data, data_size, true ) )
+	{
+		if ( Script::GetInteger( CRCD(0x25dc7904,"AssertOnMissingAssets") ) )
+		{
+			Dbg_Assert( 0 );
+		}
+		delete pSeq;
+		pSeq = NULL;
+		goto failure;
+	}
+
+	// if we get this far, then it's successful
+	errorCode = 0;
+
+failure:
+	// Add it to the list:
+	SetData( (void*)pSeq );
+	Mem::PopMemProfile(/*(char*)p_file*/);
+	return errorCode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CAnimAsset::Unload()                     
+{
+	// Unload the asset
+	
+	if (GetData())
+	{
+        delete (Gfx::CBonedAnimFrameData*) GetData();
+		
+        SetData(NULL);
+	}
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CAnimAsset::Reload(const char* p_file)
+{
+	Dbg_Message( "Reloading %s", p_file );
+	
+	Unload();
+
+	return ( Load(p_file, false, 0, NULL, NULL ) == 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimAsset::LoadFinished()
+{
+	Gfx::CBonedAnimFrameData * p_anim = (Gfx::CBonedAnimFrameData*) GetData();
+	Dbg_MsgAssert(p_anim, ("LoadFinished(): Data pointer NULL (load probably was never started)"));
+
+	return p_anim->LoadFinished();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* CAnimAsset::Name()            
+{
+	// printable name, for debugging
+	return "Animation";	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAssetType CAnimAsset::GetType()         
+{
+	// type is hard wired into asset class 
+	return ASSET_ANIM;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Gel/AssMan/animasset.h b/Code/Gel/AssMan/animasset.h
new file mode 100644
index 0000000..dbc0de2
--- /dev/null
+++ b/Code/Gel/AssMan/animasset.h
@@ -0,0 +1,40 @@
+////////////////////////////////////////////////////////////////////////////
+// animasset.h - interface between asset manager, and the actual animation
+// provides a common interface to the 
+#ifndef	__GEL_ANIMASSET_H__
+#define	__GEL_ANIMASSET_H__
+
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include	
+
+namespace Ass
+{
+
+
+
+class 	CAnimAsset : public CAsset
+{
+
+public:
+        virtual int 				Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct);	// create or load the asset
+		virtual int 				Load(uint32* p_data, int data_size);	// create or load the asset
+        virtual int 				Unload();                     // Unload the asset
+        virtual int 				Reload(const char *p_file);
+		virtual bool				LoadFinished();    // Check to make sure asset is actually there
+     	virtual const char *  		Name();            // printable name, for debugging
+		virtual EAssetType 			GetType();         // type is hard wired into asset class 
+private:
+		
+
+};
+
+
+} // end namespace Ass
+
+#endif			// #ifndef	__GEL_ASSET_H__
+
diff --git a/Code/Gel/AssMan/asset.cpp b/Code/Gel/AssMan/asset.cpp
new file mode 100644
index 0000000..790a8bd
--- /dev/null
+++ b/Code/Gel/AssMan/asset.cpp
@@ -0,0 +1,113 @@
+///////////////////////////////////////////////////////////////////////////
+// Asset.cpp
+//
+// Mick
+//
+// Base class of all assets
+// provides basic interface to them
+
+
+#include	
+#include 	
+
+
+namespace Ass
+{
+
+// when destroying an asset, we also unload it
+// this should only be done via the asset manager
+
+CAsset::CAsset()
+{
+}
+
+CAsset::~CAsset()
+{
+}
+
+// Unlink for the doubly linked list
+void CAsset::Unlink()
+{
+	mp_next->mp_prev = mp_prev;
+	mp_prev->mp_next = mp_next;
+	mp_prev = this;
+	mp_next = this;
+}
+
+int CAsset::Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData , Script::CStruct *pParams)
+{
+	Dbg_MsgAssert(0,("CAsset::Load() should not be called"));
+	return 0;
+}  	
+
+int CAsset::Load(uint32* p_data, int data_size)
+{
+	Dbg_MsgAssert(0,("CAsset::Load() from data buffer should not be called"));
+	return 0;
+}
+
+int CAsset::Reload(const char *p_file)   
+{
+	Dbg_MsgAssert(0,("CAsset::Reload() should not be called"));
+	return 0;
+}
+
+int 	CAsset::Unload()                  
+{
+	Dbg_MsgAssert(0,("CAsset::Unload() should not be called"));
+	return 0;
+}
+
+bool	CAsset::LoadFinished()
+{
+	Dbg_MsgAssert(0,("CAsset::LoadFinished() should not be called"));
+	return false;
+}
+
+
+
+const char *  CAsset::Name()            // printable name, for debugging
+{
+	return	"Unnamed Asset";	
+}
+
+void * 	CAsset::GetData()             // return a pointer to the asset's data
+{
+	return mp_data;					
+}
+
+void  	CAsset::SetData(void *p_data)             
+{
+	mp_data = p_data;				   
+}
+
+
+//----------------------------------------------------
+
+void   CAsset::SetID(uint32 id)         
+{
+	m_id = id;
+}
+
+uint32 CAsset::GetID()
+{
+	return m_id;
+}
+
+void   CAsset::SetGroup(uint32 group)     
+{
+	m_group = group;
+}
+
+uint32 CAsset::GetGroup()
+{
+	return m_group;
+}
+
+
+EAssetType CAsset::GetType()         // type is hard wired into asset class 
+{
+	return ASSET_UNKNOWN; 		// for now return 0, not sure if this should return the EAssetType
+}
+
+}
diff --git a/Code/Gel/AssMan/asset.h b/Code/Gel/AssMan/asset.h
new file mode 100644
index 0000000..9b50ac0
--- /dev/null
+++ b/Code/Gel/AssMan/asset.h
@@ -0,0 +1,102 @@
+///////////////////////////////////////////////////////////
+// asset.h - base class for managed assets
+
+#ifndef	__GEL_ASSET_H__
+#define	__GEL_ASSET_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+
+namespace Script
+{
+	class CStruct;
+}
+
+
+namespace Ass
+{
+class 	CAsset : public Spt::Class
+{
+	friend class CAssMan;
+	friend class CRefAsset;
+
+protected:																	 
+		CAsset();	  				// constructor is private, so only CAssMan can create them
+		virtual ~CAsset(); 			//
+
+		virtual    	int 		Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pParams);	// create or load the asset
+		virtual		int			Load(uint32* p_data, int data_size);
+		virtual    	int 		Unload();                  	// Unload the asset
+		virtual    	int 		Reload(const char *p_file);
+		virtual		bool		LoadFinished();				// Check to make sure asset is actually there
+
+
+					void 		Unlink();                  	// Unlink the asset
+
+// Some acessor functions for memory usage metrics					
+		virtual   	const char* Name();          	// printable name, for debugging
+
+// acessors for seting the low level id stuff 
+// maybe this should just be derived from CObject?    
+		void      			   	SetID(uint32 id);           	// Unique asset ID
+		uint32    			   	GetID();
+		virtual void      		SetGroup(uint32 group);     	// Unique group ID
+		virtual uint32    		GetGroup();
+		
+		virtual EAssetType	  	GetType();         				// type is hard wired into asset class 
+		virtual void     		SetData(void *p_data);          // return a pointer to the asset….
+		virtual void *    		GetData();             			// return a pointer to the asset….
+		virtual	void			SetPermanent(bool perm);
+
+		void 					SetText(const char *p_text);
+		const char *			GetText();
+
+private:
+		uint32				   	m_id;
+		uint32				   	m_group;
+		
+		char				   	m_permanent;  					// should it stay in memory after a cleanup
+		char					m_dead;							// asset is dead, remove it 		
+		
+		void *					mp_data;   						// pointer to the asset data
+
+//		Str::String				m_text;
+
+protected:
+		CRefAsset	*			mp_ref_asset;					// pointer to first CRefAsset that points to this 
+
+// The assets are also stored in a simple doubly linked list with a head node
+// this is to simplify the 
+		CAsset  *				mp_prev;
+		CAsset  *				mp_next;
+	
+};
+
+
+
+//////////////////////////////////////////////////////////////////////////
+// Inline member functions																	 
+
+
+inline		void 					CAsset::SetText(const char *p_text)
+{
+//	m_text = p_text;
+}
+
+inline 		const char *			CAsset::GetText()
+{
+//	return m_text.getString();
+	return "names removed";
+}
+																	 
+inline	void	CAsset::SetPermanent(bool perm) {m_permanent = perm;}
+
+
+} // end namespace Ass
+
+#endif			// #ifndef	__GEL_ASSET_H__
+
diff --git a/Code/Gel/AssMan/assettypes.h b/Code/Gel/AssMan/assettypes.h
new file mode 100644
index 0000000..5e4eca0
--- /dev/null
+++ b/Code/Gel/AssMan/assettypes.h
@@ -0,0 +1,24 @@
+// asset types for the asset manager
+
+#ifndef __GEL_ASSETTYPES_H
+#define __GEL_ASSETTYPES_H
+
+namespace Ass
+{
+
+enum	EAssetType {
+	ASSET_UNKNOWN,							// unknown, needs to be determined by inspection
+	ASSET_BINARY,							// binary file
+	ASSET_SCENE,							// world_geometry
+	ASSET_TEXTURES,							// Texture file
+	ASSET_COLLISION,						// collision file
+	ASSET_ANIM,								// animation
+    ASSET_SKELETON,                         // skeleton
+	ASSET_SKIN,								// skin
+	ASSET_CUTSCENE,							// cutscene
+	ASSET_NODEARRAY,						// model node array
+};
+
+} // namespace Ass
+
+#endif
diff --git a/Code/Gel/AssMan/cutsceneasset.cpp b/Code/Gel/AssMan/cutsceneasset.cpp
new file mode 100644
index 0000000..3859362
--- /dev/null
+++ b/Code/Gel/AssMan/cutsceneasset.cpp
@@ -0,0 +1,118 @@
+//****************************************************************************
+//* MODULE:         Ass
+//* FILENAME:       cutsceneasset.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  01/13/2003
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Ass
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CCutsceneAsset::Load( const char* p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct )     // create or load the asset
+{
+	Mem::PushMemProfile( (char*)p_file );
+	
+	Obj::CCutsceneData* p_cutsceneData = new Obj::CCutsceneData;
+
+	// get the platform-specific CUT file name
+	char platFileName[256];
+	sprintf( platFileName, "%s.%s", p_file, Nx::CEngine::sGetPlatformExtension() );
+	
+	// load the data
+	if ( !p_cutsceneData->Load( platFileName, true, async_load ) )
+	{
+		Dbg_MsgAssert( 0, ( "Cutscene %s doesn't exist.", platFileName ) );
+		return -1;
+	}
+	
+	// add it to the list:
+	SetData( (void*)p_cutsceneData );
+
+	Mem::PopMemProfile(/*(char*)p_file*/);
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CCutsceneAsset::Unload()                     
+{
+	// Unload the asset
+	Obj::CCutsceneData* pData = (Obj::CCutsceneData*)GetData();
+	if ( pData )
+	{
+        delete pData;
+        SetData(NULL);
+	}
+	
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CCutsceneAsset::Reload(const char* p_file)
+{
+	Dbg_Message( "Reloading %s...", p_file );
+	
+	Unload();
+
+	return ( Load( p_file, false, 0, NULL, NULL ) == 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneAsset::LoadFinished()
+{
+	Obj::CCutsceneData* p_cutsceneData = (Obj::CCutsceneData*)GetData();
+	Dbg_MsgAssert( p_cutsceneData, ( "LoadFinished(): Data pointer NULL (load probably was never started)" ) );
+	return p_cutsceneData->LoadFinished();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* CCutsceneAsset::Name()            
+{
+	// printable name, for debugging
+	return "Cutscene";	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAssetType CCutsceneAsset::GetType()         
+{
+	// type is hard wired into asset class 
+	return ASSET_CUTSCENE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Gel/AssMan/cutsceneasset.h b/Code/Gel/AssMan/cutsceneasset.h
new file mode 100644
index 0000000..923b38a
--- /dev/null
+++ b/Code/Gel/AssMan/cutsceneasset.h
@@ -0,0 +1,46 @@
+//****************************************************************************
+//* MODULE:         Ass
+//* FILENAME:       cutsceneasset.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  01/13/2003
+//****************************************************************************
+
+// interface between asset manager, and the actual cutscene
+
+#ifndef	__GEL_CUTSCENEASSET_H__
+#define	__GEL_CUTSCENEASSET_H__
+
+#include 
+#include 
+
+#include 
+
+namespace Ass
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+class CCutsceneAsset : public CAsset
+{
+
+public:
+        virtual int 				Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct);	// create or load the asset
+        virtual int 				Unload();                     				// unload the asset
+        virtual int 				Reload(const char *p_file);
+		virtual bool				LoadFinished();    							// check to make sure asset is actually there
+     	virtual const char *  		Name();            							// printable name, for debugging
+		virtual EAssetType 			GetType();         							// type is hard wired into asset class 
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
+
+#endif
+
diff --git a/Code/Gel/AssMan/refasset.cpp b/Code/Gel/AssMan/refasset.cpp
new file mode 100644
index 0000000..cdfe66d
--- /dev/null
+++ b/Code/Gel/AssMan/refasset.cpp
@@ -0,0 +1,156 @@
+///////////////////////////////////////////////////////////////////////////
+// RefAsset.cpp
+//
+// Mick
+//
+// Type of asset that just refers to another asset
+// so you can have multiple indicies to reference an asset
+
+
+#include	
+#include	
+
+
+namespace Ass
+{
+
+CRefAsset::CRefAsset(CAsset *p_asset)
+{
+
+	mp_asset = p_asset;
+	mp_sibling = NULL;
+	if (p_asset->mp_ref_asset == NULL)
+	{
+		// this is the first reference, so just stick it in mp_ref_asset
+		p_asset->mp_ref_asset = this;		
+	}
+	else
+	{
+		// it's a new asset, so insert it as the head
+		// of siblings that refer to this
+		CRefAsset *p_other_ref = p_asset->mp_ref_asset;
+		p_asset->mp_ref_asset = this;
+		mp_sibling = p_other_ref;
+	}
+	mp_data = NULL;							// Ref Assets explicitly have no data
+	m_permanent = p_asset->m_permanent;		// inherit the permanence flag
+}
+
+
+
+CRefAsset::~CRefAsset()
+{
+
+// unhook the references
+// from the assman table, the asset it refers to, and other assets
+	
+	if (mp_asset)
+	{
+		CRefAsset * p_prev = mp_asset->mp_ref_asset;
+		if (p_prev == this)
+		{
+			mp_asset->mp_ref_asset = mp_sibling;
+		}
+		else
+		{
+			while (p_prev && p_prev->mp_sibling  != this)
+			{
+				p_prev = p_prev->mp_sibling;
+			}
+			Dbg_MsgAssert(p_prev,("Reference not listed in its parent asset")); 
+			p_prev->mp_sibling = mp_sibling;
+		}	
+		mp_asset = NULL;	
+		mp_sibling = NULL;
+	}					
+	
+
+	Dbg_MsgAssert(mp_asset == NULL,("Destroying ref with non-null asset"));	
+	Dbg_MsgAssert(mp_sibling == NULL,("Destroying ref with non-null sibling"));	
+	Dbg_MsgAssert(mp_data == NULL,("Ref Asset has data!"));
+
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////
+// all other functions simply call the appropiate function on the referenced asset
+// (might want to make these inline later)
+
+int 			CRefAsset::Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData)  	// create or load the asset
+{
+	Dbg_MsgAssert(!async_load, ("Async load not supported on CRefAsset"));
+
+//	return mp_asset->Load(p_file);
+	return 0;
+}
+
+
+int 			CRefAsset::Unload()                  	// Unload the asset
+{
+//	return mp_asset->Unload();
+	return 0;
+}
+
+
+int 			CRefAsset::Reload(const char *p_file)
+{
+//	printf("Reloading dereferenced asset with %s\n", p_file);
+
+	return mp_asset->Reload(p_file);
+}
+
+bool			CRefAsset::LoadFinished()
+{
+	Dbg_MsgAssert(GetData(), ("LoadFinished(): Data pointer NULL (load probably was never started)"));
+
+	return mp_asset->LoadFinished();
+}
+
+
+
+const char* 	CRefAsset::Name()          			// printable name, for debugging
+{
+	return mp_asset->Name();
+}
+
+
+void      		CRefAsset::SetGroup(uint32 group)     // Unique group ID
+{
+//	mp_asset->SetGroup(group);
+	m_group = group;
+}
+
+
+uint32    		CRefAsset::GetGroup()
+{
+//	return mp_asset->GetGroup();
+	return m_group;
+}
+
+
+EAssetType	  	CRefAsset::GetType()         			// type is hard wired into asset class 
+{
+	return mp_asset->GetType();
+}
+
+
+void     		CRefAsset::SetData(void *p_data)      // return a pointer to the asset….
+{
+	mp_asset->SetData(p_data);
+}
+
+
+void *    		CRefAsset::GetData()             		// return a pointer to the asset….
+{
+	return mp_asset->GetData();
+}
+
+
+void			CRefAsset::SetPermanent(bool perm)
+{
+	m_permanent = perm;
+	mp_asset->SetPermanent(perm);
+}
+
+
+}
diff --git a/Code/Gel/AssMan/refasset.h b/Code/Gel/AssMan/refasset.h
new file mode 100644
index 0000000..643f14b
--- /dev/null
+++ b/Code/Gel/AssMan/refasset.h
@@ -0,0 +1,53 @@
+///////////////////////////////////////////////////////////
+// asset.h - base class for managed assets
+
+#ifndef	__GEL_REFASSET_H__
+#define	__GEL_REFASSET_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+namespace Ass
+{
+
+
+
+class 	CRefAsset : public CAsset
+{
+		friend class CAssMan;
+protected:
+						CRefAsset(CAsset *p_asset);
+						~CRefAsset();
+		
+		int 			Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData);	// create or load the asset
+		int 			Unload();                  	// Unload the asset
+		int 			Reload(const char *p_file);
+		bool			LoadFinished();				// Check to make sure asset is actually there
+		int  			RamUsage();
+		int  			VramUsage();
+		int  			SramUsage();
+		const char* 	Name();          			// printable name, for debugging
+		void      		SetGroup(uint32 group);     // Unique group ID
+		uint32    		GetGroup();
+		EAssetType	  	GetType();         			// type is hard wired into asset class 
+		void     		SetData(void *p_data);      // return a pointer to the asset….
+		void *    		GetData();             		// return a pointer to the asset….
+		void			SetPermanent(bool perm);
+
+private:
+		CAsset	*				mp_asset;			// pointer to the actual asset 
+		CRefAsset	*			mp_sibling;			// pointer to other CRefAsset that references mp_asset 	
+};
+
+//////////////////////////////////////////////////////////////////////////
+// Inline member functions																	 
+																	 
+
+
+} // end namespace Ass
+
+#endif			// #ifndef	__GEL_REFASSET_H__
+
diff --git a/Code/Gel/AssMan/skeletonasset.cpp b/Code/Gel/AssMan/skeletonasset.cpp
new file mode 100644
index 0000000..23bb251
--- /dev/null
+++ b/Code/Gel/AssMan/skeletonasset.cpp
@@ -0,0 +1,137 @@
+//****************************************************************************
+//* MODULE:         Ass
+//* FILENAME:       skeletonasset.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  ??/??/????
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+namespace Ass
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CSkeletonAsset::Load( const char *p_file, bool async_load, bool use_pip, void* pExtraData , Script::CStruct *pStruct)     // create or load the asset
+{																			   
+	Dbg_MsgAssert( !async_load, ( "Async load not supported on CSkeletonAsset" ) );
+
+	// Load the data, add it to the list:
+	Gfx::CSkeletonData* pSkeletonData = new Gfx::CSkeletonData;
+	
+	char fullName[256];
+	
+	// add extension to create name of platform-specific SKE file
+	sprintf( fullName, "%s.%s", p_file, Nx::CEngine::sGetPlatformExtension() );
+	
+	if ( !pSkeletonData->Load( fullName, true ) )
+	{
+		Dbg_MsgAssert( 0,( "File %s doesn't exist.", fullName ));
+		return -1;
+	}
+	SetData( (void*)pSkeletonData );
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CSkeletonAsset::Load( uint32* p_data, int data_size )
+{
+	char pDebugAssetString[256];
+	sprintf( pDebugAssetString, "skeleton from data stream" );
+	
+	Mem::PushMemProfile((char*)pDebugAssetString);
+
+	// Load the data, add it to the list:
+	Gfx::CSkeletonData* pSkeletonData = new Gfx::CSkeletonData;
+	
+	if ( !pSkeletonData->Load( p_data, data_size, true ) )
+	{
+		Dbg_MsgAssert( 0,( "Couldn't create skeleton from data stream." ));
+		return -1;
+	}
+
+	SetData((void*)pSkeletonData);
+
+	Mem::PopMemProfile(/*"skeleton from data stream"*/);
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CSkeletonAsset::Unload()                     // Unload the asset
+{
+	Gfx::CSkeletonData* pData = (Gfx::CSkeletonData*)GetData();
+	if ( pData )
+	{
+        delete pData;
+        SetData(NULL);
+	}
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CSkeletonAsset::Reload( const char *p_file )
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CSkeletonAsset::LoadFinished()
+{
+	Dbg_MsgAssert( GetData(), ( "LoadFinished(): Data pointer NULL (load probably was never started)" ) );
+
+	// Since we don't support async, this is always true
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+const char *  CSkeletonAsset::Name()            // printable name, for debugging
+{
+	return "Skeleton";	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+EAssetType CSkeletonAsset::GetType()         // type is hard wired into asset class 
+{
+	return ASSET_SKELETON; 					// for now return 0, not sure if this should return the EAssetType
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/AssMan/skeletonasset.h b/Code/Gel/AssMan/skeletonasset.h
new file mode 100644
index 0000000..ab39cc5
--- /dev/null
+++ b/Code/Gel/AssMan/skeletonasset.h
@@ -0,0 +1,47 @@
+//****************************************************************************
+//* MODULE:         Ass
+//* FILENAME:       skeletonasset.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  ??/??/????
+//****************************************************************************
+
+// interface between asset manager, and the actual skeleton data
+// provides a common interface to the skeleton data
+
+#ifndef	__GEL_SKELETONASSET_H__
+#define	__GEL_SKELETONASSET_H__
+
+#include 
+#include 
+
+#include 
+
+namespace Ass
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+class CSkeletonAsset : public CAsset
+{
+
+public:
+        virtual int 				Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct);	// create or load the asset
+		virtual int 				Load(uint32* p_data, int data_size);		// create or load the asset from data stream
+        virtual int 				Unload();                     				// unload the asset
+        virtual int 				Reload(const char *p_file);
+		virtual bool				LoadFinished();    							// check to make sure asset is actually there
+     	virtual const char *  		Name();            							// printable name, for debugging
+		virtual EAssetType 			GetType();         							// type is hard wired into asset class 
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
+
+#endif
\ No newline at end of file
diff --git a/Code/Gel/AssMan/skinasset.cpp b/Code/Gel/AssMan/skinasset.cpp
new file mode 100644
index 0000000..44f099a
--- /dev/null
+++ b/Code/Gel/AssMan/skinasset.cpp
@@ -0,0 +1,161 @@
+///////////////////////////////////////////////////////////////////////////
+// SkinAsset.cpp
+//
+// Asset depended code for loading, unloading and reloading a skin
+//
+// Mick
+//
+
+
+#include	
+
+#include	
+						   
+#include	
+
+#include	
+#include	
+
+namespace Ass
+{
+
+int CSkinAsset::Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct)     // create or load the asset
+{
+	Dbg_MsgAssert(!async_load, ("Async load not supported on CSkinAsset"));
+
+	Mem::PushMemProfile((char*)p_file);
+
+	SSkinAssetLoadContext theContext;
+	theContext.forceTexDictLookup = false;
+	theContext.doShadowVolume = false;
+	theContext.texDictOffset = 0;
+
+	// GJ:  kludge to prevent tex dict offset clashes
+	// in bail board asset...  i will remove this
+	// after i figure out why the tex dict offsets
+	// are clashing in the first place...
+//	if ( strstr(p_file,"board_default") )
+//	{
+//		texDictOffset = -1;
+//	}
+
+	if ( pExtraData )
+	{
+		theContext = *(SSkinAssetLoadContext*)pExtraData;
+	}
+	
+	Nx::CMesh* pMesh = Nx::CEngine::sLoadMesh( p_file, theContext.texDictOffset, theContext.forceTexDictLookup, theContext.doShadowVolume );
+	if ( !pMesh )
+	{
+		Dbg_MsgAssert( 0,( "mesh %s doesn't exist.", p_file ));
+		return -1;
+	}
+
+	SetData((void*)pMesh);
+
+	// make sure the filename is in lower-case
+	char msg[128];
+	strcpy( msg, p_file );
+	Str::LowerCase( msg );
+
+	// only MDL files should load their collision data
+	if ( strstr( msg, ".mdl" ) )
+	{
+		// Might be overrided by having a "nocollision=1" as a parameter
+		int no_collision = 0;
+		if (pStruct)
+		{
+			pStruct->GetInteger(CRCD(0xbf29bc0,"nocollision"),&no_collision);
+		}
+		if (!no_collision)
+		{
+			pMesh->LoadCollision(p_file);
+		}
+	}
+	
+	// Model node arrays are now loaded via the NodeArrayComponent.
+//	pMesh->LoadModelNodeArray(p_file);
+
+	Mem::PopMemProfile(/*"Skin"*/);
+
+	return 0;
+}
+
+int CSkinAsset::Load(uint32* p_data, int data_size)     // create or load the asset
+{
+	char pDebugAssetString[256];
+	sprintf( pDebugAssetString, "skin from data stream" );
+	
+	Mem::PushMemProfile((char*)pDebugAssetString);
+
+	// GJ:  for now, we need to access 3 pointers for the MDL, TEX, and CAS
+	// data...  eventually, i'd like to store each of the 3 files
+	// separately in the asset manager, in which case we wouldn't
+	// have to do this clumsy hack of sending 3 pointers wrapped up
+	// in a struct...
+	SCutsceneModelDataInfo* p_cutscene_data = (SCutsceneModelDataInfo*)p_data;
+
+	Nx::CMesh* pMesh = Nx::CEngine::sLoadMesh( p_cutscene_data->modelChecksum,
+											   p_cutscene_data->pModelData,
+											   p_cutscene_data->modelDataSize,
+											   p_cutscene_data->pCASData,
+											   p_cutscene_data->texDictChecksum,
+											   p_cutscene_data->pTextureData,
+											   p_cutscene_data->textureDataSize,
+											   p_cutscene_data->texDictOffset,
+											   p_cutscene_data->isSkin,
+											   p_cutscene_data->doShadowVolume );
+	if ( !pMesh )
+	{
+		Dbg_MsgAssert( 0,( "Couldn't create mesh from data stream." ));
+		return -1;
+	}
+
+	SetData((void*)pMesh);
+
+	// for now, we're going to assume that data loaded in through
+	// a data stream will not have collision or a node array,
+	// since we're only using this for cutscene assets right 
+	// now...  this can be changed very easily...
+
+	Mem::PopMemProfile(/*"skin from data stream"*/);
+
+	return 0;
+}
+
+int CSkinAsset::Unload()                     // Unload the asset
+{
+	if (GetData())
+	{
+		Nx::CEngine::sUnloadMesh( (Nx::CMesh*) GetData() );
+
+        SetData(NULL);
+	}
+	
+	return 0;
+}
+
+int CSkinAsset::Reload(const char *p_file)
+{
+	return 0;
+}
+
+bool CSkinAsset::LoadFinished()
+{
+	Dbg_MsgAssert(GetData(), ("LoadFinished(): Data pointer NULL (load probably was never started)"));
+
+	// Since we don't support async, this is always true
+	return true;
+}
+
+const char *  CSkinAsset::Name()            // printable name, for debugging
+{
+	return "Skin";	
+}
+
+EAssetType CSkinAsset::GetType()         // type is hard wired into asset class 
+{
+	return ASSET_SKIN; 					// for now return 0, not sure if this should return the EAssetType
+}
+
+}
diff --git a/Code/Gel/AssMan/skinasset.h b/Code/Gel/AssMan/skinasset.h
new file mode 100644
index 0000000..43e0557
--- /dev/null
+++ b/Code/Gel/AssMan/skinasset.h
@@ -0,0 +1,59 @@
+////////////////////////////////////////////////////////////////////////////
+// skinasset.h - interface between asset manager, and the actual skin
+// provides a common interface to the asset manager
+#ifndef	__GEL_SKINASSET_H__
+#define	__GEL_SKINASSET_H__
+
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include	
+
+namespace Ass
+{
+
+struct SCutsceneModelDataInfo
+{
+	uint32		modelChecksum;
+	uint32*		pModelData;
+	int			modelDataSize;
+	uint32*		pTextureData;
+	int			textureDataSize;
+	uint8*		pCASData;
+	uint32		texDictChecksum;
+	int			texDictOffset;
+	bool		isSkin;
+	bool		doShadowVolume;
+};
+
+struct SSkinAssetLoadContext
+{
+	int			forceTexDictLookup;
+	bool		doShadowVolume;
+	int			texDictOffset;
+};
+
+class 	CSkinAsset : public CAsset
+{
+
+public:
+        virtual int 				Load(const char *p_file, bool async_load, bool use_pip, void* pExtraData, Script::CStruct *pStruct);	// create or load the asset
+		virtual int 				Load(uint32* p_data, int data_size);	// create or load the asset
+        virtual int 				Unload();                     // Unload the asset
+        virtual int 				Reload(const char *p_file);
+		virtual bool				LoadFinished();    // Check to make sure asset is actually there
+     	virtual const char *  		Name();            // printable name, for debugging
+		virtual EAssetType 			GetType();         // type is hard wired into asset class 
+private:
+		
+
+};
+
+
+} // end namespace Ass
+
+#endif			// #ifndef	__GEL_SKINASSET_H__
+
diff --git a/Code/Gel/Collision/BatchTriColl.cpp b/Code/Gel/Collision/BatchTriColl.cpp
new file mode 100644
index 0000000..91d21dc
--- /dev/null
+++ b/Code/Gel/Collision/BatchTriColl.cpp
@@ -0,0 +1,482 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx								 						**
+**																			**
+**	File name:		gel\collision\BatchTriColl.cpp 							**
+**																			**
+**	Created by:		04/12/02	-	grj										**
+**																			**
+**	Description:															**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#ifdef	__PLAT_NGPS__
+#include 
+#include 
+#include 
+#include 
+#endif //__PLAT_NGPS__
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Nx
+{
+
+#ifdef BATCH_TRI_COLLISION
+
+CollData *		CBatchTriColl::sp_coll_data;
+
+volatile bool	CBatchTriCollMan::s_processing = false;
+volatile bool	CBatchTriCollMan::s_result_processing = false;
+volatile int	CBatchTriCollMan::s_nested = 0;
+volatile bool	CBatchTriCollMan::s_found_collision = false;
+CBatchTriColl 	CBatchTriCollMan::s_tri_collision_array[MAX_BATCH_COLLISIONS][2];
+int				CBatchTriCollMan::s_current_array = 0;
+int				CBatchTriCollMan::s_array_size = 0;
+int				CBatchTriCollMan::s_next_idx_to_batch = 0;
+int				CBatchTriCollMan::s_num_collisions = 0;
+#ifdef __PLAT_NGPS__
+int 			CBatchTriCollMan::s_collision_handler_id = -1;
+bool 			CBatchTriCollMan::s_use_vu0_micro = false;
+#endif
+
+//----------------------------------------------------------------------------
+//
+
+#ifdef	__PLAT_NGPS__
+bool vu0RayTriangleCollision(const Mth::Vector *rayStart, const Mth::Vector *rayDir,
+							 Mth::Vector *v0, Mth::Vector *v1, Mth::Vector *v2, float *t)
+{
+	register bool result;
+
+	asm __volatile__(
+	"
+	.set noreorder
+	lqc2    vf06,0x0(%4)		# v0
+	lqc2    vf08,0x0(%6)		# v2
+	lqc2    vf07,0x0(%5)		# v1
+	lqc2    vf04,0x0(%2)		# rayStart
+	lqc2    vf05,0x0(%3)		# rayDir
+
+	vcallms	RayTriangleCollision	# call microsubroutine
+	vnop							# interlocking instruction, waits for vu0 completion
+
+	cfc2	%0,$vi02			# get boolean result from vu0
+	#li		%0,1				# store 1 for return value
+
+	beq		%0, $0, vu0RayTriDone	# skip copy of t if not needed
+	nop
+
+	qmfc2	$8, $vf17			# move t to $8
+	sw		$8, 0(%1)			# and write out
+
+vu0RayTriDone:
+
+	.set reorder
+	": "=r" (result), "+r" (t) : "r" (rayStart), "r" (rayDir), "r" (v0), "r" (v1) , "r" (v2) : "$8", "$9" );
+
+	return result;
+}
+#endif //	__PLAT_NGPS__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CBatchTriCollMan::sInit(CollData *p_coll_data)
+{
+	// Check for nesting; abort if true
+	if (s_processing)
+	{
+		s_nested++;
+		return false;
+	}
+
+	Dbg_Assert(s_processing == false);
+
+	s_array_size = 0;
+	s_next_idx_to_batch = 0;
+	s_found_collision = false;
+
+	CBatchTriColl::sp_coll_data = p_coll_data;
+
+#ifdef	__PLAT_NGPS__
+    // install interrupt handler for render completion
+	if (s_collision_handler_id == -1)
+	{
+		s_collision_handler_id = AddIntcHandler(INTC_VU0, s_collision_done, 0);
+		EnableIntc(INTC_VU0);
+		sceDevVu0PutTBit(1);		// Use T-bit interrupt
+	}
+#endif //	__PLAT_NGPS__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CBatchTriCollMan::sFinish()
+{
+	if (s_nested)
+	{
+		s_nested--;
+		Dbg_Assert(s_nested >= 0);
+		return;
+	}
+
+	// For now, don't wait for collision to finish, since we
+	// are trying to avoid a stall here.  But this will probably
+	// cause deadlock situations once it is on the VU0.
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CBatchTriCollMan::sAddTriCollision(const Mth::Line &test_line, const Mth::Vector &test_line_dir,
+										   CCollObj *p_collision_obj, FaceIndex face_index)
+{
+	Dbg_Assert(s_nested == 0);
+
+	CBatchTriColl *p_tri_collision;
+	p_tri_collision = &(s_tri_collision_array[s_array_size++][s_current_array]);
+
+	p_tri_collision->m_test_line = test_line;
+	p_tri_collision->m_test_line_dir = test_line_dir;
+	p_tri_collision->mp_collision_obj = p_collision_obj;
+	p_tri_collision->m_face_index = face_index;
+
+	//Dbg_Assert(s_array_size <= MAX_BATCH_COLLISIONS);
+	if (s_array_size == MAX_BATCH_COLLISIONS)
+	{
+//		sWaitTriCollisions();		// make sure were done working
+		sStartNewTriCollisions();
+
+		//s_switch_buffers();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CBatchTriCollMan::sRemoveTriCollision(CBatchTriColl *p_tri_collision)
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CBatchTriCollMan::sStartNewTriCollisions()
+{
+	Dbg_Assert(s_nested == 0);
+
+	sWaitTriCollisions();		// make sure were done working
+	Dbg_Assert(!s_processing);
+
+	// Check if we have anything to do
+	if (s_next_idx_to_batch >= s_array_size)
+	{
+		return;
+	}
+
+	s_processing = true;
+
+#ifdef	__PLAT_NGPS__
+	if(s_use_vu0_micro)
+	{
+		uint128 *p_vu0_mem = (uint128*)0x11004000;		// VU0 data memory
+
+		// Write out sizes first
+		uint32 *p_vu0_word_mem = (uint32 *) (p_vu0_mem++);
+		*(p_vu0_word_mem++) = s_next_idx_to_batch;
+		*(p_vu0_word_mem++) = s_array_size;
+
+		for (int i = s_next_idx_to_batch; i < s_array_size; i++)
+		{
+			const CBatchTriColl &tri_coll = s_tri_collision_array[i][s_current_array];
+
+			Mth::Vector *v0, *v1, *v2;
+			if (tri_coll.mp_collision_obj->mp_coll_tri_data->m_use_face_small)
+			{
+				CCollObjTriData::SFaceSmall *face = &(tri_coll.mp_collision_obj->mp_coll_tri_data->mp_face_small[tri_coll.m_face_index]);
+
+				v0 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+				v1 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+				v2 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+			} else {
+				CCollObjTriData::SFace *face = &(tri_coll.mp_collision_obj->mp_coll_tri_data->mp_faces[tri_coll.m_face_index]);
+
+				v0 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+				v1 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+				v2 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+			}
+
+			*(p_vu0_mem++) = *((uint128*) &(tri_coll.m_test_line.m_start));
+			*(p_vu0_mem++) = *((uint128*) &(tri_coll.m_test_line_dir));
+			*(p_vu0_mem++) = *((uint128*) v0);
+			*(p_vu0_mem++) = *((uint128*) v1);
+			*(p_vu0_mem++) = *((uint128*) v2);
+		}
+
+		//FlushCache(WRITEBACK_DCACHE);
+
+		//sceDevVu0PutTBit(1);		// Use T-bit interrupt
+
+		// Switch the buffers before we make the call (in case it finishes immediately)
+		s_switch_buffers();
+
+		asm __volatile__(
+		"
+		.set noreorder
+		sync.l
+		vcallms	BatchRayTriangleCollision	# call microsubroutine
+
+		.set reorder
+		");
+	}
+	else
+#endif //	__PLAT_NGPS__
+	{
+		// Just do it manually now
+		for (int i = s_next_idx_to_batch; i < s_array_size; i++)
+		{
+			const CBatchTriColl &tri_coll = s_tri_collision_array[i][s_current_array];
+
+			Mth::Vector *v0, *v1, *v2;
+			if (tri_coll.mp_collision_obj->mp_coll_tri_data->m_use_face_small)
+			{
+				CCollObjTriData::SFaceSmall *face = &(tri_coll.mp_collision_obj->mp_coll_tri_data->mp_face_small[tri_coll.m_face_index]);
+
+				v0 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+				v1 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+				v2 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+			} else {
+				CCollObjTriData::SFace *face = &(tri_coll.mp_collision_obj->mp_coll_tri_data->mp_faces[tri_coll.m_face_index]);
+
+				v0 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+				v1 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+				v2 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+			}
+
+			float distance;
+#ifdef	__PLAT_NGPS__
+			if (vu0RayTriangleCollision(&tri_coll.m_test_line.m_start, &tri_coll.m_test_line_dir, v0, v1, v2, &distance))
+#else
+			if (CCollObj::sRayTriangleCollision(&tri_coll.m_test_line.m_start, &tri_coll.m_test_line_dir,
+												v0, v1, v2, &distance))
+#endif
+			{
+				SCollSurface collisionSurface;
+
+				/* We've got one */
+				collisionSurface.point = (*v0);
+				collisionSurface.index = tri_coll.m_face_index;
+
+				// Find normal
+				Mth::Vector vTmp1(*v1 - *v0);
+				Mth::Vector vTmp2(*v2 - *v0);
+				collisionSurface.normal = Mth::CrossProduct(vTmp1, vTmp2);
+				collisionSurface.normal.Normalize();
+
+				if (CCollObj::s_found_collision(&tri_coll.m_test_line, tri_coll.mp_collision_obj, &collisionSurface, distance, CBatchTriColl::sp_coll_data))
+				{
+					s_found_collision = true;
+				}
+			}
+		}
+		s_processing = false;		// Just for manual version
+
+		s_switch_buffers();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		CBatchTriCollMan::s_collision_done(int arg)
+{
+#ifdef	__PLAT_NGPS__
+	// We should check to see if this is the T-bit first (instead of the debug D bit)
+	uint32 stat;
+    asm( "cfc2 %0, $vi29" :"=r"( stat ) : );
+    if ( !(stat & 0x4) )
+	{
+		ExitHandler();
+		return 0;
+    }
+
+	// Save floats
+    //unsigned int floatBuffer[32];
+    //saveFloatRegs(floatBuffer);
+
+	asm __volatile__(
+	"
+	.set noreorder
+	cfc2	%0,$vi01					# get number of collisions result from vu0
+
+	.set reorder
+	": "=r" (s_num_collisions) );
+
+	s_processing = false;		// Tell that we are done
+
+	if (s_num_collisions > 0)
+	{
+		s_result_processing = true;
+	}
+
+    // restore floats
+    //restoreFloatRegs(floatBuffer);
+
+	ExitHandler();
+#endif //	__PLAT_NGPS__
+
+	return 0;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CBatchTriCollMan::s_process_results()
+{
+#ifdef	__PLAT_NGPS__
+	SCollOutput *p_output = (SCollOutput*)0x11004010;		// VU0 data memory
+	int previous_array = s_current_array ^ 1;
+	for (int i = 0; i < s_num_collisions; i++)
+	{
+		const CBatchTriColl &tri_coll = s_tri_collision_array[p_output->index][previous_array];
+
+		Mth::Vector *v0, *v1, *v2;
+		if (tri_coll.mp_collision_obj->mp_coll_tri_data->m_use_face_small)
+		{
+			CCollObjTriData::SFaceSmall *face = &(tri_coll.mp_collision_obj->mp_coll_tri_data->mp_face_small[tri_coll.m_face_index]);
+
+			v0 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+			v1 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+			v2 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+		} else {
+			CCollObjTriData::SFace *face = &(tri_coll.mp_collision_obj->mp_coll_tri_data->mp_faces[tri_coll.m_face_index]);
+
+			v0 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+			v1 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+			v2 = &tri_coll.mp_collision_obj->mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+		}
+
+		SCollSurface collisionSurface;
+
+		/* We've got one */
+		collisionSurface.point = (*v0);
+		collisionSurface.index = tri_coll.m_face_index;
+
+		// Find normal
+		Mth::Vector vTmp1(*v1 - *v0);
+		Mth::Vector vTmp2(*v2 - *v0);
+		collisionSurface.normal = Mth::CrossProduct(vTmp1, vTmp2);
+		collisionSurface.normal.Normalize();
+
+		if (CCollObj::s_found_collision(&tri_coll.m_test_line, tri_coll.mp_collision_obj, &collisionSurface, p_output->distance, CBatchTriColl::sp_coll_data))
+		{
+			s_found_collision = true;
+		}
+
+		p_output++;
+	}
+
+	s_result_processing = false;
+#else
+	Dbg_Assert(s_result_processing);
+#endif //	__PLAT_NGPS__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CBatchTriCollMan::sWaitTriCollisions()
+{
+	// Wait for collision to finish
+	while (s_processing)
+		;
+
+	if (s_result_processing)
+	{
+		s_process_results();
+	}
+
+	return s_found_collision;
+}
+
+#ifdef	__PLAT_NGPS__
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CBatchTriCollMan::sUseVU0Micro()
+{
+	// Make sure we're not in the middle of something
+	Dbg_Assert(!s_processing);
+	Dbg_Assert(!s_result_processing);
+
+	return s_use_vu0_micro = NxPs2::CSystemResources::sRequestResource(NxPs2::CSystemResources::vVU0_MEMORY);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CBatchTriCollMan::sDisableVU0Micro()
+{
+	// Make sure we're not in the middle of something
+	Dbg_Assert(!s_processing);
+	Dbg_Assert(!s_result_processing);
+
+	if (s_use_vu0_micro)
+	{
+		NxPs2::CSystemResources::sFreeResource(NxPs2::CSystemResources::vVU0_MEMORY);
+		s_use_vu0_micro = false;
+	}
+}
+#endif	//	__PLAT_NGPS__
+
+#endif	// BATCH_TRI_COLLISION
+
+} // namespace Nx
+
diff --git a/Code/Gel/Collision/BatchTriColl.h b/Code/Gel/Collision/BatchTriColl.h
new file mode 100644
index 0000000..ee6fc26
--- /dev/null
+++ b/Code/Gel/Collision/BatchTriColl.h
@@ -0,0 +1,163 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx														**
+**																			**
+**	File name:		BatchTriColl.h											**
+**																			**
+**	Created: 		04/12/2002	-	grj										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__GEL_BATCHTRICOLL_H
+#define	__GEL_BATCHTRICOLL_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#ifdef __PLAT_NGPS__
+//#define BATCH_TRI_COLLISION
+#endif
+
+namespace Nx
+{
+
+#ifdef BATCH_TRI_COLLISION
+
+class CCollObj;
+class CollData;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CBatchTriColl
+{
+public:
+	Mth::Line					m_test_line;
+	Mth::Vector					m_test_line_dir;
+	CCollObj *					mp_collision_obj;
+	float						m_distance;
+	FaceIndex					m_face_index;
+
+	static CollData *			sp_coll_data;			// We should only have one at a time
+};
+
+// Results from collision
+struct SCollOutput
+{
+	float distance;
+	int pad1;
+	int pad2;
+	int index;
+};
+
+class CBatchTriCollMan
+{
+public:
+	static bool					sInit(CollData *p_coll_data);
+	static void					sFinish();
+
+	static void					sAddTriCollision(const Mth::Line &test_line, const Mth::Vector &test_line_dir,
+												 CCollObj *p_collision_obj, FaceIndex face_index);
+	static bool					sRemoveTriCollision(CBatchTriColl *p_tri_collsion);
+
+	static void					sStartNewTriCollisions();
+	static volatile bool		sIsTriCollisionDone();
+	static volatile bool		sIsNested();
+	static bool					sWaitTriCollisions();
+
+#ifdef __PLAT_NGPS__
+	static bool					sUseVU0Micro();
+	static void					sDisableVU0Micro();
+#endif
+
+protected:
+	enum
+	{
+		MAX_BATCH_COLLISIONS = 40,
+	};
+
+	// Collision callback
+	static int					s_collision_done(int);
+	static void					s_process_results();
+
+	static void					s_switch_buffers();
+
+	static volatile bool		s_processing;
+	static volatile bool		s_result_processing;
+	static volatile int			s_nested;				// Indicates nesting level, so normal collision must be used
+	static volatile bool		s_found_collision;
+
+	static CBatchTriColl		s_tri_collision_array[MAX_BATCH_COLLISIONS][2];		// Double buffered
+	static int					s_current_array;
+	static int					s_array_size;
+	static int					s_next_idx_to_batch;
+
+	static int					s_num_collisions;
+	static SCollOutput			s_collision_results[MAX_BATCH_COLLISIONS];
+
+private:
+#ifdef __PLAT_NGPS__
+	static int 					s_collision_handler_id;// for interrupt callback
+	static bool					s_use_vu0_micro;
+#endif
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline volatile bool			CBatchTriCollMan::sIsTriCollisionDone()
+{
+	return !s_processing;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline volatile bool			CBatchTriCollMan::sIsNested()
+{
+	return s_nested > 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void						CBatchTriCollMan::s_switch_buffers()
+{
+	s_array_size = 0;
+	s_next_idx_to_batch = 0;
+	s_current_array = s_current_array ^ 1;
+}
+
+#endif // BATCH_TRI_COLLISION
+
+} // namespace Nx
+
+#endif	//	__GEL_BATCHTRICOLL_H
diff --git a/Code/Gel/Collision/CollCache.cpp b/Code/Gel/Collision/CollCache.cpp
new file mode 100644
index 0000000..be73f25
--- /dev/null
+++ b/Code/Gel/Collision/CollCache.cpp
@@ -0,0 +1,333 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx								 						**
+**																			**
+**	File name:		gel\collision\collcache.cpp   							**
+**																			**
+**	Created by:		08/16/02	-	grj										**
+**																			**
+**	Description:															**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+#define PRINT_TIMES 0
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CCollCache::CCollCache()
+{
+	Clear();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CCollCache::~CCollCache()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollCache::Clear()
+{
+	m_bbox.Reset();
+
+	m_array_size = 0;
+	m_num_static_coll = 0;
+	m_num_movable_coll = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollCache::Update(const Mth::CBBox &bbox)
+{
+#if PRINT_TIMES
+	static uint64 s_total_time = 0, s_num_collisions = 0;
+	uint64 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	// Clear old cache first
+	Clear();
+
+	// Make line
+	Mth::Line is(bbox.GetMin(), bbox.GetMax());
+
+	// initialize starting "max" distance to be max collision distance
+	SSec::Manager *ss_man;
+	ss_man = Nx::CEngine::sGetNearestSuperSectorManager(is);
+//	Dbg_Assert(ss_man);
+   	if (!ss_man)
+	{
+		return;
+	}
+ 
+	// Copy bounding box
+	m_bbox = bbox;
+
+
+
+	CCollStatic** p_coll_obj_list = ss_man->GetIntersectingCollSectors( is );
+	
+	// Bloat the collision line only for the purposes of deciding against which world 
+	// sectors to test the actual collision line
+	Mth::CBBox line_bbox;
+	Mth::Vector extend;
+
+	extend = bbox.GetMax();
+	extend[X] += CCollObj::sLINE_BOX_EXTENT;
+	extend[Y] += CCollObj::sLINE_BOX_EXTENT;
+	extend[Z] += CCollObj::sLINE_BOX_EXTENT;
+	line_bbox.AddPoint(extend);
+
+	extend = bbox.GetMin();
+	extend[X] -= CCollObj::sLINE_BOX_EXTENT;
+	extend[Y] -= CCollObj::sLINE_BOX_EXTENT;
+	extend[Z] -= CCollObj::sLINE_BOX_EXTENT;
+	line_bbox.AddPoint(extend);
+
+	// Look for static collision
+    CCollStatic* p_coll_obj;
+	while((p_coll_obj = *p_coll_obj_list))
+	{
+		// TODO: Come up with a cleaner BBox check
+		//Dbg_Assert(p_coll_obj->GetGeometry());
+		//if(line_bbox.Intersect(p_coll_obj->GetGeometry()->GetBBox()))
+		if (p_coll_obj->WithinBBox(line_bbox))
+		{
+			add_static_collision(p_coll_obj);
+		}
+		p_coll_obj_list++;
+	}
+
+	// Now look for movable collision
+	Lst::Node< CCollObj > *p_movable_node = CMovableCollMan::sGetCollisionList()->GetNext();
+	while(p_movable_node)
+	{
+		CCollObj *p_coll_obj = p_movable_node->GetData();
+		if (p_coll_obj && !(p_coll_obj->m_Flags & (mSD_NON_COLLIDABLE | mSD_KILLED )))
+		{
+			if (p_coll_obj->WithinBBox(line_bbox))
+			{
+				add_movable_collision(p_coll_obj);
+			}
+		}
+		p_movable_node = p_movable_node->GetNext();
+	}
+
+#if PRINT_TIMES
+	uint64 end_time = Tmr::GetTimeInUSeconds();
+	s_total_time += end_time - start_time;
+
+	if (++s_num_collisions >= 1000)
+	{
+		Dbg_Message("Cache Update time %d us", s_total_time);
+		s_total_time = s_num_collisions = 0;
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollCache::add_static_collision(CCollStatic *p_collision)
+{
+	Dbg_Assert(p_collision);
+	Dbg_MsgAssert(m_num_movable_coll == 0, ("Can't add static collision to cache after movable collision"));
+	Dbg_MsgAssert(m_array_size < MAX_COLLISION_OBJECTS, ("Collision cache full."));
+
+	m_collision_array[m_array_size].mp_bbox = &(p_collision->GetGeometry()->GetBBox());
+	Dbg_MsgAssert(m_collision_array[m_array_size].mp_bbox, ("No bounding box found for the static collision"));
+
+	m_collision_array[m_array_size++].mp_coll_obj = p_collision;
+	
+	m_num_static_coll++;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollCache::add_movable_collision(CCollObj *p_collision)
+{
+	Dbg_Assert(p_collision);
+	Dbg_MsgAssert(m_array_size < MAX_COLLISION_OBJECTS, ("Collision cache full."));
+
+	m_collision_array[m_array_size].mp_bbox = p_collision->get_bbox();
+	//Dbg_MsgAssert(m_collision_array[m_array_size].mp_bbox, ("No bounding box found for the movable collision"));
+
+	m_collision_array[m_array_size++].mp_coll_obj = p_collision;
+
+	m_num_movable_coll++;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollCache::delete_static_collision(CCollStatic *p_collision)
+{
+	for (int i = 0; i < m_num_static_coll; i++)
+	{
+		if (m_collision_array[i].mp_coll_obj == p_collision)
+		{
+			// Must copy the whole block down
+			for (int j = i + 1; j < m_array_size; j++)
+			{
+				m_collision_array[j - 1] = m_collision_array[j];
+			}
+			m_num_static_coll--;
+			m_array_size--;
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollCache::delete_movable_collision(CCollObj *p_collision)
+{
+	for (int i = m_num_static_coll; i < m_array_size; i++)
+	{
+		if (m_collision_array[i].mp_coll_obj == p_collision)
+		{
+			m_collision_array[i] = m_collision_array[--m_array_size];
+			m_num_movable_coll--;
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollCache::delete_collision(CCollObj *p_collision)
+{
+	delete_static_collision(static_cast(p_collision));
+	delete_movable_collision(p_collision);
+}
+
+////////////////////////////////////
+
+CCollCache *	CCollCacheManager::sp_coll_cache_array[MAX_COLLISION_CACHES];
+int				CCollCacheManager::s_num_coll_caches = 0;
+
+bool			CCollCacheManager::s_assert_on_cache_miss;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollCache *	CCollCacheManager::sCreateCollCache()
+{
+	Dbg_MsgAssert(s_num_coll_caches < MAX_COLLISION_CACHES, ("Too many collision caches"));
+	sp_coll_cache_array[s_num_coll_caches] = new CCollCache;
+
+	return sp_coll_cache_array[s_num_coll_caches++];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollCacheManager::sDestroyCollCache(CCollCache *p_cache)
+{
+	bool found = false;
+
+	// Take entry out of array
+	for (int i = 0; i < s_num_coll_caches; i++)
+	{
+		if (sp_coll_cache_array[i] == p_cache)
+		{
+			// Move last in array to here
+			sp_coll_cache_array[i] = sp_coll_cache_array[--s_num_coll_caches];
+
+			found = true;
+			break;
+		}
+	}
+
+	// And do the actual deletion
+	delete p_cache;
+
+	Dbg_Assert(found);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollCacheManager::sDeleteMovableCollision(CCollObj *p_collision)
+{
+	// Check each cache
+	for (int i = 0; i < s_num_coll_caches; i++)
+	{
+		sp_coll_cache_array[i]->delete_movable_collision(p_collision);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollCacheManager::sDeleteCollision(CCollObj *p_collision)
+{
+	// Check each cache
+	for (int i = 0; i < s_num_coll_caches; i++)
+	{
+		sp_coll_cache_array[i]->delete_collision(p_collision);
+	}
+}
+
+} // namespace Nx
+
diff --git a/Code/Gel/Collision/CollCache.h b/Code/Gel/Collision/CollCache.h
new file mode 100644
index 0000000..42b1e30
--- /dev/null
+++ b/Code/Gel/Collision/CollCache.h
@@ -0,0 +1,212 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx														**
+**																			**
+**	File name:		CollCache.h												**
+**																			**
+**	Created: 		08/16/2002	-	grj										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__GEL_COLLCACHE_H
+#define	__GEL_COLLCACHE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Nx
+{
+
+class CCollObj;
+class CCollStatic;
+class CCollObjTriData;
+class CCollCacheManager;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+struct SCollCacheNode
+{
+	CCollObj			*mp_coll_obj;		// Collision object
+	const Mth::CBBox	*mp_bbox;			// Current bounding box
+};
+
+////////////////////////////////////
+
+class CCollCache
+{
+public:
+							CCollCache();
+							~CCollCache();
+
+	void					Clear();
+
+	// WARNING: The Update() function takes just as long as a normal collision call.  Make sure you
+	// plan on using the cache multiple times.
+	void					Update(const Mth::CBBox &bbox);
+
+	// Checks to see if this cache can be used
+	bool					Contains(const Mth::CBBox &test_bbox) const;
+	bool					Contains(const Mth::Line &test_line) const;
+
+	const SCollCacheNode *	GetCollisionArray() const;
+	int						GetNumCollisions() const;
+	const SCollCacheNode *	GetStaticCollisionArray() const;
+	int						GetNumStaticCollisions() const;
+	const SCollCacheNode *	GetMovableCollisionArray() const;
+	int						GetNumMovableCollisions() const;
+
+protected:
+	enum
+	{
+		MAX_COLLISION_OBJECTS = 500,
+	};
+
+	void					add_static_collision(CCollStatic *p_collision);
+	void					add_movable_collision(CCollObj *p_collision);
+	void					delete_static_collision(CCollStatic *p_collision);
+	void					delete_movable_collision(CCollObj *p_collision);
+	void					delete_collision(CCollObj *p_collision);			// In case we don't know what type (less efficient)
+
+	Mth::CBBox				m_bbox;				// bounding box where cache is valid
+	SCollCacheNode			m_collision_array[MAX_COLLISION_OBJECTS];
+	int						m_array_size;
+	int						m_num_static_coll;
+	int						m_num_movable_coll;
+
+	// Friends
+	friend CCollCacheManager;
+};
+
+////////////////////////////////////
+
+class CCollCacheManager
+{
+public:
+
+	// Allocation functions
+	static CCollCache *		sCreateCollCache();
+	static void				sDestroyCollCache(CCollCache *p_cache);
+
+	// Collision deletion functions
+	static void				sDeleteStaticCollision(CCollStatic *p_collision);
+	static void				sDeleteMovableCollision(CCollObj *p_collision);
+	static void				sDeleteCollision(CCollObj *p_collision);			// In case we don't know what type (less efficient)
+	
+	static void				sSetAssertOnCacheMiss ( bool state ) { s_assert_on_cache_miss = state; }
+	static bool				sGetAssertOnCacheMiss (   ) { return s_assert_on_cache_miss; }
+
+protected:
+	enum
+	{
+		MAX_COLLISION_CACHES = 8,
+	};
+	
+	static CCollCache *		sp_coll_cache_array[MAX_COLLISION_CACHES];
+	static int				s_num_coll_caches;
+	
+	static bool				s_assert_on_cache_miss;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CCollCache::Contains(const Mth::CBBox &test_bbox) const
+{
+	return m_bbox.Within(test_bbox);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CCollCache::Contains(const Mth::Line &test_line) const
+{
+	return (m_bbox.Within(test_line.m_start) && m_bbox.Within(test_line.m_end));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const SCollCacheNode *CCollCache::GetCollisionArray() const
+{
+	return m_collision_array;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int					CCollCache::GetNumCollisions() const
+{
+	return m_array_size;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const SCollCacheNode *CCollCache::GetStaticCollisionArray() const
+{
+	return m_collision_array;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int					CCollCache::GetNumStaticCollisions() const
+{
+	return m_num_static_coll;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const SCollCacheNode *CCollCache::GetMovableCollisionArray() const
+{
+	return &(m_collision_array[m_num_static_coll]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int					CCollCache::GetNumMovableCollisions() const
+{
+	return m_num_movable_coll;
+}
+
+} // namespace Nx
+
+#endif	//	__GEL_COLLCACHE_H
diff --git a/Code/Gel/Collision/CollEnums.h b/Code/Gel/Collision/CollEnums.h
new file mode 100644
index 0000000..456c88e
--- /dev/null
+++ b/Code/Gel/Collision/CollEnums.h
@@ -0,0 +1,46 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx														**
+**																			**
+**	File name:		CollEnums.h												**
+**																			**
+**	Created: 		02/27/2002	-	grj										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__GEL_COLLENUMS_H
+#define	__GEL_COLLENUMS_H
+
+namespace Nx
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+////////////////////////////////////////////////////////////////
+// Types of collision
+enum CollType
+{
+	vCOLL_TYPE_NONE = 0,
+	vCOLL_TYPE_BBOX,
+	vCOLL_TYPE_SPHERE,
+	vCOLL_TYPE_CYLINDRICAL,
+	vCOLL_TYPE_GEOM,
+};
+
+// Typedefs
+typedef uint16 FaceIndex;
+typedef uint8  FaceByteIndex;
+
+} // namespace Nx
+
+#endif  //	__GEL_COLLENUMS_H
diff --git a/Code/Gel/Collision/CollTriData.cpp b/Code/Gel/Collision/CollTriData.cpp
new file mode 100644
index 0000000..5dd41c4
--- /dev/null
+++ b/Code/Gel/Collision/CollTriData.cpp
@@ -0,0 +1,2239 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx								 						**
+**																			**
+**	File name:		gel\collision\collide.cpp      							**
+**																			**
+**	Created by:		02/20/02	-	grj										**
+**																			**
+**	Description:															**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 	// for face flag stuff
+#include 
+
+#include 
+
+
+// For debugging rendering
+#include 
+#include 
+
+#ifdef	__PLAT_NGPS__	
+#include 
+#include 
+#include 
+#include 
+namespace	NxPs2
+{
+	bool TestSphereAgainstOccluders( Mth::Vector *p_center, float radius, uint32 meshes = 1 );
+	bool	IsInFrame(Mth::Vector ¢er, float radius);
+}
+
+#endif
+
+#ifdef	__PLAT_NGC__
+#include 
+#include 
+#endif		// __PLAT_NGC__
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Nx
+{
+
+
+FaceIndex	CCollObjTriData::s_face_index_buffer[MAX_FACE_INDICIES];
+FaceIndex	CCollObjTriData::s_seq_face_index_buffer[MAX_FACE_INDICIES] = { 0xFFFF }; // Set to uninitialized
+uint		CCollObjTriData::s_num_face_indicies;
+
+const uint	CCollObjTriData::s_max_face_per_leaf = 20;		// maximum number faces per leaf
+const uint	CCollObjTriData::s_max_tree_levels = 7;			// maximum number of levels in a tree
+
+#define USE_BSP_CLONE
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObjTriData::CCollObjTriData()
+{
+#ifdef __PLAT_NGC__
+	mp_cloned_vert_pos = NULL;
+#endif		// __PLAT_NGC__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObjTriData::~CCollObjTriData()
+{
+	// Clones are the only types that should make it here
+	// Remove vertices
+#ifdef __PLAT_NGC__
+	if ( mp_cloned_vert_pos )
+	{
+		delete [] mp_cloned_vert_pos;
+	}
+#else
+#ifdef FIXED_POINT_VERTICES
+	if (mp_float_vert)
+	{
+		delete [] mp_float_vert;
+	}
+#else
+	if (mp_vert_pos)
+	{
+		delete[] mp_vert_pos;
+	}
+#endif // FIXED_POINT_VERTICES
+#endif		// __PLAT_NGC__
+	if (mp_intensity)
+	{
+		delete[] mp_intensity;
+	}
+
+	// Remove faces
+	if (mp_faces)
+	{
+		if (m_use_face_small)
+		{
+			delete[] mp_face_small;
+		} else{
+			delete[] mp_faces;
+		}
+	}
+
+	DeleteBSPTree();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollObjTriData::InitCollObjTriData(CScene * p_scene, void *p_base_vert_addr, void *p_base_intensity_addr,
+											void *p_base_face_addr, void *p_base_node_addr, void *p_base_face_idx_addr)
+{
+	// Set base addr
+#ifdef __PLAT_NGC__
+	NxNgc::sScene *p_engine_scene = ( static_cast( p_scene ))->GetEngineScene();
+	mp_raw_vert_pos = (NsVector*)p_engine_scene->mp_pos_pool;
+	mp_intensity = (unsigned char *) ((int) p_base_vert_addr + (int)mp_intensity); 
+	mp_cloned_vert_pos = NULL;
+#else
+#ifdef FIXED_POINT_VERTICES
+	// Get indexes
+	int start_vert_pos_offset = (int) mp_float_vert;
+	int start_intensity_index = (int) mp_intensity;
+
+	// Find first element
+	mp_float_vert = (SFloatVert *) ((int) p_base_vert_addr + start_vert_pos_offset);
+	mp_intensity = (uint8 *) p_base_intensity_addr;
+	mp_intensity += start_intensity_index;
+#else
+	// Get indexes
+	int start_vert_pos_offset = (int) mp_vert_pos;
+
+	// Find first element
+	mp_vert_pos = (Mth::Vector *) ((int) p_base_vert_addr + start_vert_pos_offset);
+
+	mp_intensity = NULL;
+#endif // FIXED_POINT_VERTICES
+#endif		// __PLAT_NGC__
+
+	mp_faces = (SFace *)((int) mp_faces + (int) p_base_face_addr);
+
+	//Dbg_Message ( "Object has %d verts sizeof %d", m_num_verts, sizeof(SReadVertex));
+	//Dbg_Message ( "Object has %d faces sizeof %d", m_num_faces, sizeof(SReadFace) );
+#ifndef FIXED_POINT_VERTICES
+	Dbg_Assert(!m_use_fixed_verts)
+#endif
+
+	// Init BSP tree
+	mp_bsp_tree = (CCollBSPNode *)((int) mp_bsp_tree + (int) p_base_node_addr);
+	s_init_tree(mp_bsp_tree, p_base_node_addr, p_base_face_idx_addr);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollBSPNode::CCollBSPNode()
+{
+	//m_node.m_split_axis = 0;
+	m_node.m_split_point = 0; 		// This is so we know it is a node
+	m_node.m_children.Init();
+	//mp_greater_branch = NULL;
+}
+
+CCollBSPNode::~CCollBSPNode()
+{
+#ifdef USE_BSP_CLONE
+	// Since the cloned BSP tree is allocated as one big buffer, these calls shouldn't
+	// be necessary.
+	return;
+
+#else
+	Dbg_MsgAssert(IsNode(), ("Called ~CCollBSPNode() on CCollBSPLeaf()"));
+
+	Dbg_MsgAssert(0, ("Only cloned collision should get to the BSP destructor"));
+#endif // USE_BSP_CLONE
+
+	if (GetLessBranch())
+	{
+		delete GetLessBranch();
+	}
+
+	if (GetGreaterBranch())
+	{
+		delete GetGreaterBranch();
+	}
+
+	if (GetFaceIndexArray())
+	{
+		delete GetFaceIndexArray();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollBSPNode::SetSplitAxis(int axis)
+{
+	Dbg_Assert(IsNode());
+	
+	m_node.m_split_point = m_node.m_split_point & ~((1 << NUM_AXIS_BITS) - 1);		// Clear out old axis
+	m_node.m_split_point = m_node.m_split_point | (axis & ((1 << NUM_AXIS_BITS) - 1));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Garrett: This is an unconventional way of cloning the data.  We are assuming that all the
+// tree nodes are in one contiguous block of memory.  I normally wouldn't write code like
+// this, but we need to avoid lots of small allocations.
+CCollBSPNode * CCollBSPNode::clone(bool instance)
+{
+	Dbg_MsgAssert(!instance, ("CCollBSPNode::clone() with instances not implemented yet"));
+	//Dbg_MsgAssert(IsNode(), ("Called CCollBSPNode::clone() on CCollBSPLeaf()"));
+
+	// So that we don't make lots of little allocations, allocate the whole BSP array at once
+	int num_nodes = count_bsp_nodes();
+	int num_face_indices = count_bsp_face_indices();
+	if (num_nodes > 0)
+	{
+		Dbg_MsgAssert(num_face_indices, ("Didn't find any face indices in the BSP tree"));
+		FaceIndex *		p_orig_face_index_array = find_bsp_face_index_array_start();
+
+		CCollBSPNode *	p_new_bsp_array = new CCollBSPNode[num_nodes];
+		FaceIndex *		p_new_face_index_array = new FaceIndex[num_face_indices];
+
+		// Just memcpy it first, since we know the source tree is in an array
+		memcpy(p_new_bsp_array, this, num_nodes * sizeof(CCollBSPNode));
+		memcpy(p_new_face_index_array, p_orig_face_index_array, num_face_indices * sizeof(FaceIndex));
+
+#ifdef	__NOPT_ASSERT__
+		// Look through new bsp array that still has the old face index pointers in it.
+		for (int node_idx = 0; node_idx < num_nodes; node_idx++)
+		{
+			if (p_new_bsp_array[node_idx].IsLeaf())
+			{
+				Dbg_MsgAssert(p_orig_face_index_array <= p_new_bsp_array[node_idx].GetFaceIndexArray(), ("find_bsp_face_index_array_start() failed"));
+			}
+		}
+#endif //	__NOPT_ASSERT__
+
+		// Now adjust the pointers by finding the differences
+		int node_address_diff = (int) p_new_bsp_array - (int) this;
+		int face_address_diff = (int) p_new_face_index_array - (int) p_orig_face_index_array;
+		for (int i = 0; i < num_nodes; i++)
+		{
+			if (p_new_bsp_array[i].IsNode())
+			{
+				// Adjust branch pointers
+				p_new_bsp_array[i].m_node.m_children.SetBasePointer((CCollBSPNode *)((int) p_new_bsp_array[i].m_node.m_children.GetBasePointer() + node_address_diff));
+			}
+			else
+			{
+				// Adjust face index pointer
+				p_new_bsp_array[i].m_leaf.mp_face_idx_array = (FaceIndex *) ((int) p_new_bsp_array[i].m_leaf.mp_face_idx_array + face_address_diff);
+			}
+		}
+
+		return p_new_bsp_array;
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int				CCollBSPNode::count_bsp_nodes()
+{
+	if (IsLeaf())
+	{
+		return 1;
+	}
+	else
+	{
+		return 1 + GetLessBranch()->count_bsp_nodes() + GetGreaterBranch()->count_bsp_nodes();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int				CCollBSPNode::count_bsp_face_indices()
+{
+	if (IsLeaf())
+	{
+		return m_leaf.m_num_faces;
+	}
+	else
+	{
+		return GetLessBranch()->count_bsp_face_indices() + GetGreaterBranch()->count_bsp_face_indices();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+FaceIndex *		CCollBSPNode::find_bsp_face_index_array_start()
+{
+	if (IsLeaf())
+	{
+		return m_leaf.mp_face_idx_array;
+	}
+	else
+	{
+		FaceIndex *p_index1 = GetLessBranch()->find_bsp_face_index_array_start();
+		FaceIndex *p_index2 = GetGreaterBranch()->find_bsp_face_index_array_start();
+
+		if (p_index1 < p_index2)
+			return p_index1;
+		else
+			return p_index2;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollBSPNode::CCollBSPChildren::SetLeftGreater(bool greater)
+{
+	 if (greater)
+	 {
+		 m_left_child_and_flags |= mLEFT_IS_GREATER;
+	 }
+	 else
+	 {
+		 m_left_child_and_flags &= ~mLEFT_IS_GREATER;
+	 }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollBSPNode::translate(const Mth::Vector & delta_trans)
+{
+	// Translate node
+	if (IsNode())
+	{
+		int axis = GetSplitAxis();
+
+		SetFSplitPoint(GetFSplitPoint() + delta_trans[axis]);
+
+		// Traverse the tree
+		GetLessBranch()->translate(delta_trans);
+		GetGreaterBranch()->translate(delta_trans);
+	}
+
+	// Leaf does nothing
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollBSPNode::rotate_y(const Mth::Vector & world_origin, Mth::ERot90 rot_y, bool root_node)
+{
+	// Take it to the origin
+	if (root_node)
+	{
+		translate(-world_origin);
+	}
+
+	// Rotate node
+	if (IsNode())
+	{
+		int axis = GetSplitAxis();
+
+		if (axis != Y)		// Y won't change
+		{
+			int other_axis = (axis == X) ? Z : X;		// So we know what axis we potentially flip to
+
+			switch (rot_y)
+			{
+			case Mth::ROT_0:
+				break;
+
+			case Mth::ROT_90:
+				if (axis == X)
+				{
+					SetSplitPoint(-GetSplitPoint());
+					m_node.m_children.SetLeftGreater(!m_node.m_children.IsLeftGreater());
+				}
+				SetSplitAxis(other_axis);
+				break;
+
+			case Mth::ROT_180:
+				SetSplitPoint(-GetSplitPoint());
+				m_node.m_children.SetLeftGreater(!m_node.m_children.IsLeftGreater());
+				break;
+
+			case Mth::ROT_270:
+				if (axis == Z)
+				{
+					SetSplitPoint(-GetSplitPoint());
+					m_node.m_children.SetLeftGreater(!m_node.m_children.IsLeftGreater());
+				}
+				SetSplitAxis(other_axis);
+				break;
+
+			default:
+				Dbg_MsgAssert(0, ("CCollBSPNode::rotate_y() out of range: %d", rot_y));
+				break;
+			}
+		}
+
+		// Traverse the tree
+		GetLessBranch()->rotate_y(world_origin, rot_y, false);
+		GetGreaterBranch()->rotate_y(world_origin, rot_y, false);
+	}
+	// Leaf does nothing
+
+	// Put it back
+	if (root_node)
+	{
+		translate(world_origin);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CCollBSPNode::scale(const Mth::Vector & world_origin, const Mth::Vector & scale, bool root_node)
+{
+	// Take it to the origin
+	if (root_node)
+	{
+		translate(-world_origin);
+	}
+
+	// Scale node
+	if (IsNode())
+	{
+		int axis = GetSplitAxis();
+
+		SetFSplitPoint(GetFSplitPoint() * scale[axis]);
+
+		// Traverse the tree
+		GetLessBranch()->scale(world_origin, scale, false);
+		GetGreaterBranch()->scale(world_origin, scale, false);
+	}
+	// Leaf does nothing
+
+	// Put it back
+	if (root_node)
+	{
+		translate(world_origin);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollObjTriData::s_init_tree(CCollBSPNode *p_tree, void *p_base_node_addr, void *p_base_face_idx_addr)
+{
+	if (p_tree->IsLeaf())
+	{
+		// Set face index array pointer
+		int face_idx = (int) p_tree->m_leaf.mp_face_idx_array;
+		p_tree->m_leaf.mp_face_idx_array = (FaceIndex *) p_base_face_idx_addr;
+		p_tree->m_leaf.mp_face_idx_array += face_idx;
+
+		//Dbg_Assert(((int) p_leaf->mp_less_branch) == -1);
+		//Dbg_Assert(((int) p_leaf->mp_greater_branch) == -1);
+
+		//p_leaf->mp_less_branch = NULL;
+		//p_leaf->mp_greater_branch = NULL;
+	} else {
+		Dbg_MsgAssert(p_tree->GetSplitAxis() < 3, ("BSP split axis is %d", p_tree->GetSplitAxis()));
+		// Set branch pointers
+		p_tree->m_node.m_children.SetBasePointer((CCollBSPNode *)((int) p_tree->m_node.m_children.GetBasePointer() + (int) p_base_node_addr));
+
+		// And init branches
+		s_init_tree(p_tree->GetLessBranch(), p_base_node_addr, p_base_face_idx_addr);
+		s_init_tree(p_tree->GetGreaterBranch(), p_base_node_addr, p_base_face_idx_addr);
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollObjTriData::InitBSPTree()
+{
+	// Initialize sequence face index list, if not done yet
+	if (s_seq_face_index_buffer[0] != 0)
+	{
+		for (int i = 0; i < MAX_FACE_INDICIES; i++)
+		{
+			s_seq_face_index_buffer[i] = i;
+		}
+	}
+
+	Dbg_MsgAssert(m_num_faces < MAX_FACE_INDICIES, ("Too many polys in a collision sector: %d", m_num_faces));
+
+	//Dbg_MsgAssert(0, ("Node size %d; Leaf size %d", sizeof(CCollBSPNode), sizeof(CCollBSPLeaf)));
+
+	// Create the tree
+   	// Don't use this since it is pip-ed
+	//mp_bsp_tree = create_bsp_tree(m_bbox, s_seq_face_index_buffer, m_num_faces);
+
+	return mp_bsp_tree != NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollObjTriData::DeleteBSPTree()
+{
+#ifndef USE_BSP_CLONE
+   	// Don't use this since it is pip-ed
+	return false;
+
+	Dbg_MsgAssert(0, ("This should only be called on clones"));
+#endif
+
+	if (mp_bsp_tree)
+	{
+#ifndef USE_BSP_CLONE
+		// Recursively delete
+		delete mp_bsp_tree;
+#else
+		// Delete as array
+		FaceIndex *p_face_index_array = mp_bsp_tree->find_bsp_face_index_array_start();
+		if (p_face_index_array)
+		{
+			delete [] p_face_index_array;
+		}
+		delete [] mp_bsp_tree;
+#endif
+		mp_bsp_tree = NULL;
+
+		return true;
+	} else {
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollObjTriData::calc_split_faces(uint axis, float axis_distance, FaceIndex *p_face_indexes,
+										  uint num_faces, uint & less_faces, uint & greater_faces,
+										  FaceIndex *p_less_face_indexes, FaceIndex *p_greater_face_indexes)
+{
+	less_faces = greater_faces = 0;
+
+	for (uint i = 0; i < num_faces; i++)
+	{
+		bool less = false, greater = false;
+
+		// Check the face
+		for (uint j = 0; j < 3; j++)
+		{
+			uint vidx = GetFaceVertIndex(i, j);
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+			if (GetRawVertexPos(vidx)[axis] < axis_distance)
+			{
+				less = true;
+			} else if (GetRawVertexPos(vidx)[axis] >= axis_distance)
+			{
+				greater = true;
+			}
+#else
+			if (mp_vert_pos[vidx][axis] < axis_distance)
+			{
+				less = true;
+			} else if (mp_vert_pos[vidx][axis] >= axis_distance)
+			{
+				greater = true;
+			}
+#endif
+		}
+
+		Dbg_Assert(less || greater);
+
+		// Increment counts and possibly put in new array
+		if (less)
+		{
+			if (p_less_face_indexes)
+			{
+				p_less_face_indexes[less_faces] = p_face_indexes[i];
+			}
+			less_faces++;
+		}
+		if (greater)
+		{
+			if (p_greater_face_indexes)
+			{
+				p_greater_face_indexes[greater_faces] = p_face_indexes[i];
+			}
+			greater_faces++;
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if 0
+CCollBSPLeaf * CCollObjTriData::create_bsp_leaf(FaceIndex *p_face_indexes, uint num_faces)
+{
+	CCollBSPLeaf *p_bsp_leaf = new CCollBSPLeaf;
+	p_bsp_leaf->m_split_axis = -1;
+	p_bsp_leaf->mp_less_branch = NULL;
+	p_bsp_leaf->mp_greater_branch = NULL;
+
+	// Make new array in BottomUp memory
+	p_bsp_leaf->m_num_faces = num_faces;
+	p_bsp_leaf->mp_face_idx_array = new FaceIndex[num_faces];
+	for (uint i = 0; i < num_faces; i++)
+	{
+		p_bsp_leaf->mp_face_idx_array[i] = p_face_indexes[i];
+	}
+
+	return p_bsp_leaf;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollBSPNode * CCollObjTriData::create_bsp_tree(const Mth::CBBox & bbox, FaceIndex *p_face_indexes, uint num_faces, uint level)
+{
+	if ((num_faces <= s_max_face_per_leaf) || (level == s_max_tree_levels)) // Check if this should be a leaf
+	{
+		return create_bsp_leaf(p_face_indexes, num_faces);
+	} else {																// Create Node
+
+		// Find initial splits on the three axis
+		Mth::Vector mid_width((bbox.GetMax() - bbox.GetMin()) * 0.5f);
+		Mth::Vector mid_split(mid_width + bbox.GetMin());
+
+		// Find the weighting of the three potential splits
+		uint less_faces[3], greater_faces[3];
+		calc_split_faces(X, mid_split[X], p_face_indexes, num_faces, less_faces[X], greater_faces[X]);
+		calc_split_faces(Y, mid_split[Y], p_face_indexes, num_faces, less_faces[Y], greater_faces[Y]);
+		calc_split_faces(Z, mid_split[Z], p_face_indexes, num_faces, less_faces[Z], greater_faces[Z]);
+
+		// Figure out best split
+		int best_axis = -1;
+		float best_diff = -1;
+		const int duplicate_threshold = (num_faces * 7) / 10;	// tunable
+		for (uint axis = X; axis <= Z; axis++)
+		{
+			float new_diff = (float)fabs((float)(less_faces[axis] - greater_faces[axis]));
+			int duplicates = less_faces[axis] + greater_faces[axis] - num_faces;
+
+			if (duplicates >= duplicate_threshold)
+				continue;
+
+			new_diff += duplicates;				// tunable
+			new_diff /= mid_width[axis];		// tunable
+
+			if ((best_axis < 0) || (new_diff < best_diff))
+			{
+				best_axis = axis;
+				best_diff = new_diff;
+			}
+		}
+
+		if (best_axis < 0)			// Couldn't make a good split, give up
+		{
+			return create_bsp_leaf(p_face_indexes, num_faces);
+		}
+
+		// We need to allocate temp arrays
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+		// Allocate new temp arrays for the face indexes
+		FaceIndex *p_less_face_indexes = new FaceIndex[less_faces[best_axis]];
+		FaceIndex *p_greater_face_indexes = new FaceIndex[greater_faces[best_axis]];
+
+		Mem::Manager::sHandle().PopContext();
+
+		// Now fill in the array
+		calc_split_faces(best_axis, mid_split[best_axis], p_face_indexes, num_faces, 
+						 less_faces[best_axis], greater_faces[best_axis],
+						 p_less_face_indexes, p_greater_face_indexes);
+
+		// And the new bboxes
+		Mth::CBBox less_bbox(bbox), greater_bbox(bbox);
+		Mth::Vector less_max = less_bbox.GetMax();
+		Mth::Vector greater_min = greater_bbox.GetMin();
+		less_max[best_axis] = mid_split[best_axis];
+		greater_min[best_axis] = mid_split[best_axis];
+		less_bbox.SetMax(less_max);
+		greater_bbox.SetMin(greater_min);
+	
+		// And now calculate the branches
+		CCollBSPNode *p_bsp_tree = new CCollBSPNode;
+		p_bsp_tree->m_split_axis = best_axis;
+		p_bsp_tree->m_split_point = mid_split[best_axis];
+		p_bsp_tree->mp_less_branch = create_bsp_tree(less_bbox, p_less_face_indexes, less_faces[best_axis], level + 1);
+		p_bsp_tree->mp_greater_branch = create_bsp_tree(greater_bbox, p_greater_face_indexes, greater_faces[best_axis], level + 1);
+
+		// Free temp arrays
+		delete p_less_face_indexes;
+		delete p_greater_face_indexes;
+
+		return p_bsp_tree;
+	}
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CCollObjTriData::find_faces(CCollBSPNode *p_bsp_node, const Mth::CBBox & bbox)
+{
+	int axis = p_bsp_node->GetSplitAxis();
+
+	if (axis == 3)	// Leaf
+	{
+		//CCollBSPLeaf *p_bsp_leaf = static_cast(p_bsp_node);		// We are certain that it is this type
+		uint num_faces = p_bsp_node->m_leaf.m_num_faces;
+		FaceIndex *p_dest_buffer = &(s_face_index_buffer[s_num_face_indicies]);
+		FaceIndex *p_src_buffer = p_bsp_node->m_leaf.mp_face_idx_array;
+
+		for (uint i = 0; i < num_faces; i++)
+		{
+			//s_face_index_buffer[s_num_face_indicies++] = p_bsp_leaf->mp_face_idx_array[i];
+			*(p_dest_buffer++) = *(p_src_buffer++);
+		}
+
+		s_num_face_indicies += num_faces;
+		Dbg_Assert(s_num_face_indicies < MAX_FACE_INDICIES);
+	} else {							// Node
+		float f_split_point = p_bsp_node->GetFSplitPoint();
+
+		if (bbox.GetMin()[axis] < f_split_point)
+		{
+			find_faces(p_bsp_node->GetLessBranch(), bbox);
+		}
+
+		if (bbox.GetMax()[axis] >= f_split_point)
+		{
+			find_faces(p_bsp_node->GetGreaterBranch(), bbox);
+		}
+	}
+}
+
+// Calculate the normal of a face
+// this is rather expensive if doing a lot of processing, so don't call
+// this function more than you need to.
+const Mth::Vector CCollObjTriData::GetFaceNormal(int face_idx) const
+{
+		Mth::Vector v0 = GetRawVertexPos(GetFaceVertIndex(face_idx,0));
+		Mth::Vector v1 = GetRawVertexPos(GetFaceVertIndex(face_idx,1));
+		Mth::Vector v2 = GetRawVertexPos(GetFaceVertIndex(face_idx,2));
+
+		// Find normal
+		Mth::Vector vTmp1(v1 - v0);
+		Mth::Vector vTmp2(v2 - v0);
+		Mth::Vector normal = Mth::CrossProduct(vTmp1, vTmp2);
+		normal.Normalize();
+		return normal;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+FaceIndex *			CCollObjTriData::FindIntersectingFaces(const Mth::CBBox & line_bbox, uint & num_faces)
+{
+	// Make sure we have a tree
+	if (!mp_bsp_tree)
+	{
+		num_faces = m_num_faces;
+		Dbg_Assert(num_faces < MAX_FACE_INDICIES);
+		return s_seq_face_index_buffer;
+	}
+
+	// Search tree
+	s_num_face_indicies = 0;
+	find_faces(mp_bsp_tree, line_bbox);
+
+	num_faces = s_num_face_indicies;
+
+	#if 0	
+	// see if there are any duplicates....
+	int dups = 0;
+	if (num_faces)
+	{
+		for (uint i = 0;imp_vert_pos	= (NsVector*)new char[CCollObjTriData::GetVertElemSize()*m_num_verts];
+	m_new_coll->mp_raw_vert_pos	= mp_raw_vert_pos;
+	intensity_array_size = m_num_faces * 3;
+#else
+	if (m_use_fixed_verts)
+	{
+		m_new_coll->mp_fixed_vert = new SFixedVert[m_num_verts];
+	}
+	else
+	{
+#ifdef FIXED_POINT_VERTICES
+		m_new_coll->mp_float_vert = new SFloatVert[m_num_verts];
+#else
+		m_new_coll->mp_vert_pos	= new Mth::Vector[m_num_verts];
+#endif
+	}
+	intensity_array_size = m_num_verts;
+#endif		// __PLAT_NGC__
+	if (m_use_face_small)
+	{
+		m_new_coll->mp_face_small = new SFaceSmall[m_num_faces];
+	} else {
+		m_new_coll->mp_faces = new SFace[m_num_faces];
+	}
+
+#ifdef FIXED_POINT_VERTICES
+	if (m_new_coll->mp_float_vert && m_new_coll->mp_faces)
+#else
+	if (m_new_coll->mp_raw_vert_pos && m_new_coll->mp_faces)
+#endif
+	{
+#ifdef __PLAT_NGC__
+		memcpy(m_new_coll->mp_faces,		mp_faces		, m_num_faces * sizeof(SFace));
+#else
+		if (m_use_fixed_verts)
+		{
+			memcpy(m_new_coll->mp_fixed_vert,	mp_fixed_vert	, m_num_verts * CCollObjTriData::GetVertSmallElemSize());
+		}
+		else
+		{
+#ifdef FIXED_POINT_VERTICES
+			memcpy(m_new_coll->mp_float_vert,	mp_float_vert	, m_num_verts * CCollObjTriData::GetVertElemSize());
+#else
+			memcpy(m_new_coll->mp_vert_pos,		mp_vert_pos		, m_num_verts * CCollObjTriData::GetVertElemSize());
+#endif
+		}
+		if (m_use_face_small)
+		{
+			memcpy(m_new_coll->mp_face_small,	mp_face_small	, m_num_faces * sizeof(SFaceSmall));
+		} else {
+			memcpy(m_new_coll->mp_faces,		mp_faces		, m_num_faces * sizeof(SFace));
+		}
+#endif		// __PLAT_NGC__
+	} else {
+		Dbg_Error("Can't allocate new collision data");
+		return NULL;
+	}
+
+	// Copy intensity array if there is one
+	if (mp_intensity)
+	{
+		m_new_coll->mp_intensity = new uint8[intensity_array_size];
+		memcpy(m_new_coll->mp_intensity, mp_intensity , intensity_array_size * sizeof(uint8));
+	}
+
+	// Set BSP tree to NULL for now until we come up with a method to copy and translate
+#ifdef USE_BSP_CLONE
+	m_new_coll->mp_bsp_tree = mp_bsp_tree->clone(instance);
+#else
+	m_new_coll->mp_bsp_tree = NULL;
+#endif // USE_BSP_CLONE
+
+	return m_new_coll;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::Translate(const Mth::Vector & delta_pos)
+{
+	Mth::Vector min_point, max_point;
+	int i;
+
+
+	// Translate BSP tree
+	if (mp_bsp_tree)
+	{
+		//printf ("WARNING:  moving colision geometry that contains a BSP tree.  *** CLEARING ***\n");
+		//mp_bsp_tree = NULL;
+		mp_bsp_tree->translate(delta_pos);
+	}
+
+	#ifdef __PLAT_NGC__
+	// Get Verts
+	Mth::Vector *p_float_verts = NULL;
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	p_float_verts = new Mth::Vector[m_num_verts];
+	Mem::Manager::sHandle().PopContext();
+	get_float_array_from_data(p_float_verts);
+	
+	// Translate
+	for ( i = 0; i < m_num_verts; i++ )
+	{
+		p_float_verts[i] += delta_pos;
+	}
+
+	// Put Verts
+	put_float_array_into_data(p_float_verts);
+	delete [] p_float_verts;
+
+
+//	if ( !mp_offset ) mp_offset = new Mth::Vector;
+//	mp_offset[0][X] += delta_pos[X];
+//	mp_offset[0][Y] += delta_pos[Y];
+//	mp_offset[0][Z] += delta_pos[Z];
+	#else
+	// Pos (don't touch W since the color is there)
+	if (!m_use_fixed_verts)
+	{
+		for (i = 0; i < m_num_verts; i++)
+		{
+	#ifdef FIXED_POINT_VERTICES
+			mp_float_vert[i].m_pos[X] += delta_pos[X];
+			mp_float_vert[i].m_pos[Y] += delta_pos[Y];
+			mp_float_vert[i].m_pos[Z] += delta_pos[Z];
+	#else
+			mp_vert_pos[i][X] += delta_pos[X];
+			mp_vert_pos[i][Y] += delta_pos[Y];
+			mp_vert_pos[i][Z] += delta_pos[Z];
+	#endif // FIXED_POINT_VERTICES
+		}
+	}
+	#endif		// __PLAT_NGC__
+
+	// BBox
+	min_point = m_bbox.GetMin() + delta_pos;
+	max_point = m_bbox.GetMax() + delta_pos;
+	m_bbox.Set(min_point, max_point);
+
+	//Dbg_Message("Min BBox (%f, %f, %f)", m_bbox.GetMin()[X], m_bbox.GetMin()[Y], m_bbox.GetMin()[Z]);
+	//Dbg_Message("Max BBox (%f, %f, %f)", m_bbox.GetMax()[X], m_bbox.GetMax()[Y], m_bbox.GetMax()[Z]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y)
+{
+	Mth::Vector min_point, max_point;
+	int i;
+
+	// Put object at origin
+	Translate(-world_origin);
+
+	// Rotate BSP tree
+	if (mp_bsp_tree)
+	{
+		mp_bsp_tree->rotate_y(world_origin, rot_y, false);		// Don't allow it to do another translation
+	}
+
+	// Convert to floating point, if fixed point data
+	Mth::Vector *p_float_verts = NULL;
+	#ifndef __PLAT_NGC__
+	if (m_use_fixed_verts)
+	#endif		// __PLAT_NGC__
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+		p_float_verts = new Mth::Vector[m_num_verts];
+		Mem::Manager::sHandle().PopContext();
+
+		get_float_array_from_data(p_float_verts);
+	}
+
+	// Rotate
+	for (i = 0; i < m_num_verts; i++)
+	{
+
+		if (p_float_verts)
+		{
+			p_float_verts[i].RotateY90(rot_y);
+		}
+		#ifndef __PLAT_NGC__
+		else
+		{
+		#ifdef FIXED_POINT_VERTICES
+			Mth::Vector pos(Mth::Vector::NO_INIT);
+			GetRawVertexPos(i, pos);
+			pos.RotateY90(rot_y);
+			mp_float_vert[i].m_pos[X] = pos[X];
+			mp_float_vert[i].m_pos[Y] = pos[Y];
+			mp_float_vert[i].m_pos[Z] = pos[Z];
+		#else
+			mp_vert_pos[i].RotateY90(rot_y);
+		#endif
+		}
+		#endif		// __PLAT_NGC__
+	}
+
+	// BBox rotate
+	min_point = m_bbox.GetMin();
+	max_point = m_bbox.GetMax();
+	min_point.RotateY90(rot_y);
+	max_point.RotateY90(rot_y);
+	m_bbox.Reset();
+	m_bbox.AddPoint(min_point);
+	m_bbox.AddPoint(max_point);
+
+	// Convert back to fixed point, if necessary
+	if (p_float_verts)
+	{
+		put_float_array_into_data(p_float_verts);
+		delete [] p_float_verts;
+	}
+
+	// Put object back
+	Translate(world_origin);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::Scale(const Mth::Vector & world_origin, const Mth::Vector & scale)
+{
+	// Put object at origin
+	Translate(-world_origin);
+
+	// Scale BSP tree
+	if (mp_bsp_tree)
+	{
+		mp_bsp_tree->scale(world_origin, scale, false);		// Don't allow it to do another translation
+	}
+
+	// Convert to floating point, if fixed point data
+	Mth::Vector *p_float_verts = NULL;
+	#ifndef __PLAT_NGC__
+	if (m_use_fixed_verts)
+	#endif		// __PLAT_NGC__
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+		p_float_verts = new Mth::Vector[m_num_verts];
+		Mem::Manager::sHandle().PopContext();
+
+		get_float_array_from_data(p_float_verts);
+	}
+
+	// Pos (don't touch W since the color is there)
+	for (int i = 0; i < m_num_verts; i++)
+	{
+		if (p_float_verts)
+		{
+			p_float_verts[i][X] *= scale[X];
+			p_float_verts[i][Y] *= scale[Y];
+			p_float_verts[i][Z] *= scale[Z];
+		}
+		#ifndef __PLAT_NGC__
+		else
+		{
+		#ifdef FIXED_POINT_VERTICES
+			mp_float_vert[i].m_pos[X] *= scale[X];
+			mp_float_vert[i].m_pos[Y] *= scale[Y];
+			mp_float_vert[i].m_pos[Z] *= scale[Z];
+		#else
+			mp_vert_pos[i][X] *= scale[X];
+			mp_vert_pos[i][Y] *= scale[Y];
+			mp_vert_pos[i][Z] *= scale[Z];
+		#endif
+		}
+		#endif		// __PLAT_NGC__
+	}
+
+	// BBox
+	Mth::Vector min_point(m_bbox.GetMin()), max_point(m_bbox.GetMax());
+
+	min_point[X] *= scale[X];
+	min_point[Y] *= scale[Y];
+	min_point[Z] *= scale[Z];
+
+	max_point[X] *= scale[X];
+	max_point[Y] *= scale[Y];
+	max_point[Z] *= scale[Z];
+
+	m_bbox.Set(min_point, max_point);
+
+	// Convert back to fixed point, if necessary
+	if (p_float_verts)
+	{
+		put_float_array_into_data(p_float_verts);
+		delete [] p_float_verts;
+	}
+
+	// Put object back
+	Translate(world_origin);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::SetRawVertexPos(int vert_idx, const Mth::Vector & pos)
+{
+	Dbg_MsgAssert(mp_bsp_tree == NULL, ("Cannot change a vertex within a BSP Tree"));
+
+	set_vertex_pos(vert_idx, pos);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::set_vertex_pos(int vert_idx, const Mth::Vector & pos)
+{
+#ifndef __PLAT_NGC__	// Since they are shared with the render data
+	if (m_use_fixed_verts)
+	{
+		Mth::Vector rel_pos(pos - m_bbox.GetMin());
+
+		Dbg_MsgAssert((rel_pos[X] >= 0.0f) && (rel_pos[Y] >= 0.0f) && (rel_pos[Z] >= 0.0f), ("Adding a vert that is outside of bounding box"));
+
+		mp_fixed_vert[vert_idx].m_pos[X] = (uint16) (rel_pos[X] * COLLISION_SUB_INCH_PRECISION);
+		mp_fixed_vert[vert_idx].m_pos[Y] = (uint16) (rel_pos[Y] * COLLISION_SUB_INCH_PRECISION);
+		mp_fixed_vert[vert_idx].m_pos[Z] = (uint16) (rel_pos[Z] * COLLISION_SUB_INCH_PRECISION);
+	}
+	else
+	{
+#ifdef FIXED_POINT_VERTICES
+		mp_float_vert[vert_idx].m_pos[X] = pos[X];
+		mp_float_vert[vert_idx].m_pos[Y] = pos[Y];
+		mp_float_vert[vert_idx].m_pos[Z] = pos[Z];
+#else
+		mp_vert_pos[vert_idx][X] = pos[X];
+		mp_vert_pos[vert_idx][Y] = pos[Y];
+		mp_vert_pos[vert_idx][Z] = pos[Z];
+#endif
+	}
+#endif		// __PLAT_NGC__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::GetRawVertices(Mth::Vector *p_vert_array) const
+{
+	get_float_array_from_data(p_vert_array);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::SetRawVertices(const Mth::Vector *p_vert_array)
+{
+	// Must set the bounding box first in case data is in fixed point
+	m_bbox.Reset();
+	for (int i = 0; i < m_num_verts; i++)
+	{
+		m_bbox.AddPoint(p_vert_array[i]);
+	}
+
+	put_float_array_into_data(p_vert_array);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define MAX_USED 80000
+
+void	CCollObjTriData::get_float_array_from_data(Mth::Vector *p_float_array) const
+{
+#ifdef __PLAT_NGC__
+	if ( mp_cloned_vert_pos )
+	{
+		// Already cloned, just copy.
+		for ( int i = 0; i < m_num_verts; i++ )
+		{
+			p_float_array[i][X] = mp_cloned_vert_pos[i].x;
+			p_float_array[i][Y] = mp_cloned_vert_pos[i].y;
+			p_float_array[i][Z] = mp_cloned_vert_pos[i].z;
+			p_float_array[i][W] = 0.0f;
+		}
+	}
+	else
+	{
+		// We need to pull the verts out of the main render vert list.
+		int lp;
+		int lp2;
+		int index;
+		unsigned char used[(MAX_USED/8)];
+
+		// Build a bit list of used verts.
+		for ( lp = 0; lp < (MAX_USED/8); lp++ ) used[lp] = 0;
+
+		for ( lp = 0; lp < m_num_faces; lp++ )
+		{
+			index = mp_faces[lp].m_vertex_index[0];
+			used[(index>>3)] |= (1<<(index&7));
+			index = mp_faces[lp].m_vertex_index[1];
+			used[(index>>3)] |= (1<<(index&7));
+			index = mp_faces[lp].m_vertex_index[2];
+			used[(index>>3)] |= (1<<(index&7));
+		}
+
+		// Copy verts out in order.
+		int num_copied = 0;
+		for ( lp = 0; lp < (MAX_USED/8); lp++ )
+		{
+			if ( used[lp] )
+			{
+				for ( lp2 = 0; lp2 < 8; lp2++ )
+				{
+					if ( used[lp] & (1<>3)] |= (1<<(index&7));
+			index = mp_faces[lp].m_vertex_index[1];
+			used[(index>>3)] |= (1<<(index&7));
+			index = mp_faces[lp].m_vertex_index[2];
+			used[(index>>3)] |= (1<<(index&7));
+		}
+
+		// Remap faces.
+		int num_copied = 0;
+		for ( lp = 0; lp < (MAX_USED/8); lp++ )
+		{
+			if ( used[lp] )
+			{
+				for ( lp2 = 0; lp2 < 8; lp2++ )
+				{
+					if ( used[lp] & (1<m_flags & ( 1 << 31 ))
+		{
+			continue;
+		}
+		
+		Mth::Vector	v[4];
+		v[0] = GetRawVertexPos(GetFaceVertIndex(fidx0, 0));
+		v[1] = GetRawVertexPos(GetFaceVertIndex(fidx0, 1));
+		v[2] = GetRawVertexPos(GetFaceVertIndex(fidx0, 2));
+//		printf( "Occlusion Triangle %d\n(%f,%f,%f)\n(%f,%f,%f)\n(%f,%f,%f)\n",fidx0,v[0][X],v[0][Y],v[0][Z],v[1][X],v[1][Y],v[1][Z],v[2][X],v[2][Y],v[2][Z] );
+
+		for( int fidx1 = fidx0 + 1; fidx1 < m_num_faces; ++fidx1 )
+		{
+			SFaceInfo *p_face1 = get_face_info(fidx1);
+
+			// Check we haven't already used this face.
+			if( p_face1->m_flags & ( 1 << 31 ))
+			{
+				continue;
+			}
+
+			v[0] = GetRawVertexPos(GetFaceVertIndex(fidx1, 0));
+			v[1] = GetRawVertexPos(GetFaceVertIndex(fidx1, 1));
+			v[2] = GetRawVertexPos(GetFaceVertIndex(fidx1, 2));
+//			printf( "Occlusion Triangle %d\n(%f,%f,%f)\n(%f,%f,%f)\n(%f,%f,%f)\n",fidx1,v[0][X],v[0][Y],v[0][Z],v[1][X],v[1][Y],v[1][Z],v[2][X],v[2][Y],v[2][Z] );
+
+			int indices_matched0[3]	= { -1, -1, -1 };
+			int indices_matched1[3]	= { -1, -1, -1 };
+			int num_indices_matched	= 0;
+
+			for( int i = 0; i < 3; ++i )
+			{
+				if( GetFaceVertIndex(fidx0, i) == GetFaceVertIndex(fidx1, 0) )
+				{
+					indices_matched0[i] = GetFaceVertIndex(fidx0, i);
+					indices_matched1[0] = GetFaceVertIndex(fidx1, 0);
+					++num_indices_matched;
+				}
+				else if( GetFaceVertIndex(fidx0, i) == GetFaceVertIndex(fidx1, 1) )
+				{
+					indices_matched0[i] = GetFaceVertIndex(fidx0, i);
+					indices_matched1[1] = GetFaceVertIndex(fidx1, 1);
+					++num_indices_matched;
+				}
+				else if( GetFaceVertIndex(fidx0, i) == GetFaceVertIndex(fidx1, 2) )
+				{
+					indices_matched0[i] = GetFaceVertIndex(fidx0, i);
+					indices_matched1[2] = GetFaceVertIndex(fidx1, 2);
+					++num_indices_matched;
+				}
+			}
+
+			// Three indices matched is just plain wrong.
+			Dbg_Assert( num_indices_matched < 3 );
+
+			// Two indices matched is just what we want.
+			if( num_indices_matched == 2 )
+			{
+				// Make sure that the 2 tris lie in the same plane, which is no longer necessarily the case since they could be
+				// tris which adjoin at a right angle.
+				v[0]				= GetRawVertexPos(GetFaceVertIndex( fidx0, 0 ));
+				v[1]				= GetRawVertexPos(GetFaceVertIndex( fidx0, 1 ));
+				v[2]				= GetRawVertexPos(GetFaceVertIndex( fidx0, 2 ));
+				Mth::Vector norm0	= Mth::CrossProduct( v[1] - v[0], v[2] - v[0] );
+				v[0]				= GetRawVertexPos(GetFaceVertIndex( fidx1, 0 ));
+				v[1]				= GetRawVertexPos(GetFaceVertIndex( fidx1, 1 ));
+				v[2]				= GetRawVertexPos(GetFaceVertIndex( fidx1, 2 ));
+				Mth::Vector norm1	= Mth::CrossProduct( v[1] - v[0], v[2] - v[0] );
+				norm0.Normalize();
+				norm1.Normalize();
+				
+				if(( fabs( norm1[X]-norm0[X]) <= 0.05f ) && ( fabs( norm1[Y]-norm0[Y]) <= 0.05f ) && ( fabs( norm1[Z]-norm0[Z]) <= 0.05f ))
+				{
+								
+					// These two tris are coplanar.
+				
+					// Get the index from tri1 that isn't shared with tri0.
+					int unshared_tri1_index = -1;
+					for( int i = 0; i < 3; ++i )
+					{
+						if( indices_matched1[i] == -1 )
+						{
+							unshared_tri1_index = GetFaceVertIndex(fidx1, i);
+							break;
+						}
+					}
+					Dbg_Assert( unshared_tri1_index >= 0 );
+				
+					// Fill in the missing 2 verts, one from each tri. What is important here is which side of triangle0 that
+					// that triangle1 shares, since this will determine the overall order of the quad abcd.
+					if(( indices_matched0[0] >= 0 ) && ( indices_matched0[1] >= 0 ))
+					{
+						// They share side 0->1. Thus the quad will be t0(0), t1(unshared), t0(1), t0(2).
+						v[0] = GetRawVertexPos(GetFaceVertIndex(fidx0, 0));
+						v[1] = GetRawVertexPos(unshared_tri1_index);
+						v[2] = GetRawVertexPos(GetFaceVertIndex(fidx0, 1));
+						v[3] = GetRawVertexPos(GetFaceVertIndex(fidx0, 2));
+					}
+					else if(( indices_matched0[1] >= 0 ) && ( indices_matched0[2] >= 0 ))
+					{
+						// They share side 1->2. Thus the quad will be t0(0), t0(1), t1(unshared), t0(2).
+						v[0] = GetRawVertexPos(GetFaceVertIndex(fidx0, 0));
+						v[1] = GetRawVertexPos(GetFaceVertIndex(fidx0, 1));
+						v[2] = GetRawVertexPos(unshared_tri1_index);
+						v[3] = GetRawVertexPos(GetFaceVertIndex(fidx0, 2));
+					}
+					else if(( indices_matched0[0] >= 0 ) && ( indices_matched0[2] >= 0 ))
+					{
+						// They share side 2->0. Thus the quad will be t0(0), t0(1), t0(2), t1(unshared).
+						v[0] = GetRawVertexPos(GetFaceVertIndex(fidx0, 0));
+						v[1] = GetRawVertexPos(GetFaceVertIndex(fidx0, 1));
+						v[2] = GetRawVertexPos(GetFaceVertIndex(fidx0, 2));
+						v[3] = GetRawVertexPos(unshared_tri1_index);
+					}
+					else
+					{
+						Dbg_Assert( 0 );
+					}
+
+//					printf( "Occlusion Quad\n(%f,%f,%f)\n(%f,%f,%f)\n(%f,%f,%f)\n(%f,%f,%f)\n",v[0][X],v[0][Y],v[0][Z],v[1][X],v[1][Y],v[1][Z],v[2][X],v[2][Y],v[2][Z],v[3][X],v[3][Y],v[3][Z] );
+
+					// Register this quad with the engine.
+					Nx::CEngine::sAddOcclusionPoly( 4, v, m_checksum );
+				
+					// Flag the two tris as having been used.
+					Dbg_Assert(( p_face0->m_flags & ( 1 << 31 )) == 0 );
+					Dbg_Assert(( p_face1->m_flags & ( 1 << 31 )) == 0 );
+					p_face0->m_flags |= ( 1 << 31 );
+					p_face1->m_flags |= ( 1 << 31 );
+				
+					// Break out of this inner loop now since we have found a pair.
+					break;
+				}
+			}
+		}
+	}
+
+	// Scan through and clear 'used' flag on faces.
+	for( int fidx0 = 0; fidx0 < m_num_faces; ++fidx0 )
+	{
+		SFaceInfo *p_face0 = get_face_info(fidx0);
+		p_face0->m_flags &= ~( 1 << 31 );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef	__DEBUG_CODE__
+
+void	CCollObjTriData::DebugRender(uint32 ignore_1, uint32 ignore_0)
+{
+#ifdef	__NOPT_ASSERT__
+	static Mth::Matrix ident_matrix(0.0f, 0.0f, 0.0f);	// static so it doesn't waste time in the constuctor
+
+	// simple culling based on the world bbox
+	Mth::Vector mid = (GetBBox().GetMax() + GetBBox().GetMin())/2.0f;
+	float	radius = (mid - GetBBox().GetMin()).Length();
+
+	if (Nx::CEngine::GetWireframeMode() == 6)	 // 6 = occlusion	  
+	{
+		// Rendering as 2d, to demontrate occlusion
+		// we try to get the visibility flag from the engine
+
+#if	0		//def	__PLAT_NGPS__	
+	bool	drawn = true;
+
+	Nx::CPs2Sector* p_sector = (Nx::CPs2Sector*)Nx::CEngine::sGetSector(m_checksum);
+	if (p_sector)
+	{
+	
+		NxPs2::CGeomNode *p_node = (p_sector->GetEngineObject());
+		if (p_node)
+		{
+			drawn = p_node->WasRendered();		
+		}
+	}
+
+
+//
+	if (drawn)
+#else
+		if ( Nx::CEngine::sIsVisible(mid,radius))
+#endif	
+		{
+			Nx::CSector* p_sector = Nx::CEngine::sGetSector(m_checksum); // SLOWWWWWWWWWWWWW
+			if (p_sector && !p_sector->GetVisibility())
+			{
+				DebugRender2D(ignore_1, ignore_0,0x000080);   			// red = hidden
+			}
+			else
+			{
+				DebugRender2D(ignore_1, ignore_0,0x808080);   			// grey = visible
+			}
+		}
+		else
+		{
+			#ifdef	__PLAT_NGPS__
+			// Mick:  Should be abel to use the actual Occluded bits here....
+			if (NxPs2::IsInFrame( mid, radius ) && NxPs2::TestSphereAgainstOccluders(&mid,radius,0))
+				DebugRender2D(ignore_1, ignore_0,0x40c040);	   // green = occluded 
+			else
+			#endif
+			{
+				DebugRender2DBBox(ignore_1, ignore_0,0x003030);		// dk yellow box and 
+				DebugRender2DOct(ignore_1, ignore_0,0x003000);		// dark green ocotogon off camera
+			}
+		}
+	}
+	else
+	{
+//	    if ( Nx::CEngine::sIsVisible(mid,radius))
+#		ifdef __PLAT_NGPS__	
+   		if (NxPs2::IsInFrame( mid, radius ))
+			DebugRender(ident_matrix, ignore_1, ignore_0, false);
+#		endif
+	}
+#endif	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::DebugRender(const Mth::Matrix & transform, uint32 ignore_1, uint32 ignore_0, bool do_transform)
+{
+#ifdef	__PLAT_NGPS__	
+
+	uint32	rgb = 0x000000;
+	int n = m_num_faces;	
+	int r=0;
+	int g=0;
+	int b=0;
+	
+	NxPs2::DMAOverflowOK = 2;
+
+
+	switch (Nx::CEngine::GetWireframeMode())
+	{
+		case 1:	// Polygon density, red = high, white=higher 
+		{
+			#define	peak 500	
+			if (n <= peak )
+			{
+				r = (255 * (n)) / peak;  	// r ramps up (black to red)	
+			}
+			else if (n <= peak * 2)
+			{
+				r = 255;
+				b = (255 * (n - peak) / (peak));	// b&g ramps up (to white)
+				g = b;
+				
+			}
+			else
+			{
+				r = g = b = 255;
+			}
+			break;
+		}
+		case 2:	// Low Polygon density, red = high, white=higher 
+		{
+			#define	peak 500	
+			if (n <= 2 )
+			{
+				r = g = b = 255;			// White = two or less
+			}
+			else if (n <= 4)
+			{
+				g = 255;				   // Green = 4 or less
+				
+			}
+			else if (n <= 8)
+			{
+				b = 255;				   // blue = 8 or less
+			}
+			else if (n <= 16)			   
+			{
+				r = b = 255;			   // Magenta = 16 or less
+			}
+			break;
+		}
+		case 3:	   	// Each object a different color, based on checksum
+		{
+			r = (m_checksum >>16) & 0xff;
+			g = (m_checksum >>8) & 0xff;
+			b = (m_checksum ) & 0xff;
+		}
+		default:
+			break;	
+	}
+
+
+	// fix up anything I did stupid
+	if (r<0) r=0;
+	if (r>255) r=255;
+	if (g<0) g=0;
+	if (g>255) g=255;
+	if (b<0) b=0;
+	if (b>255) b=255;
+
+	rgb = (b<<16)|(g<<8)|r;	
+	
+	uint32 current = 12345678;
+
+
+		#if 0
+		// scan through all verts, and see if any are close, but not close enough....
+		// obviously this will be somewhat slow
+		NxPs2::BeginLines3D(0x80000000 + (0x00ff00ff));		// Magenta = unwelded verts
+
+		for (int i=0;i0.0000001f && len < 1.0f)
+				{
+				
+					Mth::Vector *v0,*v1;
+					v0 = &mp_vert_pos[i];
+					v1 = &mp_vert_pos[j];
+					NxPs2::DrawLine3D((*v0)[X],(*v0)[Y],(*v0)[Z],(*v0)[X],(*v0)[Y]+100.0f,(*v0)[Z]);								
+					NxPs2::DrawLine3D((*v1)[X],(*v1)[Y],(*v1)[Z],(*v1)[X],(*v1)[Y]+100.0f,(*v1)[Z]);								
+				}
+			}
+		}
+		NxPs2::EndLines3D();
+		return;
+
+		
+		#endif
+
+
+	Mth::Vector v0, v1, v2;
+	
+
+	for (int fidx = 0; fidx < m_num_faces; fidx++)
+	{
+		SFaceInfo *face = get_face_info(fidx);
+
+		if (!(face->m_flags & ignore_1) && !(~face->m_flags & ignore_0))
+		{
+		
+		if (Nx::CEngine::GetWireframeMode() == 0)   // face flags
+		{
+			rgb = 0xffffff;						// white = no flags
+			if (face->m_flags & mFD_VERT)
+			{
+				rgb = 0x4040ff;			   		// red = vert
+			}				
+			if (face->m_flags & mFD_TRIGGER)
+			{
+				rgb = 0xff4040;				    // blue = trigger
+			}				
+			if (face->m_flags & mFD_WALL_RIDABLE)
+			{
+				rgb = 0x00ffff;				   // yellow = wallride
+			}				
+		}			   
+				   
+			// check for context changes
+			if (current != rgb)
+			{
+				if (current == 12345678)
+				{
+					NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));
+				}
+				else
+				{
+		    		NxPs2::ChangeLineColor(0x80000000 + (0x00ffffff & rgb));
+				}
+				current = rgb;
+			}
+
+#ifdef FIXED_POINT_VERTICES
+			if (do_transform)
+			{
+				v0 = transform.TransformAsPos(GetRawVertexPos(GetFaceVertIndex(fidx, 0)));
+				v1 = transform.TransformAsPos(GetRawVertexPos(GetFaceVertIndex(fidx, 1)));
+				v2 = transform.TransformAsPos(GetRawVertexPos(GetFaceVertIndex(fidx, 2)));
+			}
+			else
+			{
+				v0 = GetRawVertexPos(GetFaceVertIndex(fidx, 0));
+				v1 = GetRawVertexPos(GetFaceVertIndex(fidx, 1));
+				v2 = GetRawVertexPos(GetFaceVertIndex(fidx, 2));
+			}
+#else
+			if (do_transform)
+			{
+				v0 = transform.TransformAsPos(mp_vert_pos[GetFaceVertIndex(fidx, 0)]);
+				v1 = transform.TransformAsPos(mp_vert_pos[GetFaceVertIndex(fidx, 1)]);
+				v2 = transform.TransformAsPos(mp_vert_pos[GetFaceVertIndex(fidx, 2)]);
+			}
+			else
+			{
+				v0 = mp_vert_pos[GetFaceVertIndex(fidx, 0)];
+				v1 = mp_vert_pos[GetFaceVertIndex(fidx, 1)];
+				v2 = mp_vert_pos[GetFaceVertIndex(fidx, 2)];
+			}
+#endif // FIXED_POINT_VERTICES
+
+			NxPs2::DrawLine3D(v0[X], v0[Y], v0[Z], v1[X], v1[Y], v1[Z]);
+			NxPs2::DrawLine3D(v0[X], v0[Y], v0[Z], v2[X], v2[Y], v2[Z]);
+			NxPs2::DrawLine3D(v2[X], v2[Y], v2[Z], v1[X], v1[Y], v1[Z]);
+		}
+	}
+	// only if we actually drew some
+	if ( current != 12345678)
+	{
+		NxPs2::EndLines3D();
+	}
+#else
+	const uint32 rgba = 0x0000FF80;
+
+	for (int fidx = 0; fidx < m_num_faces; fidx++)
+	{
+		Mth::Vector v0, v1, v2;
+
+		if (do_transform)
+		{
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+			v0 = transform.TransformAsPos(GetRawVertexPos(GetFaceVertIndex(fidx, 0)));
+			v1 = transform.TransformAsPos(GetRawVertexPos(GetFaceVertIndex(fidx, 1)));
+			v2 = transform.TransformAsPos(GetRawVertexPos(GetFaceVertIndex(fidx, 2)));
+#else
+			v0 = transform.TransformAsPos(mp_vert_pos[GetFaceVertIndex(fidx, 0)]);
+			v1 = transform.TransformAsPos(mp_vert_pos[GetFaceVertIndex(fidx, 1)]);
+			v2 = transform.TransformAsPos(mp_vert_pos[GetFaceVertIndex(fidx, 2)]);
+#endif // FIXED_POINT_VERTICES
+		}
+		else
+		{
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+			v0 = GetRawVertexPos(GetFaceVertIndex(fidx, 0));
+			v1 = GetRawVertexPos(GetFaceVertIndex(fidx, 1));
+			v2 = GetRawVertexPos(GetFaceVertIndex(fidx, 2));
+#else
+			v0 = mp_vert_pos[GetFaceVertIndex(fidx, 0)];
+			v1 = mp_vert_pos[GetFaceVertIndex(fidx, 1)];
+			v2 = mp_vert_pos[GetFaceVertIndex(fidx, 2)];
+#endif // FIXED_POINT_VERTICES
+		}		
+		// Draw Triangle
+		Gfx::AddDebugLine(v0, v1, rgba, rgba, 1);
+		Gfx::AddDebugLine(v1, v2, rgba, rgba, 1);
+		Gfx::AddDebugLine(v2, v0, rgba, rgba, 1);
+	}
+#endif //	__PLAT_NGPS__	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static Mth::Vector x;
+static Mth::Vector y;
+
+inline void rot(Mth::Vector &v)
+{
+	float vx = v[X];
+	float vy = v[Z];
+	v[X] = vx * x[X] + vy * x[Z];
+	v[Z] = vx * y[X] + vy * y[Z];
+}
+
+
+
+
+static float	debug_2d_scale =  0.02f;
+static Mth::Vector s_overhead_cam_pos;
+
+void	_debug_change_2d_scale(float x)
+{
+	debug_2d_scale *= x;
+	if (debug_2d_scale > 0.5f) debug_2d_scale = 0.5f;
+	if (debug_2d_scale < 0.002f) debug_2d_scale = 0.002f;
+	
+}
+
+void	OverheadLine(Mth::Vector v0, Mth::Vector v1)
+{
+#ifdef	__PLAT_NGPS__	
+
+	Mth::Vector offset;
+	offset.Set(320.0f,0.0f,224.0f);
+
+	v0 -= s_overhead_cam_pos;
+	v1 -= s_overhead_cam_pos;
+
+	v0 *= debug_2d_scale;
+	v1 *= debug_2d_scale;
+
+	rot(v0);
+	rot(v1);
+
+	v0 += offset;
+	v1 += offset;
+	
+	if (v0[X] < 0 && v1[X] < 0) return;
+	if (v0[Z] < 0 && v1[Z] < 0) return;
+	if (v0[X] > 639 && v1[X] >639) return;
+	if (v0[Z] > 447 && v1[Z] > 447) return;
+
+
+	// Some simple clipping
+
+	NxPs2::DrawLine2D(v0[X], v0[Z], 0.0f, v1[X],  v1[Z],0.0f);
+
+#endif
+}
+
+
+void	CCollObjTriData::DebugRender2D(uint32 ignore_1, uint32 ignore_0, uint32 visible)
+{
+#ifdef	__PLAT_NGPS__	
+
+
+	Gfx::Camera *cur_camera = Nx::CViewportManager::sGetCamera( 0 );
+	s_overhead_cam_pos = cur_camera->GetPos();
+
+	// get orientation from the camere
+	y = cur_camera->GetMatrix()[Z];
+	y[Y] = 0.0f;
+	y.Normalize();
+	x[X] =   y[Z];
+	x[Y] = 0.0f;
+	x[Z] = - y[X];
+
+	uint32	rgb = visible;		 		// blue = visible
+	Mth::Vector v0, v1, v2;
+	NxPs2::BeginLines2D(0x80000000 + (0x00ffffff & rgb));
+	for (int fidx = 0; fidx < m_num_faces; fidx++)
+	{
+		SFaceInfo *face = get_face_info(fidx);
+		if (!(face->m_flags & ignore_1) && !(~face->m_flags & ignore_0))
+		{
+			GetRawVertexPos(GetFaceVertIndex(fidx, 0), v0);
+			GetRawVertexPos(GetFaceVertIndex(fidx, 1), v1);
+			GetRawVertexPos(GetFaceVertIndex(fidx, 2), v2);
+			OverheadLine(v0,v1);
+			OverheadLine(v1,v2);
+			OverheadLine(v2,v0);
+		}
+	}
+	NxPs2::EndLines2D();
+#endif
+}
+
+void	CCollObjTriData::DebugRender2DBBox(uint32 ignore_1, uint32 ignore_0, uint32 visible)
+{
+#ifdef	__PLAT_NGPS__	
+
+
+	Gfx::Camera *cur_camera = Nx::CViewportManager::sGetCamera( 0 );
+
+	// get orientation from the camere
+	y = cur_camera->GetMatrix()[Z];
+	y[Y] = 0.0f;
+	y.Normalize();
+	x[X] =   y[Z];
+	x[Y] = 0.0f;
+	x[Z] = - y[X];
+
+	uint32	rgb = visible;		 		// blue = visible
+	
+	Mth::Vector v0, v1, v2,v3;
+	
+	NxPs2::BeginLines2D(0x80000000 + (0x00ffffff & rgb));
+	v0 = GetBBox().GetMax();
+	v2 = GetBBox().GetMin();
+	v1[X] = v2[X];
+	v1[Z] = v0[Z];
+	v3[X] = v0[X];
+	v3[Z] = v2[Z];
+	
+	OverheadLine(v0,v1);
+	OverheadLine(v1,v2);
+	OverheadLine(v2,v3);
+	OverheadLine(v3,v0);
+	NxPs2::EndLines2D();
+#endif
+}
+
+// draw as an octagon, to simultate the bounding sphere									   
+void	CCollObjTriData::DebugRender2DOct(uint32 ignore_1, uint32 ignore_0, uint32 visible)
+{
+#ifdef	__PLAT_NGPS__	
+
+
+	Gfx::Camera *cur_camera = Nx::CViewportManager::sGetCamera( 0 );
+
+	// get orientation from the camere
+	y = cur_camera->GetMatrix()[Z];
+	y[Y] = 0.0f;
+	y.Normalize();
+	x[X] =   y[Z];
+	x[Y] = 0.0f;
+	x[Z] = - y[X];
+
+	uint32	rgb = visible;		 		// blue = visible
+	
+	Mth::Vector v[8];
+	
+	Mth::Vector offset;
+	
+	offset.Set(320.0f,0.0f,224.0f);
+
+
+	// simple culling based on the world bbox
+	Mth::Vector mid = (GetBBox().GetMax() + GetBBox().GetMin())/2.0f;
+	float	radius = (mid - GetBBox().GetMin()).Length();
+	
+	float	half = radius * 0.707106f;		
+
+
+	v[0].Set(-half,0,-half);
+	v[2].Set( half,0,-half);
+	v[4].Set( half,0, half);
+	v[6].Set(-half,0, half);
+	v[1].Set( 0     ,0,-radius);
+	v[3].Set( radius,0, 0     );
+	v[5].Set( 0     ,0, radius);
+	v[7].Set(-radius,0, 0     );
+
+	NxPs2::BeginLines2D(0x80000000 + (0x00ffffff & rgb));
+	for (int i=0;i<8;i++)
+	{
+		v[i] += mid;
+	}
+	for (int i=0;i<8;i++)
+	{
+		OverheadLine(v[i], v[(i+1)&7]);
+	}
+	NxPs2::EndLines2D();
+#endif
+}
+
+#else
+// Stub function for
+void	CCollObjTriData::DebugRender(uint32 ignore_1, uint32 ignore_0) {}
+void	CCollObjTriData::DebugRender(const Mth::Matrix & transform, uint32 ignore_1, uint32 ignore_0, bool do_transform) {}
+
+#endif
+
+// Checks to see if a coll sector has any potential "Uberfrig" holes or seams
+// that would be where there is NO ground beneath a point
+// Algorithm:
+// for each edge or each triangle
+//  find the midpoint of the edge
+//  for left and right sides
+//   check if there is a hole at either of 0.000001, 0.0001, 0.1, 1.0 inches away (perp to edge)
+//	 if hole, then check six feet away
+//     if collision six feet away, then we classify this as a hole, and flag it in the level 
+//		                                (flag the edge, and the point with a hole)
+//
+// Note: the checks encompass the whole world, not just this sector
+#ifdef	__DEBUG_CODE__
+
+bool CheckEdgeAt(Mth::Vector& mid, Mth::Vector& left, float dist, CFeeler& feeler)
+{
+
+	float up = 1000.0f;
+
+/*
+	// if we are next to a wall, then we need to check from a much greater height
+	feeler.SetLine(mid + left * 10.0f + up, mid - left * 10.0f + up);
+	if (feeler.GetCollision())
+	{
+		up = 1000.0f;
+	}
+	else
+	{
+		// need to check for wall from both directions, I think
+		feeler.FlipDirection();
+		if (feeler.GetCollision())
+		{
+			up = 1000.0f;		
+		}
+	}
+*/
+	
+	feeler.SetLine(mid + left * dist + Mth::Vector(0.0f,up,0.0f),
+				   mid + left * dist + Mth::Vector(0.0f,-1000.0f,0.0f));
+//				   mid + left * dist + Mth::Vector(0.0f,-5800.0f,0.0f));
+				   
+//	feeler.DebugLine(0,255,0);				   
+				   
+	return feeler.GetCollision(false);	
+}									  
+									  
+void			CheckEdgeForHoles(Mth::Vector v0, Mth::Vector v1)
+{
+	Mth::Vector	mid = (v0 + v1) / 2.0f;
+	Mth::Vector left = v1 - v0;
+	left.Normalize();
+	if (left[X] == 0.0f && left[Z] == 0.0f)
+	{
+		// edge is perfectly vertical, so return
+		return;
+	}
+
+	// make left vector be at right angles to the edge in the XZ plane	
+	float x = left[X];
+	left[X] = left[Z];
+	left[Z] = -x;
+	left[Y] = 0;
+
+	CFeeler	feeler;
+	
+	// first check six feet away, to eliminate around the edge of the level 
+
+	if (!CheckEdgeAt(mid, left, 72.0f,feeler))
+	{
+		// hole six feet away, so return
+//		Gfx::AddDebugLine(mid+left*60.0f, mid+left*60.0f * 10.0f,0xff0000);
+		return;
+	}
+
+
+	for (float x = 0.001f; x<0.0015f; x *= 10.0f)   // does 0.001, 0.01, 0.10
+	{
+		if (!CheckEdgeAt(mid,left,x,feeler))
+		{
+			// Found a hole!!!
+			// we draw bunch of lines of differning length, so we can see it when we zoom in
+			
+			Gfx::AddDebugLine(v0, v1,0xff00ff);  // magenta = bad edge
+
+//			feeler.DebugLine(0,0,255);			// blue = actual collision line			
+			Gfx::AddDebugLine(mid+left*x, mid+left*x + Mth::Vector(0,40,0),0xffff);  
+			Gfx::AddDebugLine(mid+left*x, mid+left*x + Mth::Vector(0,200,0),0xffff); 
+			Gfx::AddDebugLine(mid+left*x, mid+left*x + Mth::Vector(0,1000,0),0xffff);
+		}
+	}
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollObjTriData::CheckForHoles()
+{
+
+	printf ("\nChecking for unvelded verts in object with %d verts\n",m_num_verts);
+
+	for (int i=0;i0.0000001f && len < 0.1f)
+			{
+			
+#ifndef __PLAT_NGC__
+				Gfx::AddDebugLine(GetRawVertexPos(i), GetRawVertexPos(i) + Mth::Vector(0,400,0),0xff00ff);  
+				Gfx::AddDebugLine(GetRawVertexPos(i), GetRawVertexPos(i) + Mth::Vector(0,40,0),0xff00ff);  
+#endif		// __PLAT_NGC__
+				printf ("Found one, at dist %f,m check the magenta lines\n",len);
+			}
+		}
+	}
+
+
+	printf ("Then Checking for holes in object with %d faces\n",m_num_faces);
+	for (int fidx = 0; fidx < m_num_faces; fidx++)
+	{
+		
+		SFaceInfo *face = get_face_info(fidx);
+		if (!(face->m_flags & mFD_NON_COLLIDABLE))
+		{
+			CheckEdgeForHoles(GetRawVertexPos(GetFaceVertIndex(fidx, 0)),GetRawVertexPos(GetFaceVertIndex(fidx, 1)));
+			CheckEdgeForHoles(GetRawVertexPos(GetFaceVertIndex(fidx, 1)),GetRawVertexPos(GetFaceVertIndex(fidx, 2)));
+			CheckEdgeForHoles(GetRawVertexPos(GetFaceVertIndex(fidx, 2)),GetRawVertexPos(GetFaceVertIndex(fidx, 0)));
+		}
+	}
+
+}
+#endif
+
+} // namespace Nx
+
diff --git a/Code/Gel/Collision/CollTriData.h b/Code/Gel/Collision/CollTriData.h
new file mode 100644
index 0000000..081221a
--- /dev/null
+++ b/Code/Gel/Collision/CollTriData.h
@@ -0,0 +1,721 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx														**
+**																			**
+**	File name:		CollTriData.h											**
+**																			**
+**	Created: 		02/27/2002	-	grj										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__GEL_COLLTRIDATA_H
+#define	__GEL_COLLTRIDATA_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#include 
+
+#include 
+
+#ifdef __PLAT_NGC__
+#include 
+#include 
+#endif
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// Precision for fixed point split point value
+#define COLLISION_SUB_INCH_PRECISION 16.0f
+#define COLLISION_RECIPROCAL_SUB_INCH_PRECISION 0.0625f
+
+namespace Nx
+{
+
+class CCollStatic;
+class CCollMovable;
+class CCollStaticTri;
+class CCollMovTri;
+class CCollObjTriData;
+struct CollData;
+class CBatchTriCollMan;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+
+////////////////////////////////////////////////////////////////
+// Axis-Aligned BSP tree node
+//
+class CCollBSPNode
+{
+public:
+						CCollBSPNode();
+
+	// These functions allow us to have "virtual" functionality with an instance.  We can safely
+	// cast a pointer to CCollBSPLeaf if isLeaf() returns true.
+	uint8				GetSplitAxis() const { return m_leaf.m_split_axis & 0x3; }
+	void				SetSplitAxis(int axis);
+	bool				IsNode() const { return (GetSplitAxis() != 3); }
+	bool				IsLeaf() const { return (GetSplitAxis() == 3); }
+
+	CCollBSPNode *		GetLessBranch() { Dbg_Assert(IsNode()); return m_node.m_children.GetLessBranch(); }
+	CCollBSPNode *		GetGreaterBranch() { Dbg_Assert(IsNode()); return m_node.m_children.GetGreaterBranch(); }
+
+	FaceIndex *			GetFaceIndexArray() { Dbg_Assert(IsLeaf()); return m_leaf.mp_face_idx_array; }
+
+	int					GetSplitPoint() const { return m_node.m_split_point >> NUM_AXIS_BITS; }
+	void				SetSplitPoint(int ipoint) { m_node.m_split_point = (ipoint << NUM_AXIS_BITS) | GetSplitAxis(); }
+	float				GetFSplitPoint() const { return ((float) GetSplitPoint()) * COLLISION_RECIPROCAL_SUB_INCH_PRECISION; }
+	void				SetFSplitPoint(float point) { SetSplitPoint(point * COLLISION_SUB_INCH_PRECISION); }
+
+private:
+	////////////////////////////////////////////////////////////////
+	// Basically, a class for a pointer that uses the lowest two bits for flags.
+	// It also assumes that the pointer points to two consecutive nodes: the left
+	// node and the right node.
+	//
+	class CCollBSPChildren
+	{
+	public:
+
+		void			Init() { m_left_child_and_flags = 0; }
+
+		// Actual branches
+		CCollBSPNode *	GetLeftBranch() const { return GetBasePointer(); }
+		CCollBSPNode *	GetRightBranch() const { return GetBasePointer() + 1; }
+
+		// Figures out which branch is which
+		CCollBSPNode *	GetLessBranch() const { return IsLeftGreater() ? GetRightBranch() : GetLeftBranch(); }
+		CCollBSPNode *	GetGreaterBranch() const { return IsLeftGreater() ? GetLeftBranch() : GetRightBranch(); }
+
+		// These two are needed for the cloning function
+		CCollBSPNode *	GetBasePointer() const { return (CCollBSPNode *) (m_left_child_and_flags & ~0x3); }
+		void			SetBasePointer(CCollBSPNode *p_base) { m_left_child_and_flags = ((uint32) p_base) | (m_left_child_and_flags & 0x3); }
+
+		bool			IsLeftGreater() const { return m_left_child_and_flags & mLEFT_IS_GREATER; }
+		void			SetLeftGreater(bool greater);
+
+	private:
+
+		// Constants
+		enum
+		{
+			mLEFT_IS_GREATER = 0x01,			// Indicates that the left branch is the greater branch
+		};
+
+		uint32			m_left_child_and_flags; // points to left branch, right branch one node over
+	};		
+
+protected:
+
+   	// Constants
+	enum
+	{
+		NUM_AXIS_BITS = 2,			// Number of bits used in fixed split_point for the axis identification
+	};
+
+						~CCollBSPNode();
+
+	// m_split axis must always be in line with the low byte of m_split_point
+	struct SNode
+	{
+		int32				m_split_point;		// the point on the axis (low 2 bits is the axis itself)
+		CCollBSPChildren	m_children;			// 32-bit value points to left branch, right branch one node over
+	};
+
+	struct SLeaf
+	{
+#ifdef __PLAT_NGC__		// Big endian on NGC
+		uint16				m_num_faces;		// number in faces in face array
+		uint8				m_pad1;
+		uint8				m_split_axis;		// the axis it is split on (0 = X, 1 = Y, 2 = Z, 3 = Leaf)
+#else
+		uint8				m_split_axis;		// the axis it is split on (0 = X, 1 = Y, 2 = Z, 3 = Leaf)
+		uint8				m_pad1;
+		uint16				m_num_faces;		// number in faces in face array
+#endif __PLAT_NGC__
+		FaceIndex *			mp_face_idx_array;	// leaf
+	};
+
+	// Clone functions
+	CCollBSPNode *		clone(bool instance = false);
+	int					count_bsp_nodes();
+	int					count_bsp_face_indices();
+	FaceIndex *			find_bsp_face_index_array_start();
+
+	// Modify functions
+	void				translate(const Mth::Vector & delta_trans);		// delta since we don't store the original pos
+	void				rotate_y(const Mth::Vector & world_origin, Mth::ERot90 rot_y, bool root_node = true);
+	void				scale(const Mth::Vector & world_origin, const Mth::Vector & scale, bool root_node = true);
+
+	// The split axis data MUST be in the same place in both SNode and SLeaf
+	union
+	{
+		SNode			m_node;
+		SLeaf			m_leaf;
+	};
+
+	// Friends
+	friend CCollObjTriData;
+};
+
+#ifndef __PLAT_NGC__
+#define FIXED_POINT_VERTICES
+#endif		// __PLAT_NGC__
+
+////////////////////////////////////////////////////////////////
+// Collision data for an object
+//
+class CCollObjTriData
+{
+public:
+	////////////////////////////////////////////////////////////
+	// The following structure represent the disk format of
+	// the collision data with the correct padding.  If the
+	// disk format changes, these structures MUST change, too.
+	//
+   	struct SReadHeader
+	{
+		int 	m_version;
+		int 	m_num_objects;
+		int 	m_total_num_verts;
+#ifdef __PLAT_NGC__
+		int 	m_total_num_faces;
+#else
+		int 	m_total_num_faces_large;
+		int 	m_total_num_faces_small;
+		int		m_total_num_verts_large;
+		int		m_total_num_verts_small;
+		int		m_pad3;
+#endif
+	};
+
+	//
+						CCollObjTriData();
+						~CCollObjTriData();
+
+	bool				InitCollObjTriData(CScene * p_scene, void *p_base_vert_addr, void *p_base_intensity_addr, // Init collision sector after read from file
+										   void *p_base_face_addr, void *p_base_node_addr, void *p_base_face_idx_addr);
+	bool				InitBSPTree();					// Generate the BSP Tree
+	bool				DeleteBSPTree();				// Delete the BSP Tree
+
+	uint32				GetChecksum() const;
+	void				SetChecksum(uint32 checksum);	// For cloning
+	uint16				GetSectorFlags() const;
+	void				SetSectorFlags(uint16 flags);
+	void				ClearSectorFlags(uint16 flags);
+	const Mth::CBBox &	GetBBox() const;
+	int					GetNumVerts() const;
+	int					GetNumFaces() const;
+
+	// Vertex functions
+#ifdef __PLAT_NGC__
+	const unsigned char GetVertexIntensity(int face_idx, int vert_idx) const;
+	void				SetVertexIntensity(int face_idx, int vert_idx, unsigned char intensity);
+
+	void				SetVertexPointer( NsVector * p_vertex ) { mp_raw_vert_pos = p_vertex; }
+	NsVector *			GetVertexPointer() { return mp_raw_vert_pos; }
+#else
+	const unsigned char GetVertexIntensity(int vert_idx) const;
+	void				SetVertexIntensity(int vert_idx, unsigned char intensity);
+#endif		//__PLAT_NGC__ 
+
+	// Face functions
+	uint16				GetFaceTerrainType(int face_idx) const;
+	uint32				GetFaceFlags(int face_idx) const;
+	uint16				GetFaceVertIndex(int face_idx, int vert_num) const;
+	const Mth::Vector 	GetFaceNormal(int face_idx) const;
+	Mth::Vector			GetRawVertexPos(int vert_idx) const;		// Must copy data for fixed point
+	void				GetRawVertexPos(int vert_idx, Mth::Vector & pos) const;
+	void				SetRawVertexPos(int vert_idx, const Mth::Vector & pos);
+	void				GetRawVertices(Mth::Vector *p_vert_array) const;
+	void				SetRawVertices(const Mth::Vector *p_vert_array);
+
+	// Collision functions
+	FaceIndex *			FindIntersectingFaces(const Mth::CBBox & line_bbox, uint & num_faces);
+
+	// Clone and move functions
+	CCollObjTriData *	Clone(bool instance = false, bool skip_no_verts = false);
+	void				Translate(const Mth::Vector & delta_trans);		// delta since we don't store the original pos
+	void				RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y);
+	void				Scale(const Mth::Vector & world_origin, const Mth::Vector & scale);
+
+	void				ProcessOcclusion();
+
+	// Debug functions
+	void				DebugRender(uint32 ignore_1, uint32 ignore_0);
+	void				DebugRender2D(uint32 ignore_1, uint32 ignore_0, uint32 visible);
+	void				DebugRender2DBBox(uint32 ignore_1, uint32 ignore_0, uint32 visible);
+	void				DebugRender2DOct(uint32 ignore_1, uint32 ignore_0, uint32 visible);
+	void				DebugRender(const Mth::Matrix & transform, uint32 ignore_1, uint32 ignore_0, bool do_transfrom = true);
+	void				CheckForHoles();
+
+	// Element size functions for init
+	static uint			GetVertElemSize();
+	static uint			GetVertSmallElemSize();
+	static uint			GetFaceElemSize();
+	static uint			GetFaceSmallElemSize();
+
+protected:
+	//////////////////////////////////////////////////
+	// All of the following member variables and structures
+	// match the output of SceneConv.exe EXACTLY.
+	// Do not change these without changing SceneConv.
+
+	//////////////////////////////////////////////////
+	// Internal structures for the faces (note its size isn't a qword multiple)
+	struct SFaceInfo
+	{
+		uint16			m_flags;				// collision attributes
+		uint16			m_terrain_type;			// terrain type
+	};
+
+	// Everything in SFace and SFaceSmall before the vert indexes MUST be the same.
+	struct SFace
+	{
+		SFaceInfo		m_info;					// face info
+		FaceIndex		m_vertex_index[3];		// indexes into the corresponding vertex array
+	};
+
+	struct SFaceSmall
+	{
+		SFaceInfo		m_info;					// face info
+		FaceByteIndex	m_vertex_index[3];		// indexes into the corresponding vertex array
+#ifndef __PLAT_NGC__
+		uint8			m_pad;
+#endif		// __PLAT_NGC__
+	};
+
+	// Use this structure to shove the rgba value into the W value of a Vector
+	struct SOverlay
+	{
+		uint32			m_dont_touch_X;			// equivalent to Mth::Vector X
+		uint32			m_dont_touch_Y;			// equivalent to Mth::Vector Y
+		uint32			m_dont_touch_Z;			// equivalent to Mth::Vector Z
+		uint8			m_type;					// equivalent to Mth::Vector W
+		uint8			m_intensity;
+		uint8			m_pad[2];
+	};
+
+	// This structure is used for 16-bit fixed point vertices
+	struct SFloatVert
+	{
+		float			m_pos[3];
+//		uint8			m_type;
+//		uint8			m_intensity;
+//		uint8			m_pad[2];
+	};
+
+	// This structure is used for 16-bit fixed point vertices
+	struct SFixedVert
+	{
+		uint16			m_pos[3];			// Relative to min point of bounding box
+//		uint8			m_pad;
+//		uint8			m_intensity;
+	};
+
+	//////////////////////////////////////////////////
+	// Members
+	uint32				m_checksum;			// checksum of sector
+	uint16				m_Flags;			// Sector-level flags
+	uint16				m_num_verts;
+	uint16				m_num_faces;
+	uint8				m_use_face_small;	// Set to 1 if using SFaceSmall below
+	uint8				m_use_fixed_verts;
+
+	union {
+		SFace *			mp_faces;			// array of faces
+		SFaceSmall *	mp_face_small;		// array of small faces
+	};
+
+	Mth::CBBox			m_bbox;				// bounding box of sector
+
+#ifdef __PLAT_NGC__
+	NsVector *			mp_raw_vert_pos;
+#else
+	union {
+#ifndef FIXED_POINT_VERTICES
+		Mth::Vector *	mp_vert_pos;			// array of 32-bit vertices
+		SOverlay *		mp_vert_rgba_overlay;	// overlay array of vertex colors for 32-bit vertices
+#endif
+		SFloatVert *	mp_float_vert;			// array of 32-bit vertices
+		SFixedVert *	mp_fixed_vert;			// array of 16-bit vertices
+	};
+#endif
+
+	CCollBSPNode *		mp_bsp_tree;		// head of BSP tree
+	uint8 *				mp_intensity;		// Intensity list
+#ifdef __PLAT_NGC__
+	NsVector *			mp_cloned_vert_pos;
+#else
+	uint32				m_pad1;				// padding
+#endif		// __PLAT_NGC__
+
+	SFaceInfo *			get_face_info(int face_idx) const;
+
+private:
+	// Constants
+	enum
+	{
+		MAX_FACE_INDICIES = 6000 //2048
+	};
+
+	// Gets and puts float data (converting to and from fixed point if necessary)
+	void				get_float_array_from_data(Mth::Vector *p_float_array) const;
+	void				put_float_array_into_data(const Mth::Vector *p_float_array);
+
+	void				set_vertex_pos(int vert_idx, const Mth::Vector & pos);
+
+	CCollBSPNode *		create_bsp_tree(const Mth::CBBox & bbox, FaceIndex *p_face_indexes, uint num_faces, uint level = 1);
+	CCollBSPNode *		create_bsp_leaf(FaceIndex *p_face_indexes, uint num_faces);
+	bool				calc_split_faces(uint axis, float axis_distance, FaceIndex *p_face_indexes,
+										 uint num_faces, uint & less_faces, uint & greater_faces, 
+										 FaceIndex *p_less_face_indexes = NULL, FaceIndex *p_greater_face_indexes = NULL);
+
+	void				find_faces(CCollBSPNode *p_bsp_node, const Mth::CBBox & bbox);		// recursively search for faces
+
+	static bool			s_init_tree(CCollBSPNode *p_tree, void *p_base_node_addr, void *p_base_face_idx_addr);
+
+	static const uint	s_max_face_per_leaf;// maximum number faces per leaf
+	static const uint	s_max_tree_levels;	// maximum number of levels in a tree
+
+	static FaceIndex	s_face_index_buffer[MAX_FACE_INDICIES];
+	static FaceIndex	s_seq_face_index_buffer[MAX_FACE_INDICIES];
+	static uint			s_num_face_indicies;
+
+	// Should be readable by all the CCollObj classes
+	friend CCollStatic;
+	friend CCollMovable;
+	friend CCollStaticTri;
+	friend CCollMovTri;
+	friend CBatchTriCollMan;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CCollObjTriData::SetChecksum(uint32 checksum)
+{
+	m_checksum = checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32				CCollObjTriData::GetChecksum() const
+{
+	return m_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int					CCollObjTriData::GetNumVerts() const
+{
+	return m_num_verts;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int					CCollObjTriData::GetNumFaces() const
+{
+	return m_num_faces;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::CBBox &	CCollObjTriData::GetBBox() const
+{
+	return m_bbox;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint					CCollObjTriData::GetVertElemSize()
+{
+#ifdef __PLAT_NGC__
+	return 1 * 3;
+#else
+#ifdef FIXED_POINT_VERTICES
+	return sizeof(SFloatVert);
+#else
+	return sizeof(Mth::Vector);
+#endif // FIXED_POINT_VERTICES
+#endif		// __PLAT_NGC__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint					CCollObjTriData::GetVertSmallElemSize()
+{
+	return sizeof(SFixedVert);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint					CCollObjTriData::GetFaceElemSize()
+{
+	return sizeof(SFace);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint					CCollObjTriData::GetFaceSmallElemSize()
+{
+	return sizeof(SFaceSmall);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint16				CCollObjTriData::GetFaceTerrainType(int face_idx) const
+{
+	return (m_use_face_small) ? mp_face_small[face_idx].m_info.m_terrain_type : mp_faces[face_idx].m_info.m_terrain_type;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32				CCollObjTriData::GetFaceFlags(int face_idx) const
+{
+	return (m_use_face_small) ? mp_face_small[face_idx].m_info.m_flags : mp_faces[face_idx].m_info.m_flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint16				CCollObjTriData::GetFaceVertIndex(int face_idx, int vert_num) const
+{
+	return (m_use_face_small) ? mp_face_small[face_idx].m_vertex_index[vert_num] : mp_faces[face_idx].m_vertex_index[vert_num];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Mth::Vector		 	CCollObjTriData::GetRawVertexPos(int vert_idx) const
+{
+#if !defined(FIXED_POINT_VERTICES)
+
+#ifdef __PLAT_NGC__
+	Mth::Vector v;
+
+	if ( mp_cloned_vert_pos )
+	{
+		v[X] = mp_cloned_vert_pos[vert_idx].x;
+		v[Y] = mp_cloned_vert_pos[vert_idx].y;
+		v[Z] = mp_cloned_vert_pos[vert_idx].z;
+		v[W] = 0.0f;
+	}
+	else
+	{
+		v[X] = mp_raw_vert_pos[vert_idx].x;
+		v[Y] = mp_raw_vert_pos[vert_idx].y;
+		v[Z] = mp_raw_vert_pos[vert_idx].z;
+		v[W] = 0.0f;
+	}
+
+	return v;
+#else
+	return mp_vert_pos[vert_idx];
+#endif		// __PLAT_NGC__
+
+#else
+
+	if (m_use_fixed_verts)
+	{
+		Mth::Vector pos(m_bbox.GetMin());
+
+		pos[X] += ((float) mp_fixed_vert[vert_idx].m_pos[X]) * COLLISION_RECIPROCAL_SUB_INCH_PRECISION;
+		pos[Y] += ((float) mp_fixed_vert[vert_idx].m_pos[Y]) * COLLISION_RECIPROCAL_SUB_INCH_PRECISION;
+		pos[Z] += ((float) mp_fixed_vert[vert_idx].m_pos[Z]) * COLLISION_RECIPROCAL_SUB_INCH_PRECISION;
+		pos[W]	= 0.0f;
+
+		return pos;
+	}
+	else
+	{
+		Mth::Vector pos(Mth::Vector::NO_INIT);
+		pos[X]			= mp_float_vert[vert_idx].m_pos[X];
+		pos[Y]			= mp_float_vert[vert_idx].m_pos[Y];
+		pos[Z]			= mp_float_vert[vert_idx].m_pos[Z];
+		pos[W]			= 0.0f;
+		return pos;
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CCollObjTriData::GetRawVertexPos(int vert_idx, Mth::Vector & pos) const
+{
+#ifdef __PLAT_NGC__
+
+	if ( mp_cloned_vert_pos )
+	{
+		pos[X] = mp_cloned_vert_pos[vert_idx].x;
+		pos[Y] = mp_cloned_vert_pos[vert_idx].y;
+		pos[Z] = mp_cloned_vert_pos[vert_idx].z;
+		pos[W] = 0.0f;
+	}
+	else
+	{
+		pos[X] = mp_raw_vert_pos[vert_idx].x;
+		pos[Y] = mp_raw_vert_pos[vert_idx].y;
+		pos[Z] = mp_raw_vert_pos[vert_idx].z;
+		pos[W] = 0.0f;
+	}
+#else
+	if (m_use_fixed_verts)
+	{
+		pos = m_bbox.GetMin();
+		pos[X] += ((float) mp_fixed_vert[vert_idx].m_pos[X]) * COLLISION_RECIPROCAL_SUB_INCH_PRECISION;
+		pos[Y] += ((float) mp_fixed_vert[vert_idx].m_pos[Y]) * COLLISION_RECIPROCAL_SUB_INCH_PRECISION;
+		pos[Z] += ((float) mp_fixed_vert[vert_idx].m_pos[Z]) * COLLISION_RECIPROCAL_SUB_INCH_PRECISION;
+		pos[W]	= 0.0f;
+	}
+	else
+	{
+#ifdef FIXED_POINT_VERTICES
+		pos[X]	= mp_float_vert[vert_idx].m_pos[X];
+		pos[Y]	= mp_float_vert[vert_idx].m_pos[Y];
+		pos[Z]	= mp_float_vert[vert_idx].m_pos[Z];
+#else
+		pos		= mp_vert_pos[vert_idx];
+#endif
+		pos[W]	= 0.0f;
+	}
+#endif		// __PLAT_NGC__
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __PLAT_NGC__
+inline const unsigned char CCollObjTriData::GetVertexIntensity(int face_idx, int vert_idx) const
+{
+	return mp_intensity[(face_idx * 3) + vert_idx];
+}
+#else
+inline const unsigned char CCollObjTriData::GetVertexIntensity(int vert_idx) const
+{
+#ifdef FIXED_POINT_VERTICES
+	return mp_intensity[vert_idx];
+	//if (m_use_fixed_verts)
+	//{
+	//	return mp_fixed_vert[vert_idx].m_intensity;
+	//}
+	//else
+	//{
+	//	return mp_float_vert[vert_idx].m_intensity;
+	//}
+#else
+	{
+		return mp_vert_rgba_overlay[vert_idx].m_intensity;
+	}
+#endif // FIXED_POINT_VERTICES
+}
+#endif		// __PLAT_NGC__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __PLAT_NGC__
+inline void					CCollObjTriData::SetVertexIntensity(int face_idx, int vert_idx, unsigned char intensity)
+{
+	mp_intensity[(face_idx * 3) + vert_idx] = intensity; 
+}
+#else
+inline void					CCollObjTriData::SetVertexIntensity(int vert_idx, unsigned char intensity)
+{
+#ifdef FIXED_POINT_VERTICES
+	mp_intensity[vert_idx] = intensity;
+	//if (m_use_fixed_verts)
+	//{
+	//	mp_fixed_vert[vert_idx].m_intensity = intensity;
+	//}
+	//else
+	//{
+	//	mp_float_vert[vert_idx].m_intensity = intensity;
+	//}
+#else
+	{
+		mp_vert_rgba_overlay[vert_idx].m_intensity = intensity;
+	}
+#endif // FIXED_POINT_VERTICES
+}
+#endif		// __PLAT_NGC__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline CCollObjTriData::SFaceInfo *	CCollObjTriData::get_face_info(int face_idx) const
+{
+	return (m_use_face_small) ? &(mp_face_small[face_idx].m_info) : &(mp_faces[face_idx].m_info);
+}
+
+} // namespace Nx
+
+#endif  //	__GEL_COLLTRIDATA_H
diff --git a/Code/Gel/Collision/Collision.cpp b/Code/Gel/Collision/Collision.cpp
new file mode 100644
index 0000000..e908daf
--- /dev/null
+++ b/Code/Gel/Collision/Collision.cpp
@@ -0,0 +1,2698 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx								 						**
+**																			**
+**	File name:		gel\collision\collide.cpp      							**
+**																			**
+**	Created by:		02/20/02	-	grj										**
+**																			**
+**	Description:															**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 	// for face flag stuff
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#ifdef	__PLAT_NGPS__
+#define USE_VU0_MICROMODE
+#endif
+
+#define PRINT_CACHE_HITS 0
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+Mth::Matrix		CCollStatic::sOrient;
+Mth::Vector		CCollStatic::sWorldPos;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+static bool s_check_for_far = false;			// if true then we return the farthest result
+
+bool CCollObj::s_found_collision(const Mth::Line *p_is, CCollObj *p_coll_object, SCollSurface *p_collSurface,
+								 float distance, CollData *p_collData)
+{
+	Dbg_Assert( p_collData );
+	
+	bool good = false;
+	bool updated = false;
+
+	if (s_check_for_far)
+	{
+		if( distance >= p_collData->dist )
+		{
+			good = true;
+		}
+	}
+	else
+	{
+		//Dbg_Message("Checking new dist %f against old dist %f", distance, p_collData->dist);
+		if( distance <= p_collData->dist )
+		{
+			good = true;
+		}
+	}
+	CollData 	old;
+	
+	
+	if (!good)
+	{
+		old = *p_collData;				 
+	}
+			 
+
+	// Get the user plugin offset														
+	int index = p_collSurface->index;		// the index of the surface in the object
+	
+	// get the face flags from within this
+	// note that this uses the "index" field, which is
+	// an index of a face within the sector
+	uint32  flags = p_coll_object->GetFaceFlags(index);
+	
+	// Test flags against the ignore_1 (ignore if a bit is set in flags)
+	// and against ignore_0 (ignore if the bit is clear in flags)
+					
+	//Dbg_Message("*Flags %x ignore_0 %x ignore_1 %x", flags, p_collData->ignore_0, p_collData->ignore_1);
+	if (!(flags & p_collData->ignore_1) && !(~flags & p_collData->ignore_0))
+	{
+		// Setup the collData members		
+		
+		p_collData->coll_found = true;     		// found at least one (might be overrridden, but found at least one)
+		p_collData->surface = *p_collSurface;	// Store the collision surface
+		p_collData->dist = distance;			// set distance (will end up being the smallest dist)
+		p_collData->flags = flags; 				// Store face flags
+		p_collData->terrain = (ETerrainType) p_coll_object->GetFaceTerrainType(index);
+		p_collData->p_coll_object = p_coll_object;
+		p_collData->trigger = (flags& mFD_TRIGGER);	// set flag if it's a trigger
+
+		p_collData->node_name =  p_coll_object->GetChecksum();
+		// Determine the point of intersection on the triangle
+		// which will be in the direction of the line's vector at "distance" length
+		Mth::Vector coll_point, line_vec;
+		line_vec = p_is->m_end - p_is->m_start;
+
+		coll_point = line_vec.Scale(distance);
+		coll_point = p_is->m_start + coll_point;
+		coll_point[W] = 1.0f;					// Hack to force into homogenious space.  Even though the math is correct here,
+												// many times the line data fed to it isn't.
+		p_collData->surface.point = coll_point;	// After looking at their implementation, I don't
+												// trust "point" so I recalculate it
+
+		// check for additional per-face callback
+		
+		if (p_collData->callback)
+		{
+			// call the callback function, and pass it this col data 
+			p_collData->callback(p_collData);
+		}
+	
+		updated = true;
+	}
+		
+	if (!good)
+	{
+		*p_collData = old;				 
+	}
+
+	return (good && updated);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCollObj::sRayTriangleCollision(const Mth::Vector *rayStart, const Mth::Vector *rayDir,
+									 Mth::Vector *v0, Mth::Vector *v1, Mth::Vector *v2, float *t)
+{
+#if 0
+// #ifdef	__PLAT_NGPS__
+// Turned off assem versions to test correction to false positive issue as discussed in C code below.  Dan - 1/21/3
+#ifdef	USE_VU0_MICROMODE
+	register bool result;
+
+	asm __volatile__(
+	"
+	.set noreorder
+	lqc2    vf06,%4				# v0
+	lqc2    vf08,%6				# v2
+	lqc2    vf07,%5				# v1
+	lqc2    vf04,%2				# rayStart
+	lqc2    vf05,%3				# rayDir
+
+	vcallms	RayTriangleCollision	# call microsubroutine
+	vnop							# interlocking instruction, waits for vu0 completion
+
+	cfc2	%0,$vi02			# get boolean result from vu0
+
+	beq		%0, $0, vu0RayTriDone	# skip copy of t if not needed
+	nop
+
+	qmfc2	$12, $vf17			# move t to $8
+	sw		$12, %1				# and write out
+
+vu0RayTriDone:
+
+	.set reorder
+	": "=&r" (result), "=m" (*t) : "m" (*rayStart), "m" (*rayDir), "m" (*v0), "m" (*v1) , "m" (*v2) : "$12" );
+
+	return result;
+#else
+    //*t = 2.0f;
+	bool result;
+
+	asm __volatile__(
+	"
+	.set noreorder
+	lqc2    vf06,0x0(%4)		# v0
+	lqc2    vf08,0x0(%6)		# v2
+	lqc2    vf07,0x0(%5)		# v1
+	lqc2    vf04,0x0(%2)		# rayStart
+	lqc2    vf05,0x0(%3)		# rayDir
+
+	vsub.xyz vf10, vf08, vf06    # edge2 = v2 - v0
+	vsub.xyz vf09, vf07, vf06    # edge1 = v1 - v0 
+	vsub.xyz vf13, vf04, vf06    # tvec = rayStart - v0
+   	li.s     $f4,0.00001		 # EPSILON
+	vaddw.x  vf02, vf00, vf00w   # Load 1.0f to VF02x
+
+	vopmula.xyz  ACC,vf05,vf10   # Cross product of ray normal and edge2 (pvec)
+	vopmsub.xyz  vf12,vf10,vf05  # Second part of cross product
+   	mfc1         $8,$f4
+	li		     %0,0			 # store 0 for return value
+	#li      $9,0x80	 		 # Set the mask X sign flag
+	#li      $10,0x10			 # Set the mask W sign flag
+
+	vmul.xyz  vf11,vf09,vf12     # det = edge1 * pvec [start]
+	vmulax.x  ACC,vf09,vf12x
+	vmul.xyz  vf15,vf13,vf12     # u = tvec * pvec [start]
+	qmtc2	$8, $vf03
+	vmadday.x ACC,vf02,vf11y
+	vmaddz.x  vf11,vf02,vf11z    # det = edge1 * pvec [ready]
+	vmulax.x  ACC,vf02,vf15x
+	vmadday.x ACC,vf02,vf15y
+	vmaddz.x  vf15,vf02,vf15z    # u = tvec * pvec [ready]
+	vdiv 	  Q,vf00w,vf11x      # Q = 1.0f / det
+
+	vopmula.xyz ACC,vf13,vf09    # qvec = crossProd(tvec, edge1);
+	vopmsub.xyz vf14,vf09,vf13
+	vsub.x      vf01,vf11,vf03   # If det < EPSILON
+		vnop
+		vnop
+		vnop
+		vnop
+		vnop
+	cfc2    $8,$vi17			 # transfer if det result (MAC register)
+	vmulq.x vf15,vf15,Q          # u = (tvec * pvec) / det
+	vnop
+	vnop
+	vnop
+		vnop
+		andi	$8, $8, 0x80		 # check result
+	cfc2    $9,$vi17			 # transfer u result (MAC register)
+	bne		$8, $0, vu0RayTriDone
+	vaddx.w vf03, vf00, vf03x	 # put 1 + EPSILON in VF03w
+	andi	$9, $9, 0x80		 # check result (u < 0) fail
+	bne		$9, $0, vu0RayTriDone
+	#vaddq.x vf20,vf00,Q          
+
+    vsubw.x vf00,vf15,vf03w		 # if u > 1 + EPSILON
+		vnop
+		vnop
+		vnop
+		vnop
+		vnop
+	cfc2    $8,$vi17			 # transfer if u > 1 + EPSILON result (MAC register)
+    vmul.xyz vf16,vf05,vf14      # v = rayDir * qvec [start] 
+    vmulax.x ACC,vf05,vf14x
+	andi	$8, $8, 0x80		 # check result (u > 1) fail
+	beq		$8, $0, vu0RayTriDone
+	vnop
+
+    vmadday.x ACC,vf02,vf16
+    vmaddz.x vf16,vf02,vf16z     # v = rayDir * qvec [ready]
+	vnop
+    vmul.xyz vf17,vf10,vf14      # t = edge2 * qvec [start]
+    vmulax.x ACC,vf10,vf14x
+    vmulq.x   vf16,vf16,Q        # v = (rayDir * qvec) / det;
+	vnop
+		vnop
+		vnop
+		vnop
+		vnop
+	cfc2    $8,$vi17			 # transfer if v < 0 result (MAC register)
+
+	vmadday.x ACC,vf02,vf17
+	vmaddz.x  vf17,vf02,vf17z    # t = edge2 * qvec [ready]
+	vadd.x    vf01,vf15,vf16
+	andi	  $8, $8, 0x80		 # check result (v < 0) fail
+	bne		  $8, $0, vu0RayTriDone
+    vmulq.x    vf17,vf17,Q       # t = (edge2 * qvec) / det
+
+    vsubw.x vf31,vf01,vf03w      # if (u+v) > 1 + EPSILON [start]
+	vnop
+	vnop
+	vnop
+		vnop
+		vnop
+	cfc2    $8,$vi17			 # transfer if u+v > 1 + EPSILON result (MAC register)
+    vsub.x vf00,vf17,vf00
+	vnop
+		vnop
+		vnop
+		vnop
+	andi	$8, $8, 0x80		 # check result (u+v > 1 + EPSILON) fail
+	cfc2    $9,$vi17			 # transfer if t < 0 result (MAC register)
+	vsubx.w vf00,vf00,vf17       # if t > 1 [start]
+	vnop
+		vnop
+		#vnop
+		#vnop
+		#vnop
+	beq		$8, $0, vu0RayTriDone
+	andi	$9, $9, 0x80		 # check result (t < 0) fail
+	bne		$9, $0, vu0RayTriDone
+	cfc2    $8,$vi17			 # transfer if t > 1 result (MAC register)
+	andi	$8, $8, 0x10		 # check result (t > 1) fail
+	bne		$8, $0, vu0RayTriDone
+	qmfc2	$9, $vf17			 # move t to $9
+	sw		$9, 0(%1)			 # and write out
+	li		%0,1				 # store 1 for return value
+
+
+vu0RayTriDone:
+	.set reorder
+	": "=r" (result), "+r" (t) : "r" (rayStart), "r" (rayDir), "r" (v0), "r" (v1) , "r" (v2) : "$8", "$9");
+	//return *t <= 1.0f;
+	return result;
+#endif //	USE_VU0_MICROMODE
+#else
+    const float EPSILON = 0.00001f;
+
+    /* find vectors for two edges sharing vert0 */
+    Mth::Vector edge1 = *v1 - *v0;
+    Mth::Vector edge2 = *v2 - *v0;
+    
+    /* begin calculating determinant - also used to calculate U parameter */
+    Mth::Vector pvec = Mth::CrossProduct(*rayDir, edge2);
+
+    /* if determinant is near zero, ray lies in plane of triangle */
+    const float det = Mth::DotProduct(edge1, pvec);
+
+#if 1 // Dan - 1/21/3
+	// Moved division below several false returns and reduced mult count.  This probably removed the need for adding EPSILON to 1.0f for the u and v test,
+	// but I don't know this for sure, so left it in.
+	// Also, added EPSILON_2.  EPSILON is of appropriate scale for comparing floats of order one to zero, but is too small for comparing floats
+	// on the order of the cube of a typical vertex edge.  In certain special cases the dot product in the calculation of det will require the
+	// cancelation of very large floats, resulting in a zero which is larger than EPSILON.  This was causing false positives.
+	// It is possible that this new larger EPSILON_2 will cause false negatives.  I suppose we shall see.
+	// These changes HAVE NOT BEEN MADE to the assembler code versions.  They continue to suffer from the over-small EPSILON issue.
+
+    const float EPSILON_2 = 0.03f;
+    
+	// We are back-facing, so also throw away negative
+    if (det < EPSILON_2)
+	{
+        return false;
+    }
+
+	const float adjusted_det = (1.0f + EPSILON) * det;
+
+    /* calculate distance from vert0 to ray origin */
+    const Mth::Vector tvec = *rayStart - *v0;
+    
+    /* calculate U parameter and test bounds */
+    float u = Mth::DotProduct(tvec, pvec);
+
+    if (u < 0.0f || u > adjusted_det) {
+        return false;
+    }
+    
+    /* prepare to test V parameter */
+    const Mth::Vector qvec = Mth::CrossProduct(tvec, edge1);
+    
+    /* calculate V parameter and test bounds */
+    float v = Mth::DotProduct(*rayDir, qvec);
+
+    if (v < 0.0f || u + v > adjusted_det) {
+        return false;
+    }
+
+	const float inv_det = 1.0f / det;
+
+    /* calculate t, ray intersects triangle */
+    *t = Mth::DotProduct(edge2, qvec) * inv_det;
+
+	// And update u & v, although we don't seem to use it now.
+	//u *= inv_det;
+	//v *= inv_det;
+
+#else
+	// We are back-facing, so also throw away negative
+    if (det < EPSILON)
+	{
+        return false;
+    }
+
+	const float inv_det = 1.0f / det;
+
+    /* calculate distance from vert0 to ray origin */
+    const Mth::Vector tvec = *rayStart - *v0;
+    
+    /* calculate U parameter and test bounds */
+    float u = Mth::DotProduct(tvec, pvec) * inv_det;
+
+    if (u < 0.0f || u > (1.0f + EPSILON)) {
+        return false;
+    }
+    
+    /* prepare to test V parameter */
+    const Mth::Vector qvec = Mth::CrossProduct(tvec, edge1);
+    
+    /* calculate V parameter and test bounds */
+    float v = Mth::DotProduct(*rayDir, qvec) * inv_det;
+
+    if (v < 0.0f || u + v > (1.0f + EPSILON)) {
+        return false;
+    }
+    
+    /* calculate t, ray intersects triangle */
+    *t = Mth::DotProduct(edge2, qvec) * inv_det;
+
+	// And update u & v, although we don't seem to use it now.
+	//u *= inv_det;
+	//v *= inv_det;
+#endif
+
+    return (*t <= 1.0f) && (*t >= 0.0f);
+#endif
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCollObj::s2DLineLineCollision ( float p_start_X, float p_start_Y, float p_delta_X, float p_delta_Y, float q_start_X, float q_start_Y, float q_delta_X, float q_delta_Y, float *s )
+{
+	// determines collision between two lines (p and q) in 2D; reports the interval along p at which the collision occurs
+	
+	// Antonio's method; Real-Time Rendering, p 337
+	
+    const float EPSILON = 0.00000000001f;
+	
+	float a_X = q_delta_X;
+	float a_Y = q_delta_Y;
+	
+	float b_X = p_delta_X;
+	float b_Y = p_delta_Y;
+	
+	float c_X = p_start_X - q_start_X;
+	float c_Y = p_start_Y - q_start_Y;
+	
+	float d = c_X * -a_Y + c_Y * a_X;
+	
+	float e = c_X * -b_Y + c_Y * b_X;
+	
+	float f = a_X * -b_Y + a_Y * b_X;
+	
+	if (f > 0.0f)
+	{
+		if (d < 0.0f || d > f) return false;
+	}
+	else
+	{
+		if (d > 0.0f || d < f) return false;
+	}
+	
+	if (f > 0.0f)
+	{
+		if (e < 0.0f || e > f) return false;
+	}
+	else
+	{
+		if (e > 0.0f || e < f) return false;
+	}
+	
+	if (Mth::Abs(f) < EPSILON)
+	{
+		*s = 0.0f;
+		return true;
+	}
+	
+	*s = d / f;
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCollObj::sRectangleTriangleCollision(const Mth::Rectangle *rect, const Mth::Vector *v0, const Mth::Vector *v1, const Mth::Vector *v2, Mth::Vector p_collision_endpoints[2], ETriangleEdgeType p_collision_triangle_edges[2] )
+{
+	// ERIT method; Real-Time Rendering, p 317
+	
+    const float EPSILON = 0.00001f;
+	
+	// calculate the plane equation of the rectangle
+	Mth::Vector rectangle_n = Mth::CrossProduct(rect->m_first_edge, rect->m_second_edge);
+	float rectangle_d = -Mth::DotProduct(rectangle_n, rect->m_corner);
+	
+	// calculate the distance (times rectangle_n squared) of the triangle vertices from the rectangle's plane
+	float d0 = Mth::DotProduct(rectangle_n, *v0) + rectangle_d; 
+	float d1 = Mth::DotProduct(rectangle_n, *v1) + rectangle_d; 
+	float d2 = Mth::DotProduct(rectangle_n, *v2) + rectangle_d;
+	 
+	// if all vertices are on the same side of the plane, we reject
+	if (d0 > 0.0f && d1 > 0.0f && d2 > 0.0f) return false;
+	if (d0 < 0.0f && d1 < 0.0f && d2 < 0.0f) return false;
+	
+	// snap verticies near the plane to the plane
+	if (Mth::Abs(d0) < EPSILON)
+	{
+		d0 = 0.0f;
+	}
+	if (Mth::Abs(d1) < EPSILON)
+	{
+		d1 = 0.0f;
+	}
+	if (Mth::Abs(d2) < EPSILON)
+	{
+		d2 = 0.0f;
+	}
+	
+	// find the two points (p and q) on the plane of the rectangle which are the endpoints of the triangle's interception with the rectangle's plane
+	Mth::Vector p, q;
+	ETriangleEdgeType p_edge, q_edge;
+	if ((d0 <= 0.0f && d1 >= 0.0f) || (d0 >= 0.0f && d1 <= 0.0f))
+	{
+		p_edge = TRIANGLE_EDGE_V0_V1;
+		if (Mth::Abs(d0 - d1) > EPSILON)
+		{
+			p = *v0 + (*v1 - *v0) * d0 / (d0 - d1);
+		}
+		else
+		{
+			p = *v0;
+		}
+		
+		if ((d1 <= 0.0f && d2 >= 0.0f) || (d1 >= 0.0f && d2 <= 0.0f))
+		{
+			q_edge = TRIANGLE_EDGE_V1_V2;
+			if (Mth::Abs(d1 - d2) > EPSILON)
+			{
+				q = *v1 + (*v2 - *v1) * d1 / (d1 - d2);
+			}
+			else
+			{
+				q = *v1;
+			}
+		}
+		else
+		{
+			q_edge = TRIANGLE_EDGE_V2_V0;
+			if (Mth::Abs(d2 - d0) > EPSILON)
+			{
+				q = *v2 + (*v0 - *v2) * d2 / (d2 - d0);
+			}
+			else
+			{
+				q = *v2;
+			}
+		}
+	}
+	else
+	{
+		p_edge = TRIANGLE_EDGE_V1_V2;
+		if (Mth::Abs(d1 - d2) > EPSILON)
+		{
+			p = *v1 + (*v2 - *v1) * d1 / (d1 - d2);
+		}
+		else
+		{
+			p = *v1;
+		}
+		
+		q_edge = TRIANGLE_EDGE_V2_V0;
+		if (Mth::Abs(d0 - d2) > EPSILON)
+		{
+			q = *v0 + (*v2 - *v0) * d0 / (d0 - d2);
+		}
+		else
+		{
+			q = *v0;
+		}
+	}
+	
+	// project the rectangle and the intersection endpoints into the coordinate plane which maximizes the area of the rectangle's projection
+	
+	float r_corner_S, r_corner_T;
+	float r_first_edge_S, r_first_edge_T;
+	float r_second_edge_S, r_second_edge_T;
+	float p_S, p_T;
+	float q_S, q_T;
+	
+	if (Mth::Abs(rectangle_n[X]) < Mth::Abs(rectangle_n[Z]) && Mth::Abs(rectangle_n[Y]) < Mth::Abs(rectangle_n[Z]))
+	{
+		// project onto XY plane
+		r_corner_S = rect->m_corner[X];
+		r_corner_T = rect->m_corner[Y];
+		r_first_edge_S = rect->m_first_edge[X];
+		r_first_edge_T = rect->m_first_edge[Y];
+		r_second_edge_S = rect->m_second_edge[X];
+		r_second_edge_T = rect->m_second_edge[Y];
+		p_S = p[X];
+		p_T = p[Y];
+		q_S = q[X];
+		q_T = q[Y];
+	}
+	else if (Mth::Abs(rectangle_n[X]) < Mth::Abs(rectangle_n[Y]) && Mth::Abs(rectangle_n[Z]) < Mth::Abs(rectangle_n[Y]))
+	{
+		// project onto XZ plane
+		r_corner_S = rect->m_corner[X];
+		r_corner_T = rect->m_corner[Z];
+		r_first_edge_S = rect->m_first_edge[X];
+		r_first_edge_T = rect->m_first_edge[Z];
+		r_second_edge_S = rect->m_second_edge[X];
+		r_second_edge_T = rect->m_second_edge[Z];
+		p_S = p[X];
+		p_T = p[Z];
+		q_S = q[X];
+		q_T = q[Z];
+	}
+	else
+	{
+		// project onto YZ plane
+		r_corner_S = rect->m_corner[Y];
+		r_corner_T = rect->m_corner[Z];
+		r_first_edge_S = rect->m_first_edge[Y];
+		r_first_edge_T = rect->m_first_edge[Z];
+		r_second_edge_S = rect->m_second_edge[Y];
+		r_second_edge_T = rect->m_second_edge[Z];
+		p_S = p[Y];
+		p_T = p[Z];
+		q_S = q[Y];
+		q_T = q[Z];
+	}
+	
+	// test p and q against the four edges of the rectangle; if both lie outside any edge, we reject
+	
+	// these variables define a rectangle's edge in 2D; as + bt + c = 0
+	float a, b, c;
+	
+	float pd, qd;
+	float inside_sign;
+	
+	// not really convinced that this bitmap method of faster 
+	// char within = 0;
+	// enum
+	// {
+		// P_TRIANGLE_EDGE_0_MASK = 1 << 0,
+		// Q_TRIANGLE_EDGE_0_MASK = 1 << 1,
+		// P_TRIANGLE_EDGE_1_MASK = 1 << 2,
+		// Q_TRIANGLE_EDGE_1_MASK = 1 << 3,
+		// P_TRIANGLE_EDGE_2_MASK = 1 << 4,
+		// Q_TRIANGLE_EDGE_2_MASK = 1 << 5,
+		// P_TRIANGLE_EDGE_3_MASK = 1 << 6,
+		// Q_TRIANGLE_EDGE_3_MASK = 1 << 7,
+		// ALL_P_MASKS = P_TRIANGLE_EDGE_0_MASK | P_TRIANGLE_EDGE_1_MASK | P_TRIANGLE_EDGE_2_MASK | P_TRIANGLE_EDGE_3_MASK,
+		// ALL_Q_MASKS = Q_TRIANGLE_EDGE_0_MASK | Q_TRIANGLE_EDGE_1_MASK | Q_TRIANGLE_EDGE_2_MASK | Q_TRIANGLE_EDGE_3_MASK
+	// };
+	bool p_within[4] = { false, false, false, false };
+	bool q_within[4] = { false, false, false, false };
+	
+	// edge 0: corner to corner + first_edge
+	// second_edge defines inside direction
+	a = r_first_edge_T;
+	b = -r_first_edge_S;
+	c = r_corner_T * r_first_edge_S - r_corner_S * r_first_edge_T;
+	pd = a * p_S + b * p_T + c;
+	qd = a * q_S + b * q_T + c;
+	inside_sign = a * r_second_edge_S + b * r_second_edge_T;
+	if (inside_sign > 0.0f)
+	{
+		// within = (P_TRIANGLE_EDGE_0_MASK * (pd >= 0.0f)) | (Q_TRIANGLE_EDGE_0_MASK * (qd >= 0.0f));
+		p_within[0] = pd >= 0.0f;
+		q_within[0] = qd >= 0.0f;
+	}
+	else
+	{
+		// within = (P_TRIANGLE_EDGE_0_MASK * (pd <= 0.0f)) | (Q_TRIANGLE_EDGE_0_MASK * (qd <= 0.0f));
+		p_within[0] = pd <= 0.0f;
+		q_within[0] = qd <= 0.0f;
+	}
+	// if (!(within & (P_TRIANGLE_EDGE_0_MASK | Q_TRIANGLE_EDGE_0_MASK))) return false;
+	if (!p_within[0] && !q_within[0]) return false;
+		
+	// edge 1: corner + second_edge to corner + first_edge + second_edge
+	// second_edge defines outside direction
+	c = (r_corner_T + r_second_edge_T) * r_first_edge_S - (r_corner_S + r_second_edge_S) * r_first_edge_T;
+	pd = a * p_S + b * p_T + c;
+	qd = a * q_S + b * q_T + c;
+	if (inside_sign < 0.0f)
+	{
+		// within |= (P_TRIANGLE_EDGE_1_MASK * (pd >= 0.0f)) | (Q_TRIANGLE_EDGE_1_MASK * (qd >= 0.0f));
+		p_within[1] = pd >= 0.0f;
+		q_within[1] = qd >= 0.0f;
+	}
+	else
+	{
+		// within |= (P_TRIANGLE_EDGE_1_MASK * (pd <= 0.0f)) | (Q_TRIANGLE_EDGE_1_MASK * (qd <= 0.0f));
+		p_within[1] = pd <= 0.0f;
+		q_within[1] = qd <= 0.0f;
+	}
+	// if (!(within & (P_TRIANGLE_EDGE_1_MASK | Q_TRIANGLE_EDGE_1_MASK))) return false;
+	if (!p_within[1] && !q_within[1]) return false;
+	
+	// edge 2: corner to corner + second_edge
+	// first_edge defines inside direction
+	a = r_second_edge_T;
+	b = -r_second_edge_S;
+	c = r_corner_T * r_second_edge_S - r_corner_S * r_second_edge_T;
+	pd = a * p_S + b * p_T + c;
+	qd = a * q_S + b * q_T + c;
+	inside_sign = a * r_first_edge_S + b * r_first_edge_T;
+	if (inside_sign > 0.0f)
+	{
+		// within |= (P_TRIANGLE_EDGE_2_MASK * (pd >= 0.0f)) | (Q_TRIANGLE_EDGE_2_MASK * (qd >= 0.0f));
+		p_within[2] = pd >= 0.0f;
+		q_within[2] = qd >= 0.0f;
+	}
+	else
+	{
+		// within |= (P_TRIANGLE_EDGE_2_MASK * (pd <= 0.0f)) | (Q_TRIANGLE_EDGE_2_MASK * (qd <= 0.0f));
+		p_within[2] = pd <= 0.0f;
+		q_within[2] = qd <= 0.0f;
+	}
+	// if (!(within & (P_TRIANGLE_EDGE_2_MASK | Q_TRIANGLE_EDGE_2_MASK))) return false;
+	if (!p_within[2] && !q_within[2]) return false;
+	
+	// edge 3: corner + first_edge to corner + first_edge + second_edge
+	// first_edge defines outside direction
+	c = (r_corner_T + r_first_edge_T) * r_second_edge_S - (r_corner_S + r_first_edge_S) * r_second_edge_T;
+	pd = a * p_S + b * p_T + c;
+	qd = a * q_S + b * q_T + c;
+	if (inside_sign < 0.0f)
+	{
+		// within |= (P_TRIANGLE_EDGE_3_MASK * (pd >= 0.0f)) | (Q_TRIANGLE_EDGE_3_MASK * (qd >= 0.0f));
+		p_within[3] = pd >= 0.0f;
+		q_within[3] = qd >= 0.0f;
+	}
+	else
+	{
+		// within |= (P_TRIANGLE_EDGE_3_MASK * (pd <= 0.0f)) | (Q_TRIANGLE_EDGE_3_MASK * (qd <= 0.0f));
+		p_within[3] = pd <= 0.0f;
+		q_within[3] = qd <= 0.0f;
+	}
+	// if (!(within & (P_TRIANGLE_EDGE_3_MASK | Q_TRIANGLE_EDGE_3_MASK))) return false;
+	if (!p_within[3] && !q_within[3]) return false;
+	
+	int next_collision_endpoint_idx;
+	
+	// if p or q is within all edges, it is the endpoint of the collision
+	// if ((within & ALL_P_MASKS) == ALL_P_MASKS)
+	if (p_within[0] && p_within[1] && p_within[2] && p_within[3])
+	{
+        p_collision_endpoints[0] = p;
+		p_collision_triangle_edges[0] = p_edge;
+		// if ((within & ALL_Q_MASKS) == ALL_Q_MASKS)
+		if (q_within[0] && q_within[1] && q_within[2] && q_within[3])
+		{
+			p_collision_endpoints[1] = q;
+			p_collision_triangle_edges[1] = q_edge;
+			return true;
+		}
+		next_collision_endpoint_idx = 1;
+	}
+	// else if ((within & ALL_Q_MASKS) == ALL_Q_MASKS)
+	else if (q_within[0] && q_within[1] && q_within[2] && q_within[3])
+	{
+		p_collision_endpoints[0] = q;
+		p_collision_triangle_edges[0] = q_edge;
+		next_collision_endpoint_idx = 1;
+	}
+	else
+	{
+		next_collision_endpoint_idx = 0;
+	}
+	
+	// we've found any endpoints of the collision which are within the rectangle; further collision endpoints will lie on the edges of the rectangle;
+	 
+	// we check each edge of the rectangle for intersection with the triangle's plane-intersection line segment
+	
+	float s;
+	
+	// edge 0: corner to corner + first_edge
+	if (s2DLineLineCollision(r_corner_S, r_corner_T, r_first_edge_S, r_first_edge_T, p_S, p_T, q_S - p_S, q_T - p_T, &s))
+	{
+		p_collision_endpoints[next_collision_endpoint_idx] = rect->m_first_edge;
+		p_collision_endpoints[next_collision_endpoint_idx] *= s;
+		p_collision_endpoints[next_collision_endpoint_idx] += rect->m_corner;
+		p_collision_triangle_edges[next_collision_endpoint_idx] = NO_TRIANGLE_EDGE;
+		if (next_collision_endpoint_idx == 1) return true;
+		next_collision_endpoint_idx = 1;
+	}
+	
+	// edge 1: corner + second_edge to corner + first_edge + second_edge
+	if (s2DLineLineCollision(r_corner_S + r_second_edge_S, r_corner_T + r_second_edge_T, r_first_edge_S, r_first_edge_T, p_S, p_T, q_S - p_S, q_T - p_T, &s))
+	{
+		p_collision_endpoints[next_collision_endpoint_idx] = rect->m_first_edge;
+		p_collision_endpoints[next_collision_endpoint_idx] *= s;
+		p_collision_endpoints[next_collision_endpoint_idx] += rect->m_corner;
+		p_collision_endpoints[next_collision_endpoint_idx] += rect->m_second_edge;
+		p_collision_triangle_edges[next_collision_endpoint_idx] = NO_TRIANGLE_EDGE;
+		if (next_collision_endpoint_idx == 1) return true;
+		next_collision_endpoint_idx = 1;
+	}
+	
+	// edge 2: corner to corner + second_edge
+	if (s2DLineLineCollision(r_corner_S, r_corner_T, r_second_edge_S, r_second_edge_T, p_S, p_T, q_S - p_S, q_T - p_T, &s))
+	{
+		p_collision_endpoints[next_collision_endpoint_idx] = rect->m_second_edge;
+		p_collision_endpoints[next_collision_endpoint_idx] *= s;
+		p_collision_endpoints[next_collision_endpoint_idx] += rect->m_corner;
+		p_collision_triangle_edges[next_collision_endpoint_idx] = NO_TRIANGLE_EDGE;
+		if (next_collision_endpoint_idx == 1) return true;
+		next_collision_endpoint_idx = 1;
+	}
+	
+	// edge 3: corner + first_edge to corner + first_edge + second_edge
+	if (s2DLineLineCollision(r_corner_S + r_first_edge_S, r_corner_T + r_first_edge_T, r_second_edge_S, r_second_edge_T, p_S, p_T, q_S - p_S, q_T - p_T, &s))
+	{
+		if (next_collision_endpoint_idx == 1)
+		{
+			p_collision_endpoints[next_collision_endpoint_idx] = rect->m_second_edge;
+			p_collision_endpoints[next_collision_endpoint_idx] *= s;
+			p_collision_endpoints[next_collision_endpoint_idx] += rect->m_corner;
+			p_collision_endpoints[next_collision_endpoint_idx] += rect->m_first_edge;
+			p_collision_triangle_edges[next_collision_endpoint_idx] = NO_TRIANGLE_EDGE;
+			return true;
+		}
+	}
+	
+	// no collision could be found
+	return false;
+}
+
+////////////////////////////////////////////////////////////////////
+// CCollObj member functions
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObj::CCollObj() : m_Flags(0), mp_coll_tri_data(NULL)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObj::~CCollObj()
+{
+	// this should only be done on clones
+	if (mp_coll_tri_data && (m_Flags & mSD_CLONE))
+	{
+		delete mp_coll_tri_data;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CCollObj::SetChecksum(uint32 checksum)
+{
+	m_checksum = checksum;
+	if (mp_coll_tri_data)
+	{
+		mp_coll_tri_data->SetChecksum(checksum);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollObj::SetMovingObject(Obj::CCompositeObject *p_movable_object)
+{
+	Dbg_MsgAssert(0, ("Can't call SetMovingObject() on a non-moving collision type"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject * CCollObj::GetMovingObject() const
+{
+	return NULL;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+#define PRINT_RECT_COLLISION_DEBUG
+const float CCollObj::sLINE_BOX_EXTENT = 0.5f;	// Extent of box around collision line
+
+bool					CCollObj::sFindRectangleStaticCollision ( Mth::Rectangle& rect, S2DCollData* p_2D_coll_data, CCollCache *p_cache )
+{
+#if PRINT_CACHE_HITS
+	static uint s_rect_cache_hits = 0, s_num_rect_collisions = 0;
+#endif
+
+	Dbg_Assert(p_2D_coll_data);
+	
+	Mth::CBBox rect_bbox(rect.m_corner);
+	rect_bbox.AddPoint(rect.m_corner + rect.m_first_edge);
+	rect_bbox.AddPoint(rect.m_corner + rect.m_second_edge);
+	rect_bbox.AddPoint(rect.m_corner + rect.m_first_edge + rect.m_second_edge);
+	
+	bool use_cache = p_cache;
+	if (use_cache)
+	{
+		use_cache = p_cache->Contains(rect_bbox);
+#if PRINT_CACHE_HITS
+		if (use_cache)
+		{
+			s_rect_cache_hits++;
+		}
+		if (++s_num_rect_collisions >= 500)
+		{
+			Dbg_Message("Static Rect Cache Hits %d/500", s_rect_cache_hits);
+			s_rect_cache_hits = s_num_rect_collisions = 0;
+		}
+#endif
+	}
+	
+	CCollStatic** p_coll_obj_list = NULL;
+	if (!use_cache)
+	{
+		SSec::Manager* ss_man = Nx::CEngine::sGetNearestSuperSectorManager(rect.m_corner);
+		if (!ss_man) return false;
+		
+		// get a list of sectors within the super sectors cut by the rectangle
+		p_coll_obj_list = ss_man->GetIntersectingCollSectors(rect_bbox);
+	}
+	
+	// bloat the rectangle bounding box for comparison to sector bounding boxes
+	Mth::Vector extension(sLINE_BOX_EXTENT, sLINE_BOX_EXTENT, sLINE_BOX_EXTENT);
+	rect_bbox.Set(rect_bbox.GetMin() - extension, rect_bbox.GetMax() + extension);
+	
+	p_2D_coll_data->num_surfaces = 0;
+	
+	if (use_cache)
+	{
+		int num_collisions = p_cache->GetNumStaticCollisions();
+		const SCollCacheNode* p_coll_node = p_cache->GetStaticCollisionArray();
+		
+		// loop over sectors in the cache
+		for (int i = num_collisions; i--; )
+		{
+			// check to see if the sector's bounding box intersects with the rectangle's bounding box
+			if (rect_bbox.Intersect(*(p_coll_node->mp_bbox)))
+			{
+				// check the sector's trangle's for collision with the rectangle
+				p_coll_node->mp_coll_obj->CollisionWithRectangle(rect, rect_bbox, p_2D_coll_data);
+			}
+
+			p_coll_node++;
+		} // END loop over sectors in the cache
+	}
+	else
+	{
+		// loop over potentially collided with sectors
+		CCollStatic* p_coll_obj;
+		while ((p_coll_obj = *p_coll_obj_list))
+		{
+			Dbg_Assert(p_coll_obj->GetGeometry());
+			
+			// check to see if the sector's bounding box intersects with the rectangle's bounding box
+			if (p_coll_obj->WithinBBox(rect_bbox))
+			{
+				// check the sector's trangle's for collision with the rectangle
+				p_coll_obj->CollisionWithRectangle(rect, rect_bbox, p_2D_coll_data);
+			}
+			
+			p_coll_obj_list++;
+		} // END loop over potentially collided with sectors
+	}
+	
+	return p_2D_coll_data->num_surfaces > 0;
+}
+
+bool GPrintCollisionData = false;
+
+bool					CCollObj::sFindNearestStaticCollision( Mth::Line &is, CollData *p_data, void *p_callback, CCollCache *p_cache)
+{
+#if PRINT_CACHE_HITS
+	static uint s_cache_hits = 0, s_num_collisions = 0;
+#endif
+
+    Dbg_Assert(p_data);
+    Dbg_Assert(p_data->coll_found == false);
+
+	p_data->callback = (void (*)( CollData*)) p_callback;
+
+	// Make initial line bounding box
+	Mth::CBBox line_bbox(is.m_start);
+	line_bbox.AddPoint(is.m_end);
+
+	if (GPrintCollisionData)
+	{
+		Dbg_Message("Inited line BBox (%f, %f, %f), (%f, %f, %f)",
+					line_bbox.GetMin()[X], line_bbox.GetMin()[Y], line_bbox.GetMin()[Z],
+					line_bbox.GetMax()[X], line_bbox.GetMax()[Y], line_bbox.GetMax()[Z]);
+	}
+
+	// Check to see if we can use the cache
+	bool use_cache = p_cache != NULL;
+	if (use_cache)
+	{
+		use_cache = p_cache->Contains(line_bbox);
+		
+		if (CCollCacheManager::sGetAssertOnCacheMiss())
+		{
+			Dbg_MsgAssert(use_cache, ("Collision cache miss"));
+		}
+#if PRINT_CACHE_HITS
+		if (use_cache) s_cache_hits++;
+		if (++s_num_collisions >= 5000)
+		{
+			Dbg_Message("Static Cache Hits %d/5000", s_cache_hits);
+			s_cache_hits = s_num_collisions = 0;
+		}
+#endif
+	}
+
+	// initialize starting "max" distance to be max collision distance
+	CCollStatic** p_coll_obj_list = NULL;
+	if (!use_cache)
+	{
+		SSec::Manager *ss_man;
+
+		ss_man = Nx::CEngine::sGetNearestSuperSectorManager(is);
+		if (!ss_man)
+			return NULL;
+
+		p_coll_obj_list = ss_man->GetIntersectingCollSectors( is );
+	}
+	
+	// For a line, the dist is returned as a fraction of the line length
+	// so we set the dist field to just over 1.0, any found collision
+	// will be less than this
+	if (s_check_for_far)
+	{
+		p_data->dist = -0.01f; 		// but if checking in the other direction, we need to to reverse the check
+	}
+	else
+	{
+		p_data->dist = 1.01f;
+	}
+
+	// Bloat the collision line only for the purposes of deciding against which world 
+	// sectors to test the actual collision line
+	Mth::Vector extend;
+
+	extend = line_bbox.GetMax();
+	extend[X] += sLINE_BOX_EXTENT;
+	extend[Y] += sLINE_BOX_EXTENT;
+	extend[Z] += sLINE_BOX_EXTENT;
+	line_bbox.AddPoint(extend);
+
+	extend = line_bbox.GetMin();
+	extend[X] -= sLINE_BOX_EXTENT;
+	extend[Y] -= sLINE_BOX_EXTENT;
+	extend[Z] -= sLINE_BOX_EXTENT;
+	line_bbox.AddPoint(extend);
+
+	// Calculate ray
+	Mth::Vector line_dir(is.m_end - is.m_start);
+
+	if (GPrintCollisionData)
+	{
+		Dbg_Message("Starting collision check with line BBox (%f, %f, %f), (%f, %f, %f)",
+					line_bbox.GetMin()[X], line_bbox.GetMin()[Y], line_bbox.GetMin()[Z],
+					line_bbox.GetMax()[X], line_bbox.GetMax()[Y], line_bbox.GetMax()[Z]);
+		Dbg_Message("Line start (%f, %f, %f)", is.m_start[X], is.m_start[Y], is.m_start[Z]);
+		Dbg_Message("Line end (%f, %f, %f)", is.m_end[X], is.m_end[Y], is.m_end[Z]);
+	}
+
+	bool new_coll_found = false;
+
+	if (use_cache)
+	{
+		int num_collisions = p_cache->GetNumStaticCollisions();
+		const SCollCacheNode *p_coll_node = p_cache->GetStaticCollisionArray();
+		for (int i = 0; i < num_collisions; i++)
+		{
+			if(line_bbox.Intersect(*(p_coll_node->mp_bbox)))
+			{
+				/* It's a world sector, call the callback */
+				if (p_coll_node->mp_coll_obj->CollisionWithLine( is, line_dir, p_data, &line_bbox ))
+				{
+					new_coll_found = true;
+				}
+			}
+			p_coll_node++;
+		}
+	} else {
+		CCollStatic* p_coll_obj;
+
+	#ifdef BATCH_TRI_COLLISION
+		// Init batch manager
+		bool do_batch;
+		do_batch = CBatchTriCollMan::sInit(p_data);
+	#endif
+
+		/* Start at the top */
+		while((p_coll_obj = *p_coll_obj_list))
+		{
+			// TODO: Come up with a cleaner BBox check
+			Dbg_Assert(p_coll_obj->GetGeometry());
+			//if(line_bbox.Intersect(p_coll_obj->GetGeometry()->GetBBox()))
+			if (p_coll_obj->WithinBBox(line_bbox))
+			{
+				// this line will display the bounding box of the potential collidable object
+				// useful to make sure we are only colliding with the minimum necessary objects
+				// p_coll_obj->mp_coll_tri_data->GetBBox().DebugRender(0xfffff,1);
+
+				/* It's a world sector, call the callback */
+				if (p_coll_obj->CollisionWithLine( is, line_dir, p_data, &line_bbox ))
+				{
+					new_coll_found = true;
+				}
+			}
+			p_coll_obj_list++;
+		}
+
+	#ifdef BATCH_TRI_COLLISION
+		// Wait for tri collision to finish first
+		if (do_batch)
+		{
+			new_coll_found = CBatchTriCollMan::sWaitTriCollisions();
+		}
+		CBatchTriCollMan::sFinish();				// This should really be in the CFeeler code
+	#endif
+	}
+
+    /* All done */
+	if (p_data->coll_found)
+	{
+		if ((p_data->dist > 1.0f) || (p_data->dist < 0.0f))
+		{
+			Dbg_Message("Static Collision distance too big: %f", p_data->dist);
+		}
+	}
+
+	// return true if we found something			
+	return( new_coll_found );
+}
+
+
+bool					CCollObj::sFindFarStaticCollision( Mth::Line &is, CollData *p_data, void *p_callback, CCollCache *p_cache)
+{
+	s_check_for_far = true;
+	bool result = sFindNearestStaticCollision(is,p_data,p_callback,p_cache);
+	s_check_for_far = false;
+	return result;		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Note, p_data is assumed to be already initialized
+// either it is set to default values, or we have
+// come here after using it to check for static collision
+// and the results of that are in there
+// - see below, we don't set "dist" if we've previously found a collision
+// (stops you snapping through walls onto cars)
+				  
+Obj::CCompositeObject *	CCollObj::sFindNearestMovableCollision( Mth::Line &is, CollData *p_data, void *p_callback, CCollCache *p_cache)
+{
+#if PRINT_CACHE_HITS
+	static uint s_cache_hits = 0, s_num_collisions = 0;
+#endif
+
+    Dbg_Assert(p_data);
+
+	Obj::CCompositeObject *p_collision_obj = NULL;
+
+	p_data->callback = (void (*)( CollData*)) p_callback;
+
+	
+	// initialize starting "max" distance to be max collision distance
+	// actually this	
+	if (!p_data->coll_found)
+	{
+		// For a line, the dist is returned as a fraction of the line length
+		// so we set the dist field to just over 1.0, any found collision
+		// will be less than this
+		if (s_check_for_far)
+		{
+			p_data->dist = -0.01f; 		// but if checking in the other direction, we need to to reverse the check
+		}
+		else
+		{
+			p_data->dist = 1.01f;
+		}
+	}
+
+	// Make initial line bounding box
+	Mth::CBBox line_bbox(is.m_start);
+	line_bbox.AddPoint(is.m_end);
+
+	// Check to see if we can use the cache
+	bool use_cache = p_cache != NULL;
+	if (use_cache)
+	{
+		use_cache = p_cache->Contains(line_bbox);
+#if PRINT_CACHE_HITS
+		if (use_cache) s_cache_hits++;
+		if (++s_num_collisions >= 5000)
+		{
+			Dbg_Message("Movable Cache Hits %d/5000", s_cache_hits);
+			s_cache_hits = s_num_collisions = 0;
+		}
+#endif
+	}
+
+	if (0 && GPrintCollisionData)
+	{
+		Dbg_Message("Inited line BBox (%f, %f, %f), (%f, %f, %f)",
+					line_bbox.GetMin()[X], line_bbox.GetMin()[Y], line_bbox.GetMin()[Z],
+					line_bbox.GetMax()[X], line_bbox.GetMax()[Y], line_bbox.GetMax()[Z]);
+	}
+
+	// Bloat the collision line only for the purposes of deciding against which world 
+	// sectors to test the actual collision line
+	Mth::Vector extend;
+
+	extend = line_bbox.GetMax();
+	extend[X] += sLINE_BOX_EXTENT;
+	extend[Y] += sLINE_BOX_EXTENT;
+	extend[Z] += sLINE_BOX_EXTENT;
+	line_bbox.AddPoint(extend);
+
+	extend = line_bbox.GetMin();
+	extend[X] -= sLINE_BOX_EXTENT;
+	extend[Y] -= sLINE_BOX_EXTENT;
+	extend[Z] -= sLINE_BOX_EXTENT;
+	line_bbox.AddPoint(extend);
+
+	// Calculate ray
+	Mth::Vector line_dir(is.m_end - is.m_start);
+
+	if (0 && GPrintCollisionData)
+	{
+		Dbg_Message("Starting collision check with line BBox (%f, %f, %f), (%f, %f, %f)",
+					line_bbox.GetMin()[X], line_bbox.GetMin()[Y], line_bbox.GetMin()[Z],
+					line_bbox.GetMax()[X], line_bbox.GetMax()[Y], line_bbox.GetMax()[Z]);
+		Dbg_Message("Line start (%f, %f, %f)", is.m_start[X], is.m_start[Y], is.m_start[Z]);
+		Dbg_Message("Line end (%f, %f, %f)", is.m_end[X], is.m_end[Y], is.m_end[Z]);
+	}
+
+//	Dbg_Message("Size of movable list %d", Nx::CEngine::sGetMovableObjects().CountItems());
+//	Dbg_Message("Size of movable collision list %d", CMovableCollMan::sGetCollisionList()->CountItems());
+//	Dbg_Assert(0);
+
+	if (use_cache)
+	{
+		int num_collisions = p_cache->GetNumMovableCollisions();
+		const SCollCacheNode *p_coll_node = p_cache->GetMovableCollisionArray();
+		for (int i = 0; i < num_collisions; i++)
+		{
+			if(p_coll_node->mp_bbox && line_bbox.Intersect(*(p_coll_node->mp_bbox)))
+			{
+				// Set the callback_object, so any callback will know
+				// what object we were colliding with
+				p_data->mp_callback_object = p_coll_node->mp_coll_obj->GetMovingObject();
+
+				/* It's a world sector, call the callback */
+				if (p_coll_node->mp_coll_obj->CollisionWithLine( is, line_dir, p_data, &line_bbox ))
+				{
+					p_collision_obj = p_coll_node->mp_coll_obj->GetMovingObject();
+					Dbg_Assert(p_collision_obj);
+				}
+
+				// the callback object is only valid for the duration of a possible callback											 
+				p_data->mp_callback_object = NULL;
+			}
+			p_coll_node++;
+		}
+	} else {
+	#if 0 //def BATCH_TRI_COLLISION
+		// Init batch manager
+		bool do_batch;
+		do_batch = CBatchTriCollMan::sInit(p_data);
+	#endif
+
+
+		Lst::Node< CCollObj > *p_movable_node = CMovableCollMan::sGetCollisionList()->GetNext();
+		//CCollObj **p_movable_node = CMovableCollMan::sGetCollisionArray();
+
+		while(p_movable_node)
+		{
+			CCollObj *p_coll_obj = p_movable_node->GetData();
+			//CCollObj *p_coll_obj = *p_movable_node;
+
+			if (p_coll_obj && !(p_coll_obj->m_Flags & (mSD_NON_COLLIDABLE | mSD_KILLED )))
+			{
+		
+				/* It's a collision object, call the callback */
+				//if (p_coll_obj->CollisionWithLine( is, line_dir, p_data , &line_bbox))
+				if (p_coll_obj->WithinBBox(line_bbox))
+				{
+					// Set the callback_object, so any callback will know
+					// what object we were colliding with
+					p_data->mp_callback_object = p_coll_obj->GetMovingObject();
+				
+					if (p_coll_obj->CollisionWithLine( is, line_dir, p_data, &line_bbox ))
+					{
+						p_collision_obj = p_coll_obj->GetMovingObject();
+						Dbg_Assert(p_collision_obj);
+					}
+					// the callback object is only valid for the duration of a possible callback											 
+					p_data->mp_callback_object = NULL;
+				}
+			
+			}
+
+			p_movable_node = p_movable_node->GetNext();
+			//p_movable_node++;
+		}
+
+	#if 0 //def BATCH_TRI_COLLISION
+		// Wait for tri collision to finish first
+		if (do_batch)
+		{
+			new_coll_found = CBatchTriCollMan::sWaitTriCollisions();
+		}
+		CBatchTriCollMan::sFinish();				// This should really be in the CFeeler code
+	#endif
+	}
+
+    /* All done */
+	if (p_collision_obj)
+	{
+
+		if ((p_data->dist > 1.0f) || (p_data->dist < 0.0f))
+		{
+			Dbg_Message("Movable Collision distance too big: %f", p_data->dist);
+		}
+	}
+
+	return p_collision_obj;
+}
+
+Obj::CCompositeObject *	CCollObj::sFindFarMovableCollision( Mth::Line &is, CollData *p_data, void *p_callback, CCollCache *p_cache)
+{
+	s_check_for_far = true;
+	Obj::CCompositeObject *result = sFindNearestMovableCollision(is,p_data,p_callback,p_cache);
+	s_check_for_far = false;
+	return result;		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CollType	CCollObj::sConvertTypeChecksum(uint32 checksum)
+{
+	CollType c_type;
+
+	switch (checksum)
+	{
+	case 0x6aadf154: // Geometry
+		c_type = vCOLL_TYPE_GEOM;
+		break;
+
+	case 0xe460abde: // BoundingBox
+	case 0x2ba71070: // Spherical
+	case 0xdd42aa66: // Cylindrical
+		c_type =  vCOLL_TYPE_BBOX;
+		break;
+
+	case 0x806fff30: // None
+	default:
+		c_type = vCOLL_TYPE_NONE;
+		break;
+	}
+
+	return c_type;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObj *	CCollObj::sCreateMovableCollision(CollType type, Nx::CCollObjTriData *p_coll_obj_data, int num_coll_objects, Obj::CCompositeObject *p_object)
+{
+	CCollObj *p_collision;
+
+	switch (type)
+	{
+	case vCOLL_TYPE_BBOX:
+		{
+			CCollMovBBox *p_coll_bbox = new Nx::CCollMovBBox;
+
+			// TODO: determine if we should make more than one collision object
+			if (num_coll_objects >= 1) {
+				Mth::CBBox bbox/*(Mth::Vector(0.0f, 0.0f, 0.0f))*/;	// Add origin for now
+
+				for (int i = 0; i < num_coll_objects; i++)
+				{
+					bbox.AddPoint(p_coll_obj_data[i].GetBBox().GetMin());
+					bbox.AddPoint(p_coll_obj_data[i].GetBBox().GetMax());
+				}
+
+				p_coll_bbox->SetBoundingBox(bbox);
+			}
+
+			p_collision = p_coll_bbox;
+			break;
+		}
+	case vCOLL_TYPE_GEOM:
+		if (num_coll_objects == 1)				// one node
+		{
+			p_collision = new CCollMovTri(p_coll_obj_data);
+		} else if (num_coll_objects > 1) {		// multi nodes
+			CCollMulti *p_coll_multi = new Nx::CCollMulti;
+
+			for (int i = 0; i < num_coll_objects; i++)
+			{
+				CCollObj *p_coll_tri;
+
+				p_coll_tri = new CCollMovTri(&(p_coll_obj_data[i]));
+				p_coll_multi->AddCollision(p_coll_tri);
+			}
+
+			p_collision = p_coll_multi;
+		} else {
+			p_collision = NULL;
+		}
+		break;
+
+	default:
+		p_collision = NULL;						// default to no collision
+		break;
+	}
+
+	// Add to movable collision manager
+	if (p_collision)
+	{
+		p_collision->SetMovingObject(p_object);
+		CMovableCollMan::sAddCollision(p_collision, p_object);
+	}
+
+	return p_collision;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMulti::CCollMulti()
+{
+	mp_collision_list = new Lst::Head< CCollObj >;
+	m_movement_changed = true;
+	mp_movable_object = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMulti::~CCollMulti()
+{
+	if (mp_collision_list)
+	{
+		Lst::Node< CCollObj > *obj_node, *next;
+
+		for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = next)
+		{
+			next = obj_node->GetNext();
+
+			delete obj_node->GetData();
+			delete obj_node;
+		}
+
+		delete mp_collision_list;
+	}
+
+	// Take out of caches
+	CCollCacheManager::sDeleteCollision(this);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMulti::AddCollision(CCollObj *p_collision)
+{
+	Lst::Node< CCollObj > *node = new Lst::Node< CCollObj > (p_collision);
+	mp_collision_list->AddToTail(node);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMulti::SetWorldPosition(const Mth::Vector & pos)
+{
+	Lst::Node< CCollObj > *obj_node;
+
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		obj_node->GetData()->SetWorldPosition(pos);
+	}
+
+	m_movement_changed = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMulti::SetOrientation(const Mth::Matrix & orient)
+{
+	Lst::Node< CCollObj > *obj_node;
+
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		obj_node->GetData()->SetOrientation(orient);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMulti::SetGeometry(CCollObjTriData *p_geom_data)
+{
+	Lst::Node< CCollObj > *obj_node;
+
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		obj_node->GetData()->SetGeometry(p_geom_data);
+	}
+
+	m_movement_changed = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMulti::SetMovingObject(Obj::CCompositeObject *p_movable_object)
+{
+	mp_movable_object = p_movable_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject * CCollMulti::GetMovingObject() const
+{
+	return mp_movable_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObj * 	CCollMulti::Clone(bool instance)
+{
+	Dbg_MsgAssert(!instance, ("CCollMulti::Clone() with instances not implemented yet"));
+	Dbg_Assert(mp_coll_tri_data == NULL);
+
+	CCollMulti *m_new_coll = new CCollMulti(*this);
+
+	m_new_coll->m_Flags |=  mSD_CLONE;
+
+	Lst::Node< CCollObj > *obj_node;
+
+	// Clone List
+	m_new_coll->mp_collision_list = new Lst::Head< CCollObj >;
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		Lst::Node< CCollObj > *node = new Lst::Node< CCollObj > (obj_node->GetData()->Clone(instance));
+		m_new_coll->mp_collision_list->AddToTail(node);
+	}
+
+	return m_new_coll;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CCollMulti::IsTriangleCollision() const
+{
+	Lst::Node< CCollObj > *obj_node;
+	bool ret_val = true;
+
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		ret_val = ret_val && obj_node->GetData()->IsTriangleCollision();
+	}
+
+	return ret_val;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMulti::update_world_bbox()
+{
+	Lst::Node< CCollObj > *obj_node;
+
+	m_world_bbox_valid = true;
+	m_world_bbox.Reset();
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		Mth::CBBox *obj_bbox;
+		if ((obj_bbox = obj_node->GetData()->get_bbox()))
+		{
+			m_world_bbox.AddPoint(obj_bbox->GetMin());
+			m_world_bbox.AddPoint(obj_bbox->GetMax());
+		} else {
+			m_world_bbox_valid = false;
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::CBBox *CCollMulti::get_bbox()
+{
+	if (m_movement_changed)
+	{
+		update_world_bbox();
+		m_movement_changed = false;
+	}
+
+	if (m_world_bbox_valid)
+	{
+		return &m_world_bbox;
+	} else {
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CCollMulti::WithinBBox(const Mth::CBBox & testBBox)
+{
+	if (m_movement_changed)
+	{
+		update_world_bbox();
+		m_movement_changed = false;
+	}
+
+	return !m_world_bbox_valid || testBBox.Intersect(m_world_bbox);
+	//return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CCollMulti::CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox * p_bbox)
+{
+	Lst::Node< CCollObj > *obj_node;
+	bool ret_val = false;
+
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		ret_val = obj_node->GetData()->CollisionWithLine(testLine, lineDir, p_data, p_bbox) || ret_val;
+	}
+
+	return ret_val;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CCollMulti::CollisionWithRectangle(const Mth::Rectangle& rect, const Mth::CBBox& rect_bbox, S2DCollData* p_data)
+{
+	Dbg_MsgAssert(false, ("CCollMulti::CollisionWithRecangle is not implemented"));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMulti::DebugRender(uint32 ignore_1, uint32 ignore_0)
+{
+	Lst::Node< CCollObj > *obj_node;
+
+	for(obj_node = mp_collision_list->GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		obj_node->GetData()->DebugRender(ignore_1, ignore_0);
+	}
+}
+
+// Don't call these!
+
+const Mth::Vector &	CCollMulti::GetWorldPosition() const
+{
+	Dbg_MsgAssert(0, ("Don't call CCollMulti::GetWorldPosition()"));
+	return mp_collision_list->GetNext()->GetData()->GetWorldPosition();
+}
+
+const Mth::Matrix &	CCollMulti::GetOrientation() const
+{
+	Dbg_MsgAssert(0, ("Don't call CCollMulti::GetOrientation()"));
+	return mp_collision_list->GetNext()->GetData()->GetOrientation();
+}
+
+uint32 		CCollMulti::GetFaceFlags(int face_idx) const
+{
+	Dbg_MsgAssert(0, ("Don't call CCollMulti::GetFaceFlags()"));
+	return 0;
+}
+
+uint16 		CCollMulti::GetFaceTerrainType(int face_idx) const
+{
+	Dbg_MsgAssert(0, ("Don't call CCollMulti::GetFaceTerrainType()"));
+	return 0;
+}
+
+Mth::Vector	CCollMulti::GetVertexPos(int vert_idx) const
+{
+	Dbg_MsgAssert(0, ("Don't call CCollMulti::GetVertexPos()"));
+	return Mth::Vector(0, 0, 0, 0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovable::CCollMovable() : 
+	m_world_pos(0, 0, 0, 1),
+	m_orient(0, 0, 0),
+	m_orient_transpose(0, 0, 0),
+	mp_movable_object(NULL)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovable::~CCollMovable()
+{
+	// Take out of caches
+	CCollCacheManager::sDeleteMovableCollision(this);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector	CCollMovable::GetVertexPos(int vert_idx) const
+{
+	Dbg_Assert(mp_coll_tri_data);
+
+	Mth::Vector pos(mp_coll_tri_data->GetRawVertexPos(vert_idx));
+
+	pos[W] = 0.0f;				// start as vector
+	pos.Rotate(m_orient);
+	pos += m_world_pos;
+
+	return pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMovable::SetMovingObject(Obj::CCompositeObject *p_movable_object)
+{
+	mp_movable_object = p_movable_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject * CCollMovable::GetMovingObject() const
+{
+	return mp_movable_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMovable::convert_line_to_local(const Mth::Line &world_line, Mth::Line &local_line)
+{
+	local_line = world_line;
+
+	// First, translate back
+	local_line -= m_world_pos;
+
+	// And then do a reverse rotation
+	local_line.m_start.Rotate(m_orient_transpose);
+	local_line.m_end.Rotate(m_orient_transpose);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollStatic::CCollStatic() //: 
+//	m_world_pos(0, 0, 0, 1)//,
+//	m_orient(0, 0, 0)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollStatic::~CCollStatic()
+{
+	// Take out of caches
+	//CCollCacheManager::sDeleteStaticCollision(this);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector	CCollStatic::GetVertexPos(int vert_idx) const
+{
+	Dbg_Assert(mp_coll_tri_data);
+
+	Mth::Vector pos(mp_coll_tri_data->GetRawVertexPos(vert_idx));
+
+	pos[W] = 1.0f;				// make a point
+
+	return pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovBBox::CCollMovBBox() :
+	m_flags(0),
+	m_terrain_type(0)
+{
+	// Sets a default bounding box, if nothing loaded
+	//m_bbox.Set(Mth::Vector(-60, -20, -60, 1), Mth::Vector(60, 36, 60, 1));
+	m_bbox.Set(Mth::Vector(-50, -20, -90, 1), Mth::Vector(30, 36, 90, 1));		// size of car
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovBBox::CCollMovBBox(CCollObjTriData *p_geom_data) :
+	m_flags(0),
+	m_terrain_type(0)
+{
+	SetGeometry(p_geom_data);
+	Dbg_Assert(mp_coll_tri_data);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovBBox::~CCollMovBBox()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMovBBox::SetGeometry(CCollObjTriData *p_geom_data)
+{
+	CCollObj::SetGeometry(p_geom_data);
+
+	Dbg_Assert(mp_coll_tri_data);
+	SetBoundingBox(mp_coll_tri_data->GetBBox());
+}
+
+// Temp functions
+uint32 CCollMovBBox::GetFaceFlags(int face_idx) const
+{
+	return m_flags;
+}
+
+uint16 CCollMovBBox::GetFaceTerrainType(int face_idx) const
+{
+	return m_terrain_type;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObj * 	CCollMovBBox::Clone(bool instance)
+{
+	Dbg_MsgAssert(!instance, ("CCollMovBBox::Clone() with instances not implemented yet"));
+
+	CCollMovBBox *m_new_coll = new CCollMovBBox(*this);
+
+	m_new_coll->m_Flags |=  mSD_CLONE;
+
+	m_new_coll->mp_coll_tri_data = NULL;		// We don't need to clone the data since we have the bbox
+
+	return m_new_coll;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollMovBBox::CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox *p_bbox)
+{
+	Dbg_Assert(p_data);
+
+	// Find line in local space
+	Mth::Line local_line;
+	convert_line_to_local(testLine, local_line);
+
+
+	if (0 && GPrintCollisionData)
+	{
+		Dbg_Message("Testing ray with start (%f, %f, %f)", testLine.m_start[X], testLine.m_start[Y], testLine.m_start[Z]);
+		Dbg_Message("and end (%f, %f, %f)", testLine.m_end[X], testLine.m_end[Y], testLine.m_end[Z]);
+		Dbg_Message("with the direction (%f, %f, %f)", lineDir[X], lineDir[Y], lineDir[Z]);
+	}
+
+
+	// Mick: temp optimization, generate a bounding box for the line
+	// so it can be tested against the collision bounding box
+	Mth::CBBox line_bbox(local_line.m_start);
+	line_bbox.AddPoint(local_line.m_end);
+
+
+	// Do collision
+	if (m_bbox.Intersect(line_bbox))
+	{
+		Mth::Vector local_point, local_normal;
+
+		if (m_bbox.LineIntersect(local_line, local_point, local_normal))
+		{
+			float distance = Mth::Vector(local_point - local_line.m_start).Length() / 			// gotta be a faster way
+							 Mth::Vector(local_line.m_end - local_line.m_start).Length();
+
+			SCollSurface collisionSurface;
+
+			if (GPrintCollisionData)
+			{
+				Dbg_Message("\n*Start point (%f, %f, %f)", testLine.m_start[X], testLine.m_start[Y], testLine.m_start[Z]);
+				Dbg_Message("*End point (%f, %f, %f)", testLine.m_end[X], testLine.m_end[Y], testLine.m_end[Z]);
+				Dbg_Message("*Start local point (%f, %f, %f)", local_line.m_start[X], local_line.m_start[Y], local_line.m_start[Z]);
+				Dbg_Message("*End local point (%f, %f, %f)", local_line.m_end[X], local_line.m_end[Y], local_line.m_end[Z]);
+				Dbg_Message("*World pos (%f, %f, %f)", m_world_pos[X], m_world_pos[Y], m_world_pos[Z]);
+				Dbg_Message("*Local collision pos (%f, %f, %f)", local_point[X], local_point[Y], local_point[Z]);
+				Dbg_Message("*Local Normal (%f, %f, %f)", local_normal[X], local_normal[Y], local_normal[Z]);
+				Dbg_Message("*Orientation [%f, %f, %f]", m_orient[X][X], m_orient[X][Y], m_orient[X][Z]);
+				Dbg_Message("*            [%f, %f, %f]", m_orient[Y][X], m_orient[Y][Y], m_orient[Y][Z]);
+				Dbg_Message("*            [%f, %f, %f]", m_orient[Z][X], m_orient[Z][Y], m_orient[Z][Z]);
+			}
+
+			// Convert to world coordinates
+			collisionSurface.point = local_point.Rotate(m_orient);
+			collisionSurface.point += m_world_pos;
+			collisionSurface.normal = local_normal.Rotate(m_orient);
+
+			if (GPrintCollisionData)
+			{
+				Dbg_Message("*Collision pos (%f, %f, %f)", collisionSurface.point[X], collisionSurface.point[Y], collisionSurface.point[Z]);
+				Dbg_Message("*Normal (%f, %f, %f)", collisionSurface.normal[X], collisionSurface.normal[Y], collisionSurface.normal[Z]);
+				Dbg_Message("*distance %f", distance);
+			}
+
+			return s_found_collision(&testLine, this, &collisionSurface, distance, p_data);
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollMovBBox::CollisionWithRectangle(const Mth::Rectangle& rect, const Mth::CBBox& rect_bbox, S2DCollData* p_data)
+
+{
+	Dbg_MsgAssert(false, ("CCollMovBBox::CollisionWithRecangle is not implemented"));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollMovBBox::DebugRender(uint32 ignore_1, uint32 ignore_0)
+{
+	// Debug box
+	Mth::Matrix total_mat = m_orient;
+	total_mat[W] = m_world_pos;
+	m_bbox.DebugRender(total_mat, 0x80808080, 1);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollStaticTri::CCollStaticTri()
+{
+	// This instance can't be used until someone sets the geometry data
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollStaticTri::~CCollStaticTri()
+{
+	// Don't delete the Tri Data
+}
+
+// Temp functions
+uint32 CCollStaticTri::GetFaceFlags(int face_idx) const
+{
+	Dbg_Assert(mp_coll_tri_data);
+	return mp_coll_tri_data->GetFaceFlags(face_idx);
+}
+
+uint16 CCollStaticTri::GetFaceTerrainType(int face_idx) const
+{
+	Dbg_Assert(mp_coll_tri_data);
+	return mp_coll_tri_data->GetFaceTerrainType(face_idx);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollStaticTri::SetWorldPosition(const Mth::Vector & pos)
+{
+	Dbg_Assert(mp_coll_tri_data);
+	mp_coll_tri_data->Translate(pos - GetWorldPosition());
+
+	CCollStatic::SetWorldPosition(pos);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	CCollStaticTri::SetOrientation(const Mth::Matrix & orient)
+{
+	CCollStatic::SetOrientation(orient);
+
+	Dbg_MsgAssert(0, ("CCollStaticTri::SetOrientation() not implemented yet"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObj * 	CCollStaticTri::Clone(bool instance)
+{
+	Dbg_MsgAssert(!instance, ("CCollStaticTri::Clone() with instances not implemented yet"));
+
+	// Clone only if there is geometry
+	CCollObjTriData *p_new_tri_data = mp_coll_tri_data->Clone(instance, true);
+	if (!p_new_tri_data)
+	{
+		return NULL;
+	}
+
+	CCollStaticTri *p_new_coll = new CCollStaticTri(*this);
+
+	p_new_coll->m_Flags |=  mSD_CLONE;
+
+	p_new_coll->mp_coll_tri_data = p_new_tri_data;
+
+	return p_new_coll;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollStaticTri::RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y)
+{
+	Dbg_Assert(mp_coll_tri_data);
+
+	mp_coll_tri_data->RotateY(world_origin, rot_y);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollStaticTri::Scale(const Mth::Vector & world_origin, const Mth::Vector & scale)
+{
+	Dbg_Assert(mp_coll_tri_data);
+
+	mp_coll_tri_data->Scale(world_origin, scale);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollStaticTri::WithinBBox(const Mth::CBBox & testBBox)
+{
+	Dbg_Assert(mp_coll_tri_data);
+	return testBBox.Intersect(mp_coll_tri_data->GetBBox());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollStaticTri::CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox *p_bbox)
+{
+	Dbg_Assert(p_data);
+	Dbg_Assert(mp_coll_tri_data);
+
+	bool best_collision = false;
+
+	// Generate a bounding box for the line
+	// so it can be tested against the BSP tree.	  
+	// (Should we just pass along the one we had before, even though it won't work with movables?)
+//	Mth::CBBox line_bbox(testLine.m_start);
+//	line_bbox.AddPoint(testLine.m_end);
+
+	uint num_faces;
+	FaceIndex *p_face_indexes;
+	p_face_indexes = mp_coll_tri_data->FindIntersectingFaces(*p_bbox, num_faces);
+	Dbg_Assert(p_face_indexes);
+
+#ifdef BATCH_TRI_COLLISION
+	bool do_batch = !CBatchTriCollMan::sIsNested();
+#endif
+
+	for (uint fidx = 0; fidx < num_faces; fidx++, p_face_indexes++)
+	{
+#ifdef BATCH_TRI_COLLISION
+		if (do_batch)
+		{
+			CBatchTriCollMan::sAddTriCollision(testLine, lineDir, this, *p_face_indexes);
+			continue;
+		}
+#endif // BATCH_TRI_COLLISION
+
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+		Mth::Vector v0, v1, v2;
+		if (mp_coll_tri_data->m_use_face_small)
+		{
+			CCollObjTriData::SFaceSmall *face = &(mp_coll_tri_data->mp_face_small[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		} else {
+			CCollObjTriData::SFace *face = &(mp_coll_tri_data->mp_faces[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		}
+#else
+		Mth::Vector *v0, *v1, *v2;
+		if (mp_coll_tri_data->m_use_face_small)
+		{
+			CCollObjTriData::SFaceSmall *face = &(mp_coll_tri_data->mp_face_small[*p_face_indexes]);
+
+			v0 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+			v1 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+			v2 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+		} else {
+			CCollObjTriData::SFace *face = &(mp_coll_tri_data->mp_faces[*p_face_indexes]);
+
+			v0 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+			v1 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+			v2 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+		}
+#endif // FIXED_POINT_VERTICES
+
+#if 0
+		uint32 coll_color = (uint32) MAKE_RGB(200, 200, 200);
+		if (coll_color != (uint32) MAKE_RGB( 0, 200, 0 ))
+		{
+			Gfx::AddDebugLine(*v0,*v1,coll_color,1);
+			Gfx::AddDebugLine(*v1,*v2,coll_color,1);
+			Gfx::AddDebugLine(*v2,*v0,coll_color,1);
+		}
+#endif
+
+		float distance;
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+		if (sRayTriangleCollision(&testLine.m_start, &lineDir, &v0, &v1, &v2, &distance))
+#else
+		if (sRayTriangleCollision(&testLine.m_start, &lineDir, v0, v1, v2, &distance))
+#endif
+		{
+			SCollSurface collisionSurface;
+
+			/* We've got one */
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+			collisionSurface.point = v0;
+			collisionSurface.index = *p_face_indexes;
+
+			// Find normal
+			Mth::Vector vTmp1(v1 - v0);
+			Mth::Vector vTmp2(v2 - v0);
+#else
+			collisionSurface.point = (*v0);
+			collisionSurface.index = *p_face_indexes;
+
+			// Find normal
+			Mth::Vector vTmp1(*v1 - *v0);
+			Mth::Vector vTmp2(*v2 - *v0);
+#endif // FIXED_POINT_VERTICES
+			collisionSurface.normal = Mth::CrossProduct(vTmp1, vTmp2);
+			collisionSurface.normal.Normalize();
+
+			if (s_found_collision(&testLine, this, &collisionSurface, distance, p_data))
+			{
+				best_collision = true;
+			}
+		}
+	}
+
+#ifdef BATCH_TRI_COLLISION
+	if (do_batch)
+	{
+		CBatchTriCollMan::sStartNewTriCollisions();
+	}
+#endif // BATCH_TRI_COLLISION
+
+	return best_collision;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollStaticTri::CollisionWithRectangle(const Mth::Rectangle& testRect, const Mth::CBBox& testRectBBox, S2DCollData *p_coll_data)
+{
+	Dbg_Assert(p_coll_data);
+	
+	uint num_faces;
+	FaceIndex* p_face_indexes = mp_coll_tri_data->FindIntersectingFaces(testRectBBox, num_faces);
+	Dbg_Assert(p_face_indexes);
+	
+	// loop over the relavent faces of the sector
+	for (uint fidx = 0; fidx < num_faces; fidx++, p_face_indexes++)
+	{
+		uint32 face_flags = GetFaceFlags(fidx);
+		if ((face_flags & p_coll_data->ignore_1) || (~face_flags & p_coll_data->ignore_0)) continue;
+
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+		Mth::Vector v0, v1, v2;
+		if (mp_coll_tri_data->m_use_face_small)
+		{
+			CCollObjTriData::SFaceSmall *face = &(mp_coll_tri_data->mp_face_small[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		} else {
+			CCollObjTriData::SFace *face = &(mp_coll_tri_data->mp_faces[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		}
+#else
+		Mth::Vector v0, v1, v2;
+		if (mp_coll_tri_data->m_use_face_small)
+		{
+			CCollObjTriData::SFaceSmall *face = &(mp_coll_tri_data->mp_face_small[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		} else {
+			CCollObjTriData::SFace *face = &(mp_coll_tri_data->mp_faces[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		}
+		
+#endif // FIXED_POINT_VERTICES
+
+		Mth::Vector p_coll_point[2];
+		ETriangleEdgeType p_triangle_edge[2];
+
+		if (sRectangleTriangleCollision(&testRect, &v0, &v1, &v2, p_coll_point, p_triangle_edge))
+		{
+			if (p_coll_data->num_surfaces == MAX_NUM_2D_COLLISIONS_REPORTED)
+			{
+				Dbg_Message("Number of faces colliding with rectangle exceeds static maximum; dropping additional collisions");
+				return true;
+			}
+			
+			// add the face to the collided surface list
+			S2DCollSurface& surface = p_coll_data->p_surfaces[p_coll_data->num_surfaces];
+			surface.normal = Mth::CrossProduct(v1 - v0, v2 - v0).Normalize();
+
+			// ignore triangles with zero area
+			if (surface.normal.LengthSqr() <= 0.0f)	continue;
+			
+			surface.trigger = (face_flags & mFD_TRIGGER);
+			surface.node_name = this->GetChecksum();
+			
+			surface.ends[0].point = p_coll_point[0];
+			surface.ends[1].point = p_coll_point[1];
+
+			for (int end_idx = 2; end_idx--; )
+			{
+				surface.ends[end_idx].tangent_exists = p_triangle_edge[end_idx] != NO_TRIANGLE_EDGE;
+				switch (p_triangle_edge[end_idx])
+				{
+					case TRIANGLE_EDGE_V0_V1:
+						surface.ends[end_idx].tangent = (v1 - v0).Normalize();
+						break;
+					case TRIANGLE_EDGE_V1_V2:
+						surface.ends[end_idx].tangent = (v2 - v1).Normalize();
+						break;
+					case TRIANGLE_EDGE_V2_V0:
+						surface.ends[end_idx].tangent = (v0 - v2).Normalize();
+						break;
+					case NO_TRIANGLE_EDGE:
+						break;
+				}
+			}
+			
+			p_coll_data->num_surfaces++;
+		}
+	}
+	
+	return p_coll_data->num_surfaces > 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollStaticTri::DebugRender(uint32 ignore_1, uint32 ignore_0)
+{
+	Dbg_Assert(mp_coll_tri_data);
+	mp_coll_tri_data->DebugRender(ignore_1, ignore_0);
+}
+
+void	CCollStaticTri::ProcessOcclusion()
+{
+	Dbg_Assert(mp_coll_tri_data);
+	mp_coll_tri_data->ProcessOcclusion();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollStaticTri::CheckForHoles()
+{
+	Dbg_Assert(mp_coll_tri_data);
+#ifdef	__DEBUG_CODE__
+	mp_coll_tri_data->CheckForHoles();
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovTri::CCollMovTri()
+{
+	// This instance can't be used until someone sets the geometry data
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovTri::CCollMovTri(CCollObjTriData *p_coll_data) :
+	CCollMovable()
+{
+	SetGeometry(p_coll_data);
+	Dbg_Assert(mp_coll_tri_data);
+
+	//m_movement_changed = false;
+	m_movement_changed = true;		// to update m_world_bbox
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollMovTri::~CCollMovTri()
+{
+	// Don't delete the Tri Data
+}
+
+// Temp functions
+uint32 CCollMovTri::GetFaceFlags(int face_idx) const
+{
+	Dbg_Assert(mp_coll_tri_data);
+	return mp_coll_tri_data->GetFaceFlags(face_idx);
+}
+
+uint16 CCollMovTri::GetFaceTerrainType(int face_idx) const
+{
+	Dbg_Assert(mp_coll_tri_data);
+	return mp_coll_tri_data->GetFaceTerrainType(face_idx);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CCollMovTri::SetGeometry(CCollObjTriData *p_geom_data)
+{
+	CCollObj::SetGeometry(p_geom_data);
+	Dbg_Assert(mp_coll_tri_data);
+	
+	// Re-calculate radius
+	float new_length;
+	m_max_radius = 0.0f;
+	for (int i = 0; i < mp_coll_tri_data->m_num_verts; i++)
+	{
+		new_length = mp_coll_tri_data->GetRawVertexPos(i).Length();
+		if (new_length > m_max_radius)
+		{
+			m_max_radius = new_length;
+		}
+	}
+
+	m_movement_changed = true;		// to update m_world_bbox
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObj * 	CCollMovTri::Clone(bool instance)
+{
+	Dbg_MsgAssert(!instance, ("CCollMovTri::Clone() with instances not implemented yet"));
+
+	CCollMovTri *m_new_coll = new CCollMovTri(*this);
+
+	m_new_coll->m_Flags |=  mSD_CLONE;
+
+	m_new_coll->mp_coll_tri_data = mp_coll_tri_data->Clone(instance);
+
+	return m_new_coll;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollMovTri::WithinBBox(const Mth::CBBox & testBBox)
+{
+	Dbg_Assert(mp_coll_tri_data);
+
+	if (m_movement_changed)
+	{
+		update_world_bbox();
+		m_movement_changed = false;
+	}
+
+	return testBBox.Intersect(m_world_bbox);
+	//return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollMovTri::CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox *p_bbox)
+{
+	Dbg_Assert(p_data);
+	Dbg_Assert(mp_coll_tri_data);
+
+	bool best_collision = false;
+
+	// Find line in local space
+	Mth::Line local_line;
+	convert_line_to_local(testLine, local_line);
+	Mth::Vector local_line_dir(local_line.m_end - local_line.m_start);
+
+	// Generate a bounding box for the line
+	// so it can be tested against the BSP tree
+	Mth::CBBox line_bbox(local_line.m_start);
+	line_bbox.AddPoint(local_line.m_end);
+
+	uint num_faces;
+	FaceIndex *p_face_indexes;
+	p_face_indexes = mp_coll_tri_data->FindIntersectingFaces(line_bbox, num_faces);
+
+	for (uint fidx = 0; fidx < num_faces; fidx++, p_face_indexes++)
+	{
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+		Mth::Vector v0, v1, v2;
+		if (mp_coll_tri_data->m_use_face_small)
+		{
+			CCollObjTriData::SFaceSmall *face = &(mp_coll_tri_data->mp_face_small[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		} else {
+			CCollObjTriData::SFace *face = &(mp_coll_tri_data->mp_faces[*p_face_indexes]);
+
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[0], v0);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[1], v1);
+			mp_coll_tri_data->GetRawVertexPos(face->m_vertex_index[2], v2);
+		}
+#else
+		Mth::Vector *v0, *v1, *v2;
+		if (mp_coll_tri_data->m_use_face_small)
+		{
+			CCollObjTriData::SFaceSmall *face = &(mp_coll_tri_data->mp_face_small[*p_face_indexes]);
+
+			v0 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+			v1 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+			v2 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+		} else {
+			CCollObjTriData::SFace *face = &(mp_coll_tri_data->mp_faces[*p_face_indexes]);
+
+			v0 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[0]];
+			v1 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[1]];
+			v2 = &mp_coll_tri_data->mp_vert_pos[face->m_vertex_index[2]];
+		}
+#endif // FIXED_POINT_VERTICES
+
+		float distance;
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+		if (sRayTriangleCollision(&local_line.m_start, &local_line_dir, &v0, &v1, &v2, &distance))
+#else
+		if (sRayTriangleCollision(&local_line.m_start, &local_line_dir, v0, v1, v2, &distance))
+#endif
+		{
+			SCollSurface collisionSurface;
+
+			/* We've got one */
+#if defined(FIXED_POINT_VERTICES) || defined(__PLAT_NGC__)
+			collisionSurface.point = v0;
+			collisionSurface.index = *p_face_indexes;
+
+			// Find normal
+			Mth::Vector vTmp1(v1 - v0);
+			Mth::Vector vTmp2(v2 - v0);
+#else
+			collisionSurface.point = (*v0);
+			collisionSurface.index = *p_face_indexes;
+
+			// Find normal
+			Mth::Vector vTmp1(*v1 - *v0);
+			Mth::Vector vTmp2(*v2 - *v0);
+#endif // FIXED_POINT_VERTICES
+			collisionSurface.normal = Mth::CrossProduct(vTmp1, vTmp2);
+			collisionSurface.normal.Normalize();
+
+			// Convert to world coordinates
+			collisionSurface.point.Rotate(m_orient);
+			collisionSurface.point += m_world_pos;
+			collisionSurface.normal.Rotate(m_orient);
+
+			if (s_found_collision(&testLine, this, &collisionSurface, distance, p_data))
+			{
+				best_collision = true;
+			}
+		}
+	}
+
+	return best_collision;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCollMovTri::CollisionWithRectangle(const Mth::Rectangle& rect, const Mth::CBBox& rect_bbox, S2DCollData* p_data)
+{
+	Dbg_MsgAssert(false, ("CCollMovTri::CollisionWithRecangle is not implemented"));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollMovTri::DebugRender(uint32 ignore_1, uint32 ignore_0)
+{
+	// Debug box
+	Mth::Matrix total_mat = m_orient;
+	total_mat[W] = m_world_pos;
+	mp_coll_tri_data->DebugRender(total_mat, ignore_1, ignore_0);
+}
+
+} // namespace Nx
+
diff --git a/Code/Gel/Collision/Collision.h b/Code/Gel/Collision/Collision.h
new file mode 100644
index 0000000..17c3800
--- /dev/null
+++ b/Code/Gel/Collision/Collision.h
@@ -0,0 +1,729 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx														**
+**																			**
+**	File name:		Collision.h												**
+**																			**
+**	Created: 		02/15/2002	-	grj										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__GEL_COLLISION_H
+#define	__GEL_COLLISION_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+	class CCompositeObject;
+}
+
+namespace Nx
+{
+	
+const int MAX_NUM_2D_COLLISIONS_REPORTED = 32;
+
+
+// Forward declarations
+class CCollObj;
+class CCollMulti;
+class CCollStatic;
+struct CollData;
+class CBatchTriCollMan;
+class CCollCache;
+
+struct SCollSurface
+{
+    Mth::Vector	normal;			/**< Triangle normal */
+    Mth::Vector	point;			/**< First triangle vertex */
+    int			index;			/**< Index of surface in object (if applicable) */
+    //Mth::Vector * vertices[3];	/**< Pointers to three triangle vertices */
+};
+
+// structure used to record the collision of a single triangle with a 2D object; a collision test for a 2D object will generate a set of these
+struct S2DCollSurface
+{
+	struct S2DCollEndpoint
+	{
+		Mth::Vector point;			// endpoint position
+		Mth::Vector tangent;		// normalized tangent vector to the endpoint
+		char tangent_exists;		// if a tangent of the endpoint is defined
+	};
+	
+	S2DCollEndpoint ends[2];		// endpoints of the line cut through the triangle by the collision
+	
+	Mth::Vector	normal;				// normal of the collided triangle
+	
+	uint32 node_name;
+	char trigger;
+};
+
+struct CollData
+{
+	SCollSurface	surface;
+	float	dist;   
+	uint16	ignore_1;			// bitmask, ignore poly if flags matching tis mask are 1
+	uint16	ignore_0;			// ditto, but for flags that are 0
+	uint16	flags;
+	ETerrainType terrain;
+	CCollObj *p_coll_object;	// the sector (object) with which we collided
+	uint32	node_name;			// checsum of name of node associated with this object
+	uint32	script;				// set to the script checksum of the object (or NULL if none)
+	void	(*callback)( CollData* p_col_data);	   // a callback called every frame
+	void 	*p_callback_data;		// 	pointer to some data the callback can use
+
+	Obj::CCompositeObject *mp_callback_object;	// set only during callbacks
+	
+	char 	coll_found;
+	char	trigger;			// true if it was a trigger	(if mFD_TRIGGER flag is set on the colliding face)
+};
+
+// collision data generated by a collision test for a 2D object
+struct S2DCollData
+{
+	S2DCollSurface	p_surfaces[MAX_NUM_2D_COLLISIONS_REPORTED];	// surfaces collided with
+	int 			num_surfaces;								// number of surfaces collided with
+	uint16			ignore_1;
+	uint16			ignore_0;
+};
+
+// enum used internally by the rectangle collision code to keep track of which triangle edge a collision line endpoint is at
+enum ETriangleEdgeType
+{
+	NO_TRIANGLE_EDGE, TRIANGLE_EDGE_V0_V1, TRIANGLE_EDGE_V1_V2, TRIANGLE_EDGE_V2_V0
+};
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+////////////////////////////////////////////////////////////////
+// The base class for collision
+class CCollObj
+{
+public:
+						CCollObj();
+	virtual				~CCollObj();
+
+	uint32				GetChecksum() const;
+	void				SetChecksum(uint32 checksum);	// For cloning
+	uint16				GetObjectFlags() const;
+	void				SetObjectFlags(uint16 flags);
+	void				ClearObjectFlags(uint16 flags);
+
+	//
+	virtual bool		IsTriangleCollision() const { return false; }	// by default, no triangle collision
+
+	//
+	virtual uint32		GetFaceFlags(int face_idx) const = 0;
+	virtual uint16		GetFaceTerrainType(int face_idx) const = 0;
+
+	//
+	virtual void		SetGeometry(CCollObjTriData *p_geom_data);
+	CCollObjTriData *	GetGeometry() const;
+
+	//
+	virtual void		SetWorldPosition(const Mth::Vector & pos) = 0;
+	virtual void		SetOrientation(const Mth::Matrix & orient) = 0;
+
+	// The Get functions are virtual in case Static and Movable differ at a later point
+	virtual const Mth::Vector &	GetWorldPosition() const = 0;
+	virtual const Mth::Matrix &	GetOrientation() const = 0;
+	virtual Mth::Vector GetVertexPos(int vert_idx) const = 0;
+
+	virtual Obj::CCompositeObject *GetMovingObject() const;
+	virtual void				SetMovingObject(Obj::CCompositeObject *p_movable_object);
+
+	// Clone
+	virtual CCollObj *	Clone(bool instance = false) = 0;
+
+	// The virtual collision functions
+	virtual bool		WithinBBox(const Mth::CBBox & testBBox) = 0;		// VERY quick test to see if we're in bbox range.
+																			// Derived class may always return TRUE if it isn't
+																			// worth checking.
+	virtual bool		CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data,  Mth::CBBox *p_bbox) = 0;
+	virtual bool		CollisionWithRectangle(const Mth::Rectangle& testRect, const Mth::CBBox& testRectBBox, S2DCollData *p_coll_data) = 0;
+
+	// Wireframe drawing
+	virtual void		DebugRender(uint32 ignore_1, uint32 ignore_0) = 0;
+
+	// Conversion functions
+	static CollType		sConvertTypeChecksum(uint32 checksum);
+
+	// Collision creation
+	static CCollObj *	sCreateStaticCollision(CollType type, Nx::CCollObjTriData *p_coll_obj_data, int num_coll_objects);
+	static CCollObj *	sCreateMovableCollision(CollType type, Nx::CCollObjTriData *p_coll_obj_data, int num_coll_objects,
+												Obj::CCompositeObject *p_object);
+
+	// Does collision against all the static collision in the SuperSectors
+	static bool			sFindNearestStaticCollision(Mth::Line &is, CollData *p_data, void *p_callback = NULL, CCollCache *p_cache = NULL);
+	static bool			sFindFarStaticCollision(Mth::Line &is, CollData *p_data, void *p_callback = NULL, CCollCache *p_cache = NULL);
+
+	// Does collision against all the movable collision
+	static Obj::CCompositeObject *	sFindNearestMovableCollision(Mth::Line &is, CollData *p_data, void *p_callback = NULL, CCollCache *p_cache = NULL);
+	static Obj::CCompositeObject *	sFindFarMovableCollision(Mth::Line &is, CollData *p_data, void *p_callback = NULL, CCollCache *p_cache = NULL);
+
+	// Does rect collision against all the static collision
+	static bool			sFindRectangleStaticCollision(Mth::Rectangle& rect, S2DCollData* p_coll_data, CCollCache* p_cache = NULL);
+	
+	// Triangle collision function
+	static bool			sRayTriangleCollision(const Mth::Vector *rayStart, const Mth::Vector *rayDir,
+											  Mth::Vector *v0, Mth::Vector *v1, Mth::Vector *v2, float *distance);
+	
+	static bool			sRectangleTriangleCollision(const Mth::Rectangle *rect,
+													const Mth::Vector *v0, const Mth::Vector *v1, const Mth::Vector *v2,
+													Mth::Vector p_collision_endpoints[2], ETriangleEdgeType p_collision_triangle_edges[2] );
+	
+	// 2D line-line collision function
+	static bool			s2DLineLineCollision ( float p_start_X, float p_start_Y, float p_delta_X, float p_delta_Y,
+											   float q_start_X, float q_start_Y, float q_delta_X, float q_delta_Y, float *s );
+	
+protected:
+	virtual Mth::CBBox *get_bbox();											// Gets the quick bbox
+
+	// Called on every collision found.  Returns true if this was the best collision so far.
+	static bool			s_found_collision(const Mth::Line *p_is, CCollObj *p_coll_object, SCollSurface *p_collSurface,
+										  float distance, CollData *p_collData);
+
+	uint32				m_checksum;
+	uint16				m_Flags;
+
+	// Triangle data, similar to CCollSector's
+	CCollObjTriData		*mp_coll_tri_data;
+
+	static const float	sLINE_BOX_EXTENT;				 // Extent of box around collision line
+
+	// Friends
+	friend CCollMulti;
+	friend CBatchTriCollMan;
+	friend CCollCache;
+};
+
+////////////////////////////////////////////////////////////////
+// Collision List
+class CCollMulti : public CCollObj
+{
+public:
+						CCollMulti();
+	virtual				~CCollMulti();
+
+	//
+	virtual bool		IsTriangleCollision() const;
+
+	//
+	virtual void		SetWorldPosition(const Mth::Vector & pos);
+	virtual void		SetOrientation(const Mth::Matrix & orient);
+	// The following Get functions must be defined, but should never be called
+	virtual void		SetGeometry(CCollObjTriData *p_geom_data);
+	virtual const Mth::Vector &	GetWorldPosition() const;
+	virtual const Mth::Matrix &	GetOrientation() const;
+	virtual uint32		GetFaceFlags(int face_idx) const;
+	virtual uint16		GetFaceTerrainType(int face_idx) const;
+	virtual Mth::Vector GetVertexPos(int vert_idx) const;
+
+	virtual Obj::CCompositeObject *GetMovingObject() const;
+	virtual void		SetMovingObject(Obj::CCompositeObject *p_movable_object);
+
+	// Add to collision list
+	void				AddCollision(CCollObj *p_collision);
+
+	// Clone
+	virtual CCollObj *	Clone(bool instance = false);
+
+	// The virtual collision functions
+	virtual bool		WithinBBox(const Mth::CBBox & testBBox);			// VERY quick test to see if we're in bbox range.
+																			// Derived class may always return TRUE if it isn't
+																			// worth checking.
+	virtual bool		CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox *p_bbox);
+	virtual bool		CollisionWithRectangle(const Mth::Rectangle& testRect, const Mth::CBBox& testRectBBox, S2DCollData *p_coll_data);
+
+	// Wireframe drawing
+	virtual void		DebugRender(uint32 ignore_1, uint32 ignore_0);
+
+protected:
+	virtual Mth::CBBox *get_bbox();											// Gets the quick bbox
+
+private:
+	void				update_world_bbox();
+
+	// 
+	Lst::Head *mp_collision_list;
+	Mth::CBBox			m_world_bbox;
+	bool				m_movement_changed;				// Set to true every time SetWorldPosition() is called
+	bool				m_world_bbox_valid;
+
+	// Pointer to moving object
+	Obj::CCompositeObject	*mp_movable_object;
+};
+
+////////////////////////////////////////////////////////////////
+// Static object collision
+class CCollStatic : public CCollObj
+{
+public:
+	virtual				~CCollStatic();
+
+	//
+	virtual void		SetWorldPosition(const Mth::Vector & pos);
+	virtual void		SetOrientation(const Mth::Matrix & orient);
+	virtual const Mth::Vector &	GetWorldPosition() const;
+	virtual const Mth::Matrix &	GetOrientation() const;
+	virtual Mth::Vector GetVertexPos(int vert_idx) const;
+
+	// SuperSector ID access
+	uint8				GetSuperSectorID() const;
+	void				SetSuperSectorID(uint8);
+
+	virtual void		RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y) = 0;
+	virtual void		Scale(const Mth::Vector & world_origin, const Mth::Vector& scale) = 0;
+
+
+protected:
+						CCollStatic();	// Only the derived classes should call this
+
+	// The position and orientation are already factored into the collision
+	// data, unlike the Movable counterpart.
+//	Mth::Vector			m_world_pos;
+//	Mth::Matrix			m_orient;
+	static	Mth::Vector	sWorldPos;		// just a dummy for now....
+	static	Mth::Matrix	sOrient;		// just a dummy for now....
+
+	//
+	uint8				m_op_id;		// SuperSector ID
+};
+
+////////////////////////////////////////////////////////////////
+// Movable object collision
+class CCollMovable : public CCollObj
+{
+public:
+	virtual				~CCollMovable();
+
+	//
+	virtual void		SetWorldPosition(const Mth::Vector & pos);
+	virtual void		SetOrientation(const Mth::Matrix & orient);
+	virtual const Mth::Vector &	GetWorldPosition() const;
+	virtual const Mth::Matrix &	GetOrientation() const;
+	virtual Mth::Vector GetVertexPos(int vert_idx) const;
+
+	virtual Obj::CCompositeObject *GetMovingObject() const;
+	virtual void				SetMovingObject(Obj::CCompositeObject *p_movable_object);
+
+protected:
+						CCollMovable();	// Only the derived classes should call this
+
+	void				convert_line_to_local(const Mth::Line &world_line, Mth::Line &local_line);
+
+	// These will be set by the CMovableObject
+	Mth::Vector			m_world_pos;
+	Mth::Matrix			m_orient;
+	Mth::Matrix			m_orient_transpose;
+
+	// Pointer to moving object
+	Obj::CCompositeObject	*mp_movable_object;
+};
+
+////////////////////////////////////////////////////////////////
+// Movable collision using axis-aligned bounding box data
+class CCollMovBBox : public CCollMovable
+{
+public:
+						CCollMovBBox();
+						CCollMovBBox(CCollObjTriData *p_coll_data);
+	virtual				~CCollMovBBox();
+
+	virtual void		SetGeometry(CCollObjTriData *p_geom_data);	// Also sets the bounding box based on it
+	void				SetBoundingBox(const Mth::CBBox & bbox);	// Sets bounding box (will go away)
+
+	virtual uint32		GetFaceFlags(int face_idx) const;
+	virtual uint16		GetFaceTerrainType(int face_idx) const;
+
+	// Clone
+	virtual CCollObj *	Clone(bool instance = false);
+
+	// The virtual collision functions
+	virtual bool		WithinBBox(const Mth::CBBox & testBBox); 			// Will always return TRUE because it isn't
+																			// worth checking.
+	virtual bool		CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox *p_bbox);
+	virtual bool		CollisionWithRectangle(const Mth::Rectangle& testRect, const Mth::CBBox& testRectBBox, S2DCollData *p_coll_data);
+
+	// Wireframe drawing
+	virtual void		DebugRender(uint32 ignore_1, uint32 ignore_0);
+
+private:
+	Mth::CBBox			m_bbox;
+
+	// Face data (bbox is considered one face)
+	uint32				m_flags;
+	uint16				m_terrain_type;			// terrain type
+	Image::RGBA			m_rgba;					// just one color
+};
+
+////////////////////////////////////////////////////////////////
+// Movable collision using triangles
+class CCollStaticTri : public CCollStatic
+{
+public:
+						CCollStaticTri();
+						CCollStaticTri(CCollObjTriData *p_coll_data);
+	virtual				~CCollStaticTri();
+
+	//
+	virtual bool		IsTriangleCollision() const { return true; }
+
+	//virtual void		SetGeometry(CCollObjTriData *p_geom_data);
+	virtual void		SetWorldPosition(const Mth::Vector & pos);
+	virtual void		SetOrientation(const Mth::Matrix & orient);
+
+	virtual uint32		GetFaceFlags(int face_idx) const;
+	virtual uint16		GetFaceTerrainType(int face_idx) const;
+
+	// Clone
+	virtual CCollObj *	Clone(bool instance = false);
+
+	virtual void		RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y);
+	virtual void		Scale(const Mth::Vector & world_origin, const Mth::Vector& scale);
+
+	// The virtual collision functions
+	virtual bool		WithinBBox(const Mth::CBBox & testBBox);			// VERY quick test to see if we're in bbox range.
+	virtual bool		CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox *p_bbox);
+	virtual bool		CollisionWithRectangle(const Mth::Rectangle& testRect, const Mth::CBBox& testRectBBox, S2DCollData *p_coll_data);
+
+	void				ProcessOcclusion();			  
+			  
+	// Wireframe drawing
+	virtual void		DebugRender(uint32 ignore_1, uint32 ignore_0);
+	void				CheckForHoles();
+	
+
+private:
+};
+
+////////////////////////////////////////////////////////////////
+// Movable collision using triangles
+class CCollMovTri : public CCollMovable
+{
+public:
+						CCollMovTri();
+						CCollMovTri(CCollObjTriData *p_coll_data);
+	virtual				~CCollMovTri();
+
+	//
+	virtual bool		IsTriangleCollision() const { return true; }
+
+	virtual void		SetGeometry(CCollObjTriData *p_geom_data);
+	virtual void		SetWorldPosition(const Mth::Vector & pos);
+	virtual void		SetOrientation(const Mth::Matrix & orient);
+
+	virtual uint32		GetFaceFlags(int face_idx) const;
+	virtual uint16		GetFaceTerrainType(int face_idx) const;
+
+	// Clone
+	virtual CCollObj *	Clone(bool instance = false);
+
+	// The virtual collision functions
+	virtual bool		WithinBBox(const Mth::CBBox & testBBox);			// VERY quick test to see if we're in bbox range.
+	virtual bool		CollisionWithLine(const Mth::Line & testLine, const Mth::Vector & lineDir, CollData *p_data, Mth::CBBox *p_bbox);
+	virtual bool		CollisionWithRectangle(const Mth::Rectangle& testRect, const Mth::CBBox& testRectBBox, S2DCollData* p_coll_data);
+
+	// Wireframe drawing
+	virtual void		DebugRender(uint32 ignore_1, uint32 ignore_0);
+
+protected:
+	virtual Mth::CBBox *get_bbox();											// Gets the quick bbox
+
+private:
+	void				update_world_bbox();
+
+	float				m_max_radius;					// maximum radius that encompases geometry around the origin
+	bool				m_movement_changed;				// Set to true every time SetWorldPosition() is called
+	Mth::CBBox			m_world_bbox;					// approx bbox in world space that is guaranteed to encompass geometry
+};
+
+
+///////////////////////////////////////////////////////////////////////
+//
+// Inline functions
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32			CCollObj::GetChecksum() const
+{
+	return m_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint16			CCollObj::GetObjectFlags() const
+{
+	return m_Flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void				CCollObj::SetGeometry(CCollObjTriData *p_geom_data)
+{
+	mp_coll_tri_data = p_geom_data;
+	// This only works for non-instanced collision
+	if (p_geom_data) m_checksum = p_geom_data->GetChecksum();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline CCollObjTriData *CCollObj::GetGeometry() const
+{
+	return mp_coll_tri_data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void				CCollObj::SetObjectFlags(uint16 flags)
+{
+	m_Flags |= flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void				CCollObj::ClearObjectFlags(uint16 flags)
+{
+	m_Flags &= ~flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Mth::CBBox *		CCollObj::get_bbox()
+{
+	return NULL;		// default is none
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void				CCollStatic::SetWorldPosition(const Mth::Vector & pos)
+{
+//	m_world_pos = pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &	CCollStatic::GetWorldPosition() const
+{
+//	return m_world_pos;
+	return sWorldPos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void				CCollStatic::SetOrientation(const Mth::Matrix & orient)
+{
+//	m_orient[X] = orient[X];	// Just the 3x3
+//	m_orient[Y] = orient[Y];
+//	m_orient[Z] = orient[Z];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Matrix &	CCollStatic::GetOrientation() const
+{
+//	return m_orient;
+	return sOrient;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint8				CCollStatic::GetSuperSectorID() const
+{
+	return m_op_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CCollStatic::SetSuperSectorID(uint8 id)
+{
+	m_op_id = id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void				CCollMovable::SetWorldPosition(const Mth::Vector & pos)
+{
+	m_world_pos = pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &	CCollMovable::GetWorldPosition() const
+{
+	return m_world_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void				CCollMovable::SetOrientation(const Mth::Matrix & orient)
+{
+	m_orient[X] = orient[X];	// Just the 3x3
+	m_orient[Y] = orient[Y];
+	m_orient[Z] = orient[Z];
+
+	m_orient_transpose.Transpose(m_orient);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Matrix &	CCollMovable::GetOrientation() const
+{
+	return m_orient;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CCollMovBBox::SetBoundingBox(const Mth::CBBox & bbox)
+{
+	m_bbox = bbox;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CCollMovBBox::WithinBBox(const Mth::CBBox & testBBox)
+{
+	// Will always return TRUE because it isn't worth checking
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CCollMovTri::SetWorldPosition(const Mth::Vector & pos)
+{
+	CCollMovable::SetWorldPosition(pos);
+	m_movement_changed = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CCollMovTri::SetOrientation(const Mth::Matrix & orient)
+{
+	CCollMovable::SetOrientation(orient);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CCollMovTri::update_world_bbox()
+{
+	m_world_bbox.SetMin(Mth::Vector(-m_max_radius + m_world_pos[X], -m_max_radius + m_world_pos[Y], -m_max_radius + m_world_pos[Z]));
+	m_world_bbox.SetMax(Mth::Vector( m_max_radius + m_world_pos[X],  m_max_radius + m_world_pos[Y],  m_max_radius + m_world_pos[Z]));
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Mth::CBBox *			CCollMovTri::get_bbox()
+{
+	if (m_movement_changed)
+	{
+		update_world_bbox();
+		m_movement_changed = false;
+	}
+
+	return &m_world_bbox;
+}
+
+
+} // namespace Nx
+
+#endif  //	__GEL_COLLISION_H
diff --git a/Code/Gel/Collision/MovCollMan.cpp b/Code/Gel/Collision/MovCollMan.cpp
new file mode 100644
index 0000000..5445790
--- /dev/null
+++ b/Code/Gel/Collision/MovCollMan.cpp
@@ -0,0 +1,78 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx								 						**
+**																			**
+**	File name:		gel\collision\movcollman.cpp   							**
+**																			**
+**	Created by:		04/08/02	-	grj										**
+**																			**
+**	Description:															**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Nx
+{
+
+Lst::Head CMovableCollMan::s_collision_list;
+//CCollObj * CMovableCollMan::s_collision_array[MAX_COLLISION_OBJECTS];
+//int CMovableCollMan::s_array_size = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CMovableCollMan::sAddCollision(CCollObj *p_collision, Obj::CCompositeObject *p_object)
+{
+	Lst::Node *node = new Lst::Node(p_collision);
+	s_collision_list.AddToTail(node);
+	//s_collision_array[s_array_size++] = p_collision;
+	//Dbg_Assert(s_array_size <= MAX_COLLISION_OBJECTS);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CMovableCollMan::sRemoveCollision(CCollObj *p_collision)
+{
+	Lst::Node *node_coll, *node_next;
+
+	for(node_coll = s_collision_list.GetNext(); node_coll; node_coll = node_next)
+	{
+		node_next = node_coll->GetNext();
+
+		if (node_coll->GetData() == p_collision)
+		{
+			delete node_coll;
+		}
+	}
+}
+
+
+} // namespace Nx
+
diff --git a/Code/Gel/Collision/MovCollMan.h b/Code/Gel/Collision/MovCollMan.h
new file mode 100644
index 0000000..f1c421a
--- /dev/null
+++ b/Code/Gel/Collision/MovCollMan.h
@@ -0,0 +1,93 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx														**
+**																			**
+**	File name:		MovCollMan.h											**
+**																			**
+**	Created: 		04/08/2002	-	grj										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__GEL_MOVCOLLMAN_H
+#define	__GEL_MOVCOLLMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Nx
+{
+
+class CCollObj;
+class CCollObjTriData;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CMovableCollMan
+{
+public:
+	//static void				sInit();
+	//static void				sCleanup();
+
+	static void				sAddCollision(CCollObj *p_collsion, Obj::CCompositeObject *p_object);
+	static void				sRemoveCollision(CCollObj *p_collsion);
+
+	static Lst::Head *sGetCollisionList();
+	static CCollObj **			sGetCollisionArray();
+
+protected:
+	enum
+	{
+		MAX_COLLISION_OBJECTS = 100,
+	};
+
+	static Lst::Head	s_collision_list;
+	static CCollObj *			s_collision_array[MAX_COLLISION_OBJECTS];		// If the speed is needed
+	static int					s_array_size;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Lst::Head *CMovableCollMan::sGetCollisionList()
+{
+	return &s_collision_list;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline CCollObj **			CMovableCollMan::sGetCollisionArray()
+{
+	return s_collision_array;
+}
+
+} // namespace Nx
+
+#endif	//	__GEL_MOVCOLLMAN_H
diff --git a/Code/Gel/Components/BouncyComponent.h b/Code/Gel/Components/BouncyComponent.h
new file mode 100644
index 0000000..181a79e
--- /dev/null
+++ b/Code/Gel/Components/BouncyComponent.h
@@ -0,0 +1,116 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       BouncyComponent.h
+//* OWNER:          Mick West
+//* CREATION DATE:  10/22/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_BOUNCYCOMPONENT_H__
+#define __COMPONENTS_BOUNCYCOMPONENT_H__
+
+#include 
+#include 
+#include 
+#include 
+
+
+#define		CRC_BOUNCY CRCD(0x69c257e9,"Bouncy")
+#define		GetBouncyComponent() ((Obj::CBouncyComponent*)GetComponent(CRC_BOUNCY))
+
+namespace Mth
+{
+    class SlerpInterpolator;
+};
+
+
+#define BOUNCYOBJ_FLAG_PLAYER_COLLISION_OFF			( 1 << 0 )
+
+
+namespace Obj
+{
+
+
+// These are in the Obj:: namespace, whilse we are sperating the bouncy componet from the bouncy object
+enum
+{
+	BOUNCYOBJ_STATE_INIT,
+	BOUNCYOBJ_STATE_IDLE,
+	BOUNCYOBJ_STATE_BOUNCING,
+};
+
+class CBouncyComponent : public CBaseComponent
+{
+	
+public:
+// Functions common to components - the interface to the outside world								
+								CBouncyComponent();
+    virtual         			~CBouncyComponent();
+    virtual void    			Update();
+    virtual void    			InitFromStructure( Script::CStruct* pParams );
+	static CBaseComponent *		s_create();
+	CBaseComponent::EMemberFunctionResult	CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript );
+	virtual void 				GetDebugInfo( Script::CStruct* p_info );
+
+// private functions - indirectly called from the above functions	
+private:
+	void						bounce( const Mth::Vector &vel );
+	void 						bounce_from_object_vel( const Mth::Vector &vel, const Mth::Vector &pos );
+	void						do_bounce( void );
+	bool 						rotate_to_quat( void );
+	void 						set_up_quats( void );
+
+	void 						land_on_any_face( Mth::Vector &rot );
+	void 						land_on_top_or_bottom( Mth::Vector &rot );
+	void 						land_traffic_cone( Mth::Vector &rot );
+
+	int							m_bouncyobj_flags;
+	Mth::Vector 				m_rotation;
+	int							m_rot_per_axis;
+	int							m_bounce_rot;
+	float						m_min_bounce_vel;
+	// should the object come to rest flat (90 degree oriented?)
+	int							m_rest_orientation_type;
+	// percent of bounce vel maintained ( 1.0 would make it bounce the same height )
+	float						m_bounce_mult;
+	float						m_up_mag; // this will be the upward velocity (varies depending on initial vel)
+	int							m_bounce_count;
+	bool						m_quat_initialized;
+	float						m_quatrot_time_elapsed;
+	float						m_gravity;
+	float						m_bounciness;
+	float						m_min_initial_vel;
+	int							m_random_dir; // on bounce, change direction by this much...
+	Mth::SlerpInterpolator *	mp_bounce_slerp;
+	uint32						m_bounce_sound;
+	uint32						m_hit_sound;
+					
+	float						m_bounce_collision_radius;
+	float						m_skater_collision_radius_squared;
+						
+	bool						m_destroy_when_done;
+
+    // logic states...
+    short 						m_state;
+    short 						m_substate;
+
+	// 
+	uint32						m_collide_script;
+	Script::CStruct*			mp_collide_script_params;	
+	uint32						m_bounce_script;
+	Script::CStruct*			mp_bounce_script_params;	
+	
+	// The following is stuff that might be better off in the composite object, or some general physics component?
+	// or perhaps a "simplephysics" base compoennt
+	
+	Mth::Vector					m_pos;
+	Mth::Vector					m_old_pos;
+	Mth::Vector					m_vel;
+	Mth::Matrix					m_matrix;
+	float						m_time;
+
+
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/CameraComponent.cpp b/Code/Gel/Components/CameraComponent.cpp
new file mode 100644
index 0000000..728997a
--- /dev/null
+++ b/Code/Gel/Components/CameraComponent.cpp
@@ -0,0 +1,239 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CameraComponent.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/21/03
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent* CCameraComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CCameraComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CCameraComponent::CCameraComponent() : CBaseComponent()
+{
+	SetType( CRC_CAMERA );
+
+	// Enabled by default.
+	m_enabled = true;
+
+	// Create and attach a Gfx::Camera.
+	mp_camera = new Gfx::Camera();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CCameraComponent::~CCameraComponent()
+{
+	// Destroy the Gfx::Camera.
+	delete mp_camera;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// cameras must have a very low priority to insure that all objects update before they do (most importantly, the skater)
+	CCompositeObjectManager::Instance()->SetObjectPriority(*GetObject(), -1000);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::Update()
+{
+	if( m_enabled )
+	{
+		// Use the position and orientation of the parent object to position and orient the attached camera.
+		Mth::Vector pos = GetObject()->GetPos();
+		Mth::Matrix mat = GetObject()->GetMatrix();
+
+		// Set the Display pos of the object to the actual pos, so we can attach a model
+		// to the camera
+		GetObject()->SetDisplayMatrix(mat);
+
+		mp_camera->SetPos( pos );
+		mp_camera->SetMatrix( mat );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent::EMemberFunctionResult CCameraComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CCameraComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger("m_never_suspend",m_never_suspend);
+	p_info->AddFloat("m_suspend_distance",m_suspend_distance);
+	*/
+	
+	// We call the base component's GetDebugInfo, so we can add info from the common base component.
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::Enable( bool enable )
+{
+	m_enabled = enable;
+
+	// Go through and enable other attached components?
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector &CCameraComponent::GetPosition( void ) const
+{
+	return GetObject()->GetPos();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::SetPosition( Mth::Vector& pos )
+{
+ 	GetObject()->SetPos( pos );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Mth::Matrix& CCameraComponent::GetMatrix( void )
+{
+	return GetObject()->GetMatrix();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::StoreOldPosition( void )
+{
+	if( mp_camera )
+	{
+		mp_camera->StoreOldPos();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraComponent::SetHFOV( float hfov )
+{
+	if( mp_camera )
+	{
+		mp_camera->SetHFOV( hfov );
+	}
+}
+
+
+
+}
\ No newline at end of file
diff --git a/Code/Gel/Components/CameraComponent.h b/Code/Gel/Components/CameraComponent.h
new file mode 100644
index 0000000..7e40c69
--- /dev/null
+++ b/Code/Gel/Components/CameraComponent.h
@@ -0,0 +1,66 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CameraComponent.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/21/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_CAMERACOMPONENT_H__
+#define __COMPONENTS_CAMERACOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+// Replace this with the CRCD of the component you are adding.
+#define		CRC_CAMERA								CRCD( 0xc4e311fa, "Camera" )
+
+//  Standard accessor macros for getting the component either from within an object, or given an object.
+#define		GetCameraComponent()					((Obj::CCameraComponent*)GetComponent( CRC_CAMERA ))
+#define		GetCameraComponentFromObject( pObj )	((Obj::CCameraComponent*)(pObj)->GetComponent( CRC_CAMERA ))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CCameraComponent : public CBaseComponent
+{
+public:
+    CCameraComponent();
+    virtual ~CCameraComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+	void							SetHFOV( float hfov );
+
+	void							Enable( bool enable );
+	Gfx::Camera*					GetCamera( void )		{ return mp_camera; }
+	const Mth::Vector &				GetPosition( void ) const ;
+	void							SetPosition( Mth::Vector& pos );
+	void							StoreOldPosition( void );
+	Mth::Matrix&					GetMatrix( void );
+
+private:
+
+	bool							m_enabled;
+	Gfx::Camera*					mp_camera;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/CameraLookAroundComponent.cpp b/Code/Gel/Components/CameraLookAroundComponent.cpp
new file mode 100644
index 0000000..d71baaa
--- /dev/null
+++ b/Code/Gel/Components/CameraLookAroundComponent.cpp
@@ -0,0 +1,322 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CameraLookAroundComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/14/3
+//****************************************************************************
+
+#ifdef TESTING_GUNSLINGER
+
+#include 
+
+#else
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+						   
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CCameraLookAroundComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CCameraLookAroundComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCameraLookAroundComponent::CCameraLookAroundComponent() : CBaseComponent()
+{
+	SetType( CRC_CAMERALOOKAROUND );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCameraLookAroundComponent::~CCameraLookAroundComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	mLookaroundTilt					= 0.0f;
+	mLookaroundHeading				= 0.0f;
+	mLookaroundZoom					= 1.0f;
+	
+	mLookaroundLock					= false;
+	mLookaroundOverride				= false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::Finalize (   )
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_input_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::Update()
+{
+	CControlPad * p_control_pad;
+	
+	if (Script::GetInt(CRCD(0x702247a5,"Viewer_controller"), false))
+	{
+		p_control_pad = &mp_input_component->GetControlPad2();
+	}
+	else
+	{
+		p_control_pad = &mp_input_component->GetControlPad();
+	}
+	
+
+	// Handle pressing of "Select" button
+	// 	
+	if (!Script::GetInt(CRCD(0xf3e055e1,"select_shift")))
+	{
+		if (p_control_pad->m_select.GetTriggered())
+		{
+			printf ("Camera Detected Select Pressed\n");
+			p_control_pad->m_select.ClearTrigger();
+			// Depending on which skatercam we are, run one of the UserSelectSelect scripts
+			if (GetObject()->GetID() == CRCD(0x967c138c,"SkaterCam0"))
+			{
+				Script::RunScript(CRCD(0x60871393,"UserSelectSelect"));
+			}
+			else
+			{
+				Script::RunScript(CRCD(0xa1b1146d,"UserSelectSelect2"));
+			}
+		}
+	}
+	
+	if (p_control_pad->m_R3.GetTriggered())
+	{
+		mLookaroundLock = !mLookaroundLock;
+		p_control_pad->m_R3.ClearTrigger();
+	}
+	
+	float frame_length = Tmr::FrameLength();
+	
+	if (!mLookaroundLock && !mLookaroundOverride)
+	{
+		float target = p_control_pad->m_scaled_rightY;
+		target = -1.4f * (target < 0.0f ? (0.7f * target) : (0.5f * target));
+		if (Mth::Abs(target - mLookaroundTilt) > 0.001f)
+		{
+			mLookaroundTilt += (target - mLookaroundTilt) * 3.75f * frame_length;
+		}
+		else
+		{
+			mLookaroundTilt = target;
+		}
+		
+		target = 3.0f * p_control_pad->m_scaled_rightX;
+		if (Mth::Abs(target - mLookaroundHeading) > 0.001f)
+		{
+			mLookaroundHeading += (target - mLookaroundHeading) * 3.75f * frame_length;
+		}
+		else
+		{
+			mLookaroundHeading = target;
+		}
+	}
+	
+	float delta_time = Tmr::FrameLength();
+
+	if (mLookaroundOverride)
+	{
+		if (mLookaroundDeltaTimer > 0.0f)
+		{
+			if (mLookaroundDeltaTimer > delta_time)
+			{
+				mLookaroundHeading		+= mLookaroundHeadingDelta * delta_time;
+				mLookaroundTilt			+= mLookaroundTiltDelta * delta_time;
+				mLookaroundDeltaTimer	-= delta_time;
+			}
+			else
+			{
+				mLookaroundHeading		= mLookaroundHeadingTarget;
+				mLookaroundTilt			= mLookaroundTiltTarget;
+				mLookaroundDeltaTimer	= 0.0f;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CCameraLookAroundComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | SC_SetSkaterCamOverride | 
+        // @parm float | heading |
+        // @parm float | tilt | 
+        // @parm float | time | 
+        // @parm float | zoom | 
+		case 0xb8f23899:	// SC_SetSkaterCamOverride
+		{
+			float heading, tilt, time;
+			float zoom = 1.0f;
+			pParams->GetFloat( CRCD(0xfd4bc03e,"heading"), &heading, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xe3c07609,"tilt"), &tilt, Script::ASSERT );
+			pParams->GetFloat( CRCD(0x906b67ba,"time"), &time, Script::ASSERT );
+			pParams->GetFloat( CRCD(0x48d4868b,"zoom"), &zoom );
+
+			SetLookaround( heading, tilt, time, zoom );
+			
+			break;
+		}
+
+        // @script | SC_ClearSkaterCamOverride |
+		case 0xe3b327c0:	// SC_ClearSkaterCamOverride
+			ClearLookaround();
+			break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CCameraLookAroundComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::SetLookaround( float heading, float tilt, float time, float zoom )
+{
+	mLookaroundOverride				= true;
+
+	mLookaroundHeadingTarget		= heading;
+	mLookaroundTiltTarget			= tilt;
+
+	// Calculate delta - the amount to move in 1 second.
+	if (time > 0.0f)
+	{
+		mLookaroundHeadingDelta		= (heading - mLookaroundHeading) / time;
+		mLookaroundTiltDelta		= (tilt - mLookaroundTilt) / time;
+	}
+
+	mLookaroundDeltaTimer			= time;
+	mLookaroundZoom					= zoom;
+}
+
+#if 0 // old control input checking code
+
+char touched[256 * 256 / 8];
+bool initialized = false;
+
+void MapInput (   )
+{
+	if (!initialized)
+	{
+		for (int n = 255 * 255; n--; )
+		{
+			touched[n / 8] = 0;
+		}
+		initialized = true;
+	}
+	
+	CControlPad& pad = mp_input_component->GetControlPad();
+	
+	int x = static_cast< int >(pad.m_rightX) + 128;
+	int y = static_cast< int >(pad.m_rightY) + 128;
+	
+	// printf("x = %i; y = %i\n", x, y);
+	
+	int index = (x * 256) + y;
+	
+	// printf("index / 8 = %i\n", index / 8);
+	// printf("MAX = %i\n", 256 * 256 / 8);
+	
+	touched[index / 8] |= (1 << (index % 8));
+}
+
+
+void DrawMap (   )
+{
+	Mth::Vector pos = GetObject()->GetPos();
+	Mth::Matrix matrix = GetObject()->GetMatrix();
+	
+	Mth::Vector center = pos - 300.0f * matrix[Z];
+	Mth::Vector up = matrix[Y];
+	Mth::Vector left = -matrix[X];
+	
+	for (int n = 255 * 255; n--; )
+	{
+		if (!(touched[n / 8] & (1 << (n % 8)))) continue;
+		
+		int x = n / 256;
+		int y = n % 256;
+		
+		Mth::Vector point = center + up * (y - 128) + left * (x - 128);
+		
+		Gfx::AddDebugLine(point + up + left, point - up - left, MAKE_RGB(255, 255, 255), 0, 1);
+		Gfx::AddDebugLine(point + up - left, point - up + left, MAKE_RGB(255, 255, 255), 0, 1);
+	}
+}
+
+#endif
+
+}
+
+#endif // TESTING_GUNSLINGER
diff --git a/Code/Gel/Components/CameraLookAroundComponent.h b/Code/Gel/Components/CameraLookAroundComponent.h
new file mode 100644
index 0000000..dad58c7
--- /dev/null
+++ b/Code/Gel/Components/CameraLookAroundComponent.h
@@ -0,0 +1,120 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CameraLookAroundComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  4/14/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_CAMERALOOKAROUNDCOMPONENT_H__
+#define __COMPONENTS_CAMERALOOKAROUNDCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+#define		CRC_CAMERALOOKAROUND CRCD(0x81c7122d, "CameraLookAround")
+
+#define		GetCameraLookAroundComponent() ((Obj::CCameraLookAroundComponent*)GetComponent(CRC_CAMERALOOKAROUND))
+#define		GetCameraLookAroundComponentFromObject(pObj) ((Obj::CCameraLookAroundComponent*)(pObj)->GetComponent(CRC_CAMERALOOKAROUND))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CInputComponent;
+
+class CCameraLookAroundComponent : public CBaseComponent
+{
+	friend class CSkaterCameraComponent;
+	friend class CWalkCameraComponent;
+	friend class CVehicleCameraComponent;
+	
+public:
+    CCameraLookAroundComponent();
+    virtual ~CCameraLookAroundComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							SetLookaroundHeadingExtra( float lookaround_heading_extra );
+	void							ClearLookaround( void );
+	void							SetLookaround( float heading, float tilt, float time, float zoom );
+	
+	bool							IsHeadingTargetZero (   );
+	bool							IsTiltTargetZero (   );
+	bool							IsLookaroundActive ( void );
+	
+private:
+	bool							mLookaroundLock;
+	bool							mLookaroundOverride;	// For when the designer is scripting the lookaround values.
+	float							mLookaroundZoom;		// Allows designers to adjust the zoom when overrideing the lookaround values.
+	float							mLookaroundTilt;
+	float							mLookaroundHeading;
+	float							mLookaroundHeadingStartingPoint;
+	float							mLookaroundTiltTarget;
+	float							mLookaroundHeadingTarget;
+	float							mLookaroundTiltDelta;
+	float							mLookaroundHeadingDelta;
+	float							mLookaroundDeltaTimer;
+
+	CInputComponent*				mp_input_component;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CCameraLookAroundComponent::ClearLookaround( void )
+{
+	mLookaroundOverride				= false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CCameraLookAroundComponent::IsHeadingTargetZero ( void )
+{
+	return mp_input_component->GetControlPad().m_scaled_rightX == 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CCameraLookAroundComponent::IsTiltTargetZero ( void )
+{
+	return mp_input_component->GetControlPad().m_scaled_rightY == 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CCameraLookAroundComponent::IsLookaroundActive ( void )
+{
+	return mLookaroundHeading != 0.0f || !IsHeadingTargetZero()
+		|| mLookaroundTilt != 0.0f || !IsTiltTargetZero();
+}
+
+}
+
+#endif
diff --git a/Code/Gel/Components/CameraUtil.cpp b/Code/Gel/Components/CameraUtil.cpp
new file mode 100644
index 0000000..ff07ff5
--- /dev/null
+++ b/Code/Gel/Components/CameraUtil.cpp
@@ -0,0 +1,165 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CameraUtil.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/10/3
+//****************************************************************************
+
+/*
+ * Utility functions for camera behavior components.
+ */
+
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/*******************************************************************************************************
+logic for getTimeAdjustedSlerp()
+
+If the camera is lagging a distance D behind the target, and the target is moving with velocity V, and the lerp is set to L.
+Then when the camera is stable, it will be moving at the same rate as the target (V), and since the distance it will move is
+equal to the lerp multiplied by the distance from the target (which will be the old distance plus the new movement of the
+target V)
+
+Then:
+
+L*(D+V) = V 
+L*D + L*V = V
+D = V * (1 - L) / L
+
+Assuming in the above T = 1, then if we have a variable T, the speed of the skater will be T*V, yet we want the distance (Dt)
+moved to remain unchanged (D), so for a time adjusted lerp Lt
+
+Dt = T * V * (1-Lt)/Lt
+
+Since D = Dt
+
+V * (1 - L) / L = T * V * (1-Lt)/Lt
+
+V cancels out, and we get
+
+Lt = TL / (1 - L + TL)
+
+Sanity check,  
+
+if L is 0.25, and T is 1, then Lt = 1*0.25 / (1 - 0.25 + 1*0.25)  = 0.25
+if L is 0.25, and T is 2, then Lt = 2*0.25 / (1 - 0.25 + 2*0.25)  = 0.5 / 1.25 = 0.40
+if L is 0.25, and T is 5, then Lt = 5*0.25 / (1 - 0.25 + 5*0.25)  = 1.25 / 2 = 0.625
+
+Sounds about right.
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float GetTimeAdjustedSlerp( float slerp, float delta )
+{
+	// Mick's method, tries to guarantee that the distance from the target point
+	// will never alter at constant velocity, with changing frame rate
+	// Lt = TL / (1 - L + TL)
+	float t		= delta * 60.0f;
+	float Lt	= t * slerp / ( 1.0f - slerp + t * slerp );
+	return Lt;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define vMIN_WALL_DISTANCE (2.0f)
+
+void ApplyCameraCollisionDetection ( Mth::Vector& camera_pos, Mth::Matrix& camera_matrix, const Mth::Vector& target_pos, const Mth::Vector& focus_pos, bool side_feelers, bool refocus )
+{
+	// camera collision detection
+	
+	Mth::Vector target_to_camera_direction = camera_pos - target_pos;
+	target_to_camera_direction.Normalize();
+	
+	CFeeler feeler;
+	
+	// Ignore faces based on face falgs
+	feeler.SetIgnore(IGNORE_FACE_FLAGS_1, IGNORE_FACE_FLAGS_0);
+	feeler.SetLine(target_pos, camera_pos + vMIN_WALL_DISTANCE * target_to_camera_direction);
+	bool collision = feeler.GetCollision(true);
+	if( collision )
+	{
+//			Gfx::AddDebugLine( at_pos, cam_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 2000 );
+
+		// Limits the camera to getting nearer than 11.9 inches to the focus point.
+		float distance = feeler.GetDist();
+		float length = feeler.Length();
+		if( length * distance < 11.9f + vMIN_WALL_DISTANCE )
+		{
+			distance = (11.9f + vMIN_WALL_DISTANCE) / length;
+		}
+		camera_pos = target_pos + (distance * length - vMIN_WALL_DISTANCE) * target_to_camera_direction;
+	}
+	
+	if (!side_feelers) return;
+
+//#	if defined( __PLAT_NGPS__ ) || defined ( __PLAT_XBOX__ )
+	// Now do two additional checks 1 foot to either side of the camera.
+	Mth::Vector	left( camera_matrix[X][X], 0.0f, camera_matrix[X][Z], 0.0f );
+	left.Normalize( 8.0f );
+	left += camera_pos;
+
+	collision = feeler.GetCollision(camera_pos, left, true);
+	if( collision )
+	{
+		left		   -= feeler.GetPoint();
+		camera_pos		   -= left;
+	}
+	else
+	{
+		Mth::Vector	right( -camera_matrix[X][X], 0.0f, -camera_matrix[X][Z], 0.0f );
+		right.Normalize( 8.0f );
+		right += camera_pos;
+
+		collision = feeler.GetCollision(camera_pos, right, true);
+		if( collision )
+		{
+			right		   -= feeler.GetPoint();
+			camera_pos		   -= right;
+		}
+	}
+	
+	if (!refocus) return;
+
+	if( collision )
+	{
+		// Re-orient the camera again.
+		camera_matrix[Z].Set( focus_pos.GetX() - camera_pos.GetX(), focus_pos.GetY() - camera_pos.GetY(), focus_pos.GetZ() - camera_pos.GetZ());
+		camera_matrix[Z].Normalize();
+
+		// Read back the Y from the current matrix.
+//			target[Y][0]	= p_frame_matrix->up.x;
+//			target[Y][1]	= p_frame_matrix->up.y;
+//			target[Y][2]	= p_frame_matrix->up.z;
+
+		// Generate new orthonormal X and Y axes.
+		camera_matrix[X]		= Mth::CrossProduct( camera_matrix[Y], camera_matrix[Z] );
+		camera_matrix[X].Normalize();
+		camera_matrix[Y]		= Mth::CrossProduct( camera_matrix[Z], camera_matrix[X] );
+		camera_matrix[Y].Normalize();
+
+		// Write back into camera matrix.
+		camera_matrix[X]		= -camera_matrix[X];
+		camera_matrix[Z]		= -camera_matrix[Z];
+		
+		// Fix the final column
+		camera_matrix[X][W] = 0.0f;
+		camera_matrix[Y][W] = 0.0f;
+		camera_matrix[Z][W] = 0.0f;
+		camera_matrix[W][W] = 1.0f;
+		
+	}
+//#endif
+}
+
+}
diff --git a/Code/Gel/Components/CameraUtil.h b/Code/Gel/Components/CameraUtil.h
new file mode 100644
index 0000000..f9ac989
--- /dev/null
+++ b/Code/Gel/Components/CameraUtil.h
@@ -0,0 +1,52 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CameraUtil.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/10/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_CAMERAUTIL_H__
+#define __COMPONENTS_CAMERAUTIL_H__
+
+#include 
+#include 
+#include 
+
+#define SKATERCAMERACOMPONENT_PERFECT_ABOVE			(3.0f)
+			
+#if 0
+// Ignore faces that are NOT flagged as camera collidable
+#define IGNORE_FACE_FLAGS_1 	(0)
+#define IGNORE_FACE_FLAGS_0 	(mFD_CAMERA_COLLIDABLE)
+#endif
+
+#if 0
+// ignore faces that ARE flagged as camera collidable
+#define IGNORE_FACE_FLAGS_1 	(0)
+#define IGNORE_FACE_FLAGS_0 	(mFD_CAMERA_COLLIDABLE)
+#endif
+
+#if 1
+// ignore faces that are non-collidable or invisible
+//#define IGNORE_FACE_FLAGS_1 	(mFD_NON_COLLIDABLE | mFD_INVISIBLE)
+#define IGNORE_FACE_FLAGS_1 	(mFD_NON_COLLIDABLE | mFD_CAMERA_COLLIDABLE)
+#define IGNORE_FACE_FLAGS_0 	(0)
+#endif
+
+#	define CAMERA_SLERP_STOP 0.9999f
+	   
+namespace Obj
+{
+	float			GetTimeAdjustedSlerp ( float slerp, float delta );
+	void			ApplyCameraCollisionDetection ( Mth::Vector& camera_pos,  Mth::Matrix& camera_matrix, const Mth::Vector& target_pos, const Mth::Vector& forcus_pos, bool side_feelers = true, bool refocus = true );
+	
+	struct SCameraState
+	{
+		Mth::Matrix lastActualMatrix;
+		Mth::Vector lastTripodPos;
+		float lastDot;
+		float lastZoom;
+	};
+}
+
+#endif
diff --git a/Code/Gel/Components/CollideAndDieComponent.cpp b/Code/Gel/Components/CollideAndDieComponent.cpp
new file mode 100644
index 0000000..db3d95c
--- /dev/null
+++ b/Code/Gel/Components/CollideAndDieComponent.cpp
@@ -0,0 +1,297 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CollideAndDieComponent.cpp
+//* OWNER:          SPG
+//* CREATION DATE:  7/10/03
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#define vFIREBALL_LIFETIME 	Tmr::Seconds( 10 )
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CCollideAndDieComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CCollideAndDieComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CCollideAndDieComponent::CCollideAndDieComponent() : CBaseComponent()
+{
+    SetType( CRC_COLLIDEANDDIE );
+	m_radius = 0.0f;
+	m_scale = 0;
+	m_birth_time = Tmr::GetTime();
+	m_death_script = 0;
+	m_dying = false;
+	m_first_frame = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCollideAndDieComponent::~CCollideAndDieComponent()
+{   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCollideAndDieComponent::SetCollisionRadius( float radius )
+{
+	m_radius = radius;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CCollideAndDieComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	pParams->GetFloat(CRCD(0xc48391a5,"radius"), &m_radius, Script::ASSERT);
+	pParams->GetFloat(CRCD(0x13b9da7b,"scale"), &m_scale, Script::ASSERT);
+	
+	pParams->GetChecksum(CRCD(0x6647adc3,"death_script"), &m_death_script, Script::ASSERT);
+
+    m_vel.Set( 0, 0, 1 );
+	pParams->GetVector(CRCD(0xc4c809e, "vel"), &m_vel, Script::ASSERT);
+	m_vel.Normalize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CCollideAndDieComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	//InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCollideAndDieComponent::Finalize()
+{
+	mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+}
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCollideAndDieComponent::Hide( bool should_hide )
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CCollideAndDieComponent::Update()
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+	if (gamenet_man->InNetGame() || !mp_suspend_component->SkipLogic())
+	{
+		if(( Tmr::GetTime() - m_birth_time ) > vFIREBALL_LIFETIME )
+		{
+			GetObject()->MarkAsDead();
+		}
+		else
+		{
+			if( m_dying )
+			{
+				if( Tmr::GetTime() > m_death_time )
+				{
+					GetObject()->MarkAsDead();
+				}
+			}
+			else
+			{
+				Mth::Vector vel, start, end;
+				CFeeler feeler;
+		
+				vel = GetObject()->GetVel();
+				vel.Normalize();
+				if( m_first_frame )
+				{
+					start = GetObject()->GetPos();
+				}
+				else
+				{
+					start = m_last_pos;
+				}
+				
+				start[Y] += FEET( 2 );
+				end = GetObject()->GetPos();
+				end[Y] += FEET( 2 );
+				end += ( vel * m_radius );
+				feeler.SetStart( start );
+				feeler.SetEnd( end );
+		
+				if( feeler.GetCollision())
+				{
+					if( m_dying == false )
+					{
+						CProjectileCollisionComponent* proj_comp;
+
+						proj_comp = GetProjectileCollisionComponentFromObject( GetObject());
+						if( proj_comp )
+						{
+							proj_comp->MarkAsDying();
+						}
+
+						m_dying = true;
+						m_death_time = Tmr::GetTime() + Tmr::Seconds( 1 );
+						if( m_death_script != 0 )
+						{
+							Script::CStruct* params;
+							Mth::Vector pos;
+
+							pos = feeler.GetPoint();
+							params = new Script::CStruct;
+							
+							params->AddVector( CRCD(0x7f261953,"pos"), pos );
+							params->AddVector( CRCD(0xc4c809e,"vel"), m_vel );
+							params->AddFloat( CRCD(0x13b9da7b,"scale"), m_scale );
+
+							Script::RunScript( m_death_script, params );
+
+							delete params;
+						}
+					}
+				}
+			}
+		}
+
+		m_last_pos = GetObject()->GetPos();
+		m_first_frame = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CCollideAndDieComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCollideAndDieComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CCollideAndDieComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+	
+
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/CollideAndDieComponent.h b/Code/Gel/Components/CollideAndDieComponent.h
new file mode 100644
index 0000000..a9dd042
--- /dev/null
+++ b/Code/Gel/Components/CollideAndDieComponent.h
@@ -0,0 +1,70 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VelocityComponent.h
+//* OWNER:          SPG
+//* CREATION DATE:  07/10/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_COLLIDEANDDIECOMPONENT_H__
+#define __COMPONENTS_COLLIDEANDDIECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_COLLIDEANDDIE CRCD(0x6259b52b,"CollideAndDie")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetCollideAndDieComponent() ((Obj::CCollideAndDieComponent*)GetComponent(CRC_COLLIDE_AND_DIE))
+#define		GetCollideAndDieComponentFromObject(pObj) ((Obj::CCollideAndDieComponent*)(pObj)->GetComponent(CRC_COLLIDE_AND_DIE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CCollideAndDieComponent : public CBaseComponent
+{
+public:
+    CCollideAndDieComponent();
+    virtual ~CCollideAndDieComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void					Hide( bool should_hide );
+	virtual void					Finalize();
+		
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+	void							SetCollisionRadius( float radius );
+
+private:
+	CSuspendComponent*	mp_suspend_component;
+
+	float				m_radius;	// Radius for spherical collision detection
+	float				m_scale;
+	Tmr::Time			m_birth_time;
+	uint32				m_death_script;
+	Tmr::Time			m_death_time;
+	bool				m_dying;
+	bool				m_first_frame;
+	Mth::Vector			m_vel;
+	Mth::Vector			m_last_pos;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/FloatingLabelComponent.cpp b/Code/Gel/Components/FloatingLabelComponent.cpp
new file mode 100644
index 0000000..fdd7d0c
--- /dev/null
+++ b/Code/Gel/Components/FloatingLabelComponent.cpp
@@ -0,0 +1,155 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       FloatingLabelComponent.h
+//* OWNER:			Dan
+//* CREATION DATE:  3/13/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CFloatingLabelComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CFloatingLabelComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFloatingLabelComponent::CFloatingLabelComponent() : CBaseComponent()
+{
+	SetType( CRC_FLOATINGLABEL );
+	
+	strcpy(m_string, "Unset Label");
+	m_color_index = 2;
+	m_y_offset = 10.0f * 12.0f;
+	m_screen_element_id = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CFloatingLabelComponent::~CFloatingLabelComponent()
+{
+	Script::CStruct* pParams = pParams = new Script::CStruct;
+	pParams->AddChecksum(CRCD(0x40c698af, "id"), m_screen_element_id);
+	
+	Script::RunScript(CRCD(0x2575b406, "destroy_object_label"), pParams);
+	
+	delete pParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFloatingLabelComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	const char* string;
+	if (pParams->GetString(CRCD(0x61414d56, "string"), &string))
+	{
+		strncpy(m_string, string, 64);
+	}
+	pParams->GetInteger(CRCD(0x99a9b716, "color"), &m_color_index);
+	pParams->GetFloat(CRCD(0x14975800, "y_offset"), &m_y_offset);
+	pParams->GetChecksum(CRCD(0x727f9552, "screen_element_id"), &m_screen_element_id);
+	
+	Dbg_MsgAssert(m_screen_element_id, ("FloatingLabelComponent has bad screen_elemend_id"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFloatingLabelComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFloatingLabelComponent::Update()
+{
+	Script::CStruct* pParams = new Script::CStruct;
+	pParams->AddChecksum(CRCD(0x40c698af, "id"), m_screen_element_id);
+	
+	char text[64];
+	sprintf(text, "\\c%d%s", m_color_index, m_string);
+
+	pParams->AddString(CRCD(0xc4745838, "text"), text);
+	pParams->AddVector(CRCD(0x4b491900, "pos3D"), GetObject()->m_pos[X], GetObject()->m_pos[Y] + m_y_offset, GetObject()->m_pos[Z]);
+
+	Front::CScreenElement *p_name_elem = Front::CScreenElementManager::Instance()->GetElement(m_screen_element_id);
+	if (p_name_elem)
+	{
+		p_name_elem->SetProperties(pParams);
+	}
+	else
+	{
+		Script::RunScript(CRCD(0x6a060cf0, "create_object_label"), pParams);
+	}
+	
+	delete pParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CFloatingLabelComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFloatingLabelComponent::GetDebugInfo ( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CFloatingLabelComponent::GetDebugInfo"));
+	
+	p_info->AddString(CRCD(0x2bd21fa8, "m_string"), m_string);
+	p_info->AddInteger(CRCD(0x6d83427f, "m_color_index"), m_color_index);
+	p_info->AddFloat(CRCD(0x1bcdee78, "m_y_offset"), m_y_offset);
+	p_info->AddChecksum(CRCD(0x5f6e2f93, "m_screen_element_id"), m_screen_element_id);
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+	
+}
diff --git a/Code/Gel/Components/FloatingLabelComponent.h b/Code/Gel/Components/FloatingLabelComponent.h
new file mode 100644
index 0000000..d9ab27b
--- /dev/null
+++ b/Code/Gel/Components/FloatingLabelComponent.h
@@ -0,0 +1,60 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       FloatingLabelComponent.h
+//* OWNER:			Dan
+//* CREATION DATE:  3/13/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_FLOATINGLABELCOMPONENT_H__
+#define __COMPONENTS_FLOATINGLABELCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_FLOATINGLABEL CRCD(0xeada1621, "FloatingLabel")
+
+#define		GetFloatingLabelComponent() ((Obj::CFloatingLabelComponent*)GetComponent(CRC_FLOATINGLABEL))
+#define		GetFloatingLabelComponentFromObject(pObj) ((Obj::CFloatingLabelComponent*)(pObj)->GetComponent(CRC_FLOATINGLABEL))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CFloatingLabelComponent : public CBaseComponent
+{
+public:
+    CFloatingLabelComponent();
+    virtual ~CFloatingLabelComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							SetColor ( int color_index ) { m_color_index = color_index; }
+	void							SetString ( char* string ) { strncpy(m_string, string, 64); }
+	
+private:
+	char							m_string[64];
+	int								m_color_index;
+	float							m_y_offset;
+	uint32							m_screen_element_id;
+};
+
+
+
+}
+
+#endif
diff --git a/Code/Gel/Components/GunslingerCameraLookAroundComponent.cpp b/Code/Gel/Components/GunslingerCameraLookAroundComponent.cpp
new file mode 100644
index 0000000..ed44093
--- /dev/null
+++ b/Code/Gel/Components/GunslingerCameraLookAroundComponent.cpp
@@ -0,0 +1,452 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       GunslingerCameraLookAroundComponent.cpp
+//* OWNER:          Dave
+//* CREATION DATE:  5/13/03
+//****************************************************************************
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+						   
+float	spin_modulator			= 1.0f;
+float	tilt_modulator			= 1.0f;
+float	last_heading_change		= 0.0f;
+float	last_tilt_change		= 0.0f;
+bool	gun_fired				= false;
+bool	allow_strafe_locking	= false;
+
+float	screen_angle			= 72.0f;		// Test value for setting field of view.
+
+float	lookaround_tilt_angular_speed		= 0.0f;
+float	lookaround_heading_angular_speed	= 0.0f;
+
+
+
+namespace Obj
+{
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CCameraLookAroundComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CCameraLookAroundComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCameraLookAroundComponent::CCameraLookAroundComponent() : CBaseComponent()
+{
+	SetType( CRC_CAMERALOOKAROUND );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCameraLookAroundComponent::~CCameraLookAroundComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::Finalize (   )
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_input_component);
+
+	mLookaroundTilt					= 0.0f;
+	mLookaroundHeading				= 0.0f;
+	mLookaroundZoom					= 1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCameraLookAroundComponent::Update()
+{
+	CControlPad * p_control_pad;
+	
+	if (Script::GetInt(CRCD(0x702247a5,"Viewer_controller"), false))
+	{
+		p_control_pad = &mp_input_component->GetControlPad2();
+	}
+	else
+	{
+		p_control_pad = &mp_input_component->GetControlPad();
+	}
+
+	if( p_control_pad->m_up.GetTriggered())
+	{
+		p_control_pad->m_up.ClearTrigger();
+
+		// Unfortunately, pressing up on the left control stick will also cause m_up to be triggered, so check this
+		// is really the d-pad press.
+		if( p_control_pad->GetScaledLeftAnalogStickMagnitude() == 0.0f )
+		{
+			// This would be a good place to put the field of view setting code for the sniper rifle.
+			if( screen_angle == 72.0f )
+			{
+				screen_angle = 25.0f;
+			}
+			else if( screen_angle == 25.0f )
+			{
+				screen_angle = 7.5f;
+			}
+			else if( screen_angle == 7.5f )
+			{
+				screen_angle = 72.0f;
+			}
+			Nx::CViewportManager::sSetScreenAngle( screen_angle );
+		}
+	}
+	
+	float frame_length = Tmr::FrameLength();
+	
+	// Strafe locking is allowed by default.
+	allow_strafe_locking = true;
+
+	// Shouldn't really do this here. Disable strafe locking if not strafing, or if moving forward or backwards.
+	float fb = p_control_pad->m_scaled_leftY;
+	float lr = p_control_pad->m_scaled_leftX;
+
+	if( Mth::Abs( fb ) > 0.0f )
+	{
+		allow_strafe_locking = false;
+	}
+
+	if( Mth::Abs( lr ) < 0.2f )
+	{
+		allow_strafe_locking = false;
+	}
+
+	if (!mLookaroundLock && !mLookaroundOverride)
+	{
+		float target = p_control_pad->m_scaled_rightY;
+
+		// Modulate with the variable used to damp cursor movement when aiming at a target.
+		target = target * tilt_modulator;
+
+#		if 1
+//		if( target != 0.0f )
+		{
+			allow_strafe_locking = false;
+
+			if( Script::GetInteger( CRCD( 0x9edfc7af, "GunslingerInvertAiming" )) == 0 )
+			{
+				// Negate value if vertical aiming invert is not enabled.
+				target = -target;
+			}
+
+			// Get script values.
+			float tilt_ka = Script::GetFloat( CRCD( 0xcac0c1d4, "GunslingerLookaroundTiltKa" ), Script::ASSERT );
+			float tilt_ea = Script::GetFloat( CRCD( 0x5443ec5a, "GunslingerLookaroundTiltEa" ), Script::ASSERT );
+
+			float tilt_ks = Script::GetFloat( CRCD( 0x3979b09c, "GunslingerLookaroundTiltKs" ), Script::ASSERT );
+			float tilt_es = Script::GetFloat( CRCD( 0xa7fa9d12, "GunslingerLookaroundTiltEs" ), Script::ASSERT );
+
+			// Calculate acceleration.
+			float a = tilt_ka * powf( Mth::Abs( target ), tilt_ea ) * (( target > 0.0f ) ? 1.0f : ( target < 0.0f ) ? -1.0f : 0.0f );
+
+			// Calculate max speed.
+			float s = tilt_ks * powf( Mth::Abs( target ), tilt_es ) * (( target > 0.0f ) ? 1.0f : ( target < 0.0f ) ? -1.0f : 0.0f );
+
+			lookaround_tilt_angular_speed += a;
+
+			if( s == 0.0f )
+			{
+				lookaround_tilt_angular_speed = 0.0f;
+			}
+			else if((( s > 0.0f ) && ( lookaround_tilt_angular_speed > s )) || (( s < 0.0f ) && ( lookaround_tilt_angular_speed < s )))
+			{
+					lookaround_tilt_angular_speed = s;
+			}
+
+			float lookaround_tilt_speed = Script::GetFloat( CRCD( 0x67796648, "GunslingerLookaroundTiltSpeed" ), Script::ASSERT );
+			mLookaroundTilt += lookaround_tilt_angular_speed * lookaround_tilt_speed * frame_length;
+
+			last_tilt_change = lookaround_tilt_angular_speed * lookaround_tilt_speed * frame_length;
+
+			if( mLookaroundTilt > 1.1f )
+				mLookaroundTilt = 1.1f;
+			else if( mLookaroundTilt < -0.85f )
+				mLookaroundTilt = -0.85f;
+		}
+#		else
+		// This calculation is different for Gunslinger, since we want to be able to look up higher.
+//		target = -1.4f * (target < 0.0f ? (0.7f * target) : (0.4f * target));
+		target = -1.4f * ( target < 0.0f ? ( 0.7f * target ) : ( 0.7f * target ));
+
+		if( Mth::Abs( target - mLookaroundTilt ) > 0.001f )
+		{
+			// Ditto for this calc.
+//			mLookaroundTilt += (target - mLookaroundTilt) * 3.75f * frame_length;
+			mLookaroundTilt += (target - mLookaroundTilt) * 6.0f * frame_length;
+		}
+		else
+		{
+			mLookaroundTilt = target;
+		}
+#		endif
+		
+#		if 1
+		target = p_control_pad->m_scaled_rightX;
+
+		// Modulate with the variable used to damp cursor movement when aiming at a target.
+		target = target * spin_modulator;
+		
+//		if( target != 0.0f )
+		{
+			allow_strafe_locking = false;
+
+			// Get script values.
+			float heading_ka = Script::GetFloat( CRCD( 0x6fd803d9, "GunslingerLookaroundHeadingKa" ), Script::ASSERT );
+			float heading_ea = Script::GetFloat( CRCD( 0xf15b2e57, "GunslingerLookaroundHeadingEa" ), Script::ASSERT );
+
+			float heading_ks = Script::GetFloat( CRCD( 0x9c617291, "GunslingerLookaroundHeadingKs" ), Script::ASSERT );
+			float heading_es = Script::GetFloat( CRCD( 0x2e25f1f, "GunslingerLookaroundHeadingEs" ), Script::ASSERT );
+
+			// Calculate acceleration.
+			float a = heading_ka * powf( Mth::Abs( target ), heading_ea ) * (( target > 0.0f ) ? 1.0f : ( target < 0.0f ) ? -1.0f : 0.0f );
+
+			// Calculate max speed.
+			float s = heading_ks * powf( Mth::Abs( target ), heading_es ) * (( target > 0.0f ) ? 1.0f : ( target < 0.0f ) ? -1.0f : 0.0f );
+
+			lookaround_heading_angular_speed += a;
+
+			if( s == 0.0f )
+			{
+				lookaround_heading_angular_speed = 0.0f;
+			}
+			else if((( s > 0.0f ) && ( lookaround_heading_angular_speed > s )) || (( s < 0.0f ) && ( lookaround_heading_angular_speed < s )))
+			{
+					lookaround_heading_angular_speed = s;
+			}
+
+			float lookaround_heading_speed = Script::GetFloat( CRCD( 0x8501d824, "GunslingerLookaroundHeadingSpeed" ), Script::ASSERT );
+
+			// Control stick left - reticle should move left.
+			mLookaroundHeading -= lookaround_heading_angular_speed * lookaround_heading_speed * frame_length;
+
+			last_heading_change	= lookaround_heading_angular_speed * lookaround_heading_speed * frame_length;
+
+			if( mLookaroundHeading > ( 2 * Mth::PI ))
+				mLookaroundHeading -= ( 2 * Mth::PI );
+			else if( mLookaroundHeading < 0.0f )
+				mLookaroundHeading += 2 * Mth::PI;
+		}
+#		else
+		target = 3.0f * p_control_pad->m_scaled_rightX;
+		if (Mth::Abs(target - mLookaroundHeading) > 0.001f)
+		{
+			mLookaroundHeading += (target - mLookaroundHeading) * 3.75f * frame_length;
+		}
+		else
+		{
+			mLookaroundHeading = target;
+		}
+#		endif
+	}
+	
+	float delta_time = Tmr::FrameLength();
+
+	if (mLookaroundOverride)
+	{
+		if (mLookaroundDeltaTimer > 0.0f)
+		{
+			if (mLookaroundDeltaTimer > delta_time)
+			{
+				mLookaroundHeading		+= mLookaroundHeadingDelta * delta_time;
+				mLookaroundTilt			+= mLookaroundTiltDelta * delta_time;
+				mLookaroundDeltaTimer	-= delta_time;
+			}
+			else
+			{
+				mLookaroundHeading		= mLookaroundHeadingTarget;
+				mLookaroundTilt			= mLookaroundTiltTarget;
+				mLookaroundDeltaTimer	= 0.0f;
+			}
+		}
+	}
+
+	// Total hack to simulate shooting for now.
+	gun_fired = false;
+	if( p_control_pad->m_R1.GetTriggered())
+	{
+		gun_fired = true;
+		p_control_pad->m_R1.ClearTrigger();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CCameraLookAroundComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | SC_SetSkaterCamOverride | 
+        // @parm float | heading |
+        // @parm float | tilt | 
+        // @parm float | time | 
+        // @parm float | zoom | 
+		case 0xb8f23899:	// SC_SetSkaterCamOverride
+		{
+			float heading, tilt, time;
+			float zoom = 1.0f;
+			pParams->GetFloat( CRCD(0xfd4bc03e,"heading"), &heading, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xe3c07609,"tilt"), &tilt, Script::ASSERT );
+			pParams->GetFloat( CRCD(0x906b67ba,"time"), &time, Script::ASSERT );
+			pParams->GetFloat( CRCD(0x48d4868b,"zoom"), &zoom );
+
+			SetLookaround( heading, tilt, time, zoom );
+			
+			break;
+		}
+
+        // @script | SC_ClearSkaterCamOverride |
+		case 0xe3b327c0:	// SC_ClearSkaterCamOverride
+			ClearLookaround();
+			break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CCameraLookAroundComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCameraLookAroundComponent::SetLookaround( float heading, float tilt, float time, float zoom )
+{
+	mLookaroundOverride				= true;
+
+	mLookaroundHeadingTarget		= heading;
+	mLookaroundTiltTarget			= tilt;
+
+	// Calculate delta - the amount to move in 1 second.
+	if (time > 0.0f)
+	{
+		mLookaroundHeadingDelta		= (heading - mLookaroundHeading) / time;
+		mLookaroundTiltDelta		= (tilt - mLookaroundTilt) / time;
+	}
+
+	mLookaroundDeltaTimer			= time;
+	mLookaroundZoom					= zoom;
+}
+
+#if 0 // old control input checking code
+
+char touched[256 * 256 / 8];
+bool initialized = false;
+
+void MapInput (   )
+{
+	if (!initialized)
+	{
+		for (int n = 255 * 255; n--; )
+		{
+			touched[n / 8] = 0;
+		}
+		initialized = true;
+	}
+	
+	CControlPad& pad = mp_input_component->GetControlPad();
+	
+	int x = static_cast< int >(pad.m_rightX) + 128;
+	int y = static_cast< int >(pad.m_rightY) + 128;
+	
+	// printf("x = %i; y = %i\n", x, y);
+	
+	int index = (x * 256) + y;
+	
+	// printf("index / 8 = %i\n", index / 8);
+	// printf("MAX = %i\n", 256 * 256 / 8);
+	
+	touched[index / 8] |= (1 << (index % 8));
+}
+
+
+void DrawMap (   )
+{
+	Mth::Vector pos = GetObject()->GetPos();
+	Mth::Matrix matrix = GetObject()->GetMatrix();
+	
+	Mth::Vector center = pos - 300.0f * matrix[Z];
+	Mth::Vector up = matrix[Y];
+	Mth::Vector left = -matrix[X];
+	
+	for (int n = 255 * 255; n--; )
+	{
+		if (!(touched[n / 8] & (1 << (n % 8)))) continue;
+		
+		int x = n / 256;
+		int y = n % 256;
+		
+		Mth::Vector point = center + up * (y - 128) + left * (x - 128);
+		
+		Gfx::AddDebugLine(point + up + left, point - up - left, MAKE_RGB(255, 255, 255), 0, 1);
+		Gfx::AddDebugLine(point + up - left, point - up + left, MAKE_RGB(255, 255, 255), 0, 1);
+	}
+}
+
+#endif
+
+}
diff --git a/Code/Gel/Components/GunslingerWalkCameraComponent.cpp b/Code/Gel/Components/GunslingerWalkCameraComponent.cpp
new file mode 100644
index 0000000..19afe1c
--- /dev/null
+++ b/Code/Gel/Components/GunslingerWalkCameraComponent.cpp
@@ -0,0 +1,569 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       GunslingerWalkCameraComponent.cpp
+//* OWNER:          Dave
+//* CREATION DATE:  5/13/03
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 				// Messy, but for now used to obtain ped positions etc.
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// These should be members in the cameralookaroundcomponent.
+extern float			spin_modulator;
+extern float			tilt_modulator;
+extern float			last_heading_change;
+extern float			last_tilt_change;
+extern bool				gun_fired;
+extern bool				allow_strafe_locking;
+
+int						best_ped_timer			= 0;
+Obj::CCompositeObject*	p_selected_target		= NULL;
+float					target_selection_timer	= 0.0f;		// Times how long the selected target has been targeted for.
+
+namespace Obj
+{
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static float s_get_gunslinger_param( uint32 checksum )
+{
+	Script::CStruct* p_cam_params = Script::GetStructure( CRCD( 0xa13ff9ae,"GunslingerCameraParameters" ));
+	Dbg_Assert( p_cam_params );
+
+	float param;
+	p_cam_params->GetFloat( checksum, ¶m, Script::ASSERT );
+	return param;
+}
+
+	
+	
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CWalkCameraComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CWalkCameraComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWalkCameraComponent::CWalkCameraComponent() : CBaseComponent()
+{
+	SetType( CRC_WALKCAMERA );
+
+	m_last_actual_matrix.Ident();
+	m_last_tripod_pos.Set( 0.0f, 0.0f, 0.0f, 1.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CWalkCameraComponent::~CWalkCameraComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 target_id = 0 ;
+	pParams->GetChecksum("CameraTarget", &target_id, Script::ASSERT);
+	
+	CCompositeObject* p_target = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(target_id));
+	Dbg_MsgAssert(p_target, ("Bad CameraTarget given to WalkCameraComponent"));
+		
+	set_target(p_target);
+	
+	m_last_dot = 1.0f;
+	m_current_zoom = 1.0f;
+	
+	m_last_actual_matrix = GetObject()->GetMatrix();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::Finalize()
+{
+	mp_lookaround_component = GetCameraLookAroundComponentFromObject(GetObject());
+	mp_camera_component = GetCameraComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_lookaround_component);
+	Dbg_Assert(mp_camera_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::Update()
+{
+	// optimization KLUDGE
+//	if (Script::GetInteger(CRCD(0x1aa88b08, "vehicle_mode")))
+//	{
+//		GetObject()->Pause(true);
+//		return;
+//	}
+	
+	if (mp_target->HasTeleported())
+	{
+		m_instant_count = 3;
+	}
+
+	bool instantly;
+	if (m_instant_count > 0)
+	{
+		--m_instant_count;
+		instantly = true;
+	}
+	else
+	{
+		instantly = false;
+	}
+	
+	mp_camera_component->StoreOldPosition();
+
+	float frame_length = Tmr::FrameLength();
+	
+	// get input
+//	float horiz_control = GetInputComponentFromObject( GetObject())->GetControlPad().m_scaled_rightX;
+	float horiz_control = mp_lookaround_component->mLookaroundHeading;
+
+	// Restore camera position from last frame, previous to refocusing, lookaround and collision detection.
+	GetObject()->GetMatrix() = m_last_actual_matrix;
+
+	Mth::Vector	target_facing = -GetObject()->GetMatrix()[Z];
+	target_facing[Y] = 0.0f;
+	target_facing.Normalize();
+	
+	Mth::Vector subject_facing = mp_target->GetMatrix()[Z];
+	subject_facing[Y] = 0.0f;
+	subject_facing.Normalize();
+	
+	// two options; we either use our old facing as the target facing or use the subject's facing as the target facing
+	bool use_subject_facing = true;
+	
+	// if in a flush request
+	if (m_flush_request_active)
+	{
+		// always use the subject's matrix
+	}
+	// if controlling camera
+	else if (horiz_control != 0.0f)
+	{
+		use_subject_facing = false;
+	}
+	// if the subject's facing is towards the camera
+	else if (Mth::DotProduct(target_facing, subject_facing) < cosf(Mth::DegToRad(s_get_gunslinger_param(CRCD(0xb8da8a73, "lock_angle")))))
+	{
+		use_subject_facing = false;
+	}
+	// if the subject is moving very slowly
+	else // if (!mp_target_walk_component->UseDPadCamera())
+	{
+		float full_slerp_speed;
+		float min_slerp_speed;
+		if (!mp_target_walk_component->UseDPadCamera())
+		{
+			full_slerp_speed = s_get_gunslinger_param(CRCD(0xbcef6dda, "full_slerp_speed"));
+			min_slerp_speed = s_get_gunslinger_param(CRCD(0x824349e5, "min_slerp_speed"));
+		}
+		else
+		{
+			full_slerp_speed = s_get_gunslinger_param(CRCD(0x73da1ec0, "dpad_full_slerp_speed"));
+			min_slerp_speed = s_get_gunslinger_param(CRCD(0xda5cad3, "dpad_min_slerp_speed"));
+		}
+		
+		float target_vel = mp_target->GetVel().Length();
+		if (target_vel < full_slerp_speed)
+		{
+			use_subject_facing = false;
+		}
+	}
+	
+	// Never want to use the subject facing camera.
+	use_subject_facing = false;
+
+	if (use_subject_facing)
+	{
+		target_facing = subject_facing;
+	}
+	
+	// Build target matrix.
+	Mth::Matrix target_matrix;
+	target_matrix[W].Set( 0.0f, 0.0f, 0.0f, 1.0f );
+	target_matrix[Z] = target_facing;
+	target_matrix[Y].Set( 0.0f, 1.0f, 0.0f );
+	target_matrix[X].Set( target_facing[Z], 0.0f, -target_facing[X] );
+
+	// Dave note - testing default value for now.
+	float slerp = 0.06f;
+
+	target_matrix[X] = -target_matrix[X];
+	target_matrix[Z] = -target_matrix[Z];
+
+	// use later for camera position
+	Mth::Vector up = mp_target->GetMatrix()[Y];
+	
+	if (!instantly)
+	{
+		if( Mth::DotProduct(target_matrix[X], GetObject()->GetMatrix()[X]) > CAMERA_SLERP_STOP &&
+			Mth::DotProduct(target_matrix[Y], GetObject()->GetMatrix()[Y]) > CAMERA_SLERP_STOP &&
+			Mth::DotProduct(target_matrix[Z], GetObject()->GetMatrix()[Z]) > CAMERA_SLERP_STOP )
+		{
+			// we're already at our target, so don't do anything
+			
+			// turn off any flush request
+			if (m_flush_request_active)
+			{
+				m_flush_request_active = false;
+				mp_target_walk_component->SetForwardControlLock(false);
+			}
+		}
+		else
+		{
+			// Slerp to the target matrix.
+			Mth::SlerpInterpolator slerper( &GetObject()->GetMatrix(), &target_matrix );
+			
+			// Apply the slerping.
+			slerper.getMatrix( &GetObject()->GetMatrix(), GetTimeAdjustedSlerp( slerp, frame_length ));
+
+			// Calculate for the skater camera.
+			m_last_dot = Mth::DotProduct(m_last_actual_matrix[Z], GetObject()->GetMatrix()[Z]);
+		}
+	}
+	else
+	{
+		GetObject()->GetMatrix() = target_matrix;
+	}
+
+	// At this point, GetObject()->GetMatrix() is valid to store.
+	m_last_actual_matrix = GetObject()->GetMatrix();
+
+	// Now apply the lookaround adjustments to the matrix.
+	// Control over target facing.
+	if( horiz_control != 0.0f && !m_flush_request_active )
+	{
+		// The horiz_control value needs to be damped when there is an item of interest within the reticle area.
+		GetObject()->GetMatrix().RotateYLocal( horiz_control );
+	}
+
+	float tilt = s_get_gunslinger_param( CRCD( 0xe3c07609, "tilt" ));
+	GetObject()->GetMatrix().RotateXLocal( tilt + mp_lookaround_component->mLookaroundTilt );
+
+	Mth::Vector	camera_pos = get_tripod_pos( instantly );
+
+	// Test the weapon component to generate the 'sticky' targetting behavior.
+	if( mp_target && p_selected_target )
+	{
+		CWeaponComponent* p_weapon = GetWeaponComponentFromObject( mp_target );
+
+		p_weapon->SetCurrentTarget( p_selected_target );
+		p_weapon->SetSightPos( camera_pos );
+		p_weapon->SetSightMatrix( GetObject()->GetMatrix());
+
+		float extra_heading_change, extra_tilt_change;
+		p_weapon->ProcessStickyTarget( last_heading_change, last_tilt_change, &extra_heading_change, &extra_tilt_change );
+
+		if(( extra_heading_change != 0.0f ) || ( extra_tilt_change != 0.0f ))
+		{
+			// Reset the matrix to what it was prior to the heading and tilt adjustments.
+			GetObject()->SetMatrix( m_last_actual_matrix );
+
+			mp_lookaround_component->mLookaroundHeading += extra_heading_change;
+			horiz_control += extra_heading_change;
+			GetObject()->GetMatrix().RotateYLocal( horiz_control );
+
+			mp_lookaround_component->mLookaroundTilt += extra_tilt_change;
+			GetObject()->GetMatrix().RotateXLocal( tilt + mp_lookaround_component->mLookaroundTilt );
+		}
+	}
+
+	// Calculate zoom
+	float above, behind;
+	calculate_zoom( above, behind );
+	
+	camera_pos += GetObject()->GetMatrix()[Z] * behind + up * above;
+	
+	Mth::Vector	focus_pos = mp_target_walk_component->GetEffectivePos() + up * above;
+	
+	// Focus the camera directly on the target object
+	target_matrix[Z] = focus_pos - camera_pos;
+	target_matrix[Z].Normalize();
+
+	// Read back the Y from the current matrix.
+//	target_matrix[Y] = GetObject()->GetMatrix()[Y];
+	target_matrix[Y] = Mth::Vector( 0.0f, 1.0f, 0.0f );
+
+	// Generate new orthonormal X and Y axes.
+	target_matrix[X] = Mth::CrossProduct(target_matrix[Y], target_matrix[Z]);
+	target_matrix[X].Normalize();
+
+	target_matrix[Y] = Mth::CrossProduct(target_matrix[Z], target_matrix[X]);
+	target_matrix[Y].Normalize();
+
+	// Write back into camera matrix.
+	// Since camera points in -Z, but player in +Z, we must negate the X and Z axes
+	GetObject()->GetMatrix()[X]	= -target_matrix[X];
+	GetObject()->GetMatrix()[Y] = target_matrix[Y];
+	GetObject()->GetMatrix()[Z] = -target_matrix[Z];
+	
+	// clean up matrix
+	GetObject()->GetMatrix()[X][W] = 0.0f;
+	GetObject()->GetMatrix()[Y][W] = 0.0f;
+	GetObject()->GetMatrix()[Z][W] = 0.0f;
+	GetObject()->GetMatrix()[W].Set(0.0f, 0.0f, 0.0f, 1.0f);
+
+	// Now do collision detection.
+	ApplyCameraCollisionDetection( camera_pos, GetObject()->GetMatrix(), camera_pos - GetObject()->GetMatrix()[Z] * behind,	focus_pos );
+	
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		Gfx::AddDebugStar(focus_pos, 24.0f, MAKE_RGB(255, 200, 0), 1);
+	}
+	#endif
+	
+	camera_pos[W] = 1.0f;
+	GetObject()->SetPos( camera_pos );
+
+	// Do the target selection.
+	if( mp_target )
+	{
+		Mth::Vector reticle_min	= camera_pos;
+		Mth::Vector reticle_max;
+
+		CWeaponComponent* p_weapon = GetWeaponComponentFromObject( mp_target );
+
+		p_weapon->SetSightMatrix( GetObject()->GetMatrix());
+		p_selected_target = p_weapon->GetCurrentTarget( reticle_min, &reticle_max );
+
+		if( gun_fired )
+		{
+			p_weapon->Fire();
+		}
+
+		spin_modulator = p_weapon->GetSpinModulator();
+		tilt_modulator = p_weapon->GetTiltModulator();
+
+		p_weapon->DrawReticle();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CWalkCameraComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | WalkCamera_FlushRequest | Force the camera to lerp quickly to behind the walker
+		case CRCC(0x73febd0f, "WalkCamera_FlushRequest"):
+			FlushRequest();
+			break;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CWalkCameraComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::ReadyForActivation ( const SCameraState& state )
+{
+	m_last_tripod_pos = state.lastTripodPos;
+	m_last_actual_matrix = state.lastActualMatrix;
+	m_last_dot = state.lastDot;
+	m_current_zoom = state.lastZoom;
+	m_flush_request_active = false;
+	
+	mp_lookaround_component->mLookaroundTilt = 0.0f;
+	mp_lookaround_component->mLookaroundHeading = 0.0f;
+	mp_lookaround_component->mLookaroundZoom = 1.0f;
+	mp_lookaround_component->mLookaroundLock = false;
+	
+	m_override_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::GetCameraState ( SCameraState& state )
+{
+	state.lastActualMatrix = m_last_actual_matrix;
+	state.lastTripodPos = m_last_tripod_pos;
+	state.lastDot = m_last_dot;
+	state.lastZoom = m_current_zoom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::FlushRequest (   )
+{
+	m_flush_request_active = true;
+	mp_target_walk_component->SetForwardControlLock(true);
+	
+	// flush requests zero skater cam lookaround
+	mp_lookaround_component->mLookaroundHeading = 0.0f;
+	mp_lookaround_component->mLookaroundTilt = 0.0f;
+	mp_lookaround_component->mLookaroundZoom = 1.0f;
+	mp_lookaround_component->mLookaroundLock = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::set_target ( CCompositeObject* p_target )
+{
+	mp_target = p_target;
+	Dbg_Assert(mp_target);
+	
+	mp_target_walk_component = GetWalkComponentFromObject(mp_target);
+	Dbg_Assert(mp_target_walk_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CWalkCameraComponent::get_tripod_pos( bool instantly )
+{
+	if (instantly)
+	{
+		m_last_tripod_pos = mp_target_walk_component->GetEffectivePos();
+	}
+	else
+	{
+		float lerp_xz = GetTimeAdjustedSlerp(s_get_gunslinger_param(CRCD(0xae47899b, "lerp_xz")), Tmr::FrameLength());
+		float lerp_y = GetTimeAdjustedSlerp(s_get_gunslinger_param(CRCD(0xe1e4e104, "lerp_y")), Tmr::FrameLength());
+		
+		const Mth::Vector& target_pos = mp_target_walk_component->GetEffectivePos();
+
+		m_last_tripod_pos.Set(
+			Mth::Lerp(m_last_tripod_pos[X], target_pos[X], lerp_xz),
+			Mth::Lerp(m_last_tripod_pos[Y], target_pos[Y], lerp_y),
+			Mth::Lerp(m_last_tripod_pos[Z], target_pos[Z], lerp_xz)
+		);
+	}
+
+	return m_last_tripod_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::calculate_zoom ( float& above, float& behind )
+{
+	float target_zoom = 1.0f;
+
+	// If lookaround override is set, factor in the lookaround override zoom.
+	if (mp_lookaround_component->mLookaroundOverride && mp_lookaround_component->mLookaroundZoom != 1.0f)
+	{
+		target_zoom *= mp_lookaround_component->mLookaroundZoom;
+	}
+	
+//	m_current_zoom += (( target_zoom - m_current_zoom ) * s_get_gunslinger_param( CRCD( 0x748743a7, "zoom_lerp" )));
+	m_current_zoom += (( target_zoom - m_current_zoom ) * s_get_gunslinger_param( CRCD( 0x748743a7, "zoom_lerp" )));
+	
+//	behind = s_get_gunslinger_param(CRCD(0x52e6c41b, "behind")) * m_current_zoom;
+	behind = s_get_gunslinger_param(CRCD(0x52e6c41b, "behind")) * m_current_zoom;
+
+	// Behind is also shortened when the lookaround camera is tilting upwards.
+	if( mp_lookaround_component->mLookaroundTilt < 0.0f )
+	{
+		float max_tilt = -0.9f;
+//		behind = behind * (0.4f + (0.6f * ((max_tilt + mp_lookaround_component->mLookaroundTilt) / max_tilt)));
+		behind = behind * ( 0.0f + ( 1.0f * (( max_tilt - mp_lookaround_component->mLookaroundTilt ) / max_tilt )));
+	}
+	else if( mp_lookaround_component->mLookaroundTilt > 0.0f )
+	{
+		float max_tilt = 1.4f;
+		behind = behind * ( 0.0f + ( 1.0f * (( max_tilt - mp_lookaround_component->mLookaroundTilt ) / max_tilt )));
+	}
+
+	// Use lip_trick_above when doing a lip trick.
+//	float above_val = s_get_gunslinger_param(CRCD(0xb96ae2d, "above"));
+	float above_val = s_get_gunslinger_param( CRCD( 0xb96ae2d, "above" ));
+	
+	// Figure above tending towards the perfect above, if zoom is < 1.0.
+	if( m_current_zoom < 1.0f )
+	{
+		above = SKATERCAMERACOMPONENT_PERFECT_ABOVE + ((above_val - SKATERCAMERACOMPONENT_PERFECT_ABOVE) * m_current_zoom);
+	}
+	else
+	{
+		above = above_val;
+	}
+}
+
+}
diff --git a/Code/Gel/Components/GunslingerWalkComponent.cpp b/Code/Gel/Components/GunslingerWalkComponent.cpp
new file mode 100644
index 0000000..d4a4ff0
--- /dev/null
+++ b/Code/Gel/Components/GunslingerWalkComponent.cpp
@@ -0,0 +1,2351 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WalkComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/2/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+
+/*
+ * - Take out manual, take out manual, take out manual.
+ * - Switch over to no-origin-teleport animations.
+ * - Catching on curbs right after jump (use snap up code from skater)
+ * - Moving contacts (and sticking to moving rails).
+ * - Control Feel
+ *    - Perhaps X equals run only during run outs.
+ * - Camera
+ *    - Flush requests don't quite complete using the timer method.  Perhaps with forward control locking, the goal method may once again be useful.
+ *    - Transition out of vert facing camera.  Perhaps flush camera at first landing after an air transition.
+ *    - Rewrite camera with "forward locking" affecting target matrix instead of turning off lerping.  Lookaround when walking shouldn't reset to behind
+ *      during walking.
+ * - Shadow update.
+ * - Height update.
+ * - Enter/exit walk behavior (always rotates to along slope, run out to walk behavior still looks odd).
+ * - Turn off stance panel nollie.
+ * - Step up/down (use feeler to look ahead).  Delay WalkOffEdge animation change.
+ * - Animations:
+ *    - Pull-up-from-hang has no feet!
+ *    - Trot walking animation.
+ *    - Drop-to-hang animation.
+ *    - Hop-to-hang animation.
+ *    - Three walk/run-to-stand animations.
+ *    - Rotate walk/run animation.
+ *    - Two ladder idle animations.
+ *    - Onto-ladder-top animation. 
+ *    - Off-ladder-bottom animation.
+ *    - Ladder-to-hang/hang-to-ladder animations.
+ *    - idle skater to stand
+ *    - land to walk issues
+ *    - special pull-up-to-wire anim
+ *    - off-ladder-bottom anim to allow blending at anim start / teleport at anim end
+ *    - onto-ladder-top anim to allow blending at anim start / teleport at anim end
+ *    - grab-to-hang swing and wall anims suck
+ *    - grab-to-hang sideways swing
+ *    - Onto-ladder-top animation.  Currently play Off-ladder-top backwards.
+ *    - Fall-air before jump-air anim.
+ * - Animation comments:
+ *    - running land doesn't really work (no weight), especially when you land and then immediately run off an edge and then land again
+ *    - full run is odd looking
+ * - Walk to stand animation will require at least three versions.
+ * - Hang
+ *    - Rail-to-rail ledge transition issues.
+ *    - Jerky rail corners.
+ *    - Non horizontal hang movement animations.
+ *    - Camera pop during pull-up (caused by camera collision).
+ *    - Jump off hang.
+ * - Ladder
+ *    - Jump off ladder.
+ *    - Jump onto ladder.
+ *    - You can grind ladders.
+ * - Grab rail in air.
+ * - Little talkie boxes don't always work.
+ * - Gaps still trigger, but based on skate state.  Maybe run gap component while walking and include walking gap flag.
+ * - How can we deal with a low snap-up height, yet allow for steep slope walking?
+ * - Grind transition animation in Grind script (check to see if its playing).
+ * - Clean 1/3 second delay before X running.
+ * - Upper body interpenetrates stuff.
+ * - Hang from bottom of ladder.
+ * - Pull up collision restrictions are too restrictive (use push feeler distance?).
+ * - Make it easier to grab a rail when you walk off an edge.
+ * - Ladder to rail across top.  Rail across top to ladder.
+ * - Hand-plants, drop-ins, etc.
+ * BUGS:
+ * - Snap to hangs can cause the origin to go to bad places?  NJ by your roof.
+ * - Fall through verts with low frame rate.
+ * - Pop in transition from last to first frames of slow cycling walk animations.
+ */
+
+namespace Obj
+{
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static float s_get_gunslinger_param( uint32 checksum )
+{
+	Script::CStruct* p_walk_params = Script::GetStructure( CRCD( 0x1c33e162, "GunslingerWalkParameters" ));
+	Dbg_Assert(p_walk_params);
+	
+	float param;
+	p_walk_params->GetFloat(checksum, ¶m, Script::ASSERT);
+	return param;
+}
+
+	
+	
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CWalkComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CWalkComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWalkComponent::CWalkComponent() : CBaseComponent()
+{
+	SetType( CRC_WALK );
+	
+	mp_collision_cache = Nx::CCollCacheManager::sCreateCollCache();
+	
+	mp_input_component = NULL;
+	mp_animation_component = NULL;
+	mp_movable_contact_component = NULL;
+
+	m_facing.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+	m_control_direction.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CWalkComponent::~CWalkComponent()
+{
+	Nx::CCollCacheManager::sDestroyCollCache(mp_collision_cache);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::Finalize()
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	mp_animation_component = GetAnimationComponentFromObject(GetObject());
+	mp_model_component = GetModelComponentFromObject(GetObject());
+	mp_trigger_component = GetTriggerComponentFromObject(GetObject());
+	mp_physics_control_component = GetSkaterPhysicsControlComponentFromObject(GetObject());
+	mp_movable_contact_component = GetMovableContactComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_input_component);
+	Dbg_Assert(mp_animation_component);
+	Dbg_Assert(mp_model_component);
+	Dbg_Assert(mp_trigger_component);
+	Dbg_Assert(mp_physics_control_component);
+	Dbg_Assert(mp_movable_contact_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 camera_id;
+	if (pParams->GetChecksum(CRCD(0xc4e311fa, "camera"), &camera_id))
+	{
+		CCompositeObject* p_camera = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(camera_id));
+		Dbg_MsgAssert(mp_camera, ("No such camera object"));
+		SetAssociatedCamera(p_camera);
+	}
+}
+		 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::Update()
+{
+	// TEMP: debounce R1 after a transition
+	if (m_ignore_grab_button && !mp_input_component->GetControlPad().m_R1.GetPressed())
+	{
+		m_ignore_grab_button = false;
+	}
+
+	// zero the frame event
+	m_last_frame_event = m_frame_event;
+	m_frame_event = 0;
+	
+	// get input
+    get_controller_input();
+	
+	// extract initial state for this frame from the object
+	
+	m_frame_start_pos = m_pos = GetObject()->GetPos();
+	
+	m_horizontal_vel = GetObject()->GetVel();
+	m_horizontal_vel[Y] = 0.0f;
+	m_vertical_vel = GetObject()->GetVel()[Y];
+	
+	// note that m_facing and m_upward will often not be orthogonal, but will always span a plan
+	
+	// generally straight up, but now after a transition from skating
+	m_upward = GetObject()->GetMatrix()[Y];
+	
+	m_facing = GetObject()->GetMatrix()[Z];
+	m_facing[Y] = 0.0f;
+	float length = m_facing.Length();
+	if (length < 0.001f)
+	{
+		// upward facing orientation matrix
+		m_facing = -GetObject()->GetMatrix()[Y];
+		m_facing[Y] = 0.0f;
+		m_facing.Normalize();
+		
+		// since m_upward is now in the same plan as m_facing, push m_upward up a touch
+		m_upward[Y] += 0.01f;
+		m_upward.Normalize();
+	}
+	else
+	{
+		m_facing /= length;
+	}
+	
+	// set the frame length
+	m_frame_length = Tmr::FrameLength();
+	
+	// go to our true Y position
+	m_curb_float_height_adjusted = false;
+	m_pos[Y] -= m_curb_float_height;
+	
+	// switch logic based on walking state
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+			go_on_ground_state();
+			break;
+
+		case WALKING_AIR:
+			go_in_air_state();
+			break;
+																	  
+//		case WALKING_HOP:
+//			go_hop_state();
+//			break;
+																	  
+		case WALKING_HANG:
+			go_hang_state();
+			break;
+            
+		case WALKING_LADDER:
+			go_ladder_state();
+            break;
+			
+		case WALKING_ANIMWAIT:
+			go_anim_wait_state (   );
+			break;
+	}
+	
+	// the there's no curb to adjust due to, lerp down to zero
+	if (!m_curb_float_height_adjusted)
+	{
+		m_curb_float_height = Mth::Lerp(m_curb_float_height, 0.0f, s_get_gunslinger_param(CRCD(0x9b3388fa, "curb_float_lerp_down_rate")) * m_frame_length);
+	}
+	
+	// adjust back to our curb float Y position
+	m_pos[Y] += m_curb_float_height;
+	
+	// scripts may have restarted us / switched us to skating
+//	if (should_bail_from_frame()) return;
+	
+	// keep the object from falling through holes in the geometry
+	if (m_state == WALKING_GROUND || m_state == WALKING_AIR)
+	{
+		uber_frig();
+	}
+	
+	// rotate to upright
+	lerp_upright();
+	
+	// setup the object based on this frame's walking
+	copy_state_into_object();
+	
+	Dbg_Assert(m_frame_event);
+	GetObject()->SelfEvent(m_frame_event);
+	
+	// set the animation speeds
+	switch (m_anim_scale_speed)
+	{
+		case RUNNING:
+			if (m_anim_standard_speed > 0.0f)
+			{
+				mp_animation_component->SetAnimSpeed(m_anim_effective_speed / m_anim_standard_speed, false, false);
+			}
+			break;
+			
+		case HANGMOVE:
+			mp_animation_component->SetAnimSpeed(m_anim_effective_speed / s_get_gunslinger_param(CRCD(0xd77ee881, "hang_move_speed")), false, false);
+			break;
+					
+		case LADDERMOVE:
+			mp_animation_component->SetAnimSpeed(m_anim_effective_speed / s_get_gunslinger_param(CRCD(0xab2db54, "ladder_move_speed")), false, false);
+			break;
+	
+		default:
+			break;
+	}
+	
+	// camera controls
+	// NOTE: script parameters
+	switch (m_frame_event)
+	{
+		case CRCC(0xf41aba21, "Hop"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+		
+		case CRCC(0x2d9815c3, "HangMoveLeft"):
+		{
+			Mth::Vector facing = m_facing;
+			facing.RotateY(-0.95f);
+			mp_camera_component->SetOverrides(facing, 0.05f);
+			break;
+		}
+			
+		case CRCC(0x279b1f0b, "HangMoveRight"):
+		{
+			Mth::Vector facing = m_facing;
+			facing.RotateY(0.95f);
+			mp_camera_component->SetOverrides(facing, 0.05f);
+			break;
+		}
+					
+		case CRCC(0x4194ecca, "Hang"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+		
+		case CRCC(0xc84243da, "Ladder"):
+		case CRCC(0xaf5abc82, "LadderMoveUp"):
+		case CRCC(0xfec9dded, "LadderMoveDown"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+					
+		case CRCC(0x4fe6069c, "AnimWait"):
+			if (m_anim_wait_camera_mode == AWC_CURRENT)
+			{
+				mp_camera_component->SetOverrides(m_facing, 0.05f);
+			}
+			else
+			{
+				mp_camera_component->SetOverrides(m_drift_goal_facing, 0.05f);
+			}
+			break;
+	
+		default:
+			mp_camera_component->UnsetOverrides();
+			break;
+	}
+	
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		Gfx::AddDebugStar(GetObject()->GetPos(), 36.0f, MAKE_RGB(255, 255, 255), 1);
+	}
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CWalkComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Walk_Ground |
+		case CRCC(0x893213e5, "Walk_Ground"):
+			return m_state == WALKING_GROUND ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_Air |
+		case CRCC(0x5012082e, "Walk_Air"):
+			return m_state == WALKING_AIR ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_Hang |
+		case CRCC(0x9a3ca853, "Walk_Hang"):
+			return m_state == WALKING_HANG ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_Ladder |
+		case CRCC(0x19702ca8, "Walk_Ladder"):
+			return m_state == WALKING_LADDER ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_GetStateTime | Loads the time in milliseconds since last state change.
+		case CRCC(0xce64576c, "Walk_GetStateTime"):
+			pScript->GetParams()->AddInteger(CRCD(0x5ab23cc9, "StateTime"), Tmr::ElapsedTime(m_state_timestamp));
+			break;
+		
+		// @script | Walk_Jump |
+		case CRCC(0x83e4bd70, "Walk_Jump"):
+		{
+			// jump strength scales with the length the jump button has been held
+			jump();
+//			jump(Mth::Lerp(
+//				s_get_gunslinger_param(CRCD(0x246d0bf3, "min_jump_factor")), 
+//				1.0f,
+//				Mth::ClampMax(mp_input_component->GetControlPad().m_x.GetPressedTime() / s_get_gunslinger_param(CRCD(0x12333ebd, "hold_time_for_max_jump")), 1.0f)
+//			));
+			break;
+		}
+		
+//		case CRCC(0xeb0d763b, "Walk_HangJump"):
+//		{
+//			// jump strength scales with the length the jump button has been held
+//			jump(s_get_gunslinger_param(CRCD(0xf2fa5845, "hang_jump_factor")), true);
+//			break;
+//		}
+		
+		// @script | Walk_SetDragFactor |
+		case CRCC(0xc6100a7d, "Walk_SetDragFactor"):
+			break;
+			
+		case CRCC(0x4e4fae43, "Walk_ResetDragFactor"):
+			break;
+			
+		case CRCC(0xaf04b983, "Walk_GetSpeedScale"):
+		{
+			uint32 checksum;
+			if (m_anim_effective_speed < s_get_gunslinger_param(CRCD(0xf3649996, "max_slow_walk_speed")))
+			{
+				checksum = CRCD(0x1150cabb, "WALK_SLOW");
+			}
+			else if (m_anim_effective_speed < s_get_gunslinger_param(CRCD(0x6a5805d8, "max_fast_walk_speed")))
+			{
+				checksum = CRCD(0x131f2a2, "WALK_FAST");
+			}
+			else if (m_anim_effective_speed < s_get_gunslinger_param(CRCD(0x1c94cc9c, "max_slow_run_speed")))
+			{
+				checksum = CRCD(0x5606d106, "RUN_SLOW");
+			}
+			else
+			{
+				checksum = CRCD(0x4667e91f, "RUN_FAST");
+			}
+			pScript->GetParams()->AddChecksum(CRCD(0x92c388f, "SpeedScale"), checksum);
+			
+			break;
+		}
+		
+		// @script | Walk_ScaleAnimSpeed | Sets the manner in which the walk animations speeds should be scaled.
+		// @flag Off | No animation speed scaling.
+		// @flag Run | Scale animation speeds against running speed.
+		// @flag Walk | Scale animation speeds against walking speed.
+		case CRCC(0x56112c03, "Walk_ScaleAnimSpeed"):
+			if (pParams->ContainsFlag(CRCD(0xd443a2bc, "Off")))
+			{
+				if (m_anim_scale_speed != OFF)
+				{
+					m_anim_scale_speed = OFF;
+					mp_animation_component->SetAnimSpeed(1.0f, false, true);
+				}
+			}
+			else if (pParams->ContainsFlag(CRCD(0xaf895b3f, "Run")))
+			{
+				m_anim_scale_speed = RUNNING;
+			}
+			else if (pParams->ContainsFlag(CRCD(0x6384f1da, "HangMove")))
+			{
+				m_anim_scale_speed = HANGMOVE;
+			}
+			else if (pParams->ContainsFlag(CRCD(0xa2bfe505, "LadderMove")))
+			{
+				m_anim_scale_speed = LADDERMOVE;
+			}
+			else
+			{
+				Dbg_MsgAssert(false, ("Walk_ScaleAnimSpeed requires Off, Run, or Walk flag"));
+			}
+			
+			pParams->GetFloat(CRCD(0xb2d59baf, "StandardSpeed"), &m_anim_standard_speed);
+			break;
+			
+		// @script | Walk_AnimWaitComplete | Signal from script that the walk component should leave its animation wait
+		case CRCC(0x9d3eebe8, "Walk_AnimWaitComplete"):
+			anim_wait_complete();
+			break;
+				   			
+		// @script | Walk_GetHangInitAnimType | Determine which type of initial hang animation should be played
+		case CRCC(0xc6cd659e, "Walk_GetHangInitAnimType"):
+			// m_initial_hang_animation is set when the hang rail is filtered
+			pScript->GetParams()->AddChecksum(CRCD(0x85fa9ac4, "HangInitAnimType"), m_initial_hang_animation);
+			break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CWalkComponent::GetDebugInfo"));
+	
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x58007c97, "GROUND"));
+			break;
+		case WALKING_AIR:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x439f4704, "AIR"));
+			break;
+//		case WALKING_HOP:
+//			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0xf41aba21, "HOP"));
+//			break;										 
+		case WALKING_HANG:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x4194ecca, "HANG"));
+			break;										 
+        case WALKING_LADDER:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0xc84243da, "LADDER"));
+			break;										 
+        case WALKING_ANIMWAIT:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x4fe6069c, "ANIMWAIT"));
+			break;										 
+	}
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::SetAssociatedCamera ( CCompositeObject* camera_obj )
+{
+	mp_camera = camera_obj;
+	Dbg_Assert(mp_camera);
+	mp_camera_component = GetWalkCameraComponentFromObject(mp_camera);
+	Dbg_MsgAssert(mp_camera_component, ("No WalkCameraComponent in camera object"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::ReadyWalkState ( bool to_ground_state )
+{
+	// setup the state in preparation for being in walking mode next object update
+	MARK;
+	
+    // always reset the state timestamp
+    m_state_timestamp = Tmr::GetTime();
+
+	if (to_ground_state)
+	{
+		set_state(WALKING_GROUND);
+		
+		// will be incorrect for one frame
+		m_ground_normal.Set(0.0f, 1.0f, 0.0);
+		
+		m_last_ground_feeler_valid = false;
+		
+		GetObject()->GetVel()[Y] = 0.0f;
+	}
+	else
+	{
+		set_state(WALKING_AIR);
+		
+		// set primary air direction in the direction of velocity
+		m_primary_air_direction = GetObject()->GetVel();
+		m_primary_air_direction[Y] = 0.0f;
+		float length = m_primary_air_direction.Length();
+		if (length < 0.001f)
+		{
+			// or facing
+			m_primary_air_direction = GetObject()->GetMatrix()[Z];
+			m_primary_air_direction[Y] = 0.0f;
+			length = m_primary_air_direction.Length();
+			if (length < 0.001f)
+			{
+				// or future facing
+				m_primary_air_direction = -GetObject()->GetMatrix()[Y];
+				m_primary_air_direction[Y] = 0.0f;
+				length = m_primary_air_direction.Length();
+			}
+		}
+		m_primary_air_direction /= length;
+		
+		leave_movable_contact_for_air(GetObject()->GetVel(), GetObject()->GetVel()[Y]);
+	}
+
+	m_curb_float_height = 0.0f;
+	
+	m_last_frame_event = 0;
+	
+	// TEMP: debounce R1 after a transition
+	m_ignore_grab_button = true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::CleanUpWalkState (   )
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::go_on_ground_state( void )
+{
+	// Check for trying to mount a horse, triggered on triangle.
+	if( mp_input_component->GetControlPad().m_triangle.GetTriggered())
+	{
+		mp_input_component->GetControlPad().m_triangle.ClearTrigger();
+
+		// Get the control component.
+		CSkaterPhysicsControlComponent*	p_control_component = GetSkaterPhysicsControlComponentFromObject( GetObject());
+		p_control_component->CallMemberFunction( CRCD( 0x14c4f16b, "SkaterPhysicsControl_SwitchWalkingToRiding"), NULL, NULL );
+
+		// Send the 'Ride' exception.
+		m_frame_event = CRCD( 0x64c2832f, "Ride" );
+		GetObject()->SelfEvent( m_frame_event );
+		return;
+	}
+
+	account_for_movable_contact();
+	
+	setup_collision_cache();
+	
+	// calculate initial horizontal speed
+	float horizontal_speed = m_horizontal_vel.Length();
+	
+	calculate_horizontal_speed_and_facing(horizontal_speed);
+	
+	// calculate this frame's movement
+	m_horizontal_vel = horizontal_speed * m_facing;
+	
+	// prevent movement into walls
+	if (adjust_horizonal_vel_for_environment())
+	{
+		// turn to face newly adjusted velocity
+		adjust_facing_for_adjusted_horizontal_vel();
+	}
+	
+	// if we are wall pushing, we may have decided to switch states during adjust_horizonal_vel_for_environment based on our environment
+	if (m_state != WALKING_GROUND /*|| should_bail_from_frame()*/)
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	// apply movement for this frame
+	m_pos += m_horizontal_vel * m_frame_length;
+	
+	// snap up and down curbs and perhaps switch to air
+	respond_to_ground();
+	if (m_state != WALKING_GROUND /*|| should_bail_from_frame()*/)
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	adjust_curb_float_height();
+	
+	// insure that we do not slip through the cracks in the collision geometry which are a side-effect of moving collidable objects
+	if (CCompositeObject* p_inside_object = mp_movable_contact_component->CheckInsideObjects(m_pos, m_frame_start_pos))
+	{
+		MESSAGE("WALKING_GROUND, within moving object");
+		
+		// allow it to push us forward, causing a bit of a stumble
+		m_horizontal_vel = p_inside_object->GetVel();
+		m_horizontal_vel[Y] = 0.0f;
+		m_vertical_vel = 0.0f;
+		
+		float speed_sqr = m_horizontal_vel.LengthSqr();
+		if (speed_sqr > (10.0f * 10.0f))
+		{
+			m_facing = m_horizontal_vel * (1.0f / sqrtf(speed_sqr));
+		}
+	}
+	
+	CFeeler::sClearDefaultCache();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::CollideWithOtherSkaterLost ( CCompositeObject* p_other_skater )
+{
+	set_state(WALKING_AIR);
+	m_primary_air_direction = m_facing;
+	
+	leave_movable_contact_for_air(GetObject()->GetVel(), GetObject()->GetVel()[Y]);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::calculate_horizontal_speed_and_facing ( float &horizontal_speed )
+{
+	// calculate user's desired speed
+	float desired_speed = calculate_desired_speed();
+			   	
+	// setup frame's event
+	if (desired_speed <= s_get_gunslinger_param(CRCD(0x74e8227d, "max_stand_speed")))
+	{
+		m_frame_event = CRCD(0x9b46e749, "Stand");
+	}
+	else
+	{
+		m_frame_event = CRCD(0xaf895b3f, "Run");
+	}
+
+	bool special_acceleration = false;
+	
+	// adjust facing based on input
+	
+	if( m_control_magnitude > 0.0f )
+	{
+		float dot = Mth::DotProduct( m_facing, m_control_direction );
+		
+		if (horizontal_speed < s_get_gunslinger_param(CRCD(0x52582d5b, "max_rotate_in_place_speed")) && dot < cosf(Mth::DegToRad(s_get_gunslinger_param(CRCD(0x5dff96a4, "max_rotate_in_place_angle")))))
+		{
+			// low speed rotate to desired orientation with no speed change
+			
+			float delta_angle = Mth::DegToRad(s_get_gunslinger_param(CRCD(0xb557804b, "rotate_in_place_rate"))) * m_control_magnitude * m_frame_length;
+			bool left_turn = -m_facing[Z] * m_control_direction[X] + m_facing[X] * m_control_direction[Z] < 0.0f;
+			
+			if (!m_run_toggle)
+			{
+				delta_angle *= s_get_gunslinger_param(CRCD(0x7b446c98, "walk_rotate_factor"));
+			}
+			
+			float cos_delta_angle = cosf(left_turn ? delta_angle : -delta_angle);
+			float sin_delta_angle = sinf(left_turn ? delta_angle : -delta_angle);
+			float adjusted_vel = cos_delta_angle * m_facing[X] + sin_delta_angle * m_facing[Z];
+			m_facing[Z] = -sin_delta_angle * m_facing[X] + cos_delta_angle * m_facing[Z];
+			m_facing[X] = adjusted_vel;
+			
+			// check for overturn
+			if (left_turn != (-m_facing[Z] * m_control_direction[X] + m_facing[X] * m_control_direction[Z] < 0.0f))
+			{
+				m_facing = m_control_direction;
+			}
+			
+			// no acceleration until we reach the desired orientation
+			special_acceleration = true;
+			
+			// setup the event
+			m_frame_event = left_turn ? CRCD(0xf28adbfc, "RotateLeft") : CRCD(0x912220f8, "RotateRight");
+		}
+		else
+		{
+			if (dot > -cosf(Mth::DegToRad(s_get_gunslinger_param(CRCD(0x2d571c0f, "max_reverse_angle")))))
+			{
+				// if the turn angle is soft
+				
+				// below a speed threshold, scale up the turn rate
+				float turn_factor;
+				if (horizontal_speed < s_get_gunslinger_param(CRCD(0x27815f69, "max_pop_speed")))
+				{
+					// quick turn
+					turn_factor = Mth::Lerp(s_get_gunslinger_param(CRCD(0xb278405d, "best_turn_factor")), s_get_gunslinger_param(CRCD(0x6cb2e5de, "worse_turn_factor")), horizontal_speed / s_get_gunslinger_param(CRCD(0x27815f69, "max_pop_speed"))); 
+				}
+				else
+				{
+					// slower turn
+					turn_factor = s_get_gunslinger_param(CRCD(0x6cb2e5de, "worse_turn_factor"));
+				}
+				turn_factor *= m_control_magnitude;
+				
+				// exponentially approach the new facing
+				float turn_ratio = turn_factor * m_frame_length;
+				if (turn_ratio >= 1.0f)
+				{
+					m_facing = m_control_direction;
+				}
+				else
+				{
+					m_facing = Mth::Lerp(m_facing, m_control_direction, turn_ratio);
+					m_facing.Normalize();
+				}
+			}
+			else
+			{
+				// the turn angle is hard
+				
+				if (horizontal_speed > s_get_gunslinger_param(CRCD(0xf1e97e45, "min_skid_speed")))
+				{
+					// if moving fast enough to require a skidding stop
+					special_acceleration = true;
+					horizontal_speed -= s_get_gunslinger_param(CRCD(0x9661ed7, "skid_accel")) * m_frame_length;
+					horizontal_speed = Mth::ClampMin(horizontal_speed, 0.0f);
+					m_frame_event = CRCD(0x1d537eff, "Skid");
+				}
+				else
+				{
+					// if max_rotate_in_place_speed is larger than min_skid_speed and max_reverse_angle points farther in reverse than
+					// max_rotate_in_place_angle, as they should, then this code should never be run
+					Dbg_Message("Unexpected state in CWalkComponent::calculate_horizontal_speed_and_facing");
+					
+					// to be safe, pop to the new facing
+					m_facing = m_control_direction;
+				}
+			}
+		}
+	}
+	
+	if (special_acceleration) return;
+	
+	// store desired speed for animation speed scaling
+	m_anim_effective_speed = desired_speed;
+	
+	// adjust desired speed for slope
+	desired_speed = adjust_desired_speed_for_slope(desired_speed);
+	
+	// linear acceleration; exponential deceleration
+	if (horizontal_speed > desired_speed)
+	{
+		horizontal_speed = Mth::Lerp(horizontal_speed, desired_speed, s_get_gunslinger_param(CRCD(0xacfa4e0c, "decel_factor")) * m_frame_length);
+		
+		if (desired_speed == 0.0f)
+		{
+			if (horizontal_speed > s_get_gunslinger_param(CRCD(0x79d182ad, "walk_speed")))
+			{
+				m_frame_event = CRCD(0x1d537eff, "Skid");
+			}
+			else if (m_last_frame_event == CRCD(0x1d537eff, "Skid") && horizontal_speed > s_get_gunslinger_param(CRCD(0x311d02b2, "stop_skidding_speed")))
+			{
+				m_frame_event = CRCD(0x1d537eff, "Skid");
+			}
+		}
+	}
+	else
+	{
+		if (m_run_toggle)
+		{
+			horizontal_speed += s_get_gunslinger_param(CRCD(0x4f47c998, "run_accel_rate")) * m_frame_length;
+		}
+		else
+		{
+			horizontal_speed += s_get_gunslinger_param(CRCD(0x6590a49b, "walk_accel_rate")) * m_frame_length;
+		}
+		if (horizontal_speed > desired_speed)
+		{
+			horizontal_speed = desired_speed;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool  CWalkComponent::adjust_horizonal_vel_for_environment ( void )
+{
+	// We send out feeler rays to find nearby walls.  We limit velocity to be flush with the first wall found.  If two or more non-parallel walls
+	// are found, velocity is zeroed.
+	
+	float feeler_length = s_get_gunslinger_param(CRCD(0x99978d2b, "feeler_length"));
+	float feeler_height = s_get_gunslinger_param(CRCD(0x6da7f696, "feeler_height"));
+	
+	CFeeler feeler;
+	
+	bool contact = false;
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		// setup the the feeler
+		
+		if (n == vNUM_FEELERS)
+		{
+			// final feeler is for air state only and is at the feet; solves situations in which the feet impact with vertical surfaces which the
+			// wall feelers are too high to touch
+			if (m_state != WALKING_AIR)
+			{
+				mp_contacts[vNUM_FEELERS].in_collision = false;
+				continue;
+			}
+			
+			feeler.m_start = m_pos;
+			feeler.m_end = m_pos + m_horizontal_vel * m_frame_length;
+			feeler.m_end[Y] += m_vertical_vel * m_frame_length + 0.5f * -s_get_gunslinger_param(CRCD(0xa5e2da58, "gravity")) * Mth::Sqr(m_frame_length);
+		}
+		else
+		{
+			feeler.m_start = m_pos;
+			feeler.m_start[Y] += feeler_height;
+			feeler.m_end = m_pos + feeler_length * calculate_feeler_offset_direction(n);
+			feeler.m_end[Y] += feeler_height;
+		}
+		
+		mp_contacts[n].in_collision = feeler.GetCollision();
+		
+		if (!mp_contacts[n].in_collision)
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(0, 0, 255, 1);
+			}
+			#endif
+			continue;
+		}
+		
+		contact = true;
+		
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+
+		// grab the horizontal normal of the contacted wall
+		mp_contacts[n].normal = feeler.GetNormal();
+		mp_contacts[n].normal[Y] = 0.0f;
+		mp_contacts[n].normal.Normalize();
+		
+		// if we're on the moving object, don't count its movement when doing collision detection, as the walker's velocity is already measured
+		// relative to its movable contact's
+		if (feeler.IsMovableCollision()
+			&& (!mp_movable_contact_component->HaveContact() || mp_movable_contact_component->GetContact()->GetObject() != feeler.GetMovingObject()))
+		{
+			mp_contacts[n].movement = Mth::DotProduct(feeler.GetMovingObject()->GetVel(), mp_contacts[n].normal);
+		}
+		else
+		{
+			mp_contacts[n].movement = 0.0f;
+		}
+	}
+	
+	// check for wall push
+	if (m_state == WALKING_GROUND)
+	{
+		if (check_for_wall_push())
+		{
+			// if we're wall pushing, we may decide to switch states based on our environment
+			
+			if (Tmr::ElapsedTime(m_wall_push_test.test_start_time) > s_get_gunslinger_param(CRCD(0x928e6775, "hop_delay")))
+			{
+				if (maybe_climb_up_ladder() || /*maybe_hop_to_hang() ||*/ maybe_jump_low_barrier()) return false;
+			}
+			else if (Tmr::ElapsedTime(m_wall_push_test.test_start_time) > s_get_gunslinger_param(CRCD(0x38d36700, "barrier_jump_delay")))
+			{
+				if (maybe_climb_up_ladder() || maybe_jump_low_barrier()) return false;
+			}
+		}
+		else if (mp_input_component->GetControlPad().m_R1.GetPressed() && !m_ignore_grab_button)
+		{
+			if (maybe_climb_up_ladder(true)) return false;
+		}
+	}
+	
+	if (!contact) return false;
+	
+	// push away from walls
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		                
+		if (mp_contacts[n].feeler.GetDist() < s_get_param(CRCD(0xa20c43b7, "push_feeler_length")) / feeler_length)
+		{
+			m_pos += s_get_param(CRCD(0x4d16f37d, "push_strength")) * m_frame_length * mp_contacts[n].normal;
+		}
+	}
+
+	// from here on we ignore collisions we're moving out of
+	contact = false;
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		
+		// don't count collisions we're moving out of
+		if (Mth::DotProduct(mp_contacts[n].normal, m_horizontal_vel) >= mp_contacts[n].movement)
+		{
+			mp_contacts[n].in_collision = false;
+		}
+		else
+		{
+			contact = true;
+		}
+	}
+	if (!contact) return false;
+	
+	// Now we calculate how our movement is effected by our collisions.  The movement must have a non-negative dot product with all collision normals.
+	// The algorithm used should be valid for all convex environments.
+	
+	// if any of the colllision normals are more than right angles to one another, no movement is possible
+	// NOTE: not valid with movable contacts; could cause jerky movement in corners where walls are movable
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		for (int m = n + 1; m < vNUM_FEELERS + 1; m++)
+		{
+			if (!mp_contacts[m].in_collision) continue;
+			if (Mth::DotProduct(mp_contacts[n].normal, mp_contacts[m].normal) <= 0.0f)
+			{
+				m_horizontal_vel.Set();
+				m_anim_effective_speed = Mth::Min(s_get_gunslinger_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+				return true;
+			}
+		}
+	}
+	
+	// direction of proposed movement
+	Mth::Vector movement_direction = m_horizontal_vel;
+	movement_direction.Normalize();
+	
+	Mth::Vector adjusted_vel = m_horizontal_vel;
+	
+	// loop over the contacts (from backward to forward)
+	const int contact_idxs[] = { 7, 4, 3, 5, 2, 6, 1, 0 };
+	for (int i = 0; i < vNUM_FEELERS + 1; i++)
+	{
+		int n = contact_idxs[i];
+		
+		if (!mp_contacts[n].in_collision) continue;
+		
+		// check to see if the movement still violates this constraint
+		float normal_vel = Mth::DotProduct(adjusted_vel, mp_contacts[n].normal);
+		if (normal_vel >= mp_contacts[n].movement) continue;
+		
+		// adjust the movement to the closest direction allowed by this contraint
+		adjusted_vel -= (normal_vel - mp_contacts[n].movement) * mp_contacts[n].normal;
+		
+		// if the mvoement direction no longer points in the direction of the proposed movement, no movement occurs
+		if (Mth::DotProduct(adjusted_vel, m_horizontal_vel) <= 0.0f)
+		{
+			m_horizontal_vel.Set();
+			m_anim_effective_speed = Mth::Min(s_get_gunslinger_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+			return true;
+		}
+	}
+	
+	// insure that the adjusted velocity in the final direction is not larger than the projection of the initial velocity into that direction
+	float adjusted_speed = adjusted_vel.Length();
+	Mth::Vector adjusted_vel_direction = adjusted_vel;
+	adjusted_vel_direction *= 1.0f / adjusted_speed;
+	float projected_vel = Mth::DotProduct(m_horizontal_vel, adjusted_vel_direction);
+	
+	if (adjusted_speed > projected_vel)
+	{
+		adjusted_vel = adjusted_vel_direction * projected_vel;
+	}
+	
+	// only the velocity along the movement direction is retained
+	m_horizontal_vel = adjusted_vel;
+	
+	float final_horiz_vel = m_horizontal_vel.Length();
+	if (m_anim_effective_speed > s_get_gunslinger_param(CRCD(0xbd6a05d, "min_anim_run_speed")))
+	{
+		m_anim_effective_speed = final_horiz_vel;
+		m_anim_effective_speed = Mth::Max(s_get_gunslinger_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::adjust_facing_for_adjusted_horizontal_vel (   )
+{
+	// We adjust facing due to adjustment in horizontal velocity due to environment.  Basically, we want to object to turn to face the velocity
+	// that the environment has forced upon it.
+	
+	// IDEA: shift to basing turn amount on angle difference and not speed
+	
+	float horizontal_speed = m_horizontal_vel.Length();
+	
+	if (horizontal_speed < s_get_gunslinger_param(CRCD(0x515a933, "wall_turn_speed_threshold"))) return;
+	
+	// the new facing is in the direction of our adjusted velocity
+	Mth::Vector new_facing = m_horizontal_vel;
+	new_facing.Normalize();
+	
+	// smoothly transition between no wall turning to full wall turning
+	float turn_ratio;
+	if (horizontal_speed > s_get_gunslinger_param(CRCD(0xe6c1cd0d, "max_wall_turn_speed_threshold")))
+	{
+		turn_ratio = s_get_gunslinger_param(CRCD(0x7a583b9b, "wall_turn_factor")) * m_frame_length;
+	}
+	else
+	{
+		turn_ratio = Mth::LinearMap(
+			0.0f,
+			s_get_gunslinger_param(CRCD(0x7a583b9b, "wall_turn_factor")) * m_frame_length,
+			horizontal_speed,
+			s_get_gunslinger_param(CRCD(0x0515a933, "wall_turn_speed_threshold")),
+			s_get_gunslinger_param(CRCD(0xe6c1cd0d, "max_wall_turn_speed_threshold"))
+		);
+	}
+	
+	// exponentially approach new facing
+	if (turn_ratio >= 1.0f)
+	{
+		m_facing = new_facing;
+	}
+	else
+	{
+		m_facing = Mth::Lerp(m_facing, new_facing, turn_ratio);
+		m_facing.Normalize();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWalkComponent::adjust_desired_speed_for_slope ( float desired_speed )
+{
+	// Slow velocity up and down slopes.
+	
+	// skip if there is no appreciable slope
+	if (m_ground_normal[Y] > 0.95f) return desired_speed;
+	
+	// skip if not running
+	if (desired_speed <= s_get_gunslinger_param(CRCD(0x79d182ad, "walk_speed"))) return desired_speed;
+	
+	// calculate a horizontal vector up the slope
+	Mth::Vector up_slope = m_ground_normal;
+	up_slope[Y] = 0.0f;
+	up_slope.Normalize();
+	
+	// horizontal factor of velocity if the velocity were pointing along the slope (instead of along the horizontal)
+	float movement_factor = m_ground_normal[Y];
+	
+	// factor of velocity pointing up the slope
+	float dot = Mth::Abs(Mth::DotProduct(m_facing, up_slope));
+	
+	// scale the up-the-slope element of velocity based on the slope strength
+	return (1.0f - dot) * desired_speed + dot * movement_factor * desired_speed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::respond_to_ground (   )
+{
+	// Look for the ground below us.  If we find it, snap to it.  If not, go to air state.
+	
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += s_get_gunslinger_param(CRCD(0xcee3a3e1, "snap_up_height"));
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] -= s_get_gunslinger_param(CRCD(0xaf3e4251, "snap_down_height"));
+	
+	if (!feeler.GetCollision())
+	{
+		// no ground
+		
+		if (m_last_ground_feeler_valid)
+		{
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
+//			if (should_bail_from_frame()) return;
+		}
+		
+		if (mp_input_component->GetControlPad().m_triangle.GetPressed())
+		{
+			// need to give the player a change to rail before climbing down ladders and such
+			
+			// if we just climbed up to a rail and are immediately falling, we need some minute amount of movement in order to find a rail
+			if (m_pos == m_frame_start_pos)
+			{
+				m_frame_start_pos[Y] += 0.001f;
+			}
+			
+			if (maybe_stick_to_rail()) return;
+		}
+		
+		if (maybe_climb_down_ladder() || maybe_drop_to_hang()) return;
+		
+		// go to air state
+		set_state(WALKING_AIR);
+		m_primary_air_direction = m_facing;
+		leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+		m_frame_event = CRCD(0xabf1f6ac, "WalkOffEdge");
+		return;
+	}
+	
+	float snap_distance = feeler.GetPoint()[Y] - m_pos[Y];
+	
+	// no not send event for very small snaps
+	if (Mth::Abs(snap_distance) > s_get_gunslinger_param(CRCD(0xd3193d8e, "max_unnoticed_ground_snap")))
+	{
+		GetObject()->SelfEvent(snap_distance > 0.0f ? CRCD(0x93fcf3ed, "SnapUpEdge") : CRCD(0x56e21153, "SnapDownEdge"));
+	}
+	
+	// snap position to the ground
+	m_pos[Y] = feeler.GetPoint()[Y];
+	
+	// adjust stair float distance
+	m_curb_float_height = Mth::ClampMin(m_curb_float_height - snap_distance, 0.0f);
+	
+	// see if we've changed sectors
+	if (m_last_ground_feeler.GetSector() != feeler.GetSector())
+	{
+		if (m_last_ground_feeler_valid)
+		{
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF, m_last_ground_feeler);
+//			if (should_bail_from_frame()) return;
+		}
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, feeler);
+//		if (should_bail_from_frame()) return;
+	}
+	
+	// stash the ground feeler so that we can trip the group's triggers at a later time
+	m_last_ground_feeler = feeler;
+	m_last_ground_feeler_valid = true;
+	
+	// set the ground normal for next frame's velocity slope adjustment
+	m_ground_normal = feeler.GetNormal();
+	
+	// NOTE: need to repeat this code anywhere we enter the ground state
+	mp_movable_contact_component->CheckForMovableContact(feeler);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::adjust_curb_float_height (   )
+{
+	// adjust m_curb_float_height to smooth out moving up stairs
+	
+	// When facing a curb, we smoothly increase m_curb_float_height to the height of the curb.  When we snap up the curb, m_curb_float_height is then
+	// reduced by an amount equal to the snap distance.
+	// When we snap down a curb, m_curb_float_height is increased by the snap distance.  We then drop m_curb_float_height smoothly to zero.
+	
+	// determine appropriate direction to search for a curb
+	Mth::Vector feeler_direction = m_facing;
+	feeler_direction.ProjectToPlane(m_ground_normal);
+	feeler_direction[Y] = Mth::ClampMin(feeler_direction[Y], 0.0f);
+	feeler_direction.Normalize();
+	
+	// look for a curb
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += 0.5f;
+	feeler.m_end = m_pos + s_get_gunslinger_param(CRCD(0x11edcc52, "curb_float_feeler_length")) * feeler_direction;
+    feeler.m_end[Y] += 0.5f;
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 255, 0, 1);
+	}
+	#endif
+	
+	if (feeler.GetCollision())
+	{
+		// grab the distance to the curb
+		float distance_to_curb = feeler.GetDist();
+		
+		// look up from the curb to find the curb height
+		feeler.m_end = feeler.GetPoint() + 0.5f * feeler_direction;
+		feeler.m_start = feeler.m_end;
+		feeler.m_start[Y] = m_pos[Y] + s_get_gunslinger_param(CRCD(0xcee3a3e1, "snap_up_height"));
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(0, 255, 255, 1);
+		}
+		#endif
+		
+		if (feeler.GetCollision())
+		{
+			// calculate the m_curb_float_height we should have based on the curb height and distance
+			float appropriate_curb_float_height = (1.0f - distance_to_curb) * (feeler.GetPoint()[Y] - m_pos[Y]);
+			
+			if (Mth::Abs(m_curb_float_height) < 0.01f && m_control_magnitude == 0.0f && m_horizontal_vel.LengthSqr() < Mth::Sqr(s_get_gunslinger_param(CRCD(0x227d72ee, "min_curb_height_adjust_vel"))))
+			{
+				// don't update the curb height if we're on the ground and standing still; this is mostly to prevent snapping up right after landing a jump
+			}
+			else
+			{
+				// lerp to the appropriate height
+				m_curb_float_height = Mth::Lerp(m_curb_float_height, appropriate_curb_float_height, s_get_gunslinger_param(CRCD(0x856a80d3, "curb_float_lerp_up_rate")) * m_frame_length);
+			}
+			
+			m_curb_float_height_adjusted = true;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::account_for_movable_contact (   )
+{
+	if (!mp_movable_contact_component->UpdateContact(m_pos)) return;
+	
+	m_pos += mp_movable_contact_component->GetContact()->GetMovement();
+	
+	if (mp_movable_contact_component->GetContact()->IsRotated())
+	{
+		m_facing = mp_movable_contact_component->GetContact()->GetRotation().Rotate(m_facing);
+		if (m_facing[Y] != 0.0f)
+		{
+			DUMPF(m_facing[Y]);
+			m_facing[Y] = 0.0f;
+			m_facing.Normalize();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::jump (   )
+{
+	// switch to air state and give the object an upwards velocity
+	
+	float strength = 0.0f;
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+		case WALKING_AIR:
+			// jump strength scales with the length the jump button has been held
+			strength = Mth::Lerp(
+				s_get_param(CRCD(0x246d0bf3, "min_jump_factor")), 
+				1.0f,
+				Mth::ClampMax(mp_input_component->GetControlPad().m_x.GetPressedTime() / s_get_param(CRCD(0x12333ebd, "hold_time_for_max_jump")), 1.0f)
+			);
+			break;
+			
+		case WALKING_HANG:
+		case WALKING_LADDER:
+		case WALKING_ANIMWAIT:
+			strength = s_get_param(CRCD(0xf2fa5845, "hang_jump_factor"));
+			break;
+	}
+	
+	// if we're jumping from the ground, trip the ground's triggers
+	if (m_state == WALKING_GROUND && m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return;
+	}
+	
+	// Called by script from outside of the component update, so m_vertical_vel is not used.
+	GetObject()->GetVel()[Y] = strength * s_get_param(CRCD(0x63d62a21, "jump_velocity"));
+	
+	// jumps for ladders and hanging get a backwards velocity
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+		case WALKING_AIR:
+		{
+//			extract_state_from_object();
+	
+			if (m_control_magnitude)
+			{
+				float min_launch_speed = calculate_desired_speed()
+					* s_get_param(CRCD(0x839fe542, "jump_horiz_speed")) / get_run_speed();
+				
+				if (Mth::DotProduct(m_horizontal_vel, m_control_direction) > 0.0f)
+				{
+					m_horizontal_vel.ProjectToNormal(m_control_direction);
+					if (m_horizontal_vel.Length() < min_launch_speed)
+					{
+						m_horizontal_vel.Normalize(min_launch_speed);
+					}
+				}
+				else
+				{
+					m_horizontal_vel = min_launch_speed * m_control_direction;
+				}
+				
+				m_primary_air_direction = m_control_direction;
+				m_facing = m_control_direction;
+			}
+			else
+			{
+				m_primary_air_direction = m_facing;
+			}
+			
+//			adjust_jump_for_ceiling_obstructions();
+			
+			// setup primary air direction
+			float length_sqr = m_horizontal_vel.LengthSqr();
+			if (length_sqr > 0.01f)
+			{
+				m_primary_air_direction = m_horizontal_vel;
+				m_primary_air_direction /= sqrtf(length_sqr);
+			}
+			else
+			{
+				m_primary_air_direction = m_facing;
+			}
+			
+			copy_state_into_object();
+			
+			break;
+		}
+		
+		case WALKING_ANIMWAIT:
+			m_false_wall.active = true;
+			m_false_wall.distance = Mth::DotProduct(m_false_wall.normal, m_pos + m_critical_point_offset);
+			
+			GetObject()->GetVel()[X] = 0.0f;
+			GetObject()->GetVel()[Z] = 0.0f;
+			m_primary_air_direction = m_facing;
+			break;
+			
+		case WALKING_HANG:
+			m_false_wall.active = true;
+			m_false_wall.normal = m_facing;
+			m_false_wall.distance = Mth::DotProduct(m_false_wall.normal, m_pos + m_critical_point_offset);
+			m_false_wall.cancel_height = m_pos[Y] - m_vertical_hang_offset;
+			
+			GetObject()->GetVel()[X] = 0.0f;
+			GetObject()->GetVel()[Z] = 0.0f;
+			m_primary_air_direction = m_facing;
+			break;
+
+		case WALKING_LADDER:
+			GetObject()->GetVel()[X] = 0.0f;
+			GetObject()->GetVel()[Z] = 0.0f;
+			m_primary_air_direction = m_facing;
+			break;
+	}
+	
+	leave_movable_contact_for_air(GetObject()->GetVel(), GetObject()->GetVel()[Y]);
+	
+	set_state(WALKING_AIR);
+	
+	GetObject()->BroadcastEvent(CRCD(0x8687163a, "SkaterJump"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::go_in_air_state (   )
+{
+	setup_collision_cache();
+	
+	// default air event
+	m_frame_event = CRCD(0x439f4704, "Air");
+	
+	// user control of horizontal velocity
+	control_horizontal_vel();
+	
+	// prevent movement into walls
+	adjust_horizonal_vel_for_environment();
+//	if (should_bail_from_frame()) return;
+	
+	// check for head bonking
+	adjust_vertical_vel_for_ceiling();
+	
+	// apply movement and acceleration for this frame
+	m_pos += m_horizontal_vel * m_frame_length;
+	m_pos[Y] += m_vertical_vel * m_frame_length + 0.5f * -s_get_gunslinger_param(CRCD(0xa5e2da58, "gravity")) * Mth::Sqr(m_frame_length);
+	
+	m_vertical_vel += -s_get_gunslinger_param(CRCD(0xa5e2da58, "gravity")) * m_frame_length;
+	
+	// see if we've landed yet
+	check_for_landing(m_frame_start_pos, m_pos);
+	if (m_state != WALKING_AIR /*|| should_bail_from_frame()*/) return;
+	
+	// maybe grab a rail; delay regrabbing of hang rails
+	if (mp_input_component->GetControlPad().m_R1.GetPressed() && !m_ignore_grab_button
+		&& ((m_previous_state != WALKING_HANG && m_previous_state != WALKING_LADDER) || Tmr::ElapsedTime(m_state_timestamp) > s_get_gunslinger_param(CRCD(0xe6e0c0a4, "rehang_delay"))))
+	{
+		if (m_previous_state == WALKING_LADDER)
+		{
+			// can't regrab ladders
+			if (maybe_grab_to_hang(m_frame_start_pos, m_pos))
+			{
+				CFeeler::sClearDefaultCache();
+				return;
+			}
+		}
+		else
+		{
+			if (maybe_grab_to_hang(m_frame_start_pos, m_pos) || maybe_grab_to_ladder(m_frame_start_pos, m_pos))
+			{
+				CFeeler::sClearDefaultCache();
+				return;
+			}
+		}
+	}
+	
+	if (mp_input_component->GetControlPad().m_triangle.GetPressed())
+	{
+		if (maybe_stick_to_rail())
+		{
+			CFeeler::sClearDefaultCache();
+			return;
+		}
+	}
+	
+	// insure that we do not slip through the cracks in the collision geometry which are a side-effect of moving collidable objects
+	Mth::Vector previous_pos = m_pos;
+	if (CCompositeObject* p_inside_object = mp_movable_contact_component->CheckInsideObjects(m_pos, m_frame_start_pos))
+	{
+		MESSAGE("WALKING_AIR, within moving object");
+		
+		m_horizontal_vel.Set();
+		m_vertical_vel = 0.0f;
+		check_for_landing(m_pos, previous_pos);
+	}
+	
+	CFeeler::sClearDefaultCache();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::control_horizontal_vel (   )
+{
+	// We allow user control over the object's in air velocity.  The algorithm is complicated by the fact that the forward velocity of the jump needs
+	// to be accounted for when allowing for velocity adjustment.  It is assumed that the jump direction is the same as the facing.
+	
+	// remove uncontrollable velocity term
+	m_horizontal_vel -= m_uncontrollable_air_horizontal_vel;
+	
+	// forced run still works in the air
+	adjust_control_for_forced_run();
+	
+	// adjust speed by the script set drag factor
+	float adjust_magnitude = m_control_magnitude;
+	
+	// adjust velocity perpendicular to jump direction
+	
+	// direction perpendicular to jump direction
+	Mth::Vector perp_direction(-m_primary_air_direction[Z], 0.0f, m_primary_air_direction[X]);
+	
+	// desired perpendicular velocity
+	float perp_desired_vel = s_get_gunslinger_param(CRCD(0x896c8888, "jump_adjust_speed")) * adjust_magnitude * Mth::DotProduct(m_control_direction, perp_direction);
+	
+	// current perpendicular velocity
+	float perp_vel = Mth::DotProduct(m_horizontal_vel, perp_direction);
+	
+	// exponentially approach desired velocity
+	perp_vel = Mth::Lerp(perp_vel, perp_desired_vel, s_get_gunslinger_param(CRCD(0xf085443b, "jump_accel_factor")) * m_frame_length);
+		
+	// adjust velocity parallel to jump direction
+	
+	// desired parallel velocity
+	float para_desired_vel = s_get_gunslinger_param(CRCD(0x896c8888, "jump_adjust_speed")) * adjust_magnitude * Mth::DotProduct(m_control_direction, m_primary_air_direction);
+	
+	// current parallel velocity
+	float para_vel = Mth::DotProduct(m_horizontal_vel, m_primary_air_direction);
+	
+    // if desired velocity if forward and forward velocity already exceeds adjustment velocity
+	if (para_desired_vel >= 0.0f && para_vel > para_desired_vel)
+	{
+		// do nothing; don't slow down the jump
+	}
+	else
+	{
+		// adjust desired velocity to center around current velocity to insure that our in air stopping ability is not too amazing
+		if (para_desired_vel < 0.0f && para_vel > 0.0f)
+		{
+			para_desired_vel += para_vel;
+		}
+		
+		// expondentially approach desired velocity
+		para_vel = Mth::Lerp(para_vel, para_desired_vel, s_get_gunslinger_param(CRCD(0xf085443b, "jump_accel_factor")) * m_frame_length);
+	}
+		
+	// rebuild horizontal velocity from parallel and perpendicular components
+	m_horizontal_vel = para_vel * m_primary_air_direction + perp_vel * perp_direction;
+	
+	// reinstitute uncontrollable velocity term
+	m_horizontal_vel += m_uncontrollable_air_horizontal_vel;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::adjust_vertical_vel_for_ceiling (   )
+{
+	// If we hit our head, zero vertical velocity
+	
+	// only worry about the ceiling if we're moving upwards
+	if (m_vertical_vel <= 0.0f) return;
+	
+	// look for a collision up through the body to the head
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] += s_get_gunslinger_param(CRCD(0x9ea1974a, "walker_height"));
+	if (!feeler.GetCollision()) return;
+	
+	// zero upward velocity
+	m_vertical_vel = 0.0f;
+	
+	GetObject()->SelfEvent(CRCD(0x6e84acf3, "HitCeiling"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::check_for_landing ( const Mth::Vector& previous_pos, const Mth::Vector& final_pos )
+{
+	// See if our feet have passed through geometry.  If so, snap to it and go to ground state.
+	
+	CFeeler feeler;
+	feeler.m_start = previous_pos;
+	feeler.m_end = final_pos;
+	if (!feeler.GetCollision()) return;
+	
+	// snap to the collision point
+	m_pos = feeler.GetPoint();
+	
+	// zero vertical velocity
+	m_vertical_vel = 0.0f;
+	
+	// change to ground state
+	set_state(WALKING_GROUND);
+	
+	// stash the feeler
+	m_last_ground_feeler = feeler;
+	m_last_ground_feeler_valid = true;
+	
+	// trip any land trigger
+	mp_trigger_component->CheckFeelerForTrigger(TRIGGER_LAND_ON, m_last_ground_feeler);
+//	if (should_bail_from_frame()) return;
+	
+	// setup our ground normal for next frames velocity slope adjustment
+	m_ground_normal = feeler.GetNormal();
+	
+	// check for a moving contact
+	mp_movable_contact_component->CheckForMovableContact(feeler);
+	if (mp_movable_contact_component->HaveContact())
+	{
+		m_horizontal_vel -= mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+		m_horizontal_vel[Y] = 0.0f;
+	}
+	
+	// retain only that velocity which is parallel to our facing and forward
+	if (Mth::DotProduct(m_horizontal_vel, m_facing) > 0.0f)
+	{
+		m_horizontal_vel.ProjectToNormal(m_facing);
+	}
+	else
+	{
+		m_horizontal_vel.Set();
+	}
+	
+	// clear any jump requests
+	mp_input_component->GetControlPad().m_x.ClearRelease();
+	
+	m_frame_event = CRCD(0x57ff2a27, "Land");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::uber_frig (   )
+{
+	// insure that we don't fall to the center of the earth, even if there are holes in the geometry; also, do lighting since we've got the feeler anyway
+	
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += 1.0f;
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] -= FEET(400);
+	
+	if (feeler.GetCollision())
+	{
+		mp_model_component->ApplyLightingFromCollision(feeler);
+		return;
+	}
+	
+	MESSAGE("applying uber frig");
+	
+	// teleport us back to our position at the frame's start; not pretty, but this isn't supposed to be
+	m_pos = m_frame_start_pos;
+	
+	// zero our velocity too
+	m_horizontal_vel.Set();
+	m_vertical_vel = 0.0f;
+	
+	// set our state to ground
+	set_state(WALKING_GROUND);
+			  	
+	m_last_ground_feeler_valid = false;
+	
+	m_ground_normal.Set(0.0f, 1.0f, 0.0f);
+
+	// reset our script state
+	m_frame_event = CRCD(0x57ff2a27, "Land");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::lerp_upright (   )										 
+{
+    if (m_upward[Y] == 1.0f) return;
+	
+	if (m_upward[Y] > 0.999f)
+	{
+		m_upward.Set(0.0f, 1.0f, 0.0f);
+		return;
+	}
+	
+	m_upward = Mth::Lerp(m_upward, Mth::Vector(0.0f, 1.0f, 0.0f), s_get_gunslinger_param(CRCD(0xf22c135, "lerp_upright_rate")) * Tmr::FrameLength());
+	m_upward.Normalize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::check_for_wall_push (   )
+{
+	// ensure we have a forward contact
+	if (!mp_contacts[0].in_collision && !mp_contacts[1].in_collision && !mp_contacts[vNUM_FEELERS - 1].in_collision)
+	{
+		return m_wall_push_test.active = false;
+	}
+	
+	// ensure that control is maxed out
+	if (!m_control_pegged)
+	{
+		return m_wall_push_test.active = false;
+	}
+	
+	if (!m_wall_push_test.active)
+	{
+		// if we're not testing, simply start the test
+		m_wall_push_test.test_start_time = Tmr::GetTime();
+		m_wall_push_test.active = true;
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_jump_low_barrier (   )
+{
+	// For each forward contact in collision, we check to see if any lacks a collision at the maximum jump height.  For any that do, we find the first height
+	// at which they are in contact.  The lowest such height is used at the target jump height.
+	
+	const int p_forward_contact_idxs[] = { 0, 1, vNUM_FEELERS - 1 };
+	
+	// we use the lowest hang height as the maximum autojump barrier height
+	float top_feeler_height = s_get_gunslinger_param(CRCD(0x2c942693, "lowest_hang_height"));
+	float feeler_length = 2.0f * s_get_gunslinger_param(CRCD(0x99978d2b, "feeler_length"));
+	float height_increment = (top_feeler_height - s_get_gunslinger_param(CRCD(0x6da7f696, "feeler_height"))) / vNUM_BARRIER_HEIGHT_FEELERS;
+	
+	CFeeler feeler;
+	
+	// setup collision cache
+	Mth::CBBox bbox(
+		m_pos - Mth::Vector(feeler_length + 1.0f, 0.0f, feeler_length + 1.0f),
+		m_pos + Mth::Vector(feeler_length + 1.0f, top_feeler_height + 1.0f, feeler_length + 1.0f)
+	);
+	Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
+	p_coll_cache->Update(bbox);
+	feeler.SetCache(p_coll_cache);
+	
+	// loop over forward collisions and check to see if the barrier in each of their directions is jumpable
+	bool jumpable = false;
+	for (int i = 0; i < 3; i++)
+	{
+		int n = p_forward_contact_idxs[i];
+		
+		if (!mp_contacts[n].in_collision) continue;
+		
+		// first check to see if the collision normal is not too transverse to the control direction, making a autojump unlikely to succeed
+		if (Mth::DotProduct(mp_contacts[n].normal, m_control_direction) > -cosf(Mth::DegToRad(s_get_gunslinger_param(CRCD(0x78e6a5ec, "barrier_jump_max_angle")))))
+		{
+			mp_contacts[n].jumpable = false;
+			continue;
+		}
+		
+		feeler.m_start = m_pos;
+		feeler.m_start[Y] += top_feeler_height;
+		feeler.m_end = feeler.m_start + feeler_length * calculate_feeler_offset_direction(n);
+		
+		mp_contacts[n].jumpable = !feeler.GetCollision();
+		
+        if (mp_contacts[n].jumpable)
+		{
+			jumpable = true;
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(0, 0, 255, 0);
+			}
+			#endif
+		}
+		else
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(255, 0, 0, 0);
+			}
+			#endif
+		}
+	}
+	
+	// if the barrier is not jumpable
+	if (!jumpable)
+	{
+		// no autojump
+		Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+		return false;
+	}
+	
+	// loop over the jumpable collision directions
+	float lowest_height = m_pos[Y] + top_feeler_height;
+	for (int i = 0; i < 3; i++)
+	{
+		int n = p_forward_contact_idxs[i];
+		
+		if (!mp_contacts[n].in_collision) continue;
+		if (!mp_contacts[n].jumpable) continue;
+		
+		feeler.m_start = m_pos;
+		feeler.m_start[Y] += top_feeler_height;
+		feeler.m_end = feeler.m_start + feeler_length * calculate_feeler_offset_direction(n);
+		
+		// look for the barrier height
+		for (int h = vNUM_BARRIER_HEIGHT_FEELERS - 1; h--; )
+		{
+			feeler.m_start[Y] -= height_increment;
+			feeler.m_end[Y] -= height_increment;
+			if (feeler.GetCollision())
+			{
+				#ifdef __USER_DAN__
+				if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+				{
+					feeler.DebugLine(255, 0, 0, 0);
+				}
+				#endif
+				break;
+			}
+			else
+			{
+				#ifdef __USER_DAN__
+				if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+				{
+					feeler.DebugLine(0, 255, 0, 0);
+				}
+				#endif
+			}
+		}
+		
+		// find the lowest barrier of the jumpable directions
+		if (lowest_height > feeler.m_start[Y])
+		{
+			lowest_height = feeler.m_start[Y];
+		}
+	}
+	
+	Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+	
+	// caluclate the velocity required to clear the barrier
+	float jump_height = lowest_height + height_increment + s_get_gunslinger_param(CRCD(0x72660978, "barrier_jump_min_clearance")) - m_pos[Y];
+	float required_vertical_velocity = sqrtf(2.0f * s_get_gunslinger_param(CRCD(0xa5e2da58, "gravity")) * jump_height);
+	
+	// setup the new walking state
+	
+	m_vertical_vel = required_vertical_velocity;
+	
+	set_state(WALKING_AIR);
+	
+	m_primary_air_direction = m_facing;
+	leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+	
+	m_frame_event = CRCD(0x584cf9e9, "Jump");
+	
+	
+	// stop late jumps after an autojump
+	GetObject()->RemoveEventHandler(CRCD(0x6b9ca247, "JumpRequested"));
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::leave_movable_contact_for_air ( Mth::Vector& horizontal_vel, float& vertical_vel )
+{
+	// use movement from the latest movable contact update call
+	
+	if (mp_movable_contact_component->HaveContact())
+	{
+		// keep track of the horizontal velocity due to our old contact
+		m_uncontrollable_air_horizontal_vel = mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+
+		if (Mth::DotProduct(m_uncontrollable_air_horizontal_vel, horizontal_vel) > 0.0f)
+		{
+			// extra kicker; dangerous as there's no collision detection; without this slight extra movement, when we walk off the front of a movable object,
+			// the object will move back under us before our next frame, and we will clip its edge and land on it
+			m_pos += m_uncontrollable_air_horizontal_vel * m_frame_length;
+		}
+		
+		// add movable contact's velocity into our launch velocity
+		vertical_vel += m_uncontrollable_air_horizontal_vel[Y];
+		m_uncontrollable_air_horizontal_vel[Y] = 0.0f;
+		horizontal_vel += m_uncontrollable_air_horizontal_vel;
+	}
+	else
+	{
+		m_uncontrollable_air_horizontal_vel.Set();
+	}
+	
+	mp_movable_contact_component->LoseAnyContact();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::calculate_anim_wait_facing_drift_parameters ( const Mth::Vector& goal_facing )
+{
+	float initial_angle = atan2f(m_facing[X], m_facing[Z]);
+	float goal_angle = atan2f(goal_facing[X], goal_facing[Z]);
+	
+	m_drift_angle = goal_angle - initial_angle;
+	if (Mth::Abs(m_drift_angle) > Mth::PI)
+	{
+		m_drift_angle -= Mth::Sgn(m_drift_angle) * (2.0f * Mth::PI);
+	}
+	
+	m_drift_goal_facing = goal_facing;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::go_anim_wait_state (   )
+{
+	if (mp_movable_contact_component->UpdateContact(m_pos))
+	{
+		m_offset_due_to_movable_contact += mp_movable_contact_component->GetContact()->GetMovement();
+		if (mp_movable_contact_component->GetContact()->IsRotated())
+		{
+			m_drift_goal_facing = mp_movable_contact_component->GetContact()->GetRotation().Rotate(m_drift_goal_facing);
+			if (m_drift_goal_facing[Y] != 0.0f)
+			{
+				m_drift_goal_facing[Y] = 0.0f;
+				m_drift_goal_facing.Normalize();
+			}
+			
+		}
+	}
+	
+	float start, current, end;
+	mp_animation_component->GetPrimaryAnimTimes(&start, ¤t, &end);
+    float animation_completion_factor = Mth::LinearMap(0.0f, 1.0f, current, start, end);
+	
+	// smoothly drift the position
+	m_pos = m_offset_due_to_movable_contact + Mth::Lerp(m_anim_wait_initial_pos, m_anim_wait_goal_pos, animation_completion_factor);
+	
+	float angle = Mth::Lerp(-m_drift_angle, 0.0f, animation_completion_factor);
+	m_facing = m_drift_goal_facing;
+	m_facing.RotateY(angle);
+	
+	m_frame_event = CRCD(0x4fe6069c, "AnimWait");
+}
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::anim_wait_complete (   )
+{
+	GetObject()->SetPos(m_anim_wait_goal_pos + m_offset_due_to_movable_contact);
+    
+    Mth::Matrix matrix;
+    matrix[Z] = m_drift_goal_facing;
+    matrix[Y].Set(0.0f, 1.0f, 0.0f);
+    matrix[X].Set(m_drift_goal_facing[Z], 0.0f, -m_drift_goal_facing[X]);
+	GetObject()->SetMatrix(matrix);
+	
+    if (mp_anim_wait_complete_callback)
+    {
+        (this->*mp_anim_wait_complete_callback)();
+    }
+}
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_stick_to_rail (   )
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+	
+void CWalkComponent::setup_collision_cache (   )
+{
+	float horizontal_reach = 1.0f + s_get_gunslinger_param(CRCD(0x99978d2b, "feeler_length"));
+	float vertical_height = 1.0f + s_get_gunslinger_param(CRCD(0x9ea1974a, "walker_height"));;
+	float vertical_depth = 1.0f + s_get_gunslinger_param(CRCD(0xaf3e4251, "snap_down_height"));
+	
+	Mth::CBBox bbox(
+		GetObject()->GetPos() - Mth::Vector(horizontal_reach, vertical_depth, horizontal_reach, 0.0f),
+		GetObject()->GetPos() + Mth::Vector(horizontal_reach, vertical_height, horizontal_reach, 0.0f)
+	);
+	
+	mp_collision_cache->Update(bbox);
+	CFeeler::sSetDefaultCache(mp_collision_cache);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::copy_state_into_object (   )
+{
+	// build the object's matrix based on our facing
+	Mth::Matrix matrix;
+	
+	// basically, rotate Z upward to perpendicular with m_upward
+	matrix[X] = Mth::CrossProduct(m_upward, m_facing);
+	matrix[X].Normalize();
+	matrix[Y] = m_upward;
+	matrix[Z] = Mth::CrossProduct(matrix[X], matrix[Y]);
+	matrix[W].Set( 0.0f, 0.0f, 0.0f, 1.0f );
+	
+	GetObject()->SetPos( m_pos );
+	GetObject()->SetMatrix( matrix );
+	GetObject()->SetDisplayMatrix( matrix );
+	
+	// construct the object's velocity
+	GetObject()->SetVel(m_horizontal_vel);
+	GetObject()->GetVel()[Y] = m_vertical_vel;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::get_controller_input (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	Dbg_Assert(mp_camera);
+	
+	// rotate controller direction into camera's frame
+	
+	Mth::Vector camera_forward = -mp_camera->m_matrix[Z];
+	camera_forward[Y] = 0.0f;
+	camera_forward.Normalize();
+	
+	// allow a tolerance range for pressing directly forward
+	float angle = control_pad.m_leftAngle;
+	if (Mth::Abs(angle) < Mth::DegToRad(s_get_gunslinger_param(CRCD(0x4676a268, "forward_tolerance"))))
+	{
+		angle = 0.0f;
+	}
+	
+	float sin_angle = sinf(angle);
+	float cos_angle = cosf(angle);
+	m_control_direction[X] = cos_angle * camera_forward[X] - sin_angle * camera_forward[Z];
+	m_control_direction[Z] = sin_angle * camera_forward[X] + cos_angle * camera_forward[Z];
+	
+	// different control schemes for analog stick and d-pad
+#	if 0
+	if (control_pad.m_leftX == 0.0f && control_pad.m_leftY == 0.0f)
+	{
+		// d-pad control
+		if (control_pad.m_leftLength == 0.0f)
+		{
+			m_control_magnitude = 0.0f;
+			m_control_pegged = false;
+			
+			// don't reset dpad in the air
+			if (m_state != WALKING_AIR)
+			{
+				m_dpad_used_last_frame = false;
+			}
+		}
+		else
+		{
+			if (!m_dpad_used_last_frame)
+			{
+				m_dpad_use_time_stamp = Tmr::GetTime();
+			}
+			m_dpad_used_last_frame = true;
+			
+			if (m_state == WALKING_GROUND)
+			{
+				// slowly ramp up to a full run
+				
+				Tmr::Time elapsed_time = Tmr::ElapsedTime(m_dpad_use_time_stamp);
+				
+				Tmr::Time full_run_dpad_delay = static_cast< Tmr::Time >(s_get_gunslinger_param(CRCD(0x1832588c, "full_run_dpad_delay")));
+				Tmr::Time start_run_dpad_delay = static_cast< Tmr::Time >(s_get_gunslinger_param(CRCD(0x2c386a43, "start_run_dpad_delay")));
+				
+				if (elapsed_time < start_run_dpad_delay)
+				{
+					m_control_magnitude = s_get_gunslinger_param(CRCD(0xc1528f7f, "walk_point"));
+				}
+				else if (elapsed_time < full_run_dpad_delay)
+				{
+					m_control_magnitude = Mth::SmoothMap(
+						s_get_gunslinger_param(CRCD(0xc1528f7f, "walk_point")),
+						1.0f,
+						elapsed_time,
+						start_run_dpad_delay,
+						full_run_dpad_delay
+					);
+				}
+				else
+				{
+					m_control_magnitude = 1.0f;
+				}
+			}
+			else
+			{
+				m_control_magnitude = 1.0f;
+			}
+            m_control_pegged = true;
+		}
+        
+		// damp dpad control directions towards forward when running
+        if (m_state == WALKING_GROUND && Mth::Abs(angle) < Mth::DegToRad(90.0f + 5.0f) && (forced_run() || m_control_magnitude > s_get_gunslinger_param(CRCD(0xc1528f7f, "walk_point"))))
+        {
+			if (forced_run() || m_control_magnitude == 1.0f)
+			{
+				m_control_direction += s_get_gunslinger_param(CRCD(0x3c581621, "dpad_control_damping_factor")) * camera_forward;
+			}
+			else
+			{
+				// smoothly interpolate between damping and no damping
+				m_control_direction += Mth::SmoothMap(0.0f, s_get_gunslinger_param(CRCD(0x3c581621, "dpad_control_damping_factor")), m_control_magnitude, s_get_gunslinger_param(CRCD(0xc1528f7f, "walk_point")), 1.0f)
+					* camera_forward;
+			}
+			m_control_direction.Normalize();
+        }
+	}
+	else
+#	endif
+	{
+		// analog stick control
+		m_control_magnitude = control_pad.GetScaledLeftAnalogStickMagnitude() / 0.85f;
+		if (m_control_magnitude >= 1.0f)
+		{
+			m_control_magnitude = 1.0f;
+			m_control_pegged = true;
+		}
+		else
+		{
+			m_control_pegged = false;
+		}
+	}
+	
+	// during a forward control lock, ignore all input not in the forward direction
+	if (m_state == WALKING_GROUND && m_forward_control_lock)
+	{
+		m_control_magnitude = Mth::ClampMin(m_control_magnitude * Mth::DotProduct(m_control_direction, m_facing), 0.0f);
+		m_control_direction = m_facing;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::adjust_control_for_forced_run (   )
+{
+	if (!forced_run()) return;
+	
+	// if no direction is pressed
+	if (m_control_magnitude == 0.0f)
+	{
+		// run in the direction of our current facing
+		m_control_direction = m_facing;
+	}
+
+	// run full speed
+	m_control_magnitude = 1.0f;
+	m_control_pegged = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWalkComponent::calculate_desired_speed (   )
+{
+	// forced run
+	adjust_control_for_forced_run();
+	
+	if (m_control_magnitude == 0.0f) return 0.0f;
+	
+	float walk_point = s_get_gunslinger_param(CRCD(0xc1528f7f, "walk_point"));
+	if (m_control_magnitude <= walk_point)
+	{
+		m_run_toggle = false;
+		return Mth::LinearMap(0.0f, s_get_gunslinger_param(CRCD(0x79d182ad, "walk_speed")), m_control_magnitude, 0.3f, walk_point);
+	}
+	else
+	{
+		m_run_toggle = true;
+		return Mth::LinearMap(s_get_gunslinger_param(CRCD(0x79d182ad, "walk_speed")), s_get_gunslinger_param(CRCD(0xcc461b87, "run_speed")), m_control_magnitude, walk_point, 1.0f);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CWalkComponent::calculate_feeler_offset_direction ( int contact )
+{
+	float angle = contact * (2.0f * Mth::PI / vNUM_FEELERS);
+	float cos_angle = cosf(angle);
+	float sin_angle = sinf(angle);
+
+	Mth::Vector end_offset_direction;
+	end_offset_direction[X] = cos_angle * m_facing[X] - sin_angle * m_facing[Z];
+	end_offset_direction[Y] = 0.0f;
+	end_offset_direction[Z] = sin_angle * m_facing[X] + cos_angle * m_facing[Z];
+	end_offset_direction[W] = 1.0f;
+	
+	return end_offset_direction;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::determine_stand_pos ( const Mth::Vector& proposed_stand_pos, Mth::Vector& stand_pos, CFeeler& feeler )
+{
+	// upward offset of standing position at maximum standable slope
+	float max_height_adjustment = s_get_gunslinger_param(CRCD(0x6da7f696, "feeler_height")) / s_get_gunslinger_param(CRCD(0xa20c43b7, "push_feeler_length")) * s_get_gunslinger_param(CRCD(0x21dfbe77, "pull_up_offset_forward"));
+	
+	feeler.m_start = proposed_stand_pos;
+	feeler.m_start[Y] += max_height_adjustment;
+	feeler.m_end = proposed_stand_pos;
+	feeler.m_end[Y] -= max_height_adjustment;
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(255, 255, 0, 0);
+	}
+	#endif
+	if (feeler.GetCollision())
+	{
+		stand_pos = feeler.GetPoint();
+		return true;
+	}
+	
+	stand_pos = proposed_stand_pos;
+	return false;
+}
+
+}
+
diff --git a/Code/Gel/Components/HorseCameraComponent.cpp b/Code/Gel/Components/HorseCameraComponent.cpp
new file mode 100644
index 0000000..bc467be
--- /dev/null
+++ b/Code/Gel/Components/HorseCameraComponent.cpp
@@ -0,0 +1,559 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       HorseCameraComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  2/10/3
+//****************************************************************************
+
+#include 
+#include 
+									 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPB(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a ? "true" : "false"); }
+#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
+#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
+#define DUMPM(a) { DUMP4(a[X]); DUMP4(a[Y]); DUMP4(a[Z]); DUMP4(a[W]); }
+#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
+#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
+
+#define vVELOCITY_WEIGHT_DROP_THRESHOLD				MPH_TO_IPS(15.0f)
+// #define vLOCK_ATTRACTOR_VELOCITY_THRESHOLD			MPH_TO_IPS(5.0f)
+#define vLOCK_ATTRACTOR_VELOCITY_THRESHOLD			MPH_TO_IPS(5000000.0f)
+#define vSTATE_CHANGE_DELAY (1.0f)
+
+extern float	screen_angle;
+extern bool		gun_fired;
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CHorseCameraComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CHorseCameraComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CHorseCameraComponent::CHorseCameraComponent() : CBaseComponent()
+{
+	SetType( CRC_HORSECAMERA );
+	
+	m_state				= STATE_NORMAL;
+	m_offset_height		= FEET(5.0f);
+	m_offset_distance	= FEET(12.0f);
+	m_offset_tilt		= 0.0f;
+	m_alignment_rate	= 3.0f;
+
+	m_orientation_matrix.Ident();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CHorseCameraComponent::~CHorseCameraComponent()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 subject_id;
+	
+	if (pParams->ContainsComponentNamed(CRCD(0x431c185, "subject")))
+	{
+		pParams->GetChecksum(CRCD(0x431c185, "subject"), &subject_id, Script::ASSERT);
+		mp_subject = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(subject_id));
+		Dbg_MsgAssert(mp_subject, ("Vehicle camera given subject which is not a composite object"));
+		mp_subject_vehicle_component = static_cast< CHorseComponent* >(mp_subject->GetComponent(CRC_HORSE));
+		Dbg_MsgAssert(mp_subject_vehicle_component, ("HorseCameraComponent given subject which contains no HorseComponent"));
+	}
+		
+	pParams->GetFloat( CRCD( 0x9213625f, "alignment_rate" ), &m_alignment_rate);
+	pParams->GetFloat( CRCD( 0x14849b6d, "offset_height" ), &m_offset_height);
+	pParams->GetFloat( CRCD( 0xbd3d3ca9, "offset_distance" ), &m_offset_distance);
+
+	if( pParams->GetFloat( CRCD( 0x480e14e2, "offset_tilt"), &m_offset_tilt ))
+	{
+		// Convert tilt from degrees to radians.
+		m_offset_tilt = Mth::DegToRad( m_offset_tilt );
+	}
+	
+	reset_camera();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::Finalize( void )
+{
+	mp_input_component = GetInputComponentFromObject( GetObject());
+	Dbg_Assert( mp_input_component );
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::Update()
+{
+	// Decide whether we want to be active right now.
+	if( mp_subject_vehicle_component )
+	{
+		if( !mp_subject_vehicle_component->ShouldUpdateCamera())
+		{
+			return;
+		}
+	}
+
+	m_frame_length = Tmr::FrameLength();
+
+	get_controller_input();
+
+	calculate_attractor_direction();
+	
+	// Due to rounding errors this can sometimes be > |1|, which hoses acosf(), so limit here.
+	float angular_distance = acosf( Mth::Clamp( Mth::DotProduct( m_direction, m_attractor_direction ), -1.0f, 1.0f ));
+	if( angular_distance > Mth::PI / 2.0f )
+	{
+		angular_distance = Mth::PI - angular_distance;
+	}
+	
+	bool	sign = Mth::CrossProduct( m_direction, m_attractor_direction )[Y] > 0.0f;
+	float	step = m_alignment_rate * angular_distance * Tmr::FrameLength();
+	if( step > angular_distance )
+	{
+		step = angular_distance;
+	}
+	
+	m_direction.RotateY(( sign ? 1.0f : -1.0f ) * step );
+	
+	m_direction.Normalize();
+	calculate_orientation_matrix();
+	
+	// Save off the orientation matrix at this point.
+	Mth::Matrix saved_mat = m_orientation_matrix;
+
+	m_orientation_matrix.RotateYLocal( m_lookaround_heading );
+	m_orientation_matrix.RotateXLocal( m_lookaround_tilt - m_offset_tilt );
+
+	// Test the weapon component to generate the 'sticky' targetting behavior.
+	if( mp_best_target )
+	{
+		// Need to get the rider so we can get the base object and then the weapon component (messy).
+		CCompositeObject*	p_obj		= mp_subject_vehicle_component->GetRider();
+		if( p_obj )
+		{
+			CWeaponComponent* p_weapon	= GetWeaponComponentFromObject( p_obj );
+			p_weapon->SetCurrentTarget( mp_best_target );
+			Mth::Vector sight_pos = mp_subject->GetPos() + Mth::Vector( 0.0f, m_offset_height, 0.0f, 0.0f );
+			p_weapon->SetSightPos( sight_pos );
+			p_weapon->SetSightMatrix( m_orientation_matrix );
+
+			float extra_heading_change, extra_tilt_change;
+			p_weapon->ProcessStickyTarget( m_lookaround_heading_delta, m_lookaround_tilt_delta, &extra_heading_change, &extra_tilt_change );
+
+			if(( extra_heading_change != 0.0f ) || ( extra_tilt_change != 0.0f ))
+			{
+				// Reset the matrix to what it was prior to the heading and tilt adjustments.
+				m_orientation_matrix = saved_mat;
+
+				m_lookaround_heading += extra_heading_change;
+				m_orientation_matrix.RotateYLocal( m_lookaround_heading );
+
+				m_lookaround_tilt += extra_tilt_change;
+				m_orientation_matrix.RotateXLocal( m_lookaround_tilt - m_offset_tilt );
+			}
+		}
+	}
+
+	calculate_position();
+
+	ApplyCameraCollisionDetection(
+		m_pos, 
+		m_orientation_matrix, 
+		m_pos - m_offset_distance * m_orientation_matrix[Z], 
+		m_pos - m_offset_distance * m_orientation_matrix[Z], 
+		false, 
+		false
+	);
+	
+	m_pos[W] = 1.0f;
+	m_orientation_matrix[X][W] = 0.0f;
+	m_orientation_matrix[Y][W] = 0.0f;
+	m_orientation_matrix[Z][W] = 0.0f;
+
+	do_reticle();
+
+	GetObject()->SetPos( m_pos );
+	GetObject()->SetMatrix( m_orientation_matrix );
+	GetObject()->SetDisplayMatrix( m_orientation_matrix );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CHorseCameraComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case CRCC(0x469fd, "VehicleCamera_Reset"):
+			RefreshFromStructure(pParams);
+			break;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to C......Component::GetDebugInfo"));
+	
+	p_info->AddChecksum("mp_subject", mp_subject->GetID());
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::get_controller_input( void )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+
+	// Switch the aiming state if triggered.
+	if( control_pad.m_R3.GetTriggered())
+	{
+		control_pad.m_R3.ClearTrigger();
+
+		// Restore screen angle.
+		screen_angle = 72.0f;
+		Nx::CViewportManager::sSetScreenAngle( screen_angle );
+
+		if( m_state == STATE_NORMAL )
+			m_state = STATE_AIMING;
+		else if( m_state == STATE_AIMING )
+			m_state = STATE_NORMAL;
+	}
+
+	// Fire if triggered.
+	gun_fired = false;
+	if( control_pad.m_R1.GetTriggered())
+	{
+		gun_fired = true;
+		control_pad.m_R1.ClearTrigger();
+	}
+
+	// Switch sniper mode if triggered and in aiming mode.
+	if( control_pad.m_up.GetTriggered())
+	{
+		control_pad.m_up.ClearTrigger();
+
+		// Unfortunately, pressing up on the left control stick will also cause m_up to be triggered, so check this
+		// is really the d-pad press.
+		if( control_pad.GetScaledLeftAnalogStickMagnitude() == 0.0f )
+		{
+			if( m_state == STATE_AIMING )
+			{
+				// This would be a good place to put the field of view setting code for the sniper rifle.
+				if( screen_angle == 72.0f )
+				{
+					screen_angle = 25.0f;
+				}
+				else if( screen_angle == 25.0f )
+				{
+					screen_angle = 7.5f;
+				}
+				else if( screen_angle == 7.5f )
+				{
+					screen_angle = 72.0f;
+				}
+				Nx::CViewportManager::sSetScreenAngle( screen_angle );
+			}
+		}
+	}
+
+	// Deal with adjusting aim direction.
+	float tilt_target		= control_pad.m_scaled_rightY;
+	float heading_target	= control_pad.m_scaled_rightX;
+
+	if( m_state == STATE_NORMAL )
+	{
+		// Aiming input should be ignored.
+		tilt_target		= 0.0f;
+		heading_target	= 0.0f;
+	}
+
+	// Modulate with the variable used to damp cursor movement when aiming at a heading_target.
+	heading_target	= heading_target * m_spin_modulator;
+	tilt_target		= tilt_target * m_tilt_modulator;
+		
+	// Get script values.
+	float heading_ka = Script::GetFloat( CRCD( 0xe31bc279, "GunslingerHorseLookaroundHeadingKa" ), Script::ASSERT );
+	float heading_ea = Script::GetFloat( CRCD( 0x7d98eff7, "GunslingerHorseLookaroundHeadingEa" ), Script::ASSERT );
+	float heading_ks = Script::GetFloat( CRCD( 0x10a2b331, "GunslingerHorseLookaroundHeadingKs" ), Script::ASSERT );
+	float heading_es = Script::GetFloat( CRCD( 0x8e219ebf, "GunslingerHorseLookaroundHeadingEs" ), Script::ASSERT );
+
+	// Calculate acceleration.
+	float a = heading_ka * powf( Mth::Abs( heading_target ), heading_ea ) * (( heading_target > 0.0f ) ? 1.0f : ( heading_target < 0.0f ) ? -1.0f : 0.0f );
+
+	// Calculate max speed.
+	float s = heading_ks * powf( Mth::Abs( heading_target ), heading_es ) * (( heading_target > 0.0f ) ? 1.0f : ( heading_target < 0.0f ) ? -1.0f : 0.0f );
+
+	m_lookaround_heading_angular_speed += a;
+
+	if( s == 0.0f )
+	{
+		m_lookaround_heading_angular_speed = 0.0f;
+	}
+	else if((( s > 0.0f ) && ( m_lookaround_heading_angular_speed > s )) || (( s < 0.0f ) && ( m_lookaround_heading_angular_speed < s )))
+	{
+		m_lookaround_heading_angular_speed = s;
+	}
+
+	float lookaround_heading_speed = Script::GetFloat( CRCD( 0x8cf5f0cd, "GunslingerHorseLookaroundHeadingSpeed" ), Script::ASSERT );
+
+	// Control stick left - reticle should move left.
+	m_lookaround_heading_delta	= -m_lookaround_heading_angular_speed * lookaround_heading_speed * m_frame_length;
+	m_lookaround_heading	   += m_lookaround_heading_delta;
+
+	if( m_lookaround_heading > Mth::PI )
+		m_lookaround_heading = m_lookaround_heading - ( 2 * Mth::PI );
+	else if( m_lookaround_heading <= -Mth::PI )
+		m_lookaround_heading = ( 2 * Mth::PI ) + m_lookaround_heading;
+
+	if( Script::GetInteger( CRCD( 0x9edfc7af, "GunslingerInvertAiming" )) == 0 )
+	{
+		// Negate value if vertical aiming invert is not enabled.
+		tilt_target = -tilt_target;
+	}
+
+	// Get script values.
+	float tilt_ka = Script::GetFloat( CRCD( 0x3e8e974f, "GunslingerHorseLookaroundTiltKa" ), Script::ASSERT );
+	float tilt_ea = Script::GetFloat( CRCD( 0xa00dbac1, "GunslingerHorseLookaroundTiltEa" ), Script::ASSERT );
+	float tilt_ks = Script::GetFloat( CRCD( 0xcd37e607, "GunslingerHorseLookaroundTiltKs" ), Script::ASSERT );
+	float tilt_es = Script::GetFloat( CRCD( 0x53b4cb89, "GunslingerHorseLookaroundTiltEs" ), Script::ASSERT );
+
+	// Calculate acceleration.
+	a = tilt_ka * powf( Mth::Abs( tilt_target ), tilt_ea ) * (( tilt_target > 0.0f ) ? 1.0f : ( tilt_target < 0.0f ) ? -1.0f : 0.0f );
+
+	// Calculate max speed.
+	s = tilt_ks * powf( Mth::Abs( tilt_target ), tilt_es ) * (( tilt_target > 0.0f ) ? 1.0f : ( tilt_target < 0.0f ) ? -1.0f : 0.0f );
+
+	m_lookaround_tilt_angular_speed += a;
+
+	if( s == 0.0f )
+	{
+		m_lookaround_tilt_angular_speed = 0.0f;
+	}
+	else if((( s > 0.0f ) && ( m_lookaround_tilt_angular_speed > s )) || (( s < 0.0f ) && ( m_lookaround_tilt_angular_speed < s )))
+	{
+		m_lookaround_tilt_angular_speed = s;
+	}
+
+	float lookaround_tilt_speed = Script::GetFloat( CRCD( 0xebbaa7e8, "GunslingerHorseLookaroundTiltSpeed" ), Script::ASSERT );
+	m_lookaround_tilt_delta	= m_lookaround_tilt_angular_speed * lookaround_tilt_speed * m_frame_length;
+	m_lookaround_tilt	   += m_lookaround_tilt_delta;
+
+	if( m_lookaround_tilt > 1.1f )
+		m_lookaround_tilt = 1.1f;
+	else if( m_lookaround_tilt < -0.85f )
+		m_lookaround_tilt = -0.85f;
+
+	if( m_state == STATE_NORMAL )
+	{
+		// Smoothly move the lookaround values back to zero.
+
+		m_lookaround_tilt		-= ( m_lookaround_tilt * 0.05f );
+		m_lookaround_heading	-= ( m_lookaround_heading * 0.05f );
+	}
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::reset_camera( void )
+{
+	m_state	= STATE_NORMAL;
+
+	m_attractor_direction = -mp_subject->GetMatrix()[Z];
+	m_attractor_direction[Y] = 0.0f;
+	m_attractor_direction.Normalize();
+	
+	calculate_attractor_direction();
+	
+	m_direction = m_attractor_direction;
+	
+	calculate_orientation_matrix();
+	calculate_position();
+	
+	GetObject()->SetPos( m_pos );
+	GetObject()->SetMatrix( m_orientation_matrix );
+	GetObject()->SetDisplayMatrix(m_orientation_matrix);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::calculate_attractor_direction( void )
+{
+	if( m_state == STATE_NORMAL )
+	{
+		Mth::Vector vel_direction = -mp_subject_vehicle_component->GetVel();
+		vel_direction[Y] = 0.0f;
+		float vel = vel_direction.Length();
+		vel_direction.Normalize();
+	
+		float vel_weight = Mth::ClampMax(vel / vVELOCITY_WEIGHT_DROP_THRESHOLD, 1.0f) * Mth::DotProduct(vel_direction, -mp_subject->GetMatrix()[Z]);
+		
+		m_attractor_direction = -mp_subject->GetMatrix()[Z];
+		m_attractor_direction[Y] = 0.0f;
+		m_attractor_direction.Normalize();
+		
+		if (vel_weight > 0.0f)
+		{
+			m_attractor_direction += vel_weight * vel_direction;
+			m_attractor_direction.Normalize();
+		}
+	}
+	else if( m_state == STATE_AIMING )
+	{
+		m_attractor_direction = m_direction;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::calculate_orientation_matrix( void )
+{
+	m_orientation_matrix[Z] = m_direction;
+	m_orientation_matrix[X] = Mth::CrossProduct( Mth::Vector( 0.0f, 1.0f, 0.0f, 0.0f ), m_orientation_matrix[Z] ).Normalize();
+	m_orientation_matrix[Y] = Mth::CrossProduct( m_orientation_matrix[Z], m_orientation_matrix[X] );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::calculate_position( void )
+{
+	Mth::Vector direction = m_orientation_matrix[Z];
+	m_pos = mp_subject->GetPos() + Mth::Vector( m_offset_distance * direction[X], m_offset_height + ( m_offset_distance * direction[Y] ), m_offset_distance * direction[Z], 0.0f );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseCameraComponent::do_reticle( void )
+{
+	// Need to get the rider so we can get the base object and then the weapon component (messy).
+	CCompositeObject*	p_obj		= mp_subject_vehicle_component->GetRider();
+	if( p_obj )
+	{
+		Mth::Vector reticle_min	= m_pos;
+		Mth::Vector reticle_max;
+
+		CWeaponComponent* p_weapon	= GetWeaponComponentFromObject( p_obj );
+
+		p_weapon->SetSightMatrix( m_orientation_matrix );
+		mp_best_target = p_weapon->GetCurrentTarget( reticle_min, &reticle_max );
+
+		if( gun_fired )
+		{
+			p_weapon->Fire();
+		}
+
+		m_spin_modulator = p_weapon->GetSpinModulator();
+		m_tilt_modulator = p_weapon->GetTiltModulator();
+
+		p_weapon->DrawReticle();
+	}
+}
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/Code/Gel/Components/HorseCameraComponent.h b/Code/Gel/Components/HorseCameraComponent.h
new file mode 100644
index 0000000..f2c7148
--- /dev/null
+++ b/Code/Gel/Components/HorseCameraComponent.h
@@ -0,0 +1,106 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       HorseCameraComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_HORSECAMERACOMPONENT_H__
+#define __COMPONENTS_HORSECAMERACOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_HORSECAMERA CRCD(0xf1593757,"HorseCamera")
+#define		GetHorseCameraComponent() ((Obj::CHorseCameraComponent*)GetComponent(CRC_HORSECAMERA))
+#define		GetHorseCameraComponentFromObject(pObj) ((Obj::CHorseCameraComponent*)(pObj)->GetComponent(CRC_HORSECAMERA))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CHorseCameraComponent : public CBaseComponent
+{
+public:
+
+	enum EStateType
+	{
+		STATE_NORMAL,
+		STATE_AIMING,
+	};
+	
+									CHorseCameraComponent();
+    virtual							~CHorseCameraComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	EStateType						GetState( void )	{ return m_state; }
+	CHorseComponent*				GetSubject( void )	{ return mp_subject_vehicle_component; }
+
+private:
+	void							reset_camera( void );
+	void							calculate_attractor_direction( void );
+	void							calculate_orientation_matrix( void );
+	void							calculate_position( void );
+	void							get_controller_input( void );
+	void							do_reticle( void );
+
+private:
+
+	EStateType						m_state;
+
+	float							m_frame_length;
+
+	float							m_spin_modulator;
+	float							m_tilt_modulator;
+
+	float							m_lookaround_heading;
+	float							m_lookaround_heading_delta;
+	float							m_lookaround_heading_angular_speed;
+	float							m_lookaround_tilt;
+	float							m_lookaround_tilt_delta;
+	float							m_lookaround_tilt_angular_speed;
+
+	CCompositeObject*				mp_subject;
+
+	CCompositeObject*				mp_best_target;
+
+	// Peer components.
+	CInputComponent*				mp_input_component;
+	CHorseComponent*				mp_subject_vehicle_component;
+	
+	Mth::Vector		   				m_direction;
+	Mth::Vector		   				m_attractor_direction;
+	
+	Mth::Vector						m_pos;
+	Mth::Matrix						m_orientation_matrix;
+	
+	float							m_offset_height;
+	float							m_offset_distance;
+	float							m_offset_tilt;
+	
+	float							m_alignment_rate;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/HorseComponent.cpp b/Code/Gel/Components/HorseComponent.cpp
new file mode 100644
index 0000000..98b2230
--- /dev/null
+++ b/Code/Gel/Components/HorseComponent.cpp
@@ -0,0 +1,2330 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       HorseComponent.cpp
+//* OWNER:          Dan Nelson
+//* CREATION DATE:  1/31/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+										
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
+#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
+#define DUMPM(a) { DUMP4(a[X]); DUMP4(a[Y]); DUMP4(a[Z]); DUMP4(a[W]); }
+#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
+#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
+
+// THOUGHTS:
+//
+// - BUG: solve negative collision depth issue for rect collisions; must project collision normal into rect plane before calculating depth (i think)
+// - BUG: bad wipeout behavior; IDEAS:
+//   - use only line feelers
+//   - turn off graivty with > 2 contacts
+//   - sleep like a rigidbody
+//   - compare algorithm with rigidbody
+// - Triggers.
+// - Lock motion if very slow and there's no input.
+// - Vehicle camera needs to reports its old position to get sounds' pitch correctly.
+//
+// - two center of masses (collision at zero and suspension) is an issue; actual rotation is done around zero; what sort of poor behavior does this cause
+//   when the car is on two wheels? what way is there to retain stable suspension behavior but use a common center of mass?	perhaps simply damp rotational
+//   impulses due to the suspension; this may help with the "vert, linear-to-angular energy" freak-out issue as well
+// - body-body collisions; treat as boxes
+// - prehaps reduce wheel friction when very near vertical to prevent wall riding
+// - states: ASLEEP, AWAKE (full brake; rigidbody), DRIVEN, DRONE (?)
+// - vertical side rectangle feelers
+// - wheel camber  extracted from model
+// - side slippage when stopped on hills
+
+namespace Obj
+{
+	
+CHorseComponent::SCollisionPoint CHorseComponent::sp_collision_points[4 * (Nx::MAX_NUM_2D_COLLISIONS_REPORTED + 1)];
+								   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CHorseComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CHorseComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CHorseComponent::CHorseComponent() : CBaseComponent()
+{
+	SetType( CRC_HORSE );
+	
+	mp_input_component				= NULL;
+	mp_animation_component			= NULL;
+	mp_movable_contact_component	= NULL;
+
+	mp_collision_cache = Nx::CCollCacheManager::sCreateCollCache();
+	
+	m_draw_debug_lines = 0;
+
+	mp_rider						= NULL;
+
+	m_vel.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+	m_upward.Set( 0.0f, 1.0f, 0.0f, 0.0f );
+	m_horizontal_vel.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+	m_control_direction					= m_horizontal_vel;
+	m_primary_air_direction				= m_horizontal_vel;
+	m_uncontrollable_air_horizontal_vel = m_horizontal_vel;
+
+	m_orientation_matrix.Ident();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CHorseComponent::~CHorseComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_script_drag_factor	= 1.0f;
+
+	// extract constant parameters from struct; parameters not included in the script are left unchanged
+	
+	// toggles through the debug line drawing states
+	if (pParams->ContainsFlag(CRCD(0x935ab858, "debug")))
+	{
+		m_draw_debug_lines = ++m_draw_debug_lines % 3;
+	}
+	else if (pParams->ContainsFlag(CRCD(0x751da48b, "no_debug")))
+	{
+		m_draw_debug_lines = 0;
+	}
+
+	m_pos = GetObject()->GetPos();
+
+	if( pParams->ContainsComponentNamed(CRCD(0x7f261953, "pos" )))
+	{
+		pParams->GetVector(CRCD(0x7f261953, "pos"), &m_pos);
+		GetObject()->SetPos(m_pos);
+	}
+
+	// Grab a pointer to the skeleton.
+	mp_skeleton_component = static_cast< CSkeletonComponent* >(GetObject()->GetComponent(CRC_SKELETON));
+	Dbg_MsgAssert(mp_skeleton_component, ("HorseComponent has no peer skeleton component."));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::Finalize( void )
+{
+	mp_model_component				= GetModelComponentFromObject( GetObject());
+	mp_input_component				= GetInputComponentFromObject( GetObject());
+	mp_animation_component			= GetAnimationComponentFromObject( GetObject());
+	mp_movable_contact_component	= GetMovableContactComponentFromObject( GetObject());
+
+	Dbg_Assert( mp_model_component );
+	Dbg_Assert( mp_input_component );
+	Dbg_Assert( mp_animation_component );
+	Dbg_Assert( mp_movable_contact_component );
+
+	// Enable blending for the Horse by default.
+	mp_animation_component->EnableBlending( true );
+
+	m_display_slerp_matrix = GetObject()->GetMatrix();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::Update( void )
+{
+	// zero the frame event
+	m_last_frame_event = m_frame_event;
+	m_frame_event = 0;
+	
+	// get input
+    get_controller_input();
+	
+	// extract initial state for this frame from the object
+	m_frame_start_pos = m_pos = GetObject()->GetPos();
+	
+	m_horizontal_vel = GetObject()->GetVel();
+	m_horizontal_vel[Y] = 0.0f;
+	m_vertical_vel = GetObject()->GetVel()[Y];
+	
+	// note that m_facing and m_upward will often not be orthogonal, but will always span a plan
+	
+	// generally straight up, but now after a transition from skating
+	m_upward = GetObject()->GetMatrix()[Y];
+	
+	m_facing = GetObject()->GetMatrix()[Z];
+	m_facing[Y] = 0.0f;
+	float length = m_facing.Length();
+	if (length < 0.001f)
+	{
+		// upward facing orientation matrix
+		m_facing = -GetObject()->GetMatrix()[Y];
+		m_facing[Y] = 0.0f;
+		m_facing.Normalize();
+		
+		// since m_upward is now in the same plan as m_facing, push m_upward up a touch
+		m_upward[Y] += 0.01f;
+		m_upward.Normalize();
+	}
+	else
+	{
+		m_facing /= length;
+	}
+	
+	// Set the frame length.
+	m_frame_length = Tmr::FrameLength();
+	
+	// go to our true Y position
+	m_curb_float_height_adjusted = false;
+	m_pos[Y] -= m_curb_float_height;
+	
+	// switch logic based on walking state
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+			go_on_ground_state();
+			break;
+
+		case WALKING_AIR:
+			go_in_air_state();
+			break;
+																	  
+		case WALKING_HOP:
+//			go_hop_state();
+			break;
+																	  
+		case WALKING_HANG:
+//			go_hang_state();
+			break;
+            
+		case WALKING_LADDER:
+//			go_ladder_state();
+            break;
+			
+		case WALKING_ANIMWAIT:
+//			go_anim_wait_state();
+			break;
+	}
+	
+	// the there's no curb to adjust due to, lerp down to zero
+	if (!m_curb_float_height_adjusted)
+	{
+		m_curb_float_height = Mth::Lerp(m_curb_float_height, 0.0f, s_get_param(CRCD(0x9b3388fa, "curb_float_lerp_down_rate")) * m_frame_length);
+	}
+	
+	// adjust back to our curb float Y position
+	m_pos[Y] += m_curb_float_height;
+	
+	// scripts may have restarted us / switched us to skating
+//	if (should_bail_from_frame()) return;
+	
+	// Keep the object from falling through holes in the geometry.
+	if( m_state == WALKING_GROUND || m_state == WALKING_AIR )
+	{
+		uber_frig();
+	}
+	
+	// rotate to upright
+	lerp_upright();
+	
+	// setup the object based on this frame's walking
+	copy_state_into_object();
+	
+	// Position the rider correctly, if one exists.
+	position_rider();
+
+	Dbg_Assert(m_frame_event);
+	GetObject()->SelfEvent(m_frame_event);
+	
+	// set the animation speeds
+/*	switch (m_anim_scale_speed)
+	{
+		case RUNNING:
+			if (m_anim_standard_speed > 0.0f)
+			{
+				mp_animation_component->SetAnimSpeed(m_anim_effective_speed / m_anim_standard_speed, false, false);
+			}
+			break;
+			
+		case HANGMOVE:
+			mp_animation_component->SetAnimSpeed(m_anim_effective_speed / s_get_param(CRCD(0xd77ee881, "hang_move_speed")), false, false);
+			break;
+					
+		case LADDERMOVE:
+			mp_animation_component->SetAnimSpeed(m_anim_effective_speed / s_get_param(CRCD(0xab2db54, "ladder_move_speed")), false, false);
+			break;
+	
+		default:
+			break;
+	}
+*/
+	
+	// camera controls
+	// NOTE: script parameters
+/*
+	switch (m_frame_event)
+	{
+		case CRCC(0xf41aba21, "Hop"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+		
+		case CRCC(0x2d9815c3, "HangMoveLeft"):
+		{
+			Mth::Vector facing = m_facing;
+			facing.RotateY(-0.95f);
+			mp_camera_component->SetOverrides(facing, 0.05f);
+			break;
+		}
+			
+		case CRCC(0x279b1f0b, "HangMoveRight"):
+		{
+			Mth::Vector facing = m_facing;
+			facing.RotateY(0.95f);
+			mp_camera_component->SetOverrides(facing, 0.05f);
+			break;
+		}
+					
+		case CRCC(0x4194ecca, "Hang"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+		
+		case CRCC(0xc84243da, "Ladder"):
+		case CRCC(0xaf5abc82, "LadderMoveUp"):
+		case CRCC(0xfec9dded, "LadderMoveDown"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+					
+		case CRCC(0x4fe6069c, "AnimWait"):
+			if (m_anim_wait_camera_mode == AWC_CURRENT)
+			{
+				mp_camera_component->SetOverrides(m_facing, 0.05f);
+			}
+			else
+			{
+				mp_camera_component->SetOverrides(m_drift_goal_facing, 0.05f);
+			}
+			break;
+	
+		default:
+			mp_camera_component->UnsetOverrides();
+			break;
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent::EMemberFunctionResult CHorseComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Walk_Ground |
+		case CRCC( 0x893213e5, "Walk_Ground" ):
+		{
+			return m_state == WALKING_GROUND ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+		}
+	
+		case CRCC( 0x88bd4924, "Horse_GetSpeedScale" ):
+		{
+			uint32 checksum;
+			if( m_anim_effective_speed < s_get_param( CRCD(0xf3649996, "max_slow_walk_speed")))
+			{
+				checksum = CRCD(0x20c5a299,"HORSE_WALK");
+			}
+			else if( m_anim_effective_speed < s_get_param( CRCD(0x6a5805d8, "max_fast_walk_speed")))
+			{
+				checksum = CRCD(0x8a354e68,"HORSE_TROT");
+			}
+			else if( m_anim_effective_speed < s_get_param( CRCD(0x1c94cc9c, "max_slow_run_speed")))
+			{
+				checksum = CRCD(0x1bee30eb,"HORSE_CANTER");
+			}
+			else
+			{
+				checksum = CRCD(0x2ca2c118,"HORSE_GALLOP");
+			}
+			pScript->GetParams()->AddChecksum( CRCD( 0x92c388f, "SpeedScale" ), checksum );
+			break;
+		}
+
+		// @script | Horse_Jump |
+		case CRCC( 0xae7deda, "Horse_Jump" ):
+		{
+			// Jump strength scales with the length the jump button has been held.
+			jump( Mth::Lerp( s_get_param( CRCD( 0x246d0bf3, "min_jump_factor" )), 1.0f,	Mth::ClampMax( mp_input_component->GetControlPad().m_x.GetPressedTime() / s_get_param( CRCD( 0x12333ebd, "hold_time_for_max_jump" )), 1.0f )));
+			break;
+		}
+
+		// @script | Vehicle_Kick | kicks the vehicle with a force and torque
+		case CRCC(0x93b713a6, "Vehicle_Kick"):
+		{
+			break;
+		}
+
+		// @script : Vehicle_Reset | reset any parameters of the vehicle
+		case CRCC(0xcdcdc05e, "Vehicle_Reset"):
+			RefreshFromStructure(pParams);
+			Finalize();
+			break;
+			
+		// @script : Vehicle_MoveToRestart | teleport the vehicle to the restart node
+		case CRCC(0x4b0b27dd, "Vehicle_MoveToRestart"):
+		{
+			uint32 node_name_checksum;
+			pParams->GetChecksum(NO_NAME, &node_name_checksum, Script::ASSERT);
+			MoveToNode(SkateScript::GetNode(SkateScript::FindNamedNode(node_name_checksum, Script::ASSERT)));
+			break;
+		}
+			
+		// @script : Vehicle_PlaceBeforeCamera | moves the object before the active camera
+		case CRCC(0xc33608e4, "Vehicle_PlaceBeforeCamera"):
+		{
+			Gfx::Camera* camera = Nx::CViewportManager::sGetActiveCamera(0);
+			if (camera)
+			{
+				Mth::Vector& cam_pos = camera->GetPos();
+				Mth::Matrix& cam_mat = camera->GetMatrix();
+
+				m_pos = cam_pos;
+				m_pos += cam_mat[Y] * 12.0f * 12.0f;
+				m_pos -= cam_mat[Z] * 12.0f * 12.0f;
+				GetObject()->SetPos(m_pos);
+				
+				m_orientation_matrix[X] = -cam_mat[X];
+				m_orientation_matrix[Y] = cam_mat[Y];
+				m_orientation_matrix[Z] = -cam_mat[Z];
+				GetObject()->SetMatrix( m_orientation_matrix );
+			}
+			break;
+		}
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CHorseComponent::GetDebugInfo ( Script::CStruct *p_info )
+{
+	Dbg_MsgAssert(p_info, ("NULL p_info sent to CHorseComponent::GetDebugInfo"));
+
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::MoveToNode( Script::CStruct* p_node )
+{
+	// move to the position relative to the given node that a ped car would be at
+	Mth::Vector restart_pos;
+	SkateScript::GetPosition(p_node, &restart_pos);
+	
+	Mth::Vector restart_angles;
+	SkateScript::GetAngles( p_node, &restart_angles );
+	Mth::Matrix restart_matrix;
+	restart_matrix.SetFromAngles( restart_angles );
+	
+	// find ground hieght
+	CFeeler feeler( restart_pos + Mth::Vector( 0.0f, 24.0f, 0.0f, 0.0f ), restart_pos + Mth::Vector( 0.0f, -800.0f, 0.0f, 0.0f ));
+	if (feeler.GetCollision())
+	{
+		restart_pos[Y] = feeler.GetPoint()[Y];
+	}
+	
+	// move the car to the restart position and allow it to settle on its suspension
+	m_pos = restart_pos;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 CHorseComponent::GetAnimation( void )
+{
+	return mp_animation_component->GetCurrentSequence();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CHorseComponent::AcceptRiderMount( CCompositeObject* p_rider )
+{
+	// This is where we want to enable the associated camera.
+	CHorseCameraComponent*	p_cam_component = GetHorseCameraComponentFromObject( GetObject());
+	if( p_cam_component )
+	{
+		p_cam_component->Suspend( false );
+	}
+
+	// Also want to set the horse camera as active.
+	Script::RunScript( CRCD( 0xb08469a2, "MountHorse" ));
+
+	mp_rider = p_rider;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CHorseComponent::ShouldUpdateCamera( void )
+{
+	if( mp_rider )
+		return true;
+	else
+		return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CHorseComponent::AcceptRiderDismount( CCompositeObject* p_rider )
+{
+	// This is where we want to disable the associated camera.
+	CHorseCameraComponent*	p_cam_component = GetHorseCameraComponentFromObject( GetObject());
+	if( p_cam_component )
+	{
+		p_cam_component->Suspend( true );
+	}
+
+	// Also want to set the horse camera as inactive.
+	Script::RunScript( CRCD( 0x400b95f5, "DismountHorse" ));
+
+	mp_rider = NULL;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::draw_debug_rendering (   ) const
+{
+	if (m_draw_debug_lines)
+	{
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::account_for_movable_contact( void )
+{
+/*
+	if (!mp_movable_contact_component->UpdateContact(m_pos))
+		return;
+	
+	m_pos += mp_movable_contact_component->GetContact()->GetMovement();
+	
+	if (mp_movable_contact_component->GetContact()->IsRotated())
+	{
+		m_facing = mp_movable_contact_component->GetContact()->GetRotation().Rotate(m_facing);
+		if (m_facing[Y] != 0.0f)
+		{
+			DUMPF(m_facing[Y]);
+			m_facing[Y] = 0.0f;
+			m_facing.Normalize();
+		}
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::jump( float strength )
+{
+	// switch to air state and give the object an upwards velocity
+	
+	// if we're jumping from the ground, trip the ground's triggers
+	if (m_state == WALKING_GROUND && m_last_ground_feeler_valid)
+	{
+//		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+//		if (should_bail_from_frame()) return;
+	}
+	
+	m_primary_air_direction = m_facing;
+	
+	// Called by script from outside of the component update, so m_vertical_vel is not used.
+	GetObject()->GetVel()[Y] = strength * s_get_param( CRCD( 0x63d62a21, "jump_velocity" ));
+	
+	leave_movable_contact_for_air( GetObject()->GetVel(), GetObject()->GetVel()[Y] );
+	
+	set_state( WALKING_AIR );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::setup_collision_cache( void )
+{
+	float horizontal_reach = 1.0f + s_get_param(CRCD(0x99978d2b, "feeler_length"));
+	float vertical_height = 1.0f + s_get_param(CRCD(0x9ea1974a, "walker_height"));;
+	float vertical_depth = 1.0f + s_get_param(CRCD(0xaf3e4251, "snap_down_height"));
+	
+	Mth::CBBox bbox(
+		GetObject()->GetPos() - Mth::Vector(horizontal_reach, vertical_depth, horizontal_reach, 0.0f),
+		GetObject()->GetPos() + Mth::Vector(horizontal_reach, vertical_height, horizontal_reach, 0.0f)
+	);
+	
+	mp_collision_cache->Update(bbox);
+	CFeeler::sSetDefaultCache(mp_collision_cache);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float CHorseComponent::calculate_desired_speed( void )
+{
+	// forced run
+//	adjust_control_for_forced_run();
+	
+//	float walk_point = s_get_param(CRCD(0xc1528f7f, "walk_point"));
+
+	if( m_control_magnitude > 0.0f )
+	{
+		// Stick is in non-zero position, so adjust target speed.
+		// We may want two sets of these, one for speeding up, one for slowing down.
+		float speed_ka = 1.0f; /* Script::GetFloat( CRCD( 0xcac0c1d4, "GunslingerLookaroundTiltKa" ), Script::ASSERT ); */
+		float speed_ea = 2.0f; /* Script::GetFloat( CRCD( 0x5443ec5a, "GunslingerLookaroundTiltEa" ), Script::ASSERT ); */
+
+		float speed_ks = 1.0f; /* Script::GetFloat( CRCD( 0x3979b09c, "GunslingerLookaroundTiltKs" ), Script::ASSERT ); */
+		float speed_es = 1.0f; /* Script::GetFloat( CRCD( 0xa7fa9d12, "GunslingerLookaroundTiltEs" ), Script::ASSERT ); */
+
+		float target = m_control_direction[X] * m_control_magnitude;
+
+		// Calculate acceleration.
+		float a = speed_ka * powf( Mth::Abs( target ), speed_ea ) * (( target > 0.0f ) ? 1.0f : ( target < 0.0f ) ? -1.0f : 0.0f );
+
+		// Calculate max speed.
+		float s = speed_ks * powf( Mth::Abs( target ), speed_es ) * (( target > 0.0f ) ? 1.0f : ( target < 0.0f ) ? -1.0f : 0.0f );
+
+		m_target_speed_adjustment += a;
+
+		if( s == 0.0f )
+		{
+			m_target_speed_adjustment = 0.0f;
+		}
+		else if((( s > 0.0f ) && ( m_target_speed_adjustment > s )) || (( s < 0.0f ) && ( m_target_speed_adjustment < s )))
+		{
+			m_target_speed_adjustment = s;
+		}
+
+		m_target_speed += m_target_speed_adjustment * m_frame_length;
+
+		// Constrain to [0.0, 1.0] limits.
+		m_target_speed = ( m_target_speed < 0.0f ) ? 0.0f : (( m_target_speed > 1.0f ) ? 1.0f : m_target_speed );
+	}
+
+	return s_get_param( CRCD( 0xcc461b87, "run_speed" )) * m_target_speed;
+
+/*
+	{
+		float walk_point			= s_get_param(CRCD(0xc1528f7f, "walk_point"));
+		float velocity_magnitude	= m_control_direction[X] * m_control_magnitude;
+
+		if( velocity_magnitude <= walk_point )
+		{
+			m_run_toggle = false;
+			return Mth::LinearMap( 0.0f, s_get_param( CRCD( 0x79d182ad, "walk_speed" )), velocity_magnitude, 0.3f, walk_point );
+		}
+		else
+		{
+			m_run_toggle = true;
+			return Mth::LinearMap( s_get_param( CRCD( 0x79d182ad, "walk_speed" )), s_get_param( CRCD( 0xcc461b87, "run_speed" )), velocity_magnitude, walk_point, 1.0f );
+		}
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float CHorseComponent::adjust_desired_speed_for_slope( float desired_speed )
+{
+	// Slow velocity up and down slopes.
+	
+	// skip if there is no appreciable slope
+	if (m_ground_normal[Y] > 0.95f) return desired_speed;
+	
+	// skip if not running
+	if (desired_speed <= s_get_param(CRCD(0x79d182ad, "walk_speed"))) return desired_speed;
+	
+	// calculate a horizontal vector up the slope
+	Mth::Vector up_slope = m_ground_normal;
+	up_slope[Y] = 0.0f;
+	up_slope.Normalize();
+	
+	// horizontal factor of velocity if the velocity were pointing along the slope (instead of along the horizontal)
+	float movement_factor = m_ground_normal[Y];
+	
+	// factor of velocity pointing up the slope
+	float dot = Mth::Abs(Mth::DotProduct(m_facing, up_slope));
+	
+	// scale the up-the-slope element of velocity based on the slope strength
+	return (1.0f - dot) * desired_speed + dot * movement_factor * desired_speed;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::calculate_horizontal_speed_and_facing( float &horizontal_speed )
+{
+	// calculate user's desired speed
+	float desired_speed = calculate_desired_speed();
+	
+	// adjust speed by the script set drag factor
+	desired_speed *= m_script_drag_factor;
+			   	
+	// setup frame's event
+	if (desired_speed <= s_get_param(CRCD(0x74e8227d, "max_stand_speed")))
+	{
+		m_frame_event = CRCD(0x9b46e749, "Stand");
+	}
+	else
+	{
+		m_frame_event = CRCD(0xaf895b3f, "Run");
+	}
+
+	bool special_acceleration = false;
+	
+	// HorseComponent version.
+	// The required rate of turn depends on the x axis component of the control direction.
+//	if( horizontal_speed < s_get_param( CRCD( 0x52582d5b, "max_rotate_in_place_speed" )))
+	if( 1 )
+	{
+		// Low speed rotate to desired orientation with no speed change.
+		m_delta_angle = Mth::DegToRad( s_get_param( CRCD( 0xb557804b, "rotate_in_place_rate" ))) * -m_control_direction[Z] * m_control_magnitude * m_frame_length;
+		if( !m_run_toggle )
+		{
+			m_delta_angle *= s_get_param( CRCD( 0x7b446c98, "walk_rotate_factor" ));
+		}
+			
+		if( m_delta_angle != 0.0f )
+		{
+			float cos_delta_angle	= cosf( m_delta_angle );
+			float sin_delta_angle	= sinf( m_delta_angle );
+			float adjusted_vel		= cos_delta_angle * m_facing[X] + sin_delta_angle * m_facing[Z];
+
+			m_facing[Z]				= -sin_delta_angle * m_facing[X] + cos_delta_angle * m_facing[Z];
+			m_facing[X]				= adjusted_vel;
+			
+			// check for overturn
+//			if (left_turn != (-m_facing[Z] * m_control_direction[X] + m_facing[X] * m_control_direction[Z] < 0.0f))
+//			{
+//				m_facing = m_control_direction;
+//			}
+			
+			// no acceleration until we reach the desired orientation
+//			special_acceleration = true;
+			
+			// setup the event
+			m_frame_event = ( m_delta_angle < 0.0f ) ? CRCD( 0xf28adbfc, "RotateLeft" ) : CRCD( 0x912220f8, "RotateRight" );
+		}
+	}
+	
+	if (special_acceleration) return;
+	
+	// Store desired speed for animation speed scaling.
+	m_anim_effective_speed = desired_speed;
+	
+	// adjust desired speed for slope
+	desired_speed = adjust_desired_speed_for_slope( desired_speed );
+	
+	// linear acceleration; exponential deceleration
+	if (horizontal_speed > desired_speed)
+	{
+		horizontal_speed = Mth::Lerp(horizontal_speed, desired_speed, s_get_param(CRCD(0xacfa4e0c, "decel_factor")) * m_frame_length);
+		
+		if (desired_speed == 0.0f)
+		{
+			if (horizontal_speed > s_get_param(CRCD(0x79d182ad, "walk_speed")))
+			{
+				m_frame_event = CRCD(0x1d537eff, "Skid");
+			}
+			else if (m_last_frame_event == CRCD(0x1d537eff, "Skid") && horizontal_speed > s_get_param(CRCD(0x311d02b2, "stop_skidding_speed")))
+			{
+				m_frame_event = CRCD(0x1d537eff, "Skid");
+			}
+		}
+	}
+	else
+	{
+		if (m_run_toggle)
+		{
+			horizontal_speed += s_get_param(CRCD(0x4f47c998, "run_accel_rate")) * m_frame_length;
+		}
+		else
+		{
+			horizontal_speed += s_get_param(CRCD(0x6590a49b, "walk_accel_rate")) * m_frame_length;
+		}
+		if (horizontal_speed > desired_speed)
+		{
+			horizontal_speed = desired_speed;
+		}
+	}
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CHorseComponent::adjust_horizontal_vel_for_environment( bool wall_push_active )
+{
+	// We send out feeler rays to find nearby walls.  We limit velocity to be flush with the first wall found.  If two or more non-parallel walls
+	// are found, velocity is zeroed.
+	
+	float feeler_length = s_get_param(CRCD(0x99978d2b, "feeler_length"));
+	float feeler_height = s_get_param(CRCD(0x6da7f696, "feeler_height"));
+	
+	CFeeler feeler;
+	
+	bool contact = false;
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		// setup the the feeler
+		
+		if (n == vNUM_FEELERS)
+		{
+			// final feeler is for air state only and is at the feet; solves situations in which the feet impact with vertical surfaces which the
+			// wall feelers are too high to touch
+			if (m_state != WALKING_AIR)
+			{
+				mp_contacts[vNUM_FEELERS].in_collision = false;
+				continue;
+			}
+			
+			feeler.m_start = m_pos;
+			feeler.m_end = m_pos + m_horizontal_vel * m_frame_length;
+			feeler.m_end[Y] += m_vertical_vel * m_frame_length + 0.5f * -s_get_param(CRCD(0xa5e2da58, "gravity")) * Mth::Sqr(m_frame_length);
+		}
+		else
+		{
+			feeler.m_start = m_pos;
+			feeler.m_start[Y] += feeler_height;
+			feeler.m_end = m_pos + feeler_length * calculate_feeler_offset_direction(n);
+			feeler.m_end[Y] += feeler_height;
+		}
+		
+		mp_contacts[n].in_collision = feeler.GetCollision();
+		
+		if (!mp_contacts[n].in_collision)
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(0, 0, 255, 1);
+			}
+			#endif
+			continue;
+		}
+		
+		contact = true;
+		
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+
+		// grab the horizontal normal of the contacted wall
+		mp_contacts[n].normal = feeler.GetNormal();
+		mp_contacts[n].normal[Y] = 0.0f;
+		mp_contacts[n].normal.Normalize();
+		
+		// grab the distance of the contact from the object
+		mp_contacts[n].distance = feeler.GetDist();
+		
+		// if we're on the moving object, don't count its movement when doing collision detection, as the walker's velocity is already measured
+		// relative to its movable contact's
+		if (feeler.IsMovableCollision()
+			&& (!mp_movable_contact_component->HaveContact() || mp_movable_contact_component->GetContact()->GetObject() != feeler.GetMovingObject()))
+		{
+			mp_contacts[n].movement = Mth::DotProduct(feeler.GetMovingObject()->GetVel(), mp_contacts[n].normal);
+		}
+		else
+		{
+			mp_contacts[n].movement = 0.0f;
+		}
+	}
+	
+	// check for wall push
+	if (m_state == WALKING_GROUND)
+	{
+//		if (wall_push_active && check_for_wall_push())
+		if (false)
+		{
+			// if we're wall pushing, we may decide to switch states based on our environment
+			
+			if (Tmr::ElapsedTime(m_wall_push_test.test_start_time) > s_get_param(CRCD(0x928e6775, "hop_delay")))
+			{
+//				if (maybe_climb_up_ladder() || maybe_hop_to_hang() || maybe_jump_low_barrier()) return false;
+			}
+			else if (Tmr::ElapsedTime(m_wall_push_test.test_start_time) > s_get_param(CRCD(0x38d36700, "barrier_jump_delay")))
+			{
+//				if (maybe_climb_up_ladder() || maybe_jump_low_barrier()) return false;
+			}
+		}
+//		else if (mp_input_component->GetControlPad().m_R1.GetPressed() && !m_ignore_grab_button)
+//		{
+//			if (maybe_climb_up_ladder(true)) return false;
+//		}
+	}
+	
+	if (!contact) return false;
+	
+	// push away from walls
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		                
+		if (mp_contacts[n].distance < s_get_param(CRCD(0xa20c43b7, "push_feeler_length")) / feeler_length)
+		{
+			m_pos += s_get_param(CRCD(0x4d16f37d, "push_strength")) * m_frame_length * mp_contacts[n].normal;
+		}
+	}
+	
+	// from here on we ignore collisions we're moving out of
+	contact = false;
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		
+		// don't count collisions we're moving out of
+		if (Mth::DotProduct(mp_contacts[n].normal, m_horizontal_vel) >= mp_contacts[n].movement)
+		{
+			mp_contacts[n].in_collision = false;
+		}
+		else
+		{
+			contact = true;
+		}
+	}
+	if (!contact) return false;
+	
+	// Now we calculate how our movement is effected by our collisions.  The movement must have a non-negative dot product with all collision normals.
+	// The algorithm used should be valid for all convex environments.
+	
+	// if any of the colllision normals are more than right angles to one another, no movement is possible
+	// NOTE: not valid with movable contacts; could cause jerky movement in corners where walls are movable
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		for (int m = n + 1; m < vNUM_FEELERS + 1; m++)
+		{
+			if (!mp_contacts[m].in_collision) continue;
+			if (Mth::DotProduct(mp_contacts[n].normal, mp_contacts[m].normal) <= 0.0f)
+			{
+				m_horizontal_vel.Set();
+				m_anim_effective_speed = Mth::Min(s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+				return true;
+			}
+		}
+	}
+	
+	// direction of proposed movement
+	Mth::Vector movement_direction = m_horizontal_vel;
+	movement_direction.Normalize();
+	
+	Mth::Vector adjusted_vel = m_horizontal_vel;
+	
+	// loop over the contacts (from backward to forward)
+	const int contact_idxs[] = { 7, 4, 3, 5, 2, 6, 1, 0 };
+	for (int i = 0; i < vNUM_FEELERS + 1; i++)
+	{
+		int n = contact_idxs[i];
+		
+		if (!mp_contacts[n].in_collision) continue;
+		
+		// check to see if the movement still violates this constraint
+		float normal_vel = Mth::DotProduct(adjusted_vel, mp_contacts[n].normal);
+		if (normal_vel >= mp_contacts[n].movement) continue;
+		
+		// adjust the movement to the closest direction allowed by this contraint
+		adjusted_vel -= (normal_vel - mp_contacts[n].movement) * mp_contacts[n].normal;
+		
+		// if the mvoement direction no longer points in the direction of the proposed movement, no movement occurs
+		if (Mth::DotProduct(adjusted_vel, m_horizontal_vel) <= 0.0f)
+		{
+			m_horizontal_vel.Set();
+			m_anim_effective_speed = Mth::Min(s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+			return true;
+		}
+	}
+	
+	// insure that the adjusted velocity in the final direction is not larger than the projection of the initial velocity into that direction
+	float adjusted_speed = adjusted_vel.Length();
+	Mth::Vector adjusted_vel_direction = adjusted_vel;
+	adjusted_vel_direction *= 1.0f / adjusted_speed;
+	float projected_vel = Mth::DotProduct(m_horizontal_vel, adjusted_vel_direction);
+	
+	if (adjusted_speed > projected_vel)
+	{
+		adjusted_vel = adjusted_vel_direction * projected_vel;
+	}
+	
+	// only the velocity along the movement direction is retained
+	m_horizontal_vel = adjusted_vel;
+	
+	float final_horiz_vel = m_horizontal_vel.Length();
+	if (m_anim_effective_speed > s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")))
+	{
+		m_anim_effective_speed = final_horiz_vel;
+		m_anim_effective_speed = Mth::Max(s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+	}
+	
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::adjust_facing_for_adjusted_horizontal_vel( void  )
+{
+	// We adjust facing due to adjustment in horizontal velocity due to environment.
+	// Basically, we want to object to turn to face the velocity that the environment has forced upon it.
+	
+	// IDEA: shift to basing turn amount on angle difference and not speed
+	
+	float horizontal_speed = m_horizontal_vel.Length();
+	
+	// the new facing is in the direction of our adjusted velocity
+	Mth::Vector new_facing = m_horizontal_vel;
+	new_facing.Normalize();
+	
+	// Smoothly transition between no wall turning to full wall turning.
+	float turn_ratio;
+	if( horizontal_speed > s_get_param( CRCD( 0xe6c1cd0d, "max_wall_turn_speed_threshold" )))
+	{
+		turn_ratio = s_get_param( CRCD( 0x7a583b9b, "wall_turn_factor" )) * m_frame_length;
+	}
+	else
+	{
+		turn_ratio = Mth::LinearMap(	0.0f,
+										s_get_param( CRCD( 0x7a583b9b, "wall_turn_factor")) * m_frame_length,
+										horizontal_speed,
+//										s_get_param( CRCD( 0x0515a933, "wall_turn_speed_threshold")),
+										0.0f,
+										s_get_param( CRCD( 0xe6c1cd0d, "max_wall_turn_speed_threshold" )));
+	}
+	
+	// Exponentially approach new facing.
+	if( turn_ratio >= 1.0f )
+	{
+		m_facing = new_facing;
+	}
+	else
+	{
+		m_facing = Mth::Lerp( m_facing, new_facing, turn_ratio );
+		m_facing.Normalize();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::go_on_ground_state( void )
+{
+	account_for_movable_contact();
+	
+	setup_collision_cache();
+	
+	// Calculate initial horizontal speed.
+	float horizontal_speed = m_horizontal_vel.Length();
+	
+	calculate_horizontal_speed_and_facing( horizontal_speed );
+	
+	// Calculate this frame's movement.
+	m_horizontal_vel = horizontal_speed * m_facing;
+	
+	// Prevent movement into walls.
+	if( adjust_horizontal_vel_for_environment( true ))
+	{
+		// Turn to face newly adjusted velocity.
+		adjust_facing_for_adjusted_horizontal_vel();
+	}
+	
+	// If we are wall pushing, we may have decided to switch states during adjust_horizontal_vel_for_environment based on our environment.
+//	if (m_state != WALKING_GROUND || should_bail_from_frame())
+	if( m_state != WALKING_GROUND )
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	// Apply movement for this frame.
+	m_pos += m_horizontal_vel * m_frame_length;
+	
+	// Snap up and down curbs and perhaps switch to air.
+	respond_to_ground();
+
+//	if (m_state != WALKING_GROUND || should_bail_from_frame())
+	if( m_state != WALKING_GROUND )
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	adjust_curb_float_height();
+	
+	// Deal with intelligent path following.
+	do_path_following();
+
+	// Ensure that we do not slip through the cracks in the collision geometry which are a side-effect of moving collidable objects.
+	if (CCompositeObject* p_inside_object = mp_movable_contact_component->CheckInsideObjects(m_pos, m_frame_start_pos))
+	{
+		MESSAGE("WALKING_GROUND, within moving object");
+		
+		// allow it to push us forward, causing a bit of a stumble
+		m_horizontal_vel = p_inside_object->GetVel();
+		m_horizontal_vel[Y] = 0.0f;
+		m_vertical_vel = 0.0f;
+		
+		float speed_sqr = m_horizontal_vel.LengthSqr();
+		if (speed_sqr > (10.0f * 10.0f))
+		{
+			m_facing = m_horizontal_vel * (1.0f / sqrtf(speed_sqr));
+		}
+	}
+	
+	CFeeler::sClearDefaultCache();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::go_in_air_state( void )
+{
+	setup_collision_cache();
+	
+	// default air event
+	m_frame_event = CRCD(0x439f4704, "Air");
+	
+	// user control of horizontal velocity
+	control_horizontal_vel();
+	
+	// prevent movement into walls
+	adjust_horizontal_vel_for_environment(false);
+//	if (should_bail_from_frame()) return;
+	
+	// check for head bonking
+	adjust_vertical_vel_for_ceiling();
+	
+	// apply movement and acceleration for this frame
+	m_pos += m_horizontal_vel * m_frame_length;
+	m_pos[Y] += m_vertical_vel * m_frame_length + 0.5f * -s_get_param(CRCD(0xa5e2da58, "gravity")) * Mth::Sqr(m_frame_length);
+	
+	m_vertical_vel += -s_get_param(CRCD(0xa5e2da58, "gravity")) * m_frame_length;
+	
+	// see if we've landed yet
+	check_for_landing(m_frame_start_pos, m_pos);
+//	if (m_state != WALKING_AIR || should_bail_from_frame()) return;
+	if (m_state != WALKING_AIR ) return;
+	
+	// maybe grab a rail; delay regrabbing of hang rails
+//	if (mp_input_component->GetControlPad().m_R1.GetPressed() && !m_ignore_grab_button
+//		&& ((m_previous_state != WALKING_HANG && m_previous_state != WALKING_LADDER) || Tmr::ElapsedTime(m_state_timestamp) > s_get_param(CRCD(0xe6e0c0a4, "rehang_delay"))))
+//	{
+//		if (m_previous_state == WALKING_LADDER)
+//		{
+//			// can't regrab ladders
+//			if (maybe_grab_to_hang(m_frame_start_pos, m_pos))
+//			{
+//				CFeeler::sClearDefaultCache();
+//				return;
+//			}
+//		}
+//		else
+//		{
+//			if (maybe_grab_to_hang(m_frame_start_pos, m_pos) || maybe_grab_to_ladder(m_frame_start_pos, m_pos))
+//			{
+//				CFeeler::sClearDefaultCache();
+//				return;
+//			}
+//		}
+//	}
+	
+//	if (mp_input_component->GetControlPad().m_triangle.GetPressed())
+//	{
+//		if (maybe_stick_to_rail())
+//		{
+//			CFeeler::sClearDefaultCache();
+//			return;
+//		}
+//	}
+	
+	// insure that we do not slip through the cracks in the collision geometry which are a side-effect of moving collidable objects
+	Mth::Vector previous_pos = m_pos;
+	if (CCompositeObject* p_inside_object = mp_movable_contact_component->CheckInsideObjects(m_pos, m_frame_start_pos))
+	{
+		MESSAGE("WALKING_AIR, within moving object");
+		
+		m_horizontal_vel.Set();
+		m_vertical_vel = 0.0f;
+		check_for_landing(m_pos, previous_pos);
+	}
+	
+	CFeeler::sClearDefaultCache();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::get_controller_input( void )
+{
+	// If no rider, ignore input.
+	if( mp_rider == NULL )
+	{
+		// Set it as if we are trying to slow downn to the max.
+		m_control_direction[X]	= -1.0f;
+		m_control_direction[Z]	= 0.0f;
+		m_control_magnitude		= 1.0f;
+		return;
+	}
+
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+//	Dbg_Assert(mp_camera);
+	
+	// rotate controller direction into camera's frame
+//	Mth::Vector camera_forward = -mp_camera->m_matrix[Z];
+//	camera_forward[Y] = 0.0f;
+//	camera_forward.Normalize();
+	
+	// allow a tolerance range for pressing directly forward
+	float angle = control_pad.m_leftAngle;
+	if (Mth::Abs(angle) < Mth::DegToRad(s_get_param(CRCD(0x4676a268, "forward_tolerance"))))
+	{
+		angle = 0.0f;
+	}
+	
+	float sin_angle = sinf(angle);
+	float cos_angle = cosf(angle);
+//	m_control_direction[X] = cos_angle * camera_forward[X] - sin_angle * camera_forward[Z];
+//	m_control_direction[Z] = sin_angle * camera_forward[X] + cos_angle * camera_forward[Z];
+	m_control_direction[X] = cos_angle;
+	m_control_direction[Z] = sin_angle;
+	
+	// different control schemes for analog stick and d-pad
+#	if 0
+	if (control_pad.m_leftX == 0.0f && control_pad.m_leftY == 0.0f)
+	{
+		// d-pad control
+		if (control_pad.m_leftLength == 0.0f)
+		{
+			m_control_magnitude = 0.0f;
+			m_control_pegged = false;
+			
+			// don't reset dpad in the air
+			if (m_state != WALKING_AIR)
+			{
+				m_dpad_used_last_frame = false;
+			}
+		}
+		else
+		{
+			if (!m_dpad_used_last_frame)
+			{
+				m_dpad_use_time_stamp = Tmr::GetTime();
+			}
+			m_dpad_used_last_frame = true;
+			
+			if (m_state == WALKING_GROUND)
+			{
+				// slowly ramp up to a full run
+				
+				Tmr::Time elapsed_time = Tmr::ElapsedTime(m_dpad_use_time_stamp);
+				
+				Tmr::Time full_run_dpad_delay = static_cast< Tmr::Time >(s_get_param(CRCD(0x1832588c, "full_run_dpad_delay")));
+				Tmr::Time start_run_dpad_delay = static_cast< Tmr::Time >(s_get_param(CRCD(0x2c386a43, "start_run_dpad_delay")));
+				
+				if (elapsed_time < start_run_dpad_delay)
+				{
+					m_control_magnitude = s_get_param(CRCD(0xc1528f7f, "walk_point"));
+				}
+				else if (elapsed_time < full_run_dpad_delay)
+				{
+					m_control_magnitude = Mth::SmoothMap(
+						s_get_param(CRCD(0xc1528f7f, "walk_point")),
+						1.0f,
+						elapsed_time,
+						start_run_dpad_delay,
+						full_run_dpad_delay
+					);
+				}
+				else
+				{
+					m_control_magnitude = 1.0f;
+				}
+			}
+			else
+			{
+				m_control_magnitude = 1.0f;
+			}
+            m_control_pegged = true;
+		}
+        
+		// damp dpad control directions towards forward when running
+        if (m_state == WALKING_GROUND && Mth::Abs(angle) < Mth::DegToRad(90.0f + 5.0f) && (forced_run() || m_control_magnitude > s_get_param(CRCD(0xc1528f7f, "walk_point"))))
+        {
+//			if (forced_run() || m_control_magnitude == 1.0f)
+//			{
+//				m_control_direction += s_get_param(CRCD(0x3c581621, "dpad_control_damping_factor")) * camera_forward;
+//			}
+//			else
+//			{
+//				// smoothly interpolate between damping and no damping
+//				m_control_direction += Mth::SmoothMap(0.0f, s_get_param(CRCD(0x3c581621, "dpad_control_damping_factor")), m_control_magnitude, s_get_param(CRCD(0xc1528f7f, "walk_point")), 1.0f)
+//					* camera_forward;
+//			}
+			m_control_direction.Normalize();
+        }
+	}
+	else
+#	endif
+	{
+		// analog stick control
+		m_control_magnitude = control_pad.GetScaledLeftAnalogStickMagnitude() / 0.85f;
+		if (m_control_magnitude >= 1.0f)
+		{
+			m_control_magnitude = 1.0f;
+			m_control_pegged = true;
+		}
+		else
+		{
+			m_control_pegged = false;
+		}
+	}
+	
+	// during a forward control lock, ignore all input not in the forward direction
+	if (m_state == WALKING_GROUND && m_forward_control_lock)
+	{
+		m_control_magnitude = Mth::ClampMin(m_control_magnitude * Mth::DotProduct(m_control_direction, m_facing), 0.0f);
+		m_control_direction = m_facing;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::uber_frig( void )
+{
+	// Ensure that we don't fall to the center of the earth, even if there are holes in the geometry.
+	// Also, do lighting since we've got the feeler anyway.
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += 1.0f;
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] -= FEET(400);
+	
+	if( feeler.GetCollision())
+	{
+		mp_model_component->ApplyLightingFromCollision( feeler );
+		return;
+	}
+	
+	// Teleport us back to our position at the frame's start; not pretty, but this isn't supposed to be.
+	m_pos = m_frame_start_pos;
+	
+	// Zero our velocity too.
+	m_horizontal_vel.Set();
+	m_vertical_vel = 0.0f;
+	
+	// Set our state to ground
+	set_state( WALKING_GROUND );
+			  	
+	m_last_ground_feeler_valid = false;
+	
+	m_ground_normal.Set(0.0f, 1.0f, 0.0f);
+
+	// Reset our script state.
+	m_frame_event = CRCD(0x57ff2a27, "Land");
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::lerp_upright( void )
+{
+//    if (m_upward[Y] == 1.0f) return;
+	
+//	if (m_upward[Y] > 0.999f)
+//	{
+//		m_upward.Set(0.0f, 1.0f, 0.0f);
+//		return;
+//	}
+	
+//	m_upward = Mth::Lerp(m_upward, Mth::Vector(0.0f, 1.0f, 0.0f), s_get_param(CRCD(0xf22c135, "lerp_upright_rate")) * Tmr::FrameLength());
+//	m_upward.Normalize();
+
+	m_upward = m_ground_normal;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::copy_state_into_object( void )
+{
+	// Build the object's matrix based on our facing.
+	Mth::Matrix matrix;
+	
+	// A horse requires special consideration, since it will necessarily tilt to match it's environment along it's long axis,
+	// but will need to remain upright (or very nearly so) along it's shortest axis, since otherwise given it's high center
+	// of gravity, it would topple over when standing sideways on a steep slope.
+	matrix[X]		= Mth::CrossProduct( m_upward, m_facing );
+	matrix[X][Y]	= 0.0f;
+	matrix[X].Normalize();
+
+	matrix[Y]		= m_upward;
+	matrix[Z]		= Mth::CrossProduct( matrix[X], matrix[Y] );
+	matrix[Z].Normalize();
+
+	matrix[Y]		= Mth::CrossProduct( matrix[Z], matrix[X] );
+
+	matrix[W].Set( 0.0f, 0.0f, 0.0f, 1.0f );
+
+	GetObject()->SetPos( m_pos );
+	GetObject()->SetMatrix( matrix );
+
+	// The display matrix is slerped towards the current matrix.
+	Mth::SlerpInterpolator slerper( &m_display_slerp_matrix, &matrix );
+			
+	// Apply the slerping.
+	slerper.getMatrix( &m_display_slerp_matrix, GetTimeAdjustedSlerp( 0.1f, m_frame_length ));
+	
+	GetObject()->SetDisplayMatrix( m_display_slerp_matrix );
+
+	// Construct the object's velocity.
+	GetObject()->SetVel( m_horizontal_vel );
+	GetObject()->GetVel()[Y] = m_vertical_vel;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Mth::Vector CHorseComponent::calculate_feeler_offset_direction( int contact )
+{
+	float angle = contact * (2.0f * Mth::PI / vNUM_FEELERS);
+	float cos_angle = cosf(angle);
+	float sin_angle = sinf(angle);
+
+	Mth::Vector end_offset_direction;
+	end_offset_direction[X] = cos_angle * m_facing[X] - sin_angle * m_facing[Z];
+	end_offset_direction[Y] = 0.0f;
+	end_offset_direction[Z] = sin_angle * m_facing[X] + cos_angle * m_facing[Z];
+	end_offset_direction[W] = 0.0f;
+	
+	return end_offset_direction;
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::adjust_curb_float_height( void )
+{
+	// adjust m_curb_float_height to smooth out moving up stairs
+	
+	// When facing a curb, we smoothly increase m_curb_float_height to the height of the curb.  When we snap up the curb, m_curb_float_height is then
+	// reduced by an amount equal to the snap distance.
+	// When we snap down a curb, m_curb_float_height is increased by the snap distance.  We then drop m_curb_float_height smoothly to zero.
+	
+	// determine appropriate direction to search for a curb
+	Mth::Vector feeler_direction = m_facing;
+	feeler_direction.ProjectToPlane(m_ground_normal);
+	feeler_direction[Y] = Mth::ClampMin(feeler_direction[Y], 0.0f);
+	feeler_direction.Normalize();
+	
+	// look for a curb
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += 0.5f;
+	feeler.m_end = m_pos + s_get_param(CRCD(0x11edcc52, "curb_float_feeler_length")) * feeler_direction;
+    feeler.m_end[Y] += 0.5f;
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 255, 0, 1);
+	}
+	#endif
+	
+	if (feeler.GetCollision())
+	{
+		// grab the distance to the curb
+		float distance_to_curb = feeler.GetDist();
+		
+		// look up from the curb to find the curb height
+		feeler.m_end = feeler.GetPoint() + 0.5f * feeler_direction;
+		feeler.m_start = feeler.m_end;
+		feeler.m_start[Y] = m_pos[Y] + s_get_param(CRCD(0xcee3a3e1, "snap_up_height"));
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(0, 255, 255, 1);
+		}
+		#endif
+		
+		if (feeler.GetCollision())
+		{
+			// calculate the m_curb_float_height we should have based on the curb height and distance
+			float appropriate_curb_float_height = (1.0f - distance_to_curb) * (feeler.GetPoint()[Y] - m_pos[Y]);
+			
+			if (Mth::Abs(m_curb_float_height) < 0.01f && m_control_magnitude == 0.0f && m_horizontal_vel.LengthSqr() < Mth::Sqr(s_get_param(CRCD(0x227d72ee, "min_curb_height_adjust_vel"))))
+			{
+				// don't update the curb height if we're on the ground and standing still; this is mostly to prevent snapping up right after landing a jump
+			}
+			else
+			{
+				// lerp to the appropriate height
+				m_curb_float_height = Mth::Lerp(m_curb_float_height, appropriate_curb_float_height, s_get_param(CRCD(0x856a80d3, "curb_float_lerp_up_rate")) * m_frame_length);
+			}
+			
+			m_curb_float_height_adjusted = true;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::respond_to_ground( void )
+{
+	// Reset ground normal to straight up.
+	m_ground_normal.Set( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Look for the ground below us.  If we find it, snap to it.  If not, go to air state.
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += s_get_param(CRCD(0xcee3a3e1, "snap_up_height"));
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] -= s_get_param(CRCD(0xaf3e4251, "snap_down_height"));
+	
+	if (!feeler.GetCollision())
+	{
+		// no ground
+		
+//		if (m_last_ground_feeler_valid)
+//		{
+//			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
+//			if (should_bail_from_frame()) return;
+//		}
+		
+		// go to air state
+		set_state(WALKING_AIR);
+		m_primary_air_direction = m_facing;
+		leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+		m_frame_event = CRCD(0xabf1f6ac, "WalkOffEdge");
+		return;
+	}
+	
+	float snap_distance = feeler.GetPoint()[Y] - m_pos[Y];
+	
+	// no not send event for very small snaps
+	if (Mth::Abs(snap_distance) > s_get_param(CRCD(0xd3193d8e, "max_unnoticed_ground_snap")))
+	{
+		GetObject()->SelfEvent(snap_distance > 0.0f ? CRCD(0x93fcf3ed, "SnapUpEdge") : CRCD(0x56e21153, "SnapDownEdge"));
+	}
+	
+	// snap position to the ground
+	m_pos[Y] = feeler.GetPoint()[Y];
+	
+	// adjust stair float distance
+	m_curb_float_height = Mth::ClampMin(m_curb_float_height - snap_distance, 0.0f);
+	
+	// see if we've changed sectors
+	if (m_last_ground_feeler.GetSector() != feeler.GetSector())
+	{
+//		if (m_last_ground_feeler_valid)
+//		{
+//			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF, m_last_ground_feeler);
+//			if (should_bail_from_frame()) return;
+//		}
+//		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, feeler);
+//		if (should_bail_from_frame()) return;
+	}
+	
+	// stash the ground feeler so that we can trip the group's triggers at a later time
+	m_last_ground_feeler = feeler;
+	m_last_ground_feeler_valid = true;
+	
+	// set the ground normal for next frame's velocity slope adjustment
+	m_ground_normal = feeler.GetNormal();
+
+	// NOTE: need to repeat this code anywhere we enter the ground state
+	mp_movable_contact_component->CheckForMovableContact(feeler);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::leave_movable_contact_for_air( Mth::Vector& horizontal_vel, float& vertical_vel )
+{
+	// use movement from the latest movable contact update call
+	if (mp_movable_contact_component->HaveContact())
+	{
+		// keep track of the horizontal velocity due to our old contact
+		m_uncontrollable_air_horizontal_vel = mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+
+		if (Mth::DotProduct(m_uncontrollable_air_horizontal_vel, horizontal_vel) > 0.0f)
+		{
+			// extra kicker; dangerous as there's no collision detection; without this slight extra movement, when we walk off the front of a movable object,
+			// the object will move back under us before our next frame, and we will clip its edge and land on it
+			m_pos += m_uncontrollable_air_horizontal_vel * m_frame_length;
+		}
+		
+		// add movable contact's velocity into our launch velocity
+		vertical_vel += m_uncontrollable_air_horizontal_vel[Y];
+		m_uncontrollable_air_horizontal_vel[Y] = 0.0f;
+		horizontal_vel += m_uncontrollable_air_horizontal_vel;
+	}
+	else
+	{
+		m_uncontrollable_air_horizontal_vel.Set();
+	}
+
+	mp_movable_contact_component->LoseAnyContact();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::control_horizontal_vel( void )
+{
+	// We allow user control over the object's in air velocity.  The algorithm is complicated by the fact that the forward velocity of the jump needs
+	// to be accounted for when allowing for velocity adjustment.  It is assumed that the jump direction is the same as the facing.
+	
+	// remove uncontrollable velocity term
+	m_horizontal_vel -= m_uncontrollable_air_horizontal_vel;
+	
+	// forced run still works in the air
+//	adjust_control_for_forced_run();
+	
+	// adjust speed by the script set drag factor
+	float adjust_magnitude = m_control_magnitude * m_script_drag_factor;
+	
+	// adjust velocity perpendicular to jump direction
+	
+	// direction perpendicular to jump direction
+	Mth::Vector perp_direction( -m_primary_air_direction[Z], 0.0f, m_primary_air_direction[X], 0.0f );
+	
+	// desired perpendicular velocity
+	float perp_desired_vel = s_get_param(CRCD(0x896c8888, "jump_adjust_speed")) * adjust_magnitude * Mth::DotProduct(m_control_direction, perp_direction);
+	
+	// current perpendicular velocity
+	float perp_vel = Mth::DotProduct(m_horizontal_vel, perp_direction);
+	
+	// exponentially approach desired velocity
+	perp_vel = Mth::Lerp(perp_vel, perp_desired_vel, s_get_param(CRCD(0xf085443b, "jump_accel_factor")) * m_frame_length);
+		
+	// adjust velocity parallel to jump direction
+	
+	// desired parallel velocity
+	float para_desired_vel = s_get_param(CRCD(0x896c8888, "jump_adjust_speed")) * adjust_magnitude * Mth::DotProduct(m_control_direction, m_primary_air_direction);
+	
+	// current parallel velocity
+	float para_vel = Mth::DotProduct(m_horizontal_vel, m_primary_air_direction);
+	
+    // if desired velocity if forward and forward velocity already exceeds adjustment velocity
+	if (para_desired_vel >= 0.0f && para_vel > para_desired_vel)
+	{
+		// do nothing; don't slow down the jump
+	}
+	else
+	{
+		// adjust desired velocity to center around current velocity to insure that our in air stopping ability is not too amazing
+		if (para_desired_vel < 0.0f && para_vel > 0.0f)
+		{
+			para_desired_vel += para_vel;
+		}
+		
+		// expondentially approach desired velocity
+		para_vel = Mth::Lerp(para_vel, para_desired_vel, s_get_param(CRCD(0xf085443b, "jump_accel_factor")) * m_frame_length);
+	}
+		
+	// rebuild horizontal velocity from parallel and perpendicular components
+	// Dave note - not sure about controlling the horse velocity in midair, so disable for now.
+//	m_horizontal_vel = para_vel * m_primary_air_direction + perp_vel * perp_direction;
+	
+	// reinstitute uncontrollable velocity term
+	m_horizontal_vel += m_uncontrollable_air_horizontal_vel;
+
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::check_for_landing( const Mth::Vector& previous_pos, const Mth::Vector& final_pos )
+{
+	// See if our feet have passed through geometry.  If so, snap to it and go to ground state.
+	
+	CFeeler feeler;
+	feeler.m_start = previous_pos;
+	feeler.m_end = final_pos;
+	if (!feeler.GetCollision()) return;
+	
+	// snap to the collision point
+	m_pos = feeler.GetPoint();
+	
+	// zero vertical velocity
+	m_vertical_vel = 0.0f;
+	
+	// change to ground state
+	set_state(WALKING_GROUND);
+	
+	// stash the feeler
+	m_last_ground_feeler = feeler;
+	m_last_ground_feeler_valid = true;
+	
+	// trip any land trigger
+//	mp_trigger_component->CheckFeelerForTrigger(TRIGGER_LAND_ON, m_last_ground_feeler);
+//	if (should_bail_from_frame()) return;
+	
+	// setup our ground normal for next frames velocity slope adjustment
+	m_ground_normal = feeler.GetNormal();
+	
+	// check for a moving contact
+	mp_movable_contact_component->CheckForMovableContact(feeler);
+	if (mp_movable_contact_component->HaveContact())
+	{
+		m_horizontal_vel -= mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+		m_horizontal_vel[Y] = 0.0f;
+	}
+	
+	// retain only that velocity which is parallel to our facing and forward
+	if (Mth::DotProduct(m_horizontal_vel, m_facing) > 0.0f)
+	{
+		m_horizontal_vel.ProjectToNormal(m_facing);
+	}
+	else
+	{
+		m_horizontal_vel.Set();
+	}
+	
+	// clear any jump requests
+	mp_input_component->GetControlPad().m_x.ClearRelease();
+	
+	m_frame_event = CRCD(0x57ff2a27, "Land");
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::adjust_vertical_vel_for_ceiling( void )
+{
+	// If we hit our head, zero vertical velocity
+	
+	// only worry about the ceiling if we're moving upwards
+	if (m_vertical_vel <= 0.0f) return;
+	
+	// look for a collision up through the body to the head
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] += s_get_param(CRCD(0x9ea1974a, "walker_height"));
+	if (!feeler.GetCollision()) return;
+	
+	// zero upward velocity
+	m_vertical_vel = 0.0f;
+	
+	GetObject()->SelfEvent(CRCD(0x6e84acf3, "HitCeiling"));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::position_rider( void )
+{
+	// HACK: get player proximity checks, triggers, and the like working
+//	CCompositeObject* p_skater = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID( 0 ));
+//	p_skater->SetPos( GetObject()->GetPos());
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool get_collision( Mth::Vector& start, Mth::Vector& direction, float length, float* p_worst_length )
+{
+	CFeeler feeler;
+	
+	feeler.m_start			= start;
+	feeler.m_end			= start + length * direction;
+
+	// No collision as yet.
+	bool collision	= false;
+	*p_worst_length	= length;
+
+	Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 1 );
+
+	if( feeler.GetCollision())
+	{
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < 0.2f )
+		{
+			collision = true;
+			*p_worst_length = length * feeler.GetDist();
+			Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+		}
+	}
+
+	Mth::Vector offset( -direction[Z], 0.0f, direction[X], 0.0f );
+
+	// Try 4 inches to the left.
+	feeler.m_start			+= offset * 4.0f;
+	feeler.m_end			+= offset * 4.0f;
+	if( feeler.GetCollision())
+	{
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < 0.2f )
+		{
+			collision = true;
+			if(( length * feeler.GetDist()) < *p_worst_length )
+			{
+				*p_worst_length = length * feeler.GetDist();
+				Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+			}
+		}
+	}
+
+	// Try 8 inches to the left.
+	feeler.m_start			+= offset * 4.0f;
+	feeler.m_end			+= offset * 4.0f;
+	if( feeler.GetCollision())
+	{
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < 0.2f )
+		{
+			collision = true;
+			if(( length * feeler.GetDist()) < *p_worst_length )
+			{
+				*p_worst_length = length * feeler.GetDist();
+				Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+			}
+		}
+	}
+
+	// Try 12 inches to the left.
+	feeler.m_start			+= offset * 4.0f;
+	feeler.m_end			+= offset * 4.0f;
+	if( feeler.GetCollision())
+	{
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < 0.2f )
+		{
+			collision = true;
+			if(( length * feeler.GetDist()) < *p_worst_length )
+			{
+				*p_worst_length = length * feeler.GetDist();
+				Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+			}
+		}
+	}
+
+	// Try 4 inches to the right.
+	feeler.m_start			-= offset * 16.0f;
+	feeler.m_end			-= offset * 16.0f;
+	if( feeler.GetCollision())
+	{
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < 0.2f )
+		{
+			collision = true;
+			if(( length * feeler.GetDist()) < *p_worst_length )
+			{
+				*p_worst_length = length * feeler.GetDist();
+				Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+			}
+		}
+	}
+
+	// Try 8 inches to the right.
+	feeler.m_start			-= offset * 4.0f;
+	feeler.m_end			-= offset * 4.0f;
+	if( feeler.GetCollision())
+	{
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < 0.2f )
+		{
+			collision = true;
+			if(( length * feeler.GetDist()) < *p_worst_length )
+			{
+				*p_worst_length = length * feeler.GetDist();
+				Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+			}
+		}
+	}
+
+	// Try 12 inches to the right.
+	feeler.m_start			-= offset * 4.0f;
+	feeler.m_end			-= offset * 4.0f;
+	if( feeler.GetCollision())
+	{
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < 0.2f )
+		{
+			collision = true;
+			if(( length * feeler.GetDist()) < *p_worst_length )
+			{
+				*p_worst_length = length * feeler.GetDist();
+				Gfx::AddDebugLine( feeler.m_start, feeler.m_end, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+			}
+		}
+	}
+
+	return collision;
+}
+
+
+
+
+
+
+
+const float AVOID_COLLISION_SCORE			= 1000.0f;
+const float	TURN_ANGLE_SCORE_MULT			= 1.0f;
+const float DISTANCE_PERCENTAGE_SCORE_MULT	= 0.1f;
+
+const float	PANIC_SCORE						= 900.0f;
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::do_path_following( void )
+{
+	// Get the horse camera component for this horse. Messy.
+	Obj::CHorseCameraComponent* p_cam_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_HORSECAMERA ));
+	while( p_cam_component )
+	{
+		if( p_cam_component->GetSubject() == this )
+		{
+			break;
+		}
+		p_cam_component = static_cast( p_cam_component->GetNextSameType());
+	}
+
+	if( p_cam_component )
+	{
+		if( p_cam_component->GetState() == CHorseCameraComponent::STATE_NORMAL )
+		{
+			// No path following in normal state. Cleanup if we were doing path following.
+			if( mp_nav_nodes )
+			{
+				cleanup_node_based_path_following();
+			}
+			return;
+		}
+
+		// Did we turn this frame? If so, all bets are off...
+		if( m_delta_angle != 0.0f )
+		{
+			// Cleanup if we were doing path following.
+			if( mp_nav_nodes )
+			{
+				cleanup_node_based_path_following();
+			}
+			return;
+		}
+
+		// Are we path following? In which case, follow the path.
+		if( mp_nav_nodes )
+		{
+			do_node_based_path_following();
+			return;
+		}
+
+		// See if we are going to hit something soon if we stay on this course.
+		float feeler_height = 36.0f;
+		float feeler_length	= 50.0f * 12.0f;
+
+		float		angle		= 0.0f;
+		float		cos_angle	= cosf( angle );
+		float		sin_angle	= sinf( angle );
+		float		worst_length;
+
+		Mth::Vector	feeler_start;
+		Mth::Vector	feeler_end;
+		Mth::Vector	end_offset_direction;
+
+		end_offset_direction[X] = cos_angle * m_facing[X] - sin_angle * m_facing[Z];
+		end_offset_direction[Y] = 0.0f;
+		end_offset_direction[Z] = sin_angle * m_facing[X] + cos_angle * m_facing[Z];
+		end_offset_direction[W] = 0.0f;
+
+		feeler_start			= Mth::Vector( m_pos[X], m_pos[Y] + feeler_height, m_pos[Z], m_pos[W] );
+		feeler_end				= feeler_start + feeler_length * end_offset_direction;
+		
+		if( get_collision( feeler_start, end_offset_direction, feeler_length, &worst_length ))
+//		if( feeler.GetCollision())
+		{
+			float dist = worst_length;
+
+			// We need to figure out if turning a few degrees will enable us to obtain a clear path for
+			// a significantly further distance.
+			feeler_length *= 2.0f;
+
+			float	best_score					= 0.0f;
+			float	best_angle					= Mth::DegToRad( 90.0f );
+
+			// Scan through angles from +9 degrees to -9 degrees in 3 degree steps.
+			float step_angle = Mth::DegToRad( -9.0f );
+			while( step_angle <= Mth::DegToRad( 9.0f ))
+			{
+				float score = 0.0f;
+
+				cos_angle	= cosf( step_angle );
+				sin_angle	= sinf( step_angle );
+
+				end_offset_direction[X] = cos_angle * m_facing[X] - sin_angle * m_facing[Z];
+				end_offset_direction[Y] = 0.0f;
+				end_offset_direction[Z] = sin_angle * m_facing[X] + cos_angle * m_facing[Z];
+				end_offset_direction[W] = 0.0f;
+
+				feeler_end				= feeler_start + feeler_length * end_offset_direction;
+
+				// Score for avoiding collision.
+				bool collision = get_collision( feeler_start, end_offset_direction, feeler_length, &worst_length );
+
+				if( !collision )
+				{
+					score += AVOID_COLLISION_SCORE;
+				}
+
+				// Score for minimal turn distance.
+				score -= Mth::Abs( step_angle ) * TURN_ANGLE_SCORE_MULT;
+
+				if( collision )
+				{
+					// Score for larger distances, use the percentage of this distance over the original collision distance.
+					float this_dist	= worst_length;
+					if( this_dist > dist )
+					{
+						float pc = ( this_dist / dist ) * 100.0f;
+						score += pc * DISTANCE_PERCENTAGE_SCORE_MULT;
+					}
+				}
+
+				// Score for being a turn in the same direction as last time.
+
+				// Is this the best score yet?
+				if( score > best_score )
+				{
+					best_score = score;
+					best_angle = step_angle;
+				}
+				step_angle += Mth::DegToRad( 3.0f );
+			}
+
+			// Check for being in a potentially nasty situation.
+			if( best_score < PANIC_SCORE )
+			{
+				// Increment panic counter, give a small chance to fix itself.
+				++m_path_follow_panic_counter;
+				if( m_path_follow_panic_counter > 0 )
+				{
+					m_path_follow_panic_counter = 0;
+					switch_to_node_based_path_following();
+					return;
+				}
+			}
+
+			// Test - Immediately snap the direction to the best alternative.
+			if( best_score > 0.0f )
+			{
+				cos_angle	= cosf( best_angle );
+				sin_angle	= sinf( best_angle );
+
+				end_offset_direction[X] = cos_angle * m_facing[X] - sin_angle * m_facing[Z];
+				end_offset_direction[Z] = sin_angle * m_facing[X] + cos_angle * m_facing[Z];
+
+				printf( "Changing heading by %f degrees\n", best_angle );
+
+				m_facing[X]				= end_offset_direction[X];
+				m_facing[Z]				= end_offset_direction[Z];
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::cleanup_node_based_path_following( void )
+{
+	delete [] mp_nav_nodes;
+	mp_nav_nodes = NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::switch_to_node_based_path_following( void )
+{
+	// Try various distances, the further the better.
+	float target_distance = FEET_TO_INCHES( 500.0f );
+
+	while( target_distance > FEET_TO_INCHES( 50.0f ))
+	{
+		// Generate a distant target position.
+		Mth::Vector start_pos	= m_pos + Mth::Vector( 0.0f, 60.0f, 0.0f, 0.0f );
+		Mth::Vector target_pos	= start_pos + ( m_facing * target_distance );
+
+		CFeeler feeler;
+		feeler.m_start	= target_pos + Mth::Vector( 0.0f, 12000.0f, 0.0f, 0.0f );
+		feeler.m_end	= target_pos - Mth::Vector( 0.0f, 12000.0f, 0.0f, 0.0f );
+
+		if( feeler.GetCollision())
+		{
+			// Adjust the target position to be 5 feet above ground level.
+			target_pos = feeler.GetPoint() + Mth::Vector(  0.0f, 60.0f, 0.0f, 0.0f );
+		}
+		else
+		{
+			// Hmm, maybe should try different distances here.
+			// For now, Just go with what we started with.
+		}
+
+		CNavNode* p_target_node = CalculateNodePath( start_pos, target_pos );
+
+		if( p_target_node == NULL )
+		{
+			// Try with a shorter distance.
+			target_distance -= FEET_TO_INCHES( 50.0f );
+			continue;
+		}
+		
+		// A path was found, so set up the state accordingly. First count how many nodes on the path.
+		CNavNode*	p_node_counter	= p_target_node;
+		int			num_nodes		= 1;
+		while( p_node_counter->GetAStarNode()->mp_parent != NULL )
+		{
+			++num_nodes;
+			p_node_counter = p_node_counter->GetAStarNode()->mp_parent;
+		}
+
+		// Allocate memory for the path.
+		if( mp_nav_nodes )
+		{
+			delete [] mp_nav_nodes;
+		}
+		mp_nav_nodes = new CNavNode*[num_nodes];
+
+		// Fill in the array.
+		m_nav_node_index = 0;
+		while( p_target_node )
+		{
+			mp_nav_nodes[m_nav_node_index++] = p_target_node;
+			p_target_node = p_target_node->GetAStarNode()->mp_parent;
+		}
+		--m_nav_node_index;
+
+		// mp_nav_nodes[m_nav_node_index] now points to the first node we need to visit.
+		break;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CHorseComponent::do_node_based_path_following( void )
+{
+	// Are we within a reasonable distance of our current node?
+	Mth::Vector target_node_pos = mp_nav_nodes[m_nav_node_index]->GetPos() + Mth::Vector( 0.0f, 36.0f, 0.0f, 0.0f );
+	float dist = Mth::Distance( m_pos, target_node_pos );
+	if( dist < ( 12.0f * 6.0f ))
+	{
+		// Yes we are. Exit if no more nodes.
+		if( m_nav_node_index == 0 )
+		{
+			cleanup_node_based_path_following();
+			return;
+		}
+
+		// Switch to the next node.
+		--m_nav_node_index;
+	}
+
+
+	for( int l = m_nav_node_index; l >= 0; --l )
+	{
+		Gfx::AddDebugStar( mp_nav_nodes[l]->GetPos(), 12.0f, MAKE_RGB( 255, 200, 0 ), 1 );
+
+		if( l > 0 )
+		{
+			Gfx::AddDebugLine( mp_nav_nodes[l]->GetPos(), mp_nav_nodes[l - 1]->GetPos(), MAKE_RGB( 255, 255, 255 ), MAKE_RGB( 255, 255, 255 ), 1 );
+		}
+	}
+
+	// Head to the next node.
+	Mth::Vector heading = mp_nav_nodes[m_nav_node_index]->GetPos() - m_pos;
+	heading[Y] = 0.0f;
+	heading[W] = 0.0f;
+	heading.Normalize();
+
+	m_facing[X]	= heading[X];
+	m_facing[Z]	= heading[Z];
+}
+
+
+}
diff --git a/Code/Gel/Components/HorseComponent.h b/Code/Gel/Components/HorseComponent.h
new file mode 100644
index 0000000..1941064
--- /dev/null
+++ b/Code/Gel/Components/HorseComponent.h
@@ -0,0 +1,439 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       HorseComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_HORSECOMPONENT_H__
+#define __COMPONENTS_HORSECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#define		CRC_HORSE CRCD(0x9d65d0e7,"Horse")
+#define		GetHorseComponent() ((Obj::CHorseComponent*)GetComponent(CRC_HORSE))
+#define		GetHorseComponentFromObject(pObj) ((Obj::CHorseComponent*)(pObj)->GetComponent(CRC_HORSE))
+		 
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CInputComponent;
+
+#define vVP_GRAVITATIONAL_ACCELERATION								(386.4f)
+#define vVP_MAX_NUM_GEARS											(6)
+#define vVP_NUM_COLLIDERS											(2)
+
+class CHorseComponent : public CBaseComponent
+{
+	enum EStateType
+	{
+		WALKING_GROUND,
+		WALKING_AIR,
+		WALKING_HOP,
+		WALKING_HANG,
+		WALKING_LADDER,
+		WALKING_ANIMWAIT
+	};
+
+	enum EAnimScaleSpeed
+	{
+		OFF,
+		RUNNING,
+        HANGMOVE,
+		LADDERMOVE
+	};
+
+	// inorder to interface with models and skeletons, the number of wheels must be hardcoded to four
+	enum { vVP_NUM_WHEELS = 4 };
+	
+	// HACK for control
+	struct SControls {
+		// steering; between -1.0 and 1.0
+		float steering;
+		
+		// Spurring the horse on, or reining it in, values in range [-1.0, 1.0].
+		float spur_rein;
+
+		// throttle
+		bool throttle;
+		
+		// brake
+		bool brake;
+		
+		// handbrake
+		bool handbrake;
+		
+		// reverse
+		bool reverse;
+	};
+	
+	
+	struct SCollisionPoint
+	{
+		// world position
+		Mth::Vector pos;
+		
+		// impact normal
+		Mth::Vector normal;
+		
+		// depth of collision as defined as the dot between the displacement from the nearest collider corner and the collision normal
+		float depth;
+		
+		// normal impulse accumulator; used to set the maximum friction
+		float normal_impulse;
+		
+		bool line;
+	};
+	
+	struct SCollider
+	{
+		// the rectangle defining the collider in body space
+		Mth::Rectangle body;
+		
+		// that rectangle transformed into world space
+		Mth::Rectangle world;
+		Mth::Vector first_edge_direction_world;
+		Mth::Vector second_edge_direction_world;
+		
+		// a few precomputable values
+		float first_edge_length;
+		float second_edge_length;
+	};
+	
+public:
+    CHorseComponent();
+    virtual ~CHorseComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize (   );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							MoveToNode ( Script::CStruct* p_node );
+	
+	const Mth::Vector&				GetPos( void )						{ return GetObject()->GetPos(); }
+	const Mth::Vector&				GetVel( void )				const	{ return m_vel; }
+
+	Mth::Matrix&					GetMatrix( void )					{ return GetObject()->GetMatrix(); }
+	Mth::Matrix&					GetDisplayMatrix( void )			{ return GetObject()->GetDisplayMatrix(); }
+
+	bool							AcceptRiderMount( CCompositeObject*	p_rider );
+	bool							AcceptRiderDismount( CCompositeObject*	p_rider );
+	bool							ShouldUpdateCamera( void );
+	CCompositeObject*				GetRider( void )					{ return mp_rider; }
+
+	uint32							GetAnimation( void );
+
+private:
+	
+    void							draw_debug_rendering (   ) const;
+	
+private:
+	
+	Obj::CNavNode**					mp_nav_nodes;			// List of pointers to navigation nodes if following a path.
+	int								m_nav_node_index;		// Current index within the list (moves to zero as path progesses).
+
+	// dynamic state variables
+	
+	// dependent state variables
+	
+	// angular orientation in 3x3 matrix form
+	Mth::Matrix m_orientation_matrix;
+	
+	// linear velocity
+	Mth::Vector m_vel;
+	
+	// rotational velocity
+	Mth::Vector m_rotvel;
+	
+	// accumulators
+	
+	
+	// length of current frame
+	float m_time_step;
+	
+	// constant characteristics
+	
+	
+	// subelements
+	
+	// input state
+//	SControls m_controls;
+	
+	// collision cache
+	Nx::CCollCache m_collision_cache;
+	
+	
+	// shared objects
+	
+	// shared collision point array
+	static SCollisionPoint sp_collision_points[4 * (Nx::MAX_NUM_2D_COLLISIONS_REPORTED + 1)];
+	
+	CCompositeObject*				mp_rider;
+
+	// peer components
+	CSkeletonComponent*				mp_skeleton_component;
+	CInputComponent*				mp_input_component;
+	CModelComponent*				mp_model_component;
+	CAnimationComponent*			mp_animation_component;
+	CMovableContactComponent*		mp_movable_contact_component;
+	
+	// debug
+	int m_draw_debug_lines;
+
+	// Stuff from CWalkComponent.
+
+	static float					s_get_param ( uint32 checksum );
+
+
+	enum
+	{
+		vNUM_FEELERS = 7,
+		vNUM_BARRIER_HEIGHT_FEELERS = 7
+	};
+
+	struct SContact
+	{
+		// If this contact is in collision with the environment.
+		bool						in_collision;
+		
+		// The horizontal normal of the collided geometry.
+		Mth::Vector					normal;
+		
+		// Distance from the collision as a factor of the feeler length.
+		float						distance;
+		
+		// If the collision in this direction is determined to be low enough to jump.
+		bool						jumpable;
+		
+		// Movement of the object we're in contact with (not accounting for its rotation) along its horizontal normal.
+		float						movement;
+		
+	// The walker looks around him in vNUM_FEELERS directions and stores the results here.  The final contact is felt for along the movement of
+	// the origin, and is only used while in the air.
+	} mp_contacts [ vNUM_FEELERS + 1 ];
+
+	/////////////////////////////////
+	// WALKING_AIR state variables
+	
+	// The forward horizontal direction of the jump which, when coming out of skating or because of in-air velocity control, diverges from the direction of velocity
+	Mth::Vector						m_primary_air_direction;
+	
+	// This term of our horizontal velocity is due to leaping from a movable contact, and is not adjustable via in-air control.
+	Mth::Vector						m_uncontrollable_air_horizontal_vel;
+
+	void							get_controller_input( void );
+	void							go_on_ground_state( void );
+	void							go_in_air_state( void );
+	void							lerp_upright( void );
+	void							uber_frig( void );
+	void							account_for_movable_contact( void );
+	void							setup_collision_cache( void );
+	float							calculate_desired_speed( void );
+	float							adjust_desired_speed_for_slope( float desired_speed );
+	bool							adjust_horizontal_vel_for_environment( bool wall_push_active );
+	void							copy_state_into_object( void );
+	void							adjust_facing_for_adjusted_horizontal_vel( void );
+	void							calculate_horizontal_speed_and_facing( float &horizontal_speed );
+	Mth::Vector						calculate_feeler_offset_direction ( int contact );
+	void							adjust_curb_float_height( void );
+	bool							forced_run( void );
+	void							respond_to_ground( void );
+	void							leave_movable_contact_for_air( Mth::Vector& horizontal_vel, float& vertical_vel );
+	void							jump( float strength );
+	void							set_state( EStateType state );
+	void							control_horizontal_vel( void );
+	void							check_for_landing( const Mth::Vector& previous_pos, const Mth::Vector& final_pos );
+	void							adjust_vertical_vel_for_ceiling( void );
+	void							position_rider( void );
+
+	void							do_path_following( void );
+	void							switch_to_node_based_path_following( void );
+	void							cleanup_node_based_path_following( void );
+	void							do_node_based_path_following( void );
+
+	int								m_path_follow_panic_counter;
+
+	// Accumulates position offset due to a movable contact during an animation wait.
+	Mth::Vector						m_offset_due_to_movable_contact;
+
+	EStateType						m_state;
+
+	// Time stamp of when our state last changed.
+	Tmr::Time						m_state_timestamp;
+	
+	// State we changed from when m_state_timestamp was last set.
+	EStateType						m_previous_state;
+
+	// Certain things such as rotation rate and camera lerp are increased when this is set.
+	bool							m_run_toggle;
+
+	struct SWallPushTest
+	{
+		// Whether we currently testing.
+		bool						active;
+		
+		// Time at which this test began.
+		Tmr::Time					test_start_time;
+		
+	// Data used to detect when a player is pushing into a wall in order to climb it.
+	}								m_wall_push_test;
+
+	// Normal of the ground on which we are standing.  Only meaningful when on the ground.  Set the previous frame.
+	Mth::Vector						m_ground_normal;
+
+	// Used to slerp the display matrix towards the actual matrix (can't use CCompositeObject::m_display_matrix since that is reset every frame).
+	Mth::Matrix						m_display_slerp_matrix;
+
+	// The collision cache used.
+	Nx::CCollCache*					mp_collision_cache;
+
+	// When doing anim speed scaling when running, this is set to the run speed which corresponds to the current animation playing at standard speed.
+	float							m_anim_standard_speed;
+	
+	// A copy of the latest feeler which contacted the groud under us.
+	CFeeler							m_last_ground_feeler;
+	bool							m_last_ground_feeler_valid;
+
+	// The target fraction of maximum speed [0.0, 1.0] as modified by let analog stick up down movement.
+	float							m_target_speed;
+	float							m_target_speed_adjustment;
+
+	// Extracted from the object at frame start and back into the object at frame end.
+	Mth::Vector						m_pos;
+	Mth::Vector						m_horizontal_vel;
+	float							m_vertical_vel;
+	Mth::Vector						m_facing;
+	Mth::Vector						m_upward;
+
+	// The control-based change in heading this frame. Used mainly for path following decisions.
+	float							m_delta_angle;
+
+	// Type of animation speed scaling to do.
+	EAnimScaleSpeed					m_anim_scale_speed;
+	
+	// Horizontal speed for the purposes of animation speed scaling.
+	float							m_anim_effective_speed;
+	
+	// Factor multiplied by all velocities.  Set by script.
+	float							m_script_drag_factor;
+
+	// Current frame's frame length.
+	float							m_frame_length;
+
+	// Current frame's control input.
+	Mth::Vector						m_control_direction;
+	float							m_control_magnitude;
+	bool							m_control_pegged;
+
+ 	// Keep track of the previous state of the dpad controls so that we can ramp up to a run speed over time.
+	bool							m_dpad_used_last_frame;
+	
+	// Keep track of when we first started using the dpad so that we can ramp up to a run speed over time.
+	Tmr::Time						m_dpad_use_time_stamp;
+
+	// Cache our position at the frame's start
+	Mth::Vector						m_frame_start_pos;
+	
+	// Script event reporting our current state.  Set during each frame update and sent at the end of the update.
+	uint32							m_frame_event;
+	
+	// Last frame's frame event.
+	uint32							m_last_frame_event;
+
+	// Used to lock control in the forward direction (during a camera flush request)
+	bool							m_forward_control_lock;
+
+	// When on ground, the walker is often floated above the ground in an attempt to smooth out stair/curb climbs.
+	float							m_curb_float_height;
+	
+	// If m_curb_float_height was adjusted this frame.  If not, we lerp it to zero.
+	bool							m_curb_float_height_adjusted;
+
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline float CHorseComponent::s_get_param( uint32 checksum )
+{
+	Script::CStruct* p_params = Script::GetStructure(CRCD(0x1b38036f,"GunslingerHorseParameters"));
+	Dbg_Assert(p_params);
+	
+	float param;
+	p_params->GetFloat(checksum, ¶m, Script::ASSERT);
+	return param;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline bool CHorseComponent::forced_run( void )
+{
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline void CHorseComponent::set_state ( EStateType state )
+{
+    if (state == m_state) return;
+	
+	m_previous_state = m_state;
+    
+    if (m_state == WALKING_GROUND)
+    {
+        m_wall_push_test.active = false;
+		m_run_toggle = false;
+    }
+	
+	if (m_state != WALKING_GROUND && m_state != WALKING_AIR)
+	{
+		m_dpad_used_last_frame = false;
+	}
+	
+	m_state = state;
+	m_state_timestamp = Tmr::GetTime();
+	
+	if (m_state == WALKING_ANIMWAIT)
+	{
+		m_offset_due_to_movable_contact.Set();
+	}
+}
+
+}
+
+#endif
diff --git a/Code/Gel/Components/InputComponent.cpp b/Code/Gel/Components/InputComponent.cpp
new file mode 100644
index 0000000..387c760
--- /dev/null
+++ b/Code/Gel/Components/InputComponent.cpp
@@ -0,0 +1,489 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       InputComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/18/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+								
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+	extern bool DebugSkaterScripts;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CInputComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CInputComponent);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CInputComponent::CInputComponent() : CBaseComponent()
+{
+	SetType( CRC_INPUT);
+	
+	m_input_handler = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CInputComponent::~CInputComponent()
+{
+	if (m_input_handler)
+	{
+		if (m_input_handler->InList())
+		{
+			m_input_handler->Remove();
+		}
+		delete m_input_handler;
+	}
+	if (m_input_handler2)
+	{
+		if (m_input_handler2->InList())
+		{
+			m_input_handler2->Remove();
+		}
+		delete m_input_handler2;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	int i;
+	if (pParams->GetInteger(CRCD(0x67e6859a, "player"), &i))
+	{
+		BindToController(Mdl::Skate::Instance()->m_device_server_map[i]);
+	}
+	else if (pParams->GetInteger(CRCD(0xb30d9965, "controller"), &i))
+	{
+		BindToController(i);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CInputComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Input_Debounce | 
+		case CRCC(0xbf7d6c46, "Input_Debounce"):
+		{
+			uint32 ButtonChecksum = 0;
+			if (!pParams->GetChecksum(NO_NAME, &ButtonChecksum)) break;
+
+			float time = 1.0f;
+			pParams->GetFloat(CRCD(0x906b67ba, "time"), &time);
+			DUMPF(time);
+
+			int	clear = 0;
+			pParams->GetInteger(CRCD(0x1a4e0ef9, "clear"), &clear);
+
+			Debounce(ButtonChecksum, time, clear);
+			break;
+		}
+			
+		// @script | DisablePlayerInput | 
+		case CRCC(0x6e24f5fd, "DisablePlayerInput"):
+			DisableInput();
+			break;
+			
+        // @script | EnablePlayerInput | 
+		case CRCC(0x56c6c0cd, "EnablePlayerInput"):	  
+			EnableInput();
+			break;
+
+		// @script | NetDisablePlayerInput | 
+		case CRCC(0x6df51774, "NetDisablePlayerInput"):
+			NetDisableInput();
+			break;
+
+		// @script | NetEnablePlayerInput | 
+		case CRCC(0x31c6454f, "NetEnablePlayerInput"):	  
+			NetEnableInput();
+			break;
+				
+		// @script | PlayerInputIsDisabled	| return true if the player input is disabled
+		case CRCC(0x660746a, "PlayerInputIsDisabled"):
+			return IsInputDisabled() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;	
+
+        // @script | LeftPressed | true if left is being pressed on the pad
+		case CRCC(0x78238598, "LeftPressed"):
+			return m_pad.m_left.GetPressed() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+			
+        // @script | RightPressed | true if right is being pressed on the pad
+		case CRCC(0x1d604a9d, "RightPressed"):
+			return m_pad.m_right.GetPressed() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+			
+        // @script | UpPressed | true if up is being pressed on the pad
+		case CRCC(0x89aa9b5f, "UpPressed"):
+			return m_pad.m_up.GetPressed() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+			
+        // @script | DownPressed | true if down is being pressed on the pad
+		case CRCC(0x83028fc5, "DownPressed"):
+			return m_pad.m_down.GetPressed() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+			
+        // @script | HeldLongerThan | true if the specified button has been
+        // held longer than the specified amount of time
+        // @parm name | Button | button name
+        // @uparm 1.0 | time value (default is milliseconds)
+        // @flag seconds | time in seconds
+        // @flag frames | time in frames
+		case CRCC(0x2366469c, "HeldLongerThan"):
+		{
+			uint32 ButtonChecksum;
+			pParams->GetChecksum(CRCD(0xc5f953c2, "Button"), &ButtonChecksum, Script::ASSERT);
+				
+			float time;
+			pParams->GetFloat(NO_NAME, &time, Script::ASSERT);
+	
+			Tmr::Time TestTime = 0;
+			if (pParams->ContainsFlag(CRCD(0xd029f619, "seconds")) || pParams->ContainsFlag(CRCD(0x49e0ee96, "second")))
+			{
+				TestTime = static_cast< Tmr::Time >(time * 1000.0f);
+			}	
+			else if (pParams->ContainsFlag(CRCD(0x19176c5, "frames")) || pParams->ContainsFlag(CRCD(0x4a07c332, "frame")))
+			{
+				TestTime =static_cast< Tmr::Time >(time * (1000.0f / 60.0f));
+			}
+			else
+			{
+				TestTime =static_cast< Tmr::Time >(time);	
+			}
+
+			CSkaterButton* pButt = m_pad.GetButton(ButtonChecksum);
+			if (pButt)
+			{
+				return (pButt->GetPressed() && pButt->GetPressedTime() > TestTime) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			}
+			else
+			{
+				return CBaseComponent::MF_FALSE;
+			}	
+			break;
+		}
+			
+        // @script | EnableInputEvents |  Enable the sending of TRIGGER_ and RELEASE events from an Input component
+		case	CRCC(0x1090a150,"EnableInputEvents"):
+		{
+			m_input_events_enabled = true;			
+			break;
+		}
+		
+        // @script | DisableInputEvents |  Disable the sending of TRIGGER_ and RELEASE events from an Input component
+		case	CRCC(0x28729460,"DisableInputEvents"):
+		{
+			m_input_events_enabled = false;			
+			break;
+		}
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CInputComponent::GetDebugInfo"));
+	
+	if (m_input_handler && m_input_handler->m_Device)
+	{
+		p_info->AddInteger("port", m_input_handler->m_Device->GetPort());
+		p_info->AddInteger("slot", m_input_handler->m_Device->GetSlot());
+		p_info->AddInteger("index", m_input_handler->m_Device->GetIndex());
+		p_info->AddInteger("m_input_events_enabled",m_input_events_enabled);
+	}
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::BindToController ( int controller )
+{
+	if (!m_input_handler)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+		m_input_handler = new Inp::Handler< CInputComponent >(0, CInputComponent::s_input_logic_code, *this, Tsk::BaseTask::Node::vNORMAL_PRIORITY - 1);
+		Inp::Manager::Instance()->AddHandler(*m_input_handler);
+		m_input_handler2 = new Inp::Handler< CInputComponent >(1, CInputComponent::s_input_logic_code2, *this, Tsk::BaseTask::Node::vNORMAL_PRIORITY - 1);
+		Inp::Manager::Instance()->AddHandler(*m_input_handler2);
+
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	Inp::Manager::Instance()->ReassignHandler(*m_input_handler, controller);
+	Inp::Manager::Instance()->ReassignHandler(*m_input_handler2, 1);
+	
+	if (CVibrationComponent* p_vibration_component = GetVibrationComponentFromObject(GetObject()))
+	{
+		p_vibration_component->SetDevice(m_input_handler->m_Device);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::Debounce ( uint32 Checksum, float time, bool clear )
+{
+	CSkaterButton* button = m_pad.GetButton(Checksum);
+	
+	float debounce_time = Tmr::GetTime() + (time * 1000.0f);
+	
+	if (CTrickComponent* p_trick_component = GetTrickComponentFromObject(GetObject()))
+	{
+		p_trick_component->Debounce(Inp::GetButtonIndex(Checksum), debounce_time);
+	}
+	
+	button->SetDebounce(static_cast< int >(debounce_time));
+	
+	if (clear)
+	{
+		button->SetPressed(false);
+		button->ClearTrigger();
+		button->ClearRelease();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CInputComponent::ignore_button_presses (   )
+{
+	// if any operable menus are up in net games, ignore input
+	if( !GameNet::Manager::Instance()->InNetGame()) return false;
+	
+	if (Front::CScreenElementManager::Instance()->GetElement(CRCD(0x2edb780f, "controller_unplugged_dialog_anchor"))) return true;
+	
+	Front::CScreenElement* p_root_window
+		= Front::CScreenElementManager::Instance()->GetElement(CRCD(0x56a1eae3, "root_window"));
+	
+	if (p_root_window)
+	{
+		Script::CStruct* pTags = new Script::CStruct();
+		p_root_window->CopyTagsToScriptStruct(pTags);
+		
+		uint32 menu_state;
+		pTags->GetChecksum(CRCD(0xcdc00e63, "menu_state"), &menu_state);
+		delete pTags;
+		
+		if (menu_state == CRCD(0xf649d637, "on")) return true;
+	}
+
+	if (Front::CScreenElementManager::Instance()->GetElement(CRCD(0x31631b98, "keyboard_anchor"))) return true;
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::handle_input ( Inp::Data* input )
+{
+	if (GetObject()->IsPaused()) return;
+	
+	build_input_mask(input);
+
+	bool ignore = ignore_button_presses();
+	if (IsInputDisabled() || ignore)
+	{
+		m_pad.Zero();
+		if (ignore)
+		{
+			m_input_mask = 0;
+		}
+	}
+	else
+	{
+		#ifdef __NOPT_ASSERT__
+		m_pad.Update(input, DebugSkaterScripts && GetObject()->GetID() == 0);
+		#else
+		m_pad.Update(input);
+		#endif
+	}
+}
+
+
+void CInputComponent::handle_input2 ( Inp::Data* input )
+{
+	if (GetObject()->IsPaused()) return;
+	
+//	build_input_mask(input);
+
+	bool ignore = ignore_button_presses();
+	if (IsInputDisabled() || ignore)
+	{
+		m_pad2.Zero();
+		if (ignore)
+		{
+			m_input_mask = 0;
+		}
+	}
+	else
+	{
+		#ifdef __NOPT_ASSERT__
+		m_pad2.Update(input, DebugSkaterScripts && GetObject()->GetID() == 0);
+		#else
+		m_pad2.Update(input);
+		#endif
+	}
+}
+
+
+	
+void CInputComponent::update_input_mask ( Inp::Data* input, Inp::Data::AnalogButtonIndex button, Inp::Data::AnalogButtonMask mask, uint32 trigger_event, uint32 release_event, CSkaterButton* skater_button )
+{
+	if (skater_button && Tmr::GetTime() < skater_button->GetDebounceTime()) return;
+	
+	if (input->m_Event[button])
+	{
+		if (!(m_last_mask & mask))
+		{
+				// Fire Event to self, trigger_event
+			if (m_input_events_enabled && !m_input_disabled && !m_net_input_disabled)
+			{
+						
+						//uint32	id = GetObject()->GetID();
+						//Obj::CTracker::Instance()->LaunchEvent(trigger_event, id, id);
+						GetObject()->SelfEvent(trigger_event);
+			}
+		}
+		m_input_mask |= mask;
+	}
+	else
+	{
+		if ((m_last_mask & mask))
+		{
+			if (m_input_events_enabled && !m_input_disabled && !m_net_input_disabled)
+			{
+						// Fire Event to self, release_event
+						//uint32	id = GetObject()->GetID();
+						//Obj::CTracker::Instance()->LaunchEvent(release_event, id, id);
+						GetObject()->SelfEvent(release_event);						
+			}
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::build_input_mask ( Inp::Data* input )
+	{
+	m_last_mask = m_input_mask;
+	m_input_mask = 0;
+	update_input_mask(input, Inp::Data::vA_UP,			Inp::Data::mA_UP, 		CRCD(0x5c8faa09,"Trigger_UP"),        CRCD(0xb27804a2,"Release_UP"),		&m_pad.m_up			);	
+	update_input_mask(input, Inp::Data::vA_DOWN,		Inp::Data::mA_DOWN, 	CRCD(0xd26adae9,"Trigger_DOWN"),      CRCD(0xaef7dfd3,"Release_DOWN"),      &m_pad.m_down		);	
+	update_input_mask(input, Inp::Data::vA_LEFT,		Inp::Data::mA_LEFT, 	CRCD(0xb4f2adba,"Trigger_LEFT"),      CRCD(0xc86fa880,"Release_LEFT"),      &m_pad.m_left		);	
+	update_input_mask(input, Inp::Data::vA_RIGHT,		Inp::Data::mA_RIGHT, 	CRCD(0xedbbc2b,"Trigger_RIGHT"),      CRCD(0xc8abf89c,"Release_RIGHT"),     &m_pad.m_right 		);	
+	update_input_mask(input, Inp::Data::vA_CIRCLE,		Inp::Data::mA_CIRCLE, 	CRCD(0xb069b600,"Trigger_CIRCLE"),    CRCD(0xe5aae06b,"Release_CIRCLE"),    &m_pad.m_circle		);	
+	update_input_mask(input, Inp::Data::vA_SQUARE,		Inp::Data::mA_SQUARE, 	CRCD(0xa93dbbd0,"Trigger_SQUARE"),    CRCD(0xfcfeedbb,"Release_SQUARE"),    &m_pad.m_square		);	
+	update_input_mask(input, Inp::Data::vA_TRIANGLE,	Inp::Data::mA_TRIANGLE, CRCD(0x7f695a77,"Trigger_TRIANGLE"),  CRCD(0x7b684919,"Release_TRIANGLE"),  &m_pad.m_triangle	);	
+	update_input_mask(input, Inp::Data::vA_X,			Inp::Data::mA_X, 		CRCD(0x42717176,"Trigger_X"),         CRCD(0xbbbef674,"Release_X"),         &m_pad.m_x	 		);	
+	update_input_mask(input, Inp::Data::vA_L1,			Inp::Data::mA_L1, 		CRCD(0xc6547217,"Trigger_L1"),        CRCD(0x28a3dcbc,"Release_L1"),        &m_pad.m_L1	 		);	
+	update_input_mask(input, Inp::Data::vA_L2,			Inp::Data::mA_L2, 		CRCD(0x5f5d23ad,"Trigger_L2"),        CRCD(0xb1aa8d06,"Release_L2"),        &m_pad.m_L2 		);	
+	update_input_mask(input, Inp::Data::vA_L3,			Inp::Data::mA_L3, 		CRCD(0x285a133b,"Trigger_L3"),        CRCD(0xc6adbd90,"Release_L3"),        &m_pad.m_L3	 		);	
+	update_input_mask(input, Inp::Data::vA_R1,			Inp::Data::mA_R1, 		CRCD(0x12154dc8,"Trigger_R1"),        CRCD(0xfce2e363,"Release_R1"),        &m_pad.m_R1 		);	
+	update_input_mask(input, Inp::Data::vA_R2,			Inp::Data::mA_R2, 		CRCD(0x8b1c1c72,"Trigger_R2"),        CRCD(0x65ebb2d9,"Release_R2"),        &m_pad.m_R2 		);	
+	update_input_mask(input, Inp::Data::vA_R3,			Inp::Data::mA_R3, 		CRCD(0xfc1b2ce4,"Trigger_R3"),        CRCD(0x12ec824f,"Release_R3"),        &m_pad.m_R3			);	
+	update_input_mask(input, Inp::Data::vA_BLACK,		Inp::Data::mA_BLACK, 	CRCD(0x33947317,"Trigger_BLACK"),     CRCD(0xf5e437a0,"Release_BLACK"),     NULL				);	
+	update_input_mask(input, Inp::Data::vA_WHITE,		Inp::Data::mA_WHITE, 	CRCD(0xf8de049b,"Trigger_WHITE"),     CRCD(0x3eae402c,"Release_WHITE"),     NULL		 		);	
+	update_input_mask(input, Inp::Data::vA_Z,			Inp::Data::mA_Z,	 	CRCD(0xac7f105a,"Trigger_Z"),         CRCD(0x55b09758,"Release_Z"),     	NULL		 		);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInputComponent::s_input_logic_code ( const Inp::Handler < CInputComponent >& handler )
+{
+	handler.GetData().handle_input(handler.m_Input);
+}
+
+void CInputComponent::s_input_logic_code2 ( const Inp::Handler < CInputComponent >& handler )
+{
+	handler.GetData().handle_input2(handler.m_Input);
+}
+
+
+}
diff --git a/Code/Gel/Components/InputComponent.h b/Code/Gel/Components/InputComponent.h
new file mode 100644
index 0000000..3b221fc
--- /dev/null
+++ b/Code/Gel/Components/InputComponent.h
@@ -0,0 +1,96 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       InputComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/18/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_INPUTCOMPONENT_H__
+#define __COMPONENTS_INPUTCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_INPUT CRCD(0x27d7cd28, "Input")
+
+#define		GetInputComponent() ((Obj::CInputComponent*)GetComponent(CRC_INPUT))
+#define		GetInputComponentFromObject(pObj) ((Obj::CInputComponent*)(pObj)->GetComponent(CRC_INPUT))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	
+class CInputComponent : public CBaseComponent
+{
+public:
+    CInputComponent();
+    virtual ~CInputComponent();
+
+public:
+    virtual void            			Update();
+    virtual void            			InitFromStructure( Script::CStruct* pParams );
+    virtual void            			RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   	CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 						GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*				s_create();
+	
+	uint32								GetInputMask (   ) const { return m_input_mask; }
+	CControlPad&						GetControlPad (   ) { return m_pad; }
+	CControlPad&						GetControlPad2 (   ) { return m_pad2; }
+	
+	void								DisableInput (   ) { m_input_disabled = true; }
+	void								EnableInput (   ) { m_input_disabled = false; }
+	void								NetDisableInput (   ) { m_net_input_disabled = true; }
+	void								NetEnableInput (   ) { m_net_input_disabled = false; }
+	bool								IsInputDisabled (   ) { return m_input_disabled || m_net_input_disabled; }
+	
+	void								BindToController ( int controller );
+	Inp::Handler< CInputComponent >*	GetInputHandler (   ) { return m_input_handler; }
+	
+	void								Debounce ( uint32 Checksum, float time, bool clear );
+	
+	void								PauseDevice (   ) { m_input_handler->m_Device->Pause(); }
+	void								UnPauseDevice (   ) { m_input_handler->m_Device->UnPause(); }
+	SIO::Device*						GetDevice (   ) { return m_input_handler->m_Device; }
+	
+private:
+	static void							s_input_logic_code ( const Inp::Handler < CInputComponent >& handler );
+	static void							s_input_logic_code2 ( const Inp::Handler < CInputComponent >& handler );
+	
+	void								handle_input ( Inp::Data* input );
+	void								handle_input2 ( Inp::Data* input );
+	void								build_input_mask ( Inp::Data* input );
+	void 								update_input_mask ( Inp::Data* input, Inp::Data::AnalogButtonIndex button, Inp::Data::AnalogButtonMask mask, uint32 trigger_event, uint32 release_event, CSkaterButton* skater_button  );
+	
+	bool								ignore_button_presses (   );
+	
+private:
+	uint32								m_last_mask;
+	uint32								m_input_mask;
+	CControlPad							m_pad;
+	CControlPad							m_pad2;
+	
+	Inp::Handler< CInputComponent >*	m_input_handler;
+	Inp::Handler< CInputComponent >*	m_input_handler2;
+	
+	bool								m_input_disabled;
+	bool								m_net_input_disabled;
+	
+	bool								m_input_events_enabled;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/ModelLightUpdateComponent.cpp b/Code/Gel/Components/ModelLightUpdateComponent.cpp
new file mode 100644
index 0000000..378f4c6
--- /dev/null
+++ b/Code/Gel/Components/ModelLightUpdateComponent.cpp
@@ -0,0 +1,188 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ModelLightUpdateComponent.cpp
+//* OWNER:          Garrett
+//* CREATION DATE:  07/10/03
+//****************************************************************************
+
+// The CModelLightUpdateComponent class is used to update the brightness of
+// the model lights if nothing else is currently doing it.  Because it has
+// to do extra collision checks, this component should only be used if there
+// is no other way to get the brightness from an existing feeler.  For now,
+// all you need to do is create the component on an object that already has
+// a ModelComponent with "UseModelLights" set in the InitStructure.
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CModelLightUpdateComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CModelLightUpdateComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelLightUpdateComponent::CModelLightUpdateComponent() : CBaseComponent()
+{
+	SetType( CRC_MODELLIGHTUPDATE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CModelLightUpdateComponent::~CModelLightUpdateComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelLightUpdateComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelLightUpdateComponent::Finalize()
+{
+	mp_model_component =  GetModelComponentFromObject( GetObject() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelLightUpdateComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelLightUpdateComponent::Update()
+{
+	// Find the closest ground collision and extract the brightness from it.
+	// logic extracted from CAdjustComponent::uber_frig
+
+	CFeeler feeler;
+	
+	feeler.m_start = GetObject()->m_pos;
+	feeler.m_end = GetObject()->m_pos;
+
+	// Very minor adjustment to move origin away from vert walls
+	feeler.m_start += GetObject()->m_matrix[Y] * 0.001f;
+	
+	feeler.m_start[Y] += 8.0f;
+	feeler.m_end[Y] -= FEET(400);
+		   
+	if (!feeler.GetCollision()) return;
+
+	Dbg_Assert(mp_model_component);
+
+	if (feeler.IsBrightnessAvailable())
+	{
+		Nx::CModelLights *p_model_lights = mp_model_component->GetModel()->GetModelLights();
+		if (p_model_lights)
+		{
+			p_model_lights->SetBrightness(feeler.GetBrightness());
+		} 
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CModelLightUpdateComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelLightUpdateComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CModelLightUpdateComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger(CRCD(0x7cf2a233,"m_never_suspend"),m_never_suspend);
+	p_info->AddFloat(CRCD(0x519ab8e0,"m_suspend_distance"),m_suspend_distance);
+	*/
+	
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/ModelLightUpdateComponent.h b/Code/Gel/Components/ModelLightUpdateComponent.h
new file mode 100644
index 0000000..8b23104
--- /dev/null
+++ b/Code/Gel/Components/ModelLightUpdateComponent.h
@@ -0,0 +1,54 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ModelLightUpdateComponent.h
+//* OWNER:          Garrett
+//* CREATION DATE:  07/10/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_MODELLIGHTUPDATECOMPONENT_H__
+#define __COMPONENTS_MODELLIGHTUPDATECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_MODELLIGHTUPDATE CRCD(0xe3fca939,"ModelLightUpdate")
+#define		GetModelLightUpdateComponent() ((Obj::CModelLightUpdateComponent*)GetComponent(CRC_MODELLIGHTUPDATE))
+#define		GetModelLightUpdateComponentFromObject(pObj) ((Obj::CModelLightUpdateComponent*)(pObj)->GetComponent(CRC_MODELLIGHTUPDATE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+	class CModelComponent;
+
+class CModelLightUpdateComponent : public CBaseComponent
+{
+public:
+    CModelLightUpdateComponent();
+    virtual ~CModelLightUpdateComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual	void 					Finalize();
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+protected:
+	CModelComponent *				mp_model_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/MovableContactComponent.cpp b/Code/Gel/Components/MovableContactComponent.cpp
new file mode 100644
index 0000000..182abac
--- /dev/null
+++ b/Code/Gel/Components/MovableContactComponent.cpp
@@ -0,0 +1,270 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       MovableContactComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  5/19/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CMovableContactComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CMovableContactComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovableContactComponent::CMovableContactComponent() : CBaseComponent()
+{
+	SetType( CRC_MOVABLECONTACT );
+	
+	mp_contact = NULL;
+	
+	m_lost_contact_timestamp = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CMovableContactComponent::~CMovableContactComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovableContactComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovableContactComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovableContactComponent::Update()
+{
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CMovableContactComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovableContactComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CMovableContactComponent::GetDebugInfo"));
+	
+	if (mp_contact && mp_contact->GetObject())
+	{
+		p_info->AddChecksum(CRCD(0xb39d19c7, "contact"), mp_contact->GetObject()->GetID());
+	}
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovableContactComponent::CheckForMovableContact ( CFeeler& feeler )
+{
+	// we might now be in contact with something new
+	if (feeler.IsMovableCollision())
+	{
+		if (HaveContact())
+		{
+			if (mp_contact->GetObject() != feeler.GetMovingObject())
+			{
+				LoseAnyContact();
+			}
+		}
+		
+		if (!HaveContact())
+		{
+			// only create a new contact point if there was not one before, or if we just deleted one
+			mp_contact = new CContact(feeler.GetMovingObject());
+			return true;
+		}
+	}
+	else
+	{
+		// when we land on the ground, then we can't be in contact with anything, so delete any existing contact
+		if (HaveContact())
+		{
+			LoseAnyContact();
+		}
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCompositeObject* CMovableContactComponent::CheckInsideObjectsToTop ( Mth::Vector& pos )
+{
+	Mth::Vector top;
+	CCompositeObject *p_object = find_inside_object(pos, top);
+	if (!p_object) return NULL;
+	
+	pos = top;
+	pos[Y] += 1.0f;
+	return p_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCompositeObject* CMovableContactComponent::CheckInsideObjects ( Mth::Vector& pos, const Mth::Vector& safe_pos )
+{
+	// not movable contact functionality per say, but useful a service related to movable collision nonetheless
+	
+	// Check to see if the skater is inside any moving object and move him outside
+	// Algorithm
+	// start at a point 100 feet above the skater
+	// check collision down to the skater
+	// while there is a collision if skater has a moving object collision in the 100 feet above him
+	// and no collisions (with that object) going up to that point, then we are inside the object
+
+	Mth::Vector top;
+	CCompositeObject *p_object = find_inside_object(pos, top);
+	if (!p_object) return NULL;
+	
+	// check if the last position is now fine...
+	Mth::Vector dummy;
+	if (!find_inside_object(safe_pos, dummy))
+	{
+		// first choice: move back to old pos
+		pos = safe_pos;
+	}
+	else
+	{
+		// recheck last position here, like adding in the contact movement
+	
+		Mth::Vector test = pos + p_object->GetVel() * Tmr::FrameLength() * 2.0f;  
+		
+		if (!find_inside_object(test, dummy))
+		{
+			// first choice: move back to old pos
+			pos = test;
+		}
+		else
+		{
+			// last resort, snap up 
+			pos = top;
+			
+			// when snapping up, put him an inch above the object; otherwise, you will not hit it next frame, as the point will be in the plane
+			pos[Y] += 1.0f;
+		}
+	}
+	
+	return p_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCompositeObject* CMovableContactComponent::find_inside_object ( const Mth::Vector& pos, Mth::Vector& p_return_pos )
+{
+	// 	Given a position (pos), then find out if this position is inside this moving object
+	//  Assumes anything within 6 inches below the top of the object is actually outside as the code assumes 
+
+	// create a line that goes from 400 inches above this point to six inches above
+	CFeeler	down_feeler;
+	down_feeler.m_start = pos;
+	down_feeler.m_start[Y] += 400.0f;
+	down_feeler.m_end = pos;
+	down_feeler.m_end[Y] += 6.0f;
+	
+	CFeeler	up_feeler;
+	int loop_detect = 0;
+	while (true)
+	{
+		// return on final builds to handle highly obscure cases I've not forseen....
+		if (++loop_detect == 10) return NULL;
+		
+		// nothing above
+		if (!down_feeler.GetCollision()) return NULL;
+		
+		// something above, but not movable
+		if (!down_feeler.IsMovableCollision()) return NULL;
+		
+		// something movable above
+		
+		// Now we get the collision point, and check UP to that point from m_pos to a point just above that		
+		up_feeler.m_start = pos;
+		up_feeler.m_end = down_feeler.GetPoint();
+		up_feeler.m_end[Y] += 0.1f;
+		
+		if (!up_feeler.GetCollision())
+		{
+			p_return_pos = down_feeler.GetPoint();
+			return down_feeler.GetMovingObject(); 
+		}
+		else
+		{
+			down_feeler.m_start = down_feeler.GetPoint();
+			down_feeler.m_start[Y] -= 0.1f;
+		}
+	}
+}	
+
+}
diff --git a/Code/Gel/Components/MovableContactComponent.h b/Code/Gel/Components/MovableContactComponent.h
new file mode 100644
index 0000000..99cd0f2
--- /dev/null
+++ b/Code/Gel/Components/MovableContactComponent.h
@@ -0,0 +1,128 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       MovableContactComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  5/19/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_MOVABLECONTACTCOMPONENT_H__
+#define __COMPONENTS_MOVABLECONTACTCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_MOVABLECONTACT CRCD(0x408d2aa9, "MovableContact")
+
+#define		GetMovableContactComponent() ((Obj::CMovableContactComponent*)GetComponent(CRC_MOVABLECONTACT))
+#define		GetMovableContactComponentFromObject(pObj) ((Obj::CMovableContactComponent*)(pObj)->GetComponent(CRC_MOVABLECONTACT))
+
+class CFeeler;
+ 
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CMovableContactComponent : public CBaseComponent
+{
+public:
+    CMovableContactComponent();
+    virtual ~CMovableContactComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	bool							CheckForMovableContact ( CFeeler& feeler );
+	void							ObtainContact ( CCompositeObject* p_composite_object );
+	bool							HaveContact (   );
+	void							LoseAnyContact (   );
+	bool							UpdateContact ( const Mth::Vector& pos);
+	CContact*						GetContact (   ) { return mp_contact; }
+	CCompositeObject*				CheckInsideObjectsToTop ( Mth::Vector& pos );
+	CCompositeObject*				CheckInsideObjects ( Mth::Vector& pos, const Mth::Vector& safe_pos );
+	
+	Tmr::Time						GetTimeSinceLastLostContact (   ) { return Tmr::ElapsedTime(m_lost_contact_timestamp); }
+	
+private:		
+	CCompositeObject*				find_inside_object ( const Mth::Vector& pos, Mth::Vector& p_return_pos );
+
+	CContact*						mp_contact;						// contact point (NULL if no current contact)
+	
+	Tmr::Time						m_lost_contact_timestamp;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CMovableContactComponent::LoseAnyContact (   )
+{
+	if (mp_contact)
+	{
+		delete mp_contact;
+		mp_contact = NULL;
+		
+		m_lost_contact_timestamp = Tmr::GetTime();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CMovableContactComponent::HaveContact (   )
+{
+	if (!mp_contact) return false;
+	
+	if (mp_contact->GetObject()) return true;
+	
+	LoseAnyContact();
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CMovableContactComponent::ObtainContact ( CCompositeObject* p_composite_object )
+{
+	LoseAnyContact();
+	mp_contact = new CContact(p_composite_object);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CMovableContactComponent::UpdateContact ( const Mth::Vector& pos)
+{
+	if (!HaveContact()) return false;
+	
+	if (mp_contact->Update(pos)) return true;
+	
+	LoseAnyContact();
+	return false;
+}
+
+}
+
+#endif
diff --git a/Code/Gel/Components/NearComponent.cpp b/Code/Gel/Components/NearComponent.cpp
new file mode 100644
index 0000000..ba94af8
--- /dev/null
+++ b/Code/Gel/Components/NearComponent.cpp
@@ -0,0 +1,701 @@
+#if 0
+
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       NearComponent.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/19/03
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+	
+sSweepPruneArray CNearComponent::m_sweep_prune_array[3]	= { sSweepPruneArray( 0 ), sSweepPruneArray( 1 ), sSweepPruneArray( 2 ) };
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent* CNearComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CNearComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNearComponent::CNearComponent() : CBaseComponent()
+{
+	SetType( CRC_NEAR );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNearComponent::~CNearComponent()
+{
+	// Remove from the axis arrays.
+	for( int axis = 0; axis < 3; ++axis )
+	{
+		m_sweep_prune_array[axis].RemoveComponent( this );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNearComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// Not sure where bounding box dimensions will come from.
+	m_untransformed_bbox.SetMin( Mth::Vector( -120.0f, -120.0f, -120.0f ));
+	m_untransformed_bbox.SetMax( Mth::Vector(  120.0f,  120.0f,  120.0f ));
+
+	// Obtain the position of the parent object, and build the transformed bounding box.
+	if( GetObject())
+	{
+		Mth::Vector t;
+
+		t = m_untransformed_bbox.GetMin() + GetObject()->GetPos();
+		m_transformed_bbox.SetMin( t );
+
+		t = m_untransformed_bbox.GetMax() + GetObject()->GetPos();
+		m_transformed_bbox.SetMax( t );
+	}
+
+	// Now add this component to the axis arrays.
+	for( int axis = 0; axis < 3; ++axis )
+	{
+		m_sweep_prune_array[axis].InsertComponent( this );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNearComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Remove from axis arrays, then reinitialise.
+	for( int axis = 0; axis < 3; ++axis )
+	{
+		m_sweep_prune_array[axis].RemoveComponent( this );
+	}
+
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNearComponent::Update()
+{
+	// Re-calculate the bounding box position.
+	if( GetObject())
+	{
+		Mth::Vector t;
+
+		t = m_untransformed_bbox.GetMin() + GetObject()->GetPos();
+		m_transformed_bbox.SetMin( t );
+
+		t = m_untransformed_bbox.GetMax() + GetObject()->GetPos();
+		m_transformed_bbox.SetMax( t );
+
+		// Resort the component.
+		for( int axis = 0; axis < 3; ++axis )
+		{
+			m_sweep_prune_array[axis].ResortComponent( this );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent::EMemberFunctionResult CNearComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNearComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ( "NULL p_info sent to CNearComponent::GetDebugInfo" ));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger("m_never_suspend",m_never_suspend);
+	p_info->AddFloat("m_suspend_distance",m_suspend_distance);
+	*/
+	
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+
+
+#define MAX_COMPONENTS_PER_WORKSPACE_ARRAY		64
+static int				perAxisIntersections[3];
+static CNearComponent*	perAxisWorkspace[3][MAX_COMPONENTS_PER_WORKSPACE_ARRAY];
+static CNearComponent*	combinedAxisWorkspace[MAX_COMPONENTS_PER_WORKSPACE_ARRAY];
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int sSweepPruneArray::BinarySearchFirstBigger( float value )
+{
+	int first_bigger	= -1;
+	int l				= 0;
+	int u				= m_num_entries - 1;
+	while( l <= u )
+	{
+		int m = ( l + u ) / 2;
+		if( m_array[m].GetValue( m_axis ) <= value )
+		{
+			// x[m] <= t
+			l = m + 1;
+		}
+		else
+		{
+			// x[m] > t
+			first_bigger = m;
+			u = m - 1;
+		}
+	}
+	return first_bigger;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int sSweepPruneArray::BinarySearchLastSmaller( float value )
+{
+	int last_smaller	= -1;
+	int l				= 0;
+	int u				= m_num_entries - 1;
+	while( l <= u )
+	{
+		int m = ( l + u ) / 2;
+		if( m_array[m].GetValue( m_axis ) <= value )
+		{
+			// x[m] <= t
+			last_smaller = m;
+			l = m + 1;
+		}
+		else
+		{
+			// x[m] > t
+			u = m - 1;
+		}
+	}
+	return last_smaller;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int sSweepPruneArray::BuildCompositeIntersectingArray( void )
+{
+	// Now build the composite array of intersections that appear in all three axis lists.
+	int final_intersections	= 0;
+	int source, check1, check2;
+
+	// Use the smallest list to check against.
+	if(( perAxisIntersections[0] <= perAxisIntersections[1] ) && ( perAxisIntersections[0] <= perAxisIntersections[2] ))
+	{
+		source	= 0;
+		check1	= 1;
+		check2	= 2;
+	}
+	else if(( perAxisIntersections[1] <= perAxisIntersections[0] ) && ( perAxisIntersections[1] <= perAxisIntersections[2] ))
+	{
+		source	= 1;
+		check1	= 0;
+		check2	= 2;
+	}
+	else
+	{
+		source	= 2;
+		check1	= 0;
+		check2	= 1;
+	}
+
+	for( int i = 0; i < perAxisIntersections[source]; ++i )
+	{
+		bool found;
+		
+		// Is this intersection also in the first check list?
+		found = false;
+		for( int j = 0; j < perAxisIntersections[check1]; ++j )
+		{
+			if( perAxisWorkspace[source][i] == perAxisWorkspace[check1][j] )
+			{
+				found = true;
+				break;
+			}
+		}
+
+		// If it wasn't in the first check list, there's no intersection.
+		if( !found )
+		{
+			break;
+		}
+
+		// Is this intersection also in the second check list?
+		found = false;
+		for( int j = 0; j < perAxisIntersections[check2]; ++j )
+		{
+			if( perAxisWorkspace[source][i] == perAxisWorkspace[check2][j] )
+			{
+				found = true;
+				break;
+			}
+		}
+
+		// If it wasn't in the second check list, there's no intersection.
+		if( !found )
+		{
+			break;
+		}
+
+		// At this stage the intersection appeared in all three axis lists, so it is a valid intersection.
+		// As a final check, make sure it isn't already in the final list.
+		found = false;
+		for( int j = 0; j < final_intersections; ++j )
+		{
+			if( combinedAxisWorkspace[j] == perAxisWorkspace[source][i] )
+			{
+				found = true;
+				break;
+			}
+		}
+
+		if( !found )
+		{
+			combinedAxisWorkspace[final_intersections] = perAxisWorkspace[source][i];
+			++final_intersections;
+		}
+	}
+	return final_intersections;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNearComponent** CNearComponent::GetIntersectingNearComponents( Mth::Vector &bbmin, Mth::Vector &bbmax, int *p_num_intersecting )
+{
+	// Zero axis intersection count array.
+	perAxisIntersections[0] = perAxisIntersections[1] = perAxisIntersections[2] = 0;
+
+	for( int axis = 0; axis < 3; ++axis )
+	{
+		// Find the start index.
+		int start_index = m_sweep_prune_array[axis].BinarySearchFirstBigger( bbmin[axis] );
+		if( start_index == -1 )
+		{
+			// Nothing was found, therefore there can be no intersections.
+			*p_num_intersecting = 0;
+			return NULL;
+		}
+
+		// Find the end index.
+		int end_index = m_sweep_prune_array[axis].BinarySearchLastSmaller( bbmax[axis] );
+		if( end_index == -1 )
+		{
+			// Nothing was found, therefore there can be no intersections.
+			*p_num_intersecting = 0;
+			return NULL;
+		}
+
+		Dbg_Assert( end_index > start_index );
+
+		// Build up the workspace array for this axis.
+		int intersections_for_this_axis = 0;
+		for( int i = start_index; i <= end_index; ++i )
+		{
+			// Don't want to include the calling component in the list.
+			if( m_sweep_prune_array[axis].m_array[i].mp_component != this )
+			{
+				perAxisWorkspace[axis][intersections_for_this_axis++] = m_sweep_prune_array[axis].m_array[i].mp_component;
+			}
+		}
+
+		// If we found no intersections for this axis, there can be no components that intersect.
+		if( intersections_for_this_axis == 0 )
+		{
+			*p_num_intersecting = 0;
+			return NULL;
+		}
+
+		// Store off the number of intersections for this axis.
+		perAxisIntersections[axis] = intersections_for_this_axis;
+
+	}
+
+	// Build the composite array.
+	int final_intersections = sSweepPruneArray::BuildCompositeIntersectingArray();
+
+	*p_num_intersecting = final_intersections;
+	return ( final_intersections > 0 ) ? &combinedAxisWorkspace[0] : NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNearComponent** CNearComponent::GetIntersectingNearComponents( int *p_num_intersecting )
+{
+	// Zero axis intersection count array.
+	perAxisIntersections[0] = perAxisIntersections[1] = perAxisIntersections[2] = 0;
+
+	// Build up each workspace array.
+	for( int axis = 0; axis < 3; ++axis )
+	{
+		int i;
+		for( i = 0; i < m_sweep_prune_array[axis].m_num_entries; ++i )
+		{
+			if( m_sweep_prune_array[axis].m_array[i].mp_component == this )
+			{
+				// We have found the start of the span for this component.
+				Dbg_Assert( m_sweep_prune_array[axis].m_array[i].m_min_max == 0 );
+				++i;
+				break;
+			}
+		}
+
+		// Now add any other components we come across before we hit the end of the span.
+		while(( i < m_sweep_prune_array[axis].m_num_entries ) && ( m_sweep_prune_array[axis].m_array[i].mp_component != this ))
+		{
+			perAxisWorkspace[axis][perAxisIntersections[axis]] = m_sweep_prune_array[axis].m_array[i].mp_component;
+			++perAxisIntersections[axis];
+			++i;
+		}
+
+		// If we found no intersections for this axis, there can be no components that intersect.
+		if( perAxisIntersections[axis] == 0 )
+		{
+			*p_num_intersecting = 0;
+			return NULL;
+		}
+	}
+
+	// Build the composite array.
+	int final_intersections = sSweepPruneArray::BuildCompositeIntersectingArray();
+
+	*p_num_intersecting = final_intersections;
+	return ( final_intersections > 0 ) ? &combinedAxisWorkspace[0] : NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSweepPruneEntry::SetValues( void )
+{
+	Mth::Vector v	= ( m_min_max ) ? mp_component->GetMax() : mp_component->GetMin();
+	m_value[0]		= v[0];
+	m_value[1]		= v[1];
+	m_value[2]		= v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sSweepPruneArray::sSweepPruneArray( int axis )
+{
+	m_axis			= axis;
+	m_num_entries	= 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sSweepPruneArray::~sSweepPruneArray( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSweepPruneArray::PushUp( int index )
+{
+	Dbg_MsgAssert( m_num_entries < MAX_SWEEP_PRUNE_ENTRIES_PER_AXIS, ( "CNearComponent::SweepPruneList overflow" ));
+
+	// All array entries from index (inclusive) upwards are moved up one position.
+	for( int i = m_num_entries - 1; i >= index; --i )
+	{
+		m_array[i + 1] = m_array[i];
+	}
+	++m_num_entries;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSweepPruneArray::PullDown( int index )
+{
+	Dbg_MsgAssert( m_num_entries > 0, ( "CNearComponent::SweepPruneList underflow" ));
+
+	// All array entries from index (inclusive) upwards are moved down one position.
+	for( int i = index; i < ( m_num_entries - 1 ); ++i )
+	{
+		m_array[i] = m_array[i + 1];
+	}
+	--m_num_entries;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSweepPruneArray::ResortComponent( CNearComponent *p_component )
+{
+	// Does a simple check to see whether the component is still in the correct order.
+	// If it isn't, sorting is applied.
+
+	// Check min first.
+	int i;
+	for( i = 0; i < m_num_entries; ++i )
+	{
+		if( m_array[i].mp_component == p_component )
+		{
+			// This should be the min entry.
+			Dbg_Assert( m_array[i].m_min_max == 0 );
+
+			// Set the new min value.
+			m_array[i].SetValues();
+
+			// Check this is bigger than or equal to the previous value, if there is one.
+			while(( i > 0 ) && ( m_array[i].GetValue( m_axis ) < m_array[i - 1].GetValue( m_axis )))
+			{
+				// Move this entry down in the array.
+				sSweepPruneEntry swap	= m_array[i];
+				m_array[i]				= m_array[i - 1];
+				m_array[i - 1]			= swap;
+				--i;
+			}
+
+			// Check this is smaller than or equal to the next value, if there is one.
+			while(( i < ( m_num_entries - 1)) && ( m_array[i].GetValue( m_axis ) > m_array[i + 1].GetValue( m_axis )))
+			{
+				// Move this entry up in the array.
+				sSweepPruneEntry swap	= m_array[i];
+				m_array[i]				= m_array[i + 1];
+				m_array[i + 1]			= swap;
+				++i;
+			}
+			break;
+		}
+	}
+	
+	// Now check max.
+	++i;
+	for( ; i < m_num_entries; ++i )
+	{
+		if( m_array[i].mp_component == p_component )
+		{
+			// This should be the max entry.
+			Dbg_Assert( m_array[i].m_min_max == 1 );
+
+			// Set the new max value.
+			m_array[i].SetValues();
+
+			// Check this is bigger than or equal to the previous value, if there is one.
+			while(( i > 0 ) && ( m_array[i].GetValue( m_axis ) < m_array[i - 1].GetValue( m_axis )))
+			{
+				// Move this entry down in the array.
+				sSweepPruneEntry swap	= m_array[i];
+				m_array[i]				= m_array[i - 1];
+				m_array[i - 1]			= swap;
+				--i;
+			}
+
+			// Check this is smaller than or equal to the next value, if there is one.
+			while(( i < ( m_num_entries - 1)) && ( m_array[i].GetValue( m_axis ) > m_array[i + 1].GetValue( m_axis )))
+			{
+				// Move this entry up in the array.
+				sSweepPruneEntry swap	= m_array[i];
+				m_array[i]				= m_array[i + 1];
+				m_array[i + 1]			= swap;
+				++i;
+			}
+			break;
+		}
+	}
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSweepPruneArray::InsertComponent( CNearComponent *p_component )
+{
+	float axis_min = p_component->GetMin()[m_axis];
+	float axis_max = p_component->GetMax()[m_axis];
+
+	// Scan through until we find an entry that is bigger than the minimum for this axis, or we come to the end of the list.
+	for( int i = 0; i <= m_num_entries; ++i )
+	{
+		if(( i == m_num_entries ) || ( m_array[i].mp_component->GetMin()[m_axis] > axis_min ))
+		{
+			// Insert this entry here.
+			PushUp( i );
+			m_array[i].m_min_max	= 0;
+			m_array[i].mp_component = p_component;
+
+			// Set the new min value.
+			m_array[i].SetValues();
+			break;
+		}
+	}
+
+	// Now scan through until we find an entry that is bigger than the maximum for this axis, or we come to the end of the list.
+	for( int i = 0; i <= m_num_entries; ++i )
+	{
+		if(( i == m_num_entries ) || ( m_array[i].mp_component->GetMax()[m_axis] > axis_max ))
+		{
+			// Insert this entry here.
+			PushUp( i );
+			m_array[i].m_min_max	= 1;
+			m_array[i].mp_component = p_component;
+
+			// Set the new min value.
+			m_array[i].SetValues();
+			break;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSweepPruneArray::RemoveComponent( CNearComponent *p_component )
+{
+	// The component should appear twice in the list.
+	for( int p = 0; p < 2; ++p )
+	{
+		// Scan through until we find this entry.
+		for( int i = 0; i < m_num_entries; ++i )
+		{
+			if( m_array[i].mp_component == p_component )
+			{
+				// Remove this entry.
+				PullDown( i );
+				break;
+			}
+		}
+	}
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+
+#endif
diff --git a/Code/Gel/Components/NearComponent.h b/Code/Gel/Components/NearComponent.h
new file mode 100644
index 0000000..fbdcf81
--- /dev/null
+++ b/Code/Gel/Components/NearComponent.h
@@ -0,0 +1,121 @@
+#if 0
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       NearComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_NEARCOMPONENT_H__
+#define __COMPONENTS_NEARCOMPONENT_H__
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_NEAR							CRCD( 0x89b3e3ee, "near" )
+
+//  Standard accessor macros for getting the component either from within an object, or given an object.
+#define		GetNearComponent()					((Obj::CNearComponent*)GetComponent(CRC_NEAR))
+#define		GetNearComponentFromObject( pObj )	((Obj::CNearComponent*)(pObj)->GetComponent(CRC_NEAR))
+
+#define		MAX_SWEEP_PRUNE_ENTRIES_PER_AXIS	512
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	
+class CNearComponent;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sSweepPruneEntry
+{
+	uint32				m_min_max;			// 0 for min, 1 for max
+	float				m_value[3];			// This proves much quicker than dereferencing the component and reading the bounding box.
+	CNearComponent		*mp_component;		// Pointer to the actual component.
+
+	inline float		GetValue( int axis )	{ return m_value[axis]; }
+	void				SetValues( void );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sSweepPruneArray
+{
+public:
+	int					m_axis;
+	int					m_num_entries;
+	sSweepPruneEntry	m_array[MAX_SWEEP_PRUNE_ENTRIES_PER_AXIS];
+
+						sSweepPruneArray( int axis );
+						~sSweepPruneArray( void );
+
+	int					BinarySearchFirstBigger( float value );
+	int					BinarySearchLastSmaller( float value );
+	void				ResortComponent( CNearComponent *p_component );
+	void				InsertComponent( CNearComponent *p_component );
+	void				RemoveComponent( CNearComponent *p_component );
+	static int			BuildCompositeIntersectingArray( void );
+
+private:
+	void				PushUp( int index );
+	void				PullDown( int index );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CNearComponent : public CBaseComponent
+{
+public:
+    CNearComponent();
+    virtual ~CNearComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	Mth::Vector						GetMin( void )	{ return m_transformed_bbox.GetMin(); }
+	Mth::Vector						GetMax( void )	{ return m_transformed_bbox.GetMax(); }
+	CNearComponent**				GetIntersectingNearComponents( int *p_num_intersecting );
+	CNearComponent**				GetIntersectingNearComponents( Mth::Vector &bbmin, Mth::Vector &bbmax, int *p_num_intersecting );
+
+	static CBaseComponent*			s_create();
+
+private:
+
+	Mth::CBBox						m_untransformed_bbox;
+	Mth::CBBox						m_transformed_bbox;
+	static sSweepPruneArray			m_sweep_prune_array[3];
+};
+
+
+
+}
+
+#endif
+
+#endif
diff --git a/Code/Gel/Components/NodeArrayComponent.cpp b/Code/Gel/Components/NodeArrayComponent.cpp
new file mode 100644
index 0000000..1beabb5
--- /dev/null
+++ b/Code/Gel/Components/NodeArrayComponent.cpp
@@ -0,0 +1,221 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       NodeArrayComponent.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/17/03
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+CBaseComponent* CNodeArrayComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CNodeArrayComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CNodeArrayComponent::CNodeArrayComponent() : CBaseComponent()
+{
+	SetType( CRC_NODEARRAY );
+
+	mp_node_array = NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNodeArrayComponent::~CNodeArrayComponent()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CNodeArrayComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// Obtain the model name from the structure. At a later date, perhaps the node could override the .qb name.
+	const char *p_model_name = NULL;
+	if( !pParams->GetText( 0x286a8d26, &p_model_name ))
+	{
+		// This is currently an error.
+		Dbg_MsgAssert( 0, ( "Attempting to create NodeArrayComponent from node with no model name" ));
+		return;
+	}
+
+	// Generate the .qb name from the model name.
+	char nodearray_filename[256];
+	strcpy( nodearray_filename, "models\\" );
+	strcat( nodearray_filename, p_model_name );
+	char *p_ext = strstr( nodearray_filename, "." );
+	if( p_ext )
+	{
+		strcpy( p_ext, ".qb" );
+	}
+	else
+	{
+		strcat( nodearray_filename, ".qb" );
+	}
+
+	// Ask the Asset Manager for the .qb - it will be loaded if it is not yet memory resident.
+	Ass::CAssMan *p_ass_man = Ass::CAssMan::Instance();
+	mp_node_array = (Script::CArray*)p_ass_man->LoadOrGetAsset( nodearray_filename, false, false, false, 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CNodeArrayComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Obtain the model name from the structure. At a later date, perhaps the node could override the .qb name.
+	const char *p_model_name = NULL;
+	if( !pParams->GetText( 0x286a8d26, &p_model_name ))
+	{
+		// Not an error during a refresh, just exit with existing values maintained.
+		return;
+	}
+
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CNodeArrayComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CNodeArrayComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNodeArrayComponent::GetDebugInfo( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ("NULL p_info sent to CNodeArrayComponent::GetDebugInfo" ));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+	p_info->AddArray( "mp_node_array", mp_node_array );
+
+	// We call the base component's GetDebugInfo, so we can add info from the common base component.
+	CBaseComponent::GetDebugInfo( p_info );
+#endif				 
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/NodeArrayComponent.h b/Code/Gel/Components/NodeArrayComponent.h
new file mode 100644
index 0000000..b7377e8
--- /dev/null
+++ b/Code/Gel/Components/NodeArrayComponent.h
@@ -0,0 +1,54 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       NodeArrayComponent.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/17/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_NODEARRAYCOMPONENT_H__
+#define __COMPONENTS_NODEARRAYCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_NODEARRAY							CRCD( 0xc472ecc5, "NodeArray" )
+#define		GetNodeArrayComponent()					((Obj::CNodeArrayComponent*)GetComponent( CRC_NODEARRAY ))
+#define		GetNodeArrayComponentFromObject( pObj )	((Obj::CNodeArrayComponent*)(pObj)->GetComponent( CRC_NODEARRAY ))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CNodeArrayComponent : public CBaseComponent
+{
+public:
+    CNodeArrayComponent();
+    virtual ~CNodeArrayComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+	virtual Script::CArray*			GetNodeArray( void )	{ return mp_node_array; }
+
+private:
+	Script::CArray*					mp_node_array;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/ObjectHookManagerComponent.cpp b/Code/Gel/Components/ObjectHookManagerComponent.cpp
new file mode 100644
index 0000000..37193ef
--- /dev/null
+++ b/Code/Gel/Components/ObjectHookManagerComponent.cpp
@@ -0,0 +1,209 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ObjectHookManagerComponent.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/18/03
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+#include 
+
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+CBaseComponent* CObjectHookManagerComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CObjectHookManagerComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CObjectHookManagerComponent::CObjectHookManagerComponent() : CBaseComponent()
+{
+	SetType( CRC_OBJECTHOOKMANAGER );
+	mp_object_hook_manager = NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CObjectHookManagerComponent::~CObjectHookManagerComponent()
+{
+	// Remove the CObjectHookManager if present.
+	if( mp_object_hook_manager )
+	{
+		delete mp_object_hook_manager;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CObjectHookManagerComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// There needs to be a NodeArrayComponent attached for the ObjectHookManagerComponent to operate.
+	CNodeArrayComponent *p_nodearray_component = GetNodeArrayComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_nodearray_component, ( "ObjectHookManagerComponent created without NodeArrayComponent" ));
+
+	// Remove the CObjectHookManager if present.
+	if( mp_object_hook_manager )
+	{
+		delete mp_object_hook_manager;
+	}
+
+	// Create a CObjectHookManager, and parse the node array for the hooks.
+	mp_object_hook_manager = new Obj::CObjectHookManager();
+	mp_object_hook_manager->AddObjectHooksFromNodeArray( p_nodearray_component->GetNodeArray());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CObjectHookManagerComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CObjectHookManagerComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CObjectHookManagerComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CObjectHookManagerComponent::GetDebugInfo( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ("NULL p_info sent to CObjectHookManagerComponent::GetDebugInfo" ));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	if( mp_object_hook_manager )
+	{
+		p_info->AddString("ObjectHookManager.m_is_transformed", mp_object_hook_manager->IsMoving() ? "yes" : "no" );
+	}
+	
+	// We call the base component's GetDebugInfo, so we can add info from the common base component.
+	CBaseComponent::GetDebugInfo( p_info );
+#endif				 
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/ObjectHookManagerComponent.h b/Code/Gel/Components/ObjectHookManagerComponent.h
new file mode 100644
index 0000000..a09e47d
--- /dev/null
+++ b/Code/Gel/Components/ObjectHookManagerComponent.h
@@ -0,0 +1,55 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ObjectHookManagerComponent.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/18/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_OBJECTHOOKMANAGERCOMPONENT_H__
+#define __COMPONENTS_OBJECTHOOKMANAGERCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 	
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_OBJECTHOOKMANAGER							CRCD( 0x27443708, "ObjectHookManager" )
+#define		GetObjectHookManagerComponent()					((Obj::CObjectHookManagerComponent*)GetComponent( CRC_OBJECTHOOKMANAGER ))
+#define		GetObjectHookManagerComponentFromObject( pObj )	((Obj::CObjectHookManagerComponent*)(pObj)->GetComponent( CRC_OBJECTHOOKMANAGER ))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CObjectHookManagerComponent : public CBaseComponent
+{
+public:
+										CObjectHookManagerComponent();
+    virtual								~CObjectHookManagerComponent();
+
+public:
+    virtual void            			Update();
+    virtual void            			InitFromStructure( Script::CStruct* pParams );
+    virtual void            			RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult		CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 						GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*				s_create();
+
+	virtual Obj::CObjectHookManager*	GetObjectHookManager( void )	{ return mp_object_hook_manager; }
+
+private:
+	Obj::CObjectHookManager*			mp_object_hook_manager;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/ParticleComponent.cpp b/Code/Gel/Components/ParticleComponent.cpp
new file mode 100644
index 0000000..73582af
--- /dev/null
+++ b/Code/Gel/Components/ParticleComponent.cpp
@@ -0,0 +1,697 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ParticleComponent.cpp
+//* OWNER:          SPG
+//* CREATION DATE:  3/21/03
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CParticleComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CParticleComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CParticleComponent::CParticleComponent() : CBaseComponent()
+{
+    SetType( CRC_PARTICLE );
+
+	m_update_script = 0;
+	mp_particle = NULL;
+	m_system_lifetime = 0;
+	m_birth_time = Tmr::GetTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CParticleComponent::~CParticleComponent()
+{	
+	if( mp_particle )
+	{
+		// Remove it from the particle manager
+		Nx::CEngine::sGetParticleManager()->KillParticle( mp_particle );
+		// call the destroy function (platform specific cleanup)
+		mp_particle->Destroy();
+		// and delete it
+		delete mp_particle;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CParticleComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	int color;
+	float scale;
+	Script::CArray* array;
+	Nx::CParticleParams part;
+	Mth::Vector angles;
+	bool generate_name;
+	Mth::Matrix orientation;
+	// ** Add code to parse the structure, and initialize the component
+
+	scale = 1.0f;
+	generate_name = false;
+	pParams->GetChecksum( CRCD(0x92276db,"UpdateScript"), &m_update_script );
+	
+	pParams->GetInteger( CRCD(0x5a235299,"SystemLifetime"), (int*) &m_system_lifetime );
+	if( pParams->ContainsFlag(CRCD(0x18c671f0,"noname")))
+	{
+		// Generate a somewhat-unique id
+		generate_name = true;
+		part.m_Name = Tmr::GetTime();
+	}
+	else
+	{
+		pParams->GetChecksum( CRCD(0xa1dc81f9,"Name"), &part.m_Name );
+	}
+
+	pParams->GetChecksum( CRCD(0x7321a8d6,"Type"), &part.m_Type );
+	part.m_UseMidpoint = pParams->ContainsFlag(CRCD(0xa7a5bffb,"UseMidPoint"));
+	
+	pParams->GetFloat( CRCD(0x13b9da7b,"scale"), &scale, false );
+
+	pParams->GetFloat( CRCD(0x3bee67c8,"StartRadius"), &part.m_Radius[Nx::vBOX_START] );
+	pParams->GetFloat( CRCD(0x8e39913d,"MidRadius"), &part.m_Radius[Nx::vBOX_MID] );
+	pParams->GetFloat( CRCD(0x3f243a3c,"EndRadius"), &part.m_Radius[Nx::vBOX_END] );
+
+	orientation.Ident();
+	if( pParams->ContainsFlag( CRCD(0x7045843a,"orient_to_vel" )))
+	{
+		Mth::Vector vel;
+	
+        // Create a matrix with -vel as the AT vector of the matrix and transform the system
+		// by that matrix
+		pParams->GetVector(CRCD(0xc4c809e, "vel"), &vel, Script::ASSERT);
+		vel.Normalize();
+
+		orientation[Mth::AT] = vel;
+		if( orientation[Mth::AT] == Mth::Vector( 0, 1, 0 ))
+		{
+			orientation[Mth::RIGHT] = Mth::CrossProduct( vel, Mth::Vector( 0, 0, -1 ));
+		}
+		else
+		{
+			orientation[Mth::RIGHT] = Mth::CrossProduct( vel, Mth::Vector( 0, 1, 0 ));
+		}
+		orientation[Mth::UP] = Mth::CrossProduct( orientation[Mth::AT], orientation[Mth::RIGHT] );
+	}
+
+	part.m_Radius[Nx::vBOX_START] *= scale;
+	part.m_Radius[Nx::vBOX_MID] *= scale;
+	part.m_Radius[Nx::vBOX_END] *= scale;
+
+	pParams->GetFloat( CRCD(0x5400a25,"StartRadiusSpread"), &part.m_RadiusSpread[Nx::vBOX_START] );
+	pParams->GetFloat( CRCD(0xff7d9aa9,"MidRadiusSpread"), &part.m_RadiusSpread[Nx::vBOX_MID] );
+	pParams->GetFloat( CRCD(0x5539fcee,"EndRadiusSpread"), &part.m_RadiusSpread[Nx::vBOX_END] );
+
+	part.m_RadiusSpread[Nx::vBOX_START] *= scale;
+	part.m_RadiusSpread[Nx::vBOX_MID] *= scale;
+	part.m_RadiusSpread[Nx::vBOX_END] *= scale;
+
+	pParams->GetInteger( CRCD(0x9e52a20f,"MaxStreams"), &part.m_MaxStreams );
+	pParams->GetFloat( CRCD(0x35b65bd4,"EmitRate"), &part.m_EmitRate );
+	pParams->GetFloat( CRCD(0xc218cf77,"Lifetime"), &part.m_Lifetime );
+	pParams->GetFloat( CRCD(0x520682c6,"MidPointPCT"), &part.m_MidpointPct );
+	
+	if( pParams->ContainsFlag(CRCD(0x64f194ed,"UseStartPosition")))
+	{
+		pParams->GetVector( CRCD(0x3d5ea6a4,"StartPosition"), &part.m_BoxPos[Nx::vBOX_START] );
+	}
+	else
+	{
+		pParams->GetVector( CRCD(0x7f261953,"Pos"), &part.m_BoxPos[Nx::vBOX_START] );
+	}
+	
+	pParams->GetVector( CRCD(0x4b51a2,"MidPosition"), &part.m_BoxPos[Nx::vBOX_MID] );
+	pParams->GetVector( CRCD(0x5854ab9e,"EndPosition"), &part.m_BoxPos[Nx::vBOX_END] );
+
+	part.m_BoxPos[Nx::vBOX_START] *= scale;
+	part.m_BoxPos[Nx::vBOX_MID] *= scale;
+	part.m_BoxPos[Nx::vBOX_END] *= scale;
+
+	part.m_BoxPos[Nx::vBOX_START] = orientation.Transform( part.m_BoxPos[Nx::vBOX_START] );
+	part.m_BoxPos[Nx::vBOX_MID] = orientation.Transform( part.m_BoxPos[Nx::vBOX_MID] );
+	part.m_BoxPos[Nx::vBOX_END] = orientation.Transform( part.m_BoxPos[Nx::vBOX_END] );
+
+	pParams->GetVector( CRCD(0x9656f775,"BoxDimsStart"), &part.m_BoxDims[Nx::vBOX_START] );
+	pParams->GetVector( CRCD(0xc3cd20a2,"BoxDimsMid"), &part.m_BoxDims[Nx::vBOX_MID] );
+	pParams->GetVector( CRCD(0x829fe7dd,"BoxDimsEnd"), &part.m_BoxDims[Nx::vBOX_END] );
+
+	part.m_BoxDims[Nx::vBOX_START] *= scale;
+	part.m_BoxDims[Nx::vBOX_MID] *= scale;
+	part.m_BoxDims[Nx::vBOX_END] *= scale;
+
+	part.m_BoxDims[Nx::vBOX_START] = orientation.Transform( part.m_BoxDims[Nx::vBOX_START] );
+	part.m_BoxDims[Nx::vBOX_MID] = orientation.Transform( part.m_BoxDims[Nx::vBOX_MID] );
+	part.m_BoxDims[Nx::vBOX_END] = orientation.Transform( part.m_BoxDims[Nx::vBOX_END] );
+
+	part.m_UseMidcolor = pParams->ContainsFlag(CRCD(0x454962ef,"UseColorMidTime"));
+
+	pParams->GetFloat( CRCD(0x59463c93,"ColorMidTime"), &part.m_MidpointPct );
+
+	if( pParams->GetArray( CRCD(0xdab75b6c,"StartRGB"), &array ))
+	{
+		color = array->GetInt( 0 );
+		part.m_Color[Nx::vBOX_START].r = (uint8) color;
+		color = array->GetInt( 1 );
+		part.m_Color[Nx::vBOX_START].g = (uint8) color;
+		color = array->GetInt( 2 );
+		part.m_Color[Nx::vBOX_START].b = (uint8) color;
+	}
+	if( pParams->GetArray( CRCD(0x444769a,"MidRGB"), &array ))
+	{
+		color = array->GetInt( 0 );
+		part.m_Color[Nx::vBOX_MID].r = (uint8) color;
+		color = array->GetInt( 1 );
+		part.m_Color[Nx::vBOX_MID].g = (uint8) color;
+		color = array->GetInt( 2 );
+		part.m_Color[Nx::vBOX_MID].b = (uint8) color;
+	}
+	if( pParams->GetArray( CRCD(0x5a3728e7,"EndRGB"), &array ))
+	{
+		color = array->GetInt( 0 );
+		part.m_Color[Nx::vBOX_END].r = (uint8) color;
+		color = array->GetInt( 1 );
+		part.m_Color[Nx::vBOX_END].g = (uint8) color;
+		color = array->GetInt( 2 );
+		part.m_Color[Nx::vBOX_END].b = (uint8) color;
+	}
+
+	if( pParams->GetInteger( CRCD(0x4d83db4c,"StartAlpha"), &color ))
+	{
+		part.m_Color[Nx::vBOX_START].a = (uint8) color;
+	}
+	if( pParams->GetInteger( CRCD(0x4aba1ff1,"MidAlpha"), &color ))
+	{
+		part.m_Color[Nx::vBOX_MID].a = (uint8) color;
+	}
+	if( pParams->GetInteger( CRCD(0x5cf83aca,"EndAlpha"), &color ))
+	{
+		part.m_Color[Nx::vBOX_END].a = (uint8) color;
+	}
+	
+	pParams->GetChecksum( CRCD(0xdd6bb3d5,"BlendMode"), &part.m_BlendMode );
+	pParams->GetInteger( CRCD(0x9f677d4e,"AlphaCutoff"), &part.m_AlphaCutoff );
+	pParams->GetInteger( CRCD(0x13d0be1d,"FixedAlpha"), &part.m_FixedAlpha );
+	pParams->GetChecksum( CRCD(0x7d99f28d,"Texture"), &part.m_Texture );
+	
+	pParams->GetInteger( CRCD(0x4eedfae7,"lod_dist1"), &part.m_LODDistance1 );
+	pParams->GetInteger( CRCD(0xd7e4ab5d,"lod_dist2"), &part.m_LODDistance2 );
+	pParams->GetInteger( CRCD(0xe4279ba2,"SuspendDistance"), &part.m_SuspendDistance );
+
+	part.m_RotMatrix.Ident();	  // Mick: THis was missing before, so not sure how it could have worked
+
+	angles.Set();
+	pParams->GetVector( CRCD(0x9d2d0915,"Angles"), &angles );
+
+	part.m_RotMatrix.RotateX( angles[X] );
+	part.m_RotMatrix.RotateY( angles[Y] );
+	part.m_RotMatrix.RotateZ( angles[Z] );
+	
+
+	if (part.m_LocalCoord = pParams->ContainsFlag(CRCD(0xb7b7240d,"LocalSpace")))
+	{
+		for (int i = 0; i < Nx::vNUM_BOXES; i++)
+		{
+			part.m_LocalBoxPos[i] = part.m_BoxPos[i];
+		}
+	}
+
+	mp_particle = Nx::CEngine::sGetParticleManager()->CreateParticle( &part, generate_name );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CParticleComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	//InitFromStructure(pParams);
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CParticleComponent::Finalize()
+{
+	mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+}
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CParticleComponent::Hide( bool should_hide )
+{
+
+	mp_particle->Hide(should_hide);
+
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CParticleComponent::Update()
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+	// If it's a limited-time particle system in net games (like fireballs) never suspend the
+	// logic
+	if (!mp_suspend_component || !mp_suspend_component->SkipLogic() || 
+		( gamenet_man->InNetGame() && ( m_system_lifetime > 0 )))
+	{
+		// **  You would put in here the stuff that you would want to get run every frame
+		// **  for example, a physics type component might update the position and orientation
+		// **  and update the internal physics state 
+		if( m_update_script )
+		{
+			Script::RunScript( m_update_script, NULL, GetObject() );
+		}
+	
+		if( m_system_lifetime > 0 )
+		{
+			if(( Tmr::GetTime() - m_birth_time ) > m_system_lifetime )
+			{
+				GetObject()->MarkAsDead();
+				return;
+			}
+		}
+		// If we are using local coordinates, update all the world parameters in the
+		// particle instance.
+		if (mp_particle->GetParameters()->m_LocalCoord)
+		{
+			for (int i = 0; i < Nx::vNUM_BOXES; i++)
+			{
+				Mth::Vector world_pos(mp_particle->GetParameters()->m_LocalBoxPos[i] + GetObject()->m_pos );
+				//Mth::Vector world_pos(mp_particle->GetParameters()->m_LocalBoxPos[i] + Mth::Vector(100.0f, 100.0f, 0.0f));
+				
+				mp_particle->SetBoxPos(i, &world_pos);
+			}
+		}
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CParticleComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case 0x88be2262:	// SetStartPos
+		{
+			Mth::Vector pos;
+
+			pParams->GetVector( NONAME, &pos, true );
+			if( mp_particle )
+			{
+				mp_particle->SetBoxPos( Nx::vBOX_START, &pos );
+			}
+			break;
+		}
+        case 0xbb6018f4:	// SetMidPos
+		{
+			Mth::Vector pos;
+
+			pParams->GetVector( NONAME, &pos, true );
+			if( mp_particle )
+			{
+				mp_particle->SetBoxPos( Nx::vBOX_MID, &pos );
+			}
+			break;
+		}
+		case 0xe5134689:	// SetEndPos
+		{
+			Mth::Vector pos;
+
+			pParams->GetVector( NONAME, &pos, true );
+			if( mp_particle )
+			{
+				mp_particle->SetBoxPos( Nx::vBOX_END, &pos );
+			}
+			break;
+		}
+		case 0xf71d696e:	// SetStartBoxDimensions
+		{
+			Mth::Vector dims;
+
+			pParams->GetVector( NONAME, &dims, true );
+			if( mp_particle )
+			{
+				mp_particle->SetBoxDims( Nx::vBOX_START, &dims );
+			}
+			break;
+		}
+		case 0xaab006d:		// SetMidBoxDimensions
+		{
+			Mth::Vector dims;
+
+			pParams->GetVector( NONAME, &dims, true );
+			if( mp_particle )
+			{
+				mp_particle->SetBoxDims( Nx::vBOX_MID, &dims );
+			}
+			break;
+		}
+		case 0xe2b99038:	// SetEndBoxDimensions
+		{
+			Mth::Vector dims;
+
+			pParams->GetVector( NONAME, &dims, true );
+			if( mp_particle )
+			{
+				mp_particle->SetBoxDims( Nx::vBOX_END, &dims );
+			}
+			break;
+		}
+		case 0xc036f4d0:	// SetUpdateScript
+		{
+			pParams->GetChecksum( NONAME, &m_update_script, true );
+			break;
+		}
+		case 0xc6525c4e:	// SetEmitRate
+		{
+			float emit_rate;
+
+			pParams->GetFloat( NONAME, &emit_rate, true );
+			if( mp_particle )
+			{
+				mp_particle->SetEmitRate( emit_rate );
+			}
+			break;
+		}
+		case 0x31fcc8ed:	// SetLifetime
+		{
+			float lifetime;
+
+			pParams->GetFloat( NONAME, &lifetime, true );
+			if( mp_particle )
+			{
+				mp_particle->SetLifetime( lifetime );
+			}
+			break;
+		}
+		case 0x19af74d1:	// SetMidpointColorPct
+		{
+			float percent;
+
+			pParams->GetFloat( NONAME, &percent, true );
+			if( mp_particle )
+			{
+				mp_particle->SetMidpointColorPct( percent );
+			}
+			break;
+		}
+		case 0x4aa40fb5:	// SetMidPointPCT
+		{
+			float percent;
+
+			pParams->GetFloat( NONAME, &percent, true );
+			if( mp_particle )
+			{
+				mp_particle->SetMidpointPct( percent );
+			}
+			break;
+		}
+		case 0x234ceabb:	// SetStartRadius
+		{
+			float radius;
+
+			pParams->GetFloat( NONAME, &radius, true );
+			if( mp_particle )
+			{
+				mp_particle->SetRadius( Nx::vBOX_START, radius );
+			}
+			break;
+		}
+		case 0x9e100f60:	// SetMidRadius
+		{
+			float radius;
+
+			pParams->GetFloat( NONAME, &radius, true );
+			if( mp_particle )
+			{
+				mp_particle->SetRadius( Nx::vBOX_MID, radius );
+			}
+			break;
+		}
+		case 0x2f0da461:	// SetEndRadius
+		{
+			float radius;
+
+			pParams->GetFloat( NONAME, &radius, true );
+			if( mp_particle )
+			{
+				mp_particle->SetRadius( Nx::vBOX_END, radius );
+			}
+			break;
+		}
+		case 0xeeffae18:	// SetStartColor
+		{
+			Script::CArray* array;
+
+			pParams->GetArray( NONAME, &array, true );
+			if( mp_particle )
+			{
+				Image::RGBA color;
+
+				color.r = array->GetInt( 0 );
+				color.g = array->GetInt( 1 );
+				color.b = array->GetInt( 2 );
+				color.a = array->GetInt( 3 );
+				mp_particle->SetColor( Nx::vBOX_START, &color );
+			}
+			break;
+		}
+		case 0xfe869e8:		// SetMidColor
+		{
+			Script::CArray* array;
+
+			pParams->GetArray( NONAME, &array, true );
+			if( mp_particle )
+			{
+				Image::RGBA color;
+
+				color.r = array->GetInt( 0 );
+				color.g = array->GetInt( 1 );
+				color.b = array->GetInt( 2 );
+				color.a = array->GetInt( 3 );
+				mp_particle->SetColor( Nx::vBOX_MID, &color );
+			}
+			break;
+		}
+		case 0x19aa4cd3:	// SetEndColor
+		{
+			Script::CArray* array;
+
+			pParams->GetArray( NONAME, &array, true );
+			if( mp_particle )
+			{
+				Image::RGBA color;
+
+				color.r = array->GetInt( 0 );
+				color.g = array->GetInt( 1 );
+				color.b = array->GetInt( 2 );
+				color.a = array->GetInt( 3 );
+				mp_particle->SetColor( Nx::vBOX_END, &color );
+			}
+			break;
+		}
+		
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CParticleComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Script::CArray* array;
+	Nx::CParticleParams* part;
+
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CParticleComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	if( mp_particle )
+	{
+		part = mp_particle->GetParameters();
+
+		p_info->AddChecksum( CRCD(0x92276db,"UpdateScript"), m_update_script );
+		p_info->AddChecksum( CRCD(0x7321a8d6,"Type"), part->m_Type );
+		p_info->AddChecksum( CRCD(0xa7a5bffb,"UseMidPoint"), part->m_UseMidpoint );
+		
+		p_info->AddFloat(CRCD(0x3bee67c8,"StartRadius"), part->m_Radius[Nx::vBOX_START] );
+		p_info->AddFloat(CRCD(0x8e39913d,"MidRadius"), part->m_Radius[Nx::vBOX_MID] );
+		p_info->AddFloat(CRCD(0x3f243a3c,"EndRadius"), part->m_Radius[Nx::vBOX_END] );
+	
+		p_info->AddInteger(CRCD(0x9e52a20f,"MaxStreams"), part->m_MaxStreams );
+		p_info->AddFloat(CRCD(0x35b65bd4,"EmitRate"), part->m_EmitRate );
+		p_info->AddFloat(CRCD(0xc218cf77,"Lifetime"), part->m_Lifetime );
+		p_info->AddFloat(CRCD(0x520682c6,"MidPointPCT"), part->m_MidpointPct );
+		
+		p_info->AddVector(CRCD(0x3d5ea6a4,"StartPosition"), part->m_BoxPos[Nx::vBOX_START] );
+		p_info->AddVector(CRCD(0x4b51a2,"MidPosition"), part->m_BoxPos[Nx::vBOX_MID] );
+		p_info->AddVector(CRCD(0x5854ab9e,"EndPosition"), part->m_BoxPos[Nx::vBOX_END] );
+	
+		p_info->AddVector(CRCD(0x9656f775,"BoxDimsStart"), part->m_BoxDims[Nx::vBOX_START] );
+		p_info->AddVector(CRCD(0xc3cd20a2,"BoxDimsMid"), part->m_BoxDims[Nx::vBOX_MID] );
+		p_info->AddVector(CRCD(0x829fe7dd,"BoxDimsEnd"), part->m_BoxDims[Nx::vBOX_END] );
+	
+		p_info->AddChecksum(CRCD(0x454962ef,"UseColorMidTime"), part->m_UseMidcolor );
+		
+		p_info->AddFloat(CRCD(0x59463c93,"ColorMidTime"), part->m_ColorMidpointPct );
+	
+		array = new Script::CArray;
+		array->SetSizeAndType( 4, ESYMBOLTYPE_INTEGER );
+		array->SetInteger( 0, part->m_Color[Nx::vBOX_START].r );
+		array->SetInteger( 1, part->m_Color[Nx::vBOX_START].g );
+		array->SetInteger( 2, part->m_Color[Nx::vBOX_START].b );
+		array->SetInteger( 3, part->m_Color[Nx::vBOX_START].a );
+		p_info->AddArray(CRCD(0xfb35aacf,"StartColor"), array );
+		delete array;
+	
+		array = new Script::CArray;
+		array->SetSizeAndType( 4, ESYMBOLTYPE_INTEGER );
+		array->SetInteger( 0, part->m_Color[Nx::vBOX_MID].r );
+		array->SetInteger( 1, part->m_Color[Nx::vBOX_MID].g );
+		array->SetInteger( 2, part->m_Color[Nx::vBOX_MID].b );
+		array->SetInteger( 3, part->m_Color[Nx::vBOX_MID].a );
+		p_info->AddArray(CRCD(0xfc0c6e72,"MidColor"), array );
+		delete array;
+	
+		array = new Script::CArray;
+		array->SetSizeAndType( 4, ESYMBOLTYPE_INTEGER );
+		array->SetInteger( 0, part->m_Color[Nx::vBOX_END].r );
+		array->SetInteger( 1, part->m_Color[Nx::vBOX_END].g );
+		array->SetInteger( 2, part->m_Color[Nx::vBOX_END].b );
+		array->SetInteger( 3, part->m_Color[Nx::vBOX_END].a );
+		p_info->AddArray(CRCD(0xea4e4b49,"EndColor"), array );
+		delete array;
+	
+		p_info->AddChecksum(CRCD(0xdd6bb3d5,"BlendMode"), part->m_BlendMode );
+		p_info->AddInteger(CRCD(0x9f677d4e,"AlphaCutoff"), part->m_AlphaCutoff );
+		p_info->AddChecksum(CRCD(0x7d99f28d,"Texture"), part->m_Texture );
+		
+		p_info->AddInteger(CRCD(0x4eedfae7,"lod_dist1"), part->m_LODDistance1 );
+		p_info->AddInteger(CRCD(0xd7e4ab5d,"lod_dist2"), part->m_LODDistance2 );
+		p_info->AddInteger(CRCD(0xe4279ba2,"SuspendDistance"), part->m_SuspendDistance );
+	}
+
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/ParticleComponent.h b/Code/Gel/Components/ParticleComponent.h
new file mode 100644
index 0000000..3ab700a
--- /dev/null
+++ b/Code/Gel/Components/ParticleComponent.h
@@ -0,0 +1,63 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ParticleComponent.h
+//* OWNER:          SPG
+//* CREATION DATE:  03/21/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_PARTICLECOMPONENT_H__
+#define __COMPONENTS_PARTICLECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_PARTICLE CRCD(0xa9db601e,"Particle")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetParticleComponent() ((Obj::CParticleComponent*)GetComponent(CRC_PARTICLE))
+#define		GetParticleComponentFromObject(pObj) ((Obj::CParticleComponent*)(pObj)->GetComponent(CRC_PARTICLE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CParticleComponent : public CBaseComponent
+{
+public:
+    CParticleComponent();
+    virtual ~CParticleComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void					Hide( bool should_hide );
+	virtual void					Finalize();
+		
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+private:
+	uint32				m_update_script;
+	Nx::CNewParticle*	mp_particle;
+	CSuspendComponent*	mp_suspend_component;
+	Tmr::Time			m_birth_time;
+	uint32				m_system_lifetime;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/PedLogicComponent.cpp b/Code/Gel/Components/PedLogicComponent.cpp
new file mode 100644
index 0000000..eeeb62a
--- /dev/null
+++ b/Code/Gel/Components/PedLogicComponent.cpp
@@ -0,0 +1,2497 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       PedLogicComponent.cpp
+//* OWNER:          Brad Bulkley
+//* CREATION DATE:  1/9/03
+//****************************************************************************
+
+// start autoduck documentation
+// @DOC pedlogiccomponent
+// @module pedlogiccomponent | None
+// @subindex Scripting Database
+// @index script | pedlogiccomponent
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+namespace Obj
+{
+
+#define	vDEFAULT_RANGE (10.0f)
+#define vACTION_WEIGHT_RESOLUTION (1000)
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CPedLogicComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CPedLogicComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPedLogicComponent::CPedLogicComponent() : CBaseComponent()
+{
+	SetType( CRC_PEDLOGIC );
+
+	// reset whiskers
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+		m_whiskers[i] = 0.0;
+
+	ResetBias();
+	m_turn_frames = 0;
+	m_max_turn_frames = 0; // Script::GetInteger( CRCD( 0x793535f5, "ped_turn_frames" ), Script::ASSERT );
+	mp_path_object_tracker = NULL;
+
+	m_col_dist_above = Script::GetFloat( CRCD( 0x379d93d2, "ped_skater_stick_dist_above" ), Script::ASSERT );
+	m_col_dist_below = Script::GetFloat( CRCD( 0x8715b7b2, "ped_skater_stick_dist_below" ), Script::ASSERT );
+
+	m_flags = 0;
+
+	m_original_max_vel = 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPedLogicComponent::~CPedLogicComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+	// m_state = CRCD( 0x23db4aea, "Idle" );s
+
+	pParams->GetInteger( CRCD( 0xe50d6573, "nodeIndex" ), &m_node_from, Script::ASSERT );
+	// printf("m_node_from = %i\n", m_node_from);
+	SkateScript::GetPosition( m_node_from, &m_wp_from );
+	m_normal_lerp = 0.0f;
+	m_display_normal = m_last_display_normal = m_current_normal = GetObject()->m_matrix[Y];
+
+	pParams->GetFloat( CRCD( 0x367c4a1b, "StickToGroundDistAbove" ), &m_col_dist_above );
+	pParams->GetFloat( CRCD( 0x86f46e7b, "StickToGroundDistBelow" ), &m_col_dist_below );
+
+	m_current_display_matrix = GetObject()->GetDisplayMatrix();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::Update()
+{	
+	m_time = Tmr::FrameLength();
+	
+	// BB - Switch on the state and call the appropriate update function.
+	// This step could be removed by having the logic for each state in 
+	// this function, but this will make it easier to split the component 
+	// if need be.  Plus it just looks cleaner.
+	switch ( m_state )
+	{
+		case CRCC( 0x69b05e2c, "generic" ):
+			GenericPedUpdate();
+			break;
+		case CRCC( 0xa85af587, "generic_skater" ):
+			GenericSkaterUpdate();
+			break;
+		case CRCC(0x6367167b,"lip_trick"):
+			UpdateLipDisplayMatrix();
+			break;
+		case CRCC( 0x82b45a67, "generic_standing" ):
+			// this is currently handled in script
+			break;
+	default:
+		// Dbg_MsgAssert( 0, ( "PedLogicComponent has unknown state %s", Script::FindChecksumName( m_state ) ) );
+		break;
+	}
+	// Gfx::AddDebugArrow( GetObject()->GetDisplayMatrix()[X] + GetObject()->m_pos, GetObject()->GetDisplayMatrix()[X] * 48 + GetObject()->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 1 );
+	// Gfx::AddDebugArrow( GetObject()->GetDisplayMatrix()[Y] + GetObject()->m_pos, GetObject()->GetDisplayMatrix()[Y] * 48 + GetObject()->m_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 0, 128 ), 1 );
+	// Gfx::AddDebugArrow( GetObject()->GetDisplayMatrix()[Z] + GetObject()->m_pos, GetObject()->GetDisplayMatrix()[Z] * 48 + GetObject()->m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 0, 0, 128 ), 1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CPedLogicComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Ped_SetLogicState | set the current state of the ped
+		// @uparm name | state - eg, generic, generic_skater, etc.
+		case CRCC( 0x3685b765, "Ped_SetLogicState" ):
+			pParams->GetChecksum( NONAME, &m_state, Script::ASSERT );
+			break;
+		// @script | Ped_InitPath | initializes the ped on his linked path and starts movement
+		case CRCC( 0xe9906815, "Ped_InitPath" ):
+		{
+			Script::CStruct* pNodeData = SkateScript::GetNode( m_node_from );
+			// m_node_to = SkateScript::GetLink( pNodeData, Mth::Rnd( SkateScript::GetNumLinks( pNodeData ) ) );
+			
+			#ifdef __NOPT_ASSERT__
+			int numLinks = SkateScript::GetNumLinks( m_node_from );
+			Dbg_MsgAssert( numLinks, ( "Ped_InitPath called on %s, but path node %i has no links!", Script::FindChecksumName( GetObject()->GetID() ), m_node_from ) );
+			#endif
+
+			m_node_to = SkateScript::GetLink( pNodeData, 0 );
+			SkateScript::GetPosition( m_node_to, &m_wp_to );
+			SelectNextWaypoint();
+			m_flags |= PEDLOGIC_MOVING_ON_PATH;
+			mp_path_object_tracker = Obj::CPathMan::Instance()->TrackPed( GetObject(), m_node_to );
+			
+			Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+			Dbg_Assert( pMotionComp );
+			pMotionComp->m_movingobj_status |= MOVINGOBJ_STATUS_ON_PATH;
+
+			// turn the ped to face the right direction
+			Mth::Vector heading = ( m_wp_to - m_wp_from ).Normalize();
+			Mth::Matrix display_matrix = GetObject()->GetDisplayMatrix();
+			display_matrix[Z] = heading;
+			display_matrix.OrthoNormalizeAbout( Z );
+			GetObject()->SetDisplayMatrix( display_matrix );
+			m_current_display_matrix = display_matrix;
+
+			Mth::Vector current_pos = GetObject()->m_pos;
+			for ( int i = 0; i < vPOS_HISTORY_SIZE; i++ )
+				m_pos_history[i] = current_pos;
+
+			break;
+		}
+		// @script | Ped_StartMoving | start moving on path
+		case CRCC( 0xd711542, "Ped_StartMoving" ):
+		{
+			if ( m_flags & PEDLOGIC_STOPPED )
+			{
+				Script::CScript* pScript = Script::SpawnScript( CRCD( 0xbed339d4, "ped_skater_start_moving" ) );
+				pScript->mpObject = GetObject();
+				m_flags &= ~PEDLOGIC_STOPPED;
+			}
+			m_flags |= PEDLOGIC_MOVING_ON_PATH;
+			break;
+		}
+		// @script | Ped_StopMoving | stop moving on path
+		case CRCC( 0xcc85adfe, "Ped_StopMoving" ):
+			m_flags &= ~PEDLOGIC_MOVING_ON_PATH;
+			break;
+		// @script | Ped_IsMoving | returns true if the ped is moving on the path
+		case CRCC(0x68d298b0,"Ped_IsMoving"):
+			return m_flags & PEDLOGIC_MOVING_ON_PATH ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+		// @script | Ped_SetStickToGroundDist | Set the distances the ped uses to check for ground
+		// when sticking.  This check is not performed straight up and down, but at whatever
+		// angle the ped is currently standing (his Y vector).
+		// @parmopt float | StickToGroundDistAbove | | distance to check above the ped
+		// @parmopt float | StickToGroundDistBelow | | distance to check below the ped
+		case CRCC( 0x2cc086dd, "Ped_SetStickToGroundDist" ):
+			pParams->GetFloat( "distAbove", &m_col_dist_above );
+			pParams->GetFloat( "distBelow", &m_col_dist_below );
+			printf("StickToGround distances set to %f (above), %f (below)\n", m_col_dist_above, m_col_dist_below);
+			break;
+		// @script | Ped_SetIsGrinding | used for manually setting the grind state on and off
+		// @uparm 0 | 0 for off, anything else for on
+		case CRCC( 0xc643e8f5, "Ped_SetIsGrinding" ):
+		{
+			int is_grinding = 0;
+			pParams->GetInteger( NONAME, &is_grinding, Script::ASSERT );
+			if ( is_grinding != 0 )
+				m_flags |= PEDLOGIC_GRINDING;
+			else
+				m_flags &= ~PEDLOGIC_GRINDING;
+			break;
+		}
+		// @script | Ped_GetCurrentVelocity | returns variable named velocity...
+		// @flag max | get the maximum velocity, rather than the currnt velocity.
+		// This is useful in case the ped is currently accelerating or decelerating 
+		// to achieve maximum velocity.
+		case CRCC( 0x13713760, "Ped_GetCurrentVelocity" ):
+		{
+			Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+			Dbg_Assert( pMotionComp );
+			pScript->GetParams()->AddFloat( CRCD( 0x41272956, "velocity" ), pMotionComp->m_vel_z );
+			break;
+		}
+		// @script | Ped_StoreMaxVelocity | stores the current max velocity, so you can
+		// change it and restore it later
+		case CRCC( 0xf5627596, "Ped_StoreMaxVelocity" ):
+		{
+			Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+			Dbg_Assert( pMotionComp );
+			m_original_max_vel = pMotionComp->m_max_vel;
+			break;
+		}
+		// @script | Ped_GetOriginalMaxVelocity | returns original_max_velocity stored when 
+		// Ped_StoreMaxVelocity was called
+		case CRCC( 0xe575efa9, "Ped_GetOriginalMaxVelocity" ):
+		{
+			pScript->GetParams()->AddFloat( "original_max_velocity", m_original_max_vel );
+			break;
+		}
+		// @script | Ped_Bail |
+		case CRCC( 0xee24a410, "Ped_Bail" ):
+			Bail();
+			break;
+		case CRCC( 0xc076d74, "Ped_SetIsBailing" ):
+		{
+			int v;
+			pParams->GetInteger( NONAME, &v, Script::ASSERT );
+			if ( pParams->ContainsFlag( CRCD( 0x255ed86f, "grind" ) ) )
+			{
+				if ( v == 0 )
+					m_flags &= ~PEDLOGIC_GRIND_BAILING;
+				else
+					m_flags |= PEDLOGIC_GRIND_BAILING;				
+			}
+			else
+			{
+				if ( v == 0 )
+					m_flags &= ~PEDLOGIC_BAILING;
+				else
+					m_flags |= PEDLOGIC_BAILING;
+			}
+			break;
+		}
+		case CRCC( 0xda7be9e2, "Ped_InitVertTrick" ):
+		{
+			// get the height and gravity
+			float height;
+			pParams->GetFloat( CRCD( 0xab21af0, "height" ), &height, Script::ASSERT );
+			float gravity;
+			pParams->GetFloat( CRCD( 0xa5e2da58, "gravity" ), &gravity, Script::ASSERT );
+			Dbg_MsgAssert( gravity < 0.0f, ( "Ped_InitVertTrick given positive number for gravity" ) );
+
+			Mth::Vector pt = m_wp_to - GetObject()->m_pos;
+			// adjust height to compensate for difference between ped height
+			// and height of waypoint
+			// height += pt[Y];
+			// pt[Y] = 0.0f;
+
+			// figure the time - the gravity is always expressed as negative 
+			// acceleration, but since we're figuring the time it will take to
+			// fall from the top at velocity=0, we use positive
+			// Multiply by 2 to get time up and down
+			// d = vi*t + (a*t^2)/2
+			float time = 2 * sqrtf( 2 * -height / gravity );
+
+			// compute the inital y velocity, which equals the final y velocity when
+			// falling back down (vf^2 = vi^2 + 2ad)
+			float vi = sqrtf( 2 * gravity * -height );
+			Dbg_Assert( vi > 0 );
+
+			// find the horizontal distance we need to travel
+			float distance = pt.Length() / 2;
+			// bump it up a bit so we don't miss landing
+			distance *= Script::GetFloat( CRCD( 0x2b5bedba, "ped_skater_vert_jump_slop_distance" ), Script::ASSERT );
+
+			// horizontal velocity
+			Mth::Vector vh = ( distance / time ) * pt.Normalize();
+
+			// vertical velocity
+			Mth::Vector vy = vi * Mth::Vector(0, 1, 0);
+
+			// total speed and heading
+			// float jump_speed = ( ( vh + vy ) * time ).Length();
+			float jump_speed = ( vh + vy ).Length();
+			Mth::Vector heading = ( vh + vy ).Normalize();
+
+			pScript->GetParams()->AddVector( CRCD( 0xfd4bc03e, "heading" ), heading );
+			pScript->GetParams()->AddFloat( CRCD( 0x1c4c5690, "jumpSpeed" ), jump_speed );
+			pScript->GetParams()->AddFloat( CRCD( 0x66ced87b, "jumpTime" ), time );
+			break;
+		}
+		// @script | Ped_HitWaypoint | force the ped to register a hit on the
+		// to waypoint, regardless of his position.  Don't call this unless
+		// you know exactly why you're doing it.
+		case CRCC( 0x1affffb3, "Ped_HitWaypoint" ):
+			HitWaypoint();
+			break;
+		// @script | Ped_SetIsSkater | 
+		// @uparm 1 | 0 if not skater, anything else for skater
+		case CRCC( 0x5c794c57, "Ped_SetIsSkater" ):
+		{
+			int is_skater;
+			pParams->GetInteger( NONAME, &is_skater, Script::ASSERT );
+			if ( is_skater == 0 )
+				m_flags &= ~PEDLOGIC_IS_SKATER;
+			else
+				m_flags |= PEDLOGIC_IS_SKATER;
+			break;
+		}
+		// @script | Ped_NearVertNode | true if the next node or the to node
+		// is vert
+		case CRCC( 0x5bb23915, "Ped_NearVertNode" ):
+			return ( ( ( m_flags & PEDLOGIC_TO_NODE_IS_VERT )
+					 || ( m_flags & PEDLOGIC_NEXT_NODE_IS_VERT )
+					 || ( m_flags & PEDLOGIC_FROM_NODE_IS_VERT ) ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE );
+			break;
+		case CRCC( 0x56d9f55f, "Ped_PlayJumpSound" ):
+		{
+			//Dbg_MsgAssert( m_state == CRCD(0xa85af587,"generic_skater"), ( "%s\nPed_PlayJumpSound called on a non-skater ped %s",pScript->GetScriptInfo(),Script::FindChecksumName(GetObject()->GetID()) ) );
+			Obj::CSkaterSoundComponent *pSoundComponent = GetSkaterSoundComponentFromObject( GetObject() );
+			if ( pSoundComponent )
+				pSoundComponent->PlayJumpSound(m_speed_fraction);
+			break;
+		}
+		case CRCC( 0x749de914, "Ped_PlayLandSound" ):
+		{
+			Dbg_MsgAssert( m_state == CRCD(0xa85af587,"generic_skater"), ( "Ped_PlayLandSound called on a non-ped skater" ) );
+			Obj::CSkaterSoundComponent *pSoundComponent = GetSkaterSoundComponentFromObject( GetObject() );
+			if ( pSoundComponent )
+				pSoundComponent->PlayLandSound(m_speed_fraction);
+			break;
+		}
+		case CRCC( 0x58a588b5, "Ped_GetCurrentNodeNames" ):
+		{
+			pScript->GetParams()->AddChecksum( CRCD( 0xd14cd5bc, "node_to" ), m_node_to );
+			pScript->GetParams()->AddChecksum( CRCD( 0x8141d319, "node_from" ), m_node_from );
+			pScript->GetParams()->AddChecksum( CRCD( 0x3c746255, "node_next" ), m_node_next );
+			break;
+		}
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to C......Component::GetDebugInfo"));
+	p_info->AddChecksum( "m_state", m_state );
+	
+	Script::CArray* pTempArray = new Script::CArray();
+	pTempArray->SetSizeAndType( vNUM_WHISKERS, ESYMBOLTYPE_FLOAT );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+		pTempArray->SetFloat( i, m_whiskers[i] );
+	p_info->AddArray( "m_whiskers", pTempArray );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+		pTempArray->SetFloat( i, m_bias[i] );
+	p_info->AddArray( "m_bias", pTempArray );
+	Script::CleanUpArray( pTempArray );
+	delete pTempArray;
+
+	p_info->AddInteger( "moving_on_path", m_flags & PEDLOGIC_MOVING_ON_PATH );
+
+	p_info->AddInteger( "m_node_from", m_node_from );
+	p_info->AddInteger( "m_node_to", m_node_to );
+	p_info->AddInteger( "m_node_next", m_node_next );
+
+	p_info->AddVector( "m_wp_from", m_wp_from );
+	p_info->AddVector( "m_wp_to", m_wp_to );
+	p_info->AddVector( "m_wp_next", m_wp_next );
+
+	p_info->AddInteger( "m_turn_frames", m_turn_frames );
+	p_info->AddInteger( "m_max_turn_frames", m_max_turn_frames );
+
+	p_info->AddInteger( "m_is_grinding", m_flags & PEDLOGIC_GRINDING );
+
+	p_info->AddFloat( "m_col_dist_above", m_col_dist_above );
+	p_info->AddFloat( "m_col_dist_below", m_col_dist_below );
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPedLogicComponent::GenericPedUpdate()
+{	
+	if ( !( m_flags & PEDLOGIC_MOVING_ON_PATH ) )
+		return;
+
+	Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+	Dbg_Assert( pMotionComp );
+	// Obj::CPathObjectTracker* p_path_object_tracker = pMotionComp->mp_path_object_tracker;
+
+	bool is_avoiding = CheckForOtherPeds();	
+	bool is_turning = UpdateTargetBias();
+
+	// update bias based on path following
+	if ( !is_turning && !is_avoiding )
+	{
+		UpdatePathBias();
+	}
+	
+	// Gfx::AddDebugLine( m_wp_from, m_wp_to, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+	// Gfx::AddDebugLine( GetObject()->m_pos, m_wp_to, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 1 );
+
+	// move and decay
+	pMotionComp->DoPathPhysics();
+	UpdatePosition();
+	DecayWhiskerBiases();
+	pMotionComp->StickToGround();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::GenericSkaterUpdate()
+{
+	Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+	Dbg_Assert( pMotionComp );
+	pMotionComp->DoPathPhysics();
+	float distance = m_time * pMotionComp->m_vel_z;
+	bool is_jumping = pMotionComp->m_movingobj_status & MOVINGOBJ_STATUS_JUMPING ? true : false;
+
+	// Gfx::AddDebugLine( m_wp_from, m_wp_to, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+	// Gfx::AddDebugLine( GetObject()->m_pos, m_wp_to, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 1 );
+	// Gfx::AddDebugLine( m_pos_history[vPOS_HISTORY_SIZE - 2] + Mth::Vector( 0, 5, 1 ), m_pos_history[vPOS_HISTORY_SIZE - 1] + Mth::Vector( 0, 5, 1 ), MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ) );
+	// Gfx::AddDebugArrow( GetObject()->GetDisplayMatrix()[Z] + GetObject()->m_pos, GetObject()->GetDisplayMatrix()[Z] * 24 + GetObject()->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 1 );
+	
+	// check for skater if we're grinding
+	if ( m_flags & PEDLOGIC_GRINDING )
+	{
+		Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+		Dbg_Assert( skate_mod );
+		Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+		if ( pSkater )
+		{
+			float d = Mth::DistanceSqr( GetObject()->m_pos, pSkater->GetPos() );
+			// printf("skater dist square = %f\n", d );
+			if ( d < Script::GetFloat( CRCD( 0xbc2e678a, "ped_skater_min_square_distance_to_skater" ), Script::ASSERT ) )
+			{
+				GrindBail();
+				return;
+			}
+		}
+	}
+	
+	// see if we've hit the waypoint
+	if ( ( m_flags & PEDLOGIC_MOVING_ON_PATH ) || is_jumping )
+	{
+		Mth::Vector pt;
+		if ( ( m_flags & PEDLOGIC_TO_NODE_IS_VERT ) || is_jumping )
+		{
+			pt = ( GetObject()->m_pos - m_wp_to );
+		}
+		else
+		{
+			Mth::Vector wp_to_no_y = m_wp_to;
+			wp_to_no_y[Y] = 0.0f;
+			Mth::Vector ped_pos_no_y = GetObject()->m_pos;
+			ped_pos_no_y[Y] = 0.0f;
+			pt = ped_pos_no_y - wp_to_no_y;
+		}
+		float distance_to_target_square = pt.LengthSqr();
+		int min_dist_square = Script::GetInteger( CRCD( 0xfb5d106f, "ped_skater_min_square_distance_to_waypoint" ), Script::ASSERT );
+	
+		if ( !( m_flags & PEDLOGIC_JUMPING_TO_NODE ) )
+		{
+			if ( distance_to_target_square < Mth::Sqr( distance ) )
+				HitWaypoint();
+			else if ( distance_to_target_square < min_dist_square )
+				HitWaypoint();
+		}
+
+		if ( m_flags & PEDLOGIC_JUMP_AT_TO_NODE )
+		{
+			int dist_to_crouch = Script::GetInteger( CRCD( 0x52ec432f, "ped_skater_min_square_distance_to_crouch_for_jump" ), Script::ASSERT );
+			if ( distance_to_target_square < dist_to_crouch )
+			{
+				// clear flag!
+				m_flags &= ~PEDLOGIC_JUMP_AT_TO_NODE;
+
+				Script::CScript* pScript = Script::SpawnScript( CRCD( 0x9c7ab5f5, "ped_skater_crouch_for_jump" ) );
+				pScript->mpObject = GetObject();
+			}
+		}
+	}
+
+	// move
+	if ( ( m_flags & PEDLOGIC_MOVING_ON_PATH ) && distance )
+	{
+		Mth::Vector original_pos = GetObject()->m_pos;
+		bool already_adjusted_heading = false;
+		if ( ShouldUseBiases() )
+		{
+			// use biases to update position
+			bool is_turning = UpdateTargetBias();
+			if ( !is_turning )
+			{
+				UpdatePathBias();
+			}
+			UpdatePosition();
+			
+			// refresh the display matrix if we're jumping, as 
+			// AdjustHeading and AdjustNormal won't be called.
+			if ( is_jumping )
+			{
+				RefreshDisplayMatrix();
+			}
+		}
+		else
+		{
+			// figure the new position
+			Mth::Vector new_pos = original_pos + ( distance * ( m_wp_to - GetObject()->m_pos ).Normalize() );
+			UpdatePathBias();
+			UpdatePosition();			
+			GetObject()->m_pos = new_pos;
+			AdjustHeading( original_pos );
+			already_adjusted_heading = true;
+		}
+
+		// stick to ground if we're not grinding or jumping
+		if ( !( m_flags & PEDLOGIC_GRINDING )
+			 && !( m_flags & PEDLOGIC_GRIND_BAILING )
+			 && !is_jumping
+			)
+		{
+			StickToGround();
+			// update z vector
+			if ( !already_adjusted_heading )
+				AdjustHeading( original_pos );
+			AdjustNormal();
+		}
+	}
+	else // if ( !( pMotionComp->m_movingobj_status & MOVINGOBJ_STATUS_JUMPING ) )
+	{
+		RotatePosHistory();
+		if ( m_flags & PEDLOGIC_TO_NODE_IS_VERT )
+		{
+			// AdjustHeading();
+		}
+		RefreshDisplayMatrix();
+		if ( m_flags & PEDLOGIC_DOING_VERT_ROTATION || m_flags & PEDLOGIC_DOING_SPINE )
+		{
+			DoRotations();
+		}
+	}
+
+	// update states for looping sounds
+	if ( is_jumping )
+	{
+		UpdateSkaterSoundStates( AIR );
+	}
+	else if ( m_flags & PEDLOGIC_GRINDING )
+	{
+		UpdateSkaterSoundStates( RAIL );
+	}
+	else if ( m_flags & PEDLOGIC_MOVING_ON_PATH )
+	{
+		UpdateSkaterSoundStates( GROUND );
+	}
+	else if ( m_flags & PEDLOGIC_DOING_LIP_TRICK )
+	{
+		UpdateSkaterSoundStates( LIP );
+	}
+ 	
+	if ( m_flags & PEDLOGIC_MOVING_ON_PATH )
+		m_speed_fraction = ( pMotionComp->m_vel_z / pMotionComp->m_max_vel );
+	else
+		m_speed_fraction = 0.0f;
+	
+	Obj::CSkaterLoopingSoundComponent* pLoopingSoundComp = GetSkaterLoopingSoundComponentFromObject( GetObject() );
+	if ( pLoopingSoundComp )
+	{
+		// reset bailing flag
+		pLoopingSoundComp->SetIsBailing( m_flags & PEDLOGIC_GRIND_BAILING || m_flags & PEDLOGIC_BAILING );
+		pLoopingSoundComp->SetSpeedFraction( m_speed_fraction );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPedLogicComponent::CheckForOtherPeds()
+{
+	bool is_avoiding = false;
+	if ( mp_path_object_tracker )
+	{		
+		const CSmtPtr* pp_object_list = mp_path_object_tracker->GetObjectList();
+		Mth::Matrix display_matrix = GetObject()->GetDisplayMatrix();
+		float range = Script::GetFloat( CRCD( 0x7a78f471, "ped_avoid_ped_range" ) );
+		float avoid_ped_bias = Script::GetFloat( CRCD( 0xd1e6177b, "ped_avoid_ped_bias" ) );
+		Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+		Dbg_Assert( pMotionComp );
+
+		for ( int i = 0; i < CPathObjectTracker::MAX_OBJECTS_PER_PATH; ++i )
+		{
+			if ( pp_object_list[i] )
+			{
+				CCompositeObject* p_ob=pp_object_list[i].Convert();
+	
+				if ( p_ob != GetObject() )
+				{
+					// ignore peds that are too far above or below
+					float max_y_dist = Script::GetFloat( CRCD(0x21b13ffc,"ped_max_y_distance_to_ignore"), Script::ASSERT );
+					if ( Mth::Abs( GetObject()->GetPos()[Y] - p_ob->GetPos()[Y] ) > max_y_dist )
+					{
+						continue;
+					}
+					
+					// ignore peds that are facing the opposite direction and walking away
+					float z_dot = Mth::DotProduct( display_matrix[Z], p_ob->GetDisplayMatrix()[Z] );
+					if ( z_dot <= 0.0f && Mth::DotProduct( display_matrix[Z], p_ob->GetPos() - GetObject()->GetPos() ) <= 0.0f )
+					{
+						// printf("walking away from each other\n");
+						continue;
+					}
+					
+					// ignore peds that are facing the same direction and going faster than us
+					Obj::CMotionComponent* pObMotionComp = GetMotionComponentFromObject( p_ob );
+					Dbg_Assert( pObMotionComp );
+					if ( z_dot > 0.0f && pObMotionComp->m_max_vel >= pMotionComp->m_max_vel )
+					{
+						// printf("he's going faster than me\n");
+						continue;
+					}
+
+					float d = Mth::Distance( GetObject()->GetPos(), p_ob->GetPos() );					
+					if ( d <= range )
+					{						
+						// adjust whisker
+						// find the heading
+						float theta = acosf( z_dot / ( display_matrix[Z].Length() * p_ob->GetDisplayMatrix()[Z].Length() ) );
+						
+						// printf("theta = %f\n", theta);
+						// reduce to less than 2pi.
+						if ( theta > Mth::PI * 2 )
+							theta -= Mth::PI * 2 * (int)( theta / ( Mth::PI * 2 ) );
+						// if they're coming straight at each other, we'll have to throw in some
+						// sideways bias - we don't want them to stop moving or turn around
+						// As they start to pass each other, the offset will naturally start
+						// to create its own bias
+						if ( fabs( theta - Mth::PI ) < Script::GetFloat( CRCD( 0xd2e22cec, "ped_head_on_range" ), Script::ASSERT ) )
+						{
+							// printf("he's coming right for me!\n");
+							// left by default
+							theta = Mth::PI / 2;
+							// see if we should push them right
+							// if ( theta > Mth::PI )
+							if ( ( Mth::CrossProduct( display_matrix[Z], p_ob->GetDisplayMatrix()[Z] ) )[Y] < 0 )
+								theta *= -1;
+						}
+						is_avoiding = true;
+						AddWhiskerBias( theta, avoid_ped_bias, d, range );
+					}
+				}
+			}
+		}
+	}
+	return is_avoiding;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::UpdatePathBias()
+{
+	Mth::Vector wp_from_no_y = m_wp_from;
+	wp_from_no_y[Y] = 0.0f;
+	Mth::Vector wp_to_no_y = m_wp_to;
+	wp_to_no_y[Y] = 0.0f;
+	Mth::Vector ped_pos_no_y = GetObject()->m_pos;
+	ped_pos_no_y[Y] = 0.0f;
+
+	float max_dist_to_path = Script::GetFloat( CRCD( 0xa876aa2c, "ped_max_distance_to_path" ), Script::ASSERT );
+	Mth::Vector ft = wp_to_no_y - wp_from_no_y;
+	Mth::Vector fp = ped_pos_no_y - wp_from_no_y;
+
+	// need to store this vector so we can check the sign of y...
+	// if it's negative, we're on one side of the path, positive for the other
+	Mth::Vector ft_cross_fp = Mth::CrossProduct( ft, fp );
+
+	// get the direction we want to push the ped
+	Mth::Vector closest_pos_on_path = ( ( Mth::DotProduct( fp, ft ) / ft.LengthSqr() ) * ft ) + m_wp_from;
+	closest_pos_on_path[Y] = 0.0f;
+	Mth::Vector pos_to_path = closest_pos_on_path - ped_pos_no_y;
+
+	// Gfx::AddDebugArrow( GetObject()->m_pos, closest_pos_on_path, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 1 );
+
+	// Mth::Matrix ob_matrix = GetObject()->m_matrix;
+	Mth::Matrix display_matrix = GetObject()->GetDisplayMatrix();
+	float angle_to_path = Mth::GetAngle( display_matrix[Z], pos_to_path );
+	if ( angle_to_path > 360.0f )
+		angle_to_path -= 360.0f * (int)( angle_to_path / 360.0f );
+	if ( Mth::CrossProduct( display_matrix[Z], pos_to_path )[Y] > 0 )
+		angle_to_path = 360.0f - angle_to_path;
+
+	// float path_length = Mth::Distance( m_wp_from, m_wp_to );
+	float path_length = Mth::Distance( wp_from_no_y, wp_to_no_y );
+	float current_distance = ft_cross_fp.Length() / path_length;
+
+	// the bias should get stronger as we get farther away, approaching 1 as we 
+	// approach the max distance from the path
+	float path_bias;
+	if ( current_distance > max_dist_to_path )
+		path_bias = 1;
+	else
+		path_bias = 1 - ( ( max_dist_to_path - current_distance ) / max_dist_to_path );
+
+	// reset the bias to get back on the path
+	if ( current_distance > Script::GetFloat( CRCD( 0x81085734, "ped_min_distance_to_path" ) ) )
+		AdjustBias( Mth::DegToRad( angle_to_path ), path_bias );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPedLogicComponent::UpdateTargetBias()
+{
+	bool is_turning = false;
+	Mth::Vector wp_to_no_y = m_wp_to;
+	wp_to_no_y[Y] = 0.0f;
+	Mth::Vector ped_pos_no_y = GetObject()->m_pos;
+	ped_pos_no_y[Y] = 0.0f;
+	Mth::Vector pt = ped_pos_no_y - wp_to_no_y;
+	float distance_to_target_square = pt.LengthSqr();
+	
+	float target_bias = Script::GetFloat( CRCD(0x57342672,"ped_target_node_bias"), Script::ASSERT );
+	ResetBias();
+
+	if ( m_node_next == m_node_from )
+	{
+		float min_square_distance_to_dead_end = Script::GetFloat( CRCD( 0x65b46f32, "ped_walker_min_square_distance_to_dead_end" ), Script::ASSERT );
+		if ( distance_to_target_square <= min_square_distance_to_dead_end )
+		{
+			HitWaypoint();
+		}
+	}
+	else
+	{
+		int fade_bias_max_distance_square;
+		if ( m_flags & PEDLOGIC_IS_SKATER )
+			fade_bias_max_distance_square = Script::GetInteger( CRCD( 0x354beda1, "ped_skater_fade_target_bias_max_distance_square" ), Script::ASSERT );
+		else
+			fade_bias_max_distance_square = Script::GetInteger( CRCD( 0x5b21ab0e, "ped_fade_target_bias_max_distance_square" ), Script::ASSERT );
+
+		if ( m_max_turn_frames && m_turn_frames >= m_max_turn_frames )
+		{
+			HitWaypoint();
+		}
+		else if ( m_node_next != -1 && distance_to_target_square < fade_bias_max_distance_square )
+		{
+			// do we need to reset m_max_turn_frames?
+			if ( !m_max_turn_frames )
+			{
+				// figure how long it will take to get there if we don't turn
+				Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+				Dbg_Assert( pMotionComp );
+				
+				Mth::Vector wp_from_no_y = m_wp_from;
+				wp_from_no_y[Y] = 0.0f;
+				Mth::Vector wp_to_no_y = m_wp_to;
+				wp_to_no_y[Y] = 0.0f;
+				Mth::Vector wp_next_no_y = m_wp_next;
+				wp_next_no_y[Y] = 0.0f;
+
+				Mth::Vector ft = wp_to_no_y - wp_from_no_y;
+				Mth::Vector tn = wp_next_no_y - wp_to_no_y;
+
+				float angle_between_waypoints = Mth::DegToRad( Mth::GetAngle( ft, tn ) );
+				float distance_to_waypoint_switch = sqrtf( ( 2 * distance_to_target_square ) * ( 1 + cosf( angle_between_waypoints ) ) );
+				int frames = (int)( ( sqrtf( distance_to_waypoint_switch ) / pMotionComp->m_max_vel ) * 60.0f );
+				// printf("frames = %i\n", frames);
+				m_max_turn_frames = frames;
+				if ( m_max_turn_frames <= 0 )
+				{
+					m_max_turn_frames = 1;
+				}
+				
+				// int frames = (int)( ( sqrtf( distance_to_target_square ) / pMotionComp->m_max_vel ) * 60.0f );
+				// m_max_turn_frames = frames / 2;
+			}
+	
+			// add bias for new waypoint and adjust current target bias
+			is_turning = true;
+
+			// Guard against potential division by zero here.
+			float target_bias_turn_percent;
+			if( m_max_turn_frames == 0 )
+				target_bias_turn_percent = 0.0f;
+			else
+				target_bias_turn_percent = 0.5f * ( 1 - (float)( m_max_turn_frames - m_turn_frames ) / (float)m_max_turn_frames );
+
+			m_turn_frames++;
+			AddTargetBias( m_wp_next, target_bias * target_bias_turn_percent );
+			target_bias -= target_bias * target_bias_turn_percent;
+		}
+	}
+	AddTargetBias( m_wp_to, target_bias );
+	return is_turning;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::UpdatePosition()
+{
+	Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+	Dbg_Assert( pMotionComp );
+
+	float x_bias = GetTotalXBias();
+	float z_bias = GetTotalZBias();
+	// printf("x_bias = %f, z_bias = %f\n", x_bias, z_bias );
+
+	Mth::Vector last_pos = GetObject()->m_pos;
+	
+	RotatePosHistory();
+	
+	float vel = pMotionComp->m_vel_z;
+	float distance_to_travel = vel * m_time;
+	float x_distance = distance_to_travel * x_bias;
+	float z_distance = distance_to_travel * z_bias;
+/*	float total_bias = x_bias + z_bias;
+	if ( !total_bias )
+		return;
+	float vel = pMotionComp->m_vel_z;
+
+	float distance_to_travel = vel * m_time;
+	float x_distance = distance_to_travel * ( ( total_bias - x_bias ) / total_bias );
+	if ( x_bias < 0 )
+		x_distance *= -1;
+	float z_distance = distance_to_travel * ( ( total_bias - z_bias ) / total_bias );
+	if ( z_bias < 0 )
+		z_distance *= -1;
+*/   
+	// printf("x_dist = %f, z_dist = %f\n", x_distance, z_distance);
+	// Mth::Matrix mat0 = GetObject()->m_matrix;
+	Mth::Matrix mat0 = GetObject()->GetDisplayMatrix();
+	Mth::Vector z_vect = mat0[Z] * z_distance;
+	Mth::Vector x_vect = mat0[X] * x_distance;
+	
+	// GetObject()->GetDisplayMatrix()[Z] = new_heading;
+	// GetObject()->GetDisplayMatrix().OrthoNormalizeAbout( Z );
+	GetObject()->m_pos += x_vect;
+	GetObject()->m_pos += z_vect;
+	
+	// skaters do their own headings
+	if ( last_pos != GetObject()->m_pos && !( m_flags & PEDLOGIC_IS_SKATER ) )
+		AdjustHeading( last_pos );
+
+/*
+	float angle_to_new_heading = atan2( x_distance, z_distance );
+	if ( fabs( angle_to_new_heading ) > Script::GetFloat( "ped_max_angle_to_heading", Script::ASSERT ) )
+	{
+		GetObject()->GetDisplayMatrix().Rotate( mat0[Y], angle_to_new_heading );
+		// mat0.Rotate( mat0[Y], angle_to_new_heading );
+		// GetObject()->m_matrix.Rotate( mat0[Y], angle_to_new_heading );
+		// printf( "angle_to_new_heading = %f\n", Mth::RadToDeg( angle_to_new_heading ) );
+	}
+*/
+	// GetObject()->SetDisplayMatrix( mat0 );
+	// GetObject()->m_matrix = mat0;
+	// GetObject()->m_pos += adjustment;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::RotatePosHistory()
+{
+	for ( int i = 0; i < vPOS_HISTORY_SIZE - 1; i++ )
+	{
+		m_pos_history[i] = m_pos_history[i + 1];
+	}
+	m_pos_history[vPOS_HISTORY_SIZE - 1] = GetObject()->m_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::AddTargetBias( Mth::Vector target, float target_bias )
+{
+	Mth::Matrix display_matrix = GetObject()->GetDisplayMatrix();
+	
+	Mth::Vector ped_pos_no_y = GetObject()->m_pos;
+	ped_pos_no_y[Y] = 0.0f;
+	target[Y] = 0.0f;
+
+	// Mth::Vector pt = GetObject()->m_pos - target;
+	Mth::Vector pt = ped_pos_no_y - target;
+	float angle_to_target = Mth::GetAngle( display_matrix[Z], pt );
+	angle_to_target += 180.0f;
+	if ( angle_to_target > 360.0f )
+		angle_to_target -= 360.0f * (int)( angle_to_target / 360.0f );
+
+	if ( ( Mth::CrossProduct( display_matrix[Z], pt ) )[Y] > 0 )
+		angle_to_target = 360.0f - angle_to_target;
+
+	// printf("angle_to_target = %f\n", angle_to_target );
+	
+	// add bias to get to the target - the target_bias is constant
+	AdjustBias( Mth::DegToRad( angle_to_target ), target_bias );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::AddWhiskerBias( float angle, float amount, float distance, float range )
+{
+	// for now, linearly decrease the bias amount within the range
+	amount *= ( range - distance ) / range;
+
+	float segment_arc = Mth::DegToRad( 360 / vNUM_WHISKERS );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+	{
+		float angle_between = ( segment_arc * i ) - angle;
+		// make sure this is in the right quandrant
+		if ( fabs ( angle_between ) >= Mth::PI / 2 && fabs( angle_between ) <= 3 * Mth::PI / 2 )
+			continue;
+
+		// float adjusted_amount = fabs( amount * sinf( angle_between ) );
+		float adjusted_amount = amount * cosf( angle_between );
+		// printf("adding %f to m_whiskers[%i]\n", adjusted_amount, i );
+		// add this bias to the whisker
+		m_whiskers[i] += adjusted_amount;
+
+		// cap absolute value to 1
+		if ( m_whiskers[i] > 1 )
+			m_whiskers[i] = 1;
+		if ( m_whiskers[i] < -1 )
+			m_whiskers[i] = -1;
+	}
+/*
+	float segment_arc = Mth::DegToRad( 360 / vNUM_WHISKERS );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+	{
+		float angle_between = fabs( ( segment_arc * i ) - angle );
+		
+		// make sure this is less than 180 from the angle we're looking at
+		if ( angle_between >= Mth::PI )
+			continue;
+
+		// don't bother with maxed bias
+		if ( m_whiskers[i] == 1 )
+			continue;
+		
+		// add this bias to the whisker
+		m_whiskers[i] += fabs( amount * cos( angle_between ) );
+
+		// cap to 1
+		if ( m_whiskers[i] > 1 )
+			m_whiskers[i] = 1;
+	}
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::AdjustBias( float angle, float amount )
+{
+	// printf("AdjustBias( %f, %f )\n", angle, amount );
+	// translate the bias to the whisker(s)
+	// makes sure the angle isn't greater than 360
+	// printf("angle = %f\n", Mth::RadToDeg( angle ) );
+	float segment_arc = Mth::DegToRad( 360 / vNUM_WHISKERS );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+	{		
+		float angle_between = ( segment_arc * i ) - angle;
+		
+		// make sure this is in the right quandrant
+		if ( fabs ( angle_between ) >= Mth::PI / 2 && fabs( angle_between ) <= 3 * Mth::PI / 2 )
+			continue;
+
+		// float adjusted_amount = fabs( amount * sinf( angle_between ) );
+		float adjusted_amount = amount * cosf( angle_between );
+		// printf("adding %f to m_bias[%i]\n", adjusted_amount, i );
+		// add this bias to the whisker
+		m_bias[i] += adjusted_amount;
+
+		// cap absolute value to 1
+		if ( m_bias[i] > 1 )
+			m_bias[i] = 1;
+		if ( m_bias[i] < -1 )
+			m_bias[i] = -1;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DecayWhiskerBiases()
+{
+	// multiply each whisker by the decay factor
+	float decay_factor = Script::GetFloat( CRCD( 0x98cd0c8c, "ped_whisker_decay_factor" ), Script::ASSERT );
+	float min_bias = Script::GetFloat( CRCD( 0x4aba9798, "ped_min_bias" ), Script::ASSERT );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+	{
+		m_whiskers[i] *= decay_factor;
+		if ( m_whiskers[i] < min_bias )
+			m_whiskers[i] = 0;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CPedLogicComponent::GetTotalXBias()
+{
+	float total = 0.0f;
+	float segment_arc = Mth::DegToRad( 360 / vNUM_WHISKERS );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+	{
+		// float angle = Mth::PI - ( segment_arc * i );
+		float angle = segment_arc * i;
+		// phase shift 180
+		// angle += Mth::PI;
+		float whisker_total = m_whiskers[i] + m_bias[i];
+		// float whisker_total = m_whiskers[i];
+		// float whisker_total = m_bias[i];
+    	if ( whisker_total > 1 )
+			whisker_total = 1;
+		
+		total += -sinf( angle ) * whisker_total;
+		
+		if ( total >= 1 )
+			return 1;
+		if ( total <= -1 )
+			return -1;
+	}
+	if ( fabs( total ) < Script::GetFloat( CRCD( 0x4aba9798, "ped_min_bias" ), Script::ASSERT ) )
+		total = 0;
+	// remember that we're 180 degrees out of phase
+	return total;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CPedLogicComponent::GetTotalZBias()
+{
+	float total = 0.0f;
+	float segment_arc = Mth::DegToRad( 360 / vNUM_WHISKERS );
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+	{
+		float angle = segment_arc * i;
+		// phase shift 180
+		// angle += Mth::PI;
+		float whisker_total = m_whiskers[i] + m_bias[i];
+		// float whisker_total = m_whiskers[i];
+		// float whisker_total = m_bias[i];
+		if ( whisker_total > 1 )
+			whisker_total = 1;
+		if ( whisker_total < -1 )
+			whisker_total = -1;
+		// printf("whisker_total[%i] = %f, angle = %f\n", i, whisker_total, angle);
+		// 90 degree phase shift
+		// total += cosf( angle ) * whisker_total;
+		total += cosf( angle ) * whisker_total;
+		if ( total >= 1)
+			return 1;
+		if ( total <= -1 )
+			return -1;
+	}
+	if ( fabs( total ) < Script::GetFloat( CRCD( 0x4aba9798, "ped_min_bias" ), Script::ASSERT ) )
+		total = 0;
+	// return total;
+	return total;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::ResetBias()
+{
+	for ( int i = 0; i < vNUM_WHISKERS; i++ )
+		m_bias[i] = 0;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::HitWaypoint()
+{
+	// printf( "CPedLogicComponent::HitWaypoint\n" );
+	int hit_node = m_node_to;
+	int old_from_node = m_node_from;
+	
+	m_flags &= ~PEDLOGIC_FROM_NODE_IS_VERT;
+	m_flags &= ~PEDLOGIC_NEXT_NODE_IS_VERT;
+	if ( m_flags & PEDLOGIC_TO_NODE_IS_VERT )
+		m_flags |= PEDLOGIC_FROM_NODE_IS_VERT;
+
+	m_flags &= ~PEDLOGIC_JUMPING_TO_NODE;
+
+	m_node_from = m_node_to;
+	m_node_to = m_node_next;
+	m_wp_from = m_wp_to;
+	m_wp_to = m_wp_next;
+	
+	// handle dead ends for ped walkers
+	if ( !( m_flags & PEDLOGIC_IS_SKATER ) && old_from_node == m_node_to )
+	{
+		Script::CScript* pDeadEndScript = Script::SpawnScript( CRCD( 0xa80f965e, "ped_walker_hit_dead_end" ) );
+		pDeadEndScript->mpObject = GetObject();
+	}
+	
+	// update m_to_node_is_vert for distance checks
+	m_flags &= ~PEDLOGIC_TO_NODE_IS_VERT;
+	// m_to_node_is_vert = false;
+	Script::CStruct* pToNodeData = SkateScript::GetNode( m_node_to );
+
+	m_flags &= ~PEDLOGIC_TO_NODE_IS_LIP;
+
+	uint32 to_node_type;
+	if ( pToNodeData->GetChecksum( CRCD( 0x17ea37fb, "PedType" ), &to_node_type, Script::NO_ASSERT )
+		 && to_node_type == CRCD( 0x54166acd, "skate" ) )
+	{
+		uint32 to_node_action;
+		pToNodeData->GetChecksum( CRCD( 0x963dc198, "SkateAction" ), &to_node_action, Script::ASSERT );
+		if ( WaypointIsVert( m_node_to ) )
+		{
+			m_flags |= PEDLOGIC_TO_NODE_IS_VERT;
+			// m_to_node_is_vert = true;
+		}
+		
+		// check for a jump next - we'll need to crouch if we're not
+		// already doing some action
+		if ( to_node_action == CRCD( 0x7d9d0008, "vert_grab" )
+			 || to_node_action == CRCD( 0x584cf9e9, "jump" ) )
+		{
+			// make sure we're not coming from an action
+			Script::CStruct* pFromNodeData = SkateScript::GetNode( m_node_from );
+			uint32 from_node_action;
+			if ( pFromNodeData->GetChecksum( CRCD( 0x963dc198, "SkateAction" ), &from_node_action, Script::ASSERT ) )
+			{
+				if ( from_node_action != CRCD( 0x255ed86f, "grind" )
+					 && !( m_flags & PEDLOGIC_GRINDING )
+					 && !( m_flags & PEDLOGIC_FROM_NODE_IS_VERT ) )
+				{
+					m_flags |= PEDLOGIC_JUMP_AT_TO_NODE;
+				}
+			}
+		}
+
+		if ( to_node_action == CRCD( 0x554dbd64, "vert_lip" ) )
+		{
+			m_flags |= PEDLOGIC_TO_NODE_IS_LIP;
+		}
+	}
+	
+	SelectNextWaypoint();
+	if ( WaypointIsVert( m_node_next ) )
+		m_flags |= PEDLOGIC_NEXT_NODE_IS_VERT;
+
+	// we don't want to do any actions if we just came from a vert_grab node
+	Script::CStruct* pOldFromNodeData = SkateScript::GetNode( old_from_node );
+	uint32 old_skate_action;
+	Script::CStruct* pNodeData = SkateScript::GetNode( hit_node );
+	Dbg_Assert( pNodeData );
+	DoGenericNodeActions( pNodeData );
+
+	if ( !( pOldFromNodeData->GetChecksum( CRCD( 0x963dc198, "skateAction" ), &old_skate_action )
+		 && old_skate_action == CRCD( 0x7d9d0008, "Vert_Grab" ) ) )
+	{
+		uint32 waypoint_type;
+		if ( pNodeData->GetChecksum( CRCD( 0x17ea37fb, "PedType" ), &waypoint_type, Script::NO_ASSERT ) )
+		{
+			switch ( waypoint_type )
+			{
+			case CRCC( 0x726e85aa, "walk" ):
+				DoWalkActions( pNodeData );
+				break;
+			case CRCC( 0x54166acd, "skate" ):
+				DoSkateActions( pNodeData );
+				break;
+			default:
+				Dbg_MsgAssert( 0, ( "Waypoint has bad PedType %s", Script::FindChecksumName( waypoint_type ) ) );
+			}
+		}
+	}
+
+	m_turn_frames = m_max_turn_frames = 0;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPedLogicComponent::WaypointIsVert( int waypoint )
+{
+	Script::CStruct* pNodeData = SkateScript::GetNode( waypoint );
+	uint32 node_action;
+	if ( pNodeData->GetChecksum( CRCD( 0x963dc198, "SkateAction" ), &node_action, Script::NO_ASSERT ) )
+	{
+		if ( node_action == CRCD( 0x7d9d0008, "vert_grab" )
+			 || node_action == CRCD( 0x554dbd64, "vert_lip" )
+			 || node_action == CRCD( 0xe6dfaec7, "vert_grind" )
+			 || node_action == CRCD( 0xda0723da, "vert_land" )
+			 || node_action == CRCD( 0xe8f91257, "vert_flip" )
+			 || node_action == CRCD( 0xd5b4f014, "vert_jump" ) )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoWalkActions( Script::CStruct* pNodeData )
+{
+
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoGenericNodeActions( Script::CStruct* pNodeData )
+{
+	Dbg_Assert( pNodeData );
+	// check for special things we should do at this node
+	if ( pNodeData->ContainsFlag( CRCD( 0x74e4bba5, "AdjustSpeed" ) ) )
+	{
+		if ( ShouldExecuteAction( pNodeData, CRCD( 0x922c5b59, "AdjustSpeedWeight" ) ) )
+		{
+			float percent;
+			pNodeData->GetFloat( CRCD( 0xa04d16ec, "AdjustSpeedPercent" ), &percent, Script::ASSERT );
+			AdjustSpeed( percent );
+		}
+	}
+	if ( pNodeData->ContainsFlag( CRCD( 0x55d98ed7, "RunScript" ) ) )
+	{
+		if ( ShouldExecuteAction( pNodeData, CRCD( 0x86c9ccce, "RunScriptWeight" ) ) )
+		{
+			uint32 script_name;
+			pNodeData->GetChecksum( CRCD( 0x64b4cd9d, "RunScriptName" ), &script_name, Script::ASSERT );
+			Script::CScript* pScript = Script::SpawnScript( script_name );
+			pScript->mpObject = GetObject();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoSkateActions( Script::CStruct* pNodeData )
+{
+	// we don't want to do any actions if we're bailing
+	if ( ( m_flags & PEDLOGIC_BAILING ) || ( m_flags & PEDLOGIC_GRIND_BAILING ) )
+		return;
+	
+	Dbg_Assert( pNodeData );
+	uint32 skate_action;
+	if ( pNodeData->GetChecksum( CRCD( 0x963dc198, "SkateAction" ), &skate_action, Script::NO_ASSERT ) )
+	{
+		switch ( skate_action )
+		{
+			case CRCC( 0xec1cd520, "continue" ):
+				break;
+			case CRCC( 0x255ed86f, "grind" ):
+			case CRCC( 0xe6dfaec7, "vert_grind" ):
+				DoGrind( pNodeData );
+				break;
+			case CRCC( 0xc870bce5, "grind_off" ):
+				DoGrindOff( pNodeData );
+				break;
+			case CRCC(0xe41199af,"grind_bail"):
+				// ignore these nodes...we must have fallen here from a grind
+				break;
+			case CRCC(0xba1d1e94,"flip_trick"):
+				DoFlipTrick( pNodeData );
+				break;
+			case CRCC( 0xe8f91257, "vert_flip" ):
+				DoFlipTrick( pNodeData, true );
+				break;
+			case CRCC( 0x12797a1b, "grab_trick" ):
+				DoGrab( pNodeData );
+				break;
+			case CRCC( 0x7d9d0008, "vert_grab" ):
+				DoGrab( pNodeData, true );
+				break;
+			case CRCC( 0x554dbd64, "vert_lip" ):
+				DoLipTrick( pNodeData );
+				break;
+			case CRCC( 0xda0723da, "vert_land" ):
+				break;
+			case CRCC( 0x584cf9e9, "jump" ):
+				DoJump( pNodeData );
+				break;
+			case CRCC( 0xd5b4f014, "vert_jump" ):
+				DoJump( pNodeData, true );
+				break;
+			case CRCC( 0x6d3144bf, "Roll_off" ):
+				DoRollOff( pNodeData );
+				break;
+			case CRCC( 0xef24413b, "manual" ):
+				DoManual( pNodeData );
+				break;
+			case CRCC( 0x9403a0ed, "manual_down" ):
+				DoManualDown( pNodeData );
+				break;
+			case CRCC( 0x46a9e949, "stop" ):
+				Stop( pNodeData );
+				break;
+			default:
+				Dbg_MsgAssert( 0, ( "DoSkateActions found unknown action %s\n", Script::FindChecksumName( skate_action ) ) );
+				break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::SelectNextWaypoint()
+{
+	// are we on a grind bail path?
+	bool on_grind_bail_path = false;
+	Script::CStruct* pNode = SkateScript::GetNode( m_node_from );
+	uint32 skate_action;
+	if ( pNode->GetChecksum( CRCD( 0x963dc198, "SkateAction" ), &skate_action )
+		 && skate_action == CRCD( 0xe41199af, "Grind_Bail" ) )
+	{
+		on_grind_bail_path = true;
+	}
+	
+	int waypoint;
+	int numLinks;
+	numLinks = SkateScript::GetNumLinks( m_node_to );
+
+	if ( numLinks == 0 )
+	{
+		// turn around
+		waypoint = m_node_from;
+	}
+	else if ( numLinks == 1 ) // shortcut if there's only one link
+	{
+		waypoint = SkateScript::GetLink( m_node_to, 0 );
+		
+		// special case for jumping straight up in a pipe...land where you started
+		if ( waypoint == m_node_from )
+		{
+			Script::CStruct* pToNodeData = SkateScript::GetNode( m_node_to );
+			uint32 skate_action;
+			if ( pToNodeData->GetChecksum( CRCD( 0x963dc198, "skateAction" ), &skate_action )
+				 && skate_action == CRCD( 0x7d9d0008, "Vert_Grab" ) )
+			{
+				waypoint = m_node_to;
+			}
+		}
+
+	}
+	else
+	{
+		// sort the links by priority
+		Mth::Vector to_from_vector = m_wp_to - m_wp_from;
+		int valid_high_priority_choices[20];
+		int valid_low_priority_choices[20];
+		Dbg_MsgAssert( numLinks < 20, ( "Too many links (max is 20) from node %i\n", m_node_to ) );
+		for ( int i = 0; i < numLinks; i++ )
+		{
+			valid_high_priority_choices[i] = i;
+			valid_low_priority_choices[i] = i;
+		}
+	
+		int num_valid_low_choices = 0;
+		int num_valid_high_choices = 0;
+		int min_inner_angle = Script::GetInteger( CRCD( 0xfe65d822, "ped_min_inner_path_angle" ), Script::ASSERT );
+		
+		// store the max angle, so we can return the best choice if we don't find any good ones
+		float max_angle = 0.0f;
+		int max_angle_index = 0;
+		for ( int i = 0; i < numLinks; i++ )
+		{
+			int test_node = SkateScript::GetLink( m_node_to, i );
+	
+			if ( test_node != m_node_from )
+			{
+				Script::CStruct* pNodeData = SkateScript::GetNode( test_node );
+
+				// ignore nodes that aren't PedAI waypoints
+				uint32 class_type = 0;
+				uint32 waypoint_type = 0;
+				pNodeData->GetChecksum( CRCD( 0x12b4e660, "class" ), &class_type );
+				pNodeData->GetChecksum( CRCD( 0x7321a8d6, "Type" ), &waypoint_type );
+				if ( class_type != CRCD( 0x4c23a77e, "Waypoint" ) || waypoint_type != CRCD( 0xcba10ffa, "PedAI" ) )
+					continue;
+				
+				// ignore Grind_Bail nodes unless we're already on a grind_bail path
+				if ( !on_grind_bail_path )
+				{
+					uint32 skate_action;
+					if ( pNodeData->GetChecksum( CRCD( 0x963dc198, "SkateAction" ), &skate_action )
+						 && skate_action == CRCD( 0xe41199af, "Grind_Bail" ) )
+					{
+						continue;
+					}
+				}
+				
+				Mth::Vector test_node_position;
+				SkateScript::GetPosition( test_node, &test_node_position );
+				Mth::Vector to_test_vector = m_wp_to - test_node_position;
+				float angle = Mth::GetAngle( to_from_vector, to_test_vector );
+				if ( angle > 360.0f )
+					angle -= 360.0f * (int)( angle / 360.0f );
+	
+				// update max angle
+				if ( angle > max_angle )
+				{
+					max_angle = angle;
+					max_angle_index = i;
+				}
+		
+				if ( angle >= min_inner_angle )
+				{
+					// sort nodes by priority
+					uint32 priority;
+					pNodeData->GetChecksum( CRCD( 0x9d5923d8, "priority" ), &priority, Script::ASSERT );
+					switch ( priority )
+					{
+						case CRCC( 0xde7a971b, "normal" ):
+							valid_high_priority_choices[num_valid_high_choices] = i;
+							num_valid_high_choices++;
+							break;
+						case CRCC(0x6d77875e,"low"):
+							valid_low_priority_choices[num_valid_low_choices] = i;
+							num_valid_low_choices++;
+							break;
+						default:
+							Dbg_MsgAssert( 0, ( "Ped node has invalid priority" ) );
+							break;
+					}
+				}
+			}
+		}
+		
+		// if there weren't any good choices, pick the best one
+		if ( !( num_valid_high_choices || num_valid_low_choices ) )
+		{
+			waypoint = SkateScript::GetLink( m_node_to, max_angle_index );
+		}
+		else
+		{
+			bool find_low_priority = false;
+			// decide if we should pick from a low priority or high priority node
+			if ( num_valid_high_choices && num_valid_low_choices )
+			{			
+				float low_priority_probability = Script::GetFloat( "ped_low_priority_waypoint_probability", Script::ASSERT );
+				Dbg_MsgAssert( low_priority_probability < 1, ( "ped_low_priority_waypoint_probability is %f", low_priority_probability ) );
+				if ( Mth::Rnd( 100 ) <= ( low_priority_probability * 100 ) )
+					find_low_priority = true;
+			}
+			else if ( num_valid_low_choices )
+				find_low_priority = true;
+			
+			// select random index from valid choices
+			if ( find_low_priority )
+				waypoint = SkateScript::GetLink( m_node_to, valid_low_priority_choices[Mth::Rnd( num_valid_low_choices )] );
+			else
+				waypoint = SkateScript::GetLink( m_node_to, valid_high_priority_choices[Mth::Rnd( num_valid_high_choices )] );
+		}
+	}
+	
+	// make sure we got something
+	if ( waypoint == -1 )
+	{
+		Dbg_MsgAssert( 0, ( "SelectNextWaypoint couldn't find a valid waypoint" ) );
+	}
+	else
+	{
+		m_node_next = waypoint;
+		SkateScript::GetPosition( m_node_next, &m_wp_next );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPedLogicComponent::ShouldExecuteAction( Script::CStruct* pNodeData, uint32 weight_name )
+{
+	float weight;
+	pNodeData->GetFloat( weight_name, &weight, Script::ASSERT );
+	Dbg_MsgAssert( weight > 0 && weight <= 1, ( "Waypoint %s has a bad action weight of %f\n", Script::FindChecksumName( GetObject()->GetID() ), weight ) );
+	if ( weight > 0.99f )
+		return true;
+	return ( Mth::Rnd( vACTION_WEIGHT_RESOLUTION ) < ( weight * vACTION_WEIGHT_RESOLUTION ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::AdjustSpeed( float percent )
+{
+	Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+	pMotionComp->m_max_vel *= percent;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::AdjustNormal()
+{
+	Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+	Dbg_Assert( pMotionComp );
+	if ( pMotionComp )
+	{
+		Obj::STriangle last_triangle = pMotionComp->m_last_triangle;
+		Mth::Vector v1 = last_triangle.mpVertices[1] - last_triangle.mpVertices[0];
+		Mth::Vector v2 = last_triangle.mpVertices[2] - last_triangle.mpVertices[0];
+		Mth::Vector normal = ( Mth::CrossProduct( v1, v2 ) ).Normalize();
+		if ( normal[Y] < 0.0f )
+			normal = -normal;
+
+		if ( normal != m_current_normal )
+		{
+			m_current_normal = normal;	// remember this, for detecting if it changes
+			m_last_display_normal = m_display_normal;	// remember start position for lerping
+			m_normal_lerp = 1.0f;	// set lerp counter
+		}
+
+		// if m_normal_lerp is 0.0, then we don't need to do anything, as
+		// we should already be there
+		if ( m_normal_lerp != 0.0f )	   		// if lerping
+		{			
+			// If the last display normal is the same as the current normal
+			// then we can't interpolate between them
+			if ( m_last_display_normal == m_current_normal )
+			{
+				m_normal_lerp = 0.0f;
+			}
+			else
+			{
+				// adjust lerp at constant speed from 1.0 to 0.0, accounting for framerate
+				// m_normal_lerp -= NORMAL_LERP_SPEED * (mp_physics->m_time * 60.0f);
+				m_normal_lerp -= 0.1f * m_time * 60.0f;
+		
+				// if gone all the way, then clear lerping values
+				// and set m_display_normal to be the current face normal
+				if (m_normal_lerp <= 0.0f)
+				{
+					m_normal_lerp = 0.0f;
+					m_display_normal = m_current_normal;
+				}
+				else
+				{
+					// Still between old and current normal, so
+					// calculate intermediate normal
+					m_display_normal = Mth::Lerp( m_current_normal, m_last_display_normal, m_normal_lerp );
+					m_display_normal.Normalize();		// Must be normalized...
+				}
+			}
+		}
+		
+		// Now update the orientation matrix.
+		// We need our up (Y) vector to be this vector
+		// if it changes, rotate the X and Z vectors to match
+		Mth::Matrix display_matrix = GetObject()->GetDisplayMatrix();
+		if ( display_matrix[Y] != m_display_normal )
+		{
+			display_matrix[Y] = m_display_normal;
+			display_matrix.OrthoNormalizeAbout( Y );
+			GetObject()->SetDisplayMatrix( display_matrix );
+			m_current_display_matrix = display_matrix;
+		}
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// testing out positional history
+void CPedLogicComponent::AdjustHeading()
+{
+	AdjustHeading( m_pos_history[0] );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::AdjustHeading( Mth::Vector original_pos )
+{
+	Mth::Matrix mat0 = GetObject()->GetDisplayMatrix();
+	Mth::Vector new_pos = GetObject()->m_pos;
+	if ( m_flags & PEDLOGIC_TO_NODE_IS_LIP )
+	{
+		original_pos = m_wp_from;
+		new_pos = m_wp_to;
+	}
+
+	// Gfx::AddDebugLine( original_pos, new_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ) );
+	
+	// change z vector
+	mat0[Z] = ( new_pos - original_pos ).Normalize();
+	// get new X and Y vectors
+	mat0[X] = Mth::CrossProduct( mat0[Y], mat0[Z] );	
+	mat0[Y] = Mth::CrossProduct( mat0[Z], mat0[X] );
+	GetObject()->SetDisplayMatrix( mat0 );
+	m_current_display_matrix = mat0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::RefreshDisplayMatrix()
+{
+	GetObject()->SetDisplayMatrix( m_current_display_matrix );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPedLogicComponent::do_collision_check( Mth::Vector *p_v0, Mth::Vector *p_v1, Mth::Vector *p_collisionPoint, 
+								 Obj::STriangle *p_lastTriangle )
+{
+	// First, see if there is a collision with the last triangle, and if there is, use that.
+	CFeeler feeler;
+	if ( feeler.GetCollision( *p_v0, *p_v1, false ) )
+	{
+		Nx::CCollObj* p_col_obj=feeler.GetSector();
+		Dbg_MsgAssert( p_col_obj->IsTriangleCollision(), ( "Not triangle collision !!!" ) );
+
+		Nx::CCollObjTriData* p_tri_data=p_col_obj->GetGeometry();
+		Dbg_MsgAssert( p_tri_data, ( "NULL p_tri_data" ) );
+
+		int face_index=feeler.GetFaceIndex();
+		p_lastTriangle->mpVertices[0]=p_col_obj->GetVertexPos(p_tri_data->GetFaceVertIndex(face_index, 0));
+		p_lastTriangle->mpVertices[1]=p_col_obj->GetVertexPos(p_tri_data->GetFaceVertIndex(face_index, 1));
+		p_lastTriangle->mpVertices[2]=p_col_obj->GetVertexPos(p_tri_data->GetFaceVertIndex(face_index, 2));
+
+		// if there is a collision, then snap to it
+		*p_collisionPoint = feeler.GetPoint();
+
+		UpdateSkaterSoundTerrain( feeler.GetTerrain() );
+
+		return true;
+	}
+	return false;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::StickToGround()
+{
+	// stick to ground modified from the motion component...uses
+	// the ped's normal instead of the world's Y
+	Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+	Dbg_Assert( pMotionComp );
+	
+	Mth::Vector v0, v1;
+
+	// make sure our X vector is correct (got Z from the nav matrix...)
+	GetObject()->m_matrix[X] = Mth::CrossProduct( GetObject()->m_matrix[Y], GetObject()->m_matrix[Z] );
+	GetObject()->m_matrix[X].Normalize();
+
+	Mth::Matrix display_matrix = GetObject()->GetDisplayMatrix();
+	v0 = GetObject()->m_pos + ( display_matrix[Y] * m_col_dist_above );
+	v1 = GetObject()->m_pos + ( -display_matrix[Y] * m_col_dist_below );
+	
+	Mth::Vector collision_point;
+	if ( do_collision_check( &v0, &v1, &collision_point, &( pMotionComp->m_last_triangle ) ) )
+	{
+		GetObject()->m_pos = collision_point;
+		// GetObject()->GetDisplayMatrix()[Mth::POS] = collision_point;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::UpdateSkaterSoundStates( EStateType state )
+{
+	Dbg_MsgAssert( m_state == CRCD( 0xa85af587, "generic_skater" ), ( "CPedLogicComponent::UpdateSkaterSoundStates called on a non-skater ped" ) );
+
+	Obj::CSkaterLoopingSoundComponent *pLoopingSoundComponent = GetSkaterLoopingSoundComponentFromObject( GetObject() );
+	if ( pLoopingSoundComponent )
+		pLoopingSoundComponent->SetState( state );
+
+	Obj::CSkaterSoundComponent *pSoundComponent = GetSkaterSoundComponentFromObject( GetObject() );
+	if ( pSoundComponent )
+		pSoundComponent->SetState( state );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::UpdateSkaterSoundTerrain( ETerrainType terrain )
+{
+	Dbg_MsgAssert( m_state == CRCD(0xa85af587,"generic_skater"), ( "CPedLogicComponent::UpdateSkaterSoundTerrain called on non-skater ped" ) );
+
+	// set the terrain type for looping sound
+	Obj::CSkaterLoopingSoundComponent* pLoopingSoundComp = GetSkaterLoopingSoundComponentFromObject( GetObject() );
+	if ( pLoopingSoundComp )
+		pLoopingSoundComp->SetTerrain( terrain );
+
+	Obj::CSkaterSoundComponent *pSoundComponent = GetSkaterSoundComponentFromObject( GetObject() );
+	if ( pSoundComponent )
+	{
+		pSoundComponent->SetTerrain( terrain );
+		pSoundComponent->SetLastTerrain( terrain );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPedLogicComponent::ShouldUseBiases()
+{
+	return ( m_flags & PEDLOGIC_IS_SKATER
+			 && !( m_flags & PEDLOGIC_GRINDING )
+			 && !( m_flags & PEDLOGIC_GRIND_BAILING )
+			 && !( m_flags & PEDLOGIC_TO_NODE_IS_VERT )
+			 && !( m_flags & PEDLOGIC_FROM_NODE_IS_VERT )
+			 && !( m_flags & PEDLOGIC_JUMPING_TO_NODE )
+		   );
+}
+
+/******************************************************************/
+/*																  */
+/*             			Skate Actions!                            */
+/*                                                                */
+/******************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CPedLogicComponent::GetSkateActionParams( Script::CStruct* pNodeData )
+{
+	Script::CStruct* pScriptParams = new Script::CStruct();
+	// give higher priority to ped skater data - should this be reversed?	
+	pScriptParams->AppendStructure( pNodeData );
+	pScriptParams->AppendStructure( GetObject()->GetTags() );
+	return pScriptParams;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoGrind( Script::CStruct* pNodeData )
+{
+	// printf("CPedLogicComponent::DoGrind\n");
+	StopSkateActions();
+	
+	// m_is_grinding = true;
+	m_flags |= PEDLOGIC_GRINDING;
+
+	// set the terrain
+	uint32 terrain_checksum;
+	// TODO: Assert if terrain missing
+	if ( pNodeData->GetChecksum( CRCD( 0x54cf8532, "TerrainType" ), &terrain_checksum, Script::NO_ASSERT ) )
+	{
+		UpdateSkaterSoundTerrain( Env::CTerrainManager::sGetTerrainFromChecksum( terrain_checksum ) );
+	}
+
+	Script::CStruct* pScriptParams = GetSkateActionParams( pNodeData );
+	Script::CScript* pScript = Script::SpawnScript( CRCD( 0xb7fca430, "ped_skater_grind" ), pScriptParams );
+	pScript->mpObject = GetObject();
+	delete pScriptParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+void CPedLogicComponent::GrindUpdate()
+{
+	m_grind_wobble_frames--;
+	if ( m_grind_wobble_frames <= 0 )
+	{
+		printf("setting the wobble target to %f\n", m_grind_wobble_target);
+		// set new wobble target
+		Obj::CAnimationComponent* pAnimComp = GetAnimationComponentFromObject( GetObject() );
+		Dbg_Assert( pAnimComp );
+		pAnimComp->SetWobbleTarget( m_grind_wobble_target, false );
+		m_grind_wobble_target = -m_grind_wobble_target;
+		m_grind_wobble_frames = Script::GetInteger( "ped_skater_grind_wobble_frames", Script::ASSERT );
+	}
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoGrindOff( Script::CStruct* pNodeData )
+{
+	if ( m_flags & PEDLOGIC_GRINDING )
+	{
+		// m_is_grinding = false;
+		m_flags &= ~PEDLOGIC_GRINDING;
+	
+		Script::CScript* pScript = Script::SpawnScript( CRCD( 0x84c51e26, "ped_skater_grind_off" ) );
+		pScript->mpObject = GetObject();
+		// delete pScriptParams;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoJump( Script::CStruct* pNodeData, bool is_vert )
+{
+	StopSkateActions();
+	
+	// call Obj_Jump on the object
+	Script::CStruct* pScriptParams;
+	Script::CScript* pScript;
+	if ( is_vert )
+	{
+		m_flags &= ~PEDLOGIC_MOVING_ON_PATH;
+		pScriptParams = GetJumpParams( pNodeData, is_vert );
+		pScript = Script::SpawnScript( CRCD( 0x990152d7, "ped_skater_vert_jump" ), pScriptParams );
+	}
+	else
+	{
+		if ( pNodeData->ContainsFlag( CRCD( 0xb02567ae, "JumpToNextNode" ) ) )
+		{
+			m_flags &= ~PEDLOGIC_MOVING_ON_PATH;
+			pScriptParams = GetJumpParams( pNodeData, is_vert );
+			m_flags |= PEDLOGIC_JUMPING_TO_NODE;
+		}
+		else
+		{
+			pScriptParams = GetSkateActionParams( pNodeData );
+		}
+		pScript = Script::SpawnScript( CRCD( 0x28be3d25, "ped_skater_jump" ), pScriptParams );
+	}
+	pScript->mpObject = GetObject();
+	delete pScriptParams;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoLipTrick( Script::CStruct* pNodeData )
+{
+	StopSkateActions();
+	
+	// stop moving on path
+	m_flags &= ~PEDLOGIC_MOVING_ON_PATH;
+
+	UpdateLipDisplayMatrix();
+	
+	Script::CStruct* pScriptParams = GetSkateActionParams( pNodeData );
+	Script::CScript* pScript = Script::SpawnScript( CRCD( 0x2fd2b4b8, "ped_skater_lip_trick" ), pScriptParams );
+	pScript->mpObject = GetObject();
+	delete pScriptParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::UpdateLipDisplayMatrix()
+{
+	// set the display matrix
+	Mth::Matrix mat0 = GetObject()->GetDisplayMatrix();	
+	
+	// change z vector
+	mat0[Z] = Mth::Vector(0, 1, 0);
+	mat0[Y] = m_last_display_normal.Normalize();
+	// get new X and Y vectors
+	mat0[X] = Mth::CrossProduct( mat0[Y], mat0[Z] ).Normalize();
+	mat0[Y] = Mth::CrossProduct( mat0[Z], mat0[X] ).Normalize();
+	GetObject()->SetDisplayMatrix( mat0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoManual( Script::CStruct* pNodeData )
+{
+	StopSkateActions();
+	
+	Script::CStruct* pScriptParams = GetSkateActionParams( pNodeData );
+	Script::CScript* pScript = Script::SpawnScript( CRCD( 0x1462af22, "ped_skater_manual" ), pScriptParams );
+	pScript->mpObject = GetObject();
+	delete pScriptParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoManualDown( Script::CStruct* pNodeData )
+{
+	StopSkateActions();
+	
+	Script::CStruct* pScriptParams = GetSkateActionParams( pNodeData );
+	Script::CScript* pScript = Script::SpawnScript( CRCD( 0x4c0caa11, "ped_skater_manual_down" ), pScriptParams );
+	pScript->mpObject = GetObject();
+	delete pScriptParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CPedLogicComponent::GetJumpParams( Script::CStruct* pNodeData, bool is_vert )
+{
+	StopSkateActions();
+
+	// stop moving on path 
+	m_flags &= ~PEDLOGIC_DOING_VERT_ROTATION;
+	m_flags &= ~PEDLOGIC_DOING_SPINE;
+
+	// reset angles
+	m_spine_current_angle = 0.0f;
+	m_spine_start_angle = 0.0f;
+	m_rot_current_angle = 0.0f;
+
+	Script::CStruct* pScriptParams = GetSkateActionParams( pNodeData );
+	
+	// figure heading and time
+	// get the height and gravity
+	float height;
+	pNodeData->GetFloat( CRCD( 0x838da447, "jumpHeight" ), &height, Script::ASSERT );
+	float gravity = Script::GetFloat( CRCD( 0xe224a5d3, "ped_skater_jump_gravity" ), Script::ASSERT );
+
+	float time_total;
+
+	if ( is_vert || pNodeData->ContainsFlag( CRCD( 0xb02567ae, "JumpToNextNode" ) ) )
+	{
+		Mth::Vector pt = m_wp_to - GetObject()->m_pos;
+		pt[Y] = 0.0f;
+		
+		if ( GetObject()->m_pos[Y] + height < m_wp_to[Y] )
+		{
+			height = m_wp_to[Y] - GetObject()->m_pos[Y] + Script::GetFloat( CRCD( 0x23f14b13, "ped_skater_jump_to_next_node_height_slop" ), Script::ASSERT );						
+			// Script::PrintContents( pNodeData );
+			// Dbg_MsgAssert( 0, ( "JumpToNextNode selected but jumpHeight isn't enough to reach target node." ) );
+		}		
+		
+		// adjust height to compensate for difference between ped height
+		// and height of waypoint
+		// height += pt[Y];
+		// pt[Y] = 0.0f;
+		
+		// figure the time to rise
+		// d = vi*t + (a*t^2)/2
+		Dbg_Assert( gravity < 0 );
+		float time_to_rise = sqrtf( 2 * -height / gravity );
+	
+		// figure the time to fall
+		float time_to_fall = sqrtf( 2 * Mth::Abs( ( GetObject()->m_pos[Y] + height - m_wp_to[Y] ) / gravity ) );
+	
+		// compute the inital y velocity
+		float vi = ( height / time_to_rise ) - ( 0.5f * gravity * time_to_rise );
+		Dbg_Assert( vi > 0 );
+	
+		// find the horizontal distance we need to travel
+		float distance = pt.Length();
+	
+		// horizontal velocity
+		time_total = time_to_rise + time_to_fall;
+		Mth::Vector vh = ( distance / time_total ) * pt.Normalize();
+	
+		// vertical velocity
+		Mth::Vector vy = vi * Mth::Vector(0, 1, 0);
+	
+		// total speed and heading
+		float jump_speed = ( vh + vy ).Length();
+		Mth::Vector heading = ( vh + vy ).Normalize();
+	
+		pScriptParams->AddVector( CRCD( 0xfd4bc03e, "heading" ), heading );
+		pScriptParams->AddFloat( CRCD( 0x1c4c5690, "jumpSpeed" ), jump_speed );
+		pScriptParams->AddFloat( CRCD( 0x66ced87b, "jumpTime" ), time_total );
+	}
+	else
+	{
+		// just use the jump height
+		float jump_speed = sqrtf( 2 * gravity * -height );
+		pScriptParams->AddFloat( CRCD(0x1c4c5690,"jumpSpeed"), jump_speed );
+
+		time_total = 2 * sqrtf( 2 * -height * gravity );
+	}
+
+	// setup any rotation
+	int rot_angle = 0;
+	float rot_float;
+	if ( pNodeData->ContainsFlag( CRCD( 0xb4077854, "RandomSpin" ) ) )
+	{
+		// figure the spin angle based on the time we've got
+		float min_180_time = Script::GetFloat( CRCD( 0x8b381ab1, "ped_skater_min_180_spin_time" ), Script::ASSERT );
+	
+		int mult = (int)( time_total / min_180_time );
+		// cap the rotation to 720 degrees
+		if ( mult > 4 )
+			mult = 4;
+	
+		rot_angle = 180 * Mth::Rnd( mult + 1 );
+	}
+	else if ( pNodeData->GetFloat( CRCD( 0x96fb50d9, "SpinAngle" ), &rot_float, Script::NO_ASSERT ) )
+	{
+		// cast to int but make sure it doesn't get rounded down
+		rot_angle = (int)( rot_float + 0.01f );
+	}
+	
+	rot_angle = abs( rot_angle );
+	if ( rot_angle > 0 )
+	{
+		// figure fs or bs
+		uint32 spin_direction;
+		pNodeData->GetChecksum( CRCD( 0xef0b71a0, "SpinDirection" ), &spin_direction, Script::ASSERT );
+		
+		Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+		bool is_flipped = pAnimationComponent->IsFlipped();
+		
+		switch ( spin_direction )
+		{
+			case CRCC( 0x20e1c4a3, "BS" ):
+			{
+				if ( is_flipped )
+				{
+					rot_angle *= -1;
+				}
+				break;
+			}
+			case CRCC( 0x448d01a7, "FS" ):
+			{
+				if ( !is_flipped )
+				{
+					rot_angle *= -1;
+				}
+				break;
+			}
+			case CRCC( 0xe7390a8b, "Rand" ):
+			{
+				// randomly change direction
+				int toggle = Mth::Rnd( 2 );
+				if ( toggle == 1 )
+				{
+					rot_angle *= -1;
+				}
+				break;
+			}
+			default:
+				Dbg_MsgAssert( 0, ( "Unknown SpinDirection" ) );
+				break;
+		}
+		
+		m_rot_angle = rot_angle;
+		m_flags |= PEDLOGIC_DOING_VERT_ROTATION;
+	}
+
+	bool is_spine = pNodeData->ContainsFlag( CRCD( 0x4aa0cff, "SpineTransfer" ) );
+	if ( rot_angle % 360 == 0 )
+	{
+		if ( !is_spine )
+			pScriptParams->AddChecksum( NONAME, CRCD( 0x8868e76a, "should_flip" ) );
+	}
+	else if ( is_spine )
+	{
+		pScriptParams->AddChecksum( NONAME, CRCD( 0x8868e76a, "should_flip" ) );
+	}
+
+	// setup any spine trasnfer
+	if ( is_spine )
+	{
+		m_flags |= PEDLOGIC_DOING_SPINE;
+		
+		// initialize spine angle to current angle between ped and vert
+		Mth::Vector temp_y(0, 1, 0);
+		float spine_start = Mth::GetAngle( m_current_display_matrix[Z], temp_y );
+		m_spine_start_angle = spine_start + Script::GetFloat( CRCD(0x95e91d23,"ped_skater_spine_rotation_slop") );
+	}
+
+	if ( m_flags & PEDLOGIC_DOING_SPINE || m_flags & PEDLOGIC_DOING_VERT_ROTATION )
+	{
+		m_rot_start_matrix = m_current_display_matrix;
+		m_rot_total_time = time_total * Script::GetFloat( CRCD( 0xc1dfc97b, "ped_skater_vert_rotation_time_slop" ), Script::ASSERT );
+		m_rot_current_time = 0.0f;
+	}
+
+	// check if we're jumping to the next node
+	if ( pNodeData->ContainsFlag( CRCD( 0xb02567ae, "JumpToNextNode" ) ) )
+	{
+		pScriptParams->AddFloat( CRCD( 0xa1810c27, "land_height" ), m_wp_to[Y] );
+	}
+	
+	return pScriptParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoGrab( Script::CStruct* pNodeData, bool is_vert )
+{
+	Script::CStruct* pScriptParams;
+	if ( is_vert )
+	{
+		// Obj::CAnimationComponent* pAnimComp = GetAnimationComponentFromObject( GetObject() );
+		// printf("flipped = %i\n", pAnimComp->IsFlipped() );
+
+		m_flags &= ~PEDLOGIC_MOVING_ON_PATH;
+		pScriptParams = GetJumpParams( pNodeData, is_vert );
+		pScriptParams->AddChecksum( NONAME, CRCD( 0x1615618, "is_vert" ) );
+	}
+	else
+	{
+		pScriptParams = GetSkateActionParams( pNodeData );
+
+		// see if we're already jumping
+		Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+		if ( pMotionComp->m_movingobj_status & MOVINGOBJ_STATUS_JUMPING )
+			pScriptParams->AddInteger( CRCD( 0x65b5788d, "is_jumping" ), 1 );
+		else
+		{
+			// figure out how long before we start to fall
+			float jumpSpeed = Script::GetFloat( CRCD( 0xf1ff758a, "ped_skater_jump_speed" ), Script::ASSERT );
+			float jumpGravity = Script::GetFloat( CRCD( 0xe224a5d3, "ped_skater_jump_gravity" ), Script::ASSERT );
+			float time = 2 * sqrtf( Mth::Sqr( jumpSpeed ) / Mth::Sqr( jumpGravity ) );
+			pScriptParams->AddInteger( CRCD( 0x66ced87b, "jumpTime" ), time );
+			pScriptParams->AddInteger( CRCD( 0x65b5788d, "is_jumping" ), 0 );
+		}
+	}
+		
+	Script::CScript* pScript = Script::SpawnScript( CRCD( 0x10585cb3, "ped_skater_grab_trick" ), pScriptParams );
+	pScript->mpObject = GetObject();
+	
+	delete pScriptParams;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoRotations()
+{
+	m_rot_current_time += m_time;
+
+	float percent = m_time / m_rot_total_time;
+	if ( percent > 1.0f )
+		percent = 1.0f;
+
+	float spine_target_angle = 180.0f - m_spine_start_angle;
+	
+	if ( m_flags & PEDLOGIC_DOING_VERT_ROTATION )
+		m_rot_current_angle += percent * (float)m_rot_angle;
+	if ( m_flags & PEDLOGIC_DOING_SPINE )
+		m_spine_current_angle += percent * spine_target_angle;
+	
+	if ( m_rot_current_time >= m_rot_total_time || percent >= 1.0f )
+	{
+		if ( m_flags & PEDLOGIC_DOING_VERT_ROTATION )
+			m_rot_current_angle = (float)m_rot_angle;
+		if ( m_flags & PEDLOGIC_DOING_SPINE )
+			m_spine_current_angle = spine_target_angle;
+
+		m_flags &= ~PEDLOGIC_DOING_VERT_ROTATION;
+		m_flags &= ~PEDLOGIC_DOING_SPINE;
+	}
+
+	// cap spine angle
+	if ( m_flags & PEDLOGIC_DOING_SPINE && m_spine_current_angle > spine_target_angle )
+	{
+		m_spine_current_angle = spine_target_angle;
+	}
+
+	Mth::Matrix temp = m_rot_start_matrix;
+	temp.RotateLocal( Mth::Vector( Mth::DegToRad( m_spine_current_angle ), Mth::DegToRad( m_rot_current_angle ), 0 ) );
+	GetObject()->SetDisplayMatrix( temp );
+	m_current_display_matrix = temp;
+	m_display_normal = m_last_display_normal = m_current_normal = temp[Y];
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::Stop( Script::CStruct* pNodeData )
+{
+	StopSkateActions();
+
+	m_flags |= PEDLOGIC_STOPPED;
+	
+	Script::CStruct* pScriptParams = GetSkateActionParams( pNodeData );
+	Script::CScript* pScript = Script::SpawnScript( CRCD( 0x365b2d85, "ped_skater_stop" ), pScriptParams );
+	pScript->mpObject = GetObject();
+	delete pScriptParams;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::StopSkateActions()
+{
+	// BB - I moved this from script because it was too damn slow!
+	Script::CArray* p_scripts_to_stop = Script::GetArray( CRCD( 0x2ecd9f57, "ped_skater_action_scripts" ), Script::ASSERT );
+	int array_size = p_scripts_to_stop->GetSize();
+	Obj::CObject* p_object = (Obj::CObject*)GetObject();
+	
+	for ( int i = 0; i < array_size; i++ )
+	{
+		uint32 script_to_stop = p_scripts_to_stop->GetChecksum( i );
+		Script::StopScriptsUsingThisObject_Proper( p_object, script_to_stop );
+	}
+
+	// m_is_grinding = false;
+	m_flags &= ~PEDLOGIC_GRINDING;
+
+	m_flags &= ~PEDLOGIC_DOING_VERT_ROTATION;
+	m_flags &= ~PEDLOGIC_DOING_SPINE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::Bail()
+{
+	// kill any running bail scripts
+	Script::StopScriptsUsingThisObject_Proper( GetObject(), CRCD(0xe630bf07,"ped_skater_grind_bail") );
+	Script::StopScriptsUsingThisObject_Proper( GetObject(), CRCD(0x78131e4f,"ped_skater_generic_bail") );
+
+	// restore the max current velocity before calling this script again!
+	if ( m_original_max_vel > 0 )
+	{
+		Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+		Dbg_Assert( pMotionComp );
+		pMotionComp->m_max_vel = m_original_max_vel;
+	}
+	
+	Script::CScript* pScript;
+	if ( m_flags & PEDLOGIC_GRINDING )
+	{		
+		pScript = Script::SpawnScript( CRCD(0xe630bf07,"ped_skater_grind_bail") );
+	}
+	else 
+	{
+		pScript = Script::SpawnScript( CRCD(0x78131e4f,"ped_skater_generic_bail") );
+	}
+	StopSkateActions();
+	pScript->mpObject = GetObject();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::GrindBail()
+{
+	if ( m_flags & PEDLOGIC_GRINDING )
+	{
+		m_flags &= ~PEDLOGIC_GRINDING;
+		m_flags |= PEDLOGIC_GRIND_BAILING;
+	
+		// select a new next node
+		int num_links = SkateScript::GetNumLinks( m_node_to );
+	
+		// if there's only one link, we have to keep going...shitty
+		if ( num_links > 1 )
+		{
+			// find a grind_bail node!
+			for ( int i = 0; i < num_links; i++ )
+			{
+				int test_node = SkateScript::GetLink( m_node_to, i );
+				Script::CStruct* pNodeData = SkateScript::GetNode( test_node );
+
+				uint32 skate_action;
+				if ( pNodeData->GetChecksum( CRCD( 0x963dc198, "skateAction" ), &skate_action )
+					 && skate_action == CRCD( 0xe41199af, "grind_bail" ) )
+				{
+					m_node_next = test_node;
+					SkateScript::GetPosition( m_node_next, &m_wp_next );
+					break;
+				}
+			}
+		}
+
+		// run script!
+		Script::CScript* pScript = Script::SpawnScript( CRCD( 0xe630bf07, "ped_skater_grind_bail" ) );
+		pScript->mpObject = GetObject();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoRollOff( Script::CStruct* pNodeData )
+{
+	Script::CScript* pScript = Script::SpawnScript( CRCD( 0xb183d99e, "ped_skater_roll_off" ) );
+	pScript->mpObject = GetObject();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPedLogicComponent::DoFlipTrick( Script::CStruct* pNodeData, bool is_vert )
+{
+	StopSkateActions();
+	
+	// jump if we need to
+	Obj::CMotionComponent* pMotionComp = GetMotionComponentFromObject( GetObject() );
+	Dbg_Assert( pMotionComp );
+	Script::CStruct* pScriptParams = GetSkateActionParams( pNodeData );
+
+	Script::CStruct *pJumpParams = GetJumpParams( pNodeData, is_vert );
+	pScriptParams->AppendStructure( pJumpParams );
+	delete pJumpParams;
+
+	if ( is_vert )
+	{
+		m_flags &= ~PEDLOGIC_MOVING_ON_PATH;		
+		pScriptParams->AddChecksum( NONAME, CRCD( 0x1615618, "is_vert" ) );
+	}
+
+	// check jumping state
+	if ( pMotionComp->m_movingobj_status & MOVINGOBJ_STATUS_JUMPING )
+		pScriptParams->AddInteger( CRCD( 0x65b5788d, "is_jumping" ), 1 );
+	else
+		pScriptParams->AddInteger( CRCD( 0x65b5788d, "is_jumping" ), 0 );
+
+	if ( pNodeData->ContainsFlag( CRCD( 0xb02567ae, "JumpToNextNode" ) ) )
+	{
+		m_flags &= ~PEDLOGIC_MOVING_ON_PATH;
+		m_flags |= PEDLOGIC_JUMPING_TO_NODE;
+	}
+
+	Script::CScript *pScript = Script::SpawnScript( CRCD( 0xb83c383c, "ped_skater_flip_trick" ), pScriptParams );
+	pScript->mpObject = GetObject();
+	delete pScriptParams;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+}
diff --git a/Code/Gel/Components/PedLogicComponent.h b/Code/Gel/Components/PedLogicComponent.h
new file mode 100644
index 0000000..8e56d3a
--- /dev/null
+++ b/Code/Gel/Components/PedLogicComponent.h
@@ -0,0 +1,210 @@
+
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       PedLogicComponent.h
+//* OWNER:          Brad Bulkley
+//* CREATION DATE:  1/9/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_PEDLOGICCOMPONENT_H__
+#define __COMPONENTS_PEDLOGICCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+#define		CRC_PEDLOGIC CRCD( 0x6efddfb8, "PedLogic" )
+#define		GetPedLogicComponent() ((Obj::CPedLogicComponent*)GetComponent(CRC_PEDLOGIC))
+#define		GetPedLogicComponentFromObject(pObj) ((Obj::CPedLogicComponent*)(pObj)->GetComponent(CRC_PEDLOGIC))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+	// flags
+	enum
+	{
+		PEDLOGIC_IS_SKATER				= ( 1 << 0	),
+		PEDLOGIC_MOVING_ON_PATH			= ( 1 << 1	),
+		PEDLOGIC_GRINDING				= ( 1 << 2	),
+		PEDLOGIC_TO_NODE_IS_VERT		= ( 1 << 3	),
+		PEDLOGIC_FROM_NODE_IS_VERT		= ( 1 << 4	),
+		PEDLOGIC_NEXT_NODE_IS_VERT		= ( 1 << 5	),
+		PEDLOGIC_BAILING				= ( 1 << 6	),
+		PEDLOGIC_GRIND_BAILING			= ( 1 << 7	),
+		PEDLOGIC_JUMP_AT_TO_NODE		= ( 1 << 8	),
+		PEDLOGIC_DOING_VERT_ROTATION	= ( 1 << 9	),
+		PEDLOGIC_DOING_SPINE			= ( 1 << 10	),
+		PEDLOGIC_JUMPING_TO_NODE		= ( 1 << 11	),
+		PEDLOGIC_STOPPED				= ( 1 << 12	),
+		PEDLOGIC_DOING_LIP_TRICK		= ( 1 << 13	),
+		PEDLOGIC_DOING_MANUAL			= ( 1 << 14 ),
+		PEDLOGIC_TO_NODE_IS_LIP			= ( 1 << 15 ),
+	};
+	
+	const int vPOS_HISTORY_SIZE = 5;
+
+
+class CPathObjectTracker;
+
+enum
+{
+    vNUM_WHISKERS=4
+};
+
+
+class CPedLogicComponent : public CBaseComponent
+{
+public:
+    CPedLogicComponent();
+    virtual ~CPedLogicComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+private:
+	void							SelectNextWaypoint();
+	void							HitWaypoint();
+
+	bool							WaypointIsVert( int waypoint );
+	
+	virtual void					GenericPedUpdate();
+	virtual void					GenericSkaterUpdate();
+	void							UpdateLipDisplayMatrix();
+
+	// new generic ped logic stuff
+	bool							CheckForOtherPeds();
+	float							GetTotalXBias();
+	float							GetTotalZBias();
+	void							UpdatePathBias();
+	bool							UpdateTargetBias();
+	void							AdjustBias( float angle, float bias );
+	void							AddWhiskerBias( float angle, float bias, float distance, float range );
+	void							DecayWhiskerBiases();
+	void							UpdatePosition();
+	void							RotatePosHistory();
+	void							ResetBias();
+	void							AddTargetBias( Mth::Vector target, float target_bias );
+
+	bool							ShouldExecuteAction( Script::CStruct* pNodeData, uint32 weight_name );
+	void							AdjustSpeed( float percent );
+
+	void							DoWalkActions( Script::CStruct* pNodeData );
+	void							DoSkateActions( Script::CStruct* pNodeData );
+	void							DoGenericNodeActions( Script::CStruct* pNodeData );
+
+	void							AdjustNormal();
+	void							AdjustHeading();
+	void							AdjustHeading( Mth::Vector original_pos );
+	void							RefreshDisplayMatrix();
+	void							StickToGround();
+
+	bool							do_collision_check( Mth::Vector *p_v0, Mth::Vector *p_v1, Mth::Vector *p_collisionPoint, Obj::STriangle *p_lastTriangle );
+
+	void							UpdateSkaterSoundStates( EStateType state );
+	void							UpdateSkaterSoundTerrain( ETerrainType terrain );
+
+	bool							ShouldUseBiases();
+	
+	// skate actions
+	void							DoGrind( Script::CStruct* pNodeData );
+	void							DoGrindOff( Script::CStruct* pNodeData );
+	// void							GrindUpdate();
+	
+	Script::CStruct*				GetJumpParams( Script::CStruct* pNodeData, bool is_vert = false );
+	void							DoGrab( Script::CStruct* pNodeData, bool is_vert = false );
+	void							DoRotations();
+
+	void							DoLipTrick( Script::CStruct* pNodeData );
+	void							DoJump( Script::CStruct* pNodeData, bool is_vert = false );
+	void							DoManual( Script::CStruct* pNodeData );
+	void							DoManualDown( Script::CStruct* pNodeData );
+	void							Stop( Script::CStruct* pNodeData );
+	void							Bail();
+	void							GrindBail();
+	void							DoRollOff( Script::CStruct* pNodeData );
+	void							DoFlipTrick( Script::CStruct* pNodeData, bool is_vert = false );
+
+	void							StopSkateActions();
+
+	Script::CStruct*				GetSkateActionParams( Script::CStruct* pNodeData );
+protected:
+	uint32							m_state;
+
+	uint32							m_flags;
+	
+	// new generic ped logic stuff
+	float							m_bias[vNUM_WHISKERS];
+	float							m_whiskers[vNUM_WHISKERS];
+
+	// nodes
+	int								m_node_from;
+	int								m_node_to;
+	int								m_node_next;
+
+	// waypoints of nodes
+	Mth::Vector						m_wp_from;
+	Mth::Vector						m_wp_to;
+	Mth::Vector						m_wp_next;
+
+	int								m_turn_frames;
+	int								m_max_turn_frames;
+
+	CPathObjectTracker*				mp_path_object_tracker;
+
+	float							m_time;
+
+	// normal lerp
+	Mth::Vector						m_last_display_normal;
+	Mth::Vector						m_current_normal;
+	Mth::Vector						m_display_normal;
+	float							m_normal_lerp;
+
+	// stick to ground
+	float							m_col_dist_above;
+	float							m_col_dist_below;
+
+	// velocity
+	float							m_original_max_vel;
+
+	// rotation
+	int								m_rot_angle;
+	float							m_rot_current_angle;
+	float							m_rot_total_time;
+	float							m_rot_current_time;
+	Mth::Matrix						m_rot_start_matrix;
+
+	// spine
+	float							m_spine_current_angle;
+	float							m_spine_start_angle;
+	
+	// display matrix as of last update
+	Mth::Matrix						m_current_display_matrix;
+
+	Mth::Vector						m_pos_history[vPOS_HISTORY_SIZE];
+	
+	float							m_speed_fraction;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/ProximTriggerComponent.cpp b/Code/Gel/Components/ProximTriggerComponent.cpp
new file mode 100644
index 0000000..dc9c72c
--- /dev/null
+++ b/Code/Gel/Components/ProximTriggerComponent.cpp
@@ -0,0 +1,156 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ProximTriggerComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  2/28/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CProximTriggerComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CProximTriggerComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CProximTriggerComponent::CProximTriggerComponent() : CBaseComponent()
+{
+	SetType( CRC_PROXIMTRIGGER );
+	
+	if (!Mdl::Skate::Instance()->mpProximManager)
+	{
+		Dbg_Message("ProximTriggerComponent created before ProximManager; ProximNodes will not work");
+		return;
+	}
+	
+	Dbg_MsgAssert(Mdl::Skate::Instance()->mpProximManager, ("ProximTriggerComponent created before ProximManager"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CProximTriggerComponent::~CProximTriggerComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProximTriggerComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	if (pParams->ContainsComponentNamed(CRCD(0x1313e3c, "ProximTriggerTarget")))
+	{
+		uint32 script_target_id;
+		pParams->GetChecksum(CRCD(0x1313e3c, "ProximTriggerTarget"), &script_target_id);
+		
+		mp_script_target_object = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(script_target_id));
+		Dbg_MsgAssert(mp_script_target_object, ("ProximTriggerComponent given unidentifiable target object"));
+	}
+	
+	m_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProximTriggerComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProximTriggerComponent::Update()
+{
+	if (!m_active) return;
+	ProximUpdate();
+}
+
+void CProximTriggerComponent::ProximUpdate()
+{
+
+	#if 0	
+	
+	Dbg_Assert(mp_script_target_object);
+	
+	uint32 proxim_trigger_mask = Mdl::Skate::Instance()->mpProximManager->GetProximTriggerMask(m_viewport);
+	
+	Proxim_Update(proxim_trigger_mask, mp_script_target_object, GetObject()->GetPos());
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CProximTriggerComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+	
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProximTriggerComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info, ("NULL p_info sent to CProximTriggerComponent::GetDebugInfo"));
+	
+	p_info->AddChecksum("mp_script_target_object", mp_script_target_object->GetID());
+	p_info->AddInteger("proxim_trigger_mask", Mdl::Skate::Instance()->mpProximManager->GetProximTriggerMask(m_viewport));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProximTriggerComponent::SetScriptTarget ( CCompositeObject* p_script_target_object )
+{
+	Dbg_Assert(p_script_target_object);
+	mp_script_target_object = p_script_target_object;
+}
+
+}
diff --git a/Code/Gel/Components/ProximTriggerComponent.h b/Code/Gel/Components/ProximTriggerComponent.h
new file mode 100644
index 0000000..4ec1140
--- /dev/null
+++ b/Code/Gel/Components/ProximTriggerComponent.h
@@ -0,0 +1,74 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ProximTriggerComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  2/28/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_PROXIMTRIGGERCOMPONENT_H__
+#define __COMPONENTS_PROXIMTRIGGERCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_PROXIMTRIGGER CRCD(0x98952fb6, "ProximTrigger")
+
+#define		GetProximTriggerComponent() ((Obj::CProximTriggerComponent*)GetComponent(CRC_PROXIMTRIGGER))
+#define		GetProximTriggerComponentFromObject(pObj) ((Obj::CProximTriggerComponent*)(pObj)->GetComponent(CRC_PROXIMTRIGGER))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CCameraComponent;
+
+class CProximTriggerComponent : public CBaseComponent
+{
+public:
+    CProximTriggerComponent();
+    virtual ~CProximTriggerComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							SetScriptTarget ( CCompositeObject* p_script_target_object );
+	
+	void							SetActive (   ) { m_active = true; }
+	void							SetInactive (   ) { m_active = false; }
+	
+	void							SetViewport ( int viewport ) { m_viewport = viewport; }
+	int								GetViewport (   ) { return m_viewport; }
+	
+	void							ProximUpdate();
+	
+private:
+	// target of the scripts run by proxim nodes which do not have a proxim object ID
+	CObject*                        mp_script_target_object;
+	
+	// if the trigger is active
+	bool							m_active;
+	
+	// the viewport which this component corresponds to; all proxim trigger components on a given viewport share a proxim trigger flag so that switching
+	// camera will triggered inside/outside effects correctly; only one proxim trigger component with a given viewport should be active at any one time
+	int								m_viewport;
+	
+	// peer components
+	CCameraComponent*				mp_camera_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/RailManagerComponent.cpp b/Code/Gel/Components/RailManagerComponent.cpp
new file mode 100644
index 0000000..d4b3f76
--- /dev/null
+++ b/Code/Gel/Components/RailManagerComponent.cpp
@@ -0,0 +1,222 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RailManagerComponent.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/17/03
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+#include 
+
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+CBaseComponent* CRailManagerComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CRailManagerComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CRailManagerComponent::CRailManagerComponent() : CBaseComponent()
+{
+	SetType( CRC_RAILMANAGER );
+	mp_rail_manager = NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CRailManagerComponent::~CRailManagerComponent()
+{
+	// Remove the CRailManager if present.
+	if( mp_rail_manager )
+	{
+		delete mp_rail_manager;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CRailManagerComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// There needs to be a NodeArrayComponent attached for the RailManagerComponent to operate.
+	CNodeArrayComponent *p_nodearray_component = GetNodeArrayComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_nodearray_component, ( "RailManagerComponent created without NodeArrayComponent" ));
+
+	// Remove any existing CRailManager.
+	if( mp_rail_manager )
+	{
+		delete mp_rail_manager;
+	}
+
+	// Create a rail manager, and parse the node array for the rails.
+	mp_rail_manager = new Obj::CRailManager();
+	mp_rail_manager->AddRailsFromNodeArray( p_nodearray_component->GetNodeArray());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CRailManagerComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CRailManagerComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CRailManagerComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRailManagerComponent::GetDebugInfo( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ("NULL p_info sent to CRailManagerComponent::GetDebugInfo" ));
+
+	// Add the number of Rail Manager nodes.
+	if( mp_rail_manager )
+	{
+		p_info->AddInteger("RailManager.m_num_nodes", mp_rail_manager->GetNumNodes());
+		p_info->AddString("RailManager.m_is_transformed", mp_rail_manager->IsMoving() ? "yes" : "no" );
+	}
+
+	// We call the base component's GetDebugInfo, so we can add info from the common base component.
+	CBaseComponent::GetDebugInfo( p_info );
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Matrix CRailManagerComponent::UpdateRailManager (   )
+{
+	Dbg_Assert(mp_rail_manager);
+
+	// form a transformation matrix
+	Mth::Matrix total_mat = GetObject()->GetMatrix();
+	total_mat[X][W] = 0.0f;
+	total_mat[Y][W] = 0.0f;
+	total_mat[Z][W] = 0.0f;
+	total_mat[W] = GetObject()->GetPos();
+	total_mat[W][W] = 1.0f;
+
+	mp_rail_manager->UpdateTransform(total_mat);
+	
+	return total_mat;
+}
+	
+}
diff --git a/Code/Gel/Components/RailManagerComponent.h b/Code/Gel/Components/RailManagerComponent.h
new file mode 100644
index 0000000..5d2d8c3
--- /dev/null
+++ b/Code/Gel/Components/RailManagerComponent.h
@@ -0,0 +1,58 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RailManagerComponent.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/17/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_RAILMANAGERCOMPONENT_H__
+#define __COMPONENTS_RAILMANAGERCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_RAILMANAGER								CRCD( 0x1cdfb2a3, "RailManager" )
+#define		GetRailManagerComponent()					((Obj::CRailManagerComponent*)GetComponent( CRC_RAILMANAGER ))
+#define		GetRailManagerComponentFromObject( pObj )	((Obj::CRailManagerComponent*)(pObj)->GetComponent( CRC_RAILMANAGER ))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CRailManagerComponent : public CBaseComponent
+{
+public:
+    CRailManagerComponent();
+    virtual ~CRailManagerComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	Mth::Matrix						UpdateRailManager (   );
+
+	virtual Obj::CRailManager*		GetRailManager( void )	{ return mp_rail_manager; }
+
+private:
+	Obj::CRailManager*				mp_rail_manager;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/RibbonComponent.cpp b/Code/Gel/Components/RibbonComponent.cpp
new file mode 100644
index 0000000..1f58236
--- /dev/null
+++ b/Code/Gel/Components/RibbonComponent.cpp
@@ -0,0 +1,284 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RibbonComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  5/20/3
+//****************************************************************************
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPB(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a ? "true" : "false"); }
+#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
+#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
+#define DUMPM(a) { DUMP4(a[X]); DUMP4(a[Y]); DUMP4(a[Z]); DUMP4(a[W]); }
+#define DUMP2(a) { printf("D:%s:%i " #a " = ", __FILE__ + 15, __LINE__); for (int n = 32; n--; ) { printf("%c", ((a) & (1 << n)) ? '1' : '0'); } printf("\n"); }
+#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
+#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CRibbonComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CRibbonComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CRibbonComponent::CRibbonComponent() : CBaseComponent()
+{
+	SetType( CRC_RIBBON );
+	
+	mp_links = NULL;
+	
+	m_last_frame_length = 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CRibbonComponent::~CRibbonComponent()
+{
+	if (mp_links)
+	{
+		delete mp_links;
+		mp_links = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRibbonComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	pParams->GetChecksum(CRCD(0xcab94088, "bone"), &m_bone_name, Script::ASSERT);
+	pParams->GetInteger(CRCD(0x69feef91, "num_links"), &m_num_links, Script::ASSERT);
+	pParams->GetFloat(CRCD(0x9f4625c2, "link_length"), &m_link_length, Script::ASSERT);
+	pParams->GetChecksum(CRCD(0x99a9b716, "color"), &m_color, Script::ASSERT);
+	
+	mp_links = new SLink [ m_num_links ];
+	for (int n = m_num_links; n--; )
+	{
+		mp_links[n].p.Set();
+		mp_links[n].last_p.Set();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRibbonComponent::Finalize()
+{
+	mp_skeleton_component = GetSkeletonComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_skeleton_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRibbonComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRibbonComponent::Update()
+{
+	float frame_length = Tmr::FrameLength();
+	
+	// update link positions based on velocity
+	for (int n = m_num_links; n--; )
+	{
+		Mth::Vector vel = mp_links[n].p - mp_links[n].last_p;
+		vel *= 1.0f / m_last_frame_length;
+		
+		// damp
+		vel -= 10.0f * frame_length * vel;
+		
+		// momentum
+		mp_links[n].next_p = mp_links[n].p + frame_length * vel;
+		
+		// gravity
+		mp_links[n].next_p[Y] -= 800.0f * frame_length * frame_length;
+	}
+	
+	// fetch bone position
+	Mth::Vector bone_p;
+	mp_skeleton_component->GetBonePosition(m_bone_name, &bone_p);
+	bone_p = GetObject()->GetMatrix().Rotate(bone_p);
+	bone_p += GetObject()->GetPos();
+	
+	// update link constraints
+	for (int c = 20; c--; )
+	{
+		// first link special case
+		float length = (mp_links[0].next_p - bone_p).Length();
+		Mth::Vector axis = (mp_links[0].next_p - bone_p) * (1.0f / length);
+		float adjustment = m_link_length - length;
+		mp_links[0].next_p += adjustment * axis;
+		
+		// second and subsequent links
+		for (int n = 1; n < m_num_links; n++)
+		{
+			float length = (mp_links[n].next_p - mp_links[n - 1].next_p).Length();
+			Mth::Vector axis = (mp_links[n].next_p - mp_links[n - 1].next_p) * (1.0f / length);
+			
+			float adjustment = 0.5f * (m_link_length - length);
+			
+			// hack
+			if (length > 20.0f * m_link_length)
+			{
+				CallMemberFunction(CRCD(0x796084f4, "Ribbon_Reset"), NULL, NULL);
+				return;
+			}
+			
+			mp_links[n].next_p += adjustment * axis;
+			mp_links[n - 1].next_p -= adjustment * axis;
+		}
+	}
+	
+	#if 0
+	
+	// cheap collision detection
+	float min_y = GetObject()->GetPos()[Y] + 0.1f;
+	for (int n = m_num_links; n--; )
+	{
+		mp_links[n].next_p[Y] = Mth::ClampMin(mp_links[n].next_p[Y], min_y);
+	}
+	
+	#else
+	
+	Mth::CBBox bbox;
+	Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
+	for (int n = m_num_links; n--; )
+	{
+		bbox.AddPoint(mp_links[n].p);
+		bbox.AddPoint(mp_links[n].next_p);
+	}
+	p_coll_cache->Update(bbox);
+	
+	CFeeler feeler;
+	feeler.SetCache(p_coll_cache);
+	// real collision detection
+	for (int n = m_num_links; n--; )
+	{
+		feeler.m_start = mp_links[n].p;
+		feeler.m_end = mp_links[n].next_p;
+		if (!feeler.GetCollision()) continue;
+		
+		Mth::Vector movement = mp_links[n].next_p - mp_links[n].p;
+		
+		mp_links[n].next_p = feeler.GetPoint() + 0.1f * feeler.GetNormal();
+		
+		// assume no additional collision
+		movement *= 1.0f - feeler.GetDist();
+		movement.ProjectToPlane(feeler.GetNormal());
+		
+		mp_links[n].next_p += movement;
+	}
+	
+	Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+	
+	#endif
+	
+	// update state
+	for (int n = m_num_links; n--; )
+	{
+		mp_links[n].last_p = mp_links[n].p;
+		mp_links[n].p = mp_links[n].next_p;
+	}
+	m_last_frame_length = frame_length;
+	
+	// debug draw
+	Gfx::AddDebugLine(bone_p, mp_links[0].p, m_color, 0, 1);
+	for (int n = 1; n < m_num_links; n++)
+	{
+		Gfx::AddDebugLine(mp_links[n].p, mp_links[n - 1].p, m_color, 0, 1);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CRibbonComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case CRCC(0x796084f4, "Ribbon_Reset"):
+		{
+			MESSAGE("Resetting");
+			
+			Mth::Vector bone_p;
+			mp_skeleton_component->GetBonePosition(m_bone_name, &bone_p);
+			bone_p = GetObject()->GetMatrix().Rotate(bone_p);
+			bone_p += GetObject()->GetPos();
+			
+			for (int n = m_num_links; n--; )
+			{
+				mp_links[n].p = bone_p;
+				mp_links[n].last_p = bone_p;
+				mp_links[n].p[Y] += 0.1f * n;
+				mp_links[n].last_p[Y] += 0.1f * n;
+			}
+			break;
+		}
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRibbonComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CRibbonComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+}
diff --git a/Code/Gel/Components/RibbonComponent.h b/Code/Gel/Components/RibbonComponent.h
new file mode 100644
index 0000000..a2be771
--- /dev/null
+++ b/Code/Gel/Components/RibbonComponent.h
@@ -0,0 +1,71 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RibbonComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  5/20/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_RIBBONCOMPONENT_H__
+#define __COMPONENTS_RIBBONCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_RIBBON CRCD(0xee6fc5b, "Ribbon")
+
+#define		GetRibbonComponent() ((Obj::CRibbonComponent*)GetComponent(CRC_RIBBON))
+#define		GetRibbonComponentFromObject(pObj) ((Obj::CRibbonComponent*)(pObj)->GetComponent(CRC_RIBBON))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkeletonComponent;
+
+class CRibbonComponent : public CBaseComponent
+{
+	struct SLink
+	{
+		Mth::Vector					p;
+		Mth::Vector					last_p;
+		Mth::Vector					next_p;
+	};
+	
+public:
+    CRibbonComponent();
+    virtual ~CRibbonComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual	void 					Finalize();
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+private:
+	uint32							m_bone_name;
+	int								m_num_links;
+	float							m_link_length;
+	uint32							m_color;
+	
+	float							m_last_frame_length;
+	
+	SLink*							mp_links;
+	
+	// peer components
+	CSkeletonComponent*				mp_skeleton_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/RiderComponent.cpp b/Code/Gel/Components/RiderComponent.cpp
new file mode 100644
index 0000000..e903ac1
--- /dev/null
+++ b/Code/Gel/Components/RiderComponent.cpp
@@ -0,0 +1,757 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RiderComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/2/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+
+
+namespace Obj
+{
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent* CRiderComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CRiderComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CRiderComponent::CRiderComponent() : CBaseComponent()
+{
+	SetType( CRC_RIDER );
+	
+	mp_collision_cache = Nx::CCollCacheManager::sCreateCollCache();
+	
+	mp_input_component				= NULL;
+	mp_animation_component			= NULL;
+	mp_movable_contact_component	= NULL;
+	mp_horse_component				= NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CRiderComponent::~CRiderComponent()
+{
+	Nx::CCollCacheManager::sDestroyCollCache(mp_collision_cache);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CRiderComponent::Finalize()
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	mp_animation_component = GetAnimationComponentFromObject(GetObject());
+	mp_model_component = GetModelComponentFromObject(GetObject());
+	mp_trigger_component = GetTriggerComponentFromObject(GetObject());
+	mp_physics_control_component = GetSkaterPhysicsControlComponentFromObject(GetObject());
+	mp_movable_contact_component = GetMovableContactComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_input_component);
+	Dbg_Assert(mp_animation_component);
+	Dbg_Assert(mp_model_component);
+	Dbg_Assert(mp_trigger_component);
+	Dbg_Assert(mp_physics_control_component);
+	Dbg_Assert(mp_movable_contact_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRiderComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 camera_id;
+	if (pParams->GetChecksum(CRCD(0xc4e311fa, "camera"), &camera_id))
+	{
+		CCompositeObject* p_camera = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(camera_id));
+		Dbg_MsgAssert(mp_camera, ("No such camera object"));
+		SetAssociatedCamera(p_camera);
+	}
+	
+	m_script_drag_factor = 1.0f;
+}
+		 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRiderComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRiderComponent::Update()
+{
+	if( mp_horse_component == NULL )
+	{
+		return;
+	}
+
+	// TEMP: debounce R1 after a transition
+//	if (m_ignore_grab_button && !mp_input_component->GetControlPad().m_R1.GetPressed())
+//	{
+//		m_ignore_grab_button = false;
+//	}
+	
+	// zero the frame event
+	m_last_frame_event = m_frame_event;
+	m_frame_event = 0;
+	
+	// get input
+//    get_controller_input();
+	
+	// extract initial state for this frame from the object
+	m_frame_start_pos = m_pos = GetObject()->GetPos();
+	
+	m_horizontal_vel = GetObject()->GetVel();
+	m_horizontal_vel[Y] = 0.0f;
+	m_vertical_vel = GetObject()->GetVel()[Y];
+	
+	// note that m_facing and m_upward will often not be orthogonal, but will always span a plan
+	
+	// generally straight up, but now after a transition from skating
+	m_upward = GetObject()->GetMatrix()[Y];
+	
+	m_facing = GetObject()->GetMatrix()[Z];
+	m_facing[Y] = 0.0f;
+	float length = m_facing.Length();
+	if (length < 0.001f)
+	{
+		// upward facing orientation matrix
+		m_facing = -GetObject()->GetMatrix()[Y];
+		m_facing[Y] = 0.0f;
+		m_facing.Normalize();
+		
+		// since m_upward is now in the same plan as m_facing, push m_upward up a touch
+		m_upward[Y] += 0.01f;
+		m_upward.Normalize();
+	}
+	else
+	{
+		m_facing /= length;
+	}
+	
+	// set the frame length
+	m_frame_length = Tmr::FrameLength();
+	
+	// go to our true Y position
+	m_curb_float_height_adjusted = false;
+	m_pos[Y] -= m_curb_float_height;
+	
+	// switch logic based on walking state
+	go_on_horse_state();
+	
+//	switch (m_state)
+//	{
+//		case WALKING_GROUND:
+//			go_on_ground_state();
+//			break;
+//
+//		case WALKING_AIR:
+//			go_in_air_state();
+//			break;
+//																	  
+//		case WALKING_HOP:
+//			go_hop_state();
+//			break;
+//																	  
+//		case WALKING_HANG:
+//			go_hang_state();
+//			break;
+//            
+//		case WALKING_LADDER:
+//			go_ladder_state();
+//          break;
+//			
+//		case WALKING_ANIMWAIT:
+//			go_anim_wait_state (   );
+//			break;
+//	}
+	
+	// the there's no curb to adjust due to, lerp down to zero
+//	if (!m_curb_float_height_adjusted)
+//	{
+//		m_curb_float_height = Mth::Lerp(m_curb_float_height, 0.0f, s_get_param(CRCD(0x9b3388fa, "curb_float_lerp_down_rate")) * m_frame_length);
+//	}
+	
+	// adjust back to our curb float Y position
+//	m_pos[Y] += m_curb_float_height;
+	
+	// scripts may have restarted us / switched us to skating
+//	if (should_bail_from_frame()) return;
+	
+	// keep the object from falling through holes in the geometry
+//	if (m_state == WALKING_GROUND || m_state == WALKING_AIR)
+//	{
+//		uber_frig();
+//	}
+	
+	// rotate to upright
+//	lerp_upright();
+	
+	// setup the object based on this frame's walking
+//	copy_state_into_object();
+	
+//	Dbg_Assert(m_frame_event);
+//	GetObject()->SelfEvent(m_frame_event);
+	
+	// set the animation speeds
+//	switch (m_anim_scale_speed)
+//	{
+//		case RUNNING:
+//			if (m_anim_standard_speed > 0.0f)
+//			{
+//				mp_animation_component->SetAnimSpeed(m_anim_effective_speed / m_anim_standard_speed, false, false);
+//			}
+//			break;
+//			
+//		case HANGMOVE:
+//			mp_animation_component->SetAnimSpeed(m_anim_effective_speed / s_get_param(CRCD(0xd77ee881, "hang_move_speed")), false, false);
+//			break;
+//					
+//		case LADDERMOVE:
+//			mp_animation_component->SetAnimSpeed(m_anim_effective_speed / s_get_param(CRCD(0xab2db54, "ladder_move_speed")), false, false);
+//			break;
+//	
+//		default:
+//			break;
+//	}
+	
+	// camera controls
+	// NOTE: script parameters
+//	switch (m_frame_event)
+//	{
+//		case CRCC(0xf41aba21, "Hop"):
+//			mp_camera_component->SetOverrides(m_facing, 0.05f);
+//			break;
+//		
+//		case CRCC(0x2d9815c3, "HangMoveLeft"):
+//		{
+//			Mth::Vector facing = m_facing;
+//			facing.RotateY(-0.95f);
+//			mp_camera_component->SetOverrides(facing, 0.05f);
+//			break;
+//		}
+//			
+//		case CRCC(0x279b1f0b, "HangMoveRight"):
+//		{
+//			Mth::Vector facing = m_facing;
+//			facing.RotateY(0.95f);
+//			mp_camera_component->SetOverrides(facing, 0.05f);
+//			break;
+//		}
+//					
+//		case CRCC(0x4194ecca, "Hang"):
+//			mp_camera_component->SetOverrides(m_facing, 0.05f);
+//			break;
+//		
+//		case CRCC(0xc84243da, "Ladder"):
+//		case CRCC(0xaf5abc82, "LadderMoveUp"):
+//		case CRCC(0xfec9dded, "LadderMoveDown"):
+//			mp_camera_component->SetOverrides(m_facing, 0.05f);
+//			break;
+//					
+//		case CRCC(0x4fe6069c, "AnimWait"):
+//			if (m_anim_wait_camera_mode == AWC_CURRENT)
+//			{
+//				mp_camera_component->SetOverrides(m_facing, 0.05f);
+//			}
+//			else
+//			{
+//				mp_camera_component->SetOverrides(m_drift_goal_facing, 0.05f);
+//			}
+//			break;
+//	
+//		default:
+//			mp_camera_component->UnsetOverrides();
+//			break;
+//	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent::EMemberFunctionResult CRiderComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+#		if 0
+		// @script | Rider_Ground |
+		case CRCC(0x893213e5, "Rider_Ground"):
+			return m_state == WALKING_GROUND ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Rider_Air |
+		case CRCC(0x5012082e, "Rider_Air"):
+			return m_state == WALKING_AIR ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Rider_Hang |
+		case CRCC(0x9a3ca853, "Rider_Hang"):
+			return m_state == WALKING_HANG ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Rider_Ladder |
+		case CRCC(0x19702ca8, "Rider_Ladder"):
+			return m_state == WALKING_LADDER ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Rider_GetStateTime | Loads the time in milliseconds since last state change.
+		case CRCC(0xce64576c, "Rider_GetStateTime"):
+			pScript->GetParams()->AddInteger(CRCD(0x5ab23cc9, "StateTime"), Tmr::ElapsedTime(m_state_timestamp));
+			break;
+		
+		// @script | Rider_Jump |
+		case CRCC(0x83e4bd70, "Rider_Jump"):
+		{
+			// jump strength scales with the length the jump button has been held
+			jump(Mth::Lerp(
+				s_get_param(CRCD(0x246d0bf3, "min_jump_factor")), 
+				1.0f,
+				Mth::ClampMax(mp_input_component->GetControlPad().m_x.GetPressedTime() / s_get_param(CRCD(0x12333ebd, "hold_time_for_max_jump")), 1.0f)
+			));
+			break;
+		}
+		
+		case CRCC(0xeb0d763b, "Rider_HangJump"):
+		{
+			// jump strength scales with the length the jump button has been held
+			jump(s_get_param(CRCD(0xf2fa5845, "hang_jump_factor")), true);
+			break;
+		}
+		
+		// @script | Rider_SetDragFactor |
+		case CRCC(0xc6100a7d, "Rider_SetDragFactor"):
+			pParams->GetFloat(NO_NAME, &m_script_drag_factor);
+			break;
+			
+		case CRCC(0x4e4fae43, "Rider_ResetDragFactor"):
+			m_script_drag_factor = 1.0f;
+			break;
+			
+		case CRCC(0xaf04b983, "Rider_GetSpeedScale"):
+		{
+			uint32 checksum;
+			if (m_anim_effective_speed < s_get_param(CRCD(0xf3649996, "max_slow_walk_speed")))
+			{
+				checksum = CRCD(0x1150cabb, "WALK_SLOW");
+			}
+			else if (m_anim_effective_speed < s_get_param(CRCD(0x6a5805d8, "max_fast_walk_speed")))
+			{
+				checksum = CRCD(0x131f2a2, "WALK_FAST");
+			}
+			else if (m_anim_effective_speed < s_get_param(CRCD(0x1c94cc9c, "max_slow_run_speed")))
+			{
+				checksum = CRCD(0x5606d106, "RUN_SLOW");
+			}
+			else
+			{
+				checksum = CRCD(0x4667e91f, "RUN_FAST");
+			}
+			pScript->GetParams()->AddChecksum(CRCD(0x92c388f, "SpeedScale"), checksum);
+			
+			break;
+		}
+		
+		// @script | Rider_ScaleAnimSpeed | Sets the manner in which the walk animations speeds should be scaled.
+		// @flag Off | No animation speed scaling.
+		// @flag Run | Scale animation speeds against running speed.
+		// @flag Rider | Scale animation speeds against walking speed.
+		case CRCC(0x56112c03, "Rider_ScaleAnimSpeed"):
+			if (pParams->ContainsFlag(CRCD(0xd443a2bc, "Off")))
+			{
+				if (m_anim_scale_speed != OFF)
+				{
+					m_anim_scale_speed = OFF;
+					mp_animation_component->SetAnimSpeed(1.0f, false, true);
+				}
+			}
+			else if (pParams->ContainsFlag(CRCD(0xaf895b3f, "Run")))
+			{
+				m_anim_scale_speed = RUNNING;
+			}
+			else if (pParams->ContainsFlag(CRCD(0x6384f1da, "HangMove")))
+			{
+				m_anim_scale_speed = HANGMOVE;
+			}
+			else if (pParams->ContainsFlag(CRCD(0xa2bfe505, "LadderMove")))
+			{
+				m_anim_scale_speed = LADDERMOVE;
+			}
+			else
+			{
+				Dbg_MsgAssert(false, ("Rider_ScaleAnimSpeed requires Off, Run, or Rider flag"));
+			}
+			
+			pParams->GetFloat(CRCD(0xb2d59baf, "StandardSpeed"), &m_anim_standard_speed);
+			break;
+			
+		// @script | Rider_AnimWaitComplete | Signal from script that the walk component should leave its animation wait
+		case CRCC(0x9d3eebe8, "Rider_AnimWaitComplete"):
+			anim_wait_complete();
+			break;
+				   			
+		// @script | Rider_GetHangInitAnimType | Determine which type of initial hang animation should be played
+		case CRCC(0xc6cd659e, "Rider_GetHangInitAnimType"):
+			// m_initial_hang_animation is set when the hang rail is filtered
+			pScript->GetParams()->AddChecksum(CRCD(0x85fa9ac4, "HangInitAnimType"), m_initial_hang_animation);
+			break;
+#		endif
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRiderComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CRiderComponent::GetDebugInfo"));
+	
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x58007c97, "GROUND"));
+			break;
+		case WALKING_AIR:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x439f4704, "AIR"));
+			break;
+		case WALKING_HOP:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0xf41aba21, "HOP"));
+			break;										 
+		case WALKING_HANG:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x4194ecca, "HANG"));
+			break;										 
+        case WALKING_LADDER:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0xc84243da, "LADDER"));
+			break;										 
+        case WALKING_ANIMWAIT:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x4fe6069c, "ANIMWAIT"));
+			break;										 
+	}
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRiderComponent::SetAssociatedCamera( CCompositeObject* camera_obj )
+{
+//	mp_camera = camera_obj;
+//	Dbg_Assert(mp_camera);
+//	mp_camera_component = GetWalkCameraComponentFromObject(mp_camera);
+//	Dbg_MsgAssert(mp_camera_component, ("No RiderCameraComponent in camera object"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CRiderComponent::ReadyRiderState( bool to_ground_state )
+{
+	// setup the state in preparation for being in walking mode next object update
+	MARK;
+	
+	// Firstly ensure that there is a valid horse nearby.
+	mp_horse_component						= NULL;
+
+	Obj::CHorseComponent* p_horse_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_HORSE ));
+	while( p_horse_component )
+	{
+		Obj::CCompositeObject*	p_horse		= p_horse_component->GetObject();
+		Mth::Vector				horse_pos	= p_horse->GetPos();
+		float					dist		= Mth::Distance( GetObject()->GetPos(), horse_pos );
+		if( dist < 60.0f )
+		{
+			mp_horse_component				= p_horse_component;
+			break;
+		}
+		p_horse_component					= static_cast( p_horse_component->GetNextSameType());
+	}
+
+	if( mp_horse_component == NULL )
+	{
+		// Don't want to proceed.
+		return false;
+
+	}
+	
+	printf( "ReadyRiderState() found horse\n" );
+
+	mp_horse_component->AcceptRiderMount( GetObject());
+
+	// always reset the state timestamp
+    m_state_timestamp = Tmr::GetTime();
+
+	if (to_ground_state)
+	{
+		set_state(WALKING_GROUND);
+		
+		// will be incorrect for one frame
+		m_ground_normal.Set(0.0f, 1.0f, 0.0);
+		
+		m_last_ground_feeler_valid = false;
+		
+		GetObject()->GetVel()[Y] = 0.0f;
+	}
+	else
+	{
+		set_state(WALKING_AIR);
+		
+		// set primary air direction in the direction of velocity
+		m_primary_air_direction = GetObject()->GetVel();
+		m_primary_air_direction[Y] = 0.0f;
+		float length = m_primary_air_direction.Length();
+		if (length < 0.001f)
+		{
+			// or facing
+			m_primary_air_direction = GetObject()->GetMatrix()[Z];
+			m_primary_air_direction[Y] = 0.0f;
+			length = m_primary_air_direction.Length();
+			if (length < 0.001f)
+			{
+				// or future facing
+				m_primary_air_direction = -GetObject()->GetMatrix()[Y];
+				m_primary_air_direction[Y] = 0.0f;
+				length = m_primary_air_direction.Length();
+			}
+		}
+		m_primary_air_direction /= length;
+		
+//		leave_movable_contact_for_air(GetObject()->GetVel(), GetObject()->GetVel()[Y]);
+	}
+
+	m_curb_float_height = 0.0f;
+	
+	m_last_frame_event = 0;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRiderComponent::go_on_horse_state( void )
+{
+	Mth::Vector pos;
+	Mth::Matrix	mat, display_mat;
+
+	pos			= mp_horse_component->GetPos();
+	mat			= mp_horse_component->GetMatrix();
+	display_mat	= mp_horse_component->GetDisplayMatrix();
+
+	GetObject()->SetPos( pos );
+	GetObject()->SetMatrix( mat );
+	GetObject()->SetDisplayMatrix( display_mat );
+
+	// Check for trying to dismount a horse, triggered on triangle.
+	if( mp_input_component->GetControlPad().m_triangle.GetTriggered())
+	{
+		mp_input_component->GetControlPad().m_triangle.ClearTrigger();
+
+		// Get the control component.
+		CSkaterPhysicsControlComponent*	p_control_component = GetSkaterPhysicsControlComponentFromObject( GetObject());
+		p_control_component->CallMemberFunction( CRCD( 0x82604c1e, "SkaterPhysicsControl_SwitchRidingToWalking"), NULL, NULL );
+
+		mp_horse_component->AcceptRiderDismount( GetObject());
+
+		m_frame_event = CRCD( 0x9b46e749, "Stand" );
+		GetObject()->SelfEvent( m_frame_event );
+		return;
+	}
+
+	CAnimationComponent*	p_anim_component	= GetAnimationComponentFromObject( GetObject());
+
+	Script::CStruct* p_temp_struct = new Script::CStruct();
+
+	// Decide which animation to play.
+	switch( mp_horse_component->GetAnimation())
+	{
+		case CRCC(0xae7deda,"Horse_Jump"):
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0xe978150a,"Rider_Jump"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+		case CRCC( 0xa337737a, "Horse_Airidle" ):
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0x6600c6f9,"Rider_Airidle"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_temp_struct->AddChecksum( (uint32)0, 0x4f792e6c/*"Cycle"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+		case CRCC( 0x5540d14, "Horse_Land" ):
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0xe6cbc6c4,"Rider_Land"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+		case CRCC(0x20c5a299,"Horse_Walk"):
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0xc35a6949,"Rider_Walk"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_temp_struct->AddChecksum( (uint32)0, 0x4f792e6c/*"Cycle"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+		case CRCC(0x8a354e68,"Horse_Trot"):
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0x69aa85b8,"Rider_Trot"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_temp_struct->AddChecksum( (uint32)0, 0x4f792e6c/*"Cycle"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+		case CRCC(0x1bee30eb,"Horse_Canter"):
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0x96600d53,"Rider_Canter"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_temp_struct->AddChecksum( (uint32)0, 0x4f792e6c/*"Cycle"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+		case CRCC(0x2ca2c118,"Horse_Gallop"):
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0xa12cfca0,"Rider_Gallop"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_temp_struct->AddChecksum( (uint32)0, 0x4f792e6c/*"Cycle"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+		case CRCC(0xf0ebc152,"Horse_StandIdle"):
+		default:
+		{
+			p_temp_struct->AddChecksum( CRCD(0x98549ba4,"Anim"), CRCD(0x5b4e88ee,"Rider_StandIdle"));
+			p_temp_struct->AddChecksum( (uint32)0, 0xfe09fe09/*"NoRestart"*/);
+			p_temp_struct->AddChecksum( (uint32)0, 0x4f792e6c/*"Cycle"*/);
+			p_anim_component->PlayAnim( p_temp_struct, NULL, 0.3f );
+			break;
+		}
+	}
+
+	delete p_temp_struct;
+}
+
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRiderComponent::go_on_ground_state (   )
+{
+#	if 0
+
+	account_for_movable_contact();
+	
+	setup_collision_cache();
+	
+	// calculate initial horizontal speed
+	float horizontal_speed = m_horizontal_vel.Length();
+	
+	calculate_horizontal_speed_and_facing(horizontal_speed);
+	
+	// calculate this frame's movement
+	m_horizontal_vel = horizontal_speed * m_facing;
+	
+	// prevent movement into walls
+	if (adjust_horizonal_vel_for_environment(true))
+	{
+		// turn to face newly adjusted velocity
+		adjust_facing_for_adjusted_horizontal_vel();
+	}
+	
+	// if we are wall pushing, we may have decided to switch states during adjust_horizonal_vel_for_environment based on our environment
+	if (m_state != WALKING_GROUND || should_bail_from_frame())
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	// apply movement for this frame
+	m_pos += m_horizontal_vel * m_frame_length;
+	
+	// snap up and down curbs and perhaps switch to air
+	respond_to_ground();
+	if (m_state != WALKING_GROUND || should_bail_from_frame())
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	adjust_curb_float_height();
+	
+	// insure that we do not slip through the cracks in the collision geometry which are a side-effect of moving collidable objects
+	if (CCompositeObject* p_inside_object = mp_movable_contact_component->CheckInsideObjects(m_pos, m_frame_start_pos))
+	{
+		MESSAGE("WALKING_GROUND, within moving object");
+		
+		// allow it to push us forward, causing a bit of a stumble
+		m_horizontal_vel = p_inside_object->GetVel();
+		m_horizontal_vel[Y] = 0.0f;
+		m_vertical_vel = 0.0f;
+		
+		float speed_sqr = m_horizontal_vel.LengthSqr();
+		if (speed_sqr > (10.0f * 10.0f))
+		{
+			m_facing = m_horizontal_vel * (1.0f / sqrtf(speed_sqr));
+		}
+	}
+	
+	CFeeler::sClearDefaultCache();
+#	endif
+}
+
+
+}
+
diff --git a/Code/Gel/Components/RiderComponent.h b/Code/Gel/Components/RiderComponent.h
new file mode 100644
index 0000000..8b0698f
--- /dev/null
+++ b/Code/Gel/Components/RiderComponent.h
@@ -0,0 +1,491 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RiderComponent.h
+//* OWNER:          Dave
+//* CREATION DATE:  5/30/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_RIDERCOMPONENT_H__
+#define __COMPONENTS_RIDERCOMPONENT_H__
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#define		CRC_RIDER CRCD(0x15beefca,"Rider")
+
+#define		GetRiderComponent()					((Obj::CRiderComponent*)GetComponent(CRC_RIDER))
+#define		GetRiderComponentFromObject(pObj)	((Obj::CRiderComponent*)(pObj)->GetComponent(CRC_RIDER))
+
+class CFeeler;
+		
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+
+namespace Nx
+{
+	class CCollCache;
+}
+              
+namespace Obj
+{
+	class CAnimationComponent;
+	class CModelComponent;
+	class CTriggerComponent;
+	class CRiderCameraComponent;
+	class CSkaterPhysicsControlComponent;
+	class CMovableContactComponent;
+	
+class CRiderComponent : public CBaseComponent
+{
+public:
+	enum EStateType
+	{
+		WALKING_GROUND,
+		WALKING_AIR,
+		WALKING_HOP,
+		WALKING_HANG,
+		WALKING_LADDER,
+		WALKING_ANIMWAIT
+	};
+	
+	enum EAnimScaleSpeed
+	{
+		OFF,
+		RUNNING,
+        HANGMOVE,
+		LADDERMOVE
+	};
+	
+	enum
+	{
+		vNUM_FEELERS = 7,
+		vNUM_BARRIER_HEIGHT_FEELERS = 7
+	};
+	
+	typedef void (Obj::CRiderComponent::*AnimWaitCallbackPtr) (   );
+	
+public:
+    CRiderComponent();
+    virtual ~CRiderComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	EStateType						GetState (   ) { return m_state; }
+	
+private:
+	void							set_state ( EStateType state );
+	
+	void							go_on_horse_state( void );
+
+	void							go_on_ground_state (   );
+	void							calculate_horizontal_speed_and_facing ( float &horizontal_speed );
+	bool							adjust_horizonal_vel_for_environment ( bool wall_push_active );
+	void							adjust_facing_for_adjusted_horizontal_vel (   );
+	float							adjust_desired_speed_for_slope ( float desired_speed );
+	void							respond_to_ground (   );
+	void							adjust_curb_float_height (   );
+	void							account_for_movable_contact (   );
+	void							jump ( float strength, bool hang_jump = false );
+	void							go_in_air_state (   );
+	void							control_horizontal_vel (   );
+	void							adjust_vertical_vel_for_ceiling (   );
+	void							check_for_landing ( const Mth::Vector& previous_pos, const Mth::Vector& final_pos );
+	void							uber_frig (   );
+	void							lerp_upright (   );
+	bool							check_for_wall_push (   );
+	void							leave_movable_contact_for_air ( Mth::Vector& horizontal_vel, float& vertical_vel );
+	bool							maybe_hop_to_hang (   );
+	bool							maybe_drop_to_hang (   );
+	bool							maybe_grab_to_hang ( Mth::Vector start_pos, Mth::Vector end_pos );
+	void							go_hop_state (   );
+	void							go_hang_state (   );
+	void							hang_movement (   );
+	void							find_neighboring_hang_rail ( float movement, bool descending );
+	void							set_pos_from_hang_rail_state (   );
+	bool							maybe_pull_up_from_hang (   );
+	bool							maybe_jump_low_barrier (   );
+	bool							maybe_climb_up_ladder ( bool user_requested = false );
+	bool							maybe_climb_down_ladder (   );
+	bool							maybe_grab_to_ladder ( const Mth::Vector& start_pos, const Mth::Vector& end_pos );
+	void							go_ladder_state (   );
+	void							ladder_movement (   );
+	void							off_ladder_bottom (   );
+	void							off_ladder_top (   );
+	void							calculate_anim_wait_facing_drift_parameters ( const Mth::Vector& goal_facing );
+    void                            go_anim_wait_state (   );
+    void                            anim_wait_complete (   );
+	bool							maybe_stick_to_rail (   );
+	void							setup_collision_cache (   );
+	void							copy_state_into_object (   );
+	void							get_controller_input (   );
+	void							adjust_control_for_forced_run (   );
+	float							calculate_desired_speed (   );
+	Mth::Vector						calculate_feeler_offset_direction ( int contact );
+	bool							forced_run (   );
+	bool							determine_stand_pos ( const Mth::Vector& proposed_stand_pos, Mth::Vector& stand_pos, CFeeler& feeler );
+	bool							should_bail_from_frame (   );
+	
+	void							drop_to_hang_complete (   );
+	void							pull_up_from_hang_complete (   );
+	void							move_to_ladder_bottom_complete (   );
+	void							move_to_ladder_top_complete (   );
+	void							off_ladder_bottom_complete (   );
+	void							off_ladder_top_to_ground_complete (   );
+	void							off_ladder_top_to_air_complete (   );
+	
+	static float					s_get_param ( uint32 checksum );
+	
+public:
+	bool							IsRunning (   );
+	bool							UseDPadCamera (   );
+	void							SetForwardControlLock ( bool forward_control_lock );
+	void							SetAssociatedCamera ( CCompositeObject* camera_obj );
+    
+    const Mth::Vector&              GetEffectivePos (   );
+	
+	bool							ReadyRiderState ( bool to_ground_state );
+	
+private:
+	EStateType						m_state;
+	
+	// Time stamp of when our state last changed.
+	Tmr::Time						m_state_timestamp;
+	
+	// State we changed from when m_state_timestamp was last set.
+	EStateType						m_previous_state;
+	
+	// Extracted from the object at frame start and back into the object at frame end.
+	Mth::Vector						m_pos;
+	Mth::Vector						m_horizontal_vel;
+	float							m_vertical_vel;
+	Mth::Vector						m_facing;
+	Mth::Vector						m_upward;
+	
+	// Cache our position at the frame's start
+	Mth::Vector						m_frame_start_pos;
+	
+	// Script event reporting our current state.  Set during each frame update and sent at the end of the update.
+	uint32							m_frame_event;
+	
+	// Last frame's frame event.
+	uint32							m_last_frame_event;
+	
+	// Current frame's frame length.
+	float							m_frame_length;
+	
+	// Current frame's control input.
+	Mth::Vector						m_control_direction;
+	float							m_control_magnitude;
+	bool							m_control_pegged;
+	
+	struct SContact
+	{
+		// If this contact is in collision with the environment.
+		bool						in_collision;
+		
+		// The horizontal normal of the collided geometry.
+		Mth::Vector					normal;
+		
+		// Distance from the collision as a factor of the feeler length.
+		float						distance;
+		
+		// If the collision in this direction is determined to be low enough to jump.
+		bool						jumpable;
+		
+		// Movement of the object we're in contact with (not accounting for its rotation) along its horizontal normal.
+		float						movement;
+		
+	// The walker looks around him in vNUM_FEELERS directions and stores the results here.  The final contact is felt for along the movement of
+	// the origin, and is only used while in the air.
+	} mp_contacts [ vNUM_FEELERS + 1 ];
+	
+	// Type of animation speed scaling to do.
+	EAnimScaleSpeed					m_anim_scale_speed;
+	
+	// Horizontal speed for the purposes of animation speed scaling.
+	float							m_anim_effective_speed;
+	
+	// Factor multiplied by all velocities.  Set by script.
+	float							m_script_drag_factor;
+	
+	// Used to lock control in the forward direction (during a camera flush request)
+	bool							m_forward_control_lock;
+	
+	// When on ground, the walker is often floated above the ground in an attempt to smooth out stair/curb climbs.
+	float							m_curb_float_height;
+	
+	// If m_curb_float_height was adjusted this frame.  If not, we lerp it to zero.
+	bool							m_curb_float_height_adjusted;
+	
+	// The collision cache used.
+	Nx::CCollCache*					mp_collision_cache;
+	
+	// Associated camera.
+	CCompositeObject*				mp_camera;
+	CRiderCameraComponent*			mp_camera_component;
+	CHorseComponent*				mp_horse_component;		// This points to the CHorseComponent in the separate CCompositeObject that is the horse being ridden.
+	
+    // If associated with a rail, this is the rail node and rail manager.
+    const CRailNode*              	mp_rail_node;
+    CRailManager*                   mp_rail_manager;
+	
+	// Peer components.
+	CInputComponent*				mp_input_component;
+	CAnimationComponent*			mp_animation_component;
+	CModelComponent*				mp_model_component;
+	CTriggerComponent*				mp_trigger_component;
+	CSkaterPhysicsControlComponent*	mp_physics_control_component;
+	CMovableContactComponent*		mp_movable_contact_component;
+    
+	/////////////////////////////////
+	// WALKING_GROUND state variables
+	
+	// Normal of the ground on which we are standing.  Only meaningful when on the ground.  Set the previous frame.
+	Mth::Vector						m_ground_normal;
+	
+	// Certain things such as rotation rate and camera lerp are increased when this is set.
+	bool							m_run_toggle;
+	
+	struct SWallPushTest
+	{
+		// Whether we currently testing.
+		bool						active;
+		
+		// Time at which this test began.
+		Tmr::Time					test_start_time;
+		
+	// Data used to detect when a player is pushing into a wall in order to climb it.
+	}								m_wall_push_test;
+	
+	// When doing anim speed scaling when running, this is set to the run speed which corresponds to the current animation playing at standard speed.
+	float							m_anim_standard_speed;
+	
+	// A copy of the latest feeler which contacted the groud under us.
+	CFeeler							m_last_ground_feeler;
+	bool							m_last_ground_feeler_valid;
+	
+	/////////////////////////////////
+	// WALKING_AIR state variables
+	
+	// The forward horizontal direction of the jump which, when coming out of skating or because of in-air velocity control, diverges from the direction of velocity
+	Mth::Vector						m_primary_air_direction;
+	
+	// This term of our horizontal velocity is due to leaping from a movable contact, and is not adjustable via in-air control.
+	Mth::Vector						m_uncontrollable_air_horizontal_vel;
+	
+	/////////////////////////////////
+	// WALKING_HANG state variables
+	
+	// Measures our position along the current rail as a parameter running from 0.0 to 1.0.
+	float							m_along_rail_factor;
+	
+	// Measures the hang position offset from the actual rail we are hanging from.
+	float							m_vertical_hang_offset;
+	float							m_horizontal_hang_offset;
+	
+	// Checksum describing the type of initial hang animation to play.
+	uint32							m_initial_hang_animation;
+    
+	/*
+	/////////////////////////////////
+	// WALKING_HOP state variables
+	
+    // The position we will have at the end of the hop.
+    Mth::Vector                     m_goal_hop_pos;
+    
+    // The facing we will have at the end of the hop.
+    Mth::Vector                     m_goal_hop_facing;
+	
+	// Hang state variable.  Set at hop start.
+	// float						m_along_rail_factor;
+	
+	// Hang state variable.  Set at hop start.
+	// uint32							m_initial_hang_animation;
+	*/
+    
+	/////////////////////////////////
+	// WALKING_LADDER state variables
+	
+	// Shared with hang state.
+	// float						m_along_rail_factor;
+    
+	/////////////////////////////////
+	// WALKING_ANIMWAIT state variables
+	
+	// Position at the start of the animation wait.
+	Mth::Vector						m_anim_wait_initial_pos;
+	
+	// Facing at the start of the aniamtion wait.
+	Mth::Vector						m_anim_wait_initial_facing;
+	
+    // Amount to move the object at the end of the animation wait.
+    Mth::Vector                     m_anim_wait_goal_pos;
+	
+	// Goal facing of the position drift.
+	Mth::Vector						m_drift_goal_facing;
+	
+	// The angle over which the facing must drift.
+	float							m_drift_angle;
+    
+	// Accumulates position offset due to a movable contact during an animation wait.
+	Mth::Vector						m_offset_due_to_movable_contact;
+	
+	// The behavior of the camera during the animation wait.
+	enum EAnimWaitCameraModeType			
+	{
+		AWC_CURRENT,
+		AWC_GOAL
+	}								m_anim_wait_camera_mode;
+	
+	// Callback function to call upon completion of the animation wait.
+	AnimWaitCallbackPtr				mp_anim_wait_complete_callback;
+	
+	// WALKING_GROUND state variable sometimes set at start of animation wait based on the animation end ground situation
+	// Mth::Vector					m_ground_normal;
+	// CFeeler						m_last_ground_feeler;
+	// bool							m_last_ground_feeler_valid;
+	
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CRiderComponent::set_state ( EStateType state )
+{
+    if (state == m_state) return;
+	
+	m_previous_state = m_state;
+    
+    if (m_state == WALKING_GROUND)
+    {
+        m_wall_push_test.active = false;
+		m_run_toggle = false;
+    }
+	
+	m_state = state;
+	m_state_timestamp = Tmr::GetTime();
+	
+	if (m_state == WALKING_ANIMWAIT)
+	{
+		m_offset_due_to_movable_contact.Set();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float CRiderComponent::s_get_param ( uint32 checksum )
+{
+	Script::CStruct* p_walk_params = Script::GetStructure(CRCD(0x6775e538, "walk_parameters"));
+	Dbg_Assert(p_walk_params);
+	
+	float param;
+	p_walk_params->GetFloat(checksum, ¶m, Script::ASSERT);
+	return param;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CRiderComponent::forced_run (   )
+{
+	return false;
+	if (!mp_input_component->GetControlPad().m_x.GetPressed()) return false;
+	
+	uint32 pressed_time = mp_input_component->GetControlPad().m_x.GetPressedTime();
+	return pressed_time > m_state_timestamp || pressed_time > s_get_param(CRCD(0xa7f5ba13, "forced_run_delay"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CRiderComponent::should_bail_from_frame (   )
+{
+	// for now, all restarts switch us to skating; so, only bail from frame if we're ever switch out of walking via script
+//	return !mp_physics_control_component->IsWalking();
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CRiderComponent::IsRunning (   )
+{
+	return m_run_toggle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CRiderComponent::SetForwardControlLock ( bool forward_control_lock )
+{
+	m_forward_control_lock = forward_control_lock;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector& CRiderComponent::GetEffectivePos (   )
+{
+    if (m_state != WALKING_ANIMWAIT)
+    {
+        return GetObject()->GetPos();
+    }
+    else
+    {
+        return GetObject()->GetPos();
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CRiderComponent::UseDPadCamera (   )
+{
+	
+	return mp_input_component->GetControlPad().m_leftX == 0.0f && mp_input_component->GetControlPad().m_leftY == 0.0f && m_control_pegged;
+}
+
+}
+
+#endif
diff --git a/Code/Gel/Components/SetDisplayMatrixComponent.cpp b/Code/Gel/Components/SetDisplayMatrixComponent.cpp
new file mode 100644
index 0000000..22a9082
--- /dev/null
+++ b/Code/Gel/Components/SetDisplayMatrixComponent.cpp
@@ -0,0 +1,104 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SetDisplayMatrixComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  8/6/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSetDisplayMatrixComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSetDisplayMatrixComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSetDisplayMatrixComponent::CSetDisplayMatrixComponent() : CBaseComponent()
+{
+	SetType( CRC_SETDISPLAYMATRIX );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSetDisplayMatrixComponent::~CSetDisplayMatrixComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSetDisplayMatrixComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSetDisplayMatrixComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSetDisplayMatrixComponent::Update()
+{
+	GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSetDisplayMatrixComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSetDisplayMatrixComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSetDisplayMatrixComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+	
+}
diff --git a/Code/Gel/Components/SetDisplayMatrixComponent.h b/Code/Gel/Components/SetDisplayMatrixComponent.h
new file mode 100644
index 0000000..3d89daa
--- /dev/null
+++ b/Code/Gel/Components/SetDisplayMatrixComponent.h
@@ -0,0 +1,49 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SetDisplayMatrixComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  8/6/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SETDISPLAYMATRIXCOMPONENT_H__
+#define __COMPONENTS_SETDISPLAYMATRIXCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_SETDISPLAYMATRIX CRCD(0x1bbf844d,"SetDisplayMatrix")
+
+#define		GetSetDisplayMatrixComponent() ((Obj::CSetDisplayMatrixComponent*)GetComponent(CRC_SETDISPLAYMATRIX))
+#define		GetSetDisplayMatrixComponentFromObject(pObj) ((Obj::CSetDisplayMatrixComponent*)(pObj)->GetComponent(CRC_SETDISPLAYMATRIX))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSetDisplayMatrixComponent : public CBaseComponent
+{
+public:
+    CSetDisplayMatrixComponent();
+    virtual ~CSetDisplayMatrixComponent();
+
+public:
+	virtual void					InitFromStructure( Script::CStruct* pParams );
+	virtual void					RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Update();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/SkaterCameraComponent.cpp b/Code/Gel/Components/SkaterCameraComponent.cpp
new file mode 100644
index 0000000..17dc046
--- /dev/null
+++ b/Code/Gel/Components/SkaterCameraComponent.cpp
@@ -0,0 +1,1544 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkaterCameraComponent.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/21/03
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+namespace Obj
+{
+
+const float SKATERCAMERACOMPONENT_LEAN_TO_SKATERCAM_LEAN	= (( Mth::PI / 6.0f ) / -4096.0f );
+	
+const float	SKATERCAMERACOMPONENT_INTERPOLATOR_TIMESTEP		= ( 1.0f / 60.0f );
+
+// This is the value that mAbove should tend to when the behind value is closer than mBehind.
+
+const float VERT_AIR_LANDED_TIME	= 10.0f / 60.0f;
+const float TILT_MAX				= 0.34907f;						// 20 degees.
+const float TILT_MIN				= -0.34907f;					// -20 degees.
+const float TILT_INCREMENT			= 2.0f * ( TILT_MAX / 60.0f );	// 40 degrees/second.
+const float TILT_DECREMENT			= 2.0f * ( TILT_MAX / 60.0f );	// 40 degrees/second.
+const float TILT_RESTORE			= 8.0f * ( TILT_MAX / 60.0f );	// 160 degrees/second.
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void _xrotlocal ( Mth::Matrix& m, float a )
+{
+	m.RotateXLocal( a );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent* CSkaterCameraComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterCameraComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSkaterCameraComponent::CSkaterCameraComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERCAMERA );
+	
+	mMode = SKATERCAM_MODE_UNDEFINED;
+				  
+	// Create the SLERP interpolator here.
+	mpSlerp				= new Mth::SlerpInterpolator;
+
+	// Set current zoom and LERP rates.
+	mCurrentZoom		= 1.0f;
+	mZoomLerp			= 0.0625f;
+	mLastDot			= 1.0f;
+
+	mLerpXZ				= 0.25f;
+	mLerpY				= 0.5f;
+	mVertAirLerpXZ		= 1.0f;
+	mVertAirLerpY		= 1.0f;
+	mGrindLerp			= 0.1f;
+	
+	mpSkater= NULL;
+	mpSkaterStateComponent = NULL;
+	mpSkaterPhysicsControlComponent = NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSkaterCameraComponent::~CSkaterCameraComponent()
+{
+	delete mpSlerp;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::Finalize (   )
+{
+	mp_lookaround_component = GetCameraLookAroundComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_lookaround_component);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::Enable( bool enable )
+{
+	CCameraComponent *p_cam_comp = GetCameraComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_cam_comp, ( "SkaterCameraComponent requires CameraComponent attached to parent" ));
+	p_cam_comp->Enable( enable );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Gfx::Camera* CSkaterCameraComponent::GetCamera( void )
+{
+	CCameraComponent *p_cam_comp = GetCameraComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_cam_comp, ( "SkaterCameraComponent requires CameraComponent attached to parent" ));
+	return p_cam_comp->GetCamera();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector& CSkaterCameraComponent::GetPosition( void ) const 
+{
+	CCameraComponent *p_cam_comp = GetCameraComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_cam_comp, ( "SkaterCameraComponent requires CameraComponent attached to parent" ));
+	return p_cam_comp->GetPosition();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::SetPosition( Mth::Vector& pos )
+{
+	CCameraComponent *p_cam_comp = GetCameraComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_cam_comp, ( "SkaterCameraComponent requires CameraComponent attached to parent" ));
+	p_cam_comp->SetPosition( pos );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Matrix& CSkaterCameraComponent::GetMatrix( void ) const
+{
+	CCameraComponent *p_cam_comp = GetCameraComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_cam_comp, ( "SkaterCameraComponent requires CameraComponent attached to parent" ));
+	return p_cam_comp->GetMatrix();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::InitFromStructure( Script::CStruct* pParams )
+{
+
+// NOT We need a CCameraComponent attached in order to get camera details.
+//	CCameraComponent *p_cam_comp	= GetCameraComponentFromObject( GetObject());
+//	Dbg_MsgAssert( p_cam_comp, ( "CSkaterCameraComponent needs CCameraComponent attached to parent" ));
+
+	// Clear the frame matrix, so that future slerps make sense.
+	//Mth::Matrix& frame_matrix		= p_cam_comp->GetMatrix();
+	Mth::Matrix& frame_matrix		= GetObject()->GetMatrix();
+	frame_matrix.Ident();
+
+	mLastActualRight 				= frame_matrix[X];
+	mLastActualUp 					= frame_matrix[Y];
+	mLastActualAt 					= frame_matrix[Z];
+
+
+	// The camera requires a "CameraTarget" checksum parameter
+	uint32	target_id = 0 ;
+	pParams->GetChecksum("CameraTarget", &target_id, true);
+
+	Dbg_MsgAssert(target_id >=0 && target_id <= 15,("Bad CameraTarget 0x%x in SkaterCameraComponent",target_id));
+
+	CSkater* p_skater = static_cast(CCompositeObjectManager::Instance()->GetObjectByID(target_id)); 
+	Dbg_MsgAssert(p_skater,("Can't find skater %d in SkaterCameraComponent",target_id));
+
+		
+	SetSkater(p_skater);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Reinitialise.
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::Commit( void )
+{
+	// Under the normal operation of components, the CSkaterCameraComponent updates the values
+	// in CCameraComponent during its Update() function. The CCameraComponent in turn will set
+	// the Gfx::CCamera values during it's Update() function.
+	// In some cases however, we need the camera changes to be committed to the Gfx::Camera
+	// immediately, usually when logic is paused, and the component Update()'s are not taking place.
+	CCameraComponent *p_cam_component = GetCameraComponentFromObject( GetObject());
+	if( p_cam_component )
+	{
+		p_cam_component->Update();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCameraComponent::ReadyForActivation ( const SCameraState& state )
+{
+	Dbg_MsgAssert(mpSkater, ("Skater camera (%s) has NULL target", Script::FindChecksumName(GetObject()->GetID())));
+	
+	mLastActualRight = state.lastActualMatrix[X];
+	mLastActualUp = state.lastActualMatrix[Y];
+	mLastActualAt = state.lastActualMatrix[Z];
+	mLastTripodPos = state.lastTripodPos;
+	mLastDot = state.lastDot;
+	mCurrentZoom = state.lastZoom;
+	mVertAirLandedTimer = 0.0f;
+	mInstantCount = 0;
+	
+	mp_lookaround_component->mLookaroundHeading = 0.0f;
+	mp_lookaround_component->mLookaroundLock = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCameraComponent::Update( void )
+{
+	if( mpSkater == NULL )
+	{
+		return;
+	}
+	
+	// optimization KLUDGE
+	if (mpSkaterPhysicsControlComponent && mpSkaterPhysicsControlComponent->IsDriving())
+	{
+		GetObject()->Pause(true);
+		return;
+	}
+	
+	// We need a CCameraComponent attached in order to get camera details.
+	CCameraComponent *p_cam_comp = GetCameraComponentFromObject( GetObject());
+	Dbg_MsgAssert( p_cam_comp, ( "CSkaterCameraComponent needs CCameraComponent attached to parent" ));
+
+	// This used to be passed in as a param for the old CSkaterCam::Update().
+	// Now we get it from a flag in the skater
+	bool instantly = false;
+	
+	Dbg_Assert( mpSkater );
+	if (mpSkater->HasTeleported())
+	{
+		instantly = true;
+	}
+
+	if( instantly )
+	{
+		mInstantCount = 3;
+	}
+
+	if( mInstantCount > 0 )
+	{
+		--mInstantCount;
+		instantly = true;
+	}
+
+		
+//	if (instantly)
+//	{
+//		printf ("%d: Instantly!\n",(int)Tmr::GetRenderFrame());
+//	}
+		
+	// Length of last frame in seconds.
+	float delta_time = Tmr::FrameLength();
+
+	// Update any values we are currently interpolating.
+	UpdateInterpolators();
+
+	// Would have used a SetPos() function, but the camera update in rwviewer.cpp is spread out all over the place,
+	// so it's not easy to do. We'll just store the old position before updating anything...
+//	Gfx::Camera::StoreOldPos();
+	p_cam_comp->StoreOldPosition();
+
+//	Mth::Matrix& frame_matrix	= Gfx::Camera::GetMatrix();
+	Mth::Matrix& frame_matrix	= p_cam_comp->GetMatrix();
+
+	frame_matrix[X]   		= mLastActualRight;
+	frame_matrix[Y]			= mLastActualUp;
+	frame_matrix[Z]			= mLastActualAt;
+
+	Mth::Vector	current_at_vector( frame_matrix[Z][X], frame_matrix[Z][Y], frame_matrix[Z][Z] ); 
+
+	// Obtain skater state.
+	EStateType	state			= mpSkaterStateComponent->GetState();
+	Mth::Matrix target;
+
+	if( state == WALL )
+	{
+#		ifdef	DEBUG_CAMERA
+		printf ("SkaterCam Using mLastPreWallSkaterMatrix, as wallriding\n");
+#		endif
+		target						= mLastPreWallSkaterMatrix;
+	}
+	else
+	{
+#		ifdef	DEBUG_CAMERA
+		printf ("SkaterCam Using GetCameraDisplayMatrix\n");
+#		endif
+		// target						= static_cast< CCompositeObject* >(mpSkater)->GetDisplayMatrix();
+		if (mpSkater->IsLocalClient())
+		{
+			target						= mpSkater->GetDisplayMatrix();
+		}
+		else
+		{
+			target						= static_cast< CCompositeObject* >(mpSkater)->GetDisplayMatrix();
+		}
+		mLastPreWallSkaterMatrix	= target;
+	}
+
+	// Lerp towards the required lean.
+	float cam_lean = 0.0f;
+	if( state == RAIL && mpSkater->IsLocalClient())
+	{		
+		// Need to translate the lean value [-4096, 4096] into a reasonable camera lean angle.
+		cam_lean = GetSkaterBalanceTrickComponentFromObject(mpSkater)->mGrind.mManualLean * SKATERCAMERACOMPONENT_LEAN_TO_SKATERCAM_LEAN;
+	}
+
+	if( cam_lean != mLean )
+	{
+		if( instantly )
+		{
+			mLean = cam_lean;
+		}
+		else
+		{
+			mLean += (( cam_lean - mLean ) * mGrindLerp );
+		}
+	}
+		
+	Mth::Vector skater_up;
+					
+	if (state != GROUND)
+	{
+		skater_up	= target[Y];
+	}
+	else
+	{
+		skater_up	= mpSkaterStateComponent->GetCameraDisplayNormal();
+	}
+
+//	Gfx::AddDebugLine( p_skater->m_pos, ( target[X] * 72.0f ) + p_skater->m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 4 );
+//	Gfx::AddDebugLine( p_skater->m_pos, ( target[Y] * 72.0f ) + p_skater->m_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 4 );
+//	Gfx::AddDebugLine( p_skater->m_pos, ( target[Z] * 72.0f ) + p_skater->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+		
+	// Use vel for forward facing, if moving in xz plane.
+	Mth::Vector vel_xz( mpSkater->GetVel().GetX(), 0.0f, mpSkater->GetVel().GetZ());
+
+	bool use_vert_cam = UseVertCam();
+	
+	// use velocity if physics not paused, and if we are moving fast enough, or if we are doing vert cam
+	// or if we are doing spine physics
+	if(
+		!(mpSkaterPhysicsControlComponent && mpSkaterPhysicsControlComponent->IsPhysicsSuspended()) && (vel_xz.Length() > 0.1f || use_vert_cam  || mpSkaterStateComponent->GetFlag( SPINE_PHYSICS ))
+	   )
+	{
+		if( use_vert_cam )
+		{
+			// If it's vert, then set target direction to near straight down.
+			target[Z].Set( 0.0f, -1.0f, 0.0f );
+
+			// Zero lookaround when in vert air (and not in lookaround locked or override mode, or doing a lip trick).
+			if( !mp_lookaround_component->mLookaroundLock && !mp_lookaround_component->mLookaroundOverride && ( state != LIP ))
+			{
+				mp_lookaround_component->mLookaroundTilt		= 0.0f;
+				mp_lookaround_component->mLookaroundHeading	= 0.0f;
+			}
+		}
+		else
+		{
+			target[Z]		= mpSkater->GetVel();
+			
+#			if 1	// for use with non-straight spine transfer physics
+			if( mpSkaterStateComponent->GetFlag( SPINE_PHYSICS ))
+			{
+				// Just use the up velocity, plus the velocity over the spine will be straight down when coming down the other side.
+				target[Z][X] = 0.0f;
+				target[Z][Z] = 0.0f;
+				target[Z] += mpSkaterStateComponent->GetSpineVel();
+			}
+#			endif
+
+			target[Z].Normalize();
+
+//			printf ("Using Velocity (%f, %f, %f)\n", target[Z][X],target[Z][Y], target[Z][Z]);
+
+//			Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 128, 128, 128 ), MAKE_RGB( 128, 128, 128 ), 4 );
+
+			// Flatten out the velocity component perpendicular to the ground. However, if the skater is in the air, the
+			// m_last_normal will contain the last ground normal, which we don't want, so set it to be 'up'.
+			float			dot;
+			Mth::Vector		normal;
+
+			if( state == AIR )
+			{
+				// Patch for spine physics
+				// only reduce component perpendicular to plane by 60%
+				// so we still get some up/down camera movmeent 
+				// only do this when moving down though, otherwise it looks poo
+				if (mpSkaterStateComponent->GetFlag(SPINE_PHYSICS) && target[Z][Y] < 0.0f)
+				{
+					normal.Set( 0.0f, 1.0f, 0.0f );
+					dot =  0.7f * Mth::DotProduct(target[Z],normal);	// change this to 0.8 to look down when coming down other side of a spine
+				}
+				else
+				{
+					normal.Set( 0.0f, 1.0f, 0.0f );
+					dot		= target[Z].GetY();
+				}
+				// Set world up as the target vector.
+				target[Y].Set( 0.0f, 1.0f, 0.0f );
+			}
+			else if( state == WALL )
+			{
+				normal.Set( 0.0f, 1.0f, 0.0f );
+				dot		= target[Z].GetY();
+			}
+			else
+			{
+				normal	= mpSkaterStateComponent->GetCameraCurrentNormal();
+				dot =  0.8f * Mth::DotProduct(target[Z],normal);
+			}
+			
+			target[Z] -= normal * dot;
+			target[Z].Normalize();
+			
+//			Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 256, 256, 256 ), MAKE_RGB( 256, 256, 256 ), 4 );
+		}
+
+		// We need to orthonormalize in a specific order, as when the velocity is going backwards, then 
+		// the X orient will be wrong, and so plonk the skater upside down.
+		target[X]			= Mth::CrossProduct( target[Y], target[Z] );
+		target[X].Normalize();
+		
+		target[Y]			= Mth::CrossProduct( target[Z], target[X] );
+		target[Y].Normalize();
+
+//		Gfx::AddDebugLine( mpSkater->m_pos, ( target[X] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 4 );
+//		Gfx::AddDebugLine( mpSkater->m_pos, ( target[Y] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 4 );
+//		Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+	}
+	
+	if (mpSkater->IsLocalClient() && mpSkaterStateComponent->GetFlag(IN_ACID_DROP))
+	{
+		CSkaterCorePhysicsComponent* pSkaterPhysicsComponent = GetSkaterCorePhysicsComponentFromObject(mpSkater);
+		Dbg_Assert(pSkaterPhysicsComponent);
+		
+		target = pSkaterPhysicsComponent->m_acid_drop_camera_matrix;
+	}
+							
+	// Tilt further if in regular air (going off a big jump, for example), or doing a lip trick.
+	if( state == AIR )
+	{
+		mTiltAddition += TILT_INCREMENT * Tmr::FrameRatio();
+		if( mTiltAddition > TILT_MAX )
+		{
+			mTiltAddition = TILT_MAX;
+		}				
+	}
+	else if( mTiltAddition > 0.0f )
+	{
+		mTiltAddition -= TILT_RESTORE * Tmr::FrameRatio();
+		if( mTiltAddition < 0.0f )
+		{
+			mTiltAddition = 0.0f;
+		}				
+	}
+	else if( mTiltAddition < 0.0f )
+	{
+		mTiltAddition += TILT_RESTORE * Tmr::FrameRatio();
+		if( mTiltAddition > 0.0f )
+		{
+			mTiltAddition = 0.0f;
+		}				
+	}
+
+	// PJR: Needed to do this to fix a compiler crash on NGC. Hopefully SN will
+	// fix this & I can revert this hack...
+	target.RotateYLocal( mp_lookaround_component->mLookaroundHeading + mp_lookaround_component->mLookaroundHeadingStartingPoint );
+
+	// Now tilt the matrix down a bit.
+	if( state == LIP )
+	{
+//		target.RotateYLocal( 0.8f );
+//		target.RotateXLocal( mLipTrickTilt + mTiltAddition );
+		_xrotlocal ( target, mLipTrickTilt + mTiltAddition );
+	}					
+	else if( !(mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT )  && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS) ) /*|| !mpSkaterStateComponent->GetFlag(IN_ACID_DROP)*/)
+	{
+//		target.RotateXLocal( mTilt + mTiltAddition );
+		_xrotlocal ( target, mTilt + mTiltAddition );
+	}
+	
+	// Now adjust for 'lookaround'.
+//	target.RotateXLocal( mLookaroundTilt );
+	_xrotlocal( target, mp_lookaround_component->mLookaroundTilt );
+	
+	static float lip_rotate = 0.0f;
+
+	if( state == LIP )
+	{
+		lip_rotate = Mth::PI / 2.0f;
+
+		if( mLipSideDecided == 0 )
+		{
+			target.RotateY( lip_rotate );
+
+			// Do collision check.
+			CFeeler feeler;
+
+			Mth::Vector horiz_z( target[Z][X], 0.0f, target[Z][Z] );
+			horiz_z.Normalize();
+			Mth::Vector a = mpSkater->m_pos - ( target[Z] * 3.0f );
+			Mth::Vector b = a - ( horiz_z * 72.0f );
+
+			feeler.SetIgnore(IGNORE_FACE_FLAGS_1, IGNORE_FACE_FLAGS_0);
+			bool collision = feeler.GetCollision(a, b, true);
+			if( !collision )
+			{
+				// Side 1 is fine.
+				mLipSideDecided = 1;
+			}
+			else
+			{
+				float dist = feeler.GetDist();
+				target.RotateY( -lip_rotate * 2.0f );
+				horiz_z.Set( target[Z][X], 0.0f, target[Z][Z] );
+				horiz_z.Normalize();
+				a = mpSkater->m_pos - ( target[Z] * 3.0f );
+				b = a - ( horiz_z * 72.0f );
+
+				collision = feeler.GetCollision(a, b, true);
+				if( !collision )
+				{
+					// Side 2 is fine.
+					mLipSideDecided = 2;
+				}
+				else
+				{
+					if( feeler.GetDist() < dist )
+					{
+						// Side 2 is better than side 1.
+						mLipSideDecided = 2;
+					}
+					else
+					{
+						// Side 1 is better than side 2.
+						target.RotateY( lip_rotate * 2.0f );
+						mLipSideDecided = 1;
+					}
+				}
+			}
+		}
+		else
+		{
+			if( mLipSideDecided == 1 )
+			{
+				target.RotateY( lip_rotate );
+			}
+			else
+			{
+				target.RotateY( -lip_rotate );
+			}
+		}
+	}
+	else
+	{
+		mLipSideDecided = 0;
+	}					
+
+	// Set skater flag to indicate when the liptrick camera has spun to the opposite side.
+	if( mpSkater && ( mLipSideDecided == 2 ))
+	{
+		mpSkater->mScriptFlags |= ( 1 << Script::GetInteger( 0x16b8e4cb /* "FLAG_SKATER_LIPTRICK_CAM_REVERSED" */ ));
+	}
+	else
+	{
+		mpSkater->mScriptFlags &= ~( 1 << Script::GetInteger( 0x16b8e4cb /* "FLAG_SKATER_LIPTRICK_CAM_REVERSED" */ ));
+	}
+	
+//	Gfx::AddDebugLine( mpSkater->m_pos, ( target[X] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 4 );
+//	Gfx::AddDebugLine( mpSkater->m_pos, ( target[Y] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 4 );
+//	Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+	
+	// Why doing this, when setting below?
+	Mth::Matrix t_matrix(0.0f, 0.0f, 0.0f);
+	
+	// Since camera points in -Z, but player in +Z, we must negate the X and Z axes
+	t_matrix[X][X]		= -target[0][0];
+	t_matrix[X][Y]		= -target[0][1];
+	t_matrix[X][Z]		= -target[0][2];
+	t_matrix[X][W]		= 0.0f;
+	
+	t_matrix[Y][X]		= target[1][0];
+	t_matrix[Y][Y]		= target[1][1];
+	t_matrix[Y][Z]		= target[1][2];
+	t_matrix[Y][W]		= 0.0f;
+	
+	t_matrix[Z][X]		= -target[2][0];
+	t_matrix[Z][Y]		= -target[2][1];
+	t_matrix[Z][Z]		= -target[2][2];
+	t_matrix[Z][W]		= 0.0f;
+			
+	t_matrix[W][X]		= 0.0f;
+	t_matrix[W][Y]		= 0.0f;
+	t_matrix[W][Z]		= 0.0f;
+	t_matrix[W][W]		= 1.0f;
+        
+	frame_matrix[W][X]	= 0.0f;
+	frame_matrix[W][Y]	= 0.0f;
+	frame_matrix[W][Z]	= 0.0f;
+	frame_matrix[W][W]	= 1.0f;
+
+	// if we want an instant update, then just move it
+	if (instantly)
+	{
+		frame_matrix = t_matrix;
+	}
+
+	float dotx = Mth::DotProduct( t_matrix[X], frame_matrix[X] );
+	float doty = Mth::DotProduct( t_matrix[Y], frame_matrix[Y] );
+	float dotz = Mth::DotProduct( t_matrix[Z], frame_matrix[Z] );
+
+	if( dotx > CAMERA_SLERP_STOP && doty > CAMERA_SLERP_STOP && dotz > CAMERA_SLERP_STOP )
+	{
+		// Do nothing, camera has reached the target so we just leave the frame matrix exactly where it is.
+//	    frame_matrix = t_matrix;
+	}
+	else
+	{
+		// Initialise the SLERP interpolator, with the current matrix as the start, and the ideal
+		// matrix as the end (target).
+
+		// GARY:  is this right?  i assume p_matrix = &t_matrix
+		mpSlerp->setMatrices( &frame_matrix, &t_matrix );
+
+		// Copy flags and shit (why copying all this when most of the values will be overwritten?)
+		Mth::Matrix stored_frame;
+		stored_frame = frame_matrix;
+		frame_matrix = t_matrix;			
+
+		// Continue to slerp towards the target, updating frame_matrix with the slerped values.
+		if( !(mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS)))
+		{
+			// If the skater has just landed from big air, use the big air land slerp value.
+			if(( mpSkaterStateComponent->GetState() == GROUND ) && ( mVertAirLandedTimer >= delta_time ))
+			{
+				mVertAirLandedTimer -= delta_time;
+				mpSlerp->getMatrix( &frame_matrix, GetTimeAdjustedSlerp( mVertAirLandedSlerp, delta_time ));
+			}
+			else
+			{
+				// Clear the vert air landed timer.
+				mVertAirLandedTimer = 0.0f;
+				
+				mpSlerp->getMatrix( &frame_matrix, GetTimeAdjustedSlerp( mSlerp, delta_time ));
+
+				// At this point we can check to see the angle that the camera's 'at' vector moved through this frame.
+				// If this angle is too large, we need to limit it, regardless of the slerp. (This is primarily to avoid
+				// the nasty jerk when transitioning rails etc.
+				float this_dot = ( current_at_vector.GetX() * frame_matrix[Mth::AT][X] ) 
+                       + ( current_at_vector.GetY() * frame_matrix[Mth::AT][Y] ) 
+                       + ( current_at_vector.GetZ() * frame_matrix[Mth::AT][Z] ); 
+
+				// Dot values will be in the range [0,1], with 1 being no change.
+				if( this_dot < ( mLastDot * 0.9998f ))
+				{
+					// The angular change this frame is too big. Need to recalculate with an adjusted slerp value.
+					float new_slerp = mSlerp * ( this_dot / ( mLastDot * 0.9998f ));
+					mpSlerp->setMatrices( &stored_frame, &t_matrix );
+					mpSlerp->getMatrix( &frame_matrix, GetTimeAdjustedSlerp( new_slerp, delta_time ));
+
+					this_dot = mLastDot * 0.9998f;
+				}
+				mLastDot = this_dot;
+			}
+		}
+		else
+		{
+			mpSlerp->getMatrix( &frame_matrix, GetTimeAdjustedSlerp( mVertAirSlerp, delta_time ));
+
+			// Set the vert air landed timer to the max, since the skater is in vert air.
+			mVertAirLandedTimer = VERT_AIR_LANDED_TIME;
+		}						
+	}		
+
+	// Mick:  This is a patch to allow the walk camera to override the updating of the camera frame	
+	// At this point, frame_matrix is valid to store.
+	mLastActualRight	= frame_matrix[X];
+	mLastActualUp		= frame_matrix[Y];
+	mLastActualAt		= frame_matrix[Z];
+	
+	// Set camera position to be the same as the skater.
+	Mth::Vector	cam_pos = GetTripodPos( instantly );
+	
+	// If in the air doing a trick, we slowly reduce the behind value to 'zoom' in on the skater.
+	float above, behind;
+	if( instantly )
+	{
+		float temp = mZoomLerp;
+		mZoomLerp = 1.0f;					// fully lerp instantly
+		CalculateZoom( &above, &behind );
+		mZoomLerp = temp;
+	}
+	else
+	{
+		CalculateZoom( &above, &behind );
+	}
+	
+	// Note we use the camera's at vector, as this will keep the skater in the middle of the frame.
+	//cam_pos -= frame_matrix[Z] * behind;
+	cam_pos += frame_matrix[Z] * behind;
+		
+	// Move camera along the Skater's up vector.
+	cam_pos += skater_up * above;
+	
+	Mth::Vector	actual_focus_pos = mpSkater->m_pos + ( skater_up * above );
+	
+//	Gfx::AddDebugLine( mpSkater->m_pos, actual_focus_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+													  
+//	Dbg_Message("Matrix Z  (%5.4f,%5.4f,%5.4f)", frame_matrix[Z][X], frame_matrix[Z][Y], frame_matrix[Z][Z]);
+//	Dbg_Message("Matrix UP (%5.4f,%5.4f,%5.4f)", skater_up[X], skater_up[Y], skater_up[Z]);
+//	Dbg_Message("Above %f Behind %f", above, behind);
+//	Dbg_Message("focus pos (%5.1f,%5.1f,%5.1f)", actual_focus_pos[X], actual_focus_pos[Y], actual_focus_pos[Z]);
+//	Dbg_Message("Camera after: pos (%5.1f,%5.1f,%5.1f)", cam_pos[X], cam_pos[Y], cam_pos[Z]);
+
+	// Reorient the camera to look directly at the skater. We don't want to look at the interpolated position,
+	// since this can lead to nasty jerkiness, especially on rails etc.
+	target[Z].Set( actual_focus_pos.GetX() - cam_pos.GetX(), actual_focus_pos.GetY() - cam_pos.GetY(), actual_focus_pos.GetZ() - cam_pos.GetZ());
+	target[Z].Normalize();
+
+	// Read back the Y from the current matrix.
+	target[Y][0]	= frame_matrix[Y][X];
+	target[Y][1]	= frame_matrix[Y][Y];
+	target[Y][2]	= frame_matrix[Y][Z];
+
+	// Generate new orthonormal X and Y axes.
+	target[X]		= Mth::CrossProduct( target[Y], target[Z] );
+	target[X].Normalize();
+	target[Y]		= Mth::CrossProduct( target[Z], target[X] );
+	target[Y].Normalize();
+
+	// Here is where lean may safely be applied without moving the focus position on screen.
+	if( mLean != 0.0f )
+	{
+		target[Y].Rotate( target[Z], mLean );
+		target[X].Rotate( target[Z], mLean );
+	}
+	
+	// Write back into camera matrix.
+	// Since camera points in -Z, but player in +Z, we must negate the X and Z axes
+	frame_matrix[X][X]		= -target[0][0];
+	frame_matrix[X][Y]		= -target[0][1];
+	frame_matrix[X][Z]		= -target[0][2];
+	frame_matrix[X][W]		= 0.0f;
+
+	frame_matrix[Y][X]		= target[1][0];
+	frame_matrix[Y][Y]		= target[1][1];
+	frame_matrix[Y][Z]		= target[1][2];
+	frame_matrix[Y][W]		= 0.0f;
+
+	frame_matrix[Z][X]		= -target[2][0];
+	frame_matrix[Z][Y]		= -target[2][1];
+	frame_matrix[Z][Z]		= -target[2][2];
+	frame_matrix[Z][W]		= 0.0f;
+
+	// Update the position if there is a shake active.
+	if(	mShakeDuration > 0.0f )
+	{
+		addShake( cam_pos, frame_matrix );
+	}
+		
+	// Now do collision detection.
+	
+	bool no_camera_collision;
+	if (mpSkater->IsLocalClient())
+	{
+		CSkaterCorePhysicsComponent* pSkaterPhysicsComponent = GetSkaterCorePhysicsComponentFromObject(mpSkater);
+		Dbg_Assert(pSkaterPhysicsComponent);
+		
+		no_camera_collision = pSkaterPhysicsComponent->mp_movable_contact_component->GetContact() && state == LIP;
+	}
+	else
+	{
+		no_camera_collision = false;
+	}
+	
+	if (no_camera_collision)
+	{
+		// no camera collision if doing a lip trick on a moving object
+		//printf ("Lip trick on moving object\n");
+	}
+	else
+	{
+		ApplyCameraCollisionDetection(cam_pos, frame_matrix, cam_pos - ((Mth::Vector)( frame_matrix[Z] )) * behind, actual_focus_pos);
+	}
+	
+	cam_pos[W] = 1.0f;
+//	Gfx::Camera::SetPos( cam_pos );
+	p_cam_comp->SetPosition( cam_pos );
+	
+	// reset old position if in instant update
+	if (instantly)
+	{
+		p_cam_comp->StoreOldPosition();
+	}
+	
+//	Dbg_Message("Camera Final: pos (%5.1f,%5.1f,%5.1f)", cam_pos[X], cam_pos[Y], cam_pos[Z]);
+
+	// Store the position of the camera in CSkaterCam also (used for proximity nodes).
+
+	// Dave - there is no m_pos anymore...
+//	m_pos = cam_pos;
+
+	// Dave - this used to return the value below when it was CSkaterCam::Update()
+//	Mth::Vector	to_target = at_pos - cam_pos;
+//	return to_target.Length();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterCameraComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | SC_ShakeCamera | shake the skater camera
+        // @parm float | duration | duration of the shake (0 clears the shake)
+        // @parm float | vert_amp | maximum vertical amplidtude of the shake
+        // @parm float | horiz_amp | maximum horizontal amplidtude of the shake
+        // @parm float | vert_vel | vertical velocity factor (how many full cycles of movement in 1 second) 
+        // @parm float | horiz_vel | horizontal velocity factor (how many full cycles of movement in 1 second) 
+		case CRCC(0x75a516d3,"SC_ShakeCamera"):
+		{
+			float duration	= 0.0f;
+			float vert_amp	= 0.0f;
+			float horiz_amp	= 0.0f;
+			float vert_vel	= 0.0f;
+			float horiz_vel	= 0.0f;
+
+			pParams->GetFloat( "duration",	&duration );
+			pParams->GetFloat( "vert_amp",	&vert_amp );
+			pParams->GetFloat( "horiz_amp",	&horiz_amp );
+			pParams->GetFloat( "vert_vel",	&vert_vel );
+			pParams->GetFloat( "horiz_vel",	&horiz_vel );
+
+			SetShake( duration, vert_amp, horiz_amp, vert_vel, horiz_vel );
+			
+			break;
+		}
+		
+				 
+		// @script | SC_SetMode | Set the mode of a skater camera
+        // @parm integer | mode | mode number 1-4, start out at 2 (normal_medium)
+		case CRCC(0xd7867ffe,"SC_SetMode"):
+		{
+		
+			int mode = 0;
+			pParams->GetInteger(CRCD(0x6835b854,"mode"),&mode, true);
+			SetMode((ESkaterCamMode)mode);	   
+			break; 	
+		}
+
+	
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCameraComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterCameraComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger("m_never_suspend",m_never_suspend);
+	p_info->AddFloat("m_suspend_distance",m_suspend_distance);
+	*/
+	
+	// We call the base component's GetDebugInfo, so we can add info from the common base component.
+	CBaseComponent::GetDebugInfo( p_info );
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCameraComponent::ResetLookAround( void )
+{
+	mp_lookaround_component->mLookaroundTilt		= 0.0f;
+	mp_lookaround_component->mLookaroundHeading	= 0.0f;
+	SetNoLerpUpdate( true );
+	Update();
+	Commit();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCameraComponent::ResetMode( )
+{
+	// Just call SetMode() again with the current mode
+	SetMode(mMode);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCameraComponent::SetMode( ESkaterCamMode mode, float time )
+{
+	// We need a CCameraComponent attached in order to set camera details.
+	CCameraComponent *p_cam_comp = GetCameraComponentFromObject(GetObject());
+	Dbg_MsgAssert( p_cam_comp, ( "CSkaterCameraComponent needs CCameraComponent attached to parent" ));
+
+	Dbg_MsgAssert( mode < SKATERCAM_NUM_MODES,( "Bad mode" ));
+
+	// Obtain details for this mode.
+	float h_fov, behind, above, tilt, origin_offset, zoom_lerp;
+	const char* p_name;
+
+	// Obtain a pointer to the array entry for the desired mode. The array is now dependent
+	// on which game mode is currently being played, 1 player, 2 player vert, or 2 player horiz.
+	Nx::ScreenMode screen_mode = Nx::CViewportManager::sGetScreenMode();
+
+	Script::CArray* p_array;
+
+	switch( screen_mode )
+	{
+		case Nx::vSPLIT_V:
+		{
+			p_array = Script::GetArray( "Skater_Camera_2P_Vert_Array" );
+			break;
+		}
+
+		case Nx::vSPLIT_H:
+		{
+			p_array = Script::GetArray( "Skater_Camera_2P_Horiz_Array" );
+			break;
+		}
+
+		default:
+		{			
+			p_array = Script::GetArray( "Skater_Camera_Array" );
+			break;
+		}
+	}
+
+
+	// Extra check here, since the array in physics.q may change.
+	Dbg_MsgAssert( mode < (int)p_array->GetSize(), ( "Bad mode" ));
+
+	uint32 cs = p_array->GetNameChecksum( mode );
+	Script::CScriptStructure* p_struct = Script::GetStructure( cs );
+
+	mMode = mode;
+
+	mLipTrickTilt = 0.0f;
+
+	// Read the values from the array.
+	p_struct->GetFloat( CRCD(0x94df1603,"horiz_fov"),				&h_fov );
+	p_struct->GetFloat( CRCD(0x52e6c41b,"behind"),					&behind );
+	p_struct->GetFloat( CRCD(0xb96ae2d,"above"),					&above );
+	p_struct->GetFloat( CRCD(0xe3c07609,"tilt"),						&tilt );
+	p_struct->GetFloat( CRCD(0x12f6992,"origin_offset"),			&origin_offset );
+	p_struct->GetFloat( CRCD(0x7f0032ac,"lip_trick_tilt"),			&mLipTrickTilt );
+	p_struct->GetFloat( CRCD(0xadb6390e,"lip_trick_above"),			&mLipTrickAbove );
+
+	// Get SLERP values.
+	p_struct->GetFloat( CRCD(0xf54fb9c5,"slerp"),					&mSlerp );
+	p_struct->GetFloat( CRCD(0x2fa11029,"vert_air_slerp"),			&mVertAirSlerp );
+	p_struct->GetFloat( CRCD(0x42fd4a0,"vert_air_landed_slerp"),	&mVertAirLandedSlerp );
+
+	// Get LERP values.
+	p_struct->GetFloat( CRCD(0xae47899b,"lerp_xz"),					&mLerpXZ );
+	p_struct->GetFloat( CRCD(0xe1e4e104,"lerp_y"),					&mLerpY );
+	p_struct->GetFloat( CRCD(0xf386f4d9,"vert_air_lerp_xz"),			&mVertAirLerpXZ );
+	p_struct->GetFloat( CRCD(0x4882a1fe,"vert_air_lerp_y"),			&mVertAirLerpY );
+	p_struct->GetFloat( CRCD(0xbef0d195,"grind_lerp"),				&mGrindLerp );
+
+	// Get zoom values.
+	mGrindZoom			= 1.0f;
+	mLipTrickZoom		= 1.0f;
+	mBigAirTrickZoom	= 1.0f;
+
+	p_struct->GetFloat( CRCD(0x748743a7,"zoom_lerp"),			&zoom_lerp );
+	p_struct->GetFloat( CRCD(0x5a7f5cc5,"grind_zoom"),			&mGrindZoom );
+	p_struct->GetFloat( CRCD(0xd790b83d,"big_air_trick_zoom"),	&mBigAirTrickZoom );
+	p_struct->GetFloat( CRCD(0xd414c22e,"lip_trick_zoom"),		&mLipTrickZoom );
+	mZoomLerp			= zoom_lerp;
+
+	// Get camera style name.
+	p_struct->GetText( CRCD(0xa1dc81f9,"name"), &p_name );
+
+	// Send the new camera mode to console line 1.
+	//HUD::PanelMgr* panelMgr = HUD::PanelMgr::Instance();
+	//panelMgr->SendConsoleMessage( p_name, 1 );
+
+	// Need to calculate interpolate timers here.
+	mHorizFOV		= h_fov;
+	mTilt			= tilt;
+	mOriginOffset	= FEET( origin_offset );		// Convert to inches.
+
+	// Set up interpolators for those values that require it.
+	if( time > 0.0f )
+	{
+		behind						= FEET( behind );				// Convert to inches.
+		above						= FEET( above );				// Convert to inches.
+		mBehindInterpolatorTime		= time;
+		mBehindInterpolatorDelta	= (( behind - mBehind ) * ( SKATERCAMERACOMPONENT_INTERPOLATOR_TIMESTEP	/ time ));
+		mAboveInterpolatorTime		= time;
+		mAboveInterpolatorDelta		= (( above - mAbove ) * ( SKATERCAMERACOMPONENT_INTERPOLATOR_TIMESTEP / time ));
+	}
+	else
+	{
+		mBehind						= FEET( behind );
+		mAbove						= FEET( above );
+		mBehindInterpolatorTime		= 0.0f;
+		mAboveInterpolatorTime		= 0.0f;
+	}
+	
+	// Set focal length immediately.
+	switch( screen_mode )
+	{
+		case Nx::vSPLIT_V:
+		{
+#			ifdef __PLAT_NGC__
+			p_cam_comp->SetHFOV( Script::GetFloat( "Skater_Cam_Horiz_FOV" ) / 2.0f );
+#			else
+			p_cam_comp->SetHFOV( mHorizFOV / 2.0f );
+#			endif
+			break;
+		}
+
+		case Nx::vSPLIT_H:
+		{
+			p_cam_comp->SetHFOV( mHorizFOV );
+			break;
+		}
+
+		default:
+		{			
+			p_cam_comp->SetHFOV( mHorizFOV );
+			break;
+		}
+	}
+
+	switch( mode )
+	{
+		case ( SKATERCAM_MODE_REPLAY_FRONT ):
+		case ( SKATERCAM_MODE_REPLAY_FRONT_ZOOM ):
+			mp_lookaround_component->mLookaroundHeadingStartingPoint = Mth::PI;
+			break;
+		case ( SKATERCAM_MODE_REPLAY_LEFT ):
+		case ( SKATERCAM_MODE_REPLAY_LEFT_ZOOM ):
+			mp_lookaround_component->mLookaroundHeadingStartingPoint = Mth::PI / 2.0f;
+			break;
+		case ( SKATERCAM_MODE_REPLAY_BEHIND ):
+		case ( SKATERCAM_MODE_REPLAY_BEHIND_ZOOM ):
+			mp_lookaround_component->mLookaroundHeadingStartingPoint = 0;
+			break;
+		case ( SKATERCAM_MODE_REPLAY_RIGHT ):
+		case ( SKATERCAM_MODE_REPLAY_RIGHT_ZOOM ):
+			mp_lookaround_component->mLookaroundHeadingStartingPoint = -( Mth::PI / 2.0f );
+			break;
+		default:
+			mp_lookaround_component->mLookaroundHeadingStartingPoint = 0;
+			break;
+	}   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCameraComponent::ToggleMode()
+{
+	if ( mMode >= SKATERCAM_NUM_NORMAL_MODES )
+	{
+		return;
+	}
+	
+	mMode = (ESkaterCamMode)( mMode + 1);
+	if( mMode >= SKATERCAM_NUM_NORMAL_MODES )
+	{
+		mMode = SKATERCAM_MODE_FIRST_VALID;
+	}
+
+	ResetMode();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::SetShake( float duration, float vert_amp, float horiz_amp, float vert_vel, float horiz_vel )
+{
+	mShakeDuration			= duration;
+	mShakeInitialDuration	= duration;
+	mShakeMaxVertAmp		= vert_amp;
+	mShakeMaxHorizAmp		= horiz_amp;
+	mShakeVertVel			= vert_vel;
+	mShakeHorizVel			= horiz_vel;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CSkaterCameraComponent::UseVertCam( void )
+{   
+	if( mpSkater == NULL )
+	{
+		return false;
+	}
+	
+	// this logic is used only by cameras observing non-local clients; in all other cases, skating camera logic in suspended when viewing a walking
+	// skater; when observing, skater camera logic is always used; this prevents the "birds-eye-view" issue when observing a skater who transitioned to
+	// walking during vert air
+	if( mpSkaterStateComponent->GetPhysicsState() == WALKING )
+	{
+		return false;
+	}
+	
+	// Use vert cam when in the lip state.
+	if( mpSkaterStateComponent->GetState() == LIP )
+	{
+#		ifdef DEBUG_CAMERA
+		printf ("SkaterCam Using Vert Cam as LIP\n");
+#		endif
+		return true;
+	}
+	
+	if( mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT )  && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS))
+	{
+#		ifdef DEBUG_CAMERA
+		printf ("SkaterCam Using Vert Cam as VERT_AIR\n");
+#		endif
+		return true;
+	}
+	
+	if( mpSkaterStateComponent->GetJumpedOutOfLipTrick())
+	{
+#		ifdef DEBUG_CAMERA
+		printf ("SkaterCam Using Vert Cam as Jumped out of lip trick\n");
+#		endif
+		return true;
+	}
+
+#	ifdef DEBUG_CAMERA
+	printf ("Skatecam NOT using vert cam\n");
+#	endif
+
+	return false;	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::addShake( Mth::Vector& cam, Mth::Matrix& frame )
+{
+	float damping		= mShakeDuration / mShakeInitialDuration;
+	float vert_angle	= mShakeDuration * mShakeVertVel * Mth::PI * 2.0f;
+	float horiz_angle	= mShakeDuration * mShakeHorizVel * Mth::PI * 2.0f;
+	float vert_offset	= mShakeMaxVertAmp * sinf( vert_angle ) * damping;
+	float horiz_offset	= mShakeMaxHorizAmp * sinf( horiz_angle ) * damping;
+
+	cam += frame[X] * horiz_offset;
+	cam += frame[Y] * vert_offset;
+
+	mShakeDuration		-= Tmr::FrameLength();
+	if( mShakeDuration < 0.0f )
+		mShakeDuration = 0.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CSkaterCameraComponent::GetTripodPos( bool instantly )
+{
+	float		lerp_xz;
+	float		lerp_y;
+	float		delta		= Tmr::FrameLength();
+
+	if( mpSkater == NULL )
+	{
+		return Mth::Vector( 0.0f, 0.0f, 0.0f );
+	}
+
+	Mth::Vector now			= mpSkater->m_pos;
+
+	// The tripod pos is always *tending* towards the skater position, but the rate at which it does so is
+	// definable, to allow for some lag, which improves the feeling of speed.
+	if( mLerpReductionTimer > 0.0f )
+	{
+		mLerpReductionTimer -= delta;
+		if( mLerpReductionTimer > 0.0f )
+		{
+			mLerpReductionMult += ( mLerpReductionDelta * delta );
+		}
+		else
+		{
+			mLerpReductionMult	= 1.0f;
+			mLerpReductionTimer	= 0.0f;
+		}
+	}
+	else
+	{
+		mLerpReductionMult	= 1.0f;
+	}
+
+	Dbg_Assert( mpSkaterStateComponent );
+	if(( mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag( SPINE_PHYSICS )))
+	{
+		#ifdef	DEBUG_CAMERA
+		printf ("Skatercam using mVertAirLerp\n");
+		#endif
+
+		lerp_xz	= GetTimeAdjustedSlerp( mVertAirLerpXZ * mLerpReductionMult, delta );
+		lerp_y	= GetTimeAdjustedSlerp( mVertAirLerpY * mLerpReductionMult, delta );
+	}
+	else
+	{
+		lerp_xz	= GetTimeAdjustedSlerp( mLerpXZ * mLerpReductionMult, delta );
+		lerp_y	= GetTimeAdjustedSlerp( mLerpY * mLerpReductionMult, delta );
+
+		// Added the following check (Dave 7/16/02) to help eliminate the camera rattle when snapping on/off a curb.
+		// The flag is set for one frame only.
+		if( mpSkaterStateComponent->GetFlag( SNAPPED_OVER_CURB ))
+		{
+			lerp_y = 1.0f;
+		}
+		
+		if( mpSkaterStateComponent->GetFlag( SNAPPED ))
+		{
+			instantly = true;
+		}
+	}
+
+	if( instantly )
+	{
+		mLastTripodPos = now;
+	}
+	else
+	{
+		mLastTripodPos.Set( mLastTripodPos.GetX() + (( now.GetX() - mLastTripodPos.GetX() ) * lerp_xz ),
+							mLastTripodPos.GetY() + (( now.GetY() - mLastTripodPos.GetY() ) * lerp_y ),
+							mLastTripodPos.GetZ() + (( now.GetZ() - mLastTripodPos.GetZ() ) * lerp_xz ));
+	}
+
+	return mLastTripodPos;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::CalculateZoom( float* p_above, float* p_behind )
+{
+	if( mpSkater == NULL )
+	{
+		return;
+	}
+
+	mTargetZoom = 1.0f;
+
+	// Deal with zoom for Big Air. Set the flag only when a trick is started.
+	if( !mBigAirTrickZoomActive && ( mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS)) && mpSkaterStateComponent->DoingTrick() )
+	{
+		mBigAirTrickZoomActive = true;
+	}
+	else if( !( mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag( SPINE_PHYSICS )))
+	{
+		// Must have landed.
+		mBigAirTrickZoomActive = false;
+	}
+
+	if( mBigAirTrickZoomActive )
+	{
+		mTargetZoom = mBigAirTrickZoom;
+	}
+	else
+	{
+		EStateType state = mpSkaterStateComponent->GetState();
+
+		// If the skater is grinding, zoom also.
+		if( state == RAIL )
+		{
+			mTargetZoom = mGrindZoom;
+		}
+		else if( state == LIP )
+		{
+			mTargetZoom = mLipTrickZoom;
+		}
+		else if( state == LIP )
+		{
+			mTargetZoom = mLipTrickZoom;
+		}
+	}
+
+	
+	#if 0
+	// If lookaround override is set, factor in the lookaround override zoom.
+	if( mp_lookaround_component->mLookaroundOverride && ( mp_lookaround_component->mLookaroundZoom != 1.0f ))
+	{
+		mCurrentZoom = mTargetZoom;
+		mCurrentZoom *= mp_lookaround_component->mLookaroundZoom;
+	}
+	else
+	{
+		mCurrentZoom += (( mTargetZoom - mCurrentZoom ) * mZoomLerp );
+	}
+	#else
+	// If lookaround override is set, factor in the lookaround override zoom.
+	if( mp_lookaround_component->mLookaroundOverride && ( mp_lookaround_component->mLookaroundZoom != 1.0f ))
+	{
+		mTargetZoom *= mp_lookaround_component->mLookaroundZoom;
+	}
+	
+	mCurrentZoom += (( mTargetZoom - mCurrentZoom ) * mZoomLerp );
+	#endif
+	
+	*p_behind = mBehind * mCurrentZoom;
+
+	// Behind is also shortened when the lookaround camera is tilting upwards.
+	if( mp_lookaround_component->mLookaroundTilt < 0.0f )
+	{
+		float max_tilt = 3.14f * 0.2f;
+		*p_behind = *p_behind * ( 0.4f + ( 0.6f * (( max_tilt + mp_lookaround_component->mLookaroundTilt ) / max_tilt )));
+	}
+
+	// Use lip_trick_above when doing a lip trick.
+	float above_val = mAbove;
+	if( mpSkaterStateComponent->GetState() == LIP )
+	{
+		above_val = mLipTrickAbove;
+	}
+	
+	// Figure above tending towards the perfect above, if zoom is < 1.0.
+	if( mCurrentZoom < 1.0f )
+	{
+		*p_above = SKATERCAMERACOMPONENT_PERFECT_ABOVE + (( above_val - SKATERCAMERACOMPONENT_PERFECT_ABOVE ) * mCurrentZoom );
+	}
+	else
+	{
+		*p_above = above_val;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::UpdateInterpolators(   )
+{
+	if( mBehindInterpolatorTime > 0.0f )
+	{
+		mBehindInterpolatorTime -= SKATERCAMERACOMPONENT_INTERPOLATOR_TIMESTEP;
+		mBehind += mBehindInterpolatorDelta;		
+	}
+	else
+	{
+		mBehindInterpolatorTime = 0.0f;
+	}
+
+	if( mAboveInterpolatorTime > 0.0f )
+	{
+		mAboveInterpolatorTime -= SKATERCAMERACOMPONENT_INTERPOLATOR_TIMESTEP;
+		mAbove += mAboveInterpolatorDelta;		
+	}
+	else
+	{
+		mAboveInterpolatorTime = 0.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::SetLerpReductionTimer( float time )
+{
+	if( time > 0.0f )
+	{
+		mLerpReductionTimer	= time;
+		mLerpReductionDelta	= 1.0f / time;
+		mLerpReductionMult	= 0.0f;
+	}
+	else
+	{
+		mLerpReductionTimer	= 0.0f;
+		mLerpReductionDelta	= 0.0f;
+		mLerpReductionMult	= 1.0f;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::SetSkater( CSkater* p_skater )
+{
+	mpSkater = p_skater;
+	
+	if( p_skater == NULL )
+	{
+		mpSkaterStateComponent = NULL;
+		mpSkaterPhysicsControlComponent = NULL;
+	}
+	else
+	{
+		mpSkaterStateComponent = GetSkaterStateComponentFromObject(mpSkater);
+		Dbg_Assert(mpSkaterStateComponent);
+		
+		// non-local clients will not have a CSkaterPhysicsControlComponent
+		mpSkaterPhysicsControlComponent = GetSkaterPhysicsControlComponentFromObject(mpSkater);
+		
+		SetNoLerpUpdate(true);
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSkater* CSkaterCameraComponent::GetSkater( void )
+{
+	return mpSkater;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCameraComponent::GetCameraState ( SCameraState& state )
+{
+	state.lastActualMatrix[X] = mLastActualRight;
+	state.lastActualMatrix[Y] = mLastActualUp;
+	state.lastActualMatrix[Z] = mLastActualAt;
+	state.lastActualMatrix[W].Set( 0.0f, 0.0f, 0.0f, 1.0f );
+	state.lastTripodPos = mLastTripodPos;
+	state.lastDot = mLastDot;
+	state.lastZoom = mCurrentZoom;
+}
+	
+}
diff --git a/Code/Gel/Components/SkaterCameraComponent.h b/Code/Gel/Components/SkaterCameraComponent.h
new file mode 100644
index 0000000..5cac069
--- /dev/null
+++ b/Code/Gel/Components/SkaterCameraComponent.h
@@ -0,0 +1,192 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkaterCameraComponent.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/21/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERCAMERACOMPONENT_H__
+#define __COMPONENTS_SKATERCAMERACOMPONENT_H__
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+// Replace this with the CRCD of the component you are adding.
+#define		CRC_SKATERCAMERA							CRCD( 0x5e43a604, "SkaterCamera" )
+
+//  Standard accessor macros for getting the component either from within an object, or given an object.
+#define		GetSkaterCameraComponent()					((Obj::CSkaterCameraComponent*)GetComponent( CRC_SKATERCAMERA ))
+#define		GetSkaterCameraComponentFromObject( pObj )	((Obj::CSkaterCameraComponent*)(pObj)->GetComponent( CRC_SKATERCAMERA ))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CInputComponent;
+	class CSkaterStateComponent;
+	class CSkaterPhysicsControlComponent;
+	class CCameraLookAroundComponent;
+
+class CSkaterCameraComponent : public CBaseComponent
+{
+	friend class CWalkCameraComponent;
+	
+public:
+
+	enum ESkaterCamMode
+	{
+		SKATERCAM_MODE_UNDEFINED		= 0,
+		SKATERCAM_MODE_FIRST_VALID		= 1,
+		SKATERCAM_MODE_NORMAL_NEAR		= 1,
+		SKATERCAM_MODE_NORMAL_MEDIUM,
+		SKATERCAM_MODE_NORMAL_FAR,
+		SKATERCAM_MODE_NORMAL_MEDIUM_LTG,
+        SKATERCAM_NUM_NORMAL_MODES,
+		SKATERCAM_FIRST_REPLAY_MODE = SKATERCAM_NUM_NORMAL_MODES,
+		SKATERCAM_MODE_REPLAY_FRONT = SKATERCAM_NUM_NORMAL_MODES,
+		SKATERCAM_MODE_REPLAY_FRONT_ZOOM,
+        SKATERCAM_MODE_REPLAY_LEFT,
+		SKATERCAM_MODE_REPLAY_LEFT_ZOOM,
+		SKATERCAM_MODE_REPLAY_BEHIND,
+		SKATERCAM_MODE_REPLAY_BEHIND_ZOOM,
+		SKATERCAM_MODE_REPLAY_RIGHT,
+		SKATERCAM_MODE_REPLAY_RIGHT_ZOOM,
+		SKATERCAM_LAST_REPLAY_MODE = SKATERCAM_MODE_REPLAY_RIGHT_ZOOM,
+		SKATERCAM_NUM_MODES,
+		SKATERCAM_MODE_ANIM_PLAYBACK,
+	};
+	
+											CSkaterCameraComponent();
+    virtual									~CSkaterCameraComponent();
+
+public:
+    virtual void            				Update();
+    virtual void            				InitFromStructure( Script::CStruct* pParams );
+    virtual void            				RefreshFromStructure( Script::CStruct* pParams );
+	virtual void							Finalize();
+    
+    virtual EMemberFunctionResult   		CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 							GetDebugInfo( Script::CStruct* p_info );
+
+	Mth::Vector								GetTripodPos( bool instantly = false );
+	void									UpdateInterpolators(   );
+	void									CalculateZoom( float* p_above, float* p_behind );
+	void									addShake( Mth::Vector& cam, Mth::Matrix& frame );
+
+	void									SetNoLerpUpdate( bool instant )	{ if( instant ) mInstantCount = 3; }
+	void									Commit( void );
+
+	void									SetShake( float duration, float vert_amp, float horiz_amp, float vert_vel, float horiz_vel );
+	bool									UseVertCam( void );
+	void									ActivateLerpIntoBehavior ( float duration );
+
+	void									ResetLookAround( void );
+	void									ResetMode( );
+	void									SetMode( ESkaterCamMode mode, float time = 1.0f );
+	void									ToggleMode( );
+	ESkaterCamMode							GetMode( void )								{ return mMode; }
+	void									SetLerpReductionTimer( float time );
+	void									SetSkater( CSkater* p_skater );
+	CSkater*								GetSkater( void );
+	
+	void									ReadyForActivation ( const SCameraState& state );
+	void									GetCameraState ( SCameraState& state );
+
+	static CBaseComponent*					s_create();
+
+	// These functions access the CCameraComponent which is required to be attached.
+	void									Enable( bool enable );
+	Gfx::Camera*							GetCamera( void );
+	const Mth::Vector &						GetPosition( void ) const;
+	void									SetPosition( Mth::Vector& pos );
+	const Mth::Matrix&						GetMatrix( void ) const;
+
+	bool									mSkipButton;
+	bool									mRightButton;
+	int										mRightX;
+	int										mRightY;
+
+private:
+	ESkaterCamMode							mMode;
+
+	int										mInstantCount;				// Used to maintain the 'instantly' flag during update for several frames.
+
+	float									mLastDot;
+	Mth::Vector								mLastTripodPos;
+	Mth::Vector								mLastActualRight;			// Used because the frame is adjusted after focus LERPING.
+	Mth::Vector								mLastActualUp;
+	Mth::Vector								mLastActualAt;
+
+	Mth::Matrix								mLastPreWallSkaterMatrix;	// Used because we don't want to use the skater's matrix during wall rides.
+
+	float									mBehind;
+	float									mBehindInterpolatorTime;
+	float									mBehindInterpolatorDelta;
+	float									mAbove;
+	float									mAboveInterpolatorTime;
+	float									mAboveInterpolatorDelta;
+
+	float									mVertAirLandedTimer;		// Defines for how long the vert air landed slerp gets applied.
+
+	float									mHorizFOV;
+
+	float									mLerpXZ;
+	float									mLerpY;
+	float									mVertAirLerpXZ;
+	float									mVertAirLerpY;
+	float									mGrindLerp;
+
+	float									mLerpReductionTimer;
+	float									mLerpReductionDelta;
+	float									mLerpReductionMult;
+
+	bool									mBigAirTrickZoomActive;
+	float									mCurrentZoom;
+	float									mTargetZoom;
+	float									mZoomLerp;				// LERP rate for zoom.
+	float									mBigAirTrickZoom;		// Target zoom for big air trick.
+	float									mGrindZoom;				// Target zoom for grind.
+	float									mLipTrickZoom;			// Target zoom for lip trick.
+	int										mLipSideDecided;
+
+	float									mLean;					// For lean during grind imbalance.
+
+	float									mTilt;
+	float									mTiltAddition;
+	float									mLipTrickTilt;
+	float									mLipTrickAbove;
+	float									mWalkSlerp;
+	float									mSlerp;
+	float									mVertAirSlerp;
+	float									mVertAirLandedSlerp;
+	float									mOriginOffset;
+
+	float									mShakeDuration;			// Shake duration in seconds (0.0 means no shake).
+	float									mShakeInitialDuration;
+	float									mShakeMaxVertAmp;
+	float									mShakeMaxHorizAmp;
+	float									mShakeVertVel;
+	float									mShakeHorizVel;
+	
+	CCameraLookAroundComponent*				mp_lookaround_component;
+
+	Mth::SlerpInterpolator*					mpSlerp;
+	CSkater*								mpSkater;
+	CSkaterStateComponent*					mpSkaterStateComponent;
+	CSkaterPhysicsControlComponent*			mpSkaterPhysicsControlComponent;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/SkitchComponent.cpp b/Code/Gel/Components/SkitchComponent.cpp
new file mode 100644
index 0000000..8351b8d
--- /dev/null
+++ b/Code/Gel/Components/SkitchComponent.cpp
@@ -0,0 +1,275 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkitchComponent.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/19/03
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent* CSkitchComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkitchComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSkitchComponent::CSkitchComponent() : CBaseComponent()
+{
+	SetType( CRC_SKITCH );
+
+	// Skitching allowed by default.
+	m_skitch_allowed = true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSkitchComponent::~CSkitchComponent()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkitchComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// There needs to be a ObjectHookManagerComponent attached for the SkitchComponent to operate.
+	mp_object_hook_manager_component = GetObjectHookManagerComponentFromObject( GetObject());
+	Dbg_MsgAssert( mp_object_hook_manager_component, ( "SkitchComponent created without ObjectHookManagerComponent" ));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkitchComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkitchComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent::EMemberFunctionResult CSkitchComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Obj_AllowSkitching | Enable skitching for this object 
+		// @flag off | disable skitching
+		case 0xc38cce3b:  // Obj_AllowSkitching
+		{
+			AllowSkitch(( pParams->ContainsFlag( "off" )) ? false : true );
+			break;
+		}
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkitchComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ( "NULL p_info sent to CSkitchComponent::GetDebugInfo" ));
+	
+	p_info->AddInteger( "m_skitch_allowed", m_skitch_allowed ? 1 : 0 );
+
+	// we call the base component's GetDebugInfo, so we can add info from the common base component
+	CBaseComponent::GetDebugInfo( p_info );
+#endif				 
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CSkitchComponent::GetNearestSkitchPoint( Mth::Vector *p_point, Mth::Vector &pos )
+{
+	int nearest_node_number = -1;	// -1 means not found yet
+
+	// Only continue if we have a parent object (we always should) and a object hook manager component.
+	CCompositeObject *p_parent_object = GetObject();
+	if( mp_object_hook_manager_component && p_parent_object )
+	{
+		Obj::CObjectHookManager *p_object_hook_man = mp_object_hook_manager_component->GetObjectHookManager();
+		if( p_object_hook_man )
+		{
+			// Form a transformation matrix.
+			Mth::Matrix total_mat	= p_parent_object->GetMatrix();
+			total_mat[X][W]			= 0.0f;
+			total_mat[Y][W]			= 0.0f;
+			total_mat[Z][W]			= 0.0f;
+			total_mat[W]			= p_parent_object->GetPos();
+			total_mat[W][W]			= 1.0f;
+
+			p_object_hook_man->UpdateTransform( total_mat );
+
+			int node_number = 0;
+			CObjectHookNode *p_node		= p_object_hook_man->GetFirstNode();
+			CObjectHookNode *p_nearest	= p_node;
+			float nearest_dist_squared	= 100000000000000.0f;	
+			Mth::Vector nearest_point(0.0f, 0.0f, 0.0f);
+
+			while( p_node )
+			{
+				// Get the postition of the hook.
+				Mth::Vector point = p_object_hook_man->GetPos( p_node );
+
+				// Project the point onto the base plane of the object so we can ignore the height.
+				point -= p_parent_object->GetPos();
+				point.ProjectToPlane( p_parent_object->GetMatrix()[Y] );
+				point += p_parent_object->GetPos();
+			
+				float dist_squared = ( point - pos ).LengthSqr();
+				if( dist_squared < nearest_dist_squared )
+				{
+					nearest_dist_squared	= dist_squared;
+					nearest_point			= point;
+					p_nearest				= p_node; 
+					nearest_node_number		= node_number;
+				}
+				p_node = p_node->GetNext();
+				node_number++;
+			}		
+			*p_point = nearest_point;
+		 }
+	}
+
+	return nearest_node_number;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Given the index of a skitch point, then get the position of	  */
+/* that point.													  */
+/* Adjust the point to lay on the ground plane of the object.	  */
+/* Returns the position in *p_point returns true if object still  */
+/* exists, and false otherwise.									  */
+/*                                                                */
+/******************************************************************/
+bool CSkitchComponent::GetIndexedSkitchPoint( Mth::Vector *p_point, int index )
+{
+	// Only continue if we have a parent object (we always should) and a object hook manager component.
+	CCompositeObject *p_parent_object = GetObject();
+	if( mp_object_hook_manager_component && p_parent_object )
+	{
+		Obj::CObjectHookManager *p_object_hook_man = mp_object_hook_manager_component->GetObjectHookManager();
+		if( p_object_hook_man )
+		{
+			// Form a transformation matrix.
+			Mth::Matrix total_mat	= p_parent_object->GetMatrix();
+			total_mat[X][W]			= 0.0f;
+			total_mat[Y][W]			= 0.0f;
+			total_mat[Z][W]			= 0.0f;
+			total_mat[W]			= p_parent_object->GetPos();
+			total_mat[W][W]			= 1.0f;
+
+			p_object_hook_man->UpdateTransform( total_mat );
+
+			int node_number = 0;
+			CObjectHookNode *p_node = p_object_hook_man->GetFirstNode();
+		
+			while( p_node )
+			{
+				if( node_number == index )
+				{
+					Mth::Vector point = p_object_hook_man->GetPos( p_node );
+
+					// Project the point onto the base plane of the object so we can ignore the height.
+					point -= p_parent_object->GetPos();
+					point.ProjectToPlane( p_parent_object->GetMatrix()[Y] );
+					point += p_parent_object->GetPos();
+
+					*p_point = point;
+
+					// There was no break here in the original code in MovingObject, but I see no reason
+					// to continue the loop iteration at this point.
+					break;
+				}
+				p_node = p_node->GetNext();
+				node_number++;
+			}
+			return true;
+		 }
+	}
+	return false;
+}
+
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/Code/Gel/Components/SkitchComponent.h b/Code/Gel/Components/SkitchComponent.h
new file mode 100644
index 0000000..2e7b407
--- /dev/null
+++ b/Code/Gel/Components/SkitchComponent.h
@@ -0,0 +1,61 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkitchComponent.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/19/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKITCHCOMPONENT_H__
+#define __COMPONENTS_SKITCHCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_SKITCH								CRCD( 0x3506ce64, "Skitch" )
+
+//  Standard accessor macros for getting the component either from within an object, or given an object
+#define		GetSkitchComponent()					((Obj::CSkitchComponent*)GetComponent( CRC_SKITCH ))
+#define		GetSkitchComponentFromObject( pObj )	((Obj::CSkitchComponent*)(pObj)->GetComponent( CRC_SKITCH ))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkitchComponent : public CBaseComponent
+{
+public:
+    CSkitchComponent();
+    virtual ~CSkitchComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+	bool							CanSkitch( void )			{ return m_skitch_allowed; }
+	void							AllowSkitch( bool allow )	{ m_skitch_allowed = allow; }
+	int								GetNearestSkitchPoint( Mth::Vector *p_point, Mth::Vector &pos );
+	bool							GetIndexedSkitchPoint( Mth::Vector *p_point, int index );
+
+private:
+	bool							m_skitch_allowed;
+	CObjectHookManagerComponent*	mp_object_hook_manager_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/StaticVehicleComponent.cpp b/Code/Gel/Components/StaticVehicleComponent.cpp
new file mode 100644
index 0000000..cbaa7ad
--- /dev/null
+++ b/Code/Gel/Components/StaticVehicleComponent.cpp
@@ -0,0 +1,186 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       StaticVehicleComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  8/6/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CStaticVehicleComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CStaticVehicleComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CStaticVehicleComponent::CStaticVehicleComponent() : CBaseComponent()
+{
+	SetType( CRC_STATICVEHICLE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CStaticVehicleComponent::~CStaticVehicleComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStaticVehicleComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStaticVehicleComponent::Finalize()
+{
+	CModelComponent* p_model_component = static_cast< CModelComponent* >(GetModelComponentFromObject(GetObject()));
+	Dbg_Assert(p_model_component);
+	Nx::CModel* p_model = p_model_component->GetModel();
+	Dbg_Assert(p_model);
+	Nx::CHierarchyObject* p_hierarchy_objects = p_model->GetHierarchy();
+	Dbg_Assert(p_hierarchy_objects);
+	
+	Mth::Vector p_wheels [ CVehicleComponent::vVP_NUM_WHEELS ];
+	
+	for (int n = CVehicleComponent::vVP_NUM_WHEELS; n--; )
+	{
+		Mth::Vector& wheel_pos = p_wheels[n];
+		
+		Mth::Matrix wheel_matrix = (p_hierarchy_objects + 2 + n)->GetSetupMatrix();
+		
+		// rotate out of max coordinate system
+		wheel_pos[X] = -wheel_matrix[W][X];
+		wheel_pos[Y] = wheel_matrix[W][Z];
+		wheel_pos[Z] = -wheel_matrix[W][Y];
+		wheel_pos[W] = 1.0f;
+	}
+	
+	Mth::Matrix body_matrix = (p_hierarchy_objects + 1)->GetSetupMatrix();
+	Mth::Vector body_pos;
+	body_pos[X] = -body_matrix[W][X];
+	body_pos[Y] = body_matrix[W][Z];
+	body_pos[Z] = -body_matrix[W][Y];
+	body_pos[W] = 1.0f;
+	
+	CSkeletonComponent* p_skeleton_component = static_cast< CSkeletonComponent* >(GetSkeletonComponentFromObject(GetObject()));
+	Dbg_Assert(p_skeleton_component);
+	Dbg_Assert(p_skeleton_component->GetSkeleton());
+	Mth::Matrix* p_matrices = p_skeleton_component->GetSkeleton()->GetMatrices();
+	for (int i = 0; i < p_skeleton_component->GetSkeleton()->GetNumBones(); i++)
+	{
+		Mth::Matrix& matrix = p_matrices[i];
+		
+		// setup the matrix for each bone in the skeleton
+		
+		// shadow
+		if (i == 0)
+		{
+			matrix.Zero();
+		}
+		
+		// body
+		else if (i == 1)
+		{
+			matrix.Zero();
+			matrix[X][X] = 1.0f;
+			matrix[Y][Z] = -1.0f;
+			matrix[Z][Y] = 1.0f;
+			matrix[W] = body_pos;
+		}
+		
+		// wheel
+		else
+		{
+			Mth::Vector& wheel_pos = p_wheels[i - 2];
+			
+			matrix.Zero();
+			matrix[X][X] = -1.0f;
+			matrix[Y][Z] = 1.0f;
+			matrix[Z][Y] = 1.0f;
+			matrix[W] = wheel_pos;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStaticVehicleComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStaticVehicleComponent::Update()
+{
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CStaticVehicleComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStaticVehicleComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CStaticVehicleComponent::GetDebugInfo"));
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+	
+}
diff --git a/Code/Gel/Components/StaticVehicleComponent.h b/Code/Gel/Components/StaticVehicleComponent.h
new file mode 100644
index 0000000..c79e498
--- /dev/null
+++ b/Code/Gel/Components/StaticVehicleComponent.h
@@ -0,0 +1,55 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       StaticVehicleComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  8/6/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_STATICVEHICLECOMPONENT_H__
+#define __COMPONENTS_STATICVEHICLECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_STATICVEHICLE CRCD(0x9649d4f9, "StaticVehicle")
+
+#define		GetStaticVehicleComponent() ((Obj::CStaticVehicleComponent*)GetComponent(CRC_STATICVEHICLE))
+#define		GetStaticVehicleComponentFromObject(pObj) ((Obj::CStaticVehicleComponent*)(pObj)->GetComponent(CRC_STATICVEHICLE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CStaticVehicleComponent : public CBaseComponent
+{
+public:
+    CStaticVehicleComponent();
+    virtual ~CStaticVehicleComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual	void 					Finalize();
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+private:
+	Mth::Vector						mp_wheels [ CVehicleComponent::vVP_NUM_WHEELS ];
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/StatsManagerComponent.cpp b/Code/Gel/Components/StatsManagerComponent.cpp
new file mode 100644
index 0000000..f3947ed
--- /dev/null
+++ b/Code/Gel/Components/StatsManagerComponent.cpp
@@ -0,0 +1,1252 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       StatsManagerComponent.cpp
+//* OWNER:          Zac Drake
+//* CREATION DATE:  5/29/03
+//****************************************************************************
+
+//  This component watches the skater and checks if certain always active goals are met.
+//  When these goals are met, the skaters stats are increased. These goals are pulled from
+//  the stats_goals script array in stats.q
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  
+#include  
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CStatsManagerComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CStatsManagerComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CStatsManagerComponent::CStatsManagerComponent() : CBaseComponent()
+{
+	SetType( CRC_STATSMANAGER );
+    
+    mpSkater= NULL;
+    mpSkaterStateComponent = NULL;
+    mpSkaterPhysicsControlComponent = NULL;
+    mpSkaterCorePhysicsComponent = NULL;
+
+    vert_start_pos.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+    vert_end_pos.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+    jump_start_pos.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+    jump_end_pos.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CStatsManagerComponent::~CStatsManagerComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CStatsManagerComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+    
+    showed_info = false;    // need to grab this from memcard eventually
+    stat_goals_on = false;
+    
+    new_event = NULL;
+    last_event = NULL;
+    
+    vert_set = 0;
+    vert_height = 0;
+    vert_score = 0;
+    vert_start_base=0;
+    vert_start_mult=0;
+    
+    jump_set = 0;
+    
+    num_fliptricks=0;
+    num_grabtricks=0;
+
+    air_combo_score=0;
+
+    m_restarted_this_frame = true;
+
+    // Set p_skater
+    uint32 target_id = Obj::CBaseComponent::GetObject()->GetID();
+    Dbg_MsgAssert(target_id >=0 && target_id <= 15,("Bad StatsManagerTarget 0x%x in SkaterStatsManagerComponent",target_id));
+    
+	CSkater* p_skater = static_cast(CCompositeObjectManager::Instance()->GetObjectByID(target_id)); 
+	Dbg_MsgAssert(p_skater,("Can't find skater %d in SkaterStatsManagerComponent",target_id));
+    
+	SetSkater(p_skater);
+
+    // get difficulty level
+    Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+    Dbg_Assert( pGoalManager );
+    dif_level = (int)pGoalManager->GetDifficultyLevel(true);
+
+    printf("STATSManager: difficulty ====================== %i\n", dif_level );
+
+    // should bump stats?
+    int bump = Script::GetInteger( "bump_stats", Script::ASSERT );
+    if ( bump == 1 )
+    {
+        bump_stats=true;
+    }
+
+    // reset stats to defaults
+    /*Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+    pProfile->SetPropertyValue( CRCD(0x439f4704,"air"), 3 );
+    pProfile->SetPropertyValue( CRCD(0x9016b4e7,"switch"), 3 );
+    pProfile->SetPropertyValue( CRCD(0xedf5db70,"spin"), 3 );
+    pProfile->SetPropertyValue( CRCD(0xaf895b3f,"run"), 3 );
+    pProfile->SetPropertyValue( CRCD(0xf0d90109,"speed"), 3 );
+    pProfile->SetPropertyValue( CRCD(0xae798769,"lip_balance"), 3 );
+    pProfile->SetPropertyValue( CRCD(0xf73a13e3,"rail_balance"), 3 );
+    pProfile->SetPropertyValue( CRCD(0xb1fc0722,"manual_balance"), 3 );
+    pProfile->SetPropertyValue( CRCD(0x6dcb497c,"flip_speed"), 3 );
+    pProfile->SetPropertyValue( CRCD(0x9b65d7b8,"ollie"), 3 );
+    */
+    
+    // get initial values from script
+    Script::CArray* p_array = Script::GetArray( "stats_goals", Script::ASSERT );
+    Dbg_MsgAssert( p_array->GetType() == ESYMBOLTYPE_STRUCTURE, ( "This function only works on arrays of structures." ) );
+
+    int array_size = p_array->GetSize();
+    Script::CStruct* p_struct = NULL;
+    
+    //for( int index = 0; index < NUM_STATS_GOALS; index++)
+    for ( int index = 0; index < array_size; index++ )
+    {
+        p_struct = p_array->GetStructure( index );
+        if ( p_struct->ContainsComponentNamed( "goaltype" ) )
+        {
+            uint32 goal_value;
+            p_struct->GetChecksum( "goaltype", &goal_value, Script::ASSERT );
+            stat_goals[index].goaltype = goal_value;
+        }
+        if ( p_struct->ContainsComponentNamed( "stattype" ) )
+        {
+            uint32 stat_value;
+            p_struct->GetChecksum( "stattype", &stat_value, Script::ASSERT );
+            stat_goals[index].stattype = stat_value;
+        }
+        if ( p_struct->ContainsComponentNamed( "value" ) )
+        {
+            Script::CArray* value_array = NULL;
+            int value_value;
+            p_struct->GetArray( "value", &value_array, Script::ASSERT );
+            value_value = value_array->GetInteger(dif_level);
+            stat_goals[index].value = value_value;
+        }
+        if ( p_struct->ContainsComponentNamed( "value_string" ) )
+        {
+            Script::CArray* value_string_array = NULL;
+            const char*  value_vstring;
+            p_struct->GetArray( "value_string", &value_string_array, Script::ASSERT );
+            value_vstring = value_string_array->GetString(dif_level);
+            stat_goals[index].v_string = value_vstring;
+        }
+        if ( p_struct->ContainsComponentNamed( "text" ) )
+        {
+            const char* text_value="";
+            p_struct->GetString( "text", &text_value, Script::ASSERT );
+            stat_goals[index].text = text_value;
+        }
+        if ( p_struct->ContainsComponentNamed( "complete" ) )
+        {
+            int complete_value;
+            p_struct->GetInteger( "complete", &complete_value, Script::ASSERT );
+            stat_goals[index].complete = complete_value;
+        }
+        if ( p_struct->ContainsComponentNamed( "value_trick" ) )
+        {
+            Script::CArray* value_trick_array = NULL;
+            uint32  value_vtrick;
+            p_struct->GetArray( "value_trick", &value_trick_array, Script::ASSERT );
+            value_vtrick = value_trick_array->GetChecksum(dif_level);
+            stat_goals[index].trick = value_vtrick;
+        }
+        if ( p_struct->ContainsComponentNamed( "value_taps" ) )
+        {
+            Script::CArray* value_taps_array = NULL;
+            uint32  value_vtaps;
+            p_struct->GetArray( "value_taps", &value_taps_array, Script::ASSERT );
+            value_vtaps = value_taps_array->GetInteger(dif_level);
+            stat_goals[index].taps = value_vtaps;
+        }
+        stat_goals[index].shown = false;
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*void CStatsManagerComponent::Finalize()
+{
+	// Virtual function, can be overridden to provided finialization to 
+	// a component after all components have been added to an object
+	// Usually this consists of getting pointers to other components
+	// Note:  It is GUARENTEED (at time of writing) that
+	// Finalize() will be called AFTER all components are added
+	// and BEFORE the CCompositeObject::Update function is called
+	
+	// Example:
+	// mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+    
+}*/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CStatsManagerComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calling InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting because everything is missing 
+	
+	InitFromStructure(pParams);
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CStatsManagerComponent::Update()
+{
+	// **  You would put in here the stuff that you would want to get run every frame
+	// **  for example, a physics type component might update the position and orientation
+	// **  and update the internal physics state
+
+    if( mpSkater == NULL )
+	{
+		return;
+	}
+
+    // Return if not in career mode
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    if ( !skate_mod->GetGameMode()->IsTrue( CRCD(0x1ded1ea4,"is_career") ) )
+    {
+        return;
+    }
+    
+    // Return if stat goals turned off
+    if ( !stat_goals_on )
+    {
+        return;
+    }
+    
+    // Return if in frontend
+    if (skate_mod->m_cur_level == CRCD(0x9f2bafb7,"load_skateshop"))
+    {
+        return;
+    }
+
+    if (m_restarted_this_frame)
+    {
+        vert_set = 0;
+        jump_set = 0;
+
+        m_restarted_this_frame = false;
+
+        return;
+    }
+    
+    // Get current trick score this frame???
+    Mdl::Score *pScore = mpSkater->GetScoreObject();
+    Dbg_Assert( pScore );
+    current_score = pScore->GetScorePotValue();
+    current_mult = pScore->GetCurrentMult();
+    if ( current_mult != 0 )
+    {
+        current_base = (current_score / current_mult);
+    }
+    else
+    {
+        current_base = 0;
+    }
+    
+    // Get skater state
+    EStateType state = mpSkaterStateComponent->GetState();
+
+    // reset new event
+    new_event = CRCD(0x26a7cadf,"other");
+    
+    // check if skating
+    if (mpSkaterPhysicsControlComponent->IsSkating())
+    {
+        // check for vert airs and landings
+        if ( mpSkaterStateComponent->GetFlag( VERT_AIR ) )
+        {
+            new_event = CRCD(0xb39b4f1b,"vert_air");
+            if ( last_event != CRCD(0xb39b4f1b,"vert_air") )
+            {
+                // vert take off
+                vert_set = 1;
+                vert_height = 0;
+                vert_start_pos = GetObject()->GetPos();
+                vert_start_base = current_base;
+                vert_start_mult = current_mult;
+                printf("vert_start_score = %i\n", current_score );
+                vert_score = 0;
+            }
+            else
+            {
+                // vert in air
+                Mth::Vector new_pos = GetObject()->GetPos();
+                if ( ( new_pos[1] - vert_start_pos[1] ) > vert_height )
+                {
+                    vert_height = ( new_pos[1] - vert_start_pos[1] );
+                }
+
+                // the dif in base times the dif in multiplier
+                air_combo_score = ((current_base - vert_start_base) * (current_mult - vert_start_mult));
+
+                if ( air_combo_score >= vert_score )
+                {
+                    vert_score = air_combo_score;
+                }
+            }
+        }
+        else
+        {
+            // landed from vert ( not really landed... could still revert to manual )
+            if ( last_event == CRCD(0xb39b4f1b,"vert_air") )
+            {
+                int air_combo_mult=0;
+
+                if ( vert_set == 1 )
+                {
+                    vert_end_pos = GetObject()->GetPos();
+                    air_combo_score = ((current_base - vert_start_base) * (current_mult - vert_start_mult));
+                    air_combo_mult = (current_mult - vert_start_mult);
+                    vert_set = 2;
+                }
+
+                // air combo messages
+                if ( air_combo_mult > 1 )
+                {
+                    if ( air_combo_score != 0 )
+                    {
+                        Script::CStruct* p_params = new Script::CStruct();
+                        p_params->AddInteger( "score", air_combo_score );
+                        Script::RunScript( Script::GenerateCRC( "show_vert_combo_message" ), p_params );
+                        delete p_params;
+                    }
+                }
+                air_combo_score=0;
+            }
+            // not vert related
+            else
+            {
+                vert_set = 0;
+                vert_height = 0;
+                vert_score = 0;
+            }
+        }
+
+        // check for jump takeoffs and landings
+        if ( (state == AIR) && (!mpSkaterStateComponent->GetFlag( VERT_AIR )) )
+        {
+            new_event = CRCD(0x439f4704,"air");
+            if ( last_event != CRCD(0x439f4704,"air") )
+            {
+                if ( !mpSkaterStateComponent->GetFlag(IS_BAILING) )
+                {
+                    jump_set = 1;
+                    jump_start_pos = GetObject()->GetPos();
+                }
+            }
+        }
+        else
+        {
+            if ( last_event == CRCD(0x439f4704,"air") )
+            {
+                if ( jump_set == 1 )
+                {
+                    jump_end_pos = GetObject()->GetPos();
+                    jump_set = 2;
+                }
+            }
+            else
+            {
+                jump_set = 0;
+            }
+        }
+    }
+    else
+    {
+        jump_set = 0;
+    }
+    
+    // check for triggered goals
+    bool beat_one = false;
+    for (int i=0; i < NUM_STATS_GOALS; i++)
+    {
+        // Combo goals
+        if ( stat_goals[i].goaltype == CRCD(0x4ec3cfb5,"combo") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    int value = stat_goals[i].value;
+                    if ( current_score >= value )
+                    {
+                        ShowStatsMessage(i);
+                        beat_one=true;
+                    }
+                }
+            }
+        }
+
+        // Multiplier goals
+        if ( stat_goals[i].goaltype == CRCD(0x5b6556a4,"multiplier") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    int value = stat_goals[i].value;
+                    if ( current_mult >= value )
+                    {
+                        ShowStatsMessage(i);
+                        beat_one=true;
+                    }
+                }
+            }
+        }
+
+        // Vert Distance
+        if ( stat_goals[i].goaltype == CRCD(0xc3ec3eed,"vertdist") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    if ( vert_set == 2 )
+                    {
+                        float value = float(stat_goals[i].value);
+                        float vert_dist = 1;
+                        
+                        // set Y values to zero
+                        vert_end_pos[1] = 0;
+                        vert_start_pos[1] = 0;
+    
+                        vert_dist = Mth::Distance(vert_end_pos, vert_start_pos);
+                        //printf("vert_dist = %f value = %f \n", (vert_dist/12), value );
+                        
+                        if ( vert_dist >= (12*value) ) // value is in feet, vert_dist is in inches
+                        {
+                            ShowStatsMessage(i);
+                            beat_one=true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Vert Height
+        if ( stat_goals[i].goaltype == CRCD(0x430b80c4,"vertheight") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    if ( vert_set != 0 )
+                    {
+                        float value = float(stat_goals[i].value);
+                        
+                        if ( vert_height >= (12*value) ) // value is in feet, vert_height is in inches
+                        {
+                            ShowStatsMessage(i);
+                            beat_one=true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Vert Score
+        if ( stat_goals[i].goaltype == CRCD(0x1dbbb148,"vertscore") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    if ( vert_set == 2 )
+                    {
+                        int value = stat_goals[i].value;
+                        
+                        if ( vert_score >= value )
+                        {
+                            ShowStatsMessage(i);
+                            beat_one=true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Vert Spin
+        if ( stat_goals[i].goaltype == CRCD(0x509aca95,"vertspin") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    if ( vert_set != 0 )
+                    {
+                        float value = float(stat_goals[i].value);
+                        
+                        if ( vert_spin >= value )
+                        {
+                            ShowStatsMessage(i);
+                            beat_one=true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Jump Distance
+        if ( stat_goals[i].goaltype == CRCD(0x8f6f9f05,"olliedist") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    if ( jump_set == 2 )
+                    {
+                        float value = float(stat_goals[i].value);
+                        float jump_dist = 1;
+                        Mth::Vector end;
+						Mth::Vector start;
+						
+                        // grab start and end points minus Y values
+                        end[0] = jump_end_pos[0];
+                        end[1] = 0;
+                        end[2] = jump_end_pos[2];
+			end[3] = 1.0f;
+
+                        start[0] = jump_start_pos[0];
+                        start[1] = 0;
+                        start[2] = jump_start_pos[2];
+                        start[3] = 1.0f;
+
+                        jump_dist = Mth::Distance(end, start);
+                        //printf("jump_dist = %f value = %f \n", (jump_dist/12), value );
+                        
+                        if ( jump_dist >= (12*value) ) // value is in feet, jump_dist is in inches
+                        {
+                            ShowStatsMessage(i);
+                            beat_one=true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Jump Height
+        if ( stat_goals[i].goaltype == CRCD(0x39e7537b,"highollie") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    if ( jump_set == 2 )
+                    {
+                        float value = float(stat_goals[i].value);
+                        float jump_high = 1;
+                        
+                        jump_high = ( jump_end_pos[1] - jump_start_pos[1] );
+                        
+                        if ( jump_high >= (12*value) ) // value is in feet, jump_high is in inches
+                        {
+                            ShowStatsMessage(i);
+                            beat_one=true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Jump Drop
+        if ( stat_goals[i].goaltype == CRCD(0x7e064ad0,"olliedrop") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    if ( jump_set == 2 )
+                    {
+                        float value = float(stat_goals[i].value);
+                        float jump_low = 1;
+                        
+                        jump_low = ( jump_start_pos[1] - jump_end_pos[1] );
+                        
+                        if ( jump_low >= (12*value) ) // value is in feet, jump_low is in inches
+                        {
+                            ShowStatsMessage(i);
+                            beat_one=true;
+                        }
+                    }
+                }
+            }
+        }
+
+        // Num Fliptricks
+        if ( stat_goals[i].goaltype == CRCD(0x364e8c16,"numfliptricks") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    int value = (stat_goals[i].value);
+                    
+                    if ( num_fliptricks >= value )
+                    {
+                        ShowStatsMessage(i);
+                        beat_one=true;
+                    }
+                }
+            }
+        }
+
+        // Num grabtricks
+        if ( stat_goals[i].goaltype == CRCD(0x68703ae9,"numgrabs") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {
+                    int value = (stat_goals[i].value);
+                    
+                    if ( num_grabtricks >= value )
+                    {
+                        ShowStatsMessage(i);
+                        beat_one=true;
+                    }
+                }
+            }
+        }
+
+        // String Counter
+        if ( stat_goals[i].goaltype == CRCD(0x9068b4e5,"stringcount") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {   
+                    int value = (stat_goals[i].value);
+                    const char* string = (stat_goals[i].v_string);
+                    //printf("%s", string );
+
+                    if ( pScore->CountStringMatches(string) >= value )
+                    {
+                        ShowStatsMessage(i);
+                        beat_one=true;
+                    }
+                }
+            }
+        }
+
+        // Trick Counter
+        if ( stat_goals[i].goaltype == CRCD(0x69b0f836,"trickcount") )
+        {
+            if (!stat_goals[i].complete)
+            {
+                if (!stat_goals[i].shown)
+                {   
+                    int value = (stat_goals[i].value);
+                    uint32 trick = (stat_goals[i].trick);
+                    int num_taps = (stat_goals[i].taps);
+                    
+                    /*int tricktimes = pScore->GetCurrentNumberOfOccurrencesByName(trick, 0, false, num_taps);
+                    if ( tricktimes > 0)
+                    {
+                        printf("CountTrickMatches = %i\n", tricktimes );
+                    }*/
+                    
+                    if ( pScore->GetCurrentNumberOfOccurrencesByName(trick, 0, false, num_taps) >= value )
+                    {
+                        ShowStatsMessage(i);
+                        beat_one=true;
+                    }
+                }
+            }
+        }
+    }
+    
+    if (beat_one)
+    {
+        Script::RunScript( Script::GenerateCRC( "showed_stat_message_sound" ) );
+    }
+
+    vert_spin = 0;
+    last_event = new_event;
+    last_score = current_score;
+    last_base = current_base;
+    last_mult = current_mult;
+    
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CStatsManagerComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+        
+        // @script | StatsManager_ReInit | reinitializes stats manger. used when starting new career/story
+        case 0x82ce30ce: // StatsManager_ReInit
+		{
+			RefreshFromStructure( pParams );
+		}
+		break;
+
+        // @script | StatsManager_Reset | resets stats manger. used when reinitializing skater
+        case 0x57879c96: //StatsManager_Reset
+		{
+			Bail();
+		}
+		break;
+
+        // @script | StatsManager_ActivateGoals | activates stats goals
+        case 0xee59073c: // StatsManager_ActivateGoals
+		{
+			stat_goals_on = true;
+            Script::RunScript( Script::GenerateCRC( "console_unhide" ) );
+		}
+		break;
+
+        // @script | StatsManager_DeactivateGoals | deactivates stats goals
+        case 0xf1745991: // StatsManager_DeactivateGoals
+		{
+			stat_goals_on = false;
+            Script::RunScript( Script::GenerateCRC( "console_hide" ) );
+		}
+		break;
+        
+        // @script | StatsManager_GetGoalStatus | returns status of stats goal with provided index
+        case 0x78e7d855: // StatsManager_GetGoalStatus
+        {
+            int index;
+            int status;
+            pParams->GetInteger( NONAME, &index, Script::ASSERT );
+            status = stat_goals[index].complete;
+            pScript->GetParams()->AddInteger( CRCD(0x84ff9ae3,"status"), status );
+        }
+        break;
+
+        // @script | StatsManager_UnlockAmGoals | unlocks amateur stats goals
+        case 0xb341b5a5: // StatsManager_UnlockAmGoals
+        {
+            for (int i=0; i < NUM_STATS_GOALS; i++)
+            {
+                if ( stat_goals[i].complete == 2 )
+                {
+                    stat_goals[i].complete = 0;
+                }
+            }
+        }
+        break;
+
+        // @script | StatsManager_UnlockProGoals | unlocks pro stats goals
+        case 0xb7fac894: // StatsManager_UnlockProGoals
+        {
+            for (int i=0; i < NUM_STATS_GOALS; i++)
+            {
+                if ( stat_goals[i].complete == 3 )
+                {
+                    stat_goals[i].complete = 0;
+                }
+            }
+        }
+        break;
+
+        default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CStatsManagerComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger(CRCD(0x7cf2a233,"m_never_suspend"),m_never_suspend);
+	p_info->AddFloat(CRCD(0x519ab8e0,"m_suspend_distance"),m_suspend_distance);
+	*/
+	
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CStatsManagerComponent::SetSkater( CSkater* p_skater )
+{
+	mpSkater = p_skater;
+	
+	if( p_skater == NULL )
+	{
+		mpSkaterStateComponent = NULL;
+        mpSkaterPhysicsControlComponent = NULL;
+		//mpSkaterPhysicsControlComponent = NULL;
+	}
+	else
+	{
+		mpSkaterStateComponent = GetSkaterStateComponentFromObject(mpSkater);
+		Dbg_Assert(mpSkaterStateComponent);
+
+        mpSkaterPhysicsControlComponent = GetSkaterPhysicsControlComponentFromObject(mpSkater);
+		Dbg_Assert(mpSkaterPhysicsControlComponent);
+		
+		// non-local clients will not have a CSkaterPhysicsControlComponent
+		//mpSkaterPhysicsControlComponent = GetSkaterPhysicsControlComponentFromObject(mpSkater);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CStatsManagerComponent::SetTrick( char *trick_name, int base_score, bool is_switch )
+{
+    if ( base_score != last_base_score )
+    {
+        last_trick_name = trick_name;
+//        printf("trick_name = %s\n", trick_name );
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::CheckTimedRecord( uint32 checksum, float time, bool final )
+{
+    if ( !(Mdl::Skate::Instance()->GetGameMode()->IsTrue( CRCD(0x1ded1ea4,"is_career") )) )
+    {
+        return;
+    }
+
+    if ( !stat_goals_on )
+    {
+        return;
+    }
+    
+    bool beat_one = false;
+    for (int i=0; i < NUM_STATS_GOALS; i++)
+    {
+        if (!stat_goals[i].complete)
+        {
+            if (!stat_goals[i].shown)
+            {
+                int value = stat_goals[i].value;
+
+                switch ( checksum )
+                {
+                    case 0x0ac90769: //"noseManual"
+                    case 0xef24413b: //"manual"
+                        if ( stat_goals[i].goaltype == CRCD(0xd9ffc141,"manualtime") )
+                        {
+                            if ( time >= value )
+                            {
+                                ShowStatsMessage(i);
+                                beat_one=true;
+                            }
+                        }
+                        break;
+                    case 0x255ed86f: //"grind"
+                    case 0x8d10119d: //"slide"
+                        if ( stat_goals[i].goaltype == CRCD(0xc1336019,"grindtime") )
+                        {
+                            if ( time >= value )
+                            {
+                                ShowStatsMessage(i);
+                                beat_one=true;
+                            }
+                        }
+                        break;
+                    case 0xa549b57b: //"lip"
+                        if ( stat_goals[i].goaltype == CRCD(0xb67cff14,"liptime") )
+                        {
+                            if ( time >= value )
+                            {
+                                ShowStatsMessage(i);
+                                beat_one=true;
+                            }
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+    }
+
+    if (beat_one)
+    {
+        Script::RunScript( Script::GenerateCRC( "showed_stat_message_sound" ) );
+    }
+    
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+                                                                    
+void CStatsManagerComponent::Bail()
+{
+    Script::RunScript( Script::GenerateCRC( "stats_message_bail" ) );
+
+    for (int i=0; i < NUM_STATS_GOALS; i++)
+    {
+        if (!stat_goals[i].complete)
+        {
+            if (stat_goals[i].shown)
+            {
+                stat_goals[i].shown = false;
+            }
+        }
+    }
+
+    vert_set = 0;
+    jump_set = 0;
+    num_fliptricks=0;
+    num_grabtricks=0;
+    jump_start_pos = jump_end_pos;
+    vert_start_pos = vert_end_pos;
+    
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::Land()
+{
+    if ( !(Mdl::Skate::Instance()->GetGameMode()->IsTrue( CRCD(0x1ded1ea4,"is_career") )) )
+    {
+        return;
+    }
+
+    if ( !stat_goals_on )
+    {
+        return;
+    }
+    
+    Script::CStruct* p_params = new Script::CStruct();
+
+    Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+
+    uint32 stat;
+    bool cleared=false;
+    
+    for (int i=0; i < NUM_STATS_GOALS; i++)
+    {
+        if (!stat_goals[i].complete)
+        {
+            if (stat_goals[i].shown)
+            {
+                if (!cleared)
+                {
+                    // clear current messages
+                    Script::RunScript( Script::GenerateCRC( "stats_message_land" ) );
+                    cleared = true;
+                }
+
+                // set goal as complete
+                stat_goals[i].complete = 1;
+
+                // increase proper stat
+                stat = ( stat_goals[i].stattype );
+
+                switch ( stat )
+                {
+                    case 0xb1fc0722: //"manual_balance"
+                        p_params->AddString( "text", Script::GetString( CRCD(0x69f2596,"manual_increase_text") ) );
+                        break;
+                    case 0xf73a13e3: //"rail_balance"
+                        p_params->AddString( "text", Script::GetString( CRCD(0x6e7a996d,"rail_increase_text") ) );
+                        break;
+                    case 0xae798769: //"lip_balance"
+                        p_params->AddString( "text", Script::GetString( CRCD(0x1f1bcdef,"lip_increase_text") ) );
+                        break;
+                    case 0xf0d90109: //"speed"
+                        p_params->AddString( "text", Script::GetString( CRCD(0x1686ebd3,"speed_increase_text") ) );
+                        break;
+                    case 0x9b65d7b8: //"ollie"
+                        p_params->AddString( "text", Script::GetString( CRCD(0xa8386951,"ollie_increase_text") ) );
+                        break;
+                    case 0x439f4704: //"air"
+                        p_params->AddString( "text", Script::GetString( CRCD(0xa523df6c,"air_increase_text") ) );
+                        break;
+                    case 0x6dcb497c: //"flip_speed"
+                        p_params->AddString( "text", Script::GetString( CRCD(0x585887ca,"flip_increase_text") ) );
+                        break;
+                    case 0x9016b4e7: //"switch"
+                        p_params->AddString( "text", Script::GetString( CRCD(0x1a6b74ea,"switch_increase_text") ) );
+                        break;
+                    case 0xedf5db70: //"spin"
+                        p_params->AddString( "text", Script::GetString( CRCD(0xb959cc23,"spin_increase_text") ) );
+                        break;
+                    case 0xaf895b3f: //"run"
+                        p_params->AddString( "text", Script::GetString( CRCD(0xa17ac668,"run_increase_text") ) );
+                        break;
+                    default:
+                        break;
+                }
+
+                // display message
+                p_params->AddInteger( "got_it", 1 );
+                Script::RunScript( Script::GenerateCRC( "show_stats_message" ), p_params );
+                
+                if ( bump_stats )
+                {
+                    // update skater stats
+                    int value = ( pProfile->GetStatValue( stat ) + 1 );
+                	pProfile->SetPropertyValue( stat, value );
+                }
+            }
+        }
+    }
+
+    if (cleared)
+    {
+        printf("updating skater stats\n");
+        mpSkater->UpdateStats(pProfile);
+        
+        if (!showed_info)
+        {
+            Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+            Dbg_Assert( pGoalManager );
+            if ( pGoalManager->CanStartGoal() )
+            {
+                Script::RunScript( Script::GenerateCRC( "beat_first_stat_goal" ) );
+                showed_info = true;
+            }
+        }
+        Script::RunScript( Script::GenerateCRC( "stat_play_win_sound" ) );
+    }
+    
+    delete p_params;
+    num_fliptricks=0;
+    num_grabtricks=0;
+    
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::ShowStatsMessage( int index )
+{
+    Script::CStruct* p_params = new Script::CStruct();
+    p_params->AddString( "string", stat_goals[index].text );
+    p_params->AddInteger( "value", stat_goals[index].value );
+    if ( stat_goals[index].v_string )
+    {
+        p_params->AddString( "vstring", stat_goals[index].v_string );
+    }
+    p_params->AddInteger( "index", index );
+    
+    Script::RunScript( Script::GenerateCRC( "show_stats_message" ), p_params );
+    stat_goals[index].shown = true;
+    delete p_params;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::SetSpin( float angle )
+{
+    //printf("angle = %f\n", angle);
+    vert_spin = angle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::SetTrickType( uint32 type )
+{
+    switch ( type )
+    {
+        case 0xba15fc7f: //"fliptrick"
+            num_fliptricks++;
+//            printf("num_fliptricks = %i\n", num_fliptricks );
+            break;
+        case 0xb9ae26d2: //"grabtrick"
+            num_grabtricks++;
+//            printf("num_grabtricks = %i\n", num_grabtricks );
+            break;
+        default:
+            break;
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::StatGoalsOn()
+{
+    stat_goals_on = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::StatGoalsOff()
+{
+    stat_goals_on = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CStatsManagerComponent::StatGoalGetStatus(int index)
+{
+    return (stat_goals[index].complete);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStatsManagerComponent::StatGoalSetStatus(int index, int value)
+{
+    stat_goals[index].complete = value;
+    if ( value == 1 )
+    {
+        // This means we must be loading stats, so don't show the first stat message!
+        showed_info = true;
+    }
+}
+
+}
diff --git a/Code/Gel/Components/StatsManagerComponent.h b/Code/Gel/Components/StatsManagerComponent.h
new file mode 100644
index 0000000..524aca9
--- /dev/null
+++ b/Code/Gel/Components/StatsManagerComponent.h
@@ -0,0 +1,143 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       StatsManagerComponent.h
+//* OWNER:          Zac Drake
+//* CREATION DATE:  5/29/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_STATSMANAGERCOMPONENT_H__
+#define __COMPONENTS_STATSMANAGERCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_STATSMANAGER CRCD(0x4ff8dac6,"StatsManager")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetStatsManagerComponent() ((Obj::CStatsManagerComponent*)GetComponent(CRC_STATSMANAGER))
+#define		GetStatsManagerComponentFromObject(pObj) ((Obj::CStatsManagerComponent*)(pObj)->GetComponent(CRC_STATSMANAGER))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+    class CSkaterStateComponent;
+    class CSkaterPhysicsControlComponent;
+
+const static int NUM_STATS_GOALS=70;
+
+struct stat_goal {
+    uint32 goaltype;
+    uint32 stattype;
+    int value;
+    bool shown;
+    int complete;
+    const char* text;
+    const char* v_string;
+    uint32 trick;
+    int taps;
+};
+
+
+class CStatsManagerComponent : public CBaseComponent
+{
+public:
+    CStatsManagerComponent();
+    virtual ~CStatsManagerComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	//virtual	void 					Finalize();
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+    void							SetSkater( CSkater* p_skater );
+
+    void							SetTrick( char *trick_name, int base_score, bool is_switch );
+
+    void                            CheckTimedRecord( uint32 checksum, float time, bool final );
+    void                            Bail();
+    void                            Land();
+    void                            SetSpin( float angle );
+    void                            SetTrickType( uint32 type );
+    
+    int                             StatGoalGetStatus(int index);
+    void                            StatGoalSetStatus(int index, int value);
+
+    void                            StatGoalsOn();
+    void                            StatGoalsOff();
+    
+    bool                            m_restarted_this_frame;
+    
+private:
+    
+    stat_goal                       stat_goals[NUM_STATS_GOALS];
+    bool                            stat_goals_on;
+    bool                            bump_stats;
+    bool                            showed_info;
+
+    int                             current_score;
+    int                             last_score;
+    
+    int                             current_base;
+    int                             last_base;
+
+    int                             current_mult;
+    int                             last_mult;
+
+    char                            *last_trick_name;
+    int                             last_base_score;
+
+    int                             dif_level;
+
+    uint32                          new_event;
+    uint32                          last_event;
+
+    Mth::Vector                     vert_start_pos;
+    Mth::Vector                     vert_end_pos;
+    int                             vert_start_base;
+    int                             vert_start_mult;
+    int                             vert_score;
+    int                             vert_set;
+    float                           vert_height;
+
+    float                           vert_spin;
+
+    Mth::Vector                     jump_start_pos;
+    Mth::Vector                     jump_end_pos;
+    int                             jump_set;
+
+    int                             num_fliptricks;
+    int                             num_grabtricks;
+
+    int                             air_combo_score;
+
+    CSkater*						mpSkater;
+    CSkaterStateComponent*			mpSkaterStateComponent;
+    CSkaterPhysicsControlComponent* mpSkaterPhysicsControlComponent;
+    //CSkaterScoreComponent*			mpSkaterScoreComponent;
+    CSkaterCorePhysicsComponent*	mpSkaterCorePhysicsComponent;
+
+    void                            ShowStatsMessage( int index );
+    
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/StreamComponent.cpp b/Code/Gel/Components/StreamComponent.cpp
new file mode 100644
index 0000000..6edeb59
--- /dev/null
+++ b/Code/Gel/Components/StreamComponent.cpp
@@ -0,0 +1,338 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       StreamComponent.cpp
+//* OWNER:          ???
+//* CREATION DATE:  ???
+//****************************************************************************
+
+// The CStreamComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy streamcomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Stream" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+// TODO:  Refactor this - 
+#include 
+#include 
+
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CStreamComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CStreamComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CStreamComponent::CStreamComponent() : CBaseComponent()
+{
+	SetType( CRC_STREAM );
+	mp_emitter = NULL;
+	mp_proxim_node = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CStreamComponent::~CStreamComponent()
+{
+	// If there is a streaming sound attached, remove it.
+	int i;
+	for( i = 0; i < MAX_STREAMS_PER_OBJECT; i++ )
+	{
+		if( mStreamingID[i] )
+		{
+			Pcm::StopStreamFromID( mStreamingID[i] );
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CStreamComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 proxim_node_checksum;
+
+	if (pParams->GetChecksum("ProximNode", &proxim_node_checksum))
+	{
+		//Dbg_Message("StreamComponent: Looking for ProximNode %s", Script::FindChecksumName(proxim_node_checksum));
+		mp_proxim_node = CProximManager::sGetNode(proxim_node_checksum);
+		//if (mp_proxim_node)
+		//{
+		//	Dbg_Message("Found ProximNode %s", Script::FindChecksumName(mp_proxim_node->m_name));
+		//}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CStreamComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	InitFromStructure(pParams);
+
+
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CStreamComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CStreamComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+
+		case 0xdb05a233:  // Obj_PlayStream
+			PlayStream( pParams, pScript );
+			break;
+		
+		case 0xa738aeb8:  // Obj_StopStream
+		{
+			// eventually want to be able to specify channel on here...
+			int i;
+			for ( i = 0; i < MAX_STREAMS_PER_OBJECT; i++ )
+			{
+				if ( mStreamingID[ i ] )
+				{
+					Pcm::StopStreamFromID( mStreamingID[ i ] );
+					mStreamingID[ i ] = 0;
+				}
+			}
+			break;
+		}
+		
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CStreamComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to C......Component::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger("m_never_suspend",m_never_suspend);
+	p_info->AddFloat("m_suspend_distance",m_suspend_distance);
+	*/
+	
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector	CStreamComponent::GetClosestEmitterPos( Gfx::Camera *pCamera )
+{
+	if (mp_emitter && pCamera)
+	{
+		return mp_emitter->GetClosestPoint(pCamera->GetPos());
+	}
+	else
+	{
+		return GetObject()->m_pos;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CStreamComponent::GetClosestDropoffPos( Gfx::Camera *pCamera, Mth::Vector & dropoff_pos )
+{
+	if (pCamera && mp_proxim_node)
+	{
+		dropoff_pos = mp_proxim_node->GetClosestIntersectionPoint(pCamera->GetPos());
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+// Returns 0 if no stream slots empty, or the index + 1
+int CStreamComponent::StreamSlotEmpty( void )
+{
+	int j;
+	for ( j = 0; j < MAX_STREAMS_PER_OBJECT; j++ )
+	{
+		if ( !mStreamingID[ j ] )
+		{
+			return ( j + 1 );
+		}
+	}
+	return ( 0 );
+}
+
+
+bool CStreamComponent::PlayStream( Script::CScriptStructure *pParams, Script::CScript *pScript )
+{
+	
+	if ( !StreamSlotEmpty( ) )
+	{
+	
+		#ifdef	__NOPT_ASSERT__
+		printf ("Playing too many streams on object\n");
+		#endif
+										  
+		return ( false ); // can only play 3 stream on each object max!!!
+	}
+	uint32 streamChecksum;
+	uint32 emitterChecksum;
+	uint32 dropoffFuncChecksum;
+	uint32 id = 0;
+	float dropoff = 0.0f;
+	float volume = 100.0f;
+	float pitch = 100.0f;
+	int priority = STREAM_DEFAULT_PRIORITY;
+	EDropoffFunc dropoffFunction = DROPOFF_FUNC_STANDARD;
+	pParams->GetFloat( 0xf6a36814, &volume ); // "vol"
+	pParams->GetFloat( 0xd8604126, &pitch ); // "pitch"
+	pParams->GetInteger( 0x9d5923d8, &priority ); // priority
+	pParams->GetChecksum( 0x40c698af, &id ); // id
+	if ( pParams->GetFloat( 0xff2020ec, &dropoff ) ) // "dropoff"
+	{
+		dropoff = FEET_TO_INCHES( dropoff );
+	}
+	if ( pParams->GetChecksum( 0xc6ac50a, &dropoffFuncChecksum ) ) // "dropoff_function"
+	{
+		dropoffFunction = Sfx::GetDropoffFunctionFromChecksum( dropoffFuncChecksum );
+	}
+	if (pParams->GetChecksum( 0x8a7132ce, &emitterChecksum ) ) // "emitter"
+	{
+		mp_emitter = CEmitterManager::sGetEmitter(emitterChecksum);
+	}
+	else
+	{
+		mp_emitter = NULL;
+	}
+	if ( pParams->GetChecksum( NONAME, &streamChecksum ) )
+	{
+		int use_pos_info = 1;
+		pParams->GetInteger( "use_pos_info", &use_pos_info, Script::NO_ASSERT );
+		//printf ("Playing stream 0x%x on object %s\n",streamChecksum,Script::FindChecksumName(GetObject()->GetID()));
+		return ( Pcm::PlayStreamFromObject( this, streamChecksum, dropoff, volume, pitch, priority, use_pos_info, dropoffFunction, id ) );
+	}
+	return ( false );
+}
+
+
+
+
+
+	
+}
diff --git a/Code/Gel/Components/StreamComponent.h b/Code/Gel/Components/StreamComponent.h
new file mode 100644
index 0000000..b2bc2c6
--- /dev/null
+++ b/Code/Gel/Components/StreamComponent.h
@@ -0,0 +1,87 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       StreamComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_STREAMCOMPONENT_H__
+#define __COMPONENTS_STREAMCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_STREAM CRCD(0xf1641e3,"stream")
+#define		GetStreamComponent() ((Obj::CStreamComponent*)GetComponent(CRC_STREAM))
+#define		GetStreamComponentFromObject(pObj) ((Obj::CStreamComponent*)(pObj)->GetComponent(CRC_STREAM))
+
+#define MAX_STREAMS_PER_OBJECT	3
+
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Gfx
+{
+	class	Camera;
+}
+
+namespace Obj
+{
+	// Forward declarations
+	class CEmitterObject;
+	class CProximNode;
+
+class CStreamComponent : public CBaseComponent
+{
+public:
+    CStreamComponent();
+    virtual ~CStreamComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	Mth::Vector						GetClosestEmitterPos( Gfx::Camera *pCamera );
+
+	bool							HasProximNode() const { return mp_proxim_node != NULL; }
+	void							ClearProximNode() { mp_proxim_node = NULL; }								// Needed for early proxim node cleanup
+	bool							GetClosestDropoffPos( Gfx::Camera *pCamera, Mth::Vector & dropoff_pos );	// returns true if position defined
+
+	static CBaseComponent*			s_create();
+
+
+public:
+	// PUBLIC FOR NOW!!!!
+	uint32		mStreamingID[ MAX_STREAMS_PER_OBJECT ];
+
+	
+private:	
+	bool		PlayStream( Script::CStruct *pParams, Script::CScript *pScript );
+	// if this is non-zero, the object has streaming sound from the CD attached to it.
+	// it is cleared and set in music.cpp.
+	// Returns 0 if no stream slots empty, or the index + 1
+	int			StreamSlotEmpty();
+
+	CEmitterObject *mp_emitter;
+	CProximNode *	mp_proxim_node;
+
+// End of Stream stuff
+////////////////////////////////////////////
+	
+	
+	
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/SuspendComponent.cpp b/Code/Gel/Components/SuspendComponent.cpp
new file mode 100644
index 0000000..84ab7b2
--- /dev/null
+++ b/Code/Gel/Components/SuspendComponent.cpp
@@ -0,0 +1,602 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SuspendComponent.cpp
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 		// <<<<<<<<<<<   GET RID OF THIS, use the NX camera's directly
+#include 	// <<<<<<<<<<<   GET RID OF THIS TOO!
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CSuspendComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSuspendComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// And this one is the optional "register" function that provides on-time initilization
+// usually of a component manager
+void	CSuspendComponent::s_register()
+{
+	// Create and initilize the manager
+	SuspendManager::Manager::Create();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSuspendComponent::CSuspendComponent() : CBaseComponent()
+{
+	SetType( CRC_SUSPEND );
+	
+	m_suspend_distance=Script::GetFloat(CRCD(0xad501995,"DefaultMovingObjectSuspendDistance")) * 12.0f;
+	m_suspend_distance_squared=m_suspend_distance*m_suspend_distance;
+	m_never_suspend=true;
+	m_no_suspend_count = 3; // default to not allowing suspensions in the first 3 frames
+
+	// Mick: We don't want to display things before they have done at least one frame of
+	// animation, because they start out in their default position (the "Blair Witch" position)
+	// so, we set a default number of animations that we MUST do before we are allowed to
+	// not animate.
+	// This also overrides the SkipLogic test, so we do a frame of logic
+	// which fixes problems where something is moving to the first node of a path
+	
+	// Since the first couple of frames of the game are hidden behind the loading screen
+	// we don't need to worry about framerate glitched due to the large 
+	// number of objects being created at start.	
+	
+	// I Initially had this at 1, which seemed fine
+	// however, when you change levels, it needs to be at 3
+	// I'm not sure why - it might be good to look into this later.
+	m_initial_animations = 3;	 	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSuspendComponent::~CSuspendComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSuspendComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// anything that has a suspend distance will allow suspension
+	if (pParams->GetFloat(CRCD(0xe4279ba2,"SuspendDistance"),&m_suspend_distance))
+	{
+		if (m_suspend_distance == 0.0f)
+		{
+			// if zero suspend dist, then try to get it from lod_dist1
+			if (!pParams->GetFloat(CRCD(0x4eedfae7,"Lod_dist1"),&m_suspend_distance))
+			{
+				// else get the default distance
+				m_suspend_distance=Script::GetFloat(CRCD(0xad501995,"DefaultMovingObjectSuspendDistance"));
+			}
+		}
+	
+		// if we've got a suspend distance, then convert it into inches
+		m_suspend_distance *= 12.0f;
+		m_suspend_distance_squared=m_suspend_distance*m_suspend_distance;
+		m_never_suspend=false;
+	}
+	
+	// suspension can be overridden by the "NeverSuspend" flag
+	if (pParams->ContainsFlag(CRCD(0xcb839dc1,"NeverSuspend")))
+	{
+		m_never_suspend=true;
+	}
+
+	// Get the LOD distances from the node
+	if (Script::GetInt(CRCD(0x95edc6f6,"NoLOD"),false))					 
+	{
+		m_lod_dist[0] = 1000000;
+		m_lod_dist[1] = 1000001;
+		m_lod_dist[2] = 1000002;  // Note, this is used as a magic number to turn off the high level culling
+		m_lod_dist[3] = 1000003;
+	}
+	else
+	{
+		Dbg_Assert( pParams );
+		m_lod_dist[0] = 50;
+		m_lod_dist[2] = 1000001;
+		m_lod_dist[3] = 1000002;
+		pParams->GetFloat(CRCD(0x4eedfae7,"lod_dist1"),&m_lod_dist[0]);
+		m_lod_dist[1] = m_lod_dist[0] + 75;						// default for LOD dist 2 is 75 feet beyond 1
+		pParams->GetFloat(CRCD(0xd7e4ab5d,"lod_dist2"),&m_lod_dist[1]);
+		pParams->GetFloat(CRCD(0xa0e39bcb,"lod_dist3"),&m_lod_dist[2]);
+		pParams->GetFloat(CRCD(0x3e870e68,"lod_dist4"),&m_lod_dist[3]);
+		// LOD dist is in feet
+		m_lod_dist[0] *= 12.0f;
+		m_lod_dist[1] *= 12.0f;
+		m_lod_dist[2] *= 12.0f;
+		m_lod_dist[3] *= 12.0f;
+		if ( m_lod_dist[0]>=m_lod_dist[1] )
+		{
+			Script::PrintContents( pParams );
+			Dbg_MsgAssert(0,("lod_dist1 (%f) >= lod_dist2 (%f) in node",m_lod_dist[0],m_lod_dist[1]));
+		}
+		if ( m_lod_dist[1]>=m_lod_dist[2] )
+		{
+			Script::PrintContents( pParams );
+			Dbg_MsgAssert(0,("lod_dist2 (%f) >= lod_dist3 (%f) in node",m_lod_dist[1],m_lod_dist[2]));
+		}
+		if ( m_lod_dist[2]>=m_lod_dist[3] )
+		{
+			Script::PrintContents( pParams );
+			Dbg_MsgAssert(0,("lod_dist3 (%f) >= lod_dist4 (%f) in node",m_lod_dist[2],m_lod_dist[3]));
+		}
+
+#if defined( __PLAT_XBOX__ ) || defined( __PLAT_NGC__ )
+		// Some platform specific changes to the LOD stuff
+		m_lod_dist[0]		*= Script::GetFloat( 0xd3d072f9 /* LOD0DistanceMultiplier */ );
+		m_lod_dist[1]		*= Script::GetFloat( 0x0432f2a1 /* LOD1DistanceMultiplier */ );
+#endif
+
+	}
+}
+
+void	CSuspendComponent::Finalize()
+{
+	// Might be NULL, if we don't have a model
+	mp_model_component = GetModelComponentFromObject( GetObject() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSuspendComponent::Update()
+{
+	// this funtion does not need to be called, so flag it as such
+	CBaseComponent::Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::EMemberFunctionResult CSuspendComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSuspendComponent::Suspend(bool suspend)
+{
+	// does nothing, as we need to keep checking for unsuspension
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// returns true if we skip logic on this frame
+// this can be true for a number of reasons
+// but mostly boils down to LOD
+bool CSuspendComponent::SkipLogic()
+{
+	return m_skip_logic;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// returns true if we skip logic on this frame
+// this can be true for a number of reasons
+// but mostly boils down to LOD
+bool CSuspendComponent::SkipRender()
+{
+		
+	if ( m_camera_distance_squared > m_lod_dist[1]*m_lod_dist[1] )
+	{
+		return true;
+	}	
+	
+	return false;
+
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSuspendComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSuspendComponent::GetDebugInfo"));
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	
+
+	p_info->AddInteger(CRCD(0x7cf2a233,"m_never_suspend"),m_never_suspend);
+	p_info->AddFloat(CRCD(0x519ab8e0,"m_suspend_distance"),m_suspend_distance);
+	p_info->AddFloat(CRCD(0x340a53d3,"distance_to_camera"),sqrtf(m_camera_distance_squared));
+	p_info->AddFloat(CRCD(0x266306b9,"m_lod_dist_0"),m_lod_dist[0]);
+	p_info->AddFloat(CRCD(0x5164362f,"m_lod_dist_1"),m_lod_dist[1]);
+	p_info->AddFloat(CRCD(0xc86d6795,"m_lod_dist_2"),m_lod_dist[2]);
+	p_info->AddFloat(CRCD(0xbf6a5703,"m_lod_dist_3"),m_lod_dist[3]);
+	
+	p_info->AddInteger(CRCD(0xd9872f4b,"SKIPLOGIC_RETURNS"),SkipLogic());
+	p_info->AddInteger(CRCD(0xd55771a6,"SKIPRENDER_RETURNS"),SkipRender());
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSuspendComponent::should_animate( float *p_dist )
+{
+	bool should_animate = true;  
+
+	// Mick:  don't update the skeleton if it's a long way from the camera
+//	Gfx::Camera * p_camera = Nx::CViewportManager::sGetActiveCamera( 0 );
+//	if (p_camera)
+	{
+//		Mth::Vector cam_pos = p_camera->GetPos();
+//		cam_pos = cam_pos - GetObject()->GetPos();
+//		float dist = cam_pos.LengthSqr();
+
+		float dist = m_camera_distance_squared;
+
+		// Save this dist if a valid pointer was passed.
+		if( p_dist )
+		{
+			*p_dist = dist;
+		}
+
+		if (dist > m_lod_dist[0] * m_lod_dist[0])		// dist at what we stop animating			
+		{
+		    should_animate = false;
+		}
+
+		// update animation intermittently
+		// more so as distance from camera increases
+
+		else
+		{
+#ifndef TESTING_GUNSLINGER
+
+#if defined( __PLAT_XBOX__ ) || defined( __PLAT_NGC__ )
+			float interleave2 = Script::GetFloat( 0xdb120fb5 /* AnimLODInterleave2 */ );
+			float interleave4 = Script::GetFloat( 0x3271aa80 /* AnimLODInterleave4 */ );
+			interleave2 *= interleave2;
+			interleave4 *= interleave4;
+#else
+			float interleave2 = 500.0f * 500.0f;
+			float interleave4 = 800.0f * 800.0f;
+#endif
+
+			m_interleave++;
+			should_animate = false;				
+			if (dist > interleave4)						  
+			{
+				// 1 in 4 for distance 500-1000
+				if (!(m_interleave&3))
+				{
+					should_animate = true;
+				}
+			}
+			else if (dist > interleave2)
+			{
+				// 1 in 2 for 200 to 500
+				if ((m_interleave&1))
+				{
+					should_animate = true;
+				}
+			 }
+			 else
+			 {
+				// always animate if < 200
+				should_animate = true;
+			 }
+#endif		
+		}
+
+	}
+	
+	if (m_initial_animations)
+	{
+		should_animate = true;
+	}
+
+	return should_animate;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Set Model's Active/Inactive flag based on other flags
+// and if the model is actually visible or not (with culling and occlusion)
+// and upon the load_dist2 distance  
+void CSuspendComponent::CheckModelActive()
+{
+	if ( mp_model_component && mp_model_component->GetModel() )
+	{
+		bool active = false;
+		if ( GetObject()->GetFlags() & CObject::vINVISIBLE )
+		{
+			// just leave it inactive
+		}
+		else
+		{
+			// quick check for if the NoLOD flag is set
+			// Note use of magic number (but this is only for debugging/screenshots)
+			if ( m_lod_dist[2] == 1000002 )
+			{
+				active =  true ;
+			}
+			else
+			{
+				// PATCH for multiple viewports
+				// always return true, until we sort out the per-viewport visibility (which might not be needed)				   
+				if ( Nx::CViewportManager::sGetNumActiveViewports() > 1 )
+				{
+					active = true;
+				}
+				else if ( mp_model_component->HasRefObject() )
+				{
+					// GJ:  a ref model doesn't actually have a valid bounding sphere
+					// so don't let Nx::CEngine::sIsVisible() be called...
+					// (alternatively, we could fake a large bounding sphere
+					// but that seems kludgy)
+					active = true;
+				}
+				else
+				{
+					float dist = m_camera_distance_squared;
+					
+					if ( dist < m_lod_dist[1] * m_lod_dist[1] )
+					{		
+						Mth::Vector sphere = mp_model_component->GetModel()->GetBoundingSphere();
+						Mth::Vector position = sphere;
+						position[3] = 1.0f;
+						position *= GetObject()->GetMatrix();
+						position += GetObject()->GetPos();
+						active = ( Nx::CEngine::sIsVisible( position, sphere[3]) );
+					}
+					else
+					{
+						active = false;
+					}
+				}
+			}
+		}
+		bool	was_active = mp_model_component->GetModel()->GetActive();
+		mp_model_component->GetModel()->SetActive( active);		
+		// If we've just turned the model component back on, then run the update on it
+		// so we set it to the right place
+		// otherwise it will be in the old place for a frame
+		// TODO:  This all might be better IN the modelcomponent
+		if (active && !was_active)
+		{
+			mp_model_component->Update();			
+		}
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSuspendComponent::GetDistanceSquaredToCamera()
+{
+	Gfx::Camera * p_camera = Nx::CViewportManager::sGetActiveCamera( 0 );
+	if ( p_camera )
+	{
+		return ( p_camera->GetPos() - GetObject()->GetPos() ).LengthSqr();
+	}
+
+	printf( "No active camera!\n" );
+
+	// no camera?!?
+	return 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+// The following is the SuspendOb manager, which iterates over the suspendob components 
+// to suspend them to whatever they are suspended to
+
+namespace SuspendManager
+{
+// The purpose of this manager is just to update all the suspended objects, by calling
+// CMovingObject::SuspendToObject on all objects that have had Obj_SuspendToObject run on them.
+// It is a manager so that it can be given a task with a priority such that it is called
+// after all the objects are updated.
+// The reason the suspend logic cannot be done in the object update is because sometimes the parent's 
+// update might happen after the child's, causing a frame lag. This was making the gorilla appear
+// behind the tram seat in the zoo when the tram was being skitched. (TT2324)
+
+DefineSingletonClass( Manager, "Suspended object Manager" )
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::Manager()
+{
+	mp_task = new Tsk::Task< Manager > ( s_code, *this,
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_LOCKED_OBJECT_MANAGER_LOGIC );
+
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	mlp_manager->AddLogicTask( *mp_task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager()
+{
+	delete mp_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::s_code( const Tsk::Task< Manager >& task )
+{
+
+	Mdl::SPreCalculatedObjectUpdateInfo* p_info = Mdl::Skate::Instance()->GetPreCalculatedObjectUpdateInfo();
+	
+	Obj::CSuspendComponent *p_suspend_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_SUSPEND ));
+	while( p_suspend_component )
+	{
+
+		// Set camera dist to zero for the purposes of LOD
+		p_suspend_component->m_camera_distance_squared = 0;
+		p_suspend_component->m_skip_logic = false;
+	
+		if (p_suspend_component->m_no_suspend_count)
+		{
+			p_suspend_component->m_no_suspend_count--;
+			continue;
+		}
+
+		if ( p_info->mDoNotSuspendAnything )
+		{
+		}	
+		else if ( p_suspend_component->m_never_suspend )
+		{
+		}
+		else if ( p_suspend_component->m_initial_animations )
+		{
+			p_suspend_component->m_initial_animations--;
+		}
+		else
+		{
+			p_suspend_component->m_camera_distance_squared = Mth::DistanceSqr(p_suspend_component->GetObject()->GetPos(),p_info->mActiveCameraPosition);
+		
+			if ( p_suspend_component->m_camera_distance_squared > p_suspend_component->m_suspend_distance_squared )
+			{
+				p_suspend_component->m_skip_logic = true;
+			}	
+			else
+			{
+				p_suspend_component->m_skip_logic = false;
+			}
+		}
+		// suspend or unsuspend the object and its components
+		// based on the new state of m_skip_logic
+		if (p_suspend_component->m_skip_logic)
+		{
+			if (!p_suspend_component->GetObject()->IsSuspended())
+			{
+				p_suspend_component->GetObject()->Suspend(true);
+			}
+		}
+		else
+		{
+			if (p_suspend_component->GetObject()->IsSuspended())
+			{
+				p_suspend_component->GetObject()->Suspend(false);
+			}
+		}
+	
+		// If it has a model, then check for LOD to nothing	
+		if (p_suspend_component->mp_model_component)
+		{
+			if (p_suspend_component->m_initial_animations)
+			{
+				if (p_suspend_component->mp_model_component->GetModel())
+				{
+					p_suspend_component->mp_model_component->GetModel()->SetActive(true);
+				}
+			}
+			else
+			{
+				p_suspend_component->CheckModelActive();	
+			}
+		}
+		
+		p_suspend_component = static_cast( p_suspend_component->GetNextSameType());
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+}
+	
+}
diff --git a/Code/Gel/Components/SuspendComponent.h b/Code/Gel/Components/SuspendComponent.h
new file mode 100644
index 0000000..08ad2f8
--- /dev/null
+++ b/Code/Gel/Components/SuspendComponent.h
@@ -0,0 +1,113 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SuspendComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_SUSPENDCOMPONENT_H__
+#define __COMPONENTS_SUSPENDCOMPONENT_H__
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#define		CRC_SUSPEND CRCD(0xce0ca665,"Suspend")
+#define		GetSuspendComponent() ((Obj::CSuspendComponent*)GetComponent(CRC_SUSPEND))
+#define		GetSuspendComponentFromObject(pObj) ((Obj::CSuspendComponent*)(pObj)->GetComponent(CRC_SUSPEND))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+
+
+              
+namespace Obj
+{
+
+	class	CModelComponent;
+
+
+namespace SuspendManager
+{
+
+class Manager  : public Spt::Class
+{
+	Tsk::Task< Manager > *mp_task;
+	static	Tsk::Task< Manager >::Code 	s_code;
+
+public:
+	Manager();
+	~Manager();
+	
+private:	
+	DeclareSingletonClass( Manager );
+};
+
+}
+
+
+
+class CSuspendComponent : public CBaseComponent
+{
+	friend class SuspendManager::Manager;
+	 
+public:
+    CSuspendComponent();
+    virtual ~CSuspendComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual	void					Finalize();	
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual	void					Suspend(bool suspend);
+	
+
+	void 							GetDebugInfo(Script::CStruct *p_info);
+	bool							SkipLogic();
+	bool 							SkipRender();
+
+	
+	static CBaseComponent*			s_create();
+	static void						s_register();
+
+public:		
+	void 							CheckModelActive();
+	float							GetDistanceSquaredToCamera();
+
+private:
+	bool							m_never_suspend;
+	float							m_suspend_distance;
+	float							m_suspend_distance_squared; // K: For a fast distance-squared check
+	float							m_camera_distance_squared;  // current distance from camera
+	int								m_no_suspend_count;	// number of initial frames we cannot suspend in
+
+	CModelComponent* 				mp_model_component;
+
+	bool							m_skip_logic;
+	bool							m_skip_render;
+	
+// public during the transition:
+public:
+//protected:
+	float							m_lod_dist[4];		// Mick: LOD is in the moving object for now....	
+	int								m_initial_animations;		// must do at least these initial animations
+
+protected:
+	int								m_interleave;	// Mick: counter for interleaving animation at a distance
+public:  // just for now	
+	bool							should_animate( float *p_dist = NULL );
+};
+
+
+
+
+}
+
+#endif
diff --git a/Code/Gel/Components/TriggerComponent.cpp b/Code/Gel/Components/TriggerComponent.cpp
new file mode 100644
index 0000000..1c43da7
--- /dev/null
+++ b/Code/Gel/Components/TriggerComponent.cpp
@@ -0,0 +1,288 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       TriggerComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/3/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CTriggerComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CTriggerComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTriggerComponent::CTriggerComponent() : CBaseComponent()
+{
+	SetType( CRC_TRIGGER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CTriggerComponent::~CTriggerComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_pos_last_frame = GetObject()->GetPos();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::Update()
+{
+	if (m_pos_last_frame == GetObject()->GetPos()) return;
+	
+	if (GetObject()->HasTeleported())
+	{
+		m_pos_last_frame = GetObject()->GetPos();
+		return;
+	}
+	
+	CFeeler feeler;
+	
+	feeler.m_start = m_pos_last_frame;
+	feeler.m_end = GetObject()->GetPos();
+	
+	feeler.SetIgnore(0, mFD_NON_COLLIDABLE | mFD_TRIGGER);
+	
+	feeler.SetCallback(s_through_trigger_callback);
+	feeler.SetCallbackData(this);
+	
+	feeler.GetCollision();
+	
+	m_pos_last_frame = GetObject()->GetPos();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CTriggerComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | TriggerType | checks if the trigger type is the 
+        // same as the specified type.  can also accept an array of types
+        // to check
+        // @uparmopt name | trigger type
+        // @uparmopt [] | array of trigger types
+		case CRCC(0x3a717382, "TriggerType"):
+		{
+			Script::CArray* pArray = NULL;
+			pParams->GetArray(NO_NAME, &pArray);
+			if (pArray)
+			{
+				for (int n = pArray->GetSize(); n--; )
+				{
+					if (m_latest_trigger_event_type == Script::GetInteger(pArray->GetChecksum(n))) return CBaseComponent::MF_TRUE;
+				}
+				return CBaseComponent::MF_FALSE;
+			}
+			else
+			{
+				int type;
+				pParams->GetInteger(NO_NAME, &type, Script::ASSERT);
+				return m_latest_trigger_event_type == type ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;   
+			} 	
+			break;
+		}
+		
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CTriggerComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::CheckFeelerForTrigger ( TriggerEventType type, CLineFeeler& feeler )
+{
+	if (!feeler.GetTrigger() || !feeler.GetNodeChecksum()) return;
+
+	if (feeler.GetCallbackObject())
+	{
+		CNodeArrayComponent* p_node_array_component = GetNodeArrayComponentFromObject(feeler.GetCallbackObject());
+		
+		TripTrigger(type, feeler.GetNodeChecksum(), p_node_array_component ? p_node_array_component->GetNodeArray() : NULL, feeler.GetCallbackObject());
+	}
+	else if (feeler.IsMovableCollision())
+	{
+		if (feeler.GetMovingObject())
+		{
+			CNodeArrayComponent* p_node_array_component = GetNodeArrayComponentFromObject(feeler.GetMovingObject());
+			TripTrigger(type, feeler.GetNodeChecksum(), p_node_array_component ? p_node_array_component->GetNodeArray() : NULL, feeler.GetMovingObject());
+		}
+	}
+	else
+	{
+		TripTrigger(type, feeler.GetNodeChecksum());
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::CheckFeelerForTrigger ( TriggerEventType type, CRectFeeler& feeler )
+{
+	for (int n = feeler.GetNumCollisionSurfaces(); n--; )
+	{
+		const Nx::S2DCollSurface& coll_surface = feeler.GetCollisionSurface(n);
+		if (!coll_surface.trigger) continue;
+		
+		// only trip the node's trigger if we haven't yet
+		int m;
+		for (m = n + 1; m < feeler.GetNumCollisionSurfaces(); m++)
+		{
+			const Nx::S2DCollSurface& handled_coll_surface = feeler.GetCollisionSurface(m);
+			if (!coll_surface.trigger) continue;
+			if (coll_surface.node_name == handled_coll_surface.node_name) break;
+		}
+		if (m == feeler.GetNumCollisionSurfaces())
+		{
+			TripTrigger(type, coll_surface.node_name);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::TripTrigger ( TriggerEventType type, uint32 node_checksum, Script::CArray* p_node_array, CCompositeObject* p_object)
+{
+	int node;
+	Script::CStruct* p_node = NULL;
+	
+	// find the triggered node
+	if (!p_node_array)
+	{
+		node = SkateScript::FindNamedNode(node_checksum);
+		Dbg_MsgAssert(node != -1, ("Can't find node %s, maybe .SCN out of sync with .QN ?",Script::FindChecksumName(node_checksum)));
+		p_node_array = Script::GetArray(CRCD(0xc472ecc5, "NodeArray"));
+		p_node = p_node_array->GetStructure(node);
+	}
+	else
+	{
+		for (node = p_node_array->GetSize(); node--; )
+		{
+			p_node = p_node_array->GetStructure(node);
+			
+			uint32 checksum;
+			p_node->GetChecksum(CRCD(0xa1dc81f9, "name"), &checksum);
+			if (checksum == node_checksum) break;
+		}
+	}
+	
+	Dbg_MsgAssert(node != -1, ("Cannot find node %s", Script::FindChecksumName(node_checksum)));
+	
+	// get the triggered object's trigger script parameters
+	Script::CStruct* p_model_trigger_script_params = NULL;
+	if (p_object)
+	{
+		Script::CStruct* p_object_node = Script::GetArray(CRCD(0xc472ecc5, "NodeArray"))->GetStructure(SkateScript::FindNamedNode(p_object->GetID()));
+		if (p_object_node)
+		{
+			p_object_node->GetStructure(CRCD(0x5180a4db, "ModelTriggerScriptParams"), &p_model_trigger_script_params);
+		}
+	}
+	
+	// get the trigger script
+	uint32 script_checksum = 0;
+	p_node->GetChecksum(CRCD(0x2ca8a299, "TriggerScript"), &script_checksum);
+	if (script_checksum == 0) return;
+	
+	// if this is a net game, don't trigger things from "Absent in netgame" nodes
+	if (Mdl::Skate::Instance()->ShouldBeAbsentNode(p_node)) return;
+	
+	m_latest_trigger_event_type = type;
+	
+	GetObject()->SpawnAndRunScript(
+		script_checksum,
+		node,
+		p_node->ContainsFlag(CRCD(0x20209c31, "NetEnabled")),
+		p_node->ContainsFlag(CRCD(0x23627fd7, "Permanent")),
+		p_model_trigger_script_params
+	);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTriggerComponent::s_through_trigger_callback ( CFeeler* p_feeler )
+{
+	static_cast< CTriggerComponent* >(p_feeler->GetCallbackData())->CheckFeelerForTrigger(TRIGGER_THROUGH, *p_feeler);
+}
+
+}
diff --git a/Code/Gel/Components/TriggerComponent.h b/Code/Gel/Components/TriggerComponent.h
new file mode 100644
index 0000000..6a135b2
--- /dev/null
+++ b/Code/Gel/Components/TriggerComponent.h
@@ -0,0 +1,85 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       TriggerComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/3/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_TRIGGERCOMPONENT_H__
+#define __COMPONENTS_TRIGGERCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#define		CRC_TRIGGER CRCD(0xe594f0a2, "Trigger")
+
+#define		GetTriggerComponent() ((Obj::CTriggerComponent*)GetComponent(CRC_TRIGGER))
+#define		GetTriggerComponentFromObject(pObj) ((Obj::CTriggerComponent*)(pObj)->GetComponent(CRC_TRIGGER))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	// trigger types for skating/walking
+	enum ESkaterTriggerType
+	{
+		TRIGGER_SKATE_OFF_EDGE = 1,
+		TRIGGER_JUMP_OFF,
+		TRIGGER_LAND_ON,
+		TRIGGER_SKATE_OFF,
+		TRIGGER_SKATE_ONTO,
+		TRIGGER_BONK,
+		TRIGGER_LIP_ON,
+		TRIGGER_LIP_OFF,
+		TRIGGER_LIP_JUMP,
+	};
+
+class CTriggerComponent : public CBaseComponent
+{
+	// users of CTriggerComponent define their own trigger event types, however 0 is reserved for the event of passing through trigger polys
+	enum
+	{
+		TRIGGER_THROUGH = 0
+	};
+
+public:
+	typedef int TriggerEventType;
+	
+public:
+    CTriggerComponent();
+    virtual ~CTriggerComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							CheckFeelerForTrigger ( TriggerEventType type, CLineFeeler& p_feeler );
+	void							CheckFeelerForTrigger ( TriggerEventType type, CRectFeeler& feeler );
+	void							TripTrigger ( TriggerEventType type, uint32 node_checksum, Script::CArray* p_node_array = NULL, CCompositeObject* p_object = NULL);
+	
+private:
+	static void						s_through_trigger_callback ( CFeeler* p_feeler );
+	
+private:
+	Mth::Vector						m_pos_last_frame;
+	TriggerEventType				m_latest_trigger_event_type;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/VehicleSoundComponent.cpp b/Code/Gel/Components/VehicleSoundComponent.cpp
new file mode 100644
index 0000000..edfe1f5
--- /dev/null
+++ b/Code/Gel/Components/VehicleSoundComponent.cpp
@@ -0,0 +1,553 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VehicleSoundComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  7/2/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPB(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a ? "true" : "false"); }
+#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPC(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, Script::FindChecksumName(a)); }
+#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
+#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
+#define DUMPM(a) { DUMP4((a)[X]); DUMP4((a)[Y]); DUMP4((a)[Z]); DUMP4((a)[W]); }
+#define DUMP2(a) { printf("D:%s:%i " #a " = ", __FILE__ + 15, __LINE__); for (int n = 32; n--; ) { printf("%c", ((a) & (1 << n)) ? '1' : '0'); } printf("\n"); }
+#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
+#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
+
+#ifdef __NOPT_ASSERT__
+	#define DEBUG_OUTPUT(a) \
+		{ \
+			if (Script::GetInteger(CRCD(0x54f22205, "DebugPlayerVehicleEngineSound")) \
+				|| Script::GetInteger(CRCD(0xdf03ebbf, "DebugPlayerVehicleTireSound"))) \
+			{ \
+				printf("[DebugEngineSound]: "); \
+				printf a; \
+				printf("\n"); \
+			} \
+		}
+	#define DEBUG_ENGINE(a) \
+		{ \
+			if (Script::GetInteger(CRCD(0x54f22205, "DebugPlayerVehicleEngineSound"))) \
+			{ \
+				printf("[DebugEngineSound]: "); \
+				printf a; \
+				printf("\n"); \
+			} \
+		}
+	#define DEBUG_TIRES(a) \
+		{ \
+			if (Script::GetInteger(CRCD(0xdf03ebbf, "DebugPlayerVehicleTireSound"))) \
+			{ \
+				printf("[DebugTireSound]: "); \
+				printf a; \
+				printf("\n"); \
+			} \
+		}
+	#define DEBUG_COLLISION(a) \
+		{ \
+			if (Script::GetInteger(CRCD(0xc2ddef08, "DebugPlayerVehicleCollisionSound"))) \
+			{ \
+				printf("[DebugCollisionSound]: "); \
+				printf a; \
+				printf("\n"); \
+			} \
+		}
+#else
+	#define DEBUG_OUTPUT(a)
+	#define DEBUG_ENGINE(a)
+	#define DEBUG_TIRES(a)
+	#define DEBUG_COLLISION(a)
+#endif
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CVehicleSoundComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CVehicleSoundComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CVehicleSoundComponent::CVehicleSoundComponent() : CBaseComponent()
+{
+	SetType( CRC_VEHICLESOUND );
+	
+	mp_vehicle_component = NULL;
+	mp_sound_component = NULL;
+	
+	m_sound_setup_struct = NULL;
+	
+	m_use_default_sounds = false;
+	
+	m_effective_speed = 0.0f;
+	m_effective_gear = 0;
+	m_gear_shift_time_stamp = 0;
+	m_airborne_time_stamp = 0;
+	m_effective_tire_slip_vel = 0.0f;
+	m_latest_collision_sound_time_stamp = 0;
+	
+	m_engine_sound_id = 0;
+	m_tire_sound_id = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CVehicleSoundComponent::~CVehicleSoundComponent()
+{
+	if (m_engine_sound_id)
+	{
+		Sfx::CSfxManager::Instance()->StopSound(m_engine_sound_id);
+		m_engine_sound_id = 0;
+	}
+	if (m_tire_sound_id)
+	{
+		Sfx::CSfxManager::Instance()->StopSound(m_tire_sound_id);
+		m_tire_sound_id = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_use_default_sounds = Ed::CParkEditor::Instance()->UsingCustomPark();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::Finalize()
+{
+	mp_vehicle_component = GetVehicleComponentFromObject(GetObject());
+	mp_sound_component = GetSoundComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_vehicle_component);
+	Dbg_Assert(mp_sound_component);
+	
+	fetch_script_parameters();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::Update()
+{
+	DEBUG_OUTPUT(("-- frame start --"));
+	
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0xcf5f4e16, "DynamicPlayerVehicleSound")))
+	{
+		fetch_script_parameters();
+	}
+	#endif
+	
+	if (!m_sound_setup_struct) return;
+	
+	update_engine_sounds();
+	
+	update_tire_sounds();
+	
+	update_collision_sounds();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CVehicleSoundComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CVehicleSoundComponent::GetDebugInfo"));
+	
+	p_info->AddChecksum(CRCD(0x2a19e830, "engine_sound"), m_engine_sound_checksum);
+	p_info->AddChecksum(CRCD(0xe798fe80, "tire_sound"), m_tire_sound_checksum);
+	p_info->AddStructure(CRCD(0x36fa68d2, "collide_sound"), m_collide_sound_struct);
+
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::update_engine_sounds (   )
+{
+	float frame_length = Tmr::FrameLength();
+	
+	CVehicleComponent::SWheel* p_wheels = mp_vehicle_component->mp_wheels;
+	
+	// determine the smallest of all the drive wheels' rotational velocity
+	float min_vel = 1e20f;
+	for (int n = CVehicleComponent::vVP_NUM_WHEELS; n--; )
+	{
+		if (!p_wheels[n].drive) continue;
+		min_vel = Mth::Min(min_vel, Mth::Abs(p_wheels[n].rotvel) * p_wheels[n].radius);
+	}
+	float frame_effective_speed = IPS_TO_MPH(min_vel);
+	
+	// adjust effective speed based on throttle position
+	bool throttle = (mp_vehicle_component->m_controls.throttle || mp_vehicle_component->m_controls.reverse) && !mp_vehicle_component->m_controls.brake;
+	if (!throttle)
+	{
+		frame_effective_speed *= get_global_param(CRCD(0xec67bc97, "idle_engine_effective_speed_factor"));
+	}
+	
+	// smooth out frame-to-frame fluxuations
+	m_effective_speed = Mth::Lerp(frame_effective_speed, m_effective_speed, get_global_param(CRCD(0x2a7095b, "effective_speed_lerp_rate")) * frame_length);
+	if (m_effective_speed < frame_effective_speed)
+	{
+		m_effective_speed = Mth::ClampMax(m_effective_speed + get_global_param(CRCD(0xca5efd91, "effective_speed_adjust_up_rate")) * frame_length, m_effective_speed);
+	}
+	else
+	{
+		m_effective_speed = Mth::ClampMin(m_effective_speed - get_global_param(CRCD(0x77c8e7c8, "effective_speed_adjust_down_rate")) * frame_length, m_effective_speed);
+	}
+	DEBUG_ENGINE(("speed: %.2f mph", m_effective_speed));
+	
+	// determine the appropriate gear for this frame
+	if (mp_vehicle_component->m_num_wheels_in_contact != 0)
+	{
+		if (m_effective_gear < m_num_gears - 1 && m_effective_speed > m_gears[m_effective_gear].upshift_point)
+		{
+			m_effective_gear++;
+			m_gear_shift_time_stamp = Tmr::GetTime();
+			DEBUG_ENGINE(("upshift to gear: %i", m_effective_gear));
+		}
+		else if (m_effective_gear > 0 && m_effective_speed < m_gears[m_effective_gear].downshift_point)
+		{
+			m_effective_gear--;
+			m_gear_shift_time_stamp = Tmr::GetTime();
+			DEBUG_ENGINE(("downshift to gear: %i", m_effective_gear));
+		}
+	}
+	else if (mp_vehicle_component->m_air_time > get_global_param(CRCD(0xc0f451d4, "engine_airborne_delay")))
+	{	 
+		// when airborne, drop to lowest gear and use no gear shift velocity damping
+		m_effective_gear = 0;
+	}
+	DEBUG_ENGINE(("gear: %i", m_effective_gear + 1));
+	
+	// calculate a volume adjustment based on the time since the last gear shift
+	float gear_shift_vol_factor = Mth::ClampMax(
+		Mth::LinearMap(
+			get_global_param(CRCD(0xbfa32092, "gear_shift_min_vol_factor")),
+			1.0f,
+			Tmr::ElapsedTime(m_gear_shift_time_stamp),
+			0.0f,
+			get_global_param(CRCD(0xfb6bd20e, "gear_shift_vol_adjustment_duration"))
+		),
+		1.0f
+	);
+		
+	// calculate this frame's engine rpm based on the current gear and speed
+	float frame_engine_rpm = Mth::LinearMap(
+		m_gears[m_effective_gear].bottom_rpm,
+		m_gears[m_effective_gear].top_rpm,
+		m_effective_speed,
+		m_effective_gear ? m_gears[m_effective_gear - 1].upshift_point : 0.0f,
+		m_gears[m_effective_gear].upshift_point
+	);
+	// DEBUG_ENGINE(("speed top: %.2f", m_gears[m_effective_gear].upshift_point));
+	// DEBUG_ENGINE(("speed bottom: %.2f", m_effective_gear ? m_gears[m_effective_gear - 1].upshift_point : 0.0f));
+	// DEBUG_ENGINE(("rpm top: %.2f", m_gears[m_effective_gear].top_rpm));
+	// DEBUG_ENGINE(("rpm bottom: %.2f", m_gears[m_effective_gear].bottom_rpm));
+	
+	// smooth out the engine rpm changes (causes nice shifting effects)
+	m_effective_engine_rpm = Mth::Lerp(
+		m_effective_engine_rpm,
+		frame_engine_rpm,
+		get_global_param(CRCD(0x3dd9c040, "engine_rpm_lerp_rate")) * frame_length
+	);
+	DEBUG_ENGINE(("engine RPM: %.2f", m_effective_engine_rpm));
+	
+	// calculate a number between 0.0f and 1.0f which will scale the engine sound's pitch and volume
+	float sound_factor = Mth::Clamp(
+		Mth::LinearMap(
+			0.0f,
+			1.0f,
+			m_effective_engine_rpm,
+			get_param(CRCD(0x05ac84a3, "MinEngineRPM")),
+			get_param(CRCD(0x95df9449, "MaxEngineRPM"))
+		),
+		0.0f,
+		1.0f
+	);
+	
+	// setup volume
+	Sfx::sVolume volume;
+	Sfx::CSfxManager::Instance()->SetVolumeFromPos(&volume, GetObject()->GetPos(), Sfx::CSfxManager::Instance()->GetDropoffDist(m_engine_sound_checksum));
+	float volume_percent = gear_shift_vol_factor * Mth::Lerp(
+		get_param(CRCD(0xb8f81277, "MinEngineVol")),
+		get_param(CRCD(0x288b029d, "MaxEngineVol")),
+		sound_factor
+	);
+	volume.PercentageAdjustment(volume_percent);
+	DEBUG_ENGINE(("engine volume: %.0f", volume_percent));
+	
+	// setup pitch
+	float pitch = Mth::Lerp(
+		get_param(CRCD(0x2660af3b, "MinEnginePitch")),
+		get_param(CRCD(0x9f46344a, "MaxEnginePitch")),
+		sound_factor
+	);
+	DEBUG_ENGINE(("engine pitch: %.0f", pitch));
+	
+	// play or adjust sound
+	if (!m_engine_sound_id)
+	{
+		m_engine_sound_id = Sfx::CSfxManager::Instance()->PlaySound(m_engine_sound_checksum, &volume, pitch, NULL);
+	}
+	else
+	{
+		Sfx::CSfxManager::Instance()->UpdateLoopingSound(m_engine_sound_id, &volume, pitch);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::update_tire_sounds (   )
+{
+	CVehicleComponent::SWheel* p_wheels = mp_vehicle_component->mp_wheels;
+	
+	// determine the largest of all the wheels' slip velocity
+	float max_slip_vel = 0.0f;
+	
+	if (mp_vehicle_component->m_controls.brake || mp_vehicle_component->m_controls.handbrake)
+	{
+		for (int n = CVehicleComponent::vVP_NUM_WHEELS; n--; )
+		{
+			// handbrake skid states do not depend on the tire's slip velocity, so we check the slip velocity ourselves
+			if ((p_wheels[n].state == CVehicleComponent::SWheel::SLIPPING || p_wheels[n].state == CVehicleComponent::SWheel::SKIDDING)
+				|| ((p_wheels[n].state == CVehicleComponent::SWheel::HANDBRAKE_LOCKED || p_wheels[n].state == CVehicleComponent::SWheel::HANDBRAKE_THROTTLE)
+				&& p_wheels[n].slip_vel > p_wheels[n].max_static_velocity))
+			{
+				max_slip_vel = Mth::Max(max_slip_vel, p_wheels[n].slip_vel);
+			}
+		}
+	}
+	
+	m_effective_tire_slip_vel = Mth::Lerp(
+		m_effective_tire_slip_vel,
+		max_slip_vel,
+		get_global_param(CRCD(0x95fbe774, "tire_slip_lerp_rate")) * Tmr::FrameLength()
+	);
+	DEBUG_TIRES(("tire slip velocity: %.2f", m_effective_tire_slip_vel));
+	
+	float sound_factor = Mth::Clamp(
+		m_effective_tire_slip_vel / get_param(CRCD(0x28f1aad0, "FullTireSlipVelocity")),
+		0.0f,
+		1.0f
+	);
+	
+	// setup volume
+	Sfx::sVolume volume;
+	Sfx::CSfxManager::Instance()->SetVolumeFromPos(&volume, GetObject()->GetPos(), Sfx::CSfxManager::Instance()->GetDropoffDist(m_tire_sound_checksum));
+	float volume_percent = Mth::Lerp(
+		get_param(CRCD(0x43e6cdf2, "MinTireVol")),
+		get_param(CRCD(0x42ea5746, "MaxTireVol")),
+		sound_factor
+	);
+	volume.PercentageAdjustment(volume_percent);
+	DEBUG_TIRES(("tire volume: %.0f", volume_percent));
+	
+	// setup pitch
+	float pitch = Mth::Lerp(
+		get_param(CRCD(0x76f8f76e, "MinTirePitch")),
+		get_param(CRCD(0xe68be784, "MaxTirePitch")),
+		sound_factor
+	);
+	DEBUG_TIRES(("tire pitch: %.0f", pitch));
+	
+	if (!volume.IsSilent())
+	{
+		if (!m_tire_sound_id)
+		{
+			m_tire_sound_id = Sfx::CSfxManager::Instance()->PlaySound(m_tire_sound_checksum, &volume, pitch, NULL);
+		}
+		else
+		{
+			Sfx::CSfxManager::Instance()->UpdateLoopingSound(m_tire_sound_id, &volume, pitch);
+		}
+	}
+	else if (m_tire_sound_id)
+	{
+		Sfx::CSfxManager::Instance()->StopSound(m_tire_sound_id);
+		m_tire_sound_id = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::update_collision_sounds (   )
+{
+	if (Tmr::ElapsedTime(m_latest_collision_sound_time_stamp) < get_global_param(CRCD(0x71b4b9fe, "collision_mute_delay"))) return;
+	
+	float sound_factor = mp_vehicle_component->m_max_normal_collision_impulse / get_param(CRCD(0xcea59d0b, "FullCollision"));
+	if (sound_factor > get_global_param(CRCD(0xf6981837, "collision_cutoff_factor")))
+	{
+		DEBUG_COLLISION(("collision: %.2f", mp_vehicle_component->m_max_normal_collision_impulse));
+		
+		m_collide_sound_struct->AddFloat(CRCD(0x9e497fc6, "Percent"), 100.0f * sound_factor);
+		mp_sound_component->PlayScriptedSound(m_collide_sound_struct);
+		
+		m_latest_collision_sound_time_stamp = Tmr::GetTime();
+		
+		Script::CStruct* p_params = new Script::CStruct;
+		p_params->AddFloat(CRCD(0x29461e27, "Strength"), Mth::Clamp(sound_factor, 0.0f, 1.0f));
+		mp_vehicle_component->mp_skater->SelfEvent(CRCD(0xc4a60423, "Vehicle_BodyCollision"), p_params);
+		delete p_params;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleSoundComponent::fetch_script_parameters (   )
+{
+	// get sound setup structure
+	Script::CStruct* sound_setups_struct = Script::GetStructure(CRCD(0x3c2da7f5, "PlayerVehicleSounds"), Script::ASSERT);
+	sound_setups_struct->GetStructure(mp_vehicle_component->GetSoundSetupChecksum(), &m_sound_setup_struct, Script::ASSERT);
+	
+	// extract sound checksum
+	m_sound_setup_struct->GetChecksum(CRCD(0x306f8a9f, "EngineSound"), &m_engine_sound_checksum);
+	m_sound_setup_struct->GetChecksum(CRCD(0x667fa96a, "TireSound"), &m_tire_sound_checksum);
+	
+	// extract gear information
+	
+	Script::CArray* gear_array;
+	m_sound_setup_struct->GetArray(CRCD(0x83273e6b, "Gears"), &gear_array, Script::ASSERT);
+	
+	m_num_gears = gear_array->GetSize();
+	Dbg_MsgAssert(m_num_gears <= vVS_MAX_NUM_GEARS, ("PlayerVehicleSound '%s' exceeds the maximum number of %i allowed gears", Script::FindChecksumName(mp_vehicle_component->GetSoundSetupChecksum()), vVS_MAX_NUM_GEARS));
+	for (int n = 0; n < m_num_gears; n++)
+	{
+		Script::CStruct* gear_struct = gear_array->GetStructure(n);
+		
+		gear_struct->GetFloat(CRCD(0xfad20901, "UpshiftPoint"), &m_gears[n].upshift_point, Script::ASSERT);
+		gear_struct->GetFloat(CRCD(0xfcf5c8a2, "DownshiftPoint"), &m_gears[n].downshift_point, Script::ASSERT);
+        gear_struct->GetFloat(CRCD(0x735b5ce4, "BottomRPM"), &m_gears[n].bottom_rpm, Script::ASSERT);
+        gear_struct->GetFloat(CRCD(0xe59a89a6, "TopRPM"), &m_gears[n].top_rpm, Script::ASSERT);
+		
+		Dbg_MsgAssert(m_gears[n].upshift_point > m_gears[n].downshift_point, ("In gear %i of PlayerVehicleSound '%s,' DownshiftPoint exceeds UpshiftPoint", n + 1, Script::FindChecksumName(mp_vehicle_component->GetSoundSetupChecksum())));
+		Dbg_MsgAssert(!n || m_gears[n].downshift_point < m_gears[n - 1].upshift_point, ("In gear %i of PlayerVehicleSound '%s', DownshiftPoint exceeds lower gear's UpshiftPoint", n + 1, Script::FindChecksumName(mp_vehicle_component->GetSoundSetupChecksum())));
+	}
+	
+	// extract collision sound structure
+	m_sound_setup_struct->GetStructure(CRCD(0xbf8e0ace, "CollideSound"), &m_collide_sound_struct, Script::ASSERT);
+	
+	// if we're in a create-a-goal, swap sound checksums for default sound checksums, as the true sounds may not be loaded
+	if (m_use_default_sounds)
+	{
+		Script::CStruct* global_sound_setup = Script::GetStructure(CRCD(0x38ec37ac, "PlayerVehicleSoundGlobalParameters"), Script::ASSERT);
+		
+		global_sound_setup->GetChecksum(CRCD(0xf5406e1a, "CAGEngineSound"), &m_engine_sound_checksum, Script::ASSERT);
+		global_sound_setup->GetChecksum(CRCD(0x60e93a2, "CAGTireSound"), &m_tire_sound_checksum, Script::ASSERT);
+		global_sound_setup->GetStructure(CRCD(0x22995285, "CAGCollideSound"), &m_collide_sound_struct, Script::ASSERT);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CVehicleSoundComponent::get_param ( uint32 checksum )
+{
+	Dbg_Assert(m_sound_setup_struct);
+	
+	float value;
+	m_sound_setup_struct->GetFloat(checksum, &value, Script::ASSERT);
+	return value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CVehicleSoundComponent::get_global_param ( uint32 checksum )
+{
+	Script::CStruct* global_sound_setup = Script::GetStructure(CRCD(0x38ec37ac, "PlayerVehicleSoundGlobalParameters"), Script::ASSERT);
+	
+	float value;
+	global_sound_setup->GetFloat(checksum, &value, Script::ASSERT);
+	return value;
+}
+
+}
diff --git a/Code/Gel/Components/VehicleSoundComponent.h b/Code/Gel/Components/VehicleSoundComponent.h
new file mode 100644
index 0000000..47ca357
--- /dev/null
+++ b/Code/Gel/Components/VehicleSoundComponent.h
@@ -0,0 +1,100 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VehicleSoundComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  7/2/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_VEHICLESOUNDCOMPONENT_H__
+#define __COMPONENTS_VEHICLESOUNDCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_VEHICLESOUND CRCD(0x28c97f3c, "VehicleSound")
+
+#define		GetVehicleSoundComponent() ((Obj::CVehicleSoundComponent*)GetComponent(CRC_VEHICLESOUND))
+#define		GetVehicleSoundComponentFromObject(pObj) ((Obj::CVehicleSoundComponent*)(pObj)->GetComponent(CRC_VEHICLESOUND))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CVehicleComponent;
+	class CSoundComponent;
+
+class CVehicleSoundComponent : public CBaseComponent
+{
+private:
+	static const int vVS_MAX_NUM_GEARS = 6;
+		
+public:
+    CVehicleSoundComponent();
+    virtual ~CVehicleSoundComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual	void 					Finalize();
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+private:
+	void							update_collision_sounds (   );
+	void							update_tire_sounds (   );
+	void							update_engine_sounds (   );
+	
+	void							fetch_script_parameters (   );
+	float							get_param ( uint32 checksum );
+	float							get_global_param ( uint32 checksum );
+	
+	
+private:
+	Script::CStruct*				m_sound_setup_struct;
+	
+	bool							m_use_default_sounds;
+	
+	uint32							m_engine_sound_id;
+	uint32							m_tire_sound_id;
+	Script::CStruct*				m_collide_sound_struct;
+	
+	uint32							m_engine_sound_checksum;
+	uint32							m_tire_sound_checksum;
+	
+	int								m_num_gears;
+	struct SGear
+	{
+		float						upshift_point;
+		float						downshift_point;
+		float						bottom_rpm;
+		float						top_rpm;
+	}								m_gears[vVS_MAX_NUM_GEARS];
+	
+	float							m_effective_speed;
+	int								m_effective_gear;
+	float							m_effective_engine_rpm;
+	Tmr::Time						m_gear_shift_time_stamp;
+	Tmr::Time						m_airborne_time_stamp;
+	
+	float							m_effective_tire_slip_vel;
+	
+	Tmr::Time						m_latest_collision_sound_time_stamp;
+	
+	// peer components
+	CVehicleComponent*				mp_vehicle_component;
+	CSoundComponent*				mp_sound_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/VelocityComponent.cpp b/Code/Gel/Components/VelocityComponent.cpp
new file mode 100644
index 0000000..c0c6139
--- /dev/null
+++ b/Code/Gel/Components/VelocityComponent.cpp
@@ -0,0 +1,205 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VelocityComponent.cpp
+//* OWNER:          SPG
+//* CREATION DATE:  7/10/03
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CVelocityComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CVelocityComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CVelocityComponent::CVelocityComponent() : CBaseComponent()
+{
+    SetType( CRC_VELOCITY );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CVelocityComponent::~CVelocityComponent()
+{   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CVelocityComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Mth::Vector vel;
+	
+	pParams->GetVector(CRCD(0xc4c809e, "vel"), &vel, Script::ASSERT);
+	GetObject()->SetVel( vel );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CVelocityComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	//InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVelocityComponent::Finalize()
+{
+	mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+}
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CVelocityComponent::Hide( bool should_hide )
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CVelocityComponent::Update()
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+	if (gamenet_man->InNetGame() || !mp_suspend_component->SkipLogic())
+	{
+		Mth::Vector pos, cur_pos, vel;
+
+		cur_pos = GetObject()->GetPos();
+		vel = GetObject()->GetVel();
+		pos = cur_pos + ( GetObject()->GetVel() * 0.5f * Tmr::FrameLength());
+		GetObject()->SetPos( pos );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CVelocityComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVelocityComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CVelocityComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+	
+
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/VelocityComponent.h b/Code/Gel/Components/VelocityComponent.h
new file mode 100644
index 0000000..945b2df
--- /dev/null
+++ b/Code/Gel/Components/VelocityComponent.h
@@ -0,0 +1,58 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VelocityComponent.h
+//* OWNER:          SPG
+//* CREATION DATE:  07/10/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_VELOCITYCOMPONENT_H__
+#define __COMPONENTS_VELOCITYCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_VELOCITY CRCD(0x41272956,"Velocity")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetVelocityComponent() ((Obj::CVelocityComponent*)GetComponent(CRC_VELOCITY))
+#define		GetVelocityComponentFromObject(pObj) ((Obj::CVelocityComponent*)(pObj)->GetComponent(CRC_VELOCITY))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CVelocityComponent : public CBaseComponent
+{
+public:
+    CVelocityComponent();
+    virtual ~CVelocityComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void					Hide( bool should_hide );
+	virtual void					Finalize();
+		
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+private:
+	CSuspendComponent*	mp_suspend_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/VibrationComponent.cpp b/Code/Gel/Components/VibrationComponent.cpp
new file mode 100644
index 0000000..33c19a5
--- /dev/null
+++ b/Code/Gel/Components/VibrationComponent.cpp
@@ -0,0 +1,339 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VibrationComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  2/25/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Obj
+{
+	
+// Component giving script control through a composite object over the input pad vibrators of the composite object's input handler.
+	
+// Only composite objects corresponding to local clients should be given this component.
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CVibrationComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CVibrationComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CVibrationComponent::CVibrationComponent() : CBaseComponent()
+{
+	SetType( CRC_VIBRATION );
+	
+	m_active_state = false;
+	
+	for (int n = vVB_NUM_ACTUATORS; n--; )
+	{
+		mp_vibration_timers[n].active = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CVibrationComponent::~CVibrationComponent()
+{
+	if (mp_input_device)
+	{
+		// turn off all vibration
+		for (int i = vVB_NUM_ACTUATORS; i--; )
+		{
+			mp_input_device->ActivateActuator(i, 0);
+		}
+		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::Update()
+{
+	Dbg_Assert(mp_input_device);
+	
+	if (!m_active_state)
+	{
+		Suspend(true);
+		return;
+	}
+	
+	// check the duration of active actuators
+	for (int i = vVB_NUM_ACTUATORS; i--; )
+	{
+		// actuator inactive or has no associated duration
+		if (!mp_vibration_timers[i].active) continue;
+		
+		// duration not yet up
+		if (Tmr::ElapsedTime(mp_vibration_timers[i].start_time) < mp_vibration_timers[i].duration) continue;
+		
+		// NOTE: ignoring all replay code in VibrationComponent for now
+		// Replay::WritePadVibration(i, 0);
+		
+		// turn off actuator
+		mp_input_device->ActivateActuator(i, 0);
+		
+		mp_vibration_timers[i].active = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CVibrationComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | Vibrate | 
+        // @parm int | Actuator | actuator num
+        // @parmopt float | Percent | 0 | 
+        // @flag Off | turn off both actuators
+        // @parmopt float | Duration | 0 | time to vibrate
+		case CRCC(0xdf8a5f0a, "Vibrate"):
+		{
+			// NOTE: ignoring pause state in VibrationComponent for now
+			// Don't let it be switched on if skater is paused
+			if (!pParams->ContainsFlag(CRCD(0xd443a2bc, "Off")) && (Mdl::FrontEnd::Instance()->GamePaused() || GetObject()->IsPaused())) break;
+		
+			Dbg_MsgAssert(mp_input_device, ("No associated input device"));
+			
+			if (!m_active_state) break;
+			
+			int actuator_idx = 0;
+			pParams->GetInteger(CRCD(0x114fc131, "actuator"), &actuator_idx);
+			Dbg_MsgAssert(actuator_idx >= 0 && actuator_idx < vVB_NUM_ACTUATORS, ("%s\nActuator must be between 0 and %d", pScript->GetScriptInfo(), vVB_NUM_ACTUATORS));
+
+			float percent = 0;
+			pParams->GetFloat(CRCD(0x9e497fc6, "percent"), &percent);
+			Dbg_MsgAssert(percent >= 0 && percent <= 100, ("\n%s\nPercent must be between 0 and 100", pScript->GetScriptInfo()));
+		
+			if (pParams->ContainsFlag("off"))
+			{
+				// This will switch off vibration, including if the game is paused.
+				// Ie, unpausing won't restore the old vibration. Hooray!
+				mp_input_device->StopAllVibrationIncludingSaved();
+				
+				for (int i = vVB_NUM_ACTUATORS; i--; )
+				{
+					mp_vibration_timers[i].active = false;
+				}
+				
+				// NOTE: ignoring all replay code in VibrationComponent for now
+				// for (int i = vVB_NUM_ACTUATORS; i--; )
+				// {
+					// Replay::WritePadVibration(i, 0);
+				// }
+			}	
+			else
+			{
+				// NOTE: ignoring all replay code in VibrationComponent for now
+				// Replay::WritePadVibration(Actuator, (int)Percent);
+				mp_input_device->ActivateActuator(actuator_idx, static_cast(percent));
+			}	
+		
+			// record duration so that we can later turn off the actuator; a duration of 0 gives an unbounded duration
+			float duration = 0;
+			if (pParams->GetFloat(CRCD(0x79a07f3f, "duration"), &duration))
+			{
+				mp_vibration_timers[actuator_idx].active = true;
+				mp_vibration_timers[actuator_idx].duration = static_cast(1000.0f * duration);
+				mp_vibration_timers[actuator_idx].start_time = Tmr::GetTime();
+			}
+			else
+			{
+				mp_vibration_timers[actuator_idx].active = false;
+			}
+			
+			break;
+		}
+		
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+	
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::GetDebugInfo ( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CVibrationComponent::GetDebugInfo"));
+	
+	p_info->AddInteger("m_active_state", m_active_state);
+	Script::CArray* p_timers_array = new Script::CArray;
+	p_timers_array->SetArrayType(vVB_NUM_ACTUATORS, ESYMBOLTYPE_STRUCTURE);
+	for (int i = vVB_NUM_ACTUATORS; i--; )
+	{
+		Script::CStruct* p_timer_structure = new Script::CStruct;
+		p_timer_structure->AddInteger("active", mp_vibration_timers[i].active);
+		p_timer_structure->AddInteger("duration", mp_vibration_timers[i].duration);
+		p_timers_array->SetStructure(i, p_timer_structure);
+	}
+	p_info->AddArray("mp_vibration_timers", p_timers_array);
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::StopAllVibration (   )
+{
+	if (!m_active_state) return;
+	
+	Dbg_Assert(mp_input_device);
+	
+	// NOTE: no real expanation for this, so I'm ignoring it until it breaks the code	
+	// #ifndef __PLAT_NGC__
+	for (int i = vVB_NUM_ACTUATORS; i--; )
+	{
+		// NOTE: ignoring all replay code in VibrationComponent for now
+		// Replay::WritePadVibration(i, 0);
+		
+		mp_input_device->ActivateActuator(i, 0);
+		
+		mp_vibration_timers[i].active = false;
+	}
+	// #endif // __PLAT_NGC__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::Reset (   )
+{
+	Dbg_Assert(mp_input_device);
+	
+	mp_input_device->ResetActuators();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::SetActive (   )
+{
+	if (m_active_state) return;
+	
+	m_active_state = true;
+	Suspend(false);
+	
+	if (!mp_input_device) return;
+	mp_input_device->EnableActuators();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::SetInactive (   )
+{
+	if (!m_active_state) return;
+	
+	m_active_state = false;
+	Suspend(true);
+	
+	if (!mp_input_device) return;
+	mp_input_device->StopAllVibrationIncludingSaved();
+	mp_input_device->DisableActuators();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::SetActiveState ( bool state )
+{
+	if (state == m_active_state) return;
+	
+	if (state)
+	{
+		SetActive();
+	}
+	else
+	{
+		SetInactive();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVibrationComponent::SetDevice ( SIO::Device* p_input_device )
+{
+	Dbg_Assert(p_input_device);
+	
+	mp_input_device = p_input_device;
+	
+	if (m_active_state)
+	{
+		mp_input_device->EnableActuators();
+	}
+	else
+	{
+		mp_input_device->DisableActuators();
+	}
+}
+
+}
diff --git a/Code/Gel/Components/VibrationComponent.h b/Code/Gel/Components/VibrationComponent.h
new file mode 100644
index 0000000..3ea5f2f
--- /dev/null
+++ b/Code/Gel/Components/VibrationComponent.h
@@ -0,0 +1,77 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VibrationComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  2/25/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_VIBRATIONCOMPONENT_H__
+#define __COMPONENTS_VIBRATIONCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_VIBRATION CRCD(0x404eb936, "Vibration")
+
+#define		GetVibrationComponent() ((Obj::CVibrationComponent*)GetComponent(CRC_VIBRATION))
+#define		GetVibrationComponentFromObject(pObj) ((Obj::CVibrationComponent*)(pObj)->GetComponent(CRC_VIBRATION))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CVibrationComponent : public CBaseComponent
+{
+public:
+	static const int vVB_NUM_ACTUATORS = 2;
+	
+private:	
+	// contains the information which defines the state of a vibration actuator
+	struct SVibrationTimer
+	{
+		bool active;
+		Tmr::Time start_time;
+		Tmr::Time duration;
+	};
+	
+public:
+    CVibrationComponent();
+    virtual ~CVibrationComponent();
+
+public:
+    virtual void            		Update (   );
+    virtual void            		InitFromStructure ( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure ( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction ( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo ( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create (   );
+	
+	void							StopAllVibration (   );
+	void							Reset (   );
+	void							SetActive (   );
+	void							SetInactive (   );
+	void							SetActiveState ( bool state );
+	void							SetDevice ( SIO::Device* p_input_device );
+	
+private:
+	bool							m_active_state;
+	
+	SVibrationTimer					mp_vibration_timers[vVB_NUM_ACTUATORS];
+	
+	SIO::Device*					mp_input_device;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/WalkCameraComponent.cpp b/Code/Gel/Components/WalkCameraComponent.cpp
new file mode 100644
index 0000000..a434ea5
--- /dev/null
+++ b/Code/Gel/Components/WalkCameraComponent.cpp
@@ -0,0 +1,589 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WalkCameraComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/9/3
+//****************************************************************************
+
+#ifdef TESTING_GUNSLINGER
+
+// Replace the entire contents of this file with the new file.
+#include 
+
+#else
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CWalkCameraComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CWalkCameraComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWalkCameraComponent::CWalkCameraComponent() : CBaseComponent()
+{
+	SetType( CRC_WALKCAMERA );
+	m_last_tripod_pos.Set();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CWalkCameraComponent::~CWalkCameraComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 target_id = 0 ;
+	pParams->GetChecksum("CameraTarget", &target_id, Script::ASSERT);
+	
+	CCompositeObject* p_target = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(target_id));
+	Dbg_MsgAssert(p_target, ("Bad CameraTarget given to WalkCameraComponent"));
+		
+	set_target(p_target);
+	
+	m_last_dot = 1.0f;
+	m_current_zoom = 1.0f;
+	
+	m_last_actual_matrix = GetObject()->GetMatrix();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::Finalize()
+{
+	mp_lookaround_component = GetCameraLookAroundComponentFromObject(GetObject());
+	mp_camera_component = GetCameraComponentFromObject(GetObject());
+	mp_skater_camera_component = GetSkaterCameraComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_lookaround_component);
+	Dbg_Assert(mp_camera_component);
+	Dbg_Assert(mp_skater_camera_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::Update()
+{
+	if (!mp_target) return;
+	
+	// optimization KLUDGE
+	if (mp_target_physics_control_component && mp_target_physics_control_component->IsDriving())
+	{
+		GetObject()->Pause(true);
+		return;
+	}
+	
+	if (mp_target->HasTeleported())
+	{
+		m_instant_count = 3;
+	}
+
+	bool instantly;
+	if (m_instant_count > 0)
+	{
+		--m_instant_count;
+		instantly = true;
+	}
+	else if (m_reset)
+	{
+		instantly = true;
+	}
+	else
+	{
+		instantly = false;
+	}
+	
+	mp_camera_component->StoreOldPosition();
+
+	float frame_length = Tmr::FrameLength();
+	
+	// get input
+	float horiz_control = GetInputComponentFromObject(GetObject())->GetControlPad().m_scaled_rightX;
+
+	// restore camera position from last frame, previous to refocusing and collision detection
+	GetObject()->GetMatrix() = m_last_actual_matrix;
+
+	Mth::Vector	target_facing = -GetObject()->GetMatrix()[Z];
+	target_facing[Y] = 0.0f;
+	target_facing.Normalize();
+	
+	Mth::Vector subject_facing = mp_target->GetMatrix()[Z];
+	subject_facing[Y] = 0.0f;
+	subject_facing.Normalize();
+	
+	// two options; we either use our old facing as the target facing or use the subject's facing as the target facing
+	bool use_subject_facing = true;
+	
+	// if in a flush request
+	if (m_flush_request_active)
+	{
+		// always use the subject's matrix
+	}
+	// if controlling camera
+	else if (horiz_control != 0.0f)
+	{
+		use_subject_facing = false;
+	}
+	// if the subject's facing is towards the camera
+	else if (Mth::DotProduct(target_facing, subject_facing) < cosf(Mth::DegToRad(s_get_param(CRCD(0xb8da8a73, "lock_angle")))))
+	{
+		use_subject_facing = false;
+	}
+	// if the subject is moving very slowly
+	else // if (!mp_target_walk_component->UseDPadCamera())
+	{
+		float full_slerp_speed;
+		float min_slerp_speed;
+		if (!mp_target_walk_component->UseDPadCamera())
+		{
+			full_slerp_speed = s_get_param(CRCD(0xbcef6dda, "full_slerp_speed"));
+			min_slerp_speed = s_get_param(CRCD(0x824349e5, "min_slerp_speed"));
+		}
+		else
+		{
+			full_slerp_speed = s_get_param(CRCD(0x73da1ec0, "dpad_full_slerp_speed"));
+			min_slerp_speed = s_get_param(CRCD(0xda5cad3, "dpad_min_slerp_speed"));
+		}
+		
+		float target_vel = sqrtf(mp_target->GetVel()[X] * mp_target->GetVel()[X] + mp_target->GetVel()[Z] * mp_target->GetVel()[Z]);
+		if (target_vel < full_slerp_speed)
+		{
+			use_subject_facing = false;
+			
+			// for a middle range of velocities, lerp to use of the subject's matrix
+			if (target_vel > min_slerp_speed)
+			{
+				target_facing = Mth::LinearMap(
+					target_facing,
+					subject_facing,
+					target_vel,
+					min_slerp_speed,
+					full_slerp_speed
+				);
+				target_facing.Normalize();
+			}
+		}
+	}
+	
+	if (use_subject_facing || m_reset)
+	{
+		target_facing = subject_facing;
+	}
+	
+	// control over target facing
+	if (horiz_control != 0.0f && !m_flush_request_active)
+	{
+		target_facing.RotateY(s_get_param(CRCD(0xf6f69dc8, "facing_control")) * horiz_control);
+	}
+	
+	if (m_override_active)
+	{
+		target_facing = m_override_facing;
+		target_facing.RotateY(mp_lookaround_component->mLookaroundHeading);
+	}
+	
+	// build target matrix
+	Mth::Matrix target_matrix;
+	target_matrix[Z] = target_facing;
+	target_matrix[Y].Set(0.0f, 1.0f, 0.0f);
+	target_matrix[X].Set(target_facing[Z], 0.0f, -target_facing[X]);
+	target_matrix[W].Set();
+	
+	// apply lookaround adjustments to the matrix
+	target_matrix.RotateXLocal(mp_skater_camera_component->mTilt + mp_lookaround_component->mLookaroundTilt);
+	
+	target_matrix[X] = -target_matrix[X];
+	target_matrix[Z] = -target_matrix[Z];
+	
+	// use later for camera position
+	Mth::Vector up = mp_target->GetMatrix()[Y];
+	
+	if (!instantly)
+	{
+		if (Mth::DotProduct(target_matrix[X], GetObject()->GetMatrix()[X]) > CAMERA_SLERP_STOP
+			&& Mth::DotProduct(target_matrix[Y], GetObject()->GetMatrix()[Y]) > CAMERA_SLERP_STOP
+			&& Mth::DotProduct(target_matrix[Z], GetObject()->GetMatrix()[Z]) > CAMERA_SLERP_STOP)
+		{
+			// we're already at our target, so don't do anything
+			
+			// turn off any flush request
+			if (m_flush_request_active)
+			{
+				m_flush_request_active = false;
+				mp_target_walk_component->SetForwardControlLock(false);
+			}
+		}
+		else
+		{
+			// slerp to the target matrix
+			
+			Mth::SlerpInterpolator slerper(&GetObject()->GetMatrix(), &target_matrix);
+			
+			// standard slerp rate
+			float slerp = s_get_param(CRCD(0xc39b639, "matrix_slerp_rate"));
+			
+			// running slerp rate adjustment
+			if (mp_target_walk_component->IsRunning())
+			{
+				slerp *= s_get_param(CRCD(0xd25348eb, "run_slerp_factor"));
+			}
+			
+			// animation wait and ladder slerp rate adjustment
+			// NOTE: replace with target facing override?
+			if (mp_target_walk_component->GetState() == CWalkComponent::WALKING_ANIMWAIT || mp_target_walk_component->GetState() == CWalkComponent::WALKING_LADDER)
+			{
+				slerp *= 2.0f;
+			}
+			
+			// if controlling the facing
+			if (horiz_control != 0.0f && !m_flush_request_active)
+			{
+				slerp *= s_get_param(CRCD(0xb2da3c87, "control_slerp_factor")) * Mth::Abs(horiz_control);
+			}
+			
+			// flush request slerp adjustment
+			if (m_flush_request_active)
+			{
+				slerp *= s_get_param(CRCD(0x7178e048, "flush_slerp_factor"));
+			}
+			
+			if (m_override_active)
+			{
+				// can't override flush speed
+				if (m_flush_request_active)
+				{
+					slerp = s_get_param(CRCD(0xc39b639, "matrix_slerp_rate")) * s_get_param(CRCD(0x7178e048, "flush_slerp_factor"));
+				}
+				else
+				{
+					slerp = m_override_slerp_rate;
+				}
+			}
+			
+			// apply the slerping
+			slerper.getMatrix(&GetObject()->GetMatrix(), GetTimeAdjustedSlerp(slerp, frame_length));
+
+			// calculate for the skater camera
+			m_last_dot = Mth::DotProduct(m_last_actual_matrix[Z], GetObject()->GetMatrix()[Z]);
+		}
+	}
+	else
+	{
+		GetObject()->GetMatrix() = target_matrix;
+	}
+	
+	// At this point, GetObject()->GetMatrix() is valid to store.
+	m_last_actual_matrix = GetObject()->GetMatrix();
+	
+	// Set camera position to be the same as the skater.
+	Mth::Vector	camera_pos = get_tripod_pos(instantly);
+	
+	// Calculate zoom
+	float above, behind;
+	calculate_zoom(above, behind);
+	
+	camera_pos += GetObject()->GetMatrix()[Z] * behind + up * above;
+	
+	Mth::Vector	focus_pos = mp_target->GetPos() + up * above;
+	
+	// Focus the camera directly on the target object
+	
+	target_matrix[Z] = focus_pos - camera_pos;
+	target_matrix[Z].Normalize();
+
+	// Read back the Y from the current matrix.
+	target_matrix[Y] = GetObject()->GetMatrix()[Y];
+
+	// Generate new orthonormal X and Y axes.
+	target_matrix[X] = Mth::CrossProduct(target_matrix[Y], target_matrix[Z]);
+	target_matrix[X].Normalize();
+	target_matrix[Y] = Mth::CrossProduct(target_matrix[Z], target_matrix[X]);
+	target_matrix[Y].Normalize();
+
+	// Write back into camera matrix.
+	// Since camera points in -Z, but player in +Z, we must negate the X and Z axes
+	GetObject()->GetMatrix()[X]	= -target_matrix[X];
+	GetObject()->GetMatrix()[Y] = target_matrix[Y];
+	GetObject()->GetMatrix()[Z] = -target_matrix[Z];
+	
+	// clean up matrix
+	GetObject()->GetMatrix()[X][W] = 0.0f;
+	GetObject()->GetMatrix()[Y][W] = 0.0f;
+	GetObject()->GetMatrix()[Z][W] = 0.0f;
+	GetObject()->GetMatrix()[W].Set(0.0f, 0.0f, 0.0f, 1.0f);
+	
+	// Now do collision detection.
+	ApplyCameraCollisionDetection(
+		camera_pos,
+		GetObject()->GetMatrix(),
+		camera_pos - GetObject()->GetMatrix()[Z] * behind + mp_target_walk_component->GetCameraCollisionTargetOffset(),
+		focus_pos
+	);
+	
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		Gfx::AddDebugStar(focus_pos, 24.0f, MAKE_RGB(255, 200, 0), 1);
+	}
+	#endif
+	
+	camera_pos[W] = 1.0f;
+	GetObject()->SetPos(camera_pos);
+	
+	// reset old position if in instant update
+	if (instantly)
+	{
+		mp_camera_component->StoreOldPosition();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CWalkCameraComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | WalkCamera_FlushRequest | Force the camera to lerp quickly to behind the walker
+		case CRCC(0x73febd0f, "WalkCamera_FlushRequest"):
+			FlushRequest();
+			break;
+			
+		// @script | WalkCamera_Reset | Teleports the camera to behind the target
+		case CRCC(0xd1a485d1, "WalkCamera_Reset"):
+			Reset();
+			break;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CWalkCameraComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::ReadyForActivation ( const SCameraState& state )
+{
+	Dbg_MsgAssert(mp_target, ("Walk camera (%s) has NULL target", Script::FindChecksumName(GetObject()->GetID())));
+	
+	m_last_tripod_pos = state.lastTripodPos;
+	m_last_actual_matrix = state.lastActualMatrix;
+	m_last_dot = state.lastDot;
+	m_current_zoom = state.lastZoom;
+	m_flush_request_active = false;
+	mp_target_walk_component->SetForwardControlLock(false);
+	m_instant_count = 0;
+	
+	mp_lookaround_component->mLookaroundHeading = 0.0f;
+	mp_lookaround_component->mLookaroundLock = false;
+	
+	m_override_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::GetCameraState ( SCameraState& state )
+{
+	state.lastActualMatrix = m_last_actual_matrix;
+	state.lastTripodPos = m_last_tripod_pos;
+	state.lastDot = m_last_dot;
+	state.lastZoom = m_current_zoom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::FlushRequest (   )
+{
+	m_flush_request_active = true;
+	mp_target_walk_component->SetForwardControlLock(true);
+	
+	// flush requests zero skater cam lookaround
+	mp_lookaround_component->mLookaroundHeading = 0.0f;
+	mp_lookaround_component->mLookaroundTilt = 0.0f;
+	mp_lookaround_component->mLookaroundZoom = 1.0f;
+	mp_lookaround_component->mLookaroundLock = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::Reset (   )
+{
+	m_reset = true;
+	Update();
+	m_reset = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::set_target ( CCompositeObject* p_target )
+{
+	if (p_target)
+	{
+		mp_target = p_target;
+		
+		mp_target_walk_component = GetWalkComponentFromObject(mp_target);
+		Dbg_Assert(mp_target_walk_component);
+		
+		mp_target_physics_control_component = GetSkaterPhysicsControlComponentFromObject(mp_target);
+		Dbg_Assert(mp_target_physics_control_component);
+	}
+	else
+	{
+		mp_target = NULL;
+		mp_target_walk_component = NULL;
+		mp_target_physics_control_component = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CWalkCameraComponent::get_tripod_pos( bool instantly )
+{
+	if (instantly)
+	{
+		m_last_tripod_pos = mp_target->GetPos();
+	}
+	else
+	{
+		float lerp_xz = GetTimeAdjustedSlerp(mp_skater_camera_component->mLerpXZ, Tmr::FrameLength());
+		float lerp_y = GetTimeAdjustedSlerp(mp_skater_camera_component->mLerpY, Tmr::FrameLength());
+		
+		const Mth::Vector& target_pos = mp_target->GetPos();
+
+		m_last_tripod_pos.Set(
+			Mth::Lerp(m_last_tripod_pos[X], target_pos[X], lerp_xz),
+			Mth::Lerp(m_last_tripod_pos[Y], target_pos[Y], lerp_y),
+			Mth::Lerp(m_last_tripod_pos[Z], target_pos[Z], lerp_xz)
+		);
+	}
+
+	return m_last_tripod_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkCameraComponent::calculate_zoom ( float& above, float& behind )
+{
+	float target_zoom = 1.0f;
+
+	// If lookaround override is set, factor in the lookaround override zoom.
+	if (mp_lookaround_component->mLookaroundOverride && mp_lookaround_component->mLookaroundZoom != 1.0f)
+	{
+		target_zoom *= mp_lookaround_component->mLookaroundZoom;
+	}
+	
+	m_current_zoom += ((target_zoom - m_current_zoom) * mp_skater_camera_component->mZoomLerp);
+	
+	behind = mp_skater_camera_component->mBehind * m_current_zoom;
+
+	// Behind is also shortened when the lookaround camera is tilting upwards.
+	if (mp_lookaround_component->mLookaroundTilt < 0.0f)
+	{
+		float max_tilt = 3.14f * 0.2f;
+		behind = behind * (0.4f + (0.6f * ((max_tilt + mp_lookaround_component->mLookaroundTilt) / max_tilt)));
+	}
+
+	// Use lip_trick_above when doing a lip trick.
+	float above_val = mp_skater_camera_component->mAbove;
+	
+	// Figure above tending towards the perfect above, if zoom is < 1.0.
+	if (m_current_zoom < 1.0f)
+	{
+		above = SKATERCAMERACOMPONENT_PERFECT_ABOVE + ((above_val - SKATERCAMERACOMPONENT_PERFECT_ABOVE) * m_current_zoom);
+	}
+	else
+	{
+		above = above_val;
+	}
+}
+
+}
+
+#endif // TESTING_GUNSLINGER
diff --git a/Code/Gel/Components/WalkCameraComponent.h b/Code/Gel/Components/WalkCameraComponent.h
new file mode 100644
index 0000000..0123958
--- /dev/null
+++ b/Code/Gel/Components/WalkCameraComponent.h
@@ -0,0 +1,147 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WalkCameraComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  4/9/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_WALKCAMERACOMPONENT_H__
+#define __COMPONENTS_WALKCAMERACOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#define		CRC_WALKCAMERA CRCD(0xc7d37ec2, "WalkCamera")
+
+#define		GetWalkCameraComponent() ((Obj::CWalkCameraComponent*)GetComponent(CRC_WALKCAMERA))
+#define		GetWalkCameraComponentFromObject(pObj) ((Obj::CWalkCameraComponent*)(pObj)->GetComponent(CRC_WALKCAMERA))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CWalkComponent;
+	class CCameraLookAroundComponent;
+	class CCameraComponent;
+	class CSkaterCameraComponent;
+	class CSkaterPhysicsControlComponent;
+	
+class CWalkCameraComponent : public CBaseComponent
+{
+public:
+    CWalkCameraComponent();
+    virtual ~CWalkCameraComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							ReadyForActivation ( const SCameraState& state );
+	void							GetCameraState ( SCameraState& state );
+	void							SetOverrides ( const Mth::Vector& facing, float slerp_rate );
+	void							UnsetOverrides (   );
+	
+	void							FlushRequest (   );
+	void							Reset (   );
+	
+	void							SetSkater ( CCompositeObject* p_skater );
+	
+private:
+	Mth::Vector						get_tripod_pos( bool instantly );
+	void							calculate_zoom ( float& above, float& behind );
+	
+	void							set_target ( CCompositeObject* p_target );
+	
+	float							s_get_param ( uint32 checksum );
+	
+private:
+	int								m_instant_count;
+	bool							m_flush_request_active;
+	bool							m_reset;
+	
+	float							m_current_zoom;
+	
+	Mth::Vector						m_last_tripod_pos;
+	Mth::Matrix						m_last_actual_matrix;
+	float							m_last_dot;
+	
+	bool							m_override_active;
+	Mth::Vector						m_override_facing;
+	float							m_override_slerp_rate;
+	
+	CCameraLookAroundComponent*		mp_lookaround_component;
+	CCameraComponent*				mp_camera_component;
+	CSkaterCameraComponent*			mp_skater_camera_component;
+	
+	CCompositeObject*				mp_target;
+	CWalkComponent*					mp_target_walk_component;
+	CSkaterPhysicsControlComponent*	mp_target_physics_control_component;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float CWalkCameraComponent::s_get_param ( uint32 checksum )
+{
+	Script::CStruct* p_walk_params = Script::GetStructure(CRCD(0x1cda02ae, "walk_camera_parameters"));
+	Dbg_Assert(p_walk_params);
+	
+	float param;
+	p_walk_params->GetFloat(checksum, ¶m, Script::ASSERT);
+	return param;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CWalkCameraComponent::SetOverrides ( const Mth::Vector& facing, float slerp_rate )
+{
+	m_override_active = true;
+	m_override_facing = facing;
+	m_override_slerp_rate = slerp_rate;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CWalkCameraComponent::UnsetOverrides (   )
+{
+	m_override_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CWalkCameraComponent::SetSkater ( CCompositeObject* p_skater )
+{
+	set_target(p_skater);
+	m_instant_count = 3;
+}
+
+}
+
+#endif
diff --git a/Code/Gel/Components/WalkComponent.cpp b/Code/Gel/Components/WalkComponent.cpp
new file mode 100644
index 0000000..e864a11
--- /dev/null
+++ b/Code/Gel/Components/WalkComponent.cpp
@@ -0,0 +1,3310 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WalkComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/2/3
+//****************************************************************************
+
+#ifdef TESTING_GUNSLINGER
+
+// Replace the entire contents of this file with the new file.
+#include 
+
+#else
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+	
+/*
+ * - Camera needs to initialize correctly at restarts.
+ * - Faster camera slerp, smaller no-slerp angle during run out.
+ * - Retain momentum better when leaving skating.
+ * - Accidental manuals when interacting with ledges and ladders.
+ * - Catching on curbs right after jump (use snap up code from skater)
+ * - Hang
+ *    - Jerky rail corners.
+ *    - Turn around on wire hangs.
+ *    - Drift to next rung effect in hanging too.
+ *    - Special pull up to wire animation.
+ * - Ladder
+ *    - Drift between rungs.
+ *    - Match rungs when getting on from the top.
+ *    - Match rungs when grabbing from air.
+ * - Hang from bottom of ladder.
+ * - Ladder to rail across top.  Rail across top to ladder.
+ * - Hand-plants, drop-ins, etc.
+ * BUGS:
+ * - Fall through verts with low frame rate.
+ * - Pop in transition from last to first frames of slow cycling (walk/climb) animations.
+ */
+ 
+extern bool g_CheatsEnabled;
+
+namespace Obj
+{
+	const uint32 CWalkComponent::sp_state_names [ CWalkComponent::NUM_WALKING_STATES ] =
+	{
+		CRCC(0x8cf3cb28, "WALKING_GROUND"),
+		CRCC(0x9d1f1a2c, "WALKING_AIR"),
+		CRCC(0x74ffc46d, "WALKING_HANG"),
+		CRCC(0x1cb1f465, "WALKING_LADDER"),
+		CRCC(0x79be4637, "WALKING_ANIMWAIT")
+	};
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CWalkComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CWalkComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWalkComponent::CWalkComponent() : CBaseComponent()
+{
+	SetType( CRC_WALK );
+	
+	mp_collision_cache = Nx::CCollCacheManager::sCreateCollCache();
+	
+	mp_input_component = NULL;
+	mp_animation_component = NULL;
+	mp_model_component = NULL;
+	mp_trigger_component = NULL;
+	mp_physics_control_component = NULL;
+	mp_movable_contact_component = NULL;
+	mp_state_component = NULL;
+	mp_core_physics_component = NULL;
+	
+	m_control_direction.Set();
+	m_in_air_drift_vel.Set();
+	m_rotate_upright_timer = 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CWalkComponent::~CWalkComponent()
+{
+	Nx::CCollCacheManager::sDestroyCollCache(mp_collision_cache);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::Finalize()
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	mp_animation_component = GetAnimationComponentFromObject(GetObject());
+	mp_model_component = GetModelComponentFromObject(GetObject());
+	mp_trigger_component = GetTriggerComponentFromObject(GetObject());
+	mp_physics_control_component = GetSkaterPhysicsControlComponentFromObject(GetObject());
+	mp_movable_contact_component = GetMovableContactComponentFromObject(GetObject());
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_input_component);
+	Dbg_Assert(mp_animation_component);
+	Dbg_Assert(mp_model_component);
+	Dbg_Assert(mp_trigger_component);
+	Dbg_Assert(mp_physics_control_component);
+	Dbg_Assert(mp_movable_contact_component);
+	Dbg_Assert(mp_state_component);
+	Dbg_Assert(mp_core_physics_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 camera_id;
+	if (pParams->GetChecksum(CRCD(0xc4e311fa, "camera"), &camera_id))
+	{
+		CCompositeObject* p_camera = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(camera_id));
+		Dbg_MsgAssert(mp_camera, ("No such camera object"));
+		SetAssociatedCamera(p_camera);
+	}
+	
+	#ifdef SCRIPT_WALK_DRAG
+	m_script_drag_factor = 1.0f;
+	#endif
+}
+		 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::Update()
+{
+	uint32 previous_frame_event = m_frame_event;
+	
+	// TEMP: debounce R1 after a transition
+	if (m_ignore_grab_button && !mp_input_component->GetControlPad().m_R1.GetPressed())
+	{
+		m_ignore_grab_button = false;
+	}
+	
+	// zero the frame event
+	m_last_frame_event = m_frame_event;
+	m_frame_event = 0;
+	
+	// get input
+    get_controller_input();
+	
+	// extract initial state for this frame from the object
+	extract_state_from_object();
+	
+	m_frame_start_pos = m_pos;
+	
+	// set the frame length
+	m_frame_length = Tmr::FrameLength();
+	
+	// go to our true Y position
+	m_curb_float_height_adjusted = false;
+	m_pos[Y] -= m_curb_float_height;
+	DUMP_WPOSITION
+	
+	// lerp down to standard run speed if we're not in a combo
+	update_run_speed_factor();
+	
+	// switch logic based on walking state
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+			go_on_ground_state();
+			break;
+
+		case WALKING_AIR:
+			go_in_air_state();
+			break;
+																	  
+		// case WALKING_HOP:
+			// go_hop_state();
+			// break;
+																	  
+		case WALKING_HANG:
+			go_hang_state();
+			break;
+            
+		case WALKING_LADDER:
+			go_ladder_state();
+            break;
+			
+		case WALKING_ANIMWAIT:
+			go_anim_wait_state (   );
+			break;
+	}
+	if (mp_physics_control_component->HaveBeenReset()) return;
+	
+	// the there's no curb to adjust due to, lerp down to zero
+	if (!m_curb_float_height_adjusted)
+	{
+		m_curb_float_height = Mth::Lerp(m_curb_float_height, 0.0f, s_get_param(CRCD(0x9b3388fa, "curb_float_lerp_down_rate")) * m_frame_length);
+	}
+	
+	// adjust back to our curb float Y position
+	m_pos[Y] += m_curb_float_height;
+	DUMP_WPOSITION
+	
+	// maybe transition into skating via an acid drop from the ground
+	if (m_state == WALKING_GROUND)
+	{
+		maybe_jump_to_acid_drop();
+		if (mp_physics_control_component->HaveBeenReset()) return;
+	}
+	
+	// adjust critical point offset
+	update_critical_point_offset();
+	
+	// adjust the display offset
+	update_display_offset();
+	
+	// keep the object from falling through holes in the geometry
+	if (m_state == WALKING_GROUND || m_state == WALKING_AIR)
+	{
+		uber_frig();
+	}
+	
+	// rotate to upright -- non-transition
+	lerp_upright();
+	
+	// setup the object based on this frame's walking
+	copy_state_into_object();
+	
+	// rotate to upright -- transition
+	transition_lerp_upright();
+	
+	// smooth out the anim speed over two frames to minimize poppy behavior
+	smooth_anim_speed(previous_frame_event);
+	
+	Dbg_Assert(m_frame_event);
+	GetObject()->SelfEvent(m_frame_event);
+	
+	// set the animation speeds
+	update_anim_speeds();
+	
+	// camera controls
+	set_camera_overrides();
+	
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		Gfx::AddDebugStar(GetObject()->GetPos(), 36.0f, MAKE_RGB(255, 255, 255), 1);
+		if (m_critical_point_offset.LengthSqr() != 0.0f)
+		{
+			Gfx::AddDebugStar(GetObject()->GetPos() + m_critical_point_offset, 36.0f, MAKE_RGB(150, 255, 100), 1);
+		}
+	}
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CWalkComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Walk_Ground |
+		case CRCC(0x893213e5, "Walk_Ground"):
+			return m_state == WALKING_GROUND ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_Air |
+		case CRCC(0x5012082e, "Walk_Air"):
+			return m_state == WALKING_AIR ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_Hang |
+		case CRCC(0x9a3ca853, "Walk_Hang"):
+			return m_state == WALKING_HANG ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_Ladder |
+		case CRCC(0x19702ca8, "Walk_Ladder"):
+			return m_state == WALKING_LADDER ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_AnimWait |
+		case CRCC(0x8fe2b013, "Walk_AnimWait"):
+			return m_state == WALKING_ANIMWAIT ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Walk_GetStateTime | Loads the time in milliseconds since last state change.
+		case CRCC(0xce64576c, "Walk_GetStateTime"):
+			pScript->GetParams()->AddInteger(CRCD(0x5ab23cc9, "StateTime"), Tmr::ElapsedTime(m_state_timestamp));
+			break;
+		
+		// @script | Walk_Jump |
+		case CRCC(0x83e4bd70, "Walk_Jump"):
+			jump();
+			break;
+		
+		#ifdef SCRIPT_WALK_DRAG
+		// @script | Walk_SetDragFactor |
+		case CRCC(0xc6100a7d, "Walk_SetDragFactor"):
+			pParams->GetFloat(NO_NAME, &m_script_drag_factor);
+			break;
+			
+		case CRCC(0x4e4fae43, "Walk_ResetDragFactor"):
+			m_script_drag_factor = 1.0f;
+			break;
+		#endif
+			
+		case CRCC(0xaf04b983, "Walk_GetSpeedScale"):
+		{
+			uint32 checksum;
+			if (m_anim_effective_speed < s_get_param(CRCD(0xf3649996, "max_slow_walk_speed")))
+			{
+				checksum = CRCD(0x1150cabb, "WALK_SLOW");
+			}
+			else if (m_anim_effective_speed < s_get_param(CRCD(0x6a5805d8, "max_fast_walk_speed")))
+			{
+				checksum = CRCD(0x131f2a2, "WALK_FAST");
+			}
+			else if (m_anim_effective_speed < s_get_param(CRCD(0x1c94cc9c, "max_slow_run_speed")))
+			{
+				checksum = CRCD(0x5606d106, "RUN_SLOW");
+			}
+			else
+			{
+				checksum = CRCD(0x4667e91f, "RUN_FAST");
+			}
+			pScript->GetParams()->AddChecksum(CRCD(0x92c388f, "SpeedScale"), checksum);
+			
+			break;
+		}
+		
+		// @script | Walk_ScaleAnimSpeed | Sets the manner in which the walk animations speeds should be scaled.
+		// @flag Off | No animation speed scaling.
+		// @flag Run | Scale animation speeds against running speed.
+		// @flag Walk | Scale animation speeds against walking speed.
+		case CRCC(0x56112c03, "Walk_ScaleAnimSpeed"):
+			if (pParams->ContainsFlag(CRCD(0xd443a2bc, "Off")))
+			{
+				if (m_anim_scale_speed != OFF)
+				{
+					m_anim_scale_speed = OFF;
+					mp_animation_component->SetAnimSpeed(1.0f, false, true);
+				}
+			}
+			else if (pParams->ContainsFlag(CRCD(0xaf895b3f, "Run")))
+			{
+				m_anim_scale_speed = RUNNING;
+			}
+			else if (pParams->ContainsFlag(CRCD(0x6384f1da, "HangMove")))
+			{
+				m_anim_scale_speed = HANGMOVE;
+			}
+			else if (pParams->ContainsFlag(CRCD(0xa2bfe505, "LadderMove")))
+			{
+				m_anim_scale_speed = LADDERMOVE;
+				
+				// force a server anim speed update
+				m_last_ladder_anim_speed = -1.0f;
+			}
+			else
+			{
+				Dbg_MsgAssert(false, ("Walk_ScaleAnimSpeed requires Off, Run, or Walk flag"));
+			}
+			
+			pParams->GetFloat(CRCD(0xb2d59baf, "StandardSpeed"), &m_anim_standard_speed);
+			break;
+			
+		// @script | Walk_AnimWaitComplete | Signal from script that the walk component should leave its animation wait
+		case CRCC(0x9d3eebe8, "Walk_AnimWaitComplete"):
+			anim_wait_complete();
+			break;
+				   			
+		// @script | Walk_GetHangInitAnimType | Determine which type of initial hang animation should be played
+		case CRCC(0xc6cd659e, "Walk_GetHangInitAnimType"):
+			// m_initial_hang_animation is set when the hang rail is filtered
+			pScript->GetParams()->AddChecksum(CRCD(0x85fa9ac4, "HangInitAnimType"), m_initial_hang_animation);
+			break;
+			
+		// @script | Walk_GetStateDuration | Returns the duration of the current state in StateDuration
+		case CRCC(0x55bf1cd6, "Walk_GetStateDuration"):
+			pScript->GetParams()->AddFloat(CRCD(0x4b88e4e4, "StateDuration"), (1.0f / 1000.0f) * Tmr::ElapsedTime(m_state_timestamp));
+			break;
+			
+		// @script | Walk_GetPreviousState | Returns the previous state in PreviousState
+		case CRCC(0xe9b1cde8, "Walk_GetPreviousState"):
+		{
+			pScript->GetParams()->AddChecksum(CRCD(0xf78635da, "PreviousState"), sp_state_names[m_previous_state]);
+			break;
+		}
+			
+		// @script | Walk_GetPreviousState | Returns the current state in State
+		case CRCC(0xac7b36c8, "Walk_GetState"):
+		{
+			pScript->GetParams()->AddChecksum(CRCD(0x5c6c2d04, "State"), sp_state_names[m_state]);
+			break;
+		}
+			
+		// @script | Walk_GetHangAngle | Returns the current angle of the hang rail
+		case CRCC(0x7e01b923, "Walk_GetHangAngle"):
+		{
+			Dbg_MsgAssert(mp_physics_control_component->IsWalking(), ("Called Walk_GetHangAngle when not in walk mode"));
+			Dbg_MsgAssert(m_state == WALKING_HANG, ("Called Walk_GetHangAngle when not hanging"));
+			
+			Mth::Vector rail_direction = (mp_rail_manager->GetPos(mp_rail_end) - mp_rail_manager->GetPos(mp_rail_start)).Normalize();
+			float angle = asinf(rail_direction[Y]);
+			if (Mth::CrossProduct(rail_direction, m_facing)[Y] < 0.0f)
+			{
+				angle = -angle;
+			}
+			pScript->GetParams()->AddFloat(CRCD(0xead7d286, "HangAngle"), Mth::RadToDeg(angle));
+			break;
+		}
+
+		/*
+		case CRCC(0x14e4985b, "Walk_CancelTransitionalMomentum"):
+			m_cancel_transition_momentum = true;
+			break;
+		*/
+		
+		/*
+		// @script | Walk_SuppressInAirControl | turn off in air velocity control for some number of seconds
+		case CRCC(0x30a9de5c, "Walk_SuppressInAirControl"):
+			pParams->GetFloat(NO_NAME, &m_in_air_control_suppression_timer, Script::ASSERT);
+			break;
+		*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CWalkComponent::GetDebugInfo"));
+	
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x58007c97, "GROUND"));
+			break;
+		case WALKING_AIR:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x439f4704, "AIR"));
+			break;
+		// case WALKING_HOP:
+			// p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0xf41aba21, "HOP"));
+			// break;										 
+		case WALKING_HANG:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x4194ecca, "HANG"));
+			break;										 
+        case WALKING_LADDER:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0xc84243da, "LADDER"));
+			break;										 
+        case WALKING_ANIMWAIT:
+			p_info->AddChecksum(CRCD(0x109b9260, "m_state"), CRCD(0x4fe6069c, "ANIMWAIT"));
+			break;										 
+	}
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::SetAssociatedCamera ( CCompositeObject* camera_obj )
+{
+	mp_camera = camera_obj;
+	Dbg_Assert(mp_camera);
+	mp_camera_component = GetWalkCameraComponentFromObject(mp_camera);
+	Dbg_MsgAssert(mp_camera_component, ("No WalkCameraComponent in camera object"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::ReadyWalkState ( bool to_ground_state )
+{
+	// setup the state in preparation for being in walking mode next object update
+	
+    // always reset the state timestamp
+    m_state_timestamp = Tmr::GetTime();
+	
+	if (GetObject()->GetMatrix()[Y][Y] > 0.999f)
+	{
+		m_rotate_upright_timer = 0.0f;
+	}
+	else
+	{
+		// if we're not upright, get ready to rotate to upright
+		if (GetObject()->GetMatrix()[Y][Y] > -0.999f)
+		{
+			m_rotate_upright_axis.Set(-GetObject()->GetMatrix()[Y][Z], 0.0f, GetObject()->GetMatrix()[Y][X]);
+			m_rotate_upright_angle = acosf(Mth::Clamp(GetObject()->GetMatrix()[Y][Y], -1.0f, 1.0f));
+		}
+		else
+		{
+			m_rotate_upright_axis = GetObject()->GetMatrix()[X];
+			m_rotate_upright_angle = Mth::PI;
+		}
+        
+		m_rotate_upright_timer = s_get_param(CRCD(0xb0675803, "rotate_upright_duration"));
+		
+		if (GetObject()->GetMatrix()[Y][Y] < 0.0f)
+		{
+			GetObject()->SetPos(GetObject()->GetPos() + 6.0f * GetObject()->GetMatrix()[Y]);
+			to_ground_state = false;
+		}
+	}
+
+	if (to_ground_state)
+	{
+		set_state(WALKING_GROUND);
+		
+		// will be incorrect for one frame
+		m_ground_normal.Set(0.0f, 1.0f, 0.0);
+		
+		m_last_ground_feeler_valid = false;
+		
+		GetObject()->GetVel()[Y] = 0.0f;
+		
+		m_disallow_acid_drops = false;
+
+		/*
+		if (GetObject()->GetVel().LengthSqr() > Mth::Sqr(450.0f))
+		{
+			m_cancel_transition_momentum = false;
+		}
+		else
+		{
+			m_cancel_transition_momentum = true;
+		}
+		*/
+	}
+	else
+	{
+		set_state(WALKING_AIR);
+		
+		// give a slight velocity boost when transitioning from vert air
+		if (mp_core_physics_component->GetFlag(VERT_AIR) && m_rotate_upright_timer != 0.0f)
+		{
+			Mth::Vector target_facing = GetObject()->GetMatrix()[Z];
+			target_facing.Rotate(m_rotate_upright_axis, m_rotate_upright_angle);
+			GetObject()->GetVel() += s_get_param(CRCD(0x17b37748, "initial_vert_vel_boost")) * target_facing;
+		}
+		
+		// set primary air direction in the direction of velocity
+		m_primary_air_direction = GetObject()->GetVel();
+		m_primary_air_direction[Y] = 0.0f;
+		float length = m_primary_air_direction.Length();
+		if (length < 0.001f)
+		{
+			// or facing
+			m_primary_air_direction = GetObject()->GetMatrix()[Z];
+			m_primary_air_direction[Y] = 0.0f;
+			length = m_primary_air_direction.Length();
+			if (length < 0.001f)
+			{
+				// or future facing
+				m_primary_air_direction = -GetObject()->GetMatrix()[Y];
+				m_primary_air_direction[Y] = 0.0f;
+				length = m_primary_air_direction.Length();
+			}
+		}
+		m_primary_air_direction /= length;
+		
+		leave_movable_contact_for_air(GetObject()->GetVel(), GetObject()->GetVel()[Y]);
+		
+		m_in_air_control_suppression_timer = 0.0f;
+		
+		m_false_wall.active = false;
+		
+		// you have to touch the ground at least once before acid dropping out of walking
+		m_disallow_acid_drops = mp_core_physics_component->GetFlag(VERT_AIR)
+			|| mp_core_physics_component->GetFlag(AIR_ACID_DROP_DISALLOWED);
+		
+		// m_cancel_transition_momentum = true;
+	}
+
+	m_curb_float_height = 0.0f;
+	
+	m_special_transition_data.type = NO_SPECIAL_TRANSITION;
+	
+	m_last_frame_event = 0;
+	
+	// TEMP: debounce R1 after a transition
+	m_ignore_grab_button = true;
+	
+	m_critical_point_offset.Set();
+	
+	m_display_offset = 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::CleanUpWalkState (   )
+{
+	mp_model_component->SetDisplayOffset(Mth::Vector(0.0f, 0.0f, 0.0f));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::CollideWithOtherSkaterLost ( CCompositeObject* p_other_skater )
+{
+	set_state(WALKING_AIR);
+	m_primary_air_direction = GetObject()->GetVel();
+	m_primary_air_direction[Y] = 0.0f;
+	m_primary_air_direction.Normalize();
+	
+	leave_movable_contact_for_air(GetObject()->GetVel(), GetObject()->GetVel()[Y]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::set_state ( EStateType state )
+{
+    if (state == m_state) return;
+	
+	m_previous_state = m_state;
+	m_state = state;
+	m_state_timestamp = Tmr::GetTime();
+	
+    if (m_previous_state == WALKING_GROUND || m_previous_state == WALKING_AIR)
+    {
+        m_wall_push_test.active = false;
+		m_run_toggle = false;
+    }
+	
+	if (m_previous_state == WALKING_AIR)
+	{
+		m_in_air_control_suppression_timer = 0.0f;
+	}
+
+	if (m_previous_state == WALKING_AIR)
+	{
+		m_in_air_drift_vel.Set();
+		
+		m_disallow_acid_drops = false;
+	}
+	
+	if (m_state == WALKING_GROUND)
+	{
+		m_control_pegged_duration = 10000.0f;
+		m_last_frame_controller_pegged = true;
+	}
+	
+	if (m_state == WALKING_ANIMWAIT)
+	{
+		m_offset_due_to_movable_contact.Set();
+	}
+	
+	if (m_state != WALKING_AIR)
+	{
+		m_false_wall.active = false;
+	}
+	
+	if (m_state == WALKING_GROUND || m_previous_state != WALKING_AIR)
+	{
+		m_drop_rotation_rate = false;
+	}
+	
+	if (m_state == WALKING_HANG)
+	{
+		m_hang_move_vel = 0.0f;
+	}
+	
+	if (m_state == WALKING_HANG && m_previous_state == WALKING_AIR)
+	{
+		CControlPad& control_pad = mp_input_component->GetControlPad();
+		control_pad.DebounceLeftAnalogUp(s_get_param(CRCD(0xac96d24b, "hang_control_debounce_time")));
+		control_pad.DebounceLeftAnalogDown(s_get_param(CRCD(0xac96d24b, "hang_control_debounce_time")));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::go_on_ground_state (   )
+{
+	account_for_movable_contact();
+	
+	setup_collision_cache();
+	
+	// calculate initial horizontal speed
+	float horizontal_speed = m_horizontal_vel.Length();
+	
+	calculate_horizontal_speed_and_facing(horizontal_speed);
+	
+	// only skid if you've had the controller pegged for a minimum duration
+	if (m_frame_event == CRCD(0x1d537eff, "Skid") && m_control_pegged_duration < s_get_param(CRCD(0x8e1cf27a, "pegged_duration_for_skid")))
+	{
+		m_frame_event = CRCD(0x9b46e749, "Stand");
+	}
+	
+	// calculate this frame's movement
+	m_horizontal_vel = horizontal_speed * m_facing;
+	
+	// prevent movement into walls
+	if (adjust_horizonal_vel_for_environment())
+	{
+		// turn to face newly adjusted velocity
+		adjust_facing_for_adjusted_horizontal_vel();
+	}
+	else
+	{
+		m_drop_rotation_rate = false;
+	}
+	
+	// if we are wall pushing, we may have decided to switch states during adjust_horizonal_vel_for_environment based on our environment
+	if (m_state != WALKING_GROUND || mp_physics_control_component->HaveBeenReset())
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	// apply movement for this frame
+	m_pos += m_horizontal_vel * m_frame_length;
+	DUMP_WPOSITION
+	
+	// snap up and down curbs and perhaps switch to air
+	respond_to_ground();
+	if (m_state != WALKING_GROUND || mp_physics_control_component->HaveBeenReset())
+	{
+		CFeeler::sClearDefaultCache();
+		return;
+	}
+	
+	adjust_curb_float_height();
+	
+	// insure that we do not slip through the cracks in the collision geometry which are a side-effect of moving collidable objects
+	if (CCompositeObject* p_inside_object = mp_movable_contact_component->CheckInsideObjects(m_pos, m_frame_start_pos))
+	{
+		MESSAGE("WALKING_GROUND, within moving object");
+		
+		// allow it to push us forward, causing a bit of a stumble
+		m_horizontal_vel = p_inside_object->GetVel();
+		m_horizontal_vel[Y] = 0.0f;
+		m_vertical_vel = 0.0f;
+		
+		float speed_sqr = m_horizontal_vel.LengthSqr();
+		if (speed_sqr > (10.0f * 10.0f))
+		{
+			m_facing = m_horizontal_vel / sqrtf(speed_sqr);
+		}
+	}
+	
+	CFeeler::sClearDefaultCache();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::calculate_horizontal_speed_and_facing ( float &horizontal_speed )
+{
+	if (m_control_pegged)
+	{
+		if (!m_last_frame_controller_pegged)
+		{
+			m_control_pegged_duration = 0.0f;
+		}
+		m_control_pegged_duration += m_frame_length;
+	}
+	m_last_frame_controller_pegged = m_control_pegged;
+	
+	// calculate user's desired speed
+	float desired_speed = calculate_desired_speed();
+	
+	#ifdef SCRIPT_WALK_DRAG
+	// adjust speed by the script set drag factor
+	desired_speed *= m_script_drag_factor;
+	#endif
+			   	
+	// setup frame's event
+	if (desired_speed <= 0.0f)
+	{
+		m_frame_event = CRCD(0x9b46e749, "Stand");
+	}
+	else
+	{
+		m_frame_event = CRCD(0xaf895b3f, "Run");
+	}
+
+	bool special_acceleration = false;
+	
+	// adjust facing based on input
+	
+	if (m_control_magnitude > 0.0f)
+	{
+		float dot = Mth::DotProduct(m_facing, m_control_direction);
+		
+		if ((horizontal_speed < s_get_param(CRCD(0x52582d5b, "max_rotate_in_place_speed")) && dot < cosf(Mth::DegToRad(s_get_param(CRCD(0x5dff96a4, "max_rotate_in_place_angle")))))
+			|| (horizontal_speed < s_get_param(CRCD(0xf1e97e45, "min_skid_speed")) && dot < -cosf(Mth::DegToRad(s_get_param(CRCD(0x2d571c0f, "max_reverse_angle"))))))
+		{
+			// low speed rotate to desired orientation with no speed change
+			
+			float delta_angle = Mth::DegToRad(s_get_param(CRCD(0xb557804b, "rotate_in_place_rate"))) * m_control_magnitude * m_frame_length;
+			bool left_turn = -m_facing[Z] * m_control_direction[X] + m_facing[X] * m_control_direction[Z] < 0.0f;
+			
+			if (!m_run_toggle || m_drop_rotation_rate)
+			{
+				delta_angle *= s_get_param(CRCD(0x7b446c98, "walk_rotate_factor"));
+			}
+			else
+			{
+				float cos_delta_angle = cosf(left_turn ? delta_angle : -delta_angle);
+				float sin_delta_angle = sinf(left_turn ? delta_angle : -delta_angle);
+				Mth::Vector new_facing;
+				new_facing[X] = cos_delta_angle * m_facing[X] + sin_delta_angle * m_facing[Z];
+				new_facing[Y] = 0.0f;
+				new_facing[Z] = -sin_delta_angle * m_facing[X] + cos_delta_angle * m_facing[Z];
+				
+				// kludge to stop popping when running at a wall using the dpad
+				CFeeler feeler;
+				feeler.m_start = m_pos;
+				feeler.m_start[Y] += s_get_param(CRCD(0x6da7f696, "feeler_height"));
+				feeler.m_end = feeler.m_start + s_get_param(CRCD(0x99978d2b, "feeler_length")) * new_facing;
+				if (feeler.GetCollision(false))
+				{
+					delta_angle *= s_get_param(CRCD(0x7b446c98, "walk_rotate_factor"));
+				}
+			}
+			
+			float cos_delta_angle = cosf(left_turn ? delta_angle : -delta_angle);
+			float sin_delta_angle = sinf(left_turn ? delta_angle : -delta_angle);
+			float adjusted_vel = cos_delta_angle * m_facing[X] + sin_delta_angle * m_facing[Z];
+			m_facing[Z] = -sin_delta_angle * m_facing[X] + cos_delta_angle * m_facing[Z];
+			m_facing[X] = adjusted_vel;
+			
+			// check for overturn
+			if (left_turn != (-m_facing[Z] * m_control_direction[X] + m_facing[X] * m_control_direction[Z] < 0.0f))
+			{
+				m_facing = m_control_direction;
+			}
+			
+			// no acceleration until we reach the desired orientation
+			special_acceleration = true;
+			
+			// setup the event
+			m_frame_event = left_turn ? CRCD(0xf28adbfc, "RotateLeft") : CRCD(0x912220f8, "RotateRight");
+		}
+		else
+		{
+			if (dot > -cosf(Mth::DegToRad(s_get_param(CRCD(0x2d571c0f, "max_reverse_angle")))))
+			{
+				// if the turn angle is soft
+				
+				float worse_factor = Mth::Lerp(
+					m_analog_control
+						? s_get_param(CRCD(0xa26760f5, "worse_worse_turn_factor"))
+						: s_get_param(CRCD(0xab9015bc, "dpad_worse_worse_turn_factor")),
+					m_analog_control
+						? s_get_param(CRCD(0x6cb2e5de, "worse_turn_factor"))
+						: s_get_param(CRCD(0x911f29d7, "dpad_worse_turn_factor")),
+					Mth::Clamp(2.0f * (1.0f - Mth::DotProduct(m_control_direction, m_facing)), 0.0f, 1.0f)
+				);
+				
+				// below a speed threshold, scale up the turn rate
+				float turn_factor;
+				if (horizontal_speed < s_get_param(CRCD(0x27815f69, "max_pop_speed")))
+				{
+					// quick turn
+					turn_factor = Mth::Lerp(
+						s_get_param(CRCD(0xb278405d, "best_turn_factor")),
+						worse_factor,
+						horizontal_speed / s_get_param(CRCD(0x27815f69, "max_pop_speed"))
+					);
+				}
+				else
+				{
+					// slower turn
+					turn_factor = worse_factor;
+				}
+				turn_factor *= m_control_magnitude;
+				
+				// exponentially approach the new facing
+				float turn_ratio = turn_factor * m_frame_length;
+				if (turn_ratio >= 1.0f)
+				{
+					m_facing = m_control_direction;
+				}
+				else
+				{
+					m_facing = Mth::Lerp(m_facing, m_control_direction, turn_ratio);
+					m_facing.Normalize();
+				}
+			}
+			else
+			{
+				// the turn angle is hard
+				
+				if (horizontal_speed > s_get_param(CRCD(0xf1e97e45, "min_skid_speed")))
+				{
+					// if moving fast enough to require a skidding stop
+					special_acceleration = true;
+					horizontal_speed -= s_get_param(CRCD(0x9661ed7, "skid_accel")) * m_frame_length;
+					horizontal_speed = Mth::ClampMin(horizontal_speed, 0.0f);
+					m_frame_event = CRCD(0x1d537eff, "Skid");
+				}
+				else
+				{
+					// if max_rotate_in_place_speed is larger than min_skid_speed and max_reverse_angle points farther in reverse than
+					// max_rotate_in_place_angle, as they should, then this code should never be run
+					Dbg_Message("Unexpected state in CWalkComponent::calculate_horizontal_speed_and_facing");
+					
+					// to be safe, pop to the new facing
+					m_facing = m_control_direction;
+				}
+			}
+		}
+	}
+	
+	if (special_acceleration) return;
+	
+	// store desired speed for animation speed scaling
+	m_anim_effective_speed = desired_speed;
+	
+	// adjust desired speed for slope
+	desired_speed = adjust_desired_speed_for_slope(desired_speed);
+	
+	// linear acceleration; exponential deceleration
+	if (horizontal_speed > desired_speed)
+	{
+		float decel_factor = s_get_param(CRCD(0xacfa4e0c, "decel_factor"));
+		
+		if (desired_speed == 0.0f)
+		{
+			if (horizontal_speed > s_get_param(CRCD(0x79d182ad, "walk_speed")))
+			{
+				m_frame_event = CRCD(0x1d537eff, "Skid");
+			}
+			else
+			{
+				if (m_last_frame_event == CRCD(0x1d537eff, "Skid") && horizontal_speed > s_get_param(CRCD(0x311d02b2, "stop_skidding_speed")))
+				{
+					m_frame_event = CRCD(0x1d537eff, "Skid");
+				}
+				else
+				{
+					decel_factor = s_get_param(CRCD(0xa2aa811, "low_speed_decel_factor"));
+				}
+			}
+		}
+		
+		/* needs reworked, at the least
+		if (mp_physics_control_component->GetStateSwitchTime() == m_state_timestamp
+			&& Tmr::ElapsedTime(mp_physics_control_component->GetStateSwitchTime()) < 400.0f
+			&& !m_cancel_transition_momentum
+			&& desired_speed < s_get_param(CRCD(0x79d182ad, "walk_speed")))
+		{
+			decel_factor = 1.0f;
+			if (horizontal_speed > Script::GetFloat("start_stand"))
+			{
+				m_frame_event = CRCD(0x1d537eff, "Skid");
+			}
+			else
+			{
+				m_frame_event = CRCD(0x9b46e749, "Stand");
+			}
+		}
+		*/
+		
+		horizontal_speed = Mth::Lerp(horizontal_speed, desired_speed, decel_factor * m_frame_length);
+	}
+	else
+	{
+		if (m_run_toggle)
+		{
+			horizontal_speed += s_get_param(CRCD(0x4f47c998, "run_accel_rate")) * m_frame_length;
+		}
+		else
+		{
+			horizontal_speed += s_get_param(CRCD(0x6590a49b, "walk_accel_rate")) * m_frame_length;
+		}
+		if (horizontal_speed > desired_speed)
+		{
+			horizontal_speed = desired_speed;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool  CWalkComponent::adjust_horizonal_vel_for_environment (   )
+{
+	// We send out feeler rays to find nearby walls.  We limit velocity to be flush with the first wall found.  If two or more non-parallel walls
+	// are found, velocity is zeroed.
+	
+	float feeler_length = s_get_param(CRCD(0x99978d2b, "feeler_length"));
+	float feeler_height = s_get_param(CRCD(0x6da7f696, "feeler_height"));
+	
+	CFeeler feeler;
+	
+	bool contact = false;
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		// setup the the feeler
+		
+		if (n == vNUM_FEELERS)
+		{
+			// final feeler is for air state only and is at the feet; solves situations in which the feet impact with vertical surfaces which the
+			// wall feelers are too high to touch
+			if (m_state != WALKING_AIR)
+			{
+				mp_contacts[vNUM_FEELERS].in_collision = false;
+				continue;
+			}
+			
+			feeler.m_start = m_pos + m_critical_point_offset;
+			feeler.m_end = feeler.m_start + m_horizontal_vel * m_frame_length;
+			feeler.m_end[Y] += m_vertical_vel * m_frame_length + 0.5f * -get_gravity() * Mth::Sqr(m_frame_length);
+		}
+		else
+		{
+			feeler.m_start = m_pos;
+			feeler.m_start[Y] += feeler_height;
+			feeler.m_end = m_pos + feeler_length * calculate_feeler_offset_direction(n);
+			feeler.m_end[Y] += feeler_height;
+		}
+		
+		mp_contacts[n].in_collision = feeler.GetCollision();
+		
+		if (!mp_contacts[n].in_collision)
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(0, 0, 255, 1);
+			}
+			#endif
+			continue;
+		}
+		
+		if (n == vNUM_FEELERS)
+		{
+			// feet collisions only count for walls
+			if (feeler.GetNormal()[Y] > 0.707f)
+			{
+				mp_contacts[n].in_collision = false;
+				continue;
+			}
+		}
+		
+		// store the feeler
+		mp_contacts[n].feeler = feeler;
+		
+		contact = true;
+		
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+
+		// grab the horizontal normal of the contacted wall
+		mp_contacts[n].normal = feeler.GetNormal();
+		mp_contacts[n].normal[Y] = 0.0f;
+		mp_contacts[n].normal.Normalize();
+		
+		// if we're on the moving object, don't count its movement when doing collision detection, as the walker's velocity is already measured
+		// relative to its movable contact's
+		if (feeler.IsMovableCollision()
+			&& (!mp_movable_contact_component->HaveContact() || mp_movable_contact_component->GetContact()->GetObject() != feeler.GetMovingObject()))
+		{
+			mp_contacts[n].movement = Mth::DotProduct(feeler.GetMovingObject()->GetVel(), mp_contacts[n].normal);
+		}
+		else
+		{
+			mp_contacts[n].movement = 0.0f;
+		}
+	}
+	
+	// check for wall push
+	if (m_state == WALKING_GROUND)
+	{
+		if (check_for_wall_push())
+		{
+			// if we're wall pushing, we may decide to switch states based on our environment
+			
+			/* no auto-hop-to-hang
+			if (Tmr::ElapsedTime(m_wall_push_test.test_start_time) > s_get_param(CRCD(0x928e6775, "hop_delay")))
+			{
+				if (maybe_climb_up_ladder() || maybe_jump_low_barrier()) return false;
+			}
+			else
+			*/
+			if (Tmr::ElapsedTime(m_wall_push_test.test_start_time) > s_get_param(CRCD(0x38d36700, "barrier_jump_delay")))
+			{
+				if (maybe_climb_up_ladder() || maybe_jump_low_barrier()) return false;
+			}
+		}
+		
+		if (mp_input_component->GetControlPad().m_R1.GetPressed() && !m_ignore_grab_button)
+		{
+			if (maybe_climb_up_ladder(true)/* || maybe_hop_to_hang()*/) return false;
+		}
+		
+		if (mp_physics_control_component->HaveBeenReset()) return false;
+	}
+	
+	// push down steep slopes
+	if (m_state == WALKING_GROUND && m_ground_normal[Y] < 0.5f)
+	{
+		Mth::Vector push_dir = m_ground_normal;
+		push_dir[Y] = 0.0f;
+		push_dir.Normalize();
+		m_pos += s_get_param(CRCD(0x4d16f37d, "push_strength")) * m_frame_length * push_dir;
+		DUMP_WPOSITION
+	}
+	
+	if (!contact) return false;
+	
+	// push away from walls
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		                
+		if (mp_contacts[n].feeler.GetDist() < s_get_param(CRCD(0xa20c43b7, "push_feeler_length")) / feeler_length)
+		{
+			m_pos += s_get_param(CRCD(0x4d16f37d, "push_strength")) * m_frame_length * mp_contacts[n].normal;
+			DUMP_WPOSITION
+		}
+	}
+	
+	// from here on we ignore collisions we're moving out of
+	contact = false;
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		
+		// don't count collisions we're moving out of
+		if (Mth::DotProduct(mp_contacts[n].normal, m_horizontal_vel) >= mp_contacts[n].movement)
+		{
+			mp_contacts[n].in_collision = false;
+		}
+		else
+		{
+			contact = true;
+		}
+	}
+	if (!contact) return false;
+	
+	// Now we calculate how our movement is effected by our collisions.  The movement must have a non-negative dot product with all collision normals.
+	// The algorithm used should be valid for all convex environments.
+	
+	// if any of the colllision normals are more than right angles to one another, no movement is possible
+	// NOTE: not valid with movable contacts; could cause jerky movement in corners where walls are movable
+	for (int n = 0; n < vNUM_FEELERS + 1; n++)
+	{
+		if (!mp_contacts[n].in_collision) continue;
+		for (int m = n + 1; m < vNUM_FEELERS + 1; m++)
+		{
+			if (!mp_contacts[m].in_collision) continue;
+			if (Mth::DotProduct(mp_contacts[n].normal, mp_contacts[m].normal) <= 0.0f)
+			{
+				m_horizontal_vel.Set();
+				m_anim_effective_speed = Mth::Min(s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+				m_drop_rotation_rate = true;
+				return true;
+			}
+		}
+	}
+	
+	// direction of proposed movement
+	Mth::Vector movement_direction = m_horizontal_vel;
+	movement_direction.Normalize();
+	
+	Mth::Vector adjusted_vel = m_horizontal_vel;
+	
+	// loop over the contacts (from backward to forward)
+	const int p_contact_idxs [ vNUM_FEELERS + 1 ] = { 7, 4, 3, 5, 2, 6, 1, 0 };
+	for (int i = 0; i < vNUM_FEELERS + 1; i++)
+	{
+		int n = p_contact_idxs[i];
+		
+		if (!mp_contacts[n].in_collision) continue;
+		
+		// check to see if the movement still violates this constraint
+		float normal_vel = Mth::DotProduct(adjusted_vel, mp_contacts[n].normal);
+		if (normal_vel >= mp_contacts[n].movement) continue;
+		
+		// adjust the movement to the closest direction allowed by this contraint
+		adjusted_vel -= (normal_vel - mp_contacts[n].movement) * mp_contacts[n].normal;
+		
+		// if the mvoement direction no longer points in the direction of the proposed movement, no movement occurs
+		if (Mth::DotProduct(adjusted_vel, m_horizontal_vel) <= 0.0f)
+		{
+			m_horizontal_vel.Set();
+			m_anim_effective_speed = Mth::Min(s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+			m_drop_rotation_rate = true;
+			return true;
+		}
+	}
+	
+	// insure that the adjusted velocity in the final direction is not larger than the projection of the initial velocity into that direction
+	float adjusted_speed = adjusted_vel.Length();
+	Mth::Vector adjusted_vel_direction = adjusted_vel;
+	adjusted_vel_direction *= 1.0f / adjusted_speed;
+	float projected_vel = Mth::DotProduct(m_horizontal_vel, adjusted_vel_direction);
+	
+	if (adjusted_speed > projected_vel)
+	{
+		adjusted_vel = adjusted_vel_direction * projected_vel;
+	}
+	
+	m_drop_rotation_rate = adjusted_vel.LengthSqr() / m_horizontal_vel.LengthSqr() < Mth::Sqr(0.5f);
+	
+	// only the velocity along the movement direction is retained
+	m_horizontal_vel = adjusted_vel;
+	
+	float final_horiz_vel = m_horizontal_vel.Length();
+	if (m_anim_effective_speed > s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")))
+	{
+		m_anim_effective_speed = final_horiz_vel;
+		m_anim_effective_speed = Mth::Max(s_get_param(CRCD(0xbd6a05d, "min_anim_run_speed")), m_anim_effective_speed);
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::adjust_facing_for_adjusted_horizontal_vel (   )
+{
+	// We adjust facing due to adjustment in horizontal velocity due to environment.  Basically, we want to object to turn to face the velocity
+	// that the environment has forced upon it.
+	
+	// IDEA: shift to basing turn amount on angle difference and not speed
+	
+	float horizontal_speed = m_horizontal_vel.Length();
+	
+	if (horizontal_speed < s_get_param(CRCD(0x515a933, "wall_turn_speed_threshold"))) return;
+	
+	// the new facing is in the direction of our adjusted velocity
+	Mth::Vector new_facing = m_horizontal_vel;
+	new_facing.Normalize();
+	
+	// smoothly transition between no wall turning to full wall turning
+	float turn_ratio;
+	if (horizontal_speed > s_get_param(CRCD(0xe6c1cd0d, "max_wall_turn_speed_threshold")))
+	{
+		turn_ratio = s_get_param(CRCD(0x7a583b9b, "wall_turn_factor")) * m_frame_length;
+	}
+	else
+	{
+		turn_ratio = Mth::LinearMap(
+			0.0f,
+			s_get_param(CRCD(0x7a583b9b, "wall_turn_factor")) * m_frame_length,
+			horizontal_speed,
+			s_get_param(CRCD(0x0515a933, "wall_turn_speed_threshold")),
+			s_get_param(CRCD(0xe6c1cd0d, "max_wall_turn_speed_threshold"))
+		);
+	}
+	
+	// exponentially approach new facing
+	if (turn_ratio >= 1.0f)
+	{
+		m_facing = new_facing;
+	}
+	else
+	{
+		m_facing = Mth::Lerp(m_facing, new_facing, turn_ratio);
+		m_facing.Normalize();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWalkComponent::adjust_desired_speed_for_slope ( float desired_speed )
+{
+	// Slow velocity up and down slopes.
+	
+	// skip if there is no appreciable slope
+	if (m_ground_normal[Y] > 0.95f) return desired_speed;
+	
+	// skip if not running
+	if (desired_speed <= s_get_param(CRCD(0x79d182ad, "walk_speed"))) return desired_speed;
+	
+	// calculate a horizontal vector up the slope
+	Mth::Vector up_slope = m_ground_normal;
+	up_slope[Y] = 0.0f;
+	up_slope.Normalize();
+	
+	// horizontal factor of velocity if the velocity were pointing along the slope (instead of along the horizontal)
+	float movement_factor = m_ground_normal[Y];
+	
+	// factor of velocity pointing up the slope
+	float dot = Mth::Abs(Mth::DotProduct(m_facing, up_slope));
+	
+	// scale the up-the-slope element of velocity based on the slope strength
+	return (1.0f - dot) * desired_speed + dot * movement_factor * desired_speed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::respond_to_ground (   )
+{
+	// Look for the ground below us.  If we find it, snap to it.  If not, go to air state.
+	
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += s_get_param(CRCD(0xcee3a3e1, "snap_up_height"));
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] -= s_get_param(CRCD(0xaf3e4251, "snap_down_height"));
+	
+	if (!feeler.GetCollision() || (feeler.GetFlags() & mFD_NOT_SKATABLE))
+	{
+		if (feeler.GetCollision())
+		{
+			m_pos = feeler.GetPoint() + 0.1f * feeler.GetNormal();
+			DUMP_WPOSITION
+		}
+		
+		// no ground
+		
+		if (m_last_ground_feeler_valid)
+		{
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
+			if (mp_physics_control_component->HaveBeenReset()) return;
+		}
+		
+		if (mp_input_component->GetControlPad().m_triangle.GetPressed())
+		{
+			// need to give the player a change to rail before climbing down ladders and such
+			
+			// if we just climbed up to a rail and are immediately falling, we need some minute amount of movement in order to find a rail
+			if (m_pos == m_frame_start_pos)
+			{
+				m_frame_start_pos[Y] += 0.001f;
+			}
+			
+			if (maybe_stick_to_rail()) return;
+		}
+		
+		if (maybe_climb_down_ladder() || maybe_drop_to_hang()) return;
+		
+		// go to air state
+		set_state(WALKING_AIR);
+		m_primary_air_direction = m_facing;
+		leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+		m_frame_event = CRCD(0xabf1f6ac, "WalkOffEdge");
+		GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
+		return;
+	}
+	
+	float snap_distance = feeler.GetPoint()[Y] - m_pos[Y];
+	
+	// no not send event for very small snaps
+	if (Mth::Abs(snap_distance) > s_get_param(CRCD(0xd3193d8e, "max_unnoticed_ground_snap")))
+	{
+		GetObject()->SelfEvent(snap_distance > 0.0f ? CRCD(0x93fcf3ed, "SnapUpEdge") : CRCD(0x56e21153, "SnapDownEdge"));
+	}
+	
+	// snap position to the ground
+	m_pos[Y] = feeler.GetPoint()[Y];
+	DUMP_WPOSITION
+	
+	// adjust stair float distance
+	if (snap_distance > 0.0f || snap_distance < -3.0f)
+	{
+		m_curb_float_height = Mth::ClampMin(m_curb_float_height - snap_distance, 0.0f);
+	}
+	
+	// see if we've changed sectors
+	if (m_last_ground_feeler.GetSector() != feeler.GetSector())
+	{
+		if (m_last_ground_feeler_valid)
+		{
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF, m_last_ground_feeler);
+			if (mp_physics_control_component->HaveBeenReset()) return;
+		}
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return;
+	}
+	
+	// stash the ground feeler so that we can trip the group's triggers at a later time
+	m_last_ground_feeler = feeler;
+	m_last_ground_feeler_valid = true;
+	
+	// set the ground normal for next frame's velocity slope adjustment
+	m_ground_normal = feeler.GetNormal();
+	
+	// NOTE: need to repeat this code anywhere we enter the ground state
+	mp_movable_contact_component->CheckForMovableContact(feeler);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::adjust_curb_float_height (   )
+{
+	// adjust m_curb_float_height to smooth out moving up stairs
+	
+	// When facing a curb, we smoothly increase m_curb_float_height to the height of the curb.  When we snap up the curb, m_curb_float_height is then
+	// reduced by an amount equal to the snap distance.
+	// When we snap down a curb, m_curb_float_height is increased by the snap distance.  We then drop m_curb_float_height smoothly to zero.
+	
+	// determine appropriate direction to search for a curb
+	Mth::Vector feeler_direction = m_facing;
+	feeler_direction.ProjectToPlane(m_ground_normal);
+	feeler_direction[Y] = Mth::ClampMin(feeler_direction[Y], 0.0f);
+	feeler_direction.Normalize();
+	
+	// look for a curb
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += 0.5f;
+	feeler.m_end = m_pos + s_get_param(CRCD(0x11edcc52, "curb_float_feeler_length")) * feeler_direction;
+    feeler.m_end[Y] += 0.5f;
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 255, 0, 1);
+	}
+	#endif
+	
+	if (feeler.GetCollision())
+	{
+		// grab the distance to the curb
+		float distance_to_curb = feeler.GetDist();
+		
+		// look up from the curb to find the curb height
+		feeler.m_end = feeler.GetPoint() + 0.5f * feeler_direction;
+		feeler.m_start = feeler.m_end;
+		feeler.m_start[Y] = m_pos[Y] + s_get_param(CRCD(0xcee3a3e1, "snap_up_height"));
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(0, 255, 255, 1);
+		}
+		#endif
+		
+		if (feeler.GetCollision())
+		{
+			// calculate the m_curb_float_height we should have based on the curb height and distance
+			float appropriate_curb_float_height = (1.0f - distance_to_curb) * (feeler.GetPoint()[Y] - m_pos[Y]);
+			
+			if (Mth::Abs(m_curb_float_height) < 0.01f && m_control_magnitude == 0.0f && m_horizontal_vel.LengthSqr() < Mth::Sqr(s_get_param(CRCD(0x227d72ee, "min_curb_height_adjust_vel"))))
+			{
+				// don't update the curb height if we're on the ground and standing still; this is mostly to prevent snapping up right after landing a jump
+			}
+			else
+			{
+				// lerp to the appropriate height
+				m_curb_float_height = Mth::Lerp(m_curb_float_height, appropriate_curb_float_height, s_get_param(CRCD(0x856a80d3, "curb_float_lerp_up_rate")) * m_frame_length);
+			}
+			
+			m_curb_float_height_adjusted = true;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::account_for_movable_contact (   )
+{
+	if (!mp_movable_contact_component->UpdateContact(m_pos)) return;
+	
+	m_pos += mp_movable_contact_component->GetContact()->GetMovement();
+	DUMP_WPOSITION
+	
+	if (mp_movable_contact_component->GetContact()->IsRotated())
+	{
+		m_facing = mp_movable_contact_component->GetContact()->GetRotation().Rotate(m_facing);
+		if (m_facing[Y] != 0.0f)
+		{
+			m_facing[Y] = 0.0f;
+			m_facing.Normalize();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::jump (   )
+{
+	// switch to air state and give the object an upwards velocity
+	
+	float strength = 0.0f;
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+		case WALKING_AIR:
+			// jump strength scales with the length the jump button has been held
+			strength = Mth::Lerp(
+				s_get_param(CRCD(0x246d0bf3, "min_jump_factor")), 
+				1.0f,
+				Mth::ClampMax(mp_input_component->GetControlPad().m_x.GetPressedTime() / s_get_param(CRCD(0x12333ebd, "hold_time_for_max_jump")), 1.0f)
+			);
+			break;
+			
+		case WALKING_HANG:
+		case WALKING_LADDER:
+		case WALKING_ANIMWAIT:
+			strength = s_get_param(CRCD(0xf2fa5845, "hang_jump_factor"));
+			break;
+	}
+	
+	// if we're jumping from the ground, trip the ground's triggers
+	if (m_state == WALKING_GROUND && m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return;
+	}
+	
+	// Called by script from outside of the component update, so m_vertical_vel is not used.
+	GetObject()->GetVel()[Y] = strength * s_get_param(CRCD(0x63d62a21, "jump_velocity"));
+	
+	// jumps for ladders and hanging get a backwards velocity
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+		case WALKING_AIR:
+		{
+			extract_state_from_object();
+	
+			if (m_control_magnitude)
+			{
+				float min_launch_speed = calculate_desired_speed()
+					* s_get_param(CRCD(0x839fe542, "jump_horiz_speed")) / get_run_speed();
+				
+				if (Mth::DotProduct(m_horizontal_vel, m_control_direction) > 0.0f)
+				{
+					m_horizontal_vel.ProjectToNormal(m_control_direction);
+					if (m_horizontal_vel.Length() < min_launch_speed)
+					{
+						m_horizontal_vel.Normalize(min_launch_speed);
+					}
+				}
+				else
+				{
+					m_horizontal_vel = min_launch_speed * m_control_direction;
+				}
+				
+				m_primary_air_direction = m_control_direction;
+				m_facing = m_control_direction;
+			}
+			else
+			{
+				m_primary_air_direction = m_facing;
+			}
+			
+			adjust_jump_for_ceiling_obstructions();
+			
+			// setup primary air direction
+			float length_sqr = m_horizontal_vel.LengthSqr();
+			if (length_sqr > 0.01f)
+			{
+				m_primary_air_direction = m_horizontal_vel;
+				m_primary_air_direction /= sqrtf(length_sqr);
+			}
+			else
+			{
+				m_primary_air_direction = m_facing;
+			}
+			
+			copy_state_into_object();
+			
+			break;
+		}
+		
+		case WALKING_ANIMWAIT:
+			m_false_wall.active = true;
+			m_false_wall.distance = Mth::DotProduct(m_false_wall.normal, m_pos + m_critical_point_offset);
+			
+			GetObject()->GetVel()[X] = 0.0f;
+			GetObject()->GetVel()[Z] = 0.0f;
+			m_primary_air_direction = m_facing;
+			break;
+			
+		case WALKING_HANG:
+			m_false_wall.active = true;
+			m_false_wall.normal = m_facing;
+			m_false_wall.distance = Mth::DotProduct(m_false_wall.normal, m_pos + m_critical_point_offset);
+			m_false_wall.cancel_height = m_pos[Y] - m_vertical_hang_offset;
+			
+			GetObject()->GetVel()[X] = 0.0f;
+			GetObject()->GetVel()[Z] = 0.0f;
+			m_primary_air_direction = m_facing;
+			break;
+
+		case WALKING_LADDER:
+			GetObject()->GetVel()[X] = 0.0f;
+			GetObject()->GetVel()[Z] = 0.0f;
+			m_primary_air_direction = m_facing;
+			break;
+	}
+	
+	leave_movable_contact_for_air(GetObject()->GetVel(), GetObject()->GetVel()[Y]);
+	
+	set_state(WALKING_AIR);
+	
+	GetObject()->BroadcastEvent(CRCD(0x8687163a, "SkaterJump"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::adjust_jump_for_ceiling_obstructions (   )
+{
+	// check for obstructions above and drift backwards to avoid them if we can; this prevents us from banging our head on overhangs when we're up
+	// against the wall and trying to jump to hang on them
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_start[Y] += s_get_param(CRCD(0x9ea1974a, "walker_height"));
+	feeler.m_end = feeler.m_start;
+	feeler.m_end[Y] += s_get_param(CRCD(0x48c0ac2a, "jump_obstruction_check_height"));
+	if (feeler.GetCollision(false))
+	{
+		float jump_obstruction_check_back = s_get_param(CRCD(0x6ebbab7, "jump_obstruction_check_back"));
+		float obstruction_height = feeler.GetPoint()[Y] - feeler.m_start[Y];
+
+		feeler.m_start -= m_facing * jump_obstruction_check_back;
+		feeler.m_end -= m_facing * jump_obstruction_check_back;
+
+		if (!feeler.GetCollision(false))
+		{
+
+			float acceleration = get_gravity();
+			float vel_sqr_at_obstruction_height = Mth::Sqr(m_vertical_vel) - 2.0f * acceleration * obstruction_height;
+			if (vel_sqr_at_obstruction_height > 0.0f)
+			{
+				float time_to_height = (m_vertical_vel - sqrt(vel_sqr_at_obstruction_height)) / acceleration;
+
+				// setup in air drift
+				m_in_air_drift_vel = -jump_obstruction_check_back / time_to_height * m_facing;
+				m_in_air_drift_stop_height = m_pos[Y] + obstruction_height;
+
+				// suppress control until we return to this height from above
+				m_in_air_control_suppression_timer = 2.0f * m_vertical_vel / acceleration - time_to_height;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::go_in_air_state (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	setup_collision_cache();
+	
+	// default air event
+	m_frame_event = CRCD(0x439f4704, "Air");
+	
+	// user control of horizontal velocity
+	control_horizontal_vel();
+	
+	// prevent movement into walls
+	if (!adjust_horizonal_vel_for_environment())
+	{
+		m_drop_rotation_rate = false;
+	}
+	if (mp_physics_control_component->HaveBeenReset()) return;
+	
+	// account for the in-air false wall
+	adjust_pos_for_false_wall();
+	
+	// check for head bonking
+	adjust_vertical_vel_for_ceiling();
+	
+	// apply movement and acceleration for this frame
+	float acceleration = get_gravity();
+	m_pos += m_horizontal_vel * m_frame_length;
+	m_pos += m_in_air_drift_vel * m_frame_length;
+	m_pos[Y] += m_vertical_vel * m_frame_length + 0.5f * -acceleration * Mth::Sqr(m_frame_length);
+	DUMP_WPOSITION
+	m_vertical_vel += -acceleration * m_frame_length;
+	
+	if (m_pos[Y] > m_in_air_drift_stop_height)
+	{
+		m_in_air_drift_vel.Set();
+	}
+	
+	// see if we've landed yet
+	check_for_landing(m_frame_start_pos, m_pos);
+	if (m_state != WALKING_AIR || mp_physics_control_component->HaveBeenReset()) return;
+	
+	// maybe grab a rail; delay regrabbing of hang rails
+	if (control_pad.m_R1.GetPressed() && !m_ignore_grab_button
+		&& ((m_previous_state != WALKING_HANG && m_previous_state != WALKING_LADDER && m_previous_state != WALKING_ANIMWAIT)
+		|| Tmr::ElapsedTime(m_state_timestamp) > s_get_param(CRCD(0xe6e0c0a4, "rehang_delay"))))
+	{
+		if (m_previous_state == WALKING_LADDER)
+		{
+			// can't regrab ladders
+			if (maybe_grab_to_hang(m_frame_start_pos, m_pos))
+			{
+				CFeeler::sClearDefaultCache();
+				return;
+			}
+		}
+		else
+		{
+			if (maybe_grab_to_hang(m_frame_start_pos, m_pos) || maybe_grab_to_ladder(m_frame_start_pos, m_pos))
+			{
+				CFeeler::sClearDefaultCache();
+				return;
+			}
+		}
+	}
+	
+	CFeeler::sClearDefaultCache();
+	
+	if (control_pad.m_triangle.GetPressed() && maybe_stick_to_rail()) return;
+	
+	if (maybe_in_air_acid_drop()) return;
+	
+	maybe_wallplant();
+	if (mp_physics_control_component->HaveBeenReset()) return;
+	
+	// insure that we do not slip through the cracks in the collision geometry which are a side-effect of moving collidable objects
+	Mth::Vector previous_pos = m_pos;
+	Mth::Vector critical_point = m_pos + m_critical_point_offset;
+	Mth::Vector frame_start_critical_point = m_frame_start_pos + m_critical_point_offset;
+	if (CCompositeObject* p_inside_object = mp_movable_contact_component->CheckInsideObjects(critical_point, frame_start_critical_point))
+	{
+		m_pos = critical_point - m_critical_point_offset;
+		DUMP_WPOSITION
+		
+		MESSAGE("WALKING_AIR, within moving object");
+		
+		m_horizontal_vel.Set();
+		m_vertical_vel = 0.0f;
+		check_for_landing(m_pos, previous_pos);
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::control_horizontal_vel (   )
+{
+	// We allow user control over the object's in air velocity.  The algorithm is complicated by the fact that the forward velocity of the jump needs
+	// to be accounted for when allowing for velocity adjustment.  It is assumed that the jump direction is the same as the facing.
+	
+	if (m_in_air_control_suppression_timer != 0.0f)
+	{
+		m_in_air_control_suppression_timer = Mth::ClampMin(m_in_air_control_suppression_timer - m_frame_length, 0.0f);
+		return;
+	}
+	
+	// remove uncontrollable velocity term
+	m_horizontal_vel -= m_uncontrollable_air_horizontal_vel;
+	
+	#ifdef SCRIPT_WALK_DRAG
+	// adjust speed by the script set drag factor
+	float adjust_magnitude = m_control_magnitude * m_script_drag_factor;
+	#else
+	float adjust_magnitude = m_control_magnitude;
+	#endif
+	
+	// adjust velocity perpendicular to jump direction
+	
+	// direction perpendicular to jump direction
+	Mth::Vector perp_direction(-m_primary_air_direction[Z], 0.0f, m_primary_air_direction[X]);
+	
+	// desired perpendicular velocity
+	float perp_desired_vel = s_get_param(CRCD(0x896c8888, "jump_adjust_speed")) * adjust_magnitude * Mth::DotProduct(m_control_direction, perp_direction);
+	
+	// current perpendicular velocity
+	float perp_vel = Mth::DotProduct(m_horizontal_vel, perp_direction);
+	
+	// exponentially approach desired velocity
+	perp_vel = Mth::Lerp(perp_vel, perp_desired_vel, s_get_param(CRCD(0xf085443b, "jump_accel_factor")) * m_frame_length);
+		
+	// adjust velocity parallel to jump direction
+	
+	// desired parallel velocity
+	float para_desired_vel = s_get_param(CRCD(0x896c8888, "jump_adjust_speed")) * adjust_magnitude * Mth::DotProduct(m_control_direction, m_primary_air_direction);
+	
+	// current parallel velocity
+	float para_vel = Mth::DotProduct(m_horizontal_vel, m_primary_air_direction);
+	
+    // if desired velocity if forward and forward velocity already exceeds adjustment velocity
+	if (para_desired_vel >= 0.0f && para_vel > para_desired_vel)
+	{
+		// do nothing; don't slow down the jump
+	}
+	else
+	{
+		// adjust desired velocity to center around current velocity to insure that our in air stopping ability is not too amazing
+		if (para_desired_vel < 0.0f && para_vel > 0.0f)
+		{
+			para_desired_vel += para_vel;
+		}
+		
+		// exponentially approach desired velocity
+		para_vel = Mth::Lerp(para_vel, para_desired_vel, s_get_param(CRCD(0xf085443b, "jump_accel_factor")) * m_frame_length);
+	}
+		
+	// rebuild horizontal velocity from parallel and perpendicular components
+	m_horizontal_vel = para_vel * m_primary_air_direction + perp_vel * perp_direction;
+	
+	// reinstitute uncontrollable velocity term
+	m_horizontal_vel += m_uncontrollable_air_horizontal_vel;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::adjust_vertical_vel_for_ceiling (   )
+{
+	// if we hit our head, zero vertical velocity
+	
+	// no ceiling collisions when a false wall is active; otherwise, ceilings would kill our jump when we jump from hanging on a ledge which sticks out.
+	if (m_false_wall.active) return;
+	
+	float walker_height = s_get_param(CRCD(0x9ea1974a, "walker_height"));
+	
+	// be more forgiving if we're moving down already
+	if (m_vertical_vel < 0.0f)
+	{
+		walker_height -= 18.0f;
+	}
+	
+	// look for a collision up through the body to the head
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_end = m_pos;
+	feeler.m_end[Y] += walker_height;
+	if (!feeler.GetCollision()) return;
+	
+	// project velocity into plane
+	m_horizontal_vel[Y] = m_vertical_vel;
+	if (Mth::DotProduct(m_horizontal_vel, feeler.GetNormal()) < 0.0f)
+	{
+		m_horizontal_vel.ProjectToPlane(feeler.GetNormal());
+	}
+	m_vertical_vel = m_horizontal_vel[Y];
+	m_horizontal_vel[Y] = 0.0f;
+	
+	// determine the displacement vector we must use to get out of the ceiling
+	Mth::Vector displacement = feeler.GetPoint() - feeler.m_end;
+	displacement.ProjectToNormal(feeler.GetNormal());
+	
+	// insure there are no collisions along this displacement
+	feeler.m_end = m_pos + displacement;
+	if (feeler.GetCollision())
+	{
+		m_pos = feeler.GetPoint() + feeler.GetNormal();
+		DUMP_WPOSITION
+	}
+	else
+	{
+		m_pos = feeler.m_end;
+		DUMP_WPOSITION
+	}
+	
+	GetObject()->SelfEvent(CRCD(0x6e84acf3, "HitCeiling"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::check_for_landing ( const Mth::Vector& previous_pos, const Mth::Vector& final_pos )
+{
+	// See if our feet have passed through geometry.  If so, snap to it and go to ground state.
+	
+	CFeeler feeler;
+	feeler.m_start = previous_pos;
+	feeler.m_end = final_pos;
+	if (!feeler.GetCollision()) return;
+	
+	if (feeler.GetFlags() & mFD_NOT_SKATABLE)
+	{
+        m_pos = feeler.GetPoint() + 0.1f * feeler.GetNormal();
+		DUMP_WPOSITION
+		
+		Mth::Vector remaining_movement = (1.0f - feeler.GetDist()) * (feeler.m_end - feeler.m_start);
+		remaining_movement.ProjectToPlane(feeler.GetNormal());
+		
+		// slide along the surface, but stop at any new collision
+		feeler.m_start = m_pos;
+		feeler.m_end = m_pos + remaining_movement;
+		if (feeler.GetCollision())
+		{
+			m_pos = feeler.GetPoint() + 0.05f * feeler.GetNormal();
+			DUMP_WPOSITION
+		}
+		else
+		{
+			m_pos = feeler.m_end;
+			DUMP_WPOSITION
+		}
+		return;
+	}
+	
+	// snap to the collision point
+	m_pos = feeler.GetPoint();
+	DUMP_WPOSITION
+	
+	// zero vertical velocity
+	m_vertical_vel = 0.0f;
+	
+	// change to ground state
+	set_state(WALKING_GROUND);
+	
+	// stash the feeler
+	m_last_ground_feeler = feeler;
+	m_last_ground_feeler_valid = true;
+	
+	// trip any land trigger
+	mp_trigger_component->CheckFeelerForTrigger(TRIGGER_LAND_ON, m_last_ground_feeler);
+	if (mp_physics_control_component->HaveBeenReset()) return;
+	
+	// setup our ground normal for next frames velocity slope adjustment
+	m_ground_normal = feeler.GetNormal();
+	
+	// check for a moving contact
+	mp_movable_contact_component->CheckForMovableContact(feeler);
+	if (mp_movable_contact_component->HaveContact())
+	{
+		m_horizontal_vel -= mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+		m_horizontal_vel[Y] = 0.0f;
+	}
+	
+	// retain only that velocity which is parallel to our facing and forward
+	if (Mth::DotProduct(m_horizontal_vel, m_facing) > 0.0f)
+	{
+		m_horizontal_vel.ProjectToNormal(m_facing);
+		
+		if (m_control_magnitude == 0.0f && m_horizontal_vel.Length() < s_get_param(CRCD(0x530dcf34, "sticky_land_threshold_speed")))
+		{
+			m_horizontal_vel.Set();
+		}
+	}
+	else
+	{
+		m_horizontal_vel.Set();
+	}
+
+	m_critical_point_offset.Set();
+
+	// clear any jump requests
+	mp_input_component->GetControlPad().m_x.ClearRelease();
+
+	m_frame_event = CRCD(0x57ff2a27, "Land");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::maybe_wallplant (   )
+{
+	if (!mp_input_component->GetControlPad().m_x.GetPressed()) return;
+	
+	// only wallplant on the way down
+	if (m_vertical_vel > 0.0f) return;
+					   	
+	// not when you're too near the ground
+	if (mp_state_component->m_height < Script::GetFloat(CRCD(0xd5349cc6, "Physics_Min_Wallplant_Height"))) return;
+
+	// if (Tmr::ElapsedTime(m_state_timestamp) < s_get_param(CRCD(0x2a2d65c, "min_air_before_wallplant"))) return;
+	
+	// last wallplant must not have been too recently
+	if (Tmr::ElapsedTime(mp_core_physics_component->m_last_wallplant_time_stamp) < Script::GetFloat(CRCD(0x82135dd7, "Physics_Disallow_Rewallplant_Duration"))) return;
+	
+	// no wallplants immediately after you enter air; stops wallplants during the late jump period
+	if (Tmr::ElapsedTime(m_state_timestamp) < Script::GetFloat(CRCD(0x4c2b6df3, "Skater_Late_Jump_Slop"))) return;
+	
+	// identify the primary contact wall; not that we are ignoring the "feet feeler"
+	int n;
+	int contact_idx;
+	const int p_contact_indxs[vNUM_FEELERS] = { 0, 1, 6, 2, 5, 3, 4 };
+	for (n = 0; n < vNUM_FEELERS; n++)
+	{
+		if (mp_contacts[contact_idx = p_contact_indxs[n]].in_collision) break;
+	}
+	if (n == vNUM_FEELERS) return;
+	
+	/*
+	// here we attempt to stop wallplant when in is more likely that the player is going for a grind or wants to jump up over the wall
+	if (GetObject()->m_vel[Y] > 0.0f)
+	{
+		Mth::Vector wall_point = mp_contacts[contact_idx].feeler.GetPoint();
+		Mth::Vector	wall_normal = mp_contacts[contact_idx].feeler.GetNormal();
+
+		Mth::Vector wall_up_vel(0.0f, GetObject()->m_vel[Y] * 0.15f, 0.0f);		// check 0.15 seconds ahead
+		wall_up_vel.RotateToPlane(wall_normal);  
+
+		// check at what height will be in two frames
+		wall_point += wall_up_vel;		
+
+		CFeeler feeler(wall_point + wall_normal * 6.0f, wall_point - wall_normal * 6.0f);
+		if (!feeler.GetCollision())
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(255, 255, 0, 0);
+			}
+			#endif
+			return;
+		}
+		else
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(255, 0, 255, 0);
+			}
+			#endif
+		}
+	}
+	*/
+	
+	// reflect the control direction off the wall and use it as our facing and boost direction
+	Mth::Vector exit_dir;
+	if (m_control_magnitude == 0.0f)
+	{
+		exit_dir = m_primary_air_direction;
+	}
+	else
+	{
+		exit_dir = m_control_direction;
+	}
+	float dot = Mth::DotProduct(exit_dir, mp_contacts[contact_idx].normal);
+	if (dot < 0.0f)
+	{
+		exit_dir -= 2.0f * dot * mp_contacts[contact_idx].normal;
+	}
+	
+	// set vertical velocity to the walking wallplant jump speed
+	m_vertical_vel = s_get_param(CRCD(0x420d18bc, "vert_wall_jump_speed")) * Mth::Lerp(
+		s_get_param(CRCD(0x246d0bf3, "min_jump_factor")), 
+		1.0f,
+		Mth::ClampMax(mp_input_component->GetControlPad().m_x.GetPressedTime() / s_get_param(CRCD(0x12333ebd, "hold_time_for_max_jump")), 1.0f)
+	);
+	
+	// jump out from the wall
+	m_horizontal_vel = s_get_param(CRCD(0x99510695, "horiz_wall_jump_speed")) * exit_dir;
+	m_primary_air_direction = m_facing;
+
+	// if the wall is moving, add on its velocity
+	if (mp_contacts[contact_idx].feeler.IsMovableCollision())
+	{
+		Mth::Vector movable_contact_vel = mp_contacts[contact_idx].feeler.GetMovingObject()->GetVel();
+		m_vertical_vel += movable_contact_vel[Y];
+		m_horizontal_vel[X] += movable_contact_vel[X];
+		m_horizontal_vel[Z] += movable_contact_vel[Z];
+	}
+	
+	adjust_jump_for_ceiling_obstructions();
+	
+	// trip any triggers
+	mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, mp_contacts[contact_idx].feeler);
+	if (mp_physics_control_component->HaveBeenReset()) return;
+	
+	// graffiti the object
+	GetTrickComponentFromObject(GetObject())->TrickOffObject(mp_contacts[contact_idx].feeler.GetNodeChecksum());
+	
+	// time stamp the wallplant
+	mp_core_physics_component->m_last_wallplant_time_stamp = Tmr::GetTime();
+	
+	m_frame_event = CRCD(0xcf74f6b7, "WallPlant");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::uber_frig (   )
+{
+	// insure that we don't fall to the center of the earth, even if there are holes in the geometry; also, do lighting since we've got the feeler anyway
+	
+	CFeeler feeler;
+	feeler.m_start = m_pos + m_critical_point_offset;
+	feeler.m_end = feeler.m_start;
+	feeler.m_start[Y] += 1.0f;
+	feeler.m_end[Y] -= FEET(400);
+	
+	if (feeler.GetCollision())
+	{
+		// set the height for script access
+		mp_state_component->m_height = m_pos[Y] - feeler.GetPoint()[Y];
+		
+		mp_model_component->ApplyLightingFromCollision(feeler);
+		
+		// Store these values off for the simple shadow calculation.
+		CShadowComponent* p_shadow_component = GetShadowComponentFromObject(GetObject());
+		p_shadow_component->SetShadowPos(feeler.GetPoint());
+		p_shadow_component->SetShadowNormal(feeler.GetNormal()); 
+		
+		return;
+	}
+	
+	MESSAGE("applying uber frig");
+	
+	// teleport us back to our position at the frame's start; not pretty, but this isn't supposed to be
+	m_pos = m_frame_start_pos;
+	DUMP_WPOSITION
+	
+	// zero our velocity too
+	m_horizontal_vel.Set();
+	m_vertical_vel = 0.0f;
+	
+	// set our state to ground
+	set_state(WALKING_GROUND);
+			  	
+	m_last_ground_feeler_valid = false;
+	
+	m_ground_normal.Set(0.0f, 1.0f, 0.0f);
+
+	// reset our script state
+	m_frame_event = CRCD(0x57ff2a27, "Land");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::lerp_upright (   )
+{
+    if (m_upward[Y] == 1.0f) return;
+	
+	if (m_upward[Y] > 0.999f)
+	{
+		m_upward.Set(0.0f, 1.0f, 0.0f);
+		m_rotate_upright_timer = 0.0f;
+		return;
+	}
+	
+	if (m_rotate_upright_timer == 0.0f)
+	{
+		m_upward = Mth::Lerp(m_upward, Mth::Vector(0.0f, 1.0f, 0.0f), s_get_param(CRCD(0xf22c135, "lerp_upright_rate")) * Tmr::FrameLength());
+		m_upward.Normalize();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::transition_lerp_upright (   )
+{
+	if (m_rotate_upright_timer != 0.0f)
+	{
+		float lerp_factor = Mth::LinearMap(0.0f, 1.0f, m_frame_length, 0.0f, s_get_param(CRCD(0xb0675803, "rotate_upright_duration")));
+		
+		GetObject()->GetMatrix().Rotate(m_rotate_upright_axis, m_rotate_upright_angle * lerp_factor);
+		GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+		
+		m_rotate_upright_timer -= m_frame_length;
+		m_rotate_upright_timer = Mth::ClampMin(m_rotate_upright_timer, 0.0f);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::check_for_wall_push (   )
+{
+	// ensure we have a forward contact
+	if (!mp_contacts[0].in_collision && !mp_contacts[1].in_collision && !mp_contacts[vNUM_FEELERS - 1].in_collision)
+	{
+		return m_wall_push_test.active = false;
+	}
+	
+	// ensure that control is maxed out
+	if (!m_control_pegged)
+	{
+		return m_wall_push_test.active = false;
+	}
+	
+	if (!m_wall_push_test.active)
+	{
+		// if we're not testing, simply start the test
+		m_wall_push_test.test_start_time = Tmr::GetTime();
+		m_wall_push_test.active = true;
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_jump_low_barrier (   )
+{
+	// check to see if we're in a bail
+	if (mp_core_physics_component->GetFlag(IS_BAILING)) return false;
+	
+	// For each forward contact in collision, we check to see if any lacks a collision at the maximum jump height.  For any that do, we find the first height
+	// at which they are in contact.  The lowest such height is used at the target jump height.
+	
+	const int p_forward_contact_idxs[] = { 0, 1, vNUM_FEELERS - 1 };
+	
+	// we use the lowest hang height as the maximum autojump barrier height
+	float top_feeler_height = s_get_param(CRCD(0xda85f5ae, "barrier_jump_highest_barrier"));
+	float feeler_length = 3.0f * s_get_param(CRCD(0x99978d2b, "feeler_length"));
+	float height_increment = (top_feeler_height - s_get_param(CRCD(0x6da7f696, "feeler_height"))) / vNUM_BARRIER_HEIGHT_FEELERS;
+	
+	CFeeler feeler;
+	
+	// setup collision cache
+	Mth::CBBox bbox(
+		m_pos - Mth::Vector(feeler_length + 1.0f, 0.0f, feeler_length + 1.0f),
+		m_pos + Mth::Vector(feeler_length + 1.0f, top_feeler_height + 1.0f, feeler_length + 1.0f)
+	);
+	Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
+	p_coll_cache->Update(bbox);
+	feeler.SetCache(p_coll_cache);
+	
+	// loop over forward collisions and check to see if the barrier in each of their directions is jumpable
+	bool jumpable = false;
+	for (int i = 0; i < 3; i++)
+	{
+		int n = p_forward_contact_idxs[i];
+		
+		if (!mp_contacts[n].in_collision || (mp_contacts[n].feeler.GetFlags() & mFD_NOT_SKATABLE)) continue;
+		
+		// first check to see if the collision normal is not too transverse to the control direction, making a autojump unlikely to succeed
+		if (Mth::DotProduct(mp_contacts[n].normal, m_control_direction) > -cosf(Mth::DegToRad(s_get_param(CRCD(0x78e6a5ec, "barrier_jump_max_angle")))))
+		{
+			mp_contacts[n].jumpable = false;
+			continue;
+		}
+		
+		feeler.m_start = m_pos;
+		feeler.m_start[Y] += top_feeler_height;
+		feeler.m_end = feeler.m_start + feeler_length * calculate_feeler_offset_direction(n);
+		
+		mp_contacts[n].jumpable = !feeler.GetCollision();
+		
+        if (mp_contacts[n].jumpable)
+		{
+			jumpable = true;
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(0, 0, 255, 0);
+			}
+			#endif
+		}
+		else
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(255, 0, 0, 0);
+			}
+			#endif
+		}
+	}
+	
+	// if the barrier is not jumpable
+	if (!jumpable)
+	{
+		// no autojump
+		Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+		return false;
+	}
+	
+	// loop over the jumpable collision directions
+	float lowest_height = m_pos[Y] + top_feeler_height;
+	for (int i = 0; i < 3; i++)
+	{
+		int n = p_forward_contact_idxs[i];
+		
+		if (!mp_contacts[n].in_collision) continue;
+		if (!mp_contacts[n].jumpable) continue;
+		
+		feeler.m_start = m_pos;
+		feeler.m_start[Y] += top_feeler_height;
+		feeler.m_end = feeler.m_start + feeler_length * calculate_feeler_offset_direction(n);
+		
+		// look for the barrier height
+		for (int h = vNUM_BARRIER_HEIGHT_FEELERS - 1; h--; )
+		{
+			feeler.m_start[Y] -= height_increment;
+			feeler.m_end[Y] -= height_increment;
+			if (feeler.GetCollision())
+			{
+				#ifdef __USER_DAN__
+				if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+				{
+					feeler.DebugLine(255, 0, 0, 0);
+				}
+				#endif
+				break;
+			}
+			else
+			{
+				#ifdef __USER_DAN__
+				if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+				{
+					feeler.DebugLine(0, 255, 0, 0);
+				}
+				#endif
+			}
+		}
+		
+		// find the lowest barrier of the jumpable directions
+		if (lowest_height > feeler.m_start[Y])
+		{
+			lowest_height = feeler.m_start[Y];
+		}
+	}
+	
+	Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+	
+	// caluclate the velocity required to clear the barrier
+	float jump_height = lowest_height + height_increment + s_get_param(CRCD(0x72660978, "barrier_jump_min_clearance")) - m_pos[Y];
+	float required_vertical_velocity = sqrtf(2.0f * get_gravity() * jump_height);
+	
+	if (m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return true;
+	}
+	
+	// setup the new walking state
+	
+	m_vertical_vel = required_vertical_velocity;
+	
+	set_state(WALKING_AIR);
+	
+	m_primary_air_direction = m_facing;
+	leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+	
+	m_frame_event = CRCD(0x584cf9e9, "Jump");
+	
+	GetObject()->BroadcastEvent(CRCD(0x8687163a, "SkaterJump"));
+	
+	// stop late jumps after an autojump
+	GetObject()->RemoveEventHandler(CRCD(0x6b9ca247, "JumpRequested"));
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_in_air_acid_drop (   )
+{
+	if (m_disallow_acid_drops) return false;
+	if (mp_physics_control_component->IsBoardMissing()) return false;
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	if (!WALK_SPINE_BUTTONS) return false;
+	
+	Mth::Vector vel(m_horizontal_vel[X], m_vertical_vel, m_horizontal_vel[Z]);
+	if (!mp_core_physics_component->maybe_acid_drop(false, m_pos, m_frame_start_pos, vel, m_special_transition_data.acid_drop_data)) return false;
+	
+	m_horizontal_vel = vel;
+	m_horizontal_vel[Y] = 0.0f;
+	m_vertical_vel = vel[Y];
+	m_special_transition_data.type = ACID_DROP_TRANSITION;
+	m_frame_event = CRCD(0x2eda83ea, "AcidDrop");
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_jump_to_acid_drop (   )
+{
+	// we're on the ground; look ahead for acid drops to autojump into
+	
+	if (m_disallow_acid_drops) return false;
+	if (mp_physics_control_component->IsBoardMissing()) return false;
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	if (!WALK_SPINE_BUTTONS) return false;
+	
+	Mth::Vector jump_vel = m_horizontal_vel;
+	jump_vel[Y] = s_get_param(CRCD(0xc0491d2e, "acid_drop_jump_velocity"));
+	
+	if (!mp_core_physics_component->maybe_acid_drop(false, m_pos, m_frame_start_pos, jump_vel, m_special_transition_data.acid_drop_data)) return false;
+	
+	if (m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return true;
+	}
+	
+	set_state(WALKING_AIR);
+	
+	m_horizontal_vel = jump_vel;
+	m_horizontal_vel[Y] = 0.0f;
+	m_vertical_vel = jump_vel[Y];
+	m_special_transition_data.type = ACID_DROP_TRANSITION;
+	m_frame_event = CRCD(0x2eda83ea, "AcidDrop");
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::leave_movable_contact_for_air ( Mth::Vector& horizontal_vel, float& vertical_vel )
+{
+	// use movement from the latest movable contact update call
+	
+	if (mp_movable_contact_component->HaveContact())
+	{
+		// keep track of the horizontal velocity due to our old contact
+		m_uncontrollable_air_horizontal_vel = mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+
+		if (Mth::DotProduct(m_uncontrollable_air_horizontal_vel, horizontal_vel) > 0.0f)
+		{
+			// extra kicker; dangerous as there's no collision detection; without this slight extra movement, when we walk off the front of a movable object,
+			// the object will move back under us before our next frame, and we will clip its edge and land on it
+			m_pos += m_uncontrollable_air_horizontal_vel * m_frame_length;
+			DUMP_WPOSITION
+		}
+		
+		// add movable contact's velocity into our launch velocity
+		vertical_vel += m_uncontrollable_air_horizontal_vel[Y];
+		m_uncontrollable_air_horizontal_vel[Y] = 0.0f;
+		horizontal_vel += m_uncontrollable_air_horizontal_vel;
+	}
+	else
+	{
+		m_uncontrollable_air_horizontal_vel.Set();
+	}
+	
+	mp_movable_contact_component->LoseAnyContact();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::calculate_anim_wait_facing_drift_parameters ( const Mth::Vector& goal_facing, float rotate_factor )
+{
+	float initial_angle = atan2f(m_facing[X], m_facing[Z]);
+	float goal_angle = atan2f(goal_facing[X], goal_facing[Z]);
+	
+	m_drift_angle = goal_angle - initial_angle;
+	if (Mth::Abs(m_drift_angle) > Mth::PI)
+	{
+		m_drift_angle -= Mth::Sgn(m_drift_angle) * (2.0f * Mth::PI);
+	}
+	
+	m_drift_goal_facing = goal_facing;
+	
+	m_drift_goal_rotate_factor = rotate_factor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::go_anim_wait_state (   )
+{
+	if (mp_movable_contact_component->UpdateContact(m_pos))
+	{
+		m_offset_due_to_movable_contact += mp_movable_contact_component->GetContact()->GetMovement();
+		if (mp_movable_contact_component->GetContact()->IsRotated())
+		{
+			m_drift_goal_facing = mp_movable_contact_component->GetContact()->GetRotation().Rotate(m_drift_goal_facing);
+			if (m_drift_goal_facing[Y] != 0.0f)
+			{
+				m_drift_goal_facing[Y] = 0.0f;
+				m_drift_goal_facing.Normalize();
+			}
+			
+		}
+	}
+	
+	float start, current, end;
+	mp_animation_component->GetPrimaryAnimTimes(&start, ¤t, &end);
+    float animation_completion_factor = Mth::LinearMap(0.0f, 1.0f, current, start, end);
+	
+	// smoothly drift the position
+	m_pos = m_offset_due_to_movable_contact + Mth::Lerp(m_anim_wait_initial_pos, m_anim_wait_goal_pos, animation_completion_factor);
+	DUMP_WPOSITION
+	
+	float angle = Mth::Lerp(-m_drift_angle, 0.0f, Mth::ClampMax(animation_completion_factor / m_drift_goal_rotate_factor, 1.0f));
+	m_facing = m_drift_goal_facing;
+	m_facing.RotateY(angle);
+	
+	m_display_offset = Mth::Lerp(m_drift_initial_display_offset, m_drift_goal_display_offset, animation_completion_factor);
+	
+	m_frame_event = CRCD(0x4fe6069c, "AnimWait");
+}
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::anim_wait_complete (   )
+{
+	GetObject()->SetPos(m_anim_wait_goal_pos + m_offset_due_to_movable_contact);
+	
+    Mth::Matrix matrix;
+    matrix[Z] = m_drift_goal_facing;
+    matrix[Y].Set(0.0f, 1.0f, 0.0f);
+    matrix[X].Set(m_drift_goal_facing[Z], 0.0f, -m_drift_goal_facing[X]);
+	matrix[W].Set();
+	GetObject()->SetMatrix(matrix);
+	
+	m_display_offset = m_drift_goal_display_offset;
+	
+    if (mp_anim_wait_complete_callback)
+    {
+        (this->*mp_anim_wait_complete_callback)();
+    }
+}
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_stick_to_rail (   )
+{
+	// Look for rails.  If we find one, switch to skating and stash the rail data for the skating physics.
+	
+	if (mp_physics_control_component->IsBoardMissing()) return false;
+	
+	bool rail_found = false;
+	
+	if (rail_found = Mdl::Skate::Instance()->GetRailManager()->StickToRail(
+		m_frame_start_pos,
+		m_pos,
+		&m_special_transition_data.rail_data.rail_pos,
+		&m_special_transition_data.rail_data.p_node,
+		NULL,
+		1.0f
+	))
+	{
+		m_special_transition_data.rail_data.p_rail_man = Mdl::Skate::Instance()->GetRailManager();
+		m_special_transition_data.rail_data.p_movable_contact = NULL;
+	}
+	else
+	{
+		// clean vectors
+		m_frame_start_pos[W] = 1.0f;
+		m_pos[W] = 1.0f;
+		
+		for (CRailManagerComponent* p_rail_manager_component = static_cast< CRailManagerComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_RAILMANAGER));
+			p_rail_manager_component && !rail_found;
+			p_rail_manager_component = static_cast< CRailManagerComponent* >(p_rail_manager_component->GetNextSameType()))
+		{
+			Mth::Matrix obj_matrix = p_rail_manager_component->UpdateRailManager();
+			Mth::Matrix obj_matrix_inv = obj_matrix;
+			obj_matrix_inv.Invert();
+
+			// transform into the frame of the moving object
+			Mth::Vector obj_frame_frame_start_pos = obj_matrix_inv.Transform(m_frame_start_pos);
+			Mth::Vector obj_frame_pos = obj_matrix_inv.Transform(m_pos);
+
+			if (rail_found = p_rail_manager_component->GetRailManager()->StickToRail(
+				obj_frame_frame_start_pos,
+				obj_frame_pos,
+				&m_special_transition_data.rail_data.rail_pos,
+				&m_special_transition_data.rail_data.p_node,
+				NULL,
+				1.0f
+			))
+			{
+				m_special_transition_data.rail_data.p_rail_man = p_rail_manager_component->GetRailManager();
+				m_special_transition_data.rail_data.rail_pos = obj_matrix.Transform(m_special_transition_data.rail_data.rail_pos);
+				m_special_transition_data.rail_data.p_movable_contact = p_rail_manager_component->GetObject();
+			}
+		}
+	}
+		
+	if (!rail_found || !mp_core_physics_component->will_take_rail(
+		m_special_transition_data.rail_data.p_node,
+		m_special_transition_data.rail_data.p_rail_man,
+		true
+	))
+	{
+		return false;
+	}
+	
+	// no single node rails while walking
+	if (!m_special_transition_data.rail_data.p_node->GetPrevLink() && !m_special_transition_data.rail_data.p_node->GetNextLink()) return false;
+	
+	// emulate feeler test in CSkaterCorePhysicsComponent::got_rail so that we don't transition to skating and then reject rail
+	CFeeler feeler(m_pos, m_special_transition_data.rail_data.rail_pos);
+	if (feeler.GetCollision() && (m_special_transition_data.rail_data.rail_pos - feeler.GetPoint()).LengthSqr() > 6.0f * 6.0f) return false;
+	
+	if (Script::GetInt(CRCD(0x19fb78fa, "output_tracking_lines")))
+	{
+		printf ("Tracking%d %.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n", 2, m_frame_start_pos[X], m_frame_start_pos[Y], m_frame_start_pos[Z], m_pos[X], m_pos[Y], m_pos[Z]);
+	}
+	
+	// we have stored the rail data so it can be passed on to the skating physics
+	
+	m_special_transition_data.type = RAIL_TRANSITION;
+
+	m_frame_event = CRCD(0xa6a3147e, "Rail");
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::setup_collision_cache (   )
+{
+	float horizontal_reach = 1.0f + s_get_param(CRCD(0x99978d2b, "feeler_length"));
+	float vertical_height = 1.0f + s_get_param(CRCD(0x9ea1974a, "walker_height"));;
+	float vertical_depth = 1.0f + s_get_param(CRCD(0xaf3e4251, "snap_down_height"));
+	
+	Mth::CBBox bbox(
+		GetObject()->GetPos() - Mth::Vector(horizontal_reach, vertical_depth, horizontal_reach, 0.0f),
+		GetObject()->GetPos() + Mth::Vector(horizontal_reach, vertical_height, horizontal_reach, 0.0f)
+	);
+	
+	mp_collision_cache->Update(bbox);
+	CFeeler::sSetDefaultCache(mp_collision_cache);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::update_critical_point_offset (   )
+{
+	float critical_point_offset_length_sqr = m_critical_point_offset.LengthSqr();
+	if (critical_point_offset_length_sqr != 0.0f)
+	{
+		switch (m_state)
+		{
+			case WALKING_HANG:
+				break;
+				
+			case WALKING_ANIMWAIT:
+				break;
+				
+			case WALKING_LADDER:
+			case WALKING_GROUND:
+			case WALKING_AIR:
+			{
+				Mth::Vector initial_offset = m_critical_point_offset;
+				
+				// float adjustment_length = 100.0f * m_frame_length;
+				float adjustment_length = 1000.0f * m_frame_length;
+				float offset_length = sqrtf(critical_point_offset_length_sqr);
+				if (adjustment_length < offset_length)
+				{
+					m_critical_point_offset -= (adjustment_length / offset_length) * m_critical_point_offset;
+				}
+				else
+				{
+					m_critical_point_offset.Set();
+				}
+				
+				if (m_state == WALKING_LADDER) break;
+				
+				CFeeler feeler;
+				feeler.m_start = m_frame_start_pos + initial_offset;
+				feeler.m_end = m_pos + m_critical_point_offset;
+				if (feeler.GetCollision())
+				{
+					Mth::Vector new_critical_point = feeler.GetPoint() + 0.1f * feeler.GetNormal();
+					Mth::Vector required_pos_adjustment = new_critical_point - feeler.m_end;
+					m_pos += required_pos_adjustment;
+					MESSAGE("adjusting position due to bad critical point");
+					DUMPV(required_pos_adjustment);
+					DUMPV(m_critical_point_offset);
+					DUMP_WPOSITION
+				}
+				
+				if (m_false_wall.active)
+				{
+					float offset = Mth::DotProduct(m_pos + m_critical_point_offset, m_false_wall.normal) - m_false_wall.distance;
+					if (offset > 0.0f)
+					{
+						m_pos -= offset * m_false_wall.normal;
+						DUMP_WPOSITION
+					}
+				}
+				
+				break;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::update_display_offset (   )
+{
+	switch (m_state)
+	{
+		case WALKING_ANIMWAIT:
+		case WALKING_HANG:
+			break;
+			
+		default:
+			m_display_offset = Mth::Lerp(m_display_offset, 0.0f, Tmr::FrameLength() * s_get_param(CRCD(0xb32c972b, "display_offset_restore_rate")));
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::adjust_pos_for_false_wall (   )
+{
+	if (!m_false_wall.active) return;
+	
+	if (m_pos[Y] > m_false_wall.cancel_height)
+	{
+		m_false_wall.active = false;
+	}
+	else
+	{
+		float offset = Mth::DotProduct(m_false_wall.normal, m_pos) - m_false_wall.distance;
+		if (offset > -s_get_param(CRCD(0xa20c43b7, "push_feeler_length")))
+		{
+			m_pos -= s_get_param(CRCD(0x4d16f37d, "push_strength")) * m_frame_length * m_false_wall.normal;
+			DUMP_WPOSITION
+			if (Mth::DotProduct(m_horizontal_vel, m_false_wall.normal))
+			{
+				m_horizontal_vel -= Mth::DotProduct(m_horizontal_vel, m_false_wall.normal) * m_false_wall.normal;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::extract_state_from_object (   )
+{
+	m_pos = GetObject()->m_pos;
+	DUMP_WPOSITION
+	
+	m_horizontal_vel = GetObject()->GetVel();
+	m_horizontal_vel[Y] = 0.0f;
+	m_vertical_vel = GetObject()->GetVel()[Y];
+	
+	// note that m_facing and m_upward will often not be orthogonal, but will always span a plan
+	
+	// generally straight up, but now after a transition from skating
+	m_upward = GetObject()->GetMatrix()[Y];
+	
+	m_facing = GetObject()->GetMatrix()[Z];
+	m_frame_initial_facing_Y = m_facing[Y];
+	m_facing[Y] = 0.0f;
+	float length = m_facing.Length();
+	if (length < 0.001f)
+	{
+		// upward facing orientation matrix
+		m_facing = -GetObject()->GetMatrix()[Y];
+		m_facing[Y] = 0.0f;
+		m_facing.Normalize();
+		
+		// since m_upward is now in the same plan as m_facing, push m_upward up a touch
+		m_upward[Y] += 0.01f;
+		m_upward.Normalize();
+	}
+	else
+	{
+		m_facing /= length;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::copy_state_into_object (   )
+{
+	// build the object's matrix based on our facing
+	Mth::Matrix matrix;
+	
+	m_facing *= sqrtf(1.0f - Mth::Sqr(m_frame_initial_facing_Y));
+	m_facing[Y] = m_frame_initial_facing_Y;
+	
+	// basically, rotate Z upward to perpendicular with m_upward
+	matrix[X] = Mth::CrossProduct(m_upward, m_facing);
+	matrix[X].Normalize();
+	matrix[Y] = m_upward;
+	matrix[Z] = Mth::CrossProduct(matrix[X], matrix[Y]);
+	matrix[W].Set();
+	
+	DUMP_WPOSITION
+	GetObject()->SetPos(m_pos);
+	GetObject()->SetMatrix(matrix);
+	GetObject()->SetDisplayMatrix(matrix);
+	
+	// construct the object's velocity
+	switch (m_state)
+	{
+		case WALKING_GROUND:
+		case WALKING_AIR:
+			GetObject()->SetVel(m_horizontal_vel);
+			GetObject()->GetVel()[Y] = m_vertical_vel;
+			break;
+			
+		case WALKING_HANG:
+			GetObject()->GetVel().Set(-m_facing[Z], 0.0f, m_facing[X]);
+			GetObject()->GetVel() *= m_hang_move_vel;
+			break;
+		
+		case WALKING_LADDER:
+			GetObject()->GetVel().Set(0.0f, m_anim_effective_speed, 0.0f);
+			break;
+			
+		default:
+			GetObject()->GetVel().Set();
+			break;
+	}
+	
+	mp_model_component->SetDisplayOffset(m_display_offset * matrix[Y]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWalkComponent::get_controller_input (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	Dbg_Assert(mp_camera);
+	
+	// rotate controller direction into camera's frame
+	
+	Mth::Vector camera_forward = -mp_camera->m_matrix[Z];
+	camera_forward[Y] = 0.0f;
+	camera_forward.Normalize();
+	
+	// allow a tolerance range for pressing directly forward
+	float angle = control_pad.m_leftAngle;
+	if (Mth::Abs(angle) < Mth::DegToRad(s_get_param(CRCD(0x4676a268, "forward_tolerance"))))
+	{
+		angle = 0.0f;
+	}
+	
+	float sin_angle = sinf(angle);
+	float cos_angle = cosf(angle);
+	m_control_direction[X] = cos_angle * camera_forward[X] - sin_angle * camera_forward[Z];
+	m_control_direction[Z] = sin_angle * camera_forward[X] + cos_angle * camera_forward[Z];
+	
+	// different control schemes for analog stick and d-pad
+	if (control_pad.m_leftX == 0.0f && control_pad.m_leftY == 0.0f)
+	{
+		// d-pad control
+		m_analog_control = false;
+	
+		if (control_pad.m_leftLength == 0.0f)
+		{
+			m_control_magnitude = 0.0f;
+			m_control_pegged = false;
+		}
+		else
+		{
+			if (m_state == WALKING_GROUND && !control_pad.m_x.GetPressed())
+			{
+				m_control_magnitude = s_get_param(CRCD(0xc1528f7f, "walk_point"));
+			}
+			else
+			{
+				m_control_magnitude = 1.0f;
+			}
+			
+            m_control_pegged = true;
+		}
+	}
+	else
+	{
+		// analog stick control
+		m_analog_control = true;
+		
+		m_control_magnitude = control_pad.GetScaledLeftAnalogStickMagnitude() / 0.85f;
+		if (m_control_magnitude >= 1.0f)
+		{
+			m_control_magnitude = 1.0f;
+			m_control_pegged = true;
+		}
+		else
+		{
+			m_control_pegged = false;
+		}
+	}
+	
+	// during a forward control lock, ignore all input not in the forward direction
+	if (m_state == WALKING_GROUND && m_forward_control_lock)
+	{
+		m_control_magnitude = Mth::ClampMin(m_control_magnitude * Mth::DotProduct(m_control_direction, m_facing), 0.0f);
+		m_control_direction = m_facing;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWalkComponent::calculate_desired_speed (   )
+{
+	float desired_speed;
+	
+	if (m_control_magnitude == 0.0f)
+	{
+		desired_speed = 0.0f;
+	}
+	else
+	{
+		float walk_point = s_get_param(CRCD(0xc1528f7f, "walk_point"));
+		if (m_control_magnitude <= walk_point)
+		{
+			m_run_toggle = false;
+			desired_speed = Mth::LinearMap(0.0f, s_get_param(CRCD(0x79d182ad, "walk_speed")), m_control_magnitude, 0.3f, walk_point);
+		}
+		else
+		{
+			m_run_toggle = true;
+			desired_speed = Mth::LinearMap(s_get_param(CRCD(0x79d182ad, "walk_speed")), get_run_speed(), m_control_magnitude, walk_point, 1.0f);
+		}
+	}
+	
+	return desired_speed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CWalkComponent::calculate_feeler_offset_direction ( int contact )
+{
+	float angle = contact * (2.0f * Mth::PI / vNUM_FEELERS);
+	float cos_angle = cosf(angle);
+	float sin_angle = sinf(angle);
+
+	Mth::Vector end_offset_direction;
+	end_offset_direction[X] = cos_angle * m_facing[X] - sin_angle * m_facing[Z];
+	end_offset_direction[Y] = 0.0f;
+	end_offset_direction[Z] = sin_angle * m_facing[X] + cos_angle * m_facing[Z];
+	end_offset_direction[W] = 1.0f;
+	
+	return end_offset_direction;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::determine_stand_pos ( const Mth::Vector& proposed_stand_pos, Mth::Vector& stand_pos, CFeeler& feeler )
+{
+	// upward offset of standing position at maximum standable slope
+	float max_height_adjustment = 2.0f * s_get_param(CRCD(0x6da7f696, "feeler_height"));
+	
+	feeler.m_start = proposed_stand_pos;
+	feeler.m_start[Y] += max_height_adjustment;
+	feeler.m_end = proposed_stand_pos;
+	feeler.m_end[Y] -= s_get_param(CRCD(0xaebfa9cd, "stand_pos_search_depth"));
+	if (feeler.GetCollision())
+	{
+		stand_pos = feeler.GetPoint();
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(100, 100, 0, 0);
+		}
+		#endif
+		return true;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(255, 255, 0, 0);
+	}
+	#endif
+	
+	stand_pos = proposed_stand_pos;
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::update_run_speed_factor (   )
+{
+	if (GetSkaterScoreComponentFromObject(GetObject())->GetScore()->GetScorePotValue() > 0)
+	{
+		m_run_speed_factor = 1.0f;
+		return;
+	}
+	
+	m_run_speed_factor = Mth::ClampMin(m_run_speed_factor - s_get_param(CRCD(0x3fb31dfc, "run_adjust_rate")) * Tmr::FrameLength(), 0.0f);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::smooth_anim_speed ( uint32 previous_frame_event )
+{
+	if (m_state == WALKING_GROUND)
+	{
+		if (Tmr::ElapsedTime(m_state_timestamp)	< 100)
+		{
+			// for the first few frames of the ground state, simply use this frame's anim speed
+			m_last_frame_anim_effective_speed = m_anim_effective_speed;
+		}
+		else
+		{
+			// otherwise, use the smaller of the two anim speeds from this frame and the last
+			float this_frame_anim_effective_speed = m_anim_effective_speed;
+			m_anim_effective_speed = Mth::Min(m_anim_effective_speed, m_last_frame_anim_effective_speed);
+			m_last_frame_anim_effective_speed = this_frame_anim_effective_speed;
+			
+			// adjust the frame event as appropriate
+			if (m_frame_event == CRCD(0xaf895b3f, "Run") && m_anim_effective_speed == 0.0f)
+			{
+				switch (previous_frame_event)
+				{
+					case CRCC(0x1d537eff, "Skid"):
+					case CRCC(0xf28adbfc, "RotateLeft"):
+					case CRCC(0x912220f8, "RotateRight"):
+						m_frame_event = previous_frame_event;
+						break;
+					default:
+						m_frame_event = CRCD(0x9b46e749, "Stand");
+						break;
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::set_camera_overrides (   )
+{
+	switch (m_frame_event)
+	{
+		// case CRCC(0xf41aba21, "Hop"):
+			// mp_camera_component->SetOverrides(m_facing, 0.05f);
+			// break;
+		
+		case CRCC(0x2d9815c3, "HangMoveLeft"):
+		{
+			Mth::Vector facing = m_facing;
+			facing.RotateY(-0.95f);
+			mp_camera_component->SetOverrides(facing, 0.05f);
+			break;
+		}
+			
+		case CRCC(0x279b1f0b, "HangMoveRight"):
+		{
+			Mth::Vector facing = m_facing;
+			facing.RotateY(0.95f);
+			mp_camera_component->SetOverrides(facing, 0.05f);
+			break;
+		}
+					
+		case CRCC(0x4194ecca, "Hang"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+		
+		case CRCC(0xc84243da, "Ladder"):
+		case CRCC(0xaf5abc82, "LadderMoveUp"):
+		case CRCC(0xfec9dded, "LadderMoveDown"):
+			mp_camera_component->SetOverrides(m_facing, 0.05f);
+			break;
+					
+		case CRCC(0x4fe6069c, "AnimWait"):
+			if (m_anim_wait_camera_mode == AWC_CURRENT)
+			{
+				mp_camera_component->SetOverrides(m_facing, 0.05f);
+			}
+			else
+			{
+				mp_camera_component->SetOverrides(m_drift_goal_facing, 0.05f);
+			}
+			break;
+			
+		default:
+			mp_camera_component->UnsetOverrides();
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::update_anim_speeds (   )
+{
+	switch (m_anim_scale_speed)
+	{
+		case RUNNING:
+			if (m_anim_standard_speed > 0.0f)
+			{
+				mp_animation_component->SetAnimSpeed(m_anim_effective_speed / m_anim_standard_speed, false, false);
+				
+				// if walking partial anims are active, update the partial anim speed too
+				if (mp_physics_control_component->IsBoardMissing())
+				{
+					Script::CStruct* pParams = new Script::CStruct;
+					pParams->AddFloat(CRCD(0xf0d90109, "Speed"), m_anim_effective_speed / m_anim_standard_speed);
+					mp_animation_component->CallMemberFunction(CRCD(0xbd4edd44, "SetPartialAnimSpeed"), pParams, NULL);
+					delete pParams;
+				}
+			}
+			break;
+			
+		case HANGMOVE:
+		{
+			float new_anim_speed = m_anim_effective_speed / s_get_param(CRCD(0x1a47ffab, "hang_move_animation_speed"));
+			
+			Script::CStruct* pParams = new Script::CStruct;
+			pParams->AddFloat(CRCD(0xf0d90109, "Speed"), new_anim_speed);
+			mp_animation_component->CallMemberFunction(CRCD(0xbd4edd44, "SetPartialAnimSpeed"), pParams, NULL);
+			delete pParams;
+			
+			mp_animation_component->SetAnimSpeed(new_anim_speed, false, false);
+			break;
+		}
+					
+		case LADDERMOVE:
+		{
+			float new_anim_speed = m_anim_effective_speed / s_get_param(CRCD(0xab2db54, "ladder_move_speed"));
+			
+			Script::CStruct* pParams = new Script::CStruct;
+			pParams->AddFloat(CRCD(0xf0d90109, "Speed"), new_anim_speed);
+			mp_animation_component->CallMemberFunction(CRCD(0xbd4edd44, "SetPartialAnimSpeed"), pParams, NULL);
+			delete pParams;
+			
+			// Only ladder animation speed changes are sent to the server in net games.
+			if (!GameNet::Manager::Instance()->InNetGame() || Mth::Abs(new_anim_speed - m_last_ladder_anim_speed) < 0.1f)
+			{
+				mp_animation_component->SetAnimSpeed(new_anim_speed, false, false);
+			}
+			else
+			{
+				m_last_ladder_anim_speed = new_anim_speed;
+				mp_animation_component->SetAnimSpeed(m_last_ladder_anim_speed, true, false);
+			}
+			break;
+		}
+	
+		default:
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWalkComponent::get_gravity (   )
+{
+	if (!Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0x9c8c6df1, "CHEAT_MOON")))
+	{
+		return s_get_param(CRCD(0xa5e2da58, "gravity"));
+	}
+	else
+	{
+		Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag(Script::GetInteger(CRCD(0x9c8c6df1, "CHEAT_MOON")));
+		g_CheatsEnabled = true;
+		return 0.5f * s_get_param(CRCD(0xa5e2da58, "gravity"));
+	}
+}
+
+}
+
+#endif // TESTING_GUNSLINGER
diff --git a/Code/Gel/Components/WalkComponent.h b/Code/Gel/Components/WalkComponent.h
new file mode 100644
index 0000000..0b8f578
--- /dev/null
+++ b/Code/Gel/Components/WalkComponent.h
@@ -0,0 +1,643 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WalkComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  4/2/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_WALKCOMPONENT_H__
+#define __COMPONENTS_WALKCOMPONENT_H__
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#ifdef __NOPT_ASSERT__
+#define WHEADER { printf("%i:%s:%i: ", Tmr::GetRenderFrame(), __FILE__ + 15, __LINE__); }
+#define DUMP_WPOSITION { if (Script::GetInteger(CRCD(0x029ade47, "debug_skater_pos"))) { WHEADER; printf("m_pos = %g, %g, %g\n", m_pos[X], m_pos[Y], m_pos[Z]); } }
+#else
+#define DUMP_WPOSITION {   }
+#endif
+
+#define		CRC_WALK CRCD(0x726e85aa, "Walk")
+
+#define		GetWalkComponent() ((Obj::CWalkComponent*)GetComponent(CRC_WALK))
+#define		GetWalkComponentFromObject(pObj) ((Obj::CWalkComponent*)(pObj)->GetComponent(CRC_WALK))
+
+class CFeeler;
+		
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+
+namespace Nx
+{
+	class CCollCache;
+}
+              
+namespace Obj
+{
+	class CAnimationComponent;
+	class CModelComponent;
+	class CTriggerComponent;
+	class CWalkCameraComponent;
+	class CSkaterPhysicsControlComponent;
+	class CSkaterStateComponent;
+	class CMovableContactComponent;
+    class CRailNode;
+    class CRailManager;
+
+	struct SHangRailData
+	{
+		Mth::Vector					hang_point;
+		Mth::Vector					hang_facing;
+		float						along_rail_factor;
+		float						horizontal_hang_offset;
+		float						vertical_hang_offset;
+		uint32						initial_animation;
+		const CRailNode*			p_rail_node;
+	};
+	
+	struct SLadderRailData
+	{
+		Mth::Vector					climb_point;
+		Mth::Vector					climb_facing;
+		float						along_rail_factor;
+	};
+	
+class CWalkComponent : public CBaseComponent
+{
+	friend class CSkaterCorePhysicsComponent;
+	
+public:
+	enum EStateType
+	{
+		WALKING_GROUND = 0,
+		WALKING_AIR,
+		// WALKING_HOP,
+		WALKING_HANG,
+		WALKING_LADDER,
+		WALKING_ANIMWAIT,
+	};
+	
+	enum { NUM_WALKING_STATES = 5 };
+	
+	enum EAnimScaleSpeed
+	{
+		OFF,
+		RUNNING,
+        HANGMOVE,
+		LADDERMOVE
+	};
+	
+	enum
+	{
+		vNUM_FEELERS = 7,
+		vNUM_BARRIER_HEIGHT_FEELERS = 9
+	};
+	
+	typedef void (Obj::CWalkComponent::*AnimWaitCallbackPtr) (   );
+	
+private:
+	static const uint32 sp_state_names [ NUM_WALKING_STATES ];
+	
+public:
+    CWalkComponent();
+    virtual ~CWalkComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	EStateType						GetState (   ) { return m_state; }
+	
+private:
+	void							set_state ( EStateType state );
+	
+	void							go_on_ground_state (   );
+	void							calculate_horizontal_speed_and_facing ( float &horizontal_speed );
+	bool							adjust_horizonal_vel_for_environment (   );
+	void							adjust_facing_for_adjusted_horizontal_vel (   );
+	float							adjust_desired_speed_for_slope ( float desired_speed );
+	void							respond_to_ground (   );
+	void							adjust_curb_float_height (   );
+	void							account_for_movable_contact (   );
+	void							jump (   );
+	void							adjust_jump_for_ceiling_obstructions (   );
+	void							go_in_air_state (   );
+	void							control_horizontal_vel (   );
+	void							adjust_vertical_vel_for_ceiling (   );
+	void							check_for_landing ( const Mth::Vector& previous_pos, const Mth::Vector& final_pos );
+	void							maybe_wallplant (   );
+	void							uber_frig (   );
+	void							lerp_upright (   );
+	void							transition_lerp_upright (   );
+	bool							check_for_wall_push (   );
+	void							leave_movable_contact_for_air ( Mth::Vector& horizontal_vel, float& vertical_vel );
+	bool							maybe_hop_to_hang (   );
+	bool							maybe_drop_to_hang (   );
+	bool							maybe_grab_to_hang ( Mth::Vector start_pos, Mth::Vector end_pos );
+	bool							investigate_hang_rail_vicinity ( CFeeler& feeler, const Mth::Vector& preliminary_hang_point, SHangRailData& rail_data );
+	bool							vertical_hang_obstruction_check ( CFeeler& feeler, SHangRailData& rail_data, const Mth::Vector& check_offset_direction );
+	void							go_hop_state (   );
+	void							go_hang_state (   );
+	void							hang_movement ( float movement );
+	bool							check_environment_for_hang_movement_collisions ( const Mth::Vector& facing, float along_rail_factor, const Mth::Vector& pos, bool left, const Mth::Vector& perp_facing, Mth::Vector rail, const CRailNode* p_rail_start, const CRailNode* p_rail_end );
+	void							move_onto_neighboring_hang_rail ( float movement, bool descending, const Mth::Vector& old_rail );
+	bool							find_next_rail ( float& along_rail_factor, const CRailNode* p_current_start, const CRailNode* p_current_end, const CRailNode*& p_new_start, const CRailNode*& p_new_end );
+	void							set_pos_from_hang_rail_state (   );
+	bool							maybe_pull_up_from_hang ( Mth::Vector& proposed_stand_pos );
+	bool							maybe_jump_low_barrier (   );
+	bool							maybe_in_air_acid_drop (   );
+	bool							maybe_jump_to_acid_drop (   );
+	bool							maybe_climb_up_ladder ( bool user_requested = false );
+	bool							maybe_climb_down_ladder (   );
+	bool							maybe_grab_to_ladder ( const Mth::Vector& start_pos, const Mth::Vector& end_pos );
+	void							go_ladder_state (   );
+	void							ladder_movement (   );
+	void							off_ladder_bottom (   );
+	void							off_ladder_top (   );
+	void							calculate_anim_wait_facing_drift_parameters ( const Mth::Vector& goal_facing, float rotate_factor = 1.0f );
+    void                            go_anim_wait_state (   );
+    void                            anim_wait_complete (   );
+	bool							maybe_stick_to_rail (   );
+	void							setup_collision_cache (   );
+	void							update_critical_point_offset (   );
+	void							update_display_offset (   );
+	void							adjust_pos_for_false_wall (   );
+	void							extract_state_from_object (   );
+	void							copy_state_into_object (   );
+	void							get_controller_input (   );
+	float							calculate_desired_speed (   );
+	Mth::Vector						calculate_feeler_offset_direction ( int contact );
+	bool							determine_stand_pos ( const Mth::Vector& proposed_stand_pos, Mth::Vector& stand_pos, CFeeler& feeler );
+	float							get_run_speed (   );
+	float							get_gravity (   );
+	void							update_run_speed (   );
+	void							update_run_speed_factor (   );
+	void							smooth_anim_speed ( uint32 previous_frame_event );
+	void							set_camera_overrides (   );
+	void							update_anim_speeds (   );
+	float							get_hang_display_offset (   );
+	
+	void							drop_to_hang_complete (   );
+	void							pull_up_from_hang_to_ground_complete (   );
+	void							pull_up_from_hang_to_air_complete (   );
+	float							get_hang_critical_point_vert_offset (   );
+	void							move_to_ladder_bottom_complete (   );
+	void							move_to_ladder_top_complete (   );
+	void							off_ladder_bottom_complete (   );
+	void							off_ladder_top_to_ground_complete (   );
+	void							off_ladder_top_to_air_complete (   );
+	
+	static float					s_get_param ( uint32 checksum );
+	
+public:
+	bool							IsRunning (   );
+	bool							UseDPadCamera (   );
+	void							SetForwardControlLock ( bool forward_control_lock );
+	void							SetAssociatedCamera ( CCompositeObject* camera_obj );
+	
+	void							CollideWithOtherSkaterLost ( CCompositeObject* p_other_skater );
+    
+	Mth::Vector						GetCameraCollisionTargetOffset (   );
+	
+	void							ReadyWalkState ( bool to_ground_state );
+	void							CleanUpWalkState (   );
+	const SRailData*				GetRailData (   );
+	const SAcidDropData*			GetAcidDropData (   );
+    
+	bool							FilterHangRail ( const Mth::Vector& pos_a, const Mth::Vector& pos_b, const Mth::Vector& preliminary_hang_point, const Mth::Vector& grab_from_point, float along_rail_factor, SHangRailData& rail_data, bool drop_to_hang );
+	bool							FilterLadderUpRail ( const Mth::Vector& bottom_pos, const Mth::Vector& top_pos, const Mth::Matrix& orientation, SLadderRailData& rail_data );
+	bool							FilterLadderDownRail ( const Mth::Vector& bottom_pos, const Mth::Vector& top_pos, const Mth::Matrix& orientation, SLadderRailData& rail_data );
+	bool							FilterLadderAirGrabRail ( const Mth::Vector& bottom_pos, const Mth::Vector& top_pos, const Mth::Matrix& orientation, const Mth::Vector& preliminary_climb_point, float along_rail_factor, SLadderRailData& rail_data );
+	
+private:
+	EStateType						m_state;
+	
+	// Time stamp of when our state last changed.
+	Tmr::Time						m_state_timestamp;
+	
+	// State we changed from when m_state_timestamp was last set.
+	EStateType						m_previous_state;
+	
+	// Extracted from the object at frame start and back into the object at frame end.
+	Mth::Vector						m_pos;
+	Mth::Vector						m_horizontal_vel;
+	float							m_vertical_vel;
+	Mth::Vector						m_facing;
+	Mth::Vector						m_upward;
+	
+	// Cache our position at the frame's start
+	Mth::Vector						m_frame_start_pos;
+	
+	// Script event reporting our current state.  Set during each frame update and sent at the end of the update.
+	uint32							m_frame_event;
+	
+	// Last frame's frame event.
+	uint32							m_last_frame_event;
+	
+	// Current frame's frame length.
+	float							m_frame_length;
+	
+	// Current frame's control input.
+	Mth::Vector						m_control_direction;
+	float							m_control_magnitude;
+	bool							m_control_pegged;
+	bool							m_analog_control;
+	
+	// The grab button is the same as the transition button, and is thus ignored after a transition until it is released
+	bool							m_ignore_grab_button;
+	
+	// After transitioning from skating to walking in air, you are not allowed to acid drop until you first switch out of the air state.
+	bool							m_disallow_acid_drops;
+	
+	// The offset from the skater's true origin at which to display the skater.  Used to get CAS scaled skaters looking correct.
+	float							m_display_offset;
+	
+	struct SContact
+	{
+		// If this contact is in collision with the environment.
+		bool						in_collision;
+		
+		// The horizontal normal of the collided geometry.
+		Mth::Vector					normal;
+		
+		// If the collision in this direction is determined to be low enough to jump.
+		bool						jumpable;
+		
+		// Movement of the object we're in contact with (not accounting for its rotation) along its horizontal normal.
+		float						movement;
+		
+		// Must store the full feeler in order to trip triggers and get movable contact objects
+		CFeeler						feeler;
+		
+	// The walker looks around him in vNUM_FEELERS directions and stores the results here.  The final contact is felt for along the movement of
+	// the origin, and is only used while in the air.
+	} mp_contacts [ vNUM_FEELERS + 1 ];
+	
+	// Type of animation speed scaling to do.
+	EAnimScaleSpeed					m_anim_scale_speed;
+	
+	// Horizontal speed for the purposes of animation speed scaling.
+	float							m_anim_effective_speed;
+	float							m_last_frame_anim_effective_speed;
+	
+	#ifdef SCRIPT_WALK_DRAG
+	// Factor multiplied by all velocities.  Set by script.
+	float							m_script_drag_factor;
+	#endif
+	
+	// Used to lock control in the forward direction (during a camera flush request)
+	bool							m_forward_control_lock;
+	
+	// When on ground, the walker is often floated above the ground in an attempt to smooth out stair/curb climbs.
+	float							m_curb_float_height;
+	
+	// If m_curb_float_height was adjusted this frame.  If not, we lerp it to zero.
+	bool							m_curb_float_height_adjusted;
+	
+	// The critical point is a position relative to the skater which MUST ALWAYS STAY ON THE CORRECT SIDE OF COLLIDABLE GEO.  Normally, this is simply the
+	// origin.  However, sometimes this must be adjusted (during hanging).  Once hanging is over, the critical point is pushed back to the origin.
+	Mth::Vector						m_critical_point_offset;
+	
+	// Counts down while we're slerping upright after a transition from skating
+	float m_rotate_upright_timer;
+	
+	// Angle over and axis around which to slerp to upright
+	float m_rotate_upright_angle;
+	Mth::Vector m_rotate_upright_axis;
+	
+	// The matrix's [Z][Y] component last time facing was extracted from the matrix.  Stored so it can be restored when rebuilding the matrix, thus
+	// preventing unwanted rotations.
+	float m_frame_initial_facing_Y;
+	
+	struct SSpecialTransitionData
+	{
+		// The type of transition causing the switch to skating.
+		ESpecialTransitionType		type;
+		
+		// The state data for that transition.
+		SRailData					rail_data;
+		SAcidDropData				acid_drop_data;
+		
+	// When the walker transitions to skating in some manner which requires additional state information, this structure holds that information before it
+	// is passed off to the skate physics.
+	}								m_special_transition_data;
+	
+	// The collision cache used.
+	Nx::CCollCache*					mp_collision_cache;
+	
+	// Associated camera.
+	CCompositeObject*				mp_camera;
+	CWalkCameraComponent*			mp_camera_component;
+	
+    // If associated with a rail, this is the rail node and rail manager.
+    const CRailNode*              	mp_rail_start;
+    const CRailNode*              	mp_rail_end;
+    CRailManager*                   mp_rail_manager;
+	
+	// Peer components.
+	CInputComponent*				mp_input_component;
+	CAnimationComponent*			mp_animation_component;
+	CModelComponent*				mp_model_component;
+	CTriggerComponent*				mp_trigger_component;
+	CSkaterPhysicsControlComponent*	mp_physics_control_component;
+	CMovableContactComponent*		mp_movable_contact_component;
+	CSkaterStateComponent*			mp_state_component;
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+    
+	/////////////////////////////////
+	// WALKING_GROUND state variables
+	
+	// Normal of the ground on which we are standing.  Only meaningful when on the ground.  Set the previous frame.
+	Mth::Vector						m_ground_normal;
+	
+	// Certain things such as rotation rate and camera lerp are increased when this is set.
+	bool							m_run_toggle;
+	
+	struct SWallPushTest
+	{
+		// Whether we currently testing.
+		bool						active;
+		
+		// Time at which this test began.
+		Tmr::Time					test_start_time;
+		
+	// Data used to detect when a player is pushing into a wall in order to climb it.
+	}								m_wall_push_test;
+	
+	// When doing anim speed scaling when running, this is set to the run speed which corresponds to the current animation playing at standard speed.
+	float							m_anim_standard_speed;
+	
+	// A copy of the latest feeler which contacted the groud under us.
+	CFeeler							m_last_ground_feeler;
+	bool							m_last_ground_feeler_valid;
+	
+	// lerp factor between standard run speed and combo run speed
+	float							m_run_speed_factor;
+	
+	// If true, no transitional period of reduced deceleration is used from skating.
+	// bool							m_cancel_transition_momentum;
+	
+	// Set to true when the walker's speeds is significantly reduced due to interactions with walls.  Causes the rotation rate of the skater to be dropped
+	// to walking levels next frame.  A beter solution would be to reduce rotation rate THIS frame.
+	bool							m_drop_rotation_rate;
+	
+	// Latest duration over which the controller has been continuously pegged.  Only used on the ground and reset to a large value whenever you enter ground.
+	float							m_control_pegged_duration;
+	
+	// True if the controller was pegged last frame.  Only updated while on the ground.
+	bool							m_last_frame_controller_pegged;
+	
+	/////////////////////////////////
+	// WALKING_AIR state variables
+	
+	// The forward horizontal direction of the jump which, when coming out of skating or because of in-air velocity control, diverges from the direction of velocity
+	Mth::Vector						m_primary_air_direction;
+	
+	// This term of our horizontal velocity is due to leaping from a movable contact, and is not adjustable via in-air control.
+	Mth::Vector						m_uncontrollable_air_horizontal_vel;
+	
+	// When non-zero, it counts down and suppresses in air velocity control.
+	float							m_in_air_control_suppression_timer;
+	
+	// Extra drift velocity used while in the air.
+	Mth::Vector						m_in_air_drift_vel;
+	
+	// Extra drift is turned off after the skater reaches this height.
+	float							m_in_air_drift_stop_height;
+	
+	struct SFalseWall
+	{
+		// This tracks if a false wall is active.
+		bool						active;
+		
+		// The false wall is defined by a normal.
+		Mth::Vector					normal;
+		
+		// And a scalar.
+		float						distance;
+		
+		// The false wall is automatically turned off when this height is exceeded.
+		float						cancel_height;
+		
+	// A false wall is sometimes set up while in the air to prevent movement in a certain direction.
+	}								m_false_wall;
+	
+	/////////////////////////////////
+	// WALKING_HANG state variables
+	
+	// Measures our position along the current rail as a parameter running from 0.0 to 1.0.
+	float							m_along_rail_factor;
+	
+	// Measures the hang position offset from the actual rail we are hanging from.
+	float							m_vertical_hang_offset;
+	float							m_horizontal_hang_offset;
+	
+	// Checksum describing the type of initial hang animation to play.
+	uint32							m_initial_hang_animation;
+	
+	// Speed the skater is shimmying at
+	float							m_hang_move_vel;
+    
+	/////////////////////////////////
+	// WALKING_HOP state variables
+	
+    // The position we will have at the end of the hop.
+    // Mth::Vector                     m_goal_hop_pos;
+    
+    // The facing we will have at the end of the hop.
+    // Mth::Vector                     m_goal_hop_facing;
+	
+	// Hang state variable.  Set at hop start.
+	// float						m_along_rail_factor;
+	
+	// Hang state variable.  Set at hop start.
+	// uint32						m_initial_hang_animation;
+    
+	/////////////////////////////////
+	// WALKING_LADDER state variables
+	
+	// Shared with hang state.
+	// float						m_along_rail_factor;
+	
+	// Only ladder animation speed changes are sent to the server are updated in net games.  This holds the last animimation speed sent.
+	float							m_last_ladder_anim_speed;
+    
+	/////////////////////////////////
+	// WALKING_ANIMWAIT state variables
+	
+	// Position at the start of the animation wait.
+	Mth::Vector						m_anim_wait_initial_pos;
+	
+	// Facing at the start of the aniamtion wait.
+	Mth::Vector						m_anim_wait_initial_facing;
+	
+    // Amount to move the object at the end of the animation wait.
+    Mth::Vector                     m_anim_wait_goal_pos;
+	
+	// Goal display offset of the animation wait.
+	float							m_drift_initial_display_offset;
+	float							m_drift_goal_display_offset;
+	
+	// Goal facing of the position drift.
+	Mth::Vector						m_drift_goal_facing;
+	
+	// Factor of animation in which to do the rotation in.
+	float							m_drift_goal_rotate_factor;
+	
+	// The angle over which the facing must drift.
+	float							m_drift_angle;
+    
+	// Accumulates position offset due to a movable contact during an animation wait.
+	Mth::Vector						m_offset_due_to_movable_contact;
+	
+	// The behavior of the camera during the animation wait.
+	enum EAnimWaitCameraModeType			
+	{
+		AWC_CURRENT,
+		AWC_GOAL
+	}								m_anim_wait_camera_mode;
+	
+	// Callback function to call upon completion of the animation wait.
+	AnimWaitCallbackPtr				mp_anim_wait_complete_callback;
+	
+	// WALKING_GROUND state variable sometimes set at start of animation wait based on the animation end ground situation
+	// Mth::Vector					m_ground_normal;
+	// CFeeler						m_last_ground_feeler;
+	// bool							m_last_ground_feeler_valid;
+	
+	// WALKING_AIR state variable partially setup at the start of an animation wait
+	// struct SFalseWall			m_false_wall;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float CWalkComponent::s_get_param ( uint32 checksum )
+{
+	Script::CStruct* p_walk_params = Script::GetStructure(CRCD(0x6775e538, "walk_parameters"));
+	Dbg_Assert(p_walk_params);
+	
+	float param;
+	p_walk_params->GetFloat(checksum, ¶m, Script::ASSERT);
+	return param;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float CWalkComponent::get_run_speed (   )
+{
+	return Mth::Lerp(s_get_param(CRCD(0xcc461b87, "run_speed")), s_get_param(CRCD(0x7213ef07, "combo_run_speed")), m_run_speed_factor);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CWalkComponent::IsRunning (   )
+{
+	return m_run_toggle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CWalkComponent::SetForwardControlLock ( bool forward_control_lock )
+{
+	m_forward_control_lock = forward_control_lock;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const SRailData* CWalkComponent::GetRailData (   )
+{
+	return m_special_transition_data.type == RAIL_TRANSITION ? &m_special_transition_data.rail_data : NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const SAcidDropData* CWalkComponent::GetAcidDropData (   )
+{
+	return m_special_transition_data.type == ACID_DROP_TRANSITION ? &m_special_transition_data.acid_drop_data : NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CWalkComponent::UseDPadCamera (   )
+{
+	
+	return mp_input_component->GetControlPad().m_leftX == 0.0f && mp_input_component->GetControlPad().m_leftY == 0.0f && m_control_pegged;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Mth::Vector CWalkComponent::GetCameraCollisionTargetOffset (   )
+{
+	switch (m_state)
+	{
+		case WALKING_HANG:
+		case WALKING_LADDER:
+		case WALKING_ANIMWAIT:
+			return -18.0f * m_facing;
+		
+		case WALKING_AIR:
+			if ((m_previous_state == WALKING_HANG || m_previous_state ==WALKING_LADDER || m_previous_state ==WALKING_LADDER)
+				&& Tmr::ElapsedTime(m_state_timestamp) < 150)
+			{
+				return -18.0f * m_facing;
+			}
+			else
+			{
+				return Mth::Vector(0.0f, 0.0f, 0.0f);
+			}
+			
+		case WALKING_GROUND:
+		default:
+			return Mth::Vector(0.0f, 0.0f, 0.0f);
+	}
+}
+
+}
+
+#endif
+
diff --git a/Code/Gel/Components/WalkHangUtil.cpp b/Code/Gel/Components/WalkHangUtil.cpp
new file mode 100644
index 0000000..bb7fbcb
--- /dev/null
+++ b/Code/Gel/Components/WalkHangUtil.cpp
@@ -0,0 +1,1450 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WalkHangUtil.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/2/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*
+ * Contains those CWalkComponent member functions which are concerned with hanging.
+ */
+
+namespace Obj
+{
+     
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+bool CWalkComponent::maybe_hop_to_hang (   )
+{
+	SHangRailData rail_data;
+	
+	Mth::Vector search_start_pos = m_pos;
+	search_start_pos[Y] += s_get_param(CRCD(0x2c942693, "lowest_hang_height"));
+	
+	// clean vectors; facing is a directional vector
+	search_start_pos[W] = 1.0f;
+	m_facing[W] = 0.0f;
+	
+	bool rail_found = false;
+	
+	float hop_to_hang_reach = s_get_param(CRCD(0x8b785f26, "max_hop_to_hang_height")) - s_get_param(CRCD(0x2c942693, "lowest_hang_height"));
+	float max_hop_to_hang_forward_reach = s_get_param(CRCD(0x3b4ca389, "max_hop_to_hang_forward_reach"));
+	float max_hop_to_hang_backward_reach = s_get_param(CRCD(0xae806bee, "max_hop_to_hang_backward_reach"));
+	
+	// check level for appropriate rails
+	if (rail_found = Mdl::Skate::Instance()->GetRailManager()->CheckForHopToHangRail(
+		search_start_pos,
+		hop_to_hang_reach,
+		m_facing,
+		max_hop_to_hang_forward_reach,
+		max_hop_to_hang_backward_reach,
+		this,
+		rail_data,
+		&mp_rail_node
+	))
+	{
+		mp_rail_manager = Mdl::Skate::Instance()->GetRailManager();
+		mp_movable_contact_component->LoseAnyContact();
+	}
+	else
+	{
+		float max_hop_to_movable_hang_vel_sqr = s_get_param(CRCD(0x511cb4, "max_hop_to_movable_hang_vel"));
+		max_hop_to_movable_hang_vel_sqr *= max_hop_to_movable_hang_vel_sqr;
+		
+		// check moving objects for appropriate rails
+		for (CRailManagerComponent* p_rail_manager_component = static_cast< CRailManagerComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_RAILMANAGER));
+			p_rail_manager_component && !rail_found;
+			p_rail_manager_component = static_cast< CRailManagerComponent* >(p_rail_manager_component->GetNextSameType()))
+		{
+			// only hop to moving hangs below a threshold velocity
+			if (p_rail_manager_component->GetObject()->GetVel().LengthSqr() > max_hop_to_movable_hang_vel_sqr) continue;
+			
+			Mth::Matrix obj_matrix_inv = p_rail_manager_component->UpdateRailManager();
+			obj_matrix_inv.Invert();
+
+			// transform into the frame of the moving object
+			Mth::Vector obj_frame_search_start_pos = obj_matrix_inv.Transform(search_start_pos);
+			Mth::Vector obj_frame_facing = obj_matrix_inv.Transform(m_facing);
+
+			if (rail_found = p_rail_manager_component->GetRailManager()->CheckForHopToHangRail(
+				obj_frame_search_start_pos,
+				hop_to_hang_reach,
+				obj_frame_facing,
+				max_hop_to_hang_forward_reach,
+				max_hop_to_hang_backward_reach,
+				this,
+				rail_data,
+				&mp_rail_node
+			))
+			{
+				mp_rail_manager = p_rail_manager_component->GetRailManager();
+				mp_movable_contact_component->ObtainContact(p_rail_manager_component->GetObject());
+			}
+		}
+	}
+	
+	if (!rail_found) return false;
+	
+	// we've found a rail to hop up to
+	m_goal_hop_pos = rail_data.hang_point;
+	m_goal_hop_facing = rail_data.hang_facing;
+	m_along_rail_factor = rail_data.along_rail_factor;
+	m_vertical_hang_offset = rail_data.vertical_hang_offset;
+	m_horizontal_hang_offset = rail_data.horizontal_hang_offset;
+	m_initial_hang_animation = rail_data.initial_animation;
+	
+	// calculate the vertical velocity required to jump to the hang point
+	#ifdef __NOPT_ASSERT__
+	if (m_pos[Y] >= m_goal_hop_pos[Y])
+	{
+		DUMPV(m_pos);
+		DUMPV(m_goal_hop_pos);
+	}
+	Dbg_Assert(m_pos[Y] < m_goal_hop_pos[Y]);
+	#endif
+	
+	float hop_target_vert_vel = s_get_param(CRCD(0x7aa618cc, "hop_target_vert_vel"));
+	float gravity = s_get_param(CRCD(0xa5e2da58, "gravity"));
+	
+	// ready state for the hop to hang
+	
+	// calculate the required hop velocity
+	m_vertical_vel = sqrtf(Mth::Sqr(hop_target_vert_vel) + 2.0f * gravity * (m_goal_hop_pos[Y] - m_pos[Y]));
+	
+	// calculate the required hop duration
+	float duration = (m_vertical_vel + hop_target_vert_vel) / gravity;
+	
+	// not neccesarly exactly in the check_direction of the facing
+	m_horizontal_vel = (1.0f / duration) * (m_goal_hop_pos - m_pos);
+	
+	// trip the ground's jump triggers
+	if (m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+		if (should_bail_from_frame()) return true;
+	}
+	
+	set_state(WALKING_HOP);
+    
+	m_frame_event = CRCD(0xf41aba21, "Hop");
+	
+	return true;
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_drop_to_hang (   )
+{
+	// check to see if we're in a bail
+	if (mp_core_physics_component->GetFlag(IS_BAILING)) return false;
+	
+	// only drop to hang when moving slowly
+	if (m_horizontal_vel.LengthSqr() > Mth::Sqr(s_get_param(CRCD(0x1d1e6af, "drop_to_hang_speed_factor")) * get_run_speed())) return false;
+	
+	// clean vector
+	m_frame_start_pos[W] = 1.0f;
+	m_pos[W] = 1.0f;
+	
+	SHangRailData rail_data;
+	
+	bool rail_found = false;
+	
+	// check level for appropriate rails
+	mp_rail_manager = Mdl::Skate::Instance()->GetRailManager();
+	if (rail_found = mp_rail_manager->CheckForHangRail(
+		m_frame_start_pos,
+		m_pos,
+		-m_facing,
+		this,
+		rail_data,
+		Script::GetFloat(CRCD(0xa8276303, "Drop_To_Climb_Max_Snap"))
+	))
+	{
+		mp_movable_contact_component->LoseAnyContact();
+	}
+	else
+	{
+		// check moving objects for appropriate rails
+		for (CRailManagerComponent* p_rail_manager_component = static_cast< CRailManagerComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_RAILMANAGER));
+			p_rail_manager_component && !rail_found;
+			p_rail_manager_component = static_cast< CRailManagerComponent* >(p_rail_manager_component->GetNextSameType()))
+		{
+			Mth::Matrix obj_matrix_inv = p_rail_manager_component->UpdateRailManager();
+			obj_matrix_inv.Invert();
+
+			// transform into the frame of the moving object
+			Mth::Vector obj_frame_start_pos = obj_matrix_inv.Transform(m_frame_start_pos);
+			Mth::Vector obj_pos = obj_matrix_inv.Transform(m_pos);
+
+			mp_rail_manager = p_rail_manager_component->GetRailManager();
+			if (rail_found = mp_rail_manager->CheckForHangRail(
+				obj_frame_start_pos,
+				obj_pos,
+				-m_facing,
+				this,
+				rail_data,
+				Script::GetFloat(CRCD(0xa8276303, "Drop_To_Climb_Max_Snap"))
+			))
+			{
+				mp_movable_contact_component->ObtainContact(p_rail_manager_component->GetObject());
+			}
+		}
+	}
+
+	if (!rail_found) return false;
+	
+	// we've found a rail to drop down to
+	
+	mp_rail_start = rail_data.p_rail_node;
+	mp_rail_end = mp_rail_start->GetNextLink();
+	Dbg_Assert(mp_rail_end);
+	
+	// zero velocities	
+	m_vertical_vel = 0.0f;
+	m_horizontal_vel.Set();
+	
+	m_along_rail_factor = rail_data.along_rail_factor;
+	m_vertical_hang_offset = rail_data.vertical_hang_offset;
+	m_horizontal_hang_offset = rail_data.horizontal_hang_offset;
+    
+    // setup an anim wait state
+	
+	m_anim_wait_initial_pos = m_pos;
+	m_anim_wait_goal_pos = rail_data.hang_point;
+	
+    mp_anim_wait_complete_callback = &Obj::CWalkComponent::drop_to_hang_complete;
+	
+	calculate_anim_wait_facing_drift_parameters(rail_data.hang_facing, s_get_param(CRCD(0xfdc8aec0, "drop_to_hang_rotate_factor")));
+	
+	m_anim_wait_camera_mode = AWC_GOAL;
+	
+	m_critical_point_offset = s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * rail_data.hang_facing;
+	
+	// setup the false wall incase the player jumps out of the animation wait
+	m_false_wall.normal = rail_data.hang_facing;
+	m_false_wall.cancel_height = m_pos[Y];
+	
+	m_drift_initial_display_offset = m_display_offset;
+	m_drift_goal_display_offset = get_hang_display_offset();
+
+	set_state(WALKING_ANIMWAIT);
+
+	m_frame_event = CRCD(0xeb71d98e, "DropToHang");
+    
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::drop_to_hang_complete (   )
+{
+	m_critical_point_offset = s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * m_facing;
+	m_critical_point_offset[Y] = get_hang_critical_point_vert_offset();
+	
+	set_state(WALKING_HANG);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_grab_to_hang ( Mth::Vector start_pos, Mth::Vector end_pos )
+{
+	// only grab to hang when moving down
+	if (m_vertical_vel > 50.0f) return false;
+	
+	start_pos[Y] += s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset"));
+	end_pos[Y] += s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset"));
+	
+	// clean vectors
+	start_pos[W] = 1.0f;
+	end_pos[W] = 1.0f;
+	
+	SHangRailData rail_data;
+	
+	bool rail_found = false;
+	
+	// check level for appropriate rails
+	mp_rail_manager = Mdl::Skate::Instance()->GetRailManager();
+	if (rail_found = mp_rail_manager->CheckForHangRail(
+		start_pos,
+		end_pos,
+		m_facing,
+		this,
+		rail_data,
+		Script::GetFloat(CRCD(0x30ce7f2c, "Climb_Max_Snap"))
+	))
+	{
+		mp_movable_contact_component->LoseAnyContact();
+	}
+	else
+	{
+		// check moving objects for appropriate rails
+		for (CRailManagerComponent* p_rail_manager_component = static_cast< CRailManagerComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_RAILMANAGER));
+			p_rail_manager_component && !rail_found;
+			p_rail_manager_component = static_cast< CRailManagerComponent* >(p_rail_manager_component->GetNextSameType()))
+		{
+			Mth::Matrix obj_matrix_inv = p_rail_manager_component->UpdateRailManager();
+			obj_matrix_inv.Invert();
+			
+			// transform into the frame of the moving object
+			Mth::Vector obj_frame_start_pos = obj_matrix_inv.Transform(start_pos);
+			Mth::Vector obj_frame_end_pos = obj_matrix_inv.Transform(end_pos);
+			
+			mp_rail_manager = p_rail_manager_component->GetRailManager();
+			if (rail_found = mp_rail_manager->CheckForHangRail(
+				obj_frame_start_pos,
+				obj_frame_end_pos,
+				m_facing,
+				this,
+				rail_data,
+				Script::GetFloat(CRCD(0x30ce7f2c, "Climb_Max_Snap"))
+			))
+			{
+				mp_movable_contact_component->ObtainContact(p_rail_manager_component->GetObject());
+			}
+		}
+	}
+	
+	if (!rail_found) return false;
+		
+	// we've found a rail to hang onto
+	
+	mp_rail_start = rail_data.p_rail_node;
+	mp_rail_end = mp_rail_start->GetNextLink();
+	Dbg_Assert(mp_rail_end);
+	
+	m_pos = rail_data.hang_point;
+	DUMP_WPOSITION
+	m_facing = rail_data.hang_facing;
+	m_along_rail_factor = rail_data.along_rail_factor;
+	m_vertical_hang_offset = rail_data.vertical_hang_offset;
+	m_horizontal_hang_offset = rail_data.horizontal_hang_offset;
+	m_initial_hang_animation = rail_data.initial_animation;
+	
+	m_critical_point_offset = s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * m_facing;
+	m_critical_point_offset[Y] = get_hang_critical_point_vert_offset();
+	
+	m_horizontal_vel.Set();
+	m_vertical_vel = 0.0f;
+	
+	m_display_offset = get_hang_display_offset();
+
+	set_state(WALKING_HANG);
+
+	m_frame_event = CRCD(0x4194ecca, "Hang");
+	
+	mp_core_physics_component->SetFlagTrue(SNAPPED);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::FilterHangRail ( const Mth::Vector& pos_a, const Mth::Vector& pos_b, const Mth::Vector& preliminary_hang_point, const Mth::Vector& grab_from_point, float along_rail_factor, SHangRailData& rail_data, bool drop_to_hang )
+{
+	// check this rail for potential hang use
+	
+	float hang_vert_origin_offset = s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset")); 
+	
+	// never snap upward to a hang rail
+	if (m_state == WALKING_AIR && preliminary_hang_point[Y] > grab_from_point[Y]) return false;
+	
+	// never snap too far vertically to a hang rail
+	if (m_state == WALKING_AIR && Mth::Abs(preliminary_hang_point[Y] - grab_from_point[Y]) > 12.0f) return false;
+	
+	// is the rail too steep
+	Mth::Vector rail = pos_b - pos_a;
+	Mth::Vector rail_direction = rail;
+	rail_direction.Normalize();
+	if (Mth::Abs(rail_direction[Y]) > sinf(Mth::DegToRad(s_get_param(CRCD(0x98724ebc, "hang_max_rail_ascent"))))) return false;
+	
+	// calculate the facing we'd have while hanging
+	
+	rail_data.hang_facing.Set(-(rail[Z]), 0.0f, rail[X]);
+	rail_data.hang_facing.Normalize();
+	if (!drop_to_hang)
+	{
+		// normally, we attempt retain our current facing
+		if (Mth::DotProduct(rail_data.hang_facing, m_facing) < 0.0f)
+		{
+			rail_data.hang_facing.Negate();
+		}
+	}
+	else
+	{
+		// unless dropping to a hang
+		if (Mth::DotProduct(rail_data.hang_facing, m_facing) > 0.0f)
+		{
+			rail_data.hang_facing.Negate();
+		}
+	}
+
+	bool rail_points_left = rail_data.hang_facing[Z] * rail_direction[X] - rail_data.hang_facing[X] * rail_direction[Z] > 0.0f;
+	if ((rail_data.p_rail_node->GetFlag(NO_HANG_WITH_LEFT_ALONG_RAIL) && rail_points_left)
+		|| (rail_data.p_rail_node->GetFlag(NO_HANG_WITH_RIGHT_ALONG_RAIL) && !rail_points_left))
+	{
+		rail_data.hang_facing.Negate();
+		rail_points_left = !rail_points_left;
+	}
+	
+	// investigate the hang point to see if its a good hang
+	
+	CFeeler feeler;
+	Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
+	Mth::CBBox bbox(
+		preliminary_hang_point - Mth::Vector(24.0f, 24.0f, 24.0f),
+		preliminary_hang_point + Mth::Vector(24.0f, 12.0f + hang_vert_origin_offset, 24.0f)
+	);
+	p_coll_cache->Update(bbox);
+	feeler.SetCache(p_coll_cache);
+	
+	rail_data.along_rail_factor = along_rail_factor;
+	
+	if (!investigate_hang_rail_vicinity(feeler, preliminary_hang_point, rail_data))
+	{
+		// if we're attempting to grab a rail from the air, try both possible facings
+		if (m_state == WALKING_AIR || !drop_to_hang)
+		{
+			// see if we're allowed to try the other facing
+			if ((rail_data.p_rail_node->GetFlag(NO_HANG_WITH_LEFT_ALONG_RAIL) && !rail_points_left)
+				|| (rail_data.p_rail_node->GetFlag(NO_HANG_WITH_RIGHT_ALONG_RAIL) && rail_points_left))
+			{
+				Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+				return false;
+			}
+			
+			rail_data.hang_facing.Negate();
+			if (!investigate_hang_rail_vicinity(feeler, preliminary_hang_point, rail_data))
+			{
+				Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+				return false;
+			}
+		}
+		else
+		{
+			Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+			return false;
+		}
+	}
+	
+	rail_data.vertical_hang_offset = rail_data.hang_point[Y] - preliminary_hang_point[Y];
+	rail_data.horizontal_hang_offset = Mth::DotProduct(rail_data.hang_facing, rail_data.hang_point - preliminary_hang_point);
+	
+	// determine which sort of initial hang animation would be used
+	feeler.m_start = rail_data.hang_point + s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * rail_data.hang_facing;
+	feeler.m_start[Y] += s_get_param(CRCD(0xdbb3d53e, "hang_init_anim_feeler_height"));
+	feeler.m_end = feeler.m_start;
+	feeler.m_end += s_get_param(CRCD(0x2f83ae83, "hang_init_anim_feeler_length")) * rail_data.hang_facing;
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 0);
+		}
+		#endif
+		rail_data.initial_animation = CRCD(0xec0a1009, "WALL");
+	}
+	else
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(0, 0, 255, 0);
+		}
+		#endif
+		rail_data.initial_animation = CRCD(0x1ee948a5, "SWING");
+	}
+	
+	Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::investigate_hang_rail_vicinity ( CFeeler& feeler, const Mth::Vector& preliminary_hang_point, SHangRailData& rail_data )
+{
+	/* turning off geo investigation for hang point adjustment
+	// run a feeler up/down at the preliminary hang point to find the true top of the ledge
+	feeler.SetLine(preliminary_hang_point, preliminary_hang_point);
+	feeler.m_start[Y] += s_get_param(CRCD(0x1bc501ab, "ledge_top_feeler_up"));
+	feeler.m_end[Y] -= s_get_param(CRCD(0xf5f39a8, "ledge_top_feeler_down"));
+	if (feeler.GetCollision())
+	{
+		// found a ledge top
+		rail_data.hang_point = feeler.GetPoint();
+	}
+	else
+	{
+		// use the prelimiary hang point
+		rail_data.hang_point = preliminary_hang_point;
+	}
+	
+	// run a feeler in an inch below the hang point along the hang facing to find the front of the ledge
+	feeler.m_start = rail_data.hang_point - s_get_param(CRCD(0x95fd5dc5, "ledge_front_feeler_back")) * rail_data.hang_facing;
+	feeler.m_end = rail_data.hang_point + s_get_param(CRCD(0xccb4f5d2, "ledge_front_feeler_forward")) * rail_data.hang_facing;
+	feeler.m_start[Y] -= 1.0f;
+	feeler.m_end[Y] -= 1.0f;
+	if (feeler.GetCollision())
+	{
+		// found a ledge front
+		rail_data.hang_point = feeler.GetPoint();
+		rail_data.hang_point[Y] += 1.0f;
+	}
+	// hang a touch outside of it
+	rail_data.hang_point -= 2.0f * rail_data.hang_facing;
+	*/
+	
+	rail_data.hang_point = preliminary_hang_point;
+	
+	Mth::Vector left(-rail_data.hang_facing[Z], 0.0f, rail_data.hang_facing[X]);
+	
+	Mth::Vector rail = mp_rail_manager->GetPos(rail_data.p_rail_node) - mp_rail_manager->GetPos(rail_data.p_rail_node->GetNextLink());
+	
+	// now that we have the hang position, run two feelers up and down the hang location to insure there is room
+	if (!vertical_hang_obstruction_check(feeler, rail_data, left) || !vertical_hang_obstruction_check(feeler, rail_data, -left)) return false;
+
+	// adjust the hang point for the origin of the hanging skater
+	rail_data.hang_point[Y] -= s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset"));
+	rail_data.hang_point += s_get_param(CRCD(0x262a9467, "hang_horiz_origin_offset")) * rail_data.hang_facing;
+	
+	// use our move feelers for a look around
+	if (check_environment_for_hang_movement_collisions(rail_data.hang_facing, rail_data.along_rail_factor, rail_data.hang_point, true, left, rail, rail_data.p_rail_node, rail_data.p_rail_node->GetNextLink())) return false;
+	if (check_environment_for_hang_movement_collisions(rail_data.hang_facing, rail_data.along_rail_factor, rail_data.hang_point, false, left, rail, rail_data.p_rail_node, rail_data.p_rail_node->GetNextLink())) return false;
+	
+	// adjust the feeler target check point based on hanging's critical point offset
+	Mth::Vector critical_point = rail_data.hang_point + s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * rail_data.hang_facing;
+	critical_point[Y] += get_hang_critical_point_vert_offset();
+	
+	// check for obstructions to our hang point; it is critical that our critical point remains on the happy side of collision geometry
+	feeler.m_start = m_pos;
+	feeler.m_end = critical_point;
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(255, 0, 255, 0);
+	}
+	#endif
+	if (feeler.GetCollision())
+	{
+		if (m_state == WALKING_GROUND)
+		{
+			// we found an obstruction taking a direct route; if we'll be hopping to the rail, try an up-and-over route
+			
+			feeler.m_end = m_pos;
+			feeler.m_end[Y] += s_get_param(CRCD(0xa85ec070, "hop_obstruction_feeler_up"));
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(255, 0, 255, 0);
+			}
+			#endif
+			if (feeler.GetCollision())
+			{
+				#ifdef __USER_DAN__
+				if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+				{
+					Gfx::AddDebugStar(feeler.GetPoint(), 12.0f, MAKE_RGB(0, 255, 255), 0);
+				}
+				#endif
+				return false;
+			}
+			
+			feeler.m_start = feeler.m_end;
+			feeler.m_end = critical_point;
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+			{
+				feeler.DebugLine(255, 0, 255, 0);
+			}
+			#endif
+			if (feeler.GetCollision())
+			{
+				#ifdef __USER_DAN__
+				if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+				{
+					Gfx::AddDebugStar(feeler.GetPoint(), 12.0f, MAKE_RGB(0, 255, 255), 0);
+				}
+				#endif
+				return false;
+			}
+		}
+		else
+		{
+			return false;
+		}
+	}
+	
+	return true;
+}
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::vertical_hang_obstruction_check ( CFeeler& feeler, SHangRailData& rail_data, const Mth::Vector& check_offset_direction )
+{
+	feeler.m_start = rail_data.hang_point + s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * rail_data.hang_facing
+		+ s_get_param(CRCD(0xafbf7687, "hang_obstruction_feeler_side")) * check_offset_direction;
+	feeler.m_end = feeler.m_start;
+	feeler.m_start[Y] += -s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset")) + get_hang_critical_point_vert_offset();
+	feeler.m_end[Y] += s_get_param(CRCD(0x8f35f29f, "hang_obstruction_feeler_up"));
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 50, 0);
+		}
+		#endif
+		return false;
+	}
+	Mth::Swap(feeler.m_start, feeler.m_end);
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(0, 255, 50, 0);
+		}
+		#endif
+		return false;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(200, 200, 50, 0);
+	}
+	#endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+void CWalkComponent::go_hop_state (   )
+{
+	// During the hop to hang state, the player has no control.  We simply move the goal position.
+	
+	if (mp_movable_contact_component->UpdateContact(m_pos))
+	{
+		m_pos += mp_movable_contact_component->GetContact()->GetMovement();
+		
+		if (mp_movable_contact_component->GetContact()->IsRotated())
+		{
+			m_facing = mp_movable_contact_component->GetContact()->GetRotation().Rotate(m_facing);
+			m_goal_hop_facing = mp_movable_contact_component->GetContact()->GetRotation().Rotate(m_goal_hop_facing);
+			if (m_facing[Y] != 0.0f)
+			{
+				m_facing[Y] = 0.0f;
+				m_facing.Normalize();
+			}
+			if (m_goal_hop_facing[Y] != 0.0f)
+			{
+				m_goal_hop_facing[Y] = 0.0f;
+				m_goal_hop_facing.Normalize();
+			}
+		}
+	}
+	
+	if (m_vertical_vel > -s_get_param(CRCD(0x7aa618cc, "hop_target_vert_vel")))
+	{
+		m_pos += m_horizontal_vel * m_frame_length;
+		m_pos[Y] += m_vertical_vel * m_frame_length + 0.5f * -s_get_param(CRCD(0xa5e2da58, "gravity")) * Mth::Sqr(m_frame_length);
+		
+		m_vertical_vel += -s_get_param(CRCD(0xa5e2da58, "gravity")) * m_frame_length;
+		
+		// BUG: we really want more control over this; this can cause pops; perhaps hold the hop duration and use the hop fraction complete
+		m_facing = Mth::Lerp(m_facing, m_goal_hop_facing, 30.0f * Tmr::FrameLength()); 
+
+		m_frame_event = CRCD(0xf41aba21, "Hop");
+	}
+	else
+	{
+		m_horizontal_vel.Set();
+		m_vertical_vel = 0.0f;
+		
+		m_pos = m_goal_hop_pos;
+		
+		m_facing = m_goal_hop_facing;
+		
+		set_state(WALKING_HANG);
+		
+		m_frame_event = CRCD(0x4194ecca, "Hang");
+	}
+}
+*/
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::go_hang_state (   )
+{
+	// Mth::Vector rail = (mp_rail_manager->GetPos(mp_rail_start) - mp_rail_manager->GetPos(mp_rail_end)).Normalize();
+	// DUMPF(Mth::RadToDeg(asin(Mth::Abs(rail[Y]))));
+	
+	if (mp_movable_contact_component->UpdateContact(m_pos))
+	{
+		CRailManagerComponent* p_rail_manager_component = GetRailManagerComponentFromObject(mp_movable_contact_component->GetContact()->GetObject());
+		Dbg_Assert(p_rail_manager_component);
+
+		p_rail_manager_component->UpdateRailManager();
+		
+		// calculate our new position
+		set_pos_from_hang_rail_state();
+	}
+	
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0x1a5eab7, "rail_highlights")))
+	{
+		Gfx::AddDebugLine(mp_rail_manager->GetPos(mp_rail_start), mp_rail_manager->GetPos(mp_rail_end), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+		Gfx::AddDebugLine(mp_rail_manager->GetPos(mp_rail_start) + Mth::Vector(1.0f, 0.0f, 0.0f), mp_rail_manager->GetPos(mp_rail_end) + Mth::Vector(1.0f, 0.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+		Gfx::AddDebugLine(mp_rail_manager->GetPos(mp_rail_start) + Mth::Vector(0.0f, 1.0f, 0.0f), mp_rail_manager->GetPos(mp_rail_end) + Mth::Vector(0.0f, 1.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+		Gfx::AddDebugLine(mp_rail_manager->GetPos(mp_rail_start) + Mth::Vector(0.0f, 0.0f, 1.0f), mp_rail_manager->GetPos(mp_rail_end) + Mth::Vector(0.0f, 0.0f, 1.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+	}
+	#endif
+	
+	// only respond to full up/down controls and only after a frame delay (to allow the debounce a frame to kick in)
+	if (m_control_pegged)
+	{
+		// up; IsLeftAnalogUpPressed allows for analog debouncing
+		if (mp_input_component->GetControlPad().IsLeftAnalogUpPressed())
+		{
+			// potential standing position after pull up
+			Mth::Vector stand_pos = m_pos + s_get_param(CRCD(0x21dfbe77, "pull_up_offset_forward")) * m_facing;
+			stand_pos[Y] += s_get_param(CRCD(0x52fa7ca6, "pull_up_offset_up"));
+			if (maybe_pull_up_from_hang(stand_pos)) return;
+			
+			// try standing a little closer
+			stand_pos = m_pos + 0.5f * s_get_param(CRCD(0x21dfbe77, "pull_up_offset_forward")) * m_facing;
+			stand_pos[Y] += s_get_param(CRCD(0x52fa7ca6, "pull_up_offset_up"));
+			if (maybe_pull_up_from_hang(stand_pos)) return;
+		}
+		// down; IsLeftAnalogDownPressed allows for analog debouncing
+		else if (mp_input_component->GetControlPad().IsLeftAnalogDownPressed())
+		{
+			// drop
+			set_state(WALKING_AIR);
+			m_primary_air_direction = m_facing;
+			leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+			m_frame_event = CRCD(0x439f4704, "Air");
+			
+			return;
+		}
+	}
+	
+	// left or right
+	float control_vel;
+	if (Mth::Abs((Mth::PI / 2.0f) - Mth::Abs(mp_input_component->GetControlPad().m_leftAngle)) < cosf(Mth::DegToRad(s_get_param(CRCD(0x1b877928, "hang_vert_control_tolerance")))))
+	{
+		control_vel = s_get_param(CRCD(0xd77ee881, "hang_move_speed")) * m_control_magnitude
+			* (mp_input_component->GetControlPad().m_leftAngle > 0.0f ? 1.0f : -1.0f);
+	}
+	else
+	{
+		control_vel = 0.0f;
+	}
+	m_hang_move_vel = Mth::Lerp(m_hang_move_vel, control_vel, s_get_param(CRCD(0xc506a97c, "hang_move_lerp_rate")) * m_frame_length);
+	
+	if (Mth::Abs(m_hang_move_vel) > s_get_param(CRCD(0x228b5376, "hang_move_cutoff")))
+	{
+		m_anim_effective_speed = Mth::Abs(m_hang_move_vel);
+	
+		hang_movement(m_hang_move_vel * m_frame_length);
+
+		// calculate our new position
+		set_pos_from_hang_rail_state();
+	}
+	else
+	{
+		m_frame_event = CRCD(0x4194ecca, "Hang");
+	}
+	
+	/* this doesn't make sense with the new higher critical point
+	// run a feeler between our old and new position to insure that our position stays safe
+	CFeeler feeler;
+	feeler.m_start = m_frame_start_pos + s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * m_facing;
+	feeler.m_end = m_pos + s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * m_facing;
+	if (feeler.GetCollision())
+	{
+		// our feet have hit something; often this means the rail has dropped to ground level; drop from our old position
+		m_pos = m_frame_start_pos;
+		set_state(WALKING_AIR);
+		m_primary_air_direction = m_facing;
+		leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+		m_frame_event = CRCD(0x439f4704, "Air");
+		return;
+	}
+	*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		
+void CWalkComponent::hang_movement ( float movement )
+{
+	// cache current critical point location and entire state
+	Mth::Vector initial_critical_point = m_pos + m_critical_point_offset;
+	Mth::Vector initial_pos = m_pos;
+	float initial_along_rail_factor = m_along_rail_factor;
+	const CRailNode* p_initial_rail_start = mp_rail_start;
+	const CRailNode* p_initial_rail_end = mp_rail_end;
+	Mth::Vector initial_critical_point_offset = m_critical_point_offset; 
+	Mth::Vector initial_facing = m_facing; 
+	
+	// allow left/right movement along rail
+	
+	Mth::Vector perp_facing(-m_facing[Z], 0.0f, m_facing[X]);
+	
+	// the offset from rail start to rail end
+	Mth::Vector rail = mp_rail_manager->GetPos(mp_rail_start) - mp_rail_manager->GetPos(mp_rail_end);
+	
+	// move left or right
+	bool left = movement > 0.0f;
+	
+	// is rail fractional position parameter ascends or descends with the movement
+	bool with_rail;
+	if (Mth::DotProduct(perp_facing, rail) < 0.0f)
+	{
+		with_rail = left;
+	}
+	else
+	{
+		with_rail = !left;
+	}
+
+	// check for collisions
+	if (check_environment_for_hang_movement_collisions(m_facing, m_along_rail_factor, m_pos, left, perp_facing, rail, mp_rail_start, mp_rail_end))
+	{
+		m_hang_move_vel = 0.0f;
+		m_frame_event = CRCD(0x4194ecca, "Hang");
+		return;
+	}
+	
+	// the movement in terms of the fraction of the rail
+	float delta_factor = Mth::Abs(movement) / rail.Length();
+	
+	// move along the rail
+	m_along_rail_factor += with_rail ? delta_factor : -delta_factor;
+	
+	// if we're moving off the rail
+	if (m_along_rail_factor < 0.0f || m_along_rail_factor > 1.0f)
+	{
+		bool found_rail = find_next_rail(m_along_rail_factor, mp_rail_start, mp_rail_end, mp_rail_start, mp_rail_end);
+			
+		// there's no connecting rail
+		if (!found_rail)
+		{
+			// clamp the movement
+			m_hang_move_vel = 0.0f;
+			m_along_rail_factor = Mth::Clamp(m_along_rail_factor, 0.0f, 1.0f);
+			m_frame_event = CRCD(0x4194ecca, "Hang");
+		}
+		else
+		{
+			// move onto the neighboring rail
+			float remaining_movement = Mth::Abs(m_along_rail_factor - Mth::Clamp(m_along_rail_factor, 0.0f, 1.0f)) * rail.Length();
+			move_onto_neighboring_hang_rail(remaining_movement, m_along_rail_factor < 0.0f, with_rail ? rail : -rail);
+			m_frame_event = left ? CRCD(0x2d9815c3, "HangMoveLeft") : CRCD(0x279b1f0b, "HangMoveRight");
+		}
+	}
+	else
+	{
+		m_frame_event = left ? CRCD(0x2d9815c3, "HangMoveLeft") : CRCD(0x279b1f0b, "HangMoveRight");
+	}
+	
+	// check to see if the critical point has gone through geo
+	
+	Mth::Vector critical_point = m_pos + m_critical_point_offset;
+	CFeeler feeler;
+	feeler.m_start = initial_critical_point;
+	feeler.m_end = critical_point;
+	if (feeler.GetCollision())
+	{
+		// restore entire to state to initial state
+		m_pos = initial_pos;
+		DUMP_WPOSITION
+		m_along_rail_factor = initial_along_rail_factor;
+		mp_rail_start = p_initial_rail_start;
+		mp_rail_end = p_initial_rail_end;
+		m_critical_point_offset = initial_critical_point_offset;
+		m_facing = initial_facing;
+		
+		m_hang_move_vel = 0.0f;
+		m_frame_event = CRCD(0x4194ecca, "Hang");
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		
+bool CWalkComponent::check_environment_for_hang_movement_collisions ( const Mth::Vector& facing, float along_rail_factor, const Mth::Vector& pos, bool left, const Mth::Vector& perp_facing, Mth::Vector rail, const CRailNode* p_rail_start, const CRailNode* p_rail_end )
+{
+	// check environment in the appropriate check_direction for collisions which would prevent hanging movement
+	// OPTIMIZATION: use a CCollCache here
+	
+	bool with_rail;
+	if (Mth::DotProduct(perp_facing, rail) < 0.0f)
+	{
+		with_rail = left;
+	}
+	else
+	{
+		with_rail = !left;
+	}
+	
+	// check_direction to the side to check for geo
+	Mth::Vector check_direction = left ? perp_facing : -perp_facing;
+	
+	// adjusted facing due to adjusted check_direction
+	Mth::Vector check_direction_facing = facing;
+
+	// see if we're near the end of the rail; if so, we may want to angle our feelers out (so we can make inside turns while hanging)
+	float distance_to_end_of_rail = rail.Length() * (with_rail ? 1.0f - along_rail_factor : along_rail_factor);
+	if (distance_to_end_of_rail < s_get_param(CRCD(0x3344a6d0, "hang_move_collision_side_length")) + 1.0f)
+	{
+		// pretend we're past the rail when calling find_next_rail
+		along_rail_factor += with_rail ? 2.0f : -2.0f;
+		
+		const CRailNode* p_next_start;
+		const CRailNode* p_next_end;
+		if (find_next_rail(along_rail_factor, p_rail_start, p_rail_end, p_next_start, p_next_end))
+		{
+			Mth::Vector new_check_direction = (along_rail_factor > 0.5f ? -1.0f : 1.0f)
+				* (mp_rail_manager->GetPos(p_next_start) - mp_rail_manager->GetPos(p_next_end));
+			new_check_direction[Y] = 0.0f;
+			new_check_direction.Normalize();
+			
+			// only use adjusted feelers for inside turns
+			float dot = Mth::DotProduct(facing, new_check_direction);
+			if (dot < 0.0f && dot > -0.87f)
+			{
+				check_direction = new_check_direction;
+				if (left)
+				{
+					check_direction_facing[X] = check_direction[Z];
+					check_direction_facing[Z] = -check_direction[X];
+				}
+				else
+				{
+					check_direction_facing[X] = -check_direction[Z];
+					check_direction_facing[Z] = check_direction[X];
+				}
+			}
+		}
+	}
+	
+	CFeeler feeler;
+	feeler.m_start = pos;
+	feeler.m_start[Y] += s_get_param(CRCD(0x84ff931c, "hang_move_collision_up"));
+	feeler.m_start -= s_get_param(CRCD(0xcbeb3d89, "hang_move_collision_back")) * facing;
+	
+	// feel to the side
+	feeler.m_end = feeler.m_start;
+	feeler.m_end += s_get_param(CRCD(0x3344a6d0, "hang_move_collision_side_length")) * check_direction;
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+		return true;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 1);
+	}
+	#endif
+	
+	// feel up and to the side
+	feeler.m_end = feeler.m_start;
+	feeler.m_end[Y] += s_get_param(CRCD(0xc774dd6d, "hang_move_collision_side_height"));
+	feeler.m_end += s_get_param(CRCD(0x3344a6d0, "hang_move_collision_side_length")) * check_direction;
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+		return true;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 1);
+	}
+	#endif
+	
+	// feel half up and to the side
+	feeler.m_end = feeler.m_start;
+	feeler.m_end[Y] += 0.5f * s_get_param(CRCD(0xc774dd6d, "hang_move_collision_side_height"));
+	feeler.m_end += s_get_param(CRCD(0x3344a6d0, "hang_move_collision_side_length")) * check_direction;
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+		return true;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 1);
+	}
+	#endif
+	
+	// feel down (to critical point) and to the side and back to the critical point
+	feeler.m_end = feeler.m_start;
+	feeler.m_end[Y] += -s_get_param(CRCD(0x84ff931c, "hang_move_collision_up")) + get_hang_critical_point_vert_offset() - 1.0f;
+	feeler.m_end += s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * check_direction_facing;
+	feeler.m_end += s_get_param(CRCD(0x3344a6d0, "hang_move_collision_side_length")) * check_direction;
+	 // add a touch along the rail so we don't get stuck with both directions feelers in the ground
+	feeler.m_end += with_rail ? -rail.Normalize() : rail.Normalize();
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+		return true;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 1);
+	}
+	#endif
+	
+	// feel half down and to the side and back to the critical point
+	feeler.m_end = feeler.m_start;
+	feeler.m_end[Y] -= 0.5f * s_get_param(CRCD(0xc774dd6d, "hang_move_collision_side_height"));
+	feeler.m_end += s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * check_direction_facing;
+	feeler.m_end += s_get_param(CRCD(0x3344a6d0, "hang_move_collision_side_length")) * check_direction;
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(255, 0, 0, 1);
+		}
+		#endif
+		return true;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 1);
+	}
+	#endif
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::move_onto_neighboring_hang_rail ( float movement, bool descending, const Mth::Vector& old_rail )
+{
+	Mth::Vector next_rail = mp_rail_manager->GetPos(mp_rail_start) - mp_rail_manager->GetPos(mp_rail_end);
+	float next_rail_length = next_rail.Length();
+	
+	// if the next rail is shorter than our movement, just ditch the extra movement
+	movement = Mth::ClampMax(movement, next_rail_length);
+	
+	// we've found the appropriate neighbor rail to move on to, so set up our state
+	
+	// calculate our position along the rail
+	m_along_rail_factor = movement / next_rail_length;
+	if (descending)
+	{
+		m_along_rail_factor = 1.0f - m_along_rail_factor;
+	}
+	
+	// calculate our next facing
+	Mth::Vector next_facing(-next_rail[Z], 0.0f, next_rail[X]);
+	next_facing.Normalize();
+	
+	// check its check_direction
+	if (Mth::CrossProduct(old_rail, m_facing)[Y] * Mth::CrossProduct(descending ? -next_rail : next_rail, next_facing)[Y] < 0.0f)
+	{
+		next_facing.Negate();
+	}
+	
+	if (Mth::DotProduct(next_facing, m_facing) < cosf(Mth::DegToRad(60.0f)))
+	{
+		// move the camera behind us
+		mp_camera_component->FlushRequest();
+	}
+	m_facing = next_facing;
+	
+	m_critical_point_offset = s_get_param(CRCD(0xc3263e6e, "hang_critical_point_horiz_offset")) * m_facing;
+	m_critical_point_offset[Y] = get_hang_critical_point_vert_offset();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::set_pos_from_hang_rail_state (   )
+{
+	m_pos = Mth::Lerp(mp_rail_manager->GetPos(mp_rail_start), mp_rail_manager->GetPos(mp_rail_end), m_along_rail_factor);
+	m_pos[Y] += m_vertical_hang_offset;
+	m_pos += m_horizontal_hang_offset * m_facing;
+	DUMP_WPOSITION
+	
+	if (mp_movable_contact_component->HaveContact() && mp_movable_contact_component->GetContact()->IsRotated())
+	{
+		// recalculate facing
+		Mth::Vector new_facing = mp_rail_manager->GetPos(mp_rail_start) - mp_rail_manager->GetPos(mp_rail_end);
+		new_facing[Y] = 0.0f;
+		new_facing.Normalize();
+		new_facing.Set(-new_facing[Z], 0.0f, new_facing[X]);
+		if (Mth::DotProduct(new_facing, m_facing) > 0.0f)
+		{
+			m_facing = new_facing;
+		}
+		else
+		{
+			m_facing = -new_facing;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::find_next_rail ( float& along_rail_factor, const CRailNode* p_current_start, const CRailNode* p_current_end, const CRailNode*& p_new_start, const CRailNode*& p_new_end )
+{
+	// find the neighboring rail, if there is one
+	
+	Dbg_Assert(along_rail_factor < 0.0f || along_rail_factor > 1.0f);
+	
+	const CRailNode* next_rail_node = NULL;
+	if (along_rail_factor < 0.0f)
+	{
+		// look for a connection
+		if (p_current_start->GetPrevLink() && p_current_start->GetPrevLink()->IsActive() && !p_current_start->GetPrevLink()->GetFlag(NO_CLIMBING)
+			&& Rail_ValidInEditor(mp_rail_manager->GetPos(p_current_start->GetPrevLink()), mp_rail_manager->GetPos(p_current_start)))
+		{
+			p_new_end = p_current_start;
+			p_new_start = p_current_start->GetPrevLink();
+			// MESSAGE("FOUND CONNECTING RAIL");
+			return true;
+		}
+		
+		// look for another node who's position corresponds to the current start node
+		if (!mp_rail_manager->CheckForCoincidentRailNode(p_current_start, nBit(NO_CLIMBING) | nBit(LADDER), &next_rail_node))
+		{
+			// MESSAGE("NO COINCIDENT RAIL");
+			return false;
+		}
+	}
+	else
+	{
+		// look for a connection
+		if (p_current_end->GetNextLink() && p_current_end->IsActive() && !p_current_end->GetFlag(NO_CLIMBING)
+			&& Rail_ValidInEditor(mp_rail_manager->GetPos(p_current_end), mp_rail_manager->GetPos(p_current_end->GetNextLink())))
+		{
+			p_new_start = p_current_end;
+			p_new_end = p_current_end->GetNextLink();
+			// MESSAGE("FOUND CONNECTING RAIL");
+			return true;
+		}
+		
+		// look for another node who's position corresponds a given node's position
+		if (!mp_rail_manager->CheckForCoincidentRailNode(p_current_end, nBit(NO_CLIMBING) | nBit(LADDER), &next_rail_node))
+		{
+			// MESSAGE("NO COINCIDENT RAIL");
+			return false;
+		}
+	}
+	
+	Dbg_Assert(next_rail_node && next_rail_node->GetNextLink());
+	
+	bool rails_are_flush = mp_rail_manager->RailNodesAreCoincident(next_rail_node, p_current_end)
+		|| mp_rail_manager->RailNodesAreCoincident(next_rail_node->GetNextLink(), p_current_start);
+    
+	Mth::Vector old_rail_dir = (mp_rail_manager->GetPos(p_current_end) - mp_rail_manager->GetPos(p_current_start)).Normalize();
+	Mth::Vector new_rail_dir = (mp_rail_manager->GetPos(next_rail_node->GetNextLink()) - mp_rail_manager->GetPos(next_rail_node)).Normalize();
+	if (!rails_are_flush)
+	{
+		new_rail_dir *= -1.0f;
+	}
+	if (Mth::DotProduct(old_rail_dir, new_rail_dir) < -0.866f)
+	{
+		MESSAGE("BAD ANGLE ON COINCIDENT RAIL");
+		return false;
+	}
+	
+	if (!rails_are_flush)
+	{
+		along_rail_factor = 1.0f - along_rail_factor;
+	}
+	p_new_start = next_rail_node;
+	p_new_end = next_rail_node->GetNextLink();
+	MESSAGE("FOUND COINCIDENT RAIL");
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_pull_up_from_hang ( Mth::Vector& proposed_stand_pos )
+{
+	// check to see if there's a good place for us to stand if we pull up; if so, do so
+	
+	Mth::Vector stand_pos;
+	
+	CFeeler potential_ground_feeler;
+	bool ground = determine_stand_pos(proposed_stand_pos, stand_pos, potential_ground_feeler);
+	
+	if (!ground)
+	{
+		Mth::Vector initial_stand_pos = stand_pos;
+		Mth::Vector left(-m_facing[Z], 0.0f, m_facing[X]);
+		
+		// if we couldn't find the ground, we may be at the end of a rail which hangs over the geo; look for stand positions to the left and right
+		const float SIDEWAYS_SEARCH_DISTANCE = 12.0f;
+		proposed_stand_pos += SIDEWAYS_SEARCH_DISTANCE * left;
+		ground = determine_stand_pos(proposed_stand_pos, stand_pos, potential_ground_feeler);
+		if (!ground)
+		{
+			proposed_stand_pos -= (2.0f * SIDEWAYS_SEARCH_DISTANCE) * left;
+			ground = determine_stand_pos(proposed_stand_pos, stand_pos, potential_ground_feeler);
+			if (!ground)
+			{
+				stand_pos = initial_stand_pos;
+			}
+		}
+	}
+	
+	// check for walls around the standing position
+	CFeeler feeler;
+	float push_feeler_length = 0.5f * s_get_param(CRCD(0xa20c43b7, "push_feeler_length"));
+	float feeler_height = s_get_param(CRCD(0x6da7f696, "feeler_height"));
+	for (int n = 0; n < vNUM_FEELERS; n++)
+	{
+		float angle = n * (2.0f * Mth::PI / vNUM_FEELERS);
+		
+		float cos_angle = cosf(angle);
+		float sin_angle = sinf(angle);
+		
+		Mth::Vector end_offset;
+		end_offset[X] = cos_angle * m_facing[X] - sin_angle * m_facing[Z];
+		end_offset[Y] = 0.0f;
+		end_offset[Z] = sin_angle * m_facing[X] + cos_angle * m_facing[Z];
+		end_offset[W] = 1.0f;
+		end_offset *= push_feeler_length;
+		
+		feeler.m_start = stand_pos;
+		feeler.m_start[Y] += feeler_height;
+		feeler.m_end = stand_pos + end_offset;
+		feeler.m_end[Y] += feeler_height;
+		
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(0, 255, 255, 0);
+		}
+		#endif
+		if (feeler.GetCollision()) return false;
+	}
+	
+	// check line from hang point to standing position for obstructions
+	feeler.m_start = m_pos;
+	feeler.m_start += -6.0f * m_facing;
+	feeler.m_start[Y] += s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset")) + s_get_param(CRCD(0x9b2818cf, "pull_up_obstruction_height"));
+	feeler.m_end = stand_pos;
+	feeler.m_end[Y] += 1.0f;
+	if (feeler.GetCollision())
+	{
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+		{
+			feeler.DebugLine(0, 100, 100, 0);
+		}
+		#endif
+		return false;
+	}
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(100, 0, 100, 0);
+	}
+	#endif
+	
+	// pull up looks ok, so let's do it
+	
+	if (ground)
+	{
+		mp_anim_wait_complete_callback = &Obj::CWalkComponent::pull_up_from_hang_to_ground_complete;
+		
+		m_last_ground_feeler = potential_ground_feeler;
+		m_last_ground_feeler_valid = true;
+		m_ground_normal = potential_ground_feeler.GetNormal();
+	}
+	else
+	{
+		mp_anim_wait_complete_callback = &Obj::CWalkComponent::pull_up_from_hang_to_air_complete;
+	}
+	
+	m_anim_wait_initial_pos = m_pos;
+	m_anim_wait_goal_pos = stand_pos;
+
+	calculate_anim_wait_facing_drift_parameters(m_facing);
+	
+	m_anim_wait_camera_mode = AWC_GOAL;
+	
+	m_drift_initial_display_offset = m_display_offset;
+	m_drift_goal_display_offset = 0.0f;
+						 	
+	m_critical_point_offset = -(6.0f + s_get_param(CRCD(0x21dfbe77, "pull_up_offset_forward"))) * m_facing;
+	m_critical_point_offset[Y] = 1.0f;
+	
+	// setup the false wall incase the player jumps out of the animation wait
+	m_false_wall.normal = m_facing;
+	m_false_wall.cancel_height = m_pos[Y] - m_vertical_hang_offset;
+
+	set_state(WALKING_ANIMWAIT);
+
+	m_frame_event = CRCD(0x9ef9f8f1, "PullUpFromHang");
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::pull_up_from_hang_to_ground_complete (   )
+{
+    set_state(WALKING_GROUND);
+	
+	if (m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, m_last_ground_feeler);
+
+		// check for a moving contact
+		mp_movable_contact_component->CheckForMovableContact(m_last_ground_feeler);
+	}
+	
+	m_critical_point_offset.Set();
+	
+	m_display_offset = m_drift_goal_display_offset;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::pull_up_from_hang_to_air_complete (   )
+{
+    set_state(WALKING_AIR);
+	
+	m_primary_air_direction = m_facing;
+	leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+	
+	m_display_offset = m_drift_goal_display_offset;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWalkComponent::get_hang_display_offset (   )
+{
+    if (mp_model_component->GetModel()->IsScalingEnabled())
+	{
+		return (1.0f - mp_model_component->GetModel()->GetScale()[Y]) * s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset"));
+	}
+	else
+	{
+		return 0.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWalkComponent::get_hang_critical_point_vert_offset (   )
+{
+	return s_get_param(CRCD(0xde86132e, "hang_critical_point_vert_offset"))
+		- s_get_param(CRCD(0x7201ad48, "max_cas_scaling")) * s_get_param(CRCD(0xa8c13e74, "hang_vert_origin_offset"));
+}
+
+}
diff --git a/Code/Gel/Components/WalkLadderUtil.cpp b/Code/Gel/Components/WalkLadderUtil.cpp
new file mode 100644
index 0000000..e72a1c2
--- /dev/null
+++ b/Code/Gel/Components/WalkLadderUtil.cpp
@@ -0,0 +1,582 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WalkLadderUtil.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  4/29/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+// NOTE: The ladder walking code currently ignores movable rail managers!
+                 
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_climb_up_ladder ( bool user_requested )
+{
+	// check to see if we're in a bail
+	if (mp_core_physics_component->GetFlag(IS_BAILING)) return false;
+	
+	SLadderRailData rail_data;
+
+	// Check for appropriate rails.
+    if (!Mdl::Skate::Instance()->GetRailManager()->CheckForLadderRail(
+        m_pos,
+        user_requested ? s_get_param(CRCD(0xeecd255, "button_horiz_snap_distance")) : s_get_param(CRCD(0xa2735dd9, "max_horiz_snap_distance")),
+        s_get_param(CRCD(0xc4c164e8, "max_vert_snap_distance")),
+		true,
+        this,
+        rail_data,
+        &mp_rail_start
+    )) return false;
+	
+	// store the relevant rail manager
+	mp_rail_manager = Mdl::Skate::Instance()->GetRailManager();
+	mp_movable_contact_component->LoseAnyContact();
+	
+	// zero velocities	
+	m_vertical_vel = 0.0f;
+	m_horizontal_vel.Set();
+    
+    // setup an anim wait state
+	
+	m_anim_wait_initial_pos = m_pos;
+	m_anim_wait_goal_pos = rail_data.climb_point;
+	
+    mp_anim_wait_complete_callback = &Obj::CWalkComponent::move_to_ladder_bottom_complete;
+	
+	m_drift_initial_display_offset = m_display_offset;
+	m_drift_goal_display_offset = 0.0f;
+	
+	calculate_anim_wait_facing_drift_parameters(rail_data.climb_facing);
+	
+	m_anim_wait_camera_mode = AWC_GOAL;
+		  	
+	// setup the false wall incase the player jumps out of the animation wait
+	m_false_wall.normal = rail_data.climb_facing;
+	m_false_wall.cancel_height = rail_data.climb_point[Y];
+
+	set_state(WALKING_ANIMWAIT);
+
+	m_frame_event = CRCD(0xe2524194, "LadderOntoBottom");
+    
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_climb_down_ladder (   )
+{
+   	// check to see if we're in a bail
+	if (mp_core_physics_component->GetFlag(IS_BAILING)) return false;
+	
+    SLadderRailData rail_data;
+    
+	// Check for appropriate rails.
+    if (!Mdl::Skate::Instance()->GetRailManager()->CheckForLadderRail(
+        m_pos,
+        s_get_param(CRCD(0xa2735dd9, "max_horiz_snap_distance")),
+        s_get_param(CRCD(0xc4c164e8, "max_vert_snap_distance")),
+		false,
+        this,
+        rail_data,
+        &mp_rail_start
+    )) return false;
+	
+	// store the relevant rail manager
+	mp_rail_manager = Mdl::Skate::Instance()->GetRailManager();
+	mp_movable_contact_component->LoseAnyContact();
+	
+	// zero velocities	
+	m_vertical_vel = 0.0f;
+	m_horizontal_vel.Set();
+	
+    // setup an anim wait state
+	
+	m_anim_wait_initial_pos = m_pos;
+	m_anim_wait_goal_pos = rail_data.climb_point;
+	
+    mp_anim_wait_complete_callback = &Obj::CWalkComponent::move_to_ladder_top_complete;
+	
+	m_drift_initial_display_offset = m_display_offset;
+	m_drift_goal_display_offset = 0.0f;
+	
+	calculate_anim_wait_facing_drift_parameters(rail_data.climb_facing);
+	
+	// if (Mth::DotProduct(mp_camera->GetMatrix()[Z], m_facing) > 0.0f)
+	// {
+		m_anim_wait_camera_mode = AWC_GOAL;
+	// }
+	// else
+	// {
+		// m_anim_wait_camera_mode = AWC_CURRENT;
+	// }
+	   		  	
+	// setup the false wall incase the player jumps out of the animation wait
+	m_false_wall.normal = rail_data.climb_facing;
+	m_false_wall.cancel_height = m_pos[Y];
+
+	set_state(WALKING_ANIMWAIT);
+
+	m_frame_event = CRCD(0xd63adcad, "LadderOntoTop");
+    
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::maybe_grab_to_ladder ( const Mth::Vector& start_pos, const Mth::Vector& end_pos )
+{
+    SLadderRailData rail_data;
+	
+	// Check for appropriate rails.
+    if (!Mdl::Skate::Instance()->GetRailManager()->CheckForAirGrabLadderRail(
+		start_pos,
+		end_pos,
+		this,
+		rail_data,
+		&mp_rail_start
+    )) return false;
+	
+	// store the relevant rail manager
+	mp_rail_manager = Mdl::Skate::Instance()->GetRailManager();
+	mp_movable_contact_component->LoseAnyContact();
+	
+	// we've found a rail to climb
+	m_pos = rail_data.climb_point;
+	DUMP_WPOSITION
+	m_facing = rail_data.climb_facing;
+	m_along_rail_factor = rail_data.along_rail_factor;
+	
+	// zero velocities	
+	m_vertical_vel = 0.0f;
+	m_horizontal_vel.Set();
+	
+	set_state(WALKING_LADDER);
+
+	m_frame_event = CRCD(0xc84243da, "Ladder");
+    
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::move_to_ladder_bottom_complete (   )
+{
+	// getting on at the bottom of the ladder
+	
+	// determine top node
+	const CRailNode* p_top_rail_node = mp_rail_start->GetNextLink() ? mp_rail_start->GetNextLink() : mp_rail_start->GetPrevLink();
+	Dbg_Assert(p_top_rail_node);
+		
+	// the offset from the ladder bottom to rail top
+	Mth::Vector rail = mp_rail_manager->GetPos(p_top_rail_node) - mp_rail_manager->GetPos(mp_rail_start);
+	float rail_length = rail.Length();
+	
+	// calculate initial positoin along rail
+	m_along_rail_factor = s_get_param(CRCD(0x5296e3fd, "ladder_bottom_offset_up")) / rail_length;
+	
+    set_state(WALKING_LADDER);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::move_to_ladder_top_complete (   )
+{
+	// getting on at the top of the ladder
+	
+	// determine top node
+	const CRailNode* p_top_rail_node = mp_rail_start->GetNextLink() ? mp_rail_start->GetNextLink() : mp_rail_start->GetPrevLink();
+	Dbg_Assert(p_top_rail_node);
+		
+	// the offset from the ladder bottom to rail top
+	Mth::Vector rail = mp_rail_manager->GetPos(p_top_rail_node) - mp_rail_manager->GetPos(mp_rail_start);
+	float rail_length = rail.Length();
+	
+	// calculate initial positoin along rail
+	m_along_rail_factor = (rail_length - s_get_param(CRCD(0x76bf49e0, "ladder_top_offset_up"))) / rail_length;
+	
+    set_state(WALKING_LADDER);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::FilterLadderUpRail ( const Mth::Vector& bottom_pos, const Mth::Vector& top_pos, const Mth::Matrix& orientation, SLadderRailData& rail_data )
+{
+    // check that we're at the bottom of the ladder
+    if (bottom_pos[Y] > top_pos[Y]) return false;
+    
+    // check that the proposed ladder rail is vertical
+    Mth::Vector ladder_direction = top_pos - bottom_pos;
+    ladder_direction.Normalize();
+    if (ladder_direction[Y] < 0.995f) return false;
+	
+	// check that we're facing the ladder
+	if (Mth::DotProduct(m_facing, orientation[Z]) < cosf(Mth::DegToRad(s_get_param(CRCD(0x456f216d, "max_onto_ladder_angle"))))) return false;
+    
+    // calculate the climb position
+    rail_data.climb_point = bottom_pos + ladder_direction * s_get_param(CRCD(0x5296e3fd, "ladder_bottom_offset_up"));
+	rail_data.climb_point -= s_get_param(CRCD(0x35b3bbda, "ladder_climb_offset")) * orientation[Z];
+    
+    // send feeler to climb position
+    CFeeler feeler;
+    feeler.m_start = m_pos;
+    feeler.m_end = rail_data.climb_point - 6.0f * orientation[Z];
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 0);
+	}
+	#endif
+    if (feeler.GetCollision()) return false;
+    
+    // store the climb facing
+    rail_data.climb_facing = orientation[Z];
+    
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::FilterLadderDownRail ( const Mth::Vector& bottom_pos, const Mth::Vector& top_pos, const Mth::Matrix& orientation, SLadderRailData& rail_data )
+{
+    // check that we're at the top of the ladder
+    if (bottom_pos[Y] > top_pos[Y]) return false;
+    
+    // check that the proposed ladder rail is vertical
+    Mth::Vector ladder_direction = top_pos - bottom_pos;
+    ladder_direction.Normalize();
+    if (ladder_direction[Y] < 0.995f) return false;
+    
+    // calculate the climb position
+    rail_data.climb_point = top_pos - ladder_direction * s_get_param(CRCD(0x76bf49e0, "ladder_top_offset_up"));
+	rail_data.climb_point -= s_get_param(CRCD(0x35b3bbda, "ladder_climb_offset")) * orientation[Z];
+    
+	/*
+    // send feeler to climb position
+    CFeeler feeler;
+    feeler.m_start = m_pos;
+    feeler.m_end = rail_data.climb_point - 6.0f * orientation[Z];
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 0);
+	}
+	#endif
+    if (feeler.GetCollision()) return false;
+	*/
+    
+    // store the climb facing
+    rail_data.climb_facing = orientation[Z];
+    
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWalkComponent::FilterLadderAirGrabRail ( const Mth::Vector& bottom_pos, const Mth::Vector& top_pos, const Mth::Matrix& orientation, const Mth::Vector& preliminary_climb_point, float along_rail_factor, SLadderRailData& rail_data )
+{
+    // check that the proposed ladder rail is vertical
+    Mth::Vector ladder_direction = top_pos - bottom_pos;
+    ladder_direction.Normalize();
+    if (ladder_direction[Y] < 0.995f) return false;
+	
+	// insure that the target point on the ladder is above the climb off bottom point
+    float off_bottom_height = bottom_pos[Y] + ladder_direction[Y] * s_get_param(CRCD(0x5296e3fd, "ladder_bottom_offset_up"));
+	if (preliminary_climb_point[Y] < off_bottom_height) return false;
+	
+	// insure that the target point on the ladder is below the climb off top point
+    float off_top_height = top_pos[Y] - ladder_direction[Y] * s_get_param(CRCD(0x76bf49e0, "ladder_top_offset_up"));
+	if (preliminary_climb_point[Y] > off_top_height) return false;
+	
+	// move the climb point a touch back off the rail
+	rail_data.climb_point = preliminary_climb_point - s_get_param(CRCD(0x35b3bbda, "ladder_climb_offset")) * orientation[Z];
+	
+	// send a feeler to the climb position
+	CFeeler feeler;
+	feeler.m_start = m_pos;
+	feeler.m_end = rail_data.climb_point;
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0xaf90c5fd, "walking_debug_lines")))
+	{
+		feeler.DebugLine(0, 0, 255, 0);
+	}
+	#endif
+    if (feeler.GetCollision()) return false;
+	
+	// store the climb facing
+	rail_data.climb_facing = orientation[Z];
+	
+	rail_data.along_rail_factor = along_rail_factor;
+	
+	mp_core_physics_component->SetFlagTrue(SNAPPED);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::go_ladder_state (   )
+{
+	if (Mth::Abs((Mth::PI / 2.0f) - Mth::Abs(mp_input_component->GetControlPad().m_leftAngle)) > cosf(Mth::DegToRad(s_get_param(CRCD(0x7ee1b95b, "ladder_control_tolerance")))))
+	{
+		ladder_movement();
+		return;
+	}
+	
+	m_frame_event = CRCD(0xc84243da, "Ladder");
+	m_anim_effective_speed = 0.0f;
+	return;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::ladder_movement (   )
+{
+	// movement up and down the ladder
+	
+	// the desired speed; this will be used to scale the animation speed
+	m_anim_effective_speed = s_get_param(CRCD(0xab2db54, "ladder_move_speed")) * m_control_magnitude * cosf(mp_input_component->GetControlPad().m_leftAngle);
+	
+	// the movement this frame
+	float movement = m_anim_effective_speed * m_frame_length;
+	
+	// get the top rail node; mp_rail_start is always the bottom node
+	const CRailNode* p_top_rail_node = mp_rail_start->GetNextLink() ? mp_rail_start->GetNextLink() : mp_rail_start->GetPrevLink();
+	Dbg_Assert(p_top_rail_node);
+		
+	// the offset from the ladder bottom to rail top
+	Mth::Vector rail = mp_rail_manager->GetPos(p_top_rail_node) - mp_rail_manager->GetPos(mp_rail_start);
+	Dbg_MsgAssert(rail.Length() > s_get_param(CRCD(0x76bf49e0, "ladder_top_offset_up")), ("Ladder too short for ladder animations"));
+	
+	// the movement in terms of the fraction of the rail
+	float delta_factor = movement / rail.Length();
+	
+	// movement is up or down
+	bool up = delta_factor > 0.0f;
+	
+	// move along the rail
+	m_along_rail_factor += delta_factor;
+	
+	float rail_length = rail.Length();
+	
+	// see if we've hit the bottom
+	float bottom_along_rail_factor = s_get_param(CRCD(0x5296e3fd, "ladder_bottom_offset_up")) / rail_length;
+	if (m_along_rail_factor < bottom_along_rail_factor)
+	{
+		off_ladder_bottom();
+		return;
+	}
+	
+	// see if we've hit the top
+	float top_along_rail_factor = (rail_length - s_get_param(CRCD(0x76bf49e0, "ladder_top_offset_up"))) / rail_length;
+	if (m_along_rail_factor > top_along_rail_factor)
+	{
+		// we've hit the top
+		off_ladder_top();
+		return;
+	}
+	
+	// still clamp for now
+	m_along_rail_factor = Mth::Clamp(m_along_rail_factor, 0.0f, 1.0f);
+	
+	if (m_along_rail_factor == 0.0f || m_along_rail_factor == 1.0f)
+	{
+		m_frame_event = CRCD(0xc84243da, "Ladder");
+		m_anim_effective_speed = 0.0f;
+	}
+	else
+	{
+		m_frame_event = up ? CRCD(0xaf5abc82, "LadderMoveUp") : CRCD(0xfec9dded, "LadderMoveDown");
+	}
+	
+	// calculate our new position
+	m_pos = mp_rail_manager->GetPos(mp_rail_start) + m_along_rail_factor * rail - s_get_param(CRCD(0x35b3bbda, "ladder_climb_offset")) * m_facing;
+	DUMP_WPOSITION
+	
+	// positive animation speed
+	m_anim_effective_speed = Mth::Abs(m_anim_effective_speed);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::off_ladder_bottom (   )
+{
+	Mth::Vector stand_pos = m_pos - s_get_param(CRCD(0xcaf2831b, "ladder_bottom_offset_forward")) * m_facing;
+	stand_pos[Y] -= s_get_param(CRCD(0x5296e3fd, "ladder_bottom_offset_up"));
+	m_last_ground_feeler_valid = determine_stand_pos(stand_pos, stand_pos, m_last_ground_feeler);
+	
+	if (m_last_ground_feeler_valid)
+	{
+		m_ground_normal = m_last_ground_feeler.GetNormal();
+	}
+	
+    // setup an anim wait state
+	
+	m_anim_wait_initial_pos = m_pos;
+	m_anim_wait_goal_pos = stand_pos;
+	
+    mp_anim_wait_complete_callback = &Obj::CWalkComponent::off_ladder_bottom_complete;
+	
+	m_drift_initial_display_offset = m_display_offset;
+	m_drift_goal_display_offset = 0.0f;
+	
+	calculate_anim_wait_facing_drift_parameters(m_facing);
+	
+	m_anim_wait_camera_mode = AWC_GOAL;
+	   		  	
+	// setup the false wall incase the player jumps out of the animation wait
+	m_false_wall.normal = m_facing;
+	m_false_wall.cancel_height = m_pos[Y];
+
+	set_state(WALKING_ANIMWAIT);
+
+	m_frame_event = CRCD(0x4db1bbb1, "LadderOffBottom");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::off_ladder_bottom_complete (   )
+{
+    set_state(WALKING_GROUND);
+	
+	if (m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, m_last_ground_feeler);
+
+		// check for a moving contact
+		mp_movable_contact_component->CheckForMovableContact(m_last_ground_feeler);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::off_ladder_top (   )
+{
+	// determine goal position
+	
+	Mth::Vector stand_pos = m_pos + s_get_param(CRCD(0xb620b580, "ladder_top_offset_forward")) * m_facing; 
+	stand_pos[Y] += s_get_param(CRCD(0x76bf49e0, "ladder_top_offset_up"));
+	
+	CFeeler potential_ground_feeler;
+	bool ground = determine_stand_pos(stand_pos, stand_pos, potential_ground_feeler);
+
+	// setup an anim wait state
+	
+	m_anim_wait_initial_pos = m_pos;
+	m_anim_wait_goal_pos = stand_pos;
+	
+	if (ground)
+	{
+        mp_anim_wait_complete_callback = &Obj::CWalkComponent::off_ladder_top_to_ground_complete;
+		
+		m_last_ground_feeler = potential_ground_feeler;
+		m_last_ground_feeler_valid = true;
+		m_ground_normal = m_last_ground_feeler.GetNormal();
+	}
+	else
+	{
+		mp_anim_wait_complete_callback = &Obj::CWalkComponent::off_ladder_top_to_air_complete;
+	}
+	
+	m_drift_initial_display_offset = m_display_offset;
+	m_drift_goal_display_offset = 0.0f;
+	
+	calculate_anim_wait_facing_drift_parameters(m_facing);
+	
+	m_anim_wait_camera_mode = AWC_GOAL;
+	   		  	
+	// setup the false wall incase the player jumps out of the animation wait
+	m_false_wall.normal = m_facing;
+	m_false_wall.cancel_height = stand_pos[Y];
+	
+	set_state(WALKING_ANIMWAIT);
+	
+	m_frame_event = CRCD(0x12521bfb, "LadderOffTop");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::off_ladder_top_to_ground_complete (   )
+{
+    set_state(WALKING_GROUND);
+	
+	if (m_last_ground_feeler_valid)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, m_last_ground_feeler);
+
+		// check for a moving contact
+		mp_movable_contact_component->CheckForMovableContact(m_last_ground_feeler);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWalkComponent::off_ladder_top_to_air_complete (   )
+{
+    set_state(WALKING_AIR);
+	
+	m_primary_air_direction = m_facing;
+	leave_movable_contact_for_air(m_horizontal_vel, m_vertical_vel);
+}
+
+}
diff --git a/Code/Gel/Components/animationcomponent.cpp b/Code/Gel/Components/animationcomponent.cpp
new file mode 100644
index 0000000..cf3b5c0
--- /dev/null
+++ b/Code/Gel/Components/animationcomponent.cpp
@@ -0,0 +1,2356 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       AnimationComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/24/2002
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+
+// TODO:  All the network code should
+// be at the skater- or the player- level
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+		  
+namespace Obj
+{
+	extern bool DebugSkaterScripts;
+	
+	// if the animation time increment is too small,
+	// then ignore it...
+	const float vMIN_SPEED_THRESHOLD = 0.05f;
+
+	#define nxANIMCOMPONENTFLAGS_FLIPPED				(1<<29)
+	
+	// maximum number of degenerate animations
+	const int vNUM_DEGENERATE_ANIMS = 3;
+
+	// maximum number of procedural bones
+	const int vMAXPROCEDURALBONES = 12;
+	
+	static bool s_updating_channels = false;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This static function is what is registered with the component
+// factory object, (currently the CCompositeObjectManager) 
+CBaseComponent* CAnimationComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CAnimationComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAnimationComponent::CAnimationComponent() : CBaseComponent()
+{
+	SetType( CRC_ANIMATION );
+
+	m_animScriptName = 0;
+	m_animEventTableName = 0;
+
+	m_shouldBlend = false;
+
+	Reset();
+
+	m_numProceduralBones = 0;
+
+	mp_proceduralBones = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CAnimationComponent::~CAnimationComponent()
+{
+	destroy_blend_channels();
+	
+	if ( mp_proceduralBones )
+	{
+		delete[] mp_proceduralBones;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::Reset()
+{
+	// stops all the animations...
+	destroy_blend_channels();
+
+	// resets "global" blend parameters
+	mGotBlendPeriodOut = false;
+	mBlendPeriodOut = 0.0f;
+	
+	// reset script animation waits
+	if (m_animation_script_block_active)
+	{
+		m_animation_script_block_active = false;
+		if (GetObject()->GetScript())
+		{
+			GetObject()->GetScript()->UnBlock();
+		}
+	}
+	m_animation_script_unblock_point = 0;
+	
+	// reset animation frame count
+	m_last_animation_time = 0.0f;
+	
+	m_dont_interrupt=false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::SetAnims( uint32 anim_checksum )
+{
+    m_animScriptName = anim_checksum;
+
+	PlayPrimarySequence( 0, false, 0.0f, 1000.0f, Gfx::LOOPING_HOLD, 0.3f, 1.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CAnimationComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_Assert( pParams );
+
+	pParams->GetChecksum( CRCD(0x5b8c6dc2,"animEventTableName"), &m_animEventTableName, false );
+
+	uint32 animScriptName;
+
+	if ( pParams->GetChecksum( CRCD(0x6c2bfb7f,"animName"), &animScriptName, false ) )
+	{
+		SetAnims( animScriptName );
+		Reset();
+
+		// safety-check to make sure he doesn't start out in the blair witch position
+		// (because of our animation LOD-ing system)
+		// this must be done before the models get initialized?
+		uint32 defaultAnimName;
+		if ( pParams->GetChecksum( CRCD(0xeae64b47,"defaultAnimName"), &defaultAnimName, false ) )
+		{
+			PlaySequence(defaultAnimName);
+			SetLoopingType( Gfx::LOOPING_CYCLE, true );
+		}
+		else
+		{
+			// generally, the blair witch position
+			PlaySequence( CRCD(0x1ca1ff20,"default") );
+		}
+	}
+
+	// if it's the local skater, then give it some procedural animation as well
+	// (the decision to do this should really be coming from a higher-level...)
+ 	if ( GetObject()->GetID() == 0 )
+	{
+		Dbg_MsgAssert( mp_proceduralBones == NULL, ( "Already has procedural bones!" ) );
+		mp_proceduralBones = new Gfx::CProceduralBone[vMAXPROCEDURALBONES];
+
+		Script::CStruct* pTempParams = new Script::CStruct;
+		pTempParams->AppendStructure( pParams );
+
+		pTempParams->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0xfdf0436c,"ProceduralAnim") );
+		pTempParams->AddChecksum( CRCD(0x3ed7262b,"bone_list"), CRCD(0x5d5c0e72,"procedural_skater_bones") );
+		
+		initialize_procedural_bones( pTempParams );
+
+		delete pTempParams;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::ToggleFlipState( void )								   
+{
+	// Flip the animation to the correct orientation
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client = gamenet_man->GetClient( 0 );
+	this->FlipAnimation( GetObject()->GetID(), !IsFlipped(), client->m_Timestamp, true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CAnimationComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+    switch ( Checksum )
+    {        
+        // @script | Obj_EnableAnimBlending | 
+        // @parm name | enabled | whether the anim blending should be enabled
+		// peds should disable blending, as a speed optimization
+		case 0x98a32669:	// Obj_EnableAnimBlending
+		{
+			int shouldBlend;
+			pParams->GetInteger( CRCD(0xaf06447b,"enabled"), &shouldBlend, Script::ASSERT );
+			EnableBlending( shouldBlend );
+		}
+		break;
+        
+		// @script | Obj_SetAnimCycleMode | 
+        // @flag off | turn cycle off (otherwise it will turn it on)
+		case 0x58c52f64:	// Obj_SetAnimCycleMode
+        {
+           if ( pParams->ContainsFlag( CRCD(0xd443a2bc,"off") ) )
+           {
+               SetLoopingType( Gfx::LOOPING_HOLD, true );
+           }
+           else
+           {
+               // maybe we should clear the cycle time or num loops?
+               // or should we leave it at whatever was set before?  hmmm...
+               SetLoopingType( Gfx::LOOPING_CYCLE, true );
+           }
+        }
+        break;
+
+        // @script | Obj_PlayAnim | 
+        // @parm name | Anim | animation to play
+        // @flag wobble | enable wobble
+        // @parmopt float | BlendPeriod | 0.3f | blend period (default may change)
+        // @parmopt int | BlendPeriodPercent | | blend period as a percentage of the anim length (20 means "20 percent")
+        // @parmopt float | speed | 1.0 | 
+        // @flag NoRestart | 
+        // @flag Cycle | 
+        // @flag PingPong | 
+        // @flag Wobble | 
+        // @flag DontInterrupt | Makes further PlayAnims have no effect until the current
+		// animation has finished.
+        // @parmopt name | From | | start, end, or current
+        // @parmopt int | From | | from can be specified as an int (60ths)
+        // @parmopt name | To | | start, end, or current
+        // @parmopt int | To | | to can be specified as an int (60ths)
+        // @flag Backwards | play animation backwards
+		case 0x2cb9ad09:	// Obj_PlayAnim
+		case 0x0b1e7291:	// PlayAnim
+        {
+			PlayAnim( pParams, pScript );
+        
+			Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel();
+			if ( pPrimaryChannel && pPrimaryChannel->GetLoopingType() == Gfx::LOOPING_WOBBLE )
+			{
+				// the skater balance trick needs to do some extra stuff to
+				// handle wobbles, but needs to do it after the anim has been
+				// launched.  the following gives any component a chance to
+				// handle the wobble first...  if it is not handled by the
+				// time it gets to the animation component, then it's ignored
+				// (this works for the case of the skater balance trick, because
+				// it comes early in the component list)
+				GetObject()->CallMemberFunction( CRCD(0xea6d0efd,"SetWobbleDetails"), pParams, pScript );
+			}
+		}
+		break;
+
+		case 0xea6d0efd:	// SetWobbleDetails
+			// this is a special case to make sure that the skater balance
+			// trick component gets a chance to handle wobbles, if such
+			// a component exists.
+			return MF_TRUE;
+        
+        // @script | Obj_AnimComplete | returns true when the object
+        // is done playing an anim.  This is only valid with "hold on
+		// last frame" animations.	
+		case 0x2889c6c9:	// Obj_AnimComplete
+		case 0x76cc99d5: 	// AnimFinished	
+			return ( IsAnimComplete() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE );
+			break;
+
+        // @script | Obj_AnimEquals | True if specified animation is
+        // the one currently playing on the object, False otherwise
+        // @uparmopt name | single animation name to check
+        // @uparmopt [] | array of animation names to check
+		case 0xb5fb7eb6:  	// Obj_AnimEquals
+		case 0x1a0f9646:	// AnimEquals
+			return ( AnimEquals( pParams, pScript ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE );
+			break;
+
+        // @script | Obj_GetAnimSpeed | 
+        // the one currently playing on the object
+        case 0x5195bcbb: // Obj_GetAnimSpeed
+        {
+            pScript->GetParams()->AddFloat(CRCD(0xf0d90109,"speed"), GetAnimSpeed( pParams, pScript ));
+        }
+        break;
+            
+        // @script | BlendPeriodOut | next call to playanim will use this blend period
+        // no matter what is specified in the actual call to playanim
+        // @uparm 1.0 | blend period out value
+		case 0x68c86aec:	// BlendPeriodOut	
+		{
+			if (!pParams->GetFloat(NONAME,&mBlendPeriodOut))
+            {
+				Dbg_MsgAssert(0,("\n%s\nBlendPeriodOut requires a floating point value",pScript->GetScriptInfo()));
+			}
+			// Set the flag so that PlayAnim will use mBlendPeriodOut instead next time.
+			mGotBlendPeriodOut=true;	
+		}
+		break;
+        
+        // @script | LoopingAnim | true if the current animation will loop forever
+		case 0x80fdbdf2:	// LoopingAnim	
+			{
+				return IsLoopingAnim() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			}
+			break;
+			
+		case 0x42ffc3ce: // GetAnimLength
+		{
+			#ifdef __USER_DAN__
+			printf("GetAnimLength\n");
+			Script::PrintContents(pParams);
+			#endif
+			
+			uint32 anim_checksum=0;
+			pParams->GetChecksum(CRCD(0x98549ba4, "anim"), &anim_checksum);
+			
+			if (AnimExists(anim_checksum))
+			{
+				pScript->GetParams()->AddFloat(CRCD(0xfe82614d, "Length"), fabs(AnimDuration(anim_checksum)));
+			}
+			break;
+		}
+		
+        // @script | FrameIs | checks if the current frame is equal
+        // to the specified frame
+        // @uparmopt 1 | frame number
+		case 0x922e7d14: // FrameIs
+		{
+			#ifdef __USER_DAN__
+			printf("FrameIs\n");
+			Script::PrintContents(pParams);
+			#endif
+			
+			float Start,Current,End;
+			GetPrimaryAnimTimes(&Start,&Current,&End);
+			
+			float t=0;
+			pParams->GetFloat(NO_NAME,&t);
+
+			t/=60.0f;
+			
+			float new_frame=Current-Start;
+			float old_frame=m_last_animation_time-Start;
+
+			if (t==new_frame)
+			{
+				return CBaseComponent::MF_TRUE;
+			}
+			
+			if (old_frameGetFloat(NO_NAME,&t);
+
+			if (pParams->ContainsFlag(0x4a07c332/*"frame"*/) || pParams->ContainsFlag(0x19176c5/*"frames"*/))
+			{
+				t/=60.0f;
+			}
+			else if (pParams->ContainsFlag(0x9e497fc6/*"percent"*/))
+			{
+				t=Start+(End-Start)*t/100.0f; // Dodgy?
+			}	
+			else if (pParams->ContainsFlag(0x49e0ee96/*"second"*/) || pParams->ContainsFlag(0xd029f619/*"seconds"*/))
+			{
+				// t is in seconds, so nothing to do.
+			}
+			else
+			{
+				// If they did not specify any of the above, then t is in milliseconds, so convert to seconds.
+				t/=1000.0f;
+			}
+			
+
+			if (pParams->ContainsFlag(0x91a4c826/*"relative"*/))	
+			{
+				if (Start<=End)
+				{
+					m_animation_script_unblock_point=Current+t;
+				}
+				else
+				{
+					m_animation_script_unblock_point=Current-t;
+				}	
+			}
+			else if (pParams->ContainsFlag(0xe18cd075/*"fromend"*/))	
+			{
+				if (Start<=End)
+				{
+					m_animation_script_unblock_point=End-t;
+				}
+				else
+				{
+					m_animation_script_unblock_point=Start+t;
+				}	
+			}
+			else
+			{
+				m_animation_script_unblock_point=t;
+			}
+			
+//			Dbg_MsgAssert( m_animation_script_unblock_point>=Start && m_animation_script_unblock_point<=End, ( "WaitAnim time %f out of range of anim (%f %f) in %s", m_animation_script_unblock_point, Start, End, pScript->GetScriptInfo() ) );
+
+			if ( !GetObject()->GetScript() )
+			{
+				GetObject()->SetScript(new Script::CScript);
+			}
+
+			if ((Start<=End && Current>=m_animation_script_unblock_point) || (Start>=End && Current<=m_animation_script_unblock_point))
+			{
+				GetObject()->GetScript()->UnBlock();
+				m_animation_script_block_active=false;
+			}	
+			else
+			{
+				GetObject()->GetScript()->Block();
+				m_animation_script_block_active=true;
+			}	
+			
+			return CBaseComponent::MF_TRUE;
+		}	
+
+		case 0x5f495ae0:	// InvalidateCache
+		{
+			int num_channels = m_blendChannelList.CountItems();
+			Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();
+			for ( int i = 0; i < num_channels; i++ )
+			{
+				pChannel->InvalidateCache();
+				pChannel = (Gfx::CBlendChannel*)pChannel->GetNext();
+			}
+		}
+		break;
+
+        // @script | Obj_WaitAnimFinished | wait for animation to complete
+		case 0xb628a959:  // Obj_WaitAnimFinished
+			pScript->SetWait(Script::WAIT_TYPE_OBJECT_ANIM_FINISHED,this);
+			break;
+			
+
+		// @script | AnimExists | returns true if the given anim exists
+		// @param name | animation to look for
+		case CRCC(0x9069f357, "AnimExists"):
+		{
+			uint32 anim_name;
+			pParams->GetChecksum(NO_NAME, &anim_name);
+			return AnimExists(anim_name) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; 
+		}
+
+		case 0x83654874: // AddAnimController
+		case 0x02565bd1: //	SetBoneTransMin
+		case 0x3e5b6488: //	SetBoneTransMax
+		case 0xc85b9ac0: //	SetBoneTransSpeed
+		case 0x5ee9d5bd: //	SetBoneTransCurrent
+		case 0x5661fb72: //	SetBoneTransActive
+		case 0xa47767f2: //	SetBoneRotMin
+		case 0x987a58ab: //	SetBoneRotMax
+		case 0x599d3707: //	SetBoneRotSpeed
+		case 0x7235daa7: //	SetBoneRotCurrent
+		case 0x53f06acc: //	SetBoneRotActive
+		case 0xc6889e91: //	SetBoneScaleMin
+		case 0xfa85a1c8: //	SetBoneScaleMax
+		case 0xd32c2724: //	SetBoneScaleSpeed
+		case 0xd12b3713: //	SetBoneScaleCurrent
+		case 0xf11daaae: //	SetBoneScaleActive
+		case 0xd63a1b81: // GetPartialAnimParams
+		case 0xbd4edd44: // SetPartialAnimSpeed
+		case 0x6aaeb76f: // IncrementPartialAnimTime
+		case 0xf5e2b871: // ReversePartialAnimDirection
+			{
+				Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel();
+				if ( pPrimaryChannel )
+				{
+					return pPrimaryChannel->CallMemberFunction( Checksum, pParams, pScript ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}
+			}
+			break;
+		
+		case 0x986d274e: // RemoveAnimController
+			{
+				Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();
+				while ( pChannel )
+				{
+					pChannel->CallMemberFunction( Checksum, pParams, pScript );
+					pChannel = (Gfx::CBlendChannel*)pChannel->GetNext();
+				}
+				return CBaseComponent::MF_TRUE;
+			}
+			break;
+
+		// @script | Obj_Flip | 
+		case 0x27a98022:  // Obj_Flip
+			ToggleFlipState( );
+			break;
+		
+		// @script | Obj_AnimationFlipped | 
+		case 0x6eceb234:  // Obj_AnimationFlipped
+			return ( m_flags & nxANIMCOMPONENTFLAGS_FLIPPED ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+		
+		default:
+            return CBaseComponent::MF_NOT_EXECUTED;
+
+    }
+    
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::AddAnimController( Script::CStruct* pParams )
+{
+	Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel();
+	if ( pPrimaryChannel )
+	{
+		pPrimaryChannel->AddController( pParams );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::ProcessWait( Script::CScript * pScript )
+{
+	switch (pScript->GetWaitType())
+	{
+		case Script::WAIT_TYPE_OBJECT_ANIM_FINISHED:
+			if (IsAnimComplete())
+			{
+				pScript->ClearWait();
+			}
+			break;
+		default:
+			Dbg_MsgAssert(0,("\n%s\nWait type of %d not supported by CAnimationComponent",pScript->GetScriptInfo(),pScript->GetWaitType()));
+			break;
+	}		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::GetDebugInfo( Script::CStruct* p_info )
+{
+
+#ifdef	__DEBUG_CODE__
+
+	Dbg_MsgAssert( p_info, ( "NULL p_info sent to CAnimationComponent::GetDebugInfo" ) );
+
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);
+	
+	p_info->AddInteger( CRCD(0x87ed8a3f,"m_shouldblend"), m_shouldBlend );
+	p_info->AddInteger( CRCD(0xa2f4a5ea,"mGotBlendPeriodOut"), mGotBlendPeriodOut );
+	p_info->AddFloat( CRCD(0xd0902723,"mBlendPeriodOut"), mBlendPeriodOut );
+	p_info->AddChecksum( CRCD(0xc2fe9f39,"m_animScriptName"), m_animScriptName );
+	p_info->AddInteger(CRCD(0x9fdd4257,"m_dont_interrupt"),m_dont_interrupt);
+	
+	Script::CStruct* p_tempParams = new Script::CStruct;
+	p_tempParams->Clear();
+	Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel();
+	if ( pPrimaryChannel )
+	{
+		pPrimaryChannel->GetDebugInfo( p_tempParams );
+		p_info->AddStructure( CRCD(0x969f358c,"primary_channel"), p_tempParams );
+	}
+
+	delete p_tempParams;
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::CBlendChannel* CAnimationComponent::get_primary_channel()
+{
+	// just grab the first item from the list...
+	Gfx::CBlendChannel* pBlendChannel = NULL;
+	pBlendChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();
+	return pBlendChannel;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::pack_degenerate_channels()
+{
+	// this removes all the channels that have expired...
+
+	Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();
+	while ( pChannel )
+	{
+		Gfx::CBlendChannel* pNext = (Gfx::CBlendChannel*)pChannel->GetNext();
+		
+		if ( !pChannel->IsActive() )
+		{
+			Dbg_MsgAssert(pChannel != get_primary_channel(), ("Removing primary channel"));
+			Dbg_MsgAssert( !s_updating_channels, ( "Someone is trying to remove an animation channel while in channel update loop" ) );
+			pChannel->Remove();
+			delete pChannel;
+		}
+		
+		pChannel = pNext;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::create_new_blend_channel( float blend_period )
+{
+	// If there	are too many channels, get rid of the tail channels
+	Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();//Item( 0 );
+	int channelCount = 0;
+
+	while ( pChannel )
+	{
+		channelCount++;
+
+		Gfx::CBlendChannel* pNext = (Gfx::CBlendChannel*)pChannel->GetNext();
+		
+		if ( channelCount >= vNUM_DEGENERATE_ANIMS )
+		{
+			// remove the tail items
+			pChannel->Remove();
+			Dbg_MsgAssert( !s_updating_channels, ( "Someone is trying to remove an animation channel while in channel update loop" ) );
+			delete pChannel;
+		}
+
+		pChannel = pNext;
+	}
+		
+	// degenerate the existing blend channels
+	// make the first channel degenerate
+	Gfx::CBlendChannel* pBlendChannel = m_blendChannelList.CountItems() ? get_primary_channel() : NULL;//	(Gfx::CBlendChannel*)m_blendChannelList.GetItem( 0 );
+	if ( pBlendChannel )
+	{
+		if ( pBlendChannel->Degenerate( blend_period ) )
+		{
+			// degeneration worked, so we want to do this cumulative
+			// blend value thing (not really sure what it is though)
+			float cumulative_blend_value = 0.0f;
+			Gfx::CBlendChannel* pOtherChannels = (Gfx::CBlendChannel*)pBlendChannel->GetNext();
+			while ( pOtherChannels )
+			{
+				// if( !pOtherChannels->IsActive() )
+				if( pOtherChannels->IsDegenerating() )
+				{
+					cumulative_blend_value += 
+						pOtherChannels->GetDegenerationTime() 
+						* pOtherChannels->GetDegenerationTimeToBlendMultiplier();
+				}
+				pOtherChannels = (Gfx::CBlendChannel*)pOtherChannels->GetNext();
+			}
+		
+			float blend_value1 = 1.0f - ( cumulative_blend_value );
+			pBlendChannel->SetDegenerationTimeToBlendMultiplier( blend_value1 / pBlendChannel->GetDegenerationTime() );
+		}
+	}
+	
+	// now add a new channel to the front of the list
+	Gfx::CBlendChannel* pPrimaryChannel = new Gfx::CBlendChannel( GetObject() );
+	m_blendChannelList.AddToHead( pPrimaryChannel );
+	Dbg_MsgAssert( !s_updating_channels, ( "Someone is trying to add an animation channel while in channel update loop" ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimationComponent::AnimEquals( Script::CStruct *pParams, Script::CScript *pScript )
+{	
+	uint32 current_animation=GetCurrentSequence();
+	
+	Script::CComponent *p_comp=NULL;
+	while (true)
+	{
+		p_comp=pParams->GetNextComponent(p_comp);
+		if (!p_comp)
+		{
+			break;
+		}
+
+		if (p_comp->mNameChecksum==0)
+		{
+			// It's an unnamed component
+			
+			Script::CArray *p_array=NULL;
+			if (p_comp->mType==ESYMBOLTYPE_NAME)
+			{
+				// It's an unnamed name. Maybe it's the name of a global array ...
+				Script::CSymbolTableEntry *p_entry=Script::Resolve(p_comp->mChecksum);
+				if (p_entry && p_entry->mType==ESYMBOLTYPE_ARRAY)
+				{
+					// It is a global array
+					p_array=p_entry->mpArray;
+				}
+				else
+				{
+					// Nope, it's just a name, so see if it is the name of the current animation.
+					if (p_comp->mChecksum==current_animation)
+					{
+						return true;
+					}	
+				}
+			}
+			else if (p_comp->mType==ESYMBOLTYPE_ARRAY)
+			{
+				p_array=p_comp->mpArray;
+			}
+			
+			if (p_array)
+			{
+				Dbg_MsgAssert(p_array->GetType()==ESYMBOLTYPE_NAME,("\n%s\nAnimEquals: Array must be of names",pScript->GetScriptInfo()));
+				for (uint32 i=0; iGetSize(); ++i)
+				{
+					if (p_array->GetChecksum(i)==current_animation)
+					{
+						return true;
+					}
+				}
+			}
+		}		
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CAnimationComponent::AnimDuration( uint32 checksum )
+{
+	Gfx::CBonedAnimFrameData *p_anim = find_actual_anim( checksum );
+
+    Dbg_MsgAssert( p_anim, ( "Trying to get duration on an animation that doesn't exist %s %s %s", 
+							 Script::FindChecksumName(m_animScriptName), 
+							 Script::FindChecksumName(checksum),
+							 Script::FindChecksumName(GetObject()->GetID()) ) );
+
+	return ( p_anim->GetDuration() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimationComponent::AnimExists( uint32 checksum )
+{
+	Gfx::CBonedAnimFrameData *p_anim = find_actual_anim( checksum );
+
+	return p_anim;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::AddTime( float incVal )
+{
+	Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel();
+	if ( pPrimaryChannel )
+	{
+		pPrimaryChannel->AddTime( incVal );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::CBonedAnimFrameData* CAnimationComponent::find_actual_anim( uint32 checksum )
+{
+	// need to combine the animation name (Idle) 
+	// with the owner animscript (animload_thps5_human)
+	// to get the asset name
+	return Nx::GetCachedAnim( m_animScriptName + checksum, false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::PrintStatus()
+{
+	// for debugging the viewer object
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::Update()
+{
+	if ( Gfx::CBlendChannel* primary_channel = get_primary_channel() )
+	{
+		m_last_animation_time = primary_channel->GetCurrentAnimTime();
+		
+		/*
+		float s, c, e;
+		primary_channel->GetAnimTimes(&s, &c, &e);
+		printf("Animation Factor Complete = %f\n", c / (e - s));
+		*/
+	}
+	
+	if (m_animation_script_block_active)
+	{
+		if ( !GetObject()->GetScript() )
+		{
+			GetObject()->SetScript( new Script::CScript );
+		}
+		
+		// The script should be blocked at this point, if it isn't that must
+		// be because the script just got reloaded. So clear the m_animation_script_block_active
+		// flag so that it doesn't get stuck on forever.
+		if ( !GetObject()->GetScript()->getBlocked() )
+		{
+			m_animation_script_block_active = false;
+		}	
+		else
+		{
+			float start, current, end;
+			GetPrimaryAnimTimes( &start, ¤t, &end);
+			
+			if ( ( start <= end && current >= m_animation_script_unblock_point ) || ( start >= end && current <= m_animation_script_unblock_point ) )
+			{
+				GetObject()->GetScript()->UnBlock();
+				m_animation_script_block_active = false;
+			}	
+		}	
+	}
+
+	// Alwyas update the channels
+
+	s_updating_channels = true;
+
+	int num_channels = m_blendChannelList.CountItems();
+	Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();
+	for ( int i = 0; i < num_channels; i++ )
+	{
+		pChannel->Update();
+		pChannel = (Gfx::CBlendChannel*)pChannel->GetNext();
+	}
+
+	s_updating_channels = false;
+
+	// remove channels which have decided they are complete
+	pack_degenerate_channels();
+
+	// (Mick) If there is a suspend component
+	// then ask it if we should animate
+	
+	// This call determines whether the object is sufficiently far away that no animation is disabled,
+	// or possibly at an intermediate distance, interleaved. The distance from parent object to camera is cached
+	// for subsequent animation LOD calculations.
+	Dbg_Assert(mp_suspend_component);
+	bool animate = mp_suspend_component->should_animate( &m_parent_object_dist_to_camera );
+	if ( animate )
+	{	
+		update_procedural_bones();
+		
+		// This call determines whether the object is actually on screen; animation is not requried
+		// for offscreen objects.
+		if ( ShouldAnimate() )
+		{
+			update_skeleton();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::UpdateSkeleton()
+{
+	// don't use the animation cache, because
+	// the viewer object doesn't call Update(),
+	// which normally is responsible for
+	// invalidating the cache at the
+	// appropriate times
+	Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel();
+	if ( pPrimaryChannel )
+	{
+		pPrimaryChannel->InvalidateCache();
+	}
+
+	// allows the viewer object to update the skeleton,
+	// outside of the normal Update() function...
+	// (the viewer object sometimes suspends the
+	// component and updates the time manually,
+	// in which case we'd still need to apply
+	// the animation to the skeleton...)
+	update_skeleton();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimationComponent::IsAnimComplete( void )
+{
+	if ( !get_primary_channel() )
+	{
+		return false;
+	}
+
+    return get_primary_channel()->IsAnimComplete();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimationComponent::IsLoopingAnim( void )
+{
+	if ( !get_primary_channel() )
+	{
+		return false;
+	}
+
+    return get_primary_channel()->IsLoopingAnim();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CAnimationComponent::GetCurrentSequence( void )
+{
+	if ( !get_primary_channel() )
+	{
+		return 0;
+	}
+
+    return get_primary_channel()->GetCurrentAnim();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+					  
+void CAnimationComponent::GetPrimaryAnimTimes(float *pStart, float *pCurrent, float *pEnd)
+{
+	*pStart = 0.0f;
+	*pCurrent = 0.0f;
+	*pEnd = 0.0f;
+
+	if ( get_primary_channel() )
+	{
+		get_primary_channel()->GetAnimTimes( pStart, pCurrent, pEnd );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CAnimationComponent::GetCurrentAnimTime( void )
+{
+	if ( !get_primary_channel() )
+	{
+		return 0.0f;
+	}
+
+    return get_primary_channel()->GetCurrentAnimTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::PlaySequence( uint32 checksum, float BlendPeriod )
+{
+#ifdef __NOPT_ASSERT__
+	if ( !AnimExists( checksum ) )
+	{
+		Dbg_Message( "Couldn't find anim %s\n", Script::FindChecksumName(checksum) );
+	}
+#endif
+
+	PlayPrimarySequence( checksum, false, 0.0f, AnimDuration(checksum), Gfx::LOOPING_HOLD, BlendPeriod, 1.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::PlayPrimarySequence( uint32 animName, bool propagate, float start_time, float end_time, Gfx::EAnimLoopingType loop_type, float blend_period, float speed )
+{
+	if ( animName == 0 )
+	{
+		animName = CRCD(0x1ca1ff20,"default");
+	}
+
+	if ( propagate )   
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+		
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+		if ( player && player->IsLocalPlayer())
+		{
+			GameNet::MsgPlayPrimaryAnim	anim_msg;
+			Net::MsgDesc msg_desc;
+			char msg[ Net::Manager::vMAX_PACKET_SIZE ];
+			char* stream;
+			int size;
+			Net::Client* client;
+                    
+			client = gamenet_man->GetClient( player->GetSkaterNumber());
+			Dbg_Assert( client );
+
+			//anim_msg.m_Time = client->m_Timestamp;
+            anim_msg.m_Index = animName;
+			anim_msg.m_ObjId = GetObject()->GetID();
+			anim_msg.m_LoopingType = loop_type;
+			anim_msg.m_StartTime = (unsigned short )( start_time * 4096.0f );
+			anim_msg.m_EndTime = ((unsigned short )( end_time * 4096.0f ));
+			anim_msg.m_BlendPeriod = (unsigned short )( blend_period * 4096.0f );
+			anim_msg.m_Speed = (unsigned short )( speed * 4096.0f );
+
+			stream = msg;
+			//memcpy( stream, &anim_msg.m_Time, sizeof( unsigned int ));
+			//stream += sizeof( int );
+			*stream++ = anim_msg.m_LoopingType;
+			*stream++ = anim_msg.m_ObjId;
+			memcpy( stream, &anim_msg.m_Index, sizeof( uint32 ));
+			stream += sizeof( uint32 );
+			memcpy( stream, &anim_msg.m_StartTime, sizeof( unsigned short ));
+			stream += sizeof( unsigned short );
+			memcpy( stream, &anim_msg.m_EndTime, sizeof( unsigned short ));
+			stream += sizeof( unsigned short );
+			memcpy( stream, &anim_msg.m_BlendPeriod, sizeof( unsigned short ));
+			stream += sizeof( unsigned short );
+			memcpy( stream, &anim_msg.m_Speed, sizeof( unsigned short ));
+			stream += sizeof( unsigned short );
+
+			size = (unsigned int) stream - (unsigned int) msg;
+            
+			msg_desc.m_Data = msg;
+			msg_desc.m_Length = size;
+			msg_desc.m_Id = GameNet::MSG_ID_PRIM_ANIM_START;
+			client->EnqueueMessageToServer( &msg_desc );
+		}
+	}
+    
+	// GJ:  Should this be broken up into two commands/
+	// to degenerate the old, and start playing the new?
+
+//	printf( "Playing anim %s\n", Script::FindChecksumName( animName ), blend_period );
+    
+#ifdef __NOPT_ASSERT__
+	// Define this to disable blending
+	if ( Script::GetInteger( CRCD(0xf098f123,"disable_blending"), Script::NO_ASSERT ) )
+	{
+		blend_period = 0.0f;
+	}
+#endif
+
+	if ( blend_period == 0.0f || !ShouldBlend() )
+	{
+		destroy_blend_channels();
+	}
+
+	create_new_blend_channel( blend_period );
+
+	Gfx::CBlendChannel* pPrimaryChannel = get_primary_channel();
+	Dbg_Assert( pPrimaryChannel );
+    pPrimaryChannel->PlaySequence( animName, start_time, end_time, loop_type, blend_period, speed, IsFlipped() );
+
+	delete_anim_tags();
+	add_anim_tags( animName );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::delete_anim_tags()
+{
+	Script::CStruct* pTags = GetObject()->GetTags();
+	if ( pTags )
+	{
+		pTags->RemoveComponent( CRCD(0x5db4115f,"AnimTags") );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::add_anim_tags( uint32 animName )
+{
+	Script::CStruct* pAnimTagTable = Script::GetStructure(CRCD(0x95807202,"AnimTagTable"), Script::ASSERT);
+	if ( pAnimTagTable )
+	{
+		Script::CStruct* pSubStruct;
+		if ( pAnimTagTable->GetStructure( animName, &pSubStruct, Script::NO_ASSERT ) )
+		{
+			Script::CStruct* pTempStruct = new Script::CStruct;
+
+			// add a new anim tags structure to the tags
+			Script::CStruct* pTags = new Script::CStruct;
+			pTags->AppendStructure( pSubStruct );
+			pTempStruct->AddStructurePointer(CRCD(0x5db4115f,"AnimTags"), pTags);
+
+			GetObject()->SetTagsFromScript( pTempStruct );
+
+			delete pTempStruct;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::SetWobbleTarget( float alpha, bool propagate )
+{
+	if ( propagate )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+		static unsigned char s_last_alpha = 255;
+		
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+		if ( player && player->IsLocalPlayer())
+		{
+			Net::Client* client;
+			GameNet::MsgSetWobbleTarget msg;
+
+			client = gamenet_man->GetClient( player->GetSkaterNumber() );
+			Dbg_Assert( client );
+
+			if( alpha < 0 )
+			{ 
+				alpha = 0;
+			}
+			else if( alpha > 1 )
+			{
+				alpha = 1;
+			}
+			
+			//msg.m_Time = client->m_Timestamp;
+			msg.m_Alpha = (unsigned char ) ( alpha * 255.0f );
+			msg.m_ObjId = GetObject()->GetID();
+			
+			if( s_last_alpha != msg.m_Alpha )
+			{
+				Net::MsgDesc msg_desc;
+
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgSetWobbleTarget );
+				msg_desc.m_Id = GameNet::MSG_ID_SET_WOBBLE_TARGET;
+				msg_desc.m_Singular = true;
+				client->EnqueueMessageToServer( &msg_desc );
+				s_last_alpha = msg.m_Alpha;
+			}
+		}
+	}
+	
+	if ( get_primary_channel() )
+	{
+		Script::CStruct* pTempParams = new Script::CStruct;
+
+		pTempParams->AddFloat( CRCD(0x4d747fa0,"wobbletargetalpha"), alpha );
+
+		get_primary_channel()->CallMemberFunction( CRCD(0xd0209498,"setwobbletarget"), pTempParams, NULL );
+
+		delete pTempParams;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static int s_get_wobble_mask( int variable, float value )
+{   
+	int mask;
+
+	mask = 0;
+	switch( variable )
+	{
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_A:
+			if( value == 0.05f )
+			{
+				mask = 0;
+			}
+			else if( value == 0.1f )
+			{
+				mask = 1;
+			}
+			else if( value == 10.1f )
+			{
+				mask = 2;
+			}
+			else
+			{
+				Dbg_Printf( "value is %f\n", value );
+				Dbg_Assert( 0 );
+			}
+			break;
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_B:
+			if( value == 0.04f )
+			{
+				mask = 0;
+			}
+			else if( value == 10.1f )
+			{
+				mask = 1;
+			}
+			else
+			{
+				Dbg_Printf( "VARIABLE: %d Value %f\n", variable, value );
+				Dbg_Assert( 0 );
+			}
+			break;
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_K1:
+			if( value == 0.0022f )
+			{
+				mask = 0;
+			}
+			else if( value == 20.0f )
+			{
+				mask = 1;
+			}
+			else
+			{
+				Dbg_Printf( "value is %f\n", value );
+				Dbg_Assert( 0 );
+			}
+			break;
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_K2:
+			if( value == 0.0017f )
+			{
+				mask = 0;
+			}
+			else if( value == 10.0f )
+			{
+				mask = 1;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+			break;
+		case GameNet::MsgSetWobbleDetails::vSPAZFACTOR:
+			if( value == 1.5f )
+			{
+				mask = 0;
+			}
+			else if( value == 1.0f )
+			{
+				mask = 1;
+			}
+			else
+			{
+				Dbg_Printf( "value is %f\n", value );
+				Dbg_Assert( 0 );
+			}
+			break;
+		default:
+			Dbg_Assert( 0 );
+			break;
+	}
+	
+	return mask;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::SetWobbleDetails( const Gfx::SWobbleDetails& wobble_details, bool propagate )
+{
+	if ( propagate )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+		if ( player && player->IsLocalPlayer())
+		{
+			Net::Client* client;
+			GameNet::MsgSetWobbleDetails msg;
+			int mask;
+			static char s_last_mask = -1;
+			
+			client = gamenet_man->GetClient( player->GetSkaterNumber() );
+			Dbg_Assert( client );
+
+			//msg.m_Time = client->m_Timestamp;
+			
+			msg.m_WobbleDetails = 0;
+			mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_A, wobble_details.wobbleAmpA );
+			msg.m_WobbleDetails |= mask;
+			mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_B, wobble_details.wobbleAmpB );
+			msg.m_WobbleDetails |= ( mask << 2 );
+			mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_K1, wobble_details.wobbleK1 );
+			msg.m_WobbleDetails |= ( mask << 3 );
+			mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vWOBBLE_K2, wobble_details.wobbleK2 );
+			msg.m_WobbleDetails |= ( mask << 4 );
+			mask = s_get_wobble_mask( GameNet::MsgSetWobbleDetails::vSPAZFACTOR, wobble_details.spazFactor );
+			msg.m_WobbleDetails |= ( mask << 5 );
+			msg.m_ObjId = GetObject()->GetID();
+
+			// Only propagate if it's actually different from our last wobble details message
+			if( s_last_mask != msg.m_WobbleDetails )
+			{
+				Net::MsgDesc msg_desc;
+
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgSetWobbleDetails );
+				msg_desc.m_Id = GameNet::MSG_ID_SET_WOBBLE_DETAILS;
+				msg_desc.m_Singular = true;
+
+				client->EnqueueMessageToServer( &msg_desc );
+
+				s_last_mask = msg.m_WobbleDetails;
+			}
+		}
+	}
+	
+	if ( get_primary_channel() )
+	{
+		Script::CStruct* pTempParams = new Script::CStruct;
+
+		pTempParams->AddFloat( CRCD(0xfd266a26,"wobbleAmpA"), wobble_details.wobbleAmpA );
+		pTempParams->AddFloat( CRCD(0x642f3b9c,"wobbleAmpB"), wobble_details.wobbleAmpB );
+		pTempParams->AddFloat( CRCD(0x0f43fd49,"wobbleK1"), wobble_details.wobbleK1 );
+		pTempParams->AddFloat( CRCD(0x964aacf3,"wobbleK2"), wobble_details.wobbleK2 );
+		pTempParams->AddFloat( CRCD(0xf90b0824,"spazFactor"), wobble_details.spazFactor );
+
+ 		get_primary_channel()->CallMemberFunction( CRCD(0xea6d0efd,"setwobbledetails"), pTempParams, NULL );
+
+		delete pTempParams;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::SetLoopingType( Gfx::EAnimLoopingType looping_type, bool propagate )
+{
+	if ( propagate )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+		static char s_last_type = -1;
+		
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+		if ( player && player->IsLocalPlayer())
+		{
+			Net::Client* client;
+			GameNet::MsgSetLoopingType msg;
+
+			client = gamenet_man->GetClient( player->GetSkaterNumber() );
+			Dbg_Assert( client );
+
+			//msg.m_Time = client->m_Timestamp;
+			msg.m_LoopingType = looping_type;
+			msg.m_ObjId = GetObject()->GetID();
+			if( s_last_type != looping_type )
+			{
+				Net::MsgDesc msg_desc;
+
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgSetLoopingType );
+				msg_desc.m_Id = GameNet::MSG_ID_SET_LOOPING_TYPE;
+				client->EnqueueMessageToServer( &msg_desc );
+				s_last_type = looping_type;
+			}
+		}
+	}
+
+	if ( get_primary_channel() )
+	{
+		get_primary_channel()->SetLoopingType( looping_type );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::ReverseDirection( bool propagate )
+{
+    Dbg_Assert( !propagate );
+
+	if ( get_primary_channel() )
+	{
+		get_primary_channel()->ReverseDirection();
+	}
+
+	// TODO:  eventually turn this into a net message.
+	// For now, only the viewer objects should be reversing the direction
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::SetAnimSpeed( float speed, bool propagate, bool all_channels )
+{	
+	if ( !get_primary_channel() )
+	{
+		// no primary channel, so do nothing
+		return;
+	}
+
+	float cur_speed = get_primary_channel()->GetAnimSpeed();
+
+	if( !all_channels && Mth::Abs( cur_speed - speed ) < vMIN_SPEED_THRESHOLD )
+	{
+		return;
+	}
+
+	if ( propagate )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		Net::Client* client;
+		GameNet::MsgSetAnimSpeed msg;
+		GameNet::PlayerInfo* player;
+
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+		if ( player && player->IsLocalPlayer())
+		{
+			client = gamenet_man->GetClient( player->GetSkaterNumber() );
+			if( client )
+			{
+				Net::MsgDesc msg_desc;
+
+				//msg.m_Time = client->m_Timestamp;
+				msg.m_AnimSpeed = speed;
+				msg.m_ObjId = GetObject()->GetID();
+
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgSetAnimSpeed );
+				msg_desc.m_Id = GameNet::MSG_ID_SET_ANIM_SPEED;
+				client->EnqueueMessageToServer( &msg_desc );
+			}
+		}
+	}
+
+	if (all_channels)
+	{
+		Gfx::CBlendChannel* pChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetNext();
+		while ( pChannel )
+		{
+			pChannel->SetAnimSpeed( speed );
+			pChannel = (Gfx::CBlendChannel*)pChannel->GetNext();
+		}
+	}
+	else
+	{
+		get_primary_channel()->SetAnimSpeed( speed );
+	}
+	
+	if (DebugSkaterScripts && GetObject()->GetID() == 0)
+	{
+		printf("%d: Setting Anim Speed: %f\n",(int)Tmr::GetRenderFrame(),speed);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CAnimationComponent::GetAnimSpeed( Script::CStruct* pParams, Script::CScript* pScript )
+{	
+	if ( !get_primary_channel() )
+	{
+		// no primary channel, so do nothing
+		return 0;
+	}
+
+	float cur_speed = get_primary_channel()->GetAnimSpeed();
+
+    return cur_speed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::EnableBlending( bool enabled )
+{
+	m_shouldBlend = enabled;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimationComponent::ShouldBlend()
+{
+	return m_shouldBlend;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Plays an animation given a set of parameters contained in pParams.
+// This allows both the skater and other moving objects such as pedestrians to share a common-ish interface.
+
+// Returns the checksum of the animation if it did get successfully played, 0 otherwise.
+// (This feature is used by some debug code in the CSkater's PlayAnim script command)
+uint32 CAnimationComponent::PlayAnim(Script::CStruct *pParams, Script::CScript *pScript, float defaultBlendPeriod )
+{
+	if (Gfx::CBlendChannel* primary_channel = get_primary_channel())
+	{
+		m_last_animation_time = primary_channel->GetCurrentAnimTime();
+	}
+
+	if (m_dont_interrupt && !IsAnimComplete())
+	{
+		return 0;
+	}
+		
+    if ( !has_anims() )
+    {
+        // some items don't have animation data associated with it yet...
+        return 0;
+    }
+
+	uint32 AnimChecksum=0;
+	pParams->GetChecksum( CRCD(0x98549ba4,"Anim"), &AnimChecksum );
+	if ( !AnimChecksum )
+	{
+		Script::PrintContents(pParams);
+		Dbg_MsgAssert(0,("\n%s\nobj: %s\nPlayAnim requires an anim name",Script::FindChecksumName(GetObject()->GetID()),pScript->GetScriptInfo()));
+	}
+	
+	float Speed=1.0f;
+	pParams->GetFloat( CRCD(0xf0d90109,"Speed"), &Speed );   
+	
+	uint32 CurrentAnimChecksum = GetCurrentSequence();
+	if ( AnimChecksum == CRCD(0x230ccbf4,"Current") )
+	{
+		AnimChecksum = CurrentAnimChecksum;
+	}
+	else
+	{
+		if (CurrentAnimChecksum==AnimChecksum)
+		{
+			if ( pParams->ContainsFlag( CRCD(0xfe09fe09,"NoRestart") ) )
+			{
+				// Return without doing anything, since the user specified NoRestart, and the
+				// current anim is the same as the requested one.
+				return 0;
+			}
+		}	
+	}
+
+	if ( !AnimExists( AnimChecksum ) )
+	{
+#ifdef __NOPT_ASSERT__
+		if ( Script::GetInt( CRCD(0x56d4128e,"AssertOnMissingAnims"), Script::NO_ASSERT ) )
+		{
+			Dbg_MsgAssert( 0, ( "*** Anim %s (%s) was not defined for object %s!",
+							Script::FindChecksumName(AnimChecksum),
+							Script::FindChecksumName(m_animScriptName),
+							Script::FindChecksumName(GetObject()->GetID() ) ) );
+		}
+		else if ( Script::GetInt( CRCD(0xf42cb81a,"WarnOnMissingAnims"), Script::NO_ASSERT ) )
+		{
+			Dbg_Message( "*** Anim %s (%s) was not defined for object %s!",
+						 Script::FindChecksumName(AnimChecksum),
+						 Script::FindChecksumName(m_animScriptName),
+						 Script::FindChecksumName(GetObject()->GetID() ) );
+		}
+#endif
+
+		// by default, a missing anim will play the christ-pose anim
+		AnimChecksum = CRCD(0x1ca1ff20,"default");
+	}
+	
+	float BlendPeriod=defaultBlendPeriod;
+	float BlendPeriodPercent=0.0f;
+	if (mGotBlendPeriodOut)
+	{
+		// If a BlendPeriodOut command was issued, use that value to override any
+		// value specified by PlayAnim.
+		BlendPeriod=mBlendPeriodOut;
+		// but clear the flag for next time.
+		mGotBlendPeriodOut=false;
+	}
+	else if (pParams->GetFloat(CRCD( 0x4f3abd6c,"BlendPeriodPercent"), &BlendPeriodPercent) )
+	{
+		BlendPeriod = AnimDuration(AnimChecksum) * (BlendPeriodPercent/100.0f);
+	}
+	else
+	{
+		pParams->GetFloat( CRCD(0x8f0d24ed,"BlendPeriod"), &BlendPeriod );
+	}	
+	
+	float Duration=AnimDuration(AnimChecksum);
+	float From=0;
+	float Current=0;
+	float To=Duration;
+	
+	if ( pParams->ContainsFlag( CRCD(0x099371ae,"SyncToPreviousAnim") ) )
+	{
+		float Dummy, PreviousAnimCurrent, PreviousAnimDuration;
+		
+		GetPrimaryAnimTimes(&Dummy, &PreviousAnimCurrent, &Dummy);
+		PreviousAnimDuration = AnimDuration(CurrentAnimChecksum);
+		pParams->GetFloat(CRCD(0x4b4a153a, "EffectivePreviousAnimDuration"), &PreviousAnimDuration);
+		
+		Current = Mth::Clamp(PreviousAnimCurrent / AnimDuration(CurrentAnimChecksum), 0.0f, 1.0f) * Duration;
+		if ( pParams->ContainsFlag( CRCD(0xf8cfd515,"Backwards") ) )
+		{
+			Current = -(Duration - Current);
+		}
+	}
+	else if ( pParams->ContainsFlag( CRCD(0xff5ac79e, "SyncToReversePreviousAnim") ) )
+	{
+		float Dummy, PreviousAnimCurrent, PreviousAnimDuration;
+		
+		GetPrimaryAnimTimes(&Dummy, &PreviousAnimCurrent, &Dummy);
+		PreviousAnimDuration = AnimDuration(CurrentAnimChecksum);
+		pParams->GetFloat(CRCD(0x4b4a153a, "EffectivePreviousAnimDuration"), &PreviousAnimDuration);
+		
+		Current = Mth::Clamp((PreviousAnimDuration - PreviousAnimCurrent) / PreviousAnimDuration, 0.0f, 1.0f) * Duration;
+		if ( pParams->ContainsFlag( CRCD(0xf8cfd515,"Backwards") ) )
+		{
+			Current = -(Duration - Current);
+		}
+	}
+
+	// Need TempCurrent, so that it doesn't conflict with regular Current
+	// which is possibly modified in the above "SyncToPreviousAnim" code block
+	float TempCurrent;
+	float Dummy;
+	GetPrimaryAnimTimes(&Dummy,&TempCurrent,&Dummy);
+	Gfx::GetTimeFromParams( &From, &To, TempCurrent, Duration, pParams, pScript );
+
+	Gfx::EAnimLoopingType LoopingType;
+	Gfx::GetLoopingTypeFromParams( &LoopingType, pParams );
+
+	m_dont_interrupt=pParams->ContainsFlag( CRCD(0x84f13067,"DontInterrupt") );
+	
+#ifdef __NOPT_ASSERT__
+	if ( Script::GetInt( CRCD(0xca108bce,"DebugAnims"), false ) )
+	{
+		if ( GetObject()->GetID() == 0 )
+		{
+			printf( "DebugAnims:  Playing skater anim %s\n", Script::FindChecksumName(AnimChecksum) );
+		}
+	}
+	
+	if (DebugSkaterScripts && AnimChecksum && GetObject()->GetID() == 0)
+	{
+		printf("%d: Playing anim '%s'\n",(int)Tmr::GetRenderFrame(),Script::FindChecksumName(AnimChecksum));
+	}
+#endif
+
+	PlayPrimarySequence( AnimChecksum, true, From, To, LoopingType, BlendPeriod, Speed );
+	
+	if (Current != 0.0f)
+	{
+		get_primary_channel()->AddTime(Current);
+	}
+	
+	/*
+	if ( GetObject()->GetID() == 0 )
+	{
+		printf("-standard-anim-\n");
+		printf("Anim = %s\n", Script::FindChecksumName(AnimChecksum));
+		printf("From = %f\n", From);
+		printf("To = %f\n", To);
+		printf("Duration = %f\n", Duration);
+		printf("Current = %f\n", Current);
+		printf("Speed = %f\n", Speed);
+		printf("BlendPeriod = %f\n", BlendPeriod);
+	}
+	*/
+	
+	uint32 PartialAnim;
+	if (pParams->GetChecksum(CRCD(0x6c565f57, "PartialAnimOverlay"), &PartialAnim))
+	{
+		Script::CStruct* pPartialAnimParams = new Script::CStruct;
+		
+		uint32 partial_anim_id;
+		if (pParams->GetChecksum(CRCD(0xf0ce9e0f, "PartialAnimOverlayId"), &partial_anim_id))
+		{
+			pPartialAnimParams->AddChecksum(CRCD(0x40c698af, "Id"), partial_anim_id);
+		}
+		pPartialAnimParams->AddChecksum(CRCD(0x7321a8d6, "Type"), CRCD(0x659bf355, "PartialAnim"));
+		pPartialAnimParams->AddChecksum(CRCD(0x6c2bfb7f, "AnimName"), PartialAnim);
+		pPartialAnimParams->AddFloat(CRCD(0x46e55e8f, "From"), From);
+		pPartialAnimParams->AddFloat(CRCD(0x28782d3b, "To"), To);
+		pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0xd029f619, "Seconds"));
+		pPartialAnimParams->AddFloat(CRCD(0x230ccbf4, "Current"), Current);
+		pPartialAnimParams->AddFloat(CRCD(0xf0d90109, "Speed"), Speed);
+		switch (LoopingType)
+		{
+			case Gfx::LOOPING_HOLD:
+				break;
+			case Gfx::LOOPING_CYCLE:
+				pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0x4f792e6c, "Cycle"));
+				break;
+			case Gfx::LOOPING_PINGPONG:
+				pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0x3153e314, "PingPong"));
+				break;
+			case Gfx::LOOPING_WOBBLE:
+				pPartialAnimParams->AddChecksum(NO_NAME, CRCD(0x6d941203, "Wobble"));
+				break;
+		}
+		
+		get_primary_channel()->AddController(pPartialAnimParams);
+		
+		delete pPartialAnimParams;
+		
+		#ifdef __NOPT_ASSERT__
+		if ( GetObject()->GetID() == 0 && Script::GetInt( CRCD(0xca108bce,"DebugAnims"), false ) )
+		{
+			printf( "DebugAnims:  Playing skater partial anim overlay %s\n", Script::FindChecksumName(PartialAnim) );
+		}
+		
+		if (DebugSkaterScripts && PartialAnim && GetObject()->GetID() == 0)
+		{
+			printf("%d: Playing partial anim overlay '%s'\n",(int)Tmr::GetRenderFrame(),Script::FindChecksumName(PartialAnim));
+		}
+		#endif
+	}
+
+	return AnimChecksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// static workspace data
+static Gfx::CPose	sBlendPoses[vNUM_DEGENERATE_ANIMS];
+static float		sBlendValues[vNUM_DEGENERATE_ANIMS];
+static Gfx::CPose	sResultPose;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void blend( Gfx::CPose* pPoseList, float* blendVal, int numBones, Gfx::CPose* pResultPose, int numBlendChannels )
+{
+	// the blend value of the primary animation 
+	// should be 1.0, since it's active.
+	Dbg_Assert( blendVal[0] == 1.0f );
+
+	float totalBlendVal = 0.0f;
+	for ( int i = 1; i < numBlendChannels; i++ )
+	{
+		if ( blendVal[i] )
+		{
+			totalBlendVal += 1.0f;
+		}
+	}
+
+	float degenerateBlendTotal = 0.0f;
+	for ( int i = 1; i < numBlendChannels; i++ )
+	{
+		degenerateBlendTotal += blendVal[i];
+	}
+	
+	// normalize them so that they all add up to total blend value
+	blendVal[0] = totalBlendVal - degenerateBlendTotal;
+
+	// Scan through the sets of frames, interpolating between the 2 based on the blend values.
+	float sum = 0.0f;
+
+	int i0, i1;
+	for ( i1 = (numBlendChannels - 1); i1 > 0; --i1 )
+	{
+		float blend1 = blendVal[i1];
+		if ( blend1 )
+		{
+			blend1 += sum;
+
+			i0 = i1 - 1;
+			while(( blendVal[i0] <= 0.0f ) && ( i0 > 0 ))
+			{
+				--i0;
+			}
+
+			float blend0 = blendVal[i0];
+
+			sum += blendVal[i1];
+
+			// Need to normalise blend0 and blend1 so they sum to 1.0.
+			blend0 /= ( blend0 + blend1 );
+			blend1 = 1.0f - blend0;
+
+			Mth::Quat* pRotations0 = pPoseList[i0].m_rotations;
+			Mth::Quat* pRotations1 = pPoseList[i1].m_rotations;
+			Mth::Vector* pTranslations0 = pPoseList[i0].m_translations;
+			Mth::Vector* pTranslations1 = pPoseList[i1].m_translations;
+
+			for( int b = 0; b < numBones; b++ )
+			{
+				*pRotations0 = Mth::FastSlerp( *pRotations0, *pRotations1, 1.0f - blend0 );
+				
+				*pTranslations0 = Mth::Lerp( *pTranslations0, *pTranslations1, 1.0f - blend0 );
+
+				pRotations0++;
+				pRotations1++;
+				pTranslations0++;
+				pTranslations1++;
+			}
+		}
+	}
+
+	// copy the values into the result pose:
+
+	Mth::Quat* pSrcQuat = &pPoseList[0].m_rotations[0];
+	Mth::Vector* pSrcVector = &pPoseList[0].m_translations[0];
+	
+	Mth::Quat* pDstQuat = &pResultPose->m_rotations[0];
+	Mth::Vector* pDstVector = &pResultPose->m_translations[0];
+
+	memcpy( pDstQuat, pSrcQuat, numBones * sizeof(Mth::Quat) );
+	memcpy( pDstVector, pSrcVector, numBones * sizeof(Mth::Vector) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CAnimationComponent::get_blend_channel( int blendChannel, Gfx::CPose* pResultPose, float* pBlendVal )
+{
+	float blendVal = 0.0f;
+	Gfx::CBlendChannel* pBlendChannel = NULL;
+	if ( 1 || ShouldBlend() )
+	{
+		Dbg_MsgAssert( blendChannel >= 0 && blendChannel < (int)m_blendChannelList.CountItems(), ( "out of range blend channel %d (0-%d)", blendChannel, m_blendChannelList.CountItems() ) ); 
+		pBlendChannel = (Gfx::CBlendChannel*)m_blendChannelList.GetItem(blendChannel);
+		blendVal = pBlendChannel->GetBlendValue();
+		Dbg_MsgAssert( blendChannel != 0 || blendVal == 1.0f, ( "Expected primary channel to have a blend value of 1.0f" ) );
+	}
+	else
+	{
+		switch ( blendChannel )
+		{
+			case 0:
+				pBlendChannel = get_primary_channel();
+				blendVal = 1.0f;
+				break;
+			default:
+				Dbg_MsgAssert( 0, ( "Invalid blend channel %d", blendChannel ) );
+		}
+	}
+	
+	if ( blendVal <= 0.0f )
+	{
+		// GJ TODO:  I'm not sure why this case happens,
+		// but it happened on THPS4 as well...  i should
+		// look into this at some point...
+		*pBlendVal = 0.0f;
+		return;
+	}
+
+	Gfx::CSkeleton* pSkeleton = NULL;
+
+	Dbg_Assert(mp_skeleton_component);
+	pSkeleton = mp_skeleton_component->GetSkeleton();
+
+	if ( !pBlendChannel->GetPose( pResultPose ) )//, 
+								  // m_flags & nxANIMCOMPONENTFLAGS_FLIPPED,
+								  // m_flags & nxANIMCOMPONENTFLAGS_ROTATESKATEBOARD,
+								  // pSkeleton ) )
+	{
+		// couldn't find pose...
+		// if it's the primary animation, assert
+		if ( blendChannel == 0 )
+		{
+			Dbg_MsgAssert( 0, ( "No primary animation found!  Is %08x/%s playing a default animation?", GetObject()->GetID(), Script::FindChecksumName(m_animScriptName) ) );
+		}
+	}
+	
+	*pBlendVal = blendVal;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimationComponent::ShouldAnimate()
+{
+#if 1
+
+// The model will be inactive if off the screen
+	Nx::CModel* p_model = mp_model_component->GetModel();
+	return ( p_model && p_model->GetActive() );
+
+#else
+
+// Old version, kind of mixed up	
+	
+	
+	// this function will eventually be used to LOD
+	// our animations (i.e. updating every Nth frame
+	// based on distance)
+	
+	// if we did this, we would want to make sure that
+	// the model's update function to be called at least
+	// once to sync up the skeleton initially (we don't 
+	// want to do it in the constructor, because then
+	// there's an order dependency on the components)
+	
+	// GJ:  Checking whether the model is visible
+	// is a speed optimization, but it's ugly 
+	// because it makes this component dependent on the 
+	// model component...  Eventually, I'd like to 
+	// find a more elegant solution
+		
+	// update the skeleton, if it exists
+	Dbg_Assert(mp_model_component);
+	Nx::CModel* p_model = mp_model_component->GetModel();
+	if ( p_model && p_model->GetActive() )
+	{
+		Mth::Vector sphere = p_model->GetBoundingSphere();
+		
+		printf ("sphere = (%.2f,%.2f,%.2f,%.2f)\n",sphere[X],sphere[Y],sphere[Z],sphere[W]);
+
+		// GJ:  The bounding sphere may have a position
+		// offset built into it, so need to factor that in
+		Mth::Vector pos = GetObject()->GetPos() + sphere;
+		pos[W] = 1.0f;
+		
+		// Mick:  This visibility test always returns true
+		// when in split screen mode 	
+		if ( !Nx::CEngine::sIsVisible( pos,sphere[3] ) )
+		{
+			return false;
+		}
+	}
+
+	// for now, just always animate, even if no model, or the model is inactive !?
+	return true;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::update_skeleton()
+{
+	if ( has_anims() )
+	{
+		Gfx::CSkeleton* pSkeleton = NULL;
+		
+		Dbg_Assert(mp_skeleton_component);
+		pSkeleton = mp_skeleton_component->GetSkeleton();
+
+		if ( !pSkeleton )
+		{
+			return;
+		}
+
+		// Set the current view distance for the skeleton so that it may decide on an appropriate set of
+		// bones to use in the animation.
+		pSkeleton->SetBoneSkipDistance( m_parent_object_dist_to_camera );
+
+		int numBlendChannels = m_blendChannelList.CountItems();
+
+		for ( int blendChannel = 0; blendChannel < numBlendChannels; blendChannel++ )
+		{
+			// this wil loop through each blend channel's animation channels
+			get_blend_channel( blendChannel, &sBlendPoses[blendChannel], &sBlendValues[blendChannel] );
+		}
+
+		// TODO: Here, we could run any controllers that need to act on the final, post-blend pose...
+		if( numBlendChannels > 1 )
+		{
+			blend( &sBlendPoses[0], &sBlendValues[0], pSkeleton->GetNumBones(), &sResultPose, numBlendChannels );
+			pSkeleton->Update( &sResultPose );
+		}
+		else
+		{
+			// In the case that there is just one blend channel, there is no requirement to call the blend
+			// function, which will needlessly copy the quaternion and translation values for each bone from
+			// the sBlendPoses buffer to the sResultPose buffer. Just use the sBlendPoses buffer directly. 
+			pSkeleton->Update( &sBlendPoses[0] );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimationComponent::FlipAnimation( uint32 objId, bool flip, uint32 time, bool propagate )
+{
+	if( propagate )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+        		
+		player = gamenet_man->GetPlayerByObjectID( objId );
+		if( player && player->IsLocalPlayer())
+		{
+			Net::Client* client;
+			GameNet::MsgFlipAnim msg;
+			Net::MsgDesc msg_desc;
+			
+			client = gamenet_man->GetClient( player->GetSkaterNumber());
+			Dbg_Assert( client );
+
+			msg.m_ObjId = objId;
+			msg.m_Flipped = flip;
+			//msg.m_Time = time;
+
+			msg_desc.m_Data = &msg;
+			msg_desc.m_Length = sizeof( GameNet::MsgFlipAnim );
+			msg_desc.m_Id = GameNet::MSG_ID_FLIP_ANIM;
+			client->EnqueueMessageToServer( &msg_desc );
+		}
+	}
+
+	bool oldFlipped = m_flags & nxANIMCOMPONENTFLAGS_FLIPPED;
+
+	SetFlipState( flip );
+
+    return oldFlipped;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CAnimationComponent::SetFlipState( bool shouldFlip )
+{
+	// TODO:  should actually be in blend channel code
+	// since we want certain blend channels to be flipped
+	// (the animation component will still need to know
+	// which flip state to make new channels)
+
+	m_flags &= ~nxANIMCOMPONENTFLAGS_FLIPPED;
+	m_flags |= ( shouldFlip ? nxANIMCOMPONENTFLAGS_FLIPPED : 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CAnimationComponent::destroy_blend_channels()
+{
+	m_blendChannelList.DestroyAllNodes();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		
+void CAnimationComponent::initialize_procedural_bones( Script::CStruct* pParams )
+{
+	Script::CArray* pArray;
+
+	pParams->GetArray( CRCD(0x3ed7262b,"bone_list"), &pArray, Script::ASSERT );
+	
+	for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+	{
+		this->InitializeProceduralBone( pArray->GetChecksum(i) );
+	}
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		
+void CAnimationComponent::update_procedural_bones()
+{
+	int numProceduralBones = this->GetNumProceduralBones(); 
+	Gfx::CProceduralBone* p_current_procedural_bone = this->GetProceduralBones();
+
+	for ( int i = 0; i < numProceduralBones; i++ )
+	{
+		// TODO:  Factor in the frame rate...
+		
+		if ( p_current_procedural_bone->transEnabled )
+		{
+			p_current_procedural_bone->currentTrans += p_current_procedural_bone->deltaTrans;
+			p_current_procedural_bone->currentTrans[X] = (int)p_current_procedural_bone->currentTrans[X] & 0xfff;
+			p_current_procedural_bone->currentTrans[Y] = (int)p_current_procedural_bone->currentTrans[Y] & 0xfff;
+			p_current_procedural_bone->currentTrans[Z] = (int)p_current_procedural_bone->currentTrans[Z] & 0xfff;
+		}
+
+		if ( p_current_procedural_bone->rotEnabled )
+		{
+			p_current_procedural_bone->currentRot += p_current_procedural_bone->deltaRot;
+			p_current_procedural_bone->currentRot[X] = (int)p_current_procedural_bone->currentRot[X] & 0xfff;
+			p_current_procedural_bone->currentRot[Y] = (int)p_current_procedural_bone->currentRot[Y] & 0xfff;
+			p_current_procedural_bone->currentRot[Z] = (int)p_current_procedural_bone->currentRot[Z] & 0xfff;
+		}
+
+		if ( p_current_procedural_bone->scaleEnabled )
+		{
+			p_current_procedural_bone->currentScale += p_current_procedural_bone->deltaScale;
+			p_current_procedural_bone->currentScale[X] = (int)p_current_procedural_bone->currentScale[X] & 0xfff;
+			p_current_procedural_bone->currentScale[Y] = (int)p_current_procedural_bone->currentScale[Y] & 0xfff;
+			p_current_procedural_bone->currentScale[Z] = (int)p_current_procedural_bone->currentScale[Z] & 0xfff;
+		}
+
+		p_current_procedural_bone++;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CAnimationComponent::InitializeProceduralBone( uint32 boneName )
+{
+	if ( m_numProceduralBones >= vMAXPROCEDURALBONES )
+	{
+		Dbg_MsgAssert( 0, ( "Too many procedural bones" ) );
+		return false;
+	}
+
+	Gfx::CProceduralBone* pBone = &mp_proceduralBones[m_numProceduralBones];
+
+	pBone->m_name = boneName;
+
+	pBone->transEnabled = false;
+	pBone->rotEnabled = true;
+
+	pBone->trans0[X] = Script::GetFloat( CRCD(0x802638f7,"trans_min_x"), Script::NO_ASSERT );
+	pBone->trans0[Y] = Script::GetFloat( CRCD(0xf7210861,"trans_min_y"), Script::NO_ASSERT );
+	pBone->trans0[Z] = Script::GetFloat( CRCD(0x6e2859db,"trans_min_z"), Script::NO_ASSERT);
+	pBone->trans0[W] = 1.0f;
+
+	pBone->trans1[X] = Script::GetFloat( CRCD(0x5d39cfda,"trans_max_x"), Script::NO_ASSERT );
+	pBone->trans1[Y] = Script::GetFloat( CRCD(0x2a3eff4c,"trans_max_y"), Script::NO_ASSERT );
+	pBone->trans1[Z] = Script::GetFloat( CRCD(0xb337aef6,"trans_max_z"), Script::NO_ASSERT );
+	pBone->trans1[W] = 1.0f;
+
+	pBone->currentTrans = Mth::Vector(0.0f,0.0f,0.0f,0.0f);
+	pBone->deltaTrans = Mth::Vector(32.0f,32.0f,32.0f,0.0f);
+
+	pBone->rot0[X] = Script::GetFloat( CRCD(0x2dafa9c5,"rot_min_x"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f;
+	pBone->rot0[Y] = Script::GetFloat( CRCD(0x5aa89953,"rot_min_y"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f;
+	pBone->rot0[Z] = Script::GetFloat( CRCD(0xc3a1c8e9,"rot_min_z"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f;
+	pBone->rot0[W] = 1.0f;
+
+	pBone->rot1[X] = Script::GetFloat( CRCD(0xf0b05ee8,"rot_max_x"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f;
+	pBone->rot1[Y] = Script::GetFloat( CRCD(0x87b76e7e,"rot_max_y"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f;
+	pBone->rot1[Z] = Script::GetFloat( CRCD(0x1ebe3fc4,"rot_max_z"), Script::NO_ASSERT ) * 2.0f * Mth::PI / 4096.0f;
+	pBone->rot1[W] = 1.0f;
+
+	pBone->currentRot = Mth::Vector(0.0f,0.0f,0.0f,0.0f);
+	pBone->deltaRot = Mth::Vector(32.0f,32.0f,32.0f,0.0f);
+
+	pBone->scale0[X] = 1.0f; //Script::GetFloat("scale_min_x",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f;
+	pBone->scale0[Y] = 1.0f; //Script::GetFloat("scale_min_y",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f;
+	pBone->scale0[Z] = 1.0f; //Script::GetFloat("scale_min_z",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f;
+	pBone->scale0[W] = 1.0f;
+
+	pBone->scale1[X] = 1.0f; //Script::GetFloat("scale_max_x",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f;
+	pBone->scale1[Y] = 1.0f; //Script::GetFloat("scale_max_y",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f;
+	pBone->scale1[Z] = 1.0f; //Script::GetFloat("scale_max_z",Script::NO_ASSERT) * 2.0f * Mth::PI / 4096.0f;
+	pBone->scale1[W] = 1.0f;
+
+	pBone->currentScale = Mth::Vector(0.0f,0.0f,0.0f,0.0f);
+	pBone->deltaScale = Mth::Vector(32.0f,32.0f,32.0f,0.0f);
+
+	m_numProceduralBones++;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Gfx::CProceduralBone* CAnimationComponent::GetProceduralBoneByName( uint32 boneName )
+{
+	for ( int i = 0; i < m_numProceduralBones; i++ )
+	{
+		if ( boneName == mp_proceduralBones[i].m_name )
+		{
+			return &mp_proceduralBones[i];
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CAnimationComponent::GetNumProceduralBones()
+{
+	return m_numProceduralBones;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::CProceduralBone* CAnimationComponent::GetProceduralBones()
+{
+	return mp_proceduralBones;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CAnimationComponent::IsFlipped()
+{
+	return m_flags & nxANIMCOMPONENTFLAGS_FLIPPED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimationComponent::Finalize()
+{
+	mp_skeleton_component = GetSkeletonComponentFromObject( GetObject() );
+	mp_suspend_component = GetSuspendComponentFromObject( GetObject() );
+	mp_model_component = GetModelComponentFromObject( GetObject() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/animationcomponent.h b/Code/Gel/Components/animationcomponent.h
new file mode 100644
index 0000000..8671724
--- /dev/null
+++ b/Code/Gel/Components/animationcomponent.h
@@ -0,0 +1,188 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       AnimationComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/24/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_ANIMATIONCOMPONENT_H__
+#define __COMPONENTS_ANIMATIONCOMPONENT_H__
+								   
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_ANIMATION CRCD(0x72ad7b23,"Animation")
+#define		GetAnimationComponent() ((Obj::CAnimationComponent*)GetComponent(CRC_ANIMATION))
+#define		GetAnimationComponentFromObject(pObj) ((Obj::CAnimationComponent*)(pObj)->GetComponent(CRC_ANIMATION))
+
+namespace Gfx
+{
+	class CBonedAnimFrameData;
+	class CPose;
+	class CProceduralBone;
+}
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSuspendComponent;
+	class CSkeletonComponent;
+	class CModelComponent;
+
+class CAnimationComponent : public CBaseComponent
+{
+public:
+    CAnimationComponent();
+    virtual ~CAnimationComponent();
+
+public:
+	// Basic component functions
+    virtual void            		Update();
+    virtual void					Finalize( );
+    virtual void            		InitFromStructure( Script::CStruct* pParams ); 
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void					GetDebugInfo( Script::CStruct* p_info );
+	virtual void					ProcessWait( Script::CScript * pScript );
+	
+    static CBaseComponent*			s_create();
+
+public:
+	// Viewer functions
+    void       		            	PrintStatus();
+	void							AddTime( float incVal );
+	float                       	GetCurrentAnimTime( void );
+
+public:
+    void							SetAnims( uint32 anim_checksum );
+	void                        	Reset();
+	void				        	PlaySequence(uint32 checksum, float BlendPeriod=0.3f );    
+    bool				        	AnimEquals(Script::CStruct *pParams, Script::CScript *pScript );
+    uint32                      	PlayAnim(Script::CStruct *pParams, Script::CScript *pScript, float defaultBlendPeriod = 0.3f );
+	uint32							GetAnimScriptName() const { return m_animScriptName; }
+	uint32							GetAnimEventTableName() const { return m_animEventTableName; }
+
+public:
+    // TODO:  Eventually, make this class completely independent of Gfx...
+    // (You shouldn't need to reference CBonedAnimFrameData from this class...)
+    float                       	AnimDuration( uint32 checksum );
+    bool                       		AnimExists( uint32 checksum );
+    		
+protected:
+	bool							ShouldBlend();
+
+public:
+	void							EnableBlending( bool enabled );
+	void 							ToggleFlipState();
+	void							UpdateSkeleton();
+
+public:
+	// CLIENT FUNCTIONS
+	void				        	PlayPrimarySequence( uint32 index, bool propagate, float start_time = 0.0f, float end_time = 1000.0f, Gfx::EAnimLoopingType loop_type = Gfx::LOOPING_CYCLE, float blend_period = 0.3f, float speed = 1.0f );
+
+	void				        	SetLoopingType( Gfx::EAnimLoopingType loopingType, bool propagate );
+	void				        	SetAnimSpeed( float speed, bool propagate, bool all_channels = false );
+    float				        	GetAnimSpeed( Script::CStruct* pParams, Script::CScript* pScript );
+	void							ReverseDirection( bool propagate );	
+
+	// called once per frame, during manuals!
+	void				        	SetWobbleTarget(float alpha, bool propagate);
+
+	// should not be called once per frame...  only when the trick changes
+	void				        	SetWobbleDetails( const Gfx::SWobbleDetails& wobble_details, bool propagate );
+	
+	bool				    		FlipAnimation( uint32 objId, bool flip, uint32 time, bool propagate );
+	void							SetFlipState( bool shouldFlip );
+	void							SetBoneRotation( uint32 boneId, bool shouldRotate );
+	bool							IsFlipped();
+	void							AddAnimController( Script::CStruct* pParams );
+	bool							ShouldAnimate();
+	
+public:
+	// SERVER-ONLY FUNCTIONS
+	bool				        	IsAnimComplete( void );
+	bool				        	IsLoopingAnim( void );
+	uint32				        	GetCurrentSequence( void );
+	void				        	GetPrimaryAnimTimes(float *pStart, float *pCurrent, float *pEnd);
+
+protected:
+	Gfx::CBlendChannel*				get_primary_channel();
+	void							pack_degenerate_channels();
+    Gfx::CBonedAnimFrameData*		find_actual_anim( uint32 checksum );
+	int								get_num_degenerate_anims();
+
+protected:
+	bool							has_anims() { return m_animScriptName != 0; }
+	void							update_skeleton();
+
+	void							get_blend_channel( int blendChannel, Gfx::CPose* pResultPose, float* pBlendVal );
+	void							destroy_blend_channels();
+	void							create_new_blend_channel( float blend_period );
+	void							add_anim_tags( uint32 animName );
+	void							delete_anim_tags();
+
+protected:
+	Lst::Head	m_blendChannelList;
+
+    // Get set by the BlendPeriodOut command.
+	bool                        	mGotBlendPeriodOut;
+	float                       	mBlendPeriodOut;
+
+	bool							m_shouldBlend;
+	
+	// If set, then PlayAnim will do nothing until the current anim has finished.
+	bool							m_dont_interrupt;
+	
+    uint32                      	m_animScriptName;
+	uint32							m_flags;
+	
+	// GJ:  This might get split into its own component later on
+	// (among other things, it can then have its own suspend logic)
+	uint32							m_animEventTableName;
+	
+	float							m_last_animation_time;
+	bool							m_animation_script_block_active;
+	float							m_animation_script_unblock_point;
+
+	float							m_parent_object_dist_to_camera;
+
+	// GJ:  The following used to be in the CProceduralAnimController;
+	// however, this data needs to be shared among different blend
+	// channels so I had to move it here.  Maybe there should be a 
+	// way to copy a controller from one blend channel to another?  
+	// For now, I'm just going to go with the easy implementation
+	// until there's a pressing need for it.  Maybe this should be
+	// separated off into a separate CProceduralAnimComponent that 
+	// the CProceduralAnimController acts upon?)
+protected:
+	int								m_numProceduralBones;
+	Gfx::CProceduralBone*			mp_proceduralBones;
+	bool							InitializeProceduralBone( uint32 boneName );
+	void							initialize_procedural_bones( Script::CStruct* pParams );
+	void							update_procedural_bones();
+	
+protected:
+	CSuspendComponent*				mp_suspend_component;
+	CSkeletonComponent*				mp_skeleton_component;
+	CModelComponent*				mp_model_component;
+
+public:
+	Gfx::CProceduralBone*			GetProceduralBoneByName( uint32 id );
+	int								GetNumProceduralBones();
+	Gfx::CProceduralBone*			GetProceduralBones();
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/avoidcomponent.cpp b/Code/Gel/Components/avoidcomponent.cpp
new file mode 100644
index 0000000..8edae28
--- /dev/null
+++ b/Code/Gel/Components/avoidcomponent.cpp
@@ -0,0 +1,697 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       AvoidComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/02/02
+//****************************************************************************
+
+#include 
+
+#include 
+									
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool ped_debug_on()
+{
+	return ( Script::GetInt( CRCD(0x59f8e435,"ped_debug"), false ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static int get_jump_direction( float rotAng )
+{
+	// split into forward/left/back/right quadrants 
+	// by shifting the angle by 45 degrees
+
+	rotAng += Mth::PI / 4.0f;
+	if ( rotAng < 0 )
+	{
+		rotAng += Mth::PI * 2.0f;
+	}
+
+	int direction = (int)( rotAng / ( Mth::PI / 2.0f ) );
+
+	return direction;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static float get_jump_distance( Script::CStruct* pParams, float rotAng )
+{
+	Dbg_Assert( pParams );
+
+	int direction = get_jump_direction( rotAng );
+
+	float distance = 0.0f;
+
+	switch ( direction )
+	{
+		case 1:	// left
+			pParams->GetFloat( "distLeft", &distance, Script::ASSERT );
+			break;
+
+		case 2:	// backward
+			pParams->GetFloat( "distBack", &distance, Script::ASSERT );
+			break;
+
+		case 3:	// right
+			pParams->GetFloat( "distRight", &distance, Script::ASSERT );
+			break;
+		
+		case 0: // forward
+		case 4:
+		default:
+			pParams->GetFloat( "distForward", &distance, Script::ASSERT );
+			break;
+	}
+
+	return distance;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static bool collides_with( Mth::Vector& vec0, Mth::Vector& vec1 )
+{
+	CFeeler	feeler;
+	
+	feeler.m_start = vec0;
+	feeler.m_end = vec1;
+
+	return feeler.GetCollision();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+void CAvoidComponent::draw_collision_circle( int numPoints, float radius, uint32 rgb0 )
+{
+	Mth::Vector v1, v2;
+
+	Mth::Vector fwd( 0.0f, 0.0f, radius );
+
+	v1 = m_pos + fwd;
+
+	for ( int i = 0; i < numPoints; i++ )
+	{
+		fwd.RotateY( 2.0f * Mth::PI / (float)numPoints );
+		v2 = GetObject()->m_pos + fwd;
+
+		if ( !collides_with( GetObject()->m_pos, v2 ) )
+		{
+			Gfx::AddDebugLine(v1,v2,rgb0,rgb0,1);
+		}
+
+		v1 = v2;
+	}
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAvoidComponent::play_appropriate_jump_anim( float rotAng, Script::CStruct* pParams )
+{
+	// split into forward/left/back/right quadrants 
+	// by shifting the angle by 45 degrees
+
+	rotAng += Mth::PI / 4.0f;
+	if ( rotAng < 0 )
+	{
+		rotAng += Mth::PI * 2.0f;
+	}
+
+	int direction = (int)( rotAng / ( Mth::PI / 2.0f ) );
+	// 0 = forward
+	// 1 = left
+	// 2 = back
+	// 3 = right
+
+	Dbg_Assert( pParams );
+
+	uint32 animChecksum;
+	bool flipped;
+
+	switch ( direction )
+	{
+		case ( 0 ):	// JUMP FORWARD
+		case ( 4 ):
+//			Dbg_Message( "Ped jumps forward to avoid skater" );
+			pParams->GetChecksum( CRCD(0x4de2c9e8,"JumpForward"), &animChecksum, true );
+			break;
+
+		case ( 1 ):	// JUMP LEFT
+			{
+//				Dbg_Message( "Ped jumps left to avoid skater" );
+				if ( pParams->GetChecksum( CRCD(0x733da756,"JumpLeft"), &animChecksum, false ) )
+				{
+					flipped = 0;
+				}
+				else
+				{
+					pParams->GetChecksum( CRCD(0xa7a0dd72,"JumpRight"), &animChecksum, true );
+					flipped = 1;
+				}
+				
+				Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+				if ( pAnimationComponent )
+				{
+					pAnimationComponent->FlipAnimation( GetObject()->GetID(), flipped, 0, true );
+				}
+			}
+			break;
+
+		case ( 2 ):	// JUMP BACK
+//			Dbg_Message( "Ped jumps back to avoid skater" );
+			pParams->GetChecksum( CRCD(0x64948109,"JumpBack"), &animChecksum, true );
+			break;
+
+		case ( 3 ):	// JUMP RIGHT
+			{
+//				Dbg_Message( "Ped jumps right to avoid skater" );
+				if ( pParams->GetChecksum( CRCD(0xa7a0dd72,"JumpRight"), &animChecksum, false ) )
+				{
+					flipped = 0;
+				}
+				else
+				{
+					pParams->GetChecksum( CRCD(0x733da756,"JumpLeft"), &animChecksum, true );
+					flipped = 1;
+				}
+				
+				Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+				if ( pAnimationComponent )
+				{
+					pAnimationComponent->FlipAnimation( GetObject()->GetID(), flipped, 0, true );
+				}
+			}
+			break;
+
+		default:
+			Dbg_MsgAssert( 0, ( "Ped jumped in unrecognized direction: %d", direction ) );
+			return;
+			break;
+			
+	}
+	
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+
+    if ( pAnimationComponent )
+    {
+        pAnimationComponent->PlaySequence( animChecksum, 0 );
+        m_jumpTime = pAnimationComponent->AnimDuration( animChecksum );
+    }
+    else
+    {
+        Dbg_MsgAssert( 0, ( "No animation component" ) );
+    }
+}
+                                                                 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CAvoidComponent::get_ideal_heading()
+{
+	Mdl::Skate* skate = Mdl::Skate::Instance();
+				
+	// assume pedestrians only appear in "1 player" game for now...
+	// If this assumption becomes incorrect, cycle through skate->GetNumSkaters( )
+	// and find the closest.
+	CSkater *pSkater = skate->GetLocalSkater();
+	Dbg_MsgAssert( pSkater, ( "Ped avoidance only works in 1-player games." ) );
+	
+	Mth::Vector jumpDir;
+
+	// find the 2 directions perpendicular to the skater's line of motion...
+	// and select the direction that will take us further away from the
+	// skater
+	
+	jumpDir = (pSkater->GetDisplayMatrix())[Mth::AT];	
+	jumpDir[Y] = 0.0f;
+	jumpDir.Normalize( );
+	
+	Mth::Vector upVect( 0.0f, 1.0f, 0.0f, 0.0f );
+	jumpDir = Mth::CrossProduct( jumpDir, upVect );
+
+	// chose the direction that's farther...
+	Mth::Vector posPlus;
+	Mth::Vector posMinus;
+	posPlus = GetObject()->m_pos + jumpDir;
+	posMinus = GetObject()->m_pos - jumpDir;
+			
+	if ( Mth::DistanceSqr( pSkater->m_pos, posPlus ) < Mth::DistanceSqr( pSkater->m_pos, posMinus ) )
+	{
+		jumpDir[X] = -jumpDir[X];
+		jumpDir[Z] = -jumpDir[Z];
+	}
+	
+	return Mth::RadToDeg( Mth::GetAngle( GetObject()->m_matrix, jumpDir ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CAvoidComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CAvoidComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CAvoidComponent::CAvoidComponent() : CBaseComponent()
+{
+	SetType( CRC_AVOID );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CAvoidComponent::~CAvoidComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CAvoidComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CAvoidComponent::Update()
+{
+#ifdef __NOPT_ASSERT__
+//	if ( ped_debug_on() )
+	{
+		//Gfx::AddDebugCircle( m_pos, 20, 15.0f, 0x0000ffff, 1 );
+		//Gfx::AddDebugCircle( m_stored_pos, 20, 15.0f, 0x00ff00ff, 1 );
+		//draw_collision_circle( 100, 50.0f, 0xff0000ff );	
+	}
+#else
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAvoidComponent::jump( Mth::Matrix& mat0, Mth::Matrix& mat1, float rotAng, float jumpDist, float maxHerdDistance )
+{
+	mat1 = mat0;
+	
+	mat1.RotateYLocal( Mth::DegToRad(rotAng) );
+
+	// test for an extra 15 units (1.25 feet) radius of clearance 
+	// around the player to account for his actual body
+	// (need to rotate it slightly up, to account for peds
+	// that are below ground level...  not sure why this
+	// happens, but will check it out in THPS5...)
+	const float clearanceDist = 15.0f;
+	Mth::Vector collisionTestPos = mat1.Transform( Mth::Vector( 0.0f, 10.0f, jumpDist + clearanceDist ) );
+	
+#ifdef __NOPT_ASSERT__
+/*
+	if ( ped_debug_on() )
+	{
+		Gfx::AddDebugCircle( collisionTestPos, 20, 15.0f, 0x00ff00ff, 10 );
+	}
+*/
+#endif
+
+	if ( collides_with( mat0.GetPos(), collisionTestPos ) )
+	{
+		Dbg_Message( "Collides with something!" );
+		return false;
+	}
+	else
+	{
+		// build target matrix
+		Mth::Vector targetPos = mat1.Transform( Mth::Vector( 0.0f, 0.0f, jumpDist ) );
+		mat1[Mth::POS] = targetPos;
+
+		Mdl::Skate* skate = Mdl::Skate::Instance();
+		CSkater *pSkater = skate->GetLocalSkater();
+		Dbg_MsgAssert( pSkater, ( "Ped avoidance only works in 1-player games." ) );
+		
+		Mth::Vector toSkater = targetPos - pSkater->m_pos;
+		if ( toSkater.Length() < 30.0f )
+		{
+			printf( "Too close to skater %f!\n", toSkater.Length() );
+			return false;
+		}
+
+		// check if we're going too far from the original point
+		// TODO:  check for distance from the path, not the point
+		// don't allow going too far from the original path,
+		// or else you'll be able to herd them into weird
+		// positions...
+		Mth::Vector totalAvoidVec = targetPos - m_avoidOriginalPos;
+		totalAvoidVec[Y] = 0.0f;
+
+		if ( totalAvoidVec.Length() >= maxHerdDistance )
+		{
+			Dbg_Message( "Too far from the original avoid point %f", totalAvoidVec.Length() );
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::EMemberFunctionResult CAvoidComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch (Checksum)
+	{
+		// @script | Ped_SelectAvoidPoint | Will cause the pedestrian to jump out of
+		// the way of the player.  He will land on a circle, JumpDist away from
+		// the last postition stored in Obj_StorePos.  Once he lands the jump,
+		// the LandedEscapeJump exception is triggered. 
+		// @parmopt name | JumpLeft | jumpLeft | name of anim to play when jumping left
+		// @parmopt name | JumpRight | jumpRight | name of anim to play when jumping right
+		// @parmopt name | JumpForward | jumpForward | name of anim to play when jumping forward
+		// @parmopt name | JumpBack | jumpBack | name of anum to play when jumping back
+		// position where the ped jumps to 
+		case 0x6157cbc0:	// Ped_SelectAvoidPoint
+		{
+			// this is the starting point...
+			Mth::Matrix mat0;
+			mat0 = GetObject()->GetDisplayMatrix();
+			mat0[Mth::POS] = GetObject()->m_pos;
+
+			// this is where we will eventually end up
+			Mth::Matrix mat1;
+
+			float maxHerdDistance = 100.0f;
+			pParams->GetFloat( "maxHerdDistance", &maxHerdDistance, Script::NO_ASSERT );
+
+			float idealHeading = get_ideal_heading();
+
+			if ( jump(mat0, mat1, idealHeading, get_jump_distance( pParams, idealHeading ), maxHerdDistance ) )
+			{
+			   Dbg_Message( "Selecting ideal jump (ang=%f newpos=(%f, %f, %f)",
+							idealHeading,
+							mat1[Mth::POS][X],
+							mat1[Mth::POS][Y],
+							mat1[Mth::POS][Z] );   			
+			}
+
+			// GJ:  Try a few more directions before trying 
+			// to jump back to the original point...
+			else if ( jump(mat0, mat1, idealHeading + 180.0f, get_jump_distance( pParams, idealHeading + 180.0f ), maxHerdDistance) )
+			{
+				// jump to the other side was successful!
+				printf( "Jumping to the other side...\n" );
+			}
+			else if ( jump(mat0, mat1, idealHeading - 90.0f, get_jump_distance( pParams, idealHeading - 90.0f ), maxHerdDistance) )
+			{
+				// jump away was successful!
+				printf( "Jumping away...\n" );
+			}
+			else 
+			{
+				Dbg_Message( "Jumping back towards original point %f %f %f",
+							 m_avoidOriginalPos[X],
+							 m_avoidOriginalPos[Y],
+							 m_avoidOriginalPos[Z] );
+
+				// otherwise, jump back towards original point
+				Mth::Vector jumpDir;
+				jumpDir = m_avoidOriginalPos - mat0[Mth::POS];
+
+				float rotAng;
+				rotAng = Mth::GetAngle( GetObject()->m_matrix, jumpDir );
+
+				bool success = jump( mat0, mat1, rotAng, get_jump_distance(pParams, rotAng), maxHerdDistance );
+
+				if ( !success )
+				{
+					// jump in place
+					mat1 = mat0;
+
+					return CBaseComponent::MF_FALSE;
+				}
+			}  			
+
+			// don't actually want to rotate the ped
+			// (just translate it)
+			mat1[Mth::RIGHT] = mat0[Mth::RIGHT];
+			mat1[Mth::UP] = mat0[Mth::UP];
+			mat1[Mth::AT] = mat0[Mth::AT];
+
+			m_avoidInterpolator.setMatrices( &mat0, &mat1 );
+			m_avoidAlpha = 0.0f;
+
+#ifdef __NOPT_ASSERT__
+/*
+			if ( ped_debug_on() )
+			{
+				Gfx::AddDebugCircle( mat0[Mth::POS], 20, 15.0f, 0x00ff00ff, 100 );
+				Gfx::AddDebugCircle( mat1[Mth::POS], 20, 15.0f, 0x00ff00ff, 100 );
+			}
+*/
+#endif
+
+			Mth::Vector jumpDir;
+			jumpDir = mat1[Mth::POS] - mat0[Mth::POS];
+
+			float rotAng;
+			rotAng = Mth::GetAngle( GetObject()->m_matrix, jumpDir );
+
+			// rotAng of zero means jump forward, so:
+			play_appropriate_jump_anim( rotAng, pParams );
+		}
+		break;
+
+		// @script | Ped_BackOnOriginalPath |
+		case 0xd2aa4d2d:	// Ped_BackOnOriginalPath
+		{
+			float safeDist;
+			safeDist = pParams->GetFloat( "dist", &safeDist, Script::ASSERT );
+
+			Mth::CBBox theBox;
+			theBox.SetMin( m_avoidOriginalPos - Mth::Vector( safeDist, safeDist, safeDist ) );
+			theBox.SetMax( m_avoidOriginalTargetPos + Mth::Vector( safeDist, safeDist, safeDist ) );
+
+			if ( theBox.Within( GetObject()->m_pos ) )
+			{
+				printf( "Intersects!\n" ); 
+			//	return CBaseComponent::MF_TRUE;
+			}
+			else
+			{
+				printf( "Doesn't intersect!\n" ); 
+				return CBaseComponent::MF_FALSE;
+			}
+
+#if 0
+			Mth::Vector originalPath;
+			originalPath = m_avoidOriginalTargetPos - m_avoidOriginalPos;
+
+			Mth::Vector upVect( 0.0f, 1.0f, 0.0f, 0.0f );
+
+			Mth::Vector normalToOriginalPath;
+			normalToOriginalPath = Mth::CrossProduct( originalPath, upVect );
+
+			// is the distance to the original path
+			// choose the direction that's closer...
+			Mth::Vector posPlus;
+			Mth::Vector posMinus;
+			posPlus = GetObject()->m_pos + normalToOriginalPath * safeDist;
+			posMinus = GetObject()->m_pos - normalToOriginalPath * safeDist;
+
+			Mth::Line originalLine( m_avoidOriginalTargetPos, m_avoidOriginalPos );
+			Mth::Line currentLine1( m_avoidOriginalTargetPos, posPlus ); 
+			Mth::Line currentLine2( m_avoidOriginalTargetPos, posMinus ); 
+
+			Mth::Vector my_pt;
+			Mth::Vector other_pt;
+			float dummyFloat;
+
+			if ( Mth::LineLineIntersect( originalLine, currentLine1, &my_pt, &other_pt, &dummyFloat, &dummyFloat, false ))
+			{
+				Mth::Vector difference = my_pt - other_pt;
+				printf( "intersects with current line 1: %f\n", difference.Length() );
+			//	return CBaseComponent::MF_FALSE;
+			}
+			else if ( Mth::LineLineIntersect( originalLine, currentLine2, &my_pt, &other_pt, &dummyFloat, &dummyFloat, false ))
+			{
+				Mth::Vector difference = my_pt - other_pt;
+				printf( "intersects with current line 2: %f\n", difference.Length() );
+			//	return CBaseComponent::MF_FALSE;
+			}
+			else
+			{
+				printf( "does not intersect\n" );
+		//		return CBaseComponent::MF_FALSE;
+			}
+#endif
+			return CBaseComponent::MF_FALSE;
+		}
+		break;
+
+		// @script | Ped_MoveTowardsAvoidPoint |
+		case 0x41226942:	// Ped_MoveTowardsAvoidPoint
+		{
+			Mth::Matrix mat0;
+			m_avoidAlpha += ( m_jumpTime * ( 1.0f / 60.0f ) * Tmr::FrameRatio() );
+			m_avoidInterpolator.getMatrix( &mat0, m_avoidAlpha );
+			GetObject()->m_pos = mat0[Mth::POS];
+			GetObject()->m_matrix = mat0;
+			GetObject()->m_matrix[Mth::POS] = Mth::Vector(0.0f, 0.0f, 0.0f, 1.0f);
+		}
+		break;
+
+		// @script | Ped_AvoidPointReached |
+		case 0x3c7f8f3e:	// Ped_AvoidPointReached
+		{
+			Dbg_Assert( GetAnimationComponentFromObject( GetObject() ) );
+			return ( m_avoidAlpha >= 1.0f && GetAnimationComponentFromObject( GetObject() )->IsAnimComplete() ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+
+		case 0x6a4bca91:	// Ped_RememberCurrentPosition
+			m_avoidOriginalPos = GetObject()->m_pos;
+		break;
+
+		case 0x3fb422e8:	// Ped_RememberNextWaypoint
+		{
+			Obj::CMotionComponent* pMotionComponent = GetMotionComponentFromObject( GetObject() );
+			if ( pMotionComponent && pMotionComponent->GetPathOb() )
+			{
+				m_avoidOriginalTargetPos = pMotionComponent->GetPathOb()->m_wp_pos_to;
+			}
+			else
+			{
+				m_avoidOriginalTargetPos = GetObject()->m_pos;
+			}
+		}
+		break;
+
+		case 0x539a32dc:	// Ped_RememberStickToGround
+		{
+			Obj::CMotionComponent* pMotionComponent = GetMotionComponentFromObject( GetObject() );
+			Dbg_Assert( pMotionComponent );
+			m_avoidOriginalStickToGround = ( pMotionComponent->m_movingobj_flags |= MOVINGOBJ_FLAG_STICK_TO_GROUND );
+		}	
+		break;
+
+		case 0x53bc3b4e:	// Ped_RestoreStickToGround
+		{
+			Obj::CMotionComponent* pMotionComponent = GetMotionComponentFromObject( GetObject() );
+			if ( pMotionComponent )
+			{
+				if ( m_avoidOriginalStickToGround )
+				{
+					pMotionComponent->m_movingobj_flags |= MOVINGOBJ_FLAG_STICK_TO_GROUND;
+				}
+				else
+				{
+					pMotionComponent->m_movingobj_flags &= ~MOVINGOBJ_FLAG_STICK_TO_GROUND;
+				}
+			}
+		}
+		break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAvoidComponent::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to C......Component::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*
+	p_info->AddInteger("m_never_suspend",m_never_suspend);
+	p_info->AddFloat("m_suspend_distance",m_suspend_distance);
+	*/
+	
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo( p_info );	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/avoidcomponent.h b/Code/Gel/Components/avoidcomponent.h
new file mode 100644
index 0000000..73d1c9e
--- /dev/null
+++ b/Code/Gel/Components/avoidcomponent.h
@@ -0,0 +1,69 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       AvoidComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/02/02
+//****************************************************************************
+
+#ifndef __COMPONENTS_AVOIDCOMPONENT_H__
+#define __COMPONENTS_AVOIDCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_AVOID CRCD(0x6eb050ed,"Avoid")
+#define		GetAvoidComponent() ((CAvoidComponent*)GetComponent(CRC_AVOID))
+#define		GetAvoidComponentFromObject(pObj) ((CAvoidComponent*)(pObj)->GetComponent(CRC_AVOID))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CAvoidComponent : public CBaseComponent
+{
+// share, dammit.
+friend class CPedLogicComponent;
+
+public:
+    CAvoidComponent();
+    virtual ~CAvoidComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+protected:
+	void							play_appropriate_jump_anim( float rotAngle, Script::CStruct* pParams );
+	bool							jump( Mth::Matrix& mat0, Mth::Matrix& mat1, float rotAng, float jumpDist, float maxHerdDistance );
+	float							get_ideal_heading();
+
+protected:
+	// Functions/member variables used for getting out of the skater's way:
+	float							m_jumpTime;
+
+	Mth::SlerpInterpolator			m_avoidInterpolator;
+	float							m_avoidAlpha;
+	Mth::Vector						m_avoidOriginalPos;
+	Mth::Vector						m_avoidOriginalTargetPos;
+	bool							m_avoidOriginalStickToGround;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/bouncycomponent.cpp b/Code/Gel/Components/bouncycomponent.cpp
new file mode 100644
index 0000000..6b55345
--- /dev/null
+++ b/Code/Gel/Components/bouncycomponent.cpp
@@ -0,0 +1,734 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       BouncyComponent.cpp
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+//#define	__DEBUG_BOUNCY__
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+//#include 		// TODO:  remove - only used for getting the bouncing box via collision
+#include 				// and this
+#include 				// and this
+
+namespace Obj
+{
+
+#define SWITCH_STATE( x )					m_state = x
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent *	CBouncyComponent::s_create()
+{
+	return static_cast(new CBouncyComponent);	
+}
+    
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBouncyComponent::CBouncyComponent() : CBaseComponent()
+{
+	SetType(CRC_BOUNCY);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBouncyComponent::~CBouncyComponent()
+{
+
+	if ( mp_bounce_slerp )
+		delete mp_bounce_slerp;
+	if (mp_collide_script_params)
+		delete mp_collide_script_params;
+	if (mp_bounce_script_params)
+		delete mp_bounce_script_params;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Returns 
+//	0 (MF_FALSE) if executed and returned false
+//  1 (MF_TRUE)  if executed and returned true
+//  2 (MF_NOT_EXECUTRED)    if not executed
+
+CBaseComponent::EMemberFunctionResult CBouncyComponent::CallMemberFunction( uint32 Checksum, Script::CScriptStructure *pParams, Script::CScript *pScript )
+{
+	
+	switch ( Checksum )
+	{
+
+        // @script | BouncyObj_PlayerCollisionOn | turn player collision on
+		case ( 0xf9055d81 ): // "BouncyObj_PlayerCollisionOn"
+			m_bouncyobj_flags &= ~BOUNCYOBJ_FLAG_PLAYER_COLLISION_OFF;
+			return MF_TRUE;
+
+        
+        // @script | BouncyObj_PlayerCollisionOff | turn player collision off
+		case ( 0xf64ef88e ): // "BouncyObj_PlayerCollisionOff"
+			m_bouncyobj_flags |= BOUNCYOBJ_FLAG_PLAYER_COLLISION_OFF;
+			return MF_TRUE;
+
+        // @script | BouncyObj_Go | The vector can be anything you want...
+        // the initial velocity.  It's optional, without it the object will
+        // fall from it's current location.  If it's already on the ground
+        // probably nothing will happen
+        // @uparmopt (0, 0, 0) | vector
+		case ( 0xfea0e952 ): // "BouncyObj_Go"
+		{
+			Mth::Vector vel;
+			vel.Set( );
+			pParams->GetVector( NONAME, &vel );
+			if ( m_state == BOUNCYOBJ_STATE_BOUNCING )
+			{
+				Dbg_Message( "\n%s\nWarning:  Calling BouncyObj_Go when object is already bouncing.", pScript->GetScriptInfo( ) );
+			}
+			if ( pParams->ContainsFlag( 0x26af9dc9 ) ) // "FLAG_MAX_COORDS"
+			{
+				vel.ConvertToMaxCoords( );
+			}
+			bounce( vel );
+			return MF_TRUE;
+		}
+	}
+	return MF_NOT_EXECUTED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CBouncyComponent::InitFromStructure( Script::CStruct* pParams )
+{
+
+	m_time = 0.01666666f;			// Patch!!!!
+
+	m_state = BOUNCYOBJ_STATE_INIT;
+
+	m_bounce_mult = 0.9f;
+	m_bouncyobj_flags = 0;
+
+	// Bouncy object properties:
+	m_random_dir = 10;
+	pParams->GetInteger( 0x767f323f, &m_random_dir ); // RandomDirectionOnBounce
+	
+	m_bounce_mult = 0.6f;
+	pParams->GetFloat( 0xaf6034df, &m_bounce_mult ); // BounceMult
+	
+	m_min_bounce_vel = 3;
+	pParams->GetFloat( 0xff08ee44, &m_min_bounce_vel );  // MinBounceVel
+	m_min_bounce_vel = FEET_TO_INCHES( m_min_bounce_vel );
+	
+	m_rot_per_axis = 360;
+	pParams->GetInteger( 0x33ee064f, &m_rot_per_axis ); // ConstRot
+	
+	m_bounce_rot = 45;
+	pParams->GetInteger( 0xde14151d, &m_bounce_rot ); // BounceRot
+	
+	m_rest_orientation_type=0;
+	pParams->GetInteger( 0xc0f30f40, &m_rest_orientation_type ); // RestOrientationType
+
+	m_gravity = 32;
+	pParams->GetFloat( 0xa5e2da58, &m_gravity ); // Gravity
+	m_gravity = FEET_TO_INCHES( m_gravity );
+	
+	m_bounciness = 1.0f;
+	pParams->GetFloat( 0x373d4e7d, &m_bounciness ); // Bounciness
+	
+	m_bounce_sound=0;
+	pParams->GetChecksum( 0x1fb2f60c, &m_bounce_sound ); // BounceSound
+	
+	m_hit_sound=0;
+	pParams->GetChecksum( 0x29e2e9e0, &m_hit_sound ); // HitSound
+	
+	m_up_mag = 32.0f;
+	pParams->GetFloat( 0x3eca5c95, &m_up_mag ); // UpMagnitude
+	m_up_mag = FEET_TO_INCHES( m_up_mag );
+	
+	m_destroy_when_done = !pParams->ContainsFlag( 0x65ba5a75 ); // NoDestroy
+	
+	m_min_initial_vel = 15; // feet per second...
+	pParams->GetFloat( 0x96763c79, &m_min_initial_vel ); // MinInitialVel
+	m_min_initial_vel = FEET_TO_INCHES( m_min_initial_vel );
+	
+	// Set up collision
+
+	uint32	col_type_checksum = Nx::vCOLL_TYPE_NONE;
+	pParams->GetChecksum("CollisionMode", &col_type_checksum);
+	#if 0
+
+	
+	// TODO - handle getting bouncing box from a collision component
+	Nx::CollType col_type = Nx::CCollObj::sConvertTypeChecksum(col_type_checksum);
+	
+	if (col_type != Nx::vCOLL_TYPE_NONE)
+	{
+		m_bounce_collision_radius=0.0f;
+		m_skater_collision_radius_squared=0.0f;
+//		InitCollision(col_type, p_coll_tri_data);
+		// Calc radius from bounding box.
+		if (((CMovingObject*)(GetObject()))->GetCollisionObject())
+		{
+			Nx::CCollObjTriData *p_tri_data=((CMovingObject*)(GetObject()))->GetCollisionObject()->GetGeometry();
+			Dbg_MsgAssert(p_tri_data,("NULL p_tri_data"));
+			Mth::CBBox bbox=p_tri_data->GetBBox();
+			m_bounce_collision_radius=Mth::Distance(bbox.GetMax(),bbox.GetMin())/2.0f;
+			m_skater_collision_radius_squared=m_bounce_collision_radius*m_bounce_collision_radius;
+		}
+	}
+	else
+	#endif
+	
+	{
+		m_bounce_collision_radius=0.0f;
+		m_skater_collision_radius_squared=50.0f;
+		pParams->GetFloat("CollisionRadius",&m_skater_collision_radius_squared);
+		m_skater_collision_radius_squared*=m_skater_collision_radius_squared;
+	}
+	
+
+	// We need to copy in any script struct parameters, as they might have generated temporarily
+	pParams->GetChecksum(CRCD(0x58cdc0f0,"CollideScript"),&m_collide_script);
+	Script::CStruct * p_collide_script_params = NULL;
+	pParams->GetStructure(CRCD(0x24031741,"CollideScriptParams"),&p_collide_script_params);		
+	if (p_collide_script_params)
+	{
+		mp_collide_script_params = new Script::CStruct;
+		*mp_collide_script_params += *p_collide_script_params;
+	}
+	
+	pParams->GetChecksum(CRCD(0x2d075f90,"BounceScript"),&m_bounce_script);
+	Script::CStruct * p_bounce_script_params = NULL;
+	pParams->GetStructure(CRCD(0x346b0c03,"BounceScriptParams"),&p_bounce_script_params);		
+	if (p_bounce_script_params)
+	{
+		mp_bounce_script_params = new Script::CStruct;
+		*mp_bounce_script_params += *p_bounce_script_params;
+	}
+	
+	
+//	((CMovingObject*)(GetObject()))->RunNodeScript(0x2d075f90/*BounceScript*/,0x346b0c03/*BounceScriptParams*/);
+	
+
+
+	m_pos = GetObject()->m_pos;
+	m_matrix = GetObject()->m_matrix;
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CBouncyComponent::Update()
+{
+
+	Mdl::Skate *	skate_mod =  Mdl::Skate::Instance();
+	uint32 numSkaters = skate_mod->GetNumSkaters( );
+
+	m_time = Tmr::FrameLength();
+
+	if ( !( m_bouncyobj_flags & BOUNCYOBJ_FLAG_PLAYER_COLLISION_OFF ) )
+	{
+		if ( m_state != BOUNCYOBJ_STATE_BOUNCING || ( ( !m_destroy_when_done ) && m_bounce_count ) )
+		{
+			uint32 i;
+			for ( i = 0; i < numSkaters; i++ )
+			{
+				CSkater *pSkater = skate_mod->GetSkater( i );
+				
+				// Very simple collision check ...
+				// Seems to work fine.
+				if ( Mth::DistanceSqr( m_pos, pSkater->m_pos ) < m_skater_collision_radius_squared )
+				{
+					// Trigger any collide-script specified in the object's node.
+					
+							   
+					#if 0
+					// TODO - handle this RunNodeScript - IF IT IS ACTUALLY EVER USED!!!!!
+					((CMovingObject*)GetObject())->RunNodeScript(0x58cdc0f0/*CollideScript*/,0x24031741/*CollideScriptParams*/);
+					#else
+					// This is also rather messy.  Seems like it would be better
+					// to have this handled by an exception
+					if (m_collide_script)
+					{
+						Script::CScript *p_script=Script::SpawnScript(m_collide_script,mp_collide_script_params);
+						#ifdef __NOPT_ASSERT__
+						p_script->SetCommentString("CBouncyComponent collide script");
+						#endif
+						Dbg_MsgAssert(p_script,("NULL p_script"));
+						p_script->mpObject=GetObject();;
+						p_script->Update();
+					}
+					#endif			   
+
+					// Trigger the bounce.
+					Mth::Vector vel;
+					vel = pSkater->GetVel( );
+					bounce_from_object_vel( vel, pSkater->m_pos );
+					continue;
+				}
+			}
+		}
+	}
+	
+    switch ( m_state )
+	{
+		case ( BOUNCYOBJ_STATE_INIT ):
+			m_state = BOUNCYOBJ_STATE_IDLE;
+			break;
+			
+		case ( BOUNCYOBJ_STATE_IDLE ):
+			break;
+
+		case ( BOUNCYOBJ_STATE_BOUNCING ):
+			
+			// TODO:  The following is needed only to update the position of the object
+			// This should be done by updating the postion of the model component
+			#if 0
+			// TODO - delete this code if it is not used
+			((CMovingObject*)GetObject())->MovingObj_Update( );    
+			#endif
+			
+			do_bounce( );
+			break;
+						
+		default:
+			Dbg_MsgAssert( 0,( "Unknown substate." ));
+			break;
+	}
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+#define DTR DEGREES_TO_RADIANS
+
+float get_closest_allowable_angle( float current, float allowableAngle )
+{
+	
+	float ang;
+	float halfAllowable = allowableAngle / 2.0f;
+	ang = DTR( -180.0f );
+	ang += halfAllowable;
+	while ( ang < DTR( 180.0f ) )
+	{
+		if ( current < ang )
+		{
+			return ( ang - ( halfAllowable ) );
+		}
+		ang += allowableAngle;
+	}
+	return ( ang - halfAllowable );
+}
+
+void CBouncyComponent::land_on_top_or_bottom( Mth::Vector &rot )
+{
+	
+	// x to nearest acceptable:
+	if ( ( rot[ X ] > DTR( 90.0f ) ) || ( rot[ X ] < DTR( -90.0f ) ) )
+		rot[ X ] = DTR( 180.0f );
+	else
+		rot[ X ] = 0.0f;
+	// z to nearest acceptable:
+	if ( ( rot[ Z ] > DTR( 90.0f ) ) || ( rot[ Z ] < DTR( -90.0f ) ) )
+		rot[ Z ] = DTR( 180.0f );
+	else
+		rot[ Z ] = 0.0f;
+}
+
+void CBouncyComponent::land_on_any_face( Mth::Vector &rot )
+{
+	
+	rot[ X ] = get_closest_allowable_angle( rot[ X ], DTR( 90.0f ) );
+	rot[ Y ] = get_closest_allowable_angle( rot[ Y ], DTR( 90.0f ) );
+	rot[ Z ] = get_closest_allowable_angle( rot[ Z ], DTR( 90.0f ) );
+}
+
+#define CONE_ANGLE	110.0f
+
+void CBouncyComponent::land_traffic_cone( Mth::Vector &rot )
+{
+	
+/*	if ( rot[ X ] < -DTR( CONE_ANGLE / 2.0f ) )
+		rot[ X ] = -DTR( CONE_ANGLE );
+	else if ( rot[ X ] > DTR( CONE_ANGLE / 2.0f ) )
+		rot[ X ] = DTR( CONE_ANGLE );
+	else*/
+		rot[ X ] = 0;
+
+	if ( rot[ Z ] < -DTR( CONE_ANGLE / 2.0f ) )
+		rot[ Z ] = -DTR( CONE_ANGLE );
+	else if ( rot[ Z ] > DTR( CONE_ANGLE / 2.0f ) )
+		rot[ Z ] = DTR( CONE_ANGLE );
+	else
+		rot[ Z ] = 0;
+
+//	rot[ Y ] = 0;
+}
+
+void CBouncyComponent::set_up_quats( void )
+{
+	
+	Mth::Vector rot;// = currentRot;
+	m_matrix.GetEulers( rot );
+	Dbg_MsgAssert( rot[ X ] <= DTR( 180.0f ),( "Trig functions range from 0 to 360, not -180 to 180." ));
+	Dbg_MsgAssert( rot[ Y ] <= DTR( 180.0f ),( "Trig functions range from 0 to 360, not -180 to 180." ));
+	Dbg_MsgAssert( rot[ Z ] <= DTR( 180.0f ),( "Trig functions range from 0 to 360, not -180 to 180." ));
+	
+	m_quatrot_time_elapsed = 0.0f;
+
+	// find out the amount we have to rotate:
+	switch ( m_rest_orientation_type )
+	{
+		case ( 1 ):  // Upside down, or rightside up:
+			land_on_top_or_bottom( rot );
+			break;
+
+		case ( 2 ):
+			land_on_any_face( rot );
+			break;
+
+		case ( 3 ):
+			land_traffic_cone( rot );
+			break;
+			
+		case ( 0 ):
+		default:
+			Dbg_MsgAssert( 0,( "Unknown rest orientation type: %d", m_rest_orientation_type ));
+			return;
+			break;
+	}
+	if ( !mp_bounce_slerp )
+	{
+		mp_bounce_slerp = new Mth::SlerpInterpolator;
+	}
+	
+	Mth::Matrix temp( rot[ X ], rot[ Y ], rot[ Z ] );
+	mp_bounce_slerp->setMatrices( &m_matrix, &temp );
+	m_quat_initialized = true;
+}
+
+#define QUAT_ROT_TIME 0.5f
+
+bool CBouncyComponent::rotate_to_quat( void )
+{
+	
+	float percent;
+	m_quatrot_time_elapsed += m_time;
+	if ( m_quatrot_time_elapsed >= QUAT_ROT_TIME )
+	{
+		percent = 1.0f;
+	}
+	else
+	{
+		percent = m_quatrot_time_elapsed / QUAT_ROT_TIME;
+	}
+
+	mp_bounce_slerp->getMatrix( &m_matrix, percent );
+	return ( percent == 1.0f );
+}
+
+#define MAX_SKATER_VEL 1000.0f  // not exact... just about what the max would be.
+
+void CBouncyComponent::do_bounce( void )
+{
+	m_old_pos = m_pos;
+	
+	int temp;
+	switch ( m_substate )
+	{
+		case ( 0 ):
+		
+			// generate a new constant rotation:
+			temp = Mth::Rnd( 8 );
+			m_rotation[ X ] = DEGREES_TO_RADIANS( ( float ) ( ( m_rot_per_axis >> 2 ) + ( Mth::Rnd( ( m_rot_per_axis >> 2 ) * 3 ) ) ) );
+			if ( temp & 1 )
+			{
+				m_rotation[ X ] *= -1.0f;
+			}
+			m_rotation[ Y ] = DEGREES_TO_RADIANS( ( float ) ( ( m_rot_per_axis >> 2 ) + ( Mth::Rnd( ( m_rot_per_axis >> 2 ) * 3 ) ) ) );
+			if ( temp & 2 )
+			{
+				m_rotation[ Y ] *= -1.0f;
+			}
+			m_rotation[ Z ] = DEGREES_TO_RADIANS( ( float ) ( ( m_rot_per_axis >> 2 ) + ( Mth::Rnd( ( m_rot_per_axis >> 2 ) * 3 ) ) ) );
+			if ( temp & 4 )
+			{
+				m_rotation[ Z ] *= -1.0f;
+			}
+			m_rotation[ W ] = 1.0f;
+			m_quat_initialized = false;
+			m_substate++;
+		// intentional fall through:
+		case ( 1 ):
+		{
+			Mth::Vector rot;
+			m_pos += m_vel * m_time;
+			if ( m_quat_initialized )
+			{
+				rotate_to_quat( );
+			}
+			else
+			{
+				rot = m_rotation;
+				rot *= m_time;
+				m_matrix.RotateLocal( rot );
+			}
+			
+			
+			// add the radius of the object so we don't stick through the ground:
+			Mth::Vector velNormalized = m_vel;
+			velNormalized.Normalize( );
+			Mth::Vector colPoint = m_pos + ( velNormalized * m_bounce_collision_radius );
+			m_vel[ Y ] -= m_gravity * m_time;
+			CFeeler feeler;
+			feeler.SetLine(m_old_pos, colPoint);
+			if ( feeler.GetCollision())
+			{
+				#if 0
+				// TODO - make it work, or remove if not used
+				((CMovingObject*)(GetObject()))->RunNodeScript(0x2d075f90/*BounceScript*/,0x346b0c03/*BounceScriptParams*/);
+				#else
+				// This is also rather messy.  Seems like it would be better
+				// to have this handled by an exception
+				if (m_bounce_script)
+				{
+					Script::CScript *p_script=Script::SpawnScript(m_bounce_script,mp_bounce_script_params);
+					Dbg_MsgAssert(p_script,("NULL p_script"));
+					#ifdef __NOPT_ASSERT__
+					p_script->SetCommentString("CBouncyComponent bounce script");
+					#endif
+					p_script->mpObject=GetObject();;
+					p_script->Update();
+				}
+				#endif
+
+				m_bounce_count++;
+				Mth::Vector v;
+				Mth::Vector r;
+				float dP;
+				v = m_pos - m_old_pos;
+				Mth::Vector n;
+				n = feeler.GetNormal();
+				dP = Mth::DotProduct( v, n );
+				r = ( dP * 2.0f ) * n;
+				r -= v;
+				float origVel = m_vel.Length( );
+				m_vel = -r;
+				m_vel.Normalize( origVel * m_bounce_mult );
+				m_vel.RotateY( DTR( ( float ) ( Mth::Rnd( 2 * m_random_dir ) - m_random_dir ) ) );
+				m_pos = feeler.GetPoint() - ( velNormalized * m_bounce_collision_radius );
+				if ( fabsf( m_vel[ Y ] ) < ( m_min_bounce_vel + ( 2.0f *( m_gravity * m_time ) ) ) )
+				{
+					if ( m_rest_orientation_type )
+					{
+						if ( !m_quat_initialized )
+						{
+							set_up_quats( );
+						}
+						m_substate++;
+						break;
+					}
+					
+					
+					#if 0
+					// TODO - make it work, or remove if not used
+					// This "HigerLevel" is probably redundant for bouncy obs
+					((CMovingObject*)(GetObject()))->HigherLevel( false );
+					#endif					
+					
+					SWITCH_STATE( BOUNCYOBJ_STATE_IDLE );
+					if ( m_destroy_when_done )
+					{
+						GetObject()->MarkAsDead( );
+					}
+					else
+					{
+						#if 0
+						// TODO - get exceptions working
+						GetExceptionComponentFromObject( GetObject() )->FlagException( 0x61682835 ); // DoneBouncing
+						#endif
+					}
+					return;
+				}
+				
+				if ( origVel > m_min_bounce_vel * 2.0f )
+				{
+					if ( m_bounce_sound )
+					{
+						// adjust volume depending on magnitude of current velocity:
+						#if 0
+						// TODO - Get Bouncy object sounds working with components
+						float vol;
+						vol = 100.0f * ( origVel / ( MAX_SKATER_VEL * m_bounciness ) );
+						float pitch = 80.0f + ( float ) Mth::Rnd( 40 );
+						((CMovingObject*)(GetObject()))->PlaySound_VolumeAndPan( m_bounce_sound, vol, pitch );
+						#endif
+					}
+
+					// bounce rot:
+					if ( m_bounce_rot )
+					{
+						rot[ X ] = DEGREES_TO_RADIANS( ( float )( Mth::Rnd( m_bounce_rot * 2 ) - m_bounce_rot ) );
+						rot[ Y ] = DEGREES_TO_RADIANS( ( float )( Mth::Rnd( m_bounce_rot * 2 ) - m_bounce_rot ) );
+						rot[ Z ] = DEGREES_TO_RADIANS( ( float )( Mth::Rnd( m_bounce_rot * 2 ) - m_bounce_rot ) );
+						m_matrix.RotateLocal( rot );
+					}
+					m_substate = 0;
+				}
+				else if ( m_rest_orientation_type )
+				{
+					if ( !m_quat_initialized )
+					{
+						// rotate to the resting orientation:
+						set_up_quats( );
+					}
+				}
+			}
+			break;
+		}
+
+		case ( 2 ):
+			if ( rotate_to_quat( ) )
+			{
+				#if 0
+				((CMovingObject*)(GetObject()))->HigherLevel( false );
+				#endif
+				
+				SWITCH_STATE( BOUNCYOBJ_STATE_IDLE );
+				if ( m_destroy_when_done )
+				{
+					GetObject()->MarkAsDead( );
+				}
+			}
+			break;
+			
+		default:
+			Dbg_Message( 0, "Woah!  Fire matt immediately." );
+			break;
+	}
+
+	#ifdef	__DEBUG_BOUNCY__	
+//	printf ("%d: %s: (%.2f,%.2f,%.2f)\n",__LINE__,__PRETTY_FUNCTION__,m_pos[X],m_pos[Y],m_pos[Z]); 
+	#endif
+	
+	GetObject()->m_pos = m_pos;
+	GetObject()->m_matrix = m_matrix;
+	GetObject()->SetDisplayMatrix(m_matrix);
+	
+	
+}
+
+void CBouncyComponent::bounce( const Mth::Vector &vel )
+{
+
+	#ifdef	__DEBUG_BOUNCY__	
+//	printf ("%d: %s: (%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)\n",__LINE__,__PRETTY_FUNCTION__,vel[X],vel[Y],vel[Z]); 
+	#endif
+	
+	m_vel = vel;
+	if ( m_hit_sound )
+	{
+		// adjust volume depending on magnitude of current velocity:
+		#if 0
+		// TODO - sound components for bouncy object
+		float vol;
+		vol = 100.0f * ( m_vel.Length( ) / MAX_SKATER_VEL );
+		float pitch = 80.0f + ( float ) Mth::Rnd( 40 );
+		((CMovingObject*)(GetObject()))->PlaySound_VolumeAndPan( m_hit_sound, vol, pitch );
+		#endif
+	}
+	if ( m_bounciness != 1.0f )
+	{
+		m_vel *= m_bounciness;
+	}
+	m_bounce_count = 0;
+	#if 0
+	// TODO - is HigherLevel even needed here?  or just cut and paste from some other object?
+	((CMovingObject*)(GetObject()))->HigherLevel( true );
+	#endif
+	SWITCH_STATE( BOUNCYOBJ_STATE_BOUNCING );
+	#if 0
+	// TODO - exceptions working as a component
+	GetExceptionComponentFromObject( GetObject() )->FlagException( 0x7dc30ba6 ); // Bounce
+	#endif
+}
+
+
+// Given an object velocity, then calculate the velocity to bounce by
+void CBouncyComponent::bounce_from_object_vel( const Mth::Vector &vel, const Mth::Vector &pos )
+{
+
+	#ifdef	__DEBUG_BOUNCY__	
+//	printf ("%d: %s: (%.2f,%.2f,%.2f),(%.2f,%.2f,%.2f)\n",__LINE__,__PRETTY_FUNCTION__,vel[X],vel[Y],vel[Z],pos[X],pos[Y],pos[Z]); 
+	#endif
+	
+	Mth::Vector vel2 = vel;
+	
+	float mag;
+	mag = vel2.Length( );
+	if ( mag < m_min_initial_vel )
+	{
+		mag = m_min_initial_vel;
+		vel2.Normalize( mag );
+	}
+	Mth::Vector dir = m_pos - pos;
+	dir.Normalize( mag );
+	vel2 += dir * ( ( ( float ) ( 50 + Mth::Rnd( 100 ) ) ) / 100.0f );
+	vel2.Normalize( mag );
+	vel2[ Y ] = ( m_up_mag * ( mag + MAX_SKATER_VEL ) ) / ( MAX_SKATER_VEL * 2.0f );
+	bounce(vel2);
+}
+
+void CBouncyComponent::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CBouncyComponent::GetDebugInfo"));
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+
+	p_info->AddChecksum("m_collide_script",m_collide_script);
+	p_info->AddStructure("mp_collide_script_params",mp_collide_script_params);
+
+	p_info->AddChecksum("m_bounce_script",m_bounce_script);
+	p_info->AddStructure("mp_bounce_script_params",mp_bounce_script_params);
+#endif				 
+}
+	
+}
diff --git a/Code/Gel/Components/carphysicscomponent.cpp b/Code/Gel/Components/carphysicscomponent.cpp
new file mode 100644
index 0000000..c41fb14
--- /dev/null
+++ b/Code/Gel/Components/carphysicscomponent.cpp
@@ -0,0 +1,176 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CarPhysicsComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/4/2002
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+	 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+//	#define vMAX_WHEEL_Y_ROT ( 70.0f )
+//	#define vMAX_CAR_X_ROT ( 5.0f )
+//	#define vMIN_CAR_X_ROT ( -5.0f )
+//	#define vSTEP_CAR_X_ROT ( 0.125f )
+//	#define vACCEL_TO_CAR_ROT_FACTOR ( 3.0f )
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CCarPhysicsComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CCarPhysicsComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCarPhysicsComponent::CCarPhysicsComponent() : CBaseComponent()
+{
+	SetType( CRC_CARPHYSICS );
+	
+	m_wheelRotationX = 0.0f;
+	m_wheelRotationY = 0.0f;
+	m_targetWheelRotationY = 0.0f;
+
+	m_shadowEnabled = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCarPhysicsComponent::~CCarPhysicsComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCarPhysicsComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// set up some car physics parameters
+	m_minCarRot = Script::GetFloat( CRCD(0x877db8da,"accelCarRot"), Script::ASSERT );
+	pParams->GetFloat( CRCD(0x877db8da,"accelCarRot"), &m_minCarRot );
+	
+	m_maxCarRot = Script::GetFloat( CRCD(0xfb3464df,"decelCarRot"), Script::ASSERT );
+	pParams->GetFloat( CRCD(0xfb3464df,"decelCarRot"), &m_maxCarRot );
+	
+	m_stepCarRot = Script::GetFloat( CRCD(0xfd8bc8d6,"speedCarRot"), Script::ASSERT );
+	pParams->GetFloat( CRCD(0xfd8bc8d6,"speedCarRot"), &m_stepCarRot );
+	
+	m_accelToCarRotFactor = Script::GetFloat( CRCD(0xb2c7447b,"accelCarRotFactor"), Script::ASSERT );
+	pParams->GetFloat( CRCD(0xb2c7447b,"accelCarRotFactor"), &m_accelToCarRotFactor );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCarPhysicsComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::EMemberFunctionResult CCarPhysicsComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case CRCC(0xa2331896,"EnableCarShadow"):
+			{
+				int enabled = 0;
+				pParams->GetInteger( NONAME, &enabled, Script::NO_ASSERT );
+				m_shadowEnabled = enabled;
+			}
+			break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+			break;
+
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCarPhysicsComponent::init_car_skeleton()
+{	
+	// Does some sanity checks
+	Obj::CSkeletonComponent* pSkeletonComponent = (Obj::CSkeletonComponent*)(GetObject()->GetComponent( CRC_SKELETON ));
+	Gfx::CSkeleton* pSkeleton = NULL;
+	if ( pSkeletonComponent )
+	{
+		pSkeleton = pSkeletonComponent->GetSkeleton();
+	}
+	
+	Obj::CModelComponent* pModelComponent = (Obj::CModelComponent*)(GetObject()->GetComponent( CRC_MODEL ));
+	Nx::CModel* pModel = NULL;
+	if ( pModelComponent )
+	{
+		pModel = pModelComponent->GetModel();
+	}
+	
+	Dbg_MsgAssert( pSkeleton, ("Car has no skeleton") );
+	Dbg_MsgAssert( pModel, ( "Model hasn't been set up yet" ) );
+	
+	Dbg_MsgAssert( pModel->GetHierarchy(), ("veh flagged as car has no hierarchy from node %s", Script::FindChecksumName(GetObject()->GetID())));
+	Nx::CHierarchyObject* pHierarchyObjects = pModel->GetHierarchy();
+	Dbg_MsgAssert(( pHierarchyObjects + 0 ),("Missing matrix in car"));
+	Dbg_MsgAssert(( pHierarchyObjects + 2 ),("Missing matrix in car"));
+	Dbg_MsgAssert(( pHierarchyObjects + 4 ),("Missing matrix in car"));
+
+	// TODO:  This 5-bone hierarchy shouldn't be so hardcoded...
+	// We should somehow make this generic so that we 
+	// can use different skeletons with the car physics.
+
+	Mth::Matrix frontTire = ( pHierarchyObjects + 2 )->GetSetupMatrix();
+	frontTire *= ( pHierarchyObjects + 0 )->GetSetupMatrix();
+	Mth::Matrix backTire = ( pHierarchyObjects + 4 )->GetSetupMatrix();
+	backTire *= ( pHierarchyObjects + 0 )->GetSetupMatrix();
+
+	Mth::Vector dist = frontTire[Mth::POS] - backTire[Mth::POS];
+	m_distanceBetweenTires = fabs( dist.Length() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/carphysicscomponent.h b/Code/Gel/Components/carphysicscomponent.h
new file mode 100644
index 0000000..8e387ce
--- /dev/null
+++ b/Code/Gel/Components/carphysicscomponent.h
@@ -0,0 +1,69 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CarPhysicsComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/4/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_CARPHYSICSCOMPONENT_H__
+#define __COMPONENTS_CARPHYSICSCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_CARPHYSICS CRCD(0xc695136d,"CarPhysics")
+#define		GetCarPhysicsComponent() ((Obj::CCarPhysicsComponent*)GetComponent(CRC_CARPHYSICS))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CCarPhysicsComponent : public CBaseComponent
+{
+public:
+    CCarPhysicsComponent();
+    virtual ~CCarPhysicsComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+	static CBaseComponent*			s_create();
+
+public:
+	void							init_car_skeleton();
+	
+public:
+	// public only during the transition
+	
+	// for wheel rotation
+	float							m_wheelRotationX;
+	float							m_wheelRotationY;
+	float							m_targetWheelRotationY;
+	float							m_carRotationX;
+	float							m_targetCarRotationX;
+	float							m_distanceBetweenTires;
+	float							m_old_vel_z;
+
+	// for car rocking back and forth
+	float							m_minCarRot;
+	float							m_maxCarRot;
+	float							m_stepCarRot;
+	float							m_accelToCarRotFactor;
+
+	bool							m_shadowEnabled;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/collisioncomponent.cpp b/Code/Gel/Components/collisioncomponent.cpp
new file mode 100644
index 0000000..97814de
--- /dev/null
+++ b/Code/Gel/Components/collisioncomponent.cpp
@@ -0,0 +1,228 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CollisionComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/31/2002
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+							
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CCollisionComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CCollisionComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCollisionComponent::CCollisionComponent() : CBaseComponent()
+{
+	SetType( CRC_COLLISION );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCollisionComponent::~CCollisionComponent()
+{
+	if ( mp_collision )
+	{
+		Nx::CMovableCollMan::sRemoveCollision( mp_collision );
+		// We don't want to do this after we start the PIP loading
+		delete mp_collision;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCollisionComponent::InitFromStructure( Script::CStruct* pParams )
+{	
+	Dbg_MsgAssert( pParams, ( "No node data?" ) );
+	Dbg_MsgAssert( !mp_collision, ( "Set up collision twice?" ) );
+
+	uint32 col_type_checksum = Nx::vCOLL_TYPE_NONE;
+	pParams->GetChecksum( CRCD(0x2d7e583b,"CollisionMode"), &col_type_checksum );
+	
+	Nx::CollType col_type = Nx::CCollObj::sConvertTypeChecksum( col_type_checksum );
+	if ( col_type != Nx::vCOLL_TYPE_NONE )
+	{
+		uint32 class_checksum = 0;
+		pParams->GetChecksum("class", &class_checksum);
+		if ( class_checksum == CRCD(0xb7b3bd86,"LevelObject") )
+		{
+			uint32 name;
+			pParams->GetChecksum(CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT);
+			Nx::CSector *p_sector = Nx::CEngine::sGetSector(name);
+			Dbg_MsgAssert( p_sector, ( "WARNING: sGetSector(0x%x) returned NULL (%s)\n", name, Script::FindChecksumName(name) ) );
+			Nx::CCollObjTriData *p_coll_tri_data = p_sector->GetCollSector()->GetGeometry();
+			InitCollision(col_type, p_coll_tri_data);   
+		}
+		else
+		{
+			InitCollision(col_type);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCollisionComponent::InitCollision( Nx::CollType type, Nx::CCollObjTriData *p_coll_tri_data )
+{		
+	Dbg_Assert( !mp_collision );
+
+	if ( p_coll_tri_data )
+	{
+		mp_collision = Nx::CCollObj::sCreateMovableCollision(type, p_coll_tri_data, 1, GetObject());
+	} 
+	else
+	{
+		Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( GetObject() );
+		Dbg_MsgAssert( pModelComponent, ( "Initing collision on something with no model component" ) );
+
+		Nx::CModel* pModel = pModelComponent->GetModel();
+		Dbg_MsgAssert( pModel, ( "Initing collision on something with no model" ) );
+
+		Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+		Nx::CMesh* pMesh = (Nx::CMesh*)ass_man->GetAsset(pModel->GetFileName(), false);
+		if (pMesh)
+		{
+			mp_collision = Nx::CCollObj::sCreateMovableCollision(type, pMesh->GetCollisionTriDataArray(), pMesh->GetCollisionTriDataArraySize(), GetObject());
+		}
+		else
+		{
+			mp_collision = Nx::CCollObj::sCreateMovableCollision(type, NULL, 0, GetObject());
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Nx::CCollObj* CCollisionComponent::GetCollision() const
+{
+	return mp_collision;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCollisionComponent::Teleport()
+{
+	// GJ:  the collision component's Update()
+	// function doesn't really require finalization
+	// right now, but having this assert in here
+	// might help catch future errors
+	Dbg_MsgAssert( GetObject()->IsFinalized(), ( "Has not been finalized!  Tell Gary!" ) );
+
+	Update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCollisionComponent::Update()
+{
+//	GJ:  On THPS4, we did the following test first before
+//	updating the collision;  I think this will be handled
+//	by the suspend component, so we shouldn't have to do
+//  this here...
+//	if ( (m_object_flags & vINVISIBLE) && !initialize )
+//	{
+//		return;
+//	}
+
+	// get the collision object
+	Nx::CCollObj* p_coll = GetCollision();
+	
+	// Update collision
+	// Might be problem with things being LODed off, but still needing collision?
+	if ( p_coll )
+	{
+		// GJ TODO:  Somehow remove the collision object's dependency on the model!
+
+		Nx::CModel*	p_model = NULL;
+		Nx::CHierarchyObject* p_hierarchy = NULL;
+		Obj::CModelComponent* p_model_component = GetModelComponentFromObject( GetObject() );
+		if ( p_model_component )
+		{
+			p_model = p_model_component->GetModel();
+			Dbg_MsgAssert( p_model, ( "No model?!?" ) );
+			p_hierarchy = p_model->GetHierarchy();
+		}
+
+		if (p_hierarchy)
+		{
+			Mth::Vector translation(p_hierarchy->GetSetupMatrix()[W]);
+			translation[W] = 0.0f;		// turn into vector
+			p_hierarchy->GetSetupMatrix().Transform(translation);		// Re-orients the translation
+
+			p_coll->SetWorldPosition( GetObject()->GetPos() + translation );
+			p_coll->SetOrientation( p_hierarchy->GetSetupMatrix() * GetObject()->GetDisplayMatrix() );
+		}
+		else
+		{
+			p_coll->SetWorldPosition( GetObject()->GetPos() );
+			p_coll->SetOrientation( GetObject()->GetDisplayMatrix() );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::EMemberFunctionResult CCollisionComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+//    Dbg_Assert( 0 );
+
+    return CBaseComponent::MF_NOT_EXECUTED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/collisioncomponent.h b/Code/Gel/Components/collisioncomponent.h
new file mode 100644
index 0000000..3ec41f8
--- /dev/null
+++ b/Code/Gel/Components/collisioncomponent.h
@@ -0,0 +1,63 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       CollisionComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/24/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_COLLISIONCOMPONENT_H__
+#define __COMPONENTS_COLLISIONCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_COLLISION CRCD(0xa432dc41,"Collision")
+#define		GetCollisionComponent() ((Obj::CCollisionComponent*)GetComponent(CRC_COLLISION))
+#define		GetCollisionComponentFromObject(pObj) ((Obj::CCollisionComponent*)(pObj)->GetComponent(CRC_COLLISION))
+
+namespace Nx
+{
+	class CCollObj;
+	class CCollObjTriData;
+}
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CCollisionComponent : public CBaseComponent
+{
+public:
+    CCollisionComponent();
+    virtual ~CCollisionComponent();
+
+public:
+    virtual void            		Update();
+	virtual void            		Teleport();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+    static CBaseComponent*			s_create();
+	
+public:
+	Nx::CCollObj*					GetCollision() const;
+
+protected:
+	virtual void					InitCollision( Nx::CollType type, Nx::CCollObjTriData *p_coll_tri_data = NULL );
+	Nx::CCollObj*					mp_collision;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/emptycomponent.cpp b/Code/Gel/Components/emptycomponent.cpp
new file mode 100644
index 0000000..d425b7e
--- /dev/null
+++ b/Code/Gel/Components/emptycomponent.cpp
@@ -0,0 +1,218 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       EmptyComponent.cpp
+//* OWNER:          ???
+//* CREATION DATE:  ???
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CEmptyComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CEmptyComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CEmptyComponent::CEmptyComponent() : CBaseComponent()
+{
+	SetType( CRC_EMPTY );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CEmptyComponent::~CEmptyComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CEmptyComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+	It's commented out here.  You will also need to comment in the 
+	definition in emptycomponent.h
+void CEmptyComponent::Finalize()
+{
+	// Virtual function, can be overridden to provided finialization to 
+	// a component after all components have been added to an object
+	// Usually this consists of getting pointers to other components
+	// Note:  It is GUARENTEED (at time of writing) that
+	// Finalize() will be called AFTER all components are added
+	// and BEFORE the CCompositeObject::Update function is called
+	
+	// Example:
+	// mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+										
+										
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CEmptyComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	InitFromStructure(pParams);
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CEmptyComponent::Update()
+{
+	// **  You would put in here the stuff that you would want to get run every frame
+	// **  for example, a physics type component might update the position and orientation
+	// **  and update the internal physics state 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CEmptyComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEmptyComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CEmptyComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger(CRCD(0x7cf2a233,"m_never_suspend"),m_never_suspend);
+	p_info->AddFloat(CRCD(0x519ab8e0,"m_suspend_distance"),m_suspend_distance);
+	*/
+	
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/emptycomponent.h b/Code/Gel/Components/emptycomponent.h
new file mode 100644
index 0000000..a414028
--- /dev/null
+++ b/Code/Gel/Components/emptycomponent.h
@@ -0,0 +1,53 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       EmptyComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_EMPTYCOMPONENT_H__
+#define __COMPONENTS_EMPTYCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_EMPTY CRCD(0x9738c23b,"Empty")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetEmptyComponent() ((Obj::CEmptyComponent*)GetComponent(CRC_EMPTY))
+#define		GetEmptyComponentFromObject(pObj) ((Obj::CEmptyComponent*)(pObj)->GetComponent(CRC_EMPTY))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CEmptyComponent : public CBaseComponent
+{
+public:
+    CEmptyComponent();
+    virtual ~CEmptyComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+//	virtual	void 					Finalize();
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/lockobjcomponent.cpp b/Code/Gel/Components/lockobjcomponent.cpp
new file mode 100644
index 0000000..e766ae6
--- /dev/null
+++ b/Code/Gel/Components/lockobjcomponent.cpp
@@ -0,0 +1,489 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       LockObjComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/31/2002
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+												   
+// for kludgy skater stuff
+#include 
+#include 
+#include 
+#include 
+
+
+// For the manager ... messy
+#include 
+
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CLockObjComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CLockObjComponent );	
+}
+
+
+// And this one is the optional "register" function that provides on-time initilization
+// usually of a component manager
+void	CLockObjComponent::s_register()
+{
+	// Create and initilize the manager
+	LockOb::Manager::Create();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CLockObjComponent::CLockObjComponent() : CBaseComponent()
+{
+	SetType( CRC_LOCKOBJ );
+
+	m_lock_enabled = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CLockObjComponent::~CLockObjComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CLockObjComponent::InitFromStructure( Script::CStruct* pParams )
+{
+
+	m_locked_to_object_name=0;
+	m_locked_to_object_id=Obj::CBaseManager::vNO_OBJECT_ID;
+	m_locked_to_object_bone_name=0;
+	m_lock_offset.Set();
+	
+	if (pParams->ContainsFlag("Off"))
+	{
+		EnableLock( false );
+		return;
+	}
+
+	EnableLock( true );
+
+	#if 0
+	// SHOULD ALSO CLEAR OUT SOME OF THE OTHER
+	// FLAGS THAT CANNOT CO-EXIST!
+	GetObject()->m_movingobj_status &= ~MOVINGOBJ_STATUS_FOLLOWING_LEADER;
+	GetObject()->m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
+	#endif
+
+	pParams->GetChecksum("ObjectName",&m_locked_to_object_name);
+	pParams->GetVector(NONAME,&m_lock_offset);
+	pParams->GetChecksum("id",&m_locked_to_object_id);
+	pParams->GetChecksum("bone",&m_locked_to_object_bone_name);
+
+	printf( "locking to object %s\n", Script::FindChecksumName(m_locked_to_object_id) );
+
+	m_no_rotation=pParams->ContainsFlag("NoRotation");
+	
+	m_lag=false;
+	
+	m_lag_factor_range_a=1.0f;
+	m_lag_factor_range_b=1.0f;
+	if (pParams->GetPair("LagRange",&m_lag_factor_range_a,&m_lag_factor_range_b))
+	{
+		m_lag=true;
+	}	
+	
+	m_lag_freq_a=0.0f;
+	m_lag_freq_b=0.0f;
+	if (pParams->GetPair("LagWobble",&m_lag_freq_a,&m_lag_freq_b))
+	{
+		m_lag=true;
+	}	
+
+	// The random lag phase is to ensure that two objects using the same script won't
+	// wobble in phase.
+	m_lag_phase=Mth::Rnd(1024);	
+	uint32 t=Tmr::ElapsedTime(0)+m_lag_phase;
+	t&=1023;
+	float x=sinf(m_lag_freq_a*t*2.0f*3.1415926f/1024.0f) *
+			sinf(m_lag_freq_b*t*2.0f*3.1415926f/1024.0f);
+	
+	m_lag_factor=m_lag_factor_range_a+(x+1)*(m_lag_factor_range_b-m_lag_factor_range_a)/2.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CLockObjComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::EMemberFunctionResult CLockObjComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+
+		
+	switch ( Checksum )
+	{
+        // @script | Obj_LockToObject | Makes the object be locked a fixed offset from some other
+		// object, and use the same orientation.
+        // @uparmopt tev_TramA01 | Name of the object to be locked to
+        // @uparmopt (0,0,0) | Offset from the host object.
+		// @flag Off | Switch off lock so that object can be moved independently again.
+		// @flag NoRotation | Makes the objects rotation be left unaffected.
+		// @parmopt pair | LagRange | (1,1) | The range of the lag value, which determines how quickly
+		// the object will catch up to the object being followed. The lower the value the further
+		// the object will lag behind. 1 means no lag at all.
+		// Example which works quite well: (0.05,0.07)
+		// @parmopt pair | LagWobble | (0,0) | The frequencies used to wobble the lag value between
+		// the two ranges. Two frequencies are specified so that the lag will wobble in an
+		// irregular manner. Example which works quite well: (0.779,0.65)
+		case 0x785e57c7: // Obj_LockToObject
+		{
+			#if 0
+			#ifdef	__NOPT_ASSERT__
+			uint32 foo=0;
+			if (!pParams->GetChecksum("ObjectName",&foo))
+			{
+				Dbg_MsgAssert(0,("\n%s\nMissing ObjectName parameter in Obj_LockToObject",pScript->GetScriptInfo()));
+			}
+			#endif				
+			#endif				
+			InitFromStructure( pParams );							
+			break;
+		}	
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+void CLockObjComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CLockObjComponent::GetDebugInfo"));
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+	
+	p_info->AddChecksum("m_locked_to_object_name",m_locked_to_object_name);
+	p_info->AddChecksum("m_locked_to_object_id",m_locked_to_object_id);
+	p_info->AddChecksum("m_locked_to_object_bone_name",m_locked_to_object_bone_name);
+	p_info->AddVector("m_lock_offset",m_lock_offset.GetX(),m_lock_offset.GetY(),m_lock_offset.GetZ());
+	p_info->AddFloat("m_lag_factor",m_lag_factor);
+	p_info->AddFloat("m_lag_factor_range_a",m_lag_factor_range_a);
+	p_info->AddFloat("m_lag_factor_range_b",m_lag_factor_range_b);
+	p_info->AddFloat("m_lag_freq_a",m_lag_freq_a);
+	p_info->AddFloat("m_lag_freq_b",m_lag_freq_b);
+	p_info->AddFloat("m_lag_phase",m_lag_phase);
+	p_info->AddChecksum("m_lag",m_lag ? 0x203b372/*True*/:0xd43297cf/*False*/);
+	p_info->AddChecksum("m_no_rotation",m_no_rotation ? 0x203b372/*True*/:0xd43297cf/*False*/);
+	p_info->AddChecksum("m_lock_enabled",m_lock_enabled ? 0x203b372/*True*/:0xd43297cf/*False*/);
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCompositeObject* CLockObjComponent::get_locked_to_object()
+{
+	CCompositeObject* p_obj=NULL;
+	if (m_locked_to_object_id!=CBaseManager::vNO_OBJECT_ID)
+	{
+		p_obj=(CCompositeObject*)Obj::ResolveToObject(m_locked_to_object_id);
+	}
+	else if (m_locked_to_object_name==0x5b8ab877) // Skater
+	{
+		// GJ:  phase this usage out...  we shouldn't have anything
+		// skater-specific in here!
+		Dbg_MsgAssert(Mdl::Skate::Instance()->GetNumSkaters()==1,("Called get_locked_to_object with skater as parent when there is more than one skater"));
+		p_obj=(CCompositeObject*)Mdl::Skate::Instance()->GetLocalSkater();
+	}
+	else
+	{
+		//p_obj=CMovingObject::m_hash_table.GetItem(m_locked_to_object_name);
+		p_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( m_locked_to_object_name );
+	
+	}
+	
+	return p_obj;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLockObjComponent::LockToObject( void )
+{
+	Mth::Matrix old_matrix=GetObject()->m_matrix;
+	
+	CCompositeObject* p_obj=get_locked_to_object();
+	if (!p_obj)
+	{
+		// Can't find the object, oh well.
+		return;
+	}	
+
+	Mth::Vector target_pos = GetObject()->m_pos;
+	
+	if ( m_locked_to_object_bone_name )
+	{
+		Gfx::CSkeleton* pSkeleton = NULL;
+		if ( p_obj->GetComponent(CRC_SKELETON) )
+		{
+			pSkeleton = GetSkeletonComponentFromObject(p_obj)->GetSkeleton();
+		}
+		Dbg_MsgAssert( pSkeleton, ( "Locking object to bone on non-skeletal object" ) );
+		if ( pSkeleton && pSkeleton->GetBoneMatrix( m_locked_to_object_bone_name, &GetObject()->m_matrix ) )
+		{
+			Mth::Vector boneOffset = GetObject()->m_matrix[Mth::POS];
+			GetObject()->m_matrix[Mth::POS] = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
+
+		    GetObject()->m_matrix = GetObject()->GetMatrix() * p_obj->GetMatrix();
+
+			target_pos = p_obj->GetDisplayMatrix().Transform( boneOffset );
+			target_pos += p_obj->m_pos+m_lock_offset*GetObject()->GetMatrix();
+		}
+	}
+	else
+	{
+		// Using the display matrix otherwise the object will jitter if following the skater
+		// up a half pipe.
+		
+		// K: If the parent object is doing some extra rotations (by using the RotateDisplay command),
+		// the calculation of target pos is slightly different. 
+		// However, as a speed optimization only do this for the skater because currently that 
+		// is the only object that does extra rotations.
+		// Fixes bug where the pizza box special item was not being displayed correctly when used in
+		// a create-a-trick with rotations.
+		
+		bool calculate_extra_rotation = false;
+
+		CCompositeObject* p_composite_object = (CCompositeObject*)p_obj;
+		Obj::CModelComponent* p_model_component = (Obj::CModelComponent*)p_composite_object->GetComponent( CRC_MODEL );
+		Dbg_Assert( p_model_component );
+		if ( p_model_component->HasRefObject() )
+		{
+			// GJ THPS5:  We're now starting to have non-skater objects needing the extra rotate 
+			// display (i.e. the CAT preview skater)...  in theory, we could still do an optimization
+			// where each lockobjcomponent is flagged to support the rotatedisplay, but i decided
+			// to just kludge it for now by checking for a refObject.
+			calculate_extra_rotation = true;
+		}
+		else if ( p_obj->GetID() < Mdl::Skate::vMAX_SKATERS )
+		{
+			// If it's the skater ...
+			calculate_extra_rotation = true;
+		}
+
+		if ( calculate_extra_rotation )
+		{
+			Mth::Matrix display_matrix;
+			p_model_component->GetDisplayMatrixWithExtraRotation( display_matrix );
+			
+			// Note that p_obj->GetPos() is not added because the offset is contained in the matrix.
+			target_pos=m_lock_offset*display_matrix;
+			
+			// but now zero out the translation part of the matrix otherwise the object will be rendered wrong.
+			Mth::Vector zero(0.0f,0.0f,0.0f,1.0f);
+			display_matrix.SetPos(zero);
+			GetObject()->m_matrix=display_matrix;
+		}
+		else
+		{
+			GetObject()->m_matrix=((CCompositeObject*)p_obj)->GetDisplayMatrix();
+			target_pos=p_obj->GetPos()+m_lock_offset*GetObject()->m_matrix;
+		}
+	}
+
+	if (m_lag)
+	{
+		GetObject()->m_pos=GetObject()->GetPos()+(target_pos-GetObject()->GetPos())*m_lag_factor;
+		
+		uint32 t=Tmr::ElapsedTime(0)+m_lag_phase;
+		t&=1023;
+		float x=sinf(m_lag_freq_a*t*2.0f*Mth::PI/1024.0f) *
+				sinf(m_lag_freq_b*t*2.0f*Mth::PI/1024.0f);
+		
+		m_lag_factor=m_lag_factor_range_a+(x+1)*(m_lag_factor_range_b-m_lag_factor_range_a)/2.0f;
+	}
+	else
+	{
+		GetObject()->m_pos=target_pos;
+	}
+	
+	// take the object's velocity as well
+	GetObject()->SetVel(p_obj->GetVel());
+	
+	// If the objects rotation is required to be preserved, recover the old value.
+	// Alan's ferris wheel cars use this.
+	if (m_no_rotation)
+	{
+		GetObject()->m_matrix[Mth::UP]=old_matrix[Mth::UP];
+		GetObject()->m_matrix[Mth::AT]=old_matrix[Mth::AT];
+		GetObject()->m_matrix[Mth::RIGHT]=old_matrix[Mth::RIGHT];
+		//printf("m_pos=(%f,%f,%f)\n",m_pos[X],m_pos[Y],m_pos[Z]);
+	}	
+	
+	// GJ:  This function is called from a task that
+	// happens after all the matrices have already
+	// been sent to the rendering code for this frame...
+	// so we'll need to re-apply any new m_pos/m_matrix
+	// changes to the rendered model
+	// (suggestion for THPS5...  have a render task
+	// that sends all the matrices to the model after
+	// the object update task has finished...)
+	Nx::CModel* pModel = NULL;
+	if ( GetObject()->GetComponent( CRC_MODEL ) )
+	{
+		pModel = ((Obj::CModelComponent*)GetObject()->GetComponent(CRC_MODEL))->GetModel();
+	}
+	
+	if ( pModel )
+	{
+		Gfx::CSkeleton* pSkeleton = NULL;
+		if ( GetObject()->GetComponent( CRC_SKELETON ) )
+		{
+			pSkeleton = ((Obj::CSkeletonComponent*)GetObject()->GetComponent(CRC_SKELETON))->GetSkeleton();
+		}
+
+		Mth::Matrix rootMatrix;
+		rootMatrix = GetObject()->GetDisplayMatrix();
+		rootMatrix[Mth::POS] = GetObject()->GetPos();
+		rootMatrix[Mth::POS][W] = 1.0f;
+		bool should_animate = false;
+		pModel->Render( &rootMatrix, !should_animate, pSkeleton );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+
+	
+}
+
+
+
+// The following is the LockOb manager, which iterates over the lockob components 
+// to lock them to whatever they are locked to
+
+namespace LockOb
+{
+// The purpose of this manager is just to update all the locked objects, by calling
+// CMovingObject::LockToObject on all objects that have had Obj_LockToObject run on them.
+// It is a manager so that it can be given a task with a priority such that it is called
+// after all the objects are updated.
+// The reason the lock logic cannot be done in the object update is because sometimes the parent's 
+// update might happen after the child's, causing a frame lag. This was making the gorilla appear
+// behind the tram seat in the zoo when the tram was being skitched. (TT2324)
+
+DefineSingletonClass( Manager, "Locked object Manager" )
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::Manager()
+{
+	mp_task = new Tsk::Task< Manager > ( s_code, *this,
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_LOCKED_OBJECT_MANAGER_LOGIC );
+
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	mlp_manager->AddLogicTask( *mp_task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager()
+{
+	delete mp_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::s_code( const Tsk::Task< Manager >& task )
+{
+	
+//	if (Mdl::FrontEnd::Instance()->GamePaused())
+//	{
+//		// Must not update if game paused, because the game could be running a replay,
+//		// which would mean the objects' models will have been deleted, causing the 
+//		// LockToObject function to assert.
+//		return;
+//	}
+
+	// new fast way, just go directly to the components, if any
+	Obj::CLockObjComponent *p_lock_obj_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_LOCKOBJ ));
+	while( p_lock_obj_component )
+	{
+		if ( p_lock_obj_component->IsLockEnabled() )
+		{
+			p_lock_obj_component->LockToObject();
+		}	
+		p_lock_obj_component = static_cast( p_lock_obj_component->GetNextSameType());
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
+
diff --git a/Code/Gel/Components/lockobjcomponent.h b/Code/Gel/Components/lockobjcomponent.h
new file mode 100644
index 0000000..75119f4
--- /dev/null
+++ b/Code/Gel/Components/lockobjcomponent.h
@@ -0,0 +1,103 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       LockObjComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/4/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_LOCKOBJCOMPONENT_H__
+#define __COMPONENTS_LOCKOBJCOMPONENT_H__
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_LOCKOBJ CRCD(0x5cb5cb7e,"LockObj")
+#define		GetLockObjComponent() ((Obj::CLockObjComponent*)GetComponent(CRC_LOCKOBJ))
+#define		GetLockObjComponentFromObject(pObj) ((Obj::CLockObjComponent*)(pObj)->GetComponent(CRC_LOCKOBJ))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CLockObjComponent : public CBaseComponent
+{
+public:
+    CLockObjComponent();
+    virtual ~CLockObjComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+
+	virtual void					GetDebugInfo(Script::CStruct *p_info);
+	
+	static CBaseComponent*			s_create();
+	static void						s_register();
+
+protected:
+	CCompositeObject*				get_locked_to_object();
+
+public:
+	void							LockToObject();
+	bool							IsLockEnabled() const {return m_lock_enabled;}
+	void							EnableLock( bool enabled ) {m_lock_enabled = enabled;}
+	
+protected:
+	uint32							m_locked_to_object_name;
+	uint32							m_locked_to_object_id;
+	uint32							m_locked_to_object_bone_name;
+	Mth::Vector						m_lock_offset;
+	float							m_lag_factor;
+	float							m_lag_factor_range_a;
+	float							m_lag_factor_range_b;
+	float 							m_lag_freq_a;
+	float 							m_lag_freq_b;
+	uint32							m_lag_phase;
+	bool							m_lag;
+	bool							m_no_rotation;
+
+protected:
+	bool							m_lock_enabled;
+};
+
+}
+
+namespace LockOb
+{
+// The purpose of this manager is just to update all the locked objects, by calling
+// CMovingObject::LockToObject on all objects that have had Obj_LockToObject run on them.
+// It is a manager so that it can be given a task with a priority such that it is called
+// after all the objects are updated.
+// The reason the lock logic cannot be done in the object update is because sometimes the parent's 
+// update might happen after the child's, causing a frame lag. This was making the gorilla appear
+// behind the tram seat in the zoo when the tram was being skitched. (TT2324)
+
+class Manager  : public Spt::Class
+{
+	Tsk::Task< Manager > *mp_task;
+	static	Tsk::Task< Manager >::Code 	s_code;
+
+public:
+	Manager();
+	~Manager();
+	
+private:	
+	DeclareSingletonClass( Manager );
+};
+};
+
+
+
+#endif
diff --git a/Code/Gel/Components/modelcomponent.cpp b/Code/Gel/Components/modelcomponent.cpp
new file mode 100644
index 0000000..b33f470
--- /dev/null
+++ b/Code/Gel/Components/modelcomponent.cpp
@@ -0,0 +1,1840 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ModelComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#include 
+
+#include 
+									
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 								
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+					 
+namespace Obj
+{
+
+#define vMAX_PATH (512)
+    
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::init_model_from_level_object( uint32 checksumName )
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);
+	Dbg_MsgAssert( p_sector, ( "WARNING: sGetSector(0x%x) returned NULL (%s)\n", checksumName, Script::FindChecksumName(checksumName) ) );
+	if ( p_sector )
+	{
+		// need to clone the source, not the instance?
+		Nx::CGeom* pGeom = p_sector->GetGeom();
+		if( pGeom )
+		{
+			Nx::CGeom* pClonedGeom = pGeom->Clone( true );
+			pClonedGeom->SetActive(true);
+			mp_model->AddGeom( pClonedGeom, 0 );
+			m_isLevelObject = true;
+		}
+		// Also get the collision data pointer
+//				Dbg_Assert(p_sector->GetCollSector());
+//				Nx::CCollObjTriData* p_coll_tri_data = p_sector->GetCollSector()->GetGeometry();
+//				Dbg_Assert(p_coll_tri_data);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent *	CModelComponent::s_create()
+{
+	return static_cast(new CModelComponent);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CModelComponent::CModelComponent() : CBaseComponent()
+{
+	SetType(CRCD(0x286a8d26,"Model"));
+    mp_model = Nx::CEngine::sInitModel();
+	
+	// since it's uninitialized
+	m_display_rotation_offset.Set( 0.0f, 0.0f, 0.0f );		
+	
+	mpDisplayRotationInfo[0].Clear();
+	mpDisplayRotationInfo[1].Clear();
+	mpDisplayRotationInfo[2].Clear();
+
+	m_numLODs = 0;
+	m_isLevelObject = false;
+	
+	mDisplayOffset.Set();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CModelComponent::~CModelComponent()
+{
+    Dbg_MsgAssert( mp_model, ( "No model" ) );
+    Nx::CEngine::sUninitModel( mp_model );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CModelComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// needs to come before the geoms get added,
+	// because otherwise we've cause some fragmentaton
+	// when we switch models...
+	if ( pParams->ContainsFlag( CRCD(0x10079f2d,"UseModelLights") ) )
+	{
+		if ( !mp_model->GetModelLights() )
+		{
+			mp_model->CreateModelLights();
+			Nx::CModelLights *p_lights = mp_model->GetModelLights();
+			p_lights->SetPositionPointer(&GetObject()->m_pos);
+		}
+	}
+	else
+	{
+		if ( mp_model->GetModelLights() )
+		{
+			mp_model->DestroyModelLights();
+		}
+	}
+
+	// this function assumes that the skeletoncomponent
+	// will be correctly added BEFORE the modelcomponent
+	
+	// not all models are initialized using InitFromStructure,
+	// so we should find someplace better to put the following 
+	// call to SetSkeleton()
+	CSkeletonComponent *p_skeleton_component = GetSkeletonComponentFromObject(GetObject());
+	if (p_skeleton_component)
+	{
+		GetModel()->SetSkeleton( p_skeleton_component->GetSkeleton() );
+	}
+
+	int doShadowVolume = 0;
+	pParams->GetInteger( CRCD(0x2a1f92c0,"shadowVolume"), &doShadowVolume, Script::NO_ASSERT );
+	mp_model->EnableShadowVolume( doShadowVolume );
+	
+	InitModel( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CModelComponent::InitModel( Script::CStruct* pParams )
+{
+	// should destroy the existing model
+	mp_model->ClearGeoms();
+
+	// update its scale, if any
+	// (need to do this before init_model(), 
+	// which can potentially override the scale)
+	Mth::Vector theScale(1.0f,1.0f,1.0f);
+	if ( Gfx::GetScaleFromParams( &theScale, pParams ) )
+	{
+		mp_model->SetScale( theScale );
+	}
+	
+	const char* pModelName;
+	uint32 model_name_checksum=0;
+	uint32 assetName;
+	uint32 refObjectName;
+	uint32 cloneID;
+
+	// check first if it is a LevelObject
+	// in which case it will have a "name" which is the name of the sector
+	uint32 ClassChecksum = 0;
+	pParams->GetChecksum( CRCD(0x12b4e660,"Class"), &ClassChecksum );
+	if ( ClassChecksum == CRCD(0xb7b3bd86,"LevelObject") )
+	{
+		uint32	checksumName;	
+		pParams->GetChecksum( CRCD(0xa1dc81f9,"Name"), &checksumName, Script::ASSERT );
+		init_model_from_level_object(checksumName);
+	}
+	else
+	{
+		// by default, use the asset manager
+		int useAssetManager = 1;
+		pParams->GetInteger( CRCD(0xc63c8d38,"use_asset_manager"), &useAssetManager, Script::NO_ASSERT );
+
+		int texDictOffset = 0;
+		pParams->GetInteger( CRCD(0xf891ac27,"texDictOffset"), &texDictOffset, Script::NO_ASSERT );
+
+		if ( pParams->GetChecksum( CRCD(0x153a84de,"refObjectName"), &refObjectName, Script::NO_ASSERT ) )
+		{
+			m_refObjectName = refObjectName;
+			m_hasRefObject = true;
+			mp_model->SetRenderMode( Nx::vNONE );
+		}
+		else if ( pParams->GetChecksum( CRCD(0x86bd5b8f,"assetName"), &assetName, Script::NO_ASSERT ) )
+		{														  
+			// this means that the asset name was faked somehow
+			// (for example, if we've loaded it up from a data stream
+			// instead of a filename)
+			Dbg_MsgAssert( mp_model, ( "No model" ) );
+
+			int supportMultipleMaterialColors = 0;
+			pParams->GetInteger( CRCD(0x92b43e79,"multicolor"), &supportMultipleMaterialColors, Script::NO_ASSERT );
+			
+			// component name doesn't matter...  give it a dummy
+			uint32 componentName = 0;
+			mp_model->AddGeom( assetName, componentName, supportMultipleMaterialColors );
+		}
+		else if ( pParams->GetChecksum( CRCD(0x7dd037b3,"cloneFrom"), &cloneID, Script::NO_ASSERT ) )
+		{														  
+			// Clone the geometry off of an existing model
+			CCompositeObject* pObject = static_cast(CCompositeObjectManager::Instance()->GetObjectByID(cloneID));
+			Dbg_MsgAssert( pObject, ( "Couldn't find object id %d to clone", cloneID ) );
+
+			CModelComponent *p_src_model_comp = GetModelComponentFromObject(pObject);
+			Dbg_MsgAssert( p_src_model_comp, ( "Couldn't find model component in object id %d", cloneID ) );
+			Dbg_MsgAssert( p_src_model_comp->mp_model, ( "Couldn't find CModel in model component of object id %d", cloneID ) );
+
+			uint32 geomName;
+			Script::CArray *p_geom_array=NULL;
+			if ( pParams->GetChecksum( CRCD(0xe7308c1f,"geom"), &geomName, Script::NO_ASSERT ) )
+			{
+				Nx::CGeom *p_orig_geom = p_src_model_comp->mp_model->GetGeom(geomName);
+				Dbg_MsgAssert(p_orig_geom, ("Couldn't find CGeom %s", Script::FindChecksumName(geomName)));
+
+				Nx::CGeom *p_new_geom = p_orig_geom->Clone(true, mp_model);
+				Dbg_MsgAssert(p_new_geom, ("Couldn't clone CGeom %s", Script::FindChecksumName(geomName)));
+
+				p_new_geom->SetActive(true);
+				mp_model->AddGeom(p_new_geom, geomName);
+			}
+			else if (pParams->GetArray(CRCD(0x44e31dff,"geoms"), &p_geom_array))
+			{
+				uint32 geomName;
+				for (uint i = 0; i < p_geom_array->GetSize(); i++)
+				{
+					geomName = p_geom_array->GetChecksum(i);
+					if (geomName)
+					{
+						Nx::CGeom *p_orig_geom = p_src_model_comp->GetModel()->GetGeom(geomName);
+						Dbg_MsgAssert(p_orig_geom, ("Couldn't find CGeom %s", Script::FindChecksumName(geomName)));
+
+						Nx::CGeom *p_new_geom = p_orig_geom->Clone(true, mp_model);
+						Dbg_MsgAssert(p_new_geom, ("Couldn't clone CGeom %s", Script::FindChecksumName(geomName)));
+
+						p_new_geom->SetActive(true);
+						mp_model->AddGeom(p_new_geom, geomName);
+					}
+				}
+			}
+		}
+		else if ( pParams->GetText( CRCD(0x286a8d26,"model"), &pModelName, Script::NO_ASSERT )
+			|| pParams->GetText( CRCD(0xb974f2fc,"modelName"), &pModelName, Script::NO_ASSERT ) )
+		{
+			// TODO:  If the model name is "none", then we shouldn't
+			// have added the modelcomponent in the first place...
+			// we should have some kind of higher-level logic decide
+			// whether or not to create the modelcomponent, based on
+			// whether the node specifies:  model="none".
+			if ( Script::GenerateCRC(pModelName) == CRCD(0x806fff30,"none") )
+			{
+				;	// do nothing
+			}
+			else
+			{
+				char fullModelName[vMAX_PATH];
+
+				uint32 skeletonName;
+				if ( pParams->GetChecksum( CRCD(0x9794932,"skeletonName"), &skeletonName, Script::NO_ASSERT ) )
+				{
+					// if it's a skinned model
+					Gfx::GetModelFileName(pModelName, ".skin", fullModelName);
+				}
+				else
+				{
+					// if it's a nonskinned model
+					Gfx::GetModelFileName(pModelName, ".mdl", fullModelName);
+				}
+
+				Str::LowerCase( fullModelName );
+
+				bool forceTexDictLookup = pParams->ContainsFlag( CRCD(0x6c37fdc7,"AllowReplaceTex") );
+
+				// TODO: remove this for thps5 (it's only used for the boards
+				// in the skateshop, which need to do texture replacement, and 
+				// thus cannot use the asset manager...)
+				if ( strstr( fullModelName, "thps4board_" ) )
+				{
+					useAssetManager = false;
+				}
+
+				// Model file name should look like this:  "models/testcar/testcar.mdl"
+				Dbg_MsgAssert( strlen(fullModelName) < vMAX_PATH, ( "String too long" ) );
+				mp_model->AddGeom( fullModelName, 0, useAssetManager, texDictOffset, forceTexDictLookup );
+				
+#ifdef __PLAT_NGPS__
+				int lodIndex = 0;
+				// now load up the other LODs, if they exist
+				for ( int i = 0; i < vNUM_LODS; i++ )
+				{
+					char paramName[256];
+					sprintf( paramName, "modelLOD%d", lodIndex + 1 ); 
+					if ( pParams->GetText( paramName, &pModelName, Script::NO_ASSERT ) )
+					{
+						float modelDist;
+						sprintf( paramName, "modelLODdist%d", lodIndex + 1 );
+						if ( pParams->GetFloat( paramName, &modelDist, Script::NO_ASSERT ) )
+						{
+							uint32 skeletonName;
+							if ( pParams->GetChecksum( CRCD(0x9794932,"skeletonName"), &skeletonName, Script::NO_ASSERT ) )
+							{
+								// if it's a skinned model
+								Gfx::GetModelFileName(pModelName, ".skin", fullModelName);
+							}
+							else
+							{
+								// if it's a nonskinned model
+								Gfx::GetModelFileName(pModelName, ".mdl", fullModelName);
+							}
+
+							Str::LowerCase( fullModelName );
+							
+							mp_model->AddGeom( fullModelName, lodIndex + 1, useAssetManager, texDictOffset );
+							enable_lod( lodIndex + 1, modelDist );
+							lodIndex++;
+						}
+						else
+						{
+							Dbg_MsgAssert( 0, ( "Expected to find parameter for LOD model dist: %s", paramName ) );
+						}
+					}
+				}
+#endif
+			}
+		}
+		else if ( pParams->GetChecksum( CRCD(0x286a8d26,"model"), &model_name_checksum, Script::NO_ASSERT ))
+		{
+			Nx::CSector *p_source_sector = Nx::CEngine::sGetSector(model_name_checksum);
+			Dbg_MsgAssert(p_source_sector,("Could not find sector %s\n",Script::FindChecksumName(model_name_checksum)));
+			
+			Nx::CGeom *p_orig_geom = p_source_sector->GetGeom();
+			Dbg_MsgAssert(p_orig_geom, ("Couldn't find CGeom for %s",Script::FindChecksumName(model_name_checksum)));
+
+			Nx::CGeom *p_new_geom = p_orig_geom->Clone(true, (Nx::CScene*)NULL);
+			Dbg_MsgAssert(p_new_geom, ("Couldn't clone CGeom %s", Script::FindChecksumName(model_name_checksum)));
+			p_new_geom->SetActive(true);
+			
+			mp_model->AddGeom(p_new_geom, 0);
+		}
+	}
+
+	// update its position
+	if ( GetObject()->IsFinalized() )
+	{
+		FinalizeModelInitialization();
+	}
+
+	// now that the model has been created,
+	// remember the bounding sphere
+	m_original_bounding_sphere = mp_model->GetBoundingSphere();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// create-a-ped, create-a-skater
+void CModelComponent::InitModelFromProfile( Gfx::CModelAppearance* pAppearance, bool useAssetManager, uint32 texDictOffset, uint32 buildScript  )
+{
+	Dbg_MsgAssert( mp_model, ( "No model?" ) );
+	Nx::CModel* pModel = mp_model;
+
+	// GJ:  shouldn't destroy the existing model
+	// it's up to each build script to do this...
+	// (in case we just want to re-apply a few
+	// operations)
+	//	pModel->ClearGeoms();
+
+    Gfx::CSkeleton* pSkeleton = NULL;
+	Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( GetObject() );
+	if ( pSkeletonComponent )
+	{
+		pSkeleton = pSkeletonComponent->GetSkeleton();
+	}
+
+	Gfx::CModelBuilder theBuilder( useAssetManager, texDictOffset );
+	if ( buildScript )
+	{
+		theBuilder.BuildModel( pAppearance, pModel, pSkeleton, buildScript );
+	}
+	else
+	{
+		theBuilder.BuildModel( pAppearance, pModel, pSkeleton );
+	}
+
+	// update its position
+	if ( GetObject()->IsFinalized() )
+	{
+		FinalizeModelInitialization();
+	}
+
+	// now that the model has been created,
+	// remember the bounding sphere
+	m_original_bounding_sphere = pModel->GetBoundingSphere();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelComponent::enable_lod(uint32 componentName, float distance)
+{
+	if ( m_numLODs >= vNUM_LODS )
+	{
+		Dbg_MsgAssert( 0, ( "Too many LODs!" ) );
+		return false;
+	}
+
+	Nx::CGeom* pGeom = mp_model->GetGeom( componentName );
+	if ( !pGeom )
+	{
+		Dbg_MsgAssert( 0, ( "Couldn't find geom named %s", Script::FindChecksumName(componentName) ) );
+		return false;
+	}
+
+//	printf( "Enabled lod %08x\n", componentName );
+
+	// GJ TODO:  should sort the lods by distance, 
+	// and check for duplicates!
+
+	m_LODdist[m_numLODs] = distance;
+
+	m_numLODs++;
+
+	// GJ TODO:  each client needs to do this
+	// on a case-by-case basis...
+
+	return true;
+}
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelComponent::HideGeom( uint32 geomName, bool hidden, bool propagate )
+{
+    if( propagate )
+	{
+		GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+		if( player && player->IsLocalPlayer())
+		{
+			Net::Client* client;
+			GameNet::MsgHideAtomic msg;
+			Net::MsgDesc msg_desc;
+
+			client = gamenet_man->GetClient( player->GetSkaterNumber() );
+			Dbg_Assert( client );
+
+			//msg.m_Time = client->m_Timestamp;
+			msg.m_Hide = hidden;
+			msg.m_AtomicName = geomName;
+			msg.m_ObjId = GetObject()->GetID();
+
+			msg_desc.m_Data = &msg;
+			msg_desc.m_Length = sizeof( GameNet::MsgHideAtomic );
+			msg_desc.m_Id = GameNet::MSG_ID_SET_HIDE_ATOMIC;
+			client->EnqueueMessageToServer( &msg_desc );
+		}
+	}
+
+	if ( mp_model )
+	{
+		mp_model->HideGeom( geomName, hidden );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelComponent::GeomHidden( uint32 geomName )
+{
+    return (mp_model->GeomHidden( geomName ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CModelComponent::GetDisplayMatrixWithExtraRotation( Mth::Matrix& displayMatrix )
+{
+    CCompositeObject* pObject = GetObject();
+    Dbg_MsgAssert( pObject, ( "Couldn't find parent object" ) );
+	
+	displayMatrix = pObject->GetDisplayMatrix();
+	displayMatrix[Mth::POS] = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
+	
+	if (mFlipDisplayMatrix)
+	{
+		displayMatrix[Z] = -displayMatrix[Z];
+		displayMatrix[X] = -displayMatrix[X];
+	}
+
+	// Record the display matrix for use by the camera before applying any extra rotation.
+//	m_camera_display_matrix=m_display_matrix;
+
+	Mth::Matrix extra_display_rotation;
+	
+	// only do the math if we're not dealing w/ the identity...
+	bool is_identity = true;
+
+	float new_angle;
+	if (mpDisplayRotationInfo[0].m_active)
+	{
+		new_angle=mpDisplayRotationInfo[0].CalculateNewAngle();	
+		if ( new_angle )
+		{
+			if (is_identity)
+			{
+				extra_display_rotation.Ident();
+			}
+			is_identity = false;
+			extra_display_rotation.RotateX(Mth::DegToRad(new_angle));
+		}
+	}
+	
+	if (mpDisplayRotationInfo[1].m_active)
+	{
+		new_angle=mpDisplayRotationInfo[1].CalculateNewAngle();	
+		if ( new_angle )
+		{
+			if (is_identity)
+			{
+				extra_display_rotation.Ident();
+			}
+			is_identity = false;
+			extra_display_rotation.RotateY(Mth::DegToRad(new_angle));
+		}
+	}
+	
+	if (mpDisplayRotationInfo[2].m_active)
+	{
+		new_angle=mpDisplayRotationInfo[2].CalculateNewAngle();	
+		if ( new_angle )
+		{
+			if (is_identity)
+			{
+				extra_display_rotation.Ident();
+			}
+			is_identity = false;
+			extra_display_rotation.RotateZ(Mth::DegToRad(new_angle));
+		}
+	}
+
+	Mth::Vector off(0.0f, 0.0f, 0.0f, 0.0f);	
+	if ( !is_identity )
+	{
+		off=m_display_rotation_offset;	
+		if ( !( off[X] == 0.0f && off[Y] == 0.0f && off[Z] == 0.0f ) )
+		{
+	//		is_identity = false;
+			off=off-off*extra_display_rotation;		  // Would zero it if it's the identity
+			off=off*displayMatrix;		
+		}
+	
+	//	if ( !is_identity )
+		{
+			displayMatrix=extra_display_rotation*displayMatrix;
+		}
+	}
+
+//#ifdef		DEBUG_DISPLAY_MATRIX
+//	dodgy_test(); printf("%d: Setting display_matrix[Y][Y] to %f, [X][X] to %f\n",__LINE__,m_display_matrix[Y][Y],m_display_matrix[X][X]);
+//#endif
+
+	displayMatrix[Mth::POS] = GetObject()->GetPos();
+	displayMatrix[Mth::POS] += off;
+	displayMatrix[Mth::POS] += mDisplayOffset;
+	displayMatrix[Mth::POS][W] = 1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::Hide( bool shouldHide )
+{
+	if ( mp_model )
+	{
+		mp_model->Hide( shouldHide );
+	}	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::Teleport()
+{
+	Dbg_MsgAssert( GetObject()->IsFinalized(), ( "Teleporting unfinalized component!" ) );
+
+	FinalizeModelInitialization();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CModelComponent::Update()
+{
+	if ( m_hasRefObject )
+	{
+#ifdef __NOPT_ASSERT__
+		Obj::CMovieManager* pMovieManager = Mdl::Skate::Instance()->GetMovieManager();
+		Dbg_MsgAssert( !pMovieManager->IsRolling(), ( "Wasn't expecting cutscene to be rolling" ) );
+#endif
+
+		// this code was lifted from cutscenedetails.cpp,
+		// which is why it has all these weird temp
+		// variables referencing its own member vars.
+		// it's used for doing the create-a-trick skater
+		Obj::CModelComponent* pModelComponent = this;
+		Obj::CCompositeObject* pCompositeObject = GetObject();
+
+		uint32 refObjectName = pModelComponent->GetRefObjectName();
+		Obj::CCompositeObject* pRefObject = (Obj::CCompositeObject*)Obj::ResolveToObject( refObjectName );
+		Dbg_Assert( pRefObject );
+		Obj::CModelComponent* pRefModelComponent = GetModelComponentFromObject( pRefObject );
+		Dbg_Assert( pRefModelComponent );
+		Nx::CModel* pRefModel = pRefModelComponent->GetModel();
+		bool should_animate = true;
+		Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( pCompositeObject );
+		Dbg_MsgAssert( pSkeletonComponent, ( "Was expecting a skeleton component" ) );
+		Dbg_Assert( pSkeletonComponent == mp_skeleton_component );
+
+		Mth::Matrix theDisplayMatrix;
+		this->GetDisplayMatrixWithExtraRotation( theDisplayMatrix );
+		
+		// the return matrix already takes the position (plus an additional offset)
+		// into account, so we don't have to do the following:
+		// theDisplayMatrix[Mth::POS] = GetObject()->GetPos();
+		
+		pRefModel->Render( &theDisplayMatrix, !should_animate, pSkeletonComponent->GetSkeleton() );
+		pRefModel->SetBoneMatrixData( pSkeletonComponent->GetSkeleton() );
+	}
+	// Mick: Don't need to update it if not active, just leave it where it is
+	else if (mp_model && mp_model->GetActive())
+	{
+		
+		Dbg_MsgAssert(GetObject()->IsFinalized(),("Update() to UnFinalized Composite object %s",Script::FindChecksumName(GetObject()->GetID())));
+	
+		Mth::Matrix theDisplayMatrix;
+
+		GetDisplayMatrixWithExtraRotation( theDisplayMatrix );
+			
+//			theDisplayMatrix = GetObject()->GetDisplayMatrix();
+//			theDisplayMatrix[Mth::POS] = GetObject()->GetPos();
+//			theDisplayMatrix[Mth::POS][W] = 1.0f;
+		
+		// TODO:  The interface between different components
+		// should be more generic, maybe...
+		Gfx::CSkeleton* pSkeleton = NULL;
+		if ( mp_skeleton_component )
+		{
+			pSkeleton = mp_skeleton_component->GetSkeleton();
+		}
+		
+		// default to true, for skeletal cars...
+		bool should_animate = true;
+	
+		if ( mp_animation_component )
+		{
+			// either the animation component should cache this
+			// data rather than doing the visibility test twice,
+			// or it should be able to set some member inside
+			// the model component (CModelComponent::MarkAnimationAsDirty?)
+			should_animate = mp_animation_component->ShouldAnimate();
+		}
+	
+	#ifdef __PLAT_NGPS__
+		// if it has LODs, then hide all the unnecessary ones
+		if ( m_numLODs > 0 )
+		{
+			// first hide all the models, including the base one (0)
+			for ( int i = 0; i < m_numLODs + 1; i++ )
+			{
+				mp_model->HideGeom( i, true );
+			}
+	
+			// now go through and unhide the correct one
+			float distanceSqrToCamera = mp_suspend_component->GetDistanceSquaredToCamera();
+			
+	//		printf( "distance to camera: %f feet\n", sqrtf(distanceSqrToCamera)/12.0f );
+	
+			bool found = false;
+			for ( int i = 0; i < m_numLODs; i++ )
+			{
+				if ( distanceSqrToCamera < ( m_LODdist[i] * m_LODdist[i] ) )
+				{
+					mp_model->HideGeom( i, false );
+					found = true;
+				}
+			}
+			if ( !found )
+			{
+				// then the last lod is active
+				mp_model->HideGeom( m_numLODs, false );
+			}
+		}
+	#endif
+	
+		// TODO:  if it's offscreen, the data shouldn't be copied over either...
+	
+		mp_model->Render( &theDisplayMatrix, !should_animate, pSkeleton );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::Finalize()
+{
+	mp_skeleton_component =  GetSkeletonComponentFromObject( GetObject() );
+	mp_animation_component =  GetAnimationComponentFromObject( GetObject() );
+	mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::SetModelLODDistance( int lodIndex, float distance )
+{
+	if ( !mp_model )
+	{
+		Dbg_Message( "No model!" );
+		return;
+	}
+
+	if ( m_numLODs == 0 )
+	{
+		Dbg_Message( "Model %s has no lods", Script::FindChecksumName(GetObject()->GetID()) );
+	}
+
+	if ( lodIndex < 1 || lodIndex > m_numLODs )
+	{
+		Dbg_Message( "Was expecting an lod index between 1 and %d", m_numLODs);
+		return;
+	}
+
+	m_LODdist[lodIndex-1] = distance;
+	Dbg_Message( "ModelLODDist%d is now %f", lodIndex, distance );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::SetBoundingSphere( const Mth::Vector& newSphere )
+{
+	if ( mp_model )
+	{
+		mp_model->SetBoundingSphere( newSphere );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::UpdateBrightness()
+{
+	Dbg_MsgAssert(mp_model, ("UpdateBrightness: CModel is NULL"));
+	Dbg_MsgAssert(mp_model->GetModelLights(), ("UpdateBrightness: MovingObject has no model lights"));
+	mp_model->GetModelLights()->UpdateBrightness();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CModelComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	bool success = true;
+    Script::CStruct *p_script_params;
+	 
+	p_script_params = NULL;
+	if( pScript )
+	{
+		p_script_params = pScript->GetParams();
+	}
+
+	switch (Checksum)
+	{
+		// @script | Obj_EnableScaling | in case you want to dynamically
+        // turn on scaling
+		case ( 0x347ff11b ):	// Obj_EnableScaling
+			if ( mp_model )
+			{
+				mp_model->EnableScaling( true );
+			}
+			break;
+
+        // @script | Obj_DisableScaling | in case you want to dynamically
+        // turn off scaling
+		case ( 0xf951aa64 ):	// Obj_DisableScaling
+			if ( mp_model )
+			{
+				mp_model->EnableScaling( false );
+			}
+			break;
+
+        // @script | Obj_ApplyScaling | scale on x,y,z 
+        // @parm float | x | 
+        // @parm float | y | 
+        // @parm float | z | 
+		case ( 0x657ff1ed ):	// Obj_ApplyScaling
+		{
+			// GJ:  Scaling only refers to the renderable
+			// model, not the collision volume...
+			if ( mp_model )
+			{
+				Mth::Vector theScale(1.0f,1.0f,1.0f);
+				if ( Gfx::GetScaleFromParams( &theScale, pParams ) )
+				{
+					mp_model->SetScale( theScale );
+					mp_model->EnableScaling( true );
+				}
+			}
+		}
+		break;
+        
+		// @script | Obj_ClearColor | turns off color modulation on this resetting it to the default color
+		case ( 0x6052480a ):	// Obj_ClearColor
+		{
+			if ( mp_model )
+			{
+				mp_model->ClearColor( CRCD(0xc4e78e22,"all") );
+			}
+		}
+		break;
+
+        // @script | Obj_SetColor | 
+        // @parm int | h | hue - between 0 and 360
+        // @parm int | s | saturation - between 0 and 100
+        // @parm int | v | value - between 0 and 100
+        case ( 0x76ecfa1c ):	// Obj_SetColor
+		{
+            if ( mp_model )
+			{
+				int h, s, v;
+				if ( pParams->GetInteger( CRCD(0x6e94f918,"h"), &h, false )
+					&& pParams->GetInteger( CRCD(0xe4f130f4,"s"), &s, false )
+					&& pParams->GetInteger( CRCD(0x949bc47b,"v"), &v, false ) )
+				{
+					mp_model->ModulateColor( CRCD(0xc4e78e22,"all"), (float)h, (float)s / 100.0f, (float)v / 100.0f );
+				}
+			}
+		}
+		break;
+
+        // @script | Obj_ReplaceTexture | Replaces a texture in the model's dictionary
+        // @parm text | src | filename of the source texture
+        // @parm text | dest | filename of the destination texture
+        // @parmopt name | in | all | name of geom in which to look for texture (defaults to global replacement)
+		// Ex:  Obj_ReplaceTexture src="knee_l.png" dest="textures/skater_m/knee_scuff"	in=skater_m_legs
+		case 0x83f9be15: // Obj_ReplaceTexture
+		{
+			const char* pSrcTexture;
+			const char* pDstTexture;
+			
+			success = false;
+			
+			Script::CArray* pArray;
+			if ( pParams->GetArray( CRCD(0x5ef31148,"array"), &pArray, Script::NO_ASSERT ) )
+			{
+				for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+				{
+					Script::CStruct* pSubParams = pArray->GetStructure( i );
+				
+					pSubParams->GetText( CRCD(0x9fbbdb72,"src"), &pSrcTexture, Script::ASSERT );
+					pSubParams->GetText( CRCD(0x7799d66c,"dest"), &pDstTexture, Script::ASSERT );
+
+					// by default, it searches globally in the model for the correct texture
+					uint32 partChecksumToReplace = Nx::CModel::vREPLACE_GLOBALLY;
+					pSubParams->GetChecksum( CRCD(0xa01371b1,"in"), &partChecksumToReplace, Script::NO_ASSERT );
+					if ( mp_model )
+					{
+						if ( mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDstTexture ) )
+						{
+							success = true;
+						}
+					}
+				}
+			}
+			else
+			{
+				pParams->GetText( CRCD(0x9fbbdb72,"src"), &pSrcTexture, Script::ASSERT );
+				pParams->GetText( CRCD(0x7799d66c,"dest"), &pDstTexture, Script::ASSERT );
+
+				// by default, it searches globally in the model for the correct texture
+				uint32 partChecksumToReplace = Nx::CModel::vREPLACE_GLOBALLY;
+				pParams->GetChecksum( CRCD(0xa01371b1,"in"), &partChecksumToReplace, Script::NO_ASSERT );
+				if ( mp_model )
+				{
+					if ( mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDstTexture ) )
+					{
+						success = true;
+					}
+				}
+			}
+		}
+		break;
+        		
+		// @script | Obj_ReplaceSpriteTexture | Replaces a texture in the model's dictionary
+		// @parm text | src | filename of the source texture
+		// @parm name | dest | name of the destination texture
+		// @parmopt name | in | all | name of geom in which to look for texture (defaults to global replacement)
+		// Ex:  Obj_ReplaceSpriteTexture src="knee_l.png" dest=knee_scuff	in=skater_m_legs
+		case 0x9a478afe: // Obj_ReplaceSpriteTexture
+		{
+			const char* pSrcTexture;
+			uint32 dest_checksum = 0;
+
+			pParams->GetText( CRCD(0x9fbbdb72,"src"), &pSrcTexture, Script::ASSERT );
+			pParams->GetChecksum( CRCD(0x7799d66c,"dest"), &dest_checksum, Script::ASSERT );
+
+			// by default, it searches globally in the model for the correct texture
+			uint32 partChecksumToReplace = Nx::CModel::vREPLACE_GLOBALLY;
+			pParams->GetChecksum( CRCD(0xa01371b1,"in"), &partChecksumToReplace, Script::NO_ASSERT );
+			if ( mp_model )
+			{
+				success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, dest_checksum );
+			}
+			else
+			{
+				success = false;
+			}
+		}
+		break;
+
+		// @script | Obj_ClearGeoms | removes the model's geoms
+		case 0xbd18e2e3: // Obj_ClearGeoms
+			if ( mp_model )
+			{
+				mp_model->ClearGeoms();
+			}
+			break;
+        
+		// @script | Obj_HasModelLights | tests existence of model lights
+		case 0xe11f85e8: // Obj_HasModelLights
+			{
+				Dbg_MsgAssert(mp_model, ("Obj_HasModelLights: CModel is NULL"));
+				
+				return mp_model->GetModelLights() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			}
+			break;
+
+		// @script | Obj_UpdateBrightness | the normal lighting code assumes
+		// that the collision code will be run once a frame to copy over
+		// the m_ambient_base_color into the m_ambient_mod_color.  unfortunately,
+		// the cutscene objects' ref objects don't go through this code, so I had to
+		// add this function to explicitly copy over the data...  there's probably
+		// a cleaner way to do it, but I didn't want to break the existing code.
+		case 0x1c16332e: // Obj_UpdateBrightness
+			Dbg_MsgAssert(mp_model, ("Obj_UpdateBrightness: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_UpdateBrightness: MovingObject has no model lights"));
+			mp_model->GetModelLights()->UpdateBrightness();
+			break;
+
+		// @script | Obj_EnableAmbientLight | enables the ambient model light
+		case 0xa14d6372: // Obj_EnableAmbientLight
+			Dbg_MsgAssert(mp_model, ("Obj_EnableAmbientLight: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_EnableAmbientLight: MovingObject has no model lights"));
+			mp_model->GetModelLights()->EnableAmbientLight(true);
+			break;
+
+		// @script | Obj_DisableAmbientLight | disables the ambient model light
+		case 0x8a445e26: // Obj_DisableAmbientLight
+			Dbg_MsgAssert(mp_model, ("Obj_DisableAmbientLight: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_DisableAmbientLight: MovingObject has no model lights"));
+			mp_model->GetModelLights()->EnableAmbientLight(false);
+			break;
+
+		// @script | Obj_EnableDiffuseLight | enables a diffuse model light
+		// @parm int | index | Light number
+		case 0xed5d1550: // Obj_EnableDiffuseLight
+		{
+			int index;
+
+			if (!pParams->GetInteger(CRCD(0x7f8c98fe,"index"), &index))
+			{
+				Dbg_MsgAssert(0, ("Obj_EnableDiffuseLight: Can't find 'index' of light"));
+			}
+
+			Dbg_MsgAssert(mp_model, ("Obj_EnableDiffuseLight: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_EnableDiffuseLight: MovingObject has no model lights"));
+			mp_model->GetModelLights()->EnableDiffuseLight(index, true);
+			break;
+		}
+
+		// @script | Obj_DisableDiffuseLight | disables a diffuse model light
+		// @parm int | index | Light number
+		case 0xc6542804: // Obj_DisableDiffuseLight
+		{
+			int index;
+
+			if (!pParams->GetInteger(CRCD(0x7f8c98fe,"index"), &index))
+			{
+				Dbg_MsgAssert(0, ("Obj_DisableDiffuseLight: Can't find 'index' of light"));
+			}
+
+			Dbg_MsgAssert(mp_model, ("Obj_DisableDiffuseLight: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_DisableDiffuseLight: MovingObject has no model lights"));
+			mp_model->GetModelLights()->EnableDiffuseLight(index, false);
+			break;
+		}
+
+		// @script | Obj_SetLightAmbientColor | Sets the model light ambient color
+		// @parm int | r | Red
+		// @parm int | g | Green
+		// @parm int | b | Blue
+		case 0x3cf63049: // Obj_SetLightAmbientColor
+		{
+			int r, g, b;
+			if (!pParams->GetInteger(CRCD(0x93f60062,"r"), &r))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightAmbientColor: Can't find 'r' color"));
+			}
+			if (!pParams->GetInteger(CRCD(0xfe2be489,"g"), &g))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightAmbientColor: Can't find 'g' color"));
+			}
+			if (!pParams->GetInteger(CRCD(0x8e411006,"b"), &b))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightAmbientColor: Can't find 'b' color"));
+			}
+
+			Image::RGBA rgb(r, g, b, 0x80);
+
+			Dbg_MsgAssert(mp_model, ("Obj_SetLightAmbientColor: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_SetLightAmbientColor: MovingObject has no model lights"));
+			mp_model->GetModelLights()->SetLightAmbientColor(rgb);
+			mp_model->GetModelLights()->EnableAmbientLight(true);
+			
+			break;
+		}
+
+		// @script | Obj_SetLightDirection | Sets the unit direction vector of a model light
+		// @parm int | index | Light number
+		// @parm vector | direction | Unit direction vector (overrides heading and pitch)
+		// @parm float | heading | Heading angle (in degrees)
+		// @parm float | pitch | Pitch angle (in degrees)
+		case 0x46a1f4a4: // Obj_SetLightDirection
+		{
+			int index;
+
+			if (!pParams->GetInteger(CRCD(0x7f8c98fe,"index"), &index))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightDirection: Can't find 'index' of light"));
+			}
+
+			float heading, pitch;
+			Mth::Vector direction(0, 0, 1, 0);
+			if (pParams->GetVector(CRCD(0xc1b52e4c,"direction"), &direction))
+			{
+				direction[W] = 0.0f;		// This is the only way to force this to be a vector (as opposed to a point)
+				//Dbg_Message("************ direction (%f, %f, %f)", direction[X], direction[Y], direction[Z]);
+			}
+			else if (pParams->GetFloat(CRCD(0xfd4bc03e,"heading"), &heading) && pParams->GetFloat(CRCD(0xd8604126,"pitch"), &pitch)) 
+			{
+				direction.RotateX(Mth::DegToRad(pitch));
+				direction.RotateY(Mth::DegToRad(heading));
+				//Dbg_Message("************ heading and pitch direction (%f, %f, %f)", direction[X], direction[Y], direction[Z]);
+			}
+			else
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightDirection: Can't find 'direction' or 'heading' and 'pitch' of light"));
+			}
+
+			Dbg_MsgAssert(mp_model, ("Obj_SetLightDirection: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_SetLightDirection: MovingObject has no model lights"));
+			mp_model->GetModelLights()->SetLightDirection(index, direction);
+
+			break;
+		}
+
+		// @script | Obj_SetLightDiffuseColor | Sets a model light diffuse color
+		// @parm int | index | Light number
+		// @parm int | r | Red
+		// @parm int | g | Green
+		// @parm int | b | Blue
+		case 0x70e6466b: // Obj_SetLightDiffuseColor
+		{
+			int index;
+
+			if (!pParams->GetInteger(CRCD(0x7f8c98fe,"index"), &index))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightDiffuseLight: Can't find 'index' of light"));
+			}
+
+			int r, g, b;
+			if (!pParams->GetInteger(CRCD(0x93f60062,"r"), &r))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightDiffuseColor: Can't find 'r' color"));
+			}
+			if (!pParams->GetInteger(CRCD(0xfe2be489,"g"), &g))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightDiffuseColor: Can't find 'g' color"));
+			}
+			if (!pParams->GetInteger(CRCD(0x8e411006,"b"), &b))
+			{
+				Dbg_MsgAssert(0, ("Obj_SetLightDiffuseColor: Can't find 'b' color"));
+			}
+
+			Image::RGBA rgb(r, g, b, 0x80);
+
+			Dbg_MsgAssert(mp_model, ("Obj_SetLightDiffuseColor: CModel is NULL"));
+			Dbg_MsgAssert(mp_model->GetModelLights(), ("Obj_SetLightDiffuseColor: MovingObject has no model lights"));
+			mp_model->GetModelLights()->SetLightDiffuseColor(index, rgb);
+			mp_model->GetModelLights()->EnableDiffuseLight(index, true);
+			break;
+		}
+
+		// @script | Obj_SetUVOffset | Sets the UV offset of a material pass
+		// @parm name | material | material name
+		// @parm int | pass | material pass
+		// @parm float | u | u offset
+		// @parm float | v | v offset
+		case 0x57eb306e: // Obj_SetUVOffset
+		{
+			uint32 mat_checksum;
+			if (!pParams->GetChecksum(CRCD(0x83418a6a,"material"), &mat_checksum))
+			{
+				Dbg_MsgAssert(0, ("Can't find parameter material"));
+			}
+
+			// Extract all the parameters
+			int pass = 0;
+			float u_offset;
+			float v_offset;
+			bool found_all;
+
+			found_all  = pParams->GetFloat(CRCD(0xd9295c1,"u"), &u_offset);
+			found_all &= pParams->GetFloat(CRCD(0x949bc47b,"v"), &v_offset);
+			found_all &= pParams->GetInteger(CRCD(0x318f2bdb,"pass"), &pass);
+
+			Dbg_MsgAssert(found_all, ("Missing one or more of the wibble offset parameters.  Must fill them all out."));
+
+			Dbg_MsgAssert(mp_model, ("Obj_SetUVOffset: CModel is NULL"));
+			mp_model->SetUVOffset(mat_checksum, pass, u_offset, v_offset);
+
+			break;
+		}
+
+		// @script | Obj_SetUVParams | Sets the UV parameters of a material pass
+		// @parm name | material | material name
+		// @parm int | pass | material pass
+		// @parmopt float | uoff | 0.0 | u offset
+		// @parmopt float | voff | 0.0 | v offset
+		// @parmopt float | uscale | 1.0 | u scale
+		// @parmopt float | vscale | 1.0 | v scale
+		// @parmopt float | rot | 0.0 | rotation in degrees
+		case 0x812ff44d: // Obj_SetUVParams
+		{
+			uint32 mat_checksum;
+			int pass;
+			if (!pParams->GetChecksum(CRCD(0x83418a6a,"material"), &mat_checksum))
+			{
+				Dbg_MsgAssert(0, ("Can't find parameter material"));
+			}
+			if (!pParams->GetInteger(CRCD(0x318f2bdb,"pass"), &pass))
+			{
+				Dbg_MsgAssert(0, ("Can't find parameter pass"));
+			}
+
+			// Clear matrix
+			Mth::Matrix mat;
+			mat.Ident();
+
+			// Scaling
+			float uscale = 1.0f;
+			float vscale = 1.0f;
+			bool scale = pParams->GetFloat(CRCD(0x3fb610b7,"uscale"), &uscale);
+			scale = pParams->GetFloat(CRCD(0xb9226219,"vscale"), &vscale) || scale;
+			if (scale)
+			{
+				Dbg_MsgAssert((uscale >= 0.0f) && (vscale >= 0.0f), ("Obj_SetUVParams: Don't use negative scales."));
+
+				mat[0][0] *= uscale;
+				mat[1][1] *= vscale;
+			}
+
+			// Rotation
+			float rot_deg;
+			if (pParams->GetFloat(CRCD(0xe2c6589e,"rot"), &rot_deg))
+			{
+				mat.RotateZ(Mth::DegToRad(rot_deg));
+			}
+
+			// Offset
+			float uoffset = 0.0f;
+			float voffset = 0.0f;
+			bool translate = pParams->GetFloat(CRCD(0x56a7f41c,"uoff"), &uoffset);
+			translate = pParams->GetFloat(CRCD(0x44125bf2,"voff"), &voffset) || translate;
+			if (translate)
+			{
+				mat[3][0] = uoffset;
+				mat[3][1] = voffset;
+			}
+
+			Dbg_MsgAssert(mp_model, ("Obj_SetUVParams: CModel is NULL"));
+			mp_model->SetUVMatrix(mat_checksum, pass, mat);
+
+			break;
+		}
+        // @script | EnableDisplayFlip | 
+		case 0x721184c7: // EnableDisplayFlip
+			mFlipDisplayMatrix=true;
+			break;
+
+        // @script | DisableDisplayFlip | 
+		case 0x4af3b1f7: // DisableDisplayFlip
+			mFlipDisplayMatrix=false;
+			break;
+
+        // @script | Obj_InitModel | 
+		case 0x4d8e11ab: // Obj_InitModel
+			InitModel( pParams );
+			break;
+			
+        // @script | SwitchOffAtomic | 
+        // @uparm name | geom name
+		case 0xe48fd084: // SwitchOffAtomic
+		{
+			uint32 atomicName;
+			pParams->GetChecksum( NONAME, &atomicName, Script::ASSERT );
+			HideGeom(atomicName, true, true);
+		}
+		break;
+
+        // @script | SwitchOnAtomic | 
+        // @uparm name | geom name
+		case 0x07d0c128: // SwitchOnAtomic
+		{
+			uint32 atomicName;
+			pParams->GetChecksum( NONAME, &atomicName, Script::ASSERT );
+			HideGeom(atomicName, false, true);
+		}
+		break;
+
+        // @script | AtomicIsHidden | 
+        // @uparm name | geom name
+        case 0xe7fa7dd0: // AtomicIsHidden
+		{
+			uint32 atomicName;
+			pParams->GetChecksum( NONAME, &atomicName, Script::ASSERT );
+            if (GeomHidden( atomicName ))
+                pScript->GetParams()->AddInteger(CRCD(0x77a21642,"hidden"), 1);
+            else
+                pScript->GetParams()->AddInteger(CRCD(0x77a21642,"hidden"), 0);
+		}
+		break;
+		
+		// @script | Obj_InitModelFromProfile | used for loading up peds and re-initializing the preview model
+		case 0x98ed5c8b: // Obj_InitModelFromProfile
+		{
+			if ( mp_model )
+			{
+				int texDictOffset = 0;
+				pParams->GetInteger( CRCD(0xf891ac27,"texDictOffset"), &texDictOffset, Script::NO_ASSERT );
+
+				int useAssetManager = 0;
+				pParams->GetInteger( CRCD(0xc63c8d38,"use_asset_manager"), &useAssetManager, Script::NO_ASSERT );
+
+				Script::CStruct* pAppearanceParams;
+				pParams->GetStructure( CRCD(0x456d28d1,"struct"), &pAppearanceParams, Script::ASSERT );
+
+				uint32 buildScript = 0;
+				pParams->GetChecksum( CRCD(0x299ee352,"buildscript"), &buildScript, Script::NO_ASSERT );
+				
+				// load the skeleton (do i really need to do a sanity check here?)
+//              Dbg_MsgAssert( GetSkeleton(), ( "Object has no skeleton" ) )
+
+				Gfx::CModelAppearance theAppearance;
+				theAppearance.Load( pAppearanceParams, true );
+				InitModelFromProfile( &theAppearance, useAssetManager, texDictOffset, buildScript );
+			}
+		}
+		break;
+		
+		// @script | Obj_SetBoundingSphere | sets a sphere with the specified radius (assumes position is 0,0,0)
+		// @uparm float | radius of bounding sphere
+		case 0x452fb315:  // Obj_SetBoundingSphere
+		{	
+			float radius;
+			pParams->GetFloat( NONAME, &radius, Script::ASSERT );
+			
+			// take the existing position, and change the radius on it
+			Mth::Vector newSphere( 0.0f, 0.0f, 0.0f, radius );
+			
+			SetBoundingSphere( newSphere );
+		}
+		break;
+
+		// @script | Obj_RestoreBoundingSphere | resets the sphere to whatever it was after the model was initialized
+		case 0x5b41da8f:  // Obj_RestoreBoundingSphere
+		{
+			SetBoundingSphere( m_original_bounding_sphere );
+		}
+		break;
+		
+        // @script | RotateDisplay | 
+        // @parmopt float | Duration | 0.0 | Duration time (default is ms) The rotation will take this much time to complete.
+        // @flag seconds | Units of seconds
+        // @flag frames | Units of frames
+        // @flag x | Rotate about the x-axis
+        // @flag y | Rotate about the y-axis
+        // @flag z | Rotate about the z-axis
+        // @parmopt float | StartAngle | 0.0 | Start angle, in degrees. The skater will instantly pop to this angle
+		// when the RotateDisplay command is issued.
+        // @parmopt float | EndAngle | 360.0 | End angle, in degrees. The skater will rotate from
+		// the start angle to the end angle over the time period specified by Duration.
+        // @parmopt int | SinePower | 0 | If set to zero (the default) the angle will change at a constant speed over
+		// the specified duration.
+		// A SinePower of 1 makes the speed start instantly but slow down smoothly at the end.
+		// A SinePower of greater than one will make the speed smoothly accelerate from zero to a maximum, then
+		// smoothly decelerate to zero. Values of 2,3 or 4 seem to work best.
+		// A SinePower of -1 will make the speed smoothly accelerate from zero to a constant value. It will not
+		// decelerate at the end.
+		// Note that the final constant speed will be 1.5707 times the constant speed that would be have been used
+		// if the SinePower was 0. The speed has to be a bit higher to make up for the smooth acceleration at the start
+		// whilst keeping within the same Duration value.
+		// So, if you want to follow with another RotateDisplay that maintains that constant speed, you will need to
+		// divide it's duration by 1.5707, for example:
+		// RotateDisplay y Duration=3 seconds EndAngle=(360*4) SinePower=-1
+		// Wait 3 seconds
+		// RotateDisplay y Duration=(3/1.5707) seconds EndAngle=(360*4) SinePower=0
+		// @parmopt vector | RotationOffset | (0,30,0) | The offset of the point about which to rotate the skater.
+		// @flag HoldOnLastAngle | If specified then the rotation will not pop back to what it was at
+		// the end of the duration, but will stick until a CancelRotateDisplay command is issued.
+		case 0xa4c25c2f: // RotateDisplay
+		{
+			#ifdef __USER_DAN__
+			printf("RotateDisplay\n");
+			Script::PrintContents(pParams);
+			#endif
+			
+			float duration = 0.0f;
+			pParams->GetFloat(CRCD(0x79a07f3f, "Duration"), &duration);
+			if (pParams->ContainsFlag(CRCD(0xd029f619, "Seconds")) || pParams->ContainsFlag(CRCD(0x49e0ee96, "Second")))
+			{
+				// Convert from seconds to milliseconds
+				duration *= 1000.0f;
+			}
+			else if (pParams->ContainsFlag(CRCD(0x19176c5, "Frames")) || pParams->ContainsFlag(CRCD(0x4a07c332, "Frame")))
+			{
+				// Convert from frames to milliseconds
+				duration = duration * 1000.0f / 60.0f;
+			}
+			else
+			{
+				// Milliseconds is what we want, so nothing to do.
+			}
+			
+			float start_angle = 0.0f;
+			pParams->GetFloat(CRCD(0x9de2a72f, "StartAngle"), &start_angle);
+			
+			float end_angle = 0.0f;
+			pParams->GetFloat(CRCD(0x8c9946a9, "EndAngle"), &end_angle);
+
+			int sine_power = 0;
+			pParams->GetInteger(CRCD(0xf164f03d, "SinePower"), &sine_power);
+			
+			m_display_rotation_offset.Set(0.0f, 30.0f, 0.0f);
+			pParams->GetVector(CRCD(0x8f38f109, "RotationOffset"), &m_display_rotation_offset);
+
+			bool hold_on_last_angle = pParams->ContainsFlag(CRCD(0xe14bc9ac, "HoldOnLastAngle"));
+			
+			Tmr::Time start_time = Tmr::ElapsedTime(0);
+			
+			char flags = 0;
+			if (pParams->ContainsFlag(CRCD(0x7323e97c, "X")))
+			{
+				flags |= 1;
+				mpDisplayRotationInfo[0].SetUp(duration,
+											   start_time,
+											   start_angle,
+											   end_angle - start_angle,
+											   sine_power,
+											   hold_on_last_angle);
+			}	
+			if (pParams->ContainsFlag(CRCD(0x424d9ea, "Y")))
+			{
+				flags |= 2;
+				mpDisplayRotationInfo[1].SetUp(duration,
+											   start_time,
+											   start_angle,
+											   end_angle - start_angle,
+											   sine_power,
+											   hold_on_last_angle);
+			}	
+			if (pParams->ContainsFlag(CRCD(0x9d2d8850, "Z")))
+			{
+				flags |= 4;
+				mpDisplayRotationInfo[2].SetUp(duration,
+											   start_time,
+											   start_angle,
+											   end_angle - start_angle,
+											   sine_power,
+											   hold_on_last_angle);
+			}
+				
+			// if our object is associated with a local player, send a MsgRotateDisplay message
+			GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+			GameNet::PlayerInfo* player = gamenet_man->GetPlayerByObjectID(GetObject()->GetID());
+			if (player && player->IsLocalPlayer())
+			{
+				Net::Client* client;
+				GameNet::MsgRotateDisplay msg;
+				Net::MsgDesc msg_desc;
+				
+				client = gamenet_man->GetClient( player->GetSkaterNumber() );
+				Dbg_Assert( client );
+	
+				//msg.m_Time = client->m_Timestamp;
+				msg.m_Duration = static_cast< int >( duration );
+				msg.m_StartAngle = static_cast< short >( start_angle );
+				msg.m_DeltaAngle = static_cast< short >( end_angle - start_angle );
+				msg.m_SinePower = static_cast< int >( sine_power );
+				msg.m_ObjId = GetObject()->GetID();
+				msg.m_HoldOnLastAngle = hold_on_last_angle;
+				msg.m_Flags = flags;
+	
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgRotateDisplay );
+				msg_desc.m_Id = GameNet::MSG_ID_ROTATE_DISPLAY;
+				client->EnqueueMessageToServer( &msg_desc );
+			}
+		}
+		break;
+		
+        // @script | CancelRotateDisplay | Instantly cancels any rotation due to a RotateDisplay
+		// command.
+		case 0x4424c267: // CancelRotateDisplay
+		{
+			mpDisplayRotationInfo[0].Clear();
+			mpDisplayRotationInfo[1].Clear();
+			mpDisplayRotationInfo[2].Clear();
+				
+			// if our object is associated with a local player, send a MsgRotateDisplay message
+			GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+			GameNet::PlayerInfo* player = gamenet_man->GetPlayerByObjectID(GetObject()->GetID());
+			if (player && player->IsLocalPlayer())
+			{
+				Net::Client* client;
+				GameNet::MsgObjMessage msg;
+				Net::MsgDesc msg_desc;
+				
+				client = gamenet_man->GetClient( player->GetSkaterNumber() );
+				Dbg_Assert( client );
+	
+				//msg.m_Time = client->m_Timestamp;
+				msg.m_ObjId = GetObject()->GetID();
+	
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgObjMessage );
+				msg_desc.m_Id = GameNet::MSG_ID_CLEAR_ROTATE_DISPLAY;
+				client->EnqueueMessageToServer( &msg_desc );
+			}
+		}
+		break;
+
+		default:
+            return CBaseComponent::MF_NOT_EXECUTED;
+    }
+
+    return success ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CModelComponent::GetDebugInfo"));
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+	
+	p_info->AddChecksum(CRCD(0xc3f4169a,"filename"),mp_model->GetFileName());
+	
+	Mth::Vector scale=mp_model->GetScale();
+	p_info->AddVector(CRCD(0x13b9da7b,"scale"),scale.GetX(),scale.GetY(),scale.GetZ());
+	
+	uint32 mode=CRCD(0x52d95838,"Unknown");
+	switch (mp_model->GetRenderMode())
+	{
+		case Nx::vTEXTURED:	 	mode=CRCD(0xd939b2b6,"vTEXTURED"); break;
+		case Nx::vSKELETON:		mode=CRCD(0x22b5274d,"vSKELETON"); break;
+		case Nx::vGOURAUD:		mode=CRCD(0xe6d7f928,"vGOURAUD"); break;
+		case Nx::vFLAT:			mode=CRCD(0x3b4168d5,"vFLAT"); break;
+		case Nx::vWIREFRAME:	mode=CRCD(0xec50e3e1,"vWIREFRAME"); break;
+		case Nx::vBBOX:			mode=CRCD(0x2988b32d,"vBBOX"); break;
+		case Nx::vNONE:			mode=CRCD(0x119bc25e,"vNONE"); break;
+		default:
+			break;
+	}	
+	p_info->AddChecksum(CRCD(0x2f006949,"RenderMode"),mode);		
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Mick:  This function actually gets called TWICE when a model is created
+// Once from the call to Teleport which is called from the CCOmpositeObject::Teleport functions
+// and then once from InitModelFromProfile(), whcih is called after the model is finalized.
+// Only on the second call will the model have its geometry set up correctly.
+// This does not seem to cause problems, but should probably be re-worked for future iterations of the
+// engine.
+void CModelComponent::FinalizeModelInitialization()
+{
+	// need to synchronize rendered model's position to initial world position
+	Update();
+
+	if ( mp_model && mp_model->GetModelLights() )
+	{
+		UpdateBrightness();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::RefreshModel( Gfx::CModelAppearance* pAppearance, uint32 buildScript )
+{
+	if ( mp_model )
+	{
+		// it doesn't matter whether we use the asset manager
+		// or which texture dict offset we use, because
+		// the build script will only affect colors...
+		bool dummyUseAssetManager = true;
+		int dummyTexDictOffset = 0;
+
+		InitModelFromProfile( pAppearance, dummyUseAssetManager, dummyTexDictOffset, buildScript );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void SDisplayRotationInfo::Clear()
+{
+	SDisplayRotation *p_rotation=mpRotations;
+	for (int i=0; iClear();
+		++p_rotation;
+	}	
+	m_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void SDisplayRotationInfo::SetUp(float duration, Tmr::Time start_time, float start_angle, float change_in_angle, int sine_power, bool holdOnLastAngle)
+{
+	SDisplayRotation *p_rotation=mpRotations;
+	for (int i=0; imDispRotating)
+		{
+			p_rotation->SetUp(duration, start_time, start_angle, change_in_angle, sine_power, holdOnLastAngle);
+			m_active = true;
+			return;
+		}	
+		++p_rotation;
+	}	
+	
+	//Dbg_MsgAssert(0,("Too many rotations overlaid at once, maximum is %d",SDisplayRotationInfo::MAX_ROTATIONS));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float SDisplayRotationInfo::CalculateNewAngle()
+{
+	float new_angle=0.0f;
+	if (m_active)
+	{
+		m_active = false;
+		SDisplayRotation *p_rotation=mpRotations;
+		for (int i=0; imDispRotating)
+			{
+				m_active = true;
+				new_angle+=p_rotation->CalculateNewAngle();
+			}
+			++p_rotation;
+		}
+	}	
+	return new_angle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void SDisplayRotation::Clear()
+{
+	mDispRotating=false;
+	mHoldOnLastAngle=false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void SDisplayRotation::SetUp(float duration, Tmr::Time start_time, float start_angle, float change_in_angle, int sine_power, bool holdOnLastAngle)
+{
+	mDispRotating=true;
+	mDispDuration=duration;
+	mDispStartTime=start_time;
+	mDispStartAngle=start_angle;
+	mDispChangeInAngle=change_in_angle;
+	mDispSinePower=sine_power;
+	mHoldOnLastAngle=holdOnLastAngle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float SDisplayRotation::CalculateNewAngle()
+{
+	float new_angle=0.0f;
+	
+	if (mDispRotating)
+	{
+		float t=Tmr::ElapsedTime(0)-mDispStartTime;
+		if (t>mDispDuration)
+		{
+			if (mHoldOnLastAngle)
+			{
+				// Stick forever on the end angle, until the mHoldOnLastAngle gets reset
+				// by a CancelRotateDisplay script command.
+				new_angle=mDispStartAngle+mDispChangeInAngle;
+			}
+			else
+			{
+				mDispRotating=false;
+			}	
+		}
+		else
+		{
+			if (mDispSinePower)
+			{
+				float s;
+				if (mDispSinePower<0)
+				{
+					// If a negative sine power is specified, then use an upside-down and
+					// back-to-front sine wave. This gives a rotation which smoothly accelerates
+					// to a constant speed.
+					s=1.0f-sinf(1.570796327f-t*1.570796327f/mDispDuration);
+				}
+				else
+				{
+					s=sinf(t*1.570796327f/mDispDuration);
+					for (int i=0; iGetID()) ) );
+				  
+	return m_refObjectName;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::ApplyLightingFromCollision( CFeeler& feeler )
+{
+	if (!feeler.IsBrightnessAvailable()) return;
+	
+	if (Nx::CModelLights* p_model_lights = GetModel()->GetModelLights())
+	{
+		p_model_lights->SetBrightness(feeler.GetBrightness());
+	}
+	else
+	{
+		Nx::CLightManager::sSetBrightness(feeler.GetBrightness());
+	}
+}
+									 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelComponent::ApplySceneLighting( Image::RGBA color )
+{
+	// Mick: if the model is a level object, then apply the scene lighthing to it
+	if (m_isLevelObject)
+	{
+		mp_model->GetGeomByIndex(0)->SetColor(color);
+	}
+}
+									 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+									 
+}
+
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// GJ TODO:  these cheat codes are kind of kludgy 
+// and should be re-implemented later in a more 
+// component-friendly format.
+
+bool apply_cheat_scale( Gfx::CSkeleton* pSkeleton, Nx::CModel* pModel, uint32 cheatName, uint32 bodyShapeName, uint32 animScriptName )
+{
+	int flag = Script::GetInteger( cheatName, Script::ASSERT );
+	
+	if ( Mdl::Skate::Instance()->GetCareer()->GetGlobalFlag(flag) )
+	{
+		Dbg_Assert( pModel );
+
+		if ( animScriptName == CRCD(0x501949bd,"animload_human") 
+			 || animScriptName == CRCD(0x105f6aa1,"animload_ped_f") )
+		{
+			Dbg_MsgAssert( pSkeleton, ( "Skeleton has not been assigned to this model yet" ) );
+			Script::CStruct* pBodyShapeStructure = Script::GetStructure( bodyShapeName, Script::ASSERT );
+			
+			Mth::Vector theScale( 1.0f, 1.0f, 1.0f );
+			if ( Gfx::GetScaleFromParams( &theScale, pBodyShapeStructure ) )
+			{
+				Mth::Vector vec = pModel->GetScale();
+				
+				vec[X] *= theScale[X];
+				vec[Y] *= theScale[Y];
+				vec[Z] *= theScale[Z];
+
+				// if the body shape has a scale
+				pModel->SetScale( vec );
+			}
+
+			pSkeleton->ApplyBoneScale( pBodyShapeStructure );
+		}
+	
+		return true;
+	}
+
+	return false;
+}
+#endif
diff --git a/Code/Gel/Components/modelcomponent.h b/Code/Gel/Components/modelcomponent.h
new file mode 100644
index 0000000..f35c60c
--- /dev/null
+++ b/Code/Gel/Components/modelcomponent.h
@@ -0,0 +1,197 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ModelComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_MODELCOMPONENT_H__
+#define __COMPONENTS_MODELCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_MODEL CRCD(0x286a8d26,"Model")
+#define		GetModelComponent() ((Obj::CModelComponent*)GetComponent(CRC_MODEL))
+#define		GetModelComponentFromObject(pObj) ((Obj::CModelComponent*)(pObj)->GetComponent(CRC_MODEL))
+
+class CFeeler;
+		
+namespace Gfx
+{
+	class CModelAppearance;
+}
+
+namespace Nx
+{
+    class CModel;
+}
+
+namespace Script
+{
+	class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+	class CSkeletonComponent;
+	class CAnimationComponent;
+	class CSuspendComponent;
+
+
+struct SDisplayRotation
+{
+	bool mDispRotating;
+	bool mHoldOnLastAngle;
+	float mDispDuration;
+	Tmr::Time mDispStartTime;
+	float mDispStartAngle;
+	float mDispChangeInAngle;
+	int	mDispSinePower;
+	
+	void SetUp(float duration, Tmr::Time start_time, float start_angle, float change_in_angle, int sine_power, bool holdOnLastAngle);
+	float CalculateNewAngle();
+	void Clear();
+};
+
+struct SDisplayRotationInfo
+{
+	// Factored out the data members into the SDisplayRotation structure so that
+	// there can be 6 operating at once. Needed by Zac for create-a-trick.
+	enum
+	{
+		MAX_ROTATIONS=6
+	};
+	SDisplayRotation mpRotations[MAX_ROTATIONS];
+	bool	m_active;
+	
+	void SetUp(float duration, Tmr::Time start_time, float start_angle, float change_in_angle, int sine_power, bool holdOnLastAngle);
+	float CalculateNewAngle();
+	void Clear();
+};
+
+class CModelComponent : public CBaseComponent
+{
+public:
+    CModelComponent();
+    virtual	~CModelComponent();
+
+public:
+    virtual void    		Update();
+    virtual void    		InitFromStructure( Script::CStruct* pParams );
+    virtual void			Hide( bool should_hide );
+    virtual void			Finalize();
+	virtual void			Teleport();
+
+    EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+	// Used by the script debugger code to fill in a structure
+	// for transmitting to the monitor.exe utility running on the PC.
+	virtual void			GetDebugInfo(Script::CStruct *p_info);
+	
+	static CBaseComponent*	s_create();
+
+public:
+	void					InitModel( Script::CStruct* pParams );
+	virtual void			InitModelFromProfile( Gfx::CModelAppearance* pAppearance, bool useAssetManager, uint32 texDictOffset, uint32 buildScript = 0 );
+	void 					FinalizeModelInitialization();
+	void					RefreshModel( Gfx::CModelAppearance* pAppearance, uint32 buildScript );
+    Nx::CModel*     		GetModel();
+	bool					HideGeom( uint32 geomName, bool hidden, bool propagate );
+    bool					GeomHidden( uint32 geomName );
+	void					SetModelLODDistance( int lodIndex, float distance );
+	void					SetBoundingSphere( const Mth::Vector& newSphere );
+	void					ApplyLightingFromCollision ( CFeeler& feeler );
+	void					ApplySceneLighting(Image::RGBA color);
+	void					UpdateBrightness();
+	// K: This used to be protected, but I made it public for use by CLockObjComponent::LockToObject
+	void					GetDisplayMatrixWithExtraRotation( Mth::Matrix& displayMatrix );
+	
+	void					SetDisplayOffset ( const Mth::Vector& display_offset ) { mDisplayOffset = display_offset; }
+	
+protected:
+	void 					init_model_from_level_object( uint32 checksumName );
+	bool					enable_lod(uint32 componentName, float distance);
+
+
+protected:
+	// Sometimes we need to temporarily change the bounding sphere
+	// of an object (like when an animation takes a model far
+	// from its original bouding sphere)...  the following
+	// is used to correctly restore the original bounding sphere
+	Mth::Vector				m_original_bounding_sphere;
+
+
+// the following are used for tweaking the display matrix
+// right before the rendered model actually uses it
+// public during transition
+public:
+	
+	// The offset relative to the skater's origin about which the display rotation rotates.
+	Mth::Vector 			m_display_rotation_offset;
+	
+	
+	// Stuff for rotating just the display matrix, so that
+	// the physics and camera don't notice it.
+	SDisplayRotationInfo 	mpDisplayRotationInfo[3];
+
+	// Controlled by the script commands EnableDisplayFlip and DisableDisplayFlip.
+	// Added to allow the lip script to finish its out-anim whilst on the ground without a glitch.
+	// When set, this will cause the display matrix to be the regular matrix flipped, each frame.
+	bool					mFlipDisplayMatrix;
+	
+	// Offset from object's position at which the model is drawn.  Defaults to zero.
+	Mth::Vector				mDisplayOffset;
+
+public:
+	enum
+	{
+		vNUM_LODS = 4
+	};
+
+protected:
+    Nx::CModel*    		 	mp_model;
+	CSkeletonComponent*		mp_skeleton_component;
+	CAnimationComponent*	mp_animation_component;
+	CSuspendComponent*		mp_suspend_component;
+
+protected:
+	float				m_LODdist[vNUM_LODS];
+	int					m_numLODs;
+
+	// GJ:  ref objects are used by the cutscene code
+	// for stealing another object's model
+	// (maybe cutscene objects should just have
+	// CRefModelComponent?)
+public:
+	uint32				GetRefObjectName();
+	bool				HasRefObject()
+	{
+		return m_hasRefObject;
+	}
+
+protected:
+	uint32				m_refObjectName;
+	bool				m_hasRefObject;
+	
+private:
+	bool				m_isLevelObject;	// True if it's a level object	
+	
+};
+
+inline Nx::CModel* CModelComponent::GetModel()
+{
+    return mp_model;
+}
+
+}
+
+#endif
diff --git a/Code/Gel/Components/motioncomponent.cpp b/Code/Gel/Components/motioncomponent.cpp
new file mode 100644
index 0000000..83cceab
--- /dev/null
+++ b/Code/Gel/Components/motioncomponent.cpp
@@ -0,0 +1,2731 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       MotionComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/5/2002
+//****************************************************************************
+
+// start autoduck documentation
+// @DOC motioncomponent
+// @module motioncomponent | None
+// @subindex Scripting Database
+// @index script | motioncomponent
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+									
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+enum
+{
+	UNITS_IPS,
+	UNITS_FPS,
+	UNITS_MPH,
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CMotionComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CMotionComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CMotionComponent::CMotionComponent() : CBaseComponent()
+{
+	SetType( CRC_MOTION );
+
+	mp_pathOb = NULL;
+	
+	mp_path_object_tracker = NULL;
+
+	m_point_stick_to_ground = true;
+
+	Reset();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CMotionComponent::~CMotionComponent()
+{
+	if ( mp_pathOb )
+	{
+		delete mp_pathOb;
+		mp_pathOb = NULL;
+	}
+
+	// Note: No need to unregister from mp_path_object_tracker, because the path tracker
+	// uses smart pointers.
+	
+	if ( mp_slerp )
+	{
+		delete mp_slerp;
+		mp_slerp = NULL;
+	}
+	
+	if (mp_follow_ob)
+	{
+		delete mp_follow_ob;
+		mp_follow_ob=NULL;
+	}	
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CMotionComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	int nodeNum = -1;
+	pParams->GetInteger( CRCD(0xe50d6573,"nodeIndex"), &nodeNum, Script::NO_ASSERT );
+	m_start_node = m_current_node = nodeNum;
+	OrientToNode(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CMotionComponent::ProcessWait( Script::CScript * pScript )
+{
+	switch (pScript->GetWaitType())
+	{
+		case Script::WAIT_TYPE_OBJECT_MOVE:
+			if ( !IsMoving() )
+			{
+				pScript->ClearWait();
+			}
+			break;
+		
+		case Script::WAIT_TYPE_OBJECT_JUMP_FINISHED:
+			if ( !(m_movingobj_status & MOVINGOBJ_STATUS_JUMPING) )
+			{
+				pScript->ClearWait();
+			}
+			break;
+
+		case Script::WAIT_TYPE_OBJECT_ROTATE:
+			if ( !IsRotating() )
+			{
+				pScript->ClearWait();
+			}
+			break;
+		case Script::WAIT_TYPE_OBJECT_STOP_FINISHED:
+			if ( !( m_vel_z > 0.0f ) )
+			{
+				pScript->ClearWait();
+			}
+			break;
+		default:
+			Dbg_MsgAssert(0,("\n%s\nWait type of %d not supported by CMotionComponent",pScript->GetScriptInfo(),pScript->GetWaitType()));
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CMotionComponent::Update()
+{
+
+	m_time = Tmr::FrameLength();	
+	
+	if ( m_movingobj_status & MOVINGOBJ_STATUS_HOVERING )
+	{
+		// Having these functions next to each other here does mean that objects cannot be
+		// moving and hovering at the same time, but currently nothing requires that.
+		UndoHover();
+		ApplyHover();
+	}
+
+	if ( m_movingobj_status & MOVINGOBJ_STATUS_ROTXYZ )
+	{
+		RotUpdate( );
+	}
+	
+	if ( m_movingobj_status & MOVINGOBJ_STATUS_QUAT_ROT )
+	{
+		QuatRot( );
+	}
+
+	if ( m_movingobj_status & MOVINGOBJ_STATUS_MOVETO )
+	{
+		if ( Move( ) )
+		{
+			m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
+		}
+	}
+	else if ( m_movingobj_status & MOVINGOBJ_STATUS_ON_PATH )
+	{
+		FollowPath( );
+	}
+	else if ( m_movingobj_status & MOVINGOBJ_STATUS_FOLLOWING_LEADER )
+	{
+		Dbg_MsgAssert( !(m_movingobj_status & MOVINGOBJ_STATUS_QUAT_ROT), ("Cannot have object rotating when it is following a leader"));
+		Dbg_MsgAssert( !(m_movingobj_status & MOVINGOBJ_STATUS_ROTXYZ), ("Cannot have object rotating when it is following a leader"));
+		FollowLeader( );
+	}
+
+	if ( m_movingobj_status & MOVINGOBJ_STATUS_JUMPING )
+	{
+		DoJump();
+		GetObject()->m_pos = m_jump_pos;
+		// m_pos[Y]=m_jump_y;
+	}
+
+
+	// TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
+	// BIT OF A PATCH, as the skater has a motion component
+	// but we don't want to set his display matrix directly here.
+	if (GetObject()->GetID() > 15)
+	{
+		GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMotionComponent::IsMoving()
+{   
+	return ( m_movingobj_status & ( MOVINGOBJ_STATUS_ON_PATH | MOVINGOBJ_STATUS_MOVETO ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMotionComponent::IsRotating()
+{	
+	return ( m_movingobj_status &  ( MOVINGOBJ_STATUS_ROTXYZ | MOVINGOBJ_STATUS_QUAT_ROT ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void s_get_velocity( Script::CStruct* pParams, float* pVel )
+{
+	if ( pParams->GetFloat( NONAME, pVel ) )
+	{
+		// default is mph:
+		if ( pParams->ContainsFlag( 0xaad7c80f ) ) // 'fps'
+		{
+			*pVel = FEET_TO_INCHES( *pVel );
+		}
+		else if ( !( pParams->ContainsFlag( 0xa18b8f32 ) ) ) // 'ips'
+		{
+			// miles per hour then...
+			*pVel = MPH_TO_IPS( *pVel );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void s_get_acceleration( Script::CStruct* pParams, float* pAccel )
+{
+	if ( pParams->GetFloat( NONAME, pAccel ) )
+	{
+		// default is mphps:
+		if ( pParams->ContainsFlag( 0xf414a5ea ) ) // 'fpsps'
+		{
+			*pAccel = FEET_TO_INCHES( *pAccel );
+		}
+		else if ( !( pParams->ContainsFlag( 0x7644323b ) ) ) // 'ipsps'
+		{
+			// miles per hour per second then...
+			*pAccel = MPH_TO_IPS( *pAccel );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::UndoHover( void )
+{   
+	Mth::Vector pos=GetObject()->GetPos();
+	pos[Y]=m_y_before_applying_hover;
+	GetObject()->SetPos(pos);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::ApplyHover( void )
+{   
+	uint32 t=Tmr::ElapsedTime(0)%m_hover_period;
+	m_hover_offset=m_hover_amp*sinf(t*2*3.141592653f/m_hover_period);
+	Mth::Vector pos=GetObject()->GetPos();
+	m_y_before_applying_hover=pos[Y];
+	pos[Y]+=m_hover_offset;
+	GetObject()->SetPos(pos);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMotionComponent::IsHovering( void )
+{
+	return (m_movingobj_status & MOVINGOBJ_STATUS_HOVERING) && 
+			// If it is doing a Quat rot, then pretend it is not hovering.
+			// This is so that the rocking buoy in the shipyard is not indicated as
+			// being hovering, otherwise the replay code will show it as just hovering
+			// up and down. The replay code optimizes hovering objects by not recording their
+			// precise positions and orientations every frame. We need that for the buoy though.
+		   !(m_movingobj_status &  (MOVINGOBJ_STATUS_QUAT_ROT));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::GetHoverOrgPos( Mth::Vector* p_orgPos )
+{
+	Dbg_MsgAssert(p_orgPos,("NULL p_orgPos"));
+	Mth::Vector pos=GetObject()->GetPos();
+	p_orgPos->Set(pos[X],m_y_before_applying_hover,pos[Z]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::EMemberFunctionResult CMotionComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | Obj_IsMoving | true if moving
+		case 0x21accf0d: 		// Obj_IsMoving
+			return ( IsMoving() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE );
+			break;
+	
+        // @script | Obj_IsRotating | true if rotating
+		case 0x7bc461ae: 		// Obj_IsRotating
+			return ( IsRotating() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE );
+			break;
+
+        // @script | Obj_StorePos | stores the current position
+		case 0x8e52abea: 		// Obj_StorePos
+			m_stored_pos = GetObject()->GetPos();
+			break;
+
+        // @script | Obj_StoreNode | stores the next waypoint in the path
+		case 0xc8cc6820: 		// Obj_StoreNode
+			Dbg_MsgAssert( GetPathOb(),( "\n%s\nObject trying to store waypoint when not on a path.", pScript->GetScriptInfo( ) ));
+			m_stored_node = GetPathOb()->m_node_to;
+			break;
+
+        // @script | Obj_StopMoving |
+		case 0x68e1f022: 		// Obj_StopMoving
+			m_movingobj_status &= ~( MOVINGOBJ_STATUS_MOVETO | MOVINGOBJ_STATUS_ON_PATH );
+			break;
+
+        // @script | Obj_StopRotating |
+		case 0xdfbe3db3: 		// Obj_StopRotating
+			m_movingobj_status &= ~( MOVINGOBJ_STATUS_ROTX | MOVINGOBJ_STATUS_ROTY |
+				MOVINGOBJ_STATUS_ROTZ | MOVINGOBJ_STATUS_QUAT_ROT | MOVINGOBJ_STATUS_LOOKAT );
+			break;
+
+        // @script | Obj_SetPathVelocity | 
+        // @uparm 1.0 | velocity
+        // @flag mph | default units
+        // @flag ips |
+        // @flag fps | 
+		case 0xfb603ea9:		// Obj_SetPathVelocity
+			s_get_velocity( pParams, &m_max_vel );
+			break;
+		
+        // @script | Obj_SetPathMinStopVel | Speed at which friction
+        // overcomes momentum and the object stops. If left at zero,
+        // the object will take forever to stop... A good value for
+        // a car is 1 or 2 mph. Pedestrians are more 'sticky': make
+        // it 3 or 4 mph for them. Only used if you call Obj_StopAlongPath
+        // @uparm 1.0 | velocity
+        // @flag mph | default units
+        // @flag ips |
+        // @flag fps | 
+		case 0x2ca63cdf:		// Obj_SetPathMinStopVel
+			s_get_velocity( pParams, &m_min_stop_vel );
+			break;
+		
+        // @script | Obj_SetPathAcceleration | used for when the object
+        // is stopped, and needs to get up to speed (set by Obj_SetVelocity).
+        // @uparm 1.0 | acceleration
+        // @flag mphps | default units
+        // @flag ipsps |
+        // @flag fpsps | 
+		case 0xd6697ad0:		// Obj_SetPathAcceleration
+			s_get_acceleration( pParams, &m_acceleration );
+			break;
+
+        // @script | Obj_SetPathDeceleration | used if the velocity is
+        // decreased while the object is traveling, to slow down to the new speed
+        // @uparm 1.0 | acceleration
+        // @flag mphps | default units
+        // @flag ipsps |
+        // @flag fpsps | 		
+		case 0xa67fc783:		// Obj_SetPathDeceleration
+			s_get_acceleration( pParams, &m_deceleration );
+			break;
+		
+        // @script | Obj_Rotate | rotate object
+        // @parmopt float | time | 0.0 | in seconds - if not included, instant rotation
+        // @parmopt vector | relative | (0, 0, 0) | rotate relative to the object
+        // @parmopt vector | absolute | (0, 0, 0) | rotate oriented along world axes
+        // @flag FLAG_MAX_COORDS | with this flag, coordinates will be converted to 
+        // correspond to the axes in Max ( Z up, -Y forward, and X right. )
+		case 0x5cf1b7d4:  // "Obj_Rotate"
+			QuatRot_Init( pParams );
+			break;
+		
+        // @script | Obj_RotX | rotate about x axis
+        // @parmopt float | angle | | If not specified, object will rotate forever
+        // @parmopt float | speed | | Speed of rotation in degrees per second. 
+        // Required unless deltaROT is ZERO
+        // @parmopt float | acceleration | | Acceleration in degrees per
+        // second per second.  If not set, velocity is set instantly
+        // @parmopt float | deceleration | | If specified, velocity is set instantly.
+        // Deceleration in degrees per
+        // second per second.  Target stops rotating when it reaches the target
+        // angle specified, or when speed reaches ZERO
+        // @flag FLAG_MAX_COORDS | With this flag, coordinates will be converted to 
+        // correspond to the axes in Max ( Z up, -Y forward, and X right. )
+		case 0x0b231a9d:  // "Obj_RotX"
+			RotAxis_Init( pParams, pScript, X );
+			break;
+
+        // @script | Obj_RotY | rotate about Y axis
+        // @parmopt float | angle | | If not specified, object will rotate forever
+        // @parmopt float | speed | | Speed of rotation in degrees per second. 
+        // Required unless deltaROT is ZERO
+        // @parmopt float | acceleration | | Acceleration in degrees per
+        // second per second.  If not set, velocity is set instantly
+        // @parmopt float | deceleration | | If specified, velocity is set instantly.
+        // Deceleration in degrees per
+        // second per second.  Target stops rotating when it reaches the target
+        // angle specified, or when speed reaches ZERO
+        // @flag FLAG_MAX_COORDS | With this flag, coordinates will be converted to 
+        // correspond to the axes in Max ( Z up, -Y forward, and X right. )
+		case 0x7c242a0b:  // "Obj_RotY"
+			RotAxis_Init( pParams, pScript, Y );
+			break;
+
+        // @script | Obj_RotZ | rotate about z axis
+        // @parmopt float | angle | | If not specified, object will rotate forever
+        // @parmopt float | speed | | Speed of rotation in degrees per second. 
+        // Required unless deltaROT is ZERO
+        // @parmopt float | acceleration | | Acceleration in degrees per
+        // second per second.  If not set, velocity is set instantly
+        // @parmopt float | deceleration | | If specified, velocity is set instantly.
+        // Deceleration in degrees per
+        // second per second.  Target stops rotating when it reaches the target
+        // angle specified, or when speed reaches ZERO
+        // @flag FLAG_MAX_COORDS | With this flag, coordinates will be converted to 
+        // correspond to the axes in Max ( Z up, -Y forward, and X right. )
+		case 0xe52d7bb1:  // "Obj_RotZ"
+			RotAxis_Init( pParams, pScript, Z );
+			break;
+
+        // @script | Obj_PathHeading | Defaults to on. Sets the
+        // heading of the object in the direction of the path
+        // @flag off | use to set off
+		case 0xd8f4bc32:  // "Obj_PathHeading"
+			if ( pParams->ContainsFlag( 0xd443a2bc ) ) // "off"
+			{
+				m_movingobj_flags |= MOVINGOBJ_FLAG_INDEPENDENT_HEADING;
+			}
+			else
+			{
+				m_movingobj_flags &= ~MOVINGOBJ_FLAG_INDEPENDENT_HEADING;
+			}
+			break;
+
+        // @Script | Obj_SetConstantHeight | sets object height to whatever
+        // height the object is currently at
+        // @flag off | turn it off (default is on)
+		case 0xaa3cb476:  // "Obj_SetConstantHeight"
+			if ( pParams->ContainsFlag( 0xd443a2bc ) ) // "off"
+			{
+				m_movingobj_flags &= ~MOVINGOBJ_FLAG_CONSTANT_HEIGHT;
+			}
+			else
+			{
+				m_movingobj_flags |= MOVINGOBJ_FLAG_CONSTANT_HEIGHT;
+				pParams->GetFloat( NONAME, &GetObject()->m_pos[ Y ] );
+			}
+			break;
+
+        // @script | Obj_GetNextObjOnPath | Searches forward along the path the object is on for a max
+		// of Range feet, and if another object on the path is encountered, its name is put into a parameter
+		// called Ob. If no object is found, no Ob parameter will be created, and if it existed before, it
+		// will be removed.
+        // @parmopt float | Range | 0.0 | Distance to search, in feet
+		case 0xb75589ce: // Obj_GetNextObjOnPath	
+		{	
+			float range=100.0f;
+			pParams->GetFloat(CRCD(0x6c78a5b6,"Range"),&range);
+			range*=12.0f;
+			
+			pScript->GetParams()->RemoveComponent( CRCD(0xffff9a1c,"Ob") );
+			
+			CCompositeObject* p_closest_ob = GetNextObjectOnPath( range );
+
+			if ( p_closest_ob )
+				pScript->GetParams()->AddChecksum( CRCD(0xffff9a1c,"Ob"), p_closest_ob->GetID() );			
+			break;
+		}
+
+        // @script | Obj_GetRandomLink | Chooses a random node that the object's current node is 
+		// linked to, and puts its link number into a parameter called Link.
+		// If the object's current node is not linked to anything, no Link parameter will be created.
+		case 0x6866312f: // Obj_GetRandomLink
+		{
+			uint32 num_links = SkateScript::GetNumLinks( m_current_node );
+			if (num_links)
+			{
+				// Note: The +1 is because we're using the convention of numbering the
+				// links from 1 to n rather than 0 to n-1
+				pScript->GetParams()->AddInteger(CRCD(0xc953660e,"Link"),Mth::Rnd(num_links)+1);
+			}
+			break;
+		}
+			
+        // @script | Obj_RandomPathMode | Causes Obj_FollowPath commands to
+        // pick random links as moving objects traverse paths
+        // @flag off | use to turn off - default is on
+		case 0x434ea1fb:	// Obj_RandomPathMode
+			if ( pParams->ContainsFlag( 0xd443a2bc ) ) // "off"
+			{
+				m_movingobj_flags &= ~MOVINGOBJ_FLAG_RANDOM_PATH_MODE;
+			}
+			else
+			{
+				m_movingobj_flags |= MOVINGOBJ_FLAG_RANDOM_PATH_MODE;
+			}
+			break;
+
+        // @script | Obj_SetPathTurnDist | The distance from a waypoint 
+        // at which the object starts turning
+        // @uparm 1.0 | distance in feet
+        // @flag inches | use units of inches for distance
+		case 0x76003d82: // "Obj_SetPathTurnDist"
+		{
+			float enterTurnDist;
+			if ( pParams->GetFloat( NONAME, &enterTurnDist ) )
+			{
+				if ( !pParams->ContainsFlag( 0xf915b4f3 ) ) // "inches"
+				{
+					enterTurnDist = FEET_TO_INCHES( enterTurnDist );
+				}
+				EnsurePathobExists( GetObject() );
+				GetPathOb()->m_enter_turn_dist = enterTurnDist;
+			}
+			break;
+		}
+				
+        // @script | Obj_FollowPath | Must also have Obj_SetVelocity set.
+        // If the node has no links, the object will move to the node and
+        // stop. Linked in a circle: object follows loop until stopped.
+        // Linked to a finite path: walks to the end of the path. Returns
+        // true for Obj_IsMoving until path complete. If used in conjunction
+        // with Obj_MoveTo functions, will turn moveto function off, and
+        // vice versa
+        // @parm name | Name | name of node at beginning of path
+		case 0xbfb0031d:  // "Obj_FollowPath"
+			m_movingobj_flags &= ~MOVINGOBJ_FLAG_STOP_ALONG_PATH;
+			FollowPath_Init( pParams );
+			break;
+
+        // @script | Obj_LookAtNode | 
+        // @parm name | Name | the name of the node you want to look at
+        // @parmopt name | lockAxis | | can lock any two axes (LOCK_X, LOCK_Y, LOCK_Z)
+        // @parmopt float | time | vel/acceleration | in seconds
+        // @parmopt float | speed | 360  | angular velocity in degrees per second
+        // @parmopt float | acceleration | 0 | angular acceleration in degrees per second
+        // per second 
+        // @flag FLAG_MAX_COORDS | With this flag, coordinates will be converted to 
+        // correspond to the axes in Max ( Z up, -Y forward, and X right. )
+		case 0x103bd253:  // "Obj_LookAtNode"
+			LookAtNode_Init( pParams, pScript );
+			break;
+
+		// @script | Obj_LookAtNodeLinked | looks at the next node
+        // @parmopt int | linkNum | 1 | The link to look at.
+		case 0x6419ceff: // "Obj_LookAtNodeLinked"
+			LookAtNodeLinked_Init( pParams, pScript );
+			break;
+
+        // @script | Obj_LookAtNodeStored | looks at stored node
+        // @parmopt name | lockAxis | | can lock any two axes (LOCK_X, LOCK_Y, LOCK_Z)
+        // @parmopt float | time | vel/acceleration | in seconds
+        // @parmopt float | speed | 360  | angular velocity in degrees per second
+        // @parmopt float | acceleration | 0 | angular acceleration in degrees per second
+        // per second 
+		// @parmopt float | AngleThreshold | 0 | If the object is already looking within this
+		// many degrees of the correct direction, it will not bother turning.
+        // @flag FLAG_MAX_COORDS | With this flag, coordinates will be converted to 
+        // correspond to the axes in Max ( Z up, -Y forward, and X right. )
+		case 0xa5a2d218: // "Obj_LookAtNodeStored"
+		{
+			Mth::Vector lookAtPos;
+			SkateScript::GetPosition( m_stored_node, &lookAtPos );
+			LookAt_Init( pParams, lookAtPos );
+			break;
+		}
+
+        // @script | Obj_LookAtPos |
+        // @uparm (0, 0, 0) | position vector
+        // @parmopt name | lockAxis | | can lock any two axes (LOCK_X, LOCK_Y, LOCK_Z)
+        // @parmopt float | time | vel/acceleration | in seconds
+        // @parmopt float | speed | 360  | angular velocity in degrees per second
+        // @parmopt float | acceleration | 0 | angular acceleration in degrees per second
+        // per second
+		case 0x74c30242:  // "Obj_LookAtPos"
+			LookAtPos_Init( pParams, true );
+			break;
+
+        // @script | Obj_LookAtPosStored | look at stored pos
+        // @parmopt name | lockAxis | | can lock any two axes (LOCK_X, LOCK_Y, LOCK_Z)
+        // @parmopt float | time | vel/acceleration | in seconds
+        // @parmopt float | speed | 360  | angular velocity in degrees per second
+        // @parmopt float | acceleration | 0 | angular acceleration in degrees per second
+        // per second
+		// @parmopt float | AngleThreshold | 0 | If the object is already looking within this
+		// many degrees of the correct direction, it will not bother turning.
+		case 0xe8d121cb: // "Obj_LookAtPosStored"
+			LookAt_Init( pParams, m_stored_pos );
+			break;
+
+		// @script | Obj_LookAtRelPos | look at position relative to object position
+        // @uparm (0, 0, 0) | relative position vector
+        // @parmopt name | lockAxis | | can lock any two axes (LOCK_X, LOCK_Y, LOCK_Z)
+        // @parmopt float | time | vel/acceleration | in seconds
+        // @parmopt float | speed | 360  | angular velocity in degrees per second
+        // @parmopt float | acceleration | 0 | angular acceleration in degrees per second
+        // per second
+        case 0x33df698f:  // "Obj_LookAtRelPos"
+			LookAtPos_Init( pParams, false );
+			break;
+
+        // @script | Obj_StopAlongPath | accurately stop and object
+        // @uparm 1.0 | distance in feet.  Note: doesn't use deceleration
+        // set in Obj_SetDeceleration
+		case 0xfe71453f:  // "Obj_StopAlongPath"
+			m_stop_dist = 0;
+			m_stop_dist_traveled = 0;
+			if ( !m_min_stop_vel )
+			{
+				m_min_stop_vel = 15;
+			}
+			pParams->GetFloat( NONAME, &m_stop_dist );
+			if ( !pParams->ContainsFlag( 0xf915b4f3 ) ) // inches
+			{
+				m_stop_dist = FEET_TO_INCHES( m_stop_dist );
+				m_vel_z_start = m_vel_z;
+			}
+			if ( !m_stop_dist )
+			{
+				m_vel_z = 0;
+			}
+			m_movingobj_flags |= MOVINGOBJ_FLAG_STOP_ALONG_PATH;
+			break;
+		
+        // @script | Obj_StartAlongPath | Only needs to be used if you've
+        // stopped an object along a path, to restart the object moving. 
+		case 0xbd6c6807:  // "Obj_StartAlongPath"
+			m_movingobj_flags &= ~MOVINGOBJ_FLAG_STOP_ALONG_PATH;
+			break;
+		
+        // @script | Obj_FollowPathLinked | Will cause the object to
+        // follow the path that it is linked to.
+        // When calling Obj_FollowPathLinked, you can add an
+        // optional parameter: 
+        // originalNode 
+        // to follow a linked path to the node that created the object. 
+        // If random mode is on, a random link from the original
+        // node will be used
+        // @flag originalNode | follow a linked path to the node that 
+        // created the object
+		case 0x0da2d86d:  // "Obj_FollowPathLinked"
+			m_movingobj_flags &= ~MOVINGOBJ_FLAG_STOP_ALONG_PATH;
+			FollowPathLinked_Init( pParams, pScript );
+			break;
+
+        // @script | Obj_FollowPathStored | Follow the path from current
+        // position to the position of the waypoint last stored
+        // in the script call to Obj_StoreNode (when a pedestrian, for example, has to jump
+        // out of the way of the player, he stores his current position and the current node
+        // that he's heading for... then he walks back to his position, and finally ends up
+        // heading to the waypoint he was heading for before he left the path to jump out of the way).
+		case 0xcc19c48a: // "Obj_FollowPathStored"
+			FollowStoredPath_Init( pParams );
+			break;
+
+        // @script | Obj_SetGroundOffset | 
+		case 0xbf9fffe5: // "Obj_SetGroundOffset"
+			Dbg_Message( "Obj_SetGroundOffset is obsolete!" );
+            break;
+		
+        // @script | Obj_MoveToPosStored | move to world position stored
+        // using Obj_StorePos
+        // @parmopt float | time | | default unit is milliseconds
+        // @parmopt float | speed | | used instead of time and acceleration
+        // @parmopt float | acceleration | | change in speed per second
+		case 0x8b669799: // "Obj_MoveToPosStored"
+			Move_Init( pParams, pScript, m_stored_pos );
+			break;
+
+        // @script | Obj_MoveForward | Moves a distance in the direction the object is facing.
+        // @parmopt float | dist | | Distance to move, in inches.
+        // @parmopt float | time | | default unit is milliseconds
+        // @parmopt float | speed | | used instead of time and acceleration
+        // @parmopt float | acceleration | | change in speed per second
+		case 0xf389285d: // Obj_MoveForward
+		{
+			float dist=0.0f;
+			pParams->GetFloat(CRCD(0x7e832f08,"Dist"),&dist);
+			Move_Init( pParams, pScript, GetObject()->GetPos() + GetObject()->m_matrix[Z] * dist );	 
+			break;
+		}
+
+        // @script | Obj_MoveLeft | Moves a distance at 90 degrees to the direction the object is facing.
+        // @parmopt float | dist | | Distance to move, in inches.
+        // @parmopt float | time | | default unit is milliseconds
+        // @parmopt float | speed | | used instead of time and acceleration
+        // @parmopt float | acceleration | | change in speed per second
+		case 0x5ce058fd: // Obj_MoveLeft
+		{
+			float dist=0.0f;
+			pParams->GetFloat(CRCD(0x7e832f08,"Dist"),&dist);
+			Move_Init( pParams, pScript, GetObject()->GetPos() + GetObject()->m_matrix[X] * dist );	 
+			break;
+		}
+			
+        // @script | Obj_MoveToNode | move to any node by node name
+        // @parm name | name | node name
+        // @parmopt float | time | | default unit is milliseconds
+        // @parmopt float | speed | | used instead of time and acceleration
+        // @flag mph | time in mph
+        // @flag IPS | time in IPS
+        // @flag fps | time in fps
+        // @parmopt float | acceleration | | change in speed per second
+		case 0x8819dd8b:  // "Obj_MoveToNode"
+			MoveToNode_Init( pParams, pScript );
+			break;
+			
+        // @script | Obj_MoveToPos | moves to the world position specified
+        // by the vector
+        // @uparm (0, 0, 0) | position vector 
+        // @parmopt float | time | | default unit is milliseconds
+        // @parmopt float | speed | | used instead of time and acceleration
+        // @parmopt float | acceleration | | change in speed per second
+		case 0x84ec6600:  // "Obj_MoveToPos"
+			MoveToPos_Init( pParams, pScript, true );
+			break;
+
+        // @script | Obj_MoveToRelPos | moves to a position relative to the object
+        // @uparm (0, 0, 0) | position vector 
+        // @parmopt float | time | | default unit is milliseconds
+        // @parmopt float | speed | | used instead of time and acceleration
+        // @parmopt float | acceleration | | change in speed per second
+		case 0xea81a32b:  // "Obj_MoveToRelPos"
+			MoveToPos_Init( pParams, pScript, false );
+			break;
+		
+        // @script | Obj_MoveToLink | Moves to the 1st node that this object is linked to 
+        // @parmopt float | time | | default unit is milliseconds
+        // @parmopt float | speed | | used instead of time and acceleration
+        // @parmopt float | acceleration | | change in speed per second
+		// @parm int | LinkNum | link number
+        case 0x3bcaac3f:  // "Obj_MoveToLink"
+			MoveToLink_Init( pParams, pScript );
+			break;
+	
+        // @script | Obj_StickToGround | 
+        // @flag on | turn on (optional - default value is on)
+        // @flag off | turn off (default is on)
+        // @parm float | distAbove | distance above the object to check
+        // for collision
+        // @parm float | distBelow | distance below the object to check
+        // for collision
+		case 0xc5eca638:  // "Obj_StickToGround"
+			pParams->GetFloat( 0xa8a5a3c7, &m_col_dist_above ); // "distAbove"
+			pParams->GetFloat( 0x182d87a7, &m_col_dist_below ); // "distBelow"
+			if ( pParams->ContainsFlag( 0xd443a2bc ) ) // "off"
+			{
+				m_movingobj_flags &= ~MOVINGOBJ_FLAG_STICK_TO_GROUND;
+			}
+			else
+			{
+				m_movingobj_flags |= MOVINGOBJ_FLAG_STICK_TO_GROUND;
+				if ( ( m_col_dist_above == 0 ) && ( m_col_dist_below == 0 ) )
+				{
+					Dbg_Message( "\n%s\nTrying to stick to the ground, distAbove and distBelow are zero!!!", pScript->GetScriptInfo() );
+					return CBaseComponent::MF_FALSE;
+				}
+				StickToGround();
+			}
+			break;
+
+        // @script | Obj_WaitJumpFinished | Waits for jump to finish, which is when the object 
+		// has fallen to the same height it was at when the Obj_Jump command was issued.
+		case 0x1e094b84:  // Obj_WaitJumpFinished
+		{
+			pScript->SetWait(Script::WAIT_TYPE_OBJECT_JUMP_FINISHED,this);
+			break;
+		}
+
+        // @script | Obj_WaitRotate | wait for rotation to complete
+		case 0xbdc4c878:  // Obj_WaitRotate
+			pScript->SetWait(Script::WAIT_TYPE_OBJECT_ROTATE,this);
+			break;
+
+        // @script | Obj_WaitMove | use to wait for move to complete
+		case 0xdbfdd02f:  // Obj_WaitMove
+			pScript->SetWait(Script::WAIT_TYPE_OBJECT_MOVE,this);
+			break;
+
+		// @script | Obj_WaitStop | use to wait for the object to come to a complete stop
+		case CRCC( 0x8d95f1e1, "Obj_WaitStop" ):
+			pScript->SetWait(Script::WAIT_TYPE_OBJECT_STOP_FINISHED,this);
+			break;
+
+		// @script | Obj_SkipToRestart | skips the object to a restart point
+		case 0x3407ad3d: // Obj_SkipToRestart
+		{
+			uint32 node_name=0;
+			Mth::Vector node_pos;
+			
+			pParams->GetChecksum(NONAME,&node_name);
+			int node_index = SkateScript::FindNamedNode(node_name);
+			Script::CStruct* p_node_data = SkateScript::GetNode(node_index);
+			
+			SkateScript::GetPosition(p_node_data, &node_pos);
+			GetObject()->SetPos( node_pos );
+			this->OrientToNode(p_node_data);
+		
+			// refresh the model with the new pos/orientation
+			Obj::CModelComponent* pModelComponent = (Obj::CModelComponent*)GetModelComponentFromObject( GetObject() );
+			pModelComponent->Update();
+		}
+		break;
+
+		case 0xe606eba2: // Obj_Hover
+		{
+			if (pParams->ContainsFlag(CRCD(0xd443a2bc,"Off")))
+			{
+				UndoHover();
+				m_movingobj_status &= ~MOVINGOBJ_STATUS_HOVERING;
+			}
+			else
+			{
+				m_hover_offset=0;
+				
+				pParams->GetFloat(CRCD(0xc9fde32c,"Amp"),&m_hover_amp);
+				float f=1.0f;
+				pParams->GetFloat(CRCD(0xa80bea4a,"Freq"),&f);
+				m_hover_period=(int)(1000.0f/f);
+				// Add 10% of randomness so that groups of hovering things slowly
+				// go out of phase with each other rather than hovering in unison.
+				int random_number = Mth::Rnd(m_hover_period*20/10);
+				m_hover_period += random_number-m_hover_period/10;
+
+//				printf( "hover period = %d random number = %d\n", m_hover_period, random_number );
+
+				if ( m_hover_period <= 0 )
+				{
+					// GJ:  make sure that hover period is a valid value
+					Dbg_MsgAssert( false, ( "Potential divide-by-zero m_hover_period=%d at %s...  tell Mick!", m_hover_period, pScript->GetScriptInfo() ) );
+					m_hover_period = 1;
+				}
+				
+				m_y_before_applying_hover=GetObject()->GetPos()[Y];
+				m_movingobj_status |= MOVINGOBJ_STATUS_HOVERING;
+			}	
+			break;
+		}
+		
+        // @script | Obj_GetSpeed | Gets the object's current speed and puts it into a parameter called speed.
+		// By default the units will be miles per hour.
+		// @flag ips | Make the speed value be in inches per second
+		// @flag fps | Make the speed value be in feet per second
+		case 0xe76d73d2: // Obj_GetSpeed 
+		{
+			float speed=0.0f;
+			if (m_movingobj_status & MOVINGOBJ_STATUS_MOVETO)
+			{
+				speed=m_moveto_speed;
+			}
+			else if ( GetLockObjComponentFromObject(GetObject()) && GetLockObjComponentFromObject(GetObject())->IsLockEnabled() )
+			{
+				Mth::Vector d=GetObject()->m_pos - GetObject()->m_old_pos;
+				float m_time = Tmr::FrameLength();	 	// dodgy
+				if (m_time)
+				{
+					speed=d.Length()/m_time;
+				}	
+			}
+			else
+			{
+				speed=GetObject()->m_vel.Length();
+			}
+			
+			// speed is now in inches per sec.
+			
+			if ( pParams->ContainsFlag( 0xa18b8f32 ) ) // "ips"
+			{
+				// Nothing to do.
+			}
+			else if ( pParams->ContainsFlag( 0xaad7c80f ) ) // "fps"
+			{
+				// Convert to feet per second
+				speed/=FEET_TO_INCHES(1.0f);
+			} 
+			else
+			{
+				// Convert to miles per hour
+				speed/=MPH_TO_INCHES_PER_SECOND(1.0f);
+			}
+				
+			pScript->GetParams()->AddFloat("Speed",speed);
+			break;
+		}		
+
+
+        // @script | Obj_Jump | Makes the object jump.
+		// @parmopt float | Speed | 150.0 | The upwards speed of the jump
+		// @parmopt float | Gravity | -360.0 | The gravity applied to the jump
+		// @parmopt vector | heading | | use this heading as jump direction rather than
+		// jumping straight up
+		// @parmopt float | col_dist_above | 6 | distance used for collision checks above the object
+		// @parmopt float | col_dist_below | 6 | distance used for collision checks below the object
+		// @flag use_current_heading | use the object's current Z vector as jump direction
+		case 0x1ae46261:  // Obj_Jump
+		{
+			m_jump_speed = 150.0f;
+			pParams->GetFloat( CRCD(0xf0d90109,"Speed"), &m_jump_speed );
+			m_jump_original_speed = m_jump_speed;
+
+			m_jump_gravity = -360.0f;
+			pParams->GetFloat( CRCD(0xa5e2da58,"Gravity"), &m_jump_gravity );
+
+			m_jump_start_pos = GetObject()->m_pos;
+
+			m_jump_col_dist_above = 6.0f;
+			pParams->GetFloat( CRCD( 0xecc2c699, "col_dist_above" ), &m_jump_col_dist_above, Script::NO_ASSERT );
+			m_jump_col_dist_below = 6.0f;
+			pParams->GetFloat( CRCD( 0x5c4ae2f9, "col_dist_below" ), &m_jump_col_dist_below, Script::NO_ASSERT );
+			
+			if ( pParams->GetVector( CRCD( 0xfd4bc03e, "heading" ), &m_jump_heading, Script::NO_ASSERT ) )
+			{
+				m_jump_use_heading = true;
+				m_jump_speed *= m_jump_heading[Y];
+				m_jump_heading[Y] = 0.0f;
+			}
+			else if ( pParams->ContainsFlag( CRCD( 0xfecffe49, "use_current_heading" ) ) )
+			{
+				// m_jump_heading = Mth::Vector(0, 0, 1);
+				m_jump_heading = GetObject()->GetDisplayMatrix()[Z];
+				m_jump_use_heading = true;
+				m_jump_speed *= m_jump_heading[Y];
+				m_jump_heading[Y] = 0.0f;				
+			}
+			else
+			{				
+				m_jump_use_heading = false;
+			}
+
+			m_jump_use_land_height = false;
+			if ( pParams->GetFloat( CRCD( 0xa1810c27, "land_height" ), &m_jump_land_height, Script::NO_ASSERT ) )
+			{
+				m_jump_use_land_height = true;
+			}
+
+			m_movingobj_status |= MOVINGOBJ_STATUS_JUMPING;
+			break;
+		}
+		
+        // @script | Obj_FollowLeader | Makes the object follow a fixed distance behind another object.
+        // @parm name | Name | Name of the object to follow
+        // @parmopt float | Distance | 100 | Distance behind the leader in inches
+		// @flag OrientY | Tilt the object to be at right angles to the path direction
+		// @flag Off | Switch off following
+		// @flag LeaveYUnaffected | The y coordinate of the object will be left unaffected, so
+		// that the object can be clamped to the ground and not follow the skater into the air
+		// for example.
+		case 0x2fad370d: // Obj_FollowLeader
+			FollowLeader_Init( pParams );
+			break;
+
+
+		
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::EnsurePathobExists( CCompositeObject* pCompositeObject )
+{
+	if ( !mp_pathOb )
+	{
+		mp_pathOb = new CPathOb( pCompositeObject );
+		Dbg_MsgAssert( mp_pathOb,( "Couldn't allocate pathob." ));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPathOb* CMotionComponent::GetPathOb()
+{
+	return mp_pathOb;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Added by Ken. If the object is following a path, this will return the node
+// index of the node it is heading towards. Returns -1 otherwise.
+int CMotionComponent::GetDestinationPathNode()
+{	
+	if ( !(m_movingobj_status & MOVINGOBJ_STATUS_ON_PATH) )
+	{
+		return -1;
+	}	
+
+	if ( !mp_pathOb )
+	{
+		return -1;
+	}	
+	//Dbg_MsgAssert(mp_pathOb,("NULL GetPathOb() ?"));
+	return mp_pathOb->m_node_to;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CMotionComponent::GetPreviousPathNode()
+{	
+	if ( !(m_movingobj_status & MOVINGOBJ_STATUS_ON_PATH) )
+	{
+		return -1;
+	}	
+
+	if ( !mp_pathOb )
+	{
+		return -1;
+	}	
+	//Dbg_MsgAssert(mp_pathOb,("NULL GetPathOb() ?"));
+	return mp_pathOb->m_node_from;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CMotionComponent::Reset()
+{
+	m_turn_ang_target.Set( );
+	m_cur_turn_ang.Set( );
+	m_turn_speed_target.Set( );
+	m_turn_speed.Set( );
+	m_delta_turn_speed.Set( );
+	m_angular_friction.Set( );
+	m_orig_pos.Set( );
+	
+	m_moveto_pos.Set( );
+	m_moveto_dist = 0.0f;
+	m_moveto_dist_traveled = 0.0f;
+	m_moveto_speed = 0.0f;
+	m_moveto_speed_target = 0.0f;
+	m_moveto_acceleration = 0.0f;
+	
+	m_acceleration = 0.0f;
+	m_deceleration = 0.0f;
+	m_max_vel = 0.0f;
+	m_vel_z = 0.0f;
+	m_stop_dist = 0.0f;
+	m_stop_dist_traveled = 0.0f;
+	m_vel_z_start = 0.0f;
+	m_min_stop_vel = 0.0f;
+	
+	m_rot_time_target = 0.0f;
+	m_rot_time = 0.0f;
+	
+	// don't think this is dangerous...
+	m_movingobj_status = 0;
+	m_movingobj_flags = 0;
+	
+	// Reset the Triangle collision caches
+	
+	m_last_triangle.Init();
+	m_car_left_rear_last_triangle.Init();
+	m_car_right_rear_last_triangle.Init();
+	m_car_mid_front_last_triangle.Init();
+
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::SetCorrectRotationDirection( int rotAxis )
+{
+	
+	m_turn_speed[ rotAxis ] = Mth::Abs( m_turn_speed[ rotAxis ] );
+	m_turn_speed_target[ rotAxis ] = Mth::Abs( m_turn_speed_target[ rotAxis ] );
+	m_delta_turn_speed[ rotAxis ] = Mth::Abs( m_delta_turn_speed[ rotAxis ] );
+	m_angular_friction[ rotAxis ] = Mth::Abs( m_angular_friction[ rotAxis ] );
+	if ( m_turn_ang_target[ rotAxis ] < 0 )
+	{
+		m_turn_speed[ rotAxis ] *= -1.0f;
+		m_turn_speed_target[ rotAxis ] *= -1.0f;
+		m_delta_turn_speed[ rotAxis ] *= -1.0f;
+		m_angular_friction[ rotAxis ] *= -1.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::InitPath( int nodeNumber )
+{
+	if (mp_path_object_tracker)
+	{
+		mp_path_object_tracker->StopTrackingObject( GetObject() );
+	}	
+
+	mp_path_object_tracker=Obj::CPathMan::Instance()->TrackObject( GetObject(), nodeNumber );	
+	// Note: mp_path_object_tracker could be NULL
+	
+	EnsurePathobExists( GetObject() );
+	GetPathOb()->NewPath( nodeNumber, GetObject()->GetPos() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::FollowPath_Init( Script::CStruct* pParams )
+{   
+	uint32 nodeChecksum;
+	if ( pParams->GetChecksum( 0xa1dc81f9, &nodeChecksum ) ) // "name"
+	{
+		int nodeNum = SkateScript::FindNamedNode( nodeChecksum );
+		InitPath( nodeNum );
+		m_movingobj_status |= MOVINGOBJ_STATUS_ON_PATH;
+		// these can't coexist:
+		m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
+	}
+	else
+	{
+		Dbg_MsgAssert( 0,( "Must specify name of node in Obj_FollowPath, using name = nodeName." ));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMotionComponent::PickRandomWaypoint()
+{
+	return ( m_movingobj_flags & MOVINGOBJ_FLAG_RANDOM_PATH_MODE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::HitWaypoint( int nodeIndex )
+{	
+	uint32 scriptChecksum;
+	Script::CStruct* pNodeData;
+	pNodeData = SkateScript::GetNode( nodeIndex );
+	Dbg_MsgAssert( pNodeData,( "Waypoint gave us a bad node index." ));
+	m_current_node = nodeIndex;
+	
+	if ( pNodeData->GetChecksum( 0x3aefe377, &scriptChecksum ) ) 	// "SpawnObjScript"
+	{
+		Script::CStruct* pScriptParams = NULL;
+		pNodeData->GetStructure( CRCD(0x7031f10c,"Params"), &pScriptParams );
+#ifdef __NOPT_ASSERT__
+		Script::CScript* p_script=GetObject()->SpawnScriptPlease( scriptChecksum, pScriptParams );
+		p_script->SetCommentString("Created by CMotionComponent::HitWaypoint");
+#else
+		GetObject()->SpawnScriptPlease( scriptChecksum, pScriptParams );
+#endif
+		return;
+	}
+	
+	if ( pNodeData->GetChecksum( 0x86125749, &scriptChecksum ) ) 	// "SwitchObjScript"
+	{
+		Script::CStruct* pScriptParams = NULL;
+		pNodeData->GetStructure( CRCD(0x7031f10c,"Params"), &pScriptParams );
+		GetObject()->SwitchScript( scriptChecksum, pScriptParams );
+		return;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMotionComponent::LookAt_Init( Script::CStruct* pParams, const Mth::Vector& lookAtPos )
+{	
+	int deltaDegreesPerSecond = 360;
+	int deltaDeltaDegreesPerSecond = 0;
+	int lockAxis = ( 1 << Y );
+	float time = 0.0f;
+	
+	if ( !( m_movingobj_status & MOVINGOBJ_STATUS_LOOKAT ) )
+	{
+		m_movingobj_status |= MOVINGOBJ_STATUS_LOOKAT;
+		m_movingobj_status &= ~MOVINGOBJ_STATUS_ROTXYZ;
+		m_movingobj_flags &= ~( MOVINGOBJ_FLAG_CONST_ROTX | MOVINGOBJ_FLAG_CONST_ROTY | MOVINGOBJ_FLAG_CONST_ROTZ );
+	}
+
+	pParams->GetFloat( 0x906b67ba, &time ); // "time"
+	if ( !time )
+	{
+		pParams->GetInteger( 0xf0d90109, &deltaDegreesPerSecond );		// 'speed'
+		pParams->GetInteger( 0x85894a84, &deltaDeltaDegreesPerSecond );	// 'acceleration'
+		pParams->GetInteger( 0xa4aecb20, &lockAxis );  // "lockAxis"
+	}
+	
+	float threshold=0.0f;
+	pParams->GetFloat(0xf73a4efd,&threshold); // AngleThreshold
+	threshold=DEGREES_TO_RADIANS(threshold);
+	
+	bool rotation_required=false;
+	int i;
+	for ( i = 0; i < 3; i++ )
+	{
+		if ( lockAxis & ( 1 << i ) )
+		{
+			if ( SetUpLookAtPos( lookAtPos, GetObject()->GetPos(), i == Z ? Y : Z, i, threshold ) )
+			{
+				if ( time )
+				{
+					m_delta_turn_speed[ i ] = 0.0f;
+					m_turn_speed[ i ] = m_turn_ang_target[ i ] / time;
+				}
+				else
+				{
+					m_delta_turn_speed[ i ] = DEGREES_TO_RADIANS( deltaDeltaDegreesPerSecond );
+					if ( !m_delta_turn_speed[ i ] )
+						m_turn_speed[ i ] = DEGREES_TO_RADIANS( deltaDegreesPerSecond );
+					else
+					{
+						m_turn_speed[ i ] = 0.0f;
+						m_turn_speed_target[ i ] = 0.0f;
+					}
+					SetCorrectRotationDirection( i );
+				}
+				rotation_required=true;
+			}
+		}
+	}
+	return rotation_required;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::QuatRot_Setup( Script::CStruct* pParams, const Mth::Matrix& rot )
+{
+	if ( !mp_slerp )
+	{
+		mp_slerp = new Mth::SlerpInterpolator;
+	}
+
+    Mth::Matrix tempMatrix = rot;
+	mp_slerp->setMatrices( &GetObject()->GetMatrix(), &tempMatrix );
+	
+	m_rot_time = 0.0f;
+	m_rot_time_target = 0.0f;
+	pParams->GetFloat( 0x906b67ba, &m_rot_time_target ); // "time" ( in seconds )
+	m_movingobj_status |= MOVINGOBJ_STATUS_QUAT_ROT;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::QuatRot_Init( Script::CStruct* pParams )
+{
+	Mth::Vector rot( 0.0f, 0.0f, 0.0f );
+	if ( pParams->GetVector( 0x91a4c826, &rot ) )  // relative
+	{
+		if ( pParams->ContainsFlag( 0x26af9dc9 ) ) // "FLAG_MAX_COORDS"
+		{
+			rot[ Z ] = -rot[ Z ];
+			rot.ConvertToMaxCoords( );
+		}
+		rot.DegreesToRadians( );
+		Mth::Matrix temp = GetObject()->GetMatrix();
+		temp.RotateLocal( rot );
+		QuatRot_Setup( pParams, temp );
+	}
+	else if ( pParams->GetVector( 0x592089a9, &rot ) ) //absolute
+	{
+		if ( pParams->ContainsFlag( 0x26af9dc9 ) ) // "FLAG_MAX_COORDS"
+		{
+			rot[ Z ] = -rot[ Z ];
+			rot.ConvertToMaxCoords( );
+		}
+		rot.DegreesToRadians( );
+		Mth::Matrix temp( rot[ X ], rot[ Y ], rot[ Z ] );
+		QuatRot_Setup( pParams, temp );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMotionComponent::SetUpLookAtPos( const Mth::Vector& lookToPos, const Mth::Vector& currentPos, int headingAxis, int rotAxis, float threshold )
+{
+	Mth::Vector pathHeading = lookToPos - currentPos;
+	
+	m_cur_turn_ang[ rotAxis ] = 0.0f;
+	m_turn_ang_target[ rotAxis ] = Mth::GetAngle( GetObject()->GetMatrix(), pathHeading, headingAxis, rotAxis );
+	if ( fabs(m_turn_ang_target[ rotAxis ]) > threshold )
+	{
+		m_movingobj_status |= ( MOVINGOBJ_STATUS_ROTX << rotAxis );
+/*		Dbg_MsgAssert( ( ( !( m_movingobj_status & MOVINGOBJ_STATUS_ON_PATH ) ) ||
+						( m_movingobj_flags & MOVINGOBJ_FLAG_INDEPENDENT_HEADING ) ) ||
+						rotAxis == Z,( "Can't rotate on X or Y axis while using auto-heading on path." ));*/
+		return true;
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::RotAxis_Init( Script::CStruct* pParams, Script::CScript* pScript, int rotAxis )
+{
+	
+	float mult = 1.0f;
+
+	// Mick - Just being safe here.  	
+	Dbg_MsgAssert( rotAxis == X || rotAxis == Y || rotAxis== Z,( "Illegal rotAxis %d", rotAxis ));
+	
+	
+	// get out of lookat mode... and shit.
+	if ( m_movingobj_status & MOVINGOBJ_STATUS_LOOKAT )
+	{
+		m_movingobj_status &= ~MOVINGOBJ_STATUS_LOOKAT;
+		m_movingobj_status &= ~MOVINGOBJ_STATUS_ROTXYZ;
+	}
+	if ( pParams->ContainsFlag( 0x26af9dc9 ) ) // "FLAG_MAX_COORDS"
+	{
+		switch ( rotAxis )
+		{
+			case ( X ):
+				mult = -1.0f;
+				break;
+
+			case ( Y ):
+				rotAxis = Z;
+//				mult = -1.0f;
+				break;
+
+			case ( Z ):
+				rotAxis = Y;
+				break;
+			
+			default:
+				Dbg_MsgAssert( 0,( "Illegal rotAxis" ));
+				break;
+		}
+	}
+
+	// clear this in case it isn't set...
+	m_angular_friction[ rotAxis ] = 0.0f;
+	m_delta_turn_speed[ rotAxis ] = 0.0f;
+	m_turn_ang_target[ rotAxis ] = 0.0f;
+
+	if ( pParams->GetFloat( 0xff7ebaf6, &m_turn_ang_target[ rotAxis ] ) ) // "angle"
+	{
+		m_turn_ang_target[ rotAxis ] = mult * DEGREES_TO_RADIANS( m_turn_ang_target[ rotAxis ] );
+		m_movingobj_flags &= ~( MOVINGOBJ_FLAG_CONST_ROTX << rotAxis );
+		m_cur_turn_ang[ rotAxis ] = 0.0f;
+		m_turn_speed[ rotAxis ] = 0.0f;
+		if ( !m_turn_ang_target[ rotAxis ] )
+		{
+			// stop the rotation!
+			m_movingobj_status &= ~( MOVINGOBJ_STATUS_ROTX << rotAxis );
+			return;
+		}
+	}
+	
+	if ( pParams->GetFloat( 0xf59ff7d7, &m_angular_friction[ rotAxis ] ) ) // "deceleration"
+	{
+		// K: If a deceleration is specified, the 'speed' value specified is the target speed.
+		// The current m_turn_speed remains unchanged so that it slows down to the target speed.
+		// (TT427)
+		m_angular_friction[ rotAxis ] = DEGREES_TO_RADIANS( m_angular_friction[ rotAxis ] );
+		if ( pParams->GetFloat( 0xf0d90109, &m_turn_speed_target[ rotAxis ] ) ) // "speed"
+		{
+			m_turn_speed_target[ rotAxis ] = DEGREES_TO_RADIANS( m_turn_speed_target[ rotAxis ] );
+		}
+		else
+		{
+			Dbg_MsgAssert( 0,( "\n%s\nMust specify speed for Obj_Rot functions (degrees per second).", pScript->GetScriptInfo( ) ));
+		}
+	}
+	else if ( pParams->GetFloat( 0x85894a84, &m_delta_turn_speed[ rotAxis ] ) ) // acceleration
+	{
+		m_delta_turn_speed[ rotAxis ] = DEGREES_TO_RADIANS( m_delta_turn_speed[ rotAxis ] );
+		if ( pParams->GetFloat( 0xf0d90109, &m_turn_speed_target[ rotAxis ] ) ) // "speed"
+		{
+			m_turn_speed_target[ rotAxis ] = DEGREES_TO_RADIANS( m_turn_speed_target[ rotAxis ] );
+		}
+		else
+		{
+			Dbg_MsgAssert( 0,( "\n%s\nMust specify speed for Obj_Rot functions (degrees per second).", pScript->GetScriptInfo( ) ));
+		}
+		if ( !( m_movingobj_status & ( MOVINGOBJ_STATUS_ROTX << rotAxis ) ) )
+		{
+			m_turn_speed[ rotAxis ] = 0.0f;
+		}
+	}
+	else
+	{
+		if ( pParams->GetFloat( 0xf0d90109, &m_turn_speed[ rotAxis ] ) ) // "speed"
+		{
+			m_turn_speed[ rotAxis ] = DEGREES_TO_RADIANS( m_turn_speed[ rotAxis ] );
+		}
+		else
+		{
+			Dbg_MsgAssert( 0,( "\n%s\nMust specify speed for Obj_Rot functions (degrees per second).", pScript->GetScriptInfo( ) ));
+		}
+	}
+	if ( !( m_turn_ang_target[ rotAxis ] || m_angular_friction[ rotAxis ] ) )
+	{
+		m_movingobj_flags |= ( MOVINGOBJ_FLAG_CONST_ROTX << rotAxis );
+	}
+	if ( !( m_movingobj_flags & ( MOVINGOBJ_FLAG_CONST_ROTX << rotAxis ) ) )
+	{
+		SetCorrectRotationDirection( rotAxis );
+	}
+	m_movingobj_status |= ( MOVINGOBJ_STATUS_ROTX << rotAxis );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::LookAtNodeLinked_Init( Script::CStruct* pParams, Script::CScript* pScript )
+{	
+	int numLinks;
+	numLinks = SkateScript::GetNumLinks( m_current_node );
+	if ( !numLinks )
+	{
+		Dbg_MsgAssert( 0,( "\n%s\nNo node linked to object calling Obj_LookAtNodeLinked", pScript->GetScriptInfo( ) ));
+		return;
+	}
+	
+	int link_num=1;
+	pParams->GetInteger( 0xe85997d0, &link_num ); // "linkNum"
+	// Designers number the links from 1 to n, so convert to programmer form
+	link_num -= 1;
+	
+	// Print a warning message instead of asserting, to save having to reboot.
+	if (link_num<0 || link_num>=numLinks)
+	{
+		#ifdef __NOPT_ASSERT__
+		printf("!!!!!!!!!! Bad LinkNum of %d sent to Obj_LookAtNodeLinked, range is 1 to %d\n",link_num+1,numLinks);
+		#endif
+		link_num=0;
+	}	
+	
+	Mth::Vector lookAtPos;
+	SkateScript::GetPosition( SkateScript::GetLink( m_current_node, link_num ), &lookAtPos );
+	LookAt_Init( pParams, lookAtPos );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::LookAtNode_Init( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 checksum = 0;
+	
+	pParams->GetChecksum( 0xa1dc81f9, &checksum ); // "name"
+	if ( !checksum )
+	{
+		Dbg_MsgAssert( 0,( "\n%s\nNeed node name in Obj_LookAtNode", pScript->GetScriptInfo( ) ));
+		return;
+	}
+	Mth::Vector lookAtPos;
+	SkateScript::GetPosition( SkateScript::FindNamedNode( checksum ), &lookAtPos );
+	LookAt_Init( pParams, lookAtPos );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::LookAtPos_Init( Script::CStruct* pParams, bool absolute )
+{
+	
+	Mth::Vector vec( 0, 0, 0 );
+	pParams->GetVector( NONAME, &vec );
+	if ( pParams->ContainsFlag( 0x26af9dc9 ) ) // "FLAG_MAX_COORDS"
+	{
+		vec.ConvertToMaxCoords( );
+	}
+	if ( !absolute )
+	{
+		vec += GetObject()->GetPos();
+	}
+	LookAt_Init( pParams, vec );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::Rot( int rotAxis, float deltaRot )
+{
+	switch ( rotAxis )
+	{
+		case ( X ):
+			GetObject()->m_matrix.RotateXLocal( deltaRot );
+			GetObject()->m_matrix.OrthoNormalizeAbout( X );
+			break;
+		
+		case ( Y ):
+			GetObject()->m_matrix.RotateYLocal( deltaRot );
+			GetObject()->m_matrix.OrthoNormalizeAbout( Y );
+			break;
+		
+		case ( Z  ):
+			GetObject()->m_matrix.RotateZLocal( deltaRot );
+			GetObject()->m_matrix.OrthoNormalizeAbout( Z );
+			break;
+		
+		default:
+			Dbg_MsgAssert( 0,( "illegal rot axis." ));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::RotateToFacePos( const Mth::Vector pos )
+{   
+	Mth::Vector desiredHeading;
+	desiredHeading = pos - GetObject()->GetPos();
+	float deltaY = Mth::GetAngle( GetObject()->GetMatrix(), desiredHeading );
+	GetObject()->m_matrix.RotateYLocal( deltaY );
+	GetObject()->m_matrix.OrthoNormalizeAbout( Y );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Follow the path that's linked to the current waypoint (node) that the object is sitting at.
+void CMotionComponent::FollowPathLinked_Init( Script::CStruct* pParams, Script::CScript* pScript )
+{   
+	int numLinks;
+	int nodeNum;
+
+	if ( pParams->ContainsFlag( 0x422e1fce ) ) // originalNode
+	{
+		nodeNum = m_start_node;
+	}
+	else
+	{
+		nodeNum = m_current_node;
+	}
+	
+	numLinks = SkateScript::GetNumLinks( nodeNum );
+	if ( !numLinks )
+	{
+		Dbg_MsgAssert( 0,( "\n%s\nNo node linked to object (from node %d) calling Obj_FollowPathLinked", pScript->GetScriptInfo( ), nodeNum ));
+		return;
+	}
+	if ( ( numLinks > 1 ) && PickRandomWaypoint( ) )
+	{
+		InitPath( SkateScript::GetLink( nodeNum, Mth::Rnd( numLinks ) ) );
+	}
+	else
+	{
+		InitPath( SkateScript::GetLink( nodeNum, 0 ) );
+	}
+	m_movingobj_status |= MOVINGOBJ_STATUS_ON_PATH;
+	// these can't coexist:
+	m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Follow the path from current position to the position of the waypoint last stored
+// in the script call to Obj_StoreNode (when a pedestrian, for example, has to jump
+// out of the way of the player, he stores his current position and the current node
+// that he's heading for... then he walks back to his position, and finally ends up
+// heading to the waypoint he was heading for before he left the path to jump out of the way).
+void CMotionComponent::FollowStoredPath_Init( Script::CStruct* pParams )
+{   
+	InitPath( m_stored_node );
+	m_movingobj_status |= MOVINGOBJ_STATUS_ON_PATH;
+	// these can't coexist:
+	m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Physics for when the moving object is following a path.
+void CMotionComponent::DoPathPhysics( void )
+{	
+	if ( m_movingobj_status & MOVINGOBJ_STATUS_ON_PATH )
+	{
+		// if we're stopping:
+		if ( m_movingobj_flags & MOVINGOBJ_FLAG_STOP_ALONG_PATH )
+		{
+			if ( m_stop_dist_traveled >= m_stop_dist )
+			{
+				return;
+			}
+			// keep slowing down...
+			m_stop_dist_traveled += m_vel_z * m_time;
+			if ( ( !m_stop_dist ) || ( m_stop_dist_traveled >= m_stop_dist ) )
+			{
+				m_vel_z = 0;
+			}
+			else
+			{
+				float percent = m_stop_dist_traveled / m_stop_dist;
+				m_vel_z = ( m_vel_z_start * ( 1.0f - percent ) ) + ( m_min_stop_vel * ( percent ) );
+			}
+			return;
+		}
+		
+		// Adjust velocity...
+		if ( m_vel_z < m_max_vel )
+		{
+			if ( m_acceleration )
+			{
+				m_vel_z += ( m_acceleration * m_time );
+				if ( m_vel_z > m_max_vel )
+				{
+					m_vel_z = m_max_vel;
+				}
+			}
+			else
+			{
+				m_vel_z = m_max_vel;
+			}
+		}
+		else if ( m_vel_z > m_max_vel )
+		{
+			if ( m_deceleration )
+			{
+				m_vel_z -= ( m_deceleration * m_time );
+				if ( m_vel_z < m_max_vel )
+				{
+					m_vel_z = m_max_vel;
+				}
+			}
+			else
+			{
+				m_vel_z = m_max_vel;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if 0
+
+// UNUSED...
+
+// BB: I have added a new version of this function below.  I moved the code
+// from the CallMemberFunction func because I needed to do the same thing from c.
+// I have left this here for reference, just in case.
+CCompositeObject* CMotionComponent::GetNextObjectOnPath( float range )
+{
+	if (!(m_movingobj_status & MOVINGOBJ_STATUS_ON_PATH) || !GetPathOb() || !mp_path_object_tracker)
+	{
+		return NULL;
+	}	
+	
+	float distance_covered=0.0f;
+	Mth::Vector path_pos=m_pos;
+	int next_node=GetPathOb()->m_node_to;
+	
+	// Copy the non-NULL object pointers from the tracker to a small local array for speed,
+	// since they may need to be stepped through several times and the tracker's array
+	// contains lots of NULL's
+	#define MAX_OBS 20
+	CCompositeObject* pp_objects[MAX_OBS];
+	int num_objects=0;
+	
+	const CSmtPtr* pp_object_list=mp_path_object_tracker->GetObjectList();
+	for (int i=0; iGetDestinationPathNode()==next_node)
+			{
+				float d=Mth::Distance(pp_objects[i]->m_pos,path_pos);
+				if (distance_covered+d < range)
+				{
+					if (!p_nearest || dGetArray(0x2e7d5ee7/*Links*/,&p_links);
+		
+		if (!p_links)
+		{
+			break;
+		}
+		
+		next_node=p_links->GetInteger(0);	
+	}
+	
+	return p_nearest;
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// returns TRUE if instant move, FALSE otherwise.
+bool CMotionComponent::Move_Init( Script::CStruct* pParams, Script::CScript* pScript, Mth::Vector pos, int nodeNum )
+{   
+	int units = UNITS_MPH;
+	float time;
+	float speed;
+
+	m_moveto_pos = pos;
+	m_moveto_dist = Mth::Distance( m_moveto_pos, GetObject()->GetPos() );
+	m_moveto_acceleration = 0.0f;
+	
+	// initialize everything:
+	m_orig_pos = GetObject()->GetPos();
+	m_moveto_dist_traveled = 0.0f;
+	m_movingobj_status |= MOVINGOBJ_STATUS_MOVETO;
+	// can't follow path and moveto at the same time:
+	m_movingobj_status &= ~MOVINGOBJ_STATUS_ON_PATH;
+	m_moveto_node_num = -1;
+	if ( pParams->GetFloat( 0x906b67ba, &time ) ) // "time"
+	{
+		// time is in seconds:
+		Dbg_MsgAssert( time,( "\n%s\nCan't have zero for time...", pScript->GetScriptInfo( ) ));
+		m_moveto_speed = m_moveto_dist / time;
+	}
+	else if ( pParams->GetFloat( 0xf0d90109, &speed ) ) // "speed"
+	{	// use speed and acceleration:
+		if ( pParams->ContainsFlag( 0x2ce7ee02 ) ) // "mph"
+		{
+			units = UNITS_MPH;
+		}
+		else if ( pParams->ContainsFlag( 0xaad7c80f ) ) // "fps"
+		{
+			units = UNITS_FPS;
+		} 
+		else if ( pParams->ContainsFlag( 0xa18b8f32 ) ) //IPS
+		{
+			units = UNITS_IPS;
+		}
+
+		pParams->GetFloat( 0x85894a84, &m_moveto_acceleration ); // "acceleration"
+
+		if ( units == UNITS_MPH )
+		{
+			speed = MPH_TO_IPS( speed );
+			m_moveto_acceleration = MPH_TO_IPS( m_moveto_acceleration );
+		}
+		else if ( units == UNITS_FPS )
+		{
+			speed *= 12.0f;
+			m_moveto_acceleration *= 12.0f;
+		}
+		
+		if ( !m_moveto_acceleration )
+		{
+			m_moveto_speed = speed;
+		}
+		else
+		{
+			m_moveto_speed = 0.0f;
+			m_moveto_speed_target = speed;
+		}
+	}
+	else
+	{
+		// instantaneous move:
+		GetObject()->SetPos(m_moveto_pos);
+		m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
+		
+		if ( nodeNum != -1 )
+		{
+			if ( pParams->ContainsFlag( 0x90a91232 ) ) // "orient"
+			{
+				Script::CStruct* pNodeData = SkateScript::GetNode( nodeNum );
+				OrientToNode( pNodeData );
+			}
+		}
+		
+		// GJ:  Need to re-implement this on THPS5...
+		// (used to fix quick-jump bug on Xbox/NGC,
+		// where LODing code wasn't refreshing the model's
+		// position)
+
+#if 0
+#ifndef __PLAT_NGPS__
+		// Rebuild display matrix. Gary - you should look at this post THPS4 and generalize.
+		Mth::Matrix rootMatrix;
+		rootMatrix = m_display_matrix;
+		rootMatrix[Mth::POS] = m_pos;
+		rootMatrix[Mth::POS][W] = 1.0f;
+		if( GetModel() )
+		{
+			GetModel()->Render( &rootMatrix, true, GetSkeleton() );
+
+			// Also update the shadow if relevant.
+			if( mp_shadow && mp_shadow->GetShadowType() != Gfx::vDETAILED_SHADOW )
+			{
+				update_shadow();
+			}
+		}
+#endif
+#endif
+
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::MoveToNode_Init( Script::CStruct* pParams, Script::CScript* pScript )
+{   
+	Mth::Vector pos;
+	
+	// if we're moving to a named node:
+	uint32 checksum = 0;
+	pParams->GetChecksum( 0xa1dc81f9, &checksum ); // "name"
+	if ( checksum )
+	{
+		int nodeNum = SkateScript::FindNamedNode( checksum );
+		SkateScript::GetPosition( nodeNum, &pos );
+		if ( Move_Init( pParams, pScript, pos, nodeNum ) )
+		{
+			HitWaypoint( nodeNum );
+		}
+		else
+		{
+			m_moveto_node_num = nodeNum;
+		}
+		return;
+	}
+	Dbg_MsgAssert( 0,( "\n%s\nObj_MoveToNode requires node name specification (name = *)", pScript->GetScriptInfo( ) ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::MoveToPos_Init( Script::CStruct* pParams, Script::CScript* pScript, bool absolute )
+{   
+	Mth::Vector vec( 0, 0, 0 );
+	pParams->GetVector( NONAME, &vec );
+	if ( pParams->ContainsFlag( 0x26af9dc9 ) ) // "FLAG_MAX_COORDS"
+	{
+		vec.ConvertToMaxCoords( );
+	}
+	if ( !absolute )
+	{
+		vec += GetObject()->GetPos();
+	}
+	Move_Init( pParams, pScript, vec );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::MoveToLink_Init( Script::CStruct* pParams, Script::CScript* pScript )
+{   
+	int linkNum = 0;
+	int numLinks;
+	numLinks = SkateScript::GetNumLinks( m_current_node );
+	if ( !numLinks )
+	{
+		Dbg_MsgAssert( 0,( "Obj_MoveToLink requires a link to the object's current node (last node hit in path)." ));
+		return;
+	}
+	if ( pParams->GetInteger( 0xe85997d0, &linkNum ) ) // "linkNum"
+	{
+		linkNum -= 1;
+	}
+	else if ( ( numLinks > 1 ) && ( pParams->ContainsFlag( 0x90a1c52a ) ) ) // "randomLink"
+	{
+		linkNum = Mth::Rnd( numLinks );
+	}
+	Mth::Vector pos;
+	Dbg_MsgAssert( linkNum < numLinks,( "\n%s\nLink num is greater than the number of links.", pScript->GetScriptInfo( ) ));
+	int nodeNum;
+	nodeNum = SkateScript::GetLink( m_current_node, linkNum );
+	SkateScript::GetPosition( nodeNum, &pos );
+	if ( Move_Init( pParams, pScript, pos ) )
+	{
+		HitWaypoint( nodeNum );
+	}
+	else
+	{
+		m_moveto_node_num = nodeNum;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMotionComponent::Move( void )
+{	
+	if ( m_moveto_acceleration )
+	{
+		m_moveto_speed += m_moveto_acceleration * Tmr::FrameRatio( );
+		if ( m_moveto_speed >= m_moveto_speed_target )
+		{
+			m_moveto_speed = m_moveto_speed_target;
+			m_moveto_acceleration = 0.0f;
+		}
+	}
+	m_moveto_dist_traveled += m_time * m_moveto_speed;
+	if ( m_moveto_dist_traveled >= m_moveto_dist )
+	{
+		GetObject()->m_pos = m_moveto_pos;
+		if ( m_moveto_node_num != -1 )
+		{
+			HitWaypoint( m_moveto_node_num );
+		}
+		return true;
+	}
+	Mth::Vector newPos = m_moveto_pos - m_orig_pos;
+	newPos *= ( m_moveto_dist_traveled / m_moveto_dist );
+	GetObject()->m_pos = m_orig_pos + newPos;
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::OrientToNode(Script::CStruct* pNodeData)
+{
+	Mth::Vector rot;
+	GetObject()->m_matrix.Ident( );	   		// Set identity before we decided if we need to rotate or not, in case we don't.
+	SkateScript::GetAngles( pNodeData, &rot );
+	if ( rot[ X ] || rot[ Y ] || rot[ Z ] )
+	{
+		// AML: This was rotating Z, then X, then Y
+		//		See me if there are problems, and I'll change
+		//		the exporter to do Z,X,Y
+
+		//m_matrix.RotateZ( rot[ Z ] );
+		GetObject()->GetMatrix().RotateX( rot[ X ] );
+		GetObject()->GetMatrix().RotateY( rot[ Y ] );
+		GetObject()->GetMatrix().RotateZ( rot[ Z ] );
+	}	
+	//printf ("OrientToNode %d, (%f,%f,%f)\n",nodeNum,rot[X],rot[Y],rot[Z]);
+	
+	// sync up the display matrix to the physics matrix
+	GetObject()->GetDisplayMatrix() = GetObject()->m_matrix;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::QuatRot( void )
+{	
+	float percent;
+	m_rot_time += m_time;
+	if ( ( !m_rot_time_target ) || ( m_rot_time >= m_rot_time_target ) )
+	{
+		percent = 1.0f;
+		m_movingobj_status &= ~MOVINGOBJ_STATUS_QUAT_ROT;
+	}
+	else
+	{
+		percent = m_rot_time / m_rot_time_target;
+	}
+	
+	Dbg_MsgAssert( mp_slerp,( "Missing mp_slerp..." ));
+    mp_slerp->getMatrix( &GetObject()->m_matrix, percent );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::FollowPath( void )
+{   
+	// temp hack to separate new peds from old path ob stuff
+	Obj::CPedLogicComponent* pPedLogicComp = GetPedLogicComponentFromObject( GetObject() );
+	if ( pPedLogicComp )
+		return;
+
+	Dbg_MsgAssert( GetPathOb(),( "Shouldn't be in FOLLOW_WAYPOINTS state without pathob." ));
+
+    Mth::Vector movement = -GetObject()->GetPos();				
+				
+	// change velocity:
+	DoPathPhysics();
+
+	// do path traversal:
+	if ( m_vel_z )
+	{
+		/*
+		Mth::Vector d=GetPathOb()->m_wp_pos_to-GetPathOb()->m_wp_pos_from;
+		m_shadow_normal.Set(0.0f,1.0f,0.0f);
+		m_shadow_normal=Mth::CrossProduct(m_shadow_normal,d);
+		m_shadow_normal=Mth::CrossProduct(m_shadow_normal,d);
+		m_shadow_normal.Normalize();
+		*/
+		
+		// send in our forward direction, distance traveled...
+		//if ( GetPathOb()->TraversePath( is_being_skitched, m_vel_z * m_time, 
+		//	( m_movingobj_flags & MOVINGOBJ_FLAG_INDEPENDENT_HEADING ) ? NULL : &m_matrix[ Z ] ) )
+		if ( GetPathOb()->TraversePath( m_vel_z * m_time, 
+			( m_movingobj_flags & MOVINGOBJ_FLAG_INDEPENDENT_HEADING ) ? NULL : &GetObject()->m_matrix[ Z ] ) )
+		{
+			// path is done... turn off flag:
+			m_movingobj_status &= ~MOVINGOBJ_STATUS_ON_PATH;
+			m_vel_z = 0.0f;
+		}
+
+		if ( !( m_movingobj_flags & MOVINGOBJ_FLAG_INDEPENDENT_HEADING ) )
+		{
+			// set up the matrix according to the nav point heading...
+			GetPathOb()->SetHeading( &GetObject()->m_matrix[ Z ] );
+			if ( m_movingobj_flags & MOVINGOBJ_FLAG_NO_PITCH_PLEASE )
+			{
+				if ( ( GetObject()->m_matrix[ Z ] )[ Y ] )
+				{
+					( GetObject()->m_matrix[ Z ] )[ Y ] = 0.0f;
+					GetObject()->m_matrix[ Z ].Normalize( );
+				}
+			}
+			// this heading may be changed in a moment, when we stick
+			// the object to the ground... if it isn't stuck, we should
+			// normalize the matrix around that heading...
+			if ( !( m_movingobj_flags & MOVINGOBJ_FLAG_STICK_TO_GROUND ) )
+			{
+				// make sure our matrix is orthagonal:
+				GetObject()->m_matrix[ X ] = Mth::CrossProduct( GetObject()->m_matrix[ Y ], GetObject()->m_matrix[ Z ]);
+				GetObject()->m_matrix[ X ].Normalize();
+				// This will work for things whose Y axis is always up...
+				// May have to add flags for things that rotate along Z axis or
+				// far along X axis:
+				GetObject()->m_matrix[ Z ] = Mth::CrossProduct( GetObject()->m_matrix[ X ], GetObject()->m_matrix[ Y ] );
+				GetObject()->m_matrix[ Z ].Normalize( );
+			}
+		}
+		if ( m_movingobj_flags & MOVINGOBJ_FLAG_STICK_TO_GROUND )
+		{
+			// Just move it to the nav pos ( except for y axis ) then stick it to the ground...
+			float origY = GetObject()->m_pos[ Y ];
+			GetObject()->m_pos = GetPathOb()->m_nav_pos;
+			GetObject()->m_pos[ Y ] = origY;
+
+			StickToGround();
+		}
+		else
+		{
+			float origY = GetObject()->m_pos[ Y ];
+			GetObject()->m_pos = GetPathOb()->m_nav_pos;
+			if ( m_movingobj_flags & MOVINGOBJ_FLAG_CONSTANT_HEIGHT )
+			{
+				GetObject()->m_pos[ Y ] = origY;
+			}
+		}
+	}
+
+	movement += GetObject()->m_pos;				// actually calculate movement	
+	GetObject()->m_vel = movement / m_time; 		// calculate the velocity, so we can impart velocity in things we hit, etc
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// call SetUpLookAtPos first, or one of the other rotate
+// setup functions, then call this until it returns true:
+bool CMotionComponent::Rotate( int rotAxis )
+{	
+
+	bool done = false;
+
+	// update angular acceleration/angular velocity:
+	if ( m_delta_turn_speed[ rotAxis ] )
+	{
+		m_turn_speed[ rotAxis ] += m_delta_turn_speed[ rotAxis ] * m_time;
+		if ( ( Mth::Abs( m_turn_speed[ rotAxis ] ) > Mth::Abs( m_turn_speed_target[ rotAxis ] ) )
+			&& ( ( m_turn_speed[ rotAxis ] > 0.0f ) == ( m_turn_speed_target[ rotAxis ] > 0.0f ) ) )
+		{
+			// stop angular acceleration...settle on the target:
+			m_turn_speed[ rotAxis ] = m_turn_speed_target[ rotAxis ];
+		}
+	}
+	
+	if ( m_angular_friction[ rotAxis ] )
+	{
+		float friction = m_angular_friction[ rotAxis ] * m_time;
+		// Ken: Changed this so that it decelerates down to the m_turn_speed_target rather than 0
+		if ( Mth::Abs( friction ) > Mth::Abs( m_turn_speed[ rotAxis ] - m_turn_speed_target[ rotAxis ] ) )
+		{
+			// Settle on the target speed
+			m_turn_speed[ rotAxis ] = m_turn_speed_target[ rotAxis ];
+			// Switch off the friction so that it deos not slow down any more
+			m_angular_friction[ rotAxis ] = 0.0f;
+			
+			Dbg_Message( "done rotating due to deceleration... %s angle traversed = %f", 
+				rotAxis == X ? "X" : ( rotAxis == Y ? "Y" : "Z" ), RADIANS_TO_DEGREES( m_cur_turn_ang[ rotAxis ] ) );
+			if (m_turn_speed_target[ rotAxis ]==0.0f)
+			{
+				return true; // done rotating...
+			}
+			else
+			{
+				return false;  
+			}	
+		}
+		else
+		{
+			m_turn_speed[ rotAxis ] -= friction;
+		}
+	}
+	float cur_turn_dist = m_turn_speed[ rotAxis ] * m_time;
+	
+	if ( !( m_movingobj_flags & ( MOVINGOBJ_FLAG_CONST_ROTX << rotAxis ) ) )
+	{
+		bool positive = m_turn_ang_target[ rotAxis ] > m_cur_turn_ang[ rotAxis ];
+		m_cur_turn_ang[ rotAxis ] += cur_turn_dist;
+		if ( positive != ( m_turn_ang_target[ rotAxis ] > m_cur_turn_ang[ rotAxis ] ) )
+		{
+			//done rotating!
+			done = true;
+			// and deltaYRot should be altered so as to hit 0 exactly:
+			cur_turn_dist += m_turn_ang_target[ rotAxis ] - m_cur_turn_ang[ rotAxis ];
+	//		m_cur_turn_ang[ rotAxis ] = m_turn_ang_target[ rotAxis ];
+		}
+	}
+
+	Rot( rotAxis, cur_turn_dist );
+	
+	return ( done );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::RotUpdate( void )
+{   
+	// rotate states:
+	for ( int i = 0; i < 3; i++ )
+	{
+		if ( m_movingobj_status & ( MOVINGOBJ_STATUS_ROTX << i ) )
+		{
+			if ( Rotate( i ) )
+			{
+				m_movingobj_status &= ~( MOVINGOBJ_STATUS_ROTX << i );
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+static bool s_do_collision_check(const Mth::Vector *p_v0, const Mth::Vector *p_v1, Mth::Vector *p_collisionPoint, 
+								 STriangle *p_lastTriangle)
+{
+	// First, see if there is a collision with the last triangle, and if there is, use that.
+	Mth::Vector d=*p_v1-*p_v0;
+	float alpha=0.0f;
+	if (Nx::CCollObj::sRayTriangleCollision(p_v0, &d,
+											&p_lastTriangle->mpVertices[0],
+											&p_lastTriangle->mpVertices[1],
+											&p_lastTriangle->mpVertices[2], &alpha))
+	{
+		*p_collisionPoint = *p_v0+d*alpha;
+		return true;
+	}
+	else
+	{
+		CFeeler feeler;
+		if (feeler.GetCollision(*p_v0,*p_v1, false))
+		{
+			Nx::CCollObj* p_col_obj=feeler.GetSector();
+			Dbg_MsgAssert(p_col_obj->IsTriangleCollision(),("Not triangle collision !!!"));
+	
+			Nx::CCollObjTriData* p_tri_data=p_col_obj->GetGeometry();
+			Dbg_MsgAssert(p_tri_data,("NULL p_tri_data"));
+	
+			int face_index=feeler.GetFaceIndex();
+			p_lastTriangle->mpVertices[0]=p_col_obj->GetVertexPos(p_tri_data->GetFaceVertIndex(face_index, 0));
+			p_lastTriangle->mpVertices[1]=p_col_obj->GetVertexPos(p_tri_data->GetFaceVertIndex(face_index, 1));
+			p_lastTriangle->mpVertices[2]=p_col_obj->GetVertexPos(p_tri_data->GetFaceVertIndex(face_index, 2));
+	
+			// if there is a collision, then snap to it
+			*p_collisionPoint = feeler.GetPoint();
+	
+			return true;
+		}
+	}
+	return false;	
+}
+
+
+bool CMotionComponent::StickToGround()
+{
+	if ( ( !m_col_dist_above ) && ( !m_col_dist_below ) )
+	{
+		return false;
+	}	
+
+	if ( m_point_stick_to_ground )
+	{
+		// standard stick to ground
+		// (also need a car version,
+		// which will be a separate component)
+
+		Mth::Vector v0, v1;
+
+		// make sure our X vector is correct (got Z from the nav matrix...)
+		GetObject()->m_matrix[ X ] = Mth::CrossProduct( GetObject()->m_matrix[ Y ], GetObject()->m_matrix[ Z ]);
+		GetObject()->m_matrix[ X ].Normalize();
+
+		v0.Set( 0.0f, FEET_TO_INCHES( fabs( m_col_dist_above ) ), 0.0f );
+		v1.Set( 0.0f, -FEET_TO_INCHES( fabs( m_col_dist_below ) ), 0.0f );
+
+		v0 += GetObject()->m_pos;
+		v1 += GetObject()->m_pos;
+
+		// get disatnce to ground
+		// and snap the skater to it
+
+		Mth::Vector collision_point;
+		if (s_do_collision_check(&v0, &v1, &collision_point, &m_last_triangle))
+		{
+			GetObject()->m_pos=collision_point;
+			return true;
+		}
+		return false;	
+	}
+	else
+	{
+		// if it's a car, then it's big
+		Mth::Vector v0, v1, vectForward, vectLeft, leftCollidePoint, rightCollidePoint;
+		bool leftCollision = false;
+		bool rightCollision = false;
+		CFeeler		feeler;
+
+		GetObject()->m_matrix[ Y ].Set( 0.0f, 1.0f, 0.0f );
+		// make sure our X vector is correct (got Y and Z from the nav matrix...)
+		GetObject()->m_matrix[ X ] = Mth::CrossProduct( GetObject()->m_matrix[ Y ], GetObject()->m_matrix[ Z ]);
+		GetObject()->m_matrix[ X ].Normalize();
+
+		vectForward = GetObject()->m_matrix[ Z ] * FEET_TO_INCHES( 6.0f );
+		vectLeft = GetObject()->m_matrix[ X ] * FEET_TO_INCHES( 2.5f );
+
+		GetObject()->m_pos -= vectForward;
+		GetObject()->m_pos += vectLeft;
+
+		v0 = v1 = GetObject()->m_pos;
+		v0[ Y ] += FEET_TO_INCHES( m_col_dist_above );
+		v1[ Y ] -= FEET_TO_INCHES( m_col_dist_below );
+
+		// find a collision point under the left rear:
+		if (s_do_collision_check(&v0, &v1, &leftCollidePoint, &m_car_left_rear_last_triangle))
+		{
+			leftCollision = true;
+		}
+		
+		GetObject()->m_pos -= vectLeft * 2;
+
+		v0 = v1 = GetObject()->m_pos;
+		v0[ Y ] += FEET_TO_INCHES( m_col_dist_above );
+		v1[ Y ] -= FEET_TO_INCHES( m_col_dist_below );
+
+		// find a collision point under the right rear:
+		if (s_do_collision_check(&v0, &v1, &rightCollidePoint, &m_car_right_rear_last_triangle))
+		{
+			rightCollision = true;
+			if ( leftCollision )
+			{
+				GetObject()->m_matrix[ X ] = leftCollidePoint - rightCollidePoint;
+				GetObject()->m_matrix[ X ].Normalize( );
+			}
+		}
+		
+		GetObject()->m_pos += vectForward * 2;
+		GetObject()->m_pos += vectLeft;
+
+		v0 = v1 = GetObject()->m_pos;
+		v0[ Y ] += FEET_TO_INCHES( m_col_dist_above );
+		v1[ Y ] -= FEET_TO_INCHES( m_col_dist_below );
+
+		GetObject()->m_pos -= vectForward;
+
+		Mth::Vector mid_front_collide_point;
+		if (s_do_collision_check(&v0, &v1, &mid_front_collide_point, &m_car_mid_front_last_triangle))
+		{
+			if ( rightCollision && leftCollision )
+			{
+				GetObject()->m_pos[ Y ] = leftCollidePoint[ Y ] + rightCollidePoint[ Y ];
+				GetObject()->m_pos[ Y ] /= 2;
+				GetObject()->m_pos[ Y ] += mid_front_collide_point.GetY();
+				GetObject()->m_pos[ Y ] /= 2;
+
+				Mth::Vector heading;
+
+				heading = leftCollidePoint + rightCollidePoint;
+				heading /= 2;
+				heading -= mid_front_collide_point;
+				heading *= -1;
+				heading.Normalize( );
+
+				GetObject()->m_matrix[ Z ] += heading;
+				GetObject()->m_matrix[ Z ] /= 2;
+
+				GetObject()->m_matrix[ Y ] = Mth::CrossProduct( GetObject()->m_matrix[ Z ], GetObject()->m_matrix[ X ] );
+				GetObject()->m_matrix[ Y ].Normalize( );
+
+				return true;
+			}
+		}
+
+		// (Mick) This assertion has been removed, as cars sometimes drive where the skater cannot go, and hence into no collision		
+		//	Dbg_MsgAssert( 0, ( "StickToGround car %s was driving on an area with no collision", Script::FindChecksumName(GetID()) ) );
+
+		return false;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ( "NULL p_info sent to CMotionComponent::GetDebugInfo" ) );
+	CBaseComponent::GetDebugInfo(p_info);
+	
+	p_info->AddChecksum( CRCD(0x9e6b250,"m_movingobj_status"), m_movingobj_status );
+	p_info->AddChecksum( CRCD(0xe5dff85a,"m_movingobj_flags"), m_movingobj_flags );
+	p_info->AddFloat( CRCD(0xc2d3b58d,"m_moveto_speed"), m_moveto_speed );
+	p_info->AddFloat( CRCD(0x3a3ae81a,"m_acceleration"), m_acceleration );
+	p_info->AddFloat( CRCD(0x4a2c5549,"m_deceleration"), m_deceleration );
+	p_info->AddFloat( CRCD(0x472b8f9b,"m_max_vel"), m_max_vel );
+	p_info->AddFloat( CRCD(0xc37e4e25,"m_vel_z"), m_vel_z );
+	p_info->AddFloat( CRCD(0xcab44eaf,"m_stop_dist"), m_stop_dist );
+
+	// p_info->AddFloat( "m_col_dist_above", m_col_dist_above );
+	// p_info->AddFloat( "m_col_dist_below", m_col_dist_below );
+	// p_info->AddVector( "m_last_triangle_v0", m_last_triangle_v0 );
+	// p_info->AddVector( "m_last_triangle_v1", m_last_triangle_v1 );
+	// p_info->AddVector( "m_last_triangle_v2", m_last_triangle_v2 );
+	// p_info->AddVector( "m_last_normal", m_last_normal );
+	p_info->AddInteger( CRCD(0xa13363b0,"m_point_stick_to_ground"), m_point_stick_to_ground );
+	
+	p_info->AddFloat( CRCD(0xac5b4824,"m_moveto_dist"), m_moveto_dist );
+	p_info->AddFloat( CRCD(0x3e3570a6,"m_moveto_dist_traveled"), m_moveto_dist_traveled );
+	p_info->AddFloat( CRCD(0x93e73df1,"m_moveto_speed_target"), m_moveto_speed_target );
+	p_info->AddFloat( CRCD(0x6819fd8d,"m_moveto_acceleration"), m_moveto_acceleration );	
+	
+	p_info->AddInteger( CRCD(0xd6d94326,"m_moveto_node_num"), m_moveto_node_num );
+	
+	// p_info->AddVector( "m_turn_ang_target", m_turn_ang_target );
+	// p_info->AddVector( "m_cur_turn_ang", m_cur_turn_ang );
+	// p_info->AddVector( "m_turn_speed_target", m_turn_speed_target );
+	p_info->AddVector( CRCD(0x9d27cc70,"m_turn_speed"), m_turn_speed );
+	// p_info->AddVector( "m_delta_turn_speed", m_delta_turn_speed );
+	// p_info->AddVector( "m_angular_friction", m_angular_friction );
+	p_info->AddVector( CRCD(0xfa4307e,"m_orig_pos"), m_orig_pos );
+	
+	// p_info->AddFloat( "m_stop_dist_traveled", m_stop_dist_traveled );
+	// p_info->AddFloat( "m_vel_z_start", m_vel_z_start );
+	// p_info->AddFloat( "m_min_stop_vel", m_min_stop_vel );
+	
+	p_info->AddVector( CRCD(0xc3e1cded,"m_stored_pos"), m_stored_pos );
+	p_info->AddInteger( CRCD(0x56e54ee5,"m_stored_node"), m_stored_node );
+	
+	// CPathObjectTracker*		mp_path_object_tracker;
+	
+	// Mth::SlerpInterpolator*	mp_slerp;
+	// p_info->AddFloat( "m_rot_time_target", m_rot_time_target );
+	p_info->AddFloat( CRCD(0x622c6c9b,"m_rot_time"), m_rot_time );
+	
+	p_info->AddInteger( CRCD(0x80dd9065,"m_start_node"), m_start_node );
+	p_info->AddInteger( CRCD(0xa80dc42,"m_current_node"), m_current_node );
+	
+	if (mp_pathOb)
+	{
+		Script::CStruct* p_pathOb_params = new Script::CStruct();
+		mp_pathOb->GetDebugInfo( p_pathOb_params	);
+		p_info->AddStructure( CRCD(0x81270c1f,"mp_pathOb"), p_pathOb_params );
+		delete p_pathOb_params;
+	}	
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCompositeObject* CMotionComponent::GetNextObjectOnPath( float range )
+{
+	CCompositeObject* p_closest_ob=NULL;
+
+	if ( mp_path_object_tracker )
+	{		
+		const CSmtPtr* pp_object_list = mp_path_object_tracker->GetObjectList();
+
+		float min_dist = 0.0f;
+		for (int i=0; im_matrix[Z],p_ob->GetMatrix()[Z]) >= 0.0f &&
+					Mth::DotProduct(GetObject()->m_matrix[Z],p_ob->GetPos()-GetObject()->GetPos()) >= 0.0f
+					)
+				{
+					if (GetMotionComponentFromObject(p_ob)->GetDestinationPathNode()==GetDestinationPathNode() &&
+						GetMotionComponentFromObject(p_ob)->GetPreviousPathNode()!=GetPreviousPathNode())
+					{
+						// Make vehicles on converging paths not collide, otherwise
+						// a pile up happens at the start of the car paths in the school
+						// Note: Actually, this didn't fix it ... might be a script bug?
+					}
+					else
+					{
+						float d=Mth::Distance( GetObject()->GetPos(), p_ob->GetPos());
+						if( d <= range )
+						{
+							if( !p_closest_ob || d < min_dist )
+							{
+								min_dist = d;
+								p_closest_ob = p_ob;
+							}
+						}
+					}	
+				}
+			}
+		}
+	}
+	return p_closest_ob;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void CMotionComponent::FollowLeader( void )
+{   
+	Dbg_MsgAssert( mp_follow_ob,( "Shouldn't be in follow-leader state without mp_follow_ob." ));
+	
+	mp_follow_ob->GetNewPathPointFromObjectBeingFollowed();
+	
+	float old_y = GetObject()->m_pos[Y];
+	mp_follow_ob->CalculatePositionAndOrientation(&GetObject()->m_pos,&GetObject()->m_matrix);
+	if (m_leave_y_unaffected_when_following_leader)
+	{
+		GetObject()->m_pos[Y]=old_y;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::FollowLeader_Init( Script::CStruct* pParams )
+{
+	if (pParams->ContainsFlag(CRCD(0xd443a2bc,"Off")))
+	{
+		m_movingobj_status &= ~MOVINGOBJ_STATUS_FOLLOWING_LEADER;
+		if (mp_follow_ob)
+		{
+			delete mp_follow_ob;
+			mp_follow_ob=NULL;
+		}
+		return;
+	}		
+		
+	m_leave_y_unaffected_when_following_leader=pParams->ContainsFlag("LeaveYUnaffected");
+	
+	m_movingobj_status |= MOVINGOBJ_STATUS_FOLLOWING_LEADER;
+	
+	// these can't coexist:
+	m_movingobj_status &= ~MOVINGOBJ_STATUS_MOVETO;
+
+	//	GetMotionComponent()->m_movingobj_status &= ~MOVINGOBJ_STATUS_LOCKED_TO_OBJECT;
+	Obj::CLockObjComponent* pLockObjComponent = GetLockObjComponentFromObject(GetObject());
+	pLockObjComponent->EnableLock( false );
+
+	if ( !mp_follow_ob )
+	{
+		mp_follow_ob = new CFollowOb;
+	}	
+	mp_follow_ob->Reset();
+	
+	float distance=0.0f;
+	pParams->GetFloat(CRCD(0xe36d657e,"Distance"),&distance);
+	mp_follow_ob->SetDistance(distance);
+	
+	uint32 name=0;
+	pParams->GetChecksum(CRCD(0xa1dc81f9,"Name"),&name);
+	mp_follow_ob->SetLeaderName(name);
+	
+	if (pParams->ContainsFlag(CRCD(0xe19e310a,"OrientY")))
+	{
+		mp_follow_ob->OrientY();
+	}	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMotionComponent::DoJump( void )
+{
+	Mth::Vector new_pos;
+
+	// Gfx::AddDebugLine( m_jump_start_pos, m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 1 );
+	float distance = m_jump_speed * m_time + ( m_jump_gravity * 0.5f * m_time * m_time );
+	// printf("m_jump_speed = %f, m_jump_gravity = %f\n, m_jump_original_speed = %f, m_jump_heading = (%f, %f, %f)\n", m_jump_speed, m_jump_gravity, m_jump_original_speed, m_jump_heading[X], m_jump_heading[Y], m_jump_heading[Z]);
+	new_pos = GetObject()->m_pos;
+	if ( m_jump_use_heading )
+	{		
+		// first figure the pos if we stayed on our original heading
+		new_pos += ( m_jump_heading * m_jump_original_speed * m_time );
+		new_pos[Y] = GetObject()->m_pos[Y];
+	}
+	new_pos[Y] += distance;
+
+	m_jump_speed += m_jump_gravity * m_time;
+
+	// do land checks if we're on our way down
+	if ( distance < 0.0f )
+	{
+		if ( m_jump_use_land_height )
+		{
+			if ( new_pos[Y] < m_jump_land_height )
+			{
+				m_movingobj_status &= ~MOVINGOBJ_STATUS_JUMPING;
+				new_pos[Y] = m_jump_land_height;
+			}
+		}
+		else
+		{
+			// use collision checks
+			CFeeler feeler;
+			feeler.m_start = new_pos + Mth::Vector( 0, m_jump_col_dist_above, 0 );
+			feeler.m_end = new_pos + Mth::Vector( 0, -m_jump_col_dist_below, 0 );
+			if ( feeler.GetCollision() )
+			{
+				m_movingobj_status &= ~MOVINGOBJ_STATUS_JUMPING;
+				if ( m_jump_start_pos[X] != GetObject()->m_pos[X] || m_jump_start_pos[Z] != GetObject()->m_pos[Z] )
+				{
+					// printf("StickToGround %s\n",Script::FindChecksumName(GetID()));
+					// GetMotionComponent()->StickToGround();
+					// m_jump_start_pos = m_pos;
+				
+					/*if ( new_pos[Y] < m_jump_start_pos[Y])
+					{
+						new_pos[Y] = m_jump_start_pos[Y];
+						GetMotionComponent()->m_movingobj_status &= ~MOVINGOBJ_STATUS_JUMPING;
+					}*/
+				}
+				else
+				{
+					// new_pos[Y] = m_jump_start_pos[Y];
+					// GetMotionComponent()->m_movingobj_status &= ~MOVINGOBJ_STATUS_JUMPING;
+				}
+			}
+		}
+	}
+	m_jump_pos = new_pos;
+}
+
+
+
+	
+}
diff --git a/Code/Gel/Components/motioncomponent.h b/Code/Gel/Components/motioncomponent.h
new file mode 100644
index 0000000..394cbdd
--- /dev/null
+++ b/Code/Gel/Components/motioncomponent.h
@@ -0,0 +1,275 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       MotionComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/5/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_MOTIONCOMPONENT_H__
+#define __COMPONENTS_MOTIONCOMPONENT_H__
+
+#include 
+#include 
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_MOTION CRCD(0xa015e17,"Motion")
+#define		GetMotionComponent() ((Obj::CMotionComponent*)GetComponent(CRC_MOTION))
+#define		GetMotionComponentFromObject(pObj) ((Obj::CMotionComponent*)(pObj)->GetComponent(CRC_MOTION))
+
+namespace Mth
+{
+	class SlerpInterpolator;
+}
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CPathOb;
+	class CPathObjectTracker;
+    class CFollowOb;
+
+
+	enum
+	{
+		// not used
+		// MOVINGOBJ_STATUS_IDLE 				= 0,
+
+		MOVINGOBJ_STATUS_ROTX 				= ( 1 << 0  ),
+		MOVINGOBJ_STATUS_ROTY 				= ( 1 << 1  ),
+		MOVINGOBJ_STATUS_ROTZ 				= ( 1 << 2  ),
+		MOVINGOBJ_STATUS_MOVETO 			= ( 1 << 3  ),
+		MOVINGOBJ_STATUS_ON_PATH 			= ( 1 << 4  ),
+		MOVINGOBJ_STATUS_QUAT_ROT 			= ( 1 << 5  ),
+		MOVINGOBJ_STATUS_LOOKAT 			= ( 1 << 6  ), // will be on in addition to rotx, y or z flags.
+		MOVINGOBJ_STATUS_HOVERING 			= ( 1 << 7  ),
+		MOVINGOBJ_STATUS_JUMPING  			= ( 1 << 8  ),
+		MOVINGOBJ_STATUS_FOLLOWING_LEADER	= ( 1 << 9  ),
+
+		// moved into lock obj component
+		// MOVINGOBJ_STATUS_LOCKED_TO_OBJECT	= ( 1 << 10 ),
+
+		// don't do any other processing, until this flag is off:
+		// wasn't being used...
+		// MOVINGOBJ_STATUS_HIGH_LEVEL 		= ( 1 << 31 ), // running C-code on a high level.
+	};
+
+	#define MOVINGOBJ_STATUS_ROTXYZ ( MOVINGOBJ_STATUS_ROTX | MOVINGOBJ_STATUS_ROTY | MOVINGOBJ_STATUS_ROTZ )
+	
+	// movement flags (to be used with m_movingobj_flags)
+	#define MOVINGOBJ_FLAG_STICK_TO_GROUND		( 1 << 2 )
+	#define MOVINGOBJ_FLAG_STOP_ALONG_PATH		( 1 << 3 )
+	#define MOVINGOBJ_FLAG_INDEPENDENT_HEADING	( 1 << 4 )
+	#define MOVINGOBJ_FLAG_CONST_ROTX			( 1 << 5 )
+	#define MOVINGOBJ_FLAG_CONST_ROTY			( 1 << 6 )
+	#define MOVINGOBJ_FLAG_CONST_ROTZ			( 1 << 7 )
+	#define MOVINGOBJ_FLAG_CONSTANT_HEIGHT		( 1 << 8 )
+	#define MOVINGOBJ_FLAG_RANDOM_PATH_MODE		( 1 << 9 )
+	#define MOVINGOBJ_FLAG_NO_PITCH_PLEASE		( 1 << 10 )
+
+// Used to store the coords of the last triangle collided with,
+// so that it can be checked first next time as a collision speed optimization.
+struct STriangle
+{
+	Mth::Vector mpVertices[3];
+	void	Init() {mpVertices[0].Set();mpVertices[1].Set();mpVertices[2].Set();}
+};
+
+class CMotionComponent : public CBaseComponent
+{
+public:
+    CMotionComponent();
+    virtual ~CMotionComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual void					ProcessWait( Script::CScript * pScript );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+	static CBaseComponent*			s_create();
+
+	virtual void					GetDebugInfo( Script::CStruct* p_info );
+	
+public:
+	CPathOb*				GetPathOb();
+	void					Reset();
+	bool					IsMoving( void );
+	bool 					IsRotating( void );
+
+	bool					IsHovering();
+	void					GetHoverOrgPos( Mth::Vector *p_orgPos );
+
+	// Added by Ken. If the object is following a path, this will return the node
+	// index of the node it is heading towards. Returns -1 otherwise.
+public:
+	void					DoPathPhysics();
+	int						GetDestinationPathNode();
+	int						GetPreviousPathNode();
+	void					EnsurePathobExists( CCompositeObject* pCompositeObj );
+	CCompositeObject*   	GetNextObjectOnPath(float range);
+	void 					OrientToNode( Script::CStruct* pNodeData );
+	
+	void					InitPath( int nodeNumber );
+	void					HitWaypoint( int nodeIndex );
+	bool					PickRandomWaypoint();
+
+	bool					LookAt_Init( Script::CStruct* pParams, const Mth::Vector& lookAtPos );
+	void					LookAtNodeLinked_Init( Script::CStruct* pParams, Script::CScript* pScript );
+	void					LookAtNode_Init( Script::CStruct* pParams, Script::CScript* pScript );
+	void					LookAtPos_Init( Script::CStruct* pParams, bool absolute );
+//	bool					LookAtObject_Init( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					Move_Init( Script::CStruct* pParams, Script::CScript* pScript, Mth::Vector pos, int nodeNum = -1 );
+	void					MoveToPos_Init( Script::CStruct* pParams, Script::CScript* pScript, bool absolute );
+	void					MoveToNode_Init( Script::CStruct* pParams, Script::CScript* pScript );
+	void					MoveToLink_Init( Script::CStruct* pParams, Script::CScript* pScript );
+	void					FollowPath_Init( Script::CStruct* pParams );
+	void					FollowPathLinked_Init( Script::CStruct* pParams, Script::CScript* pScript );
+	void					FollowStoredPath_Init( Script::CStruct* pParams );
+	void					FollowLeader_Init( Script::CStruct* pParams );
+	void					QuatRot_Init( Script::CStruct* pParams );
+	void					RotAxis_Init( Script::CStruct* pParams, Script::CScript* pScript, int whichAxis );
+
+	// used for looking at a node or position...
+	bool					SetUpLookAtPos( const Mth::Vector& lookToPos, const Mth::Vector& currentPos, int headingAxis, int rotAxis, float threshold=0.0f );
+	void					QuatRot_Setup( Script::CStruct* pParams, const Mth::Matrix& rot );
+	void					SetCorrectRotationDirection( int rotAxis );
+	void					RotateToFacePos( const Mth::Vector pos );
+	
+	void					Rot( int rotAxis, float deltaRot );
+	bool					Rotate( int rotAxis );
+	void					RotUpdate( void );
+	bool					Move( void );
+	void					FollowPath( void );
+	void					QuatRot( void );
+	bool					StickToGround();
+	void					UndoHover( void );
+	void					ApplyHover( void );
+public:
+	// GJ:  Had to make this public during the transition,
+	// because the moving objects need to access it
+	// eventually, everything will be manipulated through
+	// accessor functions				
+    int						m_movingobj_status;
+	int						m_movingobj_flags;
+	
+	float					m_moveto_speed;
+	float					m_acceleration;
+	float					m_deceleration;
+	float					m_max_vel;
+	float					m_vel_z;
+	float					m_stop_dist;
+
+	// when sticking to the ground, collision line length:
+	float					m_col_dist_above;
+	float					m_col_dist_below;
+	STriangle				m_last_triangle;
+	bool					m_point_stick_to_ground;
+	
+	STriangle				m_car_left_rear_last_triangle;
+	STriangle				m_car_right_rear_last_triangle;
+	STriangle				m_car_mid_front_last_triangle;
+
+	// A CPathObjectTracker keeps track of all the objects that are following a
+	// particular path. Stored so that the object can un-register with the tracker
+	// when it starts following a new path.
+	CPathObjectTracker*		mp_path_object_tracker;
+protected:
+	Mth::Vector				m_moveto_pos;
+	float					m_moveto_dist;
+	float					m_moveto_dist_traveled;
+//	float					m_moveto_speed;			// TEMPORARILY PUBLIC
+	float					m_moveto_speed_target;
+	float					m_moveto_acceleration;
+	
+	// so that we can call SpawnObjScript or SwitchObjScript when we arrive...
+	int						m_moveto_node_num;
+
+	Mth::Vector				m_turn_ang_target;
+	Mth::Vector				m_cur_turn_ang;
+	Mth::Vector				m_turn_speed_target;
+	Mth::Vector				m_turn_speed;
+	Mth::Vector				m_delta_turn_speed;
+	Mth::Vector				m_angular_friction;
+	Mth::Vector				m_orig_pos;
+
+	// path traversal:
+//	float					m_acceleration;			// TEMPORARILY PUBLIC
+//	float					m_deceleration;			// TEMPORARILY PUBLIC
+//	float					m_max_vel;				// TEMPORARILY PUBLIC
+//	float					m_vel_z;				// TEMPORARILY PUBLIC
+//	float					m_stop_dist;        	// TEMPORARILY PUBLIC
+	float					m_stop_dist_traveled;
+	float					m_vel_z_start;
+	float					m_min_stop_vel;
+	
+	// gotta remember where to return to when going to a higher level:
+	Mth::Vector				m_stored_pos;
+	int						m_stored_node;
+
+	float					m_rot_time_target;
+	float					m_rot_time;
+	Mth::SlerpInterpolator*	mp_slerp;
+
+	float					m_y_before_applying_hover;
+	float					m_hover_offset;
+	float					m_hover_amp;
+	int						m_hover_period;
+
+	float					m_time;
+
+protected:
+	// moved this down from CMovingObject
+	// (information about the node that created this object)
+	int						m_start_node; 	// renamed, as we want to use it for path objs, but not for node identification
+	int						m_current_node;  // this may change if the object is moving... (needs to be at CMovingObject level for querying)
+
+protected:
+	CPathOb*				mp_pathOb;
+	
+// Mo stuff from MovingObject.h		
+		
+		void					FollowLeader( void );
+		
+		// Pointer to the follow ob, which contains all the info required to follow
+		// an object on a path. Done this way so as to not use up memory for every instance
+		// of a CMovingObject. (similar to CPathOb)
+		CFollowOb*				mp_follow_ob;
+
+		// K: I think I should have put this in CFollowOb too?
+		bool					m_leave_y_unaffected_when_following_leader;
+
+private:		
+		
+		void					DoJump( void );
+		float					m_jump_speed;
+		float					m_jump_gravity;
+		Mth::Vector				m_jump_heading;
+		bool					m_jump_use_heading;
+		Mth::Vector				m_jump_pos;
+		float					m_jump_original_speed;
+		float					m_jump_col_dist_above;
+		float					m_jump_col_dist_below;
+		float					m_jump_y;
+		bool					m_jump_use_land_height;
+		float					m_jump_land_height;
+		// The whole start pos is stored rather than just the y so that it knows whether 
+		// it needs to do another collision check once the jump has got back to the start y.
+		// If the x or z has changed then it needs to do another collision check.
+		Mth::Vector	 		  	m_jump_start_pos;
+
+	
+	
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/rigidbodycomponent.cpp b/Code/Gel/Components/rigidbodycomponent.cpp
new file mode 100644
index 0000000..ffbc58b
--- /dev/null
+++ b/Code/Gel/Components/rigidbodycomponent.cpp
@@ -0,0 +1,1667 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RigidBodyComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  1/22/3
+//****************************************************************************
+
+// An object component which dictates the motion of an object using an approximation of rigidbody physics.
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+Nx::CCollCache CRigidBodyComponent::s_collision_cache;
+bool CRigidBodyComponent::s_debug_lines_on = false;
+bool CRigidBodyComponent::s_draw_skater_collision_circles = false;
+float CRigidBodyComponent::s_skater_head_height;
+Tmr::Time CRigidBodyComponent::s_collide_sound_allowed_time = 0;
+CRigidBodyComponent::SContactState CRigidBodyComponent::sp_contact_states [ CRigidBodyComponent::vRP_MAX_NUM_CONTACTS ];
+
+#ifdef __NOPT_DEBUG__
+bool CRigidBodyComponent::s_contact_states_in_use = false;
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CRigidBodyComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CRigidBodyComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CRigidBodyComponent::CRigidBodyComponent() : CBaseComponent()
+{
+	SetType( CRC_RIGIDBODY );
+
+	mp_sound_component = NULL;
+	
+	// setup defaults
+	
+	m_const_acc = vRP_GRAVITATIONAL_ACCELERATION;
+	m_mass_over_moment = vRP_DEFAULT_MASS_OVER_MOMENT;
+	m_coeff_restitution = vRP_DEFAULT_COEFF_RESTITUTION;
+	m_coeff_friction = vRP_DEFAULT_COEFF_FRICTION;
+	m_spring_const = vRP_DEFAULT_SPRING_CONST;
+	m_linear_velocity_sleep_point_sqr = vRP_DEFAULT_LINEAR_VELOCITY_SLEEP_POINT * vRP_DEFAULT_LINEAR_VELOCITY_SLEEP_POINT;
+	m_angular_velocity_sleep_point_sqr = vRP_DEFAULT_ANGULAR_VELOCITY_SLEEP_POINT * vRP_DEFAULT_ANGULAR_VELOCITY_SLEEP_POINT;
+	m_skater_collision_impulse_factor = vRP_DEFAULT_SKATER_COLLISION_IMPULSE_FACTOR;
+	m_skater_collision_rotation_factor = vRP_DEFAULT_SKATER_COLLISION_ROTATION_FACTOR;
+	m_skater_collision_radius = vRP_DEFAULT_SKATER_COLLISION_RADIUS;
+	m_skater_collision_application_radius = vRP_DEFAULT_SKATER_COLLISION_APPLICATION_RADIUS;
+	m_cos_skater_collision_assent = cosf(DEGREES_TO_RADIANS(vRP_DEFAULT_SKATER_COLLISION_ASSENT));
+	m_sin_skater_collision_assent = sinf(DEGREES_TO_RADIANS(vRP_DEFAULT_SKATER_COLLISION_ASSENT));
+	m_ignore_skater_duration = vRP_DEFAULT_IGNORE_SKATER_DURATION;
+	m_model_offset.Set(0.0f, 0.0f, 0.0f);
+	
+	m_num_contacts = 0;
+	
+	m_script_names.collide = m_script_names.bounce = m_script_names.settle = m_script_names.stuck = 0;
+	mp_script_params = NULL;
+	
+	m_sound_setup.collide_sound = m_sound_setup.bounce_sound = NULL;
+	m_sound_setup.collide_mute_delay = vRP_DEFAULT_COLLIDE_MUTE_DELAY;
+	m_sound_setup.global_collide_mute_delay = vRP_DEFAULT_GLOBAL_COLLIDE_MUTE_DELAY;
+	m_sound_setup.bounce_velocity_callback_threshold = vRP_DEFAULT_BOUNCE_VELOCITY_CALLBACK_THRESHOLD;
+	m_sound_setup.bounce_velocity_full_speed = vRP_DEFAULT_BOUNCE_VELOCITY_FULL_SPEED;
+	
+	m_collide_sound_allowed_time = 0;
+	
+	m_die_countdown = -1.0f;
+	
+	mp_contacts = NULL;
+	
+	#ifdef __NOPT_ASSERT__
+	m_sound_type_id = 0;
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CRigidBodyComponent::~CRigidBodyComponent()
+{
+	delete [] mp_contacts;
+	delete mp_script_params;
+	delete m_sound_setup.bounce_sound;
+	delete m_sound_setup.collide_sound;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// NOTE: right now rigidbodies ignore moving collidables; if we want to change this, we should account for the moving collidables' velocity
+	// in the collision resolution code
+	// NOTE: probably want to add some small random rotation when the skater hits the object; two reasons; one, when you hit a line of cones, right
+	// now it seems too orderly; two, when you push a cone ahead of you, the cone's dynamics should be more jerky and less smooth
+	
+	// NOTE: don't make any contacts within a foot of the center of mass or the object is in danger of falling through the ground
+
+	// IDEA: for a skateboard rigidbody, have four of the contacts only feel friction parallel to the wheel's axis
+
+	// potential optimization; collision detect only every other frame, unless the last-ditch feeler says to do it this frame
+
+	m_state = ASLEEP;
+
+	m_vel.Set(0.0f, 0.0f, 0.0f);
+	m_rotvel.Set(0.0f, 0.0f, 0.0f);
+	
+	m_matrix.Ident();
+	
+	m_pos = GetObject()->GetPos();
+	
+	m_orientation = GetObject()->GetMatrix();
+	m_orientation.Normalize();
+	m_orientation.GetMatrix(m_matrix);
+	
+	pParams->GetVector(CRCD(0xc4c809e, "vel"), &m_vel);
+	
+	pParams->GetVector(CRCD(0xfb1a83b2, "rotvel"), &m_rotvel);
+
+	pParams->GetVector(CRCD(0x737612c6, "center_of_mass"), &m_center_of_mass);
+
+	pParams->GetFloat(CRCD(0x6fae5c9a, "const_acc"), &m_const_acc);
+
+	pParams->GetFloat(CRCD(0x60b4860d, "coeff_restitution"), &m_coeff_restitution);
+
+	pParams->GetFloat(CRCD(0xf9d75930, "coeff_friction"), &m_coeff_friction);
+	
+	pParams->GetFloat(CRCD(0xf1ecb30f, "spring_const"), &m_spring_const);
+
+	if (pParams->ContainsComponentNamed(CRCD(0xc11e4dd8, "linear_velocity_sleep_point")))
+	{
+		pParams->GetFloat(CRCD(0xc11e4dd8, "linear_velocity_sleep_point"), &m_linear_velocity_sleep_point_sqr);
+		m_linear_velocity_sleep_point_sqr *= m_linear_velocity_sleep_point_sqr;
+	}
+
+	if (pParams->ContainsComponentNamed(CRCD(0xa457ece3, "angular_velocity_sleep_point")))
+	{
+		pParams->GetFloat(CRCD(0xa457ece3, "angular_velocity_sleep_point"), &m_angular_velocity_sleep_point_sqr);
+		m_angular_velocity_sleep_point_sqr *= m_angular_velocity_sleep_point_sqr;
+	}
+
+	pParams->GetFloat(CRCD(0x3390d1e5, "skater_collision_impulse_factor"), &m_skater_collision_impulse_factor);
+	
+	pParams->GetFloat(CRCD(0x5a18f61f, "skater_collision_rotation_factor"), &m_skater_collision_rotation_factor);
+
+	pParams->GetFloat(CRCD(0x85f112c1, "skater_collision_radius"), &m_skater_collision_radius);
+
+	pParams->GetFloat(CRCD(0x8ab2db68, "skater_collision_application_radius"), &m_skater_collision_application_radius);
+
+	if (pParams->ContainsComponentNamed(CRCD(0xe9156e6d, "skater_collision_assent")))
+	{
+		float skater_collision_assent;
+		pParams->GetFloat(CRCD(0xe9156e6d, "skater_collision_assent"), &skater_collision_assent);
+		m_cos_skater_collision_assent = cosf(DEGREES_TO_RADIANS(skater_collision_assent));
+		m_sin_skater_collision_assent = sinf(DEGREES_TO_RADIANS(skater_collision_assent));
+	}
+
+	pParams->GetFloat(CRCD(0x1f4d0ca5, "ignore_skater_duration"), &m_ignore_skater_duration);
+	
+	pParams->GetChecksum(CRCD(0x58cdc0f0, "CollideScript"), &m_script_names.collide);
+	pParams->GetChecksum(CRCD(0x2d075f90, "BounceScript"), &m_script_names.bounce);
+	pParams->GetChecksum(CRCD(0x571917fa, "SettleScript"), &m_script_names.settle);
+	pParams->GetChecksum(CRCD(0xccb84047, "StuckScript"), &m_script_names.stuck);
+	
+	if (pParams->ContainsComponentNamed(CRCD(0xdd85bda, "CallbackParams")))
+	{
+		Script::CStruct* p_struct;
+		pParams->GetStructure(CRCD(0xdd85bda, "CallbackParams"), &p_struct, Script::ASSERT);
+		
+		mp_script_params = new Script::CStruct;
+		*mp_script_params += *p_struct;
+	}
+	
+	// get sound setup
+	if (pParams->ContainsComponentNamed(CRCD(0x94956e48, "SoundType")))
+	{
+		uint32 sound_type_id;
+		pParams->GetChecksum(CRCD(0x94956e48, "SoundType"), &sound_type_id, Script::ASSERT);
+		
+		get_sound_setup(sound_type_id);
+		
+		#ifdef __NOPT_ASSERT__
+		m_sound_type_id = sound_type_id;
+		#endif
+	}
+	
+	m_flags.Set(DIE_UPON_SLEEP, pParams->ContainsFlag(CRCD(0x5e51924f, "DieUponSettling")));
+
+	// there are currently two ways to setup the contact points; either you pass them in as an array of offsets from the center of mass
+	// or you specify a generic geometry and pass parameters to that geometry
+	
+	EObjectGeometryType object_geometry = NO_GEOMETRY;
+	if (m_num_contacts == 0)
+	{
+		object_geometry = BOX;
+	}
+	
+	if (pParams->ContainsFlag(CRCD(0xf756b7c5, "box")))
+	{
+		object_geometry = BOX;
+	}
+	else if (pParams->ContainsFlag(CRCD(0x1cb1a2b6, "pyramid")))
+	{
+		object_geometry	= PYRAMID;
+	}
+	else if (pParams->ContainsFlag(CRCD(0x64fba415, "cylinder")))
+	{
+		object_geometry	= CYLINDER;
+	}
+	else if (pParams->ContainsFlag(CRCD(0x20689278, "triangle")))
+	{
+		object_geometry	= TRIANGLE;
+	}
+
+	// setup contacts using an array
+	if (pParams->ContainsComponentNamed(CRCD(0xccbfea8c, "contacts")))
+	{
+		Script::CArray* p_array;
+		pParams->GetArray(CRCD(0xccbfea8c, "contacts"), &p_array);
+		setup_contacts_from_array(p_array);
+		object_geometry = NO_GEOMETRY;
+	}
+	
+	// setup contacts using a generic geometry
+	switch (object_geometry)
+	{
+		// a rectangular prism with contacts at each corner
+		case BOX:
+		{
+			Mth::Vector top_half_dimensions(32.0f, 32.0f, 16.0f);
+			Mth::Vector bottom_half_dimensions;
+
+			pParams->GetVector("dimensions", &top_half_dimensions);
+			bottom_half_dimensions = top_half_dimensions;
+			pParams->GetVector("top_dimensions", &top_half_dimensions);
+			pParams->GetVector("bottom_dimensions", &bottom_half_dimensions);
+			top_half_dimensions /= 2.0f;
+			bottom_half_dimensions /= 2.0f;
+
+			setup_contacts_as_box(top_half_dimensions, bottom_half_dimensions);
+			
+			break;
+		}
+
+		// a pyramid with contacts at each corner along the bottom and one on the top
+		case PYRAMID:
+		{
+			float half_height = 42.0f;
+			float half_depth = 32.0f;
+			
+			pParams->GetFloat(CRCD(0xab21af0,"height"), &half_height);
+			pParams->GetFloat(CRCD(0x55ce396,"depth"), &half_depth);
+			half_height /= 2.0f;
+			half_depth /= 2.0f;
+			
+			setup_contacts_as_pyramid(half_height, half_depth);
+			
+			break;
+		}
+
+		// a cylinder with six contacts around the top and bottom
+		case CYLINDER:
+		{
+			float top_radius = 12.0f;
+			float bottom_radius;
+			float half_height = 60.0f;
+			int edges = 6;
+			
+			pParams->GetFloat(CRCD(0xc48391a5,"radius"), &top_radius);
+			bottom_radius = top_radius;
+			
+			pParams->GetFloat(CRCD(0x937e3b29,"top_radius"), &top_radius);
+			pParams->GetFloat(CRCD(0xe2ac8c3a,"bottom_radius"), &bottom_radius);
+			pParams->GetFloat(CRCD(0xab21af0,"height"), &half_height);
+			pParams->GetInteger(CRCD(0x4055f24a,"edges"), &edges);
+			half_height /= 2.0f;
+			
+			setup_contacts_as_cylinder(top_radius, bottom_radius, half_height, edges);
+			
+			break;
+		}
+
+		// a triangle with three contacts on the left and right
+		case TRIANGLE:
+		{
+			Mth::Vector half_dimensions(32.0f, 32.0f, 16.0f);
+			
+			pParams->GetVector(CRCD(0x1d82745a, "dimensions"), &half_dimensions);
+			half_dimensions /= 2.0f;
+			
+			setup_contacts_as_triangle(half_dimensions);
+	
+			break;
+		}
+		
+		// don't use a generic geometry
+		case NO_GEOMETRY:
+			Dbg_MsgAssert(m_num_contacts, ("Contact geometry not setup for rigidbody"));
+			break;
+	}
+	
+	// setup directional friction
+	if (pParams->ContainsComponentNamed(CRCD(0xc35cac0d, "directed_friction")))
+	{
+		Script::CArray* p_array;
+		pParams->GetArray(CRCD(0xc35cac0d, "directed_friction"), &p_array);
+		
+		Dbg_MsgAssert(p_array->GetSize() == m_num_contacts, ("Array of directed friction specifications must equal the number of contacts"));
+		
+		for (int n = p_array->GetSize(); n--; )
+		{
+			Script::CStruct* m_struct = p_array->GetStructure(n);
+			if (m_struct->ContainsComponentNamed(CRCD(0x806fff30, "none")))
+			{
+				mp_contacts[n].directed_friction = false;
+			}
+			else
+			{
+				mp_contacts[n].directed_friction = true;
+				m_struct->GetVector(NO_NAME, &mp_contacts[n].friction_direction, Script::ASSERT);
+			}
+		}
+	}
+			 	
+	// adjust contacts based on extra angles
+	if (pParams->ContainsComponentNamed(CRCD(0x17b93077, "rotate_contacts")))
+	{
+		Mth::Vector angles;
+		pParams->GetVector(CRCD(0x17b93077, "rotate_contacts"), &angles, Script::ASSERT);
+		Mth::Matrix matrix;
+		matrix.SetFromAngles(angles);
+		
+		for (int n = m_num_contacts; n--; )
+		{
+			mp_contacts[n].p = matrix.Rotate(mp_contacts[n].p);
+		}
+				 }
+
+	// adjust contacts based on node array angles
+	if (pParams->ContainsComponentNamed(CRCD(0x9d2d0915, "Angles")))
+	{
+		Mth::Vector angles;
+		SkateScript::GetAngles(pParams, &angles);
+		Mth::Matrix matrix;
+		matrix.SetFromAngles(angles);
+		
+		for (int n = m_num_contacts; n--; )
+		{
+			mp_contacts[n].p = matrix.Rotate(mp_contacts[n].p);
+		}
+	}
+	
+	// setup model offset
+	Mth::Vector center_of_mass(0.0f, 0.0f, 0.0f);
+	if (pParams->ContainsComponentNamed(CRCD(0x737612c6, "center_of_mass")))
+	{
+		if (pParams->ContainsComponentNamed(CRCD(0x737612c6, "center_of_mass")))
+		{
+			pParams->GetVector(CRCD(0x737612c6, "center_of_mass"), ¢er_of_mass);
+		}
+	}
+		
+	// if not set (or set to default), calculate the center of mass
+	if (center_of_mass[X] == 0.0f && center_of_mass[Y] == 0.0f && center_of_mass[Z] == 0.0f);
+	{
+		for (int n = m_num_contacts; n--; )
+		{
+			center_of_mass += mp_contacts[n].p;
+		}
+		center_of_mass *= (1.0f / m_num_contacts);
+	}
+	
+	m_model_offset = -center_of_mass;
+	for (int n = m_num_contacts; n--; )
+	{
+		mp_contacts[n].p -= center_of_mass;
+	}
+	
+	
+	if (!pParams->GetFloat(CRCD(0x967cd3ae, "mass_over_moment"), &m_mass_over_moment))
+	{
+		// calculate an appropriate mass over moment adjustment; otherwise, you can get some really weird behavior
+		float max_dist = 0.0f;
+		for (int n = m_num_contacts; n--; )
+		{
+			max_dist = Mth::Max(max_dist, mp_contacts[n].p.Length());
+		}
+		if (max_dist > 30.0f)
+		{
+			m_mass_over_moment /= Mth::Sqr((max_dist / 30.0f));
+		}
+	}
+	
+	// setup static variables
+	s_skater_head_height = GetPhysicsFloat(CRCD(0x542cf0c7, "Skater_default_head_height"));
+
+	// determine the largest feeler extent; used with the collision cache system
+	m_largest_contact_extent = 0.0f;
+	for (int n = m_num_contacts; n--; )
+	{
+		SContact& contact = mp_contacts[n];
+
+		float contact_extent_sqr = contact.p.LengthSqr();
+		if (contact_extent_sqr > m_largest_contact_extent)
+		{
+			m_largest_contact_extent = contact_extent_sqr;
+		}
+	}
+	m_largest_contact_extent = sqrtf(m_largest_contact_extent) + 1.0f;
+
+	m_ignore_skater_countdown = 0.0f;
+	
+	m_pos -= m_matrix.Rotate(m_model_offset);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::Finalize()
+{
+	mp_sound_component = GetSoundComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_sound_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::Update()
+{
+	#ifdef __NOPT_DEBUG__
+	Dbg_MsgAssert(!s_contact_states_in_use, ("Two CRigidBodyComponents updating simultaneously"));
+	s_contact_states_in_use = true;
+	#endif
+	
+	float full_time_step = Tmr::FrameLength();
+	
+	setup_contact_states();
+	
+	// only check for skater collisions if we haven't collided with the skater recently; this insures that the stupid skater doesn't cram us
+	// into corners and the like
+	if (m_ignore_skater_countdown > 0.0f)
+	{
+		m_ignore_skater_countdown -= full_time_step;
+	}
+	else if (!m_flags.Test(PLAYER_COLLISION_DISABLED))
+	{
+		handle_skater_collisions();
+	}
+	
+	if (m_die_countdown >= 0.0f)
+	{
+		if (m_die_countdown <= full_time_step)
+		{
+			GetObject()->MarkAsDead();
+		}
+		else
+		{
+			m_die_countdown -= full_time_step;
+		}
+	}
+	
+	// if we are asleep
+	if (m_state == ASLEEP)
+	{
+		GetObject()->SetDisplayMatrix(m_matrix);
+
+		if (s_debug_lines_on)
+		{
+			draw_debug_lines();
+		}
+
+		// do just about nothing
+			
+		#ifdef __NOPT_DEBUG__
+		s_contact_states_in_use = false;
+		#endif
+
+		return;
+	}
+
+	int main_update_loop_count = 0;
+	bool last_ditch_collision = false;
+	float remaining_time_step = full_time_step;
+	do {
+		float time_step = remaining_time_step;
+
+		// send a feeler from object's center of mass to our new center of mass; most collisions will not be caught in this manner;
+		// this is a last-ditch feeler to insure that we don't fly through objects at high speeds
+		CFeeler feeler;
+		feeler.m_start = m_pos;
+		feeler.m_end = m_pos + time_step * m_vel;
+		feeler.m_end[Y] += 0.5f * time_step * time_step * m_const_acc;
+
+		last_ditch_collision = feeler.GetCollision(false);
+	
+		// if we have such a collision, move only to it
+		if (last_ditch_collision)
+		{
+			time_step = (feeler.GetDist() * feeler.Length() - 6.0f) / m_vel.Length();
+			if (time_step < 0.0f)
+			{
+				time_step = 0.0f;
+			}
+		}
+	
+		update_dynamic_state(time_step);
+
+		// we do a very simple collision
+		if (last_ditch_collision)
+		{
+			m_vel -= (1.0f + m_coeff_restitution) * Mth::DotProduct(m_vel, feeler.GetNormal()) * feeler.GetNormal();
+		}
+
+		remaining_time_step -= time_step;
+
+		// make sure we're not in an infinite loop; a weirdly hanging box is better than a frozen game
+		if (++main_update_loop_count == 5)
+		{
+			MESSAGE("too many last-ditch contacts in a single frame; going to sleep");
+			sleep();
+			break;
+		}
+	} while (last_ditch_collision);
+	
+	if (detect_collisions())
+	{
+		resolve_collisions();
+	}
+
+	consider_sleeping();
+
+	// if we've gotten here, we need to update the object
+	
+	GetObject()->SetPos(m_pos + m_matrix.Rotate(m_model_offset));
+	
+	GetObject()->SetMatrix(m_matrix);
+	GetObject()->SetDisplayMatrix(m_matrix);
+	
+	GetObject()->SetVel(m_vel);
+
+	if (s_debug_lines_on)
+	{
+		draw_debug_lines();
+	}
+	
+	#ifdef __NOPT_DEBUG__
+	s_contact_states_in_use = false;
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CRigidBodyComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | RigidBody_IgnoreSkater | rigidbody ignores the skater for a given duration
+        // @uparm 0.0 | duration (defaults to seconds)
+        // @flag frames | time is given in frames
+		case CRCC(0xcc12cf87, "RigidBody_IgnoreSkater"):
+		{
+			pParams->GetFloat(NO_NAME, &m_ignore_skater_countdown, Script::ASSERT);
+			if (pParams->ContainsFlag(CRCD(0x19176c5, "frames")) || pParams->ContainsFlag(CRCD(0x4a07c332, "frame")))
+			{
+				m_ignore_skater_countdown *= (1.0f / 60.0f);
+			}
+			break;
+		}
+		
+		// @script | RigidBody_Wake | wakes up the rigidbody
+		case CRCC(0x9599c10f, "RigidBody_Wake"):
+			wake();
+			break;
+
+		// @script | RigidBody_Sleep | puts the rigidbody to sleep
+		case CRCC(0xcd5ba67b, "RigidBody_Sleep"):
+			sleep();
+			break;
+		
+		// @script | RigidBody_Kick | wakes the rigidbody with a kick
+		case CRCC(0xae24df9f, "RigidBody_Kick"):
+		{
+			Mth::Vector v;
+			if (pParams->GetVector(CRCD(0xc4c809e, "vel"), &v))
+			{
+				GetObject()->SetVel(m_vel += v);
+			}
+			if (pParams->GetVector(CRCD(0xfb1a83b2, "rotvel"), &v))
+			{
+				m_rotvel += v;
+			}
+			if (pParams->GetVector(CRCD(0x7f261953, "pos"), &v))
+			{
+				GetObject()->SetPos(m_pos += v);
+			}
+			wake();
+			break;
+		}
+
+		// @script : RigidBody_Reset | reset any parameters of the rigidbody
+		case CRCC(0x92f5db9a, "RigidBody_Reset"):
+			InitFromStructure(pParams);
+			break;
+			
+		// @script : RigidBody_DisablePlayerCollision
+		case CRCC(0xa2bbb3a, "RigidBody_DisablePlayerCollision"):
+			m_flags.Set(PLAYER_COLLISION_DISABLED);
+			break;
+		
+		// @script : RigidBody_EnablePlayerCollision
+		case CRCC(0x49f44585, "RigidBody_EnablePlayerCollision"):
+			m_flags.Clear(PLAYER_COLLISION_DISABLED);
+			break;
+			
+		// @script : RigidBody_EnablePlayerCollision
+		case CRCC(0x949be8a9, "RigidBody_MatchVelocityTo"):
+		{
+			Script::CComponent* p_component = pParams->GetNextComponent(NULL);
+			Dbg_MsgAssert(p_component->mType == ESYMBOLTYPE_NAME, ("RigidBody_MatchVelocityTo requires an object name as its first parameter"));
+			CCompositeObject* p_composite_object = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(p_component->mChecksum));
+			Dbg_MsgAssert(p_composite_object, ("RigidBody_MatchVelocityTo requires a composite object name as its first parameter"));
+			
+			bool apply_random_adjustment = pParams->ContainsFlag(CRCD(0x8ace8a0f, "ApplyRandomAdjustment"));
+			
+			m_vel = p_composite_object->m_vel;
+			if (apply_random_adjustment)
+			{
+				m_vel *= 1.0f + Mth::PlusOrMinus(0.4f);
+			}
+			GetObject()->SetVel(m_vel);
+			
+			if (m_state == ASLEEP && Mth::Abs(m_vel[Y]) < 1.0f)
+			{
+				m_vel[Y] = 1.0f;
+			}
+			
+			// use the rotational velocity also, if it's a rigidbody
+			CRigidBodyComponent* p_rigid_body_component = GetRigidBodyComponentFromObject(p_composite_object);
+			if (p_rigid_body_component)
+			{
+				m_rotvel = p_rigid_body_component->m_rotvel;
+				if (apply_random_adjustment)
+				{
+					m_rotvel *= 1.0f * Mth::PlusOrMinus(0.4f);
+				}
+			}
+			
+			wake();
+			
+			break;
+		}
+		
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info, ("NULL p_info sent to CRigidBodyComponent::GetDebugInfo"));
+
+	uint32 state_checksums[] =
+	{
+		CRCD(0x4484b712, "ASLEEP"), CRCD(0x99a34896, "AWAKE")
+	};
+	p_info->AddChecksum("m_state", state_checksums[m_state]);
+
+	p_info->AddFloat("m_orientation", m_orientation.GetScalar());
+	p_info->AddVector("m_orientation", m_orientation.GetVector());
+
+	p_info->AddVector("m_vel", m_vel);
+
+	p_info->AddVector("m_rotvel", m_rotvel);
+
+	p_info->AddFloat("m_const_acc", m_const_acc);
+
+	p_info->AddFloat("m_mass_over_moment", m_mass_over_moment);
+
+	p_info->AddFloat("m_coeff_restitution", m_coeff_restitution);
+
+	p_info->AddFloat("m_coeff_friction", m_coeff_friction);
+	
+	p_info->AddFloat("m_spring_const", m_spring_const);
+
+	p_info->AddFloat("m_skater_collision_impulse_factor", m_skater_collision_impulse_factor);
+	
+	p_info->AddFloat("m_skater_collision_rotation_factor", m_skater_collision_rotation_factor);
+	
+	p_info->AddFloat("m_skater_collision_radius", m_skater_collision_radius);
+
+    p_info->AddFloat("m_num_collisions", m_num_collisions);
+	
+	p_info->AddVector("m_model_offset", m_model_offset);
+	
+	p_info->AddFloat("CollideMuteDelay", m_sound_setup.collide_mute_delay);
+	
+	p_info->AddFloat("GlobalCollideMuteDelay", m_sound_setup.global_collide_mute_delay);
+	
+	p_info->AddFloat("BounceVelocityCallbackThreshold", m_sound_setup.bounce_velocity_callback_threshold);
+	
+	p_info->AddFloat("BounceVelocityFullSpeed", m_sound_setup.bounce_velocity_full_speed);
+	
+	Script::CArray* p_array = new Script::CArray;
+	p_array->SetSizeAndType(m_num_contacts, ESYMBOLTYPE_VECTOR);
+	for (int n = m_num_contacts; n--; )
+	{
+		Script::CVector* p_vector = new Script::CVector;
+		p_vector->mX = mp_contacts[n].p[X];
+		p_vector->mY = mp_contacts[n].p[Y];
+		p_vector->mZ = mp_contacts[n].p[Z];
+		p_array->SetVector(n, p_vector);
+	}
+	p_info->AddArrayPointer("m_contacts", p_array);
+	
+	p_info->AddChecksum("CollideScript", m_script_names.collide);
+	p_info->AddChecksum("BounceScript", m_script_names.bounce);
+	p_info->AddChecksum("SettleScript", m_script_names.settle);
+	p_info->AddChecksum("StuckScript", m_script_names.stuck);
+	
+	if (mp_script_params)
+	{
+		p_info->AddStructure("CallbackParams", mp_script_params);
+	}
+
+	p_info->AddChecksum("DieUponSettling", m_flags.Test(DIE_UPON_SLEEP) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum("PlayerCollisionDisabled", m_flags.Test(PLAYER_COLLISION_DISABLED) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	
+	if (m_sound_setup.bounce_sound)
+	{
+		Script::CStruct* p_struct = new Script::CStruct;
+		*p_struct += *m_sound_setup.bounce_sound;
+		p_info->AddStructurePointer(CRCD(0x1fb2f60c, "BounceSound"), p_struct);
+	}
+	if (m_sound_setup.collide_sound)
+	{
+		Script::CStruct* p_struct = new Script::CStruct;
+		*p_struct += *m_sound_setup.collide_sound;
+		p_info->AddStructurePointer(CRCD(0xbf8e0ace, "CollideSound"), p_struct);
+	}
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::sToggleDrawRigidBodyDebugLines (   )
+{
+	if (s_debug_lines_on)
+	{
+		/*
+		if (s_draw_skater_collision_circles)
+		{
+			s_debug_lines_on = s_draw_skater_collision_circles = false;
+		}
+		else
+		{
+			s_draw_skater_collision_circles = true;
+		}
+		*/
+		s_debug_lines_on = false;
+	}
+	else
+	{
+		s_debug_lines_on = true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::setup_contact_states (   )
+{
+	for (int n = m_num_contacts; n--; )
+	{
+		sp_contact_states[n].p_world = m_matrix.Rotate(mp_contacts[n].p);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::handle_skater_collisions (   )
+{
+	for (int n = Mdl::Skate::Instance()->GetNumSkaters(); n--; )
+	{
+		CSkater& skater = *Mdl::Skate::Instance()->GetSkater(n);
+
+		float radius_boost = skater.GetRigidBodyCollisionRadiusBoost();
+		
+		// detect collisions within a cylinder of the skater; if objects end up having significant extent in some directions and not others,
+		// this detection code may have to become more sophisticated
+
+		// check distance to the skater in the X-Z plane
+		float distance_sqr = (m_pos[X] - skater.GetPos()[X]) * (m_pos[X] - skater.GetPos()[X])
+			+ (m_pos[Z] - skater.GetPos()[Z]) * (m_pos[Z] - skater.GetPos()[Z]);
+		if (distance_sqr > Mth::Sqr(m_skater_collision_radius + radius_boost)) continue;
+
+		// check Y-offset to the skater
+		float y_offset = skater.GetPos()[Y] - m_pos[Y];
+		if (y_offset > m_skater_collision_application_radius + radius_boost
+			|| y_offset < -(m_skater_collision_application_radius + radius_boost + s_skater_head_height)) continue;
+
+		// apply the skater collision impulse; the impulse occurs at a slight assent and in a direction halfway between of the skater's
+		// velocity and the line to the object; the magnitude is roughly proportional to the component of the skater's velocity along the
+		// line to the object; the impulse will be applied a few inches above the skater's lowest point, along the line between the object
+		// and the skater at distance equal to the collision radius of the object
+
+		// unit vector pointing from the skater to the object in the X-Z plane
+		Mth::Vector r = m_pos;
+		r -= skater.GetPos();
+		// r[Y] = 0.0f; // optimized out as r[Y] never used
+		float eff_length = sqrtf(r[X] * r[X] + r[Z] * r[Z]);
+		r[X] /= eff_length;
+		r[Z] /= eff_length;
+		
+		// skater's relative velocity in the X-Z plane
+		Mth::Vector skater_vel = skater.GetVel();
+		skater_vel -= m_vel;
+		skater_vel[Y] = 0.0f;
+
+		if(( fabsf( skater_vel[X] ) < 0.01f ) && ( fabsf( skater_vel[Z] ) < 0.01f ))
+		{
+			// Small enough not to worry about - smaller can cause NaN decomposition.
+			return;
+		}
+		
+		// unadjusted impulse magnitude
+		float magnitude = skater_vel[X] * r[X] + skater_vel[Z] * r[Z];
+		if (magnitude <= 0.0f) continue;
+
+		// we adjust the impulse magnitude so the object will be vaulted in front of the skater so as to give the player a nice view of the object's
+		// dynamics; also, at low skater velocities, we want only a very small impulse
+
+		// fast collisions
+		if (magnitude > (2.0f * vRP_SKATER_COLLISION_VELOCITY_THRESHOLD_INCREMENT))
+		{
+			// increase the impulse by a constant
+			magnitude += ((1.0f + vRP_SKATER_COLLISION_VELOCITY_THRESHOLD_INCREMENT) * vRP_SKATER_COLLISION_VELOCITY_FACTOR);
+		}
+		// medium collisions
+		else if (magnitude > vRP_SKATER_COLLISION_VELOCITY_THRESHOLD_INCREMENT) {
+			// connect the functions continuously
+			magnitude += (vRP_SKATER_COLLISION_VELOCITY_FACTOR * vRP_SKATER_COLLISION_VELOCITY_THRESHOLD_INCREMENT)
+				+ (magnitude - vRP_SKATER_COLLISION_VELOCITY_THRESHOLD_INCREMENT);
+		}
+		else
+		// slow collisions
+		{
+			// increase the impulse by a factor
+			magnitude *= (1.0f + vRP_SKATER_COLLISION_VELOCITY_FACTOR);
+		}
+
+		// impulse direction in the X-Z plane is the average of the skater velocity and r directions
+		// we know the Y component for both is zero
+		Mth::Vector impulse = skater_vel;
+		eff_length = sqrtf(impulse[X] * impulse[X] + impulse[Z] * impulse[Z]);
+		impulse[X] = impulse[X] / eff_length + r[X];
+		impulse[Z] = impulse[Z] / eff_length + r[Z];
+		eff_length = sqrtf(impulse[X] * impulse[X] + impulse[Z] * impulse[Z]);
+		impulse[X] = impulse[X] / eff_length;
+		impulse[Z] = impulse[Z] / eff_length;
+
+		// adjust the impulse direction upwards
+		impulse[X] *= m_cos_skater_collision_assent;
+		impulse[Y] = m_sin_skater_collision_assent;
+		impulse[Z] *= m_cos_skater_collision_assent;
+
+		// factor in the magnitude
+		impulse *= m_skater_collision_impulse_factor * magnitude;
+
+		// the impulse's point of application in the XZ-plane is along the line between the skater and the object at the object's impulse application
+		// radius
+
+		// we calculate the point of application with respect to the center of mass
+        Mth::Vector application_point = skater.GetPos();
+		application_point[X] -= m_pos[X];
+		application_point[Y] = 0.0f;
+		application_point[Z] -= m_pos[Z];
+		application_point.Normalize(m_skater_collision_application_radius);
+
+		// calculate the Y-component of the point of application
+		if (y_offset > 0.0f)
+		{
+			// collide with the skater's feet
+			application_point[Y] = skater.GetPos()[Y] - m_pos[Y];
+		}
+		else if (y_offset < -s_skater_head_height)
+		{
+			// collide with the skater's head
+			application_point[Y] = skater.GetPos()[Y] + s_skater_head_height - m_pos[Y];
+		}
+		else
+		{
+			// collide with the skater's body; use an offset from the center of mass to cause rotation
+			application_point[Y] = -12.0f;
+		}
+
+		// apply the impulse; artifically reduce the initial rotation; it looks nice when the objects tumbles more after their first bounce
+		m_vel += impulse;
+		m_rotvel += m_skater_collision_rotation_factor * 0.6f * m_mass_over_moment * Mth::CrossProduct(application_point, impulse);
+
+		wake();
+
+		// start a countdown; while this countdown is complete, we will ignore the skater
+		m_ignore_skater_countdown = m_ignore_skater_duration;
+		
+		// call the bounce script callback
+		if (m_script_names.collide)
+		{
+			GetObject()->SpawnScriptPlease(m_script_names.collide, mp_script_params)->Update();
+		}
+		
+		// kill the object's shadow
+		if (Nx::CSector *p_shadow_sector = Nx::CEngine::sGetMainScene()->GetSector(Crc::ExtendCRCWithString(GetObject()->GetID(), "_Shadow")))
+		{
+			p_shadow_sector->SetActive(false);
+		}
+		
+		Tmr::Time time = Tmr::GetTime();
+		if ((int) (time - m_collide_sound_allowed_time) > 0 && (int) (time - s_collide_sound_allowed_time) > 0)
+		{
+			#ifdef __NOPT_ASSERT__
+			if (m_sound_type_id && Script::GetInteger(CRCD(0xd634a297, "DynamicRigidbodySounds")))
+			{
+				get_sound_setup(m_sound_type_id);
+			}
+			#endif
+			
+			if (m_sound_setup.collide_sound)
+			{
+				float percent = (100.0f / 1000.0f) * impulse.Length();
+				m_sound_setup.collide_sound->AddFloat(CRCD(0x9e497fc6, "Percent"), percent);
+				mp_sound_component->PlayScriptedSound(m_sound_setup.collide_sound);
+				
+				// delay the next collision sound
+				m_collide_sound_allowed_time = Tmr::GetTime() + (50 + Mth::Rnd(100)) * m_sound_setup.collide_mute_delay / 100;
+				s_collide_sound_allowed_time = Tmr::GetTime() + (50 + Mth::Rnd(100)) * m_sound_setup.global_collide_mute_delay / 100;
+			}
+		}
+
+		// collide with only a single skater
+		break;
+	} // END loop over skaters
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::update_dynamic_state ( float time_step )
+{
+	// a massively shoe-string handling of contact forces
+	if (m_num_collisions < 3)
+	{
+		Mth::Vector delta_pos = m_vel;
+		delta_pos[Y] += 0.5f * time_step * m_const_acc;
+		delta_pos *= time_step;
+		m_pos += delta_pos;
+
+		m_vel[Y] += time_step * m_const_acc;
+	}
+	else
+	{
+		m_pos += time_step * m_vel;
+	}
+
+	Mth::Quat delta_orientation = m_orientation;
+	delta_orientation *= Mth::Quat(-0.5f * m_rotvel[X], -0.5f * m_rotvel[Y], -0.5f * m_rotvel[Z], 0.0f);
+	delta_orientation *= time_step;
+	m_orientation += delta_orientation;
+
+	m_orientation.Normalize();
+
+	m_orientation.GetMatrix(m_matrix);
+
+	for (int n = m_num_contacts; n--; )
+	{
+		sp_contact_states[n].p_world = m_matrix.Rotate(mp_contacts[n].p);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::consider_sleeping (   )
+{
+	// it is possible that we may want to check these conditions over a period of several frames to insure that sleeping is warranted
+
+	// only sleep if we're experiencing three or more collisions; hopefully, a surface-to-surface contact
+	if (m_num_collisions < 3) return;
+
+	// only sleep if we're moving slow
+	if (m_vel.LengthSqr() > m_linear_velocity_sleep_point_sqr) return;
+
+	// only sleep if we're moving slow
+	if (m_rotvel.LengthSqr() > m_angular_velocity_sleep_point_sqr) return;
+
+	sleep();
+	
+	// if we're not suppose to when going to sleep
+	if (!m_flags.Test(DIE_UPON_SLEEP)) return;
+	
+	// only die if we've moved substantially from our starting point
+	if ((m_pos - m_wake_pos).LengthSqr() < 48.0f * 48.0f) return;
+	
+	m_die_countdown = 1.5f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::wake (   )
+{
+	if (m_state != AWAKE)
+	{
+		m_wake_pos = GetObject()->GetPos();
+		m_state = AWAKE;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::sleep (   )
+{
+	if (m_script_names.settle)
+	{
+		GetObject()->SpawnScriptPlease(m_script_names.settle, mp_script_params)->Update();
+	}
+	
+	m_vel.Set(0.0f, 0.0f, 0.0f);
+	m_rotvel.Set(0.0f, 0.0f, 0.0f);
+	m_state = ASLEEP;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CRigidBodyComponent::detect_collisions (   )
+{
+	CFeeler feeler;
+
+	// set up a bounding box around the space within which all collision detection will occur
+	Mth::CBBox bounding_box(m_pos - Mth::Vector(m_largest_contact_extent, m_largest_contact_extent, m_largest_contact_extent),
+		m_pos + Mth::Vector(m_largest_contact_extent, m_largest_contact_extent, m_largest_contact_extent));
+	s_collision_cache.Update(bounding_box);
+	feeler.SetCache(&s_collision_cache);
+
+	// loop over the contact points
+	m_num_collisions = 0;
+	for (int n = m_num_contacts; n--; )
+	{
+		SContactState& contact_state = sp_contact_states[n];
+
+		// run a feeler from the object's center to the contact point
+		feeler.m_start = m_pos;
+		feeler.m_end = m_pos;
+		feeler.m_end += contact_state.p_world;
+
+		contact_state.collision = feeler.GetCollision(false);
+
+		if (!contact_state.collision) continue;
+
+		contact_state.normal = feeler.GetNormal();
+		contact_state.normal_eff_mass_set = false;
+		contact_state.normal_impulse_magnitude = 0.0f;
+		
+		contact_state.depth = Mth::DotProduct(contact_state.normal, (1.0f - feeler.GetDist()) * (feeler.m_end - feeler.m_start));
+		contact_state.collision = contact_state.depth <= 0.0f;
+		
+		if (contact_state.collision)
+		{
+			m_num_collisions++;
+		}
+	}
+
+	return m_num_collisions != 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CRigidBodyComponent::calculate_effective_mass ( const Mth::Vector& direction, const SContactState& contact_state ) const
+{
+	// calculated the effective mass of the object at a contact for forces in a given direction
+
+	Mth::Vector delta_rotvel = Mth::CrossProduct(contact_state.p_world, direction);
+	delta_rotvel *= m_mass_over_moment;
+	return 1.0f / Mth::DotProduct(direction, direction + Mth::CrossProduct(delta_rotvel, contact_state.p_world));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::resolve_collisions (   )
+{
+	// collision resolution code of my own devise; simplified and not wholly accurate, it is good enough because we only have a single object
+	// colliding with a (mostly) static background; multiple collisions are not resolved simultaneously; instead, an iterative procedure is used to
+	// resolve the normal forces; this can cause objects which are colliding face-to-face with a surface to bounce off with odd rotation,
+	// as the required collision impulse will not be spread evenly across the contacts (as would be correct); also, friction is not handled in closed
+	// form; instead, after the collision is resolved and the normal forces are determined, we go back in and apply frictional forces (using the
+	// velocity of the object before the normal forces are applied; this generates friction which is incorrect, but visually reasonable; the friction
+	// forces could ruin the resolution of the collision, but we just ignore that possibility, and leave the problem to be fixed on the next frame
+
+	// cache the velocity and use it when determining the frictional forces
+	Mth::Vector cache_vel = m_vel;
+	Mth::Vector cache_rotvel = m_rotvel;
+	
+	// maximum collision velocity; used to determine if a bounce callback is warranted
+	float max_collision_velocity = 0.0f;
+
+	// loop until the collisions are fully resolved
+	int collision_resolution_pass_count = 0;
+	bool clean_pass;
+	do {
+		clean_pass = true;
+
+		// loop over the collision points
+		for (int n = m_num_contacts; n--; )
+		{
+			SContactState& contact_state = sp_contact_states[n];
+			if (!contact_state.collision) continue;
+	
+			// calculate the normal velocity at the collision point
+			Mth::Vector vel = m_vel;
+			vel += Mth::CrossProduct(m_rotvel, contact_state.p_world);
+			float normal_vel = Mth::DotProduct(contact_state.normal, vel);
+	
+			// skip if we're not colliding
+			if (normal_vel > 0.0f) continue;
+			clean_pass = false;
+			
+			if (-normal_vel > max_collision_velocity)
+			{
+				max_collision_velocity = -normal_vel;
+			}
+	
+			// calculate the goal velocity
+			float goal_normal_vel = -m_coeff_restitution * normal_vel;
+
+			// calculate normal effective mass
+			if (!contact_state.normal_eff_mass_set)
+			{
+				contact_state.normal_eff_mass = calculate_effective_mass(contact_state.normal, contact_state);
+				contact_state.normal_eff_mass_set = true;
+			}
+	
+			// calculate the normal impulse required to reach the goal velocity
+			float normal_impulse = (goal_normal_vel - normal_vel) * contact_state.normal_eff_mass;
+
+			// calculate normal impulse
+			Mth::Vector impulse = contact_state.normal;
+			impulse *= normal_impulse;
+
+			// accumulate the total normal impulse applied at his contact
+			contact_state.normal_impulse_magnitude += normal_impulse;
+
+			// apply the normal impulse
+			m_vel += impulse;
+			m_rotvel += m_mass_over_moment * Mth::CrossProduct(contact_state.p_world, impulse);
+		} // END loop over collision points
+
+		if (++collision_resolution_pass_count == 20)
+		{
+			Dbg_Message("CRigidBodyComponent::resolve_collisions: unresolveable collision; zeroing velocity");
+			// call the sleep script callback
+			if (m_script_names.stuck)
+			{
+				GetObject()->SpawnScriptPlease(m_script_names.stuck, mp_script_params)->Update();
+			}
+			else
+			{
+				m_vel.Set(0.0f, 0.0f, 0.0f);
+				m_rotvel.Set(0.0f, 0.0f, 0.0f);
+			}
+			return;
+		}
+		
+		// if there's only one collision point, we need to make only a single pass
+		if (m_num_contacts == 1) break;
+	} while (!clean_pass);
+	// END loop until collision is resolved
+
+	// we've resolved the collision; now calculate and apply the friction using the cached velocities
+
+	// loop over the collision points
+	for (int n = m_num_contacts; n--; )
+	{
+		SContact& contact = mp_contacts[n];
+		SContactState& contact_state = sp_contact_states[n];
+		if (!contact_state.collision) continue;
+
+		// calculate the tangent direction and velocity
+		Mth::Vector vel = cache_vel;
+		vel += Mth::CrossProduct(cache_rotvel, contact_state.p_world);
+		float normal_vel = Mth::DotProduct(contact_state.normal, vel);
+		Mth::Vector tangent = vel + -normal_vel * contact_state.normal;
+		
+		if (contact.directed_friction)
+		{
+			Mth::Vector friction_direction_world = m_matrix.Rotate(contact.friction_direction);
+			friction_direction_world -= Mth::DotProduct(contact_state.normal, friction_direction_world) * contact_state.normal;
+			
+			float length = friction_direction_world.Length();
+			if (length > 0.001f)
+			{
+				friction_direction_world *= (1.0f / length);
+				tangent.ProjectToNormal(friction_direction_world);
+			}
+		}
+		
+		float tangent_vel = tangent.Length();
+
+		// if the tangential velocity is too small, it's direction will be meaningless anyway
+		if (tangent_vel < 0.001f) continue;
+
+		// normalize by hand
+		tangent /= tangent_vel;
+
+		float tangent_eff_mass = calculate_effective_mass(tangent, contact_state);
+
+		// calculate the tangential impulse required to stop the tangential	velocity
+		float tangent_impulse = tangent_vel * tangent_eff_mass;
+
+		// the frictional impulse is only allowed to be so big
+		float max_tangent_impulse = m_coeff_friction * contact_state.normal_impulse_magnitude;
+		if (tangent_impulse > max_tangent_impulse)
+		{
+			tangent_impulse = max_tangent_impulse;
+		}
+		
+		// calculate the tangent impulse
+		Mth::Vector impulse = tangent;
+		impulse *= -tangent_impulse;
+		
+		if (s_draw_skater_collision_circles)
+		{
+			if (contact.directed_friction)
+			{
+				Gfx::AddDebugLine(contact_state.p_world + m_pos + Mth::Vector(0.0f, 1.0f, 0.0f), contact_state.p_world + m_pos + 6.0f * impulse + Mth::Vector(0.0f, 1.0f, 0.0f), MAKE_RGB(255, 255, 0), MAKE_RGB(255, 255, 0), 1);
+			}
+			else
+			{
+				Gfx::AddDebugLine(contact_state.p_world + m_pos + Mth::Vector(0.0f, 1.0f, 0.0f), contact_state.p_world + m_pos + 6.0f * impulse + Mth::Vector(0.0f, 1.0f, 0.0f), MAKE_RGB(255, 0, 255), MAKE_RGB(255, 0, 255), 1);
+			}
+		}
+
+		// apply the tangent impulse to both the true velocity and the velocity we are using to determine the frictional forces
+
+		Mth::Vector delta_rotvel = m_mass_over_moment * Mth::CrossProduct(contact_state.p_world, impulse);
+
+		m_vel += impulse;
+		m_rotvel += delta_rotvel;
+
+		cache_vel += impulse;
+		cache_rotvel += delta_rotvel;
+	} // END loop over collision points
+	
+	// apply penalty forces to prevent interpenetration
+	
+	float time_step = Tmr::FrameLength();
+	
+	for (int n = m_num_contacts; n--; )
+	{
+		SContactState& contact_state = sp_contact_states[n];
+		if (!contact_state.collision) continue;
+		
+		Mth::Vector impulse = -m_spring_const * contact_state.depth * time_step * contact_state.normal;
+		
+		Mth::Vector delta_rotvel = m_mass_over_moment * Mth::CrossProduct(contact_state.p_world, impulse);
+		
+		m_vel += impulse;
+		m_rotvel += delta_rotvel;
+		
+		if (s_draw_skater_collision_circles)
+		{
+			Gfx::AddDebugLine(contact_state.p_world + m_pos, contact_state.p_world + m_pos + 60.0f * impulse, MAKE_RGB(255, 255, 0), MAKE_RGB(255, 255, 255), 1);
+		}
+	}
+	
+	// script callback
+	if (max_collision_velocity > m_sound_setup.bounce_velocity_callback_threshold)
+	{
+		if (m_script_names.bounce)
+		{
+			GetObject()->SpawnScriptPlease(m_script_names.bounce, mp_script_params)->Update();
+		}
+		
+		#ifdef __NOPT_ASSERT__
+		if (m_sound_type_id && Script::GetInteger(CRCD(0xd634a297, "DynamicRigidbodySounds")))
+		{
+			get_sound_setup(m_sound_type_id);
+		}
+		#endif
+		
+		// don't use up the last vRP_NUM_UNTOUCHABLE_VOICES voices
+		if (m_sound_setup.bounce_sound && NUM_VOICES - Sfx::CSfxManager::Instance()->GetNumSoundsPlaying() > vRP_NUM_UNTOUCHABLE_VOICES)
+		{
+			float percent = Mth::Clamp(100.0f * max_collision_velocity / m_sound_setup.bounce_velocity_full_speed, 0.0f, 100.0f);
+			m_sound_setup.bounce_sound->AddFloat(CRCD(0x9e497fc6, "Percent"), percent);
+			mp_sound_component->PlayScriptedSound(m_sound_setup.bounce_sound);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::get_sound_setup ( uint32 sound_type_id )
+{
+
+	Script::CStruct* p_base_struct;
+	p_base_struct = Script::GetStructure(CRCD(0xb9738eba, "RigidBodySounds"), Script::ASSERT);
+
+	Script::CStruct* p_sound_struct = NULL;
+	p_base_struct->GetStructure(sound_type_id, &p_sound_struct);
+	Dbg_MsgAssert(p_sound_struct, ("Sound type '%s' not found in RigidBodySounds", Script::FindChecksumName(sound_type_id)));
+
+	Script::CStruct* p_struct;
+
+	if (p_sound_struct->ContainsComponentNamed(CRCD(0xbf8e0ace, "CollideSound")))
+	{
+		p_sound_struct->GetStructure(CRCD(0xbf8e0ace, "CollideSound"), &p_struct, Script::ASSERT);
+		if (m_sound_setup.collide_sound)
+		{
+			delete m_sound_setup.collide_sound;
+		}
+		m_sound_setup.collide_sound = new Script::CStruct;
+		*m_sound_setup.collide_sound += *p_struct;
+	}
+
+	if (p_sound_struct->ContainsComponentNamed(CRCD(0x1fb2f60c, "BounceSound")))
+	{
+		p_sound_struct->GetStructure(CRCD(0x1fb2f60c, "BounceSound"), &p_struct, Script::ASSERT);
+		if (m_sound_setup.bounce_sound)
+		{
+			delete m_sound_setup.bounce_sound;
+		}
+		m_sound_setup.bounce_sound = new Script::CStruct;
+		*m_sound_setup.bounce_sound += *p_struct;
+
+		if (p_sound_struct->ContainsComponentNamed(CRCD(0x3818ee47, "CollideMuteDelay")))
+		{
+			int time;
+			p_sound_struct->GetInteger(CRCD(0x3818ee47, "CollideMuteDelay"), &time);
+			m_sound_setup.collide_mute_delay = static_cast< Tmr::Time >(time);
+		}
+		if (p_sound_struct->ContainsComponentNamed(CRCD(0xad67a34d, "GlobalCollideMuteDelay")))
+		{
+			int time;
+			p_sound_struct->GetInteger(CRCD(0xad67a34d, "GlobalCollideMuteDelay"), &time);
+			m_sound_setup.global_collide_mute_delay = static_cast< Tmr::Time >(time);
+		}
+		p_sound_struct->GetFloat(CRCD(0x4511ca8d, "BounceVelocityCallbackThreshold"), &m_sound_setup.bounce_velocity_callback_threshold);
+		p_sound_struct->GetFloat(CRCD(0x8fc6519f, "BounceVelocityFullSpeed"), &m_sound_setup.bounce_velocity_full_speed);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::setup_contacts_from_array ( Script::CArray* pArray )
+{
+	m_num_contacts = pArray->GetSize();
+	Dbg_MsgAssert(m_num_contacts <= vRP_MAX_NUM_CONTACTS, ("Number of contacts in rigidbody exceeds limit of %i contacts", vRP_MAX_NUM_CONTACTS));
+	
+	if (mp_contacts)
+	{
+		delete [] mp_contacts;
+	}
+	mp_contacts = new SContact[m_num_contacts];
+	
+	for (unsigned int n = 0; n < m_num_contacts; n++)
+	{
+		Script::CVector* p_v = pArray->GetVector(n);
+        mp_contacts[n].p[X] = p_v->mX;
+        mp_contacts[n].p[Y] = p_v->mY;
+        mp_contacts[n].p[Z] = p_v->mZ;
+		mp_contacts[n].p[W] = 0.0f;
+		mp_contacts[n].directed_friction = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		
+void CRigidBodyComponent::setup_contacts_as_box ( const Mth::Vector& top_half_dimensions, const Mth::Vector& bottom_half_dimensions )
+{
+	m_num_contacts = 8;
+
+	if (mp_contacts)
+	{
+		delete [] mp_contacts;
+	}
+	mp_contacts = new SContact[m_num_contacts];
+	
+	int n = 0;
+	bool x = false;
+	do {
+		bool y = false;
+		do {
+			bool z = false;
+			do {
+				mp_contacts[n].p[X] = (x ? 1.0f : -1.0f) * (y ? top_half_dimensions[X] : bottom_half_dimensions[X]);
+				mp_contacts[n].p[Y] = (y ? top_half_dimensions[Y] : -bottom_half_dimensions[Y]);
+				mp_contacts[n].p[Z] = (z ? 1.0f : -1.0f) * (y ? top_half_dimensions[Z] : bottom_half_dimensions[Z]);
+				mp_contacts[n].p[W] = 1.0f;
+				mp_contacts[n].directed_friction = false;
+				n++;
+				z = !z;
+			} while (z);
+			y = !y;
+		} while (y);
+		x = !x;
+	} while (x);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::setup_contacts_as_pyramid ( float half_height, float half_depth )
+{
+	m_num_contacts = 5;
+
+	if (mp_contacts)
+	{
+		delete [] mp_contacts;
+	}
+	mp_contacts = new SContact[m_num_contacts];
+	
+	mp_contacts[0].p[X] = half_depth;
+	mp_contacts[0].p[Y] = -0.6f * half_height;
+	mp_contacts[0].p[Z] = half_depth;
+	mp_contacts[0].p[W] = 1.0f;
+	mp_contacts[0].directed_friction = false;
+
+	mp_contacts[1].p[X] = -half_depth;
+	mp_contacts[1].p[Y] = -0.6f * half_height;
+	mp_contacts[1].p[Z] = half_depth;
+	mp_contacts[1].p[W] = 1.0f;
+	mp_contacts[0].directed_friction = false;
+
+	mp_contacts[2].p[X] = -half_depth;
+	mp_contacts[2].p[Y] = -0.6f * half_height;
+	mp_contacts[2].p[Z] = -half_depth;
+	mp_contacts[2].p[W] = 1.0f;
+	mp_contacts[0].directed_friction = false;
+
+	mp_contacts[3].p[X] = half_depth;
+	mp_contacts[3].p[Y] = -0.6f * half_height;
+	mp_contacts[3].p[Z] = -half_depth;
+	mp_contacts[3].p[W] = 1.0f;
+	mp_contacts[0].directed_friction = false;
+
+	mp_contacts[4].p[X] = 0.0f;
+	mp_contacts[4].p[Y] = 1.6f * half_height;
+	mp_contacts[4].p[Z] = 0.0f;
+	mp_contacts[4].p[W] = 1.0f;
+	mp_contacts[0].directed_friction = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::setup_contacts_as_cylinder ( float top_radius, float bottom_radius, float half_height, int edges )
+{
+	m_num_contacts = 2 * edges;
+
+	if (mp_contacts)
+	{
+		delete [] mp_contacts;
+	}
+	mp_contacts = new SContact[m_num_contacts];
+	
+	int i = 0;
+	bool top = false;
+	do {
+		for (int n = edges; n--; )
+		{
+			mp_contacts[i].p[X] = (top ? top_radius : bottom_radius) * cosf(n * 2.0f * 3.1415f / edges);
+			mp_contacts[i].p[Y] = (top ? half_height : -half_height);
+			mp_contacts[i].p[Z] = (top ? top_radius : bottom_radius) * sinf(n * 2.0f * 3.1415f / edges);
+			mp_contacts[i].p[W] = 1.0f;
+			mp_contacts[i].directed_friction = false;
+			i++;
+		}
+		top = !top;
+	} while (top);
+}
+			
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::setup_contacts_as_triangle ( const Mth::Vector& half_dimensions )
+{
+	m_num_contacts = 6;
+
+	if (mp_contacts)
+	{
+		delete [] mp_contacts;
+	}
+	mp_contacts = new SContact[m_num_contacts];
+	
+	int i = 0;
+	bool left = false;
+	do {
+		mp_contacts[i].p[X] = half_dimensions[X];
+		mp_contacts[i].p[Y] = -half_dimensions[Y];
+		mp_contacts[i].p[Z] = (left ? -1.0f : 1.0f) * half_dimensions[Z];
+		mp_contacts[i].p[W] = 1.0f;
+		mp_contacts[i].directed_friction = false;
+		i++;
+		
+		mp_contacts[i].p[X] = -half_dimensions[X];
+		mp_contacts[i].p[Y] = -half_dimensions[Y];
+		mp_contacts[i].p[Z] = (left ? -1.0f : 1.0f) * half_dimensions[Z];
+		mp_contacts[i].p[W] = 1.0f;
+		mp_contacts[i].directed_friction = false;
+		i++;
+		
+		mp_contacts[i].p[X] = 0.0f;
+		mp_contacts[i].p[Y] = half_dimensions[Y];
+		mp_contacts[i].p[Z] = (left ? -1.0f : 1.0f) * half_dimensions[Z];
+		mp_contacts[i].p[W] = 1.0f;
+		mp_contacts[i].directed_friction = false;
+		i++;
+		
+		left = !left;
+	} while (left);
+}			
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRigidBodyComponent::draw_debug_lines (   ) const
+{
+	int color = 0;
+	bool collidable = false;
+	switch (m_state)
+	{
+		case ASLEEP:
+			color = MAKE_RGB(0, 0, 200);
+			collidable = true;
+			break;
+			
+		case AWAKE:
+			if (m_ignore_skater_countdown > 0.0f)
+			{
+				color = MAKE_RGB(0, 200, 0);
+			}
+			else
+			{
+				color = MAKE_RGB(200, 0, 0);
+				collidable = true;
+			}
+			break;
+	}
+	
+	if (collidable && s_draw_skater_collision_circles)
+	{
+		Gfx::AddDebugCircle(m_pos + Mth::Vector(0.0f, m_skater_collision_application_radius, 0.0f), 8, m_skater_collision_radius, MAKE_RGB(200, 200, 0), 1);
+		Gfx::AddDebugCircle(m_pos + Mth::Vector(0.0f, -m_skater_collision_application_radius, 0.0f), 8, m_skater_collision_radius, MAKE_RGB(200, 200, 0), 1);
+	}
+	
+	if (s_draw_skater_collision_circles)
+	{
+		Gfx::AddDebugStar(m_pos, 36.0f, MAKE_RGB(200, 0, 200), 1);
+	}
+	
+	// draw debug lines in less pretty yet general manner
+	for (int n = m_num_contacts; n--; )
+	{
+		for (int m = 0; m < n; m++)
+		{
+			Gfx::AddDebugLine(sp_contact_states[n].p_world + m_pos, sp_contact_states[m].p_world + m_pos, color, color, 1);
+		}
+	}
+}
+
+}
diff --git a/Code/Gel/Components/rigidbodycomponent.h b/Code/Gel/Components/rigidbodycomponent.h
new file mode 100644
index 0000000..53fc1f6
--- /dev/null
+++ b/Code/Gel/Components/rigidbodycomponent.h
@@ -0,0 +1,304 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       RigidBodyComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  1/22/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_RIGIDBODYCOMPONENT_H__
+#define __COMPONENTS_RIGIDBODYCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+#define		CRC_RIGIDBODY CRCD(0xffb39248, "RigidBody")
+#define		GetRigidBodyComponent() ((Obj::CRigidBodyComponent*)GetComponent(CRC_RIGIDBODY))
+#define		GetRigidBodyComponentFromObject(pObj) ((Obj::CRigidBodyComponent*)(pObj)->GetComponent(CRC_RIGIDBODY))
+
+#define vRP_GRAVITATIONAL_ACCELERATION	 					(-386.4f)
+#define vRP_DEFAULT_MASS_OVER_MOMENT						(0.003f)
+#define vRP_DEFAULT_COEFF_RESTITUTION	  					(0.4f)
+#define vRP_DEFAULT_COEFF_FRICTION	    					(0.5f)
+#define vRP_DEFAULT_SPRING_CONST    						(10.0f)
+#define vRP_DEFAULT_SKATER_COLLISION_RADIUS	  				(12.0f + 36.0f)
+#define vRP_DEFAULT_SKATER_COLLISION_APPLICATION_RADIUS		(12.0f)
+#define vRP_DEFAULT_SKATER_COLLISION_ASSENT					(25.0f)
+#define vRP_DEFAULT_SKATER_COLLISION_IMPULSE_FACTOR			(1.0f)
+#define vRP_DEFAULT_SKATER_COLLISION_ROTATION_FACTOR		(1.0f)
+#define vRP_SKATER_COLLISION_VELOCITY_FACTOR 		   		(1.0f)
+#define vRP_SKATER_COLLISION_VELOCITY_THRESHOLD_INCREMENT	(50.0f)
+#define vRP_DEFAULT_LINEAR_VELOCITY_SLEEP_POINT	   			(10.0f)
+#define vRP_DEFAULT_ANGULAR_VELOCITY_SLEEP_POINT   			(0.4f)
+#define vRP_DEFAULT_IGNORE_SKATER_DURATION					(5.0f / 60.0f)
+#define vRP_DEFAULT_COLLIDE_MUTE_DELAY						(1000)
+#define vRP_DEFAULT_GLOBAL_COLLIDE_MUTE_DELAY				(100)
+#define vRP_DEFAULT_BOUNCE_VELOCITY_CALLBACK_THRESHOLD		(20.0f)
+#define vRP_DEFAULT_BOUNCE_VELOCITY_FULL_SPEED				(300.0f)
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSoundComponent;
+
+class CRigidBodyComponent : public CBaseComponent
+{
+	enum ERigidBodyStateType
+	{
+		ASLEEP, AWAKE
+	};
+
+	enum EObjectGeometryType
+	{
+		BOX, PYRAMID, CYLINDER, TRIANGLE, NO_GEOMETRY
+	};
+	
+	enum EFlagType
+	{
+		DIE_UPON_SLEEP,
+		PLAYER_COLLISION_DISABLED
+	};
+
+	struct SContact
+	{
+		// defining element of a rigidbody contact point; position
+		Mth::Vector p;
+		
+		// the axis of friction
+		Mth::Vector friction_direction;
+		
+		// if friction at this contact occurs only along a single axis
+		char directed_friction;
+	};
+	
+	struct SContactState
+	{
+		// offset to the contact point in world space
+		Mth::Vector p_world;
+
+		// normal of the surface is it colliding with
+		Mth::Vector normal;
+
+		// the effective mass of this contact for impulses applied along the normal
+		float normal_eff_mass;
+
+		// keeps track of the total impulse magnitude of the normal force in a frame; used to limit the frictional impulse
+		float normal_impulse_magnitude;
+		
+		// depth of the contact; negative corresponds to interpenetration
+		float depth;
+
+		// is this contact in collision
+		char collision;
+
+		// false before the normal_inv_mass has been set
+		char normal_eff_mass_set;
+	};
+	
+	// if the number of free voices is this number or less, don't play bounce sounds
+	static const int vRP_NUM_UNTOUCHABLE_VOICES = 12;
+	
+	// maximum number of contacts allowed on a single rigidbody
+	static const unsigned int vRP_MAX_NUM_CONTACTS = 24;
+
+public:
+    CRigidBodyComponent();
+    virtual ~CRigidBodyComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	static void 					sToggleDrawRigidBodyDebugLines (   );
+
+private:
+
+	void							setup_contact_states (   );
+	void							handle_skater_collisions (   );
+	void							update_dynamic_state ( float time_step );
+	void							consider_sleeping (   );
+	void							wake (   );
+	void							sleep (   );
+	bool							detect_collisions (   );
+	float							calculate_effective_mass ( const Mth::Vector& direction, const SContactState& contact_state ) const;
+	void							resolve_collisions (   );
+	void							draw_debug_lines (   ) const;
+	void							get_sound_setup ( uint32 sound_type_id );
+	void							setup_contacts_as_box ( const Mth::Vector& top_half_dimensions, const Mth::Vector& bottom_half_dimensions );
+	void							setup_contacts_as_pyramid ( float radius, float half_depth );
+	void							setup_contacts_as_cylinder ( float top_radius, float bottom_radius, float half_depth, int edges = 6 );
+	void							setup_contacts_as_triangle ( const Mth::Vector& half_dimensions );
+	void							setup_contacts_from_array ( Script::CArray *pArray );
+
+private:
+
+	// properties:
+
+	// object's don't have masses or moments of inertia;
+	// instead, a single scalar is used for the moment of inertia and only the ratio of mass over moment is used;
+	// this ratio controls the object's relative tendency to move linearly or rotationally;
+	// if proper external forces are ever applied to rigidbodies, they will need a mass;
+	// units are inverse inches squared; should be on the order of the inverse of the square of the characteristic
+	// distance between the contact points and the center of mass
+	float m_mass_over_moment;
+
+	// object's acceleration due to constant force on its center of mass (gravity)
+	float m_const_acc;
+
+	// coefficient of restitution; bounce factor
+	float m_coeff_restitution;
+
+	// coefficient of friction
+	float m_coeff_friction;
+	
+	// spring constant used to prevent interpenetration
+	float m_spring_const;
+
+	// effectively the sum of the object radius and the skater radius; when the center-to-center distance is below this, there is a collision
+	float m_skater_collision_radius;
+
+	// effectively the radius of the object; the impulse is applied at this distance from the object; to have reasonable rotations, m_mass_over_moment
+	// should on the order of the inverse of this radius squared; also used in skater collisions to check their Y-interception
+	float m_skater_collision_application_radius;
+
+	// sine and cosine of the angle of assent of the skater collision impulse
+	float m_cos_skater_collision_assent;
+	float m_sin_skater_collision_assent;
+
+	// factor which adjusts the magnitude of skater collision impulses; sort of like an inverse mass
+	float m_skater_collision_impulse_factor;
+	
+	// scales the rotation the object gains from a collision with the skater
+	float m_skater_collision_rotation_factor;
+
+	// if the velocities are below these points, the object may go to sleep
+	float m_linear_velocity_sleep_point_sqr;
+	float m_angular_velocity_sleep_point_sqr;
+
+	// currently, we assume a box; the algorithm is general enough to do anything
+	SContact* mp_contacts;
+	unsigned short m_num_contacts;
+	
+	// center of mass of the object
+	Mth::Vector m_center_of_mass;
+	
+	// offset from the center of mass to the origin of the model
+	Mth::Vector m_model_offset;
+
+	// number of frames after a skater collision before we begin checking for skater collisions again
+	float m_ignore_skater_duration;
+	
+	// callback script names
+	struct SScriptCallbackNames
+	{
+		uint32 collide;
+		uint32 bounce;
+		uint32 settle;
+		uint32 stuck;
+	} m_script_names;
+	
+	// parameter structure passed to callback scripts
+	Script::CStruct* mp_script_params;
+	
+	// sound setup
+	struct SSoundSetup
+	{
+		Script::CStruct* collide_sound;
+		Script::CStruct* bounce_sound;
+		Tmr::Time collide_mute_delay;
+		Tmr::Time global_collide_mute_delay;
+		float bounce_velocity_callback_threshold;
+		float bounce_velocity_full_speed;
+	} m_sound_setup;
+	
+	// time when the next collide sound is allowed
+	Tmr::Time m_collide_sound_allowed_time;
+	
+	// time when the any next collide sound is allowed
+	static Tmr::Time s_collide_sound_allowed_time;
+	
+	// used to allow dynamic updating of sounds
+	#ifdef __NOPT_ASSERT__
+	uint32 m_sound_type_id;
+	#endif
+	
+	// property flags
+	Flags m_flags;
+	
+	// position at which we were last woke
+	Mth::Vector m_wake_pos;
+	
+	// countdown to dying; not active if set to -1.0f
+	float m_die_countdown;
+	
+	// primary state variables:
+
+	// object's state; objects sleep when they are not active
+	ERigidBodyStateType m_state;
+
+	// object's center of mass position
+	Mth::Vector m_pos;
+
+	// object's velocity
+	Mth::Vector m_vel;
+
+	// object's orientation around center of mass
+	Mth::Quat m_orientation;
+
+	// object's angular velocity
+	Mth::Vector m_rotvel;
+
+	// dependent state variables:
+
+	// rotation matrix based on our orientation
+	Mth::Matrix m_matrix;
+
+	// number of contacts in current collision
+	char m_num_collisions;
+
+	// count down used to time the interval between a skater collision and the point when we begin looking for skater collisions again
+	float m_ignore_skater_countdown;
+
+	// work variables:
+
+	// collision cache; used to improve collision detection turn-around time; shared by all rigidbodies
+	static Nx::CCollCache s_collision_cache;
+
+	// longest distance between a contact point and the center of mass; used to generate the collision cache's bounding box
+	float m_largest_contact_extent;
+
+	// debug variables
+	static bool s_debug_lines_on;
+	static bool s_draw_skater_collision_circles;
+
+	// height of the skater; used when determining skater collisions and resulting impulse point of application
+	static float s_skater_head_height;
+	
+	// to save memory, the rigidbodies share an array of structures which hold the state of their contacts during collision resolution
+	static SContactState sp_contact_states [ vRP_MAX_NUM_CONTACTS ];
+	
+	#ifdef __NOPT_DEBUG__
+	// insures that only a single rigidbody is using sp_contact_states at a time
+	static bool s_contact_states_in_use;
+	#endif
+	
+	// peer components
+	CSoundComponent* mp_sound_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/shadowcomponent.cpp b/Code/Gel/Components/shadowcomponent.cpp
new file mode 100644
index 0000000..195cd62
--- /dev/null
+++ b/Code/Gel/Components/shadowcomponent.cpp
@@ -0,0 +1,542 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ShadowComponent.cpp
+//* OWNER:          gj
+//* CREATION DATE:  2/06/03
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CShadowComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CShadowComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CShadowComponent::CShadowComponent() : CBaseComponent()
+{
+	SetType( CRC_SHADOW );
+
+	// no shadow object
+	mp_shadow = NULL;					
+	m_shadowNormal.Set(0.0f,1.0f,0.0f);
+	m_shadowType = CRCD(0x806fff30,"none");
+	m_shadowPos.Set(0,0,0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CShadowComponent::~CShadowComponent()
+{
+	if ( mp_shadow )
+	{
+		delete mp_shadow;
+		mp_shadow = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CShadowComponent::Finalize()
+{
+	mp_model_component = GetModelComponentFromObject( GetObject() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CShadowComponent::Teleport()
+{									 
+	// GJ:  the shadow component's Update()
+	// function doesn't really require finalization
+	// right now, but having this assert in here
+	// might help catch future errors
+	Dbg_MsgAssert( GetObject()->IsFinalized(), ( "Has not been finalized!  Tell Gary!" ) );
+
+	Update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CShadowComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_shadowType = CRCD(0x3e84c2fd,"simple");
+	pParams->GetChecksum( CRCD(0x9ac24b18,"ShadowType"), &m_shadowType, Script::NO_ASSERT );
+
+	if ( mp_shadow )
+	{
+		// get rid of existing shadow, in case it was the wrong type...
+		SwitchOffShadow();
+	}
+
+	switch ( m_shadowType )
+	{
+		case 0x3e84c2fd:	// simple
+			{
+				if ( !mp_shadow )
+				{
+					SwitchOnShadow( Gfx::vSIMPLE_SHADOW );
+				}
+				
+				float scale = 1.0f;
+				pParams->GetFloat( CRCD(0x6f8cd62f,"ShadowScale"), &scale, Script::NO_ASSERT );
+				
+				const char *p_shadow_model_name = "Ped_Shadow";
+				pParams->GetString( CRCD(0x545f8172,"ShadowModel"), &p_shadow_model_name, Script::NO_ASSERT );
+						
+				Dbg_MsgAssert( mp_shadow, ("NULL mp_shadow") );
+				Gfx::CSimpleShadow* pSimpleShadow = (Gfx::CSimpleShadow*)mp_shadow;
+				pSimpleShadow->SetScale( scale );
+				pSimpleShadow->SetModel( p_shadow_model_name );
+
+				// GJ:  need to immediately change the shadow's position if Obj_ShadowOn gets called
+				// this is because sometimes the shadow component will be suspended, and so
+				// update_shadow() won't get called (fixes shadow appearing at the origin in SC2)
+				pSimpleShadow->UpdatePosition( m_shadowPos, GetObject()->m_matrix, m_shadowNormal );
+			}
+			break;
+
+		case 0x76a54cd1:	// detailed
+			{
+				if ( !mp_shadow )
+				{
+					SwitchOnShadow( Gfx::vDETAILED_SHADOW );
+				}
+			}
+			break;
+
+		case 0x806fff30:	// none
+			{
+				if ( mp_shadow )
+				{
+					SwitchOffShadow();
+				}
+			}
+			break;
+
+		default:
+			Dbg_MsgAssert( 0, ( "Unrecognized shadow type %s", Script::FindChecksumName(m_shadowType) ) );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CShadowComponent::update_shadow()
+{
+	if ( mp_shadow )
+	{
+//		Dbg_MsgAssert(mp_shadow->GetShadowType()!=Gfx::vDETAILED_SHADOW,("Tried to update a detailed shadow in MovingObj_Update, will need to change code to send a higher-up position"));
+
+		
+		Nx::CModel* pModel = NULL;
+		if ( mp_model_component )
+		{
+			pModel = mp_model_component->GetModel();
+		}
+		
+		// if we have a model and the model is not active,then don't need to update the shadwo
+		if (pModel)
+		{
+			if (pModel->GetActive())
+			{
+				switch( mp_shadow->GetShadowType() )
+				{
+					case Gfx::vDETAILED_SHADOW:
+					{
+		//				Mth::Vector shadow_target_pos = pos + ( matrix.GetUp() * 36.0f );
+						Mth::Vector shadow_target_pos = GetObject()->m_pos + ( GetObject()->m_matrix.GetUp() * 36.0f );
+
+		#ifdef __PLAT_XBOX__
+						// K: Moved this in here cos it was giving an unused-variable compile error on PS2
+						Mth::Vector ground_dir( 0.2f, -0.8f, 0.3f );
+
+						// If lights are active, set the ground direction to be that of the primary light.
+						if( pModel )
+						{
+							Nx::CModelLights* p_lights = pModel->GetModelLights();
+							if( p_lights )
+							{
+								ground_dir = p_lights->GetLightDirection( 0 ) * -1.0f;
+							}
+						}
+		//				mp_shadow->UpdateDirection( ground_dir );
+		#endif	
+						mp_shadow->UpdatePosition( shadow_target_pos );
+					}
+					break;
+
+					case Gfx::vSIMPLE_SHADOW:
+					{
+						if ( pModel )
+						{
+							((Gfx::CSimpleShadow*)mp_shadow)->SetScale( pModel->GetScale().GetX() );
+						}	
+
+						/*
+						TODO:  Commented this section out, because it 
+						references m_jump_start_pos, which isn't accessible
+						from the shadow code yet...
+
+						if ( GetMotionComponentFromObject( GetObject() )->m_movingobj_status & MOVINGOBJ_STATUS_JUMPING )
+						{
+							// If jumping, use the jump's start-y so that the shadow stays on the ground.	
+							Mth::Vector p=GetObject()->m_pos;
+							p[Y]=m_jump_start_pos[Y];
+							mp_shadow->UpdatePosition(p,GetObject()->m_matrix,m_shadowNormal);
+						}
+						else
+						*/
+						{
+							mp_shadow->UpdatePosition( m_shadowPos, GetObject()->m_matrix, m_shadowNormal );
+						}
+					}
+					break;
+
+					default:
+						Dbg_MsgAssert(0,("Bad shadow type: %d",mp_shadow->GetShadowType()));
+						break;
+				}
+
+				mp_shadow->UnHide();
+			}
+			else
+			{
+				// model is not active, so we probably don't want the shadow's model to be active either
+				mp_shadow->Hide();
+			}
+		}
+	}			
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CShadowComponent::Hide( bool shouldHide )
+{
+	HideShadow( shouldHide );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CShadowComponent::Update()
+{
+//	m_shadowNormal.Set( 0.0f, 1.0f, 0.0f );
+
+	/*
+	if ( mp_shadow )
+	{
+		if (mp_shadow->GetShadowType()==Gfx::vSIMPLE_SHADOW)
+		{
+			mp_shadow->UpdatePosition(m_shadow_pos,m_matrix,pShadowComponent->m_shadownormal);
+		}		
+		else
+		{
+			mp_shadow->UpdatePosition(shadow_target_pos); // at this point m_pos is the same as mp_physics->m_pos
+		}
+	}
+	*/
+
+	if ( GetObject()->GetID() >= 0 && GetObject()->GetID() < Mdl::Skate::vMAX_SKATERS )
+	{
+		// the skater shadows are handled elsewhere by other components
+		// (CSkaterAdjustPhysicsComponent or CWalkComponent)
+//		SetShadowPos( GetObject()->GetPos() );
+	}
+	else
+	{
+		// the ped shadow is handled here...
+		SetShadowPos( GetObject()->GetPos() );
+	}
+
+	update_shadow();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::EMemberFunctionResult CShadowComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | Obj_ShadowOff | turn off shadow
+		case ( 0xaaf6e513 ): // Obj_ShadowOff
+			SwitchOffShadow();
+			break;
+
+        // @script | Obj_ShadowOn | turn shadow on
+		// @parmopt float | Offset | 0.2 | The y offset of the shadow, > 0 = up
+		// @parmopt float | ShadowScale | 1.0 | The scale of the shadow.
+		// @parmopt string | ShadowModel | "Ped_Shadow" | The mdl file to use
+		// @parmopt name | shadow_type | Either "detailed" or "simple"
+		case ( 0xf272c43a ): // Obj_ShadowOn
+		{
+			// add a default shadow_type
+			uint32 shadowType;
+			if ( !pParams->GetChecksum( "shadowType", &shadowType, Script::NO_ASSERT ) )
+			{
+				pParams->AddChecksum( "shadowType", CRCD(0x3e84c2fd,"simple") );
+			}
+
+			this->InitFromStructure( pParams );
+
+			if ( mp_shadow )
+			{
+				if( mp_shadow->GetShadowType() == Gfx::vSIMPLE_SHADOW )
+				{
+					float offset=0.2f;
+					pParams->GetFloat(CRCD(0xa6f5352f,"Offset"),&offset);
+					((Gfx::CSimpleShadow*)mp_shadow)->SetOffset(offset);
+				}
+			}
+		}
+		break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CShadowComponent::SwitchOnShadow( Gfx::EShadowType mode )
+{
+	
+	
+	if ( mp_shadow )
+	{
+		// it's already got a shadow
+		return;
+	}
+
+// Note we can't use mp_model_component here, as SwitchOnShaodow is called
+// from InitFromStructure, which is obviously called before Finalize(); 
+	CModelComponent* pModelComponent = GetModelComponentFromObject( GetObject() );
+	Nx::CModel* pModel = NULL;
+	if ( pModelComponent )
+	{
+		pModel = pModelComponent->GetModel();
+	}
+
+
+	switch (mode)
+	{
+		case Gfx::vDETAILED_SHADOW:
+		{
+			Dbg_MsgAssert( mp_shadow == NULL, ("mp_shadow not NULL?") );
+			Dbg_MsgAssert( pModel, ("adding detailed shadow to something with no model?") );
+			mp_shadow = new Gfx::CDetailedShadow( pModel );
+		}
+		break;
+
+		case Gfx::vSIMPLE_SHADOW:
+		{
+			Dbg_MsgAssert( mp_shadow == NULL, ("mp_shadow not NULL?") );
+			Dbg_MsgAssert( pModel, ("adding simple shadow to something with no model?") );
+			
+			Gfx::CSimpleShadow* pSimpleShadow = new Gfx::CSimpleShadow;
+			pSimpleShadow->SetScale( pModel->GetScale().GetX() );
+			pSimpleShadow->SetModel( "Ped_Shadow" );
+			
+			mp_shadow = pSimpleShadow;
+			mp_shadow->UpdatePosition( m_shadowPos, GetObject()->m_matrix, m_shadowNormal );
+		}	
+		break;
+		
+		default:
+			Dbg_Message( "Unrecognized shadow mode %d", mode );
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CShadowComponent::SwitchOffShadow()
+{
+	if ( mp_shadow )
+	{
+		if ( mp_model_component )
+		{
+			Nx::CModel* pModel = mp_model_component->GetModel();
+			Dbg_Assert( pModel );
+			pModel->EnableShadow( false );
+		}
+
+		delete mp_shadow;
+		mp_shadow = NULL;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CShadowComponent::HideShadow( bool should_hide )
+{
+	if ( mp_shadow )
+	{
+		if ( should_hide )
+		{
+			mp_shadow->Hide();
+		}
+		else
+		{
+			mp_shadow->UnHide();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CShadowComponent::SetShadowPos( const Mth::Vector& pos )
+{
+	m_shadowPos = pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CShadowComponent::SetShadowNormal( const Mth::Vector& normal )
+{
+	m_shadowNormal = normal;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CShadowComponent::SetShadowScale( float scale )
+{
+	if ( m_shadowType == CRCD(0x3e84c2fd,"simple") )
+	{
+		((Gfx::CSimpleShadow*)mp_shadow)->SetScale( scale );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CShadowComponent::SetShadowDirection( const Mth::Vector& vector )
+{
+	if( mp_shadow )
+	{
+		mp_shadow->UpdateDirection( vector );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CShadowComponent::SwitchOnSkaterShadow()
+{
+	// NOTE: skater specific and should not be here; migrate to somewhere like Mdl::Skate
+	
+	// only call on shadows attached to skaters
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CShadowComponent::SwitchOnSkaterShadow called on shadow not attached to skater object"));
+	
+	CSkater* pSkater = static_cast< CSkater* >(GetObject());
+	
+	Gfx::EShadowType mode = Gfx::vDETAILED_SHADOW;
+	
+	// put it on the bottom up heap, because we don't want to fragment the skater geom heap...
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterHeap(pSkater->GetHeapIndex()));
+
+	// In Splitscreen games, shadow type for local player is optional
+	if (CFuncs::ScriptInSplitScreenGame(NULL, NULL))
+	{
+		mode = Mdl::Skate::Instance()->GetShadowMode();
+	}
+
+	if (!pSkater->IsLocalClient())
+	{
+		Dbg_Printf( "************************ SWITCHING ON SKATER SHADOW FOR CLIENT\n" );
+		mode = Gfx::vSIMPLE_SHADOW;
+	}
+	
+	Script::CStruct* pTempParams = new Script::CStruct;
+	pTempParams->AddFloat(CRCD(0x6f8cd62f, "ShadowScale"), 1.0f);
+	pTempParams->AddString(CRCD(0x545f8172, "ShadowModel"), "Ped_Shadow");
+	InitFromStructure(pTempParams);
+	delete pTempParams;
+
+	SwitchOffShadow();
+	SwitchOnShadow(mode);
+
+	Mem::Manager::sHandle().PopContext();
+}
+	
+}
diff --git a/Code/Gel/Components/shadowcomponent.h b/Code/Gel/Components/shadowcomponent.h
new file mode 100644
index 0000000..79fc1a6
--- /dev/null
+++ b/Code/Gel/Components/shadowcomponent.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       ShadowComponent.h
+//* OWNER:			Gary
+//* CREATION DATE:  2/06/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_SHADOWCOMPONENT_H__
+#define __COMPONENTS_SHADOWCOMPONENT_H__
+
+#include 
+#include 
+
+// For EShadowType
+#include 
+
+#include 
+			
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_SHADOW CRCD(0x8a897dd2,"shadow")
+#define		GetShadowComponent() ((CShadowComponent*)GetComponent(CRC_SHADOW))
+#define		GetShadowComponentFromObject(pObj) ((Obj::CShadowComponent*)(pObj)->GetComponent(CRC_SHADOW))
+
+namespace Gfx
+{
+	class CShadow;
+}
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class	CModelComponent;
+
+class CShadowComponent : public CBaseComponent
+{
+public:
+    CShadowComponent();
+    virtual ~CShadowComponent();
+
+public:
+    virtual void					Finalize( );
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void					Hide( bool should_hide );
+	virtual void					Teleport();
+
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+	static CBaseComponent*			s_create();
+
+protected: 
+	void							update_shadow();
+		
+		
+public:		
+	void							SwitchOnShadow(Gfx::EShadowType mode);
+	void 							SwitchOffShadow();
+	void 							HideShadow( bool should_hide );
+	void							SetShadowPos( const Mth::Vector& vector );
+	void							SetShadowNormal( const Mth::Vector& vector );
+	void							SetShadowScale( float scale );
+	void							SetShadowDirection( const Mth::Vector& vector );
+	
+	void							SwitchOnSkaterShadow();
+
+protected:
+	Gfx::CShadow *					mp_shadow;
+	Mth::Vector						m_shadowPos;
+	Mth::Vector						m_shadowNormal;
+	uint32							m_shadowType;
+	CModelComponent * 				mp_model_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/skeletoncomponent.cpp b/Code/Gel/Components/skeletoncomponent.cpp
new file mode 100644
index 0000000..25a5228
--- /dev/null
+++ b/Code/Gel/Components/skeletoncomponent.cpp
@@ -0,0 +1,431 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkeletonComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/22/2002
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+// TODO:  These won't be needed after the initial
+// matrix data gets moved to the SKE file
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// GJ:  CSkeletonComponent is supposed to be a wrapper
+// around the Gfx::CSkeleton, which contains a skeletal
+// object's final, post-blend matrix buffer.  It should
+// be pretty dumb;  neither the CSkeletonComponent nor
+// the Gfx::CSkeleton should contain any blending or 
+// procedural anim data or functionality.  Furthermore, 
+// it shouldn't know anything about animation flipping
+// or skateboard rotation.   	    
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// This static function is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+CBaseComponent* CSkeletonComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkeletonComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeletonComponent::init_skeleton( uint32 skeleton_name )
+{
+	Dbg_MsgAssert( mp_skeleton == NULL, ( "Component already has skeleton." ) );
+    
+	Gfx::CSkeletonData* pSkeletonData = (Gfx::CSkeletonData*)Ass::CAssMan::Instance()->GetAsset( skeleton_name, false );
+
+	if ( !pSkeletonData )
+	{
+		Dbg_MsgAssert( 0, ("Unrecognized skeleton %s. (Is skeleton.q up to date?)", Script::FindChecksumName(skeleton_name)) );
+	}
+    
+	mp_skeleton = new Gfx::CSkeleton( pSkeletonData );   
+    Dbg_MsgAssert( mp_skeleton, ( "Couldn't create skeleton" ) );
+
+    Dbg_MsgAssert( mp_skeleton->GetNumBones() > 0, ( "Skeleton needs at least one bone" ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkeletonComponent::CSkeletonComponent() : CBaseComponent()
+{
+	SetType( CRC_SKELETON );
+
+    mp_skeleton = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkeletonComponent::~CSkeletonComponent()
+{
+    Dbg_MsgAssert( mp_skeleton, ( "No skeleton had been initialized" ) );
+    
+    if ( mp_skeleton )
+    {
+        delete mp_skeleton;
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkeletonComponent::InitFromStructure( Script::CStruct* pParams )
+{
+    uint32 skeletonName;
+
+    if ( !pParams->GetChecksum( CRCD(0x222756d5,"skeleton"), &skeletonName, Script::NO_ASSERT ) )
+	{
+		pParams->GetChecksum( CRCD(0x9794932,"skeletonName"), &skeletonName, Script::ASSERT );
+	}
+
+    init_skeleton( skeletonName );
+
+	int maxBoneSkipLOD;
+	if(( pParams->GetInteger( CRCD(0xd3982061,"max_bone_skip_lod"), &maxBoneSkipLOD )) && mp_skeleton )
+	{
+		mp_skeleton->SetMaxBoneSkipLOD( maxBoneSkipLOD );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkeletonComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Gfx::CSkeleton* CSkeletonComponent::GetSkeleton()
+{
+    return mp_skeleton;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkeletonComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch (Checksum)
+    {
+		// @script | Obj_SetBoneActive | changes whether the bone should be updated once per frame
+		case 0x20f7b992: // Obj_SetBoneActive
+		{
+			Dbg_MsgAssert( mp_skeleton, ( "Obj_SetBoneActive on object without a skeleton" ) );
+			
+			uint32 boneName;
+			pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, Script::ASSERT );
+			
+			int enabled;
+			pParams->GetInteger( CRCD(0xb4e103fd,"active"), &enabled, Script::ASSERT );
+
+			Dbg_Assert( mp_skeleton );
+            mp_skeleton->SetBoneActive( boneName, enabled );
+
+			break;
+		}
+
+		case 0xbe1c58ca:	// Obj_GetBonePosition
+		{
+			Mth::Vector bonePos(0.0f,0.0f,0.0f,1.0f);
+
+			uint32 boneName;
+			pParams->GetChecksum( CRCD(0xcab94088, "bone"), &boneName, Script::ASSERT );
+			bool success = GetBoneWorldPosition( boneName, &bonePos );
+			
+			// make sure we have somewhere to return the data
+			Dbg_Assert( pScript && pScript->GetParams() );
+			pScript->GetParams()->AddFloat( CRCD(0x7323e97c,"x"), bonePos[X] );
+			pScript->GetParams()->AddFloat( CRCD(0x424d9ea,"y"), bonePos[Y] );
+			pScript->GetParams()->AddFloat( CRCD(0x9d2d8850,"z"), bonePos[Z] );
+		
+			return success ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		
+		// @script | TextureSplat | create a texture splat
+        // @parm float | size | 
+        // @parmopt float | lifetime | 5.0 | lifetime in seconds
+        // @parmopt float | radius | 0.0 | radius
+        // @parmopt float | dropdown_length | 3.0 | dropdown length in feet
+        // @parmopt float | forward | | distance forward of the origin that this spat goes, assuming no bone specififed		
+        // @parmopt string | bone || bone name
+        // @parmopt string | name || texture name
+        // @parmopt string | trail || indicates that this splat should start or join a trail
+		case CRCC(0xb244c9cf,"TextureSplat"):
+			
+		// @script | Skeleton_SpawnTextureSplat | create a texture splat
+		// @parm float | size | 
+		// @parmopt float | lifetime | 5.0 | lifetime in seconds
+		// @parmopt float | radius | 0.0 | radius
+		// @parmopt float | dropdown_length | 3.0 | dropdown length in feet
+		// @parmopt float | forward | | distance forward of the origin that this spat goes, assuming no bone specififed		
+		// @parmopt string | bone || bone name
+		// @parmopt string | name || texture name
+		// @parmopt string | trail || indicates that this splat should start or join a trail
+		case CRCC(0xc51ce359, "Skeleton_SpawnTextureSplat"):
+		{	
+			float		size;
+			float		radius				= 0.0f;
+			float		lifetime			= 5.0f;
+			float		dropdown_length		= 3.0f;
+			float 		forward 			= 0.0f;
+			bool		trail				= pParams->ContainsFlag( 0x4d977a70 /*"trail"*/ );
+			bool		dropdown_vertical	= pParams->ContainsFlag( 0x7ee6c892 /*"dropdown_vertical"*/ );
+			uint32		bone_name_checksum  = 0;
+			const char*	p_texture_name;
+			Mth::Vector	pos;
+
+			pParams->GetFloat( CRCD(0x83fdb95,"size"), &size );
+			pParams->GetFloat( CRCD(0xc48391a5,"radius"), &radius );
+			pParams->GetFloat( CRCD(0xc218cf77,"lifetime"), &lifetime );
+			pParams->GetFloat( CRCD(0x12c62572,"dropdown_length"), &dropdown_length );
+			
+			if( !( pParams->GetText( "name", &p_texture_name )))
+			{
+				Dbg_MsgAssert( 0, ("%s\nTextureSplat requires a texture name"));
+			}
+
+			if( !(pParams->GetChecksumOrStringChecksum( "bone", &bone_name_checksum )))
+			{
+				if (!pParams->GetFloat( CRCD(0x186ed079,"forward"), &forward ))
+				{
+					Dbg_MsgAssert( 0, ("%s\nTextureSplat requires a bone name"));
+				}
+				else
+				{
+					pos = GetObject()->GetPos();
+					pos += forward * GetObject()->GetMatrix()[Z];
+				}
+			}
+
+			SpawnTextureSplat( pos, bone_name_checksum, size, radius, lifetime, dropdown_length, p_texture_name, trail, dropdown_vertical );
+            break;
+		}
+		
+		// @script | Skeleton_SpawnCompositeObject | creates a rigidbody at the specified bone's position and orientation
+		// finish autoducking
+		case CRCC(0xf99f9faf, "Skeleton_SpawnCompositeObject"):
+		{
+			uint32 bone_name;
+			pParams->GetChecksum(CRCD(0xcab94088, "bone"), &bone_name, Script::ASSERT);
+			
+			Mth::Vector offset;
+			offset.Set();
+			pParams->GetVector(CRCD(0xa6f5352f, "offset"), &offset);
+			
+			Script::CArray* p_component_array;
+			pParams->GetArray(CRCD(0x11b70a02, "components"), &p_component_array, Script::ASSERT);
+			
+			Script::CStruct* p_params;
+			pParams->GetStructure(CRCD(0x7031f10c, "params"), &p_params, Script::ASSERT);
+			
+			Mth::Vector relative_vel;
+			pParams->GetVector(CRCD(0xc4c809e, "vel"), &relative_vel, Script::ASSERT);
+			
+			Mth::Vector relative_rotvel;
+			pParams->GetVector(CRCD(0xfb1a83b2, "rotvel"), &relative_rotvel, Script::ASSERT);
+			
+			float object_vel_factor = 1.0f;
+			pParams->GetFloat(CRCD(0x8c6a6e08, "object_vel_factor"), &object_vel_factor);
+			
+			SpawnCompositeObject(bone_name, p_component_array, p_params, object_vel_factor, relative_vel, relative_rotvel, offset);
+			break;
+		}
+
+        default:
+            return CBaseComponent::MF_NOT_EXECUTED;
+    }
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeletonComponent::GetBonePosition( uint32 boneName, Mth::Vector* pBonePos )
+{
+	Dbg_Assert(mp_skeleton);
+	return mp_skeleton->GetBonePosition(boneName, pBonePos);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeletonComponent::GetBoneWorldPosition( uint32 boneName, Mth::Vector* pBonePos )
+{
+	Dbg_Assert(mp_skeleton);
+	
+	if ( !mp_skeleton->GetBonePosition( boneName, pBonePos ) )
+	{
+		pBonePos->Set( 0.0f, 0.0f, 0.0f );
+	}
+
+	// now tranform it by the position of the object
+    Mth::Matrix rootMatrix;
+    rootMatrix = GetObject()->GetMatrix();
+    rootMatrix[Mth::POS] = GetObject()->GetPos();
+	rootMatrix[Mth::POS][W] = 1.0f;
+	Mth::Vector vec = rootMatrix.Transform(* pBonePos );
+	(*pBonePos) = vec;
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeletonComponent::SpawnTextureSplat( Mth::Vector& pos, uint32 bone_name_checksum, float size, float radius, float lifetime, float dropdown_length, const char *p_texture_name, bool trail, bool dropdown_vertical )
+{
+	Mth::Vector bone_pos;
+	if (!bone_name_checksum)
+	{
+		bone_pos = pos;
+	}
+	else
+	{
+		GetBoneWorldPosition( bone_name_checksum, &bone_pos );
+	}
+
+	// Convert dropdown_length from feet to inches.
+	dropdown_length *= 12.0f;
+	
+	// Calculate the end position by dropping down 3 feet along the skater's up vector, or 'world up',
+	// depending on the dropdown_vertical flag.
+	Mth::Vector end;
+	if( dropdown_vertical )
+	{
+		end = bone_pos - ( Mth::Vector( 0.0f, 1.0f, 0.0f ) * dropdown_length );
+	}
+	else
+	{
+		end = bone_pos - ( GetObject()->GetMatrix().GetUp() * dropdown_length );
+	}
+	
+	// In some cases, the bone can be beneath geometry (trucks during a revert), so move the start position up slightly.
+	bone_pos += ( GetObject()->GetMatrix().GetUp() * 12.0f );
+	
+	// Calculate radial offset for end position.
+	if( radius > 0.0f )
+	{
+		float x_offset = -radius + (( radius * 2.0f ) * (float)rand() / RAND_MAX );
+		float z_offset = -radius + (( radius * 2.0f ) * (float)rand() / RAND_MAX );
+
+		end += ( GetObject()->GetMatrix().GetRight() * x_offset );
+		end += ( GetObject()->GetMatrix().GetAt() * z_offset );
+	}
+	
+	Nx::TextureSplat( bone_pos, end, size, lifetime, p_texture_name, trail ? bone_name_checksum : 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeletonComponent::SpawnCompositeObject ( uint32 bone_name, Script::CArray* pArray, Script::CStruct* pParams,
+	float object_vel_factor, Mth::Vector& relative_vel, Mth::Vector& relative_rotvel, Mth::Vector& offset )
+{
+	Dbg_Assert(pArray);
+	Dbg_Assert(pParams);
+	
+	CSkeletonComponent* p_skeleton_component = GetSkeletonComponentFromObject(GetObject());
+	Dbg_Assert(p_skeleton_component);
+
+	// get the bone's matrix
+	Mth::Matrix bone_matrix;
+	p_skeleton_component->GetSkeleton()->GetBoneMatrix(bone_name, &bone_matrix);
+	
+	// build the object's matrix
+	Mth::Matrix object_matrix = GetObject()->GetMatrix();
+	object_matrix[W] = GetObject()->GetPos();
+	object_matrix[X][W] = 0.0f;
+	object_matrix[Y][W] = 0.0f;
+	object_matrix[Z][W] = 0.0f;
+	
+	// NOTE: gludge to fix skater board model's coordinate system to match board bone's system
+	Mth::Vector temp;
+	temp = bone_matrix[Y];
+	bone_matrix[Y] = bone_matrix[Z];
+	bone_matrix[Z] = -temp;
+	offset[W] = 0.0f;
+	bone_matrix[W] += offset;
+	
+	// map the bone to world space
+	bone_matrix *= object_matrix;
+	
+	// write the bone state into the composite object parameters
+	
+	pParams->AddVector(CRCD(0x7f261953, "pos"), bone_matrix[W]);
+	
+	Mth::Quat orientation(bone_matrix);
+	if (orientation.GetScalar() > 0.0f)
+	{
+		pParams->AddVector(CRCD(0xc97f3aa9, "orientation"), orientation.GetVector());
+	}
+	else
+	{
+		pParams->AddVector(CRCD(0xc97f3aa9, "orientation"), -orientation.GetVector());
+	}
+	
+	// setup rigidbody's initial velocity
+	Mth::Vector vel = object_vel_factor * GetObject()->GetVel() + GetObject()->GetMatrix().Rotate(relative_vel);
+	pParams->AddVector(CRCD(0xc4c809e, "vel"), vel);
+	
+	// setup rigidbody's initial rotational velocity
+	Mth::Vector rotvel = GetObject()->GetMatrix().Rotate(relative_rotvel);
+	pParams->AddVector(CRCD(0xfb1a83b2, "rotvel"), rotvel);
+	
+	CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(pArray, pParams);
+}
+
+}
+
diff --git a/Code/Gel/Components/skeletoncomponent.h b/Code/Gel/Components/skeletoncomponent.h
new file mode 100644
index 0000000..f312ee5
--- /dev/null
+++ b/Code/Gel/Components/skeletoncomponent.h
@@ -0,0 +1,80 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkeletonComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKELETONCOMPONENT_H__
+#define __COMPONENTS_SKELETONCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_SKELETON CRCD(0x222756d5,"Skeleton")
+#define		GetSkeletonComponent() ((Obj::CSkeletonComponent*)GetComponent(CRC_SKELETON))
+#define		GetSkeletonComponentFromObject(pObj) ((Obj::CSkeletonComponent*)(pObj)->GetComponent(CRC_SKELETON))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+namespace Gfx
+{
+    class CSkeleton;
+}
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+class CSkeletonComponent : public CBaseComponent
+{
+public:
+    CSkeletonComponent();
+    virtual ~CSkeletonComponent();
+
+public:
+    virtual void            Update();
+    virtual void            InitFromStructure( Script::CStruct* pParams );
+    
+    EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+    static CBaseComponent*	s_create();
+    Gfx::CSkeleton*         GetSkeleton();
+
+    bool					GetBonePosition( uint32 boneName, Mth::Vector* pBonePos );
+	bool					GetBoneWorldPosition( uint32 boneName, Mth::Vector* pBonePos );
+	
+	void					SpawnTextureSplat( Mth::Vector& pos, uint32 bone_name_checksum, float size, float radius, float lifetime, float dropdown_length, const char *p_texture_name, bool trail, bool dropdown_vertical );
+	void					SpawnCompositeObject ( uint32 bone_name, Script::CArray* pArray, Script::CStruct* pParams, float object_vel_factor, Mth::Vector& relative_vel, Mth::Vector& relative_rotvel, Mth::Vector& offset );
+
+protected:
+    void                    init_skeleton( uint32 skeleton_name );
+	
+protected:
+    Gfx::CSkeleton*         mp_skeleton;
+};
+	
+}
+
+#endif
diff --git a/Code/Gel/Components/soundcomponent.cpp b/Code/Gel/Components/soundcomponent.cpp
new file mode 100644
index 0000000..e1f10da
--- /dev/null
+++ b/Code/Gel/Components/soundcomponent.cpp
@@ -0,0 +1,607 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SoundComponent.cpp
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+// start autoduck documentation
+// @DOC soundcomponent.cpp
+// @module soundcomponent | None
+// @subindex Scripting Database
+// @index script | soundcomponent
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+// TODO:  Refactor this - 
+#include 
+#include 
+#include 
+
+
+namespace Obj
+{
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent *	CSoundComponent::s_create()
+{
+	return static_cast(new CSoundComponent);	
+}
+    
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSoundComponent::CSoundComponent() : CBaseComponent()
+{
+	SetType(CRC_SOUND);
+	mp_emitter = NULL;
+	mp_proxim_node = NULL;
+	m_pos.Set();
+	m_old_pos.Set();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSoundComponent::~CSoundComponent()
+{
+	// Tell the sound manager that the sond component is being removed to clean up
+	// any sound effects that are attached to this
+	Sfx::CSfxManager::Instance()->ObjectBeingRemoved( this );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSoundComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 proxim_node_checksum;
+
+	if (pParams->GetChecksum("ProximNode", &proxim_node_checksum))
+	{
+		//Dbg_Message("SoundComponent: Looking for ProximNode %s", Script::FindChecksumName(proxim_node_checksum));
+		mp_proxim_node = CProximManager::sGetNode(proxim_node_checksum);
+		//if (mp_proxim_node)
+		//{
+		//	Dbg_Message("Found ProximNode %s", Script::FindChecksumName(mp_proxim_node->m_name));
+		//}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSoundComponent::Update()
+{
+	
+	// For tracking the position of the sound, then 
+	// we need to update the old position and the current position
+	m_old_pos = m_pos;
+	m_pos = GetObject()->m_pos;
+	
+	
+	UpdateMutedSounds();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSoundComponent::Teleport()
+{
+	// Garrett: Note that this doesn't update currently playing sounds.  That won't happen
+	// until the next sound update iteration.
+	Update();
+
+	//Dbg_Message("SoundComponent: Teleporting to (%f, %f, %f)", m_pos[X], m_pos[Y], m_pos[Z]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+CBaseComponent::EMemberFunctionResult CSoundComponent::CallMemberFunction( uint32 Checksum, Script::CScriptStructure *pParams, Script::CScript *pScript )
+{
+
+	switch (Checksum)
+	{
+		// @script | Obj_PlaySound | Add your own enums to sfx_*.q.  Up to 8 allowed (0 - 7), 
+		// @flag Filename | name of the sound (filename without the quotes) or 
+		// Type = [as defined in Obj_SetSound] 
+		// @parm name | Type | the type (if this is being called from a car script, for example, 
+		// use CARSFX_HONK, or another enum from sfx_car.q (similarly for ped: sfx_ped.q)
+		// @parmopt float | vol | 100 | This is the volume of the wav AFTER being modified 
+		// by the volume (if specified) in LoadSound 
+		// @parmopt float | pitch | 100 | range: 1 to 300 (assuming normal pitch in LoadSound) 
+		case 0xafa20e18:  // "Obj_PlaySound"
+			PlayScriptedSound( pParams );
+			return MF_TRUE;
+	
+		// @script | Obj_AdjustSound | This function adjusts a looping sound 
+		// associated with an object. If no volumeStep is specified, volume is 
+		// adjusted immediately, same with pitchStep and pitch.  If volumeStep 
+		// or pitchStep are specified, the current volume/pitch is adjusted towards 
+		// the target by the step, each frame (or 60 times a second, rather). So 
+		// if the current pitch is 100, and you call: Obj_AdjustSound pitch = 40 
+		// pitchStep = 1 It will take one second for the pitch to go from 100 to 40. 
+		// The percent values (pitchPercent and volumePercent) modify the overall 
+		// pitch or volume after taking into consideration the pitch specified globally 
+		// during the LoadSound call, then specified per this object on the Obj_
+		// PlaySound call (see example).
+		// @flag Filename | name of the sound you want to adjust (no quotes)
+		// @parm float | VolumePercent | Percentage to adjust the volume
+		// @parm float | PitchPercent | Percentage to adjust the pitch
+		// @parmopt float | VolumeStep | 0 |  Amount of time in seconds it takes to go from the current volume to new volume.
+		// @parmopt float | PitchStep | 0 | Amount of time in seconds it takes to go from the current pith to new pitch.
+		case 0x3e5e8023: // "Obj_AdjustSound"
+			AdjustObjectSound( pParams, pScript );
+			return MF_TRUE;
+		
+		// @script | Obj_StopSound | Obj_StopSound doesn't take a type = parameter... 
+		// it is used only for stopping a looping sound by checksum. If you don't 
+		// specify the name of the sound, all sounds that were loaded with PosUpdate 
+		// or PosUpdateWithDoppler parameters and are associated with the object 
+		// calling this will be stopped.
+		// @flag Filename | name of the sound you want to stop (no quotes)
+		case 0x4e4132d6: // "Obj_StopSound"
+		{
+			uint32 soundChecksum = 0;
+			pParams->GetChecksum( NONAME, &soundChecksum );
+			Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+			sfx_manager->StopObjectSound( this, soundChecksum );
+			// if it's temporarily out of range, we gotta stop it too!
+			int i;
+			for ( i = 0; i < MAX_NUM_LOOPING_SOUNDS_PER_OBJECT; i++ )
+			{
+				if ( mSoundInfo[ i ].checksum == soundChecksum )
+				{
+					// since a sound checksum of 0 is invalid, this turns off the update:
+					mSoundInfo[ i ].checksum = 0;
+				}
+			}
+			return MF_TRUE;
+		}
+		
+		// @script | Obj_SetSound | Add your own enums to sfx_*.q.  Up to 8 allowed (0 - 7), 
+		// can be increased easily.  Use Obj_PlaySound (with the optional Type = * parameter 
+		// instead of the filename) to trigger one of these sounds to be played.  Don't 
+		// trigger a looping sound!  The engine loop on the car needs to be set up per car 
+		// using this function, but the update happens in the game engine (pun regretted because 
+		// it's confusing).  Don't forget to load the sounds specified by 'filename'.
+		// @flag Filename | Name of the sound, no quotes
+		// @parm name | Type | the type (if this is being called from a car script, for example, 
+		// use CARSFX_HONK, or another enum from sfx_car.q (similarly for ped: sfx_ped.q)
+		case 0xe8347171:  // "Obj_SetSound"
+			SetSound( pParams );
+			return MF_TRUE;
+	}
+	return MF_NOT_EXECUTED;
+}
+
+void CSoundComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSoundComponent::GetDebugInfo"));
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+	
+	Script::CArray *p_array=new Script::CArray;
+	p_array->SetSizeAndType(MAX_NUM_SOUNDFX_CHECKSUMS,ESYMBOLTYPE_NAME);
+	for (int i=0; iSetChecksum(i,mSoundFXChecksums[i]);
+	}
+	p_info->AddArrayPointer("mSoundFXChecksums",p_array);
+
+	p_array=new Script::CArray;
+	p_array->SetSizeAndType(MAX_NUM_LOOPING_SOUNDS_PER_OBJECT,ESYMBOLTYPE_STRUCTURE);
+	for (int i=0; iSetStructure(i,p_struct);
+	}	
+	p_info->AddArrayPointer("mSoundInfo",p_array);
+#endif				 
+}
+
+#define CHECKSUM_TYPE	0x7321a8d6  // 'type'
+
+// Set individual soundfx for objects...
+// Designers can also trigger these sounds to be played by using the Obj_PlaySound
+// script command, using type =  (instead of using the checksum) to define the sound.
+void CSoundComponent::SetSound( Script::CScriptStructure *pParams )
+{
+	
+	int type = -1;
+	uint32 soundChecksum = 0;
+	pParams->GetInteger( CHECKSUM_TYPE, &type );
+	pParams->GetChecksum( NONAME, &soundChecksum );
+	if ( type == -1 )
+	{
+		Dbg_MsgAssert( 0,( "Script command Obj_SetSound used without specifying sound type." ));
+		return;
+	}
+	if ( !soundChecksum )
+	{
+		Dbg_MsgAssert( 0,( "Script command Obj_SetSound used without specifying name of sound." ));
+		return;
+	}
+	if ( ( type > MAX_NUM_SOUNDFX_CHECKSUMS ) || ( type == -1 ) )
+	{
+		Dbg_MsgAssert( 0,( "Matt needs to increase MAX_NUM_SOUND_CHECKSUMS (or bad type %d sent in...)", type ));
+		return;
+	}
+	mSoundFXChecksums[ type ] = soundChecksum;
+} // end of SetSound( )
+
+
+void CSoundComponent::PlayScriptedSound( Script::CScriptStructure *pParams )
+{
+	uint32 soundChecksum = 0;
+	uint32 emitterChecksum = 0;
+	uint32 dropoffFuncChecksum;
+	float volume = 100.0f;
+	float pitch = 100.0f;
+	float dropoffDist = 0.0f;
+	EDropoffFunc dropoffFunction = DROPOFF_FUNC_STANDARD;
+	bool noPosUpdate = false;
+    
+	pParams->GetChecksum( NONAME, &soundChecksum );
+	pParams->GetFloat( 0xf6a36814, &volume ); // "vol"
+	pParams->GetFloat( 0xd8604126, &pitch ); // "pitch"
+	
+	if (pParams->ContainsComponentNamed(CRCD(0x9e497fc6, "Percent")))
+	{
+		float minVol = 0.0f;
+		float maxVol = 100.0f;
+		float minPitch = 100.0f;
+		float maxPitch = 100.0f;
+		
+		pParams->GetFloat(CRCD(0x0693daaf, "MaxVol"), &maxVol);
+		pParams->GetFloat(CRCD(0x4391992d, "MinVol"), &minVol);
+		pParams->GetFloat(CRCD(0xfa3e14c5, "MaxPitch"), &maxPitch);
+		pParams->GetFloat(CRCD(0x1c5ebb24, "MinPitch"), &minPitch);
+		
+		float percent;
+		pParams->GetFloat(CRCD(0x9e497fc6, "Percent"), &percent, Script::ASSERT);
+		percent *= (1.0f / 100.0f);
+		
+		volume = Mth::Lerp(minVol, maxVol, percent);
+		pitch = Mth::Lerp(minPitch, maxPitch, percent);
+	}
+	
+	if ( pParams->GetFloat( 0xff2020ec, &dropoffDist ) ) // "dropoff"
+	{
+		dropoffDist = FEET_TO_INCHES( dropoffDist );
+	}
+	if ( pParams->GetChecksum( 0xc6ac50a, &dropoffFuncChecksum ) ) // "dropoff_function"
+	{
+		dropoffFunction = Sfx::GetDropoffFunctionFromChecksum( dropoffFuncChecksum );
+	}
+	if ( pParams->ContainsFlag( 0xbb39837f ) ) // "NoPosUpdate"
+	{
+		noPosUpdate = true;
+	}
+	if (pParams->GetChecksum( 0x8a7132ce, &emitterChecksum ) ) // "emitter"
+	{
+		mp_emitter = CEmitterManager::sGetEmitter(emitterChecksum);
+	}
+	else
+	{
+		mp_emitter = NULL;
+	}
+
+	if ( !soundChecksum )
+	{
+		// If the checksum isn't specified, the designer may be requesting
+		// to trigger a sound that can be individually specified per object
+		// using the script command Obj_SetSound...
+		// If that is the case, there will be a 'type = X' included, where
+		// X is an enum such as CARSFX_HONK.
+		int type = -1;
+		pParams->GetInteger( "Type", &type );
+		if ( type == -1 )
+		{
+			Dbg_MsgAssert( 0,( "Obj_PlaySound requires the name of the sound or a type specified (as in type = CARSFX_HONK)" ));
+			return;
+		}
+		if ( type > MAX_NUM_SOUNDFX_CHECKSUMS )
+		{
+			Dbg_MsgAssert( 0,( "Bad type (%d) sent to Obj_PlaySound", type ));
+			return;
+		}
+		soundChecksum = mSoundFXChecksums[ type ];
+		if ( !soundChecksum )
+		{
+			Dbg_Message( "Object trying to play sound type %d, which hasn't been defined.", type );
+			return;
+		}
+	}
+	
+	// network stuff for higher level objects (need to add the update flag to this...)
+	BroadcastScriptedSound( soundChecksum, volume, pitch );
+	
+	PlaySound_VolumeAndPan( soundChecksum, volume, pitch, dropoffDist, dropoffFunction, noPosUpdate );
+}// end of PlayScriptedSound( )
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector		CSoundComponent::GetClosestEmitterPos( Gfx::Camera *pCamera )
+{
+	if (mp_emitter && pCamera)
+	{
+		return mp_emitter->GetClosestPoint(pCamera->GetPos());
+	}
+	else
+	{
+		return m_pos;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector		CSoundComponent::GetClosestOldEmitterPos( Gfx::Camera *pCamera )
+{
+	if (mp_emitter && pCamera)
+	{
+		// This doesn't take into consideration the old position
+		return mp_emitter->GetClosestPoint(pCamera->GetPos());
+	}
+	else
+	{
+		return m_old_pos;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CSoundComponent::GetClosestDropoffPos( Gfx::Camera *pCamera, Mth::Vector & dropoff_pos )
+{
+	if (pCamera && mp_proxim_node)
+	{
+		dropoff_pos = mp_proxim_node->GetClosestIntersectionPoint(pCamera->GetPos());
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ObjectSoundInfo *CSoundComponent::GetLoopingSoundInfo( uint32 soundChecksum )
+{
+	
+	int i;
+	for ( i = 0; i < MAX_NUM_LOOPING_SOUNDS_PER_OBJECT; i++ )
+	{
+		if ( mSoundInfo[ i ].checksum == soundChecksum )
+			return &mSoundInfo[ i ];
+	}
+	return ( NULL );
+}
+
+void CSoundComponent::UpdateMutedSounds( void )
+{
+	
+	int i;
+	Tmr::Time gameTime;
+	gameTime = Tmr::GetTime();
+
+	for ( i = 0; i < MAX_NUM_LOOPING_SOUNDS_PER_OBJECT; i++ )
+	{
+		if ( mSoundInfo[ i ].checksum )
+		{
+			ObjectSoundInfo *pInfo = &mSoundInfo[ i ];
+			UpdateObjectSound( pInfo );
+			if ( pInfo->timeForNextDistCheck <= gameTime )
+			{
+//				Dbg_Message( "checking if we need to turn %s on", Script::FindChecksumName( mSoundInfo[ i ].checksum ) );
+				// see if we're dropoffDist*1.33 from the nearest camera...
+				// if so, start the sound up again!
+				float dist;
+				Gfx::Camera *p_camera = Nx::CViewportManager::sGetClosestCamera( m_pos, &dist );
+				if ( p_camera && ( dist < ( pInfo->dropoffDist + DIST_FROM_DROPOFF_AT_WHICH_TO_START_SOUND ) ) )
+				{
+					// start the sound playing again on this object:
+					if ( PlaySound_VolumeAndPan( pInfo->checksum, pInfo->origVolume, pInfo->origPitch, pInfo->dropoffDist, pInfo->dropoffFunction ) )
+					{
+						//Dbg_Message( "sound %s auto on object %x time %d", Script::FindChecksumName( pInfo->checksum ), ( int ) this, gameTime );
+						ObjectSoundInfo *tempInfo;
+						Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+						tempInfo = sfx_manager->GetObjectSoundProperties( this, pInfo->checksum );
+						// and set up the targets to match the adjustments the scripts made
+						// while the sound was turned off:
+						tempInfo->targetVolume = pInfo->targetVolume;
+						tempInfo->targetPitch = pInfo->targetPitch;
+						tempInfo->deltaPitch = pInfo->deltaPitch;
+                        // give it a ramp up in volume, so it doesn't frickin'jump in our faces:
+						tempInfo->currentVolume = 0.0f;
+						tempInfo->deltaVolume = tempInfo->targetVolume / 100.0f;
+						// don't forget to clear the slot to avoid updating it:
+						pInfo->checksum = 0;
+					}
+				}
+				else
+				{
+					// vary the time between distance checks, depending on how far
+					// away the object is right now...
+					pInfo->timeForNextDistCheck = gameTime;
+					pInfo->timeForNextDistCheck += ( int )( ( 1000.0f * ( dist - pInfo->dropoffDist ) ) / 600.0f );
+					pInfo->timeForNextDistCheck += 333;  // plus a third of a second...
+				}
+			}
+		}
+	}
+}
+
+void CSoundComponent::UpdateObjectSound( ObjectSoundInfo *pInfo )
+{
+	
+	// adjust pitch and volume, if necessary:
+	if ( pInfo->targetPitch != pInfo->currentPitch )
+	{
+		pInfo->currentPitch = Mth::FRunFilter( pInfo->targetPitch, pInfo->currentPitch, pInfo->deltaPitch * Tmr::FrameRatio( ) );
+	}
+	if ( pInfo->targetVolume != pInfo->currentVolume )
+	{
+		pInfo->currentVolume = Mth::FRunFilter( pInfo->targetVolume, pInfo->currentVolume, pInfo->deltaVolume * Tmr::FrameRatio( )  );
+	}
+}
+
+void CSoundComponent::AdjustObjectSound( Script::CScriptStructure *pParams, Script::CScript *pScript )
+{
+	
+#ifndef __PLAT_NGC__
+	ObjectSoundInfo *pInfo;
+	float volumePercent = 100.0f;
+	float pitchPercent = 100.0f;
+	uint32 soundChecksum;
+
+	if ( !pParams->GetChecksum( NONAME, &soundChecksum ) )
+	{
+		Dbg_MsgAssert( 0,( "\n%s\nSound checksum required on Obj_AdjustSound", pScript->GetScriptInfo( ) ));
+	}
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+	pInfo = sfx_manager->GetObjectSoundProperties( this, soundChecksum );
+	if ( !pInfo )
+	{
+		//Dbg_MsgAssert( 0,( "\n%s\nAttempting to adjust object sound that isn't playing.", pScript->GetScriptInfo( ) ));
+		
+		// Object went out of range, and the sound was stopped.
+		// Store the current properties, so that when the sound
+		// is started again the pitch/volume are correct!
+		
+		// NOTE:  If keeping the pitch/volume updated accurately by
+		// adjusting by deltaPitch and deltaVolume causes noticable
+		// slowdown, just set the pitch and vol immediately in this
+		// function if the pInfo comes from the next line:
+		pInfo = GetLoopingSoundInfo( soundChecksum );
+
+#		ifdef __PLAT_XBOX__
+		if( !pInfo )
+		{
+			return;
+		}
+#		endif
+
+		if (!pInfo)
+		{
+			//Dbg_MsgAssert( 0,( "\n%s\nCan't adjust a sound (%s) that was never played on an object.",  pScript->GetScriptInfo( ), Script::FindChecksumName(soundChecksum)  ));
+			Dbg_Message("\n%s\nCan't adjust a sound (%s) that was never played on an object.",  pScript->GetScriptInfo( ), Script::FindChecksumName(soundChecksum));
+			return;
+		}
+	}
+
+	if ( pParams->GetFloat( 0x330f3415, &volumePercent ) ) // volumePercent
+	{
+		pInfo->targetVolume = PERCENT( pInfo->origVolume, volumePercent );
+		if ( !pParams->GetFloat( 0xdaa9a3c2, &pInfo->deltaVolume ) )// volumeStep
+		{
+			// adjust immediately:
+			pInfo->currentVolume = pInfo->targetVolume;
+		}
+		else
+		{
+			Dbg_MsgAssert( pInfo->deltaVolume,( "\n%s\nCan't have zero delta volume.", pScript->GetScriptInfo( ) ));
+		}
+	}
+	if ( pParams->GetFloat( 0xee616c13, &pitchPercent ) )	// pitchPercent
+	{
+		pInfo->targetPitch = PERCENT( pInfo->origPitch, pitchPercent );
+		if ( !pParams->GetFloat( 0x7b090774, &pInfo->deltaPitch ) ) // pitchStep
+		{
+			// adjust immediately:
+			pInfo->currentPitch = pInfo->targetPitch;
+		}
+		else
+		{
+			Dbg_MsgAssert( pInfo->deltaPitch,( "\n%s\nCan't have zero delta pitch.", pScript->GetScriptInfo( ) ));
+		}
+	}
+#endif		// __PLAT_NGC__
+}
+
+void CSoundComponent::SoundOutOfRange( ObjectSoundInfo *pInfo, Tmr::Time gameTime )
+{
+	
+	int i;
+	for ( i = 0; i < MAX_NUM_LOOPING_SOUNDS_PER_OBJECT; i++ )
+	{
+		if ( !mSoundInfo[ i ].checksum  )
+		{
+			mSoundInfo[ i ] = *pInfo;
+			mSoundInfo[ i ].timeForNextDistCheck = gameTime + 2000;
+			return;
+		}
+	}
+	Dbg_MsgAssert( 0,( "No empty looping sound slots for (%s) on object... (Limit %d)",
+					Script::FindChecksumName( pInfo->checksum ) ,MAX_NUM_LOOPING_SOUNDS_PER_OBJECT ));
+}
+
+
+// Will play a sound, setting the volume and pan according to world position
+// from the closest camera(s).
+int CSoundComponent::PlaySound_VolumeAndPan( uint32 soundChecksum, float volume, float pitch, float dropoffDist,
+											 EDropoffFunc dropoffFunction, bool noPosUpdate )
+{
+	
+	
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+	Sfx::SoundUpdateInfo soundUpdateInfo;
+	soundUpdateInfo.volume = volume;
+	soundUpdateInfo.pitch = pitch;
+	soundUpdateInfo.dropoffDist = dropoffDist;
+	soundUpdateInfo.dropoffFunction = dropoffFunction;
+	#if 0
+	// TODO:  Replay code handling sound components
+	Replay::WritePositionalSoundEffect(m_id,soundChecksum,volume,pitch,dropoffDist);
+	#endif
+	return ( sfx_manager->PlaySoundWithPos( soundChecksum, &soundUpdateInfo, this, noPosUpdate ) );
+} // end of PlaySound_VolumeAndPan( )
+
+	
+}
diff --git a/Code/Gel/Components/soundcomponent.h b/Code/Gel/Components/soundcomponent.h
new file mode 100644
index 0000000..1d66783
--- /dev/null
+++ b/Code/Gel/Components/soundcomponent.h
@@ -0,0 +1,100 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SoundComponent.h
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#ifndef __COMPONENTS_SOUNDCOMPONENT_H__
+#define __COMPONENTS_SOUNDCOMPONENT_H__
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_SOUND CRCD(0x7713c7b,"Sound")
+#define		GetSoundComponent() ((Obj::CSoundComponent*)GetComponent(CRC_SOUND))
+#define		GetSoundComponentFromObject(pObj) ((Obj::CSoundComponent*)(pObj)->GetComponent(CRC_SOUND))
+
+//#define		GetSounds() ((CSoundComponent*)GetComponent(CRC_SOUND))
+
+#define NUM_GENERAL_SOUNDS_PER_OBJECT	( 10 )
+
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	// Forward declarations
+	class CEmitterObject;
+	class CProximNode;
+
+
+class CSoundComponent : public CBaseComponent
+{
+public:
+    CSoundComponent();
+    virtual         ~CSoundComponent();
+    virtual void    Update();
+	virtual void    Teleport();
+    virtual void    InitFromStructure( Script::CStruct* pParams );
+	static CBaseComponent *	s_create();
+
+
+	CBaseComponent::EMemberFunctionResult CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript );
+
+	virtual void GetDebugInfo(Script::CStruct *p_info);
+							
+// Big old pile of crap....
+
+public:	
+	void 			SetSound( Script::CStruct *pParams );
+	void			SoundOutOfRange( ObjectSoundInfo *pInfo, Tmr::Time gameTime );
+	void			UpdateObjectSound( ObjectSoundInfo *pInfo );
+	int 			PlaySound_VolumeAndPan( uint32 soundChecksum, float volume = 100.0f, float pitch = 100.0,
+											float dropoffDist = 0.0f, EDropoffFunc dropoffFunction = DROPOFF_FUNC_STANDARD,
+											bool noPosUpdate = false );
+	virtual void 	BroadcastScriptedSound( uint32 soundChecksum, float volume, float pitch ) { };
+	void			PlayScriptedSound( Script::CStruct *pParams );
+
+	Mth::Vector		GetPosition() const { return m_pos; }
+	Mth::Vector		GetClosestEmitterPos( Gfx::Camera *pCamera );
+	Mth::Vector		GetClosestOldEmitterPos( Gfx::Camera *pCamera );
+
+	bool			HasProximNode() const { return mp_proxim_node != NULL; }
+	void			ClearProximNode() { mp_proxim_node = NULL; }								// Needed for early proxim node cleanup
+	bool			GetClosestDropoffPos( Gfx::Camera *pCamera, Mth::Vector & dropoff_pos );	// returns true if position defined
+
+	// If the sound is muted due to the object being far from the nearest camera,
+	// update the pitch and volume so that when it comes back onto the screen it
+	// is how the designer would expect it ( called from GameObj_Update )
+	void			UpdateMutedSounds( void );
+
+	#define		MAX_NUM_SOUNDFX_CHECKSUMS	8
+	uint32		mSoundFXChecksums[ MAX_NUM_SOUNDFX_CHECKSUMS ];
+
+	void AdjustObjectSound( Script::CStruct *pParams, Script::CScript *pScript );
+	ObjectSoundInfo *GetLoopingSoundInfo( uint32 soundChecksum );
+	
+	#define		MAX_NUM_LOOPING_SOUNDS_PER_OBJECT	2
+	ObjectSoundInfo	mSoundInfo[ MAX_NUM_LOOPING_SOUNDS_PER_OBJECT ];
+							
+protected:
+							   
+	Mth::Vector	m_pos,m_old_pos;
+	CEmitterObject *mp_emitter;
+	CProximNode *	mp_proxim_node;
+
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/specialitemcomponent.cpp b/Code/Gel/Components/specialitemcomponent.cpp
new file mode 100644
index 0000000..c8e0af3
--- /dev/null
+++ b/Code/Gel/Components/specialitemcomponent.cpp
@@ -0,0 +1,429 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SpecialItemComponent.cpp
+//* OWNER:          ???
+//* CREATION DATE:  ???
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CSpecialItemComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSpecialItemComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CSpecialItemComponent::CSpecialItemComponent() : CBaseComponent()
+{
+	SetType( CRC_SPECIALITEM );
+	
+	for ( int i = 0; i < vMAX_SPECIAL_ITEMS; i++ )
+	{
+		mp_special_items[i] = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSpecialItemComponent::~CSpecialItemComponent()
+{	
+	// Cleanup the special items
+	DestroyAllSpecialItems();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CSpecialItemComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CSpecialItemComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CSpecialItemComponent::Update()
+{
+	// Doing nothing, so tell code to do nothing next time around
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CSpecialItemComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | CreateSpecialItem | attaches a new special item to the skater in the specified slot
+		// @parm int | index | Index of special item to create
+		// @parm structure | params | Special item creation data (should look like an entry in the node array)
+		// @parmopt name | bone | | The bone to which the special item is attached; otherwise, it will be attached to the root
+		// @parmopt vector | offset | (0, 0, 0) | An additional offset to the specified bone (or root) applied to the special item's position
+		case 0x827c6bd1:  // CreateSpecialItem
+			{
+				int index;
+				uint32 bone = 0;
+				pParams->GetInteger( CRCD(0x7f8c98fe,"index"), &index, true );
+				pParams->GetChecksum( "bone", &bone );
+
+				uint32 checksum;
+				pParams->GetChecksum( "params", &checksum, Script::ASSERT );
+				Script::CStruct* pSpecialItemParams;
+				pSpecialItemParams = Script::GetStructure( checksum, Script::ASSERT );
+								
+				CCompositeObject* pMovingObject = CreateSpecialItem( index, pSpecialItemParams );
+				
+				// special items need a lock object to work...
+				Script::CStruct* pLockParams = new Script::CStruct;
+				pLockParams->AppendStructure( pParams ); 			// pass along any bone or offset parameters...
+				pLockParams->AddChecksum( "id", GetObject()->GetID() );				  	
+				Obj::CLockObjComponent* pLockObjComponent = GetLockObjComponentFromObject( pMovingObject );
+				Dbg_MsgAssert( pLockObjComponent, ( "No lock obj component" ) );
+				pLockObjComponent->InitFromStructure( pLockParams );				 		
+				delete pLockParams;
+
+				GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+				if( gamenet_man->InNetGame())
+				{
+					Net::Client* client;
+					GameNet::MsgSpecialItem msg;
+					Net::MsgDesc msg_desc;
+	
+                    client = gamenet_man->GetClient( 0 );
+					Dbg_Assert( client );
+	
+					msg.m_ObjId = GetObject()->GetID();
+					msg.m_Params = checksum;
+					msg.m_Index = index;
+					msg.m_Bone = bone;
+					
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = sizeof( GameNet::MsgSpecialItem );
+					msg_desc.m_Id = GameNet::MSG_ID_CREATE_SPECIAL_ITEM;
+					client->EnqueueMessageToServer( &msg_desc );
+				}
+			}
+			break;
+		
+		// @script | DestroySpecialItem | Destroys a special item attached to the skater, if it exists
+		// @parm int | index | Index of special item to destroy
+		case 0x742188fa:  // DestroySpecialItem
+			{
+				int index;
+				pParams->GetInteger( CRCD(0x7f8c98fe,"index"), &index, true );
+
+				this->DestroySpecialItem( index );
+				
+				GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+				if( gamenet_man->InNetGame())
+				{
+					Net::Client* client;
+					GameNet::MsgSpecialItem msg;
+					Net::MsgDesc msg_desc;
+	
+                    client = gamenet_man->GetClient( 0 );
+					Dbg_Assert( client );
+	
+					msg.m_ObjId = GetObject()->GetID();
+					msg.m_Index = index;
+					
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = sizeof( GameNet::MsgSpecialItem );
+					msg_desc.m_Id = GameNet::MSG_ID_DESTROY_SPECIAL_ITEM;
+					client->EnqueueMessageToServer( &msg_desc );
+				}
+			}
+			break;
+		
+		// @script | DestroyAllSpecialItems | Destroys all special items currently attached to the skater 
+		case 0xa1baa25e:  // DestroyAllSpecialItems
+			{
+				this->DestroyAllSpecialItems();
+			}
+			break;
+		
+		// @script | SpecialItemExists | Returns whether a special item exists
+		// @parm int | index | Index of special item to check
+		case 0xce887492:  // SpecialItemExists
+			{
+				int index;
+				if ( pParams->GetInteger( CRCD(0x7f8c98fe,"index"), &index, false ) )
+				{
+					Dbg_MsgAssert( index >= 0 && index < vMAX_SPECIAL_ITEMS, ( "Special item index %d must be between 0 and %d", index, vMAX_SPECIAL_ITEMS ) );
+					return ( mp_special_items[index] ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}
+				else
+				{
+					for ( int i = 0; i < vMAX_SPECIAL_ITEMS; i++ )
+					{
+						if ( mp_special_items[i] )
+						{
+							return CBaseComponent::MF_TRUE;
+						}
+					}
+					return CBaseComponent::MF_FALSE;
+				}
+			}
+			break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSpecialItemComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSpecialItemComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger("m_never_suspend",m_never_suspend);
+	p_info->AddFloat("m_suspend_distance",m_suspend_distance);
+	*/
+	
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject* CSpecialItemComponent::CreateSpecialItem( int index, Script::CStruct* pNodeData )
+{
+	Dbg_MsgAssert( index >= 0 && index < vMAX_SPECIAL_ITEMS, ( "Special item index %d must be between 0 and %d", index, vMAX_SPECIAL_ITEMS ) );
+	Dbg_MsgAssert( pNodeData, ( "Missing parameter" ) );
+
+	if ( mp_special_items[index] )
+	{
+		// TODO:  Maybe this should just be a warning?
+//		Dbg_MsgAssert( 0, ( "Special item index %d not empty", index ) );
+		DestroySpecialItem( index );
+	}
+
+//	Dbg_Message( "Adding special item to index %d", index );
+	
+	// eventually, we'll want to create a generic composite object
+	// (maybe by running a script)
+	Mem::PushMemProfile("Game Objects");
+	Obj::CCompositeObject* pGameObj = Obj::CreateGameObj( Mdl::Skate::Instance()->GetObjectManager(), pNodeData );
+	Dbg_Assert( pGameObj );
+	Mem::PopMemProfile();
+
+	// GJ:  This is a special-case function that's used for
+	// replays...  I'm not bothering to move it over to the
+	// CCompositeObject stuff, because we've already made the
+	// decision not to make the replay backwards-compatible.
+//	pGameObj->FlagAsSpecialItem();
+	
+	mp_special_items[index] = pGameObj;
+
+	// add the skater's id to the object's tags
+	// so that the object knows which skater it's tied to
+	Script::CStruct* pTempStructure;
+	pTempStructure = new Script::CStruct;
+	pTempStructure->AddChecksum( "parentId", GetObject()->GetID() );
+	uint32 cleanupScript;
+	if ( pNodeData->GetChecksum( CRCD(0x40764820,"CleanupScript"), &cleanupScript ) )
+	{
+		pTempStructure->AddChecksum( CRCD(0x40764820,"CleanupScript"), cleanupScript );
+	}
+	mp_special_items[index]->SetTagsFromScript( pTempStructure );
+	delete pTempStructure;
+
+	// in case there's any scaling applied to the model,
+	// apply that to the special item's model as well...
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( GetObject() );
+	Nx::CModel* pModel = NULL;
+	if ( pModelComponent )
+	{
+		pModel = pModelComponent->GetModel();
+	}
+	
+	Obj::CModelComponent* pGameObjModelComponent = GetModelComponentFromObject( pGameObj );
+	Nx::CModel* pGameObjModel = NULL;
+	if ( pGameObjModelComponent )
+	{
+		pGameObjModel = pGameObjModelComponent->GetModel();
+	}
+
+	// (Mick) ... but only if it has a model (might be just collision)
+	if ( pModel && pGameObjModel )		
+	{
+		Mth::Vector scaleVec = pModel->GetScale();
+		pGameObjModel->SetScale( scaleVec );		// (Mick) - see check above
+	}
+	
+	return pGameObj;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSpecialItemComponent::DestroySpecialItem( int index )
+{
+	Dbg_MsgAssert( index >= 0 && index < vMAX_SPECIAL_ITEMS, ( "Special item index %d must be between 0 and %d", index, vMAX_SPECIAL_ITEMS ) );
+	
+	if ( mp_special_items[index] )
+	{
+		// run the cleanup script if it exists...
+		Script::CStruct* pStruct = new Script::CStruct;
+		mp_special_items[index]->CopyTagsToScriptStruct(pStruct);
+		uint32 cleanupScript;
+		if ( pStruct->GetChecksum( CRCD(0x40764820,"CleanupScript"), &cleanupScript, Script::NO_ASSERT ) )
+		{
+			// run a cleanup script if it exists
+			Script::RunScript( cleanupScript, NULL, mp_special_items[index] );
+		}
+		delete pStruct;
+
+		// don't actually delete it yet...
+		// instead, just mark it as dead, so 
+		// that if any objects still refer to
+		// it on this frame, it won't crash...
+		mp_special_items[index]->MarkAsDead();
+		mp_special_items[index] = NULL;
+		
+		// THPS4:  flushes dead objects so that we can
+		// recreate it on the same frame
+		// (theoretically, we should have been
+		// able to call "wait 1 gameframe",
+		// but it turns out that CScript::Update()
+		// can be called more than once per frame,
+		// if someone uses the MakeSkaterGoto
+		// command)
+		// THPS5:  doesn't seem to be a problem any
+		// more, perhaps due to the subtle changes in
+		// order which object/component logic gets
+		// run.  having this line in here does cause
+		// some issues now, in that it's possible
+		// for an object to indirectly delete itself from
+		// its update() function, which is bad.  i've
+		// taken this line out and will fix any
+		// related bugs as they arise.
+//		Mdl::Skate::Instance()->GetObjectManager()->FlushDeadObjects();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSpecialItemComponent::DestroyAllSpecialItems()
+{
+	// This also gets called when we switch levels...
+	// (from DeleteResources)
+		
+	for ( int index = 0; index < vMAX_SPECIAL_ITEMS; index++ )
+	{
+		DestroySpecialItem( index );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/specialitemcomponent.h b/Code/Gel/Components/specialitemcomponent.h
new file mode 100644
index 0000000..635069a
--- /dev/null
+++ b/Code/Gel/Components/specialitemcomponent.h
@@ -0,0 +1,61 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SpecialItemComponent.h
+//* OWNER:          gj
+//* CREATION DATE:  02/13/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_SPECIALITEMCOMPONENT_H__
+#define __COMPONENTS_SPECIALITEMCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_SPECIALITEM CRCD(0xf27a294d,"SpecialItem")
+#define		GetSpecialItemComponent() ((Obj::CSpecialItemComponent*)GetComponent(CRC_SPECIALITEM))
+#define		GetSpecialItemComponentFromObject(pObj) ((Obj::CSpecialItemComponent*)(pObj)->GetComponent(CRC_SPECIALITEM))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSpecialItemComponent : public CBaseComponent
+{
+public:
+    CSpecialItemComponent();
+    virtual ~CSpecialItemComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+public:
+	CCompositeObject*				CreateSpecialItem( int index, Script::CStruct* pNodeData );
+	void 							DestroySpecialItem( int index );
+	void 							DestroyAllSpecialItems();
+
+protected:
+	enum
+	{
+		vMAX_SPECIAL_ITEMS = 2
+	};
+	CCompositeObject*				mp_special_items[vMAX_SPECIAL_ITEMS];
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/trickcomponent.cpp b/Code/Gel/Components/trickcomponent.cpp
new file mode 100644
index 0000000..76b49a1
--- /dev/null
+++ b/Code/Gel/Components/trickcomponent.cpp
@@ -0,0 +1,4069 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       TrickComponent.cpp
+//* OWNER:          Mr Kendall Stuart Harrison Esq.
+//* CREATION DATE:  3 Jan 2003
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include  // Needed for GetPhysicsInt(...)
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+static bool s_is_direction_button(uint32 buttonName);
+#ifdef	__DEBUG_CODE__
+static void s_add_array_of_names(Script::CStruct *p_info, int size, uint32 *p_source_array, const char *p_name);
+#endif				 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Used by ExtraGapTrickLogic
+static bool s_is_direction_button(uint32 buttonName)
+{
+	switch (buttonName)
+	{
+		case 0xbc6b118f: // Up
+		case 0xe3006fc4: // Down
+		case 0x85981897: // Left
+		case 0x4b358aeb: // Right
+		case 0xb7231a95: // UpLeft
+		case 0xa50950c5: // UpRight
+		case 0xd8847efa: // DownLeft
+		case 0x786b8b68: // DownRight
+			return true;
+			break;
+				
+		default:
+			break;
+	}
+	return false;	
+}
+
+#ifdef	__DEBUG_CODE__
+// Converts an array of uint32's to a script array and inserts it into the passed structure, naming it p_name
+static void s_add_array_of_names(Script::CStruct *p_info, int size, uint32 *p_source_array, const char *p_name)
+{
+	Script::CArray *p_array=new Script::CArray;
+	if (size)
+	{
+		p_array->SetSizeAndType(size,ESYMBOLTYPE_NAME);
+		
+		for (int i=0; iSetChecksum(i,p_source_array[i]);
+		}
+	}
+	p_info->AddArrayPointer(p_name,p_array);
+}
+#endif				 
+
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CTrickComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CTrickComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CTrickComponent::CTrickComponent() : CBaseComponent()
+{
+	SetType( CRC_TRICK );
+	
+	mpTrickMappings=NULL;
+	mp_score=NULL;
+	mp_input_component=NULL;
+	mp_skater_balance_trick_component=NULL;
+	mp_skater_core_physics_component=NULL;
+	mp_skater_flip_and_rotate_component=NULL;
+	mp_skater_state_component=NULL;
+    mp_stats_manager_component=NULL;
+				 
+	Clear();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CTrickComponent::~CTrickComponent()
+{
+	if (mpTrickMappings)
+	{
+		delete mpTrickMappings;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickComponent::Finalize()
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	mp_skater_balance_trick_component = GetSkaterBalanceTrickComponentFromObject(GetObject());
+	mp_skater_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	mp_skater_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
+	mp_skater_state_component = GetSkaterStateComponentFromObject(GetObject());
+    mp_stats_manager_component = GetStatsManagerComponentFromObject(GetObject());
+            
+	Dbg_Assert(mp_input_component);
+	Dbg_Assert(mp_skater_balance_trick_component);
+	Dbg_Assert(mp_skater_core_physics_component);
+	Dbg_Assert(mp_skater_flip_and_rotate_component);
+	Dbg_Assert(mp_skater_state_component);
+    Dbg_Assert(mp_stats_manager_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Gets called from the CTrickComponent constructor, and also from CSkater::ResetEverything()
+void CTrickComponent::Clear()
+{
+	m_num_runtrick_recursions=0;
+	mNumButtonsToIgnore=0;
+
+	mUseSpecialTrickText=false;
+	
+	mFrontTrick = -1;
+	mBackTrick = -1;
+	
+	ClearQueueTricksArrays();
+	
+	mSpecialTricksArrayChecksum = 0;
+	mDoNotIgnoreMask=0;
+
+	ClearManualTrick();
+	mSpecialManualTricksArrayChecksum=0;
+	
+	ClearExtraGrindTrick();
+	mSpecialExtraGrindTrickArrayChecksum=0;
+	
+	mGotExtraTricks = false;
+	mExtraTricksInfiniteDuration = false;
+	mExtraTricksDuration = 0;
+	mExtraTricksStartTime = 0;
+	
+	mNumExtraTrickArrays = 0;
+	mSpecialExtraTricksArrayChecksum = 0;
+	mExcludedSpecialExtraTricks = 0;
+	
+	ClearEventBuffer();
+	for (int i=0; iGetStructure(CRCD(0xce42ee1a,"XBox_Trigger"),&p_trigger))
+		{
+			p_struct->GetStructure(CRCD(0xe594f0a2,"Trigger"),&p_trigger,Script::ASSERT);
+		}
+	}
+	else if (Config::GetHardware()==Config::HARDWARE_NGC)
+	{
+		if (!p_struct->GetStructure(CRCD(0xcb0e600c,"NGC_Trigger"),&p_trigger))
+		{
+			p_struct->GetStructure(CRCD(0xe594f0a2,"Trigger"),&p_trigger,Script::ASSERT);
+		}
+	}
+	else
+	{
+		p_struct->GetStructure(CRCD(0xe594f0a2,"Trigger"),&p_trigger,Script::ASSERT);
+	}
+	
+	return p_trigger;
+}
+
+// K: 6/16/03 This is to implement a new feature, where as well as a Trigger structure, an Alt_Trigger
+// structure can be specified. If either of Trigger or Alt_Trigger is satisfied, the trick triggers.
+// The Alt_Trigger is the same for all three platforms.
+Script::CStruct *CTrickComponent::get_alternate_trigger_structure(Script::CStruct *p_struct)
+{
+	Script::CStruct *p_trigger=NULL;
+	// Note: Not passing Script::ASSERT as in get_trigger_structure above, because the Alt_Trigger is optional.
+	p_struct->GetStructure(CRCD(0x68b74085,"Alt_Trigger"),&p_trigger);
+	
+	return p_trigger;
+}
+	
+void CTrickComponent::ClearQueueTricksArrays()
+{
+	mNumQueueTricksArrays=0;
+	for (int i=0; iClear();
+
+	// "max_specials" is not stored in the skater profile by
+	// the time the skater gets here...  so just loop through
+	// all 10 slots
+	
+	// get the special tricks from the skater profile
+	// they're stored in a different format than the
+	// regular tricks, so we can't just append the structure
+	for ( int i = 0; i < Obj::CSkaterProfile::vMAXSPECIALTRICKSLOTS; i++ )
+	{
+		SSpecialTrickInfo theInfo = pSkaterProfile->GetSpecialTrickInfo( i );
+		if ( !theInfo.IsUnassigned() )
+		{
+			if ( theInfo.m_isCat )
+			{
+				mpTrickMappings->AddComponent( theInfo.m_TrickSlot, ESYMBOLTYPE_INTEGER, (int)theInfo.m_TrickName );
+			}
+			else
+				mpTrickMappings->AddComponent( theInfo.m_TrickSlot, ESYMBOLTYPE_NAME, (int)theInfo.m_TrickName );
+		}
+	}
+
+	// get the regular tricks from the skater profile
+	mpTrickMappings->AppendStructure( pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") ) );
+}
+
+/******************************************************************/
+/*  Adds a single new event and returns a pointer to it.		  */
+/*                                                                */
+/******************************************************************/
+
+SButtonEvent *CTrickComponent::AddEvent(EEventType EventType)
+{
+	Dbg_MsgAssert(mNumEvents<=MAX_EVENTS,("mNumEvents too big, = %d",mNumEvents));
+	Dbg_MsgAssert(mLastEvent=MAX_EVENTS)
+	{
+		mLastEvent=0;
+	}
+	if (mNumEvents= OlderThan)
+			{
+				mpButtonEvents[Index].Used=0xffffffff;
+			}	
+		}	
+		--Index;
+		if (Index<0)
+		{
+			Index+=MAX_EVENTS;
+		}
+	}
+}
+
+bool CTrickComponent::TriggeredInLastNMilliseconds(uint32 ButtonNameChecksum, uint32 Duration, uint32 IgnoreMask)
+{
+	
+	
+	int Index=mLastEvent;
+	for (int i=0; i=Duration)
+		{
+			return false;
+		}
+
+		if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+			mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+			mpButtonEvents[Index].ButtonNameChecksum==ButtonNameChecksum)
+		{
+			mpButtonEvents[Index].MaybeUsed=true;
+			return true;
+		}	
+		
+		--Index;
+		if (Index<0)
+		{
+			Index+=MAX_EVENTS;
+		}	
+	}
+	return false;
+}
+
+bool CTrickComponent::BothTriggeredNothingInBetween(uint32 Button1, uint32 Button2, uint32 Duration, uint32 IgnoreMask)
+{
+	bool GotButton1=false;	
+	bool GotButton2=false;	
+	bool AnyOtherCancel=false;
+	
+	int Index=mLastEvent;
+	for (int i=0; i=Duration)
+		{
+			return false;
+		}
+
+		if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+			mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+		{
+			if (mpButtonEvents[Index].ButtonNameChecksum==Button1)
+			{
+				mpButtonEvents[Index].MaybeUsed=true;
+				if (GotButton2)
+				{
+					return true;
+				}
+				GotButton1=true;
+				AnyOtherCancel=true;
+			}	
+			else if (mpButtonEvents[Index].ButtonNameChecksum==Button2)
+			{
+				mpButtonEvents[Index].MaybeUsed=true;
+				if (GotButton1)
+				{
+					return true;
+				}
+				GotButton2=true;
+				AnyOtherCancel=true;
+			}	
+			else
+			{
+				if (AnyOtherCancel)
+				{
+					return false;
+				}
+			}		
+		}	
+		
+		--Index;
+		if (Index<0)
+		{
+			Index+=MAX_EVENTS;
+		}	
+	}
+	return false;
+}
+
+// Reads the button names and time duration out of a 'Trigger' structure.
+uint32 CTrickComponent::get_buttons_and_duration(Script::CComponent *p_comp, int num_buttons, uint32 *p_button_names)
+{
+	Dbg_MsgAssert(p_comp,("NULL p_comp"));
+	Dbg_MsgAssert(p_button_names,("NULL p_button_names"));
+	Dbg_MsgAssert(num_buttons<=MAX_TRICK_BUTTONS,("Bad num_buttons"));
+	
+	for (int i=0; imType==ESYMBOLTYPE_NAME,("Bad component type, expected name"));
+		
+		switch (p_comp->mChecksum)
+		{
+			case 0x9a7dc229: // Parent1
+				p_button_names[i]=mp_trick_button[0];
+				break;
+			case 0x03749393: // Parent2
+				p_button_names[i]=mp_trick_button[1];
+				break;
+			case 0x7473a305: // Parent3
+				p_button_names[i]=mp_trick_button[2];
+				break;
+			default:
+				p_button_names[i]=p_comp->mChecksum;
+				break;
+		}
+				
+		p_comp=p_comp->mpNext;
+	}	
+	
+	Dbg_MsgAssert(p_comp,("Missing duration"));
+	if (p_comp->mType==ESYMBOLTYPE_INTEGER)
+	{
+		return p_comp->mIntegerValue;
+	}	
+	Dbg_MsgAssert(p_comp->mType==ESYMBOLTYPE_NAME,("Bad duration value in trick, must be either an integer or a named integer"));
+	return GetPhysicsInt(p_comp->mChecksum);
+}
+
+void CTrickComponent::record_last_tricks_buttons(uint32 *p_button_names)	
+{
+	Dbg_MsgAssert(p_button_names,("NULL p_button_names"));
+	
+	// Remember what buttons were used by the last trick that got triggered.
+	// They are stored so that the extra-trick trigger can use one of the parent trick's buttons,
+	// rather than being hard wired. This is needed in case the player re-maps the parent trick to
+	// a different button combination.
+	uint32 *p_dest=mp_trick_button;
+	for (int i=0; i mButtonDebounceTime[Button]))
+		{
+			mButtonDebounceTime[Button] = 0;
+		}
+		else
+		{
+			return;
+		}
+	}
+		 
+	if (mButtonState[Button]==Pressed)
+	{
+		return;
+	}	
+	
+	mButtonState[Button]=Pressed;
+
+
+	uint32 ButtonChecksum=Inp::GetButtonChecksum( Button );
+	
+	// Perhaps ignore the button.
+	// This feature is used by the skater to ignore the button events used to control the balance
+	// when doing a manual, otherwise ChrisR can't do kickflips by very quickly jumping out of a 
+	// grind & kickflipping by rolling from X to Square.
+	for (int i=0; iButtonNameChecksum=ButtonChecksum;
+	}
+	else
+	{
+		SButtonEvent *pEvent=AddEvent(EVENT_BUTTON_RELEASED);
+		Dbg_MsgAssert(pEvent,("NULL pEvent ?"));
+		pEvent->ButtonNameChecksum=ButtonChecksum;
+	}
+}
+
+bool CTrickComponent::GetButtonState(uint32 Checksum)
+{
+	return mButtonState[Inp::GetButtonIndex(Checksum)];
+}
+
+// Note: This probably needs a lot of optimization, hmmm.
+bool CTrickComponent::QueryEvents(Script::CStruct *pQuery, uint32 UsedMask, uint32 IgnoreMask)
+{
+	if (!pQuery)
+	{
+		return false;
+	}	
+	
+	if (mp_input_component->IsInputDisabled())
+	{
+		return false;
+	}
+		
+	//Script::PrintContents(pQuery);	
+    Script::CComponent *pComp=pQuery->GetNextComponent(NULL);
+	if (!pComp)
+	{
+		return false;
+	}	
+	
+	Dbg_MsgAssert(pComp->mType==ESYMBOLTYPE_NAME,("Bad component type, expected name"));
+	uint32 NameChecksum=pComp->mChecksum;
+	pComp=pComp->mpNext;
+	
+	uint32 p_button_names[MAX_TRICK_BUTTONS];
+	for (int b=0; b=Duration)
+				{
+					return false;
+				}
+				
+				switch (Count)
+				{
+				case 0:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=1;
+					}	
+					break;
+				case 1:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						ConvertMaybeUsedToUsed(UsedMask);
+						record_last_tricks_buttons(p_button_names);
+						return true;
+					}	
+					break;
+				default:
+					break;
+				}
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+		case 0x2c434c90: // TapTwiceRelease
+		{
+			// Get two button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,2,p_button_names);
+
+			int Index=mLastEvent;
+			int Count=0;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				switch (Count)
+				{
+				case 0:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						(mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED && 
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1]))
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=1;
+					}	
+					break;
+				case 1:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=2;
+					}	
+					break;
+				case 2:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						ConvertMaybeUsedToUsed(UsedMask);
+						record_last_tricks_buttons(p_button_names);
+						return true;
+					}	
+					break;
+				default:
+					break;
+				}
+				
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+		case 0x696e5e66: // ReleaseAndTap
+		{
+			// Get two button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,2,p_button_names);
+
+			int e[3]={-1,-1,-1};
+				
+			int index=mLastEvent;
+			int count=0;
+			for (int i=0; i=Duration ||
+				Tmr::ElapsedTime(mpButtonEvents[e[1]].Time)>=Duration ||
+				Tmr::ElapsedTime(mpButtonEvents[e[2]].Time)>=Duration)
+			{
+				return false;
+			}	
+
+			if (mpButtonEvents[e[0]].EventType==EVENT_BUTTON_RELEASED && 
+				mpButtonEvents[e[0]].ButtonNameChecksum==p_button_names[1] &&
+                mpButtonEvents[e[1]].EventType==EVENT_BUTTON_PRESSED && 
+				mpButtonEvents[e[1]].ButtonNameChecksum==p_button_names[1] &&
+                mpButtonEvents[e[2]].EventType==EVENT_BUTTON_RELEASED && 
+				mpButtonEvents[e[2]].ButtonNameChecksum==p_button_names[0])
+			{
+				mpButtonEvents[e[0]].MaybeUsed=true;
+				mpButtonEvents[e[1]].MaybeUsed=true;
+				mpButtonEvents[e[2]].MaybeUsed=true;
+				ConvertMaybeUsedToUsed(UsedMask);
+				record_last_tricks_buttons(p_button_names);
+				return true;
+			}
+			break;
+		}	
+		case 0x3c3028f4: // PressTwo
+		{
+			// Get two button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,2,p_button_names);
+			
+			if (GetButtonState(p_button_names[0]) && GetButtonState(p_button_names[1]))
+			{
+				if (BothTriggeredNothingInBetween(p_button_names[0],p_button_names[1],Duration,IgnoreMask))
+				{
+					ConvertMaybeUsedToUsed(UsedMask);
+					record_last_tricks_buttons(p_button_names);
+					return true;
+				}
+			}		
+			break;
+		}	
+		
+		case 0x7d482318: // InOrder
+		{
+			// Get two button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,2,p_button_names);
+			
+			int Index=mLastEvent;
+			int Count=0;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				switch (Count)
+				{
+				case 0:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=1;
+					}	
+					break;
+				case 1:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						ConvertMaybeUsedToUsed(UsedMask);
+						record_last_tricks_buttons(p_button_names);
+						return true;
+					}	
+					break;
+				default:
+					break;
+
+				}
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+
+		case 0x3f369070: // PressTwoAnyOrder
+		{
+			int i;
+			int Index;
+			
+			// Get two button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,2,p_button_names);
+			
+			bool Found = false;
+			
+			bool FirstPressed = false;
+			bool SecondPressed = false;
+			Index=mLastEvent;
+			for (i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+					mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+				{
+					if (mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED)
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						FirstPressed=false;
+						SecondPressed=false;
+					}
+					else if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						if (SecondPressed)
+						{
+							Found=true;
+							break;
+						}
+						FirstPressed=true;
+					}
+				}	
+			
+				if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+					mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+				{
+					if (mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED)
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						FirstPressed=false;
+						SecondPressed=false;
+					}
+					else if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						if (FirstPressed)
+						{
+							Found=true;
+							break;
+						}
+						SecondPressed=true;
+					}
+				}	
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			
+			if (Found)
+			{
+				mpButtonEvents[Index].MaybeUsed=true;
+				ConvertMaybeUsedToUsed(UsedMask);
+				record_last_tricks_buttons(p_button_names);
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}	
+		
+		// Various different TripleInOrder's, which became necessary when using it for different types of
+		// tricks such as the boneless, and the special grinds.
+		// It was found that the logic needs to be quite sloppy for the boneless so that they can get triggered
+		// easily otherwise it feels wrong, whereas it needs to be stricter for special grinds otherwise 
+		// they go off all the time.
+		
+		// Requires that Button1 be pressed, followed by Button2 pressed, followed by Button3 pressed.
+		// Doesn't care when they get released.
+		case 0xc1ad35c0: // TripleInOrderSloppy
+		{
+			// Get three button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,3,p_button_names);
+			
+			int Index=mLastEvent;
+			int Count=0;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				switch (Count)
+				{
+				case 0:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[2])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=1;
+					}	
+					break;
+				case 1:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=2;
+					}	
+					break;
+				case 2:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						ConvertMaybeUsedToUsed(UsedMask);
+						record_last_tricks_buttons(p_button_names);
+						return true;
+					}	
+					break;
+				default:
+					break;
+
+				}
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+		// Button1 must be pressed then released, then Button2 must be pressed then released,
+		// then Button3 must be pressed. Doesn't require that Button3 be released, because it
+		// feels better if the logic takes effect exactly on the 3rd press.
+		case 0x3fe7c311: // TripleInOrderStrict
+		{
+			// Get three button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,3,p_button_names);
+			
+			int Index=mLastEvent;
+			int Count=0;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				switch (Count)
+				{
+				case 0:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[2])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=1;
+					}	
+					break;
+				case 1:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=2;
+					}	
+					break;
+				case 2:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=3;
+					}	
+					break;
+				case 3:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=4;
+					}	
+					break;
+				case 4:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						ConvertMaybeUsedToUsed(UsedMask);
+						record_last_tricks_buttons(p_button_names);
+						return true;
+					}	
+					break;
+				default:
+					break;
+
+				}
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+		// Button1 must be pressed then released, followed by either button2 pressed then button3 pressed,
+		// or button3 pressed then button2 pressed.
+		// Sort of strict & sloppy at the same time.
+		case 0xa2e042d7: // TripleInOrder
+		{
+			// Get three button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,3,p_button_names);
+			
+			int Index=mLastEvent;
+			int Count=0;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				switch (Count)
+				{
+				case 0:
+					// Even though it's called TripleInOrder, buttons 2 and 3
+					// can be pressed in either order, so we do a bit of convoluted
+					// branching here to check for both cases.
+					if (!(mpButtonEvents[Index].Used & IgnoreMask))
+					{
+						if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+							mpButtonEvents[Index].ButtonNameChecksum==p_button_names[2])
+						{
+							mpButtonEvents[Index].MaybeUsed=true;
+							Count=1;
+							break;
+						}	
+						if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+							mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+						{
+							mpButtonEvents[Index].MaybeUsed=true;
+							Count=2;
+							break;
+						}	
+					}	
+					break;
+					
+					
+				case 1:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=3;
+					}	
+					break;
+					
+				case 2:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[2])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=3;
+					}	
+					break;
+					
+					
+				// Button 1 has to be pressed and released before the other 2.	
+				case 3:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=4;
+					}	
+					break;
+				case 4:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						ConvertMaybeUsedToUsed(UsedMask);
+						record_last_tricks_buttons(p_button_names);
+						return true;
+					}	
+					break;
+				default:
+					break;
+
+				}
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}
+		case 0x823b8342: // Press
+		{
+			// Get one button name followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,1,p_button_names);
+			
+			int Index=mLastEvent;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+					mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+					mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+				{
+					mpButtonEvents[Index].Used|=UsedMask;
+					record_last_tricks_buttons(p_button_names);
+					return true;
+				}	
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+		case 0x61b8fce2: // Release
+		{
+			// Get one button name followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,1,p_button_names);
+			
+			int Index=mLastEvent;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+				
+				if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+					mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+					mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+				{
+					mpButtonEvents[Index].Used|=UsedMask;
+					record_last_tricks_buttons(p_button_names);
+					return true;
+				}	
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+		case 0x7fa5cdbb: // Tap
+		{
+			// Get one button name followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,1,p_button_names);
+			
+			int Count=0;
+			int Index=mLastEvent;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+				
+				if (Count == 0)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count++;
+					}
+				}
+				else
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						ConvertMaybeUsedToUsed(UsedMask);
+						return true;
+					}
+				}
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+		case 0xf630dae5: // HoldTwoAndPress
+		{
+			// Get three button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,3,p_button_names);
+			
+			int Index=mLastEvent;
+			int Count=0;
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+			
+				switch (Count)
+				{
+				case 0:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[2])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						Count=1;
+						break;
+					}	
+					break;
+					
+				case 1:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask))
+					{
+						if (mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+						{
+							if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+							{
+								mpButtonEvents[Index].MaybeUsed=true;
+								Count=2;
+							}
+							else if (mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED)
+							{
+								return false;
+							}	
+						}
+						else if (mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+						{
+							if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+							{
+								mpButtonEvents[Index].MaybeUsed=true;
+								Count=3;
+							}
+							else if (mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED)
+							{
+								return false;
+							}	
+						}
+					}		
+					break;
+					
+				case 2:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask))
+					{
+						if (mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+						{
+							if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+							{
+								mpButtonEvents[Index].MaybeUsed=true;
+								ConvertMaybeUsedToUsed(UsedMask);
+								record_last_tricks_buttons(p_button_names);
+								return true;
+							}
+							else if (mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED)
+							{
+								return false;
+							}	
+						}
+					}		
+					break;
+
+				case 3:
+					if (!(mpButtonEvents[Index].Used & IgnoreMask))
+					{
+						if (mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+						{
+							if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+							{
+								mpButtonEvents[Index].MaybeUsed=true;
+								ConvertMaybeUsedToUsed(UsedMask);
+								record_last_tricks_buttons(p_button_names);
+								return true;
+							}
+							else if (mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED)
+							{
+								return false;
+							}	
+						}
+					}		
+					break;
+					
+				default:
+					break;
+
+				}
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+
+		case 0xac2b1445: // ReleaseTwoAndPress
+		{
+			// Get three button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,3,p_button_names);
+			
+			int Index=mLastEvent;
+			bool got_a_released=false;
+			bool got_b_released=false;
+			bool got_c_pressed=false;
+			
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+
+				if (!got_a_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						got_a_released=true;
+					}	
+				}					
+				if (!got_b_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						got_b_released=true;
+					}	
+				}					
+				if (!got_c_pressed)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[2])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						got_c_pressed=true;
+					}	
+				}					
+
+
+				if (got_a_released && !got_b_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						return false;
+					}	
+				}					
+				if (got_b_released && !got_a_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						return false;
+					}	
+				}					
+				
+				if (got_a_released && got_b_released && got_c_pressed)
+				{
+					ConvertMaybeUsedToUsed(UsedMask);
+					record_last_tricks_buttons(p_button_names);
+					return true;
+				}	
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+
+		case 0x395ec2d4: // ReleaseTwo
+		{
+			// Get two button names followed by a duration value.
+			uint32 Duration=get_buttons_and_duration(pComp,2,p_button_names);
+			
+			int Index=mLastEvent;
+			bool got_a_released=false;
+			bool got_b_released=false;
+			
+			for (int i=0; i=Duration)
+				{
+					return false;
+				}
+
+				if (!got_a_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						got_a_released=true;
+					}	
+				}					
+				if (!got_b_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						mpButtonEvents[Index].MaybeUsed=true;
+						got_b_released=true;
+					}	
+				}					
+
+				if (got_a_released && !got_b_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						return false;
+					}	
+				}					
+				if (got_b_released && !got_a_released)
+				{
+					if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+						mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED &&
+						mpButtonEvents[Index].ButtonNameChecksum==p_button_names[1])
+					{
+						return false;
+					}	
+				}					
+				
+				if (got_a_released && got_b_released)
+				{
+					ConvertMaybeUsedToUsed(UsedMask);
+					record_last_tricks_buttons(p_button_names);
+					return true;
+				}	
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+			break;
+		}	
+
+		case 0xb6258557: // ExtraGrabTrickLogic
+		{
+			uint32 last_direction_button=0;
+			for (int b=0; b=Duration)
+				{
+					break;
+				}
+			
+				if (!(mpButtonEvents[Index].Used & IgnoreMask) &&
+					mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED)
+				{
+					if (mpButtonEvents[Index].ButtonNameChecksum==p_button_names[0])
+					{
+						// OK, so the button has been pressed, but don't return true
+						// just yet cos we want to keep looking back in time & kill any
+						// direction button events that are the same direction button as
+						// used by last trick.
+						do_trigger=true;
+						pressed_index=Index;
+					}	
+					else if (s_is_direction_button(mpButtonEvents[Index].ButtonNameChecksum))
+					{
+						// A direction button has gotten pushed during the same time interval ...
+						
+						if (mpButtonEvents[Index].ButtonNameChecksum != last_direction_button)
+						{
+							// It is not the same as the previous trick's direction button,
+							// so do not trigger at all. Run away without killing any events.
+							return false;
+						}	
+						else
+						{
+							// It is the same as the previous trick's direction button,
+							// so flag it as maybe used.
+							// If we survive the rest of this loop without the above 'return true'
+							// firing then the MaybeUsed flags will get converted to used after
+							// the loop has finished.
+							mpButtonEvents[Index].MaybeUsed=true;
+						}	
+					}
+				}	
+				
+				--Index;
+				if (Index<0)
+				{
+					Index+=MAX_EVENTS;
+				}	
+			}
+
+			if (do_trigger)
+			{
+				// Kill the events for sure.
+				mpButtonEvents[pressed_index].Used|=UsedMask;
+				ConvertMaybeUsedToUsed(UsedMask);
+				record_last_tricks_buttons(p_button_names);
+				return true;
+			}
+			break;
+		}
+
+		case 0xa8123ecf: // HoldThree			
+		{
+			Script::CComponent *p_comp=pComp;
+			
+			Dbg_MsgAssert(p_comp && p_comp->mType==ESYMBOLTYPE_NAME,("HoldThree logic requires 3 button names"));
+			uint32 button1=p_comp->mChecksum;
+			p_comp=p_comp->mpNext;
+			Dbg_MsgAssert(p_comp && p_comp->mType==ESYMBOLTYPE_NAME,("HoldThree logic requires 3 button names"));
+			uint32 button2=p_comp->mChecksum;
+			p_comp=p_comp->mpNext;
+			Dbg_MsgAssert(p_comp && p_comp->mType==ESYMBOLTYPE_NAME,("HoldThree logic requires 3 button names"));
+			uint32 button3=p_comp->mChecksum;
+			
+			return GetButtonState(button1) && GetButtonState(button2) && GetButtonState(button3);
+			break;
+		}
+			
+		default:
+			Dbg_MsgAssert(0,("Unexpected name '%s'",Script::FindChecksumName(NameChecksum)));
+			break;
+	}		
+
+	ResetMaybeUsedFlags();
+	return false;
+}
+
+/*****************************************************************************
+**																			**
+**							Trick queue stuff								**
+**																			**
+*****************************************************************************/
+
+void CTrickComponent::ClearTrickQueue()
+{
+	
+	mFrontTrick=mBackTrick=-1;
+}
+
+void CTrickComponent::AddTrick(uint32 ArrayChecksum, uint Index, bool UseSpecialTrickText)
+{
+	int i=0;
+	if (mFrontTrick==-1 && mBackTrick==-1)
+	{
+		// The queue is empty, so initialise the front and back.
+		mFrontTrick=i;
+		mBackTrick=i;
+	}
+	else
+	{
+		Dbg_MsgAssert(mFrontTrick!=-1,("mFrontTrick = -1 ??"));
+		Dbg_MsgAssert(mBackTrick!=-1,("mBackTrick = -1 ??"));
+		
+		// Go to the current back of the queue
+		i=mBackTrick;
+		// Advance to the next entry
+		++i;
+		if (i>=TRICK_QUEUE_SIZE)
+		{
+			i=0;
+		}
+		// If reached the front again then there is no room left,
+		// so don't do anything.
+		if (i==mFrontTrick)
+		{
+			return;
+		}
+		// Got a new back of the queue.
+		mBackTrick=i;			
+	}	
+	
+	// Write in the info.
+	mpTricks[i].ArrayChecksum=ArrayChecksum;
+	mpTricks[i].Index=Index;
+	mpTricks[i].UseSpecialTrickText=UseSpecialTrickText;
+}
+
+void CTrickComponent::RemoveFrontTrick()
+{
+	
+	// If the queue is empty then there is nothing to do.
+	if (mFrontTrick==-1)
+	{
+		return;
+	}	
+	
+	Dbg_MsgAssert(mBackTrick!=-1,("mBackTrick = -1 ??"));
+	
+	// If only one element in the queue, then clear the queue.
+	if (mFrontTrick==mBackTrick)
+	{
+		ClearTrickQueue();
+		return;
+	}	
+	
+	// Advance the front trick to the next entry.
+	++mFrontTrick;
+	if (mFrontTrick>=TRICK_QUEUE_SIZE)
+	{
+		mFrontTrick=0;
+	}	
+}
+
+// This will remove all tricks that came from a certain trick array.
+// This is used by the ClearTricksFrom command, which is used by air-trick
+// scripts to remove any boneless's as soon as the air trick is triggered, so that 
+// the boneless does not trigger afterwards. It is possible to trigger a boneless for
+// a short time after becoming airborne due to the ground-gone exception, this is
+// the 'late-jump'.
+void CTrickComponent::ClearTricksFrom(uint32 ArrayChecksum)
+{
+	
+	
+	int i;
+	
+	// Remove the array from the list of arrays being checked.
+	for (i=0; iGetChecksum(0xa6d2d890/* Scr */,&ScriptChecksum))
+	{
+		return true;
+	}
+	if (pTrick->GetChecksum(0x22e168c1/* Scripts */,&ScriptChecksum))
+	{
+		return true;
+	}
+	Script::CArray *p_template_array=NULL;
+	if (pTrick->GetArray(0x689fe07c/* Template */,&p_template_array))
+	{
+		return true;
+	}
+
+
+	// Get the checksum of the slot ...
+	uint32 TrickSlotChecksum=0;
+	if (pTrick->GetChecksum(0xa92a2280/* TrickSlot */,&TrickSlotChecksum))
+	{
+		// Look up this slot in the trick mappings ...
+		Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings ?"));
+		uint32 TrickChecksum=0;
+		mpTrickMappings->GetChecksum(TrickSlotChecksum,&TrickChecksum);
+	
+		if (TrickChecksum)
+		{
+			// Now, look up the global structure with this name.
+			if (Script::GetStructure(TrickChecksum))
+			{
+				return true;
+			}
+		}
+		else
+		{
+			// If the trick slot is defined to be an integer, then it is referring
+			// to a create-a-trick.
+			int create_a_trick=0;
+			if (mpTrickMappings->GetInteger(TrickSlotChecksum,&create_a_trick))
+			{
+				return true;
+			}	
+		}
+	}			
+	return false;
+}
+
+bool CTrickComponent::RunTrick(Script::CStruct *pTrick, uint32 optionalFlag, Script::CStruct* pExtraParams)
+{
+	Dbg_MsgAssert(pTrick,("NULL pTrick"));
+	
+    uint32 ScriptChecksum=0;
+    pTrick->GetChecksum(CRCD(0xa6d2d890,"Scr"),&ScriptChecksum);
+	
+	// Dan: HACK
+	// final check to insure that we don't do a grind extra trick when we're not on a rail; if the scripts were perfect, this would never occur; however,
+	// this should prevent an obscure net crash
+	if (ScriptChecksum == CRCD(0x255ed86f, "Grind") && mp_skater_core_physics_component->GetRailNode() == -1)
+	{
+		return false;
+	}
+    
+    // counting fliptricks and grabtricks for stats goals
+    Script::CStruct* pParams;
+    if(pTrick->GetStructure(CRCD(0x7031f10c,"Params"),&pParams))
+    {
+        if (!pParams->ContainsComponentNamed(CRCD(0x7a16aca0,"IsExtra")) )
+        {
+            mp_stats_manager_component->SetTrickType( ScriptChecksum );
+        }
+    }
+	
+#ifdef __NOPT_ASSERT__
+	if (m_num_runtrick_recursions>5)
+	{
+		#ifdef	__NOPT_ASSERT__
+		printf("WARNING! More than 5 RunTrick recursions\nScriptChecksum='%s'",Script::FindChecksumName(ScriptChecksum));
+		#endif
+		return false;
+	}	
+#endif		// __NOPT_ASSERT__
+	
+	if (ScriptChecksum)
+	{
+		// Increment this so that too many recursions can be detected.
+		++m_num_runtrick_recursions;
+		
+		// A trick is triggered, so clear any manual or special grind trick that might have got triggered before.
+		mGotManualTrick=false;
+		mGotExtraGrindTrick=false;
+	
+		// Also clear the do-not-ignore mask, which may have got set by the last SetQueueTricks command.
+		mDoNotIgnoreMask=0;
+		
+		// Change the skater script to be the trick script.
+		Script::CStruct *pScriptParams=NULL;
+		pTrick->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptParams);
+	
+		// Run the script.
+		mp_skater_flip_and_rotate_component->DoAnyFlipRotateOrBoardRotateAfters(); // <- See huge comment above definition of this function.
+		
+		CCompositeObject *p_object=GetObject();
+		Dbg_MsgAssert(p_object,("Trick component has NULL object ?"));
+		p_object->SwitchScript(ScriptChecksum,pScriptParams);
+
+		// If a flag value was passed in, insert it into the script's parameters.
+		if (optionalFlag)
+		{
+			// Note that the flag is being inserted into the script's params after calling
+			// SetScript rather than putting it into pScriptParams.
+			// This is because pScriptParams could be pointing into some global structure, and
+			// that should be kept read-only.
+			Script::CStruct *p_script_params=p_object->GetScript()->GetParams();
+			Dbg_MsgAssert(p_script_params,("NULL p_script_params"));
+			p_script_params->AddChecksum(NONAME,optionalFlag);
+		}
+		
+		p_object->GetScript()->GetParams()->AppendStructure(pExtraParams);
+			
+		p_object->GetScript()->Update();
+	
+		// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
+		mp_skater_state_component->SetDoingTrick( true );
+	
+		// Increment the trick count, which may set pedestrian exceptions to
+		// make them go "oooOOOOOooooh"
+		IncrementNumTricksInCombo();
+		
+		// Decrement the recursion counter now that this chunk of code has completed.
+		--m_num_runtrick_recursions;
+		return true;
+	}	
+	else
+	{
+		#ifdef	__NOPT_ASSERT__
+		uint32 trickslot=0;
+		if (!pTrick->GetChecksum(CRCD(0xa92a2280,"TrickSlot"),&trickslot))
+		{
+			printf("WARNING! Scr not defined in trick structure!\n");
+			Script::PrintContents(pTrick);
+		}	
+		#endif
+	}			
+	
+	return false;
+}
+
+/******************************************************************/
+/* Runs the next trick in the queue.							  */
+/******************************************************************/
+void CTrickComponent::TriggerNextQueuedTrick(uint32 scriptToRunFirst, Script::CStruct *p_scriptToRunFirstParams, Script::CStruct* pExtraParams)
+{
+	mp_skater_state_component->SetDoingTrick( false );
+	mUseSpecialTrickText=false;
+
+	while (true)
+	{
+		if (mFrontTrick==-1)
+		{
+			// No tricks left in the queue, so break.
+			break;
+		}
+			
+		// Grab the array checksum & index of the trick within the array,
+		// then remove the trick from the queue.
+		// Removing the trick straight away in case the new script also does
+		// a DoNextTrick, which would cause infinite recursion if this trick
+		// was still in the queue.
+		uint32 ArrayChecksum=mpTricks[mFrontTrick].ArrayChecksum;
+		uint Index=mpTricks[mFrontTrick].Index;
+		// Set this flag so that any special trick scripts that do get executed during the RunTrick
+		// will use the yellow text if they execute a Display command. 
+		mUseSpecialTrickText=mpTricks[mFrontTrick].UseSpecialTrickText;
+		
+		RemoveFrontTrick();
+
+		// The ArrayChecksum might be zero, this would indicate that 
+		// the trick got cancelled by a call to ClearTricksFrom().
+		if (ArrayChecksum)
+		{
+			// The DoNextTrick command can specify a script that must be run before the trick script.
+			// So if one was specified, run it.
+			if (scriptToRunFirst)
+			{
+				GetObject()->AllocateScriptIfNeeded();
+				GetObject()->GetScript()->Interrupt(scriptToRunFirst, p_scriptToRunFirstParams);
+			}
+				
+			// Get the trick array that the trick belongs to.
+			Script::CArray *pArray=Script::GetArray(ArrayChecksum);
+			Dbg_MsgAssert(pArray,("NULL pArray ?"));
+			
+			// Get the structure defining the trick.
+			Script::CStruct *pTrickStruct=pArray->GetStructure(Index);
+			Dbg_MsgAssert(pTrickStruct,("NULL pTrickStruct ???"));
+			
+			// Try running the trick the old way, which is where the trick script is
+			// specified directly in the structure. 
+			if (!RunTrick(pTrickStruct, 0, pExtraParams))
+			{
+				// That didn't work, so they must be wanting to specify a trick slot
+				// instead, so get the trickslot ...
+				
+				// Get the checksum of the slot ...
+				uint32 TrickSlotChecksum=0;
+				pTrickStruct->GetChecksum(CRCD(0xa92a2280,"TrickSlot"),&TrickSlotChecksum);
+			
+				if (TrickSlotChecksum)
+				{
+					// Look up this slot in the trick mappings ...
+					Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings ?"));
+				
+					// Maybe it is a create-a-trick, in which case it is referred to by an
+					// integer index value.
+					int create_a_trick=0;
+					if (mpTrickMappings->GetInteger(TrickSlotChecksum,&create_a_trick))
+					{
+						// There is a create-a-trick defined!
+						// Run the special CreateATrick script passing the index as a parameter.
+						Script::CStruct *p_params=new Script::CStruct;
+						p_params->AddInteger(CRCD(0xb4a39841,"trick_index"),create_a_trick);
+						
+						p_params->AppendStructure(pExtraParams);
+						
+						CCompositeObject *p_object=GetObject();
+						Dbg_MsgAssert(p_object,("Trick component has NULL object ?"));
+						p_object->SwitchScript(CRCD(0x2d90485d,"CreateATrick"),p_params);
+						delete p_params;
+							
+						p_object->GetScript()->Update();
+						
+						// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
+						mp_skater_state_component->SetDoingTrick( true );
+					
+						// Increment the trick count, which may set pedestrian exceptions to
+						// make them go "oooOOOOOooooh"
+						IncrementNumTricksInCombo();
+					}
+					else
+					{
+						uint32 TrickChecksum=0;
+						mpTrickMappings->GetChecksum(TrickSlotChecksum,&TrickChecksum);
+					
+						if (TrickChecksum)
+						{
+							// Now, look up the global structure with this name.
+							
+							Script::CStruct *pTrick=Script::GetStructure(TrickChecksum);
+							if (pTrick)
+							{
+								RunTrick(pTrick, 0, pExtraParams);
+							}
+						}
+					}
+				}			
+			}
+			
+			// The ArrayChecksum was not zero, so break.
+			// We only keep looping if the ArrayChecksum is zero, so that all the zeros get skipped over.
+			// If the ArrayChecksum is zero this indicates that the trick got cancelled by a call to
+			// ClearTricksFrom().
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/* Scans through a trick array, and adds any tricks that got   	  */
+/* triggered to the trick queue.                                  */
+/******************************************************************/
+void CTrickComponent::MaybeAddTrick(uint32 ArrayChecksum, bool UseSpecialTrickText, uint32 DoNotIgnoreMask)
+{
+	
+
+	Dbg_MsgAssert(ArrayChecksum,("Zero ArrayChecksum sent to MaybeAddTrick"));
+		
+	// Resolve the checksum into a CArray pointer.
+	// The array is stored by checksum in case it gets reloaded.
+	Script::CArray *pArray=Script::GetArray(ArrayChecksum);
+		
+	// The above would have asserted if the array wasn't found, but check pArray isn't NULL anyway.
+	if (pArray)
+	{
+		// Scan through the array checking each trick's trigger condition.
+		int Size=pArray->GetSize();
+		for (int i=0; iGetStructure(i);
+			Dbg_MsgAssert(pStruct,("NULL pStruct ???"));
+			// pStruct is the structure defining the trick.
+			
+			if (TrickIsDefined(pStruct))
+			{
+				// Get the trigger, which is a structure defining the button combination that triggers the trick.
+				Script::CStruct *p_trigger=get_trigger_structure(pStruct);
+				// An alternate trigger may also be specified. This will also trigger the trick.
+				Script::CStruct *p_alternate_trigger=get_alternate_trigger_structure(pStruct);
+			
+				// p_trigger could be NULL, but OK cos QueryEvents will return false in that case.
+				// The DoNotIgnoreMask specifies additional used-events that should not be ignored.
+				uint32 ignore_mask=(~USED_BY_MANUAL_TRICK) & (~DoNotIgnoreMask);
+				
+				if (QueryEvents(p_trigger,USED_BY_REGULAR_TRICK,ignore_mask) ||
+					QueryEvents(p_alternate_trigger,USED_BY_REGULAR_TRICK,ignore_mask))
+				{
+					// The conditions are met, so add the trick to the trick queue.
+					// The trick is specified by the name of the array it is in, and its index within that array.
+					AddTrick(ArrayChecksum,i,UseSpecialTrickText);
+				}
+			}	
+		}
+	}
+}
+	
+void CTrickComponent::AddTricksToQueue()
+{
+	
+
+	// If a special tricks array is specified, and we're in the special state, check them.
+	if (mSpecialTricksArrayChecksum)
+	{
+		Mdl::Score *pScore=GetScoreObject();
+		if (pScore->GetSpecialState())
+		{
+			MaybeAddTrick(mSpecialTricksArrayChecksum,USE_SPECIAL_TRICK_TEXT,mDoNotIgnoreMask);
+		}
+	}	
+
+	// Check the usual arrays.
+	for (int TrickArrayIndex=0; TrickArrayIndexGetChecksum(CRCD(0x148fee96,"ScriptToRunFirst"),&scriptToRunFirst);
+	pExtraParams->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptToRunFirstParams);
+
+	mUseSpecialTrickText=false;
+	
+	if (mGotManualTrick)
+	{
+		// Now that the trick is being done, remove it.
+		// Removing it before changing the script rather than after to
+		// prevent any possibility of infinite recursion.
+		mGotManualTrick=false;
+		
+		
+		// The DoNextManualTrick command can specify a script that must be run before the trick script.
+		// So if one was specified, run it.
+		if (scriptToRunFirst)
+		{
+			// we need to copy and restore the script parameters as they will be corrupted by the interrupt
+			Script::CStruct extraParamsCopy(*pExtraParams);
+			GetObject()->AllocateScriptIfNeeded();
+			GetObject()->GetScript()->Interrupt(scriptToRunFirst, pScriptToRunFirstParams);
+			*pExtraParams = extraParamsCopy;
+		}
+		
+		// Get the trick array that the trick belongs to.
+		Script::CArray *pArray=Script::GetArray(mManualTrick.ArrayChecksum);
+		if (pArray)
+		{
+			// Get the structure defining the trick.
+			Script::CStruct *pStruct=pArray->GetStructure(mManualTrick.Index);
+			Dbg_MsgAssert(pStruct,("NULL pStruct ???"));
+			
+
+			// Get the checksum of the slot if there is one ...
+			uint32 TrickSlotChecksum=0;
+			pStruct->GetChecksum(CRCD(0xa92a2280,"TrickSlot"),&TrickSlotChecksum);
+			
+			if (TrickSlotChecksum)
+			{
+				// Look up this slot in the trick mappings ...
+				Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings ?"));
+				uint32 TrickChecksum=0;
+				mpTrickMappings->GetChecksum(TrickSlotChecksum,&TrickChecksum);
+				
+				if (TrickChecksum)
+				{
+					// Now, look up the global structure with this name.
+					
+					Script::CStruct *pTrick=Script::GetStructure(TrickChecksum);
+					if (pTrick)
+					{
+						// Now change pStruct to be pTrick, so that Scr and Params get read out of it instead.
+						pStruct=pTrick;
+					}
+				}
+			}
+			// If there is no TrickSlot specified, then use pStruct as is, to maintain backwards compatibility.			
+
+			
+			// Read the script checksum of the trick.
+			uint32 ScriptChecksum=0;
+			pStruct->GetChecksum(CRCD(0xa6d2d890,"Scr"),&ScriptChecksum);
+			if (ScriptChecksum)
+			{
+				mp_skater_flip_and_rotate_component->DoAnyFlipRotateOrBoardRotateAfters(); // <- See huge comment above definition of this function.
+				mUseSpecialTrickText=mManualTrick.UseSpecialTrickText;
+			
+				// Change the skater script to be the trick script.
+				Script::CStruct *pScriptParams=NULL;
+				pStruct->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptParams);
+				
+				CCompositeObject *p_object=GetObject();
+				Dbg_MsgAssert(p_object,("Trick component has NULL object ?"));
+
+				if (pExtraParams)
+				{
+					Dbg_MsgAssert(pExtraParams!=pScriptParams,("Eh ??"));
+					
+					// If extra params (params following the DoNextTrick command) were
+					// specified, then merge the pScriptParams onto them and use them.
+					pExtraParams->AppendStructure(pScriptParams);
+					
+					p_object->SwitchScript(ScriptChecksum,pExtraParams);
+				}
+				else
+				{
+					p_object->SwitchScript(ScriptChecksum,pScriptParams);
+				}
+				p_object->GetScript()->Update();
+								
+				// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
+				mp_skater_state_component->SetDoingTrick( true );
+				
+				// Increment the trick count, which may set pedestrian exceptions to
+				// make them go "oooOOOOOooooh"
+				IncrementNumTricksInCombo();
+			}	
+		}
+	}
+}
+	
+void CTrickComponent::CheckManualTrickArray(uint32 ArrayChecksum, uint32 IgnoreMask, bool UseSpecialTrickText)
+{
+	
+	Dbg_MsgAssert(ArrayChecksum,("Zero ArrayChecksum sent to CheckManualTrickArray"));
+	
+	// Resolve the checksum into a CArray pointer.
+	// The array is stored by checksum in case it gets reloaded.
+	Script::CArray *pArray=Script::GetArray(ArrayChecksum);
+		
+	// The above would have asserted if the array wasn't found, but check pArray isn't NULL anyway.
+	if (pArray)
+	{
+		// Scan through the array checking each trick.
+		int Size=pArray->GetSize();
+		for (int t=0; tGetStructure(t);
+			Dbg_MsgAssert(pStruct,("NULL pStruct ???"));
+			// pStruct is the structure defining the trick.
+			
+			if (TrickIsDefined(pStruct))
+			{
+				// Get the trigger, which is a structure defining the button combination that triggers the trick.
+				Script::CStruct *p_trigger=get_trigger_structure(pStruct);
+				// An alternate trigger may also be specified. This will also trigger the trick.
+				Script::CStruct *p_alternate_trigger=get_alternate_trigger_structure(pStruct);
+				
+				
+				// p_trigger could be NULL, but OK cos QueryEvents will return false in that case.
+				if (QueryEvents(p_trigger,USED_BY_MANUAL_TRICK,IgnoreMask) ||
+					QueryEvents(p_alternate_trigger,USED_BY_MANUAL_TRICK,IgnoreMask))
+				{
+					// The conditions are met, so add the manual trick.
+					mManualTrick.ArrayChecksum=ArrayChecksum;
+					mManualTrick.Index=t;
+					mManualTrick.Time=Tmr::GetTime();
+					mManualTrick.Duration=0xffffffff;
+					mManualTrick.UseSpecialTrickText=UseSpecialTrickText;
+					pStruct->GetInteger(0x79a07f3f/*Duration*/,(int*)&mManualTrick.Duration);
+					mGotManualTrick=true;
+				}
+			}	
+		}
+	}
+}
+	
+// Always called every frame.
+void CTrickComponent::MaybeQueueManualTrick()
+{
+	
+
+	// Check any special manual tricks.
+	if (mSpecialManualTricksArrayChecksum)
+	{
+		// Got extra manual tricks ...
+		Mdl::Score *pScore=GetScoreObject();
+		if (pScore->GetSpecialState())
+		{
+			// And we're special, so check the tricks.
+			// The ~USED_BY_MANUAL_TRICK means don't ignore button events that have already been
+			// used by manuals. This is so that any normal manual that has just got queued won't
+			// steal the events needed by a up-down-triangle special manual for example.
+			CheckManualTrickArray(mSpecialManualTricksArrayChecksum,~USED_BY_MANUAL_TRICK,USE_SPECIAL_TRICK_TEXT);
+			if (mGotManualTrick)
+			{
+				return;
+			}	
+		}
+	}
+	
+	// For each of the trick arrays, check whether any of the tricks listed within are triggered.
+	for (int i=0; imManualTrick.Duration)
+		{
+			mGotManualTrick=false;
+		}	
+	}	
+}
+
+/*****************************************************************************
+**																			**
+**							Extra trick stuff								**
+**																			**
+*****************************************************************************/
+
+bool CTrickComponent::TriggerAnyExtraTrick(uint32 ArrayChecksum, uint32 ExcludedTricks)
+{
+	
+
+	bool TriggeredATrick=false;
+	
+	Dbg_MsgAssert(ArrayChecksum,("Zero ArrayChecksum sent to TriggerAnyExtraTrick"));
+	Script::CArray *pArray=Script::GetArray(ArrayChecksum);
+	
+	// The above would have asserted if the array wasn't found, but check pArray isn't NULL anyway.
+	if (pArray)
+	{
+		// Scan through the array checking each trick.
+		int Size=pArray->GetSize();
+		// Only 32 bits available in the mpExcludedExtraTricks entries.
+		Dbg_MsgAssert(Size<=32,("Extra trick array '%s' has more than 32 entries",Script::FindChecksumName(ArrayChecksum)));
+		
+		for (int i=0; iGetStructure(i);
+				Dbg_MsgAssert(pTrickStruct,("NULL pTrickStruct ???"));
+				// pTrickStruct is the structure defining the trick.
+				
+				if (TrickIsDefined(pTrickStruct))
+				{
+					// Get the trigger, which is a structure defining the button combination that triggers the trick.
+					Script::CStruct *p_trigger=get_trigger_structure(pTrickStruct);
+					// An alternate trigger may also be specified. This will also trigger the trick.
+					Script::CStruct *p_alternate_trigger=get_alternate_trigger_structure(pTrickStruct);
+					
+					// p_trigger could be NULL, but OK cos QueryEvents will return false in that case.
+					
+					// The USED_BY_EXTRA_TRICK flags value means flag any button events used as being
+					// used by the extra tricks.
+					if (QueryEvents(p_trigger,USED_BY_EXTRA_TRICK,~USED_BY_MANUAL_TRICK) ||
+						QueryEvents(p_alternate_trigger,USED_BY_EXTRA_TRICK,~USED_BY_MANUAL_TRICK))
+					{
+						// Run the trick ...
+					
+						// Try running the trick the old way, which is where the trick script is
+						// specified directly in the structure. 
+						// Passing the flag IsExtra to the script, so that the script can tell
+						// whether it was run as an extra trick or a regular trick.
+						if (!RunTrick(pTrickStruct,0x7a16aca0/*IsExtra*/))
+						{
+							// That didn't work, so they must be wanting to specify a trick slot
+							// instead, so get the trickslot ...
+					
+							// Get the checksum of the slot ...
+							uint32 TrickSlotChecksum=0;
+							pTrickStruct->GetChecksum(0xa92a2280/*TrickSlot*/,&TrickSlotChecksum);
+					
+							if (TrickSlotChecksum)
+							{
+								// Look up this slot in the trick mappings ...
+								Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings ?"));
+								uint32 TrickChecksum=0;
+								mpTrickMappings->GetChecksum(TrickSlotChecksum,&TrickChecksum);
+					
+								if (TrickChecksum)
+								{
+									// Now, look up the global structure with this name.
+					
+									Script::CStruct *pTrick=Script::GetStructure(TrickChecksum);
+									if (pTrick)
+									{
+										// Passing the flag IsExtra to the script, so that the script can tell
+										// whether it was run as an extra trick or a regular trick.
+										if (RunTrick(pTrick,0x7a16aca0/*IsExtra*/))
+										{
+											TriggeredATrick=true;
+										}	
+									}
+								}
+							}
+						}
+						else
+						{
+							TriggeredATrick=true;
+						}	
+					}
+							
+					// Check the status of mGotExtraTricks again, because it might have got
+					// reset by a call to KillExtraTricks in any script run above.		
+					if (!mGotExtraTricks)
+					{
+						return TriggeredATrick;
+					}	
+				}	
+			}
+		}
+	}	
+	
+	return TriggeredATrick;
+}
+
+// Called every frame.
+void CTrickComponent::TriggerAnyExtraTricks()
+{
+	
+
+	// Check whether any special extra tricks need to get triggered.
+	if (mGotExtraTricks)
+	{
+		if (!mExtraTricksInfiniteDuration && Tmr::ElapsedTime(mExtraTricksStartTime)>mExtraTricksDuration)
+		{
+			// If not infinite duration, and the time is up, then stop checking for extra tricks.
+			mGotExtraTricks=false;
+		}
+		else
+		{
+			// Check any special extra tricks if there are some & we're special.
+			if (mSpecialExtraTricksArrayChecksum)
+			{
+				Mdl::Score *pScore=GetScoreObject();
+				if (pScore->GetSpecialState())
+				{
+					// Set this flag so that any trick scripts that do get executed during the TriggerAnyExtraTrick
+					// will use the yellow text if they execute a Display command. 
+					mUseSpecialTrickText=true;
+					if (!TriggerAnyExtraTrick(mSpecialExtraTricksArrayChecksum,mExcludedSpecialExtraTricks))
+					{
+						// No trick was run, so reset this flag just to be sure that it doesn't cause a non-special
+						// trick triggered later to get displayed in yellow.
+						mUseSpecialTrickText=false;
+					}	
+					
+					// Check the status of mGotExtraTricks again, because it might have got
+					// reset by a call to KillExtraTricks in any script run above.		
+					if (!mGotExtraTricks)
+					{
+						return;
+					}	
+				}	
+				else
+				{
+					mUseSpecialTrickText=false;
+				}	
+			}
+			
+			// Check all the extra-trick arrays
+			for (int i=0; iGetFloat(CRCD(0x79a07f3f,"Duration"),&Duration))
+	{
+		mExtraTricksInfiniteDuration=false;
+		mExtraTricksStartTime=Tmr::ElapsedTime(0);
+		// Convert Duration, which is assumed to be a number of 60ths, to milliseconds.
+		mExtraTricksDuration=(uint32)(Duration*100/6.0f);
+	}	
+
+	mNumExtraTrickArrays=0;
+
+	// If a single Tricks parameter is specified, then use that. 
+	uint32 ExtraTrickArrayChecksum=0;
+	if (pParams->GetChecksum(CRCD(0x1e26fd3e,"Tricks"),&ExtraTrickArrayChecksum))
+	{
+		mNumExtraTrickArrays=1;
+		mpExtraTrickArrays[0]=ExtraTrickArrayChecksum;
+	}
+	else
+	{
+		// Otherwise, assume all the un-named names in the parameter list are the trick arrays required.
+		Script::CComponent *pComp=NULL;
+		while (true)
+		{
+			pComp=pParams->GetNextComponent(pComp);
+			if (!pComp)
+			{
+				break;
+			}
+		
+			if (pComp->mNameChecksum==0 && pComp->mType==ESYMBOLTYPE_NAME)
+			{
+				// Found a name, so add it to the array.
+				Dbg_MsgAssert(mNumExtraTrickArraysGetScriptInfo()));
+				Dbg_MsgAssert(pComp->mChecksum,("Zero checksum ???"));
+				mpExtraTrickArrays[mNumExtraTrickArrays++]=pComp->mChecksum;
+			}	
+		}
+	}
+
+	// Set any special extra tricks required.
+	mSpecialExtraTricksArrayChecksum=0;
+	pParams->GetChecksum(CRCD(0xb394c01c,"Special"),&mSpecialExtraTricksArrayChecksum);
+	
+	// Make sure no tricks are going to be excluded first of all ...
+	for (int i=0; iGetLocalText(CRCD(0xf277291d,"Ignore"),&pExtraTrickToIgnore))
+	{
+		ExcludeExtraTricks(pExtraTrickToIgnore);
+	}	
+	else if (pParams->GetArray(CRCD(0xf277291d,"Ignore"),&pArrayOfTricksToIgnore))
+	{
+		Dbg_MsgAssert(pArrayOfTricksToIgnore,("Eh ? NULL pArrayOfTricksToIgnore ??"));
+		int Size=pArrayOfTricksToIgnore->GetSize();
+		for (int i=0; iGetLocalString(i));
+		}
+	}		
+}
+	
+void CTrickComponent::KillExtraTricks()
+{
+	
+	mGotExtraTricks=false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+//
+// 									Extra grind trick stuff
+//
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Called by the ClearExtraGrindTrick script command.
+void CTrickComponent::ClearExtraGrindTrick()
+{
+	mGotExtraGrindTrick=false;
+	mNumExtraGrindTrickArrays=0;
+	
+	// Just to be sure, zero the lot of 'em.
+	for (int i=0; iGetStructure(mExtraGrindTrick.Index);
+			Dbg_MsgAssert(pStruct,("NULL pStruct ???"));
+			
+			Script::CArray *pScriptArray=NULL;
+			pStruct->GetArray(CRCD(0x22e168c1,"Scripts"),&pScriptArray);
+			
+			// The scripts array can alternatively be specified using a template array and a prefix.
+			Script::CArray *p_template_array=NULL;
+			pStruct->GetArray(CRCD(0x689fe07c,"Template"),&p_template_array);
+			const char *p_prefix=NULL;
+			pStruct->GetString(CRCD(0x6c4e7971,"Prefix"),&p_prefix);
+			
+			if (!(pScriptArray || p_template_array))
+			{
+				// No array parameters found, so look for a TrickSlot
+				uint32 TrickSlotChecksum=0;
+				pStruct->GetChecksum(CRCD(0xa92a2280,"TrickSlot"),&TrickSlotChecksum);
+				if (TrickSlotChecksum)
+				{
+					// Look up this slot in the trick mappings ...
+					Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings ?"));
+					uint32 TrickChecksum=0;
+					mpTrickMappings->GetChecksum(TrickSlotChecksum,&TrickChecksum);
+				
+					if (TrickChecksum)
+					{
+						// Now, look up the global structure with this name.
+						Script::CStruct *pFoo=Script::GetStructure(TrickChecksum);
+						if (pFoo)
+						{
+							pFoo->GetArray(CRCD(0x22e168c1,"Scripts"),&pScriptArray);
+							pFoo->GetArray(CRCD(0x689fe07c,"Template"),&p_template_array);
+							pFoo->GetString(CRCD(0x6c4e7971,"Prefix"),&p_prefix);
+						}
+					}		
+				}
+			}	
+
+			// Calculate the index into the scripts array, which ahs size 16
+			uint Index=0;
+			if (Right) Index|=1;
+			if (Parallel) Index|=2;
+			if (Backwards) Index|=4;
+			if (Regular) Index|=8;
+
+				
+			uint32 script_checksum=0;
+			if (pScriptArray)
+			{
+				script_checksum=pScriptArray->GetNameChecksum(Index);
+			}
+			else
+			{
+				// The grind script arrays all tend to follow the same pattern, with all the
+				// script names having a common prefix, with the suffix following a fixed pattern.
+				// So to save Scott having to add a whole new array for each new grind trick, a
+				// template array can be specified instead.
+				// This template array is an array of 16 suffix strings. The full script name can 
+				// then be calculated given the prefix.
+				Dbg_MsgAssert(p_template_array,("No template array found for entry %d of array '%s'",mExtraGrindTrick.Index,Script::FindChecksumName(mExtraGrindTrick.ArrayChecksum)));
+				Dbg_MsgAssert(p_prefix,("No script prefix specified for entry %d of array '%s'",mExtraGrindTrick.Index,Script::FindChecksumName(mExtraGrindTrick.ArrayChecksum)));
+				
+				// Look up the suffix to use.
+				const char *p_suffix=p_template_array->GetString(Index);
+				char p_temp[100];
+				Dbg_MsgAssert(strlen(p_prefix)+strlen(p_suffix)<100,("Oops, grind script name '%s%s' too long",p_prefix,p_suffix));
+				// Generate the full script name
+				sprintf(p_temp,"%s%s",p_prefix,p_suffix);
+				// and hence calculate the script name checksum.
+				script_checksum=Script::GenerateCRC(p_temp);
+			}
+				
+			Script::CStruct *pParams=NULL;
+			pStruct->GetStructure(CRCD(0x7031f10c,"Params"),&pParams);
+			
+
+			// Initialise mGrindTweak, which should get set by a SetGrindTweak command
+			// in the script that is about to be run.
+			// TODO: Is there a neater way of doing this?
+			CCompositeObject *p_object=GetObject();
+			if (p_object->GetType()==SKATE_TYPE_SKATER)
+			{
+				mp_skater_core_physics_component->ResetGrindTweak();
+			}
+
+			// Set this flag so that any special trick scripts that do get executed during the mp_script->Update()
+			// will use the yellow text if they execute a Display command. 
+			mUseSpecialTrickText=mExtraGrindTrick.UseSpecialTrickText;
+		
+			mp_skater_flip_and_rotate_component->DoAnyFlipRotateOrBoardRotateAfters(); // <- See huge comment above definition of this function.
+			
+			p_object->SwitchScript(script_checksum,pParams);
+			p_object->GetScript()->Update();
+			
+			// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
+			mp_skater_state_component->SetDoingTrick( true );
+
+			// Increment the trick count, which may set pedestrian exceptions to
+			// make them go "oooOOOOOooooh"
+			IncrementNumTricksInCombo();
+			
+			return true;
+		}
+	}
+	
+	return false;
+}
+
+// Checks a single array of grind tricks.
+void CTrickComponent::MaybeQueueExtraGrindTrick(uint32 ArrayChecksum, bool UseSpecialTrickText)
+{
+	
+	Dbg_MsgAssert(ArrayChecksum,("Zero ArrayChecksum sent to MaybeQueueExtraGrindTrick"));
+
+	// Resolve the checksum into a CArray pointer.
+	// The array is stored by checksum in case it gets reloaded.
+	Script::CArray *pArray=Script::GetArray(ArrayChecksum);
+		
+	// The above would have asserted if the array wasn't found, but check pArray isn't NULL anyway.
+	if (pArray)
+	{
+		// Scan through the array checking each trick.
+		int Size=pArray->GetSize();
+		for (int t=0; tGetStructure(t);
+			Dbg_MsgAssert(pStruct,("NULL pStruct ???"));
+			// pStruct is the structure defining the trick.
+			
+			if (TrickIsDefined(pStruct))
+			{
+				// Get the trigger, which is a structure defining the button combination that triggers the trick.
+				Script::CStruct *p_trigger=get_trigger_structure(pStruct);
+				// An alternate trigger may also be specified. This will also trigger the trick.
+				Script::CStruct *p_alternate_trigger=get_alternate_trigger_structure(pStruct);
+				
+				// p_trigger could be NULL, but OK cos QueryEvents will return false in that case.
+				if (QueryEvents(p_trigger,USED_BY_EXTRA_GRIND_TRICK,~USED_BY_MANUAL_TRICK) ||
+					QueryEvents(p_alternate_trigger,USED_BY_EXTRA_GRIND_TRICK,~USED_BY_MANUAL_TRICK))
+				{
+					// The conditions are met, so add the special grind trick.
+					mExtraGrindTrick.ArrayChecksum=ArrayChecksum;
+					mExtraGrindTrick.Index=t;
+					mExtraGrindTrick.Time=Tmr::GetTime();
+					mExtraGrindTrick.Duration=0xffffffff;
+					mExtraGrindTrick.UseSpecialTrickText=UseSpecialTrickText;
+					pStruct->GetInteger(0x79a07f3f/*Duration*/,(int*)&mExtraGrindTrick.Duration);
+					mGotExtraGrindTrick=true;
+				}
+			}	
+		}
+	}
+}
+	
+// Always called every frame.
+void CTrickComponent::MaybeQueueExtraGrindTrick()
+{
+	
+	
+	// If got a special special grind trick array, and we're special, check it.
+	if (mSpecialExtraGrindTrickArrayChecksum)
+	{
+		Mdl::Score *pScore=GetScoreObject();
+		if (pScore->GetSpecialState())
+		{
+			MaybeQueueExtraGrindTrick(mSpecialExtraGrindTrickArrayChecksum,USE_SPECIAL_TRICK_TEXT);
+		}	
+	}
+	
+	// Note: Should this function carry on checking once mGotExtraGrindTrick becomes true?
+	// Currently it does, so the first trick triggered will be overridden by any further special
+	// grind. Check with Scott.
+	
+	// For each of the trick arrays, check whether any of the tricks listed within are triggered.
+	for (int i=0; imExtraGrindTrick.Duration)
+		{
+			// It's past its sell by date, so kill it.
+			mGotExtraGrindTrick=false;
+		}	
+	}	
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Returns true if the passed trick has the name pIgnoreName.
+bool CTrickComponent::IsExcluded(Script::CStruct *pTrick, const char *pIgnoreName)
+{
+	
+	
+	Dbg_MsgAssert(pTrick,("NULL pTrick"));
+	
+	// See if it has a trick slot.
+	uint32 TrickSlotChecksum=0;
+	if (pTrick->GetChecksum(0xa92a2280/*TrickSlot*/,&TrickSlotChecksum))
+	{
+		// Look up this slot in the trick mappings ...
+		Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings ?"));
+		uint32 TrickChecksum=0;
+		mpTrickMappings->GetChecksum(TrickSlotChecksum,&TrickChecksum);
+							
+		if (TrickChecksum)
+		{
+			// Now, look up the global structure with this name.
+			pTrick=Script::GetStructure(TrickChecksum);
+			if (!pTrick)
+			{
+				return false;
+			}
+		}
+	}
+
+	Script::CStruct *pParams=NULL;
+	Dbg_MsgAssert(pTrick,("Eh ?  NULL pTrick"));
+	pTrick->GetStructure(CRCD(0x7031f10c,"Params"),&pParams);
+	if (pParams)
+	{
+		const char *pName=NULL;
+		pParams->GetLocalText(CRCD(0xa1dc81f9,"Name"),&pName);
+		if (pName)
+		{
+			Dbg_MsgAssert(pIgnoreName,("NULL pIgnoreName"));
+			// Compare pName and pIgnoreName.
+			// If they match, return true so that the trick gets excluded.
+			if (stricmp(pName,pIgnoreName)==0)
+			{
+				return true;
+			}	
+		}
+	}		
+		
+	return false;
+}				
+
+// Returns a bitfield indicating which of the entries in the passed array are excluded because they have the passed Id.
+uint32 CTrickComponent::CalculateIgnoreMask(uint32 ArrayChecksum, const char *pIgnoreName)
+{
+	
+	Dbg_MsgAssert(ArrayChecksum,("Zero ArrayChecksum sent to CalculateIgnoreMask"));
+	Script::CArray *pArray=Script::GetArray(ArrayChecksum);
+	
+	int Size=pArray->GetSize();
+	// Only 32 bits available ...
+	Dbg_MsgAssert(Size<=32,("Extra-trick array '%s' has more than 32 entries",Script::FindChecksumName(ArrayChecksum)));
+			
+	uint32 Mask=0;	
+	for (int i=0; iGetStructure(i);
+		if (IsExcluded(pTrick,pIgnoreName))
+		{
+			Mask |= (1<GetInputMask();
+	
+	uint32 Direction = CSkaterPad::sGetDirection(
+		input_mask & Inp::Data::mA_UP,
+		input_mask & Inp::Data::mA_DOWN,
+		input_mask & Inp::Data::mA_LEFT,
+		input_mask & Inp::Data::mA_RIGHT
+	);
+	
+	ButtonRecord(PAD_U, Direction == PAD_U);
+	ButtonRecord(PAD_D, Direction == PAD_D);
+	ButtonRecord(PAD_L, Direction== PAD_L);
+	ButtonRecord(PAD_R, Direction == PAD_R);
+	ButtonRecord(PAD_UL, Direction == PAD_UL);
+	ButtonRecord(PAD_UR, Direction == PAD_UR);
+	ButtonRecord(PAD_DL, Direction == PAD_DL);
+	ButtonRecord(PAD_DR, Direction == PAD_DR);
+	
+	ButtonRecord(PAD_CIRCLE, input_mask & Inp::Data::mA_CIRCLE);
+	ButtonRecord(PAD_SQUARE, input_mask & Inp::Data::mA_SQUARE);
+	ButtonRecord(PAD_TRIANGLE, input_mask & Inp::Data::mA_TRIANGLE);
+	ButtonRecord(PAD_X, input_mask & Inp::Data::mA_X);
+	ButtonRecord(PAD_L1, input_mask & Inp::Data::mA_L1);
+	ButtonRecord(PAD_L2, input_mask & Inp::Data::mA_L2);
+	ButtonRecord(PAD_L3, input_mask & Inp::Data::mA_L3);
+	ButtonRecord(PAD_R1, input_mask & Inp::Data::mA_R1);
+	ButtonRecord(PAD_R2, input_mask & Inp::Data::mA_R2);
+	ButtonRecord(PAD_R3, input_mask & Inp::Data::mA_R3);
+	ButtonRecord(PAD_BLACK, input_mask & Inp::Data::mA_BLACK);
+	ButtonRecord(PAD_WHITE, input_mask & Inp::Data::mA_WHITE);
+	ButtonRecord(PAD_Z, input_mask & Inp::Data::mA_Z);
+}
+
+void CTrickComponent::DumpEventBuffer()
+{
+	printf("Event buffer:\n");
+	int Index=mLastEvent;
+	int n=mNumEvents;
+	if (n>10) n=10;
+	for (int i=0; iSetAnimSpeed(GetBashFactor(), true);
+}
+
+// Calculates the amount by which the skater's animation needs to be sped up
+// due to the player bashing buttons.
+// The animation speed needs to be multiplied by the value that this returns.
+float CTrickComponent::GetBashFactor()
+{
+	uint32 Duration=GetPhysicsInt(CRCD(0x6c83fdbf,"BashPeriod"));
+	
+	// Scan through all the events in the last BashPeriod milliseconds,
+	// counting all the button press and release events.
+	int Bashes=0;
+	int Index=mLastEvent;
+	for (int i=0; i=Duration)
+		{
+			break;
+		}
+	
+		if (mpButtonEvents[Index].EventType==EVENT_BUTTON_PRESSED ||
+			mpButtonEvents[Index].EventType==EVENT_BUTTON_RELEASED)
+		{
+			// Flag the event as used, so that it won't register once the bail has finished.
+			mpButtonEvents[Index].Used=0xffffffff;
+			++Bashes;
+		}	
+		
+		--Index;
+		if (Index<0)
+		{
+			Index+=MAX_EVENTS;
+		}	
+	}
+	
+	// Increase the anim speed based on the number of bashes.
+	
+	float bash_factor=Bashes*GetPhysicsFloat(CRCD(0xced14273, "BashSpeedupFactor"));
+	float max=GetPhysicsFloat(CRCD(0xd24abfa3, "BashMaxPercentSpeedup"))/100.0f;
+	if (bash_factor > max)
+	{
+		bash_factor=max;
+	}	
+	
+	return 1.0f+bash_factor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently isfrequently the contents of a node
+// but you can pass in anything you like.	
+void CTrickComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CTrickComponent added to non-skater composite object"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickComponent::Update()
+{
+	
+	// nonlocal clients have a SkaterCorePhysicsComponent only to provide uber_frig functionality
+	if (!GetSkater()->IsLocalClient())
+	{
+		Suspend(true);
+		return;
+	}
+	
+	RecordButtons();
+	
+	mp_skater_balance_trick_component->ExcludeBalanceButtons(mNumButtonsToIgnore, mpButtonsToIgnore);
+	
+	TriggerAnyExtraTricks();
+	AddTricksToQueue();
+	MaybeQueueManualTrick();
+	MaybeExpireManualTrick();
+	MaybeQueueExtraGrindTrick();
+	MaybeExpireExtraGrindTrick();
+	HandleBashing();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CTrickComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | DumpEventBuffer | dumps the last ten button events
+		case 0x72c926ca: // DumpEventBuffer
+			DumpEventBuffer();
+			break;
+
+        // @script | Held | true if the specified button is being held
+        // @uparmopt name | Single button name
+		// @parmopt array | Buttons | | Optional array of button names. If specified, then if
+		// any of those buttons are held it will return true.
+		case 0xeda2772e: // Held
+		{
+			Script::CArray *p_array=NULL;
+			if (pParams->GetArray(CRCD(0xbca37a49,"Buttons"),&p_array))
+			{
+				// If an array of buttons is specified, check each of them.
+				int n=p_array->GetSize();
+				for (int i=0; iGetChecksum(i)))
+					{
+						return CBaseComponent::MF_TRUE;
+					}
+				}
+				return CBaseComponent::MF_FALSE;		
+			}
+			else
+			{
+			uint32 ButtonChecksum=0;
+			pParams->GetChecksum(NONAME,&ButtonChecksum);
+			if (!ButtonChecksum)
+			{
+				return CBaseComponent::MF_FALSE;
+			}	
+			return GetButtonState(ButtonChecksum) ? CBaseComponent::MF_TRUE:CBaseComponent::MF_FALSE;
+			}	
+			break;
+		}
+			
+        // @script | Released | true if the specified button is released
+        // @uparm X | button name
+		case 0x4ba9ee9: // Released	
+		{
+			uint32 ButtonChecksum=0;
+			pParams->GetChecksum(NONAME,&ButtonChecksum);
+			if (!ButtonChecksum)
+			{
+				return CBaseComponent::MF_FALSE;
+			}	
+			return GetButtonState(ButtonChecksum) ? CBaseComponent::MF_FALSE:CBaseComponent::MF_TRUE;
+			break;
+		}
+
+/*
+        // @script | DoNextTrick | runs the next trick in the queue
+		// @parmopt name | ScriptToRunFirst | | Script that will be run first before any trick script.
+		// @parmopt structure | Params | | Parameters to pass to ScriptToRunFirst
+		
+		case 0x09d58f25: // DoNextTrick
+		{
+			uint32 scriptToRunFirst=0;
+			Script::CStruct *pScriptToRunFirstParams=NULL;
+			pParams->GetChecksum(CRCD(0x148fee96,"ScriptToRunFirst"),&scriptToRunFirst);
+			pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptToRunFirstParams);
+			TriggerNextQueuedTrick(scriptToRunFirst, pScriptToRunFirstParams);
+			break;
+		}
+*/
+		
+         // @script | ClearTricksFrom | 
+		case 0xcb30572d: // ClearTricksFrom
+		{
+			// Run through all the parameters.
+			Script::CComponent *pComp=NULL;
+			while (true)
+			{
+				pComp=pParams->GetNextComponent(pComp);
+				if (!pComp)
+				{
+					break;
+				}
+			
+				if (pComp->mNameChecksum==0 && pComp->mType==ESYMBOLTYPE_NAME)
+				{
+					// It's an unnamed name, so remove the tricks that came from the array with that name.
+					ClearTricksFrom(pComp->mChecksum);
+				}	
+			}
+			break;
+		}
+
+        // @script | SetQueueTricks | sets the tricks queue
+        // @uparmopt name | trick array.  if no array specified
+        // a default air tricks array will be used
+        // @parmopt name | Special | | special tricks array
+		case 0xd8c79bb0: // SetQueueTricks
+		{
+			mNumQueueTricksArrays=0;
+
+			Script::CComponent *pComp=NULL;
+			while (true)
+			{
+				pComp=pParams->GetNextComponent(pComp);
+				if (!pComp)
+				{
+					break;
+				}
+			
+				if (pComp->mNameChecksum==0 && pComp->mType==ESYMBOLTYPE_NAME)
+				{
+					// Found a name, so add it to the array.
+					Dbg_MsgAssert(mNumQueueTricksArraysGetScriptInfo()));
+					Dbg_MsgAssert(pComp->mChecksum,("Zero checksum ???"));
+					mpQueueTricksArrays[mNumQueueTricksArrays++]=pComp->mChecksum;
+				}	
+			}
+			
+			if (mNumQueueTricksArrays==1)
+			{
+				// Check if the first array exists. If not, use DefaultAirTricks instead. This array is required to exist, otherwise
+				// AddTricksToQueue will assert.
+				
+				Script::CArray *pArray=Script::GetArray(mpQueueTricksArrays[0]);
+				if (!pArray)
+				{
+					mpQueueTricksArrays[0]=CRCD(0x9e22d268,"DefaultAirTricks");
+				}	
+			}	
+			
+			// Set any special tricks array required.
+			mSpecialTricksArrayChecksum=0;
+			pParams->GetChecksum(CRCD(0xb394c01c,"Special"),&mSpecialTricksArrayChecksum);
+			break;
+		}
+
+        // @script | UseGrindEvents | 
+		case 0xd4099d35: // UseGrindEvents
+			mDoNotIgnoreMask=USED_BY_EXTRA_GRIND_TRICK;		
+			break;
+			
+        // @script | AddTricksToQueue | This will check the button events and add any triggered tricks 
+		// to the trick queue. The c-code actually does this every frame anyway, but this function
+		// allows it to be done immediately when necessary.
+		case 0xde98de7a: // AddTricksToQueue
+			AddTricksToQueue();
+			break;
+			
+        // @script | ClearTrickQueue | clears the trick queue
+		case 0x54c33b20: // ClearTrickQueue
+			ClearTrickQueue();		
+			break;
+			
+        // @script | DoNextManualTrick | does next trick while in a manual
+		case 0x788f9a4e: //	DoNextManualTrick
+			TriggerAnyManualTrick(pParams);
+			break;
+			
+        // @script | SetManualTricks | 
+        // @uparm name | trick array
+		case 0x1fcf080a: // SetManualTricks
+		{
+			mNumManualTrickArrays=0;
+
+			Script::CComponent *pComp=NULL;
+			while (true)
+			{
+				pComp=pParams->GetNextComponent(pComp);
+				if (!pComp)
+				{
+					break;
+				}
+			
+				if (pComp->mNameChecksum==0 && pComp->mType==ESYMBOLTYPE_NAME)
+				{
+					// Found a name, so add it to the array.
+					Dbg_MsgAssert(mNumManualTrickArraysGetScriptInfo()));
+					Dbg_MsgAssert(pComp->mChecksum,("Zero checksum ???"));
+					mpManualTrickArrays[mNumManualTrickArrays++]=pComp->mChecksum;
+				}	
+			}
+			
+			// Set any special manual tricks array required.
+			mSpecialManualTricksArrayChecksum=0;
+			pParams->GetChecksum(CRCD(0xb394c01c,"Special"),&mSpecialManualTricksArrayChecksum);
+			break;
+		}
+				
+        // @script | ClearManualTrick | 
+		case 0x8e3d3bec: // ClearManualTrick
+			ClearManualTrick();
+			break;
+			
+		/////////////////////////////////////////////////////////////////////////////////////////////
+		//
+		// Extra-Grind trick stuff, which has a very similar interface to the manual stuff above,
+		// except that there is no script command corresponding to DoNextManualTrick because the
+		// C-code will automatically run any queued extra grind trick when it snaps the player
+		// to a rail in CSkater::MaybeStickToRail
+		//
+		/////////////////////////////////////////////////////////////////////////////////////////////
+
+        // @script | SetExtraGrindTricks | 
+        // @uparm name | trick array
+		case 0x6ba35ab4: // SetExtraGrindTricks
+		{
+			mNumExtraGrindTrickArrays=0;
+
+			Script::CComponent *pComp=NULL;
+			while (true)
+			{
+				pComp=pParams->GetNextComponent(pComp);
+				if (!pComp)
+				{
+					break;
+				}
+			
+				if (pComp->mNameChecksum==0 && pComp->mType==ESYMBOLTYPE_NAME)
+				{
+					// Found a name, so add it to the array.
+					Dbg_MsgAssert(mNumExtraGrindTrickArraysGetScriptInfo()));
+					Dbg_MsgAssert(pComp->mChecksum,("Zero checksum ???"));
+					mpExtraGrindTrickArrays[mNumExtraGrindTrickArrays++]=pComp->mChecksum;
+				}	
+			}
+			
+			mSpecialExtraGrindTrickArrayChecksum=0;
+			pParams->GetChecksum(CRCD(0xb394c01c,"Special"),&mSpecialExtraGrindTrickArrayChecksum);
+			break;
+		}
+				
+        // @script | ClearExtraGrindTrick | 
+		case 0xab6b8dd9: // ClearExtraGrindTrick
+			ClearExtraGrindTrick();
+			break;
+
+        // @script | ClearEventBuffer |
+        // @parmopt array | Buttons | | buttons array
+        // @parmopt int | OlderThan | 0 | used in conjuction with optional buttons array
+		case 0x86928082: // ClearEventBuffer	
+		{
+			Script::CArray *pButtonArray=NULL;
+			if (pParams->GetArray(CRCD(0xbca37a49,"Buttons"),&pButtonArray))
+			{
+				int OlderThan=0;
+				pParams->GetInteger(CRCD(0xcdcfee8a,"OlderThan"),&OlderThan);
+				int Size=pButtonArray->GetSize();
+				for (int i=0; iGetNameChecksum(i),OlderThan);
+				}
+			}
+			else
+			{
+				ClearEventBuffer();
+			}	
+			break;
+		}
+
+        // @script | RemoveXEvents | Run through all the recorded button
+        // events, and remove any involving button X.
+		// This is used as an easy way of fixing the boneless bug.
+	    // This function is called at the start of the land script, 
+        // which prevents any previously
+		// queued bonelesses from being detected.
+		case 0x70e934d: // RemoveXEvents
+		{
+			for (int i=0; iGetInteger(CRCD(0x79a07f3f,"Duration"),&duration);
+			
+			uint32 used_by=0;
+			pParams->GetChecksum(CRCD(0x9694fc41,"UsedBy"),&used_by);
+			
+			uint32 mask=0;
+			switch (used_by)
+			{
+				case 0xb58efc2b: // Regular
+					mask=USED_BY_REGULAR_TRICK;
+					break;
+				case 0xb2c0f29a: // Extra
+					mask=USED_BY_EXTRA_TRICK;
+					break;
+				case 0xef24413b: // Manual
+					mask=USED_BY_MANUAL_TRICK;
+					break;
+				case 0xec066b6a: // ExtraGrind
+					mask=USED_BY_EXTRA_GRIND_TRICK;
+					break;
+				default:
+					break;
+			}
+				
+			int index=mLastEvent;
+			for (int i=0; i= (uint32)duration)
+				{
+					break;
+				}
+		
+				if (mpButtonEvents[index].Used & mask)
+				{
+					mpButtonEvents[index].Used&=~mask;
+				}	
+				
+				--index;
+				if (index<0)
+				{
+					index+=MAX_EVENTS;
+				}	
+			}
+			
+			break;
+		}
+
+        // @script | SetExtraTricks | 
+        // @parmopt float | Duration | | 
+        // @parmopt name | Tricks | | trick array
+        // @parmopt name | Special | | special tricks array
+        // @parmopt local string | Ignore | | trick to ignore
+        // @parmopt array | Ignore | | array of tricks to ignore
+		case 0x7627dc71: // SetExtraTricks
+			SetExtraTricks(pParams,pScript);
+			break;
+			
+        // @script | KillExtraTricks | 
+		case 0xd5813251: // KillExtraTricks
+			KillExtraTricks();
+			break;
+
+        // @script | SetSlotTrick | 
+        // @parm name | Slot | slot name
+        // @parm name | Trick | 
+		case 0xb42a1230: // SetSlotTrick
+		{
+			uint32 Slot=0;
+			pParams->GetChecksum(CRCD(0x53f1df98,"Slot"),&Slot);
+			Dbg_MsgAssert(Slot,("\n%s\nSetSlotTrick requires a slot name",pScript->GetScriptInfo()));
+			uint32 Trick=0;
+			pParams->GetChecksum(CRCD(0x270f56e1,"Trick"),&Trick);
+			
+			Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings"));
+			// This will just replace the slot if it exists already.
+			mpTrickMappings->AddComponent(Slot,(uint8)ESYMBOLTYPE_NAME,Trick);
+			break;
+		}
+
+        // @script | ChangeProTricks | 
+		case 0xabff7889: // ChangeProTricks 
+		{
+			// Have to use GetNextComponent rather than GetChecksum, because GetChecksum won't work if
+			// the checksum is unnamed, because by convention an unnamed checksum that resolves to a structure
+			// effectively makes that structure part of the params, & the name of the structure is just considered
+			// an intermediate thing which is not part of the params.
+			Script::CComponent *pComp=pParams->GetNextComponent(NULL);
+			if (pComp && pComp->mNameChecksum==0 && pComp->mType==ESYMBOLTYPE_NAME)
+			{
+				Script::CStruct *pNewTrickMappings=Script::GetStructure(pComp->mChecksum);
+				if (pNewTrickMappings)
+				{
+					// Found the new structure, so load it in to mpTrickMappings.
+					Dbg_MsgAssert(mpTrickMappings,("NULL mpTrickMappings"));
+					mpTrickMappings->Clear();
+					mpTrickMappings->AppendStructure(pNewTrickMappings);
+				}
+				else
+				{
+					#ifdef __NOPT_ASSERT__
+					printf("\n%s\nWarning: Protrick mapping '%s' not found\n",pScript->GetScriptInfo(),Script::FindChecksumName(pComp->mChecksum));
+					#endif
+				}
+			}
+			else
+			{
+				#ifdef __NOPT_ASSERT__
+				printf("\n%s\nWarning: ChangeProTricks command requires a name.\n",pScript->GetScriptInfo());
+				#endif
+			}			
+			break;
+		}
+					
+        // @script | BashOn | turns bash on (for speeding up bails?)
+		case 0xd872002d: // BashOn
+			mBashingEnabled=true;
+			// Clear the event buffer so that button presses done before the bail will not
+			// speed it up.
+			ClearEventBuffer();
+			break;
+			
+        // @script | BashOff | turns bash off
+		case 0x290f6010: // BashOff
+			mBashingEnabled=false;
+			break;
+		
+        // @script | SetTrickName | 
+        // @uparmopt 'local string' | trick name
+        // @uparmopt "string" | trick name
+		case 0x4b2ee2bc: // SetTrickName
+		{
+			const char *pName=NULL;
+			pParams->GetString(NO_NAME,&pName);
+			// Make it accept regular strings too, because trick names probably won't get translated.
+			if (pName)
+			{
+				Dbg_MsgAssert(strlen(pName)GetScriptInfo(),MAX_TRICK_NAME_CHARS));
+				strcpy(mpTrickName, pName);
+			}
+			else
+			{
+				mpTrickName[0]=0;
+			}
+			break;
+		}
+
+        // @script | SetTrickScore | sets trick score flag
+        // @uparm 1 | trick score
+		case 0xcb3a8fd2: // SetTrickScore
+			pParams->GetInteger(NO_NAME,&mTrickScore);	  
+			break;
+                          
+        // @script | GetSpin | Gets the current spin value, and adds it as an integer parameter
+		// called Spin. It will always be positive and a multiple of 180. It takes into account
+		// the spin slop.
+		case 0x4ba7719f: // GetSpin
+		{
+			int spin = static_cast< int >(Mth::Abs(mTallyAngles) + GetPhysicsFloat(CRCD(0x50c5cc2f, "spin_count_slop")));
+			pScript->GetParams()->AddFloat(CRCD(0xedf5db70, "Spin"), (spin / 180) * 180.0f);
+			break;
+		}
+		
+        // @script | ResetSpin | 
+		case 0xe50d471e: // ResetSpin
+			mTallyAngles = 0.0f;
+			break;
+		
+        // @script | Display | display trick
+        // @flag Deferred | If deferred (currently only used for the ollie)
+        // then store it away, because we may not want to add it to the
+        // combo after all. Eg, a 180 ollie on its own is counted,
+		// but if you do a 180 kickflip, you'll get the kickflip but not 
+        // the ollie as well. 
+        // @flag BlockSpin |
+		// @parmopt int | AddSpin | 0 | If this is specified and the current trick is the
+		// same as the last trick, then the spin will be added to the last trick rather than
+		// adding it as a new trick.
+		case 0xf32e8d5c: // Display				  
+		{
+			if (mpTrickName)
+			{
+				Mdl::Score *pScore=GetScoreObject();
+				int spin=0;
+				
+				if (pParams->ContainsFlag(CRCD(0x5c9b1765,"Deferred")))
+				{
+					strcpy(mpDeferredTrickName,mpTrickName);
+					mDeferredTrickScore=mTrickScore;
+					if (mp_skater_core_physics_component->IsSwitched())
+					{
+						mDeferredTrickFlags|=Mdl::Score::vSWITCH;
+					}
+				}
+				// If an AddSpin value is specified and the current trick is the same as
+				// the last one in the score, then instead of adding the trick again just
+				// give the last one some more spin. Used by the truckstand2 spin.
+				// (See the ManualLink script in groundtricks.q)
+				else if (pParams->GetInteger(CRCD(0x83cb0082,"AddSpin"),&spin) && 
+						 pScore->GetLastTrickId()==Script::GenerateCRC(mpTrickName))
+				{
+					mTallyAngles+=spin;
+					if (GetSkater()->IsLocalClient())
+					{
+						pScore->UpdateSpin(mTallyAngles);
+					}	
+				}
+				else
+				{
+					Mdl::Score::Flags Flags=0;
+					if (pParams->ContainsFlag(CRCD(0xc2150bb0,"BlockSpin")))
+					{
+						Flags|=Mdl::Score::vBLOCKING;
+					}
+					if (pParams->ContainsFlag(CRCD(0x3c24ac46,"NoDegrade")))
+					{
+						Flags|=Mdl::Score::vNODEGRADE;
+					}	
+					if (mp_skater_core_physics_component->IsSwitched())
+					{
+						Flags|=Mdl::Score::vSWITCH;
+					}
+					if (mUseSpecialTrickText)
+					{
+						Flags|=Mdl::Score::vSPECIAL;
+					}
+					if ( pParams->ContainsFlag( CRCD(0x61a1bc57,"cat") ) )
+					{
+						Flags |= Mdl::Score::vCAT;
+					}
+					
+					// Need to also include nollie check later.
+						
+					if( GetSkater()->IsLocalClient())
+					{
+						// if on a rail, then might need to degrade the rail score
+						if (mp_skater_core_physics_component->GetState()==RAIL && mTrickScore)
+						{
+							printf ("RAIL score %d * %2.3f = %d\n", mTrickScore,pScore->GetRobotRailMult(),(int) (pScore->GetRobotRailMult()*mTrickScore));
+							mTrickScore = (int) (mTrickScore * pScore->GetRobotRailMult());
+						}
+					
+						// add a trick to the series
+						pScore->Trigger(mpTrickName, mTrickScore, Flags);
+						
+						if (mpTrickName[0])
+						{
+							// tell any observers to do the chicken dance:
+							GetObject()->BroadcastEvent(CRCD(0x11d8bc9e, "SkaterTrickDisplayed"));
+							
+							if (mp_skater_core_physics_component->GetFlag(VERT_AIR) || mp_skater_core_physics_component->GetTrueLandedFromVert())
+							{
+								// If vert, only count it if the spin is at least 360
+								if (Mth::Abs(mTallyAngles)>=360.0f-GetPhysicsFloat(CRCD(0x50c5cc2f,"spin_count_slop")) + 0.1f)
+								{
+									pScore->SetSpin(mTallyAngles);
+								}
+							}
+							else
+							{
+								pScore->SetSpin(mTallyAngles);
+							}
+						}
+						else
+						{
+							// K: If the trick was a 'Null' trick, then do not add the spin.
+							// This is to fix TT6057
+							// A null trick is a trick whose name is set using SetTrickName ""
+							// and is often used to do a BlockSpin without displaying a trick name.
+						}
+					}
+					
+					if (pParams->ContainsFlag(CRCD(0xc2150bb0,"BlockSpin")))
+					{
+						mTallyAngles=0.0f;
+					}
+						
+					// Clear the deferred trick, so that you won't get a 180 kickflip and a 180 ollie.
+					//dodgy_test(); printf("Clearing deferred trick\n");
+					mpDeferredTrickName[0]=0;
+				}	
+            }
+			
+			// Only clear the mUseSpecialTrickText flag if it is not a 'null' trick, which is
+			// a trick whose name is just '' used to insert spin blocks.
+			// Want to preserve the mUseSpecialTrickText for the next proper trick.
+			if (mpTrickName[0])
+			{
+				mUseSpecialTrickText=false;
+			}	
+			break;
+		}	
+		
+        // @script | ClearPanel_Landed | end of trick combo...scoring and such
+		case 0x11ca5c42: // ClearPanel_Landed
+		{
+			Mdl::Score *pScore=GetScoreObject();
+
+			if(GetSkater()->IsLocalClient())
+			{
+				// If there is a deferred trick stored up, then decide whether to add it
+				// to the combo depending on how much spin there is.
+				if (mpDeferredTrickName[0])
+				{
+					//dodgy_test(); printf("Got a deferred trick, mTallyAngles=%f\n",mTallyAngles);
+					// Note: Using the new 'mp_skater_core_physics_component->m_true_landed_from_vert' rather than 'mp_skater_core_physics_component->mLandedFromVert', because
+					// mp_skater_core_physics_component->mLandedFromVert can be cleared by a script command, and if landing from vert it
+					// will be cleared at this point, due to some script logic for detecting reverts.
+					if (mp_skater_core_physics_component->GetTrueLandedFromVert())
+					{
+						// If vert, only count it if the spin is at least 360
+						if (Mth::Abs(mTallyAngles)>= 360.0f - GetPhysicsFloat(CRCD(0x50c5cc2f,"spin_count_slop")) + 0.1f)	// (Mick) adkisted for slop
+						{
+							// add the deferred trick to the series
+							pScore->Trigger(mpDeferredTrickName, mDeferredTrickScore, mDeferredTrickFlags);
+							pScore->SetSpin(mTallyAngles);
+						}	
+					}
+					else
+					{
+						// Only count it if the spin is at least 180, cos just ollieing is easy.
+						if (Mth::Abs(mTallyAngles)>=180.0f - GetPhysicsFloat(CRCD(0x50c5cc2f,"spin_count_slop")) + 0.1f)  // (Mick) adkisted for slop
+						{
+							// add the deferred trick to the series
+							pScore->Trigger(mpDeferredTrickName, mDeferredTrickScore, mDeferredTrickFlags);
+							pScore->SetSpin(mTallyAngles);
+						}	
+					}
+						
+					// Clear the deferred trick.
+					mpDeferredTrickName[0]=0;
+				}
+			
+                // check for special trick if we're in a special trick goal
+                Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+                Dbg_MsgAssert( pGoalManager, ( "Unable to get goal manager\n" ) );
+                pGoalManager->CheckTrickText();
+				
+				if (pScore->GetScorePotValue() != 0)
+				{
+					Script::CStruct* p_params = new Script::CStruct;
+					p_params->AddChecksum(CRCD(0x5b24faaa, "SkaterId"), GetObject()->GetID());
+					GetObject()->BroadcastEvent(CRCD(0x4b3ce1fe, "SkaterExitCombo"), p_params);
+					delete p_params;
+				}
+
+				///////////////////////////////////////////////////////////////////////
+				// K: Warning! I moved AwardPendingGaps() to before Land(), so that AwardPendingGaps()
+				// can query the score to see if the required trick was got in the case of the
+				// gap being part of a created goal.
+				// Possible alternative solution in case this causes problems: Modify the c-code of
+				// StartGap so that it detects if the gap is part of a created goal, and if so sets the
+				// tricktext according to the trick required by the goal. 
+				GetSkaterGapComponentFromObject(GetObject())->AwardPendingGaps();
+                
+				pScore->Land();
+				mTallyAngles=0.0f;		// Mick: Cleared, so a manual will not get it
+				///////////////////////////////////////////////////////////////////////
+				
+				
+                mp_stats_manager_component->Land();
+			}
+			
+			// tell any observers to cheer if they want:
+			GetObject()->BroadcastEvent( CRCD(0xc98ba111,"skaterLanded"));
+
+			// allows us to end the run if we're in horse mode
+			if ( m_first_trick_started )
+				m_first_trick_completed = true;
+
+			mNumTricksInCombo=0;
+			
+			mp_skater_balance_trick_component->UpdateRecord();
+			mp_skater_balance_trick_component->Reset();
+			
+			// Clear the special friction index (used by Reverts)
+			mp_skater_core_physics_component->ResetSpecialFrictionIndex();
+
+			break;
+		}
+		  		
+        // @script | ClearPanel_Bailed | trick combo ended in bail...
+		case 0xb8045433: // ClearPanel_Bailed
+		{
+			if( GetSkater()->IsLocalClient())
+			{
+				if (GetScoreObject()->GetScorePotValue() != 0)
+				{
+					Script::CStruct* p_params = new Script::CStruct;
+					p_params->AddChecksum(CRCD(0x5b24faaa, "SkaterId"), GetObject()->GetID());
+					GetObject()->BroadcastEvent(CRCD(0x4b3ce1fe, "SkaterExitCombo"), p_params);
+					delete p_params;
+				}
+				
+				GetScoreObject()->Bail();
+			}
+
+			// tell any observers to balk if they want:
+			GetObject()->BroadcastEvent( CRCD(0x6045a960,"skaterBailed"));
+		   
+ 
+			m_first_trick_started = true;
+			m_first_trick_completed = true;
+
+			// clear out the graffiti tricks
+			SetGraffitiTrickStarted( false );
+			
+			mNumTricksInCombo=0;
+			
+			mp_skater_balance_trick_component->Reset();
+			
+            mp_skater_core_physics_component->StopSkitch();
+			
+			// Mick: Cleared, so a manual will not get it
+			mTallyAngles=0.0f;
+			
+			// Clear the special friction index (used by Reverts)
+			mp_skater_core_physics_component->ResetSpecialFrictionIndex();
+			m_pending_tricks.FlushTricks();
+			
+			GetSkaterGapComponentFromObject(GetObject())->ClearPendingGaps();
+
+            mp_stats_manager_component->Bail();
+
+			break;
+		}		
+
+        // @script | TrickOffObject | 
+        // @uparm name | object name
+		case 0x36b2be57: // TrickOffObject
+		{
+			uint32 clusterName;
+			pParams->GetChecksum( NO_NAME, &clusterName, Script::ASSERT );
+			TrickOffObject( clusterName );
+			break;
+		}
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CTrickComponent::GetDebugInfo"));
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	p_info->AddStructure("mpTrickMappings",mpTrickMappings);
+ 
+	Script::CArray *p_events_array=new Script::CArray;
+	if (mNumEvents)
+	{
+		p_events_array->SetSizeAndType(mNumEvents,ESYMBOLTYPE_STRUCTURE);
+		
+		int index=mLastEvent;
+		for (int i=0; iAddChecksum(CRCD(0xc5f953c2,"Button"),mpButtonEvents[index].ButtonNameChecksum);
+			
+			uint32 event_type=0;
+			switch (mpButtonEvents[index].EventType)
+			{
+				case EVENT_NONE:  event_type=CRCD(0x806fff30,"None"); break;
+				case EVENT_BUTTON_PRESSED:  event_type=CRCD(0xe4ab4785,"Pressed"); break;
+				case EVENT_BUTTON_RELEASED:  event_type=CRCD(0x4ba9ee9,"Released"); break;
+				default: break;
+			}
+			p_event->AddChecksum(NONAME,event_type);
+
+			p_event->AddInteger(CRCD(0x906b67ba,"Time"),mpButtonEvents[index].Time);
+			p_event->AddInteger(CRCD(0x86b89ce7,"Used"),mpButtonEvents[index].Used);
+				
+			p_events_array->SetStructure(i,p_event);
+				
+			--index;
+			if (index<0)
+			{
+				index+=MAX_EVENTS;
+			}
+		}		
+	}
+    p_info->AddArrayPointer(CRCD(0xac78a8b5,"Events"),p_events_array);
+
+	s_add_array_of_names(p_info,mNumQueueTricksArrays,mpQueueTricksArrays,"QueueTrickArrays");
+	
+	p_info->AddInteger(CRCD(0x50cab1f1,"GotManualTrick"),mGotManualTrick);
+	s_add_array_of_names(p_info,mNumManualTrickArrays,mpManualTrickArrays,"ManualTrickArrays");
+	p_info->AddChecksum(CRCD(0x9c93be77,"SpecialManualTricksArray"),mSpecialManualTricksArrayChecksum);
+	
+	p_info->AddInteger(CRCD(0xdf2c1464,"GotExtraGrindTrick"),mGotExtraGrindTrick);
+	s_add_array_of_names(p_info,mNumExtraGrindTrickArrays,mpExtraGrindTrickArrays,"ExtraGrindTrickArrays");
+	p_info->AddChecksum(CRCD(0x4cb1bfe,"SpecialExtraGrindTrickArray"),mSpecialExtraGrindTrickArrayChecksum);
+
+	p_info->AddInteger(CRCD(0x33660920,"GotExtraTricks"),mGotExtraTricks);
+	p_info->AddInteger(CRCD(0x15d82e35,"ExtraTricksInfiniteDuration"),mExtraTricksInfiniteDuration);
+	p_info->AddInteger(CRCD(0x4c85f690,"ExtraTricksDuration"),mExtraTricksDuration);
+	p_info->AddInteger(CRCD(0x9737fa16,"ExtraTricksStartTime"),mExtraTricksStartTime);
+	s_add_array_of_names(p_info,mNumExtraTrickArrays,mpExtraTrickArrays,"ExtraTrickArrays");
+	p_info->AddChecksum(CRCD(0x42569716,"SpecialExtraTricksArray"),mSpecialExtraTricksArrayChecksum);
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickComponent::Debounce ( int button, float time )
+{
+	mButtonState[button] = false;
+	mButtonDebounceTime[button] = time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Increment the trick count, which may set pedestrian exceptions to
+// make them go "oooOOOOOooooh"
+
+// Note: This count won't be totally accurate, because if an 'extra' trick is triggered it
+// will increment the count too, making the count get incremented more times than it should.
+// But this shouldn't be a big problem, cos it'll just mean the croud will more likely to cheer if
+// you do lots of extra tricks.
+// NOTE: move to trick component
+void CTrickComponent::IncrementNumTricksInCombo()
+{
+	++mNumTricksInCombo;
+	if ( mNumTricksInCombo > 3 )
+	{
+		// tell any observers to start taking notice:
+		GetObject()->BroadcastEvent(CRCD(0x94182a08,"skaterStartingRun"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickComponent::SetGraffitiTrickStarted( bool started )
+{
+	m_graffiti_trick_started = started;
+	if ( !started )
+	{
+		m_pending_tricks.FlushTricks();
+	}
+	else
+	{
+		TrickOffObject( mp_skater_core_physics_component->GetLastNodeChecksum() );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickComponent::TrickOffObject( uint32 node_name )
+{
+	if ( GraffitiTrickStarted() )
+	{
+		m_pending_tricks.TrickOffObject( node_name );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickComponent::ClearMiscellaneous()
+{
+	mNumTricksInCombo = 0;
+	m_first_trick_completed = false;
+	m_first_trick_started = false;
+	SetGraffitiTrickStarted( false );
+	m_pending_tricks.FlushTricks();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CTrickComponent::WritePendingTricks( uint32* p_buffer, uint32 max_size )
+{
+	uint32 size = m_pending_tricks.WriteToBuffer( p_buffer, max_size );
+
+	// resets the graffiti trick list
+	SetGraffitiTrickStarted( false );
+	
+	return size;
+}
+
+
+// Experimenting with a new way of binding script commands
+// Doing it like this is twice as fast (11us vs 22 us) which can add up for
+// things that are called every frame (like the skater physics scripts) 
+// It's a bit cumbersome to do it like this though
+// Perhaps there could be a more general database of script commands
+// with registration info about which component it goes to.
+// and a pointer to the memeber function
+ 
+// @script | DoNextTrick | runs the next trick in the queue
+// @parmopt name | ScriptToRunFirst | | Script that will be run first before any trick script.
+// @parmopt structure | Params | | Parameters to pass to ScriptToRunFirst
+bool ScriptDoNextTrick( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	CCompositeObject *p_object = (CCompositeObject *) (pScript->mpObject.Convert());
+	Dbg_MsgAssert(p_object, ("Can't run DoNextTrick with no object"));
+	CTrickComponent* p_trick = GetTrickComponentFromObject(p_object);
+	Dbg_MsgAssert(p_trick, ("Can't run DoNextTrick with no object"));
+		
+	uint32 scriptToRunFirst=0;
+	Script::CStruct *pScriptToRunFirstParams=NULL;
+	pParams->GetChecksum(CRCD(0x148fee96,"ScriptToRunFirst"),&scriptToRunFirst);
+	pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptToRunFirstParams);
+	
+	Script::CStruct *pExtraTrickParams=NULL;
+	pParams->GetStructure(CRCD(0x31261d2f, "TrickParams"),&pExtraTrickParams);
+	if (pExtraTrickParams)
+	{
+		Script::CStruct *pCopyOfExtraTrickParams=pCopyOfExtraTrickParams = new Script::CStruct(*pExtraTrickParams);
+		p_trick->TriggerNextQueuedTrick(scriptToRunFirst, pScriptToRunFirstParams, pCopyOfExtraTrickParams);
+		delete pCopyOfExtraTrickParams;
+	}
+	else
+	{
+		p_trick->TriggerNextQueuedTrick(scriptToRunFirst, pScriptToRunFirstParams);
+	}
+	return true;
+} 
+ 
+}
diff --git a/Code/Gel/Components/trickcomponent.h b/Code/Gel/Components/trickcomponent.h
new file mode 100644
index 0000000..0954a78
--- /dev/null
+++ b/Code/Gel/Components/trickcomponent.h
@@ -0,0 +1,371 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       TrickComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_TRICKCOMPONENT_H__
+#define __COMPONENTS_TRICKCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+#ifndef __OBJECT_BASECOMPONENT_H__
+#include 
+#endif
+
+#ifndef __GEL_INPMAN_H
+#include 
+#endif
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_TRICK CRCD(0x270f56e1,"Trick")
+#define		GetTrickComponent() ((Obj::CTrickComponent*)GetComponent(CRC_TRICK))
+#define		GetTrickComponentFromObject(pObj) ((Obj::CTrickComponent*)(pObj)->GetComponent(CRC_TRICK))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+    class CComponent;
+}
+              
+namespace Mdl
+{
+	class Score;
+};
+	
+namespace Obj
+{
+
+class CSkaterProfile;
+class CSkaterBalanceTrickComponent;
+class CSkaterFlipAndRotateComponent;
+class CSkaterStateComponent;
+class CSkaterRunTimerComponent;
+
+// An element of the trick queue.
+struct STrick
+{
+	uint32 ArrayChecksum;	// Checksum of the script array, eg AirTricks
+	uint Index;				// Index within the array of the trick.
+	
+	// These next two are only used by manual tricks and special grind tricks.
+	// These have a finite duration. For example, if a manual is queued up early on
+	// in a jump, it may expire before the player lands.
+	uint32 Time;			// The time when the trick was triggered.
+	uint32 Duration;		// How long after Time that the trick will last for.
+	bool UseSpecialTrickText; // Whether to use the yellow text when displaying the trick.
+};
+
+enum EEventType
+{
+	// All the events in the buffer have this type initially.
+	EVENT_NONE,
+	
+	EVENT_BUTTON_PRESSED,
+	EVENT_BUTTON_RELEASED,
+	
+	EVENT_BUTTON_PRESSURE_CHANGE,
+	
+	EVENT_ON_RAIL,
+	EVENT_OFF_RAIL,
+};
+
+#define MAX_EVENTS 100
+struct SButtonEvent
+{
+	uint32 Time;
+	EEventType EventType;
+	uint32 ButtonNameChecksum;
+	int ButtonPressure;
+	bool MaybeUsed;
+	uint32 Used;
+};
+
+#define USED_BY_REGULAR_TRICK (1<<0)
+#define USED_BY_EXTRA_TRICK (1<<1)
+#define USED_BY_MANUAL_TRICK (1<<2)
+#define USED_BY_EXTRA_GRIND_TRICK (1<<3)
+
+#define USE_SPECIAL_TRICK_TEXT true
+
+class CTrickComponent : public CBaseComponent
+{
+	// Used to detect infinite recursion of RunTrick
+	uint32 m_num_runtrick_recursions;
+	
+	Mdl::Score *mp_score;
+	
+	CInputComponent* 				mp_input_component;
+	CSkaterBalanceTrickComponent*	mp_skater_balance_trick_component;
+	CSkaterCorePhysicsComponent*	mp_skater_core_physics_component;
+	CSkaterFlipAndRotateComponent*	mp_skater_flip_and_rotate_component;
+	CSkaterStateComponent*			mp_skater_state_component;
+    CStatsManagerComponent*			mp_stats_manager_component;
+	
+	Script::CStruct*				get_trigger_structure(Script::CStruct *p_struct);
+	Script::CStruct*				get_alternate_trigger_structure(Script::CStruct *p_struct);
+	
+public:
+	CSkater*						GetSkater (   ) { return static_cast< CSkater* >(GetObject()); }
+	
+	void UpdateTrickMappings( Obj::CSkaterProfile* pSkaterProfile );
+
+	Mdl::Score *GetScoreObject();
+	void SetAssociatedScore(Mdl::Score *p_score) {mp_score=p_score;}
+
+	// Gets called from the CTrickComponent constructor, and also from CSkater::ResetEverything()
+	void Clear();
+
+	void RecordButtons();
+	void Debounce ( int button, float time );
+
+	// The Button Event buffer.
+	SButtonEvent mpButtonEvents[MAX_EVENTS];
+	// How many events are in the buffer. When the skater is first created this will be zero,
+	// but will increase until it equals MAX_EVENTS then will stay equal to MAX_EVENTS from then on.
+	uint16 mNumEvents;
+	// Index of the last event in the buffer.
+	uint16 mLastEvent;
+
+	// If a trick gets triggered, the buttons which triggered it will be stored here.
+	// Eg, for a kickflip, button 0 will be Square (checksum of), and button 1 will be Left.
+	// They are stored so that the extra-trick trigger can use one of the parent trick's buttons,
+	// rather than being hard wired. This is needed in case the player re-maps the parent trick to
+	// a different button combination.
+	#define MAX_TRICK_BUTTONS 3
+	uint32 mp_trick_button[MAX_TRICK_BUTTONS];
+	// Reads the button names and time duration out of a 'Trigger' structure.
+	uint32 get_buttons_and_duration(Script::CComponent *p_comp, int num_buttons, uint32 *p_button_names);
+	void record_last_tricks_buttons(uint32 *p_button_names);
+
+	float mButtonDebounceTime[PAD_NUMBUTTONS];
+	bool mButtonState[PAD_NUMBUTTONS];
+	
+	bool GetButtonState(uint32 Checksum);
+
+	int mNumButtonsToIgnore;
+	#define MAX_BUTTONS_TO_IGNORE 2
+	uint32 mpButtonsToIgnore[MAX_BUTTONS_TO_IGNORE];
+	void ButtonRecord(uint Button, bool Pressed);
+	
+	bool TriggeredInLastNMilliseconds(uint32 ButtonNameChecksum, uint32 Duration, uint32 IgnoreMask=0xffffffff);
+	bool BothTriggeredNothingInBetween(uint32 Button1, uint32 Button2, uint32 Duration, uint32 IgnoreMask);
+	bool QueryEvents(Script::CStruct *pQuery, uint32 UsedMask=USED_BY_REGULAR_TRICK, uint32 IgnoreMask=0xffffffff);
+	void ResetMaybeUsedFlags();
+	void ConvertMaybeUsedToUsed(uint32 UsedMask);
+	void RemoveOldButtonEvents(uint32 Button, uint32 OlderThan);
+	SButtonEvent *AddEvent(EEventType EventType);
+	void ClearEventBuffer();
+	void DumpEventBuffer();
+	
+	// If set, then the next call to Display will use the yellow text.
+	bool mUseSpecialTrickText;
+
+	// This maps trick slots to trick names. Each pro has their own set of default
+	// mappings, stored in the profile.
+	// The default mappings are defined in script, eg the structure HawkTricks.
+	Script::CStruct *mpTrickMappings;
+
+	//////////////////////////////////////////////////////////
+	// 					Trick queue stuff
+	//////////////////////////////////////////////////////////
+	
+	int mFrontTrick;	// The trick that is at the front of the queue, ie, next to be executed.
+	int mBackTrick;		// The trick at the back of the queue, ie the last to be added.
+	// mFrontTrick and mBackTrick are both -1 to indicate no tricks in queue.
+	#define TRICK_QUEUE_SIZE 100
+	STrick mpTricks[TRICK_QUEUE_SIZE];
+	// Called by the ClearTrickQueue script command.
+	void ClearTrickQueue();
+	
+	void AddTrick(uint32 ArrayChecksum, uint Index, bool UseSpecialTrickText=false);
+	void RemoveFrontTrick();
+	void ClearTricksFrom(uint32 ArrayChecksum);
+
+	bool TrickIsDefined(Script::CStruct *pTrick);
+	bool RunTrick(Script::CStruct *pTrick, uint32 optionalFlag=0, Script::CStruct *pExtraParams=NULL);
+
+	// Called by the DoNextTrick script command.
+	void TriggerNextQueuedTrick(uint32 scriptToRunFirst=0, Script::CStruct *p_scriptToRunFirstParams=NULL, Script::CStruct* pExtraParams=NULL);
+	
+	int mNumQueueTricksArrays;
+	// Storing array checksums rather than pointers to the arrays, in case they get reloaded.
+	#define MAX_QUEUE_TRICK_ARRAYS 10
+	uint32 mpQueueTricksArrays[MAX_QUEUE_TRICK_ARRAYS];
+	void ClearQueueTricksArrays();
+	
+	// Checksum of any special tricks array specified by the last SetQueueTricks script command.
+	uint32 mSpecialTricksArrayChecksum;
+	
+	// This can be set by the SetQueueTricks command, and allows events that have already been
+	// used by certain tricks, eg extra grinds, to be not ignored.
+	uint32 mDoNotIgnoreMask;
+	
+	void MaybeAddTrick(uint32 ArrayChecksum, bool UseSpecialTrickText=false, uint32 DoNotIgnoreMask=0);
+	
+	// Always called every frame.
+	void AddTricksToQueue();
+
+	//////////////////////////////////////////////////////////
+	// 					Manual stuff
+	//////////////////////////////////////////////////////////
+	
+	int mNumManualTrickArrays;
+	// Storing array checksums rather than pointers to the arrays, in case they get reloaded.
+	#define MAX_MANUAL_TRICK_ARRAYS 10
+	uint32 mpManualTrickArrays[MAX_MANUAL_TRICK_ARRAYS];
+	
+	// Checksum of any special manual tricks specified by the SetManualTricks script command.
+	uint32 mSpecialManualTricksArrayChecksum;
+	
+	bool mGotManualTrick;
+	// A special one entry 'queue' for manual tricks.
+	STrick mManualTrick;
+	// Called by the DoNextManualTrick script command.
+	void TriggerAnyManualTrick(Script::CStruct *pExtraParams=NULL);
+	
+	// Checks a single array to see if any trick needs to be put in the manual queue.
+	// Called from MaybeQueueManualTrick()
+	void CheckManualTrickArray(uint32 ArrayChecksum, uint32 IgnoreMask=0xffffffff, bool UseSpecialTrickText=false);
+	
+	// These two functions are always called every frame.
+	void MaybeQueueManualTrick();
+	void MaybeExpireManualTrick();
+	// Called by the ClearManualTrick script command.
+	void ClearManualTrick();
+
+	//////////////////////////////////////////////////////////
+	// 					Extra-grind stuff
+	//////////////////////////////////////////////////////////
+	
+	int mNumExtraGrindTrickArrays;
+	// Storing array checksums rather than pointers to the arrays, in case they get reloaded.
+	#define MAX_EXTRA_GRIND_TRICK_ARRAYS 10
+	uint32 mpExtraGrindTrickArrays[MAX_EXTRA_GRIND_TRICK_ARRAYS];
+	
+	uint32 mSpecialExtraGrindTrickArrayChecksum;
+	
+	bool mGotExtraGrindTrick;
+	// A special one entry 'queue' for extra grind tricks.
+	STrick mExtraGrindTrick;
+	
+	// Called from CSkater::MaybeStickToRail()
+	bool TriggerAnyExtraGrindTrick(bool Right, bool Parallel, bool Backwards, bool Regular);
+	
+	// For checking an single array. Called from MaybeQueueExtraGrindTrick()
+	void MaybeQueueExtraGrindTrick(uint32 ArrayChecksum, bool UseSpecialTrickText=false);
+	
+	// These two functions are always called every frame.
+	void MaybeQueueExtraGrindTrick();
+	void MaybeExpireExtraGrindTrick();
+	// Called by the ClearExtraGrindTrick script command.
+	void ClearExtraGrindTrick();
+	
+	////////////////////////////////////////////////////////////////
+	//
+	// 'Extra' trick stuff.
+	//
+	////////////////////////////////////////////////////////////////
+	
+	// Whether we've got extra tricks to check for.
+	bool mGotExtraTricks;
+	// Whether to check for extra tricks forever.
+	bool mExtraTricksInfiniteDuration;
+	// If not infinite duration, this is how long after the start time to check for extra tricks for.
+	uint32 mExtraTricksDuration;
+	// The start time.
+	uint32 mExtraTricksStartTime;
+	
+	// How many array checksums are in the array.
+	int mNumExtraTrickArrays;
+	#define MAX_EXTRA_TRICK_ARRAYS 10
+	uint32 mpExtraTrickArrays[MAX_EXTRA_TRICK_ARRAYS];
+	// Bitfields indicating which tricks in each array are excluded. (Hence each array can't have more than 32 tricks in it)
+	// Used when an extra trick can branch to another trick in the same array, but we
+	// don't want the same trick again, eg rail balance to another rail balance.
+	uint32 mpExcludedExtraTricks[MAX_EXTRA_TRICK_ARRAYS];
+
+	// Array checksum & exclusion bitfield for the special extra tricks, which are only checked for when special.
+	uint32 mSpecialExtraTricksArrayChecksum;
+	uint32 mExcludedSpecialExtraTricks;
+	
+	bool TriggerAnyExtraTrick(uint32 ArrayChecksum, uint32 ExcludedTricks);
+	void TriggerAnyExtraTricks();
+	void SetExtraTricks(Script::CStruct *pParams, Script::CScript *pScript);
+	void KillExtraTricks();
+	uint32 CalculateIgnoreMask(uint32 ArrayChecksum, const char *pIgnoreName);
+	void ExcludeExtraTricks(const char *pIgnoreName);
+	bool IsExcluded(Script::CStruct *pTrick, const char *pIgnoreName);
+
+	bool mBashingEnabled;
+	
+	void HandleBashing();
+	float GetBashFactor();
+
+	// Name of the current trick, which gets displayed when the trick is triggered.
+	char mpTrickName[MAX_TRICK_NAME_CHARS+1];
+	int mTrickScore;
+
+	// Name of the deferred trick, which gets displayed only if a 'display blockspin' or ClearPanel_Landed is done.
+	char mpDeferredTrickName[MAX_TRICK_NAME_CHARS+1];
+	int mDeferredTrickScore;
+	Mdl::Score::Flags mDeferredTrickFlags;
+
+	float mTallyAngles;
+	
+	// for knowing when to stop horse mode
+	bool m_first_trick_started;
+	bool m_first_trick_completed;
+	
+	int mNumTricksInCombo;
+	void IncrementNumTricksInCombo();
+    void ClearMiscellaneous();
+	
+	void SetGraffitiTrickStarted( bool started );
+	bool GraffitiTrickStarted() { return m_graffiti_trick_started; }
+	
+	// for knowing when to accumulate graffiti objects
+	bool m_graffiti_trick_started;
+	
+	void TrickOffObject( uint32 node_name );
+		
+	void SetFirstTrickStarted( bool started ) { m_first_trick_started = started; }
+	bool FirstTrickStarted() { return m_first_trick_started; }
+	bool FirstTrickCompleted() { return m_first_trick_completed; }
+	
+	uint32 WritePendingTricks( uint32* p_buffer, uint32 max_size );
+	
+	Obj::CPendingTricks m_pending_tricks;
+
+    CTrickComponent();
+    virtual ~CTrickComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		Finalize();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+};
+
+inline Mdl::Score* CTrickComponent::GetScoreObject()
+{
+	Dbg_MsgAssert(mp_score,("NULL pScore"));
+	return mp_score;
+}	
+
+bool ScriptDoNextTrick( Script::CStruct *pParams, Script::CScript *pScript );
+
+
+}
+
+#endif
diff --git a/Code/Gel/Components/vehiclecameracomponent.cpp b/Code/Gel/Components/vehiclecameracomponent.cpp
new file mode 100644
index 0000000..bdc30ea
--- /dev/null
+++ b/Code/Gel/Components/vehiclecameracomponent.cpp
@@ -0,0 +1,314 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VehicleCameraComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  2/10/3
+//****************************************************************************
+
+#include 
+#include 
+									 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPB(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a ? "true" : "false"); }
+#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
+#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
+#define DUMPM(a) { DUMP4(a[X]); DUMP4(a[Y]); DUMP4(a[Z]); DUMP4(a[W]); }
+#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
+#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
+
+#define vVELOCITY_WEIGHT_DROP_THRESHOLD				MPH_TO_IPS(15.0f)
+#define vLOCK_ATTRACTOR_VELOCITY_THRESHOLD			MPH_TO_IPS(5000000.0f)
+#define vSTATE_CHANGE_DELAY (1.0f)
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CVehicleCameraComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CVehicleCameraComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CVehicleCameraComponent::CVehicleCameraComponent() : CBaseComponent()
+{
+	SetType( CRC_VEHICLECAMERA );
+	
+	m_offset_height = FEET(5.0f);
+	m_offset_distance = FEET(12.0f);
+	
+	m_alignment_rate = 3.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CVehicleCameraComponent::~CVehicleCameraComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 subject_id;
+	
+	if (pParams->ContainsComponentNamed(CRCD(0x431c185, "subject")))
+	{
+		pParams->GetChecksum(CRCD(0x431c185, "subject"), &subject_id, Script::ASSERT);
+		mp_subject = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(subject_id));
+		Dbg_MsgAssert(mp_subject, ("Vehicle camera given subject which is not a composite object"));
+		mp_subject_vehicle_component = static_cast< CVehicleComponent* >(mp_subject->GetComponent(CRC_VEHICLE));
+		Dbg_MsgAssert(mp_subject_vehicle_component, ("Vehicle camera given subject which contains no vehicle component"));
+	}
+		
+	pParams->GetFloat(CRCD(0x9213625f, "alignment_rate"), &m_alignment_rate);
+	
+	pParams->GetFloat(CRCD(0x14849b6d, "offset_height"), &m_offset_height);
+	
+	pParams->GetFloat(CRCD(0xbd3d3ca9, "offset_distance"), &m_offset_distance);
+	
+	pParams->GetFloat(CRCD(0xff7ebaf6, "angle"), &m_angle);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::Finalize (   )
+{
+	mp_camera_lookaround_component = GetCameraLookAroundComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_camera_lookaround_component);
+	
+	reset_camera();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::Update()
+{
+	// NOTE: plenty of room for optimiziation
+	
+	GetCameraComponentFromObject(GetObject())->StoreOldPosition();
+	
+	calculate_attractor_direction();
+	
+	// Due to rounding errors this can sometimes be > |1|, which hoses acosf(), so limit here.
+	float angular_distance = acosf(Mth::Clamp(Mth::DotProduct(m_direction, m_attractor_direction), -1.0f, 1.0f));
+	if (angular_distance > Mth::PI / 2.0f)
+	{
+		angular_distance = Mth::PI - angular_distance;
+	}
+	
+	bool sign = Mth::CrossProduct(m_direction, m_attractor_direction)[Y] > 0.0f;
+	
+	float step = m_alignment_rate * angular_distance * Tmr::FrameLength();
+	
+	if (step > angular_distance)
+	{
+		step = angular_distance;
+	}
+	
+	m_direction.RotateY((sign ? 1.0f : -1.0f) * step);
+	m_direction.Normalize();
+	
+	calculate_dependent_variables();
+	
+	ApplyCameraCollisionDetection(
+		m_pos, 
+		m_orientation_matrix, 
+		m_pos - m_offset_distance * m_orientation_matrix[Z], 
+		m_pos - m_offset_distance * m_orientation_matrix[Z], 
+		false, 
+		false
+	);
+	
+	apply_state();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CVehicleCameraComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case CRCC(0x469fd, "VehicleCamera_Reset"):
+			RefreshFromStructure(pParams);
+			Finalize();
+			break;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to C......Component::GetDebugInfo"));
+	
+	p_info->AddChecksum("mp_subject", mp_subject->GetID());
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::reset_camera (   )
+{
+	mp_camera_lookaround_component->mLookaroundHeading = 0.0f;
+	mp_camera_lookaround_component->mLookaroundTilt = 0.0f;
+	mp_camera_lookaround_component->mLookaroundLock = false;
+	
+	m_attractor_direction = -mp_subject->GetMatrix()[Z];
+	m_attractor_direction[Y] = 0.0f;
+	m_attractor_direction.Normalize();
+	
+	calculate_attractor_direction();
+	
+	m_direction = m_attractor_direction;
+	
+	calculate_dependent_variables();
+	
+	apply_state();
+	
+	GetCameraComponentFromObject(GetObject())->Update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::calculate_attractor_direction (   )
+{
+	Mth::Vector vel_direction = -mp_subject_vehicle_component->GetVel();
+	vel_direction[Y] = 0.0f;
+	float vel = vel_direction.Length();
+	vel_direction.Normalize();
+	
+	if (mp_subject_vehicle_component->GetNumWheelsInContact() < 2)
+	{
+		// if vel under certain threshold, we lock the attractor
+		if (vel < vLOCK_ATTRACTOR_VELOCITY_THRESHOLD)
+		{
+			return;
+		}
+		
+		m_attractor_direction = vel_direction;
+	}
+	else
+	{
+		float vel_weight = Mth::ClampMax(vel / vVELOCITY_WEIGHT_DROP_THRESHOLD, 1.0f) * Mth::DotProduct(vel_direction, -mp_subject->GetMatrix()[Z]);
+		
+		m_attractor_direction = -mp_subject->GetMatrix()[Z];
+		m_attractor_direction[Y] = 0.0f;
+		m_attractor_direction.Normalize();
+		
+		if (vel_weight > 0.0f)
+		{
+			m_attractor_direction += vel_weight * vel_direction;
+			m_attractor_direction.Normalize();
+		}
+		
+		// NOTE: potential bug if car is pointing straight down
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::calculate_dependent_variables (   )
+{
+	Mth::Vector frame_direction = m_direction;
+	frame_direction.RotateY(mp_camera_lookaround_component->mLookaroundHeading);
+	
+	m_orientation_matrix[X].Set(frame_direction[Z], 0.0f, -frame_direction[X]);
+	m_orientation_matrix[Y].Set(0.0f, 1.0f, 0.0f);
+	m_orientation_matrix[Z] = frame_direction;
+	m_orientation_matrix[W].Set();
+	
+	m_pos = mp_subject->GetPos() + Mth::Vector(m_offset_distance * frame_direction[X], m_offset_height, m_offset_distance * frame_direction[Z]);
+	
+	m_orientation_matrix.RotateZLocal(DEGREES_TO_RADIANS(m_angle));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleCameraComponent::apply_state (   )
+{
+	m_pos[W] = 1.0f;
+	m_orientation_matrix[X][W] = 0.0f;
+	m_orientation_matrix[Y][W] = 0.0f;
+	m_orientation_matrix[Z][W] = 0.0f;
+	
+	GetObject()->SetPos(m_pos);
+	GetObject()->SetMatrix(m_orientation_matrix);
+	GetObject()->SetDisplayMatrix(m_orientation_matrix);
+}
+
+}
diff --git a/Code/Gel/Components/vehiclecameracomponent.h b/Code/Gel/Components/vehiclecameracomponent.h
new file mode 100644
index 0000000..94bb890
--- /dev/null
+++ b/Code/Gel/Components/vehiclecameracomponent.h
@@ -0,0 +1,76 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VehicleCameraComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_VEHICLECAMERACOMPONENT_H__
+#define __COMPONENTS_VEHICLECAMERACOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+// Just thinking about it - a generic way of accessing the component				 
+#define		CRC_VEHICLECAMERA CRCD(0x2c747d8a, "VehicleCamera")
+#define		GetVehicleCameraComponent() ((Obj::CVehicleCameraComponent*)GetComponent(CRC_VEHICLECAMERA))
+#define		GetVehicleCameraComponentFromObject(pObj) ((Obj::CVehicleCameraComponent*)(pObj)->GetComponent(CRC_VEHICLECAMERA))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CCameraLookAroundComponent;
+
+class CVehicleCameraComponent : public CBaseComponent
+{
+public:
+    CVehicleCameraComponent();
+    virtual ~CVehicleCameraComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize (   );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+private:
+	void							reset_camera (   );
+	void							calculate_attractor_direction (   );
+	void							calculate_dependent_variables (   );
+	void							apply_state (   );
+
+private:
+	CCompositeObject*				mp_subject;
+	CVehicleComponent*				mp_subject_vehicle_component;
+	
+	Mth::Vector		   				m_direction;
+	Mth::Vector		   				m_attractor_direction;
+	
+	Mth::Vector						m_pos;
+	Mth::Matrix						m_orientation_matrix;
+	
+	float							m_offset_height;
+	float							m_offset_distance;
+	float							m_angle;
+	
+	float							m_alignment_rate;
+	
+	CCameraLookAroundComponent*		mp_camera_lookaround_component;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Components/vehiclecomponent.cpp b/Code/Gel/Components/vehiclecomponent.cpp
new file mode 100644
index 0000000..035e19e
--- /dev/null
+++ b/Code/Gel/Components/vehiclecomponent.cpp
@@ -0,0 +1,3321 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VehicleComponent.cpp
+//* OWNER:          Dan Nelson
+//* CREATION DATE:  1/31/3
+//****************************************************************************
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
+#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
+#define DUMPM(a) { DUMP4(a[X]); DUMP4(a[Y]); DUMP4(a[Z]); DUMP4(a[W]); }
+#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
+#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
+
+// THOUGHTS:
+//
+// - BUG: solve negative collision depth issue for rect collisions; must project collision normal into rect plane before calculating depth (i think)
+// - BUG: bad wipeout behavior; IDEAS:
+//   - use only line feelers
+//   - turn off graivty with > 2 contacts
+//   - sleep like a rigidbody
+//   - compare algorithm with rigidbody
+// - Triggers.
+// - Lock motion if very slow and there's no input.
+// - Vehicle camera needs to reports its old position to get sounds' pitch correctly.
+//
+// - two center of masses (collision at zero and suspension) is an issue; actual rotation is done around zero; what sort of poor behavior does this cause
+//   when the car is on two wheels? what way is there to retain stable suspension behavior but use a common center of mass?	perhaps simply damp rotational
+//   impulses due to the suspension; this may help with the "vert, linear-to-angular energy" freak-out issue as well
+// - body-body collisions; treat as boxes
+// - prehaps reduce wheel friction when very near vertical to prevent wall riding
+// - states: ASLEEP, AWAKE (full brake; rigidbody), DRIVEN, DRONE (?)
+// - vertical side rectangle feelers
+// - wheel camber  extracted from model
+// - side slippage when stopped on hills
+
+namespace Obj
+{
+	
+CVehicleComponent::SCollisionPoint CVehicleComponent::sp_collision_points[4 * (Nx::MAX_NUM_2D_COLLISIONS_REPORTED + 1)];
+								   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CVehicleComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CVehicleComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CVehicleComponent::CVehicleComponent() : CBaseComponent()
+{
+	SetType( CRC_VEHICLE );
+	
+	mp_input_component = NULL;
+	
+	m_draw_debug_lines = 0;
+	
+	m_update_suspension_only = false;
+	
+	m_steering_display = 0.0f;
+	
+	m_skater_pos.Set();
+	
+	mp_wheels = NULL;
+	
+	m_sound_setup_checksum = CRCD(0x1ca1ff20, "Default");
+	
+	m_num_collision_points = 0;
+	
+	m_artificial_collision_timer = 0.0f;
+	
+	m_state = AWAKE;
+	
+	m_consider_sleeping_count = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CVehicleComponent::~CVehicleComponent()
+{
+	delete [] mp_wheels;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// extract constant parameters from struct; parameters not included in the script are left unchanged
+	
+	// toggles through the debug line drawing states
+	if (pParams->ContainsFlag(CRCD(0x935ab858, "debug")))
+	{
+		m_draw_debug_lines = ++m_draw_debug_lines % 3;
+	}
+	else if (pParams->ContainsFlag(CRCD(0xe6b5fcd9, "full_debug")))
+	{
+		m_draw_debug_lines = (m_draw_debug_lines + 2) % 3;
+	}
+	else if (pParams->ContainsFlag(CRCD(0x751da48b, "no_debug")))
+	{
+		m_draw_debug_lines = 0;
+	}
+	
+	// center of mass location (in)
+	// the point around which the car rotates; a lower center of mass reduces roll tendency; the XZ location affects the
+	// vehicle's behavior during jumps
+	if (pParams->ContainsComponentNamed(CRCD(0x93346b8c, "suspension_center_of_mass")))
+	{
+		float suspension_center_of_mass_offset;
+		pParams->GetFloat(CRCD(0x93346b8c, "suspension_center_of_mass"), &suspension_center_of_mass_offset);
+		m_suspension_center_of_mass.Set(0.0f, suspension_center_of_mass_offset, 0.0f);
+	}
+	
+	// mass (lbs)
+	// affects acceleration, suspension behavior, and resistance to drag
+	if (pParams->ContainsComponentNamed(CRCD(0x93fca499, "mass")))
+	{
+		pParams->GetFloat(CRCD(0x93fca499, "mass"), &m_inv_mass);
+		m_inv_mass = vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass;
+	}
+
+	// moment of inertia (in^2 lbs)
+	// resistance to changes in rotation along the three axes; natural value is about the weight of the car times the
+	// square of the average radius of the car along the axes in question
+	// X: resistance to rolling
+	// Y: resistance to turning
+	// Z: resistance to pitcing during acceleration, braking, and jumps
+	if (pParams->ContainsComponentNamed(CRCD(0x8c6473ec, "moment_of_inertia")))
+	{
+		pParams->GetVector(CRCD(0x8c6473ec, "moment_of_inertia"), &m_inv_moment_body);
+		m_inv_moment_body[X] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[X];
+		m_inv_moment_body[Y] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Y];
+		m_inv_moment_body[Z] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Z];
+	}
+	
+	// vehicle body's coefficient of restitution
+	pParams->GetFloat(CRCD(0x79c3b862, "body_restitution"), &m_body_restitution);
+	
+	// vehicle body's coefficient of friction
+	pParams->GetFloat(CRCD(0xf476273c, "body_friction"), &m_body_friction);
+	
+	// vehicle body's coefficient of friction
+	pParams->GetFloat(CRCD(0x4a23a53c, "body_wipeout_friction"), &m_body_wipeout_friction);
+	
+	// vehicle body's penalty-method interpenetration-prevention spring constant
+	pParams->GetFloat(CRCD(0x1dd4890, "body_spring"), &m_body_spring);
+	
+	// factor of normal impulse over which you can control via steering
+	pParams->GetFloat(CRCD(0xb9f026ed, "collision_control"), &m_collision_control);
+	
+	// horizontal velocity cutoff below which no in-air slerping to face velocity occurs
+	pParams->GetFloat(CRCD(0x8c14709e, "in_air_slerp_velocity_cutoff"), &m_in_air_slerp_vel_cutoff);
+	
+	// time over which in-air slerping lerps to full strength after takeoff
+	pParams->GetFloat(CRCD(0x2c91e49b, "in_air_slerp_time_delay"), &m_in_air_slerp_time_delay);
+	
+	// in-air slerping strength
+	pParams->GetFloat(CRCD(0x280f0e0b, "in_air_slerp_strength"), &m_in_air_slerp_strength);
+	
+	// special slerping below standard velocity threshold
+	m_vert_correction = pParams->ContainsFlag(CRCD(0x5341806a, "vert_correction"));
+	
+	// maximum steering angle (degrees)
+	if (pParams->ContainsComponentNamed(CRCD(0xc1e5abdd, "max_steering_angle")))
+	{
+        pParams->GetFloat(CRCD(0xc1e5abdd, "max_steering_angle"), &m_max_steering_angle);
+		m_max_steering_angle = DEGREES_TO_RADIANS(m_max_steering_angle);
+	}
+	
+	// rotational damping parameters which prevent fishtailing and drift
+	pParams->GetFloat(CRCD(0x386837db, "constant_rotational_damping"), &m_const_rotvel_damping);
+	pParams->GetFloat(CRCD(0x469cf79a, "quadratic_rotational_damping"), &m_quad_rotvel_damping);
+	
+	// if the car can be exited via triangle
+	m_exitable = pParams->ContainsFlag(CRCD(0xc2a136cc, "exitable"));
+	
+	// if the car has a handbrke
+	m_no_handbrake = pParams->ContainsFlag(CRCD(0xad6c0a46, "no_handbrake"));
+	
+	// the vehicle's body's interaction with the environment is expressed as an array of rectangular colliders
+	if (pParams->ContainsComponentNamed(CRCD(0xfc8c4ac6, "colliders")))
+	{
+		Script::CArray* p_colliders_array;
+		pParams->GetArray(CRCD(0xfc8c4ac6, "colliders"), &p_colliders_array, Script::ASSERT);
+		
+		float average_distance = 0.0f;
+		
+		int num_colliders = p_colliders_array->GetSize();
+		// Dbg_MsgAssert(num_colliders == vVP_NUM_COLLIDERS, ("Number of colliders for CVehicleComponent is incorrect"));
+		
+		for (int collider_idx = num_colliders; collider_idx--; )
+		{
+            SCollider& collider = mp_colliders[collider_idx];
+			
+			Script::CArray* p_corner_array = p_colliders_array->GetArray(collider_idx);
+			Dbg_MsgAssert(p_corner_array->GetSize() == 3, ("Incorrect number of corner vectors in collider array"));
+			
+			Script::CVector* p_corner_vector;
+            p_corner_vector = p_corner_array->GetVector(0);
+            collider.body.m_corner[X] = p_corner_vector->mX;
+            collider.body.m_corner[Y] = p_corner_vector->mY;
+            collider.body.m_corner[Z] = p_corner_vector->mZ;
+			p_corner_vector = p_corner_array->GetVector(1);
+			collider.body.m_first_edge[X] = p_corner_vector->mX;
+			collider.body.m_first_edge[Y] = p_corner_vector->mY;
+			collider.body.m_first_edge[Z] = p_corner_vector->mZ;
+			collider.body.m_first_edge -= collider.body.m_corner;
+			p_corner_vector = p_corner_array->GetVector(2);
+			collider.body.m_second_edge[X] = p_corner_vector->mX;
+			collider.body.m_second_edge[Y] = p_corner_vector->mY;
+			collider.body.m_second_edge[Z] = p_corner_vector->mZ;
+			collider.body.m_second_edge -= collider.body.m_corner;
+			
+			// update maximum_distance
+			average_distance += collider.body.m_corner.Length();
+			average_distance += (collider.body.m_corner + collider.body.m_first_edge).Length();
+			average_distance += (collider.body.m_corner + collider.body.m_second_edge).Length();
+			
+			// calculate the precomputable values
+			collider.first_edge_length = collider.body.m_first_edge.Length();
+			collider.second_edge_length = collider.body.m_second_edge.Length();
+			
+			Dbg_MsgAssert(collider.first_edge_length > 0.0f, ("Collider of zero area"));
+			Dbg_MsgAssert(collider.second_edge_length > 0.0f, ("Collider of zero area"));
+		}
+		
+		// set the skater's rigid body collision radius while in the car
+		Mdl::Skate::Instance()->GetLocalSkater()->SetRigidBodyCollisionRadiusBoost(average_distance / 3.0f / num_colliders - 48.0f);
+	}
+	
+	if (pParams->ContainsComponentNamed(CRCD(0x1757e572, "engine")))
+	{
+		Script::CStruct* p_engine_struct;
+		pParams->GetStructure(CRCD(0x1757e572, "engine"), &p_engine_struct, Script::ASSERT);
+		
+		// base drive torque of the engine; multiplied by the gear and differential ratio (ft-lbs)
+		if (p_engine_struct->ContainsComponentNamed(CRCD(0x9aa9faee, "drive_torque")))
+		{
+			p_engine_struct->GetFloat(CRCD(0x9aa9faee, "drive_torque"), &m_engine.drive_torque);
+			m_engine.drive_torque *= 12.0f;
+		}
+		
+		// base drag torque of the engine; multiplied by the square of the gear ratio (ft-lbs)
+		if (p_engine_struct->ContainsComponentNamed(CRCD(0xb2268648, "drag_torque")))
+		{
+			p_engine_struct->GetFloat(CRCD(0xb2268648, "drag_torque"), &m_engine.drag_torque);
+			m_engine.drag_torque *= 12.0f;
+		}
+		
+		// when rpm reaches this speed, the transmition upshifts (rpm)
+		if (p_engine_struct->ContainsComponentNamed(CRCD(0x33921583, "upshift_rpm")))
+		{
+			p_engine_struct->GetFloat(CRCD(0x33921583, "upshift_rpm"), &m_engine.upshift_rotvel);
+			m_engine.upshift_rotvel = RPM_TO_RADIANS_PER_SECOND(m_engine.upshift_rotvel);
+		}
+		
+		// base gear ratio; torque and rpm are multiplied by this when translated up and down the drive train
+		p_engine_struct->GetFloat(CRCD(0x94ea6601, "differential_ratio"), &m_engine.differential_ratio);
+		
+		// instead of having a true reverse gear, all gears are scaled down by this ratio when in reverse
+		p_engine_struct->GetFloat(CRCD(0x82bcbb10, "reverse_torque_ratio"), &m_engine.reverse_torque_ratio);
+		
+		// gear ratios; torque and rpm are multiplied by these when translated up and down the drive train
+		if (p_engine_struct->ContainsComponentNamed(CRCD(0xc023fa75, "gear_ratios")))
+		{
+			Script::CArray* p_engine_gear_ratios_array;
+			p_engine_struct->GetArray(CRCD(0xc023fa75, "gear_ratios"), &p_engine_gear_ratios_array, Script::ASSERT);
+			
+			m_engine.num_gears = p_engine_gear_ratios_array->GetSize();
+			Dbg_MsgAssert(m_engine.num_gears <= vVP_MAX_NUM_GEARS, ("Number of gears exceeds the maximum allowed number"));
+			
+			for (int n = 0; n < m_engine.num_gears; n++)
+			{
+				m_engine.p_gear_ratios[n] = p_engine_gear_ratios_array->GetFloat(n);
+				
+				if (n == 0) continue;
+				
+				Dbg_MsgAssert(n == 0 || m_engine.p_gear_ratios[n - 1] > m_engine.p_gear_ratios[n],
+					("Low gear has lower gear ratio than high gear"));
+			}
+		}
+	}
+	
+	// you can setup all wheels at once or each individually
+	
+	// an array of wheel structures
+	if (pParams->ContainsComponentNamed(CRCD(0xb3f8557e, "wheels")))
+	{
+		Script::CArray* p_wheels_array = NULL;
+		pParams->GetArray(CRCD(0xb3f8557e, "wheels"), &p_wheels_array, Script::ASSERT);
+		
+		Dbg_MsgAssert(!mp_wheels || m_num_wheels == p_wheels_array->GetSize(), ("Changed number of wheels"));
+		
+		m_num_wheels = p_wheels_array->GetSize();
+			
+		if (!mp_wheels)
+		{
+			mp_wheels = new SWheel[m_num_wheels];
+		}
+			
+		for (int n = m_num_wheels; n--; )
+		{
+			Script::CStruct* p_wheel_struct = NULL;
+			p_wheel_struct = p_wheels_array->GetStructure(n);
+			
+			// see update_wheel_from_structure() for documentation on wheel parameters
+			update_wheel_from_structure(mp_wheels[n], p_wheel_struct);
+		} // END loop over wheels
+	} // END if wheel array specified
+
+	Dbg_MsgAssert(mp_wheels, ("Wheels not created"));
+	
+	// a structure of wheel parameters which acts on all wheels
+	if (pParams->ContainsComponentNamed(CRCD(0x371badff, "all_wheels")))
+	{
+		Script::CStruct* p_wheel_struct;
+		pParams->GetStructure(CRCD(0x371badff, "all_wheels"), &p_wheel_struct, Script::ASSERT);
+		
+		for (int n = m_num_wheels; n--; )
+		{
+			// see update_wheel_from_structure() for documentation on wheel parameters
+			update_wheel_from_structure(mp_wheels[n], p_wheel_struct);
+		} // END loop over wheels
+	} // END if all_wheel structure specified
+	
+	// calculate dependent constant characteristics
+	
+	// count the number of drive wheels
+	m_num_drive_wheels = 0;
+	for (int n = m_num_wheels; n--; )
+	{
+		if (mp_wheels[n].drive)
+		{
+			m_num_drive_wheels++;
+		}
+	}
+	
+	// setup position and orientation based on the object's state
+	m_orientation = GetObject()->GetMatrix();
+	m_orientation.Normalize();
+	if (pParams->ContainsComponentNamed(CRCD(0xaa99c521, "save")))
+	{
+		Mth::Vector orientation_vector;
+		orientation_vector.Set();
+		pParams->GetVector(CRCD(0xc97f3aa9, "orientation"), &orientation_vector);
+		m_orientation.SetVector(orientation_vector);
+		m_orientation.Normalize();
+		m_orientation.GetMatrix(m_orientation_matrix);
+		m_orientation_matrix[W].Set();
+		GetObject()->SetMatrix(m_orientation_matrix);
+		GetObject()->SetDisplayMatrix(m_orientation_matrix);
+	}
+	
+	m_pos = GetObject()->GetPos();
+	if (pParams->ContainsComponentNamed(CRCD(0x7f261953, "pos")))
+	{
+		pParams->GetVector(CRCD(0x7f261953, "pos"), &m_pos);
+		GetObject()->SetPos(m_pos);
+	}
+	
+	m_skater_visible = m_skater_visible || pParams->ContainsFlag(CRCD(0x2ed67657, "make_skater_visible"));
+	if (pParams->ContainsFlag(CRCD(0x2ed67657, "make_skater_visible")))
+	{
+		pParams->GetVector(CRCD(0xec86ef7a, "skater_pos"), &m_skater_pos, Script::ASSERT);
+		pParams->GetChecksum(CRCD(0xda75a33e, "skater_anim"), &m_skater_anim, Script::ASSERT);
+	}
+	
+	pParams->GetChecksum(CRCD(0xedcf90e, "Sounds"), &m_sound_setup_checksum);
+	
+	// zero velocities and accumulators
+	
+	m_mom.Set(0.0f, 0.0f, 0.0f);
+	m_rotmom.Set(0.0f, 0.0f, 0.0f);
+	
+	m_force.Set(0.0f, 0.0f, 0.0f);
+	m_torque.Set(0.0f, 0.0f, 0.0f);
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		wheel.state = SWheel::OUT_OF_CONTACT;
+ 		wheel.orientation = 0.0f;
+		wheel.rotvel = 0.0f;
+		wheel.y_offset = wheel.y_offset_hang;
+		wheel.steering_angle = 0.0f;
+		wheel.steering_angle_display = 0.0f;
+		
+		wheel.rotacc = 0.0f;
+
+		// set normal force history to their default values
+		for (int i = vVP_NORMAL_FORCE_HISTORY_LENGTH; i--; )
+		{
+			wheel.normal_force_history[i] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass / m_num_wheels;
+		}
+	}
+	m_next_normal_force_history_idx = 0;
+	
+	m_gravity_override_timer = 0.0f;
+	m_gravity_override_fraction = 1.0f;
+	
+	m_in_flip = false;
+	
+	// grab a pointer to the vehicle's skeleton
+	mp_skeleton_component = static_cast< CSkeletonComponent* >(GetObject()->GetComponent(CRC_SKELETON));
+	Dbg_MsgAssert(mp_skeleton_component, ("Vehicle component has no peer skeleton component."));
+	Dbg_MsgAssert(mp_skeleton_component->GetSkeleton()->GetNumBones() == static_cast< int >(2 + m_num_wheels), ("Vehicle component's peer skeleton component has an unexpected number of bones"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	Dbg_Assert(false);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::Finalize (   )
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	mp_model_component = GetModelComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_input_component);
+	Dbg_Assert(mp_model_component);
+	
+	// extract information about the car from the model
+	
+	Dbg_MsgAssert(m_num_wheels == vVP_NUM_WHEELS, ("Number of wheels must equal CVehicleComponent::vVP_NUM_WHEELS"));
+	
+	CModelComponent* p_model_component = static_cast< CModelComponent* >(GetModelComponentFromObject(GetObject()));
+	Dbg_Assert(p_model_component);
+	Nx::CModel* p_model = p_model_component->GetModel();
+	Dbg_Assert(p_model);
+	Nx::CHierarchyObject* p_hierarchy_objects = p_model->GetHierarchy();
+	Dbg_Assert(p_hierarchy_objects);
+	
+	for (int n = vVP_NUM_WHEELS; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		Mth::Matrix wheel_matrix = (p_hierarchy_objects + 2 + n)->GetSetupMatrix();
+		
+		// rotate out of max coordinate system
+		wheel.pos[X] = -wheel_matrix[W][X];
+		wheel.pos[Y] = wheel_matrix[W][Z];
+		wheel.pos[Z] = -wheel_matrix[W][Y];
+		wheel.pos[W] = 1.0f;
+	}
+	
+	Mth::Matrix body_matrix = (p_hierarchy_objects + 1)->GetSetupMatrix();
+	m_body_pos[X] = -body_matrix[W][X];
+	m_body_pos[Y] = body_matrix[W][Z];
+	m_body_pos[Z] = -body_matrix[W][Y];
+	m_body_pos[W] = 1.0f;
+	
+	// extract axle and wheelbase information
+	int left_steering_tire = -1;
+	int right_steering_tire = -1;
+	int rear_tire = -1;
+	for (int n = vVP_NUM_WHEELS; n--; )
+	{
+		switch (mp_wheels[n].steering)
+		{
+			case SWheel::LEFT:
+				left_steering_tire = n;
+				break;
+			case SWheel::RIGHT:
+				right_steering_tire = n;
+				break;
+			case SWheel::FIXED:
+				rear_tire = n;
+				break;
+		}
+	}
+	Dbg_Assert(left_steering_tire != -1);
+	Dbg_Assert(right_steering_tire != -1);
+	Dbg_Assert(rear_tire != -1);
+	m_cornering_wheelbase = Mth::Abs(
+		(p_hierarchy_objects + 2 + left_steering_tire)->GetSetupMatrix()[W][Y] - (p_hierarchy_objects + 2 + rear_tire)->GetSetupMatrix()[W][Y]
+	);
+	m_cornering_axle_length = Mth::Abs(
+		(p_hierarchy_objects + 2 + left_steering_tire)->GetSetupMatrix()[W][X] - (p_hierarchy_objects + 2 + right_steering_tire)->GetSetupMatrix()[W][X]
+	);
+	
+	// calculate the true wheel positions based on the desired wheel positions with vehicle weight applied
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+	
+		// place the wheel in the desired location based on the vehicle mass and the suspension spring rate; the suspension feeler starts at two
+		// radii above the desired position
+		float desired_y_pos = wheel.pos[Y];
+		wheel.pos[Y] = desired_y_pos + 2.0f * wheel.radius;
+		wheel.y_offset_hang = -wheel.pos[Y] + desired_y_pos - vVP_GRAVITATIONAL_ACCELERATION / (m_inv_mass * m_num_wheels * wheel.spring);
+	}
+	
+	// determine the lowest collider point
+	float lowest_collider_height = mp_colliders[0].body.m_corner[Y];
+	for (int n = vVP_NUM_COLLIDERS; n--; )
+	{
+		lowest_collider_height = Mth::Min(mp_colliders[n].body.m_corner[Y], lowest_collider_height);
+		lowest_collider_height = Mth::Min(mp_colliders[n].body.m_corner[Y] + mp_colliders[n].body.m_first_edge[Y], lowest_collider_height);
+		lowest_collider_height = Mth::Min(mp_colliders[n].body.m_corner[Y] + mp_colliders[n].body.m_second_edge[Y], lowest_collider_height);
+	}
+	
+	// ready the skater for control
+	mp_skater = Mdl::Skate::Instance()->GetLocalSkater();
+	mp_skater_core_physics_component = GetSkaterCorePhysicsComponentFromObject(mp_skater);
+	mp_skater_trigger_component = GetTriggerComponentFromObject(mp_skater);
+	Dbg_Assert(mp_skater_core_physics_component);
+	Dbg_Assert(mp_skater_trigger_component);
+	
+	if (!m_skater_visible)
+	{
+		mp_skater->Hide(true);
+	}
+	else
+	{
+		mp_skater_animation_component = GetAnimationComponentFromObject(mp_skater);
+		Dbg_Assert(mp_skater_animation_component);
+	}
+	
+	// calculate the center of mass we will use based on the wheel locations
+	Mth::Vector center_of_mass(0.0f, 0.0f, 0.0f, 0.0f);
+	for (int n = m_num_wheels; n--; )
+	{
+		center_of_mass += mp_wheels[n].pos;
+	}
+	center_of_mass /= m_num_wheels;
+	center_of_mass[Y] = lowest_collider_height;
+	center_of_mass[W] = 0.0f;
+	
+	// move wheels and colliders so that they are relative to the correct center of mass
+	for (int n = m_num_wheels; n--; )
+	{
+		mp_wheels[n].pos -= center_of_mass;
+	}
+	for (int n = vVP_NUM_COLLIDERS; n--; )
+	{
+		mp_colliders[n].body.m_corner -= center_of_mass;
+	}
+	m_body_pos -= center_of_mass;
+	m_skater_pos -= center_of_mass;
+	
+	update_dependent_variables();
+	
+	GetObject()->SetPos(m_pos);
+	GetObject()->SetVel(m_vel);
+	GetObject()->SetMatrix(m_orientation_matrix);
+	GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+	
+	update_skeleton();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::Update()
+{
+	m_reset_this_frame = false;
+	
+	if (!m_update_suspension_only)
+	{
+		get_input();
+	}
+	else
+	{
+		zero_input();
+	}
+	
+	if (m_state == ASLEEP)
+	{
+		// reduced work set if we're asleep
+		GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+		update_steering_angles();
+		if (m_controls.brake)
+		{
+			for (int n = m_num_wheels; n--; )
+			{
+				mp_wheels[n].rotvel = 0.0f;
+			}
+		}
+		update_wheel_dynamic_state();
+		update_skeleton();
+		draw_shadow();
+		control_skater();
+		return;
+	}
+	
+	int num_time_steps;
+	float frame_length = Tmr::FrameLength();
+	
+	if (in_artificial_collision())
+	{
+		m_artificial_collision_timer -= frame_length;
+	}
+	
+	// count down timers
+	if (m_gravity_override_timer != 0.0f)
+	{
+		m_gravity_override_timer -= frame_length;
+		if (m_gravity_override_timer <= 0.0f || m_num_wheels_in_contact > 1)
+		{
+			MESSAGE("Ending vehicle gravity override");
+			m_gravity_override_timer = 0.0f;
+			m_gravity_override_fraction = 1.0f;
+		}
+	}
+	
+	// the physics is unstable at low frame rates, so we take multiple physics steps during long frame; if vehicle physics is a significant fraction
+	// of CPU time, this could exacerbate whatever frame rate problems are occuring
+	if (frame_length >= (1.0f / 30.0f))
+	{
+		num_time_steps = static_cast< int >(ceilf(frame_length / (1.0f / 60.0f)));
+		if (num_time_steps > 6)
+		{
+			num_time_steps = 6;
+		}
+		m_time_step = frame_length / num_time_steps;
+		Dbg_Message("CVehicleComponent::Update: using %i steps this frame", num_time_steps);
+	}
+	else
+	{
+		num_time_steps = 1;
+		m_time_step = frame_length;
+	}
+	
+	for (int step = num_time_steps; step--; )
+	{
+		update_dynamic_state();
+		
+		m_force.Set(0.0f, 0.0f, 0.0f);
+		m_torque.Set(0.0f, 0.0f, 0.0f);
+		for (int n = m_num_wheels; n--; )
+		{
+			mp_wheels[n].rotacc = 0.0f;
+		}
+		
+		if (!m_update_suspension_only)
+		{
+			apply_environment_collisions();
+			if (reset_this_frame()) return;
+		}
+		
+		// teleport wheels to the ground, if within reach of the suspension
+		update_wheel_heights();
+		if (reset_this_frame()) return;
+		
+		// update the steering wheels' angles
+		update_steering_angles();
+		
+		// damp out rotations to create a more drivable car
+		damp_rotation();
+		
+		// accumulate all forces and torques on the body and wheels
+		accumulate_forces();
+
+		/////////////
+		// update state observers
+
+		if (m_num_wheels_in_contact > 0)
+		{
+			m_air_time = 0.0f;
+		}
+		else
+		{
+			m_air_time += m_time_step;
+		}
+		
+		if (m_num_wheels_in_contact > 0 || m_max_normal_collision_impulse > 0.0f)
+		{
+			m_air_time_no_collision = 0.0f;
+		}
+		else
+		{
+			m_air_time_no_collision += m_time_step;
+		}
+	}
+	
+	if (m_update_suspension_only) return;
+	
+	// draw debug lines
+	draw_debug_rendering();
+	
+	// update object's position and orientation
+	GetObject()->SetPos(m_pos);
+	GetObject()->SetVel(m_vel);
+	GetObject()->SetMatrix(m_orientation_matrix);
+	GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+	
+	consider_sleeping();
+	
+	update_skeleton();
+
+	// Hack to draw shadow
+	draw_shadow();
+	
+	#if 0
+	Mth::Vector forward(0.0f, 0.0f, 1.0f);
+	forward = m_orientation_matrix.Rotate(forward);
+	float vel = IPS_TO_MPH(Mth::Abs(Mth::DotProduct(m_vel, forward)));
+	PERIODIC(60) DUMPF(vel);
+	#endif
+	
+	// HACK: get player proximity checks, triggers, driving animations, and the like working
+	control_skater();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CVehicleComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Vehicle_Kick | kicks the vehicle with a force and torque
+		case CRCC(0x93b713a6, "Vehicle_Kick"):
+		{
+			Mth::Vector linear;
+			Mth::Vector angular;
+			
+			m_state = AWAKE;
+			m_consider_sleeping_count = 0;
+			
+			pParams->GetVector("linear", &linear);
+			pParams->GetVector("angular", &angular);
+			
+			linear = m_orientation_matrix.Rotate(linear);
+			angular = m_orientation_matrix.Rotate(angular);
+			
+			m_mom += linear / m_inv_mass;
+			m_rotmom += angular / m_inv_moment_body[X];
+			
+			update_velocities();
+			
+			break;
+		}
+		
+		// @script | Vehicle_Wake | wakes the vehicle if asleep
+		case CRCC(0xa80a0d36, "Vehicle_Wake"):
+			m_state = AWAKE;
+			m_consider_sleeping_count = 0;
+			break;
+
+		// @script | Vehicle_MoveToRestart | teleport the vehicle to the restart node
+		case CRCC(0x4b0b27dd, "Vehicle_MoveToRestart"):
+		{
+			uint32 node_name_checksum;
+			if (pParams->GetChecksum(NO_NAME, &node_name_checksum))
+			{
+				// a node is specifically specified
+				MoveToNode(SkateScript::GetNode(SkateScript::FindNamedNode(node_name_checksum, Script::ASSERT)));
+			}
+			else
+			{
+				// find a linked restart node
+				int node = pScript->mNode;
+				Dbg_MsgAssert(node !=  -1,( "Vehicle_MoveToRestart called from non-node script with no target node specified"));
+				{
+					int num_links = SkateScript::GetNumLinks(node);
+					for (int n = 0; n < num_links; n++)
+					{
+						int linked_node = SkateScript::GetLink(node, n);
+						if (IsRestart(linked_node))
+						{
+							MoveToNode(SkateScript::GetNode(linked_node));
+							return CBaseComponent::MF_TRUE;
+						}
+					}
+					if (Ed::CParkEditor::Instance()->UsingCustomPark())
+					{
+						MoveToNode(SkateScript::GetNode(Mdl::Skate::Instance()->find_restart_node(0)));
+					}
+					else
+					{
+						Dbg_MsgAssert(0, ("Vehicle_MoveToRestart called but node %d not linked to restart", node));			
+					}
+				}
+			}			
+			break;
+		}
+			
+		// @script | Vehicle_PlaceBeforeCamera | moves the object before the active camera
+		case CRCC(0xc33608e4, "Vehicle_PlaceBeforeCamera"):
+		{
+			Gfx::Camera* camera = Nx::CViewportManager::sGetActiveCamera(0);
+			if (camera)
+			{
+				Mth::Vector& cam_pos = camera->GetPos();
+				Mth::Matrix& cam_mat = camera->GetMatrix();
+
+				m_pos = cam_pos;
+				m_pos += cam_mat[Y] * 12.0f * 12.0f;
+				m_pos -= cam_mat[Z] * 12.0f * 12.0f;
+				GetObject()->SetPos(m_pos);
+				
+				m_orientation_matrix[X] = -cam_mat[X];
+				m_orientation_matrix[Y] = cam_mat[Y];
+				m_orientation_matrix[Z] = -cam_mat[Z];
+				m_orientation = m_orientation_matrix;
+				m_orientation.Normalize();
+				m_orientation.GetMatrix(m_orientation_matrix);
+				m_orientation_matrix[W].Set();
+				GetObject()->SetMatrix(m_orientation_matrix);
+				
+				update_dependent_variables();
+			}
+			break;
+		}
+			
+		// @script | Vehicle_AdjustGravity | adjusts effective gravity for a given duration or until the vehicle has two or more wheels on the ground,
+		// whichever occurs first
+		// @parm float | Percent | Percent of standard gravity.
+		// @parm float | Duration | Duration in seconds over which to override gravity.
+		case CRCC(0xdb35aad8, "Vehicle_AdjustGravity"):
+			pParams->GetFloat(CRCD(0x9e497fc6, "Percent"), &m_gravity_override_fraction, Script::ASSERT);
+			m_gravity_override_fraction *= 1.0f / 100.0f;
+			pParams->GetFloat(CRCD(0x79a07f3f, "Duration"), &m_gravity_override_timer, Script::ASSERT);
+			Dbg_MsgAssert(m_gravity_override_timer > 0.0f, ("Vehicle_AdjustGravity must have positive Duration"));
+			MESSAGE("Initiating vehicle gravity override");
+			break;
+			
+		// @script | Vehicle_ForceBrake | forces on the brake
+		// case CRCC(0x1ad6b6bc, "Vehicle_ForceBrake"):
+			// m_force_brake = true;
+			// break;
+			
+		// @script | Vehicle_HandbrakeActive | returns true if the car has a handbrake
+		case CRCC(0x5008b253, "Vehicle_HandbrakeActive"):
+			return m_force_brake ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		// @script | Vehicle_AllWheelsAreInContact | returns true if all of the wheels are in contact with geo
+		case CRCC(0x279602ed, "Vehicle_AllWheelsAreInContact"):
+			return m_num_wheels_in_contact == m_num_wheels ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+
+		// @script | Vehicle_LostCollision | lost a net collision; respond appropriately
+		case CRCC(0x4c73526b, "Vehicle_LostCollision"):
+		{
+			Mth::Vector offset;
+			pParams->GetVector(CRCD(0xa6f5352f, "Offset"), &offset, Script::ASSERT);
+			ApplyArtificialCollision(offset);
+			break;
+		}
+		
+		// @script | Vehicle_IsSkaterVisible | true if skater should be visible while driving
+		case CRCC(0x81faac21, "Vehicle_IsSkaterVisible"):
+			return m_skater_visible ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; 
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::GetDebugInfo ( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info, ("NULL p_info sent to CVehicleComponent::GetDebugInfo"));
+	
+	p_info->AddVector("m_pos", m_pos);
+	p_info->AddVector("m_mom", m_mom);
+	p_info->AddVector("m_rotmom", m_rotmom);
+	p_info->AddFloat("m_orientation", m_orientation.GetScalar());
+	p_info->AddVector("m_orientation", m_orientation.GetVector());
+	
+	p_info->AddVector("m_force", m_force);
+	p_info->AddVector("m_torque", m_torque);
+	
+	p_info->AddVector("m_vel", m_vel);
+	p_info->AddVector("m_rotvel", m_rotvel);
+	
+	p_info->AddVector("m_suspension_center_of_mass", m_suspension_center_of_mass);
+	p_info->AddFloat("mass", vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass);
+	p_info->AddVector("moment_of_inertia",
+		vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[X],
+		vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Y],
+		vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Z]
+	);
+	p_info->AddFloat("body_restitution", m_body_restitution);
+	p_info->AddFloat("body_friction", m_body_friction);
+	p_info->AddFloat("body_spring", m_body_spring);
+	p_info->AddFloat("collision_control", m_collision_control);
+	p_info->AddFloat("in_air_slerp_velocity_cutoff", m_in_air_slerp_vel_cutoff);
+	p_info->AddFloat("in_air_slerp_time_delay", m_in_air_slerp_time_delay);
+	p_info->AddFloat("in_air_slerp_strength", m_in_air_slerp_strength);
+	p_info->AddChecksum("vert_correction", m_vert_correction ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	
+	p_info->AddFloat("wipeout_body_friction", m_body_wipeout_friction);
+	p_info->AddFloat("max_steering_angle", RADIANS_TO_DEGREES(m_max_steering_angle));
+	p_info->AddFloat("cornering_wheelbase", m_cornering_wheelbase);
+	p_info->AddFloat("cornering_axle_length", m_cornering_axle_length);
+	p_info->AddFloat("constant_rotational_damping", m_const_rotvel_damping);
+	p_info->AddFloat("quadratic_rotational_damping", m_quad_rotvel_damping);
+	
+	Script::CStruct* p_engine_info = new Script::CStruct;
+	p_engine_info->AddFloat("drive_torque", m_engine.drive_torque / 12.0f);
+	p_engine_info->AddFloat("drag_torque", m_engine.drag_torque / 12.0f);
+	p_engine_info->AddFloat("upshift_rpm", RADIANS_PER_SECOND_TO_RPM(m_engine.upshift_rotvel));
+	p_engine_info->AddFloat("differential_ratio", m_engine.differential_ratio);
+	p_engine_info->AddFloat("reverse_torque_ratio", m_engine.reverse_torque_ratio);
+	Script::CArray* p_engine_gear_ratio_info = new Script::CArray;
+	p_engine_gear_ratio_info->SetSizeAndType(m_engine.num_gears, ESYMBOLTYPE_FLOAT);
+	for (int n = m_engine.num_gears; n--; )
+	{
+		p_engine_gear_ratio_info->SetFloat(n, m_engine.p_gear_ratios[n]);
+	}
+	p_engine_info->AddArrayPointer("gear_ratios", p_engine_gear_ratio_info);
+	p_info->AddStructurePointer("m_engine", p_engine_info);
+	
+	Script::CArray* p_wheels_info = new Script::CArray;
+	p_wheels_info->SetSizeAndType(m_num_wheels, ESYMBOLTYPE_STRUCTURE);
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		Script::CStruct* p_local_info = new Script::CStruct;
+		
+		uint32 state_checksums[] =
+		{
+			CRCC(0xba2b31d7, "NO_STATE"),
+			CRCC(0xcd419ee6, "OUT_OF_CONTACT"),
+			CRCC(0x927c0fed, "UNDER_GRIPPING"),
+			CRCC(0xe9bc148a, "GRIPPING"),
+			CRCC(0x26acccc8, "SLIPPING"),
+			CRCC(0xa91003cc, "SKIDDING"),
+			CRCC(0x2e7ef449, "HANDBRAKE_THROTTLE"),
+			CRCC(0xbf6d6529, "HANDBRAKE_LOCKED"),
+		};
+		uint32 steering_checksums[] =
+		{
+			CRCC(0x613631cd, "FIXED"),
+			CRCC(0x85981897, "LEFT"),
+			CRCC(0x4b358aeb, "RIGHT")
+		};
+		p_local_info->AddChecksum("state", state_checksums[wheel.state]);
+		p_local_info->AddFloat("rotvel", wheel.rotvel);
+        p_local_info->AddFloat("y_offset", wheel.y_offset);
+        p_local_info->AddFloat("steering_angle", RADIANS_TO_DEGREES(wheel.steering_angle));
+        p_local_info->AddFloat("steering_angle_display", RADIANS_TO_DEGREES(wheel.steering_angle_display));
+        p_local_info->AddFloat("rotacc", wheel.rotacc);
+		p_local_info->AddFloat("orientation", wheel.orientation);
+		p_local_info->AddVector("pos", wheel.pos);
+		p_local_info->AddFloat("y_offset_hang", wheel.y_offset_hang);
+		p_local_info->AddFloat("max_draw_y_offset", wheel.max_draw_y_offset);
+		p_local_info->AddChecksum("steering", steering_checksums[wheel.steering]);
+		p_local_info->AddInteger("drive", wheel.drive);
+		p_local_info->AddFloat("radius", wheel.radius);
+		p_local_info->AddFloat("moment", vVP_GRAVITATIONAL_ACCELERATION / wheel.inv_moment);
+		p_local_info->AddFloat("spring_rate", wheel.spring);
+		p_local_info->AddFloat("damping_rate", wheel.damping);
+		p_local_info->AddFloat("static_friction", wheel.static_friction);
+		p_local_info->AddFloat("dynamic_friction", wheel.dynamic_friction);
+		p_local_info->AddFloat("handbrake_throttle_friction", wheel.handbrake_throttle_friction);
+		p_local_info->AddFloat("handbrake_locked_friction", wheel.handbrake_locked_friction);
+		p_local_info->AddFloat("min_static_grip_velocity", IPS_TO_MPH(wheel.min_static_velocity));
+		p_local_info->AddFloat("max_static_grip_velocity", IPS_TO_MPH(wheel.max_static_velocity));
+		p_local_info->AddFloat("min_dynamic_grip_velocity", IPS_TO_MPH(wheel.min_dynamic_velocity));
+		p_local_info->AddFloat("brake_torque", wheel.brake.torque / 12.0f);
+		p_local_info->AddFloat("handbrake_torque", wheel.brake.handbrake_torque / 12.0f);
+		
+		p_wheels_info->SetStructure(n, p_local_info);
+	}
+	p_info->AddArrayPointer("mp_wheels", p_wheels_info);
+	
+	Script::CStruct* p_controls_info = new Script::CStruct;
+	p_controls_info->AddFloat("steering", m_controls.steering);
+	p_controls_info->AddInteger("throttle", m_controls.throttle);
+	p_controls_info->AddInteger("brake", m_controls.brake);
+	p_controls_info->AddInteger("handbrake", m_controls.handbrake);
+	p_controls_info->AddInteger("reverse", m_controls.reverse);
+	p_info->AddStructurePointer("m_controls", p_controls_info);
+	
+	if (m_skater_visible)
+	{
+		p_info->AddVector(CRCD(0xec86ef7a, "skater_pos"), m_skater_pos);
+		p_info->AddChecksum(CRCD(0xda75a33e, "skater_anim"), m_skater_anim);
+	}
+
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::MoveToNode ( Script::CStruct* p_node )
+{
+	// move to the position relative to the given node that a ped car would be at
+	
+	Mth::Vector restart_pos;
+	SkateScript::GetPosition(p_node, &restart_pos);
+	
+	Mth::Vector restart_angles;
+	SkateScript::GetAngles(p_node, &restart_angles);
+	Mth::Matrix restart_matrix;
+	restart_matrix.SetFromAngles(restart_angles);
+	
+	// calculate appropriate offset from the ground based on estimated wheel offsets
+	float avg_ground_offset = 0.0f;
+	for (int n = vVP_NUM_WHEELS; n--; )
+	{
+		avg_ground_offset += mp_wheels[n].y_offset_hang + mp_wheels[n].pos[Y] + vVP_GRAVITATIONAL_ACCELERATION / (m_inv_mass * m_num_wheels * mp_wheels[n].spring);
+		avg_ground_offset -= mp_wheels[n].radius;
+	}
+	avg_ground_offset /= vVP_NUM_WHEELS;
+	
+	// find ground height
+	CFeeler feeler(restart_pos + Mth::Vector(0.0f, 24.0f, 0.0f), restart_pos + Mth::Vector(0.0f, -240.0f, 0.0f));
+	if (feeler.GetCollision(false))
+	{
+		restart_pos[Y] = feeler.GetPoint()[Y] - avg_ground_offset;
+	}
+	
+	// ped cars have their origin between the rear wheels
+	int rear_wheel_idx = -1;
+	for (int n = vVP_NUM_WHEELS; n--; )
+	{
+		if (mp_wheels[n].steering == SWheel::FIXED)
+		{
+			rear_wheel_idx = n;
+			break;
+		}
+	}
+	Dbg_Assert(rear_wheel_idx != -1);
+	restart_pos -= restart_matrix[Z] * mp_wheels[rear_wheel_idx].pos[Z];
+
+	// move the car to the restart position and allow it to settle on its suspension
+	
+	m_pos = restart_pos;
+	m_orientation = restart_matrix;
+	
+	m_mom.Set(0.0f, 0.0f, 0.0f);
+	m_rotmom.Set(0.0f, 0.0f, 0.0f);
+	
+	// zero wheels
+	
+	m_update_suspension_only = true;
+	for (int n = 60; n--; )
+	{
+		// lock wheels each frame
+		for (int n = m_num_wheels; n--; )
+		{
+			mp_wheels[n].rotvel = 0.0f;
+		}
+		
+		Update();
+	}
+	m_update_suspension_only = false;
+	
+	m_mom.Set(0.0f, 0.0f, 0.0f);
+	m_rotmom.Set(0.0f, 0.0f, 0.0f);
+	
+	m_reset_this_frame = true;
+	
+	m_state = ASLEEP;
+	
+	// update object's position and orientation
+	GetObject()->SetPos(m_pos);
+	GetObject()->SetVel(m_vel);
+	GetObject()->SetMatrix(m_orientation_matrix);
+	GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+	
+	update_skeleton();
+	
+	control_skater();
+	
+	GetObject()->SetTeleported();
+	mp_skater->SetTeleported();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::ApplyArtificialCollision ( const Mth::Vector& offset )
+{
+	Mth::Vector impulse_direction = offset;
+	impulse_direction[Y] = 0.0f;
+	impulse_direction.Normalize();
+	
+	Mth::Vector sideways(-impulse_direction[Z], 0.0f, impulse_direction[X]);
+	
+	float impulse_forward = (1.0f + Mth::PlusOrMinus(0.5f)) * Script::GetFloat(CRCD(0x3db3fa83, "vehicle_physics_netcoll_forward_impulse"));
+	float impulse_sideways = Mth::PlusOrMinus(1.5f) * Script::GetFloat(CRCD(0x39e99fec, "vehicle_physics_netcoll_sideways_impulse"));
+	float impulse_upwards = (1.0f + Mth::PlusOrMinus(0.2f)) * Script::GetFloat(CRCD(0x738500fd, "vehicle_physics_netcoll_upwards_impulse"));
+	
+	Mth::Vector impulse = impulse_forward * impulse_direction + impulse_sideways * sideways;
+	impulse[Y] += impulse_upwards;
+	
+	float rotate_spin = Mth::PlusOrMinus(1.5f) * Script::GetFloat(CRCD(0x14ddb3ef, "vehicle_physics_netcoll_spin_impulse"));
+	float rotate_flip = (1.0f + Mth::PlusOrMinus(0.8f)) * Script::GetFloat(CRCD(0x4088e6ec, "vehicle_physics_netcoll_flip_impulse"));
+	
+	Mth::Vector rotate = rotate_flip * sideways;
+	rotate[Y] += rotate_spin;
+	
+	m_mom += impulse / m_inv_mass;
+	m_rotmom += rotate / m_inv_moment_body[X];
+
+	update_velocities();
+	
+	m_artificial_collision_timer = Script::GetFloat(CRCD(0x771922a6, "vehicle_physics_artificial_collision_duration"));
+	
+	m_state = AWAKE;
+	m_consider_sleeping_count = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_flip (   )
+{
+	m_orientation.GetMatrix(m_orientation_matrix);
+	m_orientation_matrix[W].Set();
+	
+	if (m_orientation_matrix[Y][Y] > 0.999f) // || !m_controls.throttle)
+	{
+		m_in_flip = false;
+		return;
+	}
+	
+	bool flipping_appropriate_this_frame = m_vel.LengthSqr() < Mth::Sqr(300.0f)
+		&& m_rotvel.LengthSqr() < Mth::Sqr(Mth::PI / 4.0f)
+		&& m_max_normal_collision_impulse > 0.0f
+		&& m_orientation_matrix[Y][Y] < 0.25f
+		&& (m_num_wheels_in_contact == 0 || m_orientation_matrix[Y][Y] < 0.0f)
+		&& m_controls.throttle;
+
+	if (!m_in_flip && !flipping_appropriate_this_frame) return;
+	
+	// starting a flip
+	if (!m_in_flip)
+	{
+		m_mom.Set();
+		m_rotmom.Set();
+		
+		m_in_flip = true;
+		m_flip_start_time_stamp = Tmr::GetTime();
+	}
+	
+	// check for maximum flip duration
+	if (Tmr::ElapsedTime(m_flip_start_time_stamp) > vVP_FLIP_DURATION + vVP_FLIP_DELAY)
+	{
+		m_in_flip = false;
+		return;
+	}
+	
+	// put a short delay before the flip actually has an effect
+	if (Tmr::ElapsedTime(m_flip_start_time_stamp) < vVP_FLIP_DELAY)
+	{
+		return;
+	}
+	
+	// setup this frame's slerp
+	Mth::Matrix goal_orientation;
+	goal_orientation[Z] = m_orientation_matrix[Z];
+	goal_orientation[Z][Y] = 0.0f;
+	goal_orientation[Z].Normalize();
+	goal_orientation[Y].Set(0.0f, 1.0f, 0.0f);
+	goal_orientation[X].Set(goal_orientation[Z][Z], 0.0f, -goal_orientation[Z][X]);
+	Mth::SlerpInterpolator slerper;
+	slerper.setMatrices(&m_orientation_matrix, &goal_orientation);
+	
+	// calculate the new orientation matrix
+	slerper.getMatrix(
+		&m_orientation_matrix,
+		Mth::ClampMax(vVP_FLIP_RATE * Tmr::FrameLength() / slerper.getRadians(), 1.0f)
+	);
+	m_orientation = m_orientation_matrix;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_wheel_from_structure ( SWheel& wheel, Script::CStruct* p_wheel_struct )
+{
+	uint32 checksum;
+	
+	// wheel's steering type
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0x7560e63, "steering")))
+	{
+		p_wheel_struct->GetChecksum(CRCD(0x7560e63, "steering"), &checksum);
+		switch (checksum)
+		{
+			case CRCC(0x85981897, "left"):
+				wheel.steering = SWheel::LEFT;
+				break;
+			case CRCC(0x4b358aeb, "right"):
+				wheel.steering = SWheel::RIGHT;
+				break;
+			case CRCC(0x613631cd, "fixed"):
+				wheel.steering = SWheel::FIXED;
+				break;
+			default:
+				Dbg_MsgAssert(false, ("Bad wheel steering setting"));
+		}
+	}
+
+	// if the wheel is a drive wheel
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0x97e20a70, "drive")))
+	{
+		p_wheel_struct->GetChecksum(CRCD(0x97e20a70, "drive"), &checksum);
+		switch (checksum)
+		{
+			case CRCC(0x8a18ca56, "yes"):
+				wheel.drive = true;
+				break;
+			case CRCC(0x9855d7e0, "no"):
+				wheel.drive = false;
+				break;
+			default:
+				Dbg_MsgAssert(false, ("Bad wheel drive setting"));
+		}
+	}
+
+	// suspension spring rate (lbs / in)
+	// strength of the suspension spring; multiplied by the compresion to get the spring's force; must be scaled
+	// with the vehicle weight; large spring rates give stiffer suspensions; large values may cause instability in
+	// physics model
+	p_wheel_struct->GetFloat(CRCD(0x9e931fdf, "spring_rate"), &wheel.spring);
+
+	// suspension damping; bump rate (lbs s / in)
+	// bounce damping strength of the suspension; smaller values cause bouncier suspensions; very large values may
+	// cause instability in the physics model
+	p_wheel_struct->GetFloat(CRCD(0xafa960ff, "damping_rate"), &wheel.damping);
+
+	// wheel radius (in)
+	// larger radius wheels are harder for the engine to rotate and the brake and rolling resistance to stop
+	p_wheel_struct->GetFloat(CRCD(0xc48391a5, "radius"), &wheel.radius);
+
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0xca73775d, "moment")))
+	{
+		p_wheel_struct->GetFloat(CRCD(0xca73775d, "moment"), &wheel.inv_moment);
+		wheel.inv_moment = vVP_GRAVITATIONAL_ACCELERATION / wheel.inv_moment;
+	}
+	
+	p_wheel_struct->GetFloat(CRCD(0xd87d0183, "max_draw_y_offset"), &wheel.max_draw_y_offset);
+
+	// the following parameters affect the shape of the tire's friction curve
+	// the friction curve gives the strength of the frictional force as a function of the tire's slip velocity; our
+	// simple curve rises linearly from zero at zero slip velocity, reaches static_friction at a slip velocity of
+	// min_static_velocity, remains at static_friction up to a slip velocity of max_static_velocity, drops linearly
+	// to dynamic_friction until a slip velocity of min_dynamic_velocity, and then remains constant at dynamic_friction
+
+	// normalized friction strength when the wheel is not skidding
+	// low values will cause the vehicle to be spin-out and skid easily; high values cause the tires to feel sticky;
+	// real world values are on the order of 1.3; fun values are much higher
+	p_wheel_struct->GetFloat(CRCD(0xf36b97c8, "static_friction"), &wheel.static_friction);
+
+	// normalized friction strength when the wheel is skidding
+	// should be lower than static_friction; values much lower than static_friction cause the vehicle to be unforgiving;
+	// once you lose grip, it is harder to regain control of the vehicle; values near static_friction cause the vehicle
+	// to be very forgiving, with skidding causing little loss of handling 
+	p_wheel_struct->GetFloat(CRCD(0x674852f6, "dynamic_friction"), &wheel.dynamic_friction);
+	
+	// normalized friction strength when the handbrake is applied
+	// currently specially tuned in script to force a fishtail when the handbrake and throttle are both applied and spin-outs when only handbrake is applied
+	p_wheel_struct->GetFloat(CRCD(0x52242919, "handbrake_throttle_friction"), &wheel.handbrake_throttle_friction);
+	p_wheel_struct->GetFloat(CRCD(0x6d615f67, "handbrake_locked_friction"), &wheel.handbrake_locked_friction);
+
+	// slip speed below which tires undergrip (mph)
+	// should be around 1 to 2 mph
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0x60b2c097, "min_static_grip_velocity")))
+	{
+		p_wheel_struct->GetFloat(CRCD(0x60b2c097, "min_static_grip_velocity"), &wheel.min_static_velocity);
+		wheel.min_static_velocity = MPH_TO_IPS(wheel.min_static_velocity);
+	}
+
+	// maximum slip speed before tires begin to lose their grip (mph)
+	// should be larger than min_static_velocity; with a low value the vehicle will skid more readily; a high value
+	// causes there to be a large range of slip velocties with maximum tire grip
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0xbb0d9370, "max_static_grip_velocity")))
+	{
+		p_wheel_struct->GetFloat(CRCD(0xbb0d9370, "max_static_grip_velocity"), &wheel.max_static_velocity);
+		wheel.max_static_velocity = MPH_TO_IPS(wheel.max_static_velocity);
+	}
+
+	// minimum slip speed before tires begin skidding (imph)
+	// should be larger than max_static_velocity; with a low values, tires are very unforgiving and begin to skid as soon
+	// as they begin to slip; with a larger value, the loss of control and onset of skidding will be more gradual
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0xb71803ad, "min_dynamic_grip_velocity")))
+	{
+		p_wheel_struct->GetFloat(CRCD(0xb71803ad, "min_dynamic_grip_velocity"), &wheel.min_dynamic_velocity);
+		wheel.min_dynamic_velocity = MPH_TO_IPS(wheel.min_dynamic_velocity);
+	}
+
+	// torque which the brake exerts on the wheel (ft-lbs)
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0x3bccbadc, "brake_torque")))
+	{
+		p_wheel_struct->GetFloat(CRCD(0x3bccbadc, "brake_torque"), &wheel.brake.torque);
+		wheel.brake.torque *= 12.0f;
+	}
+
+	// torque which the handbrake exerts on the wheel when steering is straight and the throttle is down (ft-lbs)
+	if (p_wheel_struct->ContainsComponentNamed(CRCD(0x9439d144, "handbrake_torque")))
+	{
+		p_wheel_struct->GetFloat(CRCD(0x9439d144, "handbrake_torque"), &wheel.brake.handbrake_torque);
+		wheel.brake.handbrake_torque *= 12.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_dynamic_state (   )
+{
+	// update body state
+	
+	// calculate accelerations
+	
+	Mth::Vector acc = m_inv_mass * m_force;
+	Mth::Vector rotacc = m_inv_moment.Rotate(m_torque);
+	
+	// update state variables
+	
+	m_mom += m_time_step * m_force;
+	update_pos_with_uber_frig(m_time_step * (m_vel + 0.5f * m_time_step * acc));
+	
+	Mth::Vector delta_orientation_vector = -0.5f * m_time_step * (m_rotvel + 0.5f * m_time_step * rotacc);
+	Mth::Quat delta_orientation = m_orientation * Mth::Quat(delta_orientation_vector[X], delta_orientation_vector[Y], delta_orientation_vector[Z], 0.0f);
+	m_orientation += delta_orientation;
+	m_orientation.Normalize();
+
+    m_rotmom += m_time_step * m_torque;
+	
+	update_wheel_dynamic_state();
+	
+    // if we're in the air and moving fast enough horizontally
+	if (m_num_wheels_in_contact == 0 && m_max_normal_collision_impulse == 0.0f)
+	{
+		slerp_to_face_velocity();
+	}
+	
+	update_flip();
+    
+	update_dependent_variables();
+	
+#if 0
+	PERIODIC(60) {
+		float K = 0.5f * Mth::DotProduct(m_vel, m_mom) + 0.5f * Mth::DotProduct(m_rotvel, m_rotmom);
+		float U = vVP_GRAVITATIONAL_ACCELERATION * m_pos[Y] / m_inv_mass;
+		DUMPF(K + U);
+		Mth::Vector a, b;
+		a = m_rotmom;
+		b = m_rotvel;
+		DUMPF(Mth::DotProduct(a.Normalize(), b.Normalize()));
+		// DUMPF(m_rotvel.Length());
+		// DUMPV(m_rotvel);
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_wheel_dynamic_state (   )
+{
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		// wheel rotvel updates as torques are applied; thus, wheel rotvel is currently the frame's final rotvel 
+		wheel.orientation += m_time_step * (wheel.rotvel - 0.5f * m_time_step * wheel.rotacc);
+		
+		if (wheel.orientation > (200.0f * Mth::PI))
+		{
+			wheel.orientation -= (100.0f * Mth::PI);
+		}
+		else if (wheel.orientation < 0.0f)
+		{
+			wheel.orientation += (100.0f * Mth::PI);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_dependent_variables (   )
+{
+	m_orientation.GetMatrix(m_orientation_matrix);
+	m_orientation_matrix[W].Set();
+	
+	m_suspension_center_of_mass_world = m_orientation_matrix.Rotate(m_suspension_center_of_mass);
+	
+	calculate_inverse_moment();
+	
+	update_velocities();
+	
+	// update wheel feeler endpoint positions
+	for	(int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		wheel.feeler_start_world = m_pos + m_orientation_matrix.Rotate(wheel.pos);
+		wheel.feeler_end_world = wheel.pos;
+		wheel.feeler_end_world[Y] += wheel.y_offset_hang - wheel.radius;
+		wheel.feeler_end_world = m_pos + m_orientation_matrix.Rotate(wheel.feeler_end_world);
+	}
+	
+	// update collider world positions
+	for (int collider_idx = vVP_NUM_COLLIDERS; collider_idx--; )
+	{
+		SCollider& collider = mp_colliders[collider_idx];
+		
+		collider.world.m_corner = m_pos + m_orientation_matrix.Rotate(collider.body.m_corner);
+		collider.world.m_first_edge = m_orientation_matrix.Rotate(collider.body.m_first_edge);
+        collider.world.m_second_edge = m_orientation_matrix.Rotate(collider.body.m_second_edge);
+		
+		collider.first_edge_direction_world = collider.world.m_first_edge / collider.first_edge_length;
+		collider.second_edge_direction_world = collider.world.m_second_edge / collider.second_edge_length;
+	}
+	
+	update_collision_cache();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CVehicleComponent::calculate_friction_coefficient ( SWheel& wheel, float velocity ) const
+{
+	// friction based on section 2.4 and 2.7 of Race Car Vehicle Dynamics by Milliken
+	
+	// the tire friction coefficient curve is very simple; just four concatenated lines
+	
+	if (m_controls.handbrake)
+	{
+		// handbrake friction is a total scam
+		
+		float friction;
+		if (m_controls.throttle)
+		{
+			friction = wheel.handbrake_throttle_friction;
+			wheel.state = SWheel::HANDBRAKE_THROTTLE;
+		}
+		else
+		{
+			friction = wheel.handbrake_locked_friction;
+			wheel.state = SWheel::HANDBRAKE_LOCKED;
+		}
+		
+		if (velocity < wheel.min_static_velocity)
+		{
+			// under gripped handbrake "skidding"
+			wheel.state = SWheel::UNDER_GRIPPING;
+			return friction * velocity / wheel.min_static_velocity;
+		}
+		else
+		{
+			// handbrake "skidding"
+			return friction;
+		}
+	}
+	
+	float multiplier = m_controls.brake ? 2.0f : 1.0f;
+	
+	if (velocity < wheel.min_static_velocity)
+	{
+		// under gripped
+		wheel.state = SWheel::UNDER_GRIPPING;
+		return wheel.static_friction * velocity / wheel.min_static_velocity;
+	}
+	else if (velocity < wheel.max_static_velocity)
+	{
+		// maximum grip
+		wheel.state = SWheel::GRIPPING;
+		return multiplier * wheel.static_friction;
+	}
+	else if (velocity < wheel.min_dynamic_velocity)
+	{
+		// on the verge of skidding
+		wheel.state = SWheel::SLIPPING;
+		return multiplier * wheel.dynamic_friction + (wheel.static_friction - wheel.dynamic_friction)
+			* (velocity - wheel.max_static_velocity) / (wheel.min_dynamic_velocity - wheel.max_static_velocity);
+	}
+	else
+	{
+		// skidding
+		wheel.state = SWheel::SKIDDING;
+		return multiplier * wheel.dynamic_friction;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::calculate_friction_coefficients (   )
+{
+	// calculate the wheels' friction coefficients this frame
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		if (wheel.state == SWheel::OUT_OF_CONTACT) continue;
+		
+		// project the wheel's velocity due to the body's motion into the plane of the contact surface
+		Mth::Vector projected_vel = wheel.vel_world;
+		projected_vel.ProjectToPlane(wheel.contact_normal);
+	
+		// calculate the forward direction of the wheel
+		Mth::Vector wheel_direction(sinf(wheel.steering_angle), 0.0f, cosf(wheel.steering_angle));
+		wheel_direction = m_orientation_matrix.Rotate(wheel_direction);
+		
+		// project that forward direction onto the contact surface
+		wheel_direction.ProjectToPlane(wheel.contact_normal);
+		wheel_direction.Normalize();
+		
+		// NOTE: from this point and up, the calculation is repeated exactly in apply_friction_forces(); we cache the rotated and projected
+		// wheel direction and the wheel velocity due to body velocity; because the wheel velocity will change before apply_friction_forces(),
+		// the calculation below must be repeated
+		
+		wheel.cache_projected_direction = wheel_direction;
+		wheel.cache_projected_vel = projected_vel;
+	
+		// subtract the contact patch velocity; positive wheel rotational velocity equals negative contact patch velocity
+		projected_vel -= wheel.rotvel * wheel.radius * wheel_direction;
+	
+		// calculate the friction coefficient
+		wheel.friction_coefficient = calculate_friction_coefficient(wheel, projected_vel.Length());
+	}
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::calculate_inverse_moment (   )
+{
+	// m_inv_moment = m_orientation_matrix * m_inv_moment_body_diag_matrix * m_orientation_matrix_transpose
+	
+	m_inv_moment[X][X] = m_inv_moment_body[X] * m_orientation_matrix[X][X];
+	m_inv_moment[X][Y] = m_inv_moment_body[Y] * m_orientation_matrix[X][Y];
+	m_inv_moment[X][Z] = m_inv_moment_body[Z] * m_orientation_matrix[X][Z];
+	m_inv_moment[Y][X] = m_inv_moment_body[X] * m_orientation_matrix[Y][X];
+	m_inv_moment[Y][Y] = m_inv_moment_body[Y] * m_orientation_matrix[Y][Y];
+	m_inv_moment[Y][Z] = m_inv_moment_body[Z] * m_orientation_matrix[Y][Z];
+	m_inv_moment[Z][X] = m_inv_moment_body[X] * m_orientation_matrix[Z][X];
+	m_inv_moment[Z][Y] = m_inv_moment_body[Y] * m_orientation_matrix[Z][Y];
+	m_inv_moment[Z][Z] = m_inv_moment_body[Z] * m_orientation_matrix[Z][Z];
+	
+	Mth::Matrix orientation_matrix_transpose;
+	orientation_matrix_transpose.Transpose(m_orientation_matrix);
+	m_inv_moment = m_inv_moment * orientation_matrix_transpose;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CVehicleComponent::determine_effective_gear ( float wheel_rotvel )
+{
+	int gear = 0;
+	
+	do
+	{
+		float engine_rotvel = wheel_rotvel * m_engine.differential_ratio * m_engine.p_gear_ratios[gear];
+		
+		if (engine_rotvel > m_engine.upshift_rotvel)
+		{
+			if (gear < m_engine.num_gears - 1)
+			{
+				gear++;
+				continue;
+			}
+		}
+		return gear;
+	}
+	while (true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_collision_cache (   )
+{
+	Mth::CBBox collision_bbox;
+	
+	// add wheel location feelers
+	for	(int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		collision_bbox.AddPoint(wheel.feeler_start_world);
+		collision_bbox.AddPoint(wheel.feeler_end_world);
+	}
+	
+	// add body collision feelers
+	for (int collider_idx = vVP_NUM_COLLIDERS; collider_idx--; )
+	{
+		SCollider& collider = mp_colliders[collider_idx];
+		
+        collision_bbox.AddPoint(collider.world.m_corner);
+		collision_bbox.AddPoint(collider.world.m_corner + collider.world.m_first_edge);
+		collision_bbox.AddPoint(collider.world.m_corner + collider.world.m_second_edge);
+		collision_bbox.AddPoint(collider.world.m_corner + collider.world.m_first_edge + collider.world.m_second_edge);
+	}
+	
+	m_collision_cache.Update(collision_bbox);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_wheel_heights (   )
+{
+	// We crap out and don't do real wheel dynamics.  Instead, if the ground is within a wheel's hang point, we teleport the wheel to the
+	// ground.  If not, we teleport the wheel to its hang point.
+	
+	CFeeler feeler;
+	
+	feeler.SetCache(&m_collision_cache);
+	
+	m_num_wheels_in_contact = 0;
+	for	(int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		// y_offset a feeler from the top of the wheel's y_offset to the wheel's hang point plus its radius
+		feeler.m_start = wheel.feeler_start_world;
+		feeler.m_end = wheel.feeler_end_world;
+		feeler.SetIgnore(mFD_NON_COLLIDABLE, 0);
+		
+		if (feeler.GetCollision(false))
+		{
+			// trip any triggers
+			if (wheel.state != SWheel::OUT_OF_CONTACT)
+			{
+				if (feeler.GetSector() != wheel.last_ground_feeler.GetSector())
+				{
+					trip_trigger(TRIGGER_SKATE_OFF, wheel.last_ground_feeler);
+					if (reset_this_frame()) return;
+					trip_trigger(TRIGGER_SKATE_ONTO, feeler);
+					if (reset_this_frame()) return;
+				}
+			}
+			else
+			{
+				trip_trigger(TRIGGER_SKATE_ONTO, feeler);
+				if (reset_this_frame()) return;
+			}
+			
+			wheel.last_ground_feeler = feeler;
+			
+			wheel.state = SWheel::NO_STATE;
+			wheel.contact_normal = feeler.GetNormal();
+			wheel.y_offset = feeler.GetDist() * (wheel.y_offset_hang - wheel.radius) + wheel.radius;
+			
+			m_num_wheels_in_contact++;
+			
+			feeler.m_end = feeler.GetPoint();
+		}
+		else
+		{
+			// trip any triggers
+			if (wheel.state != SWheel::OUT_OF_CONTACT)
+			{
+				trip_trigger(TRIGGER_SKATE_OFF, wheel.last_ground_feeler);
+				if (reset_this_frame()) return;
+			}
+			
+			wheel.state = SWheel::OUT_OF_CONTACT;
+			wheel.y_offset = wheel.y_offset_hang;
+		}
+		
+		// Allows CAP kill planes to work on the car.
+		if (Ed::CParkEditor::Instance()->UsingCustomPark())
+		{
+			// now check for non-collidable trigger polys
+			feeler.SetIgnore(0, mFD_NON_COLLIDABLE | mFD_TRIGGER);
+			if (feeler.GetCollision(false))
+			{
+				trip_trigger(TRIGGER_BONK, feeler);
+				if (reset_this_frame()) return;
+			}
+		}
+		
+	} // END loop over wheels
+	
+	// update the wheels' dependent variables which depend of the wheel heights
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		wheel.pos_world = wheel.pos;
+		wheel.pos_world[Y] += wheel.y_offset - wheel.radius;
+		wheel.pos_world = m_orientation_matrix.Rotate(wheel.pos_world);
+		
+		wheel.vel_world = calculate_body_point_velocity(wheel.pos_world);
+	} // END loop over wheels
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_steering_angles (   )
+{
+	// NOTE: could easily use a table here
+	
+	m_steering_display = Mth::Lerp(m_steering_display, m_controls.steering, 10.0f * m_time_step);
+	
+	float left_steering_angle;
+	float right_steering_angle;
+	float left_steering_angle_display;
+	float right_steering_angle_display;
+	
+	if (Mth::Abs(m_controls.steering) > 0.001f)
+	{
+		float turning_radius = m_cornering_wheelbase / tanf(m_controls.steering * m_max_steering_angle);
+		 
+		if (turning_radius > 0.0f)
+		{
+			left_steering_angle = -atan2f(m_cornering_wheelbase, turning_radius - m_cornering_axle_length / 2.0f);
+			right_steering_angle = -atan2f(m_cornering_wheelbase, turning_radius + m_cornering_axle_length / 2.0f);
+		}
+		else
+		{
+			left_steering_angle = atan2f(m_cornering_wheelbase, -turning_radius + m_cornering_axle_length / 2.0f);
+			right_steering_angle = atan2f(m_cornering_wheelbase, -turning_radius - m_cornering_axle_length / 2.0f);
+		}
+	}
+	else
+	{
+		left_steering_angle = right_steering_angle = 0.0f;
+	}
+		
+	if (Mth::Abs(m_steering_display) > 0.001f)
+	{
+		float turning_radius_display = m_cornering_wheelbase / tanf(m_steering_display * m_max_steering_angle);
+		 
+		if (turning_radius_display > 0.0f)
+		{
+			left_steering_angle_display = -atan2f(m_cornering_wheelbase, turning_radius_display - m_cornering_axle_length / 2.0f);
+			right_steering_angle_display = -atan2f(m_cornering_wheelbase, turning_radius_display + m_cornering_axle_length / 2.0f);
+		}
+		else
+		{
+			left_steering_angle_display = atan2f(m_cornering_wheelbase, -turning_radius_display + m_cornering_axle_length / 2.0f);
+			right_steering_angle_display = atan2f(m_cornering_wheelbase, -turning_radius_display - m_cornering_axle_length / 2.0f);
+		}
+	}
+	else
+	{
+		left_steering_angle_display = right_steering_angle_display = 0.0f;
+	}
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		if (wheel.steering == SWheel::FIXED) continue;
+		
+        wheel.steering_angle = (wheel.steering == SWheel::LEFT ? left_steering_angle : right_steering_angle);
+        wheel.steering_angle_display = (wheel.steering == SWheel::LEFT ? left_steering_angle_display : right_steering_angle_display);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::damp_rotation (   )
+{
+	// no damping when handbraking on the ground
+	if (m_controls.handbrake && m_num_wheels_in_contact > 2) return;
+	
+	if (!m_controls.throttle && m_controls.steering != 0.0f && m_num_wheels_in_contact > 2) return;
+	
+	if (in_artificial_collision()) return;
+	
+	// apply quadtratic damping
+	
+	// quadratic damping in the air makes jump recovery much easier; the vehicle lands on its wheels much more often
+	float quad_damping = m_time_step * m_quad_rotvel_damping * m_rotmom.LengthSqr();
+	
+	// prevent reversal
+	if (quad_damping > m_rotmom.Length())
+	{
+		m_rotmom.Set(0.0f, 0.0f, 0.0f);
+	}
+	else
+	{
+		Mth::Vector direction = m_rotmom;
+		direction.Normalize();
+		m_rotmom -= quad_damping * direction;
+	}
+
+	// apply constant damping; only applied in the Y direction
+	
+	// apply only if we're not steering, we're on the ground, and we're relatively upright
+	if (m_controls.steering == 0.0f && m_num_wheels_in_contact > 2 && m_orientation_matrix[Y][Y] > 0.5f)
+	{
+		float const_damping = m_time_step * m_const_rotvel_damping;;
+		
+		// prevent reversal
+		if (Mth::Abs(const_damping) > Mth::Abs(m_rotmom[Y]))
+		{
+			m_rotmom[Y] = 0.0f;
+		}
+		else
+		{
+			m_rotmom[Y] += (m_rotmom[Y] > 0.0f ? -const_damping : const_damping);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::accumulate_forces (   )
+{
+	// forces and torques on the body accumulate and are applied at the end of the frame; torques on the wheels are too large to accumulate;
+	// they are applied to wheel velocity as they occur; thus, the order of application of wheel torques is critical
+	
+	apply_gravitational_forces();
+	
+	apply_suspension_forces();
+	
+    // we calculate the friction coefficients before applying engine torques so that friction will get the chance to counteract rotational
+	// accelerations using static friction before the wheels spin out due to engine torque
+	calculate_friction_coefficients();
+	
+	if (!m_controls.handbrake)
+	{
+		apply_engine_forces();
+		
+		apply_drag_forces();
+		
+		// give the brakes a chance to cancel the rotation due to engine torque and to lock up the wheels before friction effects
+		apply_brake_forces();
+		
+		apply_friction_forces();
+		
+		// after friction, the brakes are given the opportunity to apply additional torque and relock the wheels
+		apply_spare_brake_forces();
+	}
+	else
+	{
+		if (m_controls.throttle)
+		{
+			apply_engine_forces();
+			
+            // scale the handbrake's braking effect with the steering factor; thus, you power through sharp handbrake turns; yet, the handbrake
+			// still brakes on straight aways
+			apply_handbrake_forces(1.0f - Mth::Abs(m_controls.steering));
+
+			apply_friction_forces();
+
+			apply_spare_brake_forces();
+		}
+		else
+		{
+			apply_friction_forces();
+			
+			// lock wheels
+			for (int n = m_num_wheels; n--; )
+			{
+				mp_wheels[n].rotvel = 0.0f;
+				mp_wheels[n].rotacc = 0.0f;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_gravitational_forces (   )
+{
+	// use reduced gravity when flipping the car
+	if (m_in_flip && Tmr::ElapsedTime(m_flip_start_time_stamp) > vVP_FLIP_DELAY)
+	{
+		m_force[Y] -= vVP_FLIP_GRAVITY_FACTOR * m_gravity_override_fraction * vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass;
+	}
+	else
+	{
+		m_force[Y] -= m_gravity_override_fraction * vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass;
+	}
+	
+	// we do our own force accumulation for this simple and constant force, having the same effect as the following code
+	// Mth::Vector force(0.0f, -vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass, 0.0f);
+	// Mth::Vector location = m_suspension_center_of_mass;
+	// accumulate_force(force, location, MAKE_RGB(50, 50, 0));
+	
+	if (m_draw_debug_lines)
+	{
+		Gfx::AddDebugLine(m_pos, m_pos + 0.05f * Mth::Vector(0.0f, -vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass, 0.0f), MAKE_RGB(50, 50, 0), MAKE_RGB(50, 50, 0), 1);
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_suspension_forces (   )
+{
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		// if not in contact with the ground, y_offset == y_offset_hang
+		if (wheel.state == SWheel::OUT_OF_CONTACT)
+		{
+			// set this frame's history to the default value to smooth out landings
+			wheel.normal_force_history[m_next_normal_force_history_idx] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass / m_num_wheels;
+			continue;
+		}
+		
+		// direction of the suspension line; commented version is slower but explicit
+		// Mth::Vector direction = m_orientation_matrix.Rotate(Mth::Vector(0.0f, 1.0f, 0.0f));
+		Mth::Vector direction = m_orientation_matrix[Y];
+		
+		// magnitude of the spring force along the contact normal
+		float magnitude = -wheel.spring * (wheel.y_offset_hang - wheel.y_offset) * Mth::DotProduct(wheel.contact_normal, direction);
+		
+		// velocity of the wheel projected into the contact normal direction
+		float projected_vel = Mth::DotProduct(wheel.vel_world, wheel.contact_normal);
+		
+		// magnitude adjusted for damping
+		magnitude += -wheel.damping * projected_vel;
+		
+		// suspension force
+		Mth::Vector force = magnitude * wheel.contact_normal;
+		
+		accumulate_force(force, wheel.pos_world, MAKE_RGB(100, 100, 0));
+		
+		// set the normal force magnitude; used in the friction force code
+		
+		wheel.normal_force_history[m_next_normal_force_history_idx] = magnitude;
+		
+		wheel.normal_force = 0.0f;
+		for (int i = vVP_NORMAL_FORCE_HISTORY_LENGTH; i--; )
+		{
+			wheel.normal_force += wheel.normal_force_history[i];
+		}
+		wheel.normal_force /= vVP_NORMAL_FORCE_HISTORY_LENGTH;
+	}
+	
+	m_next_normal_force_history_idx = ++m_next_normal_force_history_idx % vVP_NORMAL_FORCE_HISTORY_LENGTH;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_friction_forces (   )
+{
+	// NOTE: we've handled longitudinal friction reversal successfully; however, tangential friction reversal is still a significant issue for large
+	// friction coefficients and high centers of mass
+	
+	// we use a simple tire model were the friction force is opposite the velocity of the tire contact and proportional to the normal force and a
+	// friction coefficient; the friction coefficient is a function of the velocity of the contact with the following form:
+	// 0 < v < min_static_velocity: rises linearly from zero to static_friction
+	// min_static_velocity < v < max_static_velocity: constant at static_friction
+	// max_static_velocity < v < min_dynamic_velocity: falls linearly from static_friction to dynamic_friction
+	// min_dynamic_velocity < v: constant at dynamic_friction
+	
+	// NOTE: because friction is zero at zero slip velocity, hill slippage is an issue
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		if (wheel.state == SWheel::OUT_OF_CONTACT) continue;
+		
+		// grab the cached wheel direction and velocity projected into the contact plane
+		Mth::Vector projected_wheel_direction = wheel.cache_projected_direction;
+		Mth::Vector projected_vel = wheel.cache_projected_vel;
+		
+		// subtract the contact patch velocity; positive wheel rotational velocity equals negative contact patch velocity
+		projected_vel -= wheel.rotvel * wheel.radius * projected_wheel_direction;
+		
+		// store the slip vel for later display
+		wheel.slip_vel = projected_vel.Length();
+		
+		// if the velocity is too small, its direction is meaningless
+		if (wheel.slip_vel < 0.000001) continue;
+		
+		// propose a frictional force
+		Mth::Vector vel_direction = projected_vel;
+		vel_direction.Normalize();
+		Mth::Vector proposed_force = -wheel.friction_coefficient * wheel.normal_force * vel_direction;
+		
+		// now we must check that this force is not more that the force which would stop the relative longitudinal velocity
+		
+		// calculate the torque required to stick the wheel contact patch to the ground
+		float longitudinal_vel = Mth::DotProduct(projected_vel, projected_wheel_direction);
+		float stopping_rotvel = longitudinal_vel / wheel.radius;
+		float stopping_torque = -calculate_stopping_torque(wheel, stopping_rotvel);
+		
+		// calculate the proposed torque
+		float proposed_torque = -wheel.radius * Mth::DotProduct(proposed_force, projected_wheel_direction);
+		
+		Mth::Vector force;
+		float torque;
+		
+		// if the proposed torque is less than what is required to stop the rotation
+		if (Mth::Abs(proposed_torque) < Mth::Abs(stopping_torque))
+		{
+			// we're ok
+			force = proposed_force;
+			torque = proposed_torque;
+		}
+		else
+		{
+			// otherwise, reduce the proposed longitudinal force to only the stopping force
+			float proposed_longitudinal_force = Mth::DotProduct(proposed_force, projected_wheel_direction);
+			float stopping_longitudinal_force = -stopping_torque / wheel.radius;
+			force = proposed_force + (stopping_longitudinal_force - proposed_longitudinal_force) * projected_wheel_direction;
+			
+			torque = stopping_torque;
+		}
+		
+		// apply the force to the body
+		accumulate_force(force, wheel.pos_world, MAKE_RGB(0, 0, 255));
+		
+		// apply the torque to the wheel
+		apply_wheel_torque(wheel, torque);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_drag_forces (   )
+{
+	if (m_controls.throttle || m_controls.reverse || m_controls.brake) return;
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		if (!wheel.drive || wheel.state == SWheel::OUT_OF_CONTACT) continue;
+		
+		// determine the effective engine gear
+		int effective_gear = determine_effective_gear(Mth::Abs(wheel.rotvel));
+		
+		// drag torque scales like the square of the gear ratio
+		float torque = m_engine.drag_torque * m_engine.p_gear_ratios[effective_gear] * m_engine.p_gear_ratios[effective_gear] / m_num_drive_wheels;
+		
+		// prevent reversal
+		float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
+		if (torque > maximum_torque)
+		{
+			torque = maximum_torque;
+		}
+		
+		apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_engine_forces (   )
+{
+	// NOTE: test
+	float wall_climb_factor = Mth::ClampMax(m_orientation_matrix[Y][Y] * m_orientation_matrix[Y][Y] / 0.9f, 1.0f);
+	
+	if ((!m_controls.throttle && !m_controls.reverse) || m_controls.brake) return;
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		if (!wheel.drive) continue;
+		
+		// turn off power to front wheels when power handbraking
+		if (m_controls.handbrake && wheel.steering != SWheel::FIXED) continue;
+		
+		// determine effective engine gear
+		int effective_gear;
+		if (!m_controls.reverse)
+		{
+			effective_gear = determine_effective_gear(wheel.rotvel > 0.0f ? wheel.rotvel : 0.0f);
+		}
+		else
+		{
+			effective_gear = determine_effective_gear(wheel.rotvel < 0.0f ? -wheel.rotvel : 0.0f);
+		}
+		
+		float torque = m_engine.drive_torque * m_engine.differential_ratio * m_engine.p_gear_ratios[effective_gear] / m_num_drive_wheels;
+		torque *= wall_climb_factor;
+		
+		// reverse gears are scaled down by a factor
+		if (m_controls.reverse)
+		{
+			torque *= -m_engine.reverse_torque_ratio;
+		}
+		
+		// account for low of power in front wheels in four-wheel drive car when power handbraking
+		if (m_controls.handbrake && m_num_drive_wheels == 4) torque *= 2.0f;
+		
+		apply_wheel_torque(wheel, torque);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_brake_forces (   )
+{
+	if (!m_controls.brake) return;
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		float torque = wheel.brake.torque;
+		
+		float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
+		
+		if (torque > maximum_torque)
+		{
+			wheel.brake.spare_torque = torque - maximum_torque;
+			torque = maximum_torque;
+		}
+		else
+		{
+			wheel.brake.spare_torque = 0.0f;
+		}
+		
+		apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_handbrake_forces ( float application_factor )
+{
+	Dbg_Assert(m_controls.handbrake);
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		float torque = application_factor * wheel.brake.handbrake_torque;
+		
+		float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
+		
+		if (torque > maximum_torque)
+		{
+			wheel.brake.spare_torque = torque - maximum_torque;
+			torque = maximum_torque;
+		}
+		else
+		{
+			wheel.brake.spare_torque = 0.0f;
+		}
+		
+		apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
+	}
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_spare_brake_forces (   )
+{
+	if (!m_controls.brake) return;
+	
+	for (int n = m_num_wheels; n--; )
+	{
+		SWheel& wheel = mp_wheels[n];
+		
+		if (wheel.brake.spare_torque == 0.0f) continue;
+		
+		float torque = wheel.brake.spare_torque;
+		
+		float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
+		
+		if (torque > maximum_torque)
+		{
+			torque = maximum_torque;
+		}
+		
+		apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+			
+float CVehicleComponent::calculate_collision_depth ( const SCollisionPoint& collision_point, const SCollider& collider ) const
+{
+	enum EDepthDirection
+	{
+		WITH_FIRST_EDGE, AGAINST_FIRST_EDGE, WITH_SECOND_EDGE, AGAINST_SECOND_EDGE
+	};
+	
+	// calculate displacement from base corner
+	Mth::Vector displacement = collision_point.pos - collider.world.m_corner;
+	
+	// determine the minimum depth and correponding depth direction
+	
+	float min_depth = Mth::DotProduct(displacement, collider.first_edge_direction_world);
+	EDepthDirection depth_direction = WITH_FIRST_EDGE;
+	
+	float depth = collider.first_edge_length - min_depth;
+	if (depth < min_depth)
+	{
+		min_depth = depth;
+		depth_direction = AGAINST_FIRST_EDGE;
+	}
+	
+	depth = Mth::DotProduct(displacement, collider.second_edge_direction_world);
+	if (depth < min_depth)
+	{
+		min_depth = depth;
+		depth_direction = WITH_SECOND_EDGE;
+	}
+	
+	depth = collider.second_edge_length - depth;
+	if (depth < min_depth)
+	{
+		min_depth = depth;
+		depth_direction = AGAINST_SECOND_EDGE;
+	}
+	
+	if (min_depth < 0.01f)
+	{
+		return 0.0f;
+	}
+	
+	switch (depth_direction)
+	{
+		case WITH_FIRST_EDGE:
+			depth = min_depth / Mth::DotProduct(collider.first_edge_direction_world, collision_point.normal);
+			break;
+			
+		case AGAINST_FIRST_EDGE:
+			depth = -min_depth / Mth::DotProduct(collider.first_edge_direction_world, collision_point.normal);
+			break;
+		
+		case WITH_SECOND_EDGE:
+			depth = min_depth / Mth::DotProduct(collider.second_edge_direction_world, collision_point.normal);
+			break;
+		
+		case AGAINST_SECOND_EDGE:
+			depth = -min_depth / Mth::DotProduct(collider.second_edge_direction_world, collision_point.normal);
+			break;
+	}
+	
+	#ifdef __USER_DAN__
+	if (depth < 0.0f)
+	{
+		MESSAGE("negative depth bug");
+	}
+	#endif
+	
+	return depth;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+			
+bool CVehicleComponent::check_for_capping ( SCollisionPoint& collision_point, const CRectFeeler& rect_feeler, const SCollider& collider, int collision_line_idx, int collision_point_end_idx ) const
+{
+	// check to see if this culled collision is the back of a cappable collision with a 2D object
+	for (int check_line_idx = rect_feeler.GetNumMergedCollisionSurfaces(); check_line_idx--; )
+	{
+		if (check_line_idx == collision_line_idx) continue;
+		
+		for (int end = 2; end--; )
+		{
+			if (very_close(collision_point.pos, rect_feeler.GetMergedCollisionSurface(check_line_idx).ends[end].point)
+				&& Mth::DotProduct(collision_point.normal, rect_feeler.GetMergedCollisionSurface(check_line_idx).normal) < -0.99)
+			{
+				// we'll use this point to cap the collision with the 2D object; the tangent cross the normal will point out of the triangle
+				collision_point.normal = Mth::CrossProduct(
+					rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[collision_point_end_idx].tangent,
+					collision_point.normal
+				);
+				collision_point.depth = calculate_collision_depth(collision_point, collider);
+				
+				if (m_draw_debug_lines)
+				{
+					Gfx::AddDebugLine(
+						collision_point.pos,
+						collision_point.pos + (72) * collision_point.normal,
+						MAKE_RGB(255, 100, 255), MAKE_RGB(255, 100, 255), 1
+					);
+				}
+				
+				return true;
+			}
+		} // END loop over checked collision line ends
+	} // END loop over all other collision lines
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CVehicleComponent::consider_culling_point ( const SCollisionPoint& collision_point ) const
+{
+	// cull collision points which have a normal direction along their body offset
+	
+	Mth::Vector offset = collision_point.pos - m_pos;
+	bool cull = Mth::DotProduct(offset, collision_point.normal) >= 0.0f;
+	if (m_draw_debug_lines && cull)
+	{
+		uint32 c = MAKE_RGB(0, 0, 0);
+		Gfx::AddDebugLine(collision_point.pos, collision_point.pos + 48.0f * collision_point.normal, c, c, 1);
+	}
+	return cull;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::apply_environment_collisions (   )
+{
+	m_max_normal_collision_impulse = 0.0f;
+	
+	CRectFeeler rect_feeler;
+	CLineFeeler line_feeler;
+	
+	rect_feeler.SetCache(&m_collision_cache);
+	line_feeler.SetCache(&m_collision_cache);
+	
+	m_num_collision_points = 0;
+	for (int collider_idx = vVP_NUM_COLLIDERS; collider_idx--; )
+	{
+		SCollider& collider = mp_colliders[collider_idx];
+		
+		// first we use a rectangular feeler
+		
+		// setup the feeler
+		rect_feeler.SetRectangle(collider.world);
+		
+		// detect collisions
+		if (rect_feeler.GetCollision())
+		{
+			trip_trigger(TRIGGER_BONK, rect_feeler);
+			if (reset_this_frame()) return;
+			
+			rect_feeler.MergeCollisionSurfaces();
+			
+			// copy out the collision points from the feeler output
+			for (int collision_line_idx = rect_feeler.GetNumMergedCollisionSurfaces(); collision_line_idx--; )
+			{
+				for (int end = 2; end--; )
+				{
+					// add the collision line's start to the list of collision points
+					SCollisionPoint& new_collision_point = sp_collision_points[m_num_collision_points];
+					
+					new_collision_point.line = false;
+					
+					new_collision_point.pos = rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[end].point;
+					new_collision_point.normal = rect_feeler.GetMergedCollisionSurface(collision_line_idx).normal;
+					if (!consider_culling_point(new_collision_point))
+					{
+						// if this is a keeper, calculate the collision depth
+						if (rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[end].tangent_exists)
+						{
+							new_collision_point.depth = calculate_collision_depth(new_collision_point, collider);
+						}
+						else
+						{
+							new_collision_point.depth = 0.0f;
+						}
+						m_num_collision_points++;
+					}
+					else if (rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[end].tangent_exists)
+					{
+						if (check_for_capping(new_collision_point, rect_feeler, collider, collision_line_idx, end))
+						{
+							m_num_collision_points++;
+						}
+					}
+				} // END loop over collision line ends
+			} // END loop over collision surfaces
+		} // END if we had a collision this collider
+    
+		if (m_draw_debug_lines)
+		{
+			rect_feeler.DebugLines(255, 255, 255, 1);
+		}
+		
+		// line feelers
+
+		line_feeler.m_start = m_pos;
+
+		line_feeler.m_end = rect_feeler.m_corner;
+		if (line_feeler.GetCollision(false))
+		{
+			trip_trigger(TRIGGER_BONK, line_feeler);
+			if (reset_this_frame()) return;
+			
+			sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
+			sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
+			if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
+			{
+				sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
+				m_num_collision_points++;
+				sp_collision_points[m_num_collision_points].line = true;
+			}
+		}
+		if (m_draw_debug_lines)
+		{
+			line_feeler.DebugLine(255, 255, 255, 1);
+		}
+
+
+		line_feeler.m_end += rect_feeler.m_first_edge;
+		if (line_feeler.GetCollision(false))
+		{
+			trip_trigger(TRIGGER_BONK, line_feeler);
+			if (reset_this_frame()) return;
+			
+			sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
+			sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
+			if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
+			{
+				sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
+				m_num_collision_points++;
+				sp_collision_points[m_num_collision_points].line = true;
+			}
+		}
+		if (m_draw_debug_lines)
+		{
+			line_feeler.DebugLine(255, 255, 255, 1);
+		}
+
+		line_feeler.m_end += rect_feeler.m_second_edge;
+		if (line_feeler.GetCollision(false))
+		{
+			trip_trigger(TRIGGER_BONK, line_feeler);
+			if (reset_this_frame()) return;
+			
+			sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
+			sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
+			if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
+			{
+				sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
+				m_num_collision_points++;
+				sp_collision_points[m_num_collision_points].line = true;
+			}
+		}
+		if (m_draw_debug_lines)
+		{
+			line_feeler.DebugLine(255, 255, 255, 1);
+		}
+
+		line_feeler.m_end -= rect_feeler.m_first_edge;
+		if (line_feeler.GetCollision(false))
+		{
+			trip_trigger(TRIGGER_BONK, line_feeler);
+			if (reset_this_frame()) return;
+			
+			sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
+			sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
+			if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
+			{
+				sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
+				m_num_collision_points++;
+				sp_collision_points[m_num_collision_points].line = true;
+			}
+		}
+		if (m_draw_debug_lines)
+		{
+			line_feeler.DebugLine(255, 255, 255, 1);
+		}
+	} // END loop over colliders
+	
+	if (m_num_collision_points == 0) return;
+	
+	// zero accumulators
+	for (int collision_idx = m_num_collision_points; collision_idx--; )
+	{
+		sp_collision_points[collision_idx].normal_impulse = 0.0f;
+	}
+	
+	// cache the momentums and velocities before normal impulses are applied; they are used when determining the frictional forces
+	Mth::Vector cache_mom = m_mom;
+	Mth::Vector cache_rotmom = m_rotmom;
+	Mth::Vector cache_vel = m_vel;
+	Mth::Vector cache_rotvel = m_rotvel;
+	
+	// apply normal impulses
+	
+	// loop over normal impulse points until all collisions are fully resolved
+	int pass_count = 0;
+	bool clean_pass;
+	do
+	{
+		clean_pass = true;
+		
+		for (int collision_idx = m_num_collision_points; collision_idx--; )
+		{
+			SCollisionPoint& collision = sp_collision_points[collision_idx];
+			
+			// calculate the collision position in body space
+			Mth::Vector pos_local = collision.pos - m_pos;
+			
+			// calculate the velocity and normal velocity of the collision point
+			Mth::Vector vel = calculate_body_point_velocity(pos_local);
+			float normal_vel = Mth::DotProduct(vel, collision.normal);
+			
+			// ignore if we're not impacting
+			if (normal_vel > 0.0f) continue;
+			clean_pass = false;
+			
+			// calculate the goal velocity of the collision
+			float goal_normal_velocity = -m_body_restitution * normal_vel;
+			
+			// calculate the effective mass of the collision point
+			float effective_mass = calculate_body_point_effective_mass(pos_local, collision.normal);
+			
+			// calculate the normal impulse required to reach the body velocity
+			float normal_impulse = (goal_normal_velocity - normal_vel) * effective_mass;
+			
+			// accumulate the total normal impulse applied to this collision point
+			collision.normal_impulse += normal_impulse;
+			
+			// setup the required impulse vector
+			Mth::Vector impulse = normal_impulse * collision.normal;
+			
+			// apply the normal impulse immediately
+			apply_impulse(impulse, pos_local);
+		}
+
+		if (++pass_count > 5)
+		{
+			#ifdef __USER_DAN__
+			MESSAGE("environment collision resolution pass count limit exceeded");
+			#endif
+			break;
+		}
+	} while (!clean_pass);
+	
+    if (m_draw_debug_lines)
+	{
+		for (int collision_idx = m_num_collision_points; collision_idx--; )
+		{
+			// SCollisionPoint& collision = sp_collision_points[collision_idx];
+			// Gfx::AddDebugLine(
+				// collision.pos + collision.normal,
+				// collision.pos + collision.normal + collision.normal_impulse / 2.0f * collision.normal,
+				// MAKE_RGB(0, 100, 100), MAKE_RGB(0, 100, 100), 1
+			// );
+		}
+	} // END loop over collision points
+
+	// apply penalty forces to prevent interpenetration
+	for (int collision_idx = m_num_collision_points; collision_idx--; )
+	{
+		SCollisionPoint& collision = sp_collision_points[collision_idx];
+		
+		// collision normal is facing opposite the depth
+		// BUG: too many rect feeler collisions have negative depth!!!
+		if (collision.depth <= 0.0f) continue;
+		
+		// extremely deep collisions are mostly collisions with the underbody which are actually shallow in the Y direction; for now we'll cap such
+		// collisions and perhaps allow the car to sink down into geometry which comes from below but misses the wheels
+		if (collision.depth > 2.0f)
+		{
+			collision.depth = 2.0f;
+		}
+		
+		Mth::Vector force = m_body_spring * collision.depth * collision.normal;
+		
+		accumulate_collision_force(force, collision.pos - m_pos);
+		
+		// Gfx::AddDebugLine(
+			// collision.pos,
+			// collision.pos + (2 * collision.depth) * collision.normal,
+			// MAKE_RGB(255, 255, 0), MAKE_RGB(255, 255, 0), 1
+		// );
+	}
+	
+	float body_friction = m_orientation_matrix[Y][Y] > 0.707f ? m_body_friction : m_body_wipeout_friction;
+	
+	// apply a friction impulse at each point
+	for (int collision_idx = m_num_collision_points; collision_idx--; )
+	{
+		SCollisionPoint& collision = sp_collision_points[collision_idx];
+		
+		if (collision.normal_impulse == 0.0f) continue;
+		
+		// calculate the local collision position
+		Mth::Vector pos_local = collision.pos - m_pos;
+		
+		// calculate the tangential velocity
+		Mth::Vector	vel = cache_vel + Mth::CrossProduct(cache_rotvel, pos_local);
+		Mth::Vector tanjent = vel - Mth::DotProduct(vel, collision.normal) * collision.normal;
+		float tanjent_vel = tanjent.Length();
+		if (tanjent_vel < 0.001f) continue;
+		tanjent /= tanjent_vel;
+		
+		// calculate the effective mass of the collision point
+		float effective_mass = calculate_body_point_effective_mass(pos_local, tanjent);
+		
+		// calculate the tanjential impulse required to stop the tanjential velocity
+		float tanjent_impulse = tanjent_vel * effective_mass;
+		
+		// cap the frictional force at the normal force times the coefficient of friction
+		float max_tanjent_impulse = body_friction * collision.normal_impulse;
+		
+		if (tanjent_impulse > max_tanjent_impulse)
+		{
+			tanjent_impulse = max_tanjent_impulse;
+		}
+		
+		// setup the tanjent impulse vector
+		Mth::Vector impulse = -tanjent_impulse * tanjent;
+		
+		// apply the friction impulse to the true state
+		apply_impulse(impulse, pos_local);
+		
+		if (m_draw_debug_lines)
+		{
+			// Gfx::AddDebugLine(
+				// collision.pos + collision.normal,
+				// collision.pos + collision.normal + impulse / 2.0f,
+				// MAKE_RGB(100, 0, 100), MAKE_RGB(100, 0, 100), 1
+			// );
+		}
+		
+		// apply the friction impulse to the state we are using for friction calculations
+		cache_mom += impulse;
+		cache_rotmom += Mth::CrossProduct(pos_local, impulse);
+		cache_vel = m_inv_mass * cache_mom;
+		cache_rotvel = m_inv_moment.Rotate(cache_rotmom);
+	} // END loop over collision points
+	
+	// determine the maximum normal impulse this frame
+	for (int collision_idx = m_num_collision_points; collision_idx--; )
+	{
+		SCollisionPoint& collision = sp_collision_points[collision_idx];
+		
+		m_max_normal_collision_impulse = Mth::Max(collision.normal_impulse, m_max_normal_collision_impulse);
+	}
+	
+	// no player controlled impulses in the air	or with handbrake on
+	if (m_controls.steering == 0.0f || m_num_wheels_in_contact < 3 || m_controls.handbrake) return;
+	
+	// extra player controled collision impulse; allows one slight control over the direction on collision impulses
+	for (int collision_idx = m_num_collision_points; collision_idx--; )
+	{
+		SCollisionPoint& collision = sp_collision_points[collision_idx];
+				
+		Mth::Vector pos_local = collision.pos - m_pos;
+		
+		float strength = m_controls.steering * m_collision_control * Mth::DotProduct(m_orientation_matrix[Z], collision.normal) * collision.normal_impulse;
+		
+		Mth::Vector direction(-collision.normal[Z], 0.0f, collision.normal[X]);
+		
+		apply_impulse(strength * direction, pos_local);
+		
+		if (m_draw_debug_lines)	  
+		{
+			Gfx::AddDebugLine(
+				collision.pos + collision.normal,
+				collision.pos + collision.normal + strength * direction / 5.0f,
+				MAKE_RGB(255, 0, 100), MAKE_RGB(255, 0, 100), 1
+			);
+		}
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_pos_with_uber_frig ( const Mth::Vector& movement )
+{
+	// The vehicle has two uber frigs.  The first treats the c.o.m. as a point particle with respect to the geo.  The seconds is a traditional uber frig.
+	
+	Mth::Vector frame_start_pos = m_pos;
+	
+	// look for collisions along the origin's movement; only do a single bounce, assuming the remaining movement is ok
+	CFeeler feeler(m_pos, m_pos + movement);
+	if (feeler.GetCollision(false))
+	{
+		// bounce off the surface
+		MESSAGE("less than uber frig");
+		
+		m_pos = feeler.GetPoint() + feeler.GetNormal();
+		
+		Mth::Vector remaining_movement = feeler.GetDist() * movement;
+		remaining_movement -= 2.0f * Mth::DotProduct(remaining_movement, feeler.GetNormal()) * feeler.GetNormal();
+		m_pos += remaining_movement;
+		
+		// use coeff of restit of 0.5
+		m_mom -= 1.5f * Mth::DotProduct(m_mom, feeler.GetNormal()) * feeler.GetNormal();
+	}
+	else
+	{
+		m_pos += movement;
+	}
+	
+	// now, a traditional uber frig
+	feeler.m_start = m_pos;
+	feeler.m_end = feeler.m_start;
+	feeler.m_end[Y] -= FEET(400.0f);
+	if (!feeler.GetCollision(false))
+	{
+		MESSAGE("uber frig");
+		m_pos = frame_start_pos;
+		m_mom *= 1.0f;
+		m_rotmom *= 1.0f;
+	}
+	else
+	{
+		mp_model_component->ApplyLightingFromCollision(feeler);
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::slerp_to_face_velocity (  )
+{
+	// slerp to face our velocity
+	
+	if (m_in_flip) return;
+	
+	if (in_artificial_collision()) return;
+	
+	// only slerp if horizontal velocity is below a given threshold
+	bool vel_below_threshold = m_vel[X] * m_vel[X] + m_vel[Z] * m_vel[Z] < m_in_air_slerp_vel_cutoff * m_in_air_slerp_vel_cutoff;
+	if (vel_below_threshold && !m_vert_correction) return;
+
+	// extract the current orientation matrix
+	m_orientation.GetMatrix(m_orientation_matrix);
+	m_orientation_matrix[W].Set();
+
+	// construct our target matrix
+	Mth::Matrix target;
+	if (vel_below_threshold)
+	{
+		target[Z] = m_orientation_matrix[Z];
+		target[Z][Y] = 0.0f;
+		target[Z].Normalize();
+	}
+	else
+	{
+		target[Z] = m_vel;
+		target[Z].Normalize();
+	}
+	if (Mth::DotProduct(target[Z], m_orientation_matrix[Z]) < 0.0f)
+	{
+		target[Z] *= -1.0f;
+	}
+	target[Y].Set(0.0f, 1.0f, 0.0f);
+	target[X] = Mth::CrossProduct(target[Y], target[Z]);
+	target[Y] = Mth::CrossProduct(target[Z], target[X]);
+	target[W].Set();
+
+	// setup the slerp
+	Mth::SlerpInterpolator slerper;
+	slerper.setMatrices(&m_orientation_matrix, &target);
+
+	// lerp up to full strength over time
+	if (m_vel[X] * m_vel[X] + m_vel[Z] * m_vel[Z] > 350.0f * 350.0f)
+	{
+		slerper.getMatrix(&m_orientation_matrix, Mth::LinearMap(
+			0.0f,
+			m_in_air_slerp_strength * m_time_step,
+			Mth::Min(m_air_time_no_collision, m_in_air_slerp_time_delay),
+			0.0f,
+			m_in_air_slerp_time_delay
+		));
+	}
+	else
+	{
+		slerper.getMatrix(&m_orientation_matrix, Mth::LinearMap(
+			0.0f,
+			0.3f * m_in_air_slerp_strength * m_time_step,
+			Mth::Min(m_air_time_no_collision, m_in_air_slerp_time_delay),
+			0.0f,
+			m_in_air_slerp_time_delay
+		));
+	}
+	m_orientation = m_orientation_matrix;
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CVehicleComponent::calculate_body_point_effective_mass ( const Mth::Vector& pos, const Mth::Vector& direction ) const
+{
+	// calculate the effect on momentums due to a unit impulse applied at pos towards direction
+	Mth::Vector delta_mom = direction;
+	Mth::Vector delta_rotmom = Mth::CrossProduct(pos, direction);
+	
+	// calculate the resulting change in velocities
+	Mth::Vector delta_vel = m_inv_mass * delta_mom;
+	Mth::Vector delta_rotvel = m_inv_moment.Rotate(delta_rotmom);
+	
+	// calculate the corresponding change in the body point's velocity
+	Mth::Vector delta_vel_body_point = delta_vel + Mth::CrossProduct(delta_rotvel, pos);
+	
+	// extract the change in velocity along the direction of interest
+	float delta_vel_direction = Mth::DotProduct(direction, delta_vel_body_point);
+	
+	// return the effective mass of the body point in the direction of interest
+	Dbg_Assert(delta_vel_direction != 0.0f);
+	return 1.0f / delta_vel_direction;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::consider_sleeping (   )
+{
+	int last_consider_sleeping_count = m_consider_sleeping_count;
+	m_consider_sleeping_count = 0;
+	
+	if (m_controls.throttle) return;
+	
+	if (m_controls.reverse && m_num_wheels_in_contact != 0) return;
+	
+	// only sleep if we're experiencing three or more collisions; hopefully, a surface-to-surface contact
+	if (m_num_wheels_in_contact + m_num_collision_points < 3) return;
+	
+	// only sleep if we've got four-on-the-floor or are completely flipped
+	if (m_num_wheels_in_contact != 0 && m_num_wheels_in_contact != m_num_wheels) return;
+
+	// only sleep if we're moving slow
+	if (m_vel.LengthSqr() > Mth::Sqr(vVP_SLEEP_VEL)) return;
+	#ifdef __USER_DAN__
+	DUMPF(m_vel.Length());
+	#endif
+
+	// only sleep if we're moving slow
+	if (m_rotvel.LengthSqr() > Mth::Sqr(vVP_SLEEP_ROTVEL)) return;
+	#ifdef __USER_DAN__
+	DUMPF(m_rotvel.Length());
+	#endif
+	
+	// never sleep during an artificial collision period
+	if (in_artificial_collision()) return;
+	
+	m_consider_sleeping_count = last_consider_sleeping_count + 1;
+	if (m_consider_sleeping_count < 3)
+	{
+		return;
+	}
+	
+	// sleep
+	m_mom.Set();
+	m_rotmom.Set();
+	m_force.Set(0.0f, 0.0f, 0.0f);
+	m_torque.Set(0.0f, 0.0f, 0.0f);
+	for (int n = m_num_wheels; n--; )
+	{
+		mp_wheels[n].rotacc = 0.0f;
+	}
+	m_state = ASLEEP;
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::get_input (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	// analog control
+	// m_controls.steering = control_pad.m_scaled_leftX;
+	m_controls.steering = control_pad.m_scaled_leftX * Mth::Abs(control_pad.m_scaled_leftX);
+	
+	// dpad control
+	if (m_controls.steering == 0.0f)
+	{
+		if (control_pad.m_left.GetPressed() && !control_pad.m_right.GetPressed())
+		{
+			m_controls.steering = -1.0f;
+		}
+		else if (control_pad.m_right.GetPressed() && !control_pad.m_left.GetPressed())
+		{
+			m_controls.steering = 1.0f;
+		}
+	}
+	
+	m_controls.throttle = control_pad.m_x.GetPressed();
+	m_controls.handbrake = control_pad.m_R1.GetPressed() && !m_no_handbrake;
+	
+	bool brake_pressed = control_pad.m_square.GetPressed() || (m_exitable && control_pad.m_triangle.GetPressed());
+	bool exit_request = brake_pressed && !control_pad.m_square.GetPressed();
+	
+	// decide between brake and reverse
+	if (brake_pressed)
+	{
+		if ((m_controls.reverse || (Mth::DotProduct(m_vel, m_orientation_matrix[Z]) < 300.0f && m_num_wheels_in_contact != 0))
+			&& (m_state != ASLEEP || m_num_wheels_in_contact != 0))
+		{
+			// reverse
+			m_controls.reverse = true;
+			m_controls.brake = false;
+		}
+		else
+		{
+			// brake
+			m_controls.brake = true;
+			m_controls.reverse = false;
+		}
+		
+		if (exit_request)
+		{
+			if (Mth::DotProduct(m_vel, m_orientation_matrix[Z]) < 0.0f)
+			{
+				// brake
+				m_controls.brake = true;
+				m_controls.reverse = false;
+			}
+			
+			if (Mth::Abs(Mth::DotProduct(m_vel, m_orientation_matrix[Z])) < 30.0f)
+			{
+				// signal an exit
+				GetObject()->BroadcastEvent(CRCD(0xcbaa3476, "ExitVehicleRequest"));
+			}
+		}
+	}
+	else
+	{
+		m_controls.reverse = false;
+		m_controls.brake = false;
+	}
+	
+	if (m_force_brake)
+	{
+		m_controls.brake = true;
+		m_controls.reverse = false;
+	}
+	
+	if (m_state == ASLEEP && (m_controls.throttle || m_controls.reverse))
+	{
+		m_state = AWAKE;
+		m_consider_sleeping_count = 0;
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::zero_input (   )
+{
+	m_controls.brake = false;
+	m_controls.handbrake = false;
+	m_controls.throttle = false;
+	m_controls.reverse = false;
+	m_controls.steering = 0.0f;
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::update_skeleton (   )
+{
+	Mth::Matrix* p_matrices = mp_skeleton_component->GetSkeleton()->GetMatrices();
+	for (int i = mp_skeleton_component->GetSkeleton()->GetNumBones(); i--; )
+	{
+		Mth::Matrix& matrix = p_matrices[i];
+		
+		// setup the matrix for each bone in the skeleton
+		
+		if (m_draw_debug_lines == 2)
+		{
+			matrix.Zero();
+			continue;
+		}
+		
+		// shadow
+		if (i == 0)
+		{
+			matrix.Zero();
+		}
+		
+		// body
+		else if (i == 1)
+		{
+			matrix.Zero();
+			matrix[X][X] = 1.0f;
+			matrix[Y][Z] = -1.0f;
+			matrix[Z][Y] = 1.0f;
+			matrix[W] = m_body_pos;
+		}
+		
+		// wheel
+		else
+		{
+			SWheel& wheel = mp_wheels[i - 2];
+			
+			matrix.Zero();
+			matrix[X][X] = -1.0f;
+			matrix[Y][Z] = 1.0f;
+			matrix[Z][Y] = 1.0f;
+			
+			matrix.RotateZLocal(wheel.steering_angle_display);
+			matrix.RotateXLocal(-wheel.orientation);
+			matrix[W] = wheel.pos;
+			#ifdef __NOPT_ASSERT__
+			if (Script::GetInteger("use_max_y_offset"))
+			{
+				matrix[W][Y] += wheel.max_draw_y_offset;
+			}
+			else
+			{
+				matrix[W][Y] += Mth::ClampMax(wheel.y_offset, wheel.max_draw_y_offset);
+			}
+			#else
+			matrix[W][Y] += Mth::ClampMax(wheel.y_offset, wheel.max_draw_y_offset);
+			#endif
+			matrix[W][W] = 1.0f;
+		}
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::draw_shadow (   )
+{
+	//Dbg_Message("Drawing shadow for car");
+
+#if 0	// Experiment to draw a shadow using texture splats
+	Mth::Vector start_pos = GetObject()->GetPos();
+	Mth::Vector end_pos = start_pos;
+	start_pos[Y] += 12.0f;
+	end_pos[Y] -= 120.0f;
+
+	float y_rot = 360.0f - m_orientation.GetVector()[Y];
+	while (y_rot < 0.0f)
+		y_rot += 360.0f;
+
+	Nx::TextureSplat( start_pos, end_pos, 120.0f, 100.0f, 2.0f / (float) Config::FPS(), "blood_01", y_rot);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::control_skater (   )
+{
+    // HACKY; setup the skater's position, orientation, and animation each frame
+	
+    mp_skater->SetPos(GetObject()->GetPos() + GetObject()->GetMatrix().Rotate(m_skater_pos));
+	mp_skater->SetMatrix(GetObject()->GetMatrix());
+	mp_skater_core_physics_component->ResetLerpingMatrix();
+	mp_skater->SetVel(GetObject()->GetVel());
+	
+	if (!m_skater_visible) return;
+	
+	float target_time = Mth::LinearMap(
+		0.0f,
+		mp_skater_animation_component->AnimDuration(m_skater_anim),
+		mp_skater_core_physics_component->GetFlag(FLIPPED) ? m_steering_display : -m_steering_display,
+		-1.0f,
+		1.0f
+	);
+	
+	if (!GameNet::Manager::Instance()->InNetGame())
+	{
+		mp_skater_animation_component->PlayPrimarySequence(m_skater_anim, false, target_time, 1000.0f, Gfx::LOOPING_CYCLE, 0.0f, 0.0f);
+	}
+	else
+	{
+		// HACK to reduce number of animation events when driving cars
+		static int anim_send_count = 0;
+		if (++anim_send_count == 15)
+		{
+			anim_send_count = 0;
+			mp_skater_animation_component->PlayPrimarySequence(m_skater_anim, true, target_time, 1000.0f, Gfx::LOOPING_CYCLE, 0.0f, 0.0f);
+		}
+		else
+		{
+			mp_skater_animation_component->PlayPrimarySequence(m_skater_anim, false, target_time, 1000.0f, Gfx::LOOPING_CYCLE, 0.0f, 0.0f);
+		}
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const int num_edges = 12;
+
+Mth::Vector CVehicleComponent::wheel_point ( const SWheel& wheel, int i, bool side ) const
+{
+	Mth::Vector point(
+		(side ? 1.0f : -1.0f) * 3.85f,
+		wheel.radius * cosf(2.0f * 3.141592f * i / num_edges + wheel.orientation),
+		wheel.radius * sinf(2.0f * 3.141592f * i / num_edges + wheel.orientation)
+	);
+	Mth::Matrix steering_rotation(Mth::Vector(0.0f, 1.0f, 0.0f), wheel.steering_angle);
+	point = steering_rotation.Rotate(point);
+	point[Y] += wheel.y_offset;
+	point += wheel.pos;
+	point = m_pos + m_orientation_matrix.Rotate(point);
+	return point;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CVehicleComponent::draw_debug_rendering (   ) const
+{
+	if (m_draw_debug_lines)
+	{
+		for (int n = m_num_wheels; n--; )
+		{
+			SWheel& wheel = mp_wheels[n];
+			
+			int32 color = 0;
+			switch (wheel.state)
+			{
+				case SWheel::UNDER_GRIPPING:
+					color = MAKE_RGB(0, 255, 0);
+					break;
+				case SWheel::GRIPPING:
+					color = MAKE_RGB(0, 0, 0);
+					break;
+				case SWheel::SLIPPING:
+					color = MAKE_RGB(255, 200, 0);
+					break;
+				case SWheel::SKIDDING:
+					color = MAKE_RGB(255, 0, 0);
+					break;
+				case SWheel::HANDBRAKE_THROTTLE:
+					color = MAKE_RGB(150, 0, 150);
+					break;
+				case SWheel::HANDBRAKE_LOCKED:
+					color = MAKE_RGB(0, 150, 150);
+					break;
+				case SWheel::NO_STATE:
+				case SWheel::OUT_OF_CONTACT:
+				default:
+					color = MAKE_RGB(255, 255, 255);
+					break;
+			}
+			
+			for (int i = num_edges; i--; )
+			{
+				Mth::Vector start = wheel_point(wheel, i, true);
+				Mth::Vector end = wheel_point(wheel, i + 1, true);
+				Gfx::AddDebugLine(start, end, color, color, 1);
+	
+				start = wheel_point(wheel, i, false);
+				end = wheel_point(wheel, i + 1, false);
+				Gfx::AddDebugLine(start, end, color, color, 1);
+				
+				start = wheel_point(wheel, i, true);
+				end = wheel_point(wheel, i, false);
+				Gfx::AddDebugLine(start, end, color, color, 1);
+				
+				#if 1
+				// draw axis of rotation lines
+				Mth::Matrix steering_rotation(Mth::Vector(0.0f, 1.0f, 0.0f), wheel.steering_angle);
+				start.Set(-240.0f, 0.0f, 0.0f);
+				start = steering_rotation.Rotate(start);
+				start += wheel.pos;
+				start[Y] += wheel.y_offset;
+				start = m_pos + m_orientation_matrix.Rotate(start);
+				end.Set(240.0f, 0.0f, 0.0f);
+				end = steering_rotation.Rotate(end);
+				end += wheel.pos;
+				end[Y] += wheel.y_offset;
+				end = m_pos + m_orientation_matrix.Rotate(end);
+				Gfx::AddDebugLine(start, end, MAKE_RGB(0, 0, 0), MAKE_RGB(0, 0, 0), 1);
+				#endif
+			}
+		} // END loop over wheels
+	}
+	
+	#if 1
+	if (m_draw_debug_lines)
+	{
+		Gfx::AddDebugStar(m_pos + m_suspension_center_of_mass_world, 12.0f, MAKE_RGB(100, 0, 100), 1);
+		Gfx::AddDebugStar(m_pos, 12.0f, MAKE_RGB(0, 100, 100), 1);
+	}
+	#endif
+}
+
+}
diff --git a/Code/Gel/Components/vehiclecomponent.h b/Code/Gel/Components/vehiclecomponent.h
new file mode 100644
index 0000000..fb9d265
--- /dev/null
+++ b/Code/Gel/Components/vehiclecomponent.h
@@ -0,0 +1,762 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       VehicleComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_VEHICLECOMPONENT_H__
+#define __COMPONENTS_VEHICLECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#define		CRC_VEHICLE CRCD(0xe47f1b79, "Vehicle")
+#define		GetVehicleComponent() ((Obj::CVehicleComponent*)GetComponent(CRC_VEHICLE))
+#define		GetVehicleComponentFromObject(pObj) ((Obj::CVehicleComponent*)(pObj)->GetComponent(CRC_VEHICLE))
+		 
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CInputComponent;
+	class CAnimationComponent;
+	class CSkaterCorePhysicsComponent;
+	class CModelComponent;
+
+#define vVP_GRAVITATIONAL_ACCELERATION								(386.4f)
+#define vVP_MAX_NUM_GEARS											(6)
+#define vVP_NUM_COLLIDERS											(2)
+
+#define vVP_FLIP_DURATION											(5000)
+#define vVP_FLIP_DELAY												(300)
+#define vVP_FLIP_RATE												(3.0f)
+#define vVP_FLIP_GRAVITY_FACTOR										(0.3f)
+#define vVP_SLEEP_VEL												(1.0f)
+#define vVP_SLEEP_ROTVEL											(0.01f)
+
+class CVehicleComponent : public CBaseComponent
+{
+	friend class CVehicleSoundComponent;
+	
+	// number of frames of normal force history to record for each tire
+	enum { vVP_NORMAL_FORCE_HISTORY_LENGTH = 10 };
+	
+	// HACK for control
+	struct SControls {
+		// steering; between -1.0 and 1.0
+		float steering;
+		
+		// throttle
+		bool throttle;
+		
+		// brake
+		bool brake;
+		
+		// handbrake
+		bool handbrake;
+		
+		// reverse
+		bool reverse;
+	};
+	
+	struct SEngine
+	{
+		// state variables
+		// int gear;
+		
+		// constant characteristics
+		
+		// engine's base torque output before applying gear ratios
+		float drive_torque;
+		float drag_torque;
+		
+		int num_gears;
+		
+		float p_gear_ratios[vVP_MAX_NUM_GEARS];
+		
+		float reverse_torque_ratio;
+		
+		float differential_ratio;
+		
+		// float differential_stiffness;
+		
+		// float downshift_rotvel;
+		
+		float upshift_rotvel;
+	};
+	
+	struct SBrake
+	{
+		// state variables
+		
+		// brake torque is applied both after engine torque and friction torque; here we track the unused torque between the applications
+		float spare_torque;
+		
+		// constant characteristics
+		
+		// braking torque per wheel
+		float torque;
+		
+		// strength of the handbrake torque when throttle is down and steering is forward
+		float handbrake_torque;
+	};
+	
+	struct SWheel
+	{
+		
+		enum EStateType {
+			NO_STATE, OUT_OF_CONTACT, UNDER_GRIPPING, GRIPPING, SLIPPING, SKIDDING, HANDBRAKE_THROTTLE, HANDBRAKE_LOCKED
+		};
+		
+		enum ESteeringType {
+			FIXED, LEFT, RIGHT
+		};
+		
+		// state variables
+		
+		// offset along Y-axis of wheel's position in vehicle's frame
+		float y_offset;
+		
+		// is the wheel out of contact, gripped, skidding, etc
+		EStateType state;
+		
+		// angular velocity about the axle
+		float rotvel;
+		
+		// angular position about the axle
+		float orientation;
+		
+		// angle of deflection from forward
+		float steering_angle;
+		
+		// displayed angle of deflection from forward; lerps behind steering_angle
+		float steering_angle_display;
+		
+		// dependent variables
+		
+		// world frame position of the bottom of the wheel
+		Mth::Vector pos_world;
+		
+		// world frame velocity of the bottom of the wheel, not counting rotation
+		Mth::Vector vel_world;
+		
+		// magnitude of the normal force applied by the suspension through the tire; set in apply_suspension_forces(); used in apply_friction_forces()
+		float normal_force;
+		
+		// magnitude of the normal force for the last few frames; friction uses a smoothed normal force to even out control
+		float normal_force_history [ vVP_NORMAL_FORCE_HISTORY_LENGTH ];
+		
+		// normal of the wheel's contact surface
+		Mth::Vector contact_normal;
+		
+		// friction coefficient this frame
+		float friction_coefficient;
+		
+		// ground detector feeler start and end positions in world frame
+		Mth::Vector feeler_start_world;
+		Mth::Vector feeler_end_world;
+		
+		// NOTE: used only to draw skid indicators; remove if not used for anything else
+		float slip_vel;
+		
+		// accumulators
+		
+		// total rotational acceleration applied so far this frame
+		float rotacc;
+		
+		// constant characteristics
+		
+		// start of the wheel's downward pointing collision feeler; basically the top of the wheel's position in the vehicle's frame
+		Mth::Vector pos;
+		
+		// type of wheel with respect to steering
+		ESteeringType steering;
+		
+		// true if the wheel is a drive wheel
+		bool drive;
+		
+		// hang point; wheel's y_offset will drop only to this point; the effective equilibrium point for the spring, if one accounts for the wheel's
+		// weight ahead of time
+		float y_offset_hang;
+		
+		// the wheels are never rendered above this y threshold; this has no affect on their location from the physics code's perspective
+		float max_draw_y_offset;
+		
+		// radius of wheel and tire (in)
+		float radius;
+		
+		// inverse moment of the wheel and tire around the axis (1 / lb / in^2)
+		float inv_moment;
+		
+		// suspension's spring constant (lb / in); spring rate
+		float spring;
+		
+		// suspension's damping constant (lb s / in); bump rate
+		float damping;
+		
+		// the friction coefficient is a set of concatenated lines; the transitions are made as these velocities; a more sophisticated
+		float min_static_velocity;
+		float max_static_velocity;
+		float min_dynamic_velocity;
+		
+		// static friction coefficient
+		float static_friction;
+		
+		// dynamic friction coefficient
+		float dynamic_friction;
+		
+		// friction coefficient during handbraking
+		float handbrake_throttle_friction;
+		float handbrake_locked_friction;
+		
+		// subelements
+		
+		// brake
+        SBrake brake;
+		
+		// caches to avoid repeating calculations between friction coefficient calculation and friction application
+		Mth::Vector cache_projected_direction;
+		Mth::Vector cache_projected_vel;
+		
+		// the last feeler for this wheel's height which touched the ground
+		CFeeler last_ground_feeler;
+	};
+	
+	struct SCollisionPoint
+	{
+		// world position
+		Mth::Vector pos;
+		
+		// impact normal
+		Mth::Vector normal;
+		
+		// depth of collision as defined as the dot between the displacement from the nearest collider corner and the collision normal
+		float depth;
+		
+		// normal impulse accumulator; used to set the maximum friction
+		float normal_impulse;
+		
+		bool line;
+	};
+	
+	struct SCollider
+	{
+		// the rectangle defining the collider in body space
+		Mth::Rectangle body;
+		
+		// that rectangle transformed into world space
+		Mth::Rectangle world;
+		Mth::Vector first_edge_direction_world;
+		Mth::Vector second_edge_direction_world;
+		
+		// a few precomputable values
+		float first_edge_length;
+		float second_edge_length;
+	};
+	
+public:
+	// inorder to interface with models and skeletons, the number of wheels must be hardcoded to four
+	enum { vVP_NUM_WHEELS = 4 };
+	
+public:
+    CVehicleComponent();
+    virtual ~CVehicleComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize (   );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							MoveToNode ( Script::CStruct* p_node );
+	void							ApplyArtificialCollision ( const Mth::Vector& offset );
+	
+	
+	const Mth::Vector&				GetVel (   ) const { return m_vel; }
+	int								GetNumWheelsInContact (   ) const { return m_num_wheels_in_contact; }
+	bool							IsOnGround (   ) const;
+	
+	uint32							GetSoundSetupChecksum (   ) const;
+	
+private:
+	
+	void							get_input (   );
+	void							zero_input (   );
+	
+	void							update_wheel_from_structure ( SWheel& wheel, Script::CStruct* p_wheel_struct );
+	
+	void							update_dynamic_state (   );
+	void							update_wheel_dynamic_state (   );
+	void							update_dependent_variables (   );
+	void							update_velocities (   );
+	void							update_collision_cache (   );
+	
+	void							damp_rotation (   );
+	
+	void							calculate_inverse_moment (   );
+	float							calculate_friction_coefficient ( SWheel& wheel, float velocity ) const;
+	void							calculate_friction_coefficients (   );
+	Mth::Vector						calculate_body_point_velocity ( const Mth::Vector& pos ) const;
+	float							calculate_body_point_effective_mass ( const Mth::Vector& pos, const Mth::Vector& direction ) const;
+	
+	int								determine_effective_gear ( float wheel_rotvel );
+	void							update_wheel_heights (   );
+	void							update_steering_angles (   );
+	
+	void							accumulate_forces (   );
+	void							apply_gravitational_forces (   );
+	void							apply_suspension_forces (   );
+	void							apply_engine_forces (   );
+	void							apply_drag_forces (   );
+	void							apply_friction_forces (   );
+	void							apply_brake_forces (   );
+	void							apply_handbrake_forces ( float application_factor );
+	void							apply_spare_brake_forces (   );
+	
+	void							accumulate_force ( const Mth::Vector& force, const Mth::Vector& location, uint32 color = MAKE_RGB(255, 255, 255) );
+	void							accumulate_collision_force ( const Mth::Vector& force, const Mth::Vector& location );
+	void							apply_wheel_torque ( SWheel& wheel, float torque );
+	float							calculate_stopping_torque ( const SWheel& wheel, float rotvel ) const;
+	
+	void							update_flip (   );
+	void							slerp_to_face_velocity (  );
+	void							consider_sleeping (   );
+	bool							in_artificial_collision (    );
+	
+	
+									// environment collision members
+	float							calculate_collision_depth ( const SCollisionPoint& collision_point, const SCollider& collider ) const;
+	float							calculate_collision_depth ( const CLineFeeler& line_feeler ) const;
+	bool							check_for_capping ( SCollisionPoint& collision_point, const CRectFeeler& rect_feeler, const SCollider& collider, int collision_line_idx, int collision_point_end_idx ) const;
+	bool							consider_culling_point ( const SCollisionPoint& collision_point ) const;
+	bool							very_close ( const Mth::Vector p, const Mth::Vector q ) const;
+	bool							reset_this_frame (   ) const;
+	void							apply_environment_collisions (   );
+	void							update_pos_with_uber_frig ( const Mth::Vector& movement );
+	void							apply_impulse ( const Mth::Vector& impulse, const Mth::Vector& location);
+	
+	void							trip_trigger ( ESkaterTriggerType trigger_type, CLineFeeler &feeler );
+	void							trip_trigger ( ESkaterTriggerType trigger_type, CRectFeeler &feeler );
+	
+	void							control_skater (   );
+	
+	void							update_skeleton (   );
+	
+	void							draw_shadow (   );
+
+	Mth::Vector						wheel_point ( const SWheel& wheel, int i, bool side ) const;
+    void							draw_debug_rendering (   ) const;
+	
+private:
+	
+	// dynamic state variables
+	
+	// position
+	Mth::Vector m_pos;
+	
+	// linear momentum
+	Mth::Vector m_mom;
+	
+	// angular orientation
+	Mth::Quat m_orientation;
+	
+	// rotational momentum
+	Mth::Vector m_rotmom;
+	
+	// like a rigidbody, the vehicle can go to sleep
+	enum EStateType
+	{
+		ASLEEP, AWAKE
+	}
+	m_state;
+	
+	// counting the number of consecutive sleep-worthy frames; after a certain number, we will go to sleep
+	int m_consider_sleeping_count;
+	
+	// number of collision contacts during latest collision test
+	int m_num_collision_points;
+	
+	// dependent state variables
+	
+	// angular orientation in 3x3 matrix form
+	Mth::Matrix m_orientation_matrix;
+	
+	// linear velocity
+	Mth::Vector m_vel;
+	
+	// rotational velocity
+	Mth::Vector m_rotvel;
+	
+	// inverse moment of inertia in world frame
+	Mth::Matrix m_inv_moment;
+	
+	// offset of center of mass used for suspension and friction, but not collisions
+	Mth::Vector m_suspension_center_of_mass_world;
+	
+	// counts the number of wheels in contact with the ground
+	unsigned int m_num_wheels_in_contact;
+	
+	// pointer to the next normal force history to use (the oldest one)
+	char m_next_normal_force_history_idx;
+
+	// if true, we were reset from a trigger script and should bail from the frame's update logic
+	bool m_reset_this_frame;
+	
+	// if true, we are currently in the midst of flipping the car over
+	bool m_in_flip;
+	
+	// if we're flipping the car, this is a time stamp of when the flipping began
+	Tmr::Time m_flip_start_time_stamp;
+	
+	// during an artificial collision, this timer counts down to the end of the duration
+	float m_artificial_collision_timer;
+	
+	// accumulators
+	
+	// accumulates the force on the body in a frame
+	Mth::Vector m_force;
+	
+	// accumulates the torque on the body in a frame
+	Mth::Vector m_torque;
+	
+	// length of current frame
+	float m_time_step;
+	
+	// constant characteristics
+	
+	// vehicle's center of mass
+	Mth::Vector m_suspension_center_of_mass;
+	
+	// inverse mass (1 / lb)
+	float m_inv_mass;
+	
+	// inverse moment of inertia in vehicle's frame (1 / lb / in^2)
+	Mth::Vector m_inv_moment_body;
+	
+	// coefficient of restitution of the body
+	float m_body_restitution;
+	
+	// coefficient of friction of the body
+	float m_body_friction;
+	
+	// spring constant of the collision penalty system
+	float m_body_spring;
+	
+	// coefficient of friction of the body used when the vehicle is not upright
+	float m_body_wipeout_friction;
+	
+	// factor of normal collision impulse which you can control via your steering
+	float m_collision_control;
+	
+	// horizontal velocity cutoff below which no in-air slerping to face velocity occurs
+	float m_in_air_slerp_vel_cutoff;
+	
+	// time over which in-air slerping lerps to full strength after takeoff
+	float m_in_air_slerp_time_delay;
+	
+	// in-air slerping strength
+	float m_in_air_slerp_strength;
+	
+	// special slerping at low speeds
+	bool m_vert_correction;
+
+	// number of wheels
+	unsigned int m_num_wheels;
+	
+	// number of drive wheels; engine torque is divided between this number of wheels
+	unsigned int m_num_drive_wheels;
+	
+	// maximum steering angle
+	float m_max_steering_angle;
+	
+	// effective distance between fixed and steering axles (in); used when determining turning radius
+	float m_cornering_wheelbase;
+	
+	// distance between center of steering wheels (in); used when determining steering angles
+	float m_cornering_axle_length;
+	
+	// constant and quadratic rotational damp coefficients
+	float m_const_rotvel_damping;
+	float m_quad_rotvel_damping;
+	
+	// vehicles have rectangular feelers which they use to detect collisions with their environment
+	SCollider mp_colliders[vVP_NUM_COLLIDERS];
+	
+	// offset from the vehicle's center of mass the model's origin
+	// float m_body_model_offset;
+	
+	// body model's position
+	Mth::Vector m_body_pos;
+	
+	// if true, triangle acts as a brake and throws an ExitCar exception when the car stops
+	bool m_exitable;
+	
+	// if true, the car has no handbrake
+	bool m_no_handbrake;
+	
+	// subelements
+	
+	// wheels
+	SWheel* mp_wheels;
+	
+	// engine
+	SEngine m_engine;
+	
+	// input state
+	SControls m_controls;
+	
+	// collision cache
+	Nx::CCollCache m_collision_cache;
+	
+	// parameters controlling skater while vehicle is active
+	
+	// true if the skater should be visible while in the vehicle
+	bool m_skater_visible;
+	
+	// position offset of skater model
+	Mth::Vector m_skater_pos;
+	
+	// skater animation to use while in the vehicle
+	uint32 m_skater_anim;
+	
+	// controls the steering used for the displayed wheels and driving animation; lerps to m_controls.steering
+	float m_steering_display;
+	
+	// name of the sound setup structure from PlayerVehicleSounds that which vehicle uses
+	uint32 m_sound_setup_checksum;
+	
+	// dynamic script-controllable parameters
+	
+	// the effective gravity can be modified via script; graviy is multiplied by m_gravity_fraction over a duration of m_gravity_override_timer
+	float m_gravity_override_fraction;
+	float m_gravity_override_timer;
+	
+	// if true, the brakes are forced to be on
+	bool m_force_brake;
+	
+	// state observers
+	
+	// time since we last had a wheel on the ground (plus no body contact)
+	float m_air_time;
+	float m_air_time_no_collision;
+	
+	// latest updates maximum normal collision impulse
+	float m_max_normal_collision_impulse;
+	
+	// peer components
+	
+	CSkeletonComponent* mp_skeleton_component;
+	CInputComponent* mp_input_component;
+	CModelComponent* mp_model_component;
+	
+	// shared objects
+	
+	// shared collision point array
+	static SCollisionPoint sp_collision_points[4 * (Nx::MAX_NUM_2D_COLLISIONS_REPORTED + 1)];
+	
+	// debug
+	int m_draw_debug_lines;
+	
+	// if true, no collision detection is done and the skeleton is not updated; this is used to allow a car to settle on its suspension "off camera"
+	bool m_update_suspension_only;
+	
+	// the driver
+	CCompositeObject* mp_skater;
+	CAnimationComponent* mp_skater_animation_component;
+	CSkaterCorePhysicsComponent* mp_skater_core_physics_component;
+	CTriggerComponent* mp_skater_trigger_component;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CVehicleComponent::IsOnGround (   ) const
+{
+	return m_num_wheels_in_contact > 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32 CVehicleComponent::GetSoundSetupChecksum (   ) const
+{
+	return m_sound_setup_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CVehicleComponent::update_velocities (   )
+{
+	m_vel = m_inv_mass * m_mom;
+
+	// Zero the W component here to avoid possible NaN propogation issues.
+	m_vel[W] = 0.0f;
+	m_rotvel = m_inv_moment.Rotate(m_rotmom);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Mth::Vector CVehicleComponent::calculate_body_point_velocity ( const Mth::Vector& pos ) const
+{
+	return m_vel + Mth::CrossProduct(m_rotvel, pos);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CVehicleComponent::accumulate_force ( const Mth::Vector& force, const Mth::Vector& location, uint32 color )
+{
+	m_force += force;
+	m_torque += Mth::CrossProduct(location - m_suspension_center_of_mass_world, force);
+	
+	#if 0
+	if (m_draw_debug_lines)
+	{
+		Mth::Vector adjusted_location = location;
+		adjusted_location[Y] += 1.0f; // pull out of the ground
+		Gfx::AddDebugLine(m_pos + adjusted_location, m_pos + adjusted_location + 0.05f * force, color, color, 1);
+	}
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CVehicleComponent::accumulate_collision_force ( const Mth::Vector& force, const Mth::Vector& location )
+{
+	m_force += force;
+	m_torque += Mth::CrossProduct(location, force);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CVehicleComponent::apply_wheel_torque ( SWheel& wheel, float torque )
+{
+	float rotacc = wheel.inv_moment * torque;
+	
+	wheel.rotvel += rotacc * m_time_step;
+	
+	wheel.rotacc += rotacc;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float CVehicleComponent::calculate_stopping_torque ( const SWheel& wheel, float rotvel ) const
+{
+	return -rotvel / (m_time_step * wheel.inv_moment);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+			
+inline float CVehicleComponent::calculate_collision_depth ( const CLineFeeler& line_feeler ) const
+{
+	return (1.0f - line_feeler.GetDist()) * Mth::DotProduct(line_feeler.GetNormal(), line_feeler.m_start - line_feeler.m_end);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CVehicleComponent::apply_impulse ( const Mth::Vector& impulse, const Mth::Vector& location )
+{
+	m_mom += impulse;
+	m_rotmom += Mth::CrossProduct(location, impulse);
+	
+	update_velocities();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CVehicleComponent::very_close ( const Mth::Vector p, const Mth::Vector q ) const
+{
+	return Mth::Abs(p[X] - q[X]) < 0.1f && Mth::Abs(p[Y] - q[Y]) < 0.1f && Mth::Abs(p[Z] - q[Z]) < 0.1f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CVehicleComponent::reset_this_frame (   ) const
+{
+	return m_reset_this_frame || GetObject()->IsDead();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CVehicleComponent::trip_trigger ( ESkaterTriggerType trigger_type, CLineFeeler &feeler )
+{                            
+	mp_skater_trigger_component->CheckFeelerForTrigger(trigger_type, feeler);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CVehicleComponent::trip_trigger ( ESkaterTriggerType trigger_type, CRectFeeler &feeler )
+{                            
+	mp_skater_trigger_component->CheckFeelerForTrigger(trigger_type, feeler);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CVehicleComponent::in_artificial_collision (    )
+{                            
+	return m_artificial_collision_timer > 0.0f;
+}
+
+}
+
+#endif
diff --git a/Code/Gel/Components/weaponcomponent.cpp b/Code/Gel/Components/weaponcomponent.cpp
new file mode 100644
index 0000000..4b111b7
--- /dev/null
+++ b/Code/Gel/Components/weaponcomponent.cpp
@@ -0,0 +1,494 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WeaponComponent.cpp
+//* OWNER:          Dave
+//* CREATION DATE:  06/10/03
+//****************************************************************************
+
+// The CWeaponComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy Weaponcomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Weapon" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent* CWeaponComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CWeaponComponent );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CWeaponComponent::CWeaponComponent() : CBaseComponent()
+{
+	SetType( CRC_WEAPON );
+
+	m_sight_pos.Set( 0.0f, 0.0f, 0.0f, 1.0f );
+	m_sight_matrix.Identity();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CWeaponComponent::~CWeaponComponent()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWeaponComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+	It's commented out here.  You will also need to comment in the 
+	definition in Weaponcomponent.h
+void CWeaponComponent::Finalize()
+{
+	// Virtual function, can be overridden to provided finialization to 
+	// a component after all components have been added to an object
+	// Usually this consists of getting pointers to other components
+	// Note:  It is GUARENTEED (at time of writing) that
+	// Finalize() will be called AFTER all components are added
+	// and BEFORE the CCompositeObject::Update function is called
+	
+	// Example:
+	// mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+										
+										
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWeaponComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	InitFromStructure(pParams);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWeaponComponent::Update()
+{
+	// **  You would put in here the stuff that you would want to get run every frame
+	// **  for example, a physics type component might update the position and orientation
+	// **  and update the internal physics state 
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent::EMemberFunctionResult CWeaponComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+/*
+        // @script | DoSomething | does some functionality
+		case 0xbb4ad101:		// DoSomething
+			DoSomething();
+			break;
+
+        // @script | ValueIsTrue | returns a boolean value
+		case 0x769260f7:		// ValueIsTrue
+		{
+			return ValueIsTrue() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		break;
+*/
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWeaponComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CWeaponComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger(CRCD(0x7cf2a233,"m_never_suspend"),m_never_suspend);
+	p_info->AddFloat(CRCD(0x519ab8e0,"m_suspend_distance"),m_suspend_distance);
+	*/
+	
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWeaponComponent::ProcessStickyTarget( float heading_change, float tilt_change, float *p_extra_heading_change, float *p_extra_tilt_change )
+{
+	// Zero the extra chnage values.
+	*p_extra_heading_change = 0.0f;
+	*p_extra_tilt_change	= 0.0f;
+
+	// If the player is trying to aim, this section of code tries to 'nudge' the aim in the right direction imperceptibly by
+	// moving the cursor just a little more or less based on the requested movement of the cursor.
+	if( mp_current_target )
+	{
+		if( heading_change != 0.0f )
+		{
+			// Figure default adjustment amount.
+			float		heading_cursor_suck		= Script::GetFloat( CRCD( 0xd90b2dfb, "GunslingerLookaroundHeadingCursorSuck" ), Script::ASSERT );
+			float		default_adjustment		= heading_change * heading_cursor_suck;
+
+			Mth::Vector	immediate_camera_pos	= m_sight_pos;
+			Mth::Vector ped_pos					= mp_current_target->GetPos() + Mth::Vector( 0.0f, 36.0f, 0.0f );
+			Mth::Vector	ped_to_cam				= ped_pos - immediate_camera_pos;
+			ped_to_cam[Y] = 0.0f;
+			ped_to_cam.Normalize();
+
+			// Figure exactly what the angular difference is.
+			float		dp						= Mth::DotProduct( ped_to_cam, -m_sight_matrix[Z] );
+			float		angular_difference		= acosf( dp );
+
+			// If the angular difference is actually smaller than the default adjustment amount, make the default adjustment amount the angular difference.
+			if( Mth::Abs( angular_difference ) < Mth::Abs( default_adjustment ))
+			{
+				default_adjustment = angular_difference;
+			}
+
+			// Now save off this matrix, and increase the rotation by some percentage of the rotation added to the lookaround heading.
+			Mth::Matrix	saved_mat				= m_sight_matrix;
+			m_sight_matrix.RotateYLocal( default_adjustment );
+
+			// See if this brings us closer.
+			float		dp2						= Mth::DotProduct( ped_to_cam, -m_sight_matrix[Z] );
+			if( dp2 > dp )
+			{
+				// Yes it did, so store this change.
+				*p_extra_heading_change			= default_adjustment;
+			}
+			else
+			{
+				// No it didn't so revert to the old matrix.
+				m_sight_matrix = saved_mat;
+
+				// Maybe this change went too far? Try undoing 10% of the last change.
+				m_sight_matrix.RotateYLocal( -default_adjustment );
+
+				// See if this brings us closer.
+				float	dp3						= Mth::DotProduct( ped_to_cam, -m_sight_matrix[Z] );
+				if( dp3 > dp )
+				{
+					// Yes it did, so store this change.
+					*p_extra_heading_change		= -default_adjustment;
+				}
+				else
+				{
+					// No it didn't so revert to the old matrix.
+					m_sight_matrix				= saved_mat;
+				}
+			}
+		}
+	}
+
+	if( mp_current_target )
+	{
+		if( tilt_change != 0.0f )
+		{
+			// Figure default adjustment amount.
+			float		tilt_cursor_suck		= Script::GetFloat( CRCD( 0x4ece2698, "GunslingerLookaroundTiltCursorSuck" ), Script::ASSERT );
+			float		default_adjustment		= tilt_change * tilt_cursor_suck;
+
+			Mth::Vector	immediate_camera_pos	= m_sight_pos;
+			Mth::Vector ped_pos					= mp_current_target->GetPos() + Mth::Vector( 0.0f, 36.0f, 0.0f );
+			Mth::Vector	ped_to_cam				= ped_pos - immediate_camera_pos;
+			ped_to_cam.Normalize();
+
+			// Figure exactly what the angular difference is.
+			float		dp						= Mth::DotProduct( ped_to_cam, -m_sight_matrix[Z] );
+			float		angular_difference		= acosf( dp );
+
+			// If the angular difference is actually smaller than the default adjustment amount, make the default adjustment amount the angular difference.
+			if( Mth::Abs( angular_difference ) < Mth::Abs( default_adjustment ))
+			{
+				default_adjustment = angular_difference;
+			}
+
+			// Now save off this matrix, and increase the rotation by some percentage of the rotation added to the lookaround heading.
+			Mth::Matrix	saved_mat				= m_sight_matrix;
+			m_sight_matrix.RotateXLocal( default_adjustment );
+
+			// See if this brings us closer.
+			float		dp2						= Mth::DotProduct( ped_to_cam, -m_sight_matrix[Z] );
+			if( dp2 > dp )
+			{
+				// Yes it did, so store this change.
+				*p_extra_tilt_change			= default_adjustment;
+			}
+			else
+			{
+				// No it didn't so revert to the old matrix.
+				m_sight_matrix = saved_mat;
+
+				// Maybe this change went too far? Try undoing 10% of the last change.
+				m_sight_matrix.RotateXLocal( -default_adjustment );
+
+				// See if this brings us closer.
+				float	dp3						= Mth::DotProduct( ped_to_cam, -m_sight_matrix[Z] );
+
+				if( dp3 > dp )
+				{
+					// Yes it did, so store this change.
+					*p_extra_tilt_change		= -default_adjustment;
+				}
+				else
+				{
+					// No it didn't so revert to the old matrix.
+					m_sight_matrix				= saved_mat;
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWeaponComponent::DrawReticle( void )
+{
+	// Draw the reticle, red if enemy is within sights, white otherwise.
+	if( m_spin_modulator < 1.0f )
+	{
+		Gfx::AddDebugStar( m_reticle_max, 24.0f, MAKE_RGB( 255, 0, 0 ), 1 );
+	}
+	else
+	{
+		Gfx::AddDebugStar( m_reticle_max, 24.0f, MAKE_RGB( 255, 255, 255 ), 1 );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWeaponComponent::Fire( void )
+{
+	if( mp_current_target )
+	{
+		mp_current_target->SelfEvent( CRCD( 0xfaeec40f, "SkaterInAvoidRadius" ));
+	}
+
+	Script::RunScript( CRCD( 0xe03997d0, "PlayGunshot" ));
+}
+
+
+
+// These values should really be specified per-weapon.
+const float SPIN_MODULATION_ANGLE = 0.9848f;	// Cosine of 10 degrees.
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CCompositeObject* CWeaponComponent::GetCurrentTarget( Mth::Vector& start_pos, Mth::Vector* p_reticle_max )
+{
+	// This is the maximum distance that we will check for targets.
+	float target_distance = Script::GetFloat( CRCD( 0x89495011,"GunslingerTargetDistance" ), Script::ASSERT );
+
+	Mth::Vector reticle_min		= start_pos;
+	m_reticle_max				= reticle_min - ( m_sight_matrix[Z] * target_distance );
+
+	// Ignore faces based on face flags.
+	CFeeler feeler;
+	feeler.SetIgnore((uint16)( mFD_NON_COLLIDABLE | mFD_NON_CAMERA_COLLIDABLE ), 0 );
+	feeler.SetLine( reticle_min, m_reticle_max );
+	bool collision = feeler.GetCollision( true );
+	if( collision )
+	{
+		m_reticle_max = feeler.GetPoint();
+		
+		// Set the new target distance at the point of contact, to avoid considering targets beyond the collision point.
+		target_distance	= target_distance * feeler.GetDist();
+	}
+
+	// Only targets within the allowed angle will be considered.
+							mp_current_target	= NULL;
+	float					best_dp				= SPIN_MODULATION_ANGLE;
+	Mth::Vector				best_pos;
+
+	// Rather than cycling through the ped logic components, eventually everything that is targettable should contain a targetting component.
+	Obj::CPedLogicComponent *p_ped_logic_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_PEDLOGIC ));
+	while( p_ped_logic_component )
+	{
+		Obj::CCompositeObject *p_ped = p_ped_logic_component->GetObject();
+
+		// Have to hack in a height adjustment here - the default position appears to be at ground level.
+		Mth::Vector ped_pos		= p_ped->GetPos() + Mth::Vector( 0.0f, 36.0f, 0.0f );
+
+		// Want to get the position of this ped.
+		Mth::Vector	ped_to_cam	= ped_pos - reticle_min;
+
+		// Check not too far away.
+		if( ped_to_cam.LengthSqr() < ( target_distance * target_distance ))
+		{
+			ped_to_cam.Normalize();
+
+			// Calculate angle this ped subtends at the camera position.
+			float		dp			= Mth::DotProduct( ped_to_cam, -m_sight_matrix[Z] );
+			if( dp > best_dp )
+			{
+				best_dp				= dp;
+				best_pos			= ped_pos;
+				mp_current_target	= p_ped;
+			}
+		}
+		p_ped_logic_component			= static_cast( p_ped_logic_component->GetNextSameType());
+	}
+
+	// Reset spin and tilt modulators.
+	m_spin_modulator	= 1.0f;
+	m_tilt_modulator	= 1.0f;
+
+	if( mp_current_target )
+	{
+		// We have a potential candidate for targetting.
+		// However we still want to check that it is close enough to our line of sight.
+		float dist_to_ped	= Mth::Distance( best_pos, reticle_min );
+		float dist_to_line	= dist_to_ped * sqrtf( 1.0f - ( best_dp * best_dp ));
+
+		// This value should really be per-weapon in some script array.
+		if( dist_to_line < Script::GetFloat( CRCD( 0x755235db, "GunslingerLookaroundModulationDistance" ), Script::ASSERT ))
+		{
+			// Is this the same target as last time?
+//			if( mp_best_target == p_selected_target )
+//			{
+//				// If so, increase the targetting timer.
+//				target_selection_timer += frame_length;
+//			}
+//			else
+//			{
+//				// Otherwise, reset the timer.
+//				p_selected_target		= mp_best_target;
+//				target_selection_timer	= frame_length;
+//			}
+
+			// Modulator gets smaller as result tends to 1.0.
+			float max_damping = Script::GetFloat( CRCD( 0xef314a12, "GunslingerLookaroundDamping" ), Script::ASSERT );
+			m_spin_modulator = 1.0f - ( max_damping * (( best_dp - SPIN_MODULATION_ANGLE ) / ( 1.0f - SPIN_MODULATION_ANGLE )));
+
+//			if( gun_fired )
+//			{
+//				// Make this ped fall down.
+//				mp_best_target->SelfEvent( CRCD( 0xfaeec40f, "SkaterInAvoidRadius" ));
+//			}
+		}
+		else
+		{
+			mp_current_target = NULL;
+		}
+	}
+
+	// Currently, set tilt modulator to be the same as spin modulator.
+	m_tilt_modulator = m_spin_modulator;
+
+	if( p_reticle_max )
+	{
+		*p_reticle_max = m_reticle_max;
+	}
+
+	return mp_current_target;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Gel/Components/weaponcomponent.h b/Code/Gel/Components/weaponcomponent.h
new file mode 100644
index 0000000..3da5548
--- /dev/null
+++ b/Code/Gel/Components/weaponcomponent.h
@@ -0,0 +1,71 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       WeaponComponent.h
+//* OWNER:          Dave
+//* CREATION DATE:  06/10/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_WEAPONCOMPONENT_H__
+#define __COMPONENTS_WEAPONCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_WEAPON CRCD( 0x96cc5819, "Weapon" )
+
+#define		GetWeaponComponent()				((Obj::CWeaponComponent*)GetComponent(CRC_WEAPON))
+#define		GetWeaponComponentFromObject(pObj)	((Obj::CWeaponComponent*)(pObj)->GetComponent(CRC_WEAPON))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CWeaponComponent : public CBaseComponent
+{
+public:
+									CWeaponComponent();
+									virtual ~CWeaponComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+//	virtual	void 					Finalize();
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	CCompositeObject*				GetCurrentTarget( Mth::Vector& start_pos, Mth::Vector* p_reticle_max );
+	void							SetCurrentTarget( CCompositeObject* p_obj )	{ mp_current_target = p_obj; }
+	void							SetSightPos( Mth::Vector& p )				{ m_sight_pos = p; }
+	void							SetSightMatrix( Mth::Matrix& s )			{ m_sight_matrix = s; }
+	void							DrawReticle( void );
+	void							ProcessStickyTarget( float heading_change, float tilt_change, float *p_extra_heading_change, float *p_extra_tilt_change );
+	void							Fire( void );
+
+	float							GetSpinModulator( void )					{ return m_spin_modulator; }
+	float							GetTiltModulator( void )					{ return m_tilt_modulator; }
+
+	static CBaseComponent*			s_create();
+
+private:
+
+	Mth::Matrix						m_sight_matrix;
+	Mth::Vector						m_sight_pos;
+	Mth::Vector						m_reticle_max;			// Position of reticle max point from last collision check.
+	CCompositeObject*				mp_current_target;
+
+	float							m_spin_modulator;
+	float							m_tilt_modulator;
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Environment/terrain.cpp b/Code/Gel/Environment/terrain.cpp
new file mode 100644
index 0000000..36a2aed
--- /dev/null
+++ b/Code/Gel/Environment/terrain.cpp
@@ -0,0 +1,935 @@
+/*
+	This is just a database for arrays of skater sounds on different terrains.
+	
+	For each type of sound in sounds.h (like grind, land, slide, wheel roll, etc...)
+	there is an array of sounds to play, one for each possible terrain type.
+	
+	A terrain type of zero indicates that the sound is the default (for surfaces not
+	defined in the list).
+*/
+
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+
+
+namespace Env
+{
+
+#ifdef __NOPT_ASSERT__
+bool					CTerrainManager::s_terrain_loaded[ vNUM_TERRAIN_TYPES ];
+#endif __NOPT_ASSERT__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ETerrainType			CTerrainManager::sGetTerrainFromChecksum(uint32 checksum)
+{
+	switch (checksum)
+	{
+	case 0x64967688: // TERRAIN_DEFAULT
+		return vTERRAIN_DEFAULT;
+
+	case 0x9b37fa90: // TERRAIN_CONCSMOOTH
+		return vTERRAIN_CONCSMOOTH;   
+
+	case 0x9383172: // TERRAIN_CONCROUGH
+		return vTERRAIN_CONCROUGH;
+
+	case 0xa9ecf4e9: // TERRAIN_METALSMOOTH
+		return vTERRAIN_METALSMOOTH;
+
+	case 0xa5aab5e: // TERRAIN_METALROUGH
+		return vTERRAIN_METALROUGH;   
+
+	case 0x67af2cb5: // TERRAIN_METALCORRUGATED
+		return vTERRAIN_METALCORRUGATED;  
+
+	case 0x1134b31a: // TERRAIN_METALGRATING
+		return vTERRAIN_METALGRATING;
+
+	case 0xd814ef41: // TERRAIN_METALTIN
+		return vTERRAIN_METALTIN; 
+
+	case 0x39075ea5: // TERRAIN_WOOD
+		return vTERRAIN_WOOD;   
+
+	case 0x3afca53f: // TERRAIN_WOODMASONITE
+		return vTERRAIN_WOODMASONITE;
+
+	case 0x6651a5d0: // TERRAIN_WOODPLYWOOD
+		return vTERRAIN_WOODPLYWOOD;
+
+	case 0xd11a620d: // TERRAIN_WOODFLIMSY
+		return vTERRAIN_WOODFLIMSY;   
+
+	case 0xd3e04021: // TERRAIN_WOODSHINGLE
+		return vTERRAIN_WOODSHINGLE;
+
+	case 0x866b2b83: // TERRAIN_WOODPIER
+		return vTERRAIN_WOODPIER; 
+
+	case 0x3d5a952c: // TERRAIN_BRICK
+		return vTERRAIN_BRICK;
+
+	case 0x7315eeac: // TERRAIN_TILE
+		return vTERRAIN_TILE;   
+
+	case 0x6aa69ead: // TERRAIN_ASPHALT
+		return vTERRAIN_ASPHALT;  
+
+	case 0x32d3fc0a: // TERRAIN_ROCK
+		return vTERRAIN_ROCK;   
+
+	case 0x67f6ee4c: // TERRAIN_GRAVEL
+		return vTERRAIN_GRAVEL;   
+
+	case 0x103bb3b8: // TERRAIN_SIDEWALK
+		return vTERRAIN_SIDEWALK; 
+
+	case 0xa207c1e3: // TERRAIN_GRASS
+		return vTERRAIN_GRASS;   
+
+	case 0x802c8f14: // TERRAIN_GRASSDRIED
+		return vTERRAIN_GRASSDRIED;   
+
+	case 0x9dfda61e: // TERRAIN_DIRT
+		return vTERRAIN_DIRT;   
+
+	case 0xd748f9b7: // TERRAIN_DIRTPACKED
+		return vTERRAIN_DIRTPACKED;   
+
+	case 0xf1394aca: // TERRAIN_WATER
+		return vTERRAIN_WATER;   
+
+	case 0x5e354fef: // TERRAIN_ICE
+		return vTERRAIN_ICE;   
+
+	case 0x3319e21b: // TERRAIN_SNOW
+		return vTERRAIN_SNOW;   
+
+	case 0xa5e0d5b9: // TERRAIN_SAND
+		return vTERRAIN_SAND;   
+
+	case 0x6362cc4f: // TERRAIN_PLEXIGLASS
+		return vTERRAIN_PLEXIGLASS;   
+
+	case 0x66987ee0: // TERRAIN_FIBERGLASS
+		return vTERRAIN_FIBERGLASS;   
+
+	case 0x8e6a5d9d: // TERRAIN_CARPET
+		return vTERRAIN_CARPET;   
+
+	case 0x2ee8ef42: // TERRAIN_CONVEYOR
+		return vTERRAIN_CONVEYOR; 
+
+	case 0xbe7c0e6e: // TERRAIN_CHAINLINK
+		return vTERRAIN_CHAINLINK;
+
+	case 0x14259681: // TERRAIN_METALFUTURE
+		return vTERRAIN_METALFUTURE;
+
+	case 0xbb1ac0d4: // TERRAIN_GENERIC1
+		return vTERRAIN_GENERIC1; 
+
+	case 0x2213916e: // TERRAIN_GENERIC2
+		return vTERRAIN_GENERIC2;
+
+	case 0x83b85f36: // TERRAIN_WHEELS
+		return vTERRAIN_WHEELS;   
+
+	case 0x22dd3d51: // TERRAIN_WETCONC
+		return vTERRAIN_WETCONC;
+
+	case 0xfa298f50: // TERRAIN_METALFENCE
+		return vTERRAIN_METALFENCE;
+
+	case 0x69803ba4: // TERRAIN_GRINDTRAIN
+		return vTERRAIN_GRINDTRAIN;
+
+	case 0xed035a39: // TERRAIN_GRINDROPE
+		return vTERRAIN_GRINDROPE;
+
+	case 0xec66b43b: // TERRAIN_GRINDWIRE
+		return vTERRAIN_GRINDWIRE;
+
+	case CRCC(0x3884f029,"TERRAIN_GRINDCONC"):
+		return vTERRAIN_GRINDCONC;
+
+	case CRCC(0x92a2112a,"TERRAIN_GRINDROUNDMETALPOLE"):
+		return vTERRAIN_GRINDROUNDMETALPOLE;
+
+	case CRCC(0xd7e88122,"TERRAIN_GRINDCHAINLINK"):
+		return vTERRAIN_GRINDCHAINLINK;
+
+	case CRCC(0xf5842bce,"TERRAIN_GRINDMETAL"):
+		return vTERRAIN_GRINDMETAL;
+
+	case CRCC(0xffc0b5e,"TERRAIN_GRINDWOODRAILING"):
+		return vTERRAIN_GRINDWOODRAILING;
+
+	case CRCC(0x8aadc3d0,"TERRAIN_GRINDWOODLOG"):
+		return vTERRAIN_GRINDWOODLOG;
+
+	case CRCC(0x60809403,"TERRAIN_GRINDWOOD"):
+		return vTERRAIN_GRINDWOOD;
+
+	case CRCC(0xf862cfe0,"TERRAIN_GRINDPLASTIC"):
+		return vTERRAIN_GRINDPLASTIC;
+
+	case CRCC(0x2bb33575,"TERRAIN_GRINDELECTRICWIRE"):
+		return vTERRAIN_GRINDELECTRICWIRE;
+
+	case CRCC(0x110f1bd3,"TERRAIN_GRINDCABLE"):
+		return vTERRAIN_GRINDCABLE;
+
+	case CRCC(0x84e4c7cd,"TERRAIN_GRINDCHAIN"):
+		return vTERRAIN_GRINDCHAIN;
+
+	case CRCC(0x3f1c35c5,"TERRAIN_GRINDPLASTICBARRIER"):
+		return vTERRAIN_GRINDPLASTICBARRIER;
+
+	case CRCC(0x6c4a1c6a,"TERRAIN_GRINDNEONLIGHT"):
+		return vTERRAIN_GRINDNEONLIGHT;
+
+	case CRCC(0xcaf52d56,"TERRAIN_GRINDGLASSMONSTER"):
+		return vTERRAIN_GRINDGLASSMONSTER;
+
+	case CRCC(0x8da017a8,"TERRAIN_GRINDBANYONTREE"):
+		return vTERRAIN_GRINDBANYONTREE;
+
+	case CRCC(0x7825b83c,"TERRAIN_GRINDBRASSRAIL"):
+		return vTERRAIN_GRINDBRASSRAIL;
+
+	case CRCC(0x5ce9c824,"TERRAIN_GRINDCATWALK"):
+		return vTERRAIN_GRINDCATWALK;
+
+	case CRCC(0x66ab4005,"TERRAIN_GRINDTANKTURRET"):
+		return vTERRAIN_GRINDTANKTURRET;
+
+	case 0x24dca61: // TERRAIN_MEATALSMOOTH		// old typo that still exists
+		Dbg_Message("Metal is not spelled 'Meatal'");
+	default:
+		Dbg_MsgAssert(0, ("Unknown terrain checksum %x\n", checksum));
+		return vTERRAIN_DEFAULT;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ETerrainActionType		CTerrainManager::sGetActionFromChecksum(uint32 checksum)
+{
+	switch (checksum)
+	{
+	case 0xcaebd8e: // TA_ROLL
+		return vTABLE_WHEELROLL;
+
+	case 0x7e5d9202: // TA_GRIND
+		return vTABLE_GRIND;
+
+	case 0xc0669dd5: // TA_OLLIE
+		return vTABLE_JUMP;
+
+	case 0x8a1b5a98: // TA_LAND
+		return vTABLE_LAND;
+
+	case 0xf0e51d30: // TA_BONK
+		return vTABLE_BONK;
+
+	case 0x6ac1023d: // TA_GRINDJUMP
+		return vTABLE_GRINDJUMP;
+
+	case 0x6572d1f3: // TA_GRINDLAND
+		return vTABLE_GRINDLAND;
+
+	case 0xd6135bf0: // TA_SLIDE
+		return vTABLE_SLIDE;
+
+	case 0x6e27388f: // TA_SLIDEJUMP
+		return vTABLE_SLIDEJUMP;
+
+	case 0x6194eb41: // TA_SLIDELAND
+		return vTABLE_SLIDELAND;
+
+	case 0x20744cde: // TA_REVERT
+		return vTABLE_CESS;
+
+	default:
+		Dbg_MsgAssert(0, ("Unknown terrain action checksum %x\n", checksum));
+		return vTABLE_WHEELROLL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32					CTerrainManager::s_get_action_checksum(ETerrainActionType action)
+{
+	switch (action)
+	{
+	case vTABLE_WHEELROLL:
+		return 0xcaebd8e; // TA_ROLL
+
+	case vTABLE_GRIND:
+		return 0x7e5d9202; // TA_GRIND
+
+	case vTABLE_JUMP:
+		return 0xc0669dd5; // TA_OLLIE
+
+	case vTABLE_LAND:
+		return 0x8a1b5a98; // TA_LAND
+
+	case vTABLE_BONK:
+		return 0xf0e51d30; // TA_BONK
+
+	case vTABLE_GRINDJUMP:
+		return 0x6ac1023d; // TA_GRINDJUMP
+
+	case vTABLE_GRINDLAND:
+		return 0x6572d1f3; // TA_GRINDLAND
+
+	case vTABLE_SLIDE:
+		return 0xd6135bf0; // TA_SLIDE
+
+	case vTABLE_SLIDEJUMP:
+		return 0x6e27388f; // TA_SLIDEJUMP
+
+	case vTABLE_SLIDELAND:
+		return 0x6194eb41; // TA_SLIDELAND
+
+	case vTABLE_CESS:
+		return 0x20744cde; // TA_REVERT
+
+	default:
+		Dbg_MsgAssert(0, ("Unknown terrain action %d\n", action));
+		return 0xcaebd8e; // TA_ROLL
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CTerrainManager::s_get_terrain_actions_struct(ETerrainType terrain, Script::CStruct *p_actions)
+{
+	Dbg_MsgAssert(p_actions, ("s_get_terrain_actions_struct given NULL CStruct"));
+
+	Script::CStruct *p_default_actions = NULL;
+	Script::CStruct *p_terrain_actions = NULL;
+	Script::CStruct *p_level_default_actions = NULL;
+	Script::CStruct *p_level_terrain_actions = NULL;
+
+	uint32 terrain_checksum;
+	ETerrainType terrain_type;
+
+	// Search Default Terrain array first
+	Script::CArray *pDefaultTerrainArray = Script::GetArray(CRCD(0x1c2c4218,"standard_terrain"));
+	Dbg_MsgAssert(pDefaultTerrainArray, ("standard_terrain not found"));
+
+	for (uint i = 0; (!p_default_actions || !p_terrain_actions) && i < pDefaultTerrainArray->GetSize(); i++)
+	{
+		// Get next terrain struct
+		Script::CStruct *pTerrainStruct = pDefaultTerrainArray->GetStructure(i);
+		Dbg_MsgAssert(pTerrainStruct, ("terrain array not made of structures"));
+
+		pTerrainStruct->GetChecksum(CRCD(0x3789ac4e,"Terrain"), &terrain_checksum, Script::ASSERT);
+		terrain_type = sGetTerrainFromChecksum(terrain_checksum);
+
+		if (terrain_type == vTERRAIN_DEFAULT)
+		{
+			pTerrainStruct->GetStructure(CRCD(0x5c969c50,"SoundActions"), &p_default_actions);
+			//Dbg_Message("Found default terrain:");
+			//Script::PrintContents(p_default_actions);
+		}
+		else if (terrain_type == terrain)
+		{
+			pTerrainStruct->GetStructure(CRCD(0x5c969c50,"SoundActions"), &p_terrain_actions);
+			//Dbg_Message("Found terrain:");
+			//Script::PrintContents(p_terrain_actions);
+		}
+
+		if (p_default_actions && p_terrain_actions)
+		{
+			break;
+		}
+	}
+
+	// Search Level Terrain array
+	Script::CArray *pLevelTerrainArray = Script::GetArray(CRCD(0x96218951,"level_terrain"));
+	if (pLevelTerrainArray)
+	{
+		for (uint i = 0; i < pLevelTerrainArray->GetSize(); i++)
+		{
+			// Get next terrain struct
+			Script::CStruct *pTerrainStruct = pLevelTerrainArray->GetStructure(i);
+			Dbg_MsgAssert(pTerrainStruct, ("terrain array not made of structures"));
+
+			pTerrainStruct->GetChecksum(CRCD(0x3789ac4e,"Terrain"), &terrain_checksum, Script::ASSERT);
+			terrain_type = sGetTerrainFromChecksum(terrain_checksum);
+
+			if (terrain_type == vTERRAIN_DEFAULT)
+			{
+				pTerrainStruct->GetStructure(CRCD(0x5c969c50,"SoundActions"), &p_level_default_actions);
+			} else if (terrain_type == terrain)
+			{
+				pTerrainStruct->GetStructure(CRCD(0x5c969c50,"SoundActions"), &p_level_terrain_actions);
+			}
+
+			if (p_level_default_actions && p_level_terrain_actions)
+			{
+				break;
+			}
+		}
+	}
+
+	// Combine them together
+	*p_actions = *p_default_actions;
+
+	if (p_level_default_actions)
+	{
+		*p_actions += *p_level_default_actions;
+	}
+
+	if (p_terrain_actions)
+	{
+		*p_actions += *p_terrain_actions;
+	}
+
+	if (p_level_terrain_actions)
+	{
+		*p_actions += *p_level_terrain_actions;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct *		CTerrainManager::s_choose_sound_action(Script::CStruct *p_sound_action_struct)
+{
+	Script::CStruct* p_sound;
+	
+	// soundAction is a single stucture, so just use it
+	if (p_sound_action_struct->GetStructure(CRCD(0x25f654c8, "soundAction"), &p_sound))
+	{
+		Dbg_MsgAssert(!p_sound->ContainsComponentNamed(CRCD(0xdbc4c4db, "chance")),
+			("A sound may only include a chance parameter when it is an element of a random-choice list of sounds"));
+		return p_sound;
+	}
+	
+	// soundAction is an array of structures, so choose one randomly based on their given chances
+	Script::CArray* p_random_indexed_sound_array;
+	p_sound_action_struct->GetArray(CRCD(0x25f654c8, "soundAction"), &p_random_indexed_sound_array, Script::ASSERT);
+	
+	int total_chance = 0;
+	for (int sound_idx = p_random_indexed_sound_array->GetSize(); sound_idx--; )
+	{
+		p_sound = p_random_indexed_sound_array->GetStructure(sound_idx);
+		
+		int chance;
+		p_sound->GetInteger(CRCD(0xdbc4c4db, "chance"), &chance, Script::ASSERT);
+		
+		total_chance += chance;
+	}
+	
+	int choice = Mth::Rnd(total_chance);
+	
+	for (int sound_idx = p_random_indexed_sound_array->GetSize(); sound_idx--; )
+	{
+		p_sound = p_random_indexed_sound_array->GetStructure(sound_idx);
+		
+		int chance;
+		p_sound->GetInteger(CRCD(0xdbc4c4db, "chance"), &chance, Script::ASSERT);
+		
+		choice -= chance;
+		if (choice < 0)
+		{
+			return p_sound;
+		}
+	}
+	
+	Dbg_Assert(false);
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct *		CTerrainManager::s_get_action_sound_struct(Script::CStruct *p_actions, ETerrainActionType action, float soundChoice)
+{
+	Dbg_MsgAssert(p_actions, ("s_get_action_sound_struct given NULL action CStruct"));
+
+	Script::CStruct *p_sound;
+	
+	uint32 checksum = s_get_action_checksum(action);
+
+	// sound is a single sound structure, so just use it
+	if (p_actions->GetStructure(checksum, &p_sound))
+	{
+		return p_sound;
+	}
+	
+	// sound is an array of structures, one of which must be choosen depending upon speed
+	Script::CArray* p_float_indexed_sound_array;
+	p_actions->GetArray(checksum, &p_float_indexed_sound_array, Script::ASSERT);
+	
+	// check to insure that the final structure in the array has no useUpTo parameter and thus operates as a default choice
+	Dbg_MsgAssert(!p_float_indexed_sound_array->GetStructure(p_float_indexed_sound_array->GetSize() - 1)->ContainsComponentNamed(CRCD(0x9ee9a09a, "useUpTo")),
+		("Final sound in a speed-indexed sound list must not include useUpTo"));
+	
+	for (uint32 sound_action_idx = 0; sound_action_idx < p_float_indexed_sound_array->GetSize(); sound_action_idx++)
+	{
+		// each structure contains a useUpTo parameter which is checked against speed in turn; if found to be larger
+		// than speed, the sound is used; a structure lacking useUpTo is always used
+		Script::CStruct* p_sound_action_struct = p_float_indexed_sound_array->GetStructure(sound_action_idx);
+		
+		float up_to_speed;
+		if (!p_sound_action_struct->GetFloat(CRCD(0x9ee9a09a, "useUpTo"), &up_to_speed))
+		{
+			Dbg_MsgAssert(sound_action_idx == p_float_indexed_sound_array->GetSize() - 1,
+				("Only the final sound in a float-indexed sound list may fail to include useUpTo"));
+			return s_choose_sound_action(p_sound_action_struct);
+		}
+		
+		if (soundChoice <= up_to_speed)
+		{
+			return s_choose_sound_action(p_sound_action_struct);
+		}
+	}
+	
+	Dbg_Assert(false);
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CTerrainManager::s_get_sound_info(Script::CStruct *p_sound, STerrainSoundInfo *p_info)
+{
+	Dbg_MsgAssert(p_info, ("s_get_sound_info given NULL STerrainSoundInfo"));
+
+	// Set defaults
+	p_info->mp_soundName = NULL;
+	p_info->m_soundChecksum = 0;
+	p_info->m_maxPitch = 100.0f;
+	p_info->m_minPitch = 100.0f;
+	p_info->m_maxVol = 100.0f;
+	p_info->m_minVol = 0.0f;
+	p_info->m_loadPitch = 100.0f;
+	p_info->m_loadVol = 100.0f;
+
+	p_sound->GetString( CRCD(0x7713c7b,"sound"), &p_info->mp_soundName );
+	if ( !p_info->mp_soundName )
+	{
+		Dbg_MsgAssert(0, ( "Need the name of a sound in s_get_sound_info" ));
+		return false;
+	}
+
+	// Calculate checksum
+	const char	*pNameMinusPath	= p_info->mp_soundName;
+	int			stringLength	= strlen( p_info->mp_soundName );
+	for( int i = 0; i < stringLength; i++ )
+	{
+		if(( p_info->mp_soundName[i] == '\\' ) || ( p_info->mp_soundName[i] == '/' ))
+			pNameMinusPath = &p_info->mp_soundName[i + 1];
+	}
+	
+	p_info->m_soundChecksum = Script::GenerateCRC( pNameMinusPath );
+
+	p_sound->GetFloat( CRCD(0xfa3e14c5,"maxPitch"), &p_info->m_maxPitch );
+	p_sound->GetFloat( CRCD(0x1c5ebb24,"minPitch"), &p_info->m_minPitch );
+	
+	p_sound->GetFloat( CRCD(0x693daaf,"maxVol"), &p_info->m_maxVol );
+	p_sound->GetFloat( CRCD(0x4391992d,"minVol"), &p_info->m_minVol );
+
+	p_sound->GetFloat( CRCD(0xf573682f,"loadPitch"), &p_info->m_loadPitch );
+	p_sound->GetFloat( CRCD(0x312ccbcf,"loadVol"), &p_info->m_loadVol );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CTerrainManager::s_load_action_sound( Script::CStruct* p_sound )
+{
+	Dbg_MsgAssert(p_sound, ("s_load_action_sound given NULL sound CStruct"));
+	
+	STerrainSoundInfo soundInfo;
+	#ifdef	__NOPT_ASSERT__
+	bool result = 
+	#endif
+	s_get_sound_info(p_sound, &soundInfo);
+	Dbg_Assert(result);
+
+	Sfx::CSfxManager::Instance()->LoadSound( soundInfo.mp_soundName, 0, 0.0f, soundInfo.m_loadPitch, soundInfo.m_loadVol );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CTerrainManager::s_load_action_sound_struct( Script::CStruct* p_actions, ETerrainActionType action )
+{
+	Dbg_MsgAssert(p_actions, ("s_load_action_sound_struct given NULL action CStruct"));
+
+	Script::CStruct *p_sound;
+
+	uint32 checksum = s_get_action_checksum(action);
+
+	// sound is a single sound structure, so just load it
+	if (p_actions->GetStructure(checksum, &p_sound))
+	{
+		s_load_action_sound(p_sound);
+		return;
+	}
+	
+	// sound is an array of sound structures, all of which must be loaded
+	Script::CArray* p_float_indexed_sound_array;
+	p_actions->GetArray(checksum, &p_float_indexed_sound_array, Script::ASSERT);
+	
+	for (uint32 i = 0; i < p_float_indexed_sound_array->GetSize(); i++)
+	{
+		Script::CStruct* p_sound_action_struct = p_float_indexed_sound_array->GetStructure(i);
+		
+		// this entry is a single sound structure, so just load it
+		if (p_sound_action_struct->GetStructure(CRCD(0x25f654c8, "soundAction"), &p_sound))
+		{
+			s_load_action_sound(p_sound);
+			continue;
+		}
+		
+		// this entry is an array of sounds, all of which must be loaded
+		Script::CArray* p_random_indexed_sound_array;
+		p_sound_action_struct->GetArray(CRCD(0x25f654c8, "soundAction"), &p_random_indexed_sound_array, Script::ASSERT);
+		
+		for (uint32 j = 0; j < p_random_indexed_sound_array->GetSize(); j++)
+		{
+			p_sound = p_random_indexed_sound_array->GetStructure(j);
+			s_load_action_sound(p_sound);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __NOPT_ASSERT__
+void					CTerrainManager::s_check_terrain_loaded(ETerrainType terrain)
+{
+	// Garrett: Comment the assert back in when the new plugin is released (on or after 2/20/03)
+	if (!s_terrain_loaded[terrain])
+	{
+//		Dbg_Message("****** ERROR: Trying to use terrain %d, which isn't loaded ******", terrain);
+	}
+//	Dbg_MsgAssert(s_terrain_loaded[terrain], ("****** ERROR: Trying to use terrain %d, which isn't loaded ******", terrain));
+}
+#endif __NOPT_ASSERT__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CTerrainManager::sReset()
+{
+#ifdef __NOPT_ASSERT__
+	// Clear terrain loaded array
+	for (int i = 0; i < vNUM_TERRAIN_TYPES; i++)
+	{
+		s_terrain_loaded[i] = false;
+	}
+#endif __NOPT_ASSERT__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CTerrainManager::sGetTerrainSoundInfo( STerrainSoundInfo *p_info, ETerrainType terrain,
+															   ETerrainActionType action, float soundChoice )
+{
+	bool result = false;
+
+	if (Sfx::NoSoundPlease()) return false;
+
+	Dbg_MsgAssert((terrain >= 0) && (terrain < vNUM_TERRAIN_TYPES), ("Terrain type out of range"));
+
+#ifdef __NOPT_ASSERT__
+	// Check that terrain is loaded
+	s_check_terrain_loaded(terrain);
+#endif __NOPT_ASSERT__
+
+	Dbg_MsgAssert(p_info, ("sGetTerrainSoundInfo given NULL STerrainSoundInfo"));
+
+	// Allocate new script structure for combining data
+	Script::CStruct *p_actions = new Script::CStruct;
+	Script::CStruct *p_sound;
+
+	if (s_get_terrain_actions_struct(terrain, p_actions))
+	{
+		p_sound = s_get_action_sound_struct(p_actions, action, soundChoice);
+		if (p_sound)
+		{
+			//Dbg_Message("Extracted sound:");
+			//Script::PrintContents(p_sound);
+
+			result = s_get_sound_info(p_sound, p_info);
+		}
+	}
+
+	// Done with script data
+	delete p_actions;
+
+	return result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CTerrainManager::sLoadTerrainSounds( ETerrainType terrain )
+{
+	if (Sfx::NoSoundPlease()) return false;
+
+	Dbg_MsgAssert((terrain >= 0) && (terrain < vNUM_TERRAIN_TYPES), ("Terrain type out of range"));
+
+	Script::CStruct *p_actions = new Script::CStruct;
+
+//	Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+//	Dbg_Assert(sfx_manager);
+
+	if (s_get_terrain_actions_struct(terrain, p_actions))
+	{
+		// Go through each action
+		for (int action_idx = 0; action_idx < vNUM_ACTION_TYPES; action_idx++)
+		{
+			s_load_action_sound_struct(p_actions, (ETerrainActionType) action_idx);
+		}
+	}
+
+	// Done with script data
+	delete p_actions;
+
+#ifdef __NOPT_ASSERT__
+	// Mark terrain loaded
+	s_terrain_loaded[terrain] = true;
+#endif __NOPT_ASSERT__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CTerrainManager::sPlaySound( ETerrainActionType action, ETerrainType terrain, const Mth::Vector &pos,
+													 float volPercent, float pitchPercent, float soundChoice, bool propogate )
+{
+	if (Sfx::NoSoundPlease()) return;
+	
+	Dbg_MsgAssert((terrain >= 0) && (terrain < vNUM_TERRAIN_TYPES), ("Terrain type out of range"));
+
+#ifdef __NOPT_ASSERT__
+	// Check that terrain is loaded
+	s_check_terrain_loaded(terrain);
+#endif __NOPT_ASSERT__
+
+	// need to write pitch as well
+	// Replay::WriteSkaterSoundEffect(action,terrain,pos,volPercent);
+	
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+	GameNet::Manager * gamenet_manager = GameNet::Manager::Instance();
+    
+	STerrainSoundInfo soundInfo;
+	bool found = sGetTerrainSoundInfo( &soundInfo, terrain, action, soundChoice );
+
+	if ( !found )
+	{
+		// no sounds are supposed to be played for this surface on this transition:
+		return;
+	}
+	
+	if( propogate )
+	{
+		Net::Client* client;
+		GameNet::MsgPlaySound msg;
+		Net::MsgDesc msg_desc;
+
+		client = gamenet_manager->GetClient( 0 );
+		Dbg_Assert( client );
+
+		msg.m_WhichArray = (char) action;
+		msg.m_SurfaceFlag = (char) terrain;
+		msg.m_Pos[0] = (short) pos[X];
+		msg.m_Pos[1] = (short) pos[Y];
+		msg.m_Pos[2] = (short) pos[Z];
+		msg.m_VolPercent = (char) volPercent;
+		msg.m_PitchPercent = (char) pitchPercent;
+		msg.m_SoundChoice = (char) soundChoice;
+
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgPlaySound );
+		msg_desc.m_Id = GameNet::MSG_ID_PLAY_SOUND;
+		client->EnqueueMessageToServer( &msg_desc );
+	}
+
+	Sfx::sVolume vol;
+	sfx_manager->SetVolumeFromPos( &vol, pos, sfx_manager->GetDropoffDist( soundInfo.m_soundChecksum ));
+
+	// Adjust volume according to speed.
+	volPercent = sGetVolPercent( &soundInfo, volPercent );
+	vol.PercentageAdjustment( volPercent );
+	
+	if (vol.IsSilent()) return;
+	
+	pitchPercent = sGetPitchPercent( &soundInfo, pitchPercent );
+	
+	sfx_manager->PlaySound( soundInfo.m_soundChecksum, &vol, pitchPercent, 0, NULL, soundInfo.mp_soundName );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// set the volume according to the range specified by the designers...
+float					CTerrainManager::sGetVolPercent( STerrainSoundInfo *pInfo, float volPercent, bool clipToMaxVol )
+{
+	
+	Dbg_MsgAssert(pInfo, ("pInfo set to NULL."));
+
+	if ( !( ( pInfo->m_minVol == 0.0f ) && ( pInfo->m_maxVol == 100.0f ) ) )
+	{
+		volPercent = ( pInfo->m_minVol + PERCENT( ( pInfo->m_maxVol - pInfo->m_minVol ), volPercent ) );
+	}
+	
+	if ( clipToMaxVol )
+	{
+		if ( volPercent > pInfo->m_maxVol )
+			volPercent = pInfo->m_maxVol;
+	}
+	return ( volPercent );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float					CTerrainManager::sGetPitchPercent( STerrainSoundInfo *pInfo, float pitchPercent, bool clipToMaxPitch )
+{
+	
+	Dbg_MsgAssert(pInfo, ("pInfo set to NULL."));
+
+	if ( !( ( pInfo->m_minPitch == 0.0f ) && ( pInfo->m_maxPitch == 100.0f ) ) )
+	{
+		pitchPercent = ( pInfo->m_minPitch + PERCENT( ( pInfo->m_maxPitch - pInfo->m_minPitch ), pitchPercent ) );
+	}
+	
+	if ( clipToMaxPitch )
+	{
+		if ( pitchPercent > pInfo->m_maxPitch )
+			pitchPercent = pInfo->m_maxPitch;
+	}
+	return ( pitchPercent );
+}
+
+#if 0
+// Sound FX for the level...
+/*	The surface flag indicates which surface the skater is currently on (grass, cement, wood, metal)
+	whichSound is the checksum from the name of the looping sound (should be loaded using LoadSound
+		in the script file for each level)
+	whichArray indicates whether this sound belongs in the list of wheels rolling sounds, or
+		grinding sounds, etc...
+*/
+void CSk3SfxManager::SetSkaterSoundInfo( int surfaceFlag, uint32 whichSound, int whichArray,
+	float maxPitch, float minPitch, float maxVol, float minVol )
+{
+	
+	if (Sfx::NoSoundPlease()) return;
+	
+	// must initialize PInfo!
+	int i;
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+
+	if ( NULL == sfx_manager->GetWaveTableIndex( whichSound ) )
+	{
+		Dbg_MsgAssert( 0,( "Terrain sound not loaded! surface %d sound %s checksum %d soundType %d",
+			surfaceFlag, Script::FindChecksumName( whichSound ), whichSound, whichArray ));
+		return;
+	}
+	int numEntries = mNumEntries[ whichArray ];
+	SkaterSoundInfo	*pArray = mSoundArray[ whichArray ];
+	SkaterSoundInfo *pInfo = NULL;
+	
+	for ( i = 0; i < numEntries; i++ )
+	{
+		if ( pArray[ i ].surfaceFlag == surfaceFlag )
+		{
+			Dbg_Message( "Re-defining soundtype %d for surfaceFlag %d", whichArray, surfaceFlag );
+			pInfo = &pArray[ i ];
+			break;
+		}
+	}
+	if ( !pInfo )
+	{
+		pInfo = &pArray[ mNumEntries[ whichArray ] ];
+		mNumEntries[ whichArray ] += 1;
+		Dbg_MsgAssert( mNumEntries[ whichArray ] < vMAX_NUM_ENTRIES,( "Array too small type %d.  Increase MAX_NUM_ENTRIES.", whichArray ));
+	}
+	
+	Dbg_MsgAssert( pInfo,( "Please fire Matt immediately after kicking him in the nuts." ));
+	
+	// surfaceFlag of zero will be used for the default
+	pInfo->surfaceFlag = surfaceFlag;
+	// if soundChecksum is zero, no sound will play on this surface.
+	pInfo->soundChecksum = whichSound;
+	pInfo->maxPitch = maxPitch;
+	pInfo->minPitch = minPitch;
+	pInfo->maxVol = maxVol;
+	pInfo->minVol = minVol;
+
+} // end of SetSkaterSoundInfo( )
+
+#endif
+
+} // namespace Env
diff --git a/Code/Gel/Environment/terrain.h b/Code/Gel/Environment/terrain.h
new file mode 100644
index 0000000..3f0946d
--- /dev/null
+++ b/Code/Gel/Environment/terrain.h
@@ -0,0 +1,134 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Gel Module			 									**
+**																			**
+**	File name:		gel/environment/terrain.h								**
+**																			**
+**	Created by:		Garrett Feb. 2003										**
+**																			**
+**	Description:	Terrains and its properties (soundFX, etc.)				**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_TERRAIN_H
+#define __GEL_TERRAIN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+namespace Script
+{
+	class CScriptStructure;
+	class CScript;
+	class CStruct;
+}
+
+namespace Env
+{
+
+// these sound trigger values are also used to
+// communicate with the sounds.cpp module and scripted
+// soundfx for jumping/landing...
+enum ETerrainActionType
+{
+	vTABLE_WHEELROLL = 0,
+	vTABLE_GRIND, // on a rail...
+	vTABLE_JUMP,
+	vTABLE_LAND,
+	vTABLE_BONK,
+	vTABLE_GRINDJUMP,
+	vTABLE_GRINDLAND,
+	vTABLE_SLIDE, // on a rail
+	vTABLE_SLIDEJUMP,
+	vTABLE_SLIDELAND,
+	vTABLE_CESS,
+	vNUM_ACTION_TYPES,
+};
+	
+struct STerrainSoundInfo
+{
+	const char *mp_soundName;
+	uint32		m_soundChecksum;		// Checksum of sound filename
+	float		m_maxPitch;
+	float		m_minPitch;
+	float		m_maxVol;
+	float		m_minVol;
+	float		m_loadPitch;			// Values to use when the sound is loaded
+	float		m_loadVol;
+};
+
+struct STerrainInfo
+{
+	ETerrainType		m_type;
+	bool				m_loaded;		// tells if being used by current level
+	STerrainSoundInfo	m_sounds[vNUM_ACTION_TYPES];
+
+	float				m_friction;
+	// etc, etc, etc
+};
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class  CTerrainManager
+{
+	
+public:
+	static void					sReset();
+
+	static bool					sGetTerrainSoundInfo( STerrainSoundInfo *p_info, ETerrainType terrain,
+													  ETerrainActionType action, float soundChoice = 0.0f );
+
+	static bool					sLoadTerrainSounds( ETerrainType terrain );
+	static float				sGetVolPercent( STerrainSoundInfo *pInfo, float volPercent, bool clipToMaxVol = false );
+	static float				sGetPitchPercent( STerrainSoundInfo *pInfo, float pitchPercent, bool clipToMaxVol = false );
+
+	static void 				sPlaySound( ETerrainActionType action, ETerrainType terrain, const Mth::Vector &pos,
+											float volPercent, float pitchPercent, float soundChoice, bool propogate = true );
+
+	static ETerrainType			sGetTerrainFromChecksum(uint32 checksum);
+	static ETerrainActionType	sGetActionFromChecksum(uint32 checksum);
+
+
+private:
+
+	static uint32				s_get_action_checksum(ETerrainActionType action);
+
+	static bool					s_get_terrain_actions_struct(ETerrainType terrain, Script::CStruct *p_actions);
+	static Script::CStruct *	s_get_action_sound_struct(Script::CStruct *p_actions, ETerrainActionType action, float soundChoice);
+	static bool					s_get_sound_info(Script::CStruct *p_sound, STerrainSoundInfo *p_info);
+	static Script::CStruct *	s_choose_sound_action(Script::CStruct *p_sound_action_struct);
+	
+	static void					s_load_action_sound( Script::CStruct* p_sound );
+	static void					s_load_action_sound_struct( Script::CStruct* p_actions, ETerrainActionType action );
+	
+	
+	//STerrainInfo 				sDefaultTerrainArray[ vNUM_TERRAIN_TYPES ];	// This one is inited at the beginning and never changes
+	//STerrainInfo 				sCurrentTerrainArray[ vNUM_TERRAIN_TYPES ];	// This one is derived from the default and the level array
+
+#ifdef __NOPT_ASSERT__
+								// We check this array to make sure non-loaded terrains
+	static void					s_check_terrain_loaded(ETerrainType terrain);
+
+	static bool					s_terrain_loaded[ vNUM_TERRAIN_TYPES ];
+#endif __NOPT_ASSERT__
+};
+
+}  // namespace Env
+
+#endif
diff --git a/Code/Gel/Event.h b/Code/Gel/Event.h
new file mode 100644
index 0000000..d60ed61
--- /dev/null
+++ b/Code/Gel/Event.h
@@ -0,0 +1,221 @@
+#ifndef __GEL_EVENT_H
+#define __GEL_EVENT_H
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+namespace Script
+{
+	class CScript;
+	class CStruct;
+	class CArray;
+}
+
+
+namespace Obj
+{
+
+class CObject;
+class CTracker;
+
+
+class CEvent
+{
+	friend class CTracker;
+
+public:
+
+	enum EEventLevel
+	{
+		// applies to target or source
+		vSYSTEM_EVENT =	0x36b2ee74		// "system"
+	};
+
+	// should also add new event types to EventLog.q
+	enum EEventType
+	{
+		// applies to type
+		TYPE_FOCUS					= 0x9d3fb516,
+		TYPE_UNFOCUS				= 0x4adf0cd3,
+		TYPE_NOTIFY_CHILD_LOCK		= 0x3056434f,
+		TYPE_NOTIFY_CHILD_UNLOCK	= 0x4a57316b,
+		TYPE_PAD_UP					= 0x30a4f836,
+		TYPE_PAD_DOWN  				= 0x0fd1ac26,
+		TYPE_PAD_LEFT  				= 0x6949db75,
+		TYPE_PAD_RIGHT 				= 0x05ddd87c,
+		TYPE_PAD_CHOOSE				= 0x1e3e253b,
+		TYPE_PAD_BACK				= 0x7ee0fd2a,
+		TYPE_PAD_SQUARE	   			= 0x5c3979e3,
+		TYPE_PAD_CIRCLE	   			= 0x456d7433,
+		TYPE_PAD_L1					= 0xaa7f2028,
+		TYPE_PAD_L2					= 0x33767192,
+		TYPE_PAD_L3					= 0x44714104,
+		TYPE_PAD_R1					= 0x7e3e1ff7,
+		TYPE_PAD_R2					= 0xe7374e4d,
+		TYPE_PAD_R3					= 0x90307edb,
+		TYPE_PAD_START 				= 0x2e6ef8e7,
+		TYPE_PAD_SELECT				= 0xda28fb8a,
+		
+		// these have been deprecated
+		// Ryan: now they are reprecated again!
+		TYPE_PAD_X					= 0x6fb6a322,
+		TYPE_PAD_TRIANGLE			= 0xfe6869df,
+	};
+
+	enum EFlags
+	{
+		mREAD						= (1<<0),
+		mHANDLED					= (1<<1),
+		mCONTROLS_OWN_DATA			= (1<<2),
+	};
+
+	uint32						GetType() {return m_type;}
+	uint32						GetSource() {return m_source_id;}
+	uint32						GetTarget() {return m_target_id;}
+	Script::CStruct *			GetData() {return mp_data;}
+
+	void						MarkRead(uint32 receiverId = 0, uint32 script = 0);
+	void						MarkHandled(uint32 receiverId = 0, uint32 script = 0);
+	bool						WasHandled() {return (m_flags & mHANDLED);}
+
+	/*
+		Helper functions 
+	*/
+
+	// for focus, pad events
+	static int					sExtractControllerIndex(CEvent *pEvent);
+
+protected:
+
+								CEvent();
+								~CEvent();
+	
+	uint32						m_type;
+	uint32						m_source_id;
+	uint32						m_target_id;
+	uint32						m_flags;
+
+	Script::CStruct *			mp_data;
+};
+
+
+
+
+/*
+	Notes:
+	-the event_filter() function automatically locks the associated CObject, so that no 
+	event handling code or script will delete it during event_filter() or pass_event_to_listener(). 
+	The danger is the CObject will delete the CEventListener.
+*/
+class CEventListener
+{
+	friend class CTracker;
+	
+public:
+
+								CEventListener();
+	virtual						~CEventListener();
+	void						RegisterWithTracker(CObject *pObject);
+
+protected:
+
+	void						event_filter(CEvent *pEvent);
+	
+	// implement this function in subclasses for class-specific event handling
+	virtual void				pass_event_to_listener(CEvent *pEvent) = 0;
+
+	
+	CObjectPtr					mp_object;
+	CEventListener *			mp_next_listener;
+	bool						m_registered;
+	int							m_ref_count;
+};
+
+
+
+
+class CEventHandlerTable
+{
+	friend class Script::CScript;
+	friend class CObject;
+	friend class CTracker;
+
+public:
+	enum 
+	{
+		// when used, gets assigned to 'script' member of entry
+		vDEAD_ENTRY				= CRCC(0xc377c572, "null_script")
+	};
+	
+	enum
+	{
+		vDEFAULT_GROUP			= CRCC(0x1ca1ff20, "Default")
+	};
+	
+	
+	void	GetDebugInfo(Script::CStruct *p_info);
+
+public:  // temp public, for skater cleanup to prevent fragmentation
+#ifdef	__SCRIPT_EVENT_TABLE__			
+	void						unregister_all(Script::CScript *p_script);
+#else
+	void						unregister_all(CObject *p_object);
+#endif
+
+	void						AddEvent(uint32 ex, uint32 scr, uint32 group, bool exception, Script::CStruct *p_params);
+	
+	static void					sPrintTable ( CEventHandlerTable* table );
+protected:
+
+								CEventHandlerTable();
+								~CEventHandlerTable();
+
+
+	void						add_from_script(Script::CArray *pArray, bool replace = false);
+	void						remove_entry(uint32 type);
+	void						remove_group(uint32 group = vDEFAULT_GROUP);
+	void						compress_table();
+	void						set_event_enable(uint32 type, bool state);
+	
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	void						pass_event(CEvent *pEvent, Script::CScript *pScript, bool broadcast = false);
+	void						register_all(Script::CScript *p_script);
+#else	
+	void						pass_event(CEvent *pEvent, CObject *pObject, bool broadcast = false);
+	void						register_all(CObject *p_object);
+#endif	
+	
+	struct Entry
+	{
+		uint32					type;
+		uint32					script;
+		uint32					group;
+		
+		// if false, script won't be run when event
+		// of matching type received
+		bool					enabled;
+		
+		// if "exception" is true, then the script is jumped to, rather than called.
+		bool					exception;
+		
+		// if "broadcast" is true, then this handler will also respont to broadcast events
+		bool					broadcast;
+			
+		Script::CStruct		*	p_params;
+	};
+
+#ifdef	__NOPT_ASSERT__
+public:
+#endif
+	Entry *						mp_tab;
+	int							m_num_entries;
+	bool						m_valid;
+	bool						m_changed;		// set if table changed while running an event
+	int							m_in_immediate_use_counter; // by pass_event()
+};
+
+
+
+}
+#endif
diff --git a/Code/Gel/Input/InpMan.cpp b/Code/Gel/Input/InpMan.cpp
new file mode 100644
index 0000000..a9e895d
--- /dev/null
+++ b/Code/Gel/Input/InpMan.cpp
@@ -0,0 +1,389 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Input (INP) 		 									**
+**																			**
+**	File name:		inpman.cpp												**
+**																			**
+**	Created:		05/26/2000	-	spg										**
+**																			**
+**	Description:	Input manager code										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#ifdef __NOPT_DEBUG__
+#include 
+#endif
+
+#include 
+#include 
+ 
+#include 
+
+#include  
+#include  
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Inp
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Input Manager" );
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+Manager::Manager ( void )
+{
+	
+	int i;
+	
+	SIO::Manager * sio_manager = SIO::Manager::Instance();
+
+	m_process_handlers_task = new Tsk::Task< Manager >( process_handlers, *this, Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_PROCESS_HANDLERS );
+	for( i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		m_server[i].m_device = sio_manager->GetDeviceByIndex( i );
+	}
+
+	m_override_pad_with_stick = true;
+	Dbg_AssertType( m_process_handlers_task, Tsk::Task< Manager > );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+	
+	
+	Dbg_AssertType( m_process_handlers_task, Tsk::Task< Manager > );
+	delete m_process_handlers_task;   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::process_handlers( const Tsk::Task< Manager >& task )
+{
+	
+
+	int i;
+	Manager&	manager = task.GetData();      
+
+	for( i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		// See if we hit the end of our record/playback buffers
+		if( ( manager.m_server[i].m_data_in ) &&
+			( manager.m_server[i].m_data_in >= manager.m_server[i].m_data_in_end ))
+		{
+			manager.m_server[i].m_data_in = NULL;
+		}
+		if( ( manager.m_server[i].m_data_out ) &&
+			( manager.m_server[i].m_data_out >= manager.m_server[i].m_data_out_end ))
+		{
+			manager.m_server[i].m_data_out = NULL;
+		}
+		
+        manager.m_server[i].service_handlers();
+	}   
+}
+
+// Note: The order of these checksums must match the corresponding order of the PAD_ defines
+// for the buttons in inpman.h
+static uint32 ButtonChecksums[PAD_NUMBUTTONS]=
+{
+	0x0,		// Nowt
+	0xbc6b118f, // Up
+	0xe3006fc4, // Down
+	0x85981897, // Left
+	0x4b358aeb, // Right
+	0xb7231a95, // UpLeft
+	0xa50950c5, // UpRight
+	0xd8847efa, // DownLeft
+	0x786b8b68, // DownRight
+	0x2b489a86, // Circle
+	0x321c9756, // Square
+	0x7323e97c, // X
+	0x20689278, // Triangle
+	0x26b0c991, // L1
+	0xbfb9982b, // L2
+	0xc8bea8bd, // L3
+	0xf2f1f64e, // R1
+	0x6bf8a7f4, // R2
+	0x1cff9762, // R3
+	0x767a45d7, // Black
+	0xbd30325b, // White
+	0x9d2d8850, // Z
+};	
+
+uint32 GetButtonChecksum( int whichButton )
+{
+	
+	Dbg_MsgAssert( ( whichButton > 0 ) && ( whichButton < PAD_NUMBUTTONS ), ( "button %d out of range", whichButton ) );
+	return ( ButtonChecksums[ whichButton ] );
+}
+
+int GetButtonIndex(uint32 Checksum)
+{
+	
+	for (int i=1; i=0 && iEnabled();
+	}	
+	else
+	{
+		return false;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/* K: Disables the actuators on all devices.                      */
+/* Used by the VibrationOff script command.						  */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::DisableActuators( void )
+{
+	
+
+	int i;
+
+	for( i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		if (m_server[i].m_device)
+		{
+			m_server[i].m_device->DisableActuators();
+		}	
+	}
+}
+
+// Disables just one actuator. Called by the VibrationOff script command.
+void	Manager::DisableActuator( int i )
+{
+	
+
+	Dbg_MsgAssert(i>=0 && iDisableActuators();
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/* K: Enables the actuators on all devices.                       */
+/* Used by the VibrationOn script command.						  */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::EnableActuators( void )
+{
+	
+
+	int i;
+
+	for( i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		if (m_server[i].m_device)
+		{
+			m_server[i].m_device->EnableActuators();
+		}	
+	}
+}
+
+// Enables just one actuator. Called by the VibrationOn script command.
+void	Manager::EnableActuator( int i )
+{
+	
+	Dbg_MsgAssert(i>=0 && iEnableActuators();
+	}	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/* Mick: stops the actuators on all devices.                      */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ResetActuators( void )
+{
+	
+
+	int i;
+
+	for( i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		if (m_server[i].m_device)
+		{
+			m_server[i].m_device->ResetActuators();
+		}	
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetAnalogStickOverride( bool should_override_pad )
+{
+	m_override_pad_with_stick = should_override_pad;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ShouldAnalogStickOverride( void )
+{
+	return m_override_pad_with_stick;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Inp
diff --git a/Code/Gel/Input/inpserv.cpp b/Code/Gel/Input/inpserv.cpp
new file mode 100644
index 0000000..9a15a5f
--- /dev/null
+++ b/Code/Gel/Input/inpserv.cpp
@@ -0,0 +1,592 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Input (INP) 		 									**
+**																			**
+**	File name:		inpserv.cpp												**
+**																			**
+**	Created:		05/31/2000	-	spg										**
+**																			**
+**	Description:	Input server code										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+
+
+#ifdef __NOPT_DEBUG__
+#include 
+#endif
+
+#include 
+#include 
+#include 
+ 
+#include 
+
+#include 
+
+#ifdef	__PLAT_NGPS__
+#include 	  		// Mick:  needed for low level input patch bypassing task system
+#include 					// Mick: needed for low level inactivity timeout on PS2 demo disk
+#elif defined( __PLAT_NGC__ )
+#include 
+#endif
+
+#include 
+
+#ifdef	__PLAT_NGPS__
+extern	sceDemoEndReason demo_exit_reason;
+extern  int		inactive_time;
+extern  int		inactive_countdown;
+extern  int		gameplay_time;
+#endif
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Inp
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+// Used to map digital input to analog events in case of no analog button support
+static  int adc_table[][2] = 
+{
+    { Data::mD_L3,        Data::vA_L3 },
+    { Data::mD_R3,        Data::vA_R3 },
+    { Data::mD_L2,        Data::vA_L2 },
+    { Data::mD_R2,        Data::vA_R2 },
+    { Data::mD_L1,        Data::vA_L1 },
+    { Data::mD_R1,        Data::vA_R1 },
+    { Data::mD_TRIANGLE,  Data::vA_TRIANGLE },
+    { Data::mD_CIRCLE,    Data::vA_CIRCLE },
+    { Data::mD_X,         Data::vA_X },
+    { Data::mD_SQUARE,    Data::vA_SQUARE },
+    { Data::mD_UP,        Data::vA_UP },
+    { Data::mD_RIGHT,     Data::vA_RIGHT },
+    { Data::mD_DOWN,      Data::vA_DOWN },
+    { Data::mD_LEFT,      Data::vA_LEFT },
+    { Data::mD_BLACK,     Data::vA_BLACK },
+    { Data::mD_WHITE,     Data::vA_WHITE },
+    { Data::mD_Z,         Data::vA_Z },
+//	{ Data::mD_SELECT,	  Data::vA_SELECT },
+    { 0,                  0 }
+};
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+// Mick: Controller info is made public for quick and easy debug test
+// it sould not be used for any shippable code....
+uint32	gDebugButtons[SIO::vMAX_DEVICES];
+uint32	gDebugBreaks[SIO::vMAX_DEVICES];
+uint32	gDebugMakes[SIO::vMAX_DEVICES];
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Server::service_handlers( void )
+{
+//    
+    
+    unsigned char *control_data;
+	Data *handler_data;
+    Flags caps;
+
+	// If we are getting our input from recorded data, set all depended controller data from recorded data   
+	if( m_data_in && m_data_in->m_valid )
+	{
+		int i;
+		
+		m_data.m_prev = m_data.m_cur;
+		m_data.m_cur = m_data_in->m_cur;		// Get digital input from buffer
+		m_data.m_new = m_data.m_prev ^ m_data.m_cur;
+		m_data.m_Buttons = m_data.m_cur;
+		m_data.m_Makes = m_data.m_new & m_data.m_cur;
+		m_data.m_Breaks = m_data.m_new & ~m_data.m_cur;
+		
+		for( i = 0; i < Data::vMAX_ANALOG_EVENTS; i++ )	// Get analog input from buffer
+		{      
+			m_data.m_Event[i] = m_data_in->m_event[i];
+		}
+		
+		m_handler_stack.Process();
+		
+		m_data_in++;
+		return;
+	}
+			
+	if( ( m_device ) &&
+		( m_device->HasValidControlData()))
+    {   
+		Inp::Manager * inp_manager = Inp::Manager::Instance();
+
+        control_data = m_device->GetControlData();
+        handler_data = &m_data;
+        caps = m_device->GetCapabilities();
+        Dbg_Assert( control_data );
+		
+		if( m_data_out )
+		{
+			m_data_out->m_valid = FALSE;
+		}
+
+        if( control_data[0] == 0 )  // Valid controller communication
+        {
+            unsigned char controller_type;
+            
+            controller_type = control_data[1] >> 4;  // top 4 bits contain controller type
+            handler_data->m_prev = handler_data->m_cur;
+			// Regular buttons
+            handler_data->m_cur = 0xFFFF ^ (( control_data[2] << 8 ) | control_data[3] );
+			// The XBox black & white and NGC Z buttons.
+			handler_data->m_cur |= (control_data[20]<<16);
+			
+			
+			handler_data->m_new = handler_data->m_prev ^ handler_data->m_cur;
+			
+			handler_data->m_Buttons = handler_data->m_cur;         
+            handler_data->m_Makes = handler_data->m_new & handler_data->m_cur;
+            handler_data->m_Breaks = handler_data->m_new & ~handler_data->m_cur;
+            
+            switch( controller_type )
+            {
+            case SIO::Device::vNEGI_COM:
+                // TODO: Handle 
+                Dbg_MsgAssert( 0,( "Unsupported Device Type\n" ));
+                break;
+
+            case SIO::Device::vKONAMI_GUN:
+                // TODO: Handle 
+                Dbg_MsgAssert( 0,( "Unsupported Device Type\n" ));
+                break;
+
+            case SIO::Device::vDIGITAL_CTRL:
+                handler_data->ConvertDigitalToAnalog();
+                break;                                                   
+
+            case SIO::Device::vNAMCO_GUN:
+                // TODO: Handle 
+                Dbg_MsgAssert( 0,( "Unsupported Device Type\n" ));
+                break;
+
+            case SIO::Device::vJOYSTICK:
+                handler_data->m_Event[Data::vA_RIGHT_X] = control_data[4];
+                handler_data->m_Event[Data::vA_RIGHT_Y] = control_data[5];
+                handler_data->m_Event[Data::vA_LEFT_X] = control_data[6];
+                handler_data->m_Event[Data::vA_LEFT_Y] = control_data[7];
+                handler_data->ConvertDigitalToAnalog();
+				if( inp_manager->ShouldAnalogStickOverride())
+				{
+					handler_data->OverrideAnalogPadWithStick();
+				}
+                break;
+
+            case SIO::Device::vANALOG_CTRL:
+                //if( caps.TestMask( SIO::mANALOG_BUTTONS ))
+				if( m_device->GetButtonMode() == SIO::vANALOG )
+                {
+                    memcpy( handler_data->m_Event, &control_data[4], Data::vMAX_ANALOG_EVENTS );
+                }
+                else
+                {
+                    handler_data->m_Event[Data::vA_RIGHT_X] = control_data[4];
+                    handler_data->m_Event[Data::vA_RIGHT_Y] = control_data[5];
+                    handler_data->m_Event[Data::vA_LEFT_X] = control_data[6];
+                    handler_data->m_Event[Data::vA_LEFT_Y] = control_data[7];
+                    handler_data->ConvertDigitalToAnalog();
+                    handler_data->handle_analog_tolerance();
+					if( inp_manager->ShouldAnalogStickOverride())
+					{
+						handler_data->OverrideAnalogPadWithStick();
+					}
+                }
+                break;
+
+            case SIO::Device::vFISHING_CTRL:
+                // TODO: Handle 
+                Dbg_MsgAssert( 0,( "Unsupported Device Type\n" ));
+                break;
+                
+            case SIO::Device::vJOG_CTRL:        
+                // TODO: Handle 
+                Dbg_MsgAssert( 0,( "Unsupported Device Type\n" ));
+                break;
+
+            }
+            
+			if( m_data_out )
+			{
+				int i;
+				
+				m_data_out->m_cur = handler_data->m_cur;
+				m_data_out->m_valid = TRUE;
+				for( i = 0; i < Data::vMAX_ANALOG_EVENTS; i++ )	// Get analog input from buffer
+				{      
+					m_data_out->m_event[i] = handler_data->m_Event[i];
+				}
+				
+				m_data_out++;
+			}
+
+			// Make simple controller buttons accessible at global level
+			// for debugging tools			
+			
+			if (m_device)
+			{
+				gDebugButtons[m_device->GetIndex()] = m_data.m_Buttons; 							
+				gDebugMakes[m_device->GetIndex()] = m_data.m_Makes; 							
+				gDebugBreaks[m_device->GetIndex()] = m_data.m_Breaks; 							
+								
+				#ifdef	__PLAT_NGPS__
+				MemView_Input(m_data.m_Buttons, m_data.m_Makes, m_data.m_Breaks );
+							
+				// Mick: added test for "Select" which will now exit the game if in demo mode
+				
+				if (Config::Bootstrap())
+				{
+
+					bool exit = false;
+		
+					if (m_data.m_Buttons ||			
+						(handler_data->m_Event[Data::vA_RIGHT_X] !=128)||
+						(handler_data->m_Event[Data::vA_RIGHT_Y] !=128)	||
+						(handler_data->m_Event[Data::vA_LEFT_X] !=128) 	||
+						(handler_data->m_Event[Data::vA_LEFT_Y] !=128))
+					{
+						//printf ("%d activity....\n", inactive_countdown);
+						inactive_countdown = inactive_time;				
+					}
+		
+					if (m_device->GetIndex() == 0)
+					{
+						
+						if (inactive_countdown)
+						{
+							inactive_countdown --;
+							if (inactive_countdown == 0)
+							{
+								printf ("Exiting due to inactivity\n");
+								exit = true;  
+								demo_exit_reason = SCE_DEMO_ENDREASON_PLAYABLE_INACTIVITY_TIMEOUT;
+							}
+						}
+						if (gameplay_time)
+						{
+							gameplay_time--;
+							if (gameplay_time == 0)
+							{
+								printf ("Exiting due to gameplay timer exiting\n");
+								exit = true;  
+								demo_exit_reason = SCE_DEMO_ENDREASON_PLAYABLE_GAMEPLAY_TIMEOUT;
+							}
+						}
+					}
+					
+					if ((m_data.m_Buttons & Inp::Data::mD_SELECT) && (m_data.m_Buttons & Inp::Data::mD_START))
+					{
+						exit = true;  
+						demo_exit_reason = SCE_DEMO_ENDREASON_PLAYABLE_QUIT;
+					}
+					
+					if ( exit )
+					{
+                        Sys::ExitDemo();
+					}
+				}
+				#endif				  
+			}
+			
+            // At this point, we have obtained and converted valid controller information
+            // Now propogate it to clients
+            m_handler_stack.Process();
+        }
+    }            
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Data::handle_analog_tolerance( void )
+{
+
+	// copy unclamped values over before we clamp them										  
+	m_Event[Data::vA_RIGHT_X_UNCLAMPED] = m_Event[Data::vA_RIGHT_X];	
+	m_Event[Data::vA_RIGHT_Y_UNCLAMPED] = m_Event[Data::vA_RIGHT_Y];	
+	m_Event[Data::vA_LEFT_X_UNCLAMPED] = m_Event[Data::vA_LEFT_X];	
+	m_Event[Data::vA_LEFT_Y_UNCLAMPED] = m_Event[Data::vA_LEFT_Y];	
+					 
+					 
+	if( Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_RIGHT_X] ) <= vANALOGUE_TOL )
+    {
+        m_Event[Data::vA_RIGHT_X] = vANALOGUE_CENTER;
+    }
+
+    if( Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_RIGHT_Y] ) <= vANALOGUE_TOL )
+    {
+        m_Event[Data::vA_RIGHT_Y] = vANALOGUE_CENTER;
+    }
+
+    if( Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_LEFT_X] ) <= vANALOGUE_TOL )
+    {
+        m_Event[Data::vA_LEFT_X] = vANALOGUE_CENTER;
+    }
+
+    if( Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_LEFT_Y] ) <= vANALOGUE_TOL )
+    {
+        m_Event[Data::vA_LEFT_Y] = vANALOGUE_CENTER;
+    }
+
+// "unclamped", only zero the controllers if BOTH X and Y are in the dead zone
+// that way, we can smoothly go around the periphery, and get much better aiming ability
+// (improves the look-around camera, and the walking)
+
+	if( Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_RIGHT_X_UNCLAMPED] ) <= vANALOGUE_TOL 
+	&& Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_RIGHT_Y_UNCLAMPED] ) <= vANALOGUE_TOL )
+    {
+        m_Event[Data::vA_RIGHT_X_UNCLAMPED] = vANALOGUE_CENTER;
+        m_Event[Data::vA_RIGHT_Y_UNCLAMPED] = vANALOGUE_CENTER;
+    }
+
+    if( Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_LEFT_X_UNCLAMPED] ) <= vANALOGUE_TOL 
+	&& Mth::Abs( vANALOGUE_CENTER - m_Event[Data::vA_LEFT_Y_UNCLAMPED] ) <= vANALOGUE_TOL )
+    {
+        m_Event[Data::vA_LEFT_X_UNCLAMPED] = vANALOGUE_CENTER;
+        m_Event[Data::vA_LEFT_Y_UNCLAMPED] = vANALOGUE_CENTER;
+    }
+	
+	
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Server::Server( void )
+{
+//    
+    
+	
+	m_data_in = NULL;
+	m_data_out = NULL;
+	m_data_in_end = NULL;
+	m_data_out_end = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Server::RecordInput( RecordedData *data_buffer )
+{
+//    
+    
+	
+	Dbg_Assert( data_buffer );
+	
+	m_data_out = data_buffer;   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Server::PlaybackInput( RecordedData *recorded_input )
+{
+//    
+    
+	
+	Dbg_Assert( recorded_input );
+	
+	m_data_in = recorded_input;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+    
+void Data::MaskDigitalInput( int button_mask )
+{
+//    
+    
+
+	m_Buttons &= ~button_mask;   
+	
+#if 0 	// old way of masking
+	m_new = m_prev ^ m_Buttons;
+	m_Makes = m_new & m_Buttons;
+	m_Breaks = m_new & ~m_Buttons;
+#endif
+	
+	m_Makes &= ~button_mask;
+	m_Breaks &= ~button_mask;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Data::MaskAnalogInput( int button_mask )
+{
+//    
+    
+
+	int i, mask;   
+		
+    mask = 1;
+	for( i = 0; i < vMAX_ANALOG_EVENTS; i++, mask <<= 1 )
+	{
+		if( button_mask & mask )
+		{
+            // Analog stick is neutral at 128, all other buttons are neutral at 0
+            if( 
+				( i == vA_RIGHT_X ) ||
+                ( i == vA_RIGHT_Y ) ||
+                ( i == vA_LEFT_X  ) ||
+                ( i == vA_LEFT_Y  )	||
+				( i == vA_RIGHT_X_UNCLAMPED ) ||
+                ( i == vA_RIGHT_Y_UNCLAMPED ) ||
+                ( i == vA_LEFT_X_UNCLAMPED  ) ||
+                ( i == vA_LEFT_Y_UNCLAMPED  ))
+            {   
+                m_Event[i] = 128;
+            }
+            else
+            {
+                m_Event[i] = 0;
+            }
+		}
+	}
+}
+
+/******************************************************************/
+/* Overrides Analog Pad with Analog Stick values if pad is not in */
+/* in use                                                         */
+/******************************************************************/
+
+void Data::OverrideAnalogPadWithStick( void )
+{
+	
+
+	// Only override analog pad if no analog input is present
+	if( m_Buttons & ( mD_UP | mD_RIGHT | mD_DOWN | mD_LEFT ))
+	{
+		return;
+	}
+
+	if( m_Event[vA_LEFT_X] > vANALOGUE_CENTER )
+	{
+		m_Event[vA_RIGHT] = m_Event[vA_LEFT_X] - vANALOGUE_CENTER;
+		m_Buttons |= mD_RIGHT;
+	}
+	else if( m_Event[vA_LEFT_X] < vANALOGUE_CENTER )
+	{
+		m_Event[vA_LEFT] = Mth::Abs( vANALOGUE_CENTER - m_Event[vA_LEFT_X] );
+		m_Buttons |= mD_LEFT;
+	}
+    
+	if( m_Event[vA_LEFT_Y] < vANALOGUE_CENTER )
+	{
+		m_Event[vA_UP] = m_Event[Data::vA_LEFT_Y] - vANALOGUE_CENTER;
+		m_Buttons |= mD_UP;
+	}
+	else if( m_Event[vA_LEFT_Y] > vANALOGUE_CENTER )
+	{
+		m_Event[vA_DOWN] = Mth::Abs( vANALOGUE_CENTER - m_Event[vA_LEFT_Y] );
+		m_Buttons |= mD_DOWN;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Data::ConvertDigitalToAnalog( void )
+{
+	int i;
+
+    for( i = 0; adc_table[i][0] > 0; i++ )
+	{
+		if( m_Buttons & adc_table[i][0] )  // If we have digital "down" convert to fully-pressed analog input
+		{
+			m_Event[adc_table[i][1]] = vMAX_ANALOG_VALUE;
+		}
+        else
+        {
+            m_Event[adc_table[i][1]] = 0;
+        }
+	}  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Data::Data( void )
+{
+    memset( m_Event, 0, vMAX_ANALOG_EVENTS );
+    m_Buttons = 0;
+	m_cur = 0;
+    m_new = 0;
+    m_prev = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Inp
diff --git a/Code/Gel/MainLoop/Mainloop.cpp b/Code/Gel/MainLoop/Mainloop.cpp
new file mode 100644
index 0000000..de9f855
--- /dev/null
+++ b/Code/Gel/MainLoop/Mainloop.cpp
@@ -0,0 +1,426 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Main Loop (ML) 											**
+**																			**
+**	File name:		mainloop.cpp											**
+**																			**
+**	Created:		05/27/99	-	mjb										**
+**																			**
+**	Description:	Main loop and support code								**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#ifndef __PLAT_WN32__
+    #include 
+#endif
+
+#include 
+
+#ifdef	__PLAT_NGPS__
+    #include 
+    #include 
+
+    namespace NxPs2
+    {
+        void	WaitForRendering();
+		void 	StuffAfterGSFinished();
+    }
+#endif
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+Dbg_DefineProject( GEL, "GEL Library" )
+
+namespace Mdl
+{
+	void	Rail_DebugRender();			// for debugging
+}
+
+namespace Mlp
+{
+
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Main Loop Manager" );
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+Manager::Manager( void )
+{
+	
+	
+	start_render_hook = NULL;
+	end_render_hook = NULL;
+	done = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void		Manager::service_system( void )
+{
+	
+
+#ifndef __PLAT_WN32__
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PushContext( 255, 0, 0 );
+#	endif // __USE_PROFILER__
+#endif
+
+	system_task_stack.Process (currently_profiling);
+
+#ifndef __PLAT_WN32__
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PopContext();
+#	endif // __USE_PROFILER__
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void		Manager::game_logic( void )
+{
+	
+	
+#ifndef __PLAT_WN32__
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PushContext( 0, 255, 0 );
+#	endif // __USE_PROFILER__
+#endif
+
+//	printf ("\nTiming Logic\n"); 
+	logic_task_stack.Process(currently_profiling);
+
+//	printf ("\nDumping Task List\n\n");	
+//	logic_task_stack.Dump();
+
+#ifndef __PLAT_WN32__
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PopContext();
+#	endif // __USE_PROFILER__
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::render_frame( void )
+{
+	
+	
+//	printf ("############################## render_frame #############################\n");	
+	
+
+#ifndef __PLAT_WN32__
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PushContext( 0, 0, 255 );
+#	endif // __USE_PROFILER__
+#endif
+
+#	ifdef __PLAT_NGC__
+	if( start_render_hook )
+	{
+		start_render_hook->Call();
+	}
+	if( !display_tasks_paused )
+	{
+		display_task_stack.Process( currently_profiling );
+	}
+#	else
+	if (!display_tasks_paused)
+	{
+		// If paused don't call the start_render_hook
+		// as this clears the none-visible frame buffer
+		// which will result in a flash when display is unpaused
+		if ( start_render_hook )			// set up for rendering
+		{
+			start_render_hook->Call();
+		}
+
+//		printf ("\nTiming render\n"); 
+	
+		display_task_stack.Process(currently_profiling);		// service rendering routines
+	}
+#	endif // __PLAT_NGC__
+	
+#ifndef __PLAT_WN32__
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PopContext();
+	Sys::CPUProfiler->PushContext( 255, 255, 0 );
+#	endif // __USE_PROFILER__
+#endif
+
+	if ( end_render_hook )				// end rendering
+	{
+		end_render_hook->Call();
+	}
+#ifndef __PLAT_WN32__
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PopContext();
+#	endif // __USE_PROFILER__
+#endif
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void		Manager::MainLoop( void )
+{
+	
+
+	bool								old_flag = done;	// push the current done flag
+
+	done = false;
+
+	while ( !done )
+	{
+	
+		if (trigger_profiling)
+		{
+			printf ("\nProfiling.....Start of main loop\n\n");
+			trigger_profiling--;
+			currently_profiling = 1;
+		}
+		
+
+#ifndef __PLAT_WN32__
+
+#ifdef	__PLAT_NGPS__		
+// for profiling on the PS2, we do a bit of the code from sPreRender here
+// so we can time the final waiting for GS		
+#	ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PushContext( 0,0,255 );		// blue = wait for VU/DMA/GPU to finish
+#	endif		
+		NxPs2::WaitForRendering();		// PS2 Specific, wait for prior frame's DMA to finish	
+		NxPs2::StuffAfterGSFinished();		// PS2 Specific, DMA altering stuff run after previous frame's DMA finished
+#	ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PopContext(  );
+		Sys::CPUProfiler->PushContext( 128,0,0 );		// red = wait for vblank
+#	endif		
+		int framerate = Script::GetInteger(0x3214c818/*"lock_framerate"*/);
+		if (framerate)	// normal vsync
+		{
+			static uint64 next_vblanks= 0;
+			// Still call VSync, as it updates some functions
+			Tmr::VSync();
+			while (Tmr::GetVblanks()PopContext(  );
+#	endif		
+#endif				
+#ifndef	__PLAT_NGC__		
+#	ifdef __USE_PROFILER__
+		Sys::Profiler::sStartFrame();		  		
+#	endif		
+#endif		
+		
+		Nx::CEngine::sPreRender();			 			// start rendering previous frame's DMA list
+
+#ifdef	__PLAT_NGPS__		
+		Sfx::CSpuManager::sUpdateStatus();				// Garrett: This should go into some system task, but I'll put it here for now
+#endif
+
+//		Sys::Profiler::sStartFrame();		  		
+#endif
+
+#ifdef	__PLAT_NGC__		
+#	ifdef __USE_PROFILER__
+		Sys::Profiler::sStartFrame();		  		
+#	endif		
+#	endif		
+					 
+		service_system();
+
+#if	defined(__PLAT_NGPS__) && defined(BATCH_TRI_COLLISION)
+		// Enable VU0 collision
+		bool got_vu0 = Nx::CBatchTriCollMan::sUseVU0Micro();
+		Dbg_Assert(got_vu0);
+#endif
+
+ #ifdef	__PLAT_NGPS__		
+//		snProfSetRange( -1, (void*)0, (void*)-1);
+//		snProfSetFlagValue(0x01);
+ #endif
+
+		game_logic();		
+
+ #ifdef	__PLAT_NGPS__		
+//		snProfSetRange( 4, (void*)NULL, (void*)-1);
+ #endif		
+
+
+#if	defined(__PLAT_NGPS__) && defined(BATCH_TRI_COLLISION)
+		// Disable VU0 collision
+		if (got_vu0)
+		{
+			Nx::CBatchTriCollMan::sDisableVU0Micro();
+		}
+#endif
+
+#ifndef __PLAT_WN32__
+		// Display the memory contents, (if memview is active)
+		MemView_Display();
+#	ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PushContext( 255, 255, 0 );  // yellow = render world
+#	endif		
+
+ #ifdef	__PLAT_NGPS__		
+//		snProfSetRange( -1, (void*)0, (void*)-1);
+//		snProfSetFlagValue(0x01);
+ #endif
+		
+		Nx::CEngine::sRenderWorld();		
+#ifdef	__PLAT_NGC__		
+#ifdef		__USE_PROFILER__
+		Sys::Render_Profiler();		
+#endif
+#endif
+		// Mick: bit of a patch here, we need some better debug hooks
+		Mdl::Rail_DebugRender();
+#	ifdef __USE_PROFILER__
+			Sys::CPUProfiler->PushContext( 0, 0, 0 );	 	// Black (Under Yellow) = sPostRender
+#	endif // __USE_PROFILER__
+		Nx::CEngine::sPostRender();		  // Previous frames profiler is rendered here
+#	ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PopContext(  );
+#	endif		
+
+
+	
+
+#	ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PopContext(  );
+#	endif		
+		Tmr::OncePerRender();
+ #ifdef	__PLAT_NGPS__		
+//		snProfSetRange( 4, (void*)NULL, (void*)-1);
+ #endif		
+#endif		
+		
+		currently_profiling = false;
+
+	}
+
+	done = old_flag;
+}
+
+void Manager::ProfileTasks(int n)
+{
+	trigger_profiling = n;
+	currently_profiling = 0;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::QuitLoop( void )
+{
+	
+
+	Dbg_Notify( "Exiting..." );
+
+	done = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::DoGameLogic( void )
+{
+	game_logic();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mlp
+
diff --git a/Code/Gel/Module/modman.cpp b/Code/Gel/Module/modman.cpp
new file mode 100644
index 0000000..0504032
--- /dev/null
+++ b/Code/Gel/Module/modman.cpp
@@ -0,0 +1,395 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Module (MDL)		 									**
+**																			**
+**	File name:		modman.cpp												**
+**																			**
+**	Created:		05/27/99	-	mjb										**
+**																			**
+**	Description:	Module manager code										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#ifdef __NOPT_DEBUG__
+#include 
+#endif
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Module Manager" );
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+Manager::Manager ( void )
+{
+	
+
+	process_modules_task = new Tsk::Task< Manager >( process_modules, *this, Tsk::BaseTask::Node::vSYSTEM_TASK_PRIORITY_PROCESS_MODULES );
+	Dbg_AssertType( process_modules_task, Tsk::Task< Manager > );
+
+	control_change = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+	
+	
+	StopAllModules();
+		
+	Dbg_AssertType( process_modules_task, Tsk::Task< Manager > );
+	delete process_modules_task;
+	
+	Dbg_MsgAssert( module_list.IsEmpty(),( 
+		"%d module%s still registered\n",
+			module_list.CountItems(),
+			module_list.CountItems() > 1 ? "s" : "" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::process_modules( const Tsk::Task< Manager >& task )
+{
+	
+
+	Module*					next;
+	Module*					current;
+	Lst::Search< Module >	sh;
+
+	Manager&	manager = task.GetData();
+
+	if ( !manager.control_change )
+	{
+		return; // nothing to do
+	}
+
+	manager.control_change = false;
+	
+	next = sh.FirstItem( manager.module_list );
+
+	while ( next )
+	{
+		current = next;
+		next = sh.NextItem();			// get next item before excuting callback
+
+		switch ( current->state )
+		{
+			case Module::vSTOPPED :
+			{
+				if (( current->command == Module::vSTART ) ||
+					( current->command == Module::vRESTART ))
+				{
+#ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+#ifdef __NOPT_DEBUG__
+				Dbg_Notify ( "Starting module %s @ %d", current->GetName(), 
+					Tmr::GetTime() ); // should use Game Clock not system clock
+#endif
+#endif
+#endif
+					current->v_start_cb();
+					current->state = Module::vRUNNING;
+					current->command = Module::vNONE;
+				}
+				
+				break;
+			}
+			
+			case Module::vRUNNING :
+			{
+				if ( current->command == Module::vSTOP )
+				{
+#ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+#ifdef __NOPT_DEBUG__
+					Dbg_Notify ( "Stopping module %s @ %d", current->GetName(), 
+						 Tmr::GetTime() ); // should use Game Clock not system clock
+#endif
+#endif
+#endif
+					current->v_stop_cb();
+					current->state = Module::vSTOPPED;
+					current->command = Module::vNONE;
+				}
+				else if ( current->command == Module::vRESTART ) 
+				{
+#ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+#ifdef __NOPT_DEBUG__
+					Dbg_Notify ( "Restarting module %s @ %d", current->GetName(), 
+						Tmr::GetTime() ); // should use Game Clock not system clock
+#endif
+#endif
+#endif
+					current->v_stop_cb();
+					current->v_start_cb();               
+					current->command = Module::vNONE;
+				}
+
+				break;
+			}        
+
+			default:
+			{
+				Dbg_MsgAssert( false,( "Invalid module control state" ));
+			}
+		}
+	}
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void	Manager::RegisterModule( Module &module )
+{
+	
+	
+	Dbg_AssertType ( &module, Module );
+
+	module_list.AddToTail ( module.node );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::UnregisterModule( Module &module )
+{
+	
+	
+	Dbg_AssertType( &module, Module );
+
+	Dbg_MsgAssert( module.node->InList(),( "Module not Registered with Manager" ));
+
+	module.node->Remove();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::StartModule( Module &module )
+{
+	
+	
+	Dbg_MsgAssert( module.node->InList(),( "Module not Registered with Manager" ));
+
+	if ( !module.Locked() )
+	{
+		module.command = Module::vSTART;
+		control_change = true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::RestartModule( Module &module )
+{
+	
+	
+	Dbg_MsgAssert( module.node->InList(),( "Module not Registered with Manager" ));
+
+	if ( !module.Locked() )
+	{
+		module.command = Module::vRESTART;
+		control_change = true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::StopModule( Module &module )
+{
+	
+
+	if ( !module.Locked() )
+	{
+		module.command = Module::vSTOP;
+		control_change = true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::StartAllModules( void )
+{
+	
+
+	Module*					next;
+	Module*					mdl;
+	Lst::Search< Module >	sh;
+
+	next = sh.FirstItem( module_list );
+
+	while ( next )
+	{
+		mdl = next;				
+		next = sh.NextItem();	// get next item before excuting callback
+
+		if ( !mdl->Locked() )
+		{
+			if ( mdl->state == Module::vSTOPPED )
+			{
+				mdl->v_start_cb();
+				mdl->command = Module::vNONE;
+				mdl->state = Module::vRUNNING;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::StopAllModules( void )
+{
+	
+
+	Module*					next;
+	Module*					mdl;
+	Lst::Search< Module >	sh;
+
+	next = sh.FirstItem( module_list );
+
+	while ( next )
+	{
+		mdl = next;				
+		next = sh.NextItem();	// get next item before excuting callback
+								
+		if ( !mdl->Locked() )
+		{
+			if ( mdl->state == Module::vRUNNING )
+			{
+				mdl->v_stop_cb();
+				mdl->command = Module::vNONE;
+				mdl->state = Module::vSTOPPED;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::LockAllModules( void )
+{
+	
+
+	Module*					mdl;
+	Lst::Search< Module >	sh;
+
+	mdl = sh.FirstItem( module_list );
+
+	while ( mdl )
+	{
+		mdl->Lock();
+		mdl = sh.NextItem();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::UnlockAllModules ( void )
+{
+	
+
+	Module*					mdl;
+	Lst::Search< Module >	sh;
+
+	mdl = sh.FirstItem ( module_list );
+
+	while ( mdl )
+	{
+		mdl->Unlock();
+		mdl = sh.NextItem();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
diff --git a/Code/Gel/Module/module.cpp b/Code/Gel/Module/module.cpp
new file mode 100644
index 0000000..e177320
--- /dev/null
+++ b/Code/Gel/Module/module.cpp
@@ -0,0 +1,111 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Module (MDL)		 									**
+**																			**
+**	File name:		module.cpp												**
+**																			**
+**	Created:		05/27/99	-	mjb										**
+**																			**
+**	Description:	Module code												**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Module::Module ( void )
+{
+	
+
+	state = vSTOPPED;
+	locked = false;
+
+	node = new Lst::Node< Module > ( this );
+	Dbg_AssertType( node, Lst::Node< Module > );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Module::~Module ()
+{
+	
+
+	Dbg_AssertType( node, Lst::Node< Module > );
+	delete node;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
+
+
diff --git a/Code/Gel/Movies/Movies.cpp b/Code/Gel/Movies/Movies.cpp
new file mode 100644
index 0000000..0cdf1e4
--- /dev/null
+++ b/Code/Gel/Movies/Movies.cpp
@@ -0,0 +1,57 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			GEL					 									**
+**																			**
+**	File name:		movies.cpp												**
+**																			**
+**	Created:		5/14/1	-	mjd											**
+**																			**
+**	Description:	streaming movies										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGPS__
+#include 
+#elif defined( __PLAT_XBOX__ )
+#include 
+#elif defined( __PLAT_NGC__ )
+#include 
+#endif
+
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Flx
+{
+
+
+
+void PlayMovie( const char *pMovieName )
+{
+	PMovies_PlayMovie( pMovieName );
+}
+
+}  // namespace Flx
diff --git a/Code/Gel/Movies/Movies.h b/Code/Gel/Movies/Movies.h
new file mode 100644
index 0000000..562afb7
--- /dev/null
+++ b/Code/Gel/Movies/Movies.h
@@ -0,0 +1,46 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			movies													**
+**																			**
+**	File name:		gel/movies/movies.h  									**
+**																			**
+**	Created: 		5/14/01	-	mjd											**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_MOVIES_H
+#define __GEL_MOVIES_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+//#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Flx
+{
+
+
+
+void PlayMovie( const char *pMovieName );
+
+} // namespace Flx
+
+#endif	// __GEL_MOVIES_H
+
+
+
diff --git a/Code/Gel/Movies/Ngps/audiodec.cpp b/Code/Gel/Movies/Ngps/audiodec.cpp
new file mode 100644
index 0000000..212ba6c
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/audiodec.cpp
@@ -0,0 +1,483 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "sys/config/config.h"
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/audiodec.h"
+#include "gel/movies/ngps/p_movies.h"
+#include "gel/music/music.h"
+#include "gel/soundfx/soundfx.h"
+
+#define AU_HEADER_SIZE 40
+#define UNIT_SIZE 1024
+#define PRESET_VALUE(count)	(count)
+
+namespace Flx
+{
+
+
+
+#define MAX_VOL				 ( ( float ) 0x3fff )
+
+static void iopGetArea(int *pd0, int *d0, int *pd1, int *d1,
+	AudioDec *ad, int pos);
+static int sendToIOP2area(int pd0, int d0, int pd1, int d1,
+	u_char *ps0, int s0, u_char *ps1, int s1);
+static int sendToIOP(int dst, u_char *src, int size);
+//static void changeMasterVolume(u_int val);
+static void changeInputVolume(u_int val);
+
+void SetMovieVolume( void )
+{
+    // ///////////////////////////////////////
+    //
+    // Change input volume
+    //
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+	float musicVol = Pcm::GetVolume( ) * Config::GetMasterVolume()/100.0f;
+	float sfxVol = sfx_manager->GetMainVolume( )  * Config::GetMasterVolume()/100.0f;
+	float vol = musicVol > sfxVol ? musicVol : sfxVol;	
+	int inputVol = ( int ) ( ( vol * MAX_VOL ) / 100.0f );
+    changeInputVolume( inputVol );
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Create audio decoder
+//
+int audioDecCreate(
+    AudioDec *ad,
+    u_char *buff,
+    int buffSize,
+    int iopBuffSize,
+	int pIopBuff
+)
+{
+	SetMovieVolume( );
+    ad->state = AU_STATE_INIT;
+    ad->hdrCount = 0;
+    ad->data = buff;
+    ad->put = 0;
+    ad->count = 0;
+    ad->size = buffSize;
+    ad->totalBytes = 0;
+    ad->totalBytesSent = 0;
+
+    ad->iopBuffSize = iopBuffSize;
+    ad->iopLastPos = 0;
+    ad->iopPausePos = 0;
+
+    // ///////////////////////////////////////
+    //
+    // Audio data buffer on IOP
+    //
+    ad->iopBuff = pIopBuff; //(int)sceSifAllocIopHeap(iopBuffSize);
+    if(ad->iopBuff < 0) {
+	printf( "Cannot allocate IOP memory\n");
+	return 0;
+    }
+    printf("IOP memory 0x%08x(size:%d) is allocated\n",
+    	ad->iopBuff, iopBuffSize);
+
+    // ///////////////////////////////////////
+    //
+    // Zero data buffer on IOP
+    //
+    ad->iopZero = pIopBuff + iopBuffSize; //(int)sceSifAllocIopHeap(ZERO_BUFF_SIZE);
+    if(ad->iopZero < 0) {
+	printf( "Cannot allocate IOP memory\n");
+	return 0;
+    }
+    printf("IOP memory 0x%08x(size:%d) is allocated\n",
+    	ad->iopZero, ZERO_BUFF_SIZE);
+
+    // send zero data to IOP
+    memset ( MOVIE_MEM_PTR _0_buf, 0, ZERO_BUFF_SIZE);
+    sendToIOP(ad->iopZero, ( u_char * ) MOVIE_MEM_PTR _0_buf, ZERO_BUFF_SIZE);
+
+    // ///////////////////////////////////////
+    //
+    // Change master volume
+    //
+//    changeMasterVolume(0x3fff);
+
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Delete audio decoder
+//
+int audioDecDelete(AudioDec *ad)
+{
+//    sceSifFreeIopHeap((void *)ad->iopBuff);
+//    sceSifFreeIopHeap((void *)ad->iopZero);
+
+    // ///////////////////////////////////////
+    //
+    // Change master volume
+    //
+//    changeMasterVolume(0x0000);
+    changeInputVolume(0x0000);
+
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Pause
+//
+void audioDecPause(AudioDec *ad)
+{
+    int ret;
+    ad->state = AU_STATE_PAUSE;
+
+    // ///////////////////////////////////////
+    //
+    // Change input volume
+    //
+    changeInputVolume(0x0000);
+
+    // ///////////////////////////////////////
+    //
+    // Stop DMA and save the position to be played
+    //
+    ret = sceSdRemote(1, rSdBlockTrans, AUTODMA_CH,
+			   SD_TRANS_MODE_STOP, NULL, 0) & 0x00FFFFFF;
+    ad->iopPausePos = ret - ad->iopBuff;
+
+    // ///////////////////////////////////////
+    //
+    // Clear SPU2 buffer
+    //
+    sceSdRemote(1, rSdVoiceTrans, AUTODMA_CH,
+		 SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA,
+		 ad->iopZero, 0x4000, ZERO_BUFF_SIZE);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Resume
+//
+void audioDecResume(AudioDec *ad)
+{
+	SetMovieVolume( );
+    sceSdRemote(1, rSdBlockTrans, AUTODMA_CH,
+    		(SD_TRANS_MODE_WRITE_FROM | SD_BLOCK_LOOP),
+		 ad->iopBuff, (ad->iopBuffSize/UNIT_SIZE)*UNIT_SIZE,
+		 ad->iopBuff + ad->iopPausePos);
+
+    ad->state = AU_STATE_PLAY;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Start to play audio data
+//
+void audioDecStart(AudioDec *ad)
+{
+//    return audioDecResume(ad);
+    audioDecResume(ad);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Re-initialize audio decoder
+//
+void audioDecReset(AudioDec *ad)
+{
+    // ///////////////////////////////////////
+    //
+    // Stop audio
+    //
+    audioDecPause(ad);
+
+    ad->state = AU_STATE_INIT;
+    ad->hdrCount = 0;
+    ad->put = 0;
+    ad->count = 0;
+    ad->totalBytes = 0;
+    ad->totalBytesSent = 0;
+    ad->iopLastPos = 0;
+    ad->iopPausePos = 0;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Get empty areas
+//
+void audioDecBeginPut(AudioDec *ad,
+	u_char **ptr0, int *len0, u_char **ptr1, int *len1)
+{
+    int len;
+
+    // ///////////////////////////////////////
+    //
+    // return ADS header area when (state == AU_STATE_INIT)
+    //
+    if (ad->state == AU_STATE_INIT) {
+    	*ptr0 = (u_char*)&ad->sshd + ad->hdrCount;
+	*len0 = AU_HDR_SIZE - ad->hdrCount;
+	*ptr1 = (u_char*)ad->data;
+	*len1 = ad->size;
+
+	return;
+    }
+
+    // ///////////////////////////////////////
+    //
+    // Return the empty areas
+    //
+    len = ad->size - ad->count;
+
+    if (ad->size -  ad->put >= len) { // area0
+    	*ptr0 = ad->data + ad->put;
+	*len0 = len;
+	*ptr1 = NULL;
+	*len1 = 0;
+    } else {			    // area0 + area1
+    	*ptr0 = ad->data + ad->put;
+	*len0 = ad->size - ad->put;
+	*ptr1 = ad->data;
+	*len1 = len - (ad->size - ad->put);
+    }
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Update pointer
+//
+void audioDecEndPut(AudioDec *ad, int size)
+{
+    if (ad->state == AU_STATE_INIT) {
+    	int hdr_add = min( size, AU_HDR_SIZE - ( int )ad->hdrCount);
+    	ad->hdrCount += hdr_add;
+
+	if ( ( int ) ad->hdrCount >= AU_HDR_SIZE) {
+	    ad->state = AU_STATE_PRESET;
+
+	    printf("-------- audio information --------------------\n");
+	    printf("[%c%c%c%c]\n"
+	       "header size:                            %d\n"
+	       "type(0:PCM big, 1:PCM little, 2:ADPCM): %d\n"
+	       "sampling rate:                          %dHz\n"
+	       "channels:                               %d\n"
+	       "interleave size:                        %d\n"
+	       "interleave start block address:         %d\n"
+	       "interleave end block address:           %d\n",
+		    ad->sshd.id[0],
+		    ad->sshd.id[1],
+		    ad->sshd.id[2],
+		    ad->sshd.id[3],
+		    ad->sshd.size,
+		    ad->sshd.type,
+		    ad->sshd.rate,
+		    ad->sshd.ch,
+		    ad->sshd.interSize,
+		    ad->sshd.loopStart,
+		    ad->sshd.loopEnd
+	    );
+
+	    printf("[%c%c%c%c]\n"
+	       "data size:                              %d\n",
+		    ad->ssbd.id[0],
+		    ad->ssbd.id[1],
+		    ad->ssbd.id[2],
+		    ad->ssbd.id[3],
+		    ad->ssbd.size
+	    );
+
+	}
+	size -= hdr_add;
+    }
+    ad->put = (ad->put + size) % ad->size;
+    ad->count += size;
+    ad->totalBytes += size;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Check to see if enough data is already sent to IOP or not
+//
+int audioDecIsPreset(AudioDec *ad)
+{
+    return ad->totalBytesSent >= PRESET_VALUE(ad->iopBuffSize);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Send data to IOP
+//
+int audioDecSendToIOP(AudioDec *ad)
+{
+    int pd0, pd1, d0, d1;
+    u_char *ps0, *ps1;
+    int s0, s1;
+    int count_sent = 0;
+    int countAdj;
+    int pos = 0;
+
+    switch (ad->state) {
+        case AU_STATE_INIT:
+	    return 0;
+	    break;
+
+        case AU_STATE_PRESET:
+	    pd0 = ad->iopBuff + (ad->totalBytesSent) % ad->iopBuffSize;
+	    d0 = ad->iopBuffSize - ad->totalBytesSent;
+	    pd1 = 0;
+	    d1 = 0;
+	    break;
+
+        case AU_STATE_PLAY:
+	    pos = ((sceSdRemote(1, rSdBlockTransStatus, AUTODMA_CH)
+	    	& 0x00FFFFFF) - ad->iopBuff);
+	    iopGetArea(&pd0, &d0, &pd1, &d1, ad, pos);
+	    break;
+
+        case AU_STATE_PAUSE:
+	    return 0;
+	    break;
+    }
+
+    ps0 = ad->data + (ad->put - ad->count + ad->size) % ad->size;
+    ps1 = ad->data;
+
+    // adjust to UNIT_SIZE boundary
+    countAdj = (ad->count / UNIT_SIZE) * UNIT_SIZE;
+
+    s0 = min(ad->data + ad->size - ps0, countAdj);
+    s1 = countAdj - s0;
+
+    if (d0 + d1 >= UNIT_SIZE && s0 + s1 >= UNIT_SIZE) {
+    	count_sent = sendToIOP2area(pd0, d0, pd1, d1, ps0, s0, ps1, s1);
+    }
+
+    ad->count -= count_sent;
+
+    ad->totalBytesSent += count_sent;
+    ad->iopLastPos = (ad->iopLastPos + count_sent) % ad->iopBuffSize;
+
+    return count_sent;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Get empty area of IOP audio buffer
+//
+static void iopGetArea(int *pd0, int *d0, int *pd1, int *d1,
+					AudioDec *ad, int pos)
+{
+    int len = (pos + ad->iopBuffSize - ad->iopLastPos - UNIT_SIZE)
+    			% ad->iopBuffSize;
+
+    // adjust to UNIT_SIZE boundary
+    len = (len / UNIT_SIZE) * UNIT_SIZE;
+
+    if (ad->iopBuffSize -  ad->iopLastPos >= len) { // area0
+    	*pd0 = ad->iopBuff + ad->iopLastPos;
+	*d0 = len;
+	*pd1 = 0;
+	*d1 = 0;
+    } else {			    // area0 + area1
+    	*pd0 = ad->iopBuff + ad->iopLastPos;
+	*d0 = ad->iopBuffSize - ad->iopLastPos;
+	*pd1 = ad->iopBuff;
+	*d1 = len - (ad->iopBuffSize - ad->iopLastPos);
+    }
+}
+
+
+// ////////////////////////////////////////////////////////////////
+//
+// Send data to IOP
+//
+static int sendToIOP2area(int pd0, int d0, int pd1, int d1,
+		u_char *ps0, int s0, u_char *ps1, int s1)
+{
+    if (d0 + d1 < s0 + s1) {
+    	int diff = (s0 + s1) - (d0 + d1);
+	if (diff >= s1) {
+	    s0 -= (diff - s1);
+	    s1 = 0;
+	} else {
+	    s1 -= diff;
+	}
+    }
+
+    //
+    // (d0 + d1 >= s0 + s1)
+    //
+    if (s0 >= d0) {
+    	sendToIOP(pd0,			ps0,		d0);
+    	sendToIOP(pd1,			ps0 + d0,	s0 - d0);
+    	sendToIOP(pd1 + s0 - d0,	ps1,		s1);
+    } else { // s0 < d0
+    	if (s1 >= d0 - s0) {
+	    sendToIOP(pd0,		ps0,		s0);
+	    sendToIOP(pd0 + s0,		ps1,		d0 - s0);
+	    sendToIOP(pd1,		ps1 + d0 - s0,	s1 - (d0 - s0));
+	} else { // s1 < d0 - s0
+	    sendToIOP(pd0,		ps0,		s0);
+	    sendToIOP(pd0 + s0,		ps1,		s1);
+	}
+    }
+    return s0 + s1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Send data to IOP
+//
+static int sendToIOP(int dst, u_char *src, int size)
+{
+    sceSifDmaData transData;
+    int did;
+
+    if (size <= 0) {
+        return 0;
+    }
+
+    transData.data = (u_int)src;
+    transData.addr = (u_int)dst;
+    transData.size = size;
+    transData.mode = 0; // caution
+    FlushCache(0);
+
+    did = sceSifSetDma( &transData, 1 );
+
+    while (sceSifDmaStat(did) >= 0)
+    	;
+
+    return size;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Change master volume
+//
+/*
+static void changeMasterVolume(u_int val)
+{
+    int i;
+    for( i = 0; i < 2; i++ ) {
+	sceSdRemote(1, rSdSetParam, i | SD_P_MVOLL, val);
+	sceSdRemote(1, rSdSetParam, i | SD_P_MVOLR, val);
+    }
+} */
+
+// ////////////////////////////////////////////////////////////////
+//
+// Change input volume
+//
+static void changeInputVolume(u_int val)
+{
+    sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_BVOLL, val);
+    sceSdRemote(1, rSdSetParam, AUTODMA_CH | SD_P_BVOLR, val);
+}
+
+} // namespace Flx
diff --git a/Code/Gel/Movies/Ngps/audiodec.h b/Code/Gel/Movies/Ngps/audiodec.h
new file mode 100644
index 0000000..c53d666
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/audiodec.h
@@ -0,0 +1,112 @@
+#ifndef _AUDIODEC_H_
+#define _AUDIODEC_H_
+
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+
+// ///////////////////////////////////////////////////////
+//
+// Audio decoder state
+//
+#define AU_STATE_INIT		0
+#define AU_STATE_PRESET		1
+#define AU_STATE_PLAY		2
+#define AU_STATE_PAUSE		3
+
+#define AU_HDR_SIZE		( ( int )(sizeof(SpuStreamHeader) 					+ sizeof(SpuStreamBody)) )
+
+// ///////////////////////////////////////////////////////
+//
+// Spu stream header
+//
+struct SpuStreamHeader{
+    char id[4];		// 'S''S''h''d'
+    int size;		// 24
+    int type;		// 0: 16bit big endian
+    			// 1: 16bit little endian
+			// 2: SPU2-ADPCM (VAG) 
+    int rate;		// sampling rate
+    int ch;		// number of channels
+    int interSize;	// interleave size ... needs to be 512
+    int loopStart;	// loop start block address
+    int loopEnd;	// loop end block sddress
+};
+
+// ///////////////////////////////////////////////////////
+//
+// Spu stream body
+//
+struct SpuStreamBody{
+    char id[4];		// 'S''S''b''d'
+    int size;		// size of audio data
+};
+
+// ///////////////////////////////////////////////////////
+//
+// Audio decoder
+//
+struct AudioDec{
+
+    int state;
+
+    // header of ADS format
+	struct SpuStreamHeader sshd;
+	struct SpuStreamBody   ssbd;
+    int hdrCount;
+
+    // audio buffer
+    u_char *data;
+    int put;
+    int count;
+    int size;
+    int totalBytes;
+
+    // buffer on IOP
+    int iopBuff;
+    int iopBuffSize;
+    int iopLastPos;
+    int iopPausePos;
+    int totalBytesSent;
+    int iopZero;
+
+};
+
+namespace Flx
+{
+
+
+
+// ///////////////////////////////////////////////////////
+//
+// Functions
+//
+int audioDecCreate(
+    struct AudioDec *ad,
+    u_char *buff,
+    int buffSize,
+    int iopBuffSize,
+	int pIopBuf
+);
+int audioDecDelete( struct AudioDec *ad);
+void audioDecBeginPut( struct AudioDec *ad,
+	u_char **ptr0, int *len0, u_char **ptr1, int *len1);
+void audioDecEndPut(struct AudioDec *ad, int size);
+int audioDecIsPreset(struct AudioDec *ad);
+void audioDecStart(struct AudioDec *ad);
+int audioDecSendToIOP(struct AudioDec *ad);
+void audioDecReset(struct AudioDec *ad);
+void audioDecPause(struct AudioDec *ad);
+void audioDecResume(struct AudioDec *ad);
+
+} // namespace Flx
+
+#endif _AUDIODEC_H_
+
+
+
diff --git a/Code/Gel/Movies/Ngps/defs.h b/Code/Gel/Movies/Ngps/defs.h
new file mode 100644
index 0000000..1d2aaa0
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/defs.h
@@ -0,0 +1,55 @@
+#ifndef _DEFS_H_
+#define _DEFS_H_
+
+#include 
+#include 
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#define STRFILE_NUM_STREAMING_SECTORS 80
+
+#define UNCMASK 0x0fffffff
+#define UNCBASE 0x20000000
+
+#define MAX_WIDTH 720
+#define MAX_HEIGHT 576
+
+#define MOVIE_THREAD_PRIORITY 1
+#define AUTODMA_CH 0
+
+#define N_LDTAGS (MAX_WIDTH/16 * MAX_HEIGHT/16 * 6 + 10)
+#define TS_NONE (-1)
+
+#define bound(val, x) ((((val) + (x) - 1) / (x))*(x))
+#define min(x, y) (((x) > (y))? (y): (x))
+#define max(x, y) (((x) < (y))? (y): (x))
+
+extern inline void *DmaAddr(void *val)
+{
+    return (void*)((u_int)val & UNCMASK);
+}
+
+extern inline void *UncAddr(void *val)
+{
+    return (void*)(((u_int)val & UNCMASK)|UNCBASE);
+}
+ 
+namespace Flx
+{
+
+
+
+void ErrMessage(char *message);
+void switchThread();
+void proceedAudio();
+
+}
+
+
+
+#endif // _DEFS_H_
diff --git a/Code/Gel/Movies/Ngps/disp.cpp b/Code/Gel/Movies/Ngps/disp.cpp
new file mode 100644
index 0000000..f3bd603
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/disp.cpp
@@ -0,0 +1,328 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/disp.h"
+#include "gel/movies/ngps/videodec.h"
+#include "gel/movies/ngps/vobuf.h"
+#include "gel/movies/ngps/p_movies.h"
+#include 
+
+extern sceGsDBuff gDB;
+
+volatile int isCountVblank = 0;
+volatile int vblankCount = 0;
+volatile int isFrameEnd = 0;
+volatile int oddeven = 0;
+volatile int handler_error = 0;
+
+extern int frd;
+
+typedef struct {
+  int x, y, w, h;
+} Rect;
+
+namespace Flx
+{
+
+
+
+///////////////////////////////////////////////////////////////////
+//
+// Delete previous image of frame buffer and texture buffer
+//
+void clearGsMem(int r, int g, int b, int disp_width, int disp_height)
+{
+	const u_long giftag_clear[2] = { SCE_GIF_SET_TAG(0, 1, 0, 0, 0, 1), 
+				 0x000000000000000eL };
+	sceGifPacket packet;
+	
+	u_long128 packetBase[ 6 ];
+
+	// Abort if gDB hasn't been initialized
+	if (*((uint64 *) &gDB.disp[0].display) == 0)
+	{
+		return;
+	}
+	
+	sceDmaChan* dmaGif = sceDmaGetChan(SCE_DMA_GIF);
+	
+	// setting GIF tag for gDB.draw0 structure
+	SCE_GIF_CLEAR_TAG(&gDB.giftag0);
+	gDB.giftag0.NLOOP = 8;
+	gDB.giftag0.EOP = 1;
+	gDB.giftag0.NREG = 1;
+	gDB.giftag0.REGS0 = 0xe; // A_D
+	
+	// define GS memory as a one big drawing area
+	// "bound(DISP_HEIGHT/2, 32)*2" is for frame buffer * 2
+	// "bound(DISP_HEIGHT, 32)*2" is for texture buffer
+	sceGsSetDefDrawEnv(&gDB.draw0, SCE_GS_PSMCT32, disp_width,
+			 bound(disp_height/2, 32)*2 + bound(disp_height, 32)*2,
+			 0, 0);
+	*(u_long *)&gDB.draw0.xyoffset1 = SCE_GS_SET_XYOFFSET_1(0, 0);
+	
+	FlushCache(0);
+	sceGsSyncPath(0, 0);
+	sceGsPutDrawEnv(&gDB.giftag0);
+	
+//	sceGifPkInit(&packet, MOVIE_MEM_PTR packetBase);
+	sceGifPkInit(&packet, packetBase);	  		// Mick: Use a local one, so I can use this function for laodng screen clearing
+	sceGifPkReset(&packet);
+	
+	sceGifPkEnd(&packet, 0, 0, 0);
+	
+	// packet for a big sprite polygon
+	{
+	sceGifPkOpenGifTag(&packet, *(u_long128*)&giftag_clear);
+	sceGifPkAddGsAD(&packet, SCE_GS_PRIM,
+			SCE_GS_SET_PRIM(6, 0, 0, 0, 0, 0, 0, 0, 0));
+	sceGifPkAddGsAD(&packet, SCE_GS_RGBAQ, SCE_GS_SET_RGBAQ(r, g, b, 0, 0));
+	sceGifPkAddGsAD(&packet, SCE_GS_XYZ2,
+			SCE_GS_SET_XYZ2(0 << 4,0 << 4, 0)); 
+	sceGifPkAddGsAD(&packet, SCE_GS_XYZ2,
+			SCE_GS_SET_XYZ2( MAX_WIDTH << 4, MAX_HEIGHT*5 << 4, 0)); 
+	sceGifPkCloseGifTag(&packet);
+	}
+	
+	sceGifPkTerminate(&packet);
+	
+	FlushCache(0);
+	sceGsSyncPath(0, 0);
+	sceDmaSend(dmaGif, (u_long128*)((u_int)packet.pBase));
+	sceGsSyncPath(0, 0);
+//	free(packetBase);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Set tags for image transfer using path3
+//
+void setImageTag(u_int *tags, void *image, int index, int image_w, int image_h)
+{
+  u_int dbp, dbw, dpsm;
+  u_int dir, dsax, dsay;
+  u_int rrw, rrh;
+  u_int xdir;
+  int mbx = image_w >> 4;
+  int mby = image_h >> 4;
+  int i, j;
+  Rect tex;
+  Rect poly;
+
+  sceGifPacket packet;	
+  const u_long giftag[2] = { SCE_GIF_SET_TAG(0, 0, 0, 0, 0, 1), 
+			     0x000000000000000eL };
+  const u_long giftag_eop[2] = { SCE_GIF_SET_TAG(0, 1, 0, 0, 0, 1), 
+				 0x000000000000000eL };
+  sceGifPkInit(&packet, (u_long128*)UncAddr(tags));
+  sceGifPkReset(&packet);
+  
+  if (index == 0) {  // set image data to packet
+
+    //  set params
+    dbp = (bound(MAX_WIDTH, 64) * bound((MAX_HEIGHT/2), 32) * 2)/64;
+    dbw = bound(MAX_WIDTH, 64)/64;
+    dpsm = SCE_GS_PSMCT32;
+
+    dir = 0;
+    dsax = 0;
+    dsay = 0;
+
+    rrw = 16;
+    rrh = 16;
+
+    xdir = 0;
+
+    sceGifPkCnt(&packet, 0, 0, 0);
+
+    // eop == 0
+    {    
+      sceGifPkOpenGifTag(&packet, *(u_long128*)&giftag);
+      sceGifPkAddGsAD(&packet, SCE_GS_BITBLTBUF,
+		      SCE_GS_SET_BITBLTBUF(0, 0, 0, dbp, dbw, dpsm));
+      sceGifPkAddGsAD(&packet, SCE_GS_TRXREG, SCE_GS_SET_TRXREG(rrw, rrh));
+      sceGifPkCloseGifTag(&packet);
+    }
+
+    // //////////////////////////////////////
+    // 
+    //  create packet for image data
+    // 
+    for (i = 0; i < mbx; i++) {
+      for (j = 0; j < mby; j++) {
+
+	sceGifPkCnt(&packet, 0, 0, 0);
+
+	// eop == 0
+	{
+	  sceGifPkOpenGifTag(&packet, *(u_long128*)giftag);
+	  sceGifPkAddGsAD(&packet, SCE_GS_TRXPOS,
+			  SCE_GS_SET_TRXPOS(0, 0, 16*i+dsax, 16*j+dsay, 0));
+	  sceGifPkAddGsAD(&packet, SCE_GS_TRXDIR, SCE_GS_SET_TRXDIR(xdir));
+	  sceGifPkCloseGifTag(&packet);
+	}
+	
+	{
+	  u_long* const tag = (u_long*)sceGifPkReserve(&packet, 4);
+	  tag[0] = SCE_GIF_SET_TAG(16*16*4/16, 0, 0, 0, 2, 0);
+		tag[1] = 0;
+	}
+	sceGifPkRef(&packet, (u_long128 *) DmaAddr(image), 16*16*4/16, 0, 0, 0);
+	image = (u_char*)image + 16*16*4;
+      }
+    }
+  }
+
+  tex.x = 8; // 0.5
+  tex.y = 8; // 0.5
+  tex.w = image_w << 4;
+  tex.h = image_h << 4;
+  
+	if (Config::PAL())
+	{
+		poly.x = (2048 - 320) << 4;
+		poly.y = (2048 - (512/2)/2) << 4;
+		poly.w = 640 << 4;
+		poly.h = (512/2) << 4;
+	}  
+	else
+	{
+		poly.x = (2048 - 320) << 4;
+		poly.y = (2048 - (480/2)/2) << 4;
+		poly.w = 640 << 4;
+		poly.h = (480/2) << 4;
+	}  
+  // --------------------------------
+  sceGifPkEnd(&packet, 0, 0, 0);
+
+  // eop == 1
+  {
+    sceGifPkOpenGifTag(&packet, *(u_long128*)giftag_eop);
+    sceGifPkAddGsAD(&packet, SCE_GS_TEXFLUSH, 0);
+    sceGifPkAddGsAD(&packet, SCE_GS_TEX1_1,
+		    SCE_GS_SET_TEX1_1(0, 0, 1, 1, 0, 0, 0));
+    sceGifPkAddGsAD(&packet, SCE_GS_TEX0_1,
+		    SCE_GS_SET_TEX0_1((bound(MAX_WIDTH, 64) 
+				       * bound((MAX_HEIGHT/2), 32) * 2)/64,
+				      bound(MAX_WIDTH, 64)/64, SCE_GS_PSMCT32, 
+				      10, 10, 0, 1, 0, 0, 0, 0, 0));
+    sceGifPkAddGsAD(&packet, SCE_GS_PRIM,
+		    SCE_GS_SET_PRIM(6, 0, 1, 0, 0, 0, 1, 0, 0));
+    sceGifPkAddGsAD(&packet, SCE_GS_UV, SCE_GS_SET_UV(tex.x, tex.y)); 
+    sceGifPkAddGsAD(&packet, SCE_GS_XYZ2, SCE_GS_SET_XYZ2(poly.x, poly.y, 0)); 
+    sceGifPkAddGsAD(&packet, SCE_GS_UV,
+		    SCE_GS_SET_UV(tex.x + tex.w, tex.y + tex.h)); 
+    sceGifPkAddGsAD(&packet, SCE_GS_XYZ2, 
+		    SCE_GS_SET_XYZ2(poly.x + poly.w, poly.y + poly.h, 0)); 
+    sceGifPkCloseGifTag(&packet);
+  }
+
+  // finish making packet
+  sceGifPkTerminate(&packet);
+}
+
+// /////////////////////////////////////////////////////////////////////
+//
+// vblank handler
+//
+int vblankHandler(int val)
+{
+	
+	
+	CHECK_MOVIE_MEM;
+	
+	sceDmaChan* dmaGif_loadimage = sceDmaGetChan(SCE_DMA_GIF);
+	oddeven = ((*GS_CSR) >> 13) & 1; // odd == 1, even == 0
+
+	if (isCountVblank)
+	{
+
+		VoTag *tag;
+		vblankCount++;
+		handler_error = sceGsSyncPath(1, 0);
+		if (!handler_error)
+		{ // no error
+			tag = voBufGetTag(& MOVIE_MEM_PTR voBuf);
+			if (!tag)
+			{
+				frd++;
+				ExitHandler();
+				return 0;
+			}
+			
+			sceGsSetHalfOffset((oddeven&1)?(sceGsDrawEnv1*)UncAddr(&gDB.draw1)
+				:(sceGsDrawEnv1*)UncAddr(&gDB.draw0), 2048, 2048, oddeven^0x1);
+			
+			if ((oddeven == 0) && (tag->status == VOBUF_STATUS_FULL))
+			{
+				sceGsSwapDBuff(&gDB, 0);
+				
+				// Load image data to GS using path3
+				sceGsSyncPath(0, 0);
+				sceDmaSend(dmaGif_loadimage, (u_long128*)((u_int)tag->v[0]));
+				
+				tag->status = VOBUF_STATUS_TOPDONE;
+				
+			}
+			else if ((oddeven == 1) && tag->status == VOBUF_STATUS_TOPDONE)
+			{
+				sceGsSwapDBuff(&gDB, 1);
+				
+				// Load image data to GS using path3
+				sceGsSyncPath(0, 0);
+				sceDmaSend(dmaGif_loadimage, (u_long128*)((u_int)tag->v[1]));
+				tag->status = VOBUF_STATUS_;
+				
+				isFrameEnd = 1;
+			}
+		}
+	}
+	ExitHandler();
+	return 0;
+}
+
+// ///////////////////////////////////////////////////////////////
+// 
+//  Handler to check the end of image transfer
+// 
+int handler_endimage(int val)
+{
+  if (isFrameEnd) {
+    voBufDecCount(& MOVIE_MEM_PTR voBuf);
+    isFrameEnd = 0;
+  }
+  ExitHandler();
+  return 0;
+}
+
+// ///////////////////////////////////////////////////////////////////
+// 
+//  Wait until even/odd field
+//  Start to count vblank
+// 
+void startDisplay(int waitEven)
+{
+  // wait untill even field
+  while (sceGsSyncV(0) == waitEven)
+    ;
+  
+  frd = 0;
+  isCountVblank = 1;
+  vblankCount = 0;
+}
+
+// ///////////////////////////////////////////////////////////////////
+// 
+//  Stop to count vblank
+// 
+void endDisplay()
+{
+  isCountVblank =  0;
+  frd = 0;
+}
+
+} // namespace Flx
diff --git a/Code/Gel/Movies/Ngps/disp.h b/Code/Gel/Movies/Ngps/disp.h
new file mode 100644
index 0000000..703f9d8
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/disp.h
@@ -0,0 +1,29 @@
+#ifndef _DISP_H_
+#define _DISP_H_
+
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+namespace Flx
+{
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+// Functions
+//
+void clearGsMem(int r, int g, int b, int disp_width, int disp_height);
+void setImageTag(u_int *tags, void *image, int index,
+		 int image_w, int image_h);
+void startDisplay(int waitEven);
+void endDisplay();
+
+} // namespace Flx
+
+#endif _DISP_H_
diff --git a/Code/Gel/Movies/Ngps/p_movies.cpp b/Code/Gel/Movies/Ngps/p_movies.cpp
new file mode 100644
index 0000000..ea012e1
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/p_movies.cpp
@@ -0,0 +1,763 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gfx/gfxman.h"
+
+#include "gfx/ngps/nx/nx_init.h"
+#include "gfx/ngps/nx/pcrtc.h"
+#include "gfx/ngps/nx/resource.h"
+
+#include "gel/music/ngps/p_music.h"
+
+#include "gel/movies/movies.h"
+#include "gel/movies/ngps/p_movies.h"
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/videodec.h"
+#include "gel/movies/ngps/disp.h"
+#include "gel/movies/ngps/readbuf.h"
+#include "gel/movies/ngps/vobuf.h"
+#include "gel/movies/ngps/audiodec.h"
+#include "gel/movies/ngps/strfile.h"
+#include "sys/timer.h"
+
+#include 
+#include 
+#include 
+#include 
+
+// K: To switch this off, make it 0 rather than undefining it, cos it won't compile otherwise
+#define FORCE_MOVIES_FROM_CD_PLEASE 0
+
+int videoDecTh;
+int defaultTh;
+int frd;
+u_int controller_val;
+int isWithAudio = 1;
+
+bool hitStart = false;
+uint64 hitStartFrame;
+
+sceGsDBuff gDB;
+
+namespace Flx
+{
+
+
+
+static bool initAll( const char *bsfilename );
+static void termAll();
+static void defMain(void *dummy);
+static u_int movie( const char *name );
+static int readMpeg(VideoDec *vd, ReadBuf *rb, StrFile *file);
+static int isAudioOK();
+
+void dispMain(void);
+void videoDecMain( void *dummy);
+int videoCallback(sceMpeg *mp, sceMpegCbDataStr *str, void *data);
+int pcmCallback(sceMpeg *mp, sceMpegCbDataStr *str, void *data);
+int vblankHandler(int);
+int handler_endimage(int);
+void loadModule(char *moduleName);
+
+#define NUM_VIDEO_BUFFERS	3
+#define VRAM_TOTAL_SIZE		( 1024 * 4096 ) // 4 megs of video ram
+#define MAX_PIXEL_DATA_SIZE	( 32767 * 16 )
+
+#define ZBUF_ADDR(w, h)		( (((w) + 63) / 64) * (((h) + 31) / 32) * 2)
+
+SMovieMem *gpMovieMem = NULL;
+
+int GetThreadPriority( )
+{
+	struct ThreadParam info;
+	// Fill info with zeros to be sure the following call is doing
+	// something to info
+	memset(&info,0,sizeof(info));
+	// return value not defined in 2.0 PDF EE lib ref
+	ReferThreadStatus( GetThreadId(), &info );
+	return ( info.currentPriority );
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Play Movie
+//
+
+#define RESTORE_VRAM 1
+#define PLAY_MOVIE_PLEASE 1
+#define SAVE_SCRATCHPAD 0
+#define SCRATCHPAD_SIZE	8192
+
+void PMovies_PlayMovie( const char *pName )
+{
+	// Re-enabled for CD even though we don't have a Sony fix.
+//	// Garrett: Disabled CD movies until I know the sceCdSt() calls are working again in 2.7.2
+//	if (Config::CD() || FORCE_MOVIES_FROM_CD_PLEASE)
+//	{
+//		return;
+//	}
+	
+	int origThreadPriority;
+//	int origArenaSize;
+	int width=0;
+	int height=0;
+//	int origDepth;
+//	char *pOrigVram;
+//	char *pNonAllignedOrigVram;
+
+	//Dbg_Message("*************** Movie: In render frame %d", Tmr::GetRenderFrame());
+
+	// Check to see if we need to skip movies
+	if (hitStart && (hitStartFrame == Tmr::GetRenderFrame()))
+	{
+		return;
+	}
+	
+	// stop music and streams (we're gonna be using the IOP memory)
+	Pcm::StopMusic( );
+	Pcm::StopStreams( );
+
+	// Suspend Nx engine and its interrupts
+	NxPs2::SuspendEngine();
+		
+
+	if (Config::PAL())
+	{
+		width = 640;
+		height = 512;
+	} else {
+		width = 640;
+		height = 480;
+	}
+
+	// clear vram onscreen:
+	if (Config::PAL())
+	{
+		clearGsMem(0x00, 0x00, 0x00, 640, ( height * width * 2 ) / 640 );
+	}
+	else
+	{
+		clearGsMem(0x00, 0x00, 0x00, width, height * 2);
+	}
+
+	// get current thread priority:
+	origThreadPriority = GetThreadPriority( );
+    ChangeThreadPriority(GetThreadId(), MOVIE_THREAD_PRIORITY );
+	
+
+#if PLAY_MOVIE_PLEASE
+    isWithAudio = 1; // withAudio;
+    
+	Dbg_MsgAssert( pName, ( "No movie name specified" ) );
+
+	Dbg_MsgAssert( !gpMovieMem, ( "Movie memory already allocated!" ) );
+									  
+	// Allocate memory on high heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+	void *pNonAllignedMovieMem = (void *) Mem::Malloc( sizeof( SMovieMem ) + 64 );
+	
+	if ( !pNonAllignedMovieMem )
+	{
+		Dbg_MsgAssert( 0, ( "Ran out of memory... can't play movie %s.", pName ) );
+		return;
+	}
+	gpMovieMem = ( SMovieMem * )( ( ( int )pNonAllignedMovieMem ) + ( 64 - ( ( ( int ) pNonAllignedMovieMem ) & 63 ) ) );
+
+	Dbg_MsgAssert( !( ( ( int )& MOVIE_MEM_PTR voBufData & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR voBufTag & 63 ) ||
+        ( ( int )& MOVIE_MEM_PTR viBufTag & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR mpegWork & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR defStack & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR audioBuff & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR viBufData & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR videoDecStack & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR timeStamp & 63 ) ||
+//		( ( int )& MOVIE_MEM_PTR controller_dma_buf & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR readBuf & 63 ) ||
+        ( ( int )& MOVIE_MEM_PTR infile & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR videoDec & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR audioDec & 63 ) ||
+		( ( int )& MOVIE_MEM_PTR voBuf & 63 ) ), ( "Bad allignment in SMovieMem structure." ) );
+/*
+	Dbg_Message( "Allignment: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", ( int )& MOVIE_MEM_PTR voBufData & 63, ( int )& MOVIE_MEM_PTR voBufTag & 63,
+	 ( int )& MOVIE_MEM_PTR viBufTag & 63, ( int )& MOVIE_MEM_PTR mpegWork & 63, ( int )& MOVIE_MEM_PTR defStack & 63, ( int )& MOVIE_MEM_PTR audioBuff & 63, ( int )& MOVIE_MEM_PTR viBufData & 63,
+	 ( int )& MOVIE_MEM_PTR videoDecStack & 63, ( int )& MOVIE_MEM_PTR timeStamp & 63, ( int )& MOVIE_MEM_PTR controller_dma_buf & 63, ( int )& MOVIE_MEM_PTR readBuf & 63,
+	 ( int )& MOVIE_MEM_PTR infile & 63, ( int )& MOVIE_MEM_PTR videoDec & 63, ( int )& MOVIE_MEM_PTR audioDec & 63, ( int )& MOVIE_MEM_PTR voBuf & 63 );
+*/
+	Dbg_Message( "Size of SMovieMem struct: %d k", sizeof( SMovieMem ) / 1024 );
+
+
+#if SAVE_SCRATCHPAD
+	// remember scratchpad:
+	unsigned char *pScratchpad = ( unsigned char * )Mem::Malloc( SCRATCHPAD_SIZE );
+	Dbg_MsgAssert( pScratchpad, ( "Ran out of memory allocating scratchpad." ) );
+	int i;
+	for ( i = 0; i < SCRATCHPAD_SIZE; i++ )
+	{
+		pScratchpad[ i ] = ( ( unsigned char * ) 0x70000000 )[ i ];
+	}
+#else
+	// Make sure scratchpad is available
+	bool got_scratch = NxPs2::CSystemResources::sRequestResource(NxPs2::CSystemResources::vSCRATCHPAD_MEMORY);
+	Dbg_Assert(got_scratch);
+#endif // SAVE_SCRATCHPAD
+
+	// Pop top-down context
+	Mem::Manager::sHandle().PopContext();
+
+	sceGsResetPath();
+    sceDmaReset(1);
+
+	NxPs2::SetupPCRTC(1, SCE_GS_FRAME);
+
+	// Old way of initializing.  We can't use sceGsResetGraph() anymore.  It interferes with SetupPCRTC().
+	// Also, clearGsMem() did nothing before because it used gDB, which you can see was not initialized until
+	// after the calls.
+#if 0  
+		//clearGsMem(0x00, 0x00, 0x00, 640, 480);
+		//VSync( );
+		
+		//sceGsResetGraph(0, SCE_GS_INTERLACE, SCE_GS_NTSC, SCE_GS_FRAME);
+
+		//  Initialize GS memory
+		//clearGsMem(0x00, 0x00, 0x00, 640, 480);
+#endif
+	
+	sceGsSetDefDBuff(&gDB, SCE_GS_PSMCT32, width, (height/2),
+			 SCE_GS_ZNOUSE, 0, SCE_GS_CLEAR);
+
+	int zbuf_addr = ZBUF_ADDR(width, (height/2));
+
+	// Garrett: Since sceGsResetGraph() saves some register settings in internal library variables (including FRAME),
+	// we have to update some settings in gDB with the correct values.
+	gDB.disp[0].smode2.FFMD = SCE_GS_FRAME;
+	gDB.disp[1].smode2.FFMD = SCE_GS_FRAME;
+	gDB.disp[0].dispfb.FBP	= 0x0;
+	gDB.disp[1].dispfb.FBP	= zbuf_addr >> 1;
+	gDB.disp[0].display.DH	= height-1;
+	gDB.disp[1].display.DH	= height-1;
+	gDB.draw0.frame1.FBP	= zbuf_addr >> 1;
+	gDB.draw1.frame1.FBP	= 0x0;
+	gDB.draw0.zbuf1.ZBP		= zbuf_addr;
+	gDB.draw1.zbuf1.ZBP		= zbuf_addr;
+				 
+    FlushCache(0);
+
+	// Wait for all the music and streams to really stop, since we are stealing their IOP memory
+	uint32 target_vblank = Tmr::GetVblanks() + (5 * Config::FPS());		// 5 seconds
+	while (Tmr::GetVblanks() < target_vblank)
+	{
+		Pcm::PCMAudio_Update();
+
+		if (Pcm::PCMAudio_GetMusicStatus() != Pcm::PCM_STATUS_FREE)
+			continue;
+		
+		bool done = true;
+		for (int streamNum = 0; streamNum < NUM_STREAMS; streamNum++)
+		{
+			if (Pcm::PCMAudio_GetStreamStatus( streamNum ) != Pcm::PCM_STATUS_FREE)
+				done = false;
+		}
+
+		if (done)
+		{
+			break;
+		}
+	}
+
+	movie( pName );
+    //while ( ( movie( pName ) ) != MOVIE_ABORTED)
+    //	;
+
+	// Clear double buffers to black:
+	clearGsMem(0x00, 0x00, 0x00, width, height);
+	
+	
+	// wait for any sound transfer to complete and shit...
+	if ( sceSdRemote( 1, rSdVoiceTransStatus, AUTODMA_CH, SD_TRANS_STATUS_WAIT ) == -1 )
+	{
+		Dbg_MsgAssert( 0,( "Complete SPU2 Transfer Failed\n"));
+	}
+
+	Mem::Free( pNonAllignedMovieMem );
+	gpMovieMem = NULL;
+	
+#if SAVE_SCRATCHPAD
+	// restore scratchpad:
+	for ( i = 0; i < SCRATCHPAD_SIZE; i++ )
+	{
+		( ( unsigned char * ) 0x70000000 )[ i ] = pScratchpad[ i ];
+	}
+	Mem::Free( pScratchpad );
+#else
+	if (got_scratch)
+	{
+		NxPs2::CSystemResources::sFreeResource(NxPs2::CSystemResources::vSCRATCHPAD_MEMORY);
+	}
+#endif // SAVE_SCRATCHPAD
+
+//	VSync( );
+//	while ( Tmr::GetVblanks() & 1 );
+
+#endif // playmovieplease
+
+
+	ChangeThreadPriority(GetThreadId(), origThreadPriority );
+
+	Tmr::VSync( );
+
+	return;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Decode MPEG bitstream
+//
+// ret:
+//   1: ok
+//   0: error
+//  -1: abort
+static u_int movie( const char *name )
+{
+	CHECK_MOVIE_MEM;
+
+    static int count = 0;
+
+    printf("========================== decode MPEG2 ============= %d ===========\n", count++);
+	
+    if (initAll(name ))
+	{
+		printf( "done w/ initall\n" );	
+		readMpeg(& MOVIE_MEM_PTR videoDec, & MOVIE_MEM_PTR readBuf, & MOVIE_MEM_PTR infile);
+	}
+
+	termAll();
+
+    return controller_val;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Read MPEG data
+//
+// return value
+//     1: normal end
+//     -1: aborted
+static int readMpeg(VideoDec *vd, ReadBuf *rb, StrFile *file)
+{
+	
+    u_int ctrlmask =  SCE_PADRdown | SCE_PADstart;
+    u_char *put_ptr;
+    u_char *get_ptr;
+    int putsize;
+    int getsize;
+    int readrest = file->size;
+    int writerest = file->size;
+    int count;
+    int proceed;
+    int isStarted = 0;
+    u_int button_old = 0;
+    u_int pushed = 0;
+    u_char cdata[32];
+    int isPaused = 0;
+
+    // writerest > 4: to skip the last 4 bytes
+    while (isPaused
+    	|| (writerest > 4 && videoDecGetState(vd) != VD_STATE_END)) {
+
+    	// /////////////////////////////////////////////////
+	//
+	// Get controller information
+	//
+   	if (scePadRead(0, 0, cdata) > 0) {
+	    controller_val = 0xffff ^ ((cdata[2] << 8) | cdata[3]);
+	} else {
+	    controller_val = 0;
+	}
+	pushed = (button_old ^ controller_val)
+			& controller_val & ctrlmask;
+	button_old = controller_val;
+
+	CHECK_MOVIE_MEM;
+
+	if (pushed && vd->mpeg.frameCount > 10)
+	{
+
+/*	    if (pushed & SCE_PADRleft) {
+	    	if (isPaused) {
+		    startDisplay(1);
+		    if (isWithAudio) {
+			audioDecResume(& MOVIE_MEM_PTR audioDec);
+		    }
+		} else {
+		    endDisplay();
+		    if (isWithAudio) {
+			audioDecPause(& MOVIE_MEM_PTR audioDec);
+		    }
+		}
+		isPaused ^= 1;
+	    } else if (!isPaused) {
+*/
+		// /////////////////////////////////////////////////
+		//
+		// Abort decoding
+		//
+		videoDecAbort(& MOVIE_MEM_PTR videoDec);
+	//    }
+
+		// Record frame number if we hit START so we skip all movies
+		if (pushed & SCE_PADstart)
+		{
+			hitStartFrame = Tmr::GetRenderFrame();
+			hitStart = true;
+		} else {
+			hitStart = false;
+		}
+	}
+   	
+	// /////////////////////////////////////////////////
+	//
+	// Read data to the read buffer
+	//
+        putsize = readBufBeginPut(rb, &put_ptr);
+	if (readrest > 0 && putsize >= READ_UNIT_SIZE) {
+	    count = strFileRead(file, put_ptr, READ_UNIT_SIZE);
+	    readBufEndPut(rb, count);
+	    readrest -= count;
+	}
+
+	switchThread();
+
+    	// /////////////////////////////////////////////////
+	//
+	// De-multiplex and put data on video/audio input buffer
+	//
+	getsize = readBufBeginGet(rb, &get_ptr);
+	if (getsize > 0) {
+
+	    proceed = sceMpegDemuxPssRing(&vd->mpeg,
+	    		get_ptr, getsize, rb->data, rb->size);
+
+	    readBufEndGet(rb, proceed);
+	    writerest -= proceed;
+
+	}
+
+    	// /////////////////////////////////////////////////
+	//
+	// Send audio data to IOP
+	//
+	proceedAudio();
+
+    	// /////////////////////////////////////////////////
+	//
+	// Wait until video and audio output buffer become full
+	//
+	CHECK_MOVIE_MEM;
+	
+	if (!isStarted && voBufIsFull(& MOVIE_MEM_PTR voBuf) && isAudioOK()) {
+
+	    startDisplay(1);		// start video
+	    if (isWithAudio) {
+		audioDecStart(& MOVIE_MEM_PTR audioDec);	// start audio
+	    }
+	    isStarted = 1;
+	}
+    }
+
+    // try to flush buffers inside decoder
+    while (!videoDecFlush(vd)) {
+	switchThread();
+    }
+
+    // wait till buffers are flushed
+    while (!videoDecIsFlushed(vd)
+    	&& videoDecGetState(vd) != VD_STATE_END) {
+
+	switchThread();
+    }
+
+    endDisplay();
+    if (isWithAudio)
+	{
+		audioDecReset(& MOVIE_MEM_PTR audioDec);
+    }
+
+    return 1;
+}
+
+// /////////////////////////////////////////////////
+//
+// Switch to another thread
+//
+void switchThread()
+{
+    RotateThreadReadyQueue( MOVIE_THREAD_PRIORITY );
+}
+
+// /////////////////////////////////////////////////
+//
+// Check audio
+//
+static int isAudioOK()
+{
+	
+    CHECK_MOVIE_MEM;
+	return (isWithAudio)? audioDecIsPreset(& MOVIE_MEM_PTR audioDec): 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Initialize all modules
+//
+static bool initAll( const char *bsfilename )
+{
+	
+    struct ThreadParam th_param;
+
+    *D_CTRL = (*D_CTRL | 0x003);
+    *D_STAT = 0x4; // clear D_STAT.CIS2
+
+    // /////////////////////////////
+    // 
+    //  Create read buffer
+    // 
+    CHECK_MOVIE_MEM;
+	
+	readBufCreate(& MOVIE_MEM_PTR readBuf);
+
+    // /////////////////////////////
+    // 
+    //  Initialize video decoder
+    // 
+    sceMpegInit();
+    videoDecCreate(& MOVIE_MEM_PTR videoDec,
+    	 MOVIE_MEM_PTR mpegWork, MPEG_WORK_SIZE,
+    	 MOVIE_MEM_PTR viBufData,  MOVIE_MEM_PTR viBufTag, VIBUF_SIZE,  MOVIE_MEM_PTR timeStamp, VIBUF_TS_SIZE);
+
+    // /////////////////////////////
+    // 
+    //  Initialize audio decoder
+    // 
+#define NEED_TO_INIT_SD_AND_SHIT 0
+#if NEED_TO_INIT_SD_AND_SHIT    
+	sceSdRemoteInit();
+    sceSdRemote(1, rSdInit, SD_INIT_COLD);  // i think having this in would fuck up soundfx module...
+#endif
+    audioDecCreate(& MOVIE_MEM_PTR audioDec,  MOVIE_MEM_PTR audioBuff, AUDIO_BUFF_SIZE,
+			IOP_BUFF_SIZE, Pcm::PCMAudio_GetIopMemory( ) );
+
+    ///////////////////////////////
+    // 
+    //  Choose stream to be played
+    // 
+    videoDecSetStream(& MOVIE_MEM_PTR videoDec,
+	    sceMpegStrM2V, 0, (sceMpegCallback)videoCallback, & MOVIE_MEM_PTR readBuf);
+    if (isWithAudio) {
+	videoDecSetStream(& MOVIE_MEM_PTR videoDec,
+	    sceMpegStrPCM, 0, (sceMpegCallback)pcmCallback, & MOVIE_MEM_PTR readBuf);
+    }
+
+    // /////////////////////////////
+    // 
+    //  Initialize video output buffer
+    // 
+    voBufCreate(& MOVIE_MEM_PTR voBuf, ( VoData * )UncAddr( MOVIE_MEM_PTR voBufData),  MOVIE_MEM_PTR voBufTag, N_VOBUF);
+
+    // /////////////////////////////
+    // 
+    //  Create 'default' thread
+    // 
+	Dbg_Message( "Starting default thread" );
+    th_param.entry = defMain;
+    th_param.stack =  MOVIE_MEM_PTR defStack;
+    th_param.stackSize = MOVIE_DEF_STACK_SIZE;
+    th_param.initPriority = MOVIE_THREAD_PRIORITY;
+    th_param.gpReg = &_gp;
+    th_param.option = 0;
+    defaultTh = CreateThread(&th_param);
+    StartThread(defaultTh, NULL);
+    
+	// /////////////////////////////
+    // 
+    //  Create docode thread
+    // 
+	Dbg_Message( "Starting video decoder thread" );
+    th_param.entry = videoDecMain;
+    th_param.stack =  MOVIE_MEM_PTR videoDecStack;
+    th_param.stackSize = MOVIE_STACK_SIZE;
+    th_param.initPriority = MOVIE_THREAD_PRIORITY;
+    th_param.gpReg = &_gp;
+    th_param.option = 0;
+    videoDecTh = CreateThread(&th_param);
+    StartThread(videoDecTh, NULL );
+
+    // /////////////////////////////
+    // 
+    //  Initialize controller
+    // 
+
+/*	static int isFirst = 1;
+	if (isFirst)
+	{
+	    scePadInit(0);
+	    scePadPortOpen(0, 0,  MOVIE_MEM_PTR controller_dma_buf);
+	    isFirst = 0;
+	}*/
+
+	char filename[ 256 ];
+	if (Config::CD() || FORCE_MOVIES_FROM_CD_PLEASE)
+	{
+		sprintf( filename, "cdrom0:\\%s%s.PSS;1",Config::GetDirectory(), bsfilename );
+		unsigned int schraw;
+		for ( schraw = 7; schraw < strlen( filename ); schraw++ ) // start after "cdrom0:\"
+		{
+			if ( filename[ schraw ] >= 'a' && filename[ schraw ] <= 'z' )
+			{
+				filename[ schraw ] += 'A' - 'a';
+			}
+			else if ( filename[ schraw ] == '/' )
+			{
+				filename[ schraw ] = '\\';
+			}
+		}
+	}	
+	else
+	{
+		sprintf( filename, "host0:\\%s.pss", bsfilename );
+	}	
+	
+    // /////////////////////////////
+    // 
+    //  Open bitstream file
+    // 
+	File::StopStreaming( );
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "Can't load IRX modules when CD is busy." ));
+		return false;
+	}
+    
+	bool	loaded = false;
+	for (int i = 0;i <10; i++)
+	{
+		printf ("Attempt %d\n",i);
+		if (!strFileOpen(& MOVIE_MEM_PTR infile, filename,
+			Pcm::PCMAudio_GetIopMemory( ) + IOP_BUFF_SIZE + ( 2048 * STRFILE_NUM_STREAMING_SECTORS + 64 ) ))			 
+		{
+			printf ("Can't Open file %s\n", filename);
+		}
+		else
+		{
+			loaded = true;
+			break;
+		}
+	}
+	
+    // /////////////////////////////
+    // 
+    //  Set Interrupt handlers
+    // 
+	MOVIE_MEM_PTR videoDec.hid_vblank = AddIntcHandler(INTC_VBLANK_S, vblankHandler, 0);
+	EnableIntc(INTC_VBLANK_S);
+
+	MOVIE_MEM_PTR videoDec.hid_endimage = AddDmacHandler(DMAC_GIF, handler_endimage, 0);
+	EnableDmac(DMAC_GIF);
+	
+	return loaded;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Terminate all modules
+//
+static void termAll()
+{
+	
+	
+	CHECK_MOVIE_MEM;
+
+    TerminateThread(videoDecTh);
+    DeleteThread(videoDecTh);
+
+    TerminateThread(defaultTh);
+    DeleteThread(defaultTh);
+
+    DisableDmac(DMAC_GIF);
+    RemoveDmacHandler(DMAC_GIF,  MOVIE_MEM_PTR videoDec.hid_endimage);
+	//EnableDmac(DMAC_GIF);
+
+    DisableIntc(INTC_VBLANK_S);
+    RemoveIntcHandler(INTC_VBLANK_S,  MOVIE_MEM_PTR videoDec.hid_vblank);
+	EnableIntc(INTC_VBLANK_S);
+    
+	readBufDelete(& MOVIE_MEM_PTR readBuf);
+    voBufDelete(& MOVIE_MEM_PTR voBuf);
+
+    videoDecDelete(& MOVIE_MEM_PTR videoDec);
+    audioDecDelete(& MOVIE_MEM_PTR audioDec);
+
+    strFileClose(& MOVIE_MEM_PTR infile);
+
+	// Re-init nx engine
+	NxPs2::ResetEngine();
+
+	// Re-init quick filesystem (since the CD stream buffer has changed)
+	if (Config::CD())
+	{
+		File::ResetQuickFileSystem();
+	}
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Main function of default thread
+//
+static void defMain(void * dummy)
+{
+    while (1) {
+	switchThread();
+    }
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Print error message
+//
+void ErrMessage(char *message)
+{
+    printf("[ Error ] %s\n", message);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Send audio data to IOP
+//
+void proceedAudio()
+{
+	
+    
+	CHECK_MOVIE_MEM;
+	
+	audioDecSendToIOP(& MOVIE_MEM_PTR audioDec);
+}
+
+
+
+} // namespace Flx
+
diff --git a/Code/Gel/Movies/Ngps/p_movies.h b/Code/Gel/Movies/Ngps/p_movies.h
new file mode 100644
index 0000000..917e96f
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/p_movies.h
@@ -0,0 +1,129 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			ps2 movies												**
+**																			**
+**	File name:		gel/movies/ngps/p_movies.h 								**
+**																			**
+**	Created: 		5/14/01	-	mjd											**
+**																			**
+*****************************************************************************/
+
+#ifndef __P_MOVIES_H
+#define __P_MOVIES_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/vobuf.h"
+#include "gel/movies/ngps/vibuf.h"
+#include "gel/movies/ngps/strfile.h"
+#include "gel/movies/ngps/readbuf.h"
+#include "gel/movies/ngps/videodec.h"
+#include "gel/movies/ngps/audiodec.h"
+
+namespace Flx
+{
+
+
+
+#define MOVIE_STACK_SIZE    (16*1024)
+#define MOVIE_DEF_STACK_SIZE    2048
+#define MAX_MBX		(MAX_WIDTH/16)
+#define MAX_MBY		(MAX_HEIGHT/16)
+#define IOP_BUFF_SIZE (12288*2) // 512 * 48
+#define MOVIE_ABORTED SCE_PADRdown
+//#define DEF_PRIORITY       32
+#define ERR_STOP while(1)
+
+#define MPEG_WORK_SIZE ( SCE_MPEG_BUFFER_SIZE(MAX_WIDTH, MAX_HEIGHT) )
+#define AUDIO_BUFF_SIZE ( IOP_BUFF_SIZE * 2 )
+
+#define ZERO_BUFF_SIZE	0x800
+	  
+// send in size, returns the size needed to create a 64-byte boundary:
+#define PAD_FOR_64( x ) ( 64 - ( ( x ) & 63 ) )
+// send in size, returns the size to the next 64 byte boundary:
+#define PADDED_64( x ) ( ( x ) + PAD_FOR_64( x ) )
+
+// This structure contains everything that was previously just
+// global (and taking up loads of memory)...
+struct SMovieMem{
+
+	u_long128 packetBase[ 6 ];
+	char pad665[ PAD_FOR_64( sizeof( u_long128 ) * 6 ) ];
+		 
+	char _0_buf [ ZERO_BUFF_SIZE ];
+	char pad666[ PAD_FOR_64( ZERO_BUFF_SIZE ) ];
+	
+	// ******* NOTE ::: KEEP ALL THESE ON 64 - BYTE BOUNDARIES AND SHIT!! ********
+
+	// These variables could be accessed from Uncached Area
+	VoData voBufData[ N_VOBUF ];
+	char pad0[ PAD_FOR_64( ( sizeof( VoData ) * N_VOBUF ) ) ];
+
+	VoTag voBufTag[ N_VOBUF ];
+	char pad1[ PAD_FOR_64( ( sizeof( VoTag ) * N_VOBUF ) ) ];
+
+	u_long128 viBufTag[ VIBUF_SIZE + 1 ];
+	char pad2[ PAD_FOR_64( ( sizeof( u_long128 ) * ( VIBUF_SIZE + 1 ) ) ) ];
+
+	// -------------- this needs to be 64 byte boudary -------------------
+	
+	// These variables are NOT accessed from Uncached Area
+	u_char mpegWork[ PADDED_64( MPEG_WORK_SIZE ) ];
+	char defStack[ PADDED_64( MOVIE_DEF_STACK_SIZE )];
+	u_char audioBuff[PADDED_64( IOP_BUFF_SIZE*2 )];
+	u_long128 viBufData[ PADDED_64( VIBUF_SIZE * VIBUF_ELM_SIZE/16 ) ];
+	char videoDecStack[ PADDED_64 ( MOVIE_STACK_SIZE )];
+	TimeStamp timeStamp[ VIBUF_TS_SIZE ];
+	char pad3[ PAD_FOR_64( sizeof( TimeStamp ) * VIBUF_TS_SIZE ) ];
+
+//	u_long128 controller_dma_buf[ scePadDmaBufferMax ];
+//	char pad4[ PAD_FOR_64( ( sizeof( u_long128 ) * ( scePadDmaBufferMax ) ) ) ];
+
+	struct ReadBuf readBuf;
+	char pad5[ PAD_FOR_64( sizeof( ReadBuf ) ) ];
+	
+	struct StrFile infile;
+	char pad6[ PAD_FOR_64( sizeof( StrFile ) ) ];
+	struct VideoDec videoDec;
+	char pad7[ PAD_FOR_64( sizeof( VideoDec ) ) ];
+	struct AudioDec audioDec;
+	char pad8[ PAD_FOR_64( sizeof( AudioDec ) ) ];
+	struct VoBuf voBuf;
+};
+
+extern SMovieMem *gpMovieMem;
+
+#define MOVIE_MEM_PTR	gpMovieMem->
+#define CHECK_MOVIE_MEM	Dbg_MsgAssert( gpMovieMem, ( "Movie Memory not initialized." ) )
+
+void PMovies_PlayMovie( const char *pName );
+
+void FuckUpVram( char color ); // testing what the fuck vram funcs are doing...
+
+} // namespace Flx
+
+#endif	// __P_MOVIES_H
+
+
+
diff --git a/Code/Gel/Movies/Ngps/read.cpp b/Code/Gel/Movies/Ngps/read.cpp
new file mode 100644
index 0000000..eef0429
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/read.cpp
@@ -0,0 +1,132 @@
+#include 
+#include 
+#include 
+#include 
+#include "gel/movies/ngps/p_movies.h"
+#include "gel/movies/ngps/readbuf.h"
+#include "gel/movies/ngps/videodec.h"
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/audiodec.h"
+
+namespace Flx
+{
+
+
+
+static int copy2area(u_char *pd0, int d0, u_char *pd1, int d1,
+    u_char *ps0, int s0, u_char *ps1, int s1);
+
+// ////////////////////////////////////////////////////////////////
+//
+// Stream callback function for MPEG2 video stream
+//
+int videoCallback(sceMpeg *mp, sceMpegCbDataStr *str, void *data)
+{
+	
+    
+	CHECK_MOVIE_MEM;
+	
+	ReadBuf *rb = (ReadBuf*)data;
+    u_char *ps0 = str->data;
+    u_char *ps1 = rb->data;
+    int s0 = min( ( int )rb->data + ( int )rb->size - ( int )str->data, ( int )str->len);
+    int s1 = str->len - s0;
+    u_char *pd0;
+    u_char *pd1;
+    u_char *pd0Unc;
+    u_char *pd1Unc;
+    int d0, d1;
+    int len;
+
+    videoDecBeginPut(& MOVIE_MEM_PTR videoDec, &pd0, &d0, &pd1, &d1);
+    pd0Unc = (u_char*)UncAddr(pd0);
+    pd1Unc = (u_char*)UncAddr(pd1);
+
+    len = copy2area(pd0Unc, d0, pd1Unc, d1, ps0, s0, ps1, s1);
+
+    // set PTS
+    if (len > 0) {
+	if (!videoDecPutTs(& MOVIE_MEM_PTR videoDec, str->pts, str->dts, pd0, len)) {
+	    ErrMessage("pts buffer overflow\n");
+	}
+    }
+
+    videoDecEndPut(& MOVIE_MEM_PTR videoDec, len);
+
+    // ////////////////////////////////////////////
+    //
+    // Return 0 if no data is put
+    //
+    return (len > 0)? 1: 0;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Stream callback function for PS2 PCM stream
+//
+int pcmCallback(sceMpeg *mp, sceMpegCbDataStr *str, void *data)
+{
+    ReadBuf *rb = (ReadBuf*)data;
+    u_char *ps0 = str->data;
+    u_char *ps1 = rb->data;
+    int s0;
+    int s1;
+    u_char *pd0;
+    u_char *pd1;
+    int d0, d1;
+    int len;
+    int ret;
+
+    // skip
+    // sub_stream_id
+    ps0 = str->data + 4;
+    if (ps0 >= rb->data + rb->size) {
+    	ps0 -= rb->size;
+    }
+    len = str->len - 4;
+
+    ps1 = rb->data;
+    s0 = min(rb->data + rb->size - ps0, len);
+    s1 = len - s0;
+
+    audioDecBeginPut(& MOVIE_MEM_PTR audioDec, &pd0, &d0, &pd1, &d1);
+    ret = copy2area(pd0, d0, pd1, d1, ps0, s0, ps1, s1);
+
+    audioDecEndPut(& MOVIE_MEM_PTR audioDec, ret);
+
+    // ////////////////////////////////////////////
+    //
+    // Return 0 if no data is put
+    //
+    return (ret > 0)? 1: 0;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Copy two areas
+//
+static int copy2area(u_char *pd0, int d0, u_char *pd1, int d1,
+    u_char *ps0, int s0, u_char *ps1, int s1)
+{
+    if (d0 + d1 < s0 + s1) {
+        return 0;
+    }
+
+    if (s0 >= d0) {
+    	memcpy(pd0,		ps0,		d0);
+    	memcpy(pd1,		ps0 + d0,	s0 - d0);
+    	memcpy(pd1 + s0 - d0,	ps1,		s1);
+    } else { // s0 < d0
+    	if (s1 >= d0 - s0) {
+	    memcpy(pd0,		ps0,		s0);
+	    memcpy(pd0 + s0,	ps1,		d0 - s0);
+	    memcpy(pd1,		ps1 + d0 - s0,	s1 - (d0 - s0));
+	} else { // s1 < d0 - s0
+	    memcpy(pd0,		ps0,		s0);
+	    memcpy(pd0 + s0,	ps1,		s1);
+	}
+    }
+    return s0 + s1;
+}
+
+} // namespace Flx
diff --git a/Code/Gel/Movies/Ngps/readbuf.cpp b/Code/Gel/Movies/Ngps/readbuf.cpp
new file mode 100644
index 0000000..add4207
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/readbuf.cpp
@@ -0,0 +1,81 @@
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/readbuf.h"
+
+#define BUFF_SIZE (N_READ_UNIT * READ_UNIT_SIZE)
+
+namespace Flx
+{
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+// Create read buffer
+//
+void readBufCreate(ReadBuf *b)
+{
+    b->put = b->count = 0;
+    b->size = BUFF_SIZE;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Delete read buffer
+//
+void readBufDelete(ReadBuf *b)
+{
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Get empty area
+//
+int readBufBeginPut(ReadBuf *b, u_char **ptr)
+{
+    int size = b->size - b->count;
+    if (size) {
+        *ptr = b->data + b->put;
+    }
+    return size;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Proceed 'write' pointer
+//
+int readBufEndPut(ReadBuf *b, int size)
+{
+    int size_ok = min(b->size - b->count, size);
+
+    b->put = (b->put + size_ok) % b->size;
+    b->count += size_ok;
+
+    return size_ok;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Get data area
+//
+int readBufBeginGet(ReadBuf *b, u_char **ptr)
+{
+    if (b->count) {
+        *ptr = b->data + (b->put - b->count + b->size) % b->size;
+    }
+    return b->count;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Proceed 'read' pointer
+//
+int readBufEndGet(ReadBuf *b, int size)
+{
+    int size_ok = min(b->count, size);
+
+    b->count -= size_ok;
+
+    return size_ok;
+}
+
+} // namespace Flx
diff --git a/Code/Gel/Movies/Ngps/readbuf.h b/Code/Gel/Movies/Ngps/readbuf.h
new file mode 100644
index 0000000..463144c
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/readbuf.h
@@ -0,0 +1,46 @@
+#ifndef _READBUF_H_
+#define _READBUF_H_
+
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+
+#define READ_UNIT_SIZE (64*1024)
+#define N_READ_UNIT     5
+
+// ////////////////////////////////////////////////////////////////
+//
+// Read buffer
+//
+struct ReadBuf{
+    u_char data[N_READ_UNIT * READ_UNIT_SIZE];
+    int put;
+    int count;
+    int size;
+};
+
+namespace Flx
+{
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+// Functions
+//
+void readBufCreate(ReadBuf *buff);
+void readBufDelete(ReadBuf *buff);
+int readBufBeginPut(ReadBuf *buff, u_char **ptr);
+int readBufEndPut(ReadBuf *buff, int size);
+int readBufBeginGet(ReadBuf *buff, u_char **ptr);
+int readBufEndGet(ReadBuf *buff, int size);
+
+} // namespace Flx
+
+#endif // _READBUF_H_
+
diff --git a/Code/Gel/Movies/Ngps/strfile.cpp b/Code/Gel/Movies/Ngps/strfile.cpp
new file mode 100644
index 0000000..438d6b9
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/strfile.cpp
@@ -0,0 +1,180 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/strfile.h"
+#include 
+
+namespace Flx
+{
+
+
+
+int isStrFileInit = 0;
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Open a file to read and check its size
+//
+//	< file name conversion >
+//	   = from HD =
+//	    dir/file.pss           -> host0:dir/file.pss
+//	    host0:dir/file.pss     -> host0:dir/file.pss
+//
+//	   = from CD/DVD =
+//	    cdrom0:\dir\file.pss;1 -> cdrom0:\DIR\FILE.PSS;1
+//	    cdrom0:/dir/file.pss;1 -> cdrom0:\DIR\FILE.PSS;1
+//	    cdrom0:/dir/file.pss   -> cdrom0:\DIR\FILE.PSS;1
+//
+int strFileOpen(StrFile *file, char *filename, int pIopBuff )
+{
+    int ret;
+    char *body = NULL;
+    char fn[256];
+    char devname[64];
+
+    body = index(filename, ':');
+
+	if (body)
+	{
+		int dlen;
+
+		// copy device name
+		dlen = body - filename;
+		strncpy(devname, filename, dlen);
+		devname[dlen] = 0;
+
+		body += 1;
+
+		if (!strcmp(devname, "cdrom0"))
+		{ // CD/DVD
+			int i;
+			int len = strlen(body);
+			const char *tail;
+
+			file->isOnCD = 1;
+
+			for (i = 0; i < len; i++)
+			{
+				if (body[i] == '/')
+				{
+					body[i] = '\\';
+				}
+				body[i] = toupper(body[i]);
+			}
+
+			tail = (index(filename, ';'))? "": ";1";
+			sprintf(fn, "%s%s", body, tail);
+
+		}
+		else
+		{			 // HD
+			file->isOnCD = 0;
+			sprintf(fn, "%s:%s", devname, body+1);  // Mick:  The +1 is to skip the initial \, so file names are the same off HD aas CD 
+		}
+	}
+	else
+	{				 // HD (default)
+		body = filename;            
+		strcpy(devname, "host0");
+		file->isOnCD = 0;
+		sprintf(fn, "%s:%s", devname, body);
+	}
+
+    printf("file: %s\n", fn);
+
+    if (file->isOnCD)
+	{
+		sceCdRMode mode;
+		if (!Config::CD())
+		{
+			if (!isStrFileInit)
+			{
+				sceCdInit(SCECdINIT);
+				sceCdMmode(SCECdDVD);
+				sceCdDiskReady(0);
+		
+				isStrFileInit = 1;
+			}
+		}	
+		file->iopBuf = ( u_char * )pIopBuff; //( u_char * )sceSifAllocIopHeap((2048 * 80) + 16);
+		sceCdStInit(80, 5, bound((u_int)file->iopBuf, 16));
+	
+		if(!sceCdSearchFile(&file->fp, fn)){
+	
+			printf("Cannot open '%s'(sceCdSearchFile)\n", fn);
+			return 0;
+		}
+	
+		file->size = file->fp.size;
+		mode.trycount = 0;
+		mode.spindlctrl = SCECdSpinStm;
+		mode.datapattern = SCECdSecS2048;
+		sceCdStStart(file->fp.lsn, &mode);
+    }
+	else
+	{
+
+		file->fd = sceOpen(fn, SCE_RDONLY);
+		if (file->fd < 0)
+		{
+			printf("Cannot open '%s'(sceOpen)\n", fn);
+			return 0;
+		}
+		file->size = sceLseek(file->fd, 0, SCE_SEEK_END);
+		if (file->size < 0)
+		{
+			printf("sceLseek() fails (%s): %d\n", fn, file->size);
+			sceClose(file->fd);
+			return 0;
+		}
+	
+		ret = sceLseek(file->fd, 0, SCE_SEEK_SET);
+		if (ret < 0)
+		{
+			printf("sceLseek() fails (%s)\n", fn);
+			sceClose(file->fd);
+			return 0;
+		}
+    }
+
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Close a file
+//
+int strFileClose(StrFile *file)
+{
+    if (file->isOnCD) {
+    	sceCdStStop();
+//        sceSifFreeIopHeap((void *)file->iopBuf);
+    } else {
+	sceClose(file->fd);
+    }
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Read data
+//
+int strFileRead(StrFile *file, void *buff, int size)
+{
+    int count;
+    if (file->isOnCD) {
+	u_int err;
+
+        count= sceCdStRead(size >> 11, (u_int *)buff, STMBLK, &err);
+	count <<= 11;
+
+    } else {
+	count = sceRead(file->fd, buff, size);
+    }
+    return count;
+}
+
+}
diff --git a/Code/Gel/Movies/Ngps/strfile.h b/Code/Gel/Movies/Ngps/strfile.h
new file mode 100644
index 0000000..300af49
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/strfile.h
@@ -0,0 +1,38 @@
+#ifndef _STRFILE_H_
+#define _STRFILE_H_
+
+#include 
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Structure to read data from CD/DVD or HD
+//
+struct StrFile{
+    int isOnCD;		// CD/DVD or HD
+    int size;
+
+    sceCdlFILE fp;	// for CD/DVD stream
+    u_char *iopBuf;
+
+    int fd;		// for HD stream
+};
+
+namespace Flx
+{
+
+
+
+int strFileOpen(StrFile *file, char *filename, int pIopBuff);
+int strFileClose(StrFile *file);
+int strFileRead(StrFile *file, void *buff, int size);
+
+} // namespace Flx
+
+#endif // _STRFILE_H_
diff --git a/Code/Gel/Movies/Ngps/vibuf.cpp b/Code/Gel/Movies/Ngps/vibuf.cpp
new file mode 100644
index 0000000..5ac7646
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/vibuf.cpp
@@ -0,0 +1,639 @@
+#include 
+#include 
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/vibuf.h"
+#include "gel/movies/ngps/p_movies.h"
+
+// ////////////////////////////////////////////////////////////////
+//
+// Definitions 
+//
+#define DMA_ID_REFE	0
+#define DMA_ID_NEXT	2
+#define DMA_ID_REF	3
+
+#define WAITSEMA(v) WaitSema(v)
+#define SIGNALSEMA(v) SignalSema(v)
+
+#define REST	2
+
+#define TAG_ADDR(i)	((u_int)DmaAddr(f->tag + i))
+#define DATA_ADDR(i)	((u_int)f->data + VIBUF_ELM_SIZE * (i))
+#define WRAP_ADDR(addr) ((u_int)(f->data)     + (((u_int)(addr) - (u_int)(f->data)) % (VIBUF_ELM_SIZE * f->n)))
+
+#define IsInRegion(i,start,len,n)  (     (0 <= (((i) + (n) - (start)) % (n))) &&     ((((i) + (n) - (start)) % (n)) < (len)))
+
+#define FS(f)	((f->dmaStart + f->dmaN) * VIBUF_ELM_SIZE)
+#define FN(f)	((f->n - REST -  f->dmaN) * VIBUF_ELM_SIZE)
+
+namespace Flx
+{
+
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+// definition of QWORD
+//
+typedef union {
+    u_long128	q;
+    u_long 	l[2];
+    u_int  	i[4];
+    u_short	s[8];
+    u_char	c[16];
+} QWORD;
+
+extern inline int IsPtsInRegion(int tgt, int pos, int len, int size)
+{
+    int tgt1 = (tgt + size - pos) % size;
+    return tgt1 < len;
+}
+
+int getFIFOindex(ViBuf *f, void *addr)
+{
+    if (addr == DmaAddr(f->tag + (f->n + 1))) {
+	return 0;
+    } else {
+	return ((u_int)addr - (u_int)f->data) / VIBUF_ELM_SIZE;
+    }
+}
+
+void setD3_CHCR(u_int val)
+{
+    DI();
+    *D_ENABLEW = ((*D_ENABLER)|0x00010000);	// pause all channels
+    *D3_CHCR = val;
+    *D_ENABLEW = ((*D_ENABLER)&~0x00010000);	// restart all channels
+    EI();
+}
+
+void setD4_CHCR(u_int val)
+{
+    DI();
+    *D_ENABLEW = ((*D_ENABLER)|0x00010000);	// pause all channels
+    *D4_CHCR = val;
+    *D_ENABLEW = ((*D_ENABLER)&~0x00010000);	// restart all channels
+    EI();
+}
+
+void scTag2(
+    QWORD *q,
+    void *addr,
+    u_int id,
+    u_int qwc
+)
+{
+    q->l[0] =
+    	  (u_long)(u_int)addr << 32
+    	| (u_long)id << 28
+    	| (u_long)qwc << 0;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Create video input buffer
+//
+int viBufCreate(ViBuf *f,
+    u_long128 *data, u_long128 *tag, int size,
+    TimeStamp *ts, int n_ts)
+{
+    struct SemaParam param;
+
+    f->data = data;
+    f->tag = ( u_long128 * )UncAddr(tag);
+    f->n = size;
+    f->buffSize = size * VIBUF_ELM_SIZE;
+
+    f->ts = ts;
+    f->n_ts = n_ts;
+
+    // ////////////////////////////////
+    //
+    // Create Semaphore
+    //
+    param.initCount = 1;
+    param.maxCount = 1;
+    f->sema = CreateSema(¶m);
+
+    // ////////////////////////////////
+    //
+    // Reset
+    //
+    viBufReset(f);
+
+    f->totalBytes = 0;
+
+    return TRUE;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Reset video input buffer
+//
+int viBufReset(ViBuf *f)
+{
+    int i;
+
+    f->dmaStart = 0;
+    f->dmaN = 0;
+    f->readBytes = 0;
+    f->isActive = TRUE;
+
+    f->count_ts = 0;
+    f->wt_ts = 0;
+    for (i = 0; i < f->n_ts; i++) {
+	f->ts[i].pts = TS_NONE;
+	f->ts[i].dts = TS_NONE;
+	f->ts[i].pos = 0;
+	f->ts[i].len = 0;
+    }
+
+    // ////////////////////////////////
+    //
+    // Init DMA tags
+    //
+    for (i = 0; i < f->n; i++) {
+    	scTag2(
+	    (QWORD*)(f->tag + i),
+	    DmaAddr((char*)f->data + VIBUF_ELM_SIZE * i),
+	    DMA_ID_REF,
+	    VIBUF_ELM_SIZE/16
+	);
+    }
+    scTag2(
+	(QWORD*)(f->tag + i),
+	DmaAddr(f->tag),
+	DMA_ID_NEXT,
+	0
+    );
+
+    *D4_QWC = 0;
+    *D4_MADR = (u_int)DmaAddr(f->data);
+    *D4_TADR = (u_int)DmaAddr(f->tag);
+    setD4_CHCR((0<<8) | (1<<2) | 1);
+
+    return TRUE;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Get areas to put data
+//
+void viBufBeginPut(ViBuf *f,
+	u_char **ptr0, int *len0, u_char **ptr1, int *len1)
+{
+	
+    int es;
+    int en;
+    int fs;
+    int fn;
+
+    WAITSEMA(f->sema);
+
+    fs = FS(f);
+    fn = FN(f);
+
+    es = (fs + f->readBytes) % f->buffSize;
+    en = fn - f->readBytes;
+
+    if (f->buffSize - es >= en) {	// area0
+	*ptr0 = (u_char*)f->data + es;
+    	*len0 = en;
+	*ptr1 = NULL;
+	*len1 = 0;
+    } else {				// area0 + area1
+	*ptr0 = (u_char*)f->data + es;
+    	*len0 = f->buffSize - es;
+	*ptr1 = (u_char*)f->data;
+	*len1 = en - (f->buffSize - es);
+    }
+
+    SIGNALSEMA(f->sema);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Proceed inner pointer
+//
+void viBufEndPut(ViBuf *f, int size)
+{
+    WAITSEMA(f->sema);
+
+    f->readBytes += size;
+    f->totalBytes += size;
+
+    SIGNALSEMA(f->sema);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Add bitstream data to DMA tag list
+//
+int viBufAddDMA(ViBuf *f)
+{
+    int i;
+    int index;
+    int id;
+    int last;
+    u_int d4chcr;
+    int isNewData = 0;
+    int consume;
+    int read_start, read_n;
+
+    WAITSEMA(f->sema);
+
+    if (!f->isActive) {
+	ErrMessage("DMA ADD not active\n");
+        return FALSE;
+    }
+
+    // ////////////////////////////////////////
+    //
+    // STOP DMA ch4
+    //
+    // d4chcr:
+    //	(1) DMA was running
+    //	    ORIGINAL DMA tag
+    //	(2) DMA was not running
+    //	    REFE tag
+    setD4_CHCR((DMA_ID_REFE<<28) | (0<<8) | (1<<2) | 1);
+    d4chcr = *D4_CHCR;
+
+    // ////////////////////////////////////////
+    //
+    // update dma pointer using D4_MADR
+    //
+    index = getFIFOindex(f, (void*)*D4_MADR);
+    consume = (index + f->n - f->dmaStart) % f->n;
+    f->dmaStart = (f->dmaStart + consume) % f->n;
+    f->dmaN -= consume;
+
+    // ////////////////////////////////////////
+    //
+    // update read pointer
+    //
+    read_start = (f->dmaStart + f->dmaN) % f->n;
+    read_n = f->readBytes/VIBUF_ELM_SIZE;
+    f->readBytes %= VIBUF_ELM_SIZE;
+
+    // ////////////////////////////////////////
+    //
+    // the last REFE -> REF
+    //
+    if (read_n > 0) {
+	last = (f->dmaStart + f->dmaN - 1 + f->n) % f->n;
+	scTag2(
+	    (QWORD*)(f->tag + last),
+	    (char*)f->data + VIBUF_ELM_SIZE * last, 
+	    DMA_ID_REF,
+	    VIBUF_ELM_SIZE/16
+	);
+	isNewData = 1;
+    }
+
+    index = read_start;
+    for (i = 0; i < read_n; i++) {
+    	id = (i == read_n - 1)? DMA_ID_REFE: DMA_ID_REF;
+	scTag2(
+	    (QWORD*)(f->tag + index),
+	    (char*)f->data + VIBUF_ELM_SIZE * index, 
+	    id,
+	    VIBUF_ELM_SIZE/16
+	);
+	index = (index + 1) % f->n;
+    }
+
+    f->dmaN += read_n;
+
+    // ////////////////////////////////////////
+    //
+    // RESTART DMA ch4
+    //
+    if (f->dmaN) {
+	if (isNewData) {
+	    // change ref/refe ----> ref
+	    d4chcr = (d4chcr & 0x0fffffff) | (DMA_ID_REF << 28);
+	}
+	setD4_CHCR(d4chcr | 0x100);
+    }
+
+    SIGNALSEMA(f->sema);
+
+    return TRUE;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Stop DMA and save DMA environment
+//
+int viBufStopDMA(ViBuf *f)
+{
+    WAITSEMA(f->sema);
+
+    f->isActive = FALSE;
+
+    setD4_CHCR((0<<8) | (1<<2) | 1);		// STR: 0, DIR: 1
+
+    f->env.d4madr = *D4_MADR;
+    f->env.d4tadr = *D4_TADR;
+    f->env.d4qwc =  *D4_QWC;
+    f->env.d4chcr = *D4_CHCR;
+
+    // wait till ofc becomes 0
+    while (DGET_IPU_CTRL() & 0xf0)
+	;
+
+    // DMA ch3
+    setD3_CHCR((0<<8) | 0);		// STR: 0, DIR: 0
+    f->env.d3madr = *D3_MADR;
+    f->env.d3qwc =  *D3_QWC;
+    f->env.d3chcr = *D3_CHCR;
+
+    f->env.ipubp = DGET_IPU_BP();
+    f->env.ipuctrl = DGET_IPU_CTRL();
+
+    SIGNALSEMA(f->sema);
+
+    return TRUE;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Restore DMA environment and restart DMA
+//
+int viBufRestartDMA(ViBuf *f)
+{
+    int bp = f->env.ipubp & 0x7f;
+    int fp = (f->env.ipubp >> 16) & 0x3;
+    int ifc = (f->env.ipubp >> 8) & 0xf;
+    u_int d4madr_next = f->env.d4madr - ((fp + ifc) << 4);
+    u_int d4qwc_next = f->env.d4qwc + (fp + ifc);
+    u_int d4tadr_next = f->env.d4tadr;
+    u_int d4chcr_next = f->env.d4chcr | 0x100;
+    int index;
+    int index_next;
+    int id;
+
+    WAITSEMA(f->sema);
+
+    //
+    // check wrap around
+    //
+    if (d4madr_next < (u_int)f->data) {
+	d4qwc_next = (DATA_ADDR(0) - d4madr_next) >> 4;
+    	d4madr_next += (u_int)(f->n * VIBUF_ELM_SIZE);
+	d4tadr_next = TAG_ADDR(0);
+	id = (f->env.d4madr == DATA_ADDR(0)
+		|| f->env.d4madr == DATA_ADDR(f->n))?
+	    DMA_ID_REFE:
+	    DMA_ID_REF;
+	d4chcr_next = (f->env.d4chcr & 0x0fffffff)
+				| (id << 28) | 0x100;
+
+	if (!IsInRegion(0, f->dmaStart, f->dmaN, f->n)) {
+	    f->dmaStart = f->n - 1;
+	    f->dmaN++;
+	}
+    } else if ((index = getFIFOindex(f, (void*)f->env.d4madr))
+    		!= (index_next = getFIFOindex(f, (void*)d4madr_next))) {
+	d4tadr_next = TAG_ADDR(index);
+	d4qwc_next = (DATA_ADDR(index) - d4madr_next) >> 4;
+	id = (WRAP_ADDR(f->env.d4madr)
+		== DATA_ADDR((f->dmaStart + f->dmaN) % f->n))?
+	    DMA_ID_REFE:
+	    DMA_ID_REF;
+	d4chcr_next = (f->env.d4chcr & 0x0fffffff)
+				| (id << 28) | 0x100;
+
+	if (!IsInRegion(index_next, f->dmaStart, f->dmaN, f->n)) {
+	    f->dmaStart = index_next;
+	    f->dmaN++;
+	}
+    }
+
+    // Restart DMA ch3
+    if (f->env.d3madr && f->env.d3qwc) {
+	*D3_MADR = f->env.d3madr;
+	*D3_QWC  = f->env.d3qwc;
+	setD3_CHCR(f->env.d3chcr | 0x100);
+    }
+
+    if (f->dmaN) {
+	while (sceIpuIsBusy())
+	    ;
+	// BCLR
+	sceIpuBCLR(bp);
+	while (sceIpuIsBusy())
+	    ;
+    }
+
+    // Restart DMA ch4
+    *D4_MADR = d4madr_next;
+    *D4_TADR = d4tadr_next;
+    *D4_QWC  = d4qwc_next;
+    if (f->dmaN) {
+	setD4_CHCR(d4chcr_next);
+    }
+
+    *IPU_CTRL = f->env.ipuctrl;
+
+    f->isActive = TRUE;
+
+    SIGNALSEMA(f->sema);
+
+    return TRUE;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Delete video input buffer
+//
+int viBufDelete(ViBuf *f)
+{
+    setD4_CHCR((0<<8) | (1<<2) | 1);	// STR: 0, chain, DIR: 1
+    *D4_QWC = 0;
+    *D4_MADR = 0;
+    *D4_TADR = 0;
+
+    DeleteSema(f->sema);
+    return TRUE;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Check to see if decoder is in CSC period or not (0: CSC period)
+//
+int viBufIsActive(ViBuf *f)
+{
+    int ret;
+
+    WAITSEMA(f->sema);
+
+    ret = f->isActive;
+
+    SIGNALSEMA(f->sema);
+
+    return ret;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Data size in video input buffer
+//
+int viBufCount(ViBuf *f)
+{
+    int ret;
+
+    WAITSEMA(f->sema);
+
+    ret = f->dmaN * VIBUF_ELM_SIZE + f->readBytes;
+
+    SIGNALSEMA(f->sema);
+
+    return ret;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Flush video input buffer
+//
+void viBufFlush(ViBuf *f)
+{
+    WAITSEMA(f->sema);
+
+    f->readBytes = bound(f->readBytes, VIBUF_ELM_SIZE);
+
+    SIGNALSEMA(f->sema);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Add new time stamp and remove old one from the buffer
+//
+int viBufModifyPts(ViBuf *f, TimeStamp *new_ts)
+{
+    TimeStamp *ts;
+    int rd = (f->wt_ts - f->count_ts + f->n_ts) % f->n_ts;
+    int datasize =  VIBUF_ELM_SIZE * f->n;
+    int loop = 1;
+
+    if (f->count_ts > 0) {
+	while (loop) {
+	    ts = f->ts + rd;
+
+	    if (ts->len == 0 || new_ts->len == 0) {
+		break;
+	    }
+
+	    if (IsPtsInRegion(ts->pos, new_ts->pos, new_ts->len, datasize)) {
+		int len = min(new_ts->pos + new_ts->len - ts->pos, ts->len);
+
+		ts->pos = (ts->pos + len) % datasize;
+		ts->len -= len;
+
+		if (ts->len == 0) {
+		    if (ts->pts >= 0) {
+/*			ErrMessage("pts is not used");*/
+			ts->pts = TS_NONE;
+			ts->dts = TS_NONE;
+			ts->pos = 0;
+			ts->len = 0;
+		    }
+		    f->count_ts = max(f->count_ts - 1, 0);
+		}
+	    } else {
+		loop = 0;
+	    }
+
+	    rd = (rd + 1) % f->n_ts;
+	}
+    }
+
+    return 0;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Add new time stamp
+//
+int viBufPutTs(ViBuf *f, TimeStamp *ts)
+{
+    int ret = 0;
+
+    WAITSEMA(f->sema);
+
+    if (f->count_ts < f->n_ts) {
+
+	viBufModifyPts(f, ts);
+
+	if (ts->pts >= 0 || ts->dts >= 0) {
+
+	    f->ts[f->wt_ts].pts = ts->pts;
+	    f->ts[f->wt_ts].dts = ts->dts;
+	    f->ts[f->wt_ts].pos = ts->pos;
+	    f->ts[f->wt_ts].len = ts->len;
+
+	    f->count_ts++;
+	    f->wt_ts = (f->wt_ts + 1) % f->n_ts;
+	}
+	ret = 1;
+    } 
+
+    SIGNALSEMA(f->sema);
+
+    return ret;
+}
+// ////////////////////////////////////////////////////////////////
+//
+// Get time stamp
+//
+int viBufGetTs(ViBuf *f, TimeStamp *ts)
+{
+    u_int d4madr = *D4_MADR;
+    u_int ipubp = DGET_IPU_BP();
+    int bp = f->env.ipubp & 0x7f;
+    int fp = (ipubp >> 16) & 0x3;
+    int ifc = (ipubp >> 8) & 0xf;
+    u_int d4madr_next = d4madr - ((fp + ifc) << 4);
+    u_int stop;
+    int datasize =  VIBUF_ELM_SIZE * f->n;
+    int isEnd = 0;
+    int tscount;
+    int wt;
+    int i;
+
+    WAITSEMA(f->sema);
+
+    ts->pts = TS_NONE;
+    ts->dts = TS_NONE;
+
+    stop = (d4madr_next + (bp >> 3) + datasize - (u_int)f->data)
+    							% datasize;
+
+    tscount = f->count_ts;
+    wt = f->wt_ts;
+
+    for (i = 0; i < tscount && !isEnd; i++) {
+
+	int rd = (wt - tscount + f->n_ts + i) % f->n_ts;
+
+	if (IsPtsInRegion(stop,
+		f->ts[rd].pos, f->ts[rd].len, datasize)) {
+
+	    ts->pts = f->ts[rd].pts;
+	    ts->dts = f->ts[rd].dts;
+	    f->ts[rd].pts = TS_NONE;
+	    f->ts[rd].dts = TS_NONE;
+
+	    isEnd = 1;
+	    f->count_ts -= min(1, f->count_ts);
+	}
+    }
+
+    SIGNALSEMA(f->sema);
+
+    return 1;
+}
+
+} // namespace Flx
diff --git a/Code/Gel/Movies/Ngps/vibuf.h b/Code/Gel/Movies/Ngps/vibuf.h
new file mode 100644
index 0000000..3294b4a
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/vibuf.h
@@ -0,0 +1,84 @@
+#ifndef _VIBUF_H_
+#define _VIBUF_H_
+
+#include 
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#define VIBUF_ELM_SIZE 2048
+#include "gel/movies/ngps/vibuf.h"
+#define VIBUF_SIZE 256
+#define VIBUF_TS_SIZE (VIBUF_SIZE*2)
+
+namespace Flx
+{
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Time stamp
+//
+struct TimeStamp{
+    long pts;
+    long dts;
+    int pos;
+    int len;
+};
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Video input buffer
+//
+struct ViBuf{
+    u_long128 *data;	// data array
+    u_long128 *tag;	// tag array
+    int n;		// the number of data/tag element in ViBuf
+    int dmaStart;	// DMA area start position
+    int dmaN;		// DMA area size
+    int readBytes;	// read area size
+    int buffSize;	// buffer size of ViBuf(bytes)
+    sceIpuDmaEnv env;	// DMA environment
+    int sema;		// semaphore
+    int isActive;	// flag to check CSC period
+    long totalBytes;	// total bytes of data which sent to ViBuf
+
+    // Time Stamp
+    TimeStamp *ts;	// time stamp array
+    int n_ts;		// time stamp array size
+    int count_ts;	// the number of time stamps in the array
+    int wt_ts;		// write position of time stamp array
+
+};
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Functions
+//
+int viBufCreate(ViBuf *f,
+    u_long128 *data, u_long128 *tag, int size,
+    TimeStamp *ts, int n_ts);
+int viBufReset(ViBuf *f);
+int viBufAddDMA(ViBuf *f);
+int viBufDelete(ViBuf *f);
+int viBufStopDMA(ViBuf *f);
+int viBufRestartDMA(ViBuf *f);
+void viBufPrint(ViBuf *f);
+int viBufIsActive(ViBuf *f);
+void viBufBeginPut(ViBuf *f,
+	u_char **ptr0, int *len0, u_char **ptr1, int *len1);
+void viBufEndPut(ViBuf *f, int size);
+int viBufPutTs(ViBuf *f, TimeStamp *ts);
+int viBufGetTs(ViBuf *f, TimeStamp *ts);
+int viBufCount(ViBuf *f);
+void viBufFlush(ViBuf *f);
+
+} // namespace Flx
+
+#endif // _VIBUF_H_
+
diff --git a/Code/Gel/Movies/Ngps/videodec.cpp b/Code/Gel/Movies/Ngps/videodec.cpp
new file mode 100644
index 0000000..7066d93
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/videodec.cpp
@@ -0,0 +1,432 @@
+#include 
+#include 
+#include "gel/movies/ngps/p_movies.h"
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/vobuf.h"
+#include "gel/movies/ngps/videodec.h"
+#include "gel/movies/ngps/disp.h"
+
+namespace Flx
+{
+
+
+
+void videoDecMain( void * dummy);
+int decBs0(VideoDec *vd);
+int mpegError(sceMpeg *mp, sceMpegCbDataError *cberror, void *anyData);
+int mpegNodata(sceMpeg *mp, sceMpegCbData *cbdata, void *anyData);
+int mpegStopDMA(sceMpeg *mp, sceMpegCbData *cbdata, void *anyData);
+int mpegRestartDMA(sceMpeg *mp, sceMpegCbData *cbdata, void *anyData);
+int mpegTS(sceMpeg *mp, sceMpegCbDataTimeStamp *cbts, void *anyData);
+static int cpy2area(u_char *pd0, int d0, u_char *pd1, int d1,
+    u_char *ps0, int s0, u_char *ps1, int s1);
+
+extern int vblankCount;
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Create video decoder
+//
+int videoDecCreate(VideoDec *vd,
+    u_char *mpegWork, int mpegWorkSize,
+    u_long128 *data, u_long128 *tag,
+    int tagSize, TimeStamp *pts, int n_pts)
+{
+    // Create sceMpeg
+    sceMpegCreate(&vd->mpeg, mpegWork, mpegWorkSize);
+
+    // Add Callbacks
+    sceMpegAddCallback(&vd->mpeg,
+    	sceMpegCbError, (sceMpegCallback)mpegError, NULL);
+    sceMpegAddCallback(&vd->mpeg,
+    	sceMpegCbNodata, mpegNodata, NULL);
+    sceMpegAddCallback(&vd->mpeg,
+    	sceMpegCbStopDMA, mpegStopDMA, NULL);
+    sceMpegAddCallback(&vd->mpeg,
+    	sceMpegCbRestartDMA, mpegRestartDMA, NULL);
+    sceMpegAddCallback(&vd->mpeg, sceMpegCbTimeStamp,
+    	(sceMpegCallback)mpegTS, NULL);
+
+    videoDecReset(vd);
+
+    // Create input video buffer
+    viBufCreate(&vd->vibuf, data, tag, tagSize, pts, n_pts);
+
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Set decode mode
+//
+void videoDecSetDecodeMode(VideoDec *vd, int ni, int np, int nb)
+{
+    sceMpegSetDecodeMode(&vd->mpeg, ni, np, nb);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Choose stream to be decoded
+//
+int videoDecSetStream(VideoDec *vd, int strType, int ch,
+	sceMpegCallback cb, void *data)
+{
+    sceMpegAddStrCallback(&vd->mpeg, ( sceMpegStrType )strType, ch, cb, data);
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Get areas to put data in video input buffer
+//
+void videoDecBeginPut(VideoDec *vd,
+	u_char **ptr0, int *len0, u_char **ptr1, int *len1)
+{
+    viBufBeginPut(&vd->vibuf, ptr0, len0, ptr1, len1);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Proceed pointers of video input buffer
+//
+void videoDecEndPut(VideoDec *vd, int size)
+{
+    viBufEndPut(&vd->vibuf, size);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Reset video decoder
+//
+void videoDecReset(VideoDec *vd)
+{
+    vd->state = VD_STATE_NORMAL;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Delete video decoder
+//
+int videoDecDelete(VideoDec *vd)
+{
+    viBufDelete(&vd->vibuf);
+    sceMpegDelete(&vd->mpeg);
+
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Abort decoding
+//
+void videoDecAbort(VideoDec *vd)
+{
+    vd->state = VD_STATE_ABORT;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Get state of video decoder
+//
+u_int videoDecGetState(VideoDec *vd)
+{
+    return vd->state;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Set state of video decoder
+//
+u_int videoDecSetState(VideoDec *vd, u_int state)
+{
+    u_int old = vd->state;
+
+    vd->state = state;
+
+    return old;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Put time stamp to the video decoder
+//
+int videoDecPutTs(VideoDec *vd, long pts_val,
+    long dts_val, u_char *start, int len)
+{
+    TimeStamp ts;
+
+    // Set PTS
+    ts.pts = pts_val;
+    ts.dts = dts_val;
+    ts.pos = start - (u_char*)vd->vibuf.data;
+    ts.len = len;
+    return viBufPutTs(& MOVIE_MEM_PTR videoDec.vibuf, &ts);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Data size in video input buffer
+//
+int videoDecInputCount(VideoDec *vd)
+{
+    return viBufCount(&vd->vibuf);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Empty space size in video input buffer
+//
+int videoDecInputSpaceCount(VideoDec *vd)
+{
+    u_char *ptr0;
+    u_char *ptr1;
+    int len0, len1;
+
+    videoDecBeginPut(vd, &ptr0, &len0, &ptr1, &len1);
+
+    return len0 + len1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Flush video decoder
+//
+int videoDecFlush(VideoDec *vd)
+{
+    u_char *pd0;
+    u_char *pd1;
+    u_char *pd0Unc;
+    u_char *pd1Unc;
+    u_char seq_end_code[4] = {0x00, 0x00, 0x01, 0xb7};
+    int d0, d1;
+    int len;
+
+    videoDecBeginPut(vd, &pd0, &d0, &pd1, &d1);
+
+    if (d0 + d1 < 4) {
+    	return 0;
+    }
+
+    pd0Unc = (u_char*)UncAddr(pd0);
+    pd1Unc = (u_char*)UncAddr(pd1);
+
+    len = cpy2area(pd0Unc, d0, pd1Unc, d1, seq_end_code, 4, NULL, 0);
+
+    videoDecEndPut(& MOVIE_MEM_PTR videoDec, len);
+
+    viBufFlush(&vd->vibuf);
+
+    if (vd->state == VD_STATE_NORMAL) {
+	vd->state = VD_STATE_FLUSH;
+    }
+
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Check to see if input buffer and reference image buffer are
+//  flushed
+//
+int videoDecIsFlushed(VideoDec *vd)
+{
+    return (videoDecInputCount(vd) == 0)
+    	&& sceMpegIsRefBuffEmpty(&vd->mpeg);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Main function of decode thread
+//
+void videoDecMain( void * dummy)
+{
+	
+	
+	CHECK_MOVIE_MEM;
+	
+//	Dbg_MsgAssert( ( & MOVIE_MEM_PTR videoDec ) == vd, ( "What the fuck assface?" ) );
+    viBufReset(& MOVIE_MEM_PTR videoDec.vibuf);
+//    viBufReset(&vd->vibuf);
+
+    voBufReset(& MOVIE_MEM_PTR voBuf);
+
+    decBs0(& MOVIE_MEM_PTR videoDec);
+
+    while ( MOVIE_MEM_PTR voBuf.count)
+    	;
+
+     videoDecSetState( & MOVIE_MEM_PTR videoDec, VD_STATE_END);
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Decode bitstream using MPEG decoder
+//
+// return value
+//     1: normal end
+//     -1: aborted
+int decBs0(VideoDec *vd)
+{
+	
+    
+	CHECK_MOVIE_MEM;
+	
+	VoData *voData;
+    sceIpuRGB32 *rgb32;
+    int ret;
+    int status = 1;
+    sceMpeg *mp = &vd->mpeg;
+
+    // ////////////////////////////
+    // 
+    //  Main loop to decode MPEG bitstream
+    // 
+    while (!sceMpegIsEnd(&vd->mpeg)) {
+
+	if (videoDecGetState(vd) == VD_STATE_ABORT) {
+	    status = -1;
+	    printf("decode thread: aborted\n");
+	    break;
+	}
+
+	// ////////////////////////////////////////////////
+	// 
+	//  Get next available ouput buffer from voBuf
+	//  switch to another thread if voBuf is full
+	//
+	while (!(voData = voBufGetData(& MOVIE_MEM_PTR voBuf))) {
+	    switchThread();
+	}
+	rgb32 = (sceIpuRGB32*)voData->v;
+
+	// ////////////////////////////////////////////////
+	// 
+	//  Get decoded picture in sceIpuRGB32 format
+	//
+        ret = sceMpegGetPicture(mp, rgb32, MAX_WIDTH/16 * MAX_HEIGHT/16);
+
+        if (ret < 0) {
+	    ErrMessage("sceMpegGetPicture() decode error");
+	}
+
+	if (mp->frameCount == 0) {
+	    int i;
+	    int image_w, image_h;
+
+	    image_w = mp->width;
+	    image_h = mp->height;
+
+	    for (i = 0; i <  MOVIE_MEM_PTR voBuf.size; i++) {
+
+	      // packet with texture data
+	      setImageTag( MOVIE_MEM_PTR voBuf.tag[i].v[0],  MOVIE_MEM_PTR voBuf.data[i].v,
+			  0, image_w, image_h);
+	      
+	      // packet without texture data
+	      setImageTag( MOVIE_MEM_PTR voBuf.tag[i].v[1],  MOVIE_MEM_PTR voBuf.data[i].v,
+			  1, image_w, image_h);
+	    }
+	}
+
+	// ////////////////////////////
+	// 
+	//  Increment video output buffer count
+	// 
+	voBufIncCount(& MOVIE_MEM_PTR voBuf);
+
+	switchThread();
+    }
+
+    sceMpegReset(mp);
+
+    return status;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Callback function for sceMpegCbError
+//
+int mpegError(sceMpeg *mp, sceMpegCbDataError *cberror, void *anyData)
+{
+    printf("%s\n", cberror->errMessage);
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Callback function for sceMpegCbNodata
+//
+int mpegNodata(sceMpeg *mp, sceMpegCbData *cbdata, void *anyData)
+{
+    switchThread();
+    viBufAddDMA(& MOVIE_MEM_PTR videoDec.vibuf);
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Callback function for sceMpegCbStopDMA
+//
+int mpegStopDMA(sceMpeg *mp, sceMpegCbData *cbdata, void *anyData)
+{
+    // Stop DMA
+    viBufStopDMA(& MOVIE_MEM_PTR videoDec.vibuf);
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Callback function for sceMpegCbRestartDMA
+//
+int mpegRestartDMA(sceMpeg *mp, sceMpegCbData *cbdata, void *anyData)
+{
+    // Restart DMA
+    viBufRestartDMA(& MOVIE_MEM_PTR videoDec.vibuf);
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Callback function for sceMpegCbTimeStamp
+//	retruns PTS and DTS
+//
+int mpegTS(sceMpeg *mp, sceMpegCbDataTimeStamp *cbts, void *anyData)
+{
+    TimeStamp ts;
+
+    viBufGetTs(& MOVIE_MEM_PTR videoDec.vibuf, &ts);
+    cbts->pts = ts.pts;
+    cbts->dts = ts.dts;
+    return 1;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+//  copy 2 areas
+//
+static int cpy2area(u_char *pd0, int d0, u_char *pd1, int d1,
+    u_char *ps0, int s0, u_char *ps1, int s1)
+{
+    if (d0 + d1 < s0 + s1) {
+        return 0;
+    }
+
+    if (s0 >= d0) {
+    	memcpy(pd0,		ps0,		d0);
+    	memcpy(pd1,		ps0 + d0,	s0 - d0);
+    	memcpy(pd1 + s0 - d0,	ps1,		s1);
+    } else { // s0 < d0
+    	if (s1 >= d0 - s0) {
+	    memcpy(pd0,		ps0,		s0);
+	    memcpy(pd0 + s0,	ps1,		d0 - s0);
+	    memcpy(pd1,		ps1 + d0 - s0,	s1 - (d0 - s0));
+	} else { // s1 < d0 - s0
+	    memcpy(pd0,		ps0,		s0);
+	    memcpy(pd0 + s0,	ps1,		s1);
+	}
+    }
+    return s0 + s1;
+}
+
+
+} // namespace Flx
diff --git a/Code/Gel/Movies/Ngps/videodec.h b/Code/Gel/Movies/Ngps/videodec.h
new file mode 100644
index 0000000..4d09025
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/videodec.h
@@ -0,0 +1,71 @@
+#ifndef _VIDEODEC_H_
+#define _VIDEODEC_H_
+
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#include "gel/movies/ngps/vibuf.h"
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Video decoder state
+//
+#define VD_STATE_NORMAL    0
+#define VD_STATE_ABORT     1
+#define VD_STATE_FLUSH     2
+#define VD_STATE_END       3
+
+namespace Flx
+{
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Video Decoder
+//
+struct VideoDec{
+    sceMpeg mpeg;	// MPEG decoder
+    ViBuf vibuf;	// video input buffer
+    u_int state;	// video decoder state
+    int sema;		// semaphore
+
+    int hid_endimage;	// handler to check the end of image transfer
+    int hid_vblank;	// vlbank handler
+
+};
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Functions
+//
+void videoDecReset(VideoDec *vd);
+int videoDecCreate(VideoDec *vd,
+    u_char *mpegWork, int mpegWorkSize,
+    u_long128 *data, u_long128 *tag,
+    int tagSize, TimeStamp *pts, int n_pts);
+int videoDecDelete(VideoDec *vd);
+void videoDecAbort(VideoDec *vd);
+u_int videoDecGetState(VideoDec *vd);
+u_int videoDecSetState(VideoDec *vd, u_int state);
+int videoDecInputCount(VideoDec *vd);
+int videoDecInputSpaceCount(VideoDec *vd);
+void videoDecSetDecodeMode(VideoDec *vd, int ni, int np, int nb);
+int videoDecFlush(VideoDec *vd);
+int videoDecIsFlushed(VideoDec *vd);
+int videoDecSetStream(VideoDec *vd, int strType, int ch,
+	sceMpegCallback h, void *data);
+void videoDecBeginPut(VideoDec *vd,
+	u_char **ptr0, int *len0, u_char **ptr1, int *len1);
+void videoDecEndPut(VideoDec *vd, int size);
+int videoDecPutTs(VideoDec *vd, long pts_val,
+    long dts_val, u_char *start, int len);
+
+} // namespace Flx
+
+#endif // _VIDEODEC_H_
diff --git a/Code/Gel/Movies/Ngps/vobuf.cpp b/Code/Gel/Movies/Ngps/vobuf.cpp
new file mode 100644
index 0000000..4534de7
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/vobuf.cpp
@@ -0,0 +1,90 @@
+#include 
+#include "gel/movies/ngps/defs.h"
+#include "gel/movies/ngps/vobuf.h"
+
+namespace Flx
+{
+
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+// Functions called from decoding thread
+//
+void voBufCreate(
+    VoBuf *f,
+    VoData *data,
+    VoTag *tag,
+    int size
+)
+{
+    int i;
+
+    f->data = data;
+    f->tag = tag;
+    f->size = size;
+    f->count = 0;
+    f->write = 0;
+
+    for (i = 0; i < size; i++) {
+        f->tag[i].status = VOBUF_STATUS_;
+    }
+}
+
+void voBufDelete(VoBuf *f)
+{
+}
+
+void voBufReset(VoBuf *f)
+{
+    f->count = 0;
+    f->write = 0;
+}
+
+int voBufIsFull(VoBuf *f)
+{
+    return f->count == f->size;
+}
+
+void voBufIncCount(VoBuf *f)
+{
+    // disable interrupt
+    DI();
+
+    f->tag[f->write].status = VOBUF_STATUS_FULL;
+    f->count++;
+    f->write = (f->write + 1) % f->size;
+
+    // enable interrupt
+    EI();
+}
+
+VoData *voBufGetData(VoBuf *f)
+{
+    return voBufIsFull(f)? (VoData*)NULL: f->data + f->write;
+}
+
+// ////////////////////////////////////////////////////////////////
+//
+// Functions called from interrupt handler
+//
+int voBufIsEmpty(VoBuf *f)
+{
+    return f->count == 0;
+}
+
+VoTag *voBufGetTag(VoBuf *f)
+{
+    return voBufIsEmpty(f)? (VoTag*)NULL:
+    	f->tag + ((f->write - f->count + f->size) % f->size);
+}
+
+void voBufDecCount(VoBuf *f)
+{
+    if (f->count > 0) {
+	f->count--;
+    }
+}
+
+} // namespace Flx
diff --git a/Code/Gel/Movies/Ngps/vobuf.h b/Code/Gel/Movies/Ngps/vobuf.h
new file mode 100644
index 0000000..f0d6f33
--- /dev/null
+++ b/Code/Gel/Movies/Ngps/vobuf.h
@@ -0,0 +1,90 @@
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#ifndef _VOBUF_H_
+#define _VOBUF_H_
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Definitions
+//
+#define N_VOBUF 2
+
+#define VOBUF_STATUS_    0
+#define VOBUF_STATUS_TOPDONE  1
+#define VOBUF_STATUS_FULL     2
+
+#define MAX_MBX		(MAX_WIDTH/16)
+#define MAX_MBY		(MAX_HEIGHT/16)
+
+namespace Flx
+{
+
+
+
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Element data for video output buffer
+//
+struct VoData{
+    u_char v[MAX_WIDTH * MAX_HEIGHT * 4];
+};
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Tag for video output buffer
+//
+struct VoTag{
+    int status;		// status
+    int dummy[15];	// this is to adjust D$ line
+    u_int v[N_VOBUF][bound((N_LDTAGS+100)*4, 64)];
+};
+
+// ////////////////////////////////////////////////////////////////
+//
+//  Video output buffer
+//
+struct VoBuf{
+    VoData *data;	    // data array
+    VoTag *tag;		    // tag array for path3 transfer
+    volatile int write;	    // write position
+    volatile int count;	    // the number of images in VoBuf
+    int size;		    // total number of elements in VoBuf
+};
+
+// ////////////////////////////////////////////////////////////////
+//
+// Functions called from decoding thread
+//
+struct _Display;
+
+void voBufCreate(
+    VoBuf *f,
+    VoData *data,
+    VoTag *tag,
+    int size
+);
+void voBufReset(VoBuf *f);
+int voBufSetTags(VoBuf *f, struct _Display *d);
+int voBufIsFull(VoBuf *f);
+void voBufIncCount(VoBuf *f);
+VoData *voBufGetData(VoBuf *f);
+void voBufDelete(VoBuf *f);
+
+// ////////////////////////////////////////////////////////////////
+//
+// Functions called from interrupt handler
+//
+int voBufIsEmpty(VoBuf *f);
+VoTag *voBufGetTag(VoBuf *f);
+void voBufDecCount(VoBuf *f);
+
+extern VoBuf voBuf;
+
+} // namespace Flx
+
+#endif // _VOBUF_H_
diff --git a/Code/Gel/Movies/Xbox/p_movies.cpp b/Code/Gel/Movies/Xbox/p_movies.cpp
new file mode 100644
index 0000000..8381b8c
--- /dev/null
+++ b/Code/Gel/Movies/Xbox/p_movies.cpp
@@ -0,0 +1,457 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SK3														**
+**																			**
+**	Module:			Game Engine (GEL)	 									**
+**																			**
+**	File name:		p_movies.cpp											**
+**																			**
+**	Created:		07/24/01	-	dc										**
+**																			**
+**	Description:	Xbox specific .bik Bink movie streaming code			**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+extern DWORD PixelShader4;
+
+namespace Flx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+typedef struct
+{
+	float	sx,sy,sz;
+	float	rhw;
+	uint32	color;
+	float	tu,tv;
+}
+MOVIE_VERT;
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+bool	alphaPixels					= false;
+uint32	currentPlaybackSurfaceType	= BINKSURFACE32;
+float	playbackWidth				= 640.0f;
+float	playbackHeight				= 480.0f;
+float	textureWidth				= 640.0f;
+float	textureHeight				= 480.0f;
+float	playbackWidthScale			= 1.0f;
+float	playbackHeightScale			= 1.0f;
+HBINK	playbackBinkHandle			= NULL;
+
+DWORD	cullMode;
+DWORD	multisampleAntialias;
+DWORD	minFilter;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+LPDIRECT3DTEXTURE8 openPlaybackImage( uint32 width, uint32 height )
+{
+	// Need to this prevent the single poly being culled.
+	D3DDevice_GetRenderState( D3DRS_CULLMODE,				&cullMode );
+	D3DDevice_SetRenderState( D3DRS_CULLMODE,				D3DCULL_NONE );
+
+	D3DDevice_GetRenderState( D3DRS_MULTISAMPLEANTIALIAS,	&multisampleAntialias );
+	D3DDevice_SetRenderState( D3DRS_MULTISAMPLEANTIALIAS,	FALSE );
+	
+	D3DDevice_SetRenderState( D3DRS_LIGHTING,				FALSE );
+
+	NxXbox::set_render_state( RS_ZWRITEENABLE,	0 );
+	NxXbox::set_render_state( RS_ZTESTENABLE,	0 );
+	
+	D3DDevice_GetTextureStageState( 0, D3DTSS_MINFILTER,	&minFilter );
+	D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER,	D3DTEXF_LINEAR );
+
+	textureWidth	= (float)width;
+	textureHeight	= (float)height;
+	
+	// Adjust for our current screen buffer.
+	playbackWidth	= (float)NxXbox::EngineGlobals.backbuffer_width;
+	playbackHeight	= (float)NxXbox::EngineGlobals.backbuffer_height;
+	
+	// Create a surface for our texture with the DirectDraw handle.
+	LPDIRECT3DTEXTURE8 p_texture_surface;
+	if( SUCCEEDED( D3DDevice_CreateTexture( width, height, 1, 0, D3DFMT_LIN_X8R8G8B8, 0, &p_texture_surface )))
+	{
+		return p_texture_surface;
+	}
+
+	return NULL;
+}
+
+
+
+void closePlaybackImage( LPDIRECT3DTEXTURE8 p_image )
+{
+	// Restore various states.
+	D3DDevice_SetRenderState( D3DRS_CULLMODE,				cullMode );
+	D3DDevice_SetRenderState( D3DRS_MULTISAMPLEANTIALIAS,	multisampleAntialias );
+	D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER,	minFilter );
+	
+	NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+	NxXbox::set_render_state( RS_ZTESTENABLE,	1 );
+
+	if( p_image )
+	{
+		ULONG refcount = p_image->Release();
+		Dbg_Assert( refcount == 0 );
+	}
+
+	NxXbox::set_texture( 0, NULL );
+}
+
+
+
+// Advance a Bink file by one frame into a 3D image buffer.
+static void decompressFrame( HBINK bink, LPDIRECT3DTEXTURE8 p_image, bool copy_all )
+{
+	// Decompress the Bink frame.
+	BinkDoFrame( bink );
+
+	// Lock the 3D image so that we can copy the decompressed frame into it.
+	D3DLOCKED_RECT lock_rect;
+	if( SUCCEEDED( p_image->LockRect( 0, &lock_rect, 0, 0 )))
+	{
+		void*	pixels		= lock_rect.pBits;
+		uint32	pixel_pitch	= lock_rect.Pitch;
+
+		// Copy the decompressed frame into the 3D image.
+		BinkCopyToBuffer(	bink,
+							pixels,
+							pixel_pitch,
+							bink->Height,
+							0,					// Left pixel offset.
+							0,					// Top pixel offset.
+							currentPlaybackSurfaceType | (( copy_all ) ? BINKCOPYALL : 0 ));
+
+		// Unlock the 3D image.
+		p_image->UnlockRect( 0 );
+	}
+}
+
+
+
+// Blit a 3D image onto the render target.
+void blitImage( LPDIRECT3DTEXTURE8 p_image, float x_offset, float y_offset, float x_scale, float y_scale, float alpha_level )
+{
+	if( p_image == NULL )
+	{
+		return;
+	}
+
+	NxXbox::set_blend_mode( NxXbox::vBLEND_MODE_DIFFUSE );
+	
+	// Turn on clamping so that the linear textures work
+	NxXbox::set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+	
+	// Use a default vertex and pixel shader
+	NxXbox::set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 );
+	NxXbox::set_pixel_shader( PixelShader4 );
+
+	// Select the texture.
+	NxXbox::set_texture( 0, NULL );
+	NxXbox::set_texture( 0, p_image );
+
+	// Setup up the vertices.
+	MOVIE_VERT vertices[4];
+
+	vertices[0].sx		= x_offset;
+	vertices[0].sy		= y_offset;
+	vertices[0].sz		= 0.0f;
+	vertices[0].rhw		= 0.0f;
+	vertices[0].color	= ((int)(( alpha_level * 255.0f )) << 24 ) | 0x808080;
+	vertices[0].tu		= -0.5f;
+	vertices[0].tv		= -0.5f;
+
+	vertices[1]			= vertices[ 0 ];
+	vertices[1].sx		= x_offset + (((float)playbackWidth ) * x_scale );
+	vertices[1].tu		= ((float)textureWidth ) - 0.5f;
+
+	vertices[2]			= vertices[0];
+	vertices[2].sy		= y_offset + (((float)playbackHeight ) * y_scale );
+	vertices[2].tv		= ((float)textureHeight ) - 0.5f;
+
+	vertices[3]			= vertices[1];
+	vertices[3].sy		= vertices[2].sy;
+	vertices[3].tv		= ((float)textureHeight ) - 0.5f;
+
+	// Draw the vertices.
+	D3DDevice_DrawVerticesUP( D3DPT_TRIANGLESTRIP, 4, vertices, sizeof( MOVIE_VERT ));
+}
+
+
+
+
+
+
+static void showFrame( HBINK bink, LPDIRECT3DTEXTURE8 p_image )
+{
+	// Begin a 3D frame.
+	D3DDevice_Clear( 0, 0, D3DCLEAR_TARGET, 0, 1.0f, 0 );
+
+	// Draw the image on the screen (centered)...
+	float x = 0.0f;
+	float y = 0.0f;
+	blitImage( p_image, x, y, playbackWidthScale, playbackHeightScale, 1.0f );
+
+	// End a 3D frame.
+	D3DDevice_Swap( D3DSWAP_DEFAULT );
+
+	// Keep playing the movie.
+	BinkNextFrame( bink );
+}
+
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+static bool sDebounceCheck(uint16 *p_debounceFlags, uint8 *p_data, uint16 mask)
+{
+	if (p_data==NULL)
+	{
+		return false;
+	}
+		
+	Dbg_MsgAssert(p_debounceFlags,("NULL p_debounceFlags"));
+	
+	uint16 data=(p_data[2]<<8)|p_data[3];
+	if ((data & mask)==0)
+	{
+		// Button is pressed.
+		
+		if (*p_debounceFlags & mask)
+		{
+			// OK to return true, because the button got released at some point in the past.
+			return true;
+		}	
+	}
+	else
+	{
+		// Button is not pressed, so set the appropriate debounce flag so that next time it is
+		// pressed it will be detected.
+		*p_debounceFlags |= mask;
+	}
+	return false;	
+}
+
+void PMovies_PlayMovie( const char* pName )
+{
+	// Figure the volume we want to play at.
+	Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+	float music_vol	= Pcm::GetVolume();
+	float sfx_vol	= sfx_manager->GetMainVolume( );
+	float vol		= music_vol > sfx_vol ? music_vol : sfx_vol;
+
+	// Incoming movie name is in the form movies\, convert to d:\data\movies\bik\.bik.
+	char name_conversion_buffer[256] = "d:\\data\\movies\\bik\\";
+	int length		= strlen( pName );
+	int backwards	= length;
+	while( backwards )
+	{
+		if( pName[backwards] == '\\' )
+		{
+			++backwards;
+			break;
+		}
+		--backwards;
+	}
+	strncpy( name_conversion_buffer + 19, pName + backwards, length - backwards );
+	length = strlen( name_conversion_buffer );
+	name_conversion_buffer[length] = '.';
+	name_conversion_buffer[length + 1] = 'b';
+	name_conversion_buffer[length + 2] = 'i';
+	name_conversion_buffer[length + 3] = 'k';
+	name_conversion_buffer[length + 4] = 0;
+
+	// Stop music and streams.
+	Pcm::StopMusic();
+	Pcm::StopStreams();
+	
+	playbackBinkHandle = BinkOpen( name_conversion_buffer, 0 );
+	if( playbackBinkHandle == NULL )
+	{
+		// Movie not there, just quit.
+		return;
+	}
+
+	// Switch the presentation interval to 30fps.
+	DWORD presentation_interval;
+	D3DDevice_GetRenderState( D3DRS_PRESENTATIONINTERVAL, &presentation_interval );
+	D3DDevice_SetRenderState( D3DRS_PRESENTATIONINTERVAL, D3DPRESENT_INTERVAL_ONE );
+	
+	// Open a 3D image for the Bink.
+	LPDIRECT3DTEXTURE8 p_image = openPlaybackImage( playbackBinkHandle->Width, playbackBinkHandle->Height );
+	Dbg_Assert( p_image );
+
+	// K: Call XGetDeviceChanges once before going into the loop, otherwise the first time this function
+	// is run it will think new cards and pads have been inserted.
+//	bool running_demo_movie=Script::GetInt("RunningDemoMovie");
+	bool running_demo_movie=false;
+	if (running_demo_movie)
+	{
+		DWORD insertions=0;
+		DWORD removals=0;
+		XGetDeviceChanges(XDEVICE_TYPE_MEMORY_UNIT, &insertions, &removals);
+		XGetDeviceChanges(XDEVICE_TYPE_GAMEPAD, &insertions, &removals);
+	}				
+	
+	if( p_image )
+	{
+		BinkSetSoundOnOff( playbackBinkHandle, 1 );
+
+		// For BinkSetVolume(), 32768 is the 'nornal volume', effectively full volume on systems that don't
+		// do amplification, like Xbox.
+		BinkSetVolume( playbackBinkHandle, 0, (int)( 16384 * ( vol * 0.01f )));
+
+		// Will need this in the loop...
+		Spt::SingletonPtr< SIO::Manager > sio_manager;
+		uint16 debounce_flags[4] = { 0,0,0,0 };
+
+		// Start the playback loop.
+		while( true )
+		{
+			if( !BinkWait( playbackBinkHandle ))
+			{
+				decompressFrame( playbackBinkHandle, p_image, true );
+				showFrame( playbackBinkHandle, p_image );
+
+				if( playbackBinkHandle->FrameNum >= ( playbackBinkHandle->Frames - 1 ))
+				{
+					break;
+				}
+
+				// Check for a button being pressed to skip out of movie playback.
+				sio_manager->ProcessDevices();
+
+				// If running a demo movie, then pad or mem card insertions must make it quit. (TRC requirement)
+				if( running_demo_movie )
+				{
+					// Use the XGetDeviceChanges function to determine if a card or pad has been inserted ...
+					DWORD insertions=0;
+					DWORD removals=0;
+					if( XGetDeviceChanges( XDEVICE_TYPE_MEMORY_UNIT, &insertions, &removals ))
+					{
+						if( insertions )
+						{
+							break;
+						}	
+					}	
+					if( XGetDeviceChanges( XDEVICE_TYPE_GAMEPAD, &insertions, &removals ))
+					{
+						if( insertions )
+						{
+							break;
+						}	
+					}	
+				}				
+
+				bool quit = false;
+				for( int d = 0; d < 4; ++d )
+				{
+					SIO::Device* p_device = sio_manager->GetDeviceByIndex( d );
+					if( p_device )
+					{
+						unsigned char* p_data = p_device->GetControlData();
+
+						if( sDebounceCheck( &debounce_flags[d], p_data, 0x0800 ) ||	// Start
+							sDebounceCheck( &debounce_flags[d], p_data, 0x0040 ))		// A
+						{
+							quit = true;
+							break;
+						}							
+						
+						// If running a demo movie, then these buttons must also make it quit. (TRC requirement)
+						if( running_demo_movie )
+						{
+							if( sDebounceCheck(&debounce_flags[d], p_data, 0x0100) ||	// Back
+								sDebounceCheck(&debounce_flags[d], p_data, 0x0020) ||	// B
+								sDebounceCheck(&debounce_flags[d], p_data, 0x0080) ||	// X
+								sDebounceCheck(&debounce_flags[d], p_data, 0x0010) ||	// Y
+								sDebounceCheck(&debounce_flags[d], p_data, 0x0002) ||	// Black
+								sDebounceCheck(&debounce_flags[d], p_data, 0x0001) ||	// White
+								sDebounceCheck(&debounce_flags[d], p_data, 0x0200) ||	// Thumbstick 1
+								sDebounceCheck(&debounce_flags[d], p_data, 0x0400 ))	// Thumbstick 2
+							{
+								quit = true;
+								break;
+							}							
+						}
+					}
+				}
+				if( quit )
+				{
+					break;
+				}
+			}
+		}
+	}
+
+	closePlaybackImage( p_image );
+	BinkClose( playbackBinkHandle );
+	playbackBinkHandle = NULL;
+
+	// Switch the presentation interval bacl to what it was.
+	D3DDevice_SetRenderState( D3DRS_PRESENTATIONINTERVAL, presentation_interval );
+}
+
+} // namespace Flx
+
diff --git a/Code/Gel/Movies/Xbox/p_movies.h b/Code/Gel/Movies/Xbox/p_movies.h
new file mode 100644
index 0000000..b6645a4
--- /dev/null
+++ b/Code/Gel/Movies/Xbox/p_movies.h
@@ -0,0 +1,43 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			ps2 movies												**
+**																			**
+**	File name:		gel/movies/ngps/p_movies.h 								**
+**																			**
+**	Created: 		6/27/01	-	dc											**
+**																			**
+*****************************************************************************/
+
+#ifndef __P_MOVIES_H
+#define __P_MOVIES_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Flx
+{
+
+	void PMovies_PlayMovie( const char *pName );
+
+} // namespace Flx
+
+#endif	// __P_MOVIES_H
+
+
+
diff --git a/Code/Gel/Movies/ngc/p_movies.cpp b/Code/Gel/Movies/ngc/p_movies.cpp
new file mode 100644
index 0000000..1d5f527
--- /dev/null
+++ b/Code/Gel/Movies/ngc/p_movies.cpp
@@ -0,0 +1,2839 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SK3														**
+**																			**
+**	Module:			Game Engine (GEL)	 									**
+**																			**
+**	File name:		p_movies.cpp											**
+**																			**
+**	Created:		08/27/01	-	dc										**
+**																			**
+**	Description:	Gamecube specific movie streaming code					**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef DVDETH
+
+#undef __GX_H__
+#define _GX_H_
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "sys\ngc\p_aram.h"
+#include 
+#include	
+#include 
+#include 
+#include	
+#include	
+#include	
+#include "sys/ngc/p_prim.h"
+#include "gel\music\ngc\p_music.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include 
+#include 
+#include 
+#include 
+
+#include "VIDSimpleDEMO.h"
+#include "VIDSimplePlayer.h"
+#include "VIDSimpleAudio.h"
+#include "VIDSimpleDraw.h"
+
+#include 
+#include 
+
+#define MY_DEBUG
+
+extern PADStatus		padData[PAD_MAX_CONTROLLERS]; // game pad state
+
+extern GXColor messageColor;
+
+#undef ASSERT
+#define ASSERT(exp)                                             \
+    (void) ((exp) ||                                            \
+            (OSPanic(__FILE__, __LINE__, "Failed assertion " #exp), 0))
+
+extern GXRenderModeObj *rmode;
+
+//#define USE_DIRECT_XFB
+
+//! Base address for 'Locked Cache' simple memory manager
+static u8* 	lcMemBase = 0;
+
+//! Locked cache base address for XFB conversion stuff
+//! This is only required, if USE_DIRECT_XFB is set
+static void* xfbLCStart = 0;		
+
+//#define	VIDEO_FILENAME		"movies/peacemaker.vid"
+//#define	VIDEO_FILENAME		"movies/video3.vid"
+//#define	VIDEO_FILENAME		"movies/doomraiders.vid"
+//#define	VIDEO_FILENAME		"movies/pu.vid"
+//#define	VIDEO_FILENAME		"movies/nslogo.vid"
+//#define	VIDEO_FILENAME		"movies/out4.vid"
+//#define	VIDEO_FILENAME		"movies/hom.vid"
+//#define	VIDEO_FILENAME		"movies/out5.vid"
+
+extern bool	g_legal;
+
+/******************************************************************************
+ *  GLOABL VARIABLES
+ ******************************************************************************/
+extern void*   hwCurrentBuffer;         // current XFB frame buffer allocated in DEMO library 
+VidSimplePlayer player;
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+
+/******************************************************************************
+ *  LOCAL VARIABLES
+ ******************************************************************************/
+static VidChunk workChunk ATTRIBUTE_ALIGN(32);
+static void dvdDoneCallback(s32 result, DVDFileInfo *videoInfo);
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Initializes all player structures.
+ *
+ ******************************************************************************
+ */
+void VIDSimpleInit(VIDAllocator _cbAlloc, VIDDeallocator _cbFree, VIDAllocator _cbLockedCache)
+	{
+	memset(&player, 0, sizeof(player));
+	player.cbAlloc = _cbAlloc;
+	player.cbFree = _cbFree;
+	player.cbLockedCache = _cbLockedCache;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Request an async file transfer.
+ *
+ *		This function starts the transfer of the next frame into a free
+ *		buffer.
+ *
+ ******************************************************************************
+ */
+static void ReadFrameAsync(void)
+	{
+	if (!player.error && player.preFetchState == TRUE)
+		{
+		if (player.currentFrameCount >  player.videoInfo.maxFrameCount - 1)
+			{
+			if (player.loopMode)
+				{
+				player.currentFrameCount = 0;
+				player.nextFrameOffset = player.firstFrameOffset;
+				player.nextFrameSize  = player.firstFrameSize;
+				}
+			else
+				return;
+			}
+		
+		player.asyncDvdRunning = TRUE;
+
+		if (DVDReadAsync(&player.fileHandle,
+						 player.readBuffer[player.readIndex].ptr,
+						 (s32)player.nextFrameSize,
+						 (s32)player.nextFrameOffset, dvdDoneCallback) != TRUE )
+			{
+			player.asyncDvdRunning = FALSE;
+			player.error = TRUE;
+			}
+		}
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		DVD callback if async transfer is finished (or aborted)
+ *	
+ *		The idea here is to transfer ONE frame and additional 32 bytes for the
+ *		HEADER of the NEXT frame in one transfer step. We store the size of
+ *		the next frame, which is used in ReadFrameAsync().
+ *
+ *		
+ * \note
+ *		There a 32 padding bytes at the end of the .vid file. So, the reading
+ *		of 32 additional bytes is even possible for the LAST frame. (Otherwise,
+ *		we would 'point' out of the file)
+ *
+ *		See Dolphin documentation for information about parameters.
+ *
+ ******************************************************************************
+ */
+static void dvdDoneCallback(s32 result, DVDFileInfo * _UNUSED(videoInfo))
+	{
+	if (result == DVD_RESULT_FATAL_ERROR)
+		{
+		player.error = TRUE;
+		return;
+		}
+	else if (result == DVD_RESULT_CANCELED)
+		{
+		return;
+		}
+
+	player.asyncDvdRunning = FALSE;
+
+	player.readBuffer[player.readIndex].frameNumber = player.currentFrameCount;
+	player.readBuffer[player.readIndex].size = (u32)result;	
+	player.readBuffer[player.readIndex].valid = TRUE;
+	
+	player.currentFrameCount++;
+
+	// move file pointer
+	player.nextFrameOffset += player.nextFrameSize;
+	
+	if(player.currentFrameCount < player.videoInfo.maxFrameCount)
+		{
+		// set read size for next 'FRAM' chunk
+		u32* nextFrameStart = (u32*)(player.readBuffer[player.readIndex].ptr + player.nextFrameSize - 32);
+	
+		// some check if file structure is okay
+		ASSERT(nextFrameStart[0] == VID_FCC('F','R','A','M'));
+		
+		// get the size of the next 'FRAM' chunk to read
+		player.nextFrameSize = nextFrameStart[1];
+		ASSERT(player.nextFrameSize);
+		}
+	else
+		player.nextFrameSize = 0;	// at end of file we have a size of '0'. This should be reinitialized later
+									// using the size of the first frame somwhere else! Otherwise, we get an assertion
+
+	// use next buffer
+	player.readIndex = (player.readIndex + 1) % VID_NUM_READ_BUFFERS;
+
+	// continue loading if we have a free buffer
+	if (!player.readBuffer[player.readIndex].valid)
+		ReadFrameAsync();
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Allocate buffer memory for asynchronous dvd read
+ *
+ * \param memAlloc
+ *		Pointer to memory allocation function
+ *
+ * \return
+ *		TRUE if DVD buffer setup was successful.
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleAllocDVDBuffers(void)
+	{
+	u32 i;
+	u32 bufferSize;
+	u8* ptr;
+
+	bufferSize = player.videoInfo.maxBufferSize;
+	ASSERT(bufferSize);
+	
+	bufferSize += VID_CHUNK_HEADER_SIZE;	// 'fram' header
+	bufferSize += VID_CHUNK_HEADER_SIZE;	// 'vidd' header
+	bufferSize = OSRoundUp32B(bufferSize);
+	
+	ASSERT(player.cbAlloc);
+	player.readBufferBaseMem = (u8*)((*player.cbAlloc)(bufferSize * VID_NUM_READ_BUFFERS));
+	
+	if(!player.readBufferBaseMem)
+		return FALSE;	// out of mem
+	
+	ptr = player.readBufferBaseMem;
+	for (i = 0; i < VID_NUM_READ_BUFFERS ; i++)
+		{
+		player.readBuffer[i].ptr = ptr;
+		ptr += bufferSize;
+		player.readBuffer[i].valid = FALSE;
+		}
+	
+	return TRUE;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Free buffer memory used for dvd read
+ *
+ * \param memFree
+ *		Pointer to memory deallocation function
+ *
+ ******************************************************************************
+ */
+void VIDSimpleFreeDVDBuffers(void)
+	{
+	ASSERT(player.cbFree);
+	ASSERT(player.readBufferBaseMem);
+	(*player.cbFree)(player.readBufferBaseMem);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Create a new decoder instance.
+ *
+ *		The required parameters about the decoding process will be supplied
+ *		in the VIDDecoderSetup structure.
+ *
+ * \param supportBFrames
+ *		Set to TRUE for enabling b-frame support.
+ *
+ * \return
+ *		TRUE if decoder creation was successful
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleCreateDecoder(BOOL supportBFrames)
+	{
+	VIDDecoderSetup	setup;
+	
+	setup.size = sizeof(VIDDecoderSetup);
+	setup.width = player.videoInfo.width;
+	setup.height = player.videoInfo.height;
+	setup.flags = supportBFrames ? VID_DECODER_B_FRAMES : 0;
+	setup.cbMemAlloc = player.cbAlloc;
+	setup.cbMemFree = player.cbFree;
+	setup.cbMemAllocLockedCache = player.cbLockedCache;
+
+	// Check if we want to setup audio decoding.
+	// The audio header info must be already preset here!
+	if(player.audioInfo.audioID == VID_FCC('V','A','U','D'))
+		{
+		u32 skip;
+		ASSERT(player.audioHeaderChunk);
+		ASSERT(player.audioInfo.vaud.maxHeap > 0);
+		ASSERT(player.audioInfo.vaud.preAlloc > 0);
+		
+		setup.flags |= VID_DECODER_AUDIO;
+
+		skip = VID_CHUNK_HEADER_SIZE + sizeof(u32) + (player.audioInfo.vaudex.version > 0 ? player.audioInfo.vaudex.size : sizeof(VidAUDHVAUD));
+		setup.audio.headerInfo = player.audioHeaderChunk + skip;
+        setup.audio.headerInfoBytes = ((VidChunk*)player.audioHeaderChunk)->len - skip;
+		setup.audio.maxHeap = player.audioInfo.vaud.maxHeap;
+		setup.audio.preAlloc = player.audioInfo.vaud.preAlloc;
+		}
+	
+	player.decoder = VIDCreateDecoder(&setup);
+
+	// check if decoder creation failed!
+	return player.decoder ? TRUE : FALSE;
+	}/*!
+ ******************************************************************************
+ * \brief
+ *		Destroy decoder instance.
+ *
+ *		At this point the decoder returns all allocated memory by using
+ *		the cbFree callback.
+ *
+ ******************************************************************************
+ */
+void VIDSimpleDestroyDecoder(void)
+	{
+	ASSERT(player.decoder);
+	VIDDestroyDecoder(player.decoder);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Preload the allocated buffers.
+ *
+ *		This functions fills all buffers with initial data
+ *
+ * \param loopMode
+ *		TRUE if we want to operate in loop mode
+ *
+ * \return
+ *		TRUE if preload was okay 		
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleLoadStart(BOOL loopMode)
+	{
+	u8* ptr;
+	u32	i, readNum;
+	u32* nextFrame;
+
+	if (player.open && player.preFetchState == FALSE)
+		{
+		
+		readNum = VID_NUM_READ_BUFFERS;
+
+		// in 'non-loop' mode we must take care if we have LESS frames than preloading buffers
+        if (!loopMode && player.videoInfo.maxFrameCount < VID_NUM_READ_BUFFERS)
+                readNum = player.videoInfo.maxFrameCount;
+				
+		for(i = 0; i < readNum; i++)
+			{
+			
+			ptr = player.readBuffer[player.readIndex].ptr;
+			
+			// read total 'FRAM' chunk and 32 bytes of NEXT chunk
+			if (DVDRead(&player.fileHandle, ptr, (s32)player.nextFrameSize, (s32)player.nextFrameOffset) < 0 )
+				{
+#ifdef MY_DEBUG
+				OSReport("*** VIDSimpleLoadStart: Failed to read from file.\n");
+#endif
+				player.error = TRUE;
+				return FALSE;
+				}
+			
+			player.nextFrameOffset += player.nextFrameSize;
+			player.readBuffer[player.readIndex].size = player.nextFrameSize;
+
+            // set read size for next 'FRAM' chunk
+			nextFrame = (u32*)(ptr + player.nextFrameSize - 32);
+			
+			// some sanity check if file structure is valid!
+			ASSERT(nextFrame[0] == VID_FCC('F','R','A','M'));
+			
+			player.nextFrameSize = nextFrame[1];
+			ASSERT(player.nextFrameSize);
+
+			player.readBuffer[player.readIndex].valid = TRUE;
+			player.readBuffer[player.readIndex].frameNumber = player.currentFrameCount;
+
+			// use next buffer
+			player.readIndex = (player.readIndex + 1) % VID_NUM_READ_BUFFERS;
+
+			player.currentFrameCount++;
+
+			if (player.currentFrameCount >  player.videoInfo.maxFrameCount - 1)
+				{
+				if (loopMode)
+					{
+					player.currentFrameCount = 0;
+					player.nextFrameOffset = player.firstFrameOffset;
+					player.nextFrameSize  = player.firstFrameSize;
+					}
+				}
+			}
+			
+			player.loopMode = loopMode;
+			player.preFetchState = TRUE;
+
+			return TRUE;
+		}
+	return FALSE;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Stops the asynchronous loading process.
+ *
+ * \return
+ *		TRUE if player could be stopped!		
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleLoadStop(void)
+	{
+	u32 i;
+
+	if (player.open)
+		{
+		// stop preloading process
+		player.preFetchState = FALSE;
+
+		if (player.asyncDvdRunning)
+			{
+			DVDCancel(&player.fileHandle.cb);
+			player.asyncDvdRunning = FALSE;
+			}
+
+		// invalidate all buffers
+		for (i = 0 ; i < VID_NUM_READ_BUFFERS; i++)
+			player.readBuffer[i].valid = FALSE;
+
+		player.nextFrameOffset = player.firstFrameOffset;
+		player.nextFrameSize = player.firstFrameSize;
+		player.currentFrameCount = 0;
+		
+		player.error 	   		= FALSE;
+		player.readIndex   		= 0;
+		player.decodeIndex 		= 0;
+
+		return TRUE;
+		}
+	return FALSE;
+    }
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Open video file.
+ *
+ *		This functions opens a video file and parses some basic file
+ *		information.
+ *
+ * \param fileName
+ *		Name of file to open
+ *
+ * \return
+ *		TRUE if file could be opened and is in valid format!
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleOpen(char* fileName, BOOL suppressAudio)
+	{
+	u32 fileOffset = 0;
+	u32 headSize;
+	u32 audioInfoSize;
+	
+    if (player.open)
+		{
+#ifdef _DEBUG
+		OSReport("*** Cannot open '%s', because player already open.\n");
+#endif
+		return FALSE;
+		}
+
+	if (DVDOpen(fileName, &player.fileHandle) == FALSE)
+		{
+#ifdef _DEBUG
+		OSReport("*** Cannot open: '%s'\n", fileName);
+#endif
+		return FALSE;
+		}
+		
+	// Read 'VID1' chunk from file and check for correct version
+    if (DVDRead(&player.fileHandle, &workChunk, 32, 0) < 0)
+		{
+#ifdef _DEBUG
+		OSReport("*** Failed to read the header.\n");
+#endif
+		DVDClose(&player.fileHandle);
+		return FALSE;
+		}
+		
+	fileOffset += 32;
+	
+	// Check file id	
+	if(workChunk.id != VID_FCC('V','I','D','1')  )
+		{
+#ifdef _DEBUG
+		OSReport("*** No VID1 file: '%s'\n", fileName);
+#endif
+		DVDClose(&player.fileHandle);
+		return FALSE;
+		}
+	
+	// Check for correct version of vid chunk.
+	// If we find this version we assume a 'special' alignment and chunk ordering which may be invalid
+	// in another version of the file format.
+	if(workChunk.vid.versionMajor != 1 || workChunk.vid.versionMinor != 0)
+		{
+#ifdef _DEBUG
+		OSReport("*** Unsupported file version: major: %d, minor: %d\n",
+				  workChunk.vid.versionMajor, workChunk.vid.versionMajor);
+#endif
+		DVDClose(&player.fileHandle);
+		return FALSE;
+		}
+	
+#ifdef _DEBUG
+	// Sometimes, it's required to check for a special build of the VidConv converter.
+	{
+	u32 version = VID_VERSION(workChunk.vid.vidConvMajor, workChunk.vid.vidConvMinor, workChunk.vid.vidConvBuild);
+	if(version < VID_VERSION(1,6,4))
+		OSReport("*** WARNING: Vid file created using an unsupported converter version: %d.%d.%d\n", (u32)workChunk.vid.vidConvMajor, (u32)workChunk.vid.vidConvMinor, (u32)workChunk.vid.vidConvBuild);
+	}
+#endif
+
+	// Check types of chunks we have in this file.
+	// !!! Note that we assume start of 'HEAD' chunk at byte offset 32 from file start !!!
+	if (DVDRead(&player.fileHandle, &workChunk, 32, (s32)fileOffset) < 0)
+		{
+#ifdef _DEBUG
+		OSReport("*** Failed to read 'HEAD' chunk.\n");
+#endif
+		DVDClose(&player.fileHandle);
+		return FALSE;
+		}
+	
+	if(workChunk.id != VID_FCC('H','E','A','D')  )
+		{
+#ifdef _DEBUG
+		OSReport("*** No HEAD chunk found at expected offset\n");
+#endif
+		DVDClose(&player.fileHandle);
+		return FALSE;
+		}
+	
+	// Calculate the start of the first frame chunk
+	// (we know the header chunk starts at offset 32)
+	player.nextFrameOffset = workChunk.len + 32;
+
+	// Skip 'HEAD' chunk id, len and version fields
+	fileOffset += VID_CHUNK_HEADER_SIZE;
+	
+	// We initialize audio codec info to a known value
+	// (this way we can detect the absence of any audio data)
+	player.audioInfo.audioID = VID_FCC('N','O','N','E');
+	
+	// The header chunk contains one or more header chunks for the different data types contained
+	// in the stream. Parse them all...
+
+	headSize = workChunk.len - VID_CHUNK_HEADER_SIZE;
+	while(headSize >= 32)
+		{
+		if (DVDRead(&player.fileHandle, &workChunk, 32, (s32)fileOffset) < 0)
+			{
+#ifdef _DEBUG
+			OSReport("*** Error reading file at offset %d\n", fileOffset);
+#endif
+			DVDClose(&player.fileHandle);
+			return FALSE;
+			}
+		
+		fileOffset += 32;
+		headSize -= 32;
+
+		// We analyze the 1st 32 bytes of the chunk for a known header format
+		
+		// Video header?
+		if(workChunk.id == VID_FCC('V','I','D','H') )
+			{
+			// check if we have an old vid file.
+			if(workChunk.version == 0)
+				{
+				workChunk.vidh.frameRateScale = (u16)(*((u32*)&workChunk.vidh.frameRateScale));
+				workChunk.vidh.flags = 0;
+				}
+			
+			// Yes...
+			ASSERT(workChunk.len <= 32);
+			ASSERT(workChunk.len <= (sizeof(VidVIDH) + VID_CHUNK_HEADER_SIZE));
+			memcpy(&player.videoInfo, &workChunk.vidh, sizeof(VidVIDH));
+			}
+		// It's an audio header chunk! May we initialize it?
+		else if(workChunk.id == VID_FCC('A','U','D','H') && !suppressAudio)
+			{
+			// Allocate memory for audio header chunk
+            player.audioHeaderChunk = (u8*)((*player.cbAlloc)(workChunk.len));
+			audioInfoSize = workChunk.len - VID_CHUNK_HEADER_SIZE;
+			
+			// Copy the already loaded part
+			memcpy(player.audioHeaderChunk, &workChunk, 32);
+			workChunk.len -= 32;
+			
+			// Read additional audio header bytes if the audio header is greater that 32 bytes
+			if(workChunk.len >= 32)
+				{
+				ASSERT((workChunk.len&31)==0);
+				if (DVDRead(&player.fileHandle, player.audioHeaderChunk+32, workChunk.len, (s32)fileOffset) < 0)
+					{
+#ifdef _DEBUG
+					OSReport("*** Error reading file at offset %d\n", fileOffset);
+#endif
+					DVDClose(&player.fileHandle);
+					return FALSE;
+					}
+				fileOffset += workChunk.len;
+				headSize -= workChunk.len;
+				}
+
+			// Setup and calc the number of bytes which we are allowed to copy into the audioInfo struct
+			memcpy(&player.audioInfo, player.audioHeaderChunk+VID_CHUNK_HEADER_SIZE, MIN(audioInfoSize, sizeof(player.audioInfo) + sizeof(player.adpcmInfo)));
+			}
+		// Skip unknown chunks. We already read 32 bytes for the header which we must subtract here.
+		else
+			{
+			fileOffset += workChunk.len - 32;
+			headSize -= workChunk.len - 32;
+			}
+        }
+	
+	ASSERT(player.videoInfo.width && player.videoInfo.height);
+	ASSERT(player.videoInfo.maxBufferSize);
+	
+	player.fps = (f32)player.videoInfo.frameRate / (f32)player.videoInfo.frameRateScale;
+	
+	// read beginning of 1st frame chunk to get required size information
+	if (DVDRead(&player.fileHandle, &workChunk, 32 , (s32)player.nextFrameOffset) < 0 )
+		{
+#ifdef _DEBUG
+		OSReport("*** Failed to read 'FRAM' chunk.\n");
+#endif
+		DVDClose(&player.fileHandle);
+		return FALSE;
+		}
+
+	if(workChunk.id != VID_FCC('F','R','A','M')  )
+		{
+#ifdef _DEBUG
+		OSReport("*** No FRAM chunk found.");
+#endif
+		DVDClose(&player.fileHandle);
+		return FALSE;
+		}
+
+	player.nextFrameSize = workChunk.len; 		// 32 bytes of this chunk are already consumed, but
+													// we want to 'preload' the NEXT chunk's FRAM header
+	player.nextFrameOffset += 32;
+	
+	player.firstFrameOffset = player.nextFrameOffset;
+	player.firstFrameSize   = player.nextFrameSize;
+
+	strncpy(player.fileName, fileName, 64);
+	player.fileName[63] = 0;
+	
+	player.open 			 	= TRUE;
+
+	player.readIndex 			= 0;
+	player.decodeIndex 			= 0;
+	player.lastDecodedFrame		= 0;
+	player.error 			 	= FALSE;
+	player.preFetchState 	 	= FALSE;
+	player.loopMode			 	= FALSE;
+	player.asyncDvdRunning		= FALSE;
+	player.currentFrameCount 	= 0;
+	player.readBufferBaseMem	= 0;
+
+	return TRUE;
+	} 	
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Close open video file
+ *
+ * \return
+ *		TRUE if file is closed sucessfully.
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleClose(void)
+	{
+	if (player.open)
+		{
+		if (player.preFetchState == FALSE)
+			{
+			if (!player.asyncDvdRunning)
+				{
+				player.open = FALSE;
+				DVDClose(&player.fileHandle);
+				if(player.audioHeaderChunk != NULL)
+					{
+					(*player.cbFree)(player.audioHeaderChunk);
+					player.audioHeaderChunk = NULL;
+					}
+				return TRUE;
+				}
+			}
+		}
+	return FALSE;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Decode all frame data
+ *
+ *		This function operates on the full frame input data. It forwards this
+ *		data to the required decoder.
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleDecode(void)
+	{
+	BOOL enabled;
+	u8* chunkStart;
+	u32 chunkSize;
+	u32 frameSize;
+
+	if ( player.readBuffer[player.decodeIndex].valid )
+		{
+		
+		// ptr to our (pre-) loaded data INSIDE (!) 'FRAM' chunk
+		// (in other words, the 'FRAM' chunk itself is not visible here)
+		chunkStart = player.readBuffer[player.decodeIndex].ptr;
+
+		// usually, we read additional 32 bytes for getting info about the NEXT chunk.
+		// We only deal with the actual 'FRAM' chunk data here and adjust the size by 32 bytes.
+        frameSize = player.readBuffer[player.decodeIndex].size - 32;
+		
+		// loop across ALL chunks inside 'FRAM'
+		while(frameSize >= 32)
+			{
+			chunkSize = VID_CHUNK_LEN(chunkStart);
+			
+			if( VID_CHUNK_ID(chunkStart) == VID_FCC('V','I','D','D') )
+				{
+				if(! VIDVideoDecode(player.decoder, chunkStart + VID_CHUNK_HEADER_SIZE, chunkSize - VID_CHUNK_HEADER_SIZE, &player.image))
+					{
+#ifdef _DEBUG
+					OSReport("*** VIDVideoDecode failed!\n");
+#endif
+					}
+				}
+			else if( VID_CHUNK_ID(chunkStart) == VID_FCC('A','U','D','D') )
+				{
+				// This is audio data!
+				
+				// Get the data to the audio system...
+				if(! VIDSimpleAudioDecode(chunkStart + VID_CHUNK_HEADER_SIZE, chunkSize - VID_CHUNK_HEADER_SIZE))
+					{
+#ifdef _DEBUG
+					OSReport("*** VIDAudioDecode failed!\n");
+#endif
+					}
+				}
+#ifdef _DEBUG
+			else
+				{
+				OSReport("*** VIDSimpleDecode: unknown chunk type!\n");
+				}
+#endif
+			
+			// goto next chunk
+			chunkStart += chunkSize;
+			frameSize -= chunkSize;
+			}
+			
+		player.lastDecodedFrame = player.readBuffer[player.decodeIndex].frameNumber;
+		player.readBuffer[player.decodeIndex].valid = FALSE;
+		player.decodeIndex = (player.decodeIndex + 1) % VID_NUM_READ_BUFFERS;
+
+		// check if loading is still running
+		enabled = OSDisableInterrupts();
+		if (!player.readBuffer[player.readIndex].valid && !player.asyncDvdRunning)
+			ReadFrameAsync();
+		OSRestoreInterrupts(enabled);
+		
+        return TRUE;
+		}
+
+#ifdef _DEBUG
+	OSReport("*** VIDSimpleDecode: No valid decode buffer found (?).\n");
+#endif
+	return FALSE;
+
+	}
+/*!
+ ******************************************************************************
+ * \brief
+ *		Draw a decoded video frame.
+ *
+ * \param rmode
+ *		Required info about current rendering mode
+ * \param x
+ *		current x pos of drawing surface
+ * \param y
+ *		current y pos of drawing surface
+ * \param width
+ *		current width of drawing surface
+ * \param height
+ *		current height of drawing surface
+ *
+ ******************************************************************************
+ */
+void VIDSimpleDraw(GXRenderModeObj *rmode, u32 x, u32 y, u32 width, u32 height)
+	{
+	VIDDrawGXYuv2RgbSetup(rmode);
+	VIDDrawGXYuv2RgbDraw((s16)x, (s16)y, (s16)width, (s16)height, player.image);
+	VIDDrawGXRestore();
+	}
+/*!
+ ******************************************************************************
+ * \brief
+ *		Draw decoded frame directely into the XFB
+ *
+ * \param rmode
+ *		Required info about current rendering mode
+ * \param lcMem
+ *		Pointer to free locked cache mem used for conversion.
+ *
+ ******************************************************************************
+ */
+void VIDSimpleDrawXFB(GXRenderModeObj *rmode, void* lcMem)
+	{
+	VIDXFBDraw(player.image, hwCurrentBuffer, rmode->fbWidth, rmode->xfbHeight, lcMem);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Get width and height of loaded video file.
+ *
+ ******************************************************************************
+ */
+void VIDSimpleGetVideoSize(u32* width, u32* height)
+	{
+	// can only be returned if player has a loaded file
+	ASSERT(player.open);
+	
+	*width = player.videoInfo.width;
+	*height = player.videoInfo.height;
+	}
+	
+/*!
+ ******************************************************************************
+ * \brief
+ *		Get FPS rate of loaded video file.
+ *
+ ******************************************************************************
+ */
+f32 VIDSimpleGetFPS(void)
+	{
+	return(player.fps);
+	}
+/*!
+ ******************************************************************************
+ * \brief
+ *		Check if the currently loaded video is in interlace mode or not
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleIsInterlace(void)
+	{
+	return(((player.videoInfo.flags & VID_VIDH_INTERLACED) != 0));
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Get number of frames of loaded video file.
+ *
+ ******************************************************************************
+ */
+u32 VIDSimpleGetFrameCount(void)
+	{
+	return(player.videoInfo.maxFrameCount);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Get audio sample rate in Hz.
+ *
+ ******************************************************************************
+ */
+u32 VIDSimpleGetAudioSampleRate(void)
+	{
+	return(player.audioInfo.vaud.frq);
+	}
+/*!
+ ******************************************************************************
+ * \brief
+ *		Check for drive status
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleCheckDVDError(void)
+	{
+	switch (DVDGetDriveStatus())
+		{
+		case DVD_STATE_FATAL_ERROR:
+			{
+			OSReport("DVDGetDriveStatus()=DVD_STATE_FATAL_ERROR\n");
+			break;
+			}
+		case DVD_STATE_NO_DISK:
+			{
+			OSReport("DVDGetDriveStatus()=DVD_STATE_NO_DISK\n");
+			break;
+			}
+		case DVD_STATE_COVER_OPEN:
+			{
+			OSReport("DVDGetDriveStatus()=DVD_STATE_COVER_OPEN\n");
+			break;
+			}
+		case DVD_STATE_WRONG_DISK:
+			{
+			OSReport("DVDGetDriveStatus()=DVD_STATE_WRONG_DISK\n");
+			break;
+			}
+		case DVD_STATE_RETRY:
+			{
+			OSReport("DVDGetDriveStatus()=DVD_STATE_RETRY\n");
+			break;
+			}
+		default:
+			return(TRUE);
+		}
+	
+	return(FALSE);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Memory allocation callback.
+ *
+ *		The player calls this function for all its memory needs. In this example
+ *		an OSAlloc() is all we need to do.
+ *
+ * \note
+ *		The returned memory address MUST be aligned on a 32 byte boundary.
+ *		Otherwise, the player will fail!
+ *
+ * \param size
+ *		Number of bytes to allocate
+ *
+ * \return
+ *		Ptr to allocated memory (aligned to 32 byte boundary)
+ *	
+ ******************************************************************************
+ */
+static void* myAlloc(u32 size)
+{
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+	void * p = new u8[size]; 
+	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+	Mem::Manager::sHandle().PopContext();
+	return p;
+}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Memory free callback.
+ *
+ * \param ptr
+ *		Memory address to free
+ *	
+ ******************************************************************************
+ */
+static void myFree(void* ptr)
+	{
+	ASSERT(ptr);	// free on address 0 is only valid in C++ delete
+	delete ptr;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Memory allocation callback for 'Locked Cache' memory.
+ *
+ *		If the system should operate in 'Locked Cache' mode, you must
+ *		supply a callback which is called for any 'locked cache'
+ *		memory requirements.
+ *
+ *		Note that there's no 'free' for the 'locked cache' memory,
+ *		because if the player is destroyed ANY 'locked cache' memory is
+ *		avaiable immediately.
+ *
+ * \note
+ *		The returned memory address MUST be aligned on a 32 byte boundary.
+ *		Otherwise, the player will fail!
+ *
+ * \param size
+ *		Number of bytes to allocate
+ *
+ * \return
+ *		Ptr to allocated memory (aligned to 32 byte boundary)
+ *	
+ ******************************************************************************
+ */
+static void* myAllocFromLC(u32 size)
+	{
+#ifdef MY_DEBUG
+	u32 lockCacheMem;
+#endif
+	void* ret = lcMemBase;
+	ASSERT(ret);
+	
+	lcMemBase += size;
+	lcMemBase = (u8*)OSRoundUp32B(lcMemBase);
+
+#ifdef MY_DEBUG
+	lockCacheMem = (u32)(lcMemBase - ((u8*)LCGetBase()));
+	//OSReport("myMallocFromLC: Used 'Locked Cache' Mem: %d kB.\n", lockCacheMem/1024);
+	ASSERTMSG(lockCacheMem < (15*1024), "myMallocFromLC: Too much locked cache mem needed!\n");
+#endif
+	return ret;
+	}
+
+/******************************************************************************
+ *  DEFINES
+ ******************************************************************************/
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Restore GX graphics context to some 'defaults'
+ *
+ ******************************************************************************
+ */
+void VIDDrawGXRestore(void)
+	{
+    GXSetZMode(GX_ENABLE, GX_ALWAYS, GX_DISABLE);
+    GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET);
+
+    GXSetNumTexGens(1);
+    GXSetNumChans(0);
+    GXSetNumTevStages(1);
+    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
+    GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
+
+    // Swap mode settings
+    GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
+    GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);
+    GXSetTevSwapMode(GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0);
+    GXSetTevSwapMode(GX_TEVSTAGE3, GX_TEV_SWAP0, GX_TEV_SWAP0);
+
+    GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED,   GX_CH_GREEN,
+                                        GX_CH_BLUE,  GX_CH_ALPHA); // RGBA
+    GXSetTevSwapModeTable(GX_TEV_SWAP1, GX_CH_RED,   GX_CH_RED,
+                                        GX_CH_RED,   GX_CH_ALPHA); // RRRA
+    GXSetTevSwapModeTable(GX_TEV_SWAP2, GX_CH_GREEN, GX_CH_GREEN,
+                                        GX_CH_GREEN, GX_CH_ALPHA); // GGGA
+    GXSetTevSwapModeTable(GX_TEV_SWAP3, GX_CH_BLUE,  GX_CH_BLUE,
+                                        GX_CH_BLUE,  GX_CH_ALPHA); // BBBA
+
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Setup GX for YUV conversion
+ *
+ ******************************************************************************
+ */
+void VIDDrawGXYuv2RgbSetup(GXRenderModeObj *rmode)
+	{
+    s32         scrWidth;
+    s32         scrHeight;
+    Mtx44       pMtx;
+
+    scrWidth  = rmode->fbWidth;
+    scrHeight = rmode->efbHeight;
+
+	GXSetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
+
+	MTXOrtho(pMtx, 0.0f, (f32)scrHeight, 0.0f, (f32)scrWidth, 0.0f, -1.0F);
+
+	GXSetProjection(pMtx, GX_ORTHOGRAPHIC);
+    GXSetViewport(0.0F, 0.0F, (f32)scrWidth, (f32)scrHeight, 0.0F, 1.0F);
+	GXSetScissor(0, 0, (u32)scrWidth, (u32)scrHeight);
+	
+    GXSetCurrentMtx(GX_IDENTITY);
+
+    // Framebuffer
+    GXSetZMode(GX_ENABLE, GX_ALWAYS, GX_DISABLE);
+    GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_CLEAR);
+    GXSetColorUpdate(GX_TRUE);
+    GXSetAlphaUpdate(GX_FALSE);
+    GXSetDispCopyGamma(GX_GM_1_0);
+	
+	// Color channels
+    GXSetNumChans(0);
+
+    // Texture coord generation
+    GXSetNumTexGens(2);
+    GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY);
+
+    // Texture cache
+    GXInvalidateTexAll();
+
+    // Vertex formats
+    GXClearVtxDesc();
+    GXSetVtxDesc(GX_VA_POS,  GX_DIRECT);
+    GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
+    GXSetVtxDesc(GX_VA_TEX1, GX_DIRECT);
+	
+    GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_POS,  GX_POS_XYZ, GX_S16, 0);
+    GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_TEX0, GX_TEX_ST,  GX_U16, 0);
+    GXSetVtxAttrFmt(GX_VTXFMT7, GX_VA_TEX1, GX_TEX_ST,  GX_U16, 0);
+
+	// Setup TEV environment to perform color space conversion.
+	// The function will return the number of TEV stages need and will
+	// use the following HW resources:
+	//
+	//	GX_TEVPREV
+	//	GX_TEVREG0
+	//	GX_TEVREG1
+	//	GX_TEVREG2
+	//
+	//	GX_KCOLOR0
+	//	GX_KCOLOR1
+	//	GX_KCOLOR2
+	//	GX_KCOLOR3
+	//
+	// plus everything visible in this source
+	//
+	GXSetNumTevStages(VIDSetupTEV(VID_YUVCONV_HIGHPRECISION));
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Draw a textured polygon using the decoded image a texture.
+ *
+ * \param x
+ *		xpos of polygon on screen
+ * \param y
+ *		ypos of polygon on screen
+ * \param polygonWidth
+ *		width of polygon to draw
+ * \param polygonHeight
+ *		height of polygon to draw
+ * \param image
+ *		ptr to VIDImage containing the required YUV pointers
+ ******************************************************************************
+ */
+void VIDDrawGXYuv2RgbDraw(s16 x, s16 y, s16 polygonWidth, s16 polygonHeight, const VIDImage* image)
+	{
+	u16 textureWidth2, textureHeight2;
+	GXTexObj tobj0, tobj1, tobj2;
+	
+	textureWidth2 = (u16)(image->texWidth >> 1);
+	textureHeight2 = (u16)(image->texHeight >> 1);
+	
+	// Y Texture
+	GXInitTexObj(&tobj0, image->y, image->texWidth, image->texHeight,
+                 GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
+	GXInitTexObjLOD(&tobj0, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj(&tobj0, GX_TEXMAP0);
+
+	// Cb Texture
+	GXInitTexObj(&tobj1, image->u, textureWidth2, textureHeight2,
+                 GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
+	GXInitTexObjLOD(&tobj1, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj(&tobj1, GX_TEXMAP1);
+
+	// Cr Texture
+	GXInitTexObj(&tobj2, image->v, textureWidth2, textureHeight2,
+                 GX_TF_I8, GX_CLAMP, GX_CLAMP, GX_FALSE);
+	GXInitTexObjLOD(&tobj2, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj(&tobj2, GX_TEXMAP2);
+
+	GXSetTexCoordScaleManually(GX_TEXCOORD0, GX_ENABLE, 1, 1);
+	GXSetTexCoordScaleManually(GX_TEXCOORD1, GX_ENABLE, 1, 1);
+
+	// Draw a textured quad
+	GXBegin(GX_QUADS, GX_VTXFMT7, 4);
+		GXPosition3s16(x, y, 0);
+		GXTexCoord2u16(0, 0);
+		GXTexCoord2u16(0, 0);
+		
+		GXPosition3s16((s16)(x+polygonWidth), y, 0);
+		GXTexCoord2u16(image->width, 0);
+		GXTexCoord2u16((u16)(image->width>>1), 0);
+
+		GXPosition3s16((s16)(x+polygonWidth), (s16)(y+polygonHeight), 0);
+		GXTexCoord2u16(image->width, image->height);
+		GXTexCoord2u16((u16)(image->width>>1), (u16)(image->height>>1));
+		
+		GXPosition3s16(x, (s16)(y+polygonHeight), 0);
+		GXTexCoord2u16(0, image->height);
+		GXTexCoord2u16(0, (u16)(image->height>>1));
+	GXEnd();
+
+	GXSetTexCoordScaleManually(GX_TEXCOORD0, GX_DISABLE, 1, 1);
+	GXSetTexCoordScaleManually(GX_TEXCOORD1, GX_DISABLE, 1, 1);
+	
+	}
+
+/******************************************************************************
+ *  DEFINES
+ ******************************************************************************/
+
+#define	VID_AUDIO_READAHEADFRAMES	8			// Number of video frames worth of audio data that should be able to be stored prio to being routed into the AI buffer
+												// (the data stream will contain MORE data than needed at times -- esspecially at the beginning of the stream.
+												//  Hence an intermediate buffer is needed!)
+
+#define	VID_AUDIO_NUMLEADFRAMES		4			// Number of lead in frames for audio in data file (VIDCONV default)
+#define	VID_AUDIO_LEADFACTOR		1.5f		// Lead in data ratio factor in lead in frames (VIDCONV default)
+
+#define	VID_AUDIO_AIBUFFERSAMPLES	(2*256)		// 10.6ms of 48KHz data per AI buffer (32 byte multiple), that'll be about 15.9ms at 32Khz
+#define	VID_AUDIO_NUMAIBUFFERS		2			// Number of AI playback buffers (this has an impact on audio latency. 2 is the minimum needed)
+#define VID_AUDIO_NUMAIREQUESTS		16
+
+#define	AX_ARAM_BUFFER_SIZE			(VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS)
+
+//#define	AX_ARAM_LEFT_CHANNEL		0x200000	// @ 4MB (16-Bit addressing for DSP!)
+#define	AX_ARAM_LEFT_CHANNEL		( NxNgc::EngineGlobals.aram_stream0 >> 1 )
+#define	AX_ARAM_RIGHT_CHANNEL		(AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE)
+
+/******************************************************************************
+ *  LOCAL VARIABLES & LOCAL EXTERNAL REFERENCES
+ ******************************************************************************/
+
+extern VidSimplePlayer	player;										// Player instance
+
+static void				*audioReadBuffer;							// Buffer to store audio data received from the data stream
+static u32				audioReadBufferNumSamples;					// Size of the above buffer in samples
+static u32				audioReadBufferWritePos;					// Write position in the above buffer in samples
+static u32				audioReadBufferReadPos;						// Read position in the above buffer in samples
+static u8				audioReadBufferNumChannels;					// Number of channels stored in the read buffer
+
+static void				*audioPlayBuffer[VID_AUDIO_NUMAIBUFFERS];	// AI playback buffers
+static u8				audioPlayBufferWriteIndex;					// Index to next AI buffer to be written to from the read buffer
+static u32				audioPlayBufferFrq;							// Playback frequency of AI in Hz
+static volatile BOOL	audioPlayBufferEnabled;						// TRUE if playback is enabled. AI will operate at all times, but zeros will be filled in instead of real data if this is FALSE.
+static volatile BOOL	audioPlayBackPossible;						// TRUE if read buffer is initialized and playback may start
+																	// Normally this should be 1. It should never be greater or equal to the number of AI buffers.
+
+static void (*VIDSimpleDoAudioDecode)(s16* dest,u32 channels,const s16** samples,u32 sampleOffset,u32 sampleNum); // vector to the current decoder function
+static u32 (*VIDSimpleAudioBytesFromSamples)(u32 samples);					// vector to the current bytes to samples conversion function
+static u32 (*VIDSimpleAudioSamplesFromBytes)(u32 bytes);					// vector to the current samples to bytes conversion function
+
+static const u32		*audioPlayMaskArray;						// Pointer to an array of channel play masks (each set bit signals an active channel)
+static u32				audioNumPlayMasks;							// Number of play masks specified (0 indicates all channels should be active)
+static u32				audioNumActiveVoices;						// Number of active voices
+
+static AXVPB			*axVoice[2];								// AX voice structures
+static ARQRequest		arqRequest[2][VID_AUDIO_NUMAIREQUESTS];		// Enough ARQ request structures for worst case scenario
+static u32				axLastAddr;									// Last known address DSP read from for 1st voice
+static u32				axPlayedSamples;							// Number of samples played on first voice since last update
+static f32				axCurrentFrq;								// Current frequency used for playback
+static u32				axMinAvailFrames;							// Minimum number of frames available in the read buffer at which we are still "happy" and play at the specified frequency			
+
+typedef enum VID_AXPHASE {
+		AX_PHASE_STARTUP = 0,
+		AX_PHASE_START,
+		AX_PHASE_PLAY
+} VID_AXPHASE;
+
+static VID_AXPHASE		axPhase;
+
+/******************************************************************************
+ *  LOCAL PROTOTYPES
+ ******************************************************************************/
+
+static void AXCallback(void);
+
+static void VIDSimpleDoAudioDecodePCM16(s16* dest,u32 channels,const s16** samples,u32 sampleOffset,u32 sampleNum);
+static u32 VIDSimpleAudioBytesFromSamplesPCM16(u32 samples);
+static u32 VIDSimpleAudioSamplesFromBytesPCM16(u32 bytes);
+
+static void VIDSimpleDoAudioDecodeVAUD(s16* dest,u32 channels,const s16** samples,u32 sampleOffset,u32 sampleNum);
+static u32 VIDSimpleAudioBytesFromSamplesVAUD(u32 samples);
+static u32 VIDSimpleAudioSamplesFromBytesVAUD(u32 bytes);
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Initialize audio decoder
+ *
+ *		This function allocates all neccessary memory for the audio processing
+ *		and sets the audio decoder into an idle state, waiting for first data.
+ *		A file must be opened with the VIDSimplePlayer before calling this
+ *		function.
+ *
+ * \param cbAlloc
+ *		Pointer to a memory allocation function to be used
+ *
+ * \return
+ *		TRUE if any problem was detected
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleInitAudioDecoder(void)
+	{
+	u32	i;
+	BOOL old;
+
+	AXPBMIX	  axMix[2];
+	AXPBVE    axVE;
+	AXPBSRC	  axSRC;
+	AXPBADDR  axAddr;
+	AXPBADPCM axADPCM;
+	u32		  ratio;
+
+	// Do we have any audio data?
+	if (player.audioInfo.audioID != VID_FCC('N','O','N','E'))
+		{
+		// VAUD?
+		if (player.audioInfo.audioID == VID_FCC('V','A','U','D'))
+			{
+			// Calculate buffer size to allocate proper memry to keep a bit of "extra" audio data around...
+			audioReadBufferNumSamples = (u32)(((f32)VID_AUDIO_READAHEADFRAMES * player.audioInfo.vaud.frq) / player.fps);
+			audioReadBufferNumChannels = player.audioInfo.vaud.numChannels <= 2 ? player.audioInfo.vaud.numChannels : 2;
+
+			// Setup decoder & conversion functions to be used
+			VIDSimpleDoAudioDecode = VIDSimpleDoAudioDecodeVAUD;
+			VIDSimpleAudioBytesFromSamples = VIDSimpleAudioBytesFromSamplesVAUD;
+			VIDSimpleAudioSamplesFromBytes = VIDSimpleAudioSamplesFromBytesVAUD;
+			}
+		else
+			{
+			// PCM16?
+	        if (player.audioInfo.audioID == VID_FCC('P','C','1','6'))
+				{
+		        // Calculate buffer size to allocate proper memry to keep a bit of "extra" audio data around...
+		        audioReadBufferNumSamples = (u32)(((f32)VID_AUDIO_READAHEADFRAMES * player.audioInfo.pcm16.frq) / player.fps);
+        
+		        if (player.audioInfo.pcm16.numChannels <= 2)
+			        audioReadBufferNumChannels = player.audioInfo.pcm16.numChannels;
+		        else
+			        audioReadBufferNumChannels = 2;
+		        
+		        // Setup decoder & conversion functions to be used
+		        VIDSimpleDoAudioDecode = VIDSimpleDoAudioDecodePCM16;
+		        VIDSimpleAudioBytesFromSamples = VIDSimpleAudioBytesFromSamplesPCM16;
+		        VIDSimpleAudioSamplesFromBytes = VIDSimpleAudioSamplesFromBytesPCM16;
+		        }
+	        else
+		        {
+		        if (player.audioInfo.audioID == VID_FCC('A','P','C','M'))
+			        {
+			        // [...]
+			        }
+		        else
+			        {
+			        // Other audio codecs might be implemented here
+			        // (the idea being to decode the data into the audioReadBuffer allocated below)
+			        // [...]
+			        ASSERT(FALSE);
+			        }
+		        }
+			}
+			
+		// Allocate read buffer
+		audioReadBuffer = player.cbAlloc(audioReadBufferNumSamples * sizeof(s16) * player.audioInfo.pcm16.numChannels);
+		if (audioReadBuffer == NULL)
+			return TRUE;					// error
+		
+		// Reset ring buffer
+		audioReadBufferReadPos = audioReadBufferWritePos = 0;
+		
+		// What AI frquency is best?
+		audioPlayBufferFrq = player.audioInfo.pcm16.frq;
+		
+		// Allocate AI playback buffer
+		audioPlayBuffer[0] = player.cbAlloc(2 * sizeof(s16) * VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS);
+		if (audioPlayBuffer[0] == NULL)
+			return TRUE;					// error
+		
+		for(i=1; i> 16);
+		axSRC.ratioLo = (u16)ratio;
+		
+		axCurrentFrq = (f32)audioPlayBufferFrq;
+
+		// Calculate what we deem is a save amount of extra data in our read buffers
+		axMinAvailFrames = (u32)((audioPlayBufferFrq * (VID_AUDIO_LEADFACTOR - 1.0f) * ((f32)VID_AUDIO_NUMLEADFRAMES / player.fps)) / (f32)VID_AUDIO_AIBUFFERSAMPLES);
+		
+		AXSetVoiceSrcType(axVoice[0],AX_SRC_TYPE_4TAP_16K);
+		AXSetVoiceSrc(axVoice[0],&axSRC);
+		AXSetVoiceSrcType(axVoice[1],AX_SRC_TYPE_4TAP_16K);
+		AXSetVoiceSrc(axVoice[1],&axSRC);
+		
+        *(u32 *)&axAddr.currentAddressHi = AX_ARAM_LEFT_CHANNEL;
+		*(u32 *)&axAddr.loopAddressHi = AX_ARAM_LEFT_CHANNEL;
+		*(u32 *)&axAddr.endAddressHi = AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE - 1;
+		axAddr.format = AX_PB_FORMAT_PCM16;
+		axAddr.loopFlag = AXPBADDR_LOOP_ON;
+		AXSetVoiceAddr(axVoice[0],&axAddr);
+		
+		*(u32 *)&axAddr.currentAddressHi = AX_ARAM_RIGHT_CHANNEL;
+		*(u32 *)&axAddr.loopAddressHi = AX_ARAM_RIGHT_CHANNEL;
+		*(u32 *)&axAddr.endAddressHi = AX_ARAM_RIGHT_CHANNEL + AX_ARAM_BUFFER_SIZE - 1;
+		AXSetVoiceAddr(axVoice[1],&axAddr);
+
+		memset(&axADPCM,0,sizeof(axADPCM));
+		axADPCM.gain = 0x0800;
+		
+		AXSetVoiceAdpcm(axVoice[0],&axADPCM);
+		AXSetVoiceAdpcm(axVoice[1],&axADPCM);
+		
+		AXSetVoiceType(axVoice[0],AX_PB_TYPE_STREAM);
+		AXSetVoiceType(axVoice[1],AX_PB_TYPE_STREAM);
+		
+		AXRegisterCallback(AXCallback);
+		
+		axLastAddr = AX_ARAM_LEFT_CHANNEL;
+		axPlayedSamples = VID_AUDIO_AIBUFFERSAMPLES * VID_AUDIO_NUMAIBUFFERS;
+		axPhase = AX_PHASE_STARTUP;
+		
+		// All is setup for the voices. We'll start them inside the AX callback as soon as we got data in the ARAM buffers
+        OSRestoreInterrupts(old);
+		}
+		
+	return FALSE;			// no error
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Shutdown audio decoder and free resources
+ *
+ * \param cbFree
+ *		Pointer to a fucntion to be used to free the allocated memory
+ *
+ ******************************************************************************
+ */
+void VIDSimpleExitAudioDecoder(void)
+	{
+	// Any audio?
+	if (player.audioInfo.audioID != VID_FCC('N','O','N','E'))
+		{
+		// Yes. Unregister callback & stop AI DMA
+		BOOL old = OSDisableInterrupts();
+		
+		AXRegisterCallback(NULL);
+		AXSetVoiceState(axVoice[0],AX_PB_STATE_STOP);
+		AXSetVoiceState(axVoice[1],AX_PB_STATE_STOP);
+		AXFreeVoice(axVoice[0]);
+		AXFreeVoice(axVoice[1]);
+		
+		axVoice[0] = axVoice[1] = NULL;
+		
+		OSRestoreInterrupts(old);
+		
+		// Any codec related resources should be freed, too
+		// [...]
+		
+		// Free allocated resources...
+		player.cbFree(audioPlayBuffer[0]);
+		player.cbFree(audioReadBuffer);
+		}
+	}
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Stop audio playback without shutting down AI etc.
+ *
+ ******************************************************************************
+ */
+void VIDSimpleAudioReset(void)
+	{
+	BOOL	old;
+	
+	if (player.audioInfo.audioID != VID_FCC('N','O','N','E'))
+		{
+		old = OSDisableInterrupts();
+		
+		audioReadBufferWritePos = 0;
+		audioReadBufferReadPos = 0;
+		
+		audioPlayBufferEnabled = FALSE;
+		audioPlayBackPossible = FALSE;
+		
+		// ADPCM?
+		if (player.audioInfo.audioID != VID_FCC('A','P','C','M'))
+			{
+			// [...]
+			}
+		else
+			{
+			// Other codecs could reset their state right here...
+			// [...]
+			}
+			
+		OSRestoreInterrupts(old);
+		}
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Return some information about current audio stream.
+ *
+ ******************************************************************************
+ */
+void VIDSimpleAudioGetInfo(VidAUDH* audioHeader)
+	{
+	ASSERT(audioHeader);
+	memcpy(audioHeader, &player.audioInfo, sizeof(*audioHeader));
+    }
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		'Decode' PCM16 to PCM16
+ *
+ *		This function simply copies over new PCM16 samples into the read buffer.
+ *		Other codecs might use a more elaborate decoding of course ;-)
+ *
+ ******************************************************************************
+ */
+static void VIDSimpleDoAudioDecodePCM16(s16* dest, u32 channels, const s16** samples, u32 sampleOffset, u32 sampleNum)
+	{
+	u32		i,b,s;
+    u32 		bytes  = VIDSimpleAudioBytesFromSamplesPCM16(sampleNum);
+	const void*	src    = (const u8*)samples[0] + VIDSimpleAudioBytesFromSamplesPCM16(sampleOffset);
+	
+	// Do we have any playback masks?
+	if (audioNumPlayMasks != 0)
+		{
+		// Yes! How many samples?
+		u32 numSamples = bytes / (player.audioInfo.pcm16.numChannels * sizeof(s16));
+		
+		// Scan through all playback masks and update channels as they come
+		u32 c = 0;
+		u32 rc = 0;
+		for(i=0; i= audioReadBufferReadPos)
+		{
+		freeSamples = audioReadBufferNumSamples - (audioReadBufferWritePos - audioReadBufferReadPos);
+		
+		if (freeSamples < sampleNum)
+			{
+			OSRestoreInterrupts(old);
+			#ifndef FINAL
+			OSReport("*** audioDecode: overflow\n");
+			#endif
+			return FALSE;				// overflow!
+			}
+		
+		// We might have a two buffer update to do. Check for it...
+		if ((len1 = (audioReadBufferNumSamples - audioReadBufferWritePos)) >= sampleNum)
+			{
+			// No. We got ourselfs a nice, simple single buffer update.
+			VIDSimpleDoAudioDecode((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,sampleNum);
+			}
+		else
+			{
+			// Dual buffer case
+			VIDSimpleDoAudioDecode((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,len1);
+			VIDSimpleDoAudioDecode((s16 *)audioReadBuffer,numChannels,samples,len1,sampleNum-len1);
+			}
+		}
+	else
+		{
+		freeSamples = audioReadBufferReadPos - audioReadBufferWritePos;
+		
+		if (freeSamples < sampleNum)
+			{
+			OSRestoreInterrupts(old);
+			#ifndef FINAL
+			OSReport("*** audioDecode: overflow\n");
+			#endif
+			return FALSE;				// overflow!
+			}
+            // We're save to assume to have a single buffer update in any case...
+			VIDSimpleDoAudioDecode((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,sampleNum);
+		}
+
+	// Advance write position...
+	audioReadBufferWritePos += sampleNum;
+		
+	if (audioReadBufferWritePos >= audioReadBufferNumSamples)
+		audioReadBufferWritePos -= audioReadBufferNumSamples;
+		
+	// We're done with all critical stuff. IRQs may be enabled again...
+	OSRestoreInterrupts(old);
+		
+	return TRUE;
+	}
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Receive data from bitstream and direct to required encoding facility
+ *
+ *		This function will receive the AUDD chunck from the VIDSimplePlayer's
+ *		decode function. 
+ *
+ * \param bitstream
+ *		Pointer to the data of a AUDD chunk
+ *
+ * \param bitstreamLen
+ *		Length of the data in the AUDD chunk pointed to by above pointer
+ *
+ * \return
+ *		FALSE if data could not be interpreted properly
+ *
+ ******************************************************************************
+ */
+BOOL VIDSimpleAudioDecode(const u8* bitstream, u32 bitstreamLen)
+	{
+	// Any audio data present?
+	if (player.audioInfo.audioID != VID_FCC('N','O','N','E'))
+		{
+        // Select requested audio decoding method
+        if(player.audioInfo.audioID == VID_FCC('V','A','U','D'))
+			{
+			u32 headerSize = ((VidAUDDVAUD*)bitstream)->size;
+			// a channel mask if 0x3 selects the first two channels...
+			if(!VIDAudioDecode(player.decoder, bitstream+headerSize, bitstreamLen-headerSize, 0x3, audioDecode, NULL))
+				return FALSE;
+			
+			}
+		else
+			{
+			// Calc data and call audio decode callback by ourself
+			const u8* data = bitstream + sizeof(u32);
+			u32 dataLength = *(const u32 *)bitstream;
+			
+			// We always assume 1 source data pointer
+			if(!audioDecode(1, (const s16**)&data, VIDSimpleAudioSamplesFromBytes(dataLength), NULL))
+				return FALSE;
+
+			}
+		audioPlayBackPossible = TRUE;
+		}
+	return TRUE;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Enable AI playback (within about 5ms)
+ *
+ ******************************************************************************
+ */
+				
+void VIDSimpleAudioStartPlayback(const u32 *playMaskArray,u32 numMasks)
+	{
+	BOOL old = OSDisableInterrupts();
+	
+	if (audioPlayBackPossible)
+		{
+		audioPlayBufferEnabled = TRUE;
+		
+		VIDSimpleAudioChangePlayback(playMaskArray,numMasks);
+		}
+	
+	OSRestoreInterrupts(old);
+	}
+	
+/*!
+ ******************************************************************************
+ * \brief
+ *		Change the active audio channels
+ *
+ ******************************************************************************
+ */
+				
+void VIDSimpleAudioChangePlayback(const u32 *playMaskArray,u32 numMasks)
+	{
+	u32		i,b;
+	BOOL	old = OSDisableInterrupts();
+	
+	audioPlayMaskArray = playMaskArray;
+	audioNumPlayMasks = numMasks;
+	
+	// Any playback mask specified?
+	if (audioNumPlayMasks != 0)
+		{
+		// Yes. Count the active voices...
+		audioNumActiveVoices = 0;
+		
+		for(i=0; i= audioReadBufferNumSamples)
+		audioReadBufferReadPos -= audioReadBufferNumSamples;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		AX callback
+ *
+ ******************************************************************************
+ */
+static void AXCallback(void)
+	{
+	u32		availSamples,availFrames,numUpdate,i,n;
+	u32		audioPlayBufferNeededAudioFrames;
+	u32		currentAddr;
+	u32		leftSource, rightSource, leftTarget, rightTarget;
+
+	// First thing to do here is call the regular soundfx callback.
+	Sfx::AXUserCBack();
+	
+	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+	
+	if (axPhase == AX_PHASE_START)
+	{
+		AXSetVoiceState(axVoice[0],AX_PB_STATE_RUN);
+		AXSetVoiceState(axVoice[1],AX_PB_STATE_RUN);
+		axPhase = AX_PHASE_PLAY;
+	}
+	else if( axPhase == AX_PHASE_PLAY )
+	{
+		if( axVoice[0]->pb.state == AX_PB_STATE_STOP )
+		{
+			return;
+		}
+	}
+		
+	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+	
+	currentAddr = *(u32 *)&axVoice[0]->pb.addr.currentAddressHi;
+
+	if (currentAddr >= axLastAddr)
+		axPlayedSamples += currentAddr - axLastAddr;
+	else
+		axPlayedSamples += ((AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE) - axLastAddr) + (currentAddr - AX_ARAM_LEFT_CHANNEL);
+		
+	axLastAddr = currentAddr;
+
+	//OSReport("%d\n",axPlayedSamples);
+		
+	if (axPlayedSamples >= VID_AUDIO_AIBUFFERSAMPLES)
+	{
+		audioPlayBufferNeededAudioFrames = axPlayedSamples / VID_AUDIO_AIBUFFERSAMPLES;
+
+		// Make sure that we never get an underrun we don't notice...
+		if(!(audioPlayBufferNeededAudioFrames <= VID_AUDIO_NUMAIBUFFERS))
+		{
+//			OSReport("AX audio buffer underrun!\n");
+			audioPlayBufferEnabled = false;
+		}
+		
+		//ASSERT(audioPlayBufferNeededAudioFrames <= VID_AUDIO_NUMAIBUFFERS);
+
+		// Is actual audio playback enabled?
+		if (audioPlayBufferEnabled)
+		{
+			// How many samples could we get from the read buffer?
+			if (audioReadBufferWritePos >= audioReadBufferReadPos)
+				availSamples = audioReadBufferWritePos - audioReadBufferReadPos;
+			else
+				availSamples = audioReadBufferNumSamples - (audioReadBufferReadPos - audioReadBufferWritePos);
+			
+			// That's how many audio frames?
+			availFrames = availSamples / VID_AUDIO_AIBUFFERSAMPLES;
+    
+			// Are the voice already playing?
+			if (axPhase == AX_PHASE_PLAY)
+			{
+				f32 targetFrq;
+				
+				// Yes. We better watch out for the buffer status, so we don't get under-runs...
+				if (availFrames < axMinAvailFrames)
+					targetFrq = audioPlayBufferFrq * (1.0f - (1.0f/200.0f) * (axMinAvailFrames - availFrames));
+				else
+					targetFrq = (f32)audioPlayBufferFrq;
+				
+				// Track the target frequency quite slowly to avoid audible artifacts
+                if (axCurrentFrq < targetFrq)
+				{
+					axCurrentFrq += audioPlayBufferFrq * 0.00125f;
+					if (axCurrentFrq > targetFrq)
+						axCurrentFrq = targetFrq;
+				}
+				else
+				{
+					axCurrentFrq -= audioPlayBufferFrq * 0.00125f;
+					if (axCurrentFrq < targetFrq)
+						axCurrentFrq = targetFrq;
+				}
+				
+				//OSReport("%d %f (%d)\n",availFrames,axCurrentFrq,axMinAvailFrames);
+					
+				// Set frequency
+				AXSetVoiceSrcRatio(axVoice[0],axCurrentFrq / (f32)AX_IN_SAMPLES_PER_SEC);
+				AXSetVoiceSrcRatio(axVoice[1],axCurrentFrq / (f32)AX_IN_SAMPLES_PER_SEC);
+			}
+
+			//OSReport("AX: %d %d (%d)\n",availSamples,audioPlayBufferNeededAudioFrames * VID_AUDIO_AIBUFFERSAMPLES,axPlayedSamples);
+	
+			// So, how many can we update?
+			numUpdate = (availFrames > audioPlayBufferNeededAudioFrames) ? audioPlayBufferNeededAudioFrames : availFrames;
+	
+			// If anything... go do it!
+			if (numUpdate != 0)
+			{
+				axPlayedSamples -= numUpdate * VID_AUDIO_AIBUFFERSAMPLES;
+				
+				// Perform updates on each AI buffer in need of data...
+				for(i=0; iviYOrigin = 0;
+			}
+			break;
+		default:
+			break;
+	}
+	VISetBlack( TRUE );
+    VIConfigure(rmode);
+    VIFlush();
+    VIWaitForRetrace();
+
+	g_legal = false;
+
+	unsigned short last = ( padData[0].button | padData[1].button );
+
+	Pcm::StopMusic();
+
+#	ifndef DVDETH
+//	GXRenderModeObj *rmode;	//, demoMode;
+	u32		videoWidth, videoHeight;
+	s32		surfaceX, surfaceY;
+	s32		surfaceWidth, surfaceHeight;
+//	u16 	buttonsDown;
+	f64		totalTime = 0.0;
+	f64		idealTime = 0.0;
+	f32		idealTimeInc;
+	BOOL	first = TRUE;
+	BOOL 	xfbMode = FALSE;
+
+#	ifdef USE_DIRECT_XFB
+	xfbMode = TRUE;
+#	endif
+
+	OSInitFastCast();
+
+
+
+	// Reset a bunch of GX state.
+
+	// Color definitions
+	
+	#define GX_DEFAULT_BG (GXColor){64, 64, 64, 255}
+	#define BLACK (GXColor){0, 0, 0, 0}
+	#define WHITE (GXColor){255, 255, 255, 255}
+	
+	//
+	// Render Mode
+	//
+	// (set 'rmode' based upon VIGetTvFormat(); code not shown)
+	
+	//
+	// Geometry and Vertex
+	//
+	GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX2, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD3, GX_TG_MTX2x4, GX_TG_TEX3, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD4, GX_TG_MTX2x4, GX_TG_TEX4, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD5, GX_TG_MTX2x4, GX_TG_TEX5, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD6, GX_TG_MTX2x4, GX_TG_TEX6, GX_IDENTITY);
+	GXSetTexCoordGen(GX_TEXCOORD7, GX_TG_MTX2x4, GX_TG_TEX7, GX_IDENTITY);
+	GXSetNumTexGens(1);
+	GXSetCurrentMtx(GX_PNMTX0);
+	GXSetCullMode(GX_CULL_BACK);
+	GXSetClipMode(GX_CLIP_ENABLE);
+	GXSetNumChans(0); // no colors by default
+	
+	GXSetChanCtrl(
+	GX_COLOR0A0,
+	GX_DISABLE,
+	GX_SRC_REG,
+	GX_SRC_VTX,
+	GX_LIGHT_NULL,
+	GX_DF_NONE,
+	GX_AF_NONE );
+	
+	GXSetChanAmbColor(GX_COLOR0A0, BLACK);
+	GXSetChanMatColor(GX_COLOR0A0, WHITE);
+	
+	GXSetChanCtrl(
+	GX_COLOR1A1,
+	GX_DISABLE,
+	GX_SRC_REG,
+	GX_SRC_VTX,
+	GX_LIGHT_NULL,
+	GX_DF_NONE,
+	GX_AF_NONE );
+	
+	GXSetChanAmbColor(GX_COLOR1A1, BLACK);
+	GXSetChanMatColor(GX_COLOR1A1, WHITE);
+	
+	GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD2, GX_TEXMAP2, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD3, GX_TEXMAP3, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD4, GX_TEXMAP4, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE5, GX_TEXCOORD5, GX_TEXMAP5, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE6, GX_TEXCOORD6, GX_TEXMAP6, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE7, GX_TEXCOORD7, GX_TEXMAP7, GX_COLOR0A0);
+	GXSetTevOrder(GX_TEVSTAGE8, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetTevOrder(GX_TEVSTAGE9, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetTevOrder(GX_TEVSTAGE10,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetTevOrder(GX_TEVSTAGE11,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetTevOrder(GX_TEVSTAGE12,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetTevOrder(GX_TEVSTAGE13,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetTevOrder(GX_TEVSTAGE14,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetTevOrder(GX_TEVSTAGE15,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GXSetNumTevStages(1);
+	GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
+	GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
+	for ( int i = GX_TEVSTAGE0; i < GX_MAX_TEVSTAGE; i++) {
+	GXSetTevKColorSel((GXTevStageID) i, GX_TEV_KCSEL_1_4 );
+	GXSetTevKAlphaSel((GXTevStageID) i, GX_TEV_KASEL_1 );
+	GXSetTevSwapMode ((GXTevStageID) i, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+	}
+
+
+
+
+
+//	demoMode = GXNtsc480IntDf;
+//	demoMode.fbWidth   = 640; //512;
+//	
+//	_DEMOInit(&demoMode, xfbMode);
+//	
+//	// Init AI interface in case we got audio data
+//	if ( !AICheckInit() ) AIInit(NULL);
+//	if ( !ARCheckInit() ) ARInit(NULL,0);
+//	if ( !ARQCheckInit() ) ARQInit();
+//	AXInit();
+
+	// If we want to use the 'Locked Cache', we need to enable it here!
+	// The usage of 'Locked Cache' is optional, but speeds up decoding time a little bit
+	LCEnable();
+	lcMemBase = (u8*)LCGetBase();
+	
+	// In xfb mode, we need some memory for the conversion stuff.
+	// We offset our locked cache 'base addr' by the required number of bytes!
+	// NOTE: This mem is only used TEMPORARY during the VIDXFBDraw() function!
+	if(xfbMode)
+		{
+		xfbLCStart = lcMemBase;
+		lcMemBase += VIDXFBGetLCSize();
+		}
+
+//	rmode = DEMOGetRenderModeObj();
+
+	VIDSimpleInit(myAlloc, myFree, myAllocFromLC);
+
+//	if (VIDSimpleOpen(VIDEO_FILENAME, FALSE) == FALSE)
+
+
+
+
+	// Incoming movie name is in the form movies\, convert to movies/vid\.vid.
+	char name_conversion_buffer[256] = "movies/vid/";
+	int length		= strlen( pName );
+	int backwards	= length;
+	while( backwards )
+	{
+		if( pName[backwards] == '\\' )
+		{
+			++backwards;
+			break;
+		}
+		--backwards;
+	}
+	strncpy( name_conversion_buffer + 11, pName + backwards, length - backwards );
+	length = strlen( name_conversion_buffer );
+	name_conversion_buffer[length] = '.';
+	name_conversion_buffer[length + 1] = 'v';
+	name_conversion_buffer[length + 2] = 'i';
+	name_conversion_buffer[length + 3] = 'd';
+	name_conversion_buffer[length + 4] = 0;
+
+
+
+
+
+
+
+	if (VIDSimpleOpen(name_conversion_buffer, FALSE) == FALSE) goto quit;
+//		OSHalt("*** VIDSimpleOpen failed!\n");
+
+	if (VIDSimpleAllocDVDBuffers() == FALSE)
+	{
+		VIDSimpleLoadStop();
+		VIDSimpleClose();
+
+        goto quit;
+	}
+//		OSHalt("*** VIDSimpleAllocDVDBuffers failed!\n");
+	
+	if (VIDSimpleCreateDecoder(TRUE) == FALSE)
+	{
+		VIDSimpleLoadStop();
+		VIDSimpleClose();
+		VIDSimpleFreeDVDBuffers();
+
+        goto quit;
+	}
+//		OSHalt("*** VIDSimpleCreateDecoder failed!\n");
+		
+	if (VIDSimpleInitAudioDecoder())
+	{
+		VIDSimpleLoadStop();
+		VIDSimpleClose();
+		VIDSimpleFreeDVDBuffers();
+		VIDSimpleDestroyDecoder();
+
+        goto quit;
+	}
+//		OSHalt("*** VIDSimpleInitAudioDecoder failed!\n");
+
+
+
+
+
+
+	// Preload all DVD buffers and set 'loop' mode
+//	VIDSimpleLoadStart(TRUE);
+	VIDSimpleLoadStart(FALSE);
+
+	VIDSimpleGetVideoSize(&videoWidth, &videoHeight);
+	
+	// Calculate width and height to be used to display movie
+	surfaceWidth = rmode->fbWidth;
+	surfaceHeight = (s32)(videoHeight * ((f32)rmode->fbWidth / (f32)videoWidth));
+	
+	// Get the pixels "square"
+	surfaceHeight = (s32)(surfaceHeight * (640.0f / (f32)rmode->fbWidth));	// assuming 480 vertical at all times!
+	
+	// Calculate offset to put it into the center of the screen
+	surfaceX = 0;
+	surfaceY = ((s32)rmode->xfbHeight - (s32)surfaceHeight) / 2;
+	if (surfaceY < 0)
+		surfaceY = 0;
+	
+//#ifdef MY_DEBUG
+//	OSReport("Resolution: %dx%d\n",videoWidth,videoHeight);
+//	OSReport("Display resolution: %dx%d\n",surfaceWidth,surfaceHeight);
+//	OSReport("Rate: %f fps\n",VIDSimpleGetFPS());
+//	OSReport("Frames: %d\n",VIDSimpleGetFrameCount());
+//	VIDSimpleAudioGetInfo(&header);
+//	if(header.audioID != VID_FCC('N','O','N','E'))
+//		{
+//		OSReport("Audio codec: %4s\n", &header.audioID);
+//		OSReport("Channels: %d\n", (u32)header.pcm16.numChannels);
+//		OSReport("Sample rate: %d Hz\n", header.pcm16.frq);
+//		}
+//#endif
+
+	idealTimeInc = 1000.0f / VIDSimpleGetFPS();
+
+		while (1)
+		{
+		OSTick startTicks;
+	
+		startTicks = OSGetTick();
+
+		VISetBlack( FALSE );
+		NsDisplay::begin();
+		NsRender::begin();
+//		_DEMOBeforeRender();
+		
+		// Decode one frame
+		if (VIDSimpleDecode() == FALSE)
+		{
+			// Decode failed, usually just due to the end of the movie being reached.
+			break;
+		}
+
+		// We deomstrate two different drawing methods
+		if(!xfbMode)
+		{
+			// Render decoded frame as texture. YUV conversion is handled by hardware.
+			VIDSimpleDraw(rmode, (u32)surfaceX, (u32)surfaceY, (u32)surfaceWidth, (u32)surfaceHeight);
+		}
+		
+		// Wait for frame buffer swap to be done (we might have triggered one in the last loop)
+//		_DEMOWaitFrameBuffer();
+		
+		if (xfbMode)
+			{
+			// Render image into the XFB immediately
+			VIDSimpleDrawXFB(rmode, xfbLCStart);
+			}
+			
+		s32 error;
+		error = DVDGetDriveStatus();
+		if ( ( error != DVD_STATE_END ) &&
+			 ( error != DVD_STATE_BUSY ) &&
+			 ( error != DVD_STATE_CANCELED ) &&
+			 ( error != DVD_STATE_PAUSING ) &&
+			 ( error != DVD_STATE_WAITING ) )
+
+
+			{
+				switch (VIGetTvFormat())
+				{
+					case VI_PAL:
+					case VI_DEBUG_PAL:
+						if ( !NxNgc::EngineGlobals.use_60hz )
+						{
+							rmode->viYOrigin = 23;
+						}
+						break;
+					default:
+						break;
+				}
+				VIConfigure(rmode);
+
+				// Stop audio, turn volume to 0.
+				AXSetVoiceState(axVoice[0],AX_PB_STATE_STOP);
+				AXSetVoiceState(axVoice[1],AX_PB_STATE_STOP);
+
+				hwGXInit();
+	
+				GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
+				GXSetColorUpdate(GX_ENABLE);
+				GXSetAlphaUpdate(GX_ENABLE);
+
+				NxNgc::EngineGlobals.screen_brightness = 1.0f;
+
+				// Display n
+				while ( 1 )
+				{
+					error = DVDGetDriveStatus();
+//					printf( "Error code: %d\n", error );
+
+					NsDisplay::doReset();
+
+					if ( error == DVD_STATE_END ) break;
+					if ( error == DVD_STATE_BUSY ) continue;
+					if ( error == DVD_STATE_CANCELED ) break;
+					if ( error == DVD_STATE_PAUSING ) continue;
+					if ( error == DVD_STATE_WAITING ) continue;
+
+					// Render the text.
+					NsDisplay::begin();
+					NsRender::begin();
+	
+					NsCamera cam;
+					cam.orthographic( 0, 0, rmode->fbWidth, 448 );
+	
+					// Draw the screen.
+					NsPrim::begin();
+	
+					cam.begin();
+	
+					GXSetZMode( GX_FALSE, GX_ALWAYS, GX_TRUE );
+	
+					NxNgc::set_blend_mode( NxNgc::vBLEND_MODE_BLEND );
+	
+		//			if ( NsDisplay::shouldReset() )
+		//			{
+		//				// Reset message.
+		//				Script::RunScript( "ngc_reset" );
+		//			}
+		//			else
+					{
+						// DVD Error message.
+						switch ( error )
+						{
+							case DVD_STATE_FATAL_ERROR:
+								Script::RunScript( "ngc_dvd_fatal" );
+								NxNgc::EngineGlobals.disableReset = true;
+								break;
+							case DVD_STATE_RETRY:
+								Script::RunScript( "ngc_dvd_retry" );
+								break;
+							case DVD_STATE_COVER_OPEN:
+								Script::RunScript( "ngc_dvd_cover_open" );
+								NxNgc::EngineGlobals.disableReset = false;
+								break;
+							case DVD_STATE_NO_DISK:
+								Script::RunScript( "ngc_dvd_no_disk" );
+								NxNgc::EngineGlobals.disableReset = false;
+								break;
+							case DVD_STATE_WRONG_DISK:
+								Script::RunScript( "ngc_dvd_wrong_disk" );
+								NxNgc::EngineGlobals.disableReset = false;
+								break;
+							default:
+								break;
+						}
+					}
+	
+					NsDisplay::setBackgroundColor( messageColor );
+	
+					cam.end();
+	
+					NsPrim::end();
+	
+					NsRender::end();
+					NsDisplay::end( true );
+				}
+
+				// Reset a bunch of GX state.
+			
+				NsDisplay::setBackgroundColor( (GXColor){0,0,0,0} );
+
+				// Color definitions
+				
+				#define GX_DEFAULT_BG (GXColor){64, 64, 64, 255}
+				#define BLACK (GXColor){0, 0, 0, 0}
+				#define WHITE (GXColor){255, 255, 255, 255}
+				
+				//
+				// Render Mode
+				//
+				// (set 'rmode' based upon VIGetTvFormat(); code not shown)
+				
+				//
+				// Geometry and Vertex
+				//
+				GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
+				GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX1, GX_IDENTITY);
+				GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX2, GX_IDENTITY);
+				GXSetTexCoordGen(GX_TEXCOORD3, GX_TG_MTX2x4, GX_TG_TEX3, GX_IDENTITY);
+				GXSetTexCoordGen(GX_TEXCOORD4, GX_TG_MTX2x4, GX_TG_TEX4, GX_IDENTITY);
+				GXSetTexCoordGen(GX_TEXCOORD5, GX_TG_MTX2x4, GX_TG_TEX5, GX_IDENTITY);
+				GXSetTexCoordGen(GX_TEXCOORD6, GX_TG_MTX2x4, GX_TG_TEX6, GX_IDENTITY);
+				GXSetTexCoordGen(GX_TEXCOORD7, GX_TG_MTX2x4, GX_TG_TEX7, GX_IDENTITY);
+				GXSetNumTexGens(1);
+				GXSetCurrentMtx(GX_PNMTX0);
+				GXSetCullMode(GX_CULL_BACK);
+				GXSetClipMode(GX_CLIP_ENABLE);
+				GXSetNumChans(0); // no colors by default
+				
+				GXSetChanCtrl(
+				GX_COLOR0A0,
+				GX_DISABLE,
+				GX_SRC_REG,
+				GX_SRC_VTX,
+				GX_LIGHT_NULL,
+				GX_DF_NONE,
+				GX_AF_NONE );
+				
+				GXSetChanAmbColor(GX_COLOR0A0, BLACK);
+				GXSetChanMatColor(GX_COLOR0A0, WHITE);
+				
+				GXSetChanCtrl(
+				GX_COLOR1A1,
+				GX_DISABLE,
+				GX_SRC_REG,
+				GX_SRC_VTX,
+				GX_LIGHT_NULL,
+				GX_DF_NONE,
+				GX_AF_NONE );
+				
+				GXSetChanAmbColor(GX_COLOR1A1, BLACK);
+				GXSetChanMatColor(GX_COLOR1A1, WHITE);
+				
+				GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD2, GX_TEXMAP2, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD3, GX_TEXMAP3, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD4, GX_TEXMAP4, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE5, GX_TEXCOORD5, GX_TEXMAP5, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE6, GX_TEXCOORD6, GX_TEXMAP6, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE7, GX_TEXCOORD7, GX_TEXMAP7, GX_COLOR0A0);
+				GXSetTevOrder(GX_TEVSTAGE8, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetTevOrder(GX_TEVSTAGE9, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetTevOrder(GX_TEVSTAGE10,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetTevOrder(GX_TEVSTAGE11,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetTevOrder(GX_TEVSTAGE12,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetTevOrder(GX_TEVSTAGE13,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetTevOrder(GX_TEVSTAGE14,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetTevOrder(GX_TEVSTAGE15,GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+				GXSetNumTevStages(1);
+				GXSetTevOp(GX_TEVSTAGE0, GX_REPLACE);
+				GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0);
+				for ( int i = GX_TEVSTAGE0; i < GX_MAX_TEVSTAGE; i++) {
+				GXSetTevKColorSel((GXTevStageID) i, GX_TEV_KCSEL_1_4 );
+				GXSetTevKAlphaSel((GXTevStageID) i, GX_TEV_KASEL_1 );
+				GXSetTevSwapMode ((GXTevStageID) i, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+				// Restart audio, volume to full.
+				AXSetVoiceState(axVoice[0],AX_PB_STATE_RUN);
+				AXSetVoiceState(axVoice[1],AX_PB_STATE_RUN);
+
+				}
+
+				switch (VIGetTvFormat())
+				{
+					case VI_PAL:
+					case VI_DEBUG_PAL:
+						if ( !NxNgc::EngineGlobals.use_60hz )
+						{
+							rmode->viYOrigin = 0;
+						}
+						break;
+					default:
+						break;
+				}
+				VIConfigure(rmode);
+
+//			OSHalt("*** DVD error occured. A true DVD error handler is not part of this demo.\n");
+				startTicks = OSGetTick();
+			}
+			
+		// Final processing for this frame (copy out etc.)
+//		_DEMODoneRender();
+		NsRender::end();
+		NsDisplay::end();
+
+//		// handle GC controller
+//		DEMOPadRead();
+//		buttonsDown = DEMOPadGetButtonDown(0);
+//		if(buttonsDown & PAD_BUTTON_START)
+//			break;
+			
+		unsigned short current = ( padData[0].button | padData[1].button );
+		unsigned short press = ( current ^ last ) & current;
+		last = current;
+
+		if ( press & ( PAD_BUTTON_START | PAD_BUTTON_A ) ) break;
+
+		// Control timing
+		if (!first)
+			{
+			f64	dt;
+			idealTime += 1000.0 / VIDSimpleGetFPS();
+			do
+				{
+				dt = OSTicksToMilliseconds((f64)(OSGetTick() - startTicks));
+				}
+			while((totalTime + dt) < idealTime);
+			totalTime += dt;
+			}
+		else
+			first = FALSE;
+			
+		} // while (1)
+		
+	// free our ressources. Just as demonstration what you need to do for a 'clean' exit
+	VIDSimpleLoadStop();
+	VIDSimpleClose();
+
+	VIDSimpleFreeDVDBuffers();
+	VIDSimpleDestroyDecoder();
+	VIDSimpleExitAudioDecoder();
+
+quit:
+	NsDisplay::setBackgroundColor( (GXColor){0,0,0,0} );
+
+//	GXSetPixelFmt(/*GX_PF_RGB8_Z24*/GX_PF_RGBA6_Z24, GX_ZC_LINEAR);
+
+	GQRSetup6( GQR_SCALE_64,		// Pos
+			   GQR_TYPE_S16,
+			   GQR_SCALE_64,
+			   GQR_TYPE_S16 );
+	GQRSetup7( 14,		// Normal
+			   GQR_TYPE_S16,
+			   14,
+			   GQR_TYPE_S16 );
+
+	hwGXInit();
+
+	GXSetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
+	GXSetColorUpdate(GX_ENABLE);
+	GXSetAlphaUpdate(GX_ENABLE);
+
+	// Register our default AX callback.
+	AXRegisterCallback( Sfx::AXUserCBack );
+
+	LCDisable();
+
+#	ifdef MY_DEBUG
+	OSReport("VidPlayer done!\n");	
+#	endif	
+
+#	endif		// DVDETH
+
+	switch (VIGetTvFormat())
+	{
+		case VI_PAL:
+		case VI_DEBUG_PAL:
+			if ( !NxNgc::EngineGlobals.use_60hz )
+			{
+				rmode->viYOrigin = 23;
+			}
+			break;
+		default:
+			break;
+	}
+	VISetBlack( TRUE );
+    VIConfigure(rmode);
+    VIFlush();
+    VIWaitForRetrace();
+	VISetBlack( FALSE );
+}
+	
+
+bool Movie_Render ( void )
+{
+//	if ( !playing ) return false;
+//
+//	//
+//	// Decompress the Bink frame.
+//	//
+//	
+//	BinkDoFrame( Bink );
+//	
+//	//
+//	// Draw the next frame.
+//	//
+//	
+//	Show_frame( Bink );
+//	
+//	//
+//	// Keep playing the movie.
+//	//
+//	
+//	BinkNextFrame( Bink );
+//
+//	return true;
+
+	return false;
+}
+
+} // namespace Flx
+
+
+
+
diff --git a/Code/Gel/Movies/ngc/p_movies.h b/Code/Gel/Movies/ngc/p_movies.h
new file mode 100644
index 0000000..2f70d3b
--- /dev/null
+++ b/Code/Gel/Movies/ngc/p_movies.h
@@ -0,0 +1,43 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Movies													**
+**																			**
+**	File name:		gel/movies/ngc/p_movies.h 								**
+**																			**
+**	Created: 		8/27/01	-	dc											**
+**																			**
+*****************************************************************************/
+
+#ifndef __P_MOVIES_H
+#define __P_MOVIES_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Flx
+{
+
+void PMovies_PlayMovie( const char *pName );
+
+bool Movie_Render( void );
+
+} // namespace Flx
+
+#endif	// __P_MOVIES_H
+
+
+
diff --git a/Code/Gel/Music/Ngps/Bgm/PathDefs b/Code/Gel/Music/Ngps/Bgm/PathDefs
new file mode 100644
index 0000000..a40be87
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/PathDefs
@@ -0,0 +1,16 @@
+ AR      = C:/usr/local/sce/iop/gcc/bin/iop-elf-ar
+ AS      = C:/usr/local/sce/iop/gcc/mipsel-scei-elfl/bin/as
+ CC      = C:/usr/local/sce/iop/gcc/bin/iop-elf-gcc
+ GCC     = C:/usr/local/sce/iop/gcc/bin/iop-elf-gcc
+ LD      = C:/usr/local/sce/iop/gcc/mipsel-scei-elfl/bin/ld
+ NM      = C:/usr/local/sce/iop/gcc/bin/iop-elf-nm
+ SIZE    = C:/usr/local/sce/iop/gcc/bin/iop-elf-size
+ STRIP   = C:/usr/local/sce/iop/gcc/bin/iop-elf-strip
+ RANLIB  = C:/usr/local/sce/iop/gcc/bin/iop-elf-ranlib
+ OBJCOPY = C:/usr/local/sce/iop/gcc/bin/iop-elf-objcopy
+ OBJDUMP = C:/usr/local/sce/iop/gcc/bin/iop-elf-objdump
+ IFIXUP  = iopfixup
+ ILBGEN  = ioplibgen
+ ILBLD   = ioplibld
+ ILBDUMP = ioplibdump
+ BIN2OBJ = bin2elf
diff --git a/Code/Gel/Music/Ngps/Bgm/bgm_com.c b/Code/Gel/Music/Ngps/Bgm/bgm_com.c
new file mode 100644
index 0000000..b7a38d2
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/bgm_com.c
@@ -0,0 +1,144 @@
+// This code builds an IRX module that handles PCM streaming from
+// the CD to SPU2 Sound Data Input area...
+
+#include 
+#include 
+#include 
+#include "sif.h"
+#include "sifcmd.h"
+#include "sifrpc.h"
+#include 
+#include "bgm_i.h"
+
+int gRpcArg[16];	//--- Receiving channel for RPC arguments transferred from EE
+
+
+static void* bgmFunc(unsigned int fno, void *data, int size);
+
+extern int  BgmInit(int ch, int useCD);
+extern void BgmQuit(int channel, int status );
+extern int  BgmOpen( int ch, char* filename );
+extern void BgmClose(int ch, int status);
+extern void BgmCloseNoWait(int ch, int status);
+extern int  BgmPreLoad( int ch, int status );
+extern void BgmStart(int ch, int status);
+extern void BgmStop(int ch, int status);
+extern void BgmSeek( int ch, unsigned int vol );
+extern int BgmSetVolume( int ch, unsigned int vol );
+extern int BgmRaw2Spu( int ch, int which, int mode );
+extern int BgmSetVolumeDirect( int ch, unsigned int vol );
+// fuck this you big assbreath.
+//extern void BgmSetMasterVolume( int ch, unsigned int vol );
+extern unsigned int BgmGetMode( int ch, int status );
+//extern void BgmSetMode( int ch, u_int mode );
+extern void BgmSdInit(int ch, int status );
+
+
+/* ------------------------------------------------------------------------
+   Main thread for the ezbgm module.
+   After execution, initialize interrupt environment, register command, and
+   then wait until there is a request from the EE.
+   ------------------------------------------------------------------------ */
+int sce_bgm_loop()
+{
+	sceSifQueueData qd;
+	sceSifServeData sd;
+
+	//-- Initialize interrupt environment in advance.
+
+	CpuEnableIntr();
+	EnableIntr( INUM_DMA_4 );
+	EnableIntr( INUM_DMA_7 );
+	
+	//--- Register function that is called according to request
+
+
+	sceSifInitRpc(0);
+
+	sceSifSetRpcQueue( &qd, GetThreadId() );
+	sceSifRegisterRpc( &sd, EZBGM_DEV, bgmFunc, (void*)gRpcArg, NULL, NULL, &qd );
+	PRINTF(("goto bgm cmd loop\n"));
+
+	//--- Command-wait loop
+
+	sceSifRpcLoop(&qd);
+
+	return 0;
+}
+
+
+/* ------------------------------------------------------------------------
+   Function that is awakened by a request from the EE.
+   The argument is stored in *data.  The leading four bytes are reserved and are not used.
+   The return value of this function becomes the return value of the EE side's RPC.
+   When the argument is a structure, it is sent to gRpcData and used.
+   When a structure is returned to the EE, the value is sent to the address of the first argument (on the EE side).
+   ------------------------------------------------------------------------ */
+int ret = 0;
+
+static void* bgmFunc(unsigned int command, void *data, int size)
+{ 
+	int ch;
+
+//	asm volatile( "break 1");
+
+//	PRINTF(( " bgmfunc %x, %x, %x, %x\n", *((int*)data + 0), 
+//		*((int*)data + 1), *((int*)data + 2),*((int*)data + 3) ));
+
+	ch = command&0x000F;
+
+	switch( command&0xFFF0 )
+	{
+	case EzBGM_INIT:
+		ret = BgmInit( ch, *((int*)data) );
+		break;
+	case EzBGM_QUIT:
+		BgmQuit ( ch, *((int*)data) );
+		break;
+	case EzBGM_OPEN:
+		ret = BgmOpen ( ch, (char*)((int*)data) );
+		break;
+	case EzBGM_CLOSE:
+		BgmClose( ch, *((int*)data) );
+		break;
+	case EzBGM_CLOSE_NO_WAIT:
+		BgmCloseNoWait( ch, *((int*)data) );
+		break;
+	case EzBGM_PRELOAD:
+		ret = BgmPreLoad ( ch, *((int*)data) );
+		break;
+	case EzBGM_START:
+		BgmStart( ch, *((int*)data) );
+		break;
+	case EzBGM_STOP:
+		BgmStop( ch, *((int*)data) );
+		break;
+	case EzBGM_SEEK:
+		BgmSeek( ch, (unsigned int)*((int*)data) );
+		break;
+	case EzBGM_SETVOL:
+		ret = BgmSetVolume( ch, (unsigned int)*((int*)data) );
+		break;
+	case EzBGM_SETVOLDIRECT:
+		ret = BgmSetVolumeDirect( ch, (unsigned int)*((int*)data) );
+		break;
+	case EzBGM_GETMODE:
+		ret = BgmGetMode( ch, *((int*)data) );
+		break;
+	case EzBGM_SDINIT:
+		BgmSdInit( ch, *((int*)data) );
+		break;
+	default:
+		ERROR(("EzBGM driver error: unknown command %d \n", *((int*)data) ));
+		break;
+	}
+//	PRINTF(( "return value = %x \n", ret )); 
+	return (void*)(&ret);
+}
+
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
+
diff --git a/Code/Gel/Music/Ngps/Bgm/bgm_entr.c b/Code/Gel/Music/Ngps/Bgm/bgm_entr.c
new file mode 100644
index 0000000..402256a
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/bgm_entr.c
@@ -0,0 +1,49 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "bgm_i.h"
+
+ModuleInfo Module = {"pcmaudio_driver", 0x0102 };
+
+extern int sce_bgm_loop();
+extern volatile int gStThid;
+
+int create_th();
+
+int start ()
+{
+	struct ThreadParam param;
+	int th;
+
+	CpuEnableIntr();
+
+	if( ! sceSifCheckInit() )
+		sceSifInit();
+	sceSifInitRpc(0);
+
+	printf("Entering PCM Audio Driver\n");
+
+	param.attr         = TH_C;
+	param.entry        = sce_bgm_loop;
+	param.initPriority = BASE_priority-2;
+	param.stackSize    = 0x800;
+	param.option       = 0;
+	th = CreateThread(¶m);
+	if (th > 0) {
+		StartThread(th,0);
+		printf("Exit PCM Audio Driver\n");
+		return 0;
+	}else{
+		return 1;
+	}
+}
+
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
+
diff --git a/Code/Gel/Music/Ngps/Bgm/bgm_i.h b/Code/Gel/Music/Ngps/Bgm/bgm_i.h
new file mode 100644
index 0000000..aa752ab
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/bgm_i.h
@@ -0,0 +1,58 @@
+//--- Highest bit indicates presence of return value
+#define EzBGM_INIT           0x8000
+#define EzBGM_QUIT           0x0010
+#define EzBGM_OPEN           0x8020
+#define EzBGM_CLOSE          0x0030
+#define EzBGM_PRELOAD        0x0040
+#define EzBGM_START          0x0050
+#define EzBGM_STOP           0x0060
+#define EzBGM_SEEK           0x0070
+#define EzBGM_SETVOL         0x8080
+#define EzBGM_SETVOLDIRECT   0x8090
+#define EzBGM_CLOSE_NO_WAIT  0x0100
+//#define EzBGM_SETMASTERVOL 0x00a0
+#define EzBGM_GETMODE        0x80b0
+#define EzBGM_SETMODE        0x80c0
+#define EzBGM_SDINIT         0x00d0
+
+
+//-- SET AVAILABLE
+#define BGM_MODE_REPEAT_OFF      0x0000
+#define BGM_MODE_REPEAT_DEFAULT  0x0001
+#define BGM_MODE_REPEAT_FORCED   0x0002
+
+#define BGM_MODE_STEREO          0x0000
+#define BGM_MODE_MONO            0x0010
+
+//-- GET ONLY
+#define BGM_MODE_IDLE            0x0000
+#define BGM_MODE_RUNNING         0x1000
+#define BGM_MODE_PAUSE           0x2000
+//#define BGM_MODE_KICKSTART       0x4000
+#define BGM_MODE_TERMINATE       0x8000
+
+
+#define BGM_MASK_STATUS          0x0FFF
+#define BGM_MASK_REPEAT          0xFFF0
+#define BGM_MASK_STEREO          0xFF0F
+
+#define WAV_STEREO_BIT           ( 1 << 1 )
+
+
+/* ----------------------------------------------
+   module ID number
+  ----------------------------------------------- */
+#define EZBGM_DEV   0x000666
+
+
+#define PRINTF(x) printf x
+//#define PRINTF(x) 
+
+#define ERROR(x) printf x
+//#define ERROR(x) 
+
+#define BASE_priority  42
+
+#define OLDLIB 0
+#define TRANS_CH  0
+
diff --git a/Code/Gel/Music/Ngps/Bgm/bgm_play.c b/Code/Gel/Music/Ngps/Bgm/bgm_play.c
new file mode 100644
index 0000000..51f78b7
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/bgm_play.c
@@ -0,0 +1,797 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "bgm_i.h"
+
+#define DEBUGGING_PLEASE 0
+#if DEBUGGING_PLEASE
+#define Dbg_Printf(A...)  printf(##A) 		
+#else
+#define Dbg_Printf(A...)
+#endif
+
+// wav format ------------------------
+#define RIFF_HEADER_SIZE 44
+typedef struct {
+	unsigned char     chunkID[4];
+	unsigned int      chunkSize;
+	unsigned short*   data;
+} DATAHeader;
+
+typedef struct {
+	unsigned char     chunkID[4];
+	unsigned int      chunkSize;
+	unsigned short    waveFmtType;
+	unsigned short    channel;
+	unsigned int      samplesPerSec;
+	unsigned int      bytesPerSec;
+	unsigned short    blockSize;
+	unsigned short    bitsPerSample;
+} FMTHeader;
+
+typedef struct {
+	unsigned char     chunkID[4];
+	unsigned int      chunkSize;
+	unsigned char     formType[4];
+	FMTHeader         fmtHdr;
+	DATAHeader        dataHdr;
+} RIFFHeader;
+//------------------------------------
+
+typedef struct {
+	unsigned int     size;
+//	unsigned int     offset;
+	unsigned int     pos;
+} BGM_WAVE;
+
+
+BGM_WAVE gWave[2];
+
+int gMemInitializedForCD = 0;
+short gBgmPause[2] = { 0, 0 };
+short gBgmPreload[ 2 ] = { 0, 0 };
+char gFileFromCD[ 2 ] = { 0, 0 };
+char gBgmVolumeSet[2] = { 0, 0 };
+volatile char gFileOpened[2] = { 0, 0 };
+volatile char gBgmIntr[2] = { 0, 0 };
+int gThid = 0;
+int gSem = 0;
+int gFd[2];
+int gBuffRaw[2];
+int gBuffSpu[2] = {0, 0};
+int gRPacketSize[2];
+int gSPacketSize[2];
+int gAllockedSize[2];
+int gBgmVolume[2];
+int gBgmMode[2] = { BGM_MODE_REPEAT_OFF, BGM_MODE_REPEAT_OFF };
+int gBgmStereoMode[ 2 ] = { 0, 0 };
+
+#define MAX_FILENAME_LENGTH		50
+char gFilename[ 2 ][ MAX_FILENAME_LENGTH ];
+
+// Larger buffers will prevent skipping when loading in the background...
+// The more streams, the larger the buffers have to be.
+// Reading from the CDVD will be faster than debug stations from the host over the network,
+// so don't freak if you notice corruption on a dev system w/o CD.
+#define SECTOR_SIZE					2048
+#define NUM_SECTORS_PER_READ		2
+#define BUFFER_SIZE_FOR_HOST_FILE	( 2048 * 132 ) // multiple of 6
+#define BUFFER_SIZE_FOR_CD_FILE		( SECTOR_SIZE * NUM_SECTORS_PER_READ * 6 )
+
+#define RING_BUFFER_NUM_SECTORS		48
+#define RING_BUFFER_ALLIGNMENT		64
+#define RING_BUFFER_NUM_PARTITIONS	3
+unsigned char gRingBuffer[ 2 ][ RING_BUFFER_NUM_SECTORS * 2048 ] __attribute__((aligned ( RING_BUFFER_ALLIGNMENT )));
+unsigned char gCDBuffSpu[ 2 ][ BUFFER_SIZE_FOR_CD_FILE ]__attribute__((aligned ( 64 )));
+
+int  _BgmPlay( int status );
+void _BgmRaw2Spu(u_long *src, u_long *dst,  u_long block_count );
+void _BgmRaw2SpuMono(u_long *src, u_long *dst,  u_long block_count );
+int	BgmPreLoad( int ch, int status );
+void SetStereoOn( int ch, int stereo );
+
+static int IntFunc0( void* common )
+{
+	gBgmIntr[0] = 1;
+	iSignalSema( gSem );
+	return 1;  //-- Required for re-enabling interrupts
+}
+
+static int IntFunc1( void* common )
+{
+	gBgmIntr[1] = 1;
+	iSignalSema( gSem );
+	return 1;  //-- Required for re-enabling interrupts
+}
+
+
+static int makeMyThread( void )
+{
+    struct ThreadParam param;
+    int	thid;
+
+    param.attr         = TH_C;
+    param.entry        = _BgmPlay;
+    param.initPriority = BASE_priority-3;
+    param.stackSize    = 0x800;
+    param.option = 0;
+
+    /* Create thread */
+	thid = CreateThread(¶m);
+
+	return thid;
+}
+
+static int makeMySem( void )
+{
+    struct SemaParam sem;
+
+    sem.initCount = 0;
+    sem.maxCount = 1;
+    sem.attr = AT_THFIFO;
+
+    /* Create semaphore.  */
+    return CreateSema(&sem);
+}
+
+
+int BgmRaw2Spu( int ch, int which )
+{
+    if ( gBgmStereoMode[ ch ] )
+	{
+		_BgmRaw2Spu( (u_long *)(gBuffRaw[ch]+ (gRPacketSize[ch])*which)
+			, (u_long *)(gBuffSpu[ch] + (gSPacketSize[ch])*which)
+			, gSPacketSize[ch]/1024 );
+	}
+	else
+	{
+		_BgmRaw2SpuMono ((u_long *)(gBuffRaw [ch] + (gRPacketSize [ch]) * which),
+				 (u_long *)(gBuffSpu [ch] + (gSPacketSize [ch]) * which),
+				 gSPacketSize [ch] / 1024);
+    }
+	return 0;
+}
+
+// returns zero if no longer playing...
+int BgmSetVolumeDirect( int ch, unsigned int vol )
+{
+	sceSdSetParam( (ch)|SD_P_BVOLR, vol>>16 );
+	sceSdSetParam( (ch)|SD_P_BVOLL, vol&0xFFFF);
+	return ( gBgmMode[ch] & ~BGM_MASK_STATUS );
+}
+
+
+
+void BgmSdInit(int ch, int status )
+{
+	// This is already called from the soundFX module.
+	//sceSdInit(0);
+	return;
+}
+
+
+int BgmInit( int ch, int useCD )
+{
+
+	if( gSem == 0 ){
+		gSem = makeMySem();
+	}
+	if( gThid == 0 ){
+		gThid = makeMyThread();
+		Dbg_Printf("PCM Audio Streamer: create thread ID= %d, ", gThid );
+		/* Activate thread  */
+		StartThread( gThid, (u_long)NULL );
+	}
+
+	if ( !gBuffSpu[ ch ] )
+	{
+		gAllockedSize[ch] = useCD ? BUFFER_SIZE_FOR_CD_FILE : BUFFER_SIZE_FOR_HOST_FILE;
+		if ( useCD )
+		{
+			gBuffSpu[ ch ] = ( int )gCDBuffSpu[ ch ]; //(int)AllocSysMemory( 0, gAllockedSize[ ch ], NULL );
+		}
+		else
+		{
+			gBuffSpu[ ch ] = ( int )AllocSysMemory( 0, gAllockedSize[ ch ], NULL );
+		}
+		Dbg_Printf(" PCM spu buffer mem: 0x%x - 0x%x\n", gBuffSpu[ch], gBuffSpu[ch]+gAllockedSize[ ch ] );
+	}
+	// the memory should work for mono or stereo 44k files ( raw buffer always the same... )
+	gRPacketSize[ ch ] = gAllockedSize[ ch ] / 6;
+	gBuffRaw[ ch ] = ( int )( gBuffSpu[ ch ] + ( gRPacketSize[ ch ] * 4 ) );
+	gMemInitializedForCD = useCD;
+
+	return  gThid;
+}
+
+void BgmQuit(int ch, int status )
+{
+	if ( ( gBuffSpu[ ch ] ) && ( gBuffSpu[ ch ] != ( int ) gCDBuffSpu ) )
+		FreeSysMemory( (void*)gBuffSpu[ch] );
+	gBuffSpu[ch] = 0;
+	//-- Frees resources unless another channel is used.
+	if( gBuffSpu[1-ch] == 0 ){
+		if (gThid != 0) TerminateThread( gThid );
+		if (gSem != 0) DeleteSema( gSem );
+		if (gThid != 0) DeleteThread( gThid );
+		gThid = 0;
+		gSem = 0;
+	}
+	return;
+}
+
+/*
+void WaitForCDReady( char *description )
+{
+	printf( "wait %s", description );
+	while( SCECdComplete != sceCdDiskReady( 1 ) )
+	{
+		printf( "." );
+	}
+	printf( "\n" );
+}*/
+
+#define NUM_BYTES_TO_CLEAR	512 // must be divisible by 4!!!
+
+int BgmOpen( int data, char* filename )
+{
+//	unsigned int channel;
+	RIFFHeader wavHdr;
+	int offset;
+	int *addr;
+	int i;
+	int ch = data & 1;
+	int stereo = data & WAV_STEREO_BIT;
+	
+	SetStereoOn( ch, stereo );
+
+	for ( i = 0; i < 2; i++ )
+	{
+		if ( gFileOpened[ i ] )
+		{
+			printf( "%s: Failed: Audio stream already running: %s\n", filename, gFilename[ i ] );
+//			Dbg_Printf( "File already opened on that channel...\n" );
+			return -1;
+		}
+	}
+	printf("pcm filename %s\n", filename );
+	
+	if ( !strncmp( filename, "host", 4 ) )
+	{
+		if ( gMemInitializedForCD )
+		{
+			printf( "Loading file from host when mem configured for CD... SOUND QUALITY WILL SUCK.\n" );
+			printf( "If calling ezBGM_INIT with useCD (2nd param), send CDROM0 in filename...\n" );
+		}
+		
+		// load the file from the dev system...
+		if (( gFd[ch] = open ( filename, O_RDONLY)) < 0)
+		{
+			ERROR(("file open failed. %s \n", filename));
+			return -1;
+		}
+		
+		if ( read (gFd[ch], (unsigned char*)(&wavHdr)
+			,RIFF_HEADER_SIZE ) != RIFF_HEADER_SIZE ) {
+			ERROR(("file read failed. %s \n", filename));
+			return -1;
+		}
+		if ( wavHdr.fmtHdr.channel == 2 )
+		{
+			if ( !stereo )
+				printf( "WARNING: Playing stereo sound %s in mono mode.\n", filename );
+		}
+		else if ( stereo )
+		{
+			printf( "WARNING: Playing mono sound %s in stereo mode.\n", filename );
+		}
+		
+		gWave[ch].size   = wavHdr.dataHdr.chunkSize;
+		gWave[ch].pos    = 0;
+	
+		//--- Seek to start of data
+		offset = (unsigned int)&(wavHdr.dataHdr.data) -(unsigned int)&wavHdr;
+	
+		lseek( gFd[ ch ], offset , SEEK_SET );
+	
+		gFileFromCD[ ch ] = 0;
+		gFileOpened[ ch ] = 1;
+		gBgmPreload[ ch ] = 3;
+	}
+	else
+	{
+		sceCdlFILE fp;
+		sceCdRMode mode;
+		int retVal;
+		unsigned int stringLength;
+		unsigned int lsn;
+		int foundCDData = 0;
+		int byteIndex;
+		// open a stream and load from CD...
+		// 1st arg: size in num sectors ( actual size / 2048 ).
+//		WaitForCDReady( "st init" );
+//		sceCdDiskReady( 0 );
+		sceCdStInit( RING_BUFFER_NUM_SECTORS, RING_BUFFER_NUM_PARTITIONS, ( u_int )( gRingBuffer[ ch ] ) ); //+ ( RING_BUFFER_ALLIGNMENT - 1 ) )  );
+//		printf( "wait for ready." );
+//		sceCdDiskReady( 0 );
+//		WaitForCDReady( "init" );
+//		printf( "cdSearchFile\n" );
+		stringLength = strlen( filename );
+		if ( filename[ stringLength + 1 ] == '@' )
+		{
+			foundCDData = 1;
+			lsn = 0;
+			for ( byteIndex = 0; byteIndex < 4; byteIndex++ )
+			{
+				lsn |= ( ( ( unsigned int )( filename[ stringLength + 2 + byteIndex ] ) ) & 255 ) << ( 8 * byteIndex );
+			}
+		}
+		if ( !foundCDData )
+		{
+			if ( !( retVal = sceCdSearchFile( &fp, filename ) ) )
+			{
+				ERROR(("cd filesearch failed %d. file %s \n", retVal, filename));
+				return -1;
+			}
+			lsn = fp.lsn;
+		}
+	
+//		WaitForCDReady( "find" );
+//		sceCdDiskReady( 0 );
+		mode.trycount = 0;
+		mode.spindlctrl = SCECdSpinNom;
+		mode.datapattern = SCECdSecS2048;
+		sceCdStStart( lsn, &mode );
+		gFileFromCD[ ch ] = 1;
+		gBgmPreload[ ch ] = 3;
+		gFileOpened[ ch ] = 1;
+		gWave[ ch ].pos = 0;
+		gWave[ ch ].size = gRPacketSize[ ch ] * 4;
+	}
+	
+//	printf("wave size %d  offset %d\n", gWave[ch].size, gWave[ch].offset );
+	
+	BgmSetVolumeDirect( ch, 0 );
+
+	strncpy( gFilename[ ch ], filename, MAX_FILENAME_LENGTH  );
+
+	// clear out the end of the second half of the last buffer...
+	addr = ( int * )( gBuffRaw[ ch ] + ( gRPacketSize[ ch ] ) + ( gRPacketSize[ ch ] >> 1 ) );
+	for ( i = 0; i < ( ( gRPacketSize[ ch ] >> 1 ) >> 2 ); i++ )
+	{
+		*( addr++ ) = 0;
+	}
+	
+	if( ch == 0 )
+	{
+		sceSdSetTransCallback( ch, IntFunc0 );
+	}
+	else
+	{
+		sceSdSetTransCallback( ch, IntFunc1 );
+	}
+		
+	return ( gWave[ ch ].size );
+}
+
+void _BgmClose(int ch, int status)
+{
+	if ( !gFileOpened[ ch ] )
+	{
+		PRINTF(("Calling BgmClose when no file opened on that channel...\n"));
+		return;
+	}
+	Dbg_Printf( "_BgmClose\n" );
+	if ( gFileFromCD[ ch ] )
+	{
+		sceCdStStop( );
+	}
+	else
+	{
+		close( gFd[ch] );
+		//FreeSysMemory( ( void* )gBuffSpu[ ch ] );
+		//gBuffSpu[ ch ] = 0;
+	}
+	gFileOpened[ ch ] = 0;
+	return;
+}
+
+void BgmStart(int ch, int status )
+{
+	int retVal;
+	if ( !gFileOpened[ ch ] )
+	{
+		printf( "trying to start track not loaded.\n" );
+		return;
+	}
+	Dbg_Printf( "starting block trans from bgm start/unpause\n" );
+	if ( gBgmPause[ ch ] )
+	{
+		Dbg_Printf( "Starting music on channel where pause is requested...\n" );
+		gBgmPause[ ch ] = 0;
+	}
+	while( -1 == sceSdBlockTrans( ch, SD_TRANS_MODE_WRITE|SD_BLOCK_LOOP, (u_char*)gBuffSpu[ch], (gSPacketSize[ch]*2) ) )
+	{
+		for ( retVal = 0; retVal < 10000; retVal++ )
+			;
+		Dbg_Printf( "failed to start loop.\n" );
+	}
+	if ( !( gBgmPreload[ ch ] ) )
+	{
+		BgmSetVolumeDirect(ch, gBgmVolume[ch]);
+	}
+	gBgmMode[ch] &= BGM_MASK_STATUS;
+	gBgmMode[ch] |= BGM_MODE_RUNNING;
+
+	return;
+}
+
+void BgmCloseNoWait( int ch, int status )
+{
+	if ( !gFileOpened[ ch ] )
+	{
+		PRINTF(("Calling BgmClose when no file opened on that channel...\n"));
+		return;
+	}
+	if ( gBgmPause[ ch ] )
+	{
+		printf( "stopping music when pause is requested..." );
+		return;
+	}
+	// if the pause has already stopped the callback, it's safe to unload now:
+	if ( gBgmMode[ ch ] & BGM_MODE_PAUSE )
+	{
+		// callback isn't happening right now... can terminate with no worries here:
+		Dbg_Printf( "unpausing from BgmClose\n" );
+		gBgmMode[ch] &= BGM_MASK_STATUS; // Switch to IDLE mode
+		_BgmClose( ch, status );
+		Dbg_Printf( "calling _BgmClose from BgmClose\n" );
+		return;
+	}
+	gBgmMode[ch] &= BGM_MASK_STATUS;
+	gBgmMode[ch] |= BGM_MODE_TERMINATE;
+}
+
+void BgmClose( int ch, int status )
+{
+	int i;
+	BgmCloseNoWait( ch, status );
+	printf( "PCM close" );
+	while ( ( gBgmMode[ ch ] & BGM_MODE_TERMINATE ) )
+	{
+		for ( i = 0; i < 20000; i++ )
+			;
+		printf( "." );
+	}
+	printf( "\n" );
+}
+
+int BgmPreLoad( int ch, int status )
+{
+	if ( ( status == 666 ) || ( ( gWave[ ch ].size - gWave[ ch ].pos ) < gRPacketSize[ ch ] * 4 ) )
+	{
+		int i;
+		int *addr;
+		Dbg_Printf( "preloading at end of song. zero buffers.\n" );
+		// we're at the end of the song... fill up read buffers with zero,
+		// and convert...
+		addr = ( int * )( gBuffRaw[ ch ] );
+		for ( i = 0; i < ( ( gRPacketSize[ ch ] * 2 ) >> 2 ); i++ )
+		{
+			*( addr++ ) = 0;
+		}
+		// fill up the spu with both buffers...
+		BgmRaw2Spu( ch, 0 );
+		BgmRaw2Spu( ch, 1 );
+		return ( 0 );
+	}
+	
+	// it's still in preload mode... no need to preload ourselves...
+	if ( gBgmPreload[ ch ] == 3 )
+		return ( 0 );
+
+	if ( gFileFromCD[ ch ] )
+	{
+		unsigned int err;
+		sceCdStRead( NUM_SECTORS_PER_READ * 2, ( u_int * )gBuffRaw[ ch ], STMBLK, &err );
+		if ( err )
+		{
+			printf( "PCM A Disk error code 0x%08x\n", err );
+		}
+
+		// fill up the spu with both buffers...
+		BgmRaw2Spu( ch, 0 );
+		BgmRaw2Spu( ch, 1 );
+		
+		// fill up the buffers again...
+		sceCdStRead( NUM_SECTORS_PER_READ * 2, ( u_int * )gBuffRaw[ ch ], STMBLK, &err );
+		if ( err )
+		{
+			printf( "PCM B Disk error code 0x%08x\n", err );
+		}
+		
+		gWave[ ch ].pos += gRPacketSize[ ch ] * 2;
+		return 0;
+	}
+	// reading from a file...
+	if ( read (gFd[ch], (unsigned char*)(gBuffRaw[ch])
+	, gRPacketSize[ch]*2 ) != gRPacketSize[ch]*2 ) {
+		ERROR (("BgmPreLoad: read failed \n")); return -1;
+	}
+
+	BgmRaw2Spu( ch, 0 );
+	BgmRaw2Spu( ch, 1 );
+
+	if ( read (gFd[ch], (unsigned char*)(gBuffRaw[ch])
+	, (gRPacketSize[ch]*2) ) != gRPacketSize[ch]*2) {
+		ERROR (("BgmPreLoad: read failed \n")); return -1;
+	}
+
+	gWave[ch].pos += gRPacketSize[ch]*4;
+
+	return 0;
+}
+
+void _BgmPause(int ch, int status)
+{
+	Dbg_Printf( "stopping spu block trans in _BgmPause\n" );
+	sceSdBlockTrans( ch, SD_TRANS_MODE_STOP, NULL, 0 );
+	gBgmMode[ch] &= BGM_MASK_STATUS; // Switch to PAUSE mode
+	gBgmMode[ch] |= BGM_MODE_PAUSE;
+	return;
+}
+
+void _BgmStopAndUnload(int ch, int status)
+{
+	Dbg_Printf( "stopping block trans from _BgmStopAndUnload\n" );
+	sceSdBlockTrans( ch, SD_TRANS_MODE_STOP, NULL, 0 );
+	gBgmMode[ch] &= BGM_MASK_STATUS; // Switch to IDLE mode
+	Dbg_Printf( "bgm close from _BgmStopAndUnload\n" );
+	_BgmClose( ch, status );
+	return;
+}
+
+// should be fucking called "BgmPause"
+void BgmStop( int ch, unsigned int vol )
+{
+	int i;
+
+	if ( gBgmMode[ ch ] & BGM_MODE_TERMINATE )
+	{
+		Dbg_Printf( "trying to pause PCM when stopped.\n" );
+		return;
+	}
+	if ( !gBgmMode[ ch ] & BGM_MODE_RUNNING )
+	{
+		Dbg_Printf( "trying to pause PCM when not running.\n" );
+		return;
+	}
+	gBgmPause[ch] = 1;
+	printf( "waiting for pause" );
+	while ( !( gBgmMode[ ch ] & BGM_MODE_PAUSE ) )
+	{
+		for ( i = 0; i < 10000; i++ )
+			;
+		printf( "." );
+	}
+	printf( "\n" );
+	// don't set the flag until the callback has paused it:
+//	gBgmMode[ch] &= BGM_MASK_STATUS;
+//	gBgmMode[ch] |= BGM_MODE_PAUSE;
+	return;
+}
+
+
+int BgmSetVolume( int ch, unsigned int vol )
+{
+	gBgmVolumeSet[ch] = 1;
+	gBgmVolume[ch] = vol;
+	return ( gBgmMode[ch] & ~BGM_MASK_STATUS );
+}
+
+
+unsigned int BgmGetMode( int ch, int status )
+{
+	return gBgmMode[ch];
+}
+
+void BgmSeek( int ch, unsigned int value )
+{
+//	lseek(gFd[ch], gWave[ch].offset+value, SEEK_SET );
+//	gWave[ch].pos = value;
+	printf( "seek not supported\n" );
+	return;
+}
+
+void SetStereoOn( int ch, int stereo )
+{
+    if ( stereo )
+	{
+		gSPacketSize[ ch ] = gAllockedSize[ ch ] / 6;
+		gBgmStereoMode[ ch ] = 1;
+	}
+	else
+	{
+		gSPacketSize[ ch ] = gAllockedSize [ ch ] / 3;
+		gBgmStereoMode[ ch ] = 0;
+	}
+}
+
+void PreloadFile( int which, int ch )
+{
+	switch ( gBgmPreload[ ch ] )
+	{
+		case ( 3 ):
+			if ( gFileFromCD[ ch ] )
+			{
+				RIFFHeader *pWavHdr;
+				int i;
+				int offset;
+				unsigned char *pData;
+				pWavHdr = ( RIFFHeader * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which );
+				if ( pWavHdr->dataHdr.chunkSize < gWave[ ch ].size )
+				{
+					printf( "BIG WARNING:  Cd audio file currently playing is TOO SMALL!\n" );
+				}
+				if ( pWavHdr->fmtHdr.channel == 2 )
+				{
+					if ( !gBgmStereoMode[ ch ] )
+						printf( "WARNING: Playing stereo sound %s in mono mode.\n", gFilename[ ch ] );
+				}
+				else if ( gBgmStereoMode[ ch ] )
+				{
+					printf( "WARNING: Playing mono sound %s in stereo mode.\n", gFilename[ ch ] );
+				}
+				
+				SetStereoOn( ch, pWavHdr->fmtHdr.channel == 2 );
+				gWave[ch].size   = pWavHdr->dataHdr.chunkSize;
+				//gWave[ch].offset = (unsigned int)&( pWavHdr->dataHdr.data ) - ( unsigned int )pWavHdr;
+				offset = (unsigned int)&( pWavHdr->dataHdr.data ) - ( unsigned int )pWavHdr;
+				// clear out the area where the header is... don't want some loud obnoxious sounds...
+				pData = ( unsigned char * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which );
+				if ( offset > gRPacketSize[ ch ] )
+					offset = gRPacketSize[ ch ]; // don't fuck up memory if some strange read happened...
+				for ( i = 0; i < offset; i++ )
+				{
+					pData[ i ] = 0;
+				}
+			}
+			break;
+		
+		case ( 2 ):
+			break;
+		
+		case ( 1 ):
+			// turn the volume back up, the spooler is at the beginning of
+			// the good SPU buffer...
+			BgmSetVolume( ch, gBgmVolume[ch] );
+			break;
+		
+		default:
+			printf( "unknown preload state... fire Matt.\n" );
+			break;
+	}
+	--gBgmPreload[ ch ];
+}
+
+int _BgmPlay( int status )
+{
+	int i, ch, read_size, which;
+	int *addr, remain;
+	Dbg_Printf( "entering _BgmPlay\n" );
+	while ( 1 )
+	{
+		//-- Wait for playing of buffer to finish
+		WaitSema(gSem);
+
+		//-- Which channel is the interrupt from?
+		if( (gBgmIntr[0] == 1) && ( ch != 0 ) )  ch = 0;
+		else if( (gBgmIntr[1] == 1) && ( ch != 1 ) )  ch = 1;
+		else if( gBgmIntr[0] == 1 )  ch = 0;
+		else if( gBgmIntr[1] == 1 )  ch = 1;
+		else continue;
+
+		gBgmIntr[ch] = 0;
+
+		which = 1 - (sceSdBlockTransStatus( ch, 0 )>>24);
+
+		//--- Stopped due to end of data (no looping)
+		if( ( gBgmMode[ ch ] & BGM_MODE_TERMINATE ) != 0 )
+		{
+			WaitSema( gSem ); // Wait until another interrupt is received
+			_BgmStopAndUnload( ch, 0 );
+			BgmSetVolumeDirect( ch, 0x0 );
+			continue;
+		}
+
+		//--- Volume change event
+		if ( ( gBgmVolumeSet[ ch ] == 1 ) && ( !gBgmPreload[ ch ] ) )
+		{
+			BgmSetVolumeDirect( ch, gBgmVolume[ch] );
+			gBgmVolumeSet[ch] = 0;
+		}
+
+		//--- Convert buffer
+
+		BgmRaw2Spu( ch, which );
+
+		//--- File READ for buffer
+
+		remain = gWave[ ch ].size - gWave[ ch ].pos;
+		if ( remain > gRPacketSize[ch] )
+		{
+			if ( gFileFromCD[ ch ] )
+			{
+				//--- Not end of data
+				unsigned int err;
+				sceCdStRead( NUM_SECTORS_PER_READ, ( u_int * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which ), STMBLK, &err );
+				if ( err )
+				{
+					printf( "PCM C Disk error code 0x%08x\n", err );
+				}
+				read_size = gRPacketSize[ ch ];
+			}
+			else
+			{
+				read_size = read (gFd[ch], (unsigned char*)(gBuffRaw[ch]+gRPacketSize[ch]*which), gRPacketSize[ch] );
+			}
+			if ( read_size < gRPacketSize[ch] )
+				continue; //retry
+			if ( gBgmPreload[ ch ] )
+			{
+				PreloadFile( which, ch );
+			}
+			gWave[ch].pos += read_size;
+		}
+		else  //--- End of data
+		{
+			if ( gFileFromCD[ ch ] )
+			{
+				//--- Not end of data
+				unsigned int err;
+				sceCdStRead( NUM_SECTORS_PER_READ, ( u_int * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which ), STMBLK, &err );
+				if ( err )
+				{
+					printf( "PCM C Disk error code 0x%08x\n", err );
+				}
+				read_size = gRPacketSize[ ch ];
+			}
+			else
+			{
+				read_size = read (gFd[ch], (unsigned char*)(gBuffRaw[ch]+gRPacketSize[ch]*which), remain );
+			}
+			
+			if( read_size < remain ) continue; //retry
+	    	
+			PRINTF(("end of PCM track - ch %d\n", ch));
+
+			addr = ( int * )( gBuffRaw[ ch ] + ( gRPacketSize[ ch ] * which ) + remain );
+			for( i = 0; i < ((gRPacketSize[ch]-remain)>>2); i++ )
+			{
+				*(addr++) = 0;
+			}
+			gBgmMode[ch] &= BGM_MASK_STATUS;
+			gBgmMode[ch] |= BGM_MODE_TERMINATE;
+		}
+
+		//-- Stop event
+		if( (gBgmPause[ch] == 1) )
+		{
+			//printf( "_BgmPlay :: pause\n" );
+			_BgmPause( ch, 0 );
+			BgmSetVolumeDirect(ch, 0x0);
+			gBgmPause[ch] = 0;
+		}
+	}
+	return 0;
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* This file ends here, DON'T ADD STUFF AFTER THIS */
+
+// Ha ha I added some stuff... FUCK YOU SONY!
diff --git a/Code/Gel/Music/Ngps/Bgm/bgm_r2s.s b/Code/Gel/Music/Ngps/Bgm/bgm_r2s.s
new file mode 100644
index 0000000..9850f3e
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/bgm_r2s.s
@@ -0,0 +1,129 @@
+/* SCEI CONFIDENTIAL
+ "PlayStation 2" Programmer Tool Runtime Library  Release 2.0
+ */
+/* 
+ *                  I/O Proseccor sample program
+ *                          Version 1.20
+ *                           Shift-JIS
+ *
+ *      Copyright (C) 1998-1999 Sony Computer Entertainment Inc.
+ *                        All Rights Reserved.
+ *
+ *                       ezbgm.irx - bgm_r2s.c
+ *                          raw to spu pcm
+ *
+ *   Version   Date            Design    Log
+ *  --------------------------------------------------------------------
+ *   1.20      Nov.23.1999     morita    modify for EzBGM
+ *   0.01      Nov.18.1999     ishii     optimize for IOP
+ */
+
+#include 
+
+#define src  a0
+#define dst  a1
+#define blk  a2
+#define cnt  a3
+
+	.globl	_BgmRaw2Spu
+	.ent	_BgmRaw2Spu
+_BgmRaw2Spu:
+	subu	sp, (8*4)
+	sw	s0, 0*4(sp)	; 	sw	s1, 1*4(sp)
+	sw	s2, 2*4(sp)	; 	sw	s3, 3*4(sp)
+	sw	s4, 4*4(sp)	; 	sw	s5, 5*4(sp)
+	sw	s6, 6*4(sp)	; 	sw	s7, 7*4(sp)
+	move	v0, zero
+	move	v1, zero
+	addi	cnt,zero, 256
+
+					.set noreorder
+	/* ‚±‚±‚©‚çAŽè‚Å–½—ß‚Ì‚È‚ç‚Ñ‚ð§ŒäB */
+pcm_separate_loop:
+	lw	t0, 0*4(src)	/* 1 + 4 clock */
+	lw	t1, 1*4(src)	/* 1 + 2 clock */
+
+	/* 7 clock */
+	and	s0, t0, 0xffff		/* t0 ‚̉ºˆÊ‚ð s0 ‚Ö */
+	srl	s1, t0, 16		/* t0 ‚ÌãˆÊ‚ð s1 ‚Ö */
+	sll	t0, t1, 16		/* t1 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s0 ‚Ö */
+	or	s0, t0
+	srl	t1, t1, 16		/* t1 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s1 ‚Ö */
+	sll	t1, t1, 16
+	or	s1, t1
+
+	/* Šù‚ɃLƒƒƒbƒVƒ…‚É“Ç‚Ýž‚Ü‚ê‚Ä‚¢‚é‚Í‚¸ */
+	lw	t2, 2*4(src)	/* 1 clock */
+	lw	t3, 3*4(src)	/* 1 clock */
+	/* ŽŸ‚̃LƒƒƒbƒVƒ…ƒ‰ƒCƒ“‚ð“Ç‚Ýž‚Þ‚«‚Á‚©‚¯‚ðì‚é */
+	lw	t4, 4*4(src)	/* 1 + 4 clock */
+
+	/* 7 clock */
+	and	s2, t2, 0xffff		/* t2 ‚̉ºˆÊ‚ð s2 ‚Ö */
+	srl	s3, t2, 16		/* t2 ‚ÌãˆÊ‚ð s3 ‚Ö */
+	sll	t2, t3, 16		/* t3 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s2 ‚Ö */
+	or	s2, t2
+	srl	t3, t3, 16		/* t3 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s3 ‚Ö */
+	sll	t3, t3, 16
+	or	s3, t3
+
+	/* Šù‚ɃLƒƒƒbƒVƒ…‚É“Ç‚Ýž‚Ü‚ê‚Ä‚¢‚é‚Í‚¸ */
+	lw	t5, 5*4(src)	/* 1 clock */
+	lw	t6, 6*4(src)	/* 1 clock */
+	lw	t7, 7*4(src)	/* 1 clock */
+	add	src, 8*4	/* 1 clock */
+
+	/* 7 clock */
+	and	s4, t4, 0xffff		/* t4 ‚̉ºˆÊ‚ð s4 ‚Ö */
+	srl	s5, t4, 16		/* t4 ‚ÌãˆÊ‚ð s5 ‚Ö */
+	sll	t4, t5, 16		/* t5 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s4 ‚Ö */
+	or	s4, t4
+	srl	t5, t5, 16		/* t5 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s5 ‚Ö */
+	sll	t5, t5, 16
+	or	s5, t5
+
+	/* store  dst1(s0, s2),  dst2(s1, s3) */
+	/* 4 clock */
+
+	sw	s0, 0*4(dst)	;	sw	s2, 1*4(dst)
+	sw	s1, (0*4+512)(dst); 	sw	s3, (1*4+512)(dst)
+	/* ˆÈ‰º‚Æ•½s‚µ‚Ä +2, 4+2 clock ‚Å‘‚«ž‚Ü‚ê‚é‚Í‚¸ */
+
+	/* 7 clock */
+	and	s6, t6, 0xffff		/* t6 ‚̉ºˆÊ‚ð s6 ‚Ö */
+	srl	s7, t6, 16		/* t6 ‚ÌãˆÊ‚ð s7 ‚Ö */
+	sll	t6, t7, 16		/* t7 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s6 ‚Ö */
+	or	s6, t6
+	srl	t7, t7, 16		/* t7 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s7 ‚Ö */
+	sll	t7, t7, 16
+	or	s7, t7
+
+	/* store  dst1(s4, s6),  dst2(s5, s7) */
+	/* 4 clock */
+	sw	s4, 2*4(dst)	; 	sw	s6, 3*4(dst)
+	sw	s5, (2*4+512)(dst)	; 	sw	s7, (3*4+512)(dst)
+	/* ˆÈ‰º‚Æ•½s‚µ‚Ä +2, 4+2 clock ‚Å‘‚«ž‚Ü‚ê‚é‚Í‚¸ */
+
+	add	dst, 4*4		/* 1 clock */
+
+
+	/* ‚±‚±‚Ü‚ÅAŽè‚Å–½—ß‚Ì‚È‚ç‚Ñ‚ð§ŒäB */
+					.set reorder
+
+	add	v0, 8			/* 1 clock */
+	blt	v0, cnt, pcm_separate_loop	/* 1 clock */
+
+	add	dst, 512
+	move	v0, zero
+
+	add	v1, 1
+	blt	v1, blk, pcm_separate_loop
+
+	lw	s0, 0*4(sp)	; 	lw	s1, 1*4(sp)
+	lw	s2, 2*4(sp)	; 	lw	s3, 3*4(sp)
+	lw	s4, 4*4(sp)	; 	lw	s5, 5*4(sp)
+	lw	s6, 6*4(sp)	; 	lw	s7, 7*4(sp)
+	addu	sp, (8*4)
+	j	ra
+	.end	_BgmRaw2Spu
+
diff --git a/Code/Gel/Music/Ngps/Bgm/bgm_r2sm.c b/Code/Gel/Music/Ngps/Bgm/bgm_r2sm.c
new file mode 100644
index 0000000..fdf005a
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/bgm_r2sm.c
@@ -0,0 +1,20 @@
+#include 
+
+#define SPU_BLOCK 512
+
+// Using SP will speed things up, but ...
+
+void _BgmRaw2SpuMono( unsigned int *src, unsigned int *dst, unsigned int block )
+{
+	int i;
+
+	for ( i = 0; i < block; i++ )
+	{
+		memcpy( (void*)((int)dst+i*SPU_BLOCK*2), (void*)((int)src+i*SPU_BLOCK), SPU_BLOCK );
+		memcpy( (void*)((int)dst+i*SPU_BLOCK*2+SPU_BLOCK), (void*)((int)src+i*SPU_BLOCK) , SPU_BLOCK );
+	}
+
+	return;
+}
+
+
diff --git a/Code/Gel/Music/Ngps/Bgm/makefile b/Code/Gel/Music/Ngps/Bgm/makefile
new file mode 100644
index 0000000..b9021e5
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Bgm/makefile
@@ -0,0 +1,33 @@
+ifeq ($(wildcard PathDefs),)
+PathDefs:
+	iop-path-setup > PathDefs || (rm -f PathDefs ; exit 1)
+	make all
+else
+include PathDefs
+endif
+
+INCDIR	= -I/usr/local/sce/common/include -I/usr/local/sce/iop/install/include
+
+CFLAGS  = $(INCDIR) -I. -Wall -G0 -g
+ASFLAGS  = $(INCDIR) -G0
+
+COMPILE.s = $(CC) -xassembler-with-cpp $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c
+
+#----------- customize section --------------
+PROGNAME = ezbgm
+
+OBJS     = bgm_entr.o bgm_com.o bgm_play.o bgm_r2s.o bgm_r2sm.o
+#ILIBS	= -ilb=$(TOP)lib/iop.ilb 
+ILIBS	= 
+LIBI	= /usr/local/sce/iop/install/lib
+
+#----------- rules --------------
+all:	$(PROGNAME).irx
+
+clean:
+	rm -f *.o $(PROGNAME).irx *.obj *.map
+
+$(PROGNAME).irx: $(OBJS)
+	$(LINK.o)  -o $@ \
+		$(OBJS) -L../../../lib -L./ -L$(LIBI) -ilb=libsd.ilb  -ilb=cdvdman.ilb
+
diff --git a/Code/Gel/Music/Ngps/Pcm/Makefile b/Code/Gel/Music/Ngps/Pcm/Makefile
new file mode 100644
index 0000000..1f4b74c
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Pcm/Makefile
@@ -0,0 +1,39 @@
+ifeq ($(wildcard PathDefs),)
+PathDefs:
+	iop-path-setup
+	make all
+else
+include PathDefs
+endif
+
+TOPDIR = /usr/local/sce
+INCOPT = -I$(TOPDIR)/common/include -I$(TOPDIR)/iop/install/include
+
+CFLAGS  = $(INCOPT) -I. -Wall -G0 -g -D__PLAT_NGPS__
+ASFLAGS = $(INCOPT) -G0
+RM          = /bin/rm -f
+
+COMPILE.s = $(CC) -xassembler-with-cpp $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c
+
+#----------- customize section --------------
+PROGNAME = ezpcm
+
+OBJS     = pcm_ent.o pcm_com.o pcm_sound.o 
+#ILIBS	= -ilb=$(TOP)lib/iop.ilb 
+ILIBS	=
+LIBI	= /usr/local/sce/iop/install/lib
+
+#----------- rules --------------
+all:	$(PROGNAME).irx
+
+clean:
+	rm -f *.o $(PROGNAME).irx *.obj *.map
+
+$(PROGNAME).irx: $(OBJS)
+	$(LINK.o)  -o $@ \
+		$(OBJS) -L../../../lib -L./ -L$(LIBI) -ilb=libsd.ilb
+
+pcm_ent.o:	pcm.h pcmiop.h
+pcm_com.o:	pcm.h pcmiop.h
+pcm_sound.o:	pcm.h pcmiop.h
+
diff --git a/Code/Gel/Music/Ngps/Pcm/PathDefs b/Code/Gel/Music/Ngps/Pcm/PathDefs
new file mode 100644
index 0000000..6d730f4
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Pcm/PathDefs
@@ -0,0 +1,16 @@
+AR	 = snarl.exe
+AS	 = ps2cc -iop
+CC	 = ps2cc -iop
+GCC	 = ps2cc -iop
+LD	 = ps2cc -iop
+NM	 = C:/usr/local/sce/iop/gcc/bin/iop-nm.exe
+SIZE	 = iop-size.exe
+STRIP	 = C:/usr/local/sce/iop/gcc/bin/iop-strip.exe
+RANLIB	 = C:/usr/local/sce/iop/gcc/bin/iop-ranlib.exe
+OBJCOPY	 = C:/usr/local/sce/iop/gcc/bin/iop-objcopy.exe
+OBJDUMP	 = C:/usr/local/sce/iop/gcc/bin/iop-objdump.exe
+IFIXUP	 = iopfixup.exe
+ILBGEN	 = C:/usr/local/sce/iop/gcc/bin/ioplibgen.exe
+ILBLD	 = C:/usr/local/sce/iop/gcc/mipsel-scei-elfl/bin/ioplibld.exe
+ILBDUMP	 = C:/usr/local/sce/iop/gcc/mipsel-scei-elfl/bin/ioplibdump.exe
+BIN2OBJ	 = bin2elf.exe
diff --git a/Code/Gel/Music/Ngps/Pcm/pcm.h b/Code/Gel/Music/Ngps/Pcm/pcm.h
new file mode 100644
index 0000000..aa6e93e
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Pcm/pcm.h
@@ -0,0 +1,310 @@
+
+#ifndef	__GEL_MUSIC_NGPS_PCM_PCM_H__
+#define	__GEL_MUSIC_NGPS_PCM_PCM_H__
+
+
+#ifdef	__PLAT_NGPS__
+#include 
+#endif
+
+#define NUM_STREAMS 3
+
+#define PCM_RPC_ARG_NUM_INTS 16
+
+#define DEFAULT_PITCH	0x1000
+
+// VAG header structure
+struct VagHeader
+{
+	char ID[4];
+	int version;
+	char reserved1[4];
+	int dataSize;
+	int sampleFreq;
+	char reserved2[12];
+	char name[16];
+} ;
+
+#define SIZE_OF_VAGHEADER ( sizeof( VagHeader ) )
+
+// sound stuff:
+#define SB_BUF_SIZE ( 1024 * 32 )		// hex 2000
+#define SB_BUF_HALF ( SB_BUF_SIZE / 2 )	// hex 1000
+#define SB_BUF_SIZE_WITH_PADDING	( SB_BUF_SIZE + 0x40 )
+
+// spu ram layout:
+// first valid address for use in SPU RAM
+#define SPU_RAM_SIZE					0x1fffff
+#define SB_FIRST_USEABLE_ADDR			0x5010
+
+//#define STRAY_VOICE_BLOCKER_SIZE		128
+//#define STRAY_VOICE_BLOCKER_SPU_ADDR	SB_TOP
+//#define STREAM_SPU_ADDR					( STRAY_VOICE_BLOCKER_SPU_ADDR + STRAY_VOICE_BLOCKER_SIZE )
+#define STREAM_SPU_ADDR					SB_FIRST_USEABLE_ADDR
+
+// slip in room for 3 streaming VAGS ( 2 for stereo music and one extra stream ):
+//#define SINGLE_STREAM_BUFFER_SIZE 2048
+//#define TOTAL_SPU_REQUIRED_FOR_STREAMS		( SB_BUF_SIZE_WITH_PADDING * 4 )  // Assumes 2 streams
+#define TOTAL_SPU_REQUIRED_FOR_STREAMS		( SB_BUF_SIZE_WITH_PADDING * ( 2 + NUM_STREAMS ) )
+#define MUSIC_L_SPU_BUF_LOC					( STREAM_SPU_ADDR + 0x40 )
+#define MUSIC_R_SPU_BUF_LOC					( MUSIC_L_SPU_BUF_LOC + SB_BUF_SIZE_WITH_PADDING )
+#define STREAM_SPU_BUF_LOC( i )				( MUSIC_R_SPU_BUF_LOC + ( ( 1 + ( i ) ) * SB_BUF_SIZE_WITH_PADDING ) )
+
+#define END_OF_STREAMS						( STREAM_SPU_ADDR + TOTAL_SPU_REQUIRED_FOR_STREAMS + 0x40 )
+
+//#define RAM_NEEDED_FOR_EFFECTS		0	// FUCK the effects...
+#define	RAM_NEEDED_FOR_EFFECTS			0xade0 // enough for HALL reverb
+//#define	RAM_NEEDED_FOR_EFFECTS		0xF6C0 // enough for space echo
+ 
+#define REVERB_ONLY_ON_CORE_0			1
+
+#if !REVERB_ONLY_ON_CORE_0
+
+#define CORE_1_EFFECTS_START_ADDRESS	END_OF_STREAMS
+#define CORE_1_EFFECTS_END_ADDRESS		0x02ffff // has to be on a 0xffff boundary!!
+#if ( ( CORE_1_EFFECTS_END_ADDRESS - CORE_1_EFFECTS_START_ADDRESS ) < RAM_NEEDED_FOR_EFFECTS )
+#error "not enough space for core 0 effects!"
+#endif
+
+#define BASE_WAVE_DATA_ADDR				CORE_0_EFFECTS_END_ADDRESS
+
+#else
+#define BASE_WAVE_DATA_ADDR				END_OF_STREAMS
+#endif
+
+#define CORE_0_EFFECTS_START_ADDRESS	( SPU_RAM_SIZE - RAM_NEEDED_FOR_EFFECTS )
+#define CORE_0_EFFECTS_END_ADDRESS		( SPU_RAM_SIZE )
+#define END_WAVE_DATA_ADDR				( CORE_0_EFFECTS_START_ADDRESS  - 0x40 )
+
+// used by temporary sounds and permanently loaded sounds...
+#define MAX_SPU_RAM_AVAILABLE			( END_WAVE_DATA_ADDR - BASE_WAVE_DATA_ADDR )
+
+//#define MUSIC_R_SPU_BUF_LOC		SB_TOP
+//#define MUSIC_L_SPU_BUF_LOC		( MUSIC_R_SPU_BUF_LOC + SB_BUF_SIZE )
+//#define STREAM_SPU_BUF_LOC		( MUSIC_L_SPU_BUF_LOC + SB_BUF_SIZE )
+
+// iop buffer taking streams off of the CD:
+#define MUSIC_IOP_BUFFER_SIZE				( 1024 * (/*192*/192) )  // must be a multiple of 4k!
+#define MUSIC_HALF_IOP_BUFFER_SIZE			( MUSIC_IOP_BUFFER_SIZE / 2 )  // must be a multiple of 2k!
+//#define STREAM_IOP_BUFFER_SIZE				( 1024 * (/*128*/96) )  // must be a multiple of 4k!
+#define STREAM_IOP_BUFFER_SIZE				( 1024 * (/*128*/64) )  // must be a multiple of 4k!
+#define STREAM_HALF_IOP_BUFFER_SIZE			( STREAM_IOP_BUFFER_SIZE / 2 )  // must be a multiple of 2k!
+
+// must match values in pcm_com.c!!!
+#define TOTAL_IOP_BUFFER_SIZE_NEEDED		( ( MUSIC_IOP_BUFFER_SIZE * 2 ) + ( STREAM_IOP_BUFFER_SIZE * NUM_STREAMS ) )
+#define ALLIGN_REQUIREMENT					64
+#define SECTOR_SIZE							( 2048 )
+#define NUM_SECTORS_PER_STREAM_BUFFER		( STREAM_IOP_BUFFER_SIZE / SECTOR_SIZE )
+#define NUM_SECTORS_PER_STREAM_HALF_BUFFER	( NUM_SECTORS_PER_STREAM_BUFFER / 2 )
+#define NUM_SECTORS_PER_MUSIC_BUFFER		( MUSIC_IOP_BUFFER_SIZE / SECTOR_SIZE )
+#define NUM_SECTORS_PER_MUSIC_HALF_BUFFER	( NUM_SECTORS_PER_MUSIC_BUFFER / 2 )
+
+#define MUSIC_L_IOP_OFFSET	0
+#define MUSIC_R_IOP_OFFSET	( MUSIC_HALF_IOP_BUFFER_SIZE )  // interwoven with the L IOP buffer... and shit
+#define STREAM_IOP_OFFSET( ch )				( ( 2 * MUSIC_IOP_BUFFER_SIZE ) + ( STREAM_IOP_BUFFER_SIZE * ( ch ) ) )
+
+#define MUSIC_L_VOICE	22
+#define MUSIC_R_VOICE	23
+#define MUSIC_CORE		1
+
+#define STREAM_VOICE( i ) ( 23 - ( i ) )
+#define STREAM_CORE		0
+
+// RPC command:
+#define EzADPCM_COMMAND_MASK			0xfff0
+#define EzADPCM_CH_MASK					0x000f
+	
+#define EzADPCM_INIT					0x0000
+#define EzADPCM_QUIT					0x0010
+#define EzADPCM_PLAYMUSIC				0x0020
+#define EzADPCM_STOPMUSIC				0x0030
+#define EzADPCM_PLAYSTREAM				0x0040
+#define EzADPCM_STOPSTREAM				0x0070
+#define EzADPCM_STOPSTREAMS				0x00a0
+#define EzADPCM_SETMUSICVOL				0x00b0
+#define EzADPCM_SETSTREAMVOL			0x00c0
+#define EzADPCM_SETSTREAMVOLANDPITCH	0x00d0
+#define EzADPCM_GETMUSICSTATUS			0x00f0
+#define EzADPCM_PAUSEMUSIC				0x0100
+#define EzADPCM_PAUSESTREAM				0x0110
+#define EzADPCM_PAUSESTREAMS			0x0120
+#define EzADPCM_SETSTREAMGLOBVOL		0x0130
+#define EzADPCM_PRELOADMUSIC			0x0140
+#define EzADPCM_PRELOADSTREAM			0x0150
+#define EzADPCM_PLAYPRELOADEDMUSIC		0x0160
+#define EzADPCM_PLAYPRELOADEDSTREAM		0x0170
+#define EzADPCM_SDINIT					0x7ff0
+
+// for GETSTREAMSTATUS or GETMUSICSTATUS:
+#define PCM_STATUS					0x0001f000
+#define PCM_STATUS_IDLE		   		0x00000000
+#define PCM_STATUS_PRELOAD			0x00001000
+#define PCM_STATUS_READY_TO_STOP	0x00002000
+#define PCM_STATUS_NEED_UPDATE		0x00003000
+#define PCM_STATUS_RUNNING			0x00005000
+#define PCM_STATUS_TERMINATE		0x00006000
+
+// flags for GETSTATUS
+#define PCMSTATUS_NEED_MUSIC_BUFFER_0	( 1 << 0 ) // don't change the order of these!!! IMPORTANT!
+#define PCMSTATUS_NEED_STREAM0_BUFFER_0	( 1 << 1 )
+#define PCMSTATUS_NEED_STREAM1_BUFFER_0	( 1 << 2 )
+#define PCMSTATUS_NEED_STREAM2_BUFFER_0	( 1 << 3 )
+#define PCMSTATUS_NEED_MUSIC_BUFFER_1	( 1 << 4 )
+#define PCMSTATUS_NEED_STREAM0_BUFFER_1	( 1 << 5 )
+#define PCMSTATUS_NEED_STREAM1_BUFFER_1	( 1 << 6 )
+#define PCMSTATUS_NEED_STREAM2_BUFFER_1	( 1 << 7 )
+#define PCMSTATUS_MUSIC_PLAYING			( 1 << 8 )
+#define PCMSTATUS_STREAM0_PLAYING		( 1 << 9 )
+#define PCMSTATUS_STREAM1_PLAYING		( 1 << 10 )
+#define PCMSTATUS_STREAM2_PLAYING		( 1 << 11 )
+#define PCMSTATUS_MUSIC_READY			( 1 << 12 )
+#define PCMSTATUS_STREAM0_READY			( 1 << 13 )
+#define PCMSTATUS_STREAM1_READY			( 1 << 14 )
+#define PCMSTATUS_STREAM2_READY			( 1 << 15 )
+#define PCMSTATUS_LOAD_MUSIC			( 1 << 16 )
+#define PCMSTATUS_LOAD_STREAM0			( 1 << 17 )
+#define PCMSTATUS_LOAD_STREAM1			( 1 << 18 )
+#define PCMSTATUS_LOAD_STREAM2			( 1 << 19 )
+#define PCMSTATUS_INITIALIZED			( 1 << 20 )
+#define PCMSTATUS_PANIC					( 1 << 30 )	// Irrecoverable error occurred
+
+// flags for LOAD_STATUS
+#define LOAD_STATUS_DONE_MUSIC_BUFFER_0		( 1 << 0 ) // don't change the order of these!!! IMPORTANT!
+#define LOAD_STATUS_DONE_MUSIC_BUFFER_1		( 1 << 1 )
+#define LOAD_STATUS_DONE_STREAM0_BUFFER_0	( 1 << 2 )
+#define LOAD_STATUS_DONE_STREAM0_BUFFER_1	( 1 << 3 )
+#define LOAD_STATUS_DONE_STREAM1_BUFFER_0	( 1 << 4 )
+#define LOAD_STATUS_DONE_STREAM1_BUFFER_1	( 1 << 5 )
+#define LOAD_STATUS_DONE_STREAM2_BUFFER_0	( 1 << 6 )
+#define LOAD_STATUS_DONE_STREAM2_BUFFER_1	( 1 << 7 )
+
+// macros:
+#define PCMSTATUS_NEED_STREAM_BUFFER_0( xxx )	( PCMSTATUS_NEED_STREAM0_BUFFER_0 << ( xxx ) )
+#define PCMSTATUS_NEED_STREAM_BUFFER_1( xxx )	( PCMSTATUS_NEED_STREAM0_BUFFER_1 << ( xxx ) )
+#define PCMSTATUS_STREAM_PLAYING( xxx )			( PCMSTATUS_STREAM0_PLAYING << ( xxx ) )
+#define PCMSTATUS_STREAM_READY( xxx )			( PCMSTATUS_STREAM0_READY << ( xxx ) )
+#define PCMSTATUS_LOAD_STREAM( xxx )			( PCMSTATUS_LOAD_STREAM0 << ( xxx ) )
+
+// These following macros work for both music and streams, where xxx is 0 for music and
+// starts at 1 for streams
+#define PCMSTATUS_NEED_AUDIO_BUFFER_0( xxx )	( PCMSTATUS_NEED_MUSIC_BUFFER_0 << ( xxx ) )
+#define PCMSTATUS_NEED_AUDIO_BUFFER_1( xxx )	( PCMSTATUS_NEED_MUSIC_BUFFER_1 << ( xxx ) )
+#define PCMSTATUS_AUDIO_PLAYING( xxx )			( PCMSTATUS_MUSIC_PLAYING << ( xxx ) )
+#define PCMSTATUS_AUDIO_READY( xxx )			( PCMSTATUS_MUSIC_READY << ( xxx ) )
+#define PCMSTATUS_LOAD_AUDIO( xxx )				( PCMSTATUS_LOAD_MUSIC << ( xxx ) )
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef	__PLAT_NGPS__
+// Make sure this doesn't break the alignment of SSifCmdStreamReqPacket
+typedef struct
+{
+	int				m_command;
+	int				m_param[3];
+} SStreamRequest;
+
+// Make sure this doesn't break the alignment of SSifCmdSoundReqPacket
+typedef struct
+{
+	unsigned short	m_function;
+	unsigned short	m_entry;
+	unsigned int	m_value;
+	unsigned int	m_arg_0;
+	unsigned int	m_arg_1;
+	unsigned int	m_arg_2;
+	unsigned int	m_ee_dma_id;		// Used on EE side; not sent to IOP side
+	unsigned int	m_pad[2];
+} SSoundRequest;
+
+// SifCmd structures ( keeping them 128-bit aligned )
+
+///////////////////////////////////
+// A stream request from the EE to the IOP
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	SStreamRequest	m_request;
+} SSifCmdStreamRequestPacket;
+
+///////////////////////////////////
+// The result of a stream request
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	int				m_return_value;
+	int				m_status_flags;
+	int				m_pad[2];
+} SSifCmdStreamResultPacket;
+
+///////////////////////////////////
+// This packet is sent from the IOP to the EE after the status flags are updated on the IOP
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	int				m_status_flags;
+	int				m_pad[3];
+} SSifCmdStreamStatusPacket;
+
+///////////////////////////////////
+// This packet is sent from the EE to the IOP after a load finishes
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	int				m_stream_num;		// -1 for music
+	int				m_buffer_num;
+	int				m_bytes_loaded;
+	int				m_pad[1];
+} SSifCmdStreamLoadStatusPacket;
+
+///////////////////////////////////
+// This packet requests that a function is executed on the SPU2 chip
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	SSoundRequest	m_request;
+} SSifCmdSoundRequestPacket;
+
+///////////////////////////////////
+// The result of any GET function (not all request packets get results)
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	unsigned short	m_function;
+	unsigned short	m_entry;
+	unsigned int	m_result;
+	unsigned int	m_panic;			// indicates that request caused a panic on the IOP side
+	int				m_pad;
+} SSifCmdSoundResultPacket;
+
+///////////////////////////////////
+// Stores the request
+typedef struct
+{
+	SStreamRequest	m_request;
+	int				m_request_id;
+} SStreamRequestPacket;
+
+// EE to IOP commands
+#define STREAM_REQUEST_COMMAND		(0)
+										// command number 1 used by fileio.irx
+#define STREAM_LOAD_STATUS_COMMAND	(2)
+#define SOUND_REQUEST_COMMAND		(3)
+
+// IOP to EE commands
+#define STREAM_RESULT_COMMAND		(0)
+										// command number 1 used by fileio.irx
+#define STREAM_STATUS_COMMAND		(2)
+#define SOUND_RESULT_COMMAND		(3)
+
+//#define NUM_COMMAND_HANDLERS 	(0x10)
+
+#endif // __PLAT_NGPS__
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
+
+#endif	//	__GEL_MUSIC_NGPS_PCM_PCM_H__
+
+
diff --git a/Code/Gel/Music/Ngps/Pcm/pcm_com.c b/Code/Gel/Music/Ngps/Pcm/pcm_com.c
new file mode 100644
index 0000000..87a3ef9
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Pcm/pcm_com.c
@@ -0,0 +1,1821 @@
+/* Vag streaming into SPU2 -- from sony sample -- matt may 2001 */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "pcm.h"
+#include "pcmiop.h"
+
+// ================================================================
+
+#define STREAM_PITCH( x ) ( ( ( x ) * 22050 ) / 48000 )
+
+#define CHECK_TRANSFER_INTEGRITY	0
+#define TEST_PLAY_TIMING			0
+
+#define SHOW_STREAM_INFO	0
+#define SHOW_STATE	0
+#define SHOW_SMALL_STREAM_STATE	0
+#define SHOW_SMALL_MUSIC_STATE	0
+#define SHOW_ACTION 0
+#define SHOW_SMALL_ACTION 0
+#define DBUGPRINTF	0
+#define SHOW_TIMING	0
+#define SHOW_STOP	0
+#define SHOW_SMALL_COMMANDS	0
+#define PRINT_EXCESSIVE_WAIT 0
+
+#if SHOW_STREAM_INFO
+#define ShowStreamInfo( x ) _ShowStreamInfo( x )
+#else
+#define ShowStreamInfo( x ) 
+#endif
+
+#if SHOW_SMALL_COMMANDS
+#define ShowSmallCommand printf
+#else
+#define ShowSmallCommand( x )
+#endif
+
+#if SHOW_STOP
+#define ShowStop printf
+#else
+#define ShowStop( x )
+#endif
+
+#if SHOW_TIMING
+#define ShowTime	printf
+#else
+#define ShowTime DoNothing
+void DoNothing( char* text, ... ) { }
+#endif
+
+#if SHOW_ACTION
+#define ShowAction printf
+#else
+#define ShowAction( x )
+#endif
+
+#if SHOW_SMALL_STREAM_STATE
+#define SmallShowStreamState printf( "  %d", whichStream ), printf
+#else
+#define SmallShowStreamState( x )
+#endif
+
+#if SHOW_SMALL_MUSIC_STATE
+#define SmallShowMusicState printf
+#else
+#define SmallShowMusicState( x )
+#endif
+
+#if SHOW_SMALL_ACTION
+#define SmallShowAction printf
+#else
+#define SmallShowAction( x )
+#endif
+
+#if SHOW_STATE
+#define ShowState printf
+#else
+#define ShowState( x )
+#endif
+
+#if DBUGPRINTF
+#define Dbug_Printf( x ) printf( x )
+#else
+#define Dbug_Printf( x )
+#endif
+
+// the program running on the EE side will check to see if any of the buffers need to be filled:
+volatile unsigned int gEECommand = 0;
+
+#define TRANS_DMA_CH_MUSIC		1
+#define TRANS_DMA_CH_STREAM		0
+
+#define _1st 0
+#define _2nd 1
+
+#define TRUE  1
+#define FALSE 0
+
+unsigned int VCount=0;
+unsigned int gThid = 0;
+unsigned int gSem = 0;
+unsigned int gpIopBuf;		// IOP SMEM
+
+struct StreamInfo gStreamInfo[ NUM_STREAMS ];
+struct StreamInfo gMusicInfo;
+volatile int gUsingStreamDMA = 0;
+volatile int gSPUUsingStreamDMA = 0;
+
+// Done on the EE now
+//volatile unsigned int gStreamVolPercent = 100;
+
+// Timing test code
+#if TEST_PLAY_TIMING
+static 			int		test_timing_request_id = 2;
+static			int		test_timing_send_timing_result = FALSE;
+static			int		test_timing_hit_point = FALSE;
+#endif // TEST_PLAY_TIMING
+
+u_int GetNAX(int core,int ch);
+int OutsideMusicSPULoadBuffer();
+int OutsideStreamSPULoadBuffer(int whichStream);
+int DownloadMusic(int whichChannel);
+int DownloadStream(int whichStream);
+void IncMusicBuffer();
+void IncStreamBuffer(int whichStream);
+void StartMusic();
+void StartStream(int whichStream);
+int RefreshStreams( int status );
+void SendStatus();
+
+static int _AdpcmDmaIntMusic( int, void* );
+static int _AdpcmDmaIntStream( int, void* );
+
+#define _L 0
+#define _R 1
+#define _Lch(x) ((x >> 16) & 0xffff)
+#define _Rch(x) (x & 0xffff)
+
+// Timer structure.
+typedef struct TimerCtx {
+    int thread_id;
+    int timer_id;
+    int count;
+} TimerCtx;
+
+TimerCtx gTimer;
+
+#define ONE_HSCAN 1000*1000/60	// Approx 60 frames per second
+
+/**********************************************************************************
+IRQ Interrupt - called, say, once per frame
+***********************************************************************************/
+int TimerFunc( void *common)
+{
+    TimerCtx *tc = (TimerCtx *) common;
+		VCount++;
+		iSignalSema(gSem);	// Signal StreamADPCM to be called
+    return (tc->count);
+}
+
+/**********************************************************************************
+SetTimer - IRQ Interrupt
+***********************************************************************************/
+int SetTimer( TimerCtx* timer )
+{
+    struct SysClock clock;
+    int timer_id;
+
+    USec2SysClock (ONE_HSCAN, & clock);
+    timer->count = clock.low;	/* within 32-bit */
+
+    // Use sysclock timer
+    if ((timer_id = AllocHardTimer (TC_SYSCLOCK, 32, 1)) <= 0) {
+	printf ("Can NOT allocate hard timer ...\n");
+	return (-1);
+    }
+    timer->timer_id = timer_id;
+
+    if (SetTimerHandler (timer_id, timer->count,
+			 (void *)TimerFunc, (void *) timer) != KE_OK) {
+	printf ("Can NOT set timeup timer handler ...\n");
+	return (-1);
+    }
+
+    if (SetupHardTimer (timer_id, TC_SYSCLOCK, TM_NO_GATE, 1) != KE_OK) {
+	printf ("Can NOT setup hard timer ...\n");
+	return (-1);
+    }
+    return 0;
+
+}
+
+/**********************************************************************************
+startTimer - IRQ Interrupt
+***********************************************************************************/
+int StartTimer( TimerCtx* timer )
+{
+    if (StartHardTimer (timer->timer_id) != KE_OK)
+    {
+		printf ("Can NOT start hard timer ...\n");
+		return (-1);
+    }
+    return 0;
+}
+
+// EzADPCM_SDINIT
+void AdpcmSdInit( void )
+{
+    sceSdInit (0);
+
+    //	Disk media: DVD
+    //	Output format: PCM
+    //	Copy guard: normal (one generation recordable / default)
+    sceSdSetCoreAttr (SD_C_SPDIF_MODE, (SD_SPDIF_MEDIA_DVD |
+					SD_SPDIF_OUT_PCM   |
+					SD_SPDIF_COPY_NORMAL));
+    return;
+}
+
+void AdpcmSetUpVoice( int core, int vnum, int pSpuBuf )
+{
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_VOLL, 0 );
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_VOLR, 0 );
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_PITCH, core == MUSIC_CORE ? DEFAULT_PITCH : STREAM_PITCH( DEFAULT_PITCH ) );
+//    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_ADSR1, 0x000f );
+//    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_ADSR2, 0x1fc0 );
+    sceSdSetAddr  ( core | ( vnum << 1 ) | SD_VA_SSA,  pSpuBuf );
+    return;
+}
+
+// EzADPCM_INIT
+int AdpcmInit( int pIopBuf )
+{
+	int i;
+	if ( gEECommand & PCMSTATUS_INITIALIZED )
+	{
+		return ( 0 );
+	}
+
+    if ( gSem == 0 )
+	{
+		struct SemaParam sem;
+		sem.initCount = 0;
+		sem.maxCount = 1;
+		sem.attr = AT_THFIFO;
+		gSem = CreateSema (&sem);
+    }
+    if (gThid == 0)
+	{
+		struct ThreadParam param;
+		param.attr         = TH_C;
+		param.entry        = RefreshStreams;
+		param.initPriority = BASE_priority-3;
+		param.stackSize    = 0x800; // was 800
+		param.option = 0;
+		gThid = CreateThread (¶m);
+		printf( "EzADPCM: create thread ID= %d\n", gThid );
+		StartThread (gThid, (u_long) NULL);
+    }
+
+	sceSdSetTransIntrHandler( TRANS_DMA_CH_MUSIC, ( sceSdTransIntrHandler ) _AdpcmDmaIntMusic, ( void * ) &gSem );
+    sceSdSetTransIntrHandler( TRANS_DMA_CH_STREAM, ( sceSdTransIntrHandler ) _AdpcmDmaIntStream, ( void * ) &gSem );
+	SetTimer(&gTimer);		// Setup IRQ interrupt
+	StartTimer(&gTimer);		// Start IRQ interrupt (calls TimerFunc)
+	
+	AdpcmSetUpVoice( MUSIC_CORE, MUSIC_L_VOICE, MUSIC_L_SPU_BUF_LOC );
+	AdpcmSetUpVoice( MUSIC_CORE, MUSIC_R_VOICE, MUSIC_R_SPU_BUF_LOC );
+	for ( i = 0; i < NUM_STREAMS; i++ )
+	{
+		AdpcmSetUpVoice( STREAM_CORE, STREAM_VOICE( i ), STREAM_SPU_BUF_LOC( i ) );
+	}
+
+	memset( gStreamInfo, 0, sizeof( struct StreamInfo ) * NUM_STREAMS );
+	memset( &gMusicInfo, 0, sizeof( struct StreamInfo ) );
+	
+	gpIopBuf = pIopBuf;
+	gEECommand |= PCMSTATUS_INITIALIZED;
+	//printf( "PCM Irx iop buf %d\n", gpIopBuf );
+	//Dbug_Printf( "PCM irx is initialized\n" );
+    return gThid;
+}
+
+// EzADPCM_QUIT
+void AdpcmQuit( void )
+{
+    DeleteThread (gThid);
+    gThid = 0;
+#if 0
+    DeleteSema (gSem);
+    gSem = 0;
+#endif
+    return;
+}
+
+// EzADPCM_PRELOADMUSIC
+// EzADPCM_PLAYMUSIC
+int AdpcmPlayMusic( int size, int preload_only )
+{
+    extern void AdpcmSetMusicVolumeDirect( unsigned int );
+
+    if ( gMusicInfo.status != PCM_STATUS_IDLE )
+	{
+		ShowAction( "NOTE NOTE NOTE NOTE Can't play music -- music isn't in Idle state.\n" );
+		return -1;
+    }
+
+    AdpcmSetMusicVolumeDirect( gMusicInfo.volume );
+
+	if (preload_only)
+	{
+		gMusicInfo.m_preloadMode = TRUE;
+	}
+	else
+	{
+		gMusicInfo.m_preloadMode = FALSE;
+	}
+				
+	gMusicInfo.status = PCM_STATUS_PRELOAD;
+	gMusicInfo.loadState = MUSIC_LOAD_STATE_IDLE;
+	gMusicInfo.size = size;
+	gMusicInfo.m_iopBufLoaded[0] = TRUE;
+	gMusicInfo.m_iopBufLoaded[1] = FALSE;
+	gEECommand &= ~PCMSTATUS_NEED_MUSIC_BUFFER_0;
+	if (size > MUSIC_HALF_IOP_BUFFER_SIZE)
+	{
+		gEECommand |= PCMSTATUS_NEED_MUSIC_BUFFER_1;
+	}
+	gEECommand |= PCMSTATUS_MUSIC_PLAYING;
+	ShowAction( "Starting music\n" );
+	SmallShowAction( "-mb" );
+    return 0;
+}
+
+void AdpcmSetStreamVolume( unsigned int vol, int whichStream );
+
+// EzADPCM_PRELOADMUSIC
+// EzADPCM_PLAYSTREAM
+int AdpcmPlayStream( int size, int whichStream, unsigned int vol, unsigned int pitch, int preload_only )
+{
+    if ( gStreamInfo[ whichStream ].status != PCM_STATUS_IDLE )
+	{
+		printf( "trying to play stream when stream already playing!\n" );
+		return -1;
+    }
+
+	if (preload_only)
+	{
+		gStreamInfo[ whichStream ].m_preloadMode = TRUE;
+	}
+	else
+	{
+		gStreamInfo[ whichStream ].m_preloadMode = FALSE;
+		AdpcmSetStreamVolume( vol, whichStream );
+		gStreamInfo[ whichStream ].pitch = STREAM_PITCH( pitch );
+	}
+
+	// have to make sure it's okay to use the stream DMA first...
+	gStreamInfo[ whichStream ].status = PCM_STATUS_PRELOAD;
+	gStreamInfo[ whichStream ].size = size;
+	gStreamInfo[ whichStream ].m_iopBufLoaded[0] = TRUE;
+	gStreamInfo[ whichStream ].m_iopBufLoaded[1] = FALSE;
+	gEECommand &= ~( PCMSTATUS_NEED_STREAM0_BUFFER_0 << whichStream );
+	if (size > STREAM_HALF_IOP_BUFFER_SIZE)
+	{
+		gEECommand |= PCMSTATUS_NEED_STREAM0_BUFFER_1 << whichStream;
+	}
+	gStreamInfo[ whichStream ].loadState = STREAM_LOAD_STATE_IDLE;
+	gEECommand |= ( PCMSTATUS_STREAM_PLAYING( whichStream ) );
+	ShowAction( "Starting stream\n" );
+	SmallShowAction( "-sb" );
+	return 0;
+}
+
+// EzADPCM_PLAYPRELOADEDMUSIC
+int AdpcmPlayPreloadedMusic()
+{
+	// Tell update loop it is OK to play
+	gMusicInfo.m_preloadMode = FALSE;
+	SignalSema(gSem);	// Signal RefreshStreams() to be called
+
+	ShowAction( "Playing preloaded music\n" );
+	SmallShowAction( "-ppm" );
+	return 0;
+}
+
+// EzADPCM_PLAYPRELOADEDSTREAM
+int AdpcmPlayPreloadedStream(int whichStream, unsigned int vol, unsigned int pitch)
+{
+	AdpcmSetStreamVolume( vol, whichStream );
+	gStreamInfo[ whichStream ].pitch = STREAM_PITCH( pitch );
+
+	// Tell update loop it is OK to play
+	gStreamInfo[ whichStream ].m_preloadMode = FALSE;
+	SignalSema(gSem);	// Signal RefreshStreams() to be called
+
+	ShowAction( "Playing preloaded stream\n" );
+	SmallShowAction( "-pps" );
+	return 0;
+}
+
+/* internal */
+void _AdpcmSetMusicVoiceMute( void )
+{
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1) | SD_VP_VOLL, 0);
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1) | SD_VP_VOLR, 0);
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1) | SD_VP_VOLL, 0);
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1) | SD_VP_VOLR, 0);
+    return;
+}
+
+/* internal */
+void _AdpcmSetStreamVoiceMute( int whichVoice )
+{
+    sceSdSetParam( STREAM_CORE | ( STREAM_VOICE( whichVoice ) << 1) | SD_VP_VOLL, 0);
+    sceSdSetParam( STREAM_CORE | ( STREAM_VOICE( whichVoice ) << 1) | SD_VP_VOLR, 0);
+    return;
+}
+
+void StopMusic( void )
+{
+	sceSdSetCoreAttr( MUSIC_CORE | SD_C_IRQ_ENABLE, 0 );
+	_AdpcmSetMusicVoiceMute( );
+	sceSdSetSwitch( MUSIC_CORE | SD_S_KOFF, ( 1 << MUSIC_R_VOICE ) | ( 1 << MUSIC_L_VOICE ) );
+	ShowAction( "Stopping music\n" );
+    SmallShowAction( "-!sm!" );
+	gMusicInfo.stop = 0;
+	gMusicInfo.loadState = MUSIC_LOAD_STATE_IDLE;
+	//gMusicInfo.m_iopBufLoaded[0] = FALSE;		// This should not be necessary
+	//gMusicInfo.m_iopBufLoaded[1] = FALSE;
+	gEECommand &= ~( PCMSTATUS_MUSIC_PLAYING | PCMSTATUS_MUSIC_READY | PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 );
+	gMusicInfo.status = PCM_STATUS_IDLE;
+}
+
+void StopStream( int whichStream )
+{
+	sceSdSetCoreAttr( STREAM_CORE | SD_C_IRQ_ENABLE, 0 );
+	_AdpcmSetStreamVoiceMute( whichStream );
+	sceSdSetSwitch( STREAM_CORE | SD_S_KOFF, ( 1 << STREAM_VOICE( whichStream ) ) );
+	ShowAction( "Stopping stream\n" );
+    SmallShowAction( "-!ss!" );
+	gStreamInfo[ whichStream ].stop = 0;
+	gStreamInfo[ whichStream ].loadState = STREAM_LOAD_STATE_IDLE;
+	//gStreamInfo[ whichStream ].m_iopBufLoaded[0] = FALSE;		// This should not be necessary
+	//gStreamInfo[ whichStream ].m_iopBufLoaded[1] = FALSE;
+	gEECommand &= ~( ( PCMSTATUS_STREAM0_PLAYING | PCMSTATUS_STREAM0_READY | PCMSTATUS_NEED_STREAM0_BUFFER_0 | PCMSTATUS_NEED_STREAM0_BUFFER_1 ) << whichStream );
+	gStreamInfo[ whichStream ].status = PCM_STATUS_IDLE;
+}
+
+#define NUM_WAIT_CLICKS 10000
+
+// EzADPCM_STOPMUSIC
+int AdpcmStopMusic( void )
+{
+	int i;
+	int j = 0;
+
+	if ( gMusicInfo.status != PCM_STATUS_IDLE )
+	{
+		ShowStop( "stopmusic" );
+		gMusicInfo.stop++;
+		SignalSema(gSem);	// Signal RefreshStreams() to be called
+		// Why the hell are we waiting????  This is VERY lame!
+		while ( gMusicInfo.status != PCM_STATUS_IDLE )
+		{
+			for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+				;
+			if ( j++ > NUM_WAIT_CLICKS )
+			{
+				j = 0;
+				printf( "...Waiting for music stop...\n" );
+			}
+		}
+		ShowStop( "\n" );
+		return ( 1 );
+    }
+	return ( 0 );
+}
+
+// EzADPCM_STOPSTREAM
+int AdpcmStopStream( int whichStream )
+{
+	int i;
+	int j = 0;
+
+	if ( gStreamInfo[ whichStream ].status != PCM_STATUS_IDLE )
+	{
+		ShowStop( "Stop stream" );
+		gStreamInfo[ whichStream ].stop++;
+		SignalSema(gSem);	// Signal RefreshStreams() to be called
+		// Why the hell are we waiting????  This is VERY lame!
+		while ( gStreamInfo[ whichStream ].status != PCM_STATUS_IDLE )
+		{
+			for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+				;
+			if ( j++ > NUM_WAIT_CLICKS )
+			{
+				printf( "...Waiting for stream %d stop...\n", whichStream );
+				j = 0;
+			}
+		}
+		ShowStop( "\n" );
+		SmallShowAction( "-ss" );
+		ShowAction( "Stopped stream\n" );
+		return ( 1 );
+    }
+	return ( 0 );
+}
+
+// EzADPCM_SETMUSICVOL
+void AdpcmSetMusicVolume( unsigned int vol )
+{
+    gMusicInfo.volume = vol;
+    gMusicInfo.volumeSet = 1;
+    return;
+}
+
+// EzADPCM_SETMUSICVOLDIRECT
+void AdpcmSetMusicVolumeDirect( unsigned int vol )
+{
+    gMusicInfo.volume = vol;
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_VOLL, vol );
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_VOLR, 0 );
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_VOLL, 0 );
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_VOLR, vol );
+    return;
+}
+
+// Done on the EE now
+//void AdpcmSetStreamGlobVol( unsigned int volPercent )
+//{
+//	gStreamVolPercent = volPercent;
+//}
+
+#define PERCENT_MULT ( ( 1 << 16 ) / 100 )
+#define PERCENT( x, y ) ( ( ( x ) * ( y ) * PERCENT_MULT ) >> 16 )
+
+// EzADPCM_SETSTREAMVOL
+void AdpcmSetStreamVolume( unsigned int vol, int whichStream )
+{
+    gStreamInfo[ whichStream ].volume = vol;
+    gStreamInfo[ whichStream ].volumeSet = 1;
+    return;
+}
+
+// EzADPCM_SETSTREAMVOLDIRECT
+void AdpcmSetStreamVolumeDirect( unsigned int vol, int whichStream )
+{
+    //sceSdSetParam ( STREAM_CORE | ( STREAM_VOICE( whichStream ) << 1 ) | SD_VP_VOLL, PERCENT( gStreamVolPercent, _Lch( vol ) ) );
+    //sceSdSetParam ( STREAM_CORE | ( STREAM_VOICE( whichStream ) << 1 ) | SD_VP_VOLR, PERCENT( gStreamVolPercent, _Rch( vol ) ) );
+    sceSdSetParam ( STREAM_CORE | ( STREAM_VOICE( whichStream ) << 1 ) | SD_VP_VOLL, _Lch( vol ) );
+    sceSdSetParam ( STREAM_CORE | ( STREAM_VOICE( whichStream ) << 1 ) | SD_VP_VOLR, _Rch( vol ) );
+    return;
+}
+
+// Shouldn't need these unless debugging or something --
+// Instead just get gEECommand each frame and act accordingly.
+
+// EzADPCM_GETMUSICSTATUS
+unsigned int AdpcmGetMusicStatus( void )
+{
+	return gMusicInfo.status;
+}
+
+// EzADPCM_GETSTREAMSTATUS
+/*unsigned int AdpcmGetStreamStatus( int whichStream )
+{
+    return gStreamInfo[ whichStream ].status;
+} */
+
+static volatile unsigned int sEELastCommand = 0xFFFFFFFF;
+
+// EzADPCM_GETSTATUS
+unsigned int AdpcmGetStatus( void )
+{
+	unsigned int temp;
+	temp = gEECommand;
+	gEECommand &= ~( PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 | 
+					PCMSTATUS_NEED_STREAM0_BUFFER_0 | PCMSTATUS_NEED_STREAM0_BUFFER_1 |
+					PCMSTATUS_NEED_STREAM1_BUFFER_0 | PCMSTATUS_NEED_STREAM1_BUFFER_1 |
+					PCMSTATUS_NEED_STREAM2_BUFFER_0 | PCMSTATUS_NEED_STREAM2_BUFFER_1 );
+	sEELastCommand = gEECommand;
+#if SHOW_SMALL_COMMANDS
+	if ( temp & ( PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 | 
+					PCMSTATUS_NEED_STREAM0_BUFFER_0 | PCMSTATUS_NEED_STREAM0_BUFFER_1 |
+					PCMSTATUS_NEED_STREAM1_BUFFER_0 | PCMSTATUS_NEED_STREAM1_BUFFER_1 |
+					PCMSTATUS_NEED_STREAM2_BUFFER_0 | PCMSTATUS_NEED_STREAM2_BUFFER_1 ) )
+	{
+		ShowSmallCommand( " <_L_>" );
+	}
+#endif
+	return temp;
+}
+
+int AdpcmHasStatusChanged( void )
+{
+	return sEELastCommand != gEECommand;
+}
+
+// ================================================================
+
+#define _ADPCM_MARK_START 0x04
+#define _ADPCM_MARK_LOOP  0x02
+#define _ADPCM_MARK_END   0x01
+
+#define _AdpcmSetMarkFINAL(a,s) { \
+  *( ( unsigned int * ) ( ( a ) + ( s ) - 16 ) ) = 0; \
+  *( ( unsigned int * ) ( ( a ) + ( s ) - 12 ) ) = 0; \
+  *( ( unsigned int * ) ( ( a ) + ( s ) - 8 ) ) = 0; \
+  *( ( unsigned int * ) ( ( a ) + ( s ) - 4 ) ) = 0; \
+  *((unsigned char *)((a)+(s)-0x0f)) = (_ADPCM_MARK_LOOP | _ADPCM_MARK_START | _ADPCM_MARK_END); \
+  FlushDcache (); }
+
+
+// Start of loop - note that a (address is either the start or mid address of the IOP buffer. s is half the buffer size)
+#define _AdpcmSetMarkSTART(a,s) { \
+  *((unsigned char *)((a)+1)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_START); \
+  *((unsigned char *)((a)+0x10+1)) = \
+	_ADPCM_MARK_LOOP; \
+  *((unsigned char *)((a)+(s)-0x0f)) = \
+	_ADPCM_MARK_LOOP; \
+	FlushDcache (); }
+
+// End of loop - note that a (address is either the start or mid address of the IOP buffer. s is half the buffer size)
+#define _AdpcmSetMarkEND(a,s) { \
+  *((unsigned char *)((a)+1)) = \
+	 _ADPCM_MARK_LOOP; \
+  *((unsigned char *)((a)+0x10+1)) = \
+	_ADPCM_MARK_LOOP; \
+  *((unsigned char *)((a)+(s)-0x0f)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_END); \
+	FlushDcache (); }
+
+// Preload Start marker - note that a (address is the start address of the IOP buffer. s is buffer size)
+
+#define _AdpcmSetMarkSTARTpre(a,s) { \
+  *((unsigned char *)((a)+1)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_START); \
+  *((unsigned char *)((a)+0x10+1)) = \
+	_ADPCM_MARK_LOOP; \
+	FlushDcache (); }
+
+// Preload End marker - note that a (address is the start address of the IOP buffer. s is buffer size)
+#define _AdpcmSetMarkENDpre(a,s) { \
+  *((unsigned char *)((a)+(s)-0xf)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_END); \
+	FlushDcache (); }
+
+/* internal */
+static int _AdpcmDmaIntMusic(int ch, void *common)	// DMA Interrupt -- when transfering data to SPU2 from IOP
+{
+	//Kprintf("Got music IRQ for channel %d\n", ch);
+	if ( gMusicInfo.status != PCM_STATUS_IDLE )
+	{
+		gMusicInfo.m_spuTransDone = TRUE;
+		return 1;
+	}
+	return 0;
+}
+
+/* internal */
+static int _AdpcmDmaIntStream(int ch, void *common)	// DMA Interrupt -- when transfering data to SPU2 from IOP
+{
+	if ( gUsingStreamDMA )
+	{
+		int whichStream = gUsingStreamDMA - 1;
+		//Kprintf("Received Stream DMA 0 callback\n");
+		if ( gStreamInfo[ whichStream ].status != PCM_STATUS_IDLE )
+		{
+			gStreamInfo[ whichStream ].m_spuTransDone = TRUE;
+			gUsingStreamDMA = 0;
+			return 1;
+		}
+	}
+	else if (gSPUUsingStreamDMA)
+	{
+		gSPUUsingStreamDMA = 0;
+		//Kprintf("Received SPU DMA 0 callback\n");
+	}
+	else
+	{
+		//Kprintf("Received outside DMA 0 callback\n");
+	}
+	return 0;
+}
+
+void UpdateMusic( void )
+{
+	struct StreamInfo *pInfo;
+	pInfo = &gMusicInfo;
+	switch ( pInfo->status )
+	{
+		case PCM_STATUS_IDLE:
+			break;
+
+		case PCM_STATUS_PRELOAD:
+			switch ( pInfo->loadState )
+			{
+				case ( MUSIC_LOAD_STATE_IDLE ):
+				{
+					ShowState( "music preload l\n" );
+					SmallShowMusicState( "-MPL" );
+					pInfo->m_spuBufSide = _1st;
+					pInfo->m_iopBufIndex = _1st;
+					pInfo->m_iopOffset = 0;
+					pInfo->remaining = pInfo->size;
+
+					// preload left:
+					if (DownloadMusic(_L))
+					{
+						pInfo->loadState++;
+					}
+
+					break;
+				}
+				case ( MUSIC_LOAD_STATE_PRELOADING_L ):
+				{
+					// Check that transfer is done
+					if (!pInfo->m_spuTransDone)
+					{
+						break;
+					}
+
+					if ( pInfo->stop )		// Don't stop until we know the first CD stream and DMA to SPU have finished
+					{
+						StopMusic( );
+						break;
+					}
+
+					ShowState( "music preload r\n" );
+					SmallShowMusicState( "-MPR" );
+
+					// preload right:
+					DownloadMusic(_R);
+
+					pInfo->loadState++;
+					break;
+				}	
+				case ( MUSIC_LOAD_STATE_PRELOADING_R ):
+				{
+					// Check that transfer is done
+					if (!pInfo->m_spuTransDone)
+					{
+						break;
+					}
+
+					if ( pInfo->stop )		// Don't stop until we the DMA to SPU has finished
+					{
+						StopMusic( );
+						break;
+					}
+
+					// This will let the EE know that we are ready to play
+					gEECommand |= PCMSTATUS_MUSIC_READY;
+
+					// Now check if we are in a preload_only mode
+					if (pInfo->m_preloadMode)
+					{
+						// We need to wait until we get a message from the EE to start
+						break;
+					}
+
+					ShowState( "Music starting -- waiting for IRQ\n" );
+					SmallShowMusicState( " -MSIRQ" );
+
+					pInfo->status = PCM_STATUS_RUNNING;
+					IncMusicBuffer();
+
+					// Play music
+					StartMusic();
+#if TEST_PLAY_TIMING
+					if (test_timing_send_timing_result)
+					{
+						test_timing_hit_point = TRUE;
+					}
+#endif // TEST_PLAY_TIMING
+
+					pInfo->loadState++;
+					break;
+				}
+			}
+			break;
+
+		case PCM_STATUS_RUNNING:
+			pInfo->m_spuCurAddr = GetNAX( MUSIC_CORE, MUSIC_L_VOICE );
+
+			switch ( pInfo->loadState )
+			{
+				case ( MUSIC_LOAD_STATE_WAITING_FOR_REFRESH ):
+				{
+					if ( pInfo->stop )
+					{
+						//pInfo->status = PCM_STATUS_READY_TO_STOP;
+						StopMusic( );
+						return;
+					}
+
+					// Wait for SPU buffer cross
+					if (!OutsideMusicSPULoadBuffer())
+					{
+						break;
+					}
+
+					ShowState( "Music Running -- Wait For Last Load\n" );
+					SmallShowStreamState( "-MRWLL" );
+
+					// Change state and go immediately into it
+					pInfo->loadState++;
+				}
+
+				case ( MUSIC_LOAD_STATE_WAITING_FOR_LAST_LOAD ):
+				{
+					if ( pInfo->stop )
+					{
+						StopMusic( );
+						break;
+					}
+
+					// After we got this far, we've crossed the SPU buffer and we
+					// know we need to download.  But if the new IOP buffer isn't
+					// loaded and download before we cross SPU buffers AGAIN, then 
+					// we know the stream has failed.  Time to panic!
+					if (!OutsideMusicSPULoadBuffer())
+					{
+						if (!(gEECommand & PCMSTATUS_PANIC))
+						{
+							// Print only once
+							Kprintf("ERROR: Music did not load data in time.  In SPU buffer %d and pos %x\n", pInfo->m_spuBufSide, pInfo->m_spuCurAddr - MUSIC_L_SPU_BUF_LOC);
+						}
+						gEECommand |= PCMSTATUS_PANIC;
+						break;
+					}
+
+					ShowState( "Music Running -- Loading L\n" );
+					SmallShowMusicState( "-MRLL" );
+
+					// load in left side:
+					if (DownloadMusic(_L))
+					{
+						pInfo->loadState++;
+					}
+
+					break;
+				}
+
+				case ( MUSIC_LOAD_STATE_LOADING_L ):
+				{
+					// Check that transfer is done
+					if (!pInfo->m_spuTransDone)
+					{
+						break;
+					}
+
+					//Kprintf( "Music Running -- Loading R at offset %x and address %x\n", pInfo->m_iopOffset, gpIopBuf + MUSIC_R_IOP_OFFSET + pInfo->m_iopOffset );
+					ShowState( "Music Running -- Loading R\n" );
+					SmallShowMusicState( "-MRLR" );
+					
+					// load in right side:
+					DownloadMusic(_R);
+
+					pInfo->loadState++;
+					break;
+				}
+				case ( MUSIC_LOAD_STATE_LOADING_R ):
+				{
+					// Check that transfer is done
+					if (!pInfo->m_spuTransDone)
+					{
+						break;
+					}
+
+					ShowState( "Music Running -- Waiting for IRQ\n" );
+					SmallShowMusicState( "-MRIRQ" );
+
+					IncMusicBuffer();
+
+					pInfo->loadState = MUSIC_LOAD_STATE_WAITING_FOR_REFRESH;
+					break;
+				}
+				default:
+					printf( "Unknown music loading state %d\n", pInfo->loadState );
+					break;
+			}
+			break;
+
+		case PCM_STATUS_TERMINATE: 
+			pInfo->m_spuCurAddr = GetNAX( MUSIC_CORE, MUSIC_L_VOICE );
+
+			if ( !pInfo->stop && ( ( pInfo->m_spuCurAddr > pInfo->m_spuEndAddr ) || ( pInfo->m_spuCurAddr <= pInfo->m_spuEndAddr - 16 ) ) )
+			{
+				break;
+			}
+
+			ShowState( "Music at end\n" );
+
+		case PCM_STATUS_READY_TO_STOP:
+			ShowState( "Music terminate\n" );
+			SmallShowMusicState( "-MT!!!" );
+			StopMusic( );
+			break;
+
+		default:
+			printf( "unknown music status in pcm irx!!!\n" );
+			break;
+	}
+}
+
+void UpdateStream( int whichStream )
+{
+	struct StreamInfo *pInfo;
+	pInfo = &gStreamInfo[ whichStream ];
+
+	switch ( pInfo->status )
+	{
+		case PCM_STATUS_IDLE:
+			break;
+
+		case PCM_STATUS_PRELOAD:
+			switch ( pInfo->loadState )
+			{
+				case ( STREAM_LOAD_STATE_IDLE ):
+				{
+					ShowState( "stream preload l\n" );
+					SmallShowStreamState( "-SPL" );
+
+					pInfo->m_spuBufSide = _1st;
+					pInfo->m_iopBufIndex = _1st;
+					pInfo->m_iopOffset = 0;
+					pInfo->remaining = pInfo->size;
+
+					if (DownloadStream(whichStream))
+					{
+						pInfo->loadState++;
+					}
+
+					break;
+				}
+				case ( STREAM_LOAD_STATE_PRELOADING ):
+				{
+					ShowState( "Stream starting -- waiting for IRQ\n" );
+					SmallShowStreamState( "-SSIRQ" );
+
+					// Check that transfer is done
+					if (!pInfo->m_spuTransDone)
+					{
+						break;
+					}
+
+					if ( pInfo->stop )		// Don't stop until we know the first CD stream and DMA to SPU have finished
+					{
+						StopStream( whichStream );
+						break;
+					}
+
+					// This will let the EE know that we are ready to play
+					gEECommand |= PCMSTATUS_STREAM_READY(whichStream);
+
+					// Now check if we are in a preload_only mode
+					if (pInfo->m_preloadMode)
+					{
+						// We need to wait until we get a message from the EE to start
+						break;
+					}
+
+					pInfo->status = PCM_STATUS_RUNNING;
+					IncStreamBuffer(whichStream);
+
+					// Play the stream
+					StartStream(whichStream);
+					//Kprintf("Started playing stream %d; paused = %d; volume = %x\n", whichStream, gStreamInfo[ whichStream ].paused, pInfo->volume);
+
+					pInfo->loadState++;
+					break;
+				}
+			}
+			break;
+
+		case PCM_STATUS_RUNNING:
+			pInfo->m_spuCurAddr = GetNAX( STREAM_CORE, STREAM_VOICE( whichStream ) );
+
+			switch ( pInfo->loadState )
+			{
+				case ( STREAM_LOAD_STATE_WAITING_FOR_REFRESH ):
+				{
+					if ( pInfo->stop )
+					{
+						//pInfo->status = PCM_STATUS_READY_TO_STOP;
+						StopStream( whichStream );
+						return;
+					}
+
+					// Wait for SPU buffer cross
+					if (!OutsideStreamSPULoadBuffer(whichStream))
+					{
+						break;
+					}
+
+					ShowState( "Stream Running -- Wait For Last Load\n" );
+					SmallShowStreamState( "-SRWLL" );
+
+					// Inc state and start it directly
+					pInfo->loadState++;
+				}
+
+				case ( STREAM_LOAD_STATE_WAITING_FOR_LAST_LOAD ):
+				{
+					if ( pInfo->stop )
+					{
+						StopStream( whichStream );
+						break;
+					}
+
+					// After we got this far, we've crossed the SPU buffer and we
+					// know we need to download.  But if the new IOP buffer isn't
+					// loaded and download before we cross SPU buffers AGAIN, then 
+					// we know the stream has failed.  Time to panic!
+					if (!OutsideStreamSPULoadBuffer(whichStream))
+					{
+						if (!(gEECommand & PCMSTATUS_PANIC))
+						{
+							// Print only once
+							Kprintf("ERROR: Stream %d did not load data in time.  In SPU buffer %d and pos %x\n", whichStream, pInfo->m_spuBufSide, pInfo->m_spuCurAddr - STREAM_SPU_BUF_LOC( whichStream ));
+						}
+						gEECommand |= PCMSTATUS_PANIC;
+						break;
+					}
+
+					ShowState( "Stream Running -- Loading\n" );
+					SmallShowStreamState( "-SRL" );
+
+					if (DownloadStream(whichStream))
+					{
+						pInfo->loadState++;
+					}
+
+					break;
+				}
+				case ( STREAM_LOAD_STATE_LOADING ):
+				{
+					// Check that transfer is done
+					if (!pInfo->m_spuTransDone)
+					{
+						break;
+					}
+
+					ShowState( "Stream Running -- Waiting for IRQ\n" );
+					SmallShowStreamState( "-SRIRQ" );
+
+					IncStreamBuffer(whichStream);
+
+					pInfo->loadState = STREAM_LOAD_STATE_WAITING_FOR_REFRESH;
+					break;
+				}
+				default:
+					printf( "Unknown Stream loading state %d\n", pInfo->loadState );
+					break;
+			}
+			break;
+
+		case PCM_STATUS_TERMINATE: 
+			pInfo->m_spuCurAddr = GetNAX( STREAM_CORE, STREAM_VOICE( whichStream ) );
+
+			if ( !pInfo->stop && ( ( pInfo->m_spuCurAddr > pInfo->m_spuEndAddr ) || ( pInfo->m_spuCurAddr <= pInfo->m_spuEndAddr - 16 ) ) )
+			{
+				break;
+			}
+
+		case PCM_STATUS_READY_TO_STOP:
+			if ( gUsingStreamDMA != (whichStream + 1) )
+			{
+				ShowState( "Stream terminate\n" );
+				SmallShowStreamState( "-ST!!!" );
+				StopStream( whichStream );
+			}
+			break;
+
+		default:
+			printf( "unknown stream status in pcm irx!!!\n" );
+			break;
+	}
+}
+
+// Reserve Stream DMA channel
+int LockDMA( int whichStream )
+{
+	if ( gSPUUsingStreamDMA )
+	{
+		SmallShowStreamState( " -|spu_no" );		
+		//Kprintf("******* Can't lock DMA for Stream %d because of SPU\n", whichStream);
+		return FALSE;
+	}
+	if ( gUsingStreamDMA )
+	{
+		SmallShowStreamState( " -|no" );		
+		//Kprintf("******* Can't lock DMA for Stream %d\n", whichStream);
+		return FALSE;
+	}
+	else
+	{
+		SmallShowStreamState( " -|ok" );		
+		gUsingStreamDMA = 1 + whichStream;
+		//Kprintf("Locked DMA for Stream %d\n", whichStream);
+		return TRUE;
+	}
+}
+
+/**********************************************************************************
+GetNAX
+	Get current NAX (Address where channel is playing in SPU RAM)
+	Code must check the NAX 3 times to decide which result is correct.
+***********************************************************************************/
+u_int GetNAX(int core,int ch)
+{
+	u_int pos,pos2,pos3;
+	ch <<= 1;
+
+	while(1)
+	{
+		pos=sceSdGetAddr(core|ch|SD_VA_NAX);
+		pos2=sceSdGetAddr(core|ch|SD_VA_NAX);
+		pos3=sceSdGetAddr(core|ch|SD_VA_NAX);
+
+		if (pos == pos2)
+			return(pos);
+		else if (pos2 == pos3)
+			return(pos2);
+	}
+}
+
+#if CHECK_TRANSFER_INTEGRITY
+void CheckTransfer( int dmaCh )
+{
+	if ( !sceSdVoiceTransStatus( dmaCh, SD_TRANS_STATUS_CHECK ) )
+	{
+		int i;
+		printf( "waiting for voicetrans" );
+		while ( !sceSdVoiceTransStatus( dmaCh, SD_TRANS_STATUS_CHECK ) )
+		{
+			printf( "." );
+			for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+				;
+		}
+		printf( "\n" );
+	}
+}
+#endif					
+
+// Checks to see if current SPU playing address is outside the loading buffer
+int OutsideMusicSPULoadBuffer()
+{
+	struct StreamInfo *pInfo;
+	pInfo = &gMusicInfo;
+
+	if ( pInfo->m_spuBufSide == _1st )
+	{
+		if ( pInfo->m_spuCurAddr > ( MUSIC_L_SPU_BUF_LOC + SB_BUF_HALF ) )
+		{
+#if CHECK_TRANSFER_INTEGRITY
+			CheckTransfer( TRANS_DMA_CH_MUSIC );
+#endif					
+			return TRUE;
+			//Kprintf("music position past end for SPU buffer %d\n", pInfo->bufSide);
+		}
+	}
+	else
+	{
+		if ( pInfo->m_spuCurAddr < ( MUSIC_L_SPU_BUF_LOC + SB_BUF_HALF ) )
+		{
+#if CHECK_TRANSFER_INTEGRITY
+			CheckTransfer( TRANS_DMA_CH_MUSIC );
+#endif					
+			return TRUE;
+			//Kprintf("music position past end for SPU buffer %d\n", pInfo->bufSide);
+		}
+	}
+
+	return FALSE;
+}
+
+// Checks to see if current SPU playing address is outside the loading buffer
+int OutsideStreamSPULoadBuffer(int whichStream)
+{
+	struct StreamInfo *pInfo;
+	pInfo = &gStreamInfo[ whichStream ];
+
+	if ( pInfo->m_spuBufSide == _1st )
+	{
+		if ( pInfo->m_spuCurAddr > ( STREAM_SPU_BUF_LOC( whichStream ) + SB_BUF_HALF ) )
+		{
+#if CHECK_TRANSFER_INTEGRITY
+			CheckTransfer( TRANS_DMA_CH_STREAM );
+#endif
+			return TRUE;
+		}
+	}
+	else
+	{
+		if ( pInfo->m_spuCurAddr < ( STREAM_SPU_BUF_LOC( whichStream ) + SB_BUF_HALF ) )
+		{
+#if CHECK_TRANSFER_INTEGRITY
+			CheckTransfer( TRANS_DMA_CH_STREAM );
+#endif					
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
+
+// Downloads music data from the IOP to a SPU buffer
+int DownloadMusic(int whichChannel)
+{
+	int music_iop_offset;
+	int music_spu_addr;
+
+	struct StreamInfo *pInfo;
+	pInfo = &gMusicInfo;
+
+	if (whichChannel == _L)
+	{
+		//int iopBufferIndex = (pInfo->m_iopOffset < MUSIC_HALF_IOP_BUFFER_SIZE) ? 0 : 1;
+		if (!pInfo->m_iopBufLoaded[pInfo->m_iopBufIndex])
+		{
+			//Kprintf("****** ERROR: Music buffer #%d not loaded.\n", pInfo->m_iopBufIndex);
+			return FALSE;
+		}
+
+		pInfo->m_spuTransSize = SB_BUF_HALF;
+		pInfo->remaining -= SB_BUF_HALF;
+		if ( pInfo->remaining < 0 )
+		{
+			pInfo->m_spuTransSize += pInfo->remaining;
+		}
+
+		music_iop_offset = MUSIC_L_IOP_OFFSET;
+		music_spu_addr = MUSIC_L_SPU_BUF_LOC;
+	}
+	else
+	{
+		music_iop_offset = MUSIC_R_IOP_OFFSET;
+		music_spu_addr = MUSIC_R_SPU_BUF_LOC;
+	}
+
+	// load in a side:
+	if ( pInfo->m_spuBufSide == _1st )
+	{
+		_AdpcmSetMarkSTART( gpIopBuf + music_iop_offset + pInfo->m_iopOffset, pInfo->m_spuTransSize );
+	}
+	if ( pInfo->remaining <= 0 )
+	{
+		_AdpcmSetMarkFINAL( gpIopBuf + music_iop_offset + pInfo->m_iopOffset, pInfo->m_spuTransSize );
+		pInfo->m_spuEndAddr = MUSIC_L_SPU_BUF_LOC + pInfo->m_spuTransSize + SB_BUF_HALF * pInfo->m_spuBufSide;
+	}
+	else if ( pInfo->m_spuBufSide == _2nd )
+	{
+		_AdpcmSetMarkEND( gpIopBuf + music_iop_offset + pInfo->m_iopOffset, pInfo->m_spuTransSize );
+	}
+
+	pInfo->m_spuTransDone = FALSE;
+	sceSdVoiceTrans( TRANS_DMA_CH_MUSIC, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+		(unsigned char *) gpIopBuf + music_iop_offset + pInfo->m_iopOffset,
+		( music_spu_addr + SB_BUF_HALF * pInfo->m_spuBufSide ), pInfo->m_spuTransSize );
+	//Kprintf("Downloading music channel %d from IOP %x to SPU %x of size %d\n", whichChannel, gpIopBuf + music_iop_offset + pInfo->m_iopOffset,
+	//		( music_spu_addr + SB_BUF_HALF * pInfo->m_spuBufSide ), pInfo->m_spuTransSize );
+
+
+	return TRUE;
+}
+
+// Downloads audio stream data from the IOP to a SPU buffer
+int DownloadStream(int whichStream)
+{
+	struct StreamInfo *pInfo;
+	pInfo = &gStreamInfo[ whichStream ];
+
+	//iopBufferIndex = (pInfo->m_iopOffset < STREAM_HALF_IOP_BUFFER_SIZE) ? 0 : 1;
+	if (!pInfo->m_iopBufLoaded[pInfo->m_iopBufIndex])
+	{
+		//Kprintf("****** ERROR: Stream %d buffer #%d not loaded.\n", whichStream, pInfo->m_iopBufIndex);
+		return FALSE;
+	}
+
+	if (!LockDMA(whichStream))
+	{
+		return FALSE;
+	}
+
+	pInfo->m_spuTransSize = SB_BUF_HALF;
+	pInfo->remaining -= SB_BUF_HALF;
+	if ( pInfo->remaining < 0 )
+	{
+		pInfo->m_spuTransSize += pInfo->remaining;
+	}
+	//Kprintf( "Stream: IOP offset %d, transfer %d, remaining %d\n", pInfo->m_iopOffset, pInfo->m_spuTransSize, pInfo->remaining);
+
+	// load in left side:
+	if ( pInfo->m_spuBufSide == _1st )
+	{
+		_AdpcmSetMarkSTART( gpIopBuf + STREAM_IOP_OFFSET( whichStream ) + pInfo->m_iopOffset, pInfo->m_spuTransSize );
+
+	}
+	if ( pInfo->remaining <= 0 )
+	{
+		_AdpcmSetMarkFINAL( gpIopBuf + STREAM_IOP_OFFSET( whichStream ) + pInfo->m_iopOffset, pInfo->m_spuTransSize );
+		pInfo->m_spuEndAddr = STREAM_SPU_BUF_LOC( whichStream ) + pInfo->m_spuTransSize + SB_BUF_HALF * pInfo->m_spuBufSide;
+	}
+	else if ( pInfo->m_spuBufSide == _2nd )
+	{
+		_AdpcmSetMarkEND( gpIopBuf + STREAM_IOP_OFFSET( whichStream ) + pInfo->m_iopOffset, pInfo->m_spuTransSize );
+	}
+
+	pInfo->m_spuTransDone = FALSE;
+	sceSdVoiceTrans( TRANS_DMA_CH_STREAM, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+		(unsigned char *) gpIopBuf + STREAM_IOP_OFFSET( whichStream ) + pInfo->m_iopOffset,
+		( STREAM_SPU_BUF_LOC( whichStream ) + SB_BUF_HALF * pInfo->m_spuBufSide ), pInfo->m_spuTransSize );
+
+	//Kprintf("Downloaded Stream %d buffer #%d\n", whichStream, iopBufferIndex);
+
+	return TRUE;
+}
+
+void IncMusicBuffer()
+{
+	struct StreamInfo *pInfo;
+	pInfo = &gMusicInfo;
+
+	pInfo->m_iopOffset += SB_BUF_HALF;
+	pInfo->m_spuBufSide = ( pInfo->m_spuBufSide == _1st ) ? _2nd : _1st;
+
+	if ( pInfo->remaining <= 0 )
+	{
+		// the vag data up to the end should contain all zeros,
+		// so it doesn't matter that the interrupt won't happen until the end.
+		// stop the music when it gets to the end:
+		pInfo->status = PCM_STATUS_TERMINATE;
+		ShowState( "Music last buffer\n" );
+	}
+	else
+	{
+		if ( (pInfo->m_iopBufIndex == _1st) && (pInfo->m_iopOffset >= MUSIC_HALF_IOP_BUFFER_SIZE) )
+		{
+			//Dbug_Printf( "music move over\n" );
+			pInfo->m_iopOffset += MUSIC_HALF_IOP_BUFFER_SIZE; // move over by half the size since L/R load in together...
+			pInfo->m_iopBufIndex = _2nd;
+			if ( pInfo->remaining > MUSIC_HALF_IOP_BUFFER_SIZE )
+			{
+				gEECommand |= PCMSTATUS_NEED_MUSIC_BUFFER_0;
+				pInfo->m_iopBufLoaded[0] = FALSE;
+			}
+		}
+		else if ( (pInfo->m_iopBufIndex == _2nd) && ( pInfo->m_iopOffset >= ( MUSIC_IOP_BUFFER_SIZE + MUSIC_HALF_IOP_BUFFER_SIZE ) ) )
+		{
+			//Dbug_Printf( "music move back\n" );
+			pInfo->m_iopOffset = 0;
+			pInfo->m_iopBufIndex = _1st;
+			if ( pInfo->remaining > MUSIC_HALF_IOP_BUFFER_SIZE )
+			{
+				gEECommand |= PCMSTATUS_NEED_MUSIC_BUFFER_1;
+				pInfo->m_iopBufLoaded[1] = FALSE;
+			}
+		}
+	}
+}
+
+void IncStreamBuffer(int whichStream)
+{
+	struct StreamInfo *pInfo;
+	pInfo = &gStreamInfo[ whichStream ];
+
+	pInfo->m_iopOffset += SB_BUF_HALF;
+	pInfo->m_spuBufSide = ( pInfo->m_spuBufSide == _1st ) ? _2nd : _1st;
+	if ( pInfo->remaining <= 0 )
+	{
+		// the vag data up to the end should contain all zeros,
+		// so it doesn't matter that the interrupt won't happen until the end.
+		// stop the stream when it gets to the end:
+		pInfo->status = PCM_STATUS_TERMINATE;
+	}
+	else
+	{
+		// Must check if it has crossed from one IOP buffer to the other
+		if ( (pInfo->m_iopBufIndex == _1st) && (pInfo->m_iopOffset >= STREAM_HALF_IOP_BUFFER_SIZE) )
+		{
+			SmallShowStreamState( "-SMO" );
+			pInfo->m_iopBufIndex = _2nd;
+			if ( pInfo->remaining > STREAM_HALF_IOP_BUFFER_SIZE )
+			{
+				gEECommand |= ( PCMSTATUS_NEED_STREAM0_BUFFER_0 << whichStream );
+				pInfo->m_iopBufLoaded[0] = FALSE;
+				//Kprintf("Asking for Stream %d buffer #%d\n", whichStream, 0);
+			}
+		}
+		else if ( (pInfo->m_iopBufIndex == _2nd) && pInfo->m_iopOffset >= STREAM_IOP_BUFFER_SIZE )
+		{
+			SmallShowStreamState( "-SMB" );
+			pInfo->m_iopOffset -= STREAM_IOP_BUFFER_SIZE;
+			pInfo->m_iopBufIndex = _1st;
+			if ( pInfo->remaining > STREAM_HALF_IOP_BUFFER_SIZE )
+			{
+				gEECommand |= ( PCMSTATUS_NEED_STREAM0_BUFFER_1 << whichStream );
+				pInfo->m_iopBufLoaded[1] = FALSE;
+				//Kprintf("Asking for Stream %d buffer #%d\n", whichStream, 1);
+			}
+		}
+	}
+}
+
+// Start the music that is already preloaded
+void StartMusic()
+{
+	AdpcmSetMusicVolumeDirect( gMusicInfo.volume );
+	sceSdSetSwitch( MUSIC_CORE | SD_S_KON, ( ( 1 << MUSIC_R_VOICE ) | ( 1 << MUSIC_L_VOICE ) ) );
+}
+
+// Start the stream that is already preloaded
+void StartStream(int whichStream)
+{
+	AdpcmSetStreamVolumeDirect( gStreamInfo[ whichStream ].volume, whichStream );
+	// Here's the bug! This was causing a recently paused stream to un-pause...
+	if ( !gStreamInfo[ whichStream ].paused )
+	{
+		sceSdSetParam( STREAM_CORE | ( STREAM_VOICE( whichStream ) << 1 ) | SD_VP_PITCH, gStreamInfo[ whichStream ].pitch );
+	}
+	sceSdSetSwitch( STREAM_CORE | SD_S_KON, 1 << STREAM_VOICE( whichStream ) );
+
+	//Kprintf("Stream %d using SPU memory (%6x-%6x)\n", whichStream, STREAM_SPU_BUF_LOC( whichStream ), STREAM_SPU_BUF_LOC( whichStream ) + SB_BUF_SIZE - 1);
+}
+
+/* internal */
+int RefreshStreams( int status )
+{
+	int i;
+
+    while ( 1 )
+	{
+		WaitSema(gSem);
+
+		// Update everything
+		for ( i = 0; i < NUM_STREAMS; i++ )
+		{
+			UpdateStream( i );
+		}
+		UpdateMusic( );
+
+		if ( gMusicInfo.volumeSet )
+		{
+			gMusicInfo.volumeSet = 0;
+			AdpcmSetMusicVolumeDirect( gMusicInfo.volume );
+		}
+		for ( i = 0; i < NUM_STREAMS; i++ )
+		{
+			if ( gStreamInfo[ i ].volumeSet )
+			{
+				gStreamInfo[ i ].volumeSet = 0;
+				AdpcmSetStreamVolumeDirect( gStreamInfo[ i ].volume, i );
+			}
+		}
+
+		SendStatus();
+    }
+    return 0;
+}
+
+void _ShowStreamInfo( struct StreamInfo *pInfo )
+{
+	printf( "\nStream Info:\n" );
+	printf( "paused     %d\n", pInfo->paused );
+	printf( "m_spuEndAddr %d\n", pInfo->m_spuEndAddr );
+	printf( "m_spuBufSide %d\n", pInfo->m_spuBufSide );
+	printf( "m_spuTransSize %d\n", pInfo->m_spuTransSize );
+	printf( "loadState  %d\n", pInfo->loadState );
+	printf( "volume     %d\n", pInfo->volume );
+	printf( "volumeSet  %d\n", pInfo->volumeSet );
+	printf( "stop       %d\n", pInfo->stop );
+	printf( "status     %d\n", pInfo->status );
+	printf( "size       %d\n", pInfo->size );
+	printf( "remaining  %d\n", pInfo->remaining );
+	printf( "m_iopOffset %d\n", pInfo->m_iopOffset );
+	printf( "pitch      %d\n", pInfo->pitch );
+	printf( "eecom      %d\n", gEECommand );
+}
+
+void PauseStream( int whichStream, int pause )
+{
+	ShowStreamInfo( &gStreamInfo[ whichStream ] );
+	if ( pause )
+	{
+		if ( !gStreamInfo[ whichStream ].paused )
+		{
+			sceSdSetParam( STREAM_CORE | ( STREAM_VOICE( whichStream ) << 1 ) | SD_VP_PITCH, 0 );
+			gStreamInfo[ whichStream ].paused = 1;
+			ShowAction( "Pausing stream\n" );
+			SmallShowAction( "-ps" );
+		}
+	}
+	else
+	{
+		if ( gStreamInfo[ whichStream ].paused )
+		{
+			sceSdSetParam( STREAM_CORE | ( STREAM_VOICE( whichStream ) << 1 ) | SD_VP_PITCH, gStreamInfo[ whichStream ].pitch );
+			gStreamInfo[ whichStream ].paused = 0;
+			ShowAction( "Unpausing stream\n" );
+			SmallShowAction( "-ups" );
+		}
+	}
+}
+
+volatile int ret = 0;
+
+#define U_DATA( x )	( *( ( unsigned int * ) data_ + x ) ) 
+#define DATA( x )	( *( ( int * ) data_ + x ) )
+
+static void *dispatch( unsigned int command, void *data_, int size )
+{ 
+    int ch;
+
+//    printf( "size %d", size );
+//	printf("# dispatch [%04x] %x, %x, %x, %x\n", command, *((int*) data_ + 0), *((int*) data_ + 1), *((int*) data_ + 2), *((int*) data_ + 3));
+
+    ch = command & EzADPCM_CH_MASK;
+    switch ( command & EzADPCM_COMMAND_MASK )
+	{
+		case EzADPCM_INIT:
+			ret = AdpcmInit( DATA( 0 ) ); // iop buffer pointer
+			break;
+
+		case EzADPCM_QUIT:
+			AdpcmQuit( );
+			break;
+
+		case EzADPCM_PRELOADMUSIC:
+			ret = AdpcmPlayMusic( DATA( 0 ), TRUE );  // size of the entire PCM data in the file
+			break;
+
+		case EzADPCM_PRELOADSTREAM:
+			ret = AdpcmPlayStream( DATA( 0 ), ch, 0, 0, TRUE); // size of the entire PCM data in the file
+			break;
+
+		case EzADPCM_PLAYPRELOADEDMUSIC:
+			ret = AdpcmPlayPreloadedMusic();
+			break;
+
+		case EzADPCM_PLAYPRELOADEDSTREAM:
+			ret = AdpcmPlayPreloadedStream(ch, DATA(0), DATA(1));
+			break;
+
+		case EzADPCM_PLAYMUSIC:
+			ret = AdpcmPlayMusic( DATA( 0 ), FALSE );  // size of the entire PCM data in the file
+			break;
+
+		case EzADPCM_PLAYSTREAM:
+			ret = AdpcmPlayStream( DATA( 0 ), ch, DATA( 1 ), DATA( 2 ), FALSE ); // size of the entire PCM data in the file
+			break;
+		
+		case EzADPCM_PAUSEMUSIC:
+			if ( DATA( 0 ) )
+			{
+				if ( !gMusicInfo.paused )
+				{
+					sceSdSetParam( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_PITCH, 0 );
+					sceSdSetParam( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_PITCH, 0 );
+					gMusicInfo.paused = 1;
+					ShowAction( "Pausing music\n" );
+					SmallShowAction( "-pm" );
+				}
+			}
+			else
+			{
+				if ( gMusicInfo.paused )
+				{
+					sceSdSetParam( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_PITCH, DEFAULT_PITCH );
+					sceSdSetParam( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_PITCH, DEFAULT_PITCH );
+					gMusicInfo.paused = 0;
+					ShowAction( "Unpausing music\n" );
+					SmallShowAction( "-upm" );
+				}
+			}
+			break;
+
+		case EzADPCM_PAUSESTREAMS:
+		{
+			int whichStream;
+			
+			for ( whichStream = 0; whichStream < NUM_STREAMS; whichStream++ )
+			{
+				PauseStream( whichStream, DATA( 0 ) );
+			}
+			break;
+		}
+		
+		case EzADPCM_PAUSESTREAM:
+			PauseStream( ch, DATA( 0 ) );
+			break;
+		
+		case EzADPCM_STOPMUSIC:
+			ret = AdpcmStopMusic( );
+			break;
+
+		case EzADPCM_STOPSTREAMS:
+		{
+			int i;
+			for ( i = 0; i < NUM_STREAMS; i++ )
+			{
+				ret = AdpcmStopStream( i );
+			}
+			break;
+		}
+		
+		case EzADPCM_STOPSTREAM:
+			if ( NUM_STREAMS > DATA( 0 ) )
+				ret = AdpcmStopStream( DATA( 0 )  );
+			break;
+		
+		case EzADPCM_SETMUSICVOL:
+			AdpcmSetMusicVolume( U_DATA( 0 ) );
+			break;
+
+		case EzADPCM_SETSTREAMVOLANDPITCH:
+			sceSdSetParam( STREAM_CORE | ( STREAM_VOICE( ch ) << 1 ) | SD_VP_PITCH, ( U_DATA( 1 ) / 2 ) );
+			// Adjust for pitch --> 22,050 hz when standard is 48000
+			gStreamInfo[ ch ].pitch = STREAM_PITCH( U_DATA( 1 ) );
+			// intentional fall through:
+		case EzADPCM_SETSTREAMVOL:
+			AdpcmSetStreamVolume( U_DATA( 0 ), ch );
+			break;
+
+#if 0
+		case EzADPCM_SETSTREAMGLOBVOL:
+			AdpcmSetStreamGlobVol( U_DATA( 0 ) );
+			break;
+
+		case EzADPCM_GETMUSICSTATUS:
+			ret = AdpcmGetMusicStatus( );
+			break;
+#endif
+
+		case EzADPCM_SDINIT:
+			AdpcmSdInit( );
+			break;
+
+		default:
+			ERROR (("EzADPCM driver error: unknown command %d \n", DATA( 0 ) ) );
+			break;
+    }
+    //printf( "! return value = %x \n", ret );
+    return (void*)(&ret);
+}
+
+// SifCmd variables
+
+//static sceSifCmdData cmdbuffer[NUM_COMMAND_HANDLERS];
+unsigned int gCmdSem;
+
+// Note these can be changed in an interrupt
+#define NUM_STREAM_REQUESTS	(10)
+
+static volatile SStreamRequestPacket StreamRequestArray[NUM_STREAM_REQUESTS];
+static volatile int FirstStreamRequest;		// Interrupt only reads this value.
+static volatile	int FreeStreamRequest;		// This is the main variable that changes in the interrupt
+
+static SSifCmdStreamResultPacket StreamResult;
+
+void request_callback(void *p, void *q);
+void load_status_callback(void *p, void *q);
+
+int sce_adpcm_loop( void )
+{
+	int oldStat;
+	int last_cmd_id = -1;
+
+	// Create semaphore to signal command came in
+	struct SemaParam sem;
+	sem.initCount = 0;
+	sem.maxCount = 1;
+	sem.attr = AT_THFIFO;
+	gCmdSem = CreateSema (&sem);
+
+	// Init the stream queue
+	FirstStreamRequest = 0;
+	FreeStreamRequest = 0;
+
+	sceSifInitRpc(0);
+
+	// set local buffer & functions
+	CpuSuspendIntr(&oldStat);
+
+	// SIFCMD
+	// No longer need to call sceSifSetCmdBuffer() since we share it with fileio.irx
+	//sceSifSetCmdBuffer( &cmdbuffer[0], NUM_COMMAND_HANDLERS);
+
+	sceSifAddCmdHandler(STREAM_REQUEST_COMMAND, (void *) request_callback, NULL );
+	sceSifAddCmdHandler(STREAM_LOAD_STATUS_COMMAND, (void *) load_status_callback, NULL );
+
+	CpuResumeIntr(oldStat);
+
+	// The loop
+	while (1) {
+		//printf("waiting for pcm command semaphore\n");
+		WaitSema(gCmdSem);		// Get signal from callback
+		//printf("got pcm command semaphore\n");
+
+		// Note that FreeStreamRequest can change in the interrupt at any time
+		// Also, FirstStreamRequest is examined in the interrupt, but just to make sure we don't overflow the buffer
+		//Dbg_Assert(FreeStreamRequest != FirstStreamRequest);
+		while (FreeStreamRequest != FirstStreamRequest)
+		{
+			int *p_ret;
+			volatile SStreamRequestPacket *p_request = &(StreamRequestArray[FirstStreamRequest]);
+
+#if TEST_PLAY_TIMING
+			if (p_request->m_request.m_command == EzADPCM_PLAYPRELOADEDMUSIC)
+			{
+				test_timing_request_id = p_request->m_request_id;
+				test_timing_send_timing_result = TRUE;
+				test_timing_hit_point = FALSE;
+			}
+#endif // TEST_PLAY_TIMING
+
+			//printf("EzPcm: got request id %d with command %x\n", p_request->m_request_id, p_request->m_request.m_command);
+			p_ret = dispatch(p_request->m_request.m_command, (void *) p_request->m_request.m_param, 0);
+
+			// Send the result back
+			if (last_cmd_id >= 0)	// Wait for previous send to complete (it should already be done, though)
+			{
+               while(sceSifDmaStat(last_cmd_id) >= 0)
+				   printf("Waiting for PCM DMA\n");
+			}
+			// Gotta copy the id into SStreamRequest
+			StreamResult.m_header.opt = p_request->m_request_id;	// Copy id
+			StreamResult.m_return_value = *p_ret;
+			StreamResult.m_status_flags = AdpcmGetStatus();
+            last_cmd_id = sceSifSendCmd(STREAM_RESULT_COMMAND, &StreamResult, sizeof(StreamResult), 0, 0, 0);
+
+			// Increment request index; Note that interrupt can look at this
+			FirstStreamRequest = (FirstStreamRequest + 1) % NUM_STREAM_REQUESTS;
+		}
+	}
+
+    return 0;
+}
+
+void request_callback(void *p, void *q)
+{
+	SSifCmdStreamRequestPacket *h = (SSifCmdStreamRequestPacket *) p;
+
+	// Check to make sure we can add
+	int nextFreeReq = (FreeStreamRequest + 1) % NUM_STREAM_REQUESTS;
+	if (nextFreeReq == FirstStreamRequest)
+	{
+		// We can't allow a request to be ignored.  We must abort
+		//Dbg_Assert(0);
+	}
+
+	// Copy the request into the reuest queue
+	StreamRequestArray[FreeStreamRequest].m_request = h->m_request;
+	StreamRequestArray[FreeStreamRequest].m_request_id = h->m_header.opt;
+	FreeStreamRequest = nextFreeReq;
+
+	// And wake up the dispatch thread
+	iSignalSema(gCmdSem);
+
+	return;
+}
+
+void load_status_callback(void *p, void *q)
+{
+	SSifCmdStreamLoadStatusPacket *h = (SSifCmdStreamLoadStatusPacket *) p;
+
+	// Mark buffer as loaded
+	if (h->m_stream_num == -1)
+	{
+		//Kprintf("Music buffer #%d finished loading.\n", h->m_buffer_num);
+		gMusicInfo.m_iopBufLoaded[h->m_buffer_num] = TRUE;
+	}
+	else
+	{
+		//Kprintf("Stream %d buffer #%d finished loading.\n", h->m_stream_num, h->m_buffer_num);
+		gStreamInfo[h->m_stream_num].m_iopBufLoaded[h->m_buffer_num] = TRUE;
+	}
+}
+
+void SendStatus()
+{
+	static SSifCmdStreamStatusPacket StreamStatus;
+
+#if TEST_PLAY_TIMING
+	if (test_timing_send_timing_result && test_timing_hit_point)
+	{
+		sEELastCommand = 0xFFFFFFFF;
+		StreamStatus.m_header.opt = test_timing_request_id;
+		test_timing_send_timing_result = FALSE;
+	}
+	else
+	{
+		StreamStatus.m_header.opt = 0;
+	}
+#endif // TEST_PLAY_TIMING
+
+	// Only send updates when it has changed
+	if (AdpcmHasStatusChanged())
+	{
+		StreamStatus.m_status_flags = AdpcmGetStatus();
+		sceSifSendCmd(STREAM_STATUS_COMMAND, &StreamStatus, sizeof(StreamStatus), 0, 0, 0);
+	}
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+
+/* DON'T ADD STUFF AFTER THIS */
diff --git a/Code/Gel/Music/Ngps/Pcm/pcm_ent.c b/Code/Gel/Music/Ngps/Pcm/pcm_ent.c
new file mode 100644
index 0000000..2b29335
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Pcm/pcm_ent.c
@@ -0,0 +1,59 @@
+/* Vag streaming into SPU2 -- Converted from Sony samples -- matt may 2001 */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "pcm.h"
+#include "pcmiop.h"
+
+ModuleInfo Module = {"pcm_driver", 0x0102};
+
+// in command.c
+extern int sce_adpcm_loop (void);
+extern int sce_sound_loop (void);
+
+int start ( void )
+{
+    struct ThreadParam param;
+    int th;
+
+    if (! sceSifCheckInit ())
+	sceSifInit ();
+    sceSifInitRpc (0);
+
+    printf ("PCM driver version 666\n");
+
+    param.attr         = TH_C;
+    param.entry        = sce_adpcm_loop;
+    param.initPriority = BASE_priority - 2;
+    param.stackSize    = 0x800;
+    param.option       = 0;
+    th = CreateThread (¶m);
+    if (th > 0) {
+		StartThread (th, 0);
+    } else {
+		return 1;
+    }
+
+    param.attr         = TH_C;
+    param.entry        = sce_sound_loop;
+    param.initPriority = BASE_priority - 2;
+    param.stackSize    = 0x800;
+    param.option       = 0;
+    th = CreateThread (¶m);
+    if (th > 0) {
+		StartThread (th, 0);
+        printf (" Exit PCM loader thread \n");
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
diff --git a/Code/Gel/Music/Ngps/Pcm/pcm_sound.c b/Code/Gel/Music/Ngps/Pcm/pcm_sound.c
new file mode 100644
index 0000000..a0bb485
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Pcm/pcm_sound.c
@@ -0,0 +1,430 @@
+/* Vag streaming into SPU2 -- from sony sample -- matt may 2001 */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "pcm.h"
+#include "pcmiop.h"
+
+#define PRINT_DROPPED_REQUESTS	0
+
+// ================================================================
+
+#define _1st 0
+#define _2nd 1
+
+#define TRUE  1
+#define FALSE 0
+
+#define TRANS_DMA_CH_MUSIC		1
+#define TRANS_DMA_CH_STREAM		0
+
+#define NUM_CORES				2
+
+unsigned int gSndSem;
+
+extern volatile int gUsingStreamDMA;
+extern volatile int gSPUUsingStreamDMA;
+
+#if PRINT_DROPPED_REQUESTS
+static int s_received_requests = 0;
+#endif
+
+//	sceSdSetTransIntrHandler( TRANS_DMA_CH_MUSIC, ( sceSdTransIntrHandler ) _AdpcmDmaIntMusic, ( void * ) &gSem );
+//  sceSdSetTransIntrHandler( TRANS_DMA_CH_STREAM, ( sceSdTransIntrHandler ) _AdpcmDmaIntStream, ( void * ) &gSem );
+
+// Used to hold the queued up SPU switch registers
+volatile static uint32			s_spu_reg_kon[NUM_CORES];
+volatile static uint32			s_spu_reg_koff[NUM_CORES];
+volatile static uint32			s_spu_kon_changed[NUM_CORES];
+volatile static uint32			s_spu_koff_changed[NUM_CORES];
+volatile static uint32			s_spu_reg_endx[NUM_CORES];
+volatile static uint32			s_switch_alarm_set;
+volatile static uint32			s_send_spu_status;
+
+#define	SET_PARAM_ALARM_TIME	(100)			// Number of microseconds to wait before setting SPU switches (must be greater
+												// than 1/24000 seconds)
+
+uint32 switch_set_callback(void *p_nothing)
+{
+	int core;
+
+	for (core = 0; core < NUM_CORES; core++)
+	{
+		// First, get the status
+		s_spu_reg_endx[core] = sceSdGetSwitch( core | SD_S_ENDX );
+
+		if (s_spu_kon_changed[core])
+		{
+			sceSdSetSwitch( core | SD_S_KON, s_spu_reg_kon[core] );
+			s_spu_kon_changed[core] = FALSE;
+			s_spu_reg_endx[core] &= ~s_spu_reg_kon[core];	// Update the status since SPU won't do it for another tick
+			s_spu_reg_kon[core] = 0;
+		}
+
+		if (s_spu_koff_changed[core])
+		{
+			sceSdSetSwitch( core | SD_S_KOFF, s_spu_reg_koff[core] );
+			s_spu_koff_changed[core] = FALSE;
+			s_spu_reg_endx[core] |= s_spu_reg_koff[core];	// Update the status since SPU won't do it for another tick
+			s_spu_reg_koff[core] = 0;
+		}
+	}
+
+	s_switch_alarm_set = FALSE;
+	s_send_spu_status = TRUE;
+
+	// And wake up the dispatch thread
+	iSignalSema(gSndSem);
+
+	return 0;
+}
+
+// This function will set an alarm so that multiple KON and KOFF settings are combined.  If it isn't these type
+// of switches, it will be passed through
+void queue_set_switch(unsigned short entry, unsigned int value)
+{
+	int core = entry & 0x1;
+	int switch_reg = entry & 0xFF00;
+	int oldStat;
+
+	// Make sure alarm callback doesn't run here
+	CpuSuspendIntr(&oldStat);
+
+	switch (switch_reg)
+	{
+	case SD_S_KON:
+		if(s_spu_reg_koff[core] & value)
+		{
+			//Dbg_MsgAssert(0, ("Turning on a voice that was turned off this frame."));
+			Kprintf("Turning on a voice that was turned off this SPU tick.\n");
+			s_spu_reg_koff[core] &= ~(s_spu_reg_koff[core] & value);	// turn off those bits
+		}
+
+		s_spu_reg_kon[core] |= value;
+		s_spu_kon_changed[core] = TRUE;
+
+		break;
+
+	case SD_S_KOFF:
+		if(s_spu_reg_kon[core] & value)
+		{
+			//Dbg_MsgAssert(0, ("Turning off a voice that was turned on this frame."));
+			Kprintf("Turning off a voice that was turned on this SPU tick.\n");
+			s_spu_reg_kon[core] &= ~(s_spu_reg_kon[core] & value);		// turn off those bits
+		}
+
+		s_spu_reg_koff[core] |= value;
+		s_spu_koff_changed[core] = TRUE;
+
+		break;
+
+	default:
+		// Just set SPU and exit (so we don't set alarm)
+		sceSdSetSwitch( entry, value );
+		CpuResumeIntr(oldStat);
+		return;
+	}
+
+	// Turn on alarm if it isn't already on
+	if (!s_switch_alarm_set)
+	{
+		struct SysClock clock;
+		USec2SysClock(SET_PARAM_ALARM_TIME, &clock);
+
+		SetAlarm(&clock, switch_set_callback, NULL);
+
+		s_switch_alarm_set = TRUE;
+	}
+
+	CpuResumeIntr(oldStat);
+}
+
+// This function belongs in pcm_sound.c, but because of some SN Sys link error, it has to be in here for now.
+int execute_sound_request(unsigned short function, unsigned short entry, unsigned int value,
+						  unsigned int arg0, unsigned int arg1, unsigned int arg2, ESendSoundResult *p_send_result)
+{ 
+	int ret = -1;
+	*p_send_result = SEND_SOUND_RESULT_FALSE;
+
+	switch (function)
+	{
+	case rSdInit:
+		ret = sceSdInit( entry );
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdSetParam:
+		sceSdSetParam( entry, value );
+		break;
+
+	case rSdGetParam:
+		ret = sceSdGetParam( entry );
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdSetSwitch:
+		queue_set_switch( entry, value );
+		break;
+
+	case rSdGetSwitch:
+		ret = sceSdGetSwitch( entry );
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdSetAddr:
+		sceSdSetAddr( entry, value );
+		//Kprintf("Setting addr %x to %x\n", entry, value);
+		break;
+
+	case rSdGetAddr:
+		ret = sceSdGetAddr( entry );
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdSetCoreAttr:
+		sceSdSetCoreAttr( entry, value );
+		break;
+
+	case rSdGetCoreAttr:
+		ret = sceSdGetCoreAttr( entry );
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdNote2Pitch:
+		ret = sceSdNote2Pitch ( entry /*center_note*/, value /*center_fine*/, arg0 /*note*/, arg1 /*fine*/);
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdPitch2Note:
+		ret = sceSdPitch2Note ( entry /*center_note*/, value /*center_fine*/, arg0 /*pitch*/);
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdVoiceTrans:
+		sceSdVoiceTrans( entry /*channel*/, value /*mode*/, (u_char *) arg0 /*m_addr*/, arg1 /*s_addr*/, arg2 /*size*/ );
+		break;
+
+	case rSdBlockTrans:
+		sceSdBlockTrans( entry /*channel*/, value /*mode*/, (u_char *) arg0 /*m_addr*/, arg1 /*size*/, (u_char *) arg2 /*start_addr*/ );
+		break;
+
+	case rSdVoiceTransStatus:
+		ret = sceSdVoiceTransStatus (entry /*channel*/, value /*flag*/);
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdBlockTransStatus:
+		ret = sceSdBlockTransStatus (entry /*channel*/, value /*flag*/);
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+	case rSdSetEffectAttr:
+		{
+			sceSdEffectAttr attr;
+
+			attr.core		= entry;
+			attr.mode		= value;
+			attr.depth_L	= (short) (arg0 >> 16);
+			attr.depth_R	= (short) (arg0 & 0xFFFF);
+			attr.delay		= arg1;
+			attr.feedback	= arg2;
+
+			//Kprintf("About to set core %d reverb mode to %d\n", entry, value);
+			ret = sceSdSetEffectAttr(entry /*core*/, &attr);
+
+			// Wait for Stream DMA (0) to finish
+			while (gUsingStreamDMA)
+				;
+
+			// Clear out reverb memory (and make sure it doesn't interfere with streams)
+			gSPUUsingStreamDMA = 1;
+            sceSdCleanEffectWorkArea(entry, 0, value);
+            sceSdVoiceTransStatus(0, SD_TRANS_STATUS_WAIT);
+			if ((value == SD_REV_MODE_OFF) && gSPUUsingStreamDMA)		// This mode doesn't seem to cause a DMA callback
+			{
+				gSPUUsingStreamDMA = 0;
+				//Kprintf("Manually clearing DMA flag\n");
+			}
+			//Kprintf("Set core %d reverb mode to %d with return value of %d and %d\n", entry, value, ret);
+		}
+		*p_send_result = SEND_SOUND_RESULT_TRUE;
+		break;
+
+		// This is a meaningless call because we can't send the data back
+//	case rSdGetEffectAttr:
+//		sceSdGetEffectAttr(entry /*core*/, (sceSdEffectAttr *) value /*attr*/);
+//		break;
+
+//#define rSdClearEffectWorkArea   0x8150
+//#define rSdStopTrans             0x8180
+//#define rSdCleanEffectWorkArea   0x8190
+//#define rSdSetEffectMode         0x81a0
+//#define rSdSetEffectModeParams   0x81b0
+
+	default:
+		ret = -1;
+		*p_send_result = SEND_SOUND_RESULT_PANIC;
+		Kprintf("Unknown or unsupported SPU function %x\n", function);
+		break;
+	}
+
+    return ret;
+}
+
+// SifCmd variables
+
+// Note these can be changed in an interrupt
+#define NUM_SOUND_REQUESTS	(128)
+
+static volatile SSoundRequest SoundRequestArray[NUM_SOUND_REQUESTS];
+static volatile int FirstSoundRequest;		// Interrupt only reads this value.
+static volatile	int FreeSoundRequest;		// This is the main variable that changes in the interrupt
+
+// Set to TRUE if we hit a problem
+static volatile int global_sound_panic = FALSE;
+
+static SSifCmdSoundResultPacket SoundResult;
+
+void sound_request_callback(void *p, void *q);
+
+int sce_sound_loop( void )
+{
+	int oldStat;
+	int last_cmd_id = -1;
+
+	// Create semaphore to signal command came in
+	struct SemaParam sem;
+	sem.initCount = 0;
+	sem.maxCount = 1;
+	sem.attr = AT_THFIFO;
+	gSndSem = CreateSema (&sem);
+
+	// Init the stream queue
+	FirstSoundRequest = 0;
+	FreeSoundRequest = 0;
+
+	// set local buffer & functions
+	CpuSuspendIntr(&oldStat);
+
+	// SIFCMD
+	// No longer need to call sceSifSetCmdBuffer() since we share it with fileio.irx
+	sceSifAddCmdHandler(SOUND_REQUEST_COMMAND, (void *) sound_request_callback, NULL );
+
+	CpuResumeIntr(oldStat);
+
+	// The loop
+	while (1) {
+		//printf("waiting for sound command semaphore\n");
+		WaitSema(gSndSem);		// Get signal from callback
+		//printf("got sound command semaphore\n");
+
+		// Note that FreeSoundRequest can change in the interrupt at any time
+		// Also, FirstSoundRequest is examined in the interrupt, but just to make sure we don't overflow the buffer
+		//Dbg_Assert(FreeSoundRequest != FirstSoundRequest);
+		while (FreeSoundRequest != FirstSoundRequest)
+		{
+			int ret;
+			ESendSoundResult send_result;
+
+			volatile SSoundRequest *p_request = &(SoundRequestArray[FirstSoundRequest]);
+			//Kprintf("Sound: %d executing command %x with entry %x and value %x\n", FirstSoundRequest, p_request->m_function, p_request->m_entry, p_request->m_value);
+			ret = execute_sound_request(p_request->m_function, p_request->m_entry, p_request->m_value,
+										p_request->m_arg_0, p_request->m_arg_1, p_request->m_arg_2, &send_result);
+
+			if ((send_result != SEND_SOUND_RESULT_FALSE) || global_sound_panic)
+			{
+				//Kprintf("Sending sound result from command %x with entry %x and result %x\n", p_request->m_function, p_request->m_entry, ret);
+
+				// Send the result back
+				if (last_cmd_id >= 0)	// Wait for previous send to complete (it should already be done, though)
+				{
+					while(sceSifDmaStat(last_cmd_id) >= 0)
+						;
+				}
+
+				// Gotta copy the function and entry
+				SoundResult.m_function	= p_request->m_function;
+				SoundResult.m_entry 	= p_request->m_entry;
+				SoundResult.m_result	= ret;
+				SoundResult.m_panic		= (send_result == SEND_SOUND_RESULT_PANIC) || global_sound_panic;
+				last_cmd_id = sceSifSendCmd(SOUND_RESULT_COMMAND, &SoundResult, sizeof(SoundResult), 0, 0, 0);
+			}
+
+			// Increment request index; Note that interrupt can look at this
+			FirstSoundRequest = (FirstSoundRequest + 1) % NUM_SOUND_REQUESTS;
+		}
+
+		// Send the status back, if it has been updated
+		if (s_send_spu_status)
+		{
+			int core;
+
+			//Kprintf("Sending sound status back\n");
+
+			for (core = 0; core < NUM_CORES; core ++)
+			{
+				if (last_cmd_id >= 0)	// Wait for previous send to complete
+				{
+					while(sceSifDmaStat(last_cmd_id) >= 0)
+						;
+				}
+
+				// Gotta copy the function and entry
+				SoundResult.m_function	= rSdGetSwitch;
+				SoundResult.m_entry 	= SD_S_ENDX | core;
+				SoundResult.m_result	= s_spu_reg_endx[core];
+				SoundResult.m_panic		= FALSE;
+				last_cmd_id = sceSifSendCmd(SOUND_RESULT_COMMAND, &SoundResult, sizeof(SoundResult), 0, 0, 0);
+			}
+			s_send_spu_status = FALSE;
+		}
+	}
+
+    return 0;
+}
+
+void sound_request_callback(void *p, void *q)
+{
+	SSifCmdSoundRequestPacket *h = (SSifCmdSoundRequestPacket *) p;
+
+	// Check to make sure we can add
+	int nextFreeReq = (FreeSoundRequest + 1) % NUM_SOUND_REQUESTS;
+	if (nextFreeReq == FirstSoundRequest)
+	{
+		// We can't allow a request to be ignored.  We must abort
+		Kprintf("Fuck, we've overrun the sound request buffer\n");
+		global_sound_panic = TRUE;
+	}
+
+#if PRINT_DROPPED_REQUESTS
+	if ((++s_received_requests) != h->m_request.m_pad[0])
+	{
+		Kprintf("ERROR: Missed sound request.  Expected %d but only got up to %d\n", h->m_request.m_pad[0], s_received_requests);
+		s_received_requests = h->m_request.m_pad[0];
+	}
+#endif PRINT_DROPPED_REQUESTS
+
+	// Copy the request into the reuest queue
+	SoundRequestArray[FreeSoundRequest] = h->m_request;
+	FreeSoundRequest = nextFreeReq;
+
+	//Kprintf("Sound: %d got command %x with entry %x and value %x\n", FreeSoundRequest, h->m_request.m_function, h->m_request.m_entry, h->m_request.m_value);
+
+	// And wake up the dispatch thread
+	iSignalSema(gSndSem);
+
+	return;
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
diff --git a/Code/Gel/Music/Ngps/Pcm/pcmiop.h b/Code/Gel/Music/Ngps/Pcm/pcmiop.h
new file mode 100644
index 0000000..e2764b6
--- /dev/null
+++ b/Code/Gel/Music/Ngps/Pcm/pcmiop.h
@@ -0,0 +1,76 @@
+#if 0
+#define PRINTF(x) printf x
+#else
+#define PRINTF(x) 
+#endif
+#define ERROR(x) printf x
+#define xPRINTF(x) 
+
+#define BASE_priority  32
+
+#define OLDLIB 0
+#define TRANS_CH  0
+
+typedef char				int8;
+typedef short				int16;
+typedef int					int32;
+
+typedef unsigned char		uint8;
+typedef unsigned short		uint16;
+typedef unsigned int		uint32;
+
+
+typedef enum
+{
+	SEND_SOUND_RESULT_FALSE = 0,
+	SEND_SOUND_RESULT_TRUE,
+	SEND_SOUND_RESULT_PANIC,
+} ESendSoundResult;
+
+enum{
+	MUSIC_LOAD_STATE_IDLE = 0, // Idle state must be zero!
+	MUSIC_LOAD_STATE_PRELOADING_L,
+	MUSIC_LOAD_STATE_PRELOADING_R,
+	MUSIC_LOAD_STATE_WAITING_FOR_REFRESH,
+	MUSIC_LOAD_STATE_WAITING_FOR_LAST_LOAD,
+	MUSIC_LOAD_STATE_LOADING_L,
+	MUSIC_LOAD_STATE_LOADING_R,
+};
+
+enum{
+	STREAM_LOAD_STATE_IDLE = 0, // Idle state must be zero!
+	STREAM_LOAD_STATE_PRELOADING,
+	STREAM_LOAD_STATE_WAITING_FOR_REFRESH,
+	STREAM_LOAD_STATE_WAITING_FOR_LAST_LOAD,
+	STREAM_LOAD_STATE_LOADING,
+};
+
+// Holds all the data necessary for an audio or music stream
+struct StreamInfo{
+	// State variables
+	volatile uint32	loadState;
+	volatile uint32	status;
+	volatile uint16	m_preloadMode;
+
+	volatile uint16	m_iopBufLoaded[2];
+
+	// Stream parameters
+	volatile uint16	volumeSet;
+	volatile uint32	volume;
+	volatile uint16	paused;
+	volatile uint16	stop;
+	int				pitch;
+
+	// Stream size info
+	volatile uint32	size;
+	int 			remaining;
+
+	// Current buffer and address info
+	uint16			m_iopBufIndex;
+	uint32			m_iopOffset;
+	uint32			m_spuBufSide;			// buffer we are LOADING to
+	uint32			m_spuTransSize;
+	volatile uint16 m_spuTransDone;
+	uint32			m_spuCurAddr;
+	uint32			m_spuEndAddr;
+};
diff --git a/Code/Gel/Music/Ngps/p_music.cpp b/Code/Gel/Music/Ngps/p_music.cpp
new file mode 100644
index 0000000..a8f6318
--- /dev/null
+++ b/Code/Gel/Music/Ngps/p_music.cpp
@@ -0,0 +1,1426 @@
+// Streaming music off the cd...
+
+
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// temp for debugging ghost voice:
+#include 
+#include 
+#include 
+
+// temp for stream sim Rnd( ) call:
+#include 
+
+#include "p_music.h"
+
+#define TEST_MOTHERFUCKING_CD 0			// Doubt this even works now
+#define TEST_PLAY_TIMING	  0
+
+namespace Pcm
+{
+	
+#define MAX_VOL					( 0x3fff )
+#define NORMAL_VOL				( 0x3fff >> 1 )
+
+#define MUSIC_PRIORITY			( DEFAULT_ASYNC_PRIORITY - 20 )
+#define STREAM_PRIORITY			( DEFAULT_ASYNC_PRIORITY - 10 )		// The lower the number, the higher the priority
+
+int	gIopBuffer = 0;
+int	gNonAllignedIopBuffer = 0;
+
+int	gPcmStatus = 0;
+
+// All the Wad file info for the music and audio streams
+SWadInfo gWadInfo[ 2 ];
+
+CFileStreamInfo gStreamList [ NUM_FILE_STREAMS ] = {
+		{ FILE_STREAM_MUSIC, LOAD_STATE_DONE, PLAY_STATE_DONE, NULL, 0, 0, 0 },					// Music stream
+		{ FILE_STREAM_STREAM0, LOAD_STATE_DONE, PLAY_STATE_DONE, NULL, 0, 0, 0 },				// Audio stream 0
+		{ FILE_STREAM_STREAM1, LOAD_STATE_DONE, PLAY_STATE_DONE, NULL, 0, 0, 0 },   			// Audio stream 1
+		{ FILE_STREAM_STREAM2, LOAD_STATE_DONE, PLAY_STATE_DONE, NULL, 0, 0, 0 }				// Audio stream 2
+};
+
+CFileStreamInfo * gpMusicInfo = &gStreamList[FILE_STREAM_MUSIC];
+CFileStreamInfo * gpStreamInfo[ NUM_STREAMS ] = { &gStreamList[FILE_STREAM_STREAM0], &gStreamList[FILE_STREAM_STREAM1],
+												  &gStreamList[FILE_STREAM_STREAM2] };
+
+unsigned int gStreamVolume = 100;
+
+// Communication to RPC -- ezpcm.irx:
+// EzPcm is the function that the EE uses to call the IRX program EzPcm.irx.
+// EzPcm.irx is a separate code module that runs on the IOP chip (the playstation one chip).
+// The EzPcm module is build from the pcm* files in music\ngps\pcm.
+// The files in the music\ngps\bgm folder were used to build a different irx that is
+// no longer used for streaming music, so don't worry about the BGM files.
+
+#define MAX_QUEUED_REQUESTS	(10)
+
+// SifCmd packets
+static SSifCmdStreamRequestPacket s_cmd_requests[MAX_QUEUED_REQUESTS] __attribute__ ((aligned(16)));
+static SSifCmdStreamLoadStatusPacket s_cmd_load_status __attribute__ ((aligned(16)));
+
+static uint32 s_cmd_request_dma_ids[MAX_QUEUED_REQUESTS] = { 0 };
+static uint32 s_cmd_load_status_last_id = 0;
+static int s_cmd_request_free_index = 0;
+
+static int s_request_id = 0;
+static volatile bool s_request_done;
+static volatile int s_request_result;
+static volatile int s_request_result_id;
+
+// These are set by sifcmds sent directly from the IOP
+volatile int sSentStatus = 0;
+volatile bool sNewStatus = false;
+
+// Timing test code
+#if TEST_PLAY_TIMING
+static volatile uint32 test_timing_start;
+static volatile uint32 test_timing_end;
+static 			int	   test_timing_request_id = -1;
+#endif // TEST_PLAY_TIMING
+
+void result_callback(void *p, void *q)
+{
+	SSifCmdStreamResultPacket *h = (SSifCmdStreamResultPacket *) p;
+
+	s_request_result = h->m_return_value;
+	s_request_result_id = h->m_header.opt;
+	sSentStatus |= h->m_status_flags;
+	sNewStatus = true;
+	s_request_done = true;
+
+	//scePrintf("*************** EzPcm() result ID %d result = %d\n", s_request_result_id, s_request_result);
+
+#if TEST_PLAY_TIMING
+	if (s_request_result_id == test_timing_request_id)
+	{
+		test_timing_end = Tmr::GetTimeInUSeconds();
+		scePrintf("EzPcm() turnaround time Frame %d Time %d (%x - %x)\n", Tmr::GetVblanks(), (test_timing_end - test_timing_start), test_timing_end, test_timing_start);
+	}
+#endif // TEST_PLAY_TIMING
+
+	ExitHandler();
+}
+
+void status_callback(void *p, void *q)
+{
+	SSifCmdStreamStatusPacket *h = (SSifCmdStreamStatusPacket *) p;
+
+	sSentStatus |= h->m_status_flags;
+	sNewStatus = true;
+
+#if TEST_PLAY_TIMING
+	if ((int) h->m_header.opt == test_timing_request_id)
+	{
+		test_timing_end = Tmr::GetTimeInUSeconds();
+		scePrintf("EzPcm() final turnaround time Frame %d Time %d (%x - %x)\n", Tmr::GetVblanks(), (test_timing_end - test_timing_start), test_timing_end, test_timing_start);
+	}
+#endif // TEST_PLAY_TIMING
+
+	ExitHandler();
+}
+
+bool get_status(int & status)
+{
+	bool new_stat;
+
+	// Disable interrupts just in case we get a sifcmd callback
+	DI();
+	status = sSentStatus;
+	new_stat = sNewStatus;
+	sSentStatus = 0;
+	sNewStatus = false;
+	EI();
+
+	return new_stat;
+}
+
+// prototype:
+void EzPcm( int command, int data, int data2 = 0, int data3 = 0 );
+
+// IOP
+void EzPcm( int command, int data, int data2, int data3 )
+{
+	//scePrintf("Doing EzPcm request id %d with command %x\n", s_request_id, command);
+
+	#if TEST_PLAY_TIMING
+	if (command == EzADPCM_PLAYPRELOADEDMUSIC)
+	{
+		test_timing_start = Tmr::GetTimeInUSeconds();
+		test_timing_request_id = s_request_id;
+		Dbg_Message("Doing EzPcm request id %d with command %x", test_timing_request_id, command);
+	}
+	#endif // TEST_PLAY_TIMING
+
+	int nextFreeIndex = (s_cmd_request_free_index + 1) % MAX_QUEUED_REQUESTS;
+	SSifCmdStreamRequestPacket *p_packet = &s_cmd_requests[s_cmd_request_free_index];
+
+	// Make sure this request packet is not still in the Sif DMA queue
+	if (s_cmd_request_dma_ids[s_cmd_request_free_index])
+	{
+		while(sceSifDmaStat(s_cmd_request_dma_ids[s_cmd_request_free_index]) >= 0)
+			;
+	}
+
+	p_packet->m_header.opt = s_request_id++;
+	p_packet->m_request.m_command = command;
+	p_packet->m_request.m_param[0] = data;
+	p_packet->m_request.m_param[1] = data2;
+	p_packet->m_request.m_param[2] = data3;
+
+	s_request_done = false;		// Callback will change it to true
+	s_cmd_request_dma_ids[s_cmd_request_free_index] = sceSifSendCmd(STREAM_REQUEST_COMMAND, p_packet, sizeof(*p_packet), 0, 0, 0);
+
+	s_cmd_request_free_index = nextFreeIndex;
+
+	//Dbg_MsgAssert(request_result_id == (request_id -1), ("Result id (%d) differs from request id (%d)", request_result_id, request_id - 1));
+}
+
+// Tell IOP that the buffer is done loading
+void send_load_done(EFileStreamType streamType, int buffer_num, int bytes_loaded)
+{
+	Dbg_Assert((buffer_num >= 0) && (buffer_num <= 1));
+
+	// Wait for last send to complete
+	if (s_cmd_load_status_last_id)
+	{
+		while(sceSifDmaStat(s_cmd_load_status_last_id) >= 0)
+			;
+	}
+
+	s_cmd_load_status.m_stream_num = streamType - 1;
+	s_cmd_load_status.m_buffer_num = buffer_num;
+	s_cmd_load_status.m_bytes_loaded = bytes_loaded;
+
+	//FlushCache(0);
+
+	s_cmd_load_status_last_id = sceSifSendCmd(STREAM_LOAD_STATUS_COMMAND, &s_cmd_load_status, sizeof(s_cmd_load_status), 0, 0, 0);
+	//scePrintf("Audio stream %d buffer #%d finished loading\n", streamType, buffer_num);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+#include 
+
+void InitCDPlease( void )
+{
+	if (Config::CD())
+	{
+		// already done since we're building in CD mode...
+		return;
+	}
+	
+	#if TEST_MOTHERFUCKING_CD
+	static bool initializedAndShit = false;
+	if ( !initializedAndShit )
+	{
+		sceCdInit(SCECdINIT);
+		// this may oughtta be changed to SCDCdDVD...
+		// Actually, a smart thing to do would be to have this defined somewhere
+		// and switched between SCECdCD or SCDCdDVD depending on the build?
+		//sceCdMmode(SCECdCD);
+		sceCdMmode(SCECdDVD);
+		initializedAndShit = true;
+	}
+	#endif
+}	
+
+static bool CheckForCDErrors( void )
+{
+	int cdErr;
+	cdErr = sceCdGetError( );
+	switch ( cdErr )
+	{
+		case ( SCECdErNO ): // no error
+			break;
+		case ( SCECdErTRMOPN ):	// cover opened during playback
+		case ( SCECdErREAD ):	// read error
+			return ( true );
+		default:
+			printf( "cd err %d\n", cdErr );
+			break;
+	}
+	return ( false );
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+// CFileStreamInfo
+//
+
+// Preloads the SPU audio buffers (because this can take a few frames)
+void			CFileStreamInfo::preload_spu_buffers()
+{
+	// Check if it is music or normal audio
+	if (IsMusicStream())
+	{
+		// got initial music buffers loaded - start preloading SPU buffers
+		EzPcm( EzADPCM_PRELOADMUSIC, m_fileSize / 2 ); // because there are two channels L and R
+	}
+	else
+	{
+		// got initial stream buffers loaded - start preloading SPU buffers
+		EzPcm( EzADPCM_PRELOADSTREAM | GetAudioStreamIndex(), m_fileSize );
+	}
+}
+
+// Starts playing a preloaded audio stream
+void			CFileStreamInfo::start_preloaded_audio()
+{
+	gPcmStatus &= ~PCMSTATUS_LOAD_AUDIO( m_streamType );
+	gPcmStatus |= PCMSTATUS_AUDIO_PLAYING( m_streamType );
+
+	// Check if it is music or normal audio
+	if (IsMusicStream())
+	{
+		// got initial music buffers loaded on the SPU - start it playing:
+		EzPcm( EzADPCM_PLAYPRELOADEDMUSIC, 0 );
+	}
+	else
+	{
+		// got initial stream buffers loaded on the SPU - start it playing:
+		EzPcm( EzADPCM_PLAYPRELOADEDSTREAM | GetAudioStreamIndex(), m_volume, m_pitch );
+	}
+}
+
+// Starts playing the audio stream
+void			CFileStreamInfo::start_audio()
+{
+	gPcmStatus &= ~PCMSTATUS_LOAD_AUDIO( m_streamType );
+	gPcmStatus |= PCMSTATUS_AUDIO_PLAYING( m_streamType );
+
+	// Check if it is music or normal audio
+	if (IsMusicStream())
+	{
+		// got initial music buffers loaded - start it playing:
+		EzPcm( EzADPCM_PLAYMUSIC, m_fileSize / 2 ); // because there are two channels L and R
+	}
+	else
+	{
+		// got initial stream buffers loaded - start it playing:
+		EzPcm( EzADPCM_PLAYSTREAM | GetAudioStreamIndex(), m_fileSize, m_volume, m_pitch );
+	}
+}
+
+// Starts the load of an audio stream chunk
+void			CFileStreamInfo::load_chunk(int buffer)
+{
+	if ( !mp_fileHandle )
+	{
+		Dbg_MsgAssert( 0,( "s'it, yo... no file opened for stream index %d", GetAudioStreamIndex() ));
+		return;
+	}
+
+	// Check if it is music or normal audio
+	if (IsMusicStream())
+	{
+		void *p_iop_addr = (void *) (gIopBuffer + ( MUSIC_IOP_BUFFER_SIZE * buffer ));
+		mp_fileHandle->Read( p_iop_addr, 1, MUSIC_IOP_BUFFER_SIZE );
+		//scePrintf("Reading music buffer %d from disk to IOP buffer %x of size %d\n", buffer, p_iop_addr, MUSIC_IOP_BUFFER_SIZE);
+
+		m_offset += MUSIC_IOP_BUFFER_SIZE;
+	}
+	else
+	{
+		int whichStream =  GetAudioStreamIndex();
+		void *p_iop_addr = (void *) (gIopBuffer + STREAM_IOP_OFFSET( whichStream ) + ( STREAM_HALF_IOP_BUFFER_SIZE * buffer ));
+		mp_fileHandle->Read( p_iop_addr, 1, STREAM_HALF_IOP_BUFFER_SIZE );
+
+		m_offset += STREAM_HALF_IOP_BUFFER_SIZE;
+	}
+}
+
+// Checks to see if the IOP module is requesting another stream chunk
+ELoadState		CFileStreamInfo::check_for_load_requests()
+{
+	ELoadState state = LOAD_STATE_IDLE;
+
+	if ( gPcmStatus & PCMSTATUS_NEED_AUDIO_BUFFER_0( m_streamType ) )
+	{
+		load_chunk( 0 );
+		state = LOAD_STATE_LOADING0;
+		gPcmStatus &= ~PCMSTATUS_NEED_AUDIO_BUFFER_0( m_streamType );
+	}
+	else if ( gPcmStatus & PCMSTATUS_NEED_AUDIO_BUFFER_1( m_streamType ) )
+	{
+		load_chunk( 1 );
+		state = LOAD_STATE_LOADING1;
+		gPcmStatus &= ~PCMSTATUS_NEED_AUDIO_BUFFER_1( m_streamType );
+	}
+
+	return state;
+}
+
+// Checks to see if loading of stream chunk is done
+// errno will be non-zero if we were busy AND there was an error
+bool			CFileStreamInfo::is_chunk_load_done(int & errno)
+{
+	Dbg_Assert(mp_fileHandle);
+
+	// Clear error var
+	errno = 0;
+
+	// Check for delay errors
+	// Garrett: We should only be concerned if we get a request for the buffer we are currently loading.  The
+	// only problem to this is the fact that the IOP doesn't check to see if a buffer is loaded at all.  We
+	// must add this so we can get rid of the conservative check of making sure we don't get ANY new load requests.
+//	if ( gPcmStatus & ( PCMSTATUS_NEED_AUDIO_BUFFER_0( m_streamType ) | PCMSTATUS_NEED_AUDIO_BUFFER_1( m_streamType ) ) )
+	int load_flag = (m_loadState == LOAD_STATE_LOADING0) ? PCMSTATUS_NEED_AUDIO_BUFFER_0( m_streamType ) : PCMSTATUS_NEED_AUDIO_BUFFER_1( m_streamType );
+	if ( gPcmStatus & load_flag )
+	{
+		int cur_buffer_load = (int) (m_loadState - LOAD_STATE_LOADING0);
+		Dbg_Message("Load State: %d", m_loadState);
+		Dbg_MsgAssert( 0, ( "If repeatable, tell Garrett that audio #%d buffer #%d didn't load on time. Status: %x", m_streamType, cur_buffer_load, gPcmStatus ));
+		printf( "If repeatable, tell Garrett audio #%d buffer #%d didn't load on time -- must increase IOP streaming buf size. Status %x", m_streamType, cur_buffer_load, gPcmStatus );
+		errno = (IsMusicStream()) ? -1 : m_streamType;
+	}
+
+	if (!mp_fileHandle->IsBusy())
+	{
+		int cur_buffer_load = (int) (m_loadState - LOAD_STATE_LOADING0);
+
+		// Tell IOP that we are done
+		send_load_done(m_streamType, cur_buffer_load, IsMusicStream() ? MUSIC_IOP_BUFFER_SIZE : STREAM_HALF_IOP_BUFFER_SIZE);
+
+		return true;
+	}
+
+	// Return that we aren't done
+	return false;
+}
+
+// returns true and closes fine if we read the whole file
+bool			CFileStreamInfo::file_finished()
+{
+	if ( m_offset < m_fileSize )
+	{
+		return false;
+	} else {
+		Dbg_Assert(mp_fileHandle);		// It shouldn't be closed yet
+
+		File::CAsyncFileLoader::sClose( mp_fileHandle );
+		mp_fileHandle = NULL;
+
+		return true;
+	}
+}
+
+// Starts an audio stream
+bool			CFileStreamInfo::StartStream(File::SHedFile *pHedFile, bool play)
+{
+	// make sure CD isn't being used by another system:
+	// Garrett: This should go away
+	File::StopStreaming( );
+
+	// Make sure these load flags aren't set
+	if ( gPcmStatus & ( PCMSTATUS_NEED_AUDIO_BUFFER_0( m_streamType ) | PCMSTATUS_NEED_AUDIO_BUFFER_1( m_streamType ) ) )
+	{
+		Dbg_Message("Load State: %d", m_loadState);
+		Dbg_MsgAssert( 0, ( "Start Stream on audio #%d still has load buffer flag. Status: %x", m_streamType, gPcmStatus ));
+		printf( "Start Stream on audio #%d still has load buffer flag. Status %x", m_streamType, gPcmStatus );
+	}
+
+	// Find correct wad filename
+	char *p_wadName;
+	unsigned int wadLsn;
+	int priority;
+
+	if (IsMusicStream())
+	{
+		wadLsn = gWadInfo[MUSIC_CHANNEL].m_lsn;
+		p_wadName = gWadInfo[MUSIC_CHANNEL].m_fileName;
+		priority = MUSIC_PRIORITY;
+	}
+	else
+	{
+		wadLsn = gWadInfo[EXTRA_CHANNEL].m_lsn;
+		p_wadName = gWadInfo[EXTRA_CHANNEL].m_fileName;
+		priority = STREAM_PRIORITY;
+	}
+
+	// load Audio wad:
+	if ( mp_fileHandle )
+	{
+		Dbg_Message( "What?  Audio file opened still: %d", mp_fileHandle );
+		Dbg_MsgAssert( 0,( "What?  Stream file still (or already) opened." ));
+		return false;
+	}
+
+	if (Config::CD() || TEST_MOTHERFUCKING_CD)
+	{
+		mp_fileHandle = File::CPs2AsyncFileLoader::sRawOpen(wadLsn, false, priority);
+	}
+	else
+	{
+		if (pHedFile->HasNoWad())
+		{
+			char local_file[256];
+
+			strcpy(local_file, pHedFile->p_filename);
+			if (IsMusicStream())
+			{
+				strcat(local_file, ".ivg");
+			}
+			else
+			{
+				strcat(local_file, ".vag");
+			}
+			mp_fileHandle = File::CAsyncFileLoader::sOpen(&local_file[1], false, priority);
+		}
+		else
+		{
+			mp_fileHandle = File::CAsyncFileLoader::sOpen(p_wadName, false, priority);
+		}
+	}
+
+	if ( !mp_fileHandle )
+	{
+		Dbg_MsgAssert( 0,( "Play Audio: couldn't find audio wad file %s", p_wadName ));
+		printf( "play audio failed to find audio wad file %s\n", p_wadName );
+		return false;
+	}
+
+	m_offset = 0;
+	mp_fileHandle->SetDestination(File::MEM_IOP);
+
+	if (pHedFile->HasNoWad())
+	{
+		// .ivg files don't have the VAG header
+		if (!IsMusicStream()) mp_fileHandle->Seek( SIZE_OF_VAGHEADER, SEEK_SET );
+	}
+	else
+	{
+		mp_fileHandle->Seek( pHedFile->Offset, SEEK_SET );
+	}
+
+	m_fileSize = pHedFile->GetFileSize();
+	m_loadState = LOAD_STATE_LOADING0;
+	m_playState = (play) ? PLAY_STATE_START : PLAY_STATE_PRELOAD_EE;
+
+	// Start loading the data, but don't play it yet
+	load_chunk(0);
+
+	// Change the state
+	gPcmStatus |= PCMSTATUS_LOAD_AUDIO(m_streamType);
+
+	return true;
+}
+
+// Plays a preloaded audio stream
+bool			CFileStreamInfo::PlayStream()
+{
+	switch (m_playState)
+	{
+	case PLAY_STATE_DONE:
+	case PLAY_STATE_PLAYING:
+		return false;
+
+	case PLAY_STATE_PAUSED:
+		// Not implemented yet
+		Dbg_MsgAssert(0, ("Playing a paused stream not implemented yet"));
+		return false;
+
+	case PLAY_STATE_PRELOAD_EE:
+		Dbg_MsgAssert(0, ("Starting a preloaded stream that isn't finished preloading"));
+		return false;
+
+	case PLAY_STATE_PRELOAD_IOP:
+	case PLAY_STATE_STOP:
+	case PLAY_STATE_START:
+		switch (m_loadState)
+		{
+		case LOAD_STATE_DONE:
+		case LOAD_STATE_IDLE:
+		case LOAD_STATE_LOADING1:			// Since it may already be loading the next section
+			m_playState = PLAY_STATE_PLAYING;
+
+			//start_audio();
+			start_preloaded_audio();
+
+			break;
+
+		case LOAD_STATE_LOADING0:
+			m_playState = PLAY_STATE_START;
+			Dbg_MsgAssert(0, ("Starting a preloaded stream that isn't finished preloading. Load state: %d", m_loadState));
+
+			break;
+		}
+
+		break;
+	}
+
+	return true;
+}
+
+// Stops an audio stream
+bool			CFileStreamInfo::StopStream()
+{
+	int status = IsMusicStream() ? PCMAudio_GetMusicStatus( ) : PCMAudio_GetStreamStatus( GetAudioStreamIndex() );
+
+	// Check states
+	if ( status == PCM_STATUS_PLAYING )
+	{
+		if (IsMusicStream())
+		{
+			EzPcm( EzADPCM_STOPMUSIC, 0 );
+		}
+		else
+		{
+			EzPcm( EzADPCM_STOPSTREAM, GetAudioStreamIndex() );
+		}
+	}
+
+	// Close async file
+	if ( mp_fileHandle )
+	{
+		m_loadState = LOAD_STATE_DONE;
+		File::CAsyncFileLoader::sClose( mp_fileHandle );
+		mp_fileHandle = NULL;
+	}
+
+	// Clear flags
+	PCMAudio_UpdateStatus();
+	m_playState = PLAY_STATE_DONE;
+	gPcmStatus &= ~( PCMSTATUS_NEED_AUDIO_BUFFER_0( m_streamType ) |
+					 PCMSTATUS_NEED_AUDIO_BUFFER_1( m_streamType ) |
+					 PCMSTATUS_LOAD_AUDIO( m_streamType ) |
+					 PCMSTATUS_AUDIO_PLAYING( m_streamType ) |
+					 PCMSTATUS_AUDIO_READY(m_streamType) );
+
+	return true;
+}
+
+// Update: executed on each stream every frame
+// returns error number on error, 0 otherwise...
+int				CFileStreamInfo::Update()
+{
+	int errno = 0;
+
+	switch (m_loadState)
+	{
+	case LOAD_STATE_IDLE:
+		// Make sure IOP didn't drop out (would probably be a state problem)
+		//Dbg_MsgAssert(gPcmStatus & PCMSTATUS_AUDIO_PLAYING( m_streamType ), ("Generic stream %d not playing while in LOAD_STATE_IDLE", m_streamType));
+
+		m_loadState = check_for_load_requests();
+
+		break;
+
+	case LOAD_STATE_LOADING0:
+	case LOAD_STATE_LOADING1:
+		// Make sure IOP didn't drop out (would probably be a state problem)
+		//Dbg_MsgAssert(gPcmStatus & PCMSTATUS_AUDIO_PLAYING( m_streamType ), ("Generic stream %d not playing while in LOAD_STATE_LOADING", m_streamType));
+
+		if (!is_chunk_load_done(errno))
+		{
+			if (errno != 0)
+			{
+				return errno;
+			}
+
+			break; // still busy loading
+		}
+
+		// Check if it needs starting
+		if (m_playState == PLAY_STATE_START)
+		{
+			m_playState = PLAY_STATE_PLAYING;
+
+			start_audio();
+		}
+		else if (m_playState == PLAY_STATE_PRELOAD_EE)
+		{
+			m_playState = PLAY_STATE_PRELOAD_IOP;
+
+			preload_spu_buffers();
+		}
+
+		// Change state and possibly close file
+		if (file_finished())
+		{
+			m_loadState = LOAD_STATE_DONE;
+		} else {
+			m_loadState = LOAD_STATE_IDLE;
+		}
+
+		break;
+
+	case LOAD_STATE_DONE:
+		// Just wait for the audio to finish playing
+		if ( !(gPcmStatus & PCMSTATUS_AUDIO_PLAYING( m_streamType )) )
+		{
+			m_playState = PLAY_STATE_DONE;
+			// Clear out any old sent load request flags
+			gPcmStatus &= ~( PCMSTATUS_NEED_AUDIO_BUFFER_0( m_streamType ) | PCMSTATUS_NEED_AUDIO_BUFFER_1( m_streamType ) );
+		}
+
+		break;
+	}
+
+	return errno;
+}
+
+#define DEBUG_LOAD_FLAGS 0
+
+#if DEBUG_LOAD_FLAGS
+int old_load_state[NUM_FILE_STREAMS];
+int old_play_state[NUM_FILE_STREAMS];
+#endif
+
+// Combine the IOP sent status into gPcmStatus
+void PCMAudio_UpdateStatus()
+{
+	int new_status;
+
+	if (get_status(new_status))
+	{
+		for ( int i = 0; i < NUM_FILE_STREAMS; i++ )
+		{
+			gPcmStatus &= ~( PCMSTATUS_AUDIO_PLAYING( i ) | PCMSTATUS_AUDIO_READY( i ) );
+		}
+		gPcmStatus |= new_status;
+	}
+}
+
+
+// returns error number on error, 0 otherwise...
+int PCMAudio_Update( void )
+{
+	int i;
+
+#if DEBUG_LOAD_FLAGS
+	for (int i = 0; i < NUM_FILE_STREAMS; i++)
+	{
+		old_load_state[i] = gStreamList[ i ].m_loadState;
+		old_play_state[i] = gStreamList[ i ].m_playState;
+	}
+#endif
+
+	// Clear the flags
+	PCMAudio_UpdateStatus();
+
+	static bool print_panic = true;		// Using this now until we can clear the PANIC flag on the IOP
+	if ((gPcmStatus & PCMSTATUS_PANIC) && print_panic)
+	{
+		print_panic = false;
+		scePrintf("ezpcm.irx module set PANIC flag: Look at its last error message.\n");
+		//Dbg_MsgAssert(0, ("ezpcm.irx module set PANIC flag: Look at its last error message."));
+	}
+
+	// Make sure there aren't any CD errors
+	if (0 && (Config::CD() || TEST_MOTHERFUCKING_CD) && CheckForCDErrors( ))
+	{
+		return ( -1 );
+	}
+
+	// Go through each stream and update
+	for (i = 0; i < NUM_FILE_STREAMS; i++)
+	{
+		CFileStreamInfo *pInfo = &gStreamList[ i ];
+
+		int ret_val = pInfo->Update();
+
+		if (ret_val != 0)
+		{
+			return ret_val;
+		}
+	}
+
+#if 0
+		// If file loads go out of sync over a busy network, keep it from crashing:
+		if ( gMusicInfo.mp_fileHandle && !( gPcmStatus & PCMSTATUS_MUSIC_PLAYING ) )
+		{
+			File::CAsyncFileLoader::sClose( gMusicInfo.mp_fileHandle );
+			gMusicInfo.mp_fileHandle = NULL;
+			Dbg_Message( "Symptom of a busy network: Closing music file." );
+		}
+		for ( i = 0; i < NUM_STREAMS; i++ )
+		{
+			if ( gStreamInfo[ i ].mp_fileHandle && !( gPcmStatus & (PCMSTATUS_STREAM_PLAYING( i ) | PCMSTATUS_LOAD_STREAM( i ))) )
+			{
+				File::CAsyncFileLoader::sClose( gStreamInfo[i].mp_fileHandle );
+				gStreamInfo[ i ].mp_fileHandle = NULL;
+				Dbg_Message( "Symptom of a busy network: Closing stream file." );
+			}
+		}
+#endif
+
+#if 0
+	Dbg_MsgAssert(!(gPcmStatus & ( PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 |
+						PCMSTATUS_NEED_STREAM0_BUFFER_0 | PCMSTATUS_NEED_STREAM0_BUFFER_1 |
+						PCMSTATUS_NEED_STREAM1_BUFFER_0 | PCMSTATUS_NEED_STREAM1_BUFFER_1 |
+						PCMSTATUS_NEED_STREAM2_BUFFER_0 | PCMSTATUS_NEED_STREAM2_BUFFER_1 )), ("Skipped a buffer load: status %x", gPcmStatus));
+#else
+	if (gPcmStatus & ( PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 |
+						PCMSTATUS_NEED_STREAM0_BUFFER_0 | PCMSTATUS_NEED_STREAM0_BUFFER_1 |
+						PCMSTATUS_NEED_STREAM1_BUFFER_0 | PCMSTATUS_NEED_STREAM1_BUFFER_1 |
+						PCMSTATUS_NEED_STREAM2_BUFFER_0 | PCMSTATUS_NEED_STREAM2_BUFFER_1 ))
+	{
+		Dbg_Message("************Skipped a buffer load: status %x", gPcmStatus);
+#if DEBUG_LOAD_FLAGS
+		for (int i = 0; i < NUM_FILE_STREAMS; i++)
+		{
+			Dbg_Message("Audio Stream %d: load state = %d", i, gStreamList[ i ].m_loadState);
+			Dbg_Message("Audio Stream %d: play state = %d", i, gStreamList[ i ].m_playState);
+			Dbg_Message("Audio Stream %d: old load state = %d", i, old_load_state[i]);
+			Dbg_Message("Audio Stream %d: old play state = %d", i, old_play_state[i]);
+		}
+#endif
+	}
+#endif
+
+	return ( 0 );
+}
+
+/* ------------
+   Initialize rpc  -- Just call once at the very beginning!
+   ------------ */
+void PCMAudio_Init( void )
+{
+	Dbg_MsgAssert(NUM_FILE_STREAMS == (NUM_STREAMS + 1),
+				  ("Number of file slots allocated for audio streams (%d) different than number of IOP audio streams (%d)", NUM_FILE_STREAMS - 1, NUM_STREAMS));
+
+	// Check to make sure stream array is filled
+	for (int i = 0; i < NUM_STREAMS; i++)
+	{
+		Dbg_Assert(gpStreamInfo[i]);
+	}
+
+	DI();
+
+	printf( "Setting up Sif Cmd with EzPCM Streaming IRX...\n" );
+	// No longer need to call sceSifSetCmdBuffer() since we share it with async filesys
+//	static sceSifCmdData cmdbuffer[NUM_COMMAND_HANDLERS];
+//	sceSifSetCmdBuffer( &cmdbuffer[0], NUM_COMMAND_HANDLERS);
+
+	sceSifAddCmdHandler(STREAM_RESULT_COMMAND, result_callback, NULL );
+	sceSifAddCmdHandler(STREAM_STATUS_COMMAND, status_callback, NULL );
+
+	EI();
+
+	// if CD system hasn't been initialized,
+	// but you want to test music from CD,
+	// this will do it...
+	InitCDPlease( );
+
+
+	Dbg_MsgAssert( !gIopBuffer,( "What the fuck - buffer already exists?" ));
+
+	FlushCache( 0 );
+	
+	PCMAudio_GetIopMemory( );
+	
+	EzPcm( EzADPCM_INIT, gIopBuffer );
+	//EzPcm( EzADPCM_SETSTREAMGLOBVOL, gStreamVolume );
+} // end of PCMAudio_Init( )
+
+int PCMAudio_GetIopMemory( void )
+{
+	
+	
+	if ( !gNonAllignedIopBuffer )
+	{
+		// streaming buffers:
+		gNonAllignedIopBuffer = ( int )sceSifAllocIopHeap( TOTAL_IOP_BUFFER_SIZE_NEEDED + ALLIGN_REQUIREMENT );
+		gIopBuffer = gNonAllignedIopBuffer + ALLIGN_REQUIREMENT - ( gNonAllignedIopBuffer & ( ALLIGN_REQUIREMENT - 1 ) );
+	
+		if ( !gNonAllignedIopBuffer )
+		{
+			Dbg_MsgAssert( 0,( "Failed to allocate IOP memory - %d k\n", TOTAL_IOP_BUFFER_SIZE_NEEDED / 1024 ));
+			return 0;
+		}
+	}
+	return ( gIopBuffer );
+}
+void PCMAudio_SetStreamGlobalVolume( unsigned int volume )
+{
+	gStreamVolume = volume;
+	//if ( gPcmStatus ) // make sure we've initialized PCM stuff:
+	//{
+	//	EzPcm( EzADPCM_SETSTREAMGLOBVOL, gStreamVolume );
+	//}
+}
+
+bool PCMAudio_SetMusicVolume( float volume )
+{
+	int vol = ( int )PERCENT( volume, MAX_VOL );
+	if ( vol > MAX_VOL )
+		vol = MAX_VOL;
+
+	EzPcm( EzADPCM_SETMUSICVOL, ( vol << 16 ) | vol );
+	return true;
+}// end of PCMAudio_SetMusicVolume( )
+
+
+/*int PCMAudio_SetStreamVolumeAndPitch( float volumeL, float volumeR, float fPitch, int whichStream )
+{
+	Dbg_Message( "pitch %d", pitch );
+   	return ( EzPcm( EzADPCM_SETSTREAMVOLANDPITCH | whichStream, ( ( volL << 16 ) | volR ), pitch ) );
+}// end of PCMAudio_SetVolumeAndPitch( )
+*/
+
+// Should check before calling this to make sure the sound is playing and has the uniqueID of the sound you are
+// trying to adjust:
+bool PCMAudio_SetStreamVolume( float volumeL, float volumeR, int whichStream )
+{
+	int volL = ( int )PERCENT( volumeL, gStreamVolume );
+	volL = ( int )PERCENT( volL, NORMAL_VOL );
+	if ( volL > MAX_VOL )
+		volL = MAX_VOL;
+	else if ( volL < -MAX_VOL )
+		volL = -MAX_VOL;
+	int volR;
+	if ( volumeR == volumeL )
+	{
+		volR = volL;
+	}
+	else
+	{
+		volR = ( int )PERCENT( volumeR, gStreamVolume );
+		volR = ( int )PERCENT( volR, NORMAL_VOL );
+		if ( volR > MAX_VOL )
+			volR = MAX_VOL;
+		else if ( volR < -MAX_VOL )
+			volR = -MAX_VOL;
+	}
+
+	// for phasing effects (of sounds behind you), set the volume
+	// to 0x7fff
+	if ( volL < 0 )
+	{
+		volL = 0x7fff + volL;
+	}
+	if ( volR < 0 )		// Just in case we start phase shifting the right side instead
+	{
+		volR = 0x7fff + volR;
+	}
+
+	gpStreamInfo[ whichStream ]->m_volume = ( ( (volL & 0x7FFF) << 16 ) | (volR & 0x7FFF) );
+
+	if (gpStreamInfo[ whichStream ]->m_playState == PLAY_STATE_PLAYING)
+	{
+		EzPcm( EzADPCM_SETSTREAMVOL | whichStream, gpStreamInfo[ whichStream ]->m_volume);
+	}
+
+	return true;
+}// end of PCMAudio_SetVolume( )
+
+// Currently, this only works when the stream isn't playing
+bool PCMAudio_SetStreamPitch( float fPitch, int whichStream )
+{
+	if ( fPitch > 100.0f )
+	{
+		fPitch = 100.0f;
+	}
+	int pitch;
+	pitch = ( int ) PERCENT( fPitch, ( float )DEFAULT_PITCH ); 
+	if ( pitch == 0 )
+	{
+		Dbg_Message( "Trying to set stream to zero pitch." );
+		return ( false );
+	}
+
+	if (gpStreamInfo[ whichStream ]->m_playState == PLAY_STATE_PLAYING)
+	{
+		Dbg_MsgAssert(0, ("Can't change pitch of stream %d while it is playing", whichStream));
+		return false;
+	}
+	else
+	{
+		gpStreamInfo[ whichStream ]->m_pitch = pitch;
+		return true;
+	}
+}
+
+bool PCMAudio_PlayMusicTrack( const char *filename )
+{
+	if ( PCMAudio_GetMusicStatus( ) != PCM_STATUS_FREE )
+	{
+		Dbg_MsgAssert( 0 , ( "Playing new track without stopping the first track." ) );
+		return ( false );
+	}
+
+	if (!(Config::CD() || TEST_MOTHERFUCKING_CD))
+	{
+		int testMusic = 0;
+		testMusic = Script::GetInteger( 0x47ac7ba5 ); // checksum 'testMusicFromHost'
+		if ( !testMusic )
+		{
+			return ( false );
+		}
+	}
+
+	// make sure CD is not in use by another system:
+	File::StopStreaming( );
+
+	if ( !gWadInfo[MUSIC_CHANNEL].mp_hed )
+	{
+		Dbg_Message( "Music header not loaded, can't play track %s", filename );
+		return ( false );
+	}
+
+	File::SHedFile *pHed = FindFileInHed( filename, gWadInfo[MUSIC_CHANNEL].mp_hed );
+	if ( !pHed )
+	{
+		Dbg_Message( "Track %d not found in music header.", filename );
+		return ( false );
+	}
+
+	// Start the actual stream
+	return gpMusicInfo->StartStream(pHed, true);
+} // end of PCMAudio_PlayMusicTrack( )
+
+
+bool PCMAudio_PlayMusicStream( uint32 checksum )
+{
+	if ( PCMAudio_GetMusicStatus( ) != PCM_STATUS_FREE )
+	{
+		Dbg_MsgAssert( 0 , ( "Playing music stream without stopping the last music track." ) );
+		return ( false );
+	}
+
+	if (!(Config::CD() || TEST_MOTHERFUCKING_CD))
+	{
+		int testMusic = 0;
+		testMusic = Script::GetInteger( 0x47ac7ba5 ); // checksum 'testMusicFromHost'
+		if ( !testMusic )
+		{
+			return ( false );
+		}
+	}
+
+	// make sure CD is not in use by another system:
+	File::StopStreaming( );
+
+	if ( !gWadInfo[MUSIC_CHANNEL].mp_hed )
+	{
+		Dbg_Message( "Music header not loaded, can't play music stream %s", Script::FindChecksumName(checksum) );
+		return ( false );
+	}
+
+	File::SHedFile *pHed = FindFileInHedUsingChecksum( checksum, gWadInfo[MUSIC_CHANNEL].mp_hed );
+	if ( !pHed )
+	{
+		Dbg_Message( "Music Stream %s not found in music header.", Script::FindChecksumName(checksum) );
+		return ( false );
+	}
+
+	// Start the actual stream
+	return gpMusicInfo->StartStream(pHed, true);
+}
+
+bool PCMAudio_PlayStream( uint32 checksum, int whichStream, float volumeL, float volumeR, float fPitch )
+{
+	Dbg_MsgAssert( whichStream < NUM_STREAMS, ( "Stream is out of range" ) );
+
+	if ( PCMAudio_GetStreamStatus( whichStream ) != PCM_STATUS_FREE )
+	{
+		Dbg_Message("Stream %d not free: status is %x", whichStream, PCMAudio_GetStreamStatus( whichStream ));
+		return ( false );
+	}
+
+	if (!(Config::CD() || TEST_MOTHERFUCKING_CD))
+	{
+		int testStreams = 0;
+		testStreams = Script::GetInteger( 0x62df9442 ); // checksum 'testStreamsFromHost'
+		if ( !testStreams )
+		{
+			return ( false );
+		}
+	}	
+	
+	//Dbg_Message("Playing stream %x on %d", checksum, whichStream);
+
+	// load from vag wad:
+	if ( !gWadInfo[EXTRA_CHANNEL].mp_hed )
+	{
+		//Dbg_Message( "Stream header not loaded, can't play track %s", filename );
+		return ( false );
+	}
+	File::SHedFile *pHed = FindFileInHedUsingChecksum( checksum, gWadInfo[EXTRA_CHANNEL].mp_hed );
+	if ( !pHed )
+	{
+		Dbg_Message( "Stream %s not found in stream header.", Script::FindChecksumName(checksum) );
+		return ( false );
+	}
+
+	// Set pitch and volume
+	PCMAudio_SetStreamPitch(fPitch, whichStream);
+	PCMAudio_SetStreamVolume(volumeL, volumeR, whichStream);
+
+	// Start the actual stream
+	return gpStreamInfo[ whichStream ]->StartStream(pHed, true);
+} // end of PCMAudio_PlayStream( )
+
+void PCMAudio_StopMusic( bool waitPlease )
+{
+	gpMusicInfo->StopStream();
+}
+
+void PCMAudio_StopStream( int whichStream, bool waitPlease )
+{
+	gpStreamInfo[ whichStream ]->StopStream();
+}
+
+void PCMAudio_StopStreams( void )
+{
+	int i;
+	for ( i = 0; i < NUM_STREAMS; i++ )
+	{
+		PCMAudio_StopStream( i, true );
+	}
+}
+
+// Get a stream loaded into a buffer, but don't play yet
+bool PCMAudio_PreLoadStream( uint32 checksum, int whichStream )
+{
+	Dbg_MsgAssert( whichStream < NUM_STREAMS, ( "Stream is out of range" ) );
+
+	if ( PCMAudio_GetStreamStatus( whichStream ) != PCM_STATUS_FREE )
+	{
+		return ( false );
+	}
+
+	if (!(Config::CD() || TEST_MOTHERFUCKING_CD))
+	{
+		int testStreams = 0;
+		testStreams = Script::GetInteger( 0x62df9442 ); // checksum 'testStreamsFromHost'
+		if ( !testStreams )
+		{
+			return ( false );
+		}
+	}
+
+	// load from vag wad:
+	if ( !gWadInfo[EXTRA_CHANNEL].mp_hed )
+	{
+		//Dbg_Message( "Stream header not loaded, can't play track %s", filename );
+		return ( false );
+	}
+	File::SHedFile *pHed = FindFileInHedUsingChecksum( checksum, gWadInfo[EXTRA_CHANNEL].mp_hed );
+	if ( !pHed )
+	{
+		//Dbg_Message( "Track %d not found in stream header.", filename );
+		return ( false );
+	}
+
+	// Start the actual stream
+	return gpStreamInfo[ whichStream ]->StartStream(pHed, false);
+}
+
+// Returns true if preload done.  Assumes that caller is calling this on a preloaded, but not yet played, stream.
+// The results are meaningless otherwise.
+bool PCMAudio_PreLoadStreamDone( int whichStream )
+{
+	Dbg_MsgAssert( whichStream < NUM_STREAMS, ( "Stream is out of range" ) );
+	Dbg_MsgAssert( (gpStreamInfo[whichStream]->m_playState == PLAY_STATE_PRELOAD_EE) ||
+				   (gpStreamInfo[whichStream]->m_playState == PLAY_STATE_PRELOAD_IOP), ( "PreLoadStreamDone(): This stream on channel %d is either playing or wasn't preloaded.  Load state %d.  Play State %d. IOP flags %x",
+																						 whichStream, gpStreamInfo[whichStream]->m_loadState, gpStreamInfo[whichStream]->m_playState, gPcmStatus ) );
+
+	//return gpStreamInfo[whichStream]->m_loadState != LOAD_STATE_LOADING0;
+	if (gPcmStatus & PCMSTATUS_STREAM_READY(whichStream))
+	{
+		Dbg_Message("PCMAudio_PreLoadStreamDone true for stream channel %d.  Load state %d.  Play State %d. IOP flags %x", whichStream,
+					gpStreamInfo[whichStream]->m_loadState, gpStreamInfo[whichStream]->m_playState, gPcmStatus);
+	}
+	return (gPcmStatus & PCMSTATUS_STREAM_READY(whichStream)) && (gpStreamInfo[whichStream]->m_playState == PLAY_STATE_PRELOAD_IOP);
+}
+
+// Tells a preloaded stream to start playing.  Must call PCMAudio_PreLoadStreamDone() first to guarantee that
+// it starts immediately.
+bool PCMAudio_StartPreLoadedStream( int whichStream, float volumeL, float volumeR, float pitch )
+{
+	Dbg_MsgAssert( whichStream < NUM_STREAMS, ( "Stream is out of range" ) );
+
+	// Set pitch and volume
+	PCMAudio_SetStreamPitch(pitch, whichStream);
+	PCMAudio_SetStreamVolume(volumeL, volumeR, whichStream);
+
+	// Start playing the actual stream
+	return gpStreamInfo[ whichStream ]->PlayStream();
+}
+
+bool PCMAudio_PreLoadMusicStream( uint32 checksum )
+{
+	if ( PCMAudio_GetMusicStatus( ) != PCM_STATUS_FREE )
+	{
+		Dbg_MsgAssert( 0 , ( "Preloading music stream without stopping the last music track." ) );
+		return ( false );
+	}
+
+	if (!(Config::CD() || TEST_MOTHERFUCKING_CD))
+	{
+		int testMusic = 0;
+		testMusic = Script::GetInteger( 0x47ac7ba5 ); // checksum 'testMusicFromHost'
+		if ( !testMusic )
+		{
+			return ( false );
+		}
+	}
+
+	// load from vag wad:
+	if ( !gWadInfo[MUSIC_CHANNEL].mp_hed )
+	{
+		//Dbg_Message( "Stream header not loaded, can't play track %s", filename );
+		return ( false );
+	}
+	File::SHedFile *pHed = FindFileInHedUsingChecksum( checksum, gWadInfo[MUSIC_CHANNEL].mp_hed );
+	if ( !pHed )
+	{
+		Dbg_Message( "Music Stream %s not found in music header.", Script::FindChecksumName(checksum) );
+		return ( false );
+	}
+
+	// Start the actual stream
+	return gpMusicInfo->StartStream(pHed, false);
+}
+
+bool PCMAudio_PreLoadMusicStreamDone( void )
+{
+	Dbg_MsgAssert( (gpMusicInfo->m_playState == PLAY_STATE_PRELOAD_EE) ||
+				   (gpMusicInfo->m_playState == PLAY_STATE_PRELOAD_IOP), ( "PreLoadMusicStreamDone(): This stream is either playing or wasn't preloaded." ) );
+
+	//return gpMusicInfo->m_loadState != LOAD_STATE_LOADING0;
+	return (gPcmStatus & PCMSTATUS_MUSIC_READY) && (gpMusicInfo->m_playState == PLAY_STATE_PRELOAD_IOP);
+}
+
+bool PCMAudio_StartPreLoadedMusicStream( void )
+{
+	// Start playing the actual stream
+	return gpMusicInfo->PlayStream();
+}
+
+int PCMAudio_GetMusicStatus( void )
+{
+	
+
+	if ( gPcmStatus & PCMSTATUS_LOAD_MUSIC )
+	{
+		return ( PCM_STATUS_LOADING );
+	}
+	else if ( gPcmStatus & PCMSTATUS_MUSIC_PLAYING )
+	{
+		return ( PCM_STATUS_PLAYING );
+	}
+	else if ( gpMusicInfo->m_playState == PLAY_STATE_STOP)
+	{
+		return ( PCM_STATUS_STOPPED );
+	}
+	else if ( gpMusicInfo->m_playState == PLAY_STATE_PLAYING)		// EE thinks it is playing, but not IOP
+	{
+		return ( PCM_STATUS_END );
+	}
+	else
+	{
+		//Dbg_Message("Music status is free %x", gPcmStatus);
+		return ( PCM_STATUS_FREE );
+	}
+	Dbg_MsgAssert( 0,( "Sell your stock in GCC." ));
+	return ( 0 );
+}
+
+int PCMAudio_GetStreamStatus( int whichStream )
+{
+	
+	if ( (whichStream < 0) || (whichStream >= NUM_STREAMS) )
+	{
+		Dbg_MsgAssert( 0, ( "Checking stream status on stream %d, past valid range ( 0 to %d ).", whichStream, NUM_STREAMS - 1 ) );
+		return ( false );
+	}
+	if ( gPcmStatus & PCMSTATUS_LOAD_STREAM( whichStream ) )
+	{
+		return ( PCM_STATUS_LOADING );
+	}
+	else if ( gPcmStatus & PCMSTATUS_STREAM_PLAYING( whichStream ) )
+	{
+		return ( PCM_STATUS_PLAYING );
+	}
+	else if ( gpStreamInfo[whichStream]->m_playState == PLAY_STATE_STOP)
+	{
+		return ( PCM_STATUS_STOPPED );
+	}
+	else if ( gpStreamInfo[whichStream]->m_playState == PLAY_STATE_PLAYING)		// EE thinks it is playing, but not IOP
+	{
+		return ( PCM_STATUS_END );
+	}
+	else
+	{
+		return ( PCM_STATUS_FREE );
+	}
+	Dbg_MsgAssert( 0,( "Sell your stock in GCC." ));
+	return ( 0 );
+}
+
+void PCMAudio_Pause( bool pause, int ch )
+{
+	if ( ch == MUSIC_CHANNEL )
+	{
+		EzPcm( EzADPCM_PAUSEMUSIC, pause );
+	}
+	else
+	{
+		EzPcm( EzADPCM_PAUSESTREAMS, pause );
+	}
+} // end of PCMAudio_Pause( )
+
+void PCMAudio_PauseStream( bool pause, int whichStream )
+{
+	EzPcm( EzADPCM_PAUSESTREAM | whichStream, pause );
+}
+
+unsigned int GetCDLocation( const char *pWadName )
+{
+	
+
+	InitCDPlease( );
+
+	File::StopStreaming( );
+	sceCdSync( 0 );
+
+	char tempFilename[ 255 ];
+	sprintf( tempFilename, "\\%s%s.WAD;1", Config::GetDirectory(), pWadName );
+	int i;
+	for ( i = strlen( tempFilename ) - 1; i > 0; i-- )
+	{
+		if ( tempFilename[ i ] >= 'a' && tempFilename[ i ] <= 'z' )
+		{
+			tempFilename[ i ] += 'A' - 'a';
+		}
+	}
+	sceCdlFILE fileInfo;
+	int retVal = 0;
+	if ( !( retVal = sceCdSearchFile( &fileInfo, tempFilename ) ) )
+	{
+		printf( "Wad %s not found -- Err %d\n", tempFilename, retVal );
+		return ( 0 );
+	}
+	return ( fileInfo.lsn );
+}
+
+bool PCMAudio_TrackExists( const char *pTrackName, int ch )
+{
+	if ( !gWadInfo[ch].mp_hed )
+	{
+		return ( false );
+	}
+	if ( !FindFileInHed( pTrackName, gWadInfo[ch].mp_hed ) )
+	{
+		Dbg_Message( "Audio file %s not found in header file.", pTrackName );
+		return ( false );
+	}
+	return ( true );
+	
+}
+
+bool PCMAudio_LoadMusicHeader( const char *nameOfFile )
+{
+	bool no_wad = false;
+
+	if (!(Config::CD() || TEST_MOTHERFUCKING_CD))
+	{
+		no_wad = true;
+	}	
+
+	return gWadInfo[MUSIC_CHANNEL].LoadHeader(nameOfFile, no_wad);
+}
+
+bool PCMAudio_LoadStreamHeader( const char *nameOfFile )
+{
+	bool no_wad = false;
+
+	if (!(Config::CD() || TEST_MOTHERFUCKING_CD))
+	{
+		int testStreams = 0;
+		testStreams = Script::GetInteger( 0x62df9442 ); // checksum 'testStreamsFromHost'
+		if ( !testStreams )
+		{
+			return ( false );
+		}
+
+		no_wad = true;
+	}	
+	
+	return gWadInfo[EXTRA_CHANNEL].LoadHeader(nameOfFile, no_wad);
+}
+
+uint32 PCMAudio_FindNameFromChecksum( uint32 checksum, int ch )
+{
+	
+	File::SHed *pHed;
+
+	pHed = gWadInfo[ch].mp_hed;
+
+	if ( !pHed )
+	{
+		return ( NULL );
+	}
+
+	File::SHedFile* pHedFile = File::FindFileInHedUsingChecksum( checksum, pHed );
+	if( pHedFile )
+	{
+		return pHedFile->Checksum;
+	}
+	return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+// SWadInfo
+//
+bool			SWadInfo::LoadHeader(const char *nameOfFile, bool no_wad, bool assertOnError)
+{
+	if ( mp_hed )
+	{
+		Mem::Free( mp_hed );
+		mp_hed = NULL;
+	}
+
+	mp_hed = File::LoadHed( nameOfFile, TEST_MOTHERFUCKING_CD, no_wad );
+	if ( !mp_hed )
+	{
+		Dbg_Message( "Couldn't find audio header %s", nameOfFile );
+		Dbg_MsgAssert( !assertOnError, ( "Couldn't find audio header %s", nameOfFile ));
+		return false;
+	}
+
+	if (Config::CD() || TEST_MOTHERFUCKING_CD)
+	{
+		Dbg_MsgAssert(!no_wad, ("Can't use a no-wad version of a hed file on the CD"));
+
+		m_lsn = GetCDLocation( nameOfFile );
+		if ( !m_lsn )
+		{
+			Mem::Free( mp_hed );
+			mp_hed = NULL;
+			Dbg_Message( "Couldn't find audio wad %s", nameOfFile );
+			Dbg_MsgAssert( !assertOnError,( "Couldn't find audio wad %s", nameOfFile ));
+			return false;
+		}
+	}
+	else
+	{
+		 sprintf( m_fileName, "host:%s.WAD", nameOfFile );
+	}	 
+
+	return (bool) mp_hed;
+}
+
+} // namespace PCM
diff --git a/Code/Gel/Music/Ngps/p_music.h b/Code/Gel/Music/Ngps/p_music.h
new file mode 100644
index 0000000..67c9d45
--- /dev/null
+++ b/Code/Gel/Music/Ngps/p_music.h
@@ -0,0 +1,189 @@
+// Audio streaming function prototypes:
+// mjd jan 2001
+
+#ifndef __P_MUSIC_H__
+#define __P_MUSIC_H__
+
+// Forward declarations
+namespace File
+{
+	class CAsyncFileHandle;
+	struct SHed;
+	struct SHedFile;
+}
+
+namespace Pcm
+{
+
+// Stream types
+enum EFileStreamType 
+{
+	FILE_STREAM_MUSIC = 0,
+	FILE_STREAM_STREAM0,
+	FILE_STREAM_STREAM1,
+	FILE_STREAM_STREAM2,
+	NUM_FILE_STREAMS
+};
+
+// The load state of a stream
+enum ELoadState
+{
+	LOAD_STATE_IDLE = 0,
+	LOAD_STATE_LOADING0,
+	LOAD_STATE_LOADING1,
+	LOAD_STATE_DONE,
+};
+
+// The play state of a stream
+enum EPlayState
+{
+	PLAY_STATE_STOP = 0,
+	PLAY_STATE_PRELOAD_EE,
+	PLAY_STATE_PRELOAD_IOP,
+	PLAY_STATE_START,
+	PLAY_STATE_PLAYING,
+	PLAY_STATE_PAUSED,
+	PLAY_STATE_DONE,
+};
+
+// allows one channel for music, another for audio:
+enum{
+	EXTRA_CHANNEL,
+	MUSIC_CHANNEL,
+};
+
+
+// Holds the info for each stream
+class CFileStreamInfo
+{
+public:
+
+	bool					IsMusicStream() const;
+	int						GetAudioStreamIndex() const;		// returns a negative number if it isn't a audio stream
+
+	bool					StartStream(File::SHedFile *pHedFile, bool play);
+	bool					PlayStream();
+	bool					StopStream();
+	int						Update();
+
+protected:
+	void					start_audio();
+	void					load_chunk(int buffer);
+
+	// For preloading
+	void					preload_spu_buffers();
+	void					start_preloaded_audio();
+
+	ELoadState				check_for_load_requests();
+	bool					is_chunk_load_done(int & errno);	// errno will be non-zero if we were busy AND there was an error
+	bool					file_finished();					// returns true and closes fine if we read the whole file
+
+// This section should eventually become "protected"
+public:
+	EFileStreamType			m_streamType;			// Either music or audio stream
+	ELoadState				m_loadState;
+	EPlayState				m_playState;
+	File::CAsyncFileHandle *mp_fileHandle;
+	int						m_fileSize;
+	int						m_offset;
+	int						m_offsetInWad;
+	int						m_pitch;
+	uint32					m_volume;
+};
+
+// All the Wad information for music and audio
+struct SWadInfo
+{
+	bool					LoadHeader(const char *nameOfFile, bool no_wad = false, bool assertOnError = false);
+
+	char  					m_fileName[ 256 ];
+	int						m_lsn;
+	File::SHed *			mp_hed;
+};
+
+
+
+// Onetime call once upon loading the game...
+void PCMAudio_Init( void );
+
+// Combine the IOP sent status into gPcmStatus
+void PCMAudio_UpdateStatus();
+
+// Call every frame to make sure music and stream buffers are loaded and current status is checked each frame...
+// Returns true if there is a CD error, false otherwise...
+int PCMAudio_Update( void );
+
+// Load a track and start it playing...
+// You wanna loop a track?  Wait until this track is done, and PLAY IT AGAIN SAM!
+bool PCMAudio_PlayMusicTrack( const char *filename );
+bool PCMAudio_PlayMusicStream( uint32 checksum );
+bool PCMAudio_PlayStream( uint32 checksum, int whichStream, float volumeL, float volumeR, float pitch );
+
+// keep song loaded, stop playing it (or continue playing paused song)
+void PCMAudio_Pause( bool pause = true, int ch = MUSIC_CHANNEL );
+
+void PCMAudio_PauseStream( bool pause, int whichStream );
+
+void PCMAudio_StopMusic( bool waitPlease );
+void PCMAudio_StopStream( int whichStream, bool waitPlease = true );
+void PCMAudio_StopStreams( void );
+
+// Preload streams.  By preloading, you can guarantee they will start at a certain time.
+bool PCMAudio_PreLoadStream( uint32 checksum, int whichStream );
+bool PCMAudio_PreLoadStreamDone( int whichStream );
+bool PCMAudio_StartPreLoadedStream( int whichStream, float volumeL, float volumeR, float pitch );
+
+// Preload Music Streams.  By preloading, you can guarantee they will start at a certain time.
+bool PCMAudio_PreLoadMusicStream( uint32 checksum );
+bool PCMAudio_PreLoadMusicStreamDone( void );
+bool PCMAudio_StartPreLoadedMusicStream( void );
+
+// set the music volume ( 0 to 100 )
+bool	PCMAudio_SetMusicVolume( float volume );
+bool	PCMAudio_SetStreamVolume( float volumeL, float volumeR, int whichStream );
+bool	PCMAudio_SetStreamPitch( float pitch, int whichStream );
+//int PCMAudio_SetStreamVolumeAndPitch( float volumeL, float volumeR, float pitch, int whichStream );
+
+void PCMAudio_SetStreamGlobalVolume( unsigned int volume );
+
+enum{
+	PCM_STATUS_FREE 			= ( 1 << 0 ),
+	PCM_STATUS_PLAYING			= ( 1 << 1 ),
+	PCM_STATUS_LOADING 			= ( 1 << 2 ),
+	PCM_STATUS_END 				= ( 1 << 3 ),		// Done playing on IOP, but not completely cleaned up on the IOP
+	PCM_STATUS_STOPPED 			= ( 1 << 4 ),
+};
+
+// Return one of the PCM_STATUS values from above...
+int PCMAudio_GetMusicStatus( );
+int PCMAudio_GetStreamStatus( int whichStream );
+
+// preload any CD location info (to avoid a hitch in framerate on PS2):
+unsigned int PCMAudio_GetCDLocation( const char *pTrackName );
+
+bool PCMAudio_TrackExists( const char *pTrackName, int ch );
+bool PCMAudio_LoadMusicHeader( const char *nameOfFile );
+bool PCMAudio_LoadStreamHeader( const char *nameOfFile );
+
+uint32 PCMAudio_FindNameFromChecksum( uint32 checksum, int ch );
+
+// borrow this memory for the movies and shit...
+int PCMAudio_GetIopMemory( void );
+		 
+//////////////////////////////////////////////////////////////////////////////
+// Inlines
+//
+
+inline bool		CFileStreamInfo::IsMusicStream() const
+{
+	return m_streamType == FILE_STREAM_MUSIC;
+}
+
+inline int		CFileStreamInfo::GetAudioStreamIndex() const
+{
+	return (int) (m_streamType - FILE_STREAM_STREAM0);
+}
+
+} // namespace PCM
+
+#endif // __P_MUSIC_H__
diff --git a/Code/Gel/Music/Xbox/p_adpcmfilestream.cpp b/Code/Gel/Music/Xbox/p_adpcmfilestream.cpp
new file mode 100644
index 0000000..c95af15
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_adpcmfilestream.cpp
@@ -0,0 +1,1134 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:																	**
+**																			**
+**	File name:		p_adpcmfilestream.cpp									**
+**																			**
+**	Created:		01/27/03	-	dc										**
+**																			**
+**	Description:	Xbox specific .pcm streaming code						**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include "p_music.h"
+#include "p_adpcmfilestream.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace Pcm
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// Define the maximum amount of packets we will ever submit to the ADPCM renderer
+#define ADPCMSTRM_PACKET_COUNT					8
+
+// Define the ADPCM renderer packet size. See the comment block above for an explanation.
+// This value is hard-coded assuming an ADPCM frame of 36 samples and 16 bit stereo (128 frames per packet)
+#define ADPCMSTRM_16BIT_MONO_PACKET_BYTES		( 1 * 2 * 36 * 128 ) 
+#define ADPCMSTRM_16BIT_STEREO_PACKET_BYTES		( 2 * 2 * 36 * 128 ) 
+
+// Read size is 16k (most efficient size for DVD reads).
+#define BYTES_PER_READ				16384
+
+	
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+//-----------------------------------------------------------------------------
+// Name: CADPCMFileStream()
+// Desc: Object constructor.
+//-----------------------------------------------------------------------------
+CADPCMFileStream::CADPCMFileStream( bool use_3d )
+{
+    m_pSourceFilter		= NULL;
+    m_pRenderFilter		= NULL;
+    m_pvSourceBuffer	= NULL;
+    m_pFileBuffer		= NULL;
+	m_hFile				= INVALID_HANDLE_VALUE;
+	m_hThread			= NULL;
+	m_bUse3D			= use_3d;
+	m_bOkayToPlay		= true;
+
+    for( DWORD i = 0; i < ADPCMSTRM_PACKET_COUNT; i++ )
+	{
+        m_adwPacketStatus[i] = XMEDIAPACKET_STATUS_SUCCESS;
+	}
+
+    m_dwStartingDataOffset	= 0;
+	m_Completed				= false;
+	m_Paused				= false;
+
+	// Grab a pointer to the next overlapped structure.
+	m_pOverlapped			= PCMAudio_GetNextOverlapped();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ~CADPCMFileStream()
+// Desc: Object destructor.
+//-----------------------------------------------------------------------------
+CADPCMFileStream::~CADPCMFileStream()
+{
+	if( m_hThread )
+	{
+		CloseHandle( m_hThread );
+	}
+	
+	// If the file i/o is still active, we need to remove the event and close the file.
+	if(( m_hFile != INVALID_HANDLE_VALUE ) && !m_bUseWAD )
+	{
+		CloseHandle( m_hFile );
+	}
+
+    if( m_pSourceFilter )
+	{
+		m_pSourceFilter->Release();
+	}
+
+    if( m_pRenderFilter )
+	{
+		m_pRenderFilter->Release();
+	}
+
+	if( m_pvSourceBuffer )
+	{
+		delete[] m_pvSourceBuffer;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: AsyncRead()
+// Desc: Called while the async read is in progress. If the read is still
+//       underway, simply returns. If the read has completed, sets up the
+//       next read. If the file has been completely read, closes the file.
+//-----------------------------------------------------------------------------
+void CADPCMFileStream::AsyncRead( void )
+{
+	Dbg_Assert( m_hFile != INVALID_HANDLE_VALUE );
+
+    // If paused, do nothing.
+	if( m_Paused )
+	{
+        return;
+    }
+
+    // See if the previous read is complete.
+	DWORD	dwBytesTransferred;
+	BOOL	bIsReadDone = GetOverlappedResult( m_hFile, m_pOverlapped, &dwBytesTransferred, FALSE );
+	DWORD	dwLastError = GetLastError();
+
+    // If the read isn't complete, keep going.
+	if( !bIsReadDone )
+	{
+		Dbg_Assert( dwLastError == ERROR_IO_INCOMPLETE );
+		if( dwLastError != ERROR_IO_INCOMPLETE )
+		{
+			m_AwaitingDeletion = true;
+		}
+		return;
+    }
+
+	// If we get here, the read is complete.
+    m_pOverlapped->Offset	+= dwBytesTransferred;
+	m_FileBytesRead			+= dwBytesTransferred;
+
+	++m_SuccessiveReads;
+
+	if( dwBytesTransferred < BYTES_PER_READ )
+	{ 
+		// We've reached the end of the file during the call to ReadFile.
+
+        // Close the file (if not using the global WAD file).
+		if( !m_bUseWAD )
+		{
+			BOOL bSuccess = CloseHandle( m_hFile );
+	        Dbg_Assert( bSuccess );
+		}
+
+        m_hFile = INVALID_HANDLE_VALUE;
+
+        // All done
+		m_ReadComplete = true;
+    }
+    else
+    {
+		if( m_bUseWAD && ( m_FileBytesRead >= (int)m_dwWADLength ))
+		{
+			m_hFile = INVALID_HANDLE_VALUE;
+
+			// All done
+			m_ReadComplete = true;
+			return;
+		}
+		
+		// We still have more data to read. Start another asynchronous read from the file.
+		BOOL bComplete	= ReadFile( m_hFile, (BYTE*)m_pFileBuffer + ( m_FileBytesRead % PCMAudio_GetFilestreamBufferSize()), BYTES_PER_READ, NULL, m_pOverlapped );
+		dwLastError		= GetLastError();
+
+		// Deal with hitting EOF (for files that are some exact multiple of BYTES_PER_READ bytes).
+		if( bComplete || ( !bComplete && ( dwLastError == ERROR_HANDLE_EOF )))
+		{
+			// Close the file
+			if( !m_bUseWAD )
+			{
+				BOOL bSuccess = CloseHandle( m_hFile );
+				Dbg_Assert( bSuccess );
+			}
+			m_hFile = INVALID_HANDLE_VALUE;
+
+			// All done
+			m_ReadComplete = true;
+		}
+		else
+		{
+			Dbg_MsgAssert( bComplete || ( !bComplete && ( dwLastError == ERROR_IO_PENDING )), ( "ReadFile error: %x\n", dwLastError ));
+			if( !bComplete && ( dwLastError != ERROR_IO_PENDING ))
+			{
+				// There was a problem, so shut this stream down.
+				m_AwaitingDeletion = true;
+			}
+		}
+    }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Initialize()
+// Desc: Initializes the wave file streaming subsystem.
+//-----------------------------------------------------------------------------
+HRESULT CADPCMFileStream::Initialize( HANDLE h_file, unsigned int offset, unsigned int length, void* fileBuffer )
+{
+    m_dwPercentCompleted = 0;
+    
+	// At this stage we don't want to create the decoder or the stream. We do want to just allocate the read
+	// buffer, and start pulling in the data. Once sufficient data has been grabbed, we can analyze the header
+	// and create the required objects for playback.
+	m_hFile			= h_file;
+	m_bUseWAD		= true;
+	m_dwWADOffset	= offset;	
+	m_dwWADLength	= length;
+	m_pFileBuffer = fileBuffer;
+
+	Dbg_Assert( DWORD( m_pFileBuffer ) % sizeof( DWORD ) == 0 );
+
+	return PostInitialize();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: PostInitialize()
+// Desc: Initialisation stuff following the file creation
+//-----------------------------------------------------------------------------
+HRESULT CADPCMFileStream::PostInitialize( void )
+{
+	// Start the asynchronous read from the start of the file.
+	m_FirstRead				= true;
+	m_ReadComplete			= false;
+	m_FileBytesRead			= 0;
+	m_FileBytesProcessed	= 0;
+	m_SuccessiveReads		= 0;
+
+	if( m_bUseWAD )
+	{
+		m_pOverlapped->Offset	= m_dwWADOffset;
+	}
+	else
+	{
+		m_pOverlapped->Offset	= 0;
+	}
+	m_pOverlapped->OffsetHigh	= 0;
+
+	BOOL bComplete = ReadFile( m_hFile, m_pFileBuffer, BYTES_PER_READ, NULL, m_pOverlapped );
+	if( !bComplete )
+	{
+		DWORD dwLastError = GetLastError();
+		Dbg_Assert( dwLastError == ERROR_IO_PENDING );
+		if( dwLastError != ERROR_IO_PENDING )
+		{
+			return HRESULT_FROM_WIN32( dwLastError );
+		}
+	}
+	else
+	{
+		m_ReadComplete = true;
+	}
+
+	// That's it for now.
+    return S_OK;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: InitializeFormatBlock()
+// Desc: 
+//-----------------------------------------------------------------------------
+HRESULT CADPCMFileStream::InitializeFormatBlock( uint32 *p_header_data )
+{
+	// Ensure the format block is where we expect it to be.
+	if( p_header_data[3] == 0x20746D66UL )
+	{
+		// Read format and number of channels.
+		m_wfxExtendedSourceFormat.m_wfxSourceFormat.wFormatTag		= (uint16)( p_header_data[5] & 0xFFFFUL );
+		m_wfxExtendedSourceFormat.m_wfxSourceFormat.nChannels		= (uint16)( p_header_data[5] >> 16 );
+
+		// Make sure this is Xbox ADPCM.
+		if( m_wfxExtendedSourceFormat.m_wfxSourceFormat.wFormatTag != WAVE_FORMAT_XBOX_ADPCM )
+		{
+			return E_FAIL;
+		}
+
+		// Read samples per second.
+		m_wfxExtendedSourceFormat.m_wfxSourceFormat.nSamplesPerSec	= p_header_data[6];
+
+		// Read average bytes per second.
+		m_wfxExtendedSourceFormat.m_wfxSourceFormat.nAvgBytesPerSec	= p_header_data[7];
+
+		// Read block alignment and bits per sample.
+		m_wfxExtendedSourceFormat.m_wfxSourceFormat.nBlockAlign		= (uint16)( p_header_data[8] & 0xFFFFUL );
+		m_wfxExtendedSourceFormat.m_wfxSourceFormat.wBitsPerSample	= (uint16)( p_header_data[8] >> 16 );
+
+		// Extra information.
+		m_wfxExtendedSourceFormat.m_wfxSourceFormat.cbSize			= (uint16)( p_header_data[9] & 0xFFFFUL );
+		m_wfxExtendedSourceFormat.m_extendedInfo					= (uint16)( p_header_data[9] >> 16 );
+
+		// We have now processed the first 48 bytes of data.
+		m_FileBytesProcessed = 48;
+
+		// Now that we know the format, we can set the packet size.
+		if( m_wfxExtendedSourceFormat.m_wfxSourceFormat.nChannels == 1 )
+			m_PacketBytes = ADPCMSTRM_16BIT_MONO_PACKET_BYTES;
+		else
+			m_PacketBytes = ADPCMSTRM_16BIT_STEREO_PACKET_BYTES;
+
+		return S_OK;
+	}
+	return E_FAIL;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: CreateSourceBuffer()
+// Desc: 
+//-----------------------------------------------------------------------------
+bool CADPCMFileStream::CreateSourceBuffer( void )
+{
+	// Allocate data buffers. The source buffer holds the CPU decompressed packets ready to submit to the stream.
+	// The size of the buffer will depend on the format of the stream - stereo requires double the packet size of mono.
+	// Explicitly allocate from the bottom up heap.
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().BottomUpHeap());
+	m_pvSourceBuffer = new BYTE[m_PacketBytes * ADPCMSTRM_PACKET_COUNT];
+	Mem::Manager::sHandle().PopContext();
+
+	return ( m_pvSourceBuffer != NULL );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: PreLoadDone()
+// Desc: 
+//-----------------------------------------------------------------------------
+bool CADPCMFileStream::PreLoadDone( void )
+{
+	if( m_DecoderCreation == 1 )
+	{
+		if( m_ReadComplete || ( m_FileBytesRead >= ( m_FileBytesProcessed + m_PacketBytes )))
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Process()
+// Desc: Performs any work necessary to keep the stream playing.
+//-----------------------------------------------------------------------------
+HRESULT CADPCMFileStream::Process( void )
+{
+    HRESULT			hr;
+	DSSTREAMDESC	dssd;
+    DWORD			dwPacketIndex;
+    
+	// Do nothing if waiting to die.
+	if( m_AwaitingDeletion )
+	{
+		return S_OK;
+	}
+
+	// Do we need to kick off another read of data?
+	// Don't read anymore if we have read ahead to the point where we are within 32k of free read buffer space.
+	if( !m_ReadComplete )
+	{
+		if( m_FileBytesRead <= ( m_FileBytesProcessed + ((int)PCMAudio_GetFilestreamBufferSize() - 32768 )))
+		{
+			AsyncRead();
+		}
+	}
+
+	// Has the first block of raw data been read? If so we need to instantiate the playback objects and data buffers.
+	if( m_FirstRead && ( m_FileBytesRead > 1024 ))
+	{
+		if( m_pSourceFilter == NULL )
+		{
+			// Create the thread which will create the in-memory decoder. 
+			m_DecoderCreation = 0;
+
+			// For WMA format, here is where we create the decoder. However, since there is no CPU-side decompression required
+			// for ADPCM format, there is no requirement for a decoder.
+			m_DecoderCreation	= 1;
+		}
+
+		if( m_DecoderCreation == 0 )
+		{
+			// Still waiting to create decoder.
+			return S_OK;
+		}
+		else if( m_DecoderCreation == 2 )
+		{
+			// Failed to create decoder, just mark for deletion.
+			m_AwaitingDeletion = true;
+			return S_OK;
+		}
+		else if( m_DecoderCreation == 1 )
+		{
+			// Managed to create decoder.
+			m_FirstRead = false;
+
+			// Set up the format information.
+			if( InitializeFormatBlock((uint32*)m_pFileBuffer ) == E_FAIL )
+			{
+				// Failed to decode format information, just mark for deletion.
+				m_AwaitingDeletion = true;
+				return S_OK;
+			}
+
+			// Create the render (DirectSoundStream) filter.
+			DSMIXBINS			dsmixbins;
+			DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+			ZeroMemory( &dssd, sizeof( dssd ));
+
+			dssd.dwFlags					= 0;
+			dssd.dwMaxAttachedPackets		= ADPCMSTRM_PACKET_COUNT;
+			dssd.lpwfxFormat				= &m_wfxExtendedSourceFormat.m_wfxSourceFormat;
+			dssd.lpMixBins					= &dsmixbins;
+
+			if( m_bUse3D )
+			{
+				// This is only designed to be fed a mono signal.
+				Dbg_Assert( m_wfxExtendedSourceFormat.m_wfxSourceFormat.nChannels == 1 );
+
+				dsmixbins.dwMixBinCount			= 6;
+				dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+				dsmbvp[0].dwMixBin				= DSMIXBIN_3D_FRONT_LEFT;
+				dsmbvp[0].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[1].dwMixBin				= DSMIXBIN_3D_FRONT_RIGHT;
+				dsmbvp[1].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[2].dwMixBin				= DSMIXBIN_3D_BACK_LEFT;
+				dsmbvp[2].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[3].dwMixBin				= DSMIXBIN_3D_BACK_RIGHT;
+				dsmbvp[3].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[4].dwMixBin				= DSMIXBIN_FRONT_CENTER;
+				dsmbvp[4].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[5].dwMixBin				= DSMIXBIN_I3DL2;
+				dsmbvp[5].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+
+				m_Mixbins						= ( 1 << DSMIXBIN_3D_FRONT_LEFT ) |
+												  ( 1 << DSMIXBIN_3D_FRONT_RIGHT ) |
+												  ( 1 << DSMIXBIN_3D_BACK_LEFT ) |
+												  ( 1 << DSMIXBIN_3D_BACK_RIGHT ) |
+												  ( 1 << DSMIXBIN_FRONT_CENTER ) |
+												  ( 1 << DSMIXBIN_I3DL2 );
+				m_NumMixbins					= dsmixbins.dwMixBinCount;
+			}
+			else
+			{
+				// If we are playing a music track, and if proper 5.1 output is selected, we want to feed the music to the left and
+				// right back speakers with a slight echo via mixbins 5 and 6.
+				// This is currently disabled.
+				if( false && ( XGetAudioFlags() & XC_AUDIO_FLAGS_ENABLE_AC3 ))
+				{
+					// This is only designed to be fed a stereo signal.
+					Dbg_Assert( m_wfxExtendedSourceFormat.m_wfxSourceFormat.nChannels == 2 );
+
+					dsmixbins.dwMixBinCount			= 4;
+					dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+					dsmbvp[0].dwMixBin				= DSMIXBIN_FRONT_LEFT;
+					dsmbvp[0].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[1].dwMixBin				= DSMIXBIN_FRONT_RIGHT;
+					dsmbvp[1].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[2].dwMixBin				= DSMIXBIN_FXSEND_5;
+					dsmbvp[2].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[3].dwMixBin				= DSMIXBIN_FXSEND_6;
+					dsmbvp[3].lVolume				= DSBVOLUME_MIN;
+
+					m_Mixbins						= ( 1 << DSMIXBIN_FRONT_LEFT ) |
+													  ( 1 << DSMIXBIN_FRONT_RIGHT ) |
+													  ( 1 << DSMIXBIN_FXSEND_5 ) |
+													  ( 1 << DSMIXBIN_FXSEND_6 );
+					m_NumMixbins					= dsmixbins.dwMixBinCount;
+				}
+				else
+				{
+					// This is only designed to be fed a stereo signal.
+					Dbg_Assert( m_wfxExtendedSourceFormat.m_wfxSourceFormat.nChannels == 2 );
+
+					dsmixbins.dwMixBinCount			= 4;
+					dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+					dsmbvp[0].dwMixBin				= DSMIXBIN_FRONT_LEFT;
+					dsmbvp[0].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[1].dwMixBin				= DSMIXBIN_FRONT_RIGHT;
+					dsmbvp[1].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[2].dwMixBin				= DSMIXBIN_BACK_LEFT;
+					dsmbvp[2].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[3].dwMixBin				= DSMIXBIN_BACK_RIGHT;
+					dsmbvp[3].lVolume				= DSBVOLUME_MIN;
+
+					m_Mixbins						= ( 1 << DSMIXBIN_FRONT_LEFT ) |
+													  ( 1 << DSMIXBIN_FRONT_RIGHT ) |
+													  ( 1 << DSMIXBIN_BACK_LEFT ) |
+													  ( 1 << DSMIXBIN_BACK_RIGHT );
+					m_NumMixbins					= dsmixbins.dwMixBinCount;
+				}
+			}
+
+			hr = DirectSoundCreateStream( &dssd, &m_pRenderFilter );
+			if( FAILED( hr ))
+			{
+				Dbg_Assert( 0 );
+				m_pRenderFilter		= NULL;
+				m_AwaitingDeletion	= true;
+				return S_OK;
+			}
+
+			// Set deferred volume if present.
+			SetDeferredVolume();
+
+			// Handle deferred pause.
+			if( m_Paused )
+			{
+				m_pRenderFilter->Pause( 1 );
+			}
+		}
+	}
+
+	// We only want to do processing when there is sufficient data available.
+	if( m_ReadComplete || ( m_FileBytesRead >= ( m_FileBytesProcessed + m_PacketBytes )))
+	{
+		if( !m_Completed && m_pRenderFilter )
+		{
+			// Find a free packet. If there's none free, we don't have anything to do.
+			if( FindFreePacket( &dwPacketIndex ))
+			{
+				// Read from the source filter.
+				if( m_bOkayToPlay )
+				{
+					hr = ProcessSource( dwPacketIndex );
+					if( FAILED( hr ))
+					{
+						return hr;
+					}
+
+					if( !m_Paused )
+					{
+						// Send the data to the renderer
+						hr = ProcessRenderer( dwPacketIndex );
+						if( FAILED( hr ))
+						{
+							 return hr;
+						}
+					}
+				}
+			}
+		}
+	}
+    return S_OK;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: FindFreePacket()
+// Desc: Finds a render packet available for processing.
+//-----------------------------------------------------------------------------
+BOOL CADPCMFileStream::FindFreePacket( DWORD* pdwPacketIndex )
+{
+    for( DWORD dwPacketIndex = 0; dwPacketIndex < ADPCMSTRM_PACKET_COUNT; ++dwPacketIndex )
+    {
+        if( XMEDIAPACKET_STATUS_PENDING != m_adwPacketStatus[dwPacketIndex] )
+        {
+            if( pdwPacketIndex )
+			{
+                (*pdwPacketIndex) = dwPacketIndex;
+			}
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ProcessSource()
+// Desc: Reads data from the source filter.
+//-----------------------------------------------------------------------------
+HRESULT CADPCMFileStream::ProcessSource( DWORD dwPacketIndex )
+{
+	if( m_pvSourceBuffer == NULL )
+	{
+		if( CreateSourceBuffer() == false )
+		{
+			// Failed to create the source buffer, mark for deletion.
+			m_AwaitingDeletion = true;
+			return E_FAIL;
+		}
+	}
+    
+	// We just want to copy a full packet's worth of data from the file buffer directly into the source buffer...
+	uint8*	p_destination		= (BYTE*)m_pvSourceBuffer + ( dwPacketIndex * m_PacketBytes );
+
+	// However we don't want to overrun the file buffer when copying.
+	uint32	file_buffer_offset	= m_FileBytesProcessed % PCMAudio_GetFilestreamBufferSize();
+	if(( PCMAudio_GetFilestreamBufferSize() - file_buffer_offset ) < (uint32)m_PacketBytes )
+	{
+		// Copying the data in one chunk will take us beyond the edge of the file buffer.
+		// So we need to do the copy in two chunks.
+		uint8*	p_source			= (BYTE*)m_pFileBuffer + file_buffer_offset;
+		uint32	first_chunk_bytes	= PCMAudio_GetFilestreamBufferSize() - file_buffer_offset;
+		CopyMemory( p_destination, p_source, first_chunk_bytes );
+
+		// Wrap file buffer back round to start.
+		p_source					= (BYTE*)m_pFileBuffer + 0;
+		p_destination				= p_destination + first_chunk_bytes;
+		CopyMemory( p_destination, p_source, m_PacketBytes - first_chunk_bytes );
+	}
+	else
+	{
+		// Copying the data in one chunk is fine.
+		uint8* p_source = (BYTE*)m_pFileBuffer + file_buffer_offset;
+		CopyMemory( p_destination, p_source, m_PacketBytes );
+	}
+
+	// Now these bytes have been processed.
+	m_FileBytesProcessed += m_PacketBytes;
+
+	// If we've caught up with the number of bytes read, it's because we've finished processing.
+    if( m_FileBytesProcessed >= m_FileBytesRead )
+	{
+		// Set completion flag.
+		m_Completed = true;
+
+		// Zero remaining part of packet.
+		uint32 bytes_to_zero = m_FileBytesProcessed - m_FileBytesRead;
+		if( bytes_to_zero > 0 )
+		{
+			p_destination = (BYTE*)m_pvSourceBuffer + ( dwPacketIndex * m_PacketBytes ) + m_PacketBytes - bytes_to_zero;
+			ZeroMemory( p_destination, bytes_to_zero );
+		}
+	}
+    return S_OK;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ProcessRenderer()
+// Desc: Sends data to the renderer.
+//-----------------------------------------------------------------------------
+HRESULT CADPCMFileStream::ProcessRenderer( DWORD dwPacketIndex )
+{
+    XMEDIAPACKET xmp;
+    HRESULT      hr;
+
+	Dbg_Assert( m_pvSourceBuffer != NULL );
+
+	// There's a full packet's worth of data ready for us to send to the renderer.  We want to track the status
+	// of this packet since the render filter is asychronous and we need to know when the packet is completed.
+    ZeroMemory( &xmp, sizeof( xmp ));
+    xmp.pvBuffer  = (BYTE*)m_pvSourceBuffer + ( dwPacketIndex * m_PacketBytes );
+    xmp.dwMaxSize = m_PacketBytes;
+    xmp.pdwStatus = &m_adwPacketStatus[dwPacketIndex];
+
+	if( m_Completed )
+	{
+		// Store index of last packet, since we will need to test the status of this for proper completion test.
+		m_LastPacket = dwPacketIndex;
+	}
+
+    hr = m_pRenderFilter->Process( &xmp, NULL );
+
+	if( m_Completed )
+	{
+		// Tell the renderer not to expect any more data.
+		m_pRenderFilter->Discontinuity();
+	}
+	
+	if( FAILED( hr ))
+	{
+		return hr;
+	}
+	
+	return S_OK;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Pause
+// Desc: Pauses and resumes stream playback
+//-----------------------------------------------------------------------------
+void CADPCMFileStream::Pause( DWORD dwPause )
+{
+	m_Paused = ( dwPause > 0 );
+
+	// Possible that the render filter hasn't been created yet.
+	if( m_pRenderFilter )
+	{
+		m_pRenderFilter->Pause(( dwPause > 0 ) ? DSSTREAMPAUSE_PAUSE : DSSTREAMPAUSE_RESUME );
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CADPCMFileStream::SetVolume( float volume )
+{
+	if( m_pRenderFilter )
+	{
+		int i_volume		= DSBVOLUME_EFFECTIVE_MIN;
+		int i_volume_rear	= DSBVOLUME_EFFECTIVE_MIN;
+		if( volume > 0.0f )
+		{
+			// Figure base volume.
+			float attenuation	= 20.0f * log10f( volume * 0.01f );
+			i_volume			= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+			if( i_volume < DSBVOLUME_EFFECTIVE_MIN )
+				i_volume = DSBVOLUME_EFFECTIVE_MIN;
+			else if( i_volume > DSBVOLUME_MAX )
+				i_volume = DSBVOLUME_MAX;
+
+			// Also figure half volume, in case we are routing to the back speakers.
+			attenuation			= 20.0f * log10f( volume * 0.5f * 0.01f );
+			i_volume_rear		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+			if( i_volume_rear < DSBVOLUME_EFFECTIVE_MIN )
+				i_volume_rear = DSBVOLUME_EFFECTIVE_MIN;
+			else if( i_volume_rear > DSBVOLUME_MAX )
+				i_volume_rear = DSBVOLUME_MAX;
+		}
+		
+		// Set individual mixbins for panning.
+		DSMIXBINS			dsmixbins;
+		DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+
+		dsmixbins.dwMixBinCount			= 0;
+		dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+
+		if( i_volume > DSBVOLUME_EFFECTIVE_MIN )
+		{
+			// Set the volume up depending on how the initial mixbins were set up.
+			int mbbf = 0;
+			for( uint32 mb = 0; mb < m_NumMixbins; ++mb )
+			{
+				while(( m_Mixbins & ( 1 << mbbf )) == 0 )
+				{
+					++mbbf;
+					Dbg_Assert( mbbf < 32 );
+				}
+				dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= mbbf;
+
+				// For rear speakers (or mixbins that route to the rear), use half volume.
+				if(( mbbf == DSMIXBIN_FXSEND_5 ) || ( mbbf == DSMIXBIN_FXSEND_6 ) || ( mbbf == DSMIXBIN_BACK_LEFT ) || ( mbbf == DSMIXBIN_BACK_RIGHT ))
+				{
+					dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volume_rear;
+				}
+				else
+				{
+					dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volume;
+				}
+				++dsmixbins.dwMixBinCount;
+				++mbbf;
+			}
+		}
+
+		// Set all speaker volumes.
+		m_pRenderFilter->SetMixBinVolumes( &dsmixbins );
+
+		// Set overall buffer volume.
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MAX );
+		}
+		else
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MIN );
+		}
+	}
+	else
+	{
+		m_SetDeferredVolume = true;
+		m_DeferredVolume[0]	= volume;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CADPCMFileStream::SetVolume( float volL, float volR )
+{
+	if( m_pRenderFilter )
+	{
+		// This array will hold individual volumes for the five speakers.
+		// In order, they are: front left, center, front right, rear right, rear left.
+		float	volumes[5];
+		int		i_volumes[5], max_i_volume;
+		memset( volumes, 0, sizeof( float ) * 5 );
+		
+		if(( volL == 0.0f ) && ( volR == 0.0f ))
+		{
+			// Pointless doing any more work.
+		}
+		else
+		{
+			// Get the length of the vector here which will be used to multiply out the normalised speaker volumes.
+			Mth::Vector test( fabsf( volL ), fabsf( volR ), 0.0f, 0.0f );
+			float amplitude = test.Length();
+
+			// Look just at the normalized right component to figure the sound angle from Matt's calculations.
+			test.Normalize();
+
+			float angle;
+			angle	= asinf( test[Y] );
+			angle	= ( angle * 2.0f ) - ( Mth::PI * 0.5f );
+			angle	= ( volL < 0.0f ) ? ( Mth::PI - angle ) : angle;
+		
+			// Now figure volumes based on speaker coverage.
+			angle	= Mth::RadToDeg( angle );
+		
+			Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+			sfx_manager->Get5ChannelMultipliers( angle, &volumes[0] );
+
+			// Now readjust the relative values...
+			for( int v = 0; v < 5; ++v )
+			{
+				// Scale back up to original amplitude.
+				volumes[v] *= amplitude;
+
+				if( volumes[v] > 100.0f )
+					volumes[v] = 100.0f;
+			}
+		}
+		
+		// Now figure the attenuation of the sound. To convert to a decibel value, figure the ratio of requested
+		// volume versus max volume, then calculate the log10 and multiply by (10 * 2). (The 2 is because sound
+		// power varies as square of pressure, and squaring doubles the log value).
+		max_i_volume = DSBVOLUME_EFFECTIVE_MIN;
+		for( int v = 0; v < 5; ++v )
+		{
+			if( volumes[v] > 0.0f )
+			{
+				float attenuation	= 20.0f * log10f( volumes[v] * 0.01f );
+				i_volumes[v]		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+				if( i_volumes[v] < DSBVOLUME_EFFECTIVE_MIN )
+					i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+				else if( i_volumes[v] > DSBVOLUME_MAX )
+					i_volumes[v] = DSBVOLUME_MAX;
+
+				if( i_volumes[v] > max_i_volume )
+					max_i_volume = i_volumes[v];
+			}
+			else
+			{
+				i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+			}
+		}
+		
+		// Set individual mixbins for panning.
+		DSMIXBINS			dsmixbins;
+		DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+
+		dsmixbins.dwMixBinCount			= 0;
+		dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+
+		if( i_volumes[0] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[0];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[1] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[1];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[2] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[2];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[3] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[3];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[4] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_FRONT_CENTER;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[4];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_I3DL2;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= DSBVOLUME_MAX;
+            dsmixbins.dwMixBinCount++;
+		}
+
+		// Set all speaker volumes.
+		m_pRenderFilter->SetMixBinVolumes( &dsmixbins );
+
+		// Set overall buffer volume.
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MAX );
+		}
+		else
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MIN );
+		}
+	}
+	else
+	{
+		m_SetDeferredVolumeLR	= true;
+		m_DeferredVolume[0]		= volL;
+		m_DeferredVolume[1]		= volR;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CADPCMFileStream::SetVolume( float v0, float v1, float v2, float v3, float v4 )
+{
+	if( m_pRenderFilter )
+	{
+		float volumes[5];
+		volumes[0] = ( v0 > 100.0f ) ? 100.0f : v0;
+		volumes[1] = ( v1 > 100.0f ) ? 100.0f : v1;
+		volumes[2] = ( v2 > 100.0f ) ? 100.0f : v2;
+		volumes[3] = ( v3 > 100.0f ) ? 100.0f : v3;
+		volumes[4] = ( v4 > 100.0f ) ? 100.0f : v4;
+
+		int i_volumes[5], max_i_volume;
+
+		// Now figure the attenuation of the sound. To convert to a decibel value, figure the ratio of requested
+		// volume versus max volume, then calculate the log10 and multiply by (10 * 2). (The 2 is because sound
+		// power varies as square of pressure, and squaring doubles the log value).
+		max_i_volume = DSBVOLUME_EFFECTIVE_MIN;
+		for( int v = 0; v < 5; ++v )
+		{
+			if( volumes[v] > 0.0f )
+			{
+				float attenuation	= 20.0f * log10f( volumes[v] * 0.01f );
+				i_volumes[v]		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+				if( i_volumes[v] < DSBVOLUME_EFFECTIVE_MIN )
+					i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+				else if( i_volumes[v] > DSBVOLUME_MAX )
+					i_volumes[v] = DSBVOLUME_MAX;
+
+				if( i_volumes[v] > max_i_volume )
+					max_i_volume = i_volumes[v];
+			}
+			else
+			{
+				i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+			}
+		}
+		
+		// Set individual mixbins for panning.
+		DSMIXBINS			dsmixbins;
+		DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+
+		dsmixbins.dwMixBinCount			= 0;
+		dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+
+		if( i_volumes[0] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[0];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[1] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[1];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[2] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[2];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[3] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[3];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[4] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_FRONT_CENTER;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[4];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_I3DL2;
+			dsmbvp[dsmixbins.dwMixBinCount].lVolume		= DSBVOLUME_MAX;
+			dsmixbins.dwMixBinCount++;
+		}
+
+		// Set all speaker volumes.
+		m_pRenderFilter->SetMixBinVolumes( &dsmixbins );
+
+		// Set overall buffer volume.
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MAX );
+		}
+		else
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MIN );
+		}
+	}
+	else
+	{
+		m_SetDeferredVolume5Channel	= true;
+		m_DeferredVolume[0]			= v0;
+		m_DeferredVolume[1]			= v1;
+		m_DeferredVolume[2]			= v2;
+		m_DeferredVolume[3]			= v3;
+		m_DeferredVolume[4]			= v4;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetDeferredVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CADPCMFileStream::SetDeferredVolume( void )
+{
+	if( m_SetDeferredVolume )
+	{
+		m_SetDeferredVolume = false;
+		SetVolume( m_DeferredVolume[0] );
+	}
+
+	if( m_SetDeferredVolumeLR )
+	{
+		m_SetDeferredVolumeLR = false;
+		SetVolume( m_DeferredVolume[0], m_DeferredVolume[1] );
+	}
+
+	if( m_SetDeferredVolume5Channel )
+	{
+		m_SetDeferredVolume5Channel = false;
+		SetVolume( m_DeferredVolume[0], m_DeferredVolume[1], m_DeferredVolume[2], m_DeferredVolume[3], m_DeferredVolume[4] );
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: IsSafeToDelete
+// Desc: 
+//-----------------------------------------------------------------------------
+bool CADPCMFileStream::IsSafeToDelete( void )
+{
+	return true;
+}
+
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+} // namespace PCM
diff --git a/Code/Gel/Music/Xbox/p_adpcmfilestream.h b/Code/Gel/Music/Xbox/p_adpcmfilestream.h
new file mode 100644
index 0000000..36631d0
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_adpcmfilestream.h
@@ -0,0 +1,174 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Game Engine												**
+**																			**
+**	File name:		p_adpcmfilesteam.h										**
+**																			**
+**	Created: 		01/27/2003	-	dc										**
+**																			**
+*****************************************************************************/
+
+#ifndef __P_ADPCMFILESTREAM_H
+#define __P_ADPCMFILESTREAM_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Pcm
+{
+
+// Define the maximum amount of packets we will ever submit to the renderer.
+#define ADPCMSTRM_PACKET_COUNT		8
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+struct sADPCMExtendedWaveFormatEx
+{
+	WAVEFORMATEX	m_wfxSourceFormat;
+	uint16			m_extendedInfo;
+};
+	
+	
+/******************************************************************/
+/*																  */
+/* ADPCM file streaming object, designed for fully asynchronous	  */
+/* streaming from disc.											  */
+/*																  */
+/******************************************************************/
+class CADPCMFileStream : public Spt::Class
+{
+	private:
+
+	HANDLE				m_hFile;
+	HANDLE				m_hThread;
+	OVERLAPPED*			m_pOverlapped;								// OVERLAPPED structure for asynchronous file access.
+	bool				m_FirstRead;								// Flag to indicate first request for more data to be streamed from disc.
+	bool				m_ReadComplete;
+	int					m_SuccessiveReads;							// Counts how many read operations performed in total.
+	bool				m_bUseWAD;
+	bool				m_bUse3D;									// Sets whether 2D or 3D mixbins are set up.
+	DWORD				m_dwWADOffset;	
+	DWORD				m_dwWADLength;
+
+	public:
+
+    IDirectSoundStream* m_pRenderFilter;							// Render (DirectSoundStream) filter
+    LPVOID              m_pvSourceBuffer;							// Source filter data buffer
+    LPVOID              m_pvRenderBuffer;							// Render filter data buffer
+    DWORD               m_adwPacketStatus[ADPCMSTRM_PACKET_COUNT];	// Packet status array
+	int					m_PacketBytes;								// Size of each packet (will differ for mono and stereo).
+	DWORD               m_dwStartingDataOffset;						// Offset into wma file where data begins.
+    DWORD				m_dwPercentCompleted;						// Percent completed
+	bool				m_Paused;
+	bool				m_Completed;								// For single-shot, indicates loading of final packet to renderer is completed
+    DWORD               m_LastPacket;								// Last packet array index
+	bool				m_SetDeferredVolume;
+	bool				m_SetDeferredVolumeLR;
+	bool				m_SetDeferredVolume5Channel;
+	bool				m_bOkayToPlay;								// Used when exact syncing is required - will load buffers but won't start decompression until this flag is true.
+	float				m_DeferredVolume[5];
+	uint32				m_Mixbins;									// Bitfield indicating which mixbins are active for this buffer.
+	uint32				m_NumMixbins;
+
+    // Packet processing
+    BOOL				FindFreePacket( DWORD* pdwPacketIndex );
+    HRESULT				ProcessSource( DWORD dwPacketIndex );
+    HRESULT				ProcessRenderer( DWORD dwPacketIndex );
+
+	sADPCMExtendedWaveFormatEx	m_wfxExtendedSourceFormat;
+    XFileMediaObject*   m_pSourceFilter;							// Source (wave file) filter
+	LPVOID				m_pFileBuffer;								// Buffer for async read of raw data.
+	int					m_FileBytesRead;							// Total number of raw bytes read from disc.
+	int					m_FileBytesProcessed;						// Total number of raw bytes processed by XMO.
+	int					m_DecoderCreation;
+	bool				m_AwaitingDeletion;
+
+    // Processing
+    HRESULT				Process();
+
+    // Initialization
+	HRESULT				InitializeFormatBlock( uint32 *p_header_data );
+	HRESULT				Initialize( HANDLE h_file, unsigned int offset, unsigned int length, void* fileBuffer );
+	HRESULT				PostInitialize( void );
+	bool				CreateSourceBuffer( void );
+    
+    // Play control
+    void				Pause( DWORD dwPause );
+	void				SetVolume( float volume );
+	void				SetVolume( float volumeL, float volumeR );
+	void				SetVolume( float v0, float v1, float v2, float v3, float v4 );
+	void				SetDeferredVolume( void );
+
+	// Query
+	IDirectSoundStream*	GetSoundStream( void )					{ return m_pRenderFilter; }
+	bool				IsCompleted( void )						{ return ( m_Completed && ( XMEDIAPACKET_STATUS_PENDING != m_adwPacketStatus[m_LastPacket] )); }
+	bool				IsSafeToDelete( void );
+	bool				PreLoadDone( void );
+
+	// Asynchronous stuff.
+	void				AsyncRead( void );
+
+
+    CADPCMFileStream( bool use_3d = false );
+    ~CADPCMFileStream();
+};
+
+	
+	
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Pcm
+
+#endif	// __P_ADPCMFILESTREAM_H
+
+
+
+
diff --git a/Code/Gel/Music/Xbox/p_music.cpp b/Code/Gel/Music/Xbox/p_music.cpp
new file mode 100644
index 0000000..34dbd74
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_music.cpp
@@ -0,0 +1,1324 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Game Engine (GEL)	 									**
+**																			**
+**	File name:		p_music.cpp												**
+**																			**
+**	Created:		07/24/01	-	dc										**
+**																			**
+**	Description:	Xbox specific .wma streaming code						**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include "p_music.h"
+#include "p_wmafilestream.h"
+#include "p_adpcmfilestream.h"
+#include "p_soundtrack.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace Pcm
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define STREAMS_ARE_PCM
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sFileStreamInfo
+{
+	enum FileStreamType
+	{
+		FILESTREAM_TYPE_WMA		= 0,
+		FILESTREAM_TYPE_ADPCM	= 1
+	};
+
+	// These two could really be a union...
+	CWMAFileStream*		p_wma_filestream;
+	CADPCMFileStream*	p_adpcm_filestream;
+
+	int					pitch;
+	float				volume;
+	bool				paused;
+
+	void				CreateFileStream( FileStreamType type, bool use_3d = false );
+	void				DestroyFileStream( void );
+	
+	HRESULT				Initialize( HANDLE h_file, void* fileBuffer );
+    HRESULT				Initialize( HANDLE h_file, unsigned int offset, unsigned int length, void* fileBuffer );
+	HRESULT				Process( void );
+	void				Pause( uint32 pause );
+	void				SetOkayToPlay( bool okay_to_play );
+	bool				GetOkayToPlay( void );
+	bool				HasFileStream( void )	{ return (( p_wma_filestream != NULL ) || ( p_adpcm_filestream != NULL )); }
+	bool				IsCompleted( void );
+	bool				IsAwaitingDeletion( void );
+	void				SetAwaitingDeletion( bool is_awaiting );
+	bool				IsSafeToDelete( void );
+	void				Flush( void );
+	bool				IsPreLoadDone( void );
+	void				SetVolume( float v0 );
+	void				SetVolume( float v0, float v1 );
+	void				SetVolume( float v0, float v1, float v2, float v3, float v4 );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::CreateFileStream( FileStreamType type, bool use_3d )
+{
+	Dbg_Assert(( p_wma_filestream == NULL ) && ( p_adpcm_filestream == NULL ));
+	if( type == FILESTREAM_TYPE_WMA )
+	{
+		p_wma_filestream = new CWMAFileStream( use_3d );
+	}
+	else if( type == FILESTREAM_TYPE_ADPCM )
+	{
+		p_adpcm_filestream = new CADPCMFileStream( use_3d );
+	}
+	else
+	{
+		Dbg_Assert( 0 );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::DestroyFileStream( void )
+{
+	if( p_wma_filestream )
+	{
+		delete p_wma_filestream;
+		p_wma_filestream = NULL;
+	}
+
+	if( p_adpcm_filestream )
+	{
+		delete p_adpcm_filestream;
+		p_adpcm_filestream = NULL;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+HRESULT sFileStreamInfo::Initialize( HANDLE h_file, void* fileBuffer )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->Initialize( h_file, fileBuffer );
+	}
+	else if( p_adpcm_filestream )
+	{
+		Dbg_Assert( 0 );
+	}
+	return -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+HRESULT sFileStreamInfo::Initialize( HANDLE h_file, unsigned int offset, unsigned int length, void* fileBuffer )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->Initialize( h_file, offset, length, fileBuffer );
+	}
+	else if( p_adpcm_filestream )
+	{
+		return p_adpcm_filestream->Initialize( h_file, offset, length, fileBuffer );
+	}
+	return -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+HRESULT sFileStreamInfo::Process( void )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->Process();
+	}
+	else if( p_adpcm_filestream )
+	{
+		return p_adpcm_filestream->Process();
+	}
+	return -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::Pause( uint32 pause )
+{
+	if( p_wma_filestream )
+	{
+		p_wma_filestream->Pause( pause );
+	}
+	else if( p_adpcm_filestream )
+	{
+		p_adpcm_filestream->Pause( pause );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::SetOkayToPlay( bool okay_to_play )
+{
+	if( p_wma_filestream )
+	{
+		p_wma_filestream->m_bOkayToPlay = okay_to_play;
+	}
+	else if( p_adpcm_filestream )
+	{
+		p_adpcm_filestream->m_bOkayToPlay = okay_to_play;
+	}
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sFileStreamInfo::GetOkayToPlay( void )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->m_bOkayToPlay;
+	}
+	else if( p_adpcm_filestream )
+	{
+		return p_adpcm_filestream->m_bOkayToPlay;
+	}
+	return false;
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sFileStreamInfo::IsCompleted( void )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->IsCompleted();
+	}
+	else if( p_adpcm_filestream )
+	{
+		return p_adpcm_filestream->IsCompleted();
+	}
+	return false;
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sFileStreamInfo::IsAwaitingDeletion( void )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->m_AwaitingDeletion;
+	}
+	else if( p_adpcm_filestream )
+	{
+		return p_adpcm_filestream->m_AwaitingDeletion;
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::SetAwaitingDeletion( bool is_awaiting )
+{
+	if( p_wma_filestream )
+	{
+		p_wma_filestream->m_AwaitingDeletion = is_awaiting;
+	}
+	else if( p_adpcm_filestream )
+	{
+		p_adpcm_filestream->m_AwaitingDeletion = is_awaiting;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sFileStreamInfo::IsSafeToDelete( void )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->IsSafeToDelete();
+	}
+	else if( p_adpcm_filestream )
+	{
+		return p_adpcm_filestream->IsSafeToDelete();
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sFileStreamInfo::IsPreLoadDone( void )
+{
+	if( p_wma_filestream )
+	{
+		return p_wma_filestream->PreLoadDone();
+	}
+	else if( p_adpcm_filestream )
+	{
+		return p_adpcm_filestream->PreLoadDone();
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::Flush( void )
+{
+	if( p_wma_filestream ) 
+	{
+		if( p_wma_filestream->m_pRenderFilter )
+		{
+			p_wma_filestream->m_pRenderFilter->FlushEx( 0, DSSTREAMFLUSHEX_ASYNC );
+		}
+	}
+	else if( p_adpcm_filestream )
+	{
+		if( p_adpcm_filestream->m_pRenderFilter )
+		{
+			p_adpcm_filestream->m_pRenderFilter->FlushEx( 0, DSSTREAMFLUSHEX_ASYNC );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::SetVolume( float v0 )
+{
+	if( p_wma_filestream ) 
+	{
+		p_wma_filestream->SetVolume( v0 );
+	}
+	else if( p_adpcm_filestream )
+	{
+		p_adpcm_filestream->SetVolume( v0 );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::SetVolume( float v0, float v1 )
+{
+	if( p_wma_filestream ) 
+	{
+		p_wma_filestream->SetVolume( v0, v1 );
+	}
+	else if( p_adpcm_filestream )
+	{
+		p_adpcm_filestream->SetVolume( v0, v1 );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sFileStreamInfo::SetVolume( float v0, float v1, float v2, float v3, float v4 )
+{
+	if( p_wma_filestream ) 
+	{
+		p_wma_filestream->SetVolume( v0, v1, v2, v3, v4 );
+	}
+	else if( p_adpcm_filestream )
+	{
+		p_adpcm_filestream->SetVolume( v0, v1, v2, v3, v4 );
+	}
+}
+
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+sFileStreamInfo	gMusicInfo;
+sFileStreamInfo	gStreamInfo[NUM_STREAMS];
+
+const uint32	FILESTREAM_BUFFER_SIZE	= 80 * 1024;
+
+#pragma pack( 16 )
+// Grab an 80k read buffer. Must be DWORD aligned.
+// This is big enough for (9 * 8k packets) for WMA, where last packet mirrors the first packet,
+// for cases when the decoder reads past the end of the ring buffer.
+// Also big enough for ( 5 * 16k packets) for ADPCM, where no wraparound is required.
+DWORD			gMusicFileBuffer[FILESTREAM_BUFFER_SIZE / 4];
+DWORD			gStreamFileBuffer[NUM_STREAMS][FILESTREAM_BUFFER_SIZE / 4];
+#pragma pack()
+
+const int		NUM_OVERLAPPED	= 256;
+OVERLAPPED		gOverlapped[NUM_OVERLAPPED];
+int				gNextOverlap = 0;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+HANDLE	ghWADFile				= INVALID_HANDLE_VALUE;
+uint32	*pWADData				= NULL;
+uint32	numWADFileEntries		= 0;
+
+HANDLE	ghMusicWADFile			= INVALID_HANDLE_VALUE;
+uint32	*pMusicWADData			= NULL;
+uint32	numMusicWADFileEntries	= 0;
+
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+OVERLAPPED *PCMAudio_GetNextOverlapped( void )
+{
+	OVERLAPPED *p_return = &( gOverlapped[gNextOverlap] );
+	if( ++gNextOverlap >= NUM_OVERLAPPED )
+	{
+		gNextOverlap = 0;
+	}
+
+	ZeroMemory( p_return, sizeof( OVERLAPPED ));
+	return p_return;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 PCMAudio_GetFilestreamBufferSize( void )
+{
+	return FILESTREAM_BUFFER_SIZE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_Init( void )
+{
+	// Zero the music and filestream info arrays.
+	ZeroMemory( &gMusicInfo, sizeof( sFileStreamInfo ));
+	ZeroMemory( &gStreamInfo[0], sizeof( sFileStreamInfo ) * NUM_STREAMS );
+
+	// Enumerate user soundtracks.
+	GetNumSoundtracks();
+
+	// Figure out the language, and open appropriate file.
+	Config::ELanguage lang = Config::GetLanguage();
+
+	// Assume English.
+#	ifdef STREAMS_ARE_PCM
+	ghWADFile		= CreateFile( "d:\\data\\streams\\pcm\\pcm.wad",
+#	else
+	ghWADFile		= CreateFile( "d:\\data\\streams\\wma\\wma.wad",
+#	endif
+								GENERIC_READ,
+								FILE_SHARE_READ,								// Share mode.
+								NULL,											// Ignored (security attributes).
+								OPEN_EXISTING,									// File has to exist already.
+								FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,	// Xbox has no asynchronous i/o buffering.
+								NULL );											// Ignored (template file).
+#	ifdef STREAMS_ARE_PCM
+	ghMusicWADFile = CreateFile( "d:\\data\\streams\\pcm\\music_pcm.wad",
+#	else
+	ghMusicWADFile = CreateFile( "d:\\data\\streams\\wma\\music_wma.wad",
+#	endif
+								GENERIC_READ,
+								FILE_SHARE_READ,								// Share mode.
+								NULL,											// Ignored (security attributes).
+								OPEN_EXISTING,									// File has to exist already.
+								FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,	// Xbox has no asynchronous i/o buffering.
+								NULL );											// Ignored (template file).
+
+	// Now read in the data files used for indexing into the WAD files.
+#	ifdef STREAMS_ARE_PCM
+	HANDLE wad_data = CreateFile( "d:\\data\\streams\\pcm\\pcm.dat",
+#	else
+	HANDLE wad_data = CreateFile( "d:\\data\\streams\\wma\\wma.dat",
+#	endif
+									GENERIC_READ,
+									FILE_SHARE_READ,								// Share mode.
+									NULL,											// Ignored (security attributes).
+									OPEN_EXISTING,									// File has to exist already.
+									FILE_FLAG_SEQUENTIAL_SCAN,
+									NULL );											// Ignored (template file).
+
+	if( wad_data != INVALID_HANDLE_VALUE )
+	{
+		uint32 bytes_read;
+		ReadFile( wad_data, &numWADFileEntries, sizeof( uint32 ), &bytes_read, NULL );
+		pWADData = new uint32[numWADFileEntries * 3];
+		ReadFile( wad_data, pWADData, sizeof( uint32 ) * numWADFileEntries * 3, &bytes_read, NULL );
+		CloseHandle( wad_data );
+	}
+			
+	// Sort the wad file entries into increasing checksum order, so that we can use a binary search algorithm to
+	// find the checksum quickly.
+	for( uint32 i = 0; i < numWADFileEntries; ++i )
+	{
+		for( uint32 j = i + 1; j < numWADFileEntries; ++j )
+		{
+			if( pWADData[i * 3] > pWADData[j * 3] )
+			{
+				uint32 temp[3];
+				temp[0]				= pWADData[i * 3];
+				temp[1]				= pWADData[i * 3 + 1];
+				temp[2]				= pWADData[i * 3 + 2];
+				pWADData[i * 3]		= pWADData[j * 3];
+				pWADData[i * 3 + 1]	= pWADData[j * 3 + 1];
+				pWADData[i * 3 + 2]	= pWADData[j * 3 + 2];
+				pWADData[j * 3]		= temp[0];
+				pWADData[j * 3 + 1]	= temp[1];
+				pWADData[j * 3 + 2]	= temp[2];
+			}
+		}
+	}
+			
+#	ifdef STREAMS_ARE_PCM
+	wad_data = CreateFile( "d:\\data\\streams\\pcm\\music_pcm.dat",
+#	else
+	wad_data = CreateFile( "d:\\data\\streams\\wma\\music_wma.dat",
+#	endif
+							GENERIC_READ,
+							FILE_SHARE_READ,								// Share mode.
+							NULL,											// Ignored (security attributes).
+							OPEN_EXISTING,									// File has to exist already.
+							FILE_FLAG_SEQUENTIAL_SCAN,
+							NULL );											// Ignored (template file).
+
+	if( wad_data != INVALID_HANDLE_VALUE )
+	{
+		uint32 bytes_read;
+		ReadFile( wad_data, &numMusicWADFileEntries, sizeof( uint32 ), &bytes_read, NULL );
+		pMusicWADData = new uint32[numMusicWADFileEntries * 3];
+		ReadFile( wad_data, pMusicWADData, sizeof( uint32 ) * numMusicWADFileEntries * 3, &bytes_read, NULL );
+		CloseHandle( wad_data );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Call every frame to make sure music and stream buffers are	  */
+/* loaded and current status is checked each frame...			  */
+/*                                                                */
+/******************************************************************/
+int PCMAudio_Update( void )
+{
+	if( gMusicInfo.HasFileStream())
+	{
+		HRESULT hr = gMusicInfo.Process();
+
+		if( gMusicInfo.IsCompleted())
+		{
+			gMusicInfo.DestroyFileStream();
+		}
+		else if( gMusicInfo.IsAwaitingDeletion() && gMusicInfo.IsSafeToDelete())
+		{
+			gMusicInfo.DestroyFileStream();
+		}
+	}
+
+	for( int i = 0; i < NUM_STREAMS; ++i )
+	{
+		if( gStreamInfo[i].HasFileStream())
+		{
+			HRESULT hr = gStreamInfo[i].Process();
+
+			if( gStreamInfo[i].IsCompleted())
+			{
+				gStreamInfo[i].DestroyFileStream();
+			}
+			else if( gStreamInfo[i].IsAwaitingDeletion() && gStreamInfo[i].IsSafeToDelete())
+			{
+				gStreamInfo[i].DestroyFileStream();
+			}
+		}
+	}
+
+	// A non-zero return value singnals an error condition.
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_StopMusic( bool waitPlease )
+{
+	if( gMusicInfo.HasFileStream())
+	{
+		if( gMusicInfo.IsSafeToDelete())
+		{	
+			gMusicInfo.Flush();
+			gMusicInfo.DestroyFileStream();
+		}
+		else
+		{
+			gMusicInfo.SetAwaitingDeletion( true );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_StopStream( int whichStream, bool waitPlease )
+{
+	if( gStreamInfo[whichStream].HasFileStream())
+	{
+		if( gStreamInfo[whichStream].IsSafeToDelete())
+		{
+			gStreamInfo[whichStream].Flush();
+			gStreamInfo[whichStream].DestroyFileStream();
+		}
+		else
+		{
+			gStreamInfo[whichStream].SetAwaitingDeletion( true );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_StopStreams( void )
+{
+	for( int i = 0; i < NUM_STREAMS; ++i )
+	{
+		PCMAudio_StopStream( i, false );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This is temp code for the preload streams.  It just calls the normal one.
+
+static uint32 sPreLoadChecksum[NUM_STREAMS];
+static uint32 sPreLoadMusicChecksum;
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Get a stream loaded into a buffer, but don't play yet		  */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadStream( uint32 checksum, int whichStream )
+{
+	Dbg_Assert(( whichStream >= 0 ) && ( whichStream < NUM_STREAMS ));
+	sPreLoadChecksum[whichStream] = checksum;
+
+	// Start the track as normal...
+	if( PCMAudio_PlayStream( checksum, whichStream, NULL, 0.0f, false ))
+	{
+		// ...but then flag it as not okay to play until we say so.
+		if( gStreamInfo[whichStream].HasFileStream())
+		{
+			gStreamInfo[whichStream].SetOkayToPlay( false );
+		}
+        return true;
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Returns true if preload done. Assumes that caller is calling	  */
+/* this on a preloaded, but not yet played, stream. The results	  */
+/* are meaningless otherwise.									  */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadStreamDone( int whichStream )
+{
+	if( gStreamInfo[whichStream].HasFileStream())
+	{
+		return gStreamInfo[whichStream].IsPreLoadDone();
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Tells a preloaded stream to start playing.					  */
+/* Must call PCMAudio_PreLoadStreamDone() first to guarantee that */
+/* it starts immediately.										  */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_StartPreLoadedStream( int whichStream, Sfx::sVolume *p_volume, float pitch )
+{
+	// Maybe we should check here to make sure the checksum of the music info filestream matches that
+	// passed in when the music stream preload request came in.
+	if( gStreamInfo[whichStream].HasFileStream())
+	{
+		gStreamInfo[whichStream].SetOkayToPlay( true );
+		PCMAudio_SetStreamVolume( p_volume, whichStream );
+		return true;
+	}
+	return false;	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadMusicStream( uint32 checksum )
+{
+	sPreLoadMusicChecksum = checksum;
+
+	// Start the track as normal...
+	if( PCMAudio_PlayMusicTrack( sPreLoadMusicChecksum ))
+	{
+		// ...but then flag it as not okay to play until we say so.
+		if( gMusicInfo.HasFileStream())
+		{
+			gMusicInfo.SetOkayToPlay( false );
+		}
+        return true;
+	}
+
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadMusicStreamDone( void )
+{
+	if( gMusicInfo.HasFileStream())
+	{
+		return gMusicInfo.IsPreLoadDone();
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_StartPreLoadedMusicStream( void )
+{
+	// Maybe we should check here to make sure the checksum of the music info filestream matches that
+	// passed in when the music stream preload request came in.
+	if( gMusicInfo.HasFileStream())
+	{
+		// Call update immediately to start playback ASAP.
+		gMusicInfo.SetOkayToPlay( true );
+		PCMAudio_Update();
+
+		return true;
+	}
+	return false;	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int PCMAudio_GetMusicStatus( void )
+{
+	if( gMusicInfo.HasFileStream())
+	{
+		return PCM_STATUS_PLAYING;
+	}
+	else
+	{
+		return PCM_STATUS_FREE;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int PCMAudio_GetStreamStatus( int whichStream )
+{
+	int start, end;
+
+	// Negative one is used to signal 'any stream'.
+	if( whichStream == -1 )
+	{
+		start	= 0;
+		end		= NUM_STREAMS;
+	}
+	else
+	{
+		start	= whichStream;
+		end		= start + 1;
+	}
+
+	for( int s = start; s < end; ++s )
+	{
+		if( !gStreamInfo[s].HasFileStream())
+		{		
+			return PCM_STATUS_FREE;
+		}
+	}
+
+	return PCM_STATUS_PLAYING;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_Pause( bool pause, int ch )
+{
+	if( ch == MUSIC_CHANNEL )
+	{
+		gMusicInfo.paused = pause;
+		if( gMusicInfo.HasFileStream())
+		{
+			if( pause )
+			{
+				gMusicInfo.Pause( 1 );
+			}
+			else
+			{
+				gMusicInfo.Pause( 0 );
+			}
+		}
+	}
+	else
+	{
+		for( int s = 0; s < NUM_STREAMS; ++s )
+		{
+			if( gStreamInfo[s].HasFileStream())
+			{
+				if( pause )
+				{
+					gStreamInfo[s].Pause( 1 );
+					gStreamInfo[s].paused = true;
+				}
+				else
+				{
+					gStreamInfo[s].Pause( 0 );
+					gStreamInfo[s].paused = false;
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_TrackExists( const char *pTrackName, int ch )
+{
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_LoadMusicHeader( const char *nameOfFile )
+{
+	// Legacy call left over from PS2 code.
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_LoadStreamHeader( const char *nameOfFile )
+{
+	// Legacy call left over from PS2 code.
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Return (any) position if t in sorted x[0..n-1] or -1 if t is	  */
+/* not present.                                                    */
+/*                                                                */
+/******************************************************************/
+static int binarySearch( uint32 checksum )
+{
+	int l = 0;
+	int u = numWADFileEntries - 1;
+	while( l <= u )
+	{
+		int m = ( l + u ) / 2;
+		if( pWADData[m * 3] < checksum )
+			l = m + 1;
+		else if ( pWADData[m * 3] == checksum )
+			return m;
+		else // x[m] > t
+			u = m - 1;
+	}
+	return -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 PCMAudio_FindNameFromChecksum( uint32 checksum, int ch )
+{
+	if( ch != EXTRA_CHANNEL )
+		return 0;
+
+	int rv = binarySearch( checksum );
+	
+	return ( rv == -1 ) ? 0 : checksum;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_SetStreamVolume( Sfx::sVolume *p_volume, int whichStream )
+{
+	if( gStreamInfo[whichStream].HasFileStream())
+	{
+		// Adjust volumes for overall sound volume.
+		Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+
+		switch( p_volume->GetVolumeType())
+		{
+			case Sfx::VOLUME_TYPE_5_CHANNEL_DOLBY5_1:
+			{
+				float v0	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 0 ));
+				float v1	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 1 ));
+				float v2	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 2 ));
+				float v3	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 3 ));
+				float v4	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 4 ));
+
+				gStreamInfo[whichStream].volume = ( v0 + v1 + v2 + v3 + v4 ) * ( 1.0f / 5.0f );
+				gStreamInfo[whichStream].SetVolume( v0, v1, v2, v3, v4 );
+				break;
+			}
+			case Sfx::VOLUME_TYPE_2_CHANNEL_DOLBYII:
+			{
+				float v0	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 0 ));
+				float v1	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 1 ));
+
+				gStreamInfo[whichStream].volume = v0;
+				gStreamInfo[whichStream].SetVolume( v0, v1 );
+				break;
+			}
+			case Sfx::VOLUME_TYPE_BASIC_2_CHANNEL:
+			{
+				float v0	= PERCENT( sfx_manager->GetMainVolume(), p_volume->GetChannelVolume( 0 ));
+
+				gStreamInfo[whichStream].volume = v0;
+				gStreamInfo[whichStream].SetVolume( v0 );
+				break;
+			}
+			default:
+			{
+				Dbg_Assert( 0 );
+				break;
+			}
+		}
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int PCMAudio_SetMusicVolume( float volume )
+{
+	if( gMusicInfo.HasFileStream())
+	{
+		gMusicInfo.volume = volume;
+		gMusicInfo.SetVolume( volume );
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_SetStreamPitch( float fPitch, int whichStream )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PlayMusicTrack( uint32 checksum )
+{
+	// Find the entry in the offset array.
+	bool found = false;
+	
+	unsigned int samplelength, sampleoffset;
+	
+	// English.
+	for( unsigned int entry = 0; entry < numMusicWADFileEntries; ++entry )
+	{
+		if( pMusicWADData[entry * 3] == checksum )
+		{
+			sampleoffset	= pMusicWADData[entry * 3 + 1];
+			samplelength	= pMusicWADData[entry * 3 + 2];
+			found			= true;
+			break;
+		}
+	}
+
+	if( !found )
+	{
+		return false;
+	}
+
+	// Just a stream like everything else.
+	if( PCMAudio_GetMusicStatus() != PCM_STATUS_FREE )
+	{
+		return false;
+	}
+
+	// Don't want to use 3d processing for the music track.
+#	ifdef STREAMS_ARE_PCM
+	gMusicInfo.CreateFileStream( sFileStreamInfo::FILESTREAM_TYPE_ADPCM, false );
+#	else
+	gMusicInfo.CreateFileStream( sFileStreamInfo::FILESTREAM_TYPE_WMA, false );
+#	endif
+
+	HRESULT hr = gMusicInfo.Initialize( ghMusicWADFile, sampleoffset, samplelength, gMusicFileBuffer );
+	if( hr == S_OK )
+	{
+		// All started fine. Pause music if paused flag is set.
+		if( gMusicInfo.paused )
+		{
+			PCMAudio_Pause( true, MUSIC_CHANNEL );
+		}
+		return true;
+	}
+	else
+	{
+		// Failed to initialize the stream.
+		gMusicInfo.DestroyFileStream();
+		Dbg_MsgAssert( 0, ( "Failed to initialize music stream: %x", checksum ));
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PlayMusicTrack( const char *filename )
+{
+	const char	*samplename = filename;
+	char		*locate;
+	
+	// Search for the last directory seperator, and cut off all of the path prior to it.
+	if( locate = strrchr( samplename, '\\' ))
+	{
+		samplename = locate + 1;
+	}
+	if( locate = strrchr( samplename, '/' ))
+	{
+		samplename = locate + 1;
+	}
+
+	// Now generate the checksum for this samplename.
+	uint32 checksum = Crc::GenerateCRCFromString( samplename );
+
+	bool rv = PCMAudio_PlayMusicTrack( checksum );
+	if( rv == false )
+	{
+		Dbg_Message( "Failed to find stream: %s", filename );
+	}
+	return rv;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PlaySoundtrackMusicTrack( int soundtrack, int track )
+{
+	// Just a stream like everything else.
+	if( gMusicInfo.HasFileStream())
+	{
+		Dbg_MsgAssert( 0, ( "Playing new track without stopping the first track." ));
+	}
+	else
+	{
+		// Don't want to use 3d processing for the music track.
+		gMusicInfo.CreateFileStream( sFileStreamInfo::FILESTREAM_TYPE_WMA, false );
+
+		HANDLE h_song = GetSoundtrackWMAHandle( soundtrack, track );
+		if( h_song == INVALID_HANDLE_VALUE )
+		{
+			return false;
+		}
+
+		HRESULT hr = gMusicInfo.Initialize( h_song, gMusicFileBuffer );
+		if( hr == S_OK )
+		{
+			// All started fine. Pause music if paused flag is set.
+			if( gMusicInfo.paused )
+			{
+				PCMAudio_Pause( true, MUSIC_CHANNEL );
+			}
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_StartStream( int whichStream )
+{
+	if( gStreamInfo[whichStream].HasFileStream())
+	{
+		if( gStreamInfo[whichStream].GetOkayToPlay() == false )
+		{
+			// Now okay to start playing.
+			gStreamInfo[whichStream].SetOkayToPlay( true );
+		}
+		return true;
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PlayStream( uint32 checksum, int whichStream, Sfx::sVolume *p_volume, float fPitch, bool preload )
+{
+	unsigned int samplelength, sampleoffset;
+
+	// Perform a binary search on the checksum table to find the correct entry.	
+	int entry = binarySearch( checksum );
+	if( entry >= 0 )
+	{
+		sampleoffset	= pWADData[entry * 3 + 1];
+		samplelength	= pWADData[entry * 3 + 2];
+	}
+	else
+	{
+		return false;
+	}
+	
+	Dbg_Assert(( whichStream >= 0 ) && ( whichStream < NUM_STREAMS ));
+
+	// Just a stream like everything else.
+	if( PCMAudio_GetStreamStatus( whichStream ) != PCM_STATUS_FREE )
+	{
+		return false;
+	}
+
+#	ifdef STREAMS_ARE_PCM
+	gStreamInfo[whichStream].CreateFileStream( sFileStreamInfo::FILESTREAM_TYPE_ADPCM, true );
+#	else
+	gStreamInfo[whichStream].CreateFileStream( sFileStreamInfo::FILESTREAM_TYPE_WMA, true );
+#	endif
+
+	// The preload parameter indicates that we want to get this stream in a position where it is ready to play
+	// immediately at some later point in time. The m_bOkayToPlay member is used to signal this.
+	if( preload )
+	{
+		// Not okay to start playing until told to do so.
+		gStreamInfo[whichStream].SetOkayToPlay( false );
+	}
+
+	HRESULT hr = gStreamInfo[whichStream].Initialize( ghWADFile, sampleoffset, samplelength, &gStreamFileBuffer[whichStream][0] );
+
+	if( hr == S_OK )
+	{
+		// All started fine.
+		if( p_volume )
+		{
+			PCMAudio_SetStreamVolume( p_volume, whichStream );
+		}
+		return true;
+	}
+	else
+	{
+		// Failed to initialize the stream.
+		gStreamInfo[whichStream].DestroyFileStream();
+	}
+	return false;
+}
+
+
+} // namespace PCM
diff --git a/Code/Gel/Music/Xbox/p_music.h b/Code/Gel/Music/Xbox/p_music.h
new file mode 100644
index 0000000..b2ae987
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_music.h
@@ -0,0 +1,82 @@
+// Audio streaming function prototypes:
+// mjd jan 2001
+
+#ifndef __P_MUSIC_H__
+#define __P_MUSIC_H__
+
+// Needed so music.cpp will build. Grrr.
+#define NUM_STREAMS 3
+
+namespace Pcm
+{
+
+// allows one channel for music, another for audio:
+enum{
+	EXTRA_CHANNEL,
+	MUSIC_CHANNEL,
+};
+
+// Onetime call once upon loading the game...
+void PCMAudio_Init( void );
+
+OVERLAPPED *PCMAudio_GetNextOverlapped( void );
+
+// Call every frame to make sure music and stream buffers are loaded and current status is checked each frame...
+int PCMAudio_Update( void );
+
+// Load a track and start it playing...
+// You wanna loop a track?  Wait until this track is done, and PLAY IT AGAIN SAM!
+bool PCMAudio_PlayMusicTrack( const char *filename );
+bool PCMAudio_PlayMusicTrack( uint32 checksum );
+bool PCMAudio_PlaySoundtrackMusicTrack( int soundtrack, int track );
+//bool PCMAudio_PlayStream( uint32 checksum, int whichStream, float volumeL, float volumeR, float pitch, bool preload = false );
+bool PCMAudio_PlayStream( uint32 checksum, int whichStream, Sfx::sVolume *p_volume, float pitch, bool preload = false );
+bool PCMAudio_StartStream( int whichStream );
+
+// keep song loaded, stop playing it (or continue playing paused song)
+void PCMAudio_Pause( bool pause = true, int ch = MUSIC_CHANNEL );
+
+void PCMAudio_StopMusic( bool waitPlease );
+void PCMAudio_StopStream( int whichStream, bool waitPlease = true );
+void PCMAudio_StopStreams( void );
+
+uint32	PCMAudio_GetFilestreamBufferSize( void );
+
+// Preload streams. By preloading, you can guarantee they will start at a certain time.
+bool PCMAudio_PreLoadStream( uint32 checksum, int whichStream );
+bool PCMAudio_PreLoadStreamDone( int whichStream );
+//bool PCMAudio_StartPreLoadedStream( int whichStream, float volumeL, float volumeR, float pitch );
+bool PCMAudio_StartPreLoadedStream( int whichStream, Sfx::sVolume *p_volume, float pitch );
+
+// Preload music streams. By preloading, you can guarantee they will start at a certain time.
+bool PCMAudio_PreLoadMusicStream( uint32 checksum );
+bool PCMAudio_PreLoadMusicStreamDone( void );
+bool PCMAudio_StartPreLoadedMusicStream( void );
+
+// set the music volume ( 0 to 100 )
+int PCMAudio_SetMusicVolume( float volume );
+bool	PCMAudio_SetStreamPitch( float pitch, int whichStream );
+//int PCMAudio_SetStreamVolumeAndPitch( float volL, float volR, float pitch, int whichStream );
+
+enum{
+	PCM_STATUS_FREE 			= ( 1 << 0 ),
+	PCM_STATUS_PLAYING			= ( 1 << 1 ),
+	PCM_STATUS_LOADING 			= ( 1 << 2 ),
+};
+
+bool PCMAudio_SetStreamVolume( Sfx::sVolume *p_volume, int whichStream );
+
+// Return one of the PCM_STATUS values from above...
+// Return one of the PCM_STATUS values from above...
+int PCMAudio_GetMusicStatus( );
+int PCMAudio_GetStreamStatus( int whichStream );
+
+bool PCMAudio_TrackExists( const char *pTrackName, int ch );
+bool PCMAudio_LoadMusicHeader( const char *nameOfFile );
+bool PCMAudio_LoadStreamHeader( const char *nameOfFile );
+
+uint32 PCMAudio_FindNameFromChecksum( uint32 checksum, int ch );
+	 
+} // namespace PCM
+
+#endif // __P_MUSIC_H__
diff --git a/Code/Gel/Music/Xbox/p_soundtrack.cpp b/Code/Gel/Music/Xbox/p_soundtrack.cpp
new file mode 100644
index 0000000..ac9ffef
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_soundtrack.cpp
@@ -0,0 +1,222 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Game Engine (GEL)	 									**
+**																			**
+**	File name:		p_soundtrack.cpp										**
+**																			**
+**	Created:		11/29/01	-	dc										**
+**																			**
+**	Description:	Xbox specific user soundtrack code						**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "p_music.h"
+#include "p_soundtrack.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace Pcm
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define MAX_SOUNDTRACKS	100
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+XSOUNDTRACK_DATA	soundtrackData[MAX_SOUNDTRACKS];
+bool				initialised = false;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+int gNumSoundtracks = 0;
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int GetNumSoundtracks( void )
+{
+	BOOL				rv;
+	HANDLE				h_strack;
+
+	if( initialised )
+	{
+		return gNumSoundtracks;
+	}
+
+	initialised		= true;
+	gNumSoundtracks	= 0;
+
+	h_strack = XFindFirstSoundtrack( &soundtrackData[gNumSoundtracks] );
+	if( h_strack != INVALID_HANDLE_VALUE )
+	{
+		do
+		{
+			++gNumSoundtracks;
+
+			// Don't go over the maximum (should be a system-limit of 100 anyway).
+			if( gNumSoundtracks >= MAX_SOUNDTRACKS )
+			{
+				break;
+			}
+
+			rv = XFindNextSoundtrack( h_strack, &soundtrackData[gNumSoundtracks] );
+		}	
+		while( rv );
+	}
+
+	return gNumSoundtracks;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const WCHAR* GetSoundtrackName( int soundtrack )
+{
+	if( soundtrack < gNumSoundtracks )
+	{
+		return soundtrackData[soundtrack].szName;
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+unsigned int GetSoundtrackNumSongs( int soundtrack )
+{
+	if( soundtrack < gNumSoundtracks )
+	{
+		return soundtrackData[soundtrack].uSongCount;
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const WCHAR* GetSongName( int soundtrack, int track )
+{
+	static WCHAR wcSongName[MAX_SONG_NAME];
+
+	if( soundtrack < gNumSoundtracks )
+	{
+		// Check the track is within limits.
+		if((UINT)track < soundtrackData[soundtrack].uSongCount )
+		{
+			DWORD dwSongID;
+			DWORD dwSongLength;
+
+			BOOL rv = XGetSoundtrackSongInfo(	soundtrackData[soundtrack].uSoundtrackId,
+												track,
+												&dwSongID,
+												&dwSongLength,
+												wcSongName,
+												MAX_SONG_NAME );
+
+			if( rv )
+			{
+				return wcSongName;
+			}
+		}
+	}
+	return NULL;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+HANDLE GetSoundtrackWMAHandle( int soundtrack, int track )
+{
+	if( soundtrack < gNumSoundtracks )
+	{
+		// Check the track is within limits.
+		if((UINT)track < soundtrackData[soundtrack].uSongCount )
+		{
+			DWORD dwSongID;
+			DWORD dwSongLength;
+
+			BOOL rv = XGetSoundtrackSongInfo(	soundtrackData[soundtrack].uSoundtrackId,
+												track,
+												&dwSongID,
+												&dwSongLength,
+												NULL,
+												0 );
+
+			if( rv )
+			{
+				// Second parameter is true for asynchronous mode reads.
+				HANDLE h_song = XOpenSoundtrackSong( dwSongID, TRUE );
+				return h_song;
+			}
+		}
+	}
+	return INVALID_HANDLE_VALUE;
+}
+
+
+
+
+} // namespace PCM
diff --git a/Code/Gel/Music/Xbox/p_soundtrack.h b/Code/Gel/Music/Xbox/p_soundtrack.h
new file mode 100644
index 0000000..130284a
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_soundtrack.h
@@ -0,0 +1,80 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Game Engine												**
+**																			**
+**	File name:		p_soundtrack.h											**
+**																			**
+**	Created: 		11/29/2001	-	dc										**
+**																			**
+*****************************************************************************/
+
+#ifndef __MODULES_P_SOUNDTRACK_H
+#define __MODULES_P_SOUNDTRACK_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+ 
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Pcm
+{
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+int				GetNumSoundtracks( void );
+const WCHAR*	GetSoundtrackName( int soundtrack );
+unsigned int	GetSoundtrackNumSongs( int soundtrack );
+const WCHAR*	GetSongName( int soundtrack, int track );
+HANDLE			GetSoundtrackWMAHandle( int soundtrack, int track );
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Pcm
+
+#endif	// __MODULES_P_SOUNDTRACK_H
diff --git a/Code/Gel/Music/Xbox/p_wmafilestream.cpp b/Code/Gel/Music/Xbox/p_wmafilestream.cpp
new file mode 100644
index 0000000..0ee5f39
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_wmafilestream.cpp
@@ -0,0 +1,1219 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:																	**
+**																			**
+**	File name:		p_wmafilestream.cpp										**
+**																			**
+**	Created:		01/27/03	-	dc										**
+**																			**
+**	Description:	Xbox specific .wma streaming code						**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "p_music.h"
+#include "p_wmafilestream.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace Pcm
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// Define the source packet size:
+// This value is hard-coded assuming a WMA file of stero, 16bit resolution.  If
+// this value can by dynamically set based on the wma format, keeping in mind
+// that wma needs enough buffer for a minimum of 2048 samples worth of PCM data
+#define WMASTRM_SOURCE_PACKET_BYTES	( 2048 * 2 * 2 )
+
+
+
+// This is a homegrown value which is used in conjunction with the existing
+// DSound packet status values. This indicates a packet which has been written
+// to, but which has not yet been submitted to the renderer. The low-order 24
+// bits contain a timestamp. Packets should be submitted lowest-timestamp first.
+#define XMEDIAPACKET_STATUS_AWAITING_RENDER		0x40000000UL
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+uint32 CALLBACK WMAXMediaObjectDataCallback( LPVOID pContext, uint32 offset, uint32 num_bytes, LPVOID *ppData )
+{
+	Dbg_Assert( pContext != NULL );
+	Dbg_Assert( ppData != NULL );
+
+	CWMAFileStream *p_this = (CWMAFileStream*)pContext;
+	Dbg_Assert(( p_this->m_DecoderCreation == 0 ) || ( p_this->m_DecoderCreation == 1 ));
+
+	*ppData = (BYTE*)( p_this->m_pFileBuffer ) + ( offset % ( 8 * 8192 ));
+
+    // Update current progress.
+    p_this->m_FileBytesProcessed	= offset;
+
+    return num_bytes;
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+//-----------------------------------------------------------------------------
+// Name: CWMAFileStream()
+// Desc: Object constructor.
+//-----------------------------------------------------------------------------
+CWMAFileStream::CWMAFileStream( bool use_3d )
+{
+    m_pSourceFilter		= NULL;
+    m_pRenderFilter		= NULL;
+    m_pvSourceBuffer	= NULL;
+    m_pFileBuffer		= NULL;
+	m_hFile				= INVALID_HANDLE_VALUE;
+	m_hThread			= NULL;
+	m_bUse3D			= use_3d;
+	m_bOkayToPlay		= true;
+
+    for( uint32 i = 0; i < WMASTRM_PACKET_COUNT; i++ )
+	{
+        m_adwPacketStatus[i] = XMEDIAPACKET_STATUS_SUCCESS;
+	}
+
+    m_dwStartingDataOffset	= 0;
+	m_Completed				= false;
+	m_Paused				= false;
+
+	// Grab a pointer to the next overlapped structure.
+	m_pOverlapped			= PCMAudio_GetNextOverlapped();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ~CWMAFileStream()
+// Desc: Object destructor.
+//-----------------------------------------------------------------------------
+CWMAFileStream::~CWMAFileStream()
+{
+	if( m_hThread )
+	{
+		CloseHandle( m_hThread );
+	}
+	
+	// If the file i/o is still active, we need to remove the event and close the file.
+	if(( m_hFile != INVALID_HANDLE_VALUE ) && !m_bUseWAD )
+	{
+		CloseHandle( m_hFile );
+	}
+
+    if( m_pSourceFilter )
+	{
+		m_pSourceFilter->Release();
+	}
+
+    if( m_pRenderFilter )
+	{
+		m_pRenderFilter->Release();
+	}
+
+	if( m_pvSourceBuffer )
+	{
+		delete[] m_pvSourceBuffer;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: AsyncRead()
+// Desc: Called while the async read is in progress. If the read is still
+//       underway, simply returns. If the read has completed, sets up the
+//       next read. If the file has been completely read, closes the file.
+//-----------------------------------------------------------------------------
+void CWMAFileStream::AsyncRead( void )
+{
+	Dbg_Assert( m_hFile != INVALID_HANDLE_VALUE );
+
+    // If paused, do nothing.
+	if( m_Paused )
+	{
+        return;
+    }
+
+    // See if the previous read is complete.
+	uint32	dwBytesTransferred;
+	bool	bIsReadDone = GetOverlappedResult( m_hFile, m_pOverlapped, &dwBytesTransferred, false );
+	uint32	dwLastError = GetLastError();
+
+    // If the read isn't complete, keep going.
+	if( !bIsReadDone )
+	{
+		Dbg_Assert( dwLastError == ERROR_IO_INCOMPLETE );
+		if( dwLastError != ERROR_IO_INCOMPLETE )
+		{
+			m_AwaitingDeletion = true;
+		}
+		return;
+    }
+
+    // If we get here, the read is complete.
+    m_pOverlapped->Offset	+= dwBytesTransferred;
+	m_FileBytesRead			+= dwBytesTransferred;
+
+	// If we just read to the first block in the buffer, copy to the extra block at the end, to ensure jitter-free playback.
+	if(( m_SuccessiveReads & 0x07 ) == 0 )
+	{
+		CopyMemory((BYTE*)m_pFileBuffer + ( 8 * 8192 ), (BYTE*)m_pFileBuffer, 8192 );
+	}
+
+	++m_SuccessiveReads;
+
+#	define BYTES_PER_CALL	8192
+
+	if( dwBytesTransferred < 8192 )
+	{ 
+		// We've reached the end of the file during the call to ReadFile.
+
+        // Close the file (if not using the global WAD file).
+		if( !m_bUseWAD )
+		{
+			bool bSuccess = CloseHandle( m_hFile );
+	        Dbg_Assert( bSuccess );
+		}
+
+        m_hFile = INVALID_HANDLE_VALUE;
+
+        // All done
+		m_ReadComplete = true;
+    }
+    else
+    {
+		if( m_bUseWAD && ( m_FileBytesRead >= (int)m_dwWADLength ))
+		{
+			m_hFile = INVALID_HANDLE_VALUE;
+
+			// All done
+			m_ReadComplete = true;
+			return;
+		}
+		
+		// We still have more data to read. Start another asynchronous read from the file.
+		bool bComplete	= ReadFile( m_hFile, (BYTE*)m_pFileBuffer + ( m_FileBytesRead % ( 8 * 8192 )), BYTES_PER_CALL, NULL, m_pOverlapped );
+		dwLastError		= GetLastError();
+
+		// Deal with hitting EOF (for files that are some exact multiple of 8192 bytes).
+		if( bComplete || ( !bComplete && ( dwLastError == ERROR_HANDLE_EOF )))
+		{
+			// Close the file
+			if( !m_bUseWAD )
+			{
+				bool bSuccess = CloseHandle( m_hFile );
+				Dbg_Assert( bSuccess );
+			}
+			m_hFile = INVALID_HANDLE_VALUE;
+
+			// All done
+			m_ReadComplete = true;
+		}
+		else
+		{
+			Dbg_MsgAssert( bComplete || ( !bComplete && ( dwLastError == ERROR_IO_PENDING )), ( "ReadFile error: %x\n", dwLastError ));
+			if( !bComplete && ( dwLastError != ERROR_IO_PENDING ))
+			{
+				// There was a problem, so shut this stream down.
+				m_AwaitingDeletion = true;
+			}
+		}
+    }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Initialize()
+// Desc: Initializes the wave file streaming subsystem.
+//-----------------------------------------------------------------------------
+HRESULT CWMAFileStream::Initialize( HANDLE h_file, unsigned int offset, unsigned int length, void* fileBuffer )
+{
+    m_dwPercentCompleted = 0;
+    
+	// At this stage we don't want to create the decoder or the stream. We do want to just allocate the read
+	// buffer, and start pulling in the data. Once sufficient data has been grabbed, we can analyze the header
+	// and create the required objects for playback.
+	m_hFile			= h_file;
+	m_bUseWAD		= true;
+	m_dwWADOffset	= offset;	
+	m_dwWADLength	= length;
+	
+	// Grab a 72k (9 * 8k packets) read buffer. Must be uint32 aligned.
+	// The last packet mirrors the first packet, for cases when the decoder reads past the end of the ring buffer.
+	m_pFileBuffer = fileBuffer;
+	Dbg_Assert( uint32( m_pFileBuffer ) % sizeof( uint32 ) == 0 );
+
+	return PostInitialize();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Initialize()
+// Desc: Initializes the wave file streaming subsystem.
+//-----------------------------------------------------------------------------
+HRESULT CWMAFileStream::Initialize( HANDLE h_song, void* fileBuffer )
+{
+    HRESULT			hr;
+
+    m_dwPercentCompleted = 0;
+    
+	// At this stage we don't want to create the decoder or the stream. We do want to just allocate the read
+	// buffer, and start pulling in the data. Once sufficient data has been grabbed, we can analyze the header
+	// and create the required objects for playback.
+	m_hFile = h_song;
+	if( m_hFile == INVALID_HANDLE_VALUE )
+	{
+		hr = HRESULT_FROM_WIN32( GetLastError());
+        return hr;
+    }
+
+	// Grab a 72k (9 * 8k packets) read buffer. Must be uint32 aligned.
+	// The last packet mirrors the first packet, for cases when the decoder reads past the end of the ring buffer.
+	m_pFileBuffer = fileBuffer;
+	Dbg_Assert( uint32( m_pFileBuffer ) % sizeof( uint32 ) == 0 );
+
+	return PostInitialize();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: PostInitialize()
+// Desc: Initialisation stuff following the file creation
+//-----------------------------------------------------------------------------
+HRESULT CWMAFileStream::PostInitialize( void )
+{
+	// Start the asynchronous read from the start of the file.
+	m_FirstRead				= true;
+	m_ReadComplete			= false;
+	m_FileBytesRead			= 0;
+	m_FileBytesProcessed	= 0;
+	m_SuccessiveReads		= 0;
+
+	if( m_bUseWAD )
+	{
+		m_pOverlapped->Offset	= m_dwWADOffset;
+	}
+	else
+	{
+		m_pOverlapped->Offset	= 0;
+	}
+	m_pOverlapped->OffsetHigh	= 0;
+
+	bool bComplete = ReadFile( m_hFile, m_pFileBuffer, BYTES_PER_CALL, NULL, m_pOverlapped );
+	if( !bComplete )
+	{
+		uint32 dwLastError = GetLastError();
+		Dbg_Assert( dwLastError == ERROR_IO_PENDING );
+		if( dwLastError != ERROR_IO_PENDING )
+		{
+			return HRESULT_FROM_WIN32( dwLastError );
+		}
+	}
+	else
+	{
+		m_ReadComplete = true;
+	}
+
+	// That's it for now.
+    return S_OK;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: CreateSourceBuffer()
+// Desc: 
+//-----------------------------------------------------------------------------
+void CWMAFileStream::CreateSourceBuffer( void )
+{
+	// Allocate data buffers. The source buffer holds the CPU decompressed packets ready to submit to the stream.
+	// Explicitly allocate from the bottom up heap.
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().BottomUpHeap());
+	m_pvSourceBuffer = new BYTE[WMASTRM_SOURCE_PACKET_BYTES * WMASTRM_PACKET_COUNT];
+	Mem::Manager::sHandle().PopContext();
+	Dbg_Assert( m_pvSourceBuffer != NULL );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: PreLoadDone()
+// Desc: 
+//-----------------------------------------------------------------------------
+bool CWMAFileStream::PreLoadDone( void )
+{
+	if( m_DecoderCreation == 1 )
+	{
+		if( m_ReadComplete || ( m_FileBytesRead >= ( m_FileBytesProcessed + 8192 )))
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Process()
+// Desc: Performs any work necessary to keep the stream playing.
+//-----------------------------------------------------------------------------
+HRESULT CWMAFileStream::Process( void )
+{
+    HRESULT			hr;
+	DSSTREAMDESC	dssd;
+    uint32			dwPacketIndex;
+    
+	// Do nothing if waiting to die.
+	if( m_AwaitingDeletion )
+	{
+		return S_OK;
+	}
+
+	// Do we need to kick off another read of data? Don't read anymore if we have read ahead more than 32k.
+	if( !m_ReadComplete )
+	{
+		if( m_FileBytesRead > ( m_FileBytesProcessed + 32768 ))
+		{
+//			OutputDebugString( "waiting...\n" );
+		}
+		else
+		{
+			AsyncRead();
+//			OutputDebugString( "reading...\n" );
+		}
+	}
+	else
+	{
+//		OutputDebugString( "complete...\n" );
+	}
+
+	// Has the first block of raw data been read? If so we need to instantiate the playback objects and data buffers.
+	if( m_FirstRead && ( m_FileBytesRead > 1024 ))
+	{
+		if( m_pSourceFilter == NULL )
+		{
+			// Create the thread which will create the in-memory decoder. 
+			m_DecoderCreation = 0;
+
+			HRESULT hr = WmaCreateInMemoryDecoder(	WMAXMediaObjectDataCallback,			// Callback pointer.
+													this,									// Callback context pointer.
+													3,										// Yield rate during decoding.
+													&m_wfxSourceFormat,						// Source format description.
+													(LPXMEDIAOBJECT*)&m_pSourceFilter );	// Pointer to media object.
+			if( FAILED( hr ))
+			{
+				// Signal a failed creation.
+				m_DecoderCreation	= 2;
+				m_pSourceFilter		= NULL;
+			}
+			else
+			{
+				m_DecoderCreation	= 1;
+			}
+			
+			// That's all we can do until the decoder has been instantiated.
+		}
+
+		if( m_DecoderCreation == 0 )
+		{
+			// Still waiting to create decoder.
+			return S_OK;
+		}
+		else if( m_DecoderCreation == 2 )
+		{
+			// Failed to create decoder, just mark for deletion.
+			m_AwaitingDeletion = true;
+			return S_OK;
+		}
+		else if( m_DecoderCreation == 1 )
+		{
+			// Managed to create decoder.
+			Dbg_Assert( m_pSourceFilter != NULL );
+
+			m_FirstRead = false;
+
+			// Create the render (DirectSoundStream) filter.
+			DSMIXBINS			dsmixbins;
+			DSMIXBINVOLUMEPAIR	dsmbvp[7];
+			ZeroMemory( &dssd, sizeof( dssd ));
+
+			dssd.dwFlags					= 0;
+			dssd.dwMaxAttachedPackets		= WMASTRM_PACKET_COUNT;
+			dssd.lpwfxFormat				= &m_wfxSourceFormat;
+			dssd.lpMixBins					= &dsmixbins;
+
+			if( m_bUse3D )
+			{
+				// This is only designed to be fed a mono signal.
+				Dbg_Assert( m_wfxSourceFormat.nChannels == 1 );
+
+				dsmixbins.dwMixBinCount			= 6;
+				dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+				dsmbvp[0].dwMixBin				= DSMIXBIN_3D_FRONT_LEFT;
+				dsmbvp[0].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[1].dwMixBin				= DSMIXBIN_3D_FRONT_RIGHT;
+				dsmbvp[1].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[2].dwMixBin				= DSMIXBIN_3D_BACK_LEFT;
+				dsmbvp[2].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[3].dwMixBin				= DSMIXBIN_3D_BACK_RIGHT;
+				dsmbvp[3].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[4].dwMixBin				= DSMIXBIN_FRONT_CENTER;
+				dsmbvp[4].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+				dsmbvp[5].dwMixBin				= DSMIXBIN_I3DL2;
+				dsmbvp[5].lVolume				= DSBVOLUME_EFFECTIVE_MIN;
+
+				m_Mixbins						= ( 1 << DSMIXBIN_3D_FRONT_LEFT ) |
+												  ( 1 << DSMIXBIN_3D_FRONT_RIGHT ) |
+												  ( 1 << DSMIXBIN_3D_BACK_LEFT ) |
+												  ( 1 << DSMIXBIN_3D_BACK_RIGHT ) |
+												  ( 1 << DSMIXBIN_FRONT_CENTER ) |
+												  ( 1 << DSMIXBIN_I3DL2 );
+				m_NumMixbins					= dsmixbins.dwMixBinCount;
+			}
+			else
+			{
+				// If we are playing a music track, and if proper 5.1 output is selected, we want to feed the music to the left and
+				// right back speakers with a slight echo via mixbins 5 and 6.
+				if( XGetAudioFlags() & XC_AUDIO_FLAGS_ENABLE_AC3 )
+				{
+					// This is only designed to be fed a stereo signal.
+					Dbg_Assert( m_wfxSourceFormat.nChannels == 2 );
+
+					dsmixbins.dwMixBinCount			= 4;
+					dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+					dsmbvp[0].dwMixBin				= DSMIXBIN_FRONT_LEFT;
+					dsmbvp[0].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[1].dwMixBin				= DSMIXBIN_FRONT_RIGHT;
+					dsmbvp[1].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[2].dwMixBin				= DSMIXBIN_FXSEND_5;
+					dsmbvp[2].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[3].dwMixBin				= DSMIXBIN_FXSEND_6;
+					dsmbvp[3].lVolume				= DSBVOLUME_MIN;
+
+					m_Mixbins						= ( 1 << DSMIXBIN_FRONT_LEFT ) |
+													  ( 1 << DSMIXBIN_FRONT_RIGHT ) |
+													  ( 1 << DSMIXBIN_FXSEND_5 ) |
+													  ( 1 << DSMIXBIN_FXSEND_6 );
+					m_NumMixbins					= dsmixbins.dwMixBinCount;
+				}
+				else
+				{
+					// This is only designed to be fed a stereo signal.
+					Dbg_Assert( m_wfxSourceFormat.nChannels == 2 );
+
+					dsmixbins.dwMixBinCount			= 4;
+					dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+					dsmbvp[0].dwMixBin				= DSMIXBIN_FRONT_LEFT;
+					dsmbvp[0].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[1].dwMixBin				= DSMIXBIN_FRONT_RIGHT;
+					dsmbvp[1].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[2].dwMixBin				= DSMIXBIN_BACK_LEFT;
+					dsmbvp[2].lVolume				= DSBVOLUME_MIN;
+					dsmbvp[3].dwMixBin				= DSMIXBIN_BACK_RIGHT;
+					dsmbvp[3].lVolume				= DSBVOLUME_MIN;
+
+					m_Mixbins						= ( 1 << DSMIXBIN_FRONT_LEFT ) |
+													  ( 1 << DSMIXBIN_FRONT_RIGHT ) |
+													  ( 1 << DSMIXBIN_BACK_LEFT ) |
+													  ( 1 << DSMIXBIN_BACK_RIGHT );
+					m_NumMixbins					= dsmixbins.dwMixBinCount;
+				}
+			}
+
+			hr = DirectSoundCreateStream( &dssd, &m_pRenderFilter );
+			if( FAILED( hr ))
+			{
+				Dbg_Assert( 0 );
+				m_pRenderFilter		= NULL;
+				m_AwaitingDeletion	= true;
+				return S_OK;
+			}
+
+			// Set deferred volume if present.
+			SetDeferredVolume();
+
+			// Handle deferred pause.
+			if( m_Paused )
+			{
+				m_pRenderFilter->Pause( DSSTREAMPAUSE_PAUSE );
+			}
+		}
+	}
+
+	// We only want to do processing when there is sufficient data available.
+	if( m_ReadComplete || ( m_FileBytesRead >= ( m_FileBytesProcessed + 8192 )))
+	{
+		if( !m_Completed && m_pSourceFilter && m_pRenderFilter )
+		{
+			// Find a free packet. If there's none free, we don't have anything to do.
+			if( FindFreePacket( &dwPacketIndex ))
+			{
+				// Read from the source filter.
+				hr = ProcessSource( dwPacketIndex );
+				if( FAILED( hr ) )
+				{
+					return hr;
+				}
+			}
+
+			// Don't want to submit if we're paused, or we're flagged not to play. (Flagging is used to allow
+			// the stream to be spooled up ready to play instantly when triggered).
+			if(( !m_Paused ) && m_bOkayToPlay )
+			{
+				if( FindRenderablePacket( &dwPacketIndex ))
+				{
+					// Send the data to the renderer
+					hr = ProcessRenderer( dwPacketIndex );
+					if( FAILED( hr ))
+					{
+						 return hr;
+					}
+				}
+			}
+		}
+	}
+    return S_OK;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: FindFreePacket()
+// Desc: Finds a render packet available for processing.
+//-----------------------------------------------------------------------------
+bool CWMAFileStream::FindFreePacket( uint32* pdwPacketIndex )
+{
+    for( uint32 dwPacketIndex = 0; dwPacketIndex < WMASTRM_PACKET_COUNT; ++dwPacketIndex )
+    {
+		if(( m_adwPacketStatus[dwPacketIndex] != XMEDIAPACKET_STATUS_PENDING ) &&
+		   (( m_adwPacketStatus[dwPacketIndex] & XMEDIAPACKET_STATUS_AWAITING_RENDER ) != XMEDIAPACKET_STATUS_AWAITING_RENDER ))
+        {
+            if( pdwPacketIndex )
+			{
+				*pdwPacketIndex = dwPacketIndex;
+
+				// Mark this packet as awaiting rendering.
+				uint32 timestamp = (uint32)Tmr::GetVblanks();
+				m_adwPacketStatus[dwPacketIndex] = XMEDIAPACKET_STATUS_AWAITING_RENDER | ( timestamp & 0xFFFFFFUL );
+			}
+            return true;
+        }
+    }
+    return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: FindFreePacket()
+// Desc: Finds a render packet available for rendering.
+//-----------------------------------------------------------------------------
+bool CWMAFileStream::FindRenderablePacket( uint32* pdwPacketIndex )
+{
+	// Found nothing yet.
+	uint32 lowest_timestamp	= 0xFFFFFFUL;
+	uint32 lowest_index		= WMASTRM_PACKET_COUNT;
+
+	for( uint32 dwPacketIndex = 0; dwPacketIndex < WMASTRM_PACKET_COUNT; ++dwPacketIndex )
+    {
+		if(( m_adwPacketStatus[dwPacketIndex] & XMEDIAPACKET_STATUS_AWAITING_RENDER ) == XMEDIAPACKET_STATUS_AWAITING_RENDER )
+        {
+			uint32 timestamp = m_adwPacketStatus[dwPacketIndex] & 0xFFFFFFUL;
+			if( timestamp < lowest_timestamp )
+			{
+				lowest_timestamp	= timestamp;
+				lowest_index		= dwPacketIndex;
+			}
+		}
+    }
+
+	// If we found a packet, return its index.
+	if( lowest_index < WMASTRM_PACKET_COUNT )
+	{
+		if( pdwPacketIndex )
+		{
+			*pdwPacketIndex = lowest_index;
+		}
+		return true;
+	}
+
+	// No packets awaiting rendering.
+	return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ProcessSource()
+// Desc: Reads data from the source filter.
+//-----------------------------------------------------------------------------
+HRESULT CWMAFileStream::ProcessSource( uint32 dwPacketIndex )
+{
+    uint32        dwTotalSourceUsed   = 0;
+    uint32        dwSourceUsed;
+    XMEDIAPACKET xmp;
+    HRESULT      hr;
+    
+	if( m_pvSourceBuffer == NULL )
+	{
+		CreateSourceBuffer();
+	}
+    
+	// We're going to read a full packet's worth of data into the source buffer.
+    ZeroMemory( &xmp, sizeof( xmp ));
+    xmp.pvBuffer         = (BYTE*)m_pvSourceBuffer + (dwPacketIndex * WMASTRM_SOURCE_PACKET_BYTES);
+    xmp.dwMaxSize        = WMASTRM_SOURCE_PACKET_BYTES;
+    xmp.pdwCompletedSize = &dwSourceUsed;
+
+	// Read from the source.
+	hr = m_pSourceFilter->Process( NULL, &xmp );
+	if( FAILED( hr ))
+	{
+		return hr;
+	}
+
+	// Add the amount read to the total
+	dwTotalSourceUsed += dwSourceUsed;
+
+	// If we read less than the amount requested, it's because we hit the end of the file.
+    if( dwSourceUsed < xmp.dwMaxSize )
+	{
+		// Set completion flag.
+		m_Completed = true;
+
+		// Zero remaining part of packet.
+		xmp.pvBuffer  = (BYTE*)xmp.pvBuffer + dwSourceUsed;
+		xmp.dwMaxSize = xmp.dwMaxSize - dwSourceUsed;
+		ZeroMemory( xmp.pvBuffer, xmp.dwMaxSize );
+	}
+
+    return S_OK;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ProcessRenderer()
+// Desc: Sends data to the renderer.
+//-----------------------------------------------------------------------------
+HRESULT CWMAFileStream::ProcessRenderer( uint32 dwPacketIndex )
+{
+    XMEDIAPACKET xmp;
+    HRESULT      hr;
+
+	Dbg_Assert( m_pvSourceBuffer != NULL );
+
+	// There's a full packet's worth of data ready for us to send to the renderer.  We want to track the status
+	// of this packet since the render filter is asychronous and we need to know when the packet is completed.
+    ZeroMemory( &xmp, sizeof( xmp ));
+    xmp.pvBuffer  = (BYTE*)m_pvSourceBuffer + ( dwPacketIndex * WMASTRM_SOURCE_PACKET_BYTES );
+    xmp.dwMaxSize = WMASTRM_SOURCE_PACKET_BYTES;
+    xmp.pdwStatus = &m_adwPacketStatus[dwPacketIndex];
+
+	if( m_Completed )
+	{
+		// Store index of last packet, since we will need to test the status of this for proper completion test.
+		m_LastPacket = dwPacketIndex;
+	}
+
+    hr = m_pRenderFilter->Process( &xmp, NULL );
+
+	if( m_Completed )
+	{
+		// Tell the renderer not to expect any more data.
+		m_pRenderFilter->Discontinuity();
+	}
+	
+	if( FAILED( hr ))
+	{
+		return hr;
+	}
+	
+	return S_OK;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Pause
+// Desc: Pauses and resumes stream playback
+//-----------------------------------------------------------------------------
+void CWMAFileStream::Pause( uint32 dwPause )
+{
+	m_Paused = ( dwPause > 0 );
+
+	// Possible that the render filter hasn't been created yet.
+	if( m_pRenderFilter )
+	{
+		m_pRenderFilter->Pause(( dwPause > 0 ) ? DSSTREAMPAUSE_PAUSE : DSSTREAMPAUSE_RESUME );
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CWMAFileStream::SetVolume( float volume )
+{
+	if( m_pRenderFilter )
+	{
+		int i_volume		= DSBVOLUME_EFFECTIVE_MIN;
+		int i_volume_rear	= DSBVOLUME_EFFECTIVE_MIN;
+		if( volume > 0.0f )
+		{
+			// Figure base volume.
+			float attenuation	= 20.0f * log10f( volume * 0.01f );
+			i_volume			= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+			if( i_volume < DSBVOLUME_EFFECTIVE_MIN )
+				i_volume = DSBVOLUME_EFFECTIVE_MIN;
+			else if( i_volume > DSBVOLUME_MAX )
+				i_volume = DSBVOLUME_MAX;
+
+			// Also figure half volume, in case we are routing to the back speakers.
+			attenuation			= 20.0f * log10f( volume * 0.5f * 0.01f );
+			i_volume_rear		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+			if( i_volume_rear < DSBVOLUME_EFFECTIVE_MIN )
+				i_volume_rear = DSBVOLUME_EFFECTIVE_MIN;
+			else if( i_volume_rear > DSBVOLUME_MAX )
+				i_volume_rear = DSBVOLUME_MAX;
+		}
+		
+		// Set individual mixbins for panning.
+		DSMIXBINS			dsmixbins;
+		DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+
+		dsmixbins.dwMixBinCount			= 0;
+		dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+
+		if( i_volume > DSBVOLUME_EFFECTIVE_MIN )
+		{
+			// Set the volume up depending on how the initial mixbins were set up.
+			int mbbf = 0;
+			for( uint32 mb = 0; mb < m_NumMixbins; ++mb )
+			{
+				while(( m_Mixbins & ( 1 << mbbf )) == 0 )
+				{
+					++mbbf;
+					Dbg_Assert( mbbf < 32 );
+				}
+				dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= mbbf;
+
+				// For rear speakers (or mixbins that route to the rear), use half volume.
+				if(( mbbf == DSMIXBIN_FXSEND_5 ) || ( mbbf == DSMIXBIN_FXSEND_6 ) || ( mbbf == DSMIXBIN_BACK_LEFT ) || ( mbbf == DSMIXBIN_BACK_RIGHT ))
+				{
+					dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volume_rear;
+				}
+				else
+				{
+					dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volume;
+				}
+				++dsmixbins.dwMixBinCount;
+				++mbbf;
+			}
+		}
+
+		// Set all speaker volumes.
+		m_pRenderFilter->SetMixBinVolumes( &dsmixbins );
+
+		// Set overall buffer volume.
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MAX );
+		}
+		else
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MIN );
+		}
+	}
+	else
+	{
+		m_SetDeferredVolume = true;
+		m_DeferredVolume[0]	= volume;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CWMAFileStream::SetVolume( float volL, float volR )
+{
+	if( m_pRenderFilter )
+	{
+		// This array will hold individual volumes for the five speakers.
+		// In order, they are: front left, center, front right, rear right, rear left.
+		float	volumes[5];
+		int		i_volumes[5], max_i_volume;
+		memset( volumes, 0, sizeof( float ) * 5 );
+		
+		if(( volL == 0.0f ) && ( volR == 0.0f ))
+		{
+			// Pointless doing any more work.
+		}
+		else
+		{
+			// Get the length of the vector here which will be used to multiply out the normalised speaker volumes.
+			Mth::Vector test( fabsf( volL ), fabsf( volR ), 0.0f, 0.0f );
+			float amplitude = test.Length();
+
+			// Look just at the normalized right component to figure the sound angle from Matt's calculations.
+			test.Normalize();
+
+			float angle;
+			angle	= asinf( test[Y] );
+			angle	= ( angle * 2.0f ) - ( Mth::PI * 0.5f );
+			angle	= ( volL < 0.0f ) ? ( Mth::PI - angle ) : angle;
+		
+			// Now figure volumes based on speaker coverage.
+			angle	= Mth::RadToDeg( angle );
+		
+			Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+			sfx_manager->Get5ChannelMultipliers( angle, &volumes[0] );
+
+			// Now readjust the relative values...
+			for( int v = 0; v < 5; ++v )
+			{
+				// Scale back up to original amplitude.
+				volumes[v] *= amplitude;
+
+				if( volumes[v] > 100.0f )
+					volumes[v] = 100.0f;
+			}
+		}
+		
+		// Now figure the attenuation of the sound. To convert to a decibel value, figure the ratio of requested
+		// volume versus max volume, then calculate the log10 and multiply by (10 * 2). (The 2 is because sound
+		// power varies as square of pressure, and squaring doubles the log value).
+		max_i_volume = DSBVOLUME_EFFECTIVE_MIN;
+		for( int v = 0; v < 5; ++v )
+		{
+			if( volumes[v] > 0.0f )
+			{
+				float attenuation	= 20.0f * log10f( volumes[v] * 0.01f );
+				i_volumes[v]		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+				if( i_volumes[v] < DSBVOLUME_EFFECTIVE_MIN )
+					i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+				else if( i_volumes[v] > DSBVOLUME_MAX )
+					i_volumes[v] = DSBVOLUME_MAX;
+
+				if( i_volumes[v] > max_i_volume )
+					max_i_volume = i_volumes[v];
+			}
+			else
+			{
+				i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+			}
+		}
+		
+		// Set individual mixbins for panning.
+		DSMIXBINS			dsmixbins;
+		DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+
+		dsmixbins.dwMixBinCount			= 0;
+		dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+
+		if( i_volumes[0] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[0];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[1] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[1];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[2] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[2];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[3] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[3];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[4] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_FRONT_CENTER;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[4];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_I3DL2;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= DSBVOLUME_MAX;
+            dsmixbins.dwMixBinCount++;
+		}
+
+		// Set all speaker volumes.
+		m_pRenderFilter->SetMixBinVolumes( &dsmixbins );
+
+		// Set overall buffer volume.
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MAX );
+		}
+		else
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MIN );
+		}
+	}
+	else
+	{
+		m_SetDeferredVolumeLR	= true;
+		m_DeferredVolume[0]		= volL;
+		m_DeferredVolume[1]		= volR;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CWMAFileStream::SetVolume( float v0, float v1, float v2, float v3, float v4 )
+{
+	if( m_pRenderFilter )
+	{
+		float volumes[5];
+		volumes[0] = ( v0 > 100.0f ) ? 100.0f : v0;
+		volumes[1] = ( v1 > 100.0f ) ? 100.0f : v1;
+		volumes[2] = ( v2 > 100.0f ) ? 100.0f : v2;
+		volumes[3] = ( v3 > 100.0f ) ? 100.0f : v3;
+		volumes[4] = ( v4 > 100.0f ) ? 100.0f : v4;
+
+		int i_volumes[5], max_i_volume;
+
+		// Now figure the attenuation of the sound. To convert to a decibel value, figure the ratio of requested
+		// volume versus max volume, then calculate the log10 and multiply by (10 * 2). (The 2 is because sound
+		// power varies as square of pressure, and squaring doubles the log value).
+		max_i_volume = DSBVOLUME_EFFECTIVE_MIN;
+		for( int v = 0; v < 5; ++v )
+		{
+			if( volumes[v] > 0.0f )
+			{
+				float attenuation	= 20.0f * log10f( volumes[v] * 0.01f );
+				i_volumes[v]		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+				if( i_volumes[v] < DSBVOLUME_EFFECTIVE_MIN )
+					i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+				else if( i_volumes[v] > DSBVOLUME_MAX )
+					i_volumes[v] = DSBVOLUME_MAX;
+
+				if( i_volumes[v] > max_i_volume )
+					max_i_volume = i_volumes[v];
+			}
+			else
+			{
+				i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+			}
+		}
+		
+		// Set individual mixbins for panning.
+		DSMIXBINS			dsmixbins;
+		DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+
+		dsmixbins.dwMixBinCount			= 0;
+		dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+
+		if( i_volumes[0] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[0];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[1] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[1];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[2] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[2];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[3] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[3];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( i_volumes[4] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_FRONT_CENTER;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[4];
+            dsmixbins.dwMixBinCount++;
+		}
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_I3DL2;
+			dsmbvp[dsmixbins.dwMixBinCount].lVolume		= DSBVOLUME_MAX;
+			dsmixbins.dwMixBinCount++;
+		}
+
+		// Set all speaker volumes.
+		m_pRenderFilter->SetMixBinVolumes( &dsmixbins );
+
+		// Set overall buffer volume.
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MAX );
+		}
+		else
+		{
+			m_pRenderFilter->SetVolume( DSBVOLUME_MIN );
+		}
+	}
+	else
+	{
+		m_SetDeferredVolume5Channel	= true;
+		m_DeferredVolume[0]			= v0;
+		m_DeferredVolume[1]			= v1;
+		m_DeferredVolume[2]			= v2;
+		m_DeferredVolume[3]			= v3;
+		m_DeferredVolume[4]			= v4;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetDeferredVolume
+// Desc: 
+//-----------------------------------------------------------------------------
+void CWMAFileStream::SetDeferredVolume( void )
+{
+	if( m_SetDeferredVolume )
+	{
+		m_SetDeferredVolume = false;
+		SetVolume( m_DeferredVolume[0] );
+	}
+
+	if( m_SetDeferredVolumeLR )
+	{
+		m_SetDeferredVolumeLR = false;
+		SetVolume( m_DeferredVolume[0], m_DeferredVolume[1] );
+	}
+
+	if( m_SetDeferredVolume5Channel )
+	{
+		m_SetDeferredVolume5Channel = false;
+		SetVolume( m_DeferredVolume[0], m_DeferredVolume[1], m_DeferredVolume[2], m_DeferredVolume[3], m_DeferredVolume[4] );
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: IsSafeToDelete
+// Desc: 
+//-----------------------------------------------------------------------------
+bool CWMAFileStream::IsSafeToDelete( void )
+{
+	return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: WMAFileStreamThreadProc
+//-----------------------------------------------------------------------------
+uint32 WINAPI WMAFileStreamThreadProc( LPVOID lpParameter )
+{
+	CWMAFileStream*	p_filestream = (CWMAFileStream*)lpParameter;
+
+	Dbg_Assert( p_filestream->m_DecoderCreation == 0 );
+
+	// Create the memory-based XMO.
+	HRESULT hr = WmaCreateInMemoryDecoder(	WMAXMediaObjectDataCallback,						// Callback pointer.
+											p_filestream,										// Callback context pointer.
+											3,													// Yield rate during decoding.
+											&p_filestream->m_wfxSourceFormat,					// Source format description.
+											(LPXMEDIAOBJECT*)&p_filestream->m_pSourceFilter );	// Pointer to media object.
+	if( FAILED( hr ))
+	{
+		// Signal a failed creation.
+		p_filestream->m_DecoderCreation	= 2;
+		p_filestream->m_pSourceFilter	= NULL;
+	}
+	else
+	{
+		p_filestream->m_DecoderCreation	= 1;
+	}
+
+	// Terminate the thread.
+	return 0;
+}
+
+
+
+
+} // namespace PCM
diff --git a/Code/Gel/Music/Xbox/p_wmafilestream.h b/Code/Gel/Music/Xbox/p_wmafilestream.h
new file mode 100644
index 0000000..86670da
--- /dev/null
+++ b/Code/Gel/Music/Xbox/p_wmafilestream.h
@@ -0,0 +1,162 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Game Engine												**
+**																			**
+**	File name:		p_wmafilesteam.h										**
+**																			**
+**	Created: 		01/27/2003	-	dc										**
+**																			**
+*****************************************************************************/
+
+#ifndef __P_WMAFILESTREAM_H
+#define __P_WMAFILESTREAM_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// Define the maximum amount of packets we will ever submit to the renderer.
+#define WMASTRM_PACKET_COUNT		8
+
+namespace Pcm
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*																  */
+/* WMA file streaming object, designed for fully asynchronous	  */
+/* streaming from disc.											  */
+/* Note that WMA decompression does incur a CPU overhead.		  */
+/*																  */
+/******************************************************************/
+class CWMAFileStream : public Spt::Class
+{
+	private:
+
+	HANDLE				m_hFile;
+	HANDLE				m_hThread;
+	OVERLAPPED*			m_pOverlapped;							// OVERLAPPED structure for asynchronous file access.
+	bool				m_FirstRead;							// Flag to indicate first request for more data to be streamed from disc.
+	bool				m_ReadComplete;
+	int					m_SuccessiveReads;						// Counts how many read operations performed in total.
+	bool				m_bUseWAD;
+	bool				m_bUse3D;								// Sets whether 2D or 3D mixbins are set up.
+	uint32				m_dwWADOffset;	
+	uint32				m_dwWADLength;
+
+	public:
+
+    IDirectSoundStream* m_pRenderFilter;                         // Render (DirectSoundStream) filter
+    LPVOID              m_pvSourceBuffer;                        // Source filter data buffer
+    LPVOID              m_pvRenderBuffer;                        // Render filter data buffer
+    uint32				m_adwPacketStatus[WMASTRM_PACKET_COUNT]; // Packet status array
+    uint32				m_dwStartingDataOffset;                  // Offset into wma file where data begins.
+    uint32				m_dwPercentCompleted;					 // Percent completed
+	bool				m_Paused;
+	bool				m_Completed;							// For single-shot, indicates loading of final packet to renderer is completed
+    uint32				m_LastPacket;							// Last packet array index
+	bool				m_SetDeferredVolume;
+	bool				m_SetDeferredVolumeLR;
+	bool				m_SetDeferredVolume5Channel;
+	bool				m_bOkayToPlay;							// Used when exact syncing is required - will load buffers but won't start decompression until this flag is true.
+	float				m_DeferredVolume[5];
+	uint32				m_Mixbins;								// Bitfield indicating which mixbins are active for this buffer.
+	uint32				m_NumMixbins;
+
+    // Packet processing
+    bool				FindFreePacket( uint32* pdwPacketIndex );
+	bool				FindRenderablePacket( uint32* pdwPacketIndex );
+    HRESULT				ProcessSource( uint32 dwPacketIndex );
+    HRESULT				ProcessRenderer( uint32 dwPacketIndex );
+
+	WAVEFORMATEX		m_wfxSourceFormat;
+    XFileMediaObject*   m_pSourceFilter;							// Source (wave file) filter
+	LPVOID				m_pFileBuffer;								// Buffer for async read of raw data.
+	int					m_FileBytesRead;							// Total number of raw bytes read from disc.
+	int					m_FileBytesProcessed;						// Total number of raw bytes processed by XMO.
+	int					m_DecoderCreation;
+	bool				m_AwaitingDeletion;
+
+    // Processing
+    HRESULT				Process();
+
+    // Initialization
+    HRESULT				Initialize( HANDLE h_file, unsigned int offset, unsigned int length, void* fileBuffer );
+    HRESULT				Initialize( HANDLE h_file, void* fileBuffer );
+	HRESULT				PostInitialize( void );
+	void				CreateSourceBuffer( void );
+    
+    // Play control
+    void				Pause( uint32 dwPause );
+	void				SetVolume( float volume );
+	void				SetVolume( float volumeL, float volumeR );
+	void				SetVolume( float v0, float v1, float v2, float v3, float v4 );
+	void				SetDeferredVolume( void );
+
+	// Query
+	IDirectSoundStream*	GetSoundStream( void )					{ return m_pRenderFilter; }
+	bool				IsCompleted( void )						{ return ( m_Completed && ( XMEDIAPACKET_STATUS_PENDING != m_adwPacketStatus[m_LastPacket] )); }
+	bool				IsSafeToDelete( void );
+	bool				PreLoadDone( void );
+
+	// Asynchronous stuff.
+	void				AsyncRead( void );
+
+
+    CWMAFileStream( bool use_3d = false );
+    ~CWMAFileStream();
+};
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+	
+	
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Pcm
+
+#endif	// __P_WMAFILESTREAM_H
+
+
+
+
diff --git a/Code/Gel/Music/music.cpp b/Code/Gel/Music/music.cpp
new file mode 100644
index 0000000..a5185d9
--- /dev/null
+++ b/Code/Gel/Music/music.cpp
@@ -0,0 +1,2539 @@
+/*  
+	Music.cpp -- highest level
+	p_music.cpp -- platform specific
+	pcm*.c --  the code that runs on the IOP.
+	
+	This module should actually be renamed now, as it isn't just for
+	music any more!
+
+	This module should be called Streaming.cpp.
+	
+	This module handles streaming music, and soundfx.
+	
+	The stream data is read into IOP memory from the DVD, into a
+	double buffer in IOP memory, about once every three seconds.
+	
+	The IOP chip has a thread that is running on a timer interrupt (see the pcm
+	code, which compiles as an IOP module EzPcm.irx).  60 times per second,
+	this thread updates small areas of memory on the SPU2 chip -- double
+	buffers for each stream that need updating approximately every second
+	and a half (or longer if the pitch has been turned down lower).
+
+	These voices work just like the other voices for soundfx, except that they
+	loop over a small area that is updated with the sound data streaming off
+	the DVD.  You can change the pitch and volume of these voices on the fly
+	just like soundFX, so streaming sounds can be adjusted positionally over time.
+
+	Oh, yeah, just one more thing:  yo mamma, yo pappa, yo greasy bald gradma!
+*/
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#ifdef __PLAT_NGPS__
+#include 
+#include 
+#elif defined( __PLAT_XBOX__ )
+#include 
+#include 
+#elif defined( __PLAT_NGC__ )
+#include 
+#include 
+#endif
+#include 
+#include 
+#include 
+
+#define TEST_FROM_CD 0
+#define WAIT_AFTER_STOP_STREAM		0		// Set to 1 if we need to wait for stream to clear after stopping a stream
+
+namespace Pcm
+{
+
+static bool music_hed_there = true;		// assume music and streams are there to start with, so we can load the hed
+static bool streams_hed_there = true;	// if hed is not there, then these get set to false
+										// which will disable all music and/or streams.
+
+bool NoMusicPlease()
+{
+	#if NO_MUSIC_PLEASE
+	return true;
+	#else
+	// Cannot have music if building for proview, because there is not enough IOP memory
+	// (ProView uses up some IOP memory with it's monitor)
+	if (Config::GetHardware()==Config::HARDWARE_PS2_PROVIEW)
+	{
+		return true;
+	}
+	
+	return !music_hed_there;	
+	#endif
+}
+
+bool StreamsDisabled()
+{
+	#if DISABLE_STREAMS
+	return true;
+	#else
+	// Cannot have music if building for proview, because there is not enough IOP memory
+	// (ProView uses up some IOP memory with it's monitor)
+	if (Config::GetHardware()==Config::HARDWARE_PS2_PROVIEW)
+	{
+		return true;
+	}
+	
+	return !streams_hed_there;	
+	#endif
+}
+
+// prototypes...
+bool TrackIsPlaying( int whichList, int whichTrack );
+
+static int gLastStreamChannelPlayed = 0;
+
+CurrentStreamInfo gCurrentStreamInfo[ NUM_STREAMS ];
+
+TrackTitle PermTrackTitle[ MAX_NUM_TRACKS ];
+
+// Static globals
+static int          current_music_track = 0;
+static TrackList	gTrackLists[ NUM_TRACKLISTS ];
+static int			gCurrentTrackList = TRACKLIST_PERM;  // start out playing songs...
+static char			gTrackName[ MAX_TRACKNAME_STRING_LENGTH ];
+static float		gMusicVolume = DEFAULT_MUSIC_VOLUME;
+static float		gMusicStreamVolume = DEFAULT_MUSIC_STREAM_VOLUME;
+static bool 		gMusicInRandomMode = true;
+static bool			gMusicLooping = false;
+static int			sCounter = 1;
+static bool			gPcmInitialized = false;
+static int			gNumStreams = 0;
+static Obj::CStreamComponent	*gpStreamingObj[ NUM_STREAMS ]; // should have as many of these as streams...
+
+// Music stream data
+
+static EMusicStreamType	gMusicStreamType = MUSIC_STREAM_TYPE_NONE;
+#if WAIT_AFTER_STOP_STREAM
+static uint32 			gMusicStreamChecksum;	// Checksum of music stream
+static float 			gMusicStreamVolume;		// Volume of music stream
+static bool 			gMusicStreamWaitingToStart;	// true if we are still waiting for the channel to become free
+#endif // WAIT_AFTER_STOP_STREAM
+
+// Limit is actually 500 ...
+#define			MAX_USER_SONGS					600
+
+#ifdef __PLAT_XBOX__
+static bool		s_xbox_play_user_soundtracks	= false;
+static int		s_xbox_user_soundtrack			= 0;
+static uint32	s_xbox_user_soundtrack_song		= 0;
+static bool		s_xbox_user_soundtrack_random	= false;
+static uint32	s_xbox_random_index				= 0;
+
+static int		sp_xbox_randomized_songs[MAX_USER_SONGS];
+#endif
+
+static bool     shuffle_random_songs=true;
+static int      num_random_songs = 1;
+static int      random_song_index = 0;
+static int		sp_random_song_order[MAX_USER_SONGS];
+
+
+// Stream ID functions
+bool IDAvailable(uint32 id)
+{
+	for ( int i = 0; i < NUM_STREAMS; i++ )
+	{
+		if ( (gCurrentStreamInfo[ i ].uniqueID == id) && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[ i ].voice) != PCM_STATUS_FREE))
+		{
+			return false;
+		}
+	}
+
+	return true;
+}
+
+int GetChannelFromID(uint32 id, bool assert_if_duplicate = true)
+{
+	int i;
+
+	for ( i = 0; i < NUM_STREAMS; i++ )
+	{
+		if ( (gCurrentStreamInfo[ i ].uniqueID == id) && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[ i ].voice) != PCM_STATUS_FREE))
+		{
+			return i;
+		}
+	}
+
+	for ( i = 0; i < NUM_STREAMS; i++ )
+	{
+		if ( (gCurrentStreamInfo[ i ].controlID == id) && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[ i ].voice) != PCM_STATUS_FREE))
+		{
+			Dbg_MsgAssert(!assert_if_duplicate, ("Trying to control duplicate stream %s by checksum", Script::FindChecksumName(id)));
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+uint32 GenerateUniqueID(uint32 id)
+{
+	// Keep incrementing ID until one works.
+	while (!IDAvailable(id))
+		id++;
+
+	return id;
+}
+
+void StopMusic( void )
+{
+	if (NoMusicPlease()) return;
+	
+	// the counter has to reach SONG_UPDATE_INTERVAL before a new song is played...
+	// so resetting the counter keeps a new song from playing right away.
+	sCounter = 1;
+	gMusicStreamType = MUSIC_STREAM_TYPE_NONE;		// In case we were in this mode
+	PCMAudio_StopMusic( true );
+}
+
+// If a channel is specified, stop a specific stream (might be a streaming soundFX on an object that's getting
+// destroyed, for example).
+// If channel parameter is -1, stop all non-music streams!
+void StopStreams( int channel )
+{
+	Replay::WriteStopStream(channel);
+	if (StreamsDisabled()) return;
+	
+	Dbg_MsgAssert( ( channel >= -1 ) && ( channel < NUM_STREAMS ), ( "Invalid stream channel %d", channel ) );
+	if ( channel == -1 )
+	{
+		PCMAudio_StopStreams( );
+	}
+	else
+	{
+		PCMAudio_StopStream( channel );
+	}
+}
+
+// if uniqueID isn't specified, stop whatever stream is playing!
+void StopStreamFromID( uint32 streamID )
+{
+	if (StreamsDisabled()) return;
+	
+	int channel = GetChannelFromID(streamID);
+
+	if (channel >= 0)
+	{
+		PCMAudio_StopStream( channel, true );
+		streamID = gCurrentStreamInfo[ channel ].uniqueID;		// change over to uniqueID
+	}
+
+	for ( int i = 0; i < NUM_STREAMS; i++ )
+	{
+		if ( gpStreamingObj[ i ] )
+		{
+			for ( int j = 0; j < MAX_STREAMS_PER_OBJECT; j++ )
+			{
+				if ( gpStreamingObj[ i ]->mStreamingID[ j ] == streamID )
+				{
+					gpStreamingObj[ i ]->mStreamingID[ j ] = 0;
+					gpStreamingObj[ i ] = NULL;
+					return;
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool SetStreamVolumeFromID( uint32 streamID, Sfx::sVolume *p_volume )
+{
+	if (StreamsDisabled()) return true;
+	
+	int channel = GetChannelFromID(streamID);
+
+	if (channel >= 0)
+	{
+#		ifdef __PLAT_XBOX__
+		PCMAudio_SetStreamVolume( p_volume, channel );
+#		else
+		PCMAudio_SetStreamVolume( p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), channel );
+#		endif
+
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool SetStreamPitchFromID( uint32 streamID, float pitch )
+{
+	if (StreamsDisabled()) return true;
+	
+	int channel = GetChannelFromID(streamID);
+
+	if (channel >= 0)
+	{
+		PCMAudio_SetStreamPitch( pitch, channel );
+
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_XBOX__
+static void sGenerateRandomSongOrder( void )
+{
+	int num_songs=Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack );
+
+	if( num_songs == 0 )
+	{
+		// Nothing to do.
+		return;
+	}
+		
+	// Limit to MAX_USER_SONGS to prevent any buffer overwriting.
+	if (num_songs>MAX_USER_SONGS)
+	{
+		num_songs=MAX_USER_SONGS;
+	}	
+
+	// Remember the last song in the previous order, so we can make sure that
+	// the first song in the new order is different from it.
+	// Note: OK if the last song was just an uninitialised random value, won't cause any problems.
+	int last_song=sp_xbox_randomized_songs[num_songs-1];
+	
+	// Initialise the order to be 0,1,2,3 ... etc
+	for (int i=0; i1)
+		{
+			// Make b not able to equal 0, so that we don't swap the first with itself.
+			b=1+Mth::Rnd(num_songs-1);
+		}
+		else
+		{
+			// Unless there is only one song, in which case just swap it with itself. No point but what the heck.
+			b=0;
+		}
+		
+		// Do the swap.
+		int temp=sp_xbox_randomized_songs[a];
+		sp_xbox_randomized_songs[a]=sp_xbox_randomized_songs[b];
+		sp_xbox_randomized_songs[b]=temp;
+	}
+}
+#endif // __PLAT_XBOX__
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void UseUserSoundtrack( int soundtrack )
+{
+#	ifdef __PLAT_XBOX__
+
+	Dbg_MsgAssert(soundtrack>=0 && soundtrackAddComponent(Script::GenerateCRC("UserSoundtrackIndex"),ESYMBOLTYPE_INTEGER,s_xbox_user_soundtrack);
+
+		char p_buf[100];
+		const WCHAR* p_soundtrack_name_wide=Pcm::GetSoundtrackName(s_xbox_user_soundtrack);
+		if (p_soundtrack_name_wide)
+		{
+			wsprintfA( p_buf, "%ls", p_soundtrack_name_wide);
+		}	
+		else
+		{
+			p_buf[0]=0;
+		}	
+				
+		pStuff->AddComponent(Script::GenerateCRC("UserSoundtrackName"),ESYMBOLTYPE_STRING,p_buf);
+	}	
+#	endif // __PLAT_XBOX__
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void GetSoundtrackFromStructure( Script::CStruct *pStuff)
+{
+#	ifdef __PLAT_XBOX__
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+	int user_soundtrack_index=0;
+	if (pStuff->GetInteger("UserSoundtrackIndex",&user_soundtrack_index))
+	{
+		// A user soundtrack index is specified, but maybe the name does not match what is on this
+		// machine, ie, they could have put the mem card into a different xbox ...
+		
+		// Get the name of this soundtrack on this machine
+		char p_buf[100];
+		const WCHAR* p_soundtrack_name_wide=Pcm::GetSoundtrackName(user_soundtrack_index);
+		if (p_soundtrack_name_wide)
+		{
+			wsprintfA( p_buf, "%ls", p_soundtrack_name_wide);
+		}	
+		else
+		{
+			p_buf[0]=0;
+		}	
+	
+		// Get the name as stored on the mem card
+		const char *p_stored_name="";
+		pStuff->GetText("UserSoundtrackName",&p_stored_name);
+		
+		if (strcmp(p_stored_name,p_buf)==0)
+		{
+			// They match! So use that soundtrack.
+			UseUserSoundtrack(user_soundtrack_index);
+			return;
+		}	
+	}	
+	
+	// Oh well, use the standard soundtrack instead.
+	UseStandardSoundtrack();
+#	endif	// __PLAT_XBOX__
+}
+
+
+
+bool _PlayMusicTrack( const char *filename, float volume )
+{
+	if (NoMusicPlease()) return false;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling playtrack %s, when PCM Audio not initialized.", filename ));
+	Dbg_MsgAssert( strlen( filename ) < 200,( "Filename too long" ));
+
+#	ifdef __PLAT_XBOX__
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if( s_xbox_play_user_soundtracks && !skate_mod->GetGameMode()->IsFrontEnd())
+	{
+		if( Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack ) == 0 )
+		{
+			return false;
+		}
+			
+		if( !PCMAudio_PlaySoundtrackMusicTrack( s_xbox_user_soundtrack, s_xbox_user_soundtrack_song ))
+		{
+			return false;
+		}
+		else
+		{
+			if( s_xbox_user_soundtrack_random )
+			{
+				++s_xbox_random_index;
+				if( s_xbox_random_index >= Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack ))
+				{
+					sGenerateRandomSongOrder();
+					s_xbox_random_index = 0;
+				}	
+				s_xbox_user_soundtrack_song=sp_xbox_randomized_songs[s_xbox_random_index];
+			}	
+			else
+			{
+				++s_xbox_user_soundtrack_song;
+				if( s_xbox_user_soundtrack_song >= Pcm::GetSoundtrackNumSongs( s_xbox_user_soundtrack ))
+				{
+					s_xbox_user_soundtrack_song = 0;
+				}
+			}	
+		}		
+	}
+	else
+	{
+		if( !PCMAudio_PlayMusicTrack( filename ))
+		{
+			return false;
+		}
+	}		
+#	else
+	if( !PCMAudio_PlayMusicTrack( filename ))
+	{
+		return false;
+	}
+#	endif // __PLAT_XBOX__
+
+//	volume = volume * Config::GetMasterVolume()/100.0f;
+//	if (volume <0.1f)
+//	{
+//		volume = 0.0f;
+//	}
+	PCMAudio_SetMusicVolume( volume );
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool _PlayMusicStream( uint32 checksum, float volume )
+{
+	if (NoMusicPlease()) return false;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling playtrack %s, when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
+
+#ifdef __PLAT_NGPS__
+	if( !PCMAudio_PlayMusicStream( checksum ))
+	{
+		Dbg_MsgAssert(0, ( "Failed playing track %s", Script::FindChecksumName(checksum) ) );
+		Dbg_Message( "Failed playing track %s", Script::FindChecksumName(checksum) );
+		return false;
+	}
+#else
+	Dbg_MsgAssert(0, ("PCMAudio_PlayMusicStream() not implemented on this platform."));
+#endif // __PLAT_NGPS__
+
+	PCMAudio_SetMusicVolume( volume );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// returns -1 on failure, otherwise the index into which voice played the sound.
+int _PlayStream( uint32 checksum, Sfx::sVolume *p_volume, float pitch, int priority, uint32 controlID, bool preload_only )
+{
+	if (StreamsDisabled()) return -1;
+	
+#	ifdef __PLAT_XBOX__
+	Dbg_MsgAssert( gPcmInitialized,( "Calling _PlayStream(), when PCM Audio not initialized." ));
+#	endif // __PLAT_XBOX__
+
+	// Initialize lowest to a valid entry
+	int lowest_priority = gCurrentStreamInfo[ 0 ].priority;
+	int lowest_priority_channel = 0;
+	uint32 lowest_priority_start_frame = gCurrentStreamInfo[ 0 ].start_frame;
+
+	bool success;
+
+	for ( int i = 0; i < NUM_STREAMS; i++ )
+	{
+		if ( PCMAudio_GetStreamStatus( i ) == PCM_STATUS_FREE )
+		{
+			if (preload_only)
+			{
+				success = PCMAudio_PreLoadStream( checksum, i );
+			}
+			else
+			{
+//				success = PCMAudio_PlayStream( checksum, i, volumeL, volumeR, pitch );
+#				ifdef __PLAT_XBOX__
+				success = PCMAudio_PlayStream( checksum, i, p_volume, pitch );
+#				else
+				success = PCMAudio_PlayStream( checksum, i, p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), pitch );
+#				endif
+			}
+
+			if ( !success )
+			{
+				return ( -1 );
+			}
+
+	   		gCurrentStreamInfo[ i ].controlID = controlID;
+	   		gCurrentStreamInfo[ i ].uniqueID = GenerateUniqueID(controlID);
+	   		gCurrentStreamInfo[ i ].voice = i;
+	   		gCurrentStreamInfo[ i ].priority = priority;
+	   		gCurrentStreamInfo[ i ].start_frame = (uint32)Tmr::GetRenderFrame();
+			gCurrentStreamInfo[ i ].p_frame_amp = CStreamFrameAmpManager::sGetFrameAmp(checksum);
+			return ( i );
+		}
+		else if ( gCurrentStreamInfo[ i ].priority <= lowest_priority )
+		{
+			// If it is equal priority, we want to find the oldest one
+			if ((gCurrentStreamInfo[ i ].priority == lowest_priority) &&
+				(gCurrentStreamInfo[ i ].start_frame >= lowest_priority_start_frame))
+			{
+				continue;
+			}
+
+			lowest_priority = gCurrentStreamInfo[ i ].priority;
+			lowest_priority_channel = i;
+			lowest_priority_start_frame = gCurrentStreamInfo[ i ].start_frame;
+		}
+	}
+
+	// Couldn't find a free one, so lets see if we can knock one off
+	if (priority >= lowest_priority)
+	{
+		PCMAudio_StopStream( lowest_priority_channel );
+
+	#if WAIT_AFTER_STOP_STREAM
+		Dbg_MsgAssert(0, ("Priority streams not implemented on this system"));		// Not implemented
+		return ( -1 );
+	#else
+		if (preload_only)
+		{
+			success = PCMAudio_PreLoadStream( checksum, lowest_priority_channel );
+		}
+		else
+		{
+//			success = PCMAudio_PlayStream( checksum, lowest_priority_channel, volumeL, volumeR, pitch );
+#			ifdef __PLAT_XBOX__
+			success = PCMAudio_PlayStream( checksum, lowest_priority_channel, p_volume, pitch );
+#			else
+			success = PCMAudio_PlayStream( checksum, lowest_priority_channel, p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), pitch );
+#			endif
+		}
+
+		if ( !success )
+		{
+			Dbg_MsgAssert(0, ("Higher priority stream could not start.  Make sure there was another error (like file not found)."));
+			return ( -1 );
+		}
+
+		gCurrentStreamInfo[ lowest_priority_channel ].controlID = controlID;
+		gCurrentStreamInfo[ lowest_priority_channel ].uniqueID = GenerateUniqueID(controlID);
+		gCurrentStreamInfo[ lowest_priority_channel ].voice = lowest_priority_channel;
+		gCurrentStreamInfo[ lowest_priority_channel ].priority = priority;
+		gCurrentStreamInfo[ lowest_priority_channel ].start_frame = (uint32)Tmr::GetRenderFrame();
+		gCurrentStreamInfo[ lowest_priority_channel ].p_frame_amp = CStreamFrameAmpManager::sGetFrameAmp(checksum);
+	#endif // WAIT_AFTER_STOP_STREAM
+		return ( lowest_priority_channel );
+	}
+
+	return ( -1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool _PreLoadMusicStream( uint32 checksum )
+{
+	if (NoMusicPlease()) return false;
+
+	Dbg_MsgAssert( gPcmInitialized,( "Calling _PreLoadMusicStream %s, when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
+
+	return PCMAudio_PreLoadMusicStream( checksum );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool _StartPreLoadedMusicStream( float volume )
+{
+	if( !PCMAudio_StartPreLoadedMusicStream( ))
+	{
+		return false;
+	}
+
+	PCMAudio_SetMusicVolume( volume );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define MIN_STREAM_VOL		2.0f
+
+
+
+uint32 PlayStreamFromObject( Obj::CStreamComponent *pComponent, uint32 streamNameChecksum, float dropoff, float volume,
+							 float pitch, int priority, int use_pos_info, EDropoffFunc dropoffFunc, uint32 controlID )
+{
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+
+	// Don't start a stream if it won't be heard
+	if( sfx_manager->PositionalSoundSilenceMode() )
+	{
+		return 0;
+	}
+
+	Replay::WritePositionalStream(pComponent->GetObject()->GetID(),streamNameChecksum,dropoff,volume,pitch,priority,use_pos_info);
+	if (StreamsDisabled()) return 0;
+	
+	Sfx::sVolume vol;
+	vol.SetSilent();
+	
+	// Let the priority system figure this out
+	//if (!StreamAvailable())
+	//	return 0;
+	
+	// Set up volume according to position of object to camera.
+	if ( use_pos_info != 0 )
+	{
+		Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pComponent->GetObject()->m_pos );
+
+		if (pCamera)
+		{
+			Mth::Vector dropoff_pos;
+			Mth::Vector *p_dropoff_pos = NULL;
+			if (pComponent->GetClosestDropoffPos(pCamera, dropoff_pos))
+			{
+				p_dropoff_pos = &dropoff_pos;
+			}
+
+			sfx_manager->SetVolumeFromPos( &vol, pComponent->GetClosestEmitterPos(pCamera), dropoff, dropoffFunc, pCamera, p_dropoff_pos );
+		}
+		else
+		{
+			vol.SetSilent();
+		}
+
+		// Seems strange to cancel out a stream if it starting low
+		if( fabsf( vol.GetLoudestChannel()) < MIN_STREAM_VOL )
+		{
+			if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
+			{
+				Dbg_Message("I wanted to cancel stream %s", Script::FindChecksumName(streamNameChecksum));
+			}
+//			return 0;
+		}
+	}
+	else
+	{
+		vol.SetChannelVolume( 0, volume );
+		vol.SetChannelVolume( 1, volume );
+	}
+	
+	// K: The 'false' on the end means do not record the call in the replay, because this
+	// call to PlayStreamFromObject has already been recorded.
+	uint32 uniqueID = PlayStream( streamNameChecksum, &vol, pitch, priority, controlID, false );
+	if ( uniqueID )
+	{
+		Dbg_MsgAssert( ( ( gLastStreamChannelPlayed >= 0 ) && ( gLastStreamChannelPlayed < MAX_STREAMS_PER_OBJECT ) ), ( "Tell Matt to fix object streams" ) );
+		if ( gpStreamingObj[ gLastStreamChannelPlayed ] )
+		{
+			// the object might not have called StreamUpdate yet, so the slot might
+			// still think it's in use... clear the slot if that's the case:
+			gpStreamingObj[ gLastStreamChannelPlayed ]->mStreamingID[ gLastStreamChannelPlayed ] = 0;
+			gpStreamingObj[ gLastStreamChannelPlayed ] = NULL;
+		}
+//		Dbg_MsgAssert( !gpStreamingObj[ gLastStreamChannelPlayed ], ( "Have Matt fix object streams... Kick him in the nutsack while you're at it." ) );
+//		Dbg_MsgAssert( !pObject->mStreamingID[ gLastStreamChannelPlayed ], ( "Have Matt fix object streams... Hell, fire matt!!!" ) );
+		gpStreamingObj[ gLastStreamChannelPlayed ] = pComponent;
+		pComponent->mStreamingID[ gLastStreamChannelPlayed ] = uniqueID;
+		// if volume modifier sent in, store that:
+		if ( volume != 100.0f )
+		{
+			int i;
+			for ( i = 0; i < NUM_STREAMS; i++ )
+			{
+				if ( gCurrentStreamInfo[ i ].uniqueID == uniqueID )
+				{
+					gCurrentStreamInfo[ i ].volume = PERCENT( gCurrentStreamInfo[ i ].volume, volume );
+					gCurrentStreamInfo[ i ].dropoff = dropoff;
+				}
+			}
+		}
+
+		// store state of use_pos_info
+		if ( !use_pos_info )
+		{
+			for ( int i = 0; i < NUM_STREAMS; i++ )
+			{
+				if ( gCurrentStreamInfo[i].uniqueID == uniqueID )
+				{
+					gCurrentStreamInfo[i].use_pos_info = false;
+				}
+			}
+		}
+		else
+		{
+			for ( int i = 0; i < NUM_STREAMS; i++ )
+			{
+				if ( gCurrentStreamInfo[i].uniqueID == uniqueID )
+				{
+					gCurrentStreamInfo[i].use_pos_info = true;
+					gCurrentStreamInfo[i].dropoffFunction = dropoffFunc;
+				}
+			}
+		}
+	}
+	return ( uniqueID );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 PlayStream( uint32 checksum, Sfx::sVolume *p_volume, float pitch, int priority, uint32 controlID, bool record_in_replay )
+{
+	if (record_in_replay)
+	{
+		Replay::WritePlayStream( checksum, p_volume, pitch, priority );
+	}	
+	if (StreamsDisabled()) return 0;
+	
+	// Just use checksum if no controlID is set
+	if (controlID == 0)
+	{
+		controlID = checksum;
+	}
+
+	if (!IDAvailable(controlID))
+	{
+		Dbg_Message("Playing stream %s with same checksum as one already being played, won't be able to control directly.", Script::FindChecksumName(controlID));
+	}
+	
+	int whichStream;
+
+	// allow streams to play with default values, if not pre-loaded.
+	whichStream = _PlayStream( checksum, p_volume, pitch, priority, controlID, false );
+	if ( whichStream != -1 )
+	{
+		gCurrentStreamInfo[ whichStream ].dropoff = DEFAULT_STREAM_DROPOFF;
+		gCurrentStreamInfo[ whichStream ].volume = 100.0f;
+		gLastStreamChannelPlayed = whichStream;
+		return gCurrentStreamInfo[ whichStream ].uniqueID;
+	}
+
+//	Dbg_MsgAssert( 0,( "Tried to play track %s not in header.", Script::FindChecksumName( checksum ) ));
+	return ( 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 PreLoadStream( uint32 checksum, int priority )
+{
+	if (StreamsDisabled()) return 0;
+	
+	// Since this isn't hooked up to a script, we just supply the checksum
+	uint32 controlID = checksum;
+
+	int whichStream;
+
+	// allow streams to play with default values, if not in list.
+	Sfx::sVolume vol;
+	vol.SetSilent();
+	whichStream = _PlayStream( checksum, &vol, 0.0, priority, controlID, true );
+
+	if ( whichStream != -1 )
+	{
+#ifdef __PLAT_NGPS__
+		Dbg_Message("PreLoadStream for stream %s on channel %d", Script::FindChecksumName( checksum ), whichStream);
+#endif 
+		gCurrentStreamInfo[ whichStream ].dropoff = DEFAULT_STREAM_DROPOFF;
+		gCurrentStreamInfo[ whichStream ].volume = 100.0f;
+		return gCurrentStreamInfo[ whichStream ].uniqueID;
+	}
+
+//	Dbg_MsgAssert( 0,( "Tried to play track %s not in header.", Script::FindChecksumName( checksum ) ));
+	return ( 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool PreLoadStreamDone( uint32 streamID )
+{
+	if (StreamsDisabled()) {
+		return false;
+	}
+
+	int channel = GetChannelFromID(streamID);
+
+	if ( channel >= 0 )
+	{
+#ifdef __PLAT_NGPS__
+		if (PCMAudio_PreLoadStreamDone( channel ))
+		{
+			Dbg_Message("PreLoadStreamDone for stream %s on channel %d is true", Script::FindChecksumName( streamID ), channel );
+		}
+#endif 
+		return PCMAudio_PreLoadStreamDone( channel );
+	}
+
+	// Couldn't find stream
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool StartPreLoadedStream( uint32 streamID, Sfx::sVolume *p_volume, float pitch )
+{
+	if (StreamsDisabled()) return false;
+	
+	int channel = GetChannelFromID(streamID);
+
+	if ( channel >= 0 )
+	{
+#ifdef __PLAT_NGPS__
+		Dbg_Message("StartPreLoadStream for stream %s on channel %d", Script::FindChecksumName( streamID ), channel);
+#endif 
+
+//		return PCMAudio_StartPreLoadedStream( channel, volumeL, volumeR, pitch );
+#		ifdef __PLAT_XBOX__
+		return PCMAudio_StartPreLoadedStream( channel, p_volume, pitch );
+#		else
+		return PCMAudio_StartPreLoadedStream( channel, p_volume->GetChannelVolume( 0 ), p_volume->GetChannelVolume( 1 ), pitch );
+#		endif
+	}
+
+	// Couldn't find stream
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool PreLoadMusicStream( uint32 checksum )
+{
+	if (NoMusicPlease()) return false;
+
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::PlayMusicStream '%s', when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
+	Dbg_MsgAssert( !gMusicStreamType,( "Calling Pcm::PlayMusicStream '%s', when one is already playing.", Script::FindChecksumName(checksum) ));
+
+	// Stop any previous track
+	PCMAudio_StopMusic(false);
+	
+	bool success = false;
+
+#if WAIT_AFTER_STOP_STREAM
+	gMusicStreamChecksum = checksum;
+	gMusicStreamWaitingToStart = true;
+#else
+	// allow streams to play with default values, if not in list.
+	success = _PreLoadMusicStream( checksum );
+#endif // WAIT_AFTER_STOP_STREAM
+
+	gMusicStreamType = MUSIC_STREAM_TYPE_PRELOAD;
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool PreLoadMusicStreamDone( void )
+{
+	if (NoMusicPlease()) return false;
+	Dbg_MsgAssert( gMusicStreamType == MUSIC_STREAM_TYPE_PRELOAD,( "Calling Pcm::PreLoadMusicStreamDone while in normal music mode." ));
+
+	return PCMAudio_PreLoadMusicStreamDone( );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool StartPreLoadedMusicStream( float volume )
+{
+	if (NoMusicPlease()) return false;
+
+	Dbg_MsgAssert( gMusicStreamType == MUSIC_STREAM_TYPE_PRELOAD,( "Calling Pcm::StartPreLoadedMusicStream while in normal music mode." ));
+
+	if ( volume < 0.0f )
+	{
+		volume = gMusicStreamVolume;
+	}
+	
+	return _StartPreLoadedMusicStream( volume );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool SetStreamVolume( int objStreamIndex )
+{
+	if ( StreamsDisabled() ) return false;
+	
+	Obj::CStreamComponent *pComp;
+	pComp = gpStreamingObj[ objStreamIndex ];
+
+	CurrentStreamInfo *pInfo = NULL;
+
+	int i;
+	for ( i = 0; i < NUM_STREAMS; i++ )
+	{
+		if ( gCurrentStreamInfo[ i ].uniqueID == pComp->mStreamingID[ objStreamIndex ] )
+		{
+			if ( PCMAudio_GetStreamStatus( gCurrentStreamInfo[ objStreamIndex ].voice ) == PCM_STATUS_FREE )
+			{
+				return ( false );
+			}	
+			pInfo = &gCurrentStreamInfo[ i ];
+
+			// Update volume from dropoff/pan for the object responsible for streaming.
+			Sfx::sVolume vol;
+
+			Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+			if ( pInfo->use_pos_info )
+			{
+				Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pComp->GetObject()->m_pos );
+
+				if (pCamera)
+				{
+					Mth::Vector dropoff_pos;
+					Mth::Vector *p_dropoff_pos = NULL;
+					if (pComp->GetClosestDropoffPos(pCamera, dropoff_pos))
+					{
+						p_dropoff_pos = &dropoff_pos;
+					}
+
+					sfx_manager->SetVolumeFromPos( &vol, pComp->GetClosestEmitterPos(pCamera), pInfo->dropoff, pInfo->dropoffFunction, pCamera, p_dropoff_pos );
+				}
+				else
+				{
+					vol.SetSilent();
+				}
+			}
+			else
+			{
+				vol.SetChannelVolume( 0, pInfo->volume );
+				vol.SetChannelVolume( 1, pInfo->volume );
+			}
+
+			if( pInfo->volume != 100.0f )
+			{
+				vol.PercentageAdjustment( pInfo->volume );
+			}
+
+#			ifdef __PLAT_XBOX__
+			// We need the sign-correct version for Xbox to calculate proper 5.1 values.
+			PCMAudio_SetStreamVolume( &vol, pInfo->voice );
+#			else
+			PCMAudio_SetStreamVolume( vol.GetChannelVolume( 0 ), vol.GetChannelVolume( 1 ), pInfo->voice );
+#			endif
+			return ( true );
+		}
+	}
+	return ( false );
+}
+
+void Init( void )
+{
+	// Zero these out at startup.
+	for( int s = 0; s < NUM_STREAMS; ++s )
+	{
+		gpStreamingObj[s] = NULL;
+	}
+
+	if (NoMusicPlease() && StreamsDisabled()) return;
+	
+	PCMAudio_Init( );
+	gPcmInitialized = true;
+	gMusicVolume = (float)Script::GetInteger( 0xabd4a575 ); // checksum 'MusicVolume'
+	if ( !gMusicVolume )
+	{
+		gMusicVolume = DEFAULT_MUSIC_VOLUME;
+	}
+//	SetVolume( gMusicVolume );
+	gMusicStreamVolume = (float)Script::GetInteger(CRCD(0x73f8e03b,"MusicStreamVolume"));
+	if ( !gMusicStreamVolume )
+	{
+		gMusicStreamVolume = DEFAULT_MUSIC_STREAM_VOLUME;
+	}
+
+	int i;
+	for ( i = 0; i < NUM_TRACKLISTS; i++ )
+	{
+		gTrackLists[ i ].numTracks = 0;
+		gTrackLists[ i ].trackForbidden0 = 0;
+        gTrackLists[ i ].trackForbidden1 = 0;
+		gTrackLists[ i ].trackPlayed0 = 0;
+        gTrackLists[ i ].trackPlayed1 = 0;
+		// no tracks, so all tracks forbidden, right?
+		gTrackLists[ i ].allTracksForbidden = true;
+	}
+	for ( i = 0; i < NUM_STREAMS; i++ )
+	{
+		gpStreamingObj[ i ] = NULL;
+		gCurrentStreamInfo[ i ].controlID = 0;
+		gCurrentStreamInfo[ i ].uniqueID = 0;
+	}
+} // end of Init( )
+
+
+// Should be renamed to StopUsingCD( ) as if the cd is in use
+// by streams, this function stops the streams and frees up the
+// CD.
+bool UsingCD( void )
+{
+	
+	#if defined( __PLAT_NGC__ )
+	if ( !( gPcmInitialized ) )
+	{
+		return ( false );
+	}
+	StopMusic( );
+	StopStreams( );
+	
+	#else
+	
+	if (Config::CD() || TEST_FROM_CD)
+	{
+		if ( !( gPcmInitialized ) )
+		{
+	//		Dbg_Message( "uninitialized" );
+			return ( false );
+		}
+		StopMusic( );
+		StopStreams( );
+	}	
+	#endif
+		
+	return ( false );
+} // end of UsingCD( )
+
+// plays a music track.
+void PlayTrack( const char *filename, bool loop )
+{
+	if (NoMusicPlease()) return;
+
+	if (gMusicStreamType)
+	{
+		Dbg_MsgAssert(0, ("Trying to play a music track in stereo stream mode."));
+		return;
+	}
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::PlayTrack '%s', when PCM Audio not initialized.", filename ));
+	Dbg_MsgAssert( strlen( filename ) < MAX_TRACKNAME_STRING_LENGTH,( "CD Audio Filename too long." ));
+	strcpy( gTrackName, filename );
+	
+	int trackList = gCurrentTrackList;
+	float volume;
+	if ( trackList == TRACKLIST_PERM )
+	{
+		volume = gMusicVolume;
+	}
+	else
+	{
+		Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+		volume = sfx_manager->GetMainVolume( );
+	}
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
+	{
+		trackList = TRACKLIST_LEVEL_SPECIFIC;
+		volume = gMusicVolume;
+	}
+
+	SetLoopingMode(loop);
+	
+	if ( !_PlayMusicTrack( gTrackName, volume ) )
+	{
+		if (Config::CD() || TEST_FROM_CD)
+		{
+			Dbg_Message( "Failed playing track %s", filename );
+		}	
+		else
+		{
+			// see if this is a track in the current playlists...
+			// if so, don't keep trying to play it...
+			TrackList *pTrackList = &gTrackLists[ trackList ];
+			int i;
+			for ( i = 0; i < pTrackList->numTracks; i++ )
+			{
+				if ( Script::GenerateCRC( filename ) == Script::GenerateCRC( pTrackList->trackInfo[ i ].trackName ) )
+				{
+					SetTrackForbiddenStatus( i, true, trackList );
+					return;
+				}
+			}
+		}	
+	}
+}
+
+// Plays a stereo stream through the music channels
+void PlayMusicStream( uint32 checksum, float volume )
+{
+	if (NoMusicPlease()) return;
+
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::PlayMusicStream '%s', when PCM Audio not initialized.", Script::FindChecksumName(checksum) ));
+	Dbg_MsgAssert( !gMusicStreamType,( "Calling Pcm::PlayMusicStream '%s', when one is already playing.", Script::FindChecksumName(checksum) ));
+
+	// Stop any previous track
+	PCMAudio_StopMusic(false);
+
+	if ( volume < 0.0f )
+	{
+		volume = gMusicStreamVolume;
+	}
+
+#if WAIT_AFTER_STOP_STREAM
+	gMusicStreamChecksum = checksum;
+	gMusicStreamVolume = volume;
+	gMusicStreamWaitingToStart = true;
+#else
+	_PlayMusicStream( checksum, volume );
+#endif
+
+	gMusicStreamType = MUSIC_STREAM_TYPE_NORMAL;
+}
+
+static bool sMusicIsPaused=false;
+void PauseMusic( int pause )
+{
+	
+	
+	static bool wasPaused = false;
+
+	if (NoMusicPlease()) return;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::Pause when PCM Audio not initialized." ));
+	switch ( pause )
+	{
+		case ( -1 ):
+			// special case... unpause only if it was unpaused last time pause was called...
+			if ( !wasPaused )
+			{
+				// recursive... unpause this bitch...
+				PauseMusic( 0 );
+				return;
+			}
+			break;
+		case ( 1 ):
+			wasPaused = sMusicIsPaused;
+			sMusicIsPaused = true;
+			PCMAudio_Pause( true, MUSIC_CHANNEL );
+			break;
+		case ( 0 ):
+			sMusicIsPaused = false;
+			PCMAudio_Pause( false, MUSIC_CHANNEL );
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Unknown pause state %d", pause ) );
+			break;
+	}
+}
+
+bool MusicIsPaused()
+{
+	return sMusicIsPaused;
+}
+	
+void PauseStream( int pause )
+{
+	
+	
+	static bool paused = false;
+	static bool wasPaused = false;
+
+	if (StreamsDisabled()) return;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::Pause when PCM Audio not initialized." ));
+	switch ( pause )
+	{
+		case ( -1 ):
+			// special case... unpause only if it was unpaused last time pause was called...
+			if ( !wasPaused )
+			{
+				// recursive... unpause this bitch...
+				PauseStream( 0 );
+				return;
+			}
+			break;
+		case ( 1 ):
+			wasPaused = paused;
+			paused = true;
+			PCMAudio_Pause( true, EXTRA_CHANNEL );
+			break;
+		case ( 0 ):
+			paused = false;
+			PCMAudio_Pause( false, EXTRA_CHANNEL );
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Unknown pause state %d", pause ) );
+			break;
+	}
+}
+
+void SetVolume( float volume )
+{
+	
+	if (NoMusicPlease()) return;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::SetVolume, when PCM Audio not initialized." ));
+	if ( ( volume > 100.0f ) || ( volume < 0.0f ) )
+	{
+		Dbg_MsgAssert( 0,( "Volume (%f) not between 0 and 100", volume ));
+		return;
+	}
+	
+	
+	gMusicVolume = volume;
+	if ( gPcmInitialized )
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		if ( ( gMusicStreamType == MUSIC_STREAM_TYPE_NONE) &&
+			 ( ( gCurrentTrackList == TRACKLIST_PERM ) ||
+			 ( skate_mod->GetGameMode( )->IsFrontEnd( ) ) ) )
+		{
+//			volume = volume * Config::GetMasterVolume()/100.0f;
+//			if (volume <0.1f)
+//			{
+//				volume = 0.0f;
+//			}
+			PCMAudio_SetMusicVolume( volume );
+		}
+	}
+}
+
+float GetVolume( void )
+{
+	
+	return ( gMusicVolume );
+}
+
+void SetMusicStreamVolume( float volume )
+{
+	if (NoMusicPlease()) return;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::SetVolume, when PCM Audio not initialized." ));
+	if ( ( volume > 100.0f ) || ( volume < 0.0f ) )
+	{
+		Dbg_MsgAssert( 0,( "Volume (%f) not between 0 and 100", volume ));
+		return;
+	}
+	
+	gMusicStreamVolume = volume;
+	if ( gPcmInitialized )
+	{
+		if (gMusicStreamType == MUSIC_STREAM_TYPE_NORMAL)
+		{
+			PCMAudio_SetMusicVolume( volume );
+		}
+	}
+}
+
+float GetMusicStreamVolume( void )
+{
+	return gMusicStreamVolume;
+}
+
+void AddTrackToPlaylist( const char *trackName, int whichList, const char *trackTitle )
+{
+	
+	if (NoMusicPlease()) return;
+	
+
+	Dbg_MsgAssert( trackName,( "What the fuck?" ));
+	Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
+	TrackList *pTrackList = &gTrackLists[ whichList ];
+
+	// Ken: This is a fix to TT6484. When an ambient track finished, it was choosing
+	// a new random ambient track, rather than playing the same one again.
+	// So to fix, just force numTracks to zero if adding an ambient track, so that there
+	// can be only one. That way, it will be forced to play the same one again. Nya ha!
+	if (whichList==TRACKLIST_LEVEL_SPECIFIC)
+	{
+		pTrackList->numTracks=0;
+	}
+	
+	if ( pTrackList->numTracks >= MAX_NUM_TRACKS )
+	{
+		Dbg_MsgAssert( 0,( "Too many tracks in playlist (increase MAX_NUM_TRACKS or clear list)." ));
+		return;
+	}
+	Dbg_MsgAssert( strlen( trackName ) < MAX_TRACKNAME_STRING_LENGTH,( "Track name too long." ));
+	char *pTrackName = pTrackList->trackInfo[ pTrackList->numTracks ].trackName;
+	strcpy( pTrackName, trackName );
+	
+	uint i;
+	for ( i = 0; i < strlen( pTrackName ); i++ )
+	{
+		if ( ( pTrackName[ i ] >= 'a' ) && ( pTrackName[ i ] <= 'z' ) )
+		{
+			pTrackName[ i ] = pTrackName[ i ] - 'a' + 'A';
+		}
+	}
+	// Music tracks should have a title (for the user to be able to turn songs on/off):
+	if ( whichList == TRACKLIST_PERM )
+	{
+		char *pTrackTitle = PermTrackTitle[ pTrackList->numTracks ].trackTitle;
+		if ( trackTitle )
+		{
+			Dbg_MsgAssert( strlen( trackTitle ) < TRACK_TITLE_MAX_SIZE, ( "Track title %s too long (max %d chars)", trackTitle, TRACK_TITLE_MAX_SIZE ) );
+			strncpy( pTrackTitle, trackTitle, TRACK_TITLE_MAX_SIZE );
+			pTrackTitle[ TRACK_TITLE_MAX_SIZE - 1 ] = '\0';
+		}
+		else
+		{
+	   		Dbg_MsgAssert( 0, ( "Track %s not given a title.", trackName ) );
+			strcpy( pTrackTitle, "No title provided" );
+		}
+	}
+	if ( !PCMAudio_TrackExists( pTrackName, MUSIC_CHANNEL ) )
+	{
+		Dbg_MsgAssert(!Config::CD(),("Could not find music track '%s'\n",pTrackName));
+		Dbg_Message("Could not find music track '%s'",pTrackName);
+		return;
+	}	
+	pTrackList->allTracksForbidden = false;
+    pTrackList->numTracks++;
+}
+
+void ClearPlaylist( int whichList )
+{
+	
+	if (NoMusicPlease()) return;
+	
+	Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbshit." ));
+	TrackList *pTrackList = &gTrackLists[ whichList ];
+	pTrackList->numTracks = 0;
+	pTrackList->allTracksForbidden = true; // if there are no tracks, all are forbidden, no?
+	pTrackList->trackForbidden0 = 0;
+    pTrackList->trackForbidden1 = 0;
+	pTrackList->trackPlayed0 = 0;
+    pTrackList->trackPlayed1 = 0;
+	
+	gNumStreams = 0;
+}
+
+void SkipMusicTrack( void )
+{
+	
+	if (NoMusicPlease()) return;
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
+	{
+		return;
+	}
+
+	if ( gCurrentTrackList == TRACKLIST_PERM )
+	{
+		// will stop the current song, auto update will
+		// cause music to continue with the next track...
+		StopMusic( );
+	}
+}
+
+void SetRandomMode( int randomModeOn )
+{
+	if (NoMusicPlease()) return;
+
+	gMusicInRandomMode = randomModeOn;
+
+#	ifdef __PLAT_XBOX__
+	s_xbox_user_soundtrack_random = ( randomModeOn > 0 );
+	if( randomModeOn )
+	{
+		// Generate a new random order now, since the current one may be invalid for this soundtrack.
+		sGenerateRandomSongOrder();
+		s_xbox_random_index = 0;
+		s_xbox_user_soundtrack_song = sp_xbox_randomized_songs[0];
+	}
+#	endif // __PLAT_XBOX__
+	
+	Dbg_MsgAssert(gCurrentTrackList>=0 && gCurrentTrackListtrackPlayed0=0;
+    pTrackList->trackPlayed1=0;
+}
+
+int GetRandomMode( void )
+{
+	return ( gMusicInRandomMode );
+}
+
+void SetLoopingMode( bool loop )
+{
+	gMusicLooping = loop;
+}
+
+#if WAIT_AFTER_STOP_STREAM
+static void _StartMusicStream()
+{
+	switch (gMusicStreamType)
+	{
+	case MUSIC_STREAM_TYPE_NORMAL:
+		_PlayMusicStream( gMusicStreamChecksum, gMusicStreamVolume );
+		break;
+
+	case MUSIC_STREAM_TYPE_PRELOAD:
+		_PreLoadMusicStream( gMusicStreamChecksum );
+		break;
+
+	case MUSIC_STREAM_TYPE_NONE:
+		Dbg_MsgAssert(0, ("Bad start music stream state"));
+		break;
+	}
+
+	// No longer waiting
+	gMusicStreamWaitingToStart = false;
+}
+#endif // WAIT_AFTER_STOP_STREAM
+
+static void sSetCorrectMusicVolume()
+{
+	#ifdef __PLAT_XBOX__
+	if (s_xbox_play_user_soundtracks)
+	{
+		// This 'if' is part of the fix to TT6957, where user soundtracks were getting sfx vol
+		// when music vol turned down to zero.
+		PCMAudio_SetMusicVolume( Pcm::GetVolume() );
+	}
+	else if (gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC)
+	{
+		Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+		PCMAudio_SetMusicVolume( sfx_manager->GetMainVolume() );
+	}
+	#else
+	// Ken: I added this as a fix to TT5588, 'ambient tracks don't play when music vol is set to zero'
+	// This forces the ambient track to use the sfx volume rather than gMusicVolume.
+	// Hopefully there is not too much of an overhead to calling PCMAudio_SetMusicVolume ...
+	// This bit of code won't be executed every frame though, but every SONG_UPDATE_INTERVAL frames,
+	// so I think it'll be OK ...
+	if (gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC)
+	{
+		Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+		//printf("Playing ambient track: Forcing volume to %f ...\n",sfx_manager->GetMainVolume());
+		PCMAudio_SetMusicVolume( sfx_manager->GetMainVolume() );
+	}
+	#endif
+}
+
+void RandomTrackUpdate( void )
+{
+	
+	TrackList *pTrackList = &gTrackLists[ gCurrentTrackList ];
+	
+	if (NoMusicPlease()) return;
+	
+	
+	if ( pTrackList->allTracksForbidden )
+	{
+//		Dbg_Message( "all tracks forbidden.. random mode" );
+		return;
+	}
+	int pcmStatus = PCMAudio_GetMusicStatus( );
+	if ( pcmStatus == PCM_STATUS_FREE )
+	{
+	#if WAIT_AFTER_STOP_STREAM
+		// If we were waiting for StopMusic() to finish, we need to start the music stream
+		if (gMusicStreamWaitingToStart)
+		{
+			_StartMusicStream();
+			return;
+		}
+	#endif // WAIT_AFTER_STOP_STREAM
+
+		gMusicStreamType = MUSIC_STREAM_TYPE_NONE;		// Just in case we were playing a music stream
+		sSetCorrectMusicVolume();
+
+		// Replay track if looping
+		if (gMusicLooping)
+		{
+			PlayTrack( gTrackName, true );
+			return;
+		}
+		
+		if ( !pTrackList->numTracks )
+		{
+			Dbg_MsgAssert( 0,( "AllTracksForbidden flag should be set if no tracks exist." ));
+			return;
+		}
+		
+        if ( shuffle_random_songs )
+        {
+            // Remember the last song in the previous order, so we can make sure that
+        	// the first song in the new order is different from it.
+        	// Note: OK if the last song was just an uninitialised random value, won't cause any problems.
+        	int last_song=sp_random_song_order[num_random_songs-1];
+            
+            // count active tracks
+            num_random_songs=0;
+            for (int i=0; inumTracks; ++i)
+            {
+                if ( i < 64)
+                {
+                    if ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << i ) ) )
+                    {
+                        // Intialize order
+                        sp_random_song_order[num_random_songs]=i;
+                        // add one
+                        num_random_songs++;
+                    }
+                }
+                else
+                {
+                    if ( !( pTrackList->trackForbidden1 & ( ((uint64)1) << (i-64) ) ) )
+                    {
+                        // Intialize order
+                        sp_random_song_order[num_random_songs]=i;
+                        // add one
+                        num_random_songs++;
+                    }
+                }
+            }
+
+            // need to shuffle order
+            printf("shuffling random song order num_songs=%i\n", num_random_songs );
+            
+            // Jumble it up.		
+        	for (int n=0; n<2000; ++n)
+        	{
+        		int a=Mth::Rnd(num_random_songs);
+        		int b=Mth::Rnd(num_random_songs);
+        		
+                if ( a != b )
+                {
+                    int temp=sp_random_song_order[a];
+            		sp_random_song_order[a]=sp_random_song_order[b];
+            		sp_random_song_order[b]=temp;
+                }
+        	}
+        	
+            // If the first song in the new order equals the last song of the last order,
+        	// do one further swap to make sure it is different.
+        	if (sp_random_song_order[0]==last_song)
+        	{
+        		// a is the first song.
+        		int a=0;
+        		
+        		// Choose b to be a random one of the other songs.
+        		int b;
+        		if (num_random_songs>1)
+        		{
+        			// Make b not able to equal 0, so that we don't swap the first with itself.
+        			b=1+Mth::Rnd(num_random_songs-1);
+        		}
+        		else
+        		{
+        			// Unless there is only one song, in which case just swap it with itself. No point but what the heck.
+        			b=0;
+        		}
+        		
+        		// Do the swap.
+        		int temp=sp_random_song_order[a];
+        		sp_random_song_order[a]=sp_random_song_order[b];
+        		sp_random_song_order[b]=temp;
+        	}
+            
+            // reset index
+            random_song_index=0;
+            shuffle_random_songs=false;
+            pTrackList->trackPlayed0 = 0;
+            pTrackList->trackPlayed1 = 0;
+        }
+        else
+        {
+            random_song_index++;
+            if ( random_song_index >= (num_random_songs-1) )
+            {
+                shuffle_random_songs=true;
+            }
+        }
+        
+        for ( int i = 0; i < pTrackList->numTracks; i++ )
+		{
+			int t = ( sp_random_song_order[random_song_index] );
+            //printf("index = %i song = %i\n", random_song_index, sp_random_song_order[random_song_index] );
+
+            if ( ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << t ) ) && ( t < 64 ) && 
+                   !( pTrackList->trackPlayed0 & ( ((uint64)1) << t ) ) ) )
+			{
+				pTrackList->trackPlayed0 |= ( ((uint64)1) << t );
+				PlayTrack( pTrackList->trackInfo[ t ].trackName );
+                current_music_track=t;
+				Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ t ].trackName, t, current_music_track );
+
+                // update track text on screen
+                Script::CStruct *pParams = new Script::CStruct;
+                pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
+                Script::RunScript( "spawn_update_music_track_text", pParams );
+                delete pParams;
+
+				return;
+			}
+
+            if ( ( !( pTrackList->trackForbidden1 & ( ((uint64)1) << (t-64) ) ) && ( t >= 64 ) && 
+                   !( pTrackList->trackPlayed1 & ( ((uint64)1) << (t-64) ) ) )
+               )
+			{
+				pTrackList->trackPlayed1 |= ( ((uint64)1) << (t-64) );
+				PlayTrack( pTrackList->trackInfo[ t ].trackName );
+                current_music_track=t;
+				Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ t ].trackName, t, current_music_track );
+
+                // update track text on screen
+                Script::CStruct *pParams = new Script::CStruct;
+                pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
+                Script::RunScript( "spawn_update_music_track_text", pParams );
+                delete pParams;
+
+				return;
+			}
+		}
+	}
+}
+
+void TrackUpdate( void )
+{
+	
+
+	if (NoMusicPlease()) return;
+	
+	int trackList = gCurrentTrackList;
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
+	{
+		trackList = TRACKLIST_LEVEL_SPECIFIC;
+	}
+
+	TrackList *pTrackList = &gTrackLists[ trackList ];
+	
+	if ( pTrackList->allTracksForbidden )
+	{
+//		Dbg_Message( "all tracks forbidden.. sequential mode" );
+		return;
+	}
+	int pcmStatus = PCMAudio_GetMusicStatus( );
+	if ( pcmStatus == PCM_STATUS_FREE )
+	{
+	#if WAIT_AFTER_STOP_STREAM
+		// If we were waiting for StopMusic() to finish, we need to start the music stream
+		if (gMusicStreamWaitingToStart)
+		{
+			_StartMusicStream();
+			return;
+		}
+	#endif // WAIT_AFTER_STOP_STREAM
+
+		gMusicStreamType = MUSIC_STREAM_TYPE_NONE;		// Just in case we were playing a music stream
+		sSetCorrectMusicVolume();
+		
+		// Replay track if looping
+		if (gMusicLooping)
+		{
+			PlayTrack( gTrackName, true );
+			return;
+		}
+
+		// don't have a random function.  just play next track...
+		int i;
+		for ( i = 0; i < pTrackList->numTracks; i++ )
+		{
+            if ( ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << i ) ) && ( i < 64 ) && 
+                   !( pTrackList->trackPlayed0 & ( ((uint64)1) << i ) ) ) )
+			{
+				pTrackList->trackPlayed0 |= ( ((uint64)1) << i );
+				PlayTrack( pTrackList->trackInfo[ i ].trackName );
+                current_music_track=i;
+				Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ i ].trackName, i, current_music_track );
+
+                // update track text on screen
+                Script::CStruct *pParams = new Script::CStruct;
+                pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
+                Script::RunScript( "spawn_update_music_track_text", pParams );
+                delete pParams;
+
+				return;
+			}
+
+            if ( ( !( pTrackList->trackForbidden1 & ( ((uint64)1) << (i-64) ) ) && ( i >= 64 ) && 
+                   !( pTrackList->trackPlayed1 & ( ((uint64)1) << (i-64) ) ) ) )
+			{
+				pTrackList->trackPlayed1 |= ( ((uint64)1) << (i-64) );
+				PlayTrack( pTrackList->trackInfo[ i ].trackName );
+                current_music_track=i;
+				Dbg_Message( "Playing track %s %i %i", pTrackList->trackInfo[ i ].trackName, i, current_music_track );
+
+                // update track text on screen
+                Script::CStruct *pParams = new Script::CStruct;
+                pParams->AddInteger(CRCD(0x8d02705d,"current_track"),current_music_track);
+                Script::RunScript( "spawn_update_music_track_text", pParams );
+                delete pParams;
+
+				return;
+			}
+		}
+		// all the tracks have been played... reset:
+		pTrackList->trackPlayed0 = 0;
+        pTrackList->trackPlayed1 = 0;
+	}
+}
+
+//powers of 2 please...
+#define SONG_UPDATE_INTERVAL					64
+#define STREAM_POSITIONAL_UPDATE_INTERVAL		4
+
+// Call this function every frame...
+// keeps tracks playing during levels and shit like that...
+void Update( void )
+{
+	
+	//static int streamingObjToUpdate = 0;
+
+	if (NoMusicPlease() && StreamsDisabled()) return;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm::Update when PCM Audio not initialized." ));
+
+	int err_no = PCMAudio_Update( );
+	if ( err_no != 0 )
+	{
+		if (err_no < 0)
+		{
+			StopStreams( );
+			StopMusic( );
+		} else {
+			// Even though we are returning the stream number, lets just kill them all because we are at the end of the project
+			StopStreams( );
+		}
+	}
+
+	// Garrett: This should go away, since the IOP communication is async now.  But I left it in for
+	// the music now since I don't know if there is a CPU hit there.
+	sCounter++;
+
+	//if ( !( sCounter & ( STREAM_POSITIONAL_UPDATE_INTERVAL - 1 ) ) )
+	for (int streamingObjToUpdate = 0; streamingObjToUpdate < NUM_STREAMS; streamingObjToUpdate++)
+	{
+		if ( gpStreamingObj[ streamingObjToUpdate ] )
+		{
+			if ( !SetStreamVolume( streamingObjToUpdate ) )
+			{
+				gpStreamingObj[ streamingObjToUpdate ]->mStreamingID[ streamingObjToUpdate ] = 0;
+				gpStreamingObj[ streamingObjToUpdate ] = NULL;
+			}
+		}
+		//// update the other one next time...
+		//streamingObjToUpdate++;
+		//if ( streamingObjToUpdate >= NUM_STREAMS )
+		//{
+		//	streamingObjToUpdate = 0;
+		//}
+	}
+
+	// Free any frame amp data that was used by a stream that already stopped
+	for (int i = 0; i < NUM_STREAMS; i++)
+	{
+		if (gCurrentStreamInfo[i].p_frame_amp && (PCMAudio_GetStreamStatus(gCurrentStreamInfo[i].voice) == PCM_STATUS_FREE))
+		{
+			Dbg_Message("Clearing StreamFrameAmp %s", Script::FindChecksumName(gCurrentStreamInfo[i].controlID));
+			CStreamFrameAmpManager::sFreeFrameAmp(gCurrentStreamInfo[i].p_frame_amp);
+			gCurrentStreamInfo[i].p_frame_amp = NULL;
+		}
+	}
+	
+	if ( sCounter & ( SONG_UPDATE_INTERVAL - 1 ) )
+		return;
+
+	// if volume is turned all the way down, the current song will finish
+	// playing and then we'll keep returning here until volume isn't zero,
+	// as soon as it is turned up a new song will begin playing.
+	if ( gMusicVolume == 0.0f && gCurrentTrackList != TRACKLIST_LEVEL_SPECIFIC) 
+	{
+		return;
+	}
+
+	#ifdef __PLAT_XBOX__
+	// This is part of the fix to TT6957, 
+	// where user soundtracks were getting sfx vol when music vol turned down to zero.
+	// (The other part of the fix is a bit further down, search for TT6957)
+	if ( gMusicVolume == 0.0f && s_xbox_play_user_soundtracks) 
+	{
+		return;
+	}
+	#endif
+	
+	if (sMusicIsPaused)
+	{
+		return;
+	}
+
+
+	// K: Commented this out since it does not appear to be needed, and was stopping random
+	// track mode from working on XBox & Gamecube, I think because is_frontend is defined to
+	// be 1 in mode_skateshop in gamemode.q. I commented this out instead of changing
+	// is_frontend to 0 in case that broke something else.		
+	
+	// don't play music in the frontend: <- old comment
+	/*
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
+	{
+		TrackUpdate( );
+		return;
+	}
+	*/
+
+		
+	// if song is finished, start a new one playing...
+	if ( gMusicInRandomMode || ( gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC ) )
+		RandomTrackUpdate( );
+	else
+		TrackUpdate( );
+}
+
+void GetPlaylist( uint64* flags1, uint64* flags2 )
+{
+    *flags1 = gTrackLists[ TRACKLIST_PERM ].trackForbidden0;
+    *flags2 = gTrackLists[ TRACKLIST_PERM ].trackForbidden1;
+}
+
+void SetPlaylist( uint64 flags1, uint64 flags2 )
+{
+	gTrackLists[ TRACKLIST_PERM ].trackForbidden0 = flags1;
+    gTrackLists[ TRACKLIST_PERM ].trackForbidden1 = flags2;
+}
+
+// Ambient volume is the same as SoundFX volume...
+void SetAmbientVolume( float volPercent )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if ( skate_mod->GetGameMode( )->IsFrontEnd( ) )
+	{
+		return;
+	}
+	if ( gCurrentTrackList == TRACKLIST_LEVEL_SPECIFIC )
+	{
+		PCMAudio_SetMusicVolume( volPercent );
+	}
+}
+
+// set a particular song as 'forbidden' (don't play it, the player hates that song...)
+void SetTrackForbiddenStatus( int trackNum, bool forbidden, int whichList )
+{
+    if (NoMusicPlease()) return;
+	
+    shuffle_random_songs=true;
+
+	int i;
+	Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
+	TrackList *pTrackList = &gTrackLists[ whichList ];
+	Dbg_MsgAssert( trackNum < pTrackList->numTracks,( "Not that many tracks in list." ));
+	if ( forbidden )
+	{
+		if ( trackNum < 64)
+        {
+            pTrackList->trackForbidden0 |= ( ((uint64)1) << trackNum );
+        }
+        else
+        {
+            pTrackList->trackForbidden1 |= ( ((uint64)1) << (trackNum-64) );
+        }
+        
+        if ( TrackIsPlaying( whichList, trackNum ) )
+		{
+			StopMusic( );
+		}
+		for ( i = 0; i < pTrackList->numTracks; i++ )
+		{
+			if ( !( pTrackList->trackForbidden0 & ( ((uint64)1) << i ) ) || !( pTrackList->trackForbidden1 & ( ((uint64)1) << (i-64) ) ) )
+			{
+				pTrackList->allTracksForbidden = false;
+				return;
+			}
+		}
+		pTrackList->allTracksForbidden = true;
+		return;
+	}
+	
+    if ( trackNum < 64)
+    {
+        pTrackList->trackForbidden0 &= ~( ((uint64)1) << trackNum );
+    }
+    else
+    {
+        pTrackList->trackForbidden1 &= ~( ((uint64)1) << (trackNum-64) );
+    }
+	pTrackList->allTracksForbidden = false;
+}
+
+/*void CheckLockedTracks()
+{
+    if ( !Mdl::Skate::Instance()->GetCareer()->GetGlobalFlag(286) )
+    {
+        // if the kiss songs aren't unlocked make sure they stay forbidden
+        SetTrackForbiddenStatus( 32, true, Pcm::TRACKLIST_PERM );
+        SetTrackForbiddenStatus( 33, true, Pcm::TRACKLIST_PERM );
+    }
+}*/
+
+bool TrackIsPlaying( int whichList, int whichTrack )
+{
+	
+	if (NoMusicPlease()) return false;
+	
+	Dbg_MsgAssert( gPcmInitialized,( "Calling Pcm func when PCM Audio not initialized." ));
+	int pcmStatus = PCMAudio_GetMusicStatus( );
+	if ( pcmStatus == PCM_STATUS_FREE )
+		return ( false );
+	if ( whichTrack >= gTrackLists[ whichList ].numTracks )
+		return ( false );
+	// can't do a strcmp, because files in gTrackName have been converted to all uppercase...
+	if ( Script::GenerateCRC( gTrackName ) == Script::GenerateCRC( gTrackLists[ whichList ].trackInfo[ whichTrack ].trackName ) )
+		return ( true );
+	return ( false );
+}
+
+int GetNumTracks( int whichList )
+{
+	
+
+	if (NoMusicPlease()) return 0;
+	
+
+	Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
+	TrackList *pTrackList = &gTrackLists[ whichList ];
+	return ( pTrackList->numTracks );
+}
+
+int GetTrackForbiddenStatus( int trackNum, int whichList )
+{
+	
+
+	if (NoMusicPlease()) return 1;
+	
+	
+	Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
+	TrackList *pTrackList = &gTrackLists[ whichList ];
+	Dbg_MsgAssert( trackNum < pTrackList->numTracks,( "Requesting forbidden status on invalid track." ));
+	
+    if ( trackNum < 64 )
+    {
+        if (pTrackList->trackForbidden0 & ( ((uint64)1) << trackNum ))
+    	{
+    		return true;
+    	}
+    	else
+    	{
+    		return false;
+    	}
+    }
+    else
+    {
+        if (pTrackList->trackForbidden1 & ( ((uint64)1) << (trackNum-64) ))
+    	{
+    		return true;
+    	}
+    	else
+    	{
+    		return false;
+    	}
+    }
+}
+
+const char *GetTrackName( int trackNum, int whichList )
+{
+	
+
+	if (NoMusicPlease())
+	{
+		strcpy( gTrackLists[ 0 ].trackInfo[ 0 ].trackName, "Empty." );
+		return ( gTrackLists[ 0 ].trackInfo[ 0 ].trackName );
+	}
+	
+	//if ( whichList == TRACKLIST_PERM )
+	//{
+	//	Dbg_MsgAssert( trackNum < MAX_NUM_TRACKS, ( "Invalid track num specified." ) );
+	//	return ( PermTrackTitle[ trackNum ].trackTitle );
+	//}
+
+	Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
+	TrackList *pTrackList = &gTrackLists[ whichList ];
+	Dbg_MsgAssert( trackNum < pTrackList->numTracks,( "Requesting invalid track name." ));
+	int i;
+	char *pText = pTrackList->trackInfo[ trackNum ].trackName;
+	char *pRet = pText;
+	int len = strlen( pText );
+	for ( i = 0; i < len; i++ )
+	if ( ( pText[ i ] == '\\' ) || ( pText[ i ] == '/' ) )
+		pRet = &pText[ i + 1 ];
+	return ( pRet );
+}
+
+// Ambient (TRACKLIST_LEVEL_SPECIFIC) or music (TRACKLIST_PERM)
+void SetActiveTrackList( int whichList )
+{
+	
+
+	if (NoMusicPlease()) return;
+	
+	Dbg_MsgAssert( whichList < NUM_TRACKLISTS,( "Dumbass." ));
+	if ( gCurrentTrackList != whichList )
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		if ( !skate_mod->GetGameMode( )->IsFrontEnd( ) )
+		{
+			StopMusic( );
+		}
+	}
+	gCurrentTrackList = whichList;
+}
+
+// A user can choose to listen to music or ambience during gameplay.
+int GetActiveTrackList( void )
+{
+	
+	return ( gCurrentTrackList );
+}
+
+// A music header contains the names and sizes of all the music files available:
+bool LoadMusicHeader( const char *nameOfFile )
+{
+	if (NoMusicPlease()) return false;
+	
+	music_hed_there =( PCMAudio_LoadMusicHeader( nameOfFile ) );
+	
+	return	music_hed_there; 
+}
+
+// Headers contain all available streams (probably will be one header per level).
+bool LoadStreamHeader( const char *nameOfFile )
+{
+	if (StreamsDisabled()) return false;
+	
+	streams_hed_there = PCMAudio_LoadStreamHeader( nameOfFile );
+
+	return streams_hed_there;
+}
+
+int GetNumStreamsAvailable( void )
+{
+	return ( NUM_STREAMS );
+}
+
+bool StreamAvailable( int whichStream )
+{
+	if (StreamsDisabled()) return false;
+	
+	if ( PCMAudio_GetStreamStatus( whichStream ) == PCM_STATUS_FREE )
+	{
+		return ( true );
+	}
+	return ( false );
+}
+
+bool StreamAvailable( void )
+{
+
+	if (StreamsDisabled()) return false;
+	
+	if (!(Config::CD() || TEST_FROM_CD))
+	{
+		int testStreams = 0;
+		testStreams = Script::GetInt( 0x62df9442, false ); // checksum 'testStreamsFromHost'
+		if ( !testStreams )
+		{
+			return ( true );
+		}
+	}
+
+//	return ( PCMAudio_GetStreamStatus( 0 ) == PCM_STATUS_FREE );
+
+	int i;
+	for ( i = 0; i < NUM_STREAMS; i++ )
+	{
+		if ( PCMAudio_GetStreamStatus( i ) == PCM_STATUS_FREE )
+		{
+			return ( true );
+		}
+	}
+	return ( false );
+}
+
+bool StreamExists( uint32 streamChecksum )
+{
+	// try to find the name in the header file using the checksum:
+	uint32 streamName = PCMAudio_FindNameFromChecksum( streamChecksum, EXTRA_CHANNEL );
+	return ( streamName != NULL );
+}
+
+bool StreamLoading( uint32 streamID )
+{
+	if (StreamsDisabled()) return false;
+	
+	if (!(Config::CD() || TEST_FROM_CD))
+	{
+		int testStreams = 0;
+		testStreams = Script::GetInt( 0x62df9442, false ); // checksum 'testStreamsFromHost'
+		if ( !testStreams )
+		{
+			return ( false );
+		}
+	}
+
+	int channel = GetChannelFromID(streamID);
+
+	if ( channel >= 0 )
+	{
+		return ( PCMAudio_GetStreamStatus( channel ) == PCM_STATUS_LOADING );
+	}
+
+	return ( false );
+}
+
+bool StreamPlaying( uint32 streamID )
+{
+	if (StreamsDisabled()) return false;
+	
+	int channel = GetChannelFromID(streamID, false);
+
+	if ( channel >= 0 )
+	{
+		return true;	// Even if it is in loading state
+	}
+
+	return false;
+}
+
+int GetCurrentTrack()
+{
+    return ( current_music_track );
+}
+
+void SetCurrentTrack( int value )
+{
+    current_music_track=value;
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+// Pcm::CStreamFrameAmp
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CStreamFrameAmp::CStreamFrameAmp()
+{
+	Init();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CStreamFrameAmp::~CStreamFrameAmp()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CStreamFrameAmp::Init()
+{
+	m_checksum = vNOT_ALLOCATED;
+	m_num_samples = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8				CStreamFrameAmp::GetSample(int frame) const
+{
+	Dbg_Assert(is_allocated());
+	Dbg_Assert(frame < m_num_samples);
+
+	return m_frame_amp_samples[frame];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const uint8 *		CStreamFrameAmp::GetSamples() const
+{
+	Dbg_Assert(is_allocated());
+
+	return m_frame_amp_samples;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const uint8 *		CStreamFrameAmp::GetSamples(int frame) const
+{
+	Dbg_Assert(is_allocated());
+	Dbg_Assert(frame < m_num_samples);
+
+	return &m_frame_amp_samples[frame];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					CStreamFrameAmp::GetNumSamples() const
+{
+	return m_num_samples;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CStreamFrameAmp::load(uint32 stream_checksum)
+{
+	File::CAsyncFileHandle *p_handle;
+	char filename[256];
+
+	// Generate filename from checksum
+	sprintf(filename,"fam\\%x.fam", stream_checksum);
+
+	// Open with async blocking for now.  Look to make truly async later.
+	p_handle = File::CAsyncFileLoader::sOpen(filename, true);
+	if (p_handle)
+	{
+		m_num_samples = p_handle->GetFileSize();
+		if (m_num_samples >= vMAX_SAMPLES)
+		{
+			Dbg_MsgAssert(0, (".fam file has too many samples: %d", m_num_samples));
+			File::CAsyncFileLoader::sClose(p_handle);
+			return false;
+		}
+
+		p_handle->Read(m_frame_amp_samples, 1, m_num_samples);
+		File::CAsyncFileLoader::sClose(p_handle);
+
+		m_checksum = stream_checksum;
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CStreamFrameAmp::free()
+{
+	if (is_allocated())
+	{
+		// Easiest way to clear, since there is nothing to de-allocate.
+		Init();
+
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+// Pcm::CStreamFrameAmpManager
+
+CStreamFrameAmp		CStreamFrameAmpManager::s_frame_amp_data[vMAX_BUFFERS];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CStreamFrameAmp *	CStreamFrameAmpManager::sLoadFrameAmp(uint32 stream_checksum)
+{
+	for (int i = 0; i < vMAX_BUFFERS; i++)
+	{
+		if (!s_frame_amp_data[i].is_allocated())
+		{
+			if (s_frame_amp_data[i].load(stream_checksum))
+			{
+				return &s_frame_amp_data[i];
+			}
+			else
+			{
+				// If we got here, there weren't any free slots
+				Dbg_MsgAssert(0, ("Couldn't load StreamFrameAmp %s", Script::FindChecksumName(stream_checksum)));
+				return NULL;
+			}
+		}
+	}
+
+	// If we got here, there weren't any free slots
+	Dbg_MsgAssert(0, ("Can't load StreamFrameAmp %s: no free slots", Script::FindChecksumName(stream_checksum)));
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CStreamFrameAmp *	CStreamFrameAmpManager::sGetFrameAmp(uint32 stream_checksum)
+{
+	for (int i = 0; i < vMAX_BUFFERS; i++)
+	{
+		if (s_frame_amp_data[i].GetStreamNameCRC() == stream_checksum)
+		{
+			return &s_frame_amp_data[i];
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CStreamFrameAmpManager::sFreeFrameAmp(uint32 stream_checksum)
+{
+	for (int i = 0; i < vMAX_BUFFERS; i++)
+	{
+		if (s_frame_amp_data[i].GetStreamNameCRC() == stream_checksum)
+		{
+			return s_frame_amp_data[i].free();
+		}
+	}
+
+	// If we got here, we didn't find it
+	Dbg_MsgAssert(0, ("Can't find StreamFrameAmp %s data to free", Script::FindChecksumName(stream_checksum)));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CStreamFrameAmpManager::sFreeFrameAmp(CStreamFrameAmp *p_fam_data)
+{
+	return p_fam_data->free();
+}
+
+}  // namespace Pcm
diff --git a/Code/Gel/Music/music.h b/Code/Gel/Music/music.h
new file mode 100644
index 0000000..7cd70f3
--- /dev/null
+++ b/Code/Gel/Music/music.h
@@ -0,0 +1,272 @@
+/*	Stream audio off the disc...
+	Play music tracks or what not.
+	
+	md Jan 2001
+*/
+
+#ifndef __MUSIC_H__
+#define __MUSIC_H__
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+#include 
+
+// change the following line to compile out music or streams:
+#define NO_MUSIC_PLEASE	0
+#define DISABLE_STREAMS	0
+
+namespace Obj
+{
+	class CStreamComponent;
+}
+			
+namespace Pcm
+{
+
+// Forward declarations
+class CStreamFrameAmp;
+class CStreamFrameAmpManager;
+
+
+#define NUM_CHANNELS				2
+
+#define DEFAULT_MUSIC_VOLUME		50.0f
+#define DEFAULT_MUSIC_STREAM_VOLUME	100.0f
+
+#define MAX_NUM_TRACKS				80
+#define MAX_TRACKNAME_STRING_LENGTH	40
+#define TRACK_TITLE_MAX_SIZE		100
+
+#define DEFAULT_STREAM_DROPOFF		DEFAULT_DROPOFF_DIST
+
+#define STREAM_DEFAULT_PRIORITY		(50)		// Stream priority that is used when not set
+
+enum EMusicStreamType
+{
+	MUSIC_STREAM_TYPE_NONE = 0,
+	MUSIC_STREAM_TYPE_NORMAL,
+	MUSIC_STREAM_TYPE_PRELOAD,
+};
+
+// Structures
+struct CurrentStreamInfo
+{
+	uint32				controlID;		// ID used from script to control stream (default value is checksum of stream)
+	uint32				uniqueID;		// ID used within code (same as controlID unless this stream is playing on more than 1 channel)
+	float				volume;
+	float				dropoff;
+	int					voice;
+	int					priority;
+	uint32				start_frame;
+	bool				use_pos_info;
+	EDropoffFunc		dropoffFunction;
+	CStreamFrameAmp *	p_frame_amp;
+};
+
+struct TrackInfo
+{
+	char trackName[ MAX_TRACKNAME_STRING_LENGTH ];
+};
+
+struct TrackTitle
+{
+	char trackTitle[ TRACK_TITLE_MAX_SIZE ];
+};
+
+struct TrackList
+{
+	// on PS2 version, the sceCdlFILE info is put into the end of this string:
+	TrackInfo 		trackInfo[ MAX_NUM_TRACKS ];
+	int				numTracks;
+	// the following are bitflags:
+	uint64			trackForbidden0;
+    uint64			trackForbidden1;
+	uint64			trackPlayed0;
+    uint64			trackPlayed1;
+	bool			allTracksForbidden;
+};
+
+
+void	Init( void );
+
+bool NoMusicPlease();
+bool StreamsDisabled();
+
+int GetNumStreamsAvailable( void );
+int GetCurrentTrack( void );
+void SetCurrentTrack( int value );
+
+// Call update every frame...
+// this function checks once every second to see if the previous
+// song finished playing... if so it starts a new song.
+// Also handles Stop/Play commands.
+void	Update( void );
+
+void	PlayTrack( const char *filename, bool loop = false );
+void	PlayMusicStream( uint32 checksum, float volume = -1 );		// Plays a stereo stream through the music channels
+
+// These five only for XBox.
+void UseUserSoundtrack( int soundtrack );
+void UseStandardSoundtrack();
+bool UsingUserSoundtrack();
+void SaveSoundtrackToStructure( Script::CStruct *pStuff );
+void GetSoundtrackFromStructure( Script::CStruct *pStuff );
+
+uint32	PlayStream( uint32 checksum, Sfx::sVolume *p_volume, float pitch = 100, int priority = STREAM_DEFAULT_PRIORITY,
+					uint32 controlID = 0, bool record_in_replay=true );
+uint32	PlayStreamFromObject( Obj::CStreamComponent *pObject, uint32 streamNameChecksum, float dropoff = 0, float volume = 0,
+							  float pitch = 100, int priority = STREAM_DEFAULT_PRIORITY, int use_pos_info = 0,
+							  EDropoffFunc dropoffFunc = DROPOFF_FUNC_STANDARD, uint32 controlID = 0 );
+
+// Control functions
+bool	SetStreamVolumeFromID( uint32 streamID, Sfx::sVolume *p_volume );		// For non-object streams
+bool	SetStreamPitchFromID( uint32 streamID, float pitch );					// For non-object streams
+void	StopStreamFromID( uint32 streamID );
+void	StopStreams( int channel = -1 );
+bool	StreamAvailable( void );
+bool	StreamAvailable( int whichStream );
+
+bool	StreamExists( uint32 streamChecksum );
+
+bool	StreamLoading( uint32 streamID );
+bool	StreamPlaying( uint32 streamID );
+
+// Preload streams.  By preloading, you can guarantee they will start at a certain time.
+uint32	PreLoadStream( uint32 checksum, int priority = STREAM_DEFAULT_PRIORITY );
+bool	PreLoadStreamDone( uint32 streamID );
+bool	StartPreLoadedStream( uint32 streamID, Sfx::sVolume* p_volume, float pitch = 100 );
+
+// Preload Music streams.  By preloading, you can guarantee they will start at a certain time.
+bool	PreLoadMusicStream( uint32 checksum );
+bool	PreLoadMusicStreamDone( void );
+bool	StartPreLoadedMusicStream( float volume = -1 );
+
+/*	Arg:	-1 to unpause only if it was unpaused last time pause was called
+			1 to pause
+			0 to unpause
+*/
+void	PauseMusic( int pause );
+bool	MusicIsPaused();
+void	PauseStream( int pause );
+
+void	StopMusic( void );
+
+// ambient tracks use the soundfx volume instead of music volume:
+void	SetAmbientVolume( float volPercent );
+
+void	SetVolume( float volume );
+float	GetVolume( void );
+void	SetMusicStreamVolume( float volume );
+float	GetMusicStreamVolume( void );
+void	GetPlaylist( uint64* flags1, uint64* flags2 );
+void	SetPlaylist( uint64 flags1, uint64 flags2 );
+
+enum{
+		TRACKLIST_PERM,
+		TRACKLIST_LEVEL_SPECIFIC,
+		NUM_TRACKLISTS,
+};
+
+// Music track management:
+void	AddTrackToPlaylist( const char *trackName, int whichList, const char *trackTitle = NULL );
+void	ClearPlaylist( int whichList );
+
+const char *GetTrackName( int trackNum, int whichList );
+int		GetNumTracks( int whichList );
+
+bool UsingCD( void );
+
+// set a particular song as 'forbidden' (don't play it, the player hates that song...)
+void	SetTrackForbiddenStatus( int trackNum, bool forbidden, int whichList );
+//void	CheckLockedTracks( );
+int		GetTrackForbiddenStatus( int trackNum, int whichList );
+void	SkipMusicTrack( void );
+void	SetRandomMode( int randomModeOn );
+int		GetRandomMode( void );
+void	SetLoopingMode( bool loop );
+
+// Perm list? Or ambient list?
+void	SetActiveTrackList( int whichList );
+int		GetActiveTrackList( void );
+
+bool LoadMusicHeader( const char *nameOfFile );
+bool LoadStreamHeader( const char *nameOfFile );
+
+///////////////////////////////////////////////////////////////////////////////////
+// Pcm::CStreamFrameAmp
+
+class CStreamFrameAmp
+{
+public:
+	// Constants
+	enum
+	{
+		vNOT_ALLOCATED			= 0,				// 0 indicates not allocated
+		vMAX_SAMPLES			= 4096,
+	};
+
+								CStreamFrameAmp();
+								~CStreamFrameAmp();
+
+	void						Init();
+	uint32						GetStreamNameCRC() const;
+
+	uint8						GetSample(int frame) const;
+	const uint8 *  				GetSamples() const;
+	const uint8 *  				GetSamples(int frame) const;
+	int							GetNumSamples() const;
+
+protected:
+
+	bool						load(uint32 stream_checksum);
+	bool						free();
+	bool						is_allocated() const;
+
+	uint32						m_checksum;
+	int							m_num_samples;
+	uint8						m_frame_amp_samples[vMAX_SAMPLES] nAlign(128);	// Aligned data for the loading code
+
+	// Friends
+	friend CStreamFrameAmpManager;
+};
+
+///////////////////////////////////////////////////////////////////////////////////
+// Pcm::CStreamFrameAmpManager
+class CStreamFrameAmpManager
+{
+public:
+	// Constants
+	enum
+	{
+		vMAX_BUFFERS			= 1,
+	};
+
+	static CStreamFrameAmp *	sLoadFrameAmp(uint32 stream_checksum);
+	static CStreamFrameAmp *	sGetFrameAmp(uint32 stream_checksum);
+	static bool					sFreeFrameAmp(uint32 stream_checksum);
+	static bool					sFreeFrameAmp(CStreamFrameAmp *p_fam_data);
+
+protected:
+
+	static CStreamFrameAmp		s_frame_amp_data[vMAX_BUFFERS];
+};
+
+///////////////////////////////////////////////////////////////////////////////////
+
+inline uint32			CStreamFrameAmp::GetStreamNameCRC() const
+{
+	return m_checksum;
+}
+
+inline bool				CStreamFrameAmp::is_allocated() const
+{
+	return m_checksum != vNOT_ALLOCATED;
+}
+
+
+}  // namespace Pcm
+
+#endif // __MUSIC_H__
+
diff --git a/Code/Gel/Music/ngc/bgm/bgm_com.c b/Code/Gel/Music/ngc/bgm/bgm_com.c
new file mode 100644
index 0000000..b7a38d2
--- /dev/null
+++ b/Code/Gel/Music/ngc/bgm/bgm_com.c
@@ -0,0 +1,144 @@
+// This code builds an IRX module that handles PCM streaming from
+// the CD to SPU2 Sound Data Input area...
+
+#include 
+#include 
+#include 
+#include "sif.h"
+#include "sifcmd.h"
+#include "sifrpc.h"
+#include 
+#include "bgm_i.h"
+
+int gRpcArg[16];	//--- Receiving channel for RPC arguments transferred from EE
+
+
+static void* bgmFunc(unsigned int fno, void *data, int size);
+
+extern int  BgmInit(int ch, int useCD);
+extern void BgmQuit(int channel, int status );
+extern int  BgmOpen( int ch, char* filename );
+extern void BgmClose(int ch, int status);
+extern void BgmCloseNoWait(int ch, int status);
+extern int  BgmPreLoad( int ch, int status );
+extern void BgmStart(int ch, int status);
+extern void BgmStop(int ch, int status);
+extern void BgmSeek( int ch, unsigned int vol );
+extern int BgmSetVolume( int ch, unsigned int vol );
+extern int BgmRaw2Spu( int ch, int which, int mode );
+extern int BgmSetVolumeDirect( int ch, unsigned int vol );
+// fuck this you big assbreath.
+//extern void BgmSetMasterVolume( int ch, unsigned int vol );
+extern unsigned int BgmGetMode( int ch, int status );
+//extern void BgmSetMode( int ch, u_int mode );
+extern void BgmSdInit(int ch, int status );
+
+
+/* ------------------------------------------------------------------------
+   Main thread for the ezbgm module.
+   After execution, initialize interrupt environment, register command, and
+   then wait until there is a request from the EE.
+   ------------------------------------------------------------------------ */
+int sce_bgm_loop()
+{
+	sceSifQueueData qd;
+	sceSifServeData sd;
+
+	//-- Initialize interrupt environment in advance.
+
+	CpuEnableIntr();
+	EnableIntr( INUM_DMA_4 );
+	EnableIntr( INUM_DMA_7 );
+	
+	//--- Register function that is called according to request
+
+
+	sceSifInitRpc(0);
+
+	sceSifSetRpcQueue( &qd, GetThreadId() );
+	sceSifRegisterRpc( &sd, EZBGM_DEV, bgmFunc, (void*)gRpcArg, NULL, NULL, &qd );
+	PRINTF(("goto bgm cmd loop\n"));
+
+	//--- Command-wait loop
+
+	sceSifRpcLoop(&qd);
+
+	return 0;
+}
+
+
+/* ------------------------------------------------------------------------
+   Function that is awakened by a request from the EE.
+   The argument is stored in *data.  The leading four bytes are reserved and are not used.
+   The return value of this function becomes the return value of the EE side's RPC.
+   When the argument is a structure, it is sent to gRpcData and used.
+   When a structure is returned to the EE, the value is sent to the address of the first argument (on the EE side).
+   ------------------------------------------------------------------------ */
+int ret = 0;
+
+static void* bgmFunc(unsigned int command, void *data, int size)
+{ 
+	int ch;
+
+//	asm volatile( "break 1");
+
+//	PRINTF(( " bgmfunc %x, %x, %x, %x\n", *((int*)data + 0), 
+//		*((int*)data + 1), *((int*)data + 2),*((int*)data + 3) ));
+
+	ch = command&0x000F;
+
+	switch( command&0xFFF0 )
+	{
+	case EzBGM_INIT:
+		ret = BgmInit( ch, *((int*)data) );
+		break;
+	case EzBGM_QUIT:
+		BgmQuit ( ch, *((int*)data) );
+		break;
+	case EzBGM_OPEN:
+		ret = BgmOpen ( ch, (char*)((int*)data) );
+		break;
+	case EzBGM_CLOSE:
+		BgmClose( ch, *((int*)data) );
+		break;
+	case EzBGM_CLOSE_NO_WAIT:
+		BgmCloseNoWait( ch, *((int*)data) );
+		break;
+	case EzBGM_PRELOAD:
+		ret = BgmPreLoad ( ch, *((int*)data) );
+		break;
+	case EzBGM_START:
+		BgmStart( ch, *((int*)data) );
+		break;
+	case EzBGM_STOP:
+		BgmStop( ch, *((int*)data) );
+		break;
+	case EzBGM_SEEK:
+		BgmSeek( ch, (unsigned int)*((int*)data) );
+		break;
+	case EzBGM_SETVOL:
+		ret = BgmSetVolume( ch, (unsigned int)*((int*)data) );
+		break;
+	case EzBGM_SETVOLDIRECT:
+		ret = BgmSetVolumeDirect( ch, (unsigned int)*((int*)data) );
+		break;
+	case EzBGM_GETMODE:
+		ret = BgmGetMode( ch, *((int*)data) );
+		break;
+	case EzBGM_SDINIT:
+		BgmSdInit( ch, *((int*)data) );
+		break;
+	default:
+		ERROR(("EzBGM driver error: unknown command %d \n", *((int*)data) ));
+		break;
+	}
+//	PRINTF(( "return value = %x \n", ret )); 
+	return (void*)(&ret);
+}
+
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
+
diff --git a/Code/Gel/Music/ngc/bgm/bgm_entr.c b/Code/Gel/Music/ngc/bgm/bgm_entr.c
new file mode 100644
index 0000000..402256a
--- /dev/null
+++ b/Code/Gel/Music/ngc/bgm/bgm_entr.c
@@ -0,0 +1,49 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "bgm_i.h"
+
+ModuleInfo Module = {"pcmaudio_driver", 0x0102 };
+
+extern int sce_bgm_loop();
+extern volatile int gStThid;
+
+int create_th();
+
+int start ()
+{
+	struct ThreadParam param;
+	int th;
+
+	CpuEnableIntr();
+
+	if( ! sceSifCheckInit() )
+		sceSifInit();
+	sceSifInitRpc(0);
+
+	printf("Entering PCM Audio Driver\n");
+
+	param.attr         = TH_C;
+	param.entry        = sce_bgm_loop;
+	param.initPriority = BASE_priority-2;
+	param.stackSize    = 0x800;
+	param.option       = 0;
+	th = CreateThread(¶m);
+	if (th > 0) {
+		StartThread(th,0);
+		printf("Exit PCM Audio Driver\n");
+		return 0;
+	}else{
+		return 1;
+	}
+}
+
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
+
diff --git a/Code/Gel/Music/ngc/bgm/bgm_i.h b/Code/Gel/Music/ngc/bgm/bgm_i.h
new file mode 100644
index 0000000..aa752ab
--- /dev/null
+++ b/Code/Gel/Music/ngc/bgm/bgm_i.h
@@ -0,0 +1,58 @@
+//--- Highest bit indicates presence of return value
+#define EzBGM_INIT           0x8000
+#define EzBGM_QUIT           0x0010
+#define EzBGM_OPEN           0x8020
+#define EzBGM_CLOSE          0x0030
+#define EzBGM_PRELOAD        0x0040
+#define EzBGM_START          0x0050
+#define EzBGM_STOP           0x0060
+#define EzBGM_SEEK           0x0070
+#define EzBGM_SETVOL         0x8080
+#define EzBGM_SETVOLDIRECT   0x8090
+#define EzBGM_CLOSE_NO_WAIT  0x0100
+//#define EzBGM_SETMASTERVOL 0x00a0
+#define EzBGM_GETMODE        0x80b0
+#define EzBGM_SETMODE        0x80c0
+#define EzBGM_SDINIT         0x00d0
+
+
+//-- SET AVAILABLE
+#define BGM_MODE_REPEAT_OFF      0x0000
+#define BGM_MODE_REPEAT_DEFAULT  0x0001
+#define BGM_MODE_REPEAT_FORCED   0x0002
+
+#define BGM_MODE_STEREO          0x0000
+#define BGM_MODE_MONO            0x0010
+
+//-- GET ONLY
+#define BGM_MODE_IDLE            0x0000
+#define BGM_MODE_RUNNING         0x1000
+#define BGM_MODE_PAUSE           0x2000
+//#define BGM_MODE_KICKSTART       0x4000
+#define BGM_MODE_TERMINATE       0x8000
+
+
+#define BGM_MASK_STATUS          0x0FFF
+#define BGM_MASK_REPEAT          0xFFF0
+#define BGM_MASK_STEREO          0xFF0F
+
+#define WAV_STEREO_BIT           ( 1 << 1 )
+
+
+/* ----------------------------------------------
+   module ID number
+  ----------------------------------------------- */
+#define EZBGM_DEV   0x000666
+
+
+#define PRINTF(x) printf x
+//#define PRINTF(x) 
+
+#define ERROR(x) printf x
+//#define ERROR(x) 
+
+#define BASE_priority  42
+
+#define OLDLIB 0
+#define TRANS_CH  0
+
diff --git a/Code/Gel/Music/ngc/bgm/bgm_play.c b/Code/Gel/Music/ngc/bgm/bgm_play.c
new file mode 100644
index 0000000..51f78b7
--- /dev/null
+++ b/Code/Gel/Music/ngc/bgm/bgm_play.c
@@ -0,0 +1,797 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "bgm_i.h"
+
+#define DEBUGGING_PLEASE 0
+#if DEBUGGING_PLEASE
+#define Dbg_Printf(A...)  printf(##A) 		
+#else
+#define Dbg_Printf(A...)
+#endif
+
+// wav format ------------------------
+#define RIFF_HEADER_SIZE 44
+typedef struct {
+	unsigned char     chunkID[4];
+	unsigned int      chunkSize;
+	unsigned short*   data;
+} DATAHeader;
+
+typedef struct {
+	unsigned char     chunkID[4];
+	unsigned int      chunkSize;
+	unsigned short    waveFmtType;
+	unsigned short    channel;
+	unsigned int      samplesPerSec;
+	unsigned int      bytesPerSec;
+	unsigned short    blockSize;
+	unsigned short    bitsPerSample;
+} FMTHeader;
+
+typedef struct {
+	unsigned char     chunkID[4];
+	unsigned int      chunkSize;
+	unsigned char     formType[4];
+	FMTHeader         fmtHdr;
+	DATAHeader        dataHdr;
+} RIFFHeader;
+//------------------------------------
+
+typedef struct {
+	unsigned int     size;
+//	unsigned int     offset;
+	unsigned int     pos;
+} BGM_WAVE;
+
+
+BGM_WAVE gWave[2];
+
+int gMemInitializedForCD = 0;
+short gBgmPause[2] = { 0, 0 };
+short gBgmPreload[ 2 ] = { 0, 0 };
+char gFileFromCD[ 2 ] = { 0, 0 };
+char gBgmVolumeSet[2] = { 0, 0 };
+volatile char gFileOpened[2] = { 0, 0 };
+volatile char gBgmIntr[2] = { 0, 0 };
+int gThid = 0;
+int gSem = 0;
+int gFd[2];
+int gBuffRaw[2];
+int gBuffSpu[2] = {0, 0};
+int gRPacketSize[2];
+int gSPacketSize[2];
+int gAllockedSize[2];
+int gBgmVolume[2];
+int gBgmMode[2] = { BGM_MODE_REPEAT_OFF, BGM_MODE_REPEAT_OFF };
+int gBgmStereoMode[ 2 ] = { 0, 0 };
+
+#define MAX_FILENAME_LENGTH		50
+char gFilename[ 2 ][ MAX_FILENAME_LENGTH ];
+
+// Larger buffers will prevent skipping when loading in the background...
+// The more streams, the larger the buffers have to be.
+// Reading from the CDVD will be faster than debug stations from the host over the network,
+// so don't freak if you notice corruption on a dev system w/o CD.
+#define SECTOR_SIZE					2048
+#define NUM_SECTORS_PER_READ		2
+#define BUFFER_SIZE_FOR_HOST_FILE	( 2048 * 132 ) // multiple of 6
+#define BUFFER_SIZE_FOR_CD_FILE		( SECTOR_SIZE * NUM_SECTORS_PER_READ * 6 )
+
+#define RING_BUFFER_NUM_SECTORS		48
+#define RING_BUFFER_ALLIGNMENT		64
+#define RING_BUFFER_NUM_PARTITIONS	3
+unsigned char gRingBuffer[ 2 ][ RING_BUFFER_NUM_SECTORS * 2048 ] __attribute__((aligned ( RING_BUFFER_ALLIGNMENT )));
+unsigned char gCDBuffSpu[ 2 ][ BUFFER_SIZE_FOR_CD_FILE ]__attribute__((aligned ( 64 )));
+
+int  _BgmPlay( int status );
+void _BgmRaw2Spu(u_long *src, u_long *dst,  u_long block_count );
+void _BgmRaw2SpuMono(u_long *src, u_long *dst,  u_long block_count );
+int	BgmPreLoad( int ch, int status );
+void SetStereoOn( int ch, int stereo );
+
+static int IntFunc0( void* common )
+{
+	gBgmIntr[0] = 1;
+	iSignalSema( gSem );
+	return 1;  //-- Required for re-enabling interrupts
+}
+
+static int IntFunc1( void* common )
+{
+	gBgmIntr[1] = 1;
+	iSignalSema( gSem );
+	return 1;  //-- Required for re-enabling interrupts
+}
+
+
+static int makeMyThread( void )
+{
+    struct ThreadParam param;
+    int	thid;
+
+    param.attr         = TH_C;
+    param.entry        = _BgmPlay;
+    param.initPriority = BASE_priority-3;
+    param.stackSize    = 0x800;
+    param.option = 0;
+
+    /* Create thread */
+	thid = CreateThread(¶m);
+
+	return thid;
+}
+
+static int makeMySem( void )
+{
+    struct SemaParam sem;
+
+    sem.initCount = 0;
+    sem.maxCount = 1;
+    sem.attr = AT_THFIFO;
+
+    /* Create semaphore.  */
+    return CreateSema(&sem);
+}
+
+
+int BgmRaw2Spu( int ch, int which )
+{
+    if ( gBgmStereoMode[ ch ] )
+	{
+		_BgmRaw2Spu( (u_long *)(gBuffRaw[ch]+ (gRPacketSize[ch])*which)
+			, (u_long *)(gBuffSpu[ch] + (gSPacketSize[ch])*which)
+			, gSPacketSize[ch]/1024 );
+	}
+	else
+	{
+		_BgmRaw2SpuMono ((u_long *)(gBuffRaw [ch] + (gRPacketSize [ch]) * which),
+				 (u_long *)(gBuffSpu [ch] + (gSPacketSize [ch]) * which),
+				 gSPacketSize [ch] / 1024);
+    }
+	return 0;
+}
+
+// returns zero if no longer playing...
+int BgmSetVolumeDirect( int ch, unsigned int vol )
+{
+	sceSdSetParam( (ch)|SD_P_BVOLR, vol>>16 );
+	sceSdSetParam( (ch)|SD_P_BVOLL, vol&0xFFFF);
+	return ( gBgmMode[ch] & ~BGM_MASK_STATUS );
+}
+
+
+
+void BgmSdInit(int ch, int status )
+{
+	// This is already called from the soundFX module.
+	//sceSdInit(0);
+	return;
+}
+
+
+int BgmInit( int ch, int useCD )
+{
+
+	if( gSem == 0 ){
+		gSem = makeMySem();
+	}
+	if( gThid == 0 ){
+		gThid = makeMyThread();
+		Dbg_Printf("PCM Audio Streamer: create thread ID= %d, ", gThid );
+		/* Activate thread  */
+		StartThread( gThid, (u_long)NULL );
+	}
+
+	if ( !gBuffSpu[ ch ] )
+	{
+		gAllockedSize[ch] = useCD ? BUFFER_SIZE_FOR_CD_FILE : BUFFER_SIZE_FOR_HOST_FILE;
+		if ( useCD )
+		{
+			gBuffSpu[ ch ] = ( int )gCDBuffSpu[ ch ]; //(int)AllocSysMemory( 0, gAllockedSize[ ch ], NULL );
+		}
+		else
+		{
+			gBuffSpu[ ch ] = ( int )AllocSysMemory( 0, gAllockedSize[ ch ], NULL );
+		}
+		Dbg_Printf(" PCM spu buffer mem: 0x%x - 0x%x\n", gBuffSpu[ch], gBuffSpu[ch]+gAllockedSize[ ch ] );
+	}
+	// the memory should work for mono or stereo 44k files ( raw buffer always the same... )
+	gRPacketSize[ ch ] = gAllockedSize[ ch ] / 6;
+	gBuffRaw[ ch ] = ( int )( gBuffSpu[ ch ] + ( gRPacketSize[ ch ] * 4 ) );
+	gMemInitializedForCD = useCD;
+
+	return  gThid;
+}
+
+void BgmQuit(int ch, int status )
+{
+	if ( ( gBuffSpu[ ch ] ) && ( gBuffSpu[ ch ] != ( int ) gCDBuffSpu ) )
+		FreeSysMemory( (void*)gBuffSpu[ch] );
+	gBuffSpu[ch] = 0;
+	//-- Frees resources unless another channel is used.
+	if( gBuffSpu[1-ch] == 0 ){
+		if (gThid != 0) TerminateThread( gThid );
+		if (gSem != 0) DeleteSema( gSem );
+		if (gThid != 0) DeleteThread( gThid );
+		gThid = 0;
+		gSem = 0;
+	}
+	return;
+}
+
+/*
+void WaitForCDReady( char *description )
+{
+	printf( "wait %s", description );
+	while( SCECdComplete != sceCdDiskReady( 1 ) )
+	{
+		printf( "." );
+	}
+	printf( "\n" );
+}*/
+
+#define NUM_BYTES_TO_CLEAR	512 // must be divisible by 4!!!
+
+int BgmOpen( int data, char* filename )
+{
+//	unsigned int channel;
+	RIFFHeader wavHdr;
+	int offset;
+	int *addr;
+	int i;
+	int ch = data & 1;
+	int stereo = data & WAV_STEREO_BIT;
+	
+	SetStereoOn( ch, stereo );
+
+	for ( i = 0; i < 2; i++ )
+	{
+		if ( gFileOpened[ i ] )
+		{
+			printf( "%s: Failed: Audio stream already running: %s\n", filename, gFilename[ i ] );
+//			Dbg_Printf( "File already opened on that channel...\n" );
+			return -1;
+		}
+	}
+	printf("pcm filename %s\n", filename );
+	
+	if ( !strncmp( filename, "host", 4 ) )
+	{
+		if ( gMemInitializedForCD )
+		{
+			printf( "Loading file from host when mem configured for CD... SOUND QUALITY WILL SUCK.\n" );
+			printf( "If calling ezBGM_INIT with useCD (2nd param), send CDROM0 in filename...\n" );
+		}
+		
+		// load the file from the dev system...
+		if (( gFd[ch] = open ( filename, O_RDONLY)) < 0)
+		{
+			ERROR(("file open failed. %s \n", filename));
+			return -1;
+		}
+		
+		if ( read (gFd[ch], (unsigned char*)(&wavHdr)
+			,RIFF_HEADER_SIZE ) != RIFF_HEADER_SIZE ) {
+			ERROR(("file read failed. %s \n", filename));
+			return -1;
+		}
+		if ( wavHdr.fmtHdr.channel == 2 )
+		{
+			if ( !stereo )
+				printf( "WARNING: Playing stereo sound %s in mono mode.\n", filename );
+		}
+		else if ( stereo )
+		{
+			printf( "WARNING: Playing mono sound %s in stereo mode.\n", filename );
+		}
+		
+		gWave[ch].size   = wavHdr.dataHdr.chunkSize;
+		gWave[ch].pos    = 0;
+	
+		//--- Seek to start of data
+		offset = (unsigned int)&(wavHdr.dataHdr.data) -(unsigned int)&wavHdr;
+	
+		lseek( gFd[ ch ], offset , SEEK_SET );
+	
+		gFileFromCD[ ch ] = 0;
+		gFileOpened[ ch ] = 1;
+		gBgmPreload[ ch ] = 3;
+	}
+	else
+	{
+		sceCdlFILE fp;
+		sceCdRMode mode;
+		int retVal;
+		unsigned int stringLength;
+		unsigned int lsn;
+		int foundCDData = 0;
+		int byteIndex;
+		// open a stream and load from CD...
+		// 1st arg: size in num sectors ( actual size / 2048 ).
+//		WaitForCDReady( "st init" );
+//		sceCdDiskReady( 0 );
+		sceCdStInit( RING_BUFFER_NUM_SECTORS, RING_BUFFER_NUM_PARTITIONS, ( u_int )( gRingBuffer[ ch ] ) ); //+ ( RING_BUFFER_ALLIGNMENT - 1 ) )  );
+//		printf( "wait for ready." );
+//		sceCdDiskReady( 0 );
+//		WaitForCDReady( "init" );
+//		printf( "cdSearchFile\n" );
+		stringLength = strlen( filename );
+		if ( filename[ stringLength + 1 ] == '@' )
+		{
+			foundCDData = 1;
+			lsn = 0;
+			for ( byteIndex = 0; byteIndex < 4; byteIndex++ )
+			{
+				lsn |= ( ( ( unsigned int )( filename[ stringLength + 2 + byteIndex ] ) ) & 255 ) << ( 8 * byteIndex );
+			}
+		}
+		if ( !foundCDData )
+		{
+			if ( !( retVal = sceCdSearchFile( &fp, filename ) ) )
+			{
+				ERROR(("cd filesearch failed %d. file %s \n", retVal, filename));
+				return -1;
+			}
+			lsn = fp.lsn;
+		}
+	
+//		WaitForCDReady( "find" );
+//		sceCdDiskReady( 0 );
+		mode.trycount = 0;
+		mode.spindlctrl = SCECdSpinNom;
+		mode.datapattern = SCECdSecS2048;
+		sceCdStStart( lsn, &mode );
+		gFileFromCD[ ch ] = 1;
+		gBgmPreload[ ch ] = 3;
+		gFileOpened[ ch ] = 1;
+		gWave[ ch ].pos = 0;
+		gWave[ ch ].size = gRPacketSize[ ch ] * 4;
+	}
+	
+//	printf("wave size %d  offset %d\n", gWave[ch].size, gWave[ch].offset );
+	
+	BgmSetVolumeDirect( ch, 0 );
+
+	strncpy( gFilename[ ch ], filename, MAX_FILENAME_LENGTH  );
+
+	// clear out the end of the second half of the last buffer...
+	addr = ( int * )( gBuffRaw[ ch ] + ( gRPacketSize[ ch ] ) + ( gRPacketSize[ ch ] >> 1 ) );
+	for ( i = 0; i < ( ( gRPacketSize[ ch ] >> 1 ) >> 2 ); i++ )
+	{
+		*( addr++ ) = 0;
+	}
+	
+	if( ch == 0 )
+	{
+		sceSdSetTransCallback( ch, IntFunc0 );
+	}
+	else
+	{
+		sceSdSetTransCallback( ch, IntFunc1 );
+	}
+		
+	return ( gWave[ ch ].size );
+}
+
+void _BgmClose(int ch, int status)
+{
+	if ( !gFileOpened[ ch ] )
+	{
+		PRINTF(("Calling BgmClose when no file opened on that channel...\n"));
+		return;
+	}
+	Dbg_Printf( "_BgmClose\n" );
+	if ( gFileFromCD[ ch ] )
+	{
+		sceCdStStop( );
+	}
+	else
+	{
+		close( gFd[ch] );
+		//FreeSysMemory( ( void* )gBuffSpu[ ch ] );
+		//gBuffSpu[ ch ] = 0;
+	}
+	gFileOpened[ ch ] = 0;
+	return;
+}
+
+void BgmStart(int ch, int status )
+{
+	int retVal;
+	if ( !gFileOpened[ ch ] )
+	{
+		printf( "trying to start track not loaded.\n" );
+		return;
+	}
+	Dbg_Printf( "starting block trans from bgm start/unpause\n" );
+	if ( gBgmPause[ ch ] )
+	{
+		Dbg_Printf( "Starting music on channel where pause is requested...\n" );
+		gBgmPause[ ch ] = 0;
+	}
+	while( -1 == sceSdBlockTrans( ch, SD_TRANS_MODE_WRITE|SD_BLOCK_LOOP, (u_char*)gBuffSpu[ch], (gSPacketSize[ch]*2) ) )
+	{
+		for ( retVal = 0; retVal < 10000; retVal++ )
+			;
+		Dbg_Printf( "failed to start loop.\n" );
+	}
+	if ( !( gBgmPreload[ ch ] ) )
+	{
+		BgmSetVolumeDirect(ch, gBgmVolume[ch]);
+	}
+	gBgmMode[ch] &= BGM_MASK_STATUS;
+	gBgmMode[ch] |= BGM_MODE_RUNNING;
+
+	return;
+}
+
+void BgmCloseNoWait( int ch, int status )
+{
+	if ( !gFileOpened[ ch ] )
+	{
+		PRINTF(("Calling BgmClose when no file opened on that channel...\n"));
+		return;
+	}
+	if ( gBgmPause[ ch ] )
+	{
+		printf( "stopping music when pause is requested..." );
+		return;
+	}
+	// if the pause has already stopped the callback, it's safe to unload now:
+	if ( gBgmMode[ ch ] & BGM_MODE_PAUSE )
+	{
+		// callback isn't happening right now... can terminate with no worries here:
+		Dbg_Printf( "unpausing from BgmClose\n" );
+		gBgmMode[ch] &= BGM_MASK_STATUS; // Switch to IDLE mode
+		_BgmClose( ch, status );
+		Dbg_Printf( "calling _BgmClose from BgmClose\n" );
+		return;
+	}
+	gBgmMode[ch] &= BGM_MASK_STATUS;
+	gBgmMode[ch] |= BGM_MODE_TERMINATE;
+}
+
+void BgmClose( int ch, int status )
+{
+	int i;
+	BgmCloseNoWait( ch, status );
+	printf( "PCM close" );
+	while ( ( gBgmMode[ ch ] & BGM_MODE_TERMINATE ) )
+	{
+		for ( i = 0; i < 20000; i++ )
+			;
+		printf( "." );
+	}
+	printf( "\n" );
+}
+
+int BgmPreLoad( int ch, int status )
+{
+	if ( ( status == 666 ) || ( ( gWave[ ch ].size - gWave[ ch ].pos ) < gRPacketSize[ ch ] * 4 ) )
+	{
+		int i;
+		int *addr;
+		Dbg_Printf( "preloading at end of song. zero buffers.\n" );
+		// we're at the end of the song... fill up read buffers with zero,
+		// and convert...
+		addr = ( int * )( gBuffRaw[ ch ] );
+		for ( i = 0; i < ( ( gRPacketSize[ ch ] * 2 ) >> 2 ); i++ )
+		{
+			*( addr++ ) = 0;
+		}
+		// fill up the spu with both buffers...
+		BgmRaw2Spu( ch, 0 );
+		BgmRaw2Spu( ch, 1 );
+		return ( 0 );
+	}
+	
+	// it's still in preload mode... no need to preload ourselves...
+	if ( gBgmPreload[ ch ] == 3 )
+		return ( 0 );
+
+	if ( gFileFromCD[ ch ] )
+	{
+		unsigned int err;
+		sceCdStRead( NUM_SECTORS_PER_READ * 2, ( u_int * )gBuffRaw[ ch ], STMBLK, &err );
+		if ( err )
+		{
+			printf( "PCM A Disk error code 0x%08x\n", err );
+		}
+
+		// fill up the spu with both buffers...
+		BgmRaw2Spu( ch, 0 );
+		BgmRaw2Spu( ch, 1 );
+		
+		// fill up the buffers again...
+		sceCdStRead( NUM_SECTORS_PER_READ * 2, ( u_int * )gBuffRaw[ ch ], STMBLK, &err );
+		if ( err )
+		{
+			printf( "PCM B Disk error code 0x%08x\n", err );
+		}
+		
+		gWave[ ch ].pos += gRPacketSize[ ch ] * 2;
+		return 0;
+	}
+	// reading from a file...
+	if ( read (gFd[ch], (unsigned char*)(gBuffRaw[ch])
+	, gRPacketSize[ch]*2 ) != gRPacketSize[ch]*2 ) {
+		ERROR (("BgmPreLoad: read failed \n")); return -1;
+	}
+
+	BgmRaw2Spu( ch, 0 );
+	BgmRaw2Spu( ch, 1 );
+
+	if ( read (gFd[ch], (unsigned char*)(gBuffRaw[ch])
+	, (gRPacketSize[ch]*2) ) != gRPacketSize[ch]*2) {
+		ERROR (("BgmPreLoad: read failed \n")); return -1;
+	}
+
+	gWave[ch].pos += gRPacketSize[ch]*4;
+
+	return 0;
+}
+
+void _BgmPause(int ch, int status)
+{
+	Dbg_Printf( "stopping spu block trans in _BgmPause\n" );
+	sceSdBlockTrans( ch, SD_TRANS_MODE_STOP, NULL, 0 );
+	gBgmMode[ch] &= BGM_MASK_STATUS; // Switch to PAUSE mode
+	gBgmMode[ch] |= BGM_MODE_PAUSE;
+	return;
+}
+
+void _BgmStopAndUnload(int ch, int status)
+{
+	Dbg_Printf( "stopping block trans from _BgmStopAndUnload\n" );
+	sceSdBlockTrans( ch, SD_TRANS_MODE_STOP, NULL, 0 );
+	gBgmMode[ch] &= BGM_MASK_STATUS; // Switch to IDLE mode
+	Dbg_Printf( "bgm close from _BgmStopAndUnload\n" );
+	_BgmClose( ch, status );
+	return;
+}
+
+// should be fucking called "BgmPause"
+void BgmStop( int ch, unsigned int vol )
+{
+	int i;
+
+	if ( gBgmMode[ ch ] & BGM_MODE_TERMINATE )
+	{
+		Dbg_Printf( "trying to pause PCM when stopped.\n" );
+		return;
+	}
+	if ( !gBgmMode[ ch ] & BGM_MODE_RUNNING )
+	{
+		Dbg_Printf( "trying to pause PCM when not running.\n" );
+		return;
+	}
+	gBgmPause[ch] = 1;
+	printf( "waiting for pause" );
+	while ( !( gBgmMode[ ch ] & BGM_MODE_PAUSE ) )
+	{
+		for ( i = 0; i < 10000; i++ )
+			;
+		printf( "." );
+	}
+	printf( "\n" );
+	// don't set the flag until the callback has paused it:
+//	gBgmMode[ch] &= BGM_MASK_STATUS;
+//	gBgmMode[ch] |= BGM_MODE_PAUSE;
+	return;
+}
+
+
+int BgmSetVolume( int ch, unsigned int vol )
+{
+	gBgmVolumeSet[ch] = 1;
+	gBgmVolume[ch] = vol;
+	return ( gBgmMode[ch] & ~BGM_MASK_STATUS );
+}
+
+
+unsigned int BgmGetMode( int ch, int status )
+{
+	return gBgmMode[ch];
+}
+
+void BgmSeek( int ch, unsigned int value )
+{
+//	lseek(gFd[ch], gWave[ch].offset+value, SEEK_SET );
+//	gWave[ch].pos = value;
+	printf( "seek not supported\n" );
+	return;
+}
+
+void SetStereoOn( int ch, int stereo )
+{
+    if ( stereo )
+	{
+		gSPacketSize[ ch ] = gAllockedSize[ ch ] / 6;
+		gBgmStereoMode[ ch ] = 1;
+	}
+	else
+	{
+		gSPacketSize[ ch ] = gAllockedSize [ ch ] / 3;
+		gBgmStereoMode[ ch ] = 0;
+	}
+}
+
+void PreloadFile( int which, int ch )
+{
+	switch ( gBgmPreload[ ch ] )
+	{
+		case ( 3 ):
+			if ( gFileFromCD[ ch ] )
+			{
+				RIFFHeader *pWavHdr;
+				int i;
+				int offset;
+				unsigned char *pData;
+				pWavHdr = ( RIFFHeader * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which );
+				if ( pWavHdr->dataHdr.chunkSize < gWave[ ch ].size )
+				{
+					printf( "BIG WARNING:  Cd audio file currently playing is TOO SMALL!\n" );
+				}
+				if ( pWavHdr->fmtHdr.channel == 2 )
+				{
+					if ( !gBgmStereoMode[ ch ] )
+						printf( "WARNING: Playing stereo sound %s in mono mode.\n", gFilename[ ch ] );
+				}
+				else if ( gBgmStereoMode[ ch ] )
+				{
+					printf( "WARNING: Playing mono sound %s in stereo mode.\n", gFilename[ ch ] );
+				}
+				
+				SetStereoOn( ch, pWavHdr->fmtHdr.channel == 2 );
+				gWave[ch].size   = pWavHdr->dataHdr.chunkSize;
+				//gWave[ch].offset = (unsigned int)&( pWavHdr->dataHdr.data ) - ( unsigned int )pWavHdr;
+				offset = (unsigned int)&( pWavHdr->dataHdr.data ) - ( unsigned int )pWavHdr;
+				// clear out the area where the header is... don't want some loud obnoxious sounds...
+				pData = ( unsigned char * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which );
+				if ( offset > gRPacketSize[ ch ] )
+					offset = gRPacketSize[ ch ]; // don't fuck up memory if some strange read happened...
+				for ( i = 0; i < offset; i++ )
+				{
+					pData[ i ] = 0;
+				}
+			}
+			break;
+		
+		case ( 2 ):
+			break;
+		
+		case ( 1 ):
+			// turn the volume back up, the spooler is at the beginning of
+			// the good SPU buffer...
+			BgmSetVolume( ch, gBgmVolume[ch] );
+			break;
+		
+		default:
+			printf( "unknown preload state... fire Matt.\n" );
+			break;
+	}
+	--gBgmPreload[ ch ];
+}
+
+int _BgmPlay( int status )
+{
+	int i, ch, read_size, which;
+	int *addr, remain;
+	Dbg_Printf( "entering _BgmPlay\n" );
+	while ( 1 )
+	{
+		//-- Wait for playing of buffer to finish
+		WaitSema(gSem);
+
+		//-- Which channel is the interrupt from?
+		if( (gBgmIntr[0] == 1) && ( ch != 0 ) )  ch = 0;
+		else if( (gBgmIntr[1] == 1) && ( ch != 1 ) )  ch = 1;
+		else if( gBgmIntr[0] == 1 )  ch = 0;
+		else if( gBgmIntr[1] == 1 )  ch = 1;
+		else continue;
+
+		gBgmIntr[ch] = 0;
+
+		which = 1 - (sceSdBlockTransStatus( ch, 0 )>>24);
+
+		//--- Stopped due to end of data (no looping)
+		if( ( gBgmMode[ ch ] & BGM_MODE_TERMINATE ) != 0 )
+		{
+			WaitSema( gSem ); // Wait until another interrupt is received
+			_BgmStopAndUnload( ch, 0 );
+			BgmSetVolumeDirect( ch, 0x0 );
+			continue;
+		}
+
+		//--- Volume change event
+		if ( ( gBgmVolumeSet[ ch ] == 1 ) && ( !gBgmPreload[ ch ] ) )
+		{
+			BgmSetVolumeDirect( ch, gBgmVolume[ch] );
+			gBgmVolumeSet[ch] = 0;
+		}
+
+		//--- Convert buffer
+
+		BgmRaw2Spu( ch, which );
+
+		//--- File READ for buffer
+
+		remain = gWave[ ch ].size - gWave[ ch ].pos;
+		if ( remain > gRPacketSize[ch] )
+		{
+			if ( gFileFromCD[ ch ] )
+			{
+				//--- Not end of data
+				unsigned int err;
+				sceCdStRead( NUM_SECTORS_PER_READ, ( u_int * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which ), STMBLK, &err );
+				if ( err )
+				{
+					printf( "PCM C Disk error code 0x%08x\n", err );
+				}
+				read_size = gRPacketSize[ ch ];
+			}
+			else
+			{
+				read_size = read (gFd[ch], (unsigned char*)(gBuffRaw[ch]+gRPacketSize[ch]*which), gRPacketSize[ch] );
+			}
+			if ( read_size < gRPacketSize[ch] )
+				continue; //retry
+			if ( gBgmPreload[ ch ] )
+			{
+				PreloadFile( which, ch );
+			}
+			gWave[ch].pos += read_size;
+		}
+		else  //--- End of data
+		{
+			if ( gFileFromCD[ ch ] )
+			{
+				//--- Not end of data
+				unsigned int err;
+				sceCdStRead( NUM_SECTORS_PER_READ, ( u_int * )( gBuffRaw[ ch ]+gRPacketSize[ch]*which ), STMBLK, &err );
+				if ( err )
+				{
+					printf( "PCM C Disk error code 0x%08x\n", err );
+				}
+				read_size = gRPacketSize[ ch ];
+			}
+			else
+			{
+				read_size = read (gFd[ch], (unsigned char*)(gBuffRaw[ch]+gRPacketSize[ch]*which), remain );
+			}
+			
+			if( read_size < remain ) continue; //retry
+	    	
+			PRINTF(("end of PCM track - ch %d\n", ch));
+
+			addr = ( int * )( gBuffRaw[ ch ] + ( gRPacketSize[ ch ] * which ) + remain );
+			for( i = 0; i < ((gRPacketSize[ch]-remain)>>2); i++ )
+			{
+				*(addr++) = 0;
+			}
+			gBgmMode[ch] &= BGM_MASK_STATUS;
+			gBgmMode[ch] |= BGM_MODE_TERMINATE;
+		}
+
+		//-- Stop event
+		if( (gBgmPause[ch] == 1) )
+		{
+			//printf( "_BgmPlay :: pause\n" );
+			_BgmPause( ch, 0 );
+			BgmSetVolumeDirect(ch, 0x0);
+			gBgmPause[ch] = 0;
+		}
+	}
+	return 0;
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* This file ends here, DON'T ADD STUFF AFTER THIS */
+
+// Ha ha I added some stuff... FUCK YOU SONY!
diff --git a/Code/Gel/Music/ngc/bgm/bgm_r2s.s b/Code/Gel/Music/ngc/bgm/bgm_r2s.s
new file mode 100644
index 0000000..9850f3e
--- /dev/null
+++ b/Code/Gel/Music/ngc/bgm/bgm_r2s.s
@@ -0,0 +1,129 @@
+/* SCEI CONFIDENTIAL
+ "PlayStation 2" Programmer Tool Runtime Library  Release 2.0
+ */
+/* 
+ *                  I/O Proseccor sample program
+ *                          Version 1.20
+ *                           Shift-JIS
+ *
+ *      Copyright (C) 1998-1999 Sony Computer Entertainment Inc.
+ *                        All Rights Reserved.
+ *
+ *                       ezbgm.irx - bgm_r2s.c
+ *                          raw to spu pcm
+ *
+ *   Version   Date            Design    Log
+ *  --------------------------------------------------------------------
+ *   1.20      Nov.23.1999     morita    modify for EzBGM
+ *   0.01      Nov.18.1999     ishii     optimize for IOP
+ */
+
+#include 
+
+#define src  a0
+#define dst  a1
+#define blk  a2
+#define cnt  a3
+
+	.globl	_BgmRaw2Spu
+	.ent	_BgmRaw2Spu
+_BgmRaw2Spu:
+	subu	sp, (8*4)
+	sw	s0, 0*4(sp)	; 	sw	s1, 1*4(sp)
+	sw	s2, 2*4(sp)	; 	sw	s3, 3*4(sp)
+	sw	s4, 4*4(sp)	; 	sw	s5, 5*4(sp)
+	sw	s6, 6*4(sp)	; 	sw	s7, 7*4(sp)
+	move	v0, zero
+	move	v1, zero
+	addi	cnt,zero, 256
+
+					.set noreorder
+	/* ‚±‚±‚©‚çAŽè‚Å–½—ß‚Ì‚È‚ç‚Ñ‚ð§ŒäB */
+pcm_separate_loop:
+	lw	t0, 0*4(src)	/* 1 + 4 clock */
+	lw	t1, 1*4(src)	/* 1 + 2 clock */
+
+	/* 7 clock */
+	and	s0, t0, 0xffff		/* t0 ‚̉ºˆÊ‚ð s0 ‚Ö */
+	srl	s1, t0, 16		/* t0 ‚ÌãˆÊ‚ð s1 ‚Ö */
+	sll	t0, t1, 16		/* t1 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s0 ‚Ö */
+	or	s0, t0
+	srl	t1, t1, 16		/* t1 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s1 ‚Ö */
+	sll	t1, t1, 16
+	or	s1, t1
+
+	/* Šù‚ɃLƒƒƒbƒVƒ…‚É“Ç‚Ýž‚Ü‚ê‚Ä‚¢‚é‚Í‚¸ */
+	lw	t2, 2*4(src)	/* 1 clock */
+	lw	t3, 3*4(src)	/* 1 clock */
+	/* ŽŸ‚̃LƒƒƒbƒVƒ…ƒ‰ƒCƒ“‚ð“Ç‚Ýž‚Þ‚«‚Á‚©‚¯‚ðì‚é */
+	lw	t4, 4*4(src)	/* 1 + 4 clock */
+
+	/* 7 clock */
+	and	s2, t2, 0xffff		/* t2 ‚̉ºˆÊ‚ð s2 ‚Ö */
+	srl	s3, t2, 16		/* t2 ‚ÌãˆÊ‚ð s3 ‚Ö */
+	sll	t2, t3, 16		/* t3 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s2 ‚Ö */
+	or	s2, t2
+	srl	t3, t3, 16		/* t3 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s3 ‚Ö */
+	sll	t3, t3, 16
+	or	s3, t3
+
+	/* Šù‚ɃLƒƒƒbƒVƒ…‚É“Ç‚Ýž‚Ü‚ê‚Ä‚¢‚é‚Í‚¸ */
+	lw	t5, 5*4(src)	/* 1 clock */
+	lw	t6, 6*4(src)	/* 1 clock */
+	lw	t7, 7*4(src)	/* 1 clock */
+	add	src, 8*4	/* 1 clock */
+
+	/* 7 clock */
+	and	s4, t4, 0xffff		/* t4 ‚̉ºˆÊ‚ð s4 ‚Ö */
+	srl	s5, t4, 16		/* t4 ‚ÌãˆÊ‚ð s5 ‚Ö */
+	sll	t4, t5, 16		/* t5 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s4 ‚Ö */
+	or	s4, t4
+	srl	t5, t5, 16		/* t5 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s5 ‚Ö */
+	sll	t5, t5, 16
+	or	s5, t5
+
+	/* store  dst1(s0, s2),  dst2(s1, s3) */
+	/* 4 clock */
+
+	sw	s0, 0*4(dst)	;	sw	s2, 1*4(dst)
+	sw	s1, (0*4+512)(dst); 	sw	s3, (1*4+512)(dst)
+	/* ˆÈ‰º‚Æ•½s‚µ‚Ä +2, 4+2 clock ‚Å‘‚«ž‚Ü‚ê‚é‚Í‚¸ */
+
+	/* 7 clock */
+	and	s6, t6, 0xffff		/* t6 ‚̉ºˆÊ‚ð s6 ‚Ö */
+	srl	s7, t6, 16		/* t6 ‚ÌãˆÊ‚ð s7 ‚Ö */
+	sll	t6, t7, 16		/* t7 ‚̉ºˆÊ‚ðã‚ɃVƒtƒg‚µ s6 ‚Ö */
+	or	s6, t6
+	srl	t7, t7, 16		/* t7 ‚ÌãˆÊ‚ðƒ}ƒXƒN‚µ‚Ä s7 ‚Ö */
+	sll	t7, t7, 16
+	or	s7, t7
+
+	/* store  dst1(s4, s6),  dst2(s5, s7) */
+	/* 4 clock */
+	sw	s4, 2*4(dst)	; 	sw	s6, 3*4(dst)
+	sw	s5, (2*4+512)(dst)	; 	sw	s7, (3*4+512)(dst)
+	/* ˆÈ‰º‚Æ•½s‚µ‚Ä +2, 4+2 clock ‚Å‘‚«ž‚Ü‚ê‚é‚Í‚¸ */
+
+	add	dst, 4*4		/* 1 clock */
+
+
+	/* ‚±‚±‚Ü‚ÅAŽè‚Å–½—ß‚Ì‚È‚ç‚Ñ‚ð§ŒäB */
+					.set reorder
+
+	add	v0, 8			/* 1 clock */
+	blt	v0, cnt, pcm_separate_loop	/* 1 clock */
+
+	add	dst, 512
+	move	v0, zero
+
+	add	v1, 1
+	blt	v1, blk, pcm_separate_loop
+
+	lw	s0, 0*4(sp)	; 	lw	s1, 1*4(sp)
+	lw	s2, 2*4(sp)	; 	lw	s3, 3*4(sp)
+	lw	s4, 4*4(sp)	; 	lw	s5, 5*4(sp)
+	lw	s6, 6*4(sp)	; 	lw	s7, 7*4(sp)
+	addu	sp, (8*4)
+	j	ra
+	.end	_BgmRaw2Spu
+
diff --git a/Code/Gel/Music/ngc/bgm/bgm_r2sm.c b/Code/Gel/Music/ngc/bgm/bgm_r2sm.c
new file mode 100644
index 0000000..fdf005a
--- /dev/null
+++ b/Code/Gel/Music/ngc/bgm/bgm_r2sm.c
@@ -0,0 +1,20 @@
+#include 
+
+#define SPU_BLOCK 512
+
+// Using SP will speed things up, but ...
+
+void _BgmRaw2SpuMono( unsigned int *src, unsigned int *dst, unsigned int block )
+{
+	int i;
+
+	for ( i = 0; i < block; i++ )
+	{
+		memcpy( (void*)((int)dst+i*SPU_BLOCK*2), (void*)((int)src+i*SPU_BLOCK), SPU_BLOCK );
+		memcpy( (void*)((int)dst+i*SPU_BLOCK*2+SPU_BLOCK), (void*)((int)src+i*SPU_BLOCK) , SPU_BLOCK );
+	}
+
+	return;
+}
+
+
diff --git a/Code/Gel/Music/ngc/divx/AUDSimpleAudio.cpp b/Code/Gel/Music/ngc/divx/AUDSimpleAudio.cpp
new file mode 100644
index 0000000..bc96b14
--- /dev/null
+++ b/Code/Gel/Music/ngc/divx/AUDSimpleAudio.cpp
@@ -0,0 +1,790 @@
+#ifndef DVDETH
+/*!
+ ******************************************************************************
+ * \file AUDSimpleAudio.cpp
+ *
+ * \brief
+ *		This file provides the required player audio functions.
+ *
+ * \note
+ *      This is a demonstration source only!
+ *
+ * \date
+ *		08/19/02
+ *		04/28/03 - updated to AX audio playback
+ *
+ * \version
+ *		1.0
+ *
+ * \author
+ *		Thomas Engel
+ *
+ ******************************************************************************
+ */
+
+//
+// Note about sample frequencies on Nintendo GameCube:
+//
+// GameCube's AI knows two native sample frequencies. Generally these are
+// referenced as 48KHz and 32KHz. The DSP (no matter if driven by MusyX or AX)
+// is using 32KHz.
+//
+// The frequencies that are in fact used are not exactly these frequencies, though!
+//
+// While this is not important when handling sound effects or even streamed audio,
+// it is very important when interlaeving audio and video data since buffer under-runs
+// might be triggered if no care is taken.
+//
+// The actual AI output frequencies are 48043Hz and 32028.66Hz.
+//
+// Streamed audio data interleaved with video should match these frequencies as
+// closely as possible.
+//
+
+/******************************************************************************
+ *  INCLUDES
+ ******************************************************************************/
+#include 
+#include 
+
+#include "AUDSimplePlayer.h"
+#include "AUDSimpleAudio.h"
+#include "gel/soundfx/ngc/p_sfx.h"
+#include "gfx/ngc/nx/nx_init.h"
+
+/******************************************************************************
+ *  DEFINES
+ ******************************************************************************/
+
+// Number of audio frames that should be able to be stored prio to being routed into the AX buffer
+#define	AUD_AUDIO_READAHEADFRAMES	0.5f
+
+#define	AUD_AUDIO_AIBUFFERSAMPLES	(2*256)		// 10.6ms of 48KHz data per AI buffer (32 byte multiple), that'll be about 15.9ms at 32Khz
+#define	AUD_AUDIO_NUMAIBUFFERS		2			// Number of AI playback buffers (this has an impact on audio latency. 2 is the minimum needed)
+
+#define	AX_ARAM_BUFFER_SIZE			(AUD_AUDIO_AIBUFFERSAMPLES * AUD_AUDIO_NUMAIBUFFERS)
+//#define	AX_ARAM_LEFT_CHANNEL		0x200000	// @ 4MB (16-Bit addressing for DSP!)
+//#define	AX_ARAM_RIGHT_CHANNEL		(AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE)
+#define	AX_ARAM_LEFT_CHANNEL		( NxNgc::EngineGlobals.aram_music >> 1 )
+#define	AX_ARAM_RIGHT_CHANNEL		(AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE)
+
+#define	AUD_NUM_ARQ_REQUESTS		16
+
+/******************************************************************************
+ *  LOCAL VARIABLES & LOCAL EXTERNAL REFERENCES
+ ******************************************************************************/
+
+extern AudSimplePlayer	audio_player;								// Player instance
+
+static void				*audioReadBuffer;							// Buffer to store audio data received from the data stream
+static u32				audioReadBufferNumSamples;					// Size of the above buffer in samples
+static u32				audioReadBufferWritePos;					// Write position in the above buffer in samples
+static u32				audioReadBufferReadPos;						// Read position in the above buffer in samples
+static u8				audioReadBufferNumChannels;					// Number of channels stored in the read buffer
+
+static void				*audioPlayBuffer[AUD_AUDIO_NUMAIBUFFERS];	// AI playback buffers
+static u8				audioPlayBufferWriteIndex;					// Index to next AI buffer to be written to from the read buffer
+static u32				audioPlayBufferFrq;							// Playback frequency of AI in Hz
+static volatile BOOL	audioPlayBufferEnabled;						// TRUE if playback is enabled. AI will operate at all times, but zeros will be filled in instead of real data if this is FALSE.
+
+static const u32		*audioPlayMaskArray;						// Pointer to an array of channel play masks (each set bit signals an active channel)
+static u32				audioNumPlayMasks;							// Number of play masks specified (0 indicates all channels should be active)
+static u32				audioNumActiveVoices;						// Number of active voices
+
+static AXVPB			*axVoice[2];								// AX voice structures
+static ARQRequest		arqRequest[2][AUD_NUM_ARQ_REQUESTS];		// Enough ARQ request structures for worst case scenario
+static u32				axLastAddr;									// Last known address DSP read from for 1st voice
+static u32				axPlayedSamples;							// Number of samples played on first voice since last update
+static u32				axPlayedSamplesTotal;						// Number of samples played on first voice since playback began
+
+typedef enum VID_AXPHASE {
+		AX_PHASE_STARTUP = 0,
+		AX_PHASE_START,
+		AX_PHASE_PLAY
+		} VID_AXPHASE;
+
+static VID_AXPHASE		axPhase;
+
+
+/******************************************************************************
+ *  LOCAL PROTOTYPES
+ ******************************************************************************/
+
+static void AXCallback(void);
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Initialize audio decoder
+ *
+ *		This function allocates all neccessary memory for the audio processing
+ *		and sets the audio decoder into an idle state, waiting for first data.
+ *		A file must be opened with the VIDSimplePlayer before calling this
+ *		function.
+ *
+ * \return
+ *		FALSE if any problem was detected
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleInitAudioDecoder(void)
+	{
+	u32	 		i, ratio;
+	BOOL 		old;
+	AXPBMIX		axMix[2];
+	AXPBVE 		axVE;
+	AXPBSRC		axSRC;
+	AXPBADDR 	axAddr;
+	AXPBADPCM 	axADPCM;
+
+	// Calculate buffer size to allocate proper memry to keep a bit of "extra" audio data around...
+	audioReadBufferNumSamples = (u32)((f32)AUD_AUDIO_READAHEADFRAMES * audio_player.audioInfo.vaud.frq);
+	audioReadBufferNumChannels = audio_player.audioInfo.vaud.numChannels <= 2 ? audio_player.audioInfo.vaud.numChannels : 2;
+
+	// Allocate read buffer
+	audioReadBuffer = audio_player.cbAlloc(audioReadBufferNumSamples * sizeof(s16) * audio_player.audioInfo.vaud.numChannels);
+	if (audioReadBuffer == NULL)
+		return FALSE;					// error
+	
+	// Reset ring buffer
+	audioReadBufferReadPos = audioReadBufferWritePos = 0;
+
+	// What frquency is best?
+	audioPlayBufferFrq = audio_player.audioInfo.vaud.frq;
+	
+	// Allocate AI playback buffer
+	audioPlayBuffer[0] = audio_player.cbAlloc(2 * sizeof(s16) * AUD_AUDIO_AIBUFFERSAMPLES * AUD_AUDIO_NUMAIBUFFERS);
+	if (audioPlayBuffer[0] == NULL)
+		return FALSE;					// error
+	
+	for(i=1; i> 16);
+	axSRC.ratioLo = (u16)ratio;
+	
+	AXSetVoiceSrcType(axVoice[0],AX_SRC_TYPE_4TAP_16K);
+	AXSetVoiceSrc(axVoice[0],&axSRC);
+	AXSetVoiceSrcType(axVoice[1],AX_SRC_TYPE_4TAP_16K);
+	AXSetVoiceSrc(axVoice[1],&axSRC);
+	
+	*(u32 *)&axAddr.currentAddressHi = AX_ARAM_LEFT_CHANNEL;
+	*(u32 *)&axAddr.loopAddressHi = AX_ARAM_LEFT_CHANNEL;
+	*(u32 *)&axAddr.endAddressHi = AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE - 1;
+	axAddr.format = AX_PB_FORMAT_PCM16;
+	axAddr.loopFlag = AXPBADDR_LOOP_ON;
+	AXSetVoiceAddr(axVoice[0],&axAddr);
+	
+	*(u32 *)&axAddr.currentAddressHi = AX_ARAM_RIGHT_CHANNEL;
+	*(u32 *)&axAddr.loopAddressHi = AX_ARAM_RIGHT_CHANNEL;
+	*(u32 *)&axAddr.endAddressHi = AX_ARAM_RIGHT_CHANNEL + AX_ARAM_BUFFER_SIZE - 1;
+	AXSetVoiceAddr(axVoice[1],&axAddr);
+
+	memset(&axADPCM,0,sizeof(axADPCM));
+	axADPCM.gain = 0x0800;
+	
+	AXSetVoiceAdpcm(axVoice[0],&axADPCM);
+	AXSetVoiceAdpcm(axVoice[1],&axADPCM);
+	
+	AXSetVoiceType(axVoice[0],AX_PB_TYPE_STREAM);
+	AXSetVoiceType(axVoice[1],AX_PB_TYPE_STREAM);
+	
+	AXRegisterCallback( AXCallback );
+	
+	axLastAddr				= AX_ARAM_LEFT_CHANNEL;
+	axPlayedSamples			= AUD_AUDIO_AIBUFFERSAMPLES * AUD_AUDIO_NUMAIBUFFERS;
+	axPlayedSamplesTotal	= 0;
+	axPhase					= AX_PHASE_STARTUP;
+	
+	// All is setup for the voices. We'll start them inside the AX callback as soon as we got data in the ARAM buffers
+	OSRestoreInterrupts(old);
+
+	return TRUE;
+    }
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Shutdown audio decoder and free resources
+ *
+ ******************************************************************************
+ */
+void AUDSimpleExitAudioDecoder(void)
+	{
+	// Yes. Unregister callback & stop AI DMA
+	BOOL old = OSDisableInterrupts();
+	
+	// Register our default AX callback.
+	AXRegisterCallback( Sfx::AXUserCBack );
+	
+	AXSetVoiceState(axVoice[0],AX_PB_STATE_STOP);
+	AXSetVoiceState(axVoice[1],AX_PB_STATE_STOP);
+	AXFreeVoice(axVoice[0]);
+	AXFreeVoice(axVoice[1]);
+	
+	axVoice[0] = axVoice[1] = NULL;
+	
+	OSRestoreInterrupts(old);
+
+	// Free allocated resources...
+	audio_player.cbFree(audioPlayBuffer[0]);
+	audio_player.cbFree(audioReadBuffer);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Stop audio playback without shutting down AI etc.
+ *
+ ******************************************************************************
+ */
+void AUDSimpleAudioReset(void)
+	{
+	BOOL	old;
+
+	old = OSDisableInterrupts();
+	
+	audioReadBufferWritePos = 0;
+	audioReadBufferReadPos = 0;
+	
+	audioPlayBufferEnabled = FALSE;
+		
+	OSRestoreInterrupts(old);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Return some information about current audio stream.
+ *
+ ******************************************************************************
+ */
+void AUDSimpleAudioGetInfo(VidAUDH* audioHeader)
+{
+	ASSERT(audioHeader);
+	memcpy(audioHeader, &audio_player.audioInfo, sizeof(*audioHeader));
+}
+
+
+
+void AUDSimpleAudioSetVolume( u16 vl, u16 vr )
+{
+	AXPBVE axVE;
+	
+	axVE.currentDelta	= 0;
+	axVE.currentVolume	= vl;
+	AXSetVoiceVe( axVoice[0], &axVE );
+	
+	axVE.currentVolume	= vr;
+	AXSetVoiceVe( axVoice[1], &axVE );
+}
+
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *
+ ******************************************************************************
+ */
+static void writeChannelData(s16* dest, u32 channels, const s16** samples, u32 sampleOffset, u32 sampleNum)
+	{
+    u32 j;
+	if(channels == 1)
+		{
+		const s16* in = samples[0] + sampleOffset;
+		for(j = 0; j < sampleNum; j++)
+			*dest++ = in[j];
+		}
+	else
+		{
+		const s16* inL;
+		const s16* inR;
+
+		ASSERT(channels == 2);
+        inL = samples[0] + sampleOffset;
+		inR = samples[1] + sampleOffset;
+        for(j = 0; j < sampleNum; j++)
+			{
+			*dest++ = *inL++;
+			*dest++ = *inR++;
+			}
+		}
+	}
+
+
+u32 AUDSimpleAudioGetFreeReadBufferSamples( void )
+{
+	u32 freeSamples;
+	
+	if( audioReadBufferWritePos >= audioReadBufferReadPos )
+	{
+		freeSamples = audioReadBufferNumSamples - (audioReadBufferWritePos - audioReadBufferReadPos);
+	}
+	else
+	{
+		freeSamples = audioReadBufferReadPos - audioReadBufferWritePos;
+	}
+	return freeSamples;
+}
+
+
+
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Main audio data decode function.
+ *
+ *		This function will be used as a callback from the VIDAudioDecode
+ *		function or is called directely in case of PCM or ADPCM.
+ *
+ * \param numChannels
+ *		Number of channels present in the sample array
+ *
+ * \param samples
+ *		Array of s16 pointers to the sample data
+ *
+ * \param sampleNum
+ *		Number of samples in the array. All arrays have the same amount
+ *		of sample data
+ *
+ * \param userData
+ *		Some user data
+ *
+ * \return
+ *		FALSE if data could not be interpreted properly
+ *
+ ******************************************************************************
+ */
+static BOOL audioDecode(u32 numChannels, const s16 **samples, u32 sampleNum, void* _UNUSED(userData))
+	{
+	u32	 freeSamples;
+	u32	 sampleSize;
+	u32	 len1;
+	BOOL old;
+	
+	// we can only play mono or stereo!
+	ASSERT(numChannels <= 2);
+
+	// Disable IRQs. We must make sure we don't get interrupted by the AI callback.
+	old = OSDisableInterrupts();
+
+	// Did the video decoder just jump back to the beginning of the stream?
+	if (audio_player.readBuffer[audio_player.decodeIndex].frameNumber < audio_player.lastDecodedFrame)
+		{
+		// Yes! We have to reset the internal read buffer and disable any audio output
+		// until we got new video data to display...
+		//
+		// Note: we have to reset our buffers because the stream contains more audio data than
+		// neccessary to cover a single video frame within the first few frames to accumulate
+		// some safety buffer. If the stream would just be allowed to loop we would get an
+		// overflow (unless we used up the extra buffer due to read / decode delays) after a few
+		// loops...
+		//
+		AUDSimpleAudioReset();
+		}
+
+	// Calculate the read buffer's sample size
+	sampleSize = sizeof(s16) * audioReadBufferNumChannels;
+
+	// How many samples could we put into the buffer?
+	if (audioReadBufferWritePos >= audioReadBufferReadPos)
+	{
+		freeSamples = audioReadBufferNumSamples - (audioReadBufferWritePos - audioReadBufferReadPos);
+
+		if( freeSamples < sampleNum )
+		{
+			OSRestoreInterrupts(old);
+			#ifndef FINAL
+			OSReport("*** audioDecode: overflow case 1\n");
+			#endif
+			return FALSE;				// overflow!
+		}
+
+		// We might have a two buffer update to do. Check for it...
+		if ((len1 = (audioReadBufferNumSamples - audioReadBufferWritePos)) >= sampleNum)
+		{
+			// No. We got ourselfs a nice, simple single buffer update.
+			writeChannelData((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,sampleNum);
+		}
+		else
+		{
+			// Dual buffer case
+			writeChannelData((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,len1);
+            writeChannelData((s16 *)audioReadBuffer,numChannels,samples,len1,sampleNum-len1);
+		}
+	}
+	else
+	{
+		freeSamples = audioReadBufferReadPos - audioReadBufferWritePos;
+
+		if (freeSamples < sampleNum)
+		{
+			OSRestoreInterrupts(old);
+			#ifndef FINAL
+			OSReport("*** audioDecode: overflow case 2\n");
+			#endif
+			return FALSE;				// overflow!
+		}
+
+		// We're save to assume to have a single buffer update in any case...
+		writeChannelData((s16 *)((u32)audioReadBuffer + audioReadBufferWritePos * sampleSize),numChannels,samples,0,sampleNum);
+	}
+
+	// Advance write position...
+	audioReadBufferWritePos += sampleNum;
+
+	if (audioReadBufferWritePos >= audioReadBufferNumSamples)
+		audioReadBufferWritePos -= audioReadBufferNumSamples;
+
+	// We're done with all critical stuff. IRQs may be enabled again...
+	OSRestoreInterrupts(old);
+
+	return TRUE;
+	}
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Receive data from bitstream and direct to required encoding facility
+ *
+ *		This function will receive the AUDD chunck from the VIDSimplePlayer's
+ *		decode function.
+ *
+ * \param bitstream
+ *		Pointer to the data of a AUDD chunk
+ *
+ * \param bitstreamLen
+ *		Length of the data in the AUDD chunk pointed to by above pointer
+ *
+ * \return
+ *		FALSE if data could not be interpreted properly
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleAudioDecode(const u8* bitstream, u32 bitstreamLen)
+	{
+
+	// select first two channels by default
+	u32 channelSelectMask = audioPlayMaskArray ? audioPlayMaskArray[0] : 0x3;		
+	
+	u32 headerSize = ((VidAUDDVAUD*)bitstream)->size;
+	if(!VAUDDecode(audio_player.decoder, bitstream+headerSize, bitstreamLen-headerSize, channelSelectMask, audioDecode, NULL))
+		return FALSE;
+	
+	audioPlayBufferEnabled = TRUE;
+	
+	return TRUE;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Change the active audio channels
+ *
+ ******************************************************************************
+ */
+				
+void AUDSimpleAudioChangePlayback(const u32 *playMaskArray,u32 numMasks)
+	{
+	u32		i,b;
+	BOOL	old = OSDisableInterrupts();
+	
+	audioPlayMaskArray = playMaskArray;
+	audioNumPlayMasks = numMasks;
+	
+	// Any playback mask specified?
+	if (audioNumPlayMasks != 0)
+		{
+		// Yes. Count the active voices...
+		audioNumActiveVoices = 0;
+		
+		for(i=0; i= audioReadBufferNumSamples)
+		audioReadBufferReadPos -= audioReadBufferNumSamples;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		AX callback
+ *
+ ******************************************************************************
+ */
+static void AXCallback(void)
+{
+	// First thing to do here is call the regular soundfx callback.
+	Sfx::AXUserCBack();
+
+	u32		availSamples,availFrames,numUpdate,i,n;
+	u32		audioPlayBufferNeededAudioFrames;
+	u32		currentAddr;
+
+	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+	
+	if (axPhase == AX_PHASE_START)
+	{
+		AXSetVoiceState(axVoice[0],AX_PB_STATE_RUN);
+		AXSetVoiceState(axVoice[1],AX_PB_STATE_RUN);
+		axPhase = AX_PHASE_PLAY;
+	}
+		
+	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+	
+	currentAddr = *(u32 *)&axVoice[0]->pb.addr.currentAddressHi;
+
+	if( currentAddr >= axLastAddr )
+	{
+		axPlayedSamples			+= currentAddr - axLastAddr;
+		axPlayedSamplesTotal	+= currentAddr - axLastAddr;
+	}
+	else
+	{
+		axPlayedSamples			+= (( AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE ) - axLastAddr ) + ( currentAddr - AX_ARAM_LEFT_CHANNEL );
+		axPlayedSamplesTotal	+= (( AX_ARAM_LEFT_CHANNEL + AX_ARAM_BUFFER_SIZE ) - axLastAddr ) + ( currentAddr - AX_ARAM_LEFT_CHANNEL );
+	}
+	
+	axLastAddr = currentAddr;
+
+	// If we have played the required number of samples, stop the voice.
+	if( axPlayedSamplesTotal >= audio_player.audioInfo.vaudex.totalSampleCount )
+	{
+		AXSetVoiceState( axVoice[0], AX_PB_STATE_STOP );
+		AXSetVoiceState( axVoice[1], AX_PB_STATE_STOP );
+		audio_player.playbackComplete = true;
+		return;
+	}
+		
+	if( axPlayedSamples >= AUD_AUDIO_AIBUFFERSAMPLES )
+	{
+		audioPlayBufferNeededAudioFrames = axPlayedSamples / AUD_AUDIO_AIBUFFERSAMPLES;
+
+		// Make sure that we never get an underrun we don't notice...
+		if( !( audioPlayBufferNeededAudioFrames <= AUD_AUDIO_NUMAIBUFFERS ))
+		{
+//			OSReport( "AX audio buffer underrun!\n" );
+
+			// Disable playback.
+			audioPlayBufferEnabled = false;
+		}
+
+		// Is actual audio playback enabled?
+		if( audioPlayBufferEnabled )
+		{
+			// How many samples could we get from the read buffer?
+			if( audioReadBufferWritePos >= audioReadBufferReadPos )
+				availSamples = audioReadBufferWritePos - audioReadBufferReadPos;
+			else
+				availSamples = audioReadBufferNumSamples - ( audioReadBufferReadPos - audioReadBufferWritePos );
+			
+			// That's how many audio frames?
+			availFrames = availSamples / AUD_AUDIO_AIBUFFERSAMPLES;
+
+			//OSReport("AX: %d %d (%d)\n",availSamples,audioPlayBufferNeededAudioFrames * VID_AUDIO_AIBUFFERSAMPLES,axPlayedSamples);
+	
+			// So, how many can we update?
+			numUpdate = ( availFrames > audioPlayBufferNeededAudioFrames ) ? audioPlayBufferNeededAudioFrames : availFrames;
+	
+			// If anything... go do it!
+			if( numUpdate != 0 )
+			{
+				axPlayedSamples -= numUpdate * AUD_AUDIO_AIBUFFERSAMPLES;
+				
+				// Perform updates on each AI buffer in need of data...
+				for( i = 0; i < numUpdate; i++ )
+				{
+					u32 leftSource, rightSource, leftTarget, rightTarget;
+
+					// Can we copy everything from a single source or does the data wrap around?
+					if(( n = audioReadBufferNumSamples - audioReadBufferReadPos) < AUD_AUDIO_AIBUFFERSAMPLES )
+					{
+						// It wraps...
+						audioCopy( 0, n );
+						audioCopy( n, AUD_AUDIO_AIBUFFERSAMPLES - n );
+					}
+					else
+					{
+						// We got one continous source buffer
+						audioCopy( 0, AUD_AUDIO_AIBUFFERSAMPLES );
+					}
+						
+					// Make sure the data ends up in real physical memory
+					DCFlushRange( audioPlayBuffer[audioPlayBufferWriteIndex], AUD_AUDIO_AIBUFFERSAMPLES * sizeof( s16 ) * 2 );
+					
+					leftSource	= (u32)audioPlayBuffer[audioPlayBufferWriteIndex];
+					rightSource	= (u32)audioPlayBuffer[audioPlayBufferWriteIndex] + ( AUD_AUDIO_AIBUFFERSAMPLES * sizeof( s16 ));
+					leftTarget	= 2 * ( AX_ARAM_LEFT_CHANNEL + audioPlayBufferWriteIndex * AUD_AUDIO_AIBUFFERSAMPLES );
+					rightTarget	= 2 * ( AX_ARAM_RIGHT_CHANNEL + audioPlayBufferWriteIndex * AUD_AUDIO_AIBUFFERSAMPLES );
+					
+					// Make sure we get this into ARAM ASAP...
+					ARQPostRequest( &arqRequest[0][i%AUD_NUM_ARQ_REQUESTS], 0, ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH, leftSource, leftTarget, AUD_AUDIO_AIBUFFERSAMPLES * sizeof( s16 ), NULL );
+					ARQPostRequest( &arqRequest[1][i%AUD_NUM_ARQ_REQUESTS], 1, ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH, rightSource, rightTarget, AUD_AUDIO_AIBUFFERSAMPLES * sizeof( s16 ), NULL );
+					
+					// Advance write index...
+					audioPlayBufferWriteIndex = (u8)(( audioPlayBufferWriteIndex + 1 ) % AUD_AUDIO_NUMAIBUFFERS );
+				}
+				
+				if( axPhase == AX_PHASE_STARTUP )
+				{
+					axPhase = AX_PHASE_START;
+				}
+			}
+		}
+		else
+		{
+			// Update buffer(s) with silence...
+			axPlayedSamples -= audioPlayBufferNeededAudioFrames * AUD_AUDIO_AIBUFFERSAMPLES;
+			
+			for( i = 0; i < audioPlayBufferNeededAudioFrames; i++ )
+			{
+				u32 leftSource, rightSource, leftTarget, rightTarget;
+
+				memset(audioPlayBuffer[audioPlayBufferWriteIndex],0,2 * sizeof(s16) * AUD_AUDIO_AIBUFFERSAMPLES);
+				DCFlushRange(audioPlayBuffer[audioPlayBufferWriteIndex],2 * sizeof(s16) * AUD_AUDIO_AIBUFFERSAMPLES);
+	
+				leftSource = (u32)audioPlayBuffer[audioPlayBufferWriteIndex];
+				rightSource = (u32)audioPlayBuffer[audioPlayBufferWriteIndex] + (AUD_AUDIO_AIBUFFERSAMPLES * sizeof(s16)) / 2;
+				leftTarget = 2 * (AX_ARAM_LEFT_CHANNEL + audioPlayBufferWriteIndex * AUD_AUDIO_AIBUFFERSAMPLES);
+				rightTarget = 2 * (AX_ARAM_RIGHT_CHANNEL + audioPlayBufferWriteIndex * AUD_AUDIO_AIBUFFERSAMPLES);
+				
+				// Make sure we get this into ARAM ASAP...
+				ARQPostRequest(&arqRequest[0][i%AUD_NUM_ARQ_REQUESTS],0,ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH,leftSource,leftTarget,AUD_AUDIO_AIBUFFERSAMPLES * sizeof(s16),NULL);
+				ARQPostRequest(&arqRequest[1][i%AUD_NUM_ARQ_REQUESTS],1,ARQ_TYPE_MRAM_TO_ARAM,ARQ_PRIORITY_HIGH,rightSource,rightTarget,AUD_AUDIO_AIBUFFERSAMPLES * sizeof(s16),NULL);
+				
+				audioPlayBufferWriteIndex = (u8)((audioPlayBufferWriteIndex + 1) % AUD_AUDIO_NUMAIBUFFERS);
+			}
+			
+			if (axPhase == AX_PHASE_STARTUP)
+				axPhase = AX_PHASE_START;
+		}
+	}
+}
+
+#endif		// DVDETH
+
diff --git a/Code/Gel/Music/ngc/divx/AUDSimpleAudio.h b/Code/Gel/Music/ngc/divx/AUDSimpleAudio.h
new file mode 100644
index 0000000..c0a3d28
--- /dev/null
+++ b/Code/Gel/Music/ngc/divx/AUDSimpleAudio.h
@@ -0,0 +1,75 @@
+/*!
+ ******************************************************************************
+ * \file AUDSimpleAudio.h
+ *
+ * \brief
+ *		Header file for AUDSimpleAudio interface
+ *
+ * \date
+ *		5/22/03
+ *
+ * \version
+ *		1.0
+ *
+ * \author
+ *		Thomas Engel/Achim Moller
+ *
+ ******************************************************************************
+ */
+#ifndef __AUDSIMPLE_AUDIO_H__
+#define __AUDSIMPLE_AUDIO_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/******************************************************************************
+ *  INCLUDES
+ ******************************************************************************/
+#include 
+#include 
+#include "vaud.h"
+
+/******************************************************************************
+ *  DEFINES
+ ******************************************************************************/
+
+/******************************************************************************
+ *  STRUCTS AND TYPES
+ ******************************************************************************/
+
+/******************************************************************************
+ *  PROTOTYPES
+ ******************************************************************************/
+
+//! Initialize audio decoding / playback
+extern BOOL AUDSimpleInitAudioDecoder(void);
+
+//! Shutdown audio decoding / playback
+extern void AUDSimpleExitAudioDecoder(void);
+
+//! Decode audio data for one frame
+extern BOOL AUDSimpleAudioDecode(const u8* bitstream, u32 bitstreamLen);
+
+//! Change currently active audio channels
+extern void AUDSimpleAudioChangePlayback(const u32 *playMaskArray,u32 numMasks);
+
+//! Stop audio decode / output
+extern void AUDSimpleAudioReset(void);
+
+//! Return some information about running audio
+extern void AUDSimpleAudioGetInfo(VidAUDH* audioHeader);
+
+extern u32	AUDSimpleAudioGetFreeReadBufferSamples( void );
+extern void	AUDSimpleAudioSetVolume( u16 vl, u16 vr );
+
+extern void AUDSimplePause( bool pause );
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif	// __AUDSIMPLE_PLAYER_H__
+
+
diff --git a/Code/Gel/Music/ngc/divx/AUDSimplePlayer.cpp b/Code/Gel/Music/ngc/divx/AUDSimplePlayer.cpp
new file mode 100644
index 0000000..94c7fec
--- /dev/null
+++ b/Code/Gel/Music/ngc/divx/AUDSimplePlayer.cpp
@@ -0,0 +1,1146 @@
+#ifndef DVDETH
+/*!
+ ******************************************************************************
+ * \file AUDSimplePlayer.cpp
+ *
+ * \brief
+ *		This file provides the required player control functions.
+ *
+ * \note
+ *      This is a demonstration source only!
+ *
+ * \date
+ *		05/21/03
+ *
+ * \version
+ *		1.0
+ *
+ * \author
+ *		Achim Moller
+ *
+ ******************************************************************************
+ */
+
+/******************************************************************************
+ *  INCLUDES
+ ******************************************************************************/
+#include 
+#include 
+
+#include "AUDSimplePlayer.h"
+#include "AUDSimpleAudio.h"
+
+/******************************************************************************
+ *  GLOABL VARIABLES
+ ******************************************************************************/
+AudSimplePlayer audio_player;
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+
+/******************************************************************************
+ *  LOCAL VARIABLES
+ ******************************************************************************/
+static VidChunk workChunk ATTRIBUTE_ALIGN(32);
+static void dvdDoneCallback(s32 result, DVDFileInfo *videoInfo);
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Initializes all audio_player structures.
+ *
+ ******************************************************************************
+ */
+void AUDSimpleInit(VAUDAllocator _cbAlloc, VAUDDeallocator _cbFree)
+	{
+	memset(&audio_player, 0, sizeof(audio_player));
+	audio_player.cbAlloc = _cbAlloc;
+	audio_player.cbFree = _cbFree;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Request an async file transfer.
+ *
+ *		This function starts the transfer of the next frame into a free
+ *		buffer.
+ *
+ ******************************************************************************
+ */
+static void ReadFrameAsync(void)
+	{
+	if (!audio_player.error && audio_player.preFetchState == TRUE)
+		{
+		if (audio_player.currentFrameCount >  audio_player.audioInfo.vaudex.frameCount - 1)
+			{
+			if (audio_player.loopMode)
+				{
+				audio_player.currentFrameCount = 0;
+				audio_player.nextFrameOffset = audio_player.firstFrameOffset;
+				audio_player.nextFrameSize  = audio_player.firstFrameSize;
+				}
+			else
+				return;
+			}
+		
+		audio_player.asyncDvdRunning = TRUE;
+
+		if (DVDReadAsync(&audio_player.fileHandle,
+						 audio_player.readBuffer[audio_player.readIndex].ptr,
+						 (s32)audio_player.nextFrameSize,
+						 (s32)audio_player.nextFrameOffset, dvdDoneCallback) != TRUE )
+			{
+			audio_player.asyncDvdRunning = FALSE;
+			audio_player.error = TRUE;
+			}
+		}
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		DVD callback if async transfer is finished (or aborted)
+ *	
+ *		The idea here is to transfer ONE frame and additional 32 bytes for the
+ *		HEADER of the NEXT frame in one transfer step. We store the size of
+ *		the next frame, which is used in ReadFrameAsync().
+ *
+ *		
+ * \note
+ *		There a 32 padding bytes at the end of the .vid file. So, the reading
+ *		of 32 additional bytes is even possible for the LAST frame. (Otherwise,
+ *		we would 'point' out of the file)
+ *
+ *		See Dolphin documentation for information about parameters.
+ *
+ ******************************************************************************
+ */
+static void dvdDoneCallback(s32 result, DVDFileInfo * _UNUSED(videoInfo))
+	{
+	if (result == DVD_RESULT_FATAL_ERROR)
+		{
+		audio_player.error = TRUE;
+		return;
+		}
+	else if (result == DVD_RESULT_CANCELED)
+		{
+		return;
+		}
+
+	audio_player.asyncDvdRunning = FALSE;
+
+	audio_player.readBuffer[audio_player.readIndex].frameNumber = audio_player.currentFrameCount;
+	audio_player.readBuffer[audio_player.readIndex].size = (u32)result;	
+	audio_player.readBuffer[audio_player.readIndex].valid = TRUE;
+	
+	audio_player.currentFrameCount++;
+
+	// move file pointer
+	audio_player.nextFrameOffset += audio_player.nextFrameSize;
+	
+	if(audio_player.currentFrameCount < audio_player.audioInfo.vaudex.frameCount)
+		{
+		// set read size for next 'FRAM' chunk
+		u32* nextFrameStart = (u32*)(audio_player.readBuffer[audio_player.readIndex].ptr + audio_player.nextFrameSize - 32);
+	
+		// some check if file structure is okay
+		ASSERT(nextFrameStart[0] == VID_FCC('F','R','A','M'));
+		
+		// get the size of the next 'FRAM' chunk to read
+		audio_player.nextFrameSize = nextFrameStart[1];
+		ASSERT(audio_player.nextFrameSize);
+		}
+	else
+		audio_player.nextFrameSize = 0;	// at end of file we have a size of '0'. This should be reinitialized later
+									// using the size of the first frame somwhere else! Otherwise, we get an assertion
+
+	// use next buffer
+	audio_player.readIndex = (audio_player.readIndex + 1) % AUD_NUM_READ_BUFFERS;
+
+	// continue loading if we have a free buffer
+	if (!audio_player.readBuffer[audio_player.readIndex].valid)
+		ReadFrameAsync();
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Allocate buffer memory for asynchronous dvd read
+ *
+* \return
+ *		TRUE if DVD buffer setup was successful.
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleAllocDVDBuffers(void)
+	{
+	u32 i;
+	u32 bufferSize;
+	u8* ptr;
+
+	bufferSize = audio_player.audioInfo.vaudex.maxBufferSize;
+	ASSERT(bufferSize);
+	
+	bufferSize += VID_CHUNK_HEADER_SIZE;	// 'fram' header
+	bufferSize += VID_CHUNK_HEADER_SIZE;	// 'vidd' header
+	bufferSize = OSRoundUp32B(bufferSize);
+	
+	ASSERT(audio_player.cbAlloc);
+	audio_player.readBufferBaseMem = (u8*)((*audio_player.cbAlloc)(bufferSize * AUD_NUM_READ_BUFFERS));
+	
+	if(!audio_player.readBufferBaseMem)
+		return FALSE;	// out of mem
+	
+	ptr = audio_player.readBufferBaseMem;
+	for (i = 0; i < AUD_NUM_READ_BUFFERS ; i++)
+		{
+		audio_player.readBuffer[i].ptr = ptr;
+		ptr += bufferSize;
+		audio_player.readBuffer[i].valid = FALSE;
+		}
+	
+	return TRUE;
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Free buffer memory used for dvd read
+ *
+ * \param memFree
+ *		Pointer to memory deallocation function
+ *
+ ******************************************************************************
+ */
+void AUDSimpleFreeDVDBuffers(void)
+	{
+	ASSERT(audio_player.cbFree);
+	ASSERT(audio_player.readBufferBaseMem);
+	(*audio_player.cbFree)(audio_player.readBufferBaseMem);
+	}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Create a new decoder instance.
+ *
+ * \return
+ *		TRUE if decoder creation was successful
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleCreateDecoder(void)
+	{
+	u32 skip;
+	
+	audio_player.decoder = VAUDCreateDecoder(audio_player.cbAlloc, audio_player.cbFree, audio_player.audioInfo.vaud.maxHeap);
+	
+	if(!audio_player.decoder)
+		return FALSE;
+
+	ASSERT(audio_player.audioHeaderChunk);
+	ASSERT(audio_player.audioInfo.vaud.maxHeap > 0);
+	ASSERT(audio_player.audioInfo.vaud.preAlloc > 0);
+	skip = VID_CHUNK_HEADER_SIZE + sizeof(u32) + (audio_player.audioInfo.vaudex.version > 0 ? audio_player.audioInfo.vaudex.size : sizeof(VidAUDHVAUD));
+
+	if(!VAUDInitDecoder(audio_player.decoder, audio_player.audioHeaderChunk + skip, ((VidChunk*)audio_player.audioHeaderChunk)->len - skip, audio_player.audioInfo.vaud.preAlloc))
+		{
+		VAUDDestroyDecoder(audio_player.decoder);
+		return FALSE;
+		}
+	
+	return TRUE;
+	}
+/*!
+ ******************************************************************************
+ * \brief
+ *		Destroy decoder instance.
+ *
+ *		At this point the decoder returns all allocated memory by using
+ *		the cbFree callback.
+ *
+ ******************************************************************************
+ */
+void AUDSimpleDestroyDecoder(void)
+{
+	ASSERT(audio_player.decoder);
+	VAUDDestroyDecoder(audio_player.decoder);
+	
+	// Set the decoder to NULL.
+	audio_player.decoder = NULL;
+}
+
+
+
+static u32 readNum = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void AUDSimpleLoadStartDVDCallback( s32 result, DVDFileInfo* fileInfo )
+{
+	// Deal with errors.
+	if( audio_player.error )
+	{
+		return;
+	}
+	
+	audio_player.nextFrameOffset += audio_player.nextFrameSize;
+	audio_player.readBuffer[audio_player.readIndex].size = audio_player.nextFrameSize;
+
+	// Set read size for next 'FRAM' chunk.
+	u32* nextFrame = (u32*)(audio_player.readBuffer[audio_player.readIndex].ptr + audio_player.nextFrameSize - 32);
+
+	// Some sanity check if file structure is valid!
+	ASSERT( nextFrame[0] == VID_FCC( 'F','R','A','M' ));
+
+	audio_player.nextFrameSize = nextFrame[1];
+	ASSERT( audio_player.nextFrameSize );
+
+	audio_player.readBuffer[audio_player.readIndex].valid = TRUE;
+	audio_player.readBuffer[audio_player.readIndex].frameNumber = audio_player.currentFrameCount;
+
+	// Use next buffer.
+	audio_player.readIndex = (audio_player.readIndex + 1) % AUD_NUM_READ_BUFFERS;
+	audio_player.currentFrameCount++;
+	
+	if( --readNum > 0 )
+	{
+		if( DVDReadAsync( &audio_player.fileHandle, audio_player.readBuffer[audio_player.readIndex].ptr, (s32)audio_player.nextFrameSize, (s32)audio_player.nextFrameOffset, AUDSimpleLoadStartDVDCallback ) < 0 )
+		{
+#			ifdef _DEBUG
+			OSReport("*** AUDSimpleLoadStart: Failed to read from file.\n");
+#			endif
+			audio_player.error = TRUE;
+			return;
+		}
+	}
+	else
+	{
+		// All done.
+		audio_player.loopMode		= FALSE;
+		audio_player.preFetchState	= TRUE;
+	}
+}
+
+
+
+
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Preload the allocated buffers.
+ *
+ *		This functions fills all buffers with initial data
+ *
+ * \param loopMode
+ *		TRUE if we want to operate in loop mode
+ *
+ * \return
+ *		TRUE if preload was okay 		
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleLoadStart(BOOL loopMode)
+{
+//	u32		i, readNum;
+//	u32		i;
+//	u32*	nextFrame;
+
+	ASSERT( loopMode == FALSE );
+	
+	if( audio_player.open && audio_player.preFetchState == FALSE )
+	{
+		readNum = AUD_NUM_READ_BUFFERS;
+
+		// In 'non-loop' mode we must take care if we have LESS frames than preloading buffers
+        if( !loopMode && audio_player.audioInfo.vaudex.frameCount < AUD_NUM_READ_BUFFERS )
+			readNum = audio_player.audioInfo.vaudex.frameCount;
+				
+//		for( i = 0; i < readNum; i++ )
+		if( readNum > 0 )
+		{
+			// Read total 'FRAM' chunk and 32 bytes of NEXT chunk.
+//			if( DVDRead( &audio_player.fileHandle, ptr, (s32)audio_player.nextFrameSize, (s32)audio_player.nextFrameOffset ) < 0 )
+			if( DVDReadAsync( &audio_player.fileHandle, audio_player.readBuffer[audio_player.readIndex].ptr, (s32)audio_player.nextFrameSize, (s32)audio_player.nextFrameOffset, AUDSimpleLoadStartDVDCallback ) < 0 )
+			{
+#				ifdef _DEBUG
+				OSReport("*** AUDSimpleLoadStart: Failed to read from file.\n");
+#				endif
+				audio_player.error = TRUE;
+				return FALSE;
+			}
+			
+//			audio_player.nextFrameOffset += audio_player.nextFrameSize;
+//			audio_player.readBuffer[audio_player.readIndex].size = audio_player.nextFrameSize;
+
+            // set read size for next 'FRAM' chunk
+//			nextFrame = (u32*)(audio_player.readBuffer[audio_player.readIndex].ptr + audio_player.nextFrameSize - 32);
+			
+			// some sanity check if file structure is valid!
+//			ASSERT(nextFrame[0] == VID_FCC('F','R','A','M'));
+			
+//			audio_player.nextFrameSize = nextFrame[1];
+//			ASSERT( audio_player.nextFrameSize );
+
+//			audio_player.readBuffer[audio_player.readIndex].valid = TRUE;
+//			audio_player.readBuffer[audio_player.readIndex].frameNumber = audio_player.currentFrameCount;
+
+			// Use next buffer
+//			audio_player.readIndex = (audio_player.readIndex + 1) % AUD_NUM_READ_BUFFERS;
+
+//			audio_player.currentFrameCount++;
+
+//			if (audio_player.currentFrameCount >  audio_player.audioInfo.vaudex.frameCount - 1)
+//			{
+//				if (loopMode)
+//				{
+//					audio_player.currentFrameCount = 0;
+//					audio_player.nextFrameOffset = audio_player.firstFrameOffset;
+//					audio_player.nextFrameSize  = audio_player.firstFrameSize;
+//				}
+//			}
+		}
+//		audio_player.loopMode = loopMode;
+//		audio_player.preFetchState = TRUE;
+		return TRUE;
+	}
+	return FALSE;
+}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Stops the asynchronous loading process.
+ *
+ * \return
+ *		TRUE if audio_player could be stopped!		
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleLoadStop(void)
+	{
+	u32 i;
+
+	if (audio_player.open)
+		{
+		// stop preloading process
+		audio_player.preFetchState = FALSE;
+
+		if (audio_player.asyncDvdRunning)
+			{
+			DVDCancel(&audio_player.fileHandle.cb);
+			audio_player.asyncDvdRunning = FALSE;
+			}
+
+		// invalidate all buffers
+		for (i = 0 ; i < AUD_NUM_READ_BUFFERS; i++)
+			audio_player.readBuffer[i].valid = FALSE;
+
+		audio_player.nextFrameOffset = audio_player.firstFrameOffset;
+		audio_player.nextFrameSize = audio_player.firstFrameSize;
+		audio_player.currentFrameCount = 0;
+		
+		audio_player.error 	   		= FALSE;
+		audio_player.readIndex   		= 0;
+		audio_player.decodeIndex 		= 0;
+
+		return TRUE;
+		}
+	return FALSE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void AUDSimpleOpenDVDCallback( s32 result, DVDFileInfo* fileInfo )
+{
+	static u32	fileOffset		= 0;
+	static u32	headSize		= 0;
+	static u32	audioInfoSize	= 0;
+	
+	// Deal with errors, possibly flagged as an indication to shut down immediately.
+	if( audio_player.error )
+	{
+		// Set this back to zero so the player will be regarded as 'free' again.
+		audio_player.asyncOpenCallbackStatus = 0;
+		return;
+	}
+	
+	switch( audio_player.asyncOpenCallbackStatus )
+	{
+		case 1:
+		{
+			// The read of the 'VID1' chunk has completed.
+			fileOffset = 32;
+	
+			// Check file id.
+			if( workChunk.id != VID_FCC('V','I','D','1' ))
+			{
+#				ifdef _DEBUG
+				OSReport("*** No VID1 file: '%s'\n", fileName);
+#				endif
+				DVDClose( &audio_player.fileHandle );
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				return;
+			}
+	
+			// Check for correct version of vid chunk.
+			// If we find this version we assume a 'special' alignment and chunk ordering which may be invalid
+			// in another version of the file format.
+			if( workChunk.vid.versionMajor != 1 || workChunk.vid.versionMinor != 0 )
+			{
+#				ifdef _DEBUG
+				OSReport("*** Unsupported file version: major: %d, minor: %d\n", workChunk.vid.versionMajor, workChunk.vid.versionMajor);
+#				endif
+				DVDClose( &audio_player.fileHandle );
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				return;
+			}
+	
+#			ifdef _DEBUG
+			// Sometimes, it's required to check for a special build of the VidConv converter.
+			{
+				u32 version = VID_VERSION(workChunk.vid.vidConvMajor, workChunk.vid.vidConvMinor, workChunk.vid.vidConvBuild);
+				if( version < VID_VERSION( 1, 0, 1 ))
+					OSReport("*** WARNING: Vid file created using an unsupported converter version: %d.%d.%d\n", (u32)workChunk.vid.vidConvMajor, (u32)workChunk.vid.vidConvMinor, (u32)workChunk.vid.vidConvBuild);
+			}
+#			endif
+
+			// Set callback status to indicate 'VID1' chunk is read.
+			audio_player.asyncOpenCallbackStatus = 2;
+			
+			// Check types of chunks we have in this file.
+			// !!! Note that we assume start of 'HEAD' chunk at byte offset 32 from file start !!!
+			if( DVDReadAsync( &audio_player.fileHandle, &workChunk, 32, (s32)fileOffset, AUDSimpleOpenDVDCallback ) < 0 )
+			{
+#				ifdef _DEBUG
+				OSReport("*** Failed to read 'HEAD' chunk.\n");
+#				endif
+				DVDClose( &audio_player.fileHandle );
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				return;
+			}
+			break;
+		}
+	
+		case 2:
+		{
+			// The read of the chunk type info has completed.
+			if( workChunk.id != VID_FCC('H','E','A','D' ))
+			{
+#				ifdef _DEBUG
+				OSReport("*** No HEAD chunk found at expected offset\n");
+#				endif
+				DVDClose( &audio_player.fileHandle );
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				return;
+			}
+
+			// Calculate the start of the first frame chunk (we know the header chunk starts at offset 32).
+			audio_player.nextFrameOffset = workChunk.len + 32;
+
+			// Skip 'HEAD' chunk id, len and version fields.
+			fileOffset += VID_CHUNK_HEADER_SIZE;
+
+			// The header chunk contains one or more header chunks for the different data types contained
+			// in the stream. Parse them all...
+
+			headSize = workChunk.len - VID_CHUNK_HEADER_SIZE;
+			
+			audio_player.asyncOpenCallbackStatus = 3;
+			if( DVDReadAsync( &audio_player.fileHandle, &workChunk, 32, (s32)fileOffset, AUDSimpleOpenDVDCallback ) < 0 )
+			{
+#				ifdef _DEBUG
+				OSReport("*** Error reading file at offset %d\n", fileOffset);
+#				endif
+				DVDClose( &audio_player.fileHandle );
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				return;
+			}
+			break;
+		}
+	
+		case 3:
+		{
+			fileOffset	+= 32;
+			headSize	-= 32;
+
+			// We analyze the 1st 32 bytes of the chunk for a known header format.
+			if( workChunk.id == VID_FCC( 'A','U','D','H' ))
+			{
+				// Allocate memory for audio header chunk.
+				audio_player.audioHeaderChunk	= (u8*)((*audio_player.cbAlloc)(workChunk.len));
+				audioInfoSize					= workChunk.len - VID_CHUNK_HEADER_SIZE;
+
+				// Copy the already loaded part.
+				memcpy( audio_player.audioHeaderChunk, &workChunk, 32 );
+				workChunk.len -= 32;
+
+				// Read additional audio header bytes if the audio header is greater that 32 bytes
+				if( workChunk.len >= 32 )
+				{
+					ASSERT(( workChunk.len & 31 ) == 0 );
+					audio_player.asyncOpenCallbackStatus = 4;
+					if( DVDReadAsync( &audio_player.fileHandle, audio_player.audioHeaderChunk + 32, workChunk.len, (s32)fileOffset, AUDSimpleOpenDVDCallback ) < 0 )
+					{
+#						ifdef _DEBUG
+						OSReport("*** Error reading file at offset %d\n", fileOffset);
+#						endif
+						DVDClose( &audio_player.fileHandle );
+						audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+						return;
+					}
+				}
+				else
+				{
+					ASSERT( 0 );
+				}
+			}
+			else
+			{
+				ASSERT( 0 );
+			}
+			break;
+		}
+					
+		case 4:
+		{
+			fileOffset	+= workChunk.len;
+			headSize	-= workChunk.len;
+
+			// Setup and calc the number of bytes which we are allowed to copy into the audioInfo struct
+			memcpy( &audio_player.audioInfo, audio_player.audioHeaderChunk + VID_CHUNK_HEADER_SIZE, MIN( audioInfoSize, sizeof( audio_player.audioInfo )));
+			
+			// Check if we have the correct vaud file version (>0).
+			if( audio_player.audioInfo.vaudex.version == 0 )
+			{
+#				ifdef _DEBUG
+				OSReport("*** Invalid version in vaud file.");
+#				endif
+				DVDClose( &audio_player.fileHandle );
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				return;
+			}
+
+			// We can only play audio files which have the following fields set.
+			// Note that in case of VIDEO files this fields are allowed to be 0.
+			ASSERT( audio_player.audioInfo.vaudex.maxBufferSize > 0 );
+			ASSERT( audio_player.audioInfo.vaudex.frameCount > 0 );
+			ASSERT( audio_player.audioInfo.vaudex.frameTimeMs > 0 );
+
+			// Read beginning of 1st frame chunk to get required size information.
+			audio_player.asyncOpenCallbackStatus = 5;
+			if( DVDReadAsync( &audio_player.fileHandle, &workChunk, 32 , (s32)audio_player.nextFrameOffset, AUDSimpleOpenDVDCallback ) < 0 )
+			{
+#				ifdef _DEBUG
+				OSReport("*** Failed to read 'FRAM' chunk.\n");
+#				endif
+				DVDClose( &audio_player.fileHandle );
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				return;
+			}
+			break;
+		}
+			
+		case 5:
+		{
+			if( workChunk.id != VID_FCC('F','R','A','M') )
+			{
+#				ifdef _DEBUG
+				OSReport("*** No FRAM chunk found.");
+#				endif
+				audio_player.asyncOpenCallbackStatus = ASYNC_OPEN_FAIL;
+				DVDClose( &audio_player.fileHandle );
+				return;
+			}
+
+			// 32 bytes of this chunk are already consumed, but we want to 'preload' the NEXT chunk's FRAM header.
+			audio_player.nextFrameSize				= workChunk.len; 		
+			audio_player.nextFrameOffset			+= 32;
+
+			audio_player.firstFrameOffset			= audio_player.nextFrameOffset;
+			audio_player.firstFrameSize				= audio_player.nextFrameSize;
+
+//			strncpy( audio_player.fileName, fileName, 64 );
+//			audio_player.fileName[63]				= 0;
+
+			audio_player.open 			 			= TRUE;
+
+			audio_player.asyncOpenCallbackStatus	= ASYNC_OPEN_SUCCESS;
+			audio_player.readIndex 					= 0;
+			audio_player.decodeIndex 				= 0;
+			audio_player.lastDecodedFrame			= 0;
+			audio_player.error 			 			= FALSE;
+			audio_player.preFetchState 	 			= FALSE;
+			audio_player.loopMode		 			= FALSE;
+			audio_player.asyncDvdRunning			= FALSE;
+			audio_player.currentFrameCount 			= 0;
+			audio_player.readBufferBaseMem			= 0;
+			break;
+		}
+	
+		default:
+		{
+			ASSERT( 0 );
+			break;
+		}
+	}
+}
+
+
+
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Open video file.
+ *
+ *		This functions opens a video file and parses some basic file
+ *		information.
+ *
+ * \param fileName
+ *		Name of file to open
+ *
+ * \return
+ *		TRUE if file could be opened and is in valid format!
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleOpen( char* fileName )
+{
+//	u32 fileOffset = 0;
+//	u32 headSize;
+//	u32 audioInfoSize;
+	
+	if( audio_player.open )
+	{
+#		ifdef _DEBUG
+		OSReport( "*** Cannot open '%s' because audio_player already open.\n", fileName );
+#		endif
+		return FALSE;
+	}
+
+	// Initialise the callback status.
+	audio_player.asyncOpenCallbackStatus = 0;
+	
+	s32 entry_num = DVDConvertPathToEntrynum( fileName );
+	if( entry_num == -1 )
+	{
+#		ifdef _DEBUG
+		OSReport( "*** Cannot find '%s'\n", filename );
+#		endif
+		return FALSE;
+	}
+	if( DVDFastOpen( entry_num, &audio_player.fileHandle ) == FALSE )
+	{
+#		ifdef _DEBUG
+		OSReport( "*** Cannot open: '%s'\n", fileName );
+#		endif
+		return FALSE;
+	}
+		
+	// Set callback status to indicate file is now open.
+	audio_player.asyncOpenCallbackStatus = 1;
+	
+	// Read 'VID1' chunk from file and check for correct version.
+//	if( DVDRead( &audio_player.fileHandle, &workChunk, 32, 0 ) < 0 )
+	if( DVDReadAsync( &audio_player.fileHandle, &workChunk, 32, 0, AUDSimpleOpenDVDCallback ) < 0 )
+	{
+#		ifdef _DEBUG
+		OSReport( "*** Failed to read the header for %s.\n", fileName );
+#		endif
+		DVDClose( &audio_player.fileHandle );
+		return FALSE;
+	}
+
+	strncpy( audio_player.fileName, fileName, 64 );
+	audio_player.fileName[63]				= 0;
+
+	// Nothing more to do here.
+	return TRUE;
+		
+//	fileOffset += 32;
+	
+	// Check file id	
+//	if( workChunk.id != VID_FCC('V','I','D','1' ))
+//	{
+//#		ifdef _DEBUG
+//		OSReport("*** No VID1 file: '%s'\n", fileName);
+//#		endif
+//		DVDClose( &audio_player.fileHandle );
+//		return FALSE;
+//	}
+	
+	// Check for correct version of vid chunk.
+	// If we find this version we assume a 'special' alignment and chunk ordering which may be invalid
+	// in another version of the file format.
+//	if( workChunk.vid.versionMajor != 1 || workChunk.vid.versionMinor != 0 )
+//	{
+//#		ifdef _DEBUG
+//		OSReport("*** Unsupported file version: major: %d, minor: %d\n", workChunk.vid.versionMajor, workChunk.vid.versionMajor);
+//#		endif
+//		DVDClose(&audio_player.fileHandle);
+//		return FALSE;
+//	}
+	
+//#	ifdef _DEBUG
+	// Sometimes, it's required to check for a special build of the VidConv converter.
+//	{
+//		u32 version = VID_VERSION(workChunk.vid.vidConvMajor, workChunk.vid.vidConvMinor, workChunk.vid.vidConvBuild);
+//		if(version < VID_VERSION(1,0,1))
+//		OSReport("*** WARNING: Vid file created using an unsupported converter version: %d.%d.%d\n", (u32)workChunk.vid.vidConvMajor, (u32)workChunk.vid.vidConvMinor, (u32)workChunk.vid.vidConvBuild);
+//	}
+//#	endif
+
+	// Check types of chunks we have in this file.
+	// !!! Note that we assume start of 'HEAD' chunk at byte offset 32 from file start !!!
+//	if( DVDRead( &audio_player.fileHandle, &workChunk, 32, (s32)fileOffset ) < 0 )
+//	{
+//#		ifdef _DEBUG
+//		OSReport("*** Failed to read 'HEAD' chunk.\n");
+//#		endif
+//		DVDClose( &audio_player.fileHandle );
+//		return FALSE;
+//	}
+	
+//	if( workChunk.id != VID_FCC('H','E','A','D' ))
+//	{
+//#		ifdef _DEBUG
+//		OSReport("*** No HEAD chunk found at expected offset\n");
+//#		endif
+//		DVDClose(&audio_player.fileHandle);
+//		return FALSE;
+//	}
+	
+	// Calculate the start of the first frame chunk
+	// (we know the header chunk starts at offset 32)
+//	audio_player.nextFrameOffset = workChunk.len + 32;
+
+	// Skip 'HEAD' chunk id, len and version fields
+//	fileOffset += VID_CHUNK_HEADER_SIZE;
+	
+	// The header chunk contains one or more header chunks for the different data types contained
+	// in the stream. Parse them all...
+//	headSize = workChunk.len - VID_CHUNK_HEADER_SIZE;
+//	while( headSize >= 32 )
+//	{
+//		if( DVDRead( &audio_player.fileHandle, &workChunk, 32, (s32)fileOffset ) < 0 )
+//		{
+//#			ifdef _DEBUG
+//			OSReport("*** Error reading file at offset %d\n", fileOffset);
+//#			endif
+//			DVDClose(&audio_player.fileHandle);
+//			return FALSE;
+//		}
+		
+//		fileOffset += 32;
+//		headSize -= 32;
+
+		// We analyze the 1st 32 bytes of the chunk for a known header format
+//		if(workChunk.id == VID_FCC('A','U','D','H'))
+//		{
+			// Allocate memory for audio header chunk
+//			audio_player.audioHeaderChunk = (u8*)((*audio_player.cbAlloc)(workChunk.len));
+//			audioInfoSize = workChunk.len - VID_CHUNK_HEADER_SIZE;
+			
+			// Copy the already loaded part
+//			memcpy(audio_player.audioHeaderChunk, &workChunk, 32);
+//			workChunk.len -= 32;
+			
+			// Read additional audio header bytes if the audio header is greater that 32 bytes
+//			if(workChunk.len >= 32)
+//			{
+//				ASSERT((workChunk.len&31)==0);
+//				if( DVDRead( &audio_player.fileHandle, audio_player.audioHeaderChunk + 32, workChunk.len, (s32)fileOffset ) < 0 )
+//				{
+//#					ifdef _DEBUG
+//					OSReport("*** Error reading file at offset %d\n", fileOffset);
+//#					endif
+//					DVDClose(&audio_player.fileHandle);
+//					return FALSE;
+//				}
+//				fileOffset += workChunk.len;
+//				headSize -= workChunk.len;
+//			}
+
+			// Setup and calc the number of bytes which we are allowed to copy into the audioInfo struct
+//			memcpy(&audio_player.audioInfo, audio_player.audioHeaderChunk+VID_CHUNK_HEADER_SIZE, MIN(audioInfoSize, sizeof(audio_player.audioInfo)));
+//		}
+//		else
+//		{
+//			// Skip unknown chunks. We already read 32 bytes for the header which we must subtract here.
+//			fileOffset += workChunk.len - 32;
+///			headSize -= workChunk.len - 32;
+//		}
+//	}
+	
+	// check if we have the correct vaud file version (>0)
+//	if(audio_player.audioInfo.vaudex.version == 0)
+//	{
+//#		ifdef _DEBUG
+//		OSReport("*** Invalid version in vaud file.");
+//#		endif
+//		DVDClose(&audio_player.fileHandle);
+//		return FALSE;
+//	}
+
+	// we can only play audio files which have the following fiels set.
+	// Note that in case of VIDEO files this fields are allowed to be 0.
+//	ASSERT(audio_player.audioInfo.vaudex.maxBufferSize > 0);
+//	ASSERT(audio_player.audioInfo.vaudex.frameCount > 0);
+//	ASSERT(audio_player.audioInfo.vaudex.frameTimeMs > 0);
+	
+	// read beginning of 1st frame chunk to get required size information
+//	if( DVDRead( &audio_player.fileHandle, &workChunk, 32 , (s32)audio_player.nextFrameOffset ) < 0 )
+//	{
+//#		ifdef _DEBUG
+//		OSReport("*** Failed to read 'FRAM' chunk.\n");
+//#		endif
+//		DVDClose(&audio_player.fileHandle);
+//		return FALSE;
+//	}
+
+//	if( workChunk.id != VID_FCC('F','R','A','M') )
+//	{
+//#		ifdef _DEBUG
+//		OSReport("*** No FRAM chunk found.");
+//#		endif
+//		DVDClose(&audio_player.fileHandle);
+//		return FALSE;
+//	}
+
+//	audio_player.nextFrameSize = workChunk.len; 		// 32 bytes of this chunk are already consumed, but we want to 'preload' the NEXT chunk's FRAM header
+//	audio_player.nextFrameOffset += 32;
+	
+//	audio_player.firstFrameOffset = audio_player.nextFrameOffset;
+//	audio_player.firstFrameSize   = audio_player.nextFrameSize;
+
+//	strncpy(audio_player.fileName, fileName, 64);
+//	audio_player.fileName[63] = 0;
+	
+//	audio_player.open 			 	= TRUE;
+
+//	audio_player.readIndex 			= 0;
+//	audio_player.decodeIndex 			= 0;
+//	audio_player.lastDecodedFrame		= 0;
+//	audio_player.error 			 	= FALSE;
+//	audio_player.preFetchState 	 	= FALSE;
+//	audio_player.loopMode			 	= FALSE;
+//	audio_player.asyncDvdRunning		= FALSE;
+//	audio_player.currentFrameCount 	= 0;
+//	audio_player.readBufferBaseMem	= 0;
+
+//	return TRUE;
+} 	
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Close open video file
+ *
+ * \return
+ *		TRUE if file is closed sucessfully.
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleClose(void)
+	{
+	if (audio_player.open)
+		{
+		if (audio_player.preFetchState == FALSE)
+			{
+			if (!audio_player.asyncDvdRunning)
+				{
+				audio_player.open = FALSE;
+				DVDClose(&audio_player.fileHandle);
+				if(audio_player.audioHeaderChunk != NULL)
+					{
+					(*audio_player.cbFree)(audio_player.audioHeaderChunk);
+					audio_player.audioHeaderChunk = NULL;
+					}
+				return TRUE;
+				}
+			}
+		}
+	return FALSE;
+	}
+
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Decode all frame data
+ *
+ *		This function operates on the full frame input data. It forwards this
+ *		data to the required decoder.
+ *
+ ******************************************************************************
+ */
+BOOL AUDSimpleDecode(void)
+{
+	BOOL enabled;
+	u8* chunkStart;
+	u32 chunkSize;
+	u32 frameSize;
+
+	if( audio_player.readBuffer[audio_player.decodeIndex].valid )
+	{
+		
+		// ptr to our (pre-) loaded data INSIDE (!) 'FRAM' chunk
+		// (in other words, the 'FRAM' chunk itself is not visible here)
+		chunkStart = audio_player.readBuffer[audio_player.decodeIndex].ptr;
+
+		// usually, we read additional 32 bytes for getting info about the NEXT chunk.
+		// We only deal with the actual 'FRAM' chunk data here and adjust the size by 32 bytes.
+        frameSize = audio_player.readBuffer[audio_player.decodeIndex].size - 32;
+		
+		// loop across ALL chunks inside 'FRAM'
+		while(frameSize >= 32)
+		{
+			chunkSize = VID_CHUNK_LEN(chunkStart);
+			
+			if( VID_CHUNK_ID(chunkStart) == VID_FCC('A','U','D','D') )
+			{
+				// Get the data to the audio system...
+				if(! AUDSimpleAudioDecode(chunkStart + VID_CHUNK_HEADER_SIZE, chunkSize - VID_CHUNK_HEADER_SIZE))
+				{
+#ifdef _DEBUG
+					OSReport("*** AUDSimpleAudioDecode failed!\n");
+#endif
+				}
+			}
+#ifdef _DEBUG
+			else
+			{
+				OSReport("*** AUDSimpleDecode: unknown chunk type!\n");
+			}
+#endif
+			
+			// goto next chunk
+			chunkStart += chunkSize;
+			frameSize -= chunkSize;
+		}
+			
+		audio_player.lastDecodedFrame = audio_player.readBuffer[audio_player.decodeIndex].frameNumber;
+		audio_player.readBuffer[audio_player.decodeIndex].valid = FALSE;
+		audio_player.decodeIndex = (audio_player.decodeIndex + 1) % AUD_NUM_READ_BUFFERS;
+
+		// check if loading is still running
+		enabled = OSDisableInterrupts();
+		if (!audio_player.readBuffer[audio_player.readIndex].valid && !audio_player.asyncDvdRunning)
+			ReadFrameAsync();
+		OSRestoreInterrupts(enabled);
+		
+        return TRUE;
+	}
+
+#ifdef _DEBUG
+	OSReport("*** AUDSimpleDecode: No valid decode buffer found (?).\n");
+#endif
+	return FALSE;
+
+}
+
+
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Change active play mask
+ *
+ ******************************************************************************
+ */
+void AUDSimplePlayMaskChange(void)
+	{
+	static u32 myPlayMask = 0x3;
+	u32 maxChannels = audio_player.audioInfo.vaud.numChannels;
+	ASSERT(maxChannels > 0 && maxChannels <= 32);
+
+	myPlayMask <<= 1;
+	if(myPlayMask >= ((u32)(1<
+#include 
+#include "vaud.h"
+
+/******************************************************************************
+ *  DEFINES
+ ******************************************************************************/
+
+//! Number of 'read-ahead' buffers
+#define AUD_NUM_READ_BUFFERS	10
+#define	ASYNC_OPEN_FAIL			666
+#define	ASYNC_OPEN_SUCCESS		999
+
+/******************************************************************************
+ *  STRUCTS AND TYPES
+ ******************************************************************************/
+
+typedef struct {
+	u8*				ptr;
+	u32				size;
+	u32				frameNumber;
+	volatile BOOL 	valid;
+} AudReadBuffer;
+
+//! Required fields for simple demonstation player
+typedef struct {
+
+	VAUDAllocator 	cbAlloc;			// memory allocator
+	VAUDDeallocator cbFree;				// memory deallocator
+
+    DVDFileInfo		fileHandle;
+	char			fileName[64];
+
+	VidAUDH			audioInfo;			// common header and first 2 channels (or all channels if not ADPCM)
+    u8*				audioHeaderChunk;
+	
+	BOOL            open;
+	BOOL            error;
+	BOOL			preFetchState;
+	BOOL			loopMode;
+	BOOL			asyncDvdRunning;
+	u32				asyncOpenCallbackStatus;	// Value indicating status of callback position for async startup
+
+	BOOL			decodeComplete;
+	BOOL			playbackComplete;
+	
+	u32				nextFrameOffset;
+	u32				nextFrameSize;
+	u32				currentFrameCount;
+	u32 			lastDecodedFrame;
+	
+	u32 			firstFrameOffset;
+	u32				firstFrameSize;
+	
+	u32             readIndex;
+	u32				decodeIndex;
+	
+	AudReadBuffer	readBuffer[AUD_NUM_READ_BUFFERS];
+	u8*				readBufferBaseMem;
+	
+	VAUDDecoder		decoder;
+
+} AudSimplePlayer;
+
+/******************************************************************************
+ *  PROTOTYPES
+ ******************************************************************************/
+extern void AUDSimpleInit(VAUDAllocator cbAlloc, VAUDDeallocator cbFree);
+
+extern BOOL AUDSimpleAllocDVDBuffers(void);
+extern void AUDSimpleFreeDVDBuffers(void);
+
+extern BOOL AUDSimpleCreateDecoder(void);
+extern void AUDSimpleDestroyDecoder(void);
+
+extern BOOL AUDSimpleLoadStart(BOOL loop);
+extern BOOL AUDSimpleLoadStop(void);
+
+extern BOOL AUDSimpleOpen(char* fileName);
+extern BOOL AUDSimpleClose(void);
+
+extern BOOL AUDSimpleDecode(void);
+extern u32  AUDSimpleGetAudioSampleRate(void);
+extern u32  AUDSimpleGetFrameTime(void);
+
+extern void AUDSimplePlayMaskChange(void);
+
+extern BOOL AUDSimpleCheckDVDError(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif	// __AUDSIMPLE_PLAYER_H__
+
+
diff --git a/Code/Gel/Music/ngc/p_music.cpp b/Code/Gel/Music/ngc/p_music.cpp
new file mode 100644
index 0000000..329f72c
--- /dev/null
+++ b/Code/Gel/Music/ngc/p_music.cpp
@@ -0,0 +1,2121 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2001 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL Library												**
+**																			**
+**	Module:			Music (Pcm)			 									**
+**																			**
+**	File name:		ngc/p_music.cpp											**
+**																			**
+**	Created by:		08/23/01	-	dc										**
+**																			**
+**	Description:	NGC audio streaming										**
+**																			**
+*****************************************************************************/
+
+#ifndef DVDETH
+#define USE_VORBIS
+#endif		// DVDETH
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef USE_VORBIS
+#include 
+#include 
+#endif
+
+#include 
+#include  
+//#include 
+#include "p_music.h"
+#include 
+#include "gfx\ngc\nx\nx_init.h"
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+#ifdef USE_VORBIS
+// Player instance
+extern AudSimplePlayer audio_player;
+#endif
+
+//bool g_using_dtk = false;
+//extern bool g_in_cutscene;
+
+// Info about last 8 reads.
+static DVDFileInfo* last_fileInfo[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+static void * last_addr[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; 
+static s32 last_length[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+static s32 last_offset[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+// Callback will happen 8 frames later.
+DVDCallback last_callback[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };  
+s32 last_callback_length[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; 
+DVDFileInfo* last_callback_fileInfo[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+int last_callback_counter[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; 
+
+namespace Pcm
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define MAX_DTK_VOL				0xFF				// Max volume for DTK hardware streamed audio tracks.
+#define MAX_VOL					0x7FFF
+#define NORMAL_VOL				( MAX_VOL >> 1 )
+#define UNALTERED_PITCH			0x10000L
+
+#define	QUIT_DMA_INACTIVE			0
+#define	QUIT_DMA_PENDING			1
+#define	QUIT_DMA_COMPLETE			2
+
+#define STREAM_BUFFER_HEADER_SIZE	96				// Size of the header for each stream
+#define STREAM_BUFFER_SIZE			16384
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+struct FileStreamInfo
+{
+	int				fileSize;
+	int				offset;
+	int				hedOffset;
+	int				offsetInARAM;
+	float			pitch;
+	float			pitchAdjustmentForSamplingRate;
+	float			volumeL;
+	float			volumeR;
+	bool			paused;
+	bool			hasStartedPlaying;
+	unsigned int	currentFileInfo;
+	DVDFileInfo		fileInfo[8];
+	AXVPB*			p_voice;
+	uint32			nextWrite;		// 0 for first half of ARAM buffer, 1 for second half of ARAM buffer.
+	bool			has_paused;
+	bool			preloadActive;
+};
+
+// This is also defined in p_sfx.cpp - should probably share a common definition.
+struct sDSPADPCM
+{
+	// for WAV header generation during decode
+	u32 num_samples;		// total number of RAW samples
+	u32 num_adpcm_nibbles;	// number of ADPCM nibbles (including frame headers)
+	u32 sample_rate;		// Sample rate, in Hz
+
+	// DSP addressing and decode context
+	u16 loop_flag; // 1=LOOPED, 0=NOT LOOPED
+	u16 format; // Always 0x0000, for ADPCM
+	u32 sa; // Start offset address for looped samples (zero for non-looped)
+	u32 ea; // End offset address for looped samples
+	u32 ca; // always zero
+	u16 coef[16]; // decode coefficients (eight pairs of 16-bit words)
+
+	// DSP decoder initial state
+	u16 gain; // always zero for ADPCM
+	u16 ps; // predictor/scale
+	u16 yn1; // sample history
+	u16 yn2; // sample history
+
+	// DSP decoder loop context
+	u16 lps; // predictor/scale for loop context
+	u16 lyn1; // sample history (n-1) for loop context
+	u16 lyn2; // sample history (n-2) for loop context
+	u16 pad[10]; // reserved
+};
+
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+//#ifndef USE_VORBIS
+//#ifndef DVDETH
+//static DTKTrack	s_dtk_track;
+//#endif		// DVDETH
+//#endif		// USE_VORBIS
+
+FileStreamInfo		gStreamInfo[NUM_STREAMS];
+int					gPcmStatus					= 0;
+unsigned int		gStreamVolume				= 100;
+File::SHed*			gpStreamHed					= NULL;
+int					stream_base[3];
+int					stream_size[3];
+static char			stream_0_mem[STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE] __attribute__((aligned( 32 )));	// Must be 32 for DVD.
+static char			stream_1_mem[STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE] __attribute__((aligned( 32 )));	// Must be 32 for DVD.
+static char			stream_2_mem[STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE] __attribute__((aligned( 32 )));	// Must be 32 for DVD.
+static char*		stream_mem_buffer[3]		= { stream_0_mem, stream_1_mem, stream_2_mem };
+static ARQRequest	stream_arq_request[NUM_STREAMS];
+bool				musicPaused					= false;
+float				musicVolume					= 0.0f;
+bool				musicPreloadActive			= false;
+bool				streamsPaused				= false;
+
+static uint32		sPreLoadChecksum[NUM_STREAMS];
+static uint32		sPreLoadMusicChecksum;
+static char			sPreLoadMusicFilename[256];
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+BOOL _DVDReadAsync( DVDFileInfo* fileInfo, void* addr, s32 length, s32 offset, DVDCallback callback )
+{
+	// See if this is a repeat read.
+	bool repeat_read = false;
+	for ( int lp = 0; lp < 8; lp++ )
+	{
+		if ( ( fileInfo == last_fileInfo[lp] ) &&
+			 ( addr == last_addr[lp] ) &&
+			 ( length == last_length[lp] ) &&
+			 ( offset == last_offset[lp] ) )
+		{
+			repeat_read = true;
+			break;
+		}
+	}
+
+	if ( repeat_read )
+	{
+		// Find an empty slot for this callback.
+		int cb = -1;
+		for ( int lp = 0; lp < 8; lp++ )
+		{
+			if ( last_callback_counter[lp] == -1 )
+			{
+				cb = lp;
+				break;
+			}
+		}
+
+		if ( cb == -1 )
+		{
+#			ifdef __NOPT_FINAL__
+			OSReport( "Warning: Overflowed DVD callback buffer\n" );
+#			else
+			Dbg_MsgAssert( 0, ( "Overflowed DVD callback buffer" ));
+#			endif
+			// Issue read normally in this case.
+			DVDReadAsync( fileInfo, addr, length, offset, callback ); 
+		}
+		last_callback[cb] = callback;
+		last_callback_length[cb] = length;
+		last_callback_fileInfo[cb] = fileInfo;
+		last_callback_counter[cb] = 8;
+		
+		return TRUE;
+	}
+	else
+	{
+		// Shunt last reads down & fill in new one.
+		for ( int lp = 7; lp > 0; lp-- )
+		{
+			last_fileInfo[lp] = last_fileInfo[lp-1];
+			last_addr[lp] = last_addr[lp-1];
+			last_length[lp] = last_length[lp-1];
+			last_offset[lp] = last_offset[lp-1];
+		}
+		last_fileInfo[0] = fileInfo;
+		last_addr[0] = addr;
+		last_length[0] = length;
+		last_offset[0] = offset;
+
+		return DVDReadAsync( fileInfo, addr, length, offset, callback );
+	}
+}
+
+void setDolby( AXPB * p_pb, AXVPB * p_vpb, float volL, float volR, unsigned int pitch, bool set_pitch, float volume_percent )
+{
+	float	volumes[4];
+	int		i_volumes[4];	//, max_i_volume;
+	volumes[0] = 0.0f;
+	volumes[1] = 0.0f;
+	volumes[2] = 0.0f;
+	volumes[3] = 0.0f;
+	
+	if( Sfx::isStereo )
+	{
+		if(( volL == 0.0f ) && ( volR == 0.0f ))
+		{
+			// Dave note - in theory setting the mixerCtrl to zero should cause the sound not to play.
+			// In practice however it seems to lead to small audio 'pops'.
+//			p_pb->mixerCtrl = 0;
+//			p_vpb->sync |= AX_SYNC_USER_MIXCTRL;
+			
+			// Pointless doing any more work.
+			p_pb->mix.vL	= 0;
+			p_pb->mix.vR	= 0;
+			p_pb->mixerCtrl = AX_PB_MIXCTRL_L | AX_PB_MIXCTRL_R;
+			p_vpb->sync	   |= AX_SYNC_USER_MIX | AX_SYNC_USER_MIXCTRL;
+		}
+		else
+		{
+			// Get the length of the vector here which will be used to multiply out the normalised speaker volumes.
+			Mth::Vector test( fabsf( volL ), fabsf( volR ), 0.0f, 0.0f );
+			float amplitude = test.Length();
+
+			// Look just at the normalized right component to figure the sound angle from Matt's calculations.
+			test.Normalize();
+
+			float angle;
+			angle	= asinf( test[Y] );
+			angle	= ( angle * 2.0f ) - ( Mth::PI * 0.5f );
+			angle	= ( volL < 0.0f ) ? ( Mth::PI - angle ) : angle;
+
+			// Now figure volumes based on speaker coverage.
+			angle	= Mth::RadToDeg( angle );
+
+			// Left front channel.
+			if(( angle >= 225.0f ) || ( angle <= 45.0f ))
+			{
+				// Because of the discontinuity, shift this angle round to the [0,180] range.
+				float shift_angle = angle + 135;
+				shift_angle = ( shift_angle >= 360.0f ) ? ( shift_angle - 360.0f ) : shift_angle;
+				volumes[0]	= ( shift_angle / 180.0f ) * Mth::PI;
+				volumes[0]	= sqrtf( sinf( volumes[0] ));
+			}
+
+//			// Center channel.
+//			if(( angle >= -60.0f ) && ( angle <= 60.0f ))
+//			{
+//				// Scale this into [0,PI] space so we can get smooth fadeout.
+//				volumes[1]	= (( angle + 60.0f ) / 120.0f ) * Mth::PI;
+//				volumes[1]	= sqrtf( sinf( volumes[1] ));
+//			}
+
+			// Right front channel.
+			if(( angle >= -45.0f ) && ( angle <= 135.0f ))
+			{
+				// Scale this into [0,PI] space so we can get smooth fadeout.
+				volumes[1]	= (( angle + 45.0f ) / 180.0f ) * Mth::PI;
+				volumes[1]	= sqrtf( sinf( volumes[1] ));
+			}
+
+			// Right rear channel.
+			if(( angle >= 45.0f ) && ( angle <= 225.0f ))
+			{
+				// Scale this into [0,PI] space so we can get smooth fadeout.
+				volumes[2]	= (( angle - 45.0f ) / 180.0f ) * Mth::PI;
+				volumes[2]	= sqrtf( sinf( volumes[2] ));
+			}
+
+			// Left rear channel.
+			if(( angle >= 135.0f ) || ( angle <= -45.0f ))
+			{
+				// Because of the discontinuity, shift this angle round to the [0,180] range.
+				float shift_angle = angle + 225;
+				shift_angle = ( shift_angle >= 360.0f ) ? ( shift_angle - 360.0f ) : shift_angle;
+				volumes[3]	= ( shift_angle / 180.0f ) * Mth::PI;
+				volumes[3]	= sqrtf( sinf( volumes[3] ));
+			}
+
+			// Now readjust the relative values...
+			for( int v = 0; v < 4; ++v )
+			{
+				// Scale back up to original amplitude.
+				volumes[v] *= amplitude;
+
+				// Then adjust for SFX volume level, and scale into limits.
+//				volumes[v] = PERCENT( volume_percent, volumes[v] );
+//				volumes[v] = fabsf( PERCENT( sfx_manager->GetMainVolume(), volumes[v] ));
+
+				if( volumes[v] > 100.0f )
+					volumes[v] = 100.0f;
+
+			}
+
+			// Now figure the attenuation of the sound. To convert to a decibel value, figure the ratio of requested
+			// volume versus max volume, then calculate the log10 and multiply by (10 * 2). (The 2 is because sound
+			// power varies as square of pressure, and squaring doubles the log value).
+//#define DSBVOLUME_EFFECTIVE_MIN -6400
+//#define DSBVOLUME_MAX 0
+//			max_i_volume = DSBVOLUME_EFFECTIVE_MIN;
+			for( int v = 0; v < 4; ++v )
+			{
+//				if( volumes[v] > 0.0f )
+//				{
+//					float attenuation	= 20.0f * log10f( volumes[v] * 0.01f );
+//					i_volumes[v]		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+//					if( i_volumes[v] < DSBVOLUME_EFFECTIVE_MIN )
+//						i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+//
+//					if( i_volumes[v] > max_i_volume )
+//						max_i_volume = i_volumes[v];
+//				}
+//				else
+//				{
+//					i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+//				}
+				// GameCube:: Change from -6400 -> 0 to 0 -> 0x1fff.
+//				i_volumes[v] = ( ( i_volumes[v] + -DSBVOLUME_EFFECTIVE_MIN ) * 0x1fff ) / -DSBVOLUME_EFFECTIVE_MIN;
+//				i_volumes[v] = (int)PERCENT( volume_percent, (float)i_volumes[v] );
+				i_volumes[v] = (int)(( volumes[v] * (float)0x3fff ) / 100.0f);
+				i_volumes[v] = (int)PERCENT( volume_percent, (float)i_volumes[v] );
+			}
+
+			// Set individual mixbins for panning. Clampo very low volumes to zero, and for zero volumes, early out
+			// without setting the Dolby stuff up.
+			if(( i_volumes[0] <= 32 ) && ( i_volumes[1] <= 32 ) && ( i_volumes[2] <= 32 ) && ( i_volumes[3] <= 32 ))
+			{
+				// Pointless doing any more work.
+				p_pb->mix.vL	= 0;
+				p_pb->mix.vR	= 0;
+				p_pb->mixerCtrl = AX_PB_MIXCTRL_L | AX_PB_MIXCTRL_R;
+				p_vpb->sync	   |= AX_SYNC_USER_MIX | AX_SYNC_USER_MIXCTRL;
+			}
+			else
+			{
+				p_pb->mixerCtrl = AX_PB_MIXCTRL_B_DPL2;
+			
+				if( i_volumes[0] > 0 )
+				{
+					p_pb->mix.vL = i_volumes[0];
+					p_pb->mixerCtrl |= AX_PB_MIXCTRL_L;
+				}
+	
+				if( i_volumes[1] > 0 )
+				{
+					p_pb->mix.vR = i_volumes[1];
+					p_pb->mixerCtrl |= AX_PB_MIXCTRL_R;
+				}
+	
+				if( i_volumes[2] > 0 )
+				{
+					p_pb->mix.vAuxBL = i_volumes[2];
+					p_pb->mixerCtrl |= AX_PB_MIXCTRL_B_L;
+				}
+	
+				if( i_volumes[3] > 0 )
+				{
+					p_pb->mix.vAuxBR = i_volumes[3];
+					p_pb->mixerCtrl |= AX_PB_MIXCTRL_B_R;
+				}
+				p_vpb->sync |= AX_SYNC_USER_MIX | AX_SYNC_USER_MIXCTRL;
+			}
+		}
+	}
+	else
+	{
+		// Adjust for SFX volume level.
+		volL = PERCENT( volume_percent, volL );
+		volR = PERCENT( volume_percent, volR );
+
+		// Adjust for channel volume.
+		int lVol = (int)PERCENT( 0x3fff, volL );	
+		int rVol = (int)PERCENT( 0x3fff, volR );
+
+		// Clamp to maximum allowed volume.
+		if ( lVol > 0x7fff )
+			lVol = 0x7fff;
+		else if ( lVol < 0 )
+			lVol = 0;
+
+		if ( rVol > 0x7fff )
+			rVol = 0x7fff;
+		else if ( rVol < 0 )
+			rVol = 0;
+
+		p_pb->mix.vL	= ( lVol + rVol ) / 2;
+		p_pb->mix.vR	= ( lVol + rVol ) / 2;
+		p_pb->mixerCtrl = AX_PB_MIXCTRL_L | AX_PB_MIXCTRL_R;
+		p_vpb->sync	   |= AX_SYNC_USER_MIX | AX_SYNC_USER_MIXCTRL;
+	}
+	
+	if ( set_pitch )
+	{
+		p_pb->src.ratioHi		= ( pitch >> 16 );
+		p_pb->src.ratioLo		= ( pitch & 0xFFFF );
+		p_vpb->sync			   |= AX_SYNC_USER_SRCRATIO;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void ax_voice_reacquisition_callback( void* p )
+{
+	// Should never happen for stream voices.
+	Dbg_Assert( 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+////#ifndef USE_VORBIS
+//#ifndef DVDETH
+//static void s_dtk_callback( u32 event_mask )
+//{
+//	if( event_mask & DTK_EVENT_TRACK_ENDED )
+//	{
+//		// Stop the music track.
+//		DTKSetState( DTK_STATE_STOP );
+//		gPcmStatus &= ~PCMSTATUS_MUSIC_PLAYING;
+//	}
+//
+//	if( event_mask & DTK_EVENT_PLAYBACK_STARTED )
+//	{
+//		if( musicPaused )
+//		{
+//			// Pause the music track.
+//			DTKSetState( DTK_STATE_PAUSE );
+//		}
+//	}
+//}
+//#endif		// DVDETH
+////#endif		// USE_VORBIS
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void s_start_voice_callback( u32 pointerToARQRequest )
+{
+	// Figure which stream this is for.
+	int stream = -1;
+	
+	for( int s = 0; s < NUM_STREAMS; ++s )
+	{
+		if( pointerToARQRequest == (u32)( &stream_arq_request[s] ))
+		{
+			stream = s;
+			break;
+		}
+	}
+	
+	if( stream == -1 )
+	{
+		// Something has gone very wrong.
+		Dbg_MsgAssert( 0, ( "Unsynced dvd callback" ));
+		return;
+	}
+
+	// Deal with the voice already having been deleted.
+	if( gStreamInfo[stream].p_voice == NULL )
+	{
+		return;
+	}
+
+	// Set the voice to run.
+	gStreamInfo[stream].hasStartedPlaying	= true;
+	gStreamInfo[stream].paused				= streamsPaused;
+	if( !streamsPaused && !gStreamInfo[stream].preloadActive )
+	{
+		if( gStreamInfo[stream].p_voice == NULL )
+		{
+			Dbg_Assert( 0 );
+			return;			
+		}
+		AXSetVoiceState( gStreamInfo[stream].p_voice, AX_PB_STATE_RUN );
+	}
+
+	// Adjust pitch to account for lower sampling rates:
+	float			pitch	= PERCENT( gStreamInfo[stream].pitch, gStreamInfo[stream].pitchAdjustmentForSamplingRate );
+	unsigned int	u_pitch	= (unsigned int)PERCENT( UNALTERED_PITCH, pitch );
+
+	int old = OSDisableInterrupts();
+	AXPB*	p_pb						= &( gStreamInfo[stream].p_voice->pb );
+	Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+	setDolby( p_pb, gStreamInfo[stream].p_voice, gStreamInfo[stream].volumeL, gStreamInfo[stream].volumeR, u_pitch, true, sfx_manager->GetMainVolume() );
+	OSRestoreInterrupts( old );
+
+	gPcmStatus |= PCMSTATUS_STREAM_PLAYING( stream );
+	gPcmStatus &= ~PCMSTATUS_LOAD_STREAM( stream );
+}
+
+						
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void s_dvd_callback( s32 result, DVDFileInfo* fileInfo )
+{
+	// Figure which stream this is for.
+	int stream	= -1;
+
+	for( unsigned int i = 0; i < 8; ++i )
+	{
+		for( uint32 s = 0; s < NUM_STREAMS; ++s )
+		{
+			if( fileInfo == &( gStreamInfo[s].fileInfo[i] ))
+			{
+				if( i == gStreamInfo[s].currentFileInfo )
+				{
+					stream = s;
+					break;
+				}
+			}
+		}
+		if( stream >= 0 )
+			break;
+	}
+
+	if( stream == -1 )
+	{
+		// Possibly the result of a read in which we are no longer interested.
+		return;
+	}
+
+	// Handle read errors by just stopping the stream.
+	if(( result < 0 ) || gStreamInfo[stream].has_paused )
+	{
+		int enabled = OSDisableInterrupts();
+		if( gStreamInfo[stream].p_voice )
+		{
+			// This should be the case.
+			AXFreeVoice( gStreamInfo[stream].p_voice );
+		}
+		gStreamInfo[stream].p_voice			= NULL;
+		gStreamInfo[stream].preloadActive	= false;
+		gStreamInfo[stream].currentFileInfo	= ( gStreamInfo[stream].currentFileInfo + 1 ) & 0x07;
+		OSRestoreInterrupts( enabled );
+
+		gPcmStatus &= ~PCMSTATUS_LOAD_STREAM( stream );
+		gPcmStatus &= ~PCMSTATUS_STREAM_PLAYING( stream );
+		return;
+	}
+
+	// Check if has already been stopped.
+	AXVPB* p_axvpb = gStreamInfo[stream].p_voice;
+	if( p_axvpb == NULL )
+	{
+		return;
+	}
+
+	// If this is the first chunk read for this voice, need to grab the voice and look at the header information.
+	if( gStreamInfo[stream].offset == 0 )
+	{
+		AXPBADDR        addr;
+		AXPBADPCM       adpcm;
+		AXPBVE			ve;
+		
+		int enabled = OSDisableInterrupts();
+		// Grab pointer to header (first 96 byes of data).
+		sDSPADPCM*	p_header		= (sDSPADPCM*)( stream_mem_buffer[stream] );
+
+		addr.format					= AX_PB_FORMAT_ADPCM;   
+
+		// Depending on the number of bytes in the sound, we may not need to loop.
+		// Also depending on how many times we will be uploading to ARAM, we may want to start the stream
+		// halfway through the ARAM buffer, in order to ensure that we always write the last block to the upper half of ARAM
+		// (in order that we can safely set the end position).
+		int num_adpcm_bytes			= p_header->num_adpcm_nibbles / 2;
+
+		// Convert from byte to nibble address, and split into low and high words.
+		u32 start_address			= ( stream_base[stream] * 2 ) + 2;
+		addr.currentAddressHi		= (u16)( start_address >> 16 );
+		addr.currentAddressLo		= (u16)( start_address & 0xFFFFU );
+
+		if( num_adpcm_bytes <= 32768 )
+		{
+			// Stream fits in to ARAM buffer completely. No need to loop.
+			u32 end_address					= ( stream_base[stream] * 2 ) + p_header->num_adpcm_nibbles - 1;
+		
+			addr.endAddressHi				= (u16)( end_address >> 16 );
+			addr.endAddressLo				= (u16)( end_address & 0xFFFFU );
+		
+			u32 zero_buffer_address			= NxNgc::EngineGlobals.aram_zero * 2;
+			addr.loopAddressHi				= (u16)( zero_buffer_address >> 16 );
+			addr.loopAddressLo				= (u16)( zero_buffer_address & 0xFFFFU );
+		
+			addr.loopFlag					= AXPBADDR_LOOP_OFF;
+		
+			gStreamInfo[stream].nextWrite	= 1;
+//			OSReport( "Stream: %d is simple <= 32768 bytes\n", stream );
+		}
+		else
+		{
+			// Stream does not fit in to ARAM buffer completely. Needs to loop.
+			uint32 num_uploads				= num_adpcm_bytes / STREAM_BUFFER_SIZE;
+			if(( num_adpcm_bytes % STREAM_BUFFER_SIZE ) > 0 )
+				++num_uploads;
+			Dbg_Assert( num_uploads > 2 );
+
+			addr.loopAddressHi		= addr.currentAddressHi;
+			addr.loopAddressLo		= addr.currentAddressLo;
+			
+			if( num_uploads & 0x01 )
+			{
+				// This is the tricky situation, where we start halfway through the ARAM buffer.
+				gStreamInfo[stream].offsetInARAM   += STREAM_BUFFER_SIZE;
+				start_address					   += ( STREAM_BUFFER_SIZE * 2 );
+				addr.currentAddressHi				= (u16)( start_address >> 16 );
+				addr.currentAddressLo				= (u16)( start_address & 0xFFFFU );
+				gStreamInfo[stream].nextWrite		= 0;
+//				OSReport( "Stream: %d is complex tricky %d bytes\n", stream, num_adpcm_bytes );
+			
+			}
+			else
+			{
+				// This is the easy situation, where we start at the start of the ARAM buffer.
+				gStreamInfo[stream].offsetInARAM	= 0;
+				gStreamInfo[stream].nextWrite		= 1;
+//				OSReport( "Stream: %d is complex non-tricky %d bytes\n", stream, num_adpcm_bytes );
+			}
+			
+			u32 end_address			= ( stream_base[stream] * 2 ) + ( 32768 * 2 ) - 1;
+			addr.endAddressHi		= (u16)( end_address >> 16 );
+			addr.endAddressLo		= (u16)( end_address & 0xFFFFU );
+		
+			addr.loopFlag			= AXPBADDR_LOOP_ON;
+		}
+
+		memcpy( &adpcm.a[0][0], p_header->coef, sizeof( u16 ) * 16 );
+		adpcm.gain					= p_header->gain;
+		adpcm.pred_scale			= p_header->ps;
+		adpcm.yn1					= p_header->yn1;
+		adpcm.yn2					= p_header->yn2;
+
+		ve.currentVolume			= 0x7FFF;
+		ve.currentDelta				= 0;
+
+		// Set voice parameters.
+		AXSetVoiceType( p_axvpb, AX_PB_TYPE_NORMAL );		// No loop context.
+		AXSetVoiceSrcType( p_axvpb, AX_PB_SRCSEL_LINEAR );	// Use linear interpolation.
+		AXSetVoiceVe( p_axvpb, &ve );						// Set overall volume.
+		AXSetVoiceAddr( p_axvpb, &addr );					// Input addressing.
+		AXSetVoiceAdpcm( p_axvpb, &adpcm );					// ADPCM coefficients.
+
+//		p_axvpb->sync |= AX_SYNC_USER_ALLPARAMS;
+//		AXSetVoiceSrcRatio( p_axvpb, (48000.0f / ((float)AX_IN_SAMPLES_PER_SEC)) );
+
+		// If this chunk is less than buffersize, zero out the excess bytes to prevent sound pop.
+		if( result < ( STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE ))
+		{
+			memset( stream_mem_buffer[stream] + result, 0, ( STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE ) - result );
+			DCFlushRange( stream_mem_buffer[stream] + result, ( STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE ) - result );
+		}
+		OSRestoreInterrupts( enabled );
+
+		// DMA this chunk up to ARAM, then use the ARQ callback to start the voice.
+		ARQPostRequest(	&stream_arq_request[stream],							// Address to user-allocated storage for an ARQTask data structure.
+						0,														// User-enumerated identifier for the owner of the ARAM transaction.
+						ARQ_TYPE_MRAM_TO_ARAM,									// Direction.
+						ARQ_PRIORITY_HIGH,										// Priority.
+						(u32)( stream_mem_buffer[stream] + 96 ),				// Source address.
+						stream_base[stream] + gStreamInfo[stream].offsetInARAM,	// Destination address.
+						STREAM_BUFFER_SIZE,										// Transfer length (bytes). Must be multiple of 4.
+						&s_start_voice_callback );								// DMA complete callback function.
+
+		gStreamInfo[stream].pitchAdjustmentForSamplingRate	= ((float)p_header->sample_rate * 100.0f ) / 32000.0f;
+		
+		// Increment the ARAM offset and wrap when appropriate.
+		gStreamInfo[stream].offsetInARAM += STREAM_BUFFER_SIZE;
+		if( gStreamInfo[stream].offsetInARAM >= ( STREAM_BUFFER_SIZE * 2 ))
+		{
+			gStreamInfo[stream].offsetInARAM -= ( STREAM_BUFFER_SIZE * 2 );
+		}
+		
+		// Increment the file pointer (remember we read the extra buffer bytes for the first read).
+		gStreamInfo[stream].offset += STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE;
+	}
+	else
+	{
+		// If this chunk is less than buffersize, zero out the excess bytes to prevent sound pop.
+		if( result < STREAM_BUFFER_SIZE )
+		{
+			memset( stream_mem_buffer[stream] + result, 0, STREAM_BUFFER_SIZE - result );
+			DCFlushRange( stream_mem_buffer[stream] + result, STREAM_BUFFER_SIZE - result );
+		}
+
+		// We want to DMA this chunk up to ARAM, but only if safe to do so, i.e. only when the playback position is in
+		// the opposite half of the ARAM buffer.
+		int		current_aram_pos	= ((uint32)( p_axvpb->pb.addr.currentAddressHi ) << 16 ) | (uint32)( p_axvpb->pb.addr.currentAddressLo );
+		bool	safe_to_dma			= false;
+
+		// Convert from current ARAM position (in nibbles) to current offset within buffer (in bytes).
+		current_aram_pos			= ( current_aram_pos - ( stream_base[stream] * 2 )) / 2;
+		
+		if( current_aram_pos < STREAM_BUFFER_SIZE )
+		{
+			// Safe to write to the second half of the ARAM buffer, if that is what needs updating.
+			if( gStreamInfo[stream].nextWrite == 1 )
+			{
+				safe_to_dma = true;
+//				OSReport( "Stream: %d ARAM: %d writing to second half\n", stream, current_aram_pos );
+			}
+		}
+		else
+		{
+			// Safe to write to the first half of the ARAM buffer, if that is what needs updating.
+			if( gStreamInfo[stream].nextWrite == 0 )
+			{
+				safe_to_dma = true;
+//				OSReport( "Stream: %d ARAM: %d writing to first half\n", stream, current_aram_pos );
+			}
+		}
+		
+		if( safe_to_dma )
+		{
+			// DMA this chunk up to ARAM.
+			ARQPostRequest(	&stream_arq_request[stream],							// Address to user-allocated storage for an ARQTask data structure.
+							0,														// User-enumerated identifier for the owner of the ARAM transaction.
+							ARQ_TYPE_MRAM_TO_ARAM,									// Direction.
+							ARQ_PRIORITY_HIGH,										// Priority.
+							(u32)( stream_mem_buffer[stream] ),						// Source address.
+							stream_base[stream] + gStreamInfo[stream].offsetInARAM,	// Destination address.
+							STREAM_BUFFER_SIZE,										// Transfer length (bytes). Must be multiple of 4.
+							NULL );													// DMA complete callback function.
+
+			// Increment the ARAM offset and wrap when appropriate.
+			gStreamInfo[stream].offsetInARAM += STREAM_BUFFER_SIZE;
+			if( gStreamInfo[stream].offsetInARAM >= ( STREAM_BUFFER_SIZE * 2 ))
+			{
+				gStreamInfo[stream].offsetInARAM -= ( STREAM_BUFFER_SIZE * 2 );
+			}
+	
+			// We have now processed another chunk.
+			gStreamInfo[stream].nextWrite = ( gStreamInfo[stream].nextWrite == 0 ) ? 1 : 0;
+		
+			// Increment the file pointer.
+			gStreamInfo[stream].offset += STREAM_BUFFER_SIZE;
+		}
+		else
+		{
+//			OSReport( "Stream: %d ARAM: %d discarding file buffer\n", stream, current_aram_pos );
+		}
+	}
+
+	// If there is more data, kick off another read. NOTE this could potentially write over what we are about to DMA to ARAM.
+	int total_bytes_after_next_read	= gStreamInfo[stream].offset + STREAM_BUFFER_SIZE;
+	int bytes_to_read;
+	if( total_bytes_after_next_read <= gStreamInfo[stream].fileSize )
+	{
+		bytes_to_read = STREAM_BUFFER_SIZE;
+	}
+	else
+	{
+		bytes_to_read = STREAM_BUFFER_SIZE - ( total_bytes_after_next_read - gStreamInfo[stream].fileSize );
+
+		// Reduce bytes_to_read down to the next lowest multiple of 32.
+		bytes_to_read &= ~31;
+	}
+
+	if( bytes_to_read > 0 )
+	{
+		_DVDReadAsync(	&( gStreamInfo[stream].fileInfo[gStreamInfo[stream].currentFileInfo] ),
+						stream_mem_buffer[stream],												// Base pointer of memory to read data into.
+						bytes_to_read,															// Bytes to read.
+						gStreamInfo[stream].hedOffset + gStreamInfo[stream].offset,				// Offset in file.
+						s_dvd_callback );														// Read complete callback.
+	}
+	else
+	{
+		// Here we may need to set the stop point for the voice, and switch from looping to non-looping.
+		if( p_axvpb->pb.addr.loopFlag == AXPBADDR_LOOP_ON )
+		{
+//			OSReport( "Stream: %d switching to loop off\n", stream );
+			p_axvpb->pb.addr.loopFlag		= AXPBADDR_LOOP_OFF;
+			u32 zero_buffer_address			= NxNgc::EngineGlobals.aram_zero * 2;
+			p_axvpb->pb.addr.loopAddressHi	= (u16)( zero_buffer_address >> 16 );
+			p_axvpb->pb.addr.loopAddressLo	= (u16)( zero_buffer_address & 0xFFFFU );
+			p_axvpb->sync |= ( AX_SYNC_USER_LOOP | AX_SYNC_USER_LOOPADDR );
+		}
+	}
+}
+
+
+
+/*****************************************************************************
+**							    Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_Init( void )
+{
+////#	ifdef USE_VORBIS
+////#	else
+//#	ifndef DVDETH
+//	DTKInit();
+//	DTKSetVolume( 64, 64 );		// Defaults to half volume.
+//	DTKSetRepeatMode( DTK_MODE_NOREPEAT );
+//#	endif		// DVDETH
+////#	endif		// VORBIS
+
+	memset( gStreamInfo, 0, sizeof( FileStreamInfo ) * NUM_STREAMS );
+
+	// Just zero out the audio_player structure.
+	memset( &audio_player, 0, sizeof( audio_player ));
+	
+	Sfx::GetStreamBufferInfo( &stream_base[0], &stream_base[1], &stream_base[2],
+							  &stream_size[0], &stream_size[1], &stream_size[2] );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int PCMAudio_SetMusicVolume( float volume )
+{
+	// Save this value off so it can be used again for async startup.
+	musicVolume = volume;
+	
+	// Normalize down to match PS2.
+	volume *= 0.5f;
+
+//#	ifdef USE_VORBIS
+//	if ( !g_using_dtk )
+	{
+#		ifdef USE_VORBIS
+		if( PCMAudio_GetMusicStatus() == PCM_STATUS_PLAYING )
+		{
+			uint32 vol = (uint32)PERCENT( volume, 0x7FFF );
+			AUDSimpleAudioSetVolume( vol, vol );
+		}
+#		endif	// VORBIS
+	}
+//	else
+//	{
+//		int vol = (int)PERCENT( volume, MAX_DTK_VOL );
+//		if( vol > MAX_DTK_VOL )
+//		{
+//			vol = MAX_DTK_VOL;
+//		}
+//	#	ifndef DVDETH
+//		DTKSetVolume( vol, vol );
+//	#	endif	// DVDETH
+//	}
+////#	else	
+////#	endif	// VORBIS
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_LoadMusicHeader( const char *nameOfFile )
+{
+	// For now, just fix this as true.
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_TrackExists( const char *pTrackName, int ch )
+{
+	if( ch == MUSIC_CHANNEL )
+	{
+		// For now, assume all music tracks exist.
+		return true;
+
+/*
+		if( !gpMusicHed )
+		{
+			return false;
+		}
+
+		if( !FindFileInHed( pTrackName, gpMusicHed ))
+		{
+			Dbg_Message( "Track %s not found in header file.", pTrackName );
+			return false;
+		}
+
+		return true;
+*/
+	}
+
+	if( !gpStreamHed )
+	{
+		return false;
+	}
+	if( !FindFileInHed( pTrackName, gpStreamHed ))
+	{
+		return false;
+	}
+	return true;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+#ifdef USE_VORBIS
+/*!
+ ******************************************************************************
+ * \brief
+ *		Memory allocation callback.
+ *
+ *		The player calls this function for all its memory needs. In this example
+ *		an OSAlloc() is all we need to do.
+ *
+ * \note
+ *		The returned memory address MUST be aligned on a 32 byte boundary.
+ *		Otherwise, the player will fail!
+ *
+ * \param size
+ *		Number of bytes to allocate
+ *
+ * \return
+ *		Ptr to allocated memory (aligned to 32 byte boundary)
+ *	
+ ******************************************************************************
+ */
+
+static void* myAlloc(u32 size)
+{
+//	if ( g_in_cutscene )
+//	{
+//		int mem_available;
+//
+//		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+//		mem_available = Mem::Manager::sHandle().Available();
+//		if ( (int)size < ( mem_available - ( 30 * 1024 ) ) )
+//		{
+//		}
+//		else
+//		{
+//			Mem::Manager::sHandle().PopContext();
+//			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+//			mem_available = Mem::Manager::sHandle().Available();
+//			if ( (int)size < ( mem_available - ( 5 * 1024 ) ) )
+//			{
+//			}
+//			else
+//			{
+//				Mem::Manager::sHandle().PopContext();
+//				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+//				mem_available = Mem::Manager::sHandle().Available();
+//				if ( (int)size < ( mem_available - ( 15 * 1024 ) ) )
+//				{
+//				}
+//				else
+//				{
+//					Mem::Manager::sHandle().PopContext();
+//					Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneTopDownHeap());	
+//				}
+//			}
+//		}
+//	}
+//	else
+//	{
+
+//	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+//	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().BottomUpHeap());
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().AudioHeap());
+
+//	}
+	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+	void * p = new u8[size]; 
+	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+	Mem::Manager::sHandle().PopContext();
+
+	return p;
+}
+
+/*!
+ ******************************************************************************
+ * \brief
+ *		Memory free callback.
+ *
+ * \param ptr
+ *		Memory address to free
+ *	
+ ******************************************************************************
+ */
+static void myFree(void* ptr)
+{
+	ASSERT(ptr);	// free on address 0 is only valid in C++ delete
+	delete [] ptr;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_DecodeLoop( void )
+{
+	// Deal with the asynchronous opening of music tracks.
+	if( audio_player.asyncOpenCallbackStatus > 0 )
+	{
+		gPcmStatus |= PCMSTATUS_MUSIC_PLAYING;
+	
+		if( audio_player.asyncOpenCallbackStatus == ASYNC_OPEN_SUCCESS )
+		{
+			// Reset the callback status.
+			audio_player.asyncOpenCallbackStatus = 0;
+			
+			// At this point we can complete the initialization process.
+			if( AUDSimpleAllocDVDBuffers() == FALSE )
+				OSHalt("*** AUDSimpleAllocDVDBuffers failed!\n");
+			if( AUDSimpleCreateDecoder() == FALSE )
+				OSHalt("*** AUDSimpleCreateDecoder failed!\n");
+			if( AUDSimpleInitAudioDecoder() == FALSE )
+				OSHalt("*** AUDSimpleInitAudioDecoder failed!\n");
+			
+			// Reset the music volume, now that we have voices allocated.
+			PCMAudio_SetMusicVolume( musicVolume );
+			
+			// Preload all DVD buffers and set 'loop' mode to false.
+			AUDSimpleLoadStart( FALSE );
+		}
+		return;
+	}
+	
+	// Decode one frame.
+	if( audio_player.decoder && !musicPaused )
+	{
+		gPcmStatus |= PCMSTATUS_MUSIC_PLAYING;
+		
+		if(	audio_player.decodeComplete )
+		{
+			if(	audio_player.playbackComplete )
+			{
+//				OSReport( "Ending song\n" );
+			
+				// Come to the end of the music data. Free our resources.
+				AUDSimpleLoadStop();
+				AUDSimpleClose();
+				AUDSimpleFreeDVDBuffers();
+				
+				AUDSimpleDestroyDecoder();
+				AUDSimpleExitAudioDecoder();
+			
+				gPcmStatus &= ~PCMSTATUS_MUSIC_PLAYING;
+			}
+		}
+		else if( audio_player.preFetchState && !musicPreloadActive )
+		{
+			// See how many free samples in the read buffer (the big buffer into which the bitstream
+			// is decoded). Overflowing this buffer causes DivX to behave very badly.
+			u32	free_samples = AUDSimpleAudioGetFreeReadBufferSamples();
+//			OSReport( "Free samples is: %d\n", free_samples );
+
+			if( free_samples > 20000 )
+			{
+				// 20000 samples was derived empirically.
+				if( AUDSimpleDecode() == FALSE )
+				{
+//					OSReport( "Decode complete\n" );
+					audio_player.decodeComplete = true;
+				}
+			}
+			else if( audio_player.playbackComplete )
+			{
+				// Seems like if the CPU is starved, the playbackComplete flag can be set despite the 
+				// decodeComplete flag not being set. Try to cater for this situation here.
+				if( AUDSimpleDecode() == FALSE )
+				{
+//					OSReport( "Decode complete\n" );
+					audio_player.decodeComplete = true;
+				}
+			}
+		}
+	}
+
+	// The decoder changes these, so reset them, otherwise animations/particles are messed up.
+    GQRSetup6( GQR_SCALE_64,		// Pos
+               GQR_TYPE_S16,
+			   GQR_SCALE_64,
+               GQR_TYPE_S16 );
+    GQRSetup7( 14,		// Normal
+               GQR_TYPE_S16,
+               14,
+               GQR_TYPE_S16 );
+//	if( AUDSimpleCheckDVDError() == FALSE )
+//	{
+//		OSHalt( "*** DVD error occured. A true DVD error handler is not part of this demo.\n" );
+//	}
+
+
+}
+#endif
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PlayMusicTrack( const char *filename, bool preload )
+{
+	// Set the preload flag.
+	musicPreloadActive = preload;
+	
+	if ( NxNgc::EngineGlobals.disableReset ) return false;
+
+	if( PCMAudio_GetMusicStatus() != PCM_STATUS_FREE )
+	{
+		Dbg_MsgAssert( 0, ( "Playing new track without stopping the first track." ));
+		return false;
+	}
+
+	File::StopStreaming();
+
+//#	ifdef USE_VORBIS
+#	ifdef USE_VORBIS
+	{
+		// Fix up the filename, which comes in the form music\vag\songs\, and add the .ogg extension.
+		static char	name_buffer[256] = "music/ogg/";
+		strcpy( &name_buffer[10], &filename[10] );
+		strcpy( name_buffer + strlen( name_buffer ), ".ogg" );
+		char* p = name_buffer;
+		while( *p != '\0' )
+		{
+			if ( *p == '\\' ) *p = '/';
+			++p;
+		}
+
+		AUDSimpleInit( myAlloc, myFree );
+		
+		// This will open the file asynchronously, so nothing after this point except wait.
+		if( AUDSimpleOpen( name_buffer ) == FALSE )
+		{
+			// File not found.
+			return false;
+		}
+
+		audio_player.decodeComplete		= false;
+		audio_player.playbackComplete	= false;
+		
+//		if( AUDSimpleAllocDVDBuffers() == FALSE )
+//			OSHalt("*** AUDSimpleAllocDVDBuffers failed!\n");
+//		if( AUDSimpleCreateDecoder() == FALSE )
+//			OSHalt("*** AUDSimpleCreateDecoder failed!\n");
+//		if( AUDSimpleInitAudioDecoder() == FALSE )
+//			OSHalt("*** AUDSimpleInitAudioDecoder failed!\n");
+
+		// Preload all DVD buffers and set 'loop' mode
+//		AUDSimpleLoadStart( FALSE );
+		
+		return true;
+	}
+#	endif		// USE_VORBIS
+////#	else
+//
+//	// Allow turning off of music from script global.
+////	int testMusic = Script::GetInt( 0x47ac7ba5, false ); // checksum 'testMusicFromHost'
+////	if( !testMusic )
+////	{
+////		return false;
+////	}
+//
+//	{
+//		// Fix up the filename, and add the .dtk extension.
+//		static char	name_buffer[256] = "music/dtk/";
+//		strcpy( &name_buffer[10], &filename[10] );
+//		strcpy( name_buffer + strlen( name_buffer ), ".dtk" );
+//		char* p = name_buffer;
+//		while( *p != '\0' )
+//		{
+//			if ( *p == '\\' ) *p = '/';
+//			++p;
+//		}
+//
+//	#	ifndef DVDETH
+//		u32 dtk_result = DTKQueueTrack( name_buffer, &s_dtk_track, 0xff, s_dtk_callback );
+//	//	Dbg_MsgAssert( dtk_result == DTK_QUEUE_SUCCESS, ( "Failed to play: %s\n", name_buffer ));
+//
+//		if( dtk_result == DTK_QUEUE_SUCCESS )
+//		{
+//			if(	musicPaused )
+//			{
+//				// The callback will handle pausing the track.
+//				DTKSetState( DTK_STATE_RUN );
+//	//			DTKSetState( DTK_STATE_PAUSE );
+//			}
+//			else
+//			{
+//				DTKSetState( DTK_STATE_RUN );
+//			}
+//
+//			gPcmStatus |= PCMSTATUS_MUSIC_PLAYING;
+//			return true;
+//		}
+//	#	endif		// DVDETH
+//		// If we get here then the track did not get queued.
+//	}
+//	return false;
+////#	endif // USE_VORBIS
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_StopMusic( bool waitPlease )
+{
+//	int status = PCMAudio_GetMusicStatus();
+
+//	if ( g_using_dtk )
+//	{
+//		if( status == PCM_STATUS_LOADING )
+//		{
+//			Dbg_MsgAssert( 0, ( "What, how can we be loading?" ));
+//		}
+//		else if( status == PCM_STATUS_PLAYING )
+//		{
+//			// Stop the track playing.
+//#			ifndef DVDETH
+//			DTKSetState( DTK_STATE_STOP );
+//#			endif		// DVDETH
+//		}
+//		gPcmStatus &= ~PCMSTATUS_MUSIC_PLAYING;
+//	}
+//	else
+	{
+#		ifdef USE_VORBIS
+		if( audio_player.asyncOpenCallbackStatus > 0 )
+		{
+			// Player is in async startup state, hasn't allocated buffers yet.
+			audio_player.error = TRUE;
+		}
+		else if( audio_player.decoder )
+		{
+			if( !audio_player.preFetchState )
+			{
+				// Still seeding the DVD buffers, so dangerous to stop the music immediately.
+				s32 tick = OSGetTick();
+				while( audio_player.preFetchState == false )
+				{
+					s32 diff = OSDiffTick( OSGetTick(), tick );
+					
+					// Wait no more than quarter of a second.
+					if( diff > (s32)( OS_TIMER_CLOCK / 4 ))
+					{
+						break;
+					}
+				}
+			}
+			
+			AUDSimpleAudioReset();
+			AUDSimpleLoadStop();
+			AUDSimpleClose();
+	
+			AUDSimpleFreeDVDBuffers();
+			AUDSimpleDestroyDecoder();
+			AUDSimpleExitAudioDecoder();
+		}
+#	endif
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int PCMAudio_GetMusicStatus( void )
+{
+//	if ( g_using_dtk )
+//	{
+//		if( gPcmStatus & PCMSTATUS_LOAD_MUSIC )
+//		{
+//			return PCM_STATUS_LOADING;
+//		}
+//		else if( gPcmStatus & PCMSTATUS_MUSIC_PLAYING )
+//		{
+//			return PCM_STATUS_PLAYING;
+//		}
+//		else
+//		{
+//			// Sanity check.
+//	#		ifndef DVDETH
+//			u32 state = DTKGetState();
+//			if( state == DTK_STATE_BUSY )
+//			{
+//				return PCM_STATUS_PLAYING;
+//			}
+//	#		endif		// DVDETH
+//			return PCM_STATUS_FREE;
+//		}
+//		return 0;
+//	}
+//	else
+	{
+#		ifdef USE_VORBIS
+		if( audio_player.asyncOpenCallbackStatus > 0 )
+		{
+			return PCM_STATUS_LOADING;
+		}
+		if( audio_player.decoder )
+		{
+			return PCM_STATUS_PLAYING;
+		}
+#		endif // USE_VORBIS
+		return PCM_STATUS_FREE;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_Pause( bool pause, int ch )
+{
+	if( ch == MUSIC_CHANNEL )
+	{
+		musicPaused = pause;
+
+//		if ( g_using_dtk )
+//		{
+//#			ifndef DVDETH
+//			u32 state = DTKGetState();
+//			if( pause && ( state == DTK_STATE_RUN ))
+//			{
+//				DTKSetState( DTK_STATE_PAUSE );
+//			}
+//			else if( !pause && ( state == DTK_STATE_PAUSE ))
+//			{
+//				DTKSetState( DTK_STATE_RUN );
+//			}
+//#			endif	// DVDETH
+//		}
+//		else
+		{
+#			ifdef USE_VORBIS
+			if( audio_player.decoder )
+			{
+				AUDSimplePause( pause );
+			}
+
+//			PCMAudio_StopMusic( true );
+#			endif // USE_VORBIS
+		}
+//#		ifdef USE_VORBIS
+//#		else
+//#		endif	// USE_VORBIS
+	}
+	else
+	{
+		streamsPaused = pause;
+
+		for( int i = 0; i < NUM_STREAMS; ++i )
+		{
+			if( gStreamInfo[i].p_voice )
+			{
+				if( pause && !gStreamInfo[i].paused )
+				{
+					gStreamInfo[i].paused = true;
+					AXSetVoiceState( gStreamInfo[i].p_voice, AX_PB_STATE_STOP );
+				}
+				else if( !pause && gStreamInfo[i].paused )
+				{
+					gStreamInfo[i].paused = false;
+					AXSetVoiceState( gStreamInfo[i].p_voice, AX_PB_STATE_RUN );
+				}
+			}
+		}
+		if ( pause )
+		{
+			for( int s = 0; s < NUM_STREAMS; ++s )
+			{
+				gStreamInfo[s].has_paused = true;
+				if( gStreamInfo[s].p_voice )
+					setDolby( &gStreamInfo[s].p_voice->pb, gStreamInfo[s].p_voice, 0, 0 );
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Returns true if there is a pcm error.                          */
+/*                                                                */
+/******************************************************************/
+//static uint64 last_vb = 0;
+int PCMAudio_Update( void )
+{
+//	uint64 vb = Tmr::GetVblanks();
+//	if ( last_vb == vb ) return true;
+//	last_vb = vb;
+
+	gPcmStatus &= ~PCMSTATUS_MUSIC_PLAYING;
+
+//	if ( g_using_dtk )
+//	{
+//#		ifndef DVDETH
+//		u32 state = DTKGetState();
+//	
+//		if( state != DTK_STATE_STOP )
+//		{
+//			gPcmStatus |= PCMSTATUS_MUSIC_PLAYING;
+//		}
+//#		endif	// DVDETH
+//	}
+//	else
+	{
+#		ifdef USE_VORBIS
+		PCMAudio_DecodeLoop();
+#		endif		// USE_VORBIS
+	}
+#	ifdef USE_VORBIS
+#	else
+#	endif	// USE_VORBIS
+
+	// Check the stream voices to see if they are finished playing.
+	bool check = false;
+	for( int i = 0; i < NUM_STREAMS; ++i )
+	{
+		if( gStreamInfo[i].p_voice )
+		{
+			if(( gStreamInfo[i].p_voice->pb.state == AX_PB_STATE_STOP ) && ( gStreamInfo[i].hasStartedPlaying ) && !gStreamInfo[i].paused && !gStreamInfo[i].preloadActive )
+			{
+				// Playback on this voice has ended, so free up the voice.
+				int enabled = OSDisableInterrupts();
+				AXFreeVoice( gStreamInfo[i].p_voice );
+				gStreamInfo[i].p_voice = NULL;
+				gStreamInfo[i].currentFileInfo = ( gStreamInfo[i].currentFileInfo + 1 ) & 0x07;
+				OSRestoreInterrupts( enabled );
+
+				gPcmStatus &= ~PCMSTATUS_LOAD_STREAM( i );
+				gPcmStatus &= ~PCMSTATUS_STREAM_PLAYING( i );
+//				OSReport( "freed voice for stream: %d\n", i );
+			}
+			else
+			{
+				// Check to see whether the sound has played beyond the amount of data already dma'ed.
+				int offset = gStreamInfo[i].p_voice->pb.addr.currentAddressLo + ((int)gStreamInfo[i].p_voice->pb.addr.currentAddressHi * 65536 );
+
+				// Convert offset from nibble to byte address, and make relative to stream base.
+				offset = ( offset / 2 ) - stream_base[i];
+//				OSReport( "stream %d offset is at: %d\n", i, offset );
+
+				if( offset > gStreamInfo[i].offsetInARAM )
+				{
+//					OSReport( "play point beyond byuffer...\n" );
+				}
+
+				// Flag this stream as playing.
+				gPcmStatus |= PCMSTATUS_STREAM_PLAYING( i );
+				check = true;
+			}
+		}
+	}
+	// Deal with disk errors.
+	if ( gPcmStatus & PCMSTATUS_MUSIC_PLAYING ) check = true;
+	if ( check ) DVDCheckAsync();
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 PCMAudio_FindNameFromChecksum( uint32 checksum, int ch )
+{
+	File::SHed*	pHed;
+
+	if( ch == EXTRA_CHANNEL )
+	{
+		pHed = gpStreamHed;
+	}
+	else
+	{
+		// Don't use the music hed file.
+		return NULL;
+	}
+
+	if( !pHed )
+	{
+		return NULL;
+	}
+
+	File::SHedFile* pHedFile = File::FindFileInHedUsingChecksum( checksum, pHed );
+	if( pHedFile )
+	{
+		return pHedFile->Checksum;
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PlayStream( uint32 checksum, int whichStream, float volumeL, float volumeR, float fPitch, bool preload )
+{
+//#ifdef 
+	
+	if( PCMAudio_GetStreamStatus( whichStream ) != PCM_STATUS_FREE )
+	{
+		return false;
+	}
+
+//	int testStreams = Script::GetInt( 0x62df9442, false ); // checksum 'testStreamsFromHost'
+//	if( !testStreams )
+//	{
+//		return false;
+//	}
+
+	// Incoming filenames are either of the form '\skateshp\menu', or '\streams\la\la_argue51' etc.
+	// Strip the preceeding '\streams' if present.
+//	if( strnicmp( filename, "\\streams", 8 ) == 0 )
+//	{
+//		filename += 8;
+//	}
+//
+//	static char name_buffer[128] = "streams/dsp";
+//	const int MUSIC_PREPEND_START_POS = 11;
+//
+//	// Need to append file type. Copy string into a buffer. Check there is room to add the extension.
+//	int length = strlen( filename );
+//	Dbg_Assert( length <= ( 128 - ( MUSIC_PREPEND_START_POS + 4 )));
+//	strcpy( name_buffer + MUSIC_PREPEND_START_POS, filename );
+//	strcpy( name_buffer + MUSIC_PREPEND_START_POS + length, ".dsp" );
+//
+//	// Fix directory separators.
+//	char* p = name_buffer;
+//	while( *p != '\0' )
+//	{
+//		if ( *p == '\\' ) *p = '/';
+//		++p;
+//	}
+
+	File::SHedFile *pHed = FindFileInHedUsingChecksum( checksum, gpStreamHed );
+
+#ifdef DVDETH
+	BOOL result = false;
+#else
+	BOOL result = DVDFastOpen( DVDConvertPathToEntrynum( "streams/streamsngc.wad" ), &( gStreamInfo[whichStream].fileInfo[gStreamInfo[whichStream].currentFileInfo] ) );
+#endif
+//	DVDSeek( &( gStreamInfo[whichStream].fileInfo[gStreamInfo[whichStream].currentFileInfo] ), pHed->Offset );
+	if( result )
+	{
+		if ( pHed && pHed->FileSize )
+		{
+			// Allocate a voice for use, and store it for later.
+			AXVPB*		p_axvpb							= AXAcquireVoice( AX_PRIORITY_NODROP, ax_voice_reacquisition_callback, 0 );
+			Dbg_Assert( p_axvpb );
+			gStreamInfo[whichStream].p_voice			= p_axvpb;
+			gStreamInfo[whichStream].paused				= false;
+			gStreamInfo[whichStream].hasStartedPlaying	= false;
+	
+			// Flag that we are loading the stream up.
+			gPcmStatus |= PCMSTATUS_LOAD_STREAM( whichStream );
+	
+			// Read in the first chunk.
+			gStreamInfo[whichStream].offset				= 0;
+			gStreamInfo[whichStream].offsetInARAM		= 0;
+			gStreamInfo[whichStream].nextWrite			= 0;
+	
+			gStreamInfo[whichStream].hedOffset			= pHed->Offset;
+			
+			// Round filesize up to 32 byte alignment.
+			gStreamInfo[whichStream].fileSize			= pHed->FileSize;
+			gStreamInfo[whichStream].pitch				= fPitch;
+			gStreamInfo[whichStream].volumeL			= volumeL;
+			gStreamInfo[whichStream].volumeR			= volumeR;
+//			OSReport( "%s %d\n", name_buffer, gStreamInfo[whichStream].fileSize );
+	
+			// Shhhh.....
+			setDolby( &p_axvpb->pb, p_axvpb, 0, 0 );
+
+			// Set the preload flag.
+			gStreamInfo[whichStream].preloadActive = preload;
+			
+			// The first chunk we read is STREAM_BUFFER_HEADER_SIZE bigger than normal, so that even with the header in
+			// the first read, we can still DMA the full amount up to ARAM.
+			int bytes_to_read = ( pHed->FileSize >= ( STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE )) ? ( STREAM_BUFFER_SIZE + STREAM_BUFFER_HEADER_SIZE ) : ( pHed->FileSize & ~31 );
+			gStreamInfo[whichStream].has_paused = false;
+//			OSReport( "Starting stream %x on channel %d\n", checksum, whichStream );
+			result = DVDReadAsync(	&( gStreamInfo[whichStream].fileInfo[gStreamInfo[whichStream].currentFileInfo] ),
+									stream_mem_buffer[whichStream],		// Base pointer of memory to read data into.
+									bytes_to_read,						// Bytes to read.
+									pHed->Offset,						// Offset in file.
+									s_dvd_callback );					// Read complete callback.
+	
+			// Nothing more required for now.
+			return true;
+		}
+		else
+		{
+			DVDClose( &( gStreamInfo[whichStream].fileInfo[gStreamInfo[whichStream].currentFileInfo] ));
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_SetStreamGlobalVolume( unsigned int volume )
+{
+	gStreamVolume = volume;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_SetStreamVolume( float volumeL, float volumeR, int whichStream )
+{
+	if( gStreamInfo[whichStream].p_voice )
+	{
+		int old = OSDisableInterrupts();
+
+		AXPB*	p_pb	= &( gStreamInfo[whichStream].p_voice->pb );
+
+		Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+		volumeL = PERCENT( sfx_manager->GetMainVolume() / 2.0f, volumeL );
+		volumeR = PERCENT( sfx_manager->GetMainVolume() / 2.0f, volumeR );
+		setDolby( p_pb, gStreamInfo[whichStream].p_voice, volumeL, volumeR );
+
+		OSRestoreInterrupts( old );
+
+//		// Adjust stream volume based on global sound effect volume.
+//		Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+//		p_pb->mix.vL						= (unsigned int)(( volL * sfx_manager->GetMainVolume()) * 0.01f );
+//		p_pb->mix.vR						= (unsigned int)(( volR * sfx_manager->GetMainVolume()) * 0.01f );
+//
+//		if( !Sfx::isStereo )
+//		{
+//			unsigned int mix	= ( p_pb->mix.vL + p_pb->mix.vR ) / 2;
+//			p_pb->mix.vL		= mix;
+//			p_pb->mix.vR		= mix;
+//		}
+//
+//		gStreamInfo[whichStream].p_voice->sync  |= ( AX_SYNC_USER_MIX );
+	}
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_SetStreamPitch( float fPitch, int whichStream )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_StopStream( int whichStream, bool waitPlease )
+{
+	// Clear the preload flag to be safe.
+	gStreamInfo[whichStream].preloadActive	= false;
+	sPreLoadChecksum[whichStream]			= 0;
+	
+	if( gStreamInfo[whichStream].p_voice )
+	{
+		int enabled = OSDisableInterrupts();
+		AXFreeVoice( gStreamInfo[whichStream].p_voice );
+		gStreamInfo[whichStream].p_voice			= NULL;
+		gStreamInfo[whichStream].currentFileInfo	= ( gStreamInfo[whichStream].currentFileInfo + 1 ) & 0x07;
+		OSRestoreInterrupts( enabled );
+
+		gPcmStatus &= ~PCMSTATUS_LOAD_STREAM( whichStream );
+		gPcmStatus &= ~PCMSTATUS_STREAM_PLAYING( whichStream );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_StopStreams( void )
+{
+	for( int i = 0; i < NUM_STREAMS; i++ )
+	{
+		PCMAudio_StopStream( i, true );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/* Get a stream loaded into a buffer, but don't play yet.		  */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadStream( uint32 checksum, int whichStream )
+{
+	bool rv = PCMAudio_PlayStream( checksum, whichStream, 0.0f, 0.0f, 100.0f, true );
+	if( rv )
+	{
+		sPreLoadChecksum[whichStream] = checksum;
+	}
+	return rv;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Returns true if preload done. Assumes that caller is calling	  */
+/* this on a preloaded, but not yet played, stream. The results	  */
+/* are meaningless otherwise.									  */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadStreamDone( int whichStream )
+{
+	if( gStreamInfo[whichStream].preloadActive )
+	{
+		// The hasStartedPlaying member will be set to true in the start voice callback, which is when
+		// we are ready to allow playback.
+		if( gStreamInfo[whichStream].hasStartedPlaying )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Tells a preloaded stream to start playing. Must call			  */
+/* PCMAudio_PreLoadStreamDone() first to guarantee that it starts */
+/* immediately.													  */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_StartPreLoadedStream( int whichStream, float volumeL, float volumeR, float pitch )
+{
+	Dbg_Assert( gStreamInfo[whichStream].offset > 0 );
+	
+	sPreLoadChecksum[whichStream] = 0;
+	
+	if( gStreamInfo[whichStream].p_voice )
+	{
+		gStreamInfo[whichStream].preloadActive = false;
+		
+		// If we are not paused, set voice to RUN.
+		if( !streamsPaused )
+		{
+			AXSetVoiceState( gStreamInfo[whichStream].p_voice, AX_PB_STATE_RUN );
+		}
+		
+		// Set the correct volume.
+		PCMAudio_SetStreamVolume( volumeL, volumeR, whichStream );
+		PCMAudio_SetStreamPitch( pitch, whichStream );
+		return true;
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadMusicStream( uint32 checksum )
+{
+	sPreLoadMusicChecksum = checksum;
+
+	static char* p_filename[] =
+	{
+		"AU/AU_01V_Female",
+		"AU/AU_01V_Male",
+		"AU/AU_02_Female",
+		"AU/AU_02_Male",
+		"Final/Final_Female",
+		"Final/Final_Male",
+		"FL/FL_01_Female",
+		"FL/FL_01_Male",
+		"FL/FL_02_Female",
+		"FL/FL_02_Male",
+		"FL/FL_03_Female",
+		"FL/FL_03_Male",
+		"FL/FL_04_Female",
+		"FL/FL_04_Male",
+		"HI/HI_01_Female",
+		"HI/HI_01_Male",
+		"HI/HI_02V_Female",
+		"HI/HI_02V_Male",
+		"HI/HI_03_Female",
+		"HI/HI_03_Male",
+		"HI/HI_04_Female",
+		"HI/HI_04_Male",
+		"HI/HI_05_Female",
+		"HI/HI_05_Male",
+		"Intro/Intro_01_Female",
+		"Intro/Intro_01_Male",
+		"Intro/Intro_02_Female",
+		"Intro/Intro_02_Male",
+		"NJ/NJ_01V_Female",
+		"NJ/NJ_01V_Male",
+		"NJ/NJ_02A_Female",
+		"NJ/NJ_02A_Male",
+		"NJ/NJ_02B_Female",
+		"NJ/NJ_02B_Male",
+		"NJ/NJ_02_Female",
+		"NJ/NJ_02_Male",
+		"NJ/NJ_03_Female",
+		"NJ/NJ_03_Male",
+		"NJ/NJ_04_Female",
+		"NJ/NJ_04_Male",
+		"NJ/NJ_05B_Female",
+		"NJ/NJ_05B_Male",
+		"NJ/NJ_05_Female",
+		"NJ/NJ_05_Male",
+		"NJ/NJ_06_Female",
+		"NJ/NJ_06_Male",
+		"NJ/NJ_07_Female",
+		"NJ/NJ_07_Male",
+		"NJ/NJ_08_Female",
+		"NJ/NJ_08_Male",
+		"NJ/NJ_09_ALT_Female",
+		"NJ/NJ_09_ALT_Male",
+		"NJ/NJ_09_Female",
+		"NJ/NJ_09_Male",
+		"NJ/NJ_10_Female",
+		"NJ/NJ_10_Male",
+		"NJ/NJ_Pool_Female",
+		"NJ/NJ_Pool_Male",
+		"NY/NY_01V_Female",
+		"NY/NY_01V_Male",
+		"NY/NY_02_Female",
+		"NY/NY_02_Male",
+		"NY/NY_03_Female",
+		"NY/NY_03_Male",
+		"RU/RU_01V_Female",
+		"RU/RU_01V_Male",
+		"RU/RU_02_Female",
+		"RU/RU_02_Male",
+		"RU/RU_03B_Female",
+		"RU/RU_03B_Male",
+		"RU/RU_03_Female",
+		"RU/RU_03_Male",
+		"SD/SD_01_Female",
+		"SD/SD_01_Male",
+		"SD/SD_02_Female",
+		"SD/SD_02_Male",
+		"SD/SD_03_Female",
+		"SD/SD_03_Male",
+		"SJ/SJ_01A_Female",
+		"SJ/SJ_01A_Male",
+		"SJ/SJ_01B_Female",
+		"SJ/SJ_01B_Male",
+		"SJ/SJ_02_Female",
+		"SJ/SJ_02_Male",
+		"VC/VC_01V_Female",
+		"VC/VC_01V_Male",
+		"VC/VC_02_Female",
+		"VC/VC_02_Male"
+	};
+
+	// Clear the preload flag.
+	musicPreloadActive = false;
+	
+	for ( uint lp = 0; lp < ( sizeof( p_filename ) / sizeof( char* )); lp++ )
+	{
+		char *p_name = strchr( p_filename[lp], '/' );
+		if( p_name )
+		{
+			uint32 name_checksum = Crc::GenerateCRCFromString( p_name + 1 );
+			if( checksum == name_checksum )
+			{
+				// Got a match - hook up filename here....
+				sprintf( sPreLoadMusicFilename, "music/vag/cutscenes/%s", p_filename[lp] );
+				return PCMAudio_PlayMusicTrack( sPreLoadMusicFilename, true );
+			}
+		}
+	}
+	sPreLoadMusicFilename[0] = 0;
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_PreLoadMusicStreamDone( void )
+{
+	if( musicPreloadActive )
+	{
+		if( audio_player.preFetchState )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_StartPreLoadedMusicStream( void )
+{
+	// Maybe we should check here to make sure the checksum of the music info filestream matches that
+	// passed in when the music stream preload request came in.
+	if( sPreLoadMusicFilename[0] > 0 )
+	{
+		// Turn off the preload flag, which will allow decoding to start.
+		musicPreloadActive = false;
+
+		// Call the update loop immediately.
+		PCMAudio_DecodeLoop();
+		return true;
+	}
+	return false;	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int PCMAudio_GetStreamStatus( int whichStream )
+{
+	if( whichStream >= NUM_STREAMS )
+	{
+		Dbg_MsgAssert( 0, ( "Checking stream status on stream %d, past valid range ( 0 to %d ).", whichStream, NUM_STREAMS - 1 ) );
+		return false;
+	}
+
+	if( sPreLoadChecksum[whichStream] > 0 )
+	{
+		return PCM_STATUS_LOADING;
+	}
+
+	if( gPcmStatus & PCMSTATUS_LOAD_STREAM( whichStream ))
+	{
+		return PCM_STATUS_LOADING;
+	}
+	else if( gPcmStatus & PCMSTATUS_STREAM_PLAYING( whichStream ))
+	{
+		return PCM_STATUS_PLAYING;
+	}
+	else
+	{
+		return PCM_STATUS_FREE;
+	}
+
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PCMAudio_PauseStream( bool pause, int whichStream )
+{
+	// Not sure how to do this yet...
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool PCMAudio_LoadStreamHeader( const char *nameOfFile )
+{
+	if( gpStreamHed )
+	{
+		Mem::Free( gpStreamHed );
+		gpStreamHed = NULL;
+	}
+
+	gpStreamHed = File::LoadHed( nameOfFile );
+
+	if( !gpStreamHed )
+	{
+		Dbg_Message( "Couldn't find stream header %s\n", nameOfFile );
+		return false;
+	}
+	
+	return true;
+}
+
+
+
+
+
+
+
+} // namespace Pcm
diff --git a/Code/Gel/Music/ngc/p_music.h b/Code/Gel/Music/ngc/p_music.h
new file mode 100644
index 0000000..8f2bf56
--- /dev/null
+++ b/Code/Gel/Music/ngc/p_music.h
@@ -0,0 +1,85 @@
+// Audio streaming function prototypes:
+// mjd jan 2001
+
+#ifndef __P_MUSIC_H__
+#define __P_MUSIC_H__
+
+#include 
+#include 
+#include 
+
+namespace Pcm
+{
+
+// allows one channel for music, another for audio:
+enum{
+	EXTRA_CHANNEL,
+	MUSIC_CHANNEL,
+};
+
+
+// Onetime call once upon loading the game...
+void PCMAudio_Init( void );
+
+// Call every frame to make sure music and stream buffers are loaded and current status is checked each frame...
+int PCMAudio_Update( void );
+
+// Load a track and start it playing...
+// You wanna loop a track?  Wait until this track is done, and PLAY IT AGAIN SAM!
+bool PCMAudio_PlayMusicTrack( const char *filename, bool preload = false );
+bool PCMAudio_PlayStream( uint32 checksum, int whichStream, float volumeL, float volumeR, float pitch, bool preload = false );
+
+// keep song loaded, stop playing it (or continue playing paused song)
+void PCMAudio_Pause( bool pause = true, int ch = MUSIC_CHANNEL );
+
+void PCMAudio_PauseStream( bool pause, int whichStream );
+
+void PCMAudio_StopMusic( bool waitPlease );
+void PCMAudio_StopStream( int whichStream, bool waitPlease = true );
+void PCMAudio_StopStreams( void );
+
+// Preload streams. By preloading, you can guarantee they will start at a certain time.
+bool PCMAudio_PreLoadStream( uint32 checksum, int whichStream );
+bool PCMAudio_PreLoadStreamDone( int whichStream );
+bool PCMAudio_StartPreLoadedStream( int whichStream, float volumeL, float volumeR, float pitch );
+
+// Preload music streams. By preloading, you can guarantee they will start at a certain time.
+bool PCMAudio_PreLoadMusicStream( uint32 checksum );
+bool PCMAudio_PreLoadMusicStreamDone( void );
+bool PCMAudio_StartPreLoadedMusicStream( void );
+
+// set the music volume ( 0 to 100 )
+int		PCMAudio_SetMusicVolume( float volume );
+bool	PCMAudio_SetStreamVolume( float volumeL, float volumeR, int whichStream );
+bool	PCMAudio_SetStreamPitch( float pitch, int whichStream );
+//int PCMAudio_SetStreamVolumeAndPitch( float volumeL, float volumeR, float pitch, int whichStream );
+
+void PCMAudio_SetStreamGlobalVolume( unsigned int volume );
+
+enum{
+	PCM_STATUS_FREE 			= ( 1 << 0 ),
+	PCM_STATUS_PLAYING			= ( 1 << 1 ),
+	PCM_STATUS_LOADING 			= ( 1 << 2 ),
+};
+
+// Return one of the PCM_STATUS values from above...
+int PCMAudio_GetMusicStatus( );
+int PCMAudio_GetStreamStatus( int whichStream );
+
+// preload any CD location info (to avoid a hitch in framerate on PS2):
+unsigned int PCMAudio_GetCDLocation( const char *pTrackName );
+
+bool PCMAudio_TrackExists( const char *pTrackName, int ch );
+bool PCMAudio_LoadMusicHeader( const char *nameOfFile );
+bool PCMAudio_LoadStreamHeader( const char *nameOfFile );
+
+uint32 PCMAudio_FindNameFromChecksum( uint32 checksum, int ch );
+
+// borrow this memory for the movies and shit...
+int PCMAudio_GetIopMemory( void );
+		 
+void setDolby( AXPB * p_pb, AXVPB * p_vpb, float volL, float volR, unsigned int pitch = 0, bool set_pitch = false, float volume_percent = 100.0f );
+
+} // namespace PCM
+
+#endif // __P_MUSIC_H__
diff --git a/Code/Gel/Music/ngc/pcm/pcm.h b/Code/Gel/Music/ngc/pcm/pcm.h
new file mode 100644
index 0000000..5193644
--- /dev/null
+++ b/Code/Gel/Music/ngc/pcm/pcm.h
@@ -0,0 +1,167 @@
+#ifndef __MODULES_PCM_PCM_H
+#define __MODULES_PCM_PCM_H
+
+#define NUM_STREAMS 3
+
+#define PCM_RPC_ARG_NUM_INTS 16
+
+#define DEFAULT_PITCH	0x1000
+
+// VAG header structure
+typedef struct
+{
+	char ID[4];
+	int version;
+	char reserved1[4];
+	int dataSize;
+	int sampleFreq;
+	char reserved2[12];
+	char name[16];
+} VagHeader;
+
+#define SIZE_OF_VAGHEADER ( sizeof( VagHeader ) )
+
+// sound stuff:
+#define SB_BUF_SIZE ( 1024 * 32 )		// hex 2000
+#define SB_BUF_HALF ( SB_BUF_SIZE / 2 )	// hex 1000
+#define SB_BUF_SIZE_WITH_PADDING	( SB_BUF_SIZE + 0x40 )
+
+// spu ram layout:
+// first valid address for use in SPU RAM
+#define SPU_RAM_SIZE					0x1fffff
+#define SB_FIRST_USEABLE_ADDR			0x5010
+
+//#define STRAY_VOICE_BLOCKER_SIZE		128
+//#define STRAY_VOICE_BLOCKER_SPU_ADDR	SB_TOP
+//#define STREAM_SPU_ADDR					( STRAY_VOICE_BLOCKER_SPU_ADDR + STRAY_VOICE_BLOCKER_SIZE )
+#define STREAM_SPU_ADDR					SB_FIRST_USEABLE_ADDR
+
+// slip in room for 3 streaming VAGS ( 2 for stereo music and one extra stream ):
+//#define SINGLE_STREAM_BUFFER_SIZE 2048
+#define TOTAL_SPU_REQUIRED_FOR_STREAMS		( SB_BUF_SIZE_WITH_PADDING * 4 )
+#define MUSIC_L_SPU_BUF_LOC					( STREAM_SPU_ADDR + 0x40 )
+#define MUSIC_R_SPU_BUF_LOC					( MUSIC_L_SPU_BUF_LOC + SB_BUF_SIZE_WITH_PADDING )
+#define STREAM_SPU_BUF_LOC( i )				( MUSIC_R_SPU_BUF_LOC + ( ( 1 + ( i ) ) * SB_BUF_SIZE_WITH_PADDING ) )
+
+#define END_OF_STREAMS						( STREAM_SPU_ADDR + TOTAL_SPU_REQUIRED_FOR_STREAMS + 0x40 )
+
+//#define RAM_NEEDED_FOR_EFFECTS		0	// FUCK the effects...
+#define	RAM_NEEDED_FOR_EFFECTS			0xade0 // enough for HALL reverb
+//#define	RAM_NEEDED_FOR_EFFECTS		0xF6C0 // enough for space echo
+ 
+#define REVERB_ONLY_ON_CORE_0			1
+
+#if !REVERB_ONLY_ON_CORE_0
+
+#define CORE_1_EFFECTS_START_ADDRESS	END_OF_STREAMS
+#define CORE_1_EFFECTS_END_ADDRESS		0x02ffff // has to be on a 0xffff boundary!!
+#if ( ( CORE_1_EFFECTS_END_ADDRESS - CORE_1_EFFECTS_START_ADDRESS ) < RAM_NEEDED_FOR_EFFECTS )
+#error "not enough space for core 0 effects!"
+#endif
+
+#define BASE_WAVE_DATA_ADDR				CORE_0_EFFECTS_END_ADDRESS
+
+#else
+#define BASE_WAVE_DATA_ADDR				END_OF_STREAMS
+#endif
+
+#define CORE_0_EFFECTS_START_ADDRESS	( SPU_RAM_SIZE - RAM_NEEDED_FOR_EFFECTS )
+#define CORE_0_EFFECTS_END_ADDRESS		( SPU_RAM_SIZE )
+#define END_WAVE_DATA_ADDR				( CORE_0_EFFECTS_START_ADDRESS  - 0x40 )
+
+// used by temporary sounds and permanently loaded sounds...
+#define MAX_SPU_RAM_AVAILABLE			( END_WAVE_DATA_ADDR - BASE_WAVE_DATA_ADDR )
+
+//#define MUSIC_R_SPU_BUF_LOC		SB_TOP
+//#define MUSIC_L_SPU_BUF_LOC		( MUSIC_R_SPU_BUF_LOC + SB_BUF_SIZE )
+//#define STREAM_SPU_BUF_LOC		( MUSIC_L_SPU_BUF_LOC + SB_BUF_SIZE )
+
+// iop buffer taking streams off of the CD:
+#define MUSIC_IOP_BUFFER_SIZE				( 1024 * 192 )  // must be a multiple of 4k!
+#define MUSIC_HALF_IOP_BUFFER_SIZE			( MUSIC_IOP_BUFFER_SIZE / 2 )  // must be a multiple of 2k!
+#define STREAM_IOP_BUFFER_SIZE				( 1024 * /*128*/64 )  // must be a multiple of 4k!
+#define STREAM_HALF_IOP_BUFFER_SIZE			( STREAM_IOP_BUFFER_SIZE / 2 )  // must be a multiple of 2k!
+
+// must match values in pcm_com.c!!!
+#define TOTAL_IOP_BUFFER_SIZE_NEEDED		( ( MUSIC_IOP_BUFFER_SIZE * 2 ) + ( STREAM_IOP_BUFFER_SIZE * NUM_STREAMS ) )
+#define ALLIGN_REQUIREMENT					64
+#define SECTOR_SIZE							( 2048 )
+#define NUM_SECTORS_PER_STREAM_BUFFER		( STREAM_IOP_BUFFER_SIZE / SECTOR_SIZE )
+#define NUM_SECTORS_PER_STREAM_HALF_BUFFER	( NUM_SECTORS_PER_STREAM_BUFFER / 2 )
+#define NUM_SECTORS_PER_MUSIC_BUFFER		( MUSIC_IOP_BUFFER_SIZE / SECTOR_SIZE )
+#define NUM_SECTORS_PER_MUSIC_HALF_BUFFER	( NUM_SECTORS_PER_MUSIC_BUFFER / 2 )
+
+#define MUSIC_L_IOP_OFFSET	0
+#define MUSIC_R_IOP_OFFSET	( MUSIC_HALF_IOP_BUFFER_SIZE )  // interwoven with the L IOP buffer... and shit
+#define STREAM_IOP_OFFSET( ch )				( ( 2 * MUSIC_IOP_BUFFER_SIZE ) + ( STREAM_IOP_BUFFER_SIZE * ( ch ) ) )
+
+#define MUSIC_L_VOICE	22
+#define MUSIC_R_VOICE	23
+#define MUSIC_CORE		1
+
+#define STREAM_VOICE( i ) ( 23 - ( i ) )
+#define STREAM_CORE		0
+
+// RPC command:
+#define EzADPCM_COMMAND_MASK			0xfff0
+#define EzADPCM_CH_MASK					0x000f
+	
+#define EzADPCM_INIT					0x0000
+#define EzADPCM_QUIT					0x0010
+#define EzADPCM_PLAYMUSIC				0x0020
+#define EzADPCM_STOPMUSIC				0x0030
+#define EzADPCM_PLAYSTREAM				0x0040
+#define EzADPCM_STOPSTREAM				0x0070
+#define EzADPCM_STOPSTREAMS				0x00a0
+#define EzADPCM_SETMUSICVOL				0x00b0
+#define EzADPCM_SETSTREAMVOL			0x00c0
+#define EzADPCM_SETSTREAMVOLANDPITCH	0x00d0
+#define EzADPCM_GETSTATUS				0x00e0
+#define EzADPCM_GETMUSICSTATUS			0x00f0
+#define EzADPCM_PAUSEMUSIC				0x0100
+#define EzADPCM_PAUSESTREAM				0x0110
+#define EzADPCM_PAUSESTREAMS			0x0120
+#define EzADPCM_SETSTREAMGLOBVOL		0x0130
+#define EzADPCM_SDINIT					0x7ff0
+
+// for GETSTREAMSTATUS or GETMUSICSTATUS:
+#define PCM_STATUS					0x0001f000
+#define PCM_STATUS_IDLE		   		0x00000000
+#define PCM_STATUS_PRELOAD			0x00001000
+#define PCM_STATUS_READY_TO_STOP	0x00002000
+#define PCM_STATUS_NEED_UPDATE		0x00003000
+#define PCM_STATUS_RUNNING			0x00005000
+#define PCM_STATUS_TERMINATE		0x00006000
+
+// flags for GETSTATUS
+#define PCMSTATUS_NEED_MUSIC_BUFFER_0	( 1 << 0 )
+#define PCMSTATUS_NEED_MUSIC_BUFFER_1	( 1 << 1 )
+#define PCMSTATUS_NEED_STREAM0_BUFFER_0	( 1 << 2 ) // don't change the order of these!!! IMPORTANT!
+#define PCMSTATUS_NEED_STREAM1_BUFFER_0	( 1 << 3 )
+#define PCMSTATUS_NEED_STREAM2_BUFFER_0	( 1 << 4 )
+#define PCMSTATUS_NEED_STREAM0_BUFFER_1	( 1 << 5 )
+#define PCMSTATUS_NEED_STREAM1_BUFFER_1	( 1 << 6 )
+#define PCMSTATUS_NEED_STREAM2_BUFFER_1	( 1 << 7 )
+#define PCMSTATUS_MUSIC_PLAYING			( 1 << 8 )
+#define PCMSTATUS_STREAM0_PLAYING		( 1 << 9 )
+#define PCMSTATUS_STREAM1_PLAYING		( 1 << 10 )
+#define PCMSTATUS_STREAM2_PLAYING		( 1 << 11 )
+#define PCMSTATUS_LOAD_MUSIC			( 1 << 12 )
+#define PCMSTATUS_LOAD_STREAM0			( 1 << 13 )
+#define PCMSTATUS_LOAD_STREAM1			( 1 << 14 )
+#define PCMSTATUS_LOAD_STREAM2			( 1 << 15 )
+#define PCMSTATUS_INITIALIZED			( 1 << 16 )
+
+// macros:
+#define PCMSTATUS_NEED_STREAM_BUFFER_0( xxx )	( PCMSTATUS_NEED_STREAM0_BUFFER_0 << ( xxx ) )
+#define PCMSTATUS_NEED_STREAM_BUFFER_1( xxx )	( PCMSTATUS_NEED_STREAM0_BUFFER_1 << ( xxx ) )
+#define PCMSTATUS_STREAM_PLAYING( xxx )			( PCMSTATUS_STREAM0_PLAYING << ( xxx ) )
+#define PCMSTATUS_LOAD_STREAM( xxx )			( PCMSTATUS_LOAD_STREAM0 << ( xxx ) )
+// Module ID number
+#define EzADPCM_DEV 0x00012345
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+#endif // __MODULES_PCM_PCM_H
+/* DON'T ADD STUFF AFTER THIS */
diff --git a/Code/Gel/Music/ngc/pcm/pcm_com.c b/Code/Gel/Music/ngc/pcm/pcm_com.c
new file mode 100644
index 0000000..75cb795
--- /dev/null
+++ b/Code/Gel/Music/ngc/pcm/pcm_com.c
@@ -0,0 +1,1655 @@
+/* Vag streaming into SPU2 -- from sony sample -- matt may 2001 */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "pcm.h"
+#include "pcmiop.h"
+
+// ================================================================
+#define DATA_INTEGRITY_CHECK 1
+
+#define SHOW_STATE	0
+#define SHOW_SMALL_STATE 1
+#define SHOW_ACTION 0
+#define SHOW_SMALL_ACTION 1
+#define DBUGPRINTF	0
+#define SHOW_TIMING	1
+#define SHOW_STOP	1
+#define SHOW_SMALL_COMMANDS	1
+
+#if SHOW_SMALL_COMMANDS
+#define ShowSmallCommand printf
+#else
+#define ShowSmallCommand( x )
+#endif
+
+#if SHOW_STOP
+#define ShowStop printf   //printf( " %d}{", GetTime( ) ), printf
+#else
+#define ShowStop( x )
+#endif
+
+#if SHOW_TIMING
+#define ShowTime	printf
+#else
+#define ShowTime DoNothing
+void DoNothing( char* text, ... ) { }
+#endif
+
+#if SHOW_ACTION
+#define ShowAction printf( "Time %d", GetTime( ) ), printf
+#else
+#define ShowAction( x )
+#endif
+
+#if SHOW_SMALL_STATE
+#define SmallShowState printf
+#else
+#define SmallShowState( x )
+#endif
+
+#if SHOW_SMALL_ACTION
+#define SmallShowAction printf( " %d*", GetTime( ) ), printf
+#else
+#define SmallShowAction( x )
+#endif
+
+#if SHOW_STATE
+#define ShowState printf( "Time %d                  ", GetTime( ) ), printf
+#else
+#define ShowState( x )
+#endif
+
+#if DBUGPRINTF
+#define Dbug_Printf( x ) printf( x )
+#else
+#define Dbug_Printf( x )
+#endif
+
+//volatile int gTestPause = 0;
+
+#define SHARE_DMA_CH	0
+
+// the program running on the EE side will check to see if any of the buffers need to be filled:
+volatile unsigned int gEECommand = 0;
+
+#if SHARE_DMA_CH
+#define TRANS_DMA_CH_COMMON		0
+#define TRANS_DMA_CH_MUSIC		TRANS_DMA_CH_COMMON
+#define TRANS_DMA_CH_STREAM		TRANS_DMA_CH_COMMON
+
+enum{
+	LOAD_STATUS_IDLE = 				0,
+	LOAD_STATUS_LOADING_MUSIC =		( 1 << 0 ),
+	LOAD_STATUS_LOADING_STREAM = 	( 1 << 1 ),
+	LOAD_STATUS_NEED_MUSIC =		( 1 << 2 ),
+	LOAD_STATUS_NEED_STREAM =		( 1 << 3 ),
+};
+
+volatile unsigned int gLoadStatus = LOAD_STATUS_IDLE;
+
+#else
+#define TRANS_DMA_CH_MUSIC		1
+#define TRANS_DMA_CH_STREAM		0
+#endif
+
+
+#define _1st 0
+#define _2nd 1
+
+#define DEFAULT_PITCH	0x1000
+
+unsigned int gThid = 0;
+unsigned int gSem = 0;
+unsigned int gpIopBuf;		// IOP SMEM
+unsigned int gMusicIopOffset;
+unsigned int gStreamIopOffset;
+
+enum{
+	MUSIC_LOAD_STATE_IDLE,
+	MUSIC_LOAD_STATE_PRELOADING_L,
+	MUSIC_LOAD_STATE_PRELOADING_R,
+	MUSIC_LOAD_STATE_WAITING_FOR_IRQ,
+	MUSIC_LOAD_STATE_LOADING_L,
+	MUSIC_LOAD_STATE_LOADING_R,
+};
+
+volatile unsigned int gMusicPaused = 0;
+volatile unsigned int gStreamPaused = 0;
+volatile unsigned int gStreamTimeOffset = 0;  // when paused, keep track of time passed since last IRQ
+volatile unsigned int gMusicTimeOffset = 0;   // when paused, keep track of time passed since last IRQ
+
+#define RF_START_MUSIC			( 1 << 0 )
+#define RF_START_STREAM			( 1 << 1 )
+#define RF_UNPAUSE_MUSIC		( 1 << 2 )
+#define RF_UNPAUSE_STREAM		( 1 << 3 )
+
+volatile unsigned int gRequestFlags = 0;
+
+volatile unsigned int gTimeOfLastMusicUpdate = 0;
+volatile unsigned int gTimeOfLastStreamUpdate = 0;
+
+volatile unsigned int gMusicBufSide = _1st;
+volatile unsigned int gMusicTransSize = 0;
+volatile unsigned int gMusicLoadState = MUSIC_LOAD_STATE_IDLE;
+volatile unsigned int gMusicVolume;
+volatile unsigned int gMusicVolumeSet = 0;
+volatile unsigned int gMusicStop = 0;
+volatile unsigned int gMusicStatus = EzADPCM_STATUS_IDLE;
+volatile unsigned int gUpdateMusic = 0;
+volatile unsigned int gMusicSize;
+volatile unsigned int gMusicRemaining;
+
+enum{
+	STREAM_LOAD_STATE_IDLE,
+	STREAM_LOAD_STATE_PRELOADING,
+	STREAM_LOAD_STATE_WAITING_FOR_IRQ,
+	STREAM_LOAD_STATE_LOADING,
+};
+
+
+volatile unsigned int gStreamBufSide = _1st;
+volatile unsigned int gStreamTransSize = 0;
+volatile unsigned int gStreamLoadState = STREAM_LOAD_STATE_IDLE;
+volatile unsigned int gStreamVolume;
+volatile unsigned int gStreamVolumeSet = 0;
+volatile unsigned int gStreamStop = 0;
+volatile unsigned int gStreamStatus = EzADPCM_STATUS_IDLE;
+volatile unsigned int gUpdateStream = 0;
+volatile unsigned int gStreamSize;
+volatile unsigned int gStreamRemaining;
+
+int _AdpcmPlay( int status );
+
+#if SHARE_DMA_CH
+static int _AdpcmDmaIntCommon( int, void* );
+#else
+static int _AdpcmDmaIntMusic( int, void* );
+static int _AdpcmDmaIntStream( int, void* );
+#endif
+static int _AdpcmSpu2Int( int, void * );
+
+#define _L 0
+#define _R 1
+#define _Lch(x) ((x >> 16) & 0xffff)
+#define _Rch(x) (x & 0xffff)
+
+unsigned int GetTime( void )
+{
+	unsigned int sex, usex, msex;
+	struct SysClock sysClock;
+	GetSystemTime( &sysClock );
+	SysClock2USec( &sysClock, &sex, &usex );
+	msex = ( sex * 1000 ) + ( usex / 1000 );
+	return ( msex );
+}
+
+// EzADPCM_SDINIT
+void AdpcmSdInit( void )
+{
+    sceSdInit (0);
+
+    //	Disk media: DVD
+    //	Output format: PCM
+    //	Copy guard: normal (one generation recordable / default)
+    sceSdSetCoreAttr (SD_C_SPDIF_MODE, (SD_SPDIF_MEDIA_DVD |
+					SD_SPDIF_OUT_PCM   |
+					SD_SPDIF_COPY_NORMAL));
+    return;
+}
+
+void AdpcmSetUpVoice( int core, int vnum, int pSpuBuf )
+{
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_VOLL, 0 );
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_VOLR, 0 );
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_PITCH, DEFAULT_PITCH );
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_ADSR1, 0x000f );
+    sceSdSetParam ( core | ( vnum << 1 ) | SD_VP_ADSR2, 0x1fc0 );
+    sceSdSetAddr  ( core | ( vnum << 1 ) | SD_VA_SSA,  pSpuBuf );
+    return;
+}
+
+/*const char StrayVoiceData[ STRAY_VOICE_BLOCKER_SIZE ] =
+{
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x0C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};*/
+
+// EzADPCM_INIT
+int AdpcmInit( int pIopBuf )
+{
+	if ( gEECommand & PCMSTATUS_INITIALIZED )
+	{
+		return ( 0 );
+	}
+
+    if ( gSem == 0 )
+	{
+		struct SemaParam sem;
+		sem.initCount = 0;
+		sem.maxCount = 1;
+		sem.attr = AT_THFIFO;
+		gSem = CreateSema (&sem);
+    }
+    if (gThid == 0)
+	{
+		struct ThreadParam param;
+		param.attr         = TH_C;
+		param.entry        = _AdpcmPlay;
+		param.initPriority = 5;// BASE_priority-3;
+		param.stackSize    = 0x800; // was 800
+		param.option = 0;
+		gThid = CreateThread (¶m);
+		printf( "EzADPCM: create thread ID= %d\n", gThid );
+		StartThread (gThid, (u_long) NULL);
+    }
+	
+/*
+	// This data represents an empty looping sound...
+	// The voices wandering across SPU ram will get
+	// stuck here, so as not to trigger the interrupts
+	// reserved for the stream and music voices and shit.
+	strcpy( ( char * )pIopBuf, StrayVoiceData );
+	// send that to the SPU ram:
+	sceSdVoiceTrans( 0, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+		(unsigned char *) pIopBuf, STRAY_VOICE_BLOCKER_SPU_ADDR, STRAY_VOICE_BLOCKER_SIZE );
+	sceSdVoiceTransStatus( 0, SD_TRANS_STATUS_WAIT );*/
+		
+#if SHARE_DMA_CH
+	sceSdSetTransIntrHandler( TRANS_DMA_CH_COMMON, ( sceSdTransIntrHandler ) _AdpcmDmaIntCommon, ( void * ) &gSem );
+#else
+	sceSdSetTransIntrHandler( TRANS_DMA_CH_MUSIC, ( sceSdTransIntrHandler ) _AdpcmDmaIntMusic, ( void * ) &gSem );
+    sceSdSetTransIntrHandler( TRANS_DMA_CH_STREAM, ( sceSdTransIntrHandler ) _AdpcmDmaIntStream, ( void * ) &gSem );
+#endif
+    sceSdSetSpu2IntrHandler( ( sceSdSpu2IntrHandler ) _AdpcmSpu2Int, ( void * ) &gSem );
+
+	AdpcmSetUpVoice( MUSIC_CORE, MUSIC_L_VOICE, MUSIC_L_SPU_BUF_LOC );
+	AdpcmSetUpVoice( MUSIC_CORE, MUSIC_R_VOICE, MUSIC_R_SPU_BUF_LOC );
+	AdpcmSetUpVoice( STREAM_CORE, STREAM_VOICE, STREAM_SPU_BUF_LOC );
+
+	gpIopBuf = pIopBuf;
+	gEECommand |= PCMSTATUS_INITIALIZED;
+	//printf( "PCM Irx iop buf %d\n", gpIopBuf );
+	//Dbug_Printf( "PCM irx is initialized\n" );
+    return gThid;
+}
+
+// EzADPCM_QUIT
+void AdpcmQuit( void )
+{
+    DeleteThread (gThid);
+    gThid = 0;
+#if 0
+    DeleteSema (gSem);
+    gSem = 0;
+#endif
+    return;
+}
+
+// EzADPCM_PLAYMUSIC
+int AdpcmPlayMusic( int size )
+{
+    extern void AdpcmSetMusicVolumeDirect( unsigned int );
+
+    if ( gMusicStatus != EzADPCM_STATUS_IDLE )
+	{
+		ShowAction( "NOTE NOTE NOTE NOTE Can't play music -- music isn't in Idle state.\n" );
+		return -1;
+    }
+
+    AdpcmSetMusicVolumeDirect( gMusicVolume );
+				
+	gMusicStatus = EzADPCM_STATUS_PRELOAD;
+	gMusicLoadState = MUSIC_LOAD_STATE_IDLE;
+	gMusicSize = size;
+	gMusicTimeOffset = 0;
+	if ( gMusicSize < 64 )
+	{
+		gMusicSize = 64;
+	}
+	gEECommand &= ~( PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 );
+	gEECommand |= PCMSTATUS_MUSIC_PLAYING;
+
+	// stagger the two streams so there are no IRQ or DMA interrupt conflicts:
+	gRequestFlags |= RF_START_MUSIC;
+    return 0;
+}
+
+// EzADPCM_PLAYSTREAM
+int AdpcmPlayStream( int size )
+{
+    extern void AdpcmSetStreamVolumeDirect( unsigned int );
+
+    if ( gStreamStatus != EzADPCM_STATUS_IDLE )
+	{
+		printf( "trying to play stream when stream already playing!\n" );
+		return -1;
+    }
+
+    AdpcmSetStreamVolumeDirect( gStreamVolume );
+
+	gStreamStatus = EzADPCM_STATUS_PRELOAD;
+	gStreamSize = size;
+	if ( gStreamSize < 64 )
+	{
+		gStreamSize = 64;
+	}
+	gStreamLoadState = STREAM_LOAD_STATE_IDLE;
+	gEECommand &= ~( PCMSTATUS_NEED_STREAM_BUFFER_0 | PCMSTATUS_NEED_STREAM_BUFFER_1 );
+	gEECommand |= PCMSTATUS_STREAM_PLAYING;
+	gStreamTimeOffset = 0;
+	gRequestFlags = RF_START_STREAM;
+	ShowAction( "Requesting stream start\n" );
+    SmallShowAction( "-rsb" );
+	return 0;
+}
+
+/* internal */
+void _AdpcmSetMusicVoiceMute( void )
+{
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1) | SD_VP_VOLL, 0);
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1) | SD_VP_VOLR, 0);
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1) | SD_VP_VOLL, 0);
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1) | SD_VP_VOLR, 0);
+    return;
+}
+
+/* internal */
+void _AdpcmSetStreamVoiceMute( void )
+{
+    sceSdSetParam( STREAM_CORE | ( STREAM_VOICE << 1) | SD_VP_VOLL, 0);
+    sceSdSetParam( STREAM_CORE | ( STREAM_VOICE << 1) | SD_VP_VOLR, 0);
+    return;
+}
+
+void StopMusic( void )
+{
+	sceSdSetCoreAttr( MUSIC_CORE | SD_C_IRQ_ENABLE, 0 );
+	gMusicStatus = EzADPCM_STATUS_IDLE;
+	gEECommand &= ~( PCMSTATUS_MUSIC_PLAYING | PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 );
+	_AdpcmSetMusicVoiceMute( );
+	sceSdSetSwitch( MUSIC_CORE | SD_S_KOFF, ( 1 << MUSIC_L_VOICE ) );
+	sceSdSetSwitch( MUSIC_CORE | SD_S_KOFF, ( 1 << MUSIC_R_VOICE ) );
+	ShowAction( "Stopping music\n" );
+    SmallShowAction( "-!sm!" );
+}
+
+void StopStream( void )
+{
+	sceSdSetCoreAttr( STREAM_CORE | SD_C_IRQ_ENABLE, 0 );
+	gStreamStatus = EzADPCM_STATUS_IDLE;
+	gEECommand &= ~( PCMSTATUS_STREAM_PLAYING | PCMSTATUS_NEED_STREAM_BUFFER_0 | PCMSTATUS_NEED_STREAM_BUFFER_1 );
+	_AdpcmSetStreamVoiceMute( );
+	sceSdSetSwitch( STREAM_CORE | SD_S_KOFF, ( 1 << STREAM_VOICE ) );
+	ShowAction( "Stopping stream\n" );
+    SmallShowAction( "-!ss!" );
+}
+
+#define NUM_WAIT_CLICKS 25000
+
+// EzADPCM_STOPMUSIC
+int AdpcmStopMusic( void )
+{
+	int i;
+	int j = 0;
+
+	if ( gMusicStatus != EzADPCM_STATUS_IDLE )
+	{
+		if ( gRequestFlags & RF_START_MUSIC )
+		{
+			gRequestFlags &= ~RF_START_MUSIC;
+			gMusicStatus = EzADPCM_STATUS_IDLE;
+			ShowAction( "Supressing music request\n" );
+			SmallShowAction( "-smr" );
+		}
+		else if ( gMusicPaused )
+		{
+			// if loading is happening, wait then just stop:
+			ShowStop( "stop music %d", GetTime( ) );
+			while ( gMusicLoadState != MUSIC_LOAD_STATE_WAITING_FOR_IRQ )
+			{
+				for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+					;
+				j++;
+				if ( j > NUM_WAIT_CLICKS )
+				{
+					j = 0;
+					ShowStop( "." );
+				}
+			}
+//			ShowStop( "\n" );
+			ShowAction( "Stopped paused music\n" );
+			SmallShowAction( "-spm" );
+			StopMusic( );
+		}
+		else
+		{
+			ShowStop( " %d-stopmusic", GetTime( ) );
+			gMusicStop++;
+			while ( gMusicStatus != EzADPCM_STATUS_IDLE )
+			{
+				for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+					;
+				if ( j++ > NUM_WAIT_CLICKS )
+				{
+					j = 0;
+					ShowStop( "." );
+					//printf( " ms %d mls %d rf %d eecom %d\n", gMusicStatus, gMusicLoadState, gRequestFlags, gEECommand );
+				}
+			}
+			//ShowStop( "\n" );
+		}
+		gEECommand &= ~ ( PCMSTATUS_MUSIC_PLAYING | PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 );
+		return ( 1 );
+    }
+	return ( 0 );
+}
+
+// EzADPCM_STOPSTREAM
+int AdpcmStopStream( void )
+{
+	int i;
+	int j = 0;
+
+	if ( gStreamStatus != EzADPCM_STATUS_IDLE )
+	{
+		if ( gRequestFlags & RF_START_STREAM )
+		{
+			gRequestFlags &= ~RF_START_STREAM;
+			gStreamStatus = EzADPCM_STATUS_IDLE;
+			ShowAction( "Supressing stream request\n" );
+			SmallShowAction( "-ssr" );
+		}
+		else if ( gStreamPaused )
+		{
+			// if loading is happening, wait then just stop:
+			ShowStop( " %d-Stop p stream", GetTime( ) );
+			while ( gStreamLoadState != STREAM_LOAD_STATE_WAITING_FOR_IRQ )
+			{
+				for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+					;
+				ShowStop( "." );
+			}
+			//ShowStop( "\n" );
+			StopStream( );
+		}
+		else
+		{
+			ShowStop( " %d-Stop stream", GetTime( ) );
+			gStreamStop++;
+			while ( gStreamStatus != EzADPCM_STATUS_IDLE )
+			{
+				for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+					;
+				if ( j++ > NUM_WAIT_CLICKS )
+				{
+					ShowStop( "." );
+//					printf( " ss %x sls %d rf %d eecom %d\n", gStreamStatus, gStreamLoadState, gRequestFlags, gEECommand );
+					j = 0;
+				}
+			}
+			//ShowStop( "\n" );
+			SmallShowAction( "-ss" );
+			ShowAction( "Stopped stream\n" );
+		}
+		gEECommand &= ~ ( PCMSTATUS_STREAM_PLAYING | PCMSTATUS_NEED_STREAM_BUFFER_0 | PCMSTATUS_NEED_STREAM_BUFFER_1 );
+		return ( 1 );
+    }
+	return ( 0 );
+}
+
+// EzADPCM_SETMUSICVOL
+void AdpcmSetMusicVolume( unsigned int vol )
+{
+    gMusicVolumeSet = 1;
+    gMusicVolume = vol;
+    return;
+}
+
+// EzADPCM_SETMUSICVOLDIRECT
+void AdpcmSetMusicVolumeDirect( unsigned int vol )
+{
+    gMusicVolume = vol;
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_VOLL, vol );
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_VOLR, 0 );
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_VOLL, 0 );
+    sceSdSetParam ( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_VOLR, vol );
+    return;
+}
+
+// EzADPCM_SETSTREAMVOL
+void AdpcmSetStreamVolume( unsigned int vol )
+{
+    gStreamVolumeSet = 1;
+    gStreamVolume = vol;
+    return;
+}
+
+// EzADPCM_SETSTREAMVOLDIRECT
+void AdpcmSetStreamVolumeDirect( unsigned int vol )
+{
+    gStreamVolume = vol;
+    sceSdSetParam ( STREAM_CORE | ( STREAM_VOICE << 1 ) | SD_VP_VOLL, _Lch( vol ) );
+    sceSdSetParam ( STREAM_CORE | ( STREAM_VOICE << 1 ) | SD_VP_VOLR, _Rch( vol ) );
+    return;
+}
+
+// Shouldn't need these unless debugging or something --
+// Instead just get gEECommand each frame and act accordingly.
+
+// EzADPCM_GETMUSICSTATUS
+unsigned int AdpcmGetMusicStatus( void )
+{
+	return gMusicStatus;
+}
+
+// EzADPCM_GETSTREAMSTATUS
+unsigned int AdpcmGetStreamStatus( void )
+{
+    return gStreamStatus;
+}
+
+// actual time 300 ms across one buffer (600ms across double buffer)
+#define SINGLE_BUFFER_MS	300
+#define MIN_INTERRUPT_SEPARATION_MS		90
+#define MAX_MS_BETWEEN_STREAMS			( SINGLE_BUFFER_MS - MIN_INTERRUPT_SEPARATION_MS )
+#define MIN_MS_BETWEEN_STREAMS			( MIN_INTERRUPT_SEPARATION_MS )
+
+int SafeForStreamToGo( unsigned int time )
+{
+	if ( gMusicStatus == EzADPCM_STATUS_IDLE )
+	{
+		SmallShowState( "[ss]" );
+		return ( 1 );
+	}
+	else
+	{
+		time -= gStreamTimeOffset;
+		if ( gMusicPaused )
+		{
+			if ( time > gMusicPaused + MIN_MS_BETWEEN_STREAMS )
+			{
+				SmallShowState( "[ss0]" );
+				return ( 1 );
+			}
+		}
+		else
+		{
+			int musicMod;
+			int streamMod;
+			musicMod = gTimeOfLastMusicUpdate % SINGLE_BUFFER_MS;
+			streamMod = time % SINGLE_BUFFER_MS;
+			if ( musicMod > streamMod )
+			{
+				if ( ( musicMod - streamMod > MIN_MS_BETWEEN_STREAMS ) &&
+					( ( ( streamMod + SINGLE_BUFFER_MS ) - musicMod ) > MIN_MS_BETWEEN_STREAMS ) )
+				{
+					SmallShowState( "[ss1]" );
+					return ( 1 );
+				}
+			}
+			else if ( ( streamMod - musicMod > MIN_MS_BETWEEN_STREAMS ) &&
+					( ( ( musicMod + SINGLE_BUFFER_MS ) - streamMod ) > MIN_MS_BETWEEN_STREAMS ) )
+			{
+				SmallShowState( "[ss2]" );
+				return ( 1 );
+			}
+		}
+/*		
+		else if ( ( time > gTimeOfLastMusicUpdate + MIN_MS_BETWEEN_STREAMS ) &&
+			 ( time < gTimeOfLastMusicUpdate + MAX_MS_BETWEEN_STREAMS ) )
+		{
+			SmallShowState( "[ss1]" );
+			return ( 1 );
+		}
+		else
+		{
+			unsigned int predictedTimeOfInterrupt;
+			predictedTimeOfInterrupt = ( time + SINGLE_BUFFER_MS );
+			if ( ( predictedTimeOfInterrupt > ( gTimeOfLastMusicUpdate + MIN_MS_BETWEEN_STREAMS ) )
+				&& ( predictedTimeOfInterrupt < ( gTimeOfLastMusicUpdate + MAX_MS_BETWEEN_STREAMS ) ) )
+			{
+				SmallShowState( "[ss2]" );
+				return ( 1 );
+			}
+			else
+			{
+				//printf( " <>gsto %d<>", gStreamTimeOffset );
+				SmallShowState( "[nss]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+			}
+		}*/
+	}
+	SmallShowState( "[nss]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+	return ( 0 );
+}
+
+int SafeForMusicToGo( unsigned int time )
+{
+	if ( gStreamStatus == EzADPCM_STATUS_IDLE )
+	{
+		SmallShowState( "[ms]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+		return ( 1 );
+	}
+	else
+	{
+		time -= gMusicTimeOffset;
+		if ( gStreamPaused )
+		{
+			if ( time > gStreamPaused + MIN_MS_BETWEEN_STREAMS )
+			{
+				SmallShowState( "[ms0]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+				return ( 1 );
+			}
+		}
+		else
+		{
+			int streamMod;
+			int musicMod;
+			streamMod = gTimeOfLastStreamUpdate % SINGLE_BUFFER_MS;
+			musicMod = time % SINGLE_BUFFER_MS;
+			if ( musicMod > streamMod )
+			{
+				if ( ( musicMod - streamMod > MIN_MS_BETWEEN_STREAMS ) &&
+					( ( ( streamMod + SINGLE_BUFFER_MS ) - musicMod ) > MIN_MS_BETWEEN_STREAMS ) )
+				{
+					SmallShowState( "[ms1]" );
+					return ( 1 );
+				}
+			}
+			else if ( ( streamMod - musicMod > MIN_MS_BETWEEN_STREAMS ) &&
+					( ( ( musicMod + SINGLE_BUFFER_MS ) - streamMod ) > MIN_MS_BETWEEN_STREAMS ) )
+			{
+				SmallShowState( "[ms2]" );
+				return ( 1 );
+			}
+		}
+/*		
+        else if ( ( time > gTimeOfLastStreamUpdate + MIN_MS_BETWEEN_STREAMS ) &&
+			 ( time < gTimeOfLastStreamUpdate + MAX_MS_BETWEEN_STREAMS ) )
+		{
+			SmallShowState( "[ms1]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+			return ( 1 );
+		}
+		else
+		{
+			unsigned int predictedTimeOfInterrupt;
+			predictedTimeOfInterrupt = ( time + SINGLE_BUFFER_MS );
+			if ( ( predictedTimeOfInterrupt > ( gTimeOfLastStreamUpdate + MIN_MS_BETWEEN_STREAMS ) )
+				&& ( predictedTimeOfInterrupt < ( gTimeOfLastStreamUpdate + MAX_MS_BETWEEN_STREAMS ) ) )
+			{
+				SmallShowState( "[ms2]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+				return ( 1 );
+			}
+			else
+			{
+				SmallShowState( "[nsm]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+			}
+		}*/
+		
+	}
+	SmallShowState( "[nsm]" ); //fe ss %x sls %d eecom %d\n", gStreamStatus, gStreamLoadState, gEECommand );
+	return ( 0 );
+}
+
+// EzADPCM_GETSTATUS
+unsigned int AdpcmGetStatus( void )
+{
+	unsigned int temp;
+	unsigned int time;
+	
+	if ( gRequestFlags & ( RF_START_MUSIC | RF_START_STREAM | RF_UNPAUSE_MUSIC | RF_UNPAUSE_STREAM ) )
+	{
+		time = GetTime( );
+		if ( ( gRequestFlags & RF_START_STREAM ) && ( SafeForStreamToGo( time ) ) )
+		{
+			gUpdateStream = 1;
+			gTimeOfLastStreamUpdate = time;
+			ShowAction( "Starting stream\n" );
+			SmallShowAction( "-sb" );
+			SignalSema( gSem );
+			gRequestFlags &= ~RF_START_STREAM;
+		}
+		else if ( ( gRequestFlags & RF_START_MUSIC ) && ( SafeForMusicToGo( time ) ) )
+		{
+			gUpdateMusic = 1;
+			gTimeOfLastStreamUpdate = time;
+			ShowAction( "Starting music\n" );
+			SmallShowAction( "-mb" );
+			SignalSema( gSem );
+			gRequestFlags &= ~RF_START_MUSIC;
+		}
+		else  // unpause one or both of these motherfuckers:
+		{
+			if ( ( gRequestFlags & RF_UNPAUSE_STREAM ) && ( SafeForStreamToGo( time ) )	)
+			{
+				sceSdSetParam( STREAM_CORE | ( STREAM_VOICE << 1 ) | SD_VP_PITCH, DEFAULT_PITCH );
+				gStreamPaused = 0;
+				gTimeOfLastStreamUpdate = time - gStreamTimeOffset;
+				gStreamTimeOffset = 0;
+				gRequestFlags &= ~RF_UNPAUSE_STREAM;
+				ShowAction( "Unpausing stream\n" );
+				SmallShowAction( "-ups" );
+			}
+			if ( ( gRequestFlags & RF_UNPAUSE_MUSIC ) && ( SafeForMusicToGo( time ) ) )
+			{
+				sceSdSetParam( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_PITCH, DEFAULT_PITCH );
+				sceSdSetParam( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_PITCH, DEFAULT_PITCH );
+				gMusicPaused = 0;
+				gTimeOfLastMusicUpdate = time - gMusicTimeOffset;
+				gMusicTimeOffset = 0;
+				gRequestFlags &= ~RF_UNPAUSE_MUSIC;
+				ShowAction( "Unpausing music\n" );
+				SmallShowAction( "-upm" );
+			}
+		}
+	}
+	
+	temp = gEECommand;
+	gEECommand &= ~( PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 | 
+					PCMSTATUS_NEED_STREAM_BUFFER_0 | PCMSTATUS_NEED_STREAM_BUFFER_1 );
+#if SHOW_SMALL_COMMANDS
+	if ( temp & ( PCMSTATUS_NEED_MUSIC_BUFFER_0 | PCMSTATUS_NEED_MUSIC_BUFFER_1 | 
+					PCMSTATUS_NEED_STREAM_BUFFER_0 | PCMSTATUS_NEED_STREAM_BUFFER_1 ) )
+	{
+		ShowSmallCommand( " <_L_>" );
+	}
+#endif
+	return temp;
+}
+
+// ================================================================
+
+#define _ADPCM_MARK_START 0x04
+#define _ADPCM_MARK_LOOP  0x02
+#define _ADPCM_MARK_END   0x01
+
+#define _AdpcmSetMarkSTART(a,s) { \
+  *((unsigned char *)((a)+1)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_START); \
+  *((unsigned char *)((a)+0x10+1)) = \
+	_ADPCM_MARK_LOOP; \
+  *((unsigned char *)((a)+(s)-0x0f)) = \
+	_ADPCM_MARK_LOOP; \
+	FlushDcache (); }
+#define _AdpcmSetMarkEND(a,s) { \
+  *((unsigned char *)((a)+1)) = \
+	 _ADPCM_MARK_LOOP; \
+  *((unsigned char *)((a)+0x10+1)) = \
+	_ADPCM_MARK_LOOP; \
+  *((unsigned char *)((a)+(s)-0x0f)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_END); \
+	FlushDcache (); }
+
+#define _AdpcmSetMarkFINAL(a,s) { \
+  *((unsigned char *)((a)+(s)-0x0f)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_START | _ADPCM_MARK_END); \
+	FlushDcache (); }
+
+#define _AdpcmSetMarkSTARTpre(a,s) { \
+  *((unsigned char *)((a)+1)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_START); \
+  *((unsigned char *)((a)+0x10+1)) = \
+	_ADPCM_MARK_LOOP; \
+	FlushDcache (); }
+#define _AdpcmSetMarkENDpre(a,s) { \
+  *((unsigned char *)((a)+(s)-0x0f)) = \
+	(_ADPCM_MARK_LOOP | _ADPCM_MARK_END); \
+	FlushDcache (); }
+
+#if SHARE_DMA_CH
+
+/* internal */
+static int _AdpcmDmaIntCommon(int ch, void *common) // DMA Interrupt -- when transfering data to SPU2 from IOP
+{
+	if ( gLoadStatus & LOAD_STATUS_LOADING_MUSIC )
+	{
+		if ( gMusicStatus != EzADPCM_STATUS_IDLE )
+		{
+			gUpdateMusic++;
+			iSignalSema (* (int *) common);
+			return 1;
+		}
+	}
+	else if ( gLoadStatus & LOAD_STATUS_LOADING_STREAM )
+	{
+		if ( gStreamStatus != EzADPCM_STATUS_IDLE )
+		{
+			gUpdateStream++;
+			iSignalSema (* (int *) common);
+			return 1;
+		}
+	}
+	return 0;
+}
+
+#else
+
+/* internal */
+static int _AdpcmDmaIntMusic(int ch, void *common)	// DMA Interrupt -- when transfering data to SPU2 from IOP
+{
+	if ( gMusicStatus != EzADPCM_STATUS_IDLE )
+	{
+		gUpdateMusic++;
+		iSignalSema (* (int *) common);
+		return 1;
+	}
+	return 0;
+}
+
+/* internal */
+static int _AdpcmDmaIntStream(int ch, void *common)	// DMA Interrupt -- when transfering data to SPU2 from IOP
+{
+	if ( gStreamStatus != EzADPCM_STATUS_IDLE )
+	{
+		gUpdateStream++;
+		iSignalSema (* (int *) common);
+		return 1;
+	}
+	return 0;
+}
+
+#endif
+
+/* internal */
+static int _AdpcmSpu2Int( int core, void *common ) // SPU2 Voice Interrupt
+{
+	if ( core == ( 1 << MUSIC_CORE ) )
+	{
+		if ( gMusicLoadState != MUSIC_LOAD_STATE_WAITING_FOR_IRQ )
+		{
+			int shit;
+			for ( shit = 0; shit < 666; shit++ )
+			{
+				printf( "shit\n" );
+				return 0;
+			}
+		}
+		gUpdateMusic++;
+	}
+	else
+	{
+		if ( gStreamLoadState != STREAM_LOAD_STATE_WAITING_FOR_IRQ )
+		{
+			int shit;
+			for ( shit = 0; shit < 666; shit++ )
+			{
+				printf( "shit\n" );
+				return 0;
+			}
+		}
+		gUpdateStream++;
+	}
+	iSignalSema (* (int *) common);
+    return 1;
+}
+
+#define LAST_VAG_POS 15
+
+//prototype:
+void UpdateStream( void );
+
+void UpdateMusic( void )
+{
+	switch ( gMusicStatus )
+	{
+		case EzADPCM_STATUS_IDLE:
+			break;
+
+		case EzADPCM_STATUS_PRELOAD:
+			switch ( gMusicLoadState )
+			{
+				case ( MUSIC_LOAD_STATE_IDLE ):
+				{
+#if SHARE_DMA_CH					
+					if ( gLoadStatus & LOAD_STATUS_LOADING_STREAM )
+					{
+						gLoadStatus |= LOAD_STATUS_NEED_MUSIC;
+						return;
+					}
+					gLoadStatus |= LOAD_STATUS_LOADING_MUSIC;
+#endif
+					ShowState( "music preload l\n" );
+					SmallShowState( "-MPL" );
+					gMusicBufSide = _1st;
+					gMusicIopOffset = 0;
+					gMusicRemaining = gMusicSize;
+					gMusicRemaining -= SB_BUF_SIZE;
+					gMusicTransSize = SB_BUF_SIZE;
+					if ( gMusicRemaining < 0 )
+					{
+						gMusicTransSize += gMusicRemaining;
+					}
+
+#if DATA_INTEGRITY_CHECK
+					{
+						int gronk;
+						for ( gronk = 0; gronk < gMusicTransSize >> 4; gronk++ )
+						{
+							if ( *( ( char * )( 1 + gpIopBuf + MUSIC_L_IOP_OFFSET + ( gronk * 16 ) ) ) != 0 )
+							{
+								printf( "Fucked up data!!!!" );
+							}
+						}
+					}
+#endif
+					
+					// preload left:
+					_AdpcmSetMarkSTARTpre( gpIopBuf + MUSIC_L_IOP_OFFSET, gMusicTransSize );
+					if ( gMusicRemaining > 0 )
+					{
+						_AdpcmSetMarkENDpre( gpIopBuf + MUSIC_L_IOP_OFFSET, gMusicTransSize );
+					}
+					else
+					{
+						_AdpcmSetMarkFINAL( gpIopBuf + MUSIC_L_IOP_OFFSET, gMusicTransSize );
+					}
+					sceSdVoiceTrans( TRANS_DMA_CH_MUSIC, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+						(unsigned char *) ( gpIopBuf + MUSIC_L_IOP_OFFSET ),
+						MUSIC_L_SPU_BUF_LOC, gMusicTransSize );
+					gMusicLoadState++;
+					break;
+				}
+				case ( MUSIC_LOAD_STATE_PRELOADING_L ):
+				{
+					ShowState( "music preload r\n" );
+					SmallShowState( "-MPR" );
+					// preload right:
+#if DATA_INTEGRITY_CHECK
+					{
+						int gronk;
+						for ( gronk = 0; gronk < gMusicTransSize >> 4; gronk++ )
+						{
+							if ( *( ( char * )( 1 + gpIopBuf + MUSIC_R_IOP_OFFSET + ( gronk * 16 ) ) ) != 0 )
+							{
+								printf( "Fucked up data!!!!" );
+							}
+						}
+					}
+#endif
+					_AdpcmSetMarkSTARTpre( gpIopBuf + MUSIC_R_IOP_OFFSET, gMusicTransSize );
+					if ( gMusicRemaining > 0 )
+					{
+						_AdpcmSetMarkENDpre( gpIopBuf + MUSIC_R_IOP_OFFSET, gMusicTransSize );
+					}
+					else
+					{
+						_AdpcmSetMarkFINAL( gpIopBuf + MUSIC_R_IOP_OFFSET, gMusicTransSize );
+					}
+					sceSdVoiceTrans( TRANS_DMA_CH_MUSIC, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+						(unsigned char *) ( gpIopBuf + MUSIC_R_IOP_OFFSET ),
+						MUSIC_R_SPU_BUF_LOC, gMusicTransSize );
+					gMusicLoadState++;
+					break;
+				}	
+				case ( MUSIC_LOAD_STATE_PRELOADING_R ):
+				{
+					ShowState( "Music starting -- waiting for IRQ\n" );
+					SmallShowState( " %d-MSIRQ", GetTime( ) );
+					gMusicIopOffset = SB_BUF_SIZE;
+					if ( gMusicRemaining <= 0 )
+					{
+						sceSdSetAddr( MUSIC_CORE | SD_A_IRQA, MUSIC_L_SPU_BUF_LOC + gMusicTransSize - LAST_VAG_POS );
+						gMusicStatus = EzADPCM_STATUS_TERMINATE;
+					}
+					else
+					{
+						sceSdSetAddr( MUSIC_CORE | SD_A_IRQA, MUSIC_L_SPU_BUF_LOC + SB_BUF_HALF );
+						gMusicStatus = EzADPCM_STATUS_RUNNING;
+					}
+					AdpcmSetMusicVolumeDirect( gMusicVolume );
+					sceSdSetCoreAttr( MUSIC_CORE | SD_C_IRQ_ENABLE, 1 );
+					sceSdSetSwitch( MUSIC_CORE | SD_S_KON, ( ( 1 << MUSIC_R_VOICE ) | ( 1 << MUSIC_L_VOICE ) ) );
+					gMusicLoadState++;
+					gTimeOfLastMusicUpdate = GetTime( );
+//					printf( "music wait IRQ...\n" );
+#if SHARE_DMA_CH					
+					gLoadStatus &= ~LOAD_STATUS_LOADING_MUSIC;
+					if ( gLoadStatus & LOAD_STATUS_NEED_STREAM )
+					{
+						gLoadStatus &= ~LOAD_STATUS_NEED_STREAM;
+						UpdateStream( );
+					}
+#endif
+					break;
+				}
+			}
+			break;
+
+		case EzADPCM_STATUS_RUNNING:
+			switch ( gMusicLoadState )
+			{
+				case ( MUSIC_LOAD_STATE_WAITING_FOR_IRQ ):
+				{
+#if SHARE_DMA_CH					
+					if ( gLoadStatus & LOAD_STATUS_LOADING_STREAM )
+					{
+						gLoadStatus |= LOAD_STATUS_NEED_MUSIC;
+						return;
+					}
+					gLoadStatus |= LOAD_STATUS_LOADING_MUSIC;
+#endif					
+					ShowState( "Music Running -- Loading L\n" );
+					SmallShowState( "-MRLL" );
+					sceSdSetCoreAttr ( MUSIC_CORE | SD_C_IRQ_ENABLE, 0);
+					gMusicTransSize = SB_BUF_HALF;
+					gMusicRemaining -= SB_BUF_HALF;
+					if ( gMusicRemaining < 0 )
+					{
+						gMusicTransSize += gMusicRemaining;
+					}
+#if DATA_INTEGRITY_CHECK
+					{
+						int gronk;
+						for ( gronk = 0; gronk < gMusicTransSize >> 4; gronk++ )
+						{
+							if ( *( ( char * )( 1 + gpIopBuf + MUSIC_L_IOP_OFFSET + gMusicIopOffset + ( gronk * 16 ) ) ) != 0 )
+							{
+								printf( "Fucked up data!!!!" );
+							}
+						}
+					}
+#endif
+					
+					// load in left side:
+					if ( gMusicBufSide == _1st )
+					{
+						_AdpcmSetMarkSTART( gpIopBuf + MUSIC_L_IOP_OFFSET + gMusicIopOffset, gMusicTransSize );
+					}
+					if ( gMusicRemaining <= 0 )
+					{
+						_AdpcmSetMarkFINAL( gpIopBuf + MUSIC_L_IOP_OFFSET + gMusicIopOffset, gMusicTransSize );
+					}
+					else if ( gMusicBufSide == _2nd )
+					{
+						_AdpcmSetMarkEND( gpIopBuf + MUSIC_L_IOP_OFFSET + gMusicIopOffset, gMusicTransSize );
+					}
+					
+					sceSdVoiceTrans( TRANS_DMA_CH_MUSIC, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+						(unsigned char *) gpIopBuf + MUSIC_L_IOP_OFFSET + gMusicIopOffset,
+						( MUSIC_L_SPU_BUF_LOC + SB_BUF_HALF * gMusicBufSide ), gMusicTransSize );
+					gMusicLoadState++;
+					break;
+				}
+				case ( MUSIC_LOAD_STATE_LOADING_L ):
+				{
+					ShowState( "Music Running -- Loading R\n" );
+					SmallShowState( "-MRLR" );
+#if DATA_INTEGRITY_CHECK
+					{
+						int gronk;
+						for ( gronk = 0; gronk < gMusicTransSize >> 4; gronk++ )
+						{
+							if ( *( ( char * )( 1 + gpIopBuf + MUSIC_R_IOP_OFFSET + gMusicIopOffset + ( gronk * 16 ) ) ) != 0 )
+							{
+								printf( "Fucked up data!!!!" );
+							}
+						}
+					}
+#endif
+					
+					// load in right side:
+					if ( gMusicBufSide == _1st )
+					{
+						_AdpcmSetMarkSTART( gpIopBuf + MUSIC_R_IOP_OFFSET + gMusicIopOffset, gMusicTransSize );
+					}
+					if ( gMusicRemaining <= 0 )
+					{
+						_AdpcmSetMarkFINAL( gpIopBuf + MUSIC_R_IOP_OFFSET + gMusicIopOffset, gMusicTransSize );
+					}
+					else if ( gMusicBufSide == _2nd )
+					{
+						_AdpcmSetMarkEND( gpIopBuf + MUSIC_R_IOP_OFFSET + gMusicIopOffset, gMusicTransSize );
+					}
+					sceSdVoiceTrans( TRANS_DMA_CH_MUSIC, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+						(unsigned char *) gpIopBuf + MUSIC_R_IOP_OFFSET + gMusicIopOffset,
+						( MUSIC_R_SPU_BUF_LOC + SB_BUF_HALF * gMusicBufSide ), gMusicTransSize );
+					gMusicLoadState++;
+					break;
+				}
+				case ( MUSIC_LOAD_STATE_LOADING_R ):
+				{
+					ShowState( "Music Running -- Waiting for IRQ\n" );
+					SmallShowState( " %d-MRIRQ", GetTime( ) );
+					// reset the interrupt:
+					if ( gMusicRemaining <= 0 )
+					{
+						sceSdSetAddr( MUSIC_CORE | SD_A_IRQA, gMusicTransSize - LAST_VAG_POS + MUSIC_L_SPU_BUF_LOC + SB_BUF_HALF * gMusicBufSide );
+					}
+					else
+					{
+						sceSdSetAddr( MUSIC_CORE | SD_A_IRQA, MUSIC_L_SPU_BUF_LOC + SB_BUF_HALF * gMusicBufSide );
+					}
+					gTimeOfLastMusicUpdate = GetTime( );
+					gMusicIopOffset += SB_BUF_HALF;
+					gMusicBufSide = ( gMusicBufSide == _1st ) ? _2nd : _1st;
+					
+					if ( gMusicRemaining <= 0 )
+					{
+						// the vag data up to the end should contain all zeros,
+						// so it doesn't matter that the interrupt won't happen until the end.
+						// stop the music when it gets to the end:
+						gMusicStop++;
+					}
+					else
+					{
+						if ( gMusicIopOffset == HALF_BUFFER_SIZE )
+						{
+							//Dbug_Printf( "music move over\n" );
+							gMusicIopOffset += HALF_BUFFER_SIZE; // move over by half the size since L/R load in together...
+							if ( gMusicRemaining > HALF_BUFFER_SIZE )
+							{
+								gEECommand |= PCMSTATUS_NEED_MUSIC_BUFFER_0;
+							}
+						}
+						else if ( gMusicIopOffset == ( SINGLE_STREAM_BUFFER_SIZE + HALF_BUFFER_SIZE ) )
+						{
+							//Dbug_Printf( "music move back\n" );
+							gMusicIopOffset = 0;
+							if ( gMusicRemaining > HALF_BUFFER_SIZE )
+							{
+								gEECommand |= PCMSTATUS_NEED_MUSIC_BUFFER_1;
+							}
+						}
+					}
+					if ( gMusicStop )
+					{
+						gMusicStop = 0;
+						gMusicStatus = EzADPCM_STATUS_TERMINATE;
+					}
+					sceSdSetCoreAttr( MUSIC_CORE | SD_C_IRQ_ENABLE, 1);
+					gMusicLoadState = MUSIC_LOAD_STATE_WAITING_FOR_IRQ;
+//					printf( "music wait IRQ\n" );
+#if SHARE_DMA_CH
+					gLoadStatus &= ~LOAD_STATUS_LOADING_MUSIC;
+					if ( gLoadStatus & LOAD_STATUS_NEED_STREAM )
+					{
+						gLoadStatus &= ~LOAD_STATUS_NEED_STREAM;
+						UpdateStream( );
+					}
+#endif
+					break;
+				}
+				default:
+					printf( "Unknown music loading state %d\n", gMusicLoadState );
+					break;
+			}
+			break;
+
+		case EzADPCM_STATUS_TERMINATE: 
+			ShowState( "Music terminate\n" );
+			SmallShowState( "-MT!!!" );
+			StopMusic( );
+			break;
+
+		default:
+			printf( "unknown music status in pcm irx!!!\n" );
+			break;
+	}
+}
+
+void UpdateStream( void )
+{
+//	printf( "hmmm %d %d bufside %d left %d\n", gStreamStatus, gStreamLoadState, gStreamBufSide, gStreamRemaining );
+	switch ( gStreamStatus )
+	{
+		case EzADPCM_STATUS_IDLE:
+			break;
+
+		case EzADPCM_STATUS_PRELOAD:
+			switch ( gStreamLoadState )
+			{
+				case ( STREAM_LOAD_STATE_IDLE ):
+				{
+#if SHARE_DMA_CH					
+					if ( gLoadStatus & LOAD_STATUS_LOADING_MUSIC )
+					{
+						gLoadStatus |= LOAD_STATUS_NEED_STREAM;
+						return;
+					}
+					gLoadStatus |= LOAD_STATUS_LOADING_STREAM;
+#endif					
+					ShowState( "stream preload l\n" );
+					SmallShowState( "-SPL" );
+					gStreamBufSide = _1st;
+					gStreamIopOffset = 0;
+					gStreamRemaining = gStreamSize;
+					gStreamRemaining -= SB_BUF_SIZE;
+					gStreamTransSize = SB_BUF_SIZE;
+					if ( gStreamRemaining < 0 )
+					{
+						gStreamTransSize += gStreamRemaining;
+					}
+#if DATA_INTEGRITY_CHECK
+					{
+						int gronk;
+						for ( gronk = 0; gronk < gStreamTransSize >> 4; gronk++ )
+						{
+							if ( *( ( char * )( 1 + gpIopBuf + STREAM_IOP_OFFSET + ( gronk * 16 ) ) ) != 0 )
+							{
+								printf( "Fucked up data!!!!" );
+							}
+						}
+					}
+#endif
+					// preload left:
+					_AdpcmSetMarkSTARTpre( gpIopBuf + STREAM_IOP_OFFSET, gStreamTransSize );
+					if ( gStreamRemaining > 0 )
+					{
+						_AdpcmSetMarkENDpre( gpIopBuf + STREAM_IOP_OFFSET, gStreamTransSize );
+					}
+					else
+					{
+						_AdpcmSetMarkFINAL( gpIopBuf + STREAM_IOP_OFFSET, gStreamTransSize );
+					}
+					sceSdVoiceTrans( TRANS_DMA_CH_STREAM, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+						(unsigned char *) ( gpIopBuf + STREAM_IOP_OFFSET ),
+						STREAM_SPU_BUF_LOC, gStreamTransSize );
+					gStreamLoadState++;
+					break;
+				}
+				case ( STREAM_LOAD_STATE_PRELOADING ):
+				{
+					ShowState( "Stream starting -- waiting for IRQ\n" );
+					SmallShowState( " %d-SSIRQ", GetTime( ) );
+					gStreamIopOffset = SB_BUF_SIZE;
+					if ( gStreamRemaining <= 0 )
+					{
+						sceSdSetAddr( STREAM_CORE | SD_A_IRQA, STREAM_SPU_BUF_LOC + gStreamTransSize - LAST_VAG_POS );
+						//printf( "tiny stream!!! addr %x transsize %x remaining %x", STREAM_SPU_BUF_LOC + gStreamTransSize - LAST_VAG_POS, gStreamTransSize, gStreamRemaining );
+						gStreamStatus = EzADPCM_STATUS_TERMINATE;
+					}
+					else
+					{
+						sceSdSetAddr( STREAM_CORE | SD_A_IRQA, STREAM_SPU_BUF_LOC + SB_BUF_HALF );
+						gStreamStatus = EzADPCM_STATUS_RUNNING;
+					}
+					AdpcmSetStreamVolumeDirect( gStreamVolume );
+					sceSdSetCoreAttr( STREAM_CORE | SD_C_IRQ_ENABLE, 1 );
+					sceSdSetSwitch( STREAM_CORE | SD_S_KON, 1 << STREAM_VOICE );
+					gStreamLoadState++;
+					gTimeOfLastStreamUpdate = GetTime( );
+#if SHARE_DMA_CH
+					gLoadStatus &= ~LOAD_STATUS_LOADING_STREAM;
+					if ( gLoadStatus & LOAD_STATUS_NEED_MUSIC )
+					{
+						gLoadStatus &= ~LOAD_STATUS_NEED_MUSIC;
+						UpdateMusic( );
+					}
+#endif
+					break;
+				}
+			}
+			break;
+
+		case EzADPCM_STATUS_RUNNING:
+			switch ( gStreamLoadState )
+			{
+				case ( STREAM_LOAD_STATE_WAITING_FOR_IRQ ):
+				{
+					ShowState( "Stream Running -- Loading\n" );
+					SmallShowState( "-SRL" );
+#if SHARE_DMA_CH					
+					if ( gLoadStatus & LOAD_STATUS_LOADING_MUSIC )
+					{
+						gLoadStatus |= LOAD_STATUS_NEED_STREAM;
+						return;
+					}
+					gLoadStatus |= LOAD_STATUS_LOADING_STREAM;
+#endif					
+					sceSdSetCoreAttr ( STREAM_CORE | SD_C_IRQ_ENABLE, 0);
+					gStreamTransSize = SB_BUF_HALF;
+					gStreamRemaining -= SB_BUF_HALF;
+					if ( gStreamRemaining < 0 )
+					{
+						gStreamTransSize += gStreamRemaining;
+					}
+
+#if DATA_INTEGRITY_CHECK
+					{
+						int gronk;
+						for ( gronk = 0; gronk < gStreamTransSize >> 4; gronk++ )
+						{
+							if ( *( ( char * )( 1 + gpIopBuf + gStreamIopOffset + STREAM_IOP_OFFSET + ( gronk * 16 ) ) ) != 0 )
+							{
+								printf( "Fucked up data!!!!" );
+							}
+						}
+					}
+#endif
+					
+					// load in left side:
+					if ( gStreamBufSide == _1st )
+					{
+						_AdpcmSetMarkSTART( gpIopBuf + STREAM_IOP_OFFSET + gStreamIopOffset, gStreamTransSize );
+						
+					}
+					if ( gStreamRemaining <= 0 )
+					{
+						_AdpcmSetMarkFINAL( gpIopBuf + STREAM_IOP_OFFSET + gStreamIopOffset, gStreamTransSize );
+					}
+					else if ( gStreamBufSide == _2nd )
+					{
+						_AdpcmSetMarkEND( gpIopBuf + STREAM_IOP_OFFSET + gStreamIopOffset, gStreamTransSize );
+					}
+					sceSdVoiceTrans( TRANS_DMA_CH_STREAM, ( SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA ),
+						(unsigned char *) gpIopBuf + STREAM_IOP_OFFSET + gStreamIopOffset,
+						( STREAM_SPU_BUF_LOC + SB_BUF_HALF * gStreamBufSide ), gStreamTransSize );
+					gStreamLoadState++;
+					break;
+				}
+				case ( STREAM_LOAD_STATE_LOADING ):
+				{
+					ShowState( "Stream Running -- Waiting for IRQ\n" );
+					SmallShowState( " %d-SRIRQ", GetTime( ) );
+					// reset the interrupt:
+					if ( gStreamRemaining <= 0 )
+					{
+						sceSdSetAddr( STREAM_CORE | SD_A_IRQA, gStreamTransSize - LAST_VAG_POS + STREAM_SPU_BUF_LOC + SB_BUF_HALF * gStreamBufSide );
+					}
+					else
+					{
+						sceSdSetAddr( STREAM_CORE | SD_A_IRQA, STREAM_SPU_BUF_LOC + SB_BUF_HALF * gStreamBufSide );
+					}
+					gTimeOfLastStreamUpdate = GetTime( );
+		
+					gStreamIopOffset += SB_BUF_HALF;
+					gStreamBufSide = ( gStreamBufSide == _1st ) ? _2nd : _1st;
+					
+					if ( gStreamRemaining <= 0 )
+					{
+						// the vag data up to the end should contain all zeros,
+						// so it doesn't matter that the interrupt won't happen until the end.
+						// stop the stream when it gets to the end:
+						gStreamStop++;
+					}
+					else
+					{
+						if ( gStreamIopOffset == HALF_BUFFER_SIZE )
+						{
+							//Dbug_Printf( "Stream move over\n" );
+							if ( gStreamRemaining > HALF_BUFFER_SIZE )
+							{
+								gEECommand |= PCMSTATUS_NEED_STREAM_BUFFER_0;
+							}
+						}
+						else if ( gStreamIopOffset == SINGLE_STREAM_BUFFER_SIZE )
+						{
+							//Dbug_Printf( "Stream move back\n" );
+							gStreamIopOffset = 0;
+							if ( gStreamRemaining > HALF_BUFFER_SIZE )
+							{
+								gEECommand |= PCMSTATUS_NEED_STREAM_BUFFER_1;
+							}
+						}
+					}
+					if ( gStreamStop )
+					{
+						gStreamStop = 0;
+						gStreamStatus = EzADPCM_STATUS_TERMINATE;
+					}
+					sceSdSetCoreAttr( STREAM_CORE | SD_C_IRQ_ENABLE, 1);
+					gStreamLoadState = STREAM_LOAD_STATE_WAITING_FOR_IRQ;
+//					printf( "stream wait IRQ\n" );
+#if SHARE_DMA_CH
+					gLoadStatus &= ~LOAD_STATUS_LOADING_STREAM;
+					if ( gLoadStatus & LOAD_STATUS_NEED_MUSIC )
+					{
+						gLoadStatus &= ~LOAD_STATUS_NEED_MUSIC;
+						UpdateMusic( );
+					}
+#endif
+					break;
+				}
+				default:
+					printf( "Unknown Stream loading state %d\n", gStreamLoadState );
+					break;
+			}
+			break;
+
+		case EzADPCM_STATUS_TERMINATE: 
+			ShowState( "Stream terminate\n" );
+			SmallShowState( "-ST!!!" );
+			StopStream( );
+			break;
+
+		default:
+			printf( "unknown stream status in pcm irx!!!\n" );
+			break;
+	}
+}
+
+/* internal */
+int _AdpcmPlay( int status )
+{
+    while ( 1 )
+	{
+//        int st, mu;
+		WaitSema(gSem);
+		
+//		ShowTime( "ssig us %d um %d ss %d ms %d ee %d time %d\n", gUpdateStream, gUpdateMusic, gStreamStatus, gMusicStatus, gEECommand, GetTime( ) );
+//		SmallShowTime( " <%d>", GetTime( ) );
+//		st = gUpdateStream;
+//		mu = gUpdateMusic;
+		while ( gUpdateStream )
+		{
+//printf( "s u\n" );		
+			gUpdateStream--;
+			UpdateStream( );
+		}
+		while ( gUpdateMusic )
+		{
+//printf( "m u\n" );		
+			gUpdateMusic--;
+			UpdateMusic( );
+		}
+		
+/*		
+		if ( gTestPause == 666 )
+		{
+			sceSdSetParam( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_PITCH, 0 );
+			sceSdSetParam( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_PITCH, 0 );
+			sceSdSetParam( STREAM_CORE | ( STREAM_VOICE << 1 ) | SD_VP_PITCH, 0 );
+			gPaused = 1;
+		}
+		if ( ( !gTestPause ) && ( !st && !mu ) )
+		{
+			printf( "MOTHERFUCKER MOTHERFUCKER MOTHERFUCKER MOTHERFUCKER\n" );
+			gTestPause = 30;
+		}*/
+
+//printf( "hmm NAX %x\n", sceSdGetAddr( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VA_NAX ) );
+		if ( gMusicVolumeSet )
+		{
+			gMusicVolumeSet = 0;
+			AdpcmSetMusicVolumeDirect( gMusicVolume );
+		}
+		if ( gStreamVolumeSet )
+		{
+			gStreamVolumeSet = 0;
+			AdpcmSetStreamVolumeDirect( gStreamVolume );
+		}
+    }
+    return 0;
+}
+
+static int rpc_arg [16];
+volatile int ret = 0;
+
+static void *dispatch( unsigned int command, void *data_, int size )
+{ 
+    int ch;
+    int          data  = *((         int *) data_);
+    unsigned int dataU = *((unsigned int *) data_);
+
+/*    PRINTF (("# dispatch [%04x] %x, %x, %x, %x\n",
+	      command,
+	      *((int*) data_ + 0), 
+	      *((int*) data_ + 1),
+	      *((int*) data_ + 2),
+          *((int*) data_ + 3)));*/
+
+    ch = command & EzADPCM_CH_MASK;
+    switch ( command & EzADPCM_COMMAND_MASK )
+	{
+		case EzADPCM_INIT:
+			ret = AdpcmInit( data ); // iop buffer pointer
+			break;
+
+		case EzADPCM_QUIT:
+			AdpcmQuit( );
+			break;
+
+		case EzADPCM_PLAYMUSIC:
+			ret = AdpcmPlayMusic( data );  // size of the entire PCM data in the file
+			break;
+
+		case EzADPCM_PLAYSTREAM:
+			ret = AdpcmPlayStream( data ); // size of the entire PCM data in the file
+			break;
+
+		case EzADPCM_PAUSEMUSIC:
+			if ( data )
+			{
+				if ( !gMusicPaused )
+				{
+					sceSdSetParam( MUSIC_CORE | ( MUSIC_L_VOICE << 1 ) | SD_VP_PITCH, 0 );
+					sceSdSetParam( MUSIC_CORE | ( MUSIC_R_VOICE << 1 ) | SD_VP_PITCH, 0 );
+					gMusicPaused = GetTime( );
+					gMusicTimeOffset = gMusicPaused - gTimeOfLastMusicUpdate;
+					if ( gMusicTimeOffset > SINGLE_BUFFER_MS )
+					{
+/*						if ( gMusicStatus != EzADPCM_STATUS_TERMINATE )
+						{
+							printf( "\n\nfuck your mom %x \n\n", gMusicStatus );
+						}*/
+						gMusicTimeOffset -= SINGLE_BUFFER_MS;
+					}
+					ShowAction( "Pausing music\n" );
+					SmallShowAction( "-pm" );
+				}
+				gRequestFlags &= ~RF_UNPAUSE_MUSIC;
+			}
+			else
+			{
+				if ( gMusicPaused )
+				{
+					gRequestFlags |= RF_UNPAUSE_MUSIC;
+					ShowAction( "Requesting unpause music\n" );
+					SmallShowAction( "-rupm" );
+				}
+			}
+			break;
+
+		case EzADPCM_PAUSESTREAM:
+			if ( data )
+			{
+				if ( !gStreamPaused )
+				{
+					sceSdSetParam( STREAM_CORE | ( STREAM_VOICE << 1 ) | SD_VP_PITCH, 0 );
+					gStreamPaused = GetTime( );
+					gStreamTimeOffset = gStreamPaused - gTimeOfLastStreamUpdate;
+					if ( gStreamTimeOffset > SINGLE_BUFFER_MS )
+					{
+/*						if ( gStreamStatus != EzADPCM_STATUS_TERMINATE )
+						{
+							printf( "\n\nfuck your mom %x \n\n", gStreamStatus );
+						}*/
+						gStreamTimeOffset -= SINGLE_BUFFER_MS;
+					}
+					ShowAction( "Pausing stream\n" );
+					SmallShowAction( "-ps" );
+				}
+				gRequestFlags &= ~( RF_UNPAUSE_STREAM );
+			}
+			else
+			{
+				if ( gStreamPaused )
+				{
+					gRequestFlags |= RF_UNPAUSE_STREAM;
+					ShowAction( "Requesting unpause stream\n" );
+					SmallShowAction( "-rups" );
+				}
+			}
+			break;
+		
+		case EzADPCM_STOPMUSIC:
+			ret = AdpcmStopMusic( );
+			break;
+
+		case EzADPCM_STOPSTREAM:
+			ret = AdpcmStopStream( );
+			break;
+
+		case EzADPCM_SETMUSICVOL:
+			AdpcmSetMusicVolume( dataU );
+			break;
+
+		case EzADPCM_SETMUSICVOLDIRECT:
+			AdpcmSetMusicVolumeDirect( dataU );
+			break;
+
+		case EzADPCM_SETSTREAMVOL:
+			AdpcmSetStreamVolume( dataU );
+			break;
+
+		case EzADPCM_SETSTREAMVOLDIRECT:
+			AdpcmSetStreamVolumeDirect( dataU );
+			break;
+
+		case EzADPCM_GETSTATUS:
+			ret = AdpcmGetStatus( );
+			break;
+
+		case EzADPCM_GETMUSICSTATUS:
+			ret = AdpcmGetMusicStatus( );
+			break;
+
+		case EzADPCM_GETSTREAMSTATUS:
+			ret = AdpcmGetStreamStatus( );
+			break;
+
+		case EzADPCM_SDINIT:
+			AdpcmSdInit( );
+			break;
+
+		default:
+			ERROR (("EzADPCM driver error: unknown command %d \n", data));
+			break;
+    }
+    //printf( "! return value = %x \n", ret );
+    return (void*)(&ret);
+}
+
+int sce_adpcm_loop( void )
+{
+    sceSifQueueData qd;
+    sceSifServeData sd;
+
+    sceSifInitRpc (0);
+    sceSifSetRpcQueue (&qd, GetThreadId ());
+    sceSifRegisterRpc (&sd, EzADPCM_DEV, dispatch, (void*)rpc_arg, NULL, NULL, &qd);
+    //Dbug_Printf(("goto adpcm cmd loop\n"));
+    
+    sceSifRpcLoop (&qd);
+
+    return 0;
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
diff --git a/Code/Gel/Music/ngc/pcm/pcm_ent.c b/Code/Gel/Music/ngc/pcm/pcm_ent.c
new file mode 100644
index 0000000..a013012
--- /dev/null
+++ b/Code/Gel/Music/ngc/pcm/pcm_ent.c
@@ -0,0 +1,46 @@
+/* Vag streaming into SPU2 -- Converted from Sony samples -- matt may 2001 */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "pcm.h"
+#include "pcmiop.h"
+
+ModuleInfo Module = {"pcm_driver", 0x0102};
+
+// in command.c
+extern int sce_adpcm_loop (void);
+
+int start ( void )
+{
+    struct ThreadParam param;
+    int th;
+
+    if (! sceSifCheckInit ())
+	sceSifInit ();
+    sceSifInitRpc (0);
+
+    printf ("PCM driver version 666\n");
+
+    param.attr         = TH_C;
+    param.entry        = sce_adpcm_loop;
+    param.initPriority = BASE_priority - 2;
+    param.stackSize    = 0x800;
+    param.option       = 0;
+    th = CreateThread (¶m);
+    if (th > 0) {
+	StartThread (th, 0);
+	printf (" Exit PCM loader thread \n");
+	return 0;
+    }else{
+	return 1;
+    }
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
+/* DON'T ADD STUFF AFTER THIS */
diff --git a/Code/Gel/Music/ngc/pcm/pcmiop.h b/Code/Gel/Music/ngc/pcm/pcmiop.h
new file mode 100644
index 0000000..6571684
--- /dev/null
+++ b/Code/Gel/Music/ngc/pcm/pcmiop.h
@@ -0,0 +1,13 @@
+#if 0
+#define PRINTF(x) printf x
+#else
+#define PRINTF(x) 
+#endif
+#define ERROR(x) printf x
+#define xPRINTF(x) 
+
+#define BASE_priority  32
+
+#define OLDLIB 0
+#define TRANS_CH  0
+
diff --git a/Code/Gel/Net/App/netapp.cpp b/Code/Gel/Net/App/netapp.cpp
new file mode 100644
index 0000000..eb60400
--- /dev/null
+++ b/Code/Gel/Net/App/netapp.cpp
@@ -0,0 +1,2817 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Net (OBJ) 												**
+**																			**
+**	File name:		netapp.cpp												**
+**																			**
+**	Created:		01/29/01	-	spg										**
+**																			**
+**	Description:	Network app code										**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#ifndef __PLAT_WN32__
+#include 
+#endif// __PLAT_WN32__
+
+#include 
+
+#include 
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+//extern "C"
+//{
+	void NS_SetSemaphores( int send_semaphore, int recv_semaphore );
+//}
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+//#define DEBUG_MESSAGES
+ 
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+const unsigned int App::MAX_LATENCY = 2000;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/* Initialize the socket and set socket options					  */
+/* Also, add default message handlers                             */
+/******************************************************************/
+
+bool	App::init( void )
+{	
+#ifndef __PLAT_NGPS__
+	if( !IsLocal())
+	{
+		// create socket
+		m_socket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
+#ifdef __PLAT_NGC__
+		if( m_socket < 0 )
+#else
+		if( m_socket == INVALID_SOCKET )
+#endif
+		{
+			ReportError();
+			return false;
+		}
+	
+#ifdef __PLAT_NGC__
+		int val;
+		 
+		val = SOFcntl( m_socket, SO_F_GETFL, 0);
+		if( val < 0 )
+		{
+			ReportError();
+			return false;
+		}
+		val = SOFcntl( m_socket, SO_F_SETFL, val | SO_O_NONBLOCK);
+		if( val < 0 )
+		{
+			ReportError();
+			return false;
+		}
+#else
+		unsigned long argp = 1L;	// non-zero enables non-blocking mode
+
+		if( ioctlsocket( m_socket, FIONBIO, &argp ) == SOCKET_ERROR )
+		{
+			ReportError();
+			return false;
+		}
+	
+		if( m_flags.TestMask( mBROADCAST ))
+		{
+			if(( setsockopt( m_socket, SOL_SOCKET, SO_BROADCAST, 
+					(char*) &argp, sizeof(argp))) == SOCKET_ERROR )
+			{
+				ReportError();
+				return false;
+			}
+		}
+#endif 	// __PLAT_NGC
+	}
+#else // __PLAT_NGPS__
+	if( !IsLocal())
+	{
+		sn_int32 true_val, false_val;
+	
+		true_val = 1;
+		false_val = 0;
+		
+		if(( m_socket = socket( AF_INET, SOCK_DGRAM, PF_INET )) == INVALID_SOCKET )
+		{
+			ReportError();
+			return false;
+		}
+		
+		// make socket non-blocking
+		if( setsockopt( m_socket, SOL_SOCKET, SO_NBIO, &true_val, sizeof( sn_int32 )) == SOCKET_ERROR )
+		{
+			ReportError();
+			return false;
+		}
+	
+		if( m_flags.TestMask( mBROADCAST ))
+		{
+			if(( setsockopt( m_socket, SOL_SOCKET, SO_BROADCAST, 
+					(char*) &true_val, sizeof( sn_int32 ))) == SOCKET_ERROR )
+			{
+				ReportError();
+				return false;
+			}
+		}
+		else
+		{
+			if(( setsockopt( m_socket, SOL_SOCKET, SO_BROADCAST, 
+					(char*) &false_val, sizeof( sn_int32 ))) == SOCKET_ERROR )
+			{
+				ReportError();
+				return false;
+			}
+		}
+	}
+
+	m_shutting_down = false;
+
+	m_socket_thread_data.m_pEntry = threaded_transfer_data;
+	m_socket_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	m_socket_thread_data.m_pStackBase = m_socket_thread_stack;
+	m_socket_thread_data.m_iStackSize = vSOCKET_THREAD_STACK_SIZE;
+	m_socket_thread_data.m_utid = vBASE_SOCKET_THREAD_ID + m_net_man->NumApps();
+	Thread::CreateThread( &m_socket_thread_data );
+	m_socket_thread_id = m_socket_thread_data.m_osId;
+	
+	struct SemaParam params;
+
+	params.initCount = 1;
+	params.maxCount = 10;
+
+	m_send_semaphore_id = CreateSema( ¶ms );
+	m_receive_semaphore_id = CreateSema( ¶ms );
+	m_transfer_semaphore_id = CreateSema( ¶ms );
+	m_active_semaphore_id = CreateSema( ¶ms );
+
+    if( !IsLocal())
+	{
+        NS_SetSemaphores( m_send_semaphore_id, m_receive_semaphore_id );
+	}
+
+	#ifdef __USER_STEVE__
+	Dbg_Printf( "Send Semaphore %d\n", m_send_semaphore_id );
+	Dbg_Printf( "Receive Semaphore %d\n", m_receive_semaphore_id );
+	Dbg_Printf( "Transfer Semaphore %d\n", m_transfer_semaphore_id );
+	#endif
+
+	StartThread( m_socket_thread_id, this );
+#endif	// __PLAT_NGPS__
+
+	m_num_connections = 0;	
+	m_TotalBytesIn = 0;
+	m_TotalBytesOut = 0;
+	m_LostPackets = 0;
+	m_LatePackets = 0;
+	m_DupePackets = 0;
+	m_FrameCounter = 0;
+
+	m_Dispatcher.Init();
+    
+	m_Dispatcher.AddHandler( MSG_ID_ACK, handle_ack, mHANDLE_LATE | mHANDLE_FOREIGN );
+	m_Dispatcher.AddHandler( MSG_ID_SEQUENCED, handle_sequenced_messages, mHANDLE_LATE );
+	m_Dispatcher.AddHandler( MSG_ID_STREAM_START, handle_stream_messages, mHANDLE_LATE );
+	m_Dispatcher.AddHandler( MSG_ID_STREAM_DATA, handle_stream_messages, mHANDLE_LATE );
+	m_Dispatcher.AddHandler( MSG_ID_PING_TEST, handle_latency_test, mHANDLE_LATE );
+	m_Dispatcher.AddHandler( MSG_ID_PING_RESPONSE, handle_latency_response, mHANDLE_LATE );
+
+	return true;
+}
+
+/******************************************************************/
+/* Bind a socket                                      			  */
+/*                                                                */
+/******************************************************************/
+
+bool App::bind_app_socket( int address, unsigned short port )
+{
+	struct sockaddr_in host_address;
+	int address_len, result;
+
+	// bind the socket to an address.
+
+	memset( &host_address, 0, sizeof(host_address));
+	host_address.sin_family = AF_INET;
+	host_address.sin_port = htons( port );
+	// This should basically check if we're using DHCP, but right now modem is the only
+	// device using it
+	if( m_net_man->GetConnectionType() != vCONN_TYPE_MODEM )
+	{
+#ifdef __PLAT_XBOX__
+		host_address.sin_addr.s_addr = INADDR_ANY;
+#else
+#ifdef __PLAT_NGC__
+		host_address.s_addr.s_addr = address;
+#else
+		host_address.sin_addr.s_addr = address;
+#endif
+#endif				
+	}
+#ifdef __PLAT_NGC__
+	host_address.len = sizeof( SOSockAddrIn );
+#else
+	memset ( &host_address.sin_zero, 0, 8 );  
+#endif
+
+	// bind(name) socket
+	result = bind( m_socket, (struct sockaddr *)&host_address, sizeof(host_address));
+	if( result < 0 )// == SOCKET_ERROR )
+	{
+#ifdef WIN32
+		int err = WSAGetLastError();
+		if( err == WSAEADDRINUSE )  
+#else
+#ifdef __PLAT_NGPS__
+		int err = sn_errno( m_socket );
+		if( err == EADDRINUSE )
+#else
+#ifdef __PLAT_NGC__
+		if( result == SO_EADDRINUSE )
+#endif
+#endif
+#endif
+
+		{
+			ReportError();
+			host_address.sin_port = htons( 0 );
+			result = bind( m_socket, (struct sockaddr *)&host_address, sizeof(host_address));
+			if( result < 0 )// == SOCKET_ERROR )
+			{
+				ReportError();
+				return false;
+			}
+		}
+		else
+		{
+			ReportError();
+			return false;
+		}
+	}		
+    
+#ifdef __PLAT_NGC__
+	m_local_address.len = sizeof( SOSockAddrIn );
+#endif
+	address_len = sizeof( m_local_address );
+	result = getsockname( m_socket, (sockaddr*) &m_local_address, &address_len );
+	if( result < 0 )// == SOCKET_ERROR )
+	{
+		ReportError();
+		return false;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/* Free up memory used by the network app. Remove msg handlers    */
+/*                                                                */
+/******************************************************************/
+
+void	App::deinit( void )
+{
+	
+
+	Lst::Search< Conn > sh;
+	Conn *conn, *next;
+
+	if( !IsLocal())
+	{
+		closesocket( m_socket );
+	}
+	
+	m_Dispatcher.Deinit();
+
+	for( conn = sh.FirstItem( m_connections ); conn; conn = next )
+	{
+		next = sh.NextItem();
+		delete conn;
+	}
+}
+
+/******************************************************************/
+/* Get the next free available handle                             */
+/*                                                                */
+/******************************************************************/
+
+int		App::get_free_handle( void )
+{
+	int i;
+
+	
+
+	for( i = Conn::vHANDLE_FIRST; i < 256; i++ )
+	{
+		if( !GetConnectionByHandle( i ))
+		{
+			return i;
+		}
+	}
+
+	Dbg_MsgAssert( 0,( "No free handles\n" ));
+	return Conn::vHANDLE_INVALID;
+}
+
+/******************************************************************/
+/* Process the list of sequenced messages that we have queued up  */
+/*                                                                */
+/******************************************************************/
+
+void	App::process_sequenced_messages( Conn *conn )
+{
+	int i;
+	Lst::Search< MsgSeqLink > sh;
+	MsgSeqLink *msg_link, *next;
+	MsgHandlerContext msg_context;
+	int result;
+
+	
+
+	Dbg_Assert( conn );
+
+	result = HANDLER_CONTINUE;	
+	for( i = 0; i < MAX_SEQ_GROUPS; i++ )
+	{
+		for( msg_link = sh.FirstItem( conn->m_SequencedBuffer[i] );
+				msg_link; msg_link = next )
+		{
+			next = sh.NextItem();
+			
+			if( msg_link->m_SequenceId == conn->m_WaitingForSequenceId[msg_link->m_GroupId] )
+			{				
+				msg_context.m_Conn = conn;
+				msg_context.m_Msg = NULL;
+				
+				if( msg_link->m_QMsg->m_Data )
+				{
+					msg_context.m_Msg = msg_link->m_QMsg->m_Data;
+				}
+				msg_context.m_App = this;
+				msg_context.m_PacketFlags = 0;
+				msg_context.m_MsgId = msg_link->m_QMsg->m_MsgId;
+				msg_context.m_MsgLength = msg_link->m_QMsg->m_MsgLength;
+
+				conn->m_WaitingForSequenceId[msg_link->m_GroupId]++;
+
+				result = m_Dispatcher.DispatchMessage( &msg_context );
+								
+				if( result != HANDLER_MSG_DESTROYED )
+				{
+					delete msg_link->m_QMsg;
+					delete msg_link;
+				}
+
+				// If we've gotten a false result, that means we should
+				// stop processing this connection
+				if( result != HANDLER_CONTINUE )
+				{
+					return;
+				}
+			}
+			else
+			{
+#ifdef NET_DEBUG_MESSAGES
+				static unsigned int stall = 0;
+
+				if( stall != msg_link->m_SequenceId )
+				{
+					Dbg_Printf( "*** Stalling on seq: %d, waiting for %d : msg %d, group %d\n", 
+								msg_link->m_SequenceId, conn->m_WaitingForSequenceId[msg_link->m_GroupId],
+								msg_link->m_QMsg->m_MsgId, msg_link->m_GroupId );
+					stall = msg_link->m_SequenceId;
+				}
+#endif
+				break;
+			}
+
+		}
+	}
+}
+
+/******************************************************************/
+/* Kill off invalid connections. Should be done at a safe time.   */
+/* i.e. when you're not processing connections anymore            */
+/******************************************************************/
+
+void	App::terminate_invalid_connections( void )
+{
+	Conn *conn, *next_conn;
+	Lst::Search< Conn > sh;
+
+#ifdef __PLAT_NGPS__
+	WaitSema( m_transfer_semaphore_id );
+#endif	// __PLAT_NGPS__    
+	for( conn = FirstConnection( &sh ); conn;
+			conn = next_conn )
+	{
+		next_conn = NextConnection( &sh );
+
+		// If we're in the process of disconnecting them and we have no more messages
+		// to send them, go ahaed and disconnect them
+		if( conn->TestStatus( Conn::mSTATUS_DISCONNECTING ))
+		{
+			if( MessagesToSend( conn ) == false )
+			{
+				conn->Invalidate();
+			}
+		}
+
+		// Destroy invalid connections
+		if( conn->IsValid() == false )
+		{
+			if( ( MessagesToProcess( conn ) == false ) && 
+				( MessagesToSend( conn ) == false ))
+			{
+				TerminateConnection( conn );
+			}
+		}
+	}       
+#ifdef __PLAT_NGPS__
+	SignalSema( m_transfer_semaphore_id );
+#endif	// __PLAT_NGPS__    
+}
+
+/******************************************************************/
+/* Processes any pending stream messages. Enqueuing data if the	  */
+/* sliding window permits it                                      */
+/******************************************************************/
+
+void    App::process_stream_messages( void )
+{
+	int size, packet_len, msg_len;
+	MsgStreamData data_msg;
+	MsgDesc msg_desc;
+	StreamDesc* stream_desc;
+	StreamLink* stream_link, *next_link;
+	Conn *conn;
+	Lst::Search< Conn > sh;
+	Lst::Search< StreamLink > stream_sh;
+	int num_pending, num_to_send;
+	Net::Manager* net_man = Net::Manager::Instance();
+
+	for( conn = FirstConnection( &sh ); conn; conn = NextConnection( &sh ))
+	{
+		int pending_threshold, max_to_send;
+
+		pending_threshold = 5;
+		max_to_send = 3;
+		if(( net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM ) ||
+		   ( conn->GetBandwidthType() == Conn::vNARROWBAND ))
+		{
+			pending_threshold = 2;
+			max_to_send = 1;
+		}
+		// If we have any pending streams, see if there is any room to queue up chunks
+		if( conn->m_StreamOutList.CountItems() > 0 )
+		{
+			//Dbg_Printf( "******** %d items remain to stream\n", conn->m_StreamOutList.CountItems());
+			num_pending = conn->GetNumPendingStreamMessages();
+			if( num_pending < pending_threshold ) // Should be configurable and dynamic
+			{
+				num_to_send = pending_threshold - num_pending;
+				if( num_to_send > max_to_send )
+				{
+					num_to_send = max_to_send;
+				}
+				for( stream_link = stream_sh.FirstItem( conn->m_StreamOutList ); stream_link; stream_link = next_link )
+				{
+					next_link = stream_sh.NextItem();
+
+					stream_desc = stream_link->m_Desc;
+					size = (unsigned int) stream_desc->m_DataPtr - (unsigned int) stream_desc->m_Data;
+					msg_len = stream_desc->m_Size;
+					while(( size < msg_len ) && ( num_to_send > 0 ))
+					{
+						packet_len = msg_len - size;
+						if( packet_len > MAX_STREAM_CHUNK_LENGTH )
+						{
+							packet_len = MAX_STREAM_CHUNK_LENGTH;
+						}
+				
+						data_msg.m_StreamId = stream_desc->m_StreamId;
+						memcpy( data_msg.m_Data, stream_desc->m_DataPtr, packet_len );
+						
+						msg_desc.m_Id = MSG_ID_STREAM_DATA;
+						msg_desc.m_Data = &data_msg;
+						msg_desc.m_Length = packet_len + sizeof( int );
+						msg_desc.m_Queue = QUEUE_SEQUENCED;
+						msg_desc.m_GroupId = stream_desc->m_GroupId;
+						msg_desc.m_StreamMessage = 1;
+						msg_desc.m_ForcedSequenceId = stream_desc->m_SequenceId++;
+				
+						//Dbg_Printf( "%d ******** [%d] Streaming %d bytes. ForcedSequence: %d\n", m_FrameCounter, stream_desc->m_StreamId, packet_len, stream_desc->m_SequenceId );
+						EnqueueMessage( conn->GetHandle(), &msg_desc );
+				
+						stream_desc->m_DataPtr += packet_len;
+						size += packet_len;
+
+						num_to_send--;
+					}
+
+					if( size >= msg_len )
+					{
+						//Dbg_Printf( "******** [%d] Finished streaming. Destroying Stream Link\n", stream_desc->m_StreamId );
+						delete stream_desc;
+						delete stream_link;
+					}
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/* Read the data from the stack and store in appropriate conn     */
+/*                                                                */
+/******************************************************************/
+
+void	App::read_network_data( const Tsk::Task< App > &task )
+{
+	App *app;
+    
+    app = &task.GetData();
+
+#ifdef __PLAT_NGPS__
+	app->m_double_timestamp += ( Tmr::UncappedFrameLength() * 60.0f ) * 
+									( Tmr::vRESOLUTION / 60.0f );
+	app->m_Timestamp = (unsigned int) app->m_double_timestamp;
+#else
+	app->m_Timestamp = Tmr::GetTime();
+#endif // _PLAT_NGPS__
+
+	app->terminate_invalid_connections();
+	
+#ifdef __PLAT_NGPS__
+	WaitSema( app->m_transfer_semaphore_id );
+	SignalSema( app->m_transfer_semaphore_id );
+	WakeupThread( app->m_socket_thread_id );
+#else
+	app->ReceiveData();
+#endif
+}
+
+/******************************************************************/
+/* Service metrics data                 					  	  */
+/*                                                                */
+/******************************************************************/
+
+void	App::service_network_metrics( const Tsk::Task< App > &task )
+{
+	App *app;
+	Conn *conn;
+	Lst::Search< Conn > sh;
+    
+    app = &task.GetData();
+	Dbg_Assert( app );
+
+	for( conn = app->FirstConnection( &sh ); conn; conn = app->NextConnection( &sh ))
+	{
+		conn->GetInboundMetrics()->CalculateBytesPerSec( app->m_Timestamp );
+		conn->GetOutboundMetrics()->CalculateBytesPerSec( app->m_Timestamp );
+	}
+}
+
+/******************************************************************/
+/* Process network data task                 					  */
+/*                                                                */
+/******************************************************************/
+
+void	App::process_network_data( const Tsk::Task< App > &task )
+{
+	App *app;
+    
+    app = &task.GetData();
+	Dbg_Assert( app );
+	
+		app->m_FrameCounter++;
+
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PushContext( 255, 255, 255 );
+#	endif // __USE_PROFILER__
+
+#ifdef __PLAT_NGPS__
+	WaitSema( app->m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__    
+	app->ProcessData(); 
+#ifdef __PLAT_NGPS__
+	SignalSema( app->m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__    
+
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PopContext();
+#	endif // __USE_PROFILER__
+}
+
+/******************************************************************/
+/* Threaded send/receive data for the PS2						  */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __PLAT_NGPS__
+
+void	App::threaded_transfer_data( void *data )
+{
+	
+    Net::App* app = (Net::App *) data;
+    
+	WaitSema( app->m_active_semaphore_id );
+
+	if( !app->IsLocal())
+	{
+#ifdef DEBUG_MESSAGES
+		Dbg_Printf( "Registering transfer thread %d with stack\n", GetThreadId());
+#endif
+	
+#ifdef __NOPT_ASSERT__
+		int result = 
+#endif		
+		sockAPIregthr();
+		Dbg_Assert( result == 0 );
+	}
+
+	app->m_socket_thread_active = true;
+	
+	app->TransferData();
+
+	if( !app->IsLocal())
+	{   
+#ifdef DEBUG_MESSAGES
+		Dbg_Printf( "DeRegistering transfer thread %d with stack\n", GetThreadId());
+#endif
+		sockAPIderegthr();
+	}
+
+	app->m_socket_thread_active = false;
+	
+	SignalSema( app->m_active_semaphore_id );
+}
+
+#endif
+
+/******************************************************************/
+/* Send queued messages across the wire                           */
+/*                                                                */
+/******************************************************************/
+
+void	App::send_network_data( const Tsk::Task< App > &task )
+{
+	App *app;
+
+	app = &task.GetData();
+	Dbg_Assert( app );
+
+	app->process_stream_messages();
+
+#ifdef __PLAT_NGPS__
+	WaitSema( app->m_transfer_semaphore_id );
+	SignalSema( app->m_transfer_semaphore_id );
+	WakeupThread( app->m_socket_thread_id );
+#else
+	app->SendData( true );    
+#endif
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	App::crc_and_copy_stream( char* in_stream, char* out_stream, int in_len, int* out_len )
+{
+	uint32 actual_crc;	
+	int i;
+	unsigned char* in, *out;
+	unsigned char key;
+	unsigned short hi_word, lo_word, sum_crc;
+
+	
+	
+	Dbg_Assert( out_stream );
+	Dbg_Assert( in_stream );
+	Dbg_Assert( in_len > 0 );
+	
+	actual_crc = Crc::GenerateCRCCaseSensitive( in_stream, in_len );
+	hi_word = ( actual_crc & 0xFFFF0000 ) >> 16;
+	lo_word = ( actual_crc & 0xFFFF );
+	sum_crc = hi_word + lo_word;
+
+	memcpy( out_stream, &sum_crc, sizeof( unsigned short ));
+
+	// Encrypt by XOring each byte with the lsB of the checksum
+	out = (unsigned char*) ( out_stream + sizeof( unsigned short ));
+	in = (unsigned char*) in_stream;
+	key = (unsigned char) sum_crc;
+	for( i = 0; i < in_len; i++ )
+	{
+		*out++ = ( *in++ ) ^ key;
+	}
+	
+	*out_len = in_len + sizeof( unsigned short );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	App::validate_and_copy_stream( char* in_stream, char* out_stream, int in_len )
+{
+    uint32 actual_crc;
+	int out_len;
+	unsigned char key;
+	unsigned short hi_word, lo_word;
+	unsigned short packet_crc, sum_crc;
+	unsigned char* in;
+	int i;
+	char packet_data[ Manager::vMAX_PACKET_SIZE ];
+
+	
+	
+	Dbg_Assert( out_stream );
+	Dbg_Assert( in_stream );
+		
+	if( in_len < Manager::vMIN_PACKET_SIZE )
+	{
+		return false;
+	}
+
+	// If this isn't a secure app, just process the data as unencrypted and un-checksumed
+	if(( m_flags & mSECURE ) == 0 )
+	{
+		memcpy( out_stream, in_stream, in_len );
+		return true;
+	}
+
+	out_len = in_len - sizeof( unsigned short );
+	memcpy( &packet_crc, in_stream, sizeof( unsigned short ));
+	memcpy( packet_data, in_stream + sizeof( unsigned short ), out_len );	// skip CRC
+
+	key = (unsigned char) packet_crc;	// use the lsB of the packet CRC to decrypt the packet
+	in = (unsigned char*) packet_data;
+	for( i = 0; i < out_len; i++ )
+	{
+		*in = (*in) ^ key;
+		in++;
+	}
+
+	actual_crc = Crc::GenerateCRCCaseSensitive( packet_data, out_len );
+	hi_word = ( actual_crc & 0xFFFF0000 ) >> 16;
+	lo_word = ( actual_crc & 0xFFFF );
+	sum_crc = hi_word + lo_word;
+	if( sum_crc != packet_crc )
+	{
+#ifdef DEBUG_MESSAGES
+		Dbg_Printf( "CRC Check Failed!!! Got %d instead of %d\n", packet_crc, sum_crc );
+#endif
+		return false;
+	}
+	
+	memcpy( out_stream, packet_data, out_len );
+
+#ifdef WRITE_OUT_NET_PACKETS
+	static int f = 0;
+	char path[32];
+
+	sprintf( path, "jpacket%d.net", f );
+	void* handle;
+
+	handle = File::Open( path, "wb");
+	if( handle )
+	{
+		File::Write( out_stream, 1, out_len, handle );
+		File::Close( handle );	
+		f++;
+	}
+#endif
+
+	return true;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+LatencyTest::LatencyTest( void )
+{
+	memset( m_TimeTests, 0, sizeof( int ) * NET_NUM_LATENCY_TESTS );
+	m_AveLatency = Manager::vSTANDARD_RESEND_THRESHOLD / 2;
+	m_CurrentTest = 0;
+	m_SendTime = 0;
+	m_ReceiveTime = 0;
+}
+
+/******************************************************************/
+/* Add data to our latency calculator                             */
+/*                                                                */
+/******************************************************************/
+
+void	LatencyTest::InputLatencyValue( int latency )
+{
+	int i, num_tests, total;
+
+	m_TimeTests[ m_CurrentTest ] = latency;
+	m_CurrentTest = ( m_CurrentTest + 1 ) % NET_NUM_LATENCY_TESTS;
+
+	total = 0;
+	num_tests = 0;
+	// compute new average
+	for( i = 0; i < NET_NUM_LATENCY_TESTS; i++ )
+	{
+		if( m_TimeTests[i] != 0 )
+		{
+			total += m_TimeTests[i];
+			num_tests++;
+		}
+	}
+
+	if( num_tests > 0 )
+	{
+        m_AveLatency = total/num_tests;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+App::App( int flags )
+: m_Dispatcher( this ), m_node( this )
+{
+	m_network_metrics_task = new Tsk::Task< App > ( service_network_metrics, *this, 
+														 Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_PROCESS_NETWORK_METRICS );
+	m_receive_network_data_task = new Tsk::Task< App > ( read_network_data, *this, 
+														 Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_RECEIVE_NETWORK_DATA );
+	m_process_network_data_task = new Tsk::Task< App > ( process_network_data, *this,
+														 Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_PROCESS_NETWORK_DATA );
+	m_double_timestamp = 0;
+	m_flags = flags;
+	m_Timestamp = 0;
+	m_foreign_handler = NULL;
+#ifdef	__PLAT_NGPS__											  
+	Dbg_MsgAssert(Mem::SameContext(this,Mem::Manager::sHandle().NetworkHeap()),("Net::App not on network heap"));	
+#endif		//	__PLAT_NGPS__											  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+App::~App( void )
+{
+	
+	BannedConn* banned_conn, *next;
+	Lst::Search< BannedConn > sh;
+	
+	for( banned_conn = sh.FirstItem( m_banned_connections ); banned_conn;
+			banned_conn = next )
+	{
+		next = sh.NextItem();
+		delete banned_conn;
+	}
+	
+    
+	delete m_send_network_data_task;
+	delete m_receive_network_data_task;
+    delete m_process_network_data_task;
+	delete m_network_metrics_task;
+}
+
+/******************************************************************/
+/* Processes the messages in the input queue and also sequenced   */
+/* messages. Sends them off to the dispatcher                     */
+/******************************************************************/
+
+void	App::ProcessData( void )
+{
+	Conn *conn, *next_conn;
+	Lst::Search< Conn > sh;
+	int result;
+
+	for( conn = FirstConnection( &sh ); conn;
+			conn = next_conn )
+	{   
+		next_conn = NextConnection( &sh );
+
+		if( conn->IsLocal() &&
+			conn->m_alias_connection )
+		{   
+			int num_bytes;
+			
+			num_bytes = (int) conn->m_alias_connection->m_write_ptr - 
+							(int) conn->m_alias_connection->m_write_buffer;
+			memcpy( conn->m_read_ptr, conn->m_alias_connection->m_write_buffer, num_bytes );
+			conn->m_alias_connection->m_write_ptr = conn->m_alias_connection->m_write_buffer;
+			conn->m_read_ptr += num_bytes;
+		}
+
+		result = m_Dispatcher.DispatchMsgHandlers( conn, 0 );
+		if( result == HANDLER_ERROR )
+		{
+			// output that we've received mal-formatted data
+		}
+		else if( result == HANDLER_HALT )	// something terminable happened
+		{
+			return;
+		}
+		process_sequenced_messages( conn );
+	}		
+}
+
+/******************************************************************/
+/* Given an ip/port combo, return the corresponding connection    */
+/* if one exists                                                  */
+/******************************************************************/
+
+Conn *App::GetConnectionByAddress( int ip, unsigned short port )
+{
+	Lst::Search< Conn > sh;
+	Conn *conn;
+
+	for( conn = sh.FirstItem( m_connections ); 
+			conn; conn = sh.NextItem())
+	{
+		if(( conn->GetIP() == ip ) && ( conn->GetPort() == port ))
+		{
+			return conn;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/* Given a handle, return the connection if there is a match      */
+/*                                                                */
+/******************************************************************/
+
+Conn *App::GetConnectionByHandle( int handle )
+{
+	Lst::Search< Conn > sh;
+	Conn *conn;
+
+	for( conn = sh.FirstItem( m_connections ); 
+			conn; conn = sh.NextItem())
+	{
+		if( conn->GetHandle() == handle )
+		{
+			return conn;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			App::GetID( void )
+{
+	return m_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char		*App::GetName( void )
+{
+	return m_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tsk::BaseTask&	App::GetSendDataTask( void )
+{
+	return *m_send_network_data_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tsk::BaseTask&	App::GetReceiveDataTask( void )
+{
+	return *m_receive_network_data_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tsk::BaseTask&	App::GetProcessDataTask( void )
+{
+	return *m_process_network_data_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tsk::BaseTask&	App::GetNetworkMetricsTask( void )
+{
+	return *m_network_metrics_task;
+}
+
+/******************************************************************/
+/*  Create a new connection for an ip/port combo                  */
+/*                                                                */
+/******************************************************************/
+
+Conn *App::NewConnection( int ip, unsigned short port, int flags )
+{
+	Conn *conn;
+	
+	
+
+	conn = NULL;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	if( m_num_connections < m_max_connections )
+	{
+		conn = new Conn( flags );
+		conn->SetIP( ip );
+		conn->SetPort( port );
+		conn->m_handle = get_free_handle();
+		conn->m_app = this;
+		conn->m_last_comm_time = Tmr::GetTime();
+		m_connections.AddToTail( &conn->m_node );
+	}
+
+	Mem::Manager::sHandle().PopContext();
+
+	return conn;
+}
+
+/******************************************************************/
+/* Allocate a new sequenced message with a header                 */
+/*                                                                */
+/******************************************************************/
+
+QueuedMsgSeq*	App::AllocateNewSeqMessage( unsigned char msg_id, unsigned short msg_len, char* data, unsigned char group_id )
+{
+	QueuedMsgSeq* queued_msg;
+
+	queued_msg = new QueuedMsgSeq( msg_id, msg_len, data, group_id );
+
+	return queued_msg;
+}
+
+/******************************************************************/
+/* Build the outgoing stream to send to a connection.  This       */
+/* will also re-send important messages which haven't been acked  */
+/******************************************************************/
+
+bool App::BuildMsgStream( Conn *conn, QUEUE_TYPE queue, bool resends_only )
+{
+	bool all_fit;
+	unsigned short total_msg_len;
+
+	all_fit = true;
+
+	Dbg_Assert( conn );
+	// For each queue, copy all messages into what will be the final
+	// buffer to send over the net
+	switch( queue )
+	{
+		case QUEUE_DEFAULT:
+		{
+			Lst::Search< MsgLink > sh;
+			MsgLink *msg_link, *next_link;
+			QueuedMsg* queued_msg;
+						
+			for( msg_link = sh.FirstItem( conn->m_normal_msg_list ); msg_link; msg_link = next_link )
+			{   
+				int msg_header_size;
+
+				next_link = sh.NextItem();
+				if( m_Timestamp < msg_link->m_SendTime )
+				{
+					continue;
+				}
+
+				if( BandwidthExceeded( conn ))
+				{
+					break;
+				}
+
+				queued_msg = msg_link->m_QMsg;              
+				if( m_net_man->GetMessageFlags( queued_msg->m_MsgId ) & mMSG_SIZE_UNKNOWN )
+				{
+					msg_header_size = Manager::vMSG_HEADER_LENGTH;
+				}
+				else
+				{
+					msg_header_size = Manager::vMSG_HEADER_LENGTH_WITH_SIZE;
+				}
+				total_msg_len = msg_header_size + queued_msg->m_MsgLength;
+				if((( conn->m_write_ptr - conn->m_write_buffer ) + total_msg_len ) < Manager::vMAX_PAYLOAD )
+				{
+					memcpy( conn->m_write_ptr, &queued_msg->m_MsgId, sizeof( unsigned char ));
+					conn->m_write_ptr += sizeof( unsigned char );
+					// Optionally write out the message's size to the stream
+					if(( m_net_man->GetMessageFlags( queued_msg->m_MsgId ) & mMSG_SIZE_UNKNOWN ) == 0 )
+					{
+						memcpy( conn->m_write_ptr, &queued_msg->m_MsgLength, sizeof( unsigned short ));
+						conn->m_write_ptr += sizeof( unsigned short );
+					}
+					if( queued_msg->m_MsgLength > 0 )
+					{
+						memcpy( conn->m_write_ptr, queued_msg->m_Data, queued_msg->m_MsgLength );
+						conn->m_write_ptr += queued_msg->m_MsgLength;
+					}
+					conn->GetOutboundMetrics()->AddMessage( queued_msg->m_MsgId, queued_msg->m_MsgLength );
+					
+					// K: Moved this here so that multiple EnqueueMessage's can be done in one frame
+					// without earlier ones being dropped.
+					// Otherwise CScriptDebugger::SplitAndEnqueueMessage will not work, because
+					// only the last message enqueued will get through.
+					queued_msg->m_Ref.Release();
+					if( queued_msg->m_Ref.InUse() == false )
+					{   
+						delete queued_msg;
+					}
+					delete msg_link;
+				}
+				else
+				{
+					all_fit = false;
+				}
+			}
+
+			break;
+		}
+
+		case QUEUE_IMPORTANT:
+		{
+			MsgImpLink *msg_link;
+			Lst::Search< MsgImpLink > sh;
+			QueuedMsg* queued_msg;
+						
+			for( msg_link = sh.FirstItem( conn->m_important_msg_list ); msg_link; 
+					msg_link = sh.NextItem())
+			{
+				if( m_Timestamp < msg_link->m_SendTime )
+				{
+					continue;
+				}
+
+				if( BandwidthExceeded( conn ))
+				{
+					break;
+				}
+
+				if(( msg_link->m_Timestamp == 0 ) && resends_only )
+				{
+					continue;
+				}
+
+				// resend if it takes longer than roughly twice the latency
+				if( ( msg_link->m_Timestamp == 0 ) ||	// i.e. hasn't been sent yet
+					(( m_Timestamp - msg_link->m_Timestamp ) > (unsigned int) conn->GetResendThreshold()))
+				{
+					int msg_header_size;
+
+					queued_msg = msg_link->m_QMsg;
+#ifdef DEBUG_MESSAGES
+					if( conn->IsRemote())
+					{
+						if( msg_link->m_Timestamp != 0 )
+						{
+							Dbg_Printf( "** RESEND #%d (%d) Conn %d: (%d) %s T %d %d, P %d %d, L %d %d\n", 
+								msg_link->m_NumResends + 1,
+								m_FrameCounter,
+								conn->GetHandle(),
+	
+								queued_msg->m_MsgId,
+								m_net_man->GetMessageName( queued_msg->m_MsgId ),
+								
+								m_Timestamp,
+								msg_link->m_Timestamp,
+								
+								conn->m_latest_sent_packet_stamp,
+								msg_link->m_Packetstamp,
+								
+								conn->GetResendThreshold(),
+								conn->GetAveLatency());							
+						}
+					}
+#endif
+					if( m_net_man->GetMessageFlags( queued_msg->m_MsgId ) & mMSG_SIZE_UNKNOWN )
+					{
+						msg_header_size = Manager::vMSG_HEADER_LENGTH;
+					}
+					else
+					{
+						msg_header_size = Manager::vMSG_HEADER_LENGTH_WITH_SIZE;
+					}
+
+					total_msg_len = msg_header_size + queued_msg->m_MsgLength;
+					if((( conn->m_write_ptr - conn->m_write_buffer ) + total_msg_len ) < Manager::vMAX_PAYLOAD )
+					{
+						memcpy( conn->m_write_ptr, &queued_msg->m_MsgId, sizeof( unsigned char ));
+						conn->m_write_ptr += sizeof( unsigned char );
+						// Optionally write out the message's size to the stream
+						if(( m_net_man->GetMessageFlags( queued_msg->m_MsgId ) & mMSG_SIZE_UNKNOWN ) == 0 )
+						{
+							memcpy( conn->m_write_ptr, &queued_msg->m_MsgLength, sizeof( unsigned short ));
+							conn->m_write_ptr += sizeof( unsigned short );
+						}
+						
+						if( queued_msg->m_MsgLength > 0 )
+						{
+							memcpy( conn->m_write_ptr, queued_msg->m_Data, queued_msg->m_MsgLength );
+							conn->m_write_ptr += queued_msg->m_MsgLength;
+						}
+						conn->GetOutboundMetrics()->AddMessage( queued_msg->m_MsgId, queued_msg->m_MsgLength );
+						
+						if( msg_link->m_Timestamp != 0 )
+						{   
+							msg_link->m_NumResends++;							
+						}
+						msg_link->m_Timestamp = m_Timestamp;
+						msg_link->m_Packetstamp = conn->m_latest_sent_packet_stamp;
+					}					
+					else
+					{
+						all_fit = false;
+					}
+				}
+			}
+
+			break;
+		}
+
+		case QUEUE_SEQUENCED:
+		{
+			MsgSeqLink *msg_link;
+			Lst::Search< MsgSeqLink > sh;
+			QueuedMsgSeq* queued_msg;
+			unsigned char seq_msg_id;
+			unsigned short seq_msg_length;
+						
+			seq_msg_id = MSG_ID_SEQUENCED;
+			for( msg_link = sh.FirstItem( conn->m_sequenced_msg_list ); msg_link; 
+					msg_link = sh.NextItem())
+			{   
+				if( m_Timestamp < msg_link->m_SendTime )
+				{
+					continue;
+				}
+
+				if( BandwidthExceeded( conn ))
+				{
+					break;
+				}
+
+				if(( msg_link->m_Timestamp == 0 ) && resends_only )
+				{
+					continue;
+				}
+
+				// resend if it takes longer than roughly twice the latency
+				if( ( msg_link->m_Timestamp == 0 ) ||	// i.e. hasn't been sent yet
+					(( m_Timestamp - msg_link->m_Timestamp ) > (unsigned int) conn->GetResendThreshold()))
+				{
+					queued_msg = msg_link->m_QMsg;
+#ifdef DEBUG_MESSAGES
+					if( conn->IsRemote())
+					{
+						if( msg_link->m_Timestamp != 0 )
+						{
+							Dbg_Printf( "** RESEND #%d (%d) Conn %d: (%d) %s T %d %d, P %d %d, Seq: %d Group: %d\n", 
+								msg_link->m_NumResends + 1,
+								m_FrameCounter,
+								conn->GetHandle(),
+								
+								queued_msg->m_MsgId,
+								m_net_man->GetMessageName( queued_msg->m_MsgId ),
+								
+								m_Timestamp,
+								msg_link->m_Timestamp,
+								
+								conn->m_latest_sent_packet_stamp,
+								msg_link->m_Packetstamp,
+								
+								msg_link->m_SequenceId,
+								msg_link->m_GroupId );							
+						}						
+					}
+#endif
+					total_msg_len = Manager::vMSG_HEADER_LENGTH_WITH_SIZE + Manager::vMSG_SEQ_HEADER_LENGTH + queued_msg->m_MsgLength;
+					seq_msg_length = Manager::vMSG_SEQ_HEADER_LENGTH + queued_msg->m_MsgLength;
+					if((( conn->m_write_ptr - conn->m_write_buffer ) + total_msg_len ) < ( Manager::vMAX_PAYLOAD ))
+					{						
+						memcpy( conn->m_write_ptr, &seq_msg_id, sizeof( unsigned char ));
+						conn->m_write_ptr += sizeof( unsigned char );
+                        memcpy( conn->m_write_ptr, &seq_msg_length, sizeof( unsigned short ));
+						conn->m_write_ptr += sizeof( unsigned short );
+                        memcpy( conn->m_write_ptr, &msg_link->m_GroupId, sizeof( char ));
+						conn->m_write_ptr += sizeof( char );
+						memcpy( conn->m_write_ptr, &msg_link->m_SequenceId, sizeof( unsigned int ));
+						conn->m_write_ptr += sizeof( unsigned int );
+						memcpy( conn->m_write_ptr, &queued_msg->m_MsgId, sizeof( unsigned char ));
+						conn->m_write_ptr += sizeof( unsigned char );
+						if( queued_msg->m_MsgLength > 0 )
+						{
+							memcpy( conn->m_write_ptr, queued_msg->m_Data, queued_msg->m_MsgLength );
+							conn->m_write_ptr += queued_msg->m_MsgLength;
+						}
+						
+						conn->GetOutboundMetrics()->AddMessage( queued_msg->m_MsgId, queued_msg->m_MsgLength );
+						if( msg_link->m_Timestamp != 0 )
+						{
+							msg_link->m_NumResends++;
+						}
+						msg_link->m_Timestamp = m_Timestamp;
+						msg_link->m_Packetstamp = conn->m_latest_sent_packet_stamp;
+					}                   
+					else
+					{
+						all_fit = false;
+					}
+				}
+			}
+			break;
+		}
+	}
+
+	return all_fit;
+}
+
+/******************************************************************/
+/* Any pending messages to process from this client??     		  */
+/*                                                                */
+/******************************************************************/
+
+bool	App::MessagesToProcess( Conn* conn )
+{
+	if( conn->m_read_ptr > conn->m_read_buffer )
+	{
+		return true;
+	}
+		
+	return false;
+}
+
+/******************************************************************/
+/* Are there any important messages to send?					  */
+/*                                                                */
+/******************************************************************/
+
+bool	App::ImportantMessagesToSend( Conn* conn )
+{
+	{
+		MsgImpLink *msg_link;
+		Lst::Search< MsgImpLink > imp_sh;
+		for( msg_link = imp_sh.FirstItem( conn->m_important_msg_list ); 
+				msg_link; msg_link = imp_sh.NextItem())
+		{
+			// resend if it takes longer than roughly twice the latency
+			if(	( msg_link->m_Timestamp == 0 ) ||
+				(( m_Timestamp - msg_link->m_Timestamp ) > (unsigned int) conn->GetResendThreshold()))
+			{
+				return true;
+			}
+		}
+	}
+
+	{
+		MsgSeqLink *msg_link;
+		Lst::Search< MsgSeqLink > seq_sh;
+		for( msg_link = seq_sh.FirstItem( conn->m_sequenced_msg_list ); 
+				msg_link; msg_link = seq_sh.NextItem())
+		{
+			// resend if it takes longer than roughly twice the latency
+			if( ( msg_link->m_Timestamp == 0 ) || 
+				(( m_Timestamp - msg_link->m_Timestamp ) > (unsigned int) conn->GetResendThreshold()))
+			{
+				return true;		
+			}
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/* Are there any messages to send to a particular connection?     */
+/*                                                                */
+/******************************************************************/
+
+bool	App::MessagesToSend( Conn* conn )
+{   
+	{
+		Lst::Search< MsgLink > sh;
+		MsgLink* msg_link;
+		for( msg_link = sh.FirstItem( conn->m_normal_msg_list ); 
+				msg_link; msg_link = sh.NextItem())
+		{
+			if( m_Timestamp < msg_link->m_SendTime )
+			{
+				continue;
+			}
+			return true;
+		}
+	}
+
+	{
+		MsgImpLink *msg_link;
+		Lst::Search< MsgImpLink > imp_sh;
+		for( msg_link = imp_sh.FirstItem( conn->m_important_msg_list ); 
+				msg_link; msg_link = imp_sh.NextItem())
+		{
+			// resend if it takes longer than roughly twice the latency
+			if( ( msg_link->m_Timestamp == 0 ) ||
+				( m_Timestamp < msg_link->m_SendTime ) ||
+				(( m_Timestamp - msg_link->m_Timestamp ) > (unsigned int) conn->GetResendThreshold()))
+			{
+				return true;
+			}
+		}
+	}
+
+	{
+		MsgSeqLink *msg_link;
+		Lst::Search< MsgSeqLink > seq_sh;
+		for( msg_link = seq_sh.FirstItem( conn->m_sequenced_msg_list ); 
+				msg_link; msg_link = seq_sh.NextItem())
+		{
+			// resend if it takes longer than roughly twice the latency
+			if(	( msg_link->m_Timestamp == 0 ) || 
+				( m_Timestamp < msg_link->m_SendTime ) ||
+				(( m_Timestamp - msg_link->m_Timestamp ) > (unsigned int) conn->GetResendThreshold()))
+			{
+				return true;		
+			}
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/* Dequeue Messages of type    							  */
+/*                                                                */
+/******************************************************************/
+
+void App::DequeueMessagesByType( Net::Conn* conn, QUEUE_TYPE queue, unsigned char msg_id )
+{
+	
+
+    switch( queue )
+	{
+		case QUEUE_DEFAULT:
+		{
+			Lst::Search< MsgLink > sh;
+			MsgLink *msg_link, *next;
+			QueuedMsg *msg;
+
+			for( msg_link = sh.FirstItem( conn->m_normal_msg_list ); msg_link;
+					msg_link = next )
+			{
+				next = sh.NextItem();
+				msg = msg_link->m_QMsg;
+				if( msg->m_MsgId == msg_id )
+				{
+					msg->m_Ref.Release();
+					if( msg->m_Ref.InUse() == false )
+					{
+						delete msg;
+					}
+					delete msg_link;
+				}
+			}
+			
+			break;
+		}
+
+		case QUEUE_IMPORTANT:
+		{
+			Lst::Search< MsgImpLink > sh;
+			MsgImpLink *msg_link, *next;
+			QueuedMsg *msg;
+
+			for( msg_link = sh.FirstItem( conn->m_important_msg_list ); msg_link;
+					msg_link = next )
+			{
+				next = sh.NextItem();
+				msg = msg_link->m_QMsg;
+				if( msg->m_MsgId == msg_id )
+				{
+					msg->m_Ref.Release();
+					if( msg->m_Ref.InUse() == false )
+					{
+						delete msg;
+					}
+					delete msg_link;
+				}
+			}
+			break;
+		}
+
+		case QUEUE_SEQUENCED:
+		{
+			Lst::Search< MsgSeqLink > sh;
+			MsgSeqLink *msg_link, *next;
+			QueuedMsgSeq *msg;
+
+			for( msg_link = sh.FirstItem( conn->m_sequenced_msg_list ); msg_link;
+					msg_link = next )
+			{
+				next = sh.NextItem();
+				msg = msg_link->m_QMsg;
+				if( msg->m_MsgId == msg_id )
+				{
+					msg->m_Ref.Release();
+					if( msg->m_Ref.InUse() == false )
+					{
+						delete msg;
+					}
+					delete msg_link;
+				}
+			}
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/* Enqueue a new message to be sent to a connection (by handle)   */
+/*                                                                */
+/******************************************************************/
+
+void App::EnqueueMessage( int handle, MsgDesc* desc )
+{
+	Conn *conn;
+	Lst::Search< Conn > sh;
+
+#ifdef __PLAT_NGPS__
+	WaitSema( m_send_semaphore_id );
+#endif
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+	// Make sure the message + headers isn't larger than a packet or else it will never be sent
+	Dbg_MsgAssert( desc->m_Length < ( Manager::vMAX_PAYLOAD - 64 ), ("Network Message size (%d) is bigger than %d",desc->m_Length, Manager::vMAX_PAYLOAD - 64) );
+	
+	// Add new message to the appropriate queue	
+	switch( desc->m_Queue )
+	{
+		case QUEUE_DEFAULT:
+		{
+			MsgLink *msg_link;
+			QueuedMsg *queued_msg;
+			
+			queued_msg = new QueuedMsg( desc->m_Id, desc->m_Length, desc->m_Data );
+			if( handle == HANDLE_ID_BROADCAST )
+			{	
+				for( conn = sh.FirstItem( m_connections ); conn; conn = sh.NextItem())
+				{   // Only enqueue the message if the connection is ready and valid
+					if( conn->GetStatus() == Conn::mSTATUS_READY )
+					{
+						if( desc->m_Singular )
+						{
+							DequeueMessagesByType( conn, desc->m_Queue, desc->m_Id );
+						}
+
+						queued_msg->m_Ref.Acquire();
+						msg_link = new MsgLink( queued_msg );
+						msg_link->SetPri( desc->m_Priority );
+						msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+						conn->m_normal_msg_list.AddNodeFromTail( msg_link );
+					}
+				}
+			}
+			else if( handle & HANDLE_ID_EXCLUDE_BROADCAST )
+			{
+				int excluded_handle;
+
+				excluded_handle = handle & ~HANDLE_ID_EXCLUDE_BROADCAST;
+				for( conn = sh.FirstItem( m_connections ); conn; conn = sh.NextItem())
+				{
+					// Broadcast to all but one handle
+					if( conn->GetHandle() == excluded_handle )
+					{
+						continue;
+					}
+					// Only enqueue the message if the connection is ready and valid
+					if( conn->GetStatus() == Conn::mSTATUS_READY )
+					{
+						if( desc->m_Singular )
+						{
+							DequeueMessagesByType( conn, desc->m_Queue, desc->m_Id );
+						}
+						queued_msg->m_Ref.Acquire();
+						msg_link = new MsgLink( queued_msg );
+						msg_link->SetPri( desc->m_Priority );
+						msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+						conn->m_normal_msg_list.AddNodeFromTail( msg_link );
+					}
+				}
+			}
+			else
+			{
+				conn = this->GetConnectionByHandle( handle );
+				Dbg_Assert( conn );
+
+				// Only enqueue the message if the connection is ready and valid
+				if(	( conn->GetStatus() == Conn::mSTATUS_READY ) ||
+					( desc->m_Id == MSG_ID_TIMESTAMP ))
+				{
+					if( desc->m_Singular )
+					{
+						DequeueMessagesByType( conn, desc->m_Queue, desc->m_Id );
+					}
+					queued_msg->m_Ref.Acquire();
+					msg_link = new MsgLink( queued_msg );
+					msg_link->SetPri( desc->m_Priority );
+					msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+					conn->m_normal_msg_list.AddNodeFromTail( msg_link );
+				}
+			}
+			// If it didn't actually get enqueued, delete it
+			if( queued_msg->m_Ref.InUse() == false )
+			{
+				delete queued_msg;
+			}
+			break;
+		}
+
+		case QUEUE_IMPORTANT:
+		{
+			MsgImpLink* msg_link;
+			QueuedMsg* queued_msg;
+			
+			queued_msg = new QueuedMsg( desc->m_Id, desc->m_Length, desc->m_Data );
+			if( handle == HANDLE_ID_BROADCAST )
+			{	
+				for( conn = sh.FirstItem( m_connections ); conn; conn = sh.NextItem())
+				{
+					if( !( conn->TestStatus( Conn::mSTATUS_DISCONNECTING )))
+					{
+						if( desc->m_Singular )
+						{
+							DequeueMessagesByType( conn, desc->m_Queue, desc->m_Id );
+						}
+						queued_msg->m_Ref.Acquire();
+						msg_link = new MsgImpLink( queued_msg );
+						msg_link->SetPri( desc->m_Priority );
+						msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+						msg_link->m_Timestamp = 0;
+						conn->m_important_msg_list.AddNodeFromTail( msg_link ); 
+					}
+				}
+			}
+			else if( handle & HANDLE_ID_EXCLUDE_BROADCAST )
+			{
+				int excluded_handle;
+
+				excluded_handle = handle & ~HANDLE_ID_EXCLUDE_BROADCAST;
+				for( conn = sh.FirstItem( m_connections ); conn; conn = sh.NextItem())
+				{
+					// Broadcast to all but one handle
+					if( conn->GetHandle() == excluded_handle )
+					{
+						continue;
+					}
+					if( !( conn->TestStatus( Conn::mSTATUS_DISCONNECTING )))
+					{
+						if( desc->m_Singular )
+						{
+							DequeueMessagesByType( conn, desc->m_Queue, desc->m_Id );
+						}
+						queued_msg->m_Ref.Acquire();
+						msg_link = new MsgImpLink( queued_msg );
+						msg_link->SetPri( desc->m_Priority );
+						msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+						msg_link->m_Timestamp = 0;
+						conn->m_important_msg_list.AddNodeFromTail( msg_link ); 
+					}
+				}
+			}
+			else
+			{
+				conn = this->GetConnectionByHandle( handle );
+				Dbg_Assert( conn );
+			
+				if( !( conn->TestStatus( Conn::mSTATUS_DISCONNECTING )))
+				{
+					if( desc->m_Singular )
+					{
+						DequeueMessagesByType( conn, desc->m_Queue, desc->m_Id );
+					}
+					queued_msg->m_Ref.Acquire();
+					msg_link = new MsgImpLink( queued_msg );
+					msg_link->SetPri( desc->m_Priority );
+					msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+					msg_link->m_Timestamp = 0;
+					conn->m_important_msg_list.AddNodeFromTail( msg_link ); 
+				}
+			}
+			// If it didn't actually get enqueued, delete it
+			if( queued_msg->m_Ref.InUse() == false )
+			{
+				delete queued_msg;
+			}
+			break;
+		}
+
+		case QUEUE_SEQUENCED:
+		{
+			MsgSeqLink *msg_link;
+			QueuedMsgSeq* queued_msg;
+			
+			queued_msg = AllocateNewSeqMessage( desc->m_Id, desc->m_Length, (char *) desc->m_Data, desc->m_GroupId );
+						
+			if( handle == HANDLE_ID_BROADCAST )
+			{	
+				for( conn = sh.FirstItem( m_connections ); conn; conn = sh.NextItem())
+				{
+					if( !( conn->TestStatus( Conn::mSTATUS_DISCONNECTING )))
+					{
+						Dbg_Assert( desc->m_Singular == false );
+						
+						queued_msg->m_Ref.Acquire();
+						msg_link = new MsgSeqLink( queued_msg );
+						msg_link->SetPri( desc->m_Queue );
+						msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+						msg_link->m_Timestamp = 0;
+						msg_link->m_GroupId = desc->m_GroupId;
+						if( desc->m_ForcedSequenceId > 0 )
+						{
+							msg_link->m_SequenceId = desc->m_ForcedSequenceId;
+						}
+						else
+						{
+							msg_link->m_SequenceId = conn->m_SequenceId[desc->m_GroupId]++;
+						}
+						
+						msg_link->m_StreamMessage = desc->m_StreamMessage;
+						conn->m_sequenced_msg_list.AddNodeFromTail( msg_link );
+					}
+				}
+			}
+			else if( handle & HANDLE_ID_EXCLUDE_BROADCAST )
+			{
+				int excluded_handle;
+
+				excluded_handle = handle & ~HANDLE_ID_EXCLUDE_BROADCAST;
+				for( conn = sh.FirstItem( m_connections ); conn; conn = sh.NextItem())
+				{
+					// Broadcast to all but one handle
+					if( conn->GetHandle() == excluded_handle )
+					{
+						continue;
+					}
+					if( !( conn->TestStatus( Conn::mSTATUS_DISCONNECTING )))
+					{
+						Dbg_Assert( desc->m_Singular == false );
+
+						queued_msg->m_Ref.Acquire();
+						msg_link = new MsgSeqLink( queued_msg );
+						msg_link->SetPri( desc->m_Priority );
+						msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+						msg_link->m_Timestamp = 0;
+						msg_link->m_GroupId = desc->m_GroupId;
+						if( desc->m_ForcedSequenceId > 0 )
+						{
+							msg_link->m_SequenceId = desc->m_ForcedSequenceId;
+						}
+						else
+						{
+							msg_link->m_SequenceId = conn->m_SequenceId[desc->m_GroupId]++;
+						}
+						msg_link->m_StreamMessage = desc->m_StreamMessage;
+						conn->m_sequenced_msg_list.AddNodeFromTail( msg_link );
+					}
+				}
+			}
+			else
+			{
+				conn = GetConnectionByHandle( handle );
+				Dbg_Assert( conn );
+
+				if( !( conn->TestStatus( Conn::mSTATUS_DISCONNECTING )))
+				{
+					Dbg_Assert( desc->m_Singular == false );
+
+					queued_msg->m_Ref.Acquire();
+					msg_link = new MsgSeqLink( queued_msg );
+					msg_link->SetPri( desc->m_Priority );
+					msg_link->m_SendTime = m_Timestamp + desc->m_Delay;
+					msg_link->m_Timestamp = 0;
+					msg_link->m_GroupId = desc->m_GroupId;
+					if( desc->m_ForcedSequenceId > 0 )
+					{
+						msg_link->m_SequenceId = desc->m_ForcedSequenceId;
+						//Dbg_Printf( "Sending stream data sequence %d\n", msg_link->m_SequenceId );
+					}
+					else
+					{
+						msg_link->m_SequenceId = conn->m_SequenceId[desc->m_GroupId]++;
+						//Dbg_Printf( "Sending stream start sequence %d\n", msg_link->m_SequenceId );
+					}
+					msg_link->m_StreamMessage = desc->m_StreamMessage;
+					conn->m_sequenced_msg_list.AddNodeFromTail( msg_link );
+				}
+			}
+			// If it didn't actually get enqueued, delete it
+			if( queued_msg->m_Ref.InUse() == false )
+			{
+				delete queued_msg;
+			}
+			break;
+		}
+
+		default:
+			Dbg_Assert( 0 );		// unsupported queue type
+			break;
+
+	}
+	
+	Mem::Manager::sHandle().PopContext();
+
+#ifdef __PLAT_NGPS__
+	SignalSema( m_send_semaphore_id );
+#endif
+}
+
+/******************************************************************/
+/* Stream a new message, given a connection instead of a handle	  */
+/*                                              				  */
+/******************************************************************/
+
+void	App::StreamMessageToConn( Net::Conn* conn, unsigned char msg_id, unsigned short msg_len, void* data, char* desc, unsigned char group_id,
+								  bool all_at_once, bool send_in_place )
+{
+	char* data_ptr;
+	int size, packet_len;
+	MsgStreamStart start_msg;
+	MsgStreamData data_msg;
+	MsgDesc msg_desc;
+	StreamDesc* stream_desc;
+	StreamLink* stream_link;
+
+	Dbg_Assert( desc );
+	Dbg_Assert( conn );
+
+    // Also, for now, just split the data up into chunks no larger than MAX_STREAM_CHUNK_LENGTH
+	// but eventually, maybe let the user decide what that chunk size should be.
+
+    start_msg.m_Size = msg_len;
+	start_msg.m_StreamId = conn->m_NextStreamId;
+	start_msg.m_MsgId = msg_id;
+	start_msg.m_Checksum = Crc::GenerateCRCCaseSensitive((char*) data, msg_len );
+	strcpy( start_msg.m_StreamDesc, desc );
+	size = 0;
+
+	memcpy( start_msg.m_Data, data, ( msg_len <= MAX_STREAM_CHUNK_LENGTH ) ? msg_len : MAX_STREAM_CHUNK_LENGTH );
+	size += ( msg_len <= MAX_STREAM_CHUNK_LENGTH ) ? msg_len : MAX_STREAM_CHUNK_LENGTH;
+	packet_len = ( sizeof( start_msg ) - MAX_STREAM_CHUNK_LENGTH ) + size;
+
+	msg_desc.m_Id = MSG_ID_STREAM_START;
+	msg_desc.m_Length = packet_len;
+	msg_desc.m_Data = &start_msg;
+	msg_desc.m_Queue = QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = group_id;
+	msg_desc.m_StreamMessage = 1;
+
+	//Dbg_Printf( "******** [%d] Streaming %d bytes\n", conn->m_NextStreamId, packet_len );
+    EnqueueMessage( conn->GetHandle(), &msg_desc );
+
+	if( msg_len > MAX_STREAM_CHUNK_LENGTH )
+	{
+		if( all_at_once )
+		{
+			data_ptr = (char*) data + size;
+			while( size < msg_len )
+			{
+				packet_len = msg_len - size;
+				if( packet_len > MAX_STREAM_CHUNK_LENGTH )
+				{
+					packet_len = MAX_STREAM_CHUNK_LENGTH;
+				}
+		
+				data_msg.m_StreamId = conn->m_NextStreamId;
+				memcpy( data_msg.m_Data, data_ptr, packet_len );
+				
+				msg_desc.m_Id = MSG_ID_STREAM_DATA;
+				msg_desc.m_Data = &data_msg;
+				msg_desc.m_Length = packet_len + sizeof( int );
+				msg_desc.m_Queue = QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = group_id;
+				msg_desc.m_StreamMessage = 1;
+		
+				//Dbg_Printf( "%d ******** [%d] Streaming %d bytes.\n", m_FrameCounter, conn->m_NextStreamId, packet_len );
+				EnqueueMessage( conn->GetHandle(), &msg_desc );
+		
+				data_ptr += packet_len;
+				size += packet_len;
+			}
+		}
+		else
+		{
+			// If this message is larger than one chunk, add it to our list of out-bound streams so that we can
+			// send it to the connection with a sliding window
+			int num_extra_msgs;
+			char* send_data;
+	
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+	
+			num_extra_msgs = ( msg_len - 1 ) / MAX_STREAM_CHUNK_LENGTH;
+			
+			if( send_in_place )
+			{
+				send_data = (char*) data;
+			}
+			else
+			{
+				send_data = new char[msg_len];
+				memcpy( send_data, data, msg_len );
+			}
+				
+			data_ptr = (char*) send_data + size;
+	
+			stream_desc = new StreamDesc;
+			stream_link = new StreamLink( stream_desc );
+		
+			stream_desc->m_StreamId = conn->m_NextStreamId;
+			stream_desc->m_Size = msg_len;
+			stream_desc->m_MsgId = msg_id;
+			strcpy( stream_desc->m_StreamDesc, desc );
+			stream_desc->m_Data = (char*) send_data;
+			stream_desc->m_DataPtr = data_ptr;
+			stream_desc->m_GroupId = group_id;
+			stream_desc->m_SequenceId = conn->m_SequenceId[group_id];
+			stream_desc->m_SendInPlace = send_in_place;
+	
+			conn->m_StreamOutList.AddToTail( stream_link );
+	
+			//Dbg_Printf( "******** [%d] Stream rest (%d bytes, %d chunks) later....\n", conn->m_NextStreamId, msg_len - size, num_extra_msgs );
+	
+			conn->m_SequenceId[group_id] += num_extra_msgs;
+			
+			Mem::Manager::sHandle().PopContext();
+		}
+	}
+
+	conn->m_NextStreamId++;
+}
+
+/******************************************************************/
+/* Stream a new message to send to the connection.				  */
+/*                                              				  */
+/******************************************************************/
+
+void	App::StreamMessage( int handle, unsigned char msg_id, unsigned short msg_len, void* data, char* desc, unsigned char group_id,
+							bool all_at_once, bool send_in_place )
+{
+	Lst::Search< Conn > sh;
+	Conn* conn;
+
+	Dbg_Assert( desc );
+	
+	if( handle == HANDLE_ID_BROADCAST )
+	{	
+		for( conn = sh.FirstItem( m_connections ); conn; conn = sh.NextItem())
+		{
+			if( conn->IsValid())
+			{
+				StreamMessageToConn( conn, msg_id, msg_len, data, desc, group_id, all_at_once, send_in_place );
+			}
+		}
+	}
+	else
+	{
+		conn = GetConnectionByHandle( handle );
+		Dbg_Assert( conn );
+
+		if( conn->IsValid())
+		{
+			StreamMessageToConn( conn, msg_id, msg_len, data, desc, group_id, all_at_once, send_in_place );
+		}
+	}
+}
+
+/******************************************************************/
+/* Stream a new message to send to the server. Just a shortcut	  */
+/* to EnqueueMessage                                              */
+/******************************************************************/
+
+void	App::StreamMessageToServer( unsigned char msg_id, unsigned short msg_len, void* data, char* desc, unsigned char group_id,
+									bool all_at_once, bool send_in_place )
+{
+	Conn *conn;
+	Lst::Search< Conn > sh;
+
+	if(( conn = FirstConnection( &sh )))
+	{
+		StreamMessageToConn( conn, msg_id, msg_len, data, desc, group_id, all_at_once, send_in_place );
+	}
+}
+
+/******************************************************************/
+/* Enqueue a new message to send to the server. Just a shortcut   */
+/* to EnqueueMessage                                              */
+/******************************************************************/
+
+void	App::EnqueueMessageToServer( MsgDesc* desc )
+{
+	Conn *conn;
+	Lst::Search< Conn > sh;
+
+	
+
+	if(( conn = FirstConnection( &sh )))
+	{
+		EnqueueMessage( conn->GetHandle(), desc );
+	}
+}
+
+/******************************************************************/
+/* Free the message queue for a given connection                  */
+/*                                                                */
+/******************************************************************/
+
+void	App::FreeConnMessageQueue( Conn *conn, QUEUE_TYPE queue )
+{
+		
+	
+
+	Dbg_Assert( conn );
+
+	switch( queue )
+	{
+		case QUEUE_DEFAULT:
+		{
+			Lst::Search< MsgLink > sh;
+			MsgLink *msg_link, *next;
+			QueuedMsg *msg;
+
+			for( msg_link = sh.FirstItem( conn->m_normal_msg_list ); msg_link;
+					msg_link = next )
+			{
+				next = sh.NextItem();
+				msg = msg_link->m_QMsg;
+				msg->m_Ref.Release();
+				if( msg->m_Ref.InUse() == false )
+				{
+					delete msg;
+				}
+				delete msg_link;
+
+			}
+			
+			break;
+		}
+
+		case QUEUE_IMPORTANT:
+		{
+			Lst::Search< MsgImpLink > sh;
+			MsgImpLink *msg_link, *next;
+			QueuedMsg *msg;
+
+			for( msg_link = sh.FirstItem( conn->m_important_msg_list ); msg_link;
+					msg_link = next )
+			{
+				next = sh.NextItem();
+				msg = msg_link->m_QMsg;
+				msg->m_Ref.Release();
+				if( msg->m_Ref.InUse() == false )
+				{
+					delete msg;
+				}
+				delete msg_link;
+
+			}
+			break;
+		}
+
+		case QUEUE_SEQUENCED:
+		{
+			Lst::Search< MsgSeqLink > sh;
+			MsgSeqLink *msg_link, *next;
+			QueuedMsgSeq *msg;
+
+			for( msg_link = sh.FirstItem( conn->m_sequenced_msg_list ); msg_link;
+					msg_link = next )
+			{
+				next = sh.NextItem();
+				msg = msg_link->m_QMsg;
+				msg->m_Ref.Release();
+				if( msg->m_Ref.InUse() == false )
+				{
+					delete msg;
+				}
+				delete msg_link;
+
+			}
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/* Free message queues of a certain type from all connections     */
+/*                                                                */
+/******************************************************************/
+
+void	App::FreeMessageQueue( QUEUE_TYPE queue )
+{
+	Conn *conn;
+	Lst::Search< Conn > sh;
+
+	for( conn = FirstConnection( &sh ); conn; conn = NextConnection( &sh ))
+	{
+		FreeConnMessageQueue( conn, queue );
+	}
+}
+
+/******************************************************************/
+/* Medium-level send function.  Works as the message level		  */
+/*                                                                */
+/******************************************************************/
+
+bool	App::SendMessageTo( unsigned char msg_id, unsigned short msg_len, void* data,
+							int ip, unsigned short port, int flags )
+{
+	char	msg_data[Manager::vMAX_PACKET_SIZE];
+	bool result;
+
+	msg_data[0] = msg_id;
+	memcpy( &msg_data[1], &msg_len, sizeof( unsigned short ));
+	if( msg_len > 0 )
+	{
+		memcpy( &msg_data[3], data, msg_len ); 
+	}
+    
+#ifdef __PLAT_NGPS__	
+	WaitSema( m_send_semaphore_id );
+#endif // __PLAT_NGPS__
+	
+	result = SendTo( ip, port, msg_data, msg_len + Manager::vMSG_HEADER_LENGTH_WITH_SIZE, flags );
+
+#ifdef __PLAT_NGPS__    
+	SignalSema( m_send_semaphore_id );
+#endif // __PLAT_NGPS__
+	
+	return result;
+}
+
+/******************************************************************/
+/* Lower level, pre-connection network send function              */
+/*                                                                */
+/******************************************************************/
+
+bool	App::SendTo( int ip, unsigned short port, char *data, int len, int flags )
+{
+	struct sockaddr_in	to_address;
+#ifdef __PLAT_NGC__
+    to_address.len = sizeof( sockaddr );
+#else
+	int addr_len = sizeof(to_address);
+#endif
+	int	result = 0;
+	int send_len;
+	
+	
+
+	Dbg_Assert( data );
+	Dbg_Assert( len <= Manager::vMAX_PAYLOAD );
+
+	// Send data immediately to a destination socket
+	memset( &to_address, 0, sizeof(to_address));
+	to_address.sin_family = AF_INET;
+	to_address.sin_port = htons( port );
+		
+	// use ip = INADDR_BROADCAST to broadcast
+	to_address.sin_addr.s_addr = ip;
+	if( m_flags & mSECURE )
+	{
+		crc_and_copy_stream( data, m_out_packet_buffer, len, &send_len );
+		result = sendto( m_socket, m_out_packet_buffer, send_len, flags, 
+				(struct sockaddr *) &(to_address), addr_len );
+	}
+	else
+	{
+		result = sendto( m_socket, data, len, flags, (struct sockaddr *) &(to_address), addr_len );
+	}
+	
+#ifndef __PLAT_NGC__
+	if(	result == SOCKET_ERROR )
+	{
+		int err;
+#if defined( __PLAT_WN32__ ) || defined( __PLAT_XBOX__ )
+		err = WSAGetLastError();
+		if(	( err != WSAEWOULDBLOCK ) &&
+			( err != WSAEINPROGRESS ))
+#else
+#ifdef __PLAT_NGPS__
+		err = sn_errno( m_socket );
+		if( ( err != EWOULDBLOCK ) &&
+			( err != EINPROGRESS ))
+#endif
+#endif
+		{
+#ifdef DEBUG_MESSAGES
+		Dbg_Printf( "Sendto Error: packet length %d\n", len );
+			ReportError();
+#endif
+			return false;
+		}
+	}
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/* Lower level, pre-(app-level) connection network send function  */
+/*                                                                */
+/******************************************************************/
+
+bool	App::Send( char *data, int len, int flags )
+{
+	int	result = 0;
+	int send_len;
+		
+	
+
+	Dbg_Assert( data );
+	
+	if( m_connected == false )
+	{
+		return false;
+	}
+
+	Dbg_Assert( len <= Manager::vMAX_PAYLOAD );
+
+	// Send data immediately to a destination socket
+	if( m_flags & mSECURE )
+	{
+		crc_and_copy_stream( data, m_out_packet_buffer, len, &send_len );
+		result = send( m_socket, m_out_packet_buffer, send_len, flags );
+	}
+	else
+	{
+		result = send( m_socket, data, len, flags );
+	}
+
+	if( result == SOCKET_ERROR )
+	{
+#ifdef DEBUG_MESSAGES
+		Dbg_Printf( "Send Error: packet length %d\n", len );
+		ReportError();
+#endif
+		return false;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Conn	*App::FirstConnection( Lst::Search< Conn > *sh )
+{
+	
+
+	Dbg_Assert( sh );
+
+	return( sh->FirstItem( m_connections ));	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Conn	*App::NextConnection( Lst::Search< Conn > *sh )
+{
+	
+
+	Dbg_Assert( sh );
+
+	return( sh->NextItem());
+}
+
+/******************************************************************/
+/* Immediately terminate connection.  Don't use this in msg		  */
+/* handlers. Instead, invalidate the connection only              */
+/******************************************************************/
+
+void	App::TerminateConnection( Conn* conn )
+{
+	
+	
+	Dbg_Assert( conn );
+
+	struct in_addr address;
+
+	address.s_addr = conn->GetIP();
+	conn->m_node.Remove();
+	delete conn;
+}
+
+/******************************************************************/
+/* Report the last error on the socket                            */
+/*                                                                */
+/******************************************************************/
+
+void		App::ReportError( void )
+{
+	
+
+#ifdef WIN32
+	char msg[1024];
+
+	//Dbg_Printf( "%s: Error %d\n", m_name, WSAGetLastError());
+	sprintf( msg, "%s: Error %d\n", m_name, WSAGetLastError());
+	OutputDebugString( msg );
+#else
+#ifdef __PLAT_NGPS__
+#ifdef DEBUG_MESSAGES
+	Dbg_Printf( "(%d) %s: Error %d\n", m_FrameCounter, m_name, sn_errno( m_socket ));
+#endif
+#endif
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager*	App::GetManager( void )
+{
+	return m_net_man;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		App::SendEnqueuedMessages( void )
+{
+	Conn *conn;
+	Lst::Search< Conn > sh;
+		
+	
+    
+#ifdef __PLAT_NGPS__
+    WaitSema( m_send_semaphore_id );
+#endif	// __PLAT_NGPS__    
+	for( conn = FirstConnection( &sh );
+			conn; conn = NextConnection( &sh ))
+	{
+		SendEnqueuedMessages( conn );
+	}
+	
+#ifdef __PLAT_NGPS__
+	SignalSema( m_send_semaphore_id );
+#endif	// __PLAT_NGPS__
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGPS__
+
+void		App::TransferData( void )
+{
+	while( 1 )
+	{
+		// Receive data
+		SleepThread();
+		if( m_shutting_down )
+		{
+			break;
+		}
+		WaitSema( m_transfer_semaphore_id );
+		ReceiveData();
+		SignalSema( m_transfer_semaphore_id );
+		
+		// Now wait until that data has been processed and it is time to send new data
+		// outbound
+		SleepThread();
+
+		// Send Data
+		if( m_shutting_down )
+		{
+			break;
+		}
+		WaitSema( m_transfer_semaphore_id );
+		SendEnqueuedMessages();
+		SignalSema( m_transfer_semaphore_id );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		App::WaitForAsyncCallsToFinish( void )
+{
+	
+    
+	WaitForTransferSemaphore();
+    SignalTransferSemaphore(); 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		App::WaitForTransferSemaphore( void )
+{   
+	WaitSema( m_transfer_semaphore_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		App::SignalTransferSemaphore( void )
+{   
+	SignalSema( m_transfer_semaphore_id );
+}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		App::SendData( bool scheduled_send )
+{
+	
+	Lst::Search< Conn > sh;
+	Conn* conn;
+
+	// If someone is requesting a send of all curretly enqueued data before its time
+	// we need to tell each connection to ignore the logic to send every N frames and rather
+	// send immediately
+	if( scheduled_send == false )
+	{   
+		m_Timestamp++;	// This will ensure that the timestamp is unique. Otherwise
+						// the recipient will think it's a dupe packet
+		for( conn = FirstConnection( &sh ); conn; conn = NextConnection( &sh ))
+		{
+			conn->SetForceSendThisFrame( true );
+		}
+	}
+
+#ifdef __PLAT_NGPS__
+	if( !IsLocal() && m_socket_thread_active )
+	{   
+		WaitSema( m_transfer_semaphore_id );
+	}
+#endif
+
+	SendEnqueuedMessages();
+
+#ifdef __PLAT_NGPS__
+	if( !IsLocal() && m_socket_thread_active )
+	{   
+		SignalSema( m_transfer_semaphore_id );
+	}
+#endif	// __PLAT_NGPS_
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		App::ShouldSendThisFrame( Conn* conn )
+{
+	if( (( m_Timestamp - conn->m_last_send_time ) >= (unsigned int) conn->GetSendInterval()) ||
+		( conn->GetForceSendThisFrame() ))
+		
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	App::ShutDown( void )
+{
+	
+#ifdef DEBUG_MESSAGES
+	Dbg_Printf( "Shutting NetApp %s Down\n", GetName());
+#endif
+
+#ifdef __PLAT_NGPS__
+	m_shutting_down = true;
+
+	//if( m_socket_thread_active )
+	{   
+		WakeupThread( m_socket_thread_id );
+	}
+#ifdef DEBUG_MESSAGES	
+	Dbg_Printf( "Waiting for active semaphore\n" );
+#endif
+	WaitSema( m_active_semaphore_id );
+
+#ifdef DEBUG_MESSAGES
+    Dbg_Printf( "Deleting semaphores\n" );
+#endif
+	DeleteSema( m_send_semaphore_id );
+	DeleteSema( m_receive_semaphore_id );
+	DeleteSema( m_transfer_semaphore_id );
+	DeleteSema( m_active_semaphore_id );
+	
+#ifdef DEBUG_MESSAGES
+	Dbg_Printf( "Deleting socket thread\n" );
+#endif
+	DeleteThread( m_socket_thread_id );
+    
+#endif
+    
+    deinit();
+#ifdef DEBUG_MESSAGES
+	Dbg_Printf( "NetApp %s successfully shut down\n", GetName());
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	App::AliasConnections( Conn* server_conn, Conn* client_conn )
+{
+	
+
+	Dbg_Assert( server_conn->IsLocal() && client_conn->IsLocal());
+	server_conn->m_alias_connection = client_conn;
+	client_conn->m_alias_connection = server_conn;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	App::IsLocal( void )
+{
+	return m_flags.TestMask( mLOCAL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	App::AcceptForeignConnections( bool accept )
+{
+	if( accept )
+	{
+		m_flags.SetMask( mACCEPT_FOREIGN_CONN );
+	}
+	else
+	{
+		m_flags.ClearMask( mACCEPT_FOREIGN_CONN );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	App::AcceptsForeignConnections( void )
+{
+	return m_flags.TestMask( mACCEPT_FOREIGN_CONN );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SOCKET	App::GetSocket( void )
+{
+	return m_socket;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	App::SetForeignPacketHandler( ForeignPacketHandlerCode* code )
+{
+	m_foreign_handler = code;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	App::BanConnection( Conn* conn )
+{
+	BannedConn* banned_conn;
+
+	banned_conn = new BannedConn;
+
+	banned_conn->m_Ip = conn->GetIP();
+	m_banned_connections.AddToTail( banned_conn );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	App::IsConnectionBanned( Conn* conn )
+{
+	BannedConn* banned_conn;
+	Lst::Search< BannedConn > sh;
+	
+	for( banned_conn = sh.FirstItem( m_banned_connections ); banned_conn;
+			banned_conn = sh.NextItem())
+	{
+		if( banned_conn->m_Ip == conn->GetIP())
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		App::BandwidthUsed( void )
+{
+	int total_bandwidth;
+	Conn* conn;
+	Lst::Search< Conn > sh;
+
+	total_bandwidth = 0;
+	for( conn = FirstConnection( &sh ); conn; conn = NextConnection( &sh ))
+	{
+		if( conn->IsRemote())
+		{
+			Metrics* metrics;
+	
+			metrics = conn->GetOutboundMetrics();
+			total_bandwidth += metrics->GetBytesPerSec();
+		}
+	}
+
+	return total_bandwidth;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	App::BandwidthExceeded( Conn* conn )
+{
+	Metrics* metrics;
+	int total_bandwidth, per_user_bandwidth, num_remote_connections;
+	Conn* tmp_conn;
+	Lst::Search< Conn > sh;
+
+    metrics = conn->GetOutboundMetrics();
+	total_bandwidth = BandwidthUsed();
+	// First, make sure we haven't exceeded the total bandwidth of our connection
+	if( total_bandwidth >= m_net_man->GetBandwidth())
+	{
+		Dbg_Printf( "Total bandwidth exceeded : %d  %d\n", total_bandwidth, m_net_man->GetBandwidth() );
+		return true;
+	}
+
+	// Then, make sure we haven't flooded the client
+	if( metrics->GetBytesPerSec() >= conn->GetBandwidth())
+	{
+		Dbg_Printf( "(%d) Client flooded: %d %d\n", m_FrameCounter, metrics->GetBytesPerSec(), conn->GetBandwidth() );
+		return true;
+	}
+
+	num_remote_connections = 0;
+	for( tmp_conn = FirstConnection( &sh ); tmp_conn; tmp_conn = NextConnection( &sh ))
+	{
+		if( tmp_conn->IsRemote() && !tmp_conn->IsForeign())
+		{
+			num_remote_connections++;
+		}
+	}
+
+	if( num_remote_connections == 0 )
+	{
+		num_remote_connections = 1;	// Avoid DBZ
+	}
+	// Also, check if we've used up this client's share of our bandwidth
+	per_user_bandwidth = ( m_net_man->GetBandwidth() / num_remote_connections );
+	if( metrics->GetBytesPerSec() > per_user_bandwidth )
+	{
+		Dbg_Printf( "Share flooded: %d %d\n",  metrics->GetBytesPerSec(), per_user_bandwidth );
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
diff --git a/Code/Gel/Net/Client/netclnt.cpp b/Code/Gel/Net/Client/netclnt.cpp
new file mode 100644
index 0000000..07382be
--- /dev/null
+++ b/Code/Gel/Net/Client/netclnt.cpp
@@ -0,0 +1,611 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Net (OBJ) 												**
+**																			**
+**	File name:		netclnt.cpp												**
+**																			**
+**	Created:		01/29/01	-	spg										**
+**																			**
+**	Description:	Network client code										**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+								  
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+bool	Client::init( void )
+{
+	
+
+#ifdef	__PLAT_NGPS__											  
+	Dbg_MsgAssert(Mem::SameContext(this,Mem::Manager::sHandle().NetworkHeap()),("Client not on network heap"));	
+#endif		//	__PLAT_NGPS__											  
+
+
+	m_connected = false;
+	m_Timestamp = 0;
+	if( App::init() == false )
+	{
+		return false;
+	}
+	
+	m_Dispatcher.AddHandler( MSG_ID_CONNECTION_ACCEPTED, handle_connection_accepted, 
+							 mHANDLE_FOREIGN | mHANDLE_LATE );
+	m_Dispatcher.AddHandler( MSG_ID_CONNECTION_REFUSED, handle_connection_refusal, mHANDLE_LATE );
+	m_Dispatcher.AddHandler( MSG_ID_CONNECTION_TERMINATED, handle_connection_refusal, mHANDLE_LATE );
+	m_Dispatcher.AddHandler( MSG_ID_TIMESTAMP, handle_timestamp, mHANDLE_LATE | mHANDLE_FOREIGN );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Client::deinit( void )
+{
+	
+
+	App::deinit();
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/* Connect (socket-level) to a server at a given IP/Port          */
+/*                                                                */
+/******************************************************************/
+
+bool Client::ConnectToServer( int ip, unsigned short port )
+{
+	int result;
+	bool connected;
+	
+	memset( &m_server_address, 0, sizeof(m_server_address));
+	m_server_address.sin_family = AF_INET;
+	m_server_address.sin_port = htons( port );	
+	m_server_address.sin_addr.s_addr = ip;
+#ifdef __PLAT_NGC__
+	m_server_address.len = sizeof( sockaddr_in );
+#else
+	memset ( &m_server_address.sin_zero, 0, 8 );  
+#endif
+
+	Dbg_Printf( "%s Connecting to server\n", GetName());
+	connected = false;
+	do
+	{
+		if(( result = connect( m_socket, (struct sockaddr *)&m_server_address, 
+					sizeof( m_server_address ))) == SOCKET_ERROR )
+		{
+#if defined( __PLAT_WN32__ ) || defined( __PLAT_XBOX__ )
+			if( WSAGetLastError() != WSAEWOULDBLOCK )
+#else
+#ifdef __PLAT_NGPS__
+			if( sn_errno( m_socket ) != EWOULDBLOCK )
+#endif
+#endif
+			{
+				ReportError();
+				return false;
+			}
+		}
+		else
+		{
+			connected = true;
+		}
+	} while( !connected );
+
+	// "Connecting" to INADDR_ANY disassociates our socket from all addresses
+	if( ip == INADDR_ANY )
+	{   
+		m_connected = false;
+	}
+	else
+	{   
+		m_connected = true;
+	}
+	return true;
+}
+
+/******************************************************************/
+/* Client's send function.                                        */
+/*                                                                */
+/******************************************************************/
+
+void	Client::SendEnqueuedMessages( Conn* conn )
+{
+	int buf_len;
+	MsgPacketStamp msg;
+	bool buffer_full;
+    
+	// Should we send this frame?
+	if( ShouldSendThisFrame( conn ) == false )
+	{
+		return;
+	}
+
+	buffer_full = false;
+    while( !buffer_full && MessagesToSend( conn ) && !BandwidthExceeded( conn ))
+	{
+		// Tack on a timestamp (for acking) if we are sending important
+		// messages in this packet
+		if( ImportantMessagesToSend( conn ))
+		{
+			MsgDesc msg_desc;
+#ifdef __PLAT_NGPS__
+			SignalSema( m_send_semaphore_id );
+#endif  
+			msg.m_Packetstamp = (unsigned short) conn->m_latest_sent_packet_stamp;
+            //msg.m_Handle = conn->GetHandle();
+			
+			msg_desc.m_Data = &msg;
+			msg_desc.m_Length = sizeof( MsgPacketStamp );
+			msg_desc.m_Id = MSG_ID_TIMESTAMP;
+			msg_desc.m_Priority = HIGHEST_PRIORITY;
+			EnqueueMessage( conn->GetHandle(), &msg_desc );
+#ifdef __PLAT_NGPS__
+			WaitSema( m_send_semaphore_id );
+#endif
+		}
+	
+		// Send Latency Tests/Responses if applicable
+		if( ( conn->IsRemote()) &&
+			( conn->IsForeign() == false ) &&
+			( m_flags.TestMask( App::mDYNAMIC_RESEND )))
+		{
+			MsgDesc msg_desc;
+			MsgTimestamp latency_msg;
+			unsigned int cur_time;
+	
+			cur_time = m_Timestamp;
+			latency_msg.m_Timestamp = cur_time;
+			// send out a new latency test, keeping track of the time at which
+			// we sent it
+			if(	( conn->m_latency_test.m_SendTime == 0 ) ||
+				( ( cur_time - conn->m_latency_test.m_SendTime ) > App::MAX_LATENCY ))
+			{
+				// If we never got a response, simulate an increased latency
+				if( conn->m_latency_test.m_SendTime > conn->m_latency_test.m_ReceiveTime )
+				{
+					unsigned int latency_value;
+	
+					latency_value = conn->GetAveLatency();
+					latency_value += 100;
+	
+					if( latency_value > App::MAX_LATENCY )
+					{
+						latency_value = App::MAX_LATENCY;
+					}
+					conn->m_latency_test.InputLatencyValue( latency_value );
+				}
+	
+				conn->m_latency_test.m_SendTime = cur_time;
+	
+#ifdef __PLAT_NGPS__
+				SignalSema( m_send_semaphore_id );
+#endif  
+				msg_desc.m_Data = &latency_msg;
+				msg_desc.m_Id = MSG_ID_PING_TEST;
+				msg_desc.m_Length = sizeof( MsgTimestamp );
+				EnqueueMessage( conn->GetHandle(), &msg_desc ); 
+	
+#ifdef __PLAT_NGPS__
+				WaitSema( m_send_semaphore_id );
+#endif
+			}			
+		}
+		
+		if( !BuildMsgStream( conn, QUEUE_DEFAULT ))
+		{
+			buffer_full = true;
+		}
+		// First, use up our bandwidth with re-sends or else they might remain un-sent
+		// indefinitely, causing a bad backup on the client if he's waiting on 
+		// one particular sequence
+		if( !BuildMsgStream( conn, QUEUE_SEQUENCED, true ))
+		{
+			buffer_full = true;
+		}
+		if( !BuildMsgStream( conn, QUEUE_IMPORTANT, true ))
+		{
+			buffer_full = true;
+		}
+		if( !BuildMsgStream( conn, QUEUE_SEQUENCED ))
+		{
+			buffer_full = true;
+		}
+		if( !BuildMsgStream( conn, QUEUE_IMPORTANT ))
+		{
+			buffer_full = true;
+		}
+		
+		buf_len = conn->m_write_ptr - conn->m_write_buffer;
+		// If there is data to send
+		if(	( buf_len > 0 ) &&
+			( conn->IsRemote()))
+		{
+			if( m_connected )
+			{           
+				if( Send( conn->m_write_buffer, buf_len, 0 ))
+				{
+					m_TotalBytesOut += buf_len;
+					conn->GetOutboundMetrics()->AddPacket( buf_len + vUDP_PACKET_OVERHEAD, m_Timestamp );
+					conn->m_last_send_time = m_Timestamp;
+					conn->SetForceSendThisFrame( false );
+				}
+				else
+				{
+					// If it didn't send the messages properly, flag them for resend next frame
+					conn->FlagMessagesForResending( conn->m_latest_sent_packet_stamp );
+					conn->m_write_ptr = conn->m_write_buffer;
+					break;
+				}
+	
+				conn->m_write_ptr = conn->m_write_buffer;								
+			}
+			else
+			{
+				if( SendTo( conn->GetIP(), conn->GetPort(), conn->m_write_buffer, buf_len, 0 ))
+				{
+					m_TotalBytesOut += buf_len;
+					conn->GetOutboundMetrics()->AddPacket( buf_len + vUDP_PACKET_OVERHEAD, m_Timestamp );
+					conn->m_last_send_time = m_Timestamp;
+					conn->SetForceSendThisFrame( false );
+				}
+				else
+				{
+					Dbg_Printf( "*** SendTo Error: Flagging messages for resending\n" );
+					// If it didn't send the messages properly, flag them for resend next frame
+					conn->FlagMessagesForResending( conn->m_latest_sent_packet_stamp );
+					conn->m_write_ptr = conn->m_write_buffer;
+					break;
+				}
+				conn->m_write_ptr = conn->m_write_buffer;								
+			}
+		}
+		
+		conn->m_latest_sent_packet_stamp++;
+	}
+}
+
+/******************************************************************/
+/* Client's receive function                                      */
+/*                                                                */
+/******************************************************************/
+
+void	Client::ReceiveData( void )
+{
+	Conn *conn;
+	int num_bytes, actual_data_len;
+	struct sockaddr from_address;
+#ifdef __PLAT_NGC__
+	from_address.len = sizeof( sockaddr );
+#else
+	int addr_len = sizeof( from_address );
+#endif
+	struct sockaddr_in *foreign_address;
+	
+		
+	
+	
+	// Local clients never really receive. Their data is automatically "received" when
+	// the local server writes its data into the client's receive buffer automatically
+	if( IsLocal())
+	{
+		return;
+	}
+	// read data into local buffers
+	do
+	{   
+		if( !m_connected )
+		{   
+			num_bytes = recvfrom( m_socket, m_in_packet_buffer, 
+				Manager::vMAX_PACKET_SIZE, 0, &from_address, &addr_len );
+		}
+		else
+		{   
+			num_bytes = recv( m_socket, m_in_packet_buffer, Manager::vMAX_PACKET_SIZE, 0 );
+		}
+		if ( num_bytes < 0 )//== SOCKET_ERROR ) 
+		{
+#if defined( __PLAT_WN32__ ) || defined( __PLAT_XBOX__ )
+			if( WSAGetLastError() != WSAEWOULDBLOCK )
+#else
+#ifdef __PLAT_NGPS__
+			if( sn_errno( m_socket ) != EWOULDBLOCK )
+#else
+#ifdef __PLAT_NGC__
+			if( num_bytes != SO_EWOULDBLOCK )
+#endif
+#endif
+#endif
+            {
+#ifdef NET_DEBUG_MESSAGES
+				Dbg_Printf( "Client Receive Error :" );
+#ifdef __PLAT_WN32__
+				OutputDebugString( "Client Receive Error!\n" );
+#endif
+				ReportError();
+#endif
+			}
+			break;
+		}
+		else
+		{   
+#ifdef __PLAT_NGPS__
+			WaitSema( m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__
+			m_TotalBytesIn += num_bytes;
+			if( m_flags & App::mSECURE )
+			{
+				actual_data_len = num_bytes - sizeof( unsigned short );	// total size - CRC size
+			}
+			else
+			{
+				actual_data_len = num_bytes;
+			}
+			
+			conn = GetConnectionByAddress( m_server_address.sin_addr.s_addr, 
+											ntohs( m_server_address.sin_port ));
+            if( conn )
+			{
+				if(( conn->m_read_ptr + actual_data_len ) < ( conn->m_read_buffer + Conn::vREAD_BUFFER_LENGTH ))
+				{
+					Tmr::Time cur_time;
+
+					cur_time = Tmr::GetTime();
+					if( !validate_and_copy_stream( m_in_packet_buffer, conn->m_read_ptr, num_bytes ))
+					{
+#ifdef __PLAT_NGPS__
+						SignalSema( m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__
+						// If the game would like to handle this foreign data, allow it to do so now
+						if( m_foreign_handler )
+						{
+							m_foreign_handler( m_in_packet_buffer, num_bytes, &from_address );
+						}
+						continue;
+					}
+					conn->m_read_ptr += actual_data_len;
+					conn->GetInboundMetrics()->AddPacket( num_bytes + vUDP_PACKET_OVERHEAD, m_Timestamp );
+					if( cur_time > conn->m_last_comm_time )
+					{
+						conn->m_last_comm_time = cur_time;
+					}					
+				}
+				else
+				{
+#ifdef __PLAT_WN32__
+					char message[1024];
+					sprintf( message, "****** %d: Dropped Packet\n", m_Timestamp );
+					OutputDebugString( message ); 
+#endif	// __PLAT_WN32__					
+				}
+			}
+			else if( !m_connected )
+			{
+				Conn* foreign_conn;
+
+				foreign_address = (struct sockaddr_in*) &from_address;
+				// Foreign connection. Create temporary connection to process this foreign message
+				foreign_conn = NewConnection( foreign_address->sin_addr.s_addr, 
+												ntohs( foreign_address->sin_port ), 
+											  Conn::mREMOTE | 
+											  Conn::mFOREIGN );
+				if( foreign_conn )
+				{
+					foreign_conn->m_app = this;
+					//memcpy( foreign_conn->m_read_buffer, m_read_buffer, actual_data_len );				
+					if( !validate_and_copy_stream( m_in_packet_buffer, foreign_conn->m_read_ptr, num_bytes ))
+					{
+#ifdef __PLAT_NGPS__
+						SignalSema( m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__
+						// If the game would like to handle this foreign data, allow it to do so now
+						if( m_foreign_handler )
+						{
+							m_foreign_handler( m_in_packet_buffer, num_bytes, &from_address );
+						}
+						delete foreign_conn;
+						continue;
+					}
+					foreign_conn->m_read_ptr = foreign_conn->m_read_buffer + actual_data_len;				
+					foreign_conn->Invalidate();
+				}
+			}
+#ifdef __PLAT_NGPS__
+			SignalSema( m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__
+		}		
+
+	} while( num_bytes > 0 );
+}
+
+#ifdef USE_ALIASES
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Client::ClearAliasTable( void )
+{
+	int i;
+
+	for( i = 0; i < vNUM_ALIASES; i++ )
+	{
+		m_alias_table[i].m_Id = 0;
+		m_alias_table[i].m_Expiration = -1;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+unsigned char	Client::GetObjectAlias( int obj_id, int cur_time )
+{
+	int hash, i;
+	bool quit;
+
+	hash = obj_id & 0xff;	// mask off all but lowest byte and use as a hash into the table
+	hash %= vNUM_ALIASES;
+	quit = false;
+	i = hash;
+	do
+	{   
+		if( m_alias_table[i].m_Id == obj_id )
+		{   
+			if( cur_time > m_alias_table[i].m_Expiration )
+			{
+				return (unsigned char) vNO_ALIAS;
+			}
+			return i;
+		}
+		
+		i = ( i + 1 ) % vNUM_ALIASES;
+		if( i == hash )	// complete revolution
+		{
+			break;
+		}
+	} while( !quit );
+
+	return (unsigned char) vNO_ALIAS;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+unsigned char	Client::NewObjectAlias( int obj_id, int cur_time, bool expires )
+{
+	int hash, i;
+	bool quit;
+
+	
+
+	hash = obj_id & 0xff;	// mask off all but lowest byte and use as a hash into the table
+	hash %= vNUM_ALIASES;
+	quit = false;
+	i = hash;
+	do
+	{
+		if( m_alias_table[i].m_Id == obj_id )
+		{   
+			m_alias_table[i].m_Expiration = cur_time + vALIAS_DURATION;
+			return (unsigned char) i;
+		}
+		else if(( cur_time - m_alias_table[i].m_Expiration ) > vALIAS_DURATION )
+		{
+			m_alias_table[i].m_Id = obj_id;
+			m_alias_table[i].m_Expiration = cur_time + vALIAS_DURATION;
+			return (unsigned char) i;
+		}
+		i = ( i + 1 ) % vNUM_ALIASES;
+		if( i == hash )	// complete revolution
+		{
+			break;
+		}
+	} while( !quit );
+
+	// no free aliases
+	return (unsigned char) vNO_ALIAS;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Client::GetObjectId( unsigned char alias )
+{
+	
+    
+	//Dbg_MsgAssert(( m_LatestPacketStamp <= m_alias_table[ alias ].m_Expiration ), "Server using expired alias\n" );
+	return m_alias_table[ alias ].m_Id;
+}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Client::Client( int flags )
+: App( flags )
+{
+	m_send_network_data_task = new Tsk::Task< App > ( send_network_data, *this, 
+													  Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_CLIENT_SEND_NETWORK_DATA );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
diff --git a/Code/Gel/Net/Client/netclnt.h b/Code/Gel/Net/Client/netclnt.h
new file mode 100644
index 0000000..06ce41b
--- /dev/null
+++ b/Code/Gel/Net/Client/netclnt.h
@@ -0,0 +1,79 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			NET  (Net)												**
+**																			**
+**	File name:		gel/netclnt.h											**
+**																			**
+**	Created: 		01/29/01	-	spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef __NETCLNT_H__
+#define __NETCLNT_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class Client : public App
+{
+	friend class Manager;
+
+public:
+			Client( int flags = 0 );
+
+	bool	ConnectToServer( int ip, unsigned short port );	
+	void	ReceiveData( void );	
+	void	SendEnqueuedMessages( Conn* conn );
+	
+#ifdef USE_ALIASES
+	// Alias functions
+	void	ClearAliasTable( void );
+	unsigned char	GetObjectAlias( int obj_id, int cur_time );
+	unsigned char	NewObjectAlias( int obj_id, int cur_time, bool expires = true );
+	int		GetObjectId( unsigned char alias );
+private:
+	
+	AliasEntry	m_alias_table[ vNUM_ALIASES ];
+#endif
+    
+protected:
+	bool	init( void );
+	void	deinit( void );
+
+	static	MsgHandlerCode	handle_timestamp;
+	static	MsgHandlerCode	handle_connection_refusal;
+
+	struct	sockaddr_in	m_server_address;
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
+
+#endif // __NETCLNT_H__
\ No newline at end of file
diff --git a/Code/Gel/Net/Dispatch/netdsptch.cpp b/Code/Gel/Net/Dispatch/netdsptch.cpp
new file mode 100644
index 0000000..8246f0e
--- /dev/null
+++ b/Code/Gel/Net/Dispatch/netdsptch.cpp
@@ -0,0 +1,334 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Net (OBJ) 												**
+**																			**
+**	File name:		netdsptch.cpp											**
+**																			**
+**	Created:		01/29/01	-	spg										**
+**																			**
+**	Description:	Network Dispatching code								**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+//#define NET_PRINT_MESSAGES 1 
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+MsgHandler::MsgHandler( MsgHandlerCode *code, int flags, void *data, int pri )
+: Lst::Node< MsgHandler >( this, pri ), m_code( code ), m_data( data ), m_flags( flags )
+{   
+}
+
+/******************************************************************/
+/* Register a handler for a net msg                               */
+/*                                                                */
+/******************************************************************/
+
+MsgHandler*	Dispatcher::AddHandler( unsigned char net_msg_id, MsgHandlerCode *code, int flags, 
+						void *data, int pri )
+{	
+	MsgHandler* handler;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	handler = new MsgHandler( code, flags, data, pri );
+
+	m_handler_list[net_msg_id].AddNode( handler );
+
+	Mem::Manager::sHandle().PopContext();
+
+	return handler;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Dispatcher::Init( void )
+{
+	int i;
+
+	for( i = 0; i < MAX_NET_MSG_ID; i++ )
+	{
+		m_handler_list[i].DestroyAllNodes();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Dispatcher::Deinit( void )
+{
+	int i;
+
+	
+
+	for( i = 0; i < MAX_NET_MSG_ID; i++ )
+	{   
+		Lst::Search< MsgHandler > sh;
+		MsgHandler* handler, *next;
+		
+		for( handler = sh.FirstItem( m_handler_list[i] ); handler;
+				handler = next )
+		{
+			next = sh.NextItem();
+
+			delete handler;
+		}
+	}
+}
+
+/******************************************************************/
+/* Dispatch messages to their appropriate handlers                */
+/*                                                                */
+/******************************************************************/
+
+int	Dispatcher::DispatchMsgHandlers( Conn *conn, int flags )
+{
+	MsgHandlerContext msg_context;
+	char msg_data[MAX_STREAM_LENGTH];
+	int result;
+	char *data;
+
+	Dbg_Assert( conn != NULL );	
+
+	msg_context.m_Conn = conn;
+	msg_context.m_App = conn->m_app;
+	msg_context.m_PacketFlags = flags;
+	msg_context.m_Msg = msg_data;
+	msg_context.m_MsgLength = 0;
+
+	data = conn->m_read_buffer;		
+
+	// loop through received data and handle messages
+	while( data < conn->m_read_ptr )
+	{		
+		bool size_known;
+
+        // *** IMPORTANT.  Because of alignment issues, I now copy the data from the byte stream
+		// into an aligned message buffer.  Indexing into the buffer and casting to Msg's caused
+		// load errors
+		memcpy( &msg_context.m_MsgId, data, sizeof( unsigned char ));
+		data += sizeof( unsigned char );
+
+		// Check to see if the size of this message type is stored after the msg id
+		size_known = (( conn->m_app->GetManager()->GetMessageFlags( msg_context.m_MsgId ) & mMSG_SIZE_UNKNOWN ) == 0 );
+		//if( conn->IsRemote())
+		//{
+			//Dbg_Printf( "Got message %s with size_known of %d\n", conn->m_app->GetManager()->GetMessageName(msg_context.m_MsgId ), size_known);
+		//}
+		
+		if( size_known )
+		{
+			unsigned short temp;
+			memcpy( &temp, data, sizeof( unsigned short ));
+			msg_context.m_MsgLength = (unsigned long)temp;
+
+			data += sizeof( unsigned short );		
+		}
+        
+		if( msg_context.m_MsgId == MSG_ID_SEQUENCED )
+		{
+			unsigned char embedded_msg_id;
+			unsigned short embedded_msg_length;
+            
+			Dbg_Assert( size_known );
+
+			memcpy( &embedded_msg_id, data + 5, sizeof( unsigned char ));
+			embedded_msg_length = msg_context.m_MsgLength - Manager::vMSG_SEQ_HEADER_LENGTH;
+						
+			conn->GetInboundMetrics()->AddMessage( embedded_msg_id, msg_context.m_MsgLength );
+#ifdef NET_PRINT_MESSAGES
+			if( conn->IsRemote())
+			{
+				unsigned char group_id;
+				unsigned int seq_id;
+
+				memcpy( &group_id, data, sizeof( unsigned char ));
+				memcpy( &seq_id, data + 1, sizeof( unsigned int ));
+				Dbg_Printf( "(%d) Dispatching Sequenced Message (%d) Length (%d) Emb. Length (%d) : %s. Seq: %d Group: %d\n", 
+							conn->m_app->m_FrameCounter,
+							embedded_msg_id,
+							msg_context.m_MsgLength,
+							embedded_msg_length,
+							conn->m_app->GetManager()->GetMessageName( embedded_msg_id ),
+							seq_id,
+							group_id );
+			}
+#endif NET_PRINT_MESSAGES
+		}
+		
+		// If we know the size, just copy the message into the aligned buffer
+		// otherwise, copy the maximum size of a message into the buffer and have the
+		// handler tell us the size of the message by changint the context's m_MsgLength member
+		if( size_known )
+		{
+			if( msg_context.m_MsgLength > 0 )
+			{
+				memcpy( msg_context.m_Msg, data, msg_context.m_MsgLength );
+			}
+		}
+		else
+		{
+			memcpy( msg_context.m_Msg, data, Manager::vMAX_PACKET_SIZE );
+		}
+
+		if( conn->IsForeign())
+		{
+			msg_context.m_PacketFlags |= mHANDLE_FOREIGN;
+		}
+		result = DispatchMessage( &msg_context );
+		if(	( result == HANDLER_ERROR ) ||
+			( result == HANDLER_HALT ))
+		{
+			// output error indicating mal-formmated data
+			Dbg_Printf( "Stream processing halted on message %d\n", msg_context.m_MsgId );
+			conn->m_read_ptr = conn->m_read_buffer;
+			return HANDLER_HALT;
+		}
+
+		// By this time we know the message size, whether it was explicit or implicit
+		// so we can do the metrics calculation and the debug printout
+		if( msg_context.m_MsgId != MSG_ID_SEQUENCED )
+		{
+			conn->GetInboundMetrics()->AddMessage( msg_context.m_MsgId, msg_context.m_MsgLength );
+#ifdef NET_PRINT_MESSAGES
+			/*if( conn->IsRemote())
+			{
+				Dbg_Printf( "(%d) Dispatching Message (%d) Length (%d) : %s\n", 
+							conn->m_app->m_FrameCounter,
+							msg_context.m_MsgId,
+							msg_context.m_MsgLength,
+							conn->m_app->GetManager()->GetMessageName( msg_context.m_MsgId ));
+			}*/
+#endif
+		}
+
+		data += msg_context.m_MsgLength;
+	}
+
+	conn->m_read_ptr = conn->m_read_buffer;
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Dispatch a message to its appropriate handler. Stop if higher  */
+/* priority handlers say to                                       */
+/******************************************************************/
+
+int	Dispatcher::DispatchMessage( MsgHandlerContext *context )
+{
+	Lst::Search< MsgHandler > sh;
+	MsgHandler *handler, *next;
+	int result;
+
+	
+
+	Dbg_Assert( context );
+
+    // If this is a message we want to handle, call the handler
+	for( handler = sh.FirstItem( m_handler_list[context->m_MsgId] );
+			handler; handler = next )
+	{
+		next = sh.NextItem();
+
+		if(( handler->m_flags & context->m_PacketFlags ) != context->m_PacketFlags )
+		{
+			Dbg_Printf( "*** Conn %d packetflag mismatch on id %d : 0x%x 0x%x\n", context->m_Conn->GetHandle(), context->m_MsgId, handler->m_flags, context->m_PacketFlags );
+			continue;
+		}
+		
+		context->m_Data = handler->m_data;
+		result = handler->m_code( context );
+		// Check the result from the handler
+		if(	( result == HANDLER_ERROR ) ||
+			( result == HANDLER_HALT ) ||
+			( result == HANDLER_MSG_DESTROYED ))
+		{
+			// Stop processing the buffer
+			return result;
+		}		
+		// A higher int handler has chosen to mask this message from the rest
+		if( ( result == HANDLER_MSG_DONE ))
+		{
+			return HANDLER_CONTINUE;
+		}
+	}
+
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
\ No newline at end of file
diff --git a/Code/Gel/Net/Handler/nethndlr.cpp b/Code/Gel/Net/Handler/nethndlr.cpp
new file mode 100644
index 0000000..5c1bd74
--- /dev/null
+++ b/Code/Gel/Net/Handler/nethndlr.cpp
@@ -0,0 +1,745 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Net (OBJ) 												**
+**																			**
+**	File name:		net.cpp													**
+**																			**
+**	Created:		01/29/01	-	spg										**
+**																			**
+**	Description:	Network Manager code									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+//#define DEBUG_MESSAGES
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/* Ack important messages                               		  */
+/*                                                                */
+/******************************************************************/
+
+int	handle_ack( MsgHandlerContext *context )
+{
+	MsgImpLink *msg_imp_link, *next_imp;
+	MsgSeqLink *msg_seq_link, *next_seq;
+	Lst::Search< MsgImpLink > imp_sh;
+	Lst::Search< MsgSeqLink > seq_sh;
+	MsgAck *ack_msg;
+	QueuedMsg* msg;
+	QueuedMsgSeq* msg_seq;
+	unsigned int send_time;
+		
+	
+
+	Dbg_Assert( context );
+
+	ack_msg = (MsgAck*) context->m_Msg;
+	send_time = 0;
+
+#ifdef DEBUG_MESSAGES
+	if( context->m_Conn->IsRemote())
+	{
+		Dbg_Printf( "(%d) Conn %d: (%d) Got Ack for Packetstamp %d\n", 
+			context->m_App->m_FrameCounter, 
+			context->m_Conn->GetHandle(),
+			context->m_App->m_Timestamp, 
+			ack_msg->m_Packetstamp );
+	}
+#endif
+
+#ifdef __PLAT_NGPS__
+	/*if( context->m_Conn->GetHandle() != ack_msg->m_Handle )
+	{
+		Lst::Search< Conn > sh;
+		Conn* conn;
+
+		for( conn = context->m_App->FirstConnection( &sh ); conn; conn = context->m_App->NextConnection( &sh ))
+		{
+			Dbg_Printf( "*** Conn %d : %p. Ip: 0x%x Port: %d\n", conn->GetHandle(), conn, conn->GetIP(), conn->GetPort());
+		}
+	}
+
+	Dbg_MsgAssert( context->m_Conn->GetHandle() == ack_msg->m_Handle, ("*** Different handles! %d %d\n", context->m_Conn->GetHandle(), ack_msg->m_Handle ));*/
+#endif
+
+	// Tell the dispatcher the size of this message because it does not know (optimization)
+	context->m_MsgLength = sizeof( MsgAck );
+
+	// Don't ack foreign acks. We only even handle them because we need to tell the dispatcher
+	// the length of the message
+	if( context->m_PacketFlags & mHANDLE_FOREIGN )
+	{
+	    return HANDLER_MSG_DONE;
+	}
+
+	for( msg_imp_link = imp_sh.FirstItem( context->m_Conn->m_important_msg_list ); 
+			msg_imp_link; msg_imp_link = next_imp )
+	{
+		next_imp = imp_sh.NextItem();
+		if(	( ack_msg->m_Packetstamp == (unsigned short) msg_imp_link->m_Packetstamp ) &&
+			( msg_imp_link->m_Timestamp != 0 ))	// guard against acking packet stamp of zero where zero is the initialized value
+		{
+			msg = msg_imp_link->m_QMsg;
+			msg->m_Ref.Release();
+			if( msg->m_Ref.InUse() == false )
+			{
+				delete msg;
+			}
+			send_time = msg_imp_link->m_Timestamp;
+			delete msg_imp_link;
+		}
+	}	
+
+	for( msg_seq_link = seq_sh.FirstItem( context->m_Conn->m_sequenced_msg_list ); 
+			msg_seq_link; msg_seq_link = next_seq )
+	{
+		next_seq = seq_sh.NextItem();
+		// ACK the message
+		if( ( ack_msg->m_Packetstamp == (unsigned short) msg_seq_link->m_Packetstamp ) &&
+			( msg_seq_link->m_Timestamp != 0 ))	// guard against acking packet stamp of zero where zero is the initialized value
+		{
+			msg_seq = msg_seq_link->m_QMsg;
+			msg_seq->m_Ref.Release();
+			// If this message is no longer being referenced, free it
+			if( msg_seq->m_Ref.InUse() == false )
+			{
+				delete msg_seq;
+			}
+			send_time = msg_seq_link->m_Timestamp;
+			delete msg_seq_link;
+		}
+	}
+
+	// If we can detect that the connection is starting to take longer to ack
+	// our messages then affect his calculated latency so that we don't expect
+	// acks back so quickly (which prompts resends )
+	if(	( context->m_Conn->IsRemote()) &&
+		( context->m_Conn->TestStatus( Conn::mSTATUS_READY )) &&
+		( send_time > 0 ))
+	{
+		unsigned int latency_value;
+
+		latency_value = context->m_App->m_Timestamp - send_time;
+		if( latency_value > (unsigned int) context->m_Conn->GetAveLatency())
+		{
+            if( latency_value > App::MAX_LATENCY )
+			{
+				latency_value = App::MAX_LATENCY;
+			}
+			
+			context->m_Conn->m_latency_test.InputLatencyValue( latency_value );
+		}
+	}
+
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Aggregate stream messages									  */
+/*                                                                */
+/******************************************************************/
+
+int	App::handle_stream_messages( MsgHandlerContext *context )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	//Dbg_Printf( "******** Got Stream Message\n" );
+
+	switch( context->m_MsgId )
+	{
+		case MSG_ID_STREAM_START:
+		{
+			//Dbg_Printf( "******** Got Stream Start\n" );
+			MsgStreamStart* msg;
+			StreamLink* str_link;
+			StreamDesc* new_stream;
+			Lst::Search< StreamLink > str_sh;
+			int packet_len;
+
+			msg = (MsgStreamStart*) context->m_Msg;
+			
+			for( str_link = str_sh.FirstItem( context->m_Conn->m_StreamInList ); str_link; 
+					str_link = str_sh.NextItem())
+			{
+				StreamDesc* desc;
+
+				desc = str_link->m_Desc;
+				if( desc->m_StreamId == msg->m_StreamId )
+				{
+					Mem::Manager::sHandle().PopContext();
+					return HANDLER_MSG_DONE;
+				}
+			}
+            
+			new_stream = new StreamDesc;
+			strcpy( new_stream->m_StreamDesc, msg->m_StreamDesc );
+			Dbg_Printf( "StreamDesc: %s\n", new_stream->m_StreamDesc );
+			new_stream->m_Size = msg->m_Size;
+			new_stream->m_StreamId = msg->m_StreamId;
+			new_stream->m_Checksum = msg->m_Checksum;
+			new_stream->m_MsgId = msg->m_MsgId;
+			new_stream->m_Data = new char[ msg->m_Size ];
+			new_stream->m_DataPtr = new_stream->m_Data;
+			packet_len = ( msg->m_Size < MAX_STREAM_CHUNK_LENGTH ) ? msg->m_Size : MAX_STREAM_CHUNK_LENGTH;
+			memcpy( new_stream->m_Data, msg->m_Data, packet_len );
+			new_stream->m_DataPtr += packet_len;
+			str_link = new StreamLink( new_stream );
+			context->m_Conn->m_StreamInList.AddToTail( str_link );
+
+			if(((unsigned int) new_stream->m_DataPtr - (unsigned int) new_stream->m_Data ) >= (unsigned int) new_stream->m_Size )
+			{
+				MsgHandlerContext msg_context;
+				int result;
+
+				// Shouldn't go over
+				Dbg_Assert( ((unsigned int) new_stream->m_DataPtr - (unsigned int) new_stream->m_Data ) == (unsigned int) new_stream->m_Size );
+
+				msg_context.m_Conn = context->m_Conn;
+				msg_context.m_App = context->m_App;
+				msg_context.m_PacketFlags = 0;
+				msg_context.m_MsgId = new_stream->m_MsgId;
+				msg_context.m_MsgLength = new_stream->m_Size;
+				msg_context.m_Msg = new char[new_stream->m_Size];
+				
+                memcpy( msg_context.m_Msg, new_stream->m_Data, new_stream->m_Size );
+
+				result = context->m_App->m_Dispatcher.DispatchMessage( &msg_context );
+				if( result != HANDLER_MSG_DESTROYED )
+				{
+					delete str_link->m_Desc;
+					delete str_link;
+				}
+
+				delete [] msg_context.m_Msg;
+				Mem::Manager::sHandle().PopContext();
+				return result;
+			}
+			break;
+		}
+		
+		case MSG_ID_STREAM_DATA:
+		{
+			MsgStreamData* msg;
+			StreamLink* str_link;
+			Lst::Search< StreamLink > str_sh;
+
+			msg = (MsgStreamData*) context->m_Msg;
+
+			//Dbg_Printf( "******** Got Stream Data\n" );
+			for( str_link = str_sh.FirstItem( context->m_Conn->m_StreamInList ); str_link; 
+					str_link = str_sh.NextItem())
+			{
+				StreamDesc* desc;
+				int packet_len;
+
+				desc = str_link->m_Desc;
+				if( desc->m_StreamId == msg->m_StreamId )
+				{   
+					packet_len = context->m_MsgLength - sizeof( int );	// subtract the size of the stream id
+					memcpy( desc->m_DataPtr, msg->m_Data, packet_len );
+					desc->m_DataPtr += packet_len;
+
+					if(((unsigned int) desc->m_DataPtr - (unsigned int) desc->m_Data ) >= (unsigned int) desc->m_Size )
+					{
+						MsgHandlerContext msg_context;
+						int result;
+						uint32 actual_crc;
+
+						// Shouldn't go over
+						Dbg_Assert( ((unsigned int) desc->m_DataPtr - (unsigned int) desc->m_Data ) == (unsigned int) desc->m_Size );
+
+						msg_context.m_Conn = context->m_Conn;
+						msg_context.m_App = context->m_App;
+						msg_context.m_PacketFlags = 0;
+						msg_context.m_MsgId = desc->m_MsgId;
+						msg_context.m_MsgLength = desc->m_Size;
+						msg_context.m_Msg = new char[desc->m_Size];
+						
+						memcpy( msg_context.m_Msg, desc->m_Data, desc->m_Size );
+						actual_crc = Crc::GenerateCRCCaseSensitive( msg_context.m_Msg, desc->m_Size );
+						if( actual_crc != desc->m_Checksum )
+						{
+							msg_context.m_PacketFlags |= mHANDLE_CRC_MISMATCH;
+							Dbg_Printf( "CRC MISMATCH!!!\n" );
+						}
+
+						Dbg_Printf( "handling stream: %s\n", desc->m_StreamDesc );
+						result = context->m_App->m_Dispatcher.DispatchMessage( &msg_context );
+						if( result != HANDLER_MSG_DESTROYED )
+						{
+							delete str_link->m_Desc;
+							delete str_link;
+						}
+
+						delete [] msg_context.m_Msg;
+
+						Mem::Manager::sHandle().PopContext();
+						return result;
+					}
+				}
+			}
+			break;
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Put sequenced messages in their queue in order in their group  */
+/*                                                                */
+/******************************************************************/
+
+int	App::handle_sequenced_messages( MsgHandlerContext *context )
+{
+	MsgSeqLink *msg_link;	
+	char* data;
+	unsigned short	msg_len;
+	QueuedMsgSeq* queued_msg;
+	unsigned char group_id, msg_id;	
+	unsigned int seq_id;
+	Lst::Search< MsgHandler > sh;
+	
+	
+
+	Dbg_Assert( context );
+
+	data = context->m_Msg;
+	memcpy( &group_id, data, sizeof( char ));
+	data += sizeof( char );
+	memcpy( &seq_id, data, sizeof( unsigned int ));
+	data += sizeof (unsigned int );	
+		
+	// don't accept old sequenced messages
+	if( seq_id < context->m_Conn->m_WaitingForSequenceId[ group_id ] )
+	{
+		return HANDLER_CONTINUE;
+	}
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	memcpy( &msg_id, data, sizeof( unsigned char ));
+	data += sizeof( unsigned char );
+
+	msg_len = context->m_MsgLength - Manager::vMSG_SEQ_HEADER_LENGTH;
+	
+	queued_msg = new QueuedMsgSeq( msg_id, msg_len, data, group_id );
+
+	msg_link = new MsgSeqLink( queued_msg );
+	msg_link->SetPri( seq_id );
+	msg_link->m_Timestamp = 0;
+	msg_link->m_Packetstamp = 0;
+	msg_link->m_GroupId = group_id;
+	msg_link->m_SequenceId = seq_id;
+    
+	Mem::Manager::sHandle().PopContext();
+
+	// This might be dangerous in some apps, but it makes sense right now.  Don't add
+	// this message to the sequenced queue if we dont have a handler for it.
+	/*if( sh.FirstItem( context->m_App->m_Dispatcher.m_handler_list[msg_id] ) == NULL )
+	{
+		Dbg_MsgAssert( 0, ( "No handler for msg %d\n", msg_id ));
+		delete msg_link->m_QMsg;
+		delete msg_link;	
+		return HANDLER_MSG_DONE;
+	}*/
+		// Don't re-add identical sequenced messages
+	if( context->m_Conn->m_SequencedBuffer[group_id].AddUniqueSequence( msg_link ) == false )
+	{
+	    delete msg_link->m_QMsg;
+		delete msg_link;
+		return HANDLER_MSG_DONE;
+	}
+
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Ack the packet immediately when we get it                      */
+/*                                                                */
+/******************************************************************/
+
+int	Server::handle_timestamp( MsgHandlerContext *context )
+{
+	MsgDesc msg_desc;
+	MsgPacketStamp *time_msg;
+	MsgAck ack_msg;
+	
+	Dbg_Assert( context );
+
+	// Tell the dispatcher the size of this message because it does not know (optimization)
+	context->m_MsgLength = sizeof( MsgPacketStamp );
+
+	// Don't ack foreign timestamps. We only even handle them because we need to tell the dispatcher
+	// the length of the message
+	if( context->m_PacketFlags & mHANDLE_FOREIGN )
+	{
+		return HANDLER_MSG_DONE;
+	}
+	
+	time_msg = (MsgPacketStamp *) context->m_Msg;	
+	ack_msg.m_Packetstamp = time_msg->m_Packetstamp;
+	//ack_msg.m_Handle = time_msg->m_Handle;
+
+#ifdef DEBUG_MESSAGES
+	if( context->m_Conn->IsRemote())
+	{
+		Dbg_Printf( "(%d) Conn %d: (%d) Got Packetstamp %d\n", 
+			context->m_App->m_FrameCounter, 
+			context->m_Conn->GetHandle(),
+			context->m_App->m_Timestamp, 
+			time_msg->m_Packetstamp );
+	}
+#endif
+
+	msg_desc.m_Data = &ack_msg;
+	msg_desc.m_Length = sizeof( MsgAck );
+	msg_desc.m_Id = MSG_ID_ACK;
+	context->m_App->EnqueueMessage( context->m_Conn->GetHandle(), &msg_desc );
+	
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*     															  */
+/*                                                         		  */
+/******************************************************************/
+
+int Server::handle_disconn_req( MsgHandlerContext* context )
+{
+	MsgDesc msg_desc;
+
+#ifdef DEBUG_MESSAGES
+	Dbg_Message( "Got disconnection request. Setting connection status to mSTATUS_DISCONNECTING\n" );
+#endif
+	// Don't re-disconnect someone
+	if( context->m_Conn->TestStatus( Conn::mSTATUS_DISCONNECTING ))
+	{
+		return HANDLER_MSG_DONE;
+	}
+	// Enqueue a disconn acceptance message before the cutoff point 
+	// for enqueuing messages( Conn::mSTATUS_DISCONNECTING )
+    msg_desc.m_Id = MSG_ID_DISCONN_ACCEPTED;
+	context->m_App->EnqueueMessage( context->m_Conn->GetHandle(), &msg_desc );
+	context->m_Conn->SetStatus( Conn::mSTATUS_DISCONNECTING );
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Handle the timestamp from the server. Check for lost & late    */
+/* packets                                                        */
+/******************************************************************/
+
+int	Client::handle_timestamp( MsgHandlerContext *context )
+{
+	MsgPacketStamp *time_msg;
+	MsgAck ack_msg;
+	MsgDesc msg_desc;
+
+	Dbg_Assert( context );
+
+	// Tell the dispatcher the size of this message because it does not know (optimization)
+	context->m_MsgLength = sizeof( MsgPacketStamp );
+
+	// Don't ack foreign timestamps. We only even handle them because we need to tell the dispatcher
+	// the length of the message
+	if( context->m_PacketFlags & mHANDLE_FOREIGN )
+	{
+		return HANDLER_MSG_DONE;
+	}
+    
+	time_msg = (MsgPacketStamp *) context->m_Msg;
+	ack_msg.m_Packetstamp = time_msg->m_Packetstamp;
+	//ack_msg.m_Handle = time_msg->m_Handle;
+
+#ifdef DEBUG_MESSAGES
+	if( context->m_Conn->IsRemote())
+	{
+		Dbg_Printf( "(%d) Conn %d: (%d) Got Packetstamp %d\n", 
+			context->m_App->m_FrameCounter, 
+			context->m_Conn->GetHandle(),
+			context->m_App->m_Timestamp, 
+			time_msg->m_Packetstamp );
+	}
+#endif
+	
+	msg_desc.m_Data = &ack_msg;
+	msg_desc.m_Length = sizeof( MsgAck );
+	msg_desc.m_Id = MSG_ID_ACK;
+	context->m_App->EnqueueMessageToServer( &msg_desc );
+
+	// Todo : Change this logic as packetstamps DO wraparound
+	// if == 0, we just connected : accept first timestamp unconditionally
+	if( context->m_Conn->m_latest_packet_stamp == 0 )
+	{
+		context->m_Conn->m_latest_packet_stamp = time_msg->m_Packetstamp;
+	}
+	else
+	{
+		// Check for late packets, but also make sure it's actually late, and not just wraparound
+		if( time_msg->m_Packetstamp < context->m_Conn->m_latest_packet_stamp )
+		{
+			if(( context->m_Conn->m_latest_packet_stamp - time_msg->m_Packetstamp ) > 5000 )
+			{
+				context->m_Conn->m_latest_packet_stamp = time_msg->m_Packetstamp;
+			}
+			else
+			{
+				context->m_App->m_LatePackets++;
+				context->m_App->m_LostPackets--;
+				context->m_PacketFlags |= mHANDLE_LATE;
+			}
+		}
+		else if( time_msg->m_Packetstamp == context->m_Conn->m_latest_packet_stamp )
+		{
+			context->m_App->m_DupePackets++;
+			Dbg_Printf( "**** Got Duplicate Packet: %d\n", context->m_App->m_DupePackets ); 
+			return HANDLER_ERROR;	// don't process dupes
+		}
+		else if(( time_msg->m_Packetstamp - context->m_Conn->m_latest_packet_stamp ) > 1 )
+		{
+			// Check for lost packets and make sure it's not just wraparound
+			if(( time_msg->m_Packetstamp - context->m_Conn->m_latest_packet_stamp ) > 5000 )
+			{
+				context->m_App->m_LatePackets++;
+				context->m_App->m_LostPackets--;
+				context->m_PacketFlags |= mHANDLE_LATE;
+			}
+			else
+			{
+				context->m_App->m_LostPackets += 
+						(( time_msg->m_Packetstamp - context->m_Conn->m_latest_packet_stamp ) - 1 );
+				context->m_Conn->m_latest_packet_stamp = time_msg->m_Packetstamp;  
+			}
+		}
+		else
+		{
+			context->m_Conn->m_latest_packet_stamp = time_msg->m_Packetstamp;  
+		}
+	}
+
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Respond to ping test                                           */
+/*                                                                */
+/******************************************************************/
+
+int	handle_latency_test( MsgHandlerContext *context )
+{
+	MsgDesc msg_desc;
+	MsgTimestamp latency_response_msg;
+    MsgTimestamp *time_msg;
+	Conn *conn;
+
+	Dbg_Assert( context );
+    
+	time_msg = (MsgTimestamp *) context->m_Msg;
+	conn = context->m_Conn;
+    
+	// "Pong" back to the ping
+	latency_response_msg.m_Timestamp = time_msg->m_Timestamp;
+
+	msg_desc.m_Data = &latency_response_msg;
+	msg_desc.m_Length = sizeof( MsgTimestamp );
+	msg_desc.m_Id = MSG_ID_PING_RESPONSE;
+	context->m_App->EnqueueMessage( conn->GetHandle(), &msg_desc );   
+    
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Take note of latency value                                     */
+/*                                                                */
+/******************************************************************/
+
+int	handle_latency_response( MsgHandlerContext *context )
+{
+	MsgTimestamp *time_msg;
+	Conn *conn;
+	int cur_time;
+
+	Dbg_Assert( context );
+
+	time_msg = (MsgTimestamp *) context->m_Msg;
+	conn = context->m_Conn;
+
+	if( conn->IsLocal())
+	{
+		return HANDLER_CONTINUE;
+	}
+	
+	cur_time = context->m_App->m_Timestamp;
+
+	// if it's the one we're waiting for
+	if( time_msg->m_Timestamp == conn->m_latency_test.m_SendTime )
+	{   
+		unsigned int latency_value;
+
+		conn->m_latency_test.m_ReceiveTime = time_msg->m_Timestamp;
+		latency_value = ( cur_time - time_msg->m_Timestamp );
+		if( latency_value > App::MAX_LATENCY )
+		{
+			latency_value = App::MAX_LATENCY;
+		}
+		conn->m_latency_test.InputLatencyValue( latency_value );		
+	}
+
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* We've been accepted to the server                              */
+/*                                                                */
+/******************************************************************/
+
+int	App::handle_connection_accepted( MsgHandlerContext *context )
+{
+	
+#ifdef DEBUG_MESSAGES
+	Dbg_Message( "Connection was accepted!\n" );
+#endif
+	return HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The server rejected our connection request (app_level)         */
+/*                                                                */
+/******************************************************************/
+
+int	Client::handle_connection_refusal( MsgHandlerContext *context )
+{
+	Client *client;
+
+	
+
+	Dbg_Assert( context );
+
+	client = (Client *) context->m_App;
+	// disconnect
+	//client->ConnectToServer( INADDR_ANY, 0 );
+	client->TerminateConnection( context->m_Conn );
+
+	return HANDLER_HALT;
+}
+
+/******************************************************************/
+/* Someone is requesting connection (app-level)                   */
+/*                                                                */
+/******************************************************************/
+
+int	App::handle_connection_request( MsgHandlerContext *context )
+{
+	MsgDesc msg_desc;
+
+	Dbg_Assert( context );
+
+#ifdef DEBUG_MESSAGES
+	Dbg_Message( "Got Connection Request!\n" );
+#endif
+
+	// Redundancy check
+    if( context->m_PacketFlags & mHANDLE_FOREIGN )
+	{
+		int ip;
+
+		if( context->m_App->AcceptsForeignConnections() == false )
+		{
+			return HANDLER_MSG_DONE;
+		}
+
+		ip = context->m_Conn->GetIP();
+        
+#ifndef __PLAT_NGC__
+		/*Dbg_Message( "Accepting connection from IP:%s Port:%d\n", 
+					 inet_ntoa( *(struct in_addr *) &ip), 
+					 context->m_Conn->GetPort());*/
+#endif		// __PLAT_NGC__
+		context->m_App->SendMessageTo( MSG_ID_CONNECTION_ACCEPTED, 0, NULL,
+									   context->m_Conn->GetIP(), context->m_Conn->GetPort(), 0 );
+
+		return HANDLER_CONTINUE;
+	}
+	else if( context->m_Conn->IsLocal())
+	{
+		msg_desc.m_Id = MSG_ID_CONNECTION_ACCEPTED;
+		msg_desc.m_Priority = HIGHEST_PRIORITY;
+		msg_desc.m_Queue = QUEUE_IMPORTANT;
+		context->m_App->EnqueueMessage( context->m_Conn->GetHandle(), &msg_desc );
+
+		return HANDLER_CONTINUE;
+	}
+	
+	return HANDLER_MSG_DONE;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
diff --git a/Code/Gel/Net/Server/netserv.cpp b/Code/Gel/Net/Server/netserv.cpp
new file mode 100644
index 0000000..b5f0e5f
--- /dev/null
+++ b/Code/Gel/Net/Server/netserv.cpp
@@ -0,0 +1,570 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Net (OBJ) 												**
+**																			**
+**	File name:		net.cpp													**
+**																			**
+**	Created:		01/29/01	-	spg										**
+**																			**
+**	Description:	Network Manager code									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+bool	Server::init( void )
+{
+	m_Timestamp = 0;
+	m_connected = false;
+	m_max_connections = 0;
+
+	if( App::init() == false )
+	{
+		return false;
+	}
+    
+#ifdef __PLAT_XBOX__
+	//DWORD dwStatus = XNetGetEthernetLinkStatus();
+	//if( dwStatus == 0 )
+	//{
+		//return false;
+	//}
+
+	// Get host XNADDR (asynchronous).
+	while( XNetGetTitleXnAddr/*XNetGetDebugXnAddr*/( &m_XboxAddr ) == XNET_GET_XNADDR_PENDING )
+	{
+		// Can do other work/rendering here ...
+	}
+
+	// Create session key and register the session with the SNL.
+	XNetCreateKey( &m_XboxKeyId, &m_XboxKey );
+	XNetRegisterKey( &m_XboxKeyId, &m_XboxKey );	// != NO_ERROR	   
+#endif
+
+	m_Dispatcher.AddHandler( MSG_ID_CONNECTION_REQ, handle_connection_request, 
+							 mHANDLE_FOREIGN | mHANDLE_LATE, 
+							 NULL, 
+							 HIGHEST_PRIORITY );
+	m_Dispatcher.AddHandler( MSG_ID_TIMESTAMP, handle_timestamp, mHANDLE_LATE | mHANDLE_FOREIGN );
+	m_Dispatcher.AddHandler( MSG_ID_DISCONN_REQ, handle_disconn_req,mHANDLE_LATE,
+								NULL, HIGHEST_PRIORITY );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Server::deinit( void )
+{
+#ifdef USE_ALIASES
+	int i;
+
+	for( i = 0; i < vNUM_ALIASES; i++ )
+	{
+		if( m_alias_table[i] )
+		{
+			delete [] m_alias_table[i];
+		}
+	}
+#endif
+
+	App::deinit();
+
+#ifdef __PLAT_XBOX__
+	XNetUnregisterKey( &m_XboxKeyId );
+#endif
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/* Server's send function                                         */
+/*                                                                */
+/******************************************************************/
+
+void	Server::SendEnqueuedMessages( Conn* conn )
+{
+	int buf_len;
+	MsgPacketStamp msg;
+	int num_resends;
+	MsgDesc timestamp_msg_desc;
+	bool buffer_full;
+	
+	// Should we send this frame?
+	if(	ShouldSendThisFrame( conn ) == false )
+	{
+		return;
+	}
+
+	buffer_full = false;
+	while( !buffer_full && MessagesToSend( conn ) && !BandwidthExceeded( conn ))
+	{    
+		// Send Latency Tests/Responses if applicable
+		if( ( conn->IsRemote()) &&
+			( conn->IsForeign() == false ) &&
+			( m_flags.TestMask( App::mDYNAMIC_RESEND )))
+		{
+			MsgDesc msg_desc;
+			MsgTimestamp latency_msg;
+			unsigned int cur_time;
+	
+			cur_time = m_Timestamp;
+			latency_msg.m_Timestamp = cur_time;
+			// send out a new latency test, keeping track of the time at which
+			// we sent it
+			if(	( conn->m_latency_test.m_SendTime == 0 ) ||
+				( ( cur_time - conn->m_latency_test.m_SendTime ) > App::MAX_LATENCY ))
+			{   
+				// If we never got a response, simulate an increased latency
+				if( conn->m_latency_test.m_SendTime > conn->m_latency_test.m_ReceiveTime )
+				{
+					unsigned int latency_value;
+	
+					latency_value = conn->GetAveLatency();
+					latency_value += 100;
+	
+					if( latency_value > App::MAX_LATENCY )
+					{
+						latency_value = App::MAX_LATENCY;
+					}
+					conn->m_latency_test.InputLatencyValue( latency_value );
+				}
+#ifdef __PLAT_NGPS__
+				SignalSema( m_send_semaphore_id );
+#endif
+				conn->m_latency_test.m_SendTime = cur_time;
+	
+				msg_desc.m_Data = &latency_msg;
+				msg_desc.m_Length = sizeof( MsgTimestamp );
+				msg_desc.m_Id = MSG_ID_PING_TEST;
+				EnqueueMessage( conn->GetHandle(), &msg_desc );
+#ifdef __PLAT_NGPS__
+				WaitSema( m_send_semaphore_id );
+#endif
+			}
+		}	
+    
+#ifdef __PLAT_NGPS__
+		SignalSema( m_send_semaphore_id );
+#endif
+	
+		msg.m_Packetstamp = (unsigned short) conn->m_latest_sent_packet_stamp;
+		//msg.m_Handle = conn->GetHandle();
+	
+		timestamp_msg_desc.m_Data = &msg;
+		timestamp_msg_desc.m_Length = sizeof( MsgPacketStamp );
+		timestamp_msg_desc.m_Id = MSG_ID_TIMESTAMP;
+		timestamp_msg_desc.m_Priority = HIGHEST_PRIORITY;
+		EnqueueMessage( conn->GetHandle(), ×tamp_msg_desc );
+
+#ifdef __PLAT_NGPS__
+		WaitSema( m_send_semaphore_id );
+#endif
+
+		num_resends = conn->GetNumResends();
+
+		if( !BuildMsgStream( conn, QUEUE_DEFAULT ))
+		{
+			buffer_full = true;
+		}
+		// First, use up our bandwidth with re-sends or else they might remain un-sent
+		// indefinitely, causing a bad backup on the client if he's waiting on 
+		// one particular sequence
+		if( !BuildMsgStream( conn, QUEUE_SEQUENCED, true ))
+		{
+			buffer_full = true;
+		}
+		if( !BuildMsgStream( conn, QUEUE_IMPORTANT, true ))
+		{
+			buffer_full = true;
+		}
+		if( !BuildMsgStream( conn, QUEUE_SEQUENCED ))
+		{
+			buffer_full = true;
+		}
+		if( !BuildMsgStream( conn, QUEUE_IMPORTANT ))
+		{
+			buffer_full = true;
+		}
+
+		// If we had to resend some messages this frame, affect the latency for this client
+		// so that we don't expect so much from their connection.
+		// Also, reduce their estimated bandwidth
+		if( conn->GetNumResends() > num_resends )
+		{
+			conn->m_latency_test.InputLatencyValue( App::MAX_LATENCY );
+			conn->SetBandwidth( conn->GetBandwidth() - 500 );
+			// Make sure that we'll still be able to send at least one large (max-size) packet.
+			// Otherwise, we might get deadlocked.
+			if( conn->GetBandwidth() < ( MAX_UDP_PACKET_SIZE + vUDP_PACKET_OVERHEAD ))
+			{
+				conn->SetBandwidth( MAX_UDP_PACKET_SIZE + vUDP_PACKET_OVERHEAD );
+			}
+		}
+
+		buf_len = conn->m_write_ptr - conn->m_write_buffer;
+		// If there is data to send
+		if( ( buf_len > 0 ) &&
+			( conn->IsRemote()))
+		{
+#ifdef NET_DEBUG_MESSAGES
+			/*Dbg_Printf( "(%d) Conn %d: Sending pstamp %d at time %d\n", 
+							m_FrameCounter, 
+							conn->GetHandle(),
+							conn->m_latest_sent_packet_stamp,
+							m_Timestamp );*/
+#endif																	
+			if ( SendTo( conn->GetIP(), conn->GetPort(), conn->m_write_buffer, buf_len, 0 ))
+			{
+				m_TotalBytesOut += buf_len;
+				conn->GetOutboundMetrics()->AddPacket( buf_len + vUDP_PACKET_OVERHEAD, m_Timestamp );
+				conn->SetForceSendThisFrame( false );
+				conn->m_last_send_time = m_Timestamp;
+			}
+			else
+			{
+				// If it didn't send the messages properly, flag them for resend next frame
+				conn->FlagMessagesForResending( conn->m_latest_sent_packet_stamp );
+				conn->m_write_ptr = conn->m_write_buffer;		
+				break;
+			}
+			
+			conn->m_write_ptr = conn->m_write_buffer;		
+		}
+
+		conn->m_latest_sent_packet_stamp++;
+	}
+
+	// If we're successfully sending data at/near the connection's estimated max bandwidth and there
+	// are no problems, try ramping up the estimated bandwidth for this connection
+	if( BandwidthExceeded( conn ) && ( conn->GetNumResends() == 0 ))
+	{
+		conn->SetBandwidth( conn->GetBandwidth() + 1000 );
+	}
+}
+
+/******************************************************************/
+/* Server's Receive function                                      */
+/*                                                                */
+/******************************************************************/
+
+void	Server::ReceiveData( void )
+{
+	Conn *conn;
+	struct sockaddr from_address;
+	struct sockaddr_in *client_address;
+	int num_bytes, actual_data_len;
+    
+#ifdef __PLAT_NGC__
+	from_address.len = sizeof( sockaddr );
+#else
+	int addr_len = sizeof( from_address );
+#endif
+
+	// Local servers (i.e. non-network play) never really receive. Their data is automatically 
+	// "received" when the local client writes its data into the server's receive buffer transparently
+	if( IsLocal())
+	{
+		return;
+	}
+	
+	do
+	{
+		num_bytes = recvfrom( m_socket, m_in_packet_buffer, 
+			Manager::vMAX_PACKET_SIZE, 0, &from_address, &addr_len );
+		if ( num_bytes < 0 )// == SOCKET_ERROR ) 
+		{
+#if defined( __PLAT_WN32__ ) || defined( __PLAT_XBOX__ )
+			if( WSAGetLastError() != WSAEWOULDBLOCK )
+#else
+#ifdef __PLAT_NGPS__
+			if( sn_errno( m_socket ) != EWOULDBLOCK )
+#else
+#ifdef __PLAT_NGC__
+			if( num_bytes != SO_EWOULDBLOCK )
+#endif
+#endif
+#endif
+			{
+#ifdef NET_DEBUG_MESSAGES
+				Dbg_Printf( "Server Receive Error: " );
+				ReportError();
+#endif
+			}
+
+			break;
+		}
+		else
+		{   
+#ifdef __PLAT_NGPS__
+			WaitSema( m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__
+			m_TotalBytesIn += num_bytes;
+			if( m_flags & App::mSECURE )
+			{
+				actual_data_len = num_bytes - sizeof( unsigned short );	// subtract size of CRC
+			}
+			else
+			{
+				actual_data_len = num_bytes;
+			}
+			
+			client_address = (struct sockaddr_in*) &from_address;
+			//Dbg_Printf( "**** Received data from client 0x%x : %d, addr_len : %d\n", client_address->sin_addr.s_addr,
+						//client_address->sin_port, addr_len );
+			conn = GetConnectionByAddress( client_address->sin_addr.s_addr, 
+											ntohs( client_address->sin_port ));
+			if( conn )
+			{
+				if( conn->IsValid() || (( m_flags & App::mSECURE ) == 0 ))
+				{
+					if( ( conn->m_read_ptr + actual_data_len ) < ( conn->m_read_buffer + Conn::vREAD_BUFFER_LENGTH ))
+					{
+						if( !validate_and_copy_stream( m_in_packet_buffer, conn->m_read_ptr, num_bytes ))
+						{
+	#ifdef __PLAT_NGPS__
+							SignalSema( m_receive_semaphore_id );
+	#endif	// __PLAT_NGPS__
+							
+							// If the game would like to handle this foreign data, allow it to do so now
+							if( m_foreign_handler )
+							{
+								m_foreign_handler( conn->m_read_ptr, num_bytes, &from_address );
+							}
+							continue;
+						}
+                        
+						conn->m_read_ptr += actual_data_len;
+						conn->GetInboundMetrics()->AddPacket( num_bytes + vUDP_PACKET_OVERHEAD, m_Timestamp );
+						conn->m_last_comm_time = Tmr::GetTime();
+					}
+				}
+			}
+			else
+			{
+				Conn* foreign_conn;
+								
+				// Foreign connection. Create temporary connection to process this foreign message
+				foreign_conn = NewConnection( client_address->sin_addr.s_addr, 
+											  ntohs( client_address->sin_port ), 
+											  Conn::mREMOTE |
+											  Conn::mFOREIGN );
+
+				if( foreign_conn )
+				{
+					foreign_conn->m_app = this;
+					if( !validate_and_copy_stream( m_in_packet_buffer, foreign_conn->m_read_ptr, num_bytes ))
+					{
+#ifdef __PLAT_NGPS__
+						SignalSema( m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__
+						// If the game would like to handle this foreign data, allow it to do so now
+						if( m_foreign_handler )
+						{
+							m_foreign_handler( m_in_packet_buffer, num_bytes, &from_address );
+						}
+						delete foreign_conn;
+						continue;
+					}
+					foreign_conn->m_read_ptr = foreign_conn->m_read_buffer + actual_data_len;
+					foreign_conn->Invalidate();
+				}
+			}
+#ifdef __PLAT_NGPS__
+			SignalSema( m_receive_semaphore_id );
+#endif	// __PLAT_NGPS__
+		}       
+	} while( num_bytes > 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef USE_ALIASES
+
+void	Server::AllocateAliasTables( void )
+{
+	int i;
+
+	
+    
+	Dbg_Assert( m_max_connections > 0 );
+
+	for( i = 0; i < vNUM_ALIASES; i++ )
+	{
+		m_alias_table[i] = new AliasEntry[ m_max_connections ];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Server::ClearAliasTables( void )
+{
+	
+
+	int i, j;
+
+	for( i = 0; i < m_max_connections; i++ )
+	{
+		for( j = 0; j < vNUM_ALIASES; j++ )
+		{
+			m_alias_table[j][i].m_Id = ~0;
+			m_alias_table[j][i].m_Expiration = -1;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Server::ClearAliasTable( int handle )
+{
+	
+
+	int i;
+
+	for( i = 0; i < vNUM_ALIASES; i++ )
+	{
+		m_alias_table[i][handle].m_Id = 0;
+		m_alias_table[i][handle].m_Expiration = -1;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+unsigned char	Server::GetObjectAlias( int handle, int obj_id, int cur_time )
+{
+	int hash, i;
+	bool quit;
+
+	hash = obj_id & 0xff;	// mask off all but lowest byte and use as a hash into the table
+	hash %= vNUM_ALIASES;
+	quit = false;
+	i = hash;
+	do
+	{
+		if( m_alias_table[i][handle].m_Id == obj_id )
+		{
+			if(	( m_alias_table[i][handle].m_Expiration == -1 ) ||
+				( cur_time > m_alias_table[i][handle].m_Expiration ))
+			{   
+				return (unsigned char) vNO_ALIAS;
+			}
+			return (unsigned char) i;
+		}
+		
+		i = ( i + 1 ) % vNUM_ALIASES;
+		if( i == hash )	// complete revolution
+		{
+			break;
+		}
+	} while( !quit );
+    
+	return (unsigned char) vNO_ALIAS;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Server::SetObjectAlias( int handle, unsigned char alias, int obj_id, int expiration )
+{
+	m_alias_table[alias][handle].m_Id = obj_id;
+	m_alias_table[alias][handle].m_Expiration = expiration;
+}
+
+#endif // USE_ALIASES
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Server::Server( int flags )
+: App( flags )
+{
+	m_send_network_data_task = new Tsk::Task< App > ( send_network_data, *this, 
+													  Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_SERVER_SEND_NETWORK_DATA );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
diff --git a/Code/Gel/Net/Server/netserv.h b/Code/Gel/Net/Server/netserv.h
new file mode 100644
index 0000000..c44ebc6
--- /dev/null
+++ b/Code/Gel/Net/Server/netserv.h
@@ -0,0 +1,77 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			NET  (Net)												**
+**																			**
+**	File name:		gel/net.h												**
+**																			**
+**	Created: 		01/29/01	-	spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef __NETSERV_H__
+#define __NETSERV_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class Server : public App
+{
+	friend class Manager;	
+public:
+			Server( int flags = 0 );
+
+	void	ReceiveData( void );	
+	void	SendEnqueuedMessages( Conn* conn );
+		
+#ifdef USE_ALIASES
+	// Alias Functionality
+	void	AllocateAliasTables( void );
+	void	ClearAliasTables( void );
+	void	ClearAliasTable( int handle );
+	unsigned char	GetObjectAlias( int handle, int obj_id, int cur_time );
+	void	SetObjectAlias( int handle, unsigned char alias, int obj_id, int expiration );
+
+private:
+	AliasEntry*	m_alias_table[vNUM_ALIASES];
+#endif
+
+private:
+	bool	init( void );
+	void	deinit( void );
+    
+	static	MsgHandlerCode	handle_timestamp;		
+	static	MsgHandlerCode	handle_disconn_req;
+
+	AliasEntry*	m_alias_table[vNUM_ALIASES];
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
+
+#endif // __NETSERV_H__
\ No newline at end of file
diff --git a/Code/Gel/Net/net.cpp b/Code/Gel/Net/net.cpp
new file mode 100644
index 0000000..930b166
--- /dev/null
+++ b/Code/Gel/Net/net.cpp
@@ -0,0 +1,3220 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Net (OBJ) 												**
+**																			**
+**	File name:		net.cpp													**
+**																			**
+**	Created:		01/29/01	-	spg										**
+**																			**
+**	Description:	Network Manager code									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+#ifndef __PLAT_WN32__
+#include 
+#endif
+
+#ifdef __PLAT_XBOX__
+//#include 
+#include 
+#endif
+
+#include 
+
+#ifdef __PLAT_NGPS__
+
+#include 
+#include 
+
+#endif
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+//#define USE_DECI2
+
+#define DEFAULT_SNPS2_SUB_MSK	"255.255.255.0"
+#define DEFAULT_SNPS2_GATEWAY	"192.168.0.1"
+
+const char  spduartArgs[]   = "-nogci\0dial=cdrom0:\\IOP\\DIAL_SPD.CNF;1";
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+
+
+namespace Net
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Network Manager" )
+
+#ifdef __PLAT_NGPS__
+/* Set up the following list with one or more suitable DNS servers   */
+
+/*static const sn_char* dns_servers[] =
+{   
+	    "205.147.0.100",
+		"205.147.0.102",
+		""                // List is terminated by null string
+};*/
+static sn_char custom_built_script[SN_MAX_SCRIPT_LINES][SN_MAX_SCRIPT_LEN+1];
+static const char *s_custom_isp_script[] =
+    {
+        "input 30 ogin:",      /* Line 0 */
+        "output ",             /* Line 1 */
+        "input 10 word:",      /* Line 2 */
+        "output ",             /* Line 3 */
+        "input 10 ing PPP",    /* Line 4 */
+        ""                     /* script is terminated with null string     */
+    };
+
+#define vCUSTOM_USERNAME_LINE  1
+#define vCUSTOM_PASSWORD_LINE  3
+
+static bool s_cancel_dialup_conn;
+static int  s_conn_semaphore;
+#endif // __PLAT_NGPS__
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+#ifdef __PLAT_NGPS__
+
+#define vPOWEROFF_STACK_SIZE		(2 * 1024)
+
+static u_char s_poweroff_stack[vPOWEROFF_STACK_SIZE] __attribute__ ((aligned(16)));
+
+/******************************************************************/
+/* The thread which waits for			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+static void s_power_off_thread(void *arg) 
+{
+	int sid = (int)arg;
+	int stat;
+	while( 1 ) 
+	{
+		WaitSema(sid);
+		// dev9 power off, need to power off PS2
+		//while( sceDevctl("dev9x:", DDIOC_OFF, NULL, 0, NULL, 0 ) < 0 );
+		while( sceDevctl("dev9x:", DDIOC_OFF, NULL, 0, NULL, 0 ) < 0 );
+		// PS2 power off
+		while( !sceCdPowerOff( &stat ) || stat );
+    }
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+static void s_power_off_handler(void *arg) 
+{
+	int sid = (int)arg;
+    iSignalSema(sid);
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+static void s_prepare_power_off(void) 
+{
+    struct ThreadParam tparam;
+    struct SemaParam   sparam;
+    int tid, sid;
+
+	sparam.initCount = 0;
+    sparam.maxCount  = 1;
+    sparam.option    = 0;
+    sid = CreateSema(&sparam);
+
+    tparam.stackSize = vPOWEROFF_STACK_SIZE;
+    tparam.gpReg = &_gp;
+    tparam.entry = s_power_off_thread;
+    tparam.stack = (void *) s_poweroff_stack;
+    tparam.initPriority = 1;
+    tid = CreateThread( &tparam );
+    StartThread( tid, (void *) sid );
+
+    sceCdPOffCallback( s_power_off_handler, (void *) sid );
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+static sn_int32 set_host_name( sn_char* host_name )
+{
+    sndev_set_dhost_type optval;
+    sn_int32 host_size, r;
+
+	
+
+	Dbg_Assert( host_name );
+
+    host_size = strlen(host_name) + 1;
+
+    // Check host_name isn't too big
+    if (host_size > (sn_int32) sizeof(optval.host))
+    {
+        Dbg_Printf("EE:set_host_name():error host_name too big\n");
+        return -1;
+    }
+
+    // I'm going to fill optval with zeros to start with, not strictly
+    // necessary but this is only an example.
+    memset(&optval,0,sizeof(optval));
+
+    // Fill in the optval structure
+    optval.flags = 0;        // Steve, you could set one of these two
+                             // values SN_DFLAG_EXCL_DIS or
+                             // SN_DFLAG_EXCL_REQ in the flags to
+                             // *exclude* either the discovery or
+                             // request message from the operation with
+                             // flags set to zero the option will be
+                             // applied to both messages
+
+	if( host_name[0] == '\0' )
+	{
+		Dbg_Printf( "Clearing Host Name Option\n" );
+		optval.clear_option = 1; // Steve, you would set this to 1 if you
+								 // wanted to remove the host name
+	}
+	else
+	{
+		Dbg_Printf( "Setting Host Name Option to %s\n", host_name );
+		optval.clear_option = 0;
+	}
+    
+
+    optval.include_null = 0; // Steve, if you wanted the null terminator
+                             // included in the host name that's sent in
+                             // the DHCP msg you would set this to 1
+
+    optval.reserved = 0;    // Must be 0
+
+    memcpy(optval.host, host_name, host_size);
+
+    // Send the option to the IOP
+
+    r = sndev_set_options(0, SN_DEV_SET_DHOST, &optval, sizeof(optval));
+
+    if (r != 0)
+    {
+        Dbg_Printf("EE:sndev_set_options():error %d\n",r);
+        return -1;
+    }
+
+    return 0;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::stop_stack( void )
+{
+	
+
+	int result, stack_state;
+
+	Dbg_Printf( "EE:Stopping the TCP/IP stack\n" );
+	
+	result = sn_stack_state(SN_STACK_STATE_STOP, &stack_state);
+
+	if( result != 0 )
+	{
+		Dbg_Printf( "EE:sn_stack_sate() failed %d\n", result );
+		SetError( vRES_ERROR_GENERAL );
+		return false;
+	}
+
+	Dbg_Printf( "EE:Stack Stopped\n" );
+
+	return true;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::start_stack( void )
+{
+	sn_int32	result, stack_state;
+	Tmr::Time 	start_time;
+
+	
+
+	// Start the stack
+    Dbg_Printf("EE:Starting the TCP/IP stack\n");
+
+    result = sn_stack_state( SN_STACK_STATE_START, &stack_state );
+    
+    if( result != 0 )
+    {
+        Dbg_Printf( "EE:sn_stack_sate() failed %d\n", result );
+		SetError( vRES_ERROR_GENERAL );
+        return false;
+    }
+
+	Dbg_Printf( "EE:Stack Started\n" );
+	if(( GetConnectionType() == vCONN_TYPE_MODEM ) ||
+	   ( GetConnectionType() == vCONN_TYPE_PPPOE ))
+	{
+		int modem_state, prev_modem_state;
+		Tmr::Time start_time;
+
+        modem_state      = -1; // Invalid
+		prev_modem_state = -2; // Ivalid and != modem_state
+
+		start_time = Tmr::GetTime();
+		Dbg_Printf( "EE:Calling snmdm_get_state() - until modem ready\n" );
+		while( modem_state != SN_MODEM_READY )
+		{
+			result = snmdm_get_state( &modem_state );
+	
+			if (result != 0)
+			{
+				Dbg_Printf("EE:snmdm_get_state() failed %d\n", result);
+				SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+				return false;
+			}
+	
+			if (modem_state == prev_modem_state)
+			{
+				sn_delay(10);
+			}
+			else
+			{
+				prev_modem_state = modem_state;
+				Dbg_Printf("  Modem state = %d %s\n", modem_state, sntc_str_modem_state( modem_state ));
+			}
+
+			// After 5 seconds, time out and say there was no dialtone
+			if(( Tmr::GetTime() - start_time ) > Tmr::Seconds( 10 ))
+			{
+				SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+				
+				return false;
+			}
+		}
+
+		Dbg_Printf( "EE:Ready\n" );
+	}
+
+
+    // Wait for the stack to come up
+    Dbg_Printf( "EE:Waiting for socket API to be ready\n" );
+	start_time = Tmr::GetTime();
+    while( sn_socket_api_ready() == SN_FALSE )
+	{
+		// Delay to avoid hogging the processor
+		sn_delay( 500 );
+		if(( Tmr::GetTime() - start_time ) > Tmr::Seconds( 10 ))
+		{
+			Dbg_Printf( "EE:Timed out waiting for socket API to be ready\n" );
+			return false;
+		}
+    }
+
+	if( ShouldUseDHCP())
+	{
+		struct hostent* hentp       = NULL;
+		sn_bool         got_ip_addr = SN_FALSE;
+		struct in_addr  ip_addr;
+	
+		Dbg_Printf( "EE:Waiting for DHCP server to supply IP addr etc\n" );
+		start_time = Tmr::GetTime();
+		do
+		{
+			// A way of getting the local IP address
+			hentp = gethostbyname(LOCAL_NAME);
+	
+			if(( hentp != NULL ) && ( hentp->h_addr_list[0] != NULL ))
+			{
+				// Read the IP address from the hostent struct
+				memcpy( &ip_addr,hentp->h_addr_list[0], sizeof( ip_addr ));
+	
+				if( ip_addr.s_addr != 0 )
+				{
+					got_ip_addr = SN_TRUE;
+					Dbg_Printf( "DHCP allocated IP addr %s\n", inet_ntoa( ip_addr ));
+				}
+			}
+	
+			// Delay to avoid hogging the processor
+			if( got_ip_addr == SN_FALSE )
+			{
+				sn_delay(500);
+			}
+			if(( Tmr::GetTime() - start_time ) > Tmr::Seconds( 10 ))
+			{
+				Dbg_Printf( "EE:Timed out waiting for DHCP response\n" );
+				SetError( vRES_ERROR_DHCP );
+				return false;
+			}
+		} while( got_ip_addr == SN_FALSE );
+
+		strcpy( m_local_ip, inet_ntoa( ip_addr ));
+    }
+
+	return true;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::setup_ethernet_params( void )
+{
+	sndev_set_ether_ip_type params;
+	int result;
+
+    if( ShouldUseDHCP())
+    {
+		Dbg_Printf( "\n\n\n*********************** USING DHCP ******************* \n\n\n" );
+        memset( ¶ms,0, sizeof( params ));
+    }
+    else
+    {   
+        inet_aton( GetLocalIP(), (struct in_addr*) ¶ms.ip_addr );
+		Dbg_Printf( "======================== Ip is %d : %d\n", params.ip_addr, htonl( params.ip_addr ));
+        inet_aton( GetSubnetMask(), (struct in_addr*) ¶ms.sub_mask );
+        inet_aton( GetGateway(), (struct in_addr*) ¶ms.gateway );
+	}
+
+    result = sndev_set_options( 0, SN_DEV_SET_ETHER_IP, ¶ms, sizeof(params));
+
+    if( result != 0 )
+    {
+        Dbg_Printf( "EE:Error sndev_set_options() returned %d\n", result );
+		SetError( vRES_ERROR_GENERAL );
+        return false;
+    }
+
+	if( ShouldUseDHCP())
+	{
+		result = set_host_name( m_host_name );
+		if( result != 0 )
+		{   
+			SetError( vRES_ERROR_GENERAL );
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::initialize_device( void )
+{
+	sn_int32            result;
+    sn_int32            device_type;
+    sn_int16            idVendor;
+    sn_int16            idProduct;
+    sn_bool             first_time;
+	Tmr::Time 			start_time;
+	Tmr::Time			timeout;
+
+    // Initialise the socket API, if fails print error and return
+    Dbg_Printf( "EE:Initialising socket API\n" );
+
+    result = sockAPIinit( 6 );
+    if(	( result != 0 ) &&
+		( result != SN_EALRDYINIT ))
+    {
+        Dbg_Printf( "EE:sockAPIinit() failed %d\n", result );
+		SetError( vRES_ERROR_GENERAL );
+        return false;
+    }
+    
+    // If we've already init'd the sockets API, don't re-register this thread
+	// just let it run through the rest of setup
+	if( result != SN_EALRDYINIT )
+	{
+		// Register this thread with the socket API
+		result = sockAPIregthr();
+	
+		if (result != 0)
+		{
+			Dbg_Printf( "EE:sockAPIregthr() failed %d\n", result );
+			SetError( vRES_ERROR_GENERAL );
+			return false;
+		}
+	}
+
+    // Now wait for DECI2 'device' to be 'attached'
+    device_type = SN_DEV_TYPE_NONE;
+    first_time  = SN_TRUE;
+	start_time = Tmr::GetTime();
+	// It takes longer to init the sony modem
+	if( GetDeviceType() == vDEV_TYPE_SONY_MODEM )
+	{
+		timeout = Tmr::Seconds( 15 );
+	}
+	else
+	{
+		timeout = Tmr::Seconds( 10 );
+	}
+	
+    while( device_type == SN_DEV_TYPE_NONE )
+    {
+        result = sndev_get_attached( 0, &device_type, &idVendor, &idProduct );
+
+        if( result != 0 )
+        {
+            Dbg_Printf( "EE:sndev_get_attached() failed %d\n", result );
+			SetError( vRES_ERROR_GENERAL );
+            return false;
+        }
+
+        if( device_type == SN_DEV_TYPE_NONE )
+        {
+            if( first_time == SN_TRUE )
+            {
+                first_time = SN_FALSE;
+                Dbg_Printf( "EE:Waiting for network device to be attached ...\n" );
+            }
+            sn_delay( 10 );
+        }
+
+		if(( Tmr::GetTime() - start_time ) > timeout )
+		{
+			Dbg_Printf( "EE:Timed out waiting for network device to be attached\n" );
+			break;
+		}
+    }
+
+	switch( device_type )
+	{
+		case SN_DEV_TYPE_DECI2:
+			Dbg_Printf( "Using DECI-2 ethernet emulation\n" );
+			break;
+		case SN_DEV_TYPE_USB_MODEM:
+			Dbg_Printf("EE:USB-Modem Attached (idVendor=0x%04X idProduct=0x%04X)\n",
+					((int)idVendor)  & 0xFFFF,
+					((int)idProduct) & 0xFFFF);
+			break;
+		case SN_DEV_TYPE_USB_ETHER:
+			Dbg_Printf("EE:USB-Ethernet Attached (idVendor=0x%04X idProduct=0x%04X)\n",
+					((int)idVendor)  & 0xFFFF,
+					((int)idProduct) & 0xFFFF);
+			break;
+		default:
+		{
+			if( idVendor == 0 )
+			{
+				Dbg_Printf( "Unknown Device %d\n", device_type );
+				SetError( vRES_ERROR_UNKNOWN_DEVICE );
+			}
+			else
+			{
+				Dbg_Assert( idVendor == 1 );
+				SetError( vRES_ERROR_DEVICE_NOT_HOT );
+			}
+			
+			return false;
+		}
+	}
+    
+	return true;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::sn_stack_setup( void )
+{
+	// Set up the list of DNS servers
+	char* dns_servers[3];
+	int result, i;
+	sn_int32	stack_state;
+		
+	
+
+	sn_stack_state( SN_STACK_STATE_READ, &stack_state );
+	if( stack_state == SN_STACK_STATE_START )
+	{
+		if( stop_stack() == false )
+		{
+			return false;
+		}
+	}
+		
+	if( GetConnectionType() == vCONN_TYPE_ETHERNET ) 
+	{
+		if(( setup_ethernet_params() == false ))
+		{
+			return false;
+		}
+	}
+
+	for( i = 0; i < 3; i++ )
+	{
+		dns_servers[i] = m_dns_servers[i];
+		Dbg_Printf( "DNS Server %d is %s\n", i, dns_servers[i] );
+	}
+	result = sntc_set_dns_server_list((const sn_char**) dns_servers);
+
+	if( result != 0 )
+	{
+		Dbg_Printf( "EE:sntc_set_dns_server_list() failed %d\n", result );
+		SetError( vRES_ERROR_GENERAL );
+		
+		return false;
+	}
+
+	if( GetConnectionType() == vCONN_TYPE_PPPOE )
+	{
+		sndev_set_pppoe_opt_type pppoe;
+
+		// Enable PPPoE
+		pppoe.flags = 1;
+
+		Dbg_Printf( "Enabling PPPoE\n" );
+		result = sndev_set_options( 0, SN_DEV_SET_PPPOE_OPT, &pppoe, sizeof(pppoe));
+
+		Dbg_Printf( "EE:sndev_set_options(pppoe) returned %d\n", result );
+	}
+	if(( GetConnectionType() == vCONN_TYPE_MODEM ) ||
+	   ( GetConnectionType() == vCONN_TYPE_PPPOE ))
+	{
+		// Now, we have a valid modem. Try to initialize
+		//result = snmdm_set_mdm_init( "AT&F S0=0" );
+		result = snmdm_set_mdm_init( "AT&F S0=0 W2" );
+	
+		if( result != 0 )
+		{
+			Dbg_Printf( "EE:snmdm_set_mdm_init() failed %d\n", result );
+			SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+			
+			return false;
+		}
+
+		Dbg_Printf( "EE:snmdm_set_mdm_init() worked ok\n" );
+	}
+
+    if( start_stack() == false )
+	{
+		return false;
+	}
+
+	m_options_changed = false;
+	return true;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+static sn_int32 custom_construct_script( sn_int32 isp_type, sn_char* user_name, 
+										 sn_char* password)
+{
+    sn_int32  line_index;
+    sn_bool   copy_done;
+
+    copy_done  = SN_FALSE;
+    line_index = 0;
+
+    // Copy the preset script to custom_built_script
+    while( copy_done == SN_FALSE )
+    {
+        // Copy this line of the script
+        strcpy( custom_built_script[line_index], s_custom_isp_script[line_index] );
+
+        // If this line was a null string then finished copy
+        if( s_custom_isp_script[line_index][0] == 0 )
+		{
+             copy_done = SN_TRUE;
+		}
+        else
+		{
+			line_index++;
+		}
+    }
+
+    sn_strcat( custom_built_script[vCUSTOM_USERNAME_LINE], user_name );
+    sn_strcat( custom_built_script[vCUSTOM_USERNAME_LINE], "\\r" );
+
+    // Concatenate password\r on to line index CUSTOM_PASSWORD_LINE
+    sn_strcat( custom_built_script[vCUSTOM_PASSWORD_LINE], password );
+    sn_strcat( custom_built_script[vCUSTOM_PASSWORD_LINE], "\\r" );
+
+    return 0; // Success
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+sn_int32 custom_connect_modem(	sn_char*               phone_no,
+								sn_int32               isp_type,
+								sn_char*               user_name,
+								sn_char*               password,
+								sn_int32               timeout_secs,
+								sntc_mdmstate_callback callback )
+{
+    sn_int32           result;
+    sn_int32           modem_state;
+    sn_bool            done_script;
+    sn_int32           script_index;
+    sn_int32           prev_modem_state;
+    sn_bool            connect_started;
+    sn_int32           timeout_ms;
+    sn_int32           connect_err;
+    
+    result = snmdm_set_phone_no( phone_no );
+
+    // Check that the above function call worked ok
+    if( result != 0 )
+    {
+		Dbg_Printf( "Failed to set phone number: %d\n", result );
+        return SNTC_ERR_MDMAPI;
+    }
+
+    // Construct the log in script file (in custom_built_script)
+    result = custom_construct_script( isp_type, user_name, password );
+    if( result != 0 )
+    {
+        Dbg_Printf( "Failed to build login script: %d\n", result );
+        return SNTC_ERR_BSCRIPT;
+    }
+
+    // Store the log in script file via the modem API
+    // Send a null string first, this resets the script write ptr to 0,
+    // it should already be at 0, but just being defensive.
+    result = snmdm_set_script("");
+    if( result != 0 )
+    {
+		Dbg_Printf( "snmdm_set_script() failed: %d\n", result );
+        return SNTC_ERR_MDMAPI;
+    }
+
+    // Now send the script file to the modem API
+    script_index = 0;
+    done_script  = SN_FALSE;
+    while( done_script == SN_FALSE )
+    {
+        result = snmdm_set_script( custom_built_script[ script_index ] );
+        if( result != 0 )
+        {
+			Dbg_Printf( "snmdm_set_script() failed: %d\n", result );
+            return SNTC_ERR_MDMAPI;
+        }
+
+        // Check for null line, which is last line
+        if( custom_built_script[script_index][0] == 0 )
+		{
+			 done_script = SN_TRUE;
+		}
+		else
+		{
+			script_index++;
+		}
+    }
+
+    // Everything is ready, so ask the modem to connect
+	result = snmdm_connect();
+    if( result != 0 )
+    {
+		Dbg_Printf( "snmdm_connect() failed: %d\n", result );
+        return SNTC_ERR_MDMAPI;
+    }
+
+    // Now wait for the modem to become connected
+    modem_state      = -1; /* Invalid */
+    prev_modem_state = -2; /* Ivalid and != modem_state */
+    connect_started  = SN_FALSE;
+    timeout_ms       = timeout_secs * 1000;
+
+    while(( modem_state != SN_MODEM_PPP_UP ) &&
+		  ( s_cancel_dialup_conn == false ))
+    {
+
+        // Get the current state of the modem link
+        result = snmdm_get_state(&modem_state);
+        if( result != 0 )
+        {
+			Dbg_Printf( "snmdm_get_state() failed: %d\n", result );
+            return SNTC_ERR_MDMAPI;
+        }
+
+        // Monitor for modem connection process starting, then if it
+        // goes back to ready we know it's failed to connect.
+        if( connect_started == SN_FALSE )
+        {
+            // Any of the following means modem connection started
+            if(  (modem_state == SN_MODEM_DIALING)
+               ||(modem_state == SN_MODEM_LOGIN)
+               ||(modem_state == SN_MODEM_CONNECTED)
+               ||(modem_state == SN_MODEM_PPP_UP))
+            {
+                connect_started = SN_TRUE;
+            }
+        }
+        else
+        {
+            // Having started the connection, then unless it's in
+            // one of the following states it's failed to connect
+            if(  (modem_state != SN_MODEM_DIALING)
+               &&(modem_state != SN_MODEM_LOGIN)
+               &&(modem_state != SN_MODEM_CONNECTED)
+               &&(modem_state != SN_MODEM_PPP_UP))
+            {
+                // Read the reason why the connect failed.
+                result = snmdm_get_connect_err( &connect_err );
+                if( result != 0 )
+                {
+					Dbg_Printf( "snmdm_get_connect_err() failed: %d\n", result );
+                    return SNTC_ERR_MDMAPI;
+                }
+
+                switch( connect_err )
+                {
+                    case SN_CONERR_BUSY:
+                       Dbg_Printf( "Busy\n" );
+                       return SNTC_ERR_BUSY;
+                    break;
+
+                    case SN_CONERR_NOCARRIER:
+                       Dbg_Printf( "No carrier\n" );
+                       return SNTC_ERR_NOCARRIER;
+                    break;
+
+					case SN_CONERR_NOANSWER:
+						Dbg_Printf( "No answer\n" );
+						return SNTC_ERR_NOANSWER;
+                    break;
+
+					case SN_CONERR_NODIALTONE:
+						Dbg_Printf( "No dialtone\n" );
+						return SNTC_ERR_NODIALTONE;
+                    break;
+
+                    default:
+                        Dbg_Printf( "connect started then modem state=sntc_str_modem_state(modem_state)\n" );
+                        return SNTC_ERR_CONNECT;
+                }
+            }
+        }
+
+        // Now check whether the modem state has changed since the
+        // previous time round this loop, and if so call the user
+        // callback function (unless it's NULL), otherwise check
+        // for time-out / do a delay
+        if( modem_state != prev_modem_state )
+        {
+            prev_modem_state = modem_state;
+            if( callback != NULL )
+			{
+				(*callback)(modem_state);
+			}
+        }
+        else
+        {
+            if( timeout_ms <= 0 )
+            {
+				Dbg_Printf( "Connect timed out in modem state %s\n", sntc_str_modem_state(modem_state) );
+                return SNTC_ERR_TIMEOUT;
+            }
+
+            sn_delay( 10 );
+            timeout_ms -= 10;
+        }
+    }
+
+    // If we get to here, the modem has successfully connected
+    Dbg_Printf( "Connected!\n" );
+    
+	return 0;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::threaded_modem_conn( void *data )
+{
+	
+	Manager* man = (Manager *) data;
+	int result;
+    
+	Dbg_Printf( "Registering modem thread %d with stack\n", GetThreadId());
+	
+	result = sockAPIregthr();
+	Dbg_Assert( result == 0 );
+
+	WaitSema( s_conn_semaphore );
+
+	Dbg_Printf("EE:Calling sntc_connect_modem()\n");
+
+	// Set the script write pointer back to zero
+    result = snmdm_set_script("");
+
+	// Set up Authentication parameters.
+	sndev_set_chap_type ChapOptions = {0};
+	
+    ChapOptions.accept_chap = man->ShouldUseDialupAuthentication();
+	Dbg_Printf( "Setting chap to %d\n", ChapOptions.accept_chap );
+	ChapOptions.require_chap = 0;
+	strcpy(ChapOptions.locl_name, man->m_isp_user_name );
+	strcpy(ChapOptions.locl_secr, man->m_isp_password );
+	strcpy(ChapOptions.chal_name,"*"); // Accept any challenge name
+	
+	Dbg_Printf( "Chap username: %s password: %s\n", man->m_isp_user_name, man->m_isp_password );
+	result = sndev_set_options( 0, SN_DEV_SET_CHAP, &ChapOptions, sizeof(ChapOptions));
+	Dbg_Printf( "EE:sndev_set_options(SN_DEV_SET_CHAP) returned %d\n", result );
+
+	sndev_set_pap_type PapOptions = {0};
+
+	PapOptions.accept_pap = man->ShouldUseDialupAuthentication();
+	Dbg_Printf( "Setting pap to %d\n", PapOptions.accept_pap );
+	PapOptions.require_pap = 0;
+	strcpy(PapOptions.locl_name, man->m_isp_user_name );
+	strcpy(PapOptions.locl_pass, man->m_isp_password );
+	Dbg_Printf( "Pap username: %s password: %s\n", man->m_isp_user_name, man->m_isp_password );
+
+	result = sndev_set_options( 0, SN_DEV_SET_PAP, &PapOptions, sizeof(PapOptions));
+	Dbg_Printf( "EE:sndev_set_options(SN_DEV_SET_PAP) returned %d\n", result );
+
+	man->m_modem_err = 0;
+	if( man->GetConnectionType() == vCONN_TYPE_MODEM )
+	{   
+		Dbg_Printf( "Dialing %s user: %s pass: %s\n", man->m_isp_phone_no, man->m_isp_user_name, man->m_isp_password );
+		result = custom_connect_modem
+				 ( man->m_isp_phone_no,			// phone_no
+				   SNTC_ISP_GENERIC,		// isp_type
+				   man->m_isp_user_name,			// user_name
+				   man->m_isp_password,			// password
+				   vMODEM_CONNECT_TIMEOUT,	// timeout_secs
+				   man->conn_modem_state_callback );// callback
+	}
+	else if( man->GetConnectionType() == vCONN_TYPE_PPPOE )
+	{
+		result = custom_connect_modem
+				 ( "",			// phone_no
+				   0,		// isp_type
+				   "",			// user_name
+				   "",			// password
+				   vMODEM_CONNECT_TIMEOUT,	// timeout_secs
+				   man->conn_modem_state_callback );// callback
+	}
+	// Check whether connection succeeded
+	if( result == 0 )
+	{
+        sn_int32 result, statval, statlen;
+
+		statval = 1234; // so can see it's modified
+		statlen = sizeof(statval);
+
+        result = sndev_get_status(0, SN_DEV_STAT_BAUD, &statval, &statlen);
+        man->m_modem_baud_rate = statval;
+		
+		man->m_online = true;
+	}
+	else
+	{   
+		sntc_disconnect_modem(	vMODEM_DISCONNECT_TIMEOUT,	// timeout_secs
+								NULL,		// callback
+								NULL );				// error_message
+		man->SetModemState( vMODEM_STATE_ERROR );
+		man->m_modem_err = result;
+		Dbg_Printf( "EE:sntc_connect_modem() failed: %d\n", result );
+		man->m_online = false;
+	}   
+
+	Dbg_Printf( "DeRegistering modem thread %d with stack\n", GetThreadId());
+	sockAPIderegthr();
+	SignalSema( s_conn_semaphore );
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::threaded_modem_disconn( void *data )
+{
+	
+	Manager* man = (Manager *) data;
+	sn_char* err_msg_ptr;
+	bool modem_disconnected;
+	int result;
+    
+	Dbg_Printf( "Registering modem thread %d with stack\n", GetThreadId());
+	
+	result = sockAPIregthr();
+	Dbg_Assert( result == 0 );
+		
+	modem_disconnected = false;
+	if( man->m_online )
+	{
+		Dbg_Printf( "EE:Calling sntc_disconnect_modem()\n" );
+		result = sntc_disconnect_modem(	vMODEM_DISCONNECT_TIMEOUT,	// timeout_secs
+										disconn_modem_state_callback,		// callback
+										&err_msg_ptr );				// error_message
+	
+		// Check whether disconnection succeeded
+		if (result == 0)
+		{
+			Dbg_Printf("EE:sntc_disconnect_modem() worked:%s\n",err_msg_ptr);
+			modem_disconnected = true;
+		}
+		else
+		{
+			Dbg_Printf("EE:sntc_disconnect_modem() failed:%s\n",err_msg_ptr);
+			modem_disconnected = false;
+		}
+	}
+
+	// If the modem didn't connect and disconnect ok, then reset it
+	if( !modem_disconnected )
+	{
+		bool modem_reset;
+
+		do
+		{
+			Dbg_Printf( "EE:Calling sntc_reset_modem()\n" );
+
+			result = sntc_reset_modem
+						(	vMODEM_RESET_TIMEOUT,	// timeout_secs
+							disconn_modem_state_callback,	// callback
+							&err_msg_ptr );     	// error_message
+
+			// Check whether reset succeeded
+			if( result == 0 )
+			{
+				Dbg_Printf( "EE:sntc_reset_modem() worked:%s\n", err_msg_ptr );
+				modem_reset = true;
+			}
+			else
+			{
+				Dbg_Printf( "EE:sntc_reset_modem() failed:%s\n", err_msg_ptr );
+				modem_reset = false;
+				// If their modem is no longer plugged in, just consider it "hung up"
+				if( result == SNTC_ERR_NOMODEM )
+				{
+					break;
+				}
+				sn_delay( 1000 ); // avoid excessive printf if unplugged
+			}
+		} while( !modem_reset );
+	}
+	
+	man->SetModemState( vMODEM_STATE_DISCONNECTED );
+	man->m_online = false;
+
+	Dbg_Printf( "DeRegistering modem thread %d with stack\n", GetThreadId());
+	sockAPIderegthr();
+}
+
+#endif	// __PLAT_NGPS__
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/* Add logic tasks to the current task list                       */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::AddLogicTasks( App* app )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+    
+	mlp_manager->AddLogicTask( app->GetReceiveDataTask());
+	mlp_manager->AddLogicTask( app->GetSendDataTask());
+	mlp_manager->AddLogicTask( app->GetProcessDataTask());
+	mlp_manager->AddLogicTask( app->GetNetworkMetricsTask());
+}
+
+/******************************************************************/
+/* Add logic tasks to the push logic task list                    */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::AddLogicPushTasks( App* app )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+
+	app->GetSendDataTask().Remove();
+	app->GetReceiveDataTask().Remove();
+	app->GetProcessDataTask().Remove();
+	app->GetNetworkMetricsTask().Remove();
+
+	mlp_manager->AddLogicPushTask( app->GetReceiveDataTask());
+	mlp_manager->AddLogicPushTask( app->GetSendDataTask());
+	mlp_manager->AddLogicPushTask( app->GetProcessDataTask());
+	mlp_manager->AddLogicPushTask( app->GetNetworkMetricsTask());
+}
+
+/******************************************************************/
+/* Removes network logic tasks                    				  */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::RemoveNetworkTasks( App* app )
+{
+	app->GetSendDataTask().Remove();
+	app->GetReceiveDataTask().Remove();
+	app->GetProcessDataTask().Remove();
+	app->GetNetworkMetricsTask().Remove();
+}
+
+/******************************************************************/
+/* Creates a new Server at the given address and port			  */
+/*                                                                */
+/******************************************************************/
+
+Server*	Manager::CreateNewAppServer( int id, char* appName, int max_clients, 
+									  unsigned short port, int address, int flags )
+{
+	Server *new_app;
+		
+	
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	// If we're using DHCP, we should have gotten our real IP address by now
+	// from the DHCP server. Use that address from now on
+	if( ShouldUseDHCP())
+	{
+		address = inet_addr( m_local_ip );		
+	}
+
+	new_app = new Server( flags );
+	new_app->m_net_man = this;
+	new_app->init();
+    
+	new_app->m_id = id;	
+	strncpy( new_app->m_name, appName, MAX_LEN_APP_NAME );
+	new_app->m_max_connections = max_clients;
+    
+	m_net_servers.AddToTail( &new_app->m_node );
+	if( new_app->IsLocal())
+	{                  
+		new_app->m_connected = true;
+	}
+	else
+	{
+		new_app->bind_app_socket( address, port );
+		if( flags & App::mBROADCAST )
+		{
+			new_app->m_connected = true;
+		}
+	}
+
+#ifdef USE_ALIASES
+	if( flags & App::mALIAS_SUPPORT )
+	{
+		new_app->AllocateAliasTables();
+		new_app->ClearAliasTables();
+	}
+#endif
+    
+	AddLogicTasks( new_app );
+
+#ifndef __PLAT_NGC__
+    Dbg_Printf( "Created new: %s server %p, Max Clients: %d, IP: %s, Port: %d", appName, new_app, max_clients,
+		   					inet_ntoa( *(struct in_addr*) &address ), port );
+#endif		// __PLAT_NGC__
+
+	m_num_apps++;
+	
+	Mem::Manager::sHandle().PopContext();
+	return new_app;
+}
+
+/******************************************************************/
+/* Creates a new client socket									  */
+/*                                                                */
+/******************************************************************/
+
+Client	*Manager::CreateNewAppClient( 	int id, char* appName, unsigned short port, int address,
+										int flags )
+{
+	Client *new_app;
+
+	
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	// If we're using DHCP, we should have gotten our real IP address by now
+	// from the DHCP server. Use that address from now on
+	if( ShouldUseDHCP())
+	{
+		address = inet_addr( m_local_ip );
+	}
+
+	new_app = new Client( flags );
+	strncpy( new_app->m_name, appName, MAX_LEN_APP_NAME );
+	new_app->m_net_man = this;
+    new_app->init();
+
+	new_app->m_id = id; 
+	new_app->m_max_connections = 1;
+    
+	m_net_clients.AddToTail( &new_app->m_node );
+	if( !new_app->IsLocal())
+	{
+#ifndef __PLAT_XBOX__
+		new_app->bind_app_socket( address, port );
+#endif
+	}
+	
+#ifdef USE_ALIASES
+	new_app->ClearAliasTable();
+#endif
+    
+	AddLogicTasks( new_app );
+
+	Dbg_Printf( "Created new: %s client %p on port %d\n", appName, new_app, port );
+	m_num_apps++;
+
+	Mem::Manager::sHandle().PopContext();
+	return new_app;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::DestroyApp( App *app )
+{
+	app->m_node.Remove();
+	app->ShutDown();
+	delete app;
+	m_num_apps--;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+Metrics::Metrics( void )
+{
+	m_num_packets = 0;
+	m_total_bytes = 0;
+	m_bytes_per_sec = 0;
+	memset( m_num_messages, 0, sizeof( int ) * MAX_MSG_IDS );
+	memset( m_size_messages, 0, sizeof( int ) * MAX_MSG_IDS );
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Metrics::CalculateBytesPerSec( int cur_time )
+{
+	int i, num_bytes;
+		
+	num_bytes = 0; 
+	// Sum up number of bytes transferred over the last second
+	for( i = 0; i < vNUM_BUFFERED_PACKETS; i++ )
+	{
+		if( i >= m_num_packets )
+		{
+			break;
+		}
+
+		if(( cur_time - m_packets[i].GetTime()) < (int) Tmr::Seconds( 1 ))
+		{
+			num_bytes += m_packets[i].GetNumBytes();
+		}
+	}
+
+	m_bytes_per_sec = num_bytes;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Metrics::GetBytesPerSec( void )
+{
+	return m_bytes_per_sec;
+}
+	
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Metrics::GetTotalBytes( void )
+{
+	return m_total_bytes;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Metrics::GetTotalMessageData( int msg_id )
+{
+	return m_size_messages[ msg_id ];
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Metrics::GetTotalNumMessagesOfId( int msg_id )
+{
+	return m_num_messages[ msg_id ];
+}
+	
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Metrics::AddPacket( int size, int time )
+{
+	int index;
+
+	index = m_num_packets % vNUM_BUFFERED_PACKETS;
+
+	m_packets[ index ].SetNumBytes( size );
+	m_packets[ index ].SetTime( time );
+	m_total_bytes += size;
+	m_num_packets++;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Metrics::AddMessage( int msg_id, int size )
+{
+	m_num_messages[ msg_id ]++;
+	m_size_messages[ msg_id ] += size;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		PacketInfo::GetNumBytes( void )
+{
+	return m_num_bytes;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		PacketInfo::GetTime( void )
+{
+	return m_time;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	PacketInfo::SetNumBytes( int size )
+{
+	m_num_bytes = size;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+	
+void	PacketInfo::SetTime( int time )
+{
+	m_time = time;
+}
+
+/******************************************************************/
+/* Iterator                                                       */
+/*                                                                */
+/******************************************************************/
+
+Server *Manager::FirstServer( Lst::Search< App > *sh )
+{
+	
+
+	Dbg_Assert( sh );
+
+	return((Server*) sh->FirstItem( m_net_servers ));
+}
+
+/******************************************************************/
+/* Iterator                                                       */
+/*                                                                */
+/******************************************************************/
+
+Client *Manager::FirstClient( Lst::Search< App > *sh )
+{
+	
+
+	Dbg_Assert( sh );
+
+	return((Client*) sh->FirstItem( m_net_clients ));
+}
+
+/******************************************************************/
+/* Iterator                                                       */
+/*                                                                */
+/******************************************************************/
+
+App *Manager::NextApp( Lst::Search< App > *sh )
+{
+	
+
+	Dbg_Assert( sh );
+
+	return( sh->NextItem());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::NumApps( void )
+{
+	return m_num_apps;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __PLAT_NGC__
+static	void*    s_so_alloc( u32 name, s32 size )
+{
+	return Mem::Malloc( size );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static	void     s_so_free( u32 name, void* ptr, s32 size )
+{
+	Mem::Free( ptr );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::initialize_ngc( void )
+{
+	int result;
+    SOConfig config;
+
+	memset( &config, 0, sizeof( SOConfig ));
+	
+	config.vendor = SO_VENDOR_NINTENDO;
+	config.version = SO_VERSION;
+	config.alloc = s_so_alloc;
+	config.free = s_so_free;
+	if( ShouldUseDHCP())
+    {
+		config.flag = SO_FLAG_DHCP;
+    }
+    else
+    {   
+		config.flag = 0;
+
+        inet_aton( GetLocalIP(), (struct in_addr*) &config.addr );
+        inet_aton( GetSubnetMask(), (struct in_addr*) &config.netmask );
+        inet_aton( GetGateway(), (struct in_addr*) &config.router );
+		inet_aton( GetDNSServer( 0 ), (struct in_addr*) &config.dns1 );
+		inet_aton( GetDNSServer( 1 ), (struct in_addr*) &config.dns2 );
+	}
+
+	result = SOStartup( &config );
+	if( result != 0 )
+	{
+		SOCleanup();
+		return false;
+	}
+
+	return true;
+}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGPS__
+
+bool	Manager::load_irx_files( void )
+{
+
+//////////////////////////////
+#if 0	// MOVED TO SIOMAN.CPP
+
+	int result;
+
+	// Load the stack IRX file
+#ifdef __NOPT_DEBUG__
+	result = SIO::LoadIRX( "SNSTKDBG" );
+#else
+	result = SIO::LoadIRX( "SNSTKREL" );
+#endif
+
+    if( result < 0 )
+    {
+		Dbg_MsgAssert( 0,( "EE:Can't load module snstkrel/dbg.irx. Error : %d\n", result ));
+		SetError( vRES_ERROR_INVALID_IRX );
+		
+		return false;
+	}
+#endif	
+//////////////////////////////
+
+
+
+#ifdef USE_DECI2
+    // Load the DECI2 driver IRX file
+    SIO::LoadIRX( "sndrv000" );
+#else
+
+	switch( GetConnectionType())
+	{
+		case vCONN_TYPE_PPPOE:
+		{
+			switch( GetDeviceType())
+			{
+				case vDEV_TYPE_USB_ETHERNET:
+				{
+					// Load the PPPoE Driver IRX file
+					if( SIO::LoadIRX( "sndrv200" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					// Load the USB-Ethernet Driver IRX file
+					if( SIO::LoadIRX( "sndrv201" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					break;
+				}
+				case vDEV_TYPE_PC_ETHERNET:
+				{
+					// Load the PPPoE Driver IRX file
+					if( SIO::LoadIRX( "sndrv200" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					// Load the SN Wrapper (PPPoE variant) for Sony Ether
+					if( SIO::LoadIRX( "sndrv202" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					} 
+					// Load the Sony pcmcia irx
+					if( SIO::LoadIRX( "dev9" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					s_prepare_power_off();
+					// Load the Sony Ethernet driver IRX file
+					if( SIO::LoadIRX( "smap", 0, NULL, false ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					break;
+				}
+				default:
+					SetError( vRES_ERROR_GENERAL );
+					return false;
+			}
+			break;
+		}
+		case vCONN_TYPE_ETHERNET:
+		{
+			switch( GetDeviceType())
+			{
+				case vDEV_TYPE_USB_ETHERNET:
+				{
+					// Load the USB-Ethernet Driver IRX file
+					if( SIO::LoadIRX( "sndrv001" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					break;
+				}
+				case vDEV_TYPE_PC_ETHERNET:
+				{
+                    if( SIO::LoadIRX( "sndrv100" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					// Load the Sony pcmcia irx
+					if( SIO::LoadIRX( "dev9" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					s_prepare_power_off();
+					// Load the Sony Ethernet driver IRX file
+					if( SIO::LoadIRX( "smap", 0, NULL, false ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					break;
+				}
+				default:
+					SetError( vRES_ERROR_GENERAL );
+					return false;
+			}
+			break;
+		}
+		case vCONN_TYPE_MODEM:
+		{
+			switch( GetDeviceType())
+			{
+				case vDEV_TYPE_USB_MODEM:
+				{
+					// Load the USB-Modem Driver IRX file
+					if( SIO::LoadIRX( "sndrv002" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					break;
+				}
+
+				case vDEV_TYPE_SONY_MODEM:
+				{
+					if (Config::PAL())
+					{
+						// Sony Modem Not Supported in PAL territories
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					
+					// Load the SN modem wrapper irx
+					if( SIO::LoadIRX( "sndrv101" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					// Load the Sony pcmcia irx
+					if( SIO::LoadIRX( "dev9" ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					s_prepare_power_off();
+                    // Load the Sony modem driver irx
+					if( SIO::LoadIRX( "spduart", sizeof(spduartArgs),  (char*) spduartArgs ) < 0 )
+					{
+						SetError( vRES_ERROR_DEVICE_NOT_CONNECTED );
+						return false;
+					}
+					break;
+				}
+				default:
+					SetError( vRES_ERROR_GENERAL );
+					return false;
+			}
+			break;
+		}
+		default:
+			// No valid device specified
+			SetError( vRES_ERROR_GENERAL );
+			return false;
+	}
+	
+#endif	// USE_DECI2
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::initialize_ps2( void )
+{
+	
+	
+	bool result;
+
+	Dbg_Printf( "initializing PS2\n" );
+    
+    if( m_net_drivers_loaded == false )
+	{   
+		bool success;
+
+		File::StopStreaming( );
+		if ( Pcm::UsingCD( ) )
+		{
+			Dbg_MsgAssert( 0,( "Can't load IRX modules when CD is busy." ));
+			return false;
+		}
+	
+		Dbg_Printf( "initializing PS2_2\n" );
+		success = load_irx_files();
+		m_net_drivers_loaded = true;
+		if( success == false )
+		{
+			return false;
+		}
+	}
+	else
+	{
+		if( m_device_changed )
+		{
+			SetError( vRES_ERROR_DEVICE_CHANGED );
+			return false;
+		}
+	}
+    
+	result = initialize_device();
+	if( result == false )
+	{
+		return false;
+	}
+
+	if( !m_stack_setup || m_options_changed )
+	{
+		m_stack_setup = sn_stack_setup();
+	}
+
+	m_device_changed = false;
+	return m_stack_setup;
+}
+
+#endif	// __PLAT_NGPS__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::Manager( void )
+{	
+	int msg_id, i;
+
+#if( defined ( __PLAT_WN32__ ) || defined ( __PLAT_XBOX__ ))
+	int err;
+	WORD version_required;
+	WSADATA wsa_data;
+
+#ifdef __PLAT_XBOX__
+	XNetStartupParams xnsp;
+	ZeroMemory( &xnsp, sizeof(xnsp) );
+	xnsp.cfgSizeOfStruct = sizeof(xnsp);
+
+#ifdef __NOPT_NOASSERTIONS__
+	xnsp.cfgFlags = 0;
+#else
+	xnsp.cfgFlags = XNET_STARTUP_BYPASS_SECURITY;// | XNET_STARTUP_BYPASS_DHCP;
+#endif
+
+	err = XNetStartup( &xnsp );
+	if( err )
+	{
+		XNetCleanup();
+		return;
+	}	
+#endif
+
+	version_required = MAKEWORD( 2, 2 );
+	if( err = WSAStartup ( version_required, &wsa_data ))
+	{
+		Dbg_MsgAssert( 0,( "Failed to start WinSock\n" ));
+		WSACleanup();	
+#ifdef __PLAT_XBOX__
+		XNetCleanup();
+#endif
+		return;
+	}	
+	if (	( LOBYTE( wsa_data.wVersion ) != 2 ) ||
+			( HIBYTE( wsa_data.wVersion ) != 2 ))
+	{
+		Dbg_MsgAssert( 0,( "Failed to start WinSock\n" ));
+		WSACleanup();	
+#ifdef __PLAT_XBOX__
+		XNetCleanup();
+#endif
+		return;
+	}
+
+#ifdef __PLAT_XBOX__	
+#	if 0
+	HRESULT hr;
+	XONLINE_STARTUP_PARAMS xosp = { 0 };
+
+	hr = XOnlineStartup( &xosp );
+	if( FAILED( hr ))
+	{
+		XOnlineCleanup();
+		return;
+	}
+#	endif
+#endif
+
+#else    
+#ifdef __PLAT_NGPS__
+	ChangeThreadPriority( GetThreadId(), vMAIN_THREAD_PRIORITY );
+	m_stack_setup = false;
+	m_options_changed = false;
+	m_device_changed = false;
+	m_net_drivers_loaded = false;
+#endif	// __PLAT_NGPS__
+#endif	// __PLAT_WN32__
+
+	for( msg_id = 0; msg_id < 255; msg_id++ )
+	{
+		SetMessageName( msg_id, "" );
+		SetMessageFlags( msg_id, 0 );
+	}
+
+	SetMessageName( MSG_ID_PING_TEST, "Ping Test" );
+	SetMessageName( MSG_ID_PING_RESPONSE, "Ping Response" );
+	SetMessageName( MSG_ID_CONNECTION_REQ, "Connection Request" );
+	SetMessageName( MSG_ID_CONNECTION_ACCEPTED, "Connection Accepted" );
+	SetMessageName( MSG_ID_CONNECTION_REFUSED, "Connection Refused" );
+	SetMessageName( MSG_ID_CONNECTION_TERMINATED, "Connection Terminated" );
+	SetMessageName( MSG_ID_SEQUENCED, "Sequenced Message" );
+	SetMessageName( MSG_ID_ACK, "Ack" );
+	SetMessageName( MSG_ID_FIND_SERVER, "Find Server" );
+	SetMessageName( MSG_ID_SERVER_RESPONSE, "Server Find Response" );
+	SetMessageName( MSG_ID_TIMESTAMP, "Timestamp" );
+	SetMessageName( MSG_ID_ALIAS, "New Alias" );
+	SetMessageName( MSG_ID_DISCONN_REQ,	"Disconn Request" );
+	SetMessageName( MSG_ID_DISCONN_ACCEPTED, "Disconn Accepted" );
+
+	SetMessageFlags( MSG_ID_TIMESTAMP, mMSG_SIZE_UNKNOWN );
+	SetMessageFlags( MSG_ID_ACK, mMSG_SIZE_UNKNOWN );
+
+	sprintf( m_local_ip, "" );
+	sprintf( m_gateway, DEFAULT_SNPS2_GATEWAY );
+	sprintf( m_subnet, DEFAULT_SNPS2_SUB_MSK );
+			 
+	m_num_apps = 0;
+	m_conn_type = vCONN_TYPE_NONE;
+	m_use_dhcp = false;
+	m_online = false;
+	m_use_dialup_auth = false;
+	m_last_error = vRES_SUCCESS;
+	m_modem_state = vMODEM_STATE_DISCONNECTED;
+	m_modem_err = 0;
+
+	for( i = 0; i < 3; i++ )
+	{
+		m_dns_servers[i][0] = '\0';
+	}
+
+#ifdef __PLAT_NGPS__
+	m_bandwidth = 4200;	// Default to a 33.6kbps modem's approximate payload threshold (i.e. including packet overhead)
+#else
+	m_bandwidth = 400000;	// On Xbox, just assume broadband
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+	
+#if( defined ( __PLAT_WN32__ ) || defined ( __PLAT_XBOX__ ))
+	WSACleanup();	
+#endif
+
+#ifdef __PLAT_XBOX__
+	XNetCleanup();
+#	if 0
+	XOnlineCleanup();
+#	endif
+#endif
+
+#ifdef __PLAT_NGC__
+	SOCleanup();
+#endif
+
+
+#ifdef __PLAT_NGPS__
+	sn_int32	stack_state, result;
+	
+	if( m_stack_setup )
+	{
+		// Stop the stack
+		result = sn_stack_state(SN_STACK_STATE_STOP, &stack_state );
+		Dbg_MsgAssert( result == 0,( "EE:sn_stack_sate() failed %d\n", result ));
+			
+		// De-Register this thread with the socket API
+		Dbg_Printf( "EE:Calling sockAPIderegthr()\n" );
+		sockAPIderegthr();
+	}
+#endif // __PLAY_NGPS__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGPS__
+
+void Manager::conn_modem_state_callback( sn_int32 modem_state )
+{
+	
+	Net::Manager * net_man = Net::Manager::Instance();
+		
+	Dbg_Printf("  Modem state %d = %s\n", modem_state, sntc_str_modem_state( modem_state ));
+	switch( modem_state )
+	{
+		case SN_MODEM_READY:
+		case SN_MODEM_DIALING:
+			net_man->SetModemState( vMODEM_STATE_DIALING );
+			
+			// If we're using PAP/CHAP, clear the login script. We do it here instead of before
+			// sntc_connect_modem() because that call resets to the default login script.
+			if( net_man->ShouldUseDialupAuthentication())
+			{
+				sndev_set_null_scrpt_type clr;
+				int result;
+
+				clr.reserved = 0;
+
+				result = sndev_set_options(0, SN_DEV_SET_NULL_SCRPT, &clr, sizeof(clr));
+                Dbg_Printf( "EE:sndev_set_options(clr) returned %d\n", result );
+			}
+			
+			Dbg_Printf( "Setting modem state to modem state dialing\n" );
+			break;
+		case SN_MODEM_LOGIN:
+			Dbg_Printf( "Setting modem state to modem state connected\n" );
+			net_man->SetModemState( vMODEM_STATE_CONNECTED );
+			break;
+		case SN_MODEM_PPP_UP:
+			Dbg_Printf( "Setting modem state to modem state logged in\n" );
+			net_man->SetModemState( vMODEM_STATE_LOGGED_IN );
+			break;
+		default:
+			return;
+	};
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::disconn_modem_state_callback( sn_int32 modem_state )
+{
+	Net::Manager * net_man = Net::Manager::Instance();
+    
+    Dbg_Printf("  Modem state = %s\n", sntc_str_modem_state( modem_state ));
+	switch( modem_state )
+	{
+		case SN_MODEM_HANGINGUP:
+			net_man->SetModemState( vMODEM_STATE_HANGING_UP );
+			break;
+		default:
+			return;
+	};
+}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+bool	Manager::NetworkEnvironmentSetup( void )
+{
+#ifdef __PLAT_NGPS__
+	return initialize_ps2();
+#endif
+#ifdef __PLAT_NGC__
+	return initialize_ngc();
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::NeedToTestNetworkEnvironment( void )
+{
+#ifdef __PLAT_NGPS__
+	if( m_net_drivers_loaded && m_device_changed )
+	{
+		return true;
+	}
+	if( !m_stack_setup || m_options_changed )
+	{
+		return true;
+	}
+#endif
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ConnectToInternet( void )
+{
+	
+
+#ifdef __PLAT_NGPS__
+
+	if(( IsOnline() == false ) || m_options_changed )
+	{
+        if( GetConnectionType() == vCONN_TYPE_ETHERNET )
+		{
+			m_online = true;
+		}
+		else if(( GetConnectionType() == vCONN_TYPE_MODEM ) ||
+				( GetConnectionType() == vCONN_TYPE_PPPOE ))
+		{
+			int result, device_type;
+			int modem_state;
+            
+			s_cancel_dialup_conn = false;
+            result = sndev_get_attached(0, &device_type, NULL, NULL);
+			// Check that the above function call worked ok
+			if( result != 0 )
+			{
+				SetModemState( vMODEM_STATE_ERROR );
+				m_modem_err = SNTC_ERR_NOMODEM;
+				return false;
+			}
+		
+			// Check that there is a compatible modem attached
+			if( device_type != SN_DEV_TYPE_USB_MODEM )
+			{
+				SetModemState( vMODEM_STATE_ERROR );
+				m_modem_err = SNTC_ERR_NOMODEM;
+				return false;
+			}
+
+			result = snmdm_get_state( &modem_state );
+			// Check that the above function call worked ok
+			if( result != 0 )
+			{
+				SetModemState( vMODEM_STATE_ERROR );
+				m_modem_err = SNTC_ERR_NOMODEM;
+				return false;
+			}
+		
+			// Make sure that the modem is ready to dial, if not reset it
+			if(	( modem_state != SN_MODEM_READY ) && 
+				( modem_state != SN_MODEM_READY_AUTOANS ))
+			{
+				sn_char* err_msg_ptr;
+
+				Dbg_Printf( "EE:Calling sntc_reset_modem()\n" );
+				// Attempt to reset the modem
+				result = sntc_reset_modem(	vMODEM_RESET_TIMEOUT,	// timeout_secs
+											disconn_modem_state_callback,	// callback
+											&err_msg_ptr );     	// error_message
+				// If failed to reset the modem, then the result, and
+				// error_message will have been set up by sntc_reset_modem.
+				if( result != 0 )
+				{
+					SetModemState( vMODEM_STATE_ERROR );
+					m_modem_err = SNTC_ERR_NOMODEM;
+					return false;
+				}
+			}
+
+			{
+				struct SemaParam params;
+
+				params.initCount = 1;
+				params.maxCount = 10;
+			
+				s_conn_semaphore = CreateSema( ¶ms );
+
+                // Clear the modem state before we start
+				SetModemState( -1 );
+
+				m_modem_thread_data.m_pEntry = threaded_modem_conn;
+				m_modem_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+				m_modem_thread_data.m_pStackBase = m_modem_thread_stack;
+				m_modem_thread_data.m_iStackSize = vMODEM_THREAD_STACK_SIZE;
+				m_modem_thread_data.m_utid = 0x150;//vBASE_SOCKET_THREAD_ID + NumApps();
+				Thread::CreateThread( &m_modem_thread_data );
+				m_modem_thread_id = m_modem_thread_data.m_osId;
+                
+				StartThread( m_modem_thread_id, this );
+			}
+		}   
+	}
+#else
+	m_online = true;
+#endif
+
+	return m_online;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::DisconnectFromInternet( void )
+{
+	
+
+#ifdef __PLAT_NGPS__
+	if(	m_conn_type == vCONN_TYPE_ETHERNET )
+	{
+		m_online = false;
+	}
+	else if( ( m_conn_type == vCONN_TYPE_MODEM ) ||
+			 ( m_conn_type == vCONN_TYPE_PPPOE ))
+	{
+        // Just in case the modem thread is running, stop it
+		s_cancel_dialup_conn = true;
+		WaitSema( s_conn_semaphore );
+		DeleteSema( s_conn_semaphore );
+		TerminateThread( m_modem_thread_id );
+		DeleteThread( m_modem_thread_id );
+
+		SetModemState( vMODEM_STATE_DISCONNECTING );
+		m_modem_err = 0;
+
+		m_modem_thread_data.m_pEntry = threaded_modem_disconn;
+		m_modem_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+		m_modem_thread_data.m_pStackBase = m_modem_thread_stack;
+		m_modem_thread_data.m_iStackSize = vMODEM_THREAD_STACK_SIZE;
+		m_modem_thread_data.m_utid = 0x14F;//vBASE_SOCKET_THREAD_ID + NumApps();
+		Thread::CreateThread( &m_modem_thread_data );
+		m_modem_thread_id = m_modem_thread_data.m_osId;
+		
+		StartThread( m_modem_thread_id, this );
+	}
+#else
+	m_online = false;
+#endif
+
+	return ( m_online == false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetModemBaudRate( void )
+{
+	return m_modem_baud_rate;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetBandwidth( int bytes_per_sec )
+{
+	m_bandwidth = bytes_per_sec;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetBandwidth( void )
+{
+	return m_bandwidth;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::IsOnline( void )
+{   
+#ifdef __PLAT_NGPS__
+	if(	( GetConnectionType() == vCONN_TYPE_MODEM ) ||
+		( GetConnectionType() == vCONN_TYPE_PPPOE ))
+	{
+		int modem_state;
+        
+		m_online = false;
+		if( snmdm_get_state( &modem_state ) == 0 )
+		{
+			if( modem_state == SN_MODEM_PPP_UP )
+			{
+				m_online = true;
+			}
+		}
+	}
+#endif// __PLAT_NGPS__
+
+	return m_online;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetISPPhoneNumber( char* phone_no )
+{
+	
+
+	Dbg_Assert( phone_no );
+
+	strcpy( m_isp_phone_no, phone_no );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetISPLogin( char* login )
+{
+	
+
+	Dbg_Assert( login );
+
+	strcpy( m_isp_user_name, login );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetISPPassword( char* password )
+{
+	
+
+	Dbg_Assert( password );
+
+	strcpy( m_isp_password, password );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetGateway( char* ip )
+{
+	
+
+	if( strcmp( ip, m_gateway ))
+	{
+		strcpy( m_gateway, ip );
+#ifdef __PLAT_NGPS__
+		m_options_changed = true;
+#endif		// __PLAT_NGC__
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetSubnetMask( char* ip )
+{
+	
+
+	if( strcmp( ip, m_subnet ))
+	{
+		strcpy( m_subnet, ip );
+#ifdef __PLAT_NGPS__
+		m_options_changed = true;
+#endif		// __PLAT_NGC__
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetPublicIP( unsigned int ip )
+{
+	m_public_ip = ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetLocalIP( char* ip )
+{
+	
+
+	if( strcmp( ip, m_local_ip ))
+	{
+		strcpy( m_local_ip, ip );
+#ifdef __PLAT_NGPS__
+		m_options_changed = true;
+#endif		// __PLAT_NGC__
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetConnectionType( ConnType conn_type )
+{
+	if( m_conn_type != conn_type )
+	{
+#ifdef __PLAT_NGPS__
+		if( m_conn_type != vCONN_TYPE_NONE )
+		{
+			m_device_changed = true;
+		}
+#endif		// __PLAT_NGC__
+		m_conn_type = conn_type;
+
+        // Some default values for bandwidth limiting
+		switch( m_conn_type )
+		{
+			case vCONN_TYPE_MODEM:
+				SetBandwidth( 4200 );	
+				break;
+			case vCONN_TYPE_ETHERNET:
+				SetBandwidth( 400000 );
+				break;
+			case vCONN_TYPE_PPPOE:
+				SetBandwidth( 300000 );
+				break;
+			default:
+				break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetDeviceType( DeviceType dev_type )
+{
+	if( m_device_type != dev_type )
+	{
+#ifdef __PLAT_NGPS__
+		if( m_device_type != vDEV_TYPE_NONE )
+		{
+			m_device_changed = true;
+		}
+#endif		// __PLAT_NGC__
+		m_device_type = dev_type;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetDHCP( bool use_dhcp )
+{    
+	if( m_use_dhcp != use_dhcp )
+	{
+		m_use_dhcp = use_dhcp;
+#ifdef __PLAT_NGPS__
+		m_options_changed = true;
+#endif		// __PLAT_NGC__
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetDialupAuthentication( bool authenticate )
+{
+	m_use_dialup_auth = authenticate;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetDNSServer( int index, char* ip )
+{
+	
+	
+	Dbg_Assert(( index >= 0 ) && ( index < 3 ));
+	Dbg_Assert( ip );
+
+	if( strcmp( ip, m_dns_servers[index] ))
+	{
+		strcpy( m_dns_servers[index], ip );
+#ifdef __PLAT_NGPS__
+		m_options_changed = true;
+#endif		// __PLAT_NGC__
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetHostName( char* host )
+{
+	
+
+	// Either they pass us NULL or they pass us a value less than 32 chars long
+	Dbg_Assert( !host || ( strlen( host ) < 32 ));
+
+    if( host == NULL )
+	{
+		m_host_name[0] = '\0';
+	}
+
+	strcpy( m_host_name, host );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetDomainName( char* domain )
+{
+	
+
+	// Either they pass us NULL or they pass us a value less than 32 chars long
+	Dbg_Assert( !domain || ( strlen( domain ) < 32 ));
+
+    if( domain == NULL )
+	{
+		m_domain_name[0] = '\0';
+	}
+
+	strcpy( m_domain_name, domain );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetDNSServer( int index )
+{
+	
+
+	Dbg_Assert(( index >= 0 ) && ( index < 3 ));
+
+	return m_dns_servers[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetISPPhoneNumber( void )
+{
+	if( GetConnectionType() == vCONN_TYPE_PPPOE )
+	{
+		return "";
+	}
+	return m_isp_phone_no;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ShouldUseDHCP( void )
+{
+	if(( GetConnectionType() == vCONN_TYPE_PPPOE ) ||
+	   ( GetConnectionType() == vCONN_TYPE_MODEM ))
+	{
+		return false;
+	}
+
+	return m_use_dhcp;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ShouldUseDialupAuthentication( void )
+{
+	if( GetConnectionType() == vCONN_TYPE_PPPOE )
+	{
+		return true;
+	}
+	return m_use_dialup_auth;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetGateway( void )
+{
+	
+
+	return m_gateway;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetSubnetMask( void )
+{
+	
+
+	return m_subnet;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetHostName( void )
+{
+	
+
+	return m_host_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetDomainName( void )
+{
+	
+
+	return m_domain_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+unsigned int	Manager::GetPublicIP( void )
+{
+	if( m_public_ip == 0 )
+	{
+		return inet_addr( m_local_ip );
+	}
+	
+	return m_public_ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetLocalIP( void )
+{   
+	
+
+#if defined( __PLAT_NGPS__ ) || defined( __PLAT_XBOX__ ) || defined( __PLAT_NGC__ )
+	return m_local_ip;
+#else
+	struct hostent *host, *local_host;
+	struct in_addr address;
+	char *ip_str;
+	int ip;
+
+	if(( local_host = gethostbyname( "localhost" )))
+	{	
+		if(( host = gethostbyname( local_host->h_name )))
+		{
+			ip = *(unsigned long *) host->h_addr_list[0];			
+			address.s_addr = ip;
+			ip_str = inet_ntoa( address ); 
+			return ip_str;
+		}
+		else
+		{
+			ip = *(unsigned long *) local_host->h_addr_list[0];			
+			address.s_addr = ip;
+			ip_str = inet_ntoa( address ); 
+			return ip_str;
+		}
+	}
+	
+	return "";
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ConnType	Manager::GetConnectionType( void )
+{
+	return m_conn_type;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+DeviceType	Manager::GetDeviceType( void )
+{
+#ifdef __PLAT_XBOX__
+	return vDEV_TYPE_USB_ETHERNET;
+#else
+	return m_device_type;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::SetError( int error )
+{
+	m_last_error = error;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			Manager::GetLastError( void )
+{
+	return m_last_error;
+}
+
+/******************************************************************/
+/* Get the name associated with a message id 					  */
+/*                                                                */
+/******************************************************************/
+
+char*	Manager::GetMessageName( unsigned char msg_id )
+{
+#ifdef	NET_DEBUG_MESSAGES
+	return m_message_names[ msg_id ];
+#else
+	return "";
+#endif
+}
+
+/******************************************************************/
+/* Associate a text name with a message id for debugging purposes */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetMessageName( unsigned char msg_id, char* msg_name )
+{
+#ifdef NET_DEBUG_MESSAGES
+	Dbg_Assert( msg_name );
+
+	strncpy( m_message_names[ msg_id ], msg_name, vMAX_MSG_NAME_LEN - 1 );
+	m_message_names[msg_id][vMAX_MSG_NAME_LEN - 1] = '\0';
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char	Manager::GetMessageFlags( unsigned char msg_id )
+{
+	return m_message_flags[ msg_id ];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetMessageFlags( unsigned char msg_id, char flags )
+{
+	m_message_flags[ msg_id ] = flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetModemState( void )
+{
+	return m_modem_state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetModemState( int state )
+{
+	m_modem_state = state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetModemError( void )
+{
+	return m_modem_err;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::CanChangeDevices( void )
+{
+#ifdef __PLAT_NGPS__
+	return !m_net_drivers_loaded;
+#else
+	return true;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+MsgLink::MsgLink( QueuedMsg *msg )
+: Lst::Node< MsgLink > ( this ), m_QMsg( msg ), m_SendTime( 0 )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+MsgImpLink::MsgImpLink( QueuedMsg *msg )
+: Lst::Node< MsgImpLink > ( this ), m_QMsg( msg ), m_SendTime( 0 )
+{
+	m_NumResends = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+MsgSeqLink::MsgSeqLink( QueuedMsgSeq *msg )
+: Lst::Node< MsgSeqLink > ( this ), m_StreamMessage( 0 ), m_SendTime( 0 ), m_QMsg( msg )
+{
+	m_NumResends = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StreamLink::StreamLink( StreamDesc* desc )
+: Lst::Node< StreamLink > ( this ), m_Desc ( desc )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StreamDesc::StreamDesc( void )
+: m_Size( 0 ), m_Data( NULL ), m_DataPtr( NULL ), m_GroupId( 0 ), m_SendInPlace( false )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StreamDesc::~StreamDesc( void )
+{
+	if( m_Data && !m_SendInPlace )
+	{
+		delete [] m_Data;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+MsgDesc::MsgDesc( void )
+: m_Id( 0 ), m_StreamMessage( 0 ), m_Length( 0 ), m_Data( 0 ), m_Priority( NORMAL_PRIORITY ), m_Queue( QUEUE_DEFAULT ), m_GroupId( GROUP_ID_DEFAULT ),
+	m_Singular( false ), m_Delay( 0 ), m_ForcedSequenceId( 0 )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+QueuedMsg::QueuedMsg( unsigned char msg_id, unsigned short msg_len, void* data )
+{
+	
+	
+	m_Data = NULL;
+	if( msg_len > 0 )
+	{
+		m_Data = new char[ msg_len ];
+		memcpy( m_Data, data, msg_len );
+	}
+	
+	m_MsgId = msg_id;
+	m_MsgLength = msg_len;
+#ifdef	__PLAT_NGPS__											  
+	Dbg_MsgAssert(Mem::SameContext(this,Mem::Manager::sHandle().NetworkHeap()),("QueuedMsg not on network heap"));	
+#endif		//	__PLAT_NGPS__											  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+QueuedMsg::~QueuedMsg( void )
+{
+	
+	if( m_Data )
+	{
+		delete [] m_Data;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+QueuedMsgSeq::QueuedMsgSeq( unsigned char msg_id, unsigned short msg_len, void* data, unsigned char group_id )
+{
+	
+	
+	m_Data = NULL;
+	if( msg_len > 0 )
+	{
+		m_Data = new char[ msg_len ];
+		memcpy( m_Data, data, msg_len );
+	}
+	
+	m_MsgId = msg_id;
+	m_MsgLength = msg_len;
+	m_GroupId = group_id;	
+#ifdef	__PLAT_NGPS__											  
+	Dbg_MsgAssert(Mem::SameContext(this,Mem::Manager::sHandle().NetworkHeap()),("QueuedMsgSeq not on network heap"));	
+#endif		//	__PLAT_NGPS__											  
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+QueuedMsgSeq::~QueuedMsgSeq( void )
+{
+	if( m_Data )
+	{
+		delete [] m_Data;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+BitStream::BitStream( void )
+{
+	m_data = NULL;
+	m_bits_left = 32;
+	m_size = 0;
+	m_cur_val = 0;
+	m_bits_processed = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BitStream::SetInputData( char* data, int size )
+{
+	m_data = (unsigned int*) data;
+	m_start_data = m_data;
+	m_size = size;
+	m_bits_left = 0;
+	m_bits_processed = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BitStream::SetOutputData( char* data, int size )
+{
+	m_data = (unsigned int*) data;
+	m_start_data = m_data;
+	m_size = size;
+	m_bits_left = 32;
+	m_bits_processed = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	BitStream::GetByteLength( void )
+{
+	return (( m_bits_processed + 7 ) >> 3 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BitStream::WriteValue( int value, int num_bits )
+{
+	m_bits_processed += num_bits;
+	
+	// If we cannot fit all bits into our current int, we'll have to span it over
+	// two ints
+	if( m_bits_left < num_bits )
+	{
+		// Write out the first portion, then advance to the next int
+
+        // First clear all top (unwritten) bits
+		m_cur_val &= ( 1 << ( 32 - m_bits_left )) - 1;
+		
+		// Now write them (without having to mask low bits off)
+		m_cur_val |= value << ( 32 - m_bits_left );
+		num_bits -= m_bits_left;
+		value >>= m_bits_left;
+		memcpy( m_data, &m_cur_val, sizeof( unsigned int ));
+		m_data++;
+		m_cur_val = 0;
+		m_bits_left = 32;
+	}
+
+    // First clear all top (unwritten) bits
+	m_cur_val &= ( 1 << ( 32 - m_bits_left )) - 1;
+	// Now write them (without having to mask low bits off)
+	m_cur_val |= value << ( 32 - m_bits_left );
+	m_bits_left -= num_bits;
+	
+	// Flush bits if full
+	if( m_bits_left == 0 )
+	{
+		memcpy((char*) m_data, (char*) &m_cur_val, sizeof( unsigned int ));
+		m_data++;
+		m_cur_val = 0;
+		m_bits_left = 32;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BitStream::WriteFloatValue( float value )
+{
+	WriteValue( *(int*) &value, sizeof( float ) * 8 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BitStream::Flush( void )
+{
+	// Only flush if we have bits to flush
+	if( m_bits_left != 32)
+	{
+		memcpy( m_data, &m_cur_val, sizeof( unsigned int ));
+		m_data++;
+		m_cur_val = 0;
+		m_bits_left = 32;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float			BitStream::ReadFloatValue( void )
+{
+	float res;
+	int num_bits;
+	
+	num_bits = 32;
+	m_bits_processed += num_bits;
+
+	// If we're all out of bits (like at start), read a full word
+	if( m_bits_left == 0 )
+	{
+		memcpy( &m_cur_val, m_data, sizeof( unsigned int ));
+		m_data++;
+		m_bits_left = 32;
+	}
+	
+	// Do we hold all bits requested?
+	if( m_bits_left >= num_bits )
+	{
+		// Yes, easy peasy case
+		m_bits_left -= num_bits;
+		if( num_bits == 32 )
+		{
+			memcpy( &res, &m_cur_val, sizeof( float ));
+		}
+		else
+		{
+			unsigned int int_val;
+
+			int_val = m_cur_val & ((1 << num_bits) - 1);
+			memcpy( &res, &int_val, sizeof( float ));
+		}
+		
+		m_cur_val >>= num_bits;
+	}
+	else
+	{
+		unsigned int int_val;
+		// Nope, grab those we have and fetch more from stream
+
+		int_val = m_cur_val & (( 1 << m_bits_left ) - 1);
+		num_bits -= m_bits_left;
+		memcpy( &m_cur_val, m_data, sizeof( unsigned int ));
+		m_data++;
+		int_val |= ( m_cur_val & (( 1 << num_bits ) - 1)) << m_bits_left;
+		m_cur_val >>= num_bits;
+		m_bits_left = 32 - num_bits;
+
+		memcpy( &res, &int_val, sizeof( float ));
+	}
+	
+	return res;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+unsigned int	BitStream::ReadUnsignedValue( int num_bits )
+{
+	unsigned long res;
+	
+	Dbg_Assert(( num_bits >= 0 ) && ( num_bits <= 32 ));
+   
+	m_bits_processed += num_bits;
+
+	// If we're all out of bits (like at start), read a full word
+	if( m_bits_left == 0 )
+	{
+		memcpy( &m_cur_val, m_data, sizeof( unsigned int ));
+		m_data++;
+		m_bits_left = 32;
+	}
+	
+	// Do we hold all bits requested?
+	if( m_bits_left >= num_bits )
+	{
+				// Yes, easy peasy case
+		m_bits_left -= num_bits;
+		if( num_bits == 32 )
+		{
+			res = m_cur_val;
+		}
+		else
+		{
+			res = m_cur_val & ((1 << num_bits) - 1);
+		}
+		
+		m_cur_val >>= num_bits;
+	}
+	else
+	{
+		// Nope, grab those we have and fetch more from stream
+		res = m_cur_val & (( 1 << m_bits_left ) - 1);
+		num_bits -= m_bits_left;
+		memcpy( &m_cur_val, m_data, sizeof( unsigned int ));
+		m_data++;
+		res |= ( m_cur_val & (( 1 << num_bits ) - 1)) << m_bits_left;
+		m_cur_val >>= num_bits;
+		m_bits_left = 32 - num_bits;
+	}
+	
+	return res;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	BitStream::ReadSignedValue( int num_bits )
+{
+	long res;
+
+	Dbg_Assert(( num_bits >= 0 ) && ( num_bits <= 32 ));
+
+	// Cheasy call to that other function for simplicity
+	res = ReadUnsignedValue( num_bits );
+
+	// Sign extend result if sign bit set
+	if( res & ( 1 << ( num_bits - 1 )))
+	{
+		res |= ~(( 1 << num_bits ) - 1 );
+	}
+
+	return res;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
\ No newline at end of file
diff --git a/Code/Gel/Net/net.h b/Code/Gel/Net/net.h
new file mode 100644
index 0000000..b4d8116
--- /dev/null
+++ b/Code/Gel/Net/net.h
@@ -0,0 +1,1381 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			NET  (Net)												**
+**																			**
+**	File name:		gel/net.h												**
+**																			**
+**	Created: 		01/29/01	-	spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef __NET_H__
+#define __NET_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#if __PLAT_NGPS__
+#include         /* For sceSifInitRpc()                    */
+#include         /* For sceSifLoadModule()                 */
+
+#include "sntypes.h"       /* SN Systems types                       */
+#include "sneeutil.h"      /* SN Systems PS2 EE Utilites (General)   */
+#include "snsocket.h"      /* SN Systems socket API header file      */
+#include "sntcutil.h"      /* SN Systems PS2 EE Utilites (TCP/IP)    */
+#endif	// __PLAT_NGPS__
+
+#ifdef __PLAT_WN32__
+#include 
+#endif // __PLAT_WN32__
+
+#ifdef __PLAT_XBOX__
+#include 
+#include 
+#endif // __PLAT_XBOX__
+
+#ifdef __PLAT_NGC__
+//// GameCube only.
+//#include 
+//#include "sys/ngc/p_dl.h"
+//#include "sys/ngc/p_scene.h"
+//#include "sys/ngc/p_slerp.h"
+//#include "sys/ngc/p_vector.h"
+//#include "sys/ngc/p_matrix.h"
+//#include "sys/ngc/p_texman.h"
+#include 
+#define NsWorldSector	NsDL
+#define gethostbyname(name) SOGetHostByName(name)
+#define gethostname(name,namelen) 0
+#define inet_addr(cp) 0
+#define inet_aton(cp,addr) SOInetAtoN(cp, addr)
+#define inet_ntoa(in) SOInetNtoA(in)
+#define in_addr SOInAddr
+#define sockaddr_in SOSockAddrIn
+#define sockaddr SOSockAddr
+#define AF_INET         SO_PF_INET              /* internetwork: UDP, TCP, etc. */
+#define PF_INET         AF_INET
+#define htonl(l)        SOHtoNl(l)
+#define ntohl(l)        SONtoHl(l)
+#define htons(s)        SOHtoNs(s)
+#define ntohs(s)        SONtoHs(s)
+#define sin_family family
+#define sin_port port
+#define sin_addr addr
+#define sin_zero zero
+#define s_addr addr
+#define INADDR_ANY	SO_INADDR_ANY
+#define IPPROTO_UDP 0
+
+#define accept(s,addr,addrlen) SOAccept(s,addr)
+#define bind(s,addr,addrlen) SOBind(s,addr)
+#define closesocket(s) SOClose(s)
+#define connect(s,addr,addrlen) SOConnect(s,addr)
+#define getpeername(s,addr,addrlen) SOGetPeerName(s,addr)
+#define getsockname(s,addr,addrlen) SOGetSockName(s,addr)
+#define getsockopt(s,level,optname,optval,optlen) SOGetSockOpt(s,level,optname,optval)
+#define listen(s,backlog) SOListen(s,backlog)
+#define recv(s,buf,len,flags) SORecv(s,buf,len,flags)
+#define recvfrom(s,buf,len,flags,from,fromlen) SORecvFrom(s,buf,len,flags,from)
+#define recvmsg(s,msg,flags) 0
+#define select(nfds,readfs,writefs,exceptfs,timeout) 0
+#define send(s,buf,len,flags) SOSend(s,buf,len,flags)
+#define sendmsg(s,msg,flags) 0
+#define sendto(s,buf,len,flags,to,tolen) SOSendTo(s,buf,len,flags,to)
+#define setsockopt(s,level,optname,optval,optlen) SOSetSockOpt(s,level,optname,optval)
+#define shutdown(s,how) SOShutdown(s,how)
+#define sockAPIinit(maxthreads) 0
+#define sockAPIreinit() 0
+#define sockAPIregthr() 0
+#define sockAPIderegthr() 0
+#define socket(af,type,protocol) SOSocket(af,type,protocol)
+
+
+#define ENOBUFS         1
+#define ETIMEDOUT       2
+#define EISCONN         3
+#define EOPNOTSUPP      4
+#define ECONNABORTED    5
+#define EWOULDBLOCK     6
+#define ECONNREFUSED    7
+#define ECONNRESET      8
+#define ENOTCONN        9
+#define EALREADY        10
+#define EINVAL          11
+#define EMSGSIZE        12
+#define EPIPE           13
+#define EDESTADDRREQ    14
+#define ESHUTDOWN       15
+#define ENOPROTOOPT     16
+#define EHAVEOOB        17
+#define ENOMEM          18
+#define EADDRNOTAVAIL   19
+#define EADDRINUSE      20
+#define EAFNOSUPPORT    21
+#define EINPROGRESS     22
+#define ELOWER          23
+#define EBADF           24
+
+#define SOCK_STREAM     1               /* stream socket */
+#define SOCK_DGRAM      2               /* datagram socket */
+
+#define SO_DEBUG        0x0001          /* turn on debugging info recording */
+#define SO_ACCEPTCONN   0x0002          /* socket has had listen() */
+#define SO_REUSEADDR    0x0004          /* allow local address reuse */
+#define SO_KEEPALIVE    0x0008          /* keep connections alive */
+#define SO_DONTROUTE    0x0010          /* just use interface addresses */
+#define SO_BROADCAST    0x0020          /* permit sending of broadcast msgs */
+#define SO_USELOOPBACK  0x0040          /* bypass hardware when possible */
+#define SO_LINGER       0x0080          /* linger on close if data present */
+#define SO_OOBINLINE    0x0100          /* leave received OOB data in line */
+
+/* Additional options not kept in so_options */
+
+#define SO_SNDBUF       0x1001          /* send buffer size */
+#define SO_RCVBUF       0x1002          /* receive buffer size */
+#define SO_SNDLOWAT     0x1003          /* send low-water mark */
+#define SO_RCVLOWAT     0x1004          /* receive low-water mark */
+#define SO_SNDTIMEO     0x1005          /* send timeout */
+#define SO_RCVTIMEO     0x1006          /* receive timeout */
+#define SO_ERROR        0x1007          /* get error status and clear */
+#define SO_TYPE         0x1008          /* get socket type */
+
+#define SO_HOPCNT       0x1009          /* Get hop count to destination */
+#define SO_MAXMSG       0x1010          /* Get TCP_MSS (max segment size) */
+
+/* Option extensions */
+
+#define SO_RXDATA       0x1011          /* Get count of received bytes */
+#define SO_MYADDR       0x1012          /* Get local IP address */
+#define SO_NBIO         0x1013          /* Set socket non-blocking */
+#define SO_BIO          0x1014          /* Set socket blocking */
+#define SO_NONBLOCK     0x1015          /* Set/get blocking state */
+
+#define MSG_OOB         0x1             /* process out-of-band data */
+#define MSG_PEEK        0x2             /* peek at incoming message */
+#define MSG_DONTROUTE   0x4             /* send without using routing tables */
+#define MSG_DONTWAIT    0x20            /* this message should be nonblocking */
+
+#define SD_RECEIVE  0
+#define SD_SEND     1
+#define SD_BOTH     2
+#endif // __PLAT_NGC__
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#ifdef __PLAT_NGPS__
+#define vBASE_SOCKET_THREAD_ID	0x110
+
+#define vSOCKET_THREAD_PRIORITY	10
+#define vMAIN_THREAD_PRIORITY	11
+
+#define vSOCKET_THREAD_STACK_SIZE	( 8 * 1024 )
+#define vMODEM_THREAD_STACK_SIZE	( 12 * 1024 )
+#endif
+
+#ifndef SOCKET_ERROR
+#	define SOCKET_ERROR -1
+#endif
+
+#ifndef INVALID_SOCKET
+#	define INVALID_SOCKET -1
+#endif
+
+#if( defined( __PLAT_NGPS__ ) || defined( __PLAT_NGC__ ))
+typedef int	SOCKET;	// Xbox type SOCKET is defined in winsockx.h.
+#endif
+
+//#define NET_DEBUG_MESSAGES
+
+namespace Net
+{
+
+
+
+enum
+{
+	MSG_ID_PING_TEST			= 1,		// = 1 : Server->Client
+	MSG_ID_PING_RESPONSE,					// = 2 : Client->Server
+	MSG_ID_CONNECTION_REQ,					// = 3 : Client->Server
+	MSG_ID_CONNECTION_ACCEPTED,				// = 4 : Server->Client
+	MSG_ID_CONNECTION_REFUSED,				// = 5 : Server->Client
+	MSG_ID_CONNECTION_TERMINATED,			// = 6 : Server->Client
+	MSG_ID_SEQUENCED,						// = 7 : Bi-directional sequenced identifier
+	MSG_ID_ACK,								// = 8 : Bi-directional acknowledgement
+	MSG_ID_TIMESTAMP,						// = 9 : Server->Client
+	MSG_ID_ALIAS,							// = 10 : Client->Server New alias request
+	MSG_ID_DISCONN_REQ,						// = 11 : Client->Server Request to disconnect
+	MSG_ID_DISCONN_ACCEPTED,				// = 12 : Server->Client Go ahead and quit
+	MSG_ID_KEEPALIVE,						// = 13 : Bi-directional general keepalive message		
+	MSG_ID_STREAM_START,					// = 14 : Bi-directional general stream start message
+	MSG_ID_STREAM_DATA,						// = 15 : Bi-directional general stream data message
+	MSG_ID_SERVER_RESPONSE,					// = 16 : Server->Client
+	MSG_ID_FIND_SERVER,						// = 17 : Client->Server Broadcast
+
+	MSG_ID_USER					= 32,		// Game-Specific messages start here
+};
+
+enum
+{
+	GROUP_ID_DEFAULT,
+	GROUP_ID_LATENCY_TESTS,
+	GROUP_ID_STREAMS,
+};
+
+#define MAX_NET_MSG_ID	255
+#define MAX_MSG_IDS		256
+#define MAX_SEQ_GROUPS	256
+#define MAX_LEN_APP_NAME 64
+#define MAX_STREAM_CHUNK_LENGTH	256
+#define MAX_STREAM_LENGTH (Net::Conn::vREAD_BUFFER_LENGTH)
+#define MAX_UDP_PACKET_SIZE 1300
+#define vUDP_PACKET_OVERHEAD 28 // 20 for IPv4 header, 8 for UDP Header
+
+#define NET_NUM_LATENCY_TESTS	10
+
+enum
+{
+	HANDLE_ID_EXCLUDE_BROADCAST	= 0x80,	// Or this with a handle ID and it will send to all
+										// handles except that id
+	HANDLE_ID_BROADCAST 		= 255,
+};
+
+enum
+{
+	vNO_ALIAS  		= 255,
+	vNUM_ALIASES	= 255,
+	vALIAS_DURATION = 2000	// number of ms before an alias expires (2 seconds)
+};
+
+enum
+{
+	LOWEST_PRIORITY = 0,
+	NORMAL_PRIORITY	= 128,
+	HIGHEST_PRIORITY = 255
+};
+
+// MSG_HANDLER_FLAGS
+enum
+{
+	mHANDLE_FOREIGN 		= 0x0001,
+	mHANDLE_LATE			= 0x0002,
+	mHANDLE_CRC_MISMATCH	= 0x0004,
+};
+
+enum
+{
+	HANDLER_ERROR,
+	HANDLER_CONTINUE,
+	HANDLER_HALT,
+	HANDLER_MSG_DONE,
+	HANDLER_MSG_DESTROYED,
+};
+	
+typedef enum
+{
+	QUEUE_DEFAULT,
+	QUEUE_IMPORTANT,
+	QUEUE_SEQUENCED
+
+} QUEUE_TYPE;
+
+enum ConnType
+{
+	vCONN_TYPE_NONE,
+	vCONN_TYPE_MODEM,
+	vCONN_TYPE_ETHERNET,
+	vCONN_TYPE_PPPOE
+};
+
+enum DeviceType
+{
+	vDEV_TYPE_NONE,
+	vDEV_TYPE_USB_ETHERNET,
+	vDEV_TYPE_USB_MODEM,
+	vDEV_TYPE_PC_ETHERNET,
+	vDEV_TYPE_SONY_MODEM,
+};
+
+enum
+{
+	vMODEM_CONNECT_TIMEOUT		= 60,	// seconds
+	vMODEM_DISCONNECT_TIMEOUT	= 10,	// seconds
+	vMODEM_RESET_TIMEOUT		= 10,	// seconds
+};
+
+enum
+{
+	vRES_SUCCESS,
+	vRES_ERROR_GENERAL,
+	vRES_ERROR_UNKNOWN_DEVICE,
+	vRES_ERROR_INVALID_IRX,
+	vRES_ERROR_DEVICE_NOT_CONNECTED,
+	vRES_ERROR_DEVICE_NOT_HOT,
+	vRES_ERROR_DHCP,
+	vRES_ERROR_DEVICE_CHANGED,
+};
+
+enum
+{
+	vMODEM_STATE_DIALING,
+	vMODEM_STATE_CONNECTED,
+	vMODEM_STATE_LOGGED_IN,
+	vMODEM_STATE_DISCONNECTING,
+	vMODEM_STATE_HANGING_UP,
+	vMODEM_STATE_DISCONNECTED,
+	vMODEM_STATE_READY_TO_TRANSMIT,
+	vMODEM_STATE_ERROR,
+};
+
+enum
+{
+	mMSG_SIZE_UNKNOWN = 0x01
+};
+
+class App;
+class Server;
+class Client;
+class Conn;
+class Manager;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+// STANDARD NET MESSAGES
+class MsgMax
+{
+public:
+	char	m_Data[4096];
+};
+
+class MsgTimestamp
+{
+public:
+	unsigned int m_Timestamp;
+};
+
+class MsgPacketStamp
+{
+public:
+	//unsigned char  m_Handle;
+	unsigned short m_Packetstamp;
+};
+
+typedef MsgPacketStamp	MsgAck;
+
+class MsgAlias
+{
+public:
+	unsigned char	m_Alias;
+	int				m_ObjId;
+	int				m_Expiration;	// Frame of expiration
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class MsgStreamStart
+{
+public:
+	char	m_StreamDesc[32];
+	int		m_Size;
+	unsigned int	m_StreamId;
+	uint32	m_Checksum;
+	unsigned char	m_MsgId;
+	char	m_Data[ MAX_STREAM_CHUNK_LENGTH ];
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class MsgStreamData
+{
+public:
+	unsigned int	m_StreamId;
+	char			m_Data[ MAX_STREAM_CHUNK_LENGTH ];
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class StreamDesc
+{
+public:
+	StreamDesc( void );
+	~StreamDesc( void );
+	int			m_Size;
+	unsigned int	m_StreamId;
+	unsigned char		m_MsgId;
+	char		m_StreamDesc[32];
+	char*		m_Data;
+	char* 		m_DataPtr;
+	unsigned char	m_GroupId;
+	unsigned int	m_SequenceId;
+	uint32		m_Checksum;
+	bool		m_SendInPlace;
+};
+
+class StreamLink : public Lst::Node< StreamLink >
+{
+public:
+	StreamLink( StreamDesc* desc );
+
+	StreamDesc*	m_Desc;
+};
+
+// NET MESSAGE LINKS
+class QueuedMsg
+{
+public:
+	QueuedMsg( unsigned char msg_id, unsigned short msg_len, void* data );
+	~QueuedMsg( void );
+
+	unsigned char	m_MsgId;
+	unsigned short	m_MsgLength;
+	char*			m_Data;
+	Spt::Ref		m_Ref;
+};
+
+class QueuedMsgSeq
+{
+public:
+	QueuedMsgSeq( unsigned char msg_id, unsigned short msg_len, void* data, unsigned char group_id );
+	~QueuedMsgSeq( void );
+
+	unsigned char	m_MsgId;
+	unsigned short	m_MsgLength;
+	char*			m_Data;
+	char			m_GroupId;
+	
+	Spt::Ref	m_Ref;
+};
+
+class MsgLink : public Lst::Node< MsgLink >
+{
+public:
+	MsgLink( QueuedMsg* msg );
+
+	QueuedMsg*		m_QMsg;
+	unsigned int	m_SendTime;
+};
+
+class MsgImpLink : public Lst::Node< MsgImpLink >
+{	
+public:
+	MsgImpLink( QueuedMsg* msg );
+
+public:
+	unsigned int 	m_Timestamp;
+	QueuedMsg*		m_QMsg;
+	unsigned int	m_Packetstamp;
+	unsigned char	m_NumResends;
+	unsigned int	m_SendTime;
+};
+
+class MsgSeqLink : public Lst::Node< MsgSeqLink >
+{
+public:
+	MsgSeqLink( QueuedMsgSeq* msg );
+
+	unsigned char	m_NumResends;
+	unsigned char	m_GroupId;
+	char			m_StreamMessage;	// boolean
+	unsigned int	m_Packetstamp;
+	unsigned int 	m_Timestamp;
+	unsigned int	m_SequenceId;   
+	unsigned int	m_SendTime;
+
+	QueuedMsgSeq*	m_QMsg;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class AliasEntry
+{
+public:
+	int	m_Id;			// full id of aliased object
+	int m_Expiration;	// frame of expiration
+};
+
+class MsgHandlerContext
+{
+public:
+	
+	char*			m_Msg;
+	unsigned char	m_MsgId;
+	unsigned long   m_MsgLength;
+	App*			m_App;
+	Conn*			m_Conn;
+	int				m_PacketFlags;
+	void*			m_Data;
+};
+
+typedef int	(MsgHandlerCode)( MsgHandlerContext *context );
+
+class MsgHandler : public Lst::Node< MsgHandler >
+{
+	friend class Dispatcher;
+	friend class Server;
+	friend class Client;
+
+public:
+	MsgHandler( MsgHandlerCode *code, int flags = 0, 
+						void *data = NULL, int pri = NORMAL_PRIORITY );
+
+private:
+	MsgHandlerCode	*m_code;
+	void			*m_data;
+	int				m_flags;
+	
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class LatencyTest
+{
+public:
+
+	LatencyTest ();
+
+	void	InputLatencyValue( int latency );
+
+	int				m_AveLatency;
+	unsigned int	m_TimeTests[NET_NUM_LATENCY_TESTS];
+	unsigned int	m_CurrentTest;
+	unsigned int	m_SendTime;
+	unsigned int	m_ReceiveTime;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  PacketInfo  : public Spt::Class
+{	
+public:
+	int			GetNumBytes( void );
+	int			GetTime( void );
+
+	void		SetNumBytes( int size );
+	void		SetTime( int time );
+
+private:
+	int			m_num_bytes;
+	int			m_time;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Metrics  : public Spt::Class
+{
+public:
+	enum
+	{
+		vNUM_BUFFERED_PACKETS = 256
+	};
+
+				Metrics( void );
+
+	void		CalculateBytesPerSec( int cur_time );
+	int			GetBytesPerSec( void );
+	
+	int			GetTotalBytes( void );
+	int			GetTotalMessageData( int msg_id );
+	int			GetTotalNumMessagesOfId( int msg_id );
+		
+	void		AddPacket( int size, int time );
+	void		AddMessage( int msg_id, int size );
+    
+private:
+
+	PacketInfo	m_packets[ vNUM_BUFFERED_PACKETS ];
+	int			m_num_packets;
+		
+	int			m_num_messages[ MAX_MSG_IDS ];		// total # messages per msg id
+	int			m_size_messages[ MAX_MSG_IDS ];		// total # bytes per msg id
+
+	int			m_total_bytes;
+	int			m_bytes_per_sec;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// NET CONNECTION
+
+class Conn
+{
+	friend class Client;
+	friend class Server;
+	friend class App;
+	friend class Dispatcher;
+
+private:
+	
+	Lst::Head< MsgLink >	m_normal_msg_list;
+	Lst::Head< MsgImpLink >	m_important_msg_list;
+	Lst::Head< MsgSeqLink >	m_sequenced_msg_list;
+
+	friend	MsgHandlerCode	handle_ack;	
+	friend	MsgHandlerCode	handle_latency_response;
+	friend	MsgHandlerCode	handle_latency_test;
+
+	int				m_handle;
+	int				m_ip;
+	unsigned short	m_port;
+	
+	char*			m_write_buffer;
+	char*			m_write_ptr;
+	char*			m_read_buffer;
+	char*			m_read_ptr;
+	int				m_num_bytes_waiting;
+
+	
+	Flags< int >	m_status;
+	Flags< int >	m_flags;
+    
+	App*			m_app;	
+	Tmr::Time		m_last_comm_time;	// The last time we've communicated with this connection
+    int				m_last_send_time;
+	int				m_send_interval;
+public:
+	enum Status
+	{
+		mSTATUS_READY 			= 0x0001,
+		mSTATUS_BUSY			= 0x0002,
+		mSTATUS_INVALID			= 0x0004,
+		mSTATUS_DISCONNECTING	= 0x0008
+	};
+
+	enum
+	{
+		mLOCAL		= 0x01,	// local connection: i.e. on this machine
+		mREMOTE		= 0x02,	// remote connection: i.e. on another machine
+		mFOREIGN	= 0x04,	// foreign connection: i.e. one we don't recognize
+	};
+
+	enum
+	{   
+		vHANDLE_INVALID	= 0,
+		vHANDLE_FIRST 	= 1,
+	};
+
+	enum
+	{
+#ifdef __PLAT_WN32__
+		vREAD_BUFFER_LENGTH = 10*MAX_UDP_PACKET_SIZE // Used to be 2048
+#else
+		vREAD_BUFFER_LENGTH = 2*MAX_UDP_PACKET_SIZE // Used to be 2048
+#endif
+	};
+
+	enum
+	{
+		vBROADBAND,
+		vNARROWBAND,
+		vLAN,
+	};
+
+	Conn( int flags );
+	~Conn( void );
+
+	void			DestroyAllMessageData( void );
+    void			AckAllMessages( void );
+	void			DestroyMessageQueues( void );
+	void			DestroyImportantMessageQueues( void );
+
+	int				GetHandle( void );
+	unsigned short	GetPort( void );
+	int				GetIP( void );
+	int				GetResendThreshold( void );
+	Tmr::Time		GetLastCommTime( void );
+	Tmr::Time		GetTimeElapsedSinceCommunication( void );
+	void			UpdateCommTime( Tmr::Time extra_time = 0 );
+	
+	void			SetStatus( int status_mask );
+	void			ClearStatus( int status_mask );
+	bool			TestStatus( int status_mask );
+	int				GetStatus( void );
+
+	void			SetIP( int ip );
+	void			SetPort( unsigned short port );
+
+	void			Invalidate( void );
+	bool			IsValid( void );
+	bool			IsForeign( void );
+	bool			IsLocal( void );
+	bool			IsRemote( void );
+    
+	void			SetSendInterval( int interval );		// in milliseconds
+	int				GetSendInterval( void );
+	void			SetForceSendThisFrame( bool force_send );
+	bool			GetForceSendThisFrame( void );
+	int				GetBandwidthType( void );
+    void			SetBandwidthType( int type );
+
+	int				GetAveLatency( void );
+
+	Metrics*		GetOutboundMetrics( void );
+	Metrics*		GetInboundMetrics( void );
+
+	void			SetBandwidth( int in_bytes_per_sec );
+	int				GetBandwidth( void );
+
+	void			ClearNumResends( void );
+	int				GetNumResends( void );
+	void			FlagMessagesForResending( unsigned int packet_stamp );
+
+	int				GetNumPendingStreamMessages( void );
+
+	unsigned int	m_SequenceId[MAX_SEQ_GROUPS];			// per-group, per-client array of 'next' sequence ids		
+	unsigned int	m_WaitingForSequenceId[MAX_SEQ_GROUPS];	// per-group, per-client array of 'waiting for' sequence ids			
+	unsigned int	m_NextStreamId;			// Next stream id
+	Lst::Head< MsgSeqLink >	m_SequencedBuffer[MAX_SEQ_GROUPS];	
+	Lst::Head< StreamLink > m_StreamInList;
+	Lst::Head< StreamLink > m_StreamOutList;
+    
+private:
+	void			destroy_pending_sequenced_messages( void );
+
+	LatencyTest 	m_latency_test;
+	Metrics			m_metrics_in;
+	Metrics			m_metrics_out;
+	bool			m_force_send_packet;
+	Lst::Node< Conn > m_node;
+	Conn*			m_alias_connection;
+	int				m_bandwidth_type;
+	unsigned int 	m_latest_packet_stamp;
+	unsigned int 	m_latest_sent_packet_stamp;
+	int				m_bandwidth;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class Dispatcher
+{	
+	friend class App;
+
+public:
+	Dispatcher( App* app ) 
+		: m_app( app ) {}
+	MsgHandler*	AddHandler( unsigned char net_msg_id, MsgHandlerCode *code, int flags = 0, 
+						void *data = NULL, int pri = NORMAL_PRIORITY );
+	void		Init( void );
+	void		Deinit( void );
+	int			DispatchMsgHandlers( Conn *conn, int flags );
+	int			DispatchMessage( MsgHandlerContext *context );
+
+private:
+	Lst::Head< MsgHandler > m_handler_list[MAX_NET_MSG_ID];
+	App*		m_app;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Manager  : public Spt::Class
+{
+	
+public:
+	enum
+	{
+		vMAX_MSG_NAME_LEN	= 32,
+		vMAX_PACKET_SIZE 	= MAX_UDP_PACKET_SIZE,
+		vMIN_PACKET_SIZE	= 3,		// msg should be sizeof( crc) plus at least 1 byte of data
+		vPORT_ANY 			= 0,
+		vSTANDARD_RESEND_THRESHOLD 	= 2000,	// ms
+		vMINIMUM_RESEND_THRESHOLD 	= 500,		// ms
+		vMSG_HEADER_LENGTH 			= 1,
+		vMSG_HEADER_LENGTH_WITH_SIZE= 3,
+		vMSG_SEQ_HEADER_LENGTH	= 6,
+		vMSG_CRC_LEN = 2,
+		vMAX_PAYLOAD = vMAX_PACKET_SIZE - vMSG_CRC_LEN,
+	};
+
+	void		SetError( int error );
+	int			GetLastError( void );
+
+	bool		ConnectToInternet( void );
+	bool		DisconnectFromInternet( void );
+	bool		IsOnline( void );
+
+	Server*		CreateNewAppServer( int id, char* appName, int max_clients, unsigned short port, 
+									int address, int flags = 0 );
+	Client*		CreateNewAppClient( int id, char* appName, unsigned short port, int address,
+									int flags = 0 );
+	
+	void		DestroyApp( App *app );
+		
+	void		SetConnectionType( ConnType conn_type );
+	void		SetDeviceType( DeviceType dev_type );
+	void		SetPublicIP( unsigned int ip );
+	void		SetLocalIP( char* ip );
+	void		SetGateway( char* ip );
+	void		SetSubnetMask( char* ip );
+	void		SetISPPhoneNumber( char* phone_no );
+	void		SetISPLogin( char* login );
+	void		SetISPPassword( char* password );
+	void		SetDHCP( bool use_dhcp );
+	void		SetDialupAuthentication( bool authenticate );
+	void		SetDNSServer( int index, char* ip );
+	void		SetHostName( char* host );
+	void		SetDomainName( char* host );
+	ConnType	GetConnectionType( void );
+	DeviceType	GetDeviceType( void );
+	char*		GetLocalIP( void );
+	unsigned int	GetPublicIP( void );
+	char*		GetGateway( void );
+	char*		GetSubnetMask( void );
+	char*		GetDNSServer( int index );
+	char*		GetISPPhoneNumber( void );
+	char*		GetHostName( void );
+	char*		GetDomainName( void );
+	bool		ShouldUseDialupAuthentication( void );
+	bool		ShouldUseDHCP( void );
+
+	Server*		FirstServer( Lst::Search< App > *sh );
+	Client*		FirstClient( Lst::Search< App > *sh );
+	App*		NextApp( Lst::Search< App > *sh );
+	int			NumApps( void );
+
+	void		SetMessageName( unsigned char msg_id, char* msg_name );
+	char*		GetMessageName( unsigned char msg_id );
+	void		SetMessageFlags( unsigned char msg_id, char flags );
+	char		GetMessageFlags( unsigned char msg_id );
+
+	void		AddLogicTasks( App* app );
+	void		AddLogicPushTasks( App* app );
+	void		RemoveNetworkTasks( App* app );
+
+	bool		NeedToTestNetworkEnvironment( void );
+	bool		NetworkEnvironmentSetup( void );
+
+	int			GetModemState( void );
+	void		SetModemState( int state );
+	int			GetModemError( void );
+	int			GetModemBaudRate( void );
+
+	void		SetBandwidth( int bytes_per_sec );
+	int			GetBandwidth( void );
+
+	bool		CanChangeDevices( void );
+	
+protected:	
+	Lst::Head< App >	m_net_servers;
+	Lst::Head< App >	m_net_clients;
+	
+private:
+	Manager();
+	~Manager();
+
+#ifdef NET_DEBUG_MESSAGES
+	char		m_message_names[ MAX_MSG_IDS ][ vMAX_MSG_NAME_LEN ];	// Text names for each message
+#endif
+	char		m_message_flags[ MAX_MSG_IDS ];
+	ConnType	m_conn_type;
+	DeviceType	m_device_type;
+	char		m_local_ip[16];
+	unsigned int	m_public_ip;
+	char		m_dns_servers[3][16];
+	char		m_gateway[16];
+	char		m_subnet[16];
+	char		m_isp_phone_no[32];
+	char		m_isp_user_name[80];
+	char		m_isp_password[80];
+	char		m_host_name[32];
+	char		m_domain_name[32];
+	int			m_num_apps;
+	int			m_last_error;
+
+	bool		m_online;
+	bool		m_use_dhcp;
+	bool		m_use_dialup_auth;
+	int			m_modem_state;
+	int			m_modem_err;	
+	int			m_modem_baud_rate;
+	int			m_bandwidth;
+#ifdef __PLAT_NGC__
+	bool		initialize_ngc( void );
+#endif
+#ifdef __PLAT_NGPS__
+	bool		initialize_ps2( void );
+	bool		sn_stack_setup( void );
+	bool		setup_ethernet_params( void );
+	bool		initialize_device( void );
+	bool		load_irx_files( void );
+	bool		start_stack( void );
+	bool		stop_stack( void );
+	bool		m_stack_setup;
+	bool		m_net_drivers_loaded;
+	bool		m_options_changed;
+	bool		m_device_changed;
+    
+
+	// Sn Modem Setup
+	static 	void 			conn_modem_state_callback( sn_int32 modem_state );
+	static 	void 			disconn_modem_state_callback( sn_int32 modem_state );
+	char					m_modem_thread_stack[ vMODEM_THREAD_STACK_SIZE ]	__attribute__ ((aligned(16)));
+	static 	void			threaded_modem_conn( void *data );
+	static 	void			threaded_modem_disconn( void *data );
+	Thread::PerThreadStruct	m_modem_thread_data;
+	Thread::THREADID		m_modem_thread_id;
+
+#endif // __PLAT_NGPS__
+    
+	DeclareSingletonClass( Manager );
+};
+
+class BitStream
+{
+public:
+	BitStream( void );
+
+	void			SetInputData( char* data, int size );
+	void			SetOutputData( char* data, int size );
+
+	void			WriteValue( int value, int num_bits );
+	void			WriteFloatValue( float value );
+	void 			Flush( void );
+	float			ReadFloatValue( void );
+	unsigned int	ReadUnsignedValue( int num_bits );
+	int				ReadSignedValue( int num_bits );
+
+	int				GetByteLength( void );
+
+private:
+	unsigned int*	m_data;
+	unsigned int*	m_start_data;
+	unsigned int	m_cur_val;
+	int				m_bits_left;
+	int				m_size;
+	int				m_bits_processed;
+};
+
+class MsgDesc
+{
+public:
+	MsgDesc( void );
+
+	unsigned char	m_Id;
+	char			m_StreamMessage;	// bool
+	unsigned short	m_Length;
+	void*			m_Data;
+	int				m_Priority;
+	QUEUE_TYPE		m_Queue;
+	unsigned char	m_GroupId;
+    bool			m_Singular;
+	int				m_Delay;
+	unsigned int	m_ForcedSequenceId;
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class BannedConn : public Lst::Node< BannedConn >
+{
+public:
+	BannedConn( void ) : Lst::Node< BannedConn > ( this ) {}
+	int	m_Ip;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class App
+{
+friend class Manager;
+
+public:
+	enum
+	{
+		mBROADCAST				= 0x01,
+		mALIAS_SUPPORT			= 0x02,
+		mDYNAMIC_RESEND			= 0x04,
+		mLOCAL					= 0x08,	// Local client (i.e. no real port/address info associated with it)
+		mACCEPT_FOREIGN_CONN	= 0x10,
+		mSECURE					= 0x20,
+	};
+    
+	App( int flags = 0 );
+	virtual	~App( void );
+
+	static	const	unsigned int	MAX_LATENCY;
+	typedef void	(ForeignPacketHandlerCode)( char* packet, int len, struct sockaddr* sender );
+    
+	Conn	*GetConnectionByHandle( int handle );
+	Conn	*GetConnectionByAddress( int ip, unsigned short port );
+	Conn	*NewConnection( int ip, unsigned short port, int flags = Conn::mREMOTE );
+	void	BanConnection( Conn* conn );
+	bool	IsConnectionBanned( Conn* conn );
+
+	void	StreamMessageToConn( Net::Conn* conn, unsigned char msg_id, unsigned short msg_len, void* data, char* desc, unsigned char group_id = GROUP_ID_DEFAULT,
+								 bool all_at_once = true, bool send_in_place = false );
+	void	StreamMessage( int handle, unsigned char msg_id, unsigned short msg_len, void* data, char* desc, unsigned char group_id = GROUP_ID_DEFAULT,
+						   bool all_at_once = true, bool send_in_place = false );
+	void	StreamMessageToServer( unsigned char msg_id, unsigned short msg_len, void* data, char* desc, unsigned char group_id = GROUP_ID_DEFAULT,
+								   bool all_at_once = true, bool send_in_place = false );
+	void	EnqueueMessage( int handle, MsgDesc* desc );
+	void	EnqueueMessageToServer( MsgDesc* desc );
+	void 	DequeueMessagesByType( Net::Conn* conn, QUEUE_TYPE queue, unsigned char msg_id );
+	void	FreeConnMessageQueue( Conn *conn, QUEUE_TYPE queue );
+	void	FreeMessageQueue( QUEUE_TYPE queue );
+	bool	BuildMsgStream( Conn *conn, QUEUE_TYPE queue, bool resends_only = false );
+	QueuedMsgSeq*	AllocateNewSeqMessage( unsigned char msg_id, unsigned short msg_len, char* data, unsigned char group_id );
+	bool	SendMessageTo( unsigned char msg_id, unsigned short msg_len, void* data,
+							int ip, unsigned short port, int flags );
+	bool	SendTo( int ip, unsigned short port, char *data, int len, int flags );
+	bool	Send( char *data, int len, int flags );
+	
+	Conn	*FirstConnection( Lst::Search< Conn > *sh );
+	Conn	*NextConnection( Lst::Search< Conn > *sh );	
+	void	TerminateConnection( Conn* conn );
+
+	Manager*	GetManager( void );
+	
+	int		GetID( void );
+	char*	GetName( void );
+
+	bool	IsLocal( void );
+	
+	void	AcceptForeignConnections( bool accept );
+	bool	AcceptsForeignConnections( void );
+
+	bool	ShouldSendThisFrame( Conn* conn );
+	bool	MessagesToProcess( Conn* conn );	
+	bool	MessagesToSend( Conn *conn );
+	bool	ImportantMessagesToSend( Conn* conn );
+
+	int		BandwidthUsed( void );
+	bool	BandwidthExceeded( Conn* conn );
+
+	void	ReportError( void );
+
+	void	AliasConnections( Conn* server_conn, Conn* client_conn );
+
+	Tsk::BaseTask&	GetSendDataTask( void );
+	Tsk::BaseTask&	GetReceiveDataTask( void );
+	Tsk::BaseTask&	GetProcessDataTask( void );
+	Tsk::BaseTask&	GetNetworkMetricsTask( void );
+	    
+	virtual	void	ProcessData( void );
+    
+	void			SendData( bool scheduled_send = false );
+	virtual	void	ReceiveData( void ) = 0;	
+
+	void	SendEnqueuedMessages( void );
+	virtual void	SendEnqueuedMessages( Conn* conn ) = 0;
+	
+	void			ShutDown( void );
+    		
+	SOCKET			GetSocket( void );
+	void			SetForeignPacketHandler( ForeignPacketHandlerCode* code );
+
+	unsigned int	m_Timestamp;
+	Dispatcher		m_Dispatcher;
+
+	unsigned int m_TotalBytesOut;
+	unsigned int m_TotalBytesIn;
+	unsigned int m_LostPackets;
+	unsigned int m_LatePackets;
+	unsigned int m_DupePackets;
+	unsigned int m_FrameCounter;
+
+#ifdef __PLAT_XBOX__
+	XNADDR		m_XboxAddr;
+	XNKID		m_XboxKeyId;
+	XNKEY		m_XboxKey;
+	BYTE		m_Nonce[8];
+#endif
+
+protected:
+
+	virtual bool	init( void );
+	virtual	void	deinit( void );
+
+	bool			bind_app_socket( int address, unsigned short port );
+
+	Lst::Node< App >	m_node;
+	SOCKET		m_socket;
+	struct	sockaddr_in	m_local_address;
+	bool	m_connected;
+	
+	Manager *m_net_man;	
+
+	Tsk::Task< App >*	m_process_network_data_task;
+	Tsk::Task< App >*	m_send_network_data_task;
+	Tsk::Task< App >*	m_receive_network_data_task;        
+	Tsk::Task< App >*	m_network_metrics_task;
+
+    void    process_stream_messages( void );
+	void	terminate_invalid_connections( void );
+	void	crc_and_copy_stream( char* in_stream, char* out_stream, int in_len, int* out_len );
+	bool	validate_and_copy_stream( char* in_stream, char* out_stream, int in_len );
+
+    int 	m_num_connections;
+	int 	m_max_connections;
+
+	static	MsgHandlerCode	handle_sequenced_messages; 	
+	static	MsgHandlerCode	handle_connection_request;
+	static	MsgHandlerCode	handle_connection_accepted;
+	static	MsgHandlerCode	handle_stream_messages; 	
+
+	char	m_out_packet_buffer[ Manager::vMAX_PACKET_SIZE ];
+	char	m_in_packet_buffer[ Manager::vMAX_PACKET_SIZE ];
+
+	Flags< int > m_flags;
+
+	ForeignPacketHandlerCode*	m_foreign_handler;
+#ifdef __PLAT_NGPS__
+
+public:
+	void			TransferData( void );
+	
+	void			WaitForAsyncCallsToFinish( void );
+	void			WaitForTransferSemaphore( void );
+	void			SignalTransferSemaphore( void );
+
+protected:
+	char	m_socket_thread_stack[ vSOCKET_THREAD_STACK_SIZE ]	__attribute__ ((aligned(16)));
+
+	bool	m_socket_thread_active;
+
+	Thread::THREADID	m_socket_thread_id;
+    
+	int		m_transfer_semaphore_id;
+	int		m_send_semaphore_id;
+	int		m_receive_semaphore_id;
+	int 	m_active_semaphore_id;
+	
+	Thread::PerThreadStruct	m_socket_thread_data;
+		
+	static 	void	threaded_transfer_data( void *data );
+
+	bool	m_shutting_down;
+#endif // __PLAT_NGPS__
+
+	static	Tsk::Task< App >::Code	process_network_data;
+	static	Tsk::Task< App >::Code	send_network_data;
+	static	Tsk::Task< App >::Code	read_network_data;
+	static	Tsk::Task< App >::Code	service_network_metrics;
+    
+private:
+
+	void	process_sequenced_messages( Conn *conn );   
+    
+	int	get_free_handle( void );
+	
+    Lst::Head< Conn >		m_connections;
+	Lst::Head< BannedConn >	m_banned_connections;
+	
+	int		m_id;
+	char	m_name[MAX_LEN_APP_NAME];
+	bool	m_loop_read;
+	double	m_double_timestamp;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	unsigned short	Conn::GetPort( void )
+{
+	return m_port;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	int		Conn::GetIP( void )
+{
+	return m_ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	int Conn::GetHandle( void )
+{
+	return m_handle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	int Conn::GetAveLatency( void )
+{
+	if( IsLocal())
+	{
+		return 0;
+	}
+	return m_latency_test.m_AveLatency;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void	Conn::SetIP( int ip )
+{
+	m_ip = ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void	Conn::SetPort( unsigned short port )
+{
+	m_port = port;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void		Conn::SetStatus( int status_mask )
+{
+	m_status.SetMask( status_mask );
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void		Conn::ClearStatus( int status_mask )
+{
+	m_status.ClearMask( status_mask );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool		Conn::TestStatus( int status_mask )
+{
+	return m_status.TestMask( status_mask );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int		Conn::GetStatus( void )
+{
+	return m_status;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	Metrics*	Conn::GetInboundMetrics( void )
+{
+	return &m_metrics_in;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	Metrics*	Conn::GetOutboundMetrics( void )
+{
+	return &m_metrics_out;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+}	// namespace Net
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+#ifdef __PLAT_XBOX__
+// Need this to be outside the Net:: namespace...
+inline char* inet_ntoa( struct in_addr addr )
+{
+	IN_ADDR		in_a;
+	in_a.S_un	= addr.S_un;
+
+	const int	STRING_BUFFER_SIZE = 100;
+//	static char	string_buffer[STRING_BUFFER_SIZE];
+//	int			rv = XNetInAddrToString( in_a, string_buffer, STRING_BUFFER_SIZE );
+
+	// Cheesy hack for now.
+	static char	string_buffer[STRING_BUFFER_SIZE] = "0.0.0.0";
+
+	return string_buffer;
+}
+#endif
+
+#endif // __NET_H__
+
diff --git a/Code/Gel/Net/netconn.cpp b/Code/Gel/Net/netconn.cpp
new file mode 100644
index 0000000..f87fa44
--- /dev/null
+++ b/Code/Gel/Net/netconn.cpp
@@ -0,0 +1,688 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Net (OBJ) 												**
+**																			**
+**	File name:		netconn.cpp												**
+**																			**
+**	Created:		08/09/01	-	spg										**
+**																			**
+**	Description:	Network Connection code									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Conn::Conn( int flags )
+: m_node( this )
+{
+	m_flags = flags;
+	m_status.ClearAll();
+	m_status.SetMask( mSTATUS_READY );
+	m_read_buffer = new char[ vREAD_BUFFER_LENGTH ];	
+	m_read_ptr = m_read_buffer;
+	m_write_buffer = new char[ Manager::vMAX_PACKET_SIZE ];
+	m_write_ptr = m_write_buffer;
+	m_send_interval = 0;
+	m_last_send_time = 0;
+	m_force_send_packet = false;
+	m_alias_connection = NULL;
+	m_latest_packet_stamp = 0;
+	m_latest_sent_packet_stamp = 0;
+#ifdef __PLAT_NGPS__
+	m_bandwidth = 4200;	// Start with a 33.6kbps modem's payload threshold (i.e. including packet overhead)
+#else
+	m_bandwidth = 400000;	// On xbox, just assume broadband
+#endif
+	
+	m_NextStreamId = 0;
+	memset( m_SequenceId, 0, MAX_SEQ_GROUPS );	
+	memset( m_WaitingForSequenceId, 0, MAX_SEQ_GROUPS );
+#ifdef	__PLAT_NGPS__											  
+	Dbg_MsgAssert(Mem::SameContext(this,Mem::Manager::sHandle().NetworkHeap()),("Conn not on network heap"));	
+#endif		//	__PLAT_NGPS__		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Conn::~Conn( void )
+{
+	DestroyAllMessageData();
+    
+	delete [] m_read_buffer;
+	delete [] m_write_buffer;
+}
+
+/******************************************************************/
+/*  Destroy pending sequenced messages							  */
+/*                                                                */
+/******************************************************************/
+
+void 	Conn::destroy_pending_sequenced_messages( void )
+{
+	int i;
+	Lst::Search< MsgSeqLink > sh;
+    MsgSeqLink *msg_link, *next;
+    
+	
+
+    for( i = 0; i < MAX_SEQ_GROUPS; i++ )
+	{
+        for( msg_link = sh.FirstItem( m_SequencedBuffer[i] );
+				msg_link; msg_link = next )
+		{
+			next = sh.NextItem();
+			
+			delete msg_link->m_QMsg;
+			delete msg_link;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Conn::DestroyAllMessageData( void )
+{
+	destroy_pending_sequenced_messages();
+
+	AckAllMessages();
+	DestroyMessageQueues();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Conn::DestroyMessageQueues( void )
+{
+	int i;
+	Lst::Search< MsgLink >	msg_sh;
+	Lst::Search< MsgImpLink >	imp_sh;
+	Lst::Search< MsgSeqLink >	seq_sh;
+	Lst::Search< StreamLink > stream_sh;
+
+	MsgLink *msg_link, *next_msg_link;
+	MsgImpLink *imp_link, *next_imp_link;
+	MsgSeqLink *seq_link, *next_seq_link;
+	StreamLink* str_link, *next_str_link;
+
+	for( msg_link = msg_sh.FirstItem( m_normal_msg_list ); msg_link; msg_link = next_msg_link )
+	{
+		next_msg_link = msg_sh.NextItem();
+        
+		delete msg_link;
+	}
+
+	for( imp_link = imp_sh.FirstItem( m_important_msg_list ); imp_link; imp_link = next_imp_link )
+	{
+		next_imp_link = imp_sh.NextItem();
+		
+		delete imp_link;
+	}
+
+	for( seq_link = seq_sh.FirstItem( m_sequenced_msg_list ); seq_link; seq_link = next_seq_link )
+	{
+		next_seq_link = seq_sh.NextItem();
+		
+		delete seq_link;
+	}
+
+	for( i = 0; i < MAX_SEQ_GROUPS; i++ )
+	{
+		for( seq_link = seq_sh.FirstItem( m_SequencedBuffer[i] ); seq_link; seq_link = next_seq_link )
+		{
+			next_seq_link = seq_sh.NextItem();
+			
+			delete seq_link;
+		}
+	}
+
+	//Dbg_Printf( "** Destroying StreamIn list\n" );
+	for( str_link = stream_sh.FirstItem( m_StreamInList ); str_link; str_link = next_str_link )
+	{
+		next_str_link = stream_sh.NextItem();
+
+		delete str_link->m_Desc;
+		delete str_link;
+	}
+
+	//Dbg_Printf( "** Destroying StreamOut list\n" );
+	for( str_link = stream_sh.FirstItem( m_StreamOutList ); str_link; str_link = next_str_link )
+	{
+		next_str_link = stream_sh.NextItem();
+
+		delete str_link->m_Desc;
+		delete str_link;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Conn::DestroyImportantMessageQueues( void )
+{
+	MsgImpLink *msg_imp_link, *next_imp;
+	MsgSeqLink *msg_seq_link, *next_seq;
+	Lst::Search< MsgLink > msg_sh;
+	Lst::Search< MsgImpLink > imp_sh;
+	Lst::Search< MsgSeqLink > seq_sh;
+	QueuedMsg* msg;
+	QueuedMsgSeq* msg_seq;
+		
+	for( msg_imp_link = imp_sh.FirstItem( m_important_msg_list ); 
+			msg_imp_link; msg_imp_link = next_imp )
+	{
+		next_imp = imp_sh.NextItem();
+		msg = msg_imp_link->m_QMsg;
+		msg->m_Ref.Release();
+		if( msg->m_Ref.InUse() == false )
+		{
+			delete msg;
+		}
+		
+		delete msg_imp_link;
+	}	
+
+	for( msg_seq_link = seq_sh.FirstItem( m_sequenced_msg_list ); 
+			msg_seq_link; msg_seq_link = next_seq )
+	{
+		next_seq = seq_sh.NextItem();
+		// ACK the message
+		msg_seq = msg_seq_link->m_QMsg;
+		msg_seq->m_Ref.Release();
+
+		Dbg_Printf( "*** Destroying seq message %d, still on queue\n", msg_seq->m_MsgId );
+		// If this message is no longer being referenced, free it
+		if( msg_seq->m_Ref.InUse() == false )
+		{
+			delete msg_seq;
+		}
+
+		delete msg_seq_link;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Conn::AckAllMessages( void )
+{
+	MsgLink* msg_link, *next_msg_link;
+	MsgImpLink *msg_imp_link, *next_imp;
+	MsgSeqLink *msg_seq_link, *next_seq;
+	Lst::Search< MsgLink > msg_sh;
+	Lst::Search< MsgImpLink > imp_sh;
+	Lst::Search< MsgSeqLink > seq_sh;
+	QueuedMsg* msg;
+	QueuedMsgSeq* msg_seq;
+		
+	for( msg_link = msg_sh.FirstItem( m_normal_msg_list ); msg_link; msg_link = next_msg_link )
+	{
+		next_msg_link = msg_sh.NextItem();
+		msg = msg_link->m_QMsg;
+		msg->m_Ref.Release();
+		if( msg->m_Ref.InUse() == false )
+		{
+			delete msg;
+		}
+	}
+
+	for( msg_imp_link = imp_sh.FirstItem( m_important_msg_list ); 
+			msg_imp_link; msg_imp_link = next_imp )
+	{
+		next_imp = imp_sh.NextItem();
+		msg = msg_imp_link->m_QMsg;
+		msg->m_Ref.Release();
+		if( msg->m_Ref.InUse() == false )
+		{
+			delete msg;
+		}
+	}	
+
+	for( msg_seq_link = seq_sh.FirstItem( m_sequenced_msg_list ); 
+			msg_seq_link; msg_seq_link = next_seq )
+	{
+		next_seq = seq_sh.NextItem();
+		// ACK the message
+		msg_seq = msg_seq_link->m_QMsg;
+		msg_seq->m_Ref.Release();
+		// If this message is no longer being referenced, free it
+		if( msg_seq->m_Ref.InUse() == false )
+		{
+			delete msg_seq;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tmr::Time	Conn::GetLastCommTime( void )
+{
+	if( IsLocal())
+	{
+		return Tmr::GetTime();
+	}
+	return m_last_comm_time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Conn::UpdateCommTime( Tmr::Time extra_time )
+{
+	Tmr::Time cur_time;
+
+	cur_time = Tmr::GetTime();
+	if(( cur_time + extra_time ) > m_last_comm_time )
+	{
+		m_last_comm_time = cur_time + extra_time;
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tmr::Time	Conn::GetTimeElapsedSinceCommunication( void )
+{
+	Tmr::Time cur_time;
+
+	cur_time = Tmr::GetTime();
+
+	if( cur_time < m_last_comm_time )
+	{
+		return 0;
+	}
+
+    return cur_time - m_last_comm_time;
+}
+
+/******************************************************************/
+/* Used to invalidate a connection. This way, you can defer the   */
+/* Destruction of the connection until later, but ignore it now   */
+/******************************************************************/
+
+void	Conn::Invalidate( void )
+{
+	//Dbg_Message( "Invalidating Connection\n" );
+	ClearStatus( mSTATUS_READY );
+	SetStatus( mSTATUS_INVALID );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Conn::IsValid( void )
+{
+	
+
+	return( TestStatus( mSTATUS_INVALID ) == false );
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Conn::IsForeign( void )
+{
+	return m_flags.TestMask( mFOREIGN );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Conn::IsLocal( void )
+{
+	return m_flags.TestMask( mLOCAL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Conn::IsRemote( void )
+{
+	return m_flags.TestMask( mREMOTE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Conn::GetResendThreshold( void )
+{
+	int threshold;
+	 
+	// Don't resend as often if the client is busy
+	if( TestStatus( mSTATUS_BUSY ))
+	{
+		return Tmr::Seconds( 1 );
+	}
+
+	// by default, the resend threshold is two times your average latency
+	threshold = 2 * GetAveLatency();
+
+	// We have a threshold for resending.  So that clients don't spam the server with resends
+	// while he's busy
+	if( threshold < Manager::vMINIMUM_RESEND_THRESHOLD )
+	{
+		return Manager::vMINIMUM_RESEND_THRESHOLD;
+	}
+
+	return threshold;
+}
+
+/******************************************************************/
+/* Send every N milliseconds                                  	  */
+/*                                                                */
+/******************************************************************/
+
+void	Conn::SetSendInterval( int interval )
+{
+	m_send_interval = interval;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Conn::GetSendInterval( void )
+{
+	return m_send_interval;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Conn::SetForceSendThisFrame( bool force_send )
+{
+	m_force_send_packet = force_send;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+bool	Conn::GetForceSendThisFrame( void )
+{
+	return m_force_send_packet;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Conn::GetNumResends( void )
+{
+	MsgImpLink *msg_imp_link;
+	MsgSeqLink *msg_seq_link;
+	Lst::Search< MsgImpLink > imp_sh;
+	Lst::Search< MsgSeqLink > seq_sh;
+	int highest_num_resends;
+    
+	
+
+	highest_num_resends = 0;
+
+	for( msg_imp_link = imp_sh.FirstItem( m_important_msg_list ); 
+			msg_imp_link; msg_imp_link = imp_sh.NextItem())
+	{
+		if( msg_imp_link->m_NumResends > highest_num_resends )
+		{
+			highest_num_resends = msg_imp_link->m_NumResends;
+		}
+	}	
+
+	for( msg_seq_link = seq_sh.FirstItem( m_sequenced_msg_list ); 
+			msg_seq_link; msg_seq_link = seq_sh.NextItem())
+	{
+		if( msg_seq_link->m_NumResends > highest_num_resends )
+		{
+			highest_num_resends = msg_seq_link->m_NumResends;
+		}
+	}
+
+	return highest_num_resends;
+}
+
+/******************************************************************/
+/* Get the number of stream messages still pending for this conn. */
+/* If num_pending > our sliding window's size, don't send any more*/
+/******************************************************************/
+
+int		Conn::GetNumPendingStreamMessages( void )
+{
+	MsgSeqLink *msg_seq_link;
+	Lst::Search< MsgSeqLink > seq_sh;
+	int num_pending;
+
+	num_pending = 0;
+	for( msg_seq_link = seq_sh.FirstItem( m_sequenced_msg_list ); msg_seq_link; msg_seq_link = seq_sh.NextItem())
+	{
+		if( msg_seq_link->m_StreamMessage )
+		{
+			num_pending++;
+		}
+	}
+
+	return num_pending;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Conn::ClearNumResends( void )
+{
+	MsgImpLink *msg_imp_link;
+	MsgSeqLink *msg_seq_link;
+	Lst::Search< MsgImpLink > imp_sh;
+	Lst::Search< MsgSeqLink > seq_sh;
+	
+	
+
+    for( msg_imp_link = imp_sh.FirstItem( m_important_msg_list ); 
+			msg_imp_link; msg_imp_link = imp_sh.NextItem())
+	{
+		msg_imp_link->m_NumResends = 0;
+	}	
+
+	for( msg_seq_link = seq_sh.FirstItem( m_sequenced_msg_list ); 
+			msg_seq_link; msg_seq_link = seq_sh.NextItem())
+	{
+		msg_seq_link->m_NumResends = 0;
+	}
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Conn::GetBandwidthType( void )
+{
+	return m_bandwidth_type;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Conn::SetBandwidthType( int type )
+{
+	m_bandwidth_type = type;
+	
+	// Some default values for bandwidth limiting
+	switch( m_bandwidth_type )
+	{
+		case vNARROWBAND:
+			SetBandwidth( 4200 );	
+			break;
+		case vBROADBAND:
+			SetBandwidth( 400000 );
+			break;
+		case vLAN:
+			SetBandwidth( 800000 );
+			break;
+		default:
+			break;
+	}
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Conn::SetBandwidth( int bytes_per_sec )
+{
+	m_bandwidth = bytes_per_sec;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+int		Conn::GetBandwidth( void )
+{
+	return m_bandwidth;
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+void	Conn::FlagMessagesForResending( unsigned int packet_stamp )
+{
+	MsgImpLink *msg_imp_link;
+	MsgSeqLink *msg_seq_link;
+	Lst::Search< MsgImpLink > imp_sh;
+	Lst::Search< MsgSeqLink > seq_sh;
+
+	// Mark each message of a matching packet stamp with a timestamp of zero.
+	// Doing so signifies that it should be sent next frame
+	for( msg_imp_link = imp_sh.FirstItem( m_important_msg_list ); 
+			msg_imp_link; msg_imp_link = imp_sh.NextItem() )
+	{
+		if( msg_imp_link->m_Packetstamp == packet_stamp )
+		{
+			msg_imp_link->m_Timestamp = 0;
+		}
+	}	
+
+	for( msg_seq_link = seq_sh.FirstItem( m_sequenced_msg_list ); 
+			msg_seq_link; msg_seq_link = seq_sh.NextItem() )
+	{
+		if( msg_seq_link->m_Packetstamp == packet_stamp )
+		{
+			msg_seq_link->m_Timestamp = 0;
+		}
+	}
+}
+
+/******************************************************************/
+/* 			                                  					  */
+/*                                                                */
+/******************************************************************/
+
+}	// namespace Net
\ No newline at end of file
diff --git a/Code/Gel/ObjPtr.h b/Code/Gel/ObjPtr.h
new file mode 100644
index 0000000..556d106
--- /dev/null
+++ b/Code/Gel/ObjPtr.h
@@ -0,0 +1,204 @@
+#ifndef __GEL_OBJPTR_H
+#define __GEL_OBJPTR_H
+
+namespace Obj
+{
+
+
+
+class CRefCounted;
+
+
+
+/*
+	-Self assignment
+	-http://web.ftech.net/~honeyg/articles/smartp.htm
+*/
+
+template
+class CSmtPtr
+{
+	friend class CRefCounted;
+
+public:							
+							CSmtPtr();
+							CSmtPtr(T* pT);
+							CSmtPtr(CSmtPtr &ref);
+							~CSmtPtr();
+
+	CSmtPtr &			operator=(T* pT);
+	CSmtPtr &			operator=(CSmtPtr &ref);
+	T *						operator->() const;
+	T &						operator*() const;
+	T **					operator&() const;
+
+	bool					operator!() const {return (mp_object == NULL);}
+	
+							operator T*() const {return mp_object;}
+
+	friend bool 			operator==(const CSmtPtr& lhs, T* pRhs) {return lhs.mp_object == pRhs;}
+	friend bool 			operator!=(const CSmtPtr& lhs, T* pRhs) {return lhs.mp_object != pRhs;}
+
+	T *						Convert() const {return mp_object;}
+	void					Kill() const;
+
+private:
+
+	void					add_ref();
+	void					remove_ref();
+	
+	T *						mp_object;
+	
+	CSmtPtr *	mp_prev_ptr;
+	CSmtPtr *	mp_next_ptr;
+};
+
+
+
+
+template
+inline CSmtPtr::CSmtPtr()
+{
+	mp_prev_ptr = NULL;
+	mp_next_ptr = NULL;
+	
+	mp_object = NULL;
+}
+
+
+
+
+template
+inline CSmtPtr::CSmtPtr(T *pT)
+{
+	mp_prev_ptr = NULL;
+	mp_next_ptr = NULL;
+	
+	mp_object = pT;
+	add_ref();
+	//printf("WWW in copy constructor for 0x%x\n", mp_object);
+}
+
+
+
+
+template
+inline CSmtPtr::CSmtPtr(CSmtPtr &ref)
+{
+	mp_prev_ptr = NULL;
+	mp_next_ptr = NULL;
+	
+	mp_object = ref.mp_object;
+	add_ref();
+	//printf("WWW in copy constructor 2 for pointer at 0x%x, object 0x%x\n", this, mp_object);
+}
+
+
+
+
+template
+inline CSmtPtr::~CSmtPtr()
+{
+	//printf("WWW in destructor for 0x%x\n", mp_object);
+	remove_ref();
+}
+
+
+
+
+template
+inline CSmtPtr & CSmtPtr::operator=(T *pT)
+{
+	//printf("WWW in CSmtPtr::operator=(T *pT) for pointer at 0x%x, object 0x%x (old) and 0x%x (new)\n", this, mp_object, pT);
+	remove_ref();
+	mp_object = pT;
+	add_ref();
+	return *this;
+}
+
+
+
+
+template
+inline CSmtPtr & CSmtPtr::operator=(CSmtPtr &ref)
+{
+	//printf("WWW in CSmtPtr::operator=(const CSmtPtr &ref) for pointer at 0x%x, object 0x%x (old) and 0x%x (new)\n", this, mp_object, ref.mp_object);
+	remove_ref();
+	mp_object = ref.mp_object;
+	add_ref();
+	return *this;
+}
+
+
+
+
+template
+inline T * CSmtPtr::operator->() const
+{
+	Dbg_MsgAssert(mp_object, ("NULL smart pointer!"));
+	return (T *) mp_object;
+}
+
+
+
+
+template
+inline T & CSmtPtr::operator*()	const
+{
+	Dbg_MsgAssert(mp_object, ("NULL smart pointer!"));
+	return *((T *) mp_object);
+}
+
+
+
+
+template
+inline T ** CSmtPtr::operator&()	const
+{
+	Dbg_MsgAssert(0, ("operator& not supported, comrade!"));
+}
+
+
+
+
+template
+inline void CSmtPtr::Kill() const 
+{
+	Dbg_MsgAssert(mp_object, ("attempting delete with NULL smart pointer!"));
+	// this will lead to remove_ref() being called on this
+	delete mp_object;
+}
+
+
+
+
+template
+inline void CSmtPtr::add_ref()
+{
+	if (mp_object)
+	{
+		//printf("WWW adding ref to 0x%x\n", mp_object);
+		mp_object->AddSmartPointer((CSmtPtr *) this);
+	}
+}
+
+
+
+
+template
+inline void CSmtPtr::remove_ref()
+{
+	if (mp_object)
+	{
+		//printf("WWW removing ref from 0x%x\n", mp_object);
+		mp_object->RemoveSmartPointer((CSmtPtr *) this);
+		mp_object = NULL;
+	}
+}
+
+
+
+
+}
+
+#endif
diff --git a/Code/Gel/Object/Event.cpp b/Code/Gel/Object/Event.cpp
new file mode 100644
index 0000000..890852c
--- /dev/null
+++ b/Code/Gel/Object/Event.cpp
@@ -0,0 +1,1092 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+#ifdef __NOPT_ASSERT__
+#include 
+#endif
+
+namespace Obj
+{
+	
+#ifdef __NOPT_ASSERT__
+extern bool DebugSkaterScripts;
+#endif
+
+
+
+
+CEvent::CEvent()
+{
+	m_flags = 0;
+}
+
+
+
+
+CEvent::~CEvent()
+{
+	if (m_flags & mCONTROLS_OWN_DATA && mp_data)
+		delete mp_data;
+}
+
+
+
+
+void CEvent::MarkRead(uint32 receiverId, uint32 script)
+{
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	p_tracker->LogEventRead(this, receiverId, script);
+	m_flags |= mREAD;
+}
+
+
+
+
+void CEvent::MarkHandled(uint32 receiverId, uint32 script)
+{
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	p_tracker->LogEventHandled(this, receiverId, script);
+	m_flags |= mHANDLED;
+}
+
+
+
+
+int	CEvent::sExtractControllerIndex(CEvent *pEvent)
+{
+	int controller = 0;
+	Script::CStruct *pData = pEvent->GetData();
+	if (pData)
+		pData->GetInteger("controller", &controller);
+
+	return controller;
+}
+
+
+
+
+CEventListener::CEventListener()
+{
+	m_registered = false;
+	m_ref_count = 0;
+}
+
+
+
+
+CEventListener::~CEventListener()
+{
+	CTracker* p_tracker = CTracker::Instance();
+	p_tracker->UnregisterEventListener(this);
+	Dbg_MsgAssert(m_ref_count == 0, ("Event listener still being referenced"));
+}
+
+
+
+
+/*
+	The flags are for optimization purposes. Don't allow events that aren't
+	targeted to the attached object if it isn't necessary -- this will 
+	speed things up.
+	
+	pObject = NULL means no associated CObject, which is legal, but you must
+	set mALL_ALL_EVENTS
+*/
+void CEventListener::RegisterWithTracker(CObject *pObject)
+{
+	mp_object = pObject;
+
+	CTracker* p_tracker = CTracker::Instance();
+	p_tracker->RegisterEventListener(this);
+}
+
+
+
+
+/*
+	Called by CTracker singleton on every event listener that is registered,
+	whenever CTracker receives an event.
+*/
+void CEventListener::event_filter(CEvent *pEvent)
+{	
+	if (mp_object)
+	{
+		#ifdef __NOPT_ASSERT__
+//		if (mp_object->GetFlags() & Front::CScreenElement::vIS_SCREEN_ELEMENT)
+//			((Front::CScreenElement *) mp_object.Convert())->debug_verify_integrity();
+		#endif
+	}
+	
+	// TRICKY DELETE
+	// Can in theory lead to the destruction of any CObject or any listener.
+	// Will assert if this listener gets destroyed
+	pass_event_to_listener(pEvent);
+	
+	if (mp_object)
+	{
+		#ifdef __NOPT_ASSERT__
+//		if (mp_object->GetFlags() & Front::CScreenElement::vIS_SCREEN_ELEMENT)
+//			((Front::CScreenElement *) mp_object.Convert())->debug_verify_integrity();
+		#endif
+	}
+}
+
+
+
+
+CEventHandlerTable::CEventHandlerTable()
+{
+	m_num_entries = 0; 
+	mp_tab = NULL;
+	m_valid = true;
+	m_in_immediate_use_counter = 0;
+	m_changed = false;
+}
+
+
+
+
+CEventHandlerTable::~CEventHandlerTable()
+{
+	Dbg_Assert(!m_in_immediate_use_counter);
+
+	
+	if (mp_tab)
+	{
+		for (int i = 0; i < m_num_entries; i++)
+		{
+			if (mp_tab[i].p_params)
+			{
+				delete mp_tab[i].p_params;
+			}
+		}
+		delete [] mp_tab;
+	}
+}
+
+
+void CEventHandlerTable::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	if (mp_tab && m_num_entries)
+	{
+	
+		Script::CArray *p_array=new Script::CArray;
+		p_array->SetSizeAndType(m_num_entries,ESYMBOLTYPE_STRUCTURE);
+		for (int i=0; iAddChecksum(CRCD(0x7321a8d6,"Type"), mp_tab[i].type);		
+				p_ex->AddChecksum(CRCD(0xd1e70f97,"Event_Script"), mp_tab[i].script);		
+				p_ex->AddChecksum(CRCD(0x923fbb3a, "Group"), mp_tab[i].group);
+				p_array->SetStructure(i,p_ex);
+			}
+			else
+			{
+				Script::CStruct *p_ex = new Script::CStruct;
+				p_ex->AddChecksum(NO_NAME, CRCD(0xd3d5f556, "DeadEntry"));
+				p_array->SetStructure(i,p_ex);
+			}
+		}
+		p_info->AddArrayPointer(CRCD(0x3e55ff39,"mp_event_handler_table"),p_array);
+
+	}
+#endif				 
+}
+
+
+
+// Add a single event to the table
+// replacing any existing event handler with the same ex type
+void	CEventHandlerTable::AddEvent(uint32 ex, uint32 scr, uint32 group, bool exception, Script::CStruct *p_params)
+{
+	Entry *p_entry = NULL;	
+
+// if there is no mp_tab, then we'll need to create a single entry one 
+	if (!mp_tab)
+	{
+		m_num_entries = 1;
+		mp_tab = new Entry[1];
+		p_entry = mp_tab;
+	}
+	else
+	{
+	// otherwise see it it exists, and if so, then simply replace it
+		int i;
+
+		for (i = 0; ip_params)
+				{
+					delete p_entry->p_params;
+				}
+				goto GOT_ENTRY;				// goto useful
+			}
+		}
+		
+		// check for empty slots, and use them	
+
+		for (i = 0; ienabled = true;
+	p_entry->exception = exception;
+	p_entry->group = group;
+	if (p_params)
+	{
+//		printf ("Allocating parameters\n");
+		// if params are passed, then we need to make a copy of them
+		p_entry->p_params = new Script::CStruct(*p_params);
+	}
+	else
+	{
+		p_entry->p_params = NULL;  
+	}
+	p_entry->script = scr;
+	p_entry->type = ex;
+
+	// Need to flag it as changed, so we can break out of pass_event
+	// if an event causes a change in the table (which we are iterating over)   
+	m_changed = true;
+
+}
+
+
+void CEventHandlerTable::add_from_script(Script::CArray *pArray, bool replace)
+{
+	Dbg_Assert(pArray);
+
+	
+	int new_entries = pArray->GetSize();
+	if (replace && !new_entries)
+	{
+		// if no new entries, and we are "replacing", then delete any existing table
+		// and return 
+		if (mp_tab)
+		{
+			for (int i = 0; i < m_num_entries; i++)
+			{
+				if (mp_tab[i].p_params)
+				{
+					delete mp_tab[i].p_params;
+				}
+			}
+			delete [] mp_tab;
+			mp_tab = NULL;
+			m_num_entries = 0;
+			m_changed = true;
+		}
+		return;
+	}
+
+/*
+	// Optimization for adding a single entry
+	if (new_entries == 1)
+	{
+
+		// get the type, script pair, and any params and flags
+		Script::CStruct *pEventStruct = pArray->GetStructure(0);
+		
+		Script::CComponent *p_left = pEventStruct->GetNextComponent(NULL);
+		Dbg_MsgAssert(p_left, ("missing 'type' half of event handler pair"));
+		Dbg_Assert(p_left->mType == ESYMBOLTYPE_NAME);
+
+		Script::CComponent *p_right = pEventStruct->GetNextComponent(p_left);
+		Dbg_MsgAssert(p_right, ("missing 'handler' half of event handler pair"));
+		Dbg_Assert(p_right->mType == ESYMBOLTYPE_NAME);
+
+		uint32	type = p_left->mChecksum;
+
+		Entry *p_entry = NULL;
+
+
+		// if it's a "replace" entry, then we scan through to see if we already have an entry
+		// then we delete it, and can use that slot
+		if (replace)
+		{
+			for (int i = 0; i < m_num_entries; i++)
+			{
+				if (mp_tab[i].type == type)
+				{
+					mp_tab[i].script = vDEAD_ENTRY;
+					// delete the original parameters, whilst (while!) we are at it
+					if (mp_tab[i].p_params)
+					{
+						delete mp_tab[i].p_params;
+						mp_tab[i].p_params = NULL;
+					}
+					p_entry = &mp_tab[i];
+					break;
+				}
+			}
+		}
+		
+		// if not got one via replace, we need to look for an empty slot
+		// 95% of the time there should be one, as the table
+		// will expand to the max size required, and will not shrink
+		// so we only need to add slots during expansion.
+
+		for (int i = 0; i < m_num_entries; i++)
+		{
+			if (mp_tab[i].script == vDEAD_ENTRY)
+			{
+				p_entry = &mp_tab[i];
+				break;
+			}
+		}
+
+		if (p_entry)
+		{
+
+			// DANG!  This is a chunk of cut and paste code from below
+			// this event handler container is in serious need
+			// of refactoring
+			
+			// Mick, if the event handler has a "params" structure
+			// then add it to the entry.
+			// (note, have to be careful in cleaning these up)
+			Script::CStruct *p_params;
+			if (pEventStruct->GetStructure(CRCD(0x7031f10c,"params"),&p_params))
+			{
+				printf ("Allocating Params\n");
+				p_entry->p_params = new Script::CStruct(*p_params);
+			}
+			else
+			{
+				p_entry->p_params = NULL;
+			}
+			
+			if (!pEventStruct->GetChecksum(CRCD(0x923fbb3a, "Group"), &p_entry->group))
+			{
+				p_entry->group = vDEFAULT_GROUP;
+			}
+	
+			p_entry->exception = pEventStruct->ContainsFlag(CRCD(0x80367192,"Exception"));
+			
+			p_entry->type = p_left->mChecksum;
+			p_entry->script = p_right->mChecksum;
+			Dbg_MsgAssert(p_entry->script,("Adding Null script in event handler"));
+			p_entry->enabled = true;
+			m_changed = true;
+			return;
+		}
+	}
+
+*/
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+	// create new table	(this is the one with problems with p_params)
+	// the values are not initialized
+	// so if it's allocated over an old one, then
+	// errors might occur
+	
+//	printf("Allocating memory for %d new entries\n",new_entries);
+	
+	Entry *p_edit_tab = new Entry[m_num_entries + new_entries];
+	#ifdef __NOPT_ASSERT__ 
+	int first_edit_entry = m_num_entries;	
+	#endif
+
+
+	int	new_entry_index = 0;
+
+	// transfer the new entries from script array into new table
+	for (int i = 0; i < new_entries; i++)
+	{
+		// get the type, script pair, and any params and flags
+		Script::CStruct *pEventStruct = pArray->GetStructure(i);
+		
+		Script::CComponent *p_left = pEventStruct->GetNextComponent(NULL);
+		Dbg_MsgAssert(p_left, ("missing 'type' half of event handler pair"));
+		Dbg_Assert(p_left->mType == ESYMBOLTYPE_NAME);
+
+		Script::CComponent *p_right = pEventStruct->GetNextComponent(p_left);
+		Dbg_MsgAssert(p_right, ("missing 'handler' half of event handler pair"));
+		Dbg_Assert(p_right->mType == ESYMBOLTYPE_NAME);
+
+		if (replace)
+		{
+			// remove entry from old table, if it exists in there
+			remove_entry(p_left->mChecksum);
+		}
+		
+		Entry *p_entry = p_edit_tab + new_entry_index++;
+
+		// Mick, if the event handler has a "params" structure
+		// then add it to the entry.
+		// (note, have to be careful in cleaning these up)
+		Script::CStruct *p_params;
+		if (pEventStruct->GetStructure(CRCD(0x7031f10c,"params"),&p_params))
+		{
+			Dbg_MsgAssert( (i + first_edit_entry) < (m_num_entries + new_entries),
+						   ( "Array overflow" ) );
+			
+			p_entry->p_params = new Script::CStruct(*p_params);
+		}
+		else
+		{
+			p_entry->p_params = NULL;
+		}
+		
+		if (!pEventStruct->GetChecksum(CRCD(0x923fbb3a, "Group"), &p_entry->group))
+		{
+			p_entry->group = vDEFAULT_GROUP;
+		}
+
+		p_entry->exception = pEventStruct->ContainsFlag(CRCD(0x80367192,"Exception"));
+		
+		p_entry->type = p_left->mChecksum;
+		p_entry->script = p_right->mChecksum;
+		Dbg_MsgAssert(p_entry->script,("Adding Null script in event handler"));
+		p_entry->enabled = true;
+		m_changed = true;
+	}
+	
+	// if member table already exists, copy it into new one, at the end
+	// (append to items already added)
+	// Note that some entries in the original table might have been deleted, so
+	// the size of mp_tab might end up being bigger than that indicated by m_num_entries
+	// but the extra at the end is just garbage.
+	// This is most common when you Set individual events on
+	// and already existing table that contains that event
+	// (old code would have left an uninitialized gap, causing very obscure bugs)
+
+	if (mp_tab)
+	{
+		for (int i = 0; i < m_num_entries; i++)
+		{
+			p_edit_tab[new_entry_index++] = mp_tab[i];
+		}
+	}
+	delete [] mp_tab;  					// old table has been coped over, so we can delete it
+	mp_tab = p_edit_tab;				// and make the newly constructed table the active table
+	m_num_entries = new_entry_index;	// set the number of entries to the actual counted entries (not the size of the array)
+	
+	Mem::Manager::sHandle().PopContext();
+	
+}
+
+
+
+
+// doesn't change the array size, just marks entry dead (and deletes p_params struct)
+void CEventHandlerTable::remove_entry(uint32 type)
+{
+	for (int i = 0; i < m_num_entries; i++)
+	{
+		if (mp_tab[i].type == type)
+		{
+			mp_tab[i].type = 0;
+			mp_tab[i].script = vDEAD_ENTRY;
+			// delete the original parameters, whilst (while!) we are at it
+			if (mp_tab[i].p_params)
+			{
+				delete mp_tab[i].p_params;
+				mp_tab[i].p_params = NULL;
+			}
+		}
+	}
+	
+	// Note, we don't set m_changed here, as the table has not really changed
+	// just one entry has changed
+	// we will set the m_changed flag if compress_table later removed this entry
+	
+}
+
+
+// removes all entries with the given group id
+// or remove them all if "all_groups" is specifed
+void CEventHandlerTable::remove_group(uint32 group)
+{
+	for (int i = 0; i < m_num_entries; i++)
+	{
+		if (mp_tab[i].group == group || group == CRCD(0x8b713e0e,"all_groups"))
+		{
+			mp_tab[i].script = vDEAD_ENTRY;
+			if (mp_tab[i].p_params)
+			{
+				delete mp_tab[i].p_params;
+				mp_tab[i].p_params = NULL;
+			}
+		}
+	}
+}
+
+
+
+
+// changes size of table, removing dead entries
+void CEventHandlerTable::compress_table()
+{
+	if (!mp_tab) return;
+
+	// count up dead entries
+	int num_dead = 0;
+	int in = 0;
+	for (; in < m_num_entries; in++)
+	{
+		if (mp_tab[in].script == vDEAD_ENTRY)
+			num_dead++;
+	}
+
+	if (num_dead == 0) return;
+
+	int new_size = m_num_entries - num_dead;
+
+	m_changed = true;
+
+	// Mick - If new table has zero size, then don't allocate it	
+	// just delete the old table, and set it to NULL
+	if ( 0 == new_size)
+	{
+		for (int i = 0; i < m_num_entries; i++)
+		{
+			// we're about to remove an entry
+			// so delete its params if necessary
+			if ( mp_tab[i].p_params )
+			{
+				delete mp_tab[i].p_params;
+				mp_tab[i].p_params = NULL;
+			}
+		}
+
+		delete[]	mp_tab;
+		mp_tab = NULL;
+		m_num_entries = 0;
+	}
+	else
+	{
+		
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		Entry *p_new_tab = new Entry[new_size];		
+		Mem::Manager::sHandle().PopContext();
+		
+		int out = 0;
+		for (in = 0; in < m_num_entries; in++)
+		{
+			if (mp_tab[in].script != vDEAD_ENTRY)
+			{
+				p_new_tab[out++] = mp_tab[in];
+			}
+			else
+			{
+				// we're about to remove an entry
+				// so delete its params if necessary
+				if ( mp_tab[in].p_params )
+				{
+					delete mp_tab[in].p_params;
+					mp_tab[in].p_params = NULL;
+				}
+			}
+		}
+		delete[] mp_tab;
+		mp_tab = p_new_tab;
+		m_num_entries = new_size;
+	}
+}
+
+
+
+
+void CEventHandlerTable::set_event_enable(uint32 type, bool state)
+{
+	for (int i = 0; i < m_num_entries; i++)
+	{
+		if (mp_tab[i].type == type)
+			mp_tab[i].enabled = state;
+	}
+}
+
+
+
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+void CEventHandlerTable::pass_event(CEvent *pEvent, Script::CScript *pScript, bool broadcast)
+{
+	// if it's a screen element, check that events aren't blocked
+#ifdef __NOPT_ASSERT__
+//	if ( ( pObject->GetFlags() & Front::CScreenElement::vIS_SCREEN_ELEMENT ) && 
+//		 ( pObject->GetFlags() & Front::CScreenElement::vEVENTS_BLOCKED ) )
+//	{
+//		return;
+//	}
+#endif		// __NOPT_ASSERT__
+
+
+
+	#if 1
+	bool	old_m_changed = m_changed;
+	m_changed = false;		
+	#else	 
+	// only clear the m_changed flag if we are not recursing into here
+	if (!m_in_immediate_use_counter)
+	{	 	
+			m_changed = false;		
+	}
+	#endif
+	
+	m_in_immediate_use_counter++;
+
+#ifndef __PLAT_WN32__
+	// Need to assert that mp_tab is valid (or we have no entries in it)
+	Dbg_MsgAssert(!m_num_entries || Mem::Valid(mp_tab),("Invalid event handler table for Event %s", Script::FindChecksumName(pEvent->GetType())));
+#endif
+
+	
+	Entry *p_entry = mp_tab;
+	for (int i = 0; i < m_num_entries; i++)
+	{  
+		if (p_entry->type == pEvent->GetType() && p_entry->script != vDEAD_ENTRY && p_entry->enabled)
+		{
+			
+			Script::CScript *p_new_script = NULL;
+			Script::CStruct	*p_params = NULL;
+			Script::CStruct	*p_passed_params = NULL;
+			
+			
+			
+	//		printf ("%s calls %s\n",Script::FindChecksumName(p_entry->type),Script::FindChecksumName(p_entry->script));
+			
+			if (pEvent->GetData())
+			{
+				// there is event data, so copy it into a new structure
+				p_params = new Script::CStruct();
+				// and then merge the parameters in with it
+				if (p_entry->p_params)
+				{
+					*p_params += *(p_entry->p_params);
+				}
+				*p_params += *(pEvent->GetData());
+				p_passed_params = p_params;
+				// using p_params like this is safe, since SpawnScript makes its own copy
+			}
+			else
+			{
+				p_passed_params = p_entry->p_params;
+			}			
+
+			if (p_entry->exception)
+			{
+				#ifdef __NOPT_ASSERT__
+				if (DebugSkaterScripts && (pEvent->GetTarget() == 0 || pEvent->GetSource()))
+				{
+					printf("%d: Exception %s, Script %s, Target %s, Source %s\n",
+						(int)Tmr::GetRenderFrame(),
+						Script::FindChecksumName(pEvent->GetType()),
+						Script::FindChecksumName(p_entry->script),
+						pEvent->GetTarget() ? Script::FindChecksumName(pEvent->GetTarget()) : "Skater",
+						pEvent->GetSource() ? Script::FindChecksumName(pEvent->GetSource()) : "Skater"
+					);
+				}
+				#endif
+				
+				
+				// If it's an exception, we check to see if the object
+				// has flagged an "OnExceptionRun" 
+
+				if (pScript->GetOnExceptionScriptChecksum())
+				{
+					// Pass in the name of the exception so that certain exceptions can be ignored.
+					Script::CStruct *pFoo=new Script::CStruct;
+					pFoo->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int)p_entry->exception);
+					// Script::RunScript(pScript->GetOnExceptionScriptChecksum(), pFoo, pScript->mpObject );
+					// Dan: interrupt instead of run
+					uint32 checksum = pScript->GetOnExceptionScriptChecksum();
+					pScript->SetOnExceptionScriptChecksum(0);	// clear it once it has been run
+					pScript->Interrupt(checksum, pFoo);
+					delete pFoo;
+				}
+				
+				// the OnException script may alter the table
+				if (p_entry->script != vDEAD_ENTRY)
+				{
+					// Exceptions act like a GOTO, so we just set the script we are running on to this new script
+					// the object reamins the same
+					pScript->SetScript(p_entry->script,p_passed_params,pScript->mpObject);
+					pScript->Update();
+				}
+			}
+			else
+			{
+				// Current Screen element code relies on spawned scripts
+				// as they don't update their own scripts
+				// but we are moving to an "Interrupt" model, so we
+				// only support this "Spawned" model for the screen elements
+				
+				if (pScript->mpObject &&  pScript->mpObject->GetFlags() & Front::CScreenElement::vIS_SCREEN_ELEMENT)
+				{
+					// Normal events spawn a new script running on the same object as the current script (if any)
+					p_new_script = Script::SpawnScript(p_entry->script, p_passed_params, 0, NULL);
+					#ifdef __NOPT_ASSERT__
+					p_new_script->SetCommentString("Spawned by CEventHandlerTable::pass_event, 1");
+					#endif
+					p_new_script->mpObject = pScript->mpObject;	   
+					p_new_script->Update(); 
+				}
+				else
+				{
+					// Instead of spawning, just interrupt the current script
+					pScript->Interrupt(p_entry->script, p_passed_params);					
+				}				
+			}
+
+			if (p_params)
+			{
+				delete p_params;
+			}
+			
+			Dbg_MsgAssert((*(uint32*)this) != 0x01010101,("%s\nCEventHandlerTable deleted whilst being used",p_new_script->GetScriptInfo()));
+			//p_new_script->mpObject = NULL;
+
+			// do logging
+			//pEvent->MarkHandled(pObject->GetID(), p_entry->script);
+			pEvent->MarkHandled(0, p_entry->script);	// receiver id not important
+		}
+
+		p_entry++;
+
+		if (!m_valid)
+		{
+			// Looks like the spawned script deleted the CObject, invalidating this event handler table.
+			// Exit now. If the outermost pass_event() in a recursive chain, then kill self.
+			m_in_immediate_use_counter--;
+			if (!m_in_immediate_use_counter)
+				delete this;
+			return;
+		}
+		
+		if (m_changed)
+		{
+			break;
+		}
+		
+	}
+
+	// if we were previously flagged as changed, or some other function flagged us as changed
+	// then say we have changed	
+	m_changed = m_changed || old_m_changed;
+
+	m_in_immediate_use_counter--;
+}
+
+#else
+
+void CEventHandlerTable::pass_event(CEvent *pEvent, CObject *pObject, bool broadcast)
+{
+	// if it's a screen element, check that events aren't blocked
+#ifdef __NOPT_ASSERT__
+	if ( ( pObject->GetFlags() & Front::CScreenElement::vIS_SCREEN_ELEMENT ) )
+	{
+		Front::CScreenElement* pScreenElement = (Front::CScreenElement*)pObject;
+		if ( pScreenElement->EventsBlocked() )
+		{
+			return;
+		}		
+	}
+#endif		// __NOPT_ASSERT__
+
+	// only clear the m_changed flag if we are not recursing into here
+	if (!m_in_immediate_use_counter)
+	{	 	
+			m_changed = false;		
+	}
+	
+	m_in_immediate_use_counter++;
+	
+	Entry *p_entry = mp_tab;
+	for (int i = 0; i < m_num_entries; i++)
+	{  
+		 
+//		if (broadcast && !p_entry->broadcast)
+//		{
+//			p_entry++;
+//			continue;
+//		}		
+	
+		if (p_entry->type == pEvent->GetType() && p_entry->script != vDEAD_ENTRY && p_entry->enabled)
+		{
+			
+			Script::CScript *p_new_script = NULL;
+			Script::CStruct	*p_params = NULL;
+			Script::CStruct	*p_passed_params = NULL;
+			
+			// visual debugging, if there is a source and target, and they are different
+			// then draw a line between the two objects
+			#ifdef	__NOPT_ASSERT__
+			if (Script::GetInteger(CRCD(0xffc8c5f8,"Display_event_arrows")))
+			{
+				if (pObject->GetFlags() & Obj::CObject::vCOMPOSITE)
+				{
+					uint32		source_id = pEvent->GetSource();
+	//				printf ("Source = 0x%x  %s,  target = 0x%x, %s\n",source_id, Script::FindChecksumName(source_id),pObject->GetID(),Script::FindChecksumName(pObject->GetID()));
+					if ( source_id != pObject->GetID() )
+					{
+						Obj::CObject * p_source_object = Obj::CTracker::Instance()->GetObject(source_id);
+						if (p_source_object)
+						{
+	//						printf ("objects types %d, %d\n",pObject->GetType(), p_source_object->GetType());
+							// and we only want to do it if they are composite object
+							if (pObject->GetFlags() & Obj::CObject::vCOMPOSITE && p_source_object->GetFlags() & Obj::CObject::vCOMPOSITE)	// eek!
+							{	
+								Gfx::AddDebugArrow(((CCompositeObject*)p_source_object)->GetPos(),((CCompositeObject*)pObject)->GetPos(),0xff00ff,0xff00ff,200);
+							}
+						}
+						else
+						{
+							printf ("No Source object found\n");
+						}
+					}
+				}			
+			}
+			
+			if (DebugSkaterScripts && (pObject->GetID() == 0 || pEvent->GetSource() == 0))
+			{
+				printf("%d: Exception %s, Script %s, Target %s, Source %s\n",
+					(int)Tmr::GetRenderFrame(),
+					Script::FindChecksumName(pEvent->GetType()),
+					Script::FindChecksumName(p_entry->script),
+					pEvent->GetTarget() ? Script::FindChecksumName(pEvent->GetTarget()) : "Skater",
+					pEvent->GetSource() ? Script::FindChecksumName(pEvent->GetSource()) : "Skater"
+				);
+			}
+			
+			#endif
+			
+			
+			if (pEvent->GetData())
+			{
+				// there is event data, so copy it into a new structure
+				p_params = new Script::CStruct();
+				// and then merge the parameters in with it
+				if (p_entry->p_params)
+				{
+					*p_params += *(p_entry->p_params);
+				}
+				*p_params += *(pEvent->GetData());
+				p_passed_params = p_params;
+				// using p_params like this is safe, since SpawnScript makes its own copy
+			}
+			else
+			{
+				p_passed_params = p_entry->p_params;
+			}			
+
+			if (p_entry->exception)
+			{
+				// If it's an exception, we check to see if the object
+				// has flagged an "OnExceptionRun" 
+
+
+				// Ooh, we need to run a script, so better make sure mp_script points to something.
+				if (pObject->GetScript()==NULL)
+				{
+					pObject->SetScript( new Script::CScript);
+					#ifdef __NOPT_ASSERT__
+					pObject->GetScript()->SetCommentString("Created in CEventHandlerTable::pass_event(...) (exception)");
+					#endif
+				}	
+				// Now we're safe. Note: mp_script will get cleaned up by the CObject destructor.
+
+				
+				if (pObject->GetOnExceptionScriptChecksum())
+				{
+//					printf ("Running onException script %s\n",Script::FindChecksumName(pObject->GetOnExceptionScriptChecksum()));
+					// Pass in the name of the exception so that certain exceptions can be ignored.
+					Script::CStruct *pFoo=new Script::CStruct;
+					pFoo->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int)p_entry->exception);
+					Script::RunScript(pObject->GetOnExceptionScriptChecksum(), pFoo, pObject );
+					delete pFoo;
+					pObject->SetOnExceptionScriptChecksum(0);	// clear it once it has been run
+				}
+			
+				
+				// An exception will act like a "goto"
+				Script::CScript *p_old_script = pObject->GetScript();
+//				printf("RUNNING exception %s script %s on object %s\n",Script::FindChecksumName(p_entry->type),Script::FindChecksumName(p_entry->script),Script::FindChecksumName(pObject->GetID()));
+				p_old_script->SetScript(p_entry->script,p_passed_params,pObject);
+				p_old_script->Update();
+			}
+			else
+			{
+				// Normal events spawn a script running on an object
+//				printf("SPAWNING exception %s script %s on object %s\n",Script::FindChecksumName(p_entry->type),Script::FindChecksumName(p_entry->script),Script::FindChecksumName(pObject->GetID()));
+				p_new_script = Script::SpawnScript(p_entry->script, p_passed_params, 0, NULL);
+				#ifdef __NOPT_ASSERT__
+				p_new_script->SetCommentString("Spawned by CEventHandlerTable::pass_event, 1");
+				#endif
+				p_new_script->mpObject = pObject;			   
+				p_new_script->Update();
+			}
+
+			if (p_params)
+			{
+				delete p_params;
+			}
+			
+			Dbg_MsgAssert((*(uint32*)this) != 0x01010101,("%s\nCEventHandlerTable deleted whilst being used",p_new_script->GetScriptInfo()));
+			//p_new_script->mpObject = NULL;
+
+			// do logging
+			pEvent->MarkHandled(pObject->GetID(), p_entry->script);
+		}
+
+		p_entry++;
+
+		if (!m_valid)
+		{
+			// Looks like the spawned script deleted the CObject, invalidating this event handler table.
+			// Exit now. If the outermost pass_event() in a recursive chain, then kill self.
+			m_in_immediate_use_counter--;
+			if (!m_in_immediate_use_counter)
+				delete this;
+			return;
+		}
+		
+		if (m_changed)
+		{
+			break;
+		}
+		
+	}
+
+	m_in_immediate_use_counter--;
+}
+#endif
+
+
+
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+// Register all the entries in this even table with the event receiver table in the tracker
+// (Hey, maybe store the script in here, allow multiple entries and do away with this table?)
+void	CEventHandlerTable::register_all(Script::CScript *p_script)
+{
+	Entry *p_entry = mp_tab;
+	for (int i = 0; i < m_num_entries; i++)
+	{
+		// if it's a live entry, then add it as a receiver
+		if (p_entry->type && p_entry->script && p_entry->script != vDEAD_ENTRY)
+		{
+			Obj::CTracker::Instance()->RegisterEventReceiver(p_entry->type, p_script);
+		}
+		p_entry++;
+	}  
+}
+
+// Flush out all references from the Event Receiver table to this object, based on the event handler table
+void	CEventHandlerTable::unregister_all(Script::CScript *p_script)
+{
+	Entry *p_entry = mp_tab;
+	for (int i = 0; i < m_num_entries; i++)
+	{
+		if (p_entry->type)
+		{
+			Obj::CTracker::Instance()->UnregisterEventReceiver(p_entry->type, p_script);
+		}
+		p_entry++;
+	}  
+}
+
+#else
+
+// Register all the entries in this even table with the event receiver table in the tracker
+// (Hey, maybe store the script in here, allow multiple entries and do away with this table?)
+void	CEventHandlerTable::register_all(CObject *p_object)
+{
+	Entry *p_entry = mp_tab;
+	for (int i = 0; i < m_num_entries; i++)
+	{
+		if (p_entry->type && p_entry->script)
+		{
+			Obj::CTracker::Instance()->RegisterEventReceiver(p_entry->type, p_object);
+		}
+		p_entry++;
+	}  
+}
+
+// Flush out all references from the Event Receiver table to this object, based on the event handler table
+void	CEventHandlerTable::unregister_all(CObject *p_object)
+{
+	Entry *p_entry = mp_tab;
+	for (int i = 0; i < m_num_entries; i++)
+	{
+		if (p_entry->type)
+		{
+			Obj::CTracker::Instance()->UnregisterEventReceiver(p_entry->type, p_object);
+		}
+		p_entry++;
+	}  
+}
+
+#endif
+
+// Dump the given table
+void	CEventHandlerTable::sPrintTable ( CEventHandlerTable* table )
+{
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+	printf("====================================\n");
+	printf("Event Handler Table:\n");
+	if (table)
+	{
+		for (int i = 0; i < table->m_num_entries; i++)
+		{
+			Entry* p_entry = table->mp_tab + i;
+			
+			printf("------------------------------------\n");
+			printf("  Type     : %s\n", Script::FindChecksumName(p_entry->type));
+			printf("  Script   : %s\n", Script::FindChecksumName(p_entry->script));
+			printf("  Group    : %s\n", Script::FindChecksumName(p_entry->group));
+			printf("  Exception: %s\n", p_entry->exception ? "yes" : "no");
+		}
+	}
+	else
+	{
+		printf("  Emtpy\n");
+	}
+	printf("====================================\n");
+#endif		// __NOPT_FINAL__
+}
+
+}
diff --git a/Code/Gel/Object/ObjPtr.cpp b/Code/Gel/Object/ObjPtr.cpp
new file mode 100644
index 0000000..36404f0
--- /dev/null
+++ b/Code/Gel/Object/ObjPtr.cpp
@@ -0,0 +1,11 @@
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+
+
+
+}
diff --git a/Code/Gel/Object/RefCounted.cpp b/Code/Gel/Object/RefCounted.cpp
new file mode 100644
index 0000000..fbaf886
--- /dev/null
+++ b/Code/Gel/Object/RefCounted.cpp
@@ -0,0 +1,105 @@
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+
+
+
+CRefCounted::CRefCounted()
+{
+	mp_smart_ptr_list = NULL;
+}
+
+
+
+
+CRefCounted::~CRefCounted()
+{
+	CSmtPtr *p_smt = mp_smart_ptr_list;
+	while(p_smt)
+	{
+		CSmtPtr *p_next = p_smt->mp_next_ptr;
+		Dbg_Assert(p_smt->mp_object);
+		Dbg_Assert(p_smt->mp_object == this);
+		*p_smt = NULL;
+		p_smt = p_next;
+	}
+}
+
+
+
+
+void CRefCounted::AddSmartPointer(CSmtPtr *pSmtPtr)
+{
+	//debug_validate_smart_pointers();
+	
+	Dbg_Assert(!pSmtPtr->mp_prev_ptr);
+	Dbg_Assert(!pSmtPtr->mp_next_ptr);
+	pSmtPtr->mp_next_ptr = mp_smart_ptr_list;
+	if (mp_smart_ptr_list)
+		mp_smart_ptr_list->mp_prev_ptr = pSmtPtr;
+	mp_smart_ptr_list = pSmtPtr;
+
+	//debug_validate_smart_pointers();
+}
+
+
+
+
+void CRefCounted::RemoveSmartPointer(CSmtPtr *pSmtPtr)
+{
+	Dbg_Assert(pSmtPtr->mp_object == this);
+	//debug_validate_smart_pointers(pSmtPtr);
+
+	if (pSmtPtr->mp_prev_ptr)
+	{
+		Dbg_Assert(pSmtPtr->mp_prev_ptr->mp_next_ptr == pSmtPtr);
+		pSmtPtr->mp_prev_ptr->mp_next_ptr = pSmtPtr->mp_next_ptr;
+	}
+	else
+		mp_smart_ptr_list = pSmtPtr->mp_next_ptr;
+
+	if (pSmtPtr->mp_next_ptr)
+	{
+		Dbg_Assert(pSmtPtr->mp_next_ptr->mp_prev_ptr == pSmtPtr);
+		pSmtPtr->mp_next_ptr->mp_prev_ptr = pSmtPtr->mp_prev_ptr;
+	}
+
+	pSmtPtr->mp_prev_ptr = NULL;
+	pSmtPtr->mp_next_ptr = NULL;
+	
+	//debug_validate_smart_pointers();
+}
+
+
+
+
+void CRefCounted::debug_validate_smart_pointers(CSmtPtr *pPtrToCheckForInclusion)
+{
+	bool included = false;
+	
+	CSmtPtr *p_entry = mp_smart_ptr_list;
+	while(p_entry)
+	{
+		Dbg_Assert(p_entry->mp_object == this);
+		if (p_entry->mp_prev_ptr)
+			Dbg_Assert(p_entry->mp_prev_ptr->mp_next_ptr == p_entry);
+		if (p_entry->mp_next_ptr)
+			Dbg_Assert(p_entry->mp_next_ptr->mp_prev_ptr == p_entry);
+
+		if (pPtrToCheckForInclusion == p_entry)
+			included = true;
+
+		p_entry = p_entry->mp_next_ptr;
+	}
+
+	Dbg_MsgAssert(!pPtrToCheckForInclusion || included, ("smart pointer not in list as expected"));
+}
+
+
+
+
+}
diff --git a/Code/Gel/Object/basecomponent.cpp b/Code/Gel/Object/basecomponent.cpp
new file mode 100644
index 0000000..52f62c9
--- /dev/null
+++ b/Code/Gel/Object/basecomponent.cpp
@@ -0,0 +1,127 @@
+//****************************************************************************
+//* MODULE:         Gel/Object
+//* FILENAME:       basecomponent.cpp
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::CBaseComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent::~CBaseComponent()
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBaseComponent::Finalize()
+{
+	// Virtual function, can be overridden to provided finialization to 
+	// a component after all components have been added to an object
+}
+
+
+void CBaseComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// This is primarily for pre-existing components that we 
+	// need to update
+	//
+	// Components that are created from EmptyComponent.cpp/h should
+	// have the funtion automatically overridden
+
+	printf ("WARNING:  Refreshing component %s using InitFromStructure\n");  
+	InitFromStructure(pParams);
+}
+
+void CBaseComponent::ProcessWait( Script::CScript * pScript )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// By default, a component has no member functions
+// so attempting to call one will just return MF_NOT_EXECUTED
+CBaseComponent::EMemberFunctionResult CBaseComponent::CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript )
+{
+	return MF_NOT_EXECUTED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBaseComponent::Suspend(bool suspend)
+{
+	m_flags.Set(BC_NO_UPDATE, suspend);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBaseComponent::Hide( bool shouldHide )
+{
+	// the base component does nothing,
+	// but some components need to do extra stuff
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBaseComponent::Teleport()
+{
+	// the base component does nothing,
+	// but some components need to do extra stuff
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Used by the script debugger code to fill in a structure
+// for transmitting to the monitor.exe utility running on the PC.
+void CBaseComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CBaseComponent::GetDebugInfo"));
+	
+	#ifdef __NOPT_ASSERT__
+	p_info->AddInteger("CPUTime",m_update_time);
+	#endif
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Gel/Object/basecomponent.h b/Code/Gel/Object/basecomponent.h
new file mode 100644
index 0000000..3505caa
--- /dev/null
+++ b/Code/Gel/Object/basecomponent.h
@@ -0,0 +1,94 @@
+//****************************************************************************
+//* MODULE:         Gel/Object
+//* FILENAME:       BaseComponent.h
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#ifndef __OBJECT_BASECOMPONENT_H__
+#define __OBJECT_BASECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+namespace Script
+{
+    class CStruct;
+    class CScript;
+}
+
+namespace Obj
+{
+
+// CBaseComponent is a virtual base class for object components
+class CBaseComponent : public Spt::Class
+{
+	friend	class	CCompositeObject;
+
+	
+public:
+    CBaseComponent();
+    virtual ~CBaseComponent();
+
+	// Possible results from calling CallMemberFunction
+	// (If the order of these change, then please
+	// also change Gfx::EAnimFunctionResult)
+	enum EMemberFunctionResult
+	{
+		MF_FALSE 		= 0,
+		MF_TRUE  		= 1,
+		MF_NOT_EXECUTED = 2
+	};
+	
+	enum EBaseComponentFlags
+	{
+		BC_NO_UPDATE
+	};
+
+
+public:
+	virtual	void 					Update() = 0;
+    virtual void 					InitFromStructure( Script::CStruct* pParams ) = 0;
+	virtual	void 					Finalize();
+    virtual void 					RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					ProcessWait( Script::CScript * pScript );
+	
+	void							SetObject(CCompositeObject* p_object) {mp_object = p_object;}
+	CCompositeObject*				GetObject() const {return mp_object;}
+	CBaseComponent*					GetNext() const {return mp_next;}
+	CBaseComponent*					GetNextSameType() const {return mp_next_same_type;}
+	void							SetNextSameType( CBaseComponent *p_component ){ mp_next_same_type = p_component; }
+	uint32							GetType() const {return m_type;}
+	virtual	void					Suspend(bool suspend);
+	bool							IsSuspended() const { return m_flags.Test(BC_NO_UPDATE); }
+	virtual void					Hide(bool shouldHide);
+	virtual void					Teleport();
+
+	// Used by the script debugger code to fill in a structure
+	// for transmitting to the monitor.exe utility running on the PC.
+	virtual void					GetDebugInfo(Script::CStruct *p_info);
+
+public:
+	virtual EMemberFunctionResult 	CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript );
+	
+protected:
+	void							SetType(uint32 type) {m_type = type;}
+
+private:
+	uint32							m_type;	   			// Unique ID of the component, stuck here during constuction
+	Flags		m_flags;
+	CCompositeObject	* 			mp_object; 			// Parent object that contains this component
+	CBaseComponent * 				mp_next;			// next component in the list
+	CBaseComponent * 				mp_next_same_type;	// next component in the list that is of the same type
+
+	#ifdef __NOPT_ASSERT__
+	// The time spent (in microseconds) executing ::Update(), for displaying in the script debugger.
+	int								m_update_time;
+	#endif	
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Object/compositeobject.cpp b/Code/Gel/Object/compositeobject.cpp
new file mode 100644
index 0000000..2fc871c
--- /dev/null
+++ b/Code/Gel/Object/compositeobject.cpp
@@ -0,0 +1,1096 @@
+//****************************************************************************
+//* MODULE:         Gel/Object
+//* FILENAME:       compositeobject.cpp
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+/***************************************************************************
+ A composite object is a generic object which basically consists of:
+ - a position and orientation
+ - some flags
+ - a script
+ - a list of components
+ 
+ The purpose of the Composite Object is to represent a thing that exists 
+ in the game world.  For example:
+ 
+  - Cars and other vechicals
+  - Pedestrians, animals, etc
+  - Pickup Icons
+  - 3D sound emitters
+  - Particle systems
+  - Skaters or other player type objects
+  - Bouncy Objects like trash cans
+
+ CCompositeObject is derived from CObject, so all CCompositeObjects have
+ a mp_script (which might be NULL)
+ 
+ A composite object is normally comprised of several components.  These are 
+ generally independent things such as:
+  - A Model	componet
+  - A physics component (e.g., drive around, or skating)
+  - A suspend component
+  - and various other components as needed
+  
+ There is one global list of all composite objects.  This is managed by the 
+ CCompositeObjectManager class, whcih is also responsible for creating
+ the composite objects.
+ 
+ A composite object is typically create from an array of structs
+ and a structe that contains the initialization parameters.
+ 
+ Here's an example of the array of structs.  Note that some components
+ have default parameters.  These can be overridden by the struct of parameters
+ 
+ gameobj_composite_structure = [
+    { component = suspend }
+    { component = model  }
+    { component = exception }
+    { component = collision }
+ ]
+
+****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  // Needed by GetDebugInfo
+#include 
+
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCompositeObject::CCompositeObject()
+{
+    // set position and orientation to a known state (at the origin, identity matrix)	
+	m_vel.Set( 0.0f, 0.0f, 0.0f );
+	m_pos.Set( 0.0f, 0.0f, 0.0f );
+	m_matrix.Ident();
+	m_display_matrix.Ident();
+
+	mp_component_list = NULL;
+	m_composite_object_flags.ClearAll();
+
+	SetFlags( GetFlags() | vCOMPOSITE);   // Kind of a temp solution for now
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCompositeObject::~CCompositeObject()
+{   
+	while ( mp_component_list )
+    {
+		// Get the component at the head of the list		
+		CBaseComponent* pComponent = mp_component_list;
+		
+		// advance the list past this component, effectivly isolating it
+		// (unless someone else is storing a pointer to it)
+		mp_component_list =  pComponent->mp_next;
+		
+		// remove the component from the by-type list of components maintained by the CompositeObjectManager
+		Obj::CCompositeObjectManager::Instance()->RemoveComponentByType( pComponent );
+
+		// delete the isolated component
+		delete pComponent;   
+		     
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+#ifdef __NOPT_ASSERT__
+// Runs through all the components and zeroes their update times.
+void CCompositeObject::zero_component_update_times()
+{
+	CBaseComponent* pComponent = mp_component_list;
+    while ( pComponent )
+    {
+		pComponent->m_update_time=0;
+        pComponent = pComponent->mp_next;
+    }
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompositeObject::Update()
+{
+#ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PushContext( m_profile_color );
+#endif // __USE_PROFILER__
+
+	Dbg_MsgAssert(IsFinalized(),("Update called on UnFinalized Composite object %s",Script::FindChecksumName(GetID())));
+
+#ifdef	__NOPT_ASSERT__
+	// GJ:  don't do this if the composite object is paused,
+	// because some lock obj component might be trying
+	// to lock to this object while the game is paused
+	if (!m_composite_object_flags.Test(CO_SUSPENDED)
+		&& !m_composite_object_flags.Test(CO_PAUSED))
+	{
+		// clear out the display matrix, to make 
+		// sure that no one's relying on the
+		// last frame's matrix                                                                                               
+		m_display_matrix.Ident();
+	}
+#endif
+	
+#ifdef __NOPT_ASSERT__
+	// Make sure the component update times are initialized to zero so that they do not
+	// show incorrect old values in the debugger if this function returns before
+	// getting to the component update loop.
+	zero_component_update_times();
+	
+	m_total_script_update_time=0;
+	m_do_game_logic_time=0;
+#endif
+
+	if (m_composite_object_flags.Test(CO_PAUSED))
+	{
+#ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PopContext();
+#endif // __USE_PROFILER__
+		return;
+	}
+
+
+	if (!m_composite_object_flags.Test(CO_SUSPENDED))
+	{
+#ifdef __NOPT_ASSERT__
+		Tmr::CPUCycles time_before=Tmr::GetTimeInCPUCycles();
+#endif
+		if ( mp_script )
+		{
+			if ( mp_script->Update() == Script::ESCRIPTRETURNVAL_FINISHED )
+			{
+				// if we have script based events then we only want to kill the script if
+				// it has an empty event handler table
+				// as a script can finish, but still have event handlers
+				// this kind of logic will be duplicated in various places in the code
+				// so we could do with a bit of refactoring
+				// like a mp_script->CanBeDeleted() function
+				#ifdef	__SCRIPT_EVENT_TABLE__	 
+					// in practice it seems okay to just leave the script
+					// probably not a big issue   
+				#else
+					delete mp_script;
+					mp_script = NULL;
+				#endif
+			}
+		}
+		
+		// if a script has called "Die",
+		// then don't update the components
+		if ( IsDead() )
+		{
+			#	ifdef __USE_PROFILER__
+				Sys::CPUProfiler->PopContext();
+			#	endif // __USE_PROFILER__
+			return;
+		}
+#ifdef __NOPT_ASSERT__
+		// Convert to microseconds by dividing by 150
+		m_total_script_update_time=(Tmr::GetTimeInCPUCycles()-time_before)/150;
+#endif
+
+		// transition-only function call,
+		// call each specific object's
+		// DoGameLogic() function (previously,
+		// this was called by each object's task)
+#ifdef __NOPT_ASSERT__
+		time_before=Tmr::GetTimeInCPUCycles();
+#endif
+		// Mick:  DoGameLogic is Deprecated, and only exists for a few misc objects
+		// it should eventually be removed
+		DoGameLogic();
+#ifdef __NOPT_ASSERT__
+		// Convert to microseconds by dividing by 150
+		m_do_game_logic_time=(Tmr::GetTimeInCPUCycles()-time_before)/150;
+#endif
+
+	}
+
+	CBaseComponent* pComponent = mp_component_list;
+    while ( pComponent )
+    {
+		if ( ! (pComponent->m_flags.Test(CBaseComponent::BC_NO_UPDATE)))
+		{
+			#ifdef __NOPT_ASSERT__
+			Tmr::CPUCycles time_before_components=Tmr::GetTimeInCPUCycles();
+			#endif
+			
+			pComponent->Update();
+			
+			#ifdef __NOPT_ASSERT__
+			// Convert to microseconds by dividing by 150
+			pComponent->m_update_time=(Tmr::GetTimeInCPUCycles()-time_before_components)/150;
+			#endif
+
+			// If a component update has killed the object
+			// then we don't process any more components
+			// as they might attempt to fire an event, or reference this object in some way
+			// and it won't be in the tracking system any more
+			if (IsDead())
+			{
+				break;
+			}
+			
+			
+		}
+        pComponent = pComponent->mp_next;
+    }
+	#	ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PopContext();
+	#	endif // __USE_PROFILER__
+	
+	m_composite_object_flags.Clear(CO_TELEPORTED);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCompositeObject::Hide( bool should_hide )
+{
+	if ( should_hide == m_composite_object_flags.Test(CO_HIDDEN) )
+	{
+		return;
+	}
+
+	m_composite_object_flags.Set(CO_HIDDEN, should_hide);
+		
+	// loop through all the components
+	// and call their individual Hide functions...
+	CBaseComponent* pComponent = mp_component_list;
+	while ( pComponent )
+	{
+		pComponent->Hide( should_hide );
+		pComponent = pComponent->mp_next;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Suspending a composite object tells it not to update the script
+// but the individual components take care of suspending themselves
+// (Generally handled by the BC_NO_UPDATE flag being set)	
+// "suspending" is generally soemthing done when the object goes a
+// certain distance from the camera
+void CCompositeObject::Suspend(bool suspended)
+{
+    if (suspended == m_composite_object_flags.Test(CO_SUSPENDED)) return;
+	
+	m_composite_object_flags.Set(CO_SUSPENDED, suspended);
+	
+	// suspend the individual components
+	CBaseComponent* pComponent = mp_component_list;
+	while ( pComponent )
+	{
+		pComponent->Suspend(suspended);
+		pComponent = pComponent->mp_next;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompositeObject::Teleport()
+{
+	// calls each component's Teleport() function so
+	// that things like the model, the collision, and the
+	// shadow can be updated immediately when the
+	// object has radically changed position
+	CBaseComponent* pComponent = mp_component_list;
+	while ( pComponent )
+	{
+		pComponent->Teleport();
+		pComponent = pComponent->mp_next;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompositeObject::AddComponent( CBaseComponent* pComponent )
+{
+#ifdef __NOPT_ASSERT__
+	if ( GetComponent( pComponent->GetType() ) )
+	{
+		Dbg_MsgAssert( 0, ( "Object already has a component of type '%s'", Script::FindChecksumName(pComponent->GetType()) ) );
+	}
+#endif
+
+	Dbg_MsgAssert(!IsFinalized(),("Adding Component %s to Finalized Composite object %s",Script::FindChecksumName(pComponent->GetType()),Script::FindChecksumName(GetID())));
+
+	if (!mp_component_list)
+	{
+		mp_component_list = pComponent;
+	}
+	else
+	{
+		CBaseComponent* p_tail = mp_component_list;
+		while(p_tail->mp_next)
+		{
+			p_tail = p_tail->mp_next;
+		}
+		p_tail->mp_next = pComponent;
+	}
+
+    // now that the component is "officially" associated with
+    // this object, we can set the component's object ptr
+    pComponent->SetObject( this );
+
+	// add the component to the by-type list of components maintained by the CompositeObjectManager
+	Obj::CCompositeObjectManager::Instance()->AddComponentByType( pComponent );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given a single component definition in the form:
+// {
+// 		component = ...
+//      .... other params
+// }
+//
+// Then create a component of the correct type
+// add it to this object
+// and initialize it with this structure
+//
+// Optionally supply a seperate additional p_params structure from which to initialize it
+
+void CCompositeObject::CreateComponentFromStructure(Script::CStruct *p_struct, Script::CStruct *p_params)
+{
+	uint32	component_name;
+	p_struct->GetChecksum("component",&component_name,Script::ASSERT);
+	Obj::CBaseComponent* p_component = Obj::CCompositeObjectManager::Instance()->CreateComponent(component_name);
+	AddComponent(p_component);	 // Add it first, as InitFromStructure might need to query the object
+	if (p_params)
+	{
+		// If we have additional parameters, then add then in to the parameters in the array
+		// this will override the array paramaeters, which are assumed to be defaults
+		Script::CScriptStructure *p_combinedParams=new Script::CScriptStructure;
+		*p_combinedParams += *p_struct;
+		*p_combinedParams += *p_params;
+		p_component->InitFromStructure(p_combinedParams);
+		delete p_combinedParams;
+	}
+	else
+	{
+		// No additional parameters, so just initialze from the params in the structure in the array
+		p_component->InitFromStructure(p_struct);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given an array of component definitions in the form:
+// [
+//   { component = ......
+//     ... other params .....
+//   }
+//   {
+//    ... other components
+//   }
+// ]
+// then iterate over the array, and add the components to the composite object
+//
+// Optionally supply a "p_params" structure that contains all the initialization info
+// instead of interleaving it in the array 
+													  
+void CCompositeObject::CreateComponentsFromArray(Script::CArray *p_array, Script::CStruct* p_params)
+{
+	int num_components = p_array->GetSize();
+	for (int i=0;iGetStructure(i);
+        CreateComponentFromStructure(p_struct, p_params);
+	}
+}
+
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently isfrequently the contents of a node
+// but you can pass in anything you like.	
+void CCompositeObject::InitFromStructure( Script::CStruct* pParams )
+{
+	uint32 NameChecksum;
+	if ( pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &NameChecksum ))
+	{
+		SetID(NameChecksum);
+	}
+	
+	SkateScript::GetPosition(pParams,&m_pos);
+	
+	SkateScript::GetOrientation(pParams,&m_matrix);
+	
+#	ifdef __USE_PROFILER__
+	int ProfileColor = 0x800000;			// default to blue profile color		
+	pParams->GetInteger( CRCD(0x72444899,"ProfileColor"), &ProfileColor );
+	SetProfileColor(ProfileColor);
+#	endif // __USE_PROFILER__
+
+	if (pParams->ContainsFlag(CRCD(0x23627fd7,"permanent")))
+	{
+		SetFlags( GetFlags() | vLOCKED);
+	}
+}
+
+
+// Finalize is called after all components have been added
+// and will call the virtual Finalize() function on each component
+// This is intended for any components that are depended on other components
+// but where the initialization order can't be guaranteed
+void CCompositeObject::Finalize()
+{
+	Dbg_MsgAssert(!IsFinalized(),("Finalizing Composite object %s twice",Script::FindChecksumName(GetID())));
+	CBaseComponent *p_component = mp_component_list;
+	while (p_component)
+	{
+		p_component->Finalize();
+		p_component = p_component->GetNext();
+	}
+	m_composite_object_flags.Set(CO_FINALIZED);
+
+	// now that the component is finalized,
+	// update the components that depend
+	// on the position of the object
+	Teleport();
+}
+
+
+// RefreshFromStructure is passed the same parameters as the above
+// but will use them to update 
+void CCompositeObject::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Make sure they are not trying to change the id to something else
+	#ifdef	__NOPT_ASSERT__
+	uint32 NameChecksum;
+	if ( pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &NameChecksum ))
+	{
+		Dbg_MsgAssert(GetID() == NameChecksum,("Attempting to refresh id from %s to %s\n", Script::FindChecksumName(GetID()),NameChecksum));
+	}
+	#endif
+
+	// Update the position	
+	// Note this is just cut and paste from above
+	// so if we're going to be doing more initting of the composite object
+	// from a script struct, then we might want to factor it out
+	SkateScript::GetPosition(pParams,&m_pos);
+	
+	// Now iterate over the components, and Refresh them
+	CBaseComponent* pComponent = mp_component_list;
+    while ( pComponent )
+    {
+		pComponent->RefreshFromStructure(pParams);
+        pComponent = pComponent->mp_next;
+    }
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CCompositeObject::GetComponent( uint32 id ) const
+{
+	CBaseComponent *p_component = mp_component_list;
+	while (p_component)
+	{
+		if (p_component->GetType() == id)
+		{	
+			return p_component;
+		}
+		p_component = p_component->GetNext();
+	}
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool 	CCompositeObject::CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+
+	// This should probably go in some debug component?
+	switch ( Checksum )
+	{
+		// @script | Obj_PrintDetails | This is a debug function.  Add this
+        // command to an object's script
+		// and it will print the details.  Add anything you want to the Dbg_Message( )
+		// below.
+        // @uparmopt "string" | text to print
+		case 0xc2404947: // Obj_PrintDetails
+		{
+			const char* pText;
+			if ( pParams->GetText( NONAME, &pText ) )
+			{
+				Dbg_Message( "%s", pText );
+			}
+			Dbg_Message( "Obj details:\n  pos %f %f %f\n  time %d\n  type %d",
+				m_pos[ X ], m_pos[ Y ], m_pos[ Z ], Tmr::GetTime(), m_type );
+			return true;
+		}
+		break;
+
+		// @script | CreateComponentFromStructure |
+		case 0x406998a5: // CreateComponentFromStructure
+		{
+			CreateComponentFromStructure( pParams, NULL );
+			return true;
+		}
+		break;
+
+		// @script | Obj_GetPosition |
+		case 0xe90aad2d: // Obj_GetPosition
+		{									 
+			pScript->GetParams()->AddVector( CRCD(0x7f261953,"pos"), m_pos[X], m_pos[Y], m_pos[Z] );
+			return true;
+		}
+		break;
+
+		// @script | Obj_SetPosition | Sets the world position of the object.
+		// @parmopt vector | Position | | Position to give to the object.
+		case 0xf7251a64: // Obj_SetPosition
+		{									 
+			pParams->GetVector(CRCD(0xb9d31b0a,"Position"),&m_pos);
+			return true;
+		}
+		break;
+		
+		// @script | Obj_SetOrientation |
+		// @parmopt float | x | 0.0 | The x angle, in degrees
+		// @parmopt float | y | 0.0 | The y angle, in degrees
+		// @parmopt float | z | 0.0 | The z angle, in degrees
+		// @parmopt vector | dir | (0,0,1) | Direction vector (an alternative to specifying angles)
+		// The direction vector does not need to be normalized.
+		case 0xc6f3baa5: // Obj_SetOrientation
+		{
+			Mth::Vector dir(0.0f,0.0f,1.0f);
+			if (pParams->GetVector(CRCD(0x455485ef,"dir"),&dir))
+			{
+				Mth::Matrix mat;
+				mat.Ident();
+				
+				mat[Z]=dir;
+				mat[Z].Normalize();
+				mat[X] = Mth::CrossProduct(mat[Y], mat[Z]);
+				mat[X].Normalize();
+				mat[Y] = Mth::CrossProduct(mat[Z], mat[X]);
+				mat[Y].Normalize();
+				
+				SetMatrix(mat);
+				SetDisplayMatrix(mat);
+			}
+			else
+			{
+				float p=0.0f;
+				float h=0.0f;
+				float r=0.0f;
+				pParams->GetFloat(CRCD(0x7323e97c,"x"),&p);
+				pParams->GetFloat(CRCD(0x424d9ea,"y"),&h);
+				pParams->GetFloat(CRCD(0x9d2d8850,"z"),&r);
+				p*=3.141592654f/180.0f;
+				h*=3.141592654f/180.0f;
+				r*=3.141592654f/180.0f;
+				Mth::Matrix mat(p,h,r);
+				SetMatrix(mat);
+				SetDisplayMatrix(mat);
+			}	
+			return true;
+		}	
+		break;
+
+        // @script | Obj_ForceUpdate | Does a single call to the object's Update function
+		case 0xc1bff0f3: // Obj_ForceUpdate
+		{
+			Update();
+			return true;
+		}	
+		break;
+		
+		// @script | Obj_GetVelocity |
+		case 0x11fe9f71: // Obj_GetVelocity
+		{								   
+			pScript->GetParams()->AddVector( CRCD(0x0c4c809e,"vel"), m_vel[X], m_vel[Y], m_vel[Z] );
+			return true;
+		}
+		break;
+		
+		// @script | GetSpeed |
+		case 0xc0caac4a: // GetSpeed
+		{								   
+			pScript->GetParams()->AddFloat( CRCD(0xf0d90109,"speed"), m_vel.Length() );
+			return true;
+		}
+		break;
+		
+		// @script | Hide | hides object
+		case 0x5b6634d4: // Hide
+		{
+			Hide( true );
+			return true;
+		}
+		break;
+
+		// @script | Unhide | unhides object
+		case 0xb60d1f35: // Unhide
+		{
+			Hide( false );
+			return true;
+		}
+		break;
+
+		// @script | IsHidden | checks the hide flag of object
+		case 0xb16619ae: // IsHidden
+		{
+			return m_composite_object_flags.Test(CO_HIDDEN);
+		}
+		break;
+		
+        // @script | Move |
+        // @parmopt float | x | 0.0 | x component
+        // @parmopt float | y | 0.0 | y component
+        // @parmopt float | z | 0.0 | z component
+		case 0x10c1c887: // Move	
+		{
+			float distance = 0.0f;
+			pParams->GetFloat(CRCD(0x7323e97c, "x"), &distance);
+			m_pos += distance * m_matrix[X];
+			distance = 0.0f;
+			pParams->GetFloat(CRCD(0x424d9ea, "y"), &distance);
+			m_pos += distance * m_matrix[Y];
+			distance = 0.0f;
+			pParams->GetFloat(CRCD(0x9d2d8850, "z"), &distance);
+			m_pos += distance * m_matrix[Z];
+			return true;
+		}
+		
+		case 0xce0ca665: // Suspend
+			Suspend(true);
+			return true;
+			break;
+
+		case 0xca3c59a6: // Unsuspend
+			Suspend(false);
+			return true;
+			break;
+			
+		case 0x28656d12: // Pause
+			Pause(true);
+			return true;
+			break;
+
+		case 0xff85d4d7: // Unpause
+			Pause(false);
+			return true;
+			break;
+
+// MOVED FROM CMOVINGOBJECT.CPP		
+		
+		// @script | Obj_GetCollision | Checks to see if there is a collision along a line in front
+		// of the object.
+        // @parmopt float | Height | 3.0 | Height above origin of start point in feet
+        // @parmopt float | Lnegth | 3.0 | length of ocllision line in feet
+        // @flag side | Check side collision instead of forward collision
+        // @flag debug | display green debug lines at each collision test, white if there is a collision
+		case 0x168b09c:	// Obj_GetCollision
+			{
+				CFeeler	feeler;
+				//if (pParams->ContainsFlag(0xc4e78e22/*All*/))
+				
+				float	length = 3.0f;
+				pParams->GetFloat(0xfe82614d /*length*/,&length);
+				length *= 12.0f;
+				float	height = 3.0f;
+				pParams->GetFloat(0xab21af0 /*height*/,&height);
+				height *= 12.0f;
+				
+				feeler.m_start = m_pos + m_matrix[Y] * height;;
+				feeler.m_end = feeler.m_start;
+				if (pParams->ContainsFlag(0xdc7ee44a/*side*/))
+				{
+					feeler.m_end += length * m_matrix[X];
+				}
+				else
+				{
+					feeler.m_end += length * m_matrix[Z];
+				}
+				
+				if (feeler.GetCollision())
+				{
+					#ifdef	__NOPT_ASSERT__
+					if (pParams->ContainsFlag(0x935ab858/*debug*/))
+					{
+						feeler.DebugLine(255,255,255);	 // White line = collision
+					}
+					#endif
+					return true;
+				}
+				else
+				{
+					#ifdef	__NOPT_ASSERT__
+					if (pParams->ContainsFlag(0x935ab858/*debug*/))
+					{
+						feeler.DebugLine(0,255,0);	 // green line = no collision
+					}
+					#endif
+					return false;
+				}
+				
+			}
+		
+        // @script | Obj_GetOrientation | Gets the X,Y,Z vector representing orientation 
+		case 0xb9ed09d2: // Obj_GetOrientation
+		{
+			Mth::Vector atVector;
+			atVector = m_display_matrix[Mth::AT];
+			atVector.Normalize();
+
+			pScript->GetParams()->AddFloat( "x", atVector[X] );
+			pScript->GetParams()->AddFloat( "y", atVector[Y] );
+			pScript->GetParams()->AddFloat( "z", atVector[Z] );
+			return true;
+		}
+		break;
+		
+		case 0xd96c0a20: // Obj_GetDistToNode
+		{
+			uint32 node_name=0;
+			pParams->GetChecksum(NONAME,&node_name);
+			int node = SkateScript::FindNamedNode(node_name);
+			Mth::Vector node_pos;
+			SkateScript::GetPosition(node, &node_pos);
+			pScript->GetParams()->AddFloat("Dist",(m_pos-node_pos).Length());
+			return true;
+		}
+		
+        // @script | Obj_GetDistanceToObject | Calculates the distance to the specified object,
+		// and puts the result, in units of feet, into ObjectDistance
+        // @uparmopt name | Name of the object. May be Skater.
+		case 0x4af57bbb: // Obj_GetDistanceToObject	
+		{
+			uint32 object_name=0;
+			pParams->GetChecksum(NONAME,&object_name);
+			CCompositeObject* p_obj=(CCompositeObject*)Obj::ResolveToObject(object_name);
+			
+			float dist=0.0f;
+			if (p_obj)
+			{
+				Mth::Vector d = m_pos - p_obj->GetPos();
+				dist=d.Length()/12.0f;
+			}
+			pScript->GetParams()->AddFloat("ObjectDistance",dist);
+			return true;
+		}
+
+		// @script | Obj_GetOrientationToObject |
+        // @uparm name | Name of the object. May be Skater.
+		case 0x2a26ffa2: // Obj_GetOrientationToObject	
+		{
+			uint32 object_name=0;
+			pParams->GetChecksum(NONAME,&object_name,Script::ASSERT);
+			CCompositeObject* p_obj=(CCompositeObject*)Obj::ResolveToObject(object_name);
+			Dbg_MsgAssert( p_obj, ( "Couldn't find object %s to get orientation", Script::FindChecksumName(object_name) ) );
+			
+    		float dotProd=0.0f;
+			float orientation=0.0f;
+
+			if ( p_obj )
+			{
+				// first get the object's current right vector
+				Mth::Vector atVector;
+				atVector = m_matrix[Mth::RIGHT];
+				atVector[W] = 0.0f;
+				atVector.Normalize();
+
+				// now take the vector to the object
+				Mth::Vector	toObjVector;
+				toObjVector = p_obj->m_pos - m_pos;
+				toObjVector[W] = 0.0f;
+				toObjVector.Normalize();
+                
+				// dot product tells us whether the right
+				// vector is on the left or the right hand
+				// side of the toObject vector
+				dotProd = Mth::DotProduct( atVector, toObjVector );
+
+				// subtracting 90 gets us whether the at vector is on
+				// the left or the right...
+				orientation = Mth::RadToDeg( Mth::Kenacosf( dotProd ) ) - 90.0f;	// add 90 to get the at vector
+			}
+
+			pScript->GetParams()->AddFloat(CRCD(0x7c6a7d7c,"DotProd"),dotProd);
+			pScript->GetParams()->AddFloat(CRCD(0xc97f3aa9,"Orientation"),orientation);
+			return true;
+		}
+
+        // @script | Backwards | Returns true if the skater is
+        // facing backwards from his direction of travel.
+		case CRCC(0xf8cfd515, "Backwards"):
+			return Mth::DotProduct(m_vel, m_matrix[Z]) < 0.0f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | SpeedEquals | true if skater speed is within 0.1 
+        // of the specified speed
+        // @uparm 1.0 | speed
+		case CRCC(0x7dcc5fb9, "SpeedEquals"):	 
+		{
+			float TestSpeed = 0;
+			pParams->GetFloat(NO_NAME, &TestSpeed);
+			return Mth::Abs(m_vel.Length() - TestSpeed) < 0.1f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; 
+		}
+			
+        // @script | SpeedGreaterThan | true if the skater speed is 
+        // greater than the specified speed
+        // @uparm 1.0 | speed
+		case CRCC(0xe5df66d7, "SpeedGreaterThan"):
+		{
+			float TestSpeed = 0;
+			pParams->GetFloat(NO_NAME, &TestSpeed);
+			return m_vel.Length() > TestSpeed ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; 
+		}
+
+        // @script | SpeedLessThan | true if the skater speed is
+        // less than the specified speed
+        // @uparm 1.0 | speed
+		case CRCC(0xdd468509, "SpeedLessThan"):
+		{
+			float TestSpeed = 0;
+			pParams->GetFloat(NO_NAME, &TestSpeed);
+			return m_vel.Length() < TestSpeed ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE; 
+		}
+	}
+
+
+	Dbg_MsgAssert(IsFinalized(),("CallMemberFunction %s to UnFinalized Composite object %s",Script::FindChecksumName(Checksum),Script::FindChecksumName(GetID())));
+	CBaseComponent *p_component = mp_component_list;
+	while (p_component)
+	{
+		switch (p_component->CallMemberFunction(Checksum, pParams, pScript))
+		{
+			case CBaseComponent::MF_TRUE:
+				return true;
+			case CBaseComponent::MF_FALSE:
+				return false;
+			default:
+				break;
+		}
+		p_component = p_component->GetNext();
+	}
+	
+	return CObject::CallMemberFunction( Checksum, pParams, pScript );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Used by the script debugger code (gel\scripting\debugger.cpp) to fill in a structure
+// for transmitting to the monitor.exe utility running on the PC.
+
+// This adds basic info about the CCompositeObject such as the id, position etc.
+// It also adds the debug info for each of the components in the list.
+// If classes derived from CCompositeObject override this function, then they should call
+// CCompositeObject::GetDebugInfo at the end.
+// It would not matter too much if they called it at the start instead, that would just mean
+// that derived classes info would appear at the end of the structure (after the list of components)
+// rather than at the start.
+void CCompositeObject::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CCompositeObject::GetDebugInfo"));
+	
+	int node_index=SkateScript::FindNamedNode(m_id,false); // false means don't assert if not found.
+	if (node_index >= 0 && m_id) // The && m_id is cos FindNamedNode erroneously returns 0 in that case.
+	{
+		Script::CStruct *p_node=SkateScript::GetNode(node_index);
+		p_info->AddInteger("NodeIndex",node_index);
+		p_info->AddStructure("NodeInfo",p_node);
+	}
+	else
+	{
+		p_info->AddChecksum(NONAME,CRCD(0xc39ae7b3,"NoNode"));
+	}	
+	
+	#ifdef __NOPT_ASSERT__
+	p_info->AddInteger("CPUTime",m_update_time);
+	p_info->AddInteger("m_total_script_update_time",m_total_script_update_time);
+	p_info->AddInteger("m_do_game_logic_time",m_do_game_logic_time);
+	#endif
+
+	CObject::GetDebugInfo(p_info);
+	
+	uint32 type_name=0;
+
+	switch (m_type)
+	{
+		case SKATE_TYPE_UNDEFINED:
+			type_name=CRCD(0x4cb79b3b,"SKATE_TYPE_UNDEFINED");
+			break;
+		case SKATE_TYPE_SKATER: 			
+			type_name=CRCD(0xdbb4aeb6,"SKATE_TYPE_SKATER");
+			break;
+		case SKATE_TYPE_PED:
+			type_name=CRCD(0xa88987b7,"SKATE_TYPE_PED");
+			break;
+		case SKATE_TYPE_CAR:
+			type_name=CRCD(0x2651eacb,"SKATE_TYPE_CAR");
+			break;
+		case SKATE_TYPE_GAME_OBJ:			
+			type_name=CRCD(0x84f6eb36,"SKATE_TYPE_GAME_OBJ");
+			break;
+		case SKATE_TYPE_BOUNCY_OBJ:		
+			type_name=CRCD(0x5f4886c1,"SKATE_TYPE_BOUNCY_OBJ");
+			break;
+		case SKATE_TYPE_CASSETTE:			
+			type_name=CRCD(0xd5021817,"SKATE_TYPE_CASSETTE");
+			break;
+		case SKATE_TYPE_ANIMATING_OBJECT:	
+			type_name=CRCD(0x42018493,"SKATE_TYPE_ANIMATING_OBJECT");
+			break;
+		case SKATE_TYPE_CROWN:	
+			type_name=CRCD(0xf4f7488e,"SKATE_TYPE_CROWN");
+			break;
+		case SKATE_TYPE_PARTICLE:			
+			type_name=CRCD(0x8feeb37e,"SKATE_TYPE_PARTICLE");
+			break;
+		case SKATE_TYPE_REPLAY_DUMMY:		
+			type_name=CRCD(0xf4ac5a55,"SKATE_TYPE_REPLAY_DUMMY");
+			break;
+		case SKATE_TYPE_COMPOSITE:	
+			type_name=CRCD(0x8a61a62c,"SKATE_TYPE_COMPOSITE");
+			break;
+		default:
+			type_name=0;
+			break;
+		
+	}
+	if (type_name)
+	{
+		p_info->AddChecksum("m_type",type_name);
+	}
+	else
+	{
+		// If the value is missing from the above switch statement then just add it as an integer.
+		p_info->AddInteger("m_type",m_type);
+	}
+	
+	p_info->AddChecksum("m_object_flags",m_object_flags);
+
+	// CCompositeObject stuff
+	if (m_composite_object_flags.Test(CO_PAUSED))
+	{
+		p_info->AddChecksum(NONAME,Script::GenerateCRC("Paused"));
+	}
+	
+	p_info->AddVector("m_pos",m_pos.GetX(),m_pos.GetY(),m_pos.GetZ());
+	p_info->AddVector("m_vel",m_vel.GetX(),m_vel.GetY(),m_vel.GetZ());
+	
+	Script::CArray *p_mat=new Script::CArray;
+	p_mat->SetSizeAndType(3,ESYMBOLTYPE_VECTOR);
+	Script::CVector *p_vec=new Script::CVector;
+	p_vec->mX=m_matrix[X][X]; p_vec->mY=m_matrix[X][Y]; p_vec->mZ=m_matrix[X][Z];
+	p_mat->SetVector(0,p_vec);
+	p_vec=new Script::CVector;
+	p_vec->mX=m_matrix[Y][X]; p_vec->mY=m_matrix[Y][Y]; p_vec->mZ=m_matrix[Y][Z];
+	p_mat->SetVector(1,p_vec);
+	p_vec=new Script::CVector;
+	p_vec->mX=m_matrix[Z][X]; p_vec->mY=m_matrix[Z][Y]; p_vec->mZ=m_matrix[Z][Z];
+	p_mat->SetVector(2,p_vec);
+	p_info->AddArrayPointer("m_matrix",p_mat);
+
+	p_mat=new Script::CArray;
+	p_mat->SetSizeAndType(3,ESYMBOLTYPE_VECTOR);
+	p_vec=new Script::CVector;
+	p_vec->mX=m_display_matrix[X][X]; p_vec->mY=m_display_matrix[X][Y]; p_vec->mZ=m_display_matrix[X][Z];
+	p_mat->SetVector(0,p_vec);
+	p_vec=new Script::CVector;
+	p_vec->mX=m_display_matrix[Y][X]; p_vec->mY=m_display_matrix[Y][Y]; p_vec->mZ=m_display_matrix[Y][Z];
+	p_mat->SetVector(1,p_vec);
+	p_vec=new Script::CVector;
+	p_vec->mX=m_display_matrix[Z][X]; p_vec->mY=m_display_matrix[Z][Y]; p_vec->mZ=m_display_matrix[Z][Z];
+	p_mat->SetVector(2,p_vec);
+	p_info->AddArrayPointer("m_display_matrix",p_mat);
+
+	Script::CStruct *p_components_structure=new Script::CStruct;
+	CBaseComponent *p_comp=mp_component_list;
+	while (p_comp)
+	{
+		Script::CStruct *p_component_structure=new Script::CStruct;
+		p_comp->GetDebugInfo(p_component_structure);
+		
+		// Using the component type as the name given to the structure, since the
+		// type is the checksum of the type name.
+		p_components_structure->AddStructurePointer(p_comp->GetType(),p_component_structure);
+		p_comp=p_comp->GetNext();
+	}
+		
+	p_info->AddStructurePointer("Components",p_components_structure);
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompositeObject::SetTeleported( bool update_components )
+{
+	m_composite_object_flags.Set(CO_TELEPORTED);
+
+	if ( update_components )
+	{
+		Teleport();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
+
diff --git a/Code/Gel/Object/compositeobject.h b/Code/Gel/Object/compositeobject.h
new file mode 100644
index 0000000..aa748c4
--- /dev/null
+++ b/Code/Gel/Object/compositeobject.h
@@ -0,0 +1,168 @@
+//****************************************************************************
+//* MODULE:         Gel/Object
+//* FILENAME:       CompositeObject.h
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#ifndef __OBJECT_COMPOSITEOBJECT_H__
+#define __OBJECT_COMPOSITEOBJECT_H__
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+// CComposite object contains a list of simple object components
+// which can be combined to make up complex stuff like cars and stuff
+
+class	CCompositeObject : public CObject
+{
+
+public:
+									CCompositeObject();
+    virtual 						~CCompositeObject();
+
+	enum ECompositeObjectFlags
+	{
+		CO_PAUSED,
+		CO_SUSPENDED,
+		CO_HIDDEN,
+		
+		// components should not consider the object's change in position since the beginning of this frame to have been smooth
+		CO_TELEPORTED,
+		
+		// Used by the script debugger.
+		CO_SENDING_DEBUG_INFO_EVERY_FRAME,
+		
+		// Used to indicate the object has been finalized
+		// so we can't add any more components to it
+		CO_FINALIZED,
+	};
+
+    void 							Update();
+	void 							Pause( bool paused )				{ m_composite_object_flags.Set(CO_PAUSED, paused); }
+	bool 							IsPaused( void )  const  			{ return m_composite_object_flags.Test(CO_PAUSED); }
+	void 							Suspend( bool suspended );
+	bool							IsSuspended( void )	const			{ return m_composite_object_flags.Test(CO_SUSPENDED); }
+	void							Hide( bool should_hide );
+	bool 							IsHidden( void ) const				{ return m_composite_object_flags.Test(CO_HIDDEN); }
+	bool 							IsFinalized( void )	const			{ return m_composite_object_flags.Test(CO_FINALIZED); }
+
+	void							SetTeleported( bool update_components = true );
+	void							ClearTeleported( void )				{ m_composite_object_flags.Clear(CO_TELEPORTED); }
+	bool							HasTeleported( void )	const		{ return m_composite_object_flags.Test(CO_TELEPORTED); }
+
+	// called when a model has radically changed position
+	void							Teleport();
+
+    void 							AddComponent( CBaseComponent* pComponent );
+	
+	void            				InitFromStructure( Script::CStruct* pParams );
+	void							Finalize();
+	void            				RefreshFromStructure( Script::CStruct* pParams );
+	void 							CreateComponentFromStructure(Script::CStruct *p_struct, Script::CStruct* p_params = NULL);
+	void 							CreateComponentsFromArray(Script::CArray *p_array, Script::CStruct * p_params = NULL);
+	CBaseComponent* 				GetComponent( uint32 id ) const;
+	virtual bool 					CallMemberFunction (uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript);
+
+	
+	void							LoadComponent ( CBaseComponent** pComp, uint32 id );
+	
+	// Used by the script debugger code (gel\scripting\debugger.cpp) to fill in a structure
+	// for transmitting to the monitor.exe utility running on the PC.
+	virtual void					GetDebugInfo(Script::CStruct *p_info);
+	void							SetDebuggerEveryFrameFlag(bool state) { m_composite_object_flags.Set(CO_SENDING_DEBUG_INFO_EVERY_FRAME,state); }
+	bool							GetDebuggerEveryFrameFlag()			{ return m_composite_object_flags.Test(CO_SENDING_DEBUG_INFO_EVERY_FRAME); }
+	
+		
+	// when a composite object is paused, spawned (non-main) scripts running on it should be paused, if the script has its mPauseWithObject flag set
+	virtual bool					ShouldUpdatePauseWithObjectScripts (   ) { return !IsPaused(); }
+
+	
+public:
+	const Mth::Vector&					GetPos( void )	  const   { return m_pos; }
+	void 							SetPos(const Mth::Vector& pos) 		{ m_pos = pos; }
+	Mth::Matrix&					GetMatrix( void )			{ return m_matrix; }
+	void							SetMatrix( const Mth::Matrix& matrix )	{ m_matrix = matrix; }
+	void							SetDisplayMatrix( const Mth::Matrix& matrix )	{ m_display_matrix = matrix; }
+	Mth::Matrix&					GetDisplayMatrix( void )	{ return m_display_matrix; }
+
+//////////////////////////////
+// These are public only while transitioning from CMovingObject to CComposititeobject
+public:
+	Mth::Vector						m_pos;
+	Mth::Vector						m_old_pos;
+	Mth::Matrix						m_matrix;
+///////////////////////////////	
+	
+	// velocity data doesn't belong here,
+	// but it's needed during the transition
+	// so that it doesn't break the collision code
+	Mth::Vector&					GetVel( void )  { return m_vel; }
+	void							SetVel( const Mth::Vector& vel )			{ m_vel = vel; }
+	Mth::Vector						m_vel;
+
+protected:
+	Mth::Matrix						m_display_matrix;		// final matrix used for display.  You need to rebuild this every frame
+	
+	// this is needed during the transition, only
+	// until we move all of the specific objects'
+	// logic into components.  this allows us
+	// to get rid of the tasks
+	virtual void					DoGameLogic() {}
+
+private:	
+	CBaseComponent*					mp_component_list;
+	
+	Flags	m_composite_object_flags;
+	
+	#ifdef __NOPT_ASSERT__
+	// The time (in microseconds) spent executing ::Update(), for displaying in the script debugger.
+	int								m_update_time;		
+	
+	int								m_total_script_update_time;
+	int								m_do_game_logic_time;
+	
+	// Runs through all the components and zeroes their update times.
+	void							zero_component_update_times();
+public:
+	void							SetUpdateTime(int t) {m_update_time=t;}
+	int								GetUpdateTime() const {return m_update_time;}
+private:	
+	#endif	
+
+#ifdef __USE_PROFILER__
+public:	
+	void 							SetProfileColor(uint32 ProfileColor) {m_profile_color = ProfileColor;} 
+	uint32 							GetProfileColor() const {return m_profile_color;} 
+private:
+	uint32							m_profile_color;
+#else
+public:	
+	void 							SetProfileColor(uint32 ProfileColor) {;} 
+#endif	
+	
+	// Composite objects have some common components hard wired, as it
+	// much simpler and more efficient to do it that way	
+};
+
+inline void CCompositeObject::LoadComponent ( CBaseComponent** pComp, uint32 id )
+{
+	if (!*pComp)
+	{
+		*pComp = GetComponent(id);
+	}
+	Dbg_Assert(*pComp);
+}
+
+}
+
+#endif
+
+
diff --git a/Code/Gel/Object/compositeobjectmanager.cpp b/Code/Gel/Object/compositeobjectmanager.cpp
new file mode 100644
index 0000000..99d8dbf
--- /dev/null
+++ b/Code/Gel/Object/compositeobjectmanager.cpp
@@ -0,0 +1,512 @@
+//****************************************************************************
+//* MODULE:         Gel/Object
+//* FILENAME:       CompositeObjectManager.cpp
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+// move these to the component factory
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+//#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef TESTING_GUNSLINGER
+#include 
+#include 
+#include 
+#include 
+#endif
+
+
+
+namespace Obj
+{
+
+DefineSingletonClass( CCompositeObjectManager, "Composite Manager" );
+
+CBaseComponent*	CCompositeObjectManager::mp_components_by_type[vMAX_COMPONENTS];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCompositeObjectManager::CCompositeObjectManager()
+{
+    mp_logic_task = new Tsk::Task< CCompositeObjectManager > ( CCompositeObjectManager::s_logic_code, *this, Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_COMPOSITE_MANAGER );
+	
+    Mlp::Manager* mlp_manager =  Mlp::Manager::Instance(); 
+	mlp_manager->AddLogicTask( *mp_logic_task );
+
+	// Register the default component objects
+	// This also might be better moved to a CComponentFactory class
+	// but let's see how it works first.
+	
+	RegisterComponent(CRC_SKATERSTANCEPANEL,	CSkaterStancePanelComponent::s_create);
+	RegisterComponent(CRC_SKATERLOOPINGSOUND,	CSkaterLoopingSoundComponent::s_create);
+	RegisterComponent(CRC_SKATERSOUND,			CSkaterSoundComponent::s_create);
+	RegisterComponent(CRC_SKATERGAP,			CSkaterGapComponent::s_create);
+	RegisterComponent(CRC_SKATERROTATE,			CSkaterRotateComponent::s_create);
+	RegisterComponent(CRC_SKATERPHYSICSCONTROL,	CSkaterPhysicsControlComponent::s_create);
+	RegisterComponent(CRC_SKATERLOCALNETLOGIC,	CSkaterLocalNetLogicComponent::s_create);
+	RegisterComponent(CRC_SKATERNONLOCALNETLOGIC,	CSkaterNonLocalNetLogicComponent::s_create);
+	RegisterComponent(CRC_SKATERSCORE,			CSkaterScoreComponent::s_create);
+	RegisterComponent(CRC_SKATERMATRIXQUERIES,	CSkaterMatrixQueriesComponent::s_create);
+	RegisterComponent(CRC_SKATERFLOATINGNAME,	CSkaterFloatingNameComponent::s_create);
+	RegisterComponent(CRC_SKATERSTATEHISTORY,	CSkaterStateHistoryComponent::s_create);
+	RegisterComponent(CRC_SKATERCOREPHYSICS,	CSkaterCorePhysicsComponent::s_create);
+	RegisterComponent(CRC_SKATERADJUSTPHYSICS,	CSkaterAdjustPhysicsComponent::s_create);
+	RegisterComponent(CRC_SKATERFINALIZEPHYSICS,	CSkaterFinalizePhysicsComponent::s_create);
+	RegisterComponent(CRC_SKATERCLEANUPSTATE,	CSkaterCleanupStateComponent::s_create);
+	RegisterComponent(CRC_SKATERENDRUN,			CSkaterEndRunComponent::s_create);
+	RegisterComponent(CRC_SKATERBALANCETRICK,	CSkaterBalanceTrickComponent::s_create);
+	RegisterComponent(CRC_SKATERFLIPANDROTATE,	CSkaterFlipAndRotateComponent::s_create);
+	RegisterComponent(CRC_SKATERSTATE,			CSkaterStateComponent::s_create);
+	RegisterComponent(CRC_GOALEDITOR,			CGoalEditorComponent::s_create); 	
+	RegisterComponent(CRC_RAILEDITOR,			CRailEditorComponent::s_create); 	
+	RegisterComponent(CRC_SKATERPROXIMITY,		CSkaterProximityComponent::s_create);
+	RegisterComponent(CRC_EDITORCAMERA,			CEditorCameraComponent::s_create);
+	RegisterComponent(CRC_SKATERRUNTIMER,		CSkaterRunTimerComponent::s_create);
+	
+	RegisterComponent(CRC_MODEL,				CModelComponent::s_create); 
+	RegisterComponent(CRC_MODELLIGHTUPDATE,		CModelLightUpdateComponent::s_create); 
+	RegisterComponent(CRC_BOUNCY,				CBouncyComponent::s_create); 
+	RegisterComponent(CRC_SKELETON,		    	CSkeletonComponent::s_create); 
+	RegisterComponent(CRC_ANIMATION,			CAnimationComponent::s_create); 
+	RegisterComponent(CRC_SOUND,		    	CSoundComponent::s_create); 
+	RegisterComponent(CRC_COLLISION,			CCollisionComponent::s_create); 
+	RegisterComponent(CRC_LOCKOBJ,				CLockObjComponent::s_create, CLockObjComponent::s_register); 
+	RegisterComponent(CRC_CARPHYSICS,			CCarPhysicsComponent::s_create);
+	RegisterComponent(CRC_MOTION,				CMotionComponent::s_create); 
+	RegisterComponent(CRC_SUSPEND,				CSuspendComponent::s_create, CSuspendComponent::s_register); 
+	RegisterComponent(CRC_TRICK,				CTrickComponent::s_create);
+	RegisterComponent(CRC_AVOID,				CAvoidComponent::s_create);
+	RegisterComponent(CRC_PEDLOGIC,				CPedLogicComponent::s_create);
+	RegisterComponent(CRC_RIGIDBODY,			CRigidBodyComponent::s_create);
+	RegisterComponent(CRC_SHADOW,				CShadowComponent::s_create);
+	RegisterComponent(CRC_STREAM,				CStreamComponent::s_create);
+	RegisterComponent(CRC_SPECIALITEM,			CSpecialItemComponent::s_create);
+	RegisterComponent(CRC_VEHICLE,				CVehicleComponent::s_create); 
+	RegisterComponent(CRC_VEHICLECAMERA,		CVehicleCameraComponent::s_create); 
+	RegisterComponent(CRC_NODEARRAY,			CNodeArrayComponent::s_create); 
+	RegisterComponent(CRC_RAILMANAGER,			CRailManagerComponent::s_create); 
+	RegisterComponent(CRC_OBJECTHOOKMANAGER,	CObjectHookManagerComponent::s_create); 
+	RegisterComponent(CRC_SKITCH,				CSkitchComponent::s_create);
+//	RegisterComponent(CRC_NEAR,					CNearComponent::s_create);
+	RegisterComponent(CRC_CAMERA,				CCameraComponent::s_create);
+	RegisterComponent(CRC_SKATERCAMERA,			CSkaterCameraComponent::s_create);
+	RegisterComponent(CRC_VIBRATION,			CVibrationComponent::s_create);
+//	RegisterComponent(CRC_PROXIMTRIGGER,		CProximTriggerComponent::s_create);
+	RegisterComponent(CRC_TRIGGER,				CTriggerComponent::s_create);
+	RegisterComponent(CRC_FLOATINGLABEL,		CFloatingLabelComponent::s_create);
+	RegisterComponent(CRC_INPUT,				CInputComponent::s_create);
+	RegisterComponent(CRC_PARTICLE,				CParticleComponent::s_create);
+	RegisterComponent(CRC_WALK,					CWalkComponent::s_create);
+	RegisterComponent(CRC_WALKCAMERA,			CWalkCameraComponent::s_create);
+	RegisterComponent(CRC_CAMERALOOKAROUND,		CCameraLookAroundComponent::s_create);
+	RegisterComponent(CRC_MOVABLECONTACT,		CMovableContactComponent::s_create);
+    RegisterComponent(CRC_STATSMANAGER,			CStatsManagerComponent::s_create); 
+    RegisterComponent(CRC_VEHICLESOUND,			CVehicleSoundComponent::s_create); 
+	RegisterComponent(CRC_VELOCITY,				CVelocityComponent::s_create);
+	RegisterComponent(CRC_COLLIDEANDDIE,		CCollideAndDieComponent::s_create);
+	RegisterComponent(CRC_PROJECTILECOLLISION,	CProjectileCollisionComponent::s_create);
+	RegisterComponent(CRC_SETDISPLAYMATRIX,		CSetDisplayMatrixComponent::s_create);
+	RegisterComponent(CRC_STATICVEHICLE,		CStaticVehicleComponent::s_create);
+
+
+#	ifdef TESTING_GUNSLINGER
+	RegisterComponent(CRC_HORSE,				CHorseComponent::s_create);
+	RegisterComponent(CRC_HORSECAMERA,			CHorseCameraComponent::s_create);
+	RegisterComponent(CRC_RIDER,				CRiderComponent::s_create);
+	RegisterComponent(CRC_WEAPON,				CWeaponComponent::s_create);
+#	endif
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCompositeObjectManager::~CCompositeObjectManager()
+{
+    if ( mp_logic_task )
+    {
+        mp_logic_task->Remove();
+        delete mp_logic_task;
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCompositeObject*	CCompositeObjectManager::CreateCompositeObject()
+{
+	Obj::CCompositeObject *p_composite = new Obj::CCompositeObject;
+	
+	// printf("+++> Frame: %i\n", Tmr::GetRenderFrame());
+	
+	// DODGY SETTING OF COMPOSITE TYPE
+	// FOR TEMP PATCHING OF SYSTEMS THAT ASSUME EVERYTHING IS A CMovingObject
+	p_composite->SetType(11);	// SKATE_TYPE_COMPOSITE;
+
+    RegisterObject( *p_composite );
+	return p_composite;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given a composite object definition array, and a "node" style structure
+// with a "name" and "position"
+// then create the Composite object at that position, with the id or the name
+// and create the componets for it from the array
+// NOTE: this is kind of a specific utility function, added
+// to ease the transition from old nodearray style contruction of objects 
+
+CCompositeObject* CCompositeObjectManager::CreateCompositeObjectFromNode(Script::CArray *pArray, Script::CStruct *pNodeData, bool finalize)
+{
+	Obj::CCompositeObject* pObj = CreateCompositeObject();
+	pObj->InitFromStructure(pNodeData);	
+	pObj->CreateComponentsFromArray(pArray, pNodeData);	
+	if (finalize)
+	{
+		pObj->Finalize();		// Finalize the interfaces between components
+	}
+	// printf("+++> Creating Composite Object:  %s\n", Script::FindChecksumName(pObj->GetID()));
+	return pObj;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Registering components simply adds them to the list of components that
+// the compositeobject manager can create
+// All that is passed in is a 32 bit id (of the component, e.g. CRC_MODEL)
+// and the callback function pointer to the s_create function
+	
+void	CCompositeObjectManager::RegisterComponent(uint32 id, CBaseComponent *(p_create_function)(), void(p_register_function)()) 
+{
+	Dbg_MsgAssert(m_num_components < vMAX_COMPONENTS,("Too many components (%d)",vMAX_COMPONENTS));
+	m_registered_components[m_num_components].mComponentID = id;
+	m_registered_components[m_num_components].mpCreateFunction = p_create_function;
+	m_num_components++;
+
+	// I'm letting the component manager control calling the "register" function
+	// no good reason, just better encapsulation.  Prevents bugs.
+	if (p_register_function)
+	{
+		p_register_function();
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent*		CCompositeObjectManager::CreateComponent(uint32 id)
+{
+	for (uint32 i=0;i sh;
+	
+	uint32 stamp_mask = m_stamp_bit_manager.RequestBit();
+	
+	// clear the appropriate stamp bit
+	for (CObject* pObject = sh.FirstItem(m_object_list); pObject; pObject = sh.NextItem())
+	{
+		pObject->ClearStampBit(stamp_mask);
+	}
+	
+	// traverse the object list
+	do
+	{
+		// clear the appropriate list changed bit
+		m_list_changed &= ~stamp_mask;
+		
+        CObject* pNextObject = sh.FirstItem(m_object_list);
+		
+		while (pNextObject)
+		{
+			CObject* pObject = pNextObject;
+			pNextObject = sh.NextItem();
+			
+			if (!pObject->CheckStampBit(stamp_mask))
+			{
+				pObject->SetStampBit(stamp_mask);
+				
+				#ifdef __NOPT_ASSERT__
+				Tmr::CPUCycles time_before = Tmr::GetTimeInCPUCycles();
+				CSmtPtr< CObject > p_smart_object = pObject;
+				uint32 obj_id = pObject->GetID(); 
+				#endif
+				
+				static_cast< CCompositeObject* >(pObject)->Update();
+				
+				#ifdef __NOPT_ASSERT__
+				Dbg_MsgAssert(p_smart_object, ("Object %s has deleted itself in its Update() function", Script::FindChecksumName(obj_id)));
+				// Convert to microseconds by dividing by 150
+				static_cast< CCompositeObject* >(pObject)->SetUpdateTime((Tmr::GetTimeInCPUCycles() - time_before) / 150);
+				#endif
+				
+				if (m_list_changed & stamp_mask) break;
+			}
+		}
+	}
+	while (m_list_changed & stamp_mask);
+	
+	m_stamp_bit_manager.ReturnBit(stamp_mask);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompositeObjectManager::Pause(bool paused)
+{
+	Lst::Search< CObject >	sh;
+	
+	uint32 stamp_mask = m_stamp_bit_manager.RequestBit();
+	
+	// clear the appropriate stamp bit
+	for (CObject* pObject = sh.FirstItem(m_object_list); pObject; pObject = sh.NextItem())
+	{
+		pObject->ClearStampBit(stamp_mask);
+	}
+	
+	// traverse the object list
+	do
+	{
+		// clear the appropriate list changed bit
+		m_list_changed &= ~stamp_mask;
+		
+        CObject* pNextObject = sh.FirstItem(m_object_list);
+		
+		while (pNextObject)
+		{
+			CObject* pObject = pNextObject;
+			pNextObject = sh.NextItem();
+			
+			if (!pObject->CheckStampBit(stamp_mask))
+			{
+				pObject->SetStampBit(stamp_mask);
+				
+				static_cast< CCompositeObject* >(pObject)->Pause(paused);
+				
+				if (m_list_changed & stamp_mask) break;
+			}
+		}
+	}
+	while (m_list_changed & stamp_mask);
+	
+	m_stamp_bit_manager.ReturnBit(stamp_mask);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompositeObjectManager::s_logic_code ( const Tsk::Task< CCompositeObjectManager >& task )
+{
+    Dbg_AssertType ( &task, Tsk::Task< CCompositeObjectManager > );
+
+	CCompositeObjectManager& obj_man = task.GetData();
+
+	obj_man.Update();    
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CBaseComponent* CCompositeObjectManager::GetFirstComponentByType( uint32 id )
+{
+	for( uint32 i = 0; i < m_num_components; ++i )
+	{
+		if( m_registered_components[i].mComponentID == id )
+		{
+			return mp_components_by_type[i];
+		}
+	}
+	Dbg_MsgAssert( 0, ( "Component %s not registered", Script::FindChecksumName( id )));
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCompositeObjectManager::AddComponentByType( CBaseComponent *p_component )
+{
+	uint32 id = p_component->GetType();
+
+	for( uint32 i = 0; i < m_num_components; ++i )
+	{
+		if( m_registered_components[i].mComponentID == id )
+		{
+			p_component->SetNextSameType( mp_components_by_type[i] );
+			mp_components_by_type[i] = p_component;
+			return;
+		}
+	}
+	Dbg_MsgAssert( 0, ( "Component %s not registered", Script::FindChecksumName( id )));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CCompositeObjectManager::RemoveComponentByType( CBaseComponent *p_component )
+{
+	uint32 id = p_component->GetType();
+
+	for( uint32 i = 0; i < m_num_components; ++i )
+	{
+		if( m_registered_components[i].mComponentID == id )
+		{
+			if( mp_components_by_type[i] == p_component )
+			{
+				mp_components_by_type[i] = p_component->GetNextSameType();
+				p_component->SetNextSameType( NULL );
+				return;
+			}
+			else
+			{
+				CBaseComponent *p_loop = mp_components_by_type[i];
+				while( p_loop )
+				{
+					if( p_loop->GetNextSameType() == p_component )
+					{
+						p_loop->SetNextSameType( p_component->GetNextSameType());
+						p_component->SetNextSameType( NULL );
+						return;
+					}
+					p_loop = p_loop->GetNextSameType();
+				}
+			}
+		}
+	}
+	Dbg_MsgAssert( 0, ( "Component %s not registered", Script::FindChecksumName( id )));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+	
+}
diff --git a/Code/Gel/Object/compositeobjectmanager.h b/Code/Gel/Object/compositeobjectmanager.h
new file mode 100644
index 0000000..993f9b8
--- /dev/null
+++ b/Code/Gel/Object/compositeobjectmanager.h
@@ -0,0 +1,72 @@
+//****************************************************************************
+//* MODULE:         Gel/Object
+//* FILENAME:       compositeobjectmanager.h
+//* OWNER:          Mick West
+//* CREATION DATE:  10/17/2002
+//****************************************************************************
+
+#ifndef __OBJECT_COMPOSITEMANAGER_H__
+#define __OBJECT_COMPOSITEMANAGER_H__
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+                          
+namespace Obj
+{
+
+// Pointer to member.  See Bjarne, Page 418
+// not sure if we can use these instead of function pointers to static functions
+// (for now, use the old fasioned way)
+//typedef	CBaseComponent * (Obj::CBaseComponent *PComponent)();	// Pointer to member of CBaseComponent
+
+struct	SRegisteredComponent
+{
+	uint32				mComponentID;		   		
+	CBaseComponent*		(*mpCreateFunction)();
+};
+
+
+class CCompositeObjectManager : public Obj::CGeneralManager
+{
+
+	enum 
+	{
+				vMAX_COMPONENTS=128						
+	};
+
+public:
+						CCompositeObjectManager();
+    virtual 			~CCompositeObjectManager();
+
+	void 				Update();
+	void 				Pause( bool paused );
+	
+	CCompositeObject*	CreateCompositeObject();
+	CCompositeObject* 	CreateCompositeObjectFromNode(Script::CArray *pArray, Script::CStruct *pNodeData, bool finalize=true);
+
+	void				RegisterComponent(uint32 id, CBaseComponent *(p_create_function)(), void(p_register_function)() = NULL); 
+	CBaseComponent*		CreateComponent(uint32 id);
+    
+	CBaseComponent*		GetFirstComponentByType( uint32 id );
+	void				AddComponentByType( CBaseComponent *p_component );
+	void				RemoveComponentByType( CBaseComponent *p_component );
+
+protected:
+	static Tsk::Task< CCompositeObjectManager >::Code   	s_logic_code; 
+	Tsk::Task< CCompositeObjectManager >*				    mp_logic_task;	
+
+	uint32													m_num_components;
+	SRegisteredComponent									m_registered_components[vMAX_COMPONENTS];
+
+	static CBaseComponent									*mp_components_by_type[vMAX_COMPONENTS];
+	
+	DeclareSingletonClass( CCompositeObjectManager );
+};
+
+}
+
+#endif
diff --git a/Code/Gel/Object/object.cpp b/Code/Gel/Object/object.cpp
new file mode 100644
index 0000000..45b5dd0
--- /dev/null
+++ b/Code/Gel/Object/object.cpp
@@ -0,0 +1,1189 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		object.cpp												**
+**																			**
+**	Created:		05/27/99	-	mjb										**
+**																			**
+**	Description:	Game object code										**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC object.cpp
+// @module object | None
+// @subindex Scripting Database
+// @index script | object
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+
+
+namespace Script
+{
+	extern void PrintContents(CStruct *p_structure, int indent);
+}
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// temp
+// temp	  REMOVE THIS WHEN script.h is implemented
+// temp
+//class	CScript
+//{
+//};
+// temp
+// temp
+// temp
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/*
+	Creates CObject, does not give it an ID or register it.
+*/
+CObject::CObject() :
+	m_node(this)
+{
+	m_id = CBaseManager::vNO_OBJECT_ID;	
+	m_type = CBaseManager::vNO_OBJECT_TYPE;	
+#ifndef	__SCRIPT_EVENT_TABLE__		
+	mp_event_handler_table = NULL;
+#endif	
+	mp_tags = NULL;
+	mp_script = NULL;
+	mp_manager = NULL;
+	m_object_flags = 0;
+	
+	m_ref_count = 0;
+	
+	m_stamp = 0;
+}
+
+
+
+
+/*
+	Creates CObject, registers it with manager, giving it automatic ID.
+*/
+CObject::CObject( CBaseManager* p_obj_manager )
+	: m_node(this)
+//: manager( obj_manager )
+
+{
+	Dbg_Assert(p_obj_manager);	
+
+	m_id = CBaseManager::vNO_OBJECT_ID;	
+	m_type = CBaseManager::vNO_OBJECT_TYPE;	
+	p_obj_manager->RegisterObject( *this );	// add to manager's object list
+#ifndef	__SCRIPT_EVENT_TABLE__		
+	mp_event_handler_table = NULL;
+#endif	
+	mp_tags = NULL;
+	mp_script = NULL;
+	mp_manager = NULL;
+	m_object_flags = 0;
+	
+	m_ref_count = 0;
+
+
+}
+
+
+
+
+/*
+	Destroying CObject disconnects it from manager, stops all scripts running on it.
+*/
+CObject::~CObject()
+{
+	
+	#ifdef	__NOPT_ASSERT__	
+	if (GetLockAssert())
+	{
+		if (mp_script)
+		{	
+			Dbg_MsgAssert(0,("\n%s\n Object %s at %p has been deleted while updating a script running on it, maybe goal manager callback? See Mick.",mp_script->GetScriptInfo(),Script::FindChecksumName(GetID()),this));
+		}
+		Dbg_MsgAssert(0,("Object %s at %p has been deleted while updating a script running on it, maybe goal manager callback? See Mick.",Script::FindChecksumName(GetID()),this));
+	}
+	#endif
+	
+	Dbg_MsgAssert(!IsLocked(), ("can't destroy CObject, is locked"));
+	
+	if (mp_tags)
+		delete mp_tags;
+
+#ifndef	__SCRIPT_EVENT_TABLE__		
+	if (mp_event_handler_table)
+	{
+		// Remove the references to the event handler from the tracker
+		mp_event_handler_table->unregister_all(this);
+		
+		if (mp_event_handler_table->m_in_immediate_use_counter)
+		{
+			// table still in use by its pass_event() function,
+			// so leave it up to that function to destroy the table
+			mp_event_handler_table->m_valid = false;
+		}
+		else
+		{
+			delete mp_event_handler_table;
+		}
+	}
+#endif
+
+	if (mp_manager)
+	{
+		mp_manager->UnregisterObject( *this );
+	}
+
+	if (mp_script)
+	{
+		delete mp_script;
+		mp_script = NULL;
+	}
+	
+	// Stop any scripts that have this object as their parent.
+	// Note: This won't actually delete the scripts, to prevent dangling pointers.
+	Script::StopAllScriptsUsingThisObject(this);
+}
+
+
+
+
+/*
+	Used by object managers when they delete all but a few objects (which have been locked).
+*/
+void CObject::DestroyIfUnlocked()
+{	   
+	if ( !IsLocked() )
+	{
+		delete this;
+	}
+}
+
+
+
+
+/*
+	Sets the global ID of this CObject. A CObject needs an ID in order to function
+	in the system. If no ID is assigned, it will automatically be assigned one 
+	when registered with a manager.
+	
+	Calling this function on a CObject that already has an ID will attempt to change
+	its ID globally (whereever that ID is used).
+*/
+void CObject::SetID(uint32 id) 
+{	
+	if (m_id != CBaseManager::vNO_OBJECT_ID && mp_manager)
+	{
+		mp_manager->ReregisterObject(*this, id);
+	}
+	m_id = id;
+	SetChecksumTag(CRCD(0x40c698af, "id"), m_id);
+}
+
+
+
+
+/*
+	Type is pretty much just the checksum of the class, minus the "C"
+*/
+void CObject::SetType(sint type) 
+{
+	Dbg_MsgAssert(m_type == CBaseManager::vNO_OBJECT_TYPE, ("CObject already assigned a type"));
+	m_type = type;
+	SetChecksumTag(CRCD(0x7321a8d6, "type"), m_type);
+}
+
+
+#ifndef	__SCRIPT_EVENT_TABLE__		
+
+/*
+	Sets up the table that specifies which scripts to run in response to which events.
+	
+	See object scripting document.
+*/
+void CObject::SetEventHandlers(Script::CArray *pArray, EReplaceEventHandlers replace)
+{
+	
+	if (!mp_event_handler_table)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		mp_event_handler_table = new CEventHandlerTable();
+		Mem::Manager::sHandle().PopContext();
+	}
+	else
+	{
+		mp_event_handler_table->unregister_all(this);	
+	}
+	mp_event_handler_table->add_from_script(pArray, replace);
+//	mp_event_handler_table->compress_table();
+	
+	mp_event_handler_table->register_all(this);
+
+}
+
+
+
+/*
+	Removes an entry in the event table with the given type id
+*/
+void CObject::RemoveEventHandler(uint32 type)
+{
+	if (!mp_event_handler_table) return;
+	mp_event_handler_table->remove_entry(type);
+
+// Mick:  by not compressing the event handler table after removing entries
+// we should minimize the amount of allocs needed
+// new event handlers can just re-use these "removed" sloes
+//	mp_event_handler_table->compress_table();
+
+// Refresh the Obj::Tracker
+
+	Obj::CTracker::Instance()->UnregisterEventReceiver(type, this); 
+		
+}
+
+
+
+/*
+	Removes all entries in the event table with the given group id
+*/
+void CObject::RemoveEventHandlerGroup(uint32 group)
+{
+	if (!mp_event_handler_table) return;
+	
+	mp_event_handler_table->unregister_all(this);	
+	
+	mp_event_handler_table->remove_group(group);
+
+// Mick:  by not compressing the event handler table after removing entries
+// we should minimize the amount of allocs needed
+// new event handlers can just re-use these "removed" sloes
+//	mp_event_handler_table->compress_table();
+	
+	mp_event_handler_table->register_all(this);	
+
+}
+
+#else
+
+
+/*
+	Sets up the table that specifies which scripts to run in response to which events.
+	
+	See object scripting document.
+*/
+void CObject::SetEventHandlers(Script::CArray *pArray, EReplaceEventHandlers replace)
+{
+	// TEMP - Pass into script object.  Eventually handled soley by script with no need for object
+	AllocateScriptIfNeeded();
+	mp_script->SetEventHandlers(pArray, replace);
+}
+
+/*
+	Removes an entry in the event table with the given type id
+*/
+void CObject::RemoveEventHandler(uint32 type)
+{
+	if (!mp_script)
+	{
+		return;
+	}
+	mp_script->RemoveEventHandler(type );
+}
+
+/*
+	Removes all entries in the event table with the given group id
+*/
+void CObject::RemoveEventHandlerGroup(uint32 group)
+{
+	if (!mp_script)
+	{
+		return;
+	}
+	mp_script->RemoveEventHandlerGroup(group);
+}
+
+
+
+#endif
+
+
+void CObject::SetIntegerTag(uint32 name, int value)
+{
+	allocate_tags_if_needed();
+
+	mp_tags->AddInteger(name, value);
+}
+
+
+
+
+void CObject::SetChecksumTag(uint32 name, uint32 value)
+{
+	allocate_tags_if_needed();
+
+	mp_tags->AddChecksum(name, value);
+}
+
+
+
+
+void CObject::RemoveFlagTag(uint32 name)
+{
+	if (mp_tags)
+		mp_tags->RemoveFlag(name);
+}
+
+
+
+
+bool CObject::GetIntegerTag(uint32 name, int *pResult) const
+{
+	if (mp_tags)
+		return mp_tags->GetInteger(name, pResult);
+	return false;
+}
+
+
+
+
+bool CObject::GetChecksumTag(uint32 name, uint32 *pResult) const 
+{
+	if (mp_tags)
+		return mp_tags->GetChecksum(name, pResult);
+	return false;
+}
+
+
+
+
+bool CObject::ContainsFlagTag(uint32 name) const
+{
+	if (mp_tags)
+		return mp_tags->ContainsFlag(name);
+	return false;
+}
+
+
+// Add a vector to the tag structure with name "name", and copy in the vector "v"
+void CObject::SetVectorTag(uint32 name, Mth::Vector	v)
+{
+	allocate_tags_if_needed();
+
+	mp_tags->AddVector(name, v[X], v[Y], v[Z]);
+}
+
+
+// get the value a vector tag.
+// returning false if not found
+bool CObject::GetVectorTag(uint32 name, Mth::Vector *pResult) const
+{
+	if (mp_tags)
+		return mp_tags->GetVector(name, pResult);
+	return false;
+}
+
+#ifndef	__SCRIPT_EVENT_TABLE__		
+
+void 	CObject::SetOnExceptionScriptChecksum(uint32 OnExceptionScriptChecksum) 
+{
+	mOnExceptionScriptChecksum = OnExceptionScriptChecksum;
+}
+
+uint32 	CObject::GetOnExceptionScriptChecksum() const 
+{
+	return mOnExceptionScriptChecksum;
+}
+#else
+// Todo - Pass these to the script
+
+#endif
+
+
+void CObject::SetTagsFromScript(Script::CStruct *pStruct)
+{
+	allocate_tags_if_needed();
+
+	mp_tags->AppendStructure(pStruct);
+	//printf("Set tags of object %s to:\n", Script::FindChecksumName(m_id));
+	//Script::PrintContents(mp_tags, 4);
+}
+
+
+
+
+void CObject::RemoveTagsFromScript(Script::CArray *pNameArray)
+{
+	if (!mp_tags) return;
+
+	for (uint i = 0; i < pNameArray->GetSize(); i++)
+	{
+		uint32 name_crc = pNameArray->GetChecksum(i);
+		mp_tags->RemoveComponent(name_crc);
+		mp_tags->RemoveFlag(name_crc);
+	}
+	
+	if (mp_tags->IsEmpty())
+	{
+		delete mp_tags;
+		mp_tags = NULL;
+	}
+}
+
+
+
+
+void CObject::CopyTagsToScriptStruct(Script::CStruct *pStruct)
+{
+	Dbg_Assert(pStruct);
+	
+	if (mp_tags)	
+	{
+		pStruct->AppendStructure(mp_tags);
+		//printf("Fetching tags of object %s:\n", Script::FindChecksumName(m_id));
+		//Script::PrintContents(pStruct, 4);
+	}
+}
+
+
+
+
+/*
+	Property setting script commands enter through this function.
+*/
+void CObject::SetProperties(Script::CStruct *pProps)
+{
+	bool replace_handlers = pProps->ContainsFlag("replace_handlers");
+
+	Script::CArray *p_event_handler_table;
+	if (pProps->GetArray("event_handlers", &p_event_handler_table))
+	{
+#ifdef	__SCRIPT_EVENT_TABLE__		
+		CObject::SetEventHandlers(p_event_handler_table, EReplaceEventHandlers(replace_handlers));
+#else	
+		CObject::SetEventHandlers(p_event_handler_table, EReplaceEventHandlers(replace_handlers));
+#endif	
+	}
+	/*
+	else if (pProps->GetArray("event_handler", &p_event_handler_table))
+	{
+		// just a single entry (makes for easier-to-read script)
+		CObject::SetEventHandlers(p_event_handler_table, replace_handlers, true);
+	}
+	*/
+
+	Script::CStruct *p_tags;
+	if (pProps->GetStructure("tags", &p_tags)) 
+		SetTagsFromScript(p_tags);
+
+	Script::CArray *p_remove_tags;
+	if (pProps->GetArray("remove_tags", &p_remove_tags))
+		RemoveTagsFromScript(p_remove_tags);
+}
+
+
+
+
+/******************************************************************/
+/*   CObject::CallMemberFunction                                  */
+/*   Call a member function, based on a checksum                  */
+/*   This is usually the checksum of the name of the function     */
+/*   but can actually be any number, as it just uses a switch     */
+/*   note this is a virtual function, so the same checksum        */
+/*   can do differnet things for different objects                */
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CObject::CallMemberFunction( uint32 Checksum, Script::CScriptStructure *pParams, Script::CScript *pScript )
+{
+    
+	Dbg_MsgAssert(pScript,("NULL pScript"));
+
+	switch(Checksum)
+	{
+		case CRCC(0x3611c136, "GetTags"):
+			CopyTagsToScriptStruct(pScript->GetParams());
+			break;
+
+		case CRCC( 0xa58079eb, "SetTags" ):
+			SetTagsFromScript( pParams );
+			break;
+			 
+		case 0xc6870028:// "Die"
+			MarkAsDead( );
+			break;	
+		        
+		case 0xb3c262ec:// "DisassociateFromObject"
+			pScript->DisassociateWithObject(this);
+			break;	
+		        
+		// @script | Obj_GetId | 
+		case 0x500eb224: // Obj_GetId
+			Dbg_Assert( pScript && pScript->GetParams() );
+			pScript->GetParams()->AddChecksum( "objId", m_id ); 
+		break;
+		
+		// @script | Obj_FlagSet | Check to see if a flag has been set.  Flags can 
+		// be defined anywhere, but you should keep them in your personal scripts file.  
+		// It is important that you prefix all flags with your initials to ensure that 
+		// there are no conflicts with the other designers.
+		// Example 1:
+		// MJD_LAMP_GOT_BROKEN = 14
+		// MJD_LAMP_GOT_HIT = 15
+		// MJD_LAMP_GOT_DISABLED = 16
+		// if Obj_FlagSet 15
+		//     Obj_ClearFlag MJD_LAMP_GOT_HIT
+		//     WiggleProfusely
+		// endif
+		// Example 2:
+		// if Obj_FlagSet MJD_LAMP_GOT_HIT clear
+		//     WiggleProfusely
+		// endif
+		// Example 3:
+		// If Obj_FlagSet JKU_FLAG_PED_START  // JKU_FLAG_PED_START would be defined in JKU_Scripts.q
+		//      Printf “Yes”
+		// endif
+		// @flag all | Clear all flags
+		// @flag clear | Clear the flag
+		// @flag reset | I just added a 'reset' flag you can send in to clear 
+		// the flag after checking it, if it's set
+		case 0x4babc987: // Obj_FlagSet
+		{
+			uint32 scriptFlags = mScriptFlags;
+			uint32 Flags = 0;
+			Flags = GetFlags( pParams, pScript );
+			if ( !Flags )
+			{
+				Dbg_MsgAssert( 0,( "\n%s\nObj_FlagSet command requires a flag to be specified.\n(Either an integer or a name defined to be an integer)",pScript->GetScriptInfo()));
+				return ( false );
+			}
+			if ( pParams->ContainsFlag( 0x1a4e0ef9 ) ) // clear
+			{
+				mScriptFlags &= ~( Flags );
+			}
+			if ( scriptFlags & Flags )
+			{
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+			break;
+		}		
+		
+		// @script | Obj_FlagNotSet | Check to see if a flag has not been set.
+		case 0x53ebee03: // Obj_FlagNotSet
+		{
+			uint32 Flags = 0;
+			Flags = GetFlags( pParams, pScript );
+			if ( !Flags )
+			{
+				Dbg_MsgAssert( 0,( "\n%s\nObj_FlagNotSet command requires a flag to be specified.\n(Either an integer or a name defined to be an integer)",pScript->GetScriptInfo()));
+				return ( false );
+			}
+			if ( mScriptFlags & ( Flags ) )
+			{
+				return false;
+			}
+			else
+			{
+				return true;
+			}
+			break;
+		}		
+		
+		// @script | Obj_ClearFlag | Object member function which clears the specified flag or flags.
+		// There are 3 ways of using it, it can clear just one flag, or an array of flags, 
+		// or all the flags.
+		// Example 1:
+		// Obj_ClearFlag JKU_FLAG_PED_START  // Set the flag to 0
+		// Example 2:
+		// Obj_ClearFlag [ JKU_FLAG_PED_START JKU_FLAG_PED_STOP ]  // Clears an array of flags
+		// Example 3:
+		// Obj_ClearFlag All  // Clears all the flags.
+		// @flag all | Clear all the flags
+		case 0x6c2b67f9: // Obj_ClearFlag
+		{
+			if (pParams->ContainsFlag(0xc4e78e22/*All*/))
+			{
+				mScriptFlags=0;
+				return true;
+			}	
+			uint32 Flags = 0;
+	
+			Flags = GetFlags( pParams, pScript );
+			if ( !Flags )
+			{
+				Dbg_MsgAssert( 0,( "\n%s\nObj_ClearFlag command requires a flag to be specified.\n(Either an integer or a name defined to be an integer)",pScript->GetScriptInfo()));
+				return ( false );
+			}
+			mScriptFlags &= ~Flags;
+			break;
+		}
+	
+		// @script | Obj_SetFlag | Sets the flag (or an array of flags) on an object.
+		// Same parameters as Obj_ClearFlag (except doesn't recognize the 
+		// 'all' parameter).
+		case 0xbe563426: // Obj_SetFlag
+		{
+			uint32 Flags = 0;
+			Flags = GetFlags( pParams, pScript );
+			if ( !Flags )
+			{
+				Dbg_MsgAssert( 0,( "\n%s\nObj_SetFlag command requires a flag to be specified.\n(Either an integer or a name defined to be an integer)",pScript->GetScriptInfo()));
+				return ( false );
+			}
+			mScriptFlags |= Flags;
+			break;
+		}
+
+		// @script | Obj_KillSpawnedScript | Kills a spawned script.  Can be passeed a name or id.
+		// @parm name | name | Name of the script you want to spawn (no quotes)
+		// @parm name | id | id of the script you want to spawn (no quotes)
+		case 0xfbd89cd5: // Obj_KillSpawnedScript
+		{
+			uint32 ScriptChecksum=0;
+			if (pParams->GetChecksum(CRCD(0xa1dc81f9,"Name"),&ScriptChecksum))
+			{
+				// Got a script name, so kill all spawned scripts that ran that script.
+				// BUT NOT THE SCRIPT CALLING THIS!
+				Script::KillSpawnedScriptsWithObjectAndName( this, ScriptChecksum, pScript );
+				return true;
+			}
+		
+			uint32 Id=0;										   
+			if (pParams->GetChecksum(CRCD(0x40c698af,"Id"),&Id))
+			{
+				// They specified an Id, so kill all spawned scripts with this Id,
+				// BUT NOT THE SCRIPT CALLING THIS!
+				Script::KillSpawnedScriptsWithObjectAndId( this, Id, pScript );
+				return ( true );
+			}
+			Dbg_MsgAssert( 0,( "\n%s\nMust specify Name or ID on Obj_KillSpawnedScript", pScript->GetScriptInfo() ));
+			return true;
+			break;
+		}
+	
+		// @script | Obj_SpawnScript | Causes the object to run a script, in parallel
+		// to whatever script is running on the object.
+		// @flag ScriptName | Name of the script you want to spawn (no quotes)
+		// @parmopt name | Params | {} | Any parameters you want to pass to the script being
+		// spawned.  Must surround params in { }
+		case 0x23a4e5c2: // Obj_SpawnScript
+		{
+			Script::CComponent* p_component = pParams->GetNextComponent();
+			if ( p_component && p_component->mType == ESYMBOLTYPE_NAME )
+			{
+				uint32 scriptChecksum = p_component->mChecksum;
+				
+				// The spawned script can optionally be given an Id, so that it can be deleted
+				// by KillSpawnedScript.
+				uint32 Id=0;
+				// keep the same ID as the parent if not specified...
+				Id = Script::FindSpawnedScriptID(pScript);
+				pParams->GetChecksum("Id",&Id);
+				Script::CScriptStructure *pScriptParams = NULL;
+				pParams->GetStructure( "Params", &pScriptParams );
+				#ifdef __NOPT_ASSERT__	
+				Script::CScript *p_script=SpawnScriptPlease( scriptChecksum, pScriptParams, Id, pParams->ContainsFlag(CRCD(0x8757d0bb, "PauseWithObject")) );
+				p_script->SetCommentString("Created by Obj_SpawnScript");
+				p_script->SetOriginatingScriptInfo(pScript->GetCurrentLineNumber(),pScript->mScriptChecksum);
+				#else
+				SpawnScriptPlease( scriptChecksum, pScriptParams, Id );
+				#endif
+
+			}
+			break;
+		}
+	
+		// @script | Obj_SwitchScript | Causes the object to replace the current script 
+		// attached to it with the script specified by ScriptName. 
+		// Can use the pass params just like Obj_SpawnScript.
+		// @flag ScriptName | Name of the script you want to spawn (no quotes)
+		case 0x714937c7: // Obj_SwitchScript
+		{
+			uint32 scriptChecksum;
+			if ( pParams->GetChecksum( NONAME, &scriptChecksum ) )
+			{
+				Script::CScriptStructure *pScriptParams = NULL;
+				pParams->GetStructure( "Params", &pScriptParams );
+				SwitchScript( scriptChecksum, pScriptParams );
+			}
+			break;
+		}
+		
+		
+		// case	CRCC(0x6df6caef,"SetProperties"):
+		case	CRCC(0x6c63c7c5,"SetProps"):
+		{
+			// Lowest level SetProperties, allowing ANY object to set event handlers
+			SetProperties(pParams);
+			break;
+		}
+		
+/*		
+		case	CRCC(0x1127430c, "ClearEventHandler"):
+		{
+			uint32 type;
+			pParams->GetChecksum(NO_NAME, &type, Script::ASSERT);
+			RemoveEventHandler(type);
+			break;
+		}
+			
+			
+		case	CRCC(0x8968da7f, "ClearEventHandlerGroup"):
+		{
+			uint32 group = CEventHandlerTable::vDEFAULT_GROUP;
+			pParams->GetChecksum(NO_NAME, &group);
+			RemoveEventHandlerGroup(group);
+			break;
+		}
+		
+// @script | OnExceptionRun | run the specified script on exception
+// @uparm name | script name to run
+// can be called without a parameter to clear it
+//		case 0x2c0c9e7b: // OnExceptionRun	
+		case CRCC(0x2c0c9e7b,"OnExceptionRun"):
+		{
+			uint32 OnExceptionScriptChecksum = 0;
+			pParams->GetChecksum( NONAME, &OnExceptionScriptChecksum);
+			#ifdef	__SCRIPT_EVENT_TABLE__		
+			if (mp_script)
+			{
+				mp_script->SetOnExceptionScriptChecksum(OnExceptionScriptChecksum);
+			}
+			#else
+			SetOnExceptionScriptChecksum(OnExceptionScriptChecksum);
+			#endif
+			break;
+		}
+
+		case CRCC(0xbefaa466,"OnExitRun"):
+		{
+			uint32 OnExitScriptChecksum = 0;
+			pParams->GetChecksum( NONAME, &OnExitScriptChecksum);
+			#ifdef	__SCRIPT_EVENT_TABLE__		
+			if (mp_script)
+			{
+				mp_script->SetOnExitScriptChecksum(OnExitScriptChecksum);
+			}
+			#else
+			SetOnExitScriptChecksum(OnExitScriptChecksum);
+			#endif
+			break;
+		}
+
+*/
+
+		case 0x20d125d7: // Obj_Visible
+			m_object_flags &= ~vINVISIBLE;
+			break;
+
+		case 0x3578e5a9: // Obj_Invisible
+			m_object_flags |= vINVISIBLE;
+			break;
+
+
+		
+		default:
+			Dbg_MsgAssert(0,("\n%s\nNo CObject member function found for '%s'",pScript->GetScriptInfo(),Script::FindChecksumName(Checksum)));
+
+			
+	}
+
+	return true;
+}
+
+
+
+uint32 CObject::GetFlags( Script::CScriptStructure *pParams, Script::CScript *pScript ) const
+{
+	
+	uint32 Flags = 0;
+	int Flag = 0;
+
+	// Scan through any array of flags specified.
+	Script::CArray *pArray=NULL;
+	pParams->GetArray(NONAME,&pArray);
+	if (pArray)
+	{
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			uint32 Checksum=pArray->GetNameChecksum(i);
+			int Flag=Script::GetInteger(Checksum);
+			Dbg_MsgAssert(Flag>=0 && Flag<32,("\n%s\nBad flag value %s=%d, value must be between 0 and 31",pScript->GetScriptInfo(),Script::FindChecksumName(Checksum),Flag));
+			Flags |= ( 1 << Flag );
+		}	
+	}
+	else if ( pParams->GetInteger( NONAME, &Flag ) )
+	{
+		Dbg_MsgAssert(Flag>=0 && Flag<32,("\n%s\nBad flag value of %d, value must be between 0 and 31",pScript->GetScriptInfo(),Flag));
+		Flags = ( 1 << Flag );
+	}
+	return Flags;
+}
+
+/*
+	If an event is targeted specifically to this CObject (using the same ID), it will be
+	passed through this function.
+*/
+bool CObject::PassTargetedEvent(CEvent *pEvent, bool broadcast)
+{
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	if (!mp_script)
+	{
+		return true;
+	}
+#endif
+	
+	Obj::CSmtPtr p = this;
+	
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	mp_script->PassTargetedEvent(pEvent, broadcast);
+#else	
+	if (mp_event_handler_table)
+		mp_event_handler_table->pass_event(pEvent, this, broadcast);
+#endif
+
+	return (p != NULL);
+}
+
+
+// Send myself an event.  Just a useful shortcut for a commonly done thing
+void CObject::SelfEvent(uint32 event, Script::CStruct* pParams)
+{
+	Obj::CTracker::Instance()->LaunchEvent(event,GetID(),GetID(),pParams); 
+}
+
+
+void CObject::BroadcastEvent(uint32 event, Script::CStruct *pData, float radius)
+{
+	// no target
+	// radius not implemented yet
+	Obj::CTracker::Instance()->LaunchEvent( event, 0xffffffff, GetID(), pData, true /*, radius */  );
+}
+
+
+
+/*
+	Has the same effect as deleting the CObject, except the actual deletion is deferred until next frame.
+	So that objects can be killed and not mess up list traversal
+*/
+void CObject::MarkAsDead( void )
+{
+	
+	// make sure we don't continue running anything on this script!
+	if ( mp_script )
+	{
+		mp_script->Block( );
+	}
+
+	// The following line will set the mp_object of any referring scripts to NULL
+	Script::StopAllScriptsUsingThisObject(this);  // Mick: we don't want any other scripts to continue running on a dead object 	
+	
+	Dbg_MsgAssert( mp_manager,( "No object manager associated with this object..." ));
+	// who locks an object?
+	SetLockOff();
+
+	
+	// mp_manager will be cleared by UnregisterObject()
+	CBaseManager *p_manager = mp_manager;
+	p_manager->UnregisterObject( *this );
+	p_manager->KillObject( *this );  // add object to kill list, to be purged next frame...	
+	m_object_flags |= ( vDEAD );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObject::allocate_tags_if_needed()
+{
+	if (!mp_tags) 
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		mp_tags = new Script::CStruct();
+		Mem::Manager::sHandle().PopContext();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObject::AllocateScriptIfNeeded()
+{
+	if (!mp_script) 
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+		mp_script = new Script::CScript();
+		mp_script->SetScript(CRCD(0x3f5cdb8a,"empty_script"),NULL,this);
+		Mem::Manager::sHandle().PopContext();
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObject::SwitchScript( uint32 scriptChecksum, Script::CScriptStructure *pParams )
+{
+	
+	if ( mp_script )
+	{
+		mp_script->SetScript( scriptChecksum, pParams, this );
+	}
+	else
+	{
+		mp_script = new Script::CScript;
+		mp_script->SetScript( scriptChecksum, pParams, this );
+		#ifdef __NOPT_ASSERT__
+		mp_script->SetCommentString("Created in CObject::SwitchScript(...)");
+		#endif
+	}
+	if ( !mp_script->GotScript( ) )
+	{
+		Dbg_MsgAssert( 0,( "Couldn't find script specified: %s", Script::FindChecksumName( scriptChecksum ) ));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CScript *CObject::SpawnScriptPlease( uint32 scriptChecksum, Script::CScriptStructure *pParams, int Id, bool pause_with_object )
+{
+	
+	Script::CScript *pScript;
+	pScript = Script::SpawnScript( scriptChecksum, pParams, 0, NULL, -1, Id, false, false, false, pause_with_object ); // K: The 0,NULL means no callback specified.
+	pScript->mpObject = this;
+	return pScript;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// spawn and immediatly run a script
+// script will be deleted after it terminates
+void CObject::SpawnAndRunScript ( uint32 ScriptChecksum, int node, bool net_script, bool permanent, Script::CStruct *p_params )
+{
+    // Send this event to other clients if applicable
+	if( net_script )
+	{
+		// Ken: TODO: Currently p_params are not being passed on in net games, fix if necessary ...
+		// Currently p_params is only used to store the contents of any ModelTriggerScriptParams
+		// that may be specified in an object's node and which are required to be passed on to
+		// any TriggerScript in it's local node array. (Used by a goal in London)
+		// (Note: The NULL below means a NULL p_object)
+		Script::SendSpawnScript( ScriptChecksum, NULL, node, permanent );
+	}
+
+	Script::CScript* pScript = Script::SpawnScript( ScriptChecksum, p_params, 0, NULL, node );
+	
+	#ifdef __NOPT_ASSERT__
+	pScript->SetCommentString( "Spawned by CObject::SpawnAndRun(...)" );
+	#endif
+	
+	pScript->mpObject = this; 
+	
+	pScript->Update(); 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObject::SpawnAndRunScript( const char *pScriptName, int node, bool net_script, bool permanent, Script::CStruct *p_params )
+{
+	SpawnAndRunScript(Script::GenerateCRC(pScriptName),node, net_script, permanent, p_params );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObject::CallScript( uint32 ScriptChecksum, Script::CStruct *pParams )
+{
+	if ( !mp_script )
+	{
+		mp_script = new Script::CScript;
+		
+		// K: Added this in case the script being called contains member functions, which
+		// it does when a two player trick-attack game is started.
+		mp_script->mpObject=this;
+	}
+
+	mp_script->Interrupt(ScriptChecksum, pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			DestroyIfUnlocked( Obj::CObject* obj, void* data )
+{
+	obj->DestroyIfUnlocked();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			SetLockOff( CObject* obj, void* data )
+{
+	obj->SetLockOff();
+}
+
+void			CObject::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+		// CObject stuff
+	Script::CStruct *p_scripts=new Script::CStruct;
+	Script::CScript *p_script=Script::GetNextScript(NULL);
+	while (p_script)
+	{
+		if (p_script->mpObject==this)
+		{
+			Script::CStruct *p_script_info=new Script::CStruct;
+			
+			#ifdef __NOPT_ASSERT__
+			// Convert to microseconds by dividing by 150
+			p_script_info->AddInteger("CPUTime",p_script->m_last_time/150);
+			#endif
+			
+			p_script_info->AddChecksum("m_unique_id",p_script->GetUniqueId());
+			if (p_script->mIsSpawned)
+			{
+				p_script_info->AddChecksum(NONAME,CRCD(0xf697fda7,"Spawned"));
+			}
+			
+			#ifdef	__SCRIPT_EVENT_TABLE__		
+				if (p_script->GetEventHandlerTable())
+				{
+					p_script->GetEventHandlerTable()->GetDebugInfo(p_script_info);
+				}
+			#endif
+			
+			
+			p_scripts->AddStructurePointer(p_script->GetBaseScript(),p_script_info);
+		}
+			
+		p_script=Script::GetNextScript(p_script);
+	}	
+	p_info->AddStructurePointer("Scripts",p_scripts);
+	
+	p_info->AddChecksum("m_id",m_id);
+	if (mp_tags)
+	{
+		p_info->AddStructure("mp_tags",mp_tags);
+	}
+	else
+	{
+		p_info->AddChecksum("mp_tags",CRCD(0xda3403b0,"NULL"));
+	}
+	
+#ifndef	__SCRIPT_EVENT_TABLE__		
+	if (mp_event_handler_table)
+	{
+		mp_event_handler_table->GetDebugInfo(p_info);
+	}
+#endif
+#endif				 
+
+}
+
+// Leftover from when this function was more complex
+// just retained for convenience
+CObject *ResolveToObject(uint32 id)
+{
+	return Obj::CTracker::Instance()->GetObject(id); 	
+}
+
+
+} // namespace Obj
+
diff --git a/Code/Gel/Object/objman.cpp b/Code/Gel/Object/objman.cpp
new file mode 100644
index 0000000..0855870
--- /dev/null
+++ b/Code/Gel/Object/objman.cpp
@@ -0,0 +1,459 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		objman.cpp												**
+**																			**
+**	Created:		05/27/99	-	mjb										**
+**																			**
+**	Description:	Object Manager											**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+//#define __DEBUG_OBJ_MAN__
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+const uint32 CBaseManager::vNO_GROUP = 0;
+const uint32 CBaseManager::vNO_OBJECT_ID		=	vUINT32_MAX;
+
+const sint CBaseManager::vNO_OBJECT_TYPE	=	0;
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+void	CGeneralManager::flush_dead_objects( const Tsk::Task< CGeneralManager >& task )
+{
+	 
+
+	CGeneralManager&		obj_manager = task.GetData();
+	
+	obj_manager.FlushDeadObjects();
+
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseManager::CBaseManager( void )
+: m_list_changed( 0x00000000 )
+{
+	m_momentary_removal = false;
+}
+
+
+
+
+/*
+	Adds the specified CObject to the manager, registers it with the CTracker singleton.
+	
+	If the CObject has no ID, it is automatically assigned one at this point.
+*/
+void CBaseManager::RegisterObject ( CObject& obj )
+{
+	CTracker* p_tracker = CTracker::Instance();
+	Dbg_MsgAssert(!obj.mp_manager, ("object already registered with manager"));
+
+   	obj.mp_manager = this;
+	m_object_list.AddNode ( &obj.m_node );
+	
+	// set all list changed bits
+	m_list_changed = 0xFFFFFFFF;
+	
+	if (obj.GetID() == vNO_OBJECT_ID)
+		obj.SetID(p_tracker->GetFreshId());
+
+	p_tracker->addObject(&obj);
+}
+
+
+
+
+/*
+	Removes the specified CObject to the manager, unregisters it from the CTracker singleton.
+	
+	After this, the CObject is no longer "in the system", and probably should be deleted. Its
+	ID will be available for usage by other CObjects again.
+*/
+void CBaseManager::UnregisterObject( CObject& obj )
+{
+	CTracker* p_tracker = CTracker::Instance();
+	Dbg_MsgAssert(obj.mp_manager == this, ("object not registered with this manager"));
+	Dbg_AssertType( &obj, CObject );
+
+	p_tracker->removeObject(&obj, m_new_id_of_object_being_momentarily_removed, m_momentary_removal);
+	
+	if ( obj.m_node.InList() )
+	{
+		obj.m_node.Remove();
+		obj.mp_manager = NULL;
+
+		// set all list changed bits
+		m_list_changed = 0xFFFFFFFF;
+	}
+}
+
+
+
+
+/*
+	Called by CObject::SetID() when a CObject's ID is changed. Shouldn't be called
+	from any other place.
+*/
+void CBaseManager::ReregisterObject(CObject& obj, uint32 newId)
+{
+	/* 
+		In the CTracker singleton, we are concerned with keeping:
+		-Aliases. They don't use object ID, just pointers
+		-Event listeners: don't use ID, so no worries
+		-Waiting script entries: scan through and change stored ID
+	*/
+	
+	m_new_id_of_object_being_momentarily_removed = newId;
+	m_momentary_removal = true;
+	UnregisterObject(obj);
+	obj.m_id = newId;
+	RegisterObject(obj);
+	m_momentary_removal = false;
+	
+}
+
+/*
+	Changes the priority of an object, then reregisters.
+*/
+void CBaseManager::SetObjectPriority ( CObject& obj, Lst::Node< CObject >::Priority priority )
+{
+	obj.m_node.SetPri(priority);
+	ReregisterObject(obj, obj.GetID());
+}
+
+
+
+
+
+
+/*
+	Deletes CObjects that have been killed by CObject::mark_as_dead() (which is called
+	by the "Die" member command). These CObjects will be "out of play" by this point, and
+	only need be deleted.
+*/
+void CGeneralManager::FlushDeadObjects()
+{
+	Lst::Head< CObject >*		head = &m_kill_list;
+	Lst::Node< CObject >*		next = head->GetNext();
+
+	while ( next )
+	{
+		Lst::Node< CObject >*	kill = next;
+		next = next->GetNext();
+		kill->Remove();
+		CObject *pObject = kill->GetData( );
+		#ifdef __NOPT_ASSERT__
+		// Lock Assert is not needed for objects that have been flagged for deletion  	
+		pObject->SetLockAssertOff();
+		#endif
+		delete pObject;
+	}	
+}
+
+
+CGeneralManager::CGeneralManager( void )
+{	
+	// add flush task
+	mp_kill_task = new Tsk::Task< CGeneralManager > ( flush_dead_objects, *this, Tsk::BaseTask::Node::vSYSTEM_TASK_PRIORITY_FLUSH_DEAD_OBJECTS );
+	Dbg_AssertType ( mp_kill_task, Tsk::Task< CGeneralManager > );
+
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	mlp_man->AddSystemTask ( *mp_kill_task ); 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeneralManager::~CGeneralManager( void )
+{
+	UnlockAllObjects();
+	DestroyAllObjects();
+		
+	Dbg_AssertType ( mp_kill_task, Tsk::Task< CGeneralManager > );
+	delete mp_kill_task;
+}
+
+
+
+
+/*
+	Runs the specified function on all CObjects of the given type. Deleting is allowed.
+*/
+void			CGeneralManager::ProcessAllObjectsOfType( sint type, Callback* process, void* data )
+{
+	Lst::Search< CObject >	sh;
+	
+	uint32 stamp_mask = m_stamp_bit_manager.RequestBit();
+	
+	// clear the appropriate stamp bit
+	for (CObject* pObject = sh.FirstItem(m_object_list); pObject; pObject = sh.NextItem())
+	{
+		pObject->ClearStampBit(stamp_mask);
+	}
+	
+	// traverse the object list
+	do
+	{
+		// clear the appropriate list changed bit
+		m_list_changed &= ~stamp_mask;
+		
+        CObject* pNextObject = sh.FirstItem(m_object_list);
+		
+		while (pNextObject)
+		{
+			CObject* pObject = pNextObject;
+			pNextObject = sh.NextItem();
+			
+			if (pObject->GetType() == type && !pObject->CheckStampBit(stamp_mask))
+			{
+				pObject->SetStampBit(stamp_mask);
+				
+				process(pObject, data);
+				
+				if (m_list_changed & stamp_mask) break;
+			}
+		}
+	}
+	while (m_list_changed & stamp_mask);
+	
+	m_stamp_bit_manager.ReturnBit(stamp_mask);
+}
+
+
+
+
+/*
+	Runs the specified function on all CObjects in manager. Deleting is allowed.
+*/
+void			CGeneralManager::ProcessAllObjects( Callback* process, void *data )
+{
+	Lst::Search< CObject >	sh;
+	
+	uint32 stamp_mask = m_stamp_bit_manager.RequestBit();
+	
+	// clear the appropriate stamp bit
+	for (CObject* pObject = sh.FirstItem(m_object_list); pObject; pObject = sh.NextItem())
+	{
+		pObject->ClearStampBit(stamp_mask);
+	}
+	
+	// traverse the object list
+	do
+	{
+		// clear the appropriate list changed bit
+		m_list_changed &= ~stamp_mask;
+		
+        CObject* pNextObject = sh.FirstItem(m_object_list);
+		
+		while (pNextObject)
+		{
+			CObject* pObject = pNextObject;
+			pNextObject = sh.NextItem();
+			
+			if (!pObject->CheckStampBit(stamp_mask))
+			{
+				pObject->SetStampBit(stamp_mask);
+				
+				process(pObject, data);
+				
+				if (m_list_changed & stamp_mask) break;
+			}
+		}
+	}
+	while (m_list_changed & stamp_mask);
+	
+	m_stamp_bit_manager.ReturnBit(stamp_mask);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+sint			CGeneralManager::CountObjectsOfType( sint type )
+{
+	
+
+	sint					count = 0;
+	CObject*					obj;
+	Lst::Search< CObject >	sh;
+
+
+	obj = sh.FirstItem( m_object_list );
+
+	while ( obj )
+	{
+		Dbg_AssertType( obj, CObject );
+
+		if (obj->GetType() == type)
+		{
+			count++;
+		}
+		obj = sh.NextItem();
+	}
+
+	return count;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeneralManager::AssertIfObjectsRemainApartFrom( sint *pApartFromThisType )
+{
+	
+
+	#ifdef __NOPT_ASSERT__
+	
+	CObject* pObj;
+	Lst::Search< CObject > sh;
+
+	bool GotRemainingObjects=false;
+	
+	pObj = sh.FirstItem( m_object_list );
+
+	while ( pObj )
+	{
+		Dbg_AssertType( pObj, CObject );
+
+		bool Match=false;
+		sint *pType=pApartFromThisType;
+		while (*pType!=-1)
+		{
+			if (pObj->GetType()==*pType)
+			{
+				Match=true;
+				break;
+			}	
+			++pType;
+		}	
+		
+		if ( !Match )
+		{
+			printf("Object type = %d\n",pObj->GetType());
+			GotRemainingObjects=true;
+		}
+		pObj = sh.NextItem();
+	}
+
+	Dbg_MsgAssert(!GotRemainingObjects,("Objects remain !!!"));
+	
+	#endif
+}
+
+
+
+
+/*
+	Called by CObject::mark_as_dead(). Shouldn't call from anywhere else.
+*/
+void		CGeneralManager::KillObject( CObject& obj )
+{	
+	Dbg_AssertType( &obj, CObject );
+	//Dbg_MsgAssert( obj.mp_node, ( "CObject does not have a node" ));
+	Dbg_MsgAssert( !obj.m_node.InList(),( "CObject has not been unregistered" ));
+	
+	m_kill_list.AddToHead( &obj.m_node );
+}
+
+
+
+
+/*
+	Returns a (currently) unused ID.
+*/
+uint32 CGeneralManager::NewObjectID( void )
+{
+	CTracker* p_tracker = CTracker::Instance();
+	return p_tracker->GetFreshId();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CObject*		CGeneralManager::GetObjectByID( uint32 id )
+{
+	CTracker* p_tracker = CTracker::Instance();
+	return p_tracker->GetObject(id);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
diff --git a/Code/Gel/Object/objsearch.cpp b/Code/Gel/Object/objsearch.cpp
new file mode 100644
index 0000000..723a172
--- /dev/null
+++ b/Code/Gel/Object/objsearch.cpp
@@ -0,0 +1,134 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		objsearch.cpp											**
+**																			**
+**	Created:		05/27/99	-	mjb										**
+**																			**
+**	Description:	Object search code										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+CObject*			Search::FindFirstObjectOfType( Lst::Head< CObject >& head, sint type )
+{
+	
+	
+	Dbg_AssertType( &head, Lst::Head< CObject > );
+
+	CObject*	obj = FirstItem( head );
+	
+	obj_type = type;
+
+	while ( obj )
+	{
+		Dbg_AssertType( obj, CObject );
+
+		if ( obj->GetType() == obj_type )
+		{
+			return obj;
+		}
+		
+		obj = NextItem();
+	}
+	
+	return obj;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CObject*			Search::FindNextObjectOfType( void )
+{
+	
+
+	CObject*	obj = NextItem();
+
+	while ( obj )
+	{
+		Dbg_AssertType( obj, CObject );
+	
+		if ( obj->GetType() == obj_type )
+		{
+			return obj;
+		}
+			
+		obj = NextItem();
+	}
+	
+	return obj;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+
diff --git a/Code/Gel/Object/objtrack.cpp b/Code/Gel/Object/objtrack.cpp
new file mode 100644
index 0000000..04094d7
--- /dev/null
+++ b/Code/Gel/Object/objtrack.cpp
@@ -0,0 +1,1249 @@
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+// start autoduck documentation
+// @DOC objtrack
+// @module objtrack | None
+// @subindex Scripting Database
+// @index script | bails
+
+namespace Obj
+{
+
+
+
+CEventLog::CEventLog()
+{
+	m_next_entry = 0;
+	m_wrapped = false;
+	
+	mp_event_type_hash_table = NULL;
+	m_event_depth = 0;
+	m_num_new_entries = 0;
+}
+
+
+
+CEventLog::~CEventLog()
+{
+	Dbg_Assert(mp_event_type_hash_table);
+	delete mp_event_type_hash_table;
+}
+
+
+
+void CEventLog::AddEntry(uint32 type, uint32 target, uint32 source, uint32 script, uint32 receiverID, EOccurenceType occurenceType)
+{
+	if (!mp_event_type_hash_table)
+	{
+		mp_event_type_hash_table = new Lst::HashTable(8);
+		Script::CArray *p_event_type_array = Script::GetArray(CRCD(0x8114f90,"event_type_array"), Script::ASSERT);
+		for (int i = 0; i < (int) p_event_type_array->GetSize(); i++)
+		{
+			strcpy(m_event_type_table[i].mName, p_event_type_array->GetString(i));
+			uint32 name_crc = Script::GenerateCRC(p_event_type_array->GetString(i));
+			mp_event_type_hash_table->PutItem(name_crc, &m_event_type_table[i]);
+		}
+	}
+			
+	int last_entry = m_next_entry - 1;
+	if (last_entry < 0)
+		last_entry = MAX_LOG_ENTRIES - 1;
+	
+	if (occurenceType == vUPDATE && m_table[last_entry].m_occurence_type == vUPDATE)
+	{
+		// no need to keep piling on update entries, just increase tick count
+		m_table[last_entry].m_tick_count++;
+	}
+	else
+	{
+		if (occurenceType == vHANDLED)
+			m_event_depth--;
+
+		Entry *p_entry = m_table + m_next_entry;
+		if (occurenceType == vUPDATE)
+			p_entry->m_tick_count		= 0;
+		else
+			p_entry->m_type 			= type;
+		p_entry->m_target 				= target;
+		p_entry->m_source 				= source;
+		p_entry->m_script 				= script;
+		p_entry->m_receiver_id			= receiverID;
+		p_entry->m_occurence_type		= occurenceType;
+		p_entry->m_depth				= m_event_depth;
+	
+		m_next_entry++;
+		if (m_next_entry == MAX_LOG_ENTRIES)
+		{
+			m_next_entry = 0;
+			m_wrapped = true;
+		}
+		
+		if (occurenceType == vLAUNCHED)
+			m_event_depth++;
+		
+		m_num_new_entries++;
+		
+		//PrintLatestEntry();
+	}
+}
+
+
+
+void CEventLog::Print(bool onlyPrintNewEntries, int maxEntriesToPrint)
+{
+	int index = 0;
+	int count = m_next_entry;
+	if (m_wrapped)
+	{
+		index = m_next_entry;
+		count = MAX_LOG_ENTRIES;
+	}
+
+	if (onlyPrintNewEntries)
+	{
+		maxEntriesToPrint = m_num_new_entries;
+	}
+	
+	printf("========================================================\n");
+	if (onlyPrintNewEntries)
+		printf("PRINTING EVENT LOG (ONLY NEW ENTRIES)\n\n");
+	else
+		printf("PRINTING EVENT LOG\n\n");
+	
+	while(count > 0)
+	{
+		if (count <= maxEntriesToPrint)		
+			print_entry(index);
+		index++;
+		if (index == MAX_LOG_ENTRIES) index = 0;
+		count--;
+	}
+	
+	printf("========================================================\n");
+
+	m_num_new_entries = 0;
+}
+
+
+
+void CEventLog::PrintLatestEntry()
+{
+	int last_entry = m_next_entry - 1;
+	if (last_entry < 0)
+		last_entry = MAX_LOG_ENTRIES - 1;
+	print_entry(last_entry);
+	m_num_new_entries = 0;
+}
+
+
+
+void CEventLog::print_entry(int index)
+{
+#ifdef __NOPT_ASSERT__
+	Entry *p_entry = m_table + index;
+
+	//printf("%d ", p_entry->m_depth);
+	for (int i = 0; i < p_entry->m_depth; i++) printf(" ");
+	
+	// entry format: (<...> denotes optional)
+	// "ACTION: type=TYPE    "
+	// or: "ACTION: type=TYPE   "
+	
+	if (p_entry->m_occurence_type == vLAUNCHED || p_entry->m_occurence_type == vHANDLED || p_entry->m_occurence_type == vREAD) 
+	{
+		if (p_entry->m_occurence_type == vLAUNCHED) 
+			printf("Launched event: type=%s ", get_type_name(p_entry->m_type));
+		else if (p_entry->m_occurence_type == vHANDLED) 
+		{
+			if (p_entry->m_receiver_id == CTracker::vID_OBJECT_TRACKER)
+				printf("Event expired: type=%s ", get_type_name(p_entry->m_type));
+			else
+				printf("Handled event: type=%s ", get_type_name(p_entry->m_type));
+		}
+		else if (p_entry->m_occurence_type == vREAD) 
+			printf("Read event: type=%s ", get_type_name(p_entry->m_type));
+		
+		bool reciever_equals_target = (p_entry->m_target == p_entry->m_receiver_id);
+
+		if (p_entry->m_target != CEvent::vSYSTEM_EVENT) 
+		{
+			const char *p_target_name;
+			p_target_name = Script::FindChecksumName(p_entry->m_target);
+			
+			if (reciever_equals_target)
+				printf("target/receiver=%s ", p_target_name);
+			else
+				printf("target=%s ", p_target_name);
+		}
+		if (!reciever_equals_target && p_entry->m_receiver_id != CTracker::vID_UNSPECIFIED_RECEIVER) 
+		{
+			const char *p_receiver_name;
+			if (p_entry->m_receiver_id == CTracker::vID_SCREEN_ELEMENT_MANAGER) 
+				p_receiver_name = "Screen Element Manager";
+			else if (p_entry->m_receiver_id == CTracker::vID_SUSPENDED_SCRIPT) 
+				p_receiver_name = "Suspended Script";
+			else if (p_entry->m_receiver_id == CTracker::vID_OBJECT_TRACKER) 
+				p_receiver_name = "(Killed By) Object Tracker";
+			else
+				p_receiver_name = Script::FindChecksumName(p_entry->m_target);
+			
+			printf("receiver=%s ", p_receiver_name);
+		}
+		if (p_entry->m_source != CEvent::vSYSTEM_EVENT) 
+			printf("source=%s ", Script::FindChecksumName(p_entry->m_source));
+		if (p_entry->m_script) 
+			printf("script=%s ", Script::FindChecksumName(p_entry->m_script));
+		printf("\n");
+	}
+	else if (p_entry->m_occurence_type == vOBJECT_ADD || p_entry->m_occurence_type == vOBJECT_REMOVE) 
+	{
+		if (p_entry->m_occurence_type == vOBJECT_ADD)
+			printf("Added object: %s", Script::FindChecksumName(p_entry->m_target));
+		else
+			printf("Removed object: %s", Script::FindChecksumName(p_entry->m_target));
+		printf("\n");
+	}
+	else if (p_entry->m_occurence_type == vUPDATE) 
+	{
+		printf("Tick, counts = %d\n", p_entry->m_tick_count);
+	}
+#endif  
+}
+
+
+
+const char *CEventLog::get_type_name(uint32 type)
+{
+	EventType *p_type_entry = mp_event_type_hash_table->GetItem(type);
+	if (p_type_entry)
+	{
+		return p_type_entry->mName;
+	}
+	else
+		return Script::FindChecksumName(type);
+}
+
+
+
+DefineSingletonClass( CTracker, "CObject Tracker" );
+
+
+
+/*
+	Every CObject that is registered with a CBaseManager should also be registered with CTracker.
+*/
+void CTracker::addObject(CObject *pObject)
+{
+	Dbg_MsgAssert(pObject->GetID() != 0xFFFFFFFF, ("CObject has no ID"));
+	Dbg_MsgAssert(!mp_hash_table->GetItem(pObject->GetID()), ("CObject with ID %s already in tracking system", Script::FindChecksumName(pObject->GetID())));
+	
+	// if object ID already being used as alias, remove the alias
+	CObject *p_alias_obj = mp_alias_table->GetItem(pObject->GetID());
+	if (p_alias_obj)
+	{
+		p_alias_obj->RemoveReference();
+		mp_alias_table->FlushItem(pObject->GetID());
+	}
+	
+	mp_hash_table->PutItem(pObject->GetID(), pObject);
+#ifdef __DEBUG_OBJ_MAN__
+	printf("*** Added object %s to global tracker\n", Script::FindChecksumName(pObject->GetID()));
+#endif
+
+	m_event_log.AddEntry(0, pObject->GetID(), CEvent::vSYSTEM_EVENT, 0, 0, CEventLog::vOBJECT_ADD);
+}
+
+
+
+
+/*
+	Ryan Old Comment: The 'newIdOfObjectBeingMomentarilyRemoved' parameter is set if we are just changing the ID of the object,
+	which requires removing it, then adding it again. Otherwise, this parameter will be zero (the id of the skater, fool!)
+	Mick New comment: now we have an additional boolean passed top indicate if the value in newIdOfObjectBeingMomentarilyRemoved
+	is valid, as it might be 0, if we are changing the id of a client skater back to 0
+*/
+
+void CTracker::removeObject(CObject *pObject, uint32 newIdOfObjectBeingMomentarilyRemoved, bool momentary_removal)
+{
+	mp_hash_table->FlushItem(pObject->GetID());
+	if (momentary_removal)
+	{
+		// go through all the scripts waiting on object, change the ID
+		for (int i = 0; i < vMAX_SCRIPT_ENTRIES; i++)
+		{
+			if (m_waiting_script_tab[i].mObjectId == pObject->GetID())
+			{
+				m_waiting_script_tab[i].mObjectId = newIdOfObjectBeingMomentarilyRemoved;
+			}
+		}
+	}
+	else
+	{
+		// only remove aliases to an object if it is going away "permanently"
+		remove_aliases(pObject);
+	}
+
+#ifdef __DEBUG_OBJ_MAN__
+	printf("*** Removed object %s from global tracker\n", Script::FindChecksumName(pObject->GetID()));
+#endif
+
+	m_event_log.AddEntry(0, pObject->GetID(), CEvent::vSYSTEM_EVENT, 0, 0, CEventLog::vOBJECT_REMOVE);
+}
+
+
+
+
+/*
+	Forwards the event to listeners associated with the object. Handy for forwarding global
+	events to specific recipients.
+	
+	If no object specified, event goes to all listeners.
+*/
+void CTracker::forward_event_to_listeners(CEvent *pEvent, CObject *pObject)
+{
+	Dbg_Assert(pEvent);
+	
+	// Try event on all registered listeners
+	// The ref counting stuff will fire an assert if an	event listener gets deleted
+	CEventListener *p_listener = mp_event_listener_list;
+	while (p_listener)
+	{
+		// prevents listener from being deleted (without an assert)
+		p_listener->m_ref_count++;
+		if (!pObject || p_listener->mp_object == pObject)
+			p_listener->event_filter(pEvent);
+		p_listener->m_ref_count--;
+		p_listener = p_listener->mp_next_listener;
+	}
+}
+
+
+
+
+/*
+	When a CObject is removed from tracking, all aliases that point to it must be removed.
+*/
+void CTracker::remove_aliases(CObject *pObject)
+{
+	while(1)
+	{
+		//if (mp_alias_table->GetSize() == 0)
+		//	break;
+		
+		mp_alias_table->IterateStart();
+		uint32 entry_key;
+		CObject *p_entry = mp_alias_table->IterateNext(&entry_key);
+		while(p_entry)
+		{
+			if (p_entry->GetID() == pObject->GetID())
+			{
+				// Found match. Remove it and repeat outer loop
+				p_entry->RemoveReference();
+				mp_alias_table->FlushItem(entry_key);
+				break;
+			}
+			p_entry = mp_alias_table->IterateNext(&entry_key);
+		}
+
+		if (!p_entry)
+			// no more matches left, we're done
+			break;
+	}
+}
+
+
+
+
+CTracker::CTracker()
+{
+	mp_hash_table = new Lst::HashTable(8);
+	mp_alias_table = new Lst::HashTable(4);
+	mp_event_receiver_table = new Lst::HashTable(8);	
+	
+	m_id_seed = 0;
+
+	mp_event_listener_list = NULL;
+	m_block_event_launching = false;
+	m_event_recurse_depth = 0;
+
+	m_next_event_script = 0;
+
+	for (int i = 0; i < vMAX_SCRIPT_ENTRIES; i++) 
+		m_waiting_script_tab[i].mEventType = vDEAD_SCRIPT_ENTRY;
+	
+	// XXX
+	m_debug = false;
+}
+
+
+
+
+CTracker::~CTracker()
+{
+	Dbg_MsgAssert(mp_hash_table->GetSize(), ("entries still in tracker"));
+	delete mp_hash_table;
+}
+
+
+
+
+/*
+	Returns a pointer to the CObject whose ID matches the one given, returns NULL if not found.
+	Will return the object if it has an alias, which is used in the front end for stuff like "MainMenu"
+	But is also now used for "Skater" and "Skater2"
+*/
+CObject *CTracker::GetObject(uint32 id)
+{
+	CObject *p_obj = mp_hash_table->GetItem(id);
+	if (!p_obj)
+	{
+		p_obj = mp_alias_table->GetItem(id);
+	}
+	return p_obj;
+}
+
+
+
+
+CObject *CTracker::GetObjectByAlias(uint32 aliasId)
+{
+	return mp_alias_table->GetItem(aliasId);
+}
+
+
+
+
+void CTracker::AddAlias(uint32 alias, CObject *pObject)
+{
+	// make sure alias not already being used for object ID
+	Dbg_MsgAssert(!mp_hash_table->GetItem(alias), ("CObject with ID %s already in tracking system", Script::FindChecksumName(alias)));
+	
+	// if desired alias already being used as alias, remove old one
+	CObject *p_alias_obj = mp_alias_table->GetItem(alias);
+	if (p_alias_obj)
+	{
+		p_alias_obj->RemoveReference();
+		mp_alias_table->FlushItem(alias);
+	}
+
+	pObject->AddReference();
+	mp_alias_table->PutItem(alias, pObject);
+}
+
+
+
+
+/*
+	Returns an ID that is not currently being used. Pretty high probability of not
+	colliding with a user-created ID.
+*/
+uint32 CTracker::GetFreshId()
+{
+	while(1)
+	{
+		char name_string[64];
+		sprintf(name_string, "autoid%d", m_id_seed++);
+		if (m_id_seed >= 1000000)
+			m_id_seed = 0;
+		uint32 id = Script::GenerateCRC(name_string);
+		if (!mp_hash_table->GetItem(id))
+		{
+			return id;
+		}
+	}
+}
+
+#ifdef	__NOPT_ASSERT__
+
+// Debug function to track down corruption in scripts event handler tables
+// we iterate over all the event receivers, and check that the tables that
+// they point to are valid
+
+void CTracker::ValidateReceivers()
+{
+	
+	mp_event_receiver_table->IterateStart();
+	uint32 entry_key;
+	CEventReceiverList *p_entry = mp_event_receiver_table->IterateNext(&entry_key);
+	while(p_entry)
+	{
+		p_entry = mp_event_receiver_table->IterateNext(&entry_key);
+		if (p_entry)
+		{
+				Lst::Node< Script::CScript >*p_node = p_entry->FirstItem();
+				while (p_node)
+				{
+					Script::CScript * p_script =  p_node->GetData();
+					// Got a pointer to a node, so first validate that
+					// then validate the event handler table
+					
+#ifndef __PLAT_WN32__	// These script functions are not necessary from PC tools
+
+					Obj::CEventHandlerTable * p_event_handler_table = p_script->GetEventHandlerTable();
+					// Validate the table object
+					Dbg_MsgAssert(Mem::Valid(p_event_handler_table),("Corrupt Event handler table object for event %s\n",Script::FindChecksumName(entry_key)));
+					// and the table it contains
+					Dbg_MsgAssert(!p_event_handler_table->m_num_entries || Mem::Valid(p_event_handler_table->mp_tab),("Corrupt Event handler table actual table for event %s\n",Script::FindChecksumName(entry_key)));
+
+#endif
+
+					p_node = p_node->GetNext();					
+				}
+
+			
+		}
+	
+	}
+}
+#endif
+
+
+// returns true if event was handled
+bool CTracker::LaunchEvent(uint32 type, uint32 target, uint32 source, Script::CStruct *pData, bool broadcast)
+{
+//	printf("launch event, type=%s, target=0x%x, source= 0x%x, pData = %p\n", Script::FindChecksumName(type),target,source, pData);
+	
+//	Dbg_MsgAssert(!broadcast,("Don't use the broadcast flag yet!!!"));
+	
+	Dbg_MsgAssert(!m_block_event_launching, ("event launches are blocked"));
+	Dbg_MsgAssert(m_event_recurse_depth < vMAX_EVENT_RECURSE_DEPTH, ("too many nested LaunchEvents -- check your scripts"));
+	//printf("### launch event depth %d\n", m_event_recurse_depth);
+	m_event_recurse_depth++;
+	
+	// log event
+	m_event_log.AddEntry(type, target, source, m_next_event_script, 0, CEventLog::vLAUNCHED);
+	m_next_event_script = 0;
+	
+	CEvent event;
+	event.m_type = type;
+	event.m_target_id = target;
+	event.m_source_id = source;
+	event.mp_data = pData;
+	
+	
+
+
+	
+	if (broadcast)
+	{	
+		#ifdef __SCRIPT_EVENT_TABLE__
+
+		// broadcast to each script that has an event handler table
+		CEventReceiverList *p_event_list = mp_event_receiver_table->GetItem(type);
+		if (p_event_list)
+		{
+			int scripts = p_event_list->CountItems();
+			Dbg_MsgAssert(scripts, ("Empty list for event %s",Script::FindChecksumName(type)));
+			if (scripts == 1)
+			{
+				// Just one object, so do a quicker sending of the event
+				Lst::Node< Script::CScript >*p_node =   p_event_list->FirstItem();
+				Script::CScript *p_script = p_node->GetData();
+				p_script->PassTargetedEvent(&event, broadcast);			
+			}
+			else
+			{
+				// For multiple scripts, we need to make a copy of the the list of scripts
+				// that will receive this. Otherwise chaos ensues, as scripts change their event handlers based on events. 
+				// We also use a list of smart pointers to the mp_reference in a script, so if one event handler deletes
+				// another object (and hence its script)
+				// then we can safely ignore that object (since it no longer exists)
+				// as the pointer will be set to NULL.			
+				
+				Script::CScript ** pp_scripts = new Script::CScript*[scripts];
+				CSmtPtr * pp_refs = new CSmtPtr[scripts];
+				
+				Lst::Node< Script::CScript >*p_node = p_event_list->FirstItem();
+				int n=0;
+				while (p_node)
+				{
+					pp_scripts[n] = p_node->GetData();
+					CRefCounted * p_ref = &(p_node->GetData()->m_reference_counter);
+					pp_refs[n] = p_ref;
+					
+					n++;
+					p_node = p_node->GetNext();					
+				}
+				for (n = 0; nPassTargetedEvent(&event, broadcast);			
+					}
+				}
+				delete [] pp_refs;	 
+				delete [] pp_scripts;
+				
+				
+			}
+			
+		}
+		
+		
+		#else
+		// broadcast to each object that has an event handler table
+		CEventReceiverList *p_event_list = mp_event_receiver_table->GetItem(type);
+		if (p_event_list)
+		{
+			int objects = p_event_list->CountItems();
+			Dbg_MsgAssert(objects, ("Empty list for event %s",Script::FindChecksumName(type)));
+			if (objects == 1)
+			{
+				// Just one object, so do a quicker sending of the event
+				Lst::Node< Obj::CObject >*p_node =   p_event_list->FirstItem();
+				Obj::CObject *p_obj = p_node->GetData();
+				p_obj->PassTargetedEvent(&event, broadcast);			
+			}
+			else
+			{
+				// For multiple objects, we need to make a copy of the the list of objects
+				// that will receive this. Otherwise chaos ensues, as objects change their event handlers based on events. 
+				// We use a list of smart pointers, so if one object handler deletes another object
+				// then we can safely ignore that object (since it no longer exists)
+				// as the pointer will be set to NULL.			
+				CObjectPtr * pp_objects = new CObjectPtr[objects];
+				Lst::Node< Obj::CObject >*p_node = p_event_list->FirstItem();
+				int n=0;
+				while (p_node)
+				{
+					pp_objects[n++] = p_node->GetData();
+					p_node = p_node->GetNext();
+				}
+				for (n = 0; nPassTargetedEvent(&event, broadcast);			
+					}
+				}
+				delete [] pp_objects;
+				
+				
+			}
+			
+		}
+		#endif
+	}
+	else
+	{
+		
+		
+		// try event on all registered listeners
+		forward_event_to_listeners(&event, NULL);
+	
+		// if event has a specific target, then send to that CObject
+		if (event.m_target_id != CEvent::vSYSTEM_EVENT && !event.WasHandled())
+		{
+			// look for a object with the target id
+			CObject *p_object = GetObject(event.m_target_id);
+			if (!p_object)
+			{
+				// or look for a script with the target id
+				Script::CScript *p_script = Script::FindSpawnedScriptWithID(event.m_target_id);
+				
+				if (!p_script)
+				{
+					m_event_log.Print(256);
+					#ifdef __NOPT_ASSERT__			
+					mp_hash_table->PrintContents();
+					#endif
+				}
+				
+				Dbg_MsgAssert(p_script, ("couldn't find object or script id %s", Script::FindChecksumName(event.m_target_id)));
+				
+				p_script->PassTargetedEvent(&event);
+			}
+			else
+			{
+				p_object->PassTargetedEvent(&event);
+			}
+			// p_object may have been deleted
+		}
+	}
+	
+	/* 
+		See if any scripts were waiting on the event.
+		
+		Unblock script if type matches AND:
+		-target matches
+		-OR script not associated with object
+		-OR a system event
+	*/
+	for (int i = 0; i < vMAX_SCRIPT_ENTRIES; i++)
+	{
+		//if (m_waiting_script_tab[i].mEventType == Script::GenerateCRC("showed_wait_message"))
+		//	Ryan("   hoorah: 0x%x 0x%x\n", m_waiting_script_tab[i].mObjectId, target);
+
+		if (m_waiting_script_tab[i].mEventType == type && 
+			(m_waiting_script_tab[i].mObjectId == target || m_waiting_script_tab[i].mObjectId == 0 || target == CEvent::vSYSTEM_EVENT || broadcast))
+		{			
+			// We found a suspended script of the correct type and target.
+			// Can remove entry.
+			m_waiting_script_tab[i].mEventType = vDEAD_SCRIPT_ENTRY;
+
+			// does the script actually exist right now?
+			Script::CScript *p_script = Script::GetScriptWithUniqueId(m_waiting_script_tab[i].mScriptId);
+			if (p_script)
+			{
+				p_script->UnBlock();
+				event.MarkHandled(vID_SUSPENDED_SCRIPT, p_script->mScriptChecksum);
+				//printf("unblocking script %s (id=%d)\n", Script::FindChecksumName(p_script->mScriptChecksum), m_waiting_script_tab[i].mScriptId);
+				// alright, the event is handled, so leave loop
+				break;
+			}
+			else
+			{
+				//printf("script to unblock doesn't exist (id=%d)\n", m_waiting_script_tab[i].mScriptId);
+			}
+
+			// the event is NOT handled
+		}
+	}
+
+	if (!event.WasHandled())
+		// no one handled event, mark as killed by object tracker
+		LogEventHandled(&event, vID_OBJECT_TRACKER);
+	
+	m_event_recurse_depth--;
+
+	return event.WasHandled();
+}
+
+
+
+
+// Call right before calling LaunchEvent()
+void CTracker::LogEventScript(uint32 script)
+{
+	m_next_event_script = script;
+}
+
+
+
+void CTracker::LogEventHandled(CEvent *pEvent, uint32 receiverID, uint32 script)
+{
+	// log event
+	m_event_log.AddEntry(pEvent->GetType(), pEvent->GetTarget(), pEvent->GetSource(), script, receiverID, CEventLog::vHANDLED);
+}
+
+
+
+void CTracker::LogEventRead(CEvent *pEvent, uint32 receiverID, uint32 script)
+{
+	// log event
+	m_event_log.AddEntry(pEvent->GetType(), pEvent->GetTarget(), pEvent->GetSource(), script, receiverID, CEventLog::vREAD);
+}
+
+
+
+void CTracker::LogTick()
+{
+	m_event_log.AddEntry(0, 0, 0, 0, 0, CEventLog::vUPDATE);
+}
+
+
+
+void CTracker::PrintEventLog(bool mostRecentOnly, int maxToPrint)
+{
+	m_event_log.Print(mostRecentOnly, maxToPrint);
+}
+
+
+
+void CTracker::RegisterEventListener(CEventListener *pListener)
+{
+	Dbg_MsgAssert(!pListener->m_registered, ("listener already registered"));
+
+	pListener->mp_next_listener = mp_event_listener_list;
+	mp_event_listener_list = pListener;
+	pListener->m_registered = true;
+}
+
+
+
+
+void CTracker::UnregisterEventListener(CEventListener *pListener)
+{
+	Dbg_MsgAssert(pListener->m_registered, ("listener not registered"));
+
+	CEventListener *p_prev = NULL;
+	CEventListener *p_current = mp_event_listener_list;
+	while(p_current)
+	{
+		if (p_current == pListener)
+		{
+			if (p_prev)
+				p_prev->mp_next_listener = pListener->mp_next_listener;
+			else
+				mp_event_listener_list = pListener->mp_next_listener;
+			break;
+		}
+		
+		p_prev = p_current;
+		p_current = p_current->mp_next_listener;
+	}
+	pListener->m_registered = false;
+}
+
+// Add this object to the list of objects that are listening for
+// events of this "type", so any broadcast event of this type will go directly there
+#ifdef	__SCRIPT_EVENT_TABLE__		
+void		CTracker::RegisterEventReceiver(uint32 type, Script::CScript *p_obj)
+#else
+void		CTracker::RegisterEventReceiver(uint32 type, CObject *p_obj)
+#endif
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+//	printf ("Registering Event %s from Object %s\n",Script::FindChecksumName(type), Script::FindChecksumName(p_obj->GetID()));
+// If there is no CEventList in the hash table for "type" then create one
+	CEventReceiverList *p_event_list = mp_event_receiver_table->GetItem(type);
+	if (!p_event_list)
+	{
+		p_event_list = new CEventReceiverList();
+		mp_event_receiver_table->PutItem(type,p_event_list); 
+	}
+	
+// Add this object to the that list, if it is not already added
+	p_event_list->RegisterEventReceiverObject(p_obj);
+	
+	Mem::Manager::sHandle().PopContext();		
+}
+
+
+
+// Remove this object from the list of objects that are listening for
+// events of this "type"
+#ifdef	__SCRIPT_EVENT_TABLE__		
+void		CTracker::UnregisterEventReceiver(uint32 type, Script::CScript *p_obj)
+#else
+void		CTracker::UnregisterEventReceiver(uint32 type, CObject *p_obj)
+#endif
+{
+//	printf ("Unregistering Event %s from Object %s\n",Script::FindChecksumName(type), Script::FindChecksumName(p_obj->GetID()));
+	// If there is a reciever list for this "type"
+	CEventReceiverList *p_event_list = mp_event_receiver_table->GetItem(type);
+	if (p_event_list)
+	{
+		// then remove the object from the CEventList
+		p_event_list->UnregisterEventReceiverObject(p_obj);
+	
+		// If the list is empty
+		if (p_event_list->IsEmpty())
+		{		
+			// Then remove it from the hash table
+			mp_event_receiver_table->FlushItem(type);
+			// and delete it
+			delete p_event_list; 
+		}
+	}
+}
+
+
+CEventReceiverList::CEventReceiverList()
+{
+
+}
+
+
+// Register an object with this event receiver list
+// all this does is add it to the list, so events of this "type" are sent to it
+// it will not be added to the same list twice (just ignores additional registers)
+	
+#ifdef	__SCRIPT_EVENT_TABLE__		
+void		CEventReceiverList::RegisterEventReceiverObject(Script::CScript *p_script)
+{
+	// See if the object is already in the list, and return if it is
+	Lst::Node * p_search = m_objects.FirstItem();
+	while (p_search)
+	{
+		if (p_search->GetData() == p_script)
+			return;
+		p_search = p_search->GetNext();
+	}
+	
+	// Create a new node for adding to the list
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	Lst::Node* p_node = new Lst::Node(p_script);
+	Mem::Manager::sHandle().PopContext();	
+	// Just add it to the tail of the list
+	m_objects.AddToTail(p_node);
+}
+#else
+
+void		CEventReceiverList::RegisterEventReceiverObject(CObject *p_obj)
+{
+	// See if the object is already in the list, and return if it is
+	Lst::Node * p_search = m_objects.FirstItem();
+	while (p_search)
+	{
+		if (p_search->GetData() == p_obj)
+			return;
+		p_search = p_search->GetNext();
+	}
+	
+	// Create a new node for adding to the list
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	Lst::Node* p_node = new Lst::Node(p_obj);
+	Mem::Manager::sHandle().PopContext();	
+	// Just add it to the tail of the list
+	m_objects.AddToTail(p_node);
+}
+#endif
+
+// remove an object from this list (if it is in it)
+// if it is not in the list, it will just be ignored
+#ifdef	__SCRIPT_EVENT_TABLE__		
+void		CEventReceiverList::UnregisterEventReceiverObject(Script::CScript *p_script)
+{
+	// Get the node from the list
+
+	Lst::Node* p_node = NULL;
+	Lst::Node * p_search = m_objects.FirstItem();
+	while (p_search)
+	{
+		if (p_search->GetData() == p_script)
+		{
+			p_node = p_search;
+			break;
+		}
+		p_search = p_search->GetNext();
+	}
+	if (p_node)
+	{
+		
+		// Just remove it from the list
+		p_node->Remove();
+		
+		// and delete the node (might be left with an empty list, but the tracker is responsible for cleaning it up)
+		delete p_node;
+	}	
+}
+#else
+
+void		CEventReceiverList::UnregisterEventReceiverObject(CObject *p_obj)
+{
+	// Get the node from the list
+
+	Lst::Node* p_node = NULL;
+	Lst::Node * p_search = m_objects.FirstItem();
+	while (p_search)
+	{
+		if (p_search->GetData() == p_obj)
+		{
+			p_node = p_search;
+			break;
+		}
+		p_search = p_search->GetNext();
+	}
+	if (p_node)
+	{
+		
+		// Just remove it from the list
+		p_node->Remove();
+		
+		// and delete the node (might be left with an empty list, but the tracker is responsible for cleaning it up)
+		delete p_node;
+	}	
+}
+#endif
+
+
+
+/*
+	Causes a script to be blocked until the (future) arrival of an event of the designated type.
+	If the script is associated with an object AND the event has a target, then the target
+	must be that object.
+*/
+void CTracker::SuspendScriptUntilEvent(Script::CScript *pScript, uint32 event_type)
+{
+	// find an unused entry
+
+	int unused_entry_index = -1;
+	int i = 0;
+	for (; i < vMAX_SCRIPT_ENTRIES; i++) 
+	{
+		if (m_waiting_script_tab[i].mEventType == vDEAD_SCRIPT_ENTRY)
+		{
+			unused_entry_index = i;
+			break;
+		}
+	}
+	
+	if (unused_entry_index < 0)
+	{
+		// hmm, no unused entries, so find entry whose script is dead
+		for (i = 0; i < vMAX_SCRIPT_ENTRIES; i++) 
+		{
+			Script::CScript *p_script = Script::GetScriptWithUniqueId(m_waiting_script_tab[i].mScriptId);
+			if (!p_script)
+			{
+				unused_entry_index = i;
+				break;
+			}
+		}
+	}
+
+	if (unused_entry_index >= 0)
+	{
+		//printf("suspending script %s (id=%d)\n", Script::FindChecksumName(pScript->mScriptChecksum), pScript->GetUniqueId());
+		m_waiting_script_tab[unused_entry_index].mScriptId = pScript->GetUniqueId();
+		if (pScript->mpObject)
+			m_waiting_script_tab[unused_entry_index].mObjectId = pScript->mpObject->GetID();
+		else
+			m_waiting_script_tab[unused_entry_index].mObjectId = 0;
+		m_waiting_script_tab[unused_entry_index].mEventType = event_type;
+
+		#ifdef __NOPT_ASSERT__
+		m_waiting_script_tab[unused_entry_index].mScriptName = pScript->mScriptChecksum;
+		#endif
+
+		// suspend the script until later notice
+		pScript->Block();
+	}
+	else
+	{
+		#ifdef __NOPT_ASSERT__
+		printf("out of script entries, scripts still waiting for events:\n");
+		for (i = 0; i < vMAX_SCRIPT_ENTRIES; i++) 
+		{
+			printf("   %s\n", Script::FindChecksumName(m_waiting_script_tab[i].mScriptName));
+		}
+		Dbg_MsgAssert(0, ("%s\nout of script entries (%d)in WaitForEvent",pScript->GetScriptInfo(),vMAX_SCRIPT_ENTRIES));
+		#endif
+	}
+}
+
+
+
+#ifndef __PLAT_WN32__	// These script functions are not necessary from PC tools
+
+// @script | LaunchEvent | 
+// @parm name | type | event type
+// @parm structure | data | 
+bool ScriptLaunchEvent(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Although events aren't necessarily tied to the Screen Element system, it is
+	// convenient to enable this function to support complex ID's, for when we
+	// are dealing with Screen Elements
+	Front::CScreenElementManager* pManager = Front::CScreenElementManager::Instance();
+	uint32 target = pManager->ResolveComplexID(pParams, CRCD(0xb990d003,"target"));
+	if (target)	
+	{
+		if (target == CRCD(0x36b2ee74, "system"))
+			target = CEvent::vSYSTEM_EVENT;			
+	}
+	else
+	{
+		if (pScript->mpObject)
+			target = pScript->mpObject->GetID();
+		else
+			target = CEvent::vSYSTEM_EVENT;
+	}
+	
+	uint32 source = pManager->ResolveComplexID(pParams, CRCD(0xa075808c,"source"));
+	if (source)	
+	{
+		if (source == CRCD(0x36b2ee74, "system"))
+			source = CEvent::vSYSTEM_EVENT;			
+	}
+	else
+	{
+		if (pScript->mpObject)
+			source = pScript->mpObject->GetID();
+		else
+			source = CEvent::vSYSTEM_EVENT;
+	}	
+		
+	Script::CStruct *pData = NULL;
+	pParams->GetStructure(CRCD(0x520c0c9c,"data"), &pData);
+	
+	bool broadcast = pParams->ContainsFlag(CRCD(0x640e830a,"broadcast"));
+	
+	CTracker* p_tracker = CTracker::Instance();	
+	
+	uint32 type;
+	if (pParams->GetChecksum(CRCD(0x7321a8d6,"type"), &type))
+	{
+		p_tracker->LogEventScript(pScript->mScriptChecksum);
+		p_tracker->LaunchEvent(type, target, source, pData, broadcast);
+	}
+	else
+	{
+		Script::CArray* pTypes;
+		if (pParams->GetArray(CRCD(0x7321a8d6,"type"), &pTypes))
+		{
+			Dbg_Assert(pTypes->GetType() == ESYMBOLTYPE_NAME)
+			unsigned num_events = pTypes->GetSize();
+			for (unsigned n = 0; n < num_events; n++)
+			{
+				p_tracker->LogEventScript(pScript->mScriptChecksum);
+				p_tracker->LaunchEvent(pTypes->GetChecksum(n), target, source, pData, broadcast);
+			}
+		}
+		else
+		{
+			Script::CStruct* pTypes;
+			if (pParams->GetStructure(CRCD(0x7321a8d6,"type"), &pTypes))
+			{
+				for (Script::CComponent* pComp = pTypes->GetNextComponent(); pComp; pComp = pTypes->GetNextComponent(pComp))
+				{
+					Dbg_Assert(pComp->mType == ESYMBOLTYPE_NAME);
+					p_tracker->LogEventScript(pScript->mScriptChecksum);
+					p_tracker->LaunchEvent(pComp->mChecksum, target, source, pData, broadcast);
+				}
+			}
+			else
+			{
+				Dbg_MsgAssert(0, ("can't launch event without type"));
+			}
+		}
+	}
+	
+	return true;
+}
+
+
+bool ScriptWaitForEvent(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 type;
+	if (!pParams->GetChecksum(CRCD(0x7321a8d6,"type"), &type))
+		Dbg_MsgAssert(0, ("no event type specified"));;
+	
+	CTracker* p_tracker = CTracker::Instance();	
+	p_tracker->SuspendScriptUntilEvent(pScript, type);
+
+	return true;
+}
+
+
+
+
+bool ScriptObjectExists(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Although events aren't necessarily tied to the Screen Element system, it is
+	// convenient to enable this function to support complex ID's, for when we
+	// are dealing with Screen Elements
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	uint32 id = p_manager->ResolveComplexID(pParams, CRCD(0x40c698af,"id"));
+	
+	CTracker* p_tracker = CTracker::Instance();
+	return (p_tracker->GetObject(id) != NULL);
+}
+
+
+
+
+bool ScriptTerminateObjectsScripts(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	uint32 id = p_manager->ResolveComplexID(pParams, CRCD(0x40c698af,"id"));
+	
+	CTracker* p_tracker = CTracker::Instance();
+	CObject *p_object = p_tracker->GetObject(id);
+	
+	
+	// Brad - the use_proper_version flag is a last minute fix at the end of THPS4.
+	// StopScriptsUsingThisObject is broken, but fixing it may break other things.  The
+	// proper version works as it always should have.  It is only used when the user 
+	// specifically requests it.
+	bool use_proper = pParams->ContainsFlag( CRCD(0xc89f3564,"use_proper_version") );
+	
+	// see if array of script names
+	Script::CArray *p_array;
+	if (pParams->GetArray(CRCD(0x22e168c1,"scripts"), &p_array))
+	{
+		for (uint i = 0; i < p_array->GetSize(); i++)
+		{
+			if ( use_proper )
+				Script::StopScriptsUsingThisObject_Proper(p_object, p_array->GetChecksum(i));
+			else
+				Script::StopScriptsUsingThisObject(p_object, p_array->GetChecksum(i));
+		}
+	}
+	else
+	{
+		uint32 script_to_stop = 0; // means 'stop all'
+		pParams->GetChecksum(CRCD(0x6166f3ad,"script_name"), &script_to_stop);
+		if ( use_proper )
+			Script::StopScriptsUsingThisObject_Proper(p_object, script_to_stop);
+		else
+			Script::StopScriptsUsingThisObject(p_object, script_to_stop);
+	}
+
+	return true;
+}
+
+
+
+
+bool ScriptAssignAlias(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	uint32 id_of_original = p_manager->ResolveComplexID(pParams, CRCD(0x40c698af,"id"));
+	
+	CTracker* p_tracker = CTracker::Instance();	
+	CObject *p_object_to_alias = p_tracker->GetObject(id_of_original);
+	Dbg_Assert(p_object_to_alias);
+	
+	uint32 alias;
+	pParams->GetChecksum(CRCD(0x1e93946b,"alias"), &alias, Script::ASSERT);
+
+	p_tracker->AddAlias(alias, p_object_to_alias);
+	
+	return true;
+}
+
+
+
+
+bool ScriptSetObjectProperties(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	uint32 id_of_original = p_manager->ResolveComplexID(pParams, CRCD(0x40c698af,"id"));
+	
+	CTracker* p_tracker = CTracker::Instance();	
+	CObject *p_object = p_tracker->GetObject(id_of_original);
+	Dbg_Assert(p_object);
+	
+	p_object->SetProperties(pParams);
+	
+	return true;
+}
+
+
+
+
+bool ScriptPrintEventLog(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool only_print_new = true;
+	
+	int num_to_print = CEventLog::MAX_LOG_ENTRIES;
+	if (pParams->GetInteger(NONAME, &num_to_print))
+		only_print_new = false;
+	
+	CTracker* p_tracker = CTracker::Instance();	
+	p_tracker->PrintEventLog(only_print_new, num_to_print);
+	return true;
+}
+
+
+#endif
+
+}
diff --git a/Code/Gel/Prefs/Prefs.cpp b/Code/Gel/Prefs/Prefs.cpp
new file mode 100644
index 0000000..dcefd66
--- /dev/null
+++ b/Code/Gel/Prefs/Prefs.cpp
@@ -0,0 +1,370 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate3													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		GameNet.cpp												**
+**																			**
+**	Created:		03/12/2001	-	gj										**
+**																			**
+**	Description:	Network Preferences 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Prefs
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Preferences::Preferences()
+{
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Preferences::~Preferences()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 Preferences::WriteToBuffer(uint8 *pBuffer, uint32 BufferSize)
+{
+	return Script::WriteToBuffer(&m_root, pBuffer, BufferSize);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Preferences::ReadFromBuffer(uint8 *pBuffer)
+{
+	Script::ReadFromBuffer(&m_root,pBuffer);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* Preferences::GetPreference( uint32 field_id )
+{
+	Script::CStruct* p_structure = NULL;
+	m_root.GetStructure( field_id, &p_structure );
+	return p_structure;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CArray* Preferences::GetPreferenceArray( uint32 field_id )
+{
+	Script::CArray* p_array = NULL;
+	m_root.GetArray( field_id, &p_array );
+
+	return p_array;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Preferences::RemoveComponent( uint32 field_id, uint32 sub_field_id )
+{
+	Script::CStruct* p_structure = NULL;
+	m_root.GetStructure( field_id, &p_structure );
+
+	p_structure->RemoveComponent( sub_field_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Preferences::SetRoot(Script::CStruct* pStuff)
+{
+	
+	// OK if pStuff is NULL, so no need to assert
+	// Probably still an error if it is NULL, but AppendStructure won't crash if it is.
+	
+	// Don't clear out the options structure. New stuff should just override old options
+	//m_root.Clear();
+	m_root.AppendStructure(pStuff); 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* Preferences::GetPreferenceString( uint32 field_id, uint32 sub_field_id )
+{
+	Script::CStruct* p_structure = NULL;
+	m_root.GetStructure( field_id, &p_structure );
+
+	const char* p_string;
+	p_structure->GetText( sub_field_id, &p_string, true );
+	return p_string;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Preferences::GetPreferenceValue( uint32 field_id, uint32 sub_field_id )
+{
+	Script::CStruct* p_structure = NULL;
+	m_root.GetStructure( field_id, &p_structure );
+
+	int returnVal;
+	p_structure->GetInteger( sub_field_id, &returnVal, true );
+	return returnVal;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 Preferences::GetPreferenceChecksum( uint32 field_id, uint32 sub_field_id )
+{
+	Script::CStruct* p_structure = NULL;
+	m_root.GetStructure( field_id, &p_structure, true );
+
+	uint32 returnVal;
+	p_structure->GetChecksum( sub_field_id, &returnVal, true );
+	return returnVal;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Preferences::SetPreference( uint32 field_id, Script::CStruct* p_to_append )
+{
+	
+	
+	Script::CStruct* p_structure = NULL;
+
+	// TODO:  Decide if we can add preferences at runtime,
+	// or whether all the categories already exist at load-time
+	if ( !m_root.GetStructure( field_id, &p_structure ) )
+	{
+		// structure doesn't already exist, so it's not a valid preference
+		Dbg_MsgAssert( 0,( "Trying to add an invalid preference" ));
+		return false;
+	}
+	
+	p_structure->AppendStructure( p_to_append );
+
+//	p_structure->PrintContents();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Preferences::SetPreference( uint32 field_id, Script::CArray* p_to_append )
+{
+	Script::CArray* p_array = NULL;
+
+	// TODO:  Decide if we can add preferences at runtime,
+	// or whether all the categories already exist at load-time
+	if ( !m_root.GetArray( field_id, &p_array ) )
+	{
+		// structure doesn't already exist, so it's not a valid preference
+		Dbg_MsgAssert( 0,( "Trying to add an invalid preference" ));
+		return false;
+	}
+	
+	m_root.AddArray( field_id, p_to_append );
+
+	//PrintContents();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Preferences::Load( uint32 structure_checksum )
+{
+	
+
+	// Don't clear out the options structure. New stuff should just override old options
+	//Reset();
+	
+	Script::CStruct* p_structure = Script::GetStructure( structure_checksum, Script::ASSERT );
+
+	m_root.AppendStructure( p_structure );
+	
+#if 0
+	uint8 testbuffer[2048];
+	m_root.WriteToBuffer(testbuffer, 2048);
+	Script::CStruct* p_new_structure = new Script::CStruct;
+	p_new_structure->Clear();
+	p_new_structure->ReadFromBuffer(testbuffer);
+	p_new_structure->PrintContents();
+	delete p_new_structure;
+#endif
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Preferences::Reset()
+{
+	m_root.Clear();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Preferences::UpdateUIElement( uint32 control_id, uint32 field_id, bool mask_password )
+{
+	
+	
+	Script::CStruct* pStructure = NULL;
+	m_root.GetStructure( field_id, &pStructure, true );
+
+	char buf[256];
+	const char* pText = NULL;
+	int value = 0;
+	if ( pStructure->GetText( "ui_string", &pText ) )
+	{
+		Dbg_Assert( strlen( pText ) < 256 );
+		strcpy( buf, pText );
+	}
+	else if ( pStructure->GetInteger( "value", &value ) )
+	{
+		sprintf( buf, "%d", value );
+	}
+	else if ( pStructure->GetText( NONAME, &pText, true ) )
+	{
+		Dbg_Assert( strlen( pText ) < 256 );
+		strcpy( buf, pText );
+	}
+	else
+	{
+		Dbg_MsgAssert( "Couldn't find valid parameters in %s",( Script::FindChecksumName( control_id ) ));
+	}
+
+	if ( mask_password )
+	{
+		// replace all the letters with stars
+		for ( uint32 i = 0; i < strlen(buf); i++ )
+		{
+			buf[i] = '*';
+		}
+	}
+	
+	//Front::SendString( control_id, buf );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Preferences::PrintContents()
+{
+#ifdef __NOPT_ASSERT__
+	Script::PrintContents(&m_root);
+#endif
+}
+
+} // namespace Prefs
+
+
+
+
diff --git a/Code/Gel/Prefs/Prefs.h b/Code/Gel/Prefs/Prefs.h
new file mode 100644
index 0000000..abe9863
--- /dev/null
+++ b/Code/Gel/Prefs/Prefs.h
@@ -0,0 +1,118 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate3													**
+**																			**
+**	Module:			Gel						 								**
+**																			**
+**	File name:		Prefs.h													**
+**																			**
+**	Created:		03/12/2001	-	gj										**
+**																			**
+**	Description:	Generic Preferences Class								**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_PREFS_H
+#define __GEL_PREFS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+//#include 
+
+#ifndef	__SCRIPTING_STRUCT_H
+#include 
+#endif
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Prefs
+{
+
+						
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Preferences  : public Spt::Class
+{
+	
+
+public:
+	Preferences();
+	virtual						~Preferences();
+
+public:
+	uint32						WriteToBuffer(uint8 *pBuffer, uint32 BufferSize);
+	void						ReadFromBuffer(uint8 *pBuffer);
+
+public:
+	bool						Reset();
+	bool						Load( uint32 structure_checksum );
+	void						PrintContents();
+	bool						UpdateUIElement( uint32 control_id, uint32 field_id, bool mask_password = false );
+
+public:
+	Script::CScriptStructure*	GetPreference( uint32 field_id );
+	bool						SetPreference( uint32 field_id, Script::CScriptStructure* p_to_append );
+	bool						SetPreference( uint32 field_id, Script::CArray* p_to_append );
+	Script::CArray*				GetPreferenceArray( uint32 field_id );
+	const char*					GetPreferenceString( uint32 field_id, uint32 sub_field_id );
+	int							GetPreferenceValue( uint32 field_id, uint32 sub_field_id );
+	uint32						GetPreferenceChecksum( uint32 field_id, uint32 sub_field_id );
+	void						RemoveComponent( uint32 field_id, uint32 sub_field_id );
+
+	Script::CScriptStructure*	GetRoot() {return &m_root;}
+	void						SetRoot(Script::CScriptStructure* pStuff);
+	
+private:
+	Script::CScriptStructure	m_root;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Prefs
+
+#endif	// __GEL_PREFS_H
+
+
diff --git a/Code/Gel/RefCounted.h b/Code/Gel/RefCounted.h
new file mode 100644
index 0000000..39e8c85
--- /dev/null
+++ b/Code/Gel/RefCounted.h
@@ -0,0 +1,32 @@
+#ifndef __GEL_REFCOUNTED_H
+#define __GEL_REFCOUNTED_H
+
+namespace Obj
+{
+
+template class CSmtPtr;
+
+
+
+
+class CRefCounted : public Spt::Class
+{
+public:
+							CRefCounted();
+	virtual					~CRefCounted();
+
+	void					AddSmartPointer(CSmtPtr *pSmtPtr);
+	void					RemoveSmartPointer(CSmtPtr *pSmtPtr);
+
+	void 					debug_validate_smart_pointers(CSmtPtr *pPtrToCheckForInclusion);
+
+protected:
+
+	CSmtPtr *	mp_smart_ptr_list;
+};
+
+
+
+
+}
+#endif
diff --git a/Code/Gel/Scripting/array.cpp b/Code/Gel/Scripting/array.cpp
new file mode 100644
index 0000000..f6e5ce1
--- /dev/null
+++ b/Code/Gel/Scripting/array.cpp
@@ -0,0 +1,559 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// array.cpp		KSH 22 Oct 2001
+//
+// CArray class member functions.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+
+DefinePoolableClass(Script::CArray);
+
+namespace Script
+{
+
+CArray::CArray()
+{
+	// Initialise everything. CArray is not derived from CClass so we don't get
+	// the auro-zeroing.
+	m_type=ESYMBOLTYPE_NONE;
+	mp_array_data=NULL;
+	m_size=0;
+}
+
+CArray::~CArray()
+{
+	Clear();
+}
+
+bool CArray::operator==( const CArray& v ) const
+{
+	// TODO ...
+	#ifdef __NOPT_ASSERT__
+	printf("CArray comparisons are not supported yet ... implement when needed\n");
+	#endif
+	return false;
+}
+
+bool CArray::operator!=( const CArray& v ) const
+{
+	return !(*this==v);
+}
+
+// Deletes the array buffer if it exists, asserting if it contains any non-NULL pointers.
+// Sets type to NONE and size to 0.
+void CArray::Clear()
+{
+	if (m_size==1)
+	{
+		// Memory optimization:
+		// Special case for size 1. In this case, no memory block has been allocated.
+		
+		if (m_union)
+		{
+			// The element is not zero ...
+			
+			#ifdef __NOPT_ASSERT__
+			// Check that no references to things remain in the array.
+			switch (m_type)
+			{
+				case ESYMBOLTYPE_INTEGER:
+				case ESYMBOLTYPE_FLOAT:
+				case ESYMBOLTYPE_NAME:
+					// No need for the user to have zeroed these.
+					break;
+					
+				case ESYMBOLTYPE_STRING:
+				case ESYMBOLTYPE_LOCALSTRING:
+				case ESYMBOLTYPE_PAIR:
+				case ESYMBOLTYPE_VECTOR:
+				case ESYMBOLTYPE_STRUCTURE:
+				case ESYMBOLTYPE_ARRAY:
+				{
+					// The array contains a pointer to something.
+					// The CArray cannot delete it itself because this would cause cyclic dependencies.
+					Dbg_MsgAssert(0,("Tried to delete a CArray that still contains non-NULL data: size=%d type='%s'",m_size,GetTypeName(m_type)));
+					break;
+				}	
+				
+				default:
+					Dbg_MsgAssert(0,("Bad CArray::m_type of '%s'",GetTypeName(m_type)));
+					break;
+			}		
+			#endif
+		
+			m_union=0;
+		}	
+	}
+	else
+	{
+		if (mp_array_data)
+		{
+			#ifdef __NOPT_ASSERT__
+			// Check that no references to things remain in the array.
+			switch (m_type)
+			{
+				case ESYMBOLTYPE_INTEGER:
+				case ESYMBOLTYPE_FLOAT:
+				case ESYMBOLTYPE_NAME:
+					// No need for the user to have zeroed these.
+					break;
+					
+				case ESYMBOLTYPE_STRING:
+				case ESYMBOLTYPE_LOCALSTRING:
+				case ESYMBOLTYPE_PAIR:
+				case ESYMBOLTYPE_VECTOR:
+				case ESYMBOLTYPE_STRUCTURE:
+				case ESYMBOLTYPE_ARRAY:
+				{
+					// The array is of pointers, so make sure that the user of CArray has deleted and zeroed these before deleting the array.
+					// The CArray cannot delete them itself because this would cause cyclic dependencies.
+					for (uint32 i=0; im_size,("Tried to resize CArray to a smaller size, not supported yet ..."));
+	// TODO: Make it able to make the CArray smaller, if a need arises. To do, factor out some of the
+	// code from CleanUpArray so that the leftover bit can be cleaned up.
+	Dbg_MsgAssert(newSize>1,("Resizing arrays to size 1 not supported yet ..."));
+	// TODO: Support the above if need be. Need to not allocate a new buffer in that case.
+	
+	// Allocate the new buffer.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	uint32 *p_new_buffer=(uint32*)Mem::Malloc(newSize*sizeof(uint32));
+	Mem::Manager::sHandle().PopContext();
+	
+	// Copy the contents of the old buffer into the new.
+	uint32 *p_source=GetArrayPointer();
+	// Note: Does not support resizing zero size arrays because it does not know what the type of the 
+	// new array should be.
+	Dbg_MsgAssert(p_source,("NULL array pointer ?"));
+	uint32 *p_dest=p_new_buffer;
+	uint32 i;
+	for (i=0; i 1)
+	{
+		Mem::Free(mp_array_data);
+	}	
+	mp_array_data=p_new_buffer;
+	
+	m_size=newSize;
+	Dbg_MsgAssert(m_size>1,("Expected array size to be > 1 ??")); // Just to be sure
+}
+
+uint32 *CArray::GetArrayPointer() const
+{
+	if (m_size==1)
+	{
+		return (uint32*)&m_union;
+	}
+	return mp_array_data;
+}
+
+void CArray::SetString(uint32 index, char *p_string)
+{
+	Dbg_MsgAssert(m_type==ESYMBOLTYPE_STRING,("Called CArray::SetString when m_type was '%s'",GetTypeName(m_type)));
+	Dbg_MsgAssert(index
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_SYMBOLTYPE_H
+#include  // For ESymbolType
+#endif
+
+namespace Script
+{
+
+class CVector;
+class CPair;
+class CStruct;
+
+#ifdef __PLAT_WN32__
+class CArray
+#else
+class CArray : public Mem::CPoolable
+#endif
+{
+	// Pointer to the array data.
+	union
+	{
+		// Generic pointer.
+		// Used when calling Mem::Free.
+		uint32 *mp_array_data;
+		
+		int *mp_integers;
+		float *mp_floats;
+		uint32 *mp_checksums;
+		
+		char **mpp_strings;
+		char **mpp_local_strings;
+		CPair **mpp_pairs;
+		CVector **mpp_vectors;
+		CStruct **mpp_structures;
+		CArray **mpp_arrays;
+		
+		// In the case of the array containing only 1 element, the element itself is
+		// stored here, rather than allocating a block of memory for it.
+		// This is a memory optimization.
+		// Each memory block uses 16 bytes for the header, and the data is padded to
+		// occupy 16 bytes. So in the case of an array of 1 element this saves 32 bytes.
+		// There are lots of arrays of 1 element, eg the links arrays in each node of 
+		// the NodeArray often only contain 1 link.
+		int m_integer;
+		float m_float;
+		uint32 m_checksum;
+		char *mp_string;
+		char *mp_local_string;
+		CPair *mp_pair;
+		CVector *mp_vector;
+		CStruct *mp_structure;
+		CArray *mp_array;
+		// Used to zero the single element.
+		uint32 m_union;
+	};
+
+	// The type of the things in the array.
+	ESymbolType m_type;
+	
+	// The number of items in the array.
+	uint32 m_size;
+
+public:
+	CArray();
+	~CArray();
+
+	// These cannot be defined because it would cause a cyclic dependency, because
+	// a CArray member function can't create things. So declare them but leave them undefined
+	// so that it will not link if they are attempted to be used.
+	CArray( const CArray& rhs );
+	CArray& operator=( const CArray& rhs );
+	
+	// This is used when interpreting switch statements.
+	bool operator==( const CArray& v ) const;
+	bool operator!=( const CArray& v ) const;
+	
+	void Clear();
+	void SetSizeAndType(int size, ESymbolType type);
+	void Resize(uint32 newSize);
+	
+	// TODO: Remove later. Only included for back compatibility.
+	void SetArrayType(int size, ESymbolType type) {SetSizeAndType(size,type);}
+	
+	void 	  SetString(uint32 index, char *p_string);
+	void SetLocalString(uint32 index, char *p_string);
+	void 	 SetInteger(uint32 index, int int_val);
+	void 	   SetFloat(uint32 index, float float_val);
+	void 	SetChecksum(uint32 index, uint32 checksum);
+	void 	  SetVector(uint32 index, CVector *p_vector);
+	void 		SetPair(uint32 index, CPair *p_pair);
+	void   SetStructure(uint32 index, CStruct *p_struct);
+	void 	   SetArray(uint32 index, CArray *p_array);
+
+	char 			*GetString(uint32 index) const;
+	char 	   *GetLocalString(uint32 index) const;
+	int 			GetInteger(uint32 index) const;
+	float 			  GetFloat(uint32 index) const;
+	uint32 		   GetChecksum(uint32 index) const;
+	CVector			*GetVector(uint32 index) const;
+	CPair 			  *GetPair(uint32 index) const;
+	CStruct 	 *GetStructure(uint32 index) const;
+	CArray			 *GetArray(uint32 index) const;
+
+	////////////////////////////////////////////////////////////////////////////////////
+	// TODO: Remove these later, only needed for back compatibility.
+	uint32	   GetNameChecksum(uint32 index) const {return GetChecksum(index);}
+	int 				GetInt(uint32 index) const {return GetInteger(index);}
+	////////////////////////////////////////////////////////////////////////////////////
+	
+	uint32 		GetSize() const {return m_size;};
+	ESymbolType GetType() const {return m_type;};
+	
+	// Needed by CleanUpArray and CopyArray in struct.cpp so that they can
+	// quickly scan through the array data without having to use the access functions
+	// to get each element.
+	uint32 *GetArrayPointer() const;
+};
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_ARRAY_H
diff --git a/Code/Gel/Scripting/checksum.cpp b/Code/Gel/Scripting/checksum.cpp
new file mode 100644
index 0000000..c641224
--- /dev/null
+++ b/Code/Gel/Scripting/checksum.cpp
@@ -0,0 +1,274 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// checksum.cpp		KSH 22 Oct 2001
+//
+// Checksum name lookup stuff for use by asserts.
+// Uses up a heck of a lot of memory, but only when __NOPT_ASSERT__ is defined.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef __PLAT_WN32__
+#ifdef __QDEBUG__
+#include <../tools/src/monitor/stdafx.h> // Needed for the message box
+#endif
+#endif
+
+#include 
+#include 	// For stricmp
+#include 
+#include 
+#include 
+
+// These are only referenced in the Playstation version.
+// They are set in NGPS.lk
+#ifdef __PLAT_NGPS__
+extern char _script_debugger_start[];
+extern char _script_start[];
+extern char _script_end[];
+#endif
+
+namespace Script
+{
+
+// The number of bits is set to be high to speed up AddChecksumName, so that
+// LoadQB does not take ages to execute.
+#define CHECKSUM_LOOKUP_HASH_BITS 16
+// This probably does not need to be so big, but we've got lots of memory in debug so it's OK.
+#define CHECKSUM_ENTRIES_PER_SLOT 10
+
+// There are (1<=hash_table_size+CHECKSUM_NAME_SPACE,(
+				  "Chunk of debug memory reserved for script stuff is not big enough.\nRequired=%d, got=%d",
+				  hash_table_size+CHECKSUM_NAME_SPACE, script_debug_mem_size));
+	#endif				  
+				  
+	sp_checksum_name_hash_table=(SChecksumName*)_script_start;
+	sp_checksum_names=(char*)(sp_checksum_name_hash_table+((1<mppStart=&sp_checksum_names;
+	p_checksum_name_info->mppEnd=&sp_end_of_checksum_names;
+	
+	#else
+
+	// Just allocate the arrays off the heap.
+	
+	// Checksum-name lookup stuff.
+	sp_checksum_name_hash_table=(SChecksumName*)Mem::Malloc((1<mpName)
+	{
+		// Already a name here.
+		// See if it has the same checksum.
+		if (p_first->mChecksum==checksum)
+		{
+			// It does! Check whether it is the same name.
+			if (stricmp(p_first->mpName,p_name)==0)
+			{
+				// Phew, the name matches. No need to do anything.
+				return;
+			}
+			else
+			{
+				// Oh bugger, a checksum clash.
+				#ifdef __PLAT_WN32__
+				#ifdef __QDEBUG__
+				char p_foo[1024];
+				sprintf(p_foo,"Checksum clash between %s and %s, both have checksum 0x%08x",p_name,p_first->mpName,checksum);
+				MessageBox(NULL,p_foo,"Warning",MB_OK);
+				#endif
+				// Carry on anyway, won't cause a crash.
+				#else				
+				Dbg_MsgAssert(0,("Checksum clash between %s and %s, both have checksum 0x%08x",p_name,p_first->mpName,checksum));
+				#endif
+				return;
+			}
+		}
+		
+		// Not the same checksum. Onto the next entry.
+		++c;
+		Dbg_MsgAssert(cmChecksum=checksum;
+	p_first->mpName=sp_end_of_checksum_names;
+	
+	int space_left=CHECKSUM_NAME_SPACE-(sp_end_of_checksum_names-sp_checksum_names);
+	while (*p_name)
+	{
+		Dbg_MsgAssert(space_left>0,("Need to increase CHECKSUM_NAME_SPACE in checksum.cpp"));
+		*sp_end_of_checksum_names++=*p_name++;
+		--space_left;
+	}	
+	Dbg_MsgAssert(space_left>0,("Need to increase CHECKSUM_NAME_SPACE in checksum.cpp"));
+	*sp_end_of_checksum_names++=0;
+}
+
+static char sp_unknown_checksum_buf[100];
+
+// Returns NULL if not found.
+// This version is not safe to use in a printf because it may return NULL, but can be handy for
+// when the calling code needs to do something special if the name is not found.
+const char *FindChecksumNameNULL(uint32 checksum)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return NULL;
+	}
+	
+	Dbg_MsgAssert(sp_checksum_name_hash_table,("NULL sp_checksum_name_hash_table"));
+	SChecksumName *p_first=&sp_checksum_name_hash_table[(checksum&((1<mpName)
+	{
+		if (p_first->mChecksum==checksum)
+		{
+			return p_first->mpName;
+		}	
+		--c;
+		if (c==0)
+		{
+			// Run out of slots.
+			break;
+		}	
+		++p_first;
+	}
+	
+	return NULL;		
+}
+
+// Returns "Unknown" if not found.
+// This version is always safe to use in a printf.
+const char *FindChecksumName(uint32 checksum)
+{
+	const char *p_name=FindChecksumNameNULL(checksum);
+	if (p_name)	
+	{
+		return p_name;
+	}
+	else
+	{
+		#ifdef __PLAT_WN32__
+		// The monitor.exe application requires that the unknown checksum be
+		// displayed like this.
+		sprintf(sp_unknown_checksum_buf,"0x%08x",checksum);	
+		#else
+		sprintf(sp_unknown_checksum_buf,"Unknown(0x%08x)",checksum);	
+		#endif
+		return sp_unknown_checksum_buf;		
+	}	
+}
+
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/checksum.h b/Code/Gel/Scripting/checksum.h
new file mode 100644
index 0000000..00881c1
--- /dev/null
+++ b/Code/Gel/Scripting/checksum.h
@@ -0,0 +1,21 @@
+#ifndef	__SCRIPTING_CHECKSUM_H
+#define	__SCRIPTING_CHECKSUM_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Script
+{
+
+void AllocateChecksumNameLookupTables();
+void DeallocateChecksumNameLookupTables();
+void GetChecksumNamesBuffer(char **pp_start, char **pp_end);
+void AddChecksumName(uint32 checksum, const char *pName);
+const char *FindChecksumNameNULL(uint32 checksum);
+const char *FindChecksumName(uint32 checksum);
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_CHECKSUM_H
+
diff --git a/Code/Gel/Scripting/component.cpp b/Code/Gel/Scripting/component.cpp
new file mode 100644
index 0000000..9d1a661
--- /dev/null
+++ b/Code/Gel/Scripting/component.cpp
@@ -0,0 +1,61 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// component.cpp		KSH 17 Oct 2001
+//
+// Notes:
+// When a CComponent is deleted, it does not delete any CStruct, CArray or string that
+// it may contain a pointer to. This is so that a cyclic dependency between component and struct is avoided.
+// It is up to the parent CStruct to clean these up before deleting the CComponent.
+// 
+// This cpp just declares the constructor, and the pool statics.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#include 
+#endif
+
+DefinePoolableClass(Script::CComponent);
+
+namespace Script
+{
+
+// Used in parkgen.cpp, but only in a debug message, so make it just return 0 for now.
+uint32 GetNumUsedComponents()
+{
+	return 0;
+}
+
+CComponent::CComponent()
+{
+	// Initialise everything. CComponent is not derived from CClass so we don't get
+	// the auro-zeroing.
+	mType=ESYMBOLTYPE_NONE;
+	mNameChecksum=NO_NAME;
+	mScriptSize=0;
+	mUnion=0;
+	mpNext=NULL;
+}
+
+// The destructor does not delete anything to avoid cyclic dependencies, so just assert
+// if it look like there could be a non-NULL pointer left.
+#ifdef __NOPT_ASSERT__
+CComponent::~CComponent()
+{
+	Dbg_MsgAssert(mUnion==0,("CComponent still contains data, possibly an undeleted pointer"));
+	Dbg_MsgAssert(mScriptSize==0,("CComponent::mScriptSize not zero in destructor ?"));
+}
+#endif
+
+// Note: No destructor.
+// When a CComponent is deleted, it does not delete any CStruct, CArray or string that
+// it may contain a pointer to.
+// This is so that a cyclic dependency between component and struct is avoided, because to
+// delete a CStruct from here would require the inclusion of struct.h.
+
+// It is up to the parent CStruct to delete anything in the component before deleting the CComponent.
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/component.h b/Code/Gel/Scripting/component.h
new file mode 100644
index 0000000..514415d
--- /dev/null
+++ b/Code/Gel/Scripting/component.h
@@ -0,0 +1,93 @@
+#ifndef	__SCRIPTING_COMPONENT_H
+#define	__SCRIPTING_COMPONENT_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_SYMBOLTYPE_H
+#include  // For ESymbolType
+#endif
+
+namespace Script
+{
+
+class CPair;
+class CVector;
+class CStruct;
+class CArray;
+
+// Note: This is not derived from CClass to avoid the extra memory overhead due to the virtual destructor.
+// There will be loads of CComponents, in THPS3 there were 58000.
+#ifdef __PLAT_WN32__
+class CComponent
+#else
+class CComponent : public Mem::CPoolable
+#endif
+{
+public:	
+	CComponent();
+	#ifdef __NOPT_ASSERT__
+	~CComponent();
+	#endif
+
+	// These cannot be defined because it would cause a cyclic dependency, because
+	// a CComponent member function can't create things. So declare them but leave them undefined
+	// so that it will not link if they are attempted to be used.
+	CComponent( const CComponent& rhs );
+	CComponent& operator=( const CComponent& rhs );
+
+	// These are used when interpreting switch statements and expressions
+	bool operator==( const CComponent& v ) const;
+	bool operator!=( const CComponent& v ) const;
+	bool operator<( const CComponent& v ) const;
+	bool operator>( const CComponent& v ) const;
+	bool operator<=( const CComponent& v ) const;
+	bool operator>=( const CComponent& v ) const;
+
+	// Making this use just one byte keeps the size of CComponent to 16 bytes.
+	// There are 64000 CComponents so they need to be as small as possible.
+	// It seems that the compact pool class has an overhead of one byte, so if the
+	// size of this class is 3 mod 4 it will be making optimal use of space.
+	// Hence we have 2 bytes spare if needed later.
+	uint8 mType;
+	
+	// Well, this has used up those 2 spare bytes mentioned above.
+	// This is the size of the mpScript buffer in the case of mType being ESYMBOLTYPE_QSCRIPT
+	// The size is needed when copying a component, so that the new component knows how big a
+	// buffer to allocate. In theory the size could be calculated, but that would introduce
+	// a circular dependency because struct.cpp would have to include parse.h in order to call
+	// SkipOverScript.
+	uint16 mScriptSize;
+	
+	uint32 mNameChecksum;	
+	union
+	{
+		int mIntegerValue;
+		float mFloatValue;
+		char *mpString;
+		char *mpLocalString;
+		CPair *mpPair;
+		CVector *mpVector;
+		CStruct *mpStructure;
+		CArray *mpArray;
+		uint8 *mpScript;
+		uint32 mChecksum;
+		uint32 mUnion; // For when all the above need to be zeroed 
+	};
+	
+	CComponent *mpNext;
+};
+	
+void AllocateComponentPool(uint32 maxComponents);
+void DeallocateComponentPool();
+	
+uint32 GetNumUsedComponents();
+	
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_COMPONENT_H
diff --git a/Code/Gel/Scripting/debugger.cpp b/Code/Gel/Scripting/debugger.cpp
new file mode 100644
index 0000000..b5fcd48
--- /dev/null
+++ b/Code/Gel/Scripting/debugger.cpp
@@ -0,0 +1,1628 @@
+#include "gel/scripting/debugger.h"
+#include "sk/gamenet/scriptdebugger.h"
+#include "gel/mainloop.h"
+#include "gel/scripting\symboltable.h"
+#include "gel/scripting\checksum.h"
+#include "gel/scripting\script.h"
+#include "gel/scripting\symboltype.h"
+#include "gel/scripting\array.h"
+#include "gel/scripting\struct.h"
+#include "gel/scripting\utils.h"
+#include "gel/scripting\component.h"
+#include "gel/scripting\scriptcache.h"
+#include "gel/object/compositeobjectmanager.h"
+#include "gel/components/modelcomponent.h"
+#include "gfx/nxviewman.h"
+#include "sk/modules/skate/skate.h"
+#include "sk/objects/skater.h"
+#include "sk/objects/skatercam.h"
+#include "gel/event.h"
+
+
+#ifdef __NOPT_ASSERT__
+
+
+namespace Dbg
+{
+
+uint32 gpDebugInfoBuffer[SCRIPT_DEBUGGER_BUFFER_SIZE/4];
+
+static Script::CScript *s_find_cscript_given_id(uint32 id)
+{
+	Script::CScript *p_script=Script::GetNextScript(NULL);
+	while (p_script)
+	{
+		if (p_script->GetUniqueId()==id)
+		{
+			return p_script;
+		}	
+		p_script=Script::GetNextScript(p_script);
+	}	
+	return NULL;
+}
+
+static Script::CScript *s_find_cscript_given_name(uint32 name)
+{
+	Script::CScript *p_script=Script::GetNextScript(NULL);
+	while (p_script)
+	{
+		if (p_script->RefersToScript(name))
+		{
+			return p_script;
+		}	
+		p_script=Script::GetNextScript(p_script);
+	}	
+	return NULL;
+}
+
+static Obj::CCompositeObject *s_find_nearest_object(int screen_x, int screen_y, float search_radius)
+{
+	Nx::CViewport *p_viewport = Nx::CViewportManager::sGetActiveViewport();
+	Dbg_MsgAssert(p_viewport, ("Can't find an active viewport"));
+	
+	Lst::Search sh;
+	Obj::CObject *p_ob = (Obj::CObject*) sh.FirstItem( Obj::CCompositeObjectManager::Instance()->GetRefObjectList() );
+	Obj::CCompositeObject *p_nearest_ob=NULL;
+	float min_d=1000000.0f;
+	while (p_ob)
+	{
+		float x,y;
+		Nx::ZBufferValue z;
+		p_viewport->TransformToScreenCoord(((Obj::CCompositeObject*)p_ob)->GetPos(),x,y,z);
+		
+		float dx=x-screen_x;
+		float dy=y-screen_y;
+		float d=sqrtf(dx*dx+dy*dy);
+		if (dGetComponent(CRCD(0x286a8d26,"Model"));
+		if (p_model)
+		{
+			Nx::CModel *p_cmodel=p_model->GetModel();
+			if (p_cmodel)
+			{
+				old_render_mode=p_cmodel->GetRenderMode();
+				p_cmodel->SetRenderMode(new_render_mode);
+			}
+		}
+	}
+	
+	return old_render_mode;
+}
+
+DefineSingletonClass( CScriptDebugger, "Script debugger module" );
+
+CScriptDebugger::CScriptDebugger()
+{
+	// Note: Split message id's start at 1 because 0 is used to indicate no message.
+	m_split_message_id=1;
+	
+	m_state=SCRIPT_DEBUGGER_STATE_IDLE;
+	mp_checksum_names_current_pos=NULL;
+	mp_checksum_names_end=NULL;
+	mp_server=NULL;
+	mp_logic_task=NULL;
+	
+	m_num_watched_scripts=0;
+	
+	m_mouse_on_screen=false;
+	m_got_valid_mouse_position=false;
+	m_mouse_x=0;
+	m_mouse_y=0;
+	mp_currently_selected_object=NULL;
+	m_original_object_render_mode=Nx::vNONE;
+	m_left_button_down=false;	
+	m_right_button_down=false;	
+
+	m_state_when_left_pressed.mTime=0;
+	m_state_when_left_pressed.mMouseX=0;
+	m_state_when_left_pressed.mMouseY=0;
+	m_state_when_left_pressed.mCamPhi=0.0f;
+	m_state_when_left_pressed.mCamTheta=0.0f;
+	
+	m_state_when_right_pressed.mCamRadius=0.0f;
+	m_state_when_right_pressed.mMouseX=0;
+	m_state_when_right_pressed.mMouseY=0;
+	
+	mp_viewed_object=NULL;
+	setup_default_camera_viewing_position();
+	
+	m_in_object_single_step_mode=false;
+	m_single_step_object_id=0;
+	
+	m_object_update_total_bytes_sent=0;
+	m_frames_since_last_object_update=0;
+}
+
+CScriptDebugger::~CScriptDebugger()
+{
+	if (mp_server)
+	{
+		delete mp_server;
+	}	
+	if (mp_logic_task)
+	{
+		delete mp_logic_task;
+	}	
+}
+
+void CScriptDebugger::v_start_cb ( void )
+{
+// Stick it all on the network heap, as it's kind of network releated
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+
+	Net::Manager * p_net_man = Net::Manager::Instance();
+	mp_server = p_net_man->CreateNewAppServer( 0, "Script debugger", 4, GameNet::vSCRIPT_DEBUG_PORT,
+												inet_addr( p_net_man->GetLocalIP()), Net::App::mACCEPT_FOREIGN_CONN | Net::App::mDYNAMIC_RESEND );
+	
+	// Note: If the default task priority is used then the 'view object' functionality does not work.
+	// s_logic_code needs to be called after all the normal camera code has executed, so that the camera
+	// position can be overridden. Hence a priority lower than the default needs to be used. 
+	mp_logic_task = new Tsk::Task< CScriptDebugger > ( CScriptDebugger::s_logic_code, *this, Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_SCRIPT_DEBUGGER );
+
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	mlp_manager->AddLogicTask( *mp_logic_task );
+	
+	mp_server->m_Dispatcher.AddHandler(	Net::MSG_ID_CONNECTION_REQ, 
+										s_handle_connection_request, 
+										Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+										
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_WATCH_THIS, 
+										s_handle_watch_this, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_WATCH_SCRIPT, 
+										s_handle_watch_script, 
+										Net::mHANDLE_LATE, this);
+										
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_STOP, 
+										s_handle_stop, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_STEP_INTO, 
+										s_handle_step_into, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_STEP_OVER, 
+										s_handle_step_over, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_GO, 
+										s_handle_go, 
+										Net::mHANDLE_LATE, this);
+
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_CLEAR_SCRIPT_WATCHES, 
+										s_handle_clear_script_watches, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_REFRESH, 
+										s_handle_refresh, 
+										Net::mHANDLE_LATE, this);
+										
+	mp_server->m_Dispatcher.AddHandler(	Net::MSG_ID_DISCONN_REQ, 
+										s_handle_disconnect, 
+										Net::mHANDLE_LATE, this);
+
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_SEND_CSCRIPT_LIST, 
+										s_handle_send_cscript_list, 
+										Net::mHANDLE_LATE, this);
+
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_SEND_WATCH_LIST, 
+										s_handle_send_watch_list, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_STOP_WATCHING_THIS_INDEX, 
+										s_handle_stop_watching_this_index, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_STOP_WATCHING_THIS_CSCRIPT, 
+										s_handle_stop_watching_this_cscript, 
+										Net::mHANDLE_LATE, this);
+
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_SEND_COMPOSITE_OBJECT_INFO, 
+										s_handle_send_composite_object_info, 
+										Net::mHANDLE_LATE, this);
+
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_MOUSE_POSITION, 
+										s_handle_mouse_position, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_MOUSE_ON_SCREEN, 
+										s_handle_mouse_on_screen, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_MOUSE_OFF_SCREEN, 
+										s_handle_mouse_off_screen, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_MOUSE_LEFT_BUTTON_DOWN, 
+										s_handle_mouse_left_button_down, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_MOUSE_LEFT_BUTTON_UP, 
+										s_handle_mouse_left_button_up, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_MOUSE_RIGHT_BUTTON_DOWN, 
+										s_handle_mouse_right_button_down, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_MOUSE_RIGHT_BUTTON_UP, 
+										s_handle_mouse_right_button_up, 
+										Net::mHANDLE_LATE, this);
+
+										
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_SEND_COMPOSITE_OBJECT_LIST, 
+										s_handle_send_composite_object_list, 
+										Net::mHANDLE_LATE, this);
+										
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_SET_OBJECT_UPDATE_MODE, 
+										s_handle_set_object_update_mode, 
+										Net::mHANDLE_LATE, this);
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_VIEW_OBJECT, 
+										s_handle_view_object, 
+										Net::mHANDLE_LATE, this);
+
+	mp_server->m_Dispatcher.AddHandler(	GameNet::vMSG_ID_DBG_SEND_SCRIPT_GLOBAL_INFO, 
+										s_handle_send_script_global_info, 
+										Net::mHANDLE_LATE, this);
+										
+	m_paused = false;
+	
+	Net::Manager* net_man = Net::Manager::Instance();
+	net_man->SetBandwidth( 200000 );
+
+	Mem::Manager::sHandle().PopContext();
+	
+}
+
+void CScriptDebugger::v_stop_cb ( void )
+{
+	mp_logic_task->Remove();
+	mp_server->m_Dispatcher.Deinit();
+}
+
+void CScriptDebugger::transmit_cscript_list()
+{
+	Script::CStruct *p_list=new Script::CStruct;
+
+	//int total_num_return_addresses=0;
+	//int total_scripts_using_big_buffer=0;
+	int num_scripts=0;
+	Script::CScript *p_script=Script::GetNextScript(NULL);
+	while (p_script)
+	{
+		//total_num_return_addresses+=p_script->GetNumReturnAddresses();
+		//if (p_script->UsingBigLoopBuffer())
+		//{
+		//	++total_scripts_using_big_buffer;
+		//}
+			
+		p_script=Script::GetNextScript(p_script);
+		++num_scripts;
+	}	
+	p_list->AddInteger(CRCD(0x62f3d8fa,"NumScripts"),num_scripts);
+	//p_list->AddInteger(CRCD(0xed4b5c6e,"NumScriptsUsingBigBuffer"),total_scripts_using_big_buffer);
+	//p_list->AddFloat(CRCD(0x4f73b852,"AverageNumReturnAddresses"),((float)total_num_return_addresses)/num_scripts);
+
+
+	// Add the script cache info.	
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	p_script_cache->GetDebugInfo(p_list);
+
+	p_script=Script::GetNextScript(NULL);
+	while (p_script)
+	{
+		Script::CStruct *p_script_info=new Script::CStruct;
+		
+		p_script_info->AddChecksum("m_unique_id",p_script->GetUniqueId());
+		
+		//p_script_info->AddInteger(CRCD(0x46b0d38c,"NumReturnAddresses"),p_script->GetNumReturnAddresses());
+		//p_script_info->AddInteger(CRCD(0x477f712b,"UsingBigCallstackBuffer"),p_script->UsingBigCallstackBuffer());
+		
+		Obj::CObject *p_object=p_script->mpObject;
+		if (p_object)
+		{
+			p_script_info->AddChecksum("Object",p_object->GetID());
+		}	
+		
+		if (p_script->mIsSpawned)
+		{
+			p_script_info->AddChecksum(NONAME,CRCD(0xf697fda7,"Spawned"));
+		}
+		
+		#ifdef	__SCRIPT_EVENT_TABLE__		
+			if (p_script->GetEventHandlerTable())
+			{
+				p_script->GetEventHandlerTable()->GetDebugInfo(p_script_info);
+			}
+		#endif
+
+		
+		// Convert to microseconds by dividing by 150
+		p_script_info->AddInteger("CPUTime",p_script->m_last_time/150);
+
+		// Create a new structure component for p_script_info, and name it using the name of the script.
+		// Then add this component to the p_list structure using AppendComponentPointer.
+		// The reason it is done this way rather than simply using AddStructurePointer is that
+		// AddStructurePointer will remove any previously added components of the same name.
+		// There may be many instances of the same script running, so we want those to all appear.
+		// AppendComponentPointer will add the new component to the structure and will leave any
+		// existing components untouched. (it was written specially for this function)
+		Script::CComponent *p_comp=new Script::CComponent;
+		p_comp->mType=ESYMBOLTYPE_STRUCTURE;
+		p_comp->mpStructure=p_script_info;
+		
+		uint32 base_script=p_script->GetBaseScript();
+		if (base_script)
+		{
+			p_comp->mNameChecksum=base_script;
+		}
+		else	
+		{
+			// Occasionally the base_script might be zero (sometimes when scrolling
+			// through certain menus)
+			// Name it using the unique id of the script.
+			p_comp->mNameChecksum=p_script->GetUniqueId();
+		}
+
+		p_list->AppendComponentPointer(p_comp);
+				
+		p_script=Script::GetNextScript(p_script);
+	}	
+
+	int structure_bytes_written=Script::WriteToBuffer(p_list,(uint8*)gpDebugInfoBuffer,SCRIPT_DEBUGGER_BUFFER_SIZE);
+	delete p_list;
+	
+	Net::MsgDesc msg;
+	msg.m_Data = gpDebugInfoBuffer;
+	msg.m_Length = structure_bytes_written;
+	msg.m_Id = GameNet::vMSG_ID_DBG_CSCRIPT_LIST;
+	StreamMessage(&msg);
+}
+
+void CScriptDebugger::transmit_watch_list()
+{
+	SWatchInfo *p_watch_info=(SWatchInfo*)gpDebugInfoBuffer;
+	Dbg_MsgAssert(m_num_watched_scripts*sizeof(SWatchInfo)<=SCRIPT_DEBUGGER_BUFFER_SIZE,("gpDebugInfoBuffer overflow"));
+
+	for (int i=0; imScriptName=mp_watched_scripts[i].mChecksum;
+		p_watch_info->mStopImmediately=mp_watched_scripts[i].mStopScriptImmediately;
+		++p_watch_info;
+	}		
+
+	Net::MsgDesc msg;
+	msg.m_Data = gpDebugInfoBuffer;
+	msg.m_Length = m_num_watched_scripts*sizeof(SWatchInfo);
+	msg.m_Id = GameNet::vMSG_ID_DBG_WATCH_LIST;
+	StreamMessage(&msg);
+}
+
+void CScriptDebugger::transmit_composite_object_list()
+{
+	Script::CStruct *p_list=new Script::CStruct;
+	
+	Lst::Search sh;
+	Obj::CObject *p_ob = (Obj::CObject*) sh.FirstItem( Obj::CCompositeObjectManager::Instance()->GetRefObjectList() );
+	while (p_ob)
+	{
+		Script::CStruct *p_object_info=new Script::CStruct;
+		p_object_info->AddInteger("CPUTime",((Obj::CCompositeObject*)p_ob)->GetUpdateTime());
+		if (p_ob->GetID())
+		{
+			p_list->AddStructurePointer(p_ob->GetID(),p_object_info);
+		}
+		else
+		{
+			p_list->AddStructurePointer("Skater",p_object_info);
+		}	
+		
+		p_ob = sh.NextItem();
+	}
+	
+	int structure_bytes_written=Script::WriteToBuffer(p_list,(uint8*)gpDebugInfoBuffer,SCRIPT_DEBUGGER_BUFFER_SIZE);
+	delete p_list;
+	
+	Net::MsgDesc msg;
+	msg.m_Data = gpDebugInfoBuffer;
+	msg.m_Length = structure_bytes_written;
+	msg.m_Id = GameNet::vMSG_ID_DBG_COMPOSITE_OBJECT_LIST;
+	Dbg::CScriptDebugger::Instance()->StreamMessage(&msg);
+}
+
+void CScriptDebugger::transmit_compression_lookup_table()
+{   
+	Script::CArray *p_table=Script::GetArray(0x35115a20/*WriteToBuffer_CompressionLookupTable_8*/);
+	Dbg_MsgAssert(p_table->GetType()==ESYMBOLTYPE_NAME,("Bad array type"));
+	Dbg_MsgAssert(p_table->GetSize()<=256,("Bad array size"));
+	
+    Net::MsgDesc msg;
+	msg.m_Data = p_table->GetArrayPointer();
+	msg.m_Length = p_table->GetSize()*4;
+	msg.m_Id = GameNet::vMSG_ID_DBG_COMPRESSION_LOOKUP_TABLE;
+	StreamMessage(&msg);
+
+	p_table=Script::GetArray(0x25231f42/*WriteToBuffer_CompressionLookupTable_16*/);
+	Dbg_MsgAssert(p_table->GetSize()==0,("Need to update transmit_compression_lookup_table to handle WriteToBuffer_CompressionLookupTable_16, since it is no longer empty ..."));
+}	
+
+SWatchedScript *CScriptDebugger::find_watched_script(uint32 checksum)
+{
+	for (int i=0; iWatchInDebugger(stopScriptImmediately);
+	}
+		
+	SWatchedScript *p_watched_script=find_watched_script(checksum);
+	if (p_watched_script)
+	{
+		p_watched_script->mStopScriptImmediately=stopScriptImmediately;
+		return;
+	}
+
+	Dbg_MsgAssert(m_num_watched_scriptsBeingWatchedInDebugger())
+		{
+			// DEFINITELY_TRANSMIT means override the optimization whereby a packet is not
+			// sent if it's checksum matches that of the last sent.
+			p_script->TransmitInfoToDebugger(Script::DEFINITELY_TRANSMIT);
+		}	
+		p_script=Script::GetNextScript(p_script);
+	}	
+}
+
+void CScriptDebugger::stop_watching_script(int index)
+{
+	if (index<0 || index >= m_num_watched_scripts)
+	{
+		return;
+	}	
+
+	if (m_num_watched_scripts)
+	{
+		for (int i=index; iGetDebugInfo(p_info);
+		
+		Dbg_MsgAssert(SCRIPT_DEBUGGER_BUFFER_SIZE>4,("Oops"));
+		gpDebugInfoBuffer[0]=p_obj->GetID();
+		uint8 *p_buf=(uint8*)(gpDebugInfoBuffer+1);
+		int structure_bytes_written=Script::WriteToBuffer(p_info,p_buf,SCRIPT_DEBUGGER_BUFFER_SIZE-4);
+		delete p_info;
+		
+		Net::MsgDesc msg;
+		msg.m_Data = gpDebugInfoBuffer;
+		msg.m_Length = 4+structure_bytes_written;
+		msg.m_Id = GameNet::vMSG_ID_DBG_COMPOSITE_OBJECT_INFO;
+
+		StreamMessage(&msg);
+		num_bytes_sent=msg.m_Length;
+		
+		printf("Sent composite object info for '%s'\n",Script::FindChecksumName(p_obj->GetID()));
+	}	
+	return num_bytes_sent;
+}
+
+void CScriptDebugger::send_composite_object_info(uint32 id)
+{
+	send_composite_object_info((Obj::CCompositeObject*)Obj::CCompositeObjectManager::Instance()->GetObjectByID( id ));
+}
+
+void CScriptDebugger::set_object_update_mode(SObjectUpdateMode *pObjectUpdateMode)
+{
+	// Make sure the game is not paused, which it will be if in single step mode.
+	Dbg::CScriptDebugger::Instance()->ScriptDebuggerUnpause();
+	
+	switch (pObjectUpdateMode->mMode)
+	{
+		case Dbg::EVERY_FRAME:
+		{
+			Obj::CCompositeObject* p_obj=(Obj::CCompositeObject*)Obj::CCompositeObjectManager::Instance()->GetObjectByID(pObjectUpdateMode->mObjectId);
+			if (p_obj)
+			{
+				p_obj->SetDebuggerEveryFrameFlag(pObjectUpdateMode->mOn);
+			}	
+			break;
+		}	
+		case Dbg::SINGLE_STEP:
+			if (pObjectUpdateMode->mOn)
+			{
+				// Set the single step mode flag to true, which means that the next time in to
+				// s_logic_code it will send a packet of info for this object, then pause the game.
+				// When paused, it will still respond to incoming messages, so the next time
+				// the user clicks the 'single step' button it will call this function, hence
+				// unpausing the game for one frame until it gets in to s_logic_code again, where
+				// it will send another packet of info, then pause again etc.
+				m_in_object_single_step_mode=true;
+				m_single_step_object_id=pObjectUpdateMode->mObjectId;
+			}	
+			else
+			{
+				m_in_object_single_step_mode=false;
+				m_single_step_object_id=0;
+			}
+			break;
+		default:	
+			break;
+	}	
+}
+
+void CScriptDebugger::view_object(SViewObject *pViewObject)
+{
+	Obj::CCompositeObject *p_obj=NULL;
+	
+	if (pViewObject->mDoViewObject)
+	{
+		p_obj=(Obj::CCompositeObject*)Obj::CCompositeObjectManager::Instance()->GetObjectByID(pViewObject->mObjectId);
+	}
+	mp_viewed_object=p_obj;
+	
+	setup_default_camera_viewing_position();
+}
+
+void CScriptDebugger::send_any_single_step_object_info()
+{
+	if (m_in_object_single_step_mode)
+	{
+		Obj::CCompositeObject* p_obj=(Obj::CCompositeObject*)Obj::CCompositeObjectManager::Instance()->GetObjectByID(m_single_step_object_id);
+		if (p_obj)
+		{
+			// Send a packet of info, then pause the game until the user clicks the 'single step'
+			// button again, which will cause CScriptDebugger::set_object_update_mode to be called.
+			send_composite_object_info(p_obj);
+			Dbg::CScriptDebugger::Instance()->ScriptDebuggerPause();
+		}
+	}	
+}
+
+void CScriptDebugger::send_script_global_info(uint32 id)
+{
+	// Look up the symbol to see if it is the name of some global.
+	Script::CSymbolTableEntry *p_entry=Script::LookUpSymbol(id);
+	if (p_entry)
+	{
+		int message_size=0;		
+		uint8 *p_buf=(uint8*)gpDebugInfoBuffer;
+
+		// Write in the name, type, and source file name.		
+		Dbg_MsgAssert(SCRIPT_DEBUGGER_BUFFER_SIZE > 8,("SCRIPT_DEBUGGER_BUFFER_SIZE is like way too small dude"));
+		*(uint32*)p_buf=id;
+		p_buf+=4;
+		*(uint32*)p_buf=p_entry->mType;
+		p_buf+=4;
+		message_size+=8;
+		
+		const char *p_source_filename=Script::FindChecksumName(p_entry->mSourceFileNameChecksum);
+		int len=strlen(p_source_filename)+1;
+		Dbg_MsgAssert(SCRIPT_DEBUGGER_BUFFER_SIZE-message_size > len,("SCRIPT_DEBUGGER_BUFFER_SIZE is too small"));
+		
+		strcpy((char*)p_buf,p_source_filename);
+		p_buf+=len;
+		message_size+=len;
+
+		// For pure data types, write in a structure containing the value.
+		switch (p_entry->mType)
+		{
+			case ESYMBOLTYPE_INTEGER:
+			case ESYMBOLTYPE_FLOAT:
+			case ESYMBOLTYPE_STRING:
+			case ESYMBOLTYPE_LOCALSTRING:
+			case ESYMBOLTYPE_PAIR:
+			case ESYMBOLTYPE_VECTOR:
+			case ESYMBOLTYPE_STRUCTURE:
+			case ESYMBOLTYPE_ARRAY:
+			case ESYMBOLTYPE_NAME:
+			{
+				// Copy the name and value into a structure for sending to the
+				// script debugger for display.
+				Script::CStruct *p_struct=new Script::CStruct;
+		
+				// Create a structure component and fill it in with the value of the global.
+				Script::CComponent *p_source=new Script::CComponent;
+				p_source->mType=p_entry->mType;
+				p_source->mUnion=p_entry->mUnion; // What mUnion represents depends on mType
+				
+				// Now use CopyComponent to make a copy. This needs to be done because mUnion may
+				// be a pointer to some entity, and we don't want that to be deleted when p_struct
+				// gets deleted.
+				Script::CComponent *p_new=new Script::CComponent;
+				Script::CopyComponent(p_new,p_source);
+				// Delete p_source now that we've got a copy.
+				p_source->mUnion=0;
+				delete p_source;
+				
+				// Name the component using id so that it appears as expected in the structure.
+				// Ie, if Foo is a global with value 6, then it will appear as Foo=6 in the structure.
+				p_new->mNameChecksum=id;
+				p_struct->AddComponent(p_new);
+		
+				
+				int structure_bytes_written=Script::WriteToBuffer(p_struct,p_buf,SCRIPT_DEBUGGER_BUFFER_SIZE-message_size);
+				delete p_struct;
+				
+				p_buf+=structure_bytes_written;
+				message_size+=structure_bytes_written;
+				break;
+			}	
+			default:	
+				break;
+		}			
+		
+		// Send the info to the debugger.
+		Net::MsgDesc msg;
+		msg.m_Data = gpDebugInfoBuffer;
+		msg.m_Length = message_size;
+		msg.m_Id = GameNet::vMSG_ID_DBG_SCRIPT_GLOBAL_INFO;
+		StreamMessage(&msg);
+	}	
+}
+
+void CScriptDebugger::stop_debugging()
+{
+	m_num_watched_scripts=0;
+	
+	Script::CScript *p_script=Script::GetNextScript(NULL);
+	while (p_script)
+	{
+		p_script->StopWatchingInDebugger();
+		p_script=Script::GetNextScript(p_script);
+	}	
+
+	stop_all_every_frame_object_updates();
+	
+	ScriptDebuggerUnpause();
+	
+	switch_off_mouse_cursor();
+}
+
+int	CScriptDebugger::s_handle_connection_request( Net::MsgHandlerContext* context )
+{   
+	Dbg_Assert( context );
+
+	Dbg_Printf( "Got Join Request from ip %x port %d\n", context->m_Conn->GetIP(), context->m_Conn->GetPort());
+
+	// We'll just accept local clients
+	if( context->m_Conn->IsRemote())
+	{
+		// Rule out redundancy
+		if( context->m_PacketFlags & Net::mHANDLE_FOREIGN )
+		{
+			Net::Conn* conn;
+
+			// Are we currently accepting players?
+			if( context->m_App->AcceptsForeignConnections() == false )
+			{
+				return Net::HANDLER_MSG_DONE;
+			}
+			
+			// Create a more permanent connection
+			conn = context->m_App->NewConnection( context->m_Conn->GetIP(), context->m_Conn->GetPort());
+			conn->SetBandwidthType( Net::Conn::vNARROWBAND );//Net::Conn::vLAN );
+			conn->SetSendInterval( 0 );
+			
+			context->m_Conn->Invalidate();
+			context->m_Conn = conn;	// the rest of the chain will use this new, valid connection
+		}
+	}
+
+
+	// Make sure that the debugger is updated with the latest info.
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->refresh();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+// This function is kind of redundant at the moment, since there is now a seperate s_handle_watch_script
+// and there will probably soon be a s_handle_watch_object.
+int	CScriptDebugger::s_handle_watch_this( Net::MsgHandlerContext* context )
+{   
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	
+	Dbg_MsgAssert(context->m_MsgLength==4,("Unexpected length of %d for vMSG_ID_DBG_WATCH_THIS", context->m_MsgLength));
+	
+	uint32 checksum=*(uint32*)context->m_Msg;
+
+	// Determine what kind of thing the checksum is referring to ...
+	
+	Script::CSymbolTableEntry *p_sym=Script::LookUpSymbol(checksum);
+	if (p_sym)
+	{
+		switch (p_sym->mType)
+		{
+			case ESYMBOLTYPE_QSCRIPT:
+			{
+				// false means do not stop the script immediately when detected.
+				p_this->add_to_watched_scripts(checksum,false);
+				break;
+			}	
+			default:
+				break;
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+int	CScriptDebugger::s_handle_watch_script( Net::MsgHandlerContext* context )
+{   
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	
+	Dbg_MsgAssert(context->m_MsgLength==12,("Unexpected length of %d for vMSG_ID_DBG_WATCH_SCRIPT", context->m_MsgLength));
+	
+	uint32 *p_data=(uint32*)context->m_Msg;   			
+	bool stop_script_immediately=p_data[0];
+	
+	uint32 type_of_checksum=p_data[1];
+	uint32 checksum=p_data[2];
+
+	if (type_of_checksum==CHECKSUM_IS_SCRIPT_ID)
+	{
+		Script::CScript *p_script=s_find_cscript_given_id(checksum);  
+		if (p_script)
+		{
+			p_script->WatchInDebugger(stop_script_immediately);
+			// Make sure the debugger at least gets some info, otherwise it won't get
+			// any if the scripts update function is not called.
+			p_script->TransmitInfoToDebugger();
+		}	
+		return Net::HANDLER_CONTINUE;
+	}
+
+	p_this->add_to_watched_scripts(checksum,stop_script_immediately);
+	
+	return Net::HANDLER_CONTINUE;	
+}
+
+int	CScriptDebugger::s_handle_stop( Net::MsgHandlerContext* context )
+{   
+	Dbg_MsgAssert(context->m_MsgLength==4,("Unexpected length of %d for vMSG_ID_DBG_STOP", context->m_MsgLength));
+	uint32 id=*(uint32*)context->m_Msg;
+	
+	Script::CScript *p_script=s_find_cscript_given_id(id);
+	if (!p_script)
+	{
+		return Net::HANDLER_CONTINUE;
+	}
+
+	p_script->DebugStop();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int	CScriptDebugger::s_handle_step_into( Net::MsgHandlerContext* context )
+{   
+	Dbg_MsgAssert(context->m_MsgLength==4,("Unexpected length of %d for vMSG_ID_DBG_STOP", context->m_MsgLength));
+	uint32 id=*(uint32*)context->m_Msg;
+
+	Script::CScript *p_script=s_find_cscript_given_id(id);
+	if (!p_script)
+	{
+		return Net::HANDLER_CONTINUE;
+	}
+	
+	p_script->DebugStepInto();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int	CScriptDebugger::s_handle_step_over( Net::MsgHandlerContext* context )
+{   
+	Dbg_MsgAssert(context->m_MsgLength==4,("Unexpected length of %d for vMSG_ID_DBG_STOP", context->m_MsgLength));
+	uint32 id=*(uint32*)context->m_Msg;
+
+	Script::CScript *p_script=s_find_cscript_given_id(id);
+	if (!p_script)
+	{
+		return Net::HANDLER_CONTINUE;
+	}
+	
+	p_script->DebugStepOver();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int	CScriptDebugger::s_handle_go( Net::MsgHandlerContext* context )
+{   
+	Dbg_MsgAssert(context->m_MsgLength==4,("Unexpected length of %d for vMSG_ID_DBG_STOP", context->m_MsgLength));
+	uint32 id=*(uint32*)context->m_Msg;
+
+	Script::CScript *p_script=s_find_cscript_given_id(id);
+	if (!p_script)
+	{
+		return Net::HANDLER_CONTINUE;
+	}
+	
+	p_script->DebugGo();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+// This gets called whenever the "Clear script watches" menu option in the script debugger
+// is chosen.
+int	CScriptDebugger::s_handle_clear_script_watches( Net::MsgHandlerContext* context )
+{   
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	
+	// Clear the list of scripts being watched.
+	p_this->m_num_watched_scripts=0;
+	// Make sure the list gets updated in the debugger.
+	p_this->transmit_watch_list();
+	
+	// Then run through all existing scripts making sure none of them are transmitting any more.
+	Script::CScript *p_script=Script::GetNextScript(NULL);
+	while (p_script)
+	{
+		p_script->StopWatchingInDebugger();
+		
+		// Also force any window open in the debugger for this script to close, by
+		// sending a script-died message.
+		uint32 id=p_script->GetUniqueId();
+		Net::MsgDesc msg;
+		msg.m_Data = &id;;
+		msg.m_Length = 4;
+		msg.m_Id = GameNet::vMSG_ID_DBG_SCRIPT_DIED;
+		p_this->StreamMessage(&msg);
+		
+		p_script=Script::GetNextScript(p_script);
+	}	
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int	CScriptDebugger::s_handle_refresh( Net::MsgHandlerContext* context )
+{   
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->refresh();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int	CScriptDebugger::s_handle_disconnect( Net::MsgHandlerContext* context )
+{   
+	Dbg_Printf("Got disconnect message from monitor.exe\n");
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->stop_debugging();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_send_cscript_list( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->transmit_cscript_list();
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_send_watch_list( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->transmit_watch_list();
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_send_composite_object_list( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->transmit_composite_object_list();
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_set_object_update_mode( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->set_object_update_mode((SObjectUpdateMode*)context->m_Msg);
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_view_object( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->view_object((SViewObject*)context->m_Msg);
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_stop_watching_this_index( Net::MsgHandlerContext* context )
+{
+	uint32 index=*(uint32*)context->m_Msg;
+
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->stop_watching_script(index);
+		
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_stop_watching_this_cscript( Net::MsgHandlerContext* context )
+{
+	uint32 id=*(uint32*)context->m_Msg;
+	
+	Script::CScript *p_script=s_find_cscript_given_id(id);
+	if (p_script)
+	{
+		p_script->StopWatchingInDebugger();
+	}	
+		
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_send_composite_object_info( Net::MsgHandlerContext* context )
+{
+	uint32 id=*(uint32*)context->m_Msg;
+	
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->send_composite_object_info(id);
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_send_script_global_info( Net::MsgHandlerContext* context )
+{
+	uint32 id=*(uint32*)context->m_Msg;
+	
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->send_script_global_info(id);
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_mouse_position( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	
+	// Ignore the message if the m_mouse_on_screen flag is not set, because that would
+	// mean it was a late message, received after the mouse had gone off screen.
+	if (p_this->m_mouse_on_screen)
+	{
+		sint16 *p_coords=(sint16*)context->m_Msg;
+		int x=p_coords[0];
+		int y=p_coords[1];
+	
+		p_this->m_mouse_x=x;	
+		p_this->m_mouse_y=y;	
+		p_this->m_got_valid_mouse_position=true;
+		
+		Script::CStruct *p_params=new Script::CStruct;
+		p_params->AddInteger("x",x);
+		p_params->AddInteger("y",y);
+		Script::RunScript("UpdateDebuggerMousePosition",p_params);
+		delete p_params;
+	}	
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_mouse_off_screen( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->switch_off_mouse_cursor();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_mouse_left_button_down( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->left_button_down();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_mouse_left_button_up( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->left_button_up();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_mouse_right_button_down( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->right_button_down();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_mouse_right_button_up( Net::MsgHandlerContext* context )
+{
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->m_right_button_down=false;
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+int CScriptDebugger::s_handle_mouse_on_screen( Net::MsgHandlerContext* context )
+{
+	// Set the m_mouse_on_screen flag so that mouse position messages are not ignored.
+	CScriptDebugger *p_this=(CScriptDebugger*)context->m_Data;
+	p_this->m_mouse_on_screen=true;
+	
+	// Initialise these flags to false so that the buttons have to be released first.
+	p_this->m_left_button_down=false;
+	p_this->m_right_button_down=false;
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+void CScriptDebugger::EnqueueMessage(Net::MsgDesc *p_message)
+{
+	if (mp_server)
+	{
+		mp_server->EnqueueMessage(Net::HANDLE_ID_BROADCAST,p_message);
+	}	
+}
+
+void CScriptDebugger::StreamMessage(Net::MsgDesc *p_message)
+{
+	if (mp_server)
+	{
+		mp_server->StreamMessage(	Net::HANDLE_ID_BROADCAST,
+									p_message->m_Id,
+									p_message->m_Length,
+									p_message->m_Data,
+									"",
+									GameNet::vSCRIPT_DEBUG_GROUP );
+	}									
+}
+
+void CScriptDebugger::send_sub_packet(Net::MsgDesc *p_message, uint32 size, char *p_source,
+									  int packetIndex, int totalNumPackets)
+{
+	if (!mp_server)
+	{
+		return;
+	}	
+	
+	Dbg_MsgAssert(size <= SPLIT_MESSAGE_MAX_SIZE,("Sub packet size too big"));
+	
+	static char sp_packet_data[sizeof(SSplitMessageHeader)+SPLIT_MESSAGE_MAX_SIZE];
+	SSplitMessageHeader *p_header=(SSplitMessageHeader*)sp_packet_data;
+		
+	p_header->mOriginalMessageId=p_message->m_Id;
+	p_header->mOriginalMessageSize=p_message->m_Length;
+	p_header->mThisMessageId=m_split_message_id;
+	p_header->mThisMessageIndex=packetIndex;
+	p_header->mThisMessageSize=size;
+	p_header->mTotalNumMessages=totalNumPackets;
+	
+	//printf("Id=%x, index=%d, total packets=%d\n",p_header->mThisMessageId,
+	//p_header->mThisMessageIndex,
+	//p_header->mTotalNumMessages);
+	
+	memcpy(p_header+1,p_source,size);
+	
+	Net::MsgDesc msg;
+	msg.m_Id = GameNet::vMSG_ID_DBG_PACKET;
+	msg.m_Data = sp_packet_data;
+	msg.m_Length = sizeof(SSplitMessageHeader)+size;
+	
+	printf("Transmitting sub packet, size=%d\n",size);
+	
+	mp_server->EnqueueMessage(Net::HANDLE_ID_BROADCAST,&msg);
+}
+
+// This can be used in place of EnqueueMessage or StreamMessage when the message
+// data is large, ie bigger than the maximum packet size that EnqueueMessage will allow,
+// which is about 1300 bytes.
+// The advantage of EnqueueMessage is that the messages arrive very quickly at the other end.
+// The disadvantage is that it asserts for packet sizes > 1300 bytes or so.
+// The advantage of StreamMessage is that it does allow for large packets.
+// The disadvantage is that the packets can take a long time to arrive, since it guarantees that they
+// arrive in the correct order, a feature that the script debugger does not always need.
+// This means that composite object info cannot be sent every frame, because the packets start piling
+// up at the PS2 side and it eventually asserts.
+
+// SplitAndEnqueueMessage tries to get the advantages of both Enqueue and Stream.
+// It allows for large packets because it splits them up into smaller packets and sends each of them
+// using EnqueueMessage.
+void CScriptDebugger::SplitAndEnqueueMessage(Net::MsgDesc *p_message)
+{
+	if (!mp_server)
+	{
+		return;
+	}	
+
+	printf("Splitting message of size %d\n",p_message->m_Length);
+	
+	int num_packets=p_message->m_Length / SPLIT_MESSAGE_MAX_SIZE;
+	int total_num_packets=num_packets;
+	int remainder=p_message->m_Length % SPLIT_MESSAGE_MAX_SIZE;
+	if (remainder)
+	{
+		++total_num_packets;
+	}
+	
+	char *p_source_data=(char*)p_message->m_Data;
+	for (int i=0; iGetReceiveDataTask().vCall();
+		mp_server->GetProcessDataTask().vCall();
+		mp_server->GetSendDataTask().vCall();
+		mp_server->GetNetworkMetricsTask().vCall();
+		while(( Tmr::GetTime() - start_loop_time ) < 17 );
+	}
+}
+
+void CScriptDebugger::ScriptDebuggerPause( void )
+{
+	//Dbg_Printf( "Pausing Game\n" );
+	process_net_tasks_loop();
+}
+
+void CScriptDebugger::ScriptDebuggerUnpause( void )
+{
+	//Dbg_Printf( "Unpausing Game\n" );
+	m_paused = false;
+}
+
+void CScriptDebugger::TransmitExceptionInfo(Obj::SException *p_exception, Obj::CCompositeObject* p_obj)
+{
+	#if 0
+	if (mp_server)
+	{
+		//SCRIPT_DEBUGGER_BUFFER_SIZE
+		gpDebugInfoBuffer[0]=(uint32)(p_obj->GetID());
+		gpDebugInfoBuffer[1]=p_exception->ExceptionNameChecksum;
+		gpDebugInfoBuffer[2]=p_exception->ScriptChecksum;
+		
+		Net::MsgDesc msg;
+		msg.m_Data = gpDebugInfoBuffer;
+		msg.m_Length = 3*4;
+		msg.m_Id = GameNet::vMSG_ID_DBG_EXCEPTION_INFO;
+		Dbg::CScriptDebugger::Instance()->EnqueueMessage(&msg);
+	}
+	#endif
+}
+
+// SPEEDOPT: This gets called by the CScript code whenever the script changes.
+// It may need to be optimized ... or maybe not.
+SWatchedScript *CScriptDebugger::GetScriptWatchInfo(uint32 checksum)
+{
+	for (int i=0; i sh;
+	for( Net::Conn *conn = mp_server->FirstConnection(&sh); conn; conn = mp_server->NextConnection(&sh) )
+	{
+		if (conn->GetNumResends() > 3)
+		{
+			conn->Invalidate();
+			mp_server->TerminateConnection( conn );
+			printf("Dropping PC connection due to exceeding 3 resends ...\n");
+		}
+	}
+}
+
+// A third of a second
+#define DOUBLE_CLICK_TIME_WINDOW (150000000/3)
+void CScriptDebugger::left_button_down()
+{
+	m_left_button_down=true;
+	
+	// If a double-click is done then automatically view the object.
+	Tmr::CPUCycles time=Tmr::GetTimeInCPUCycles();
+	if (time-m_state_when_left_pressed.mTime < DOUBLE_CLICK_TIME_WINDOW)
+	{
+		if (mp_currently_selected_object)
+		{
+			SViewObject v;
+			v.mDoViewObject=true;
+			v.mObjectId=mp_currently_selected_object->GetID();
+			view_object(&v);
+			
+			m_left_button_down=false;
+		}
+	}
+	m_state_when_left_pressed.mTime=time;
+	
+	m_state_when_left_pressed.mMouseX=m_mouse_x;
+	m_state_when_left_pressed.mMouseY=m_mouse_y;
+	m_state_when_left_pressed.mCamPhi=m_cam_phi;
+	m_state_when_left_pressed.mCamTheta=m_cam_theta;
+	
+	if (0)//mp_viewed_object)
+	{
+		// Mouse clicks do not send object info when viewing an object. Instead, holding the left button
+		// down allows the mouse to rotate the camera around the object.
+	}
+	else
+	{
+		// Run a script to make the mouse text do something so that we know the click has had an effect.
+		Script::RunScript("DoMouseClickEffect");
+		send_composite_object_info(mp_currently_selected_object);
+	}	
+}
+
+void CScriptDebugger::left_button_up()
+{
+	m_left_button_down=false;
+}
+
+void CScriptDebugger::right_button_down()
+{
+	m_right_button_down=true;
+	
+	m_state_when_right_pressed.mMouseX=m_mouse_x;
+	m_state_when_right_pressed.mMouseY=m_mouse_y;
+	m_state_when_right_pressed.mCamRadius=m_cam_radius;
+}
+
+enum
+{
+	MOUSE_PICK_SEARCH_RADIUS=70
+};
+	
+void CScriptDebugger::update_mouse_cursor()
+{
+	// The m_got_valid_mouse_position is to prevent glitches due to m_mouse_on_screen being
+	// set but no mouse position message received yet.
+	if (m_mouse_on_screen && m_got_valid_mouse_position)
+	{
+		Obj::CCompositeObject *p_ob=NULL;
+															
+		if (0)//mp_viewed_object)
+		{
+			// If we are viewing an object, then the mouse will be used to rotate the camera view
+			// around the object, so don't search for a new object cos the text will just get in the way.
+		}
+		else
+		{
+			p_ob=s_find_nearest_object(	m_mouse_x,
+										m_mouse_y,
+										MOUSE_PICK_SEARCH_RADIUS);
+		}
+		
+		if (p_ob != mp_currently_selected_object)
+		{
+			s_change_render_mode(mp_currently_selected_object,m_original_object_render_mode);
+			
+			mp_currently_selected_object=p_ob;
+			m_original_object_render_mode=s_change_render_mode(p_ob,Nx::vBBOX);
+		}
+		
+		if (mp_currently_selected_object)
+		{
+			Script::CStruct *p_struct=new Script::CStruct;
+			p_struct->AddInteger("x",m_mouse_x);
+			p_struct->AddInteger("y",m_mouse_y);
+			p_struct->AddString("text",Script::FindChecksumName(mp_currently_selected_object->GetID()));
+			Script::RunScript("SetMouseText",p_struct);
+			delete p_struct;
+		}	
+		else
+		{
+			Script::RunScript("DestroyMouseText");
+		}
+	}		
+}
+
+void CScriptDebugger::switch_off_mouse_cursor()
+{
+	// Destroy the mouse cursor screen elements, then set m_mouse_on_screen to false 
+	// so that any late mouse position messages get ignored.
+	Script::RunScript("DestroyMouseCursor");
+	m_mouse_on_screen=false;
+	m_got_valid_mouse_position=false;
+	// Reset these too so that there is no confusion.
+	m_left_button_down=false;
+	m_right_button_down=false;
+	
+	
+	if (mp_currently_selected_object)
+	{
+		s_change_render_mode(mp_currently_selected_object,m_original_object_render_mode);
+	}
+	mp_currently_selected_object=NULL;	
+	m_original_object_render_mode=Nx::vNONE;
+	
+}
+
+void CScriptDebugger::do_any_every_frame_object_updates()
+{
+	++m_frames_since_last_object_update;
+	
+	// Restrict the rate at which data is sent to be less than 200K per second, as a safeguard.
+	if (m_object_update_total_bytes_sent * 60 / m_frames_since_last_object_update > 200000)
+	{
+		return;
+	}
+		
+	m_object_update_total_bytes_sent=0;
+	m_frames_since_last_object_update=0;
+	
+	Lst::Search sh;
+	Obj::CCompositeObject *p_ob = (Obj::CCompositeObject*) sh.FirstItem( Obj::CCompositeObjectManager::Instance()->GetRefObjectList() );
+	while (p_ob)
+	{
+		if (p_ob->GetDebuggerEveryFrameFlag())
+		{
+			m_object_update_total_bytes_sent+=send_composite_object_info(p_ob);
+		}
+		p_ob = (Obj::CCompositeObject*)sh.NextItem();
+	}
+}
+
+void CScriptDebugger::stop_all_every_frame_object_updates()
+{
+	Lst::Search sh;
+	Obj::CCompositeObject *p_ob = (Obj::CCompositeObject*) sh.FirstItem( Obj::CCompositeObjectManager::Instance()->GetRefObjectList() );
+	while (p_ob)
+	{
+		p_ob->SetDebuggerEveryFrameFlag(false);
+		p_ob = (Obj::CCompositeObject*)sh.NextItem();
+	}
+}
+
+void CScriptDebugger::setup_default_camera_viewing_position()
+{
+	int type=SKATE_TYPE_UNDEFINED;
+	if (mp_viewed_object)
+	{
+		type=mp_viewed_object->GetType();
+	}
+		
+	switch (type)
+	{
+	case SKATE_TYPE_PED:
+	case SKATE_TYPE_SKATER:
+		m_cam_radius=100.0f;
+		m_cam_theta=1.107f;
+		m_cam_focus_offset=40;
+		break;
+	case SKATE_TYPE_CAR:
+		m_cam_radius=200.0f;
+		m_cam_theta=1.326f;
+		m_cam_focus_offset=40;
+		break;
+	default:
+		m_cam_radius=100.0f;
+		m_cam_theta=1.107f;
+		m_cam_focus_offset=0;
+		break;
+	}
+	m_cam_phi=0.0f;
+
+	m_state_when_left_pressed.mCamPhi=m_cam_phi;
+	m_state_when_left_pressed.mCamTheta=m_cam_theta;
+	m_state_when_right_pressed.mCamRadius=m_cam_radius;
+	
+	// If the viewed object has a bounding radius defined then add that to the initial camera radius
+	// too, so that the camera does not start off inside big things.
+	if (mp_viewed_object)
+	{
+		Obj::CModelComponent *p_model_component=(Obj::CModelComponent*)mp_viewed_object->GetComponent(CRCD(0x286a8d26,"Model"));
+		if (p_model_component)
+		{
+			Nx::CModel *p_model=p_model_component->GetModel();
+			if (p_model)
+			{
+				Mth::Vector r=p_model->GetBoundingSphere();
+				
+				// Apply any scaling, needed for the big alligator in the zoo.
+				Mth::Vector scale=p_model->GetScale();
+				float s=scale.GetX();
+				if (scale.GetY()>s) s=scale.GetY();
+				if (scale.GetZ()>s) s=scale.GetZ();
+				
+				m_cam_radius+=r.Length()*s;
+			}	
+		}
+	}	
+}
+
+// This will override the camera position and angles so as to view the mp_viewed_object.
+void CScriptDebugger::update_camera()
+{
+	// Note: mp_viewed_object is a smart pointer, so will become NULL if the object dies.
+	if (!mp_viewed_object)
+	{
+		// Nothing to see
+		return;
+	}	
+
+	// Get the skater's camera.
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	Obj::CSkater *p_skater = p_skate_mod->GetSkater(0);
+	if (!p_skater)
+	{
+		// No skater
+		return;
+	}
+		
+	Gfx::Camera *p_skater_cam=p_skater->GetActiveCamera();
+	if (!p_skater_cam)
+	{
+		// No skater camera
+		return;
+	}	
+
+
+	if (m_mouse_on_screen && m_got_valid_mouse_position)
+	{
+		if (m_left_button_down)
+		{
+			m_cam_theta=m_state_when_left_pressed.mCamTheta+Script::GetFloat("MouseRotationUpDownFactor")*(m_mouse_y-m_state_when_left_pressed.mMouseY);
+			
+			if (m_cam_theta > Mth::PI-0.01f)
+			{
+				m_cam_theta = Mth::PI-0.01f;
+			}	
+			else if (m_cam_theta < 0.01f)
+			{
+				m_cam_theta = 0.01f;
+			}	
+			
+			m_cam_phi=m_state_when_left_pressed.mCamPhi+Script::GetFloat("MouseRotationLeftRightFactor")*(m_mouse_x-m_state_when_left_pressed.mMouseX);
+			if (m_cam_phi > Mth::PI*2.0f)
+			{
+				m_cam_phi -= Mth::PI*2.0f;
+			}
+			else if (m_cam_phi < -Mth::PI*2.0f)
+			{
+				m_cam_phi += Mth::PI*2.0f;
+			}
+		}
+			
+		if (m_right_button_down)
+		{
+			m_cam_radius=m_state_when_right_pressed.mCamRadius+Script::GetFloat("MouseZoomFactor")*(m_mouse_y-m_state_when_right_pressed.mMouseY);
+			
+			if (m_cam_radius < 0.01f)
+			{
+				m_cam_radius=0.01f;
+			}	
+		}
+	}
+
+	Mth::Vector cam_radius_vector;
+	cam_radius_vector[Y]=cosf(m_cam_theta);
+	float s=sinf(m_cam_theta);
+	cam_radius_vector[X]=s*cosf(m_cam_phi);
+	cam_radius_vector[Z]=s*sinf(m_cam_phi);
+	cam_radius_vector.Normalize(); // Just to be sure
+	cam_radius_vector *= m_cam_radius;
+
+	// Rotate using the object's matrix so that the camera's frame of
+	// reference is that of the object, otherwise the camera will not turn with the object.
+	cam_radius_vector *= mp_viewed_object->GetDisplayMatrix();
+	
+	Mth::Vector focus_pos=mp_viewed_object->GetPos();
+	focus_pos[Y]+=m_cam_focus_offset;
+	Mth::Vector camera_pos=focus_pos+cam_radius_vector;
+
+	camera_pos[W] = 1.0f;
+	p_skater_cam->SetPos(camera_pos);
+
+	Mth::Matrix mat;
+	mat.Ident();
+	mat[Z]=focus_pos-camera_pos;
+	mat[Z].Normalize();
+	mat[X]	= Mth::CrossProduct( mat[Y], mat[Z] );
+	mat[X].Normalize();
+	mat[Y]	= Mth::CrossProduct( mat[Z], mat[X] );
+	mat[Y].Normalize();
+
+	Mth::Matrix& frame_matrix = p_skater_cam->GetMatrix();
+	frame_matrix[X][X] = -mat[0][0];
+	frame_matrix[X][Y] = -mat[0][1];
+	frame_matrix[X][Z] = -mat[0][2];
+	frame_matrix[Y][X] = mat[1][0];
+	frame_matrix[Y][Y] = mat[1][1];
+	frame_matrix[Y][Z] = mat[1][2];
+	frame_matrix[Z][X] = -mat[2][0];
+	frame_matrix[Z][Y] = -mat[2][1];
+	frame_matrix[Z][Z] = -mat[2][2];
+}
+
+void CScriptDebugger::s_logic_code ( const Tsk::Task< CScriptDebugger >& task )
+{
+	CScriptDebugger&	mdl = task.GetData();
+	Dbg_AssertType ( &task, Tsk::Task< CScriptDebugger > );
+	
+	mdl.check_for_timeouts();
+
+	mdl.update_camera();
+
+	mdl.update_mouse_cursor();	
+
+	mdl.do_any_every_frame_object_updates();
+	mdl.send_any_single_step_object_info();
+	
+	switch (mdl.m_state)
+	{
+	case SCRIPT_DEBUGGER_STATE_IDLE:
+	{
+		break;
+	}
+			
+	default:
+		break;
+	}			
+}
+
+} // namespace Dbg
+#endif
diff --git a/Code/Gel/Scripting/debugger.h b/Code/Gel/Scripting/debugger.h
new file mode 100644
index 0000000..bb8f78a
--- /dev/null
+++ b/Code/Gel/Scripting/debugger.h
@@ -0,0 +1,308 @@
+#ifndef __GEL_SCRIPTING_DEBUGGER_H__
+#define __GEL_SCRIPTING_DEBUGGER_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __GEL_MODULE_H
+#include 
+#endif
+
+#ifndef __NET_H__
+#include 
+#endif
+
+#ifndef __NETSERV_H__
+#include 
+#endif
+
+#ifndef	__GFX_NXMODEL_H__
+#include 
+#endif
+
+#ifndef __GEL_OBJPTR_H
+#include 
+#endif
+
+// This is outside the #ifdef __NOPT_ASSERT__ so that they can be used in the PC code,
+// which includes this file but does not have __NOPT_ASSERT__ defined.
+namespace Obj
+{
+	class CObject;
+	class CCompositeObject;
+	struct SException;
+}
+
+namespace Dbg
+{
+
+// Points to the fixed area in high memory where the game code stores stuff for the
+// script debugger to read using the target manager API.
+// (Note: The script debugger app includes this file, so has access to these defines)
+// Currently, the first thing stored there is a SChecksumNameInfo structure.
+// The game code will assert if this value does not match the symbol _script_debugger_start
+enum
+{
+	SCRIPT_DEBUGGER_MEMORY_START=0x06E00080
+};
+
+// Contains pointers to the start and end of the big list of names whose checksums are
+// loaded. (Eg, every word in every q file currently loaded)
+// This lets the script debugger register all those checksums when it starts up, so that
+// it can resolve checksums to names. 
+struct SChecksumNameInfo
+{
+	char **mppStart;
+	char **mppEnd;
+};
+
+
+
+// Used in the vMSG_ID_DBG_WATCH_SCRIPT packet.
+enum
+{
+	CHECKSUM_IS_SCRIPT_NAME,
+	CHECKSUM_IS_SCRIPT_ID,
+};
+
+// An array of these gets passed from the PS2 to the debugger in the
+// vMSG_ID_DBG_WATCH_LIST meesage
+struct SWatchInfo
+{
+	uint32 mScriptName;
+	bool mStopImmediately;
+};
+
+enum EObjectUpdateMode
+{
+	EVERY_FRAME,
+	SINGLE_STEP,
+};	
+	
+// One of these gets passed to the PS2 from the debugger in the
+// vMSG_ID_DBG_SET_OBJECT_UPDATE_MODE message.
+struct SObjectUpdateMode
+{
+	uint32 mObjectId;
+	EObjectUpdateMode mMode;
+	bool mOn;
+};
+
+// One of these gets passed to the PS2 from the debugger in the
+// vMSG_ID_DBG_VIEW_OBJECT message.
+
+struct SViewObject
+{
+	uint32 mObjectId;
+	bool mDoViewObject;
+};
+
+struct SSplitMessageHeader
+{
+	char mOriginalMessageId;
+	uint32 mOriginalMessageSize;
+	
+	int mTotalNumMessages;
+	
+	uint32 mThisMessageId;
+	uint32 mThisMessageSize;
+	int mThisMessageIndex;
+};
+
+enum
+{
+	// This makes each sub-packet be as big as possible. Netapp.cpp will assert if the total
+	// packet size is >= Net::Manager::vMAX_PACKET_SIZE - 64
+	// The sub-packet includes a SSplitMessageHeader, hence that size has to be subtracted too.
+	SPLIT_MESSAGE_MAX_SIZE=Net::Manager::vMAX_PACKET_SIZE - 65 - sizeof(SSplitMessageHeader),
+};
+
+}
+
+#ifdef __NOPT_ASSERT__
+
+namespace Net
+{
+	class MsgDesc;
+}	
+
+namespace Dbg
+{
+
+enum
+{
+	SCRIPT_DEBUGGER_BUFFER_SIZE=60*1024, // Must be a multiple of 4
+};	
+
+enum EWatchType
+{
+	NONE=0,
+	SCRIPT,
+	SCRIPT_GLOBAL,
+	OBJECT_ID,
+	EXCEPTION,
+};
+
+#define MAX_WATCHED_SCRIPTS 100
+struct SWatchedScript
+{
+	uint32 mChecksum;
+	bool mStopScriptImmediately;
+};
+
+struct SStateWhenLeftPressed
+{
+	int mMouseX;
+	int mMouseY;
+	float mCamTheta;
+	float mCamPhi;
+	Tmr::CPUCycles mTime;
+};
+
+struct SStateWhenRightPressed
+{
+	int mMouseX;
+	int mMouseY;
+	float mCamRadius;
+};
+
+class CScriptDebugger  : public Mdl::Module
+{
+	DeclareSingletonClass( CScriptDebugger );
+
+	Net::Server *mp_server;
+	// Used by the SplitAndEnqueueMessage function to give an id to the message
+	// that is being split. Each of the sub-messages that the message is split into
+	// is given this id so that they can be reconstructed at the other end.
+	// It increments by 1 each time it is used.
+	uint32 m_split_message_id;
+	void send_sub_packet(Net::MsgDesc *p_message, uint32 size, char *p_source,
+						 int packetIndex, int totalNumPackets);
+	
+	enum EState
+	{
+		SCRIPT_DEBUGGER_STATE_IDLE,
+	};
+		
+	EState m_state;
+	char *mp_checksum_names_current_pos;
+	char *mp_checksum_names_end;
+
+	int m_num_watched_scripts;
+	bool m_paused;
+	SWatchedScript mp_watched_scripts[MAX_WATCHED_SCRIPTS];
+	
+	bool m_mouse_on_screen;
+	bool m_got_valid_mouse_position;
+	int m_mouse_x;
+	int m_mouse_y;
+	Obj::CCompositeObject *mp_currently_selected_object; // TODO: Change to be a smart pointer !!!
+	Nx::ERenderMode m_original_object_render_mode;
+	bool m_left_button_down;	
+	SStateWhenLeftPressed m_state_when_left_pressed;
+	
+	bool m_right_button_down;	
+	SStateWhenRightPressed m_state_when_right_pressed;
+	
+	void left_button_down();
+	void left_button_up();
+	void right_button_down();
+
+	void update_mouse_cursor();
+	void switch_off_mouse_cursor();
+	
+	Obj::CSmtPtr mp_viewed_object;
+	bool m_in_object_single_step_mode;
+	uint32 m_single_step_object_id;
+
+	int m_object_update_total_bytes_sent;
+	int m_frames_since_last_object_update;
+	
+	float m_cam_radius;
+	float m_cam_theta;
+	float m_cam_phi;
+	float m_cam_focus_offset;
+	void setup_default_camera_viewing_position();
+	void update_camera();
+	
+	void v_start_cb ( void );
+	void v_stop_cb ( void );
+	
+	static Tsk::Task< CScriptDebugger >::Code s_logic_code;       
+	Tsk::Task< CScriptDebugger > *mp_logic_task;
+	
+	static Net::MsgHandlerCode s_handle_connection_request;
+	static Net::MsgHandlerCode s_handle_watch_this;
+	static Net::MsgHandlerCode s_handle_watch_script;
+
+	static Net::MsgHandlerCode s_handle_stop;
+	static Net::MsgHandlerCode s_handle_step_into;
+	static Net::MsgHandlerCode s_handle_step_over;
+	static Net::MsgHandlerCode s_handle_go;
+	static Net::MsgHandlerCode s_handle_clear_script_watches;
+	static Net::MsgHandlerCode s_handle_refresh;
+	static Net::MsgHandlerCode s_handle_disconnect;
+	static Net::MsgHandlerCode s_handle_send_cscript_list;
+	static Net::MsgHandlerCode s_handle_send_watch_list;
+	static Net::MsgHandlerCode s_handle_stop_watching_this_index;
+	static Net::MsgHandlerCode s_handle_stop_watching_this_cscript;
+	static Net::MsgHandlerCode s_handle_send_composite_object_info;
+	static Net::MsgHandlerCode s_handle_mouse_position;
+	static Net::MsgHandlerCode s_handle_mouse_on_screen;
+	static Net::MsgHandlerCode s_handle_mouse_off_screen;
+	static Net::MsgHandlerCode s_handle_mouse_left_button_down;
+	static Net::MsgHandlerCode s_handle_mouse_left_button_up;
+	static Net::MsgHandlerCode s_handle_mouse_right_button_down;
+	static Net::MsgHandlerCode s_handle_mouse_right_button_up;
+	static Net::MsgHandlerCode s_handle_send_composite_object_list;
+	static Net::MsgHandlerCode s_handle_set_object_update_mode;
+	static Net::MsgHandlerCode s_handle_view_object;
+	static Net::MsgHandlerCode s_handle_send_script_global_info;
+
+	void add_to_watched_scripts(uint32 checksum, bool stopScriptImmediately);
+	SWatchedScript *find_watched_script(uint32 checksum);
+	void refresh();
+	void stop_debugging();
+	void stop_watching_script(int index);
+	int  send_composite_object_info(Obj::CCompositeObject* p_obj);
+	void send_composite_object_info(uint32 id);
+	void send_script_global_info(uint32 id);
+	void do_any_every_frame_object_updates();
+	void stop_all_every_frame_object_updates();
+	void send_any_single_step_object_info();
+	
+	void set_object_update_mode(SObjectUpdateMode *pObjectUpdateMode);
+	void view_object(SViewObject *pViewObject);
+	
+	void process_net_tasks_loop( void );
+	void check_for_timeouts( void );
+	
+	void transmit_cscript_list();
+	void transmit_watch_list();
+	void transmit_composite_object_list();
+	void transmit_compression_lookup_table();
+
+public:
+	CScriptDebugger();
+	virtual	~CScriptDebugger();
+	
+	void EnqueueMessage(Net::MsgDesc *p_message);
+	void StreamMessage(Net::MsgDesc *p_message);
+	
+	void SplitAndEnqueueMessage(Net::MsgDesc *p_message);
+	
+	void ScriptDebuggerPause( void );
+	void ScriptDebuggerUnpause( void );
+
+	void TransmitExceptionInfo(Obj::SException *p_exception, Obj::CCompositeObject* p_obj);
+	SWatchedScript *GetScriptWatchInfo(uint32 checksum);
+};
+
+extern uint32 gpDebugInfoBuffer[];
+
+} // namespace Dbg
+#endif // __NOPT_ASSERT__
+
+#endif // #ifndef __GEL_SCRIPTING_DEBUGGER_H__
+
diff --git a/Code/Gel/Scripting/eval.cpp b/Code/Gel/Scripting/eval.cpp
new file mode 100644
index 0000000..96cf8ea
--- /dev/null
+++ b/Code/Gel/Scripting/eval.cpp
@@ -0,0 +1,996 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Script
+{
+
+// This is a static rather than a local var of execute_operation() to prevent the 
+// constructor being called every time execute_operation is called.
+// Saves a tiny bit of time.
+CComponent spResult;
+
+static int sPrecedence[]=
+{
+	-1, // ESCRIPTTOKEN_ENDOFFILE,			// 0
+	-1, // ESCRIPTTOKEN_ENDOFLINE,			// 1
+	-1, // ESCRIPTTOKEN_ENDOFLINENUMBER,   // 2
+	-1, // ESCRIPTTOKEN_STARTSTRUCT,       // 3
+	-1, // ESCRIPTTOKEN_ENDSTRUCT,         // 4
+	100, // ESCRIPTTOKEN_STARTARRAY,        // 5
+	-1, // ESCRIPTTOKEN_ENDARRAY,          // 6
+	76, // ESCRIPTTOKEN_EQUALS,            // 7
+	100, // ESCRIPTTOKEN_DOT,               // 8
+	-1, // ESCRIPTTOKEN_COMMA,             // 9
+	98, // ESCRIPTTOKEN_MINUS,             // 10
+	98, // ESCRIPTTOKEN_ADD,               // 11
+	99, // ESCRIPTTOKEN_DIVIDE,            // 12
+	100, // ESCRIPTTOKEN_MULTIPLY,          // 13
+	-1, // ESCRIPTTOKEN_OPENPARENTH,       // 14
+	-1, // ESCRIPTTOKEN_CLOSEPARENTH,      // 15
+	-1, // ESCRIPTTOKEN_DEBUGINFO,			// 16
+	-1, // ESCRIPTTOKEN_SAMEAS,			// 17
+	80, // ESCRIPTTOKEN_LESSTHAN,			// 18
+	79, // ESCRIPTTOKEN_LESSTHANEQUAL,     // 19
+	78, // ESCRIPTTOKEN_GREATERTHAN,       // 20
+	77, // ESCRIPTTOKEN_GREATERTHANEQUAL,  // 21
+	-1, // ESCRIPTTOKEN_NAME,				// 22
+	-1, // ESCRIPTTOKEN_INTEGER,			// 23
+	-1, // ESCRIPTTOKEN_HEXINTEGER,        // 24
+    -1, // ESCRIPTTOKEN_ENUM,              // 25
+	-1, // ESCRIPTTOKEN_FLOAT,             // 26
+	-1, // ESCRIPTTOKEN_STRING,            // 27
+	-1, // ESCRIPTTOKEN_LOCALSTRING,       // 28
+	-1, // ESCRIPTTOKEN_ARRAY,             // 29
+	-1, // ESCRIPTTOKEN_VECTOR,            // 30
+	-1, // ESCRIPTTOKEN_PAIR,				// 31
+	-1, // ESCRIPTTOKEN_KEYWORD_BEGIN,		// 32
+	-1, // ESCRIPTTOKEN_KEYWORD_REPEAT,    // 33
+	-1, // ESCRIPTTOKEN_KEYWORD_BREAK,     // 34
+	-1, // ESCRIPTTOKEN_KEYWORD_SCRIPT,    // 35
+	-1, // ESCRIPTTOKEN_KEYWORD_ENDSCRIPT, // 36
+	-1, // ESCRIPTTOKEN_KEYWORD_IF,        // 37
+	-1, // ESCRIPTTOKEN_KEYWORD_ELSE,      // 38
+	-1, // ESCRIPTTOKEN_KEYWORD_ELSEIF,    // 39
+	-1, // ESCRIPTTOKEN_KEYWORD_ENDIF,		// 40
+	-1, // ESCRIPTTOKEN_KEYWORD_RETURN,	// 41
+    -1, // ESCRIPTTOKEN_UNDEFINED,			// 42
+	-1, // ESCRIPTTOKEN_CHECKSUM_NAME,		// 43
+	-1, // ESCRIPTTOKEN_KEYWORD_ALLARGS,	// 44
+	-1, // ESCRIPTTOKEN_ARG,				// 45
+	-1, // ESCRIPTTOKEN_JUMP,				// 46
+	-1, // ESCRIPTTOKEN_KEYWORD_RANDOM,    // 47
+	-1, // ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE,	// 48
+	-1, // ESCRIPTTOKEN_AT,				// 49
+	58, // ESCRIPTTOKEN_OR,				// 50
+	60, // ESCRIPTTOKEN_AND,				// 51
+	59, // ESCRIPTTOKEN_XOR,				// 52
+	90, // ESCRIPTTOKEN_SHIFT_LEFT,		// 53
+	89, // ESCRIPTTOKEN_SHIFT_RIGHT,		// 54
+	-1, // ESCRIPTTOKEN_KEYWORD_RANDOM2,		// 55
+	-1, // ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2, // 56
+	-1, // ESCRIPTTOKEN_KEYWORD_NOT,			// 57
+	-1, // ESCRIPTTOKEN_KEYWORD_AND,			// 58
+	-1, // ESCRIPTTOKEN_KEYWORD_OR,            // 59
+	-1, // ESCRIPTTOKEN_KEYWORD_SWITCH,       	// 60
+	-1, // ESCRIPTTOKEN_KEYWORD_ENDSWITCH,   	// 61
+	-1, // ESCRIPTTOKEN_KEYWORD_CASE,          // 62
+	-1, // ESCRIPTTOKEN_KEYWORD_DEFAULT,		// 63
+	-1,	// ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT,	// 64
+	-1,	// ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE,	// 65
+	-1, // ESCRIPTTOKEN_COLON,		// 66
+	-1, // ESCRIPTTOKEN_RUNTIME_CFUNCTION,	// 67
+	-1, // ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION, // 68
+};
+
+static bool sSameOrLowerPrecedence(EScriptToken a, EScriptToken b)
+{
+	//printf("Precedence of %s=%d, %s=%d\n",GetTokenName(a),sPrecedence[a],GetTokenName(b),sPrecedence[b]);
+	if (a==b)
+	{
+		return true;
+	}
+	
+	return sPrecedence[a]<=sPrecedence[b];
+}
+
+CExpressionEvaluator::CExpressionEvaluator()
+{
+	Clear();
+	EnableErrorChecking();
+}
+
+CExpressionEvaluator::~CExpressionEvaluator()
+{
+	Clear();
+}
+
+void CExpressionEvaluator::Clear()
+{
+	int i;
+
+	for (i=0; imType==ESYMBOLTYPE_INTEGER)
+	{
+		return p_comp->mIntegerValue;
+	}
+	else if (p_comp->mType==ESYMBOLTYPE_FLOAT)
+	{
+		return p_comp->mFloatValue?1:0;
+	}
+	else
+	{
+		#ifdef __NOPT_ASSERT__
+		// So that the calling code can set an error message.
+		// Not asserting at this point because there is no way of printing the line number of the error.
+		return 2;
+		#else
+		return 0;
+		#endif	
+	}
+}
+
+void CExpressionEvaluator::execute_operation()
+{
+	if (m_value_stack_top<1)
+	{
+		set_error("Not enough values in stack to execute operation");
+		return;
+	}	
+	if (mp_operator_stack[m_operator_stack_top].mParenthesesCount)
+	{
+		set_error("Non-zero parentheses count");
+		return;
+	}	
+	
+	// Apply the top operator to the top two values, and replace them with the new value.
+	// Then remove the top operator.
+	
+	EScriptToken op=mp_operator_stack[m_operator_stack_top].mOperator;
+	if (op==NOP)
+	{
+		set_error("Tried to execute with no operator");
+		return;
+	}
+
+	CComponent *pA=&mp_value_stack[m_value_stack_top-1];
+	CComponent *pB=&mp_value_stack[m_value_stack_top];
+	
+	spResult.mType=ESYMBOLTYPE_NONE;
+	spResult.mUnion=0;
+
+	//printf("Executing '%s'\n",GetTokenName(op));
+	switch (op)
+	{
+	case ESCRIPTTOKEN_OR:
+	{
+		spResult.mType=ESYMBOLTYPE_INTEGER;
+		int a=GetBool(pA);
+		int b=GetBool(pB);
+		#ifdef __NOPT_ASSERT__
+		if (a==2 || b==2)
+		{
+			set_error("Bad data types for 'or' operator");
+		}
+		#endif
+		spResult.mIntegerValue=a | b;
+		break;
+	}
+	case ESCRIPTTOKEN_AND:
+	{
+		spResult.mType=ESYMBOLTYPE_INTEGER;
+		int a=GetBool(pA);
+		int b=GetBool(pB);
+		#ifdef __NOPT_ASSERT__
+		if (a==2 || b==2)
+		{
+			set_error("Bad data types for 'or' operator");
+		}
+		#endif
+		spResult.mIntegerValue=a & b;
+		break;
+	}
+	
+	case ESCRIPTTOKEN_EQUALS:
+		spResult.mType=ESYMBOLTYPE_INTEGER;
+		spResult.mIntegerValue=*pA==*pB;
+		break;
+		
+	case ESCRIPTTOKEN_LESSTHAN:
+		spResult.mType=ESYMBOLTYPE_INTEGER;
+		spResult.mIntegerValue=*pA<*pB;
+		break;
+
+	case ESCRIPTTOKEN_GREATERTHAN:
+		spResult.mType=ESYMBOLTYPE_INTEGER;
+		spResult.mIntegerValue=*pA>*pB;
+		break;
+	
+	case ESCRIPTTOKEN_ADD:
+		switch (pA->mType)
+		{
+		case ESYMBOLTYPE_INTEGER:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_INTEGER;
+				spResult.mIntegerValue=pA->mIntegerValue+pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=(float)pA->mIntegerValue+pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Second arg cannot be added to an integer");
+			}	
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue+(float)pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue+pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Second arg cannot be added to a float");
+			}	
+			break;
+		// Checksums may have integers added to them to give a new checksum.
+		// This was added cos Steve needed to be able to give new unique id's
+		// to some screen elements by adding an offset to a base checksum, 
+		// using id=(achecksum+1) for example.
+ 		case ESYMBOLTYPE_NAME:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_NAME;
+				spResult.mChecksum=pA->mChecksum+pB->mIntegerValue;
+			}
+			else
+			{
+				set_error("Second arg cannot be added to a checksum");
+			}	
+			break;
+		case ESYMBOLTYPE_VECTOR:
+			if (pB->mType==ESYMBOLTYPE_VECTOR)
+			{
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mpVector->mX+pB->mpVector->mX;
+				spResult.mpVector->mY=pA->mpVector->mY+pB->mpVector->mY;
+				spResult.mpVector->mZ=pA->mpVector->mZ+pB->mpVector->mZ;
+			}
+			else
+			{
+				set_error("Second arg cannot be added to a vector");
+			}	
+			break;
+		case ESYMBOLTYPE_PAIR:
+			if (pB->mType==ESYMBOLTYPE_PAIR)
+			{
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=pA->mpPair->mX+pB->mpPair->mX;
+				spResult.mpPair->mY=pA->mpPair->mY+pB->mpPair->mY;
+			}
+			else
+			{
+				set_error("Second arg cannot be added to a pair");
+			}	
+			break;
+		case ESYMBOLTYPE_STRING:
+			if (pB->mType==ESYMBOLTYPE_STRING)
+			{
+				if (strlen(pA->mpString)+strlen(pB->mpString)+1>EVALUATOR_STRING_BUF_SIZE)
+				{
+					set_error("Result of string addition too long");
+				}
+				else
+				{
+					spResult.mType=ESYMBOLTYPE_STRING;
+					char p_buf[EVALUATOR_STRING_BUF_SIZE];
+					strcpy(p_buf,pA->mpString);
+					strcat(p_buf,pB->mpString);
+					spResult.mpString=CreateString(p_buf);
+				}	
+			}
+			else
+			{
+				set_error("Second arg cannot be added to a string");
+			}	
+			break;
+		case ESYMBOLTYPE_LOCALSTRING:
+			if (pB->mType==ESYMBOLTYPE_LOCALSTRING)
+			{
+				if (strlen(pA->mpLocalString)+strlen(pB->mpLocalString)+1>EVALUATOR_STRING_BUF_SIZE)
+				{
+					set_error("Result of local-string addition too long");
+				}
+				else
+				{
+					spResult.mType=ESYMBOLTYPE_LOCALSTRING;
+					char p_buf[EVALUATOR_STRING_BUF_SIZE];
+					strcpy(p_buf,pA->mpLocalString);
+					strcat(p_buf,pB->mpLocalString);
+					spResult.mpLocalString=CreateString(p_buf);
+				}	
+			}
+			else
+			{
+				set_error("Second arg cannot be added to a local-string");
+			}	
+			break;
+		case ESYMBOLTYPE_STRUCTURE:
+		{
+			if (pB->mType!=ESYMBOLTYPE_STRUCTURE)
+			{
+				set_error("Structure add operator requires 2nd arg to be a structure");
+				break;
+			}
+
+			spResult.mType=ESYMBOLTYPE_STRUCTURE;
+			spResult.mpStructure=new CStruct;
+			spResult.mpStructure->AppendStructure(pA->mpStructure);
+			spResult.mpStructure->AppendStructure(pB->mpStructure);
+			break;
+		}	
+		default:
+			set_error("Addition not supported for this type");
+			break;
+		}	
+		break;
+	case ESCRIPTTOKEN_MINUS:
+		switch (pA->mType)
+		{
+		case ESYMBOLTYPE_INTEGER:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_INTEGER;
+				spResult.mIntegerValue=pA->mIntegerValue-pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=(float)pA->mIntegerValue-pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Second arg cannot be subtracted from an integer");
+			}	
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue-(float)pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue-pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Second arg cannot be subtracted from a float");
+			}	
+			break;
+		case ESYMBOLTYPE_VECTOR:
+			if (pB->mType==ESYMBOLTYPE_VECTOR)
+			{
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mpVector->mX-pB->mpVector->mX;
+				spResult.mpVector->mY=pA->mpVector->mY-pB->mpVector->mY;
+				spResult.mpVector->mZ=pA->mpVector->mZ-pB->mpVector->mZ;
+			}
+			else
+			{
+				set_error("Second arg cannot be subtracted from a vector");
+			}	
+			break;
+		case ESYMBOLTYPE_PAIR:
+			if (pB->mType==ESYMBOLTYPE_PAIR)
+			{
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=pA->mpPair->mX-pB->mpPair->mX;
+				spResult.mpPair->mY=pA->mpPair->mY-pB->mpPair->mY;
+			}
+			else
+			{
+				set_error("Second arg cannot be subtracted from a pair");
+			}	
+			break;
+		case ESYMBOLTYPE_STRUCTURE:
+		{
+			// If the LHS is a structure, then the subtract operator is defined
+			// so as to allow parameters to be removed from the structure,
+			// cos that's quite handy.
+			
+			// If the RHS is a single name, then all parameter with that name
+			// will be removed, including flags.
+			// If the RHS is an array of names then each of those parameters will
+			// be removed.
+			// If the RHS is a structure, then all the parameters in the LHS which have
+			// matching name AND type to something in the RHS will be removed, but if the RHS
+			// contains unnamed-names then all parameters with that name will be removed.
+			// So {x=3 x="blaa"}-{x=9} gives {x="blaa"}
+			// But {x=3 x="blaa"}-{x} gives {}
+			
+			if (pB->mType==ESYMBOLTYPE_NAME)
+			{
+				spResult.mType=ESYMBOLTYPE_STRUCTURE;
+				spResult.mpStructure=new CStruct;
+				spResult.mpStructure->AppendStructure(pA->mpStructure);
+				spResult.mpStructure->RemoveComponent(pB->mChecksum);
+				spResult.mpStructure->RemoveFlag(pB->mChecksum);
+			}
+			else if (pB->mType==ESYMBOLTYPE_ARRAY)
+			{
+				Script::CArray *p_array=pB->mpArray;
+				Dbg_MsgAssert(p_array,("NULL p_array"));
+				
+				if (!(p_array->GetType() == ESYMBOLTYPE_NAME || 
+					  p_array->GetType() == ESYMBOLTYPE_NONE)) // Allowing NONE so that empty arrays work.
+				{
+					set_error("Subtracting an array from a structure requires the array to be an array of names");
+				}
+				else
+				{
+					spResult.mType=ESYMBOLTYPE_STRUCTURE;
+					spResult.mpStructure=new CStruct;
+					spResult.mpStructure->AppendStructure(pA->mpStructure);
+					
+					for (uint32 i=0; iGetSize(); ++i)
+					{
+						spResult.mpStructure->RemoveComponent(p_array->GetChecksum(i));
+						spResult.mpStructure->RemoveFlag(p_array->GetChecksum(i));
+					}
+				}
+			}
+			else if (pB->mType==ESYMBOLTYPE_STRUCTURE)
+			{
+				spResult.mType=ESYMBOLTYPE_STRUCTURE;
+				spResult.mpStructure=new CStruct;
+				spResult.mpStructure->AppendStructure(pA->mpStructure);
+				
+				Script::CStruct *p_struct=pB->mpStructure;
+				Dbg_MsgAssert(p_struct,("NULL p_struct"));
+				Script::CComponent *p_comp=p_struct->GetNextComponent();
+				while (p_comp)
+				{
+					if (p_comp->mNameChecksum)
+					{
+						// If the component is named, remove all components of the same name and type.
+						spResult.mpStructure->RemoveComponentWithType(p_comp->mNameChecksum,p_comp->mType);
+					}
+					else if (p_comp->mType==ESYMBOLTYPE_NAME)
+					{
+						// If it's an unnamed-name (ie a flag) remove all components with that name,
+						// including flags.
+						spResult.mpStructure->RemoveComponent(p_comp->mChecksum);
+						spResult.mpStructure->RemoveFlag(p_comp->mChecksum);
+					}					
+					p_comp=p_struct->GetNextComponent(p_comp);
+				}	
+			}
+			else
+			{
+				set_error("Structure minus operator requires 2nd arg to be a name, an array of names, or a structure");
+			}			
+			break;
+		}	
+		default:
+			set_error("Subtraction not supported for this type");
+			break;
+		}	
+		break;
+	case ESCRIPTTOKEN_MULTIPLY:
+		switch (pA->mType)
+		{
+		case ESYMBOLTYPE_INTEGER:
+			switch (pB->mType)
+			{
+			case ESYMBOLTYPE_INTEGER:
+				spResult.mType=ESYMBOLTYPE_INTEGER;
+				spResult.mIntegerValue=pA->mIntegerValue*pB->mIntegerValue;
+				break;
+			case ESYMBOLTYPE_FLOAT:
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=(float)pA->mIntegerValue*pB->mFloatValue;
+				break;
+			case ESYMBOLTYPE_VECTOR:
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=(float)pA->mIntegerValue*pB->mpVector->mX;
+				spResult.mpVector->mY=(float)pA->mIntegerValue*pB->mpVector->mY;
+				spResult.mpVector->mZ=(float)pA->mIntegerValue*pB->mpVector->mZ;
+				break;
+			case ESYMBOLTYPE_PAIR:
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=(float)pA->mIntegerValue*pB->mpPair->mX;
+				spResult.mpPair->mY=(float)pA->mIntegerValue*pB->mpPair->mY;
+				break;
+			default:
+				set_error("Second arg cannot be multiplied by an integer");
+				break;
+			}	
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			switch (pB->mType)
+			{
+			case ESYMBOLTYPE_INTEGER:
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue*(float)pB->mIntegerValue;
+				break;
+			case ESYMBOLTYPE_FLOAT:
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue*pB->mFloatValue;
+				break;
+			case ESYMBOLTYPE_VECTOR:
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mFloatValue*pB->mpVector->mX;
+				spResult.mpVector->mY=pA->mFloatValue*pB->mpVector->mY;
+				spResult.mpVector->mZ=pA->mFloatValue*pB->mpVector->mZ;
+				break;
+			case ESYMBOLTYPE_PAIR:
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=pA->mFloatValue*pB->mpPair->mX;
+				spResult.mpPair->mY=pA->mFloatValue*pB->mpPair->mY;
+				break;
+			default:
+				set_error("Second arg cannot be multiplied by a float");
+			}	
+			break;
+		case ESYMBOLTYPE_VECTOR:
+			switch (pB->mType)
+			{
+			case ESYMBOLTYPE_VECTOR:
+				// Take multiplication of two vectors to mean the cross-product.
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mpVector->mY*pB->mpVector->mZ-pA->mpVector->mZ*pB->mpVector->mY;
+				spResult.mpVector->mY=pA->mpVector->mZ*pB->mpVector->mX-pA->mpVector->mX*pB->mpVector->mZ;
+				spResult.mpVector->mZ=pA->mpVector->mX*pB->mpVector->mY-pA->mpVector->mY*pB->mpVector->mX;
+				break;
+			case ESYMBOLTYPE_FLOAT:
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mpVector->mX*pB->mFloatValue;
+				spResult.mpVector->mY=pA->mpVector->mY*pB->mFloatValue;
+				spResult.mpVector->mZ=pA->mpVector->mZ*pB->mFloatValue;
+				break;
+			case ESYMBOLTYPE_INTEGER:
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mpVector->mX*(float)pB->mIntegerValue;
+				spResult.mpVector->mY=pA->mpVector->mY*(float)pB->mIntegerValue;
+				spResult.mpVector->mZ=pA->mpVector->mZ*(float)pB->mIntegerValue;
+				break;
+			default:
+				set_error("Vector cannot be multiplied by second arg");
+				break;
+			}	
+			break;
+		case ESYMBOLTYPE_PAIR:
+			switch (pB->mType)
+			{
+			case ESYMBOLTYPE_FLOAT:
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=pA->mpPair->mX*pB->mFloatValue;
+				spResult.mpPair->mY=pA->mpPair->mY*pB->mFloatValue;
+				break;
+			case ESYMBOLTYPE_INTEGER:
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=pA->mpPair->mX*(float)pB->mIntegerValue;
+				spResult.mpPair->mY=pA->mpPair->mY*(float)pB->mIntegerValue;
+				break;
+			default:
+				set_error("Pair cannot be multiplied by second arg");
+				break;
+			}	
+			break;
+		default:
+			set_error("Multiplication not supported for this type");
+			break;
+		}	
+		break;
+	case ESCRIPTTOKEN_DIVIDE:
+		if (pB->mType==ESYMBOLTYPE_INTEGER)
+		{
+			if (pB->mIntegerValue==0)
+			{
+				set_error("Integer division by zero");
+				break;
+			}
+		}
+		else if (pB->mType==ESYMBOLTYPE_FLOAT)
+		{
+			if (pB->mFloatValue==0.0f)
+			{
+				set_error("Float division by zero");
+				break;
+			}
+		}
+		
+		switch (pA->mType)
+		{
+		case ESYMBOLTYPE_INTEGER:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_INTEGER;
+				spResult.mIntegerValue=pA->mIntegerValue/pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=(float)pA->mIntegerValue/pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Integer cannot be divided by second arg");
+			}	
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue/(float)pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_FLOAT;
+				spResult.mFloatValue=pA->mFloatValue/pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Float cannot be divided by second arg");
+			}	
+			break;
+		case ESYMBOLTYPE_VECTOR:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mpVector->mX/(float)pB->mIntegerValue;
+				spResult.mpVector->mY=pA->mpVector->mY/(float)pB->mIntegerValue;
+				spResult.mpVector->mZ=pA->mpVector->mZ/(float)pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_VECTOR;
+				spResult.mpVector=new CVector;
+				spResult.mpVector->mX=pA->mpVector->mX/pB->mFloatValue;
+				spResult.mpVector->mY=pA->mpVector->mY/pB->mFloatValue;
+				spResult.mpVector->mZ=pA->mpVector->mZ/pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Vector cannot be divided by second arg");
+			}	
+			break;
+		case ESYMBOLTYPE_PAIR:
+			if (pB->mType==ESYMBOLTYPE_INTEGER)
+			{
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=pA->mpPair->mX/(float)pB->mIntegerValue;
+				spResult.mpPair->mY=pA->mpPair->mY/(float)pB->mIntegerValue;
+			}
+			else if (pB->mType==ESYMBOLTYPE_FLOAT)
+			{
+				spResult.mType=ESYMBOLTYPE_PAIR;
+				spResult.mpPair=new CPair;
+				spResult.mpPair->mX=pA->mpPair->mX/pB->mFloatValue;
+				spResult.mpPair->mY=pA->mpPair->mY/pB->mFloatValue;
+			}
+			else
+			{
+				set_error("Pair cannot be divided by second arg");
+			}	
+			break;
+		default:
+			set_error("Division not supported for this type");
+			break;
+		}	
+		break;
+	case ESCRIPTTOKEN_DOT:
+		switch (pA->mType)
+		{
+		case ESYMBOLTYPE_VECTOR:
+			if (pB->mType!=ESYMBOLTYPE_VECTOR)
+			{
+				set_error("Vector can only be dot producted with another vector");
+				break;
+			}
+			spResult.mType=ESYMBOLTYPE_FLOAT;
+			spResult.mFloatValue=pA->mpVector->mX*pB->mpVector->mX+
+								 pA->mpVector->mY*pB->mpVector->mY+
+								 pA->mpVector->mZ*pB->mpVector->mZ;
+			break;	
+		case ESYMBOLTYPE_PAIR:
+			if (pB->mType!=ESYMBOLTYPE_PAIR)
+			{
+				set_error("Pair can only be dot producted with another pair");
+				break;
+			}
+			spResult.mType=ESYMBOLTYPE_FLOAT;
+			spResult.mFloatValue=pA->mpPair->mX*pB->mpPair->mX+
+								 pA->mpPair->mY*pB->mpPair->mY;
+			break;	
+		case ESYMBOLTYPE_STRUCTURE:
+		{
+			if (pB->mType!=ESYMBOLTYPE_NAME)
+			{
+				set_error("Structure dot operator requires 2nd arg to be a name");
+				break;
+			}
+			Dbg_MsgAssert(pA->mpStructure,("NULL pA->mpStructure"));
+			CComponent *p_found=pA->mpStructure->FindNamedComponentRecurse(pB->mChecksum);
+			if (p_found)
+			{
+				CopyComponent(&spResult,p_found);
+			}	
+			break;
+		}	
+		default:
+			set_error("Dot product not supported for this type");
+			break;
+		}
+		break;
+	case ESCRIPTTOKEN_STARTARRAY:
+	{
+		if (pA->mType!=ESYMBOLTYPE_ARRAY)
+		{
+			set_error("Array element operator not supported for this type");
+			break;
+		}
+		Dbg_MsgAssert(pA->mpArray,("NULL pA->mpArray"));
+		
+		if (pB->mType!=ESYMBOLTYPE_INTEGER)
+		{
+			set_error("[] index must be an integer");
+			break;
+		}
+		uint32 index=pB->mIntegerValue;
+		
+		if (index>=pA->mpArray->GetSize())
+		{
+			set_error("[] index out of range");
+			break;
+		}
+		
+		CopyArrayElementIntoComponent(pA->mpArray,index,&spResult);
+		if (spResult.mType==ESYMBOLTYPE_NONE)
+		{
+			set_error("Array type cannot be accessed using [] yet ...");
+			break;
+		}		
+				
+		break;
+	}	
+	default:
+		set_error("Operator not supported");
+		break;
+	}
+				
+	CleanUpComponent(pA);
+	CleanUpComponent(pB);
+	pA->mType=spResult.mType;
+	pA->mUnion=spResult.mUnion;
+	spResult.mType=ESYMBOLTYPE_NONE;
+	spResult.mUnion=0;
+	
+	--m_value_stack_top;
+	
+	mp_operator_stack[m_operator_stack_top].mOperator=NOP;
+	if (m_operator_stack_top)
+	{
+		--m_operator_stack_top;
+	}
+	else
+	{
+		m_got_operators=false;
+	}	
+	
+	//printf("m_operator_stack_top=%d\n",m_operator_stack_top);
+}
+
+void CExpressionEvaluator::add_new_operator(EScriptToken op)
+{
+	++m_operator_stack_top;
+	if (m_operator_stack_top>=OPERATOR_STACK_SIZE)
+	{
+		set_error("Operator stack overflow");
+		return;
+	}
+		
+	mp_operator_stack[m_operator_stack_top].mOperator=op;	
+	mp_operator_stack[m_operator_stack_top].mParenthesesCount=0;
+	
+	m_got_operators=true;
+}
+
+void CExpressionEvaluator::Input(EScriptToken op)
+{
+	while (m_got_operators)
+	{
+		if (mp_operator_stack[m_operator_stack_top].mParenthesesCount)
+		{
+			// The most recent operator is 'protected' by parentheses, 
+			// so do not execute it.
+			break;
+		}	
+	
+		if (sSameOrLowerPrecedence(op,mp_operator_stack[m_operator_stack_top].mOperator))
+		{
+			// The new operator has the same or lower precedence than the last, so execute the
+			// last operator.
+			// Ie, in 2*3+5, we execute the * once we get the +
+			execute_operation();
+		}
+		else
+		{
+			break;
+		}	
+	}	
+				
+	// Insert the new operator.
+	add_new_operator(op);
+}
+
+void CExpressionEvaluator::Input(const CComponent *p_value)
+{
+	if (m_value_stack_top==0 && mp_value_stack[0].mType==ESYMBOLTYPE_NONE)
+	{
+	}
+	else
+	{
+		++m_value_stack_top;
+		if (m_value_stack_top>=VALUE_STACK_SIZE)
+		{
+			set_error("Value stack overflow");
+			return;
+		}
+	}
+		
+	Dbg_MsgAssert(p_value,("NULL p_value"));
+	CopyComponent(&mp_value_stack[m_value_stack_top],p_value);
+}
+
+void CExpressionEvaluator::OpenParenthesis()
+{
+	++mp_operator_stack[m_operator_stack_top].mParenthesesCount;
+}
+
+void CExpressionEvaluator::CloseParenthesis()
+{
+	while (true)
+	{
+		if (mp_operator_stack[m_operator_stack_top].mParenthesesCount)
+		{
+			--mp_operator_stack[m_operator_stack_top].mParenthesesCount;
+			break;
+		}
+		else
+		{
+			execute_operation();
+			if (mp_error_string)
+			{
+				break;
+			}	
+		}	
+	}	
+}
+
+bool CExpressionEvaluator::GetResult(CComponent *p_result)
+{
+	Dbg_MsgAssert(p_result,("NULL p_result"));
+	Dbg_MsgAssert(p_result->mType==ESYMBOLTYPE_NONE,("Non-empty component sent to GetResult, type = %s",GetTypeName(p_result->mType)));
+	Dbg_MsgAssert(p_result->mUnion==0,("CComponent::mUnion not zero, type = %s",GetTypeName(p_result->mType)));
+	
+	while (true)
+	{
+		if (mp_operator_stack[m_operator_stack_top].mParenthesesCount)
+		{
+			set_error("Not enough close parentheses");
+			break;
+		}
+			
+		if (m_value_stack_top==0)
+		{
+			if (mp_operator_stack[0].mOperator!=0 || m_operator_stack_top)
+			{
+				set_error("Too many operators in expression");
+				break;
+			}	
+			CopyComponent(p_result,&mp_value_stack[0]);
+			CleanUpComponent(&mp_value_stack[0]);
+			return true;
+		}			
+		
+		execute_operation();
+		if (mp_error_string)
+		{
+			break;
+		}	
+	}
+	return false;
+}
+
+const char *CExpressionEvaluator::GetError()
+{
+	return mp_error_string;
+}
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/eval.h b/Code/Gel/Scripting/eval.h
new file mode 100644
index 0000000..65e4058
--- /dev/null
+++ b/Code/Gel/Scripting/eval.h
@@ -0,0 +1,86 @@
+#ifndef	__SCRIPTING_EVAL_H
+#define	__SCRIPTING_EVAL_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_COMPONENT_H
+#include 
+#endif
+
+#ifndef __SCRIPTING_TOKENS_H
+#include 
+#endif
+
+namespace Script
+{
+
+#define EVALUATOR_STRING_BUF_SIZE 200
+
+#define NOP ((EScriptToken)0)
+
+#define VALUE_STACK_SIZE 20
+#define OPERATOR_STACK_SIZE VALUE_STACK_SIZE
+
+struct SOperator
+{
+	EScriptToken mOperator;
+	int mParenthesesCount;
+};
+	
+class CExpressionEvaluator : public Spt::Class
+{
+	CComponent mp_value_stack[VALUE_STACK_SIZE];
+	int m_value_stack_top;
+	
+	SOperator mp_operator_stack[OPERATOR_STACK_SIZE];
+	int m_operator_stack_top;
+	bool m_got_operators;
+
+	// mp_token points to the position in the qb file being parsed.
+	// The expression evaluator itself never does any parsing of the qb data,
+	// it only stores this so that it can print the line number if an error occurs.
+	uint8 *mp_token;	
+	const char *mp_error_string;
+	
+	// Sometimes, if an expression contains an error such as a missing parameter value, we
+	// do not want it to assert or print a warning message, but just return a void value instead.
+	// For example, when scanning through all the scripts looking for calls to EndGap, the
+	// function AddComponentsUntilEndOfLine is used, but often parameters are missing then
+	// because the script is not being run, just scanned through. Don't wan't it to stop with
+	// an error in that case.
+	bool m_errors_enabled;
+	
+	void set_error(const char *p_error);
+	void execute_operation();
+	void add_new_operator(EScriptToken op);
+									 
+public:	
+	CExpressionEvaluator();
+	~CExpressionEvaluator();
+	
+	void EnableErrorChecking() {m_errors_enabled=true;}
+	void DisableErrorChecking() {m_errors_enabled=false;}
+	bool ErrorCheckingEnabled() {return m_errors_enabled;}
+							 
+	// These are not defined, just declared so that the code won't link if they
+	// are attempted to be used.
+	CExpressionEvaluator( const CExpressionEvaluator& rhs );
+	CExpressionEvaluator& operator=( const CExpressionEvaluator& rhs );
+
+	void Clear();	
+	void ClearIfNeeded();
+	void Input(EScriptToken op);
+	void Input(const CComponent *p_value);
+	void OpenParenthesis();
+	void CloseParenthesis();
+	bool GetResult(CComponent *p_result);
+	const char *GetError();
+	void SetTokenPointer(uint8 *p_token);
+};
+	
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_EVAL_H
+
diff --git a/Code/Gel/Scripting/file.cpp b/Code/Gel/Scripting/file.cpp
new file mode 100644
index 0000000..e467ad5
--- /dev/null
+++ b/Code/Gel/Scripting/file.cpp
@@ -0,0 +1,159 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// file.cpp		KSH 31 Oct 2001
+//
+// Functions for loading and unloading qb files.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  // For Crc::GenerateCRCFromString
+#include 
+
+namespace Script
+{
+// TODO: Need another LoadQB in the game-specific script namespace, which will call this LoadQB
+// and then do any game-specific stuff that needs to be done when a qb is reloaded, such as 
+// generating the node name hash table & prefix info, reloading the skater exceptions, updating the decks on
+// the skateshop wall, etc...
+
+void restart_dirty_scripts()
+{
+	// For each symbol, check if it got reloaded by the above load, and if it did and it is a script
+	// then run through all the existing CScript's restarting those that referred to it.
+	// This is essential, because otherwise those CScripts' program counter pointers will now be invalid.
+	CSymbolTableEntry *p_sym=GetNextSymbolTableEntry();
+	while (p_sym)
+	{
+		if (p_sym->mGotReloaded && p_sym->mType==ESYMBOLTYPE_QSCRIPT)
+		{
+			CScript *p_script=GetNextScript();
+			while (p_script)
+			{
+				if (p_script->RefersToScript(p_sym->mNameChecksum))
+				{
+					p_script->Restart();
+				}
+			
+				p_script=GetNextScript(p_script);
+			}	
+			
+			// Reset the reloaded flag, otherwise it will stick on forever.
+			p_sym->mGotReloaded=false;
+		}
+		p_sym=GetNextSymbolTableEntry(p_sym);
+	}		
+}
+
+// Loads a QB file.
+// It will open the file, load it into memory and parse it, creating all the
+// symbols (scripts, arrays, integers etc) defined within in.
+void LoadQB(const char *p_fileName, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols)
+{
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+#ifndef __PLAT_NGC__
+	Dbg_MsgAssert(strcmp(p_fileName+strlen(p_fileName)-3,".qb")==0,("File does not have extension .qb. File %s",p_fileName));
+#endif __PLAT_NGC__
+
+	// Mick - Pip::Load is not going to load it from a Pip::Pre, just a regular pre
+	// so I'm sticking it on the top-down heap to avoid fragmentation							  
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	uint8 *p_qb=(uint8*)Pip::Load(p_fileName);
+	Mem::Manager::sHandle().PopContext();
+		
+	// Parse the QB, which creates all the symbols defined within it.
+	ParseQB(p_fileName,p_qb,assertIfDuplicateSymbols);
+	
+	Pip::Unload(p_fileName);	
+
+	restart_dirty_scripts();
+}
+
+// Loads a QB file from memory
+void LoadQBFromMemory(const char* p_fileName, uint8* p_qb, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols)
+{
+	// even though there's no actually filename,
+	// we'd still need a dummy string, which will
+	// be used for printing up Assert messages...
+
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+#ifndef __PLAT_NGC__
+	Dbg_MsgAssert(strcmp(p_fileName+strlen(p_fileName)-3,".qb")==0,("File does not have extension .qb. File %s",p_fileName));
+#endif __PLAT_NGC__
+
+	// do not create the hash table if it's not
+	// the node array (it's a 50K memory hog on
+	// the script heap, and it's really only
+	// needed for doing prefix stuff anyway)
+	ParseQB(p_fileName,p_qb,assertIfDuplicateSymbols,false);
+
+	RemoveChecksumNameLookupHashTable();
+
+	restart_dirty_scripts();
+}
+
+// TODO: Need another UnloadQB in the game-specific script namespace, which will call this UnloadQB
+// and then do any game-specific stuff that needs to be done when a qb is unloaded, such as 
+// resetting the node name hash table & prefix info.
+
+// Deletes all the symbols that were defined in the passed QB file.
+void UnloadQB(uint32 fileNameChecksum)
+{
+	// Do nothing if the checksum is zero. This is essential, because symbols
+	// that have a mSourceFileNameChecksum of 0 are those that did not come
+	// from a qb file, ie C-functions. These must never be deleted.
+	if (!fileNameChecksum)
+	{
+		return;
+	}
+		
+	// Scan through all the symbols in the symbol table.
+	CSymbolTableEntry *p_sym=GetNextSymbolTableEntry();
+	while (p_sym)
+	{											
+		if (p_sym->mSourceFileNameChecksum==fileNameChecksum)
+		{
+			// This symbol was defined in the passed qb file, so remove it.
+			CleanUpAndRemoveSymbol(p_sym);
+			// Need to start checking from the start of the list again rather than storing
+			// a p_next pointer before calling CleanUpAndRemoveSymbol.
+			// This is because CleanUpAndRemoveSymbol may cause the symbol above to move down,
+			// invalidating p_next.
+			// SPEEDOPT Starting again from the start of the list is inefficient though, because
+			// it may have to scan through thousands of symbols just to delete one, and may have to
+			// delete say a hundred. Optimize later. (Seems fast enough at the moment)
+			p_sym=NULL;
+		}	
+		p_sym=GetNextSymbolTableEntry(p_sym);
+	}
+	
+	// Scan through all the existing CScripts stopping any that are referring to a script
+	// that got deleted above.
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		if (!p_script->AllScriptsInCallstackStillExist())
+		{
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}
+	
+		p_script=GetNextScript(p_script);
+	}	
+}
+
+// Note: There is no Script::UnloadQB(const char *p_fileName)
+// This is because we may not want to do a GenerateCRCFromString on the p_fileName as it stands.
+// We may want to make sure the file name is prefixed with the complete path before calculating
+// the crc.
+// Since the path is game specific, the code to do this is in the SkateScript namespace.
+// See SkateScript::UnloadQB(const char *p_fileName) and SkateScript::GenerateFileNameChecksum
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/file.h b/Code/Gel/Scripting/file.h
new file mode 100644
index 0000000..3c84753
--- /dev/null
+++ b/Code/Gel/Scripting/file.h
@@ -0,0 +1,19 @@
+#ifndef	__SCRIPTING_FILE_H
+#define	__SCRIPTING_FILE_H
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#include  // For enums
+#endif
+
+namespace Script
+{
+	
+void LoadQB(const char *p_fileName, 
+			EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols=NO_ASSERT_IF_DUPLICATE_SYMBOLS);
+void LoadQBFromMemory(const char* p_fileName, uint8* p_qb, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols);
+void UnloadQB(uint32 fileNameChecksum);
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_FILE_H
+
diff --git a/Code/Gel/Scripting/init.cpp b/Code/Gel/Scripting/init.cpp
new file mode 100644
index 0000000..e36f088
--- /dev/null
+++ b/Code/Gel/Scripting/init.cpp
@@ -0,0 +1,193 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// init.cpp		KSH 19 Oct 2001
+//
+// Functions for initialising various script stuff.
+// 
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Script
+{
+
+// MEMOPT This is where all the pools of script things get allocated.
+void AllocatePools()
+{
+	// Sets up the checksum name-lookup hash table.
+	// On the PS2 this does not actually do any dynamic allocation, but uses the space set aside
+	// in NGPS.lk and defined by _script_start and _script_end
+	// On other platforms it does dynamically allocate the space.
+	// The function will not do anything if Config::GotExtraMemory() returns false.
+	Mem::PushMemProfile("AllocateChecksumNameLookupTables");
+	AllocateChecksumNameLookupTables();
+	Mem::PopMemProfile();
+	
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+
+	#ifndef __PLAT_WN32__
+	
+	#ifdef NO_SCRIPT_CACHING
+	#else
+	Mem::PushMemProfile("CScriptCacheEntry");
+	// 32ish bytes each
+	CScriptCacheEntry::SCreatePool(MAX_DECOMPRESSED_SCRIPTS, "CScriptCacheEntry");
+	Mem::PopMemProfile();
+	#endif
+		
+	Mem::PushMemProfile("CComponent and Reserve CComponent");
+	
+	// 16 bytes each
+	CComponent::SCreatePool(84000, "CComponent");  // Mick:  increased by 2000 (82000 to 84000) to account for gap lists
+
+	Mem::PopMemProfile();
+	
+
+	Mem::PushMemProfile("CStruct");
+	// 4 bytes each  (Actually 8, or 12 with asserts)
+	CStruct::SCreatePool(18800, "CStruct");
+	Mem::PopMemProfile();
+	
+	// 12 bytes each  (Actually 16)
+	Mem::PushMemProfile("CVector");
+	CVector::SCreatePool(9600, "CVector");
+	Mem::PopMemProfile();
+	
+	// 8 bytes each	  (Actually 12)
+	Mem::PushMemProfile("CPair");
+	CPair::SCreatePool(1000, "CPair");	 // Mick: was 5000, but only used 308 at last count
+	Mem::PopMemProfile();
+	
+	// 12 bytes each
+	Mem::PushMemProfile("CArray");
+	//CArray::SCreatePool(6000, "CArray");
+	CArray::SCreatePool(7500, "CArray");
+	Mem::PopMemProfile();
+	
+	
+	// 12 bytes each (actually 16)
+	Mem::PushMemProfile("CSymbolTableEntry");
+	CSymbolTableEntry::SCreatePool(8500, "CSymbolTableEntry");
+	Mem::PopMemProfile();
+
+	Mem::PushMemProfile("CScript");
+	// 1080 bytes each, bloody hell (Actually ~1238!!!)
+	CScript::SCreatePool(MAX_CSCRIPTS, "CScript");
+	Mem::PopMemProfile();
+
+	Mem::PushMemProfile("CStoredRandom");
+	// 80 bytes each (100)
+	CStoredRandom::SCreatePool(MAX_STORED_RANDOMS,"CStoredRandom");
+	Mem::PopMemProfile();
+	#endif
+
+
+	// This will create a further 4096 CSymbolTableEntry's, but as a contiguous array.
+//	Mem::PushMemProfile("CreateSymbolHashTable");
+//	CreateSymbolHashTable();
+//	Mem::PopMemProfile();
+	
+	// 60500 bytes was needed for foreign languages on THPS3, 48000 for English.
+	// 4000 is the max number of strings.
+	//AllocatePermanentStringHeap(60500,4000);
+	Mem::PushMemProfile("AllocatePermanentStringHeap");
+	AllocatePermanentStringHeap(160000,7000);	
+	Mem::PopMemProfile();
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+void DeallocatePools()
+{
+	DeallocatePermanentStringHeap();
+	DestroySymbolHashTable();
+	
+	#ifndef __PLAT_WN32__
+	CScript::SRemovePool();
+	CSymbolTableEntry::SRemovePool();
+	CArray::SRemovePool();
+	CStruct::SRemovePool();
+	CPair::SRemovePool();
+	CVector::SRemovePool();
+	CComponent::SRemovePool();
+	#endif
+		
+	DeallocateChecksumNameLookupTables();
+}
+
+// Adds all the c-functions listed in the passed array to the symbol table.
+void RegisterCFunctions(SCFunction *p_cFunctions, uint32 numFunctions)
+{
+	Dbg_MsgAssert(p_cFunctions,("NULL p_cFunctions"));
+	
+	for (uint32 i=0; imType==ESYMBOLTYPE_CFUNCTION || p_existing_entry->mType==ESYMBOLTYPE_MEMBERFUNCTION ? "ftables.cpp":FindChecksumName(p_existing_entry->mSourceFileNameChecksum),
+					  GetTypeName(p_existing_entry->mType)));
+		#endif		
+		
+		CSymbolTableEntry *p_new=CreateNewSymbolEntry(name_checksum);
+		Dbg_MsgAssert(p_new,("NULL p_new ??"));
+	
+		p_new->mType=ESYMBOLTYPE_CFUNCTION;
+		p_new->mpCFunction=p_cFunctions[i].mpFunction;
+		// Note: Not setting p_new->mSourceFileNameChecksum to anything, just leaving it as 0, since there is no
+		// source qb file.
+	}
+}
+
+// Adds all the member functions listed in the passed array to the symbol table.
+void RegisterMemberFunctions(const char **pp_memberFunctions, uint32 numFunctions)
+{
+	Dbg_MsgAssert(pp_memberFunctions,("NULL pp_memberFunctions"));
+	
+	for (uint32 i=0; imType==ESYMBOLTYPE_CFUNCTION || p_existing_entry->mType==ESYMBOLTYPE_MEMBERFUNCTION ? "ftables.cpp":FindChecksumName(p_existing_entry->mSourceFileNameChecksum),
+					  GetTypeName(p_existing_entry->mType)));
+		#endif
+		
+		CSymbolTableEntry *p_new=CreateNewSymbolEntry(name_checksum);
+		Dbg_MsgAssert(p_new,("NULL p_new ??"));
+	
+		p_new->mType=ESYMBOLTYPE_MEMBERFUNCTION;
+		// No need to set the value to anything, since the name of the symbol specifies the member function.
+		
+		// Note: Not setting p_new->mSourceFileNameChecksum to anything, just leaving it as 0, since there is no
+		// source qb file.
+	}
+}
+
+} // namespace Script
diff --git a/Code/Gel/Scripting/init.h b/Code/Gel/Scripting/init.h
new file mode 100644
index 0000000..c54a364
--- /dev/null
+++ b/Code/Gel/Scripting/init.h
@@ -0,0 +1,31 @@
+#ifndef	__SCRIPTING_INIT_H
+#define	__SCRIPTING_INIT_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Script
+{
+
+enum
+{
+	MAX_CSCRIPTS=300, // If this changes, the script debugger (qdebug.exe) will need to be rebuilt.
+};
+	
+class CStruct;
+class CScript;
+struct SCFunction
+{
+    const char *mpName;
+    bool (*mpFunction)(Script::CStruct *, Script::CScript *);
+};
+
+void AllocatePools();
+void DeallocatePools();
+void RegisterCFunctions(SCFunction *p_cFunctions, uint32 numFunctions);
+void RegisterMemberFunctions(const char **pp_memberFunctions, uint32 numFunctions);
+
+} // namespace Script
+
+#endif // #ifndef __SCRIPTING_INIT_H
diff --git a/Code/Gel/Scripting/parse.cpp b/Code/Gel/Scripting/parse.cpp
new file mode 100644
index 0000000..bf4b2f7
--- /dev/null
+++ b/Code/Gel/Scripting/parse.cpp
@@ -0,0 +1,3415 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// parse.cpp		KSH 23 Oct 2001
+//
+// Functions for parsing qb files.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  // For Mth::Rnd and Mth::Rnd2
+#include  // For Crc::GenerateCRCFromString
+#include 
+
+#ifdef __PLAT_NGC__
+#include 
+#include 
+#endif		// __PLAT_NGC__
+
+DefinePoolableClass(Script::CStoredRandom);
+
+namespace Script
+{
+
+class CScript;
+// Declaring this seperately rather than including script.h to avoid a cyclic dependency.
+// The full class declaration of CScript is not required.
+extern CScript *GetCurrentScript();
+
+static uint8 *sCalculateRandomRange(uint8 *p_token, CComponent *p_comp, bool useRnd2);
+static uint8 *sSkipType(uint8 *p_token);
+static bool   sIsEndOfLine(const uint8 *p_token);
+static uint8 *sInitArrayFromQB(CArray *p_dest, uint8 *p_token, CStruct *p_args=NULL);
+static uint8 *sAddComponentFromQB(CStruct *p_dest, uint32 nameChecksum, uint8 *p_token, CStruct *p_args=NULL);
+static uint8 *sAddComponentsWithinCurlyBraces(CStruct *p_dest, uint8 *p_token, CStruct *p_args=NULL);
+static CSymbolTableEntry *sCreateScriptSymbol(uint32 nameChecksum, uint32 contentsChecksum, const uint8 *p_data, uint32 size, const char *p_fileName);
+static uint8 *sCreateSymbolOfTheFormNameEqualsValue(uint8 *p_token, const char *p_fileName, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols);
+static CStoredRandom *sFindStoredRandom(const uint8 *p_token, EScriptToken type, int numItems);
+static CStoredRandom *sCreateNewStoredRandom();
+
+static CStoredRandom *sp_first_stored_random=NULL;
+static CStoredRandom *sp_last_stored_random=NULL;
+static int s_num_stored_randoms=0;
+
+static uint32 s_qb_being_parsed=0;
+
+// The SkipToken function is in skiptoken.cpp, so that it can also be included in PC code,
+// such as qcomp.
+#include 
+
+#ifdef __NOPT_ASSERT__
+void CheckForPossibleInfiniteLoops(uint32 scriptName, uint8 *p_token, const char *p_fileName)
+{
+	#define MAX_NESTED_LOOPS 100
+	bool loop_is_ok[MAX_NESTED_LOOPS];
+	int loop_index=0;
+	for (int i=0; i=0)
+				{
+					loop_is_ok[i]=true;
+					--i;
+				}	
+				p_token=SkipToken(p_token);
+				break;
+			}	
+			case ESCRIPTTOKEN_KEYWORD_REPEAT:
+				p_token=SkipToken(p_token);
+				
+				Dbg_MsgAssert(loop_index,("Zero loop_index"));
+				if (*p_token==ESCRIPTTOKEN_ENDOFLINE || *p_token==ESCRIPTTOKEN_ENDOFLINENUMBER)
+				{
+					if (!loop_is_ok[loop_index-1])
+					{
+						bool allow=false;
+						if (p_ok_scripts)
+						{
+							for (uint32 i=0; iGetSize(); ++i)
+							{
+								if (p_ok_scripts->GetChecksum(i)==scriptName)
+								{
+									allow=true;
+								}
+							}
+						}
+						
+						if (allow)
+						{
+						}
+						else
+						{
+							Dbg_MsgAssert(0, ("Warning! Possible infinite loop: Line %d of %s\n",Script::GetLineNumber(p_token),p_fileName));
+						}	
+					}
+				}
+				else
+				{
+					// If repeat is not followed by an end-of-line then it is probably followed by some
+					// count value, so it is not an infinite loop.
+				}
+				
+				--loop_index;	
+				break;
+
+			case ESCRIPTTOKEN_NAME:
+			{
+				++p_token;
+				uint32 name=Read4Bytes(p_token).mChecksum;
+				p_token+=4;
+				
+				if (p_blocking_functions)
+				{
+					uint32 *p_function_names=p_blocking_functions->GetArrayPointer();
+					uint32 size=p_blocking_functions->GetSize();
+					
+					for (uint32 i=0; i=0)
+								{
+									loop_is_ok[i]=true;
+									--i;
+								}	
+							}	
+							break;
+						}
+					}		
+				}
+				break;
+			}	
+				
+			default:
+				p_token=SkipToken(p_token);
+				break;
+		}
+	}	
+}
+#endif
+
+// Used for evaluating the ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE operator.
+static uint8 *sCalculateRandomRange(uint8 *p_token, CComponent *p_comp, bool useRnd2)
+{
+	// RandomRange token must be followed by a pair.
+	Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_PAIR,("RandomRange operator must be followed by a pair of values in parentheses, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
+	++p_token;
+	// Get the range values, which are floats cos it's a pair.
+	float x=Read4Bytes(p_token).mFloat;
+	p_token+=4;
+	float y=Read4Bytes(p_token).mFloat;
+	p_token+=4;
+	
+	// Get the integer values.
+	// Offset the floats up or down by 0.01 to ensure they round down to the expected integer values.
+	int a, b;
+	a=(int)( (x<0) ? x-0.1f:x+0.1f);
+	b=(int)( (y<0) ? y-0.1f:y+0.1f);
+	
+	bool they_are_integers=true;
+	if (fabs(x-a) > 0.00001f || fabs(y-b) > 0.00001f)
+	{
+		they_are_integers=false;
+	}	
+	
+	if (they_are_integers)
+	{
+		// Make sure that a<=b
+		if (a>b)
+		{
+			// Swap them.
+			int temp=a;
+			a=b;
+			b=temp;
+		}	
+		// Get val, which is between a and b inclusive.
+		// This assert is to make sure the calculations in Rnd don't overflow.
+		Dbg_MsgAssert(b-a+1<=32767,("File %s, line %d: RandomRange limits are too far apart, max is 32767",GetSourceFile(p_token),GetLineNumber(p_token)));
+		int val;
+		if (useRnd2)
+		{
+			val=a+Mth::Rnd2(b-a+1);
+		}
+		else
+		{
+			val=a+Mth::Rnd(b-a+1);
+		}	
+		// Make totally sure the value is OK, cos could lead to hard-to-track-down bugs otherwise.
+//		Dbg_MsgAssert(val>=a && val<=b,("File %s, line %d: Internal error in RandomRange (%f %f %f), fire Ken.",GetSourceFile(p_token),GetLineNumber(p_token),x,y,val));
+		
+		p_comp->mType=ESYMBOLTYPE_INTEGER;
+		p_comp->mIntegerValue=val;
+	}
+	else
+	{
+		// Make sure that x<=y
+		if (x>y)
+		{
+			// Swap them.
+			float temp=x;
+			x=y;
+			y=temp;
+		}	
+		// Get val, which is between x and y inclusive.
+		float val;
+		// Don't make FLOAT_RES bigger than 32766 because the calculations in Rnd will overflow otherwise.
+		#define FLOAT_RES 32760.0f
+		if (useRnd2)
+		{
+			val=x+((float)Mth::Rnd2((int)(FLOAT_RES+1.0f))*(y-x))/FLOAT_RES;
+		}
+		else
+		{
+			val=x+((float)Mth::Rnd((int)(FLOAT_RES+1.0f))*(y-x))/FLOAT_RES;
+		}	
+		
+		// Make totally sure the value is OK, cos could lead to hard-to-track-down bugs otherwise.
+//		Dbg_MsgAssert(val>=x && val<=y,("File %s, line %d: Internal error in RandomRange (%f %f %f), fire Ken.",GetSourceFile(p_token),GetLineNumber(p_token),x,y,val));
+		
+		p_comp->mType=ESYMBOLTYPE_FLOAT;
+		p_comp->mFloatValue=val;
+	}
+	
+	return p_token;
+}
+
+// Skips over a data type token. Eg, if p_token points to a structure, it will
+// skip over the whole structure.
+static uint8 *sSkipType(uint8 *p_token)
+{
+    switch (*p_token)
+    {
+        case ESCRIPTTOKEN_NAME:
+        case ESCRIPTTOKEN_INTEGER:
+        case ESCRIPTTOKEN_HEXINTEGER:
+        case ESCRIPTTOKEN_FLOAT:
+            p_token+=1+4;
+            break;
+        case ESCRIPTTOKEN_PAIR:
+            p_token+=1+2*4;
+            break;
+        case ESCRIPTTOKEN_VECTOR:
+            p_token+=1+3*4;
+            break;
+        case ESCRIPTTOKEN_STRING:
+        case ESCRIPTTOKEN_LOCALSTRING:
+            p_token+=1+4+Read4Bytes(p_token+1).mUInt;
+            break;
+        case ESCRIPTTOKEN_STARTSTRUCT:
+		{
+            int StructCount=1;
+            while (StructCount)
+            {
+                p_token=SkipToken(p_token);
+                if (*p_token==ESCRIPTTOKEN_STARTSTRUCT) 
+				{
+					++StructCount;
+				}	
+                else if (*p_token==ESCRIPTTOKEN_ENDSTRUCT)
+				{
+                    --StructCount;
+				}	
+            }
+            ++p_token;
+            break;
+		}	
+        case ESCRIPTTOKEN_STARTARRAY:
+		{
+            int ArrayCount=1;
+            while (ArrayCount)
+            {
+                p_token=SkipToken(p_token);
+                if (*p_token==ESCRIPTTOKEN_STARTARRAY)
+				{
+                    ++ArrayCount;
+				}	
+                else if (*p_token==ESCRIPTTOKEN_ENDARRAY)
+				{
+                    --ArrayCount;
+				}	
+            }
+            ++p_token;
+            break;
+		}	
+        default:
+            Dbg_MsgAssert(0,("Unrecognized type"));
+            break;
+    }
+    return p_token;
+}
+
+// Returns true if pToken points to and end-of-line, end-of-line-number or end-of-file token.
+static bool sIsEndOfLine(const uint8 *p_token)
+{
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+    return (*p_token==ESCRIPTTOKEN_ENDOFLINE || *p_token==ESCRIPTTOKEN_ENDOFLINENUMBER || *p_token==ESCRIPTTOKEN_ENDOFFILE);
+}
+
+// Given that p_token points to a ESCRIPTTOKEN_STARTARRAY token, this will parse the
+// array tokens adding the elements to the CArray.
+// Gives error messages if all the elements are not of the same type.
+static uint8 *sInitArrayFromQB(CArray *p_dest, uint8 *p_token, CStruct *p_args)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+    Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_STARTARRAY,("p_token does not point to an array"));
+	
+	// Remember the start, since we're going to do a first pass through to determine the array type and size.
+	uint8 *p_start=p_token;	
+    
+	// Skip over the startarray token.
+    ++p_token;
+	// Execute any random operators.
+	p_token=DoAnyRandomsOrJumps(p_token);
+
+	ESymbolType type=ESYMBOLTYPE_NONE;
+	uint32 size=0;
+	
+    while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+    {
+		// Determine type.
+        switch (*p_token)
+        {
+            case ESCRIPTTOKEN_ENDOFLINE:
+            case ESCRIPTTOKEN_ENDOFLINENUMBER:
+            case ESCRIPTTOKEN_COMMA:
+                break;
+            case ESCRIPTTOKEN_NAME:
+                type=ESYMBOLTYPE_NAME;
+                break;
+            case ESCRIPTTOKEN_INTEGER:
+            case ESCRIPTTOKEN_HEXINTEGER:
+				// Integers don't override floats.
+                if (type!=ESYMBOLTYPE_FLOAT) 
+				{
+					type=ESYMBOLTYPE_INTEGER;
+				}	
+                break;
+            case ESCRIPTTOKEN_FLOAT:
+                type=ESYMBOLTYPE_FLOAT;
+                break;
+            case ESCRIPTTOKEN_PAIR:
+                type=ESYMBOLTYPE_PAIR;
+                break;
+            case ESCRIPTTOKEN_VECTOR:
+                type=ESYMBOLTYPE_VECTOR;
+                break;
+	        case ESCRIPTTOKEN_STRING:
+                type=ESYMBOLTYPE_STRING;
+                break;
+            case ESCRIPTTOKEN_LOCALSTRING:
+                type=ESYMBOLTYPE_LOCALSTRING;
+                break;
+            case ESCRIPTTOKEN_STARTSTRUCT:
+                type=ESYMBOLTYPE_STRUCTURE;
+                break;
+            case ESCRIPTTOKEN_STARTARRAY:
+                type=ESYMBOLTYPE_ARRAY;
+                break;
+            default:
+                Dbg_MsgAssert(0,("Unrecognized data type in array, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
+                break;
+        }
+		
+		// Update the size and advance p_token.
+        switch (*p_token)
+        {
+            case ESCRIPTTOKEN_ENDOFLINE:
+            case ESCRIPTTOKEN_ENDOFLINENUMBER:
+            case ESCRIPTTOKEN_COMMA:
+                p_token=SkipToken(p_token);
+                break;
+            case ESCRIPTTOKEN_NAME:
+            case ESCRIPTTOKEN_INTEGER:
+            case ESCRIPTTOKEN_HEXINTEGER:
+            case ESCRIPTTOKEN_FLOAT:
+            case ESCRIPTTOKEN_PAIR:
+            case ESCRIPTTOKEN_VECTOR:
+	        case ESCRIPTTOKEN_STRING:
+            case ESCRIPTTOKEN_LOCALSTRING:
+            case ESCRIPTTOKEN_STARTSTRUCT:
+            case ESCRIPTTOKEN_STARTARRAY:
+                ++size;
+                p_token=sSkipType(p_token);
+                break;
+            default:
+                Dbg_MsgAssert(0,("Unrecognized data type in array, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
+                break;
+        }
+		
+		// Execute any random operators. This has to be done each time p_token is advanced.
+		p_token=DoAnyRandomsOrJumps(p_token);
+    }
+
+	if (type==ESYMBOLTYPE_NONE)
+	{
+		// Skip over the ESCRIPTTOKEN_ENDARRAY
+		++p_token;
+		// Finished, empty array.
+		return p_token;
+	}
+
+	// Rewind.										  
+	p_token=p_start;	
+	
+	// Now that the array size and type are known, set up the CArray and fill it in.
+	
+	// Make sure we're using the script heap, because the CArray is not hard-wired to
+	// allocate it's buffer off it.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	p_dest->SetSizeAndType(size,type);
+	Mem::Manager::sHandle().PopContext();
+
+	// Just to be totally sure, cos we're about to write into it ...
+	Dbg_MsgAssert(p_dest->GetArrayPointer(),("NULL array pinter ???"));
+	
+	#ifdef __NOPT_ASSERT__
+	int size_check=size;
+	#endif
+		
+    switch (type)
+    {
+		case ESYMBOLTYPE_INTEGER:
+		{
+			int *p_int=(int*)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			// Execute any random operators. This has to be done each time p_token is advanced.
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_INTEGER:
+					case ESCRIPTTOKEN_HEXINTEGER:
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						*p_int++=Read4Bytes(p_token).mInt;
+						p_token+=4;
+						break;
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+		case ESYMBOLTYPE_FLOAT:
+		{
+			float *p_float=(float*)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_INTEGER:
+					case ESCRIPTTOKEN_HEXINTEGER:
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						*p_float++=Read4Bytes(p_token).mInt;
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_FLOAT:
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						*p_float++=Read4Bytes(p_token).mFloat;
+						p_token+=4;
+						break;
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+		case ESYMBOLTYPE_NAME:
+		{
+			uint32 *p_checksum=(uint32*)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_NAME:
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						*p_checksum++=Read4Bytes(p_token).mChecksum;
+						p_token+=4;
+						break;
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+		case ESYMBOLTYPE_STRUCTURE:
+		{
+			CStruct **pp_structures=(CStruct**)p_dest->GetArrayPointer();
+
+			// For finding out which node in Chad's qn is causing a syntax error
+			//int index=0; // REMOVE!
+						
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						++p_token;
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=5;
+						break;
+					case ESCRIPTTOKEN_STARTSTRUCT:
+					{
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						// Each structure is individually allocated.
+						CStruct *p_struct=new CStruct;
+						p_token=sAddComponentsWithinCurlyBraces(p_struct,p_token,p_args);
+						
+						*pp_structures++=p_struct;
+						
+						//printf("Created array structure %d\n",index++); // REMOVE!
+						
+						break;
+					}    
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+		case ESYMBOLTYPE_ARRAY:
+		{
+			CArray **pp_arrays=(CArray**)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_STARTARRAY:
+					{
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						// Each array is individually allocated.											
+						CArray *p_array=new CArray;
+						// The -1 is to rewind p_token back to pointing to ESCRIPTTOKEN_STARTARRAY,
+						// which sInitArrayFromQB requires.
+						p_token=sInitArrayFromQB(p_array,p_token-1,p_args);
+							 
+						*pp_arrays++=p_array;
+						break;
+					}	
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+
+		case ESYMBOLTYPE_STRING:
+		{
+			char **pp_strings=(char**)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_STRING:
+					{
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+
+						int string_length=Read4Bytes(p_token).mInt;
+						Dbg_MsgAssert(string_length,("Zero string_length?"));
+						p_token+=4;
+						
+						*pp_strings++=CreateString((const char *)p_token);
+						p_token+=string_length;
+						break;
+					}	
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+
+		case ESYMBOLTYPE_LOCALSTRING:
+		{
+			char **pp_strings=(char**)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_LOCALSTRING:
+					{
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+
+						int string_length=Read4Bytes(p_token).mInt;
+						Dbg_MsgAssert(string_length,("Zero string_length?"));
+						p_token+=4;
+						
+						*pp_strings++=CreateString((const char *)p_token);
+						p_token+=string_length;
+						break;
+					}	
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+
+		case ESYMBOLTYPE_VECTOR:
+		{
+			CVector **pp_vectors=(CVector**)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_VECTOR:
+					{
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						CVector *p_new_vector=new CVector;
+						p_new_vector->mX=Read4Bytes(p_token).mFloat;
+						p_token+=4;
+						p_new_vector->mY=Read4Bytes(p_token).mFloat;
+						p_token+=4;
+						p_new_vector->mZ=Read4Bytes(p_token).mFloat;
+						p_token+=4;
+						
+						*pp_vectors++=p_new_vector;
+						break;
+					}	
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+
+		case ESYMBOLTYPE_PAIR:
+		{
+			CPair **pp_pairs=(CPair**)p_dest->GetArrayPointer();
+			
+			// Skip over the ESCRIPTTOKEN_STARTARRAY
+			++p_token;
+			p_token=DoAnyRandomsOrJumps(p_token);
+			while (*p_token!=ESCRIPTTOKEN_ENDARRAY)
+			{
+				switch (*p_token++)
+				{
+					case ESCRIPTTOKEN_ENDOFLINE:
+					case ESCRIPTTOKEN_COMMA:
+						break;
+					case ESCRIPTTOKEN_ENDOFLINENUMBER:
+						p_token+=4;
+						break;
+					case ESCRIPTTOKEN_PAIR:
+					{
+						#ifdef __NOPT_ASSERT__
+						Dbg_MsgAssert(size_check,("Array size mismatch"));
+						--size_check;
+						#endif
+						
+						CPair *p_new_pair=new CPair;
+						p_new_pair->mX=Read4Bytes(p_token).mFloat;
+						p_token+=4;
+						p_new_pair->mY=Read4Bytes(p_token).mFloat;
+						p_token+=4;
+						
+						*pp_pairs++=p_new_pair;
+						break;
+					}	
+					default:
+						Dbg_MsgAssert(0,("Array elements must be of the same type, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+						break;
+				}
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+			}    
+			#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(size_check==0,("Array size mismatch"));
+			#endif
+			// Skip over the ESCRIPTTOKEN_ENDARRAY                                     
+			++p_token;
+			break;
+		}    
+		
+		default:
+			Dbg_MsgAssert(0,("Unsupported array type, File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token)));
+			break;
+    }
+
+	return p_token;
+}
+
+// Adds one component to p_dest.
+// The component is given the name nameChecksum, and it's type and value is defined by whatever
+// p_token is pointing to.
+// p_args contains the structure defining the value of any args referred to using the <,> operators.
+// Returns a pointer to the next token after parsing the value pointed to by p_token.
+static uint8 *sAddComponentFromQB(CStruct *p_dest, uint32 nameChecksum, uint8 *p_token, CStruct *p_args)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+	
+	bool use_rnd2=false;
+	
+	switch (*p_token++)
+	{
+		case ESCRIPTTOKEN_NAME:
+		{
+			uint32 checksum=Read4Bytes(p_token).mChecksum;
+			p_token+=4;
+			p_dest->AddChecksum(nameChecksum,checksum);
+			break;
+		}    
+		case ESCRIPTTOKEN_INTEGER:
+		{
+			int integer=Read4Bytes(p_token).mInt;
+			p_token+=4;
+			p_dest->AddInteger(nameChecksum,integer);
+			break;
+		}    
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2:
+			use_rnd2=true;
+			// Intentional lack of a break here.
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
+		{
+			CComponent *p_new_component=new CComponent;
+			p_token=sCalculateRandomRange(p_token,p_new_component,use_rnd2);
+			p_new_component->mNameChecksum=nameChecksum;
+			p_dest->AddComponent(p_new_component);
+			break;
+		}    
+		
+		case ESCRIPTTOKEN_FLOAT:
+		{
+			float float_val=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			p_dest->AddFloat(nameChecksum,float_val);
+			break;
+		}    
+		case ESCRIPTTOKEN_VECTOR:
+		{
+			float x=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			float y=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			float z=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			
+			// Alert! This next 'if' is kind of game specific ...
+			// It should probably be the exporter that does not include the angles in the node array if they are 
+			// close to zero, rather than having this remove them from all structures.
+			// Note: We can't just remove zero angles from the node array after it has loaded, because there
+			// is not enough memory to store them initially.
+			if (nameChecksum==CRCD(0x9d2d0915,"Angles"))
+			{
+				// If the vector is close to (0,0,0) then don't bother adding it.
+				// This is a memory optimization. All nodes in levels are getting exported with
+				// an angles component, which most of the time is (0,0,0). 
+				// This will work OK so long as whenever GetVector is used in the code to get the angles, 
+				// the vector being loaded into is initialised with (0,0,0).
+				// That way, if the vector is not there, it will remain as (0,0,0) as required.
+				float dx=x;
+				if (dx<0.0f) dx=-dx;
+				float dy=y;
+				if (dy<0.0f) dy=-dy;
+				float dz=z;
+				if (dz<0.0f) dz=-dz;
+				if (dx<0.01f && dy<0.01f && dz<0.01f)
+				{
+					break;
+				}	
+			}
+			
+			p_dest->AddVector(nameChecksum,x,y,z);
+			break;
+		}    
+		case ESCRIPTTOKEN_PAIR:
+		{
+			float x=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			float y=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			p_dest->AddPair(nameChecksum,x,y);
+			break;
+		}    
+		case ESCRIPTTOKEN_STRING:
+		{
+			int len=Read4Bytes(p_token).mInt;
+			p_token+=4;
+			p_dest->AddString(nameChecksum,(const char *)p_token);
+			p_token+=len;
+			break;
+		}    
+		case ESCRIPTTOKEN_LOCALSTRING:
+		{
+			int len=Read4Bytes(p_token).mInt;
+			p_token+=4;
+			p_dest->AddLocalString(nameChecksum,(const char *)p_token);
+			p_token+=len;
+			break;
+		}    
+		case ESCRIPTTOKEN_STARTSTRUCT:
+		{
+			// Rewind p_token to point to ESCRIPTTOKEN_STARTSTRUCT, because the 
+			// sAddComponentsWithinCurlyBraces call requires it.
+			--p_token;
+			
+			// No need to set which heap we're using, cos CStruct's and their CComponent's come off
+			// their own pools.
+			CStruct *p_structure=new CStruct;
+			p_token=sAddComponentsWithinCurlyBraces(p_structure,p_token,p_args);
+			p_dest->AddStructurePointer(nameChecksum,p_structure);
+			// Note: Not deleting p_structure, because it has been given to p_dest, which will delete
+			// it in its destructor.
+			break;
+		}    
+		case ESCRIPTTOKEN_STARTARRAY:
+		{
+			// Rewind p_token to point to ESCRIPTTOKEN_STARTARRAY, because the CArray::Parse call requires it.
+			--p_token;
+			
+			// The CArray constructor is not hard-wired to use the script heap for it's buffer, so need
+			// to make sure we're using the script heap here.
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+			CArray *p_array=new CArray;
+			Mem::Manager::sHandle().PopContext();			
+			
+			p_token=sInitArrayFromQB(p_array,p_token,p_args);
+			p_dest->AddArrayPointer(nameChecksum,p_array);
+			// Note: Not deleting p_array, because it has been given to p_dest, which will delete
+			// it in its destructor.
+			break;
+		}
+		case ESCRIPTTOKEN_KEYWORD_SCRIPT:
+		{
+			// When adding a local script defined in a structure, we'd expect the nameChecksum passed to
+			// this function to be zero, since the name comes after the script keyword.
+			Dbg_MsgAssert(nameChecksum==0,("nameChecksum expected to be 0 for a local script ??, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+			
+			Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nKeyword 'script' must be followed by a name, File %s, line %d\n",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+			++p_token; // Skip over the ESCRIPTTOKEN_NAME
+			nameChecksum=Read4Bytes(p_token).mChecksum;
+			p_token+=4; // Skip over the name checksum
+			
+			// Skip p_token over the script tokens, and calculate the size.
+			const uint8 *p_script=p_token;
+			p_token=SkipOverScript(p_token);
+			uint32 size=p_token-p_script;
+			
+			// This will create a new buffer and copy in the script data.
+			p_dest->AddScript(nameChecksum,p_script,size);
+ 			break;
+		}	
+		case ESCRIPTTOKEN_ARG:
+		{
+			Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("Expected '<' token to be followed by a name, File %s, line %d",GetSourceFile(p_token-1),GetLineNumber(p_token-1)));
+			++p_token;
+			uint32 arg_checksum=Read4Bytes(p_token).mChecksum;
+			p_token+=4;
+			if (p_args)
+			{
+				// Look for a parameter named arg_checksum in p_args, recursing into any referenced structures
+				CComponent *p_comp=p_args->FindNamedComponentRecurse(arg_checksum);
+				if (p_comp)
+				{
+					if (nameChecksum==0 && p_comp->mType==ESYMBOLTYPE_STRUCTURE)
+					{
+						// Trying to add an unnamed structure, in which case it's contents
+						// should be merged onto p_dest.
+						p_dest->AppendStructure(p_comp->mpStructure);
+						// Note: OK to call AppendStructure with NULL, so no need to check mpStructure.
+					}
+					else
+					{
+						// Create a copy of the component and add it to p_dest.
+						
+						CComponent *p_new_component=new CComponent;
+						// Note: There is no copy-constructor for CComponent, because that would cause a
+						// cyclic dependency between component.cpp/h and struct.cpp/h.
+						// Instead, use the CopyComponent function defined in struct.cpp
+						CopyComponent(p_new_component,p_comp);
+						// Then change it's name to nameChecksum
+						p_new_component->mNameChecksum=nameChecksum;
+						// And add it to p_dest
+						p_dest->AddComponent(p_new_component);
+					}	
+				}
+			}
+			// Don't assert if p_args is NULL, cos it will be when this function is used to skip over
+			// parameter lists in FindReferences
+			break;	
+		}
+		case ESCRIPTTOKEN_KEYWORD_ALLARGS:
+		{
+			// Don't assert if p_args is NULL, cos it will be when this function is used to skip over
+			// parameter lists in FindReferences
+			if (p_args)
+			{
+				if (nameChecksum)
+				{
+					// If it's a named structure, copy the passed arguments into a new structure and insert a 
+					// pointer to it.
+					CStruct *p_structure=new CStruct;
+					*p_structure=*p_args;
+					p_dest->AddStructurePointer(nameChecksum,p_structure);
+				}
+				else	
+				{
+					// Otherwise just merge in the passed parameters.
+					// Un-named structures are always considered 'part of' the structure that they are in,
+					// eg {x=9 {y=3}} is the same as {x=9 y=3}
+					*p_dest+=*p_args;
+				}
+			}	
+			break;	
+		}
+		case ESCRIPTTOKEN_COMMA:
+			break;
+		default:
+			--p_token;
+			Dbg_MsgAssert(0,("\nBad token '%s' encountered when creating component, File %s, line %d",GetTokenName((EScriptToken)*p_token),GetSourceFile(p_token),GetLineNumber(p_token)));
+			break;
+	}
+    return p_token;    
+}
+
+// Parses the components in QB format pointed to by p_token, and adds them to p_dest until the 
+// close-curly-brace token is reached.
+// p_token must initially point to an open-curly-brace token.
+// Any end-of-lines in between are ignored.
+// For example, p_token may point to: {x=9 foo="Blaa"}
+// Used when creating sub-structures and global structures.
+// p_args contains the structure defining the value of any args referred to using the <,> operators.
+// Returns a pointer to the next token after parsing the tokens pointed to by p_token.
+static uint8 *sAddComponentsWithinCurlyBraces(CStruct *p_dest, uint8 *p_token, CStruct *p_args)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+    Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_STARTSTRUCT,("p_token expected to point to ESCRIPTTOKEN_STARTSTRUCT, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
+	// Skip over the ESCRIPTTOKEN_STARTSTRUCT
+    ++p_token;
+
+    while (true)
+    {
+		p_token=SkipEndOfLines(p_token);
+		// Execute any random operators. This has to be done each time p_token is advanced.
+		p_token=DoAnyRandomsOrJumps(p_token);
+		if (*p_token==ESCRIPTTOKEN_ENDSTRUCT)
+		{
+			break;
+		}	
+		
+        switch (*p_token)
+        {
+            case ESCRIPTTOKEN_NAME:
+            {
+                uint8 *p_name_token=p_token;
+                ++p_token;
+                uint32 name_checksum=Read4Bytes(p_token).mChecksum;
+                p_token+=4;
+                p_token=SkipEndOfLines(p_token);
+				p_token=DoAnyRandomsOrJumps(p_token);
+                if (*p_token==ESCRIPTTOKEN_EQUALS)
+                {
+                    ++p_token;
+                    p_token=SkipEndOfLines(p_token);
+					p_token=DoAnyRandomsOrJumps(p_token);
+					
+					if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
+					{
+						CComponent *p_comp=new CComponent;
+						p_token=Evaluate(p_token,p_args,p_comp);
+						if (p_comp->mType!=ESYMBOLTYPE_NONE)
+						{
+							p_comp->mNameChecksum=name_checksum;
+							p_dest->AddComponent(p_comp);
+						}
+						else
+						{
+							delete p_comp;
+						}	
+					}
+					else
+					{
+						p_token=sAddComponentFromQB(p_dest,name_checksum,p_token,p_args);
+					}
+                }
+                else
+				{
+                    p_token=sAddComponentFromQB(p_dest,NO_NAME,p_name_token,p_args);
+				}	
+                break;
+            }    
+            case ESCRIPTTOKEN_STARTSTRUCT:
+                p_token=sAddComponentsWithinCurlyBraces(p_dest,p_token,p_args);
+                break;
+			case ESCRIPTTOKEN_COMMA:	
+				++p_token;
+				break;
+			case ESCRIPTTOKEN_ENDOFLINE:
+			case ESCRIPTTOKEN_ENDOFLINENUMBER:
+				p_token=SkipEndOfLines(p_token);				
+				break;
+            default:
+			{
+				if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
+				{
+					CComponent *p_comp=new CComponent;
+					p_token=Evaluate(p_token,p_args,p_comp);
+					
+					if (p_comp->mType==ESYMBOLTYPE_STRUCTURE)
+					{
+						// The CStruct::AddComponent function does not allow unnamed structure
+						// components to be added, so merge in the contents of the structure instead.
+						p_dest->AppendStructure(p_comp->mpStructure);
+						// Now p_comp does have to be cleaned up and deleted, because it has not
+						// been given to p_dest.
+						CleanUpComponent(p_comp);
+						delete p_comp;
+					}
+					else
+					{
+						if (p_comp->mType!=ESYMBOLTYPE_NONE)
+						{
+							p_comp->mNameChecksum=NO_NAME;
+							p_dest->AddComponent(p_comp);
+						}
+						else
+						{
+							delete p_comp;
+						}	
+					}				
+				}
+				else
+				{
+					p_token=sAddComponentFromQB(p_dest,NO_NAME,p_token,p_args);
+				}	
+                break;
+			}	
+        }            
+    } 
+
+	// Skip over the ESCRIPTTOKEN_ENDSTRUCT
+    ++p_token;
+    return p_token;
+}
+
+// Creates a new script symbol entry, allocates memory for the script data and copies it in, prefixing
+// the data with the contents checksum.
+static CSymbolTableEntry *sCreateScriptSymbol(uint32 nameChecksum, uint32 contentsChecksum, const uint8 *p_data, uint32 size, const char *p_fileName)
+{
+	Dbg_MsgAssert(p_data,("NULL p_data ??"));
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+
+	#ifdef __NOPT_ASSERT__
+	#ifndef __PLAT_WN32__
+	CheckForPossibleInfiniteLoops(nameChecksum,(uint8*)(uint32)p_data, p_fileName);
+	#endif
+	#endif
+
+	CSymbolTableEntry *p_new=CreateNewSymbolEntry(nameChecksum);
+	Dbg_MsgAssert(p_new,("NULL p_new ??"));
+	
+	p_new->mType=ESYMBOLTYPE_QSCRIPT;
+	
+	#ifdef NO_SCRIPT_CACHING
+	// Allocate space for the content checksum (4 bytes) plus the script data.
+	uint8 *p_new_script=(uint8*)Mem::Malloc(4+size);
+	
+	// Write in the contents checksum.
+	// p_new_script will be long-word aligned so OK to cast to a uint32*
+	*(uint32*)p_new_script=contentsChecksum;
+	
+	uint8 *p_dest=p_new_script+4;
+	const uint8 *p_source=p_data;
+	for (uint32 i=0; impScript=p_new_script;
+	PreProcessScript(p_new_script+4);
+	
+	#else	
+	
+	
+	enum
+	{
+		COMPRESS_BUFFER_SIZE=20000,
+	};	
+	uint8 *p_compress_buffer=(uint8*)Mem::Malloc(COMPRESS_BUFFER_SIZE);
+	
+	// Compress the script data.
+	int compressed_size=Encode((char*)p_data,(char*)p_compress_buffer,size,false);
+	Dbg_MsgAssert(compressed_size <= COMPRESS_BUFFER_SIZE,("Compress buffer overflow! Compressed size of script %s is %d",Script::FindChecksumName(nameChecksum),compressed_size));
+	
+	// If it compressed to a bigger size, replace the compressed 
+	// data with a copy of the original instead.
+	if (compressed_size >= (int)size)
+	{
+		const uint8 *p_source=p_data;
+		uint8 *p_dest=p_compress_buffer;
+		for (uint32 i=0; imScriptOffset = NsARAM::alloc( SCRIPT_HEADER_SIZE+compressed_size, NsARAM::SCRIPT );
+	NsDMA::toARAM( p_new->mScriptOffset, p_new_script, SCRIPT_HEADER_SIZE+compressed_size );
+	Mem::Free( p_new_script );
+#else
+	p_new->mpScript=p_new_script;
+#endif		// __PLAT_NGC__
+	
+	Mem::Free(p_compress_buffer);
+	
+	// Now that the new script has been loaded, the script cache needs to be refreshed in case any existing
+	// CScript's are running this script. They will all get restarted later (see file.cpp)
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	// Not asserting if p_script_cache is NULL, because it will be when all the q-files are loaded on startup.
+	if (p_script_cache)	
+	{
+		p_script_cache->RefreshAfterReload(nameChecksum);
+	}
+	#endif
+
+	// Store the name of the source qb in the symbol so that the qb is able to be unloaded.
+	p_new->mSourceFileNameChecksum=Crc::GenerateCRCFromString(p_fileName);
+	
+	return p_new;
+}
+
+// Creates a symbol defined by a name, followed by equals, followed by a value.
+static uint8 *sCreateSymbolOfTheFormNameEqualsValue(uint8 *p_token, const char *p_fileName, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols)
+{
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+	
+	Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nExpected ESCRIPTTOKEN_NAME"));
+	++p_token;
+	uint32 name_checksum=Read4Bytes(p_token).mChecksum;
+	p_token+=4;
+	p_token=SkipEndOfLines(p_token);
+	Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_EQUALS,("\nExpected an equals sign to follow '%s' at line %d, %s\nThe most likely explanation is that you have a command\noutside of a script ... endscript",FindChecksumName(name_checksum),GetLineNumber(p_token),p_fileName));
+	++p_token;
+	p_token=SkipEndOfLines(p_token);
+
+	// We have a name followed by an equals, so this is defining some symbol with that name,
+	// eg Foo=6, or Foo="Blaa"
+	// So first of all, see if any symbol already exists with that name, and if it does then
+	// remove it, or assert.
+	CSymbolTableEntry *p_existing_entry=LookUpSymbol(name_checksum);
+	if (p_existing_entry)
+	{
+		if (assertIfDuplicateSymbols)
+		{
+			Dbg_MsgAssert(0,("The symbol '%s' is defined twice, in %s and %s\n Try running CleanAss ",FindChecksumName(name_checksum),FindChecksumName(p_existing_entry->mSourceFileNameChecksum),p_fileName));
+		}
+		
+		CleanUpAndRemoveSymbol(p_existing_entry);
+	}		
+
+	// Create a new symbol with the given name.
+	CSymbolTableEntry *p_new=CreateNewSymbolEntry(name_checksum);
+	Dbg_MsgAssert(p_new,("NULL p_new ??"));
+
+	// Store the name of the source qb in the symbol so that the qb is able to be unloaded.
+	// Note: This used to be after the switch statement below. Moved it here so that if any
+	// assert goes off in the switch statement the file name info will be present in the symbol.
+	p_new->mSourceFileNameChecksum=Crc::GenerateCRCFromString(p_fileName);
+
+	// Now see what type of value follows the equals, and fill in the new symbol accordingly.
+	switch (*p_token)
+	{
+		case ESCRIPTTOKEN_OPENPARENTH:
+		{
+			CComponent *p_comp=new CComponent;
+			p_token=Evaluate(p_token,NULL,p_comp);
+			Dbg_MsgAssert(p_comp->mType!=ESYMBOLTYPE_NONE,("Global symbol '%s' did not evaluate to anything ...",FindChecksumName(p_comp->mNameChecksum)));
+			p_new->mType=p_comp->mType;
+			p_new->mUnion=p_comp->mUnion;
+			p_comp->mUnion=0;
+			delete p_comp;
+			break;
+		}
+		
+		case ESCRIPTTOKEN_INTEGER:
+		case ESCRIPTTOKEN_FLOAT:
+		case ESCRIPTTOKEN_VECTOR:
+		case ESCRIPTTOKEN_PAIR:
+		case ESCRIPTTOKEN_STRING:
+		case ESCRIPTTOKEN_LOCALSTRING:
+		case ESCRIPTTOKEN_NAME:
+		{
+			CComponent *p_comp=new CComponent;
+			p_token=FillInComponentUsingQB(p_token,NULL,p_comp);
+			p_new->mType=p_comp->mType;
+			p_new->mUnion=p_comp->mUnion;
+			p_comp->mUnion=0;
+			delete p_comp;
+			break;
+		}
+
+		case ESCRIPTTOKEN_STARTSTRUCT:
+		{
+			p_new->mType=ESYMBOLTYPE_STRUCTURE;
+			p_new->mpStructure=new CStruct;
+			
+			p_token=sAddComponentsWithinCurlyBraces(p_new->mpStructure,p_token);
+			break;
+		}
+		case ESCRIPTTOKEN_STARTARRAY:
+		{
+			p_new->mType=ESYMBOLTYPE_ARRAY;
+			
+			p_new->mpArray=new CArray;
+			p_token=sInitArrayFromQB(p_new->mpArray,p_token);
+			break;                
+		}	
+			
+		default:
+			Dbg_MsgAssert(0,("\nUnexpected type value of %d given to symbol '%s', line %d of %s",*p_token,FindChecksumName(name_checksum),GetLineNumber(p_token),p_fileName));
+			break;
+	} // switch (*p_token)
+	
+	return p_token;
+}
+
+// Given a pointer to an un-preprocessed script, this will parse through it converting function names
+// to either cfunction pointers or member function tokens.
+void PreProcessScript(uint8 *p_token)
+{
+	// Skip over the default params
+	p_token=SkipToStartOfNextLine(p_token);
+	
+	while (*p_token!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT) 
+	{
+		switch (*p_token)
+		{
+			case ESCRIPTTOKEN_KEYWORD_IF:
+				++p_token;
+				break;
+				
+			case ESCRIPTTOKEN_NAME:
+			{
+				++p_token;
+				uint32 name_checksum=Read4Bytes(p_token).mChecksum;
+				p_token+=4;
+	
+				// Look up the name to see if it is a cfunction or member function.
+				CSymbolTableEntry *p_entry=Resolve(name_checksum);
+				// Must not assert if p_entry is NULL, cos they might just be loading in
+				// a qb file that refers to a script that has not been written yet.
+				if (p_entry)
+				{
+					if (p_entry->mType==ESYMBOLTYPE_CFUNCTION)
+					{
+						// Change the token type and replace the checksum with the
+						// function pointer to save having to resolve it later.
+						*(p_token-5)=ESCRIPTTOKEN_RUNTIME_CFUNCTION;
+						Dbg_MsgAssert(p_entry->mpCFunction,("NULL p_entry->mpCFunction"));
+						Write4Bytes(p_token-4, (uint32)p_entry->mpCFunction);
+					}
+					else if (p_entry->mType==ESYMBOLTYPE_MEMBERFUNCTION)
+					{
+						// Saves having to look up the checksum later to find out that it is
+						// a member function.
+						*(p_token-5)=ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION;
+					}	
+				}		
+				p_token=SkipToStartOfNextLine(p_token);
+				break;
+			}	
+			default:
+				p_token=SkipToStartOfNextLine(p_token);
+				break;
+		}
+	}	
+}
+
+static CStoredRandom *sFindStoredRandom(const uint8 *p_token, EScriptToken type, int numItems)
+{
+	CScript *p_current_script=GetCurrentScript();
+	
+	CStoredRandom *p_search=sp_first_stored_random;
+	while (p_search)
+	{
+		if (p_search->mpToken==p_token &&
+			p_search->mType==type &&
+			p_search->mNumItems==numItems)
+		{
+			if (p_search->mpScript==p_current_script || p_search->mpScript==NULL)
+			{
+				return p_search;
+			}
+		}
+			
+		p_search=p_search->mpNext;
+	}	
+	return NULL;
+}
+
+static CStoredRandom *sCreateNewStoredRandom()
+{
+	if (s_num_stored_randoms>=MAX_STORED_RANDOMS)
+	{
+		Dbg_MsgAssert(sp_last_stored_random,("NULL sp_last_stored_random ?"));
+		
+		CStoredRandom *p_new_last=sp_last_stored_random->mpPrevious;
+		delete sp_last_stored_random;
+		
+		sp_last_stored_random=p_new_last;
+		if (p_new_last)
+		{
+			p_new_last->mpNext=NULL;
+		}	
+	}
+	
+	CStoredRandom *p_new=new CStoredRandom;
+	p_new->mpNext=sp_first_stored_random;
+	p_new->mpPrevious=NULL;
+	
+	if (sp_first_stored_random)
+	{
+		sp_first_stored_random->mpPrevious=p_new;
+	}	
+	else
+	{
+		Dbg_MsgAssert(sp_last_stored_random==NULL,("sp_last_stored_random not NULL?"));
+		sp_last_stored_random=p_new;
+	}
+	
+	sp_first_stored_random=p_new;
+	return p_new;
+}
+
+S4Bytes Read4Bytes(const uint8 *p_long)
+{
+    S4Bytes four_bytes;
+	
+	#ifdef __PLAT_NGPS__
+	if ( (((uint32)p_long)&3)==0 )
+	{
+		// Small speed opt
+		four_bytes.mUInt=*(uint32*)p_long;
+	}
+	else
+	{
+		four_bytes.mUInt=(p_long[0])|(p_long[1]<<8)|(p_long[2]<<16)|(p_long[3]<<24);
+	}	
+	#else
+	four_bytes.mUInt=(p_long[0])|(p_long[1]<<8)|(p_long[2]<<16)|(p_long[3]<<24);
+	#endif
+    return four_bytes;
+}
+
+// Gets 2 bytes from p_short, which may not be long word aligned.
+S2Bytes Read2Bytes(const uint8 *p_short)
+{
+    S2Bytes two_bytes;
+    two_bytes.mUInt=p_short[0]|(p_short[1]<<8);
+    return two_bytes;
+}
+
+// Used by WriteToBuffer and ReadFromBuffer in utils.cpp
+// Moved to parse.cpp just so that all these byte reading/writing functions are in one place.
+uint8 *Write4Bytes(uint8 *p_buffer, uint32 val)
+{
+	*p_buffer++=val;
+	*p_buffer++=val>>8;
+	*p_buffer++=val>>16;
+	*p_buffer++=val>>24;
+	return p_buffer;
+}
+
+uint8 *Write4Bytes(uint8 *p_buffer, float floatVal)
+{
+	uint32 val=*(uint32*)&floatVal;
+	*p_buffer++=val;
+	*p_buffer++=val>>8;
+	*p_buffer++=val>>16;
+	*p_buffer++=val>>24;
+	return p_buffer;
+}
+
+uint8 *Write2Bytes(uint8 *p_buffer, uint16 val)
+{
+	*p_buffer++=val;
+	*p_buffer++=val>>8;
+	return p_buffer;
+}
+
+// Skips over end-of-line tokens.    
+uint8 *SkipEndOfLines(uint8 *p_token)
+{
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+    while (true)
+    {
+		if (*p_token==ESCRIPTTOKEN_ENDOFLINE)
+		{
+            ++p_token;
+		}	
+        else if (*p_token==ESCRIPTTOKEN_ENDOFLINENUMBER)
+		{
+            // ESCRIPTTOKEN_ENDOFLINENUMBER contains the line number of the previous line,
+            // so that the line number of errors can be displayed.
+            p_token+=5;
+		}	
+        else   
+		{
+            break;
+		}	
+    }
+    return p_token;
+}
+
+CStoredRandom::CStoredRandom()
+{
+	mpNext=NULL;
+	mpPrevious=NULL;
+	
+	++s_num_stored_randoms;
+}
+
+CStoredRandom::~CStoredRandom()
+{
+	--s_num_stored_randoms;
+}
+
+// This gets called whenever CScript::ClearScript is called.
+void ReleaseStoredRandoms(CScript *p_script)
+{
+	CStoredRandom *p_search=sp_first_stored_random;
+	while (p_search)
+	{
+		if (p_search->mpScript==p_script)
+		{
+			p_search->mpScript=NULL;
+		}
+		p_search=p_search->mpNext;
+	}	
+}
+
+void CStoredRandom::InitIndices()
+{
+	Dbg_MsgAssert(mNumItems<=MAX_RANDOM_INDICES,("mNumItems too big"));
+	
+	for (uint16 i=0; i=2,("mNumItems must be at least 2 for RandomNoRepeat"));
+	uint8 last=mpIndices[mNumItems-1];
+	
+	uint32 num_iterations=10*mNumItems;
+	for (uint32 n=0; n= num_items ???"));
+			}
+			
+			p_token+=2*num_items;					// Skip over the weights
+			p_token+=4*chosen_index;				// Jump to the chosen offset value
+			sint32 offset=Read4Bytes(p_token).mInt;	// Get the offset
+			p_token+=4; 							// Skip over the offset
+			p_token+=offset;						// Jump to the item
+			break;
+		}
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT:
+		{
+			++p_token; // Skip over the token.
+			uint32 num_items=Read4Bytes(p_token).mUInt;
+			Dbg_MsgAssert(num_items>1,("num_items must be greater than 1 in RandomNoRepeat operator"));
+			p_token+=4; // Skip over the num_items
+			p_token+=2*num_items;					// TODO: Skip over weights
+			
+			CStoredRandom *p_stored=sFindStoredRandom(p_token,ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT,num_items);
+			if (!p_stored)
+			{
+				p_stored=sCreateNewStoredRandom();
+				Dbg_MsgAssert(p_stored,("NULL return value from sCreateNewStoredRandom()"));
+				p_stored->mpToken=p_token;
+				p_stored->mType=ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT;
+				p_stored->mNumItems=num_items;
+				
+				p_stored->mLastIndex=Mth::Rnd(num_items);
+			}
+			p_stored->mpScript=GetCurrentScript();
+			
+			uint16 jump_index=Mth::Rnd(num_items);
+			// num_items is bigger than one, so in theory it should never get stuck in
+			// an infinite loop here ...
+			int c=0; // But have a counter just in case 
+			while (jump_index==p_stored->mLastIndex)
+			{
+				++c;
+				if (c>100) break;
+				jump_index=Mth::Rnd(num_items);
+			}	
+			p_stored->mLastIndex=jump_index;
+			
+			p_token+=4*jump_index;	// Jump to the offset
+			sint32 offset=Read4Bytes(p_token).mInt;	// Get the offset
+			p_token+=4; 							// Skip over the offset
+			p_token+=offset;						// Jump to the item
+			break;
+		}
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE:
+		{
+			++p_token; // Skip over the token.
+			uint32 num_items=Read4Bytes(p_token).mUInt;
+			Dbg_MsgAssert(num_items,("Zero num_items in random operator ?"));
+			p_token+=4; // Skip over the num_items
+			p_token+=2*num_items;					// TODO: Skip over weights
+			
+			CStoredRandom *p_stored=sFindStoredRandom(p_token,ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE,num_items);
+			if (!p_stored)
+			{
+				p_stored=sCreateNewStoredRandom();
+				Dbg_MsgAssert(p_stored,("NULL return value from sCreateNewStoredRandom()"));
+				p_stored->mpToken=p_token;
+				p_stored->mType=ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE;
+				p_stored->mNumItems=num_items;
+
+				p_stored->mCurrentIndex=0;
+				Dbg_MsgAssert(num_items<=MAX_RANDOM_INDICES,("Too many entries in RandomPermute, max is %d. Line %d of file %s",MAX_RANDOM_INDICES,GetLineNumber(p_token),GetSourceFile(p_token)));
+			
+				p_stored->InitIndices();
+				p_stored->RandomizeIndices();
+			}
+			p_stored->mpScript=GetCurrentScript();
+			
+			++p_stored->mCurrentIndex;
+			if (p_stored->mCurrentIndex>=p_stored->mNumItems)
+			{
+				p_stored->RandomizeIndices(MAKE_NEW_FIRST_DIFFER_FROM_OLD_LAST);
+				p_stored->mCurrentIndex=0;
+			}
+			uint16 jump_index=p_stored->mpIndices[p_stored->mCurrentIndex];
+			Dbg_MsgAssert(jump_indexmType==ESYMBOLTYPE_QSCRIPT)
+		{
+			uint8 *p_script=p_sym->mpScript;
+			Dbg_MsgAssert(p_script,("NULL p_sym->mpScript ?"));
+			// Skip over the contents checksum.
+			p_script+=4;
+			
+			if (PointsIntoScript(p_token,p_script))
+			{
+				return FindChecksumName(p_sym->mSourceFileNameChecksum);
+			}		
+		}
+		p_sym=GetNextSymbolTableEntry(p_sym);
+	}
+	#else
+	
+	// Script caching is on, so query the script cache to find out what the script name is.
+	// Cannot scan through the symbols mpScript's as above because when script caching is
+	// on mpScript will point to compressed data, which cannot be stepped through.
+	// So ask the script cache to step through its decompressed scripts instead.
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	return p_script_cache->GetSourceFile(p_token);
+	
+	#endif
+	
+	// Oh well.
+	return "Unknown";	
+}
+
+#if 0
+void CalcSpaceUsedByLineNumberInfo()
+{
+	int space_used=0;
+	
+	CSymbolTableEntry *p_sym=GetNextSymbolTableEntry(NULL);
+	while (p_sym)
+	{
+		// For every script ...
+		if (p_sym->mType==ESYMBOLTYPE_QSCRIPT)
+		{
+			uint8 *p_script=p_sym->mpScript;
+			Dbg_MsgAssert(p_script,("NULL p_sym->mpScript ?"));
+			// Skip over the contents checksum.
+			p_script+=4;
+			
+			// Scan through the whole script looking for line number tokens.
+			while (*p_script!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT)
+			{
+				if (*p_script==ESCRIPTTOKEN_ENDOFLINENUMBER)
+				{
+					space_used+=4;
+				}	
+				p_script=SkipToken(p_script);
+			}		
+		}
+		p_sym=GetNextSymbolTableEntry(p_sym);
+	}
+	printf("Script heap space used by line number info = %d bytes\n");
+}
+#endif
+
+uint8 *FillInComponentUsingQB(uint8 *p_token, CStruct *p_args, CComponent *p_comp)
+{
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+	Dbg_MsgAssert(p_comp,("NULL p_comp"));
+	bool use_rnd2=false;
+	
+	switch (*p_token++)
+	{
+		case ESCRIPTTOKEN_INTEGER:
+			p_comp->mType=ESYMBOLTYPE_INTEGER;
+			p_comp->mIntegerValue=Read4Bytes(p_token).mInt;
+			p_token+=4;
+			break;
+		case ESCRIPTTOKEN_FLOAT:
+			p_comp->mType=ESYMBOLTYPE_FLOAT;
+			p_comp->mFloatValue=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			break;
+		case ESCRIPTTOKEN_NAME:
+			p_comp->mType=ESYMBOLTYPE_NAME;
+			p_comp->mChecksum=Read4Bytes(p_token).mChecksum;
+			p_token+=4;
+			break;
+		case ESCRIPTTOKEN_STRING:
+		{
+			p_comp->mType=ESYMBOLTYPE_STRING;
+			int len=Read4Bytes(p_token).mInt;
+			p_token+=4;
+			p_comp->mpString=CreateString((const char *)p_token);
+			p_token+=len;
+			break;
+		}
+		case ESCRIPTTOKEN_LOCALSTRING:
+		{
+			p_comp->mType=ESYMBOLTYPE_LOCALSTRING;
+			int len=Read4Bytes(p_token).mInt;
+			p_token+=4;
+			p_comp->mpString=CreateString((const char *)p_token);
+			p_token+=len;
+			break;
+		}
+		case ESCRIPTTOKEN_VECTOR:
+		{
+			p_comp->mType=ESYMBOLTYPE_VECTOR;
+			float x=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			float y=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			float z=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			p_comp->mpVector=new CVector;
+			p_comp->mpVector->mX=x;
+			p_comp->mpVector->mY=y;
+			p_comp->mpVector->mZ=z;
+			break;
+		}	
+		case ESCRIPTTOKEN_PAIR:
+		{
+			p_comp->mType=ESYMBOLTYPE_PAIR;
+			float x=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			float y=Read4Bytes(p_token).mFloat;
+			p_token+=4;
+			p_comp->mpPair=new CPair;
+			p_comp->mpPair->mX=x;
+			p_comp->mpPair->mY=y;
+			break;
+		}	
+		case ESCRIPTTOKEN_STARTSTRUCT:
+		{
+			// Rewind p_token to point to ESCRIPTTOKEN_STARTSTRUCT, because the 
+			// sAddComponentsWithinCurlyBraces call requires it.
+			--p_token;
+			
+			// No need to set which heap we're using, cos CStruct's and their CComponent's come off
+			// their own pools.
+			CStruct *p_structure=new CStruct;
+			p_token=sAddComponentsWithinCurlyBraces(p_structure,p_token,p_args);
+			
+			p_comp->mType=ESYMBOLTYPE_STRUCTURE;
+			p_comp->mpStructure=p_structure;
+			break;
+		}    
+		case ESCRIPTTOKEN_STARTARRAY:
+		{
+			// Rewind p_token to point to ESCRIPTTOKEN_STARTARRAY, because the CArray::Parse call requires it.
+			--p_token;
+			
+			// The CArray constructor is not hard-wired to use the script heap for it's buffer, so need
+			// to make sure we're using the script heap here.
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+			CArray *p_array=new CArray;
+			Mem::Manager::sHandle().PopContext();			
+			
+			p_token=sInitArrayFromQB(p_array,p_token,p_args);
+			
+			p_comp->mType=ESYMBOLTYPE_ARRAY;
+			p_comp->mpArray=p_array;
+			break;
+		}
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2:
+			use_rnd2=true;
+			// Intentional lack of a break here.
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
+		{
+			p_token=sCalculateRandomRange(p_token,p_comp,use_rnd2);
+			break;
+		}    
+
+		case ESCRIPTTOKEN_KEYWORD_SCRIPT:
+		{
+			Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nKeyword 'script' must be followed by a name, line %d (file name not known, sorry)",GetLineNumber(p_token)));
+			++p_token; // Skip over the ESCRIPTTOKEN_NAME
+			p_comp->mNameChecksum=Read4Bytes(p_token).mChecksum;
+			p_token+=4; // Skip over the name checksum
+			
+			// Skip p_token over the script tokens, and calculate the size.
+			const uint8 *p_script=p_token;
+			p_token=SkipOverScript(p_token);
+			uint32 size=p_token-p_script;
+			
+			p_comp->mType=ESYMBOLTYPE_QSCRIPT;
+			
+			// Allocate a buffer off the script heap
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+			Dbg_MsgAssert(size,("Zero script size"));
+			uint8 *p_new_script=(uint8*)Mem::Malloc(size);
+			Mem::Manager::sHandle().PopContext();
+			
+			// Copy the script into the new buffer.
+			const uint8 *p_source=p_script;
+			uint8 *p_dest=p_new_script;
+			for (uint32 i=0; impScript=p_new_script;
+			
+ 			break;
+		}	
+
+		case ESCRIPTTOKEN_ARG:
+		{
+			Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("Expected '<' token to be followed by a name, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
+			++p_token;
+			uint32 arg_checksum=Read4Bytes(p_token).mChecksum;
+			p_token+=4;
+			if (p_args)
+			{
+				// Look for a parameter named arg_checksum in p_args, recursing into any referenced structures
+				CComponent *p_found=p_args->FindNamedComponentRecurse(arg_checksum);
+				if (p_found)
+				{
+					// Note: There is no copy-constructor for CComponent, because that would cause a
+					// cyclic dependency between component.cpp/h and struct.cpp/h.
+					// Instead, use the CopyComponent function defined in struct.cpp
+					CopyComponent(p_comp,p_found);
+				}
+			}
+			break;	
+		}
+		
+		case ESCRIPTTOKEN_KEYWORD_ALLARGS:
+		{
+			if (p_args)
+			{
+				CStruct *p_structure=new CStruct;
+				*p_structure=*p_args;
+				
+				p_comp->mType=ESYMBOLTYPE_STRUCTURE;
+				p_comp->mpStructure=p_structure;
+			}
+			break;
+		}		
+		
+		default:
+			break;
+	}
+	return p_token;	
+}
+
+// This will evaluate the expression pointed to by p_token.
+// Any args referred to by the <,> symbols are looked for in p_args.
+
+// Expressions are terminated by any token that does not make sense as part of the expression.
+// If the expression gets terminated unexpectedly, eg if there are still open braces, 
+// then it will assert.
+static CExpressionEvaluator sExpressionEvaluator;
+
+void EnableExpressionEvaluatorErrorChecking()
+{
+	sExpressionEvaluator.EnableErrorChecking();
+}
+
+void DisableExpressionEvaluatorErrorChecking()
+{
+	sExpressionEvaluator.DisableErrorChecking();
+}
+	
+static CComponent sTemp;
+uint8 *Evaluate(uint8 *p_token, CStruct *p_args, CComponent *p_result)
+{
+	Dbg_MsgAssert(p_result,("NULL p_result"));
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+	
+	// SPEEDOPT: Make the Clear function faster
+	sExpressionEvaluator.ClearIfNeeded();
+	sExpressionEvaluator.SetTokenPointer(p_token);
+	
+	Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_OPENPARENTH,("Expected expression to begin with a '(', at line %d of %s",GetLineNumber(p_token),GetSourceFile(p_token)));
+	++p_token;
+	
+	int parenth_count=0;
+	bool generate_minus_operator=false;
+	bool expecting_value=true;
+	bool operator_is_dot=false;
+	
+	bool in_expression=true;
+	while (in_expression)
+	{
+		p_token=DoAnyRandomsOrJumps(p_token);
+		uint8 token_value=*p_token;
+		
+		switch (token_value)	
+		{
+		case ESCRIPTTOKEN_OPENPARENTH:
+			if (!expecting_value)
+			{
+				in_expression=false;
+				break;
+			}	
+			++p_token;
+			sExpressionEvaluator.OpenParenthesis();
+			generate_minus_operator=false;
+			expecting_value=true;
+			++parenth_count;
+			break;
+		case ESCRIPTTOKEN_CLOSEPARENTH:
+			++p_token;
+			if (parenth_count)
+			{
+				sExpressionEvaluator.CloseParenthesis();
+				generate_minus_operator=true;
+				expecting_value=false;
+				--parenth_count;
+			}
+			else
+			{
+				in_expression=false;
+			}	
+			break;
+		case ESCRIPTTOKEN_INTEGER:
+			if (expecting_value)
+			{
+				Dbg_MsgAssert(!generate_minus_operator,("Eh? generate_minus_operator is set?"));
+				p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
+				
+				sExpressionEvaluator.Input(&sTemp);
+				CleanUpComponent(&sTemp);
+				generate_minus_operator=true;
+				expecting_value=false;
+			}	
+			else
+			{
+				uint8 *p_new_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
+				
+				if (sTemp.mIntegerValue<0 && generate_minus_operator)
+				{
+					sExpressionEvaluator.Input(ESCRIPTTOKEN_MINUS);
+					
+					sTemp.mIntegerValue=-sTemp.mIntegerValue;
+					sExpressionEvaluator.Input(&sTemp);
+					CleanUpComponent(&sTemp);
+					
+					p_token=p_new_token;
+					generate_minus_operator=true;
+					expecting_value=false;
+				}	
+				else
+				{
+					in_expression=false;
+				}	
+			}	
+			break;
+			
+		case ESCRIPTTOKEN_FLOAT:
+			if (expecting_value)
+			{
+				Dbg_MsgAssert(!generate_minus_operator,("Eh? generate_minus_operator is set?"));
+				p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
+				
+				sExpressionEvaluator.Input(&sTemp);
+				CleanUpComponent(&sTemp);
+				generate_minus_operator=true;
+				expecting_value=false;
+			}	
+			else
+			{
+				uint8 *p_new_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
+				
+				if (sTemp.mFloatValue<0.0f && generate_minus_operator)
+				{
+					sExpressionEvaluator.Input(ESCRIPTTOKEN_MINUS);
+					
+					sTemp.mFloatValue=-sTemp.mFloatValue;
+					sExpressionEvaluator.Input(&sTemp);
+					CleanUpComponent(&sTemp);
+					
+					p_token=p_new_token;
+					generate_minus_operator=true;
+					expecting_value=false;
+				}	
+				else
+				{
+					in_expression=false;
+				}	
+			}	
+			break;
+		case ESCRIPTTOKEN_NAME:
+		{
+			if (!expecting_value)
+			{
+				in_expression=false;
+				break;
+			}	
+			p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
+			
+			// TODO: Use ResolveNameComponent instead of this switch ...
+            CSymbolTableEntry *p_entry=NULL;
+			if (operator_is_dot)
+			{
+				// This is a bit of a hack ...
+				// If the operator is the dot operator, then do not check if the right-hand-side
+				// is a global. This is because when the . is used to access members of a structure,
+				// it may be that the name of the parameter being accessed is also the name of a 
+				// global, which if substituted in place of the name would result in a nonsense 
+				// expression.
+				// The only disadvantage of this hack is that it means that a vector can no longer be
+				// dot producted with a global vector, at least not directly.
+				// I don't think there even are any global vectors at the moment anyway, and if there
+				// were they could still be dot-producted with by simply copying them into a parameter 
+				// and dotting with that instead.
+				
+				// The more general solution to the problem would be to have some sort of special
+				// syntax to indicate when a name is not to be resolved. Eg, Foo.Blaa would mean 
+				// that Blaa would be resolved, whereas Foo.~Blaa would tell the interpreter not to
+				// resolve it.
+				// That would be more complex to implement though.
+			}
+			else
+			{
+				p_entry=Resolve(sTemp.mChecksum);
+			}
+				
+			if (p_entry)
+			{
+				switch (p_entry->mType)
+				{
+				case ESYMBOLTYPE_FLOAT:
+					sTemp.mType=ESYMBOLTYPE_FLOAT;
+					sTemp.mFloatValue=p_entry->mFloatValue;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_INTEGER:
+					sTemp.mType=ESYMBOLTYPE_INTEGER;
+					sTemp.mIntegerValue=p_entry->mIntegerValue;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_VECTOR:
+					sTemp.mType=ESYMBOLTYPE_VECTOR;
+					sTemp.mpVector=p_entry->mpVector;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_PAIR:
+					sTemp.mType=ESYMBOLTYPE_PAIR;
+					sTemp.mpPair=p_entry->mpPair;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_STRING:
+					sTemp.mType=ESYMBOLTYPE_STRING;
+					sTemp.mpString=p_entry->mpString;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_LOCALSTRING:
+					sTemp.mType=ESYMBOLTYPE_LOCALSTRING;
+					sTemp.mpLocalString=p_entry->mpLocalString;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_STRUCTURE:
+					sTemp.mType=ESYMBOLTYPE_STRUCTURE;
+					sTemp.mpStructure=p_entry->mpStructure;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_ARRAY:
+					sTemp.mType=ESYMBOLTYPE_ARRAY;
+					sTemp.mpArray=p_entry->mpArray;
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				case ESYMBOLTYPE_CFUNCTION:
+				{
+					CScript *p_script=GetCurrentScript();
+					Dbg_MsgAssert(p_script,("Tried to execute the cfunc '%s' in an expression with no parent script, line %d of file %s",FindChecksumName(sTemp.mChecksum),GetLineNumber(p_token),GetSourceFile(p_token)));
+					
+					sTemp.mType=ESYMBOLTYPE_INTEGER;
+				
+					CStruct *p_function_params=new CStruct;
+					// TODO: Problem if AddComponentsUntilEndOfLine calls Evaluate() again,
+					// since only one instance of CExpressionEvaluator.
+					p_token=AddComponentsUntilEndOfLine(p_function_params,p_token,p_args);
+	                sTemp.mIntegerValue=(*p_entry->mpCFunction)(p_function_params,p_script);
+					delete p_function_params;
+					
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				}	
+				
+				case ESYMBOLTYPE_MEMBERFUNCTION:
+					Dbg_MsgAssert(0,("Sorry, cannot have calls to member functions in expressions!\nTried to call member function '%s' in an expression, line %d of file %s",FindChecksumName(sTemp.mChecksum),GetLineNumber(p_token),GetSourceFile(p_token)));
+					break;
+				
+				default:
+					sExpressionEvaluator.Input(&sTemp);
+					break;
+				}
+			}
+			else
+			{
+				sExpressionEvaluator.Input(&sTemp);
+			}
+			// Must not call CleanUpComponent because any pointer in the component will
+			// have been borrowed from the global symbol.
+			sTemp.mType=ESYMBOLTYPE_NONE;
+			sTemp.mUnion=0;
+			generate_minus_operator=true;
+			expecting_value=false;
+			break;
+		}	
+		case ESCRIPTTOKEN_STRING:
+		case ESCRIPTTOKEN_LOCALSTRING:
+		case ESCRIPTTOKEN_VECTOR:
+		case ESCRIPTTOKEN_PAIR:
+		//case ESCRIPTTOKEN_STARTARRAY:
+		case ESCRIPTTOKEN_STARTSTRUCT:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
+			if (!expecting_value)
+			{
+				in_expression=false;
+				break;
+			}	
+			p_token=FillInComponentUsingQB(p_token,p_args,&sTemp);
+			sExpressionEvaluator.Input(&sTemp);
+			CleanUpComponent(&sTemp);
+			generate_minus_operator=true;
+			expecting_value=false;
+			break;
+		
+		case ESCRIPTTOKEN_ARG:
+		{
+			if (!expecting_value)
+			{
+				in_expression=false;
+				break;
+			}	
+			++p_token;
+			Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("Expected '<' token to be followed by a name, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
+			++p_token;
+			uint32 arg_checksum=Read4Bytes(p_token).mChecksum;
+			p_token+=4;
+			if (p_args)
+			{
+				// Look for a parameter named arg_checksum in p_args, recursing into any referenced structures
+				CComponent *p_comp=p_args->FindNamedComponentRecurse(arg_checksum);
+				if (p_comp)
+				{
+					sTemp.mType=p_comp->mType;
+					sTemp.mUnion=p_comp->mUnion;
+					// It might be a name that resolves to some global, so resolve it.
+					ResolveNameComponent(&sTemp);
+
+					sExpressionEvaluator.Input(&sTemp);
+					
+					// Must not call CleanUpComponent because any pointer in the component will
+					// have been borrowed from the global symbol or p_comp.
+					sTemp.mType=ESYMBOLTYPE_NONE;
+					sTemp.mUnion=0;
+				}
+			}	
+			generate_minus_operator=true;
+			expecting_value=false;
+			break;	
+		}
+		case ESCRIPTTOKEN_KEYWORD_ALLARGS:
+		{
+			if (!expecting_value)
+			{
+				in_expression=false;
+				break;
+			}	
+			++p_token;
+			
+			if (p_args)
+			{
+				sTemp.mType=ESYMBOLTYPE_STRUCTURE;
+				sTemp.mpStructure=p_args;
+				sExpressionEvaluator.Input(&sTemp);
+			}	
+			generate_minus_operator=true;
+			expecting_value=false;
+			break;	
+		}
+		
+		case ESCRIPTTOKEN_ADD:
+		case ESCRIPTTOKEN_MINUS:
+		case ESCRIPTTOKEN_MULTIPLY:
+		case ESCRIPTTOKEN_DIVIDE:
+		case ESCRIPTTOKEN_DOT:
+		case ESCRIPTTOKEN_OR:
+		case ESCRIPTTOKEN_AND:
+		case ESCRIPTTOKEN_LESSTHAN:
+		case ESCRIPTTOKEN_GREATERTHAN:
+		case ESCRIPTTOKEN_EQUALS:
+		case ESCRIPTTOKEN_STARTARRAY:
+			++p_token;
+			sExpressionEvaluator.Input((EScriptToken)token_value);
+			generate_minus_operator=false;
+			expecting_value=true;
+			if (token_value==ESCRIPTTOKEN_DOT)
+			{
+				operator_is_dot=true;
+			}	
+			else
+			{
+				operator_is_dot=false;
+			}	
+			break;
+			
+		case ESCRIPTTOKEN_ENDARRAY:
+		case ESCRIPTTOKEN_ENDOFLINE:
+			++p_token;
+			break;
+		case ESCRIPTTOKEN_ENDOFLINENUMBER:
+			p_token+=5;
+			break;
+			
+		case ESCRIPTTOKEN_ENDOFFILE:
+		case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
+		case ESCRIPTTOKEN_COMMA:
+			Dbg_MsgAssert(0,("Unexpected token '%s' in expression, line %d of %s",GetTokenName((EScriptToken)token_value),GetLineNumber(p_token),GetSourceFile(p_token)));
+			break;	
+			
+		default:
+			// Unrecognized expression
+			Dbg_MsgAssert(0,("Don't know how to evaluate '%s', at line %d of %s",GetTokenName((EScriptToken)token_value),GetLineNumber(p_token),GetSourceFile(p_token)));
+			break;	
+		}	
+		
+		// This cannot be commented in all the time, because often the evaluator will report
+		// errors when it gets called via the FindReferences function, which scans through scripts
+		// but does not execute them. Errors occur in this case when script parameters are used
+		// in expressions, because when FindReferences is scanning through the script it has no
+		// parameters. Need to fix FindReferences so that it can scan over expressions without
+		// executing them.
+		//if (sExpressionEvaluator.GetError())
+		//{
+		//	Dbg_MsgAssert(0,("Evaluator error: File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
+		//}	
+		#ifdef	__NOPT_ASSERT__
+		if (sExpressionEvaluator.ErrorCheckingEnabled() && sExpressionEvaluator.GetError())
+		{
+			printf("Evaluator error: File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token));
+		}	
+		#endif
+	}
+		
+	sExpressionEvaluator.GetResult(p_result);
+	#ifdef	__NOPT_ASSERT__
+	if (sExpressionEvaluator.ErrorCheckingEnabled() && sExpressionEvaluator.GetError())
+	{
+		printf("Evaluator error: File %s, line %d\n",GetSourceFile(p_token),GetLineNumber(p_token));
+	}	
+	#endif
+	return p_token;
+}
+
+// Parses the components in QB format pointed to by p_token, and adds them to p_dest until the 
+// end-of-line is reached.
+// For example, p_token may point to: x=9 foo="Blaa"
+// Used when creating the structure of function parameters, and the structure of default parameters
+// for scripts.
+// p_args contains the structure defining the value of any args referred to using the <,> operators.
+// Returns a pointer to the next token after parsing the tokens pointed to by p_token.
+//
+// Note: This gets called for every line of script executed, hence is probably a bottleneck in script
+// execution speed. Could speed up a lot by pre-generating any function parameters that are constant, ie
+// contain no <,> operators.
+uint8 *AddComponentsUntilEndOfLine(CStruct *p_dest, uint8 *p_token, CStruct *p_args)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+	Dbg_MsgAssert(p_token,("NULL p_token"));
+	
+    while (true)
+    {
+		// Execute any random operators.
+		p_token=DoAnyRandomsOrJumps(p_token);
+
+        if (sIsEndOfLine(p_token)) 
+		{
+			break;
+		}	
+		
+		// This is so that function calls can be put in expressions, which are always enclosed
+		// in parentheses.
+		if (*p_token==ESCRIPTTOKEN_CLOSEPARENTH)
+		{
+			break;
+		}
+			
+        switch (*p_token)
+        {
+            case ESCRIPTTOKEN_NAME:
+            {
+                uint8 *p_name_token=p_token;
+                ++p_token;
+                uint32 name_checksum=Read4Bytes(p_token).mChecksum;
+                p_token+=4;
+				// Execute any random operators. This has to be done each time p_token is advanced.
+				p_token=DoAnyRandomsOrJumps(p_token);
+
+                if (*p_token==ESCRIPTTOKEN_EQUALS)
+                {
+                    ++p_token;
+					p_token=DoAnyRandomsOrJumps(p_token);
+					
+                    Dbg_MsgAssert(!sIsEndOfLine(p_token),("Syntax error, nothing following '=', File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
+					
+					if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
+					{
+						CComponent *p_comp=new CComponent;
+						p_token=Evaluate(p_token,p_args,p_comp);
+						if (p_comp->mType!=ESYMBOLTYPE_NONE)
+						{
+							p_comp->mNameChecksum=name_checksum;
+							p_dest->AddComponent(p_comp);
+						}
+						else
+						{
+							delete p_comp;
+						}
+					}
+					else
+					{
+						p_token=sAddComponentFromQB(p_dest,name_checksum,p_token,p_args);					
+					}	
+                }
+                else
+				{
+                    p_token=sAddComponentFromQB(p_dest,NO_NAME,p_name_token,p_args);
+				}	
+                break;
+            }
+			case ESCRIPTTOKEN_KEYWORD_ALLARGS:
+                ++p_token;
+				if (p_args)
+				{
+					*p_dest+=*p_args;
+				}	
+				break;
+            case ESCRIPTTOKEN_STARTSTRUCT:
+                p_token=sAddComponentsWithinCurlyBraces(p_dest,p_token,p_args);
+                break;
+			case ESCRIPTTOKEN_ARG:
+				p_token=sAddComponentFromQB(p_dest,NO_NAME,p_token,p_args);
+				break;
+            case ESCRIPTTOKEN_ENDOFLINE:
+            case ESCRIPTTOKEN_ENDOFLINENUMBER:
+                break;
+            case ESCRIPTTOKEN_ENDOFFILE:
+				Dbg_MsgAssert(0,("End of file reached during AddComponentsUntilEndOfLine ?"));
+                break;
+			
+			case ESCRIPTTOKEN_COMMA:
+				++p_token;
+				break;
+				
+            default:
+			{
+				if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
+				{
+					CComponent *p_comp=new CComponent;
+					p_token=Evaluate(p_token,p_args,p_comp);
+					
+					if (p_comp->mType==ESYMBOLTYPE_STRUCTURE)
+					{
+						// The CStruct::AddComponent function does not allow unnamed structure
+						// components to be added, so merge in the contents of the structure instead.
+						p_dest->AppendStructure(p_comp->mpStructure);
+						// Now p_comp does have to be cleaned up and deleted, because it has not
+						// been given to p_dest.
+						CleanUpComponent(p_comp);
+						delete p_comp;
+					}
+					else
+					{
+						if (p_comp->mType!=ESYMBOLTYPE_NONE)
+						{
+							p_comp->mNameChecksum=NO_NAME;
+							p_dest->AddComponent(p_comp);
+						}
+						else
+						{
+							delete p_comp;
+						}	
+					}				
+				}
+				else
+				{
+					p_token=sAddComponentFromQB(p_dest,NO_NAME,p_token,p_args);
+				}	
+                break;
+			}	
+        }            
+    } 
+
+	// Note: Does not skip over the end-of-lines. This is so that if any asserts go off during the
+	// C-function or member function call that this structure is going to be used as parameters too,
+	// the assert will print the correct line number, rather than the one following.
+    return p_token;
+}
+
+// Deletes any entity pointed to by p_sym, then removes p_sym from the symbol table.
+void CleanUpAndRemoveSymbol(CSymbolTableEntry *p_sym)
+{
+	Dbg_MsgAssert(p_sym,("NULL p_sym"));
+	switch (p_sym->mType)
+	{
+		case ESYMBOLTYPE_INTEGER:
+		case ESYMBOLTYPE_FLOAT:
+		case ESYMBOLTYPE_NAME:
+			// Nothing to delete
+			break;		
+			
+		case ESYMBOLTYPE_STRING:
+			Dbg_MsgAssert(p_sym->mpString,("NULL p_sym->mpString"));
+			DeleteString(p_sym->mpString);
+			break;
+		case ESYMBOLTYPE_LOCALSTRING:
+			Dbg_MsgAssert(p_sym->mpLocalString,("NULL p_sym->mpLocalString"));
+			DeleteString(p_sym->mpLocalString);
+			break;
+		case ESYMBOLTYPE_PAIR:
+			Dbg_MsgAssert(p_sym->mpPair,("NULL p_sym->mpPair"));
+			delete p_sym->mpPair;
+			break;
+		case ESYMBOLTYPE_VECTOR:
+			Dbg_MsgAssert(p_sym->mpVector,("NULL p_sym->mpVector"));
+			delete p_sym->mpVector;
+			break;
+		case ESYMBOLTYPE_QSCRIPT:
+#ifdef __PLAT_NGC__
+			NsARAM::free( p_sym->mScriptOffset );
+#else
+			Dbg_MsgAssert(p_sym->mpScript,("NULL p_sym->mpScript"));
+			Mem::Free(p_sym->mpScript);
+#endif		// __PLAT_NGC__
+			break;
+		case ESYMBOLTYPE_STRUCTURE:
+			Dbg_MsgAssert(p_sym->mpStructure,("NULL p_sym->mpStructure"));
+			delete p_sym->mpStructure;
+			break;
+		case ESYMBOLTYPE_ARRAY:
+			Dbg_MsgAssert(p_sym->mpArray,("NULL p_sym->mpArray"));
+			CleanUpArray(p_sym->mpArray);
+			delete p_sym->mpArray;
+			break;
+		default:
+			Dbg_MsgAssert(0,("Bad type of '%s' in p_sym",GetTypeName(p_sym->mType)));
+			break;
+	}
+	p_sym->mUnion=0;
+
+	RemoveSymbol(p_sym);
+}
+
+// TODO: Remove? Currently used in cfuncs.cpp to delete the NodeArray, but now that we
+// have the ability to unload qb files that should not be necessary any more.
+// Note: Just found it is also needed in parkgen.cpp, so don't remove after all. 
+void CleanUpAndRemoveSymbol(uint32 checksum)
+{
+	CSymbolTableEntry *p_sym=LookUpSymbol(checksum);
+	if (p_sym)
+	{
+		CleanUpAndRemoveSymbol(p_sym);
+	}	
+}
+
+void CleanUpAndRemoveSymbol(const char *p_symbolName)
+{
+	CleanUpAndRemoveSymbol(Crc::GenerateCRCFromString(p_symbolName));
+}
+	
+#define HASHBITS 12
+struct SChecksum
+{
+	uint32 mChecksum;
+	char *mpName;
+	SChecksum *mpNext;
+};
+
+static SChecksum *sp_hash_table=NULL;
+
+const char *GetChecksumNameFromLastQB(uint32 checksum)
+{
+	Dbg_MsgAssert(sp_hash_table,("NULL sp_hash_table"));
+	
+	SChecksum *p_entry=&sp_hash_table[checksum&((1<mChecksum==checksum)
+		{
+			return p_entry->mpName;
+		}	
+		p_entry=p_entry->mpNext;
+	}	
+	
+	return NULL;
+}	
+
+void RemoveChecksumNameLookupHashTable()
+{
+	if (sp_hash_table)
+	{
+		SChecksum *p_entry=sp_hash_table;
+		for (uint32 i=0; i<(1<mpNext;
+			while (p_ch)
+			{
+				SChecksum *p_next=p_ch->mpNext;
+				// The dynamically allocated entries must have had their mpName set.
+				Dbg_MsgAssert(p_ch->mpName,("NULL p_ch->mpName ?"));
+				Mem::Free(p_ch->mpName);
+				Mem::Free(p_ch);
+				p_ch=p_next;
+			}
+			// The entries in the contiguous array may not have their mpName set.	
+			if (p_entry->mpName)
+			{
+				Mem::Free(p_entry->mpName);
+			}	
+			++p_entry;
+		}	
+	
+		Mem::Free(sp_hash_table);
+		sp_hash_table=NULL;
+	}	
+}
+
+uint8 *SkipToStartOfNextLine(uint8 *p_token)
+{
+	int curly_bracket_count=0;
+	int square_bracket_count=0;
+	int parenth_count=0;
+	
+	while (true)
+	{
+		if (*p_token==ESCRIPTTOKEN_STARTSTRUCT) ++curly_bracket_count;
+		if (*p_token==ESCRIPTTOKEN_ENDSTRUCT) --curly_bracket_count;
+		Dbg_MsgAssert(curly_bracket_count>=0,("Negative curly_bracket_count ??"));
+
+		if (*p_token==ESCRIPTTOKEN_STARTARRAY) ++square_bracket_count;
+		if (*p_token==ESCRIPTTOKEN_ENDARRAY) --square_bracket_count;
+		Dbg_MsgAssert(square_bracket_count>=0,("Negative square_bracket_count ??"));
+
+		if (*p_token==ESCRIPTTOKEN_OPENPARENTH) ++parenth_count;
+		if (*p_token==ESCRIPTTOKEN_CLOSEPARENTH) --parenth_count;
+		Dbg_MsgAssert(parenth_count>=0,("Negative parenth_count, File %s, line %d",GetSourceFile(p_token),GetLineNumber(p_token)));
+		
+		if (curly_bracket_count==0 && 
+			square_bracket_count==0 &&
+			parenth_count==0 &&
+			sIsEndOfLine(p_token))
+		{
+			break;
+		}		
+		p_token=SkipToken(p_token);
+	}
+	
+	p_token=SkipToken(p_token);
+	return p_token;
+}
+
+// Parses a qb file, creating all the symbols (scripts, arrays etc) defined within.
+// If the assertIfDuplicateSymbols is set, then it will assert if any of the symbols in the qb
+// exist already.
+// This flag is set when loading all the startup qb's, because we don't want clashing symbols.
+// It is not set when reloading a qb during development though, since that involves reloading
+// existing symbols.
+// The file name is also passed so that each symbol knows what qb it came from, which allows
+// all the symbols from a particular qb to be unloaded using the UnloadQB function (in file.cpp)
+void ParseQB(const char *p_fileName, uint8 *p_qb, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols, bool allocateChecksumNameLookupTable)
+{
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+	Dbg_MsgAssert(p_qb,("NULL p_qb"));
+
+	// Do a first parse through the qb to register the checksum names.
+	// They get added to a lookup table that can be queried using GetChecksumNameFromLastQB defined above.
+	// The lookup table only contains the checksum names defined in this qb.
+	// GetChecksumNameFromLastQB is currently only used by the game-specific nodearray.cpp to generate a lookup
+	// table of node name prefixes.
+	//
+	// If __NOPT_ASSERT__ is set the checksum names also get added to a big permanent lookup table so that
+	// the FindChecksumName function can be used at any time in asserts.
+	// 
+    uint8 *p_token=p_qb;
+    bool end_of_file=false;
+
+	// Remove any existing checksum-name-lookup hash table.
+	RemoveChecksumNameLookupHashTable();
+
+	// K: Added the passed flag allocateChecksumNameLookupTable so that we can avoid allocating the table
+	// if we know it will not be needed. Gary needs this for the cutscenes where there is not enough memory
+	// to allocate the table.
+	// By default the flag is set to true however because the table is needed when generating prefix info
+	// for the nodearray, and we don't know whether the qb contains the nodearray until after finishing
+	// parsing it.
+	if (allocateChecksumNameLookupTable)
+	{
+		// Reallocate it.
+		bool use_debug_heap=false;
+		#ifdef	__NOPT_ASSERT__	
+		if (!assertIfDuplicateSymbols)
+		{
+			// They're doing a qbr ...
+			if (Mem::Manager::sHandle().GetHeap(0x70cb0238/*DebugHeap*/))
+			{
+				// The debug heap exists, so use it.
+				// Need to do this because there is often not enough memory otherwise.
+				use_debug_heap=true;
+			}	
+		}	
+		#endif
+	
+		if (use_debug_heap)
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+		}	
+		else
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+		}
+		Dbg_MsgAssert(sp_hash_table==NULL,("sp_hash_table not NULL ?"));
+		sp_hash_table=(SChecksum*)Mem::Malloc((1<mChecksum=0;
+			p_entry->mpName=NULL;
+			p_entry->mpNext=NULL;
+			++p_entry;
+		}	
+	}
+	
+	// Line number info in qb's is excluded when doing a release build to save memory.
+	// Need to stop with an error message if there is line number info when there should not
+	// be, otherwise the release build will run out of memory & it will be hard to track down
+	// why since there will be no asserts.
+	// This flag indicates whether to give the error message or not.
+	bool allow_line_number_info=false;
+	if (!assertIfDuplicateSymbols)
+	{
+		// Doing a qbr, so allow line number info, otherwise
+		// we won't be able to qbr when __NOPT_ASSERT__ is not defined.
+		allow_line_number_info=true;
+	}	
+	#ifdef __NOPT_ASSERT__
+	// Allow line number info when asserts are on, cos the script heap will have an extra 200K for them.
+	allow_line_number_info=true;
+	#endif
+	#ifdef __PLAT_WN32__
+	// PC has lots of memory.
+	allow_line_number_info=true;
+	#endif
+		
+
+    while (!end_of_file)
+	{
+		switch (*p_token)
+		{
+		case ESCRIPTTOKEN_CHECKSUM_NAME: 
+		{
+			++p_token;
+			uint32 checksum=Read4Bytes(p_token).mChecksum;
+			const char *p_name=(const char*)(p_token+4);
+	
+			if (allocateChecksumNameLookupTable)
+			{
+				// Add it to the table.
+				SChecksum *p_entry=&sp_hash_table[checksum&((1<mpName)
+				{
+					// The first slot is already occupied, so create a new SChecksum and
+					// link it in between the first and the rest.
+					SChecksum *p_new=(SChecksum*)Mem::Malloc(sizeof(SChecksum));
+					p_new->mChecksum=checksum;
+					p_new->mpName=(char*)Mem::Malloc(strlen(p_name)+1);
+					strcpy(p_new->mpName,p_name);
+					
+					p_new->mpNext=p_entry->mpNext;
+					p_entry->mpNext=p_new;
+				}
+				else
+				{
+					// First slot is not occupied, so stick it in there.
+					p_entry->mChecksum=checksum;
+					p_entry->mpName=(char*)Mem::Malloc(strlen(p_name)+1);
+					strcpy(p_entry->mpName,p_name);
+					p_entry->mpNext=NULL;
+				}	
+			}
+						
+			// Add to the big lookup table of all checksum names, used by FindChecksumName
+			// Doing this here rather than the main parsing loop after this one so that the names 
+			// are registered first in case any asserts go off.
+			AddChecksumName(checksum,p_name);
+				
+			// Skip over the checksum.
+			p_token+=4;
+			// Skip over the name.
+			while (*p_token)
+			{
+				++p_token;
+			}
+			// Skip over the terminator.
+			++p_token;	
+			break;
+		}
+			
+        case ESCRIPTTOKEN_ENDOFFILE:
+            end_of_file=true;
+            break;
+
+		case ESCRIPTTOKEN_ENDOFLINENUMBER:
+		{
+			if (allow_line_number_info)
+			{
+				p_token=SkipToken(p_token);
+			}
+			else
+			{
+				printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n");
+				printf("Found line number info in '%s' !\nLine number info needs to be removed when __NOPT_ASSERT__ is not defined\nin order to save memory.\nTry doing a 'cleanass nolinenumbers' and running again.\n",p_fileName);
+				while (1);
+			}	
+			break;
+		}
+            
+        default:
+            p_token=SkipToken(p_token);
+            break;
+        }    
+    }
+    p_token=p_qb;
+	end_of_file=false;
+	// That's all the checksum names added.
+
+
+	// Register the file name checksum so that FindChecksumName will be able to find it.
+	// (Required if a script assert goes off that needs to print the source file name)
+	AddChecksumName(Crc::GenerateCRCFromString(p_fileName),p_fileName);
+
+	// Store the checksum of the file name so that if an assert goes off during parsing the file name can be printed.
+	s_qb_being_parsed=Crc::GenerateCRCFromString(p_fileName);
+
+	// Make sure we're using the script heap, cos we're about to create a bunch of scripty stuff.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	
+    while (!end_of_file)
+    {
+        p_token=SkipEndOfLines(p_token);
+		
+        switch (*p_token)
+        {
+			case ESCRIPTTOKEN_NAME:
+			{
+				p_token=sCreateSymbolOfTheFormNameEqualsValue(p_token,p_fileName,assertIfDuplicateSymbols);
+				break;    
+			}
+			case ESCRIPTTOKEN_KEYWORD_SCRIPT:
+			{
+				// Get the new script's name checksum ...
+				++p_token; // Skip over the ESCRIPTTOKEN_KEYWORD_SCRIPT
+				Dbg_MsgAssert(*p_token==ESCRIPTTOKEN_NAME,("\nKeyword 'script' must be followed by a name, line %d of %s",GetLineNumber(p_token),p_fileName));
+				++p_token; // Skip over the ESCRIPTTOKEN_NAME
+				uint32 name_checksum=Read4Bytes(p_token).mChecksum;
+				p_token+=4; // Skip over the name checksum
+	
+	
+				// Calculate the length of the script data in bytes.
+				uint8 *p_script_data=p_token;
+				p_token=SkipOverScript(p_token);
+				uint32 script_data_size=p_token-p_script_data;
+	
+				
+				// Calculate a checksum of the contents of the new script.
+				// The purpose of the contents checksum is to prevent unnecessary restarting of CScript's
+				// when a qb is reloaded. It may be that only one script in the qb has changed, so we only
+				// want the CScript's that were referencing that script to restart.
+				
+				// Note: The LoadQB function in gel\scripting\file.cpp does the restarting of existing
+				// CScripts by checking the mGotReloaded flag in the CSymbolTableEntry for the script.
+				
+				uint32 new_contents_checksum=CalculateScriptContentsChecksum(p_script_data);
+				
+				// Check to see if a script with this name exists already.			
+				CSymbolTableEntry *p_existing_entry=LookUpSymbol(name_checksum);
+				if (p_existing_entry)
+				{
+					if (assertIfDuplicateSymbols)
+					{
+						Dbg_MsgAssert(0,("The symbol '%s' is defined twice, in %s and %s",FindChecksumName(name_checksum),FindChecksumName(p_existing_entry->mSourceFileNameChecksum),p_fileName));
+					}
+				
+					if (p_existing_entry->mType==ESYMBOLTYPE_QSCRIPT)
+					{
+						// A script with the same name does already exist.
+						// Compare its contents checksum with the new one.
+#ifdef __PLAT_NGC__
+						uint32 header[(SCRIPT_HEADER_SIZE/4)];
+						NsDMA::toMRAM( header, p_existing_entry->mScriptOffset, SCRIPT_HEADER_SIZE );
+						uint32 old_contents_checksum=header[0];
+#else
+						Dbg_MsgAssert(p_existing_entry->mpScript,("NULL p_existing_entry->mpScript ??"));
+						uint32 old_contents_checksum=*(uint32*)(p_existing_entry->mpScript);
+#endif		// __PLAT_NGC__
+						
+						if (new_contents_checksum != old_contents_checksum)
+						{
+							// The contents of the new script differ from the old!
+							// So remove the old one, and create the new.
+							CleanUpAndRemoveSymbol(p_existing_entry);
+							sCreateScriptSymbol(name_checksum,
+												new_contents_checksum,
+												p_script_data,
+												script_data_size,
+												p_fileName);
+						}
+						else
+						{
+							// The contents of the script have not changed!
+							// So there's nothing to do.
+						}
+					}
+					else
+					{
+						// Remove the existing symbol, whatever it is. (It isn't a script)
+						CleanUpAndRemoveSymbol(p_existing_entry);
+						// Create the new script symbol.
+						sCreateScriptSymbol(name_checksum,new_contents_checksum,p_script_data,script_data_size,p_fileName);
+					}
+				}	
+				else
+				{
+					// No symbol currently exists with this name, so create the new script symbol.
+					sCreateScriptSymbol(name_checksum,new_contents_checksum,p_script_data,script_data_size,p_fileName);
+				}			
+				break;
+			}
+	
+			case ESCRIPTTOKEN_CHECKSUM_NAME: 
+				// These have been done already, so skip over.
+				p_token=SkipToken(p_token);
+				break;
+				
+			case ESCRIPTTOKEN_ENDOFFILE:
+				end_of_file=true;
+				break;
+				
+			default:
+				Dbg_MsgAssert(0,("\nConfused by line %d of %s.\nExpected either a script definition (Script ... EndScript)\nor a constant definition (Something=...)",GetLineNumber(p_token),p_fileName));
+				break;
+        }    
+    }
+
+	Mem::Manager::sHandle().PopContext();
+	s_qb_being_parsed=0;
+}
+
+// Used by the EditorCameraComponent when it checks polys to see if they are Kill polys
+bool ScriptContainsName(uint8 *p_script, uint32 searchName)
+{
+	Dbg_MsgAssert(p_script,("NULL p_script"));
+
+	//int num_names_found=0;
+	//uint32 last_name=0;
+	
+	uint8 *p_token=p_script;
+	while (*p_token!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT) 
+	{
+		switch (*p_token)
+		{
+			case ESCRIPTTOKEN_NAME:
+			{
+				++p_token;
+				uint32 name=Read4Bytes(p_token).mChecksum;
+				p_token+=4;
+				if (name == searchName)
+				{
+					return true;
+				}	
+				
+				//++num_names_found;
+				//last_name=name;
+				break;
+			}	
+				
+			default:
+				p_token=SkipToken(p_token);
+				break;
+		}
+	}	
+	
+	/*
+	// I put this in so that scripts that consist of a single call to another script will have that
+	// scanned. This was to fix a bug where the kill poly over the swimming pool in Hawaii was not
+	// being detected. However, this fix then meant that the kill polys around the hotel would get
+	// detected, meaning that the cursor could never be got off the hotel. So commented it out
+	// for the moment, since at least the first bug does not make the cursor get stuck.
+	if (num_names_found==1)
+	{
+		Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+		Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	
+		p_script=p_script_cache->GetScript(last_name);
+		if (p_script)
+		{
+			bool contains_name=ScriptContainsName(p_script, searchName);
+			p_script_cache->DecrementScriptUsage(last_name);
+			return contains_name;
+		}
+	}
+	*/
+	
+	return false;
+}
+
+// Used by the EditorCameraComponent when it checks polys to see if they are Kill polys
+bool ScriptContainsAnyOfTheNames(uint32 scriptName, uint32 *p_names, int numNames)
+{
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+
+	uint8 *p_script=p_script_cache->GetScript(scriptName);
+	Dbg_MsgAssert(p_script,("NULL p_script for %s",Script::FindChecksumName(scriptName)));
+
+	bool contains_name=false;
+	
+	Dbg_MsgAssert(p_names,("NULL p_names"));
+	uint32 *p_name=p_names;
+	for (int i=0; iDecrementScriptUsage(scriptName);
+	
+	return contains_name;		
+}
+
+} // namespace Script
+
+
+
diff --git a/Code/Gel/Scripting/parse.h b/Code/Gel/Scripting/parse.h
new file mode 100644
index 0000000..8056155
--- /dev/null
+++ b/Code/Gel/Scripting/parse.h
@@ -0,0 +1,159 @@
+#ifndef	__SCRIPTING_PARSE_H
+#define	__SCRIPTING_PARSE_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#include  // For enums
+#endif
+
+#ifndef __SCRIPTING_TOKENS_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifdef __PLAT_NGC__
+#define SCRIPT_HEADER_SIZE 32
+#else
+#define SCRIPT_HEADER_SIZE 12
+#endif
+
+namespace Script
+{
+
+class CStruct;
+class CComponent;
+class CSymbolTableEntry;
+class CScript;
+
+// Used when extracting 4 bytes from a .qb, where the 4 bytes could represent
+// different types of thing. (Saves messy casting)
+struct S4Bytes
+{
+    union
+    {
+        uint32 mChecksum;
+		uint32 mUInt;
+        int mInt;
+        float mFloat;
+        bool (*mpCFunction)(CStruct *pParams, CScript *pCScript);
+    };
+};
+
+// Same, but for 2 bytes.
+struct S2Bytes
+{
+    union
+    {
+		uint16 mUInt;
+        sint16 mInt;
+    };
+};
+
+// Gets 4 bytes from p_long, which may not be long word aligned.
+S4Bytes Read4Bytes(const uint8 *p_long);
+S2Bytes Read2Bytes(const uint8 *p_short);
+uint8 *Write4Bytes(uint8 *p_buffer, uint32 val);
+uint8 *Write4Bytes(uint8 *p_buffer, float floatVal);
+uint8 *Write2Bytes(uint8 *p_buffer, uint16 val);
+
+#define MAX_STORED_RANDOMS 100
+
+#define MAKE_NEW_FIRST_DIFFER_FROM_OLD_LAST true
+#ifdef __PLAT_WN32__
+class CStoredRandom
+#else
+class CStoredRandom : public Mem::CPoolable
+#endif
+{
+public:	
+	CStoredRandom();
+	~CStoredRandom();
+	
+	// These identify which Random operator this 'belongs' to.
+	// mpToken alone is not enough because there may be two instances of a CScript running
+	// that script.
+	CScript *mpScript;
+	const uint8 *mpToken;
+	// These are also stored to further verify that this is the correct one if a match for
+	// mpToken is found. They could differ due to a script reload.
+	uint16 mNumItems;
+	EScriptToken mType;
+	
+	// The following members are used by the RandomPermute operator
+	uint16 mCurrentIndex;
+	
+	#define MAX_RANDOM_INDICES 64
+	uint8 mpIndices[MAX_RANDOM_INDICES];
+
+	void InitIndices();
+	void RandomizeIndices(bool makeNewFirstDifferFromOldLast=false);
+	
+	// This is used by the RandomNoRepeat operator
+	uint32 mLastIndex;
+
+	
+	CStoredRandom *mpNext;
+	CStoredRandom *mpPrevious;
+};
+
+void ReleaseStoredRandoms(CScript *p_script);
+
+// Used by CScript::Update
+uint8 *SkipToken(uint8 *p_token);
+uint8 *SkipEndOfLines(uint8 *p_token);
+uint8 *DoAnyRandomsOrJumps(uint8 *p_token);
+
+uint8 *SkipOverScript(uint8 *p_token);
+
+void EnableExpressionEvaluatorErrorChecking();
+void DisableExpressionEvaluatorErrorChecking();
+uint8 *Evaluate(uint8 *p_token, CStruct *p_args, CComponent *p_result);
+uint8 *FillInComponentUsingQB(uint8 *p_token, CStruct *p_args, CComponent *p_comp);
+
+// Exported because needed by CScript when generating the structure of function 
+// parameters when executing each line of script.
+uint8 *AddComponentsUntilEndOfLine(CStruct *p_dest, uint8 *p_token, CStruct *p_args=NULL);
+
+// Used by UnloadQB in file.cpp
+void CleanUpAndRemoveSymbol(CSymbolTableEntry *p_sym);
+// TODO: Remove? Currently used in cfuncs.cpp to delete the NodeArray, but now that we
+// have the ability to unload qb files that should not be necessary any more.
+// Note: Just found it is also needed in parkgen.cpp, so don't remove after all. 
+void CleanUpAndRemoveSymbol(uint32 checksum);
+void CleanUpAndRemoveSymbol(const char *p_symbolName);
+
+
+// Used by various asserts in script.cpp
+int GetLineNumber(uint8 *p_token);
+const char *GetSourceFile(uint8 *p_token);
+bool PointsIntoScript(uint8 *p_token, uint8 *p_script);
+
+void CheckForPossibleInfiniteLoops(uint32 scriptName, uint8 *p_token, const char *p_fileName);
+
+const char *GetChecksumNameFromLastQB(uint32 checksum);
+void RemoveChecksumNameLookupHashTable();
+uint8 *SkipToStartOfNextLine(uint8 *p_token);
+void PreProcessScript(uint8 *p_token);
+
+
+// Called from LoadQB in file.cpp
+
+// By default the allocateChecksumNameLookupTable flag is set to true because the table is needed when
+// generating prefix info for the nodearray, and we don't know whether the qb contains the nodearray until
+// after finishing parsing it.
+// The flag can be set to false when we know that checksum lookup info won't be needed & we're short of memory.
+// (Eg, Gary uses this when loading qb's for the cutscenes)
+void ParseQB(const char *p_fileName, uint8 *p_qb, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols=NO_ASSERT_IF_DUPLICATE_SYMBOLS, bool allocateChecksumNameLookupTable=true);
+
+bool ScriptContainsName(uint8 *p_script, uint32 searchName);
+bool ScriptContainsAnyOfTheNames(uint32 scriptName, uint32 *p_names, int numNames);
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_PARSE_H
diff --git a/Code/Gel/Scripting/script.cpp b/Code/Gel/Scripting/script.cpp
new file mode 100644
index 0000000..6a42aa2
--- /dev/null
+++ b/Code/Gel/Scripting/script.cpp
@@ -0,0 +1,3835 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// script.cpp		KSH 1 Nov 2001
+//
+// CScript class member functions and misc script functions.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include  // for SKATE_TYPE_SKATER
+#include 
+#include  // For Net::Client class
+#ifndef __PLAT_WN32__
+#include  // For GameNet::Manager
+#endif
+#include 
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+#include 
+#include 
+#endif
+
+//char foo[sizeof(Script::SReturnAddress)/0];
+
+//#define SEND_SCRIPT_NAMES_TO_DEBUGGER
+
+DefinePoolableClass(Script::CScript);
+
+namespace Obj
+{
+	#ifdef __NOPT_ASSERT__
+	extern bool DebugSkaterScripts;
+	#endif
+
+}
+
+namespace Script
+{
+
+static CScript * sCurrentlyUpdating = NULL;
+
+// Parse.cpp needs this, for getting the script pointer to send to any cfunc it
+// encounters when evaluating an expression.
+// It also uses it when processing RandomNoRepeat tokens.
+CScript *GetCurrentScript()
+{
+	return sCurrentlyUpdating;
+}
+	
+#ifdef	__NOPT_ASSERT__
+// for debugging, return the info of script we are currently updating
+// as the function that Asserts by it might not have a pointer 
+// to the script 									  
+const char  *GetCurrentScriptInfo()
+{
+	if (sCurrentlyUpdating)
+	{
+		return sCurrentlyUpdating->GetScriptInfo();
+	}
+	else
+	{
+		return "No Script currently updating";
+	}
+}
+
+#ifdef SEND_SCRIPT_NAMES_TO_DEBUGGER
+static void s_send_script_name(uint32 script_name, int numReturnAddresses)
+{
+	static uint32 sp_ignored_script_names[]=
+	{
+		CRCC(0x1f8909cb,"timeupscript"),
+		CRCC(0x1aef674f,"game_update"),
+		CRCC(0x18b3b291,"waitonegameframe"),
+		CRCC(0x7c4264dd,"checkforswitchvehicles"),
+		CRCC(0x98f4c454,"just_coasting"),
+		CRCC(0x30e72e33,"do_random_effect"),
+		CRCC(0x7737d7b8,"do_random_effect2"),
+		CRCC(0x99443e63,"do_blur_effect"),
+		CRCC(0x3e6ab5cb,"do_blur_effect_accept"),
+		CRCC(0x9c9dfcf8,"disable_two_player_option"),
+		CRCC(0x380ec7a7,"playbrakeanim"),
+		CRCC(0x838116e9,"playbrakeidle"),
+		CRCC(0x7ae0d8aa,"handbrake"),
+		CRCC(0x9f6a63e3,"playturnanimorturnidle"),
+		CRCC(0x642587a0,"cessbrake"),
+		CRCC(0x603ea7f3,"waitanimfinished"),
+		CRCC(0x76792736,"checkfornetbrake"),
+		CRCC(0xa4f522e9,"parked_make_set_menu_item"),
+		CRCC(0xbeed50b6,"make_text_sub_menu_item"),
+		CRCC(0x277b7b33,"parked_make_piece_menu_item"),
+		CRCC(0x3ee4627d,"doapush"),
+		CRCC(0x493a52f,"helper_text_update_element"),
+		CRCC(0x5479735a,"show_panel_message"),
+		CRCC(0x7a449180,"DestroyMemStatsScreenElements"),
+		CRCC(0x901fefcf,"setexception"),
+		CRCC(0xe9242afd,"vibrateoff"),
+		CRCC(0xc4c8c400,"switchonboard"),
+		CRCC(0xf8d27a88,"switchoffboard"),
+		CRCC(0x1195c40a,"bloodparticlesoff"),
+		CRCC(0x1946686b,"skaterbloodoff"),
+		CRCC(0x243e716,"verifyparam"),
+		CRCC(0x2c0c9e7b,"onexceptionrun"),
+		CRCC(0x64df5e4c,"WaitAnimWhilstChecking"),
+		CRCC(0xcf14cc35,"ClothingLandBounce"),
+		CRCC(0x46f7302f,"OnGroundExceptions"),
+		CRCC(0x500eb95b,"InAirExceptions"),
+		CRCC(0x10da1c88,"ClearExceptions"),
+		CRCC(0xaaef4fb5,"ClearOnExceptionRun"),
+		CRCC(0xac281817,"model_hide_geom"),
+		CRCC(0x3b9039a5,"process_cas_command"),
+		CRCC(0xdea7fe56,"goal_editor_update_cursor_position"),
+	};
+	
+	uint32 *p_ignored_script_names=sp_ignored_script_names;
+	for (uint32 i=0; iStreamMessage(&msg);
+}
+#endif
+
+#endif
+
+
+static CScript *sp_scripts=NULL;
+static uint32 s_num_cscripts=0;
+
+static bool	s_done_one_per_frame;
+
+int CScript::s_next_unique_id = 0;
+
+uint32 GetNumCScripts()
+{
+	return s_num_cscripts;
+}
+	
+// For use when external functions want to step through all existing scripts.
+// Pass it NULL to get the first CScript.
+// Should probably eventually change all the script code to use the iterators in the 
+// standard container library at some point I guess.
+CScript *GetNextScript(CScript *p_script)
+{
+	if (p_script==NULL)
+	{
+		// They want the first CScript
+		return sp_scripts;
+	}
+	else
+	{
+		return p_script->mp_next;
+	}
+}
+
+CScript::CScript()
+{
+	// Zero all the members. CScript is not derived from CClass so we don't get the auto-zeroing.
+	uint32 size=sizeof(CScript);
+	Dbg_MsgAssert((size&3)==0,("sizeof(CScript) not a multiple of 4 ??"));
+	size>>=2;
+	uint32 *p_longs=(uint32*)this;
+	for (uint32 i=0; imp_previous=this;
+	}	
+	sp_scripts=this;
+
+	++s_num_cscripts;
+
+	m_unique_id = s_next_unique_id++;
+	
+	mNode = -1;
+	
+	#ifdef __NOPT_ASSERT__										
+	m_last_instruction_time_taken=-1.0f;
+	m_total_instruction_time_taken=0.0f;
+	#endif
+	
+	#if	0
+// if we go over 100 scripts, tell us why
+	static uint32 max = 100;
+	if (s_num_cscripts > max)
+	{
+		max = s_num_cscripts;
+		printf ("Record number of scripts: %d of %d\nvvvvvvvvvvvvvvvvvvvvvvvvvvv\n",CScript::SGetNumUsedItems(),CScript::SGetTotalItems());
+		Script::DumpScripts();
+		DumpUnwindStack(20,0);
+		printf (" ^^^^^^^^^^^^^^^^^^^^^^^^\nRecord number of scripts: %d of %d\n",CScript::SGetNumUsedItems(),CScript::SGetTotalItems());
+
+	}
+	#endif
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	mp_event_handler_table = NULL;
+#endif	 
+
+	mp_return_addresses=mp_return_addresses_small_buffer;
+	mp_loops=mp_loops_small_buffer;
+}
+
+CScript::~CScript()
+{
+	ClearScript();
+	if (mpCallbackScriptParams)
+	{
+		delete mpCallbackScriptParams;
+	}
+	// Check to see if the script has already been deleted
+	// perhaps we are deleteing it twice???
+	
+	Dbg_MsgAssert(mp_next != (CScript *)-1,("mp_next is -1 in script we are trying to delete!!!"));
+	Dbg_MsgAssert(mp_previous != (CScript *)-1,("mp_previous is -1 in script we are trying to delete!!!"));
+	
+	// Remove from the linked list.
+	if (mp_previous==NULL) 
+	{
+		sp_scripts=mp_next;
+	}	
+	if (mp_next) 
+	{
+		Dbg_MsgAssert(mp_next!=mp_previous,("mp_next same as mp_prev for script we are deleting"));
+		mp_next->mp_previous=mp_previous;
+	}	
+	if (mp_previous) 
+	{
+		Dbg_MsgAssert(mp_next!=mp_previous,("mp_next same as mp_prev for script we are deleting"));
+		mp_previous->mp_next=mp_next;
+	}	
+
+	#ifdef	__NOPT_ASSERT__	
+	mp_next = (CScript *)-1;
+	mp_previous = (CScript *)-1;
+	#endif
+	
+	// Release any stored RandomNoRepeat or RandomPermute info.
+	ReleaseStoredRandoms(this);
+
+	#ifdef	__NOPT_ASSERT__	
+	#ifndef __PLAT_WN32__
+	if (m_being_watched_in_debugger)
+	{
+		// Tell the debugger that this script has died.
+		Net::MsgDesc msg;
+		msg.m_Data = &m_unique_id;;
+		msg.m_Length = 4;
+		msg.m_Id = GameNet::vMSG_ID_DBG_SCRIPT_DIED;
+		Dbg::CScriptDebugger::Instance()->StreamMessage(&msg);
+	}	
+	#endif
+	#endif
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	if (mp_event_handler_table)
+	{
+		// Remove the references to the event handler from the tracker
+
+//		mp_event_handler_table->unregister_all(this);
+		ClearEventHandlerTable();   // Need this to clean up structs and stuff
+	
+		if (mp_event_handler_table->m_in_immediate_use_counter)
+		{
+			// table still in use by its pass_event() function,
+			// so leave it up to that function to destroy the table
+			mp_event_handler_table->m_valid = false;
+		}
+		else
+		{
+			delete mp_event_handler_table;
+		}
+	}
+#endif
+
+	Dbg_MsgAssert(s_num_cscripts,("Zero s_num_cscripts"));
+	--s_num_cscripts;
+}
+
+#ifdef __NOPT_ASSERT__
+#ifndef __PLAT_WN32__
+
+// This gets called whenever mScriptChecksum is changed.
+void CScript::check_if_needs_to_be_watched_in_debugger()
+{
+	Dbg::SWatchedScript *p_watched_script_info=Dbg::CScriptDebugger::Instance()->GetScriptWatchInfo(mScriptChecksum);
+	if (p_watched_script_info)
+	{
+		WatchInDebugger(p_watched_script_info->mStopScriptImmediately);
+	}	
+}
+
+uint32 *CScript::write_callstack_entry( uint32 *p_buf, int bufferSize, 
+										uint32 scriptNameChecksum, 
+										uint8 *p_PC,
+										CStruct *p_params,
+										Obj::CObject *p_ob)
+{
+	Dbg_MsgAssert(bufferSize >= 8,("write_callstack_entry buffer overflow"));
+	*p_buf++=scriptNameChecksum;
+	*p_buf++=Script::GetLineNumber(p_PC);
+	bufferSize-=8;
+		
+	// Find out the source file and write it in.
+	const char *p_source_file_name="";
+	CSymbolTableEntry *p_sym=LookUpSymbol(scriptNameChecksum);
+	if (p_sym)
+	{
+		p_source_file_name=FindChecksumName(p_sym->mSourceFileNameChecksum);
+	}
+	int len=strlen(p_source_file_name)+1;
+	len=(len+3)&~3;
+	Dbg_MsgAssert(bufferSize >= len,("write_callstack_entry buffer overflow"));	
+	strcpy((char*)p_buf,p_source_file_name);
+	p_buf+=len/4;
+	bufferSize-=len;
+
+	Dbg_MsgAssert(bufferSize >= 4,("write_callstack_entry buffer overflow"));
+	*p_buf++=(uint32)p_ob;
+	bufferSize -= 4;
+	if (p_ob)
+	{
+		Dbg_MsgAssert(bufferSize >= 12,("write_callstack_entry buffer overflow"));
+		*p_buf++=p_ob->GetType();
+		*p_buf++=p_ob->GetID();
+		*p_buf++=p_ob->MainScriptIs(this);
+		bufferSize -= 12;
+		
+		// Write out the object's tags.
+		int bytes_written=Script::WriteToBuffer(p_ob->GetTags(),(uint8*)p_buf,bufferSize);
+		bytes_written=(bytes_written+3)&~3;
+		Dbg_MsgAssert(bufferSize >= bytes_written,("write_callstack_entry buffer overflow"));
+		p_buf+=bytes_written/4;
+		bufferSize-=bytes_written;
+	}
+	
+	// Write out the parameters.
+	int bytes_written=Script::WriteToBuffer(p_params,(uint8*)p_buf,bufferSize);
+	bytes_written=(bytes_written+3)&~3;
+	Dbg_MsgAssert(bufferSize >= bytes_written,("write_callstack_entry buffer overflow"));
+	p_buf+=bytes_written/4;
+	
+	return p_buf;
+}
+
+// Sets the comment string, which the script debugger displays.
+// This is so that the c-code can describe where the script was created from.
+void CScript::SetCommentString(const char *p_comment)
+{
+	int len=strlen(p_comment);
+	if (len>MAX_COMMENT_STRING_CHARS)
+	{
+		len=MAX_COMMENT_STRING_CHARS;
+	}
+	for (int i=0; i= 16,("Dbg::gpDebugInfoBuffer overflow"));
+	// Stuff that identifies the CScript
+	*p_buf++=(uint32)this;
+	*p_buf++=m_unique_id;
+	*(float*)p_buf=m_last_instruction_time_taken;
+	++p_buf;
+	*(float*)p_buf=m_total_instruction_time_taken;
+	++p_buf;
+	
+	// The originating script info, ie which script this got spawned from if it was created
+	// by a SpawnScript command. Will be two zeros if no such info is available.
+	*p_buf++=m_originating_script_line_number;
+	*p_buf++=m_originating_script_name;
+	space_left-=24;
+
+	// Write out the comment string.	
+	int comment_len=strlen(mp_comment_string)+1;
+	int comment_space_needed=(comment_len+3)&~3;
+	Dbg_MsgAssert(space_left >= comment_space_needed,("Dbg::gpDebugInfoBuffer overflow"));
+	char *p_source=mp_comment_string;
+	char *p_dest=(char*)p_buf;
+	for (int i=0; i= structure_bytes_written,("Dbg::gpDebugInfoBuffer overflow"));
+	p_buf+=structure_bytes_written/4;
+	space_left-=structure_bytes_written;
+	
+	// Write out the callstack
+	Dbg_MsgAssert(space_left >= 4,("Dbg::gpDebugInfoBuffer overflow"));
+	// Adding one because the current script is considered the last entry in the callstack
+	// when displayed in the debugger.
+	*p_buf++=m_num_return_addresses+1;
+	space_left-=4;
+	
+	uint32 *p_old=p_buf;
+    for (int i=0; i= 20,("Dbg::gpDebugInfoBuffer overflow"));
+	
+	// Whether spawned or not
+	*p_buf++=mIsSpawned;
+	
+	// Wait state
+	*p_buf++=m_wait_type;
+	
+	// Paused or not
+	*p_buf++=mPaused;
+	
+	// Whether 'session specific' or not
+	*p_buf++=mNotSessionSpecific;
+	
+	// Script debugger needs this so that it knows to expand and highlight the
+	// last entry in the callstack when it receives the info.
+	*p_buf++=m_single_step_mode;
+	
+	space_left-=20;
+	
+	int message_size=((uint8*)p_buf)-((uint8*)Dbg::gpDebugInfoBuffer);
+	Dbg_MsgAssert(message_size <= Dbg::SCRIPT_DEBUGGER_BUFFER_SIZE,("Script info message too big !"));
+	Dbg_MsgAssert(message_size+space_left == Dbg::SCRIPT_DEBUGGER_BUFFER_SIZE,("What ??"));
+	
+	uint32 checksum=Crc::GenerateCRCCaseSensitive((const char *)Dbg::gpDebugInfoBuffer,message_size);
+	if (checksum != m_last_transmitted_info_checksum || definitely_transmit)
+	{
+		printf("CScript id=0x%08x Sending script info, message size=%d\n",m_unique_id,message_size);
+		
+		Net::MsgDesc msg;
+		msg.m_Data = Dbg::gpDebugInfoBuffer;
+		msg.m_Length = message_size;
+		msg.m_Id = GameNet::vMSG_ID_DBG_CSCRIPT_INFO;
+		Dbg::CScriptDebugger::Instance()->StreamMessage(&msg);
+		
+		m_last_transmitted_info_checksum=checksum;
+	}	
+}
+
+void CScript::TransmitBasicInfoToDebugger()
+{
+	uint32 *p_buf=Dbg::gpDebugInfoBuffer;
+	
+	*p_buf++=m_unique_id;
+	*p_buf++=mScriptChecksum;
+	*p_buf++=Script::GetLineNumber(mp_pc);
+		
+	// Find out the source file and write it in.
+	const char *p_source_file_name="";
+	CSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum);
+	if (p_sym)
+	{
+		p_source_file_name=FindChecksumName(p_sym->mSourceFileNameChecksum);
+	}
+	int len=strlen(p_source_file_name)+1;
+	len=(len+3)&~3;
+	strcpy((char*)p_buf,p_source_file_name);
+	p_buf+=len/4;
+
+	int message_size=((uint8*)p_buf)-((uint8*)Dbg::gpDebugInfoBuffer);
+	Dbg_MsgAssert(message_size <= Dbg::SCRIPT_DEBUGGER_BUFFER_SIZE,("Script info message too big !"));
+	
+	//printf("Sending vMSG_ID_DBG_BASIC_CSCRIPT_INFO\n");
+	Net::MsgDesc msg;
+	msg.m_Data = Dbg::gpDebugInfoBuffer;
+	msg.m_Length = message_size;
+	msg.m_Id = GameNet::vMSG_ID_DBG_BASIC_CSCRIPT_INFO;
+	Dbg::CScriptDebugger::Instance()->StreamMessage(&msg);
+}
+
+void CScript::WatchInDebugger(bool stopScriptImmediately)
+{
+	m_being_watched_in_debugger=true;
+	
+	if (stopScriptImmediately)
+	{
+		m_single_step_mode=STEP_INTO;
+	}
+	else
+	{
+		m_single_step_mode=OFF;
+	}	
+}
+
+void CScript::StopWatchingInDebugger()
+{
+	m_being_watched_in_debugger=false;
+}	
+
+void CScript::DebugStop()
+{
+	bool was_running=m_single_step_mode==OFF;
+	m_single_step_mode=WAITING;
+	
+	if (was_running)
+	{
+		// This is so that the debugger registers the fact that m_single_step_mode has changed
+		// and hence updates the window to indicate that the script has stopped.
+		TransmitInfoToDebugger();
+	}	
+}
+
+void CScript::DebugStepInto()
+{
+	m_single_step_mode=STEP_INTO;
+	Dbg::CScriptDebugger::Instance()->ScriptDebuggerUnpause();	
+}
+
+void CScript::DebugStepOver()
+{
+	m_single_step_mode=STEP_OVER;
+	Dbg::CScriptDebugger::Instance()->ScriptDebuggerUnpause();	
+}
+
+void CScript::DebugGo()
+{
+	m_single_step_mode=OFF;
+	Dbg::CScriptDebugger::Instance()->ScriptDebuggerUnpause();	
+}
+#endif // #ifndef __PLAT_WN32__
+#endif // #ifdef __NOPT_ASSERT__
+
+void CScript::SetWait(EWaitType type, Obj::CBaseComponent *p_component)
+{
+	m_wait_type=type;
+	Dbg_MsgAssert(mp_wait_component==NULL,("\n%s\nExpected mp_wait_component to be NULL here?",GetScriptInfo()));
+	mp_wait_component=p_component;
+}
+
+void CScript::ClearWait()
+{
+	m_wait_type=WAIT_TYPE_NONE;
+	mp_wait_component=NULL;
+}
+
+void CScript::Block()
+{
+	m_wait_type=WAIT_TYPE_BLOCKED;
+}
+
+void CScript::UnBlock()
+{
+	m_wait_type=WAIT_TYPE_NONE;
+}
+
+bool CScript::getBlocked()
+{
+	return m_wait_type==WAIT_TYPE_BLOCKED;
+}
+
+bool CScript::GotScript()
+{
+	return mp_pc?true:false;
+}
+
+void CScript::SwitchOnIfDebugging()
+{
+#ifdef __NOPT_ASSERT__
+	m_if_debug_on=true;
+#endif
+}
+
+void CScript::SwitchOffIfDebugging()
+{
+#ifdef __NOPT_ASSERT__
+	m_if_debug_on=false;
+#endif
+}
+
+void CScript::set_script(uint32 scriptChecksum, uint8 *p_script, CStruct *p_params, Obj::CObject *p_object)
+{
+	#ifdef	__NOPT_ASSERT__	
+	#ifdef SEND_SCRIPT_NAMES_TO_DEBUGGER
+	// Tell the debugger that this script is begin run.
+	s_send_script_name(scriptChecksum,0);
+	#endif
+	#endif
+	
+	#ifdef __NOPT_ASSERT__
+	if (Obj::DebugSkaterScripts)
+	{
+		if (p_object && p_object->GetType()==SKATE_TYPE_SKATER)
+		{
+			printf("%d: ############### %s ###############  [%i]\n",(int)Tmr::GetRenderFrame(),FindChecksumName(scriptChecksum),m_unique_id);
+		}
+	}	
+	#endif
+
+	CStruct *p_params_copy=MakeParamsSafeFromDeletionByClearScript(p_params);
+	
+	// Reset the script.
+	ClearScript();
+	
+	Dbg_MsgAssert(mp_function_params==NULL,("mp_function_params not NULL"));
+	mp_function_params=new CStruct;
+	
+	#ifdef __NOPT_ASSERT__ 
+	// This is so that the structure can print line number info in case one of
+	// the CStruct member function asserts goes off.
+	mp_function_params->SetParentScript(this);
+	#endif
+		
+	mScriptChecksum=scriptChecksum;
+	#ifdef __NOPT_ASSERT__ 
+	check_if_needs_to_be_watched_in_debugger();
+	#endif	
+    mpObject=p_object;
+	
+	Dbg_MsgAssert(mp_params==NULL,("mp_params not NULL"));
+	mp_params=new CStruct;
+	#ifdef __NOPT_ASSERT__ 
+	// This is so that the structure can print line number info in case one of
+	// the CStruct member function asserts goes off.
+	mp_params->SetParentScript(this);
+	#endif
+	
+	if (p_script)
+	{
+		mp_pc=p_script;	
+	}
+	else
+	{
+		Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+		Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	
+		mp_pc=p_script_cache->GetScript(scriptChecksum);
+		if (!mp_pc)
+		{
+			#ifdef	__NOPT_ASSERT__
+			if (Script::GetInteger(CRCD(0x22d1f89,"AssertOnMissingScripts")))
+			{
+				Dbg_MsgAssert(false, ("Script %s not found.",FindChecksumName(scriptChecksum)));
+			}
+			#endif
+			if (p_params_copy)
+			{
+				delete p_params_copy;
+			}
+			ClearScript();
+			ClearEventHandlerTable();
+			return;	
+		}
+	}
+	
+	// Load the default parameters into mp_params
+	mp_pc=AddComponentsUntilEndOfLine(mp_params, mp_pc);
+		
+	// Now merge what is in p_params onto mp_params.
+	if (p_params_copy)
+	{
+		mp_params->AppendStructure(p_params_copy);
+		delete p_params_copy;
+	}
+	else
+	{
+		mp_params->AppendStructure(p_params);
+	}
+	
+	if (GetOnExitScriptChecksum())
+	{
+		// Pass in the name of the exception so that certain exceptions can be ignored.
+		Script::CStruct *pFoo=new Script::CStruct;
+// Mick - that has no meaning now, as this is triggered by everything
+//		pFoo->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int)p_entry->exception);
+		// RunScript(GetOnExitScriptChecksum(), pFoo, mpObject );
+		// Dan: interrupt instead of run
+		uint32 checksum = GetOnExitScriptChecksum();
+		SetOnExitScriptChecksum(0);	// clear it once it has been run
+		Interrupt(checksum, pFoo);
+		delete pFoo;
+	}
+}
+
+void CScript::SetScript(const SStructScript *p_structScript, CStruct *p_params, Obj::CObject *p_object)
+{
+	Dbg_MsgAssert(p_structScript,("NULL p_structScript"));
+	Dbg_MsgAssert(p_structScript->mNameChecksum,("Zero p_structScript->mNameChecksum"));
+	Dbg_MsgAssert(p_structScript->mpScriptTokens,("NULL p_structScript->mpScriptTokens"));
+	
+	// Calculate the size of the script
+	uint32 size=SkipOverScript(p_structScript->mpScriptTokens)-p_structScript->mpScriptTokens;
+	
+	// Allocate a buffer and make a copy of the script (since the source may get deleted)
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	uint8 *p_new_script=(uint8*)Mem::Malloc(size);
+	Mem::Manager::sHandle().PopContext();
+	
+	uint8 *p_source=p_structScript->mpScriptTokens;
+	uint8 *p_dest=p_new_script;
+	for (uint32 i=0; imNameChecksum,p_new_script,p_params,p_object);
+	
+	// Now set mp_struct_script, so that the buffer can be deleted later.
+	// Note: The setting of mp_struct_script cannot be done before set_script, because set_script calls 
+	// ClearScript, which would have deleted mp_struct_script if it was not NULL.
+	mp_struct_script=p_new_script;
+}
+
+void CScript::SetScript(uint32 scriptChecksum, CStruct *p_params, Obj::CObject *p_object)
+{
+	set_script(scriptChecksum,NULL,p_params,p_object);
+}
+
+void CScript::SetScript(const char *p_scriptName, CStruct *p_params, Obj::CObject *p_object)
+{
+	SetScript(Crc::GenerateCRCFromString(p_scriptName),p_params,p_object);
+}
+
+CStruct *CScript::MakeParamsSafeFromDeletionByClearScript(CStruct *p_params)
+{
+	// It may be that p_params is mp_params or mp_function_params, or is a substructure of one
+	// of those.
+	// For example, when a manual is triggered, the passed p_params will be mp_function_params.
+	// Since ClearScript() deletes both mp_params and mp_function_params,
+	// this will result in p_params getting deleted too.
+	// So if either mp_params or mp_function_params references p_params, store the contents
+	// of p_params in p_new_params & return it.
+	// This is a speed optimization, because it would be slow to always store the contents of
+	// p_params. (Copying structures can be slow)
+	CStruct *p_new_params=NULL;
+	if (p_params)
+	{
+		if ((mp_function_params && mp_function_params->References(p_params)) || 
+			(mp_params && mp_params->References(p_params)))
+		{
+			p_new_params=new CStruct;
+			p_new_params->AppendStructure(p_params);
+		}
+	}		
+	
+	return p_new_params;
+}
+
+// Stops the script from executing, and deletes the parameters, clears the stack, etc.
+// (REQUIREMENT: Must set mp_pc to NULL, so script will be deleted by UpdateSpawnedScripts)
+void CScript::ClearScript()
+{
+	if (mp_struct_script)
+	{
+		Mem::Free(mp_struct_script);
+		mp_struct_script=NULL;
+	}
+
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	if (mp_pc)
+	{
+		// If a script was being executed, then decrement its usage in the script cache since
+		// it is no longer needed by this CScript.
+		p_script_cache->DecrementScriptUsage(mScriptChecksum);
+	}
+	
+	mp_pc=NULL;	
+	
+	if (mp_params)
+	{
+		delete mp_params;
+		mp_params=NULL;
+	}
+	if (mp_function_params)
+	{
+		delete mp_function_params;
+		mp_function_params=NULL;
+	}	
+	
+    for (int i=0; iDecrementScriptUsage(mp_return_addresses[i].mScriptNameChecksum);
+		
+        Dbg_MsgAssert(mp_return_addresses[i].mpParams,("NULL mpParams in return stack"));
+		if (mp_return_addresses[i].mpParams)
+		{
+			delete mp_return_addresses[i].mpParams;
+			mp_return_addresses[i].mpParams=NULL;
+		}	
+    }    
+	
+	m_num_return_addresses=0;
+	if (mp_return_addresses != mp_return_addresses_small_buffer)
+	{
+		Dbg_MsgAssert(mp_return_addresses,("NULL mp_return_addresses ?"));
+		Mem::Free(mp_return_addresses);
+	}
+	mp_return_addresses=mp_return_addresses_small_buffer;
+
+
+	mp_current_loop=NULL;
+	if (mp_loops != mp_loops_small_buffer)
+	{
+		Dbg_MsgAssert(mp_loops,("NULL mp_loops ?"));
+		Mem::Free(mp_loops);
+	}
+	mp_loops=mp_loops_small_buffer;
+	
+	
+	m_wait_timer=0;
+	m_wait_type=WAIT_TYPE_NONE;
+	mp_wait_component=NULL;
+	
+	mpObject=NULL;
+	mScriptChecksum=0;
+
+	m_interrupted=false;
+	m_skip_further_process_waits=false;
+	
+		
+	#ifdef __NOPT_ASSERT__
+	m_if_status=0;
+	#endif
+}
+
+// Cleans up the script's event handler table.  Call after ClearScript if you are not about to reset the script
+// (REQUIREMENT: Must set mp_pc to NULL, so script will be deleted by UpdateSpawnedScripts)
+void CScript::ClearEventHandlerTable()
+{
+	#ifdef	__SCRIPT_EVENT_TABLE__		
+	if (mp_event_handler_table)
+	{
+		// Remove the references to the event handler from the tracker
+		mp_event_handler_table->unregister_all(this);
+		
+		mp_event_handler_table->remove_group(CRCD(0x8b713e0e, "all_groups"));
+	}
+	#endif
+}
+
+// Restarts the script.
+void CScript::Restart()
+{
+	// This is a structure for temporarily storing the old parameters, since the SetScript call will clear
+	// everything in the script, including deleting the structure pointed to by mp_params, and the call stack.
+	CStruct *p_params=new CStruct;
+	
+	if (m_num_return_addresses)
+	{
+		// If there are things in the call stack, then the script that we want to restart is
+		// the first one in the call stack, since this is the original script.
+		Dbg_MsgAssert(mp_return_addresses[0].mpParams,("NULL p_params in return stack"));
+		*p_params=*mp_return_addresses[0].mpParams;
+		SetScript(mp_return_addresses[0].mScriptNameChecksum,p_params,mp_return_addresses[0].mpObject);
+	}
+	else
+	{
+		// If there is nothing in the call stack, restart the current script.
+		*p_params=*mp_params;
+		// The parameters are now safe in p_params, so restart the script by reinitialising it.
+		SetScript(mScriptChecksum,p_params,mpObject);
+	}
+		
+	// Delete the temporary structure.
+	delete p_params;
+}
+
+// Stops the script from being associated with an object, ie, sets mpObject to NULL.
+// Allows the script to continue executing though, so the script must not contain any
+// further member functions, otherwise the code will assert.
+// This function also sets m_wait_type=WAIT_TYPE_NONE; to ensure the Update function does
+// not assert if it is waiting for the object to finish doing something.
+void CScript::DisassociateWithObject(Obj::CObject *pObjectToDisconnectFrom)
+{
+	if (!pObjectToDisconnectFrom) return;
+
+	#ifdef	__NOPT_ASSERT__
+	pObjectToDisconnectFrom->SetLockAssertOff();	
+	#endif
+
+	if (mpObject == pObjectToDisconnectFrom)
+	{
+		mpObject=NULL;
+	}
+	for (int i=0; imSourceFileNameChecksum),FindChecksumName(mScriptChecksum));
+			}
+		}
+		else
+		{
+			sprintf(sp_script_info,"GetScriptInfo: Script symbol not found. Strange.");
+		}	
+	}
+		
+	strcat(sp_script_info,"\nScript call stack:\n");
+	CSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum);
+	if (p_sym)
+	{
+		strcat(sp_script_info,FindChecksumName(p_sym->mSourceFileNameChecksum));
+	}
+	else
+	{
+		strcat(sp_script_info,"Unknown source file");
+	}	
+	sprintf(sp_script_info+strlen(sp_script_info)," line %d, in ",GetLineNumber(mp_pc));
+	strcat(sp_script_info,FindChecksumName(mScriptChecksum));
+	strcat(sp_script_info,"\n");
+    for (int i=m_num_return_addresses-1; i>=0; --i)
+    {
+		if (mp_return_addresses[i].mpParams)
+		{
+			sprintf(sp_script_info+strlen(sp_script_info)," line %d, in ",GetLineNumber(mp_return_addresses[i].mpReturnAddress));
+			strcat(sp_script_info,FindChecksumName(mp_return_addresses[i].mScriptNameChecksum));
+			strcat(sp_script_info,"\n");
+		}	
+		else
+		{
+			strcat(sp_script_info,"-\n");
+		}	
+    }    
+		
+		
+	Dbg_MsgAssert(strlen(sp_script_info)mSourceFileNameChecksum);
+	}
+	else
+	{
+		return "Unknown";
+	}
+}		
+#else
+// Need these to be fast on Xbox because they are used in Dbg_MsgAsserts(), which will call it
+// regardless of whether the assert condition is false.
+const char *CScript::GetScriptInfo()
+{
+	return "";
+}
+int CScript::GetCurrentLineNumber(void)
+{
+	return 0;
+}
+const char *CScript::GetSourceFile(void)
+{
+	return "";
+}		
+#endif // #ifndef __PLAT_XBOX__
+#endif
+
+
+#ifdef __NOPT_ASSERT__
+#ifndef __PLAT_WN32__
+// For testing how many scripts end up needing the bigger buffer.
+bool CScript::UsingBigCallstackBuffer()
+{
+	if (mp_return_addresses == mp_return_addresses_small_buffer)
+	{
+		return false;
+	}
+	return true;	
+}
+
+bool CScript::UsingBigLoopBuffer()
+{
+	if (mp_loops == mp_loops_small_buffer)
+	{
+		return false;
+	}
+	return true;	
+}
+
+#endif
+#endif
+
+// Used by CScript::Update when calling a script.
+// call_script is also used by CScript::Interrupt below.
+void CScript::call_script(uint32 newScriptChecksum, uint8 *p_script, CStruct *p_params, Obj::CObject *p_object, bool interrupt)
+{			  
+	// K: If calling a script when nothing is currently being run, jump to it instead.
+	// This is so that the mp_function_params gets created.
+	// Fixes a bug where when a 2-player trick attack game was started, it would immediately
+	// assert because call_script was called straight away on a newly created CScript.
+	// (CGoal::RunCallbackScript would run a script which would do a MakeSkaterGosub, and the
+	// skater's script was not running anything at that point)
+	if (!mp_pc)
+	{
+		set_script(newScriptChecksum,p_script,p_params,p_object);
+		
+		// This may not really be necessary since there is no script to return to that might be affected,
+		// but set it anyway for consistency.
+		m_interrupted=interrupt;
+		return;
+	}
+
+	// Calling a subroutine, so store the current script info on the stack.
+	
+	if (mp_return_addresses == mp_return_addresses_small_buffer && 
+		m_num_return_addresses == RETURN_ADDRESSES_SMALL_BUFFER_SIZE)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+		mp_return_addresses=(SReturnAddress*)Mem::Malloc(MAX_RETURN_ADDRESSES * sizeof(SReturnAddress));
+		Mem::Manager::sHandle().PopContext();
+		
+		for (int i=0; imScriptNameChecksum=mScriptChecksum;
+    p_stack_top->mpParams=mp_params;
+    p_stack_top->mpReturnAddress=mp_pc;
+	p_stack_top->mpObject=mpObject;
+	p_stack_top->mpLoop=mp_current_loop;
+	#ifdef __NOPT_ASSERT__
+	p_stack_top->m_if_status=m_if_status;
+	#endif
+	
+	p_stack_top->mWaitType=m_wait_type;
+	p_stack_top->mpWaitComponent=mp_wait_component;
+	p_stack_top->mWaitTimer=m_wait_timer;
+	p_stack_top->mWaitPeriod=m_wait_period;
+	p_stack_top->mStartTime=m_start_time;
+	
+	p_stack_top->mInterrupted=m_interrupted;
+    
+	// Set the new mScriptChecksum
+    mScriptChecksum=newScriptChecksum;
+	#ifdef __NOPT_ASSERT__ 
+	check_if_needs_to_be_watched_in_debugger();
+	#endif	
+	
+	// Set the new mp_pc
+    mp_pc=p_script;
+    Dbg_MsgAssert(mp_pc,("NULL p_script sent to call_script"));
+	
+	// Create a new parameters structure, and fill it in using the default values listed after
+	// the script name.
+	// Note: Not asserting if mp_params is not NULL, because it probably won't be.
+	// The old value of mp_params has been safely stored in the mp_return_addresses array above.
+    mp_params=new CStruct;
+	#ifdef __NOPT_ASSERT__ 
+	// This is so that the structure can print line number info in case one of
+	// the CStruct member function asserts goes off.
+	mp_params->SetParentScript(this);
+	#endif
+	mp_pc=AddComponentsUntilEndOfLine(mp_params,mp_pc);
+	
+	// Now overlay the parameters passed to the script.
+	if (p_params)
+	{
+		*mp_params+=*p_params;
+	}	
+	
+	// Set the object pointer, which may get changed if executing another object's
+	// script using the : operator.
+	mpObject=p_object;
+	
+	ClearWait();
+	
+	#ifdef __NOPT_ASSERT__
+	// Not in an if-statement on starting the new routine.
+	m_if_status=0;
+	#endif
+	
+	m_interrupted=interrupt;
+}
+
+// Uses the above function to implement an interrupt.
+// This will jump to the passed script and also do one call to update.
+// Once the interrupt script is finished it will return to what it was doing before.
+EScriptReturnVal CScript::Interrupt(uint32 newScriptChecksum, CStruct *p_params)
+{
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	uint8 *p_script=p_script_cache->GetScript(newScriptChecksum);
+
+	// The true means interrupt the script, which means that the call to Update will not continue
+	// execution of the interrupted script when the called script finishes.
+	call_script(newScriptChecksum,p_script,p_params,mpObject,true);
+	return Update();
+}
+
+bool CScript::Finished()
+{
+	return mp_pc && *mp_pc==ESCRIPTTOKEN_KEYWORD_ENDSCRIPT;
+}
+
+#ifdef __NOPT_ASSERT__
+static bool sExcludeFromSkaterDebug(uint32 name)
+{
+	CArray *p_array=GetArray(0xdaa3a3a5/*ExcludeFromSkaterDebug*/,NO_ASSERT);
+	if (!p_array)
+	{
+		return false;
+	}
+		
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		if (name==p_array->GetChecksum(i))
+		{
+			return true;
+		}
+	}
+	
+	return false;		
+}
+#endif
+
+// This looks at m_wait_type, does the appropriate wait logic, and resets
+// m_wait_type back to WAIT_TYPE_NONE if the wait has finished.
+// Otherwise m_wait_type is left the same. 
+void CScript::process_waits()
+{
+	switch (m_wait_type)
+	{
+		case WAIT_TYPE_NONE:
+			break;
+			
+		case WAIT_TYPE_COUNTER:
+			if (m_wait_timer) 
+			{
+				--m_wait_timer;
+			}
+			else
+			{
+				// Finished counting down, so reset wait type 
+				// so that script execution continues.
+				m_wait_type=WAIT_TYPE_NONE;
+			}	
+			break;
+
+		case WAIT_TYPE_ONE_PER_FRAME:
+			if (m_wait_timer) 
+			{
+				--m_wait_timer;
+			}
+			else
+			{
+				if (s_done_one_per_frame)
+				{
+					// already done one this frame
+				}
+				else
+				{
+					// Finished counting down, so reset wait type 
+					// so that script execution continues.
+					s_done_one_per_frame = true;
+					m_wait_type=WAIT_TYPE_NONE;
+				}
+			}
+			break;
+
+			
+		case WAIT_TYPE_TIMER:
+		{
+//			if (Tmr::ElapsedTime(m_start_time) >= m_wait_period)
+			// Mick, instead of using global time, we decrement the timer whenever 
+			// the script is updated.  That way paused scripts will be genuinely paused
+			//
+			// Note, I use m_start_time as a patch variable
+			// if it is not zero, I know we have not been round this loop
+			// at least once.  
+			// If you feel like changing this, then note that m_start_time gets pushed on the stack 
+			// when calling another script, you you will need to duplicate that functionality
+			// 
+			int tick = (int) (Tmr::FrameLength() * 1000.0f);			
+			if ((int)m_wait_period < tick)			
+			{
+				if (m_start_time == 0)	   	// only if we've waited at least one frame
+				{
+					// Finished counting down, so reset wait type 
+					// so that script execution continues.
+					m_wait_type=WAIT_TYPE_NONE;
+				}
+				else
+				{
+					// finished counting down, but this was the first frame.
+					// so we don't want to exit, as we cannot have a zero frame delay if 
+					// the time specified was not zero
+					// be we want for sure to exit next time around, so set the wait period to 0
+					//
+					m_wait_period = 0;
+				}
+			}
+			else
+			{
+				m_wait_period -= tick;
+			}
+			m_start_time = 0;			// flag that we have waited at least one frame
+			break;	
+		}	
+		case WAIT_TYPE_BLOCKED:	
+			// Nothing to do except wait for whatever blocked this script to unblock it.
+			break;
+			
+		// These are the object wait options, which only apply if the script
+		// has a parent object.		
+		case WAIT_TYPE_OBJECT_MOVE:	
+		case WAIT_TYPE_OBJECT_ANIM_FINISHED:	
+		case WAIT_TYPE_OBJECT_JUMP_FINISHED:	
+		case WAIT_TYPE_OBJECT_STOP_FINISHED:
+		case WAIT_TYPE_OBJECT_ROTATE:
+		case WAIT_TYPE_STREAM_FINISHED:
+			// Call the parent object's wait function.
+			Dbg_MsgAssert(mp_wait_component,("\n%s\nNULL mp_wait_component, cannot call ProcessWait",GetScriptInfo()));
+			mp_wait_component->ProcessWait(this);
+			break;
+			
+		default:
+			Dbg_MsgAssert(0,("\n%s\nBad wait type value of %d",GetScriptInfo(),m_wait_type));
+			break;
+	}		
+}
+
+void CScript::load_function_params()
+{
+	#ifdef STOPWATCH_STUFF
+	pFunctionParamsStopWatch->Start();
+	#endif
+	
+	// Clear the function parameters structure and add the new params, using mp_params to get
+	// any parameters referenced using the <,> operators.
+	Dbg_MsgAssert(mp_function_params,("NULL mp_function_params"));
+	mp_function_params->Clear();
+	// AddComponentsUntilEndOfLine will assert if mp_pc is NULL
+	mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params);
+	
+	#ifdef STOPWATCH_STUFF
+	pFunctionParamsStopWatch->Stop();
+	#endif
+}
+
+// Runs the specified member function. Uses mpObject by default, but uses p_substituteObject 
+// if it is not NULL. p_substituteObject defaults to NULL if it is not specified.
+// Does not affect mp_pc
+bool CScript::run_member_function(uint32 functionName, Obj::CObject *p_substituteObject)
+{
+	Obj::CObject *p_obj;
+	// By default member function calls operate on mpObject, but if
+	// a substitute object has been specified use that instead.
+	if (p_substituteObject)
+	{
+		p_obj=p_substituteObject;
+	}	
+	else
+	{
+		p_obj=mpObject;
+	}	
+	
+	bool return_value=false;
+	
+	if (p_obj)
+	{
+		#ifdef STOPWATCH_STUFF
+		pUpdateStopWatch->Stop();
+		pCFunctionStopWatch->Start();
+		#endif
+		
+		#ifdef STOPWATCH_STUFF
+		Tmr::CPUCycles TimeBefore,TimeAfter;
+		TimeBefore=Tmr::GetTimeInCPUCycles();
+		#endif
+		
+		return_value=p_obj->CallMemberFunction(functionName,mp_function_params,this);
+		
+		#ifdef STOPWATCH_STUFF
+		TimeAfter=Tmr::GetTimeInCPUCycles();
+		float TimeTaken;
+		TimeTaken=((int)(TimeAfter-TimeBefore))/150.0f;
+		if (TimeTaken>200)
+		{
+			//SSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum);
+			//printf("Memb: %s, %f (%s, line %d)\n",FindChecksumName(NameChecksum),TimeTaken,p_sym->pFilename,GetLineNumber(mp_pc));
+		}	
+		
+		p_entry->mAverageExecutionTime = (p_entry->mAverageExecutionTime*p_entry->mNumCalls+TimeTaken)/(p_entry->mNumCalls+1);
+		++p_entry->mNumCalls;
+		p_entry->mNumCallsInThisFrame=600;
+		#endif
+		
+		#ifdef STOPWATCH_STUFF
+		pCFunctionStopWatch->Stop();
+		pUpdateStopWatch->Start();
+		#endif
+	}	
+	else
+	{
+		#ifdef __PLAT_WN32__
+		if (functionName != 0xb3c262ec) // "DisassociateFromObject" is okay, no harm in trying to break non-existent association
+		{
+			Dbg_MsgAssert(0,("\n%s\nTried to call member function %s from a script\nnot associated with a CObject",GetScriptInfo(),FindChecksumName(functionName)));
+		}
+		#else
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+		// It network games, it's expected that some objects will no longer exist by the time
+		// some object member functions are called via remote scripts.  One example being, when
+		// someone joins a game-in-progress, they will be instructed to execute a series of scripts
+		// and it's possible that the objects which originally spawned those scripts are no 
+		// longer in the game or that the client has yet to create skaters for them.  So in 
+		// those cases, just print a warning and try to continue processing the script
+		if( gamenet_man->InNetGame())
+		{   
+			Dbg_Warning( "\n%s\nTried to call member function %s from a script\nnot associated with a CObject",GetScriptInfo(),FindChecksumName(functionName));
+		}
+		else if (functionName != 0xb3c262ec) // "DisassociateFromObject" is okay, no harm in trying to break non-existent association
+		{
+			Dbg_MsgAssert(0,("\n%s\nTried to call member function %s from a script\nnot associated with a CObject",GetScriptInfo(),FindChecksumName(functionName)));
+		}
+		#endif
+	}
+	
+	return return_value;
+}
+
+// Executes the passed c-function pointer, using the current mp_function_params as parameters.
+bool CScript::run_cfunction(bool (*p_cfunc)(CStruct *pParams, CScript *pCScript))
+{
+	Dbg_MsgAssert(p_cfunc,("\n%s\nNULL p_cfunc",GetScriptInfo()));
+	
+	#ifdef STOPWATCH_STUFF
+	pUpdateStopWatch->Stop();
+	pCFunctionStopWatch->Start();
+	#endif
+	
+	#ifdef STOPWATCH_STUFF
+	Tmr::CPUCycles TimeBefore,TimeAfter;
+	TimeBefore=Tmr::GetTimeInCPUCycles();
+	#endif
+
+	/*
+	Tmr::Time TimeBefore,TimeAfter;
+	TimeBefore=Tmr::GetTimeInCPUCycles();
+	*/
+	
+	bool return_value=(*p_cfunc)(mp_function_params,this);
+
+	/*
+	TimeAfter=Tmr::GetTimeInCPUCycles();
+	float TimeTaken;
+	TimeTaken=((int)(TimeAfter-TimeBefore))/150.0f;
+	if (TimeTaken>Script::GetFloat("Threshold"))
+	{
+		CArray *p_exclude=Script::GetArray("Exclude");
+		bool exclude=false;
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			if (p_exclude->GetChecksum(i)==name_checksum)
+			{
+				exclude=true;
+				break;
+			}
+		}
+		if (!exclude)
+		{
+			CSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum);
+			printf("CFunc: %s, %f (%s, line %d)\n",FindChecksumName(name_checksum),TimeTaken,FindChecksumName(p_sym->mSourceFileNameChecksum),GetLineNumber(mp_pc));
+		}	
+	}	
+	*/
+
+	
+	#ifdef STOPWATCH_STUFF
+	TimeAfter=Tmr::GetTimeInCPUCycles();
+	float TimeTaken;
+	TimeTaken=((int)(TimeAfter-TimeBefore))/150.0f;
+	if (TimeTaken>200)
+	{
+		//SSymbolTableEntry *p_sym=LookUpSymbol(mScriptChecksum);
+		//printf("CFunc: %s, %f (%s, line %d)\n",FindChecksumName(NameChecksum),TimeTaken,p_sym->pFilename,GetLineNumber(mp_pc));
+	}	
+	p_entry->mAverageExecutionTime = (p_entry->mAverageExecutionTime*p_entry->mNumCalls+TimeTaken)/(p_entry->mNumCalls+1);
+	++p_entry->mNumCalls;
+	p_entry->mNumCallsInThisFrame=600;
+	#endif
+	
+	
+	#ifdef STOPWATCH_STUFF
+	pCFunctionStopWatch->Stop();
+	pUpdateStopWatch->Start();
+	#endif
+	
+	return return_value;
+}
+
+// Gets the name from mp_pc.
+// If mp_pc points to a <,> type name, it will look up that name in mp_params.
+// It will advance mp_pc to after the name.
+uint32 CScript::get_name()
+{
+	uint32 name_checksum=0;
+	
+	switch (*mp_pc)
+	{
+		case ESCRIPTTOKEN_KEYWORD_ALLARGS:
+		{
+			++mp_pc;
+			mp_params->GetChecksum(NO_NAME,&name_checksum);
+			break;
+		}
+			
+		case ESCRIPTTOKEN_ARG:
+		{
+			++mp_pc;
+			Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_NAME,("\n%s\nExpected '<' token to be followed by a name.",GetScriptInfo()));
+			++mp_pc;
+			uint32 arg_checksum=Read4Bytes(mp_pc).mChecksum;
+			mp_pc+=4;
+			mp_params->GetChecksum(arg_checksum,&name_checksum);
+			break;
+		}
+
+		case ESCRIPTTOKEN_NAME:
+		{
+			++mp_pc;
+			name_checksum=Read4Bytes(mp_pc).mChecksum;
+			mp_pc+=4;
+			break;
+		}
+		
+		default:
+		{
+			Dbg_MsgAssert(0,("\n%s\nUnexpected '%s' token when expecting some sort of name",GetScriptInfo(),GetTokenName((EScriptToken)*mp_pc)));
+			break;
+		}	
+	}
+	
+	return name_checksum;
+}
+
+// Executes the command pointed to by mp_pc, and returns true or false, which is the value to
+// be used by any preceding if-statement.
+// After executing the command, mp_pc will have changed. It might be pointing to the next command in
+// the current script, or to a command in a different script, or it might even be NULL, who knows?
+// A 'command' is either a cfunc,member func, or script call, in which the command has the form of
+// some sort of name followed by a list of parameters to be sent to it.
+// It also covers the direct setting of one of the script's parameters, such as =3
+// It also covers expressions enclosed in parentheses that can be used in if's, eg if (1>0)
+// Note that like in C, expressions on their own are valid commands, eg (23*67), but unless preceded
+// by an if their result will be discarded.
+//
+// The term 'command' does not cover things like if's, begin-repeat's, or switch statements.
+// Generally the logic for interpreting that stuff is done in CScript::Update.
+bool CScript::execute_command()
+{
+	uint8 token=*mp_pc;
+
+	// Check for the pre-processed member function token.
+	if (token==ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION)
+	{
+		++mp_pc;
+		uint32 member_function_checksum=Read4Bytes(mp_pc).mChecksum;
+		mp_pc+=4;
+		
+		load_function_params();
+		return run_member_function(member_function_checksum);
+	}
+		
+	// Check for the pre-processed cfunction token.
+	if (token==ESCRIPTTOKEN_RUNTIME_CFUNCTION)
+	{
+		++mp_pc;
+        bool (*p_cfunc)(CStruct *pParams, CScript *pCScript)=Read4Bytes(mp_pc).mpCFunction;
+		mp_pc+=4;
+		
+		load_function_params();
+		return run_cfunction(p_cfunc);		
+	}
+
+	// Check for an expression enclosed in parentheses
+	if (token==ESCRIPTTOKEN_OPENPARENTH)
+	{
+		// Note: Not skipping past the open-parenth token because Evaluate() expects it.
+		CComponent *p_comp=new CComponent;
+		mp_pc=Evaluate(mp_pc,mp_params,p_comp);
+				
+		Dbg_MsgAssert(p_comp->mType==ESYMBOLTYPE_INTEGER,("\n%s\nBad type of '%s' returned by expression, expected integer",GetScriptInfo(),GetTypeName(p_comp->mType)));
+		bool return_value=p_comp->mIntegerValue;
+
+		CleanUpComponent(p_comp);
+		delete p_comp;
+		
+		return return_value;
+	}	
+	
+	// Otherwise, expect some sort of name, ie Blaa or 
+	uint32 name=get_name();
+	
+	// Check if the name is followed by a colon, in which case the name is the id of some object.
+	if (*mp_pc==ESCRIPTTOKEN_COLON)
+	{
+		Obj::CObject *p_substitute_object = NULL;
+		// Find the object
+#ifndef __PLAT_WN32__
+		p_substitute_object=Obj::ResolveToObject(name);
+#endif
+		Dbg_MsgAssert(p_substitute_object,("\n%s\nCould not resolve '%s' to a CObject instance",GetScriptInfo(),FindChecksumName(name)));
+		if( p_substitute_object == NULL )
+		{
+			return true;
+		}
+
+//		if (p_substitute_object == mpObject)
+//		{
+//			printf("\n\n----------------------------------->\n\%s\nThis script is already running on %s\n",GetScriptInfo(),FindChecksumName(name));
+//		}
+		
+		++mp_pc;
+		mp_pc=DoAnyRandomsOrJumps(mp_pc);
+
+		// Now we're expecting some sort of function to run on the object.
+		
+		// Check for a pre-processed member function
+		if (*mp_pc==ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION)
+		{
+			++mp_pc;
+			uint32 member_function_checksum=Read4Bytes(mp_pc).mChecksum;
+			mp_pc+=4;
+			
+			load_function_params();
+			return run_member_function(member_function_checksum,p_substitute_object);
+		}
+
+		// No pre-processed member function, so expect some sort of name.
+		uint32 function_checksum=get_name();
+		
+		// Get the parameters that follow the name.
+		load_function_params();
+
+		// Look-up what kind of function it is.
+		CSymbolTableEntry *p_entry=Resolve(function_checksum);
+		
+		// if the script is "runmenow" then a syntax error 
+		// should just printf a warning and return
+		if (!p_entry && mScriptChecksum == 0xfb11e6cd)   // RunMeNow
+		{
+			#ifdef	__NOPT_ASSERT__
+			printf("WARNING !! Confused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",FindChecksumName(function_checksum));
+			#endif
+			return false;
+		}
+	
+		Dbg_MsgAssert(p_entry,("\n%s\nConfused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",GetScriptInfo(),FindChecksumName(function_checksum)));
+
+		// Expecting the function to be either a member function, or a script.	
+		switch (p_entry->mType)
+		{
+		case ESYMBOLTYPE_QSCRIPT:
+		{
+			#ifdef __NOPT_ASSERT__
+			if (Obj::DebugSkaterScripts)
+			{
+				if (p_substitute_object->GetType()==SKATE_TYPE_SKATER)
+				{
+					if (!sExcludeFromSkaterDebug(function_checksum))
+					{	 
+						printf("%d: Calling %s [%i]\n",(int)Tmr::GetRenderFrame(),FindChecksumName(function_checksum),m_unique_id);
+					}	
+				}
+			}		
+			#endif
+
+			Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+			Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+			uint8 *p_script=p_script_cache->GetScript(p_entry->mNameChecksum);
+			
+			call_script(function_checksum,p_script,mp_function_params,p_substitute_object);
+			return true;
+			break;
+		}	
+		case ESYMBOLTYPE_MEMBERFUNCTION:
+			return run_member_function(function_checksum,p_substitute_object);
+			break;
+		default:
+			Dbg_MsgAssert(0,("\n%s\n'%s' is not a member function or script",GetScriptInfo(),FindChecksumName(function_checksum)));
+			return false;
+			break;
+		}		
+	}
+	
+	// Check if the name is followed by equals.
+	// In this case, the name is the name of the parameter we want to set.
+	if (*mp_pc==ESCRIPTTOKEN_EQUALS)
+	{
+		// The get_name call will have looked up any <,> name in the structure, so re-get the
+		// preceding name, because we want both x and  to mean x.
+		mp_pc-=5;
+		Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_NAME,("\n%s\nEquals must be preceded by a param name",GetScriptInfo()));
+		++mp_pc;
+		name=Read4Bytes(mp_pc).mChecksum;
+		mp_pc+=5; // +5 to skip over the equals too.
+
+		mp_pc=DoAnyRandomsOrJumps(mp_pc);
+
+		// Calculate the value of whatever follows the equals, and store it in p_comp		
+		CComponent *p_comp=new CComponent;
+		if (*mp_pc==ESCRIPTTOKEN_OPENPARENTH)
+		{
+			// It's an expression, so evaluate it.
+			mp_pc=Evaluate(mp_pc,mp_params,p_comp);
+		}
+		else
+		{
+			// It's not an expression, so just load the value in.
+			mp_pc=FillInComponentUsingQB(mp_pc,mp_params,p_comp);
+		}
+
+		if (p_comp->mType!=ESYMBOLTYPE_NONE)
+		{
+			// Got some sort of value, so name it and stick it into mp_params.
+			p_comp->mNameChecksum=name;
+			mp_params->AddComponent(p_comp);
+			// Not deleting p_comp because it has been given to mp_params
+		}
+		else
+		{
+			// Did not get a value, so clean up p_comp
+			delete p_comp;
+		}	
+		
+		return true;
+	}
+	
+	// The name is not followed by a colon or an equals, so it must be a function call.
+	// Load in the parameters that follow.
+	load_function_params();
+	
+	// Look up the function to see what it is.
+    CSymbolTableEntry *p_entry=Resolve(name);
+	
+	// if the script is "runmenow" then a syntax error 
+	// should just printf a warning and return
+	if (!p_entry && mScriptChecksum == 0xfb11e6cd)   // RunMeNow
+	{
+		#ifdef	__NOPT_ASSERT__
+		printf("WARNING !! Confused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",FindChecksumName(name));
+		#endif
+		return false;
+	}
+
+	if (!p_entry)
+	{
+		#ifdef	__NOPT_ASSERT__
+		if (Script::GetInteger(CRCD(0x22d1f89,"AssertOnMissingScripts")))
+		{
+			Dbg_MsgAssert(p_entry,("\n%s\nConfused by '%s', which does not appear to be defined anywhere.\nIf it is a C-function or member function, it needs to be listed in ftables.cpp.",GetScriptInfo(),FindChecksumName(name)));
+		}
+		else
+		#endif
+		{
+			#ifdef __PLAT_WN32__
+			// Don't printf if compiling on PC, otherwise LevelAssetLister prints lots
+			// of warning messages when running the load sound scripts.
+			#else
+			printf ("WARNING: script %s not found, ignoring in default level.\n",FindChecksumName(name));
+			#endif
+			return true;
+		}
+	}
+									 
+
+    switch (p_entry->mType)
+	{
+		case ESYMBOLTYPE_CFUNCTION:
+			return run_cfunction(p_entry->mpCFunction);
+			break;
+		case ESYMBOLTYPE_MEMBERFUNCTION:
+			return run_member_function(name);
+			break;
+		case ESYMBOLTYPE_QSCRIPT:
+		{
+			#ifdef __NOPT_ASSERT__
+			if (Obj::DebugSkaterScripts)
+			{
+				if (mpObject && mpObject->GetType()==SKATE_TYPE_SKATER)
+				{
+					if (!sExcludeFromSkaterDebug(name))
+					{
+						printf("%d: Calling %s [%i]\n",(int)Tmr::GetRenderFrame(),FindChecksumName(name),m_unique_id);
+					}	
+				}
+			}		
+			#endif
+
+			Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+			Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+			uint8 *p_script=p_script_cache->GetScript(p_entry->mNameChecksum);
+		 
+			call_script(name,p_script,mp_function_params,mpObject);
+			// Script calls always return true.
+			return true;
+			break;
+		}	
+		default:
+			Dbg_MsgAssert(0,("\n%s\n'%s' cannot be called, because it's a '%s'",GetScriptInfo(),FindChecksumName(name),GetTypeName(p_entry->mType)));
+			break;
+	}		
+	
+	return false;
+}
+
+void CScript::execute_if()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_IF,("Unexpected *mp_pc='%s', expected keyword 'if'",GetTokenName((EScriptToken)*mp_pc)));
+
+	// Skip over the if token.	
+	++mp_pc;
+	
+	bool negate=false;
+	if (*mp_pc==ESCRIPTTOKEN_KEYWORD_NOT)
+	{
+		++mp_pc;
+		negate=true;
+	}	
+		
+	bool return_value=execute_command();
+	
+	// mp_pc will have changed, and might be NULL.
+	if (!mp_pc)
+	{
+		return;
+	}	
+	
+	if (negate)
+	{
+		return_value=!return_value;
+	}	
+
+	#ifdef __NOPT_ASSERT__
+	Dbg_MsgAssert((m_if_status&0x80000000)==0,("\n%s\nToo many nested if's",GetScriptInfo()));
+	m_if_status<<=1;
+	#endif
+	
+	// If the return value is false, skip forward to the 'else' or 'endif' statement.
+	if (!return_value)
+	{
+		int in_nested_if=0;
+
+		// Skip mp_pc to the current if's 'else' or 'endif',
+		// but ignore the else & endifs belonging to nested ifs.
+		#ifdef STOPWATCH_STUFF
+		pIfSkipStopWatch->Start();
+		#endif
+		while (in_nested_if || 
+			   !(*mp_pc==ESCRIPTTOKEN_KEYWORD_ELSE || *mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF))
+		{
+			Dbg_MsgAssert(*mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT,("endif or else keywords not found after if keyword."));
+			
+			// Keep track of nested ifs.
+			if (*mp_pc==ESCRIPTTOKEN_KEYWORD_IF)
+			{
+				++in_nested_if;
+			}
+			else if (*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF)
+			{
+				Dbg_MsgAssert(in_nested_if,("Got endif within true part of if statement without corresponding nested if"));
+				--in_nested_if;
+			}	
+				
+			mp_pc=SkipToken(mp_pc);
+		}    
+		#ifdef __NOPT_ASSERT__
+		if (*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF)
+		{
+			m_if_status>>=1;
+		}
+		#endif
+		// Skip over the 'else' or 'endif'
+		++mp_pc;
+		
+		#ifdef STOPWATCH_STUFF
+		pIfSkipStopWatch->Stop();
+		#endif
+	}
+	else
+	{
+		// Otherwise, if the return value was true, mp_pc remains unchanged so that the instructions
+		// following the if get executed.
+		
+		// Record the if-status so that spurious else's can be detected.
+		#ifdef __NOPT_ASSERT__
+		m_if_status|=1;
+		#endif
+	}	
+
+	// I'm pretty sure if-debug isn't used, check with Scott
+	/*
+	#ifdef __NOPT_ASSERT__
+	if (m_if_debug_on)
+	{
+		printf("%s: If %s ",FindChecksumName(mScriptChecksum),FindChecksumName(name_checksum));
+		if (return_value)
+		{
+			printf("TRUE\n");
+		}
+		else
+		{
+			printf("FALSE\n");
+		}	
+	}
+	#endif
+	*/
+}
+
+// On entry, mp_pc must point to an else token.
+// Skips forward till it hits an endif, then skips over the endif.
+void CScript::execute_else()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_ELSE,("Unexpected *mp_pc='%s', expected keyword 'else'",GetTokenName((EScriptToken)*mp_pc)));
+	// else's are only allowed in the true blocks of if-statements.
+	// Bit 0 of m_if_status indicates the status of the last if.
+	Dbg_MsgAssert(m_if_status&1,("\n%s\nSpurious 'else'",GetScriptInfo()));
+	
+	#ifdef STOPWATCH_STUFF
+	pIfSkipStopWatch->Start();
+	#endif
+			
+	// Skip over the code within the else, since the code before it must have executed.
+	int in_nested_if=0;
+	while (in_nested_if || *mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDIF)
+	{
+		Dbg_MsgAssert(*mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT,("endif keyword not found after else keyword."));
+		
+		// Keep track of nested ifs.
+		if (*mp_pc==ESCRIPTTOKEN_KEYWORD_IF)
+		{
+			++in_nested_if;
+		}
+		else if (*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF)
+		{
+			Dbg_MsgAssert(in_nested_if,("Got endif within else part of if statement without corresponding nested if"));
+			--in_nested_if;
+		}	
+		
+		mp_pc=SkipToken(mp_pc);
+	}    
+	// We've hit the endif, so skip over it.
+	++mp_pc;
+
+	#ifdef __NOPT_ASSERT__
+	m_if_status>>=1;
+	#endif
+	
+	#ifdef STOPWATCH_STUFF
+	pIfSkipStopWatch->Stop();
+	#endif
+}
+
+// We can hit an endif either by finishing the 'true' block of an if-statement
+// that has no else, or by finishing the 'false' block following an else.
+// Either way there is nothing to do, so just skip over it.
+void CScript::execute_endif()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_ENDIF,("Unexpected *mp_pc='%s', expected keyword 'endif'",GetTokenName((EScriptToken)*mp_pc)));
+	
+	#ifdef STOPWATCH_STUFF
+	pIfSkipStopWatch->Start();
+	#endif
+	
+	++mp_pc;
+
+	#ifdef __NOPT_ASSERT__
+	m_if_status>>=1;
+	#endif
+
+	#ifdef STOPWATCH_STUFF
+	pIfSkipStopWatch->Stop();
+	#endif
+}		
+
+// Skips mp_pc forward till it finds an endswitch, skipping over any nested switch
+// statements in between.
+// Afterwards, mp_pc will point to the token following the endswitch.
+void CScript::skip_to_after_endswitch()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	
+	while (*mp_pc!=ESCRIPTTOKEN_KEYWORD_ENDSWITCH)
+	{
+		if (*mp_pc==ESCRIPTTOKEN_KEYWORD_SWITCH)
+		{
+			// Skip over the switch
+			++mp_pc;
+			skip_to_after_endswitch();
+		}
+		else
+		{
+			mp_pc=SkipToken(mp_pc);
+		}
+	}
+	
+	// Skip over the endswitch
+	++mp_pc;
+}
+
+// Given that mp_pc points to a switch token, this will skip mp_pc forward to the 
+// code of the matching case. Or if none matches, it will point to after the endswitch.
+void CScript::execute_switch()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_SWITCH,("Unexpected *mp_pc='%s', expected keyword 'switch'",GetTokenName((EScriptToken)*mp_pc)));
+	
+	#ifdef __NOPT_ASSERT__
+	// Remember the old pc value so that the printed line number is correct if 
+	// no endswitch is found before the end of file is hit.
+	uint8 *start_of_switch_pc=mp_pc;
+	#endif
+
+	// Skip over the switch token
+	++mp_pc;
+	
+	// Put the parameters following the switch keyword into mp_function_params
+	Dbg_MsgAssert(mp_function_params,("NULL mp_function_params"));
+	mp_function_params->Clear();
+	mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params);
+	
+	// Get the first component and make a copy of it, putting it into p_comp.
+	// Making a copy cos mp_function_params is needed later to hold the params following
+	// each case statement that follows.
+	CComponent *p_switch_comp=new CComponent;
+	CComponent *p_first_comp=mp_function_params->GetNextComponent();
+	if (p_first_comp)
+	{
+		Dbg_MsgAssert(mp_function_params->GetNextComponent(p_first_comp)==NULL,("\n%s\nswitch argument contains more than one component",GetScriptInfo()));
+		CopyComponent(p_switch_comp,p_first_comp);
+	}			
+	
+	// Skip forward till we hit a matching case, or a default, or endswitch
+	// But, if we hit another switch statement whilst looking, 
+	// skip over it, ignoring its cases.
+	bool found=false;
+	while (!found)
+	{
+		switch (*mp_pc)
+		{
+		case ESCRIPTTOKEN_KEYWORD_SWITCH:
+		{
+			// Skip over the switch
+			++mp_pc;
+			skip_to_after_endswitch();
+			// mp_pc now points to the token following the ESCRIPTTOKEN_KEYWORD_ENDSCRIPT
+			break;
+		}
+			
+		case ESCRIPTTOKEN_KEYWORD_CASE:
+		{
+			++mp_pc;
+			
+			mp_function_params->Clear();
+			mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params);
+			
+			CComponent *p_case_comp=mp_function_params->GetNextComponent();
+			if (p_case_comp)
+			{
+				Dbg_MsgAssert(mp_function_params->GetNextComponent(p_case_comp)==NULL,("\n%s\ncase argument contains more than one component",GetScriptInfo()));
+				
+				if (*p_switch_comp==*p_case_comp)
+				{
+					found=true;
+				}
+			}
+			
+			if (found)
+			{
+				// We've found a match, but we need to skip over any case statements
+				// that immediately follow. This is for when multiple cases want to
+				// execute the same chunk of code.
+				while (true)
+				{
+					mp_pc=SkipEndOfLines(mp_pc);
+					if (*mp_pc==ESCRIPTTOKEN_KEYWORD_CASE)
+					{
+						++mp_pc;
+						mp_function_params->Clear();
+						mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params);
+					}							
+					else
+					{
+						break;
+					}
+				}		
+			}
+			break;
+		}	
+		case ESCRIPTTOKEN_KEYWORD_DEFAULT:
+		case ESCRIPTTOKEN_KEYWORD_ENDSWITCH:
+			++mp_pc;
+			found=true;
+			break;
+		case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
+			#ifdef __NOPT_ASSERT__
+			mp_pc=start_of_switch_pc;
+			Dbg_MsgAssert(0,("\n%s\nMissing endswitch",GetScriptInfo()));
+			#endif
+			break;
+		default:
+			mp_pc=SkipToken(mp_pc);
+			break;
+		}	
+	}
+	
+	CleanUpComponent(p_switch_comp);
+	delete p_switch_comp;
+}
+
+void CScript::execute_begin()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_BEGIN,("Unexpected *mp_pc='%s', expected keyword 'begin'",GetTokenName((EScriptToken)*mp_pc)));
+	++mp_pc;
+	
+	// First, check whether we're about to run out of space on the small statically allocated loop buffer.
+	if (mp_current_loop==mp_loops_small_buffer+NESTED_BEGIN_REPEATS_SMALL_BUFFER_SIZE-1)
+	{
+		// There won't be enough room for the new loop!
+
+		// A quick check, to ensure no memory leaks.
+		Dbg_MsgAssert(mp_loops==mp_loops_small_buffer,("Expected mp_loops==mp_loops_small_buffer, %x, %x",mp_loops,mp_loops_small_buffer));
+		
+		// Dynamically allocate a bigger buffer, and change mp_loops to point to that instead
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+		mp_loops=(SLoop*)Mem::Malloc(MAX_NESTED_BEGIN_REPEATS * sizeof(SLoop));
+		Mem::Manager::sHandle().PopContext();
+		
+		// Then copy over the contents of the small buffer, and update mp_current_loop to point into the new buffer.
+		int i;
+
+		for (i=0; i= mp_loops_small_buffer && mp_return_addresses[i].mpLoop < mp_loops_small_buffer+NESTED_BEGIN_REPEATS_SMALL_BUFFER_SIZE,("Bad mp_return_addresses[i].mpLoop"));
+				
+				mp_return_addresses[i].mpLoop=(mp_return_addresses[i].mpLoop-mp_loops_small_buffer)+mp_loops;
+			}
+		}    
+	}
+	
+	if (mp_current_loop)
+	{            
+		Dbg_MsgAssert(mp_current_loop-mp_loopsmpStart=mp_pc;
+	// These get filled in once the repeat is reached.
+	mp_current_loop->mpEnd=NULL;
+	mp_current_loop->mGotCount=false;
+	mp_current_loop->mNeedToReadCount=true;
+	mp_current_loop->mCount=0;
+}
+
+
+void CScript::execute_repeat()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_REPEAT,("Unexpected *mp_pc='%s', expected keyword 'repeat'",GetTokenName((EScriptToken)*mp_pc)));
+	Dbg_MsgAssert(mp_current_loop,("\n%s\nEncountered repeat with NULL mp_current_loop",GetScriptInfo()));
+	
+	if (mp_current_loop->mNeedToReadCount)
+	{
+		// Skip over the repeat token. 
+		++mp_pc;
+		Dbg_MsgAssert(mp_function_params,("NULL mp_function_params"));
+		mp_function_params->Clear();
+		mp_current_loop->mpEnd=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params);
+		mp_current_loop->mGotCount=mp_function_params->GetInteger(NO_NAME,&mp_current_loop->mCount);
+		if (!mp_current_loop->mGotCount)
+		{
+			Dbg_MsgAssert(mp_current_loop->mpEnd==mp_pc,("\n%s\nNo count value found following 'repeat'",GetScriptInfo()));
+		}		
+		
+		mp_current_loop->mNeedToReadCount=false;
+	}	
+		
+	if (!mp_current_loop->mGotCount)
+	{
+		// It's an infinite loop.
+		mp_pc=mp_current_loop->mpStart;
+	}
+	else
+	{
+		Dbg_MsgAssert(mp_current_loop->mCount,("\n%s\nZero count given to a begin-repeat loop",GetScriptInfo()));
+		--mp_current_loop->mCount;
+		if (mp_current_loop->mCount)
+		{
+			// Finite loop, but it has not finished yet, so jump back to the start.
+			mp_pc=mp_current_loop->mpStart;
+		}
+		else
+		{
+			// The loop has finished!
+			
+			// Jump PC to the next instruction after the repeat.
+			Dbg_MsgAssert(mp_current_loop->mpEnd,("NULL mp_current_loop->pEnd ??")); 
+			mp_pc=mp_current_loop->mpEnd;
+			
+			// Rewind to the previous loop.
+			if (mp_current_loop==mp_loops)
+			{
+				mp_current_loop=NULL;
+			}
+			else
+			{
+				--mp_current_loop;
+				Dbg_MsgAssert(mp_current_loop>=mp_loops,("\n%s\nBad mp_current_loop",GetScriptInfo()));
+			}
+		}
+	}
+}
+
+void CScript::execute_break()
+{
+	Dbg_MsgAssert(mp_pc,("NULL mp_pc"));
+	Dbg_MsgAssert(*mp_pc==ESCRIPTTOKEN_KEYWORD_BREAK,("Unexpected *mp_pc='%s', expected keyword 'break'",GetTokenName((EScriptToken)*mp_pc)));
+	Dbg_MsgAssert(mp_current_loop,("\n%s\nEncountered break with NULL mp_current_loop",GetScriptInfo()));
+	
+	// Step over every token until repeat is reached.
+
+	#ifdef __NOPT_ASSERT__
+	int nested_if_count=0;
+	#endif	
+	int nested_loop_count=0;
+	bool in_loop=true;
+	// Skip mp_pc to the end of the loop.
+	while (in_loop)
+	{
+		switch (*mp_pc)
+		{
+		case ESCRIPTTOKEN_KEYWORD_BEGIN:
+			++nested_loop_count;
+			break;
+			
+		case ESCRIPTTOKEN_KEYWORD_REPEAT:
+			if (nested_loop_count)
+			{
+				--nested_loop_count;
+			}
+			else
+			{
+				in_loop=false;
+			}
+			break;
+			
+		#ifdef __NOPT_ASSERT__
+		case ESCRIPTTOKEN_KEYWORD_IF:
+			++nested_if_count;
+			break;
+			
+		case ESCRIPTTOKEN_KEYWORD_ENDIF:
+			if (nested_if_count)
+			{
+				--nested_if_count;
+			}
+			else
+			{
+				// Make sure that the stored if-status is rewound back to its
+				// state before the loop was entered, otherwise the
+				// 'too many nested ifs' assert will go off erroneously.
+				m_if_status>>=1;
+			}
+			break;
+		#endif
+		
+		case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
+			Dbg_MsgAssert(0,("\n%s\nRepeat keyword not found after break keyword.",GetScriptInfo()));
+			break;
+		}
+			
+		mp_pc=SkipToken(mp_pc);
+	}    
+	
+	// Use AddComponentsUntilEndOfLine just to step over the possible repeat argument.
+	// Ugh! Kind of ugly, but shouldn't be too slow because repeat will probably be followed
+	// by nothing or just an integer.
+	Dbg_MsgAssert(mp_function_params,("NULL mp_function_params"));
+	mp_function_params->Clear();
+	mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc);
+
+	
+	// Rewind to the previous loop.
+	if (mp_current_loop==mp_loops)
+	{
+		mp_current_loop=NULL;
+	}
+	else
+	{
+		--mp_current_loop;
+		Dbg_MsgAssert(mp_current_loop>=mp_loops,("\n%s\nBad mp_current_loop",GetScriptInfo()));
+	}
+}
+
+// Returns from a sub-script by popping the info (mp_pc etc) off the stack.
+// If there is nothing on the stack, it sets mp_pc to NULL
+// Returns true if the script being returned from was a script triggered by Interrupt.
+// This then allows Update() to not continue execution of the script that was interrupted.
+bool CScript::execute_return()
+{
+	// The current script is finished with, so decrement its usage in the script cache.
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	p_script_cache->DecrementScriptUsage(mScriptChecksum);
+
+	bool was_interrupted=m_interrupted;
+	
+	if (m_num_return_addresses)
+	{
+		// This script was called from another, either by a regular call or by an interrupt.
+		
+		--m_num_return_addresses;
+		
+		// Delete the current mp_params, because it was created when this routine was called.
+		Dbg_MsgAssert(mp_params,("\n%s\nNULL p_params",GetScriptInfo()));
+		delete mp_params;
+		
+		// Restore the info stored on the stack.
+		SReturnAddress *p_info=mp_return_addresses+m_num_return_addresses;
+		
+		mScriptChecksum = p_info->mScriptNameChecksum;
+		mp_params		= p_info->mpParams;
+		mp_pc			= p_info->mpReturnAddress;
+		mpObject		= p_info->mpObject;
+		mp_current_loop = p_info->mpLoop;
+		#ifdef __NOPT_ASSERT__
+		m_if_status		= p_info->m_if_status;
+		#endif
+
+		m_wait_type		= p_info->mWaitType;
+		if (m_wait_type)
+		{
+			m_wait_timer	= p_info->mWaitTimer;
+			m_wait_period	= p_info->mWaitPeriod;
+			m_start_time	= p_info->mStartTime;
+			mp_wait_component = p_info->mpWaitComponent;
+		}	
+		
+		m_interrupted=p_info->mInterrupted;
+		
+		#ifdef __NOPT_ASSERT__ 
+		check_if_needs_to_be_watched_in_debugger();
+		#endif	
+		
+		// Check that the script being returned to does still exist, ie mp_pc is valid.
+		// However, if we're running an embedded script, don't do the check, because the
+		// script name may be the name of the embedded script rather than a global script.
+		if (!mp_struct_script)
+		{
+			Dbg_MsgAssert(LookUpSymbol(mScriptChecksum),("Non-existent script '%s' on call stack",FindChecksumName(mScriptChecksum)));
+		}
+			
+		// Actually, we need a better check to see if mp_pc is still valid, because the
+		// script we're returning to might have got reloaded and hence moved in memory.
+		// Should use a smart pointer.
+		// But, doesn't seem to have caused any problems so far, so leave for the moment ...
+	}
+	else
+	{
+		// Nothing to return to, so stop the script by setting mp_pc to NULL.
+		// This occurs when an endscript is hit.
+		mp_pc=NULL;
+	}	
+	
+	return was_interrupted;
+}
+
+#ifdef	__NOPT_ASSERT__
+#ifndef __PLAT_WN32__
+void CScript::advance_pc_to_next_line_and_halt()
+{
+	if (m_being_watched_in_debugger)
+	{
+		if (m_single_step_mode==STEP_OVER || 
+			m_single_step_mode==STEP_INTO)
+		{
+			// Record the current return address count so that it can be detected whether
+			// the next command is a script call. Needed for stepping over script calls.
+			m_last_num_return_addresses_when_halted=m_num_return_addresses;
+			
+			if (mp_pc)
+			{
+				mp_pc=SkipEndOfLines(mp_pc);
+			}	
+			
+			TransmitInfoToDebugger();
+			m_last_instruction_time_taken=0.0f;
+			Dbg::CScriptDebugger::Instance()->ScriptDebuggerPause();
+		}
+	}
+}			
+#endif
+#endif
+
+//static int sInstructionCount=0;
+//static uint64 sLastVBlanks=0;
+
+// Update the script, executing instructions
+// REQUIREMENT: is mp_pc is NULL, then return  ESCRIPTRETURNVAL_FINISHED, so the script is deleted by UpdateSpawnedScript
+EScriptReturnVal CScript::Update()
+{
+	#ifdef	__NOPT_ASSERT__
+	m_last_instruction_time_taken=0.0f;	
+	m_total_instruction_time_taken=0.0f;
+	if (m_being_watched_in_debugger)
+	{
+		TransmitInfoToDebugger();
+	}	
+	#endif
+
+	#ifdef STOPWATCH_STUFF
+	pUpdateStopWatch->Start();
+	#endif
+
+	#ifdef	__NOPT_ASSERT__
+	Tmr::CPUCycles start_time = Tmr::GetTimeInCPUCycles();
+	#endif
+
+
+	//if (sLastVBlanks!=Tmr::GetVblanks())
+	//{
+	//	sLastVBlanks=Tmr::GetVblanks();
+	//	printf("Instruction count = %d\n",sInstructionCount);
+	//	sInstructionCount=0;
+	//}	
+	
+
+	#ifdef	__NOPT_ASSERT__	
+	Obj::CObjectPtr p_obj = NULL;
+	if (mpObject)
+	{
+		p_obj = mpObject;	   			// remember the object we locked
+//		mpObject->SetLockAssertOn();
+	}
+	#endif
+	
+	m_skip_further_process_waits=false;
+	
+	// This loop is going to keep executing script commands until either mp_pc
+	// becomes NULL somehow, or if some sort of wait command gets executed.
+    while (true)
+    {
+		if (!mp_pc) 
+		{
+			// Break out if mp_pc became NULL during the execution of the
+			// last command. 
+			#ifdef STOPWATCH_STUFF
+			pUpdateStopWatch->Stop();
+			#endif
+			sCurrentlyUpdating=NULL;
+	#ifdef	__NOPT_ASSERT__	
+	
+//			if (mpObject)
+//			{
+//				// check the object we locked is the same one we are unlocking (or deleted)
+//				Dbg_MsgAssert(p_obj == mpObject,("\n%s\nFinished Script has changed objects to %p! Original Object %p Still Locked! Tell Mick\n",GetScriptInfo(),mpObject.Convert(),p_obj.Convert()));
+//				mpObject->SetLockAssertOff();
+//			}
+			// If script is waiting, then we might be on a different object
+			// if the original object still exists, then unlock it.
+			if (p_obj)
+			{
+				p_obj->SetLockAssertOff();
+			}
+	#endif																	
+			#ifdef	__NOPT_ASSERT__
+			start_time = Tmr::GetTimeInCPUCycles() - start_time;
+			m_last_time = (int)start_time;	 // experiment with uint etc....
+			m_total_time += m_last_time;
+			#endif
+			
+			// Seems like a reasonable thing to do ...
+			m_interrupted=false;
+			
+			return ESCRIPTRETURNVAL_FINISHED;
+		}	
+		else
+		{
+			// Make sure sCurrentlyUpdating is correct, because it may have
+			// got changed during the execution of the last command.
+			sCurrentlyUpdating=this;
+		}	
+	
+		#ifdef	__NOPT_ASSERT__
+		bool was_waiting_before;
+		if (m_wait_type==WAIT_TYPE_NONE)
+		{
+			was_waiting_before=false;
+		}
+		else
+		{
+			was_waiting_before=true;
+		}
+		#endif
+		
+		if (!m_skip_further_process_waits)
+		{
+			process_waits();
+		}	
+
+		// Return straight away if waiting.
+		if (m_wait_type!=WAIT_TYPE_NONE)
+		{
+			#ifdef STOPWATCH_STUFF
+			pUpdateStopWatch->Stop();
+			#endif
+			sCurrentlyUpdating=NULL;
+
+	#ifdef	__NOPT_ASSERT__	
+			// If script is waiting, then we might be on a different object
+			// if the original object still exists, then unlock it.
+			if (p_obj)
+			{
+				p_obj->SetLockAssertOff();
+			}
+	#endif	 
+
+	#ifdef	__NOPT_ASSERT__
+			start_time = Tmr::GetTimeInCPUCycles() - start_time;
+			m_last_time = (int)start_time;	 // experiment with uint etc....
+			m_total_time += m_last_time;
+	#endif
+	   	
+			// If an interrupt script hits a blocking command, then clear the interrupted flag.
+			// Reason:
+			// If an interrupt script contains no blocking functions, then a single call to CScript::Update()
+			// would execute the whole script, and as soon as the endscript was reached it was exit the Update()
+			// function (due to the interrupt flag being on) hence returning control to the original Update()
+			// function that was executing the interrupted script, and all would be well.
+			
+			// However, if the interrupt script did hit a blocking function, that would cause an early exit from
+			// the Update() function, leaving it to the original Update() function, ie the one that was executing the
+			// interrupted script, to finish completion of the interrupt script. In that case, we don't want
+			// the interrupt flag to be on any more, because otherwise it would cause a premature exit from
+			// the original Update() function as soon as the endscript was reached.
+			m_interrupted=false;
+	
+			// Set this to prevent any previous CScript::Update() loop that the return may return to
+			// from processing the waits further, otherwise if an interrupt script contains a wait n gameframes
+			// it will end up waiting only n-1 gameframes, due to the interrupted scripts Update() loop calling
+			// process_waits again.
+			m_skip_further_process_waits=true;
+			
+			if (m_wait_type==WAIT_TYPE_BLOCKED)
+			{
+				return ESCRIPTRETURNVAL_BLOCKED;
+			}
+			else
+			{
+				return ESCRIPTRETURNVAL_WAITING;
+			}
+		}		
+		else
+		{
+			#ifdef	__NOPT_ASSERT__
+			if (was_waiting_before)
+			{
+				advance_pc_to_next_line_and_halt();
+			}	
+			#endif
+		}		
+
+		// Execute whatever is at mp_pc
+		#ifdef	__NOPT_ASSERT__
+		Tmr::CPUCycles instruction_start_time = Tmr::GetTimeInCPUCycles();
+		#endif
+		
+		switch (*mp_pc)
+		{
+		case ESCRIPTTOKEN_KEYWORD_IF:
+			//++sInstructionCount;
+			// This will execute the function call or expression following the if,
+			// and if the command or expression returns false, it will skip
+			// mp_pc forward to the token after the else or endif.
+			// Otherwise, mp_pc will be left pointing to the first token of the 'true' block.
+			execute_if();
+			// Note: mp_pc may be NULL at this point
+			break;
+			
+		case ESCRIPTTOKEN_KEYWORD_ELSE:
+			//++sInstructionCount;
+			// The only time an else token should be hit is at the end of executing
+			// the 'true' block following the if.
+			// So this will just skip mp_pc over the 'false' block to after the endif.
+			execute_else();
+			break;
+			
+		case ESCRIPTTOKEN_KEYWORD_ENDIF:
+			//++sInstructionCount;
+			// We can hit an endif either by finishing the 'true' block of an if-statement
+			// that has no else, or by finishing the 'false' block following an else.
+			// Either way there is nothing to do, so just skip over it, but assert if we're
+			// not in an if-statement to catch spurious else's.
+			execute_endif();
+			break;
+
+        case ESCRIPTTOKEN_KEYWORD_SWITCH:
+			//++sInstructionCount;
+			// Skips mp_pc forward to the token following the matching case. 
+			// Or if none matches, it will point to after the endswitch.
+			execute_switch();
+			break;
+
+        case ESCRIPTTOKEN_KEYWORD_CASE:
+		case ESCRIPTTOKEN_KEYWORD_DEFAULT:
+        case ESCRIPTTOKEN_KEYWORD_ENDSWITCH:
+			//++sInstructionCount;
+			// If we hit a case, default or endswitch, it must be due to the completion of the
+			// previous case statement's commands.
+			// So skip mp_pc forward so that it points to after the endswitch token.
+			skip_to_after_endswitch();
+			break;
+			
+        case ESCRIPTTOKEN_KEYWORD_BEGIN:
+			//++sInstructionCount;
+			// Initialise a new loop counter
+			execute_begin();
+            break;        
+
+        case ESCRIPTTOKEN_KEYWORD_REPEAT:
+			//++sInstructionCount;
+			// This will either jump mp_pc back to the start of the loop,
+			// or skip it past the repeat if the loop has finished.
+			execute_repeat();
+            break;
+
+        case ESCRIPTTOKEN_KEYWORD_BREAK:
+			//++sInstructionCount;
+			// This will skip mp_pc to after the repeat of the current loop
+			execute_break();
+            break;
+		    
+        case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
+			//++sInstructionCount;
+			if (execute_return())
+			{
+				// If we returned from a script call caused by an interrupt, then do not continue
+				// with execution of the interrupted script, since an interrupt should not affect the
+				// interrupted script.
+				return ESCRIPTRETURNVAL_FINISHED_INTERRUPT;
+			}	
+			// Note: mp_pc may be NULL at this point, if there is no calling script.
+            break;
+			
+        case ESCRIPTTOKEN_KEYWORD_RETURN:
+			//++sInstructionCount;
+			
+			// The return keyword works very similar to hitting the endscript token.
+			// The difference is that 'return' will merge the parameters that follow the
+			// return keyword onto the parameters of the calling script, if there is one.
+			
+			// Skip over the return token. 
+			++mp_pc;
+			
+			// Put any parameters that follow into mp_function_params
+			Dbg_MsgAssert(mp_function_params,("NULL mp_function_params"));
+			mp_function_params->Clear();
+			mp_pc=AddComponentsUntilEndOfLine(mp_function_params,mp_pc,mp_params);
+
+			if (execute_return())
+			{
+				// If we returned from a script call caused by an interrupt, then do not continue
+				// with execution of the interrupted script, since an interrupt should not affect the
+				// interrupted script.
+				// For the same reason we are bailing out here before the returned parameters are merged onto
+				// the interrupted scripts params, so that they will not be affected by the interrupt either.
+				return ESCRIPTRETURNVAL_FINISHED_INTERRUPT;
+			}	
+			
+			// Note: mp_pc may be NULL at this point, if there is no calling script.
+
+			// Merge the return values onto the parameters of the calling script.
+			Dbg_MsgAssert(mp_params,("NULL mp_params ?"));
+			mp_params->AppendStructure(mp_function_params);
+			break;
+
+        case ESCRIPTTOKEN_KEYWORD_RANDOM:
+        case ESCRIPTTOKEN_KEYWORD_RANDOM2:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE:
+        case ESCRIPTTOKEN_JUMP:
+			// Modify mp_pc according to any jump or random operators, and repeat until mp_pc no
+			// longer points to a jump or random.
+			mp_pc=DoAnyRandomsOrJumps(mp_pc);
+			break;
+
+        case ESCRIPTTOKEN_ENDOFLINE:
+            ++mp_pc;
+            break;
+            
+	    case ESCRIPTTOKEN_ENDOFLINENUMBER:
+			mp_pc+=5; // 1 for the token, 4 for the line number.
+            break;
+		
+        default:
+			//++sInstructionCount;
+			//printf("Running line %d in script '%s'\n",GetLineNumber(mp_pc),FindChecksumName(mScriptChecksum));
+			execute_command();
+			// Note: mp_pc may be NULL at this point
+            break;
+		}	
+		
+		#ifdef	__NOPT_ASSERT__
+		
+		if (m_being_watched_in_debugger)
+		{
+			Tmr::CPUCycles t=Tmr::GetTimeInCPUCycles()-instruction_start_time;
+			float last_instruction_time_taken=((float)((int)t))/150.0f;
+			
+			m_last_instruction_time_taken+=last_instruction_time_taken;
+			m_total_instruction_time_taken+=last_instruction_time_taken;
+		}		
+		
+		if (m_single_step_mode==STEP_OVER &&
+			m_num_return_addresses > m_last_num_return_addresses_when_halted)
+		{
+			// If the number of return addresses has increased beyond what it was when last halted
+			// in the debugger, then the last command must have been a script call, so do not halt
+			// but keep executing instructions until the script call has finished, which will be 
+			// indicated by the return address count returning to what it was or lower.
+			// (It would be lower if say a goto occurred during the script call)
+			
+		}
+		else
+		{
+			if (m_wait_type == WAIT_TYPE_NONE)
+			{
+				advance_pc_to_next_line_and_halt();
+			}	
+		}	
+		#endif	
+    }
+}
+
+void CScript::Wait(int num_frames)
+{
+	m_wait_type=WAIT_TYPE_COUNTER;
+    m_wait_timer=num_frames;
+}
+
+void CScript::WaitOnePerFrame(int num_frames)
+{
+	m_wait_type=WAIT_TYPE_ONE_PER_FRAME;
+    m_wait_timer=num_frames;		 		
+}
+
+
+void CScript::WaitTime(Tmr::Time period)
+{
+	m_wait_type=WAIT_TYPE_TIMER;
+	m_wait_period=period;
+	// Record the start time.
+	m_start_time=Tmr::GetTime();
+}
+
+bool CScript::RefersToScript(uint32 checksum)
+{
+	if (mScriptChecksum==checksum) 
+	{
+		return true;
+	}	
+	
+	for (int i=0; iInNetGame())
+	{
+		Net::MsgDesc msg_desc;
+		msg.m_ScriptName = scriptChecksum;
+		msg.m_ObjID = -1;
+		if( p_object )
+		{
+			msg.m_ObjID = p_object->GetID();
+		}
+
+		size = WriteToBuffer(p_params, (uint8 *) msg.m_Data, GameNet::MsgRunScript::vMAX_SCRIPT_PARAMS_LEN );
+		Dbg_MsgAssert( size <= GameNet::MsgRunScript::vMAX_SCRIPT_PARAMS_LEN,( "Script too large to send over the net\n" ));
+		
+		client = gamenet_man->GetClient( 0 );
+		Dbg_Assert( client );
+
+		msg_size = ( sizeof( GameNet::MsgRunScript ) - GameNet::MsgRunScript::vMAX_SCRIPT_PARAMS_LEN ) +
+					size;
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = msg_size;
+		msg_desc.m_Id = GameNet::MSG_ID_RUN_SCRIPT;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		client->EnqueueMessageToServer( &msg_desc );
+	
+	}
+	#endif
+}
+
+#ifdef __NOPT_ASSERT__
+static const char *sGetRunScriptName(uint32 scriptChecksum, const char *p_scriptName)
+{
+	if (p_scriptName)
+	{
+		return p_scriptName;
+	}
+	return FindChecksumName(scriptChecksum);
+}	
+#endif
+
+// Used for running a simple script from start to end.
+void RunScript(uint32 scriptChecksum, CStruct *p_params, Obj::CObject *p_object, bool netScript, const char *p_scriptName )
+{
+	// First, see what type of symbol scriptChecksum is referring to.
+    CSymbolTableEntry *p_entry=Resolve(scriptChecksum);
+	if (!p_entry)
+	{
+		#ifdef __NOPT_ASSERT__
+		printf("Warning! RunScript could not find '%s'\n",sGetRunScriptName(scriptChecksum,p_scriptName));
+		#endif
+		return;
+	}	
+	if (p_entry->mType!=ESYMBOLTYPE_QSCRIPT && 
+		p_entry->mType!=ESYMBOLTYPE_CFUNCTION &&
+		p_entry->mType!=ESYMBOLTYPE_MEMBERFUNCTION)
+	{
+		#ifdef __NOPT_ASSERT__
+		printf("Warning! RunScript sent '%s' which is not a script, a c-function or a member function. Type=%s\n",sGetRunScriptName(scriptChecksum,p_scriptName),GetTypeName(p_entry->mType));
+		#endif
+		return;
+	}
+
+
+	if( netScript )
+	{
+		SendScript( scriptChecksum, p_params, p_object );
+	}
+
+	switch (p_entry->mType)
+	{
+		case ESYMBOLTYPE_CFUNCTION:
+		{
+			// If the symbol is actually a c-function rather than a script, then run the c-function.
+			// This is handy sometimes because it saves having to write a special script just to run 
+			// one c-function.
+			Dbg_MsgAssert(p_entry->mpCFunction,("NULL pCFunction"));
+
+			// Mick:  We now pass in NULL, as creating a dummy script is very slow			
+			(*p_entry->mpCFunction)(p_params,NULL);
+			
+			break;
+		}
+		
+		case ESYMBOLTYPE_MEMBERFUNCTION:
+		{
+			Dbg_MsgAssert(p_object,("Tried to run member function '%s' on NULL p_object",Script::FindChecksumName(scriptChecksum)));
+	
+			// Mick:  We now pass in NULL, as creating a dummy script is very slow			
+			p_object->CallMemberFunction(scriptChecksum,p_params,NULL);
+			
+			break;
+		}
+		
+		case ESYMBOLTYPE_QSCRIPT:
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+			CScript *p_script=new CScript;
+			#ifdef __NOPT_ASSERT__
+			p_script->SetCommentString("Created by RunScript(...)");
+			#endif
+			p_script->SetScript(scriptChecksum,p_params,p_object);
+			Mem::Manager::sHandle().PopContext();
+		
+			while (true)
+			{
+				EScriptReturnVal ret_val=p_script->Update();
+				if (ret_val==ESCRIPTRETURNVAL_FINISHED)
+				{
+					break;
+				}
+				//Dbg_MsgAssert(0,("\n%s\nScript did not finish when run by RunScript.",p_script->GetScriptInfo()));
+				// Script must not get blocked, otherwise it'll hang in this loop forever.
+				Dbg_MsgAssert(ret_val!=ESCRIPTRETURNVAL_BLOCKED,("\n%s\nScript got blocked when being run by RunScript.",p_script->GetScriptInfo()));
+				// Note: OK if the script returns ESCRIPTRETURNVAL_WAITING, because the wait period will eventually end.
+			
+			}
+			delete p_script;
+			break;
+		}	
+		
+		default:
+			break;
+	}	
+}
+
+void RunScript(const char *p_scriptName, CStruct *p_params, Obj::CObject *p_object, bool netScript )
+{
+	// Quite often, if a script cannot be found then FindChecksumName will not be able to find
+	// the checksum name either, so since we know the name here it gets passed along to
+	// the other version of RunScript so that the name can be printed in any warning message.
+	RunScript(Crc::GenerateCRCFromString(p_scriptName), p_params, p_object, netScript, p_scriptName);
+}
+
+/////////////////////////////////////////////////////////////////
+//
+// 					Spawning script stuff
+//
+/////////////////////////////////////////////////////////////////
+
+static bool s_updating_scripts = false;
+static bool s_delete_scripts_pending = false;
+
+// This gets called from within DeleteSymbols.
+void DeleteSpawnedScripts()
+{
+	// Guard against deleting spawned scripts while we are updating them
+	if( s_updating_scripts )
+	{
+		s_delete_scripts_pending = true;
+		return;
+	}
+
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		// Don't kill the script if it is not session-specific.
+		// This feature is currently used by one of Steve's net scripts which needs to not
+		// be killed by ScriptCleanup
+		if (p_script->mIsSpawned && !p_script->mNotSessionSpecific)
+		{
+			delete p_script;
+		}	
+		p_script=p_next;
+	}	
+}
+
+// This gets called from Front::PauseGame
+void PauseSpawnedScripts(bool status)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		if (p_script->mIsSpawned)
+		{
+			p_script->mPaused=status;
+		}
+		p_script=GetNextScript(p_script);
+	}	
+}
+
+void UnpauseSpawnedScript(CScript* p_script)
+{
+	if (p_script && p_script->mIsSpawned)
+	{
+		p_script->mPaused=false;
+	}
+}
+
+// Note: Does this need to be fast?
+uint32 NumSpawnedScriptsRunning()
+{
+	uint32 num_spawned_scripts=0;
+	
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		if (p_script->mIsSpawned)
+		{
+			++num_spawned_scripts;
+		}
+		p_script=GetNextScript(p_script);
+	}	
+	
+	return num_spawned_scripts;
+}
+
+// Called once a frame. Currently called from Skate::DoUpdate()
+void UpdateSpawnedScripts()
+{
+
+	s_updating_scripts = true;
+	s_done_one_per_frame = false;
+	
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next;
+		// get the next actual spawned script
+		p_next=GetNextScript(p_script);		
+		// skip any non-spawned scripts
+		// since they might get deleted in an unsafe manner
+		while (p_next && !p_next->mIsSpawned)
+		{
+			p_next=GetNextScript(p_next);				
+		}
+		
+		Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1,("Next script in spawned list has been deleted sometime earlier!"));
+		if (p_next == p_script)
+		{
+			Dbg_MsgAssert(0,("%s\nLoop in list of spawned scripts!!",p_script->GetScriptInfo()));
+		}
+		
+		// K: Added the || !p_script->GotScript() so that spawned scripts which have been cleared still get
+		// cleaned up when the game is paused. This was to fix a bug (TT1453) where cleared scripts where accumulating
+		// during the cutscenes. The scripts were the scripts of goal peds, which got cleared when the peds were
+		// killed. 
+		if (p_script->mIsSpawned && (!p_script->mPaused || !p_script->GotScript()) && (!p_script->mPauseWithObject || !p_script->mpObject || p_script->mpObject->ShouldUpdatePauseWithObjectScripts()))
+		{
+			if (p_script->Update()==ESCRIPTRETURNVAL_FINISHED)
+			{
+				// just doing the assertion before we delete the script  
+				Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1,("%s\nNext script in spawned list has been deleted by this script updating",p_script->GetScriptInfo()));
+				// If it had a callback script specified, run it.
+				if (p_script->mCallbackScript)
+				{
+					RunScript(p_script->mCallbackScript,
+							  p_script->mpCallbackScriptParams,
+							  p_script->mpObject);
+				}
+
+				// just doing the assertion before we delete the script  
+				Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1,
+				("Next script in spawned list has been deleted by callback script (%s)",FindChecksumName(p_script->mCallbackScript)));
+				// Kill it now that it has finished.
+				delete p_script;
+			}
+			else
+			{
+				Dbg_MsgAssert(GetNextScript(p_next) != (CScript*)-1,("%s\nNext script in spawned list has been deleted by this script",p_script->GetScriptInfo()));
+			}
+		}	
+			
+		p_script=p_next;
+		Dbg_MsgAssert(GetNextScript(p_script) != (CScript*)-1,("Next script in spawned list has been deleted by this script"));
+	}	
+
+
+	s_updating_scripts = false;
+	if( s_delete_scripts_pending )
+	{
+		DeleteSpawnedScripts();
+		s_delete_scripts_pending = false;
+	}
+
+
+}
+
+// Sned spawn script events to other clients
+void SendSpawnScript( uint32 scriptChecksum, Obj::CObject *p_object, int node, bool permanent )
+{
+	#ifdef __PLAT_WN32__
+	// No GameNet stuff if compiling on PC
+	#else
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client;
+	GameNet::MsgSpawnAndRunScript msg;
+    
+	if( gamenet_man->InNetGame())
+	{
+		Net::MsgDesc msg_desc;
+
+		msg.m_ScriptName = scriptChecksum;
+		msg.m_ObjID = -1;
+		msg.m_Node = node;
+		msg.m_Permanent = (char) permanent;
+		if( p_object )
+		{
+			msg.m_ObjID = p_object->GetID();
+		}
+
+		client = gamenet_man->GetClient( 0 );
+		Dbg_Assert( client );
+
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgSpawnAndRunScript );
+		msg_desc.m_Id = GameNet::MSG_ID_SPAWN_AND_RUN_SCRIPT;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		client->EnqueueMessageToServer( &msg_desc );
+	}
+	#endif
+}
+
+
+CScript *GetScriptWithUniqueId(uint32 id)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		if (p_script->GetUniqueId() == id) 
+			return p_script;
+		p_script = GetNextScript(p_script);
+	}	
+
+	return NULL;
+}
+
+// Called from ScriptSpawnScript in cfuncs.cpp
+// also called by the triggering code in skater.cpp
+// returns the new script if sucessful, asserts if not
+// optional "node" parameter is the node number that is
+// responsible for spawning this script.
+// Also now takes an optional Id, to allow individual spawned script instances to be killed.
+CScript* SpawnScript(uint32 scriptChecksum, CStruct *p_scriptParams, uint32 callbackScript, CStruct *p_callbackParams, int node, uint32 id,
+					 bool netEnabled, bool permanent, bool not_session_specific, bool pause_with_object )
+{
+	Dbg_MsgAssert(scriptChecksum,("Zero checksum sent to SpawnScript"));
+	
+    CSymbolTableEntry *p_entry=Resolve(scriptChecksum);
+    if (p_entry)
+    {
+        if (p_entry->mType==ESYMBOLTYPE_CFUNCTION)
+		{
+			Dbg_MsgAssert(callbackScript==0,("A callbackScript cannot currently be specified when running SpawnScript on a c-function. (cfunc='%s' callback='%s')",FindChecksumName(scriptChecksum),FindChecksumName(callbackScript)));
+			
+			// Creating a dummy script to send to the c-function. Normally the c-function would
+			// only be called from within a script's update function, and it uses the passed script 
+			// pointer in any call to GetScriptInfo if an assert goes off. 
+			// So we need to pass a dummy rather than NULL so that it does not crash if it
+			// dereferences the pointer.
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+			CScript *p_dummy=new CScript;
+			Mem::Manager::sHandle().PopContext();
+			
+			(*p_entry->mpCFunction)(p_scriptParams,p_dummy);
+			
+			delete p_dummy;
+			return NULL;
+		}
+		Dbg_MsgAssert(p_entry->mType!=ESYMBOLTYPE_MEMBERFUNCTION,("SpawnScript cannot run the member function '%s'",FindChecksumName(scriptChecksum)));
+	}	
+	
+	
+	CScript *p_script=new CScript;
+	p_script->SetScript(scriptChecksum, p_scriptParams, NULL);
+	#ifdef __NOPT_ASSERT__
+	p_script->SetCommentString("Created by SpawnScript");
+	#endif
+	
+	
+	p_script->mNode = node;
+	p_script->mIsSpawned = true;
+	p_script->mId = id;
+	p_script->mPaused = false;
+	p_script->mNotSessionSpecific=not_session_specific;
+	p_script->mPauseWithObject = pause_with_object;
+	
+	Dbg_MsgAssert(p_script->mpCallbackScriptParams==NULL,("p_script->mpCallbackScriptParams not NULL ?"));
+	if (callbackScript)
+	{
+		p_script->mCallbackScript=callbackScript;
+		p_script->mpCallbackScriptParams=new CStruct;
+		if (p_callbackParams)
+		{
+			*p_script->mpCallbackScriptParams+=*p_callbackParams;
+		}	
+	}
+		
+	#ifdef __PLAT_WN32__
+	// No GameNet stuff if compiling on PC
+	#else
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	if( netEnabled && gamenet_man->InNetGame())
+	{
+		// TODO: Should pass on the not_session_specific flag ...
+		SendSpawnScript( scriptChecksum, NULL, node, permanent );
+	}
+	#endif
+
+	return p_script;
+}
+
+
+bool	ScriptExists(uint32 scriptNameChecksum)
+{
+	CSymbolTableEntry *p_entry=Resolve(scriptNameChecksum);
+	if (p_entry && p_entry->mType==ESYMBOLTYPE_QSCRIPT)
+	{
+		return true;
+	}
+	return false;
+}
+
+CScript* SpawnScript(const char *p_scriptName, CStruct *p_scriptParams, uint32 callbackScript, CStruct *p_callbackParams, int node, uint32 id, 
+					 bool netEnabled, bool permanent, bool not_session_specific, bool pause_with_object )
+{
+    
+	return SpawnScript(Crc::GenerateCRCFromString(p_scriptName),p_scriptParams,callbackScript,p_callbackParams,node,id, netEnabled, permanent, not_session_specific, pause_with_object);
+}
+
+// Kills a spawned script, given a pointer to the actual script
+void KillSpawnedScript(CScript *p_script)
+{
+    if (p_script && p_script->mIsSpawned)
+	{
+		delete p_script;
+	}	
+}
+
+
+// Kills all spawned scripts that are either currently running this script directly,
+// or have it in their call stack somewhere. (Ie, they will return to it later)
+void KillSpawnedScriptsThatReferTo(uint32 checksum)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		if (p_script->mIsSpawned && p_script->RefersToScript(checksum))
+		{
+//			delete p_script;
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}	
+		p_script=p_next;
+	}	
+}
+
+// Kills all scripts with this particular Id.
+void KillSpawnedScriptsWithId(uint32 id)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		if (p_script->mIsSpawned && (p_script->mId==id || p_script->GetUniqueId()==id))
+		{
+// Mick, instead of deleteing the script, we now just clear it
+// this stops it from executing, but leaves it in the linked list
+// so it can be cleaned up next time around "UpdateSpawnedScripts"
+// Scripts that have ClearScript() called will automatically be deleted
+// as they have mp_pc set to NULL
+//			delete p_script;
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}	
+		p_script=p_next;
+	}	
+}
+
+// Kills all spawned scripts that have the passed object as their parent object.
+// This gets called from within Script::KillAllScriptsWithObject.
+void KillSpawnedScriptsWithObject(Obj::CObject *p_object)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		if (p_script->mIsSpawned && p_script->RefersToObject(p_object))
+		{
+//			delete p_script;
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}	
+		p_script=p_next;
+	}	
+}
+
+// Kills all spawned scripts with a particular Id & parent object.
+// Called by Matt's Obj_KillSpawnedScript CObject script member function.
+void KillSpawnedScriptsWithObjectAndId(Obj::CObject *p_object, uint32 id, CScript *p_callingScript)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		if (p_script->mIsSpawned && 
+			p_script!=p_callingScript &&
+			p_script->RefersToObject(p_object) &&
+			p_script->mId==id)
+		{
+//			delete p_script;
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}	
+		p_script=p_next;
+	}	
+}
+
+// Kills all spawned scripts with a particular Id & parent object.
+// Called by Matt's Obj_KillSpawnedScript CObject script member function.
+void KillSpawnedScriptsWithObjectAndName(Obj::CObject *p_object, uint32 name, CScript *p_callingScript )
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		if (p_script->mIsSpawned && 
+			p_script!=p_callingScript &&
+			p_script->RefersToObject(p_object) &&
+			p_script->mScriptChecksum==name)
+		{
+//			delete p_script;
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}	
+		p_script=p_next;
+	}	
+}
+
+// Returns: 0 if spawned script has no ID, or if the script isn't a spawned script
+// Otherwise, returns the ID:
+uint32 FindSpawnedScriptID(CScript *p_script)
+{
+	if (!p_script)
+	{
+		return 0;
+	}
+	
+	if (!p_script->mIsSpawned)
+	{
+		return 0;
+	}
+
+	return p_script->mId;		
+}
+
+CScript* FindSpawnedScriptWithID(uint32 id)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		if (p_script->mIsSpawned && 
+			p_script->mId==id)
+		{
+			return p_script;
+		}	
+		p_script=p_next;
+	}
+	
+	return NULL;
+}
+
+
+// TODO: This just included to allow compilation of GenerateCRC, remove later.
+uint32 GenerateCRC(const char *p_string)
+{
+	return Crc::GenerateCRCFromString(p_string);
+}
+
+// Stops all scripts.
+// Currently only used by the StopAllScripts script function, which currently isn't used
+// anywhere, but might be handy one day.
+void StopAllScripts()
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		p_script->ClearScript();
+		p_script->ClearEventHandlerTable();
+		p_script=GetNextScript(p_script);
+	}	
+}
+
+// Stops all scripts with a particular object as their parent, but does not delete them.
+// They cannot be restarted after being stopped.
+// This is basically a way of killing the scripts without actually
+// deleting them, so it won't cause any invalid script pointers.
+// Currently gets called at the end of the CObject destructor.
+void StopAllScriptsUsingThisObject(Obj::CObject *p_object)
+{
+	StopScriptsUsingThisObject(p_object, 0);
+}
+
+// If scriptCrc != 0, then only stop scripts with that ID, otherwise stop all scripts using object
+void StopScriptsUsingThisObject(Obj::CObject *p_object, uint32 scriptCrc)
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		if (p_script->RefersToObject(p_object) && (!scriptCrc || p_script->mScriptChecksum == scriptCrc))
+		{
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}	
+		p_script=GetNextScript(p_script);
+	}	
+}
+
+// TODO: brad - this is a last minute fix to StopScriptsUsingThisObject.
+// Rather than fixing the existing function so close to release, I've added
+// this version to be called only when we know we want to use it.
+void StopScriptsUsingThisObject_Proper(Obj::CObject *p_object, uint32 scriptCrc)
+{
+	CScript *p_script=GetNextScript();
+	while ( p_script )
+	{
+		if ( p_script->RefersToObject( p_object ) && ( !scriptCrc || p_script->RefersToScript( scriptCrc ) ) )
+		{
+			p_script->ClearScript();
+			p_script->ClearEventHandlerTable();
+		}	
+		p_script=GetNextScript( p_script );
+	}	
+}
+
+
+// Kills any stopped scripts, ie scripts with their mpPC null. These
+// would be scripts stopped by the above StopAllScriptsUsingThisObject
+void KillStoppedScripts()
+{
+	CScript *p_script=GetNextScript();
+	while (p_script)
+	{
+		CScript *p_next=GetNextScript(p_script);
+		
+		// Delete the script if it has stopped (mpPC==NULL), but not if it is a spawned script,
+		// because otherwise it will leave a dangling pointer in the array of spawned scripts,
+		// which will then get dereferenced in UpdateSpawnedScripts and crash.
+		// If the script has stopped, the next call to UpdateSpawnedScripts will delete it, so
+		// the script is guaranteed to get cleaned up eventually.
+		
+		#ifdef	__SCRIPT_EVENT_TABLE__		
+		if (!p_script->GotScript() && !p_script->mIsSpawned && !p_script->GetEventHandlerTable())
+		#else
+		if (!p_script->GotScript() && !p_script->mIsSpawned)
+		#endif
+		{
+			
+			
+			#ifdef __NOPT_ASSERT__
+			printf("Cleaning up script '%s'\n",FindChecksumName(p_script->mScriptChecksum));
+			#endif
+			
+			delete p_script;
+		}	
+		
+		p_script=p_next;
+	}	
+}
+
+// Run by the DumpScripts script function.
+// Prints out the names of all the currently existing scripts.
+void DumpScripts()
+{
+	#ifdef __NOPT_ASSERT__
+	printf("###########################################################\n\n");
+	printf("All the CScripts that currently exist ...\n\n");
+	
+	CScript *p_scr=GetNextScript();
+	int n = 0;
+	while (p_scr)
+	{
+		printf ("%3d %8d ",p_scr->m_last_time/150, p_scr->m_total_time/150);
+		p_scr->m_last_time = 0;
+		p_scr->m_total_time = 0;	
+					  
+		printf ("%3d: ",n++);
+		if (p_scr->mIsSpawned)
+		{
+			printf("S ");
+		}		
+		else
+		{
+			printf("  ");
+		}
+
+		switch (p_scr->GetWaitType())
+		{
+		case WAIT_TYPE_NONE:
+			printf("None            ");
+			break;
+		case WAIT_TYPE_COUNTER:
+			printf("Counter         ");
+			break;
+		case WAIT_TYPE_ONE_PER_FRAME:
+			printf("one_per_frame   ");
+			break;
+		case WAIT_TYPE_TIMER:
+			printf("Timer           ");
+			break;
+		case WAIT_TYPE_BLOCKED:
+			printf("Blocked         ");
+			break;
+		case WAIT_TYPE_OBJECT_MOVE:
+			printf("Ob Move         ");
+			break;
+		case WAIT_TYPE_OBJECT_ANIM_FINISHED:
+			printf("Ob AnimFinished ");
+			break;
+		case WAIT_TYPE_OBJECT_JUMP_FINISHED:
+			printf("Ob JumpFinished ");
+			break;
+		case WAIT_TYPE_OBJECT_ROTATE:
+			printf("Ob Rotate       ");
+			break;
+		default:
+			printf("Unknown ??????  ");
+			break;
+		}	
+
+		if (p_scr->Finished())
+		{
+			printf("[Fin] ");
+		}
+			
+		uint32 checksum=p_scr->GetBaseScript();
+		if (checksum)
+		{
+			CSymbolTableEntry *p_sym=LookUpSymbol(checksum);
+			Dbg_MsgAssert(p_sym,("NULL pSym ??"));
+	
+//			printf("%s: %s\n",FindChecksumName(p_sym->mSourceFileNameChecksum),FindChecksumName(checksum));
+			printf("%s\n",FindChecksumName(checksum));
+		}
+		else
+		{
+			printf (", Checksum is NULL, probably dead script\n");
+		}
+		
+		p_scr=GetNextScript(p_scr);
+	}	
+	printf("\n");
+	#endif
+}
+
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+
+/*
+	Sets up the table that specifies which scripts to run in response to which events.
+	
+	See object scripting document.
+*/
+void CScript::SetEventHandlers(Script::CArray *pArray, EReplaceEventHandlers replace)
+{
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+	if (!mp_event_handler_table)
+	{
+		mp_event_handler_table = new Obj::CEventHandlerTable();
+	}
+	else
+	{
+		mp_event_handler_table->unregister_all(this);	
+	}
+	mp_event_handler_table->add_from_script(pArray, replace);
+	
+	// Mick:  We still need the compress_table for this way of adding event handlers
+	// as the "replace" logic otherwise leads to a rapidly growing table.
+	// especially on the on-screen keyboard
+	mp_event_handler_table->compress_table();
+	
+	mp_event_handler_table->register_all(this);
+
+	Mem::Manager::sHandle().PopContext();
+
+}
+
+
+void	CScript::SetEventHandler(uint32 ex, uint32 scr, uint32 group, bool exception, CStruct *p_params)
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+	// Need to create the table if not there	
+	if (!mp_event_handler_table)
+	{
+		mp_event_handler_table = new Obj::CEventHandlerTable();
+	}
+	
+	// Add the event to the table
+	mp_event_handler_table->AddEvent(ex,scr,group, exception, p_params); 	
+
+	// Inregistering the event receiver, all we are doing is adding an
+	// script pointer to the list of scripts that handle this 
+	// type of exception
+	// there is nothing specific to the actual handler
+	// if the script is already in the list, then nothing needs changing. 
+	Obj::CTracker::Instance()->RegisterEventReceiver(ex, this); 
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+
+
+/*
+	Removes an entry in the event table with the given type id
+*/
+void CScript::RemoveEventHandler(uint32 type)
+{
+	if (!mp_event_handler_table) return;
+	mp_event_handler_table->remove_entry(type);
+//	mp_event_handler_table->compress_table();
+
+// Refresh the Obj::Tracker
+
+	Obj::CTracker::Instance()->UnregisterEventReceiver(type, this); 
+		
+}
+
+
+
+/*
+	Removes all entries in the event table with the given group id
+*/
+void CScript::RemoveEventHandlerGroup(uint32 group)
+{
+	if (!mp_event_handler_table) return;
+	
+	mp_event_handler_table->unregister_all(this);	
+	
+	mp_event_handler_table->remove_group(group);
+//	mp_event_handler_table->compress_table();
+	
+	mp_event_handler_table->register_all(this);	
+
+}
+
+/*
+	Dumps the event table
+*/
+void CScript::PrintEventHandlerTable (   )
+{
+	Obj::CEventHandlerTable::sPrintTable(mp_event_handler_table);
+}
+
+bool CScript::PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast)
+{
+	if (mp_event_handler_table)
+		mp_event_handler_table->pass_event(pEvent, this, broadcast);
+	return true;
+}
+
+void 	CScript::SetOnExceptionScriptChecksum(uint32 OnExceptionScriptChecksum) 
+{
+	mOnExceptionScriptChecksum = OnExceptionScriptChecksum;
+}
+
+uint32 	CScript::GetOnExceptionScriptChecksum() const 
+{
+	return mOnExceptionScriptChecksum;
+}
+
+void 	CScript::SetOnExitScriptChecksum(uint32 OnExitScriptChecksum) 
+{
+	mOnExitScriptChecksum = OnExitScriptChecksum;
+}
+
+uint32 	CScript::GetOnExitScriptChecksum() const 
+{
+	return mOnExitScriptChecksum;
+}
+
+
+
+#endif
+
+} // namespace Script
+
+
diff --git a/Code/Gel/Scripting/script.h b/Code/Gel/Scripting/script.h
new file mode 100644
index 0000000..f9df8fa
--- /dev/null
+++ b/Code/Gel/Scripting/script.h
@@ -0,0 +1,508 @@
+#ifndef	__SCRIPTING_SCRIPT_H
+#define	__SCRIPTING_SCRIPT_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+// TODO: Remove this at some point, only necessary for the CScriptStructure define, to make everything compile.
+#include 
+#endif
+
+#ifndef __SYS_TIMER_H
+#include  // For Tmr::Time
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+
+#include 
+
+
+namespace Obj
+{
+	class CBaseComponent;
+	class CEvent;
+	class CEventHandlerTable;
+}
+	
+namespace Script
+{
+
+// TODO: Remove this at some point, only necessary to make the other game-code headers compile without having
+// go through them all including vecpair.h
+// Currently they assume everything is declared in script.h
+class CPair;
+
+class CStruct;
+class CSymbolTableEntry;
+struct SStructScript;
+
+// The maximum level of Begin-Repeat nesting.
+#define MAX_NESTED_BEGIN_REPEATS 10
+#define NESTED_BEGIN_REPEATS_SMALL_BUFFER_SIZE 1
+
+// The maximum number of nested calls to other scripts.                              
+#define MAX_RETURN_ADDRESSES 16
+// The number of SReturnAddress structures in the small buffer in each CScript
+// Mostly 2 is all that is needed, so a buffer of MAX_RETURN_ADDRESSES is only allocated when needed.
+#define RETURN_ADDRESSES_SMALL_BUFFER_SIZE 2
+
+// Return values from CScript::Update()
+enum EScriptReturnVal
+{
+    ESCRIPTRETURNVAL_FINISHED,
+    ESCRIPTRETURNVAL_BLOCKED,
+	ESCRIPTRETURNVAL_WAITING,
+    ESCRIPTRETURNVAL_ERROR,
+	ESCRIPTRETURNVAL_STOPPED_IN_DEBUGGER,
+	ESCRIPTRETURNVAL_FINISHED_INTERRUPT,
+};
+
+// Values for CScript::m_wait_type
+enum EWaitType
+{
+	WAIT_TYPE_NONE=0,
+	WAIT_TYPE_COUNTER,
+	WAIT_TYPE_TIMER,
+	WAIT_TYPE_BLOCKED,
+	WAIT_TYPE_OBJECT_MOVE,
+	WAIT_TYPE_OBJECT_ANIM_FINISHED,
+	WAIT_TYPE_OBJECT_JUMP_FINISHED,
+	WAIT_TYPE_OBJECT_STOP_FINISHED,
+	WAIT_TYPE_OBJECT_ROTATE,
+	WAIT_TYPE_STREAM_FINISHED,
+	WAIT_TYPE_ONE_PER_FRAME,
+};
+
+enum ESingleStepMode
+{
+	OFF=0,
+	WAITING,
+	STEP_INTO,
+	STEP_OVER,
+};
+
+enum
+{
+	DEFINITELY_TRANSMIT=1
+};
+	
+// Begin-repeat loop structure.    
+struct SLoop
+{
+    // Pointer to the start of the loop, which is the
+    // token following the Begin.
+    uint8 *mpStart;
+	
+	// Pointer to the end of the loop, ie the next instruction after the loop.
+	uint8 *mpEnd;
+    
+	bool mGotCount;			// Whether it is a finite loop with a count value.
+	bool mNeedToReadCount;	// Whether the count value following the repeat needs to be read.
+    int mCount;				// Counts down to zero, and skips past the repeat once it reaches zero.
+};
+
+// Saves the original location & other info when calling a subroutine.
+struct SReturnAddress
+{
+    uint32 mScriptNameChecksum;
+    CStruct *mpParams;
+    uint8 *mpReturnAddress;
+	Obj::CObject *mpObject;
+	SLoop *mpLoop;
+	#ifdef __NOPT_ASSERT__
+	uint32 m_if_status;
+	#endif
+	EWaitType mWaitType;
+	Obj::CBaseComponent *mpWaitComponent;
+    int mWaitTimer;
+	Tmr::Time mStartTime;
+	Tmr::Time mWaitPeriod;
+	bool mInterrupted;
+};
+
+// Script class.
+// To run a script, one must create one of these, then call the SetScript member function to
+// set which script it is to run.
+//
+// Then the Update function must be called repeatedly.
+// The script will never die by itself, it must be deleted.
+//
+// Poolable to speed up allocation & deallocation.
+// MEMOPT sizeof(CScript) was 1080 last time I checked, due to the arrays of 
+// loops, return addresses and if's. Could maybe allocate these off pools instead, if the number
+// of CScript's ever gets big enough to be using up a substantial amount of memory.
+// There would be a slight speed hit if pools were used, but probably not much, 
+// allocating/deallocating off a pool is pretty quick.
+#ifdef __PLAT_WN32__
+class CScript
+#else
+class CScript : public Mem::CPoolable
+#endif
+{
+	// CScripts are linked into a list.
+	CScript *mp_next;
+	CScript *mp_previous;
+	friend CScript *GetNextScript(CScript *p_script=NULL);
+	
+	// If this CScript got setup using a SStructScript (via the SetScript member function below)
+	// then this will be a pointer to a copy of the script, and mp_pc will point somewhere into it.
+	// Otherwise mp_struct_script will be left as NULL.
+	uint8 *mp_struct_script;
+	
+    // The program counter.
+    uint8 *mp_pc;
+	
+	// Holds the parameters for passing to function calls.
+	CStruct *mp_function_params;
+
+    // The input parameters, which get accessed within the script using the <,> operator.
+    CStruct *mp_params;
+	
+    // Begin-Repeat loop stuff.
+    // NULL if not in a loop, otherwise points into the following array.
+    SLoop *mp_current_loop;
+	// If the number of loops needs to be bigger than NESTED_BEGIN_REPEATS_SMALL_BUFFER_SIZE then
+	// mp_loops will point to a dynamically allocated array of MAX_NESTED_BEGIN_REPEATS SLoop structures,
+	// otherwise mp_loops will equal mp_loops_small_buffer
+    SLoop mp_loops_small_buffer[NESTED_BEGIN_REPEATS_SMALL_BUFFER_SIZE];
+	SLoop *mp_loops;
+	
+	
+
+    // Call stack.
+    int m_num_return_addresses;
+    SReturnAddress mp_return_addresses_small_buffer[RETURN_ADDRESSES_SMALL_BUFFER_SIZE];
+	// If the num return addresses needs to be bigger than RETURN_ADDRESSES_SMALL_BUFFER_SIZE then
+	// mp_return_addresses will point to a dynamically allocated array of MAX_RETURN_ADDRESSES SReturnAddress structures,
+	// otherwise mp_return_addresses will equal mp_return_addresses_small_buffer
+	SReturnAddress *mp_return_addresses;
+	
+	#ifdef __NOPT_ASSERT__
+	// Used to determine when to step-over a script command when the script
+	// is being stepped through in the debugger.
+	int m_last_num_return_addresses_when_halted;
+	#endif
+	
+    // Used by the Wait function to cause a script to hiccup.
+    int m_wait_timer;
+	
+	// Used by the WaitTime function to cause the script to wait for an
+	// absolute amount of time.
+	Tmr::Time m_start_time;
+	Tmr::Time m_wait_period;
+
+	// How the script is waiting.
+	EWaitType m_wait_type;
+	Obj::CBaseComponent *mp_wait_component;
+
+	
+
+	// a helpful member added by Ryan
+	uint32	m_unique_id;
+	// used for computing unique IDs
+	static int s_next_unique_id;
+	
+	// Gets set to true if the script was forced to call another script by a call to Interrupt.
+	// This will get stored in the call stack.
+	// Needed to prevent the call to Update() that Interrupt does from continuing the execution of
+	// the interrupted script when the called script finishes.
+	bool m_interrupted;
+
+	void call_script(uint32 newScriptChecksum, uint8 *p_script, CStruct *p_params, Obj::CObject *p_object, bool interrupt=false);
+	void set_script(uint32 scriptChecksum, uint8 *p_script, CStruct *p_params, Obj::CObject *p_object);
+	
+	// This flag is to prevent multiple calls to process_waits in nested calls to CScript::Update(),
+	// such as when a script command causes an interrupt to occur on itself.
+	bool m_skip_further_process_waits;	
+	
+	void process_waits();
+	
+	void load_function_params();
+	bool run_member_function(uint32 functionName, Obj::CObject *p_substituteObject=NULL);
+	bool run_cfunction(bool (*p_cfunc)(CStruct *pParams, CScript *pCScript));
+	uint32 get_name();
+	
+	bool execute_command();
+	
+	
+	#ifdef __NOPT_ASSERT__
+	bool 			m_if_debug_on;
+	uint32 			m_if_status; 			// For detecting spurious else's
+	#endif
+
+
+	#ifdef __NOPT_ASSERT__
+	// Used by script debugger code.
+	uint32 m_last_transmitted_info_checksum;
+	bool m_being_watched_in_debugger;
+	ESingleStepMode m_single_step_mode;
+	enum
+	{
+		MAX_COMMENT_STRING_CHARS=63,
+	};
+	char mp_comment_string[MAX_COMMENT_STRING_CHARS+1];
+	int m_originating_script_line_number;
+	uint32 m_originating_script_name;
+	float m_last_instruction_time_taken;
+	float m_total_instruction_time_taken;
+	
+	#ifdef __PLAT_WN32__
+	uint32 *write_callstack_entry(uint32 *p_buf, int bufferSize, uint32 scriptNameChecksum, uint8 *p_PC, CStruct *p_params, Obj::CObject *p_ob) {}
+	void check_if_needs_to_be_watched_in_debugger() {}
+	void advance_pc_to_next_line_and_halt() {}
+	#else
+	uint32 *write_callstack_entry(uint32 *p_buf, int bufferSize, uint32 scriptNameChecksum, uint8 *p_PC, CStruct *p_params, Obj::CObject *p_ob);
+	void check_if_needs_to_be_watched_in_debugger();
+	void advance_pc_to_next_line_and_halt();
+	#endif
+	#endif
+	
+	void execute_if();
+	void execute_else();
+	void execute_endif();
+	
+	
+	void skip_to_after_endswitch();
+	void execute_switch();
+	
+	void execute_begin();
+	void execute_repeat();
+	void execute_break();
+	
+	bool execute_return();
+
+public:
+
+	#ifdef __NOPT_ASSERT__
+	#ifdef __PLAT_WN32__
+	void SetCommentString(const char *p_comment) {}
+	void SetOriginatingScriptInfo(int lineNumber, uint32 scriptName) {}
+	void TransmitInfoToDebugger(bool definitely_transmit=false) {}
+	void TransmitBasicInfoToDebugger() {}
+	void WatchInDebugger(bool stopScriptImmediately) {}
+	bool BeingWatchedInDebugger() {return false;}
+	void StopWatchingInDebugger() {}
+	void DebugStop() {}
+	void DebugStepInto() {}
+	void DebugStepOver() {}
+	void DebugGo() {}
+	#else	
+	void SetCommentString(const char *p_comment);
+	void SetOriginatingScriptInfo(int lineNumber, uint32 scriptName);
+	// This gets called for all CScript's every frame.
+	// It transmits lots of info about the script to the script debugger running on the PC
+	void TransmitInfoToDebugger(bool definitely_transmit=false);
+	void TransmitBasicInfoToDebugger();
+	void WatchInDebugger(bool stopScriptImmediately);
+	bool BeingWatchedInDebugger() {return m_being_watched_in_debugger;}
+	void StopWatchingInDebugger();
+	void DebugStop();
+	void DebugStepInto();
+	void DebugStepOver();
+	void DebugGo();
+	
+	int GetNumReturnAddresses() {return m_num_return_addresses;}
+	bool UsingBigCallstackBuffer();
+	bool UsingBigLoopBuffer();
+	#endif
+	#endif
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	// Event Handler/Exception Stuff
+	void				SetEventHandler(uint32 ex, uint32 scr, uint32 group, bool exception, CStruct* p_params);
+	void  				SetEventHandlers(Script::CArray *pArray, EReplaceEventHandlers replace = DONT_REPLACE_HANDLERS);
+	void				RemoveEventHandler(uint32 type);
+	void				RemoveEventHandlerGroup(uint32 group);
+	void 				SetOnExceptionScriptChecksum(uint32 OnExceptionScriptChecksum);
+	uint32 				GetOnExceptionScriptChecksum() const; 
+	void 				SetOnExitScriptChecksum(uint32 OnExceptionScriptChecksum);
+	uint32 				GetOnExitScriptChecksum() const; 
+	bool				PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast = false);	// return true if object still valid
+#endif
+
+	void				PrintEventHandlerTable (   );
+	
+	CStruct *GetParams() {Dbg_MsgAssert(mp_params,("NULL mp_params ?")); return mp_params;}
+
+	int	mNode;		// Number of the node that caused this script to be spawned, -1 if none specific
+
+    // If NULL then the script is a free script, eg StartUp.
+    // If non-NULL, then the script is associated with some object.
+    Obj::CObjectPtr mpObject;
+
+    // Checksum of the name of this script.
+    uint32 mScriptChecksum;
+
+	void SetWait(EWaitType type, Obj::CBaseComponent *p_component);
+	void ClearWait();
+	EWaitType GetWaitType() {return m_wait_type;}
+
+
+	bool mIsSpawned:1;	// True if this is a spawned script.
+	// Note: These next members only apply if the script is spawned.
+	bool mNotSessionSpecific:1; // If this is true then the spawned script will not get
+								// deleted by DeleteSpawnedScripts
+	bool mPaused:1;
+	bool mPauseWithObject:1;	// If this is true then the spawned script will pause when its object's ShouldUpdatePauseWithObjectScripts
+								// returns false.  CCompositeObjects return	false when they are paused.
+	uint32 mId;
+	// An optional callback script, which gets run as soon as the spawned script completes.
+	uint32 mCallbackScript;
+	CStruct *mpCallbackScriptParams;
+
+	CScript();
+    ~CScript();
+
+    void SetScript(uint32 scriptChecksum, CStruct *p_params=NULL, Obj::CObject *p_object=NULL);
+    void SetScript(const char *p_scriptName, CStruct *p_params=NULL, Obj::CObject *p_object=NULL);
+    void SetScript(const SStructScript *p_structScript, CStruct *p_params=NULL, Obj::CObject *p_object=NULL);
+	
+	CStruct *MakeParamsSafeFromDeletionByClearScript(CStruct *p_params);
+	void ClearScript();
+	void ClearEventHandlerTable();
+	bool RefersToScript(uint32 checksum);
+	bool RefersToObject(Obj::CObject *p_object);
+	void RemoveReferencesToObject(Obj::CObject *p_object);
+	
+	bool AllScriptsInCallstackStillExist();
+	uint32 GetBaseScript();
+	void Restart();
+	EScriptReturnVal Interrupt(uint32 newScriptChecksum, CStruct *p_params=NULL);
+	
+    EScriptReturnVal Update();
+    bool Finished();
+
+	void Block();
+	void UnBlock();
+	bool getBlocked();
+
+	void Clear();
+    void Wait(int numFrames);
+    void WaitOnePerFrame(int numFrames);
+	void WaitTime(Tmr::Time period);
+	bool GotScript();
+
+	uint32 GetUniqueId() {return m_unique_id;}
+	
+	// Stops the script from being associated with an object, ie, sets mpObject to NULL.
+	// Allows the script to continue executing though, so the script must not contain any
+	// further member functions, otherwise the code will assert.
+	// This function also sets mWaitType=WAIT_TYPE_NONE; to ensure the Update function does
+	// not assert if it is waiting for the object to finish doing something.
+	void DisassociateWithObject(Obj::CObject *pObjectToDisconnectFrom);
+	
+	#ifdef __NOPT_ASSERT__
+	const char *GetScriptInfo();
+	int GetCurrentLineNumber(void);
+	const char *GetSourceFile(void);
+	
+	// and let's have it for debugging for now, in case people (Mick!) leave it in by mistake and screw up the build
+	const inline char *GetScriptInfo( void ) const { return ""; }
+	#else // #ifdef __NOPT_ASSERT__
+#	ifdef __PLAT_XBOX__
+	const inline char *GetScriptInfo( void ) const { return ""; }
+#	endif // ifdef __PLAT_XBOX__
+
+	#ifdef __PLAT_WN32__
+	const char *GetScriptInfo() const {return "";}
+	#endif
+	
+	#endif // #ifdef __NOPT_ASSERT__
+	
+	void SwitchOnIfDebugging();
+	void SwitchOffIfDebugging();
+
+// These are pseudo-private, for debugging profiling only
+	#ifdef __NOPT_ASSERT__
+	int				m_last_time;			// time spend in last update	
+	int 			m_total_time;			// total time spent updating
+	#endif
+	
+#ifdef	__SCRIPT_EVENT_TABLE__		
+private:	
+	Obj::CEventHandlerTable *	mp_event_handler_table;
+	
+	// Script to run when there is an exception	
+	// (needed here, since we are getting rid of CExceptionComponent, to replace it with one 
+	// single event system
+	uint32 			mOnExceptionScriptChecksum;
+	uint32 			mOnExitScriptChecksum;
+public:
+	Obj::CEventHandlerTable * GetEventHandlerTable() {return mp_event_handler_table;}
+	Obj::CRefCounted			m_reference_counter;		// having a reference counter allows us to have a safe smart pointer	
+#endif	 
+
+
+
+};
+
+void SendScript( uint32 scriptChecksum, CStruct *p_params, Obj::CObject *p_object );
+
+void RunScript(const char *p_scriptName, CStruct *p_params=NULL, Obj::CObject *p_object=NULL,
+				bool netScript = false );
+void RunScript(uint32 scriptChecksum, CStruct *p_params=NULL, Obj::CObject *p_object=NULL,
+				bool netScript = false, const char *p_scriptName=NULL );
+
+void DeleteSpawnedScripts();
+void UpdateSpawnedScripts();
+void PauseSpawnedScripts(bool status);
+void UnpauseSpawnedScript(CScript* p_script);
+uint32 NumSpawnedScriptsRunning();
+bool	ScriptExists(uint32 scriptNameChecksum);
+
+CScript* SpawnScript(uint32 scriptChecksum, CStruct *p_script_params=NULL, 
+					 uint32 callbackScript=NO_NAME, CStruct *p_callbackParams=NULL,
+					 int node = -1, uint32 id=0,
+					 bool netEnabled = false, bool permanent = false, bool not_session_specific=false, bool pause_with_object=false );
+CScript* SpawnScript(const char *pScriptName, CStruct *p_script_params=NULL,
+					 uint32 callbackScript=NO_NAME, CStruct *p_callbackParams=NULL,
+					 int node = -1, uint32 id=0,
+					 bool netEnabled = false, bool permanent = false, bool not_session_specific=false, bool pause_with_object=false );
+void KillSpawnedScript(CScript *p_script);
+void KillSpawnedScriptsThatReferTo(uint32 checksum);
+void KillSpawnedScriptsWithId(uint32 id);
+void KillSpawnedScriptsWithObject(Obj::CObject *p_object);
+void KillSpawnedScriptsWithObjectAndId(Obj::CObject *p_object, uint32 id, CScript *p_callingScript);
+void KillSpawnedScriptsWithObjectAndName(Obj::CObject *p_object, uint32 name, CScript *p_callingScript);
+uint32 FindSpawnedScriptID(CScript *p_script);
+CScript* FindSpawnedScriptWithID(uint32 id);
+void SendSpawnScript( uint32 scriptChecksum, Obj::CObject *p_object, int node, bool permanent );
+
+
+CScript *GetScriptWithUniqueId(uint32 id);
+
+// These should not be here, but should be in the game-specific scripting directory.
+// Just putting them here for the moment to get things to compile ...
+class CArray;
+
+// TODO: This just included to allow compilation of GenerateCRC, remove later.
+uint32 GenerateCRC(const char *p_string);
+
+void StopAllScripts();
+void StopAllScriptsUsingThisObject(Obj::CObject *p_object);
+void StopScriptsUsingThisObject(Obj::CObject *p_object, uint32 scriptCrc);
+void StopScriptsUsingThisObject_Proper(Obj::CObject *p_object, uint32 scriptCrc);
+void KillStoppedScripts();
+void DumpScripts();
+
+uint32 GetNumCScripts();
+
+CScript *GetCurrentScript();
+
+#ifdef	__NOPT_ASSERT__
+const char * GetCurrentScriptInfo();
+#endif
+
+} // namespace Script
+
+#endif	// __SCRIPTING_SCRIPT_H
+
diff --git a/Code/Gel/Scripting/scriptcache.cpp b/Code/Gel/Scripting/scriptcache.cpp
new file mode 100644
index 0000000..a0544bf
--- /dev/null
+++ b/Code/Gel/Scripting/scriptcache.cpp
@@ -0,0 +1,574 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGC__
+#include 
+#include 
+#endif		// __PLAT_NGC__
+
+#ifdef NO_SCRIPT_CACHING
+#else
+DefinePoolableClass(Script::CScriptCacheEntry);
+#endif
+
+// Make CScriptCache::GetDebugInfo determine how much the cache is 'thrashing' (ie, average number of new script
+// entries created per frame over last second, & indicate the max)
+// Make scripts not get removed from the cache straight away when their usage hits 0, but just
+// remove the oldest zero-usage script when the heap is full and a new script needs to go in.
+// Make CScriptCache::GetDebugInfo sort the array of scripts in order of usage
+
+// Implement compression in qcomp.
+// Test, test qbr, test size of cache required, test speed.
+// Make it possible to switch back to no caching very easily. (mybe even dynamically?)
+namespace Script
+{
+
+DefineSingletonClass( CScriptCache, "Script cache module" );
+
+#ifdef NO_SCRIPT_CACHING
+CScriptCache::CScriptCache()
+{
+}
+
+CScriptCache::~CScriptCache()
+{
+}
+
+void CScriptCache::v_start_cb ( void )
+{
+}
+
+void CScriptCache::v_stop_cb ( void )
+{
+}
+
+uint8 *CScriptCache::GetScript(uint32 scriptName)
+{
+    CSymbolTableEntry *p_entry=Resolve(scriptName);
+    if (p_entry)
+    {
+        Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_QSCRIPT,("Symbol %s is not a QScript",FindChecksumName(scriptName)));
+        Dbg_MsgAssert(p_entry->mpScript,("NULL p_entry->mpScript"));
+
+		return p_entry->mpScript+4; // +4 to skip over the contents checksum
+	}
+	
+	return NULL;
+}
+
+void CScriptCache::DecrementScriptUsage(uint32 scriptName)
+{
+}
+
+void CScriptCache::RefreshAfterReload(uint32 scriptName)
+{
+}
+
+void CScriptCache::DeleteZeroUsageStraightAway()
+{
+}
+
+void CScriptCache::DeleteOldestZeroUsageWhenNecessary()
+{
+}
+
+#ifdef __NOPT_ASSERT__
+void CScriptCache::GetDebugInfo( Script::CStruct* p_info )
+{
+	p_info->AddChecksum(NONAME,CRCD(0xf34b1528,"ScriptCachingDisabled"));
+}
+#endif
+
+#else
+
+CScriptCacheEntry::CScriptCacheEntry()
+{
+	mpDecompressedScript=NULL;
+	mUsage=0;
+	mScriptNameChecksum=0;
+	mpNext=NULL;
+	mpPrevious=NULL;
+}
+
+CScriptCacheEntry::~CScriptCacheEntry()
+{
+	// This is just to make sure nothing accidentally deletes an entry in the CScriptCache's
+	// list of zero-usage entries. It won't work if there is only one entry in the list, but
+	// it's better than nothing.
+	Dbg_MsgAssert(mpNext==NULL,("CScriptCacheEntry::mpNext not NULL !"));
+	Dbg_MsgAssert(mpPrevious==NULL,("CScriptCacheEntry::mpPrevious not NULL !"));
+	
+	if (mpDecompressedScript)
+	{
+		Mem::Free(mpDecompressedScript);
+	}	
+}	
+
+CScriptCache::CScriptCache()
+{
+	Dbg_MsgAssert(IDEAL_MAX_DECOMPRESSED_SCRIPTS < MAX_DECOMPRESSED_SCRIPTS,("IDEAL_MAX_DECOMPRESSED_SCRIPTS should be less than MAX_DECOMPRESSED_SCRIPTS"));
+	
+	#ifdef __NOPT_ASSERT__
+	mp_logic_task=NULL;
+	m_current_decompress_count_index=0;
+	for (int i=0; i(8);
+	Mem::Manager::sHandle().PopContext();
+	
+	mp_first_zero_usage=NULL;
+	mp_last_zero_usage=NULL;
+	
+	m_delete_zero_usage_straight_away=false;
+}
+
+CScriptCache::~CScriptCache()
+{
+	mp_cache_hash_table->IterateStart();
+	CScriptCacheEntry *p_cache_entry = mp_cache_hash_table->IterateNext();
+	while (p_cache_entry)
+	{
+		// Would it be better to just flush all the items after this loop instead?
+		// (Ie, could flushing here confuse IterateNext ?)
+		mp_cache_hash_table->FlushItem(p_cache_entry->mScriptNameChecksum);
+		
+		// Clear these to prevent ~CScriptCacheEntry from asserting.
+		p_cache_entry->mpNext=NULL;
+		p_cache_entry->mpPrevious=NULL;
+		delete p_cache_entry;
+		
+		p_cache_entry = mp_cache_hash_table->IterateNext();
+	}
+
+	delete mp_cache_hash_table;
+	
+	#ifdef __NOPT_ASSERT__
+	if (mp_logic_task)
+	{
+		delete mp_logic_task;
+	}	
+	#endif	
+}
+
+void CScriptCache::v_start_cb ( void )
+{
+	#ifdef __NOPT_ASSERT__
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	mp_logic_task = new Tsk::Task< CScriptCache > ( CScriptCache::s_logic_code, *this );
+
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	mlp_manager->AddLogicTask( *mp_logic_task );
+	Mem::Manager::sHandle().PopContext();
+	#endif
+}
+
+void CScriptCache::v_stop_cb ( void )
+{
+	#ifdef __NOPT_ASSERT__
+	mp_logic_task->Remove();
+	#endif
+}
+
+void CScriptCache::add_to_zero_usage_list(CScriptCacheEntry *p_entry)
+{
+	Dbg_MsgAssert(p_entry,("NULL p_entry sent to add_to_zero_usage_list"));
+	
+	#ifdef __NOPT_ASSERT__
+	CScriptCacheEntry *p_search=mp_first_zero_usage;
+	while (p_search)
+	{
+		Dbg_MsgAssert(p_search != p_entry,("p_entry is already in the zero-usage list !"));
+		p_search=p_search->mpNext;
+	}	
+	#endif
+	
+	p_entry->mpPrevious=NULL;
+	p_entry->mpNext=mp_first_zero_usage;
+	mp_first_zero_usage=p_entry;
+	if (p_entry->mpNext)
+	{
+		p_entry->mpNext->mpPrevious=p_entry;
+	}
+	if (!mp_last_zero_usage)
+	{
+		mp_last_zero_usage=mp_first_zero_usage;
+	}	
+}
+
+void CScriptCache::remove_from_zero_usage_list(CScriptCacheEntry *p_entry)
+{
+	Dbg_MsgAssert(p_entry,("NULL p_entry sent to remove_from_zero_usage_list"));
+	
+	// Unlink the entry from the list.
+	if (p_entry->mpPrevious)
+	{
+		p_entry->mpPrevious->mpNext=p_entry->mpNext;
+	}
+	if (p_entry->mpNext)
+	{
+		p_entry->mpNext->mpPrevious=p_entry->mpPrevious;
+	}
+		
+	// Update mp_last_zero_usage and mp_first_zero_usage
+	if (p_entry==mp_first_zero_usage)
+	{
+		mp_first_zero_usage=p_entry->mpNext;
+	}
+	if (p_entry==mp_last_zero_usage)
+	{
+		mp_last_zero_usage=p_entry->mpPrevious;
+	}
+	
+	p_entry->mpNext=NULL;
+	p_entry->mpPrevious=NULL;
+}
+
+// This function must be used to delete a CScriptCacheEntry rather than deleting it directly,
+// since this function will update the list of zero-usage entries if necessary.
+void CScriptCache::delete_entry(CScriptCacheEntry *p_entry)
+{
+	Dbg_MsgAssert(p_entry,("NULL p_entry sent to delete_entry"));
+	
+	mp_cache_hash_table->FlushItem(p_entry->mScriptNameChecksum);
+	// Note: If the entry is not in the zero-usage list, this function won't do anything, so safe to call.
+	remove_from_zero_usage_list(p_entry);
+	delete p_entry;
+}
+
+// The scriptName is only passed so that the assert can print the name of the script
+// if no space could be freed up for it.
+void CScriptCache::remove_some_old_scripts(int space_required, uint32 scriptName)
+{
+	while (true)
+	{
+		int largest_malloc_possible=Mem::Manager::sHandle().ScriptHeap()->LargestFreeBlock();
+		largest_malloc_possible-=128; // Actually, 32 seems OK, but have a bigger margin just to be sure.
+		
+		if (space_required <= largest_malloc_possible)
+		{
+			// Hurrah! It's all clear to decompress another script since we know there is enough heap to hold it.
+			
+			// Try to keep the total number of decompressed scripts within IDEAL_MAX_DECOMPRESSED_SCRIPTS
+			// by removing unused ones until either enough have been removed or there are no more to remove.
+			
+			// This way the average memory usage can be controlled by tweaking IDEAL_MAX_DECOMPRESSED_SCRIPTS
+			// whilst spikes in the number of scripts are still permitted up to the limit set by
+			// the pool size of MAX_DECOMPRESSED_SCRIPTS. If that limit is reached, the code will assert.
+			while (CScriptCacheEntry::SGetNumUsedItems() >= IDEAL_MAX_DECOMPRESSED_SCRIPTS && mp_last_zero_usage)
+			{
+				delete_entry(mp_last_zero_usage);
+			}	
+			break;
+		}	
+
+		// Free up some space by deleting the oldest zero-usage decompressed script.
+		
+		if (!mp_last_zero_usage)
+		{
+			// Eeeek! There's nothing left to delete ...
+			Dbg_MsgAssert(0,("Script heap overflow when trying to allocate decompressed script '%s' of size %d bytes!",Script::FindChecksumName(scriptName),space_required));
+		}
+		
+		//printf("%d: Deleting %s from cache, new free space = %d\n",space_required,Script::FindChecksumName(mp_last_zero_usage->mScriptNameChecksum),Mem::Manager::sHandle().ScriptHeap()->LargestFreeBlock()-128);
+		delete_entry(mp_last_zero_usage);
+	}	
+}
+
+uint8 *CScriptCache::GetScript(uint32 scriptName)
+{
+    CSymbolTableEntry *p_entry=Resolve(scriptName);
+    if (p_entry)
+    {
+        Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_QSCRIPT,("Symbol %s is not a QScript",FindChecksumName(scriptName)));
+#ifndef __PLAT_NGC__
+        Dbg_MsgAssert(p_entry->mpScript,("NULL p_entry->mpScript"));
+#endif		// __PLAT_NGC__
+		
+		// Change the passed scriptName to be the actual scriptName so that when decompressed it gets registered in
+		// the cache under the correct name.
+		// Otherwise, if a SpawnScript Foo is done after Foo has been changed using the 'change' command, it
+		// will spawn the old script again, since it will be in the cache under Foo.
+		scriptName=p_entry->mNameChecksum;
+	}	
+
+
+	CScriptCacheEntry *p_cache_entry=mp_cache_hash_table->GetItem(scriptName);
+	if (p_cache_entry)
+	{
+		++p_cache_entry->mUsage;
+		
+		#ifdef __NOPT_ASSERT__
+		if (p_cache_entry->mUsage==1)
+		{
+			++m_num_used_scripts;
+			if (m_num_used_scripts > m_max_used_scripts)
+			{
+				m_max_used_scripts=m_num_used_scripts;
+			}	
+		}	
+		#endif
+		
+		remove_from_zero_usage_list(p_cache_entry);
+		return p_cache_entry->mpDecompressedScript;
+	}	
+		
+    if (p_entry)
+    {
+#ifdef __PLAT_NGC__
+		enum
+		{
+			COMPRESS_BUFFER_SIZE=20000,
+		};	
+		uint8 p_compress_buffer[COMPRESS_BUFFER_SIZE];
+		uint32 header[(SCRIPT_HEADER_SIZE/4)];
+		NsDMA::toMRAM( header, p_entry->mScriptOffset, SCRIPT_HEADER_SIZE );
+		uint32 uncompressed_size		= header[1];
+		uint32 compressed_size			= header[2];
+		NsDMA::toMRAM( p_compress_buffer, p_entry->mScriptOffset + SCRIPT_HEADER_SIZE, compressed_size );
+#else
+		// Note: The script contents checksum is at offset 0, but it is not needed.
+		uint32 uncompressed_size		= *(uint32*)(p_entry->mpScript+4);
+		uint32 compressed_size			= *(uint32*)(p_entry->mpScript+8);
+#endif		// __PLAT_NGC__
+		
+		remove_some_old_scripts(uncompressed_size,scriptName);
+
+		CScriptCacheEntry *p_cache_entry=new CScriptCacheEntry;
+		
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+		uint8 *p_new_script=(uint8*)Mem::Malloc(uncompressed_size);
+		Mem::Manager::sHandle().PopContext();
+		
+		if (uncompressed_size > compressed_size)
+		{
+			#ifdef	__NOPT_ASSERT__		
+			uint8 *p_end=
+			#endif
+#ifdef __PLAT_NGC__
+				DecodeLZSS(p_compress_buffer,p_new_script,compressed_size);
+#else
+				DecodeLZSS(p_entry->mpScript+SCRIPT_HEADER_SIZE,p_new_script,compressed_size);
+#endif		// __PLAT_NGC__
+			Dbg_MsgAssert(p_end==p_new_script+uncompressed_size,("Eh? p_end is not right?"));
+		}
+		else
+		{
+			// The script is uncompressed so just copy it over. Saves, errr, 1K altogether, oh well.
+			Dbg_MsgAssert(uncompressed_size == compressed_size,("Expected uncompressed_size==compressed_size"));
+#ifdef __PLAT_NGC__
+
+			uint8 *p_source=p_compress_buffer;
+#else
+			uint8 *p_source=p_entry->mpScript+SCRIPT_HEADER_SIZE;
+#endif		// __PLAT_NGC__
+			uint8 *p_dest=p_new_script;
+			for (uint32 i=0; impDecompressedScript=p_new_script;
+		p_cache_entry->mUsage=1;
+		p_cache_entry->mScriptNameChecksum=scriptName;
+		
+		#ifdef __NOPT_ASSERT__
+		++m_num_used_scripts;
+		if (m_num_used_scripts > m_max_used_scripts)
+		{
+			m_max_used_scripts=m_num_used_scripts;
+		}	
+		#endif
+		
+		
+		mp_cache_hash_table->PutItem(scriptName,p_cache_entry);
+
+		#ifdef __NOPT_ASSERT__
+		++mp_decompress_counts[m_current_decompress_count_index];
+		#endif
+		
+		return p_cache_entry->mpDecompressedScript;
+	}
+	
+	return NULL;
+}
+
+void CScriptCache::DecrementScriptUsage(uint32 scriptName)
+{
+	CScriptCacheEntry *p_cache_entry=mp_cache_hash_table->GetItem(scriptName);
+	if (p_cache_entry)
+	{
+		Dbg_MsgAssert(p_cache_entry->mUsage,("Zero cache entry usage for script '%s'",Script::FindChecksumName(scriptName)));
+		
+		--p_cache_entry->mUsage;
+		if (p_cache_entry->mUsage==0)
+		{
+			#ifdef __NOPT_ASSERT__
+			--m_num_used_scripts;
+			#endif
+			
+			if (m_delete_zero_usage_straight_away)
+			{
+				// For testing
+				delete_entry(p_cache_entry);
+			}
+			else
+			{
+				add_to_zero_usage_list(p_cache_entry);
+			}	
+		}
+	}
+}
+
+// Needed for when a script is qbr'd.
+void CScriptCache::RefreshAfterReload(uint32 scriptName)
+{
+	CScriptCacheEntry *p_cache_entry=mp_cache_hash_table->GetItem(scriptName);
+	if (p_cache_entry)
+	{
+		// Store the old usage
+		uint32 old_usage=p_cache_entry->mUsage;
+		
+		// Remove from the cache, then add it again so that it is up to date.
+		delete_entry(p_cache_entry);
+		GetScript(scriptName);
+		
+		// Put the usage value back.
+		p_cache_entry=mp_cache_hash_table->GetItem(scriptName);
+		Dbg_MsgAssert(p_cache_entry,("NULL p_cache_entry ?"));
+		p_cache_entry->mUsage=old_usage;
+	}	
+}
+
+void CScriptCache::DeleteZeroUsageStraightAway()
+{
+	m_delete_zero_usage_straight_away=true;
+}
+
+void CScriptCache::DeleteOldestZeroUsageWhenNecessary()
+{
+	m_delete_zero_usage_straight_away=false;
+}
+
+// Scans through the currently decompressed scripts looking to see
+// which one p_token points into, and returns the name of the script, or Unknown
+// if it can;t find it.
+const char *CScriptCache::GetSourceFile(uint8 *p_token)
+{
+	mp_cache_hash_table->IterateStart();
+	CScriptCacheEntry *p_cache_entry = mp_cache_hash_table->IterateNext();
+	while (p_cache_entry)
+	{
+		if (PointsIntoScript(p_token,p_cache_entry->mpDecompressedScript))
+		{
+		    CSymbolTableEntry *p_entry=Resolve(p_cache_entry->mScriptNameChecksum);
+			Dbg_MsgAssert(p_entry,("Eh? Decompressed script has no source symbol table entry ?"));
+			return Script::FindChecksumName(p_entry->mSourceFileNameChecksum);
+		}
+		
+		p_cache_entry = mp_cache_hash_table->IterateNext();		
+	}
+	
+	return "Unknown";
+}
+
+#ifdef __NOPT_ASSERT__
+void CScriptCache::s_logic_code ( const Tsk::Task< CScriptCache >& task )
+{
+	CScriptCache&	mdl = task.GetData();
+	Dbg_AssertType ( &task, Tsk::Task< CScriptCache > );
+
+	++mdl.m_current_decompress_count_index;
+	if (mdl.m_current_decompress_count_index >= MAX_DECOMPRESS_COUNTS)
+	{
+		mdl.m_current_decompress_count_index=0;
+	}	
+	mdl.mp_decompress_counts[mdl.m_current_decompress_count_index]=0;
+}
+
+void CScriptCache::GetDebugInfo( Script::CStruct* p_info )
+{
+	Script::CStruct *p_script_cache_info=new Script::CStruct;
+	
+	p_script_cache_info->AddInteger(CRCD(0xe8081c20,"NumUsedScripts"),m_num_used_scripts);
+	p_script_cache_info->AddInteger(CRCD(0x746c6f5b,"MaxUsedScripts"),m_max_used_scripts);
+	
+	int max=0;
+	int total=0;
+	for (int i=0; i max)
+		{
+			max=mp_decompress_counts[i];
+		}
+		total+=mp_decompress_counts[i];
+	}	
+	p_script_cache_info->AddInteger(CRCD(0xc5eb1eb2,"MaxDecompressCount"),max);
+	p_script_cache_info->AddInteger(CRCD(0xdd84ffd8,"AverageDecompressCount"),total/MAX_DECOMPRESS_COUNTS);
+	
+	mp_cache_hash_table->IterateStart();
+	int num_scripts_in_cache=0;
+	CScriptCacheEntry *p_cache_entry = mp_cache_hash_table->IterateNext();
+	while (p_cache_entry)
+	{
+		++num_scripts_in_cache;
+		p_cache_entry = mp_cache_hash_table->IterateNext();		
+	}
+
+	p_script_cache_info->AddInteger(CRCD(0xb6d1536,"NumScriptsInCache"),num_scripts_in_cache);
+
+	if (num_scripts_in_cache)
+	{
+		Script::CArray *p_scripts_array=new Script::CArray;
+		p_scripts_array->SetSizeAndType(num_scripts_in_cache,ESYMBOLTYPE_STRUCTURE);
+		
+		int alloc_total=0;
+		mp_cache_hash_table->IterateStart();
+		int array_index=0;
+		p_cache_entry = mp_cache_hash_table->IterateNext();
+		while (p_cache_entry)
+		{
+			Script::CStruct *p_cache_entry_info=new Script::CStruct;
+			p_cache_entry_info->AddChecksum(CRCD(0xe37e78c5,"Script"),p_cache_entry->mScriptNameChecksum);
+			p_cache_entry_info->AddInteger(CRCD(0x2f14a18f,"Usage"),p_cache_entry->mUsage);
+			
+			int alloc_size=Mem::GetAllocSize(p_cache_entry->mpDecompressedScript);
+			p_cache_entry_info->AddInteger(CRCD(0x454a3b10,"ScriptSize"),alloc_size);
+			alloc_total+=alloc_size;
+			
+			p_scripts_array->SetStructure(array_index,p_cache_entry_info);
+			p_cache_entry = mp_cache_hash_table->IterateNext();		
+			++array_index;
+		}
+		
+		p_script_cache_info->AddInteger(CRCD(0x43dde78c,"TotalScriptSizes"),alloc_total);
+		p_script_cache_info->AddArrayPointer(CRCD(0xf964b9dd,"CachedScripts"),p_scripts_array);
+	}
+		
+	p_info->AddStructurePointer(CRCD(0x3d2bde05,"ScriptCacheInfo"),p_script_cache_info);
+}
+#endif // #ifdef __NOPT_ASSERT__
+
+#endif // #ifdef NO_SCRIPT_CACHING
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/scriptcache.h b/Code/Gel/Scripting/scriptcache.h
new file mode 100644
index 0000000..847061c
--- /dev/null
+++ b/Code/Gel/Scripting/scriptcache.h
@@ -0,0 +1,139 @@
+#ifndef	__SCRIPTING_SCRIPTCACHE_H
+#define	__SCRIPTING_SCRIPTCACHE_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef __CORE_LIST_HASHTABLE_H
+#include 
+#endif
+
+#ifndef __GEL_MODULE_H
+#include 
+#endif
+
+// If this is defined then GetScript will just return the mpScript as stored in the
+// CSymbolTableEntry.
+#ifdef __PLAT_WN32__
+// No caching when compiled on PC since it is not needed and would require linking in memory manager stuff.
+#define NO_SCRIPT_CACHING
+#endif
+//#define NO_SCRIPT_CACHING
+
+// The total size of the CScriptCacheEntry pool.
+// This is the max number of decompressed scripts that can exist at once.
+// The code will assert if this number of scripts is exceeded.
+// This may be set to be quite large since the CScriptCacheEntry is quite small, 24 bytes each.
+// It needs to be fairly large to allow for spikes in the number of scripts executing (see TT1417)
+#define MAX_DECOMPRESSED_SCRIPTS 300
+
+// The code will attempt to keep the total number of decompressed scripts to no more than this value
+// by deleting unused scripts.
+// This allows us to control the average number of decompressed scripts whilst still allowing spikes
+// up to MAX_DECOMPRESSED_SCRIPTS without the code asserting.
+// Allowing spikes is necessary because sometimes a lot of scripts may exist which don't use up much
+// script heap altogether.
+
+// The larger IDEAL_MAX_DECOMPRESSED_SCRIPTS is, the less often the code will have to
+// decompress scripts during gameplay, hence less cpu time will be used.
+// However, the bigger it is, the more script heap will be used up storing decompressed scripts.
+#define IDEAL_MAX_DECOMPRESSED_SCRIPTS 100
+
+namespace Script
+{
+class CStruct;
+
+#ifdef NO_SCRIPT_CACHING
+#else
+class CScriptCacheEntry : public Mem::CPoolable
+{
+public:
+	CScriptCacheEntry();
+	~CScriptCacheEntry();
+
+	// The script name is stored here so that the make_enough_space function
+	// is able to flush entries from the hash table, given only a CScriptCacheEntry pointer.
+	// (Entries can only be flushed from the hash table if their key is known)
+	uint32 mScriptNameChecksum;
+	
+	uint8 *mpDecompressedScript;
+	uint32 mUsage;
+	
+	CScriptCacheEntry *mpNext;
+	CScriptCacheEntry *mpPrevious;
+};
+#endif // #ifdef NO_SCRIPT_CACHING
+
+#ifdef NO_SCRIPT_CACHING
+class CScriptCache   // No deriving it from Mdl::Module if not usieng it, as that foces uncessary linkages
+#else
+class CScriptCache  : public Mdl::Module
+#endif
+{
+	DeclareSingletonClass( CScriptCache );
+	
+	void v_start_cb ( void );
+	void v_stop_cb ( void );
+
+	#ifdef NO_SCRIPT_CACHING
+	#else
+
+	void add_to_zero_usage_list(CScriptCacheEntry *p_entry);
+	void remove_from_zero_usage_list(CScriptCacheEntry *p_entry);
+	void delete_entry(CScriptCacheEntry *p_entry);
+	void remove_some_old_scripts(int space_required, uint32 scriptName);
+
+	#ifdef __NOPT_ASSERT__
+	static Tsk::Task< CScriptCache >::Code s_logic_code;       
+	Tsk::Task< CScriptCache > *mp_logic_task;
+	
+	enum
+	{
+		MAX_DECOMPRESS_COUNTS=60
+	};	
+	int mp_decompress_counts[MAX_DECOMPRESS_COUNTS];
+	int m_current_decompress_count_index;
+	
+	int m_num_used_scripts; // A count of how many script entries have a usage > 0
+	int m_max_used_scripts; // The max value that the above reached, used for choosing a suitable MAX_DECOMPRESSED_SCRIPTS
+	#endif
+	
+	Lst::HashTable *mp_cache_hash_table;
+	
+	// Points to the first (youngest) of the zero-usage entries.
+	CScriptCacheEntry *mp_first_zero_usage;
+	// Points to the last (oldest) of the zero-usage entries.
+	CScriptCacheEntry *mp_last_zero_usage;
+	
+	bool m_delete_zero_usage_straight_away;
+
+	#endif // #ifdef NO_SCRIPT_CACHING
+public:
+	CScriptCache();
+	virtual	~CScriptCache();
+
+	uint8 *GetScript(uint32 scriptName);
+	void DecrementScriptUsage(uint32 scriptName);
+	void RefreshAfterReload(uint32 scriptName);
+
+	void DeleteZeroUsageStraightAway();
+	void DeleteOldestZeroUsageWhenNecessary();
+
+	#ifdef NO_SCRIPT_CACHING
+	#else
+	const char *GetSourceFile(uint8 *p_token);
+	#endif
+		
+	#ifdef __NOPT_ASSERT__
+	void GetDebugInfo( Script::CStruct* p_info );
+	#endif
+};
+	
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_SCRIPTCACHE_H
diff --git a/Code/Gel/Scripting/scriptdefs.h b/Code/Gel/Scripting/scriptdefs.h
new file mode 100644
index 0000000..ed306ab
--- /dev/null
+++ b/Code/Gel/Scripting/scriptdefs.h
@@ -0,0 +1,32 @@
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#define	__SCRIPTING_SCRIPTDEFS_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Script
+{
+// A checksum of 0, used to represent no-name.    
+#define NO_NAME ((uint32)0)
+
+enum EAssertType
+{
+	NO_ASSERT=0,
+	ASSERT
+};
+
+enum EBoolAssertIfDuplicateSymbols
+{
+	NO_ASSERT_IF_DUPLICATE_SYMBOLS=0,
+	ASSERT_IF_DUPLICATE_SYMBOLS
+};
+	
+// So that the old code still compiles without having to change a million things.
+#define CScriptStructure CStruct
+#define SPair CPair
+#define NONAME NO_NAME
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_SCRIPTDEFS_H
diff --git a/Code/Gel/Scripting/skiptoken.cpp b/Code/Gel/Scripting/skiptoken.cpp
new file mode 100644
index 0000000..046e948
--- /dev/null
+++ b/Code/Gel/Scripting/skiptoken.cpp
@@ -0,0 +1,134 @@
+// Definition of the SkipToken function.
+// This can be #included in the game code, and in PC utilities.
+// It is in this file so that only this file needs to be updated when SkipToken
+// needs to be modified.
+
+// If included in PC code, then uint8 and Dbg_MsgAssert will need to be defined.
+
+								   
+// Returns a pointer to the next token after p_token.
+// It won't necessarily skip over the complete format of the data that is expected
+// to follow, it will just skip over enough that it returns a pointer to a token again.
+// For example, ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE must be followed by a ESCRIPTTOKEN_PAIR,
+// but SkipToken will not check for that and skip over the pair token too, it will just skip over the
+// RANDOM_RANGE token and return a pointer to the ESCRIPTTOKEN_PAIR
+// So if SkipToken is passed a pointer to a token, it is guaranteed to return a pointer to token, namely
+// the nearest next one.
+uint8 *SkipToken(uint8 *p_token)
+{
+    switch (*p_token)
+    {
+        case ESCRIPTTOKEN_ENDOFFILE:
+            Dbg_MsgAssert(0,("Tried to skip past EndOfFile token"));
+            break;
+	    case ESCRIPTTOKEN_ENDOFLINE:
+        case ESCRIPTTOKEN_EQUALS:
+        case ESCRIPTTOKEN_DOT:
+        case ESCRIPTTOKEN_COMMA:
+        case ESCRIPTTOKEN_MINUS:
+        case ESCRIPTTOKEN_ADD:
+        case ESCRIPTTOKEN_DIVIDE:
+        case ESCRIPTTOKEN_MULTIPLY:
+        case ESCRIPTTOKEN_OPENPARENTH:
+        case ESCRIPTTOKEN_CLOSEPARENTH:
+        case ESCRIPTTOKEN_SAMEAS:
+        case ESCRIPTTOKEN_LESSTHAN:
+        case ESCRIPTTOKEN_LESSTHANEQUAL:
+        case ESCRIPTTOKEN_GREATERTHAN:
+        case ESCRIPTTOKEN_GREATERTHANEQUAL:
+        case ESCRIPTTOKEN_STARTSTRUCT:
+        case ESCRIPTTOKEN_STARTARRAY:
+        case ESCRIPTTOKEN_ENDSTRUCT:
+        case ESCRIPTTOKEN_ENDARRAY:
+        case ESCRIPTTOKEN_KEYWORD_BEGIN:
+        case ESCRIPTTOKEN_KEYWORD_REPEAT:
+        case ESCRIPTTOKEN_KEYWORD_BREAK:
+        case ESCRIPTTOKEN_KEYWORD_SCRIPT:
+        case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
+        case ESCRIPTTOKEN_KEYWORD_IF:
+        case ESCRIPTTOKEN_KEYWORD_ELSE:
+        case ESCRIPTTOKEN_KEYWORD_ELSEIF:
+        case ESCRIPTTOKEN_KEYWORD_ENDIF:
+        case ESCRIPTTOKEN_KEYWORD_RETURN:
+		case ESCRIPTTOKEN_KEYWORD_ALLARGS:
+		case ESCRIPTTOKEN_ARG:
+		case ESCRIPTTOKEN_OR:
+		case ESCRIPTTOKEN_AND:
+		case ESCRIPTTOKEN_XOR:
+		case ESCRIPTTOKEN_SHIFT_LEFT:
+		case ESCRIPTTOKEN_SHIFT_RIGHT:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2:
+		case ESCRIPTTOKEN_KEYWORD_NOT:
+		case ESCRIPTTOKEN_KEYWORD_AND:
+		case ESCRIPTTOKEN_KEYWORD_OR:
+		case ESCRIPTTOKEN_KEYWORD_SWITCH:
+		case ESCRIPTTOKEN_KEYWORD_ENDSWITCH:
+		case ESCRIPTTOKEN_KEYWORD_CASE:
+		case ESCRIPTTOKEN_KEYWORD_DEFAULT:
+		case ESCRIPTTOKEN_COLON:
+            ++p_token;
+			break;
+            
+        case ESCRIPTTOKEN_NAME:
+        case ESCRIPTTOKEN_INTEGER:
+        case ESCRIPTTOKEN_HEXINTEGER:
+        case ESCRIPTTOKEN_FLOAT:
+	    case ESCRIPTTOKEN_ENDOFLINENUMBER:
+		case ESCRIPTTOKEN_JUMP:
+		case ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION:
+		case ESCRIPTTOKEN_RUNTIME_CFUNCTION:
+			p_token+=5;
+            break;
+        case ESCRIPTTOKEN_VECTOR:
+            p_token+=13;
+            break;
+        case ESCRIPTTOKEN_PAIR:
+            p_token+=9;
+            break;
+	    case ESCRIPTTOKEN_STRING:
+        case ESCRIPTTOKEN_LOCALSTRING:
+		{
+            ++p_token;
+			uint32 num_bytes=*p_token++;
+			num_bytes+=(*p_token++)<<8;
+			num_bytes+=(*p_token++)<<16;
+			num_bytes+=(*p_token++)<<24;
+            p_token+=num_bytes;
+            break;
+		}	
+		case ESCRIPTTOKEN_CHECKSUM_NAME: 
+			// Skip over the token and checksum.
+			p_token+=5;
+			// Skip over the string.
+			while (*p_token)
+			{
+				++p_token;
+			}
+			// Skip over the terminator.
+			++p_token;	
+			break;			
+		case ESCRIPTTOKEN_KEYWORD_RANDOM:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM2:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT:
+		case ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE:
+		{
+            ++p_token;
+			
+			uint32 num_jumps=*p_token++;
+			num_jumps+=(*p_token++)<<8;
+			num_jumps+=(*p_token++)<<16;
+			num_jumps+=(*p_token++)<<24;
+			
+			// Skip over all the weight & jump offsets.
+			p_token+=2*num_jumps+4*num_jumps;
+			break;
+		}
+			
+        default:
+            Dbg_MsgAssert(0,("Unrecognized script token sent to SkipToken()"));
+            break;
+    }
+    return p_token;
+}
+
diff --git a/Code/Gel/Scripting/string.cpp b/Code/Gel/Scripting/string.cpp
new file mode 100644
index 0000000..f493145
--- /dev/null
+++ b/Code/Gel/Scripting/string.cpp
@@ -0,0 +1,250 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// string.cpp		KSH 17 Oct 2001
+//
+// Code for managing allocation and deallocation of strings within the script system.
+// For example it manages the 'permanent string' heap, which is for strings that get created
+// when all the qb's are loaded and never get deleted. They can therefore all be squashed
+// together for optimum use of memory.
+//
+// Note: Not defining a CString class, because it would have to have a char pointer so
+// would just be an unneccessary layer of indirection that would waste space.
+//
+// Instead CreateString and DeleteString are used to allocate and deallocate strings.
+//
+// Could add cleverer memory management here later, eg a pool of fixed sized strings
+// for strings that need to be alloc'd and dealloc'd fast.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+// Note: Small memory saving possible by deleting the sp_permanent_string_checksums array
+// once you know that no more permanent strings will need to be allocated. Will save 32K ish,
+// depending on maxStrings.
+// (There's no function here yet for doing that, but easy to add)
+
+#include 
+#ifndef __CORE_CRC_H
+#include 
+#endif
+
+namespace Script
+{
+
+static char *sAddToSpecialStringHeap(const char *p_string);
+
+static bool s_use_permanent_string_heap=false;
+
+// The permanent string buffer.
+static char *sp_permanent_string_heap=NULL;
+static char *sp_permanent_string_heap_top=NULL;
+static uint32 s_permanent_string_heap_size=0;
+
+// Array of string checksums to allow quick checking to see if a particular string
+// is already in the permanent string buffer without having to do string comparisons.
+// Needed because when loading several thousand strings, several million string comparisons
+// would have to be done, causing a significant pause in loading the game.
+struct SSpecialStringChecksum
+{
+	uint32 mChecksum;
+	char *mpString;
+};
+static SSpecialStringChecksum *sp_permanent_string_checksums=NULL;
+static uint32 s_num_permanent_string_checksums=0;
+static uint32 s_max_permanent_string_checksums=0;
+
+// Needs to be called once at the start of the game.
+// For THPS3, maxSize was 48000 for English, 60500 for other languages. maxStrings was 4000
+void AllocatePermanentStringHeap(uint32 maxSize, uint32 maxStrings)
+{
+	// Uses whatever the current heap is set to, which is set to the script heap in Script::AllocatePools
+	// which is where this function is called from.
+	s_permanent_string_heap_size=maxSize;
+	s_max_permanent_string_checksums=maxStrings;
+	
+	// Allocate the string buffer.
+	Dbg_MsgAssert(sp_permanent_string_heap==NULL,("sp_permanent_string_heap not NULL ???"));
+	sp_permanent_string_heap=(char*)Mem::Malloc(s_permanent_string_heap_size);
+	sp_permanent_string_heap_top=sp_permanent_string_heap;
+	
+	// Allocate the array of checksums.
+	Dbg_MsgAssert(sp_permanent_string_checksums==NULL,("sp_permanent_string_checksums not NULL ???"));
+	sp_permanent_string_checksums=(SSpecialStringChecksum*)Mem::Malloc(s_max_permanent_string_checksums*sizeof(SSpecialStringChecksum));
+	s_num_permanent_string_checksums=0;
+}
+
+void DeallocatePermanentStringHeap()
+{
+	// Deallocate the string buffer.
+	Dbg_MsgAssert(sp_permanent_string_heap,("NULL sp_permanent_string_heap ?"));
+	Mem::Free(sp_permanent_string_heap);
+	sp_permanent_string_heap=NULL;
+	sp_permanent_string_heap_top=NULL;
+	s_permanent_string_heap_size=0;
+	
+	// Deallocate the array of checksums.
+	// MEMOPT: TODO: Destroy this when UseRegularStringHeap is called too, to free up memory ?
+	Dbg_MsgAssert(sp_permanent_string_checksums,("NULL sp_permanent_string_checksums ?"));
+	Mem::Free(sp_permanent_string_checksums);
+	sp_permanent_string_checksums=NULL;
+	s_num_permanent_string_checksums=0;
+	s_max_permanent_string_checksums=0;
+}
+
+// Adds the contents of the passed pString to the special string heap, and returns
+// the pointer to the string within the heap.
+// If the string is already found in the heap, it won't add it again.
+static char *sAddToSpecialStringHeap(const char *p_string)
+{
+	Dbg_MsgAssert(p_string,("NULL p_string"));
+
+	// Check to see if it already exists in the special string heap ...
+
+	// Calculate the checksum of the string, then search for this in the array of
+	// checksums of all the special strings so far.
+	// Quicker than doing byte comparisons, probably due to the long time it takes to
+	// read single bytes from memory.
+	// Needs to be fast, otherwise it can add a significant delay to the startup time.
+	// This method adds about 0.3 seconds altogether (in THPS3, couple of thousand strings)
+	uint32 len=strlen(p_string);
+	uint32 checksum=Crc::GenerateCRCCaseSensitive(p_string,len);
+	
+	Dbg_MsgAssert(sp_permanent_string_checksums,("NULL sp_permanent_string_checksums ??"));
+	SSpecialStringChecksum *p_ch=sp_permanent_string_checksums;
+	for (uint32 i=0; imChecksum==checksum)
+		{
+			return p_ch->mpString;
+		}
+		++p_ch;
+	}		
+
+	char *p_heap_string=NULL;
+	
+	// Not found, so add to the pile of strings.
+	Dbg_MsgAssert(sp_permanent_string_heap,("NULL sp_permanent_string_heap ??"));
+	
+	Dbg_MsgAssert(s_permanent_string_heap_size-(sp_permanent_string_heap_top-sp_permanent_string_heap)>=len+1,("Out of special string heap"));
+	p_heap_string=sp_permanent_string_heap_top;
+	strcpy(p_heap_string,p_string);
+	// Update sp_permanent_string_heap_top
+	sp_permanent_string_heap_top+=len;
+	++sp_permanent_string_heap_top; // Skip over the terminator
+
+	// Store the checksum too.
+	Dbg_MsgAssert(s_num_permanent_string_checksums=sp_permanent_string_heap && p_string
+#endif
+
+namespace Script
+{
+
+void AllocatePermanentStringHeap(uint32 maxSize, uint32 maxStrings);
+void DeallocatePermanentStringHeap();
+
+void UsePermanentStringHeap();
+void UseRegularStringHeap();
+	
+char *CreateString(const char *p_string);
+void DeleteString(char *p_string);
+
+void SetScriptString(uint32 n, const char *p_string);
+char* GetScriptString(uint32 n);
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_STRING_H
diff --git a/Code/Gel/Scripting/struct.cpp b/Code/Gel/Scripting/struct.cpp
new file mode 100644
index 0000000..9cb74c0
--- /dev/null
+++ b/Code/Gel/Scripting/struct.cpp
@@ -0,0 +1,2080 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// struct.cpp		KSH 22 Oct 2001
+//
+// CStruct class member functions.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+							 
+//#define	__DEBUG_STRUCT_ALLOCS
+							 
+// This does technically cause a cyclic dependency, but what the heck, it's only needed
+// for some debugging. (Making the Get function print the contents of the struct in
+// the event of not finding the required parameter)
+#include 
+
+DefinePoolableClass(Script::CStruct);
+
+namespace Script
+{
+
+#ifdef	__DEBUG_STRUCT_ALLOCS
+#define	MAX_LAST	200
+static int head = 0;
+static bool init_last = true;
+static CStruct * last[MAX_LAST];
+#endif
+
+void DumpLastStructs()
+{
+#ifdef	__DEBUG_STRUCT_ALLOCS
+	for (int i=0;imType)
+	{
+		case ESYMBOLTYPE_STRING:
+		case ESYMBOLTYPE_LOCALSTRING:
+			if (p_comp->mpString)
+			{
+				DeleteString(p_comp->mpString);
+			}
+			break;
+		case ESYMBOLTYPE_PAIR:
+			if (p_comp->mpPair)
+			{
+				delete p_comp->mpPair;
+			}
+			break;
+		case ESYMBOLTYPE_VECTOR:
+			if (p_comp->mpVector)
+			{
+				delete p_comp->mpVector;
+			}
+			break;
+		case ESYMBOLTYPE_STRUCTURE:
+			if (p_comp->mpStructure)
+			{
+				delete p_comp->mpStructure;
+			}
+			break;
+		case ESYMBOLTYPE_ARRAY:
+			if (p_comp->mpArray)
+			{
+				CleanUpArray(p_comp->mpArray);
+				delete p_comp->mpArray;
+			}
+			break;
+		case ESYMBOLTYPE_QSCRIPT:
+            if (p_comp->mpScript)
+			{
+				Mem::Free(p_comp->mpScript);
+			}
+			p_comp->mScriptSize=0;
+			break;	
+		default:
+			break;
+	}	
+	// This will zero the union, which includes all the above pointers.
+	p_comp->mUnion=0;
+	
+	p_comp->mType=ESYMBOLTYPE_NONE;
+}
+
+// Copies the contents of p_source into p_dest, but without copying any pointers over.
+// Eg, if p_source contains an array, a new array will be created for p_dest.
+void CopyComponent(CComponent *p_dest, const CComponent *p_source)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+	Dbg_MsgAssert(p_source,("NULL p_source"));
+
+	// Make sure p_dest is cleaned up first.	
+	if (p_dest->mUnion) // This 'if' is just a small speed optimization to avoid an unnecessay call
+	{
+		CleanUpComponent(p_dest);
+	}	
+	
+	p_dest->mType=p_source->mType;
+	p_dest->mNameChecksum=p_source->mNameChecksum;
+	
+	switch (p_source->mType)
+	{
+	case ESYMBOLTYPE_NONE:
+		break;
+		
+    case ESYMBOLTYPE_INTEGER:
+    case ESYMBOLTYPE_FLOAT:
+	case ESYMBOLTYPE_NAME:
+		p_dest->mUnion=p_source->mUnion;
+		break;
+		
+    case ESYMBOLTYPE_STRING:
+		Dbg_MsgAssert(p_source->mpString,("NULL p_source->mpString ?"));
+		p_dest->mpString=CreateString(p_source->mpString);
+		break;
+		
+    case ESYMBOLTYPE_LOCALSTRING:
+		Dbg_MsgAssert(p_source->mpLocalString,("NULL p_source->mpLocalString ?"));
+		p_dest->mpLocalString=CreateString(p_source->mpLocalString);
+		break;
+		
+    case ESYMBOLTYPE_PAIR:
+		Dbg_MsgAssert(p_source->mpPair,("NULL p_source->mpPair ?"));
+		p_dest->mpPair=new CPair;
+		p_dest->mpPair->mX=p_source->mpPair->mX;
+		p_dest->mpPair->mY=p_source->mpPair->mY;
+		break;
+		
+    case ESYMBOLTYPE_VECTOR:
+		Dbg_MsgAssert(p_source->mpVector,("NULL p_source->mpVector ?"));
+		p_dest->mpVector=new CVector;
+		p_dest->mpVector->mX=p_source->mpVector->mX;
+		p_dest->mpVector->mY=p_source->mpVector->mY;
+		p_dest->mpVector->mZ=p_source->mpVector->mZ;
+		break;
+		
+    case ESYMBOLTYPE_STRUCTURE:
+		Dbg_MsgAssert(p_source->mpStructure,("NULL p_source->mpStructure ?"));
+		p_dest->mpStructure=new CStruct;
+		*p_dest->mpStructure=*p_source->mpStructure;
+		break;
+		
+    case ESYMBOLTYPE_ARRAY:
+		Dbg_MsgAssert(p_source->mpArray,("NULL p_source->mpArray ?"));
+		p_dest->mpArray=new CArray;
+		CopyArray(p_dest->mpArray,p_source->mpArray);
+		break;
+
+	case ESYMBOLTYPE_QSCRIPT:
+	{
+		Dbg_MsgAssert(p_source->mpScript,("NULL p_source->mpScript ?"));
+		
+		// Allocate a buffer off the script heap
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+		Dbg_MsgAssert(p_source->mScriptSize,("Zero source script size"));
+		p_dest->mpScript=(uint8*)Mem::Malloc(p_source->mScriptSize);
+		p_dest->mScriptSize=p_source->mScriptSize;
+		Mem::Manager::sHandle().PopContext();
+		
+		// Copy the script into the new buffer.
+		const uint8 *p_from=p_source->mpScript;
+		uint8 *p_to=p_dest->mpScript;
+		for (uint32 i=0; imScriptSize; ++i)
+		{
+			*p_to++=*p_from++;
+		}
+		
+		break;
+	}
+		
+	default:
+		Dbg_MsgAssert(0,("Bad p_source->mType of '%s'",GetTypeName(p_source->mType)));
+		break;
+	}
+}
+
+// This will run through all the elements of p_array deleting any entities pointed to by it.
+// It will also call the array's Clear function to delete the array buffer, so it is not
+// necessary to call Clear again after calling this.
+void CleanUpArray(CArray *p_array)
+{
+	Dbg_MsgAssert(p_array,("NULL p_array"));
+	
+	ESymbolType type=p_array->GetType();
+	uint32 size=p_array->GetSize();
+	uint32 *p_array_data=p_array->GetArrayPointer();
+	if (size)
+	{
+		Dbg_MsgAssert(p_array_data,("NULL p_array_data ?"));
+	}	
+	
+	switch (type)
+	{
+		case ESYMBOLTYPE_NONE:
+		case ESYMBOLTYPE_INTEGER:
+		case ESYMBOLTYPE_FLOAT:
+		case ESYMBOLTYPE_NAME:
+			// Nothing to delete
+			break;
+			
+		case ESYMBOLTYPE_STRING:
+		case ESYMBOLTYPE_LOCALSTRING:
+		{
+			char **pp_string=(char**)p_array_data;
+			for (uint32 i=0; iClear();
+}
+
+// Copies the contents of p_source into p_dest. No pointers will be copied over, new entities will be
+// created for p_dest. So p_source can safely be deleted afterwards.
+void CopyArray(CArray *p_dest, const CArray *p_source)
+{
+	Dbg_MsgAssert(p_source,("NULL p_source ?"));
+	Dbg_MsgAssert(p_dest,("NULL p_dest ?"));
+	
+	// Make sure that p_dest is cleaned up first.
+	CleanUpArray(p_dest);
+		
+	ESymbolType type=p_source->GetType();
+	uint32 size=p_source->GetSize();
+	
+	p_dest->SetSizeAndType(size,type);
+
+	if (size==0)
+	{
+		// Finished.
+		return;
+	}
+		
+	uint32 *p_source_array=p_source->GetArrayPointer();
+	Dbg_MsgAssert(p_source_array,("NULL p_source_array ?"));
+	
+	uint32 *p_dest_array=p_dest->GetArrayPointer();
+	Dbg_MsgAssert(p_dest_array,("NULL p_dest_array ?"));
+			
+	switch (type)
+	{
+		case ESYMBOLTYPE_INTEGER:
+		case ESYMBOLTYPE_FLOAT:
+		case ESYMBOLTYPE_NAME:
+		{
+			uint32 *p_source_word=p_source_array;
+			uint32 *p_dest_word=p_dest_array;
+			for (uint32 i=0; imax)
+	{
+		max = CStruct::SGetNumUsedItems();
+		DumpUnwindStack(20,0);
+	}
+	#endif
+}
+
+// Copy constructor.
+CStruct::CStruct( const CStruct& rhs )
+{
+	// Initialise everything. CStruct is not derived from CClass so we don't get
+	// the auro-zeroing.
+	init();
+
+	// use the overridden assignment operator
+	*this = rhs;
+	
+	x++;
+	if (x>max)
+	{
+		max = x;
+//		DumpUnwindStack(20,0);
+	}
+}
+
+// Assignement operator.
+CStruct& CStruct::operator=( const CStruct& rhs )
+{
+	// don't try to assign to yourself
+	if ( &rhs == this )
+	{
+		return *this;
+	}
+
+	this->Clear();
+	*this+=rhs;
+
+	return *this;
+}
+
+// This will merge the contents of the rhs into this structure.
+// Functionally the same as the old AppendStructure function, except AppendStructure would accept a NULL pointer.
+CStruct& CStruct::operator+=( const CStruct& rhs )
+{
+	CComponent *p_source_component=rhs.mp_components;
+	while (p_source_component)
+	{
+		CComponent *p_new=new CComponent;
+		CopyComponent(p_new, p_source_component);
+		AddComponent(p_new);
+		p_source_component=p_source_component->mpNext;
+	}	
+	return *this;
+}
+
+// TODO: Remove at some point. Provided for back-compatibility.
+void CStruct::AppendStructure(const CStruct *p_struct)
+{
+	if (p_struct)
+	{
+		*this+=*p_struct;
+	}	
+}
+
+// Written specially for use by CScriptDebugger::transmit_cscript_list.
+// This will append the passed component to the structure's list of components
+// and will leave the existing list untouched, ie it will not remove any of the same name.
+// This is so that the script debugger code can store a list of CScript instances into a structure
+// even though many may have the same name.
+void CStruct::AppendComponentPointer(CComponent *p_comp)
+{
+	CComponent *p_last=NULL;
+	CComponent *p_scan=mp_components;
+	
+	// Find p_last, the last component in the list.
+	while (p_scan)
+	{
+		p_last=p_scan;
+		p_scan=p_scan->mpNext;
+	}		
+	
+	// Tag p_comp onto the end of the list.
+	if (p_last)
+	{
+		p_last->mpNext=p_comp;
+	}
+	else
+	{
+		mp_components=p_comp;
+	}	
+	p_comp->mpNext=NULL;
+}
+
+CStruct::~CStruct()
+{
+
+#ifdef	__DEBUG_STRUCT_ALLOCS
+	for (int i=0;impNext;
+		// Note: The CComponent destructor cannot clean up, because that would cause cyclic dependencies.
+		CleanUpComponent(p_comp);
+		delete p_comp;
+		p_comp=p_next;
+	}
+	mp_components=NULL;
+}
+
+void CStruct::RemoveComponent(uint32 nameChecksum)
+{
+	CComponent *p_last=NULL;
+	CComponent *p_comp=mp_components;
+	while (p_comp)
+	{
+		CComponent *p_next=p_comp->mpNext;
+		
+		if (p_comp->mNameChecksum==nameChecksum)
+		{
+			// p_comp must be removed.
+			
+			// Unlink it.
+			if (p_last)
+			{
+				p_last->mpNext=p_next;
+			}
+			else	
+			{
+				Dbg_MsgAssert(p_comp==mp_components,("Eh ? p_comp!=mp_components ??"));
+				mp_components=p_next;
+			}
+			
+			// Note: The CComponent destructor cannot clean up, because that would cause cyclic dependencies.
+			CleanUpComponent(p_comp);
+			delete p_comp;
+			
+			// Carries on, in case there is more than one component with the given name.
+			p_comp=p_next;
+		}	
+		else
+		{
+			p_last=p_comp;
+			p_comp=p_next;
+		}	
+	}	
+}
+
+void CStruct::RemoveComponent(const char *p_name)
+{
+	RemoveComponent(Crc::GenerateCRCFromString(p_name));
+}
+
+// Same as RemoveComponent except the type must matched the passed type too.
+// Used by eval.cpp when subtracting a structure from another structure.
+void CStruct::RemoveComponentWithType(uint32 nameChecksum, uint8 type)
+{
+	CComponent *p_last=NULL;
+	CComponent *p_comp=mp_components;
+	while (p_comp)
+	{
+		CComponent *p_next=p_comp->mpNext;
+		
+		if (p_comp->mNameChecksum==nameChecksum && p_comp->mType==type)
+		{
+			// p_comp must be removed.
+			
+			// Unlink it.
+			if (p_last)
+			{
+				p_last->mpNext=p_next;
+			}
+			else	
+			{
+				Dbg_MsgAssert(p_comp==mp_components,("Eh ? p_comp!=mp_components ??"));
+				mp_components=p_next;
+			}
+			
+			// Note: The CComponent destructor cannot clean up, because that would cause cyclic dependencies.
+			CleanUpComponent(p_comp);
+			delete p_comp;
+			
+			// Carries on, in case there is more than one component with the given name.
+			p_comp=p_next;
+		}	
+		else
+		{
+			p_last=p_comp;
+			p_comp=p_next;
+		}	
+	}	
+}
+
+void CStruct::RemoveComponentWithType(const char *p_name, uint8 type)
+{
+	RemoveComponentWithType(Crc::GenerateCRCFromString(p_name),type);
+}
+
+// Note: Logical inconsistencies that could be an issue later maybe:
+// If the unnamed-name resolves to a global structure, should the component still be removed?
+// If there is a referenced global structure, should it have any of the specified flag removed
+// too, and so on recursively? (probably not, since global structures should remain constant)
+void CStruct::RemoveFlag(uint32 checksum)
+{
+	CComponent *p_last=NULL;
+	CComponent *p_comp=mp_components;
+	while (p_comp)
+	{
+		CComponent *p_next=p_comp->mpNext;
+		
+		if (p_comp->mNameChecksum==0 && p_comp->mChecksum==checksum)
+		{
+			// p_comp must be removed.
+			
+			// Unlink it.
+			if (p_last)
+			{
+				p_last->mpNext=p_next;
+			}
+			else	
+			{
+				Dbg_MsgAssert(p_comp==mp_components,("Eh ? p_comp!=mp_components ??"));
+				mp_components=p_next;
+			}
+			
+			// Note: The CComponent destructor cannot clean up, because that would cause cyclic dependencies.
+			CleanUpComponent(p_comp);
+			// Note: All the CleanUpComponent call will have done will be to zero the union,
+			// cos a flag component does not contain any pointers that need deleting.
+			// So I could have just set p_comp->mChecksum to 0 instead. Just calling
+			// CleanUpComponent for consistency.
+			delete p_comp;
+			
+			// Carries on, in case there is more than one flag with the given name.
+			// There shouldn't be, but check anyway.
+			p_comp=p_next;
+		}	
+		else
+		{
+			p_last=p_comp;
+			p_comp=p_next;
+		}	
+	}	
+}
+
+void CStruct::RemoveFlag(const char *p_name)
+{
+	RemoveFlag(Crc::GenerateCRCFromString(p_name));
+}
+
+// Returns true if the structure contains no components.
+bool CStruct::IsEmpty() const
+{
+	return mp_components==NULL;
+}
+
+// Searches for a component with the given name, but will also recurse into substructures.
+// Used when resolving the  syntax.
+CComponent *CStruct::FindNamedComponentRecurse(uint32 nameChecksum) const
+{
+	CComponent *p_found=NULL;
+	
+    CComponent *p_comp=mp_components;
+    while (p_comp)
+    {
+        if (p_comp->mNameChecksum==nameChecksum) 
+		{
+			p_found=p_comp;
+		}	
+		else if (p_comp->mNameChecksum==0 && p_comp->mType==ESYMBOLTYPE_NAME)
+		{
+            CSymbolTableEntry *p_entry=Resolve(p_comp->mChecksum);
+            if (p_entry && p_entry->mType==ESYMBOLTYPE_STRUCTURE)
+            {
+                Dbg_MsgAssert(p_entry->mpStructure,("NULL p_entry->mpStructure"));
+                CComponent *p_new_found=p_entry->mpStructure->FindNamedComponentRecurse(nameChecksum);
+				if (p_new_found)
+				{
+					p_found=p_new_found;
+				}	
+            }
+		}
+        p_comp=p_comp->mpNext;
+    }
+	
+    return p_found;
+}
+
+// If passed NULL this will return the first (leftmost) component.
+// If passed non-NULL it return the next component (to the right) 
+// Returns NULL if the passed component is the last component.
+CComponent *CStruct::GetNextComponent(CComponent *p_comp) const
+{
+	if (p_comp==NULL)
+	{
+		return mp_components;
+	}
+	
+	return p_comp->mpNext;	
+}
+
+// This will copy the contents of this structure into p_dest, but in such a way that
+// p_dest will contain no unnamed-structure references, they will all get expanded.
+// This was added because sometimes one wants to step through each of the components of
+// a structure using GetNextComponent, but GetNextComponent itself cannot recurse into
+// unnamed structures, because when it reaches the end of one it won't know how to get back
+// to the parent.
+// So instead just create a new structure, copy the other structure into it using ExpandInto,
+// then step through that using GetNextComponent, then delete the new structure once finished.
+// It's a bit memory intensive, but saves having to write special code to resolve unnamed
+// structures & recursing whenever one wants to scan through the components.
+// Could be handy for other things later too.
+// recursionCount is just to catch infinite recursion, so no need to pass it a value. (defaults to 0)
+void CStruct::ExpandInto(CStruct *p_dest, int recursionCount) const
+{
+	Dbg_MsgAssert(recursionCount<=20,("Possible infinite recursion of CStruct::ExpandInto! More than 20 recursions have occurred."));
+	Dbg_MsgAssert(p_dest,("NULL p_dest sent to ExpandInto"));
+	
+	CComponent *p_comp=mp_components;
+	while (p_comp)
+	{
+		bool added_structure=false;
+		
+		if (p_comp->mNameChecksum==0)
+		{
+			Dbg_MsgAssert(p_comp->mType!=ESYMBOLTYPE_STRUCTURE,("Unexpected unnamed structure in CStruct"));
+			
+			if (p_comp->mType==ESYMBOLTYPE_NAME)
+			{
+				CSymbolTableEntry *p_global=Resolve(p_comp->mChecksum);
+				if (p_global && p_global->mType==ESYMBOLTYPE_STRUCTURE)
+				{
+					Dbg_MsgAssert(p_global->mpStructure,("NULL p_global->mpStructure"));
+					p_global->mpStructure->ExpandInto(p_dest,recursionCount+1);
+					added_structure=true;
+				}	
+			}	
+		}
+		
+		if (!added_structure)
+		{
+			CComponent *p_new_component=new CComponent;
+			CopyComponent(p_new_component,p_comp);
+			p_dest->AddComponent(p_new_component);
+		}
+		
+		p_comp=p_comp->mpNext;
+	}
+}
+
+// Adds the component p_comp to the end of the list of components, removing any existing components
+// that have the same name and type, and asserting if any components exist with the same name but different
+// type.
+// However, if p_comp is a flag (an un-named component of type ESYMBOLTYPE_NAME) then it will always add it,
+// because otherwise we would not be able to have more than one flag in a structure.
+//
+// Note: Even two identical flags are allowed, because sometimes this is handy. For example I use a list
+// of words in a structure as a way for Scott to represent trick button sequences (see airtricks.q) and 
+// sometimes two of the words might need to be identical.
+//
+// Another note: This function needs to be pretty fast because it will need to be called many times for
+// each line of script executed. Optimize later.
+//
+void CStruct::AddComponent(CComponent *p_comp)
+{
+	Dbg_MsgAssert(p_comp,("NULL p_comp"));
+
+	// Unnamed structure components are not allowed, because to support them would require 
+	// modifying the search_for function. No need, since when structures are created from lists
+	// of tokens any substructures get expanded and added component by component at that stage.
+	// So in theory no components of type structure should get added.
+	Dbg_MsgAssert(!(p_comp->mNameChecksum==0 && p_comp->mType==ESYMBOLTYPE_STRUCTURE),("Tried to add an un-named structure component ..."));
+
+	Dbg_MsgAssert(p_comp->mType!=ESYMBOLTYPE_NONE,("Tried to add a structure component with no type, name='%s' ...",FindChecksumName(p_comp->mNameChecksum)));
+	
+	CComponent *p_last=NULL;
+	CComponent *p_scan=mp_components;
+    bool remove=false;
+	
+	while (p_scan)
+	{
+		if (p_scan->mNameChecksum==p_comp->mNameChecksum)
+		{
+			// Same name ...
+		 
+		    remove=false;
+			
+			if (p_scan->mType==p_comp->mType)
+			{
+				if (p_scan->mType==ESYMBOLTYPE_NAME && 
+					p_scan->mNameChecksum==0 && 
+					p_scan->mChecksum!=p_comp->mChecksum)
+				{
+					// Allow multiple un-named checksums if their values are different, because
+					// these are flags.
+				}
+				else
+				{
+					remove=true;
+				}
+			}
+			else
+			{
+				// Consider floats and ints to be the same type, so that x=3.1 overrides x=3 and vice versa				
+				if ((p_scan->mType==ESYMBOLTYPE_FLOAT && p_comp->mType==ESYMBOLTYPE_INTEGER) ||
+					(p_scan->mType==ESYMBOLTYPE_INTEGER && p_comp->mType==ESYMBOLTYPE_FLOAT))
+				{
+					remove=true;
+				}
+			}
+			
+			if (remove)		
+			{
+				// Remove this component.
+				if (p_last)
+				{
+					p_last->mpNext=p_scan->mpNext;
+					CleanUpComponent(p_scan);
+					delete p_scan;
+					p_scan=p_last->mpNext;
+				}	
+				else
+				{
+					mp_components=p_scan->mpNext;
+					CleanUpComponent(p_scan);
+					delete p_scan;
+					p_scan=mp_components;
+				}	
+			}
+			else
+			{
+				// Same name but different type.
+				// The old code would assert in this case, but the new code allows it.
+				// This is because there is no problem if a structure contains {x=7 x="Blaa"}
+				// Getting an integer called x from it would give 7, getting a string called x would give "Blaa"
+				// Also, it would allow {x=7 x=foo} where foo may be defined elsewhere to be a different integer,
+				// which is useful.
+				p_last=p_scan;
+				p_scan=p_scan->mpNext;
+			}	
+		}	
+		else
+		{
+			p_last=p_scan;
+			p_scan=p_scan->mpNext;
+		}	
+	}	
+	
+	// Now, p_last points to the last component in the list, and may be NULL.
+	// Tag p_comp onto the end of the list.
+	if (p_last)
+	{
+		p_last->mpNext=p_comp;
+	}
+	else
+	{
+		mp_components=p_comp;
+	}	
+	p_comp->mpNext=NULL;
+}
+
+#ifdef __NOPT_ASSERT__ 
+void CStruct::SetParentScript(CScript *p_parentScript)
+{
+	mp_parent_script=p_parentScript;
+}
+
+CScript *CStruct::GetParentScript() const
+{
+	return mp_parent_script;
+}
+#endif
+
+
+void CStruct::AddString(uint32 nameChecksum, const char *p_string)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_STRING;
+	p_new->mpString=CreateString(p_string);
+	
+	AddComponent(p_new);
+}
+
+void CStruct::AddString(const char *p_name, const char *p_string)
+{
+	AddString(Crc::GenerateCRCFromString(p_name),p_string);
+}
+
+void CStruct::AddLocalString(uint32 nameChecksum, const char *p_string)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_LOCALSTRING;
+	p_new->mpString=CreateString(p_string);
+	
+	AddComponent(p_new);
+}
+
+void CStruct::AddLocalString(const char *p_name, const char *p_string)
+{
+	AddLocalString(Crc::GenerateCRCFromString(p_name),p_string);
+}
+
+void CStruct::AddInteger(uint32 nameChecksum, int integer)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_INTEGER;
+	p_new->mIntegerValue=integer;
+	
+	AddComponent(p_new);
+}
+
+void CStruct::AddInteger(const char *p_name, int integer)
+{
+	AddInteger(Crc::GenerateCRCFromString(p_name),integer);
+}
+
+void CStruct::AddFloat(uint32 nameChecksum, float float_val)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_FLOAT;
+	p_new->mFloatValue=float_val;
+	
+	AddComponent(p_new);
+}
+
+void CStruct::AddFloat(const char *p_name, float float_val)
+{
+	AddFloat(Crc::GenerateCRCFromString(p_name),float_val);
+}
+
+void CStruct::AddChecksum(uint32 nameChecksum, uint32 checksum)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_NAME;
+	p_new->mChecksum=checksum;
+	
+	AddComponent(p_new);
+}
+
+void CStruct::AddChecksum(const char *p_name, uint32 checksum)
+{
+	AddChecksum(Crc::GenerateCRCFromString(p_name),checksum);
+}
+
+void CStruct::AddVector(uint32 nameChecksum, float x, float y, float z)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_VECTOR;
+	p_new->mpVector=new CVector;
+	p_new->mpVector->mX=x;
+	p_new->mpVector->mY=y;
+	p_new->mpVector->mZ=z;
+	
+	AddComponent(p_new);
+}
+
+void CStruct::AddVector(const char *p_name, float x, float y, float z)
+{
+	AddVector(Crc::GenerateCRCFromString(p_name),x,y,z);
+}
+
+void CStruct::AddVector( uint32 nameChecksum, Mth::Vector vector )
+{
+	AddVector( nameChecksum, vector.GetX(), vector.GetY(), vector.GetZ() );
+}
+
+void CStruct::AddVector( const char* p_name, Mth::Vector vector )
+{
+	AddVector( Crc::GenerateCRCFromString( p_name ), vector.GetX(), vector.GetY(), vector.GetZ() );
+}
+
+void CStruct::AddPair(uint32 nameChecksum, float x, float y)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_PAIR;
+	p_new->mpPair=new CPair;
+	p_new->mpPair->mX=x;
+	p_new->mpPair->mY=y;
+	
+	AddComponent(p_new);
+}
+
+void CStruct::AddPair(const char *p_name, float x, float y)
+{
+	AddPair(Crc::GenerateCRCFromString(p_name),x,y);
+}
+
+// Creates a new array & copies in the contents of the passed array.
+void CStruct::AddArray(uint32 nameChecksum, const CArray *p_array)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_ARRAY;
+	p_new->mpArray=new CArray;
+	CopyArray(p_new->mpArray,p_array);
+
+	AddComponent(p_new);
+}
+
+void CStruct::AddArray(const char *p_name, const CArray *p_array)
+{
+	AddArray(Crc::GenerateCRCFromString(p_name),p_array);
+}
+
+// This stores the passed pointer rather than copying the contents, so in this case it
+// is important for the calling code not to delete the passed pointer.
+// The CStruct will delete it in its destructor.
+// Faster than copying the contents.
+// Used parse.cpp, when creating the structure for holding function params.
+void CStruct::AddArrayPointer(uint32 nameChecksum, CArray *p_array)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_ARRAY;
+	p_new->mpArray=p_array;
+
+	AddComponent(p_new);
+}
+
+void CStruct::AddArrayPointer(const char *p_name, CArray *p_array)
+{
+	AddArrayPointer(Crc::GenerateCRCFromString(p_name),p_array);
+}
+
+// Creates a new structure & copies in the contents of the passed structure.
+void CStruct::AddStructure(uint32 nameChecksum, const CStruct *p_structure)
+{
+	Dbg_MsgAssert(p_structure,("NULL p_structure"));
+	
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_STRUCTURE;
+	p_new->mpStructure=new CStruct;
+	*p_new->mpStructure=*p_structure;
+
+	AddComponent(p_new);
+}
+
+void CStruct::AddStructure(const char *p_name, const CStruct *p_structure)
+{
+	AddStructure(Crc::GenerateCRCFromString(p_name),p_structure);
+}
+
+// This stores the passed pointer rather than copying the contents, so in this case it
+// is important for the calling code not to delete the passed pointer.
+// The CStruct will delete it in its destructor.
+// Faster than copying the contents.
+// Used parse.cpp, when creating the structure for holding function params.
+void CStruct::AddStructurePointer(uint32 nameChecksum, CStruct *p_structure)
+{
+	Dbg_MsgAssert(p_structure,("NULL p_structure"));
+	
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_STRUCTURE;
+	p_new->mpStructure=p_structure;
+
+	AddComponent(p_new);
+}
+
+void CStruct::AddStructurePointer(const char *p_name, CStruct *p_structure)
+{
+	AddStructurePointer(Crc::GenerateCRCFromString(p_name),p_structure);
+}
+
+void CStruct::AddScript(uint32 nameChecksum, const uint8 *p_scriptTokens, uint32 size)
+{
+	CComponent *p_new=new CComponent;
+	
+	p_new->mNameChecksum=nameChecksum;
+	p_new->mType=ESYMBOLTYPE_QSCRIPT;
+	p_new->mScriptSize=size;
+	
+	// Allocate a buffer off the script heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	Dbg_MsgAssert(size,("Zero script size"));
+	uint8 *p_new_script=(uint8*)Mem::Malloc(size);
+	Mem::Manager::sHandle().PopContext();
+	
+	// Copy the script into the new buffer.
+	Dbg_MsgAssert(p_scriptTokens,("NULL p_scriptTokens"));
+	const uint8 *p_source=p_scriptTokens;
+	uint8 *p_dest=p_new_script;
+	for (uint32 i=0; impScript=p_new_script;
+		
+	AddComponent(p_new);
+}
+
+void CStruct::AddScript(const char *p_name, const uint8 *p_scriptTokens, uint32 size)
+{
+	AddScript(Crc::GenerateCRCFromString(p_name),p_scriptTokens,size);
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+// TODO: Remove all these AddComponent functions at some point.
+// They are only included to provide back compatibility with the old code without
+// having to change thousands of occurrences of calls to AddComponent.
+// Gradually phase out the old AddComponent instead.
+//
+
+// String or local string.
+void CStruct::AddComponent(uint32 nameChecksum, ESymbolType type, const char *p_string)
+{
+    Dbg_MsgAssert(type==ESYMBOLTYPE_LOCALSTRING || type==ESYMBOLTYPE_STRING,("Bad type sent to AddComponent"));
+    Dbg_MsgAssert(p_string,("NULL p_string"));
+
+	if (type==ESYMBOLTYPE_STRING)
+	{
+		AddString(nameChecksum,p_string);
+	}
+	else	
+	{
+		AddLocalString(nameChecksum,p_string);
+	}
+}
+
+// Integer or any other 4 byte value.
+void CStruct::AddComponent(uint32 nameChecksum, ESymbolType type, int integer)
+{
+	switch (type)
+	{
+		case ESYMBOLTYPE_INTEGER: 
+			AddInteger(nameChecksum,integer);
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			AddFloat(nameChecksum,*(float*)&integer);
+			break;
+		case ESYMBOLTYPE_STRUCTUREPOINTER:
+			AddStructurePointer(nameChecksum,(CStruct*)integer);
+			break;
+		case ESYMBOLTYPE_ARRAY:
+			AddArrayPointer(nameChecksum,(CArray*)integer);
+			break;
+		case ESYMBOLTYPE_NAME:
+			AddChecksum(nameChecksum,(uint32)integer);
+			break;
+		default:	
+			Dbg_MsgAssert(0,("Bad type of '%s' sent to AddComponent",GetTypeName(type)));
+			break;
+	}		
+}
+
+// Vector
+void CStruct::AddComponent(uint32 nameChecksum, float x, float y, float z)
+{
+	AddVector(nameChecksum,x,y,z);
+}
+
+// Pair
+void CStruct::AddComponent(uint32 nameChecksum, float x, float y)
+{
+	AddPair(nameChecksum,x,y);
+}
+
+// Array
+void CStruct::AddComponent(uint32 nameChecksum, CArray *p_array)
+{
+	AddArray(nameChecksum,p_array);
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+
+
+struct SWhatever
+{
+	union
+	{
+		int mIntegerValue;
+		float mFloatValue;
+		char *mpString;
+		char *mpLocalString;
+		CPair *mpPair;
+		CVector *mpVector;
+		CStruct *mpStructure;
+		CArray *mpArray;
+		uint8 *mpScript;
+		uint32 mChecksum;
+		uint32 mUnion; 
+	};
+};
+
+// Infinite recursion of search_for could occur, for example if we have a global structure foo={foo}
+// which is trying to include itself.
+// This counter will make an assert go off if search_for tries to recurse a suspicious number of times.
+#ifdef __NOPT_ASSERT__ 
+static uint32 s_num_search_for_recursions=0;
+#endif
+
+bool CStruct::search_for(uint32 nameChecksum, ESymbolType type, SWhatever *p_value) const
+{
+	Dbg_MsgAssert(s_num_search_for_recursions<10,("Possible infinite recursion of the CStruct::search_for function, bailing out!\nLast search was for parameter named '%s' of type '%s'",FindChecksumName(nameChecksum),GetTypeName(type)));
+	#ifdef __NOPT_ASSERT__
+	++s_num_search_for_recursions;
+	#endif
+	
+	Dbg_MsgAssert(p_value,("NULL p_value"));
+	
+	bool found=false;
+	
+	CComponent *p_comp=mp_components;
+	while (p_comp)
+	{
+		if (p_comp->mNameChecksum==nameChecksum)
+		{
+			// The name of the component matches that required.
+			if (p_comp->mType==type)
+			{
+				// The type also matches, so the required component has been found.
+				// Only set a flag though. Need to keep searching in case the structure
+				// has the form {x=7 x=foo} where foo is a global defined to be an integer,
+				// and which must override the previous 7.
+				
+				p_value->mUnion=p_comp->mUnion;
+				found=true;
+			}
+			else
+			{
+				// The type does not match.
+				if (p_comp->mType==ESYMBOLTYPE_NAME)
+				{
+					// The name of the component matches what we're looking for, but the type
+					// does not and is of type name.
+					// For example, we might be searching for an integer called x,
+					// but we've found a component x=foo
+					
+					// So, see if foo is the name of a global defined somewhere.
+					CSymbolTableEntry *p_global=Resolve(p_comp->mChecksum);
+					if (p_global)
+					{
+						// It is! So check if this global has the type that we're looking for ...
+						if (p_global->mType==type)
+						{
+							// It does! So we have a match.
+							// Just set the found flag though, because we need to keep searching in
+							// case the structure has the form {x=foo x=7} where the later 7 needs to
+							// override what we've just found.
+							p_value->mUnion=p_global->mUnion;
+							found=true;
+						}
+					}
+				}
+			}				
+		}
+		else
+		{
+			// The name of the component does not match what we're looking for.
+			
+			// See if the component is an un-named name, eg the blaa in {a=3 blaa}
+			if (p_comp->mNameChecksum==0 && p_comp->mType==ESYMBOLTYPE_NAME)
+			{
+				// It is, so check whether it resolves to a global structure.
+				// If a global structure is referenced by name in another structure, then
+				// it is considered 'pasted in' to that structure, so it needs to be searched too.
+				CSymbolTableEntry *p_global=Resolve(p_comp->mChecksum);
+				if (p_global)
+				{
+					// It is a global something ...
+					if (p_global->mType==ESYMBOLTYPE_STRUCTURE)
+					{
+						// It is a structure. So search it for the required component also.
+						Dbg_MsgAssert(p_global->mpStructure,("NULL p_global->mpStructure ?"));
+						SWhatever value;
+						if (p_global->mpStructure->search_for(nameChecksum,type,&value))
+						{
+							// Found! Load the value, set the found flag and carry on.
+							*p_value=value;
+							found=true;
+						}
+					}
+				}			
+			}	
+		}
+			
+		p_comp=p_comp->mpNext;
+	}	
+
+	Dbg_MsgAssert(s_num_search_for_recursions,("Eh ???"));
+	#ifdef __NOPT_ASSERT__
+	--s_num_search_for_recursions;
+	#endif
+	
+	return found;
+}
+
+
+bool CStruct::GetString(uint32 nameChecksum, const char **pp_text, EAssertType assert) const
+{
+	Dbg_MsgAssert(pp_text,("NULL pp_text"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_STRING,&value))
+	{
+		*pp_text=value.mpString;
+		return true;
+	}
+	if (search_for(nameChecksum,ESYMBOLTYPE_LOCALSTRING,&value))
+	{
+		*pp_text=value.mpString;
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find string component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetString(const char *p_paramName, const char **pp_text, EAssertType assert) const
+{
+	return GetString(Crc::GenerateCRCFromString(p_paramName),pp_text,assert);
+}
+
+bool CStruct::GetLocalString(uint32 nameChecksum, const char **pp_text, EAssertType assert) const
+{
+	return GetString(nameChecksum,pp_text,assert);
+}
+
+bool CStruct::GetLocalString(const char *p_paramName, const char **pp_text, EAssertType assert) const
+{
+	return GetString(Crc::GenerateCRCFromString(p_paramName),pp_text,assert);
+}
+
+bool CStruct::GetInteger(uint32 nameChecksum, int *p_integerValue, EAssertType assert) const
+{
+	Dbg_MsgAssert(p_integerValue,("NULL p_integerValue"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_INTEGER,&value))
+	{
+		*p_integerValue=value.mIntegerValue;
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find integer component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetInteger(const char *p_paramName, int *p_integerValue, EAssertType assert) const
+{
+	return GetInteger(Crc::GenerateCRCFromString(p_paramName),p_integerValue,assert);
+}
+
+bool CStruct::GetFloat(uint32 nameChecksum, float *p_floatValue, EAssertType assert) const
+{
+	Dbg_MsgAssert(p_floatValue,("NULL p_floatValue"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_FLOAT,&value))
+	{
+		*p_floatValue=value.mFloatValue;
+		return true;
+	}
+	// If a float was not found, check for any int's with the same name, and if found cast to a float.
+	// Note that the extra search means that {x=3.0} will result in x being found quicker than {x=3}
+	// Could speed this up by making search_for() match integers with floats, but that would slightly slow
+	// down the other Get... functions. Need to do some tests to see what is best.
+	if (search_for(nameChecksum,ESYMBOLTYPE_INTEGER,&value))
+	{
+		*p_floatValue=value.mIntegerValue;
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find float component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetFloat(const char *p_paramName, float *p_floatValue, EAssertType assert) const
+{
+	return GetFloat(Crc::GenerateCRCFromString(p_paramName),p_floatValue,assert);
+}
+
+bool CStruct::GetVector(uint32 nameChecksum, Mth::Vector *p_vector, EAssertType assert) const
+{
+	Dbg_MsgAssert(p_vector,("NULL p_vector"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_VECTOR,&value))
+	{
+		Dbg_MsgAssert(value.mpVector,("NULL value.mpVector"));
+		p_vector->Set(value.mpVector->mX,value.mpVector->mY,value.mpVector->mZ);
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find vector component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetVector(const char *p_paramName, Mth::Vector *p_vector, EAssertType assert) const
+{
+	return GetVector(Crc::GenerateCRCFromString(p_paramName),p_vector,assert);
+}
+
+bool CStruct::GetPair(uint32 nameChecksum, float *p_x, float *p_y,	EAssertType assert) const
+{
+	Dbg_MsgAssert(p_x,("NULL p_x"));
+	Dbg_MsgAssert(p_y,("NULL p_y"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_PAIR,&value))
+	{
+		Dbg_MsgAssert(value.mpPair,("NULL value.mpPair"));
+		*p_x=value.mpPair->mX;
+		*p_y=value.mpPair->mY;
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find pair component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetPair(const char *p_paramName, float *p_x, float *p_y, EAssertType assert) const
+{
+	return GetPair(Crc::GenerateCRCFromString(p_paramName),p_x,p_y,assert);
+}
+
+bool CStruct::GetStructure(uint32 nameChecksum, CStruct **pp_structure,	EAssertType assert) const
+{
+	Dbg_MsgAssert(pp_structure,("NULL pp_structure"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_STRUCTURE,&value))
+	{
+		Dbg_MsgAssert(value.mpStructure,("NULL value.mpStructure"));
+		*pp_structure=value.mpStructure;
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find structure component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetStructure(const char *p_paramName, CStruct **pp_structure,	EAssertType assert) const
+{
+	return GetStructure(Crc::GenerateCRCFromString(p_paramName),pp_structure,assert);
+}
+
+bool CStruct::GetArray(uint32 nameChecksum, CArray **pp_array, EAssertType assert) const
+{
+	Dbg_MsgAssert(pp_array,("NULL pp_array"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_ARRAY,&value))
+	{
+		Dbg_MsgAssert(value.mpArray,("NULL value.mpArray"));
+		*pp_array=value.mpArray;
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find array component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetArray(const char *p_paramName, CArray **pp_array, EAssertType assert) const
+{
+	return GetArray(Crc::GenerateCRCFromString(p_paramName),pp_array,assert);
+}
+
+bool CStruct::GetScript(uint32 nameChecksum, SStructScript *p_structScript, EAssertType assert) const
+{
+	Dbg_MsgAssert(p_structScript,("NULL p_structScript"));
+	
+	SWhatever value;
+	if (search_for(nameChecksum,ESYMBOLTYPE_QSCRIPT,&value))
+	{
+		Dbg_MsgAssert(value.mpScript,("NULL value.mpScript"));
+		p_structScript->mNameChecksum=nameChecksum;
+		p_structScript->mpScriptTokens=value.mpScript;
+		return true;
+	}
+	if (assert)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Could not find script component named '%s' in structure",FindChecksumName(nameChecksum)));
+	}	
+	return false;	
+}
+
+bool CStruct::GetScript(const char *p_paramName, SStructScript *p_structScript, EAssertType assert) const
+{
+	return GetScript(Crc::GenerateCRCFromString(p_paramName),p_structScript,assert);
+}
+
+bool CStruct::GetChecksum(uint32 nameChecksum, uint32 *p_checksum, EAssertType assert) const
+{
+	Dbg_MsgAssert(p_checksum,("NULL p_checksum"));
+
+	// Does not use the search_for function, because GetChecksum needs to ignore unnamed names
+	// that resolve to structures.
+	
+	bool found=false;
+	CComponent *p_comp=mp_components;
+	while (p_comp)
+	{
+		if (p_comp->mNameChecksum==nameChecksum)
+		{
+			if (p_comp->mType==ESYMBOLTYPE_NAME)
+			{
+				uint32 ch=p_comp->mChecksum;
+						
+				// The name matches and the type matches, but ignore any un-named names which
+				// resolve to structures since these are considered 'part of' the original structure.				
+				CSymbolTableEntry *p_entry=Resolve(ch);	
+				if (p_entry && p_entry->mType==ESYMBOLTYPE_STRUCTURE && p_comp->mNameChecksum==0)
+				{
+					// Do nothing.
+				}
+				else
+				{
+					*p_checksum=ch;
+					found=true;
+				}	
+			}
+		}
+		else if (p_comp->mNameChecksum==0 && p_comp->mType==ESYMBOLTYPE_NAME)
+		{
+			// It's an unnamed name, so check whether it is a structure.
+			uint32 ch=p_comp->mChecksum;
+			CSymbolTableEntry *p_entry=Resolve(ch);
+			if (p_entry && p_entry->mType==ESYMBOLTYPE_STRUCTURE)
+			{
+				// It is a structure, so search that.
+                Dbg_MsgAssert(p_entry->mpStructure,("NULL p_entry->mpStructure"));
+				// Note: Must not assert if not found, cos could be found later.
+                if (p_entry->mpStructure->GetChecksum(nameChecksum,p_checksum)) 
+				{
+					found=true;
+				}	
+			}
+		}
+		
+		p_comp=p_comp->mpNext;
+	}
+	if (assert && !found)
+	{
+		PrintContents(this);
+		Dbg_MsgAssert(0,("Checksum parameter '%s' not found in structure",FindChecksumName(nameChecksum)));
+	}	
+	
+    return found;
+}
+
+bool CStruct::GetChecksum(const char *p_paramName, uint32 *p_checksum, EAssertType assert) const
+{
+	return GetChecksum(Crc::GenerateCRCFromString(p_paramName),p_checksum,assert);
+}
+
+// Looks for a name, and if no name component is found, looks for a string instead, and if found
+// calculates the checksum of that.
+bool CStruct::GetChecksumOrStringChecksum(uint32 nameChecksum, uint32 *p_checksum, EAssertType assert) const
+{
+	Dbg_MsgAssert(p_checksum,("NULL p_checksum"));
+	
+	if (GetChecksum(nameChecksum,p_checksum,assert))
+	{
+		return true;
+	}
+	
+	const char *p_string=NULL;
+	if (GetString(nameChecksum,&p_string,assert))
+	{
+		Dbg_MsgAssert(p_string,("NULL p_string ?"));
+		*p_checksum=Crc::GenerateCRCFromString(p_string);
+		return true;
+	}
+	
+	return false;	
+}
+
+bool CStruct::GetChecksumOrStringChecksum(const char *p_paramName, uint32 *p_checksum, EAssertType assert) const
+{
+	return GetChecksumOrStringChecksum(Crc::GenerateCRCFromString(p_paramName),p_checksum,assert);
+}
+
+// Infinite recursion of ContainsComponentNamed could occur, for example if we have a global structure foo={foo}
+// which is trying to include itself.
+// This counter will make an assert go off if ContainsComponentNamed tries to recurse a suspicious number of times.
+#ifdef __NOPT_ASSERT__ 
+static uint32 s_num_contains_component_named_recursions=0;
+#endif
+
+// Checks to see if there is any component with the given name.
+// This is similar to ContainsFlag, except it doesn't care what the type is.
+// This function is used by the script function GotParam.
+bool CStruct::ContainsComponentNamed(uint32 checksum) const
+{
+	Dbg_MsgAssert(s_num_contains_component_named_recursions<10,("Possible infinite recursion of ContainsComponentNamed when searching for component named '%s'",FindChecksumName(checksum)));
+	#ifdef __NOPT_ASSERT__
+	++s_num_contains_component_named_recursions;
+	#endif
+	
+    CComponent *p_comp=mp_components;
+    while (p_comp)
+    {
+		if (p_comp->mNameChecksum==checksum)
+		{
+			// The name of the component matches, so return true.
+			Dbg_MsgAssert(s_num_contains_component_named_recursions,("Eh ?"));
+			#ifdef __NOPT_ASSERT__
+			--s_num_contains_component_named_recursions;
+			#endif
+			return true;
+		}
+			
+		if (p_comp->mNameChecksum==0)
+		{
+			// It's an unnamed component, so maybe it's a single isolated name, or maybe it
+			// is a name referring to a structure defined elsewhere ...
+			if (p_comp->mType==ESYMBOLTYPE_NAME)
+			{
+				// It is a name. Check first to see if it is the name being searched for ...
+				uint32 ch=p_comp->mChecksum;
+				if (ch==checksum)
+				{
+					// It is.
+					Dbg_MsgAssert(s_num_contains_component_named_recursions,("Eh ?"));
+					#ifdef __NOPT_ASSERT__
+					--s_num_contains_component_named_recursions;
+					#endif
+					return true;
+				}
+				
+				// Oh well, maybe the name is referring to a global structure ...
+				CSymbolTableEntry *p_global=Resolve(ch);
+				if (p_global)
+				{
+					// It is a global ...
+					if (p_global->mType==ESYMBOLTYPE_STRUCTURE)
+					{
+						// It is a structure, so call this function on that.
+						Dbg_MsgAssert(p_global->mpStructure,("NULL p_global->mpStructure"));
+						if (p_global->mpStructure->ContainsComponentNamed(checksum))
+						{
+							Dbg_MsgAssert(s_num_contains_component_named_recursions,("Eh ?"));
+							#ifdef __NOPT_ASSERT__
+							--s_num_contains_component_named_recursions;
+							#endif
+							return true;
+						}	
+					}	
+				}
+			}	
+		}	
+		
+        p_comp=p_comp->mpNext;
+    }
+
+	Dbg_MsgAssert(s_num_contains_component_named_recursions,("Eh ?"));
+	#ifdef __NOPT_ASSERT__
+	--s_num_contains_component_named_recursions;
+	#endif
+	return false;
+}
+
+bool CStruct::ContainsComponentNamed(const char *p_name) const
+{
+	return ContainsComponentNamed(Crc::GenerateCRCFromString(p_name));
+}
+
+// Infinite recursion of ContainsFlag could occur, for example if we have a global structure foo={foo}
+// which is trying to include itself.
+// This counter will make an assert go off if ContainsFlag tries to recurse a suspicious number of times.
+#ifdef __NOPT_ASSERT__ 
+static uint32 s_num_contains_flag_recursions=0;
+#endif
+
+// Returns true if the structure contains an unnamed component of type Name, whose checksum
+// matches that passed.
+// Eg, the structure Foo={Type=type_car Position=(0,0,0) CreatedAtStart} does contain an
+// unnamed component of type name, with value GenerateCRC("CreatedAtStart"), so CreatedAtStart
+// is like a flag.
+bool CStruct::ContainsFlag(uint32 checksum) const
+{
+	Dbg_MsgAssert(s_num_contains_flag_recursions<10,("Possible infinite recursion of ContainsFlag when searching for flag '%s'",FindChecksumName(checksum)));
+	#ifdef __NOPT_ASSERT__
+	++s_num_contains_flag_recursions;
+	#endif
+	
+    CComponent *p_comp=mp_components;
+    while (p_comp)
+    {
+        if (p_comp->mNameChecksum==0 && p_comp->mType==ESYMBOLTYPE_NAME)
+		{
+			// Found an unnamed component of type name.
+			uint32 name_checksum=p_comp->mChecksum;
+			
+			if (name_checksum==checksum)
+			{	
+				Dbg_MsgAssert(s_num_contains_flag_recursions,("Eh ?"));
+				#ifdef __NOPT_ASSERT__
+				--s_num_contains_flag_recursions;
+				#endif
+				return true;
+			}
+			
+			// The component might be a structure.
+			CSymbolTableEntry *p_global=Resolve(name_checksum);
+			if (p_global)
+			{
+				// It is a global ...
+				if (p_global->mType==ESYMBOLTYPE_STRUCTURE)
+				{
+					// It is a structure so check that structure to see if it contains the flag.
+					Dbg_MsgAssert(p_global->mpStructure,("NULL p_global->mpStructure"));
+					if (p_global->mpStructure->ContainsFlag(checksum))
+					{
+						Dbg_MsgAssert(s_num_contains_flag_recursions,("Eh ?"));
+						#ifdef __NOPT_ASSERT__
+						--s_num_contains_flag_recursions;
+						#endif
+						return true;
+					}	
+				}	
+			}
+		}	
+		
+        p_comp=p_comp->mpNext;
+    }
+	
+	Dbg_MsgAssert(s_num_contains_flag_recursions,("Eh ?"));
+	#ifdef __NOPT_ASSERT__
+	--s_num_contains_flag_recursions;
+	#endif
+    return false;
+}
+
+bool CStruct::ContainsFlag(const char *p_name) const
+{
+	return ContainsFlag(Crc::GenerateCRCFromString(p_name));
+}
+
+// Returns true if this structure contains p_struct as a substructure anywhere.
+// This is used in CScript::set_script, to determine whether clearing a structure
+// will result in the clearing of another structure.
+bool CStruct::References(CStruct *p_struct)
+{
+	if (p_struct==this)
+	{
+		return true;
+	}
+	
+    CComponent *p_comp=mp_components;
+    while (p_comp)
+    {
+        if (p_comp->mType==ESYMBOLTYPE_STRUCTURE)
+		{
+			Dbg_MsgAssert(p_comp->mpStructure,("NULL p_comp->mpStructure"));
+			if (p_comp->mpStructure->References(p_struct))
+			{
+				return true;
+			}
+		}		
+        p_comp=p_comp->mpNext;
+    }
+	
+	return false;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////
+// TODO: Remove the following Get... functions at some point.
+// They are only included to provide back compatibility with the old code without
+// having to change thousands of occurrences.
+//
+bool CStruct::GetText(uint32 nameChecksum, const char **pp_text, bool assert) const
+{
+	return GetString(nameChecksum,pp_text,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetText(const char *p_paramName, const char **pp_text, bool assert) const
+{
+	return GetString(p_paramName,pp_text,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetLocalText(uint32 nameChecksum, const char **pp_text, bool assert) const
+{
+	return GetString(nameChecksum,pp_text,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetLocalText(const char *p_paramName, const char **pp_text, bool assert) const
+{
+	return GetString(p_paramName,pp_text,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetInteger(uint32 nameChecksum, int *p_integerValue, bool assert) const
+{
+	return GetInteger(nameChecksum,p_integerValue,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetInteger(const char *p_paramName, int *p_integerValue, bool assert) const
+{
+	return GetInteger(p_paramName,p_integerValue,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetFloat(uint32 nameChecksum, float *p_float, bool assert) const
+{
+	return GetFloat(nameChecksum,p_float,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetFloat(const char *p_paramName, float *p_floatValue, bool assert) const
+{
+	return GetFloat(p_paramName,p_floatValue,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetVector(uint32 nameChecksum, Mth::Vector *p_vector, bool assert) const
+{
+	return GetVector(nameChecksum,p_vector,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetVector(const char *p_paramName, Mth::Vector *p_vector, bool assert) const
+{
+	return GetVector(p_paramName,p_vector,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetPair(uint32 nameChecksum, CPair *p_pair, bool assert) const
+{
+	float x=0.0f;
+	float y=0.0f;
+	bool ret_val=GetPair(nameChecksum,&x,&y,assert?ASSERT:NO_ASSERT);
+	Dbg_MsgAssert(p_pair,("NULL p_pair"));
+	p_pair->mX=x;
+	p_pair->mY=y;
+	return ret_val;
+}
+
+bool CStruct::GetPair(const char *p_paramName, CPair *p_pair, bool assert) const
+{
+	float x=0.0f;
+	float y=0.0f;
+	bool ret_val=GetPair(p_paramName,&x,&y,assert?ASSERT:NO_ASSERT);
+	Dbg_MsgAssert(p_pair,("NULL p_pair"));
+	p_pair->mX=x;
+	p_pair->mY=y;
+	return ret_val;
+}
+
+bool CStruct::GetStructure(uint32 nameChecksum, CStruct **pp_struct, bool assert) const
+{
+	return GetStructure(nameChecksum,pp_struct,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetStructure(const char *p_paramName, CStruct **pp_struct, bool assert) const
+{
+	return GetStructure(p_paramName,pp_struct,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetChecksum(uint32 nameChecksum, uint32 *p_checksum, bool assert) const
+{
+	return GetChecksum(nameChecksum,p_checksum,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetChecksum(const char *p_paramName, uint32 *p_checksum, bool assert) const
+{
+	return GetChecksum(p_paramName,p_checksum,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetChecksumOrStringChecksum(uint32 nameChecksum, uint32 *p_checksum, bool assert) const
+{
+	return GetChecksumOrStringChecksum(nameChecksum,p_checksum,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetChecksumOrStringChecksum(const char *p_paramName, uint32 *p_checksum, bool assert) const
+{
+	return GetChecksumOrStringChecksum(p_paramName,p_checksum,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetArray(uint32 nameChecksum, CArray **pp_array, bool assert) const
+{
+	return GetArray(nameChecksum,pp_array,assert?ASSERT:NO_ASSERT);
+}
+
+bool CStruct::GetArray(const char *p_paramName, CArray **pp_array, bool assert) const
+{
+	return GetArray(p_paramName,pp_array,assert?ASSERT:NO_ASSERT);
+}
+
+#if 0
+// Go throught the raw pool and see which entries are valid, and dump them
+//	uint8 *				mp_buffer;
+//	uint8 *				mp_buffer_end;
+//	int					m_totalItems; // that we have room for
+//	int					m_itemSize;
+
+
+void DumpStructs()
+{
+	Mem::CCompactPool * p_pool = Mem::CPoolable::sp_pool[Mem::CPoolable::s_currentPool];
+	CStruct *p_struct = (CStruct *)p_pool->mp_buffer;
+	while (p_struct <  (CStruct *)p_pool->mp_buffer_end)
+	{
+		if (p_pool->IsInPool(p_struct))
+		{
+			PrintContents(p_struct);
+		}
+		
+		p_struct++;		
+	}
+}
+
+#endif
+
+
+///////////////////////////////////////////////////////////////////////////////////
+		
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/struct.h b/Code/Gel/Scripting/struct.h
new file mode 100644
index 0000000..7fb6d5e
--- /dev/null
+++ b/Code/Gel/Scripting/struct.h
@@ -0,0 +1,286 @@
+#ifndef	__SCRIPTING_STRUCT_H
+#define	__SCRIPTING_STRUCT_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#include  // For EAssertType
+#endif
+
+#ifndef	__SCRIPTING_SYMBOLTYPE_H
+#include  // For ESymbolType
+#endif
+
+namespace Mth
+{
+class Vector;
+}
+
+namespace Script
+{
+
+class CPair; // TODO: Remove once the old GetPair member function is removed.
+
+class CComponent;
+class CScript;
+class CArray;
+struct SWhatever;
+
+// This defines a reference to a script that is defined in a CStruct.
+// A pointer to one of these can then be passed to CScript::SetScript.
+// Only exists as a convenient way of passing the data from GetScript to SetScript.
+struct SStructScript
+{
+	// The name of the script, which is the same as its parameter name in the source structure.
+	uint32 mNameChecksum;
+	// Pointer to the script data in the structure.
+	// Note that this means that this pointer will become invalid once the source structure is deleted.
+	// OK though, because SStructScript is only used to pass the info to CScript::SetScript, which
+	// will make its own copy of the script data.
+	uint8 *mpScriptTokens;
+	
+	SStructScript() {mNameChecksum=NO_NAME; mpScriptTokens=NULL;}
+};
+
+#ifdef __PLAT_WN32__
+class CStruct
+#else
+class CStruct : public Mem::CPoolable
+#endif
+{
+	// Head pointer of the list of components.
+	CComponent *mp_components;
+    
+	#ifdef __NOPT_ASSERT__ 
+	// The script that created this structure. Only valid (non NULL) if this is 
+	// a mpFunctionParams structure created by a script.
+	// Needed so that if the Get... functions assert they can print info about 
+	// the parent script, eg the line number of the error.
+	CScript *mp_parent_script;
+	#endif
+
+	void init();
+	bool search_for(uint32 nameChecksum, ESymbolType type, SWhatever *p_value) const;
+	
+public:
+    CStruct();
+    ~CStruct();
+
+	// These are defined, and will create new instances of any entities pointed to by the
+	// source structure, so the two structures will not share any pointers.
+	// Careful when using these though, because if copying a complex structure with lots
+	// of nested sub-structures and arrays it could potentially take a long time to execute.
+	CStruct( const CStruct& rhs );
+	CStruct& operator=( const CStruct& rhs );
+	
+	// This will merge the contents of the rhs into this structure.
+	// Functionally the same as the old AppendStructure function.
+	CStruct& operator+=( const CStruct& rhs );
+	
+    // Deletes all components, deleting any arrays or structures referenced by the components.
+    void Clear();
+
+	// Removes the component with the given name.
+	void RemoveComponent(uint32 nameChecksum);
+	void RemoveComponent(const char *p_name);
+
+	// Same as RemoveComponent except the type must match too.
+	void RemoveComponentWithType(uint32 nameChecksum, uint8 type);
+	void RemoveComponentWithType(const char *p_name, uint8 type);
+	
+	// Removes the flag (unnamed name) with the given name.
+	void RemoveFlag(uint32 checksum);
+	void RemoveFlag(const char *p_name);
+
+	// Adds p_comp to the end of the list of components, removing any existing components with
+	// the same name.
+	void AddComponent(CComponent *p_comp);
+	
+	#ifdef __NOPT_ASSERT__ 
+	void SetParentScript(CScript *p_parentScript);
+	#endif
+
+	// If a component of the same name and type already exists it will be replaced with the new value.
+	// If a component of the same name but different type exists, it will assert.
+	// NameChecksum can be 0, meaning no name.
+	// If all OK, returns a pointer to the new component.
+    
+	// These will copy in the actual string data, not store the pointer.
+	void AddString(uint32 nameChecksum, const char *p_string);
+	void AddString(const char *p_name, const char *p_string);
+	void AddLocalString(uint32 nameChecksum, const char *p_string);
+	void AddLocalString(const char *p_name, const char *p_string);
+	
+	void AddInteger(uint32 nameChecksum, int integer);
+	void AddInteger(const char *p_name, int integer);
+	
+	void AddFloat(uint32 nameChecksum, float float_val);
+	void AddFloat(const char *p_name, float float_val);
+
+	void AddChecksum(uint32 nameChecksum, uint32 checksum);
+	void AddChecksum(const char *p_name, uint32 checksum);
+	
+	void AddVector(uint32 nameChecksum, float x, float y, float z);
+	void AddVector(const char* p_name, float x, float y, float z);
+	void AddVector( uint32 nameChecksum, Mth::Vector vector );
+	void AddVector( const char* p_name, Mth::Vector vector );
+
+	
+	void AddPair(uint32 nameChecksum, float x, float y);
+	void AddPair(const char *p_name, float x, float y);
+
+	// Creates a new array & copies in the contents of the passed array.
+	void AddArray(uint32 nameChecksum, const CArray *p_array);
+	void AddArray(const char *p_name, const CArray *p_array);
+
+	// Stores the passed pointer. Faster than copying the contents.
+	// The passed pointer is no longer const because the CStruct will delete it when destroyed.
+	void AddArrayPointer(uint32 nameChecksum, CArray *p_array);
+	void AddArrayPointer(const char *p_name, CArray *p_array);
+
+	// Creates a new structure & copies in the contents of the passed structure.
+	void AddStructure(uint32 nameChecksum, const CStruct *p_structure);
+	void AddStructure(const char *p_name, const CStruct *p_structure);
+	
+	// Stores the passed pointer. Faster than copying the contents.
+	// The passed pointer is no longer const because the CStruct will delete it when destroyed.
+	void AddStructurePointer(uint32 nameChecksum, CStruct *p_structure);
+	void AddStructurePointer(const char *p_name, CStruct *p_structure);
+
+	// Creates a new script component and copies in the passed script.
+	void AddScript(uint32 nameChecksum, const uint8 *p_scriptTokens, uint32 size);
+	void AddScript(const char *p_name, const uint8 *p_scriptTokens, uint32 size);
+	
+	///////////////////////////////////////////////////////////////////////////////////
+	// TODO: Remove all these AddComponent functions at some point.
+	// They are only included to provide back compatibility with the old code without
+	// having to change thousands of occurrences of calls to AddComponent.
+	// Gradually phase out the old AddComponent instead.
+	//
+	
+    // String or local string.
+	void AddComponent(uint32 nameChecksum, ESymbolType type, const char *p_string); // This will copy the actual string data
+    // Integer or any other 4 byte value.
+	void AddComponent(uint32 nameChecksum, ESymbolType type, int integer);
+    // Vector
+	void AddComponent(uint32 nameChecksum, float x, float y, float z);
+    // Pair
+	void AddComponent(uint32 nameChecksum, float x, float y);
+	// Array
+	void AddComponent(uint32 nameChecksum, CArray *p_array); // Copies the data in the array
+	///////////////////////////////////////////////////////////////////////////////////
+
+
+	bool GetString		(uint32 nameChecksum, 		const char **pp_text, 	EAssertType assert=NO_ASSERT) const;
+	bool GetString		(const char *p_paramName, 	const char **pp_text, 	EAssertType assert=NO_ASSERT) const;
+    bool GetLocalString	(uint32 nameChecksum, 		const char **pp_text, 	EAssertType assert=NO_ASSERT) const;
+	bool GetLocalString	(const char *p_paramName, 	const char **pp_text, 	EAssertType assert=NO_ASSERT) const;
+	bool GetInteger		(uint32 nameChecksum, 		int *p_integerValue, 	EAssertType assert=NO_ASSERT) const;
+	bool GetInteger		(const char *p_paramName, 	int *p_integerValue, 	EAssertType assert=NO_ASSERT) const;
+	bool GetFloat		(uint32 nameChecksum, 		float *p_floatValue,	EAssertType assert=NO_ASSERT) const;
+	bool GetFloat		(const char *p_paramName, 	float *p_floatValue, 	EAssertType assert=NO_ASSERT) const;
+	bool GetVector		(uint32 nameChecksum, 		Mth::Vector *p_vector, 	EAssertType assert=NO_ASSERT) const;
+	bool GetVector		(const char *p_paramName, 	Mth::Vector *p_vector, 	EAssertType assert=NO_ASSERT) const;
+	bool GetPair		(uint32 nameChecksum, 		float *p_x, float *p_y,	EAssertType assert=NO_ASSERT) const;
+	bool GetPair		(const char *p_paramName, 	float *p_x, float *p_y,	EAssertType assert=NO_ASSERT) const;
+	bool GetStructure	(uint32 nameChecksum, 		CStruct **pp_structure,	EAssertType assert=NO_ASSERT) const;
+	bool GetStructure	(const char *p_paramName, 	CStruct **pp_structure,	EAssertType assert=NO_ASSERT) const;
+	bool GetChecksum	(uint32 nameChecksum, 		uint32 *p_checksum, 	EAssertType assert=NO_ASSERT) const;
+	bool GetChecksum	(const char *p_paramName, 	uint32 *p_checksum, 	EAssertType assert=NO_ASSERT) const;
+	bool GetArray		(uint32 nameChecksum, 		CArray **pp_array, 		EAssertType assert=NO_ASSERT) const;
+	bool GetArray		(const char *p_paramName, 	CArray **pp_array, 		EAssertType assert=NO_ASSERT) const;
+	bool GetScript		(uint32 nameChecksum, 		SStructScript *p_structScript, EAssertType assert=NO_ASSERT) const;
+	bool GetScript		(const char *p_paramName,	SStructScript *p_structScript, EAssertType assert=NO_ASSERT) const;
+	bool GetChecksumOrStringChecksum(uint32 nameChecksum, uint32 *p_checksum, EAssertType assert=NO_ASSERT) const;
+	bool GetChecksumOrStringChecksum(const char *p_paramName, uint32 *p_checksum, EAssertType assert=NO_ASSERT) const;
+	bool ContainsComponentNamed(uint32 checksum) const;
+	bool ContainsComponentNamed(const char *p_name) const;
+	bool ContainsFlag	(uint32 checksum) const;
+	bool ContainsFlag	(const char *p_name) const;
+
+	///////////////////////////////////////////////////////////////////////////////////
+	// TODO: Remove all these GetText functions at some point.
+	// They are only included to provide back compatibility with the old code without
+	// having to change thousands of occurrences of calls to GetText.
+	// Some of them do not have a default value for assert given, to prevent ambiguities with the above functions.
+    bool GetText(uint32 nameChecksum, const char **pp_text, bool assert=false) const;
+	bool GetText(const char *p_paramName, const char **pp_text, bool assert=false) const;
+    bool GetLocalText(uint32 nameChecksum, const char **pp_text, bool assert=false) const;
+	bool GetLocalText(const char *p_paramName, const char **pp_text, bool assert=false) const;
+	bool GetInteger(uint32 nameChecksum, int *p_integerValue, bool assert) const;
+	bool GetInteger(const char *p_paramName, int *p_integerValue, bool assert) const;
+	bool GetFloat(uint32 nameChecksum, float *p_float, bool assert) const;
+	bool GetFloat(const char *p_paramName, float *p_floatValue, bool assert) const;
+	bool GetVector(uint32 nameChecksum, Mth::Vector *p_vector, bool assert) const;
+	bool GetVector(const char *p_paramName, Mth::Vector *p_vector, bool assert) const;
+	bool GetPair(uint32 nameChecksum, CPair *p_pair, bool assert=false) const;
+	bool GetPair(const char *p_paramName, CPair *p_pair, bool assert=false) const;
+	bool GetStructure(uint32 nameChecksum, CStruct **pp_struct, bool assert) const;
+	bool GetStructure(const char *p_paramName, CStruct **pp_struct, bool assert) const;
+	bool GetChecksum(uint32 nameChecksum, uint32 *p_checksum, bool assert) const;
+	bool GetChecksum(const char *p_paramName, uint32 *p_checksum, bool assert) const;
+	bool GetChecksumOrStringChecksum(uint32 nameChecksum, uint32 *p_checksum, bool assert) const;
+	bool GetChecksumOrStringChecksum(const char *p_paramName, uint32 *p_checksum, bool assert) const;
+	bool GetArray(uint32 nameChecksum, CArray **pp_array, bool assert) const;
+	bool GetArray(const char *p_paramName, CArray **pp_array, bool assert) const;
+	///////////////////////////////////////////////////////////////////////////////////
+	
+	// TODO: Remove at some point. Provided for back-compatibility.
+	void AppendStructure(const CStruct *p_struct);
+
+	void AppendComponentPointer(CComponent *p_comp);
+
+	// This will copy the contents of this structure into p_dest, but in such a way that
+	// p_dest will contain no unnamed-structure references, they will all get expanded.
+	// This was added because sometimes one wants to step through each of the components of
+	// a structure using GetNextComponent, but GetNextComponent itself cannot recurse into
+	// unnamed structures, because when it reaches the end of one it won't know how to get back
+	// to the parent.
+	// So instead just create a new structure, copy the other structure into it using ExpandInto,
+	// then step through that using GetNextComponent, then delete the new structure once finished.
+	// It's a bit memory intensive, but saves having to write special code to resolve unnamed
+	// structures & recursing whenever one wants to scan through the components.
+	// Could be handy for other things later too.
+	// recursionCount is just to catch infinite recursion, so no need to pass it a value.
+	void ExpandInto(CStruct *p_dest, int recursionCount=0) const;
+	
+	// Returns true if the structure contains no components.
+	bool IsEmpty() const;
+    
+	// Searches for a component with the given name, but will also recurse into substructures.
+	// Used when resolving the  syntax.
+	CComponent *FindNamedComponentRecurse(uint32 nameChecksum) const;
+	
+	// If passed NULL this will return the first (leftmost) component.
+	// If passed non-NULL it return the next component (to the right) 
+	// Returns NULL if the passed component is the last component.
+	CComponent *GetNextComponent(CComponent *p_comp=NULL) const;
+
+	// Returns true if this structure contains p_struct as a substructure anywhere.
+	// This is used in CScript::set_script, to determine whether clearing a structure
+	// will result in the clearing of another structure.
+	bool References(CStruct *p_struct);
+	
+	#ifdef __NOPT_ASSERT__ 
+	CScript *GetParentScript() const;
+	bool CheckForDuplicateFlags() const;
+	#endif
+};
+
+void CleanUpComponent(CComponent *p_comp);
+void CopyComponent(CComponent *p_dest, const CComponent *p_source);
+void CleanUpArray(CArray *p_array);
+void CopyArray(CArray *p_dest, const CArray *p_source);
+
+void DumpStructs();
+
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_STRUCT_H
diff --git a/Code/Gel/Scripting/symboltable.cpp b/Code/Gel/Scripting/symboltable.cpp
new file mode 100644
index 0000000..d98ad6c
--- /dev/null
+++ b/Code/Gel/Scripting/symboltable.cpp
@@ -0,0 +1,762 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// symboltable.cpp		KSH 17 Oct 2001
+//
+// Symbol hash table stuff.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+
+#include 
+
+DefinePoolableClass(Script::CSymbolTableEntry);
+
+namespace Script
+{
+
+CSymbolTableEntry::CSymbolTableEntry()
+{
+	// Initialise everything. CSymbolTableEntry is not derived from CClass so we don't get
+	// the auro-zeroing.
+	Reset();
+}
+
+// The destructor does not delete anything to avoid cyclic dependencies, so just assert
+// if it look like there could be a non-NULL pointer left.
+#ifdef __NOPT_ASSERT__
+CSymbolTableEntry::~CSymbolTableEntry()
+{
+	Dbg_MsgAssert(mUnion==0,("CSymbolTableEntry still contains data, possibly an undeleted pointer"));
+}
+#endif
+
+void CSymbolTableEntry::Reset()
+{
+    mNameChecksum=NO_NAME;
+	mGotReloaded=false;
+    mUsed=false;
+    mType=ESYMBOLTYPE_NONE;
+	mUnion=0;
+    mpNext=NULL;
+	mSourceFileNameChecksum=NO_NAME;
+	#ifdef COUNT_USAGE
+	mUsage=0;
+	#endif
+}
+
+static CSymbolTableEntry *sp_hash_table=NULL;
+
+void CreateSymbolHashTable()
+{
+	Dbg_MsgAssert(sp_hash_table==NULL,("sp_hash_table not NULL ?"));
+	sp_hash_table=new CSymbolTableEntry[1<mUsed) 
+//	{
+//		//++NothingCount;
+//		return NULL;
+//	}	
+
+#if 0	
+	//MCozzini 4-30-03 optimization to avoid the p_sym test
+	if(p_sym->mNameChecksum==checksum)
+	{
+		return p_sym;
+	}
+	p_sym=p_sym->mpNext;
+
+
+    // Scan through the linked list until the complete checksum is found.
+    while (p_sym && p_sym->mNameChecksum!=checksum) 
+	{
+		p_sym=p_sym->mpNext;
+	}	
+#else
+	// it it's an unused slot, then both mNameChecksum and mpNext will be 0 (NULL)
+	// so it will just drop through this test
+	do 
+	{
+		if(p_sym->mNameChecksum == checksum)
+		{
+			return p_sym;	// Quicker to return here than to break
+		}
+		p_sym=p_sym->mpNext;
+	} while (p_sym && p_sym->mNameChecksum!=checksum);
+	
+	// Here p_sym will be NULL	
+	return p_sym;	
+
+#endif
+	
+	// p_sym will be NULL if the checksum was not found.
+	/*
+	if (p_sym)
+	{
+		if (p_sym->mType==ESYMBOLTYPE_QSCRIPT)
+		{
+			++ScriptCount;
+		}
+		else if (p_sym->mType==ESYMBOLTYPE_CFUNCTION)
+		{
+			++CFuncCount;
+		}
+		else if (p_sym->mType==ESYMBOLTYPE_MEMBERFUNCTION)
+		{
+			++MemberFuncCount;
+		}
+		else if (p_sym->mType==ESYMBOLTYPE_FLOAT)
+		{
+			++FloatCount;
+		}
+		else if (p_sym->mType==ESYMBOLTYPE_INTEGER)
+		{
+			++IntegerCount;
+		}
+		else if (p_sym->mType==ESYMBOLTYPE_STRUCTURE)
+		{
+			++StructureCount;
+		}
+		else if (p_sym->mType==ESYMBOLTYPE_ARRAY)
+		{
+			++ArrayCount;
+		}
+		
+	}
+	else
+	{
+		++NothingCount;
+	}*/
+    return p_sym;
+}
+
+CSymbolTableEntry *LookUpSymbol(const char *p_name)
+{
+	return LookUpSymbol(Crc::GenerateCRCFromString(p_name));
+}	
+
+// Resolves a checksum into a symbol table entry. Does any stepping over of chains of symbols linked by
+// name. Eg, Resolving a where a=b, b=c, c=d, d=6 will return the symbol d, with value 6.
+// Returns NULL if the checksum cannot be resolved.
+CSymbolTableEntry *Resolve(uint32 checksum)
+{
+    // Look up the checksum.
+    CSymbolTableEntry *p_entry=LookUpSymbol(checksum);
+	if (p_entry && p_entry->mType==ESYMBOLTYPE_NAME)
+	{
+		// If the symbol is of type name, then it may be that the name is the name of another symbol,
+		// in which case we need to find that.
+		// Eg we could be trying to resolve a, where a=b, b=c, c=7, in which case we need to find 7.
+		// So loop until either a non-name symbol is reached, or no further symbol is reached.
+		
+		#ifdef __NOPT_ASSERT__ 
+		// An infinite loop could occur, for example if we have a global a=b where b=a
+		// This counter will make an assert go off if we try and loop a suspicious number of times.
+		uint32 num_loops=0;
+		#endif
+	
+		while (true)
+		{
+			CSymbolTableEntry *p_new=LookUpSymbol(p_entry->mChecksum);
+			if (!p_new)
+			{
+				// Reached the end of the chain, so return the last entry, which will have type name.
+				return p_entry;
+			}
+				
+			if (p_new->mType!=ESYMBOLTYPE_NAME)
+			{
+				// Reached something not of type name, so return that.
+				return p_new;
+			}
+			
+			// Repeat.	
+			p_entry=p_new;
+			Dbg_MsgAssert(num_loops++<10,("Possible infinite recursion of the Resolve function when trying to resolve '%s', bailing out!",FindChecksumName(checksum)));
+		}
+	}
+			
+    return p_entry;
+}
+
+CSymbolTableEntry *Resolve(const char *p_name)
+{
+	return Resolve(Crc::GenerateCRCFromString(p_name));
+}	
+
+// Removes the symbol from the table.
+// Used when reloading a .qb file. 
+// Note: Will not delete any entities referred to by the symbol, and will assert if any of the
+// pointers are not NULL.
+// This is to avoid a cyclic dependency between symboltable and struct.
+// It is up to the caller to have deleted these before calling this.
+void RemoveSymbol(CSymbolTableEntry *p_sym)
+{
+	Dbg_MsgAssert(p_sym,("NULL p_sym"));
+	Dbg_MsgAssert(p_sym->mUnion==0,("CSymbolTableEntry still contains data, possibly an undeleted pointer. Type='%s'",GetTypeName(p_sym->mType)));
+	Dbg_MsgAssert(p_sym->mUsed,("Tried to call RemoveSymbol on an unused CSymbolTableEntry"));
+	Dbg_MsgAssert(sp_hash_table!=NULL,("sp_hash_table is NULL ?"));
+
+	// Get the head pointer of the list that p_sym is in (or should be in) 
+    CSymbolTableEntry *p_head=&sp_hash_table[ p_sym->mNameChecksum & ((1<mpNext)
+        {
+			// There are later entries in the list, so copy the first of these down
+			// to fill the hole.
+			CSymbolTableEntry *p_source=p_sym->mpNext;
+			
+			// Copy the contents. Not using the assignement operator because that won't link.
+			// It is declared but not defined so as to cause it not to link, to prevent any problems
+			// with copying pointers if it gets used anywhere else.
+			uint32 *p_source_word=(uint32*)p_source;
+			uint32 *p_dest_word=(uint32*)p_sym;
+			Dbg_MsgAssert((sizeof(CSymbolTableEntry)&3)==0,("sizeof(CSymbolTableEntry) not a multiple of 4 ?"));
+			for (uint32 i=0; imUnion=0;  
+			delete p_source;
+        }
+		else
+		{
+			// Just reset the contents of p_sym.
+			p_sym->Reset();
+		}
+    }
+    else
+    {
+        // p_sym is not in the hash table, so it must have been allocated off the pool.
+		// In order to unlink it from the list we need to find the previous entry in the list.
+		// So search through the list until p_sym is found.		
+				
+		// Start with mpNext, cos we know p_sym is not p_head		
+		CSymbolTableEntry *p_previous=p_head;
+		CSymbolTableEntry *p_search=p_head->mpNext;
+		while (p_search)
+		{
+			if (p_search==p_sym)
+			{
+				break;
+			}	
+			p_previous=p_search;
+			p_search=p_search->mpNext;
+		}
+		
+		Dbg_MsgAssert(p_search,("p_sym not found in list ? (checksum='%s')",FindChecksumName(p_sym->mNameChecksum)));
+		
+		// Unlink p_sym and delete it.
+		p_previous->mpNext=p_sym->mpNext;
+		delete p_sym;
+	}	
+}
+
+// Adds a new checksum to the directory and returns a pointer to the new entry.
+CSymbolTableEntry *CreateNewSymbolEntry(uint32 checksum)
+{
+	#ifdef __NOPT_ASSERT__
+	CSymbolTableEntry *p_entry=LookUpSymbol(checksum);
+    Dbg_MsgAssert(p_entry==NULL,("Symbol '%s' defined twice.",FindChecksumName(checksum)));
+	#endif
+    
+    // Get the head pointer of the list where the new symbol needs to go.
+	Dbg_MsgAssert(sp_hash_table!=NULL,("sp_hash_table is NULL ?"));
+    CSymbolTableEntry *p_sym=&sp_hash_table[ checksum & ((1<mUsed)
+    {
+		p_sym->Reset();
+        // Set the checksum.
+        p_sym->mNameChecksum=checksum;
+        // Flag it as used.
+        p_sym->mUsed=true;
+		// Set the mGotReloaded flag so that other game-specific code can see 
+		// that the symbol has changed in value.
+		p_sym->mGotReloaded=true;
+        return p_sym;
+    }
+
+    // Find the last element of the linked list, checking for any checksum
+    // clashes on the way.
+    CSymbolTableEntry *p_previous=NULL;
+    while (p_sym)
+    {
+        Dbg_MsgAssert(p_sym->mNameChecksum!=checksum,("Checksum clash in symbol table: '%s'",FindChecksumName(checksum)));
+        p_previous=p_sym;
+        p_sym=p_sym->mpNext;
+    }
+
+	// Get a new entry from the pool.
+    CSymbolTableEntry *p_new=new CSymbolTableEntry;
+	
+    // Set the checksum & next pointer.
+    p_new->mNameChecksum=checksum;
+    // Flag it as used.
+    p_new->mUsed=true;
+	// Set the mGotReloaded flag so that other game-specific code can see 
+	// that the symbol has changed in value.
+	p_new->mGotReloaded=true;
+	
+    p_new->mpNext=NULL;
+
+    // This assert should never go off, but do it just in case.
+    Dbg_MsgAssert(p_previous,("NULL p_previous ???"));
+    // Stick pNew on the end of the list.
+    p_previous->mpNext=p_new;
+
+    return p_new;
+}
+
+// This function provides an easy way to loop through all the symbols in the symbol table by
+// hiding all the messy logic for stepping through the hash table.
+//
+// If passed NULL, it will return the first entry in the symbol table (or NULL if there is nothing in it)
+// If passed a pointer to a CSymbolTableEntry, it will return the next one, or NULL if none left.
+//
+// So if you want to loop through all the symbols just do something like:
+// CSymbolTableEntry *p_sym=GetNextSymbolTableEntry(); // No need to pass NULL, it defaults to it.
+// while (p_sym)
+// {
+// 		// ...
+//		p_sym=GetNextSymbolTableEntry(p_sym);
+// }
+//
+CSymbolTableEntry *GetNextSymbolTableEntry(CSymbolTableEntry *p_sym)
+{
+	// static's for keeping track of where we are in the hash table.
+	static uint32 s_current_hash_index=0;
+	static CSymbolTableEntry *sp_last=NULL;
+	
+	if (p_sym==NULL)
+	{
+		// They want the first symbol.
+		s_current_hash_index=0;
+		p_sym=sp_hash_table;
+		
+		// Step through the hash table entries until a used one is found.
+		while (!p_sym->mUsed)
+		{
+			++s_current_hash_index;
+			++p_sym;
+			
+			if (s_current_hash_index>=(1<mpNext;
+	if (!p_sym)
+	{
+		// We've gone off the end of this linked list, so go onto the next entry in the hash table.
+		++s_current_hash_index;
+		p_sym=sp_hash_table+s_current_hash_index;
+		
+		if (s_current_hash_index>=(1<mUsed)
+		{
+			++s_current_hash_index;
+			++p_sym;
+			
+			if (s_current_hash_index>=(1<mType)
+    {
+    case ESYMBOLTYPE_FLOAT:
+        return p_entry->mFloatValue;
+        break;
+    case ESYMBOLTYPE_INTEGER:
+        return (float)p_entry->mIntegerValue;
+        break;    
+    default:
+        Dbg_MsgAssert(0,("Symbol '%s' of type '%s' cannot be converted to a float.",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+        return 0.0f;
+        break;
+    }        
+	
+	return 0.0f;
+}
+
+float GetFloat(const char *p_name, EAssertType assert)
+{
+	return GetFloat(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+int GetInteger(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("Integer '%s' not found.",FindChecksumName(checksum)));
+        return 0;
+    }    
+
+    switch (p_entry->mType)
+    {
+    case ESYMBOLTYPE_FLOAT:
+        return (int)p_entry->mFloatValue;
+        break;
+    case ESYMBOLTYPE_INTEGER:
+        return p_entry->mIntegerValue;
+        break;    
+    default:
+        Dbg_MsgAssert(0,("Symbol '%s' of type '%s' cannot be converted to an integer.",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+        return 0;
+        break;
+    }        
+
+	return 0;
+}
+
+int GetInteger(const char *p_name, EAssertType assert)
+{
+	return GetInteger(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+uint32 GetChecksum(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("Checksum named '%s' not found.",FindChecksumName(checksum)));
+        return 0;
+    }    
+
+    Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_NAME,("GetChecksum: Symbol '%s' has the wrong type of '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+	return p_entry->mChecksum;	
+}
+
+uint32 GetChecksum(const char *p_name, EAssertType assert)
+{
+	return GetChecksum(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+const char *GetString(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("String named '%s' not found.",FindChecksumName(checksum)));
+        return "";
+    }    
+
+    Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_STRING || p_entry->mType==ESYMBOLTYPE_LOCALSTRING,("GetString: Symbol '%s' has the wrong type of '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+	Dbg_MsgAssert(p_entry->mpString,("NULL p_entry->mpString"));
+	return p_entry->mpString;	
+}
+
+const char *GetString(const char *p_name, EAssertType assert)
+{
+	return GetString(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+const char *GetLocalString(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("Local string named '%s' not found.",FindChecksumName(checksum)));
+        return "";
+    }    
+
+    Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_LOCALSTRING || p_entry->mType==ESYMBOLTYPE_STRING,("GetLocalString: Symbol '%s' has the wrong type of '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+	Dbg_MsgAssert(p_entry->mpLocalString,("NULL p_entry->mpLocalString"));
+	return p_entry->mpLocalString;	
+}
+
+const char *GetLocalString(const char *p_name, EAssertType assert)
+{
+	return GetLocalString(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+CVector *GetVector(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("Vector named '%s' not found.",FindChecksumName(checksum)));
+        return NULL;
+    }    
+
+    Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_VECTOR,("GetVector: Symbol '%s' has the wrong type of '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+	Dbg_MsgAssert(p_entry->mpVector,("NULL p_entry->mpVector"));
+	return p_entry->mpVector;	
+}
+
+CVector *GetVector(const char *p_name, EAssertType assert)
+{
+	return GetVector(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+CPair *GetPair(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("Pair named '%s' not found.",FindChecksumName(checksum)));
+        return NULL;
+    }    
+
+    Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_PAIR,("GetPair: Symbol '%s' has the wrong type of '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+	Dbg_MsgAssert(p_entry->mpPair,("NULL p_entry->mpPair"));
+	return p_entry->mpPair;	
+}
+
+CPair *GetPair(const char *p_name, EAssertType assert)
+{
+	return GetPair(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+CStruct *GetStructure(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("Structure named '%s' not found.",FindChecksumName(checksum)));
+        return NULL;
+    }    
+
+	#ifdef	__NOPT_ASSERT__
+	if (assert)
+	{
+		Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_STRUCTURE,("GetStructure: Symbol '%s' has the wrong type of '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+	}
+	else
+	#endif
+	{
+		if (p_entry->mType!=ESYMBOLTYPE_STRUCTURE)
+		{
+			return NULL;
+		}
+	}		
+	
+	Dbg_MsgAssert(p_entry->mpStructure,("NULL p_entry->mpStructure"));
+	return p_entry->mpStructure;	
+}
+
+CStruct *GetStructure(const char *p_name, EAssertType assert)
+{
+	return GetStructure(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+CArray *GetArray(uint32 checksum, EAssertType assert)
+{
+    CSymbolTableEntry *p_entry=Resolve(checksum);
+    if (!p_entry)
+    {
+		Dbg_MsgAssert(!assert,("Array named '%s' not found.",FindChecksumName(checksum)));
+        return NULL;
+    }    
+
+	#ifdef	__NOPT_ASSERT__
+	if (assert)
+	{
+		Dbg_MsgAssert(p_entry->mType==ESYMBOLTYPE_ARRAY,("GetArray: Symbol '%s' has the wrong type of '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+	}
+	else
+	#endif
+	{
+		if (p_entry->mType!=ESYMBOLTYPE_ARRAY)
+		{
+			return NULL;
+		}
+	}
+			
+	Dbg_MsgAssert(p_entry->mpArray,("NULL p_entry->mpArray"));
+	return p_entry->mpArray;	
+}
+
+CArray *GetArray(const char *p_name, EAssertType assert)
+{
+	return GetArray(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+bool (*GetCFunc(uint32 checksum, EAssertType assert))(CStruct *, CScript *)
+{
+	CSymbolTableEntry *p_entry=Resolve(checksum);
+	if (!p_entry)
+	{
+		Dbg_MsgAssert(!assert,("GetCFunc could not find the symbol '%s'. No symbol of any type exists with that name.",FindChecksumName(checksum)));
+		return NULL;
+	}
+		
+	switch (p_entry->mType)
+	{
+		case ESYMBOLTYPE_CFUNCTION:
+			Dbg_MsgAssert(p_entry->mpCFunction,("NULL p_entry->pCFunction ?"));
+			return p_entry->mpCFunction;
+			break;
+			
+		case ESYMBOLTYPE_MEMBERFUNCTION:
+			Dbg_MsgAssert(0,("'%s' is not a C-function, it is a member function.",FindChecksumName(checksum)));
+			break;
+			
+		default:	
+			Dbg_MsgAssert(0,("'%s' is not a C-function, but has type '%s'",FindChecksumName(checksum),GetTypeName(p_entry->mType)));
+			break;
+	}
+	
+	return NULL;			
+}
+
+bool (*GetCFunc(const char *p_name, EAssertType assert))(CStruct *, CScript *)
+{
+	return GetCFunc(Crc::GenerateCRCFromString(p_name),assert);
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+// TODO: Remove these next functions at some point.
+// They are only included to provide back compatibility with the old code without
+// having to change thousands of occurrences.
+int GetInt(uint32 checksum, bool assert)
+{
+	return GetInteger(checksum,assert?ASSERT:NO_ASSERT);
+}
+
+int GetInt(const char *p_name, bool assert)
+{
+	return GetInteger(p_name,assert?ASSERT:NO_ASSERT);
+}
+
+bool (*GetCFuncPointer(uint32 checksum, EAssertType assert))(CStruct *, CScript *)
+{
+	return GetCFunc(checksum, assert);
+}
+
+bool (*GetCFuncPointer(const char *p_name, EAssertType assert))(CStruct *, CScript *)
+{
+	return GetCFunc(p_name, assert);
+}
+///////////////////////////////////////////////////////////////////////////////////
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/symboltable.h b/Code/Gel/Scripting/symboltable.h
new file mode 100644
index 0000000..beeb56b
--- /dev/null
+++ b/Code/Gel/Scripting/symboltable.h
@@ -0,0 +1,169 @@
+#ifndef	__SCRIPTING_SYMBOLTABLE_H
+#define	__SCRIPTING_SYMBOLTABLE_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_SYMBOLTYPE_H
+#include  // For ESymbolType
+#endif
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#include  // For EAssertType
+#endif
+
+namespace Script
+{
+
+// If defined then the usage of each cfunc and member func will be counted on startup
+// by scanning through all the scripts.
+// Uses a bit of memory so only define on debug builds.
+#ifdef __NOPT_ASSERT__
+//#define COUNT_USAGE
+#endif
+
+#define NUM_HASH_BITS 12
+
+class CPair;
+class CVector;
+class CArray;
+class CStruct;
+class CScript;
+
+#ifdef __PLAT_WN32__
+class CSymbolTableEntry
+#else
+class CSymbolTableEntry : public Mem::CPoolable
+#endif
+{
+public:
+	// Note: The placement of these bitfielded members is important. The CPoolable class
+	// has an overhead of 1 byte, so by putting the small members here they will use up the 3
+	// byte padding, keeping the size of this class down. There are 4000 or so of these, so
+	// the size needs to be kept as small as possible.
+	
+	// If a symbol is deleted and recreated by parse.cpp then this flag will get set.
+	// This is so that other code can use this to detect whether a symbol has changed due to a reload.
+	// For example, file.cpp checks the script mGotReloaded flags after loading a qb, and if reloaded
+	// it will then restart all existing CScript's that refer to it so that  they do not crash due to 
+	// invalid pointers.
+	// Similarly, if a qb gets unloaded any existing CScript's that refer to a script in it must be stopped.
+	// Also, in THPS, if the symbol NodeArray gets reloaded then name prefix information must be regenerated.
+	//
+	uint8 mGotReloaded:1;
+	
+	// The symbols in the contiguous array use this flag to indicate if they are occupied.
+    bool mUsed:1;
+	
+    uint8 mType;
+	
+	#ifdef COUNT_USAGE
+	uint32 mUsage;
+	#endif
+	
+	// This used to just store the upper checksum bits, because the lower bits are implied
+	// by the index of this entries list in the hash table.
+	// This was just to save space, because of the fairly large (8000ish) number of symbols.
+	// However, it makes the LookUpSymbol function a little bit slower because of the extra bit
+	// operations needed to compare, and also it makes the RemoveSymbol function more complex
+	// and slower because it has to search the whole symbol table in order to find the previous
+	// entry, so that the current one can be unlinked.
+	// In skate4, since we have the ability to unload qb's, this should reduce the requirement
+	// for such a large symbol table, so I decided to store the whole checksum instead.
+	// Simpler, faster and neater! Just uses a wee bit more space. Not a huge amount though,
+	// probably only 25K ish.
+    uint32 mNameChecksum;
+	
+	// Required so that all the symbols from a particular a qb can be unloaded.
+	// Also handy for debugging.
+	// Could perhaps make it a 16-bit index into an array of file names rather than a checksum,
+	// then it would pack next to the above mUsed and mType and they would all 3 only use up 4 bytes
+	// altogether rather than 8 ...
+	uint32 mSourceFileNameChecksum;
+
+    union
+    {
+        int mIntegerValue;
+        float mFloatValue;
+        char *mpString;
+        char *mpLocalString;
+        CPair *mpPair;
+        CVector *mpVector;
+        CStruct *mpStructure;
+#ifdef __PLAT_NGC__
+        uint32 mScriptOffset;
+#else
+        uint8 *mpScript;
+#endif		// __PLAT_NGC__
+        bool (*mpCFunction)(CStruct *pParams, CScript *pCScript);
+        CArray *mpArray;
+        uint32 mChecksum; // Used when Type is ESYMBOLTYPE_NAME
+		uint32 mUnion; // For when all the above need to be zeroed 
+    };
+
+    CSymbolTableEntry *mpNext;
+
+	CSymbolTableEntry();
+	#ifdef __NOPT_ASSERT__
+	~CSymbolTableEntry();
+	#endif
+
+	// These cannot be defined because it would cause a cyclic dependency, because
+	// a CSymbolTableEntry member function can't create things. So declare them but leave them undefined
+	// so that it will not link if they are attempted to be used.
+	CSymbolTableEntry( const CSymbolTableEntry& rhs );
+	CSymbolTableEntry& operator=( const CSymbolTableEntry& rhs );
+	
+	void Reset();
+};
+
+void CreateSymbolHashTable();
+void DestroySymbolHashTable();
+CSymbolTableEntry *LookUpSymbol(uint32 checksum);
+CSymbolTableEntry *LookUpSymbol(const char *p_name);
+CSymbolTableEntry *Resolve(uint32 checksum);
+CSymbolTableEntry *Resolve(const char *p_name);
+void RemoveSymbol(CSymbolTableEntry *p_sym);
+CSymbolTableEntry *CreateNewSymbolEntry(uint32 checksum);
+CSymbolTableEntry *GetNextSymbolTableEntry(CSymbolTableEntry *p_sym=NULL);
+
+float GetFloat(uint32 checksum, EAssertType assert=NO_ASSERT);
+float GetFloat(const char *p_name, EAssertType assert=NO_ASSERT);
+int GetInteger(uint32 checksum, EAssertType assert=NO_ASSERT);
+int GetInteger(const char *p_name, EAssertType assert=NO_ASSERT);
+uint32 GetChecksum(uint32 checksum, EAssertType assert=NO_ASSERT);
+uint32 GetChecksum(const char *p_name, EAssertType assert=NO_ASSERT);
+const char *GetString(uint32 checksum, EAssertType assert=NO_ASSERT);
+const char *GetString(const char *p_name, EAssertType assert=NO_ASSERT);
+const char *GetLocalString(uint32 checksum, EAssertType assert=NO_ASSERT);
+const char *GetLocalString(const char *p_name, EAssertType assert=NO_ASSERT);
+CVector *GetVector(uint32 checksum, EAssertType assert=NO_ASSERT);
+CVector *GetVector(const char *p_name, EAssertType assert=NO_ASSERT);
+CPair *GetPair(uint32 checksum, EAssertType assert=NO_ASSERT);
+CPair *GetPair(const char *p_name, EAssertType assert=NO_ASSERT);
+CStruct *GetStructure(uint32 checksum, EAssertType assert=NO_ASSERT);
+CStruct *GetStructure(const char *p_name, EAssertType assert=NO_ASSERT);
+CArray *GetArray(uint32 checksum, EAssertType assert=NO_ASSERT);
+CArray *GetArray(const char *p_name, EAssertType assert=NO_ASSERT);
+bool (*GetCFunc(uint32 checksum, EAssertType assert=NO_ASSERT))(CStruct *, CScript *);
+bool (*GetCFunc(const char *p_name, EAssertType assert=NO_ASSERT))(CStruct *, CScript *);
+
+///////////////////////////////////////////////////////////////////////////////////
+// TODO: Remove these next functions at some point.
+// They are only included to provide back compatibility with the old code without
+// having to change thousands of occurrences of calls to GetInt
+int GetInt(uint32 checksum, bool assert=true);
+int GetInt(const char *p_name, bool assert=true);
+bool (*GetCFuncPointer(uint32 checksum, EAssertType assert=NO_ASSERT))(CStruct *, CScript *);
+bool (*GetCFuncPointer(const char *p_name, EAssertType assert=NO_ASSERT))(CStruct *, CScript *);
+///////////////////////////////////////////////////////////////////////////////////
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_SYMBOLTABLE_H
+
diff --git a/Code/Gel/Scripting/symboltype.cpp b/Code/Gel/Scripting/symboltype.cpp
new file mode 100644
index 0000000..d2f5ced
--- /dev/null
+++ b/Code/Gel/Scripting/symboltype.cpp
@@ -0,0 +1,75 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// symboltype.cpp		KSH 22 Oct 2001
+//
+// Just a utility function for printing a type name.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+
+namespace Script
+{
+
+#ifdef __NOPT_ASSERT__
+const char *GetTypeName(uint8 type)
+{
+	switch (type)
+	{
+	case ESYMBOLTYPE_NONE:
+		return "None";
+		break;
+	case ESYMBOLTYPE_INTEGER:
+		return "Integer";
+		break;
+    case ESYMBOLTYPE_FLOAT:
+		return "Float";
+		break;
+    case ESYMBOLTYPE_STRING:
+		return "String";
+		break;
+    case ESYMBOLTYPE_LOCALSTRING:
+		return "Local String";
+		break;
+    case ESYMBOLTYPE_PAIR:
+		return "Pair";
+		break;
+    case ESYMBOLTYPE_VECTOR:
+		return "Vector";
+		break;
+    case ESYMBOLTYPE_QSCRIPT:
+		return "Script";
+		break;
+    case ESYMBOLTYPE_CFUNCTION:
+		return "C-Function";
+		break;
+    case ESYMBOLTYPE_MEMBERFUNCTION:
+		return "Member Function";
+		break;
+    case ESYMBOLTYPE_STRUCTURE:
+		return "Structure";
+		break;
+    case ESYMBOLTYPE_STRUCTUREPOINTER:
+		return "Structure Pointer";
+		break;
+    case ESYMBOLTYPE_ARRAY:
+		return "Array";
+		break;
+    case ESYMBOLTYPE_NAME:
+		return "Name";
+		break;
+	default:
+		return "Unknown";
+		break;		
+	}
+}
+#else
+const char *GetTypeName(uint8 type)
+{
+	return "(Hey, don't use GetTypeName on the CD!)";
+}
+#endif
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/symboltype.h b/Code/Gel/Scripting/symboltype.h
new file mode 100644
index 0000000..99e47b0
--- /dev/null
+++ b/Code/Gel/Scripting/symboltype.h
@@ -0,0 +1,76 @@
+#ifndef	__SCRIPTING_SYMBOLTYPE_H
+#define	__SCRIPTING_SYMBOLTYPE_H
+
+namespace Script
+{
+// Values for CSymbolTableEntry::mType and CComponent::mType
+enum ESymbolType
+{
+    ESYMBOLTYPE_NONE=0,
+    ESYMBOLTYPE_INTEGER,
+    ESYMBOLTYPE_FLOAT,
+    ESYMBOLTYPE_STRING,
+    ESYMBOLTYPE_LOCALSTRING,
+    ESYMBOLTYPE_PAIR,
+    ESYMBOLTYPE_VECTOR,
+    ESYMBOLTYPE_QSCRIPT,
+    ESYMBOLTYPE_CFUNCTION,
+    ESYMBOLTYPE_MEMBERFUNCTION,
+    ESYMBOLTYPE_STRUCTURE,
+	// ESYMBOLTYPE_STRUCTUREPOINTER is not really used any more. It is only supported as a valid
+	// type that can be sent to AddComponent, which is an old CStruct member function that is
+	// still supported for back compatibility. mType will never be ESYMBOLTYPE_STRUCTUREPOINTER
+    ESYMBOLTYPE_STRUCTUREPOINTER,
+    ESYMBOLTYPE_ARRAY,
+    ESYMBOLTYPE_NAME,
+	
+	// These symbols are just used for memory optimization by the
+	// CScriptStructure WriteToBuffer and ReadFromBuffer functions.
+	ESYMBOLTYPE_INTEGER_ONE_BYTE,
+	ESYMBOLTYPE_INTEGER_TWO_BYTES,
+	ESYMBOLTYPE_UNSIGNED_INTEGER_ONE_BYTE,
+	ESYMBOLTYPE_UNSIGNED_INTEGER_TWO_BYTES,
+	ESYMBOLTYPE_ZERO_INTEGER,
+	ESYMBOLTYPE_ZERO_FLOAT,
+	
+	// Warning! Don't exceed 256 entries, since Type is a uint8 in SSymbolTableEntry
+	// New warning! Don't exceed 64 entries, because the top two bits of the symbol
+	// type are used to indicate whether the name checksum has been compressed to
+	// a 8 or 16 bit index, when WriteToBuffer writes out parameter names.
+};
+
+// These get masked onto the symbol type in CScriptStructure::WriteToBuffer if
+// the following name checksum matches one in the lookup table. (Defined in compress.q)
+#define MASK_8_BIT_NAME_LOOKUP (1<<7)
+#define MASK_16_BIT_NAME_LOOKUP (1<<6)
+
+const char *GetTypeName(uint8 type);
+
+// TODO: Remove these at some point, they are just to allow compilation of the existing game code
+// without having to change all the occurrences.
+#define ESYMBOLTYPE_NONE                       Script::ESYMBOLTYPE_NONE                      
+#define ESYMBOLTYPE_INTEGER                    Script::ESYMBOLTYPE_INTEGER                   
+#define ESYMBOLTYPE_FLOAT                      Script::ESYMBOLTYPE_FLOAT                     
+#define ESYMBOLTYPE_STRING                     Script::ESYMBOLTYPE_STRING                    
+#define ESYMBOLTYPE_LOCALSTRING                Script::ESYMBOLTYPE_LOCALSTRING               
+#define ESYMBOLTYPE_PAIR                       Script::ESYMBOLTYPE_PAIR                      
+#define ESYMBOLTYPE_VECTOR                     Script::ESYMBOLTYPE_VECTOR                    
+#define ESYMBOLTYPE_QSCRIPT                    Script::ESYMBOLTYPE_QSCRIPT                   
+#define ESYMBOLTYPE_CFUNCTION                  Script::ESYMBOLTYPE_CFUNCTION                 
+#define ESYMBOLTYPE_MEMBERFUNCTION             Script::ESYMBOLTYPE_MEMBERFUNCTION            
+#define ESYMBOLTYPE_STRUCTURE                  Script::ESYMBOLTYPE_STRUCTURE                 
+#define ESYMBOLTYPE_STRUCTUREPOINTER           Script::ESYMBOLTYPE_STRUCTUREPOINTER          
+#define ESYMBOLTYPE_ARRAY                      Script::ESYMBOLTYPE_ARRAY                     
+#define ESYMBOLTYPE_NAME                       Script::ESYMBOLTYPE_NAME                      
+#define ESYMBOLTYPE_INTEGER_ONE_BYTE           Script::ESYMBOLTYPE_INTEGER_ONE_BYTE          
+#define ESYMBOLTYPE_INTEGER_TWO_BYTES          Script::ESYMBOLTYPE_INTEGER_TWO_BYTES         
+#define ESYMBOLTYPE_UNSIGNED_INTEGER_ONE_BYTE  Script::ESYMBOLTYPE_UNSIGNED_INTEGER_ONE_BYTE 
+#define ESYMBOLTYPE_UNSIGNED_INTEGER_TWO_BYTES Script::ESYMBOLTYPE_UNSIGNED_INTEGER_TWO_BYTES
+#define ESYMBOLTYPE_ZERO_INTEGER               Script::ESYMBOLTYPE_ZERO_INTEGER              
+#define ESYMBOLTYPE_ZERO_FLOAT                 Script::ESYMBOLTYPE_ZERO_FLOAT                
+
+
+
+}
+
+#endif // #ifndef	__SCRIPTING_SYMBOLTYPE_H
diff --git a/Code/Gel/Scripting/tokens.cpp b/Code/Gel/Scripting/tokens.cpp
new file mode 100644
index 0000000..c9774ec
--- /dev/null
+++ b/Code/Gel/Scripting/tokens.cpp
@@ -0,0 +1,237 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// tokens.cpp		KSH 22 Oct 2001
+//
+// Just a function for getting a token name.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#ifndef __CORE_DEFINES_H
+#include  // For __NOPT_ASSERT__
+#endif
+
+namespace Script
+{
+
+#ifdef __NOPT_ASSERT__
+const char *GetTokenName(EScriptToken token)
+{
+	switch (token)
+	{
+	case ESCRIPTTOKEN_ENDOFFILE:
+		return "ENDOFFILE";
+		break;
+	case ESCRIPTTOKEN_ENDOFLINE:
+		return "ENDOFLINE";
+		break;
+	case ESCRIPTTOKEN_ENDOFLINENUMBER:
+		return "ENDOFLINENUMBER";
+		break;
+	case ESCRIPTTOKEN_STARTSTRUCT:
+		return "{";
+		break;
+	case ESCRIPTTOKEN_ENDSTRUCT:
+		return "}";
+		break;
+	case ESCRIPTTOKEN_STARTARRAY:
+		return "[";
+		break;
+	case ESCRIPTTOKEN_ENDARRAY:
+		return "]";
+		break;
+	case ESCRIPTTOKEN_EQUALS:
+		return "=";
+		break;
+	case ESCRIPTTOKEN_DOT:
+		return ".";
+		break;
+	case ESCRIPTTOKEN_COMMA:
+		return ",";
+		break;
+	case ESCRIPTTOKEN_MINUS:
+		return "-";
+		break;
+	case ESCRIPTTOKEN_ADD:
+		return "+";
+		break;
+	case ESCRIPTTOKEN_DIVIDE:
+		return "/";
+		break;
+	case ESCRIPTTOKEN_MULTIPLY:
+		return "*";
+		break;
+	case ESCRIPTTOKEN_OPENPARENTH:
+		return "(";
+		break;
+	case ESCRIPTTOKEN_CLOSEPARENTH:
+		return ")";
+		break;
+	case ESCRIPTTOKEN_DEBUGINFO:
+		return "DEBUGINFO";
+		break;
+	case ESCRIPTTOKEN_SAMEAS:
+		return "==";
+		break;
+	case ESCRIPTTOKEN_LESSTHAN:
+		return "<";
+		break;
+	case ESCRIPTTOKEN_LESSTHANEQUAL:
+		return "<=";
+		break;
+	case ESCRIPTTOKEN_GREATERTHAN:
+		return ">";
+		break;
+	case ESCRIPTTOKEN_GREATERTHANEQUAL:
+		return ">=";
+		break;
+	case ESCRIPTTOKEN_NAME:
+		return "NAME";
+		break;
+	case ESCRIPTTOKEN_INTEGER:
+		return "INTEGER";
+		break;
+	case ESCRIPTTOKEN_HEXINTEGER:
+		return "HEXINTEGER";
+		break;
+    case ESCRIPTTOKEN_ENUM:
+		return "ENUM";
+		break;
+	case ESCRIPTTOKEN_FLOAT:
+		return "FLOAT";
+		break;
+	case ESCRIPTTOKEN_STRING:
+		return "STRING";
+		break;
+	case ESCRIPTTOKEN_LOCALSTRING:
+		return "LOCALSTRING";
+		break;
+	case ESCRIPTTOKEN_ARRAY:
+		return "ARRAY";
+		break;
+	case ESCRIPTTOKEN_VECTOR:
+		return "VECTOR";
+		break;
+	case ESCRIPTTOKEN_PAIR:
+		return "PAIR";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_BEGIN:
+		return "BEGIN";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_REPEAT:
+		return "REPEAT";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_BREAK:
+		return "BREAK";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_SCRIPT:
+		return "SCRIPT";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
+		return "ENDSCRIPT";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_IF:
+		return "IF";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_ELSE:
+		return "ELSE";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_ELSEIF:
+		return "ELSEIF";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_ENDIF:
+		return "ENDIF";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_RETURN:
+		return "RETURN";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_NOT:
+		return "NOT";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_SWITCH:
+		return "SWITCH";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_ENDSWITCH:
+		return "ENDSWITCH";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_CASE:
+		return "CASE";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_DEFAULT:
+		return "DEFAULT";
+		break;
+    case ESCRIPTTOKEN_UNDEFINED:
+		return "UNDEFINED";
+		break;
+	case ESCRIPTTOKEN_CHECKSUM_NAME:
+		return "CHECKSUM_NAME";
+		break;
+	case ESCRIPTTOKEN_KEYWORD_ALLARGS:
+		return "<...>";
+		break;
+	case ESCRIPTTOKEN_ARG:	
+		return "ARG";
+		break;
+		
+	case ESCRIPTTOKEN_KEYWORD_RANDOM:
+		return "RANDOM";
+		break;
+
+	case ESCRIPTTOKEN_KEYWORD_RANDOM2:
+		return "RANDOM2";
+		break;
+		
+	case ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT:
+		return "RANDOM_NO_REPEAT";
+		break;
+
+	case ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE:
+		return "RANDOM_PERMUTE";
+		break;
+		
+	case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE:
+		return "RANDOM_RANGE";
+		break;
+
+	case ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2:
+		return "RANDOM_RANGE2";
+		break;
+			
+	case ESCRIPTTOKEN_OR:
+		return "OR";
+		break;
+	case ESCRIPTTOKEN_AND:
+		return "AND";
+		break;
+	case ESCRIPTTOKEN_XOR:
+		return "XOR";
+		break;
+	
+	case ESCRIPTTOKEN_SHIFT_LEFT:
+		return "SHIFT_LEFT";
+		break;
+	case ESCRIPTTOKEN_SHIFT_RIGHT:
+		return "SHIFT_RIGHT";
+		break;
+		
+	case ESCRIPTTOKEN_COLON:
+		return "COLON";
+		break;
+		
+	case ESCRIPTTOKEN_RUNTIME_CFUNCTION:
+		return "RUNTIME-CFUNCTION";
+		break;
+		
+	case ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION:
+		return "RUNTIME-MEMBERFUNCTION";
+		break;
+			
+	default:
+		return "Unknown";
+		break;
+	}		
+}
+#endif // #ifdef __NOPT_ASSERT__
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/tokens.h b/Code/Gel/Scripting/tokens.h
new file mode 100644
index 0000000..ebea119
--- /dev/null
+++ b/Code/Gel/Scripting/tokens.h
@@ -0,0 +1,140 @@
+
+// This file gets included in the compiler tool code (qcomp.cpp) as well as
+// in the PS2 code. So whenever this file is modified, qcomp must be recompiled.
+// qcomp.exe is a PC executable that converts a .q file into a .qb file
+// for loading into the PS2.
+
+// The following are token values used to represent the various things in
+// the .qb.
+
+#ifndef __SCRIPTING_TOKENS_H
+#define __SCRIPTING_TOKENS_H
+
+namespace Script
+{
+// Note! Careful if inserting new token values here, because this file also gets
+// included in the qcomp tool.
+// If the numerical values of the symbols here change, all the .qb's must be
+// recompiled. This won't automatically happen, so everyone will have to be
+// told to delete their .qb's to force them to be recompiled.
+
+// When adding a new token value, it would be best to add it to the bottom, to
+// prevent having to tell everyone to recompile all the qb's.
+enum EScriptToken
+{
+	// Misc
+	ESCRIPTTOKEN_ENDOFFILE,			// 0
+	ESCRIPTTOKEN_ENDOFLINE,			// 1
+	ESCRIPTTOKEN_ENDOFLINENUMBER,   // 2
+	ESCRIPTTOKEN_STARTSTRUCT,       // 3
+	ESCRIPTTOKEN_ENDSTRUCT,         // 4
+	ESCRIPTTOKEN_STARTARRAY,        // 5
+	ESCRIPTTOKEN_ENDARRAY,          // 6
+	ESCRIPTTOKEN_EQUALS,            // 7
+	ESCRIPTTOKEN_DOT,               // 8
+	ESCRIPTTOKEN_COMMA,             // 9
+	ESCRIPTTOKEN_MINUS,             // 10
+	ESCRIPTTOKEN_ADD,               // 11
+	ESCRIPTTOKEN_DIVIDE,            // 12
+	ESCRIPTTOKEN_MULTIPLY,          // 13
+	ESCRIPTTOKEN_OPENPARENTH,       // 14
+	ESCRIPTTOKEN_CLOSEPARENTH,      // 15
+
+	// This is ignored by the interpreter.
+	// Allows inclusion of source level debugging info, eg line number.
+	ESCRIPTTOKEN_DEBUGINFO,			// 16
+
+	// Comparisons
+	ESCRIPTTOKEN_SAMEAS,			// 17
+	ESCRIPTTOKEN_LESSTHAN,			// 18
+	ESCRIPTTOKEN_LESSTHANEQUAL,     // 19
+	ESCRIPTTOKEN_GREATERTHAN,       // 20
+	ESCRIPTTOKEN_GREATERTHANEQUAL,  // 21
+
+	// Types
+	ESCRIPTTOKEN_NAME,				// 22
+	ESCRIPTTOKEN_INTEGER,			// 23
+	ESCRIPTTOKEN_HEXINTEGER,        // 24
+    ESCRIPTTOKEN_ENUM,              // 25
+	ESCRIPTTOKEN_FLOAT,             // 26
+	ESCRIPTTOKEN_STRING,            // 27
+	ESCRIPTTOKEN_LOCALSTRING,       // 28
+	ESCRIPTTOKEN_ARRAY,             // 29
+	ESCRIPTTOKEN_VECTOR,            // 30
+	ESCRIPTTOKEN_PAIR,				// 31
+
+	// Key words
+	ESCRIPTTOKEN_KEYWORD_BEGIN,		// 32
+	ESCRIPTTOKEN_KEYWORD_REPEAT,    // 33
+	ESCRIPTTOKEN_KEYWORD_BREAK,     // 34
+	ESCRIPTTOKEN_KEYWORD_SCRIPT,    // 35
+	ESCRIPTTOKEN_KEYWORD_ENDSCRIPT, // 36
+	ESCRIPTTOKEN_KEYWORD_IF,        // 37
+	ESCRIPTTOKEN_KEYWORD_ELSE,      // 38
+	ESCRIPTTOKEN_KEYWORD_ELSEIF,    // 39
+	ESCRIPTTOKEN_KEYWORD_ENDIF,		// 40
+	ESCRIPTTOKEN_KEYWORD_RETURN,	// 41
+    
+    ESCRIPTTOKEN_UNDEFINED,			// 42
+	
+	// For debugging					  
+	ESCRIPTTOKEN_CHECKSUM_NAME,		// 43
+	
+	// Token for the <...> symbol					
+	ESCRIPTTOKEN_KEYWORD_ALLARGS,	// 44
+	// Token that preceds a name when the name is enclosed in < > in the source.
+	ESCRIPTTOKEN_ARG,				// 45
+	
+	// A relative jump. Used to speed up if-else-endif and break statements, and
+	// used to jump to the end of lists of items in the random operator.
+	ESCRIPTTOKEN_JUMP,				// 46
+	// Precedes a list of items that are to be randomly chosen from.
+	ESCRIPTTOKEN_KEYWORD_RANDOM,    // 47
+
+	// Precedes two integers enclosed in parentheses.
+	ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE,	// 48
+	
+	// Only used internally by qcomp, never appears in a .qb
+	ESCRIPTTOKEN_AT,				// 49
+	
+	// Logical operators
+	ESCRIPTTOKEN_OR,				// 50
+	ESCRIPTTOKEN_AND,				// 51
+	ESCRIPTTOKEN_XOR,				// 52
+									
+	// Shift operators
+	ESCRIPTTOKEN_SHIFT_LEFT,		// 53
+	ESCRIPTTOKEN_SHIFT_RIGHT,		// 54
+	
+	// These versions use the Rnd2 function, for use in certain things so as not to mess up
+	// the determinism of the regular Rnd function in replays.
+	ESCRIPTTOKEN_KEYWORD_RANDOM2,		// 55
+	ESCRIPTTOKEN_KEYWORD_RANDOM_RANGE2, // 56
+	
+	ESCRIPTTOKEN_KEYWORD_NOT,			// 57
+	ESCRIPTTOKEN_KEYWORD_AND,			// 58
+	ESCRIPTTOKEN_KEYWORD_OR,            // 59
+	ESCRIPTTOKEN_KEYWORD_SWITCH,       	// 60
+	ESCRIPTTOKEN_KEYWORD_ENDSWITCH,   	// 61
+	ESCRIPTTOKEN_KEYWORD_CASE,          // 62
+	ESCRIPTTOKEN_KEYWORD_DEFAULT,		// 63
+
+	ESCRIPTTOKEN_KEYWORD_RANDOM_NO_REPEAT,	// 64
+	ESCRIPTTOKEN_KEYWORD_RANDOM_PERMUTE,	// 65
+
+	ESCRIPTTOKEN_COLON,		// 66
+	
+	// These are calculated at runtime in the game code by PreProcessScripts,
+	// so they never appear in a qb file.
+	ESCRIPTTOKEN_RUNTIME_CFUNCTION,	// 67
+	ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION, // 68
+	
+	// Warning! Do not exceed 256 entries, since these are stored in bytes.
+};
+
+const char *GetTokenName(EScriptToken token);
+
+} // namespace Script
+
+#endif // #ifndef __SCRIPTING_TOKENS_H
+
diff --git a/Code/Gel/Scripting/utils.cpp b/Code/Gel/Scripting/utils.cpp
new file mode 100644
index 0000000..de624af
--- /dev/null
+++ b/Code/Gel/Scripting/utils.cpp
@@ -0,0 +1,1775 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// utils.cpp		KSH 22 Oct 2001
+//
+// Misc script utility functions.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// Some defines that affect how PrintContents works:
+
+// If defined, then if a structure contains a reference to a global structure, PrintContents
+// will print the contents of that structure too.
+// (Added this define so that it can be disabled for viewing the mem card career-save structure,
+// which has lots of references and takes ages to print if they are all expanded)
+#define EXPAND_GLOBAL_STRUCTURE_REFERENCES
+
+// Enables a for-loop to slow down PrintContents so that printf can catch up.
+// (Needed for viewing big mem card structures)
+//#define SLOW_DOWN_PRINTCONTENTS
+
+namespace Script
+{
+
+static uint8 *sWriteCompressedName(uint8 *p_buffer, uint8 SymbolType, uint32 NameChecksum);
+static uint32 sIntegerWriteToBuffer(uint32 Name, int val, uint8 *p_buffer, uint32 BufferSize);
+static uint32 sFloatWriteToBuffer(uint32 Name, float val, uint8 *p_buffer, uint32 BufferSize);
+static uint32 sChecksumWriteToBuffer(uint32 Name, uint32 Checksum, uint8 *p_buffer, uint32 BufferSize);
+static uint32 sStringWriteToBuffer(uint32 Name, const char *pString, uint8 *p_buffer, uint32 BufferSize);
+static uint32 sLocalStringWriteToBuffer(uint32 Name, const char *pString, uint8 *p_buffer, uint32 BufferSize);
+static uint32 sPairWriteToBuffer(uint32 Name, CPair *pPair, uint8 *p_buffer, uint32 BufferSize);
+static uint32 sVectorWriteToBuffer(uint32 Name, CVector *pVector, uint8 *p_buffer, uint32 BufferSize);
+static uint32 sStructureWriteToBuffer(uint32 Name, CStruct *pStructure, uint8 *p_buffer, uint32 BufferSize, EAssertType assert);
+static uint32 sArrayWriteToBuffer(uint32 Name, CArray *pArray, uint8 *p_buffer, uint32 BufferSize, EAssertType assert);
+
+#ifdef __NOPT_ASSERT__
+static void   sDoIndent(int indent);
+
+static void sDoIndent(int indent)
+{
+	for (int i=0; iGetSize();
+	ESymbolType type=p_array->GetType();
+	
+	if (!indent)
+	{
+		printf("Contents of Array=\n");
+	}
+	
+	sDoIndent(indent);
+	printf("[\n");
+	indent+=3;
+	
+	int int_column_count=0;
+	for (int i=0; iGetInteger(i));
+			}
+			else
+			{
+				printf("%d\n",p_array->GetInteger(i));
+			}	
+			++int_column_count;
+			if (int_column_count==15)
+			{
+				printf("\n");
+				int_column_count=0;
+			}
+            break;
+        case ESYMBOLTYPE_FLOAT:
+            printf("%f\n",p_array->GetFloat(i));
+            break;
+        case ESYMBOLTYPE_STRING:
+		{
+			const char *p_string=p_array->GetString(i);
+			if (p_string)
+			{
+				printf("\"%s\"\n",p_string);
+			}	
+			else
+			{
+				printf("NULL string\n");
+			}	
+            break;
+		}	
+        case ESYMBOLTYPE_LOCALSTRING:
+		{
+			const char *p_string=p_array->GetLocalString(i);
+			if (p_string)
+			{
+				printf("'%s'\n",p_string);
+			}	
+			else
+			{
+				printf("NULL local string\n");
+			}	
+            break;
+		}	
+        case ESYMBOLTYPE_PAIR:
+		{
+			CPair *p_pair=p_array->GetPair(i);
+			if (p_pair)
+			{
+				printf("(%f,%f)\n",p_pair->mX,p_pair->mY);
+			}	
+			else
+			{
+				printf("NULL pair\n");
+			}	
+            break;
+		}	
+        case ESYMBOLTYPE_VECTOR:
+		{
+			CVector *p_vector=p_array->GetVector(i);
+			if (p_vector)
+			{
+				printf("(%f,%f,%f)\n",p_vector->mX,p_vector->mY,p_vector->mZ);
+			}	
+			else
+			{
+				printf("NULL vector\n");
+			}	
+            break;
+		}	
+        case ESYMBOLTYPE_NAME:
+            printf("%s\n",FindChecksumName(p_array->GetChecksum(i)));
+            break;
+        case ESYMBOLTYPE_STRUCTURE:
+		{
+			CStruct *p_structure=p_array->GetStructure(i);
+			if (p_structure)
+			{
+				PrintContents(p_structure,indent);
+			}	
+			else
+			{
+				printf("NULL structure\n");
+			}	
+            break;
+		}	
+		case ESYMBOLTYPE_ARRAY:
+		{
+			CArray *p_a=p_array->GetArray(i);
+			if (p_a)
+			{
+				PrintContents(p_a,indent);
+			}	
+			else
+			{
+				printf("NULL array\n");
+			}	
+            break;
+		}	
+			
+        default:
+            Dbg_MsgAssert(0,("Bad array type"));
+            break;
+        }
+
+		#ifdef SLOW_DOWN_PRINTCONTENTS
+		// A delay to let printf catch up so that it doesn't skip stuff when printing big arrays.		
+		for (int i=0; i<10000; ++i);
+		#endif
+    }
+    sDoIndent(indent-3);
+	printf("]\n");
+#endif	
+}
+
+void PrintContents(const CStruct *p_structure, int indent)
+{
+#ifdef __NOPT_ASSERT__
+	Dbg_MsgAssert(p_structure,("NULL p_structure"));
+	
+	if (!indent)
+	{
+		printf("Contents of CStruct=\n");
+	}
+	
+	sDoIndent(indent);
+	printf("{\n");
+	indent+=3;
+		
+    CComponent *p_comp=p_structure->GetNextComponent(NULL);
+    while (p_comp)
+    {
+		sDoIndent(indent);
+		
+		if (p_comp->mNameChecksum)
+		{
+			printf("%s=",FindChecksumName(p_comp->mNameChecksum));
+		}	
+            
+        switch (p_comp->mType)
+        {
+        case ESYMBOLTYPE_INTEGER:
+            printf("%d\n",p_comp->mIntegerValue);
+            break;
+        case ESYMBOLTYPE_FLOAT:
+            printf("%f\n",p_comp->mFloatValue);
+            break;
+        case ESYMBOLTYPE_STRING:
+            printf("\"%s\"\n",p_comp->mpString);
+            break;
+        case ESYMBOLTYPE_LOCALSTRING:
+            printf("'%s'\n",p_comp->mpLocalString);
+            break;
+        case ESYMBOLTYPE_PAIR:
+            printf("(%f,%f)\n",p_comp->mpPair->mX,p_comp->mpPair->mY);
+            break;
+        case ESYMBOLTYPE_VECTOR:
+            printf("(%f,%f,%f)\n",p_comp->mpVector->mX,p_comp->mpVector->mY,p_comp->mpVector->mZ);
+            break;
+        case ESYMBOLTYPE_STRUCTURE:
+			printf("\n");
+			PrintContents(p_comp->mpStructure,indent);
+            break;
+        case ESYMBOLTYPE_NAME:
+            printf("%s\n",FindChecksumName(p_comp->mChecksum));
+			
+			#ifdef EXPAND_GLOBAL_STRUCTURE_REFERENCES
+			if (p_comp->mNameChecksum==0)
+			{
+				// It's an un-named name. Maybe it's a global structure ... 
+				// If so, print its contents too, which is handy for debugging.
+			    CSymbolTableEntry *p_entry=Resolve(p_comp->mChecksum);
+				if (p_entry && p_entry->mType==ESYMBOLTYPE_STRUCTURE)
+				{
+					sDoIndent(indent);
+					printf("... Defined in %s ...\n",FindChecksumName(p_entry->mSourceFileNameChecksum));
+					Dbg_MsgAssert(p_entry->mpStructure,("NULL p_entry->mpStructure"));
+					PrintContents(p_entry->mpStructure,indent);
+				}
+			}		
+			#endif
+            break;
+        case ESYMBOLTYPE_QSCRIPT:
+			printf("(A script)\n"); // TODO
+			break;
+		case ESYMBOLTYPE_ARRAY:
+			printf("\n");
+			PrintContents(p_comp->mpArray,indent);
+            break;
+        default:
+			printf("Component of type '%s', value 0x%08x\n",GetTypeName(p_comp->mType),p_comp->mUnion);
+            //Dbg_MsgAssert(0,("Bad p_comp->Type"));
+            break;
+        }
+        p_comp=p_structure->GetNextComponent(p_comp);
+		
+		#ifdef SLOW_DOWN_PRINTCONTENTS
+		// A delay to let printf catch up so that it doesn't skip stuff when printing big arrays.		
+		for (int i=0; i<1000000; ++i);
+		#endif
+    }
+	
+    sDoIndent(indent-3);
+	printf("}\n");
+#endif
+}
+
+static uint8 *sWriteCompressedName(uint8 *p_buffer, uint8 symbolType, uint32 nameChecksum)
+{
+	#ifdef __PLAT_WN32__
+	// If compiling on PC, the lookup table will not exist, so do no compression.
+	*p_buffer++=symbolType;
+	return Write4Bytes(p_buffer,nameChecksum);
+	#else
+	
+	// Check if the checksum is in the small array.
+	CArray *p_table=GetArray(0x35115a20/*WriteToBuffer_CompressionLookupTable_8*/);
+	int size=p_table->GetSize();
+	Dbg_MsgAssert(size<256,("Size of WriteToBuffer_CompressionLookupTable_8 too big"));
+	
+	for (int i=0; iGetChecksum(i)==nameChecksum)
+		{
+			// It is in the array! So write out a 1 byte index.
+			*p_buffer++=symbolType | MASK_8_BIT_NAME_LOOKUP;
+			*p_buffer++=i;
+			return p_buffer;
+		}
+	}		
+	
+	// Check if the checksum is in the big array.
+	p_table=GetArray(0x25231f42/*WriteToBuffer_CompressionLookupTable_16*/);
+	size=p_table->GetSize();
+	Dbg_MsgAssert(size<65536,("Size of WriteToBuffer_CompressionLookupTable_16 too big"));
+	
+	for (int i=0; iGetChecksum(i)==nameChecksum)
+		{
+			// It is in the array! So write out a 2 byte index.
+			*p_buffer++=symbolType | MASK_16_BIT_NAME_LOOKUP;
+			return Write2Bytes(p_buffer,i);
+		}
+	}		
+	
+	// Oh well, it is not in either array, so write out the whole 4 byte checksum.
+	*p_buffer++=symbolType;
+	return Write4Bytes(p_buffer,nameChecksum);
+	#endif
+}
+
+static uint32 sIntegerWriteToBuffer(uint32 name, int val, uint8 *p_buffer, uint32 bufferSize)
+{
+	uint8 *p_buffer_before=p_buffer;
+		
+	// Choose what type of symbol to use so as to minimize the space used.
+	
+	ESymbolType symbol_type=ESYMBOLTYPE_INTEGER; // Default to a full 4 bytes.
+	
+	if (val==0)
+	{
+		symbol_type=ESYMBOLTYPE_ZERO_INTEGER;
+	}
+	else if (val>=0 && val<=255)
+	{
+		symbol_type=ESYMBOLTYPE_UNSIGNED_INTEGER_ONE_BYTE;
+	}
+	else if (val>=0 && val<=65535)	
+	{
+		symbol_type=ESYMBOLTYPE_UNSIGNED_INTEGER_TWO_BYTES;
+	}
+	else if (val>=-128 && val<=127)
+	{
+		symbol_type=ESYMBOLTYPE_INTEGER_ONE_BYTE;
+	}
+	else if (val>=-32768 && val<=32767)
+	{
+		symbol_type=ESYMBOLTYPE_INTEGER_TWO_BYTES;
+	}
+
+	
+	uint8 p_temp[20];
+	uint8 *p_end=p_temp;
+	 
+	switch (symbol_type)
+	{
+		case ESYMBOLTYPE_INTEGER:
+			// Write out the component into p_temp
+			p_end=sWriteCompressedName(p_temp,symbol_type,name);
+			p_end=Write4Bytes(p_end,(uint32)val);
+			break;
+		
+		case ESYMBOLTYPE_INTEGER_ONE_BYTE:
+		case ESYMBOLTYPE_UNSIGNED_INTEGER_ONE_BYTE:
+			// Write out the component into p_temp
+			p_end=sWriteCompressedName(p_temp,symbol_type,name);
+			*p_end++=(uint8)val;
+			break;
+			
+		case ESYMBOLTYPE_INTEGER_TWO_BYTES:
+		case ESYMBOLTYPE_UNSIGNED_INTEGER_TWO_BYTES:
+			// Write out the component.
+			p_end=sWriteCompressedName(p_temp,symbol_type,name);
+			p_end=Write2Bytes(p_end,(uint16)val);
+			break;
+			
+		case ESYMBOLTYPE_ZERO_INTEGER:
+			// Write out the component.
+			p_end=sWriteCompressedName(p_temp,symbol_type,name);
+			break;
+			
+		default:
+			Dbg_MsgAssert(0,("Bad symbol_type"));
+			break;
+	}
+
+	uint32 bytes_written_to_temp=p_end-p_temp;
+	
+	// Check that there is enough space before doing any writing.
+	if (bufferSize < bytes_written_to_temp)
+	{
+		return 0;
+	}
+
+	uint8 *p_source=p_temp;
+	for (uint32 i=0; imX);
+	p_buffer=Write4Bytes(p_buffer,p_pair->mY);
+	
+	return p_buffer-p_buffer_before;
+}
+
+static uint32 sVectorWriteToBuffer(uint32 name, CVector *p_vector, uint8 *p_buffer, uint32 bufferSize)
+{
+	Dbg_MsgAssert(p_vector,("NULL p_vector sent to sVectorWriteToBuffer"));
+	uint8 *p_buffer_before=p_buffer;
+	
+	// Check that there is enough space before doing any writing.
+	if (bufferSize<17)
+	{
+		return 0;
+	}
+		
+	// Write out the component.
+	p_buffer=sWriteCompressedName(p_buffer,ESYMBOLTYPE_VECTOR,name);
+	p_buffer=Write4Bytes(p_buffer,p_vector->mX);
+	p_buffer=Write4Bytes(p_buffer,p_vector->mY);
+	p_buffer=Write4Bytes(p_buffer,p_vector->mZ);
+	
+	return p_buffer-p_buffer_before;
+}
+
+static uint32 sStructureWriteToBuffer(uint32 name, CStruct *p_structure, uint8 *p_buffer, uint32 bufferSize, EAssertType assert)
+{
+	Dbg_MsgAssert(p_structure,("NULL p_structure sent to sStructureWriteToBuffer"));
+	uint8 *p_buffer_before=p_buffer;
+	
+	// Check that there is enough space before doing any writing.
+	if (bufferSize<5)
+	{
+		return 0;
+	}
+		
+	// Write out the type and name
+	p_buffer=sWriteCompressedName(p_buffer,ESYMBOLTYPE_STRUCTURE,name);
+	int name_bytes_written=p_buffer-p_buffer_before;
+	
+	// That's name_bytes_written bytes written out successfully, but maybe the writing out of the structure will fail ...
+	
+	uint32 structure_bytes_written=WriteToBuffer(p_structure,p_buffer,bufferSize-name_bytes_written,assert);
+	// If writing out the structure failed, return 0.
+	if (!structure_bytes_written)
+	{
+		return 0;
+	}
+	
+	// Otherwise return the total bytes written.
+	return name_bytes_written+structure_bytes_written;
+}
+
+static uint32 sArrayWriteToBuffer(uint32 name, CArray *p_array, uint8 *p_buffer, uint32 bufferSize, EAssertType assert)
+{
+	Dbg_MsgAssert(p_array,("NULL p_array sent to sArrayWriteToBuffer"));
+	uint8 *p_buffer_before=p_buffer;
+	
+	// Check that there is enough space before doing any writing.
+	if (bufferSize<5)
+	{
+		return 0;
+	}
+
+	// Write out the type and name
+	p_buffer=sWriteCompressedName(p_buffer,ESYMBOLTYPE_ARRAY,name);
+	int name_bytes_written=p_buffer-p_buffer_before;
+	
+	// That's name_bytes_written bytes written out successfully, but maybe the writing out of the array will fail ...
+	
+	uint32 array_bytes_written=WriteToBuffer(p_array,p_buffer,bufferSize-name_bytes_written,assert);
+	// If writing out the array failed, return 0.
+	if (!array_bytes_written)
+	{
+		return 0;
+	}
+	
+	// Otherwise return the total bytes written.
+	return name_bytes_written+array_bytes_written;
+}	
+
+// Writes the contents of the structure to a buffer.
+// The information is outputted in a byte-packed format, so p_buffer does
+// not need to be aligned.
+// The buffer can then be used to regenerate the original structure by
+// passing it to ReadFromBuffer.
+// Passed the size of the buffer so that it can check if there is enough space.
+// Returns the number of bytes that it actually wrote.
+//
+// If there was not enough space, and assert is NO_ASSERT, it will return a count of 0.
+//
+uint32 WriteToBuffer(CStruct *p_structure, uint8 *p_buffer, uint32 bufferSize, EAssertType assert)
+{
+	Dbg_MsgAssert(p_buffer,("NULL p_buffer sent to WriteToBuffer"));
+	uint32 bytes_left=bufferSize;
+
+	// Scan through the components adding each to the buffer.	
+    CComponent *p_comp=NULL;
+	if (p_structure)
+	{
+		p_comp=p_structure->GetNextComponent(NULL);
+	}
+		
+    while (p_comp)
+    {
+		uint32 component_bytes_written=0;
+		
+        switch (p_comp->mType)
+        {
+        case ESYMBOLTYPE_INTEGER:
+			component_bytes_written=sIntegerWriteToBuffer(p_comp->mNameChecksum,p_comp->mIntegerValue,p_buffer,bytes_left);
+			break;
+			
+        case ESYMBOLTYPE_FLOAT:
+			component_bytes_written=sFloatWriteToBuffer(p_comp->mNameChecksum,p_comp->mFloatValue,p_buffer,bytes_left);
+			break;
+
+        case ESYMBOLTYPE_NAME:
+			component_bytes_written=sChecksumWriteToBuffer(p_comp->mNameChecksum,p_comp->mChecksum,p_buffer,bytes_left);
+			break;
+			
+        case ESYMBOLTYPE_STRING:
+			component_bytes_written=sStringWriteToBuffer(p_comp->mNameChecksum,p_comp->mpString,p_buffer,bytes_left);
+			break;
+
+        case ESYMBOLTYPE_LOCALSTRING:
+			component_bytes_written=sLocalStringWriteToBuffer(p_comp->mNameChecksum,p_comp->mpLocalString,p_buffer,bytes_left);
+			break;
+			
+        case ESYMBOLTYPE_PAIR:
+			component_bytes_written=sPairWriteToBuffer(p_comp->mNameChecksum,p_comp->mpPair,p_buffer,bytes_left);
+			break;
+			
+        case ESYMBOLTYPE_VECTOR:
+			component_bytes_written=sVectorWriteToBuffer(p_comp->mNameChecksum,p_comp->mpVector,p_buffer,bytes_left);
+			break;
+
+        case ESYMBOLTYPE_STRUCTURE:
+			component_bytes_written=sStructureWriteToBuffer(p_comp->mNameChecksum,p_comp->mpStructure,p_buffer,bytes_left,assert);
+			break;
+
+        case ESYMBOLTYPE_ARRAY:
+			component_bytes_written=sArrayWriteToBuffer(p_comp->mNameChecksum,p_comp->mpArray,p_buffer,bytes_left,assert);
+			break;
+			
+		case ESYMBOLTYPE_QSCRIPT:	
+			component_bytes_written=sChecksumWriteToBuffer(p_comp->mNameChecksum,CRCD(0xb9c4f664,"InlineScript"),p_buffer,bytes_left);
+            break;
+			
+        default:
+            Dbg_MsgAssert(0,("Component type of '%s' is not supported in WriteToBuffer",GetTypeName(p_comp->mType)));
+            break;
+        }
+		
+		// If any of the above writes failed due to lack of space, then bail out too.
+		if (!component_bytes_written)
+		{
+			if (assert)
+			{
+				Dbg_MsgAssert(0,("WriteToBuffer: Buffer not big enough"));
+			}	
+			return 0;
+		}
+			
+		bytes_left-=component_bytes_written;
+		p_buffer+=component_bytes_written;
+		
+        p_comp=p_structure->GetNextComponent(p_comp);
+    }
+
+	// Add the terminating ESYMBOLTYPE_NONE
+	if (!bytes_left)
+	{
+		// Aargh! Not enough room left for the final byte!
+		if (assert)
+		{
+			Dbg_MsgAssert(0,("WriteToBuffer: Buffer not big enough"));
+		}	
+		return 0;
+	}	
+	*p_buffer++=ESYMBOLTYPE_NONE;
+	--bytes_left;
+	
+	// Return how many bytes got written.
+	return bufferSize-bytes_left;
+}
+
+// Calculates how big a buffer will need to be to hold a structure using WriteToBuffer.
+uint32 CalculateBufferSize(CStruct *p_structure, uint32 tempBufferSize)
+{
+	Dbg_MsgAssert(p_structure,("NULL p_structure"));
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap()); // Use the temporary heap
+	
+	uint8 *p_temp=(uint8*)Mem::Malloc(tempBufferSize);
+	Dbg_MsgAssert(p_temp,("Could not allocate temporary buffer"));
+	uint32 space_required=WriteToBuffer(p_structure,p_temp,tempBufferSize);
+	Mem::Free(p_temp);
+	Mem::Manager::sHandle().PopContext();
+
+	return space_required;
+}
+
+// Sets the structure's contents using the passed buffer, which was generated
+// by WriteToBuffer.
+// If the structure contained anything to begin with, it will get cleared.	
+//
+// Returns a pointer to after the terminating ESYMBOLTYPE_NONE, required
+// when this function calls itself.
+uint8 *ReadFromBuffer(CStruct *p_structure, uint8 *p_buffer)
+{
+	Dbg_MsgAssert(p_structure,("NULL p_structure"));
+	Dbg_MsgAssert(p_buffer,("NULL p_buffer sent to ReadFromBuffer"));
+	float zero_float=0.0f;
+
+	// First clear anything currently in the structure.
+	p_structure->Clear();
+
+	// Make sure we're using the script heap.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	
+	// Scan through the buffer adding the components until ESYMBOLTYPE_NONE is reached.
+	while (true)
+    {
+		float x,y,z;
+		const char *p_string;
+		CStruct *p_struct;
+		CArray *p_array;
+		
+		// Get the type and the name checksum.
+		ESymbolType type=(ESymbolType)*p_buffer++;
+		if (type==ESYMBOLTYPE_NONE)
+		{
+			// All done.
+			break;
+		}
+
+		// Get the name checksum, which may be stored as an index into a table of checksums
+		// to save space.
+		uint32 name=0;
+		if (type&MASK_8_BIT_NAME_LOOKUP)			
+		{
+			Dbg_MsgAssert(!(type&MASK_16_BIT_NAME_LOOKUP),("Eh? Both lookup-table flags set ?"));
+			
+			#ifdef __PLAT_WN32__
+			// The lookup table is not loaded when compiling on PC
+			name=CRCD(0xef5f3f41,"CompressedName");
+			#else
+			CArray *p_table=GetArray(0x35115a20/*WriteToBuffer_CompressionLookupTable_8*/);
+			name=p_table->GetChecksum(*p_buffer);
+			#endif
+			++p_buffer;
+		}
+		else if (type&MASK_16_BIT_NAME_LOOKUP)			
+		{
+			#ifdef __PLAT_WN32__
+			name=CRCD(0xef5f3f41,"CompressedName");
+			#else
+			CArray *p_table=GetArray(0x25231f42/*WriteToBuffer_CompressionLookupTable_16*/);
+			name=p_table->GetChecksum(Read2Bytes(p_buffer).mUInt);
+			#endif
+			p_buffer+=2;
+		}
+		else
+		{
+			// It's not stored as an index, but as the full 4 byte checksum.
+			name=Read4Bytes(p_buffer).mChecksum;
+			p_buffer+=4;
+		}
+		type=(ESymbolType)( ((uint32)type) & ~(MASK_8_BIT_NAME_LOOKUP | MASK_16_BIT_NAME_LOOKUP) );
+			
+		
+        switch (type)
+        {
+        case ESYMBOLTYPE_INTEGER:
+			p_structure->AddInteger(name,Read4Bytes(p_buffer).mInt);
+			p_buffer+=4;
+            break;
+        case ESYMBOLTYPE_FLOAT:
+			p_structure->AddFloat(name,Read4Bytes(p_buffer).mFloat);
+			p_buffer+=4;
+            break;
+        case ESYMBOLTYPE_NAME:
+			p_structure->AddChecksum(name,Read4Bytes(p_buffer).mChecksum);
+			p_buffer+=4;
+            break;
+		case ESYMBOLTYPE_ZERO_FLOAT:
+			p_structure->AddFloat(name,zero_float);
+			break;	
+		case ESYMBOLTYPE_ZERO_INTEGER:
+			p_structure->AddInteger(name,0);
+			break;	
+		case ESYMBOLTYPE_INTEGER_ONE_BYTE:
+			p_structure->AddInteger(name,*(sint8*)p_buffer++);
+			break;
+		case ESYMBOLTYPE_UNSIGNED_INTEGER_ONE_BYTE:
+			p_structure->AddInteger(name,*p_buffer++);
+			break;
+		case ESYMBOLTYPE_INTEGER_TWO_BYTES:
+			p_structure->AddInteger(name,Read2Bytes(p_buffer).mInt);
+			p_buffer+=2;
+			break;	
+		case ESYMBOLTYPE_UNSIGNED_INTEGER_TWO_BYTES:
+			p_structure->AddInteger(name,Read2Bytes(p_buffer).mUInt);
+			p_buffer+=2;
+			break;	
+        case ESYMBOLTYPE_STRING:
+			p_string=(const char *)p_buffer;
+			p_structure->AddString(name,p_string);
+			p_buffer+=strlen(p_string);
+			++p_buffer; // Skip over the terminator too.
+            break;
+        case ESYMBOLTYPE_LOCALSTRING:
+			p_string=(const char *)p_buffer;
+			p_structure->AddLocalString(name,p_string);
+			p_buffer+=strlen(p_string);
+			++p_buffer; // Skip over the terminator too.
+            break;
+        case ESYMBOLTYPE_PAIR:
+			x=Read4Bytes(p_buffer).mFloat;
+			p_buffer+=4;
+			y=Read4Bytes(p_buffer).mFloat;
+			p_buffer+=4;
+			p_structure->AddPair(name,x,y);
+            break;
+        case ESYMBOLTYPE_VECTOR:
+			x=Read4Bytes(p_buffer).mFloat;
+			p_buffer+=4;
+			y=Read4Bytes(p_buffer).mFloat;
+			p_buffer+=4;
+			z=Read4Bytes(p_buffer).mFloat;
+			p_buffer+=4;
+			p_structure->AddVector(name,x,y,z);
+            break;
+		case ESYMBOLTYPE_STRUCTURE:
+			// Create a new structure, and fill it in by calling this function recursively.
+			p_struct=new CStruct;
+			p_buffer=ReadFromBuffer(p_struct,p_buffer);
+			// Add the new component.
+			p_structure->AddStructurePointer(name,p_struct);
+			break;	
+		case ESYMBOLTYPE_ARRAY:
+			// Create a new array, and fill it in using the array's ReadFromBuffer.
+			p_array=new CArray;
+			p_buffer=ReadFromBuffer(p_array,p_buffer);
+			// Add the new component.
+			p_structure->AddArrayPointer(name,p_array);
+			break;	
+        default:
+            Dbg_MsgAssert(0,("Unsupported component type of '%s' encountered in ReadFromBuffer",GetTypeName(type)));
+            break;
+        }
+    }
+
+	Mem::Manager::sHandle().PopContext();
+	
+	return p_buffer;
+}
+
+// Writes the contents of the array to a buffer.
+// The information is outputted in a byte-packed format, so p_buffer does
+// not need to be aligned.
+// The buffer can then be used to regenerate the original array by
+// passing it to ReadFromBuffer.
+// Passed the size of the buffer so that it can check if there is enough space.
+// Returns the number of bytes that it actually wrote.
+//
+// If there was not enough space, and assert is false, it will return a count of 0.
+//
+uint32 WriteToBuffer(CArray *p_array, uint8 *p_buffer, uint32 bufferSize, EAssertType assert)
+{
+	Dbg_MsgAssert(p_array,("NULL p_array"));
+	Dbg_MsgAssert(p_buffer,("NULL p_buffer sent to WriteToBuffer"));
+	uint32 bytes_left=bufferSize;
+
+	if (bytes_left<3)
+	{
+		if (assert)
+		{
+			Dbg_MsgAssert(0,("WriteToBuffer: Buffer not big enough"));
+		}	
+		return 0;
+	}
+	
+	ESymbolType type=p_array->GetType();
+	uint32 size=p_array->GetSize();
+	
+	*p_buffer++=type;
+
+	// Easy to change WriteToBuffer to support 4 byte sizes, but keeping as 2 for the moment for
+	// backwards compatibility.
+	Dbg_MsgAssert(size<0x10000,("Size of array too big, currently only 2 bytes used to store size in WriteToBuffer ..."));
+	p_buffer=Write2Bytes(p_buffer,size);
+	bytes_left-=3;
+	
+	switch (type)
+	{
+        case ESYMBOLTYPE_INTEGER:
+        case ESYMBOLTYPE_FLOAT:
+        case ESYMBOLTYPE_NAME:
+		{
+			if (size*4>bytes_left)
+			{
+				if (assert)
+				{
+					Dbg_MsgAssert(0,("WriteToBuffer: Buffer not big enough"));
+				}	
+				return 0;
+			}
+			
+			uint32 *p_data=p_array->GetArrayPointer();
+			for (uint32 i=0; iGetArrayPointer();
+			for (uint32 i=0; ibytes_left)
+			{
+				if (assert)
+				{
+					Dbg_MsgAssert(0,("WriteToBuffer: Buffer not big enough"));
+				}	
+				return 0;
+			}
+			
+			CPair **pp_pairs=(CPair**)p_array->GetArrayPointer();
+			Dbg_MsgAssert(pp_pairs,("NULL pp_pairs ?"));
+			
+			for (uint32 i=0; imX);
+				p_buffer=Write4Bytes(p_buffer,p_pair->mY);
+				++pp_pairs;
+			}
+			
+			bytes_left-=size*8;
+			break;
+		}
+			
+        case ESYMBOLTYPE_VECTOR:
+		{
+			if (size*12>bytes_left)
+			{
+				if (assert)
+				{
+					Dbg_MsgAssert(0,("WriteToBuffer: Buffer not big enough"));
+				}	
+				return 0;
+			}
+			
+			CVector **pp_vectors=(CVector**)p_array->GetArrayPointer();
+			Dbg_MsgAssert(pp_vectors,("NULL pp_vectors ?"));
+			
+			for (uint32 i=0; imX);
+				p_buffer=Write4Bytes(p_buffer,p_vector->mY);
+				p_buffer=Write4Bytes(p_buffer,p_vector->mZ);
+				++pp_vectors;
+			}
+			
+			bytes_left-=size*12;
+			break;
+		}
+		
+        case ESYMBOLTYPE_STRUCTURE:
+		{
+			CStruct **pp_structures=(CStruct**)p_array->GetArrayPointer();
+			Dbg_MsgAssert(pp_structures,("NULL pp_structures ?"));
+			
+			for (uint32 i=0; iGetArrayPointer();
+			Dbg_MsgAssert(pp_arrays,("NULL pp_arrays ?"));
+			
+			for (uint32 i=0; iGetSize();
+	// Easy to change WriteToBuffer to support 4 byte sizes, but keeping as 2 for the moment for
+	// backwards compatibility.
+	Dbg_MsgAssert(size<0x10000,("Size of array too big, currently only 2 bytes used to store size in WriteToBuffer ..."));
+	ESymbolType type=p_array->GetType();
+	
+	switch (type)
+	{
+        case ESYMBOLTYPE_INTEGER:
+        case ESYMBOLTYPE_FLOAT:
+        case ESYMBOLTYPE_NAME:
+			space_required+=size*4;
+			break;
+			
+        case ESYMBOLTYPE_STRING:
+			for (i=0; iGetString(i);
+				Dbg_MsgAssert(p_string,("NULL GetString() for element %d when attempting to call CalculateBufferSize",i));			
+				space_required+=strlen(p_string)+1;
+			}
+			break;
+
+        case ESYMBOLTYPE_LOCALSTRING:
+			for (i=0; iGetLocalString(i);
+				Dbg_MsgAssert(p_string,("NULL GetLocalString() for element %d when attempting to call CalculateBufferSize",i));			
+				space_required+=strlen(p_string)+1;
+			}
+			break;
+			
+        case ESYMBOLTYPE_PAIR:
+			#ifdef __NOPT_ASSERT__
+			for (i=0; iGetPair(i);
+				Dbg_MsgAssert(p_pair,("NULL GetPair() for element %d when attempting to call CalculateBufferSize",i));
+			}	
+			#endif
+			space_required+=size*8;
+			break;
+			
+        case ESYMBOLTYPE_VECTOR:
+			#ifdef __NOPT_ASSERT__
+			for (i=0; iGetVector(i);
+				Dbg_MsgAssert(p_vector,("NULL GetVector() for element %d when attempting to call CalculateBufferSize",i));
+			}	
+			#endif
+			space_required+=size*12;
+			break;
+		
+        case ESYMBOLTYPE_STRUCTURE:
+			for (i=0; iGetStructure(i);
+				Dbg_MsgAssert(p_structure,("NULL GetStructure() for element %d when attempting to call CalculateBufferSize",i));
+				space_required+=CalculateBufferSize(p_structure);
+			}
+			break;
+		
+        case ESYMBOLTYPE_ARRAY:
+			for (i=0; iGetArray(i);
+				Dbg_MsgAssert(p_array_element,("NULL GetArray() for element %d when attempting to call CalculateBufferSize",i));
+				space_required+=CalculateBufferSize(p_array_element);
+			}
+			break;
+
+        case ESYMBOLTYPE_NONE:
+			// Empty array
+			break;
+						
+        default:
+            Dbg_MsgAssert(0,("Array type of '%s' is not supported in CalculateBufferSize",GetTypeName(type)));
+            break;
+    }
+	
+	return space_required;
+}
+
+// Sets the array's contents using the passed buffer, which was generated
+// by WriteToBuffer.
+// If the array contained anything to begin with, it will get cleared.	
+
+// Returns a pointer to after the read array data.
+uint8 *ReadFromBuffer(CArray *p_array, uint8 *p_buffer)
+{
+	Dbg_MsgAssert(p_array,("NULL p_array sent to ReadFromBuffer"));
+	Dbg_MsgAssert(p_buffer,("NULL p_buffer sent to ReadFromBuffer"));
+
+	// First clear anything currently in the array.
+	CleanUpArray(p_array);
+
+	// Get the type and size
+	ESymbolType type=(ESymbolType)*p_buffer++;
+	uint32 size=*p_buffer++;
+	size+=*p_buffer++<<8; // 2 byte size.
+
+	// Make sure we're using the script heap.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	p_array->SetSizeAndType(size,type);
+	Mem::Manager::sHandle().PopContext();
+	
+    switch (type)
+    {
+		case ESYMBOLTYPE_INTEGER:
+		{
+			for (uint32 i=0; iSetInteger(i,Read4Bytes(p_buffer).mInt);
+				p_buffer+=4;
+			}
+			break;
+		}		
+		case ESYMBOLTYPE_FLOAT:
+		{
+			for (uint32 i=0; iSetFloat(i,Read4Bytes(p_buffer).mFloat);
+				p_buffer+=4;
+			}
+			break;
+		}		
+		case ESYMBOLTYPE_NAME:
+		{
+			for (uint32 i=0; iSetChecksum(i,Read4Bytes(p_buffer).mChecksum);
+				p_buffer+=4;
+			}
+			break;
+		}		
+		case ESYMBOLTYPE_STRING:
+		{
+			for (uint32 i=0; iSetString(i,CreateString((const char*)p_buffer));
+				p_buffer+=strlen((char*)p_buffer)+1;
+			}
+			break;
+		}		
+		case ESYMBOLTYPE_LOCALSTRING:
+		{
+			for (uint32 i=0; iSetLocalString(i,CreateString((const char*)p_buffer));
+				p_buffer+=strlen((char*)p_buffer)+1;
+			}
+			break;
+		}		
+		case ESYMBOLTYPE_PAIR:
+		{
+			for (uint32 i=0; imX=Read4Bytes(p_buffer).mFloat;
+				p_buffer+=4;
+				p_pair->mY=Read4Bytes(p_buffer).mFloat;
+				p_buffer+=4;
+
+				p_array->SetPair(i,p_pair);
+			}		
+			break;
+		}	
+		case ESYMBOLTYPE_VECTOR:
+		{
+			for (uint32 i=0; imX=Read4Bytes(p_buffer).mFloat;
+				p_buffer+=4;
+				p_vector->mY=Read4Bytes(p_buffer).mFloat;
+				p_buffer+=4;
+				p_vector->mZ=Read4Bytes(p_buffer).mFloat;
+				p_buffer+=4;
+
+				p_array->SetVector(i,p_vector);
+			}		
+			break;
+		}	
+		case ESYMBOLTYPE_STRUCTURE:
+		{
+			for (uint32 i=0; iSetStructure(i,p_structure);
+			}			
+			break;
+		}	
+		case ESYMBOLTYPE_ARRAY:
+		{
+			for (uint32 i=0; iSetArray(i,p_new_array);
+			}			
+			break;
+		}	
+		case ESYMBOLTYPE_NONE:
+			// Empty array
+			break;
+		default:
+			Dbg_MsgAssert(0,("Unsupported type of '%s' encountered in ReadFromBuffer",GetTypeName(type)));
+			break;
+	}
+	
+	return p_buffer;
+}
+
+
+union UValue
+{
+	int mIntegerValue;
+	float mFloatValue;
+	char *mpString;
+	char *mpLocalString;
+	CPair *mpPair;
+	CVector *mpVector;
+	CStruct *mpStructure;
+	CArray *mpArray;
+	uint8 *mpScript;
+	uint32 mChecksum;
+	uint32 mUnion;
+};
+
+// This will load type and value from the passed component, resolving it if it refers to a global.
+static void Resolve(const CComponent& comp, ESymbolType &type, UValue &value)
+{
+	value.mUnion=comp.mUnion;
+	type=(ESymbolType)comp.mType;
+	
+	if (type==ESYMBOLTYPE_NAME)
+	{
+		CSymbolTableEntry *p_entry=Resolve(value.mChecksum);
+		if (p_entry)
+		{
+			value.mUnion=p_entry->mUnion;
+			type=(ESymbolType)p_entry->mType;
+		}	
+	}
+}
+
+// The == operator is used when interpreting switch statements and expressions in script
+// This function is here rather than in component.cpp to avoid cyclic dependencies.
+bool CComponent::operator==( const CComponent& v ) const
+{
+	UValue value_a,value_b;
+	ESymbolType type_a,type_b;
+	Resolve(*this,type_a,value_a);
+	Resolve(v,type_b,value_b);
+	
+	switch (type_a)
+	{
+		case ESYMBOLTYPE_INTEGER:
+			if (type_b==ESYMBOLTYPE_FLOAT)
+			{
+				return ((float)value_a.mIntegerValue)==value_b.mFloatValue;
+			}
+			else if (type_b==ESYMBOLTYPE_INTEGER)
+			{
+				return value_a.mIntegerValue==value_b.mIntegerValue;
+			}
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			if (type_b==ESYMBOLTYPE_INTEGER)
+			{
+				return value_a.mFloatValue==((float)value_b.mIntegerValue);
+			}
+			else if (type_b==ESYMBOLTYPE_FLOAT)
+			{
+				return value_a.mFloatValue==value_b.mFloatValue;
+			}
+			break;
+		case ESYMBOLTYPE_STRING:
+			if (type_b==ESYMBOLTYPE_STRING)
+			{
+				Dbg_MsgAssert(value_a.mpString,("NULL value_a.mpString ?"));
+				Dbg_MsgAssert(value_b.mpString,("NULL value_b.mpString ?"));
+				return strcmp(value_a.mpString,value_b.mpString)==0;
+			}
+			break;
+		case ESYMBOLTYPE_LOCALSTRING:
+			if (type_b==ESYMBOLTYPE_LOCALSTRING)
+			{
+				Dbg_MsgAssert(value_a.mpLocalString,("NULL value_a.mpLocalString ?"));
+				Dbg_MsgAssert(value_b.mpLocalString,("NULL value_b.mpLocalString ?"));
+				return strcmp(value_a.mpLocalString,value_b.mpLocalString)==0;
+			}
+			break;
+		case ESYMBOLTYPE_NAME:
+			if (type_b==ESYMBOLTYPE_NAME)
+			{
+				return value_a.mChecksum==value_b.mChecksum;
+			}	
+			break;
+		case ESYMBOLTYPE_PAIR:
+			if (type_b==ESYMBOLTYPE_PAIR)
+			{
+				Dbg_MsgAssert(value_a.mpPair,("NULL value_a.mpPair ?"));
+				Dbg_MsgAssert(value_b.mpPair,("NULL value_b.mpPair ?"));
+				return value_a.mpPair->mX==value_b.mpPair->mX && 
+					   value_a.mpPair->mY==value_b.mpPair->mY;
+			}
+			break;
+		case ESYMBOLTYPE_VECTOR:
+			if (type_b==ESYMBOLTYPE_VECTOR)
+			{
+				Dbg_MsgAssert(value_a.mpVector,("NULL value_a.mpVector ?"));
+				Dbg_MsgAssert(value_b.mpVector,("NULL value_b.mpVector ?"));
+				return value_a.mpVector->mX==value_b.mpVector->mX && 
+					   value_a.mpVector->mY==value_b.mpVector->mY &&
+					   value_a.mpVector->mZ==value_b.mpVector->mZ;
+			}
+			break;
+		case ESYMBOLTYPE_QSCRIPT:
+			if (type_b==ESYMBOLTYPE_QSCRIPT)
+			{
+				return value_a.mpScript==value_b.mpScript;
+			}
+			break;
+		case ESYMBOLTYPE_STRUCTURE:
+			if (type_b==ESYMBOLTYPE_STRUCTURE)
+			{
+				Dbg_MsgAssert(value_a.mpStructure,("NULL value_a.mpStructure ?"));
+				Dbg_MsgAssert(value_b.mpStructure,("NULL value_b.mpStructure ?"));
+				
+				// The structures definitely match if the pointers match, cos its the 
+				// same bloomin' structure.
+				if (value_a.mpStructure==value_b.mpStructure)
+				{
+					return true;
+				}	
+				
+				// If the pointers do not match, the structures may still have contents that match.
+				// Structure comparison's ain't supported yet though. Implement when needed.
+				// Note: If this ever get's implemented, you'll need to deal with un-named name 
+				// components that resolve to a global structure.
+				#ifdef __NOPT_ASSERT__
+				printf("CStruct comparisons are not supported yet ... implement when needed\n");
+				#endif
+			}
+			break;
+		case ESYMBOLTYPE_ARRAY:
+			if (type_b==ESYMBOLTYPE_ARRAY)
+			{
+				Dbg_MsgAssert(value_a.mpArray,("NULL value_a.mpArray ?"));
+				Dbg_MsgAssert(value_b.mpArray,("NULL value_b.mpArray ?"));
+				
+				// The arrays definitely match if the pointers match.
+				if (value_a.mpArray==value_b.mpArray)
+				{
+					return true;
+				}	
+				
+				// If the pointers do not match, the arrays may still have contents that match.
+				return *value_a.mpArray==*value_b.mpArray;
+			}
+			break;
+		case ESYMBOLTYPE_CFUNCTION:
+			// TODO ...
+			#ifdef __NOPT_ASSERT__
+			printf("C-Function comparisons are not supported yet ... implement when needed\n");
+			#endif
+			break;
+		case ESYMBOLTYPE_MEMBERFUNCTION:
+			// TODO ...
+			#ifdef __NOPT_ASSERT__
+			printf("Member function comparisons are not supported yet ... implement when needed\n");
+			#endif
+			break;
+		case ESYMBOLTYPE_NONE:
+			break;
+		default:
+			#ifdef __NOPT_ASSERT__
+			printf("'%s' with '%s' comparisons are not supported\n",GetTypeName(type_a),GetTypeName(type_b));			
+			#endif
+			break;
+	}		
+	
+	return false;
+}
+
+bool CComponent::operator!=( const CComponent& v ) const
+{
+	return !(*this==v);
+}
+
+bool CComponent::operator<( const CComponent& v ) const
+{
+	UValue value_a,value_b;
+	ESymbolType type_a,type_b;
+	Resolve(*this,type_a,value_a);
+	Resolve(v,type_b,value_b);
+	
+	switch (type_a)
+	{
+		case ESYMBOLTYPE_INTEGER:
+			if (type_b==ESYMBOLTYPE_FLOAT)
+			{
+				return ((float)value_a.mIntegerValue) < value_b.mFloatValue;
+			}
+			else if (type_b==ESYMBOLTYPE_INTEGER)
+			{
+				return value_a.mIntegerValue < value_b.mIntegerValue;
+			}
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			if (type_b==ESYMBOLTYPE_INTEGER)
+			{
+				return value_a.mFloatValue < ((float)value_b.mIntegerValue);
+			}
+			else if (type_b==ESYMBOLTYPE_FLOAT)
+			{
+				return value_a.mFloatValue < value_b.mFloatValue;
+			}
+			break;
+		case ESYMBOLTYPE_STRING:
+			if (type_b==ESYMBOLTYPE_STRING)
+			{
+				Dbg_MsgAssert(value_a.mpString,("NULL value_a.mpString ?"));
+				Dbg_MsgAssert(value_b.mpString,("NULL value_b.mpString ?"));
+				// Using string length rather than position in the alphabet, cos it's
+				// probably more useful.
+				return strlen(value_a.mpString) < strlen(value_b.mpString);
+			}
+			break;
+		case ESYMBOLTYPE_LOCALSTRING:
+			if (type_b==ESYMBOLTYPE_LOCALSTRING)
+			{
+				Dbg_MsgAssert(value_a.mpLocalString,("NULL value_a.mpLocalString ?"));
+				Dbg_MsgAssert(value_b.mpLocalString,("NULL value_b.mpLocalString ?"));
+				return strlen(value_a.mpLocalString) < strlen(value_b.mpLocalString);
+			}
+			break;
+		default:
+			#ifdef __NOPT_ASSERT__
+			printf("'%s' with '%s' less-than comparisons are not supported\n",GetTypeName(type_a),GetTypeName(type_b));			
+			#endif
+			break;
+	}		
+	
+	return false;
+}
+
+bool CComponent::operator>( const CComponent& v ) const
+{
+	UValue value_a,value_b;
+	ESymbolType type_a,type_b;
+	Resolve(*this,type_a,value_a);
+	Resolve(v,type_b,value_b);
+	
+	switch (type_a)
+	{
+		case ESYMBOLTYPE_INTEGER:
+			if (type_b==ESYMBOLTYPE_FLOAT)
+			{
+				return ((float)value_a.mIntegerValue) > value_b.mFloatValue;
+			}
+			else if (type_b==ESYMBOLTYPE_INTEGER)
+			{
+				return value_a.mIntegerValue > value_b.mIntegerValue;
+			}
+			break;
+		case ESYMBOLTYPE_FLOAT:
+			if (type_b==ESYMBOLTYPE_INTEGER)
+			{
+				return value_a.mFloatValue > ((float)value_b.mIntegerValue);
+			}
+			else if (type_b==ESYMBOLTYPE_FLOAT)
+			{
+				return value_a.mFloatValue > value_b.mFloatValue;
+			}
+			break;
+		case ESYMBOLTYPE_STRING:
+			if (type_b==ESYMBOLTYPE_STRING)
+			{
+				Dbg_MsgAssert(value_a.mpString,("NULL value_a.mpString ?"));
+				Dbg_MsgAssert(value_b.mpString,("NULL value_b.mpString ?"));
+				// Using string length rather than position in the alphabet, cos it's
+				// probably more useful.
+				return strlen(value_a.mpString) > strlen(value_b.mpString);
+			}
+			break;
+		case ESYMBOLTYPE_LOCALSTRING:
+			if (type_b==ESYMBOLTYPE_LOCALSTRING)
+			{
+				Dbg_MsgAssert(value_a.mpLocalString,("NULL value_a.mpLocalString ?"));
+				Dbg_MsgAssert(value_b.mpLocalString,("NULL value_b.mpLocalString ?"));
+				return strlen(value_a.mpLocalString) > strlen(value_b.mpLocalString);
+			}
+			break;
+		default:
+			#ifdef __NOPT_ASSERT__
+			printf("'%s' with '%s' greater-than comparisons are not supported\n",GetTypeName(type_a),GetTypeName(type_b));			
+			#endif
+			break;
+	}		
+	
+	return false;
+}
+
+bool CComponent::operator<=( const CComponent& v ) const
+{
+	if (*this=( const CComponent& v ) const
+{
+	if (*this>v)
+	{
+		return true;
+	}
+	if (*this==v)
+	{
+		return true;
+	}
+	return false;	
+}
+
+// This is used in eval.cpp, when evaluating foo[3] say.
+// Copies the array element indicated by index into the passed component.
+// The type of p_comp may be ESYMBOLTYPE_NONE if the type is not supported yet by not being in
+// the switch statement.
+void CopyArrayElementIntoComponent(CArray *p_array, uint32 index, CComponent *p_comp)
+{
+	Dbg_MsgAssert(p_array,("NULL p_array"));
+	Dbg_MsgAssert(indexGetSize(),("Array index %d out of range, array size is %d",index,p_array->GetSize()));
+	Dbg_MsgAssert(p_comp,("NULL p_comp"));
+	
+	p_comp->mType=p_array->GetType();
+	switch (p_comp->mType)
+	{
+	case ESYMBOLTYPE_INTEGER:
+		p_comp->mIntegerValue=p_array->GetInteger(index);
+		break;
+	case ESYMBOLTYPE_FLOAT:
+		p_comp->mFloatValue=p_array->GetFloat(index);
+		break;
+	case ESYMBOLTYPE_NAME:
+		p_comp->mChecksum=p_array->GetChecksum(index);
+		break;
+	case ESYMBOLTYPE_STRUCTURE:
+		p_comp->mpStructure=new CStruct;
+		p_comp->mpStructure->AppendStructure(p_array->GetStructure(index));
+		break;
+	case ESYMBOLTYPE_ARRAY:
+		p_comp->mpArray=new CArray;
+		CopyArray(p_comp->mpArray,p_array->GetArray(index));
+		break;
+	case ESYMBOLTYPE_STRING:
+		p_comp->mpString=CreateString(p_array->GetString(index));
+		break;
+	case ESYMBOLTYPE_VECTOR:
+	{
+		CVector *p_source_vector=p_array->GetVector(index);
+		p_comp->mpVector=new CVector;
+		p_comp->mpVector->mX=p_source_vector->mX;
+		p_comp->mpVector->mY=p_source_vector->mY;
+		p_comp->mpVector->mZ=p_source_vector->mZ;
+		break;
+	}	
+	case ESYMBOLTYPE_PAIR:
+	{
+		CPair *p_source_pair=p_array->GetPair(index);
+		p_comp->mpPair=new CPair;
+		p_comp->mpPair->mX=p_source_pair->mX;
+		p_comp->mpPair->mY=p_source_pair->mY;
+		break;
+	}	
+	default:
+		Dbg_MsgAssert(0,("The [] operator is not yet supported when the array element has  type '%s'",GetTypeName(p_comp->mType)));
+		p_comp->mType=ESYMBOLTYPE_NONE;
+		p_comp->mUnion=0;
+		break;
+	}	
+}
+
+// This is used in parse.cpp, and cfuncs.cpp in sFormatText
+void ResolveNameComponent(CComponent *p_comp)
+{
+	Dbg_MsgAssert(p_comp,("NULL p_comp"));
+
+	if (p_comp->mType!=ESYMBOLTYPE_NAME)
+	{
+		return;
+	}
+		
+	CSymbolTableEntry *p_entry=Resolve(p_comp->mChecksum);
+	if (!p_entry)
+	{
+		return;
+	}
+		
+	switch (p_entry->mType)
+	{
+	case ESYMBOLTYPE_FLOAT:
+		p_comp->mType=ESYMBOLTYPE_FLOAT;
+		p_comp->mFloatValue=p_entry->mFloatValue;
+		break;
+	case ESYMBOLTYPE_INTEGER:
+		p_comp->mType=ESYMBOLTYPE_INTEGER;
+		p_comp->mIntegerValue=p_entry->mIntegerValue;
+		break;
+	case ESYMBOLTYPE_VECTOR:
+		p_comp->mType=ESYMBOLTYPE_VECTOR;
+		p_comp->mpVector=p_entry->mpVector;
+		break;
+	case ESYMBOLTYPE_PAIR:
+		p_comp->mType=ESYMBOLTYPE_PAIR;
+		p_comp->mpPair=p_entry->mpPair;
+		break;
+	case ESYMBOLTYPE_STRING:
+		p_comp->mType=ESYMBOLTYPE_STRING;
+		p_comp->mpString=p_entry->mpString;
+		break;
+	case ESYMBOLTYPE_LOCALSTRING:
+		p_comp->mType=ESYMBOLTYPE_LOCALSTRING;
+		p_comp->mpLocalString=p_entry->mpLocalString;
+		break;
+	case ESYMBOLTYPE_STRUCTURE:
+		p_comp->mType=ESYMBOLTYPE_STRUCTURE;
+		p_comp->mpStructure=p_entry->mpStructure;
+		break;
+	case ESYMBOLTYPE_ARRAY:
+		p_comp->mType=ESYMBOLTYPE_ARRAY;
+		p_comp->mpArray=p_entry->mpArray;
+		break;
+	default:
+		break;
+	}
+}
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/utils.h b/Code/Gel/Scripting/utils.h
new file mode 100644
index 0000000..5359c59
--- /dev/null
+++ b/Code/Gel/Scripting/utils.h
@@ -0,0 +1,36 @@
+#ifndef	__SCRIPTING_UTILS_H
+#define	__SCRIPTING_UTILS_H
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#include  // For EAssertType
+#endif
+
+namespace Script
+{
+
+class CArray;
+class CStruct;
+class CComponent;
+
+void PrintContents(const CArray *p_array, int indent=0);
+void PrintContents(const CStruct *p_structure, int indent=0);
+
+uint32 WriteToBuffer(CStruct *p_structure, uint8 *p_buffer, uint32 bufferSize, EAssertType assert=ASSERT);
+uint32 CalculateBufferSize(CStruct *p_structure, uint32 tempBufferSize=100000);
+uint8 *ReadFromBuffer(CStruct *p_structure, uint8 *p_buffer);
+
+uint32 WriteToBuffer(CArray *p_array, uint8 *p_buffer, uint32 bufferSize, EAssertType assert=ASSERT);
+uint32 CalculateBufferSize(CArray *p_array);
+uint8 *ReadFromBuffer(CArray *p_array, uint8 *p_buffer);
+
+// This is used in eval.cpp, when evaluating foo[3] say.
+// Copies the array element indicated by index into the passed component.
+// The type of p_comp may be ESYMBOLTYPE_NONE if the type is not supported yet by not being in
+// the switch statement.
+void CopyArrayElementIntoComponent(CArray *p_array, uint32 index, CComponent *p_comp);
+void ResolveNameComponent(CComponent *p_comp);
+
+}
+
+#endif // #ifndef	__SCRIPTING_UTILS_H
+
diff --git a/Code/Gel/Scripting/vecpair.cpp b/Code/Gel/Scripting/vecpair.cpp
new file mode 100644
index 0000000..56e05f6
--- /dev/null
+++ b/Code/Gel/Scripting/vecpair.cpp
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// vecpair.cpp		KSH 17 Oct 2001
+//
+// Just defines the constructors and pool statics.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+
+DefinePoolableClass(Script::CVector);
+DefinePoolableClass(Script::CPair);
+
+namespace Script
+{
+
+CVector::CVector()
+{
+	// Initialise everything. CVector is not derived from CClass so we don't get
+	// the auro-zeroing.
+	mX=mY=mZ=0.0f;
+}
+
+// Note: No destructor needed.
+
+
+CPair::CPair()
+{
+	// Initialise everything. CPair is not derived from CClass so we don't get
+	// the auro-zeroing.
+	mX=mY=0.0f;
+}
+
+// Note: No destructor needed.
+
+} // namespace Script
+
diff --git a/Code/Gel/Scripting/vecpair.h b/Code/Gel/Scripting/vecpair.h
new file mode 100644
index 0000000..37ade6e
--- /dev/null
+++ b/Code/Gel/Scripting/vecpair.h
@@ -0,0 +1,58 @@
+#ifndef	__SCRIPTING_VECPAIR_H
+#define	__SCRIPTING_VECPAIR_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+namespace Script
+{
+
+// Note: These are not derived from CClass to avoid the extra memory overhead due to the virtual destructor.
+
+#ifdef __PLAT_WN32__
+class CVector
+#else
+class CVector : public Mem::CPoolable
+#endif
+{
+public:
+	CVector();
+	// No copy constructor or assignement operators needed, the defaults will work.
+
+	union
+	{
+		CVector *mpNext;
+		float mX;
+	};	
+	
+	float mY;
+	float mZ;
+};
+
+#ifdef __PLAT_WN32__
+class CPair
+#else
+class CPair : public Mem::CPoolable
+#endif
+{
+public:
+	CPair();
+	// No copy constructor or assignement operators needed, the defaults will work.
+
+	union
+	{
+		CPair *mpNext;
+		float mX;
+	};	
+	
+	float mY;
+};
+
+} // namespace Script
+
+#endif // #ifndef	__SCRIPTING_VECPAIR_H
diff --git a/Code/Gel/Scripting/win32functions.cpp b/Code/Gel/Scripting/win32functions.cpp
new file mode 100644
index 0000000..7bde4c4
--- /dev/null
+++ b/Code/Gel/Scripting/win32functions.cpp
@@ -0,0 +1,125 @@
+#ifdef __PLAT_WN32__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Tmr
+{
+uint64 GetRenderFrame( void )
+{
+	return 0;
+}
+
+uint64 GetTimeInCPUCycles( void )
+{
+	return 0;
+}
+}
+
+namespace Obj
+{
+// Needed for asserts to compile
+bool DebugSkaterScripts=false;
+
+class CObject;
+
+CObject *ResolveToObject(uint32 id, bool ignoreScreenElements)
+{
+	return NULL;
+}
+}
+
+namespace Pip
+{
+void *Load(const char *p_fileName)
+{
+	return NULL;
+}
+
+void Unload(const char *p_fileName)
+{
+}
+}
+
+namespace Mem
+{
+
+Manager *Manager::sp_instance = NULL;
+
+Manager::Manager( void )
+{
+	memset(this,0,sizeof(Manager));
+}
+
+void Manager::sSetUp( void )
+{
+	if ( !sp_instance )
+	{
+		sp_instance = new Manager;
+	}
+	else
+	{
+		Dbg_Warning( "Already Initialized!" );
+	}
+}
+
+void Manager::sCloseDown( void )
+{
+}
+
+void Manager::PushContext( Allocator* alloc )
+{
+}
+
+void Manager::PopContext( void )
+{
+}
+
+char *Manager::GetContextName()
+{
+	return "";
+}
+
+Mem::Heap *Manager::FirstHeap()
+{
+	return NULL;
+}
+
+Heap* Manager::GetHeap( uint32 whichHeap )
+{
+	return NULL;
+}
+
+Mem::Heap *Manager::NextHeap(Mem::Heap * pHeap)
+{
+	return NULL;
+}
+
+void SetThreadSafe(bool safe)
+{
+}
+
+void *Malloc( size_t size )
+{
+	return new char[size];
+}
+
+void Free( void *p_mem )
+{
+	delete [] p_mem;
+}
+
+void PopMemProfile()
+{
+}
+
+void PushMemProfile(char *p_type)
+{
+}
+
+}
+
+#endif
+
+
diff --git a/Code/Gel/SoundFX/NGPS/p_sfx.cpp b/Code/Gel/SoundFX/NGPS/p_sfx.cpp
new file mode 100644
index 0000000..d9400de
--- /dev/null
+++ b/Code/Gel/SoundFX/NGPS/p_sfx.cpp
@@ -0,0 +1,1512 @@
+//--------------------------------------------------------------------------------------------
+// Playstation 2 sound effect functionality...
+// Taken from samples on Sony website, modified by mjd.
+//--------------------------------------------------------------------------------------------
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#define NO_SOUND	0
+
+namespace Pcm
+{
+extern int	gNonAllignedIopBuffer;
+};
+
+namespace Sfx
+{
+
+
+
+// Macros and constants:
+
+// This array maps the platform-independent reverb modes specified in reverb.q to the available Ps2 reverb modes.
+int ReverbModes[] =
+{
+	// put the default first in this list, since the default param to SetReverb() is 0
+	SD_REV_MODE_HALL,			// REV_MODE_DEFAULT
+	SD_REV_MODE_HALL,			// REV_MODE_GENERIC
+	SD_REV_MODE_ROOM,			// REV_MODE_PADDEDCELL
+	SD_REV_MODE_ROOM,			// REV_MODE_ROOM
+	SD_REV_MODE_STUDIO_A,		// REV_MODE_BATHROOM
+	SD_REV_MODE_ROOM,			// REV_MODE_LIVINGROOM
+	SD_REV_MODE_STUDIO_C,		// REV_MODE_STONEROOM
+	SD_REV_MODE_HALL,			// REV_MODE_AUDITORIUM
+	SD_REV_MODE_HALL,			// REV_MODE_CONCERTHALL
+	SD_REV_MODE_ROOM,			// REV_MODE_CAVE
+	SD_REV_MODE_HALL,			// REV_MODE_ARENA
+	SD_REV_MODE_HALL,			// REV_MODE_HANGAR
+	SD_REV_MODE_HALL,			// REV_MODE_CARPETEDHALLWAY
+	SD_REV_MODE_HALL,			// REV_MODE_HALLWAY
+	SD_REV_MODE_HALL,			// REV_MODE_STONECORRIDOR
+	SD_REV_MODE_HALL,			// REV_MODE_ALLEY
+	SD_REV_MODE_HALL,			// REV_MODE_FOREST
+	SD_REV_MODE_HALL,			// REV_MODE_CITY
+	SD_REV_MODE_HALL,			// REV_MODE_MOUNTAINS
+	SD_REV_MODE_HALL,			// REV_MODE_QUARRY
+	SD_REV_MODE_HALL,			// REV_MODE_PLAIN
+	SD_REV_MODE_HALL,			// REV_MODE_PARKINGLOT
+	SD_REV_MODE_PIPE,			// REV_MODE_SEWERPIPE
+	SD_REV_MODE_PIPE,			// REV_MODE_UNDERWATER
+};
+
+#define NUM_REVERB_MODES ( ( int )( sizeof( ReverbModes ) / sizeof( int ) ) )
+
+#if NUM_STREAMS > 3
+#error tell matt to handle more than 3 streams in sfx.cpp
+#endif
+
+#if NUM_STREAMS == 3
+#define VOICES_MINUS_STREAMS ( 0xFFFFFF & ~( ( 1 << STREAM_VOICE( 0 ) ) | ( 1 << STREAM_VOICE( 1 ) ) | ( 1 << STREAM_VOICE( 2 ) ) ) )
+#else
+#define VOICES_MINUS_STREAMS ( 0xFFFFFF & ~( ( 1 << MUSIC_L_VOICE ) | ( 1 << MUSIC_R_VOICE ) ) )
+#endif
+#define VOICES_MINUS_NON_REVERB ( ( ( 1 << NUM_NO_REVERB_VOICES ) - 1 ) << NUM_NO_REVERB_START_VOICE )
+
+// convert sample rate to pitch setting
+#define FREQ2PITCH( x ) ( ( ( float ) ( ( x ) * 100 ) / 48000.0f ) )
+
+#define DMA_CH						0
+
+#if ( ( CORE_0_EFFECTS_END_ADDRESS - CORE_0_EFFECTS_START_ADDRESS ) < ( RAM_NEEDED_FOR_EFFECTS ) )
+#error "not enough room for effects"
+#endif
+
+int sTargetReverbDepth = 0;
+int sCurrentReverbDepth = 0;
+
+// allocated SPU RAM
+int SpuRAMUsedTemp = 0;
+int SpuRAMUsedPerm = 0;
+
+int LoopingSounds[ NUM_CORES ];
+// gotta keep track of voices used, to be able
+// to tell whether a voice is free or not...
+volatile int VoicesUsed[ NUM_CORES ];
+volatile int VoicesCleared[ NUM_CORES ];
+
+// avoid checking voice status more than once per frame:
+//uint64	gLastVoiceAvailableUpdate[ NUM_CORES ];
+//int		gAvailableVoices[ NUM_CORES ];
+float	gSfxVolume = 100.0f;
+
+#define MAX_VOLUME					0x3FFF  // max playstation 2 volume...
+#define MAX_REVERB_DEPTH			0x3FFF
+#define MAX_PITCH					0x3FFF
+#define UNALTERED_PITCH				4096
+
+#define BLOCKHEADER_LOOP_BIT	( 1 << 9 )
+
+// Internal function prototypes:
+
+float 	GetPitchValue( VagHeader *VagHdr );
+int 	convertEnd( int inp );
+bool	PS2Sfx_LoadVAG( PlatformWaveInfo *pInfo, const char *filename, bool loadPerm, float *samplePitch = NULL );
+int		GetCore( int whichVoice );
+
+int		PS2Sfx_PlaySound( PlatformWaveInfo *pInfo, int voiceNumber, float volL = 100.0f, float volR = 100.0f, float pitch = 100.0f );
+void	PS2Sfx_StopSound( int voiceNumber );
+int		PS2Sfx_GetVoiceStatus( int core );
+
+#define ALLIGN_REQUIREMENT	64 // EE to IOP
+#define SIZE_OF_LEADIN		16
+#define USE_CLEVER_TRICK_FOR_CLOBBERED_DATA		1
+
+// IOP_MIN_TRANSFER must be a power of 2!!!
+#define IOP_MIN_TRANSFER	64	// iop will clobber memory if you're writing above a sound.  we'll restore the
+								// potentially clobbered memory by keeping a buffer containing the beginning of
+								// the previously loaded perm sound, and copying that to the end of our new
+								// data...
+
+#if USE_CLEVER_TRICK_FOR_CLOBBERED_DATA
+#define		PAD_TO_PREVENT_CLOBBERING		0
+#else
+#define		PAD_TO_PREVENT_CLOBBERING		64
+#endif
+
+#define FULL_NAME_SIZE		256
+
+int GetMemAvailable( void )
+{
+	
+	return ( MAX_SPU_RAM_AVAILABLE - ( SpuRAMUsedTemp + SpuRAMUsedPerm ) );
+}
+
+//--------------------------------------------------------------------------------------------
+// Function: int EELoadVAG(char *filename ); //, int *samplePitch)
+// Description: Transfers specified filename into SPU2 RAM and returns an address value for
+//		for use in the SetVoice() function
+// Parameters:  filename to be transfered
+//
+// Returns: value referencing the vag address in SPU2 RAM
+// Notes: This transfers all data in a blocking fashion, but quickly.
+//-------------------------------------------------------------------------------------------
+bool PS2Sfx_LoadVAG( PlatformWaveInfo *pInfo, const char *filename, bool loadPerm, float *samplePitch)
+{
+	
+	
+	uint32 size = 0;				// file size
+	int vagAddr = 0;			// VAG address in SPU2 RAM, returned to calling function
+	VagHeader vagHeader;
+	char fullname[ FULL_NAME_SIZE ]; // sound names can be HUGE...
+	unsigned int IOPbuffer = 0; // IOP memory buffer
+	
+	Dbg_MsgAssert( strlen( filename ) < FULL_NAME_SIZE - 30,( "Increase buffer for full filename..." ));
+	sprintf(fullname, "sounds\\vag\\%s.vag", filename);
+	//Matt("Loading sound %s\n", fullname);
+    
+	void *fp = File::Open( fullname, "rb" );
+	if ( !fp )
+	{
+		Dbg_MsgAssert( 0,( "LoadSound: Couldn't find file %s", filename ));
+		printf( "load sound failed to find %s\n", filename );
+		return ( false );
+	}
+	File::Seek( fp, 0, SEEK_SET );
+	if ( SIZE_OF_VAGHEADER != File::Read( &vagHeader, 1, SIZE_OF_VAGHEADER, fp ) )
+	{
+		// error reading header...
+		Dbg_MsgAssert( 0,( "Sorry, young lady, but your VAG is too small" ));
+//		Dbg_MsgAssert( 0,( "Not enough bytes to satisfy our VAG" ));
+	}
+	/*  this fucked things up...
+	int endType = 0;
+	if ( strncmp( vagHeader.ID, "VAGp", 4 ) == 0 )
+		endType = 0;
+	else
+		endType = 1;
+	
+	size = ( ( endType == 0 ) ? convertEnd( vagHeader.dataSize ) : vagHeader.dataSize );
+	*/
+	size = convertEnd( vagHeader.dataSize );
+
+	Dbg_MsgAssert( size > 0,( "There's nothing worse than an empty VAG." ));
+
+	//Dbg_Message( "vag header size %d data size %d", SIZE_OF_VAGHEADER, size );
+
+#if USE_CLEVER_TRICK_FOR_CLOBBERED_DATA
+	uint8 *buffer = new uint8[ size + ALLIGN_REQUIREMENT + IOP_MIN_TRANSFER ];
+#else
+	uint8 *buffer = new uint8[ size + ALLIGN_REQUIREMENT ];
+#endif
+	File::Seek( fp, SIZE_OF_VAGHEADER, SEEK_SET );
+	
+	uint8 *allignedBuffer = ( uint8 * )( ( ( int ) buffer + ( ALLIGN_REQUIREMENT - 1 ) ) & ~( ALLIGN_REQUIREMENT - 1 ) );
+	
+	uint32 readsize = File::Read( allignedBuffer, 1, size, fp );
+	if ( size !=  readsize)
+	{
+		Dbg_Message( "if non-converted size %d is the correct size of file %s, inconsistent VAG format", vagHeader.dataSize + SIZE_OF_VAGHEADER, filename );
+		Dbg_MsgAssert( 0,( "VAG %s is %d bytes, not as big as she said it would be (%d bytes)", filename, readsize, size ));
+	}
+	File::Close( fp );
+		
+	if ( samplePitch )
+	{
+		*samplePitch = GetPitchValue( &vagHeader );
+	}
+//	Dbg_Message( "Pitch value %f", GetPitchValue( &vagHeader ) );
+
+	// make sure we've got room for this sound in RAM:
+	// sound data must be 16byte aligned in the SPU, so check for that
+	// also, the IOP seems to transfer only in 128 byte chunks, so let's
+	// pad the buffer if we're writing a permanent sound (which stacks from
+	// the bottom if RAM up) and write the potentially clobbered bytes from
+	// the previously loaded permanent sound into the end of the buffer to
+	// be transfered (clever huh?)  NOTE: This didn't seem to work without
+	// flushing the cache...  Gives everything in memory time to settle I
+	// guess?
+	
+	if ( loadPerm )
+	{
+#if USE_CLEVER_TRICK_FOR_CLOBBERED_DATA
+		int i;
+		int overshoot = 0; // need to initialize to zero!
+		static uint8 prevPermBuffer[ IOP_MIN_TRANSFER ];
+		static int prevVagAddr;
+        if ( !SpuRAMUsedPerm )
+		{
+			// don't store nuthin' first time through...
+			prevVagAddr = 0;
+		}
+#endif
+		// stack the permanent sounds up from the bottom:
+		SpuRAMUsedPerm += ( size + PAD_TO_PREVENT_CLOBBERING );
+		if ( ( END_WAVE_DATA_ADDR - SpuRAMUsedPerm ) & 0x0F )
+		{
+#ifdef __NOPT_ASSERT__
+			int oldSpuRAM = SpuRAMUsedPerm;
+#endif			
+			SpuRAMUsedPerm += ( ( END_WAVE_DATA_ADDR - SpuRAMUsedPerm ) & 0x0F );
+			Dbg_MsgAssert( SpuRAMUsedPerm > oldSpuRAM,( "Duh asshole." ));
+		}
+		vagAddr = END_WAVE_DATA_ADDR - SpuRAMUsedPerm;
+		Dbg_MsgAssert( !( vagAddr & 0x0F ),( "Fire Matt please." ));
+		
+#if USE_CLEVER_TRICK_FOR_CLOBBERED_DATA
+		if ( prevVagAddr )
+		{
+			// don't have to do this if this is the first loaded perm sound...
+			// hence condition above...
+			
+			// find out how much of the previous perm data we'd be writing
+			// over, add that to the end of our buffer...
+			if ( size & ( IOP_MIN_TRANSFER - 1 ) )
+			{
+//				Dbg_Message( "size %d over 64-byte boundary", size & ( IOP_MIN_TRANSFER - 1 ) );
+				overshoot = ( IOP_MIN_TRANSFER ) - ( size & ( IOP_MIN_TRANSFER - 1 ) );
+//				Dbg_Message( "overshoot %d", overshoot );
+				int spaceBetween = prevVagAddr - ( vagAddr + size );
+//				Dbg_Message( "space between %d", spaceBetween );
+				int numBytesClobbered = overshoot - spaceBetween;
+				Dbg_MsgAssert( numBytesClobbered <= IOP_MIN_TRANSFER,( "how in the world?" ));
+//				Dbg_Message( "numBytesClobbered = %d", numBytesClobbered );
+				for ( i = 0; i < numBytesClobbered; i++ )
+				{
+					// store the previous buffer at the end of our buffer,
+					allignedBuffer[ size + i + spaceBetween ] = prevPermBuffer[ i ];
+				}
+			}
+			else
+			{
+				overshoot = 0;
+			}	
+			size += overshoot;
+			// add on the size of the overshoot, which in the case of permanent sounds
+			// has data from a previous sound (which will be clobbered), written in.
+//			size += overshoot;
+			Dbg_MsgAssert( !( size & ( IOP_MIN_TRANSFER - 1 ) ),( "Fire Matt." ));
+			
+		}
+		for ( i = 0; i < IOP_MIN_TRANSFER; i++ )
+		{
+			// store the beginning of our new buffer into the static buffer
+			// for when the next perm sound loads...
+			prevPermBuffer[ i ] = allignedBuffer[ i ];
+		}
+		prevVagAddr = vagAddr;
+#endif		
+	} // if ( loadPerm )
+	else
+	{
+		if ( ( SpuRAMUsedTemp + BASE_WAVE_DATA_ADDR ) & 0x0F )
+		{
+			SpuRAMUsedTemp += 0x10 - ( ( SpuRAMUsedTemp + BASE_WAVE_DATA_ADDR ) & 0x0F );
+		}
+		vagAddr = BASE_WAVE_DATA_ADDR + SpuRAMUsedTemp;
+		SpuRAMUsedTemp += ( size );
+	}
+	
+	// transfer buffer to IOP, it's a DMA operation so FlushCache() to ensure cache is flushed
+	FlushCache( 0 );
+
+	// Ken: Instead of borrowing a new chunk of memory from the IOP, (which causes it to often
+	// run out cos we're so tight on memory) use the already existing
+	// gNonAllignedIopBuffer, used by streams and music etc. They have no need of it at this point.
+	//IOPbuffer = ( unsigned int )sceSifAllocIopHeap( size );
+	Dbg_MsgAssert(size MAX_SPU_RAM_AVAILABLE )
+	{
+		Dbg_MsgAssert( 0,( "Our VAG space has been RAMed to the hilt" ));
+	}
+
+	if (!CSpuManager::sVoiceTrans(DMA_CH, SD_TRANS_MODE_WRITE | SD_TRANS_BY_DMA, (void *) IOPbuffer, (uint32 *) vagAddr, size))
+	{
+		Dbg_MsgAssert( 0,( "Failed to Transfer to SPU2 size %d vagAddr %x IOPBuffer addr %x\n", size, vagAddr, IOPbuffer ));
+	}
+
+	// Ken: Commented this out because now we're using Pcm::gNonAllignedIopBuffer instead.
+	// Now free up IOP and EE memory, check this return value is zero, as documentation (iopservice.txt v1.5) seems to be wrong
+	//if( sceSifFreeIopHeap( (void * ) IOPbuffer ) != 0 )
+	//{
+	//	Dbg_MsgAssert( 0,( "Failed to free IOP memory\n" ));
+	//}
+
+	pInfo->vagAddress = vagAddr;
+	uint16 blockHeader = *( ( uint16 * ) allignedBuffer + SIZE_OF_LEADIN );
+	pInfo->looping = ( blockHeader & BLOCKHEADER_LOOP_BIT ) ? true : false;
+	
+	delete buffer;
+	return ( true );
+}
+
+//--------------------------------------------------------------------------------------------
+// Function    : float GetPitchValue(char *buffer)
+// Description : gets pitch value from VAG header
+//
+// Parameters  : pointer to VAG header
+//
+// Returns     : pitch value
+// Notes       : 
+//-------------------------------------------------------------------------------------------
+float GetPitchValue( VagHeader *VagHdr )
+{
+	
+/*	int endType = 0;
+
+	if ( strncmp( VagHdr->ID, "VAGp", 4 ) == 0 )
+		endType = 0;
+	else
+		endType = 1;
+
+	if(endType == 0)
+    {*/
+		return ( ( FREQ2PITCH( convertEnd( VagHdr->sampleFreq ) ) ) );
+/*	}
+	else
+	{
+		return ( FREQ2PITCH( VagHdr->sampleFreq ) );
+	}
+	return 0;*/
+}
+
+#ifdef __USER_MATT__
+//--------------------------------------------------------------------------------------------
+// Function    : void PrintVAGDetails(char *buffer)
+// Description : debug function to print out VAG details
+//
+// Parameters  : pointer to VAG header
+//
+// Returns     : 
+// Notes       : 
+//-------------------------------------------------------------------------------------------
+void PrintVAGDetails(char *buffer)
+{
+	
+	VagHeader *VagHdr;
+	int i;
+	int endType = 0;	// 0 for PC, 1 for MAC
+
+	VagHdr = (VagHeader *)buffer;
+
+	Dbg_Message("Checking for VAGp string ..'");
+	for(i=0;i<4;i++)
+	{
+		Dbg_Message("%c",VagHdr->ID[i]);
+	}
+	Dbg_Message("' found.\n");
+
+	Dbg_Message("VAG Details\n");
+	Dbg_Message("===========\n");
+
+	// check for either little or big endian format (PC or MAC)
+	if(strncmp(VagHdr->ID,"VAGp",4) == 0)
+		endType = 0;
+	else
+		endType = 1;
+
+	Dbg_Message("ID         : ");
+	for(i=0;i<4;i++)
+	{
+		Dbg_Message("%c",VagHdr->ID[i]);
+	}
+	Dbg_Message("\n");
+
+	Dbg_Message("VERSION    : ");
+	Dbg_Message("0x%x", (endType == 0) ? convertEnd(VagHdr->version) : VagHdr->version);
+	Dbg_Message("\n");
+
+	Dbg_Message("RESERVED   : ");
+	for(i=0;i<4;i++)
+	{
+		Dbg_Message("%x",VagHdr->reserved1[i]);
+	}
+	Dbg_Message("\n");
+		
+	Dbg_Message("DATASIZE   : ");
+	Dbg_Message("0x%x", (endType == 0) ? convertEnd(VagHdr->dataSize) : VagHdr->dataSize);
+	Dbg_Message("\n");
+
+	Dbg_Message("SAMPLEFREQ : ");
+	Dbg_Message("0x%x (%d)", (endType == 0) ? convertEnd(VagHdr->sampleFreq) : VagHdr->sampleFreq, (endType == 0) ? convertEnd(VagHdr->sampleFreq) : VagHdr->sampleFreq);
+	Dbg_Message("\n");
+
+	Dbg_Message("RESERVED   : ");
+	for(i=0;i<12;i++)
+	{
+		Dbg_Message("%x",VagHdr->reserved2[i]);
+	}
+	Dbg_Message("\n");
+
+	Dbg_Message("NAME       : ");
+	for(i=0;i<16;i++)
+	{
+		if(VagHdr->name[i] == '\0')
+			break;
+		Dbg_Message("%c",VagHdr->name[i]);
+	}
+	Dbg_Message("\n");
+
+	return;
+}
+#endif
+
+//--------------------------------------------------------------------------------------------
+// Function    : int convertEnd(int inp)
+// Description : needed for swopping endian values if VAG has been created by PC
+//
+// Parameters  : 
+//
+// Returns     : 
+// Notes       : 
+//-------------------------------------------------------------------------------------------
+int convertEnd(int inp)
+{
+	
+	// swap bytes 0 and 4, 1 and 2
+	return((inp & 0xFF) << 24) | ((inp & 0xFF00) << 8) | ((inp & 0xFF0000) >> 8) | ((inp & 0xFF000000) >> 24);
+}
+
+// Master volumeLevel from 0 to 100 percent...
+void SetVolumePlease( float volumeLevel )
+{
+	
+	gSfxVolume = volumeLevel;
+
+	Pcm::PCMAudio_SetStreamGlobalVolume( ( unsigned int ) volumeLevel );
+/*
+	int vol = ( int )PERCENT( volumeLevel, MAX_VOLUME );
+	if ( vol > MAX_VOLUME )
+		vol = MAX_VOLUME;
+	
+	Dbg_MsgAssert( vol >= 0.0f,( "Can't have negative main volume." ));
+
+	int i;
+	// set master volume for both cores, both blocking
+	for ( i = 0; i < 2; i++ )
+	{
+		
+		if ( !CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_MVOLL, vol ) )
+		{
+			Dbg_MsgAssert( 0,( "Error Setting Left Vol on core %d\n",i));
+		}
+		if ( !CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_MVOLR, vol ) )
+		{
+			Dbg_MsgAssert( 0,( "Error Setting Right Vol on core %d\n",i));
+		}
+	}*/
+} // end of SetVolumePlease( )
+
+void ReverbOff( void )
+{
+	
+	int i;
+	sceSdEffectAttr r_attr;
+
+	sTargetReverbDepth = sCurrentReverbDepth = 0;
+	// --- set reverb attribute
+	r_attr.depth_L  = 0;
+	r_attr.depth_R  = 0;
+	r_attr.delay    = 0;
+	r_attr.feedback = 0;
+	r_attr.mode = SD_REV_MODE_OFF;
+#if REVERB_ONLY_ON_CORE_0
+	i = 0;  // just set reverb on core 0 (not the music core or any sfx on core 1)
+#else
+	for ( i = 0; i < 2; i++ )
+#endif
+	{
+		// Shut volume first
+		if(!CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_EVOLL , sTargetReverbDepth ) )
+			Dbg_Message("Error setting reverb2\n");
+		if(!CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_EVOLR , sTargetReverbDepth ) )
+			Dbg_Message("Error setting reverb3\n");
+
+		// just turning off reverb on all voices...
+		if(!CSpuManager::sSendSdRemote( rSdSetSwitch, i | SD_S_VMIXEL , 0 ) )
+			Dbg_Message("Error setting reverb4\n");
+		if(!CSpuManager::sSendSdRemote( rSdSetSwitch, i | SD_S_VMIXER , 0 ) )
+			Dbg_Message("Error setting reverb5\n");
+		if( !CSpuManager::sSendSdRemote( rSdSetEffectAttr, i, (uint32) &r_attr ) )
+			Dbg_Message( "Error setting reverb\n" );
+	}
+}
+
+#define REVERB_STEP ( ( MAX_REVERB_DEPTH / 20 ) + 1 )
+
+void DoReverbFade( void )
+{
+	int i;
+	if ( sTargetReverbDepth != sCurrentReverbDepth )
+	{
+		if ( sCurrentReverbDepth < sTargetReverbDepth )
+		{
+			sCurrentReverbDepth += REVERB_STEP;
+			if ( sCurrentReverbDepth > sTargetReverbDepth )
+			{
+				sCurrentReverbDepth = sTargetReverbDepth;
+			}
+		}
+		else
+		{
+			sCurrentReverbDepth -= REVERB_STEP;
+			if ( sCurrentReverbDepth < sTargetReverbDepth )
+			{
+				sCurrentReverbDepth = sTargetReverbDepth;
+				if ( !sTargetReverbDepth )
+				{
+					ReverbOff( );
+				}
+			}
+		}
+		
+#if REVERB_ONLY_ON_CORE_0
+		i = 0;  // just set reverb on core 0 (not the music core or any sfx on core 1)
+#else
+		for ( i = 0; i < 2; i++ )
+#endif
+		{
+			if(!CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_EVOLL , sCurrentReverbDepth ) )
+				Dbg_Message("Error setting reverb2\n");
+			
+			if(!CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_EVOLR , sCurrentReverbDepth ) )
+				Dbg_Message("Error setting reverb3\n");
+		}
+	}
+}
+
+void InitReverbAddr( void )
+{
+#if REVERB_ONLY_ON_CORE_0
+	if ( !CSpuManager::sSendSdRemote( rSdSetAddr, 0 | SD_A_ESA, CORE_0_EFFECTS_START_ADDRESS ) )
+		Dbg_Message( "Error setting reverb\n" );
+	if ( !CSpuManager::sSendSdRemote( rSdSetAddr, 0 | SD_A_EEA, CORE_0_EFFECTS_END_ADDRESS ) )
+		Dbg_Message( "Error setting reverb\n" );
+#else
+	if ( !CSpuManager::sSendSdRemote( rSdSetAddr, 0 | SD_A_ESA, CORE_0_EFFECTS_START_ADDRESS ) )
+		Dbg_Message( "Error setting reverb\n" );
+	if ( !CSpuManager::sSendSdRemote( rSdSetAddr, 0 | SD_A_EEA, CORE_0_EFFECTS_END_ADDRESS ) )
+		Dbg_Message( "Error setting reverb\n" );
+		
+	if ( !CSpuManager::sSendSdRemote( rSdSetAddr, 1 | SD_A_ESA, CORE_1_EFFECTS_START_ADDRESS ) )
+		Dbg_Message( "Error setting reverb\n" );
+	if ( !CSpuManager::sSendSdRemote( rSdSetAddr, 1 | SD_A_EEA, CORE_1_EFFECTS_END_ADDRESS ) )
+		Dbg_Message( "Error setting reverb\n" );
+#endif
+}
+
+//--------------------------------------------------------------------------------------------
+// Function: void SetReverb(void)
+// Description: Sets reverb level and type
+// Parameters:  none
+//
+// Returns:
+//-------------------------------------------------------------------------------------------
+void SetReverbPlease( float reverbLevel, int reverbType, bool instant )
+{
+	
+	int i;
+	sceSdEffectAttr r_attr;
+
+	Dbg_MsgAssert( reverbType >= 0,( "Bad reverb mode." ));
+	Dbg_MsgAssert( reverbType < NUM_REVERB_MODES,( "Bad reverb mode..." ));
+	
+	if ( !reverbLevel )
+	{
+		sTargetReverbDepth = 0;
+		if ( instant )
+		{
+			ReverbOff( );
+		}
+	}
+	else
+	{
+		// Calling this in case it wasn't set before.  There is a bug with the SPU reg set code where some
+		// messages are lost
+		InitReverbAddr();
+
+		sTargetReverbDepth = ( int )PERCENT( MAX_REVERB_DEPTH, reverbLevel );
+		
+		// --- set reverb attribute
+		r_attr.depth_L  = 0x3fff;
+		r_attr.depth_R  = 0x3fff;
+		r_attr.delay    = 30;
+		r_attr.feedback = 200;
+		r_attr.mode = ReverbModes[ reverbType ];
+#if REVERB_ONLY_ON_CORE_0
+		i = 0;
+#else		
+		for( i = 0; i < 2; i++ )
+#endif
+		{
+			if( !CSpuManager::sSendSdRemote( rSdSetEffectAttr, i, (uint32) &r_attr ) )
+				Dbg_Message( "Error setting reverb\n" );
+	
+			if(!CSpuManager::sSendSdRemote( rSdSetCoreAttr, i | SD_C_EFFECT_ENABLE, 1 ) )
+				Dbg_Message("Error setting reverb\n");
+			if ( instant )
+			{
+				if(!CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_EVOLL , sTargetReverbDepth ) )
+					Dbg_Message("Error setting reverb2\n");
+				if(!CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_EVOLR , sTargetReverbDepth ) )
+					Dbg_Message("Error setting reverb3\n");
+				sCurrentReverbDepth = sTargetReverbDepth;
+			}
+
+			uint32 reverb_voices = (i == NUM_NO_REVERB_CORE) ? VOICES_MINUS_NON_REVERB : VOICES_MINUS_STREAMS;
+			if(!CSpuManager::sSendSdRemote( rSdSetSwitch, i | SD_S_VMIXEL , reverb_voices ) )
+				Dbg_Message("Error setting reverb4\n");
+			if(!CSpuManager::sSendSdRemote( rSdSetSwitch, i | SD_S_VMIXER , reverb_voices ) )
+				Dbg_Message("Error setting reverb5\n");
+			
+		}
+	}
+} // end of SetReverb( )
+
+//--------------------------------------------------------------------------------------------
+// Function    : void StopSound(int voiceNumber)
+// Description : turns off voice
+// Parameters  : voiceNumber - voice to stop
+//
+// Returns:
+// Notes: 
+//-------------------------------------------------------------------------------------------
+void PS2Sfx_StopSound(int voiceNumber)
+{
+	//Dbg_Message("Turning off voice number %d", voiceNumber);
+
+	int coreUsed;
+
+	if ( voiceNumber >= NUM_VOICES_PER_CORE )
+	{
+		voiceNumber -= NUM_VOICES_PER_CORE;
+		coreUsed = SD_CORE_1;
+	}
+	else
+	{
+		coreUsed = SD_CORE_0;
+	}
+
+	if ( !CSpuManager::sSendSdRemote( rSdSetSwitch, coreUsed | SD_S_KOFF , 1 << voiceNumber ) )
+		Dbg_MsgAssert( 0,( "Failed to turn off\n" ));
+
+	// turn off looping sound flag (if it's even on...)
+	// Garrett: Took out this line so that we don't possibly turn off and on a voice in the same SPU tick.
+	//LoopingSounds[ coreUsed ] &= ~( 1 << voiceNumber );
+//	SoundsThatWereSwitchedOff[ coreUsed ] |= ( 1 << voiceNumber );
+	// might not want this... can't write too often to a voice...
+	// Garrett: Took out this line so that we don't possibly turn off and on a voice in the same SPU tick.
+	// The only bad thing is this won't be cleared until the next frame.
+	//VoicesUsed[ coreUsed ] &= ~( 1 << voiceNumber );
+	VoicesCleared[ coreUsed ] |= 1 << voiceNumber;
+	return;
+}
+
+bool LoadSoundPlease( const char *sfxName, uint32 checksum, PlatformWaveInfo *pInfo, bool loadPerm )
+{
+	
+	Dbg_MsgAssert( pInfo,( "Null pointer to PlatformWaveInfo." ));
+	return PS2Sfx_LoadVAG( pInfo, sfxName, loadPerm, &pInfo->pitchAdjustmentForSamplingRate );
+} // end of LoadSoundPlease( )
+
+int	GetCore( int voiceNumber )
+{
+	
+	if ( voiceNumber >= NUM_VOICES_PER_CORE )
+	{
+		voiceNumber -= NUM_VOICES_PER_CORE;
+		Dbg_MsgAssert( voiceNumber < NUM_VOICES_PER_CORE,( "Bad voice number." ));
+		return ( SD_CORE_1 );
+	}
+	Dbg_MsgAssert( voiceNumber >= 0,( "Bad voice number." ));
+	return ( SD_CORE_0 );
+	
+} // end of GetCore( )
+
+//void SetVoiceParameters( int voiceNumber, float volL, float volR, float fPitch )
+void SetVoiceParameters( int voiceNumber, sVolume *p_vol, float fPitch )
+{
+	int coreUsed = GetCore( voiceNumber );
+
+	if ( coreUsed == SD_CORE_1 )
+	{
+		voiceNumber -= NUM_VOICES_PER_CORE;
+	}
+	Dbg_MsgAssert( voiceNumber < NUM_VOICES_PER_CORE,( "What in tarnation?  Voice out of range." ));
+
+	// adjust for SFX Volume level:
+	p_vol->PercentageAdjustment( gSfxVolume );
+
+	int lVol = (int)PERCENT( MAX_VOLUME, p_vol->GetChannelVolume( 0 ));	
+	int rVol = (int)PERCENT( MAX_VOLUME, p_vol->GetChannelVolume( 1 ));	
+
+	if ( lVol > MAX_VOLUME )
+		lVol = MAX_VOLUME;
+	else if ( lVol < -MAX_VOLUME )
+		lVol = -MAX_VOLUME;
+
+	if ( rVol > MAX_VOLUME )
+		rVol = MAX_VOLUME;
+	else if ( rVol < -MAX_VOLUME )
+		rVol = -MAX_VOLUME;
+
+	if ( fPitch )
+	{
+		int pitch = ( int )PERCENT( UNALTERED_PITCH, fPitch );
+		if ( pitch > MAX_PITCH )
+			pitch = MAX_PITCH;
+		if ( pitch <= 0 )
+			pitch = 1;
+		if ( !CSpuManager::sSendSdRemote( rSdSetParam, coreUsed | ( voiceNumber << 1 ) | SD_VP_PITCH, pitch ) )
+			Dbg_Message("Failed pitch setting\n");
+	}
+
+	// for phasing effects (of sounds behind you), set the volume
+	// to 0x7fff (left channel will be negative if sound is behind
+	// us)
+	if ( lVol < 0 )
+	{
+		lVol = 0x7fff + lVol;
+	}
+	if ( rVol < 0 )		// Just in case we start phase shifting the right side instead
+	{
+		rVol = 0x7fff + rVol;
+	}
+	if ( !CSpuManager::sSendSdRemote( rSdSetParam, coreUsed | ( voiceNumber << 1 ) | SD_VP_VOLL, lVol ) )
+		Dbg_Message( "Error1\n" );
+	if ( !CSpuManager::sSendSdRemote( rSdSetParam, coreUsed | ( voiceNumber << 1 ) | SD_VP_VOLR, rVol ) )
+		Dbg_Message("Error2\n");
+} // end of SetVoiceParameters( )
+
+int PlaySoundPlease( PlatformWaveInfo *pInfo, sVolume *p_vol, float pitch, bool no_reverb )
+{
+	#if NO_SOUND
+	return ( -1 );
+	#endif
+
+	int voiceAvailableFlags;
+	int i;
+
+	if ( no_reverb )
+	{
+		voiceAvailableFlags = PS2Sfx_GetVoiceStatus( NUM_NO_REVERB_CORE );
+
+		for ( i = NUM_NO_REVERB_START_VOICE; i < ( NUM_NO_REVERB_VOICES + NUM_NO_REVERB_START_VOICE ); i++ )
+		{
+			if ( voiceAvailableFlags & ( 1 << i ) )
+			{
+				return ( PS2Sfx_PlaySound( pInfo, i + (NUM_NO_REVERB_CORE * NUM_VOICES_PER_CORE), p_vol->GetChannelVolume( 0 ), p_vol->GetChannelVolume( 1 ), pitch ));
+			}
+		}
+
+//		Dbg_MsgAssert(0, ("Ran out of NO_REVERB voices"));
+		printf("WARNING: Ran out of NO_REVERB voices - using reverb voices\n");
+	}
+
+	voiceAvailableFlags = PS2Sfx_GetVoiceStatus( 0 );
+	for ( i = 0; i < NUM_VOICES_PER_CORE; i++ )
+	{
+		if ( voiceAvailableFlags & ( 1 << i ) )
+		{
+			//gAvailableVoices[ 0 ] &= ~( 1 << i );
+			return ( PS2Sfx_PlaySound( pInfo, i, p_vol->GetChannelVolume( 0 ), p_vol->GetChannelVolume( 1 ), pitch ));
+		}
+	}
+	
+	// 2nd core ( 1st core busy, all voices used )
+	// For now, allow reverb sounds to use non-reverb voices.  They are at the end, anyway
+	voiceAvailableFlags = PS2Sfx_GetVoiceStatus( 1 );
+	for ( i = 0; i < NUM_VOICES_PER_CORE; i++ )
+	{
+		if ( voiceAvailableFlags & ( 1 << i ) )
+		{
+			//gAvailableVoices[ 1 ] &= ~( 1 << i );
+			return ( PS2Sfx_PlaySound( pInfo, i + NUM_VOICES_PER_CORE, p_vol->GetChannelVolume( 0 ), p_vol->GetChannelVolume( 1 ), pitch ));
+		}
+	}
+
+#ifdef	__NOPT_ASSERT__
+	CSpuManager::sPrintStatus();
+#endif
+	Dbg_MsgAssert(0, ("Can't play sound.  Out of voices"));
+	return ( -1 );
+} // end of PlaySoundPlease( )
+
+void StopSoundPlease( int whichVoice )
+{
+
+	
+	if ( VoiceIsOn( whichVoice ) )
+	{
+		PS2Sfx_StopSound( whichVoice );
+	}
+} // end of StopSoundPlease( )
+
+void PauseSoundsPlease( void )
+{
+	sVolume silent_volume;
+	silent_volume.SetSilent();
+
+	// just turn the volume down on all playing voices...
+	for( int i = 0; i < NUM_VOICES; i++ )
+	{
+		if( VoiceIsOn( i ))
+		{
+			SetVoiceParameters( i, &silent_volume );
+		}
+	}
+}// end of PauseSounds( )
+
+//--------------------------------------------------------------------------------------------
+// Function    : void StopAllSoundFX(void)
+// Description : turns off all voices
+// Parameters  : 
+//
+// Returns:
+// Notes: 
+//-------------------------------------------------------------------------------------------
+void StopAllSoundFX( void )
+{
+	
+	int core;
+	Pcm::StopMusic( );
+	Pcm::StopStreams( );
+	for ( core = 0; core < NUM_CORES; core++ )
+	{
+//		SoundsThatWereSwitchedOff[ core ] = ( ( 1 << NUM_VOICES_PER_CORE ) - 1 );
+		VoicesUsed[ core ] = 1;		// So that the KOFF check thinks there are sounds to kill
+		VoicesCleared[ core ] = 0;
+		LoopingSounds[ core ] = 0;
+//		Dbg_Message( "Stopping all sfx -- remind Matt to fix what this is going to do to streams!!!" );
+		CSpuManager::sClean();
+		CSpuManager::sSendSdRemote( rSdSetSwitch, core | SD_S_KOFF, VOICES_MINUS_STREAMS );
+		CSpuManager::sSendSdRemote( rSdSetSwitch, core | SD_S_ENDX, 0 );
+		VoicesUsed[ core ] = 0;
+	}
+	return;
+}
+
+//--------------------------------------------------------------------------------------------
+// Function    : void PlaySound(int voiceNumber,int pitch,int volume)
+// Description : Plays voice
+// Parameters  : voiceNumber - voice to play
+//		 pitch - pitch to play at
+//		 volume - volume to play at
+//
+// Returns: voice number
+//-------------------------------------------------------------------------------------------
+int PS2Sfx_PlaySound( PlatformWaveInfo *pInfo, int voiceNumber, float volL, float volR, float pitch )
+{
+	
+	int coreUsed = GetCore( voiceNumber );
+	int voiceNumOnCore;
+
+	if ( coreUsed == SD_CORE_1 )
+	{
+		voiceNumOnCore = voiceNumber - NUM_VOICES_PER_CORE;
+	}
+	else
+		voiceNumOnCore = voiceNumber;
+
+	if ( !CSpuManager::sSendSdRemote( rSdSetAddr, coreUsed | ( voiceNumOnCore << 1 ) | SD_VA_SSA, pInfo->vagAddress ) )
+		Dbg_Message( "Error4\n" );
+	
+	// adjust pitch to account for lower sampling rates:
+	pitch = PERCENT( pitch, pInfo->pitchAdjustmentForSamplingRate );
+	
+	// should be voiceNumber, not voiceNumOnCore:
+	sVolume vol;
+	vol.SetChannelVolume( 0, volL );
+	vol.SetChannelVolume( 1, volR );
+	SetVoiceParameters( voiceNumber, &vol, pitch );
+	
+	if ( !CSpuManager::sSendSdRemote( rSdSetSwitch, coreUsed | SD_S_KON, 0x1 << voiceNumOnCore ) )
+		Dbg_Message("Failed setswitch\n");
+	
+	if ( pInfo->looping )
+	{
+		LoopingSounds[ coreUsed ] |= ( 1 << voiceNumOnCore );
+		//Dbg_Message("Setting looping sound on voice %d core %d", voiceNumOnCore, coreUsed);
+		//Dbg_Assert(0);
+	}
+
+	// Set the VoicesUsed flag.
+	VoicesUsed[ coreUsed ] |= 1 << voiceNumOnCore;
+//	printf( "voice %d\n", voiceNumber );
+	//Dbg_Message("Voices Used (%x %x) Cleared (%x %x) Looped (%x %x) Voice Status (%x %x)", VoicesUsed[0], VoicesUsed[1], VoicesCleared[0], VoicesCleared[1],
+	//																  LoopingSounds[0], LoopingSounds[1], CSpuManager::sGetVoiceStatus(0), CSpuManager::sGetVoiceStatus(1));
+
+	return ( voiceNumber );
+} // end of PS2Sfx_PlaySound( )
+
+void CleanUpSoundFX( void )
+{
+	
+	StopAllSoundFX( );
+	SpuRAMUsedTemp = 0;
+	SetReverbPlease( 0 );
+}// end of CleanUpSoundFX( )
+
+#define NUM_VOICES_INCLUDING_STREAMS_PER_CORE 24
+
+//--------------------------------------------------------------------------------------------
+// Function    : void InitSound( void )
+// Description : initialise sound system
+// Parameters  : 
+//	
+// Returns:
+// Notes: 
+//--------------------------------------------------------------------------------------------
+void InitSoundFX( CSfxManager *p_sfx_manager )
+{
+	// Set default volume type.
+	if( p_sfx_manager )
+	{
+		p_sfx_manager->SetDefaultVolumeType( VOLUME_TYPE_2_CHANNEL_DOLBYII );
+	}
+	
+	int i, j;
+	// set master volume for both cores, both blocking
+	for ( i = 0; i < 2; i++ )
+	{
+		
+		if ( !CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_MVOLL, MAX_VOLUME ) )
+		{
+			Dbg_MsgAssert( 0,( "Error Setting Left Vol on core %d\n",i));
+		}
+		if ( !CSpuManager::sSendSdRemote( rSdSetParam, i | SD_P_MVOLR, MAX_VOLUME ) )
+		{
+			Dbg_MsgAssert( 0,( "Error Setting Right Vol on core %d\n",i));
+		}
+	}
+	
+//	SetVolumePlease( 100 );
+	
+	// Sets the reverb address registers
+	InitReverbAddr();
+			
+	CleanUpSoundFX( );
+
+	// Set the envelope info at the end because we sometimes lose messages
+	for ( i = 0; i < 2; i++ )
+	{
+		for ( j = 0; j < NUM_VOICES_INCLUDING_STREAMS_PER_CORE; j++ )
+		{
+			CSpuManager::sSendSdRemote ( rSdSetParam, i | ( j << 1 ) | SD_VP_ADSR1, 0x00ff );
+			CSpuManager::sSendSdRemote ( rSdSetParam, i | ( j << 1 ) | SD_VP_ADSR2, 0x1fc0 );
+		}
+	}
+
+	return;
+}
+
+void PS2Sfx_InitCold( void )
+{
+	
+
+	CSpuManager::sInit();
+
+//	int i;
+//	for ( i = 0; i < NUM_CORES; i++ )
+//	{
+//		gLastVoiceAvailableUpdate[ i ] = 0;
+//		gAvailableVoices[ i ] = 0;
+//	}
+
+} // end of PS2Sfx_Init( )
+
+//--------------------------------------------------------------------------------------------
+// Function    : int GetVoiceStatus( int core )
+// Description : returns bitmask showing which voices are available
+// Parameters  : core - SPU core
+//	
+// Returns     : integer value which is the bitmask for voices
+//--------------------------------------------------------------------------------------------
+int PS2Sfx_GetVoiceStatus( int core )
+{
+	
+#if 1
+	int availableVoices = CSpuManager::sGetVoiceStatus(core);
+#else
+	int availableVoices;
+
+	if ( gLastVoiceAvailableUpdate[ core ] != Tmr::GetRenderFrame( ) )
+	{
+		gLastVoiceAvailableUpdate[ core ] = Tmr::GetRenderFrame( );
+		CSpuManager::sWaitForIO();
+		gAvailableVoices[ core ] = sceSdRemote( 1, rSdGetSwitch, core | SD_S_ENDX );
+	}
+	// available voices at this point will show all voices that have reached the end...
+	availableVoices = gAvailableVoices[ core ];
+#endif
+
+	// except we've got to remove looping sounds (they set the ENDX flag first time they reach LOOPEND)
+	availableVoices &= ~LoopingSounds[ core ];
+	// include the voices that have not ever been used yet (if never used,
+	// a voice never has had a chance to set the ENDX register...)
+	availableVoices |= ~VoicesUsed[ core ];
+	
+	return ( availableVoices );
+}
+
+bool VoiceIsOn( int whichVoice )
+{
+	
+	int whichCore;
+	int voiceAvailableFlags;
+		
+	if ( whichVoice < NUM_VOICES_PER_CORE )
+	{
+		whichCore = 0;
+	}
+	else
+	{
+		whichCore = 1;
+		whichVoice -= NUM_VOICES_PER_CORE;
+	}
+	
+	voiceAvailableFlags = PS2Sfx_GetVoiceStatus( whichCore );
+	return ( ( voiceAvailableFlags & ( 1 << whichVoice ) ) ? false : true );
+} // end of	VoiceIsOn( )
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PerFrameUpdate( void )
+{
+	
+	DoReverbFade( );
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+/// CSpuManager
+
+volatile bool					CSpuManager::s_sd_remote_busy;
+volatile bool					CSpuManager::s_sd_transferring_voice;
+volatile uint32					CSpuManager::s_sd_voice_status[NUM_CORES];
+volatile uint32					CSpuManager::s_sd_voice_status_update_frame[NUM_CORES] = { 0, 0 };
+
+uint32							CSpuManager::s_spu_reg_kon[NUM_CORES] = { 0, 0 };
+uint32							CSpuManager::s_spu_reg_koff[NUM_CORES] = { 0, 0 };
+
+SSifCmdSoundRequestPacket		CSpuManager::s_sd_remote_commands[CSpuManager::MAX_QUEUED_COMMANDS] __attribute__ ((aligned(16)));
+volatile int					CSpuManager::s_sd_remote_command_free_index;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSpuManager::sInit()
+{
+	// Init variables
+	s_sd_remote_busy = false;
+	s_sd_remote_command_free_index = 0;
+
+	if ( sceSdRemoteInit( ) == -1 )
+	{
+		Dbg_MsgAssert( 0,( "Error Initialising sound\n"));
+		return;
+	}
+
+	// Initialise IOP side, blocking call
+	if ( sceSdRemote( 1, rSdInit, SD_INIT_COLD ) == -1 )
+	{
+		Dbg_MsgAssert( 0,( "Error Initialising IOP\n"));
+		return;
+	}
+
+	// Make sure the DMA ID's are 0 so we don't wait for random ID's
+	for (int i = 0; i < MAX_QUEUED_COMMANDS; i++)
+	{
+		s_sd_remote_commands[i].m_request.m_ee_dma_id = 0;
+	}
+
+	sceSifAddCmdHandler(SOUND_RESULT_COMMAND, s_result_callback, NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSpuManager::sClean()
+{
+	for (int core = 0; core < NUM_CORES; core++)
+	{
+		s_spu_reg_kon[core] = 0;
+		s_spu_reg_koff[core] = 0;
+		s_sd_voice_status[core] = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef	__NOPT_ASSERT__
+void			CSpuManager::sPrintStatus()
+{
+	for (int core = 0; core < NUM_CORES; core++)
+	{
+		Dbg_Message("Core %d: Voices Used (%x) Cleared (%x) Looped (%x) Voice Status (%x)", core, VoicesUsed[core], VoicesCleared[core],
+																		  LoopingSounds[core], sGetVoiceStatus(core));
+		Dbg_Message("        Saved KON value (%x) Saved KOFF value (%x)", s_spu_reg_kon[core], s_spu_reg_koff[core]);
+	}
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSpuManager::sWaitForIO()
+{
+	while (s_sd_remote_busy)
+		;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSpuManager::sUpdateStatus()
+{
+	//scePrintf("******** Doing CSpuManager::sUpdateStatus() on frame %d\n", Tmr::GetRenderFrame( ));
+	for (int core = 0; core < NUM_CORES; core++)
+	{
+		sSendSdRemote( rSdGetSwitch, core | SD_S_ENDX, 0 );
+
+		// Clear out switches
+		//s_spu_reg_kon[core] = 0;
+		//s_spu_reg_koff[core] = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32			CSpuManager::sGetVoiceStatus(int core)
+{
+	// For now, just go into a wait loop.  In the future, we can probably use the old value
+	while (s_sd_voice_status_update_frame[core] != Tmr::GetRenderFrame( ))
+		;
+
+	if (s_sd_voice_status_update_frame[core] != Tmr::GetRenderFrame( ))
+	{
+		Dbg_MsgAssert(0, ("Sound voice status not current: need frame %d but have frame %d", Tmr::GetRenderFrame( ), s_sd_voice_status_update_frame[core]));
+	}
+
+	return s_sd_voice_status[core];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CSpuManager::sSendSdRemote(uint16 function, uint16 entry, uint32 value)
+{
+	return s_queue_new_command(function, entry, value);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CSpuManager::sVoiceTrans(uint16 channel, uint16 mode, void *p_iop_addr, uint32 *p_spu_addr, uint32 size)
+{
+	Dbg_Assert(!s_sd_transferring_voice);
+
+#if 0
+	sceSdRemote(1, rSdVoiceTrans, channel, mode, p_iop_addr, p_spu_addr, size);
+	sceSdRemote(1, rSdVoiceTransStatus, channel, SD_TRANS_STATUS_WAIT);
+#else
+	s_sd_transferring_voice = true;
+
+	// Send transfer command first
+	if (!s_queue_new_command(rSdVoiceTrans, channel, mode, (uint32) p_iop_addr, (uint32) p_spu_addr, size))
+	{
+		return false;
+	}
+
+	if (!s_queue_new_command(rSdVoiceTransStatus, channel, SD_TRANS_STATUS_WAIT))
+	{
+		return false;
+	}
+
+	s_wait_for_voice_transfer();
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CSpuManager::s_queue_new_command(uint16 function, uint16 entry, uint32 value, uint32 arg0, uint32 arg1, uint32 arg2)
+{
+	static int s_num_sound_requests_sent = 0;
+
+	if (function == rSdSetSwitch)
+	{
+#if 0
+		// If we are sending a switch, combine with old settings to make sure we don't overwrite them.
+		// Switches are only updated every 1/48000 a second on the SPU.
+		value = s_get_switch_value(entry, value);
+#endif
+		s_update_local_status(entry, value);
+	}
+
+	int nextFreeIndex = (s_sd_remote_command_free_index + 1) % MAX_QUEUED_COMMANDS;
+	SSifCmdSoundRequestPacket *p_command = &s_sd_remote_commands[s_sd_remote_command_free_index];
+
+	// Wait for old send to complete
+	if (p_command->m_request.m_ee_dma_id)
+	{
+		//Dbg_Assert(0);
+		int wait_num = 0;
+		while(sceSifDmaStat(p_command->m_request.m_ee_dma_id) >= 0)
+		{
+			if ((wait_num++ % 1000) == 0)
+				scePrintf("Waiting for sound request buffer\n");
+		}
+	}
+
+	if (function == rSdSetEffectAttr)
+	{
+		sceSdEffectAttr *p_attr = (sceSdEffectAttr *) value;
+
+		p_command->m_request.m_function	= function;
+		p_command->m_request.m_entry		= entry;
+		p_command->m_request.m_value		= p_attr->mode;
+		p_command->m_request.m_arg_0		= ((uint32) p_attr->depth_L << 16) | (uint32) p_attr->depth_R;
+		p_command->m_request.m_arg_1		= p_attr->delay;
+		p_command->m_request.m_arg_2		= p_attr->feedback;
+	}
+	else
+	{
+		p_command->m_request.m_function	= function;
+		p_command->m_request.m_entry		= entry;
+		p_command->m_request.m_value		= value;
+		p_command->m_request.m_arg_0		= arg0;
+		p_command->m_request.m_arg_1		= arg1;
+		p_command->m_request.m_arg_2		= arg2;
+	}
+	p_command->m_request.m_pad[0] = ++s_num_sound_requests_sent;
+
+	// Send the actual command
+	//scePrintf("CSpuManager: sending command %x with entry %x and value %x\n", function, entry, value);
+	p_command->m_request.m_ee_dma_id = sceSifSendCmd(SOUND_REQUEST_COMMAND, p_command, sizeof(*p_command), 0, 0, 0);
+
+	s_sd_remote_command_free_index = nextFreeIndex;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32			CSpuManager::s_get_switch_value(uint16 entry, uint32 value)
+{
+	int core = entry & 0x1;
+	int switch_reg = entry & 0xFF00;
+
+	uint32 new_value = value;
+
+	switch (switch_reg)
+	{
+	case SD_S_KON:
+		if(s_spu_reg_koff[core] & value)
+		{
+			//Dbg_MsgAssert(0, ("Turning on a voice that was turned off this frame."));
+			Dbg_Message("Turning on a voice that was turned off this frame.");
+			s_spu_reg_koff[core] &= ~(s_spu_reg_koff[core] & value);	// turn off those bits
+		}
+
+		s_spu_reg_kon[core] |= value;
+		new_value = s_spu_reg_kon[core];
+
+		break;
+
+	case SD_S_KOFF:
+		if(s_spu_reg_kon[core] & value)
+		{
+			//Dbg_MsgAssert(0, ("Turning off a voice that was turned on this frame."));
+			Dbg_Message("Turning off a voice that was turned on this frame.");
+			s_spu_reg_kon[core] &= ~(s_spu_reg_kon[core] & value);		// turn off those bits
+		}
+
+		s_spu_reg_koff[core] |= value;
+		new_value = s_spu_reg_koff[core];
+
+		break;
+
+	default:
+		break;
+	}
+
+	return new_value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSpuManager::s_update_local_status(uint16 entry, uint32 value)
+{
+	int core = entry & 0x1;
+	int switch_reg = entry & 0xFF00;
+
+	switch (switch_reg)
+	{
+	case SD_S_KON:
+		//Dbg_Message("************ ORing KON%d with %x", core, value); 
+		if(s_spu_reg_koff[core] & value)
+		{
+#ifdef	__NOPT_ASSERT__
+			sPrintStatus();
+#endif
+			Dbg_Message("Core %d: Set KON value (%x)", core, value);
+			Dbg_MsgAssert(0, ("Turning on a voice that was turned off this frame."));
+			Dbg_Message("Turning on a voice that was turned off this frame.");
+			s_spu_reg_koff[core] &= ~(s_spu_reg_koff[core] & value);	// turn off those bits
+		}
+
+		s_spu_reg_kon[core] |= value;
+
+		// Make sure the voice is not used again before SPU update is done
+		s_sd_voice_status[core] &= ~value;
+		break;
+
+	case SD_S_KOFF:
+		//Dbg_Message("************ ORing KOFF%d with %x", core, value); 
+		if(!(value & ~PS2Sfx_GetVoiceStatus(core)))
+		{
+#ifdef	__NOPT_ASSERT__
+			sPrintStatus();
+#endif
+			Dbg_MsgAssert(0, ("Can't set KOFF with %x: status %x raw status %x", value, sGetVoiceStatus(core), s_sd_voice_status[core]));
+			Dbg_Message("Can't set KOFF with %x: status %x raw status %x", value, sGetVoiceStatus(core), s_sd_voice_status[core]);
+		}
+		if(s_spu_reg_kon[core] & value)
+		{
+#ifdef	__NOPT_ASSERT__
+			//sPrintStatus();
+#endif
+			//Dbg_Message("Core %d: Set KOFF value (%x)", core, value);
+			//Dbg_MsgAssert(0, ("Turning off a voice that was turned on this frame."));
+			Dbg_Message("Turning off a voice that was turned on this frame.");
+			s_spu_reg_kon[core] &= ~(s_spu_reg_kon[core] & value);		// turn off those bits
+		}
+
+		s_spu_reg_koff[core] |= value;
+
+		// Lets not clear the voice now.  Wait until we get the SPU status break;
+		break;
+
+	default:
+		break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSpuManager::s_result_callback(void *p, void *q)
+{
+	SSifCmdSoundResultPacket *p_result = (SSifCmdSoundResultPacket *) p;
+
+	if (p_result->m_function == rSdVoiceTransStatus)
+	{
+		s_sd_transferring_voice = false;
+	}
+	else if (p_result->m_function == rSdGetSwitch)
+	{
+		int core = p_result->m_entry & 0x1;
+
+		//scePrintf("************ CSpuManager: received result from command %x with entry %x and result %x for core %d\n", p_result->m_function, p_result->m_entry, p_result->m_result, core);
+
+		if ((p_result->m_entry & 0xFF00) == SD_S_ENDX)
+		{
+			s_sd_voice_status[core] = p_result->m_result;
+			s_sd_voice_status_update_frame[core] = Tmr::GetRenderFrame( );
+
+			// We can clear this out now since the status is up-to-date
+			VoicesUsed[ core ] &= ~VoicesCleared[ core ];
+			//if (LoopingSounds[ core ] & VoicesCleared[ core ])
+		//	{
+		//		scePrintf("Clearing looping sound voices %x on core %d\n", LoopingSounds[ core ] & VoicesCleared[ core ], core);
+		//	}
+			LoopingSounds[ core ] &= ~VoicesCleared[ core ];
+			VoicesCleared[ core ] = 0;
+
+			// Keep bits that haven't been updated yet
+			s_spu_reg_kon[core] &= (s_sd_voice_status[core] | ~VoicesUsed[core]);
+			s_spu_reg_koff[core] &= ~(s_sd_voice_status[core] | ~VoicesUsed[core]);
+
+			// And remove any leftover KON voices
+			s_sd_voice_status[core] &= ~s_spu_reg_kon[core];
+		}
+	}
+
+	if (p_result->m_panic)
+	{
+		Dbg_MsgAssert(0, ("Error on IOP module while processing SPU request %x", p_result->m_function));
+	}
+
+	ExitHandler();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSpuManager::s_wait_for_voice_transfer()
+{
+	while (s_sd_transferring_voice)
+		;
+}
+
+} // namespace Sfx
diff --git a/Code/Gel/SoundFX/NGPS/p_sfx.h b/Code/Gel/SoundFX/NGPS/p_sfx.h
new file mode 100644
index 0000000..fad25d0
--- /dev/null
+++ b/Code/Gel/SoundFX/NGPS/p_sfx.h
@@ -0,0 +1,141 @@
+/*	Playstation 2 sound support functions.
+	mjd	jan 10, 2001
+*/
+#ifndef __MODULES_P_SFX_H
+#define __MODULES_P_SFX_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+namespace Sfx
+{
+
+struct sVolume;
+class CSfxManager;
+
+		  
+#if NUM_STREAMS > 2
+#define NUM_VOICES_PER_CORE		(24 - NUM_STREAMS)  // leave #stream voices per core for streaming audio off the CD and shit...
+#else
+#define NUM_VOICES_PER_CORE		22  // leave 2 voices per core for streaming audio off the CD and shit...
+#endif
+
+#define NUM_CORES				2
+#define NUM_VOICES				( NUM_VOICES_PER_CORE * NUM_CORES )
+
+// Non-reverb voices.  Make sure they are always the last voices on the last core, since we presently allow
+// reverb sounds to use all voices.
+#define NUM_NO_REVERB_VOICES		10
+#define NUM_NO_REVERB_CORE			MUSIC_CORE
+#define NUM_NO_REVERB_START_VOICE	( NUM_VOICES_PER_CORE - NUM_NO_REVERB_VOICES )
+
+// platform specific info needed per wave (stored in the WaveTable):
+struct PlatformWaveInfo{
+	int		vagAddress; //PS2 waves played by setting a voice to the address of the wave on the sound chip...	
+	bool	looping;
+	float	pitchAdjustmentForSamplingRate; // figure out how much to alter the pitch due to lower sampling rates than 48000.
+};
+
+// on startup of the game only...
+void PS2Sfx_InitCold( void );
+
+void	InitSoundFX( CSfxManager *p_sfx_manager );
+void	CleanUpSoundFX( void );
+void	StopAllSoundFX( void );
+bool	LoadSoundPlease( const char *sfxName, uint32 checksum, PlatformWaveInfo *pInfo, bool loadPerm = 0 );
+
+int		GetMemAvailable( void );
+
+// returns 0 - ( NUM_VOICES - 1 ), or -1 if no voices are free...
+int		PlaySoundPlease( PlatformWaveInfo *pInfo, sVolume *p_vol, float pitch = 100.0f, bool no_reverb = false );
+
+void	StopSoundPlease( int whichVoice );
+
+// really just turning down the volume on all playing voices...
+// Looping sounds will get the volume adjusted by the owner
+// during the next logic loop.
+// The non-looping sounds will play (with no volume) then stop.
+// This is for when we go into the menus or whatnot...
+void	PauseSoundsPlease( void );
+
+// see reverb types in ReverbModes table (p_sfx.cpp), corresponding to reverb.q script file
+void	SetReverbPlease( float reverbLevel = 0.0f, int reverbMode = 0, bool instant = false );
+
+// volume from -100 to 100 ( negative if the sound is from behind us. ps2 alters the sound... )
+void	SetVolumePlease( float volumeLevel );
+
+// returns true if voice is being used currently.
+bool	VoiceIsOn( int whichVoice );
+
+// change parameters on an active voice:
+// if pitch parameter is zero, pitch won't be changed (lowest pitch is 1)
+void	SetVoiceParameters( int whichVoice, sVolume *p_vol, float pitch = 0.0f );
+
+void	PerFrameUpdate( void );
+
+
+///////////////////////////////////////////////////////////
+// SPU Manager class
+// All the SPU communication should go through here instead of sceSdRemote().
+// It is asynchronous, so it doesn't stall the EE while waiting for the
+// IOP (which can be milliseconds!)
+class CSpuManager
+{
+public:
+	static void				sInit();
+	static void				sUpdateStatus();				// Called at the beginning of every frame to get the voice status
+
+	static uint32			sGetVoiceStatus(int core);		// Gets the already requested status
+	static void				sFlush();
+	static volatile bool	sIsBusy();
+	static void				sWaitForIO();
+
+	static void				sClean();
+
+#ifdef	__NOPT_ASSERT__
+	static void				sPrintStatus();
+#endif
+
+	// Transfer functions
+	static bool				sSendSdRemote(uint16 function, uint16 entry, uint32 value);
+	static bool				sVoiceTrans(uint16 channel, uint16 mode, void *p_iop_addr, uint32 *p_spu_addr, uint32 size);
+
+private:
+	// Constants
+	enum
+	{
+		MAX_QUEUED_COMMANDS	= 28,
+	};
+
+	static void				s_result_callback(void *p, void *q);	// SifCmd callback
+
+	static bool				s_queue_new_command(uint16 function, uint16 entry, uint32 value, uint32 arg0 = 0, uint32 arg1 = 0, uint32 arg2 = 0);
+	static uint32			s_get_switch_value(uint16 entry, uint32 value);
+	static void				s_update_local_status(uint16 entry, uint32 value);
+
+	static void				s_wait_for_voice_transfer();
+
+	// Variables
+	static volatile bool	s_sd_remote_busy;
+	static volatile bool	s_sd_transferring_voice;
+	static volatile uint32	s_sd_voice_status[NUM_CORES];
+	static volatile uint32	s_sd_voice_status_update_frame[NUM_CORES];
+
+	// Switch settings made in frame.  Because the SPU only reads registers every 1/48000 of a sec, we may have
+	// to re-send a switch setting so we don't overwrite an old one.
+	static uint32			s_spu_reg_kon[NUM_CORES];
+	static uint32			s_spu_reg_koff[NUM_CORES];
+
+	// Store multiple request packets, so we don't have to wait for the last DMA to finish before
+	// starting a new one.
+	static SSifCmdSoundRequestPacket	s_sd_remote_commands[MAX_QUEUED_COMMANDS] __attribute__ ((aligned(16)));
+	static volatile int					s_sd_remote_command_free_index;
+};
+
+
+} // namespace Gel
+
+#endif
diff --git a/Code/Gel/SoundFX/Xbox/p_sfx.cpp b/Code/Gel/SoundFX/Xbox/p_sfx.cpp
new file mode 100644
index 0000000..843331d
--- /dev/null
+++ b/Code/Gel/SoundFX/Xbox/p_sfx.cpp
@@ -0,0 +1,1363 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL Library												**
+**																			**
+**	Module:			Sound effects (Sfx)	 									**
+**																			**
+**	File name:		xbox/p_sfx.cpp											**
+**																			**
+**	Created by:		01/10/01	-	dc										**
+**																			**
+**	Description:	XBox Sound effects										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+//#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+int num_buffers = 0;
+
+namespace Sfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define RIFFCHUNK_FLAGS_VALID   0x00000001
+
+// FourCC definitions
+const DWORD FOURCC_RIFF		= 'FFIR';
+const DWORD FOURCC_WAVE		= 'EVAW';
+const DWORD FOURCC_FORMAT	= ' tmf';
+const DWORD FOURCC_DATA		= 'atad';
+const DWORD FOURCC_SAMPLER	= 'lpms';
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+struct VoiceEntry
+{
+	IDirectSoundBuffer*	p_buffer;
+	DWORD				default_frequency;
+};
+
+typedef struct
+{
+    WCHAR *         szName;
+    DSI3DL2LISTENER ds3dl;
+} I3DL2ENVIRONMENT;
+
+/*****************************************************************************
+ *
+ * Name: RIFFHEADER
+ * Desc: For parsing WAV files
+ *
+ ****************************************************************************/
+struct RIFFHEADER
+{
+	FOURCC  fccChunkId;
+	DWORD   dwDataSize;
+};
+
+
+struct SAMPLERCHUNK
+{
+	DWORD	manufacturer;
+	DWORD	product;
+	DWORD	sample_period;
+	DWORD	MIDI_unity_note;
+	DWORD	MIDI_pitch_fraction;
+	DWORD	SMPTE_format;
+	DWORD	SMPTE_offset;
+	DWORD	num_sample_loops;
+	DWORD	sampler_data;
+};
+
+
+/*****************************************************************************
+ *
+ * Name: class CRiffChunk
+ * Desc: RIFF chunk utility class
+ *
+ ****************************************************************************/
+class CRiffChunk
+{
+	FOURCC				m_fccChunkId;       // Chunk identifier
+	const CRiffChunk*	m_pParentChunk;     // Parent chunk
+	void*				m_hFile;
+	DWORD				m_dwDataOffset;     // Chunk data offset
+	DWORD				m_dwDataSize;       // Chunk data size
+	DWORD				m_dwFlags;          // Chunk flags
+
+	public:
+
+    CRiffChunk();
+
+    // Initialization.
+    void	Initialize( FOURCC fccChunkId, const CRiffChunk* pParentChunk, void* hFile );
+	bool	Open();
+    bool    IsValid( void )		{ return !!( m_dwFlags & RIFFCHUNK_FLAGS_VALID ); }
+
+    // Data
+    bool	ReadData( LONG lOffset, VOID* pData, DWORD dwDataSize );
+
+    // Chunk information
+    FOURCC  GetChunkId()  { return m_fccChunkId; }
+    DWORD   GetDataSize() { return m_dwDataSize; }
+};
+
+
+
+
+/*****************************************************************************
+ *
+ * Name: class CWaveFile
+ * Desc: Wave file utility class
+ *
+ ****************************************************************************/
+class CWaveFile
+{
+	void*		m_hFile;            // File handle
+	CRiffChunk	m_RiffChunk;        // RIFF chunk
+	CRiffChunk  m_FormatChunk;      // Format chunk
+	CRiffChunk  m_DataChunk;        // Data chunk
+	CRiffChunk  m_SamplerChunk;		// Sampler chunk, may or may not be present
+    
+	bool		m_ContainsSamplerChunk;
+
+	public:
+
+    CWaveFile( void );
+    ~CWaveFile( void );
+
+    // Initialization
+    bool	Open( const char* strFileName );
+    void    Close( void );
+
+    // File format
+    bool	GetFormat( WAVEFORMATEX* pwfxFormat, DWORD dwFormatSize );
+
+    // File data
+    bool	ReadSample( DWORD dwPosition, VOID* pBuffer, DWORD dwBufferSize, DWORD* pdwRead );
+
+    // File properties
+    void    GetDuration( DWORD* pdwDuration ) { *pdwDuration = m_DataChunk.GetDataSize(); }
+
+	bool	ContainsLoop( void );
+};
+
+
+
+
+
+/*****************************************************************************
+ *
+ * Name: class CSound
+ * Desc: Encapsulates functionality of a DirectSound buffer.
+ *
+ ****************************************************************************/
+class CXBSound
+{
+	public:
+	XBOXADPCMWAVEFORMAT		m_WaveFormat;		// This encapsulates WAVEFORMATEX.
+    DSBUFFERDESC			m_dsbd;
+    DWORD					m_dwBufferSize;
+	void*					m_pRawData;
+	bool					m_Loops;
+
+    bool	Create( const CHAR* strFileName, DWORD dwFlags = 0L );
+    bool	Create( const XBOXADPCMWAVEFORMAT* pwfxFormat, DWORD dwFlags, VOID* pBuffer, DWORD dwBytes );
+    void    Destroy();
+
+	DWORD	GetSampleRate( void ) const;
+
+    CXBSound();
+    ~CXBSound();
+};
+
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+// Because we don't use voices in the same way as on PS2, this array simulates
+// the system. An entry with a NULL pointer means no sound is playing on that
+// 'voice', a valid pointer indicates a sound is playing on that 'voice', and
+// provides details of the buffer.
+static VoiceEntry		VoiceSimulator[NUM_VOICES];
+static IDirectSound8*	pDirectSound	= NULL;
+
+static I3DL2ENVIRONMENT reverbEnvironments[] =
+{
+    { L"Default",           { DSI3DL2_ENVIRONMENT_PRESET_DEFAULT }        },
+    { L"Generic",           { DSI3DL2_ENVIRONMENT_PRESET_GENERIC }        },
+    { L"Padded Cell",       { DSI3DL2_ENVIRONMENT_PRESET_PADDEDCELL }     },
+    { L"Room",              { DSI3DL2_ENVIRONMENT_PRESET_ROOM }           },
+    { L"Bathroom",          { DSI3DL2_ENVIRONMENT_PRESET_BATHROOM }       },
+    { L"Living Room",       { DSI3DL2_ENVIRONMENT_PRESET_LIVINGROOM }     },
+    { L"Stone Room",        { DSI3DL2_ENVIRONMENT_PRESET_STONEROOM }      },
+    { L"Auditorium",        { DSI3DL2_ENVIRONMENT_PRESET_AUDITORIUM }     },
+    { L"Concert Hall",      { DSI3DL2_ENVIRONMENT_PRESET_CONCERTHALL }    },
+    { L"Cave",              { DSI3DL2_ENVIRONMENT_PRESET_CAVE }           },
+    { L"Arena",             { DSI3DL2_ENVIRONMENT_PRESET_ARENA }          },
+    { L"Hangar",            { DSI3DL2_ENVIRONMENT_PRESET_HANGAR }         },
+    { L"Carpeted Hallway",  { DSI3DL2_ENVIRONMENT_PRESET_CARPETEDHALLWAY }},
+    { L"Hallway",           { DSI3DL2_ENVIRONMENT_PRESET_HALLWAY }        },
+    { L"Stone Corridor",    { DSI3DL2_ENVIRONMENT_PRESET_STONECORRIDOR }  },
+    { L"Alley",             { DSI3DL2_ENVIRONMENT_PRESET_ALLEY }          },
+    { L"Forest",            { DSI3DL2_ENVIRONMENT_PRESET_FOREST }         },
+    { L"City",              { DSI3DL2_ENVIRONMENT_PRESET_CITY }           },
+    { L"Mountains",         { DSI3DL2_ENVIRONMENT_PRESET_MOUNTAINS }      },
+    { L"Quarry",            { DSI3DL2_ENVIRONMENT_PRESET_QUARRY }         },
+    { L"Plain",             { DSI3DL2_ENVIRONMENT_PRESET_PLAIN }          },
+    { L"Parking Lot",       { DSI3DL2_ENVIRONMENT_PRESET_PARKINGLOT }     },
+    { L"Sewer Pipe",        { DSI3DL2_ENVIRONMENT_PRESET_SEWERPIPE }      },
+    { L"Underwater",        { DSI3DL2_ENVIRONMENT_PRESET_UNDERWATER }     },
+};
+
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+// Global sfx multiplier, percentage.
+float					gSfxVolume		= 100.0f;
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+//-----------------------------------------------------------------------------
+// Name: CRiffChunk()
+// Desc: Object constructor.
+//-----------------------------------------------------------------------------
+CRiffChunk::CRiffChunk()
+{
+    // Initialize defaults
+    m_fccChunkId   = 0;
+    m_pParentChunk = NULL;
+    m_hFile        = NULL;
+    m_dwDataOffset = 0;
+    m_dwDataSize   = 0;
+    m_dwFlags      = 0;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Initialize()
+// Desc: Initializes the object
+//-----------------------------------------------------------------------------
+VOID CRiffChunk::Initialize( FOURCC fccChunkId, const CRiffChunk* pParentChunk, void* hFile )
+{
+    m_fccChunkId   = fccChunkId;
+    m_pParentChunk = pParentChunk;
+    m_hFile        = hFile;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Open()
+// Desc: Opens an existing chunk.
+//-----------------------------------------------------------------------------
+bool CRiffChunk::Open()
+{
+    RIFFHEADER rhRiffHeader;
+    LONG       lOffset = 0;
+
+    // Seek to the first byte of the parent chunk's data section
+    if( m_pParentChunk )
+    {
+        lOffset = m_pParentChunk->m_dwDataOffset;
+
+        // Special case the RIFF chunk
+        if( FOURCC_RIFF == m_pParentChunk->m_fccChunkId )
+		{
+            lOffset += sizeof(FOURCC);
+		}
+    }
+    
+    // Read each child chunk header until we find the one we're looking for
+//	RwFileFunctions* fs = RwOsGetFileInterface();
+    for( ;; )
+    {
+//		if( fs->rwfseek( m_hFile, lOffset, SEEK_SET ) != 0 )
+		File::Seek( m_hFile, lOffset, SEEK_SET );
+//		{
+//			return false;
+//		}
+
+		DWORD dwRead;
+//		dwRead = fs->rwfread( &rhRiffHeader, sizeof( rhRiffHeader ), 1, m_hFile );
+		dwRead = File::Read( &rhRiffHeader, sizeof( rhRiffHeader ), 1, m_hFile );
+		if( dwRead != sizeof( rhRiffHeader ))
+		{
+            return false;
+		}
+
+		// Check if we found the one we're looking for.
+		if( m_fccChunkId == rhRiffHeader.fccChunkId )
+        {
+			// Save the chunk size and data offset.
+            m_dwDataOffset = lOffset + sizeof( rhRiffHeader );
+            m_dwDataSize   = rhRiffHeader.dwDataSize;
+
+            // Success.
+            m_dwFlags |= RIFFCHUNK_FLAGS_VALID;
+
+            return true;
+		}
+		lOffset += sizeof( rhRiffHeader ) + rhRiffHeader.dwDataSize;
+    }
+	return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Read()
+// Desc: Reads from the file
+//-----------------------------------------------------------------------------
+bool CRiffChunk::ReadData( LONG lOffset, VOID* pData, DWORD dwDataSize )
+{
+	// Seek to the offset
+//	RwFileFunctions* fs = RwOsGetFileInterface();
+//	if( fs->rwfseek( m_hFile, m_dwDataOffset+lOffset, SEEK_SET ) != 0 )
+//	if( File::Seek( m_hFile, m_dwDataOffset+lOffset, SEEK_SET ) != 0 )
+	File::Seek( m_hFile, m_dwDataOffset + lOffset, SEEK_SET );
+//	{
+//		return false;
+//	}
+
+    // Read from the file
+	DWORD dwRead;
+//	dwRead = fs->rwfread( pData, dwDataSize, 1, m_hFile );
+	dwRead = File::Read( pData, dwDataSize, 1, m_hFile );
+	if( dwRead != dwDataSize )
+	{
+		return false;
+	}
+	return true;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: CWaveFile()
+// Desc: Object constructor.
+//-----------------------------------------------------------------------------
+CWaveFile::CWaveFile()
+{
+    m_hFile = NULL;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ~CWaveFile()
+// Desc: Object destructor.
+//-----------------------------------------------------------------------------
+CWaveFile::~CWaveFile()
+{
+	Close();
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Open()
+// Desc: Initializes the object.
+//-----------------------------------------------------------------------------
+bool CWaveFile::Open( const char* strFileName )
+{
+    // If we're already open, close.
+    Close();
+    
+    // Open the file
+	m_hFile = File::Open( strFileName, "rb" );
+
+//	RwFileFunctions* fs = RwOsGetFileInterface();
+//	m_hFile = fs->rwfopen( strFileName, "rb" );
+
+	if( m_hFile == NULL )
+	{
+        return false;
+	}
+
+	// Initialize the chunk objects.
+    m_RiffChunk.Initialize( FOURCC_RIFF, NULL, m_hFile );
+    m_FormatChunk.Initialize( FOURCC_FORMAT, &m_RiffChunk, m_hFile );
+    m_DataChunk.Initialize( FOURCC_DATA, &m_RiffChunk, m_hFile );
+    m_SamplerChunk.Initialize( FOURCC_SAMPLER, &m_RiffChunk, m_hFile );
+
+    bool hr = m_RiffChunk.Open();
+    if( !hr )
+	{
+        return hr;
+	}
+
+    hr = m_FormatChunk.Open();
+    if( !hr )
+	{
+        return hr;
+	}
+
+    hr = m_DataChunk.Open();
+    if( !hr )
+	{
+        return hr;
+	}
+
+	// Not a problem if it doesn't, not every file will contain the sampler chunk.
+    m_ContainsSamplerChunk	= m_SamplerChunk.Open();
+
+    // Validate the file type.
+    FOURCC fccType;
+    hr = m_RiffChunk.ReadData( 0, &fccType, sizeof( fccType ));
+    if( !hr )
+	{
+        return hr;
+	}
+
+    if( FOURCC_WAVE != fccType )
+        return false;
+
+    return true;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: GetFormat()
+// Desc: Gets the wave file format
+//-----------------------------------------------------------------------------
+bool CWaveFile::GetFormat( WAVEFORMATEX* pwfxFormat, DWORD dwFormatSize )
+{
+    DWORD dwValidSize = m_FormatChunk.GetDataSize();
+
+    if( NULL == pwfxFormat || 0 == dwFormatSize )
+        return false;
+
+    // Read the format chunk into the buffer
+    bool hr = m_FormatChunk.ReadData( 0, pwfxFormat, min( dwFormatSize, dwValidSize ));
+
+	if( hr )
+	{
+	    // Zero out remaining bytes, in case enough bytes were not read
+		if( dwFormatSize > dwValidSize )
+		{
+	        ZeroMemory( (BYTE*)pwfxFormat + dwValidSize, dwFormatSize - dwValidSize );
+		}
+	}
+
+    return hr;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ReadSample()
+// Desc: Reads data from the audio file.
+//-----------------------------------------------------------------------------
+bool CWaveFile::ReadSample( DWORD dwPosition, VOID* pBuffer, DWORD dwBufferSize, DWORD* pdwRead )
+{                                   
+    // Don't read past the end of the data chunk
+    DWORD dwDuration;
+    GetDuration( &dwDuration );
+
+    if( dwPosition + dwBufferSize > dwDuration )
+        dwBufferSize = dwDuration - dwPosition;
+
+    bool hr = true;
+    if( dwBufferSize )
+        hr = m_DataChunk.ReadData( (LONG)dwPosition, pBuffer, dwBufferSize );
+
+    if( pdwRead )
+        *pdwRead = dwBufferSize;
+
+    return hr;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: 
+// Desc: 
+//-----------------------------------------------------------------------------
+bool CWaveFile::ContainsLoop( void )
+{
+	// Figure out if it is a looping sound.
+	if( m_ContainsSamplerChunk )
+	{
+		SAMPLERCHUNK sc;
+		m_SamplerChunk.ReadData( 0, &sc, sizeof( sc ));
+		if( sc.num_sample_loops > 0 )
+		{
+			return true;
+		}		
+	}
+	return false;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Close()
+// Desc: Closes the object
+//-----------------------------------------------------------------------------
+void CWaveFile::Close( void )
+{
+    if( m_hFile != NULL )
+    {
+//		RwFileFunctions* fs = RwOsGetFileInterface();
+//		fs->rwfclose( m_hFile );
+//		m_hFile = NULL;
+		File::Close( m_hFile );
+    }
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: CXBSound()
+// Desc: 
+//-----------------------------------------------------------------------------
+CXBSound::CXBSound( void )
+{
+    m_dwBufferSize  = 0L;
+	m_pRawData		= NULL;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ~CXBSound()
+// Desc: 
+//-----------------------------------------------------------------------------
+CXBSound::~CXBSound( void )
+{
+    Destroy();
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Create()
+// Desc: Creates the sound. Sound is buffered to memory allocated internally
+//       by DirectSound.
+//-----------------------------------------------------------------------------
+bool CXBSound::Create( const CHAR* strFileName, DWORD dwFlags )
+{
+    bool	br;
+
+    // Open the .wav file
+    CWaveFile waveFile;
+	br = waveFile.Open( strFileName );
+    if( !br )
+	{
+        return false;
+	}
+
+    // Get the WAVEFORMAT structure for the .wav file
+    br = waveFile.GetFormat( &m_WaveFormat.wfx, sizeof( WAVEFORMATEX ));
+    if( !br )
+	{
+        return false;
+	}
+
+	// Required if the sound is Xbox ADPCM, will be ignored otherwise.
+	m_WaveFormat.wSamplesPerBlock = 64;
+
+    // Get the size of the .wav file
+    waveFile.GetDuration( &m_dwBufferSize );
+
+	// See if the sound is looping.
+	m_Loops = waveFile.ContainsLoop();
+
+    // Create the sound buffer
+    br = Create( &m_WaveFormat, dwFlags, NULL, m_dwBufferSize );
+    if( !br )
+	{
+        return false;
+	}
+
+    // Lock the buffer so it can be filled
+    VOID* pLock1 = NULL;
+    VOID* pLock2 = NULL;
+    DWORD dwLockSize1 = 0L;
+    DWORD dwLockSize2 = 0L;
+
+	pLock1 = new char[m_dsbd.dwBufferBytes];
+
+    // Read the wave file data into the buffer
+    br = waveFile.ReadSample( 0L, pLock1, m_dsbd.dwBufferBytes, NULL );
+    if( !br )
+	{
+        return false;
+	}
+
+	// Store pointer to raw sound data.
+    m_pRawData = pLock1;
+
+    return true;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Create()
+// Desc: Creates the sound and tells DirectSound where the sound data will be
+//       stored. If pBuffer is NULL, DirectSound handles buffer creation.
+//-----------------------------------------------------------------------------
+bool CXBSound::Create( const XBOXADPCMWAVEFORMAT* pwfxFormat, DWORD dwFlags, VOID* pBuffer, DWORD dwBytes )
+{
+	// Setup the sound buffer description.
+	ZeroMemory( &m_dsbd, sizeof(DSBUFFERDESC) );
+	m_dsbd.dwSize      = sizeof(DSBUFFERDESC);
+	m_dsbd.dwFlags     = dwFlags;
+
+	if( pwfxFormat->wfx.wFormatTag == WAVE_FORMAT_XBOX_ADPCM )
+	{
+		m_dsbd.lpwfxFormat	= (LPWAVEFORMATEX)&m_WaveFormat;
+	}
+	else
+	{
+		m_dsbd.lpwfxFormat	= (LPWAVEFORMATEX)&pwfxFormat->wfx;
+	}
+
+	// If pBuffer is non-NULL, dwBufferBytes will be zero, which informs
+	// DirectSoundCreateBuffer that we will presently be using SetBufferData().
+	// Otherwise, we set dwBufferBytes to the size of the WAV data, potentially
+	// including alignment bytes.
+	if( pBuffer == NULL )
+	{
+		m_dsbd.dwBufferBytes = ( 0 == m_WaveFormat.wfx.nBlockAlign ) ? dwBytes : 
+								 dwBytes - ( dwBytes % m_WaveFormat.wfx.nBlockAlign );
+	}
+
+	// If buffer specified, tell DirectSound to use it
+	if( pBuffer != NULL )
+	{
+		// Store pointer to raw sound data.
+		m_pRawData = pBuffer;
+	}
+
+    return true;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Destroy()
+// Desc: Destroys the resources used by the sound
+//-----------------------------------------------------------------------------
+VOID CXBSound::Destroy()
+{
+	if( m_pRawData )
+	{
+		delete [] m_pRawData;
+	}
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: GetSampleRate
+// Desc: Provides the sample rate
+//-----------------------------------------------------------------------------
+DWORD CXBSound::GetSampleRate( void ) const
+{
+	return m_WaveFormat.wfx.nSamplesPerSec;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int getFreeVoice( void )
+{
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		if( VoiceSimulator[i].p_buffer == NULL )
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void InitSoundFX( CSfxManager *p_sfx_manager )
+{
+	HRESULT				hr;
+    LPDSEFFECTIMAGEDESC	pDesc;
+    DSEFFECTIMAGELOC	EffectLoc;
+
+	Dbg_Assert( pDirectSound == NULL );
+
+	// Set default volume type.
+	if( p_sfx_manager )
+	{
+		p_sfx_manager->SetDefaultVolumeType( VOLUME_TYPE_5_CHANNEL_DOLBY5_1 );
+	}
+
+	// Initialize the 'voice' array.
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		VoiceSimulator[i].p_buffer = NULL;
+	}
+
+	// Create the DirectSound interface.
+	hr = DirectSoundCreate( NULL, &pDirectSound, NULL );
+
+//	HANDLE hFile = CreateFile( "d:\\data\\sounds\\bin\\dsstdfx.bin", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL );
+	HANDLE hFile = CreateFile( "d:\\data\\sounds\\bin\\skate5fx.bin", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL );
+    if( hFile == INVALID_HANDLE_VALUE )
+    {
+		Dbg_Assert( 0 );
+	}
+	else
+	{
+		// Determine the size of the scratch image by seeking to the end of the file.
+		int size = SetFilePointer( hFile, 0, NULL, FILE_END );
+        SetFilePointer( hFile, 0, NULL, FILE_BEGIN );
+    
+		// Allocate memory to read the scratch image from disk
+        BYTE* p_buffer = new BYTE[size];
+
+        // Read the image in
+        DWORD dwBytesRead;
+        BOOL bResult = ReadFile( hFile, p_buffer, size, &dwBytesRead, 0 );
+		if( !bResult )
+        {
+			Dbg_Assert( 0 );
+        }
+		else
+	    {
+	        // Call DSound API to download the image.
+//			EffectLoc.dwI3DL2ReverbIndex	= I3DL2_CHAIN_I3DL2_REVERB;
+//			EffectLoc.dwCrosstalkIndex		= I3DL2_CHAIN_XTALK;
+			EffectLoc.dwI3DL2ReverbIndex	= GraphI3DL2_I3DL2Reverb;
+			EffectLoc.dwCrosstalkIndex		= GraphXTalk_XTalk;
+			hr = pDirectSound->DownloadEffectsImage( p_buffer, size, &EffectLoc, &pDesc );
+
+			delete[] p_buffer;
+        }
+    }
+
+	if( hFile != INVALID_HANDLE_VALUE ) 
+	{
+		CloseHandle( hFile );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CleanUpSoundFX( void )
+{
+	// This just resets the SPU RAM pointer on the PS2. However, on Xbox it needs to explicitly
+	// delete any sounds that were not marked as permanent at load time.
+	StopAllSoundFX();
+	SetReverbPlease( 0 );
+
+	for( int i = 0; i < NumWavesInTable; ++i )
+	{
+		PlatformWaveInfo* p_info = &( WaveTable[PERM_WAVE_TABLE_MAX_ENTRIES + i].platformWaveInfo );
+		Dbg_Assert( p_info->p_sound_data );
+		delete p_info->p_sound_data;
+		p_info->p_sound_data = NULL;
+	}
+	NumWavesInTable = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void StopAllSoundFX( void )
+{
+	Pcm::StopMusic();
+	Pcm::StopStreams();
+
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		StopSoundPlease( i );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int GetMemAvailable( void )
+{
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool LoadSoundPlease( const char *sfxName, uint32 checksum, PlatformWaveInfo *pInfo, bool loadPerm )
+{
+	Dbg_Assert( pInfo );
+
+	// Need to append file type. Copy string into a buffer.
+	static char name_buffer[256] = "sounds\\pcm\\";
+	const int SOUND_PREPEND_START_POS = 11;
+
+	// Check there is room to add the extension.
+	int length = strlen( sfxName );
+	Dbg_Assert( strlen( sfxName ) <= ( 256 - ( SOUND_PREPEND_START_POS + 4 )));
+	strcpy( name_buffer + SOUND_PREPEND_START_POS, sfxName );
+	strcpy( name_buffer + SOUND_PREPEND_START_POS + length, ".pcm" );
+
+	CXBSound* p_sound = new CXBSound();
+
+	// Pass the locdefer flag along to prevent this buffer taking up a voice because it will not be played directly.
+	bool result = p_sound->Create( name_buffer, DSBCAPS_LOCDEFER );
+
+	if( result )
+	{
+		pInfo->p_sound_data = p_sound;
+		pInfo->looping		= p_sound->m_Loops;
+		pInfo->permanent	= loadPerm;
+	}
+	else
+	{
+		OutputDebugString( name_buffer );
+		OutputDebugString( "\n" );
+		pInfo->p_sound_data = NULL;
+		pInfo->looping		= false;
+		pInfo->permanent	= false;
+		delete p_sound;
+	}
+ 	return result;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//int	PlaySoundPlease( PlatformWaveInfo *pInfo, float volL, float volR, float pitch )
+int	PlaySoundPlease( PlatformWaveInfo *pInfo, sVolume *p_vol, float pitch )
+{
+	Dbg_Assert( pInfo );
+
+	if( pInfo->p_sound_data )
+	{
+		// Obtain a free voice.
+		int voice = getFreeVoice();
+		if( voice >= 0 )
+		{
+			// Create the dynamic buffer. Need to set buffer bytes to 0, since we will be setting our own data below.
+			DWORD temp_flags	= pInfo->p_sound_data->m_dsbd.dwFlags;
+			DWORD buffer_bytes	= pInfo->p_sound_data->m_dsbd.dwBufferBytes;
+
+//			pInfo->p_sound_data->m_dsbd.dwFlags |= DSBCAPS_CTRLPOSITIONNOTIFY;			
+			pInfo->p_sound_data->m_dsbd.dwBufferBytes	= 0;
+
+			// Add in the MixBin setup data.			
+			DSMIXBINS			dsmixbins;
+			DSMIXBINVOLUMEPAIR	dsmbvp[7];
+			pInfo->p_sound_data->m_dsbd.lpMixBins	= &dsmixbins;
+			dsmixbins.dwMixBinCount					= 7;
+			dsmixbins.lpMixBinVolumePairs			= dsmbvp;
+			dsmbvp[0].dwMixBin						= DSMIXBIN_3D_FRONT_LEFT;
+			dsmbvp[0].lVolume						= DSBVOLUME_EFFECTIVE_MIN;
+			dsmbvp[1].dwMixBin						= DSMIXBIN_3D_FRONT_RIGHT;
+			dsmbvp[1].lVolume						= DSBVOLUME_EFFECTIVE_MIN;
+			dsmbvp[2].dwMixBin						= DSMIXBIN_FRONT_CENTER;
+			dsmbvp[2].lVolume						= DSBVOLUME_EFFECTIVE_MIN;
+			dsmbvp[3].dwMixBin						= DSMIXBIN_LOW_FREQUENCY;
+			dsmbvp[3].lVolume						= DSBVOLUME_EFFECTIVE_MIN;
+			dsmbvp[4].dwMixBin						= DSMIXBIN_3D_BACK_LEFT;
+			dsmbvp[4].lVolume						= DSBVOLUME_EFFECTIVE_MIN;
+			dsmbvp[5].dwMixBin						= DSMIXBIN_3D_BACK_RIGHT;
+			dsmbvp[5].lVolume						= DSBVOLUME_EFFECTIVE_MIN;
+			dsmbvp[6].dwMixBin						= DSMIXBIN_I3DL2;
+			dsmbvp[6].lVolume						= DSBVOLUME_EFFECTIVE_MIN;
+
+			HRESULT hr = DirectSoundCreateBuffer( &pInfo->p_sound_data->m_dsbd, &VoiceSimulator[voice].p_buffer );
+
+			// Reset various buffer description values.
+			pInfo->p_sound_data->m_dsbd.dwFlags			= temp_flags;
+			pInfo->p_sound_data->m_dsbd.dwBufferBytes	= buffer_bytes;
+			pInfo->p_sound_data->m_dsbd.lpMixBins		= NULL;
+
+			++num_buffers;
+
+			if( hr == DS_OK )
+			{
+				// Set the buffer to point to the raw sound data.
+				hr = VoiceSimulator[voice].p_buffer->SetBufferData( pInfo->p_sound_data->m_pRawData, pInfo->p_sound_data->m_dsbd.dwBufferBytes );
+				if( hr == DS_OK )
+				{
+					// Set the default sample rate.
+					VoiceSimulator[voice].default_frequency = pInfo->p_sound_data->GetSampleRate();
+
+					// Play the sound.
+					hr = VoiceSimulator[voice].p_buffer->Play( 0, 0, ( pInfo->looping ) ? DSBPLAY_LOOPING : 0 );
+					if( hr == DS_OK )
+					{
+//						SetVoiceParameters( voice, volL, volR, pitch );
+						SetVoiceParameters( voice, p_vol, pitch );
+						return voice;
+					}
+				}
+			}
+		}
+	}
+
+	return -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void StopSoundPlease( int voice )
+{
+	if( VoiceSimulator[voice].p_buffer )
+	{
+		HRESULT hr = VoiceSimulator[voice].p_buffer->Stop();
+		Dbg_Assert( hr == DS_OK )
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetVolumePlease( float volumeLevel )
+{
+	gSfxVolume = volumeLevel;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PauseSoundsPlease( void )
+{
+	sVolume vol;
+	vol.SetSilent();
+	
+	// Just turn the volume down on all playing voices...
+	for( int i = 0; i < NUM_VOICES; i++ )
+	{
+		if( VoiceIsOn( i ))
+		{
+//			SetVoiceParameters( i, 0.0f, 0.0f );
+			SetVoiceParameters( i, &vol );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetReverbPlease( float reverbLevel, int reverbMode, bool instant )
+{
+	if( reverbMode == 0 )
+	{
+		// Default to plain.
+		reverbMode = 20;
+	}
+	reverbMode = 20;
+	
+	HRESULT hr = pDirectSound->SetI3DL2Listener( &reverbEnvironments[reverbMode].ds3dl, DS3D_IMMEDIATE );
+	Dbg_Assert( hr == DS_OK );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool VoiceIsOn( int voice )
+{
+	return( VoiceSimulator[voice].p_buffer != NULL );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void SetVoiceParameters( int voice, float volL, float volR, float pitch )
+void SetVoiceParameters( int voice, sVolume *p_vol, float pitch )
+{
+	if( VoiceSimulator[voice].p_buffer )
+	{
+		// This array will hold individual volumes for the five speakers.
+		// In order, they are: front left, center, front right, rear right, rear left.
+		float	volumes[5];
+		int		i_volumes[5], max_i_volume;
+
+		memset( volumes, 0, sizeof( float ) * 5 );
+		
+		if( p_vol->IsSilent())
+		{
+			// Pointless doing any more work.
+		}
+		else
+		{
+			switch( p_vol->GetVolumeType())
+			{
+				case VOLUME_TYPE_5_CHANNEL_DOLBY5_1:
+				{
+					// All the work has been done already - just copy volumes over.
+					volumes[0] = p_vol->GetChannelVolume( 0 );
+					volumes[1] = p_vol->GetChannelVolume( 4 );
+					volumes[2] = p_vol->GetChannelVolume( 1 );
+					volumes[3] = p_vol->GetChannelVolume( 2 );
+					volumes[4] = p_vol->GetChannelVolume( 3 );
+					break;
+				}
+
+				case VOLUME_TYPE_BASIC_2_CHANNEL:
+				{
+					// Simple two channel sound, so just setup as such.
+					volumes[0] = p_vol->GetChannelVolume( 0 );
+					volumes[1] = ( p_vol->GetChannelVolume( 0 ) + p_vol->GetChannelVolume( 1 )) * 0.5f;
+					volumes[2] = p_vol->GetChannelVolume( 1 );
+					volumes[3] = p_vol->GetChannelVolume( 0 ) * 0.5f;
+					volumes[4] = p_vol->GetChannelVolume( 1 ) * 0.5f;
+					break;
+				}
+
+				case VOLUME_TYPE_2_CHANNEL_DOLBYII:
+				default:
+				{
+					// Wes should not get any of these.
+					Dbg_Assert( 0 );
+
+#					if 0
+					// Get the length of the vector here which will be used to multiply out the normalised speaker volumes.
+					Mth::Vector test( fabsf( p_vol->GetChannelVolume( 0 )), fabsf( p_vol->GetChannelVolume( 1 )), 0.0f, 0.0f );
+					float amplitude = test.Length();
+				
+					// Look just at the normalized right component to figure the sound angle from Matt's calculations.
+					test.Normalize();
+
+					float angle;
+					angle	= asinf( test[Y] );
+					angle	= ( angle * 2.0f ) - ( Mth::PI * 0.5f );
+					angle	= ( p_vol->GetChannelVolume( 0 ) < 0.0f ) ? ( Mth::PI - angle ) : angle;
+				
+				
+					// Now figure volumes based on speaker coverage.
+					angle	= Mth::RadToDeg( angle );
+
+					// Left front channel.
+					if(( angle >= 225.0f ) || ( angle <= 45.0f ))
+					{
+						// Because of the discontinuity, shift this angle round to the [0,180] range.
+						float shift_angle = angle + 135;
+						shift_angle = ( shift_angle >= 360.0f ) ? ( shift_angle - 360.0f ) : shift_angle;
+						volumes[0]	= ( shift_angle / 180.0f ) * Mth::PI;
+						volumes[0]	= sqrtf( sinf( volumes[0] ));
+					}
+				
+					// Center channel.
+					if(( angle >= -60.0f ) && ( angle <= 60.0f ))
+					{
+						// Scale this into [0,PI] space so we can get smooth fadeout.
+						volumes[1]	= (( angle + 60.0f ) / 120.0f ) * Mth::PI;
+						volumes[1]	= sqrtf( sinf( volumes[1] ));
+					}
+		
+					// Right front channel.
+					if(( angle >= -45.0f ) && ( angle <= 135.0f ))
+					{
+						// Scale this into [0,PI] space so we can get smooth fadeout.
+						volumes[2]	= (( angle + 45.0f ) / 180.0f ) * Mth::PI;
+						volumes[2]	= sqrtf( sinf( volumes[2] ));
+					}
+
+					// Right rear channel.
+					if(( angle >= 45.0f ) && ( angle <= 225.0f ))
+					{
+						// Scale this into [0,PI] space so we can get smooth fadeout.
+						volumes[3]	= (( angle - 45.0f ) / 180.0f ) * Mth::PI;
+						volumes[3]	= sqrtf( sinf( volumes[3] ));
+					}
+
+					// Left rear channel.
+					if(( angle >= 135.0f ) || ( angle <= -45.0f ))
+					{
+						// Because of the discontinuity, shift this angle round to the [0,180] range.
+						float shift_angle = angle + 225;
+						shift_angle = ( shift_angle >= 360.0f ) ? ( shift_angle - 360.0f ) : shift_angle;
+						volumes[4]	= ( shift_angle / 180.0f ) * Mth::PI;
+						volumes[4]	= sqrtf( sinf( volumes[4] ));
+					}
+
+					// Scale back up to original amplitude.
+					for( int v = 0; v < 5; ++v )
+					{
+						volumes[v] *= amplitude;
+					}
+#					endif
+					break;
+				}
+			}
+		}
+
+		// Now readjust the relative values...
+		for( int v = 0; v < 5; ++v )
+		{
+			// Adjust for SFX volume level, and scale into limits.
+			volumes[v] = fabsf( PERCENT( gSfxVolume, volumes[v] ));
+
+				if( volumes[v] > 100.0f )
+					volumes[v] = 100.0f;
+		}
+		
+		// Now figure the attenuation of the sound. To convert to a decibel value, figure the ratio of requested
+		// volume versus max volume, then calculate the log10 and multiply by (10 * 2). (The 2 is because sound
+		// power varies as square of pressure, and squaring doubles the log value).
+		max_i_volume = DSBVOLUME_EFFECTIVE_MIN;
+		for( int v = 0; v < 5; ++v )
+		{
+			if( volumes[v] > 0.0f )
+			{
+				float attenuation	= 20.0f * log10f( volumes[v] * 0.01f );
+				i_volumes[v]		= DSBVOLUME_MAX + (int)( attenuation * 100.0f );
+				if( i_volumes[v] < DSBVOLUME_EFFECTIVE_MIN )
+					i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+
+				if( i_volumes[v] > max_i_volume )
+					max_i_volume = i_volumes[v];
+			}
+			else
+			{
+				i_volumes[v] = DSBVOLUME_EFFECTIVE_MIN;
+			}
+		}
+		
+		// Set individual mixbins for panning.
+		DSMIXBINS			dsmixbins;
+		DSMIXBINVOLUMEPAIR	dsmbvp[DSMIXBIN_ASSIGNMENT_MAX];
+
+		dsmixbins.dwMixBinCount			= 0;
+		dsmixbins.lpMixBinVolumePairs	= dsmbvp;
+
+		if( i_volumes[0] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[0];
+            dsmixbins.dwMixBinCount++;
+		}
+
+		if( i_volumes[1] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_FRONT_CENTER;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[1];
+            dsmixbins.dwMixBinCount++;
+		}
+
+		if( i_volumes[2] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_FRONT_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[2];
+            dsmixbins.dwMixBinCount++;
+		}
+
+		if( i_volumes[3] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_RIGHT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[3];
+            dsmixbins.dwMixBinCount++;
+		}
+
+		if( i_volumes[4] > DSBVOLUME_EFFECTIVE_MIN )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_3D_BACK_LEFT;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= i_volumes[4];
+            dsmixbins.dwMixBinCount++;
+		}
+
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_LOW_FREQUENCY;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= max_i_volume;
+            dsmixbins.dwMixBinCount++;
+
+            dsmbvp[dsmixbins.dwMixBinCount].dwMixBin	= DSMIXBIN_I3DL2;
+            dsmbvp[dsmixbins.dwMixBinCount].lVolume		= DSBVOLUME_MAX;
+            dsmixbins.dwMixBinCount++;
+		}
+
+		// Set all speaker volumes.
+		HRESULT hr = VoiceSimulator[voice].p_buffer->SetMixBinVolumes( &dsmixbins );
+
+		// Set overall buffer volume.
+		if( dsmixbins.dwMixBinCount > 0 )
+		{
+			hr = VoiceSimulator[voice].p_buffer->SetVolume( DSBVOLUME_MAX );
+		}
+		else
+		{
+			hr = VoiceSimulator[voice].p_buffer->SetVolume( DSBVOLUME_EFFECTIVE_MIN );
+
+			// Pointless doing pitch calculation if volume is zero.
+			return;
+		}
+
+		// Same kind of deal for pitch. Each consecutive doubling (or halving) of the pitch percentage
+		// raises (or lowers) the pitch by 1 octave. So we need to figure x, where 2 ^ x = percentage / 100.
+		// Can do this with logs...
+		int ipitch;
+
+		// Matt appears to be doing an extra adjustment to the pitch on the Ps2 to account for sample rates
+		// not at 48,000Hz. Whilst this seems an unnecessary step, we need to do the same adjustment here.
+		// Adjust pitch to account for lower sampling rates.
+//		pitch = PERCENT( pitch, ((float)VoiceSimulator[voice].default_frequency / 48000.0f ) * 100.0f );
+		
+		if( pitch != 100.0f )
+		{
+			ipitch = (int)(( VoiceSimulator[voice].default_frequency * pitch ) * 0.01f );
+			if( ipitch > DSBFREQUENCY_MAX )
+			{
+				ipitch = DSBFREQUENCY_MAX;
+			}
+			else if( ipitch < DSBFREQUENCY_MIN )
+			{
+				ipitch = DSBFREQUENCY_MIN;
+			}
+		}
+		else
+		{
+			ipitch = VoiceSimulator[voice].default_frequency;
+		}
+		hr = VoiceSimulator[voice].p_buffer->SetFrequency( ipitch );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PerFrameUpdate( void )
+{
+	DirectSoundDoWork();
+
+	int num_used_voices = 0;
+
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		if( VoiceSimulator[i].p_buffer )
+		{
+			DWORD status;
+			HRESULT hr = VoiceSimulator[i].p_buffer->GetStatus( &status );
+			if(( hr == DS_OK ) && ( status == 0 ))
+			{
+				// This sound has stopped playing, so we can release the buffer.
+				hr = VoiceSimulator[i].p_buffer->Release();
+
+				--num_buffers;
+
+				Dbg_Assert( hr == 0 );
+
+				VoiceSimulator[i].p_buffer = NULL;
+			}
+			else
+			{
+				++num_used_voices;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Sfx
diff --git a/Code/Gel/SoundFX/Xbox/p_sfx.h b/Code/Gel/SoundFX/Xbox/p_sfx.h
new file mode 100644
index 0000000..b32ac5f
--- /dev/null
+++ b/Code/Gel/SoundFX/Xbox/p_sfx.h
@@ -0,0 +1,135 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Sound effects  (Sfx)									**
+**																			**
+**	File name:		gel/													**
+**																			**
+**	Created: 		01/10/2001	-	dc										**
+**																			**
+*****************************************************************************/
+
+#ifndef __MODULES_P_SFX_H
+#define __MODULES_P_SFX_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+ 
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Sfx
+{
+
+#define NUM_VOICES_PER_CORE				24
+#define NUM_CORES						2
+#define NUM_VOICES						( NUM_VOICES_PER_CORE * NUM_CORES )
+
+#define WAVE_TABLE_MAX_ENTRIES			256	// non-permanent sounds
+#define PERM_WAVE_TABLE_MAX_ENTRIES		256	// permanent sounds
+#define MAX_POSITIONAL_SOUNDS			128
+
+// (From Xbox docs) Even though DirectSound allows you to set the minimum volume to -100 dB,
+// the XBox audio hardware clamps to silence at -64 dB. For efficiency, your game should avoid
+// making volume calls and calculations where the volume is below -64 dB, particularly when
+// keeping 3D sounds on that are intended to sound distant or far away.
+#define DSBVOLUME_EFFECTIVE_MIN	-6400
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CXBSound;
+class CSfxManager;
+
+// Platform specific info needed per wave (stored in the WaveTable).
+struct PlatformWaveInfo
+{
+	CXBSound*	p_sound_data;
+	bool		looping;		// this flag is checked in soundfx.cpp.  might oughtta be added to WaveTableEntry struct...
+	bool		permanent;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+extern float	gSfxVolume;
+struct sVolume;
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+void	InitSoundFX( CSfxManager *p_sfx_manager );
+void	CleanUpSoundFX( void );
+void	StopAllSoundFX( void );
+bool	LoadSoundPlease( const char *sfxName, uint32 checksum, PlatformWaveInfo *pInfo, bool loadPerm = 0 );
+
+// returns 0 - ( NUM_VOICES - 1 ), or -1 if no voices are free...
+//int		PlaySoundPlease( PlatformWaveInfo *pInfo, float volL = 100.0f, float volR = 100.0f, float pitch = 100.0f );
+int		PlaySoundPlease( PlatformWaveInfo *pInfo, sVolume *p_vol, float pitch = 100.0f );
+
+void	StopSoundPlease( int whichVoice );
+
+int		GetMemAvailable( void );
+
+// really just turning down the volume on all playing voices...
+// Looping sounds will get the volume adjusted by the owner
+// during the next logic loop.
+// The non-looping sounds will play (with no volume) then stop.
+// This is for when we go into the menus or whatnot...
+void	PauseSoundsPlease( void );
+
+// see reverb types above...
+void	SetReverbPlease( float reverbLevel = 0.0f, int reverbMode = 0, bool instant = false );
+
+// volume from -100 to 100 ( negative if the sound is from behind us. ps2 alters the sound... )
+void	SetVolumePlease( float volumeLevel );
+
+// returns true if voice is being used currently.
+bool	VoiceIsOn( int whichVoice );
+
+// change parameters on an active voice:
+// if pitch parameter is zero, pitch won't be changed (lowest pitch is 1)
+void	SetVoiceParameters( int whichVoice, sVolume *p_vol, float pitch = 0.0 );
+
+void	PerFrameUpdate( void );
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Sfx
+
+#endif	// __MODULES_P_SFX_H
diff --git a/Code/Gel/SoundFX/Xbox/skate5fx.h b/Code/Gel/SoundFX/Xbox/skate5fx.h
new file mode 100644
index 0000000..ce1165f
--- /dev/null
+++ b/Code/Gel/SoundFX/Xbox/skate5fx.h
@@ -0,0 +1,61 @@
+
+#pragma once
+
+typedef enum _DSP_IMAGE_skate5fx_FX_INDICES {
+    UserStereoEcho_StereoEcho = 0,
+    UserI3DL224KReverb_I3DL224KReverb = 1,
+    GraphI3DL2_I3DL2Reverb = 2,
+    GraphXTalk_XTalk = 3
+} DSP_IMAGE_skate5fx_FX_INDICES;
+
+#define DSI3DL2_ENVIRONMENT_UserI3DL224KReverb_I3DL224KReverb -1000, -270, 0.000000, 1.490000, 0.860000, -1204, 0.007000, -4, 0.011000, 100.000000, 100.000000, 5000.000000
+
+#define DSI3DL2_ENVIRONMENT_GraphI3DL2_I3DL2Reverb -1000, -100, 0.000000, 1.490000, 0.830000, -2602, 0.007000, 200, 0.011000, 100.000000, 100.000000, 5000.000000
+
+typedef struct _UserStereoEcho_FX0_StereoEcho_STATE {
+    DWORD dwScratchOffset;        // Offset in bytes, of scratch area for this FX
+    DWORD dwScratchLength;        // Length in DWORDS, of scratch area for this FX
+    DWORD dwYMemoryOffset;        // Offset in DSP WORDS, of Y memory area for this FX
+    DWORD dwYMemoryLength;        // Length in DSP WORDS, of Y memory area for this FX
+    DWORD dwFlags;                // FX bitfield for various flags. See xgpimage documentation
+    DWORD dwInMixbinPtrs[2];      // XRAM offsets in DSP WORDS, of input mixbins
+    DWORD dwOutMixbinPtrs[2];     // XRAM offsets in DSP WORDS, of output mixbins
+} UserStereoEcho_FX0_StereoEcho_STATE, *LPUserStereoEcho_FX0_StereoEcho_STATE;
+
+typedef const UserStereoEcho_FX0_StereoEcho_STATE *LPCUserStereoEcho_FX0_StereoEcho_STATE;
+
+typedef struct _UserI3DL224KReverb_FX0_I3DL224KReverb_STATE {
+    DWORD dwScratchOffset;        // Offset in bytes, of scratch area for this FX
+    DWORD dwScratchLength;        // Length in DWORDS, of scratch area for this FX
+    DWORD dwYMemoryOffset;        // Offset in DSP WORDS, of Y memory area for this FX
+    DWORD dwYMemoryLength;        // Length in DSP WORDS, of Y memory area for this FX
+    DWORD dwFlags;                // FX bitfield for various flags. See xgpimage documentation
+    DWORD dwInMixbinPtrs[2];      // XRAM offsets in DSP WORDS, of input mixbins
+    DWORD dwOutMixbinPtrs[35];     // XRAM offsets in DSP WORDS, of output mixbins
+} UserI3DL224KReverb_FX0_I3DL224KReverb_STATE, *LPUserI3DL224KReverb_FX0_I3DL224KReverb_STATE;
+
+typedef const UserI3DL224KReverb_FX0_I3DL224KReverb_STATE *LPCUserI3DL224KReverb_FX0_I3DL224KReverb_STATE;
+
+typedef struct _GraphI3DL2_FX0_I3DL2Reverb_STATE {
+    DWORD dwScratchOffset;        // Offset in bytes, of scratch area for this FX
+    DWORD dwScratchLength;        // Length in DWORDS, of scratch area for this FX
+    DWORD dwYMemoryOffset;        // Offset in DSP WORDS, of Y memory area for this FX
+    DWORD dwYMemoryLength;        // Length in DSP WORDS, of Y memory area for this FX
+    DWORD dwFlags;                // FX bitfield for various flags. See xgpimage documentation
+    DWORD dwInMixbinPtrs[2];      // XRAM offsets in DSP WORDS, of input mixbins
+    DWORD dwOutMixbinPtrs[35];     // XRAM offsets in DSP WORDS, of output mixbins
+} GraphI3DL2_FX0_I3DL2Reverb_STATE, *LPGraphI3DL2_FX0_I3DL2Reverb_STATE;
+
+typedef const GraphI3DL2_FX0_I3DL2Reverb_STATE *LPCGraphI3DL2_FX0_I3DL2Reverb_STATE;
+
+typedef struct _GraphXTalk_FX0_XTalk_STATE {
+    DWORD dwScratchOffset;        // Offset in bytes, of scratch area for this FX
+    DWORD dwScratchLength;        // Length in DWORDS, of scratch area for this FX
+    DWORD dwYMemoryOffset;        // Offset in DSP WORDS, of Y memory area for this FX
+    DWORD dwYMemoryLength;        // Length in DSP WORDS, of Y memory area for this FX
+    DWORD dwFlags;                // FX bitfield for various flags. See xgpimage documentation
+    DWORD dwInMixbinPtrs[4];      // XRAM offsets in DSP WORDS, of input mixbins
+    DWORD dwOutMixbinPtrs[4];     // XRAM offsets in DSP WORDS, of output mixbins
+} GraphXTalk_FX0_XTalk_STATE, *LPGraphXTalk_FX0_XTalk_STATE;
+
+typedef const GraphXTalk_FX0_XTalk_STATE *LPCGraphXTalk_FX0_XTalk_STATE;
diff --git a/Code/Gel/SoundFX/ngc/p_sfx.cpp b/Code/Gel/SoundFX/ngc/p_sfx.cpp
new file mode 100644
index 0000000..ab7a0d7
--- /dev/null
+++ b/Code/Gel/SoundFX/ngc/p_sfx.cpp
@@ -0,0 +1,961 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2001 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL Library												**
+**																			**
+**	Module:			Sound effects (Sfx)	 									**
+**																			**
+**	File name:		ngc/p_sfx.cpp											**
+**																			**
+**	Created by:		08/21/01	-	dc										**
+**																			**
+**	Description:	NGC Sound effects										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include	
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 	"gfx\ngc\nx\nx_init.h"
+
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace Sfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define	GAMECUBE_START_DSP_DATA_ADDR		( NxNgc::EngineGlobals.aram_dsp )
+#define	GAMECUBE_END_DSP_DATA_ADDR			( NxNgc::EngineGlobals.aram_dsp + aram_dsp_size )
+
+#define MAX_VOLUME						0x7FFF  // Max Gamecube volume.
+#define MAX_CHANNEL_VOLUME				0x3FFF	// Half of the maximum, so sounds played at 100% will be half max volume.
+#define MAX_REVERB_DEPTH				0x3FFF
+#define MAX_PITCH						0xFFFFFL
+#define UNALTERED_PITCH					0x10000L
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+struct sDSPADPCM
+{
+	// for WAV header generation during decode
+	u32 num_samples;		// total number of RAW samples
+	u32 num_adpcm_nibbles;	// number of ADPCM nibbles (including frame headers)
+	u32 sample_rate;		// Sample rate, in Hz
+
+	// DSP addressing and decode context
+	u16 loop_flag; // 1=LOOPED, 0=NOT LOOPED
+	u16 format; // Always 0x0000, for ADPCM
+	u32 sa; // Start offset address for looped samples (zero for non-looped)
+	u32 ea; // End offset address for looped samples
+	u32 ca; // always zero
+	u16 coef[16]; // decode coefficients (eight pairs of 16-bit words)
+
+	// DSP decoder initial state
+	u16 gain; // always zero for ADPCM
+	u16 ps; // predictor/scale
+	u16 yn1; // sample history
+	u16 yn2; // sample history
+
+	// DSP decoder loop context
+	u16 lps; // predictor/scale for loop context
+	u16 lyn1; // sample history (n-1) for loop context
+	u16 lyn2; // sample history (n-2) for loop context
+	u16 pad[10]; // reserved
+};
+
+
+
+struct VoiceEntry
+{
+	AXVPB*				p_axvpb;		// Pointer returned by AXAcquireVoice().
+	struct sDSPADPCM*	p_dspadpcm;		// Pointer to sample header data in memory.
+};
+
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+// Because we don't use voices in the same way as on PS2, this array simulates
+// the system. An entry with a NULL pointer means no sound is playing on that
+// 'voice', a valid pointer indicates a sound is playing on that 'voice', and
+// provides details of the playback.
+static VoiceEntry		VoiceSimulator[NUM_VOICES];
+
+// Global sfx multiplier, percentage.
+static float			gSfxVolume		= 100.0f;
+
+// allocated SPU RAM
+int SpuRAMUsedTemp = 0;
+int SpuRAMUsedPerm = 0;
+
+// For maintaining pointers to the temporary sound headers, so they can be wiped
+// when cleanup between levels is performed.
+static void*			tempSoundHeaders[200];
+static int				numTempSoundHeaders = 0;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+bool					isStereo = true;
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void AXUserCBack( void )
+{
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		if( VoiceSimulator[i].p_axvpb != NULL )
+		{
+			if( VoiceSimulator[i].p_axvpb->pb.state == AX_PB_STATE_STOP )
+			{
+				// Playback on this voice has ended, so free up the voice.
+				AXFreeVoice( VoiceSimulator[i].p_axvpb );
+				VoiceSimulator[i].p_axvpb = NULL;
+//				OSReport( "voice %d stopped %d\n", i, OSGetTick() );
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void ax_voice_reacquisition_callback( void* p )
+{
+	OSReport( "Voice was reacquired\n" );
+
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		if( VoiceSimulator[i].p_axvpb != NULL )
+		{
+			if( (void*)( VoiceSimulator[i].p_axvpb ) == p )
+			{
+				OSReport( "Voice was voice %d\n", i );
+				VoiceSimulator[i].p_axvpb = NULL;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int getFreeVoice( void )
+{
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		if( VoiceSimulator[i].p_axvpb == NULL )
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool loadGamecubeDSP( PlatformWaveInfo *pInfo, const char *filename, bool loadPerm, float *samplePitch )
+{
+	int vagAddr	= 0;			// VAG address in SPU2 RAM, returned to calling function
+	bool rv = false;
+
+	void* p_file = File::Open( filename, "rb" );
+	if( p_file )
+	{
+		int		size		= File::GetFileSize( p_file );
+		if ( size )
+		{
+			int		align_size	= ( size + 31 ) & ~31;
+			int		data_size	= align_size - 96;
+	
+			// Grab enough memory to ensure a multiple of 32 bytes data length. To avoid fragmentation, we grab
+			// memory for the header copy first.
+			char*	p_header_copy	= (char*)Mem::Malloc( 96 );
+//			File::Read( p_header_copy, 1, 96, p_file );
+			Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+			char*	p_data			= (char*)Mem::Malloc( align_size );
+			Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+//	#ifdef	__NOPT_ASSERT__
+			int		bytes_read		= File::Read( p_data, 1, size, p_file );
+//	#else
+//			File::Read( p_data, 1, size, p_file );
+			DCFlushRange ( p_data, size );
+//	#endif		//	__NOPT_ASSERT__
+//			Dbg_Assert( bytes_read == size )
+			if ( bytes_read != size )
+			{
+				File::Close( p_file );
+				delete p_data;
+				return false;
+			}
+//			file.close();
+	
+			// Copy the header to the reserved area.
+			memcpy( p_header_copy, p_data, 96 );
+	
+			// Obtain information from the file header.
+			sDSPADPCM*	p_header	= (sDSPADPCM*)p_header_copy;
+	//		OSReport( "num samples: %d\n", p_header->num_samples );
+	//		OSReport( "sample rate: %d\n", p_header->sample_rate );
+	//		OSReport( "loop flag:	%d\n", p_header->loop_flag );
+	
+			if( loadPerm )
+			{
+				// Stack the permanent sounds up from the bottom.
+				SpuRAMUsedPerm += data_size;
+				if(( GAMECUBE_END_DSP_DATA_ADDR - SpuRAMUsedPerm ) & 0x0F )
+				{
+	#ifdef	__NOPT_ASSERT__
+					int oldSpuRAM = SpuRAMUsedPerm;
+	#endif		//	__NOPT_ASSERT__
+					SpuRAMUsedPerm += (( END_WAVE_DATA_ADDR - SpuRAMUsedPerm ) & 0x0F );
+					Dbg_MsgAssert( SpuRAMUsedPerm > oldSpuRAM, ( "Duh asshole." ));
+				}
+				vagAddr = GAMECUBE_END_DSP_DATA_ADDR - SpuRAMUsedPerm;
+				Dbg_MsgAssert( !( vagAddr & 0x0F ), ( "Fire Matt please." ));
+			}
+			else
+			{
+				// If a temporary sound, add the details of the header to the cleanup array.
+				tempSoundHeaders[numTempSoundHeaders++]	= p_header_copy;
+	
+				if(( SpuRAMUsedTemp + GAMECUBE_START_DSP_DATA_ADDR ) & 0x0F )
+				{
+					SpuRAMUsedTemp += 0x10 - ( ( SpuRAMUsedTemp + BASE_WAVE_DATA_ADDR ) & 0x0F );
+				}
+				vagAddr = GAMECUBE_START_DSP_DATA_ADDR + SpuRAMUsedTemp;
+				SpuRAMUsedTemp += data_size;
+			}
+		
+			// Start DMA transfer buffer to sound RAM...
+			Dbg_Assert(((unsigned int)p_data & 31 ) == 0 );
+			Dbg_Assert(((unsigned int)vagAddr & 31 ) == 0 );
+	
+			// Read data in chunks & DMA.
+//			int toread = size - 96;
+//			int offset = 0;
+//			while ( toread )
+//			{
+//#define BUFFER_SIZE 16384
+//				char readBuffer[BUFFER_SIZE];
+//				if ( toread > BUFFER_SIZE )
+//				{
+//					// Read a full buffer.
+//					File::Read( readBuffer, 1, BUFFER_SIZE, p_file );
+//					toread -= BUFFER_SIZE;
+//					offset += BUFFER_SIZE;
+//					NsDMA::toARAM( vagAddr + offset, readBuffer, BUFFER_SIZE );
+//				}
+//				else
+//				{
+//					// Last chunk
+//					File::Read( readBuffer, 1, toread, p_file );
+//					NsDMA::toARAM( vagAddr + offset, readBuffer, ( toread + 31 ) & ~31 );
+//					toread = 0;
+//				}
+//			}
+//
+
+			NsDMA::toARAM( vagAddr, p_data + 96, data_size );
+	
+			pInfo->headerAddress	= p_header_copy;
+			pInfo->vagAddress		= vagAddr;
+			pInfo->looping			= ( p_header->loop_flag > 0 );
+	
+			// Figure pitch adjustment coefficient.
+			if( samplePitch )
+			{
+				*samplePitch = ( (float)p_header->sample_rate * 100.0f ) / 32000.0f;
+			}
+	
+			// Release main RAM version of data.
+			Mem::Free( p_data );
+	
+			rv = true;
+		}
+		File::Close( p_file );
+	}
+	else
+	{
+//		OSReport( 0, "************ failed to load sound: %s\n", filename );
+//		while ( 1== 1 );
+		//Dbg_MsgAssert( 0, ( "failed to load sound: %s\n", filename ));
+	}
+	return rv;
+}
+
+
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+void GetStreamBufferInfo( int* stream0_base, int* stream1_base, int* stream2_base,
+						  int* stream0_size, int* stream1_size, int* stream2_size )
+{
+	*stream0_base	= NxNgc::EngineGlobals.aram_stream0;
+	*stream0_size	= aram_stream0_size;
+	*stream1_base	= NxNgc::EngineGlobals.aram_stream1;
+	*stream1_size	= aram_stream1_size;
+	*stream2_base	= NxNgc::EngineGlobals.aram_stream2;
+	*stream2_size	= aram_stream2_size;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PerFrameUpdate( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void InitSoundFX( CSfxManager *p_manager )
+{
+	// Initialize the 'voice' array.
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		VoiceSimulator[i].p_axvpb = NULL;
+	}
+
+	// See whether the unit is in mono or stereo mode.
+	isStereo = !( OSGetSoundMode() == OS_SOUND_MODE_MONO );
+
+	// Initialize Audio Interface API.
+//	AIInit( NULL );
+
+	// Initialize the AX library, and boot the DSP.
+//	AXInit();
+
+	// Set the callback responsible for freeing up voices.
+	AXRegisterCallback( AXUserCBack );
+
+	// Zero first 1024 bytes of ARAM - this is where one-shot sounds will end up when they finish.
+#	if aram_zero_size != 1024
+#	error aram zero buffer size has changed
+#	endif
+	
+	char reset_buffer[1024];
+	memset( reset_buffer, 0, 1024 );
+	DCFlushRange( reset_buffer, 1024 );
+	NsDMA::toARAM( NxNgc::EngineGlobals.aram_zero, reset_buffer, 1024 );
+
+	CleanUpSoundFX();
+
+	// Moved this from sioman.cpp since the pcm stuff for ngc needs the sfx stuff initialised first.
+	Pcm::Init();
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ReInitSoundFX( void )
+{
+	// Initialize the 'voice' array.
+	for( int i = 0; i < NUM_VOICES; ++i )
+	{
+		VoiceSimulator[i].p_axvpb = NULL;
+	}
+
+	// Flush pending ARQ transfers and wait for DMA to finish.
+	ARQFlushQueue();
+	while( ARGetDMAStatus() != 0 );
+
+	AXQuit();
+	AIReset();
+	ARQReset();
+	ARReset();
+
+	// Initialize Audio Interface API.
+	AIInit( NULL );
+
+	// Initialize the AX library, and boot the DSP.
+	AXInit();
+
+	// Set the callback responsible for freeing up voices.
+	AXRegisterCallback( AXUserCBack );
+
+#ifndef DVDETH
+	DTKInit();
+	DTKSetRepeatMode( DTK_MODE_NOREPEAT );
+#endif		// DVDETH
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool VoiceIsOn( int whichVoice )
+{
+	if( VoiceSimulator[whichVoice].p_axvpb != NULL )
+	{
+		// Check it hasn't already been stopped since then it will be dealt with by the 5ms callback.
+//		if( VoiceSimulator[whichVoice].p_axvpb->pb.state != AX_PB_STATE_STOP )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void SetVoiceParameters( int voiceNumber, float volL, float volR, float fPitch )
+void	SetVoiceParameters( int whichVoice, sVolume *p_vol, float pitch )
+{
+	unsigned int rpitch = UNALTERED_PITCH;
+	if( pitch != 0.0f )
+	{
+		rpitch = (unsigned int)PERCENT( UNALTERED_PITCH, pitch );
+		if ( rpitch > MAX_PITCH )
+			rpitch = MAX_PITCH;
+		if ( rpitch <= 0 )
+			rpitch = 1;
+	}
+	
+	int old = OSDisableInterrupts();
+	if( VoiceSimulator[whichVoice].p_axvpb )
+	{
+		AXPB* p_pb = &( VoiceSimulator[whichVoice].p_axvpb->pb );
+//		Pcm::setDolby( p_pb, VoiceSimulator[voiceNumber].p_axvpb, volL, volR, pitch, true, gSfxVolume );
+		//Pcm::setDolby( p_pb, VoiceSimulator[whichVoice].p_axvpb, p_vol->GetChannelVolume( 0 ), p_vol->GetChannelVolume( 1 ), rpitch, true, gSfxVolume );
+		if ( p_vol )
+		{
+			Pcm::setDolby( p_pb, VoiceSimulator[whichVoice].p_axvpb, p_vol->GetChannelVolume( 0 ), p_vol->GetChannelVolume( 1 ), rpitch, true, gSfxVolume );
+		}
+		else
+		{
+			Pcm::setDolby( p_pb, VoiceSimulator[whichVoice].p_axvpb, 0.0f, 0.0f, rpitch, true, gSfxVolume );
+		}
+	}
+	OSRestoreInterrupts( old );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool LoadSoundPlease( const char *sfxName, uint32 checksum, PlatformWaveInfo *pInfo, bool loadPerm )
+{
+	Dbg_Assert( pInfo );
+
+	// Need to append file type. Copy string into a buffer.
+	static char name_buffer[256] = "sounds\\dsp\\";
+	const int SOUND_PREPEND_START_POS = 11;
+
+	// Check there is room to add the extension.
+	int length = strlen( sfxName );
+	Dbg_Assert( strlen( sfxName ) <= ( 256 - ( SOUND_PREPEND_START_POS + 4 )));
+	strcpy( name_buffer + SOUND_PREPEND_START_POS, sfxName );
+	strcpy( name_buffer + SOUND_PREPEND_START_POS + length, ".dsp" );
+
+//	OSReport( "Loading sound: %s\n", name_buffer );
+
+	loadGamecubeDSP( pInfo, name_buffer, loadPerm, &pInfo->pitchAdjustmentForSamplingRate );
+
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//int PlaySoundPlease( PlatformWaveInfo *pInfo, float volL, float volR, float pitch )
+int PlaySoundPlease( PlatformWaveInfo *pInfo, sVolume *p_vol, float pitch )
+{
+	//Dbg_Assert( pInfo->headerAddress != NULL );
+	
+//	Spt::SingletonPtr< Sfx::CSfxManager > sfx_manager;
+//	if ( sfx_manager->GetMainVolume() < 1.0f ) return -1;
+
+	int voice = getFreeVoice();
+	if( voice == -1 )
+	{
+		// No voice in our array available.
+		return -1;
+	}
+
+	// Attempt to grab a voice. Set the priority high if it is a looping sound.
+	sDSPADPCM*	p_header	= (sDSPADPCM*)pInfo->headerAddress;
+	// HACK!!!
+	if( !p_header )
+	{
+		// No voice available.
+		return -1;
+	}
+	AXVPB*		p_axvpb		= AXAcquireVoice( ( p_header->loop_flag ) ? AX_PRIORITY_NODROP : AX_PRIORITY_LOWEST, ax_voice_reacquisition_callback, 0 );
+	if( p_axvpb == NULL )
+	{
+		// No voice available.
+		return -1;
+	}
+
+	AXPBADDR		addr;
+	AXPBSRC			src;
+	AXPBADPCM		adpcm;
+	AXPBADPCMLOOP	loop;        
+
+	addr.loopFlag           = p_header->loop_flag ? AXPBADDR_LOOP_ON : AXPBADDR_LOOP_OFF;
+	addr.format             = AX_PB_FORMAT_ADPCM;   
+
+	// Convert from byte to nibble address, and split into low and high words.
+	u32 start_address		= ( pInfo->vagAddress * 2 ) + 2;
+	u32 end_address			= ( pInfo->vagAddress * 2 ) + ( p_header->num_adpcm_nibbles - 1 );
+
+	addr.currentAddressHi   = (u16)( start_address >> 16 );
+	addr.currentAddressLo   = (u16)( start_address & 0xFFFFU );
+	addr.endAddressHi		= (u16)( end_address >> 16 );
+	addr.endAddressLo		= (u16)( end_address & 0xFFFFU );
+
+	if( p_header->loop_flag )
+	{
+		addr.loopAddressHi      = (u16)( start_address >> 16 );
+		addr.loopAddressLo      = (u16)( start_address & 0xFFFFU );
+	}
+	else
+	{
+		u32 zero_buffer_address	= NxNgc::EngineGlobals.aram_zero * 2;
+		addr.loopAddressHi      = (u16)( zero_buffer_address >> 16 );
+		addr.loopAddressLo      = (u16)( zero_buffer_address & 0xFFFFU );
+	}
+
+	loop.loop_pred_scale    = p_header->lps;
+	loop.loop_yn1           = p_header->lyn1;
+	loop.loop_yn2           = p_header->lyn2;
+
+//	uint32 srcBits			= (uint32)(0x00010000 * ((f32)p_header->sample_rate / AX_IN_SAMPLES_PER_SEC));
+//	src.ratioHi				= (u16)(srcBits >> 16);
+//	src.ratioLo				= (u16)(srcBits & 0xFFFF);
+	src.ratioHi             = 1;
+	src.ratioLo             = 0;
+	src.currentAddressFrac  = 0;
+	src.last_samples[0]     = 0;
+	src.last_samples[1]     = 0;
+	src.last_samples[2]     = 0;
+	src.last_samples[3]     = 0;
+
+	memcpy( &adpcm.a[0][0], p_header->coef, sizeof( u16 ) * 16 );
+	adpcm.gain              = p_header->gain;
+	adpcm.pred_scale        = p_header->ps;
+	adpcm.yn1               = p_header->yn1;
+	adpcm.yn2               = p_header->yn2;
+
+	AXPBVE ve;
+	ve.currentVolume		= 0x7FFF;					// This is max volume.
+	ve.currentDelta			= 0x0000;
+
+	AXSetVoiceType( p_axvpb, AX_PB_TYPE_NORMAL );
+	
+	AXSetVoiceAddr( p_axvpb, &addr );                   // input addressing
+	AXSetVoiceAdpcm( p_axvpb, &adpcm );                 // ADPCM coefficients
+	AXSetVoiceAdpcmLoop( p_axvpb, &loop );              // ADPCM loop context
+
+	AXSetVoiceVe( p_axvpb, &ve );
+	
+	AXSetVoiceSrcType( p_axvpb, AX_SRC_TYPE_LINEAR );   // SRC type
+	AXSetVoiceSrc( p_axvpb, &src );                     // initial SRC settings
+
+	// Set the voice to run.
+	AXSetVoiceState( p_axvpb, AX_PB_STATE_RUN );        
+	
+	// Fill in voice details.
+	VoiceSimulator[voice].p_axvpb		=  p_axvpb;
+	VoiceSimulator[voice].p_dspadpcm	=  p_header;
+//	OSReport( "voice %d started %d\n", voice, OSGetTick() );
+
+	// Adjust pitch to account for lower sampling rates:
+	pitch = PERCENT( pitch, pInfo->pitchAdjustmentForSamplingRate );
+
+	// This takes the place of AxSetVoiceMix().
+	SetVoiceParameters( voice, p_vol, pitch );
+	
+	return voice;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void StopSoundPlease( int whichVoice )
+{
+	if( VoiceIsOn( whichVoice ))
+	{
+		AXSetVoiceState( VoiceSimulator[whichVoice].p_axvpb, AX_PB_STATE_STOP );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void StopAllSoundFX( void )
+{
+//	Pcm::StopMusic();
+//	Pcm::StopStreams();
+
+	for( int i = 0; i < NUM_VOICES; i++ )
+	{
+		StopSoundPlease( i );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetVolumePlease( float volumeLevel )
+{
+	// Sets master volume level.
+	gSfxVolume = volumeLevel;
+
+//	Pcm::PCMAudio_SetStreamGlobalVolume( ( unsigned int ) volumeLevel );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void PauseSoundsPlease( void )
+{
+//	sVolume silent_vol;
+//	silent_vol.SetSilent();
+
+	// Just turn the volume down on all playing voices.
+	for( int i = 0; i < NUM_VOICES; i++ )
+	{
+		if( VoiceIsOn( i ))
+		{
+//			SetVoiceParameters( i, 0.0f, 0.0f );
+//			SetVoiceParameters( i, &silent_vol );
+			SetVoiceParameters( i, NULL );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CleanUpSoundFX( void )
+{
+	StopAllSoundFX();
+	SpuRAMUsedTemp = 0;
+	SetReverbPlease( 0 );
+
+	for( int i = 0; i < numTempSoundHeaders; ++i )
+	{
+		Dbg_Assert( tempSoundHeaders[i] );
+		Mem::Free( tempSoundHeaders[i] );
+		tempSoundHeaders[i] = NULL;
+	}
+	numTempSoundHeaders = 0;
+}
+
+
+
+
+
+
+
+
+
+// Macros and constants:
+
+int ReverbModes[ ] =
+{
+	// put the default first in this list, since the default param to SetReverb() is 0
+	0,
+//	SD_REV_MODE_HALL,
+//	SD_REV_MODE_ROOM,
+//	SD_REV_MODE_STUDIO_A,
+//	SD_REV_MODE_STUDIO_B,
+//	SD_REV_MODE_STUDIO_C,
+//	SD_REV_MODE_SPACE,
+//	SD_REV_MODE_ECHO,
+//	SD_REV_MODE_DELAY,
+//	SD_REV_MODE_PIPE,
+};
+
+#define NUM_REVERB_MODES ( ( int )( sizeof( ReverbModes ) / sizeof( int ) ) )
+
+#if NUM_STREAMS > 3
+#error tell matt to handle more than 3 streams in sfx.cpp
+#endif
+
+#if NUM_STREAMS == 3
+#define VOICES_MINUS_STREAMS ( 0xFFFFFF & ~( ( 1 << STREAM_VOICE( 0 ) | ( 1 << STREAM_VOICE( 1 ) ) | ( 1 << STREAM_VOICE( 2 ) ) ) )
+#else
+#define VOICES_MINUS_STREAMS ( 0xFFFFFF & ~( ( 1 << MUSIC_L_VOICE ) | ( 1 << MUSIC_R_VOICE ) ) )
+#endif
+
+// convert sample rate to pitch setting
+#define FREQ2PITCH( x ) ( ( float )( ( ( x ) * 100 ) / 48000 ) )
+
+#define DMA_CH						0
+
+#if ( ( CORE_0_EFFECTS_END_ADDRESS - CORE_0_EFFECTS_START_ADDRESS ) < ( RAM_NEEDED_FOR_EFFECTS ) )
+#error "not enough room for effects"
+#endif
+
+//int LoopingSounds[ NUM_CORES ];
+// gotta keep track of voices used, to be able
+// to tell whether a voice is free or not...
+//int VoicesUsed[ NUM_CORES ];
+
+// avoid checking voice status more than once per frame:
+//uint64	gLastVoiceAvailableUpdate[ NUM_CORES ];
+//int		gAvailableVoices[ NUM_CORES ];
+
+
+#define BLOCKHEADER_LOOP_BIT	( 1 << 9 )
+
+// Internal function prototypes:
+
+static bool	loadGamecubeDSP( PlatformWaveInfo *pInfo, const char *filename, bool loadPerm, float *samplePitch = NULL );
+
+int		PS2Sfx_PlaySound( PlatformWaveInfo *pInfo, int voiceNumber, float volL = 100.0f, float volR = 100.0f, float pitch = 100.0f );
+void	PS2Sfx_StopSound( int voiceNumber );
+int		PS2Sfx_GetVoiceStatus( int core );
+
+#define ALLIGN_REQUIREMENT	64 // EE to IOP
+#define SIZE_OF_LEADIN		16
+#define USE_CLEVER_TRICK_FOR_CLOBBERED_DATA		1
+
+
+
+
+
+
+
+
+
+
+
+// IOP_MIN_TRANSFER must be a power of 2!!!
+#define IOP_MIN_TRANSFER	64	// iop will clobber memory if you're writing above a sound.  we'll restore the
+								// potentially clobbered memory by keeping a buffer containing the beginning of
+								// the previously loaded perm sound, and copying that to the end of our new
+								// data...
+
+#if USE_CLEVER_TRICK_FOR_CLOBBERED_DATA
+#define		PAD_TO_PREVENT_CLOBBERING		0
+#else
+#define		PAD_TO_PREVENT_CLOBBERING		64
+#endif
+
+#define FULL_NAME_SIZE		256
+
+
+
+
+
+int GetMemAvailable( void )
+{
+	return ( MAX_SPU_RAM_AVAILABLE - ( SpuRAMUsedTemp + SpuRAMUsedPerm ) );
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//--------------------------------------------------------------------------------------------
+// Function: void SetReverb(void)
+// Description: Sets reverb level and type
+// Parameters:  none
+//
+// Returns:
+//-------------------------------------------------------------------------------------------
+void SetReverbPlease( float reverbLevel, int reverbMode, bool instant = false )
+{
+//	int i;
+//	sceSdEffectAttr r_attr;
+//
+//	Dbg_MsgAssert( reverbType >= 0,( "Bad reverb mode." ));
+//	Dbg_MsgAssert( reverbType < NUM_REVERB_MODES,( "Bad reverb mode..." ));
+//	
+//	if ( !reverbLevel )
+//	{
+//		// --- set reverb attribute
+//		r_attr.depth_L  = 0;
+//		r_attr.depth_R  = 0;
+//		r_attr.delay    = 0;
+//		r_attr.feedback = 0;
+//		r_attr.mode = SD_REV_MODE_OFF;
+//#if REVERB_ONLY_ON_CORE_0
+//		i = 0;  // just set reverb on core 0 (not the music core or any sfx on core 1)
+//#else
+//		for ( i = 0; i < 2; i++ )
+//#endif
+//		{
+//			// just turning off reverb on all voices...
+//			if(sceSdRemote( 1, rSdSetSwitch, i | SD_S_VMIXEL , 0 ) == -1 )
+//				Dbg_Message("Error setting reverb4\n");
+//			if(sceSdRemote( 1, rSdSetSwitch, i | SD_S_VMIXER , 0 ) == -1 )
+//				Dbg_Message("Error setting reverb5\n");
+//			if( sceSdRemote( 1, rSdSetEffectAttr, i, &r_attr ) == -1 )
+//				Dbg_Message( "Error setting reverb\n" );
+//		}
+//	}
+//	else
+//	{
+//		int reverbDepth = ( int )PERCENT( MAX_REVERB_DEPTH, reverbLevel );
+//		
+//		// --- set reverb attribute
+//		r_attr.depth_L  = 0x3fff;
+//		r_attr.depth_R  = 0x3fff;
+//		r_attr.delay    = 30;
+//		r_attr.feedback = 200;
+//		r_attr.mode = ReverbModes[ reverbType ];
+//#if REVERB_ONLY_ON_CORE_0
+//		i = 0;
+//#else		
+//		for( i = 0; i < 2; i++ )
+//#endif
+//		{
+//			if( sceSdRemote( 1, rSdSetEffectAttr, i, &r_attr ) == -1 )
+//				Dbg_Message( "Error setting reverb\n" );
+//	
+//			if(sceSdRemote( 1, rSdSetCoreAttr, i | SD_C_EFFECT_ENABLE, 1 ) == -1 )
+//				Dbg_Message("Error setting reverb\n");
+//			if(sceSdRemote( 1, rSdSetParam, i | SD_P_EVOLL , reverbDepth ) == -1 )
+//				Dbg_Message("Error setting reverb2\n");
+//			if(sceSdRemote( 1, rSdSetParam, i | SD_P_EVOLR , reverbDepth ) == -1 )
+//				Dbg_Message("Error setting reverb3\n");;
+//			if(sceSdRemote( 1, rSdSetSwitch, i | SD_S_VMIXEL , VOICES_MINUS_STREAMS ) == -1 )
+//				Dbg_Message("Error setting reverb4\n");
+//			if(sceSdRemote( 1, rSdSetSwitch, i | SD_S_VMIXER , VOICES_MINUS_STREAMS ) == -1 )
+//				Dbg_Message("Error setting reverb5\n");
+//			
+//		}
+//	}
+} // end of SetReverb( )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+} // namespace Sfx
diff --git a/Code/Gel/SoundFX/ngc/p_sfx.h b/Code/Gel/SoundFX/ngc/p_sfx.h
new file mode 100644
index 0000000..391f78f
--- /dev/null
+++ b/Code/Gel/SoundFX/ngc/p_sfx.h
@@ -0,0 +1,82 @@
+/*	Playstation 2 sound support functions.
+	mjd	jan 10, 2001
+*/
+#ifndef __MODULES_P_SFX_H
+#define __MODULES_P_SFX_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+// Need to include this to get NUM_STREAMS.
+#include 
+
+namespace Sfx
+{
+		  
+#define NUM_MUSIC_VOICES		2
+#define NUM_VOICES				( 64 - NUM_STREAMS - NUM_MUSIC_VOICES ) // leave some voices per core for streaming audio off the CD and shit...
+
+struct sVolume;
+
+// platform specific info needed per wave (stored in the WaveTable):
+struct PlatformWaveInfo
+{
+	int		vagAddress;		// PS2 waves played by setting a voice to the address of the wave on the sound chip...	
+	void*	headerAddress;	// Main memory location of header information.
+	bool	looping;
+	float	pitchAdjustmentForSamplingRate; // figure out how much to alter the pitch due to lower sampling rates than 48000.
+};
+
+void	GetStreamBufferInfo( int* stream0_base, int* stream1_base, int* stream2_base, int* stream0_size, int* stream1_size, int* stream2_size );
+class	CSfxManager;
+
+// on startup of the game only...
+void PS2Sfx_InitCold( void );
+
+void	AXUserCBack( void );
+
+
+void	InitSoundFX( CSfxManager *p_sfx_manager );
+void	ReInitSoundFX( void );
+void	CleanUpSoundFX( void );
+void	StopAllSoundFX( void );
+bool	LoadSoundPlease( const char *sfxName, uint32 checksum, PlatformWaveInfo *pInfo, bool loadPerm = 0 );
+
+int		GetMemAvailable( void );
+
+// returns 0 - ( NUM_VOICES - 1 ), or -1 if no voices are free...
+//int		PlaySoundPlease( PlatformWaveInfo *pInfo, float volL = 100.0f, float volR = 100.0f, float pitch = 100.0f );
+int		PlaySoundPlease( PlatformWaveInfo *pInfo, sVolume *p_vol, float pitch = 100.0f );
+
+void	StopSoundPlease( int whichVoice );
+
+extern bool	isStereo;
+
+// really just turning down the volume on all playing voices...
+// Looping sounds will get the volume adjusted by the owner
+// during the next logic loop.
+// The non-looping sounds will play (with no volume) then stop.
+// This is for when we go into the menus or whatnot...
+void	PauseSoundsPlease( void );
+
+// see reverb types in ReverbModes table (p_sfx.cpp), corresponding to reverb.q script file
+void	SetReverbPlease( float reverbLevel = 0.0f, int reverbMode = 0, bool instant = false );
+
+// volume from -100 to 100 ( negative if the sound is from behind us. ps2 alters the sound... )
+void	SetVolumePlease( float volumeLevel );
+
+// returns true if voice is being used currently.
+bool	VoiceIsOn( int whichVoice );
+
+// change parameters on an active voice:
+// if pitch parameter is zero, pitch won't be changed (lowest pitch is 1)
+//void	SetVoiceParameters( int whichVoice, float volL, float volR, float pitch = 0.0f );
+void	SetVoiceParameters( int whichVoice, sVolume *p_vol, float pitch = 0.0f );
+
+void	PerFrameUpdate( void );
+
+
+} // namespace Gel
+
+#endif
diff --git a/Code/Gel/SoundFX/soundfx.cpp b/Code/Gel/SoundFX/soundfx.cpp
new file mode 100644
index 0000000..969cfbd
--- /dev/null
+++ b/Code/Gel/SoundFX/soundfx.cpp
@@ -0,0 +1,1833 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL Library												**
+**																			**
+**	Module:			Sound effects (Sfx)	 									**
+**																			**
+**	File name:		soundfx.cpp												**
+**																			**
+**	Created by:		01/10/01	-	dc										**
+**																			**
+**	Description:	SoundFX for the game. These are the sounds that are		**
+**					loaded into memory. Streaming SoundFX are in the Music	**
+**					modules (music.cpp and p_music.cpp) which should		**
+**					actually be called the Streaming module.				**
+**																			**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include   // until gametimer is moved into lower level stuff..
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+// Used by the script debugger code to fill in a structure
+// for transmitting to the monitor.exe utility running on the PC.
+void ObjectSoundInfo::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to ObjectSoundInfo::GetDebugInfo"));
+	
+	p_info->AddChecksum("checksum",checksum);
+	p_info->AddFloat("dropoffDist",dropoffDist);
+	p_info->AddFloat("currentPitch",currentPitch);
+	p_info->AddFloat("currentVolume",currentVolume);
+	p_info->AddFloat("origPitch",origPitch);
+	p_info->AddFloat("origVolume",origVolume);
+	p_info->AddFloat("targetPitch",targetPitch);
+	p_info->AddFloat("targetVolume",targetVolume);
+	p_info->AddFloat("deltaPitch",deltaPitch);
+	p_info->AddFloat("deltaVolume",deltaVolume);
+	p_info->AddInteger("timeForNextDistCheck",timeForNextDistCheck);
+#endif				 
+}
+
+namespace Sfx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool NoSoundPlease( void )
+{
+#	if NO_SOUND_PLEASE
+	return true;
+#	else
+	// Cannot have sound if building for proview, because there is not enough IOP memory
+	// (ProView uses up some IOP memory with it's monitor)
+	if( Config::GetHardware() == Config::HARDWARE_PS2_PROVIEW )
+	{
+		return true;
+	}
+	return false;
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EDropoffFunc	GetDropoffFunctionFromChecksum(uint32 checksum)
+{
+	switch (checksum)
+	{
+	case 0xef082878: // standard
+		return DROPOFF_FUNC_STANDARD;
+
+	case 0xf221474c: // linear
+		return DROPOFF_FUNC_LINEAR;
+
+	case 0x9706104b: // exponential
+		return DROPOFF_FUNC_EXPONENTIAL;
+
+	case 0xca64c41: // inv_exponential
+		return DROPOFF_FUNC_INV_EXPONENTIAL;
+
+	default:
+		Dbg_MsgAssert(0, ("Invalid dropoff function type %s (%x)", Script::FindChecksumName(checksum), checksum));
+		return DROPOFF_FUNC_STANDARD;
+	}
+}
+
+
+
+DefineSingletonClass( CSfxManager, "Sound FX" );
+
+static float	DefaultDropoffDist = DEFAULT_DROPOFF_DIST;
+
+
+VoiceInfo		VoiceInfoTable[NUM_VOICES];
+
+// The entries will be permanent entries, followed by temporary entries...
+WaveTableEntry	WaveTable[PERM_WAVE_TABLE_MAX_ENTRIES + WAVE_TABLE_MAX_ENTRIES];
+
+// This structure is used on sounds that aren't looping, but are long enough that they need to be updated for
+// volume and pan (like the things pedestrians are saying to each other for example).
+struct PositionalSoundEntry
+{
+	int							uniqueID;
+	uint32						checksum;
+	uint32						flags;
+	Obj::CSoundComponent				*pObj;
+	struct PositionalSoundEntry	*pNext;
+	struct PositionalSoundEntry	*pPrev;
+};
+
+#define POSITIONAL_SOUND_FLAG_OCCUPIED	( 1 << 0 )
+#define POSITIONAL_SOUND_FLAG_DOPPLER	( 1 << 1 )
+
+static PositionalSoundEntry				*GpPositionalSounds = NULL;
+static PositionalSoundEntry				PositionalSounds[MAX_POSITIONAL_SOUNDS];
+
+int										NumWavesInTable		= 0;
+int										NumWavesInPermTable	= 0;
+int										NumPositionalSounds = 0;
+float									gVolume				= 100.0f;
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVolume::sVolume( void )
+{
+	// Set the volume type to be the default volume type of the manager.
+	Sfx::CSfxManager * p_manager = Sfx::CSfxManager::Instance();
+	if( p_manager )
+	{
+		m_volume_type = p_manager->GetDefaultVolumeType();
+	}
+	else
+	{
+		m_volume_type = VOLUME_TYPE_BASIC_2_CHANNEL;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVolume::sVolume( EVolumeType type )
+{
+	m_volume_type = type;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVolume::sVolume( const sVolume& rhs )
+{
+	m_volume_type = rhs.m_volume_type;
+	
+	uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
+	for( uint32 c = 1; c < num_channels; ++c )
+	{
+		m_channels[c] = rhs.m_channels[c];
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVolume::~sVolume( void )
+{
+}
+
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVolume::PercentageAdjustment( float percentage )
+{
+	uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
+	for( uint32 c = 0; c < num_channels; ++c )
+	{
+		m_channels[c] = PERCENT( m_channels[c], percentage );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float sVolume::GetLoudestChannel( void )
+{
+	float loudest = m_channels[0];
+	
+	uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
+	for( uint32 c = 1; c < num_channels; ++c )
+	{
+		// Allow for phase shifting, which would make a channel negative.
+		if( fabsf( m_channels[c] ) > fabsf( loudest ))
+			loudest = m_channels[c];
+	}
+	return loudest;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sVolume::IsSilent( void )
+{
+	// All volume types have at least two channels.
+	if(( m_channels[0] != 0.0f ) || ( m_channels[1] != 0.0f ))
+		return false;
+	
+	// Check remaining channels for 5.1 type volumes.
+	if( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 )
+	{
+		if(( m_channels[2] != 0.0f ) || ( m_channels[3] != 0.0f ) || ( m_channels[4] != 0.0f ))
+			return false;
+	}
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVolume::SetSilent( void )
+{
+	// Strictly speaking, we only need to set as many channels silent as the type
+	// of the volume requires, but it's probably no less efficient to do it this way.
+	memset( m_channels, 0, sizeof( float ) * MAX_CHANNELS );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sVolume::operator== ( const sVolume& rhs )
+{
+	if (m_volume_type != rhs.m_volume_type)
+	{
+		return false;
+	}
+	
+	uint32 num_channels = ( m_volume_type == VOLUME_TYPE_5_CHANNEL_DOLBY5_1 ) ? 5 : 2;
+	for( uint32 c = 1; c < num_channels; ++c )
+	{
+		if (m_channels[c] != rhs.m_channels[c])
+		{
+			return false;
+		}
+	}
+	
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSfxManager::CSfxManager( void )
+{
+	if( NoSoundPlease())
+		return;
+
+	// Set the default volume type to the most basic available - platform specific code
+	// may override this.
+	SetDefaultVolumeType( VOLUME_TYPE_BASIC_2_CHANNEL );
+
+	InitSoundFX( this );
+
+	// Clear out the uniqueID table. Only has to be done once (even if module is reset).
+	for( int i = 0; i < NUM_VOICES; i++ )
+	{
+		VoiceInfoTable[i].uniqueID = 0;
+		VoiceInfoTable[i].controlID = 0;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSfxManager::~CSfxManager( void )
+{
+	if( NoSoundPlease())
+		return;
+
+	CleanUp();	
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSfxManager::CleanUp( void )
+{
+	// Call this at the end of a level (or the beginning of the next level before loading sounds, or both).
+	if( NoSoundPlease())
+		return;
+
+	GpPositionalSounds	= NULL;
+	NumPositionalSounds	= 0;
+	for( int i = 0; i < MAX_POSITIONAL_SOUNDS; i++ )
+	{
+		PositionalSounds[i].flags = 0;
+	}
+	
+	// Should stop all sounds and do whatever cleanup necessary so soundfx can be used in the next phase
+	// (next level, frontend, whatever).
+	CleanUpSoundFX();
+
+	NumWavesInTable		= 0;
+	DefaultDropoffDist	= DEFAULT_DROPOFF_DIST;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+bool CSfxManager::IDAvailable(uint32 id)
+{
+	return !SoundIsPlaying(id, NULL);
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+int CSfxManager::GetVoiceFromID(uint32 id)
+{
+	int i;
+
+	// Check unique ID's first
+	for( i = 0; i < NUM_VOICES; i++ )
+	{
+		if ((VoiceInfoTable[i].uniqueID == id) && VoiceIsOn(i))
+		{
+			return i;
+		}
+	}
+
+	// Now the control ID's
+	for( i = 0; i < NUM_VOICES; i++ )
+	{
+		if ((VoiceInfoTable[i].controlID == id) && VoiceIsOn(i))
+		{
+			if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ) )
+			{
+				Dbg_MsgAssert(0, ("Trying to control duplicate sound %s by checksum", Script::FindChecksumName(id)));
+			}
+			else
+			{
+				Dbg_Message("ERROR: Trying to control duplicate sound %s by checksum", Script::FindChecksumName(id));
+			}
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSfxManager::GenerateUniqueID(uint32 id)
+{
+	// Keep incrementing ID until one works.
+	while (!IDAvailable(id))
+		id++;
+
+	return id;
+}
+
+
+/******************************************************************/
+/*																  */
+/* Different levels might want to have different dropoff		  */
+/* distances.													  */
+/*                                                                */
+/******************************************************************/
+void CSfxManager::SetDefaultDropoffDist( float dist )
+{
+	if( NoSoundPlease())
+		return;
+
+	Dbg_MsgAssert( dist > 0.0f, ( "Can't set dropoff dist to 0 or less." ));
+
+	DefaultDropoffDist = dist;
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+void CSfxManager::StopAllSounds( void )
+{
+	if( NoSoundPlease())
+		return;
+	
+	StopAllSoundFX();
+	GpPositionalSounds	= NULL;
+	NumPositionalSounds	= 0;
+	for( int i = 0; i < MAX_POSITIONAL_SOUNDS; i++ )
+	{
+		PositionalSounds[i].flags = 0;
+	}
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+void CSfxManager::PauseSounds( void )
+{
+	if( NoSoundPlease())
+		return;
+
+	PauseSoundsPlease();
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+WaveTableEntry * CSfxManager::GetWaveTableIndex( uint32 checksum )
+{
+	if( NoSoundPlease())
+		return NULL;
+
+	// Check through list of perm sounds.
+	for( int i = 0; i < NumWavesInPermTable; i++ )
+	{
+		if ( checksum == WaveTable[i].checksum )
+			return &WaveTable[i];
+	}
+
+	// Check through list of temp sounds.
+	for( int i = 0; i < NumWavesInTable; i++ )
+	{
+		if( checksum == WaveTable[i + PERM_WAVE_TABLE_MAX_ENTRIES].checksum )
+			return &WaveTable[i + PERM_WAVE_TABLE_MAX_ENTRIES];
+	}
+
+	// Not found.
+	return NULL;	
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+int CSfxManager::MemAvailable( void )
+{
+	return GetMemAvailable();
+}
+
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+bool CSfxManager::PositionalSoundSilenceMode()
+{
+	// Add any conditions here where you want to set the positional sound
+	// to 0.
+
+	// Don't update if loading screen is active
+	if( Nx::CLoadScreen::sIsActive() )
+	{
+		return true;
+	}
+
+	// Don't update if the game is paused
+	if( Mdl::FrontEnd::Instance()->GamePaused() )
+	{
+		return true;
+	}
+
+#if 0	// TT2808: Seemed to cause more problems than it helped.
+	// Don't update if the game is playing a CamAnim
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	if ( skate_mod->GetMovieManager()->IsRolling() )
+	{
+		return true;
+	}
+#endif
+
+	//Dbg_Message("Updating sounds frame %d", Tmr::GetRenderFrame());
+	return false;
+}
+
+/******************************************************************/
+/*																  */
+/* Returns true if sound is still playing.                        */
+/* Adjust the pitch for doppler, and the volume and pan for		  */
+/* position.													  */
+/*																  */
+/******************************************************************/
+bool CSfxManager::AdjustObjectSound( Obj::CSoundComponent *pObj, VoiceInfo *pVoiceInfo, Tmr::Time gameTime )
+{
+	Dbg_Assert( pVoiceInfo );
+
+	ObjectSoundInfo *pInfo = &pVoiceInfo->info;
+	
+	float dist;
+
+	// Get dropoff distance if sent to Obj_PlaySound. If not, get default (set up in LoadSound).
+	float dropoffDist = ( pInfo->dropoffDist == 0.0f ) ? DefaultDropoffDist : pInfo->dropoffDist;
+
+	Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pObj->GetPosition(), &dist );
+	if( !pCamera )
+	{
+		Dbg_Message( "Warning: Positional sound, but no camera... Sound won't be updated." );
+
+		// If there is no camera, just don't update the sound but keep it playing the same as before.
+		// (If we return false, the object will delete its sound).
+		// We might not be able to find the camera at the start of the level or perhaps when changing levels.
+		return true;
+	}
+
+	Mth::Vector dropoff_pos;
+	Mth::Vector *p_dropoff_pos = NULL;
+	if (pObj->GetClosestDropoffPos(pCamera, dropoff_pos))
+	{
+		p_dropoff_pos = &dropoff_pos;
+	}
+	// See if the object sound could be turned off (far enough away from the camera).
+	else if( pInfo->timeForNextDistCheck <= gameTime )
+	{
+//		Dbg_Message( "checking if we need to turn %s off", Script::FindChecksumName( pInfo->checksum ) );
+		if( dist >= ( dropoffDist + DIST_FROM_DROPOFF_AT_WHICH_TO_STOP_SOUND ))
+		{
+//			Dbg_Message( "sound %s auto off", Script::FindChecksumName( pInfo->checksum ) );
+
+			// If the sound is looping, let the scripts continue to think the
+			// sound is still around and keep pitch/volume adjusted for the action.
+			if( pVoiceInfo->waveIndex->platformWaveInfo.looping )
+			{
+				// Turn the sound off, monitor the sound from the object update.
+				pObj->SoundOutOfRange( pInfo, gameTime );
+			}
+
+			// Should cause this sound to be stopped and voice cleared!
+			return false;
+		}
+		// Still okay, sound still in range. Continue playing for x more milliseconds.
+		pInfo->timeForNextDistCheck = gameTime + 2000;
+	}
+	
+	float pitch	= 100.0f;
+	sVolume vol;
+	vol.SetSilent();
+
+	// If dist is beyond dropoff dist, leave volume at zero.
+	if( /*!PositionalSoundSilenceMode() &&*/ ( p_dropoff_pos || (dist < dropoffDist) ) )
+	{
+		Mth::Vector obj_pos(pObj->GetClosestEmitterPos(pCamera));
+
+		if( pVoiceInfo->waveIndex->flags & SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER )
+		{
+			// Even if the object didn't move, the camera could have!
+			AdjustPitchForDoppler( &pitch, obj_pos, pObj->GetClosestOldEmitterPos(pCamera), Tmr::FrameLength(), pCamera );
+		}
+		SetVolumeFromPos( &vol, obj_pos, dropoffDist, pInfo->dropoffFunction, pCamera, p_dropoff_pos );
+	}
+	
+	if( !Config::CD())
+	{
+		if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ) && !vol.IsSilent())
+		{
+			Dbg_Message( "Updating sound %s with volL = %f volR = %f", Script::FindChecksumName( pInfo->checksum ), vol.GetChannelVolume(0), vol.GetChannelVolume(1));
+		}
+	}	
+
+	return UpdateLoopingSound( pVoiceInfo->uniqueID, &vol, pitch );
+}
+
+
+
+
+/******************************************************************/
+/*																  */
+/* Only updates one sound for frame (since it's slow to send	  */
+/* commands between the EE and the IOP)                           */
+/*																  */
+/******************************************************************/
+void CSfxManager::UpdatePositionalSounds( void )
+{
+	PositionalSoundEntry	*pEntry		= GpPositionalSounds;
+	PositionalSoundEntry	*pTemp;
+	Tmr::Time				gameTime	= Tmr::GetTime();
+
+//	static int				active		= 0;	   // Mick: Only update this one this frame.
+//	int						count		= 0;	   // Counter for all the sounds.
+
+//	uint32 start_time = Tmr::GetTimeInUSeconds();	
+	while ( pEntry )
+	{
+		pTemp	= pEntry;
+		pEntry	= pEntry->pNext;
+		Dbg_MsgAssert( pTemp->flags & POSITIONAL_SOUND_FLAG_OCCUPIED, ( "Sounds in the list should have 'occupied' turned on." ));
+		
+		// This just filters the current volume/pitch towards the target, and should be done every frame per sound:
+		Dbg_MsgAssert( pTemp->uniqueID,( "UniqueID 0 means no sound was played.  Shouldn't have gotten this far." ));
+	
+		int voiceIndex;
+		if( !SoundIsPlaying( pTemp->uniqueID, &voiceIndex ))
+		{
+			// Wasn't a looping sound, and it's done playing.
+			RemovePositionalSoundFromList( pTemp, false );
+		}
+		else
+		{
+			VoiceInfo *pVoiceInfo = &VoiceInfoTable[voiceIndex];
+			pTemp->pObj->UpdateObjectSound( &pVoiceInfo->info );
+			
+			// Only do the low-down nitty gritty positional stuff on one sound per frame.
+			//if(active == count)
+			{
+				Dbg_MsgAssert( pTemp->pObj,( "Null object pointer in positional sound." ));
+
+#				ifdef __USE_PROFILER__
+				//Sys::CPUProfiler->PushContext( 0, 0, 128 );		// blue = this bit of sound code.
+#				endif // __USE_PROFILER__
+				
+				if( !AdjustObjectSound( pTemp->pObj, pVoiceInfo, gameTime ))
+				{
+					RemovePositionalSoundFromList( pTemp, true );
+				}
+
+#				ifdef __USE_PROFILER__
+				//Sys::CPUProfiler->PopContext();
+#				endif // __USE_PROFILER__
+			}
+		}
+		//count++;
+	}
+
+//	uint32 end_time = Tmr::GetTimeInUSeconds();	
+//	if ((end_time - start_time) > 250)
+//		Dbg_Message("CSfxManager::UpdatePositionalSounds Frame %d Time %d (%x - %x)", Tmr::GetVblanks(), (end_time - start_time), end_time, start_time);
+
+#if 0		// We can update all sounds now
+	// Increment the active sound and wrap based on current count.
+	active++;
+
+	// Wrap based on current count.
+	if( active >= count )
+	{
+		active = 0;
+	}
+#endif
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+void CSfxManager::RemovePositionalSoundFromList( PositionalSoundEntry *pEntry, bool stopIfPlaying )
+{
+	pEntry->flags = 0;
+	
+	// Stop the sound from playing if it is playing.
+	if( stopIfPlaying && SoundIsPlaying( pEntry->uniqueID ))
+	{
+		StopSound( pEntry->uniqueID );
+	}
+
+	if( pEntry->pNext )
+	{
+		pEntry->pNext->pPrev = pEntry->pPrev;
+	}
+
+	if ( pEntry->pPrev )
+	{
+		pEntry->pPrev->pNext = pEntry->pNext;
+	}
+	else
+	{
+		GpPositionalSounds = pEntry->pNext;
+	}
+	NumPositionalSounds--;
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* Called from the object's destructor.                           */
+/* Stops all soundfx and streams associated with the object.	  */
+/*																  */
+/******************************************************************/
+void CSfxManager::ObjectBeingRemoved( Obj::CSoundComponent *pObj )
+{
+	PositionalSoundEntry *pEntry = GpPositionalSounds;
+	PositionalSoundEntry *pTemp;
+	while( pEntry )
+	{
+		pTemp	= pEntry;
+		pEntry	= pEntry->pNext;
+		
+		if( pTemp->pObj == pObj )
+		{
+			RemovePositionalSoundFromList( pTemp, true );
+
+			// Keep scanning the list, in case there's more than one...
+		}
+	}
+
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* Stop a sound on an object.									  */
+/*																  */
+/******************************************************************/
+void CSfxManager::StopObjectSound( Obj::CSoundComponent *pObj, uint32 checksum )
+{
+	PositionalSoundEntry *pEntry = GpPositionalSounds;
+	PositionalSoundEntry *pTemp;
+	while( pEntry )
+	{
+		pTemp	= pEntry;
+		pEntry	= pEntry->pNext;
+		if( pTemp->pObj == pObj )
+		{
+			if(( !checksum ) || ( pTemp->checksum == checksum ))
+			{
+				// This will stop the sound as well as removing it from the list.
+				RemovePositionalSoundFromList( pTemp, true );
+
+				// Keep scanning the list, in case there's more than one.
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+void CSfxManager::AddPositionalSoundToUpdateList( uint32 uniqueID, uint32 soundChecksum, Obj::CSoundComponent *pObj )
+{
+	if( !uniqueID )
+	{
+		// Sound play was unsuccessful.
+		return;
+	}
+	if( NumPositionalSounds == MAX_POSITIONAL_SOUNDS )
+	{
+		Dbg_Message( "WARNING: Ran out of positional sound slots.\nIncrease the number of slots, or decrease the number of requests." );
+		return;
+	}
+	NumPositionalSounds++;
+	int i;
+	for( i = 0; i < MAX_POSITIONAL_SOUNDS; i++ )
+	{
+		PositionalSoundEntry *pEntry = &PositionalSounds[i];
+
+		if( !( pEntry->flags & POSITIONAL_SOUND_FLAG_OCCUPIED ))
+		{
+			if( GpPositionalSounds )
+			{
+				GpPositionalSounds->pPrev = pEntry;
+			}
+			pEntry->pNext = GpPositionalSounds;
+			pEntry->pPrev = NULL;
+			GpPositionalSounds = pEntry;
+			pEntry->uniqueID = uniqueID;
+			pEntry->checksum = soundChecksum;
+			pEntry->flags = POSITIONAL_SOUND_FLAG_OCCUPIED;
+			pEntry->pObj = pObj;
+			return;
+		}
+	}
+	Dbg_MsgAssert( 0, ( "This is impossible." ));
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* SFX Name should be the name of the sound, no extension or	  */
+/* path. It will be the responsibility of the loader for each	  */
+/* platform to add the path and append the extension.			  */
+/* This module assumes the sounds are all loaded at the beginning */
+/* of a level or module, and all removed at the end.			  */
+/*																  */
+/******************************************************************/
+bool CSfxManager::LoadSound( const char *sfxName,  int flags, float dropoff, float pitch, float volume )
+{
+	if( NoSoundPlease())
+		return false;
+
+	const char	*pNameMinusPath	= sfxName;
+	int			stringLength	= strlen( sfxName );
+	for( int i = 0; i < stringLength; i++ )
+	{
+		if(( sfxName[i] == '\\' ) || ( sfxName[i] == '/' ))
+			pNameMinusPath = &sfxName[i + 1];
+	}
+	
+	uint32			checksum	= Script::GenerateCRC( pNameMinusPath );
+	WaveTableEntry	*pWaveEntry	= NULL;
+	
+	// See if the sound is already loaded.
+	if( NULL != GetWaveTableIndex( checksum ))
+	{
+		if( !Config::CD())
+		{
+			if( Script::GetInt( 0xd7bb618d /* DebugSoundFx */, false )) 
+			{
+				Dbg_Message( "Warning: Sound '%s' already loaded (maybe from a different directory)?", sfxName );
+			}
+		}
+		return false;
+	}
+	
+	if( flags & SFX_FLAG_LOAD_PERM )
+	{
+		if( NumWavesInPermTable >= PERM_WAVE_TABLE_MAX_ENTRIES )
+		{
+			Dbg_Message( "Increase PERM_WAVE_TABLE_MAX_ENTRIES, no room for %s", sfxName );
+			Dbg_MsgAssert( 0,( "Too many permanent sound waves being loaded." ));
+			return false;
+		}
+		pWaveEntry = &WaveTable[ NumWavesInPermTable ];
+		if( !LoadSoundPlease( sfxName, checksum, &pWaveEntry->platformWaveInfo, 1 ))
+		{
+			Dbg_Message( "failed to load sound %s", sfxName );
+			return false;
+		}
+		NumWavesInPermTable++;
+	}
+	else
+	{
+		if( NumWavesInTable >= WAVE_TABLE_MAX_ENTRIES )
+		{
+			Dbg_MsgAssert( 0, ( "Too many sound waves being loaded." ));
+			return false;
+		}
+		pWaveEntry = &WaveTable[ NumWavesInTable + PERM_WAVE_TABLE_MAX_ENTRIES ];
+		if( !LoadSoundPlease( sfxName, checksum, &pWaveEntry->platformWaveInfo ))
+		{
+			return false;
+		}
+		NumWavesInTable++;
+	}
+
+	Dbg_MsgAssert( volume >= 0.0f,( "Tweak volume less than zero." ));
+	Dbg_MsgAssert( volume <= MAX_VOL_ALLOWED,( "Tweak volume greater than max allowed." ));	
+	Dbg_Assert( pWaveEntry );
+
+	pWaveEntry->dropoff		= dropoff;
+	pWaveEntry->pitch		= pitch;
+	pWaveEntry->volume		= volume;
+	pWaveEntry->checksum	= checksum;
+	pWaveEntry->flags		= flags;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+void CSfxManager::TweakVolumeAndPitch( sVolume *p_vol, float *pitch, WaveTableEntry* waveTableIndex )
+{
+	if( NoSoundPlease())
+		return;
+
+	Dbg_Assert( p_vol && pitch );
+	
+	// 100% volume would result in 1/2 the real MAX. This gives headroom for louder sounds!
+	p_vol->PercentageAdjustment( 50.0f );
+	
+	// Apply tweaked volumes, pitches to sound (variables from LoadSound).
+	if( waveTableIndex->volume != 100.0f )
+	{
+		p_vol->PercentageAdjustment( waveTableIndex->volume );
+	}
+	if( waveTableIndex->pitch != 100.0f )
+	{
+		*pitch = PERCENT( *pitch, waveTableIndex->pitch );
+	}
+}
+
+
+
+
+/******************************************************************/
+/*																  */
+/* Returns a unique ID each time a sound is played, 0 on failure. */
+/* The 'unique ID' is just a number that is incremented each	  */
+/* time a sound is played.										  */
+/*																  */
+/* For each platform, keep a table that stores these unique ID's  */
+/* for each voice. PS2 has 48 voices.							  */
+/*																  */
+/* If somebody calls "StopSound" I check each of the entries in	  */
+/* the table for a match, and if a match is found and that voice  */
+/* is playing, I stop the voice.								  */
+/*																  */
+/* If a match is not found, that means the voice stopped playing  */
+/* earlier and somebody else may be using the voice now, so that  */
+/* voice shouldn't be stopped									  */
+/*																  */
+/******************************************************************/
+uint32 CSfxManager::PlaySound( uint32 checksum, sVolume *p_vol, float pitch, uint32 controlID, SoundUpdateInfo *pUpdateInfo, const char *pSoundName )
+{
+	if( NoSoundPlease())
+		return 0;
+
+	Dbg_Assert( p_vol );
+	
+	WaveTableEntry *waveTableIndex = GetWaveTableIndex( checksum );
+	if( waveTableIndex == NULL )
+	{
+		//Dbg_MsgAssert( 0,( "Couldn't find sound %s", Script::FindChecksumName( checksum )));
+		Dbg_Message("******** NON-FATAL ERROR: Couldn't find sound %s", Script::FindChecksumName( checksum ));
+		return 0;
+	}
+
+	// Just use checksum if no controlID is set
+	if (controlID == 0)
+	{
+		controlID = checksum;
+	}
+
+	if( !Config::CD())
+	{
+		if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
+		{
+			if (!pUpdateInfo && !IDAvailable(controlID))
+			{
+				Dbg_Message("Playing sound %s with same checksum as one already being played, won't be able to control from script directly.", Script::FindChecksumName(controlID));
+			}
+		}
+	}
+
+	TweakVolumeAndPitch( p_vol, &pitch, waveTableIndex );
+	
+	// Continue to play the sound, even if the volume is zero. We might be starting a looping sound,
+	// so don't second guess this.
+	// For the moment, don't record looping sounds, due to problems with running out of voices due to
+	// voices still being used by game when it is paused. They just have their volume turned down to zero.
+	if( !waveTableIndex->platformWaveInfo.looping )
+	{
+		// Only record the sound in the replay if the game is not paused, otherwise the menu sounds in the
+		// paused menu will get recorded.
+		Mdl::FrontEnd* p_frontend = Mdl::FrontEnd::Instance();
+		if( !p_frontend->GamePaused())
+		{
+//			Replay::WritePlaySound( checksum, volL, volR, pitch );
+		}	
+	}
+	
+	// In PlaySoundPlease(), clip sounds to the max allowable. If no volume modifiers are listed in the script
+	// commands (LoadSound or PlaySound) and the sound is played without attenuation, volL and volR will be 1/2
+	// the max (so 50 at this point). Find the platform specific volumes like this:
+	// leftVolume = PERCENT( volL, PLATFORM_MAX ). Clip to PLATFORM_MAX (or -PLATFORM_MAX if phasing is supported).
+#ifdef __PLAT_NGPS__
+	int whichVoice = PlaySoundPlease( &waveTableIndex->platformWaveInfo, p_vol, pitch, waveTableIndex->flags & SFX_FLAG_NO_REVERB);
+#else
+	int whichVoice = PlaySoundPlease( &waveTableIndex->platformWaveInfo, p_vol, pitch );
+#endif
+
+	if( whichVoice == -1 )
+	{
+		Dbg_Message( "Couldn't find free voice." );
+		return 0;
+	}
+	Dbg_MsgAssert( whichVoice < NUM_VOICES,( "Voice larger than max allowed." ));
+	VoiceInfo *pVoiceInfo = &VoiceInfoTable[whichVoice];
+	pVoiceInfo->controlID = controlID;
+	pVoiceInfo->uniqueID = GenerateUniqueID(controlID);
+
+	// So we don't have to look this up every time we query this module for the properties of a sound that's
+	// playing (which we do often for looping sounds).
+	pVoiceInfo->waveIndex		= waveTableIndex;
+	ObjectSoundInfo *pInfo		= &pVoiceInfo->info;
+	if ( pUpdateInfo )
+	{
+		pInfo->currentPitch		= pUpdateInfo->pitch;
+		pInfo->currentVolume	= pUpdateInfo->volume;
+		pInfo->dropoffDist		= pUpdateInfo->dropoffDist ? pUpdateInfo->dropoffDist : waveTableIndex->dropoff;
+		pInfo->origPitch		= pInfo->targetPitch = pInfo->currentPitch;
+		pInfo->origVolume		= pInfo->targetVolume = pInfo->currentVolume;
+		pInfo->deltaPitch		= 0.0f;
+		pInfo->deltaVolume		= 0.0f;
+		pInfo->dropoffFunction	= pUpdateInfo->dropoffFunction;
+	}
+	else
+	{
+		pInfo->currentPitch		= 100.0f;
+		pInfo->currentVolume	= 100.0f;
+		pInfo->dropoffDist		= 0.0f;
+		pInfo->dropoffFunction	= DROPOFF_FUNC_STANDARD;
+	}
+	pInfo->checksum = checksum;
+	if( !Config::CD())
+	{
+		if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
+		{
+			//Dbg_Message( "Playing sound %s\n", (pSoundName) ? pSoundName : Script::FindChecksumName( checksum ));
+			Dbg_Message( "Playing sound %s with volL = %f volR = %f on voice %d", (pSoundName) ? pSoundName : Script::FindChecksumName( checksum ),
+						 p_vol->GetChannelVolume(0), p_vol->GetChannelVolume(1), whichVoice);
+		}
+	}	
+	return pVoiceInfo->uniqueID;
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* Send in the uniqueID returned from PlaySound()				  */
+/*																  */
+/******************************************************************/
+bool CSfxManager::StopSound( uint32 uniqueID )
+{
+	if( NoSoundPlease())
+		return true;
+
+	int whichVoice = GetVoiceFromID(uniqueID);
+
+	if (whichVoice >= 0)
+	{
+		// Stop the sound if it's playing (platform specific).
+		StopSoundPlease( whichVoice );
+		return true;
+	}
+
+	return false;
+}
+
+
+bool CSfxManager::SetSoundParams( uint32 uniqueID, sVolume *p_vol, float pitch )
+{
+	if( NoSoundPlease())
+		return true;
+
+	int whichVoice = GetVoiceFromID(uniqueID);
+
+	if (whichVoice >= 0)
+	{
+		WaveTableEntry *waveTableIndex = VoiceInfoTable[ whichVoice ].waveIndex;
+	
+		if( waveTableIndex == NULL )
+		{
+			Dbg_MsgAssert(0, ( "Can't find wave table entry for sound %s", Script::FindChecksumName( uniqueID ) ) );
+			return false;
+		}
+		if( !p_vol->IsSilent())
+		{
+			// Tweak the values to designer specs (from LoadSound and PlaySound).
+			TweakVolumeAndPitch( p_vol, &pitch, waveTableIndex );
+		}
+
+#		if defined( __PLAT_NGPS__ ) || defined( __PLAT_NGC__ )
+		// Adjust pitch to account for lower sampling rates.
+		pitch = PERCENT( pitch, waveTableIndex->platformWaveInfo.pitchAdjustmentForSamplingRate );
+#		endif
+
+		// And finally, set the parameters.
+		SetVoiceParameters( whichVoice, p_vol, pitch );
+
+		return true;
+	}
+
+	if( !Config::CD())
+	{
+		if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
+		{
+			Dbg_Message( "SetSoundParams: couldn't find sound %s", Script::FindChecksumName( uniqueID ));
+		}
+	}
+
+	return false;
+}
+
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+void CSfxManager::SetMainVolume( float volume )
+{
+	if( NoSoundPlease())
+		return;
+	
+	Dbg_MsgAssert( volume >= 0.0f, ( "Volume below 0%" ));
+	Dbg_MsgAssert( volume <= 100.0f, ( "Volume above 100%" ));
+	
+	if( Script::GetInteger( 0xd7bb618d /* DebugSoundFx */, Script::NO_ASSERT ))
+	{
+		Dbg_Message( "Setting gVolume to %f\n",volume );
+	}
+	  
+	gVolume	= volume;
+	volume	= volume * Config::GetMasterVolume() / 100.0f;
+	if( volume < 0.1f )
+	{
+		volume = 0.0f;
+	}	
+	SetVolumePlease( volume );
+	Pcm::SetAmbientVolume( volume );
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+float CSfxManager::GetMainVolume( void )
+{
+	return gVolume;
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+void CSfxManager::SetReverb( float reverbLevel, int reverbMode, bool instant )
+{
+	if( NoSoundPlease())
+		return;
+
+	Dbg_MsgAssert( reverbLevel >= 0.0f, ( "Reverb below 0%" ));
+	Dbg_MsgAssert( reverbLevel <= 100.0f, ( "Reverb above 100%" ));
+	SetReverbPlease( reverbLevel, reverbMode, instant );
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+bool CSfxManager::SoundIsPlaying( uint32 uniqueID, int *pWhichVoice )
+{
+	if( NoSoundPlease())
+		return 0;
+
+	int whichVoice;
+
+	// Zero is not a valid ID... quickly return.
+	if ( !uniqueID )
+		return false;
+		
+	for( whichVoice = 0; whichVoice < NUM_VOICES; whichVoice++ )
+	{
+		if ( VoiceInfoTable[ whichVoice ].uniqueID == uniqueID )
+		{
+			if( !VoiceIsOn( whichVoice ))
+			{
+				return false;
+			}
+			if( pWhichVoice )
+			{
+				*pWhichVoice = whichVoice;
+			}
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+
+int CSfxManager::GetNumSoundsPlaying()
+{
+	// This is not the most efficient way to figure this out, but it doesn't
+	// require any changes below the p-line.  We should add p-line code if
+	// it get used a lot.
+	int voicesUsed = 0;
+
+	for( int whichVoice = 0; whichVoice < NUM_VOICES; whichVoice++ )
+	{
+		if( VoiceIsOn( whichVoice ) )
+		{
+			voicesUsed++;
+		}
+	}
+
+	return voicesUsed;
+}
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+float CSfxManager::GetDropoffDist( uint32 soundChecksum )
+{
+	if( NoSoundPlease())
+		return DefaultDropoffDist;
+
+	WaveTableEntry *waveIndex = GetWaveTableIndex( soundChecksum );
+	if( waveIndex == NULL )
+	{
+		return 0;
+	}
+	float dropoffDist = waveIndex->dropoff;
+	return ( dropoffDist ? dropoffDist : DefaultDropoffDist );
+}
+
+
+
+/******************************************************************/
+/*																  */
+/*																  */
+/******************************************************************/
+bool CSfxManager::UpdateLoopingSound( uint32 soundID, sVolume *p_vol, float pitch )
+{
+	if( NoSoundPlease())
+		return true;
+
+//    uint32 start_time = Tmr::GetTimeInUSeconds();
+
+	int whichVoice;
+	for( whichVoice = 0; whichVoice < NUM_VOICES; whichVoice++ )
+	{
+		if( VoiceInfoTable[whichVoice].uniqueID == soundID )
+		{
+			if( !VoiceIsOn( whichVoice ))
+			{
+				if( !Config::CD())
+				{
+					if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
+					{
+						Dbg_Message( "Sound %s is done", Script::FindChecksumName( VoiceInfoTable[whichVoice].info.checksum ));
+					}
+				}	
+
+				return false;
+			}
+			
+			// Tweak sounds for this particular instance of PlaySound (this allows a designer to vary the pitch,
+			// for example, of a carloop sound on each car, by adding a pitch = parameter to Obj_PlaySound. This
+			// will be maintained, as will the volume, thanks to this code.
+			if( !p_vol->IsSilent())
+			{
+				float tweakVolume = VoiceInfoTable[whichVoice].info.currentVolume;
+				if( tweakVolume != 100.0f )
+				{
+					p_vol->PercentageAdjustment( tweakVolume );
+				}
+				float tweakPitch = VoiceInfoTable[ whichVoice ].info.currentPitch;
+				if( tweakPitch != 100.0f )
+				{
+					pitch = PERCENT( pitch, tweakPitch );
+				}
+			}
+			
+			WaveTableEntry *waveTableIndex = VoiceInfoTable[ whichVoice ].waveIndex;
+		
+			if( waveTableIndex == NULL )
+			{
+				Dbg_Message( "WARNING:  Positional/looping sound error." );
+				return false;
+			}
+			if( !p_vol->IsSilent())
+			{
+				// Tweak the values to designer specs (from LoadSound and PlaySound).
+				TweakVolumeAndPitch( p_vol, &pitch, waveTableIndex );
+			}
+
+#			if defined( __PLAT_NGPS__ ) || defined( __PLAT_NGC__ )
+			// Adjust pitch to account for lower sampling rates.
+			pitch = PERCENT( pitch, waveTableIndex->platformWaveInfo.pitchAdjustmentForSamplingRate );
+#			endif
+
+			// And finally, set the parameters.
+			SetVoiceParameters( whichVoice, p_vol, pitch );
+
+//            uint32 end_time = Tmr::GetTimeInUSeconds();	
+//			if ((end_time - start_time) > 250)
+//				Dbg_Message("CSfxManager::UpdateLoopingSound Frame %d Time %d", Tmr::GetVblanks(), (end_time - start_time));
+			return true;
+		}
+	}
+
+	if( !Config::CD())
+	{
+		if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
+		{
+			Dbg_Message( "UpdateSound: couldn't find sound %s", Script::FindChecksumName( VoiceInfoTable[whichVoice].info.checksum ));
+		}
+	}	
+	return false;
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* Calculates multipliers for front left, front right, rear left, */
+/* rear right and center channel speakers.						  */
+/*																  */
+/******************************************************************/
+void CSfxManager::Get5ChannelMultipliers( const Mth::Vector &sound_source, float *p_multipliers )
+{
+	float angle	= Mth::RadToDeg( atan2f( sound_source[X], -sound_source[Z] ));
+	Get5ChannelMultipliers( angle, p_multipliers );
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* Calculates multipliers for front left, front right, rear left, */
+/* rear right and center channel speakers.						  */
+/*																  */
+/******************************************************************/
+void CSfxManager::Get5ChannelMultipliers( float angle, float *p_multipliers )
+{
+	static float speakers[5][3]	= {	{ 330.00f, 240.01f, 359.99f },		// Front left max angle, min angle0, min angle1
+									{  30.00f,   0.01f,	119.99f },		// Front right
+									{ 240.00f, 120.01f, 329.99f },		// Rear left
+									{ 120.0f,   30.01f, 239.99f },		// Rear right
+									{   0.0f,  330.01f,  29.99f }};		// Center
+
+	// Ensure angle is in a suitable range.
+	angle = ( angle < 0.0f ) ? ( 360.0f + angle ) : angle;
+
+	// Go through and calculate the relative volumes for each speaker.
+	for( int spkr = 0; spkr < 5; ++spkr )
+	{
+		float amin0	= speakers[spkr][1];
+		float amin1	= speakers[spkr][2];
+		float amax	= speakers[spkr][0];
+		float mul	= 0.0f;
+
+		if( amin0 < amax )
+		{
+			// Regular test.
+			if(( angle > amin0 ) && ( angle <= amax ))
+			{
+				// Angle lies between amin0 and amax.
+				mul = ( angle - amin0 ) / ( amax - amin0 );
+			}
+			else if(( angle > amax ) && ( angle < amin1 ))
+			{
+				// Angle lies between amax and amin1.
+				mul = 1.0f - (( angle - amax ) / ( amin1 - amax ));
+			}
+		}
+		else
+		{
+			// Non regular test (center channel). Assumes center at 0.0.
+			if( angle > amin0 )
+			{
+				mul = ( angle - amin0 ) / ( 360.0f - amin0 );
+			}
+			else if( angle < amin1 )
+			{
+				mul = ( amin1 - angle ) / ( amin1 );
+			}
+		}
+						
+		// Angle is within scope of this speaker. Figure multiplier.
+		Dbg_Assert( mul <= 1.0f );
+		p_multipliers[spkr] = sinf( mul * Mth::PI * 0.5f );
+	}
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* Sets volume and pan considering all cameras and current		  */
+/* position.													  */
+/*																  */
+/******************************************************************/
+void CSfxManager::SetVolumeFromPos( sVolume *p_vol, const Mth::Vector &soundSource, float dropoffDist, EDropoffFunc dropoff_func,
+									Gfx::Camera *p_camera,  const Mth::Vector *p_dropoff_pos)
+{
+	if( NoSoundPlease())
+		return;
+
+	Dbg_Assert( p_vol );
+
+	// Set the volume to 0
+	p_vol->SetSilent();
+
+	// Return if in Silence Mode
+	if( PositionalSoundSilenceMode() )
+		return;
+
+	// Find the camera if one wasn't already supplied
+	if (!p_camera)
+	{
+		p_camera = Nx::CViewportManager::sGetClosestCamera(soundSource);
+
+		// If we still can't find a camera, return with the volume set to 0
+		if (!p_camera)
+		{
+			return;
+		}
+	}
+
+	// Find the dist to the sound
+	float dist = Mth::Distance( p_camera->GetPos(), soundSource );
+
+	// Calculate the dropoff dist
+	if (p_dropoff_pos)
+	{
+		dropoffDist = dist + Mth::Distance( *p_dropoff_pos, p_camera->GetPos());
+	}
+	else if( !dropoffDist )
+	{
+		dropoffDist = DefaultDropoffDist;
+	}
+	
+	// If we are outside the dropoff dist, we are out of here
+	if( dist >= dropoffDist )
+		return;
+
+	// Sound is within range of this camera.
+	float dropOff	= dist / dropoffDist;
+	float volume = 0.0f;
+	switch (dropoff_func)
+	{
+	case DROPOFF_FUNC_STANDARD:
+		volume	= ( 100.0f * 
+				  ((( 1.0f - ( dropOff * dropOff )) + 			// Exponential and...
+				  ( 3.0f * ( 1.0f - ( dropOff )))) / 4.0f ));	// ...linear averaged together.
+		break;
+
+	case DROPOFF_FUNC_LINEAR:
+		volume	= ( 100.0f * ( 1.0f - dropOff ) );
+		break;
+
+	case DROPOFF_FUNC_EXPONENTIAL:
+		volume	= ( 100.0f * ( ( 1.0f - dropOff ) * ( 1.0f - dropOff ) ) );
+		break;
+
+	case DROPOFF_FUNC_INV_EXPONENTIAL:
+		volume	= ( 100.0f * ( sqrtf ( 1.0f - dropOff ) ) );
+		break;
+	}
+
+	switch( p_vol->GetVolumeType() )
+	{
+		case VOLUME_TYPE_BASIC_2_CHANNEL:
+		case VOLUME_TYPE_2_CHANNEL_DOLBYII:
+			{
+				Dbg_Assert( p_camera );		// We should have returned by now
+
+				if( fabsf( volume ) > fabsf( p_vol->GetLoudestChannel()))
+				{
+					Mth::Vector sound_pos_from_camera = soundSource - p_camera->GetPos();
+					sound_pos_from_camera.Normalize();
+
+					Mth::Vector camRightVector = -p_camera->GetMatrix()[X];
+					
+					// Project the obj_pos vector onto the right vector.
+					// For some reason right and left were switched (must be left instead of right!)
+					float panVal = Mth::DotProduct( sound_pos_from_camera, camRightVector );
+
+					// +1.0f is all the way on the right, -1.0f is all the way on the left.
+					// Doing an exponential curve, so that if the object is right in the middle
+					// the right and left volume (at closest dist) is 75%. 100% volume if all
+					// the way to the right/left.
+					panVal		+= 1.0f;		// Will now be from 0 to 2 (Lmax to Rmax)..
+					panVal		/= 2.0f;		// ...now from 0 to 1.
+					float rVol	= ( 1.0f - ( panVal * panVal )) * volume;
+					panVal		= 1.0f - panVal;
+					float lVol	= ( 1.0f - ( panVal * panVal )) * volume;
+
+					if( p_vol->GetVolumeType() == VOLUME_TYPE_2_CHANNEL_DOLBYII )
+					{
+						bool bSetPhase = false;
+
+						if( lVol > p_vol->GetChannelVolume( 0 ))
+						{
+							p_vol->SetChannelVolume( 0, lVol );
+							bSetPhase = true;
+						}
+						if( rVol > p_vol->GetChannelVolume( 1 ))
+						{
+							p_vol->SetChannelVolume( 1, rVol );
+							bSetPhase = true;
+						}
+						if( bSetPhase )
+						{
+							// If sound is behind the camera, set volume negative and it will sound out of phase.
+							Mth::Vector camAtVector = -p_camera->GetMatrix()[Z];
+
+							float behindCamera = Mth::DotProduct( sound_pos_from_camera, camAtVector );
+							if ( behindCamera < 0.0f )
+							{
+								// Just one channel needs to be reverse phased to get the effect.
+								p_vol->SetChannelVolume( 0, p_vol->GetChannelVolume( 0 ) * -1.0f );
+							}
+						}
+					}
+					else
+					{
+						if( lVol > p_vol->GetChannelVolume( 0 ))
+						{
+							p_vol->SetChannelVolume( 0, lVol );
+						}
+						if( rVol > p_vol->GetChannelVolume( 1 ))
+						{
+							p_vol->SetChannelVolume( 1, rVol );
+						}
+					}
+				}
+			}
+			break;
+	
+		case VOLUME_TYPE_5_CHANNEL_DOLBY5_1:
+			{
+				Dbg_Assert( p_camera );		// We should have returned by now
+
+				if( fabsf( volume ) > p_vol->GetLoudestChannel())
+				{
+					// Transform the sound source into the camera's coordinate space.
+					Mth::Matrix inv_view	= p_camera->GetMatrix();
+					inv_view.Invert();
+
+					Mth::Vector sound_src	= soundSource - p_camera->GetPos();
+					Mth::Vector sound_pos	= inv_view.Transform( sound_src );
+					sound_pos.Normalize();
+
+					float channel_multipliers[5];
+                    Get5ChannelMultipliers( sound_pos, &channel_multipliers[0] );
+
+					// Go through and calculate the relative volumes for each speaker.
+					for( int spkr = 0; spkr < 5; ++spkr )
+					{
+						float mul = channel_multipliers[spkr];
+						if( mul > 0.0f )
+						{
+							// Angle is within scope of this speaker. Figure multiplier.
+							Dbg_Assert( mul <= 1.0f );
+							float vol = volume * mul;
+							if( vol > p_vol->GetChannelVolume( spkr ))
+							{
+								p_vol->SetChannelVolume( spkr, vol );
+							}
+						}
+					}
+
+					// Final step is to normalize the channels out and reset to volume level.
+					if( !p_vol->IsSilent())
+					{
+						float norm = 0.0;
+						for( int spkr = 0; spkr < 5; ++spkr )
+						{
+							norm += p_vol->GetChannelVolume( spkr ) * p_vol->GetChannelVolume( spkr );
+						}
+						norm = sqrtf( norm );
+						for( int spkr = 0; spkr < 5; ++spkr )
+						{
+							p_vol->SetChannelVolume( spkr, ( volume * p_vol->GetChannelVolume( spkr )) / norm );
+						}
+					}
+
+				}
+			}
+			break;
+		
+		default:
+			break;
+	}
+}
+
+
+
+/* Real speed of sound ~= 760 mph.								  */
+/* Lower that for dramatic effect.								  */
+#define	SPEED_OF_OUR_SOUND	MPH_TO_INCHES_PER_SECOND( 700.0f )
+
+/******************************************************************/
+/*																  */
+/* This should happen after all other pitch adjustments.		  */
+/*																  */
+/******************************************************************/
+void CSfxManager::AdjustPitchForDoppler( float *pitch, const Mth::Vector ¤tPos, const Mth::Vector &oldPos, float elapsedTime, Gfx::Camera *pCam )
+{
+#	ifndef __PLAT_NGC__
+	const float cutoff_dist =  360.0f;	// in inches
+#	endif		// __PLAT_NGC__
+
+	if( NoSoundPlease())
+		return;
+
+#	ifndef __PLAT_NGC__
+	if ( !pCam )
+	{
+		pCam = Nx::CViewportManager::sGetClosestCamera( currentPos );
+		if( !pCam )
+			return;
+	}
+
+	float prevDist	= Mth::Distance( pCam->m_old_pos, oldPos );
+	float deltaDist	= Mth::Distance( pCam->GetPos(), currentPos ) - prevDist;
+
+	if(( fabsf( deltaDist ) * Tmr::FrameRatio()) > Tmr::FrameRatio() * cutoff_dist )
+	{
+		// TT2794: Large movements in the camera are causing pitch glitches, so
+		// it would be better to not change the pitch at all than to try to find
+		// a "good" change.  Garrett
+		return;
+
+		// Clip so there aren't high pitched schreeches when camera is moved in net games and such:
+		if ( deltaDist < 0.0f )
+		{
+			deltaDist = -( Tmr::FrameRatio( ) * cutoff_dist );
+		}
+		else
+		{
+			deltaDist = Tmr::FrameRatio( ) * cutoff_dist;
+		}
+	}
+	float deltaPitch = SPEED_OF_OUR_SOUND * elapsedTime;
+	Dbg_MsgAssert( deltaPitch,( "Divide by zero." ));
+	deltaPitch = (( *pitch ) * deltaDist ) / deltaPitch;
+	*pitch -= deltaPitch;
+#	endif // __PLAT_NGC__
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* This should happen after all other pitch adjustments.		  */
+/*																  */
+/******************************************************************/
+ObjectSoundInfo *CSfxManager::GetObjectSoundProperties( Obj::CSoundComponent *pObj, uint32 checksum )
+{
+	PositionalSoundEntry *pEntry = GpPositionalSounds;
+	while( pEntry )
+	{
+		if(( pEntry->pObj == pObj ) && ( pEntry->checksum == checksum ))
+		{
+			// Change the current volume, pitch.
+			int voiceIndex;
+			if( !SoundIsPlaying( pEntry->uniqueID, &voiceIndex ))
+			{
+				return NULL;
+			}
+			return &VoiceInfoTable[ voiceIndex ].info;
+		}
+		pEntry = pEntry->pNext;
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*																  */
+/* Plays sound, considering camera position(s) from sound source. */
+/*																  */
+/******************************************************************/
+uint32 CSfxManager::PlaySoundWithPos( uint32 soundChecksum, SoundUpdateInfo *pUpdateInfo, Obj::CSoundComponent *pObj, bool noPosUpdate )
+{
+	if( NoSoundPlease())
+		return 0;
+
+	WaveTableEntry *waveTableIndex = GetWaveTableIndex( soundChecksum );
+	if( waveTableIndex == NULL )
+	{
+		Dbg_MsgAssert( 0, ( "Asking to play sound that hasn't been loaded %s.", Script::FindChecksumName( soundChecksum )));
+		return 0;
+	}
+	
+	Dbg_MsgAssert( pObj,( "pObj should be non-NULL" ));
+
+	sVolume vol;
+
+	// Garrett: Shouldn't we be calling this here, too?  Or is it not necessary?
+//	if( pVoiceInfo->waveIndex->flags & SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER )
+//	{
+//		// Even if the object didn't move, the camera could have!
+//		AdjustPitchForDoppler( &pitch, pObj->m_pos, pObj->m_old_pos, Tmr::FrameLength(), pCamera );
+//	}
+
+	Gfx::Camera *pCamera = Nx::CViewportManager::sGetClosestCamera( pObj->GetPosition() );
+	if( pCamera )
+	{
+		Mth::Vector dropoff_pos;
+		Mth::Vector *p_dropoff_pos = NULL;
+		if (pObj->GetClosestDropoffPos(pCamera, dropoff_pos))
+		{
+			p_dropoff_pos = &dropoff_pos;
+		}
+
+		SetVolumeFromPos( &vol, pObj->GetClosestEmitterPos(pCamera), pUpdateInfo->dropoffDist ? pUpdateInfo->dropoffDist : waveTableIndex->dropoff,
+						  pUpdateInfo->dropoffFunction, pCamera, p_dropoff_pos );
+	}
+	else
+	{
+		vol.SetSilent();
+	}
+	
+	Dbg_MsgAssert(!(noPosUpdate && ( waveTableIndex->flags & SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER )), ("Trying to play doppler sound with NoPosUpdate"));
+	if( vol.IsSilent() && noPosUpdate )
+	{
+		return 0;
+	}
+	
+	vol.PercentageAdjustment( pUpdateInfo->volume );
+	
+	uint32 uniqueID = PlaySound( soundChecksum, &vol, pUpdateInfo->pitch, 0, pUpdateInfo );
+	
+	if( !Config::CD())
+	{
+		if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
+		{
+			Dbg_Message( "Starting sound %s with volL = %f volR = %f", Script::FindChecksumName( soundChecksum ), vol.GetChannelVolume(0), vol.GetChannelVolume(1));
+		}
+	}	
+
+	// Should be able to have looping sounds, as long as the ones that are manually updated call PlaySound
+	// instead of PlaySoundWithPos.
+	if(!noPosUpdate)
+	{
+		AddPositionalSoundToUpdateList( uniqueID, soundChecksum, pObj );
+
+		if( !Config::CD())
+		{
+			if( Script::GetInteger( 0x6d2e270e /* DebugSoundFxUpdate */, Script::NO_ASSERT ))
+			{
+				Dbg_Message( "Added sound %s to update list", Script::FindChecksumName( soundChecksum ));
+			}
+		}	
+	}
+	return uniqueID;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSfxManager::Update( void )
+{
+	PerFrameUpdate();
+}
+
+} // namespace Sfx
+
diff --git a/Code/Gel/SoundFX/soundfx.h b/Code/Gel/SoundFX/soundfx.h
new file mode 100644
index 0000000..4dd21bc
--- /dev/null
+++ b/Code/Gel/SoundFX/soundfx.h
@@ -0,0 +1,325 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			soundfx													**
+**																			**
+**	File name:		gel/soundfx/soundfx.h  									**
+**																			**
+**	Created: 		1/4/01	-	mjd											**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_SOUNDFX_H
+#define __GEL_SOUNDFX_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGPS__
+#include 
+#elif defined( __PLAT_XBOX__ )
+#include 
+#elif defined( __PLAT_NGC__ )
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// change the following line to compile out soundfx from the game:
+#ifdef __PLAT_XBOX__
+#define NO_SOUND_PLEASE	0
+#else
+#define NO_SOUND_PLEASE	0
+#endif
+
+// if we need more, NO PROBLEMA!  Change this:
+#if defined( __PLAT_NGPS__ )
+#define WAVE_TABLE_MAX_ENTRIES			256	// non-permanent sounds
+#define PERM_WAVE_TABLE_MAX_ENTRIES		256	// permanent sounds
+#define MAX_POSITIONAL_SOUNDS			128
+#elif defined( __PLAT_XBOX__ )
+#define WAVE_TABLE_MAX_ENTRIES			256	// non-permanent sounds
+#define PERM_WAVE_TABLE_MAX_ENTRIES		256	// permanent sounds
+#define MAX_POSITIONAL_SOUNDS			128
+#else
+#define WAVE_TABLE_MAX_ENTRIES			256	// non-permanent sounds
+#define PERM_WAVE_TABLE_MAX_ENTRIES		256	// permanent sounds
+#define MAX_POSITIONAL_SOUNDS			128
+#endif
+
+#define DIST_FROM_DROPOFF_AT_WHICH_TO_START_SOUND	FEET_TO_INCHES( 20.0f )
+#define DFDAWTSS									DIST_FROM_DROPOFF_AT_WHICH_TO_START_SOUND
+
+// This buffer prevents rapid switching on/off of voices that are right on the border.
+#define SOUND_DIST_BUFFER							FEET_TO_INCHES( 20.0f )
+#define DIST_FROM_DROPOFF_AT_WHICH_TO_STOP_SOUND	( SOUND_DIST_BUFFER + DFDAWTSS )
+
+#define DEFAULT_DROPOFF_DIST						FEET_TO_INCHES( 85.0f )
+
+// When a voice is set to negative volume on some platforms, it puts the sound 'out of phase' to sound like it's behind you...
+#if defined( __PLAT_NGPS__ ) || defined( __PLAT_XBOX__ )
+#define PLATFORM_SUPPORTS_VOLUME_PHASING			1
+#endif
+
+#define MAX_VOL_ALLOWED								1000 // 1000 percent (10 times)
+
+namespace Script
+{
+	class CStruct;
+}
+
+namespace Gfx
+{
+	class Camera;
+}
+
+namespace Obj
+{
+    class CSoundComponent;
+    class CEmitterObject;
+}
+
+enum
+{
+	SFX_FLAG_LOAD_PERM						= ( 1 << 0 ),
+//	SFX_FLAG_POSITIONAL_UPDATE				= ( 1 << 1 ),
+	SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER = ( 1 << 2 ),
+	SFX_FLAG_NO_REVERB						= ( 1 << 4 ),
+};
+
+
+// Function used to calculate volume dropoff
+enum EDropoffFunc
+{
+	DROPOFF_FUNC_STANDARD,				// Combination of linear and exponential
+	DROPOFF_FUNC_LINEAR,
+	DROPOFF_FUNC_EXPONENTIAL,
+	DROPOFF_FUNC_INV_EXPONENTIAL,
+};
+
+
+struct ObjectSoundInfo
+{
+	// Needed for long sounds played on a per-object basis and positionally updated:
+	uint32	checksum;
+	float	dropoffDist;
+	float	currentPitch;
+	float	currentVolume;
+	float	origPitch;
+	float	origVolume;
+	float	targetPitch;
+	float	targetVolume;
+	float	deltaPitch;
+	float	deltaVolume;
+	EDropoffFunc dropoffFunction;
+
+	// Used to check to see if the sound should be turned off or on, if too far/close to the camera:
+	Tmr::Time timeForNextDistCheck;
+	
+	// Used by the script debugger code to fill in a structure
+	// for transmitting to the monitor.exe utility running on the PC.
+	void GetDebugInfo(Script::CStruct *p_info);
+};
+
+
+#ifdef __PLAT_WN32__
+struct PlatformWaveInfo
+{
+	// Stub to compile under win32
+};
+#endif
+
+namespace Sfx
+{
+
+struct WaveTableEntry
+{
+	uint32		checksum;	// name of the sound...
+	uint32		flags;		// see SFX_FLAG_ 's in soundfx.h
+
+	// All the shit designers want to be able to set in a script file:
+	float		pitch;		// tweakable by designers in script file
+	float		volume;		// tweakable by designers in script file
+	float		dropoff;	// tweakable by designers in script file
+
+	// Platform dependent info:
+	PlatformWaveInfo platformWaveInfo;
+};
+
+
+struct VoiceInfo
+{
+	uint32				uniqueID;
+	uint32				controlID;
+	WaveTableEntry		*waveIndex;
+	ObjectSoundInfo		info;
+};
+
+
+enum EVolumeType
+{
+	VOLUME_TYPE_BASIC_2_CHANNEL,		// Basic 2 channel left/right sound with no volume phasing.
+	VOLUME_TYPE_2_CHANNEL_DOLBYII,		// As above, but with DolbyII volume phasing.
+	VOLUME_TYPE_5_CHANNEL_DOLBY5_1		// Five discreet volumes, as per Dolby 5.1.
+};
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+struct sVolume
+{
+	enum
+	{
+		MAX_CHANNELS = 5
+	};
+	float				m_channels[MAX_CHANNELS];	// Can store up to five discreet channel volumes.
+	EVolumeType			m_volume_type;				// The type of the volume.
+
+						sVolume( void );
+						sVolume( EVolumeType type );
+						sVolume( const sVolume& rhs );
+						~sVolume( void );
+	bool				operator== ( const sVolume& rhs );
+	bool				operator!= ( const sVolume& rhs )			{ return !(*this == rhs); }
+	bool				IsSilent( void );
+	void				SetSilent( void );
+	EVolumeType			GetVolumeType( void )						{ return m_volume_type; }
+	float				GetChannelVolume( uint32 chan )				{ return m_channels[chan]; }
+	void				SetChannelVolume( uint32 chan, float vol )	{ m_channels[chan] = vol; }
+	void				PercentageAdjustment( float percentage );
+	float				GetLoudestChannel( void );
+};
+
+struct PositionalSoundEntry;
+
+struct SoundUpdateInfo
+{
+	// So each instance of Obj_PlaySound maintains the values sent in by the designer:
+	float pitch; 
+	float volume;
+	float dropoffDist;
+	EDropoffFunc dropoffFunction;
+};
+
+
+class CSfxManager : public Spt::Class
+{
+	DeclareSingletonClass( CSfxManager );
+
+public:
+					CSfxManager( void );
+				    ~CSfxManager( void );
+
+	int				MemAvailable( void );
+
+	bool			LoadSound( const char *sfxName,  int flags = 0, float dropoff = 0.0f, float pitch = 100.0f, float volume = 100.0f );
+	uint32			PlaySound( uint32 checksum, sVolume *p_vol, float pitch = 100.0f, uint32 controlID = 0, SoundUpdateInfo *pUpdateInfo = NULL, const char *pSoundName = NULL );
+	uint32			PlaySoundWithPos( uint32 soundChecksum, SoundUpdateInfo *pUpdateInfo, Obj::CSoundComponent *pObj, bool noPosUpdate );
+	ObjectSoundInfo	*GetObjectSoundProperties( Obj::CSoundComponent *pObj, uint32 checksum );
+
+	void			CleanUp( void );
+	void			StopAllSounds( void );
+	bool			StopSound( uint32 uniqueID );
+	bool			SetSoundParams( uint32 uniqueID, sVolume *p_vol, float pitch = 100.0f );	// For non-object sounds
+	void			StopObjectSound( Obj::CSoundComponent *pObj, uint32 checksum );
+	void			PauseSounds( void );
+	void			SetReverb( float reverbLevel, int reverbMode = 0, bool instant = false );
+	bool			SoundIsPlaying( uint32 uniqueID, int *pWhichVoice = NULL );
+	int				GetNumSoundsPlaying();
+	void			SetMainVolume( float volume );
+	void			SetDefaultDropoffDist( float dist );
+	void			SetVolumeFromPos( sVolume *p_vol, const Mth::Vector &soundSource, float dropoffDist,
+									  EDropoffFunc dropoff_func = DROPOFF_FUNC_STANDARD,
+									  Gfx::Camera* pCamera = NULL, const Mth::Vector *p_dropoff_pos = NULL );
+	void			AdjustPitchForDoppler( float *pitch, const Mth::Vector ¤tPos, const Mth::Vector &oldPos, float elapsedTime, Gfx::Camera* pCam = NULL );
+	void			Get5ChannelMultipliers( const Mth::Vector &sound_source, float *p_multipliers );
+	void			Get5ChannelMultipliers( float angle, float *p_multipliers );
+
+	// A per-frame function. Does nothing on PS2, on Xbox checks for stop notification events.
+	void			Update( void );
+	void			UpdatePositionalSounds( void );
+	
+	// If, for some reason, the sound has stopped playing, this returns false, true otherwise.
+	bool			UpdateLoopingSound( uint32 soundID, sVolume *p_vol, float pitch = 100.0f );
+
+	float			GetMainVolume( void );
+	float			GetDropoffDist( uint32 soundChecksum );
+
+	// -1 on failure (sound not loaded)
+	WaveTableEntry*	GetWaveTableIndex( uint32 checksum );
+
+	void			ObjectBeingRemoved( Obj::CSoundComponent *pObj );
+		
+	void			SetDefaultVolumeType( EVolumeType type )	{ m_default_volume_type = type; }
+	EVolumeType		GetDefaultVolumeType( void )				{ return m_default_volume_type; }
+
+	bool			PositionalSoundSilenceMode();		// Returns true if in mode where environment sounds should be a 0 volume
+	
+private:
+
+	EVolumeType		m_default_volume_type;
+
+	bool			AdjustObjectSound( Obj::CSoundComponent *pObj, VoiceInfo *pVoiceInfo, Tmr::Time gameTime );
+	void			TweakVolumeAndPitch( sVolume* p_vol, float *pitch, WaveTableEntry* waveTableIndex );
+	void			RemovePositionalSoundFromList( PositionalSoundEntry *pEntry, bool stopIfPlaying );
+	void			AddPositionalSoundToUpdateList( uint32 uniqueID, uint32 soundChecksum, Obj::CSoundComponent *pObj = NULL );
+
+	// UniqueID functions
+	bool			IDAvailable(uint32 id);
+	int				GetVoiceFromID(uint32 id);
+	uint32			GenerateUniqueID(uint32 id);
+};
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+extern int				NumWavesInTable;
+extern int				NumWavesInPermTable;
+extern int				NumPositionalSounds;
+extern WaveTableEntry	WaveTable[PERM_WAVE_TABLE_MAX_ENTRIES + WAVE_TABLE_MAX_ENTRIES];
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+bool			NoSoundPlease();
+EDropoffFunc	GetDropoffFunctionFromChecksum(uint32 checksum);
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Sfx
+
+#endif	// __GEL_SOUNDFX_H
+
+
diff --git a/Code/Gel/inpman.h b/Code/Gel/inpman.h
new file mode 100644
index 0000000..d2974b2
--- /dev/null
+++ b/Code/Gel/inpman.h
@@ -0,0 +1,531 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Input  (Inp)											**
+**																			**
+**	File name:		gel/inpman.h											**
+**																			**
+**	Created: 		05/26/2000	-	spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_INPMAN_H
+#define __GEL_INPMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+
+#include 
+#include 
+ 
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+#define PAD_NONE 0
+#define PAD_U 1
+#define PAD_D 2
+#define PAD_L 3
+#define PAD_R 4
+#define PAD_UL 5
+#define PAD_UR 6
+#define PAD_DL 7
+#define PAD_DR 8
+#define PAD_CIRCLE 9
+#define PAD_SQUARE 10
+#define PAD_X 11
+#define PAD_TRIANGLE 12
+#define PAD_L1 13
+#define PAD_L2 14
+#define PAD_L3 15
+#define PAD_R1 16
+#define PAD_R2 17
+#define PAD_R3 18
+#define PAD_BLACK 19
+#define PAD_WHITE 20
+#define PAD_Z 21
+#define PAD_NUMBUTTONS 22
+
+namespace Inp
+{
+
+
+
+static const int vANALOGUE_TOL      = 50;			// Mick, changed back from 32, as our controllers were wandering...
+static const int vANALOGUE_CENTER   = 128;
+
+
+extern uint32	gDebugButtons[];
+extern uint32	gDebugBreaks[];
+extern uint32	gDebugMakes[];
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+int GetButtonIndex(uint32 Checksum);
+uint32 GetButtonChecksum( int whichButton );
+
+class  Data  : public Spt::Class
+{   
+	
+
+public:
+
+        friend class Server;
+
+						Data( void );
+
+#define vMAX_ANALOG_VALUE   255
+
+    enum DigitalButtonIndex
+	{      
+        vD_L2,
+        vD_R2,
+        vD_L1,
+        vD_R1,
+        vD_TRIANGLE,
+        vD_CIRCLE,
+        vD_X,
+        vD_SQUARE,
+        vD_SELECT,
+        vD_L3,
+        vD_R3,
+        vD_START,
+        vD_UP,
+        vD_RIGHT,
+        vD_DOWN,
+		vD_LEFT,
+		vD_BLACK,	// Only supported on XBox
+		vD_WHITE,	// Only supported on XBox
+		vD_Z,		// Only supported on NGC
+        vMAX_DIGITAL_EVENTS
+    };
+       
+    enum AnalogButtonIndex
+    {
+        vA_RIGHT_X,         // Right analog controller stick
+		vA_RIGHT_Y,
+		vA_LEFT_X,          // Left analog controller stick
+		vA_LEFT_Y,
+        vA_RIGHT,
+        vA_LEFT,
+        vA_UP,
+        vA_DOWN,
+        vA_TRIANGLE,
+        vA_CIRCLE,
+        vA_X,
+        vA_SQUARE,
+        vA_L1,
+        vA_R1,
+        vA_L2,
+        vA_R2,
+        vA_L3,
+        vA_R3,
+		// (Mick) Added the following to stop the analog values before they are clamped
+		// these values only get clamped if BOTH X and Y are in the middle
+		// the above values clamp X and Y independently
+		// leading to dead zones when rotating the stick round in a circle
+		// a significant loss in accuracy
+        vA_RIGHT_X_UNCLAMPED,         // Right analog controller stick
+		vA_RIGHT_Y_UNCLAMPED,
+		vA_LEFT_X_UNCLAMPED,          // Left analog controller stick
+		vA_LEFT_Y_UNCLAMPED,
+		
+		vA_BLACK,					// Only supported on XBox
+		vA_WHITE,					// Only supported on XBox
+		
+		vA_Z,						// Only supported on XBox
+
+//		vA_SELECT,		
+					   
+		vMAX_ANALOG_EVENTS
+	};
+
+	enum DigitalButtonMask
+	{
+        // These map directly to digital input in m_Buttons, m_Prev and m_New
+        mD_L2           = nBit( vD_L2 ),
+        mD_R2           = nBit( vD_R2 ),
+        mD_L1           = nBit( vD_L1 ),
+        mD_R1           = nBit( vD_R1 ),
+        mD_TRIANGLE     = nBit( vD_TRIANGLE ),
+		mD_CIRCLE       = nBit( vD_CIRCLE ),
+        mD_X            = nBit( vD_X ),
+        mD_SQUARE       = nBit( vD_SQUARE ),
+        mD_SELECT       = nBit( vD_SELECT ),
+        mD_L3           = nBit( vD_L3 ),
+        mD_R3           = nBit( vD_R3 ),
+        mD_START        = nBit( vD_START ),
+        mD_UP           = nBit( vD_UP ),
+        mD_RIGHT        = nBit( vD_RIGHT ),
+        mD_DOWN         = nBit( vD_DOWN ),
+        mD_LEFT         = nBit( vD_LEFT ),
+		mD_BLACK		= nBit( vD_BLACK ),
+		mD_WHITE		= nBit( vD_WHITE ),
+		mD_Z			= nBit( vD_Z ),
+        mD_ALL          = 0xffffffff
+	};
+
+    enum AnalogButtonMask
+    {    
+        // These map indirectly to analog input in m_Events
+		mA_RIGHT_X      = nBit( vA_RIGHT_X ),         // Right analog controller stick
+		mA_RIGHT_Y      = nBit( vA_RIGHT_Y ),
+		mA_LEFT_X       = nBit( vA_LEFT_X ),          // Left analog controller stick
+		mA_LEFT_Y       = nBit( vA_LEFT_Y ),
+        mA_RIGHT        = nBit( vA_RIGHT ),
+        mA_LEFT         = nBit( vA_LEFT ),
+        mA_UP           = nBit( vA_UP ),
+        mA_DOWN         = nBit( vA_DOWN ),
+        mA_TRIANGLE     = nBit( vA_TRIANGLE ),
+        mA_CIRCLE       = nBit( vA_CIRCLE ),
+        mA_X            = nBit( vA_X ),
+        mA_SQUARE       = nBit( vA_SQUARE ),
+        mA_L1           = nBit( vA_L1 ),
+        mA_R1           = nBit( vA_R1 ),
+        mA_L2           = nBit( vA_L2 ),
+        mA_R2           = nBit( vA_R2 ),
+        mA_L3           = nBit( vA_L3 ),
+        mA_R3           = nBit( vA_R3 ),
+        mA_BLACK        = nBit( vA_BLACK ),
+        mA_WHITE        = nBit( vA_WHITE ),
+        mA_Z	        = nBit( vA_Z ),
+//		mA_SELECT		= nBit( vA_SELECT ),
+        mA_ALL          = 0xffffffff
+	};
+	
+
+	void    MaskDigitalInput( int button_mask );
+    void    MaskAnalogInput( int button_mask );
+    void    ConvertDigitalToAnalog( void );
+	void	OverrideAnalogPadWithStick( void );
+
+	uint8   m_Event[vMAX_ANALOG_EVENTS];	// Analog info
+	uint    m_Buttons;              // Current accessible button info (digital)   
+    uint    m_Makes;                // Depressed this frame
+    uint    m_Breaks;               // Released this frame
+
+private:
+
+	uint    m_new;                  // Edges (digital)
+	uint    m_cur;              	// Current INTERNAL button info (digital)
+	uint    m_prev;                 // last frame's trig
+	
+    void    handle_analog_tolerance( void );
+};
+
+class  RecordedData  : public Spt::Class
+{
+	
+	
+	friend class Server;
+
+private:	
+	sint8	m_valid;								// Was data valid on this frame?
+	uint8   m_event[Data::vMAX_ANALOG_EVENTS];		// Analog info
+	uint    m_cur;              					// Current INTERNAL button info (digital)     
+	
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  BaseHandler : public  Tsk::BaseTask 
+{
+	
+
+    friend class Manager;
+
+public:
+	Data*           m_Input;          // controller input  
+    SIO::Device     *m_Device;        // the device from which this data was obtained
+	int				m_Index;
+
+protected:
+                    BaseHandler ( int index, Tsk::BaseTask::Node::Priority pri )
+					: Tsk::BaseTask( pri ), m_Index ( index ) 
+					{
+						Dbg_Printf( "Creating BaseHandler with Pri %d\n", pri );
+					}
+                    
+
+    virtual         ~BaseHandler ( void ) {}
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+nTemplateSubClass( _T, Handler, BaseHandler )
+{
+	
+
+public:
+
+	typedef void	(Code)( const Handler< _T >& );
+
+					Handler( int index, Code* const code, _T& data, 
+                             Lst::Node< Tsk::BaseTask >::Priority pri = Lst::Node< Tsk::BaseTask >::vNORMAL_PRIORITY );
+		
+    virtual         ~Handler ( void );
+
+	virtual void	vCall( void ) const;
+	virtual void *	GetCode(void) const;
+	_T&				GetData( void ) const;
+
+private :
+
+	Code* const		code;			// tasks entry point
+	_T&				data;			// task defined data                    
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/                                                                   
+                                                                   
+//class  Server  : public Spt::Class // note - need compiler fix for delete[] bug!!!
+class Server
+{
+//	 
+
+	friend class Manager;
+
+public:
+				Server();
+				
+	void		RecordInput( RecordedData *data_buffer );
+	void		PlaybackInput( RecordedData *recorded_input );
+	
+private:
+
+    void        service_handlers( void );
+
+	SIO::Device                 *m_device;
+	Tsk::Stack	                m_handler_stack;
+	Data                        m_data;
+	
+	RecordedData				*m_data_in;
+	void						*m_data_in_end;
+	RecordedData				*m_data_out;
+	void						*m_data_out_end;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Manager  : public Spt::Class
+{
+	                    
+
+public :
+
+	void                        AddHandler( BaseHandler &handler );
+	void						ReassignHandler( BaseHandler &handler, int new_index );
+	void                        AddPushHandler( BaseHandler &handler );
+	Tsk::BaseTask&				GetProcessHandlersTask ( void ) const;
+	
+	void						RecordInput( int index, RecordedData *data_buffer, int byte_length );
+	void						PlaybackInput( int index, RecordedData *recorded_input, int byte_length );
+	void						PushInputLogicTasks( void );
+	void						PopInputLogicTasks( void );
+	bool						ActuatorEnabled( int i );
+	
+	void						DisableActuators( void );
+	void						DisableActuator( int i );
+	
+	void						EnableActuators( void );
+	void						EnableActuator( int i );
+	void						ResetActuators( void );
+
+	void						SetAnalogStickOverride( bool should_override_pad );
+	bool						ShouldAnalogStickOverride( void );
+
+private :
+								~Manager ( void );
+								Manager ( void );
+
+	static Tsk::Task< Manager >::Code	process_handlers;
+								
+	Tsk::Task< Manager >*		m_process_handlers_task;
+	Server                      m_server[SIO::vMAX_DEVICES];
+	bool						m_override_pad_with_stick;
+	
+	DeclareSingletonClass( Manager );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline	Tsk::BaseTask&	Manager::GetProcessHandlersTask ( void ) const
+{
+	
+	
+	Dbg_AssertType ( m_process_handlers_task, Tsk::BaseTask );
+
+	return	*m_process_handlers_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void Manager::AddHandler ( BaseHandler &handler )
+{
+	
+
+    Dbg_MsgAssert( handler.m_Index < SIO::vMAX_DEVICES,( "Invalid controller index" ));
+	
+    m_server[handler.m_Index].m_handler_stack.AddTask( handler );
+    handler.m_Input = &m_server[handler.m_Index].m_data;
+    handler.m_Device = m_server[handler.m_Index].m_device;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void Manager::ReassignHandler( BaseHandler &handler, int new_index )
+{
+	Dbg_MsgAssert( new_index < SIO::vMAX_DEVICES,( "Invalid controller index" ));
+
+	handler.Remove();
+	m_server[new_index].m_handler_stack.AddTask( handler );
+    handler.m_Input = &m_server[new_index].m_data;
+    handler.m_Device = m_server[new_index].m_device;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void Manager::AddPushHandler ( BaseHandler &handler )
+{
+	
+
+	Dbg_MsgAssert( handler.m_Index < SIO::vMAX_DEVICES,( "Invalid controller index" ));
+	
+    m_server[handler.m_Index].m_handler_stack.AddPushTask( handler );
+    handler.m_Input = &m_server[handler.m_Index].m_data;
+    handler.m_Device = m_server[handler.m_Index].m_device;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline 
+Handler< _T >::Handler( int index, Code* const _code, _T& _data, Tsk::BaseTask::Node::Priority pri )
+: BaseHandler( index, pri ), code( _code ), data( _data ) 
+{
+    
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline 
+Handler< _T >::~Handler( void )
+{
+    
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline 
+void		Handler< _T >::vCall( void ) const
+{
+	
+	
+	Dbg_AssertPtr( code );
+	code( *this );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline 
+_T&		Handler< _T >::GetData( void ) const
+{
+	
+	
+	Dbg_AssertType( &data, _T );
+	return data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline 
+void *		Handler< _T >::GetCode( void ) const
+{
+	
+
+	return NULL;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Inp
+
+#endif	// __GEL_INPMAN_H
diff --git a/Code/Gel/mainloop.h b/Code/Gel/mainloop.h
new file mode 100644
index 0000000..9b130f6
--- /dev/null
+++ b/Code/Gel/mainloop.h
@@ -0,0 +1,411 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Main Loop Module (ML)									**
+**																			**
+**	File name:		gel/mainloop.h											**
+**																			**
+**	Created: 		05/27/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_MAINLOOP_H
+#define __GEL_MAINLOOP_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+   
+namespace Mlp
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  Manager  : public Spt::Class
+{
+	
+
+public :
+
+	enum ELogicMask
+	{
+		mNONE			= 0,
+		mSKATE_MOD 		= (1<<1), // skate module
+		mGAME_OBJECTS	= (1<<2), // game objects
+		mGFX_MANAGER	= (1<<3), // gfx manager
+	};
+
+	void				RegisterRenderStartHook ( Tsk::BaseHook* start_hook );
+	void				RegisterRenderEndHook ( Tsk::BaseHook* end_hook );
+
+	Tsk::BaseHook*		GetRenderStartHook( void );
+	Tsk::BaseHook*		GetRenderEndHook( void );
+	
+	void				MainLoop ( void );
+	void				QuitLoop ( void );
+	void				DoGameLogic( void );
+
+	void				PauseDisplayTasks( bool pause = true );
+	void				RemoveAllDisplayTasks ( void );
+	void				RemoveAllLogicTasks ( void );
+	void				RemoveAllSystemTasks ( void );
+	
+	void				PushLogicTasks ( void );
+	void				PushDisplayTasks ( void );
+	void				PushSystemTasks ( void );
+	
+	void				PopLogicTasks ( void );
+	void				PopDisplayTasks ( void );
+	void				PopSystemTasks ( void );
+
+	void				AddLogicTask ( Tsk::BaseTask& task );
+	void				AddDisplayTask ( Tsk::BaseTask& task );
+	void				AddSystemTask ( Tsk::BaseTask& task );
+
+	void				AddLogicPushTask ( Tsk::BaseTask& task );
+	void				AddDisplayPushTask ( Tsk::BaseTask& task );
+	void				AddSystemPushTask ( Tsk::BaseTask& task );
+
+	void				ProfileTasks(int n=1);
+	void				SetLogicMask ( uint mask );
+
+	bool				IsProfiling(){return currently_profiling;}
+	
+private :
+						Manager ( void );
+						~Manager ( void );
+
+	void				game_logic ( void );
+	void				render_frame ( void );
+	void				service_system ( void );
+
+	bool				done;
+	Tsk::Stack			logic_task_stack;
+	Tsk::Stack			display_task_stack;
+	Tsk::Stack			system_task_stack;
+	Tsk::BaseHook*		start_render_hook;
+	Tsk::BaseHook*		end_render_hook;
+
+	bool				display_tasks_paused;
+	
+	int				trigger_profiling;
+	int				currently_profiling;
+	
+	DeclareSingletonClass( Manager );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**									Macros									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline	void		Manager::RegisterRenderStartHook ( Tsk::BaseHook* start_hook )
+{
+   	
+
+    start_render_hook = start_hook;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::RegisterRenderEndHook ( Tsk::BaseHook* end_hook )
+{
+   	
+	
+	end_render_hook = end_hook;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Tsk::BaseHook*	Manager::GetRenderStartHook( void )
+{
+	
+
+	return start_render_hook;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Tsk::BaseHook*	Manager::GetRenderEndHook( void )
+{
+	
+
+	return end_render_hook;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::AddLogicTask ( Tsk::BaseTask& task )
+{
+   	
+	
+	Dbg_AssertType ( &task, Tsk::BaseTask );
+
+	logic_task_stack.AddTask ( task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::SetLogicMask ( uint mask )
+{
+   	
+
+	logic_task_stack.SetMask ( (uint) mask );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::AddDisplayTask ( Tsk::BaseTask& task )
+{
+   	
+	
+	Dbg_AssertType ( &task, Tsk::BaseTask );
+
+	display_task_stack.AddTask ( task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::AddSystemTask ( Tsk::BaseTask& task )
+{
+   	
+	
+	Dbg_AssertType ( &task, Tsk::BaseTask );
+
+	system_task_stack.AddTask ( task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::AddSystemPushTask ( Tsk::BaseTask& task )
+{
+   	
+	
+	Dbg_AssertType ( &task, Tsk::BaseTask );
+
+	system_task_stack.AddPushTask ( task );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::AddLogicPushTask ( Tsk::BaseTask& task )
+{
+   	
+	
+	Dbg_AssertType ( &task, Tsk::BaseTask );
+
+	logic_task_stack.AddPushTask ( task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::AddDisplayPushTask ( Tsk::BaseTask& task )
+{
+   	
+	
+	Dbg_AssertType ( &task, Tsk::BaseTask );
+
+	display_task_stack.AddPushTask ( task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::RemoveAllDisplayTasks ( void )
+{
+   	
+
+	display_task_stack.RemoveAllTasks ();
+}
+
+inline void		Manager::PauseDisplayTasks( bool pause )
+{
+   	
+	display_tasks_paused = pause;
+
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::RemoveAllLogicTasks ( void )
+{
+   	
+
+	logic_task_stack.RemoveAllTasks ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::RemoveAllSystemTasks ( void )
+{
+   	
+
+	system_task_stack.RemoveAllTasks ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::PushLogicTasks ( void )
+{
+   	
+
+	logic_task_stack.Push ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::PushDisplayTasks ( void )
+{
+   	
+
+	display_task_stack.Push ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::PushSystemTasks ( void )
+{
+   	
+
+	system_task_stack.Push ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::PopLogicTasks ( void )
+{
+   	
+
+	logic_task_stack.Pop ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::PopDisplayTasks ( void )
+{
+   	
+
+	display_task_stack.Pop ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		Manager::PopSystemTasks ( void )
+{
+   	
+
+	system_task_stack.Pop ();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mlp
+
+#endif	// __GEL_MAINLOOP_H
+
+
diff --git a/Code/Gel/modman.h b/Code/Gel/modman.h
new file mode 100644
index 0000000..1d14f29
--- /dev/null
+++ b/Code/Gel/modman.h
@@ -0,0 +1,120 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Module  (MDL)											**
+**																			**
+**	File name:		gel/modman.h											**
+**																			**
+**	Created: 		05/27/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_MODMAN_H
+#define __GEL_MODMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+					  
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  Manager  : public Spt::Class
+{
+	
+
+public :
+	
+	void						RegisterModule ( Module &module );
+	void						UnregisterModule ( Module &module );
+
+	void						StartModule ( Module &module );
+	void						RestartModule( Module &module );
+	void						StopModule ( Module &module );
+	void						StartAllModules ( void );
+	void						StopAllModules ( void );
+
+	void						LockAllModules ( void );
+	void						UnlockAllModules ( void );
+
+	Tsk::BaseTask&				GetProcessModulesTask ( void ) const;
+	
+
+private :
+								Manager ( void );
+								~Manager ( void );
+
+	static Tsk::Task< Manager >::Code	process_modules;
+								
+	Tsk::Task< Manager >*		process_modules_task;
+	Lst::Head< Module >			module_list;
+	bool						control_change;
+		
+	DeclareSingletonClass( Manager );
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline	Tsk::BaseTask&	Manager::GetProcessModulesTask ( void ) const
+{
+   	
+	
+	Dbg_AssertType ( process_modules_task, Tsk::BaseTask );
+
+	return	*process_modules_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
+
+#endif	// __GEL_MODMAN_H
diff --git a/Code/Gel/module.h b/Code/Gel/module.h
new file mode 100644
index 0000000..1c7411a
--- /dev/null
+++ b/Code/Gel/module.h
@@ -0,0 +1,160 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Module  (MDL)											**
+**																			**
+**	File name:		gel/module.h											**
+**																			**
+**	Created: 		05/27/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_MODULE_H
+#define __GEL_MODULE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  Module  : public Spt::Class
+{
+	
+
+	friend class			Manager;
+
+	friend Tsk::Task< Manager >::Code	MDL_process_modules;
+
+public :
+
+							Module ( void );
+	virtual	 				~Module ( void );
+
+	void					Lock ( void );
+	void					Unlock ( void );
+
+	bool					Running ( void ) const;
+	bool					Locked ( void ) const;
+
+private :
+
+	enum State
+	{
+		vSTOPPED,
+		vRUNNING
+	};
+
+	enum Command
+	{
+		vNONE,
+		vSTART,
+		vSTOP,
+		vRESTART,
+	};
+	virtual	void			v_start_cb ( void ) = 0;
+	virtual	void			v_stop_cb ( void ) = 0;
+
+	State					state;
+	Command					command;
+	bool					locked;
+	Lst::Node*		node;
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline void			Module::Lock ( void )
+{
+   	
+
+	locked = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void			Module::Unlock ( void )
+{
+   	
+
+	locked = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	bool		Module::Locked ( void ) const
+{
+   	
+
+	return locked;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool			Module::Running ( void ) const
+{
+   	
+
+	return ( state == vRUNNING );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
+
+#endif	// __GEL_MODULE_H
diff --git a/Code/Gel/object.h b/Code/Gel/object.h
new file mode 100644
index 0000000..5c3d4d4
--- /dev/null
+++ b/Code/Gel/object.h
@@ -0,0 +1,377 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		gel/object.h											**
+**																			**
+**	Created: 		05/27/99	-	mjb										**
+**																			**
+**  Notes:
+**
+**  (Mick) This is the base type for all objects in the game.  It contains basic
+**  mechanisms for maintaining a list of object associated with Object Servers
+**  and procedures for deleting them
+**  There are also hooks into the CScript class, in that an object has a 
+**  reference to a script, and a virtual function CallMember, which allows 
+**  the script to call a member function by name (actually by checksum of the name)  
+**
+**
+*****************************************************************************/
+
+#ifndef __GEL_OBJECT_H
+#define __GEL_OBJECT_H
+
+#define	__SCRIPT_EVENT_TABLE__		// define this is you want event tables attached to scripts, rather than objects
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}	
+
+struct SBBox{
+	Mth::Vector m_max;
+	Mth::Vector m_min;
+	Mth::Vector centerOfGravity;
+	float m_radius;  // will give the max distance squared where there may still be a collision.
+};
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define OBJECT_FLAG_INVISIBLE	1
+
+namespace Front
+{
+	class CScreenElementManager;
+}
+
+namespace Script
+{
+	class CArray;
+}
+
+enum EReplaceEventHandlers
+{
+	DONT_REPLACE_HANDLERS = 0,
+	REPLACE_HANDLER
+};
+
+
+namespace Obj			 
+{
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class CEvent;
+class CEventHandlerTable;
+class CBaseManager;
+class CGeneralManager;
+class CObject;
+typedef CSmtPtr CObjectPtr;
+
+
+/*
+	CObject is described in more detail in: Q:\sk4\Docs\programming\Object Scripting System.htm
+	
+	The base class for all scriptable objects. Every CObject has a global ID that
+	is unique among all CObjects. It also has a type specifier.
+	
+	A script that is running can reference a CObject. The script can access special
+	commands for that CObject through CallMemberFunction().	Tags, which are like
+	scriptable member variables, can be attached from CObjects from script, too.
+	
+	Event handling is supported through the PassTargetedEvent() function.
+*/
+class  CObject  : public CRefCounted
+{
+  	
+	friend class		CBaseManager;
+	friend class		CGeneralManager;
+	friend class		Front::CScreenElementManager;
+
+public :						
+						CObject();
+	virtual				~CObject ( void );
+
+	void				DestroyIfUnlocked ();				// Destroy immediately 
+
+	void				AddReference ( void );
+	void				RemoveReference ( void );
+	bool				IsReferenced() {return (m_ref_count > 0);}
+
+	bool				IsDead ( void ) const;				// Access functions for the above
+	bool				IsLocked ( void ) const;
+		
+	uint32				GetID() const  {return m_id;}
+	sint				GetType() const {return m_type;}
+	void				SetID(uint32 id);
+	void				SetType(sint type);
+	
+	void				ClearStampBit(uint32 stamp_mask) { m_stamp &= ~stamp_mask; }
+	bool				CheckStampBit(uint32 stamp_mask) const { return m_stamp & stamp_mask; }
+	void				SetStampBit(uint32 stamp_mask) { m_stamp |= stamp_mask; }
+
+	uint32				GetFlags() const {return m_object_flags;}
+	void				SetFlags(uint32 flags) {m_object_flags = flags;}
+	void				SetLockOn() {m_object_flags |= vLOCKED;}
+	void				SetLockOff() {m_object_flags &= ~vLOCKED;}
+	
+	#ifdef	__NOPT_ASSERT__		
+	void				SetLockAssertOn() {m_object_flags |= vLOCKEDASSERT;}
+	void				SetLockAssertOff() {m_object_flags &= ~vLOCKEDASSERT;}
+	bool				GetLockAssert() {return m_object_flags & vLOCKEDASSERT;}
+	#endif
+
+	Script::CScript	*	GetScript() const {return mp_script;}
+	void				SetScript(Script::CScript* p_script) {mp_script = p_script;}
+
+
+
+	void  				SetEventHandlers(Script::CArray *pArray, EReplaceEventHandlers replace = DONT_REPLACE_HANDLERS);
+	void				RemoveEventHandler(uint32 type);
+	void				RemoveEventHandlerGroup(uint32 group);
+	
+	// See details on tags in object document
+	void				SetIntegerTag(uint32 name, int value);
+	void				SetChecksumTag(uint32 name, uint32 value);
+	void				RemoveFlagTag(uint32 name);
+	bool				GetIntegerTag(uint32 name, int *pResult) const ;
+	bool				GetChecksumTag(uint32 name, uint32 *pResult) const;
+	bool				ContainsFlagTag(uint32 name) const;
+	void				SetTagsFromScript(Script::CStruct *pStruct);
+	void 				RemoveTagsFromScript(Script::CArray *pNameArray);
+	void				CopyTagsToScriptStruct(Script::CStruct *pStruct);
+	void 				SetVectorTag(uint32 name, Mth::Vector	v);
+	bool 				GetVectorTag(uint32 name, Mth::Vector *pResult) const;
+
+
+	void 				SetOnExceptionScriptChecksum(uint32 OnExceptionScriptChecksum);
+	uint32 				GetOnExceptionScriptChecksum() const; 
+
+	
+	// subclasses that extend next three functions should still call CObject versions
+	virtual void		SetProperties(Script::CStruct *pProps);
+	virtual bool 		CallMemberFunction (uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript);	// Call a member function based on the checksum of the function name
+	virtual void		GetDebugInfo(Script::CStruct *p_info);
+	
+	// Called by CTracker singleton to pass events to this CObject
+	virtual bool		PassTargetedEvent(CEvent *pEvent, bool broadcast = false);	// return true if object still valid
+
+	virtual void		HideForReplayPlayback() {}
+	virtual void		RestoreAfterReplayPlayback() {}
+	
+												  
+	enum
+	{
+		vDEAD						= ( 1 << 0 ),
+		// if locked, can't be deleted
+		vLOCKED						= ( 1 << 1 ),
+		vINVISIBLE					= ( 1 << 2 ),
+
+		// this flag has been moved to the ExceptionComponent:
+		//		vDISABLE_EXCEPTIONS			= ( 1 << 3 ),
+	
+	#ifdef	__NOPT_ASSERT__	
+		vLOCKEDASSERT				= ( 1 << 4 ),
+	#endif
+		vCOMPOSITE				= ( 1 << 5 ),
+		
+		
+		// RJM: flags from 8 on are reserved for children of CObject
+		
+	};
+
+protected:
+
+						CObject ( CBaseManager* );
+	
+	void				allocate_tags_if_needed();
+
+public:	
+	void 				AllocateScriptIfNeeded();
+	
+	void 				debug_validate_smart_pointers(CSmtPtr *pPtrToCheckForInclusion = NULL);
+	void				MarkAsDead( void );			// Safe kill called from within the object's logic
+
+	CBaseManager *			GetManager() const	{return mp_manager;}	// just for exception component stuff
+
+protected:
+	
+	uint32				m_object_flags;
+	
+	CBaseManager *			mp_manager;
+	
+	// object script stuff:
+	Script::CScript*	mp_script;
+	
+	// object ID, globally unique among CObjects
+	uint32				m_id;
+	sint				m_type;						// Object type game dependent
+	uint32				m_stamp;
+
+#ifndef	__SCRIPT_EVENT_TABLE__		
+	CEventHandlerTable *	mp_event_handler_table;
+	
+	// Script to run when there is an exception	
+	// (needed here, since we are getting rid of CExceptionComponent, to replace it with one 
+	// single event system
+	uint32 			mOnExceptionScriptChecksum;
+#endif	 
+
+	// Tags are basically variables that can be attached and queried by the scripting
+	// system at runtime. Very convenient for providing state info.
+	Script::CStruct	*		mp_tags;	 
+
+public:
+	// (Moved here from CMovingObject)	
+	// A set of flags that can be set using the SendFlag script command. Used for sending messages to objects.
+	uint32  mScriptFlags;
+	uint32	GetFlags( Script::CStruct *pParams, Script::CScript *pScript ) const;
+
+	// K: Used by CScript::TransmitInfoToDebugger to determine whether the CScript is the
+	// main script of it's parent object.
+	bool	MainScriptIs(const Script::CScript *p_script) const {return mp_script==p_script;}
+	
+	// standard objects can not be paused; thus, spawned (non-main) scripts running on them should never be paused
+	virtual bool		ShouldUpdatePauseWithObjectScripts (   ) { return true; }
+	
+	// K: Also used by CScript::TransmitInfoToDebugger
+	Script::CStruct *GetTags() const {return mp_tags;}
+
+public:
+	// GJ:  Made this public so that components can set their
+	// parent objects' scripts
+	void				SwitchScript( uint32 scriptChecksum, Script::CStruct *pParams );
+	Script::CScript*	SpawnScriptPlease( uint32 scriptChecksum, Script::CStruct *pParams, int Id = 0, bool pause_with_object = false );
+	void				SpawnAndRunScript( uint32 ScriptChecksum, int node = -1, bool net_script = false, bool permanent = false, Script::CStruct *p_params = NULL );
+	void				SpawnAndRunScript( const char *pScriptName, int node = -1, bool net_script = false, bool permanent = false, Script::CStruct *p_params = NULL );
+
+	void				CallScript( uint32 ScriptChecksum, Script::CStruct *pParams );
+
+	void 				SelfEvent(uint32 event, Script::CStruct *pData = NULL);
+	void				BroadcastEvent(uint32 event, Script::CStruct *pData = NULL, float radius = 0.0f);
+	
+protected:
+	Lst::Node< CObject >	m_node;
+	
+private:
+	int						m_ref_count;
+};
+
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+void		DestroyIfUnlocked ( CObject* obj, void* data = NULL );
+void		SetLockOff ( CObject* obj, void* data = NULL );
+CObject *	ResolveToObject(uint32 id);
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline	void			CObject::AddReference ( void )
+{
+	m_ref_count++;
+}
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void			CObject::RemoveReference ( void )
+{
+	//Dbg_Assert(m_ref_count > 0);
+	if (m_ref_count > 0)
+	{
+		m_ref_count--;   	
+	}
+	// XXX
+	else
+	{
+		#ifdef	__NOPT_ASSERT__
+		printf("Aaaah!! Removing more references than there are\n");
+		#endif
+		Dbg_Assert(( m_object_flags & ( 1 << 16 )) == 0 );
+//		if (m_object_flags & (1<<16)) //Front::CScreenElement::vIS_SCREEN_ELEMENT
+//			Dbg_Assert(0);
+	
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	bool			CObject::IsLocked ( void ) const 
+{
+	return ( m_object_flags & ( vLOCKED ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	bool			CObject::IsDead ( void ) const 
+{
+   	
+	return ( m_object_flags & ( vDEAD ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+#endif	// __GEL_OBJECT_H
diff --git a/Code/Gel/objman.h b/Code/Gel/objman.h
new file mode 100644
index 0000000..0155ca7
--- /dev/null
+++ b/Code/Gel/objman.h
@@ -0,0 +1,295 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		gel/objman.h											**
+**																			**
+**	Created: 		05/27/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_OBJMAN_H
+#define __GEL_OBJMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Lst
+{
+	template< class _V > class HashTable;
+}
+
+
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+	
+	
+	
+/*
+	This class manages usage of the bits of a uint32.  Use of CBitManager insures that no two sections of the greater code are using the same bit.  When
+	a bit is requested from a CBitManager, no other requests for bits will give that bit until access to the bit has been returned to the manager.
+	Bit requests and returns are done in the form of uint32 masks.
+*/
+class CBitManager
+{
+public:
+								CBitManager (   );
+	
+	uint32						RequestBit (   );
+	void						ReturnBit ( uint32 mask );
+	
+private:
+	uint32						m_used_bit_mask;
+};
+
+
+
+
+/*
+	See document for more info on this class: Q:\sk4\Docs\programming\Object Scripting System.htm
+	
+	The base class for all object managers. An object manager keeps track of a related
+	set of CObjects (e.g. the screen element manager manages all CScreenElements).
+	The manager might also be responsible for creation and destruction of its CObjects.
+	
+	An subclass of CBaseMananger must register its CObjects with the CTracker singleton.
+	
+	This is a virtual base class.
+*/
+class CBaseManager : public Spt::Class
+{
+public:	
+	
+	static const uint32			vNO_GROUP;
+	static const uint32			vNO_OBJECT_ID;
+								
+	static const sint			vNO_OBJECT_TYPE;
+
+								CBaseManager();
+	virtual 					~CBaseManager() {;}
+	
+	virtual void				RegisterObject ( CObject& obj );
+	virtual void				UnregisterObject ( CObject& obj );
+	virtual void				ReregisterObject ( CObject& obj, uint32 newId );
+	
+	virtual void				SetObjectPriority ( CObject& obj, Lst::Node< CObject >::Priority priority );
+	
+	// Called by CObject::mark_as_dead()
+	virtual void				KillObject ( CObject& obj ) = 0;
+	
+	virtual Lst::Head< CObject > &GetRefObjectList() = 0;
+
+protected:
+
+	Lst::Head< CObject >		m_object_list;	// list of created objects
+	
+	CBitManager					m_stamp_bit_manager;
+	
+	uint32						m_list_changed;
+
+	// important in ReregisterObject(); if applicable, set to ID of new object,
+	// otherwise, zero
+	uint32						m_new_id_of_object_being_momentarily_removed;
+	bool						m_momentary_removal;
+};
+
+
+
+
+/*
+	See document for more info on this class: Q:\sk4\Docs\programming\Object Scripting System.htm
+	
+	The generic implementation of CBaseManager. Use when you want a very
+	basic manager only.
+*/
+class  CGeneralManager  : public CBaseManager
+{
+	
+
+public :
+	
+	typedef	void (Callback) ( CObject*, void *data );
+
+								CGeneralManager ( void );
+								~CGeneralManager ( void );
+
+	void						ProcessAllObjects ( Callback* process, void* data = NULL );
+	void						ProcessAllObjectsOfType ( sint type, Callback* process, 
+														  void* data = NULL );
+
+	void						DestroyAllObjects ( void );
+	void						DestroyAllObjectsOfType ( sint type );
+
+	void						UnlockAllObjects ( void );
+	void						UnlockAllObjectsOfType ( sint type );
+	
+
+	void						KillObject ( CObject& obj );
+
+	uint32						NewObjectID( void );
+	sint						CountObjectsOfType ( sint type );
+	void 						AssertIfObjectsRemainApartFrom( sint *pApartFromThisType );
+
+
+	void 						FlushDeadObjects();
+
+	CObject*					GetObjectByID( uint32 id );
+	
+	Lst::Head< CObject > &		GetRefObjectList() {return m_object_list;}
+
+protected :
+	
+	static Tsk::Task< CGeneralManager >::Code	flush_dead_objects;
+
+	Lst::Head< CObject >		m_kill_list;		// list of objects to be killed
+	Tsk::Task< CGeneralManager >*		mp_kill_task;		// kill task that processes the kill list
+
+//	Lst::HashTable *	mp_hash_table;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline				CBitManager::CBitManager (   )
+	:	m_used_bit_mask(0x00000000)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	uint32		CBitManager::RequestBit (   )
+{
+	Dbg_MsgAssert(m_used_bit_mask != 0xFFFFFFFF, ("Out of available bits in CBitManager::RequestBit"));
+	
+	uint32 ready_mask = nBit(0);
+	while (m_used_bit_mask & ready_mask)
+	{
+		ready_mask <<= 1;
+	}
+	
+	m_used_bit_mask |= ready_mask;
+	
+	return ready_mask;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		CBitManager::ReturnBit ( uint32 returning_mask )
+{
+	Dbg_MsgAssert((returning_mask & (returning_mask - 1)) == 0, ("Calling CBitManager::ReturnBit with a mask contining more than a single set bit"));
+	Dbg_MsgAssert(returning_mask & m_used_bit_mask, ("Returning an unused bit to CBitManager::ReturnBit"));
+	
+	m_used_bit_mask &= ~returning_mask;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		CGeneralManager::DestroyAllObjects ( void )
+{
+   	
+	
+	ProcessAllObjects ( DestroyIfUnlocked );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline	void		CGeneralManager::DestroyAllObjectsOfType ( sint type )
+{
+   	
+
+	ProcessAllObjectsOfType ( type, DestroyIfUnlocked );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void		CGeneralManager::UnlockAllObjects ( void )
+{
+   	
+
+	ProcessAllObjects ( SetLockOff, NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline	void		CGeneralManager::UnlockAllObjectsOfType ( sint type )
+{
+   	
+
+	ProcessAllObjectsOfType ( type, SetLockOff );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+#endif	// __GEL_OBJMAN_H
diff --git a/Code/Gel/objsearch.h b/Code/Gel/objsearch.h
new file mode 100644
index 0000000..1f5fc1f
--- /dev/null
+++ b/Code/Gel/objsearch.h
@@ -0,0 +1,82 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		gel/objsearch.h											**
+**																			**
+**	Created:		05/27/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_OBJSEARCH_H
+#define __GEL_OBJSEARCH_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class  Search : public  Lst::Search< CObject > 
+{
+	
+
+public :
+
+	CObject*			FindFirstObjectOfType  ( Lst::Head< CObject >& head, sint type );
+
+	CObject*			FindNextObjectOfType  ( void );
+
+private :
+
+	sint			obj_type;
+	
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __GEL_OBJSEARCH_H
diff --git a/Code/Gel/objserv.h b/Code/Gel/objserv.h
new file mode 100644
index 0000000..dae35fe
--- /dev/null
+++ b/Code/Gel/objserv.h
@@ -0,0 +1,116 @@
+
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GEL (Game Engine Library)								**
+**																			**
+**	Module:			Base Object-Server (OS)							        **
+**																			**
+**	File name:		gel/server/os_cb.h										**
+**																			**
+**	Created: 		07/23/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_OBJSERV_H
+#define __GEL_OBJSERV_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace ObjServ
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  Object  : public Spt::Class
+{
+	
+    
+public:
+
+	virtual		~Object( void ) {}
+
+protected:
+				Object( void ) {}
+	
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Server  : public Spt::Class
+{
+	
+
+public:
+	virtual void ProcessAllObjects ( void ) = 0;
+    virtual void RegisterObject( Object *object ) = 0;
+    
+    virtual     ~Server( void ) {}
+
+protected:
+				Server( void ) {}
+    
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace ObjServ
+
+#endif	// __GEL_OBJSERV_H
+
diff --git a/Code/Gel/objtrack.h b/Code/Gel/objtrack.h
new file mode 100644
index 0000000..ea51250
--- /dev/null
+++ b/Code/Gel/objtrack.h
@@ -0,0 +1,272 @@
+#ifndef __GEL_OBJTRACK_H
+#define __GEL_OBJTRACK_H
+
+#include 
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+
+
+namespace Front
+{
+	class CScreenElementManager;
+}
+
+
+namespace Lst
+{
+	template< class _V > class HashTable;
+}
+
+
+namespace Obj
+{
+
+class CObject;
+//class CEvent;
+class CEventListener;
+	
+
+/*
+	An instance of this class is attached to the CTracker singleton. It tracks the launching
+	and handling of all events in the system, for debugging purposes.
+*/
+class CEventLog
+{
+public:
+	
+	enum EOccurenceType
+	{
+		vLAUNCHED,
+		vHANDLED,
+		vREAD,
+		vUPDATE,
+		vOBJECT_ADD,
+		vOBJECT_REMOVE,
+	};
+
+	enum
+	{
+		MAX_LOG_ENTRIES			= 1024,
+		MAX_EVENT_TYPES			= 256,
+	};
+
+								CEventLog();
+								~CEventLog();
+
+	void						AddEntry(uint32 type, uint32 target, uint32 source, uint32 script, uint32 receiverID, EOccurenceType lifePoint);
+	void						Print(bool onlyPrintNewEntries = true, int maxEntriesToPrint = MAX_LOG_ENTRIES);
+	void						PrintLatestEntry();
+	void						Clear() {m_num_new_entries = 0;}
+
+private:
+
+	void						print_entry(int index);
+	const char *				get_type_name(uint32 type);
+	
+	struct Entry
+	{
+		union
+		{
+			uint32				m_type;
+			int					m_tick_count; // applies to update type entries
+		};
+		uint32 					m_target;
+		uint32 					m_source;
+		uint32					m_script;					
+		uint32					m_receiver_id;
+		EOccurenceType			m_occurence_type;
+		int						m_depth;
+	};
+
+	Entry						m_table[MAX_LOG_ENTRIES];
+	int							m_next_entry;
+	bool						m_wrapped;
+
+	struct EventType
+	{
+		//uint32					mCrc;
+		char					mName[64];
+	};
+	EventType					m_event_type_table[MAX_EVENT_TYPES];
+	Lst::HashTable *	mp_event_type_hash_table;
+
+	int							m_event_depth;
+	int							m_num_new_entries;
+};
+
+
+// CEventReceiverList comprises of an event type
+// and a list of object that can receive this type of event
+class	CEventReceiverList : public Spt::Class
+{
+public:
+				CEventReceiverList();
+
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	void		RegisterEventReceiverObject(Script::CScript *p_obj);
+	void		UnregisterEventReceiverObject(Script::CScript *p_obj);
+	bool		IsEmpty() {return m_objects.IsEmpty();};
+	Lst::Node< Script::CScript >*	FirstItem() {return m_objects.FirstItem();}
+	int			CountItems() {return m_objects.CountItems();}
+private:
+//	uint32						m_type;	   					// The type of event this is
+	Lst::Head		m_objects;			// a list of objects (SCRIPTS) that are listening for it
+#else
+	void		RegisterEventReceiverObject(CObject *p_obj);
+	void		UnregisterEventReceiverObject(CObject *p_obj);
+	bool		IsEmpty() {return m_objects.IsEmpty();};
+	Lst::Node< Obj::CObject >*	FirstItem() {return m_objects.FirstItem();}
+	int			CountItems() {return m_objects.CountItems();}
+private:
+//	uint32						m_type;	   					// The type of event this is
+	Lst::Head		m_objects;			// a list of objects that are listening for it
+#endif
+};
+
+
+/*
+	See document for more info on this class: Q:\sk4\Docs\programming\Object Scripting System.htm
+	
+	This class keeps track of all registered CObjects, and provides functions for retreiving
+	them by ID. It also takes care of launching and distributing events. It also can reactivate
+	scripts that are suspended while waiting for an event.
+*/
+class CTracker : public Spt::Class
+{
+	friend class CBaseManager;
+	friend class CGeneralManager;
+	friend class Front::CScreenElementManager;
+
+	DeclareSingletonClass( CTracker );
+
+public:								
+								
+	// In logging
+	enum ELoggingSystem
+	{
+		vID_UNSPECIFIED_RECEIVER				= 0,
+		vID_SCREEN_ELEMENT_MANAGER				= 1,
+		vID_SUSPENDED_SCRIPT					= 2,
+		vID_OBJECT_TRACKER						= 3,
+	};
+
+								CTracker();
+	virtual						~CTracker();
+
+	CObject *					GetObject(uint32 id);
+	
+	// See the menu document for a description of aliases
+	CObject *					GetObjectByAlias(uint32 aliasId);
+	void						AddAlias(uint32 alias, CObject *pObject);
+
+	uint32						GetFreshId();
+
+	#ifdef	__NOPT_ASSERT__
+	void 						ValidateReceivers();
+	#endif
+	
+
+	bool						LaunchEvent(uint32 type, uint32 target = CEvent::vSYSTEM_EVENT, uint32 source = CEvent::vSYSTEM_EVENT, Script::CStruct *pData = NULL, bool broadcast=false);
+	void						BlockEventLaunching(bool block) {m_block_event_launching = block;}
+	
+	void 						LogEventScript(uint32 script = 0);
+	void						LogEventHandled(CEvent *pEvent, uint32 receiverID = 0, uint32 script = 0);
+	void						LogEventRead(CEvent *pEvent, uint32 receiverID = 0, uint32 script = 0);
+	void						LogTick();
+	void 						PrintEventLog(bool mostRecentOnly, int maxToPrint);
+	void						EmptyLog() {m_event_log.Clear();}
+
+// Event Listener are special objects that will get sent all types of events
+// and it is up to them to 	
+	void 						RegisterEventListener(CEventListener *pListener);
+	void						UnregisterEventListener(CEventListener *pListener);
+
+// Event Receivers are just CObject and they only get sent events that they tell the tracker they are listening for
+	
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	void						RegisterEventReceiver(uint32 type, Script::CScript *p_obj);
+	void						UnregisterEventReceiver(uint32 type, Script::CScript *p_obj);
+#else	
+	void						RegisterEventReceiver(uint32 type, CObject *p_obj);
+	void						UnregisterEventReceiver(uint32 type, CObject *p_obj);
+#endif	
+	// Note, removing an object should remove all event receivers.
+
+	void						SuspendScriptUntilEvent(Script::CScript *pScript, uint32 event_type);
+
+private:
+
+	void						addObject(CObject *pObject);
+	void 						removeObject(CObject *pObject, uint32 newIdOfObjectBeingMomentarilyRemoved, bool momentary_removal);
+
+	void						forward_event_to_listeners(CEvent *pEvent, CObject *pObject);
+
+	void						remove_aliases(CObject *pObject);
+	
+	int							m_id_seed;
+	
+	Lst::HashTable *	mp_hash_table;
+	Lst::HashTable *	mp_alias_table;
+
+// The hash table of event listeners is keyed off the "type" of the event
+// (the "type" is the actual event name, as a uint32 CRC)	
+	Lst::HashTable *	mp_event_receiver_table;
+	
+	CEventListener *			mp_event_listener_list;
+	
+	bool						m_block_event_launching;
+	enum 
+	{
+		vMAX_EVENT_RECURSE_DEPTH = 32,
+	};
+	int							m_event_recurse_depth;
+
+	CEventLog					m_event_log;
+	uint32						m_next_event_script;
+	
+	// Used to keep track of of scripts that are suspended while waiting for an event.
+	// No pointers to scripts are kept, in case scripts are destroyed.
+	struct WaitingScriptEntry
+	{
+		// the unique ID of the script
+		uint32					mScriptId;
+		#ifdef __NOPT_ASSERT__
+		uint32					mScriptName;
+		#endif
+		// 0 denotes a dead entry
+		uint32				  	mEventType;
+		// the object on which the script is running
+		uint32					mObjectId;
+	};
+
+	enum
+	{
+		vMAX_SCRIPT_ENTRIES 	= 40,
+		vDEAD_SCRIPT_ENTRY 		= 0,
+	};
+
+	WaitingScriptEntry			m_waiting_script_tab[vMAX_SCRIPT_ENTRIES];
+
+	bool						m_debug;
+};
+	
+
+
+bool ScriptLaunchEvent(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptWaitForEvent(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptObjectExists(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptTerminateObjectsScripts(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAssignAlias(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetObjectProperties(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintEventLog(Script::CStruct *pParams, Script::CScript *pScript);
+
+
+
+} // end namespace
+
+#endif
+
diff --git a/Code/Gfx/2D/BlurEffect.cpp b/Code/Gfx/2D/BlurEffect.cpp
new file mode 100644
index 0000000..1489c0c
--- /dev/null
+++ b/Code/Gfx/2D/BlurEffect.cpp
@@ -0,0 +1,131 @@
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Front
+{
+
+
+
+CBlurEffect::CBlurEffect()
+{
+}
+
+
+
+
+CBlurEffect::~CBlurEffect()
+{
+}
+
+
+
+
+void CBlurEffect::Update()
+{
+	Tmr::Time current_time = Tmr::GetTime();
+	float motion_grad = (float) (current_time - m_last_time) / (float) m_key_time;
+	if (current_time - m_last_time < m_key_time)
+		m_key_time -= current_time - m_last_time;
+	else
+		m_key_time = 0;
+	m_last_time = current_time;
+	
+	if (m_key_time)
+	{
+		m_current.topAlpha = m_current.topAlpha + (m_target.topAlpha - m_current.topAlpha) * motion_grad;
+		m_current.bottomAlpha = m_current.bottomAlpha + (m_target.bottomAlpha - m_current.bottomAlpha) * motion_grad;
+		m_current.bottomScaleX = m_current.bottomScaleX + (m_target.bottomScaleX - m_current.bottomScaleX) * motion_grad;
+		m_current.bottomScaleY = m_current.bottomScaleY + (m_target.bottomScaleY - m_current.bottomScaleY) * motion_grad;
+		m_current.maxDisplacementX = m_current.maxDisplacementX + (m_target.maxDisplacementX - m_current.maxDisplacementX) * motion_grad;
+		m_current.maxDisplacementY = m_current.maxDisplacementY + (m_target.maxDisplacementY - m_current.maxDisplacementY) * motion_grad;
+	}
+	else
+	{
+		m_current = m_target;
+	}
+	
+	for (int i = 0; i < 16; i++)
+	{
+		float depth_mult = (float) i / 16.0f;
+		m_tab[i].SetScreenPos(m_current.maxDisplacementX * depth_mult,
+							  m_current.maxDisplacementY * depth_mult);
+		m_tab[i].SetScale(1.0f + (m_current.bottomScaleX - 1.0f) * depth_mult,
+						  1.0f + (m_current.bottomScaleY - 1.0f) * depth_mult);
+		m_tab[i].alpha = (m_current.topAlpha + (m_current.bottomAlpha - m_current.topAlpha) * depth_mult) / 16.0f;
+	}
+}
+
+
+
+
+CScreenElement::ConcatProps &CBlurEffect::GetInfo(int index)
+{
+	return m_tab[index];
+}
+
+
+
+
+void CBlurEffect::SetAllTargetProps(const Props &newProps, Tmr::Time time) 
+{
+	m_target = newProps;
+	SetAnimTime(time);
+}
+
+
+
+
+void CBlurEffect::SetAnimTime(Tmr::Time time)
+{
+	if (time == 0)
+		m_current = m_target;
+	
+	m_key_time = time;
+	m_last_time = Tmr::GetTime();
+}
+
+
+
+
+const CBlurEffect::Props &CBlurEffect::SetMorph(Script::CStruct *pProps, Tmr::Time *pRetTime)
+{	
+	Script::CPair alpha_pair;
+	if (pProps->GetPair(CRCD(0x21de240c,"blur_alpha_pair"), &alpha_pair))
+	{
+		SetAlphas(alpha_pair.mX, alpha_pair.mY);
+	}
+
+	Script::CPair bottom_scales;
+	if (pProps->GetPair(CRCD(0x3e10436d,"blur_bottom_scales"), &bottom_scales))
+	{
+		SetBottomScales(bottom_scales.mX, bottom_scales.mY);
+	}
+
+	Script::CPair max_displacement;
+	if (pProps->GetPair(CRCD(0x4c2a3cde,"blur_max_displacement"), &max_displacement))
+	{
+		SetMaxDisplacements(max_displacement.mX, max_displacement.mY);
+	}
+	
+	float desired_time;
+	Tmr::Time anim_time;
+	if (pProps->GetFloat(CRCD(0x906b67ba,"time"), &desired_time))
+	{
+		anim_time = (Tmr::Time) (desired_time * (float) Tmr::vRESOLUTION);
+	}
+	else
+		anim_time = 0;
+	SetAnimTime(anim_time);
+	if (pRetTime)
+		*pRetTime = anim_time;
+
+	return m_target;
+}
+
+
+
+
+}
diff --git a/Code/Gfx/2D/BlurEffect.h b/Code/Gfx/2D/BlurEffect.h
new file mode 100644
index 0000000..eddf6e0
--- /dev/null
+++ b/Code/Gfx/2D/BlurEffect.h
@@ -0,0 +1,64 @@
+#ifndef __GFX_2D_BLUREFFECT_H__
+#define __GFX_2D_BLUREFFECT_H__
+
+#include 
+#include 
+
+namespace Script
+{
+	class CStruct;
+}
+
+namespace Front
+{
+
+class CBlurEffect
+{
+public:
+
+	struct Props
+	{
+		float						topAlpha;
+		float						bottomAlpha;
+		float 						bottomScaleX;
+		float						bottomScaleY;					
+		float						maxDisplacementX;
+		float						maxDisplacementY;
+	};
+		
+									CBlurEffect();
+									~CBlurEffect();
+
+	void							Update();
+
+	CScreenElement::ConcatProps &	GetInfo(int index);
+	int								GetNumEntries() {return 16;}
+	Image::RGBA						GetRGBA() {return m_rgba;}
+
+	void							SetAlphas(float top, float bottom) {m_target.topAlpha = top; m_target.bottomAlpha = bottom;}
+	void							SetBottomScales(float scaleX, float scaleY) {m_target.bottomScaleX = scaleX; m_target.bottomScaleY = scaleY;}
+	void							SetMaxDisplacements(float dispX, float dispY) {m_target.maxDisplacementX = dispX; m_target.maxDisplacementY = dispY;}
+	void							SetRGBA(Image::RGBA rgba) {m_rgba = rgba;}
+	void							SetAllTargetProps(const Props &newProps, Tmr::Time time);
+	void							SetAnimTime(Tmr::Time time);
+
+	const Props &					SetMorph(Script::CStruct *pProps, Tmr::Time *pRetTime = NULL);
+
+protected:
+
+	CScreenElement::ConcatProps		m_tab[16];
+
+	Props							m_target;
+	Props							m_current;
+
+	Tmr::Time						m_last_time;
+	Tmr::Time						m_key_time;
+	
+	Image::RGBA						m_rgba;
+};
+
+}
+
+#endif // __GFX_2D_BLUREFFECT_H__
+
+
diff --git a/Code/Gfx/2D/Element3d.cpp b/Code/Gfx/2D/Element3d.cpp
new file mode 100644
index 0000000..f09dd40
--- /dev/null
+++ b/Code/Gfx/2D/Element3d.cpp
@@ -0,0 +1,945 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 		// <<<<<<<< NEEDS MOVING!!!!
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Front
+{
+
+// added by your mother
+// returns the scale that needs to be applied to (modelWorldW,modelWorldH) to make it fit (screenW,screenH)
+static float sGetScaleFromScreenDimensions(float screenW, float screenH, float zOffset, float modelWorldW, float modelWorldH)
+{
+	// Inverse project.
+	float desired_world_w = screenW * zOffset / (640.0f * (1.0f + screenW / 1280.0f));
+	float desired_world_h = screenH * zOffset / (448.0f * (1.0f + screenW / 896.0f));
+
+	float scale_x = desired_world_w / modelWorldW;
+	float scale_y = desired_world_h / modelWorldH;
+	return (-scale_x < -scale_y) ? -scale_x : -scale_y;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// screenX, screenY indicate where the origin of the model will be located
+static void sGetWorldMatrixFromScreenPosition(int camera_num, Mth::Matrix *p_world_matrix, float screenX, float screenY, float zOffset, Mth::Vector &model_offs)
+{
+	Dbg_MsgAssert(p_world_matrix,("NULL p_world_matrix"));
+	Dbg_MsgAssert((camera_num >= 0) && (camera_num < Nx::CViewportManager::vMAX_NUM_ACTIVE_VIEWPORTS), ("Camera number is out of range: %d", camera_num));
+
+	Mth::Vector camera_space_pos;
+
+#ifdef __PLAT_XBOX__
+	// Don't think Mick's assumption below is correct - for 3D objects going through the rendering pipeline, no screen correction is required.
+	screenX = screenX - ( 640 / 2 );
+	screenY = screenY - ( 448 / 2 );
+
+	// Adjust X for aspect ratio that is different than 4/3 (1.3333333)
+	screenX *= (Nx::CViewportManager::sGetScreenAspect() / 1.3333333f);
+
+	// Also, adjust for 720p (horrible hack).
+	if( NxXbox::EngineGlobals.backbuffer_width > 640 )
+	{
+		screenX *= 0.9f;
+		screenY *= 0.9f;
+	}
+
+	camera_space_pos.Set( -screenX * zOffset / 448.0f, screenY * zOffset / 448.0f, zOffset );
+#else
+	// Mick:  Need to convert from logical to physical screen coordinates
+	screenX = SCREEN_CONV_X(screenX);
+	screenY = SCREEN_CONV_Y(screenY);
+
+	// Make ScreenX and ScreenY relative to the centre of the screen.
+	screenX -= SCREEN_CONV_X(640)/2;
+	screenY -= SCREEN_CONV_Y(448)/2;
+
+	// Adjust X for aspect ratio that is different than 4/3 (1.3333333)
+	screenX *= (Nx::CViewportManager::sGetScreenAspect() / 1.3333333f);
+
+	// Inverse project.
+	camera_space_pos.Set(-screenX * zOffset / SCREEN_CONV_X(448),
+						 screenY * zOffset / SCREEN_CONV_Y(448),
+						 zOffset);
+#endif // __PLAT_XBOX__
+
+	camera_space_pos += model_offs;
+						 
+	// We now have a 3d vector in camera space, so rotate by the camera matrix
+	// to get world coords.	
+
+	p_world_matrix->Ident();
+
+	// Get the camera.
+	Gfx::Camera *p_camera = Nx::CViewportManager::sGetActiveCamera(camera_num);
+	if (p_camera)
+	{
+		Mth::Matrix cam_matrix=p_camera->GetMatrix();
+		
+		// Camera matrix might have been incorrectly set up with a translation in W
+		// so clear it out to be safe
+		// Probably should be handled at a higher level.
+		cam_matrix[W].Set();
+
+		// Apply the camera matrix to the camera_space_pos
+		Mth::Vector world_pos = camera_space_pos * cam_matrix;
+		// and add the camera position to get the final world position.
+		Mth::Vector cam_pos = p_camera->GetPos();
+		cam_pos[W] = 0.0f;
+		world_pos += cam_pos;
+		world_pos[W] = 0.0f;
+		
+	
+		// Make the rotation part of the passed matrix be the camera matrix so that the model 
+		// always faces the camera.
+		*p_world_matrix*=cam_matrix;
+		// Then add in the calculated world position.
+		p_world_matrix->Translate(world_pos);
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CElement3d::CElement3d()
+{
+	for (int i=0; iGetAsset( skeletonChecksum, false );
+
+	if ( !pSkeletonData )
+	{
+		Dbg_MsgAssert( 0, ("Unrecognized skeleton %s. (Is skeleton.q up to date?)", Script::FindChecksumName(skeletonChecksum)) );
+	}
+    
+	Gfx::CSkeleton* pSkeleton = new Gfx::CSkeleton( pSkeletonData );
+    
+    Dbg_Assert( pSkeleton );
+    Dbg_Assert( pSkeleton->GetNumBones() > 0 );
+	
+    pModel->SetSkeleton( pSkeleton );
+
+    return pSkeleton;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CModel* CElement3d::CreateModel( uint32 skeleton_name, Mth::Vector offset )
+{
+	Dbg_MsgAssert(m_num_modelsCreateSkeleton( p_new_model, skeleton_name );
+	}
+
+	p_new_model->SetColor(m_rgba.r,m_rgba.g,m_rgba.b,m_rgba.a);
+
+	Dbg_MsgAssert(mp_models[m_num_models].mpModel==NULL,("mp_models[m_num_models].mpModel not NULL ?"));
+	mp_models[m_num_models].mpModel=p_new_model;
+	
+	mp_models[m_num_models].mOffset=offset;
+	++m_num_models;
+
+	return p_new_model;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CElement3d::AddModel(const char *p_model_name, uint32 skeleton_name, Mth::Vector offset, int texDictOffset)
+{
+	Dbg_MsgAssert(m_num_modelsCreateSkeleton( p_new_model, skeleton_name );
+    }
+
+	if ( p_model_name )
+	{
+		// loads a geom, if one was specified
+		Str::String fullModelName;
+		fullModelName = Gfx::GetModelFileName(p_model_name, ".mdl");
+		p_new_model->AddGeom(fullModelName.getString(), 0, true, texDictOffset);
+	}
+	
+	p_new_model->SetColor(m_rgba.r,m_rgba.g,m_rgba.b,m_rgba.a);
+	
+	Dbg_MsgAssert(mp_models[m_num_models].mpModel==NULL,("mp_models[m_num_models].mpModel not NULL ?"));
+	mp_models[m_num_models].mpModel=p_new_model;
+	
+	mp_models[m_num_models].mOffset=offset;
+	++m_num_models;
+
+	update_visibility();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CElement3d::AddModelFromSector(uint32 sectorName, Mth::Vector offset)
+{
+	Dbg_MsgAssert(m_num_modelsGetGeom();
+		Dbg_Assert(p_geom);
+		if( p_geom )
+		{
+			Nx::CGeom* p_cloned_geom = p_geom->Clone( true );
+			p_cloned_geom->SetActive(true);
+			p_new_model->AddGeom( p_cloned_geom, 0 );
+		}
+	}
+	
+	p_new_model->SetColor(m_rgba.r,m_rgba.g,m_rgba.b,m_rgba.a);
+	
+	Dbg_MsgAssert(mp_models[m_num_models].mpModel==NULL,("mp_models[m_num_models].mpModel not NULL ?"));
+	mp_models[m_num_models].mpModel=p_new_model;
+	
+	mp_models[m_num_models].mOffset=offset;
+	++m_num_models;
+
+	update_visibility();
+}
+
+
+void CElement3d::AutoComputeScale()
+{	
+	Mth::Vector element_world_min, element_world_max;
+	for (int i = 0; i < m_num_models; i++)
+	{
+		Nx::CGeom *p_geom = mp_models[i].mpModel->GetGeom(0);
+		Dbg_Assert(p_geom);
+		Mth::CBBox b_box = p_geom->GetBoundingBox();
+		Mth::Vector model_min(mp_models[i].mOffset.GetX() + b_box.GetMin().GetX(),
+							  mp_models[i].mOffset.GetY() + b_box.GetMin().GetY(),
+							  mp_models[i].mOffset.GetZ() + b_box.GetMin().GetZ());
+		Mth::Vector model_max(mp_models[i].mOffset.GetX() + b_box.GetMax().GetX(),
+							  mp_models[i].mOffset.GetY() + b_box.GetMax().GetY(),
+							  mp_models[i].mOffset.GetZ() + b_box.GetMax().GetZ());
+
+		if (i == 0)
+		{
+			element_world_min = model_min;
+			element_world_max = model_max;
+		}
+		else
+		{
+			if (model_min[X] < element_world_min[X])
+				element_world_min[X] = model_min[X];
+			if (model_min[Y] < element_world_min[Y])
+				element_world_min[Y] = model_min[Y];
+			if (model_min[Z] < element_world_min[Z])
+				element_world_min[Z] = model_min[Z];
+
+			if (model_max[X] > element_world_max[X])
+				element_world_max[X] = model_max[X];
+			if (model_max[Y] > element_world_max[Y])
+				element_world_max[Y] = model_max[Y];
+			if (model_max[Z] > element_world_max[Z])
+				element_world_max[Z] = model_max[Z];
+		}
+
+		/*
+		Ryan("offset is (%.2f,%.2f,%.2f), min is (%.2f,%.2f,%.2f), max is (%.2f,%.2f,%.2f)\n",
+			 mp_models[i].mOffset.GetX(), mp_models[i].mOffset.GetY(), mp_models[i].mOffset.GetZ(),
+			 b_box.GetMin().GetX(), b_box.GetMin().GetY(), b_box.GetMin().GetZ(),
+			 b_box.GetMax().GetX(), b_box.GetMax().GetY(), b_box.GetMax().GetZ());
+		*/
+	}
+
+	float element_world_w = element_world_max[X] - element_world_min[X];
+	float element_world_h = element_world_max[Y] - element_world_min[Y];
+	float element_world_l = element_world_max[Z] - element_world_min[Z];
+	m_scale3d = sGetScaleFromScreenDimensions(m_base_w, m_base_h, 
+								  m_camera_z, 
+								  (element_world_w > element_world_l) ? element_world_w : element_world_l,
+									element_world_h);
+	
+	/*
+	Ryan("yo homies, world dims are: (%.2f,%.2f,%.2f), desired scale is: (%.5f,%.5f), camera_z1 is %.4f, camera_z2 is %.4f\n", 
+		 element_world_w, element_world_h, element_world_l,
+		 scale, scale, m_camera_z, m_camera_z + element_world_max[Z] * scale);
+	*/
+	//CScreenElement::SetScale(scale, scale, false, CScreenElement::FORCE_INSTANT);
+
+	m_offset_of_origin_from_center.Set(-(element_world_max[X] + element_world_min[X]) / 2.0f,
+									   -(element_world_max[Y] + element_world_min[Y]) / 2.0f,
+									   -(element_world_max[Z] + element_world_min[Z]) / 2.0f,
+									   0.0f);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This gets called by the SetProps script command.
+// Also gets called when the element is first created by CreateScreenElement, all the params sent
+// to CreateScreenElement get dent to this function.
+void CElement3d::SetProperties(Script::CStruct *p_props)
+{
+	CScreenElement::SetProperties(p_props);
+	
+	uint32 skeletonChecksum = 0;
+	p_props->GetChecksum( CRCD(0x9794932,"skeletonName"), &skeletonChecksum, false );
+	
+	const char* p_model_name="";
+	Script::CArray *p_model_array=NULL;
+	uint32 clone_model_name;
+	uint32 sector_name;
+	Script::CArray *p_sector_array=NULL;
+	bool skip_empty_model = false;
+    
+    int texDictOffset = 0;
+    p_props->GetInteger( CRCD(0xf891ac27,"TexDictOffset"), &texDictOffset, false );
+	
+	if (p_props->GetChecksum( CRCD(0x73ec4c8,"CloneModel"), &clone_model_name ))
+	{
+		UnloadModels();
+		
+		Mth::Vector off;
+		off.Set();
+
+		// Clone the geometry off of an existing model
+		Obj::CCompositeObject* pObject = static_cast(Obj::CCompositeObjectManager::Instance()->GetObjectByID(clone_model_name));
+		Dbg_MsgAssert( pObject, ( "Couldn't find object id %d to clone", clone_model_name ) );
+
+		Obj::CModelComponent *p_src_model_comp = GetModelComponentFromObject(pObject);
+		Dbg_MsgAssert( p_src_model_comp, ( "Couldn't find model component in object id %d", clone_model_name ) );
+		Dbg_MsgAssert( p_src_model_comp->GetModel(), ( "Couldn't find CModel in model component of object id %d", clone_model_name ) );
+
+		Script::CArray *p_geom_array=NULL;
+		if (p_props->GetArray(CRCD(0x3cc0910b,"CloneModelGeoms"), &p_geom_array))
+		{
+			// Create new model
+			Nx::CModel *p_model = CreateModel(skeletonChecksum, off);
+
+			uint32 geomName;
+			for (uint i = 0; i < p_geom_array->GetSize(); i++)
+			{
+				geomName = p_geom_array->GetChecksum(i);
+				if (geomName)
+				{
+					Nx::CGeom *p_orig_geom = p_src_model_comp->GetModel()->GetGeom(geomName);
+					Dbg_MsgAssert(p_orig_geom, ("Couldn't find CGeom %s", Script::FindChecksumName(geomName)));
+
+					Nx::CGeom *p_new_geom = p_orig_geom->Clone(true, p_model);
+					Dbg_MsgAssert(p_new_geom, ("Couldn't clone CGeom %s", Script::FindChecksumName(geomName)));
+
+					p_new_geom->SetActive(true);
+					p_model->AddGeom(p_new_geom, geomName);
+				}
+			}
+		}
+		else
+		{
+			Dbg_MsgAssert( 0, ( "Must name geoms for object id %d to clone", clone_model_name ) );
+		}
+
+		update_visibility();
+		skip_empty_model = true;
+	}	
+	else if (p_props->GetString( CRCD(0x286a8d26,"model"), &p_model_name ))
+	{
+		UnloadModels();
+		
+		Mth::Vector off;
+		off.Set();
+		AddModel(p_model_name,skeletonChecksum,off,texDictOffset);
+	}	
+	else if (p_props->GetArray(CRCD(0x1b29cff6,"models"),&p_model_array))
+	{
+		UnloadModels();
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			Script::CStruct *p_struct=p_model_array->GetStructure(i);
+			p_struct->GetString(NONAME,&p_model_name);
+			Mth::Vector off;
+			off.Set();
+			p_struct->GetVector(NONAME,&off);
+			AddModel(p_model_name,skeletonChecksum,off,texDictOffset);
+		}
+	}	 
+	if (p_props->GetChecksum( CRCD(0xb45c2617,"sector"), §or_name ))
+	{
+		UnloadModels();
+		
+		Mth::Vector off;
+		off.Set();
+		AddModelFromSector(sector_name, off);
+	}	
+	else if (p_props->GetArray(CRCD(0x4a6bf967,"sectors"), &p_sector_array))
+	{
+		UnloadModels();
+		for (uint32 i=0; i < p_sector_array->GetSize(); ++i)
+		{
+			Script::CStruct *p_struct = p_sector_array->GetStructure(i);
+			p_struct->GetChecksum(NONAME, §or_name);
+			Mth::Vector off;
+			off.Set();
+			p_struct->GetVector(NONAME, &off);
+			AddModelFromSector(sector_name, off);
+		}
+	}	 
+	else if ( (skeletonChecksum != 0) && !skip_empty_model )
+	{
+		// no model was specified, but we might need to create
+		// a skeleton anyway (like for the preview models)
+		Mth::Vector off;
+		off.Set();
+		AddModel(NULL, skeletonChecksum, off, 0);
+	}
+	
+	p_props->GetFloat(CRCD(0xed7c6031,"CameraZ"),&m_camera_z);
+	p_props->GetFloat(CRCD(0xaffd09d,"AngleX"),&m_angle_x);
+	p_props->GetFloat(CRCD(0x7df8e00b,"AngleY"),&m_angle_y);
+	p_props->GetFloat(CRCD(0xe4f1b1b1,"AngleZ"),&m_angle_z);
+	p_props->GetFloat(CRCD(0xc1608989,"AngVelX"),&m_angvel_x);
+	p_props->GetFloat(CRCD(0xb667b91f,"AngVelY"),&m_angvel_y);
+	p_props->GetFloat(CRCD(0x2f6ee8a5,"AngVelZ"),&m_angvel_z);
+
+	if (p_props->ContainsFlag(CRCD(0x233f6b18,"AngleOrderXYZ"))) m_angle_order = ANGLE_ORDER_XYZ;
+	if (p_props->ContainsFlag(CRCD(0x911b6961,"AngleOrderXZY"))) m_angle_order = ANGLE_ORDER_XZY;
+	if (p_props->ContainsFlag(CRCD(0x3be6306e,"AngleOrderYXZ"))) m_angle_order = ANGLE_ORDER_YXZ;
+	if (p_props->ContainsFlag(CRCD(0xe7de33c0,"AngleOrderYZX"))) m_angle_order = ANGLE_ORDER_YZX;
+	if (p_props->ContainsFlag(CRCD(0xa0a9df8d,"AngleOrderZXY"))) m_angle_order = ANGLE_ORDER_ZXY;
+	if (p_props->ContainsFlag(CRCD(0xceb5de5a,"AngleOrderZYX"))) m_angle_order = ANGLE_ORDER_ZYX;
+
+	if (p_props->ContainsFlag(CRCD(0xf01fdca4,"DisablePointing")))
+	{
+		m_point_type=POINT_TYPE_NONE;
+	}
+		
+	uint32 node_checksum=0;
+	if (p_props->GetChecksum(CRCD(0xb2a86eb5,"NodeToPointTo"),&node_checksum))
+	{
+		int node_to_point_to=SkateScript::FindNamedNode(node_checksum);
+		Script::CStruct *p_node=SkateScript::GetNode(node_to_point_to);
+		Dbg_MsgAssert(p_node,("NULL p_node"));
+		SkateScript::GetPosition(p_node,&m_node_position_to_point_to);
+
+		m_point_type=POINT_TYPE_NODE;
+	}	
+
+	m_id_of_object_to_point_to=0;
+	if (p_props->GetChecksum(CRCD(0xdad39b88,"ObjectToPointTo"),&m_id_of_object_to_point_to))
+	{
+		m_point_type=POINT_TYPE_OBJECT;
+	}	
+	
+	m_tilt=0.0f;
+	p_props->GetFloat(CRCD(0xe3c07609,"Tilt"),&m_tilt);
+	
+	m_hover_amp=0.0f;
+	m_hover_period=0;
+	Script::CStruct *p_hover_params=NULL;
+	if (p_props->GetStructure(CRCD(0xca1c41,"HoverParams"),&p_hover_params))
+	{
+		p_hover_params->GetFloat(CRCD(0xc9fde32c,"Amp"),&m_hover_amp);
+		float f=1.0f;
+		p_hover_params->GetFloat(CRCD(0xa80bea4a,"Freq"),&f);
+		m_hover_period=(int)(1000.0f/f);
+	}
+	
+	m_skater_number=0;
+	m_parent_object_name=0;
+	m_parent_node_name=0;
+	m_parent_offset.Set();
+	Script::CStruct *p_parent_params=NULL;
+	if (p_props->GetStructure(CRCD(0x36388933,"ParentParams"),&p_parent_params))
+	{
+		p_parent_params->GetChecksum(CRCD(0xa1dc81f9,"Name"),&m_parent_object_name);
+		p_parent_params->GetVector(NONAME,&m_parent_offset);
+		if (p_parent_params->GetInteger(CRCD(0x5b8ab877,"Skater"),&m_skater_number))
+		{
+			m_parent_object_name=0x5b8ab877/*Skater*/;
+		}	
+		// Sometimes they want an arrow to be hovering near an actual node,
+		// rather than the instance of some CObject associated with that node.
+		// Eg, they may want an arrow hovering over some level geometry.
+		p_parent_params->GetChecksum(CRCD(0x7a8017ba,"Node"),&m_parent_node_name);
+		
+		Dbg_MsgAssert(!(m_parent_node_name && m_parent_object_name),("Cannot specify both a parent object and a parent node for an element3d"));
+	}
+
+	if (p_props->ContainsFlag(CRCD(0x1b399065,"scale_to_screen_dims")))
+	{
+		AutoComputeScale();
+	}
+	else if (p_props->GetVector("PivotPoint", &m_pivot_point))
+	{
+		m_pivot_point[W] = 1.0f;		// force to be a point
+	}
+
+	m_scale_with_distance=p_props->ContainsFlag(CRCD(0x4564c8f2,"ScaleWithDistance"));
+	m_scale_multiplier=0.002f;
+	p_props->GetFloat(CRCD(0x571f3f3a,"ScaleMultiplier"),&m_scale_multiplier);
+	m_max_scale=3.0f;
+	p_props->GetFloat(CRCD(0x31e78f98,"MaxScale"),&m_max_scale);
+
+
+	int viewport;
+	if (p_props->GetInteger(CRCD(0x655ee08e,"Active_Viewport"), &viewport))
+	{
+		m_active_viewport_number = viewport;
+		update_visibility();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CElement3d::update()
+{
+	if (m_waiting_to_die)
+	{
+		// Return straight away to prevent repeated spawnings of the KillElement3d script
+		return;
+	}
+		
+	if (m_num_models)
+	{							
+		if (m_parent_object_name)
+		{
+			// Get a pointer to the parent object.
+			Obj::CMovingObject *p_pos_obj=NULL;
+
+// Skate module 
+//			if (m_parent_object_name==0x5b8ab877/*Skater*/)
+//			{
+//				// This is 
+//				Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+//				p_pos_obj=skate_mod->GetSkater(m_skater_number);
+//			}
+//			else			
+			{
+//				p_pos_obj=Obj::CMovingObject::m_hash_table.GetItem(m_parent_object_name);
+				p_pos_obj = (Obj::CMovingObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID(m_parent_object_name);
+			}	
+			
+			if (p_pos_obj)
+			{
+				m_model_matrix.Ident();
+				m_model_matrix.SetPos(p_pos_obj->m_pos+m_parent_offset*m_model_matrix);
+			}
+			else
+			{
+				// The parent object has disappeared!
+				// So call the KillElement3d script to safely delete this arrow.
+				
+				// Tried various ways of killing directly from c-code, but they didn't work.
+				// Instant deletion here will mess up the update loop, and calling
+				// Die() to flag the element for deletion later also cause problems, because
+				// the parent element needs to be unlocked at the time of deletion.
+				
+				// Running a script seems to be safe ...
+				// The KillElement3d script will wait a gameframe, then call the usual Die
+				Script::CScript *p_script=Script::SpawnScript(0xa66c1c31/*KillElement3d*/);
+				#ifdef __NOPT_ASSERT__
+				p_script->SetCommentString("Created by CElement3d::update()");
+				#endif
+				p_script->mpObject=this;
+				m_waiting_to_die=true;
+			}	
+		}
+		else if (m_parent_node_name)
+		{
+			// FindNamedNode will assert if it does not find the node.
+			int node_index=SkateScript::FindNamedNode(m_parent_node_name);
+			Script::CStruct *p_node=SkateScript::GetNode(node_index);
+			Dbg_MsgAssert(p_node,("NULL p_node"));
+			
+			Mth::Vector pos;
+			SkateScript::GetPosition(p_node,&pos);
+			
+			m_model_matrix.Ident();
+			m_model_matrix.SetPos( pos + m_parent_offset * m_model_matrix );
+			// m_model_matrix.SetPos(pos);
+		}
+		else
+		{
+			Mth::Vector pos_change(m_offset_of_origin_from_center[X] * m_scale3d,
+								   m_offset_of_origin_from_center[Y] * m_scale3d,
+								   m_offset_of_origin_from_center[Z] * m_scale3d);
+			/*
+			Mth::Vector pos_change(m_offset_of_origin_from_center[X] * m_summed_props.scalex * m_scale3d,
+								   m_offset_of_origin_from_center[Y] * m_summed_props.scaley * m_scale3d,
+								   m_offset_of_origin_from_center[Z] * m_summed_props.scalex * m_scale3d);
+			*/
+			sGetWorldMatrixFromScreenPosition(m_active_viewport_number, &m_model_matrix, 
+											  m_summed_props.GetScreenUpperLeftX() + GetAbsW() / 2.0f, 
+											  m_summed_props.GetScreenUpperLeftY() + GetAbsH() / 2.0f, 
+											  m_camera_z, 
+											  pos_change);
+	
+			// If the model were rendered using m_model_matrix as it is then it will be unscaled
+			// and facing the camera.
+			
+			// If the object is required to point to somewhere in the world then change the rotation
+			// the be the required one.
+			Mth::Vector position_to_point_to;
+			switch (m_point_type)
+			{
+			case POINT_TYPE_NODE:
+				position_to_point_to=m_node_position_to_point_to;
+				break;
+			case POINT_TYPE_OBJECT:
+			{
+				// Get a pointer to the object.
+				// Doing a naughty cast up to a CMovingObject, because it probably will be one, and
+				// won't cause a crash if it isn't, it'll just get a bad position.
+				Obj::CMovingObject *p_pos_obj=(Obj::CMovingObject*)Obj::ResolveToObject(m_id_of_object_to_point_to);
+				if (p_pos_obj)
+				{
+					position_to_point_to=p_pos_obj->m_pos;
+				}
+				break;
+			}	
+			default:
+				break;
+			}
+			
+			if (m_point_type!=POINT_TYPE_NONE)
+			{
+				// Save the position
+				Mth::Vector model_pos=m_model_matrix.GetPos();
+				// Clear out the rotation calculated by sGetWorldMatrixFromScreenPosition
+				m_model_matrix.Ident();
+				
+				Mth::Vector dir=position_to_point_to-model_pos;
+				
+				dir.Normalize();
+				m_model_matrix[Z]=dir;
+				
+				m_model_matrix[X].Set(dir.GetZ(),0.0f,-dir.GetX());
+				m_model_matrix[X].Normalize();
+				
+				m_model_matrix.OrthoNormalizeAbout(Z);
+				
+				// Tilt so that the arrow is not end on to the camera.
+				// However, make the tilt drop linearly down to zero as the
+				// arrow goes towards being vertical so that the arrow never
+				// ends up pointing backwards.
+				float r=m_tilt-(-m_model_matrix[Z].GetY()*m_tilt);
+				m_model_matrix.RotateXLocal(Mth::DegToRad(r));
+
+				// Put the position back in.
+				m_model_matrix.Translate(model_pos);
+			}
+		}
+				
+		// Now create a rotation matrix containing any extra rotation that we want.
+		Mth::Matrix rot_matrix;
+		rot_matrix.Ident();
+
+		rot_matrix.Translate(-m_pivot_point);
+
+		// Update the angles using the angular velocities.
+		m_angle_x+=m_angvel_x;
+		m_angle_y+=m_angvel_y;
+		m_angle_z+=m_angvel_z;
+		
+		// Reduce modulo 2pi so that the calculations don't go weird after a few days
+		// due to the angles getting huge.
+		m_angle_x=m_angle_x-((int)(m_angle_x/(2.0f*3.141592654f)))*(2.0f*3.141592654f);
+		m_angle_y=m_angle_y-((int)(m_angle_y/(2.0f*3.141592654f)))*(2.0f*3.141592654f);
+		m_angle_z=m_angle_z-((int)(m_angle_z/(2.0f*3.141592654f)))*(2.0f*3.141592654f);
+		
+		//rot_matrix.RotateY(m_angle_y);
+		//rot_matrix.RotateX(m_angle_x);
+		//rot_matrix.RotateZ(m_angle_z);
+		s_apply_rotations(rot_matrix, m_angle_x, m_angle_y, m_angle_z, m_angle_order);
+
+		rot_matrix.Translate(m_pivot_point);
+
+		// Apply scaling.
+		//Ryan("oogbat %.4f, %.4f, %.4f\n", m_summed_props.scalex, m_scale3d, m_summed_props.scalex * m_scale3d);
+		Mth::Vector scale(m_summed_props.GetScaleX() * m_scale3d,
+						  m_summed_props.GetScaleY() * m_scale3d,
+						  m_summed_props.GetScaleX() * m_scale3d,
+						  1.0f);
+						  
+		// The ped arrows (or stars) need to scale according to the distance from the camera so that
+		// they are visible from a distance. (TT2006)
+		if (m_scale_with_distance)
+		{
+			Gfx::Camera *p_camera = Nx::CViewportManager::sGetActiveCamera(m_active_viewport_number);
+			if (p_camera)
+			{
+				Mth::Vector cam_offset=m_model_matrix.GetPos()-p_camera->GetPos();
+				float multiplier=cam_offset.Length()*m_scale_multiplier;
+				if (multiplier < 1.0f)
+				{
+					multiplier=1.0f;
+				}
+				if (multiplier > m_max_scale)
+				{
+					multiplier = m_max_scale;
+				}
+					
+				scale[X]*=multiplier;
+				scale[Y]*=multiplier;
+				scale[Z]*=multiplier;
+			}	
+		}
+		
+		rot_matrix.Scale(scale);
+
+		// Apply the rotation and scaling to the m_model_matrix	
+		m_model_matrix=rot_matrix*m_model_matrix;
+
+
+		// Calculate any hover offset that may need to be applied.		
+		Mth::Vector hover_offset;
+		if (m_hover_amp>0.0f)
+		{
+			int t=Tmr::ElapsedTime(0)%m_hover_period;
+			float h=m_hover_amp*sinf(t*2*3.141592653f/m_hover_period);
+			// Use m_model_matrix[Y] so that it hovers up and down in the model's
+			// coordinate system.
+			hover_offset=h*m_model_matrix[Y];
+		}
+		
+		// Render the models
+		bool show_models = (m_summed_props.alpha >= .0001f);
+		
+		// If running a replay, hide arrows that are pointing to something,
+		// otherwise they go all spazzy. (TT8083 and TT8819)
+		if ((m_point_type!=POINT_TYPE_NONE || m_parent_object_name) && Replay::RunningReplay())
+		{
+			show_models=false;
+		}	
+		
+
+		for (int i=0; i0.0f)
+			{
+				p+=hover_offset;
+			}
+				
+			display_matrix.SetPos(p);
+			
+			mp_models[i].mpModel->SetActive(show_models);
+			mp_models[i].mpModel->Render(&display_matrix,false,mp_models[i].mpSkeleton);
+		}
+	}
+}
+
+void CElement3d::SetMorph(Script::CStruct *pProps)
+{
+	CScreenElement::SetMorph(pProps);
+	if (pProps->ContainsFlag("scale_to_screen_dims"))
+		AutoComputeScale();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CElement3d::update_visibility()
+{
+	// Make visible for only this viewport
+	uint32 vis_mask = 1 << m_active_viewport_number;
+	for (int i=0; iSetVisibility(vis_mask);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CElement3d::s_apply_rotations(Mth::Matrix & mat, float x_rot, float y_rot, float z_rot, EAngleOrder rot_order)
+{
+	switch (rot_order)
+	{
+	case ANGLE_ORDER_XYZ:
+		mat.RotateX(x_rot);
+		mat.RotateY(y_rot);
+		mat.RotateZ(z_rot);
+		break;
+
+	case ANGLE_ORDER_XZY:
+		mat.RotateX(x_rot);
+		mat.RotateZ(z_rot);
+		mat.RotateY(y_rot);
+		break;
+
+	case ANGLE_ORDER_YXZ:
+		mat.RotateY(y_rot);
+		mat.RotateX(x_rot);
+		mat.RotateZ(z_rot);
+		break;
+
+	case ANGLE_ORDER_YZX:
+		mat.RotateY(y_rot);
+		mat.RotateZ(z_rot);
+		mat.RotateX(x_rot);
+		break;
+
+	case ANGLE_ORDER_ZXY:
+		mat.RotateZ(z_rot);
+		mat.RotateX(x_rot);
+		mat.RotateY(y_rot);
+		break;
+
+	case ANGLE_ORDER_ZYX:
+		mat.RotateZ(z_rot);
+		mat.RotateY(y_rot);
+		mat.RotateX(x_rot);
+		break;
+	}
+}
+
+}
diff --git a/Code/Gfx/2D/Element3d.h b/Code/Gfx/2D/Element3d.h
new file mode 100644
index 0000000..73bee5a
--- /dev/null
+++ b/Code/Gfx/2D/Element3d.h
@@ -0,0 +1,136 @@
+#ifndef __GFX_2D_ELEMENT3D_H__
+#define __GFX_2D_ELEMENT3D_H__
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+#include 
+
+namespace Gfx
+{
+    class CSkeleton;
+};
+                     
+namespace Nx
+{
+    class CModel;
+};
+
+namespace Script
+{
+	class CScript;
+	class CStruct;
+};
+
+namespace Front
+{
+
+#define MAX_MODELS_PER_ELEMENT 25
+
+struct SSubModel
+{
+	Nx::CModel	*mpModel;
+    Gfx::CSkeleton* mpSkeleton;
+	Mth::Vector mOffset;
+};
+	
+class CElement3d : public CScreenElement
+{
+	friend class CScreenElementManager;
+
+	SSubModel mp_models[MAX_MODELS_PER_ELEMENT];
+	int		m_num_models;
+	
+	float	m_camera_z;
+	float	m_angle_x;
+	float	m_angle_y;
+	float	m_angle_z;
+	float	m_angvel_x;
+	float	m_angvel_y;
+	float	m_angvel_z;
+
+	Mth::Matrix m_model_matrix;
+	
+	enum EPointType
+	{
+		POINT_TYPE_NONE,
+		POINT_TYPE_NODE,
+		POINT_TYPE_OBJECT,
+	};
+	Mth::Vector m_node_position_to_point_to;
+	EPointType m_point_type;
+	
+	enum EAngleOrder
+	{
+		ANGLE_ORDER_XYZ,
+		ANGLE_ORDER_XZY,
+		ANGLE_ORDER_YXZ,
+		ANGLE_ORDER_YZX,
+		ANGLE_ORDER_ZXY,
+		ANGLE_ORDER_ZYX,
+	};
+	EAngleOrder m_angle_order;
+
+	uint32 m_id_of_object_to_point_to;
+
+	float m_hover_amp;
+	int m_hover_period;
+
+	Mth::Vector m_pivot_point;				// Point in local space for rotations
+
+	// These following members are used when the element3d is associated with some 3d position.
+	// The 3d poistion can be got from an instance of a CObject, or from a fixed node.
+
+	// Offset from the parent position.
+	Mth::Vector m_parent_offset;
+		
+	Mth::Vector m_offset_of_origin_from_center; // origin - center
+
+	// different from 2D scale, is scale of 3D object
+	float		m_scale3d;
+
+	// These three are used for when the object needs to scale according to the distance from the camera.
+	bool 	m_scale_with_distance;
+	float 	m_scale_multiplier;
+	float 	m_max_scale;
+
+	int			m_active_viewport_number; // viewport to draw to
+	
+	uint32 m_parent_object_name;	// Non-zero if associated with an instance of a CObject
+	int m_skater_number; 			// Valid if m_parent_object_name is skater
+	uint32 m_parent_node_name;		// Non-zero if associated with a node.
+	
+	// Tilt value. Used for the arrow so that it does not go totally flat, which makes the
+	// direction it is pointing in hard to see. Units are degrees.
+	float m_tilt;
+	
+	// These is used to flag the element to be killed if its parent object dies.	
+	bool m_waiting_to_die;
+	
+public:
+				CElement3d();
+	virtual		~CElement3d();
+
+	void		SetProperties(Script::CStruct *p_props);
+	void		SetMorph(Script::CStruct *pProps);
+
+	void		UnloadModels();
+	void		AddModel(const char *p_model_name, uint32 skeleton_name, Mth::Vector offset, int texDictOffset);
+	void 		AddModelFromSector(uint32 sectorName, Mth::Vector offset);
+    Nx::CModel* CreateModel( uint32 skeleton_name, Mth::Vector offset );
+	void 		AutoComputeScale();
+	
+protected:
+
+	void		update();
+	void		update_visibility();
+    Gfx::CSkeleton* CreateSkeleton( Nx::CModel* pModel, uint32 skeletonChecksum );
+
+	static void	s_apply_rotations(Mth::Matrix & mat, float x_rot, float y_rot, float z_rot, EAngleOrder rot_order);
+};
+
+
+}
+
+#endif
+
diff --git a/Code/Gfx/2D/Menu2.cpp b/Code/Gfx/2D/Menu2.cpp
new file mode 100644
index 0000000..eba1d28
--- /dev/null
+++ b/Code/Gfx/2D/Menu2.cpp
@@ -0,0 +1,587 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Front
+{
+
+
+
+
+CBaseMenu::CBaseMenu()
+{
+    m_selected_index = -1;
+    m_selected_id = 0;
+	m_in_focus = false;
+	m_focus_controller = 0;
+
+	m_regular_space_val = 0.0f;
+	m_padding_scale = 1.0f;
+	m_spacing_between = 0.0f;
+
+	m_internal_just_x = 0.0f;
+	m_internal_just_y = 0.0f;
+	
+	m_pad_handling_enabled = true;
+	m_allow_wrap = true;
+
+	m_current_grid_index = -1;
+}
+
+
+
+
+CBaseMenu::~CBaseMenu()
+{
+}
+
+
+
+
+void CBaseMenu::SetProperties(Script::CStruct *pProps)
+{
+	float just[2] = { 0.0f, 0.0f };
+	if (resolve_just(pProps, CRCD(0x67e093e4,"internal_just"), just, just+1))
+		SetInternalJust(just[0], just[1]);
+	
+	int regular_space_val = 0;
+	if (pProps->GetInteger(CRCD(0xf24adedb,"regular_space_amount"), ®ular_space_val))
+		m_regular_space_val = (float) regular_space_val;
+	
+	pProps->GetFloat(CRCD(0x6d853b88,"padding_scale"), &m_padding_scale);
+	
+	int spacing_between = 0;
+	if (pProps->GetInteger(CRCD(0x4b4e14ab,"spacing_between"), &spacing_between))
+		m_spacing_between = (float) spacing_between;
+		
+	if (pProps->ContainsFlag(CRCD(0x4bd08bfe,"enable_pad_handling")))
+		m_pad_handling_enabled = true;
+	else if (pProps->ContainsFlag(CRCD(0xc22541cc,"disable_pad_handling")))
+		m_pad_handling_enabled = false;
+
+	if (pProps->ContainsFlag(CRCD(0x43a7692d,"allow_wrap")))
+		m_allow_wrap = true;
+	else if (pProps->ContainsFlag(CRCD(0x5dea5c64,"dont_allow_wrap")))
+		m_allow_wrap = false;
+	
+	CScreenElement::SetProperties(pProps);
+		
+	return;
+}
+
+
+
+
+bool CBaseMenu::PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast)
+{	
+	
+	#ifdef	__NOPT_ASSERT__												 
+	Obj::CSmtPtr p = this;
+	uint32	debug_id = m_id;
+	#endif		
+	if (!Obj::CObject::PassTargetedEvent(pEvent))
+	{
+		return false;
+	}	
+	Dbg_MsgAssert(p, ("BaseMenu (%s) deleted itself in call to PassTargetedEvent", Script::FindChecksumName(debug_id)));
+		
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+    
+	//printf("VMenu receives event of type %s\n", Script::FindChecksumName(pEvent->GetType()));
+	
+    if (pEvent->GetType() == Obj::CEvent::TYPE_NOTIFY_CHILD_LOCK)
+    {
+        // The currently selected child might have been removed. Make sure we update
+        // references and send new focus event if necessary
+        bool need_new_focus_event = false;
+        
+        if (m_selected_id) 
+        {
+            int current_child_index;
+            CScreenElementPtr p_current_child = GetChildById(m_selected_id, ¤t_child_index);
+            
+            // see if selected child has disappeared
+            if (!p_current_child) 
+            {
+                // child disappeared, see if another one at same index
+                CScreenElementPtr p_new_child = GetChildByIndex(m_selected_index);
+                if (p_new_child) 
+                {
+                    m_selected_id = p_new_child->GetID();
+					setup_tags();
+					need_new_focus_event = true;
+                }
+                else
+                {
+                    // couldn't locate replacement
+                    m_selected_index = -1;
+					// will set m_selected_id to 0 if no focusable item can be found
+					find_focusable_item(1, true, true);
+					need_new_focus_event = true;
+                }
+            } // end if
+            else
+            {
+                // selected child is still there, update indices
+                m_selected_index = current_child_index;
+				setup_tags();
+            }
+        } // end if (m_selected_id)
+		else
+		{
+            // nothing selected, so find something
+			find_focusable_item(1, true, true);
+			need_new_focus_event = true;
+		}
+        
+        if (need_new_focus_event && m_in_focus) 
+        {
+            find_focusable_item(1, true, true);
+            if (m_selected_id)
+			{
+				Script::CStruct data;
+				data.AddInteger(CRCD(0xb30d9965,"controller"), m_focus_controller);
+				p_tracker->LaunchEvent(Obj::CEvent::TYPE_FOCUS, m_selected_id, Obj::CEvent::vSYSTEM_EVENT, &data);
+			}
+        }
+
+		reposition();
+
+		pEvent->MarkHandled(m_id);
+    }
+	if (pEvent->GetType() == Obj::CEvent::TYPE_FOCUS && !m_in_focus)
+	{
+		m_focus_controller = Obj::CEvent::sExtractControllerIndex(pEvent);
+		
+		//if (!m_is_vertical_menu)
+		//	printf("Menu %s receives focus event, sending to 0x%x\n", Script::FindChecksumName(m_id), m_selected_id);
+
+		// see if event has a child-to-select component
+		int desired_grid_index = -1;
+		if (pEvent->GetData() && pEvent->GetData()->GetInteger(CRCD(0xfacf9a8b,"grid_index"), &desired_grid_index))
+		{
+			// find the child that matches that grid index, if any
+			int match_index = -1;
+
+			m_current_grid_index = desired_grid_index;
+			
+			CScreenElementPtr p_child = mp_child_list;
+			int current_index = 0;
+			while(p_child)
+			{
+				int found_grid_index = -1;
+				if (p_child->GetIntegerTag(CRCD(0x5b92f8dd, "tag_grid_x"), &found_grid_index))
+				{
+					if (found_grid_index <= desired_grid_index)
+						match_index = current_index;
+				}
+				p_child = p_child->GetNextSibling();
+				current_index++;
+			} 
+			
+			if (match_index != -1)
+			{
+				m_selected_index = match_index;
+				m_selected_id = 1;
+			}
+		}
+
+		// see if the event has a child_id component 
+		uint32 desired_child_id;
+		if ( pEvent->GetData() && pEvent->GetData()->GetChecksum( CRCD(0x229d3de4,"child_id"), &desired_child_id ) )
+		{
+			// printf("got a desired child_id of %x\n", desired_child_id);			
+			// CScreenElementPtr p_child = GetChildById( desired_child_id );
+			CScreenElementPtr p_child = mp_child_list;
+
+			int current_index = 0;
+			while ( p_child )
+			{
+				if ( desired_child_id == p_child->GetID() )
+				{
+					m_selected_id = desired_child_id;
+					m_selected_index = current_index;
+					break;
+				}
+				current_index++;
+				p_child = p_child->GetNextSibling();
+			}
+		}
+
+		m_in_focus = true;
+		if (m_selected_id) 
+		{
+			find_focusable_item(1, true, false);
+            if (m_selected_id)
+				p_tracker->LaunchEvent(Obj::CEvent::TYPE_FOCUS, m_selected_id, Obj::CEvent::vSYSTEM_EVENT, pEvent->GetData());
+		}
+		pEvent->MarkHandled(m_id);
+	}
+	if (pEvent->GetType() == Obj::CEvent::TYPE_UNFOCUS && m_in_focus)
+	{
+		//printf("VMenu %s receives unfocus event\n", Script::FindChecksumName(m_id));
+		m_in_focus = false;
+		if (m_selected_id) 
+			p_tracker->LaunchEvent(Obj::CEvent::TYPE_UNFOCUS, m_selected_id);
+		pEvent->MarkHandled(m_id);
+	}
+	if (((pEvent->GetType() == Obj::CEvent::TYPE_PAD_UP && m_is_vertical_menu) ||
+		 (pEvent->GetType() == Obj::CEvent::TYPE_PAD_LEFT && !m_is_vertical_menu)) && 
+		m_in_focus && m_pad_handling_enabled) 
+	{
+		change_selection(-1);
+		pEvent->MarkHandled(m_id);
+	}
+	if (((pEvent->GetType() == Obj::CEvent::TYPE_PAD_DOWN && m_is_vertical_menu) ||
+		 (pEvent->GetType() == Obj::CEvent::TYPE_PAD_RIGHT && !m_is_vertical_menu)) && 
+		m_in_focus && m_pad_handling_enabled) 
+	{
+		change_selection(1);
+		pEvent->MarkHandled(m_id);
+	}
+	return true;
+}
+
+
+
+
+bool CBaseMenu::UsingRegularSpacing(float &rRegularSpaceAmount)
+{
+	rRegularSpaceAmount = m_regular_space_val;
+	return (m_regular_space_val > 0.0001f);
+}
+
+
+
+
+void CBaseMenu::find_focusable_item(int dir, bool include_current, bool updateGridIndex)
+{
+	int count = CountChildren() * 2;
+
+	if (m_selected_index < 0)
+		m_selected_index = 0;
+	
+	while(count)
+	{
+		if (!include_current)
+		{
+			m_selected_index += dir;
+			if (m_selected_index >= CountChildren())
+			{
+				if (m_allow_wrap)
+					m_selected_index = 0;
+				else
+				{
+					// go back to previous item, and reverse direction
+					dir = -dir;
+					m_selected_index = CountChildren() - 1;
+				}
+			}
+			else if (m_selected_index < 0)
+			{
+				if (m_allow_wrap)
+					m_selected_index = CountChildren() - 1;
+				else
+				{
+					// go back to previous item, and reverse direction
+					dir = -dir;
+					m_selected_index = 0;
+				}
+			}
+		}
+		include_current = false;
+
+		CScreenElementPtr p_new_child = GetChildByIndex(m_selected_index);
+		Dbg_Assert(p_new_child);
+		m_selected_id = p_new_child->GetID(); 
+		
+		if (updateGridIndex)
+		{
+			// update grid index that we have stored for selected child
+			m_current_grid_index = -1;
+			p_new_child->GetIntegerTag(CRCD(0x5b92f8dd, "tag_grid_x"), &m_current_grid_index);
+		}
+
+		if ( !p_new_child->ContainsFlagTag(CRCD(0xf33a3321, "tag_not_focusable")) && 
+			 !p_new_child->EventsBlocked() )
+			break;
+		
+		count--;
+	}
+	
+	if (count)
+	{
+		setup_tags();
+	}
+	else
+	{
+		// nothing selected
+		m_selected_index = -1;
+		m_selected_id = 0;
+	}
+}
+
+
+
+
+void CBaseMenu::change_selection(int dir)
+{
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	
+	// if the child is also	a menu, it will have a selected index
+	int grid_index_within_child	= -1;
+	
+	if (m_selected_id) 
+	{
+		CScreenElementManager* p_manager = CScreenElementManager::Instance();
+		
+		// figure out which of selected child's children is selected
+		CScreenElement *p_child = p_manager->GetElement(m_selected_id);
+		// This assert is causing an occasional crash. I can't figure out the problem, so 
+		// here is the next best solution.
+		//Dbg_MsgAssert(p_child, ("Couldn't find selected child %s", Script::FindChecksumName(m_selected_id)));
+		if (p_child)
+			p_child->GetIntegerTag(CRCD(0x8321dd71, "tag_selected_childs_grid_index"), &grid_index_within_child);
+
+		p_tracker->LaunchEvent(Obj::CEvent::TYPE_UNFOCUS, m_selected_id);
+	}
+
+	find_focusable_item(dir, false, true);
+
+	if (m_selected_id) 
+	{
+		Script::CStruct data;
+		data.AddInteger("controller", m_focus_controller);
+
+		if (grid_index_within_child != -1)
+		{
+			data.AddInteger("grid_index", grid_index_within_child);
+			//printf("child index is %d\n", selected_index_of_child);
+		}
+
+		p_tracker->LaunchEvent(Obj::CEvent::TYPE_FOCUS, m_selected_id, Obj::CEvent::vSYSTEM_EVENT, &data);
+	}
+}
+
+
+
+
+void CBaseMenu::setup_tags()
+{
+	SetChecksumTag(CRCD(0xac988ebe, "tag_selected_id"), m_selected_id);
+	SetIntegerTag(CRCD(0xf2239871, "tag_selected_index"), m_selected_index);
+	//printf("setup_tags(), %s\n", Script::FindChecksumName(m_id));
+
+	SetIntegerTag(CRCD(0x8321dd71, "tag_selected_childs_grid_index"), m_current_grid_index);
+}
+
+
+
+
+void CBaseMenu::reposition() 
+{
+	float z = 0.0f; // functions as a virtual x or y
+	float biggest = 0.0f; // functions as virtual 'widest' or 'highest'
+
+	bool use_regular_space = (m_regular_space_val > 0.0001f);
+	
+	if (use_regular_space)
+	{
+		// do first pass to find out biggest element, total height of all elements
+		CScreenElementPtr p_child = mp_child_list;
+		while(p_child)
+		{
+			// virtual dimensions, 'k' in the direction that menu items are added (i.e. of 'z')
+			float scaled_j;
+					
+			if (m_is_vertical_menu)
+				scaled_j = p_child->GetBaseW() * p_child->GetScaleX();
+			else
+				scaled_j = p_child->GetBaseH() * p_child->GetScaleY();
+			
+			z += m_regular_space_val;
+			
+			if (scaled_j > biggest)
+				biggest = scaled_j;
+			
+			p_child = p_child->GetNextSibling();
+		}
+	}
+	else
+	{
+		// do first pass to find out biggest element, total height of all elements
+		CScreenElementPtr p_child = mp_child_list;
+		while(p_child)
+		{
+			// virtual dimensions, 'k' in the direction that menu items are added (i.e. of 'z')
+			float scaled_j, scaled_k;
+					
+			if (m_is_vertical_menu)
+			{
+				scaled_j = p_child->GetBaseW() * m_padding_scale;
+				scaled_k = p_child->GetBaseH() * m_padding_scale;
+			}
+			else
+			{
+				scaled_j = p_child->GetBaseH() * m_padding_scale;
+				scaled_k = p_child->GetBaseW() * m_padding_scale;
+			}
+			
+			z += scaled_k + m_spacing_between;
+			
+			if (scaled_j > biggest)
+				biggest = scaled_j;
+			
+			p_child = p_child->GetNextSibling();
+		} 
+		z -= m_spacing_between;
+	}
+		
+	if ((m_object_flags & vFORCED_DIMS)) 
+	{
+		// "grow" the menu in case of overflow (disobeying rule of above flag)
+		if (m_is_vertical_menu && z > m_base_h) 
+			m_base_h = z;
+		else if (!m_is_vertical_menu && z > m_base_w) 
+			m_base_w = z;
+	}
+	else
+	{
+		// this element is in control of own dimensions
+		if (m_is_vertical_menu)
+		{
+			m_base_w = biggest;
+			m_base_h = z;
+		}
+		else
+		{
+			m_base_w = z;
+			m_base_h = biggest;
+		}
+	}
+	compute_ul_pos(m_target_local_props);
+	m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	
+		
+	// use vertical	justification to figure out starting position
+	if (m_is_vertical_menu)
+		z = (m_internal_just_y + 1.0f) * (m_base_h - z) / 2.0f;
+	else
+		z = (m_internal_just_x + 1.0f) * (m_base_w - z) / 2.0f;
+			
+	if (use_regular_space)
+	{
+		z += m_regular_space_val / 2.0f;
+		
+		// now, set positions of everything
+		CScreenElementPtr p_child = mp_child_list;
+		while(p_child)
+		{
+			if (m_is_vertical_menu)
+			{
+				p_child->SetJust(m_internal_just_x, 0.0f);
+				p_child->SetPos((m_internal_just_x + 1.0f) * m_base_w / 2.0f, z, FORCE_INSTANT);
+			}
+			else
+			{
+				p_child->SetJust(0.0f, m_internal_just_y);
+				p_child->SetPos(z, (m_internal_just_y + 1.0f) * m_base_h / 2.0f, FORCE_INSTANT);
+			}
+			
+			z += m_regular_space_val;
+			p_child = p_child->GetNextSibling();
+		}
+	}
+	else // if !use_regular_space
+	{
+		// now, set positions of everything
+		CScreenElementPtr p_child = mp_child_list;
+		while(p_child)
+		{
+			if (m_is_vertical_menu)
+			{
+				float inc_value = p_child->GetBaseH() * m_padding_scale / 2.0f;
+				z += inc_value;
+				
+				p_child->SetJust(m_internal_just_x, 0.0f);
+				p_child->SetPos((m_internal_just_x + 1.0f) * m_base_w / 2.0f, z, FORCE_INSTANT);
+				
+				z += inc_value;
+			}
+			else
+			{
+				float inc_value = p_child->GetBaseW() * m_padding_scale / 2.0f;
+				z += inc_value;
+				
+				p_child->SetJust(0.0f, m_internal_just_y);
+				p_child->SetPos(z, (m_internal_just_y + 1.0f) * m_base_h / 2.0f, FORCE_INSTANT);
+				
+				z += inc_value;
+			}
+			z += m_spacing_between;
+			
+			if (p_child->GetFlags() & vNEEDS_LOCAL_POS_CALC)
+				m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+			
+			p_child = p_child->GetNextSibling();
+		}
+	}
+    
+	if (!m_selected_id && mp_child_list && m_in_focus) 
+    {
+        m_selected_index = 0;
+        find_focusable_item(1, true, true);
+
+        if (m_selected_id)
+		{
+			Script::CStruct data;
+			data.AddInteger("controller", m_focus_controller);
+			Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+			p_tracker->LaunchEvent(Obj::CEvent::TYPE_FOCUS, m_selected_id, Obj::CEvent::vSYSTEM_EVENT, &data);
+		}
+    }
+}
+
+
+
+
+CVMenu::CVMenu()
+{
+	m_is_vertical_menu = true;
+	SetType(CScreenElement::TYPE_VMENU);
+}
+
+
+
+
+CVMenu::~CVMenu()
+{
+}
+
+
+
+
+CHMenu::CHMenu()
+{
+	m_is_vertical_menu = false;
+	
+	SetType(CScreenElement::TYPE_HMENU);
+}
+
+
+
+
+CHMenu::~CHMenu()
+{
+}
+
+
+
+
+}
diff --git a/Code/Gfx/2D/Menu2.h b/Code/Gfx/2D/Menu2.h
new file mode 100644
index 0000000..894bdcd
--- /dev/null
+++ b/Code/Gfx/2D/Menu2.h
@@ -0,0 +1,87 @@
+#ifndef __GFX_2D_MENU_H__
+#define __GFX_2D_MENU_H__
+
+#include 
+
+namespace Front
+{
+
+/*
+	A base menu is an.
+*/
+class CBaseMenu : public Front::CScreenElement
+{
+	friend class CVScrollingMenu;
+
+public:
+
+							CBaseMenu();
+	virtual					~CBaseMenu();
+
+	void					SetInternalJust(float h, float v) {m_internal_just_x = h; m_internal_just_y = v;}	
+	
+	virtual void			SetProperties(Script::CStruct *pProps);
+	virtual bool			PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast = false);
+
+	int						GetSelectedIndex() { return m_selected_index; }
+
+	bool					UsingRegularSpacing(float &rRegularSpaceAmount);
+
+protected:
+
+	void					find_focusable_item(int dir, bool include_current, bool updateGridIndex);
+	void					change_selection(int dir);
+	void					setup_tags();
+	void					reposition();
+
+	// both m_selected_index and m_selected_id will have matching, valid settings,
+	// or both will be invalid (-1, 0)
+	int                     m_selected_index;
+    uint32                  m_selected_id;
+	bool					m_in_focus;
+	int						m_focus_controller; // 0 or 1
+
+	float					m_internal_just_x, m_internal_just_y;
+	bool 					m_is_vertical_menu;
+
+	float					m_regular_space_val;
+	float					m_padding_scale;
+	float					m_spacing_between;
+
+	bool					m_pad_handling_enabled;
+	bool					m_allow_wrap;
+
+	int						m_current_grid_index; // of selected item, set to -1 when not applicable
+												  // not necessarily the same as the grid index stored
+												  // in the child that's selected (for "remembering"
+												  // the column)
+};
+
+
+
+
+class CVMenu : public CBaseMenu
+{
+public:
+
+							CVMenu();
+	virtual					~CVMenu();
+};
+
+
+
+
+class CHMenu : public CBaseMenu
+{
+public:
+
+							CHMenu();
+	virtual					~CHMenu();
+};
+
+
+
+
+}
+
+#endif
diff --git a/Code/Gfx/2D/ScreenElemMan.cpp b/Code/Gfx/2D/ScreenElemMan.cpp
new file mode 100644
index 0000000..36d5ab6
--- /dev/null
+++ b/Code/Gfx/2D/ScreenElemMan.cpp
@@ -0,0 +1,1453 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+                                          
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+// start autoduck documentation
+// @DOC ScreenElemMan
+// @module ScreenElemMan | None
+// @subindex Scripting Database
+// @index script | ScreenElemMan
+
+namespace Front
+{
+
+
+	
+DefineSingletonClass(CScreenElementManager, "Screen Element Manager");
+
+
+
+
+CScreenElementManager::CScreenElementManager()
+{
+	mp_root_element = NULL;
+	mp_resolve_temp = NULL;
+
+	// register event listener
+	RegisterWithTracker(NULL);
+
+	for (int i = 0; i < NUM_FOCUS_LISTS; i++)
+	{
+		mp_focus_list[i] = NULL;
+		m_focus_list_changed[i] = false;
+	}
+	for (int i = 0; i < NUM_FOCUS_NODES; i++)
+	{
+		m_focus_node_pool[i].mpElement = NULL;
+		m_focus_node_pool[i].mpNextNode = NULL;
+	}
+
+	m_num_pad_event_types = 0;
+	for (int i = 0; i < MAX_PAD_EVENT_TYPES; i++)
+		m_pad_event_type_tab[i] = 0;
+}
+
+
+
+
+CScreenElementManager::~CScreenElementManager()
+{
+	Dbg_MsgAssert(!m_object_list.CountItems(), ("items still in CScreenElementManager"));
+}
+
+
+
+
+/*
+	Set 'id' to Obj::CBaseManager::vNO_OBJECT_ID if object to receive automatic ID
+*/
+CScreenElementPtr CScreenElementManager::CreateElement(uint32 type, uint32 id, Script::CScriptStructure *pProps)
+{
+	CScreenElementPtr p_new_element = NULL;
+	
+	uint32 heap_crc;
+	int heap_num = 0;
+	bool switched = false;
+	if (pProps->GetChecksum("heap", &heap_crc))
+	{
+		switch ( heap_crc )
+		{
+			case 0x477fc6de:		// topdown
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+				switched = true;
+				break;
+			case 0xc80bf12d:		// bottomup 
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+				switched = true;
+				break;
+			case 0xe37e78c5:		// script
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+				switched = true;
+				break;
+			case 0x9f7b7843:		// network
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+				switched = true;
+				break;
+			case 0x03c84a59:		// profiler
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ProfilerHeap());
+				switched = true;
+				break;
+			case 0x935ab858:		// debug
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+				switched = true;
+				break;
+			case 0x5b8ab877:		// skater
+				pProps->GetInteger("heapnum", &heap_num);
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterHeap(heap_num));
+				switched = true;
+				break;
+			case 0xeabd217b:		// skaterinfo
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+				switched = true;
+				break;
+			case 0x39fb63cc:		// skatergeom
+				pProps->GetInteger("heapnum", &heap_num);
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterGeomHeap(heap_num));
+				switched = true;
+				break;
+			case 0xe3f81b18:		// internettopdown
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+				switched = true;
+				break;
+			case 0xbaa81175:		// internetbottomup
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+				switched = true;
+				break;
+			case 0x1ca1ff20:		// default (ie don't change context).
+				break;
+			default:	// Default = frontend
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+				switched = true;
+				break;
+		}
+	}
+	else
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		switched = true;
+	}
+
+	switch(type)
+	{
+		case CScreenElement::TYPE_CONTAINER_ELEMENT:
+			p_new_element = new CContainerElement();
+			break;
+		case CScreenElement::TYPE_TEXT_ELEMENT:
+			p_new_element = new CTextElement();
+			break;
+		case CScreenElement::TYPE_VMENU:
+  			p_new_element = new CVMenu();
+			break;
+		case CScreenElement::TYPE_HMENU:
+  			p_new_element = new CHMenu();
+			break;
+		case CScreenElement::TYPE_TEXT_BLOCK_ELEMENT:
+  			p_new_element = new CTextBlockElement();
+			break;
+		case CScreenElement::TYPE_SPRITE_ELEMENT:
+  			p_new_element = new CSpriteElement();
+			break;
+		case CScreenElement::TYPE_VSCROLLING_MENU:
+  			p_new_element = new CVScrollingMenu();
+			break;
+		case CScreenElement::TYPE_HSCROLLING_MENU:
+  			p_new_element = new CHScrollingMenu();
+			break;
+		case CScreenElement::TYPE_ELEMENT_3D:
+			p_new_element = new CElement3d();
+			break;
+		case CScreenElement::TYPE_WINDOW_ELEMENT:
+			p_new_element = new CWindowElement();
+			break;
+		default:
+			Dbg_MsgAssert(0, ("unknown element type 0x%x", type));
+			break;
+	}
+
+	p_new_element->SetID(id);
+	RegisterObject(*p_new_element);
+	
+	p_new_element->SetProperties(pProps);
+	p_new_element->SetMorph(pProps);
+	
+	if ( switched ) Mem::Manager::sHandle().PopContext();
+	
+	
+	return p_new_element;
+}
+
+
+
+
+void CScreenElementManager::DestroyElement(uint32 id, ERecurse recurse, EPreserveParent preserveParent, Script::CScript *pCallingScript)
+{
+
+	
+	CScreenElementPtr p_element = GetElement(id, CScreenElementManager::ASSERT);
+	Dbg_Assert(p_element);
+	if (recurse)
+	{
+		mark_element_out_of_focus(p_element);
+		if (p_element->mp_parent)
+			p_element->mp_parent->SetChildLockState(CScreenElement::UNLOCK);
+		destroy_element_recursive(preserveParent, p_element, pCallingScript);
+	}
+	else
+	{
+		mark_element_out_of_focus(p_element);
+		if (pCallingScript && p_element)
+			// must disassociate script from element being destroyed
+			pCallingScript->DisassociateWithObject(p_element);
+		
+		UnregisterObject(*p_element);
+		#ifdef	__NOPT_ASSERT__	
+		// Mick:  screen elements are deleted directly, so LockAssert is not applicable.
+		p_element->SetLockAssertOff();
+		#endif
+		p_element.Kill();
+	}
+}
+
+
+
+
+/*
+	Passing in pParent = NULL will give the child no parent
+*/
+void CScreenElementManager::SetParent(const CScreenElementPtr &pParent, const CScreenElementPtr &pChild, CScreenElement::EPosRecalc recalculatePosition)
+{
+	Dbg_Assert(pChild);
+
+	CScreenElementPtr p_current_parent = pChild->mp_parent;
+	if (p_current_parent)	
+		Dbg_MsgAssert(!(p_current_parent->m_object_flags & CScreenElement::vCHILD_LOCK), ("can't remove child -- locked"));
+	
+	// if child was root element, then seek out	a new root
+	if (pParent && (mp_root_element == pChild || !mp_root_element))
+	{
+		mp_root_element = pParent;
+		while(mp_root_element->mp_parent)
+			mp_root_element = mp_root_element->mp_parent;
+	}
+
+	mark_element_out_of_focus(pChild);
+	
+	pChild->set_parent(pParent, recalculatePosition);
+}
+
+
+
+
+CScreenElementPtr CScreenElementManager::GetElement(uint32 id, EAssert assert)
+{
+	Dbg_MsgAssert(id > 0, ("can't use 0 as an ID"));
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	Obj::CObject *p_object = p_tracker->GetObject(id);
+	if (assert)
+		Dbg_MsgAssert(p_object, ("couldn't find screen element %s", Script::FindChecksumName(id)));
+	if (p_object)
+	{
+		CScreenElementPtr p_element = static_cast(p_object);
+		Dbg_MsgAssert(p_element, ("%s not a screen element", Script::FindChecksumName(id)));
+		return p_element;
+	}
+	else
+		return NULL;
+}
+
+
+
+
+CScreenElementPtr CScreenElementManager::GetElement(Script::CStruct *pStructContainingId, uint32 IdSubStructName, EAssert assert)
+{
+	uint32 id = ResolveComplexID(pStructContainingId, IdSubStructName);
+	#ifdef __NOPT_ASSERT__
+	if ( assert )
+	{
+		if ( !id )
+			Script::PrintContents(pStructContainingId, 2);
+		Dbg_MsgAssert(id, ("could not resolve ID %s", Script::FindChecksumName(IdSubStructName)));
+	}
+	#endif
+	if ( id )
+	{
+		return GetElement(id, assert);
+	}
+	return NULL;
+}
+
+
+CScreenElementPtr CScreenElementManager::GetElement(Script::CStruct *pStructContainingId, char *pIdSubStructName, EAssert assert)
+{
+	return GetElement(pStructContainingId,Script::GenerateCRC(pIdSubStructName),ASSERT);
+}
+
+
+
+/*
+	No screen element can be destroyed during this phase
+*/
+void CScreenElementManager::Update()
+{
+	if (mp_root_element)
+	{
+		set_tree_lock_state(CScreenElement::LOCK);
+		
+		//Ryan("in CScreenElementManager::Update(), timer is %d\n", Tmr::GetTime());
+		mp_root_element->UpdateProperties();
+		
+		set_tree_lock_state(CScreenElement::UNLOCK);
+	}
+}
+
+
+
+
+void CScreenElementManager::SetPausedState(bool pause)
+{
+	// find a new parentless element to be root element
+	Lst::Node *p_node = m_object_list.FirstItem();
+	while(p_node)
+	{
+		CScreenElementPtr p_element = static_cast(p_node->GetData());
+		Dbg_Assert(p_element);
+		p_element->SetMorphPausedState(pause);
+		p_node = p_node->GetNext();
+	}
+}
+
+
+
+
+// locks/unlocks all the screen elements in the tree so they can't/can be deleted without an assert
+void CScreenElementManager::set_tree_lock_state(CScreenElement::ELockState state)
+{
+	if (mp_root_element)
+	{
+		CScreenElement* p_stack[32];
+		p_stack[0] = mp_root_element;
+		int depth = 1;
+		
+		while(depth)
+		{
+			// pop top value off stack
+			CScreenElement* p_node = p_stack[--depth];
+
+			#ifdef __NOPT_ASSERT__
+			p_node->debug_verify_integrity();
+			#endif
+			
+			// set its lock state
+			if (state == CScreenElement::LOCK)
+				p_node->AddReference();
+			else
+			{
+				// this	element may have been ADDED since the call to set_tree_lock_state(LOCK),
+				// so we can't depend on it being referenced
+				if (p_node->IsReferenced())
+					p_node->RemoveReference();
+			}
+			
+			Dbg_Assert(depth <= 30);
+			
+			// put sibling and child on stack
+			if (p_node->GetNextSibling())
+				p_stack[depth++] = p_node->GetNextSibling();
+			if (p_node->GetFirstChild())
+				p_stack[depth++] = p_node->GetFirstChild();
+		}
+	}
+}
+
+
+// Optimization - as event handling become more widespread
+// this should only be called on the type of events that 
+// it actually handles, which is basically the pad_event_types
+
+void CScreenElementManager::pass_event_to_listener(Obj::CEvent *pEvent)
+{
+	// Fill in the array of pad event types by copying it from the global "pad_event_types" script array
+	if (!m_num_pad_event_types)
+	{
+		Script::CArray *p_event_type_array = Script::GetArray("pad_event_types", Script::ASSERT);
+		m_num_pad_event_types = p_event_type_array->GetSize();
+		Dbg_MsgAssert(m_num_pad_event_types <= MAX_PAD_EVENT_TYPES, ("increase size of MAX_PAD_EVENT_TYPES"));
+		for (int i = 0; i < m_num_pad_event_types; i++)
+		{
+			m_pad_event_type_tab[i] = p_event_type_array->GetChecksum(i);
+		}
+	}
+
+	// check that the controller is bound to a skater
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	if ( !skate_mod->IsMultiplayerGame() )
+	{
+		if ( skate_mod->m_requested_level != CRCD( 0x9f2bafb7, "load_skateshop" ) )
+		{
+			CScreenElementPtr p_element = GetElement( CRCD(0x21f1f4a,"startup_menu"), CScreenElementManager::DONT_ASSERT);
+			if ( !p_element )
+			{
+				int device_num;
+				Script::CStruct* pEventData = pEvent->GetData();
+				if ( pEventData && pEventData->GetInteger( CRCD(0xc9428a08,"device_num"), &device_num, Script::NO_ASSERT ) )
+				{
+					if ( skate_mod->m_device_server_map[0] != device_num )
+					{
+						// this controller isn't bound to the skater!
+						return;
+					}
+				}
+			}
+		}
+	}
+	
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+		
+	if (pEvent->GetType() == Obj::CEvent::TYPE_FOCUS) 
+	{
+		uint32 focus_id = pEvent->GetTarget();
+		// HACK: this assert should be there, but was removed to force a last-minute fix
+		//Dbg_MsgAssert(focus_id != Obj::CEvent::vSYSTEM_EVENT, ("focus event needs specific target"));
+		if (focus_id != Obj::CEvent::vSYSTEM_EVENT)
+		{		
+			CScreenElementPtr p_focus_element = GetElement(focus_id);
+			// HACK: see above
+			//Dbg_MsgAssert(p_focus_element, ("focus screen element doesn't exist"));
+			if ( p_focus_element && !p_focus_element->EventsBlocked() )
+			{
+				int controller = Obj::CEvent::sExtractControllerIndex(pEvent);
+		
+				mark_element_in_focus(p_focus_element, controller);
+				
+				pEvent->MarkRead(Obj::CTracker::vID_SCREEN_ELEMENT_MANAGER);
+			}
+		}
+	}
+	if (pEvent->GetType() == Obj::CEvent::TYPE_UNFOCUS)
+	{
+		uint32 unfocus_id = pEvent->GetTarget();
+		// HACK: this assert should be there, but was removed to force a last-minute fix
+		//Dbg_MsgAssert(unfocus_id != Obj::CEvent::vSYSTEM_EVENT, ("unfocus event needs specific target"));
+		if (unfocus_id != Obj::CEvent::vSYSTEM_EVENT)
+		{		
+			CScreenElementPtr p_unfocus_element = GetElement(unfocus_id);
+			// HACK: see above
+			//Dbg_MsgAssert(p_unfocus_element, ("unfocus screen element %s doesn't exist", Script::FindChecksumName(unfocus_id)));
+			if ( p_unfocus_element && !p_unfocus_element->EventsBlocked() )
+			{
+				mark_element_out_of_focus(p_unfocus_element);
+				
+				pEvent->MarkRead(Obj::CTracker::vID_SCREEN_ELEMENT_MANAGER);
+			}
+		}
+	}
+	if (pEvent->GetType() == Obj::CEvent::TYPE_NOTIFY_CHILD_UNLOCK)
+	{
+		uint32 unlock_id = pEvent->GetTarget();
+		CScreenElementPtr p_unlocked_element = GetElement(unlock_id);
+		Dbg_MsgAssert(p_unlocked_element, ("unlock screen element %s doesn't exist", Script::FindChecksumName(unlock_id)));
+		
+		// mark all descendants TEMPORARILY out of focus
+		mark_element_out_of_focus(p_unlocked_element, true, true);
+		
+		pEvent->MarkRead(Obj::CTracker::vID_SCREEN_ELEMENT_MANAGER);
+	}
+	if (pEvent->GetType() == Obj::CEvent::TYPE_NOTIFY_CHILD_LOCK)
+	{
+		uint32 lock_id = pEvent->GetTarget();
+		CScreenElementPtr p_last_focus_element = GetElement(lock_id);
+		Dbg_MsgAssert(p_last_focus_element, ("unlock screen element %s doesn't exist", Script::FindChecksumName(lock_id)));
+		
+		// restore children of this element to focus
+		remark_temporarily_out_of_focus_elements(p_last_focus_element);
+		
+		pEvent->MarkRead(Obj::CTracker::vID_SCREEN_ELEMENT_MANAGER);
+	}
+	if (is_pad_event(pEvent->GetType()) &&
+		pEvent->GetTarget() == Obj::CEvent::vSYSTEM_EVENT) 
+	{
+		bool successful_handling = false;
+		
+		int controller = Obj::CEvent::sExtractControllerIndex(pEvent);
+		
+		// forward pad events (of global type) to elements in focus 		
+		FocusNode *p_node = mp_focus_list[controller];
+		while(p_node)
+		{
+			p_node->mProcessed = false;
+			p_node = p_node->mpNextNode;
+		}
+		  
+		m_focus_list_changed[controller] = true;
+		while(m_focus_list_changed[controller])
+		{
+			m_focus_list_changed[controller] = false;
+			p_node = mp_focus_list[controller];
+			while(p_node)
+			{
+				test_focus_node(p_node);
+				
+				if ( !p_node->mProcessed && !p_node->mTempOutOfFocus && !p_node->mpElement->EventsBlocked() )
+				{
+					Dbg_MsgAssert(p_node->mpElement,("Node in focus list has NULL mpElement, entry = %d", (int) (p_node - m_focus_node_pool)));
+					//Ryan("   sending pad event %d to %s\n", controller, Script::FindChecksumName(p_node->mpElement->GetID()));
+					if (p_tracker->LaunchEvent(pEvent->GetType(), p_node->mpElement->GetID(), pEvent->GetSource(), pEvent->GetData()))
+					{
+						successful_handling = true;
+						break;
+					}
+					p_node->mProcessed = true;
+				}
+
+				// the event just sent may have led to the clearing of this	node (and maybe others) -- if 
+				// that happens, start again
+				if (m_focus_list_changed[controller])
+					break;
+				
+				p_node = p_node->mpNextNode;
+			} // end while
+			
+			if (successful_handling)
+				break;
+		} // end while
+		
+		if (successful_handling)
+			pEvent->MarkHandled(Obj::CTracker::vID_SCREEN_ELEMENT_MANAGER);
+	}
+}
+
+
+
+
+bool CScreenElementManager::IsComplexID(Script::CStruct *pStructContainingId, char *pIdSubStructName)
+{
+	Script::CStruct *p_recurse_struct = NULL;	
+	return pStructContainingId->GetStructure(pIdSubStructName, &p_recurse_struct);
+}
+
+
+
+
+/*
+	This function takes a complex ID and reduces it to a regular checksum that designates a single screen
+	element. The following are examples of complex ID's:
+	
+	id=blah1						<-- also a regular ID
+	id={blah1 child=0}				<-- first child of blah1
+	id=GetStructure(IdSubStructName, &p_recurse_struct))
+	{
+		// Expecting the form something={...}
+		// May be recursively inside some other ID structure
+		
+		// grab the 'x' part of structure, where x is something={id ...}
+		
+		// grab the ID part of something={id ...}
+		// we expect either this or a child index (see below)
+		// 
+		uint32 contained_id = 0;
+		p_recurse_struct->GetChecksum(NONAME, &contained_id);
+
+		// grab	the child index part of something={child_index ...}
+		int child_index = -1;
+		p_recurse_struct->GetInteger(NONAME, &child_index);
+		
+		if (contained_id != 0)
+		{
+			if (!mp_resolve_temp)
+				// we're at the top level of the recursion chain
+				mp_resolve_temp = GetElement(contained_id);
+			else
+			{
+				// we're NOT at the top level of the recursion chain, so treat ID as
+				// a LOCAL id
+				mp_resolve_temp = get_element_by_local_id(mp_resolve_temp, contained_id);
+			}
+			if (!mp_resolve_temp)
+				return 0;
+		}
+		else if (child_index != -1) 
+		{
+			Dbg_MsgAssert(mp_resolve_temp, ("can't map child %d of %s to anything, no parent", child_index, Script::FindChecksumName(IdSubStructName)));
+			mp_resolve_temp = mp_resolve_temp->GetChildByIndex(child_index);
+			if (!mp_resolve_temp)
+				return 0;
+		}
+		else
+		{
+			#ifdef	__NOPT_ASSERT__
+			Script::PrintContents(pStructContainingId);
+			Dbg_MsgAssert(0, ("can't resolve complex ID %s, no ID or index given.  See struct above.", Script::FindChecksumName(IdSubStructName)));
+			#endif
+		}
+		// we can expect to recurse further
+		uint32 result_id = ResolveComplexID(p_recurse_struct, CRCD(0xdd4cabd6,"child"));
+		mp_resolve_temp = NULL;
+
+		return result_id;
+	}
+	else if (pStructContainingId->GetChecksum(IdSubStructName, &id))
+	{
+		// Expecting the form something=some_checksum
+		// May be recursively inside some other ID structure
+		
+		if (mp_resolve_temp)
+		{
+			// we're NOT at the top level of the recursion chain, so treat ID as
+			// a LOCAL id
+			mp_resolve_temp = get_element_by_local_id(mp_resolve_temp, id);
+			id = mp_resolve_temp->GetID();
+		}
+		else
+		{
+			// see if ID is really an alias
+			Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+			Obj::CObject *p_aliased_obj = p_tracker->GetObjectByAlias(id);
+			if (p_aliased_obj)
+				// get real ID
+				id = p_aliased_obj->GetID();
+		}
+		
+		mp_resolve_temp = NULL;
+		return id;
+	}
+	else if (pStructContainingId->GetInteger(IdSubStructName, &index))
+	{
+		// Expecting the form something=child_index
+		// May be recursively inside some other ID structure
+		
+		Dbg_MsgAssert(mp_resolve_temp, ("can't map child %d to anything, no parent", index));
+		CScreenElementPtr p_child = mp_resolve_temp->GetChildByIndex(index);
+		mp_resolve_temp = NULL;
+		if (p_child) 		
+			return p_child->GetID();
+		else
+			return 0;
+	}
+	
+	mp_resolve_temp = NULL;
+	return 0;	
+}
+
+
+uint32 CScreenElementManager::ResolveComplexID(Script::CStruct *pStructContainingId, char *pIdSubStructName)
+{
+
+	return ResolveComplexID(pStructContainingId, Script::GenerateCRC(pIdSubStructName));	
+}
+
+
+
+void CScreenElementManager::RegisterObject ( Obj::CObject& obj )
+{
+	#ifdef	__NOPT_ASSERT__
+	CScreenElementPtr p_element = static_cast(&obj);
+	Dbg_MsgAssert(p_element, ("object registered with ScreenElement manager not ScreenElement"));
+	#endif	
+						   
+	CBaseManager::RegisterObject(obj);
+}
+
+
+
+
+void CScreenElementManager::UnregisterObject ( Obj::CObject& obj )
+{
+	CScreenElementPtr p_unregister_element	= static_cast(&obj);
+	Dbg_Assert(p_unregister_element);
+	
+	Dbg_MsgAssert(!p_unregister_element->mp_parent, ("can't unregister screen element from manager -- still has parent"));
+	Dbg_MsgAssert(!p_unregister_element->GetFirstChild(), ("can't unregister screen element from manager -- still has children"));
+	Dbg_MsgAssert(!p_unregister_element->GetNextSibling() && !p_unregister_element->GetPrevSibling(), 
+				  ("can't unregister screen element from manager -- still has siblings"));
+	
+	// see if unregister element is root element
+	if (p_unregister_element && mp_root_element == p_unregister_element)
+	{
+		mp_root_element = NULL;
+		
+		// find a new parentless element to be root element
+		Lst::Node *p_node = m_object_list.FirstItem();
+		while(p_node)
+		{
+			CScreenElementPtr p_element = static_cast(p_node->GetData());
+			Dbg_Assert(p_element);
+			// must have no parent, can't be element that we're unregistering
+			if (!p_element->mp_parent && p_element != p_unregister_element)
+			{
+				mp_root_element = p_element;
+				break;
+			}
+			p_node = p_node->GetNext();
+		}
+	}
+	
+	CBaseManager::UnregisterObject(obj);
+}
+
+
+
+
+void CScreenElementManager::KillObject ( Obj::CObject& obj )
+{
+	Dbg_MsgAssert(0, ("this virtual function not supported"));
+}
+
+
+
+
+Lst::Head< Obj::CObject > &CScreenElementManager::GetRefObjectList()
+{
+	Dbg_MsgAssert(0, ("this virtual function not supported"));
+	return m_object_list;
+}
+
+
+
+
+void CScreenElementManager::destroy_element_recursive(EPreserveParent preserve_parent, const CScreenElementPtr &pElement, Script::CScript *pCallingScript)
+{
+	pElement->SetChildLockState(CScreenElement::UNLOCK);
+	
+	CScreenElementPtr p_child = pElement->GetFirstChild();
+	while(p_child)
+	{
+		CScreenElementPtr p_next = p_child->GetNextSibling();
+		destroy_element_recursive(DONT_PRESERVE_PARENT, p_child, pCallingScript);
+		p_child = p_next;
+	}
+
+	if( !preserve_parent )
+	{
+		if (pCallingScript && pElement)
+			// must disassociate script from element being destroyed
+			pCallingScript->DisassociateWithObject(pElement);
+			
+		SetParent(NULL, pElement);
+		UnregisterObject(*pElement);
+		#ifdef	__NOPT_ASSERT__	
+		// Mick:  screen elements are deleted directly, so LockAssert is not applicable.
+		pElement->SetLockAssertOff();;
+		#endif
+		delete pElement;
+	}
+}
+
+
+
+
+CScreenElementPtr CScreenElementManager::get_element_by_local_id(const CScreenElementPtr &pParent, uint32 desiredLocalID)
+{
+	uint32 crc_tag_local_id = CRCX("tag_local_id");
+
+	CScreenElementPtr p_child = pParent->GetFirstChild();
+	while(p_child)
+	{
+		uint32 local_id;
+		if (p_child->GetChecksumTag(crc_tag_local_id, &local_id))
+		{
+			if (local_id == desiredLocalID)
+				return p_child;
+		}
+		p_child = p_child->GetNextSibling();
+	}
+	return NULL;
+}
+
+
+
+
+void CScreenElementManager::mark_element_in_focus(const CScreenElementPtr &pElement, int controller)
+{
+	Dbg_Assert(controller >= 0 && controller <= 1);
+	Dbg_Assert(pElement);
+	
+	FocusNode *p_prev_node = NULL;
+	// If in the list for the same controller, then exit function.
+	// If in the list for another controller, assert
+	for (int c = 0; c < NUM_FOCUS_LISTS; c++)
+	{
+		FocusNode *p_node = mp_focus_list[c];
+		while (p_node)
+		{
+			test_focus_node(p_node);
+			if (p_node->mpElement == pElement)
+			{
+				if (controller == c) 
+				{
+					// already in focus, make sure in full focus
+					p_node->mTempOutOfFocus = false;
+					return;
+				}
+				else
+				{
+					Dbg_MsgAssert(0, ("can't change existing focus to another controller"));
+				}
+			}
+			p_prev_node = p_node;
+			p_node = p_node->mpNextNode;
+		}
+	}
+	
+	// Time to make a new focus node
+	// p_prev_node will point to last element in list, or NULL if no list
+	
+	for (int i = 0; i < NUM_FOCUS_NODES; i++)
+	{
+		if (!m_focus_node_pool[i].mpElement)
+		{
+			//Ryan("TTT element %s marked in focus, %d\n", Script::FindChecksumName(pElement->GetID()), (int) controller);
+			
+			m_focus_node_pool[i].mpElement = pElement;
+			m_focus_node_pool[i].mId = pElement->GetID();
+			m_focus_node_pool[i].mpNextNode = NULL;
+			m_focus_node_pool[i].mTempOutOfFocus = false;
+			// put in list -- at end of list
+			if (p_prev_node)
+				p_prev_node->mpNextNode = &m_focus_node_pool[i];
+			else
+				mp_focus_list[controller] = &m_focus_node_pool[i];
+			m_focus_list_changed[controller] = true;
+			return;
+		}
+	}
+	Dbg_Assert(0);
+}
+
+
+
+
+void CScreenElementManager::mark_element_out_of_focus(const CScreenElementPtr &pElement, bool onlyChildren, bool tempOnly)
+{
+	Dbg_Assert(pElement);
+	
+	for (int c = 0; c < NUM_FOCUS_LISTS; c++)
+	{
+		// remove from list all elements that are descendents of pElement, and possibly pElement itself
+
+		FocusNode *p_prev_node = NULL;
+		FocusNode *p_node = mp_focus_list[c];
+		while(p_node)
+		{
+			test_focus_node(p_node);
+			FocusNode *p_next_node = p_node->mpNextNode;
+
+			bool am_descendent = false;
+			CScreenElementPtr p_elem = p_node->mpElement;
+			if (onlyChildren && p_elem)
+				p_elem = p_elem->mp_parent;
+			while(p_elem)
+			{
+				if (p_elem == pElement)
+				{
+					am_descendent = true;
+					break;
+				}
+				p_elem = p_elem->mp_parent;
+			}
+			
+			if (am_descendent)
+			{
+				if (tempOnly)
+				{
+					// just mark temporarily out of focus
+					p_node->mTempOutOfFocus = true;
+					//Ryan("TTT element %s marked TEMPORARILY out of focus, %d\n", Script::FindChecksumName(p_node->mpElement->GetID()), c);
+				}
+				else
+				{
+					// found a match -- remove from list
+					if (p_prev_node)
+						p_prev_node->mpNextNode = p_next_node;
+					else
+						mp_focus_list[c] = p_next_node;
+					//Ryan("TTT element %s marked OUT OF focus, %d\n", Script::FindChecksumName(p_node->mpElement->GetID()), (int) c);
+					// remove from pool
+					p_node->mpElement = NULL;
+					m_focus_list_changed[c] = true;
+
+					p_node = p_prev_node;
+				}
+			}
+			
+			p_prev_node = p_node;
+			p_node = p_next_node;
+		} // end while p_node
+	} // end for c
+}
+
+
+
+
+// Elements temporarily marked out-of-focus are put back in focus. Only elements that are descendents 
+// of pElement (or pElement) itself are restored.
+void CScreenElementManager::remark_temporarily_out_of_focus_elements(const CScreenElementPtr &pElement)
+{
+	for (int c = 0; c < NUM_FOCUS_LISTS; c++)
+	{
+		FocusNode *p_node = mp_focus_list[c];
+		while(p_node)
+		{
+			if (p_node->mTempOutOfFocus)
+			{				
+				// element still exists, make sure it or its ancestor is pElement
+				test_focus_node(p_node);
+				CScreenElementPtr p_element = p_node->mpElement;
+				while (p_element)
+				{
+					if (p_element == pElement)
+					{
+						p_node->mTempOutOfFocus = false;
+						break;
+					}
+					p_element = p_element->mp_parent;
+				}
+			}			
+			
+			p_node = p_node->mpNextNode;
+		}
+	} // end for c
+}
+
+
+
+
+void CScreenElementManager::test_focus_node(FocusNode *pNode)
+{
+	#ifdef __NOPT_ASSERT__
+	// if this element was removed, its node should have been, too
+	Dbg_MsgAssert(pNode->mpElement, ("this node entry shouldn't be in list"));
+	Dbg_MsgAssert(GetElement(pNode->mId), ("ID of node associated with non-existant element")); 
+	Dbg_MsgAssert(GetElement(pNode->mId) == pNode->mpElement, ("ID of node associated with element other than one pointed to"));
+	#endif
+}
+
+
+
+
+bool CScreenElementManager::is_pad_event(uint32 eventType)
+{
+	for (int i = 0; i < m_num_pad_event_types; i++)
+	{
+		if (eventType == m_pad_event_type_tab[i])
+			return true;
+	}
+	return false;
+}
+
+void CScreenElementManager::SetRootScreenElement( uint32 id )
+{
+	CScreenElementPtr p_elem = GetElement( id, ASSERT );
+	mp_root_element = p_elem;
+}
+
+
+bool ScriptCreateScreenElement(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 type;
+	if (!pParams->GetChecksum("type", &type))
+		Dbg_MsgAssert(0, ("can't create screen element without type"));
+
+	uint32 id = Obj::CBaseManager::vNO_OBJECT_ID;    
+    pParams->GetChecksum(CRCD(0x40c698af,"id"), &id);
+
+    // get id of ScreenElement
+    CScreenElementManager* pManager = CScreenElementManager::Instance();
+    CScreenElementPtr p_Element = pManager->CreateElement(type, id, pParams);
+    id = p_Element->GetID();
+
+    // add id to params - will overwrite if id present
+    pScript->GetParams()->AddChecksum(CRCD(0x40c698af,"id"), id);
+
+	return true;
+}
+
+
+
+
+bool ScriptDestroyScreenElement(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	uint32 id = pManager->ResolveComplexID(pParams, CRCD(0x40c698af,"id"));
+	Dbg_MsgAssert(id, ("\n%s\nelement not in manager",pScript->GetScriptInfo()));
+	
+	// Let's always destroy recursively -- is there any reason not to?
+	CScreenElementManager::ERecurse recurse = 
+		CScreenElementManager::ERecurse(!pParams->ContainsFlag("dont_recurse"));
+	CScreenElementManager::EPreserveParent preserve_parent = 
+		CScreenElementManager::EPreserveParent(pParams->ContainsFlag("preserve_parent"));
+	
+	// This combination doesn't really make sense
+	// Warn the user and do nothing since, in effect, it is an empty operation
+	if( !recurse && preserve_parent )
+	{
+		Dbg_Printf( "Warning: Destroying an element without recursion and with preserve_parent makes no sense\n" );
+		return true;
+	}
+
+	pManager->DestroyElement(id, recurse, preserve_parent, pScript);
+	
+	return true;
+}
+
+
+
+
+bool ScriptRunScriptOnScreenElement(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement(pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT);
+		
+	uint32 script;
+	pParams->GetChecksum(NONAME, &script, true);
+	
+	uint32 callback = 0;
+	pParams->GetChecksum(CRCD(0x86068bd9,"callback"), &callback);
+    
+    Script::CStruct *p_ScriptParams = NULL;
+    pParams->GetStructure(CRCD(0x7031f10c,"params"), &p_ScriptParams);
+
+    Script::CStruct *p_CallbackParams = NULL;
+    pParams->GetStructure(CRCD(0xe6cf88c4,"callback_params"), &p_CallbackParams);
+
+	// K: If script is actually a member function, then call it, for Gary bless him.
+	// G: God bless us, everyone!
+    Script::CSymbolTableEntry *p_entry=Script::Resolve(script);
+    if (p_entry && p_entry->mType==ESYMBOLTYPE_MEMBERFUNCTION)
+    {
+		Dbg_MsgAssert(p_elem,("NULL p_elem"));
+		p_elem->CallMemberFunction(script,p_ScriptParams,pScript);
+	}	
+	else
+	{
+		Script::CScript *p_new_script = Script::SpawnScript(script,	p_ScriptParams, callback, p_CallbackParams);
+		#ifdef __NOPT_ASSERT__
+		p_new_script->SetCommentString("Spawned by script command RunScriptOnScreenElement");
+		p_new_script->SetOriginatingScriptInfo(pScript->GetCurrentLineNumber(),pScript->mScriptChecksum);
+		#endif
+		
+		// K: This 'if' is now required because if script is actually a cfunc, SpawnScript will have run it,
+		// then returned NULL cos it did not need to create a script.
+		if (p_new_script)
+		{
+			p_new_script->mpObject = p_elem;
+			// normally, script won't be updated until next frame -- we want it NOW, motherfucker
+			p_new_script->Update();
+			//Script::RunScript(script, pParams, pElement);
+		}	
+	}
+		
+	return true;
+}
+
+
+
+
+bool ScriptSetScreenElementProps(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement(pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT);
+			
+	p_elem->SetProperties(pParams);
+	
+	return true;
+}
+
+
+
+
+/*
+CScreenElementPtr KlaabuBaabu()
+{
+	return NULL;
+}
+*/
+
+bool ScriptDoScreenElementMorph(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement(pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT);
+			
+	p_elem->SetMorph(pParams);
+	
+	//CScreenElementPtr arg_bat = KlaabuBaabu();
+	//printf("KlaabuBaabu 0x%x\n", arg_bat.Convert());
+	//printf("KlaabuBaabu 0x%x\n", KlaabuBaabu().Convert());
+	//Dbg_Assert(0);
+	
+	return true;
+}
+
+
+
+
+bool ScriptSetScreenElementLock(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement(pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT);
+	Dbg_MsgAssert(p_elem, ("element not in manager"));
+			
+	if (pParams->ContainsFlag("off"))
+		p_elem->SetChildLockState(CScreenElement::UNLOCK);
+	else
+		p_elem->SetChildLockState(CScreenElement::LOCK);
+	
+	return true;
+}
+
+
+
+
+bool ScriptScreenElementSystemInit(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+	CWindowElement *p_window = new CWindowElement();
+	Mem::Manager::sHandle().PopContext();
+
+	p_window->SetID(Script::GenerateCRC("root_window"));
+
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	pManager->RegisterObject(*p_window);
+
+	return true;
+}
+
+/*
+	Temporary hack functions for THPS4. Of course, you the reader are probably now
+	using them for THPS8! :x
+*/
+
+void SetScoreTHPS4(char* score_text, int skater_num)
+{
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	CScreenElementPtr p_element = p_screen_elem_man->GetElement(Script::GenerateCRC("the_score") + skater_num );
+	if (p_element)
+	{
+		Dbg_MsgAssert((uint32)p_element->GetType() == CRCD(0x5200dfb6, "TextElement"), ("type is 0x%x", p_element->GetType()));
+		CTextElement *p_score_element = (CTextElement *) (p_element.Convert());
+		
+		p_score_element->SetText(score_text);
+	}
+}
+
+bool ScriptGetScreenElementDims(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement(pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT);
+	
+	int width = (int)( p_elem->GetBaseW() * p_elem->GetScaleX() );
+	int height = (int)( p_elem->GetBaseH() * p_elem->GetScaleY() );
+
+	pScript->GetParams()->AddInteger( "width", width );
+	pScript->GetParams()->AddInteger( "height", height );
+	return true;
+}
+
+
+// @script | TextElementConcatenate | this will append the given string on the end of the 
+// current text element's string
+// @parm name | id | the id of the element
+// @uparm "string" | the string to append
+// @flag enforce_max_width | Don't allow concatenation on a text block
+// element if there's no way to wrap the element and keep it within the
+// maximum width.  This has no effect on TextElements.
+bool ScriptTextElementConcatenate(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement( pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT );
+	
+	const char* p_concat_string;
+	pParams->GetString( NONAME, &p_concat_string, Script::ASSERT );
+
+	uint32 type = p_elem->GetType();
+	if ( type == CRCD( 0x40d92263, "TextBlockElement" ) )
+	{
+		CTextBlockElement* pTextBlockElement = (CTextBlockElement*)p_elem.Convert();
+		return pTextBlockElement->Concatenate( p_concat_string, pParams->ContainsFlag( CRCD( 0x27e7a420, "enforce_max_width" ) ), pParams->ContainsFlag( CRCD(0xb8c08f55,"last_line") ) );
+	}
+	else if ( type == CRCD( 0x5200dfb6, "TextElement" ) )
+	{
+		CTextElement* pTextElement = (CTextElement*)p_elem.Convert();
+		return pTextElement->Concatenate( p_concat_string );
+	}
+	else
+	{
+		Dbg_MsgAssert( 0, ( "TextElementConcatenate called on type 0x%x", p_elem->GetType() ) );
+		return false;
+	}
+}
+
+// @script | TextElementBackspace | removes one character from the text element.  This will
+// return false if the text element is already empty
+// @parm name | id | the element id
+bool ScriptTextElementBackspace(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement( pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT );
+
+	uint32 type = p_elem->GetType();
+	switch ( type )
+	{
+		case CRCC( 0x5200dfb6, "TextElement" ):
+		{
+			CTextElement* p_text_element = (CTextElement*)p_elem.Convert();
+			return p_text_element->Backspace();
+			break;
+		}
+		case CRCC( 0x40d92263, "TextBlockElement" ):
+		{
+			CTextBlockElement* p_text_block_element = (CTextBlockElement*)p_elem.Convert();
+			return p_text_block_element->Backspace();
+			break;
+		}
+		default:
+			Dbg_MsgAssert( 0, ( "TextElementBackspace called on %s, which has type %x", Script::FindChecksumName( p_elem->GetID() ), p_elem->GetType() ) );
+			return false;
+			break;
+	}
+}
+
+// @script | GetTextElementString | this returns the current string for the specified 
+// text element.  The string is returned in the script's params (string).
+// @parm name | id | the text element id
+bool ScriptGetTextElementString(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement( pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT );
+	
+	bool found_text = false;
+	switch ( (uint32)p_elem->GetType() )
+	{
+		case CRCC( 0x5200dfb6, "TextElement" ):
+		{
+			CTextElement* p_text_element = (CTextElement*)p_elem.Convert();
+			char *p_text = p_text_element->GetText();
+			if ( p_text )
+			{
+				pScript->GetParams()->AddString( "string", p_text );
+				found_text = true;
+			}
+			break;
+		}
+		case CRCC( 0x40d92263, "TextBlockElement" ):
+		{
+			CTextBlockElement* p_text_block_element = (CTextBlockElement*)p_elem.Convert();
+			char text[Front::MAX_EDITABLE_TEXT_BLOCK_LENGTH];
+			if ( p_text_block_element->GetText( text, Front::MAX_EDITABLE_TEXT_BLOCK_LENGTH ) )
+			{
+				pScript->GetParams()->AddString( "string", text );
+				found_text = true;
+			}
+			break;
+		}
+		default:
+			Dbg_MsgAssert( 0, ( "GetScreenElementText called on type %x", p_elem->GetType() ) );
+			break;
+	}
+	return false;
+}
+
+// @script | GetTextElementLength | returns the length of the specified text element
+// in the scripts params (length)
+// @parm name | id | the text element's id
+bool ScriptGetTextElementLength(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement( pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT );
+
+	uint32 type = (uint32)p_elem->GetType();
+	int length = 0;
+	switch ( type )
+	{
+		case CRCC( 0x5200dfb6, "TextElement" ):
+		{
+			CTextElement* p_text_element = (CTextElement*) p_elem.Convert();
+			length = p_text_element->GetLength();
+			break;
+		}
+		case CRCC( 0x40d92263, "TextBlockElement" ):
+		{
+			CTextBlockElement* p_text_block_element = (CTextBlockElement*) p_elem.Convert();
+			length = p_text_block_element->GetLength();
+			break;
+		}
+		default:
+			Dbg_MsgAssert( 0, ("GetTextElementLength called on screen element with type 0x%x", type ) );
+			return false;
+			break;
+	}
+	pScript->GetParams()->AddInteger( "length", length );
+	return true;
+}
+
+bool ScriptGetScreenElementPosition(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement(pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT);
+	
+	//float x = p_elem->GetAbsX();
+	//float y = p_elem->GetAbsY();
+	float x, y;
+	p_elem->GetLocalULPos( &x, &y );
+
+	pScript->GetParams()->AddPair( "ScreenElementPos", x, y );
+	return true;
+}
+
+// @script | MenuSelectedIndexIs | returns true if the selected item in the vmenu is 
+// the item specified.  Must be called with either an index, the "first" flag, or the "last" flag
+// @uparmopt 1 | some index value (first item is index 0)
+// @flag first | checks if the selected item is the first item
+// @flag last | checks if the selected item is the last item
+bool ScriptMenuSelectedIndexIs(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement(pParams, CRCD(0x40c698af,"id"), CScreenElementManager::ASSERT);
+
+	Dbg_MsgAssert( (uint32) p_elem->GetType() == CRCD( 0x130ef802, "vmenu" ), ( "Screen element has wrong type." ) );
+
+	CBaseMenu* p_menu = (CBaseMenu*)p_elem.Convert();
+
+	int selected_index = p_menu->GetSelectedIndex();
+	if ( selected_index == -1 )
+		return false;
+	
+	bool rv = false;
+	
+	int testIndex;
+	if ( pParams->GetInteger( NONAME, &testIndex, Script::NO_ASSERT ) )
+		rv = ( testIndex == selected_index );
+	else if ( pParams->ContainsFlag( "first" ) )
+		rv = ( selected_index == 0 );
+	else if ( pParams->ContainsFlag( "last" ) )
+		rv = ( selected_index == ( p_menu->CountChildren() - 1 ) );
+	else
+		Dbg_MsgAssert( 0, ("MenuSelectedIndexIs must be called with a number, the \"first\" flag, or the \"last\" flag") );
+
+	return rv;
+}
+
+// @script | ScreenElementExists | returns true if the given screen element exists
+// @parm name | id | the id to look for...supports compound id's
+bool ScriptScreenElementExists(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();
+	CScreenElementPtr p_elem = pManager->GetElement( pParams, CRCD(0x40c698af,"id"), CScreenElementManager::DONT_ASSERT );
+	if ( p_elem )
+		return true;
+	return false;
+}
+
+// @script | GetScreenElementProps | writes the props of the given screen element to the 
+// calling script's params
+// @parm name | id | the id of the screen element
+// @flag | dont_assert
+bool ScriptGetScreenElementProps( Script::CScriptStructure *pParams, Script::CScript *pScript )
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();	  
+	CScreenElementPtr p_elem = NULL;
+	uint32 id = pManager->ResolveComplexID(pParams, CRCD(0x40c698af,"id"));
+	if ( id )
+	{
+		p_elem = pManager->GetElement(id, CScreenElementManager::DONT_ASSERT);
+	}
+	
+	if ( p_elem )
+	{
+		p_elem->WritePropertiesToStruct( pScript->GetParams() );
+		return true;
+	}
+	else
+	{
+		if ( pParams->ContainsFlag( CRCD(0x3d92465e,"dont_assert") ) )
+			return false;
+		Dbg_MsgAssert( 0, ( "%s\n SetScreenElementProps unable to find screen element %s",  pScript->GetScriptInfo(),  Script::FindChecksumName(id) ) );
+	}
+	return false;
+}
+
+bool ScriptSetRootScreenElement( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CScreenElementManager* pManager = CScreenElementManager::Instance();	  
+	uint32 id = pManager->ResolveComplexID(pParams, CRCD(0x40c698af,"id"));
+	if ( id )
+	{
+		pManager->SetRootScreenElement( id );
+		return true;
+	}
+	return false;
+}
+
+void SetTimeTHPS4(int minutes, int seconds)
+{
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	CScreenElementPtr p_element = p_screen_elem_man->GetElement(CRCD(0xa6343cd4,"the_time"));
+	if (p_element)
+	{
+		Dbg_MsgAssert((uint32) p_element->GetType() == CRCD(0x5200dfb6, "TextElement"), ("type is 0x%x", p_element->GetType()));
+		CTextElement *p_time_element = (CTextElement *) p_element.Convert();
+		
+		char time_text[64];
+		sprintf(time_text, "%2d:%.2d", minutes, seconds);
+		
+		p_time_element->SetText(time_text);
+	}
+}
+
+
+void HideTimeTHPS4()
+{
+    Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+    CScreenElementPtr p_element = p_screen_elem_man->GetElement(CRCD(0xa6343cd4,"the_time"));
+    if (p_element)
+    {
+        Dbg_MsgAssert((uint32) p_element->GetType() == CRCD(0x5200dfb6, "TextElement"), ("type is 0x%x", p_element->GetType()));
+        CTextElement *p_time_element = (CTextElement *) p_element.Convert();
+        p_time_element->SetText( "" );
+    }
+}
+
+
+}
+
diff --git a/Code/Gfx/2D/ScreenElemMan.h b/Code/Gfx/2D/ScreenElemMan.h
new file mode 100644
index 0000000..c9b6848
--- /dev/null
+++ b/Code/Gfx/2D/ScreenElemMan.h
@@ -0,0 +1,138 @@
+#ifndef __GFX_2D_SCREENELEMMAN_H__
+#define __GFX_2D_SCREENELEMMAN_H__
+
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Script
+{
+	class CScriptStructure;
+	class CScript;
+}
+
+namespace Front
+{
+
+/*
+	Manages all screen elements. References the root element of the screen element
+	parent/child tree.
+	
+	Keeps track of which elements are in focus, and makes sure that pad events are
+	passed to these elements.
+*/
+class CScreenElementManager : public Obj::CBaseManager, public Obj::CEventListener
+{
+
+	DeclareSingletonClass( CScreenElementManager );
+
+public:
+								CScreenElementManager();
+								~CScreenElementManager();
+
+	CScreenElementPtr 			CreateElement(uint32 type, uint32 id, Script::CStruct *pProps);
+	enum 						ERecurse { DONT_RECURSE = 0, RECURSE = 1};
+	enum 						EPreserveParent { DONT_PRESERVE_PARENT = 0, PRESERVE_PARENT = 1};
+	void						DestroyElement(uint32 id, ERecurse recurse = RECURSE, EPreserveParent preserveParent = DONT_PRESERVE_PARENT, Script::CScript *pCallingScript = NULL);
+
+	void						SetParent(const CScreenElementPtr &pParent, const CScreenElementPtr &pChild, CScreenElement::EPosRecalc recalculatePosition = CScreenElement::vRECALC_POS);
+	enum EAssert
+	{
+		DONT_ASSERT = 0,
+		ASSERT,
+	};
+	CScreenElementPtr 			GetElement(uint32 id, EAssert assert = DONT_ASSERT);
+	CScreenElementPtr 			GetElement(Script::CStruct *pStructContainingId, char *pIdSubStructName, EAssert assert = DONT_ASSERT);
+	CScreenElementPtr 			GetElement(Script::CStruct *pStructContainingId, uint32 IdSubStructName, EAssert assert);
+
+	void						Update();
+	void						SetPausedState(bool pause);
+	
+	void 						set_tree_lock_state(CScreenElement::ELockState state);
+	void						pass_event_to_listener(Obj::CEvent *pEvent);	
+	
+	bool						IsComplexID(Script::CStruct *pStructContainingId, char *pIdSubStructName);
+	uint32 						ResolveComplexID(Script::CStruct *pStructContainingId, uint32 IdSubStructName);
+	uint32						ResolveComplexID(Script::CStruct *pStructContainingId, char *pIdSubStructName);
+	
+	/*
+		Virtual functions from CBaseManager
+	*/
+	
+	void						RegisterObject ( Obj::CObject& obj );
+	void						UnregisterObject ( Obj::CObject& obj );	
+	void						KillObject ( Obj::CObject& obj );
+	Lst::Head< Obj::CObject > &	GetRefObjectList();
+
+	void						SetRootScreenElement( uint32 id );
+
+private:
+
+	struct FocusNode // in the focus tree
+	{
+		//FocusNode *				mpFirstChild;
+		//FocusNode *				mpSibling;
+		CScreenElementPtr 		mpElement;
+		uint32					mId;
+		FocusNode *				mpNextNode;
+		bool					mProcessed;
+		bool					mTempOutOfFocus;
+	};
+	
+	void						destroy_element_recursive(EPreserveParent preserve_parent, const CScreenElementPtr &pElement, Script::CScript *pCallingScript);
+	CScreenElementPtr 			get_element_by_local_id(const CScreenElementPtr &pParent, uint32 desiredLocalID);
+
+	void						mark_element_in_focus(const CScreenElementPtr &pElement, int controller);
+	void						mark_element_out_of_focus(const CScreenElementPtr &pElement, bool onlyChildren = false, bool temporaryOnly = false);
+	void						remark_temporarily_out_of_focus_elements(const CScreenElementPtr &pElement);
+	void 						test_focus_node(FocusNode *pNode);
+
+	bool						is_pad_event(uint32 eventType);
+	
+	CScreenElementPtr			mp_root_element;
+
+	// a temporary pointer used by ResolveComplexID() for recursion
+	CScreenElementPtr 			mp_resolve_temp;
+	
+	enum
+	{
+		NUM_FOCUS_NODES	=		48,					// basically, a pool of these
+		NUM_FOCUS_LISTS =		SIO::vMAX_DEVICES,	// one for each controller
+	};
+	
+	FocusNode					m_focus_node_pool[NUM_FOCUS_NODES];	
+	FocusNode *					mp_focus_list[NUM_FOCUS_LISTS];
+	bool						m_focus_list_changed[NUM_FOCUS_LISTS];
+
+	enum
+	{
+		MAX_PAD_EVENT_TYPES		= 48,
+	};
+	uint32						m_pad_event_type_tab[MAX_PAD_EVENT_TYPES];
+	int							m_num_pad_event_types;
+};
+
+
+
+bool ScriptCreateScreenElement(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptDestroyScreenElement(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptRunScriptOnScreenElement(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetScreenElementProps(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptDoScreenElementMorph(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetScreenElementLock(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptScreenElementSystemInit(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetScreenElementDims(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptTextElementConcatenate(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptTextElementBackspace(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetTextElementString(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetTextElementLength(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetScreenElementPosition(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptMenuSelectedIndexIs(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptScreenElementExists(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetScreenElementProps( Script::CScriptStructure *pParams, Script::CScript *pScript );
+bool ScriptSetRootScreenElement( Script::CStruct* pParams, Script::CScript* pScript );
+}
+
+#endif
diff --git a/Code/Gfx/2D/ScreenElement2.cpp b/Code/Gfx/2D/ScreenElement2.cpp
new file mode 100644
index 0000000..9745c18
--- /dev/null
+++ b/Code/Gfx/2D/ScreenElement2.cpp
@@ -0,0 +1,1410 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#ifdef	__PLAT_NGPS__	
+#include 
+#endif
+
+/*
+	=========================================================
+	RTFM!!!!
+	
+	Be sure to check out the user's guide for the menu system
+	to get an idea of what all this code's for. Heurghh!!
+	=========================================================
+*/
+
+namespace Front
+{
+
+
+const float CScreenElement::vJUST_LEFT		= -1.0f;
+const float CScreenElement::vJUST_TOP		= -1.0f;
+const float CScreenElement::vJUST_CENTER	= 0.0f;
+const float CScreenElement::vJUST_RIGHT		= 1.0f;
+const float CScreenElement::vJUST_BOTTOM	= 1.0f;
+
+const float CScreenElement::AUTO_Z_SPACE	= 1.0f;
+
+CScreenElement::CScreenElement()
+	: CObject()
+{
+	m_key_time = 0;
+	mp_parent = NULL;
+	mp_child_list = NULL;
+	mp_prev_sibling = NULL;
+	mp_next_sibling = NULL;
+
+	m_local_props.SetScale(1.0f, 1.0f);
+	m_local_props.SetAbsoluteScale(1.0f, 1.0f);
+	m_local_props.SetScreenPos(0.0f, 0.0f);
+	m_local_props.SetRotate( 0.0f );
+	m_local_props.SetWorldPos( Mth::Vector(0.0f, 0.0f, 0.0f) );
+	
+	Image::RGBA default_rgba;
+	default_rgba.r = 128;
+	default_rgba.g = 128;
+	default_rgba.b = 128;
+	default_rgba.a = 128;
+	m_local_props.SetRGBA( default_rgba );
+
+	m_local_props.alpha = 1.0f;
+	m_target_local_props = m_local_props;
+	m_summed_props = m_local_props;
+
+	m_base_w = 0.0f;
+	m_base_h = 0.0f;
+
+	m_rgba.r = 128;
+	m_rgba.g = 128;
+	m_rgba.b = 128;
+	m_rgba.a = 128;
+	
+	m_z_priority = 0.0f;
+
+	m_originalAlpha = 1.0f;
+	
+	m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	m_object_flags |= vIS_SCREEN_ELEMENT;
+}
+
+
+
+
+CScreenElement::~CScreenElement()
+{
+	set_parent(NULL, vDONT_RECALC_POS);
+	m_id = 0xDEADBEEF;
+}
+
+
+
+
+// absolute upper-left, last frame's
+float CScreenElement::GetAbsX()
+{
+	return m_summed_props.GetScreenUpperLeftX();
+}
+
+
+
+
+// absolute upper-left, last frame's
+float CScreenElement::GetAbsY()
+{
+	return m_summed_props.GetScreenUpperLeftY();
+}
+
+
+
+
+// absolute width, includes scale, last frame's
+float CScreenElement::GetAbsW()
+{
+	return m_base_w * m_summed_props.GetAbsoluteScaleX();
+}
+
+
+
+
+// absolute height, includes scale, last frame's
+float CScreenElement::GetAbsH()
+{
+	return m_base_h * m_summed_props.GetAbsoluteScaleY();
+}
+
+
+
+
+/*
+	Position of anchor point, in parent's space. 'Target' means where it's going,
+	not necessarily where it is.
+*/
+void CScreenElement::GetLocalPos(float *pX, float *pY)
+{
+	Dbg_Assert(pX);
+	Dbg_Assert(pY);
+	
+	*pX = m_target_local_props.GetScreenPosX();
+	*pY = m_target_local_props.GetScreenPosY();
+}
+
+
+
+
+/*
+	Position of upper left corner of element, in parent's space. 'Target' means where 
+	it's going, not necessarily where it is.
+*/
+void CScreenElement::GetLocalULPos(float *pX, float *pY)
+{
+	Dbg_Assert(pX);
+	Dbg_Assert(pY);
+	
+	*pX = m_target_local_props.GetScreenUpperLeftX();
+	*pY = m_target_local_props.GetScreenUpperLeftY();
+}
+
+
+
+
+/*
+	Justification is a fixed attribute (doesn't morph)
+*/
+void CScreenElement::GetJust(float *pJustX, float *pJustY)
+{
+	Dbg_Assert(pJustX);
+	Dbg_Assert(pJustY);
+	
+	*pJustX = m_just_x;
+	*pJustY = m_just_y;
+}
+
+
+
+
+/* 
+	Sets desired local position, element will morph from current position to
+	that position over set morph time (unless force_instant set).
+*/
+void CScreenElement::SetPos(float x, float y, EForceInstant forceInstant)
+{
+	Dbg_MsgAssert(!(m_object_flags & CScreenElement::v3D_POS), ("Illegal to set the 2D position of a 3D screen element"));
+
+	if (m_target_local_props.GetScreenPosX() != x || m_target_local_props.GetScreenPosY() != y)
+	{
+		m_target_local_props.SetScreenPos(x, y);
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		compute_ul_pos(m_target_local_props);
+	}
+	if (forceInstant) 
+	{
+		if (m_local_props.GetScreenPosX() != x || m_local_props.GetScreenPosY() != y)
+		{
+			// on the next update, this element will arrive at the target position
+			m_local_props.SetScreenPos(x, y);
+			m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		}
+	}
+}
+
+
+/* 
+	Sets desired world position, element will morph from current position to
+	that position over set morph time (unless force_instant set).
+*/
+void CScreenElement::SetPos3D(const Mth::Vector & pos3D, EForceInstant forceInstant)
+{
+	if (m_target_local_props.GetWorldPos() != pos3D )
+	{
+		m_target_local_props.SetWorldPos(pos3D);
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		compute_ul_pos(m_target_local_props);
+	}
+	if (forceInstant) 
+	{
+		if (m_local_props.GetWorldPos() != pos3D )
+		{
+			// on the next update, this element will arrive at the target position
+			m_local_props.SetWorldPos(pos3D);
+			m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		}
+	}
+
+	m_object_flags |= CScreenElement::v3D_POS;
+}
+
+
+/*
+	Set 'force' if dims being set from outside element. When that happens, element cannot
+	change own dims.
+	
+	Well, actually it can (VMenu), but 'force' is taken is a suggestion.
+*/
+void CScreenElement::SetDims(float w, float h, EForceDims force)
+{
+	if (m_base_w != w || m_base_h != h)
+	{
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		m_base_w = w;
+		m_base_h = h;
+		compute_ul_pos(m_target_local_props);
+	}
+	
+	if (force) 
+		m_object_flags |= vFORCED_DIMS;
+	else
+		m_object_flags &= ~vFORCED_DIMS;
+}
+
+
+
+
+/*
+	Justification effects where the upper-left corner of an element is relative to 
+	the element's assigned position (anchor point). 
+	
+	(-1.0, -1.0):	anchor is at upper-left of element
+	(0.0, 0.0):		anchor is in center of element
+	(1.0, 1.0):		anchor is at lower-right of element
+	
+	You can figure out the meaning of other numbers.
+*/
+void CScreenElement::SetJust(float justX, float justY)
+{
+	if (m_just_x != justX || m_just_y != justY)
+	{
+		// calculate justification differences and adjust new position so the item still appears to
+		// have the same location as before
+		float x_diff = m_local_props.GetScaleX() * m_base_w * (justX - m_just_x) / 2.0f;
+		float y_diff = m_local_props.GetScaleY() * m_base_h * (justY - m_just_y) / 2.0f;
+		m_local_props.SetScreenPos(m_local_props.GetScreenPosX() + x_diff, m_local_props.GetScreenPosY() + y_diff);
+		m_target_local_props.SetScreenPos(m_target_local_props.GetScreenPosX() + x_diff, m_target_local_props.GetScreenPosY() + y_diff);
+		//m_local_props.ulx += x_diff;
+		//m_local_props.uly += y_diff;
+
+		m_just_x = justX;
+		m_just_y = justY;
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		
+		compute_ul_pos(m_target_local_props);
+	}
+}
+
+
+
+
+/* 
+	Sets desired local scale, element will morph from current scale to
+	that scale over set morph time (unless force_instant set).
+*/
+void CScreenElement::SetScale(float scaleX, float scaleY, bool relative, EForceInstant forceInstant )
+{
+	if (m_target_local_props.GetScaleX() != scaleX || m_target_local_props.GetScaleY() != scaleY || relative)
+	{
+		if ( !relative )
+		{
+			m_target_local_props.SetAbsoluteScale(scaleX, scaleY);
+		}
+		else
+		{
+			scaleX *= m_target_local_props.GetAbsoluteScaleX();
+			scaleY *= m_target_local_props.GetAbsoluteScaleY();
+		}
+		m_target_local_props.SetScale(scaleX, scaleY);
+		compute_ul_pos(m_target_local_props);
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	}
+	if (forceInstant) 
+	{
+		if (m_local_props.GetScaleX() != scaleX || m_local_props.GetScaleY() != scaleY)
+		{
+			// on the next update, this element will arrive at the target scale
+			m_local_props.SetScale(scaleX, scaleY);
+			m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		}
+	}
+}
+
+
+
+
+/* 
+	Sets desired local alpha, element will morph from current alpha to
+	that alpha over set morph time (unless force_instant set).
+*/
+void CScreenElement::SetAlpha(float alpha, EForceInstant forceInstant)
+{
+	if (m_target_local_props.alpha != alpha)
+	{
+		m_target_local_props.alpha = alpha;
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	}
+	if (forceInstant) 
+	{
+		if (m_local_props.alpha != alpha)
+		{
+			// on the next update, this element will arrive at the target alpha
+			m_local_props.alpha = alpha;
+			m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		}
+	}
+}
+
+
+
+
+/*
+	Specifies the amount of time over which this element will changes from its
+	current morphable properties (pos, scale, alpha) to the target ones. If
+	an animation already in progress, override previously set time.
+*/
+void CScreenElement::SetAnimTime(Tmr::Time animTime)
+{
+	m_base_time = Tmr::GetTime();
+	m_key_time = animTime;
+	m_last_motion_grad = 0.0f;
+	m_last_pct_changed = 0.0f;
+	
+	if (!animTime)
+	{
+		m_local_props = m_target_local_props;
+	}
+	m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	m_object_flags &= ~vMORPHING_PAUSED;
+	m_object_flags &= ~vMORPHING_PAUSED2;
+}
+
+
+
+
+/*
+	Note: The alpha component here is multiplied by the alpha from SetAlpha() to
+	determine final alpha.
+*/
+void CScreenElement::SetRGBA( Image::RGBA rgba, EForceInstant forceInstant )
+{
+	Image::RGBA target_rgba = m_target_local_props.GetRGBA();
+	if ( target_rgba.r != rgba.r || target_rgba.g != rgba.g || target_rgba.b != rgba.b || target_rgba.a != rgba.a )
+	{
+		m_target_local_props.SetRGBA( rgba );
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	}
+	if ( forceInstant )
+	{
+		m_local_props.SetRGBA( rgba );
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	}
+}
+
+
+/*
+	Used to force drawing order entry (instead of it being automatically assigned). Client code should be 
+	written using knowledge of how these values work.
+*/
+void CScreenElement::SetZPriority(float zPriority)
+{
+	m_z_priority = zPriority;
+	m_object_flags |= vCHANGED_STATIC_PROPS;
+	m_object_flags |= vCUSTOM_Z_PRIORITY;
+}
+
+
+
+
+void CScreenElement::SetMorphPausedState(bool pause)
+{
+	if (pause)
+		m_object_flags |= vMORPHING_PAUSED;
+	else
+	{
+		m_object_flags &= ~vMORPHING_PAUSED;
+		m_object_flags &= ~vMORPHING_PAUSED2;
+	}
+}
+
+
+
+
+/*
+	When element is unlocked, children can be added or removed, otherwise they can't. Really just an 
+	optimization so that lots of children can be added en masse without slowdown.
+	
+	Note: The children you add to an element don't officially exist until you lock the element again,
+	so don't forget to do this.
+*/
+void CScreenElement::SetChildLockState(CScreenElement::ELockState lockState)
+{
+	ELockState current_lock_state = UNLOCK;
+	if (m_object_flags & CScreenElement::vCHILD_LOCK) current_lock_state = LOCK;
+	if (lockState == current_lock_state)
+		return;
+
+	if (lockState == LOCK)
+		m_object_flags |= CScreenElement::vCHILD_LOCK;
+	else
+		m_object_flags &= ~CScreenElement::vCHILD_LOCK;
+
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	
+	if (lockState == LOCK) 
+	{	
+		//Ryan("Setting child lock on, item %s\n", Script::FindChecksumName(m_id));
+		
+		// send out notice that child lock has been turned on
+		p_tracker->LaunchEvent(Obj::CEvent::TYPE_NOTIFY_CHILD_LOCK, m_id);
+	}
+	else
+	{
+		//Ryan("Setting child lock off, item %s\n", Script::FindChecksumName(m_id));
+		
+		// send out notice that child lock has been turned off
+		p_tracker->LaunchEvent(Obj::CEvent::TYPE_NOTIFY_CHILD_UNLOCK, m_id);
+	}
+}
+
+
+
+
+CScreenElement* CScreenElement::GetLastChild()
+{
+	CScreenElement* pChild = mp_child_list;
+	while(pChild)
+	{
+		if (!pChild->GetNextSibling())
+			break;
+		
+		pChild = pChild->GetNextSibling();
+	}
+
+	return pChild;
+}
+
+
+
+
+/*
+	Returns a pointer to the child with that ID.
+*/
+CScreenElement* CScreenElement::GetChildById(uint32 id, int *pIndex)
+{
+	return get_child_by_id_or_index(id, pIndex);
+}
+
+
+
+
+/*
+	Returns a pointer to the Nth child, where N = index.
+	
+	The children are indexed in the order they are added. If one is removed,
+	then the indices of all subsequent children are shifted down a notch.
+*/
+CScreenElement* CScreenElement::GetChildByIndex(int index)
+{
+	return get_child_by_id_or_index(0, &index);
+}
+
+
+
+
+/*
+	Returns number of children.
+*/
+int CScreenElement::CountChildren()
+{
+	CScreenElementPtr p_child = mp_child_list;
+	int count = 0;
+	while(p_child)
+	{
+		p_child = p_child->GetNextSibling();
+		count++;
+	}
+	return count;
+}
+
+
+
+
+/*
+	An important entry point to CScreenElement from script. Sets the elements static
+	(as opposed to morphable) properties.
+	
+	Note: a virtual function, so can be extended by subclasses
+	Note: SetProperties() in a subclass should call this function before doing its own logic
+*/
+void CScreenElement::SetProperties(Script::CStruct *pProps)
+{
+	CScreenElementManager *pManager = static_cast(mp_manager);
+	Dbg_Assert(pManager);
+
+	Script::CPair pos;
+	if (pProps->GetPair(CRCD(0x7f261953,"pos"), &pos))
+	{
+		// in this case, position will be forced instantly to the specified position
+		SetPos(pos.mX, pos.mY, FORCE_INSTANT);
+	}
+
+	Mth::Vector pos3D;
+	if (pProps->GetVector(CRCD(0x4b491900,"pos3D"), &pos3D))
+	{
+		pos3D[W] = 1.0f;		// force to be a point
+		//Dbg_Message("************ position (%f, %f, %f)", pos3D[X], pos3D[Y], pos3D[Z]);
+		SetPos3D(pos3D, FORCE_INSTANT);
+	}
+
+	Script::CPair dims;
+	if (pProps->GetPair(CRCD(0x34a68574,"dims"), &dims))
+	{
+		SetDims(dims.mX, dims.mY, FORCE_DIMS);
+	}
+
+	uint32 parent_id = pManager->ResolveComplexID(pProps, CRCD(0xc2719fb0,"parent"));
+	if (parent_id)
+	{
+		CScreenElementPtr pParent = pManager->GetElement(parent_id);
+		#ifdef __NOPT_ASSERT__
+		
+		if (pParent->GetType() == CScreenElement::TYPE_TEXT_BLOCK_ELEMENT)
+		{
+			if (GetType() != CScreenElement::TYPE_TEXT_ELEMENT)
+			{
+				Script::PrintContents(pProps);
+				Dbg_MsgAssert(0,("TextBlockElement %s parenting child %s that is NOT a CTextElement (it's %s)\nIn CScreenElement::SetProperties\n^^^ SEE STRUCTURE ABOVE MEM DUMP & TELL BRAD^^^\n",
+					Script::FindChecksumName(pParent->GetID()), Script::FindChecksumName(GetID()), Script::FindChecksumName(GetType())));
+			}
+		}
+		
+		if (!pParent)
+		{
+			Script::PrintContents(pProps, 2);
+			Dbg_MsgAssert(0, ("could not resolve parent, see struct above"));
+		}
+		#endif
+		pManager->SetParent(pParent, this);
+	}
+
+	float just[2] = { 0.0f, 0.0f };
+	if (resolve_just(pProps, CRCD(0x8b60022f,"just"), just, just+1))
+		SetJust(just[0], just[1]);
+	
+	
+	Image::RGBA rgba;
+	if (resolve_rgba(pProps, CRCD(0x3f6bcdba,"rgba"), &rgba))
+	{
+		SetRGBA(rgba);
+	}
+		
+	float z_priority;
+	if (pProps->GetFloat(CRCD(0x57710f31,"z_priority"), &z_priority))
+	{
+		SetZPriority(z_priority);
+	}
+	
+	if (pProps->ContainsFlag(CRCD(0x1d944426,"not_focusable")))
+	{
+		// sets a flag
+		SetChecksumTag(NONAME, CRCD(0xf33a3321,"tag_not_focusable"));
+	}
+	else if (pProps->ContainsFlag(CRCD(0x5cdecf29,"focusable")))
+	{
+		RemoveFlagTag(CRCD(0xf33a3321,"tag_not_focusable"));
+	}
+	
+	uint32 local_id;
+	if (pProps->GetChecksum(CRCD(0xa2a5defe,"local_id"), &local_id))
+	{
+		SetChecksumTag(CRCD(0x9494a525,"tag_local_id"), local_id); 
+	}
+
+	if (pProps->ContainsFlag(CRCD(0xff85d4d7,"unpause")))
+	{
+		SetMorphPausedState(false);
+	}
+
+	if ( pProps->ContainsFlag( CRCD(0xe5d6fe3e,"block_events") ) )
+	{
+		m_object_flags |= vEVENTS_BLOCKED;
+	}
+	else if ( pProps->ContainsFlag( CRCD(0xd5614a50,"unblock_events") ) )
+	{
+		m_object_flags &= ~vEVENTS_BLOCKED;
+	}
+
+	if ( pProps->ContainsFlag( CRCD(0x5b6634d4,"hide") ) )
+	{
+		m_object_flags |= vHIDDEN;
+	}
+	else if ( pProps->ContainsFlag( CRCD(0xb60d1f35,"unhide") ) )
+	{
+		m_object_flags &= ~vHIDDEN;
+		m_object_flags |= vCHANGED_STATIC_PROPS;
+	}
+	
+	CObject::SetProperties(pProps);
+		
+	return;
+}
+
+
+void CScreenElement::WritePropertiesToStruct( Script::CStruct *pStruct )
+{
+	// alpha value
+	pStruct->AddFloat( CRCD(0x2f1fc695,"alpha"), m_local_props.alpha );
+	
+	// rgba array
+	Image::RGBA current_rgba = m_local_props.GetRGBA();
+	Script::CArray* p_rgba = new Script::CArray();
+	p_rgba->SetSizeAndType( 4, ESYMBOLTYPE_INTEGER );
+	p_rgba->SetInteger( 0, current_rgba.r );
+	p_rgba->SetInteger( 1, current_rgba.g );
+	p_rgba->SetInteger( 2, current_rgba.b );
+	p_rgba->SetInteger( 3, current_rgba.a );
+	pStruct->AddArray( CRCD(0x3f6bcdba,"rgba"), p_rgba );
+	Script::CleanUpArray( p_rgba );
+	delete p_rgba;
+
+	// scale value
+	float scaleX = m_local_props.GetScaleX();
+	float scaleY = m_local_props.GetScaleY();
+	// only add a pair if they're different
+	if ( scaleX == scaleY )
+		pStruct->AddFloat( CRCD(0x13b9da7b,"scale"), scaleX );
+	else
+		pStruct->AddPair( CRCD(0x13b9da7b,"scale"), scaleX, scaleY );
+		
+	// position
+	pStruct->AddPair( CRCD(0x7f261953,"pos"), m_local_props.GetScreenPosX(), m_local_props.GetScreenPosY() );
+
+	// are the events blocked?
+	if ( EventsBlocked() )
+		pStruct->AddInteger( CRCD(0x73cb6d65,"events_blocked"), 1 );
+	else
+		pStruct->AddInteger( CRCD(0x73cb6d65,"events_blocked"), 0 );
+}
+
+
+/*
+	Another important entry point to CScreenElement from script. Sets the elements morphable
+	(as opposed to static) properties.
+	
+	Note: a virtual function, so can be extended by subclasses.
+	Note: SetMorph() in a subclass should call this function before doing its own logic
+*/
+void CScreenElement::SetMorph(Script::CStruct *pProps)
+{	
+	float pos_x, pos_y;
+	if (resolve_pos(pProps, &pos_x, &pos_y))
+	{
+		SetPos(pos_x, pos_y);
+	}
+
+	// TODO: temp hack for restoring alpha values
+	if ( pProps->ContainsFlag( CRCD(0x5d8bb535,"restore_alpha") ) )
+		SetAlpha( m_originalAlpha );
+	
+	float alpha;
+	if ( pProps->GetFloat(CRCD(0x2f1fc695,"alpha"), &alpha) )
+	{
+		// TODO: temp hack for remembering alpha values		
+		if ( pProps->ContainsFlag( CRCD(0xff8df26b,"remember_alpha") ) )
+			m_originalAlpha = m_local_props.alpha;
+		
+		SetAlpha( alpha );
+	}
+
+	float scale;
+	int relative_scale = 0;
+	if ( pProps->ContainsFlag( CRCD(0xe1f4711f,"relative_scale") ) )
+		relative_scale = 1;
+	if (pProps->GetFloat(CRCD(0x13b9da7b,"scale"), &scale))
+	{
+		SetScale(scale, scale, relative_scale );
+	}
+	Script::CPair scale_pair;
+	if (pProps->GetPair(CRCD(0x13b9da7b,"scale"), &scale_pair))
+	{
+		SetScale(scale_pair.mX, scale_pair.mY, relative_scale);
+	}
+
+	float desired_time;
+	Tmr::Time anim_time;
+	if (pProps->GetFloat(CRCD(0x906b67ba,"time"), &desired_time))
+	{
+		anim_time = (Tmr::Time) (desired_time * (float) Tmr::vRESOLUTION);
+	}
+	else
+		anim_time = 0;
+	SetAnimTime(anim_time);
+
+	uint32 anim_type = 0;
+	// clear affected bits
+	m_object_flags &= ~(vANIM_BIT_MASK << vANIM_FIRST_BIT);
+	if (pProps->GetChecksum(CRCD(0x98549ba4,"anim"), &anim_type))
+	{
+		if (anim_type == CRCD(0xf5b95ce4, "fast_out"))
+			m_object_flags |= vANIM_FAST_OUT << vANIM_FIRST_BIT;
+		else if (anim_type == CRCD(0x6aa7dd49, "fast_in"))
+			m_object_flags |= vANIM_FAST_IN << vANIM_FIRST_BIT;
+		else if (anim_type == CRCD(0x768f6843, "gentle"))
+			m_object_flags |= vANIM_SLOW_INOUT << vANIM_FIRST_BIT;
+	}
+	if (!anim_type)
+	{
+		m_object_flags |= (vANIM_LINEAR << vANIM_FIRST_BIT);
+	}
+
+	// rgba values
+	Image::RGBA rgba;
+	if ( resolve_rgba( pProps, CRCD(0x3f6bcdba,"rgba"), &rgba ) )
+	{
+		SetRGBA( rgba, DONT_FORCE_INSTANT );
+	}
+
+}
+
+
+
+
+/*
+	This function must be implemented for access to member commands from script.
+*/
+bool CScreenElement::CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript )
+{
+	if (Checksum == CRCD(0x6c63c7c5, "SetProps"))
+	{
+		SetProperties(pParams);
+	}
+    else if (Checksum == CRCD(0xdd0c8df3, "DoMorph"))
+	{
+		float time;
+		if (pParams->GetFloat(CRCD(0x906b67ba,"time"), &time))
+		{
+			Tmr::Time anim_time = (Tmr::Time) (time * (float) Tmr::vRESOLUTION);
+			pScript->WaitTime(anim_time);
+		}
+
+		SetMorph(pParams);
+	}
+	else if (Checksum == CRCD(0xc6870028, "Die"))
+	{
+		CScreenElementManager* p_manager = CScreenElementManager::Instance();		
+		
+		CScreenElementPtr p_parent = mp_parent;
+		p_parent->SetChildLockState(UNLOCK);
+		
+		// will kill this screen element and all its children
+		// THIS IS POTENTIALLY DANGEROUS, LEAVE THIS FUNCTION ASAP
+		p_manager->DestroyElement(m_id, CScreenElementManager::RECURSE, CScreenElementManager::DONT_PRESERVE_PARENT, pScript);
+		
+		p_parent->SetChildLockState(LOCK);
+		return true;
+	}
+	else if ( Checksum == CRCD(0x9492f814, "GetProps" ) )
+	{
+		WritePropertiesToStruct( pScript->GetParams() );
+	}
+	else
+	{
+		return CObject::CallMemberFunction(Checksum, pParams, pScript);
+	}
+
+	return true;
+}
+
+
+
+
+/*
+	Update Procedure:
+	
+	(Recurse through all elements in tree, starting at root)
+	
+	For each element:
+	1. Update local position, scale, alpha
+	2. Update summed position, scale, alpha
+	3. Update: (optional)
+		-properties of children
+		-underlying CSprite or CText object(s)
+		-underlying 3D object(s)
+		-own base_w, base_h dimensions
+	4. Run these steps on all children
+
+	Events may be launched at various points.
+*/
+void CScreenElement::UpdateProperties()
+{
+	#ifdef __NOPT_ASSERT__
+	debug_verify_integrity();
+	#endif
+	//Dbg_MsgAssert(m_id && m_id != 0xDEADBEEF, ("this screen element seems to have been deleted"));
+	
+	Tmr::Time current_time = Tmr::GetTime();
+	
+	AddReference();
+	
+	// for convenience -- once element is being used, we can assume it's locked
+	SetChildLockState(LOCK);
+	
+	// only do expensive calculation when change occurs
+	bool needs_summed_pos_calc = false;
+
+	// Update 3D position, if any
+	if (m_object_flags & CScreenElement::v3D_POS)
+	{
+		Nx::CViewport *p_viewport = Nx::CViewportManager::sGetActiveViewport();
+		Dbg_MsgAssert(p_viewport, ("Can't find an active viewport"));
+
+		float screen_pos_x, screen_pos_y;
+		Nx::ZBufferValue screen_pos_z;
+		float scale_3d;
+		scale_3d = p_viewport->TransformToScreenCoord(m_target_local_props.GetWorldPos(), screen_pos_x, screen_pos_y, screen_pos_z);
+		if (scale_3d >= 0.0f)
+		{
+			m_target_local_props.SetScreenPos(screen_pos_x, screen_pos_y, screen_pos_z);
+			SetScale(scale_3d, scale_3d, true);		// set to relative scale
+			m_object_flags &= ~CScreenElement::v3D_CULLED;
+		} else {
+			m_target_local_props.SetScreenPos(-320.0f, -224.0f);
+			m_object_flags |= CScreenElement::v3D_CULLED;
+		}
+		compute_ul_pos(m_target_local_props);
+
+		if (mp_parent)
+		{
+			Dbg_MsgAssert(!(mp_parent->m_object_flags & CScreenElement::v3D_POS), ("Can't handle having 3D parent"));
+		}
+
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	}
+
+	// continue animation of this element
+	if ((m_object_flags & vNEEDS_LOCAL_POS_CALC) && !(m_object_flags & vMORPHING_PAUSED2))
+	{
+		if (m_key_time)
+		{
+			float pct_changed = (float) (current_time - m_base_time) / (float) m_key_time;
+			// if element has reached its new destination, end animation
+			if (current_time - m_base_time >= m_key_time)
+			{
+				pct_changed = 1.0f;
+				m_key_time = 0;
+			}
+	
+			float motion_grad;
+			int anim_type = ((m_object_flags >> vANIM_FIRST_BIT) & vANIM_BIT_MASK);
+			switch (anim_type)
+			{
+				case vANIM_FAST_OUT:
+					motion_grad = 2.0f * pct_changed - pct_changed * pct_changed;
+					break;
+				case vANIM_FAST_IN:
+					motion_grad = pct_changed * pct_changed;
+					break;
+				case vANIM_SLOW_INOUT:
+					motion_grad = (3.0f - 2.0f * pct_changed) * pct_changed * pct_changed;
+					break;
+				default:
+					motion_grad = pct_changed;
+					break;
+			}
+			
+			float motion_inc = (motion_grad - m_last_motion_grad) / (1.0f - m_last_motion_grad);
+			float pct_changed_inc = (pct_changed - m_last_pct_changed) / (1.0f - m_last_pct_changed);
+			m_local_props.SetScreenPos(m_local_props.GetScreenPosX() + (m_target_local_props.GetScreenPosX() - m_local_props.GetScreenPosX()) * motion_inc, 
+									   m_local_props.GetScreenPosY() + (m_target_local_props.GetScreenPosY() - m_local_props.GetScreenPosY()) * motion_inc);
+			m_local_props.SetScale(m_local_props.GetScaleX() + (m_target_local_props.GetScaleX() - m_local_props.GetScaleX()) * pct_changed_inc,
+								   m_local_props.GetScaleY() + (m_target_local_props.GetScaleY() - m_local_props.GetScaleY()) * pct_changed_inc);
+			
+			m_local_props.SetRotate( m_local_props.GetRotate() + ( m_target_local_props.GetRotate() - m_local_props.GetRotate() ) * pct_changed_inc);
+			
+			m_local_props.alpha = m_local_props.alpha + (m_target_local_props.alpha - m_local_props.alpha) * pct_changed_inc;
+			compute_ul_pos(m_local_props);
+
+			// figure any new rgba values 
+			Image::RGBA current_rgba = m_local_props.GetRGBA();
+			Image::RGBA target_rgba = m_target_local_props.GetRGBA();
+			Image::RGBA new_rgba;
+			new_rgba.r = (int)( current_rgba.r + ( target_rgba.r - current_rgba.r ) * pct_changed_inc );
+			new_rgba.g = (int)( current_rgba.g + ( target_rgba.g - current_rgba.g ) * pct_changed_inc );
+			new_rgba.b = (int)( current_rgba.b + ( target_rgba.b - current_rgba.b ) * pct_changed_inc );
+			new_rgba.a = (int)( current_rgba.a + ( target_rgba.a - current_rgba.a ) * pct_changed_inc );
+			m_local_props.SetRGBA( new_rgba );
+			
+			m_last_motion_grad = motion_grad;
+			m_last_pct_changed = pct_changed;
+		
+			/*
+			if (m_key_time == 0)
+			{
+				//m_local_props = m_target_local_props;
+				m_local_props.ulx = m_local_props.x - m_local_props.scalex * m_base_w * (m_just_x + 1.0f) / 2.0f;
+				m_local_props.uly = m_local_props.y - m_local_props.scaley * m_base_h * (m_just_y + 1.0f) / 2.0f;
+				printf("Hurk!! target UL=(%.2f,%.2f) local UL=(%.2f,%.2f)\n", 
+					   m_target_local_props.ulx, m_target_local_props.uly,
+					   m_local_props.ulx, m_local_props.uly);
+			}
+			*/
+		}
+		else
+		{
+			m_local_props = m_target_local_props;
+			m_object_flags &= ~vNEEDS_LOCAL_POS_CALC;
+		}
+		needs_summed_pos_calc = true;
+		
+		/*
+		if (GetID() == Script::GenerateCRC("test_h_menu"))
+			printf("local_pos=(%.2f,%.2f), ul=(%.2f,%.2f), dims=(%.2f,%.2f)\n", 
+				   m_local_props.x, m_local_props.y, 
+				   m_local_props.ulx, m_local_props.uly, 
+				   m_base_w, m_base_h);
+		*/
+	} // end if
+
+	if (mp_parent && (mp_parent->m_object_flags & vDID_SUMMED_POS_CALC))
+		needs_summed_pos_calc = true;
+	m_object_flags &= ~vDID_SUMMED_POS_CALC;
+	
+	if (needs_summed_pos_calc)
+	{
+		if (mp_parent && !(m_object_flags & CScreenElement::v3D_POS))
+		{
+			m_summed_props.SetScale(m_local_props.GetScaleX() * mp_parent->m_summed_props.GetScaleX(),
+									m_local_props.GetScaleY() * mp_parent->m_summed_props.GetScaleY());
+			m_summed_props.SetScreenPos(mp_parent->m_summed_props.GetScreenUpperLeftX() + m_local_props.GetScreenPosX() * mp_parent->m_summed_props.GetScaleX(),
+										mp_parent->m_summed_props.GetScreenUpperLeftY() + m_local_props.GetScreenPosY() * mp_parent->m_summed_props.GetScaleY());
+			m_summed_props.alpha = m_local_props.alpha * mp_parent->m_summed_props.alpha;
+			m_summed_props.SetScreenUpperLeft(mp_parent->m_summed_props.GetScreenUpperLeftX() + m_local_props.GetScreenUpperLeftX() * mp_parent->m_summed_props.GetScaleX(),
+											  mp_parent->m_summed_props.GetScreenUpperLeftY() + m_local_props.GetScreenUpperLeftY() * mp_parent->m_summed_props.GetScaleY());
+		}
+		else
+			m_summed_props = m_local_props;
+		m_object_flags |= vDID_SUMMED_POS_CALC;
+	} // end if
+
+	update();
+	
+	m_object_flags &= ~vCHANGED_STATIC_PROPS;
+	if (m_object_flags & vMORPHING_PAUSED)
+		m_object_flags |= vMORPHING_PAUSED2;
+
+	// update all children
+	//CScreenElementPtr pChild = mp_child_list;
+	CScreenElement * pChild = mp_child_list;
+	while(pChild)
+	{
+		pChild->UpdateProperties();
+		pChild = pChild->GetNextSibling();
+	}
+
+	RemoveReference();
+}
+
+
+
+
+CScreenElementPtr CScreenElement::get_child_by_id_or_index(uint32 id, int *pIndex)
+{
+	int count = 0;
+	CScreenElementPtr pChild = mp_child_list;
+	while(pChild)
+	{
+		// if we are getting by index
+		if (id == 0) 
+		{
+			if (*pIndex == count)
+				break;
+		}
+		// if we are getting by ID
+		else if (pChild->GetID() == id) 
+		{
+			// save index
+			if (pIndex)
+				*pIndex = count;
+			break;
+		}
+
+		pChild = pChild->GetNextSibling();
+		count++;
+	}
+
+	return pChild;
+}
+
+
+CScreenElementPtr CScreenElement::get_parent()
+{
+	return mp_parent;
+}
+
+
+CWindowElement * CScreenElement::get_window()
+{
+	CScreenElement * p_parent = get_parent();
+	while (p_parent)
+	{
+		if (p_parent->GetType() == (sint) TYPE_WINDOW_ELEMENT)
+		{
+			return static_cast(p_parent);
+		}
+
+		p_parent = p_parent->get_parent();
+	}
+
+	Dbg_MsgAssert(0, ("Could not find window for CScreenElement."));
+	return NULL;
+}
+
+/*
+	Private function, should not be called by anything except
+	CScreenElementManager.
+*/
+void CScreenElement::set_parent(const CScreenElementPtr &pParent, EPosRecalc recalculatePosition)
+{
+	if (pParent)	
+	{
+		Dbg_MsgAssert(!(pParent->m_object_flags & CScreenElement::vCHILD_LOCK), ("can't add child %s -- locked parent - %s", Script::FindChecksumName(m_id), Script::FindChecksumName( pParent->GetID())));
+		//Ryan("parent of screen element %s is now %s\n", Script::FindChecksumName(GetID()), Script::FindChecksumName(pParent->GetID()));
+		#ifdef	__NOPT_ASSERT__
+		if (pParent->GetType() == CScreenElement::TYPE_TEXT_BLOCK_ELEMENT)
+		{
+			Dbg_MsgAssert(GetType() == CScreenElement::TYPE_TEXT_ELEMENT,("TextBlockElement %s parenting child %s that is NOT a CTextElement (it's %s)\n",
+			Script::FindChecksumName(pParent->GetID()), Script::FindChecksumName(GetID()), Script::FindChecksumName(GetType())));
+		}
+		#endif
+	}
+
+
+
+	if (mp_parent)
+	{
+		// remove from existing parent's child list before setting new
+		// (there are max. 5 references to remove)
+		
+		
+		CScreenElementPtr p_elem = mp_parent->mp_child_list;
+		while(p_elem)
+		{
+			if (p_elem == this)
+			{
+				if (GetPrevSibling())
+				{
+					GetPrevSibling()->mp_next_sibling = GetNextSibling();
+				}
+				else
+				{
+					// ref count won't change for next sibling, since was ref'd by this
+					mp_parent->mp_child_list = GetNextSibling();
+				}
+				
+				if (GetNextSibling())
+				{
+					// Either original parent or original prev sibling is now pointing to original next sibling
+					// Would both add and remove a reference on next, but they cancel each other, so do nothing
+
+					GetNextSibling()->mp_prev_sibling = GetPrevSibling();
+				}
+
+				break;
+			}
+			
+			p_elem = p_elem->GetNextSibling();
+		}
+	}
+
+	// add to new parent's child list
+	// (at end of list)
+	mp_parent = pParent;
+	if (mp_parent) 
+	{
+		CScreenElementPtr p_prev_sib = mp_parent->GetFirstChild();
+		if (p_prev_sib)
+		{
+			while(p_prev_sib->GetNextSibling())
+				p_prev_sib = p_prev_sib->GetNextSibling();
+			p_prev_sib->mp_next_sibling = this;
+		}
+		else
+			mp_parent->mp_child_list = this;
+		mp_prev_sibling = p_prev_sib;
+	}
+	else
+		// no parent, therefore no siblings
+		mp_prev_sibling = NULL;
+	mp_next_sibling = NULL;
+
+	if (pParent)
+		auto_set_z_priorities_recursive(pParent->m_z_priority + AUTO_Z_SPACE);
+	else
+		auto_set_z_priorities_recursive(0.0f);
+}
+
+
+
+
+/*
+	...given element's dimensions, justification, scale, and target position.
+*/
+void CScreenElement::compute_ul_pos(ConcatProps &props)
+{
+	props.SetScreenUpperLeft(props.GetScreenPosX() - props.GetScaleX() * m_base_w * (m_just_x + 1.0f) / 2.0f,
+							 props.GetScreenPosY() - props.GetScaleY() * m_base_h * (m_just_y + 1.0f) / 2.0f);
+}
+
+
+
+
+// called by set_parent()
+void CScreenElement::auto_set_z_priorities_recursive(float topPriority)
+{
+	if (!(m_object_flags & vCUSTOM_Z_PRIORITY))
+	{
+		m_object_flags |= vCHANGED_STATIC_PROPS;
+		m_z_priority = topPriority;
+	}
+	
+	CScreenElementPtr pChild = mp_child_list;
+	while(pChild)
+	{
+		pChild->auto_set_z_priorities_recursive(topPriority + AUTO_Z_SPACE);
+		pChild = pChild->GetNextSibling();
+	}	
+}
+
+
+
+
+/* 
+	Resolves position info stored in a script structure:
+	
+	pos=(x y)
+	pos={relative (x y)}
+	pos={proportional (x y)}
+	
+	Returns false if no position info found.
+*/
+bool CScreenElement::resolve_pos(Script::CStruct *pProps, float *pX, float *pY)
+{
+	Script::CPair pos_pair;
+	Script::CStruct *p_pos_struct;
+	if (pProps->GetStructure(CRCD(0x7f261953,"pos"), &p_pos_struct))
+	{
+		p_pos_struct->GetPair(NONAME, &pos_pair, true);
+		if (p_pos_struct->ContainsFlag(0x91a4c826)) // "relative"
+		{
+			*pX = m_target_local_props.GetScreenPosX() + pos_pair.mX;
+			*pY = m_target_local_props.GetScreenPosY() + pos_pair.mY;
+		}
+		else if (p_pos_struct->ContainsFlag(0xb922906a)) // "proportional"
+		{
+			Dbg_Assert(mp_parent);
+			*pX = pos_pair.mX * mp_parent->GetBaseW();
+			*pY = pos_pair.mY * mp_parent->GetBaseH();
+		}
+		else
+			Dbg_MsgAssert(0, ("position needs 'relative' or 'proportional' flag"));
+		return true;
+	}	
+	else if (pProps->GetPair(CRCD(0x7f261953,"pos"), &pos_pair))
+	{
+		*pX = pos_pair.mX;
+		*pY = pos_pair.mY;
+		return true;
+	}
+	
+	return false;
+}
+
+
+
+
+/* 
+	Resolves justification info stored in a script structure, e.g.:
+	
+	just=[left top]
+	just=[center center]
+	just=[right bottom]
+	just=(-1.0 1.0)
+	just=(.65 3.75)
+	
+	Returns false if no justification info found.
+*/
+bool CScreenElement::resolve_just(Script::CStruct *pProps, const uint32 RefName, float *pHJust, float *pVJust)
+{
+	Script::CArray *p_just;
+	if (pProps->GetArray(RefName, &p_just))
+	{
+		Script::ESymbolType type = (Script::ESymbolType) p_just->GetType();
+		switch(type)
+		{
+			case ESYMBOLTYPE_FLOAT:
+				*pHJust = p_just->GetFloat(0);
+				*pVJust = p_just->GetFloat(1);
+				break;
+			case ESYMBOLTYPE_NAME:
+			{
+				float just[2];
+				for (int i = 0; i < 2; i++)
+				{
+					uint32 crc = p_just->GetNameChecksum(i); 
+					if (crc == CRCD(0x85981897, "left") || crc == CRCD(0xe126e035, "top"))
+						just[i] = -1.0f;
+					else if (crc == CRCD(0x4b358aeb, "right") || crc == CRCD(0x76a08d5b, "bottom"))
+						just[i] = 1.0f;
+					else
+						just[i] = 0;
+				}
+				*pHJust = just[0];
+				*pVJust = just[1];
+				break;
+			}
+			default:
+				Dbg_MsgAssert(0, ("wrong type for justification"));
+				break;
+		}
+		return true;
+	}
+	return false;
+}
+
+
+
+
+/*
+	Retrieves RGBA information from a script struct.
+*/
+bool CScreenElement::resolve_rgba(Script::CStruct *pProps, const uint32 RefName, Image::RGBA *pRGBA)
+{
+	Script::CArray *p_color;
+	if (pProps->GetArray(RefName, &p_color))
+	{
+		uint32 rgba = 0;
+		int size = p_color->GetSize();
+		Dbg_MsgAssert(size >= 3 && size <= 4, ("wrong size %d for color array", size));
+		for (int i = 0; i < size; i++) 
+		{
+			rgba |= (p_color->GetInteger(i) & 255) << (i*8);
+		}
+#ifdef __PLAT_NGC__
+		rgba = ( ( rgba & 0xff000000 ) >> 24 ) | ( ( rgba & 0xff0000 ) >> 8 ) | ( ( rgba & 0xff00 ) << 8 ) | ( ( rgba & 0xff ) << 24 );
+#endif		// __PLAT_NGC__
+		*pRGBA = *((Image::RGBA *) &rgba);
+		return true;
+	}
+	return false;
+}
+
+
+
+
+#ifdef __NOPT_ASSERT__
+/*
+	Used to verify that screen element has not been corrupted.
+*/
+void CScreenElement::debug_verify_integrity()
+{
+	
+// Mick - Removed for now, as it's taking up rather a lot of time....	
+	return;
+	
+	Dbg_MsgAssert(Mem::Valid(this), ("not a valid block!"));
+	Dbg_MsgAssert(m_id, ("screen element has id of 0, that shouldn't happen"));
+	Dbg_MsgAssert(m_id != 0xDEADBEEF, ("screen element has been deleted"));
+
+	CScreenElementManager* p_manager = CScreenElementManager::Instance();
+	CScreenElementPtr p_in_tracker = p_manager->GetElement(m_id);
+	Dbg_MsgAssert(p_in_tracker == this, ("this element seems to have been lost from tracking"));
+	
+	if (mp_parent)
+	{
+		Dbg_MsgAssert(!((uint) mp_parent.Convert() & 3), ("parent has goofy address"));
+		Dbg_MsgAssert(Mem::Valid(mp_parent), ("parent not a valid block!"));
+		CScreenElementPtr p_sib = mp_parent->mp_child_list;
+		while(p_sib)
+		{
+			if (p_sib == this)
+				break;
+			p_sib = p_sib->GetNextSibling();
+		}
+		Dbg_MsgAssert(p_sib, ("this element not in its parent's child list"));
+	}
+
+	CScreenElementPtr p_child = mp_child_list;
+	while(p_child)
+	{
+		Dbg_MsgAssert(!((uint) (p_child.Convert()) & 3), ("child has goofy address"));
+		Dbg_MsgAssert(Mem::Valid(p_child), ("child not a valid block!"));
+		Dbg_MsgAssert(p_child->mp_parent == this, ("this element not parent of child"));
+		p_child = p_child->GetNextSibling();
+	}
+}
+#endif
+
+
+
+
+CContainerElement::CContainerElement()
+{
+	//Ryan("I am a new CContainerElement\n");
+	m_focusable_child = 0;
+
+	SetType(CScreenElement::TYPE_CONTAINER_ELEMENT);
+}
+
+
+
+
+CContainerElement::~CContainerElement()
+{
+	//Ryan("Destroying CContainerElement\n");
+}
+
+
+
+
+void CContainerElement::SetProperties(Script::CStruct *pProps)
+{
+	pProps->GetChecksum("focusable_child", &m_focusable_child);
+	
+	CScreenElement::SetProperties(pProps);
+}
+
+
+
+
+bool CContainerElement::PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast)
+{
+	if (!Obj::CObject::PassTargetedEvent(pEvent))
+	{
+		return false;
+	}
+	
+	if (pEvent->GetType() == Obj::CEvent::TYPE_FOCUS || pEvent->GetType() == Obj::CEvent::TYPE_UNFOCUS)
+	{
+		// forward events of these types to specified focusable child
+		if (m_focusable_child) 
+		{
+			int controller = Obj::CEvent::sExtractControllerIndex(pEvent);
+			Script::CStruct data;
+			data.AddInteger(CRCD(0xb30d9965,"controller"), controller);
+
+			Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+			p_tracker->LaunchEvent(pEvent->GetType(), m_focusable_child, Obj::CEvent::vSYSTEM_EVENT, &data);
+		}
+		pEvent->MarkHandled(m_id);
+	}
+	return true;
+}
+
+bool CScreenElement::EventsBlocked()
+{
+	// recurse back up through the parents to find any parent that is hidden
+	if ( ( m_object_flags & vEVENTS_BLOCKED) || ( mp_parent && mp_parent->EventsBlocked() ) )
+		 return true;
+	return false;
+}
+
+bool CScreenElement::IsHidden()
+{
+	// recurse back up through the parents to find any parent that is hidden
+	if ( ( m_object_flags & vHIDDEN ) || ( mp_parent && mp_parent->IsHidden() ) )
+		 return true;
+	return false;
+}
+
+} // end namespace
diff --git a/Code/Gfx/2D/ScreenElement2.h b/Code/Gfx/2D/ScreenElement2.h
new file mode 100644
index 0000000..5004ff4
--- /dev/null
+++ b/Code/Gfx/2D/ScreenElement2.h
@@ -0,0 +1,457 @@
+#ifndef __GFX_2D_SCREENELEMENT2_H__
+#define __GFX_2D_SCREENELEMENT2_H__
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+#include 
+
+#include 
+#include 
+
+
+namespace Front
+{
+
+class CScreenElement;
+class CWindowElement;
+typedef Obj::CSmtPtr CScreenElementPtr;
+
+
+
+
+/*
+	The base class for all screen elements. Screen elements include things like onscreen text, HUD sprites,
+	items in menus, etc. Every screen element has 2D coordinates and (rectangular) dimensions. Screen elements
+	can be "children" of other screen elements, whereby the child uses coordinates relative the upper left corner
+	of the parent. If the parent is moved, all its children will be moved along with it.
+	
+	A screen element also has a pair of scale values, which are independent of its base width and height. A
+	screen element will generally have a fixed base width and height, but the scale can be varied for animation
+	purposes, making the element appear to grow or shrink in size. Scale changes to an element are also passed to
+	children.
+	
+	The same idea applies to alpha and RGBA. RGBA is a static setting for the screen element, but alpha is a 
+	separate morpheable multiplier that can be used for animation.  
+*/
+class CScreenElement : public Obj::CObject
+{
+	friend class CScreenElementManager;
+
+public:
+
+	enum EScreenElementType
+	{
+		TYPE_CONTAINER_ELEMENT						= 0x5b9da842,
+		TYPE_TEXT_ELEMENT							= 0x5200dfb6,
+		TYPE_VMENU									= 0x130ef802,
+		TYPE_HMENU									= 0xccded1e1,
+		TYPE_TEXT_BLOCK_ELEMENT						= 0x40d92263,
+		TYPE_SPRITE_ELEMENT							= 0xb12b510a,
+		TYPE_VSCROLLING_MENU						= 0x84298a3c,
+		TYPE_HSCROLLING_MENU						= 0x2900e3d0,
+		TYPE_ELEMENT_3D								= 0x82c59d78,
+		TYPE_WINDOW_ELEMENT							= 0xa13adbb7,
+	};
+
+	enum EPosRecalc
+	{
+		vDONT_RECALC_POS	= 0,
+		vRECALC_POS			= 1,
+	};
+	
+	// flags 8-23 belong to CScreenElement, 24-31 can be used by subclasses
+	enum 
+	{
+		// One of these values will fill bits 8-9
+		// in m_object_flags
+		vANIM_LINEAR		= 0,
+		vANIM_FAST_OUT		= 1,
+		vANIM_FAST_IN 		= 2,
+		vANIM_SLOW_INOUT	= 3,
+		vANIM_FIRST_BIT		= 8,
+		vANIM_BIT_MASK		= 3,
+		
+		/* 
+			Should be set whenever one of the following changes:
+				-a morpheable property (pos, alpha, scale, etc...)
+				-active morph time
+				-justification
+				-base width, height
+				
+			UpdateProperties() will respond by calculating current morph frame,
+			Once element has finished morph, flag will be turned off.
+		*/ 
+		vNEEDS_LOCAL_POS_CALC 	= (1<<10),
+		/*
+			Set by UpdatePropertie
+			s() after fresh absolute morph attributes (pos, scale, alpha,
+			etc.) are computed. This happens a) after new morph frame calculated for element, or
+			b) after new absolute morph attributes computed for parent.
+		
+			Cleared upon next call to UpdateProperties (if not set again). Can be used by update()
+			to decide whether or not to do expensive calculations that are only necessary when
+			element moves.
+		*/
+		vDID_SUMMED_POS_CALC 	= (1<<11),
+		/* 
+			Should be set when change made to a static property (i.e. a non-morpheable one).
+			Virtual update() function can use to decide whether or not to do expensive
+			calculation.
+			
+			Cleared by UpdateProperties() after call to virtual update() function
+		*/
+		vCHANGED_STATIC_PROPS 	= (1<<12),
+		// if set dimensions have been set from outside, don't allow to resize self
+		vFORCED_DIMS			= (1<<13),
+		// can't add children if this flags is set
+		vCHILD_LOCK				= (1<<14),
+		// if set, this element (and all its children) will
+		vCUSTOM_Z_PRIORITY		= (1<<15),
+		vIS_SCREEN_ELEMENT		= (1<<16),
+		vMORPHING_PAUSED		= (1<<17),
+		vMORPHING_PAUSED2		= (1<<18), // a second stage is necessary to allow one update
+		v3D_POS					= (1<<19), // Element is in 3D space
+		v3D_CULLED				= (1<<20), // Element in 3D space is culled
+		vEVENTS_BLOCKED			= (1<<21), // Element is not accepting any events
+		vHIDDEN					= (1<<22), // Element is hiddden
+	};
+	
+	/* 
+		These are properties which can be concatenated down the parent-child tree.
+		
+		There are local properties (relative to parent), and absolute properties
+		(concatenation of own local properties with all those of ancestors).
+	*/
+	struct ConcatProps
+	{
+	public:
+		void				SetScreenPos(float x, float y);
+		void				SetScreenPos(float x, float y, Nx::ZBufferValue z);
+		void				SetScreenUpperLeft(float x, float y);
+		void				SetWorldPos(const Mth::Vector & pos);
+		void				SetScale(float scale_x, float scale_y);
+		void				SetAbsoluteScale(float scale_x, float scale_y);
+
+		void				SetRotate( float rot );
+		void				SetRGBA( Image::RGBA rgba );
+
+		float				GetScreenPosX() const;
+		float				GetScreenPosY() const;
+		Nx::ZBufferValue	GetScreenPosZ() const;
+		float				GetScreenUpperLeftX() const;
+		float				GetScreenUpperLeftY() const;
+		const Mth::Vector &	GetWorldPos() const;
+		float				GetScaleX() const;
+		float				GetScaleY() const;
+		float				GetAbsoluteScaleX() const;
+		float				GetAbsoluteScaleY() const;
+		
+		float				GetRotate() const;
+		Image::RGBA			GetRGBA() const;
+
+		// These values do not need to be protected, since they aren't treated differently in 3D
+
+		// Alpha value
+		float				alpha;
+
+	private:
+		// 3D position of anchor point
+		Mth::Vector			world_pos;
+
+		// 2D position of anchor point
+		float				screen_x, screen_y;
+		Nx::ZBufferValue	screen_z;				// If using z-buffer sorting
+
+		// position of upper-left hand corner (for quick lookup)
+		float				ulx, uly;
+
+		// final render scale
+		float				scalex, scaley;
+		
+		// absolute scale used for calling DoMorph with a relative scale and now for 3D positions
+		float				absoluteScalex;
+		float				absoluteScaley;
+
+		float				m_rot_angle;
+		Image::RGBA			m_rgba;
+	};
+
+	static const float vJUST_LEFT;
+	static const float vJUST_TOP;
+	static const float vJUST_CENTER;
+	static const float vJUST_RIGHT;
+	static const float vJUST_BOTTOM;
+
+							CScreenElement();
+	virtual					~CScreenElement();
+
+	// "absolute" means concatenated
+	float					GetAbsX();
+	float					GetAbsY();
+	float					GetAbsW();
+	float					GetAbsH();
+	float 					GetBaseW() {return m_base_w;}
+	float 					GetBaseH() {return m_base_h;}
+	float					GetScaleX() {return m_local_props.GetScaleX();}
+	float					GetScaleY() {return m_local_props.GetScaleY();}
+	void					GetLocalPos(float *pX, float *pY);
+	void					GetLocalULPos(float *pX, float *pY);
+	void					GetJust(float *pJustX, float *pJustY);
+	
+	enum EForceInstant
+	{
+		DONT_FORCE_INSTANT	= 0,
+		FORCE_INSTANT,
+	};
+	void					SetPos(float x, float y, EForceInstant forceInstant = DONT_FORCE_INSTANT);
+	void					SetPos3D(const Mth::Vector &pos3D, EForceInstant forceInstant = DONT_FORCE_INSTANT);
+	enum EForceDims
+	{
+		DONT_FORCE_DIMS		= 0,
+		FORCE_DIMS,
+	};
+	void					SetDims(float w, float h, EForceDims force = DONT_FORCE_DIMS);
+	void					SetJust(float justX, float justY);
+	void					SetScale(float scaleX, float scaleY, bool relative = false, EForceInstant forceInstant = DONT_FORCE_INSTANT);
+	void					SetAlpha(float alpha, EForceInstant forceInstant = DONT_FORCE_INSTANT);
+	void					SetAnimTime(Tmr::Time animTime);
+	void					SetRGBA( Image::RGBA rgba, EForceInstant forceInstant = FORCE_INSTANT );
+	void					SetZPriority(float zPriority);
+	void					SetMorphPausedState(bool pause);
+
+	enum ELockState
+	{
+		UNLOCK,
+		LOCK,
+	};
+	void					SetChildLockState(ELockState lockState);
+
+	// These don't need to return smart pointers	
+	CScreenElement*		GetFirstChild() {return mp_child_list;}
+	CScreenElement*		GetLastChild();
+	CScreenElement*		GetPrevSibling() {return mp_prev_sibling;}
+	CScreenElement*		GetNextSibling() {return mp_next_sibling;}
+	CScreenElement*		GetChildById(uint32 id, int *pIndex = NULL);
+	CScreenElement*		GetChildByIndex(int index);
+	
+	int						CountChildren();
+	
+	// See notes before function definition
+	void					UpdateProperties();
+  	//void					Draw();
+
+	/* 
+		Versions of SetProperties(), SetMorph(), CallMemberFunction() in subclasses 
+		should call versions in this class.
+	*/
+	virtual void			SetProperties(Script::CStruct *pProps);
+	virtual void			WritePropertiesToStruct( Script::CStruct *pStruct );
+	virtual void			SetMorph(Script::CStruct *pProps);
+	virtual bool 			CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript );
+
+	bool					EventsBlocked();
+	bool					IsHidden();
+
+protected:
+
+	CScreenElementPtr		get_child_by_id_or_index(uint32 id, int *pIndex);
+	CScreenElementPtr		get_parent();
+	CWindowElement *		get_window();
+	void					set_parent(const CScreenElementPtr &pParent, EPosRecalc recalculatePosition = vRECALC_POS);
+	void					compute_ul_pos(ConcatProps &props);
+	void					auto_set_z_priorities_recursive(float topPriority);
+	
+	bool					resolve_pos(Script::CStruct *pProps, float *pX, float *pY);
+	bool					resolve_just(Script::CStruct *pProps, const uint32 RefName, float *pHJust, float *pVJust);
+	bool					resolve_rgba(Script::CStruct *pProps, const uint32 RefName, Image::RGBA *pRGBA);
+	
+public:	
+	#ifdef __NOPT_ASSERT__
+	// a debugging function
+	void					debug_verify_integrity();
+	#endif
+protected:
+	
+	/* 
+		See notes on UpdateProperties() about what this function can do.
+		
+		What it *CAN'T* do:
+		-use the summed pos values from children (these will be a frame out of date)
+		-changed the properties of the parent (the changes won't be converted to
+			local and summed positions until the next frame)
+	*/
+	virtual void			update() {;}
+	
+	CScreenElementPtr		mp_parent;
+	CScreenElementPtr		mp_child_list;
+	CScreenElementPtr		mp_next_sibling;
+	CScreenElementPtr		mp_prev_sibling;
+	
+	ConcatProps				m_local_props;
+	ConcatProps				m_target_local_props;
+
+	Tmr::Time				m_base_time;
+	// how much time must elapse since m_base_time for animation to end
+	// set to 0 when animation complete
+	Tmr::Time				m_key_time;
+	float					m_last_motion_grad;
+	float					m_last_pct_changed;
+	
+	// the concatenation of local props in ancestors and this
+	ConcatProps				m_summed_props;
+
+	// determines offset of upper-left corner of screen element
+	// relative	to anchor point
+	// -1.0	= left/top
+	//	0	= center
+	//  1.0	= bottom/right
+	float					m_just_x, m_just_y;
+	
+	// the dimensions of this screen element before scaling is applied
+	// (in pixels) (local)
+	float					m_base_w, m_base_h;
+
+	Image::RGBA				m_rgba;
+
+	static const float		AUTO_Z_SPACE;
+	
+	float					m_z_priority;
+
+	// TODO: temp hack to get preserve_original_alpha and restore_original_alpha working
+	float					m_originalAlpha;
+};
+
+
+
+
+class CContainerElement : public CScreenElement
+{
+	friend class CScreenElementManager;
+
+public:
+							CContainerElement();
+	virtual					~CContainerElement();
+
+	void					SetProperties(Script::CStruct *pProps);
+	virtual bool			PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast = false);
+	
+protected:
+
+	uint32					m_focusable_child;
+};
+
+
+inline void					CScreenElement::ConcatProps::SetScreenPos(float x, float y)
+{
+	screen_x = x;
+	screen_y = y;
+}
+
+inline void					CScreenElement::ConcatProps::SetScreenPos(float x, float y, Nx::ZBufferValue z)
+{
+	SetScreenPos(x, y);
+	screen_z = z;
+}
+
+inline void					CScreenElement::ConcatProps::SetScreenUpperLeft(float x, float y)
+{
+	ulx = x;
+	uly = y;
+}
+
+inline void					CScreenElement::ConcatProps::SetWorldPos(const Mth::Vector & pos)
+{
+	world_pos = pos;
+}
+
+inline void					CScreenElement::ConcatProps::SetScale(float scale_x, float scale_y)
+{
+	scalex = scale_x;
+	scaley = scale_y;
+}
+
+inline void					CScreenElement::ConcatProps::SetAbsoluteScale(float scale_x, float scale_y)
+{
+	absoluteScalex = scale_x;
+	absoluteScaley = scale_y;
+}
+
+inline void					CScreenElement::ConcatProps::SetRotate( float rot_angle )
+{
+	m_rot_angle = rot_angle;
+}
+
+inline void					CScreenElement::ConcatProps::SetRGBA( Image::RGBA rgba )
+{
+	m_rgba.r = rgba.r;
+	m_rgba.g = rgba.g;
+	m_rgba.b = rgba.b;
+	m_rgba.a = rgba.a;
+}
+
+inline float				CScreenElement::ConcatProps::GetScreenPosX() const
+{
+	return screen_x;
+}
+
+inline float				CScreenElement::ConcatProps::GetScreenPosY() const
+{
+	return screen_y;
+}
+
+inline Nx::ZBufferValue		CScreenElement::ConcatProps::GetScreenPosZ() const
+{
+	return screen_z;
+}
+
+inline float				CScreenElement::ConcatProps::GetScreenUpperLeftX() const
+{
+	return ulx;
+}
+
+inline float				CScreenElement::ConcatProps::GetScreenUpperLeftY() const
+{
+	return uly;
+}
+
+inline const Mth::Vector &	CScreenElement::ConcatProps::GetWorldPos() const
+{
+	return world_pos;
+}
+
+inline float				CScreenElement::ConcatProps::GetScaleX() const
+{
+	return scalex;
+}
+
+inline float				CScreenElement::ConcatProps::GetScaleY() const
+{
+	return scaley;
+}
+
+inline float				CScreenElement::ConcatProps::GetAbsoluteScaleX() const
+{
+	return absoluteScalex;
+}
+
+inline float				CScreenElement::ConcatProps::GetAbsoluteScaleY() const
+{
+	return absoluteScaley;
+}
+
+inline float				CScreenElement::ConcatProps::GetRotate() const
+{
+	return m_rot_angle;
+}
+
+inline Image::RGBA			CScreenElement::ConcatProps::GetRGBA() const
+{
+	return m_rgba;
+}
+
+
+
+} // end namespace
+
+#endif
diff --git a/Code/Gfx/2D/ScrollingMenu.cpp b/Code/Gfx/2D/ScrollingMenu.cpp
new file mode 100644
index 0000000..b34b907
--- /dev/null
+++ b/Code/Gfx/2D/ScrollingMenu.cpp
@@ -0,0 +1,385 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Front
+{
+
+
+
+
+CBaseScrollingMenu::CBaseScrollingMenu()
+{
+	RegisterWithTracker(this);
+	mp_the_menu = NULL;
+	m_selected_element_id = 0;
+
+	m_in_focus = false;
+	m_needs_update = false;
+
+	m_top_or_left_line = 0.0f;
+	// a very large number
+	m_scroll_speed = 10000.0f;
+
+	m_num_items_to_display = -1;
+	m_window_dimension = 0.0f;
+	m_regular_space_amount = -1.0f;
+	
+	SetType(CScreenElement::TYPE_VSCROLLING_MENU);
+}
+
+
+
+
+CBaseScrollingMenu::~CBaseScrollingMenu()
+{
+}
+
+
+
+
+void CBaseScrollingMenu::SetProperties(Script::CStruct *pProps)
+{
+	float just[2] = { 0.0f, 0.0f };
+	if (resolve_just(pProps, CRCD(0x67e093e4,"internal_just"), just, just+1))
+		SetInternalJust(just[0], just[1]);
+	
+	int scroll_speed;
+	if (pProps->GetInteger(CRCD(0x9020a1d1,"scroll_speed"), &scroll_speed))
+		m_scroll_speed = (float) scroll_speed;
+	
+	pProps->GetInteger(CRCD(0xc855511c,"num_items_to_show"), &m_num_items_to_display);
+
+	if (pProps->ContainsFlag(CRCD(0xed889e1f,"reset_window_top")))
+	{
+		m_top_or_left_line = 0;
+		m_needs_update = true;
+	}
+	else if (pProps->ContainsFlag(CRCD(0xc3a894f6,"reset_window_bottom")))
+	{
+		CScreenElementPtr p_child = mp_the_menu->GetLastChild();
+		float element_x, element_z, element_w, element_l;
+		compute_element_area(p_child, element_x, element_z, element_w, element_l);
+		m_top_or_left_line = element_z + element_l - m_window_dimension;
+		m_needs_update = true;
+	}
+	else if ( pProps->ContainsFlag( CRCD(0xa1f3b8bb,"reset_window") ) )
+	{
+		m_needs_update = true;
+	}
+	
+	CScreenElement::SetProperties(pProps);
+}
+
+
+
+
+bool CBaseScrollingMenu::PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast)
+{
+	
+	if (!Obj::CObject::PassTargetedEvent(pEvent))
+	{
+		return false;
+	}
+	
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+    
+    if (pEvent->GetType() == Obj::CEvent::TYPE_NOTIFY_CHILD_LOCK)
+    {
+        // we can assume that VMenu child is in place now, find it
+		get_the_menu();
+		m_needs_update = true;
+		update();
+
+		pEvent->MarkHandled(m_id);
+    }
+	if (pEvent->GetType() == Obj::CEvent::TYPE_FOCUS)
+	{
+		if (!mp_the_menu)
+			get_the_menu();
+		
+		// event is for this scrolling menu			
+		if (!m_in_focus)
+		{
+			//printf("VMenu %s receives focus event, sending to 0x%x\n", Script::FindChecksumName(m_id), m_selected_id);
+			m_in_focus = true;
+			p_tracker->LaunchEvent(Obj::CEvent::TYPE_FOCUS, mp_the_menu->GetID(), Obj::CEvent::vSYSTEM_EVENT, pEvent->GetData());		
+			pEvent->MarkHandled(m_id);
+		}
+	}
+	if (pEvent->GetType() == Obj::CEvent::TYPE_UNFOCUS && m_in_focus)
+	{
+		get_the_menu();
+		
+		//printf("VMenu %s receives focus event, sending to 0x%x\n", Script::FindChecksumName(m_id), m_selected_id);
+		m_in_focus = false;
+		p_tracker->LaunchEvent(Obj::CEvent::TYPE_UNFOCUS, mp_the_menu->GetID());		
+		pEvent->MarkHandled(m_id);
+	}
+	return true;
+}
+
+
+
+
+void CBaseScrollingMenu::pass_event_to_listener(Obj::CEvent *pEvent)
+{
+	//printf("VMenu receives event of type %s\n", Script::FindChecksumName(pEvent->GetType()));
+	
+    if (pEvent->GetType() == Obj::CEvent::TYPE_NOTIFY_CHILD_LOCK && mp_the_menu)
+    {
+        if (pEvent->GetTarget() == mp_the_menu->GetID())
+		{
+			m_needs_update = true;
+			update();
+		}
+		
+		//Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+		//p_tracker->PrintEventLog(true, 100);
+		
+		pEvent->MarkRead(m_id);
+    }
+	if (pEvent->GetType() == Obj::CEvent::TYPE_FOCUS)
+	{
+		if (!mp_the_menu)
+			get_the_menu();
+		
+		// see if focus event is for one of contained menu's children
+		CScreenElementPtr p_child = mp_the_menu->GetFirstChild();
+		while(p_child)
+		{
+			if (p_child->GetID() == pEvent->GetTarget())
+			{
+				put_element_in_view(p_child);
+				pEvent->MarkRead(m_id);
+				break;
+			}
+			p_child = p_child->GetNextSibling();
+		}
+	}
+}
+
+
+
+
+void CBaseScrollingMenu::get_the_menu()
+{
+	CScreenElementPtr p_child = GetFirstChild();
+	while(p_child)
+	{
+		if ((uint32) p_child->GetType() == CRCX("vmenu") && m_is_vertical)
+			mp_the_menu = (CBaseMenu *) p_child.Convert();
+		else if ((uint32) p_child->GetType() == CRCX("hmenu") && !m_is_vertical)
+			mp_the_menu = (CBaseMenu *) p_child.Convert();
+		p_child = p_child->GetNextSibling();
+	}
+	
+	Dbg_MsgAssert(mp_the_menu || !m_is_vertical, ("could not find VMenu"));
+	Dbg_MsgAssert(mp_the_menu || m_is_vertical, ("could not find HMenu"));
+
+	float space_amount = -1.0f;
+	if (mp_the_menu->UsingRegularSpacing(space_amount) && m_num_items_to_display >= 0)
+	{
+		m_window_dimension = space_amount * m_num_items_to_display;
+		m_regular_space_amount = space_amount;
+	}
+	else
+	{
+		m_num_items_to_display = -1;
+		m_window_dimension = (m_is_vertical) ? m_base_h : m_base_w;
+	}
+}
+
+
+
+
+void CBaseScrollingMenu::put_element_in_view(const CScreenElementPtr &pElement)
+{
+	Dbg_Assert(mp_the_menu);
+	m_selected_element_id = pElement->GetID();
+	m_needs_update = true;
+}
+
+
+
+
+void CBaseScrollingMenu::update()
+{
+	CScreenElementPtr pElement = NULL;
+	int element_index = 0;
+	
+	float center_menu_offset_up = 0.0f;
+	float center_menu_offset_down = 0.0f;
+	
+	if ( mp_the_menu )
+	{
+		if ( m_selected_element_id )
+			pElement = mp_the_menu->GetChildById(m_selected_element_id, &element_index);
+		else
+			pElement = mp_the_menu->GetChildByIndex( 0 );
+	
+		if ( pElement )
+		{
+			int num_children = mp_the_menu->CountChildren();
+	
+			// Good doggy. Now find out if the selection needs to be centered in the window
+			if (m_regular_space_amount >= 0.0f)
+			{
+				if (element_index >= (m_num_items_to_display / 2))
+				{
+					center_menu_offset_up = m_regular_space_amount * ((m_num_items_to_display - 1) / 2);
+				}
+				if (element_index < num_children - (m_num_items_to_display / 2))
+				{
+					center_menu_offset_down = m_regular_space_amount * (m_num_items_to_display / 2);
+				}
+			}
+		}
+	}
+
+	if (pElement)
+	{	
+		float menu_x, menu_y;
+		mp_the_menu->SetJust(-1.0f, -1.0f);
+		mp_the_menu->GetLocalPos(&menu_x, &menu_y);
+
+		// get upper-left of element, local coordinates (in relation to VMenu)
+		float element_x, element_y, element_w, element_h;
+		float element_z, element_l;
+		if (m_is_vertical)
+		{
+			compute_element_area(pElement, element_x, element_z, element_w, element_l);
+		}
+		else
+		{
+			compute_element_area(pElement, element_z, element_y, element_l, element_h);
+		}
+		if (m_regular_space_amount >= 0.0f) 
+		{
+			element_z = m_regular_space_amount * element_index;
+			element_l = m_regular_space_amount;
+		}
+		
+		// if element top is above window top
+		if (element_z < m_top_or_left_line + center_menu_offset_up)
+		{
+			// scroll up
+			m_top_or_left_line -= m_scroll_speed;
+			if (m_top_or_left_line < element_z)
+				m_top_or_left_line = element_z - center_menu_offset_up;
+			m_needs_update = true;
+		}
+		// if element bottom is below window bottom
+		else if (element_z + element_l > m_top_or_left_line + m_window_dimension - center_menu_offset_down)
+		{
+			// scroll down
+			m_top_or_left_line += m_scroll_speed;
+			if (m_top_or_left_line + m_window_dimension > element_z + element_l)
+				m_top_or_left_line = element_z + element_l - m_window_dimension + center_menu_offset_down;
+			m_needs_update = true;
+		}
+			
+		if (m_needs_update)
+		{
+			if (m_is_vertical)
+				mp_the_menu->SetPos(menu_x, -m_top_or_left_line, FORCE_INSTANT);
+			else
+				mp_the_menu->SetPos(-m_top_or_left_line, menu_y, FORCE_INSTANT);
+		
+			//printf("m_top_or_left_line is %.2f\n", m_top_or_left_line);
+			
+			// make elements visible or invisible
+			element_index = 0;
+			CScreenElementPtr p_child = mp_the_menu->GetFirstChild();
+			while(p_child)
+			{
+				if (m_is_vertical)
+				{
+					compute_element_area(p_child, element_x, element_z, element_w, element_l);
+				}
+				else
+				{
+					compute_element_area(p_child, element_z, element_y, element_l, element_h);
+				}
+				if (m_regular_space_amount >= 0.0f) 
+				{
+					element_z = m_regular_space_amount * element_index;
+					element_l = m_regular_space_amount;
+					//Ryan("oog\n");
+				}
+				
+				const float TOLERANCE = 0.0f;
+				if (element_z >= m_top_or_left_line - TOLERANCE && element_z + element_l <= m_top_or_left_line + m_window_dimension + TOLERANCE)
+				{
+					// top is (mostly) in window, bottom is (mostly) in window
+					p_child->SetAlpha(1.0f, FORCE_INSTANT);
+				}
+				else
+				{
+					p_child->SetAlpha(0.0f, FORCE_INSTANT);
+				}
+		
+				p_child = p_child->GetNextSibling();
+				element_index++;
+			}
+			m_needs_update = false;
+		}
+	} // end if pElement
+}
+
+
+
+
+// finds area of element in coordinates relative to this scrolling menu
+void CBaseScrollingMenu::compute_element_area(const CScreenElementPtr &pElement, float &elementX, float &elementY, float &elementW, float &elementH)
+{
+	pElement->GetLocalPos(&elementX, &elementY);
+	elementX *= mp_the_menu->GetScaleX();
+	elementY *= mp_the_menu->GetScaleY();
+	elementW = pElement->GetBaseW() * pElement->GetScaleX() * mp_the_menu->GetScaleX();
+	elementH = pElement->GetBaseH() * pElement->GetScaleY() * mp_the_menu->GetScaleY();
+	float element_just_x, element_just_y;
+	pElement->GetJust(&element_just_x, &element_just_y);
+	elementX -= (element_just_x + 1.0f) * elementW / 2.0f;
+	elementY -= (element_just_y + 1.0f) * elementH / 2.0f;
+}
+
+
+
+
+CVScrollingMenu::CVScrollingMenu()
+{
+	m_is_vertical = true;
+}
+
+
+
+
+CVScrollingMenu::~CVScrollingMenu()
+{
+}
+
+
+
+
+CHScrollingMenu::CHScrollingMenu()
+{
+	m_is_vertical = false;
+}
+
+
+
+
+CHScrollingMenu::~CHScrollingMenu()
+{
+}
+
+
+
+
+}
diff --git a/Code/Gfx/2D/ScrollingMenu.h b/Code/Gfx/2D/ScrollingMenu.h
new file mode 100644
index 0000000..2c4dfb0
--- /dev/null
+++ b/Code/Gfx/2D/ScrollingMenu.h
@@ -0,0 +1,78 @@
+#ifndef __GFX_2D_SCROLLINGMENU_H__
+#define __GFX_2D_SCROLLINGMENU_H__
+
+#include 
+
+namespace Front
+{
+
+class CBaseMenu;
+	
+class CBaseScrollingMenu : public Front::CScreenElement, public Obj::CEventListener
+{
+public:
+
+							CBaseScrollingMenu();
+	virtual					~CBaseScrollingMenu();
+
+	void					SetInternalJust(float h, float v) {m_internal_just_x = h; m_internal_just_y = v;}	
+	
+	virtual void			SetProperties(Script::CStruct *pProps);
+	virtual bool			PassTargetedEvent(Obj::CEvent *pEvent, bool broadcast = false);
+
+protected:
+
+	void					pass_event_to_listener(Obj::CEvent *pEvent);
+	virtual void 			update();
+
+	void					get_the_menu();
+	void					put_element_in_view(const CScreenElementPtr &pElement);
+    	
+	void					compute_element_area(const CScreenElementPtr &pElement, float &elementX, float &elementY, float &elementW, float &elementH);
+	
+	CBaseMenu *				mp_the_menu;
+	uint32					m_selected_element_id;
+	
+	float					m_internal_just_x, m_internal_just_y;
+
+	bool					m_in_focus;
+	bool					m_needs_update;
+
+	float					m_top_or_left_line;
+	float					m_scroll_speed;
+
+	// set to negative if we aren't limiting
+	int						m_num_items_to_display;
+	float					m_window_dimension;
+	// set to negative if not used
+	float					m_regular_space_amount;
+
+	bool					m_is_vertical;
+};
+
+
+
+
+class CVScrollingMenu : public CBaseScrollingMenu
+{
+public:
+							CVScrollingMenu();
+	virtual					~CVScrollingMenu();
+};
+
+
+
+
+class CHScrollingMenu : public CBaseScrollingMenu
+{
+public:
+							CHScrollingMenu();
+	virtual					~CHScrollingMenu();
+};
+
+
+
+
+};
+
+#endif
diff --git a/Code/Gfx/2D/SpriteElement.cpp b/Code/Gfx/2D/SpriteElement.cpp
new file mode 100644
index 0000000..33d8282
--- /dev/null
+++ b/Code/Gfx/2D/SpriteElement.cpp
@@ -0,0 +1,182 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Front
+{
+
+
+
+
+CSpriteElement::CSpriteElement()
+{
+	//m_rgba = 0x40909090;
+	m_texture = 0;
+	mp_sprite = Nx::CEngine::sCreateSprite(NULL);
+	
+	SetType(CScreenElement::TYPE_SPRITE_ELEMENT);
+}
+
+
+
+
+CSpriteElement::~CSpriteElement()
+{
+	if (mp_sprite)
+	{
+		Nx::CEngine::sDestroySprite(mp_sprite);
+	}
+	//Ryan("Destroying CSpriteElement\n");
+}
+
+
+
+
+void CSpriteElement::SetProperties(Script::CStruct *pProps)
+{
+	CScreenElement::SetProperties(pProps);
+
+	uint32 texture_crc;
+	if (pProps->GetChecksum("texture", &texture_crc))
+		SetTexture(texture_crc);
+	float rot_angle;
+	if ( pProps->GetFloat( "rot_angle", &rot_angle ) )
+		SetRotate( rot_angle );
+	// Dbg_MsgAssert(m_texture, ("no texture loaded"));
+}
+
+void CSpriteElement::SetMorph( Script::CStruct *pProps )
+{
+	CScreenElement::SetMorph( pProps );
+
+	float rot_angle;
+	if ( pProps->GetFloat( "rot_angle", &rot_angle ) )
+		SetRotate( rot_angle, DONT_FORCE_INSTANT );
+}
+
+
+void CSpriteElement::SetTexture(uint32 texture_checksum)
+{
+	Nx::CTexture *p_texture;
+
+	m_texture = texture_checksum;
+
+	p_texture = Nx::CTexDictManager::sp_sprite_tex_dict->GetTexture(texture_checksum);
+	Dbg_MsgAssert(p_texture, ("no texture found for sprite 0x%x %s on element 0x%x %s",
+					texture_checksum,Script::FindChecksumName(texture_checksum),
+					m_id,Script::FindChecksumName(m_id)));
+	mp_sprite->SetTexture(p_texture);
+	
+	Dbg_MsgAssert(!(m_object_flags & vFORCED_DIMS), ("Trying to override sprite texture size"));
+	SetDims(p_texture->GetWidth(), p_texture->GetHeight());
+
+	m_object_flags |= CScreenElement::vCHANGED_STATIC_PROPS;	
+}
+
+
+
+void CSpriteElement::SetRotate( float angle, EForceInstant forceInstant )
+{
+	float rot = angle * Mth::PI / 180.0f;
+	rot += 0.001f;
+	Dbg_Assert( mp_sprite );
+
+	if ( m_target_local_props.GetRotate() != rot )
+	{
+		m_target_local_props.SetRotate( rot );
+		m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+	}
+	if ( forceInstant )
+	{
+		if ( m_local_props.GetRotate() != rot )
+		{
+			// on the next update, this element will arrive at the target alpha
+			mp_sprite->SetRotation( rot );
+			m_local_props.SetRotate( rot );
+			m_object_flags |= vNEEDS_LOCAL_POS_CALC;
+		}
+	}
+}
+
+
+void CSpriteElement::update()
+{
+	// HACK
+	bool offscreen = false;
+	if (m_summed_props.GetScreenUpperLeftY() < -200.0f || m_summed_props.GetScreenUpperLeftY() > 648.0f)
+		offscreen = true;
+	
+	// IsHidden can be relatively slow (it's recursive), so only call once
+	bool hidden = IsHidden();
+
+	mp_sprite->SetHidden( hidden );
+
+	// only change state of underlying sprite if there's been a change to CScreenElement/CSpriteElement visual state
+	if ( ( m_object_flags & CScreenElement::vDID_SUMMED_POS_CALC ) || ( m_object_flags & CScreenElement::vCHANGED_STATIC_PROPS ) )
+	{
+		Image::RGBA true_rgba = m_local_props.GetRGBA();
+		if (m_summed_props.alpha >= .0001f)
+			true_rgba.a = (uint8) ((float) m_local_props.GetRGBA().a * m_summed_props.alpha);
+		else
+			true_rgba.a = 0;
+		
+		// GARRETT: 
+		// Do all drawing here
+		//
+		// -screen coordinates of upper-left of sprite: m_summed_props.ulx, m_summed_props.uly
+		// -scale to apply: m_summed_props.scale
+		// -screen W,H of sprite (if needed): m_summed_props.scale * m_base_w, m_summed_props.scale * m_base_h
+		//mp_sprite->SetPos(m_summed_props.ulx, m_summed_props.uly);
+		mp_sprite->SetPos(m_summed_props.GetScreenPosX(), m_summed_props.GetScreenPosY());
+		mp_sprite->SetSize(m_base_w, m_base_h);
+		mp_sprite->SetAnchor(m_just_x, m_just_y);
+		mp_sprite->SetScale(m_summed_props.GetScaleX(), m_summed_props.GetScaleY());
+		
+		if ( m_object_flags & CScreenElement::v3D_POS )
+		{
+			mp_sprite->SetZValue(m_summed_props.GetScreenPosZ());
+			mp_sprite->SetHidden( ( ( m_object_flags & CScreenElement::v3D_CULLED ) || hidden ) );
+		}
+		else
+		{
+			mp_sprite->SetPriority( m_z_priority );
+			mp_sprite->SetHidden( offscreen || hidden );
+		}
+		
+		mp_sprite->SetRGBA(true_rgba);
+		// mp_sprite->SetHidden( ( offscreen || hidden ) );
+	
+
+#if 0  // Garrett: Can't find window
+		// Update the clip window.  This should only be done at init time, instead.
+		CWindowElement *p_window = get_window();
+		mp_sprite->SetWindow(p_window->GetClipWindow());
+#endif
+
+		// update the rotation angle
+		mp_sprite->SetRotation( m_local_props.GetRotate() );
+		// mp_sprite->SetRotation(mp_sprite->GetRotation() + 0.001f);
+
+	}
+}
+
+void CSpriteElement::WritePropertiesToStruct( Script::CStruct* pStruct )
+{
+	CScreenElement::WritePropertiesToStruct( pStruct );
+
+	// rotation angle
+	pStruct->AddFloat( "rot_angle", m_local_props.GetRotate() );
+
+	// texture name
+	pStruct->AddChecksum( "texture", this->m_texture );
+}
+
+}
diff --git a/Code/Gfx/2D/SpriteElement.h b/Code/Gfx/2D/SpriteElement.h
new file mode 100644
index 0000000..9151a17
--- /dev/null
+++ b/Code/Gfx/2D/SpriteElement.h
@@ -0,0 +1,45 @@
+#ifndef __GFX_2D_SPRITEELEMENT_H__
+#define __GFX_2D_SPRITEELEMENT_H__
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+#include 
+
+namespace Nx
+{
+	class CSprite;
+}
+
+namespace Front
+{
+
+class CSpriteElement : public CScreenElement
+{
+	friend class CScreenElementManager;
+
+public:
+							CSpriteElement();
+	virtual					~CSpriteElement();
+
+	void					SetProperties(Script::CStruct *pProps);
+	void					SetMorph(Script::CStruct *pProps);
+	
+	void 					SetTexture(uint32 texture_checksum);
+	void					SetRotate( float angle, EForceInstant forceInstant = FORCE_INSTANT );
+
+	void					WritePropertiesToStruct( Script::CStruct *pStruct );
+	
+protected:
+
+	void					update();
+
+	uint32					m_texture;
+
+	Nx::CSprite *			mp_sprite;
+};
+
+
+}
+
+#endif
diff --git a/Code/Gfx/2D/TextElement.cpp b/Code/Gfx/2D/TextElement.cpp
new file mode 100644
index 0000000..f6c4b36
--- /dev/null
+++ b/Code/Gfx/2D/TextElement.cpp
@@ -0,0 +1,1374 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define BLUR_EFFECT_ON 1
+
+namespace Front
+{
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextElement::CTextElement() :
+	m_shadow_rgba(0, 0, 0, 128)
+{
+	//Ryan("I am a new CTextElement\n");
+	m_font_checksum = 0;
+	mp_font = NULL;
+	mp_text = NULL;
+	mp_blur_effect = NULL;
+	m_use_shadow = false;
+	create_text_instances(1);
+
+	m_override_encoded_rgba = false;
+	m_previous_override_rgba_state = false;
+	
+	SetType(CScreenElement::TYPE_TEXT_ELEMENT);
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextElement::~CTextElement()
+{
+	//Ryan("Destroying CTextElement\n");
+	DetachBlurEffect();
+	destroy_text_instances();
+	if (mp_text)
+		delete mp_text;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::SetProperties(Script::CStruct *pProps)
+{
+	CScreenElement::SetProperties(pProps);
+
+	uint32 font_crc;
+	if (pProps->GetChecksum(CRCD(0x2f6bf72d,"font"), &font_crc))
+		SetFont(font_crc);
+	Dbg_MsgAssert(m_font_checksum, ("no font loaded"));
+	
+	const char *p_text;
+	if (pProps->GetText(CRCD(0xc4745838,"text"), &p_text))
+		SetText(p_text);
+
+	Dbg_MsgAssert(m_id != Obj::CBaseManager::vNO_OBJECT_ID, ("what, no ID"));
+
+	if (pProps->ContainsFlag(CRCD(0xd1653c6c,"blur_effect")))
+		AttachBlurEffect();
+	if (pProps->ContainsFlag(CRCD(0x4b73e4f8,"no_blur_effect")))
+		DetachBlurEffect();
+
+	Image::RGBA blur_rgba;
+	if (resolve_rgba(pProps, CRCD(0x4f0e1182,"blur_rgba"), &blur_rgba) && mp_blur_effect)
+		mp_blur_effect->SetRGBA(blur_rgba);
+
+	Script::CPair shadow_offs;
+	if (pProps->GetPair(CRCD(0x2a1fe0cc,"shadow_offs"), &shadow_offs))
+	{
+		SetShadowOff(shadow_offs.mX, shadow_offs.mY);
+	}
+	
+	if (pProps->ContainsFlag(CRCD(0x8a897dd2,"shadow")))
+		SetShadowState(true);
+	if (pProps->ContainsFlag(CRCD(0x95b5b9a0,"no_shadow")))
+		SetShadowState(false);
+	
+	resolve_rgba(pProps, CRCD(0x1e7bb1f5,"shadow_rgba"), &m_shadow_rgba);
+	
+	if ( pProps->ContainsFlag( CRCD(0xbf51a0c,"remember_override_rgba_state") ) )
+	{
+		Dbg_MsgAssert( !pProps->ContainsFlag( CRCD(0x61b7b58b,"restore_override_rgba_state") ), ( "restore_override_rgba_state and remember_override_rgba_state?" ) );
+		m_previous_override_rgba_state = m_override_encoded_rgba;
+	}
+	if ( pProps->ContainsFlag( CRCD(0x61b7b58b,"restore_override_rgba_state") ) )
+	{
+		Dbg_MsgAssert( !pProps->ContainsFlag( CRCD(0xbf51a0c,"remember_override_rgba_state") ), ( "restore_override_rgba_state and remember_override_rgba_state?" ) );
+		m_override_encoded_rgba = m_previous_override_rgba_state;
+	}
+	
+	if (pProps->ContainsFlag(CRCD(0xb7686f92,"override_encoded_rgba")))
+		m_override_encoded_rgba = true;
+	if (pProps->ContainsFlag(CRCD(0xd25bc6cd,"dont_override_encoded_rgba")))
+		m_override_encoded_rgba = false;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::SetMorph(Script::CStruct *pProps)
+{	
+	if (mp_blur_effect)
+	{
+		mp_blur_effect->SetMorph(pProps);
+	}
+
+	CScreenElement::SetMorph(pProps);
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::SetFont(uint32 font_checksum)
+{
+	m_font_checksum = font_checksum;
+	mp_font = Nx::CFontManager::sGetFont(m_font_checksum);
+	Dbg_MsgAssert(Nx::CFontManager::sTestFontLoaded(m_font_checksum), ("font %s isn't loaded", Script::FindChecksumName(m_font_checksum)));
+	m_object_flags |= CScreenElement::vCHANGED_STATIC_PROPS;	
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::SetText(const char *pText)
+{
+	int new_length = strlen(pText) + 1;
+	Dbg_MsgAssert(new_length < vMAX_TEXT_LENGTH, ("string too long %d", new_length));
+
+	// if the old string is not big enough, then we need to allocate a new one	
+	// otherwize, overwrite it, as allocations are slow
+	if (!mp_text || (int)(strlen(mp_text)) < new_length-1)		  // note, does not account for actual allocated size, but will work is string are same size
+	{
+		if (mp_text)
+			delete mp_text;	
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		mp_text = new char[new_length];
+		Mem::Manager::sHandle().PopContext();
+	}
+	// copy pText to mp_text, replacing '\m' tags with '\b' tags
+	const char *p_in = pText;
+	char *p_out = mp_text;
+	while(*p_in)
+	{
+		if (*p_in == '\\' && *(p_in+1) == '\\')
+		{
+			// output "\\" tag unchanged
+			*p_out++ = *p_in++;
+			*p_out++ = *p_in++;
+		}
+		// ignore sticky space
+		else if (*p_in == '\\' && *(p_in+1) == '_')
+		{
+			p_in++;
+			p_in++;
+			*p_out++ = ' ';
+		}
+		else if (*p_in == '\\' && *(p_in+1) == 'm')
+		{
+			*p_out++ = *p_in++;
+			*p_out++ = 'b';
+			p_in++;
+			*p_out++ = Nx::CFontManager::sMapMetaCharacterToButton(p_in++);
+		}
+		else
+		{
+			*p_out++ = *p_in++;
+		}
+	}
+	*p_out = '\0';
+	
+	if( mp_font )
+	{
+		float w, h;
+		mp_font->QueryString(mp_text, w, h);	// width and height in pixels
+
+		if (!(m_object_flags & vFORCED_DIMS)) 
+		{
+			SetDims(w, h);
+		}
+	}
+
+	m_object_flags |= CScreenElement::vCHANGED_STATIC_PROPS;	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CTextElement::GetLength()
+{
+	return strlen( mp_text );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTextElement::Concatenate( const char *pText )
+{
+	int old_length = 0;
+	if ( mp_text )
+		old_length += strlen( mp_text );
+	int length  = old_length + strlen( pText );
+	
+	if ( length + 1 >= vMAX_TEXT_LENGTH )
+	{
+		// new string would be too long...
+		return false;
+	}
+
+	if ( length > 0 )
+	{
+		char new_string[vMAX_TEXT_LENGTH];
+		
+		// initialize string
+		strcpy( new_string, "" );
+		if ( mp_text )
+			strcpy( new_string, mp_text );
+
+		// add new text
+		strcat( new_string, pText );
+	
+		SetText( new_string );
+		return true;
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTextElement::Backspace()
+{
+	if ( !mp_text )
+		return false;
+	
+	int length = strlen( mp_text );
+
+	if ( length == 0 )
+		return false;
+
+	// copy string
+	char new_string[vMAX_TEXT_LENGTH];
+	strcpy( new_string, mp_text );
+	
+	// take care of escaped backslash char (ASCII code 92)
+	if ( length > 1 && new_string[length - 1] == 92 && new_string[length - 2] == 92 )
+		new_string[length - 2] = '\0';
+	
+	// terminate
+	new_string[length - 1] = '\0';
+	SetText( new_string );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::AttachBlurEffect()
+{
+	#if BLUR_EFFECT_ON
+	if (!mp_blur_effect)
+	{
+		destroy_text_instances();
+		
+		// create blur effect according to specs
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		mp_blur_effect = new CBlurEffect();
+		// mp_blur_effect->SetRGBA(m_rgba);
+		mp_blur_effect->SetRGBA( m_local_props.GetRGBA() );
+		Mem::Manager::sHandle().PopContext();
+	
+		// get number of CText objects needed
+		// create 'em
+		create_text_instances(mp_blur_effect->GetNumEntries() + 1);
+	}
+	#endif
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::DetachBlurEffect()
+{
+	if (mp_blur_effect)
+	{
+		destroy_text_instances();
+		delete mp_blur_effect;
+		mp_blur_effect = NULL;
+		create_text_instances(1);
+	}
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::SetShadowState(bool shadowOn)
+{
+	if (shadowOn && !m_use_shadow)
+	{
+		m_use_shadow = true;
+		create_text_instances(m_num_tab_entries, true);
+	}
+	else if (!shadowOn && m_use_shadow)
+	{
+		m_use_shadow = false;
+		destroy_text_instances(true);
+	}
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::SetShadowOff(float offX, float offY)
+{
+	m_shadow_off_x = offX;
+	m_shadow_off_y = offY;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::update()
+{
+	//Dbg_Message("I am drawing text element at (%.1f,%.1f), scale %.1f\n", m_summed_props.ulx, m_summed_props.uly, m_summed_props.scalex);
+#if 0
+	if( mp_font )
+	{
+		mp_font->BeginText( *((uint32 *) &m_local_props.GetRGBA() ), m_summed_props.scalex);
+		mp_font->DrawString(mp_text, m_summed_props.ulx, m_summed_props.uly);
+		mp_font->EndText();
+	}
+#else
+
+	if (!mp_text) return;
+	
+	bool offscreen = false;
+	// HACK
+	if (m_summed_props.GetScreenUpperLeftY() < -200.0f || m_summed_props.GetScreenUpperLeftY() > 648.0f)
+		offscreen = true;
+
+	if (m_num_tab_entries)
+	{
+		Image::RGBA true_rgba = m_local_props.GetRGBA();
+		if (m_summed_props.alpha >= .0001f)
+			true_rgba.a = (uint8) ((float) m_local_props.GetRGBA().a * m_summed_props.alpha);
+		else
+			true_rgba.a = 0;
+		
+		/* 
+			-pass current pos, scale, alpha (local) to blur effect
+			-for each entry in table
+				-get pos, scale, alpa (in local, relative to current)
+		*/
+		
+		mpp_text_req_tab[0]->SetFont(mp_font);
+		mpp_text_req_tab[0]->SetPos(m_summed_props.GetScreenUpperLeftX(), m_summed_props.GetScreenUpperLeftY());
+		mpp_text_req_tab[0]->SetRGBA(true_rgba, m_override_encoded_rgba);
+		mpp_text_req_tab[0]->SetScale(m_summed_props.GetScaleX(), m_summed_props.GetScaleY());
+		mpp_text_req_tab[0]->SetString(mp_text);
+		if (m_object_flags & CScreenElement::v3D_POS)
+		{
+			mpp_text_req_tab[0]->SetZValue(m_summed_props.GetScreenPosZ());
+			mpp_text_req_tab[0]->SetHidden( ( ( m_object_flags & CScreenElement::v3D_CULLED ) || IsHidden() ) );
+		} else {
+			mpp_text_req_tab[0]->SetPriority(m_z_priority);
+			mpp_text_req_tab[0]->SetHidden( ( offscreen || IsHidden() ) );
+		}
+
+#if 0  // Garrett: Can't find window
+		// Update the clip window.  This should only be done at init time, instead.
+		CWindowElement *p_window = get_window();
+		mpp_text_req_tab[0]->SetWindow(p_window->GetClipWindow());
+#endif
+
+
+		if (m_use_shadow)
+		{
+			Image::RGBA true_rgba = m_shadow_rgba;
+			if (m_summed_props.alpha >= .0001f)
+				true_rgba.a = (uint8) ((float) m_shadow_rgba.a * m_summed_props.alpha);
+			else
+				true_rgba.a = 0;
+			mpp_shadow_req_tab[0]->SetFont(mp_font);
+			mpp_shadow_req_tab[0]->SetPos(m_summed_props.GetScreenUpperLeftX() + m_shadow_off_x, m_summed_props.GetScreenUpperLeftY() + m_shadow_off_y);
+			mpp_shadow_req_tab[0]->SetRGBA(true_rgba, m_override_encoded_rgba);
+			mpp_shadow_req_tab[0]->SetScale(m_summed_props.GetScaleX(), m_summed_props.GetScaleY());
+			mpp_shadow_req_tab[0]->SetString(mp_text);
+			if (m_object_flags & CScreenElement::v3D_POS)
+			{
+				mpp_shadow_req_tab[0]->SetZValue(m_summed_props.GetScreenPosZ());
+				mpp_shadow_req_tab[0]->SetHidden( ( ( m_object_flags & CScreenElement::v3D_CULLED ) || IsHidden() ) );
+			} else {
+				mpp_shadow_req_tab[0]->SetPriority(m_z_priority - .005f);
+				mpp_shadow_req_tab[0]->SetHidden( ( offscreen || IsHidden() ) );
+			}
+		}
+	}
+	if(mp_blur_effect)
+	{
+		/* 
+			-pass current center pos, scale, alpha (local) to blur effect
+			-for each entry in table
+				-get center pos, scale, alpa (in local, relative to current)
+		*/
+
+		Dbg_MsgAssert(!(m_object_flags & CScreenElement::v3D_POS), ("Can't use motion blur on 3D positioned text"));
+
+		mp_blur_effect->Update();
+
+		// work out absolute center of this element
+		float center_x = m_summed_props.GetScreenUpperLeftX() + m_summed_props.GetScaleX() * m_base_w / 2.0f;
+		float center_y = m_summed_props.GetScreenUpperLeftY() + m_summed_props.GetScaleY() * m_base_h / 2.0f;
+
+		for (int i = 1; i < m_num_tab_entries; i++)
+		{
+			ConcatProps &blur_entry = mp_blur_effect->GetInfo(i-1);
+			
+			Image::RGBA true_rgba = mp_blur_effect->GetRGBA();
+			true_rgba.a = (uint8) ((float) true_rgba.a * blur_entry.alpha);
+			
+			float draw_pos_x = center_x - (blur_entry.GetScreenPosX() + m_base_w * blur_entry.GetScaleX() / 2.0f) * m_summed_props.GetScaleX();
+			float draw_pos_y = center_y - (blur_entry.GetScreenPosY() + m_base_h * blur_entry.GetScaleY() / 2.0f) * m_summed_props.GetScaleY();
+			float draw_scale_x = m_summed_props.GetScaleX() * blur_entry.GetScaleX();
+			float draw_scale_y = m_summed_props.GetScaleY() * blur_entry.GetScaleY();
+			
+			mpp_text_req_tab[i]->SetPos(draw_pos_x, draw_pos_y);
+			mpp_text_req_tab[i]->SetRGBA(true_rgba, m_override_encoded_rgba);
+			mpp_text_req_tab[i]->SetScale(draw_scale_x, draw_scale_y);
+			mpp_text_req_tab[i]->SetPriority(m_z_priority - i * .01f);
+			
+			mpp_text_req_tab[i]->SetFont(mp_font);
+			mpp_text_req_tab[i]->SetString(mp_text);
+			mpp_text_req_tab[i]->SetHidden( ( offscreen || IsHidden() ) );
+
+			if (m_use_shadow)
+			{
+				Image::RGBA true_rgba = m_shadow_rgba;
+				if (m_summed_props.alpha >= .0001f)
+					true_rgba.a = (uint8) ((float) m_shadow_rgba.a * m_summed_props.alpha);
+				else
+					true_rgba.a = 0;
+				mpp_shadow_req_tab[i]->SetPos(draw_pos_x + m_shadow_off_x * draw_scale_x, 
+											draw_pos_y + m_shadow_off_y * draw_scale_y);
+				mpp_shadow_req_tab[i]->SetRGBA(true_rgba, m_override_encoded_rgba);
+				mpp_shadow_req_tab[i]->SetScale(draw_scale_x, draw_scale_y);
+				mpp_shadow_req_tab[i]->SetPriority(m_z_priority - i * .01f - .005f);
+
+				mpp_shadow_req_tab[i]->SetFont(mp_font);
+				mpp_shadow_req_tab[i]->SetString(mp_text);
+				mpp_shadow_req_tab[i]->SetHidden( ( offscreen || IsHidden() ) );
+			}
+		}
+	}	
+#endif
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::create_text_instances(int numEntries, bool shadow_only)
+{
+	Dbg_Assert(!mpp_shadow_req_tab);
+
+	if (m_use_shadow)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		mpp_shadow_req_tab = new Nx::CText*[numEntries];
+		Mem::Manager::sHandle().PopContext();
+		for (int i = 0; i < numEntries; i++)
+		{
+			mpp_shadow_req_tab[i] = Nx::CTextMan::sGetTextInstance();
+		}
+	}
+	
+	if (!shadow_only)
+	{
+		Dbg_Assert(!m_num_tab_entries);
+	
+		m_num_tab_entries = numEntries;
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		mpp_text_req_tab = new Nx::CText*[numEntries];
+		Mem::Manager::sHandle().PopContext();
+		for (int i = 0; i < numEntries; i++)
+		{
+			mpp_text_req_tab[i] = Nx::CTextMan::sGetTextInstance();
+		}
+	}
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextElement::destroy_text_instances(bool shadow_only)
+{
+	if (mpp_shadow_req_tab)
+	{
+		for (int i = 0; i < m_num_tab_entries; i++)
+		{
+			Nx::CTextMan::sFreeTextInstance(mpp_shadow_req_tab[i]);
+		}
+		delete mpp_shadow_req_tab;
+		mpp_shadow_req_tab = NULL;
+	}
+	
+	if (!shadow_only)
+	{
+		Dbg_Assert(m_num_tab_entries);
+		
+		for (int i = 0; i < m_num_tab_entries; i++)
+		{
+			Nx::CTextMan::sFreeTextInstance(mpp_text_req_tab[i]);
+		}
+		delete mpp_text_req_tab;
+		m_num_tab_entries = 0;
+	}
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextBlockElement::CTextBlockElement() :
+	m_shadow_rgba(0, 0, 0, 128)
+{
+	//Ryan("I am a new CTextBlockElement\n");
+	m_font = 0;
+	m_internal_scale = 1.0f;
+    m_line_spacing_scale = 1.0f;
+	m_total_height = 0.0f;
+	m_total_out_lines = 0;
+
+	mp_blur_effect = NULL;
+	m_allow_expansion = false;
+
+	m_override_encoded_rgba = false;
+	m_previous_override_rgba_state = false;
+	
+	SetType(CScreenElement::TYPE_TEXT_BLOCK_ELEMENT);
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextBlockElement::~CTextBlockElement()
+{	
+	//Ryan("Destroying CTextBlockElement\n");
+	if (mp_blur_effect)
+		delete mp_blur_effect;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextBlockElement::SetProperties(Script::CStruct *pProps)
+{
+	CScreenElement::SetProperties(pProps);
+
+	// must find this before setting font
+	if (pProps->GetFloat(CRCD(0x1fe341d2,"internal_scale"), &m_internal_scale))
+		m_object_flags |= CScreenElement::vCHANGED_STATIC_PROPS;
+
+    pProps->GetFloat(CRCD(0xe3fa22fc,"line_spacing"), &m_line_spacing_scale);
+    
+	uint32 font_crc;
+	if (pProps->GetChecksum(CRCD(0x2f6bf72d,"font"), &font_crc))
+		SetFont(font_crc);
+	Dbg_MsgAssert(m_font, ("no font loaded"));
+	
+
+	if (resolve_just(pProps, CRCD(0x67e093e4,"internal_just"), &m_internal_just_x, &m_internal_just_y))
+		m_object_flags |= CScreenElement::vCHANGED_STATIC_PROPS;
+	
+
+	const char *pp_line_tab[32];
+	Script::CArray *p_text_array;
+	
+	if (pProps->ContainsFlag( CRCD(0x322839a2,"allow_expansion") ))
+		m_allow_expansion = true;
+	
+	if (pProps->GetText(CRCD(0xc4745838,"text"), pp_line_tab))
+	{
+		SetText(pp_line_tab, 1);
+	}
+	else if (pProps->GetArray(CRCD(0xc4745838,"text"), &p_text_array))
+	{
+		int num_entries = p_text_array->GetSize();
+		for (int i = 0; i < num_entries; i++)
+			pp_line_tab[i] = p_text_array->GetString(i);
+
+		SetText(pp_line_tab, num_entries);
+	}
+
+	/*
+		Property changes to be forwared to children follow:	
+	*/
+	
+	if (pProps->ContainsFlag(CRCD(0xd1653c6c,"blur_effect")) && !mp_blur_effect)
+	{
+		#if BLUR_EFFECT_ON
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		mp_blur_effect = new CBlurEffect();
+		// mp_blur_effect->SetRGBA(m_rgba);
+		mp_blur_effect->SetRGBA( m_local_props.GetRGBA() );
+		Mem::Manager::sHandle().PopContext();
+		#endif
+	}
+	else if (pProps->ContainsFlag(CRCD(0x4b73e4f8,"no_blur_effect")) && mp_blur_effect)
+	{
+		delete mp_blur_effect;
+		mp_blur_effect = NULL;
+	}
+	
+	Image::RGBA blur_rgba;
+	if (resolve_rgba(pProps, CRCD(0x4f0e1182,"blur_rgba"), &blur_rgba) && mp_blur_effect)
+	{
+		mp_blur_effect->SetRGBA(blur_rgba);
+	}
+
+	Script::CPair shadow_offs;
+	if (pProps->GetPair(CRCD(0x2a1fe0cc,"shadow_offs"), &shadow_offs))
+	{
+		m_shadow_off_x = shadow_offs.mX;
+		m_shadow_off_y = shadow_offs.mY;
+	}
+	
+	if (pProps->ContainsFlag(CRCD(0x8a897dd2,"shadow")))
+		m_use_shadow = true;
+	if (pProps->ContainsFlag(CRCD(0x95b5b9a0,"no_shadow")))
+		m_use_shadow = false;
+
+	resolve_rgba(pProps, CRCD(0x1e7bb1f5,"shadow_rgba"), &m_shadow_rgba);
+	
+	if ( pProps->ContainsFlag( CRCD(0xbf51a0c,"remember_override_rgba_state") ) )
+	{
+		Dbg_MsgAssert( !pProps->ContainsFlag( CRCD(0x61b7b58b,"restore_override_rgba_state") ), ( "restore_override_rgba_state and remember_override_rgba_state?" ) );
+		m_previous_override_rgba_state = m_override_encoded_rgba;
+	}
+	if ( pProps->ContainsFlag( CRCD(0x61b7b58b,"restore_override_rgba_state") ) )
+	{
+		Dbg_MsgAssert( !pProps->ContainsFlag( CRCD(0xbf51a0c,"remember_override_rgba_state") ), ( "restore_override_rgba_state and remember_override_rgba_state?" ) );
+		m_override_encoded_rgba = m_previous_override_rgba_state;
+	}
+	
+	if (pProps->ContainsFlag(CRCD(0xb7686f92,"override_encoded_rgba")))
+		m_override_encoded_rgba = true;
+	if (pProps->ContainsFlag(CRCD(0xd25bc6cd,"dont_override_encoded_rgba")))
+		m_override_encoded_rgba = false;
+	
+	forward_properties_to_children();
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextBlockElement::SetMorph(Script::CStruct *pProps)
+{	
+	if (mp_blur_effect)
+	{
+		// Distribute blur effect settings to children, account for different 
+		// vertical positions of children relative to center of this
+		
+		// cheap hack, update self so that contained elements have proper position
+		update();
+		
+		Tmr::Time target_time;
+		const CBlurEffect::Props &blur_target = mp_blur_effect->SetMorph(pProps, &target_time);
+		//printf("%f\n", blur_target.topAlpha);
+		// hook up blur effect to children
+		
+		CScreenElementPtr p_child = CScreenElement::GetFirstChild();
+		while(p_child)
+		{
+			
+			Dbg_MsgAssert(p_child->GetType() == CScreenElement::TYPE_TEXT_ELEMENT,("TextBlockElement %s has child %s that is NOT a CTextElement (it's %s)\n",
+			Script::FindChecksumName(GetID()), Script::FindChecksumName(p_child->GetID()), Script::FindChecksumName(p_child->GetType())));
+
+			
+			// "child blur target"
+			CBlurEffect::Props cbt = blur_target;
+
+			// find center of child relative to center of this
+			// (we know we'll be getting top of child, since that's the just this class sets)
+			float child_center_x, child_center_y;
+			p_child->GetLocalPos(&child_center_x, &child_center_y);
+			child_center_y += p_child->GetBaseH() / 2.0f;
+			float disp_y = m_base_h / 2.0f - child_center_y;
+			cbt.maxDisplacementY += blur_target.bottomScaleY * disp_y - disp_y;
+			
+			CBlurEffect *p_child_blur = ((CTextElement *) ((CScreenElement *) p_child))->GetBlurEffect();
+			Dbg_Assert(p_child_blur);
+			p_child_blur->SetAllTargetProps(cbt, target_time);
+			p_child = p_child->GetNextSibling();
+		}
+	}
+
+	CScreenElement::SetMorph(pProps);
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextBlockElement::SetFont(uint32 font_checksum)
+{
+	Dbg_MsgAssert(!m_font, ("font already set"));
+	m_font = font_checksum;
+	Dbg_MsgAssert(Nx::CFontManager::sTestFontLoaded(m_font), ("font %s isn't loaded", Script::FindChecksumName(m_font)));
+
+	// how many lines will fit?
+	
+	Nx::CFont* p_font = Nx::CFontManager::sGetFont(m_font);
+	Dbg_MsgAssert(p_font, ("no font found"));
+	
+	// see if word wraps past end of element
+	float word_w, word_h;
+	p_font->QueryString("AAA", word_w, word_h);	// width and height in pixels
+
+	m_num_visible_lines = (int) m_base_h / (int) (word_h * m_internal_scale);
+	m_object_flags |= CScreenElement::vCHANGED_STATIC_PROPS;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextBlockElement::SetText(const char **ppTextLines, int numLines)
+{
+	Dbg_MsgAssert(m_object_flags & vFORCED_DIMS, ("no dimensions have been set"));
+	Dbg_MsgAssert(numLines < 256, ("large number of lines, probably not good"));
+
+//	char parsed_lines[MAX_LINES][MAX_CHARS]; 
+								  
+	CScreenElementManager* p_manager = CScreenElementManager::Instance();
+	
+	// destroy any children that are present
+	SetChildLockState( UNLOCK );
+	CScreenElementPtr p_child = GetFirstChild();
+	while ( p_child )
+	{
+		CScreenElementPtr p_next = p_child->GetNextSibling();
+		p_manager->DestroyElement(p_child->GetID());
+		p_child = p_next;
+	}
+	Dbg_Assert( !GetFirstChild() );
+
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+	mpp_parsed_lines = new char*[MAX_LINES];
+	// (Mick) in order to avoid doing multiple allocations, we just
+	// allocate a single array big enough for all the lines
+	mpp_parsed_lines[0] = new char[MAX_CHARS * MAX_LINES];
+	Mem::Manager::sHandle().PopContext();
+	// then calculate the pointers for all the other lines, as an offset to this
+	for ( int l = 1; l < MAX_LINES; l++ )
+	{
+		mpp_parsed_lines[l] = mpp_parsed_lines[0] + MAX_CHARS*l;
+	}
+	for( int i = 0; i < MAX_LINES; i++ )
+	{
+		mpp_parsed_lines[i][0] = '\0';
+	}
+
+	m_out_char = 0;
+	m_virtual_out_line = 0;
+	m_out_line = 0;
+	m_current_line_width = 0.0f;
+	
+	// reset number of visible lines
+	if ( m_allow_expansion )
+	{
+		m_base_h = 0.0f;
+		m_num_visible_lines = 0;
+	}
+	
+	for ( int in_line = 0; in_line < numLines; in_line++ )
+		read_in_text_line( ppTextLines[in_line] );
+		
+	// printf("line %d: %s\n", m_out_line, mpp_parsed_lines[m_out_line]);
+	// want to handle the "no text" case
+	if (numLines)
+	{
+		m_virtual_out_line++;
+		m_out_line++;
+	}
+
+	// we only to want to use the N most recent lines,
+	// where N is the number of lines that will fit
+	int buf_use_line = 0;
+	m_total_out_lines = m_out_line;
+	if ( m_virtual_out_line >= m_num_visible_lines )
+	{
+		// expand the element if we're supposed to
+		if ( m_allow_expansion )
+		{
+			Nx::CFont* p_font = Nx::CFontManager::sGetFont(m_font);
+			Dbg_MsgAssert(p_font, ("no font found"));
+			
+			// change the height of the element
+			float word_w, word_h;
+			p_font->QueryString("AAA", word_w, word_h );	// width and height in pixels
+			m_base_h = m_virtual_out_line * (word_h * m_internal_scale * m_line_spacing_scale );
+
+			m_num_visible_lines = (int) m_base_h / (int) (word_h * m_internal_scale * m_line_spacing_scale );
+
+			// re-parse the text now that m_num_visible_lines has changed
+// Mick: no need for this, since they are fixed size, we just overwrite them
+//			for (int l = 0; l < MAX_LINES; l++)
+//			{
+//				delete mpp_parsed_lines[l];
+//				mpp_parsed_lines[l] = new char[MAX_CHARS];
+//			}
+			m_out_char = 0;
+			m_virtual_out_line = 0;
+			m_out_line = 0;
+			m_current_line_width = 0.0f;
+			for (int i = 0; i < numLines; i++)
+				read_in_text_line(ppTextLines[i]);
+
+			// printf("changing m_total_out_lines from %i to %i\n", m_total_out_lines, m_num_visible_lines);
+			m_total_out_lines = m_num_visible_lines;
+		}
+		else 
+		{
+			Dbg_MsgAssert( m_num_visible_lines, ("Text block element too small for one line") );
+            buf_use_line = m_virtual_out_line % m_num_visible_lines;
+			m_total_out_lines = m_num_visible_lines;
+		}
+	}
+
+
+	// create new children
+	m_total_height = 0.0f;
+	for (int i = 0; i < m_total_out_lines; i++)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		CTextElement *p_new_elem = new CTextElement();
+		Mem::Manager::sHandle().PopContext();
+		p_manager->RegisterObject(*p_new_elem);
+		p_manager->SetParent(this, p_new_elem);
+		p_new_elem->SetFont(m_font);
+		p_new_elem->SetText(&mpp_parsed_lines[buf_use_line][0]);
+		p_new_elem->SetScale(m_internal_scale, m_internal_scale);
+	
+		
+		m_total_height += p_new_elem->GetBaseH() * m_internal_scale * m_line_spacing_scale;
+		buf_use_line++;
+		if (buf_use_line >= m_num_visible_lines)
+			buf_use_line = 0;
+	}
+
+	// delete the lines, now we've used them
+// Mick: Now we just delete the array of chars, and the array of pointers
+//	for (int l = 0; l < MAX_LINES; l++)
+//	{
+//		delete mpp_parsed_lines[l];
+//		mpp_parsed_lines[l] = NULL;			
+//	}
+	delete mpp_parsed_lines[0];
+	delete mpp_parsed_lines;
+
+	
+	SetChildLockState(LOCK);
+
+	m_object_flags |= CScreenElement::vCHANGED_STATIC_PROPS;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextBlockElement::SetText(const char *pTextLine)
+{
+	SetText(&pTextLine, 1);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTextBlockElement::GetText( char* p_text, int size )
+{
+	strcpy( p_text, "" );
+	int total_length = 0;
+	
+	CScreenElementPtr p_child = GetFirstChild();
+	while( p_child )
+	{
+		Dbg_MsgAssert(p_child->GetType() == CScreenElement::TYPE_TEXT_ELEMENT,("TextBlockElement %s has child %s that is NOT a CTextElement (it's %s)\n",
+		Script::FindChecksumName(GetID()), Script::FindChecksumName(p_child->GetID()), Script::FindChecksumName(p_child->GetType())));
+
+	
+		CScreenElementPtr p_next = p_child->GetNextSibling();
+		CTextElement* p_text_element = (CTextElement*)p_child.Convert();
+
+		char* p_text_element_string = p_text_element->GetText();
+		total_length += strlen( p_text_element_string );
+		
+		if ( p_next )
+			total_length += 1;
+	
+		if ( total_length < size )
+		{
+			strcat( p_text, p_text_element_string );
+			
+			// add a space between lines if this isn't the last line
+			if ( p_next )
+				strcat( p_text, " " );
+		}
+		else
+		{
+			Dbg_MsgAssert( 0, ( "TextBlockElement::GetText - text too long" ) );
+			return false;
+		}
+		p_child = p_next;
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CTextBlockElement::GetLength()
+{
+	// printf( "CTextBlockElement::GetLength called on %s\n", Script::FindChecksumName( m_id ) );
+	int total_length = 0;
+	
+	CScreenElementPtr p_child = GetFirstChild();
+	while( p_child )
+	{
+		Dbg_MsgAssert(p_child->GetType() == CScreenElement::TYPE_TEXT_ELEMENT,("TextBlockElement %s has child %s that is NOT a CTextElement (it's %s)\n",
+		Script::FindChecksumName(GetID()), Script::FindChecksumName(p_child->GetID()), Script::FindChecksumName(p_child->GetType())));
+		
+		CScreenElementPtr p_next = p_child->GetNextSibling();
+		CTextElement* p_text_element = (CTextElement*)p_child.Convert();
+		total_length += p_text_element->GetLength();
+		
+		if ( p_next )
+			total_length += 1;
+		
+		p_child = p_next;
+	}	
+	return total_length;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTextBlockElement::Backspace()
+{
+	int length = GetLength();
+
+	if ( length == 0 )
+		return false;
+
+	Dbg_MsgAssert( length < MAX_EDITABLE_TEXT_BLOCK_LENGTH, ( "Backspace failed - string too long" ) );
+	char new_string[MAX_EDITABLE_TEXT_BLOCK_LENGTH];
+	GetText( new_string, MAX_EDITABLE_TEXT_BLOCK_LENGTH );
+	
+	if ( !new_string )
+		return false;
+	
+	// take care of escaped backslash char (ASCII code 92)
+	if ( length > 1 && new_string[length - 1] == 92 && new_string[length - 2] == 92 )
+		new_string[length - 2] = '\0';
+	
+	// terminate
+	new_string[length - 1] = '\0';
+	SetText( new_string );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+	Called from read_in_text_line()
+*/
+CTextBlockElement::EParseResult CTextBlockElement::parse_next_word(char *pWordBuf, const char **ppSource)
+{
+	bool keep_scanning = true;
+	EParseResult result = NORMAL; 
+	
+	char *p_out = pWordBuf;
+	
+	while (keep_scanning)
+	{
+		switch(**ppSource)
+		{
+			case ' ':
+				*p_out++ = **ppSource;
+				(*ppSource)++;
+				keep_scanning = false;
+				break;
+			case '\\':
+			{
+				(*ppSource)++;
+				if (**ppSource == '_')
+				{
+					*p_out++ = ' ';
+					(*ppSource)++;
+				}
+				else if (**ppSource == 'n')
+				{
+					(*ppSource)++;
+					result = NEXT_LINE;
+					keep_scanning = false;
+				}
+				else
+				{
+					// is \?, where ? is some character
+					// output both
+					*p_out++ = '\\';
+					*p_out++ = **ppSource;
+					(*ppSource)++;
+				}
+				break;
+			} // end case
+			case '\0':
+				result = END_OF_BUFFER;
+				keep_scanning = false;
+				break;
+			default:
+			{
+				*p_out++ = **ppSource;
+				(*ppSource)++;
+				break;
+			}
+		} // end switch
+
+		Dbg_Assert((int) p_out - (int) pWordBuf < MAX_CHARS - 1);
+	}
+
+	*p_out++ = '\0';
+	return result;
+}
+
+
+
+
+/*
+	Called from SetText()
+*/
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void CTextBlockElement::read_in_text_line(const char *pText)
+{
+	Nx::CFont* p_font = Nx::CFontManager::sGetFont(m_font);
+	Dbg_MsgAssert(p_font, ("no font found"));
+	
+	const char *p_in = pText;
+
+	char p_word[MAX_CHARS];
+
+	EParseResult last_result = NORMAL;
+	
+	// printf("reading in: %s\n", pText);
+	
+	while ( last_result != END_OF_BUFFER )
+	{
+		EParseResult result = parse_next_word( p_word, &p_in );
+		
+		// see if word wraps past end of element
+		float word_w, word_h;
+		p_font->QueryString(p_word, word_w, word_h);	// width and height in pixels
+		word_w *= m_internal_scale;
+		
+		// printf("result = %d, word = '%s', screen_w = %.2f\n", result, p_word, word_w);
+
+		// if adding this word makes the line too long
+		// or we had a newline char or similar
+		// or this word will overflow the buffer (unlikely, but possible)
+		if ( ( m_current_line_width > 0.0f && m_current_line_width + word_w > m_base_w )
+			 ||  ( last_result == NEXT_LINE )
+			 ||  ( m_out_char + strlen( p_word ) >= MAX_CHARS )
+		   )
+		{
+			// printf("m_current_line_width = %f, word_w = %f, m_base_w = %f\n", m_current_line_width, word_w, m_base_w);
+			// printf("line %d: %s\n", m_out_line, mpp_parsed_lines[m_out_line]);
+			
+			// if last character on line is a space, do away with it
+			if ( mpp_parsed_lines[m_out_line][m_out_char-1] == ' ' )
+				mpp_parsed_lines[m_out_line][m_out_char-1] = '\0';
+			
+			// Wrap to next	line.
+			// Once we fill all visible lines, we start recycling the buffer,
+			// keeping only the most recent lines, hence the virtual line stuff.
+			m_virtual_out_line++;
+			m_out_line++;
+			if ( m_out_line >= m_num_visible_lines )
+				m_out_line = 0;
+			m_out_char = 0;
+			m_current_line_width = 0.0f;
+		}
+		
+		Dbg_Assert(m_out_line < MAX_LINES); // need to increase MAX_LINES if triggered
+		Dbg_MsgAssert(m_out_char + strlen(p_word) < MAX_CHARS, ("overflow, max allowed = %d, needed = %d\n%s%s\n", MAX_CHARS, m_out_char + strlen(p_word),&mpp_parsed_lines[m_out_line][0],p_word));
+		char *p_out = &mpp_parsed_lines[m_out_line][0] + m_out_char;
+		strcpy(p_out, p_word);
+		m_out_char += strlen(p_word);
+		m_current_line_width += word_w;
+
+		last_result = result;
+	}
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextBlockElement::update()
+{
+	if (m_object_flags & CScreenElement::vCHANGED_STATIC_PROPS)
+	{
+		Dbg_Assert(m_total_height > .00001f || !m_total_out_lines);
+		
+		// position children
+		float y = (m_internal_just_y + 1.0f) * (m_base_h - m_total_height) / 2.0f;
+		CScreenElementPtr p_child = GetFirstChild();
+		for (int i = 0; i < m_total_out_lines; i++)
+		{
+			Dbg_MsgAssert(p_child->GetType() == CScreenElement::TYPE_TEXT_ELEMENT,("TextBlockElement %s has child %s that is NOT a CTextElement (it's %s)\n",
+			Script::FindChecksumName(GetID()), Script::FindChecksumName(p_child->GetID()), Script::FindChecksumName(p_child->GetType())));
+			
+			p_child->SetJust(m_internal_just_x, -1.0f);
+			// p_child->SetRGBA(m_rgba);
+			p_child->SetRGBA( m_local_props.GetRGBA() );
+			p_child->SetPos((m_internal_just_x + 1.0f) * m_base_w / 2.0f, y);
+
+			y += p_child->GetBaseH() * m_internal_scale * m_line_spacing_scale;
+			p_child = p_child->GetNextSibling();
+		}
+		forward_properties_to_children();
+	}
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTextBlockElement::forward_properties_to_children()
+{
+	CScreenElementPtr p_child = CScreenElement::GetFirstChild();
+	while(p_child)
+	{
+		
+		Dbg_MsgAssert(p_child->GetType() == CScreenElement::TYPE_TEXT_ELEMENT,("TextBlockElement %s has child %s that is NOT a CTextElement (it's %s)\n",
+			Script::FindChecksumName(GetID()), Script::FindChecksumName(p_child->GetID()), Script::FindChecksumName(p_child->GetType())));
+		
+		if (mp_blur_effect)
+			((CTextElement *) ((CScreenElement *) p_child))->AttachBlurEffect();
+		else
+			((CTextElement *) ((CScreenElement *) p_child))->DetachBlurEffect();
+		if (m_use_shadow)
+			((CTextElement *) ((CScreenElement *) p_child))->SetShadowState(true);
+		else
+			((CTextElement *) ((CScreenElement *) p_child))->SetShadowState(false);
+		((CTextElement *) ((CScreenElement *) p_child))->SetShadowOff(m_shadow_off_x, m_shadow_off_y);
+		((CTextElement *) ((CScreenElement *) p_child))->SetShadowRGBA(m_shadow_rgba);
+		((CTextElement *) ((CScreenElement *) p_child))->SetEncodedRGBAOverride(m_override_encoded_rgba);
+		p_child = p_child->GetNextSibling();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// if enforce_max_width is true, the function will fail if a single word is
+// bigger than the width of a line.  That is, if the text would go outside
+// the specified width and there's no way to wrap, concatenation will fail.
+// Only the end of the string is checked.  The text already in the element
+// is assumed to be safe.  Thus it's possible to have a textblock element go
+// outside it's box by using SetText directly, rather than using concatenate
+bool CTextBlockElement::Concatenate( const char* pText, bool enforce_max_width, bool last_line )
+{
+#ifdef __NOPT_ASSERT__
+	int length = GetLength();
+	Dbg_MsgAssert( length < (int)( MAX_EDITABLE_TEXT_BLOCK_LENGTH - strlen( pText ) ), ( "TextBlock too long to concatenate" ) );
+#endif
+	
+	// setup new, unwrapped string
+	char* p_new_string = new char[MAX_EDITABLE_TEXT_BLOCK_LENGTH];
+	GetText( p_new_string, Front::MAX_EDITABLE_TEXT_BLOCK_LENGTH );
+	strcat( p_new_string, pText );
+
+	// enforce max width but allow them to add a space.
+	if ( (enforce_max_width && strcmp( pText, " " ) != 0) || last_line)
+	{
+		Nx::CFont* p_font = Nx::CFontManager::sGetFont( m_font );
+		Dbg_MsgAssert( p_font, ( "no font found" ) );
+
+		// parse through the new text word by word
+		const char *p_in = p_new_string;
+		char p_word[MAX_CHARS];
+
+		// skip up to the last word
+		EParseResult last_result = NORMAL;
+		while ( last_result != END_OF_BUFFER )
+		{			
+			last_result = parse_next_word( p_word, &p_in );
+		}
+			
+		// check that each word is not longer than the max width
+		float word_w, word_h;
+		p_font->QueryString( p_word, word_w, word_h );	// width and height in pixels
+		word_w *= m_internal_scale;
+		if ( word_w > m_base_w )
+		{
+			// too long!
+			delete p_new_string;
+			return false;
+		}
+	}
+	
+	SetText( p_new_string );
+	delete p_new_string;
+	return true;
+}
+
+
+}
diff --git a/Code/Gfx/2D/TextElement.h b/Code/Gfx/2D/TextElement.h
new file mode 100644
index 0000000..8896faa
--- /dev/null
+++ b/Code/Gfx/2D/TextElement.h
@@ -0,0 +1,164 @@
+#ifndef __GFX_2D_TEXTELEMENT_H__
+#define __GFX_2D_TEXTELEMENT_H__
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+#include 
+
+#define TEXT_ELEMENT_MAX_LENGTH 96
+
+namespace Nx
+{
+	class CFont;
+	class CText;
+}
+
+namespace Front
+{
+	class CBlurEffect;
+
+	enum
+	{
+		MAX_EDITABLE_TEXT_BLOCK_LENGTH = 512,
+	};
+
+
+class CTextElement : public CScreenElement
+{
+	friend class CScreenElementManager;
+	friend class CTextBlockElement;
+
+public:
+							CTextElement();
+	virtual					~CTextElement();
+
+	void					SetProperties(Script::CStruct *pProps);
+	void					SetMorph(Script::CStruct *pProps);
+	
+	void 					SetFont(uint32 font_checksum);
+	void					SetText(const char *pText);
+	char*					GetText() { return mp_text; }
+	int						GetLength();
+
+    bool					Concatenate( const char *pText );
+	bool					Backspace();
+	
+	void					AttachBlurEffect();
+	void					DetachBlurEffect();
+	CBlurEffect	*			GetBlurEffect() {return mp_blur_effect;}
+
+	void					SetShadowState(bool shadowOn);
+	void					SetShadowOff(float offX, float offY);
+	void					SetShadowRGBA(Image::RGBA shadowRGBA) {m_shadow_rgba = shadowRGBA;}
+
+	void					SetEncodedRGBAOverride(bool override) {m_override_encoded_rgba = override;}
+
+protected:
+
+	void					create_text_instances(int numInstances, bool shadowOnly = false);
+	void					destroy_text_instances(bool shadowOnly = false);
+	
+	enum
+	{
+		vMAX_TEXT_LENGTH	= TEXT_ELEMENT_MAX_LENGTH,
+	};
+	
+	void					update();
+
+	char *					mp_text;
+
+	uint32					m_font_checksum;
+	Nx::CFont *				mp_font;
+	Nx::CText **			mpp_text_req_tab;
+	Nx::CText **			mpp_shadow_req_tab;
+	int						m_num_tab_entries;
+
+	CBlurEffect	*			mp_blur_effect;
+
+	bool					m_use_shadow;
+	float					m_shadow_off_x;
+	float					m_shadow_off_y;
+	Image::RGBA				m_shadow_rgba;
+
+	bool					m_override_encoded_rgba;
+	bool					m_previous_override_rgba_state;
+};
+
+
+
+
+class CTextBlockElement : public CScreenElement
+{
+	friend class CScreenElementManager;
+
+public:
+							CTextBlockElement();
+	virtual					~CTextBlockElement();
+
+	void					SetProperties(Script::CStruct *pProps);
+	void					SetMorph(Script::CStruct *pProps);
+	
+	void 					SetFont(uint32 font_checksum);
+	void					SetText(const char **ppTextLines, int numLines);
+	void					SetText(const char *pTextLine);
+
+	bool					GetText( char* p_text, int size );
+	int						GetLength();
+	bool					Backspace();
+	bool					Concatenate( const char* pText, bool enforce_max_width = false, bool last_line = false );
+
+protected:
+
+	enum EParseResult
+	{ 
+		NORMAL,
+		NEXT_LINE,
+		END_OF_BUFFER,
+	};
+
+	enum
+	{
+		MAX_LINES			= 20,
+		MAX_CHARS			= TEXT_ELEMENT_MAX_LENGTH,
+	};
+	
+	EParseResult 			parse_next_word(char *pWordBuf, const char **ppSource);
+	void 					read_in_text_line(const char *pText);
+	void					update();
+	void 					forward_properties_to_children();
+
+	uint32					m_font;
+
+	char **					mpp_parsed_lines;
+	// Line of text "outputted" so far. With a lot of text lines, only most recent ones are used.
+	int						m_virtual_out_line;	
+	int 					m_out_line;	// line of mpp_parsed_lines currently being written to
+	int 					m_out_char; // on m_out_line
+	float 					m_current_line_width;
+
+	int						m_num_visible_lines;
+
+	float					m_internal_just_x, m_internal_just_y;
+	float					m_internal_scale;
+    float					m_line_spacing_scale;
+	float					m_total_height; // of contained TextElements
+	int						m_total_out_lines; // number of contained TextElements
+	
+	CBlurEffect	*			mp_blur_effect;
+	bool					m_use_shadow;
+	bool					m_allow_expansion;
+	float					m_shadow_off_x;
+	float					m_shadow_off_y;
+	Image::RGBA				m_shadow_rgba;
+	
+	bool					m_override_encoded_rgba;
+	bool					m_previous_override_rgba_state;
+};
+
+
+
+
+}
+
+#endif
diff --git a/Code/Gfx/2D/Window.cpp b/Code/Gfx/2D/Window.cpp
new file mode 100644
index 0000000..83e157e
--- /dev/null
+++ b/Code/Gfx/2D/Window.cpp
@@ -0,0 +1,51 @@
+#include 
+#include 
+#include 
+
+#ifdef	__PLAT_NGPS__ 
+#include 
+#endif
+
+namespace Front
+{
+
+
+
+
+CWindowElement::CWindowElement()
+{
+	SetType(CScreenElement::TYPE_WINDOW_ELEMENT);
+	mp_clip_window = Nx::CWindow2DManager::sGetWindowInstance(0, 0, 640, 448);
+}
+
+
+
+
+CWindowElement::~CWindowElement()
+{
+	Dbg_Assert(mp_clip_window);
+	Nx::CWindow2DManager::sFreeWindowInstance(mp_clip_window);
+}
+
+
+Nx::CWindow2D *	CWindowElement::GetClipWindow() const
+{
+	return mp_clip_window;
+}
+
+
+#if 0
+void CWindowElement::drawMainPart()
+{
+#ifdef	__PLAT_NGPS__ 
+	NxPs2::SetTextWindow(0,639,0,447);						// a full-screen clipping window
+#else
+	printf ("WARNING: drawMainPart not the same on this platform....\n");
+#endif
+}
+#endif
+
+
+
+
+}
diff --git a/Code/Gfx/2D/Window.h b/Code/Gfx/2D/Window.h
new file mode 100644
index 0000000..35d58ee
--- /dev/null
+++ b/Code/Gfx/2D/Window.h
@@ -0,0 +1,31 @@
+#ifndef __GFX_2D_WINDOW_H__
+#define __GFX_2D_WINDOW_H__
+
+#include 
+
+namespace Nx
+{
+	class CWindow2D;
+}
+
+namespace Front
+{
+
+class CWindowElement : public Front::CScreenElement
+{
+public:
+
+							CWindowElement();
+	virtual					~CWindowElement();
+
+	void					SetClipWindow(Nx::CWindow2D *p_window);		// This should probably be protected or private
+	Nx::CWindow2D *			GetClipWindow() const;
+
+protected:
+	Nx::CWindow2D *			mp_clip_window;
+
+};
+
+}
+
+#endif
diff --git a/Code/Gfx/AnimController.cpp b/Code/Gfx/AnimController.cpp
new file mode 100644
index 0000000..db4ac0a
--- /dev/null
+++ b/Code/Gfx/AnimController.cpp
@@ -0,0 +1,474 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       AnimChannel.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/13/01
+//****************************************************************************
+
+// Current state of an animation.  There should be no
+// references to skeletons or geometry;  this is purely 
+// a time-keeping class).
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+                                  
+#include 
+#include 
+#include 
+								   
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CAnimChannel::get_new_anim_time( void )
+{
+#ifdef __NOPT_ASSERT__	
+	float old_time = m_currentTime;
+#endif
+
+	float new_time=0;
+	float new_hold_time=0;
+	float new_loop_time=0;
+
+	m_animComplete = false;
+
+	if ( m_loopingType == LOOPING_WOBBLE )
+	{
+		Dbg_MsgAssert( 0, ( "Was not expecting a wobble looping type in this controller" ) );
+		return 0.0f;
+	}
+	
+	if ( m_direction == ANIM_DIR_FORWARDS )
+	{
+		new_time = m_currentTime + ( m_animSpeed * Tmr::FrameRatio() );
+		m_animComplete = new_time > m_endTime;
+		
+		if (m_startTime == m_endTime)
+		{
+			new_loop_time = m_endTime;
+		}
+		else
+		{
+			new_loop_time = m_startTime + ( new_time - m_endTime );
+			while ( new_loop_time > m_endTime )
+			{
+				// when the FrameRatio() > 1.0f, it's possible that
+				// we need to subtract more than once, esp. with short anims
+				new_loop_time = m_startTime + ( new_loop_time - m_endTime );
+			}
+		}
+		new_hold_time = m_endTime;
+	}
+	else if ( m_direction == ANIM_DIR_BACKWARDS )
+	{
+		new_time = m_currentTime - ( m_animSpeed * Tmr::FrameRatio() );
+		m_animComplete = new_time < m_startTime;
+		if (m_startTime == m_endTime)
+		{
+			new_loop_time = m_endTime;
+		}
+		else
+		{
+			new_loop_time = m_endTime + ( new_time - m_startTime );
+			while ( new_loop_time < m_startTime )
+			{
+				// when the FrameRatio() > 1.0f, it's possible that
+				// we need to subtract more than once, esp. with short anims
+				new_loop_time = m_endTime + ( new_loop_time - m_startTime );
+			}
+		}
+		new_hold_time = m_startTime;
+	}
+	else
+	{
+		Dbg_Assert( 0 );
+	}
+
+	if ( m_animComplete )
+	{
+		if ( m_loopingType == LOOPING_HOLD )
+		{
+			new_time = new_hold_time;
+		}
+		else if ( m_loopingType == LOOPING_PINGPONG )
+		{
+			m_direction = ( m_direction == ANIM_DIR_FORWARDS ) ? ANIM_DIR_BACKWARDS : ANIM_DIR_FORWARDS;
+			new_time = new_hold_time;
+		}
+        else if ( m_loopingType == LOOPING_CYCLE )
+        {
+            new_time = new_loop_time;
+		}
+	}
+
+	// Ken: This is temporary, to prevent the following assert going off when running a replay.
+	// Eventually the replays will be fixed to always recreate the correct new_time
+    if ( new_time < m_startTime )
+	{
+		new_time = m_startTime;
+	}	
+	if ( new_time > m_endTime )
+	{
+		new_time = m_endTime;
+	}	
+
+#ifdef __NOPT_ASSERT__
+    if ( new_time < m_startTime || new_time > m_endTime )
+    {
+        Dbg_Message( "old_time = %f", old_time );
+        Dbg_Message( "new_time = %f", new_time );
+        Dbg_Message( "new_loop_time = %f", new_loop_time );
+        Dbg_Message( "new_hold_time = %f", new_hold_time );
+        Dbg_Message( "m_startTime = %f", m_startTime );
+        Dbg_Message( "m_endTime = %f", m_endTime );
+        Dbg_Message( "m_loopingType = %d", m_loopingType );
+        Dbg_Message( "m_direction = %d", m_direction );
+        Dbg_Message( "m_animComplete = %d", m_animComplete );
+        Dbg_Message( "m_animName = %s", Script::FindChecksumName( m_animName ) );
+		Dbg_Assert( 0 );
+    }
+#endif
+
+	return new_time;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAnimChannel::CAnimChannel() : Lst::Node(this)
+{
+    Reset();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAnimChannel::~CAnimChannel()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::Reset()
+{
+	m_startTime = 0.0f;
+    m_currentTime = 0.0f;
+    m_endTime = 0.0f;
+    m_animSpeed = 0.0f;
+    m_direction = ANIM_DIR_FORWARDS;
+    m_loopingType = LOOPING_CYCLE;
+    m_animComplete = false;
+
+	// Ken: Changed this to be a hard wired checksum rather than calling Script::GenerateCRC
+	// The reason for this is that CAnimChannel::Reset() gets called from 
+	// CSkaterTrackingInfo::Reset() in replay.cpp, which gets called from the CSkaterTrackingInfo
+	// constructor. There is a static instance of a CSkaterTrackingInfo in replay.cpp, so the
+	// constructor will get called at some point before main(). Since Script::GenerateCRC accesses
+	// an array of data, there is a chance that it might get called before the array is initialised,
+	// since the order of execution is not well defined.
+	// It probably would be OK, I just want to be safe.
+	m_animName = 0xf60c9090;		// unassigned <-- just giving it a non-zero value, so that
+									// we can be sure it's getting initialized correctly...
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CAnimChannel::GetCurrentAnim( void )
+{
+    return m_animName;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CAnimChannel::GetCurrentAnimTime( void )
+{
+    return m_currentTime;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::GetAnimTimes(float *pStart, float *pCurrent, float *pEnd)
+{
+  	Dbg_MsgAssert( pStart, ("NULL pStart") );
+	Dbg_MsgAssert( pCurrent, ("NULL pCurrent") );
+	Dbg_MsgAssert( pEnd, ("NULL pEnd") );
+
+	*pCurrent = m_currentTime;
+	
+    if ( m_direction == ANIM_DIR_FORWARDS )
+	{
+		*pStart = m_startTime;
+		*pEnd = m_endTime;
+	}	
+	else
+	{
+		*pStart = m_endTime;
+		*pEnd = m_startTime;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::AddTime( float incVal )
+{
+	// GJ:  a way to fool the animation controller
+	// into incrementing its time (frame rate
+	// independent) for the viewer object
+
+	float oldAnimSpeed = m_animSpeed;
+
+	m_animSpeed = 0.0f;
+
+	m_currentTime += incVal;
+
+	CAnimChannel::Update();
+
+	m_animSpeed = oldAnimSpeed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::Update()
+{
+	m_currentTime = get_new_anim_time();
+}
+
+/******************************************************************/
+/*                                                                */
+/* Returns whether the animation has completed.  This is only     */
+/* valid with "hold on last frame" animations.				  */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimChannel::IsAnimComplete( void )
+{
+	if ( m_loopingType == LOOPING_HOLD )
+	{
+		if ( m_direction == ANIM_DIR_FORWARDS )
+		{
+			return ( m_currentTime == m_endTime );
+		}	
+		else
+		{
+			return ( m_currentTime == m_startTime );
+		}	
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CAnimChannel::IsLoopingAnim( void )
+{	
+	// Returns whether the current animation will loop forever.
+	// Used in skater.cpp, to determine whether or not it should
+	// wait for an animation to finish.
+	return ( m_loopingType == LOOPING_CYCLE || m_loopingType == LOOPING_PINGPONG );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::SetAnimSpeed( float speed )
+{
+	m_animSpeed = speed / 60.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CAnimChannel::GetAnimSpeed( void )
+{
+	return ( m_animSpeed * 60.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::ReverseDirection( void )
+{
+	if ( m_direction == ANIM_DIR_FORWARDS )
+	{
+		m_direction = ANIM_DIR_BACKWARDS;
+	}	
+	else
+	{
+		m_direction = ANIM_DIR_FORWARDS;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::SetLoopingType( EAnimLoopingType type )
+{
+	m_loopingType = type;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::PlaySequence( uint32 anim_name, float start_time, float end_time, EAnimLoopingType loop_type, float blend_period, float speed )
+{		   
+// Don't reset any more, because this will call the CBlendChannel's virtual
+// virtual function, which makes the animation status inactive...
+//	Reset();
+
+	m_startTime							    = (start_time < end_time) ? start_time : end_time;
+	m_currentTime							= start_time;
+	m_endTime								= (start_time < end_time) ? end_time : start_time;
+	m_animSpeed							    = speed / 60.0f;
+	m_direction								= (start_time < end_time) ? ANIM_DIR_FORWARDS : ANIM_DIR_BACKWARDS;
+	m_loopingType							= loop_type;
+	m_animComplete							= false;
+	m_animName								= anim_name;
+
+//	Dbg_MsgAssert(m_startTime != m_endTime,("m_startTime == m_endTime (%f) TELL MICK",m_startTime));
+//	m_wobbleTargetTime					    = (start_time + end_time) / 2;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAnimChannel::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ( "NULL p_info sent to CAnimChannel::GetDebugInfo" ) );
+
+	// put the name first
+	p_info->AddChecksum( "m_animName", m_animName );
+	
+	uint32 looping_type_checksum = 0;
+	switch ( m_loopingType )
+	{
+	case 0:
+		looping_type_checksum = CRCD( 0x3f00df04, "LOOPING_HOLD" );
+		break;
+	case 1:
+		looping_type_checksum = CRCD( 0xfbada576, "LOOPING_CYCLE" );
+		break;
+	case 2:
+		looping_type_checksum = CRCD( 0x792b924b, "LOOPING_PINGPONG" );
+		break;
+	case 3:
+		looping_type_checksum = CRCD( 0x90423ff2, "LOOPING_WOBBLE" );
+		break;
+	default:
+		Dbg_MsgAssert( 0, ( "Unknown looping type found in CAnimChannel::GetDebugInfo.  Was the enum changed?" ) );
+		break;
+	}
+	if ( looping_type_checksum )
+		p_info->AddChecksum( CRCD(0x84b06b3,"m_loopingType"), looping_type_checksum );
+
+	switch ( m_direction )
+	{
+	case 0:
+		p_info->AddChecksum( CRCD(0x9f648df4,"m_direction"), CRCD( 0xdf8b2509, "ANIM_DIR_FORWARDS" ) );
+		break;
+	case -1:
+		p_info->AddChecksum( CRCD(0x9f648df4,"m_direction"), CRCD( 0xfdf0c4a4, "ANIM_DIR_BACKWARDS" ) );
+		break;
+	default:
+		Dbg_MsgAssert( 0, ( "Unknown direction found in CAnimChannel::GetDebugInfo.  Was the enum changed?" ) );
+		break;
+	}
+
+	p_info->AddFloat( CRCD(0x8fbac25e,"m_startTime"), m_startTime );
+	p_info->AddFloat( CRCD(0xb73f0945,"m_currentTime"), m_currentTime );
+	p_info->AddFloat( CRCD(0xf1cef774,"m_endTime"), m_endTime );
+	p_info->AddFloat( CRCD(0xaa1e73de,"m_animSpeed"), m_animSpeed );
+	p_info->AddInteger( CRCD(0x2d574054,"m_animComplete"), m_animComplete );
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
diff --git a/Code/Gfx/AnimController.h b/Code/Gfx/AnimController.h
new file mode 100644
index 0000000..2324f74
--- /dev/null
+++ b/Code/Gfx/AnimController.h
@@ -0,0 +1,95 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       AnimChannel.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/13/01
+//****************************************************************************
+
+// Current state of an animation.  There should be no
+// references to skeletons or geometry;  this is purely 
+// a time-keeping class).
+
+#ifndef __GFX_ANIMCHANNEL_H
+#define __GFX_ANIMCHANNEL_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+	class CStruct;
+}
+						
+namespace Gfx
+{
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class CAnimChannel : public Lst::Node
+{
+public:
+    CAnimChannel();
+    virtual ~CAnimChannel();
+
+    virtual void        Reset();
+    virtual void        Update();
+	uint32				GetCurrentAnim();
+	
+    float               GetCurrentAnimTime( void );
+    void                GetAnimTimes( float*, float*, float* );
+    bool                IsAnimComplete( void );
+    bool                IsLoopingAnim( void );
+    void                SetAnimSpeed( float );
+    float               GetAnimSpeed( void );
+	void				ReverseDirection( void );
+    void                SetLoopingType( EAnimLoopingType );
+	EAnimLoopingType	GetLoopingType() const {
+		return m_loopingType;
+	}
+    virtual void        PlaySequence( uint32 animName, float start_time, float end_time, EAnimLoopingType loop_type, float blend_period, float speed );
+	void				AddTime( float incVal );
+
+	virtual void		GetDebugInfo( Script::CStruct* p_info );
+
+	// made this public while i'm changing over to new anim controllers
+
+//protected:
+public:
+    float				get_new_anim_time( void );
+    
+    float				m_startTime;
+    float				m_currentTime;
+    float				m_endTime;
+    float				m_animSpeed;
+    EAnimDirection		m_direction;
+    EAnimLoopingType	m_loopingType;
+    bool				m_animComplete;
+	uint32				m_animName;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // namespace Gfx
+
+#endif	// __GFX_ANIMCHANNEL_H
diff --git a/Code/Gfx/BonedAnim.cpp b/Code/Gfx/BonedAnim.cpp
new file mode 100644
index 0000000..3337b3b
--- /dev/null
+++ b/Code/Gfx/BonedAnim.cpp
@@ -0,0 +1,2647 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       bonedanim.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/14/2001
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGC__
+#include 
+#include "sys/ngc/p_dma.h"
+#include "sys\ngc\p_aram.h"
+#endif		// __PLAT_NGC__
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+#ifdef __PLAT_NGC__
+#define __ARAM__
+#endif
+
+#ifdef __PLAT_NGC__
+#define _16(a) (((a>>8)&0x00ff)|((a<<8)&0xff00))
+#define _32(a) (((a>>24)&0x000000ff)|((a>>8)&0x0000ff00)|((a<<8)&0x00ff0000)|((a<<24)&0xff000000)) 
+#else
+#define _16(a) a
+#define _32(a) a
+#endif		// __PLAT_NGC__
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// In order to compress the data, we convert the animation quat into
+// a unit quat and then discard the W value during export-time/load-time.  
+// Given the X, Y, Z values, we can rebuild the W value at run-time.
+// The code uses the following tolerance to when comparing the rebuilt W value
+// to the original value.  I chose the number pretty arbitrarily, based
+// on the existing (THPS3) skater and pedestrian animations.
+//(0.0005 radians ~= 0.028 degrees)
+const float nxUNITQUAT_TOLERANCE_RADIANS = 0.0005f;
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// The following structures are only needed until new data
+// is exported in the correct format
+
+struct SBonedAnimFileHeader
+{
+	uint32 	    version;
+	uint32    	flags;
+	float	    duration;
+};
+
+struct SPlatformFileHeader
+{
+    uint32      numBones;
+    uint32      numQKeys;
+    uint32      numTKeys;
+    uint32      numCustomAnimKeys;
+};
+
+struct SStandardAnimFramePointers
+{
+	unsigned char	numQKeys;
+	unsigned char	numTKeys;
+};
+
+struct SHiResAnimFramePointers
+{
+	short			numQKeys;
+	short			numTKeys;
+};
+
+//#define nxBONEDANIMFLAGS_UNUSED			(1<<31)
+#define nxBONEDANIMFLAGS_INTERMEDIATE   	(1<<30)
+#define nxBONEDANIMFLAGS_UNCOMPRESSED   	(1<<29)
+#define nxBONEDANIMFLAGS_PLATFORM      		(1<<28)	
+#define nxBONEDANIMFLAGS_CAMERADATA    		(1<<27)	
+#define nxBONEDANIMFLAGS_COMPRESSEDTIME 	(1<<26)
+#define nxBONEDANIMFLAGS_PREROTATEDROOT 	(1<<25)
+#define nxBONEDANIMFLAGS_OBJECTANIMDATA		(1<<24)
+#define nxBONEDANIMFLAGS_USECOMPRESSTABLE	(1<<23)
+#define nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS	(1<<22)
+#define nxBONEDANIMFLAGS_CUSTOMKEYSAT60FPS	(1<<21)
+#define nxBONEDANIMFLAGS_CUTSCENEDATA		(1<<20)
+#define nxBONEDANIMFLAGS_PARTIALANIM		(1<<19)
+
+// to be phased out...
+#define nxBONEDANIMFLAGS_OLDPARTIALANIM		(1<<18)
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+#ifdef __ARAM__
+#define ARAM_CACHE_SIZE (64*1024)
+#define MAX_BONE_COUNT 128
+
+static char qqq[ARAM_CACHE_SIZE] ATTRIBUTE_ALIGN(32); 
+
+static uint16				framecount[MAX_BONE_COUNT*2] ATTRIBUTE_ALIGN(32);
+//static uint32				partial[8] ATTRIBUTE_ALIGN(32);
+
+volatile uint8				framecount_active = 0;
+static int					framecount_size = 0;
+ARQRequest					framecount_request;
+#endif
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+// compress tables take up 4K,
+// which are a waste on Xbox and GC
+// (should PLAT_NGPS it once it's working)
+
+struct CBonedAnimCompressEntry
+{
+	short x48;
+	short y48;
+	short z48;
+	short n8;
+};
+
+CBonedAnimCompressEntry sQTable[256];
+CBonedAnimCompressEntry sTTable[256];
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool init_48_table( void* pStream, CBonedAnimCompressEntry* pEntry )
+{
+	for ( int i = 0; i < 256; i++ )
+	{
+		if ( !File::Read( (int*)&pEntry->x48, sizeof(short), 1, pStream ) )
+		{
+			return false;
+		}
+		
+		if ( !File::Read( (int*)&pEntry->y48, sizeof(short), 1, pStream ) )
+		{
+			return false;
+		}
+		
+		if ( !File::Read( (int*)&pEntry->z48, sizeof(short), 1, pStream ) )
+		{
+			return false;
+		}
+
+		if ( !File::Read( (int*)&pEntry->n8, sizeof(short), 1, pStream ) )
+		{
+			return false;
+		}
+		
+		pEntry->x48 = _16( pEntry->x48 );
+		pEntry->y48 = _16( pEntry->y48 );
+		pEntry->z48 = _16( pEntry->z48 );
+		pEntry->n8 = _16( pEntry->n8 );
+
+		pEntry++;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+#ifdef __ARAM__
+//static volatile bool	dmaComplete;
+
+static void arqCallback( u32 pointerToARQRequest )
+{
+//	ARQRequest * p_arq = (ARQRequest *)pointerToARQRequest;
+
+//	if ( p_arq->owner == 0x55555555 )
+//	{
+		framecount_active = 0;
+//	}
+}
+
+void dma_count( uint32 p_source_base, int size )
+{
+	// DMA the count info.
+	size = ( ( size + 31 ) & ~31 ); 
+	DCFlushRange ( framecount, size );
+	framecount_size = size;
+	framecount_active = 1;
+	ARQPostRequest	(	&framecount_request,
+						0x55555555,											// Owner.
+						ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+						ARQ_PRIORITY_HIGH,									// Priority.
+						p_source_base,										// Source.
+						(uint32)framecount,									// Dest.
+						size,												// Length.
+						arqCallback );										// Callback
+}
+
+//void dma_partial( uint32 p_source_base )
+//{
+//	while ( framecount_active );
+//	// DMA the count info.
+//	DCFlushRange ( partial, 32 );
+//	framecount_size = 32;
+//	framecount_active = 1;
+//	ARQPostRequest	(	&framecount_request,
+//						0x55555555,											// Owner.
+//						ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+//						ARQ_PRIORITY_HIGH,									// Priority.
+//						p_source_base,										// Source.
+//						(uint32)partial,									// Dest.
+//						32,													// Length.
+//						arqCallback );										// Callback
+//}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool InitQ48Table( const char* pFileName, bool assertOnFail )
+{
+	bool success = false;
+	
+	// open the file as a stream
+	void* pStream = File::Open( pFileName, "rb" );
+	
+    // make sure the file is valid
+	if ( !pStream )
+	{
+		Dbg_MsgAssert( !assertOnFail, ("Load of %s failed - file not found?", pFileName) );
+		goto exit;
+	}
+
+	success = init_48_table( pStream, &sQTable[0] );
+
+exit:
+	Dbg_MsgAssert( success, ("Parse of %s failed", pFileName) );
+	File::Close( pStream );
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool InitT48Table( const char* pFileName, bool assertOnFail )
+{
+	bool success = false;
+
+	// open the file as a stream
+	void* pStream = File::Open( pFileName, "rb" );
+
+	// make sure the file is valid
+	if ( !pStream )
+	{
+		Dbg_MsgAssert( assertOnFail, ("Load of %s failed - file not found?", pFileName) );
+		goto exit;
+	}
+
+	success = init_48_table( pStream, &sTTable[0] );
+
+	//Dbg_Assert( 0 );
+
+exit:
+	Dbg_MsgAssert( success, ("Parse of %s failed", pFileName) );
+	File::Close( pStream );	
+	return success;
+}
+
+/******************************************************************/
+/*                                                                 */
+/*                                                                */
+/******************************************************************/
+
+inline float timeDown(short theSource)
+{
+    return (float)theSource;
+}	  
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline short timeUp(float theSource)
+{
+	short retVal = (short)((theSource * 60.0f) + 0.5f);
+
+    return retVal;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline short transUp(float theSource, float scaleFactor)
+{
+    return (short)(theSource * scaleFactor);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float transDown(short theSource, float scaleFactor)
+{
+    return (float)(theSource / scaleFactor);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline short quatUp(float theSource)
+{
+    return (short)(theSource * 16384.0f);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float quatDown(short theSource)
+{
+    return (float)(theSource / 16384.0f);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void get_rotation_from_key( CAnimQKey* p_in, Mth::Quat* pQuat, bool isHiRes )
+{
+	if ( isHiRes )
+	{
+		(*pQuat)[X] = ((CHiResAnimQKey*)p_in)->qx;
+		(*pQuat)[Y] = ((CHiResAnimQKey*)p_in)->qy;
+		(*pQuat)[Z] = ((CHiResAnimQKey*)p_in)->qz;
+//  	(*pQuat)[W] = ((CHiResAnimQKey*)p_in)->qw;
+	}
+	else
+	{
+		(*pQuat)[X] = quatDown( ((CStandardAnimQKey*)p_in)->qx );
+		(*pQuat)[Y] = quatDown( ((CStandardAnimQKey*)p_in)->qy );
+		(*pQuat)[Z] = quatDown( ((CStandardAnimQKey*)p_in)->qz );
+//		(*pQuat)[W] = quatDown( ((CStandardAnimQKey*)p_in)->qw );
+	}
+	
+	float qx = (*pQuat)[X];
+	float qy = (*pQuat)[Y];
+	float qz = (*pQuat)[Z];
+
+	// Dave note: added 09/12/02 - a simple check to ensure we don't try to take the square root of a negative
+	// number, which will hose Nan-sensitive platforms later on...
+	float sum	= 1.0f - qx * qx - qy * qy - qz * qz;
+	(*pQuat)[W] = sqrtf(( sum >= 0.0f ) ? sum : 0.0f );
+	
+	if ( p_in->signBit )
+	{
+		(*pQuat)[W] = -(*pQuat)[W];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void set_key_from_rotation( CAnimQKey* p_in, float x, float y, float z, float w, bool isHiRes )
+{
+	if ( isHiRes )
+	{
+		((CHiResAnimQKey*)p_in)->qx = x;
+		((CHiResAnimQKey*)p_in)->qy = y;
+		((CHiResAnimQKey*)p_in)->qz = z;
+//		((CHiResAnimQKey*)p_in)->qw = w;
+		((CHiResAnimQKey*)p_in)->signBit = ( w < 0.0f ) ? 1 : 0;
+	}
+	else
+	{
+		((CStandardAnimQKey*)p_in)->qx = quatUp( x );
+		((CStandardAnimQKey*)p_in)->qy = quatUp( y );
+		((CStandardAnimQKey*)p_in)->qz = quatUp( z );
+//		((CStandardAnimQKey*)p_in)->qw = quatUp( w );
+		((CStandardAnimQKey*)p_in)->signBit = ( w < 0.0f ) ? 1 : 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void get_translation_from_key( CAnimTKey* p_in, Mth::Vector* pVector, bool isHiRes )
+{
+	if ( isHiRes )
+	{
+		(*pVector)[X] = ((CHiResAnimTKey*)p_in)->tx;
+		(*pVector)[Y] = ((CHiResAnimTKey*)p_in)->ty;
+		(*pVector)[Z] = ((CHiResAnimTKey*)p_in)->tz;
+		(*pVector)[W] = 1.0f;
+	}
+	else
+	{
+		(*pVector)[X] = transDown( ((CStandardAnimTKey*)p_in)->tx, 32.0f );
+		(*pVector)[Y] = transDown( ((CStandardAnimTKey*)p_in)->ty, 32.0f );
+		(*pVector)[Z] = transDown( ((CStandardAnimTKey*)p_in)->tz, 32.0f );
+		(*pVector)[W] = 1.0f;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline void get_rotation_from_standard_key( CStandardAnimQKey* p_in, Mth::Quat* pQuat )
+{
+	if ( p_in->qx == 0 && p_in->qy == 0 && p_in->qz == 0 )
+	{
+		// Optimization: this handles the identity rotation, which is so common that it's not
+		// worth doing a square root...
+		pQuat->SetVector( 0.0f, 0.0f, 0.0f );
+		pQuat->SetScalar( 1.0f );
+		return;
+	}
+		
+	float qx = quatDown( p_in->qx );
+	float qy = quatDown( p_in->qy );
+	float qz = quatDown( p_in->qz );
+
+	// Dave note: added 09/12/02 - a simple check to ensure we don't try
+	// to take the square root of a negative number, which will hose 
+	// Nan-sensitive platforms later on...
+	float sum = 1.0f - qx * qx - qy * qy - qz * qz;
+	
+// This assert was firing off in one of the cutscenes...
+// probably worth looking into later...
+//	Dbg_Assert( sum >= 0.0f );
+	if ( sum < 0.0f )
+	{
+		sum = 0.0f;
+	}
+
+	(*pQuat)[X] = qx;
+	(*pQuat)[Y] = qy;
+	(*pQuat)[Z] = qz;
+	(*pQuat)[W] = ( p_in->signBit ) ? -sqrtf( sum ) : sqrtf( sum );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void get_translation_from_standard_key( CStandardAnimTKey* p_in, Mth::Vector* pVector )
+{
+	(*pVector)[X] = transDown( p_in->tx, 32.0f );
+	(*pVector)[Y] = transDown( p_in->ty, 32.0f );
+	(*pVector)[Z] = transDown( p_in->tz, 32.0f );
+	(*pVector)[W] = 1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+						  
+inline void set_key_from_translation( CAnimTKey* p_in, float x, float y, float z, bool isHiRes )
+{
+	if ( isHiRes )
+	{
+		((CHiResAnimTKey*)p_in)->tx = x;
+		((CHiResAnimTKey*)p_in)->ty = y;
+		((CHiResAnimTKey*)p_in)->tz = z;
+	}
+	else
+	{
+		((CStandardAnimTKey*)p_in)->tx = transUp( x, 32.0f );
+		((CStandardAnimTKey*)p_in)->ty = transUp( y, 32.0f );
+		((CStandardAnimTKey*)p_in)->tz = transUp( z, 32.0f );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline void interpolate_q_frame(Mth::Quat* p_out, CAnimQKey* p_in1, CAnimQKey* p_in2, float alpha, bool isHiRes)
+{
+	if ( alpha == 0.0f )
+	{
+		// don't need to slerp, because it's the start time
+		get_rotation_from_key( p_in1, p_out, isHiRes );
+		return;
+	}
+
+	if ( alpha == 1.0f )
+	{
+		// don't need to slerp, because it's the end time
+		get_rotation_from_key( p_in2, p_out, isHiRes );
+		return;
+	}
+	
+	// INTERPOLATE Q-COMPONENT
+
+	Mth::Quat	qIn1;
+	Mth::Quat   qIn2;
+	
+	get_rotation_from_key( p_in1, &qIn1, isHiRes );
+	get_rotation_from_key( p_in2, &qIn2, isHiRes );
+    
+	// Faster slerp, stolen from game developer magazine.
+	*p_out = Mth::FastSlerp( qIn1, qIn2, alpha );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void interpolate_t_frame(Mth::Vector* p_out, CAnimTKey* p_in1, CAnimTKey* p_in2, float alpha, bool isHiRes)
+{
+	if ( alpha == 0.0f )
+	{
+		// don't need to lerp, because it's the start time
+		get_translation_from_key( p_in1, p_out, isHiRes );
+		return;
+	}
+
+	if ( alpha == 1.0f )
+	{
+		// don't need to slerp, because it's the end time
+		get_translation_from_key( p_in2, p_out, isHiRes );
+		return;
+	}
+
+	// INTERPOLATE T-COMPONENT
+
+    Mth::Vector   tIn1;					  
+	Mth::Vector   tIn2;
+	
+	get_translation_from_key( p_in1, &tIn1, isHiRes );
+	
+	get_translation_from_key( p_in2, &tIn2, isHiRes );
+
+    /* Linearly interpolate positions */
+    *p_out = Mth::Lerp( tIn1, tIn2, alpha );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+						  
+inline void interpolate_standard_q_frame(Mth::Quat* p_out, CStandardAnimQKey* p_in1, CStandardAnimQKey* p_in2, float alpha)
+{
+	if ( alpha == 0.0f )
+	{
+		// don't need to slerp, because it's the start time
+		get_rotation_from_standard_key( p_in1, p_out );
+		return;
+	}
+
+	if ( alpha == 1.0f )
+	{
+		// don't need to slerp, because it's the end time
+		get_rotation_from_standard_key( p_in2, p_out );
+		return;
+	}
+	
+	// INTERPOLATE Q-COMPONENT
+	Mth::Quat	qIn1;
+	Mth::Quat   qIn2;
+	get_rotation_from_standard_key( p_in1, &qIn1 );
+	get_rotation_from_standard_key( p_in2, &qIn2 );
+
+	// Faster slerp, stolen from game developer magazine.
+	*p_out = Mth::FastSlerp( qIn1, qIn2, alpha );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void interpolate_standard_t_frame(Mth::Vector* p_out, CStandardAnimTKey* p_in1, CStandardAnimTKey* p_in2, float alpha)
+{
+	if ( alpha == 0.0f )
+	{
+		// don't need to lerp, because it's the start time
+		get_translation_from_standard_key( p_in1, p_out );
+		return;
+	}
+
+	if ( alpha == 1.0f )
+	{
+		// don't need to slerp, because it's the end time
+		get_translation_from_standard_key( p_in2, p_out );
+		return;
+	}
+
+	// INTERPOLATE T-COMPONENT
+
+    Mth::Vector   tIn1;					  
+	Mth::Vector   tIn2;
+	
+	get_translation_from_standard_key( p_in1, &tIn1 );
+	
+	get_translation_from_standard_key( p_in2, &tIn2 );
+
+    /* Linearly interpolate positions */
+    *p_out = Mth::Lerp( tIn1, tIn2, alpha );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// eventually, this will go in the plat-specific version of CBonedAnimFrameData
+
+bool CBonedAnimFrameData::plat_dma_to_aram( int qbytes, int tbytes, uint32 flags )
+{
+	// GameCube: DMA to ARAM.
+#ifdef __ARAM__
+
+	if ( qbytes && tbytes )
+	{
+		uint32 address;
+		uint size;
+//   	uint32 header[8];
+
+//		// Want this to happen even if asserts turned off.
+//		if ( qbytes > (ARAM_CACHE_SIZE) )
+//		{
+//			OSReport( "Too many q keys (%d)!!!\n", qbytes );
+//			while (1);
+//		}
+//
+//		if ( tbytes > (ARAM_CACHE_SIZE) )
+//		{
+//			OSReport( "Too many t keys (%d)!!!\n", tbytes );
+//			while (1);
+//		}
+//
+//		if ( m_numBones > MAX_BONE_COUNT )
+//		{
+//			OSReport( "Too many bones (%d)!!!\n", m_numBones );
+//			while (1);
+//		}
+
+//		// DMA partial animation data.
+//		if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+//		{
+//			address = NsARAM::alloc( 32 );
+//			NsDMA::toARAM( address, mp_partialAnimData, 32 );
+//			mp_partialAnimData = (uint32*)address;
+//		}
+
+		// DMA per-bone frame count data.
+		size = m_numBones * sizeof(uint16) * 2;
+		memcpy( qqq, mp_perBoneQFrameSize, size );
+		int part_size = 0;
+		int frame_size = size;
+		if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+		{
+			part_size = ( 1 + (( *mp_partialAnimData - 1 )/ 32) + 1 ) * sizeof( uint32 );
+			memcpy( &qqq[size], mp_partialAnimData, part_size );
+			size += part_size;
+		}
+		size = ( ( size + 31 ) & ~31 ); 
+		address = NsARAM::alloc( size );
+		NsDMA::toARAM( address, qqq, size );
+
+		mp_perBoneQFrameSize = (uint16*)address;
+		mp_perBoneTFrameSize = (uint16*)address;
+		if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+		{
+			mp_partialAnimData = (uint32*)( address + frame_size );
+		}
+
+//		size = m_numBones * sizeof(uint16) * 2;
+//		size = ( ( size + 31 ) & ~31 ); 
+//		address = NsARAM::alloc( size );
+//		NsDMA::toARAM( address, mp_perBoneQFrameSize, size );
+//
+//		mp_perBoneQFrameSize = (uint16*)address;
+//		mp_perBoneTFrameSize = (uint16*)address;
+
+		// DMA Q frames to ARAM, delete RAM version & assign ARAM pointer.
+		size = qbytes/* + 32*/;
+		size = ( ( size + 31 ) & ~31 ); 
+//		header[0] = size;
+		address = NsARAM::alloc( size );
+//		NsDMA::toARAM( address, header, 32 );
+		NsDMA::toARAM( address/*+32*/, mp_qFrames, size );
+//		delete mp_qFrames;
+		mp_qFrames = (char*)address;
+
+		// DMA T frames to ARAM, delete RAM version & assign ARAM pointer.
+		size = tbytes/* + 32*/;
+		size = ( ( size + 31 ) & ~31 ); 
+//		header[0] = size;
+		address = NsARAM::alloc( size );
+//		NsDMA::toARAM( address, header, 32 );
+		NsDMA::toARAM( address/*+32*/, mp_tFrames, size );
+//		delete mp_tFrames;
+		mp_tFrames = (char*)address;
+
+		// hijack this pointer as it won't be used.
+		mp_boneNames = (uint32*)( ( qbytes << 16 ) | tbytes );
+	}
+	else
+	{
+		if ( is_hires() )
+		{
+			uint32 address;
+			uint size;
+
+//			// Want this to happen even if asserts turned off.
+//			if ( ( m_num_qFrames * sizeof( CHiResAnimQKey ) ) > (ARAM_CACHE_SIZE) )
+//			{
+//				OSReport( "Too many q keys (%d)!!!\n", m_num_qFrames );
+//				while (1);
+//			}
+//
+//			if ( ( m_num_tFrames * sizeof( CHiResAnimTKey ) ) > (ARAM_CACHE_SIZE) )
+//			{
+//				OSReport( "Too many t keys (%d)!!!\n", m_num_tFrames );
+//				while (1);
+//			}
+//
+
+//			// DMA partial animation data.
+//			if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+//			{
+//				uint32 * pData = mp_partialAnimData;
+//				int numBones = *pData;
+//
+//				int numMasks = (( numBones - 1 )/ 32) + 1;		
+//				size = sizeof( int ) + ( numMasks * sizeof( uint32 ) ); 
+//
+//				header[0] = size;
+//				address = NsARAM::alloc( size );
+//				NsDMA::toARAM( address, header, 32 );
+//				NsDMA::toARAM( address+32, mp_partialAnimData, size );
+//				mp_partialAnimData = (char*)address;
+//			}
+			mp_partialAnimData = NULL;
+
+			// DMA per-bone frame count data.
+			void * ptr;
+			if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+			{
+				size = ( m_numBones * sizeof(uint32) );
+				ptr = mp_boneNames;
+			}
+			else
+			{
+				size = 0;
+				ptr = mp_perBoneFrames;
+			}
+
+			if ( m_flags & nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS )
+			{
+				size += ( m_numBones * sizeof(SHiResAnimFramePointers) );
+			}
+			else
+			{
+				size += ( m_numBones * sizeof(SStandardAnimFramePointers) );
+			}
+
+			if ( size > ( MAX_BONE_COUNT * 2 * sizeof(uint16) ) )
+			{
+				OSReport( "Too many bones (%d)!!!\n", m_numBones );
+				while (1);
+			}
+
+			size = ( ( size + 31 ) & ~31 ); 
+			address = NsARAM::alloc( size );
+			NsDMA::toARAM( address, ptr, size );
+
+			mp_perBoneFrames = (uint16*)address;
+			mp_perBoneQFrameSize = (uint16*)address;		// So that it can be deallocated.
+
+			// DMA Q frames to ARAM, delete RAM version & assign ARAM pointer.
+//			Dbg_MsgAssert( m_num_qFrames <= (ARAM_CACHE_SIZE), ( "Too many hires Q keys (%d) - maximum is %d", m_num_qFrames, (ARAM_CACHE_SIZE) ) );
+			size = sizeof( CHiResAnimQKey ) * m_num_qFrames;
+			size = ( ( size + 31 ) & ~31 ); 
+			address = NsARAM::alloc( size );
+			NsDMA::toARAM( address, mp_qFrames, size );
+//			delete mp_qFrames;
+			mp_qFrames = (char*)address;
+
+			// DMA T frames to ARAM, delete RAM version & assign ARAM pointer.
+//			Dbg_MsgAssert( m_num_tFrames <= (ARAM_CACHE_SIZE), ( "Too many hires T keys (%d) - maximum is %d", m_num_tFrames, (ARAM_CACHE_SIZE) ) );
+			size = sizeof( CHiResAnimTKey ) * m_num_tFrames;
+			size = ( ( size + 31 ) & ~31 ); 
+			address = NsARAM::alloc( size );
+			NsDMA::toARAM( address, mp_tFrames, size );
+//			delete mp_tFrames;
+			mp_tFrames = (char*)address;
+		}
+		else
+		{
+			uint32 address;
+			uint size;
+
+//			// Want this to happen even if asserts turned off.
+//			if ( ( m_num_qFrames * sizeof( CStandardAnimQKey ) ) > (ARAM_CACHE_SIZE) )
+//			{
+//				OSReport( "Too many q keys (%d)!!!\n", m_num_qFrames );
+//				while (1);
+//			}
+//
+//			if ( ( m_num_tFrames * sizeof( CStandardAnimTKey ) ) > (ARAM_CACHE_SIZE) )
+//			{
+//				OSReport( "Too many t keys (%d)!!!\n", m_num_tFrames );
+//				while (1);
+//			}
+//
+
+//			// DMA partial animation data.
+//			if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+//			{
+//				uint32 * pData = mp_partialAnimData;
+//				int numBones = *pData;
+//
+//				int numMasks = (( numBones - 1 )/ 32) + 1;		
+//				size = sizeof( int ) + ( numMasks * sizeof( uint32 ) ); 
+//
+//				header[0] = size;
+//				address = NsARAM::alloc( size );
+//				NsDMA::toARAM( address, header, 32 );
+//				NsDMA::toARAM( address+32, mp_partialAnimData, size );
+//				mp_partialAnimData = (char*)address;
+//			}
+			mp_partialAnimData = NULL;
+
+			// DMA per-bone frame count data.
+			void * ptr;
+			if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+			{
+				size = ( m_numBones * sizeof(uint32) );
+				ptr = mp_boneNames;
+			}
+			else
+			{
+				size = 0;
+				ptr = mp_perBoneFrames;
+			}
+
+			if ( m_flags & nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS )
+			{
+				size += ( m_numBones * sizeof(SHiResAnimFramePointers) );
+			}
+			else
+			{
+				size += ( m_numBones * sizeof(SStandardAnimFramePointers) );
+			}
+
+			if ( size > ( MAX_BONE_COUNT * 2 * sizeof(uint16) ) )
+			{
+				OSReport( "Too many bones (%d)!!!\n", m_numBones );
+				while (1);
+			}
+
+			size = ( ( size + 31 ) & ~31 ); 
+			address = NsARAM::alloc( size );
+			NsDMA::toARAM( address, ptr, size );
+
+			mp_perBoneFrames = (uint16*)address;
+			mp_perBoneQFrameSize = (uint16*)address;		// So that it can be deallocated.
+
+			// DMA Q frames to ARAM, delete RAM version & assign ARAM pointer.
+//			Dbg_MsgAssert( m_num_qFrames <= (short)MAX_STANDARD_Q, ( "Too many standard Q keys (%d) - maximum is %d", m_num_qFrames, MAX_STANDARD_Q ) );
+			size = sizeof( CStandardAnimQKey ) * m_num_qFrames;
+			size = ( ( size + 31 ) & ~31 ); 
+			address = NsARAM::alloc( size );
+			NsDMA::toARAM( address, mp_qFrames, size );
+//			delete mp_qFrames;
+			mp_qFrames = (char*)address;
+
+			// DMA T frames to ARAM, delete RAM version & assign ARAM pointer.
+//			Dbg_MsgAssert( m_num_tFrames <= (short)MAX_STANDARD_T, ( "Too many standard T keys (%d) - maximum is %d", m_num_tFrames, MAX_STANDARD_T ) );
+			size = sizeof( CStandardAnimTKey ) * m_num_tFrames;
+			size = ( ( size + 31 ) & ~31 ); 
+			address = NsARAM::alloc( size );
+			NsDMA::toARAM( address, mp_tFrames, size );
+//			delete mp_tFrames;
+			mp_tFrames = (char*)address;
+		}
+	}
+
+#endif		// __ARAM__
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CBonedAnimFrameData::plat_read_compressed_stream(uint8* pData, bool delete_buffer)
+{
+	Dbg_Assert( pData );
+
+	SPlatformFileHeader *pThePlatformHeader = (SPlatformFileHeader *) pData;
+	pData += sizeof(SPlatformFileHeader);
+
+	m_numBones = pThePlatformHeader->numBones;
+	m_num_qFrames = pThePlatformHeader->numQKeys;
+	m_num_tFrames = pThePlatformHeader->numTKeys;
+	
+	uint32 qAllocSize = *((uint32*)pData);
+	pData += sizeof(uint32);
+
+	uint32 tAllocSize = *((uint32*)pData);
+	pData += sizeof(uint32);
+
+	mp_perBoneQFrameSize = (uint16*)pData;
+	pData += ( m_numBones * sizeof(uint16) );
+	
+	mp_perBoneTFrameSize = (uint16*)pData;
+	pData += ( m_numBones * sizeof(uint16) );
+
+	// long-align
+	if ( (uint32)pData & 0x3 )
+	{
+		pData += ( 4 - ((uint32)pData & 0x3) );
+	}
+	
+	// first read in object anim bone names, if any
+	if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+	{
+		Dbg_MsgAssert( 0, ( "Wasn't expecting object anims to be compressed" ) );
+		mp_boneNames = NULL;
+	}
+	else
+	{
+		mp_boneNames = NULL;
+	}
+	
+	if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+	{
+		Dbg_Assert(!((uint32)pData & 0x3));
+		
+		mp_partialAnimData = (uint32*)pData;
+		
+		// skip original number of bones
+		int numBones = *((uint32*)pData);
+		pData += sizeof( int );
+		
+		int numMasks = (( numBones - 1 )/ 32) + 1;		
+		pData += ( numMasks * sizeof( uint32 ) );
+	}
+
+	// long-align
+	if ( (uint32)pData & 0x3 )
+	{
+		pData += ( 4 - ((uint32)pData & 0x3) );
+	}
+		
+	if ( is_hires() )
+	{
+		Dbg_MsgAssert( !is_hires(), ( "Hi res format not supported for compressed anims" ) );
+	}
+	else
+	{
+		mp_qFrames = (char*)pData;
+		pData += qAllocSize;
+		
+		mp_tFrames = (char*)pData;
+		pData += tAllocSize;
+	}
+	
+	Dbg_Assert( mp_perBoneFrames == NULL );
+	Dbg_Assert( mp_qFrames );
+	Dbg_Assert( mp_tFrames );
+    
+	// dma to aram here...
+	plat_dma_to_aram( qAllocSize, tAllocSize );
+
+	// GJ:  be able to PIP the custom keys in as well
+
+	// long-align
+	if ( (uint32)pData & 0x3 )
+	{
+		pData += ( 4 - ((uint32)pData & 0x3) );
+	}
+   
+	// create an array of pointers to the custom keys
+	if ( !pThePlatformHeader->numCustomAnimKeys )
+	{
+		mpp_customAnimKeyList = NULL;
+	}
+	else
+	{
+		mpp_customAnimKeyList = (CCustomAnimKey **)(Mem::Malloc(pThePlatformHeader->numCustomAnimKeys*sizeof(CCustomAnimKey *)));
+		
+		// read custom keys
+		for ( uint32 i = 0; i < pThePlatformHeader->numCustomAnimKeys; i++ )
+		{
+			CCustomAnimKey* pKey = ReadCustomAnimKey( &pData );
+			if ( !pKey )
+			{
+				Dbg_Message( "Failed while reading custom data" );
+				return false;
+			}		
+			mpp_customAnimKeyList[i] = pKey;
+		}
+	}		
+
+	m_num_customKeys = pThePlatformHeader->numCustomAnimKeys;
+
+#ifdef __ARAM__
+	if ( delete_buffer )
+	{
+		delete mp_fileBuffer;
+	}
+	mp_fileBuffer = NULL;
+#endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::plat_read_stream(uint8* pData, bool delete_buffer)
+{
+	Dbg_Assert( pData );
+
+	SPlatformFileHeader* pThePlatformHeader = (SPlatformFileHeader*)pData;
+	pData += sizeof(SPlatformFileHeader);
+	Dbg_Assert(!((uint) pData & 0x3));
+
+	m_numBones = pThePlatformHeader->numBones;
+	m_num_qFrames = pThePlatformHeader->numQKeys;
+	m_num_tFrames = pThePlatformHeader->numTKeys;
+	
+	Dbg_Assert( !mp_perBoneFrames );
+	Dbg_Assert( !mp_qFrames );
+	Dbg_Assert( !mp_tFrames );
+	
+	// first read in object anim bone names, if any
+	if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+	{
+		Dbg_Assert(!((uint) pData & 0x3));
+		mp_boneNames = (uint32*)pData;
+		pData += ( m_numBones * sizeof(uint32) );
+	}
+	else
+	{
+		mp_boneNames = NULL;
+	}
+
+	if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+	{
+		Dbg_Assert(!((uint32)pData & 0x3));
+		
+		mp_partialAnimData = (uint32*)pData;
+		
+		// skip original number of bones
+		int numBones = *((uint32*)pData);
+		pData += sizeof( int );
+		
+		int numMasks = (( numBones - 1 )/ 32) + 1;		
+		pData += ( numMasks * sizeof( uint32 ) );
+	}
+
+	// now, read in the per-bone frames of the correct size
+	if ( m_flags & nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS )
+	{
+		mp_perBoneFrames = pData;
+		pData += ( m_numBones * sizeof(SHiResAnimFramePointers) );		
+	}
+	else
+	{
+		mp_perBoneFrames = pData;
+		pData += ( m_numBones * sizeof(SStandardAnimFramePointers) );		
+	}
+
+	// long-align
+	if ( (uint32)pData & 0x3 )
+	{
+		pData += ( 4 - ((uint32)pData & 0x3) );
+	}
+   
+	// count to make sure the number of keys per bone didn't overflow
+	int runningQCount = 0;
+	int runningTCount = 0;
+
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		runningQCount += get_num_qkeys( mp_perBoneFrames, i );
+		runningTCount += get_num_tkeys( mp_perBoneFrames, i );
+	}
+
+	Dbg_MsgAssert( runningQCount == m_num_qFrames, ( "Wrong number of qframes in %x %d %d", m_fileNameCRC, runningQCount, m_num_qFrames ) );
+	Dbg_MsgAssert( runningTCount == m_num_tFrames, ( "Wrong number of tframes in %x %d %d", m_fileNameCRC, runningTCount, m_num_tFrames ) );
+
+	if ( is_hires() )
+	{
+		Dbg_Assert(!((uint) pData & 0x3));
+		mp_qFrames = (char*)pData;
+		pData += ( m_num_qFrames * sizeof(CHiResAnimQKey) );
+		
+		Dbg_Assert(!((uint) pData & 0x3));
+		mp_tFrames = (char*)pData;
+		pData += ( m_num_tFrames * sizeof(CHiResAnimTKey) );
+	}
+	else
+	{
+		Dbg_Assert(!((uint) pData & 0x3));
+		mp_qFrames = (char*)pData;
+		pData += ( m_num_qFrames * sizeof(CStandardAnimQKey) );
+		
+		Dbg_Assert(!((uint) pData & 0x3));
+		mp_tFrames = (char*)pData;
+		pData += ( m_num_tFrames * sizeof(CStandardAnimTKey) );
+	}
+	
+	Dbg_Assert( mp_perBoneFrames );
+	Dbg_Assert( mp_qFrames );
+	Dbg_Assert( mp_tFrames );
+    
+	// dma to aram here...
+	plat_dma_to_aram( 0, 0, m_flags );
+
+	// GJ:  be able to PIP the custom keys in as well
+
+	// long-align
+	if ( (uint32)pData & 0x3 )
+	{
+		pData += ( 4 - ((uint32)pData & 0x3) );
+	}
+   
+	// create an array of pointers to the custom keys
+	if ( !pThePlatformHeader->numCustomAnimKeys )
+	{
+		mpp_customAnimKeyList = NULL;
+	}
+	else
+	{
+		mpp_customAnimKeyList = (CCustomAnimKey **)(Mem::Malloc(pThePlatformHeader->numCustomAnimKeys*sizeof(CCustomAnimKey *)));
+		
+		// read custom keys
+		for ( uint32 i = 0; i < pThePlatformHeader->numCustomAnimKeys; i++ )
+		{
+			CCustomAnimKey* pKey = ReadCustomAnimKey( &pData );
+			if ( !pKey )
+			{
+				Dbg_Message( "Failed while reading custom data" );
+				return false;
+			}		
+			mpp_customAnimKeyList[i] =  pKey;
+		}
+	}		
+
+	m_num_customKeys = pThePlatformHeader->numCustomAnimKeys;
+
+#ifdef __ARAM__
+	if ( delete_buffer )
+	{
+		delete mp_fileBuffer;
+	}
+	mp_fileBuffer = NULL;
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CBonedAnimFrameData::get_num_qkeys( void * p_frame_data, int boneIndex ) const
+{
+	if ( m_flags & nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS )
+	{
+		SHiResAnimFramePointers* pFramePointers = (SHiResAnimFramePointers*)p_frame_data;
+		
+		pFramePointers += boneIndex;
+
+		return pFramePointers->numQKeys;
+	}
+	else
+	{
+		SStandardAnimFramePointers* pFramePointers = (SStandardAnimFramePointers*)p_frame_data;
+		
+		pFramePointers += boneIndex;
+
+		return pFramePointers->numQKeys;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CBonedAnimFrameData::get_num_tkeys( void * p_frame_data, int boneIndex ) const
+{
+	if ( m_flags & nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS )
+	{
+		SHiResAnimFramePointers* pFramePointers = (SHiResAnimFramePointers*)p_frame_data;
+		
+		pFramePointers += boneIndex;
+
+		return pFramePointers->numTKeys;
+	}
+	else
+	{
+		SStandardAnimFramePointers* pFramePointers = (SStandardAnimFramePointers*)p_frame_data;
+		
+		pFramePointers += boneIndex;
+
+		return pFramePointers->numTKeys;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CBonedAnimFrameData::is_hires() const
+{
+	return ( m_flags & nxBONEDANIMFLAGS_CAMERADATA ) || ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA );
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBonedAnimFrameData::CBonedAnimFrameData()
+{
+	m_duration = 0.0f;
+	m_numBones = 0;
+
+	mp_fileBuffer = NULL;
+	mp_fileHandle = NULL;
+	m_dataLoaded = false;
+
+	mp_qFrames = NULL;	
+	mp_tFrames = NULL;
+
+	mp_perBoneFrames = NULL;
+	mp_boneNames = NULL;
+
+	mp_perBoneQFrameSize = NULL;
+	mp_perBoneTFrameSize = NULL;
+
+	m_printDebugInfo = false;
+
+	m_num_customKeys = 0;
+
+	mpp_customAnimKeyList = NULL;
+	m_fileNameCRC = 0;
+	m_pipped = false;
+
+	mp_partialAnimData = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBonedAnimFrameData::~CBonedAnimFrameData()
+{
+
+	for (int i=0;iGetFileSize();
+
+		Dbg_MsgAssert(file_size, ("Anim file size is 0"));
+
+		Dbg_Assert(!mp_fileBuffer);
+//		Now that we're pipping it, we don't have to force this on the top-down heap
+//		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+		mp_fileBuffer = (void *) Mem::Malloc( file_size );
+//		Mem::Manager::sHandle().PopContext();
+
+		// Set the callback
+		mp_fileHandle->SetCallback(async_callback, (unsigned int) this, (unsigned int) assertOnFail);
+
+		// read the file in
+		mp_fileHandle->Read( mp_fileBuffer, 1, file_size );
+		//Dbg_Message("Started read of %x", this);
+
+		//mp_fileHandle->WaitForIO();
+		//Dbg_Message("Done waiting for %x", this);
+
+		// Should be the callback
+		return true; //PostLoad(assertOnFail, file_size);
+	}
+	else
+	{
+		int file_size = 0;
+
+		if ( use_pip )
+		{
+			mp_fileBuffer = Pip::Load( p_fileName );
+			file_size = Pip::GetFileSize( p_fileName );
+			Dbg_MsgAssert(file_size, ("Anim file size is 0"));
+			m_pipped = true;
+		}
+		else
+		{
+			// open the file as a stream
+			void* pStream = File::Open( p_fileName,"rb" );
+			// make sure the file is valid
+			if ( !pStream )
+			{
+				Dbg_MsgAssert( assertOnFail, ("Load of %s failed - file not found?", p_fileName) );
+				return false;
+			}
+
+			file_size = File::GetFileSize(pStream);
+			Dbg_MsgAssert(file_size, ("Anim file size is 0"));
+			Dbg_Assert(!mp_fileBuffer);
+	//		Now that we're pipping it, we don't have to force this on the top-down heap
+	//		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+			mp_fileBuffer = (void *) Mem::Malloc( file_size );
+	//		Mem::Manager::sHandle().PopContext();
+
+			// read the file in
+			if ( !File::Read( mp_fileBuffer, 1, file_size, pStream ) )
+			{
+				Mem::Free(mp_fileBuffer);
+				mp_fileBuffer = NULL;
+				File::Close( pStream );
+
+				Dbg_MsgAssert( assertOnFail, ("Load of %s failed - bad format?", p_fileName) );
+				return false;
+			}
+			
+			File::Close(pStream);
+		}
+
+		return PostLoad(assertOnFail, file_size);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBonedAnimFrameData::async_callback(File::CAsyncFileHandle *, File::EAsyncFunctionType function,
+										 int result, unsigned int arg0, unsigned int arg1)
+{
+	//Dbg_Message("Got callback from %x", arg0);
+	if (function == File::FUNC_READ)
+	{
+		CBonedAnimFrameData *p_data = (CBonedAnimFrameData *) arg0;
+		bool assert = (bool) arg1;
+
+		p_data->PostLoad(assert, result);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::PostLoad(bool assertOnFail, int file_size, bool delete_buffer)
+{
+	bool success = false;
+
+	//Dbg_Message("PostLoad of %x", this);
+
+	// Handle end of async, if that was used
+	if (mp_fileHandle)
+	{
+#if 0
+		if (mp_fileHandle->WaitForIO() != file_size)
+		{
+			Mem::Free(mp_fileBuffer);
+			mp_fileBuffer = NULL;
+			File::CAsyncFileLoader::sClose( mp_fileHandle );
+			mp_fileHandle = NULL;
+
+			Dbg_MsgAssert( assertOnFail, ("PostLoad of anim file failed - bad format?") );
+			return false;
+		}
+#endif
+		File::CAsyncFileLoader::sClose( mp_fileHandle );
+		mp_fileHandle = NULL;
+	}
+
+	uint8 *pFileData = (uint8 *) mp_fileBuffer;
+
+	// read in header information
+	SBonedAnimFileHeader* pTheHeader = (SBonedAnimFileHeader*)pFileData;
+	pFileData += sizeof(SBonedAnimFileHeader);
+
+	// store header information
+	m_duration = pTheHeader->duration;
+
+	m_flags = pTheHeader->flags;
+
+	// trying to phase out the old partial anim format here...
+	Dbg_MsgAssert( ( m_flags & nxBONEDANIMFLAGS_OLDPARTIALANIM ) == 0, ( "No longer supporting old partial anim format" ) ); 
+
+	// the anim converter should automatically do this,
+	// so that we don't have to do it at runtime...
+	Dbg_MsgAssert( pTheHeader->flags & nxBONEDANIMFLAGS_COMPRESSEDTIME, ( "Expected the anim times to be in frames" ) );
+	Dbg_MsgAssert( pTheHeader->flags & nxBONEDANIMFLAGS_PREROTATEDROOT, ( "Expected the root bones to be prerotated" ) );
+
+	// some debugging information
+//	Dbg_Message( "Loading animation %s", p_fileName );
+//	Dbg_Message( "Duration = %f", m_duration );
+//	Dbg_Message( "Flags = %08x", theHeader.flags );
+
+	// read the stream differently based on whether it's compressed...
+    if ( pTheHeader->flags & nxBONEDANIMFLAGS_PLATFORM )
+    {
+		Dbg_Assert( (pTheHeader->flags & nxBONEDANIMFLAGS_USECOMPRESSTABLE) == 0 );
+		success = this->plat_read_stream( pFileData, delete_buffer );
+    }
+    else if ( pTheHeader->flags & nxBONEDANIMFLAGS_USECOMPRESSTABLE )
+    {
+		Dbg_Assert( (pTheHeader->flags & nxBONEDANIMFLAGS_PLATFORM) == 0 );
+		success = this->plat_read_compressed_stream( pFileData, delete_buffer );
+    }
+    else
+    {
+        Dbg_MsgAssert( 0, ("Unrecognized file format (flags = %08x)...", pTheHeader->flags) );
+    }
+
+	// handle failure
+	Dbg_MsgAssert( success || !assertOnFail, ("PostLoad of anim file failed - bad format?") );
+
+	if ( !m_pipped )
+	{
+	// Done with temp buffer (since it's pipped, don't delete the file buffer)
+//	Mem::Free(mp_fileBuffer);
+//	mp_fileBuffer = NULL;
+	}
+	m_dataLoaded = success;
+
+    return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::IsValidTime( float time )
+{
+	return ( time >= 0.0f && time < m_duration );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float get_alpha( float timeStamp1, float timeStamp2, float time )
+{
+	Dbg_MsgAssert(timeStamp1 <= time && timeStamp2 >= time, ( "%f should be within [%f %f]", time, timeStamp1, timeStamp2 ));
+    
+	return (( time - timeStamp1 ) / ( timeStamp2 - timeStamp1 ));
+}
+
+// THE FOLLOWING 2 FUNCTIONS SHOULD BE REFACTORED?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::GetInterpolatedCameraFrames(Mth::Quat* pRotations, Mth::Vector* pTranslations, float time, Nx::CQuickAnim* pQuickAnim )
+{
+    Dbg_Assert( pRotations );
+    Dbg_Assert( pTranslations );
+
+	Dbg_Assert( is_hires() );
+	
+	float qAlpha = 0.0f;
+	float tAlpha = 0.0f;
+
+	// DMA the animation data here.
+#ifdef __ARAM__
+	int		size;
+	if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+	{
+		size = ( m_numBones * sizeof(uint32) );
+	}
+	else
+	{
+		size = 0;
+	}
+
+	if ( m_flags & nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS )
+	{
+		size += ( m_numBones * sizeof(SHiResAnimFramePointers) );
+	}
+	else
+	{
+		size += ( m_numBones * sizeof(SStandardAnimFramePointers) );
+	}
+	dma_count( (uint32)mp_perBoneFrames, size );
+	while ( framecount_active );
+//	if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+//	{
+//		dma_partial( (uint32)mp_partialAnimData, ( m_numBones * sizeof( uint16 ) * 2 ) );
+//	}
+//	while ( framecount_active );
+
+//	NsDMA::toMRAM( qqq, (uint32)mp_qFrames, m_num_qFrames * sizeof( CHiResAnimQKey ) );
+//	NsDMA::toMRAM( ttt, (uint32)mp_tFrames, m_num_tFrames * sizeof( CHiResAnimTKey ) );
+
+//	while( framecount_active );
+#endif		// __ARAM__
+
+	// find the nearest 2 keyframes
+	float qTimeStamp = time * 60.0f;
+	float tTimeStamp = time * 60.0f;
+	
+	CHiResAnimQKey* pStartQFrame = NULL;
+	CHiResAnimQKey* pEndQFrame = NULL;
+
+	CHiResAnimTKey* pStartTFrame = NULL;
+	CHiResAnimTKey* pEndTFrame = NULL;
+
+	CHiResAnimQKey* pCurrentQFrame = NULL;
+	CHiResAnimTKey* pCurrentTFrame = NULL;
+	
+#ifdef __ARAM__
+//	pStartQFrame = p_hqqq;
+//	pEndQFrame = p_hqqq;
+//
+//	pStartTFrame = p_httt;
+//	pEndTFrame = p_httt;
+//	
+//	pCurrentQFrame = p_hqqq;
+//	pCurrentTFrame = p_httt;
+
+	uint32 anim_q_offset = (uint32)mp_qFrames;
+	uint32 anim_t_offset = (uint32)mp_tFrames;
+
+	void * p_bone_frames;
+	if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+	{
+		p_bone_frames = &framecount[m_numBones*2];
+	}
+	else
+	{
+		p_bone_frames = framecount;
+	}
+
+#else
+	pStartQFrame = (CHiResAnimQKey*)mp_qFrames;
+	pEndQFrame = pStartQFrame;
+	pCurrentQFrame = pStartQFrame;
+
+	pStartTFrame = (CHiResAnimTKey*)mp_tFrames;
+	pEndTFrame = pStartTFrame;
+	pCurrentTFrame = pStartTFrame;
+
+	void * p_bone_frames = mp_perBoneFrames;
+#endif		// __aram__
+
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		int numQKeys = get_num_qkeys( p_bone_frames, i );
+		int numTKeys = get_num_tkeys( p_bone_frames, i );
+
+#ifdef __ARAM__
+		int q_off = 0;
+		{
+			// DMA this q track.
+			int aligned_off = ( anim_q_offset & ~31 );
+			int size = ( ( ( sizeof( CHiResAnimQKey ) * numQKeys ) - ( aligned_off - anim_q_offset ) ) + 31 ) & ~31;
+			DCFlushRange ( qqq, size );
+			framecount_size = size;
+			framecount_active = 1;
+			ARQPostRequest	(	&framecount_request,
+								0x55555555,											// Owner.
+								ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+								ARQ_PRIORITY_HIGH,									// Priority.
+								(u32)aligned_off,									// Source.
+								(uint32)qqq,										// Dest.
+								size,												// Length.
+								arqCallback );										// Callback
+			while ( framecount_active );
+			q_off = anim_q_offset - aligned_off;
+
+			pStartQFrame = (CHiResAnimQKey*)&qqq[q_off];
+			pEndQFrame = (CHiResAnimQKey*)&qqq[q_off];
+			pCurrentQFrame = (CHiResAnimQKey*)&qqq[q_off];
+		}
+#endif		// __ARAM__
+
+		for ( int j = 0; j < numQKeys; j++ )
+		{
+			if ( j == (numQKeys-1) )
+			{
+				// last frame
+				pStartQFrame = pCurrentQFrame + j;
+				pEndQFrame = pCurrentQFrame + j;
+				qAlpha = 0.0f;
+				break;
+			}
+			else if ( qTimeStamp >= timeDown(pCurrentQFrame[j].timestamp) 
+				 && qTimeStamp <= timeDown(pCurrentQFrame[j+1].timestamp) )
+			{
+				pStartQFrame = pCurrentQFrame + j;
+				pEndQFrame = pCurrentQFrame + j + 1;
+				qAlpha = get_alpha( timeDown(pStartQFrame->timestamp), timeDown(pEndQFrame->timestamp), qTimeStamp );
+				break;
+			}
+		}
+
+		interpolate_q_frame( pRotations, 
+						 pStartQFrame, 
+						 pEndQFrame,
+						 qAlpha,
+						 is_hires() );
+
+#ifdef __ARAM__
+		int t_off = 0;
+		{
+			// DMA this t track.
+			int aligned_off = ( anim_t_offset & ~31 );
+			int size = ( ( ( sizeof( CHiResAnimTKey ) * numTKeys ) - ( aligned_off - anim_t_offset ) ) + 31 ) & ~31;
+			DCFlushRange ( qqq, size );
+			framecount_size = size;
+			framecount_active = 1;
+			ARQPostRequest	(	&framecount_request,
+								0x55555555,											// Owner.
+								ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+								ARQ_PRIORITY_HIGH,									// Priority.
+								(u32)aligned_off,									// Source.
+								(uint32)qqq,										// Dest.
+								size,												// Length.
+								arqCallback );										// Callback
+			while ( framecount_active );
+			t_off = anim_t_offset - aligned_off;
+
+			pStartTFrame = (CHiResAnimTKey*)&qqq[t_off];
+			pEndTFrame = (CHiResAnimTKey*)&qqq[t_off];
+			pCurrentTFrame = (CHiResAnimTKey*)&qqq[t_off];
+		}
+#endif		// __ARAM__
+
+		for ( int j = 0; j < numTKeys; j++ )
+		{
+			if ( j == (numTKeys-1) )
+			{
+				// last frame
+				pStartTFrame = pCurrentTFrame + j;
+				pEndTFrame = pCurrentTFrame + j;
+				tAlpha = 0.0f;
+				break;
+			}
+			else if ( tTimeStamp >= timeDown(pCurrentTFrame[j].timestamp) 
+				 && tTimeStamp <= timeDown(pCurrentTFrame[j+1].timestamp) )
+			{
+				pStartTFrame = pCurrentTFrame + j;
+				pEndTFrame = pCurrentTFrame + j + 1;
+				tAlpha = get_alpha( timeDown(pStartTFrame->timestamp), timeDown(pEndTFrame->timestamp), tTimeStamp );
+				break;
+			}
+		}
+		
+		// theStartFrame and theEndFrame should contain the
+		// two closest keyframes here.  now interpolate between them
+		// TODO:  we might be able to cache some of this data...
+		interpolate_t_frame( pTranslations, 
+							 pStartTFrame, 
+							 pEndTFrame,
+							 tAlpha,
+							 is_hires() );
+
+#if 0
+		if ( m_printDebugInfo )
+		{
+			// for debug info, if necessary
+		}
+#endif
+		
+#ifdef __ARAM__
+		anim_q_offset += ( sizeof( CHiResAnimQKey ) * numQKeys );
+		anim_t_offset += ( sizeof( CHiResAnimTKey ) * numTKeys );
+#else
+		pCurrentQFrame += numQKeys;
+		pCurrentTFrame += numTKeys;
+#endif	// __ARAM__
+		pRotations++;
+		pTranslations++;
+	}
+
+    return true;
+}
+
+#ifdef __ARAM__
+inline int get_compressed_q_frame( char* pData, CStandardAnimQKey* pReturnData )
+#else
+inline char * get_compressed_q_frame( char* pData, CStandardAnimQKey* pReturnData )
+#endif		// __ARAM__
+{
+	unsigned char* p_data = (unsigned char*)pData;
+
+	short data = (short)(( p_data[1]<<8 ) | p_data[0] );
+	pReturnData->timestamp = data & 0x3fff;
+	pReturnData->signBit = data & 0x8000 ? 1 : 0;
+	
+	// skip the timestamp
+	p_data += sizeof( short );
+
+	if ( data & 0x4000 )
+	{
+		if ( !(data & 0x3800) )
+		{
+			unsigned char d = *p_data;
+
+			// a char* defaults to a signed char*,
+			// which means you'll try
+			// to access outside the array
+			// boundaries...  brutal!
+			//Dbg_Assert( *p_data == d );
+
+			CBonedAnimCompressEntry* pEntry = &sQTable[d];
+			pReturnData->qx = pEntry->x48;
+			pReturnData->qy = pEntry->y48;
+			pReturnData->qz = pEntry->z48;
+			p_data += sizeof( char );
+
+			// remove the bits
+			pReturnData->timestamp &= 0x07ff;
+		}
+		else
+		{
+			if ( data & 0x2000 )
+			{
+				pReturnData->qx = p_data[0];
+				p_data += sizeof( char );
+			}
+			else
+			{
+				pReturnData->qx = (p_data[1]<<8) | p_data[0];
+				p_data += sizeof( short );
+			}
+
+			if ( data & 0x1000 )
+			{
+				pReturnData->qy = p_data[0];
+				p_data += sizeof( char );
+			}
+			else
+			{
+				pReturnData->qy = (p_data[1]<<8) | p_data[0];
+				p_data += sizeof( short );
+			}
+
+			if ( data & 0x0800 )
+			{
+				pReturnData->qz = p_data[0];
+				p_data += sizeof( char );
+			}
+			else
+			{
+				pReturnData->qz = (p_data[1]<<8) | p_data[0];
+				p_data += sizeof( short );
+			}
+
+			// remove the bits
+			pReturnData->timestamp &= 0x07ff;
+		}
+	}
+	else
+	{
+		// no compression
+		pReturnData->qx = (p_data[1]<<8) | p_data[0];
+		pReturnData->qy = (p_data[3]<<8) | p_data[2];
+		pReturnData->qz = (p_data[5]<<8) | p_data[4];
+		p_data += 6;
+	}
+
+#ifdef __ARAM__
+	return (int)p_data - (int)pData;
+#else
+	return (char*)p_data;
+#endif		// __ARAM__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __ARAM__
+inline int get_compressed_t_frame( char* pData, CStandardAnimTKey* pReturnData )
+#else
+inline char * get_compressed_t_frame( char* pData, CStandardAnimTKey* pReturnData )
+#endif		// __ARAM__
+{
+	unsigned char* p_data = (unsigned char*)pData;
+	
+	bool useLookupTable = false;
+	
+	unsigned char flags = *p_data;
+	p_data += sizeof( char );
+	
+	if ( flags & 0x80 )
+	{					
+		useLookupTable = true;
+	}
+
+	if ( flags & 0x40 )
+	{
+		// expand the timestamp
+		pReturnData->timestamp = (unsigned short)(flags & 0x3f);
+	}
+	else
+	{
+		// read another short
+		pReturnData->timestamp = (unsigned short)(p_data[1]<<8) | p_data[0];
+		p_data += sizeof( short );
+	}
+
+	if ( useLookupTable )
+	{
+		unsigned char d = *p_data;
+
+		// defaults to signed char,
+		// which means you'll try
+		// to access outside the array
+		// boundaries...  brutal!
+		//Dbg_Assert( *p_data == d );
+		
+		CBonedAnimCompressEntry* pEntry = &sTTable[d];
+		pReturnData->tx = pEntry->x48;
+		pReturnData->ty = pEntry->y48;
+		pReturnData->tz = pEntry->z48;
+		p_data += sizeof( char );
+	}
+	else
+	{
+		// no compression
+		pReturnData->tx = (p_data[1]<<8) | p_data[0];
+		pReturnData->ty = (p_data[3]<<8) | p_data[2];
+		pReturnData->tz = (p_data[5]<<8) | p_data[4];
+		p_data += 6;
+	}
+
+#ifdef __ARAM__
+	return (int)p_data - (int)pData;
+#else
+	return (char*)p_data;
+#endif		// __ARAM__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::GetCompressedInterpolatedFrames(Mth::Quat* pRotations, Mth::Vector* pTranslations, float time, Nx::CQuickAnim* pQuickAnim )
+{
+    Dbg_Assert( pRotations );
+    Dbg_Assert( pTranslations );
+
+	Dbg_Assert( !is_hires() );
+
+	CStandardAnimQKey theStartQFrame;
+	CStandardAnimQKey theEndQFrame;
+	CStandardAnimQKey* pStartQFrame = &theStartQFrame;
+	CStandardAnimQKey* pEndQFrame = &theEndQFrame;
+	
+	CStandardAnimTKey theStartTFrame;
+	CStandardAnimTKey theEndTFrame;
+	CStandardAnimTKey* pStartTFrame = &theStartTFrame;
+	CStandardAnimTKey* pEndTFrame = &theEndTFrame;
+	
+#ifdef __ARAM__
+	char* pCurrentQFrame = mp_qFrames;//&mp_qFrames[32];
+	char* pCurrentTFrame = mp_tFrames;//&mp_tFrames[32];
+
+	// Pre-dma framecount sizes.
+	dma_count( (uint32)mp_perBoneQFrameSize, ( m_numBones * sizeof( uint16 ) * 2 ) + 16 );		// +16 should cover partial mask data.
+//	if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+//	{
+//		while ( framecount_active );
+//		dma_partial( (uint32)mp_partialAnimData );
+//	}
+//	uint32 * partial = (uint32 *)&framecount[( (uint32)mp_partialAnimData - (uint32)mp_perBoneQFrameSize ) * 2];
+	uint32 * partial = (uint32 *)&framecount[( (uint32)mp_partialAnimData - (uint32)mp_perBoneQFrameSize ) / 2];
+
+	// Pre-DMA all data.
+//	uint32 q_lines = ((uint32)mp_boneNames) >> 16;
+//	uint32 t_lines = ((uint32)mp_boneNames) & 0xffff;
+//	q_lines = ( ( q_lines + ( ARAM_CACHE_LINE_SIZE - 1 ) ) / ARAM_CACHE_LINE_SIZE ); 
+//	t_lines = ( ( t_lines + ( ARAM_CACHE_LINE_SIZE - 1 ) ) / ARAM_CACHE_LINE_SIZE ); 
+
+//	for ( uint32 lp = 0; lp < q_lines; lp++ ) dma_q_entry( ((uint32)mp_qFrames), lp );
+//	for ( uint32 lp = 0; lp < t_lines; lp++ ) dma_t_entry( ((uint32)mp_tFrames), lp );
+
+	uint16* pQSizes = &framecount[0];
+	uint16* pTSizes = &framecount[m_numBones];
+
+	// Make sure framecount sizes have been DMA'd.
+	while ( framecount_active );
+#else
+	Dbg_Assert( mp_qFrames );
+	Dbg_Assert( mp_tFrames );
+
+	char* pCurrentQFrame = (char*)mp_qFrames;
+	char* pCurrentTFrame = (char*)mp_tFrames;
+
+	uint16* pQSizes = &mp_perBoneQFrameSize[0];
+	uint16* pTSizes = &mp_perBoneTFrameSize[0];
+#endif		// __aram__
+	
+	// find the nearest 2 keyframes
+	float qTimeStamp = time * 60.0f;
+	float tTimeStamp = time * 60.0f;
+	
+	// Precalculate the skip index mask for speed.
+	uint32 skip_index_mask = ( pQuickAnim && pQuickAnim->m_quickAnimPointers.valid ) ? ( 1 << pQuickAnim->m_quickAnimPointers.skipIndex ) : 0;
+
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		// See if the QuickAnim data indicates that this bone may be skipped.
+		bool skip_this_bone = ( skip_index_mask ) ? (( pQuickAnim->m_quickAnimPointers.pSkipList[i] & skip_index_mask ) > 0 ) : false;
+
+		if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+		{
+#ifdef __ARAM__
+			uint32* pBoneMask = ( partial + 1 ) + ( i / 32 );
+#else
+			uint32* pBoneMask = ( mp_partialAnimData + 1 ) + ( i / 32 );
+#endif		// __ARAM__
+			skip_this_bone = ( ( (*pBoneMask) & ( 1 << (i%32) ) ) == 0 );
+//			skip_this_bone = ( ( (*pBoneMask) & ( (1<<31) >> (i%32) ) ) == 0 );
+		}
+
+		if( !skip_this_bone )
+		{
+			float qAlpha = 0.0f;
+			float tAlpha = 0.0f;
+
+			char* pNextQFrame = pCurrentQFrame;
+			char* pNextQBone = pCurrentQFrame + *pQSizes;
+		
+			char* pNextTFrame = pCurrentTFrame;
+    		char* pNextTBone = pCurrentTFrame + *pTSizes;
+
+			if ( pQuickAnim && pQuickAnim->m_quickAnimPointers.valid )
+			{
+				pNextQFrame = pQuickAnim->m_quickAnimPointers.pQuickQKey[i];
+				pNextTFrame = pQuickAnim->m_quickAnimPointers.pQuickTKey[i];
+			}
+	 
+#ifdef __ARAM__
+			int q_off = 0;
+			{
+				// DMA this q track.
+				uint aligned_off = ( (int)pNextQFrame & ~31 );
+				uint size = ( ( *pQSizes - ( aligned_off - (int)pCurrentQFrame ) ) + 31 ) & ~31;
+				DCFlushRange ( qqq, size );
+				framecount_size = size;
+				framecount_active = 1;
+				ARQPostRequest	(	&framecount_request,
+									0x55555555,											// Owner.
+									ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+									ARQ_PRIORITY_HIGH,									// Priority.
+									(u32)aligned_off,									// Source.
+									(uint32)qqq,										// Dest.
+									size,												// Length.
+									arqCallback );										// Callback
+				q_off = (int)pNextQFrame - aligned_off;
+			}
+#endif		// __ARAM__
+
+			while ( 1 )
+			{
+				if ( pQuickAnim )
+				{
+					pQuickAnim->m_quickAnimPointers.pQuickQKey[i] = (char*)pNextQFrame;
+				}
+
+#				ifdef __ARAM__
+				while ( framecount_active );
+#				endif		// __ARAM__
+
+#ifdef __ARAM__
+				int bytes = get_compressed_q_frame( &qqq[q_off], pStartQFrame );
+				q_off += bytes;
+				pNextQFrame = &pNextQFrame[bytes];
+#else
+				pNextQFrame = get_compressed_q_frame( pNextQFrame, pStartQFrame );
+#endif		// __ARAM__
+				if ( pNextQFrame >= pNextQBone )
+				{
+					// last frame
+					*pEndQFrame = *pStartQFrame;
+					qAlpha = 0.0f;
+					break;
+				}
+
+#ifdef __ARAM__
+				get_compressed_q_frame( &qqq[q_off], pEndQFrame );
+#else
+				get_compressed_q_frame( pNextQFrame, pEndQFrame );
+#endif		// __ARAM__
+				if ( qTimeStamp >= timeDown(pStartQFrame->timestamp) && qTimeStamp <= timeDown(pEndQFrame->timestamp) )
+				{
+					qAlpha = get_alpha( timeDown(pStartQFrame->timestamp), timeDown(pEndQFrame->timestamp), qTimeStamp );
+					break;
+				}
+			}
+
+			// theStartFrame and theEndFrame should contain the
+			// two closest keyframes here.  now interpolate between them
+			// TODO:  we might be able to cache some of this data...
+			interpolate_standard_q_frame( pRotations, pStartQFrame, pEndQFrame, qAlpha );
+			
+#ifdef __ARAM__
+			int t_off = 0;
+			{
+				// DMA this q track.
+				int aligned_off = ( (int)pNextTFrame & ~31 );
+				int size = ( ( *pTSizes - ( aligned_off - (int)pCurrentTFrame ) ) + 31 ) & ~31;
+				DCFlushRange ( qqq, size );
+				framecount_size = size;
+				framecount_active = 1;
+				ARQPostRequest	(	&framecount_request,
+									0x55555555,											// Owner.
+									ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+									ARQ_PRIORITY_HIGH,									// Priority.
+									(u32)aligned_off,									// Source.
+									(uint32)qqq,										// Dest.
+									size,												// Length.
+									arqCallback );										// Callback
+				t_off = (int)pNextTFrame - aligned_off;
+			}
+#endif		// __ARAM__
+
+			while ( 1 )
+			{
+				if ( pQuickAnim )
+				{
+					pQuickAnim->m_quickAnimPointers.pQuickTKey[i] = (char*)pNextTFrame;
+				}
+
+#				ifdef __ARAM__
+				while ( framecount_active );
+#				endif		// __ARAM__
+
+#ifdef __ARAM__
+				int bytes = get_compressed_t_frame( &qqq[t_off], pStartTFrame );
+				t_off += bytes;
+				pNextTFrame = &pNextTFrame[bytes];
+#else
+				pNextTFrame = get_compressed_t_frame( pNextTFrame, pStartTFrame );
+#endif		// __ARAM__
+				if ( pNextTFrame >= pNextTBone )
+				{
+					// last frame
+					*pEndTFrame = *pStartTFrame;
+					tAlpha = 0.0f;
+					break;
+				}
+
+#ifdef __ARAM__
+				get_compressed_t_frame( &qqq[t_off], pEndTFrame );
+#else
+				get_compressed_t_frame( pNextTFrame, pEndTFrame );
+#endif		// __ARAM__
+				if ( tTimeStamp >= timeDown(pStartTFrame->timestamp) && tTimeStamp <= timeDown(pEndTFrame->timestamp) )
+				{
+					tAlpha = get_alpha( timeDown(pStartTFrame->timestamp), timeDown(pEndTFrame->timestamp), tTimeStamp );
+					break;
+				}
+			}
+
+			interpolate_standard_t_frame( pTranslations, pStartTFrame, pEndTFrame, tAlpha );
+		}
+		
+		pCurrentQFrame += *pQSizes;
+		pCurrentTFrame += *pTSizes;
+		pQSizes++;
+		pTSizes++;
+		pRotations++;
+		pTranslations++;
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::GetInterpolatedFrames(Mth::Quat* pRotations, Mth::Vector* pTranslations, float time, Nx::CQuickAnim* pQuickAnim )
+{
+	if ( m_flags & nxBONEDANIMFLAGS_USECOMPRESSTABLE )
+	{
+		// new partial anims now use the standard GetCompressedInterpolatedFrames function
+		return GetCompressedInterpolatedFrames(pRotations, pTranslations, time, pQuickAnim);
+	}
+
+	if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+	{
+		Dbg_MsgAssert( 0, ( "Non-compressed partial animations are not supported yet" ) );
+	}
+
+    Dbg_Assert( pRotations );
+    Dbg_Assert( pTranslations );
+
+	Dbg_Assert( !is_hires() );
+    
+	float qAlpha = 0.0f;
+	float tAlpha = 0.0f;
+
+	// DMA the animation data here.
+#ifdef __ARAM__
+	int		size;
+	if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+	{
+		size = ( m_numBones * sizeof(uint32) );
+	}
+	else
+	{
+		size = 0;
+	}
+
+	if ( m_flags & nxBONEDANIMFLAGS_HIRESFRAMEPOINTERS )
+	{
+		size += ( m_numBones * sizeof(SHiResAnimFramePointers) );
+	}
+	else
+	{
+		size += ( m_numBones * sizeof(SStandardAnimFramePointers) );
+	}
+	dma_count( (uint32)mp_perBoneFrames, size );
+	while ( framecount_active );
+//	if ( m_flags & nxBONEDANIMFLAGS_PARTIALANIM )
+//	{
+//		dma_partial( (uint32)mp_partialAnimData, ( m_numBones * sizeof( uint16 ) * 2 ) );
+//	}
+//	while ( framecount_active );
+
+//	NsDMA::toMRAM( p_sqqq, (uint32)mp_qFrames, m_num_qFrames * sizeof( CStandardAnimQKey ) );
+//	NsDMA::toMRAM( p_sttt, (uint32)mp_tFrames, m_num_tFrames * sizeof( CStandardAnimTKey ) );
+
+//	while( framecount_active );
+#endif		// __ARAM__
+
+	// find the nearest 2 keyframes
+	float qTimeStamp = time * 60.0f;
+	float tTimeStamp = time * 60.0f;
+	
+	CStandardAnimQKey* pStartQFrame = NULL;
+	CStandardAnimQKey* pEndQFrame = NULL;
+
+	CStandardAnimTKey* pStartTFrame = NULL;
+	CStandardAnimTKey* pEndTFrame = NULL;
+
+	CStandardAnimQKey* pCurrentQFrame = NULL;
+	CStandardAnimTKey* pCurrentTFrame = NULL;
+	
+#ifdef __ARAM__
+//	pStartQFrame = p_sqqq;
+//	pEndQFrame = p_sqqq;
+//
+//	pStartTFrame = p_sttt;
+//	pEndTFrame = p_sttt;
+//	
+//	pCurrentQFrame = p_sqqq;
+//	pCurrentTFrame = p_sttt;
+
+	uint32 anim_q_offset = (uint32)mp_qFrames;
+	uint32 anim_t_offset = (uint32)mp_tFrames;
+
+	void * p_bone_frames;
+	if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+	{
+		p_bone_frames = &framecount[m_numBones*2];
+	}
+	else
+	{
+		p_bone_frames = framecount;
+	}
+#else
+	pStartQFrame = (CStandardAnimQKey*)mp_qFrames;
+	pEndQFrame = (CStandardAnimQKey*)mp_qFrames;
+
+	pStartTFrame = (CStandardAnimTKey*)mp_tFrames;
+	pEndTFrame = (CStandardAnimTKey*)mp_tFrames;
+	
+	pCurrentQFrame = (CStandardAnimQKey*)mp_qFrames;
+	pCurrentTFrame = (CStandardAnimTKey*)mp_tFrames;
+
+	void * p_bone_frames = mp_perBoneFrames;
+#endif		// __aram__
+
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		int numQKeys = get_num_qkeys( p_bone_frames, i );
+		int numTKeys = get_num_tkeys( p_bone_frames, i );
+
+#ifdef __ARAM__
+		int q_off = 0;
+		{
+			// DMA this q track.
+			int aligned_off = ( anim_q_offset & ~31 );
+			int size = ( ( ( sizeof( CStandardAnimQKey ) * numQKeys ) - ( aligned_off - anim_q_offset ) ) + 31 ) & ~31;
+			DCFlushRange ( qqq, size );
+			framecount_size = size;
+			framecount_active = 1;
+			ARQPostRequest	(	&framecount_request,
+								0x55555555,											// Owner.
+								ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+								ARQ_PRIORITY_HIGH,									// Priority.
+								(u32)aligned_off,									// Source.
+								(uint32)qqq,										// Dest.
+								size,												// Length.
+								arqCallback );										// Callback
+			while ( framecount_active );
+			q_off = anim_q_offset - aligned_off;
+
+			pStartQFrame = (CStandardAnimQKey*)&qqq[q_off];
+			pEndQFrame = (CStandardAnimQKey*)&qqq[q_off];
+			pCurrentQFrame = (CStandardAnimQKey*)&qqq[q_off];
+		}
+#endif		// __ARAM__
+
+		for ( int j = 0; j < numQKeys; j++ )
+		{
+			if ( j == (numQKeys-1) )
+			{
+				// last frame
+				pStartQFrame = pCurrentQFrame + j;
+				pEndQFrame = pCurrentQFrame + j;
+				qAlpha = 0.0f;
+				break;
+			}
+			else if ( qTimeStamp >= timeDown(pCurrentQFrame[j].timestamp) 
+				 && qTimeStamp <= timeDown(pCurrentQFrame[j+1].timestamp) )
+			{
+				pStartQFrame = pCurrentQFrame + j;
+				pEndQFrame = pCurrentQFrame + j + 1;
+				qAlpha = get_alpha( timeDown(pStartQFrame->timestamp), timeDown(pEndQFrame->timestamp), qTimeStamp );
+				break;
+			}
+		}
+
+		interpolate_q_frame( pRotations, 
+						 pStartQFrame, 
+						 pEndQFrame,
+						 qAlpha,
+						 is_hires() );
+
+#ifdef __ARAM__
+		int t_off = 0;
+		{
+			// DMA this t track.
+			int aligned_off = ( anim_t_offset & ~31 );
+			int size = ( ( ( sizeof( CStandardAnimTKey ) * numTKeys ) - ( aligned_off - anim_t_offset ) ) + 31 ) & ~31;
+			DCFlushRange ( qqq, size );
+			framecount_size = size;
+			framecount_active = 1;
+			ARQPostRequest	(	&framecount_request,
+								0x55555555,											// Owner.
+								ARQ_TYPE_ARAM_TO_MRAM,								// Type.
+								ARQ_PRIORITY_HIGH,									// Priority.
+								(u32)aligned_off,									// Source.
+								(uint32)qqq,										// Dest.
+								size,												// Length.
+								arqCallback );										// Callback
+			while ( framecount_active );
+			t_off = anim_t_offset - aligned_off;
+
+			pStartTFrame = (CStandardAnimTKey*)&qqq[t_off];
+			pEndTFrame = (CStandardAnimTKey*)&qqq[t_off];
+			pCurrentTFrame = (CStandardAnimTKey*)&qqq[t_off];
+		}
+#endif		// __ARAM__
+
+		for ( int j = 0; j < numTKeys; j++ )
+		{
+			if ( j == (numTKeys-1) )
+			{
+				// last frame
+				pStartTFrame = pCurrentTFrame + j;
+				pEndTFrame = pCurrentTFrame + j;
+				tAlpha = 0.0f;
+				break;
+			}
+			else if ( tTimeStamp >= timeDown(pCurrentTFrame[j].timestamp) 
+				 && tTimeStamp <= timeDown(pCurrentTFrame[j+1].timestamp) )
+			{
+				pStartTFrame = pCurrentTFrame + j;
+				pEndTFrame = pCurrentTFrame + j + 1;
+				tAlpha = get_alpha( timeDown(pStartTFrame->timestamp), timeDown(pEndTFrame->timestamp), tTimeStamp );
+				break;
+			}
+		}
+		
+		// theStartFrame and theEndFrame should contain the
+		// two closest keyframes here.  now interpolate between them
+		// TODO:  we might be able to cache some of this data...
+		interpolate_t_frame( pTranslations, 
+							 pStartTFrame, 
+							 pEndTFrame,
+							 tAlpha,
+							 is_hires() );
+
+#if 0
+		if ( m_printDebugInfo )
+		{
+			// for debug info, if necessary
+		}
+#endif
+
+#ifdef __ARAM__
+		anim_q_offset += ( sizeof( CStandardAnimQKey ) * numQKeys );
+		anim_t_offset += ( sizeof( CStandardAnimTKey ) * numTKeys );
+#else
+		pCurrentQFrame += numQKeys;
+		pCurrentTFrame += numTKeys;
+#endif	// __ARAM__
+		pRotations++;
+		pTranslations++;
+	}
+
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CBonedAnimFrameData::GetBoneName( int index )
+{
+	// only the object anim stores the name of the bones
+	Dbg_MsgAssert( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA, ( "Bone names are only stored with object anims" ) );
+	
+	Dbg_MsgAssert( index >= 0 && index < m_numBones, ( "Bone name request out of range %d (0-%d)", index, m_numBones ) );
+
+	Dbg_Assert( mp_boneNames );
+
+#ifdef __ARAM__
+	int		size;
+	if ( m_flags & nxBONEDANIMFLAGS_OBJECTANIMDATA )
+	{
+		size = ( m_numBones * sizeof(uint32) );
+	}
+	else
+	{
+		size = 0;
+	}
+	dma_count( (uint32)mp_perBoneFrames, size );
+	while ( framecount_active );
+
+	uint32 * p32 = (uint32*)framecount;
+	return p32[index];
+#else
+	return mp_boneNames[index];
+#endif		// __ARAM__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CBonedAnimFrameData::get_num_customkeys()
+{
+	return m_num_customKeys;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCustomAnimKey* CBonedAnimFrameData::get_custom_key( int index )
+{
+	Dbg_Assert( index >= 0 && index < get_num_customkeys() );
+	Dbg_MsgAssert( mpp_customAnimKeyList, ( "custom key list doesn't exist" ) );
+	return mpp_customAnimKeyList[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::ResetCustomKeys()
+{
+	// this assumes that there's only going to be one 
+	// animcontroller calling this frame data...
+	// eventually, we might need to move this
+	// into CReferencedFrameData...
+
+	int customKeyCount = get_num_customkeys();
+	for ( int i = 0; i < customKeyCount; i++ )
+	{					 
+		CCustomAnimKey* pKey = get_custom_key(i);
+		pKey->SetActive( true );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimFrameData::ProcessCustomKeys( float startTime, float endTime, Obj::CObject* pObject, bool inclusive )
+{
+	// for each key,
+	// see if it's between the start and end time
+	// start_time <= time < end_time
+
+	// get it into frames...
+	startTime *= 60.0f;
+	endTime *= 60.0f;
+
+	int customKeyCount = get_num_customkeys();
+	for ( int i = 0; i < customKeyCount; i++ )
+	{
+		CCustomAnimKey* pKey = get_custom_key(i);
+		if ( pKey->WithinRange( startTime, endTime, inclusive ) )
+		{
+//			printf( "Processing key at %f (%f %f)\n", custom_key_time, startTime, endTime );
+			pKey->ProcessKey( pObject );
+		}
+		else
+		{
+//			printf( "Not processing key at %f (%f %f)\n", custom_key_time, startTime, endTime );
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
+
+
+
+
diff --git a/Code/Gfx/BonedAnim.h b/Code/Gfx/BonedAnim.h
new file mode 100644
index 0000000..b0ab710
--- /dev/null
+++ b/Code/Gfx/BonedAnim.h
@@ -0,0 +1,206 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       bonedanim.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/14/2001
+//****************************************************************************
+
+#ifndef __GFX_BONEDANIM_H
+#define __GFX_BONEDANIM_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Nx
+{
+	class CQuickAnim;
+}
+
+namespace Obj
+{
+	class CObject;
+}
+
+namespace File
+{
+	class CAsyncFileHandle;
+}
+			 
+namespace Gfx
+{
+	class CAnimQKey;
+	class CAnimTKey;
+	struct SAnimCustomKey;
+	struct SQuickAnimPointers;
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+// Raw Animation Data
+class CBonedAnimFrameData
+{
+public:
+    CBonedAnimFrameData();
+    ~CBonedAnimFrameData();
+
+public:
+    bool    			   	Load(uint32* pData, int file_size, bool assertOnFail);
+    bool    			   	Load(const char* p_fileName, bool assertOnFail, bool async, bool use_pip = false);
+    bool    			   	PostLoad(bool assertOnFail, int file_size, bool delete_buffer = true);
+	bool					LoadFinished();
+	bool					IsValidTime(float time);
+	float					GetDuration();
+	int						GetNumBones();
+	uint32					GetBoneName( int index );
+    bool				    GetInterpolatedFrames(Mth::Quat* pRotations, Mth::Vector* pTranslations, float time, Nx::CQuickAnim* = NULL);
+    bool				    GetCompressedInterpolatedFrames(Mth::Quat* pRotations, Mth::Vector* pTranslations, float time, Nx::CQuickAnim* = NULL);
+    bool				    GetCompressedInterpolatedPartialFrames(Mth::Quat* pRotations, Mth::Vector* pTranslations, float time, Nx::CQuickAnim* = NULL);
+    bool				    GetInterpolatedCameraFrames(Mth::Quat* pRotations, Mth::Vector* pTranslations, float time, Nx::CQuickAnim* = NULL);
+	bool					ResetCustomKeys( void );
+	bool					ProcessCustomKeys( float startTimeInclusive, float endTimeExclusive, Obj::CObject* pObject, bool endInclusive = false );
+
+	CAnimQKey*				GetQFrames( void ) { return (CAnimQKey*)mp_qFrames; }
+	CAnimTKey*				GetTFrames( void ) { return (CAnimTKey*)mp_tFrames; }
+
+	void					SetQFrames( CAnimQKey * p_q ) { mp_qFrames = (char*)p_q; }
+	void					SetTFrames( CAnimTKey * p_t ) { mp_tFrames = (char*)p_t; }
+
+protected:	
+	int						get_num_customkeys();
+	CCustomAnimKey*			get_custom_key( int index );
+	int						get_num_qkeys( void * p_frame_data, int boneIndex ) const;
+	int						get_num_tkeys( void * p_frame_data, int boneIndex ) const;
+	void					set_num_qkeys( int boneIndex, int numKeys );
+	void					set_num_tkeys( int boneIndex, int numKeys );
+	bool					is_hires() const;
+
+	static void 			async_callback(File::CAsyncFileHandle *, File::EAsyncFunctionType function,
+										   int result, unsigned int arg0, unsigned int arg1);
+
+protected:
+//	bool				   	intermediate_read_stream(void* pStream);
+    bool                    plat_read_stream(uint8* pData, bool delete_buffer = true);
+    bool                    plat_read_compressed_stream(uint8* pData, bool delete_buffer = true);
+	bool					plat_dma_to_aram( int qbytes = 0, int tbytes = 0, uint32 flags = 0 );
+	
+protected:
+	float				   	m_duration;		  // could be a short
+	uint32					m_flags;
+	int						m_numBones;
+
+	// file buffer (the only malloc'ed pointer)
+	void*					mp_fileBuffer;
+	File::CAsyncFileHandle* mp_fileHandle;
+	int						m_fileSize;
+
+	// massive block of all Q-frames and T-frames
+	char*		   			mp_qFrames;
+	char*		   			mp_tFrames;
+
+	// per-bone pointers into the massive block of Q-frames and T-frames
+	void*					mp_perBoneFrames;
+
+	// list of bone names (for object anims only)
+	uint32*					mp_boneNames;
+		
+	// original number of bones + list of bone masks (for partial anims only)
+	uint32*					mp_partialAnimData;
+
+	uint32					m_fileNameCRC;
+
+	// custom keys (for cameras, changing parent bones, etc.)
+	CCustomAnimKey** 		mpp_customAnimKeyList;
+
+	uint16*					mp_perBoneQFrameSize;
+	uint16*					mp_perBoneTFrameSize;
+	
+	short					m_num_qFrames;
+	short					m_num_tFrames;
+	short					m_num_customKeys;
+	
+	short					m_printDebugInfo:1;
+	short					m_dataLoaded:1;
+	short					m_pipped:1;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+bool InitQ48Table( const char* pFileName, bool assertOnFail = true );
+bool InitT48Table( const char* pFileName, bool assertOnFail = true );
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline float CBonedAnimFrameData::GetDuration()
+{
+	return m_duration;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline int CBonedAnimFrameData::GetNumBones()
+{
+	return m_numBones;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline bool CBonedAnimFrameData::LoadFinished()
+{
+	return m_dataLoaded;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // namespace Gfx
+
+#endif	// __GFX_BONEDANIM_H
diff --git a/Code/Gfx/BonedAnimTypes.h b/Code/Gfx/BonedAnimTypes.h
new file mode 100644
index 0000000..234d6e8
--- /dev/null
+++ b/Code/Gfx/BonedAnimTypes.h
@@ -0,0 +1,143 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       BonedAnimTypes.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  02/04/2003
+//****************************************************************************
+
+#ifndef __GFX_BONEDANIMTYPES_H
+#define __GFX_BONEDANIMTYPES_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Gfx
+{
+ 
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CAnimQKey
+{
+public:
+#ifdef __PLAT_NGC__
+	short			signBit:1;	// 1 = negative
+	short			timestamp:15;
+#else
+	short			timestamp:15;
+	short			signBit:1;	// 1 = negative
+#endif		// __PLAT_NGC__
+
+protected:
+	CAnimQKey() {}
+};
+
+class CStandardAnimQKey	: public CAnimQKey
+{
+public:
+    short           qx;
+    short           qy;
+    short           qz;
+};
+
+class CHiResAnimQKey : public CAnimQKey
+{
+public:
+    float           qx;
+    float           qy;
+    float           qz;
+};
+
+class CAnimTKey
+{
+public:
+	short			timestamp;
+
+protected:
+	CAnimTKey() {}
+};
+
+class CStandardAnimTKey	: public CAnimTKey
+{
+public:
+    short           tx;
+    short           ty;
+    short           tz;
+};
+
+class CHiResAnimTKey : public CAnimTKey
+{
+public:
+    float           tx;
+    float           ty;
+    float           tz;
+};
+
+struct SQuickAnimPointers
+{
+	char* 				pQuickQKey[64];
+	char*				pQuickTKey[64];
+	CStandardAnimQKey	theStartQKey[64];
+	CStandardAnimQKey	theEndQKey[64];
+	CStandardAnimTKey	theStartTKey[64];
+	CStandardAnimTKey	theEndTKey[64];
+//	char				qSkip[64];
+//	char				tSkip[64];
+	bool				valid;
+	uint32*				pSkipList;
+	uint32				skipIndex;
+};
+
+// NOTE: if you change this enum, update the CAnimChannel::GetDebugInfo switch statement!	
+enum EAnimLoopingType
+{
+	LOOPING_HOLD				= 0,	// holds on last frame
+	LOOPING_CYCLE,						// cycles the animation forever
+	LOOPING_PINGPONG,					// pingpongs the animation forever
+	LOOPING_WOBBLE,						// Aims towards wobble_target_time whilst wobbling a bit. Used for manuals & grinds.
+
+// these are samples of other possible looping types,
+// although they have not yet been implemented
+//	LOOPING_CYCLE_X_TIMES,				// cycles the same animation x times
+//	LOOPING_PINGPONG_X_TIMES			// pingpongs the animation x times
+};
+	
+// NOTE: if you change this enum, update the CAnimChannel::GetDebugInfo switch statement!	
+enum EAnimDirection 
+{
+	ANIM_DIR_FORWARDS			= 0,
+	ANIM_DIR_BACKWARDS			= -1
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**						Inline Functions									**
+*****************************************************************************/
+					
+} // namespace Gfx
+
+#endif // __GFX_BONEDANIMTYPES_H
\ No newline at end of file
diff --git a/Code/Gfx/CasUtils.cpp b/Code/Gfx/CasUtils.cpp
new file mode 100644
index 0000000..6fe0750
--- /dev/null
+++ b/Code/Gfx/CasUtils.cpp
@@ -0,0 +1,358 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       CasUtils.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  ?/??/????
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include  
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Cas
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 get_desc_id_from_structure( Script::CStruct* pStructure )
+{
+	Dbg_Assert( pStructure );
+
+	uint32 descId;
+	pStructure->GetChecksum( CRCD(0x4bb2084e,"desc_id"), &descId, true );
+	return descId;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool IsValidOption( uint32 partChecksum, uint32 lookFor )
+{
+	return ( GetOptionStructure( partChecksum, lookFor ) != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* GetOptionStructure( uint32 partChecksum, uint32 lookFor, bool assertOnFail )
+{
+	Script::CArray* pArray;
+	pArray = Script::GetArray( partChecksum, Script::ASSERT );
+
+	Script::CStruct* pReturnStructure = NULL;
+
+	if ( lookFor == 0 )
+	{
+		// special checksum, meaning to return NULL
+		// (handy when preprocessing random descs)
+		return NULL;
+	}
+
+	for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+	{
+		Script::CStruct* pCurrDesc = pArray->GetStructure( i );
+		uint32 desc_id = get_desc_id_from_structure( pCurrDesc );
+		if ( desc_id == lookFor )
+		{
+			pReturnStructure = pCurrDesc;
+			break;
+		}
+	}
+
+	if ( assertOnFail )
+	{
+		Dbg_MsgAssert( pReturnStructure, ( "Couldn't find %s %s", Script::FindChecksumName(partChecksum), Script::FindChecksumName(lookFor) ) );
+	}
+
+	return pReturnStructure;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* GetFirstOptionStructure( uint32 partChecksum )
+{
+	Script::CArray* pArray;
+	pArray = Script::GetArray( partChecksum, Script::ASSERT );
+
+	return pArray->GetStructure( 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BuildRandomSetList( uint32 partChecksum, uint32 random_set, Script::CStruct** ppReturnStructs, int* pReturnNumStructs )
+{
+	Script::CArray* pArray;
+	pArray = Script::GetArray( partChecksum, Script::ASSERT );
+
+	// reset the return value
+	*pReturnNumStructs = 0;
+
+	int part_array_size = pArray->GetSize();
+	for ( int i = 0; i < part_array_size; i++ )
+	{
+		Script::CStruct* pStruct = pArray->GetStructure( i );
+		int allowed_to_pick = 0;
+		pStruct->GetInteger( CRCD(0x355f9467,"allowed_to_pick"), &allowed_to_pick, Script::ASSERT );
+		
+		if ( allowed_to_pick )
+		{
+			// if a random_set was specified, then
+			// make sure that this item works with it
+			// (used for matching skin tones)
+			uint32 this_random_set = 0;
+			pStruct->GetChecksum( CRCD(0x0d7260fd,"random_set"), &this_random_set, Script::NO_ASSERT );
+
+			if ( this_random_set == random_set )
+			{
+				*ppReturnStructs = pStruct;
+				ppReturnStructs++;
+
+				(*pReturnNumStructs)++;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* GetRandomOptionStructure( uint32 partChecksum, uint32 random_set )
+{
+	Script::CArray* pArray;
+	pArray = Script::GetArray( partChecksum, Script::ASSERT );
+
+	const int vMAX_CAS_ITEMS = 32;
+	Script::CStruct* pAllowedStructs[vMAX_CAS_ITEMS];
+	bool alreadySelected[vMAX_CAS_ITEMS];
+	int allowRandomCount = 0;
+	
+	int part_array_size = pArray->GetSize();
+	for ( int i = 0; i < part_array_size; i++ )
+	{
+		Script::CStruct* pStruct = pArray->GetStructure( i );
+		int allow_random = !pStruct->ContainsFlag( CRCD(0xf6f8e158,"no_random") );
+		pStruct->GetInteger( CRCD(0xf1e3cd22,"allow_random"), &allow_random, Script::ASSERT );
+		
+		if ( random_set )
+		{
+			// if a random_set was specified, then
+			// make sure that this item works with it
+			// (used for matching skin tones)
+			uint32 this_random_set = 0;
+			if ( pStruct->GetChecksum( CRCD(0x0d7260fd,"random_set"), &this_random_set, Script::NO_ASSERT ) )
+			{
+				if ( this_random_set != random_set )
+				{
+					allow_random = false;
+				}
+			}
+		}
+		
+		if ( allow_random )
+		{
+			Dbg_Assert( allowRandomCount < vMAX_CAS_ITEMS );
+			pAllowedStructs[allowRandomCount] = pStruct;
+			
+			int already_selected;
+			pStruct->GetInteger( CRCD(0x92bddfd9,"already_selected"), &already_selected, true );
+			alreadySelected[allowRandomCount] = already_selected;
+			
+			allowRandomCount++;
+		}
+	}
+
+	// make sure there's at least one allow_random,
+	// or else we'll get into an infinite loop
+	Dbg_MsgAssert( allowRandomCount > 0, ( "No allow_random was found in this array %s (Tell Gary!)", Script::FindChecksumName(partChecksum) ) );
+
+	// count how many of them are already unselected
+	int unselected_count = 0;
+	for ( int i = 0; i < allowRandomCount; i++ )
+	{
+		if ( !alreadySelected[i] )
+		{
+			unselected_count++;
+		}
+	}
+
+	// the following makes sure that all the "allow_random" parts
+	// are used at least once, before duplicates begin.
+	if ( unselected_count == 0 )
+	{
+		for ( int i = 0; i < allowRandomCount; i++ )
+		{
+			alreadySelected[i] = false;
+		}
+	}
+
+	// now get rid of all the already selected items
+	int currentCount = 0;
+	for ( int i = 0; i < allowRandomCount; i++ )
+	{
+		if ( !alreadySelected[i] )
+		{
+			pAllowedStructs[currentCount] = pAllowedStructs[i];
+			alreadySelected[currentCount] =	alreadySelected[i];
+			currentCount++;
+		}
+	}
+	allowRandomCount = currentCount;
+	
+	Dbg_MsgAssert( allowRandomCount >= 1, ( "No part to pick" ) );
+	int index = Mth::Rnd( allowRandomCount );
+	Dbg_Assert( index >= 0 && index < allowRandomCount );
+	Script::CStruct* pReturnStruct = pAllowedStructs[index];
+
+	Dbg_Assert( pReturnStruct );
+	pReturnStruct->AddInteger( CRCD(0x92bddfd9,"already_selected"), 1 );
+
+//	uint32 checksum;
+//	pStruct->GetChecksum( "desc_id", &checksum, true );
+//	Dbg_Message( "Choosing random part %s", Script::FindChecksumName( checksum ) );
+
+	return pReturnStruct;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* GetRandomOptionStructureByIndex( uint32 partChecksum, int desired_index, uint32 random_set )
+{
+	Script::CArray* pArray;
+	pArray = Script::GetArray( partChecksum, Script::ASSERT );
+
+	int part_array_size = pArray->GetSize();
+
+	if ( !random_set )
+	{
+		for ( int i = 0; i < part_array_size; i++ )
+		{
+			Script::CStruct* pStruct = pArray->GetStructure( i );
+
+			int random_index = -1;
+			if ( pStruct->GetInteger( CRCD(0x4b833e64,"random_index"), &random_index, Script::ASSERT ) )
+			{
+				if ( random_index == desired_index )
+				{
+					return pStruct;
+				}
+			}
+		}
+	}
+	else
+	{
+		// GJ FIX FOR SK5:TT12579:  "NJ - Goal peds being created with mismached skin colors"
+		// if a random_set (skintone) was specified, then find the n-th item that actually works with this
+		// random set
+		while ( 1 )
+		{
+			#ifdef	__NOPT_ASSERT__
+			int current_random_index = desired_index;
+			#endif
+
+			for ( int i = 0; i < part_array_size; i++ )
+			{
+				Script::CStruct* pStruct = pArray->GetStructure( i );
+
+				int allow_random = !pStruct->ContainsFlag( CRCD(0xf6f8e158,"no_random") );
+				pStruct->GetInteger( CRCD(0xf1e3cd22,"allow_random"), &allow_random, Script::ASSERT );
+
+				uint32 this_random_set = 0;
+				pStruct->GetChecksum( CRCD(0x0d7260fd,"random_set"), &this_random_set, Script::NO_ASSERT );
+				bool is_universal_item = ( this_random_set == 0 );
+
+				if ( allow_random && ( ( this_random_set == random_set ) || is_universal_item ) )
+				{
+					if ( desired_index <= 0 )
+					{
+						return pStruct;
+					}
+
+					desired_index--;
+				}
+			}
+
+			// once we get to the end of the array, we need to start over
+			// (basically, we're doing a modulo of the desired_index)...
+			// the following checks for an infinite loop in which there
+			// were no items found that works with this random set
+			Dbg_MsgAssert( current_random_index != desired_index, ( "Couldn't find 'random_index' %d for ped part %s (are there any that fit this skintone %s?)", desired_index, Script::FindChecksumName(partChecksum), Script::FindChecksumName(random_set) ) );
+		}
+	}
+
+	Dbg_MsgAssert( 0, ( "Couldn't find 'random_index' %d for ped part %s", desired_index, Script::FindChecksumName(partChecksum) ) );
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // end namespace
+
+
diff --git a/Code/Gfx/CasUtils.h b/Code/Gfx/CasUtils.h
new file mode 100644
index 0000000..66789c1
--- /dev/null
+++ b/Code/Gfx/CasUtils.h
@@ -0,0 +1,45 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       CasUtils.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  ?/??/????
+//****************************************************************************
+
+#ifndef __GFX_CASUTILS_H
+#define __GFX_CASUTILS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+	class CStruct;
+}
+
+namespace Cas
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+// for searching/traversing an actual optionset
+bool				IsValidOption( uint32 partChecksum, uint32 descId );
+Script::CStruct*	GetOptionStructure( uint32 partChecksum, uint32 descId, bool assertOnFail = true );
+Script::CStruct*	GetFirstOptionStructure( uint32 partChecksum );
+Script::CStruct*	GetNullOptionStructure( uint32 partChecksum );
+void				BuildRandomSetList( uint32 partChecksum, uint32 random_set, Script::CStruct** ppReturnStructs, int* pReturnCount );
+Script::CStruct*	GetRandomOptionStructure( uint32 partChecksum, uint32 random_set = 0 );
+Script::CStruct*	GetRandomOptionStructureByIndex( uint32 partChecksum, int index, uint32 random_set = 0 );
+
+} // end namespace
+
+#endif
diff --git a/Code/Gfx/CustomAnimKey.cpp b/Code/Gfx/CustomAnimKey.cpp
new file mode 100644
index 0000000..dafb686
--- /dev/null
+++ b/Code/Gfx/CustomAnimKey.cpp
@@ -0,0 +1,692 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       CustomAnimKey.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  02/11/2002
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+								   
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// all custom keys must have 32-bit data, for the animconv's
+// byte-swapping algorithm to work properly
+
+struct SIntermediateCustomAnimKeyHeader
+{
+	uint32	timeStamp;
+	uint32	keyType;
+	uint32	size;
+};
+
+typedef enum
+{
+	vUNUSED1 = 0,
+	vCHANGE_FOCAL_LENGTH,
+	vCHANGE_CAMERA_RT,
+	vCHANGE_CAMERA_RT_IGNORE,
+	vRUN_SCRIPT,
+	vCREATE_OBJECT_FROM_STRUCT,
+	vKILL_OBJECT_FROM_STRUCT,
+	vCHANGE_CAMERA_RT_ENDKEY,
+};
+
+// class CChangeFOVKey
+class CChangeFOVKey : public CCustomAnimKey
+{
+public:
+	CChangeFOVKey( int frame, float fov );
+
+public:
+	virtual	bool	process_key( Obj::CObject* pObject );
+
+protected:
+	float	m_fov;
+};
+
+// class CRunScriptKey
+class CRunScriptKey : public CCustomAnimKey
+{
+public:
+	CRunScriptKey( int frame, uint32 scriptName );
+
+public:
+	virtual	bool	process_key( Obj::CObject* pObject );
+
+protected:
+	uint32	m_scriptName;
+};
+
+// class CChangeCameraRTKey
+class CChangeCameraRTKey : public CCustomAnimKey
+{
+public:
+	CChangeCameraRTKey( int frame, const Mth::Quat& theQuat, const Mth::Vector& theVector, bool endKey );
+
+public:
+	virtual	bool	process_key( Obj::CObject* pObject );
+	virtual bool	WithinRange( float startFrame, float endFrame, bool inclusive );
+
+protected:
+	Mth::Quat	m_rot;
+	Mth::Vector	m_trans;
+	bool		m_endKey;
+};
+
+// class CEmptyKey
+class CEmptyKey : public CCustomAnimKey
+{
+public:
+	CEmptyKey( int frame );
+
+public:
+	virtual	bool	process_key( Obj::CObject* pObject );
+};
+
+// class CCreateObjectFromStructKey
+class CCreateObjectFromStructKey : public CCustomAnimKey
+{
+public:
+	CCreateObjectFromStructKey( int frame, uint32 objectStructName );
+
+public:
+	virtual	bool	process_key( Obj::CObject* pObject );
+
+protected:
+	uint32	m_objectStructName;
+};
+
+// class CKillObjectFromStructKey
+class CKillObjectFromStructKey : public CCustomAnimKey
+{
+public:
+	CKillObjectFromStructKey( int frame, uint32 objectStructName );
+
+public:
+	virtual	bool	process_key( Obj::CObject* pObject );
+
+protected:
+	uint32	m_objectStructName;
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCustomAnimKey::CCustomAnimKey( int frame ) : Lst::Node( this ), m_frame( frame )
+{
+	// defaults to inactive
+	m_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCustomAnimKey::WithinRange( float startFrame, float endFrame, bool inclusive )
+{
+	if ( inclusive )
+	{
+		if ( startFrame < endFrame )
+		{
+			// forwards
+			return ( m_frame >= startFrame && m_frame <= endFrame );
+		}
+		else
+		{
+			// backwards
+			return ( m_frame <= startFrame && m_frame >= endFrame );
+		}
+	}
+	else
+	{
+		if ( startFrame < endFrame )
+		{
+			// forwards
+			return ( m_frame >= startFrame && m_frame < endFrame );
+		}
+		else
+		{
+			// backwards
+			return ( m_frame <= startFrame && m_frame > endFrame );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCustomAnimKey::SetActive( bool active )
+{
+	m_active = active;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCustomAnimKey::ProcessKey( Obj::CObject* pObject )
+{
+	if ( !m_active )
+	{
+		Dbg_Message( "Key at %d was not active...  ignored!", m_frame );
+
+		return false;
+	}
+
+	// once it's run, don't run it again...
+	// (this is in case you pause a movie camera
+	// on the same frame that it's running a key)
+	this->SetActive( false );
+
+	return process_key( pObject );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CChangeFOVKey::CChangeFOVKey( int frame, float fov ) : CCustomAnimKey( frame ), m_fov( fov )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CChangeFOVKey::process_key( Obj::CObject* pObject )
+{
+	if ( Script::GetInt( CRCD(0x2f510f45,"moviecam_debug"), false ) )
+	{
+		Dbg_Message( "Processing Change FOV key (fov = %f, %f) (%d)!", m_fov, Mth::RadToDeg(m_fov), m_frame );
+	}
+
+	Script::CScriptStructure* pParams = new Script::CScriptStructure;
+	pParams->AddFloat( NONAME, m_fov );
+	Script::RunScript( "ChangeCameraFov", pParams, pObject );
+	delete pParams;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/				
+
+CRunScriptKey::CRunScriptKey( int frame, uint32 scriptName ) : CCustomAnimKey( frame ), m_scriptName( scriptName )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CRunScriptKey::process_key( Obj::CObject* pObject )
+{
+	if ( Script::GetInt( CRCD(0x2f510f45,"moviecam_debug"), false ) )
+	{
+		Dbg_Message( "Processing Run Script key (script = %s) @ %d!", Script::FindChecksumName( m_scriptName ), m_frame );
+	}
+	Script::RunScript( m_scriptName, NULL, pObject );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CChangeCameraRTKey::CChangeCameraRTKey( int frame, const Mth::Quat& theQuat, const Mth::Vector& theVector, bool isEndKey ) : CCustomAnimKey( frame )
+{
+	// rotate by 90 degrees,
+	// eventually, we should do this in the build tools
+	
+	Mth::Quat rotQuat( Mth::Vector(1,0,0), Mth::DegToRad(90.0f) );
+	m_rot = theQuat * rotQuat;
+
+	m_trans[X] = theVector[X];
+	m_trans[Y] = theVector[Z];
+	m_trans[Z] = -theVector[Y];
+	m_trans[W] = 1.0f;
+
+	m_endKey = isEndKey;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CChangeCameraRTKey::process_key( Obj::CObject* pObject )
+{
+	// GJ:  This key is a kludge to fix the problem with camera
+	// double-cuts in the cutscene (where we're trying to
+	// interpolate from one camera to the next, rather
+	// than just jumping)...  the way it works is that
+	// 2 of these keys need to be inserted right before
+	// a camera change, which will override the interpolated
+	// camera values.  we need 2 because we're guaranteed
+	// that the minimum time increment is 1/60th of a second
+	// so we need to stick these keys in every 1/60th of
+	// a second between the two cameras.  note that this
+	// won't work properly if we do anim speed changes
+	// or setslomo, because then the minimum time increment
+	// is actually less than 1/60th of a second, then it's
+	// possible that there will be a camera update, without
+	// an accompanying CChangeCamera key found
+	// i'd eventually like to look at how WithinRange() works, 
+	// to see if there's a better way to do this.
+	
+	if ( m_frame == 0 )
+	{
+		// GJ KLUDGE:  there's an exporter bug that writes out extra camera RT
+		// keys at the beginning of the cutscene.  this skips
+		// those extra keys
+		Dbg_Message( "Skipping Change Camera RT key @ %d!", m_frame );
+		return false;
+	}
+
+	if ( Script::GetInt( CRCD(0x2f510f45,"moviecam_debug"), false ) )
+	{
+		Dbg_Message( "Processing Change Camera RT key @ %d!", m_frame );
+		Dbg_Message( "Quat: %f %f %f %f", m_rot[X], m_rot[Y], m_rot[Z], m_rot[W] );
+		Dbg_Message( "Trans: %f %f %f %f", m_trans[X], m_trans[Y], m_trans[Z], m_trans[W] );
+	}
+
+	Gfx::Camera* pCamera = (Gfx::Camera*)pObject;
+
+	Mth::Vector& camPos = pCamera->GetPos();
+	Mth::Matrix& camMatrix = pCamera->GetMatrix();
+	
+	// update the camera position
+	camPos = m_trans;
+
+	// update the camera orientation
+	Mth::Vector nullTrans;
+	nullTrans.Set(0.0f,0.0f,0.0f,1.0f);		// Mick: added separate initialization
+	
+	// QuatVecToMatrix destroys the input data, so need to pass in a temp variable
+	Mth::Quat theQuat = m_rot;
+	Mth::QuatVecToMatrix( &theQuat, &nullTrans, &camMatrix );
+
+	// need this because of our 1-frame range check
+	this->SetActive( true );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CChangeCameraRTKey::WithinRange( float startFrame, float endFrame, bool inclusive )
+{
+	// good for 1 frame...			  1/60th of a frame
+
+	if ( inclusive )
+	{
+		if ( startFrame < endFrame )
+		{
+			// this end key business is used to fix a cutscene glitch
+			// during camera cuts.  the problem is that the keys come
+			// in sets of 3:
+			// KEY[-2]    KEY[-1]    KEY[0]
+			// old camera old camera new camera
+			// we basically take the current frame (stored
+			// in endFrame) and figure out which of the
+			// three keys should handle it...  formerly,
+			// all three keys handled it, effectively
+			// causing the last key's camera data to be 
+			// used.  to fix the glitch, we need to only
+			// run the correct key...
+			if ( m_endKey )
+			{
+				if ( endFrame > m_frame )
+				{
+					// let it interpolate
+					return false;
+				}
+			}
+			else
+			{
+				if ( m_frame < ( endFrame - 1.0f ) )
+				{
+					// there's an end key coming, so let someone else handle it
+					return false;
+				}
+			}
+  			
+			// forwards
+			return ( m_frame >= ( startFrame - 1.0f ) && m_frame <= endFrame );
+		}
+		else
+		{
+			// backwards
+			return ( m_frame <= ( startFrame + 1.0f ) && m_frame >= endFrame );
+		}
+	}
+	else
+	{
+		if ( startFrame < endFrame )
+		{
+			if ( m_endKey )
+			{
+				if ( endFrame > m_frame )
+				{
+					// let it interpolate
+					return false;
+				}
+			}
+			else
+			{
+				if ( m_frame < ( endFrame - 1.0f ) )
+				{
+					// there's an end key coming, so let someone else handle it
+					return false;
+				}
+			}
+  			
+			// forwards
+			return ( m_frame >= ( startFrame - 1.0f ) && m_frame < endFrame );
+		}
+		else
+		{
+			// backwards
+			return ( m_frame <= ( startFrame + 1.0f ) && m_frame > endFrame );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CEmptyKey::CEmptyKey( int frame ) : CCustomAnimKey( frame )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CEmptyKey::process_key( Obj::CObject* pObject )
+{
+	// do nothing
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CEventKey::CEventKey( int frame, uint32 eventType, Script::CStruct* pEventParams ) : CCustomAnimKey( frame )
+{
+	m_eventType = eventType;
+	mp_eventParams = new Script::CStruct;
+	mp_eventParams->AppendStructure( pEventParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CEventKey::~CEventKey()
+{
+	if ( mp_eventParams )
+	{
+		delete mp_eventParams;
+		mp_eventParams = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CEventKey::process_key( Obj::CObject* pObject )
+{
+	pObject->SelfEvent( m_eventType, mp_eventParams );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/				
+
+CCreateObjectFromStructKey::CCreateObjectFromStructKey( int frame, uint32 objectStructName ) : CCustomAnimKey( frame ), m_objectStructName( objectStructName )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCreateObjectFromStructKey::process_key( Obj::CObject* pObject )
+{
+	if ( Script::GetInt( CRCD(0x2f510f45,"moviecam_debug"), false ) )
+	{
+		Dbg_Message( "Processing Create Object From Struct key (struct = %s) @ %d!", Script::FindChecksumName( m_objectStructName ), m_frame );
+	}
+
+	Script::CStruct* pObjectStruct = Script::GetStructure( m_objectStructName, Script::ASSERT );
+	if ( pObjectStruct )
+	{
+		Script::RunScript( CRCD(0xbdf7f843,"CreateFromStructure"), pObjectStruct );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/				
+
+CKillObjectFromStructKey::CKillObjectFromStructKey( int frame, uint32 objectStructName ) : CCustomAnimKey( frame ), m_objectStructName( objectStructName )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CKillObjectFromStructKey::process_key( Obj::CObject* pObject )
+{
+	if ( Script::GetInt( CRCD(0x2f510f45,"moviecam_debug"), false ) )
+	{
+		Dbg_Message( "Processing Kill Object From Struct key (script = %s) @ %d!", Script::FindChecksumName( m_objectStructName ), m_frame );
+	}
+	
+	Script::CStruct* pObjectStruct = Script::GetStructure( m_objectStructName, Script::ASSERT );
+	if ( pObjectStruct )
+	{
+		uint32 objectName;
+		pObjectStruct->GetChecksum( CRCD(0xa1dc81f9,"name"), &objectName, Script::ASSERT );
+		
+		Obj::CObject* pObject = (Obj::CObject*)Obj::ResolveToObject( objectName );
+		pObject->MarkAsDead();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCustomAnimKey* ReadCustomAnimKey( uint8** pData )
+{
+	// factory function for generating a custom key...
+
+	// TODO:  might need to push the heap context
+
+	SIntermediateCustomAnimKeyHeader theIntermediateHeader;
+	memcpy(&theIntermediateHeader, *pData, sizeof(SIntermediateCustomAnimKeyHeader));
+	*pData += sizeof(SIntermediateCustomAnimKeyHeader);
+
+	switch ( theIntermediateHeader.keyType )
+	{
+		case vCHANGE_FOCAL_LENGTH:
+			{
+				float fov;
+				memcpy(&fov, *pData, sizeof(float));				// May not be word-aligned in memory
+				*pData += sizeof(float);
+				return new CChangeFOVKey( theIntermediateHeader.timeStamp, fov );
+			}
+			break;
+		case vRUN_SCRIPT:
+			{
+				uint32 script_name;
+				memcpy(&script_name, *pData, sizeof(uint32));		// May not be word-aligned in memory
+				*pData += sizeof(uint32);
+				return new CRunScriptKey( theIntermediateHeader.timeStamp, script_name );
+			}
+			break;
+		case vCHANGE_CAMERA_RT:
+			{
+				Mth::Quat theQuat;
+				Mth::Vector theVector;
+				memcpy(&theVector, *pData, sizeof(Mth::Vector));
+				*pData += sizeof(Mth::Vector);
+				memcpy(&theQuat, *pData, sizeof(Mth::Quat));
+				*pData += sizeof(Mth::Quat);
+				return new CChangeCameraRTKey( theIntermediateHeader.timeStamp, theQuat, theVector, false );
+			}
+			break;
+		case vCHANGE_CAMERA_RT_ENDKEY:
+			{
+				Mth::Quat theQuat;
+				Mth::Vector theVector;
+				memcpy(&theVector, *pData, sizeof(Mth::Vector));
+				*pData += sizeof(Mth::Vector);
+				memcpy(&theQuat, *pData, sizeof(Mth::Quat));
+				*pData += sizeof(Mth::Quat);
+				return new CChangeCameraRTKey( theIntermediateHeader.timeStamp, theQuat, theVector, true );
+			}
+			break;
+		case vCHANGE_CAMERA_RT_IGNORE:
+			{
+           		// GJ:  There's a bug in the exporter where
+				// 2 camera RT keys with different positions
+				// are listed for the same time...  this causes
+				// a glitch during certain camera transitions.
+				// To fix, I will replace the second key with one
+				// that does nothing...
+				*pData += sizeof(Mth::Vector);
+				*pData += sizeof(Mth::Quat);
+
+				return new CEmptyKey( theIntermediateHeader.timeStamp );
+			}
+			break;
+		case vCREATE_OBJECT_FROM_STRUCT:
+			{
+				uint32 object_struct_name;
+				memcpy(&object_struct_name, *pData, sizeof(uint32));		// May not be word-aligned in memory
+				*pData += sizeof(uint32);
+				return new CCreateObjectFromStructKey( theIntermediateHeader.timeStamp, object_struct_name );
+			}
+			break;
+		case vKILL_OBJECT_FROM_STRUCT:
+			{
+				uint32 object_struct_name;
+				memcpy(&object_struct_name, *pData, sizeof(uint32));		// May not be word-aligned in memory
+				*pData += sizeof(uint32);
+				return new CKillObjectFromStructKey( theIntermediateHeader.timeStamp, object_struct_name );
+			}
+			break;
+		default:
+			{
+				Dbg_Message( "Warning:  Ignoring custom anim key (type %08x is currently unsupported).", theIntermediateHeader.keyType );
+
+				// just skip past the size
+				Dbg_Assert( theIntermediateHeader.size - sizeof(SIntermediateCustomAnimKeyHeader) < 512 );
+				*pData += theIntermediateHeader.size;
+				Dbg_Assert(!((uint) *pData & 0x3));
+
+				return NULL;
+			}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
diff --git a/Code/Gfx/CustomAnimKey.h b/Code/Gfx/CustomAnimKey.h
new file mode 100644
index 0000000..190c98c
--- /dev/null
+++ b/Code/Gfx/CustomAnimKey.h
@@ -0,0 +1,137 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       CustomAnimKey.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  02/08/2002
+//****************************************************************************
+
+#ifndef __GFX_CUSTOMANIMKEY_H
+#define __GFX_CUSTOMANIMKEY_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+	class CObject;
+}
+
+namespace Script
+{
+	class CStruct;
+}
+
+namespace Gfx
+{
+	enum RangeFlags
+	{
+		mSTART_INCLUSIVE = ( 1 << 0 ),
+		mSTART_EXCLUSIVE = ( 1 << 1 ),
+		mEND_INCLUSIVE = ( 1 << 2 ),
+		mEND_EXCLUSIVE = ( 1 << 3 ),
+	};
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+// interface to all the custom keys
+// (the custom keys are defined in the CPP file)
+// there should be no allocation going on in these keys
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// class CCustomAnimKey
+class CCustomAnimKey : public Lst::Node
+{
+public:
+	CCustomAnimKey( int time );
+	virtual bool	WithinRange( float startFrame, float endFrame, bool inclusive = false );
+	void			SetActive( bool active );
+
+public:
+	bool			ProcessKey( Obj::CObject* pObject );
+
+protected:
+	virtual bool	process_key( Obj::CObject* pObject ) = 0;
+
+protected:
+	int				m_frame;
+	bool			m_active;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// class CEventKey
+class CEventKey : public CCustomAnimKey
+{
+public:
+	CEventKey( int time, uint32 eventType, Script::CStruct* pParams );
+	virtual ~CEventKey();
+
+public:
+	virtual	bool		process_key( Obj::CObject* pObject );
+
+protected:
+	uint32				m_eventType;
+	Script::CStruct*	mp_eventParams;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+CCustomAnimKey* ReadCustomAnimKey( uint8** pData );
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // namespace Gfx
+
+#endif	// __GFX_CUSTOMANIMKEY_H
+
+
+
diff --git a/Code/Gfx/FaceMassage.cpp b/Code/Gfx/FaceMassage.cpp
new file mode 100644
index 0000000..047f70d
--- /dev/null
+++ b/Code/Gfx/FaceMassage.cpp
@@ -0,0 +1,644 @@
+///////////////////////////////////////////////////////////////////////////////
+// FaceMassage.cpp
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace	Nx
+{
+
+#ifdef __NOPT_ASSERT__
+void			SFacePoints::PrintData()
+{
+	Dbg_Message("FacePoints left_eye (%d, %d)", m_left_eye[X], m_left_eye[Y]);
+	Dbg_Message("FacePoints right_eye (%d, %d)", m_right_eye[X], m_right_eye[Y]);
+	Dbg_Message("FacePoints nose (%d, %d)", m_nose[X], m_nose[Y]);
+	Dbg_Message("FacePoints lips (%d, %d)", m_lips[X], m_lips[Y]);
+	Dbg_Message("FacePoints adjust HSV (%s)", m_adjust_hsv ? "true" : "false");
+	if (m_adjust_hsv)
+	{
+		Dbg_Message("FacePoints H offset (%f)", m_h_offset);
+		Dbg_Message("FacePoints S scale (%f)", m_s_scale);
+		Dbg_Message("FacePoints V scale (%f)", m_v_scale);
+	}
+	Dbg_Message("FacePoints texture size (%d, %d)", m_texture_width, m_texture_height);
+}
+#endif // __NOPT_ASSERT__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SFacePoints s_default_face_points = {
+	{  49,  54 },		// Left eye
+	{  76,  54 },		// Right eye
+	{  64,  75 },		// Nose
+	{  64,  89 },		// Lips
+	false,				// Adjust HSV
+	0.0f,				// H offset
+	1.0f,				// S scale
+	1.0f,				// V scale
+	128,				// Texture width
+	128					// Texture height
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Utility functions
+void SetDefaultFacePoints(SFacePoints* pFacePoints)
+{
+	Dbg_MsgAssert( pFacePoints, ( "No face points" ) );
+
+	*pFacePoints = s_default_face_points;
+}  
+
+///////////////////////////////////////////////////////////////////////////////
+// CFaceTexMassager
+
+// copy over default face points
+SFacePoints		CFaceTexMassager::s_model_face_points = s_default_face_points;
+
+CTexture *		CFaceTexMassager::sp_face_texture_overlay = NULL;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CFaceTexMassager::sSetModelFacePoints(const SFacePoints &f_points)
+{
+	s_model_face_points = f_points;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CFaceTexMassager::sSetFaceTextureOverlay(CTexture *p_texture)
+{
+	if (p_texture)
+	{
+		Dbg_MsgAssert(p_texture->GetWidth() == s_model_face_points.m_texture_width, ("Face texture overlay width is not equal to the face points width"));
+		Dbg_MsgAssert(p_texture->GetHeight() == s_model_face_points.m_texture_height, ("Face texture overlay height is not equal to the face points height"));
+	}
+
+	sp_face_texture_overlay = p_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#define PRINT_TIMES 0
+
+bool			CFaceTexMassager::sMassageTexture(CTexture *p_face_texture, const SFacePoints &c_texture_points, bool palette_gen,
+												  bool use_fill_color, Image::RGBA fill_color)
+{
+	Dbg_Assert(p_face_texture);
+
+#if PRINT_TIMES
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	// Convert texture to 32-bit, if not already (speeds conversion)
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	p_face_texture->Generate32BitImage();
+	Mem::Manager::sHandle().PopContext();
+
+	// Adjust the texture based on the model face points
+	sAdjustTextureToModel(p_face_texture, c_texture_points, use_fill_color, fill_color);
+
+	// Adjust the texture colors
+	sAdjustTextureColors(p_face_texture, c_texture_points);
+
+	// And put overlay on top, if any
+	if (sp_face_texture_overlay)
+	{
+		p_face_texture->CombineTextures(sp_face_texture_overlay, palette_gen);
+	}
+
+	// Put 32-bit image into texture
+	p_face_texture->Put32BitImageIntoTexture(palette_gen);
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	Dbg_Message("sMassageTexture Update time %d us", end_time - start_time);
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CFaceTexMassager::sAdjustTextureToModel(CTexture *p_face_texture, const SFacePoints &c_texture_points,
+														bool use_fill_color, Image::RGBA fill_color)
+{
+	Dbg_Assert(p_face_texture);
+	Dbg_MsgAssert(p_face_texture->GetWidth() == s_model_face_points.m_texture_width, ("Face texture width is not equal to the face points width"));
+	Dbg_MsgAssert(p_face_texture->GetHeight() == s_model_face_points.m_texture_height, ("Face texture height is not equal to the face points height"));
+
+	// Make a local copy of the texture points, as we might need to modify it to make it safe											 
+	SFacePoints texture_points = c_texture_points;
+											 
+// Make the values "safe" (should also do this at the user interface stage)
+
+
+// if eyes are flipped, then flip them back
+	int t;
+	if (texture_points.m_left_eye[X] > texture_points.m_right_eye[X])
+	{
+		t = texture_points.m_left_eye[X];
+		texture_points.m_left_eye[X] = texture_points.m_right_eye[X];
+		texture_points.m_right_eye[X] = t;
+	}
+	
+	
+// If Left eye is below the nose then put it above it.	
+	if (texture_points.m_left_eye[Y] > texture_points.m_nose[Y])
+	{
+		texture_points.m_left_eye[Y] = texture_points.m_nose[Y]-1;
+	}
+
+
+// If Right eye is below the nose then put it above it.	
+	if (texture_points.m_right_eye[Y] >= texture_points.m_nose[Y])
+	{
+		texture_points.m_right_eye[Y] = texture_points.m_nose[Y]-2;
+	}
+
+// if Lips is above the nose, then put it below
+	if (texture_points.m_lips[Y] <= texture_points.m_nose[Y])
+	{
+		texture_points.m_lips[Y] = texture_points.m_nose[Y]+2;
+	}
+
+	// All stretching and shrinking will be done at the nose point
+	int x_axis_point = s_model_face_points.m_nose[X];
+	int y_axis_point = s_model_face_points.m_nose[Y];
+
+	// First, offset the texture by the difference in nose points
+	int x_offset = s_model_face_points.m_nose[X] - texture_points.m_nose[X];
+	int y_offset = s_model_face_points.m_nose[Y] - texture_points.m_nose[Y];
+	p_face_texture->Offset(x_offset, y_offset);
+
+	int model_eyeline_y = (s_model_face_points.m_left_eye[Y] + s_model_face_points.m_right_eye[Y]) >> 1;
+	int texture_eyeline_y = ((texture_points.m_left_eye[Y] + texture_points.m_right_eye[Y]) >> 1) + y_offset;
+
+	// Calculate the ratios to find how much we need to pull/push a texture section
+	float left_eye_to_nose_width = texture_points.m_nose[X] - texture_points.m_left_eye[X];
+	float left_nose_width = texture_points.m_nose[X] + x_offset;
+	float left_eye_ratio = left_nose_width / left_eye_to_nose_width;
+
+	float right_eye_to_nose_width = texture_points.m_right_eye[X] - texture_points.m_nose[X];
+	float right_nose_width = p_face_texture->GetWidth() - (texture_points.m_nose[X] + x_offset);
+	float right_eye_ratio = right_nose_width / right_eye_to_nose_width;
+
+	float eyeline_to_nose_height = (texture_points.m_nose[Y] + y_offset) - texture_eyeline_y;
+	float top_nose_height = texture_points.m_nose[Y] + y_offset;
+	float eyeline_ratio = top_nose_height / eyeline_to_nose_height;
+
+	float lips_to_nose_height = texture_points.m_lips[Y] - texture_points.m_nose[Y];
+	float bottom_nose_height = p_face_texture->GetHeight() - (texture_points.m_nose[Y] + y_offset);
+	float lips_ratio = bottom_nose_height / lips_to_nose_height;
+
+	// Move the left eye along the X axis
+	int left_eye_pixels = s_model_face_points.m_left_eye[X] - (texture_points.m_left_eye[X] + x_offset);
+	//Dbg_Message("Left eye pixels before %d", left_eye_pixels);
+	//Dbg_Message("Left eye to_nose %f width %f", left_eye_to_nose_width, left_nose_width);
+	left_eye_pixels = (int) ((left_eye_pixels * left_eye_ratio) + 0.5f);
+	//Dbg_Message("Left eye pixels after %d", left_eye_pixels);
+	if (left_eye_pixels < 0)
+	{
+		// Stretch out
+		p_face_texture->PullToEdge(x_axis_point, X, left_eye_pixels);
+	}
+	else
+	{
+		// Shrink
+		p_face_texture->PushToPoint(x_axis_point, X, left_eye_pixels, use_fill_color, fill_color);
+	}
+
+	// Move the right eye along the X axis
+	int right_eye_pixels = s_model_face_points.m_right_eye[X] - (texture_points.m_right_eye[X] + x_offset);
+	//Dbg_Message("Right eye pixels before %d", right_eye_pixels);
+	//Dbg_Message("Right eye to_nose %f width %f", right_eye_to_nose_width, right_nose_width);
+	right_eye_pixels = (int) ((right_eye_pixels * right_eye_ratio) + 0.5f);
+	//Dbg_Message("Right eye pixels after %d", right_eye_pixels);
+	if (right_eye_pixels >= 0)
+	{
+		// Stretch out
+		p_face_texture->PullToEdge(x_axis_point, X, right_eye_pixels);
+	}
+	else
+	{
+		// Shrink
+		p_face_texture->PushToPoint(x_axis_point, X, right_eye_pixels, use_fill_color, fill_color);
+	}
+
+	// Move the eyeline along the Y axis
+	int eyeline_pixels = model_eyeline_y - texture_eyeline_y;
+	eyeline_pixels = (int) ((eyeline_pixels * eyeline_ratio) + 0.5f);
+	if (eyeline_pixels < 0)
+	{
+		// Stretch out
+		p_face_texture->PullToEdge(y_axis_point, Y, eyeline_pixels);
+	}
+	else
+	{
+		// Shrink
+		p_face_texture->PushToPoint(y_axis_point, Y, eyeline_pixels, use_fill_color, fill_color);
+	}
+
+	// Move the lips along the Y axis
+	int lips_pixels = s_model_face_points.m_lips[Y] - (texture_points.m_lips[Y] + y_offset);
+	lips_pixels = (int) ((lips_pixels * lips_ratio) + 0.5f);
+	if (lips_pixels >= 0)
+	{
+		// Stretch out
+		p_face_texture->PullToEdge(y_axis_point, Y, lips_pixels);
+	}
+	else
+	{
+		// Shrink
+		p_face_texture->PushToPoint(y_axis_point, Y, lips_pixels, use_fill_color, fill_color);
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CFaceTexMassager::sAdjustTextureColors(CTexture *p_face_texture, const SFacePoints &texture_points)
+{
+	Dbg_Assert(p_face_texture);
+
+	if (texture_points.m_adjust_hsv)
+	{
+		p_face_texture->AdjustHSV(texture_points.m_h_offset, texture_points.m_s_scale, texture_points.m_v_scale, false);
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CFaceTexMassager::sCombineTextureWithOverlay(CTexture *p_face_texture)
+{
+	Dbg_Assert(p_face_texture);
+
+	if (sp_face_texture_overlay)
+	{
+		p_face_texture->CombineTextures(sp_face_texture_overlay, false);
+		return true;
+	}
+
+	return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// Script functions
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool SetFacePointsStruct(const SFacePoints& face_points, Script::CStruct *p_struct)
+{
+	Dbg_Assert(p_struct);
+
+	Script::CArray* p_left_eye = new Script::CArray;
+	p_left_eye->SetSizeAndType( 2, ESYMBOLTYPE_INTEGER );
+	p_left_eye->SetInteger( 0, face_points.m_left_eye[X] );
+	p_left_eye->SetInteger( 1, face_points.m_left_eye[Y] );
+	
+	Script::CArray* p_right_eye = new Script::CArray;
+	p_right_eye->SetSizeAndType( 2, ESYMBOLTYPE_INTEGER );
+	p_right_eye->SetInteger( 0, face_points.m_right_eye[X] );
+	p_right_eye->SetInteger( 1, face_points.m_right_eye[Y] );
+	
+	Script::CArray* p_nose = new Script::CArray;
+	p_nose->SetSizeAndType( 2, ESYMBOLTYPE_INTEGER );
+	p_nose->SetInteger( 0, face_points.m_nose[X] );
+	p_nose->SetInteger( 1, face_points.m_nose[Y] );
+	
+	Script::CArray* p_lips = new Script::CArray;
+	p_lips->SetSizeAndType( 2, ESYMBOLTYPE_INTEGER );
+	p_lips->SetInteger( 0, face_points.m_lips[X] );
+	p_lips->SetInteger( 1, face_points.m_lips[Y] );
+
+	p_struct->AddArrayPointer( CRCD(0x08bf1d3f,"left_eye"), p_left_eye );
+	p_struct->AddArrayPointer( CRCD(0xb0b44396,"right_eye"), p_right_eye );
+	p_struct->AddArrayPointer( CRCD(0x7f03932c,"nose"), p_nose );
+	p_struct->AddArrayPointer( CRCD(0x0e7ec187,"lips"), p_lips );
+
+	if (face_points.m_adjust_hsv)
+	{
+		p_struct->AddFloat(CRCD(0x6e94f918,"h"), face_points.m_h_offset);
+		p_struct->AddFloat(CRCD(0xe4f130f4,"s"), face_points.m_s_scale);
+		p_struct->AddFloat(CRCD(0x949bc47b,"v"), face_points.m_v_scale);
+	}
+
+	p_struct->AddInteger( CRCD(0x73e5bad0,"width"), face_points.m_texture_width );
+	p_struct->AddInteger( CRCD(0x0ab21af0,"height"), face_points.m_texture_height );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool GetFacePointsStruct(SFacePoints &face_points, Script::CStruct *p_struct)
+{
+	Dbg_Assert(p_struct);
+
+	Script::CArray *p_left_eye = NULL;
+	Script::CArray *p_right_eye = NULL;
+	Script::CArray *p_nose = NULL;
+	Script::CArray *p_lips = NULL;
+
+	// Left eye
+	p_struct->GetArray( CRCD(0x08bf1d3f,"left_eye"), &p_left_eye );
+	if (p_left_eye)
+	{
+		face_points.m_left_eye[X] = p_left_eye->GetInteger(0);
+		face_points.m_left_eye[Y] = p_left_eye->GetInteger(1);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Couldn't find member left_eye"))
+		return false;	
+	}
+
+	// Right eye
+	p_struct->GetArray( CRCD(0xb0b44396,"right_eye"), &p_right_eye );
+	if (p_right_eye)
+	{
+		face_points.m_right_eye[X] = p_right_eye->GetInteger(0);
+		face_points.m_right_eye[Y] = p_right_eye->GetInteger(1);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Couldn't find member right_eye"))
+		return false;	
+	}
+
+	// Nose
+	p_struct->GetArray( CRCD(0x7f03932c,"nose"), &p_nose );
+	if (p_nose)
+	{
+		face_points.m_nose[X] = p_nose->GetInteger(0);
+		face_points.m_nose[Y] = p_nose->GetInteger(1);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Couldn't find member nose"))
+		return false;	
+	}
+
+	// Lips
+	p_struct->GetArray( CRCD(0x0e7ec187,"lips"), &p_lips );
+	if (p_lips)
+	{
+		face_points.m_lips[X] = p_lips->GetInteger(0);
+		face_points.m_lips[Y] = p_lips->GetInteger(1);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Couldn't find member lips"))
+		return false;	
+	}
+
+	// Grab color adjustment values, if any
+	face_points.m_adjust_hsv = p_struct->GetFloat(CRCD(0x6e94f918,"h"), &face_points.m_h_offset);
+	face_points.m_adjust_hsv = p_struct->GetFloat(CRCD(0xe4f130f4,"s"), &face_points.m_s_scale) && face_points.m_adjust_hsv;
+	face_points.m_adjust_hsv = p_struct->GetFloat(CRCD(0x949bc47b,"v"), &face_points.m_v_scale) && face_points.m_adjust_hsv;
+
+	if (face_points.m_adjust_hsv)
+	{
+		Dbg_MsgAssert((face_points.m_h_offset >= 0.0f) && (face_points.m_h_offset <= 360.0f), ("h must be in the range of 0-360"));
+		Dbg_MsgAssert(face_points.m_s_scale >= 0.0f, ("s cannot be negative"));
+		Dbg_MsgAssert(face_points.m_v_scale >= 0.0f, ("v cannot be negative"));
+	}
+
+	// Width and height (some functions may not care, so init them to 0)
+	face_points.m_texture_width = 0;
+	face_points.m_texture_height = 0;
+	p_struct->GetInteger( CRCD(0x73e5bad0,"width"), &face_points.m_texture_width );
+	p_struct->GetInteger( CRCD(0x0ab21af0,"height"), &face_points.m_texture_height );
+
+	return true;
+}
+
+// @script | SetModelFaceTexturePoints | Sets the face points for the face texture of the model
+// @parm struct | face_points | face points structure
+bool ScriptSetModelFaceTexturePoints(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	SFacePoints face_points;
+
+	Script::CStruct *pFacePointsStruct = NULL;
+	pParams->GetStructure("face_points", &pFacePointsStruct);
+
+	if (pFacePointsStruct)
+	{
+		GetFacePointsStruct(face_points, pFacePointsStruct);
+
+		Dbg_MsgAssert(face_points.m_texture_width != 0, ("Must set texture width"));
+		Dbg_MsgAssert(face_points.m_texture_height != 0, ("Must set texture height"));
+
+		CFaceTexMassager::sSetModelFacePoints(face_points);
+		//face_points.PrintData();
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("The face_points structure needs to be supplied"));
+	}
+
+	return true;
+}
+
+// @script | SetFaceMassageTextureOverlay | Sets the texture that is used as an overlay to the massaged face texture 
+// @parm name |  | name of texture
+bool ScriptSetFaceMassageTextureOverlay(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		CFaceTexMassager::sSetFaceTextureOverlay(p_texture);
+	} else {
+		Dbg_MsgAssert(0, ("Can't find texture %s to adjust", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | MassageFaceTexture | Transform the supplied face texture to one that can be used with a model
+// @parm name | texture | name of texture
+// @parm struct | face_points | face points structure
+// @parmopt flag | no_palette_gen | | use original palette (faster)
+bool ScriptMassageFaceTexture(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(CRCD(0x7d99f28d,"texture"), &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		bool palette_gen = true;
+		SFacePoints face_points;
+
+		Script::CStruct *pFacePointsStruct = NULL;
+		pParams->GetStructure(CRCD(0xac3cd84c,"face_points"), &pFacePointsStruct);
+
+		if (pParams->ContainsFlag(CRCD(0x5905256b,"no_palette_gen")))
+		{
+			palette_gen = false;
+		}
+
+		if (pFacePointsStruct)
+		{
+			GetFacePointsStruct(face_points, pFacePointsStruct);
+
+			CFaceTexMassager::sMassageTexture(p_texture, face_points, palette_gen);
+			//CFaceTexMassager::sMassageTexture(p_texture, face_points, palette_gen, true, Image::RGBA(255, 0, 0, 128));
+		}
+		else
+		{
+			Dbg_MsgAssert(0, ("The face_points structure needs to be supplied"));
+		}
+
+	} else {
+		Dbg_MsgAssert(0, ("Can't find texture %s to massage", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | AdjustFaceTextureToModel | Pulls portions of the face texture from the supplied points to the model points
+// @parm name | texture | name of texture
+// @parm struct | face_points | face points structure
+bool ScriptAdjustFaceTextureToModel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(CRCD(0x7d99f28d,"texture"), &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		SFacePoints face_points;
+
+		Script::CStruct *pFacePointsStruct = NULL;
+		pParams->GetStructure(CRCD(0xac3cd84c,"face_points"), &pFacePointsStruct);
+
+		if (pFacePointsStruct)
+		{
+			GetFacePointsStruct(face_points, pFacePointsStruct);
+
+			CFaceTexMassager::sAdjustTextureToModel(p_texture, face_points);
+		}
+		else
+		{
+			Dbg_MsgAssert(0, ("The face_points structure needs to be supplied"));
+		}
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to adjust", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | AdjustFaceTextureColors | Does all the requested color adjustments to the face texture.
+// @parm name | texture | name of texture
+// @parm struct | face_points | face points structure
+bool ScriptAdjustFaceTextureColors(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(CRCD(0x7d99f28d,"texture"), &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		SFacePoints face_points;
+
+		Script::CStruct *pFacePointsStruct = NULL;
+		pParams->GetStructure(CRCD(0xac3cd84c,"face_points"), &pFacePointsStruct);
+
+		if (pFacePointsStruct)
+		{
+			GetFacePointsStruct(face_points, pFacePointsStruct);
+
+			CFaceTexMassager::sAdjustTextureColors(p_texture, face_points);
+		}
+		else
+		{
+			Dbg_MsgAssert(0, ("The face_points structure needs to be supplied"));
+		}
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to adjust", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | CombineFaceTextureWithOverlay | Puts overlay texture on top of face texture and combines them.
+// @parm name | texture | name of texture
+bool ScriptCombineFaceTextureWithOverlay(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(CRCD(0x7d99f28d,"texture"), &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		CFaceTexMassager::sCombineTextureWithOverlay(p_texture);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to combine", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+} // namespace Nx
+
+
diff --git a/Code/Gfx/FaceMassage.h b/Code/Gfx/FaceMassage.h
new file mode 100644
index 0000000..c38f409
--- /dev/null
+++ b/Code/Gfx/FaceMassage.h
@@ -0,0 +1,103 @@
+///////////////////////////////////////////////////////////////////////////////
+// FaceMassage.h
+
+#ifndef	__GFX_FACE_MASSAGE_H__
+#define	__GFX_FACE_MASSAGE_H__
+
+#include 
+#include 
+
+#include 
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+
+namespace Nx
+{
+
+// Forward declarations
+class CTexture;
+
+////////////////////////////////////////////////////////////////////////////
+// Holds all the face coordinates
+struct SFacePoints
+{
+	// Constants
+	enum
+	{
+		NUM_AXES	= 2,			// Just two axes for 2D coordinates
+	};
+
+	// Eye points
+	int				m_left_eye[NUM_AXES];
+	int				m_right_eye[NUM_AXES];
+
+	// Nose point (between the nostrils)
+	int				m_nose[NUM_AXES];
+
+	// Lips point (center)
+	int				m_lips[NUM_AXES];
+
+	// Color HSV adjustments
+	bool			m_adjust_hsv;
+	float			m_h_offset;				// Added to each pixel H value (0 - 360)
+	float			m_s_scale;				// Scale applied to each pixel S value (1=no change)
+	float			m_v_scale;				// Scale applied to each pixel V value (1=no change)
+
+	// Texture size
+	int				m_texture_width;
+	int				m_texture_height;
+
+#ifdef __NOPT_ASSERT__
+	void			PrintData();
+#endif // __NOPT_ASSERT__
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Transforms a face texture into one that can be used by a model.
+class CFaceTexMassager
+{
+public:
+	// Set data
+	static void				sSetModelFacePoints(const SFacePoints &f_points);
+	static void				sSetFaceTextureOverlay(CTexture *p_texture);
+
+	// Massage the face texture into a usable face texture for a model (overwrites old texture pixel data)
+	static bool				sMassageTexture(CTexture *p_face_texture, const SFacePoints &texture_points, bool palette_gen = true,
+											bool use_fill_color = false, Image::RGBA fill_color = Image::RGBA(128, 128, 128, 128));
+
+	// Individual massage steps (mainly for editor).  Note that these functions do not convert to 32-bit or back to
+	// 8-bit at the end.  These functions need to be called separately.
+	static bool				sAdjustTextureToModel(CTexture *p_face_texture, const SFacePoints &texture_points,
+												  bool use_fill_color = false, Image::RGBA fill_color = Image::RGBA(128, 128, 128, 128));
+	static bool				sAdjustTextureColors(CTexture *p_face_texture, const SFacePoints &texture_points);
+	static bool				sCombineTextureWithOverlay(CTexture *p_face_texture);
+
+protected:
+
+	static SFacePoints		s_model_face_points;		// Face points needed for a model
+
+	static CTexture *		sp_face_texture_overlay;  	// The texture that needs to go over the top of a supplied face texture
+
+};
+
+// Script functions
+bool ScriptSetModelFaceTexturePoints(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetFaceMassageTextureOverlay(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMassageFaceTexture(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAdjustFaceTextureToModel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAdjustFaceTextureColors(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCombineFaceTextureWithOverlay(Script::CStruct *pParams, Script::CScript *pScript);
+
+// Utility functions
+void SetDefaultFacePoints(SFacePoints* pFacePoints);
+bool SetFacePointsStruct(const SFacePoints &face_points, Script::CStruct *p_struct);
+bool GetFacePointsStruct(SFacePoints &face_points, Script::CStruct *p_struct);
+
+}
+
+#endif //	__GFX_FACE_MASSAGE_H__
+
diff --git a/Code/Gfx/FaceTexture.cpp b/Code/Gfx/FaceTexture.cpp
new file mode 100644
index 0000000..69369bd
--- /dev/null
+++ b/Code/Gfx/FaceTexture.cpp
@@ -0,0 +1,324 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       FaceTexture.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  4/29/2003
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+
+// for temporary texture
+#include 
+
+namespace Gfx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFaceTexture::CFaceTexture()
+{
+	m_isValid = false;
+
+	SetDefaultFacePoints( &m_points );
+	
+	SetOverlayTextureName( "faces\\CS_NSN_Head_wht_alpha" );
+
+/*
+	// should already be cleared out
+	memset( m_rawData, 0, vFACE_TEXTURE_SIZE );
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFaceTexture::LoadFace( const char* pFaceName, bool fullPathIncluded )
+{
+	// As a test, set the freak image
+	// (otherwise, we'd need to go through the face download interface)
+	char extended_filename[512];
+	int file_size;
+
+	if ( fullPathIncluded )
+	{
+		sprintf( extended_filename, "%s.img.ps2", pFaceName );
+	}
+	else
+	{
+		sprintf( extended_filename, "images\\%s.img.ps2", pFaceName );	
+	}
+
+	char* pData = (char*)File::LoadAlloc( extended_filename, &file_size );
+	memcpy( m_rawData, pData, file_size );
+	Dbg_MsgAssert( file_size <= vRAW_TEXTURE_SIZE, ( "File too big (file is %d, buffer is %d)", file_size, vRAW_TEXTURE_SIZE ) );
+	Mem::Free( pData );
+	
+	m_isValid = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8* CFaceTexture::ReadTextureDataFromBuffer(uint8* pBuffer, int bufferSize)
+{
+	// READ RAW IMG.PS2 DATA ONLY, NOT FACE POINT OR OVERLAY INFO
+
+	// if a safety-check was specified...
+	#ifdef	__NOPT_ASSERT__
+	if ( bufferSize )
+	{
+		int totalSize = vRAW_TEXTURE_SIZE;
+		Dbg_MsgAssert( bufferSize >= totalSize, ( "Buffer size (%d bytes) must <= %d bytes", bufferSize, totalSize ) );
+	}
+	#endif
+	
+	memcpy( &m_rawData[0], pBuffer, vRAW_TEXTURE_SIZE );
+
+	pBuffer += vRAW_TEXTURE_SIZE;
+	
+	return pBuffer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CFaceTexture::WriteToBuffer( uint8* pBuffer, int bufferSize )
+{
+	int totalSize = 0;
+		
+	totalSize += vRAW_TEXTURE_SIZE;
+	Dbg_MsgAssert( bufferSize >= totalSize, ( "Buffer size (%d bytes) must >= %d bytes", bufferSize, totalSize ) );
+	memcpy( pBuffer, &m_rawData[0], vRAW_TEXTURE_SIZE );
+	pBuffer += vRAW_TEXTURE_SIZE;
+	
+	totalSize += sizeof(Nx::SFacePoints);
+	Dbg_MsgAssert( bufferSize >= totalSize, ( "Buffer size (%d bytes) must >= %d bytes", bufferSize, totalSize ) );
+	memcpy( pBuffer, &m_points, sizeof(Nx::SFacePoints) );
+	pBuffer += sizeof(Nx::SFacePoints);
+
+	totalSize += ( strlen( m_overlayTextureName ) + 1 );
+	Dbg_MsgAssert( bufferSize >= totalSize, ( "Buffer size (%d bytes) must >= %d bytes", bufferSize, totalSize ) );
+	memcpy( pBuffer, m_overlayTextureName, strlen( m_overlayTextureName ) + 1 );
+	pBuffer += ( strlen( m_overlayTextureName ) + 1 );
+
+	return totalSize;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8* CFaceTexture::ReadFromBuffer( uint8* pBuffer, int bufferSize )
+{
+	int totalSize = 0;
+	
+	totalSize += vRAW_TEXTURE_SIZE;
+	if ( bufferSize )
+	{
+		// if a safety-check was specified...
+		Dbg_MsgAssert( bufferSize >= totalSize, ( "Buffer size (%d bytes) must <= %d bytes", bufferSize, totalSize ) );
+	}
+	memcpy( &m_rawData[0], pBuffer, vRAW_TEXTURE_SIZE );
+	pBuffer += vRAW_TEXTURE_SIZE;
+	
+	totalSize += sizeof(Nx::SFacePoints);
+	if ( bufferSize )
+	{
+		// if a safety-check was specified...
+		Dbg_MsgAssert( bufferSize >= totalSize, ( "Buffer size (%d bytes) must <= %d bytes", bufferSize, totalSize ) );
+	}
+	memcpy( &m_points, pBuffer, sizeof(Nx::SFacePoints) );
+	pBuffer += sizeof(Nx::SFacePoints);
+
+	int overlayNameLength = strlen( (char*)pBuffer );
+	if ( overlayNameLength >= vMAX_OVERLAY_NAME_SIZE )
+	{
+		Dbg_MsgAssert( 0, ( "Unusual length for overlay name" ) );
+	}
+	
+	totalSize += ( overlayNameLength + 1 );
+	if ( bufferSize )
+	{
+		// if a safety-check was specified...
+		Dbg_MsgAssert( bufferSize >= totalSize, ( "Buffer size (%d bytes) must <= %d bytes", bufferSize, totalSize ) );
+	}
+	memcpy( m_overlayTextureName, pBuffer, overlayNameLength + 1 );
+	pBuffer += ( overlayNameLength + 1 );
+
+	return pBuffer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CFaceTexture::WriteIntoStructure( Script::CStruct* pSubStruct )
+{
+	Dbg_Assert( pSubStruct );        
+	Dbg_MsgAssert( IsValid(), ( "Face texture is not valid!" ) );
+
+	Script::CArray* pRawTextureArray = new Script::CArray;
+	Dbg_MsgAssert( ( vRAW_TEXTURE_SIZE ) % 4 == 0, ( "Was expecting raw texture size to be a multiple of 4", vRAW_TEXTURE_SIZE ) ); 
+	pRawTextureArray->SetSizeAndType( vRAW_TEXTURE_SIZE / 4, ESYMBOLTYPE_NAME );
+
+	uint32* pFaceData = (uint32*)&m_rawData[0];
+
+	for ( int i = 0; i < (vRAW_TEXTURE_SIZE / 4); i++ )
+	{
+		
+		pRawTextureArray->SetChecksum( i, *pFaceData );
+		pFaceData++;
+	}
+
+	pSubStruct->AddArrayPointer( CRCD(0x99790d96,"rawTextureData"), pRawTextureArray );
+	pSubStruct->AddString( CRCD(0xa7e7a264,"overlayTextureName"), m_overlayTextureName );
+	Nx::SetFacePointsStruct( m_points, pSubStruct );
+
+	// readfromstructure should also set the valid flag
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CFaceTexture::ReadFromStructure( Script::CStruct* pSubStruct )
+{
+	Dbg_Assert( pSubStruct );
+
+	Script::CArray* pRawTextureArray;
+	pSubStruct->GetArray( CRCD(0x99790d96,"rawTextureData"), &pRawTextureArray, Script::ASSERT );
+	
+	uint32* pFaceData = (uint32*)&m_rawData[0];
+	Dbg_Assert( pRawTextureArray->GetSize() * sizeof(uint32) == vRAW_TEXTURE_SIZE );
+	for ( uint32 i = 0; i < pRawTextureArray->GetSize(); i++ )
+	{
+		*pFaceData = pRawTextureArray->GetChecksum( i );
+		pFaceData++;
+	}
+	
+	const char* pOverlayTextureName;
+	pSubStruct->GetString( CRCD(0xa7e7a264,"overlayTextureName"), &pOverlayTextureName, Script::ASSERT );
+	strcpy( m_overlayTextureName, pOverlayTextureName );
+
+	Nx::GetFacePointsStruct( m_points, pSubStruct );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CFaceTexture::PrintContents()
+{
+#ifdef __NOPT_ASSERT__
+	m_points.PrintData();
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CFaceTexture::IsValid() const
+{
+	return m_isValid;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFaceTexture::SetValid( bool valid )
+{
+	m_isValid = valid;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+uint8* CFaceTexture::GetTextureData()
+{
+	return m_rawData;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CFaceTexture::GetTextureSize() const
+{
+	return vRAW_TEXTURE_SIZE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::SFacePoints CFaceTexture::GetFacePoints() const
+{
+	return m_points;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFaceTexture::SetFacePoints( const Nx::SFacePoints& facePoints )
+{
+	m_points = facePoints;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFaceTexture::SetOverlayTextureName(const char* pTextureName)
+{
+	Dbg_MsgAssert( pTextureName, ( "No texture name specified" ) );
+	Dbg_MsgAssert( strlen(pTextureName) < vMAX_OVERLAY_NAME_SIZE, ( "Overlay name is too long %s (max=%d)", pTextureName, vMAX_OVERLAY_NAME_SIZE ) );
+
+	strcpy( m_overlayTextureName, pTextureName );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* CFaceTexture::GetOverlayTextureName()
+{
+	return m_overlayTextureName;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
+
diff --git a/Code/Gfx/FaceTexture.h b/Code/Gfx/FaceTexture.h
new file mode 100644
index 0000000..4a6308a
--- /dev/null
+++ b/Code/Gfx/FaceTexture.h
@@ -0,0 +1,82 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       FaceTexture.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  4/29/2003
+//****************************************************************************
+
+#ifndef	__GFX_FACE_TEXTURE_H__
+#define	__GFX_FACE_TEXTURE_H__
+
+#include 
+#include 
+
+// for SFacePoints
+#include 
+
+namespace Gfx
+{
+
+//////////////////////////////////////////////////////////////////////////////
+// This encapsulates all the face texture-related data
+// (raw texture data, and any tweaks needed for correct
+// mapping).  The "WriteToBuffer/ReadFromBuffer" is
+// used to store this data to a memory card, to a skater
+// profile, or for use in network games.
+class CFaceTexture : public Spt::Class
+{
+public:
+	enum
+	{
+		// TODO:  Find out how big the data really needs to be...
+		// (right now, it's 16K for the texture data, and 1K for the palette data)
+		vRAW_TEXTURE_SIZE = ( 17440 ),
+		vMAX_OVERLAY_NAME_SIZE = 64,
+		vTOTAL_CFACETEXTURE_SIZE = vRAW_TEXTURE_SIZE + sizeof(Nx::SFacePoints) + vMAX_OVERLAY_NAME_SIZE,
+	};
+
+	CFaceTexture();
+
+public:
+	// general accessors
+	bool				IsValid() const;
+	void				SetValid( bool valid );
+	uint8*				GetTextureData();
+	int					GetTextureSize() const;
+	Nx::SFacePoints		GetFacePoints() const;
+	void				SetFacePoints( const Nx::SFacePoints& facePoints );
+	void				SetOverlayTextureName(const char* pTexture);
+	const char*			GetOverlayTextureName();
+
+	// debug face
+	void				LoadFace( const char* pFaceName, bool fullPathIncluded );
+	void				PrintContents();
+
+public:
+	// for downloading faces
+	// (handles raw IMG.PS2 data only)
+	uint8*				ReadTextureDataFromBuffer(uint8* pBuffer, int bufferSize);
+
+public:
+	// for network message handling
+	// (handles raw IMG.PS2 data, face points, overlay information)
+	int					WriteToBuffer(uint8* pBuffer, int bufferSize );
+	uint8*				ReadFromBuffer(uint8* pBuffer, int bufferSize );
+	
+public:
+	// for memory card loading/saving
+	// (handles raw IMG.PS2 data, face points, overlay information)
+	void 				WriteIntoStructure( Script::CStruct* pStuff );
+	void 				ReadFromStructure( Script::CStruct* pStuff );
+
+protected:
+	bool				m_isValid;
+	Nx::SFacePoints		m_points;
+	uint8				m_rawData[vRAW_TEXTURE_SIZE];
+	char				m_overlayTextureName[vMAX_OVERLAY_NAME_SIZE];
+};
+
+}
+
+#endif //	__GFX_FACE_TEXTURE_H__
+
diff --git a/Code/Gfx/Image/ImageBasic.h b/Code/Gfx/Image/ImageBasic.h
new file mode 100644
index 0000000..849e068
--- /dev/null
+++ b/Code/Gfx/Image/ImageBasic.h
@@ -0,0 +1,107 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		            											**
+**																			**
+**	Module:			              			 								**
+**																			**
+**	File name:		                    									**
+**																			**
+**	Created by:     rjm        				    			                **
+**																			**
+**	Description:					                                        **
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_IMAGE_IMAGEBASIC_H
+#define __GFX_IMAGE_IMAGEBASIC_H
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Image
+{
+
+enum JustX
+{
+	vJUST_LEFT 		= 0,
+	vJUST_CENTER_X,
+	vJUST_RIGHT,
+};
+
+enum JustY
+{
+	vJUST_TOP		= 0,
+	vJUST_CENTER_Y,	
+	vJUST_BOTTOM,
+};
+
+
+struct RGBA
+{
+	RGBA() { }
+	RGBA(uint8 red, uint green, uint blue, uint alpha) :
+		r(red),
+		g(green),
+		b(blue),
+		a(alpha) { }
+
+	void	Blend128(const RGBA & rgba);
+	void	Blend255(const RGBA & rgba);
+
+	uint8 r;
+	uint8 g;
+	uint8 b;
+	uint8 a;
+};
+
+////////////////////////
+// Inlines
+
+inline void RGBA::Blend128(const RGBA & rgba)
+{
+	int blend_val;
+
+	blend_val = r * rgba.r;
+	r = (uint8) (blend_val >> 7);
+	blend_val = g * rgba.g;
+	g = (uint8) (blend_val >> 7);
+	blend_val = b * rgba.b;
+	b = (uint8) (blend_val >> 7);
+	blend_val = a * rgba.a;
+	a = (uint8) (blend_val >> 7);
+}
+
+inline void RGBA::Blend255(const RGBA & rgba)
+{
+	int blend_val;
+
+	blend_val = r * rgba.r;
+	r = (uint8) (blend_val / 255);
+	blend_val = g * rgba.g;
+	g = (uint8) (blend_val / 255);
+	blend_val = b * rgba.b;
+	b = (uint8) (blend_val / 255);
+	blend_val = a * rgba.a;
+	a = (uint8) (blend_val >> 7);		// Alpha is always 128 == 1.0
+}
+
+}
+
+#endif //__GFX_IMAGE_IMAGEBASIC_H
+
diff --git a/Code/Gfx/ModelAppearance.cpp b/Code/Gfx/ModelAppearance.cpp
new file mode 100644
index 0000000..573e3e3
--- /dev/null
+++ b/Code/Gfx/ModelAppearance.cpp
@@ -0,0 +1,804 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       ModelAppearance.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  4/2/2000
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+// NOTES:
+
+// A CModelAppearance contains one script structure, whose
+// contents look something like this:
+//	{
+//		head			= { desc_id=#"Andrew Reynolds" h = 0 s = 50 v = 100 use_default_hsv = 1 }
+//		torso			= { desc_id=#"Long Sleeve - Collar" h = 0 s = 50 v = 100 use_default_hsv = 1 }
+//		legs			= { desc_id=#"Reynolds's Pants" h = 0 s = 50 v = 100 use_default_hsv = 1 }
+//		shoes			= { desc_id=#"Reynolds's" h = 0 s = 50 v = 100 use_default_hsv = 1 }
+//		boardup			= { desc_id=#"Solid" }	
+//		boarddown		= { desc_id=#"Green Brand Logo" h = 0 s = 50 v = 100 use_default_hsv = 1 }
+//		scale			= 1.02
+//		weight_scale	= 0.97
+//	}
+
+// Basically, it's a bunch of "virtual structures", each 
+// assigned to a "part checksum" (head, torso, etc.).  
+// Each part checksum corresponds to the name of a global
+// array containing a list of read-only "actual structures".
+// Given a part checksum and a desc_id in the CModelAppearance, 
+// we can lookup the appropriate global array and find the 
+// appropriate actual structure.
+
+// CModelAppearance is really just a wrapper around the
+// CStruct class, but after some consideration,
+// I decided not to subclass from it.  This is so that
+// I'd be able to replace the underlying implementation
+// while keeping the same interface.
+
+// There shouldn't be anything skater- (or even player-)
+// specific hardcoded here.  The intent is that we use
+// this same class for any kind of customizable model
+// (such as Create-a-Peds, Create-a-Cars, or even
+// Create-a-Spaceships for future games).
+	
+// The CModelAppearance should have no knowledge of the full
+// list of possible part checksums;  this list should be
+// completely in script.  This implies that the code should
+// never have to iterate through a list of part checksums,
+// (unlike THPS3, which had many explicit references to
+// "editable_cas_options", Cas::GetBodyPartCount(), and
+// Cas::GetBodyPartName()).
+
+// One of the reset types used to be "randomized", but
+// that is something that is more appropriate at the skater
+// profile level (or, even better, in script), which has
+// a better understanding of what parts should be disqualified 
+// from working with a particular skater instance.  If 
+// randomization were to be implemented at this level, it should
+// be a purely naive randomization (no weighting, no part
+// disqualification).
+	
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 get_desc_id_from_structure( Script::CStruct* pStructure )
+{
+	// This function is purely for convenience.  It grabs
+	// the "desc_id" field from the supplied structure.
+	
+	uint32 descId = 0;
+	pStructure->GetChecksum( CRCD(0x4bb2084e,"desc_id"), &descId, Script::NO_ASSERT );
+	return descId;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelAppearance::resolve_randomized_desc_ids()
+{
+	// for each structure component, see whether
+	// it's got any randomized desc ids.  if so,
+	// select one to be used every time this
+	// appearance instance is used
+
+	// sometimes the skin tone must be consistent 
+	// among all the body parts...  if any structure
+	// contains a "random_set" checksum, then
+	// make sure all future random parts get
+	// this selected as well
+	uint32 random_set = 0;
+
+	Script::CComponent* p_comp = m_appearance.GetNextComponent( NULL );
+	while ( p_comp )
+	{
+		Script::CComponent* p_next = m_appearance.GetNextComponent( p_comp );		
+		
+		if ( p_comp->mType == ESYMBOLTYPE_STRUCTURE )
+		{
+			uint32 partChecksum = p_comp->mNameChecksum;
+			int randomIndex;
+
+			if ( p_comp->mpStructure->ContainsFlag( CRCD(0xd81c03b0,"randomized_desc_id") ) )
+			{
+				Script::CStruct* pActualStruct = Cas::GetRandomOptionStructure( partChecksum, random_set );
+				Dbg_MsgAssert( pActualStruct, ( "Unrecognized part checksum to randomize %s", Script::FindChecksumName(partChecksum) ) );
+				
+				// remember the random_set, if any
+				pActualStruct->GetChecksum( CRCD(0x0d7260fd,"random_set"), &random_set, Script::NO_ASSERT );
+				
+				p_comp->mpStructure->Clear();
+				p_comp->mpStructure->AddChecksum( CRCD(0x4bb2084e,"desc_id"), get_desc_id_from_structure(pActualStruct) );
+			}
+			else if ( p_comp->mpStructure->GetInteger( CRCD(0x4b833e64,"random_index"), &randomIndex, Script::NO_ASSERT ) )
+			{
+				Script::CStruct* pActualStruct = Cas::GetRandomOptionStructureByIndex( partChecksum, randomIndex, random_set );
+				Dbg_MsgAssert( pActualStruct, ( "Unrecognized part checksum to randomize %s", Script::FindChecksumName(partChecksum) ) );
+				
+				// remember the random_set, if any
+				pActualStruct->GetChecksum( CRCD(0x0d7260fd,"random_set"), &random_set, Script::NO_ASSERT );
+				
+				p_comp->mpStructure->Clear();
+				p_comp->mpStructure->AddChecksum( CRCD(0x4bb2084e,"desc_id"), get_desc_id_from_structure(pActualStruct) );
+			}
+		}
+
+		p_comp = p_next;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelAppearance::clear_part(uint32 partChecksum)
+{
+	m_appearance.RemoveComponent(partChecksum);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelAppearance::set_part(uint32 partChecksum, uint32 descID, Script::CStruct* pParams)
+{
+	Script::CStruct* pStruct;
+	
+	// if the structure doesn't already exist, then add it...
+	if ( !m_appearance.GetStructure(partChecksum, &pStruct) )
+	{
+		pStruct = new Script::CStruct;
+		m_appearance.AddComponent(partChecksum, ESYMBOLTYPE_STRUCTUREPOINTER, (int)pStruct);
+	}
+
+	pStruct->Clear();
+
+	// check for extra color parameters
+	int use_default_hsv = 1;
+	pParams->GetInteger( CRCD(0x97dbdde6,"use_default_hsv"), &use_default_hsv, Script::NO_ASSERT );
+	
+	if ( !use_default_hsv )
+	{
+		int h, s, v;
+		pParams->GetInteger( CRCD(0x6e94f918,"h"), &h, Script::ASSERT );
+		pParams->GetInteger( CRCD(0xe4f130f4,"s"), &s, Script::ASSERT );
+		pParams->GetInteger( CRCD(0x949bc47b,"v"), &v, Script::ASSERT );
+		
+		pStruct->AddInteger( CRCD(0x97dbdde6,"use_default_hsv"), 0 );
+		pStruct->AddInteger( CRCD(0x6e94f918,"h"), h );
+		pStruct->AddInteger( CRCD(0xe4f130f4,"s"), s );
+		pStruct->AddInteger( CRCD(0x949bc47b,"v"), v );
+	}
+
+	pStruct->AddChecksum( CRCD(0x4bb2084e,"desc_id"), descID );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelAppearance::set_checksum(uint32 fieldChecksum, uint32 valueChecksum)
+{
+	m_appearance.AddChecksum( fieldChecksum, valueChecksum );
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CModelAppearance::CModelAppearance( void )
+{
+	mp_faceTexture = NULL;
+
+	m_willEventuallyHaveFaceTexture = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelAppearance::CModelAppearance( const CModelAppearance& rhs )
+{
+	mp_faceTexture = NULL;
+
+	// use the overridden assignment operator
+	*this = rhs;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CModelAppearance::~CModelAppearance()
+{
+	if ( mp_faceTexture )
+	{
+		delete mp_faceTexture;
+		mp_faceTexture = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelAppearance& CModelAppearance::operator=( const CModelAppearance& rhs )
+{
+	if ( &rhs == this )
+	{
+		return *this;
+	}
+
+	// it shouldn't be necessary to define this function, as
+	// this is what the default assignment operator is supposed
+	// to do.  However, the compiler gives me warnings if I
+	// don't define it ("statement with no effect").
+	m_appearance = rhs.m_appearance;
+
+	// get rid of old face texture
+	if ( mp_faceTexture )
+	{
+		delete mp_faceTexture;
+		mp_faceTexture = NULL;
+
+	}
+
+	if ( rhs.mp_faceTexture )
+	{
+		CreateFaceTexture();
+		Dbg_Assert( mp_faceTexture );
+		*mp_faceTexture = *rhs.mp_faceTexture;
+	}
+
+	m_willEventuallyHaveFaceTexture = rhs.m_willEventuallyHaveFaceTexture;
+
+	return *this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelAppearance::Init()
+{
+	m_appearance.Clear();
+
+	if ( mp_faceTexture )
+	{
+		// reset the face texture
+		mp_faceTexture->SetValid( false );
+	}
+
+	// everyone should have certain items defined, 
+	// such as sleeves for doing sleeve colors.
+	Script::CStruct* pResetStructure = Script::GetStructure( CRCD(0xfe54486d,"appearance_init_structure"), Script::NO_ASSERT );
+	if ( pResetStructure )
+	{
+		m_appearance.AppendStructure( pResetStructure );
+	}
+
+	m_willEventuallyHaveFaceTexture = false;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelAppearance::Load( Script::CStruct* pStructure, bool resolve_randoms )
+{
+	Dbg_Assert( pStructure );
+
+	// In THPS3, we didn't clear the m_appearance first when
+	// loading from a memory card, so that if the saved data is 
+	// missing anything, it won't matter because the default 
+	// will already be in m_appearance.  (This prevents asserts
+	// on autoloading when a new component has been added to 
+	// m_appearance which is not present on the memory card)
+	// However, for THPS4, the appearances should be a lot more
+	// flexible, and should no longer fail when there are missing items.
+	Init();
+
+#if 1
+	// in case there are any global structure names,
+	// resolve them
+	pStructure->ExpandInto(	&m_appearance, 0 );
+#else
+	// add the new desired data
+	m_appearance.AppendStructure( pStructure );
+#endif
+	
+   // at this point, all the randomized_desc_ids
+	// should be resolved
+	if ( resolve_randoms )
+	{
+		resolve_randomized_desc_ids();
+	}
+		
+#ifdef	__NOPT_ASSERT__
+	// Make sure that the m_appearance does not contain any flag members,
+	// which would potentially cause leaks as we append any structures.
+	uint32 dummy = 0;
+	Dbg_MsgAssert( !m_appearance.GetChecksum( NONAME, &dummy ), ( "m_appearance contains a flag '%s'", Script::FindChecksumName(dummy) ) );
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelAppearance::Load( uint32 structure_name, bool resolve_randoms )
+{
+	Script::CStruct* pStructure;
+
+	pStructure = Script::GetStructure( structure_name, Script::ASSERT );
+
+	return Load( pStructure, resolve_randoms );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifndef __PLAT_NGC__
+void compress_model_appearance( Script::CStruct* pStruct )
+{
+	// the worst-case is too big to fit into a network packet
+	// so this will remove some of the more unnecessary items
+	// from the structure.
+	pStruct->RemoveComponent( CRCD(0xb1d19000,"deck_layer1") );
+	pStruct->RemoveComponent( CRCD(0x28d8c1ba,"deck_layer2") );
+	pStruct->RemoveComponent( CRCD(0x5fdff12c,"deck_layer3") );
+	pStruct->RemoveComponent( CRCD(0xc1bb648f,"deck_layer4") );
+	pStruct->RemoveComponent( CRCD(0xb6bc5419,"deck_layer5") );
+
+	pStruct->RemoveComponent( CRCD(0x3fa9b96e,"head_tattoo") );
+	pStruct->RemoveComponent( CRCD(0x283dea37,"left_bicep_tattoo") );
+	pStruct->RemoveComponent( CRCD(0xde55864b,"left_forearm_tattoo") );
+	pStruct->RemoveComponent( CRCD(0xd408fa96,"right_bicep_tattoo") );
+	pStruct->RemoveComponent( CRCD(0x74fee7b2,"right_forearm_tattoo") );
+	pStruct->RemoveComponent( CRCD(0x8eba2bc9,"chest_tattoo") );
+	pStruct->RemoveComponent( CRCD(0x233b7bba,"back_tattoo") );
+	pStruct->RemoveComponent( CRCD(0x2b359a94,"left_leg_tattoo") );
+	pStruct->RemoveComponent( CRCD(0x609432ca,"right_leg_tattoo") );
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CModelAppearance::WriteToBuffer(uint8 *pBuffer, uint32 BufferSize, bool ignoreFaceData )
+{
+	// for network message building
+	// (note that the face texture is not included in this buffer
+	// because the buffer is too small...  face textures will
+	// be sent in a separate net packet)
+	Script::CStruct* pTempStructure = new Script::CStruct;
+	pTempStructure->AppendStructure( &m_appearance );
+	uint32 size = Script::WriteToBuffer(pTempStructure, pBuffer, BufferSize);
+
+#ifndef __PLAT_NGC__
+	// GJ:  Need to compress the model appearances for Xbox and PS2
+	// versions, or else the worst-case model appearance will crash
+	// the server if he quits his own game (because it overflows the
+	// max net packet size).  MAX_MODEL_APPEARANCE_SIZE is a guess 
+	// based on the existing model appearance size, but it really
+	// depends on how big the info/tricks get...  700 bytes seems safe
+	const uint32 MAX_MODEL_APPEARANCE_SIZE = 700;
+	if ( size > MAX_MODEL_APPEARANCE_SIZE )
+	{
+		// if the model appearance is too big, then	strip out
+		// some of the cosmetic stuff...
+		compress_model_appearance( pTempStructure );
+		size = Script::WriteToBuffer(pTempStructure, pBuffer, BufferSize);
+	}
+#endif
+
+	delete pTempStructure;
+
+	// skip to the next chunk
+	pBuffer += size;
+	BufferSize -= size;
+
+	// we do, however, still need to send a flag that says it
+	// will eventually have a face texture...  so that the model 
+	// builder knows to use the face-mapped head rather than
+	// the non-facemapped head
+	if( ignoreFaceData )
+	{
+		
+		*pBuffer = 0;
+	}
+	else
+	{
+		*pBuffer = ( ( GetFaceTexture() && GetFaceTexture()->IsValid() ) || m_willEventuallyHaveFaceTexture ) ? 1 : 0;		
+	}
+	
+	pBuffer += 1;
+	BufferSize -= 1;
+	size += 1;
+	
+	return size;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8* CModelAppearance::ReadFromBuffer(uint8 *pBuffer)
+{
+	Script::CStruct* pTempStructure = new Script::CStruct;
+	pBuffer = Script::ReadFromBuffer( pTempStructure, pBuffer );
+	m_appearance.Clear();
+	m_appearance.AppendStructure( pTempStructure );
+	delete pTempStructure;
+	
+	// we do, however, still need to send a flag that says it
+	// will eventually have a face texture...  so that the model 
+	// builder knows to use the face-mapped head rather than
+	// the non-facemapped head
+	m_willEventuallyHaveFaceTexture = *pBuffer;
+	pBuffer++;
+	
+	return pBuffer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelAppearance::PrintContents( const Script::CStruct* p_structure )
+{
+#ifdef __NOPT_ASSERT__
+	//Script::PrintContents( &m_appearance );
+
+	if(!p_structure)
+	{
+		p_structure = &m_appearance;
+	}
+	//const Script::CStruct* p_structure= &m_appearance;
+
+	Dbg_MsgAssert(p_structure,("NULL p_structure"));
+	  
+	//printf(" ");
+	
+	
+	printf("{");
+			
+    Script::CComponent *p_comp=p_structure->GetNextComponent(NULL);
+
+    while (p_comp)
+    {
+		
+		if (p_comp->mNameChecksum)
+		{
+			printf(" %s=",Script::FindChecksumName(p_comp->mNameChecksum));
+		}	
+            
+        switch (p_comp->mType)
+        {
+        case ESYMBOLTYPE_INTEGER:
+            printf("%d",p_comp->mIntegerValue);
+            break;
+        case ESYMBOLTYPE_FLOAT:
+            printf("%f",p_comp->mFloatValue);
+            break;
+        case ESYMBOLTYPE_STRING:
+            printf("#\"%s\"",p_comp->mpString);
+            break;
+        case ESYMBOLTYPE_LOCALSTRING:
+            printf("'%s'",p_comp->mpLocalString);
+            break;
+		/*case ESYMBOLTYPE_PAIR:
+            printf("(%f,%f) ",p_comp->mpPair->mX,p_comp->mpPair->mY);
+            break;
+        case ESYMBOLTYPE_VECTOR:
+            printf("(%f,%f,%f) ",p_comp->mpVector->mX,p_comp->mpVector->mY,p_comp->mpVector->mZ);
+            break;*/
+        case ESYMBOLTYPE_STRUCTURE:
+			//printf(" ");
+			CModelAppearance::PrintContents(p_comp->mpStructure);
+            break;
+        case ESYMBOLTYPE_NAME:
+            printf("#\"%s\"",Script::FindChecksumName(p_comp->mChecksum));
+			
+			#ifdef EXPAND_GLOBAL_STRUCTURE_REFERENCES
+			if (p_comp->mNameChecksum==0)
+			{
+				// It's an un-named name. Maybe it's a global structure ... 
+				// If so, print its contents too, which is handy for debugging.
+			    CSymbolTableEntry *p_entry=Resolve(p_comp->mChecksum);
+				if (p_entry && p_entry->mType==ESYMBOLTYPE_STRUCTURE)
+				{
+					printf("... Defined in %s ...\n",Script::FindChecksumName(p_entry->mSourceFileNameChecksum));
+					Dbg_MsgAssert(p_entry->mpStructure,("NULL p_entry->mpStructure"));
+					CModelAppearance::PrintContents(p_entry->mpStructure);
+				}
+			}		
+			#endif
+            break;
+        case ESYMBOLTYPE_QSCRIPT:
+			printf("(A script) "); // TODO
+			break;
+		case ESYMBOLTYPE_ARRAY:
+			//printf(" ");
+			Script::PrintContents(p_comp->mpArray,0);
+            break;
+        default:
+			printf("Component of type '%s', value 0x%08x\n",Script::GetTypeName(p_comp->mType),p_comp->mUnion);
+            //Dbg_MsgAssert(0,("Bad p_comp->Type"));
+            break;
+        }
+        p_comp=p_structure->GetNextComponent(p_comp);
+		
+		#ifdef SLOW_DOWN_PRINTCONTENTS
+		// A delay to let printf catch up so that it doesn't skip stuff when printing big arrays.		
+		for (int i=0; i<1000000; ++i);
+		#endif
+    }
+	
+	printf("}\n");
+
+
+	/*if ( mp_faceTexture && mp_faceTexture->IsValid() )
+	{
+		mp_faceTexture->PrintContents();
+	}*/
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CModelAppearance::GetActualDescStructure( uint32 partChecksum )
+{
+	Script::CStruct* pStructure;
+	if ( m_appearance.GetStructure( partChecksum, &pStructure, Script::NO_ASSERT ) )
+	{
+		return Cas::GetOptionStructure( partChecksum, get_desc_id_from_structure( pStructure ), false );
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CModelAppearance::GetVirtualDescStructure( uint32 partChecksum )
+{
+	Script::CStruct* pStructure = NULL;
+	m_appearance.GetStructure( partChecksum, &pStructure, Script::NO_ASSERT );
+	return pStructure;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelAppearance::CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( checksum )
+	{
+		case 0x0a23400c:		// ClearPart
+		{
+			Dbg_MsgAssert(pParams, ("No params supplied to model appearance"));
+
+			uint32 partChecksum;
+			pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+			clear_part(partChecksum);
+		}
+		return true;
+		
+		case 0x83339d0b:		// SetPart
+		{
+			Dbg_MsgAssert(pParams, ("No params supplied to model appearance"));
+
+			uint32 partChecksum;
+			pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+	
+			uint32 descChecksum;
+			pParams->GetChecksum( CRCD(0x4bb2084e,"desc_id"), &descChecksum, Script::ASSERT );
+			
+			set_part(partChecksum, descChecksum, pParams);
+		}
+		return true;
+
+		case 0x10a225d6:		// GetPart
+		{
+			Dbg_MsgAssert(pParams, ("No params supplied to model appearance"));
+		
+			uint32 partChecksum;
+			pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+	
+			Script::CStruct* pVirtualDescStructure = GetVirtualDescStructure( partChecksum );
+
+			if ( pVirtualDescStructure )
+			{
+				// return all the parameters, including desc_id, use_default_hsv, h, s, v
+				pScript->GetParams()->AppendStructure( pVirtualDescStructure );
+				return true;
+			}
+
+			return false;
+		}
+		return true;
+
+		case 0xd27427ff:		// SetChecksum
+		{
+			Dbg_MsgAssert(pParams, ("No params supplied to model appearance"));
+
+			uint32 fieldChecksum;
+			pParams->GetChecksum( CRCD(0xa40abaa7,"field"), &fieldChecksum, Script::ASSERT );
+	
+			uint32 valueChecksum;
+			pParams->GetChecksum( CRCD(0xe288a7cb,"value"), &valueChecksum, Script::ASSERT );
+			
+			set_checksum(fieldChecksum, valueChecksum);
+		}
+		return true;
+
+		case 0xb13906b0:		// GotPart
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+			return ( GetActualDescStructure( partChecksum ) );
+		}
+
+		case 0xe961bafa:		// PartGotFlag
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+			uint32 flagChecksum;
+			pParams->GetChecksum( CRCD(0x2e0b1465,"flag"), &flagChecksum, Script::ASSERT );
+
+			Script::CStruct* pActualDescStructure = GetActualDescStructure( partChecksum );
+
+			if ( pActualDescStructure )
+			{
+				return pActualDescStructure->ContainsFlag( flagChecksum );
+			}
+
+			Dbg_MsgAssert( 0, ( "part %s was not defined (need it for disqualification script)", Script::FindChecksumName(partChecksum) ) );
+
+			return false;
+		}
+		return true;
+	}
+
+	return Obj::CObject::CallMemberFunction(checksum, pParams, pScript);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFaceTexture* CModelAppearance::GetFaceTexture()
+{
+	return mp_faceTexture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+											  
+void CModelAppearance::CreateFaceTexture()
+{
+	Dbg_MsgAssert( !mp_faceTexture, ( "Model appearance already has a face texture" ) );
+	
+	// the face texture should always go on the skater info heap
+	// these may be permanent, or they might be temporary...
+	mp_faceTexture = new CFaceTexture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+											  
+void CModelAppearance::DestroyFaceTexture()
+{
+	if ( mp_faceTexture )
+	{
+		delete mp_faceTexture;
+		mp_faceTexture = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelAppearance::WillEventuallyHaveFaceTexture()
+{
+	return m_willEventuallyHaveFaceTexture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
diff --git a/Code/Gfx/ModelAppearance.h b/Code/Gfx/ModelAppearance.h
new file mode 100644
index 0000000..16f1c42
--- /dev/null
+++ b/Code/Gfx/ModelAppearance.h
@@ -0,0 +1,126 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       ModelAppearance.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  4/2/2000
+//****************************************************************************
+
+#ifndef __GFX_MODELAPPEARANCE_H
+#define __GFX_MODELAPPEARANCE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**							Forward Declarations						**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+	class CFaceTexture;
+
+// A CModelAppearance contains all the data associated with
+// generating a rendered model (NxModel).
+			
+class CModelAppearance : public Obj::CObject
+{
+public:
+	CModelAppearance();
+	CModelAppearance( const CModelAppearance& rhs );
+	CModelAppearance&	operator=( const CModelAppearance& rhs );
+	virtual 			~CModelAppearance();
+
+public:
+	virtual bool		CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript );
+
+public:
+	// Init: Clears it out to nothing
+	bool				Init();
+
+	// Load: Appends a structure
+	bool				Load( Script::CStruct* pStructure, bool resolve_randoms = true );
+	bool				Load( uint32 structure_name, bool resolve_randoms = true );
+
+public:
+	// for network message building
+	// (note that the face texture is not included in this buffer
+	// because the buffer is too small...  face textures need to
+	// be sent through a separate pathway)
+	uint32				WriteToBuffer(uint8* pBuffer, uint32 bufferSize, bool ignoreFaceData = false);
+	uint8*				ReadFromBuffer(uint8* pBuffer);
+
+public:
+	// for debugging
+	void				PrintContents(const Script::CStruct* p_structure = 0);
+
+public:
+	// used by builder, to apply the desired appearance to a particular SkinModel
+	Script::CStruct*	GetActualDescStructure( uint32 partChecksum );
+	Script::CStruct*	GetVirtualDescStructure( uint32 partChecksum );
+	Script::CStruct*	GetStructure() { return &m_appearance; }
+	Gfx::CFaceTexture*	GetFaceTexture();
+	void				CreateFaceTexture();
+	void				DestroyFaceTexture();
+		
+protected:
+	void				resolve_randomized_desc_ids();
+	void				set_part(uint32 partChecksum, uint32 descID, Script::CStruct* pParams);
+	void				clear_part(uint32 partChecksum);
+	void				set_checksum(uint32 fieldChecksum, uint32 valueChecksum);
+
+protected:
+	Script::CStruct		m_appearance;
+	Gfx::CFaceTexture*	mp_faceTexture;
+	
+public:
+	// the following is needed because skaters created in net games 
+	// don't have a face texture during initial model building (the
+	// face texture will eventually come in a later net packet),
+	// but the model builder still needs to know if there will eventually
+	// be a face texture so that it can load up the correct head geometry
+	bool				WillEventuallyHaveFaceTexture();
+
+protected:
+	bool				m_willEventuallyHaveFaceTexture;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Gfx
+
+#endif	// __OBJECTS_MODELAPPEARANCE_H
diff --git a/Code/Gfx/ModelBuilder.cpp b/Code/Gfx/ModelBuilder.cpp
new file mode 100644
index 0000000..5ada4c9
--- /dev/null
+++ b/Code/Gfx/ModelBuilder.cpp
@@ -0,0 +1,1130 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       ModelBuilder.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/16/2001
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+// NOTES:
+
+// Given a CModelAppearance, the CModelBuilder generates the 
+// appropriate Nx::CModel (creating its Nx::CGeoms, modulating
+// its color, replacing its textures, etc.)
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// some handy checksums
+const uint32 vCHECKSUM_PART = 0xb6f08f39;							// part
+const uint32 vCHECKSUM_USE_DEFAULT_HSV = 0x97dbdde6;				// use_default_hsv
+const uint32 vCHECKSUM_HUE = 0x6e94f918;							// h
+const uint32 vCHECKSUM_SATURATION = 0xe4f130f4;						// s
+const uint32 vCHECKSUM_VALUE = 0x949bc47b;							// v
+const uint32 vCHECKSUM_USE_DEFAULT_SCALE = 0x5a96985d;				// use_default_scale
+const uint32 vCHECKSUM_X = 0x7323e97c;								// x
+const uint32 vCHECKSUM_Y = 0x0424d9ea;								// y
+const uint32 vCHECKSUM_Z = 0x9d2d8850;								// z
+const uint32 vCHECKSUM_REPLACE = 0xf362dbba;						// replace
+const uint32 vCHECKSUM_WITH = 0x676f1df1;							// with
+const uint32 vCHECKSUM_IN = 0xa01371b1;								// in
+const uint32 vCHECKSUM_REPLACE1 = 0x7a993873;						// replace1
+const uint32 vCHECKSUM_WITH1 = 0x9b03adad;							// with1
+const uint32 vCHECKSUM_IN1 = 0xed189051;							// in1
+const uint32 vCHECKSUM_REPLACE2 = 0xe39069c9;						// replace2
+const uint32 vCHECKSUM_WITH2 = 0x020afc17;							// with2
+const uint32 vCHECKSUM_IN2 = 0x7411c1eb;							// in2
+const uint32 vCHECKSUM_REPLACE3 = 0x9497595f;						// replace3
+const uint32 vCHECKSUM_WITH3 = 0x750dcc81;							// with3
+const uint32 vCHECKSUM_IN3 = 0x0316f17d;							// in3
+const uint32 vCHECKSUM_REPLACE4 = 0x0af3ccfc;						// replace4
+const uint32 vCHECKSUM_WITH4 = 0xeb695922;							// with4
+const uint32 vCHECKSUM_IN4 = 0x9d7264de;							// in4
+const uint32 vCHECKSUM_REPLACE5 = 0x7df4fc6a;						// replace5
+const uint32 vCHECKSUM_WITH5 = 0x9c6e69b4;							// with5
+const uint32 vCHECKSUM_IN5 = 0xea755448;							// in5
+const uint32 vCHECKSUM_REPLACE6 = 0xe4fdadd0;						// replace6
+const uint32 vCHECKSUM_WITH6 = 0x0567380e;							// with6
+const uint32 vCHECKSUM_IN6 = 0x737c05f2;							// in6
+const uint32 vCHECKSUM_MESH = 0x1e90c5a9;							// mesh
+const uint32 vCHECKSUM_MESH1 = 0xfeca8bb3;							// mesh1
+const uint32 vCHECKSUM_MESH2 = 0x67c3da09;							// mesh2
+const uint32 vCHECKSUM_MESH3 = 0x10c4ea9f;							// mesh3
+const uint32 vCHECKSUM_MESH4 = 0x8ea07f3c;							// mesh4
+const uint32 vCHECKSUM_REMOVE = 0x977fe2cf;							// remove
+const uint32 vCHECKSUM_TARGET = 0xb990d003;							// target
+const uint32 vCHECKSUM_BODY_SHAPE = 0x812684ef;						// body_shape
+const uint32 vCHECKSUM_APPLY_NORMAL_MODE = 0x2700a288;				// apply_normal_mode
+const uint32 vCHECKSUM_BODY = 0x2457f44d;							// body
+const uint32 vCHECKSUM_NO_SCALING_ALLOWED = 0xb25e39fd;				// no_scaling_allowed
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+Nx::CModel* CModelBuilder::mp_model = NULL;
+Gfx::CSkeleton* CModelBuilder::mp_skeleton = NULL;
+CModelAppearance* CModelBuilder::mp_appearance = NULL;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelBuilder::CModelBuilder( bool useAssetManager, uint32 texDictOffset )
+{
+	// parameters that will be used for the duration of
+	// the model-building:
+
+	// whether the asset manager should be used
+	// (it should NOT, for skaters and other
+	// things that do texture replacement/poly
+	// removal)
+	m_useAssetManager = useAssetManager;
+
+	// used for unique-ifying the texture dictionaries
+	// when you've got multiple skaters
+	m_texDictOffset = texDictOffset;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModelBuilder::BuildModel( CModelAppearance* pAppearance, Nx::CModel* pModel, Gfx::CSkeleton* pSkeleton, uint32 buildScriptName )
+{
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+
+	Dbg_Assert( pAppearance );
+	Dbg_Assert( pModel );
+
+	// don't destroy the existing model...
+	// leave it up to the particular buildscript
+//	pModel->ClearGeoms();
+
+	Dbg_MsgAssert( !mp_model, ( "Static temporary model already exists" ) );
+	mp_model = pModel;
+	
+	Dbg_MsgAssert( !mp_skeleton, ( "Static skeleton already exists" ) );
+	mp_skeleton = pSkeleton;
+	
+	Dbg_MsgAssert( !mp_appearance, ( "Static temporary appearance already exists" ) );
+	mp_appearance = new CModelAppearance;
+	
+	// clear it out from the previous use
+	mp_appearance->Init();
+
+	// GJ 9/8/03:  since it's a temporary variable, we don't want 
+	// it on the skater geom heap (which would fragment it on the
+	// PS2 with 17K worth of face texture data)
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+  	// copy over the desired appearance
+	// (must make a copy, so that we can
+	// make changes to it)
+	*mp_appearance = *pAppearance;
+	
+   	Mem::Manager::sHandle().PopContext();
+
+	// this script is responsible for looping
+	// through all the supported partChecksums
+	// and running all the supported operations
+	// on them.  Which partChecksums and operations
+	// are "supported" should be completely in
+	// script.
+//    Dbg_Message( "BuildModel - %s", Script::FindChecksumName(buildScriptName) );
+    Script::RunScript( buildScriptName, NULL, this );
+    
+	mp_model = NULL;
+	mp_skeleton = NULL;
+
+	delete mp_appearance;
+	mp_appearance = NULL;
+
+//	Dbg_Message( "BuildModel took %d ms", Tmr::ElapsedTime( baseTime ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( checksum )
+	{
+		case 0x30e2951d:		// DebugPrintAppearance
+		{
+			// print out the appearance to make sure
+			// it's coming through correctly in net games
+			mp_appearance->PrintContents();
+		}
+		return true;
+
+		case 0x53da94f1:		// ModelAddGeom
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+			model_add_geom(partChecksum);
+		}
+		return true;
+
+		case 0x681a03af:		// ModelHideGeom
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+		
+			int hidden;
+			pParams->GetInteger( NONAME, &hidden, Script::ASSERT );
+
+			model_hide_geom(partChecksum, hidden);
+		}
+		return true;
+
+		case 0xaee6e915:		// GeomModulateColor
+		{
+			geom_modulate_color( pParams );
+		}
+		return true;
+
+		case 0x18924da1:		// GeomSetUVOffset
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+			
+			uint32 material;
+			pParams->GetChecksum( CRCD(0x83418a6a,"material"), &material, Script::ASSERT );
+
+			int pass;
+			pParams->GetInteger( CRCD(0x318f2bdb,"pass"), &pass, Script::ASSERT );
+
+			geom_set_uv_offset(partChecksum, material, pass);
+		}
+		return true;
+
+		case 0x3bf41299:		// GeomAllocateUVMatrixParams
+		{
+			uint32 material;
+			pParams->GetChecksum( CRCD(0x83418a6a,"material"), &material, Script::ASSERT );
+
+			int pass;
+			pParams->GetInteger( CRCD(0x318f2bdb,"pass"), &pass, Script::ASSERT );
+
+			geom_allocate_uv_matrix_params(material, pass);
+		}
+		return true;
+
+		case 0xaa42a104:		// GeomReplaceTexture
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+			geom_replace_texture(partChecksum);
+		}
+		return true;
+		
+		case 0x9041e901:		// ModelRemovePolys
+		{
+			model_remove_polys();
+		}
+		return true;
+
+		case 0x50f1285b:		// ModelApplyObjectScale
+		{
+			Script::CStruct* pBodyShapeStructure = NULL;
+			if ( pParams->GetStructure( vCHECKSUM_BODY_SHAPE, &pBodyShapeStructure, false ) )
+			{
+				Mth::Vector vec = mp_model->GetScale();
+				
+				Mth::Vector theScale( 1.0f, 1.0f, 1.0f, 1.0f );
+				if ( Gfx::GetScaleFromParams( &theScale, pBodyShapeStructure ) )
+				{
+					vec[X] *= theScale[X];
+					vec[Y] *= theScale[Y];
+					vec[Z] *= theScale[Z];
+					vec[W] *= theScale[W];
+
+					// if the body shape has a scale
+					mp_model->SetScale( vec );
+				}
+			}
+			else
+			{
+				model_apply_object_scale();
+			}
+		}
+		return true;
+		
+		case 0x21aec583:		// ModelApplyBoneScale
+		{
+			if ( pParams->ContainsComponentNamed( CRCD(0x3f5f5bc2,"bone_scaling") ) )
+			{
+				uint32 partChecksum;
+				pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+				model_apply_bone_scale( partChecksum );
+			}
+		}
+		return true;
+		
+		case 0xc0bc8271:		// ModelApplyBodyShape
+		{
+			Script::CStruct* pBodyShapeStructure = NULL;
+			if ( pParams->GetStructure( vCHECKSUM_BODY_SHAPE, &pBodyShapeStructure, false ) )
+			{
+				// GJ:  PATCH TO FIX STEVE CABALLERO'S INCORRECT WEIGHTING IN BIGHEAD MODE
+				Script::CStruct* pActualStruct = mp_appearance->GetActualDescStructure( CRCD(0x650fab6d,"skater_m_head") );
+				if ( pActualStruct && pParams->ContainsFlag( CRCD(0x3c4d849d,"is_bighead_cheat") ) ) 
+				{
+				   pActualStruct->GetStructure( CRCD(0x1ade0dd8,"bighead_scale_info"), &pBodyShapeStructure );
+				}
+
+				// if a body shape was explicitly specified
+				// (for kid and gorilla modes)
+				if ( mp_skeleton )
+				{
+					return mp_skeleton->ApplyBoneScale( pBodyShapeStructure );
+				}
+	        }
+			else
+			{
+				model_apply_body_shape();
+			}
+		}
+		return true;
+
+		case 0x73ab02bc:		// ModelApplyFaceTexture
+		{
+#ifdef __PLAT_NGPS__
+			bool success = true;
+
+			Gfx::CFaceTexture* pFaceTexture = mp_appearance->GetFaceTexture() ;
+												   
+			if ( pFaceTexture && pFaceTexture->IsValid() )
+			{
+				if ( mp_model )
+				{
+					const char* pSrcTexture;
+					pParams->GetText( CRCD(0x9fbbdb72,"src"), &pSrcTexture, Script::ASSERT );
+
+					// by default, it searches globally in the model for the correct texture
+					uint32 partChecksumToReplace = Nx::CModel::vREPLACE_GLOBALLY;
+					pParams->GetChecksum( CRCD(0xa01371b1,"in"), &partChecksumToReplace, Script::NO_ASSERT );
+                    
+					success = mp_model->ApplyFaceTexture( pFaceTexture, pSrcTexture, partChecksumToReplace );
+				}
+			}
+			
+			return success;
+#else
+			// X-box and NGC don't have to implement these
+			// because they don't do face textures...
+			return true;
+#endif
+		}
+
+		case 0x6516b484:		// AppearanceAllowScalingCheat
+		{
+			Script::CStruct* pActualStruct = mp_appearance->GetActualDescStructure( vCHECKSUM_BODY );
+			if ( pActualStruct ) 
+			{
+				return !pActualStruct->ContainsFlag( vCHECKSUM_NO_SCALING_ALLOWED );
+			}
+
+			return true;
+		}
+		break;
+
+		case 0xcdeea636:		// ModelResetScale
+		{
+			model_reset_scale();
+		}
+		return true;
+
+		case 0xbff957a9:		// ModelClearGeom
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+			model_clear_geom( partChecksum );
+		}
+		return true;
+
+		case 0x0ecf0248:		// ModelClearAllGeoms
+		{
+			model_clear_all_geoms();
+		}
+		return true;
+
+		case 0xb1c39a54:		// ModelRunScript
+		{
+			uint32 partChecksum;
+			pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+			
+			uint32 targetChecksum;
+			pParams->GetChecksum( vCHECKSUM_TARGET, &targetChecksum, Script::ASSERT );
+
+			// runs an embedded script
+			model_run_script(partChecksum, targetChecksum);
+		}
+		return true;
+
+		case 0xcefe8478:		// ModelFinalize
+		{
+			model_finalize();
+		}
+		return true;
+	}
+
+	return Obj::CObject::CallMemberFunction( checksum, pParams, pScript );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_hide_geom( uint32 partChecksum, bool hidden )
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+/*
+	if ( !Script::GetArray( partChecksum, Script::NO_ASSERT ) )
+	{
+		// no array involved
+		return false;
+	}
+*/
+
+	mp_model->HideGeom( partChecksum, hidden );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_add_geom( uint32 partChecksum )
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	Script::CStruct* pActualStruct = mp_appearance->GetActualDescStructure( partChecksum );
+	if ( pActualStruct ) 
+	{
+		int supports_multiple_colors = 0;
+		pActualStruct->GetInteger( CRCD(0x92b43e79,"multicolor"), &supports_multiple_colors, Script::NO_ASSERT );
+
+#ifdef __PLAT_NGPS__
+		Gfx::CFaceTexture* pFaceTexture = mp_appearance->GetFaceTexture() ;
+
+		// GJ:  THPS5 KLUDGE
+		// WillEventuallyHaveFaceTexture is a flag used in net games...
+		// this is needed because there is no face texture pointer
+		// in net games (because it's handled in a separate net packet)...
+
+		if ( ( pFaceTexture && pFaceTexture->IsValid() ) || ( mp_appearance->WillEventuallyHaveFaceTexture() ) )
+		{
+			// if the special parameter doesn't exist, then
+			// fall through to the normal mesh-loading code...
+			
+			const char* pMeshName;
+			if ( pActualStruct->GetString( CRCD(0xc9aed80f,"mesh_if_facemapped"), &pMeshName, Script::NO_ASSERT ) )
+			{
+				mp_model->AddGeom( pMeshName, partChecksum, m_useAssetManager, m_texDictOffset, false, supports_multiple_colors );
+				return true;
+			}
+		}
+#endif
+		{
+			const char* pMeshName;
+			if ( pActualStruct->GetString( vCHECKSUM_MESH, &pMeshName, Script::NO_ASSERT ) )
+			{
+				mp_model->AddGeom( pMeshName, partChecksum, m_useAssetManager, m_texDictOffset, false, supports_multiple_colors );
+			}
+			if ( pActualStruct->GetString( vCHECKSUM_MESH1, &pMeshName, Script::NO_ASSERT ) )
+			{
+				mp_model->AddGeom( pMeshName, partChecksum, m_useAssetManager, m_texDictOffset, false, supports_multiple_colors );
+			}
+			if ( pActualStruct->GetString( vCHECKSUM_MESH2, &pMeshName, Script::NO_ASSERT ) )
+			{
+				mp_model->AddGeom( pMeshName, partChecksum, m_useAssetManager, m_texDictOffset, false, supports_multiple_colors );
+			}
+			if ( pActualStruct->GetString( vCHECKSUM_MESH3, &pMeshName, Script::NO_ASSERT ) )
+			{
+				mp_model->AddGeom( pMeshName, partChecksum, m_useAssetManager, m_texDictOffset, false, supports_multiple_colors );
+			}
+			if ( pActualStruct->GetString( vCHECKSUM_MESH4, &pMeshName, Script::NO_ASSERT ) )
+			{
+				mp_model->AddGeom( pMeshName, partChecksum, m_useAssetManager, m_texDictOffset, false, supports_multiple_colors );
+			}
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_clear_geom( uint32 partChecksum )
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	// it's destructive, so make sure you're not 
+	// doing it to the "real" appearance...
+	mp_appearance->GetStructure()->RemoveComponent( partChecksum );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_clear_all_geoms( void )
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	mp_model->ClearGeoms();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_reset_scale()
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_skeleton );
+	Dbg_Assert( mp_appearance );
+
+	Mth::Vector theBoneScaleVector( 1.0f, 1.0f, 1.0f, 1.0f );
+	mp_model->SetScale( theBoneScaleVector );
+	
+	if ( mp_skeleton )
+	{
+		mp_skeleton->ResetScale();
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_apply_body_shape()
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_skeleton );
+	Dbg_Assert( mp_appearance );
+
+	Script::CStruct* pStructure = mp_appearance->GetStructure();
+	Dbg_Assert( pStructure );
+	
+	Script::CStruct* pBodyShapeStructure = NULL;
+	if ( pStructure->GetStructure( vCHECKSUM_BODY_SHAPE, &pBodyShapeStructure, false ) )
+	{
+		return mp_skeleton->ApplyBoneScale( pBodyShapeStructure );
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_apply_bone_scale( uint32 partChecksum )
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_skeleton );
+	Dbg_Assert( mp_appearance );
+	
+	Script::CStruct* pVirtualStruct = mp_appearance->GetVirtualDescStructure( partChecksum );
+	if ( pVirtualStruct ) 
+	{
+		if ( !pVirtualStruct->ContainsComponentNamed( vCHECKSUM_USE_DEFAULT_SCALE ) )
+		{
+			return false;
+		}
+
+		int use_default_scale;
+		pVirtualStruct->GetInteger( vCHECKSUM_USE_DEFAULT_SCALE, &use_default_scale );
+	
+		if ( use_default_scale )
+		{
+			return false;
+		}
+
+		int x, y, z;
+		if ( pVirtualStruct->GetInteger( vCHECKSUM_X, &x, false )
+			 && pVirtualStruct->GetInteger( vCHECKSUM_Y, &y, false )
+			 && pVirtualStruct->GetInteger( vCHECKSUM_Z, &z, false ) )
+		{
+			Script::CArray* pBoneGroupArray = Script::GetArray( partChecksum, Script::NO_ASSERT );
+			if ( pBoneGroupArray )
+			{
+				Mth::Vector theBoneScaleVector;
+//				theBoneScaleVector[X] = (float)x / 100.0f;
+//				theBoneScaleVector[Y] = (float)y / 100.0f;
+//				theBoneScaleVector[Z] = (float)z / 100.0f;
+				theBoneScaleVector[W] = 1.0f;
+
+				for ( uint32 i = 0; i < pBoneGroupArray->GetSize(); i++ )
+				{
+					uint32 boneChecksum = pBoneGroupArray->GetChecksum(i);
+
+					// get the existing scale...
+					mp_skeleton->GetBoneScale( boneChecksum, &theBoneScaleVector );
+
+					// then combine it with the new desired bone scale...
+					theBoneScaleVector[X] *= ( (float)x / 100.0f );
+					theBoneScaleVector[Y] *= ( (float)y / 100.0f );
+					theBoneScaleVector[Z] *= ( (float)z / 100.0f );
+
+					bool isLocalScale = true;
+
+					// GJ:  The shoulder bones are handled slightly differently
+					// so that the female skaters aren't so broad-shouldered.
+					Script::CArray* pShoulderScalingArray = Script::GetArray( CRCD(0x20d9ac2f,"nonlocal_bones"), Script::NO_ASSERT );
+					if ( pShoulderScalingArray )
+					{
+						for ( uint32 i = 0; i < pShoulderScalingArray->GetSize(); i++ )
+						{
+							if ( pShoulderScalingArray->GetChecksum(i) == boneChecksum )
+							{
+								isLocalScale = false;
+							}
+						}
+					}
+
+					mp_skeleton->SetBoneScale( boneChecksum, theBoneScaleVector, isLocalScale );
+				}
+			}
+		}
+	}
+		
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_apply_object_scale()
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	Mth::Vector theScale( 1.0f, 1.0f, 1.0f );
+	if ( Gfx::GetScaleFromParams( &theScale, mp_appearance->GetStructure() ) )
+	{
+		mp_model->SetScale( theScale );
+		return true;
+	}
+
+	Script::CStruct* pStructure = mp_appearance->GetStructure();
+	Dbg_Assert( pStructure );
+	
+	// sometimes the object scale can be found in the bone params
+	Script::CStruct* pBodyShapeStructure = NULL;
+	if ( pStructure->GetStructure( vCHECKSUM_BODY_SHAPE, &pBodyShapeStructure, false ) )
+	{
+		if ( Gfx::GetScaleFromParams( &theScale, pBodyShapeStructure ) )
+		{
+			mp_model->SetScale( theScale );
+			return true;
+		}
+	}
+
+	Script::CStruct* pVirtualStruct = mp_appearance->GetVirtualDescStructure( CRCD(0x8b314358,"object_scaling") );
+	if ( pVirtualStruct ) 
+	{
+		if ( !pVirtualStruct->ContainsComponentNamed( vCHECKSUM_USE_DEFAULT_SCALE ) )
+		{
+			return false;
+		}  	
+
+		int use_default_scale;
+		pVirtualStruct->GetInteger( vCHECKSUM_USE_DEFAULT_SCALE, &use_default_scale );
+		
+		if ( use_default_scale )
+		{
+			return false;
+		}
+
+		int x, y, z;
+		if ( pVirtualStruct->GetInteger( vCHECKSUM_X, &x, false )
+			 && pVirtualStruct->GetInteger( vCHECKSUM_Y, &y, false )
+			 && pVirtualStruct->GetInteger( vCHECKSUM_Z, &z, false ) )
+		{
+			Mth::Vector theBoneScaleVector;
+			theBoneScaleVector[X] = (float)x / 100.0f;
+			theBoneScaleVector[Y] = (float)y / 100.0f;
+			theBoneScaleVector[Z] = (float)z / 100.0f;
+			theBoneScaleVector[W] = 1.0f;
+
+			mp_model->SetScale( theBoneScaleVector );
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_remove_polys()
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	mp_model->RemovePolys();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::geom_modulate_color( Script::CStruct* pParams )
+{
+	uint32 partChecksum;
+	pParams->GetChecksum( vCHECKSUM_PART, &partChecksum, Script::ASSERT );
+			
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	Script::CStruct* pVirtualStruct = mp_appearance->GetVirtualDescStructure( partChecksum );
+	if ( pVirtualStruct ) 
+	{
+		int use_default_hsv = 0;
+
+		if ( !pVirtualStruct->ContainsComponentNamed( vCHECKSUM_USE_DEFAULT_HSV ) )
+		{
+			use_default_hsv = 1;
+		}
+		else
+		{
+			pVirtualStruct->GetInteger( vCHECKSUM_USE_DEFAULT_HSV, &use_default_hsv );
+		}
+	
+		if ( use_default_hsv )
+		{
+			Script::CStruct* pActualStruct = mp_appearance->GetActualDescStructure( partChecksum );
+			int h, s, v;
+			if ( pActualStruct
+				 && pActualStruct->GetInteger( CRCD(0xc6cc7f7f,"default_h"), &h, Script::NO_ASSERT )
+				 && pActualStruct->GetInteger( CRCD(0x4ca9b693,"default_s"), &s, Script::NO_ASSERT )
+				 && pActualStruct->GetInteger( CRCD(0x3cc3421c,"default_v"), &v, Script::NO_ASSERT ) )
+			{
+				// set the default color that's specified in the actual structure
+				mp_model->SetColor( pParams, h, (float)s / 100.0f, (float)v / 100.0f );
+			}
+			else
+			{
+				// otherwise, clear out the color   
+				mp_model->ClearColor( pParams );
+			}
+			
+			return false;
+		}
+		else
+		{
+			int h, s, v;
+			if ( pVirtualStruct->GetInteger( vCHECKSUM_HUE, &h, false )
+				 && pVirtualStruct->GetInteger( vCHECKSUM_SATURATION, &s, false )
+				 && pVirtualStruct->GetInteger( vCHECKSUM_VALUE, &v, false ) )
+			{
+				// set the non-default color that's specified in the virtual structure
+				mp_model->SetColor( pParams, h, (float)s / 100.0f, (float)v / 100.0f );
+			}
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::geom_allocate_uv_matrix_params( uint32 matChecksum, int pass )
+{
+	Dbg_Assert(mp_model);
+	mp_model->AllocateUVMatrixParams(matChecksum, pass);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::geom_set_uv_offset( uint32 partChecksum, uint32 matChecksum, int pass )
+{
+#if 0
+// debugging
+	if ( mp_appearance->GetActualDescStructure( Script::GenerateCRC("skater_m_head") ) )
+	{
+		mp_model->SetMaterialColor(matChecksum, pass, Image::RGBA(0,0,128,128));
+	}
+#endif	
+	Script::CStruct* pVirtualStruct = mp_appearance->GetVirtualDescStructure( partChecksum );
+	if ( pVirtualStruct ) 
+	{
+		if ( !pVirtualStruct->ContainsComponentNamed( CRCD(0x8602f6ee,"use_default_uv") ) )
+		{
+			return false;
+		}
+		
+		int use_default_uv;
+		pVirtualStruct->GetInteger( CRCD(0x8602f6ee,"use_default_uv"), &use_default_uv );
+	
+		if ( use_default_uv )
+		{
+// 			(do nothing;  keep the default UVs)
+//			(assumes that we're starting from an unmodified model)
+			return false;
+		}
+
+		float u_offset;
+        pVirtualStruct->GetFloat(CRCD(0xcf6aa087,"uv_u"), &u_offset, Script::ASSERT);
+		
+		float v_offset;
+		pVirtualStruct->GetFloat(CRCD(0x5663f13d,"uv_v"), &v_offset, Script::ASSERT);
+		
+		float uv_scale;
+		pVirtualStruct->GetFloat(CRCD(0x266932c8,"uv_scale"), &uv_scale, Script::ASSERT);
+		
+		float uv_rot;
+		pVirtualStruct->GetFloat(CRCD(0x1256b6c6,"uv_rot"), &uv_rot, Script::ASSERT);
+		
+		// GJ:  seems if you want a bigger image, you need smaller UV values...
+		// is that right?
+
+		Mth::Matrix theMat;
+		theMat.Ident();
+		theMat.Translate( Mth::Vector( -0.5f, -0.5f, 0.0f, 0.0f ) );
+		theMat.Scale( Mth::Vector(uv_scale / 100.0f, uv_scale / 100.0f, uv_scale / 100.0f, 1.0f ) );
+		theMat.Rotate( Mth::Vector( 0.0f, 0.0f, 1.0f, 1.0f ), Mth::DegToRad(uv_rot) );  // in degrees
+		theMat.Translate( Mth::Vector( 0.5f, 0.5f, 0.0f, 0.0f ) );
+		
+		theMat.TranslateLocal( Mth::Vector( u_offset / 100.0f, v_offset / 100.0f, 0.0f, 0.0f ) );
+		
+		Dbg_Assert(mp_model);
+		mp_model->SetUVMatrix(matChecksum, pass, theMat);
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::geom_replace_texture( uint32 partChecksum )
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	bool success = false;
+
+/*
+	if ( !Script::GetArray( partChecksum, Script::NO_ASSERT ) )
+	{
+		// no array involved
+		return false;
+	}
+*/
+
+	Script::CStruct* pActualStruct = mp_appearance->GetActualDescStructure( partChecksum );
+	if ( pActualStruct ) 
+	{
+		const char* pSrcTexture;
+		const char* pDestTexture;
+		
+		if ( pActualStruct->GetText( vCHECKSUM_REPLACE, &pSrcTexture, Script::NO_ASSERT ) )
+		{
+			pActualStruct->GetText( vCHECKSUM_WITH, &pDestTexture, Script::ASSERT );
+
+			uint32 partChecksumToReplace = partChecksum;
+			pActualStruct->GetChecksum( vCHECKSUM_IN, &partChecksumToReplace, Script::NO_ASSERT );
+			success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDestTexture );
+		}
+		if ( pActualStruct->GetText( vCHECKSUM_REPLACE1, &pSrcTexture, Script::NO_ASSERT ) )
+		{
+			pActualStruct->GetText( vCHECKSUM_WITH1, &pDestTexture, Script::ASSERT );
+
+			uint32 partChecksumToReplace = partChecksum;
+			pActualStruct->GetChecksum( vCHECKSUM_IN1, &partChecksumToReplace, Script::NO_ASSERT );
+			success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDestTexture );
+		}
+		if ( pActualStruct->GetText( vCHECKSUM_REPLACE2, &pSrcTexture, Script::NO_ASSERT ) )
+		{
+			pActualStruct->GetText( vCHECKSUM_WITH2, &pDestTexture, Script::ASSERT );
+
+			uint32 partChecksumToReplace = partChecksum;
+			pActualStruct->GetChecksum( vCHECKSUM_IN2, &partChecksumToReplace, Script::NO_ASSERT );
+			success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDestTexture );
+		}
+		if ( pActualStruct->GetText( vCHECKSUM_REPLACE3, &pSrcTexture, Script::NO_ASSERT ) )
+		{
+			pActualStruct->GetText( vCHECKSUM_WITH3, &pDestTexture, Script::ASSERT );
+
+			uint32 partChecksumToReplace = partChecksum;
+			pActualStruct->GetChecksum( vCHECKSUM_IN3, &partChecksumToReplace, Script::NO_ASSERT );
+			success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDestTexture );
+		}
+		if ( pActualStruct->GetText( vCHECKSUM_REPLACE4, &pSrcTexture, Script::NO_ASSERT ) )
+		{
+			pActualStruct->GetText( vCHECKSUM_WITH4, &pDestTexture, Script::ASSERT );
+
+			uint32 partChecksumToReplace = partChecksum;
+			pActualStruct->GetChecksum( vCHECKSUM_IN4, &partChecksumToReplace, Script::NO_ASSERT );
+			success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDestTexture );
+		}
+		if ( pActualStruct->GetText( vCHECKSUM_REPLACE5, &pSrcTexture, Script::NO_ASSERT ) )
+		{
+			pActualStruct->GetText( vCHECKSUM_WITH5, &pDestTexture, Script::ASSERT );
+
+			uint32 partChecksumToReplace = partChecksum;
+			pActualStruct->GetChecksum( vCHECKSUM_IN5, &partChecksumToReplace, Script::NO_ASSERT );
+			success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDestTexture );
+		}
+		if ( pActualStruct->GetText( vCHECKSUM_REPLACE6, &pSrcTexture, Script::NO_ASSERT ) )
+		{
+			pActualStruct->GetText( vCHECKSUM_WITH6, &pDestTexture, Script::ASSERT );
+
+			uint32 partChecksumToReplace = partChecksum;
+			pActualStruct->GetChecksum( vCHECKSUM_IN6, &partChecksumToReplace, Script::NO_ASSERT );
+			success = mp_model->ReplaceTexture( partChecksumToReplace, pSrcTexture, pDestTexture );
+		}
+	}
+	
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_finalize()
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+	mp_model->Finalize();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelBuilder::model_run_script( uint32 partChecksum, uint32 targetChecksum )
+{
+	Dbg_Assert( mp_model );
+	Dbg_Assert( mp_appearance );
+
+/*
+	if ( !Script::GetArray( partChecksum, Script::NO_ASSERT ) )
+	{
+		// no array involved
+		return false;
+	}
+*/
+
+	Script::CStruct* pActualStruct = mp_appearance->GetActualDescStructure( partChecksum );
+	if ( pActualStruct ) 
+	{
+		Script::SStructScript* pStructScript = new Script::SStructScript;
+		if ( pActualStruct->GetScript( targetChecksum, pStructScript, Script::NO_ASSERT ) )
+		{
+			// construct a script and run it
+			Script::CScript* pScript = new Script::CScript;
+			pScript->SetScript( pStructScript, NULL, mp_appearance );	
+			#ifdef __NOPT_ASSERT__
+			pScript->SetCommentString("Created in CModelBuilder::model_run_script(...)");
+			#endif
+			int ret_val;
+			while ((ret_val = pScript->Update()) != Script::ESCRIPTRETURNVAL_FINISHED)
+			{
+				// Script must not get blocked, otherwise it'll hang in this loop forever.
+				Dbg_MsgAssert(ret_val != Script::ESCRIPTRETURNVAL_BLOCKED,("\n%s\nScript got blocked when being run by RunScript.",pScript->GetScriptInfo()));
+			}
+			delete pScript;
+		}
+		delete pStructScript;
+	}
+	
+	return true;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
+#if 0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelScalingModifier::ApplyModifier( Model* pModel, CModelAppearance* pAppearance )
+{
+	Dbg_Assert( pModel );
+	Dbg_Assert( pAppearance );
+
+	// Enforce that it's a skin model, for now...
+	// TODO:  Move some skin model functionality into model class...
+	Model* pModel = static_cast ( pModel );
+	if ( !pModel )
+	{
+		return false;
+	}
+
+	Mth::Vector vec = pModel->GetScale();
+
+	// always reset the guy when scaling
+	vec[X] = 1.0f;
+	vec[Y] = 1.0f;
+	vec[Z] = 1.0f;
+	
+	float heightScale = pAppearance->GetFloat( vCHECKSUM_HEIGHT_SCALE );
+	
+	vec.Scale( heightScale );
+
+	pModel->SetScale( vec );
+
+	// reset scaling mode...  for the most part,
+	// it will be apply_normal_mode
+	// unless someone overrides it in script
+	uint32 scalingMode = vCHECKSUM_APPLY_NORMAL_MODE;
+
+	pAppearance->GetChecksum( vCHECKSUM_SCALING_MODE, &scalingMode );
+
+	// apply the desired scaling script here...
+//	Dbg_Message( "Applying %s scaling mode\n", Script::FindChecksumName( scalingMode ) );
+	Script::RunScript( scalingMode, NULL, pModel );
+
+	pModel->ApplyScaling();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModelWeightModifier::ApplyModifier( Model* pModel, CModelAppearance* pAppearance )
+{
+	Dbg_Assert( pModel );
+	Dbg_Assert( pAppearance );
+
+	// Enforce that it's a skin model, for now...
+	// TODO:  Move some skin model functionality into model class...
+	Model* pModel = static_cast ( pModel );
+	if ( !pModel )
+	{
+		return false;
+	}
+
+	// should automatically be reset to identity...
+	CScalingTable scaling_table;
+
+	Script::CScriptStructure* pTempStructure = new Script::CScriptStructure;
+	float weight = pAppearance->GetFloat( "weight_scale" );
+	scaling_table.SetWeightScale( weight );
+	delete pTempStructure;
+
+	pModel->ApplyScaling( &scaling_table );
+	
+	return true;
+}
+#endif
diff --git a/Code/Gfx/ModelBuilder.h b/Code/Gfx/ModelBuilder.h
new file mode 100644
index 0000000..1cbcfef
--- /dev/null
+++ b/Code/Gfx/ModelBuilder.h
@@ -0,0 +1,108 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       ModelBuilder.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/16/2001
+//****************************************************************************
+
+#ifndef __GFX_MODELBUILDER_H
+#define __GFX_MODELBUILDER_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Nx
+{
+    class CModel;
+};
+                
+namespace Gfx
+{
+	class CModelAppearance;
+	class CSkeleton;
+	
+	const uint32 vCHECKSUM_CREATE_MODEL_FROM_APPEARANCE = 0x91c1ec93;	// create_model_from_appearance
+	const uint32 vCHECKSUM_COLOR_MODEL_FROM_APPEARANCE = 0xd2c041c2;	// color_model_from_appearance
+	const uint32 vCHECKSUM_SCALE_MODEL_FROM_APPEARANCE = 0xaee1af7d;	// scale_model_from_appearance
+	const uint32 vCHECKSUM_HIDE_MODEL_FROM_APPEARANCE = 0xb1a55798;		// hide_model_from_appearance
+
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class CModelBuilder	: public Obj::CObject
+{
+	public:
+		CModelBuilder( bool useAssetManager, uint32 texDictOffset );
+
+	public:
+		virtual void	BuildModel( CModelAppearance* pAppearance, Nx::CModel* pModel, Gfx::CSkeleton* pSkeleton, uint32 scriptName = vCHECKSUM_CREATE_MODEL_FROM_APPEARANCE );
+		virtual bool	CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+	protected:
+		virtual bool	model_add_geom( uint32 partChecksum );
+		virtual bool	model_clear_geom( uint32 partChecksum );
+		virtual bool	model_hide_geom( uint32 partChecksum, bool hidden );
+		virtual bool	geom_modulate_color( Script::CStruct* pParams );
+		virtual bool	geom_set_uv_offset( uint32 partChecksum, uint32 matChecksum, int pass );
+		virtual bool	geom_allocate_uv_matrix_params( uint32 matChecksum, int pass );
+		virtual bool	geom_replace_texture( uint32 partChecksum );
+		virtual bool	model_clear_all_geoms();
+		virtual bool	model_remove_polys();
+		virtual bool	model_reset_scale();
+		virtual bool	model_apply_body_shape();
+		virtual bool	model_apply_bone_scale( uint32 partChecksum );
+		virtual bool	model_apply_object_scale();
+		virtual bool	model_finalize();
+		virtual bool	model_run_script( uint32 partChecksum, uint32 targetChecksum );
+		
+	protected:
+		// temporary pointer to the model, so that CallMemberFunctions
+		// can operate on the Nx::CModel.  This only exists for the
+		// duration of the BuildModel() function call.
+		static Nx::CModel*			mp_model;
+		static Gfx::CSkeleton*		mp_skeleton;
+		static CModelAppearance*	mp_appearance;
+		bool						m_useAssetManager;
+		uint32						m_texDictOffset;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __GFX_MODELBUILDER_H
diff --git a/Code/Gfx/NGC/NX/anim.cpp b/Code/Gfx/NGC/NX/anim.cpp
new file mode 100644
index 0000000..3df6301
--- /dev/null
+++ b/Code/Gfx/NGC/NX/anim.cpp
@@ -0,0 +1,268 @@
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "nx_init.h"
+#include "mesh.h"
+#include "scene.h"
+#include "anim.h"
+
+namespace NxNgc
+{
+//	// Material color attenuation.
+//	static DWORD WeightedMeshVertexShader0Decl[] = {
+//	D3DVSD_STREAM( 0 ),
+//	D3DVSD_REG( VSD_REG_POS,		D3DVSDT_FLOAT3 ),	// Position.
+//	D3DVSD_REG( VSD_REG_WEIGHTS,	D3DVSDT_FLOAT4 ),	// Weights.
+//	D3DVSD_REG( VSD_REG_INDICES,	D3DVSDT_SHORT4 ),	// Indices.
+//	D3DVSD_REG( VSD_REG_NORMAL,		D3DVSDT_FLOAT3 ),	// Normals.
+//	D3DVSD_REG( VSD_REG_TEXCOORDS,	D3DVSDT_FLOAT2 ),	// Texture coordinates.
+//	D3DVSD_END() };
+//
+//	// Vertex color attenuation.
+//	static DWORD WeightedMeshVertexShader1Decl[] = {
+//	D3DVSD_STREAM( 0 ),
+//	D3DVSD_REG( VSD_REG_POS,		D3DVSDT_FLOAT3 ),	// Position.
+//	D3DVSD_REG( VSD_REG_WEIGHTS,	D3DVSDT_FLOAT4 ),	// Weights.
+//	D3DVSD_REG( VSD_REG_INDICES,	D3DVSDT_SHORT4 ),	// Indices.
+//	D3DVSD_REG( VSD_REG_NORMAL,		D3DVSDT_FLOAT3 ),	// Normals.
+//	D3DVSD_REG( VSD_REG_COLOR,		D3DVSDT_D3DCOLOR ),	// Diffuse color.
+//	D3DVSD_REG( VSD_REG_TEXCOORDS,	D3DVSDT_FLOAT2 ),	// Texture coordinates.
+//	D3DVSD_END() };
+//
+//
+//
+//DWORD WeightedMeshVertexShader0;
+//DWORD WeightedMeshVertexShader1;
+//
+//
+////DWORD GetVertexShader( bool vertex_colors )
+////{
+////	if( vertex_colors )
+////	{
+////		return WeightedMeshVertexShader1;
+////	}
+////
+////	return WeightedMeshVertexShader0;
+////}
+//
+//
+
+//void CreateWeightedMeshVertexShaders( void )
+//{
+//	static bool created_shaders = false;
+//
+//	if( !created_shaders )
+//	{
+//		created_shaders = true;
+//
+//		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShader0Decl,
+//													dwWeightedmeshvertexshader0VertexShader,	// Defined in the header file from xsasm.
+//													&WeightedMeshVertexShader0,
+//													0 ))
+//		{
+//			exit( 0 );
+//		}
+//
+//		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShader1Decl,
+//													dwWeightedmeshvertexshader1VertexShader,	// Defined in the header file from xsasm.
+//													&WeightedMeshVertexShader1,
+//													0 ))
+//		{
+//			exit( 0 );
+//		}
+//	}
+//}
+
+
+
+
+//static void MatrixInvertOrthoNormalized( D3DXMATRIX *matrix, const D3DXMATRIX *matrixIn )
+//{
+//    /*
+//     * Inverse of upper left 3x3 sub matrix
+//     * is a simple transpose
+//     */
+////    matrix->right.x = matrixIn->right.x;
+////    matrix->right.y = matrixIn->up.x;
+////    matrix->right.z = matrixIn->at.x;
+////    matrix->up.x = matrixIn->right.y;
+////    matrix->up.y = matrixIn->up.y;
+////    matrix->up.z = matrixIn->at.y;
+////    matrix->at.x = matrixIn->right.z;
+////    matrix->at.y = matrixIn->up.z;
+////    matrix->at.z = matrixIn->at.z;
+//	matrix->m[0][0] = matrixIn->m[0][0];
+//	matrix->m[0][1] = matrixIn->m[1][0];
+//	matrix->m[0][2] = matrixIn->m[2][0];
+//	matrix->m[0][3] = 0.0f;
+//	matrix->m[1][0] = matrixIn->m[0][1];
+//	matrix->m[1][1] = matrixIn->m[1][1];
+//	matrix->m[1][2] = matrixIn->m[2][1];
+//	matrix->m[1][3] = 0.0f;
+//	matrix->m[2][0] = matrixIn->m[0][2];
+//	matrix->m[2][1] = matrixIn->m[1][2];
+//	matrix->m[2][2] = matrixIn->m[2][2];
+//	matrix->m[2][3] = 0.0f;
+//
+//    /*
+//     * calculate translation componennt of inverse
+//     */
+////    matrix->pos.x = -RwV3dDotProductMacro(&matrixIn->pos, &matrixIn->right);
+////    matrix->pos.y = -RwV3dDotProductMacro(&matrixIn->pos, &matrixIn->up);
+////    matrix->pos.z = -RwV3dDotProductMacro(&matrixIn->pos, &matrixIn->at);
+//    matrix->m[3][0] = -(( matrixIn->m[3][0] * matrixIn->m[0][0] ) + ( matrixIn->m[3][1] * matrixIn->m[0][1] ) + ( matrixIn->m[3][2] * matrixIn->m[0][2] ));
+//    matrix->m[3][1] = -(( matrixIn->m[3][0] * matrixIn->m[1][0] ) + ( matrixIn->m[3][1] * matrixIn->m[1][1] ) + ( matrixIn->m[3][2] * matrixIn->m[1][2] ));
+//    matrix->m[3][2] = -(( matrixIn->m[3][0] * matrixIn->m[2][0] ) + ( matrixIn->m[3][1] * matrixIn->m[2][1] ) + ( matrixIn->m[3][2] * matrixIn->m[2][2] ));
+//    matrix->m[3][3] = 1.0f;
+//}
+
+
+
+
+
+//void setup_weighted_mesh_vertex_shader( void *p_root_matrix, void *p_bone_matrices, int num_bone_matrices )
+//{
+//	D3DXMATRIX	dest_matrix;
+//	D3DXMATRIX	inverse_view_matrix;
+//	D3DXMATRIX	temp_matrix;
+//	D3DXMATRIX	projMatrix;
+//	D3DXMATRIX	viewMatrix;
+//	D3DXMATRIX	worldMatrix;
+//
+//	// Projection matrix.
+//	projMatrix.m[0][0] = EngineGlobals.projection_matrix.m[0][0];
+//    projMatrix.m[0][1] = EngineGlobals.projection_matrix.m[1][0];
+//    projMatrix.m[0][2] = EngineGlobals.projection_matrix.m[2][0];
+//    projMatrix.m[0][3] = EngineGlobals.projection_matrix.m[3][0];
+//    projMatrix.m[1][0] = EngineGlobals.projection_matrix.m[0][1];
+//	projMatrix.m[1][1] = EngineGlobals.projection_matrix.m[1][1];
+//    projMatrix.m[1][2] = EngineGlobals.projection_matrix.m[2][1];
+//    projMatrix.m[1][3] = EngineGlobals.projection_matrix.m[3][1];
+//
+//    projMatrix.m[2][0] = 0.0f;
+//    projMatrix.m[2][1] = 0.0f;
+////    projMatrix.m[2][2] = camera->farPlane / ( camera->farPlane - camera->nearPlane );
+//    projMatrix.m[2][2] = 20000.0f / ( 20000.0f - 2.0f );
+////	projMatrix.m[2][3] = -projMatrix.m[2][2] * camera->nearPlane;
+//	projMatrix.m[2][3] = -projMatrix.m[2][2] * 2.0f;
+//
+//    projMatrix.m[3][0] = 0.0f;
+//    projMatrix.m[3][1] = 0.0f;
+//    projMatrix.m[3][2] = 1.0f;
+//    projMatrix.m[3][3] = 0.0f;
+//
+//	// View matrix.
+//	D3DXMATRIX	view_matrix = EngineGlobals.view_matrix;
+//	
+//	viewMatrix.m[0][0] = -view_matrix.m[0][0];
+//    viewMatrix.m[0][1] = view_matrix.m[1][0];
+//    viewMatrix.m[0][2] = -view_matrix.m[2][0];
+//    viewMatrix.m[0][3] = view_matrix.m[3][0];
+//    viewMatrix.m[1][0] = -view_matrix.m[0][1];
+//    viewMatrix.m[1][1] = view_matrix.m[1][1];
+//    viewMatrix.m[1][2] = -view_matrix.m[2][1];
+//    viewMatrix.m[1][3] = view_matrix.m[3][1];
+//    viewMatrix.m[2][0] = view_matrix.m[0][2];
+//    viewMatrix.m[2][1] = -view_matrix.m[1][2];
+//    viewMatrix.m[2][2] = view_matrix.m[2][2];
+//    viewMatrix.m[2][3] = -view_matrix.m[3][2];
+//    viewMatrix.m[3][0] = 0.0f;
+//    viewMatrix.m[3][1] = 0.0f;
+//    viewMatrix.m[3][2] = 0.0f;
+//    viewMatrix.m[3][3] = 1.0f;
+//	
+//	// World space transformation matrix.
+//	worldMatrix.m[0][0] = ((float*)p_root_matrix )[0];
+//	worldMatrix.m[0][1] = ((float*)p_root_matrix )[1];
+//	worldMatrix.m[0][2] = ((float*)p_root_matrix )[2];
+//	worldMatrix.m[0][3] = ((float*)p_root_matrix )[3];
+//    worldMatrix.m[1][0] = ((float*)p_root_matrix )[4];
+//    worldMatrix.m[1][1] = ((float*)p_root_matrix )[5];
+//    worldMatrix.m[1][2] = ((float*)p_root_matrix )[6];
+//    worldMatrix.m[1][3] = ((float*)p_root_matrix )[7];
+//    worldMatrix.m[2][0] = ((float*)p_root_matrix )[8];
+//    worldMatrix.m[2][1] = ((float*)p_root_matrix )[9];
+//    worldMatrix.m[2][2] = ((float*)p_root_matrix )[10];
+//    worldMatrix.m[2][3] = ((float*)p_root_matrix )[11];
+//    worldMatrix.m[3][0] = 0.0f;
+//    worldMatrix.m[3][1] = 0.0f;
+//    worldMatrix.m[3][2] = 0.0f;
+//    worldMatrix.m[3][3] = 1.0f;
+//
+//	// Calculate composite world->view->projection matrix.
+//	D3DXMatrixMultiply( &temp_matrix, &viewMatrix, &worldMatrix );
+//	D3DXMatrixMultiply( &dest_matrix, &projMatrix, &temp_matrix );
+//
+//	// Switch to 192 constant mode, removing the lock on the reserved constants c-38 and c-37.
+//	D3DDevice_SetShaderConstantMode( D3DSCM_192CONSTANTS | D3DSCM_NORESERVEDCONSTANTS );
+//
+//	// Load up the combined world, camera & projection matrix.
+//	for( int i = 0; i < 16; ++i )
+//	{
+//		if( fabsf(((float*)dest_matrix.m )[i] ) < 0.001f )
+//		{
+//			((float*)dest_matrix.m )[i] = 0.0f;
+//		}
+//	}
+//	
+//	D3DDevice_SetVertexShaderConstant( VSCONST_REG_TRANSFORM_OFFSET, (void*)&dest_matrix, VSCONST_REG_TRANSFORM_SIZE );
+//	D3DDevice_SetVertexShaderConstant( VSCONST_REG_WORLD_TRANSFORM_OFFSET,	(void*)&worldMatrix, VSCONST_REG_WORLD_TRANSFORM_SIZE );
+//
+//	// Load up the directional light data.
+//	static float directional_light_color[24] = {
+//	0.0f, -1.0f, 0.0f, 0.0f,	// Dir 0
+//	0.7f, 0.6f, 0.5f, 1.0f,		// Col 0
+//	1.0f, 0.0f, 0.0f, 0.0f,		// Dir 1
+//	0.35f, 0.4f, 0.4f, 1.0f,		// Col 1
+//	0.0f, 0.0f, 1.0f, 0.0f,		// Dir 2
+//	0.3f, 0.3f, 0.2f, 1.0f };	// Col 2
+//	
+//	D3DDevice_SetVertexShaderConstant( VSCONST_REG_DIR_LIGHT_OFFSET,		(void*)directional_light_color, VSCONST_REG_DIR_LIGHT_SIZE );
+//
+//	// Load up the ambient light color.
+//	static float ambient_light_color[4] = { 0.4f, 0.4f, 0.4f, 1.0f };
+//	D3DDevice_SetVertexShaderConstant( VSCONST_REG_AMB_LIGHT_OFFSET, (void*)ambient_light_color, 1 );
+//	
+//	// Load up the material color.
+//	static float material_color[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
+//	D3DDevice_SetVertexShaderConstant( VSCONST_REG_MATERIAL_OFFSET, (void*)material_color, 1 );
+//
+//	// Load up the bone transforms, in sets of 40.
+//	int offset = 0;
+//	while( num_bone_matrices > 0 )
+//	{
+//		if( num_bone_matrices >= 10 )
+//		{
+//			D3DDevice_SetVertexShaderConstant( VSCONST_REG_MATRIX_OFFSET + offset, (float*)p_bone_matrices + ( offset * 4 ), 10 * 3 );
+//			offset += 10 * 3;
+//			num_bone_matrices -= 10;
+//		}
+//		else
+//		{
+//			D3DDevice_SetVertexShaderConstant( VSCONST_REG_MATRIX_OFFSET + offset, (float*)p_bone_matrices + ( offset * 4 ), num_bone_matrices * 3 );
+//			num_bone_matrices = 0;
+//		}
+//	}
+//
+//	// Load up the replacement registers for c-38 and c-37.
+//	static float homogenous_to_screen_reg[8] = { 320.0f, -240.0f, 1.67772e7f, 0.0f, 320.03125f, 240.03125f, 0.0f, 0.0f };
+//	D3DDevice_SetVertexShaderConstant( 94, (void*)homogenous_to_screen_reg, 2 );
+//}
+
+
+
+//void shutdown_weighted_mesh_vertex_shader( void )
+//{
+//	// Switch back to 96 constant mode.
+//	D3DDevice_SetShaderConstantMode( D3DSCM_96CONSTANTS );
+//}
+
+
+} // namespace NxNgc
\ No newline at end of file
diff --git a/Code/Gfx/NGC/NX/anim.h b/Code/Gfx/NGC/NX/anim.h
new file mode 100644
index 0000000..7fe692b
--- /dev/null
+++ b/Code/Gfx/NGC/NX/anim.h
@@ -0,0 +1,16 @@
+#ifndef __ANIM_H
+#define __ANIM_H
+
+namespace NxNgc
+{
+
+//DWORD	GetVertexShader( bool vertex_colors );
+//void	CreateWeightedMeshVertexShaders( void );
+//void	setup_weighted_mesh_vertex_shader( void *p_root_matrix, void *p_bone_matrices, int num_bone_matrices );
+//void	shutdown_weighted_mesh_vertex_shader( void );
+
+
+} // namespace NxNgc
+
+#endif // __ANIM_H
+
diff --git a/Code/Gfx/NGC/NX/chars.cpp b/Code/Gfx/NGC/NX/chars.cpp
new file mode 100644
index 0000000..d8dcb6a
--- /dev/null
+++ b/Code/Gfx/NGC/NX/chars.cpp
@@ -0,0 +1,689 @@
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "nx_init.h"
+#include "chars.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*
+
+
+	
+.fnt file format (by Ryan)
+--------------------------
+
+	4	File size in bytes
+	4	Number of characters
+	4	Default height
+	4	Default base
+	
+	?	Character table (see below)
+	?	Texture (see below)
+
+	Character
+	2	Baseline (how many pixels down relative to top of image)
+	2	Ascii value
+
+	Texture
+	4	Size of texture
+	2	Width
+	2	Height
+	2	Bit depth
+	6	Padding
+	W*H	Raw data
+	0-3	Padding for uint32 alignment
+	1K	Palette data
+	4	Number of subtextures
+	?	Subtexture table (see below)
+
+	Subtexture
+	2	X
+	2	Y
+	2	W
+	2	H
+	
+*/
+
+// This is the function generated by the makefile when building permanent font files.
+extern void * get_font_file_address( const char * p_filename );
+
+namespace NxNgc
+{
+
+
+float CurrentScale;
+uint32 FontVramBase;
+SFont *pFontList;
+SFont *pButtonsFont = NULL;
+
+float wx0 = 0.0f;
+float wy0 = 0.0f;
+float wx1 = 640.0f;
+float wy1 = 448.0f;
+
+SFont *SText::spOverrideFont = NULL;
+
+SFont *LoadFont( const char *Filename )
+{
+	SFont	*pFont;
+	SChar	*pChar;
+	uint8	*pData;
+//	int		i,Len,NumChars,Width,Height,Depth,NumBytes;
+	int		i,NumChars,Width,Height,Depth,NumBytes;
+
+	char * p8 = (char *)Filename;
+	while ( *p8 != '\0' )
+	{
+		if ( ( *p8 >= 'A' ) && ( *p8 <= 'Z' ) )
+		{
+			*p8 = *p8 - ( 'A' - 'a' );
+		}
+		p8++;
+	}
+	unsigned char * p_fnt = (unsigned char *)get_font_file_address( Filename );
+	Dbg_MsgAssert( p_fnt, ( "Font file '%s' not found in permanent data.", Filename ));
+
+#define get_bytes(p,n) { memcpy( p, p_fnt, n ); p_fnt += n; }
+
+	// Allocate memory for the font structure.
+	pFont = new SFont();
+
+	// Allocate a temporary buffer.
+	uint8 FontBuf[2048];
+
+	// Load file header.
+	get_bytes( FontBuf, 16 );
+//	Len = File::Read( FontBuf, 16, 1, p_FH );
+//	Dbg_MsgAssert( Len==16, ( "couldn't read file header from font file %s", Filename ));
+	NumChars			 = ((uint32 *)FontBuf)[1];
+	pFont->DefaultHeight = ((uint32 *)FontBuf)[2];
+	pFont->DefaultBase	 = ((uint32 *)FontBuf)[3];
+
+#ifdef __PLAT_NGC__
+	// PJR - This needs to disappear once we start converting .ska to .ska.ngc etc.
+	NumChars				= nReverse32( NumChars );
+	pFont->DefaultHeight	= nReverse32( pFont->DefaultHeight );
+	pFont->DefaultBase		= nReverse32( pFont->DefaultBase );
+#endif		// __PLAT_NGC__
+
+	// Clear character map to zero.
+	memset( pFont->Map, 0, 256 );
+	memset(pFont->SpecialMap, 0, 32);
+
+	// Allocate memory for character table.
+	pFont->pChars = new SChar[NumChars];
+
+	// Load character map and character table.
+	get_bytes( FontBuf, NumChars << 2 );
+//	Len = File::Read( FontBuf, NumChars << 2, 1, p_FH );
+//	Dbg_MsgAssert( Len == ( NumChars << 2 ), ( "couldn't read character table in font file %s", Filename ));
+
+	for( i = 0, pChar = pFont->pChars, pData = FontBuf; i < NumChars; i++,pChar++,pData += 4 )
+	{
+#ifdef __PLAT_NGC__
+		// PJR - This needs to disappear once we start converting .ska to .ska.ngc etc.
+		pChar->Baseline = nReverse32( pChar->Baseline );
+		pChar->Baseline = nReverse16( ((uint16 *)pData)[0] );
+		sint16 ascii_val = nReverse16(((sint16*) pData)[1]);
+		if (ascii_val >= 0)
+			pFont->Map[(uint8) ascii_val] = i;
+		else
+		{
+			Dbg_Assert(ascii_val >= -32)
+			pFont->SpecialMap[(uint8) (-ascii_val - 1)] = i;
+		}
+#else
+		pChar->Baseline							= ((uint16 *)pData)[0];
+		pFont->Map[(uint8)((uint16*)pData)[1]]	= i;
+#endif		// __PLAT_NGC__
+	}
+
+	// now, if there is a null character in the font, make characters that could not be found
+	// in the font display that instead of 'A'
+	if (pFont->SpecialMap[31] != 0)
+	{
+		for (i = 0; i < 256; i++) 
+		{
+			if (pFont->Map[i] == 0 && i != 'A' && i != 'a') pFont->Map[i] = pFont->SpecialMap[31];
+			if (i < 31 && pFont->SpecialMap[i] == 0) pFont->SpecialMap[i] = pFont->SpecialMap[31];
+		}
+	}	
+	
+	// Load texture header.
+	get_bytes( FontBuf, 16 );
+//	Len = File::Read( FontBuf, 16, 1, p_FH );
+//	Dbg_MsgAssert( Len == 16, ( "couldn't read texture header from font file %s", Filename ));
+	Width	= ((uint16 *)FontBuf)[2];
+	Height	= ((uint16 *)FontBuf)[3];
+	Depth	= ((uint16 *)FontBuf)[4];
+
+#ifdef __PLAT_NGC__
+	// PJR - This needs to disappear once we start converting .ska to .ska.ngc etc.
+	Width	= nReverse16( Width );
+	Height	= nReverse16( Height );
+	Depth	= nReverse16( Depth );
+#endif		// __PLAT_NGC__
+
+	// Create texture.
+	Dbg_Assert( Depth == 8 );
+
+	NumBytes = ( Width * Height + 3 ) & 0xFFFFFFFC;
+	
+	// Align texture to 32 bytes & point up.
+	int size = ( Width * Height ) + ( 256 * 2 );
+	unsigned char * p_align = (unsigned char*)OSRoundUp32B( p_fnt );
+	for ( int lp = ( size - 1 ); lp >= 0; lp-- ) p_align[lp] = p_fnt[lp];
+	DCFlushRange( p_align, size );
+	pFont->mp_texture = p_align;
+	pFont->mp_palette = &p_align[( Width * Height )];
+	p_fnt += ( Width * Height );		// Texture
+	p_fnt += ( 256 * 2 );				// Palette
+	p_fnt += 32;						// Padding
+
+//	pFont->mp_texture = new char[ ( Width * Height ) ];
+//	Len = File::Read( pFont->mp_texture, ( Width * Height ), 1, p_FH );
+//	Dbg_MsgAssert( Len == ( Width * Height ), ( "couldn't read texture data from font file %s", Filename ));
+//
+//	pFont->mp_palette = new char[ ( 256 * 4 ) ];
+//	Len = File::Read( pFont->mp_palette, ( 256 * 4 ), 1, p_FH );
+//	Dbg_MsgAssert( Len == ( 256 * 4 ), ( "couldn't read palette data from font file %s", Filename ));
+
+	pFont->m_width = Width;
+	pFont->m_height = Height;
+
+	// Skip numsubtextures, and load subtextures.
+	get_bytes( FontBuf, ( NumChars << 3 ) + 4 );
+//	Len = File::Read( FontBuf, ( NumChars << 3 ) + 4, 1, p_FH );
+//	Dbg_MsgAssert( Len == ( NumChars << 3 ) + 4, ( "couldn't read subtexture table from font file %s", Filename ));
+	for( i = 0, pChar = pFont->pChars, pData = FontBuf + 4; i < NumChars; i++, pChar++, pData += 8 )
+	{
+		uint16 x = ((uint16 *)pData )[0];
+		uint16 y = ((uint16 *)pData )[1];
+		uint16 w = ((uint16 *)pData )[2];
+		uint16 h = ((uint16 *)pData )[3];
+		
+#ifdef __PLAT_NGC__
+		x = nReverse16( x );
+		y = nReverse16( y );
+		w = nReverse16( w );
+		h = nReverse16( h );
+#endif		// __PLAT_NGC__
+
+		pChar->w	= w;
+		pChar->h	= h;
+		pChar->u0	= ( ((float)x + 0.5f) / (float)Width );
+		pChar->v0	= ( ((float)y + 0.5f) / (float)Height );
+		pChar->u1	= pChar->u0 + ((float)w / (float)Width );
+		pChar->v1	= pChar->v0 + ((float)h / (float)Height );
+	}
+
+	// Add font to font list.
+	pFont->pNext	= pFontList;
+	pFontList		= pFont;
+
+	// We're done with the font file now.
+//	File::Close( p_FH );
+	
+	// this will serve as the default spacing
+	pFont->mSpaceSpacing = pFont->pChars[pFont->Map['I']].w;
+	
+	return pFont;
+}
+
+
+
+void UnloadFont(SFont *pFont)
+{
+	SFont *pPrevFont;
+	int found=0;
+
+	// find font and unchain from list
+	if (pFontList == pFont)
+	{
+		found=1;
+		pFontList = pFontList->pNext;
+	}
+	else
+	{
+		for (pPrevFont=pFontList; pPrevFont->pNext; pPrevFont=pPrevFont->pNext)
+		{
+			if (pPrevFont->pNext == pFont)
+			{
+				found=1;
+				pPrevFont->pNext = pFont->pNext;
+				break;
+			}
+		}
+	}
+
+	Dbg_MsgAssert(found, ("attempt to unload font which has not been loaded"));
+
+	// free memory
+	free( pFont->pChars );
+	free( pFont );
+}
+
+uint32 SFont::GetDefaultHeight() const
+{
+	return DefaultHeight << 4;
+}
+
+uint32 SFont::GetDefaultBase() const
+{
+	return DefaultBase << 4;
+}
+
+void SFont::QueryString(char *String, float &width, float &height)
+{
+	SChar *pChar;
+	char *pLetter;
+	float u0,v0,x0,u1,v1,x1;
+
+	x0 = 0;
+
+	//sint16 char_spacing = 16 + (mCharSpacing<<4);
+	float char_spacing = mCharSpacing;
+	float space_spacing = mSpaceSpacing;
+	
+	for (pLetter=String;; pLetter++)
+	{
+		pChar = NULL;
+		// may be overridden by the '\b' tag
+		SFont *p_font = this;
+		
+		// acount for tags (might be multiple ones in a row)
+		bool got_char_tag = false; // tag resulting in output of character
+		while (*pLetter == '\\' && !got_char_tag)
+		{
+			pLetter++;
+			if (*pLetter == '\\')
+				break;
+
+			switch(*pLetter)
+			{
+				case '\\':
+					got_char_tag = true;
+					break;
+				case 'c':
+				case 'C':
+					pLetter += 2; // skip over "c#"
+					break;
+				case 's':
+				case 'S':
+				{
+					pLetter++; // skip "s"
+					uint digit = Str::DehexifyDigit(pLetter);
+					pChar = pChars + SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				case 'b':
+				case 'B':
+				{
+					pLetter++; // skip "b"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					// switch over to buttons font, the regular font will be used again on the next character
+
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				case 'm':
+				case 'M':
+				{
+					pLetter++; // skip "m"
+					char button_char = Nx::CFontManager::sMapMetaCharacterToButton(pLetter);
+					uint digit = Str::DehexifyDigit(&button_char);
+					
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				default:
+					Dbg_MsgAssert(0, ("unknown tag %c", *pLetter));
+					break;
+			}
+		} // end while
+		
+		if (*pLetter == '\0') break;
+
+		if (*pLetter != ' ' || pChar)
+		{
+			if (!pChar)
+				pChar = p_font->pChars + p_font->Map[(uint8)*pLetter];
+			u0 = pChar->u0;
+			v0 = pChar->v0;
+			u1 = pChar->u1;
+			v1 = pChar->v1;
+			x1 = x0+((u1-u0)*m_width);
+		}
+		else
+		{
+			x1 = x0 + space_spacing;
+		}
+		
+		x0 = x1+char_spacing;
+	}
+
+	width  = (float)x0;
+	height = (float)DefaultHeight;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SText::SText( float pri ) : SDraw2D( pri, true )
+{
+}
+
+
+SText::~SText( void )
+{
+}
+
+
+void SText::BeginDraw( void )
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SText::Draw( void )
+{
+	bool uploaded = false;
+	bool new_color = true;
+	SChar *pChar = NULL;
+	char *pLetter;
+	float u0,v0,x0,y0,u1,v1,x1,y1,yt;
+
+	x0 = m_xpos + wx0;
+	y0 = m_ypos + wy0;
+
+	//sint16 char_spacing = 16 + (mp_font->mCharSpacing<<4);
+	float char_spacing = (float) mp_font->mCharSpacing * m_xscale;
+	float space_spacing = (float) mp_font->mSpaceSpacing * m_xscale;
+	
+	GXColor current_color;
+	current_color.a = (m_rgba&0xff);
+	current_color.b = ((m_rgba&0xff00)>>8);
+	current_color.g = ((m_rgba&0xff0000)>>16);
+	current_color.r = ((m_rgba&0xff000000)>>24);
+	
+	for (pLetter=mp_string;; pLetter++)
+	{
+		pChar = NULL;
+		SFont *p_font = (spOverrideFont) ? spOverrideFont : mp_font;
+		
+		// acount for tags (might be multiple ones in a row)
+		bool got_char_tag = false; // tag resulting in output of character
+		while (*pLetter == '\\' && !got_char_tag)
+		{
+			pLetter++;
+
+			switch(*pLetter)
+			{
+				case '\\':
+					got_char_tag = true;
+					break;
+				case 'c':
+				case 'C':
+				{
+					pLetter++;	// skip "c"					
+					uint digit = Str::DehexifyDigit(pLetter);
+					pLetter++; // skip "#"
+
+					// set active color from font
+					uint32 trgba;
+					if (digit != 0 && !m_color_override)
+					{
+						trgba = mp_font->mRGBATab[digit-1];
+						current_color.r = (trgba&0xff);
+						current_color.g = ((trgba&0xff00)>>8);
+						current_color.b = ((trgba&0xff0000)>>16);
+						current_color.a = ((trgba&0xff000000)>>24);
+					}
+					else
+					{
+						trgba = m_rgba;
+						current_color.a = (trgba&0xff);
+						current_color.b = ((trgba&0xff00)>>8);
+						current_color.g = ((trgba&0xff0000)>>16);
+						current_color.r = ((trgba&0xff000000)>>24);
+					}
+
+					//GX::SetChanAmbColor( GX_COLOR0A0, current_color );
+					new_color = true;
+
+					break;
+				}
+				case 's':
+				case 'S':
+				{
+					pLetter++;	// skip "s"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					pChar = mp_font->pChars + mp_font->SpecialMap[digit];
+					got_char_tag = true;
+					
+					break;
+				}
+				case 'b':
+				case 'B':
+				{
+					// 'B' stands for button, accesses the button font
+
+					pLetter++; // skip "b"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					// switch to the buttons font!
+					
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					
+					spOverrideFont = p_font;
+					//EndDraw();
+					//BeginDraw();
+					uploaded = false;
+					// we can now return to using the regular font (no override)
+					spOverrideFont = NULL;
+					
+					break;
+				}
+				default:
+					Dbg_MsgAssert(0, ("unknown tag %c", *pLetter));
+					break;
+			}
+		} // end while
+
+		if (*pLetter == '\0') break;
+		
+		if (*pLetter != ' ' || pChar)
+		{
+			if (!pChar)
+				pChar = p_font->pChars + p_font->Map[(uint8) *pLetter];
+			yt = y0 + (float)(p_font->DefaultBase - pChar->Baseline) * m_yscale;
+			u0 = pChar->u0;
+			v0 = pChar->v0;
+			u1 = pChar->u1;
+			v1 = pChar->v1;
+			x1 = x0+(((u1-u0)*p_font->m_width)*m_xscale);
+			y1 = yt+(((v1-v0)*p_font->m_height)*m_yscale);
+		}
+		else
+		{
+			x0 += (space_spacing + char_spacing);
+			continue;
+		}
+
+		if ( !uploaded && current_color.a )
+		{
+			uploaded = true;
+			// Upload the texture & alpha map.
+			GX::UploadTexture( p_font->mp_texture,
+							   p_font->m_width,
+							   p_font->m_height,
+							   GX_TF_C8,
+							   GX_CLAMP,
+							   GX_CLAMP,
+							   GX_FALSE,
+							   GX_LINEAR,
+							   GX_LINEAR,
+							   0.0f,
+							   0.0f,
+							   0.0f,
+							   GX_FALSE,
+							   GX_FALSE,
+							   GX_ANISO_1,
+							   GX_TEXMAP0 );
+
+			GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, p_font->m_width, p_font->m_height );
+	
+			GX::UploadPalette( p_font->mp_palette,
+							   GX_TL_RGB5A3,
+							   GX_TLUT_256,
+							   GX_TEXMAP0 );
+
+//			GXTexObj	texObj;
+//			GXInitTexObjCI(
+//				&texObj,
+//				p_font->mp_texture,
+//				p_font->m_width,
+//				p_font->m_height,
+//				GX_TF_C8,
+//				GX_CLAMP,
+//				GX_CLAMP,
+//				GX_FALSE,
+//				GX_TLUT0 );
+//			GXInitTexObjLOD(&texObj, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+//			GXLoadTexObj( &texObj, GX_TEXMAP0 );
+//	
+//			GXTlutObj	palObj;
+//			GXInitTlutObj( &palObj, p_font->mp_palette, GX_TL_RGB5A3, 16 );
+//			GXLoadTlut ( &palObj, GX_TLUT0 );
+	
+	
+		//	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP1 );       // Alpha map is in Green channel, so use this to swap it to the alpha channel.
+		//	GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR0A0 );
+		//	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+		//	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+		//	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO );
+		//	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+	
+	
+			GX::SetTexChanTevIndCull( 1, 1, 1, 0, GX_CULL_NONE );
+			GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+			GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+			GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+			GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+			GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_RASA, GX_CA_TEXA, GX_CA_ZERO,
+									 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+									 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+			GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+
+
+		//		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+	
+			// Set current vertex descriptor to enable position and color0.
+			// Both use 8b index to access their data arrays.
+			GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+	
+			// Set material color.
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+	
+			GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+		}
+
+		if ( current_color.a )
+		{
+			if ( new_color )
+			{
+				GX::SetChanAmbColor( GX_COLOR0A0, current_color );
+				new_color = false;
+			}
+			// Send coordinates.
+			GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+				GX::Position3f32(x0, yt, -1.0f);
+				GX::TexCoord2f32(u0, v0);
+				GX::Position3f32(x1, yt, -1.0f);
+				GX::TexCoord2f32(u1, v0);
+				GX::Position3f32(x1, y1, -1.0f);
+				GX::TexCoord2f32(u1, v1);
+				GX::Position3f32(x0, y1, -1.0f);
+				GX::TexCoord2f32(u0, v1);
+			GX::End();
+		}
+
+		x0 = x1 + char_spacing;
+
+		if (p_font != mp_font)
+		{
+			// we just used the button font, so return to the regular one
+			uploaded = false;
+			//EndDraw();
+			//BeginDraw();
+		}
+	} // end for
+}
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SText::EndDraw( void )
+{
+}
+
+
+
+
+
+
+
+
+
+void SetTextWindow( uint16 x0, uint16 x1, uint16 y0, uint16 y1 )
+{
+	wx0 = (float)x0;
+	wy0 = (float)y0;
+	wx1 = (float)x1;
+	wy1 = (float)y1;
+//	TextRegSCISSOR = (uint64)x0 | (uint64)x1<<16 | (uint64)y0<<32 | (uint64)y1<<48;
+}
+
+void SText::DrawSingle( void )
+{
+	BeginDraw();
+	Draw();
+	EndDraw();
+}
+
+} // namespace Ngc
+
diff --git a/Code/Gfx/NGC/NX/chars.h b/Code/Gfx/NGC/NX/chars.h
new file mode 100644
index 0000000..fdb8b92
--- /dev/null
+++ b/Code/Gfx/NGC/NX/chars.h
@@ -0,0 +1,101 @@
+#ifndef __CHARS_H
+#define __CHARS_H
+
+#include 
+
+namespace NxNgc
+{
+
+
+typedef struct
+{
+	float	u0, v0, u1, v1;
+	uint16	w, h;
+	uint16	Baseline;
+}
+SChar;
+
+
+struct SFont
+{
+public:
+	uint32		GetDefaultHeight() const;
+	uint32		GetDefaultBase() const;
+
+//	void		BeginText(uint32 rgba, float Scale);
+//	void		DrawString(char *String, float x0, float y0);
+//	void		EndText(void);
+	void		QueryString(char *String, float &width, float &height);
+
+	//char Name[16];
+	uint32		DefaultHeight, DefaultBase;
+	SChar		*pChars;
+	uint8		Map[256];
+	uint8		SpecialMap[32];
+//	uint8		*pVifData;
+//	uint32		VifSize;
+//	uint64		RegTEX0, RegTEX1;
+	SFont		*pNext;
+
+	void*		mp_texture;
+	void*		mp_palette;
+	int			m_width;
+	int			m_height;
+
+	float		mCharSpacing;
+	float		mSpaceSpacing;
+	uint32		mRGBATab[16];
+
+//	IDirect3DTexture8*	pD3DTexture;		// To do - these should probably be replaced with an sTexture.
+//	IDirect3DPalette8*	pD3DPalette;
+};
+
+
+
+SFont *		LoadFont( const char *Filename );
+void		UnloadFont( SFont * );
+void		SetTextWindow( uint16 x0, uint16 x1, uint16 y0, uint16 y1 );
+
+
+struct SText : public SDraw2D
+{
+	public:
+					SText( float pri = 0.0f );
+	virtual			~SText();
+
+	void			DrawSingle( void );
+
+	SFont			*mp_font;
+
+	char			*mp_string;
+	float			m_xpos;
+	float			m_ypos;
+	float			m_xscale;
+	float			m_yscale;
+	uint32			m_rgba;
+	bool			m_color_override;
+	
+	// used in conjunction with BeginDraw()
+	// if set, use specified font instead of mp_font
+	// if not, use mp_font
+	static SFont *	spOverrideFont;
+
+private:
+
+	void			BeginDraw( void );
+	void			Draw( void );
+	void			EndDraw( void );
+};
+
+void SwizzleTexture( void *dstBuffer, void *srcBuffer, int width, int height, int32 depth, int32 stride );
+
+extern uint32 FontVramBase;
+extern SFont *pFontList;
+extern SFont *pButtonsFont;
+
+
+} // namespace NxNgc
+
+
+#endif // __CHARS_H
+
diff --git a/Code/Gfx/NGC/NX/geomnode.cpp b/Code/Gfx/NGC/NX/geomnode.cpp
new file mode 100644
index 0000000..429313c
--- /dev/null
+++ b/Code/Gfx/NGC/NX/geomnode.cpp
@@ -0,0 +1,358 @@
+///*****************************************************************************
+//**																			**
+//**			              Neversoft Entertainment.			                **
+//**																		   	**
+//**				   Copyright (C) 2000 - All Rights Reserved				   	**
+//**																			**
+//******************************************************************************
+//**																			**
+//**	Project:													**
+//**																			**
+//**	Module:						 								**
+//**																			**
+//**	File name:		.cpp											**
+//**																			**
+//**	Created by:		00/00/00	-	initials								**
+//**																			**
+//**	Description:			 									**
+//**																			**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  	  Includes									**
+//*****************************************************************************/
+//
+//#include 
+//#include 
+//#include "render.h"
+//#include "geomnode.h"
+//#include 
+//
+///*****************************************************************************
+//**								DBG Information								**
+//*****************************************************************************/
+//
+//namespace NxNgc
+//{
+//
+//
+//
+///*****************************************************************************
+//**								  Externals									**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								   Defines									**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								Private Types								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								 Private Data								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								 Public Data								**
+//*****************************************************************************/
+//
+//CGeomNode CGeomNode::sWorld;
+//CGeomNode CGeomNode::sDatabase;
+//
+///*****************************************************************************
+//**							  Private Prototypes							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Private Functions								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Public Functions								**
+//*****************************************************************************/
+//
+//
+//CGeomNode::CGeomNode()
+//{
+//	m_flags = 0;
+//
+//	m_bounding_sphere[0] = 0.0f;
+//	m_bounding_sphere[1] = 0.0f;
+//	m_bounding_sphere[2] = 0.0f;
+//	m_bounding_sphere[3] = (float)1.0e+30;
+//
+//	mp_properties	= NULL;
+//	mp_child		= NULL;
+//	mp_sibling		= NULL;
+//	mp_dma			= NULL;
+//	mp_group		= NULL;
+//}
+//
+//
+//void CGeomNode::Init()
+//{
+//	m_flags = 0;
+//
+//	m_bounding_sphere[0] = 0.0f;
+//	m_bounding_sphere[1] = 0.0f;
+//	m_bounding_sphere[2] = 0.0f;
+//	m_bounding_sphere[3] = (float)1.0e+30;
+//
+//	mp_properties	= NULL;
+//	mp_child		= NULL;
+//	mp_sibling		= NULL;
+//	mp_dma			= NULL;
+//	mp_group		= NULL;
+//}
+//
+//
+//CGeomNode::CGeomNode(const CGeomNode &node)
+//{
+//	m_flags				= node.m_flags;
+//	m_bounding_sphere	= node.m_bounding_sphere;
+//	mp_properties		= node.mp_properties;
+//	mp_child			= node.mp_child;
+//	mp_sibling			= node.mp_sibling;
+//	mp_dma				= node.mp_dma;
+//	mp_group			= node.mp_group;
+//}
+//
+//
+//
+//
+//#define GET_FLAG(FLAG) (m_flags & (FLAG) ? true : false)
+//#define SET_FLAG(FLAG) m_flags = yes ? m_flags|FLAG : m_flags&~FLAG
+//
+//bool CGeomNode::IsLeaf()
+//{
+//	return GET_FLAG(NODEFLAG_LEAF);
+//}
+//
+//
+//bool CGeomNode::IsObject()
+//{
+//	return GET_FLAG(NODEFLAG_OBJECT);
+//}
+//
+//
+//bool CGeomNode::IsActive()
+//{
+//	return GET_FLAG(NODEFLAG_ACTIVE);
+//}
+//
+//
+//bool CGeomNode::IsTransformed()
+//{
+//	return GET_FLAG(NODEFLAG_TRANSFORMED);
+//}
+//
+//
+//void CGeomNode::SetLeaf(bool yes)
+//{
+//	SET_FLAG(NODEFLAG_LEAF);
+//}
+//
+//
+//void CGeomNode::SetObject(bool yes)
+//{
+//	SET_FLAG(NODEFLAG_OBJECT);
+//}
+//
+//
+//void CGeomNode::SetActive(bool yes)
+//{
+//	SET_FLAG(NODEFLAG_ACTIVE);
+//}
+//
+//
+//void CGeomNode::SetTransformed(bool yes)
+//{
+//	SET_FLAG(NODEFLAG_TRANSFORMED);
+//}
+//
+//
+//uint32 CGeomNode::GetChecksum()
+//{
+//	return m_checksum;
+//}
+//
+//
+//void CGeomNode::SetChecksum(uint32 checksum)
+//{
+//	m_checksum = checksum;
+//}
+//
+//
+//void CGeomNode::SetBoundingSphere(float x, float y, float z, float R)
+//{
+//	m_bounding_sphere[0] = x;
+//	m_bounding_sphere[1] = y;
+//	m_bounding_sphere[2] = z;
+//	m_bounding_sphere[3] = R;
+//}
+//
+//
+//uint8 *CGeomNode::GetDma()
+//{
+//	return mp_dma;
+//}
+//
+//
+//void CGeomNode::SetDma(uint8 *pDma)
+//{
+//	mp_dma = pDma;
+//}
+//
+//
+//CGeomNode *CGeomNode::GetChild()
+//{
+//	return mp_child;
+//}
+//
+//
+//void CGeomNode::SetChild(CGeomNode *pChild)
+//{
+//	mp_child = pChild;
+//}
+//
+//
+//CGeomNode *CGeomNode::GetSibling()
+//{
+//	return mp_sibling;
+//}
+//
+//
+//void CGeomNode::SetSibling(CGeomNode *pSibling)
+//{
+//	mp_sibling = pSibling;
+//}
+//
+//
+//sGroup *CGeomNode::GetGroup()
+//{
+//	return mp_group;
+//}
+//
+//
+//void CGeomNode::SetGroup(sGroup *pGroup)
+//{
+//	mp_group = pGroup;
+//}
+//
+//
+//Mth::Matrix& CGeomNode::GetMatrix()
+//{
+//	Dbg_MsgAssert(IsTransformed(), ("trying to access matrix of a non-transformed CGeomNode"));
+//	Dbg_MsgAssert(mp_properties, ("trying to access matrix of a CGeomNode which has no properties"));
+//	return *((Mth::Matrix *)mp_properties);
+//}
+//
+//
+//void CGeomNode::SetMatrix(Mth::Matrix *p_mat)
+//{
+//	mp_properties = (void *)p_mat;
+//	SetTransformed(p_mat ? true : false);
+//}
+//
+//
+//void *CGeomNode::GetProperties()
+//{
+//	return mp_properties;
+//}
+//
+//
+//void CGeomNode::SetProperties(void *pProperties)
+//{
+//	mp_properties = pProperties;
+//}
+//
+//
+//#ifdef __PLAT_WN32__
+//
+//void CGeomNode::Preprocess(uint8 *p_baseAddress)
+//{
+//	// recursively process any children
+//	CGeomNode *p_child, *p_sibling;
+//	for (p_child=mp_child; p_child; p_child=p_sibling)
+//	{
+//		p_sibling = p_child->mp_sibling;
+//		p_child->Preprocess(p_baseAddress);
+//	}
+//
+//	// convert properties pointer to offset
+//	if (mp_properties)
+//		mp_properties = (void *) ( (uint8 *)mp_properties - p_baseAddress );
+//	else
+//		mp_properties = (void *)(-1);
+//
+//	// convert dma pointer to offset
+//	if (mp_dma)
+//		mp_dma = (uint8 *)( mp_dma - p_baseAddress );
+//	else
+//		mp_dma = (uint8 *)(-1);
+//
+//	// leave group ID alone
+//
+//	// convert child pointer to offset
+//	if (mp_child)
+//	{
+//		mp_child = (CGeomNode *) ( (uint8 *)mp_child - p_baseAddress );
+//	}
+//	else
+//	{
+//		mp_child = (CGeomNode *)(-1);
+//	}
+//
+//	// convert sibling pointer to offset
+//	if (mp_sibling)
+//	{
+//		mp_sibling = (CGeomNode *) ( (uint8 *)mp_sibling - p_baseAddress );
+//	}
+//	else
+//	{
+//		mp_sibling = (CGeomNode *)(-1);
+//	}
+//}
+//
+//#endif
+//
+//
+//
+//CGeomNode* CGeomNode::CreateInstance(Mth::Matrix *p_mat)
+//{
+//	// copy node
+//	CGeomNode *p_node = new CGeomNode(*this);
+//
+//	// set matrix
+//	p_node->SetMatrix(p_mat);
+//
+////	// add to world
+////	p_node->BecomesChildOf(&sWorld);
+////
+////	// activate!
+////	p_node->SetActive(true);
+//
+//	// return new node
+//	return p_node;
+//}
+//
+//
+//void CGeomNode::DeleteInstance()
+//{
+////	RemoveFrom(&sWorld);
+//	delete this;
+//}
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//} // namespace NxNgc
+
+
+
+
+
diff --git a/Code/Gfx/NGC/NX/geomnode.h b/Code/Gfx/NGC/NX/geomnode.h
new file mode 100644
index 0000000..2334d7f
--- /dev/null
+++ b/Code/Gfx/NGC/NX/geomnode.h
@@ -0,0 +1,185 @@
+///*****************************************************************************
+//**																			**
+//**			              Neversoft Entertainment.			                **
+//**																		   	**
+//**				   Copyright (C) 2000 - All Rights Reserved				   	**
+//**																			**
+//******************************************************************************
+//**																			**
+//**	Project:		THPS4													**
+//**																			**
+//**	Module:			NxNgc					 								**
+//**																			**
+//**	File name:		geometry.h												**
+//**																			**
+//**	Created by:		02/08/02	-	mrd										**
+//**																			**
+//**	Description:	classes for Ngc geometry								**
+//**																			**
+//*****************************************************************************/
+//
+//#ifndef __GFX_NGC_NX_GEOMETRY_H
+//#define __GFX_NGC_NX_GEOMETRY_H
+//
+///*****************************************************************************
+//**							  	  Includes									**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								   Defines									**
+//*****************************************************************************/
+//
+//
+//namespace NxNgc
+//{
+//	// Forward declarations
+//	struct	sGroup;
+//
+//
+//#define NODEFLAG_ACTIVE			(1<<0)
+//#define NODEFLAG_LEAF			(1<<1)
+//#define NODEFLAG_OBJECT			(1<<2)
+//#define NODEFLAG_TRANSFORMED	(1<<3)
+//#define NODEFLAG_COLOURED		(1<<4)
+//
+//#define LOADFLAG_RENDERNOW		(1<<0)
+//
+//// put these here temporarily
+//#define RENDERFLAG_CULL			(1<<0)
+//#define RENDERFLAG_CLIP			(1<<1)
+//
+//
+///*****************************************************************************
+//**							Class Definitions								**
+//*****************************************************************************/
+//
+//struct STransformContext
+//{
+//	Mth::Matrix	matrix;
+//	Mth::Matrix	adjustedMatrix;
+//	Mth::Vector	translation;
+//};
+//
+//
+//class CGeomNode
+//{
+//public:
+//
+//
+//
+//	#ifdef __PLAT_NGC__
+//	void				Render(STransformContext& transformContext, uint32 renderFlags);
+//	#endif
+//
+//	bool				IsLeaf();
+//	bool				IsActive();
+//	bool				IsTransformed();
+//	bool				IsObject();
+//
+//	void				SetLeaf(bool yes);
+//	void				SetActive(bool yes);
+//	void				SetTransformed(bool yes);
+//	void				SetObject(bool yes);
+//
+//	void				SetChecksum(uint32 checksum);
+//	void				SetBoundingSphere(float x, float y, float z, float R);	// change args to const Mth::Vector sphere
+//	void				SetDma(uint8 *pDma);
+//	void				SetChild(CGeomNode *pChild);
+//	void				SetSibling(CGeomNode *pSibling);
+//	void				SetGroup(sGroup *pGroup);
+//	void				SetMatrix(Mth::Matrix *p_mat);
+//	void				SetProperties(void *pProperties);
+//
+//	uint32				GetChecksum();
+//	Mth::Matrix&		GetMatrix();
+//	void*				GetProperties();
+//	CGeomNode*			GetChild();
+//	CGeomNode*			GetSibling();
+//	uint8*				GetDma();
+//	sGroup*				GetGroup();
+//
+//	#ifdef __PLAT_WN32__
+//	void 				Preprocess(uint8 *p_baseAddress);
+//	#endif
+//
+//	#ifdef __PLAT_NGC__
+//	static CGeomNode*	sProcessInPlace(uint8 *pPipData, uint32 loadFlags);
+//	void				Cleanup();
+//	
+//	CGeomNode*			CreateInstance(Mth::Matrix *p_mat);
+//	void				DeleteInstance();
+//	CGeomNode*			CreateCopy();
+//	#endif
+//	
+//	void				Init();
+//
+//						CGeomNode();
+//						CGeomNode(const CGeomNode &node);
+//						~CGeomNode() {}
+//
+//
+//						static CGeomNode	sWorld;
+//						static CGeomNode	sDatabase;
+//
+//
+//
+//private:
+//
+//	#ifdef __PLAT_NGC__
+//	bool				IsOutsideViewVolume(Mth::Vector &s);
+//	bool				IsInsideOuterVolume(Mth::Vector &s);
+//	bool				NeedsClipping(Mth::Vector &s);
+//
+//	void				SendVu1Context(STransformContext& tc);
+//	void				LinkDma(uint8 *p_newTail);
+//
+//	void 				ProcessNodeInPlace(uint8 *p_baseAddress);
+//
+//	void 				BecomesChildOf(CGeomNode *p_parent);
+//	void				RemoveFrom(CGeomNode *p_parent);
+//
+//	#endif
+//
+//	Mth::Vector			m_bounding_sphere;
+//	uint32				m_flags;
+//	void *				mp_properties;
+//	CGeomNode *			mp_child;
+//	CGeomNode *			mp_sibling;
+//	uint8 *				mp_dma;
+//	sGroup *			mp_group;
+//	uint32				m_checksum;
+//	uint32				pad[1];
+//};
+//
+//
+//
+///*****************************************************************************
+//**							 Private Declarations							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Private Prototypes							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Public Declarations							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							   Public Prototypes							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								Inline Functions							**
+//*****************************************************************************/
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//} // namespace NxNgc
+//
+//#endif	// __GFX_NGC_NX_GEOMETRY_H
+
+
diff --git a/Code/Gfx/NGC/NX/grass.cpp b/Code/Gfx/NGC/NX/grass.cpp
new file mode 100644
index 0000000..c29a8d6
--- /dev/null
+++ b/Code/Gfx/NGC/NX/grass.cpp
@@ -0,0 +1,1413 @@
+//-----------------------------------------------------------------------------
+// File: XBFur.cpp
+//
+// Desc: routines for generating and displaying a patch of fur,
+//       which is a series of layers that give the appearance of
+//       hair, fur, grass, or other fuzzy things.
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include "nx_init.h"
+#include "render.h"
+#include "grass.h"
+//#include "mipmap.h"
+
+////extern LPDIRECT3DDEVICE8 NxNgc::EngineGlobals.p_Device;
+//
+//#define irand(a) ((rand()*(a))>>15)
+//#define frand(a) ((float)rand()*(a)/32768.0f)
+//
+//#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }
+//
+//
+//float g_fOneInch = 0.01f;
+//
+//
+//extern CXBFur *p_fur;
+//
+//
+//
+//
+////-----------------------------------------------------------------------------
+//// Name: Constructor
+//// Desc: 
+////-----------------------------------------------------------------------------
+//CXBFur::CXBFur()
+//{
+//    DWORD i;
+//    ZeroMemory(this, sizeof(CXBFur));
+//
+//    // init default patch
+//    m_fXSize = 0.1f;
+//    m_fZSize = 0.1f;
+//
+//    // init default fuzzlib
+//    m_dwNumFuzzLib = 0;
+//    m_pFuzzLib = NULL;
+//    m_fuzzCenter.colorBase = D3DXCOLOR(1.f, 1.f, 1.f, 1.f);
+//    m_fuzzCenter.colorTip = D3DXCOLOR(1.f, 1.f, 1.f, 0.f);
+//    m_fuzzCenter.dp.y = 1.0f;
+//
+//    // init default fuzz
+//    m_dwNumSegments = 4;
+//    m_pFuzz = NULL;
+//
+//    // init default volume
+//    m_dwNumSlices = 0;
+//    m_dwSliceXSize = 0;
+//    m_dwSliceZSize = 0;
+//    for(i=0; im_fYSize)
+//            m_fYSize = y;
+//    }
+//
+//     // initialize the fuzz locations & pick a random fuzz from the library
+//    srand(m_dwSeed*54795);
+//    for(i=0; i 0)
+//    {
+//        D3DSURFACE_DESC desc;
+//        m_apSliceTexture[0]->GetLevelDesc(0, &desc);
+//        if (desc.Format != D3DFMT_A8R8G8B8)
+//            bFormatOK = false;
+//    }
+//
+//    // make sure volume info is up to date
+//    if(m_dwSliceXSize!=slicexsize 
+//       || m_dwSliceZSize!=slicezsize
+//       || !bFormatOK)
+//    {
+//        m_dwNumSlices = 0;
+//        m_dwNumSlicesLOD = 0;
+//        for(UINT i=0; i> i;
+//        m_dwLODMax = i - 1;
+//
+//        // create new layers and level-of-detail layers
+//        for(i=0; iCreateTexture(slicexsize, slicezsize, 0, 0, D3DFMT_A8R8G8B8, 0, &m_apSliceTexture[i]);
+//            
+//        // release unused layers
+//        for(i=nslices+nLOD; iGetRenderTarget(&save.pBackBuffer);
+//    NxNgc::EngineGlobals.p_Device->GetDepthStencilSurface(&save.pZBuffer);
+//    NxNgc::EngineGlobals.p_Device->GetTransform( D3DTS_WORLD, &save.matWorld);
+//    NxNgc::EngineGlobals.p_Device->GetTransform( D3DTS_VIEW, &save.matView);
+//    NxNgc::EngineGlobals.p_Device->GetTransform( D3DTS_PROJECTION, &save.matProjection);
+//        
+//    // make a new depth buffer
+//    IDirect3DSurface8 *pZBuffer = NULL;
+//    NxNgc::EngineGlobals.p_Device->CreateDepthStencilSurface(slicexsize, slicezsize, D3DFMT_LIN_D24S8, D3DMULTISAMPLE_NONE, &pZBuffer);
+//    D3DXVECTOR3 va(-0.5f*m_fXSize, 0.f, -0.5f*m_fZSize), vb(0.5f*m_fXSize, m_fYSize, 0.5f*m_fZSize); // bounds of fuzz patch
+//    D3DXVECTOR3 vwidth(vb-va), vcenter(0.5f*(vb+va)); // width and center of fuzz patch
+//        
+//    // set world transformation to scale model to unit cube [-1,1] in all dimensions
+//    D3DXMATRIX matWorld, matTranslate, matScale;
+//    D3DXMatrixTranslation(&matTranslate, -vcenter.x, -vcenter.y, -vcenter.z);
+//    D3DXMatrixScaling(&matScale, 2.f/vwidth.x, 2.f/vwidth.y, 2.f/vwidth.z);
+//    matWorld = matTranslate * matScale;
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_WORLD, &matWorld);
+//        
+//    // set view transformation to flip z and look down y axis, with bottom-most slice scaled and translated to map to [0,1]
+//    D3DMATRIX matView;
+//    matView.m[0][0] = 1.f;    matView.m[0][1] =  0.f;    matView.m[0][2] = 0.f;                matView.m[0][3] = 0.f;
+//    matView.m[1][0] = 0.f;    matView.m[1][1] =  0.f;    matView.m[1][2] = 0.5f * nslices;    matView.m[1][3] = 0.f;
+//    matView.m[2][0] = 0.f;    matView.m[2][1] = -1.f;    matView.m[2][2] = 0.f;                matView.m[2][3] = 0.f;
+//    matView.m[3][0] = 0.f;    matView.m[3][1] =  0.f;    matView.m[3][2] = 0.5f * nslices;    matView.m[3][3] = 1.f;
+//        
+//    // set projection matrix to orthographic
+//    D3DXMATRIX matProjection;
+//    D3DXMatrixIdentity(&matProjection);
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_PROJECTION, &matProjection);
+//    UINT numLines = m_dwNumFuzz*m_dwNumSegments;
+//    IDirect3DVertexBuffer8 *pVB;
+//    GetLinesVertexBuffer(&pVB);
+//    NxNgc::EngineGlobals.p_Device->SetTexture(0, NULL);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); // premultiplied alpha
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, FALSE);
+//
+//    NxNgc::EngineGlobals.p_Device->SetPixelShader( 0 );
+//    NxNgc::EngineGlobals.p_Device->SetVertexShader(FVF_XYZDIFF);
+//    NxNgc::EngineGlobals.p_Device->SetStreamSource(0, pVB, sizeof(FVFT_XYZDIFF));
+//        
+//    // draw each slice
+//    for (int i = 0; i < (int)nslices; i++)
+//    {
+//        // get destination surface & set as render target, then draw the fuzz slice
+//        LPDIRECT3DTEXTURE8 pTexture = m_apSliceTexture[i];
+//        IDirect3DSurface8 *pSurface;
+//        pTexture->GetSurfaceLevel(0, &pSurface);
+//        NxNgc::EngineGlobals.p_Device->SetRenderTarget(pSurface, pZBuffer);
+//        NxNgc::EngineGlobals.p_Device->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_RGBA(0x10,0x20,0x10,0), 1.0f, 0 );
+//        matView.m[1][2] = 0.5f * nslices;
+//        matView.m[3][2] = 0.5f * nslices - (float)i;    // offset to next slice
+//        
+//        // We want the texture to wrap, so draw multiple times with offsets in the plane so that 
+//        // the boundaries will be filled in by the overlapping geometry.
+//        for (int iX = -1; iX <= 1; iX++)
+//        {
+//            for (int iY = -1; iY <= 1; iY++)
+//            {
+//                matView.m[3][0] = 2.f * iX;
+//                matView.m[3][1] = 2.f * iY;
+//                NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_VIEW, &matView);
+//                NxNgc::EngineGlobals.p_Device->DrawPrimitive(D3DPT_LINELIST, 0, numLines);
+//            }
+//        }
+//        pSurface->Release();
+//        GenerateMipmaps(pTexture, 0);
+//    }
+//    
+//    // clean up
+//    pVB->Release();
+//    pZBuffer->Release();
+//    NxNgc::EngineGlobals.p_Device->SetRenderTarget(save.pBackBuffer, save.pZBuffer);
+//    save.pBackBuffer->Release();
+//    save.pZBuffer->Release();
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_WORLD, &save.matWorld);
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_VIEW, &save.matView);
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_PROJECTION, &save.matProjection);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, TRUE);
+//
+//
+//	// Compress the textures.
+//	for( int i = 0; i < (int)nslices; i++ )
+//    {
+//		LPDIRECT3DTEXTURE8 pTextureSrc = m_apSliceTexture[i];
+//        LPDIRECT3DTEXTURE8 pTextureDst = NULL;
+//		CompressTexture( &pTextureDst, D3DFMT_DXT4, pTextureSrc );
+//		m_apSliceTexture[i] = pTextureDst;
+//		pTextureSrc->Release();
+//	}
+//}
+//
+////-----------------------------------------------------------------------------
+//// Name: GenFin
+//// Desc: Generate the fin texture in a similar way as GenSlices.
+////-----------------------------------------------------------------------------
+//void CXBFur::GenFin(DWORD finWidth, DWORD finHeight, float fFinXFraction, float fFinZFraction)
+//{
+//    m_fFinXFraction = fFinXFraction;
+//    m_fFinZFraction = fFinZFraction;
+//
+//    // Check the format of the textures
+//    bool bFormatOK = true;
+//    if (m_pFinTexture != NULL)
+//    {
+//        D3DSURFACE_DESC desc;
+//        m_pFinTexture->GetLevelDesc(0, &desc);
+//        if (desc.Format != D3DFMT_A8R8G8B8)
+//            bFormatOK = false;
+//    }
+//
+//    // make sure fin info is up to date
+//    if(m_finWidth != finWidth
+//       || m_finHeight != finHeight
+//       || !bFormatOK)
+//    {
+//        SAFE_RELEASE(m_pFinTexture);
+//    }
+//    m_finWidth = finWidth;
+//    m_finHeight = finHeight;
+//
+//    // create fin texture if needed
+//    if (m_pFinTexture == NULL)
+//        NxNgc::EngineGlobals.p_Device->CreateTexture(finWidth, finHeight, 0, 0, D3DFMT_A8R8G8B8, 0, &m_pFinTexture);
+//
+//    // save current back buffer, z buffer, and transforms
+//    struct {
+//        IDirect3DSurface8 *pBackBuffer, *pZBuffer;
+//        D3DMATRIX matWorld, matView, matProjection;
+//    } save;
+//    NxNgc::EngineGlobals.p_Device->GetRenderTarget(&save.pBackBuffer);
+//    NxNgc::EngineGlobals.p_Device->GetDepthStencilSurface(&save.pZBuffer);
+//    NxNgc::EngineGlobals.p_Device->GetTransform( D3DTS_WORLD, &save.matWorld);
+//    NxNgc::EngineGlobals.p_Device->GetTransform( D3DTS_VIEW, &save.matView);
+//    NxNgc::EngineGlobals.p_Device->GetTransform( D3DTS_PROJECTION, &save.matProjection);
+//
+//    // make a new depth buffer
+//    IDirect3DSurface8 *pZBuffer = NULL;
+//    NxNgc::EngineGlobals.p_Device->CreateDepthStencilSurface(finWidth, finHeight, D3DFMT_LIN_D24S8, D3DMULTISAMPLE_NONE, &pZBuffer);
+//    D3DXVECTOR3 va(-0.5f*m_fXSize, 0.f, -0.5f*m_fZSize), vb(0.5f*m_fXSize, m_fYSize, 0.5f*m_fZSize); // bounds of fuzz patch
+//    D3DXVECTOR3 vwidth(vb-va), vcenter(0.5f*(vb+va)); // width and center of fuzz patch
+//
+//    // set world transformation to scale model to unit cube [-1,1] in all dimensions
+//    D3DXMATRIX matWorld, matTranslate, matScale;
+//    D3DXMatrixTranslation(&matTranslate, -vcenter.x, -vcenter.y, -vcenter.z);
+//    D3DXMatrixScaling(&matScale, 2.f/vwidth.x, 2.f/vwidth.y, 2.f/vwidth.z);
+//    matWorld = matTranslate * matScale;
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_WORLD, &matWorld);
+//
+//    // set view transformation to look down z axis, with z range [0,1]
+//    float fXFraction = m_fFinXFraction;
+//    float fYFraction = 1.f;
+//    float fZFraction = m_fFinZFraction; 
+//    float fXScale = 1.f / fXFraction;
+//    float fYScale = 1.f / fYFraction;
+//    float fZScale = 1.f / fZFraction;
+//    D3DMATRIX matView;
+//    matView.m[0][0] = fXScale;    matView.m[0][1] =  0.f;        matView.m[0][2] = 0.f;                matView.m[0][3] = 0.f;
+//    matView.m[1][0] = 0.f;        matView.m[1][1] = -fYScale;    matView.m[1][2] = 0.f;                matView.m[1][3] = 0.f;
+//    matView.m[2][0] = 0.f;        matView.m[2][1] =  0.f;        matView.m[2][2] = 0.5f * fZScale;    matView.m[2][3] = 0.f;
+//    matView.m[3][0] = 0.f;        matView.m[3][1] =  0.f;     matView.m[3][2] = 0.5f * fZScale;    matView.m[3][3] = 1.f;
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_VIEW, &matView);
+//    // set projection matrix to orthographic
+//
+//    D3DXMATRIX matProjection;
+//    D3DXMatrixIdentity(&matProjection);
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_PROJECTION, &matProjection);
+//    UINT numLines = m_dwNumFuzz*m_dwNumSegments;
+//    IDirect3DVertexBuffer8 *pVB;
+//    GetLinesVertexBuffer(&pVB);
+//    NxNgc::EngineGlobals.p_Device->SetTexture(0, NULL);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); // premultiplied alpha
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_COLOR1);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetVertexShader(FVF_XYZDIFF);
+//    NxNgc::EngineGlobals.p_Device->SetStreamSource(0, pVB, sizeof(FVFT_XYZDIFF));
+//    
+//    // draw fuzz into the fin texture, looking from the side
+//    LPDIRECT3DTEXTURE8 pTexture = m_pFinTexture;
+//    IDirect3DSurface8 *pSurface;
+//    pTexture->GetSurfaceLevel(0, &pSurface);
+//    NxNgc::EngineGlobals.p_Device->SetRenderTarget(pSurface, pZBuffer);
+//    NxNgc::EngineGlobals.p_Device->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, 
+//                         D3DCOLOR_RGBA(0,0,0,0), 1.0f, 0 );
+//    NxNgc::EngineGlobals.p_Device->DrawPrimitive(D3DPT_LINELIST, 0, numLines);
+//    pSurface->Release();
+//    GenerateMipmaps(pTexture, 0, D3DTADDRESS_WRAP, D3DTADDRESS_CLAMP);
+//
+//    // clean up
+//    pVB->Release();
+//    pZBuffer->Release();
+//    NxNgc::EngineGlobals.p_Device->SetRenderTarget(save.pBackBuffer, save.pZBuffer);
+//    save.pBackBuffer->Release();
+//    save.pZBuffer->Release();
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_WORLD, &save.matWorld);
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_VIEW, &save.matView);
+//    NxNgc::EngineGlobals.p_Device->SetTransform( D3DTS_PROJECTION, &save.matProjection);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, TRUE);
+//}
+//
+////-----------------------------------------------------------------------------
+//// Name: GetLinesVertexBuffer
+//// Desc: Create and fill in a vertex buffer as a series of individual lines from the fuzz library.
+////-----------------------------------------------------------------------------
+//void CXBFur::GetLinesVertexBuffer(IDirect3DVertexBuffer8 **ppVB)
+//{
+//    IDirect3DVertexBuffer8 *pVB;
+//    UINT numVertices = m_dwNumFuzz*m_dwNumSegments*2;
+//    NxNgc::EngineGlobals.p_Device->CreateVertexBuffer(numVertices*sizeof(FVFT_XYZDIFF), 0, FVF_XYZDIFF, 0, &pVB);
+//    assert(pVB!=NULL);
+//    DWORD i, j, vidx, lidx;
+//    FVFT_XYZDIFF *verts;
+//    float x0, y0, z0;
+//    float dx, dy, dz;
+//    float ddx, ddy, ddz;
+//    float step, cp;
+//    UINT startseg = 0;
+//    step = 1.0f/(float)m_dwNumSegments;
+//    pVB->Lock(0, numVertices*sizeof(FVFT_XYZDIFF), (BYTE **)&verts, 0);
+//    vidx = 0;
+//    D3DXCOLOR colorBase, colorDelta;
+//    for(i=0; iUnlock();
+//    *ppVB = pVB;
+//}
+//
+////-----------------------------------------------------------------------------
+//// Name: RenderLines
+//// Desc: Draw the fuzz patch as a series of individual lines.
+////-----------------------------------------------------------------------------
+//void CXBFur::RenderLines()
+//{
+//    UINT numLines = m_dwNumFuzz*m_dwNumSegments;
+//    IDirect3DVertexBuffer8 *pVB;
+//    GetLinesVertexBuffer(&pVB);
+//    NxNgc::EngineGlobals.p_Device->SetTexture(0, NULL);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
+//    NxNgc::EngineGlobals.p_Device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); // premultiplied alpha
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetVertexShader(FVF_XYZDIFF);
+//    NxNgc::EngineGlobals.p_Device->SetStreamSource(0, pVB, sizeof(FVFT_XYZDIFF));
+//    NxNgc::EngineGlobals.p_Device->DrawPrimitive(D3DPT_LINELIST, 0, numLines);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, TRUE);
+//    pVB->Release();
+//}
+//
+//
+//
+//#if 0
+//
+////-----------------------------------------------------------------------------
+//// Desc: File routines for saving and loading patch files.
+////       Saving is only relevant if used on a Windows platform.
+////-----------------------------------------------------------------------------
+//struct _fphdr
+//{
+//    char sig[5];
+//    int flags;
+//};
+//
+//void CXBFur::Save(char *fname, int flags)
+//{
+//    HANDLE hFile;
+//    struct _fphdr hdr;
+//
+//    DWORD dwNumBytesWritten;
+//    hFile = CreateFile(fname, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,
+//                       OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+//    if(hFile == INVALID_HANDLE_VALUE)
+//        return;
+//
+//    // write file header
+//    strcpy(hdr.sig, "FUZ2");
+//    hdr.flags = flags;
+//
+//    WriteFile(hFile, &hdr, sizeof(struct _fphdr), &dwNumBytesWritten, NULL);
+//    
+//
+//    // write fpatch data
+//    WriteFile(hFile, &m_dwSeed, sizeof(DWORD), &dwNumBytesWritten, NULL);
+//    
+//    WriteFile(hFile, &m_fXSize, sizeof(float), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fYSize, sizeof(float), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fZSize, sizeof(float), &dwNumBytesWritten, NULL);
+//
+//    WriteFile(hFile, &m_dwNumSegments, sizeof(DWORD), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fFinXFraction, sizeof(float), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fFinZFraction, sizeof(float), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fuzzCenter.colorBase, sizeof(D3DXCOLOR), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fuzzRandom.colorBase, sizeof(D3DXCOLOR), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fuzzCenter.colorTip, sizeof(D3DXCOLOR), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fuzzRandom.colorTip, sizeof(D3DXCOLOR), &dwNumBytesWritten, NULL);
+//    
+//    WriteFile(hFile, &m_fuzzCenter.dp, sizeof(D3DVECTOR), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fuzzRandom.dp, sizeof(D3DVECTOR), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fuzzCenter.ddp, sizeof(D3DVECTOR), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_fuzzRandom.ddp, sizeof(D3DVECTOR), &dwNumBytesWritten, 0);
+//
+//    WriteFile(hFile, &m_dwNumFuzzLib, sizeof(DWORD), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_dwNumFuzz, sizeof(DWORD), &dwNumBytesWritten, NULL);
+//
+//    WriteFile(hFile, &m_dwNumSlices, sizeof(DWORD), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_dwSliceXSize, sizeof(DWORD), &dwNumBytesWritten, NULL);
+//    WriteFile(hFile, &m_dwSliceZSize, sizeof(DWORD), &dwNumBytesWritten, NULL);
+//    
+//    return;
+//
+//
+//
+//    // write volume data if available and flag is set
+//    {
+//    }
+//
+//    // cleanup
+//    CloseHandle(hFile);
+//}
+//
+//void CXBFur::Load(char *fname)
+//{
+//    HANDLE hFile;
+//    struct _fphdr hdr;
+//    DWORD numfuzzlib, numfuzz;
+//    DWORD numslices, slicexsize, slicezsize;
+//
+//    DWORD dwNumBytesRead;
+//    hFile = CreateFile(fname, GENERIC_READ, FILE_SHARE_READ, NULL,
+//                       OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
+//    if(hFile == INVALID_HANDLE_VALUE)
+//        return;
+//    
+//    // read file header
+//    ReadFile(hFile, &hdr, sizeof(struct _fphdr), &dwNumBytesRead, 0);
+//
+//    // verify signature
+//    bool bFUZ1 = !strcmp(hdr.sig, "FUZ1");
+//    bool bFUZ2 = !strcmp(hdr.sig, "FUZ2");
+//    if (!bFUZ1 && !bFUZ2)
+//        return;    // signature not understood
+//
+//    // read patch data
+//
+//    ReadFile(hFile, &m_dwSeed, sizeof(DWORD), &dwNumBytesRead, NULL);
+//    ReadFile(hFile, &m_fXSize, sizeof(float), &dwNumBytesRead, NULL);
+//    ReadFile(hFile, &m_fYSize, sizeof(float), &dwNumBytesRead, NULL);
+//    ReadFile(hFile, &m_fZSize, sizeof(float), &dwNumBytesRead, NULL);
+//    
+//
+//    ReadFile(hFile, &m_dwNumSegments, sizeof(DWORD), &dwNumBytesRead, NULL);
+//    
+//    if (bFUZ1)
+//    {
+//        m_fFinXFraction = 1.f;
+//        m_fFinZFraction = 0.05f;
+//
+//        ReadFile(hFile, &m_fuzzCenter.colorBase, sizeof(D3DXCOLOR), &dwNumBytesRead, NULL);
+//        m_fuzzCenter.colorTip = m_fuzzCenter.colorBase;
+//        m_fuzzRandom.colorBase = m_fuzzRandom.colorTip = 0ul;
+//    }
+//    else
+//    {
+//
+//        ReadFile(hFile, &m_fFinXFraction, sizeof(float), &dwNumBytesRead, NULL);
+//        ReadFile(hFile, &m_fFinZFraction, sizeof(float), &dwNumBytesRead, NULL);
+//        ReadFile(hFile, &m_fuzzCenter.colorBase, sizeof(D3DXCOLOR), &dwNumBytesRead, NULL);
+//        ReadFile(hFile, &m_fuzzRandom.colorBase, sizeof(D3DXCOLOR), &dwNumBytesRead, NULL);
+//        ReadFile(hFile, &m_fuzzCenter.colorTip, sizeof(D3DXCOLOR), &dwNumBytesRead, NULL);
+//        ReadFile(hFile, &m_fuzzRandom.colorTip, sizeof(D3DXCOLOR), &dwNumBytesRead, NULL);
+//    }
+//
+//    ReadFile(hFile, &m_fuzzCenter.dp, sizeof(D3DVECTOR), &dwNumBytesRead, NULL);  // velocity center
+//    ReadFile(hFile, &m_fuzzRandom.dp, sizeof(D3DVECTOR), &dwNumBytesRead, NULL);  // velocity random
+//    ReadFile(hFile, &m_fuzzCenter.ddp, sizeof(D3DVECTOR), &dwNumBytesRead, NULL); // acceleration center
+//    ReadFile(hFile, &m_fuzzRandom.ddp, sizeof(D3DVECTOR), &dwNumBytesRead, NULL); // acceleration random
+//    
+//
+//    ReadFile(hFile, &numfuzzlib, sizeof(DWORD), &dwNumBytesRead, NULL);
+//    ReadFile(hFile, &numfuzz, sizeof(DWORD), &dwNumBytesRead, NULL);
+//        
+//    ReadFile(hFile, &numslices, sizeof(DWORD), &dwNumBytesRead, NULL);
+//    ReadFile(hFile, &slicexsize, sizeof(DWORD), &dwNumBytesRead, NULL);
+//    ReadFile(hFile, &slicezsize, sizeof(DWORD), &dwNumBytesRead, NULL);
+//       
+//    // read volume data if available and flag is set
+//    {
+//    }
+//
+//    CloseHandle(hFile);
+//
+//    InitFuzz(numfuzz, numfuzzlib);
+//    GenSlices(numslices, slicexsize, slicezsize);
+//    ComputeLevelOfDetailTextures();
+//    SetLevelOfDetail(0.f);
+//}
+//
+////////////////////////////////////////////////////////////////////////
+//// Lighting Model
+////////////////////////////////////////////////////////////////////////
+//
+//inline float
+//Luminance(const D3DXCOLOR &c)
+//{
+//    return (0.229f * c.r) + (0.587f * c.g) + (0.114f * c.b);
+//}
+//
+//inline float
+//MaxChannel(const D3DXCOLOR &c)
+//{
+//    if (c.r > c.g)
+//    {
+//        if (c.r > c.b)
+//            return c.r;
+//        else
+//            return c.b;
+//    }
+//    else
+//    {
+//        if (c.g > c.b)
+//            return c.g;
+//        else
+//            return c.b;
+//    }
+//}
+//
+//inline D3DXCOLOR 
+//Lerp(const D3DXCOLOR &c1, const D3DXCOLOR &c2, float s)
+//{
+//    return c1 + s * (c2 - c1);
+//}
+//
+//inline D3DXCOLOR
+//Desaturate(const D3DXCOLOR &rgba)
+//{
+//    float alpha = rgba.a;
+//    if (alpha > 1.f)
+//        alpha = 1.f;
+//    float fMaxChan = MaxChannel(rgba);
+//    if (fMaxChan > alpha) 
+//    {
+//        D3DXCOLOR rgbGray(alpha, alpha, alpha, alpha);
+//        float fYOld = Luminance(rgba);
+//        if (fYOld >= alpha)
+//            return rgbGray;
+//        // scale color to preserve hue
+//        D3DXCOLOR rgbNew;
+//        float fInvMaxChan = 1.f / fMaxChan;
+//        rgbNew.r = rgba.r * fInvMaxChan;
+//        rgbNew.g = rgba.g * fInvMaxChan;
+//        rgbNew.b = rgba.b * fInvMaxChan;
+//        rgbNew.a = alpha;
+//        float fYNew = Luminance(rgbNew);
+//        // add gray to preserve luminance
+//        return Lerp(rgbNew, rgbGray, (fYOld - fYNew) / (alpha - fYNew));
+//    }
+//    return rgba;
+//}
+//
+//inline D3DXCOLOR &operator *=(D3DXCOLOR &p, const D3DXCOLOR &q)
+//{
+//    p.r *= q.r;
+//    p.g *= q.g;
+//    p.b *= q.b;
+//    p.a *= q.a;
+//    return p;
+//}
+//
+//inline D3DXCOLOR operator *(const D3DXCOLOR &p, const D3DXCOLOR &q)
+//{
+//    D3DXCOLOR r;
+//    r.r = p.r * q.r;
+//    r.g = p.g * q.g;
+//    r.b = p.b * q.b;
+//    r.a = p.a * q.a;
+//    return r;
+//}
+//
+////-----------------------------------------------------------------------------
+////  HairLighting
+////-----------------------------------------------------------------------------
+//struct HairLighting {
+//    // like D3DMATERIAL8, except populated with D3DXCOLOR's so that color arithmetic works
+//    D3DXCOLOR m_colorDiffuse;
+//    D3DXCOLOR m_colorSpecular;
+//    float m_fSpecularExponent;
+//    D3DXCOLOR m_colorAmbient;
+//    D3DXCOLOR m_colorEmissive;
+//
+//    HRESULT Initialize(LPDIRECT3DDEVICE8 pDevice, D3DMATERIAL8 *pMaterial);
+//    HRESULT CalculateColor(D3DXCOLOR *pColor, float LT, float HT);
+//    HRESULT CalculateClampedColor(D3DXCOLOR *pColor, float LT, float HT);
+//};
+//
+//HRESULT HairLighting::Initialize(LPDIRECT3DDEVICE8 pDevice, D3DMATERIAL8 *pMaterial)
+//{
+//    // Get current colors modulated by light 0 and global ambient
+//    // ignore current alphas
+//    m_colorDiffuse = D3DXCOLOR(pMaterial->Diffuse.r, pMaterial->Diffuse.g, pMaterial->Diffuse.b, 1.f);
+//    m_colorSpecular = D3DXCOLOR(pMaterial->Specular.r, pMaterial->Specular.g, pMaterial->Specular.b, 1.f);
+//    m_fSpecularExponent = pMaterial->Power;
+//    m_colorAmbient = D3DXCOLOR(pMaterial->Ambient.r, pMaterial->Ambient.g, pMaterial->Ambient.b, 1.f);
+//    m_colorEmissive = D3DXCOLOR(pMaterial->Emissive.r, pMaterial->Emissive.g, pMaterial->Emissive.b, 1.f);
+//    return S_OK;
+//}
+//
+///* Zockler et al's technique worked with streamlines, and used dot(V,T) instead of the simpler dot(H,T) */
+//#define ZOCKLER 0 
+//#if ZOCKLER
+//
+//HRESULT HairLighting::CalculateColor(D3DXCOLOR *pColor, float LT, float VT)
+//{
+//    // Zockler et al 1996
+//    float fDiffuseExponent = 2.f;    // Banks used 4.8
+//    float fDiffuse = powf(sqrtf(1.f - LT*LT), fDiffuseExponent);
+//    float VR = LT*VT - sqrtf(1.f - LT*LT) * sqrtf(1.f - VT*VT);
+//    float fSpecular = powf(VR, m_fSpecularExponent);
+//    *pColor = m_colorEmissive + m_colorAmbient + fSpecular * m_colorSpecular + fDiffuse * m_colorDiffuse;
+//    return S_OK;
+//}
+//
+//#else
+//
+//HRESULT HairLighting::CalculateColor(D3DXCOLOR *pColor, float LT, float HT)
+//{
+//    float fDiffuseExponent = 2.f;    // Banks used 4.8
+//    float fDiffuse = powf(sqrtf(1.f - LT*LT), fDiffuseExponent);
+//    float fSpecular = powf(sqrtf(1.f - HT*HT), m_fSpecularExponent);
+//    *pColor = m_colorEmissive + m_colorAmbient + fSpecular * m_colorSpecular + fDiffuse * m_colorDiffuse;
+//    return S_OK;
+//}
+//
+//#endif
+//
+//HRESULT HairLighting::CalculateClampedColor(D3DXCOLOR *pColor, float LT, float HT)
+//{
+//    D3DXCOLOR color;
+//    HRESULT hr = CalculateColor(&color, LT, HT);
+//    *pColor = Desaturate(color);    // bring back to 0,1 range
+//    return hr;
+//}
+//
+////////////////////////////////////////////////////////////////////////
+//// 
+//// Create a hair lighting lookup-table texture
+//// 
+////////////////////////////////////////////////////////////////////////
+//HRESULT FillHairLightingTexture(D3DMATERIAL8 *pMaterial, LPDIRECT3DTEXTURE8 pTexture)
+//{
+//    /* 
+//    The hair lighting texture maps U as the dot product of the hair
+//    tangent T with the light direction L and maps V as the dot product
+//    of the tangent with the half vector H.  Since the lighting is a
+//    maximum when the tangent is perpendicular to L (or H), the maximum
+//    is at zero.  The minimum is at 0.5, 0.5 (or -0.5, -0.5, since
+//    wrapping is turned on.)  For the mapped T.H, we raise the map
+//    value to a specular power.
+//    */
+//    HRESULT hr;
+//    D3DSURFACE_DESC desc;
+//    pTexture->GetLevelDesc(0, &desc);
+//    if (desc.Format != D3DFMT_A8R8G8B8)
+//        return E_NOTIMPL;
+//    DWORD dwPixelStride = 4;
+//    D3DLOCKED_RECT lockedRect;
+//    hr = pTexture->LockRect(0, &lockedRect, NULL, 0l);
+//    if (FAILED(hr))
+//        return hr;
+//    HairLighting lighting;
+//    lighting.Initialize(NxNgc::EngineGlobals.p_Device, pMaterial);
+//    Swizzler s(desc.Width, desc.Height, 0);
+//    s.SetV(s.SwizzleV(0));
+//    s.SetU(s.SwizzleU(0));
+//    for (UINT v = 0; v < desc.Height; v++)
+//    {
+//        for (UINT u = 0; u < desc.Width; u++)
+//        {
+//            BYTE *p = (BYTE *)lockedRect.pBits + dwPixelStride * s.Get2D();
+//            // vertical is specular lighting
+//            // horizontal is diffuse lighting
+//            D3DXCOLOR color;
+//#if ZOCKLER
+//            // Zockler et al 1996
+//            float LT = 2.f * u / desc.Width - 1.0f;
+//            float VT = 2.f * v / desc.Height - 1.0f;
+//            lighting.CalculateClampedColor(&color, LT, VT);
+//#else
+//            float LT = 2.f * u / desc.Width;
+//            if (LT > 1.f) LT -= 2.f;
+//            float HT = 2.f * v / desc.Height;
+//            if (HT > 1.f) HT -= 2.f;
+//            lighting.CalculateClampedColor(&color, LT, HT);
+//#endif
+//            int r, g, b;
+//            r = (BYTE)(255 * color.r);
+//            g = (BYTE)(255 * color.g);
+//            b = (BYTE)(255 * color.b);
+//            *p++ = (BYTE)b;
+//            *p++ = (BYTE)g;
+//            *p++ = (BYTE)r;
+//            *p++ = 255;    // alpha
+//            s.IncU();
+//        }
+//        s.IncV();
+//    }
+//    pTexture->UnlockRect(0);
+//    return S_OK;
+//}
+//
+////-----------------------------------------------------------------------------
+//// Name: SetHairLightingMaterial
+//// Desc: Create and fill the hair lighting texture
+////-----------------------------------------------------------------------------
+//HRESULT CXBFur::SetHairLightingMaterial(D3DMATERIAL8 *pMaterial)
+//{
+//    // Create and fill the hair lighting texture
+//    HRESULT hr;
+//    m_HairLightingMaterial = *pMaterial;
+//    if (!m_pHairLightingTexture)
+//    {
+//        DWORD dwWidth = 8; // an extremely small texture suffices when the exponents are low
+//        DWORD dwHeight = 8;
+//        D3DFORMAT surfaceFormat = D3DFMT_A8R8G8B8;
+//        DWORD nMipMap = 1;
+//        hr = D3DXCreateTexture(NxNgc::EngineGlobals.p_Device, dwWidth, dwHeight, nMipMap, 0, surfaceFormat, 0, &m_pHairLightingTexture);
+//        if (FAILED(hr)) goto e_Exit;
+//    }
+//    hr = FillHairLightingTexture(pMaterial, m_pHairLightingTexture);
+//    if (FAILED(hr)) goto e_Exit;
+//e_Exit:
+//    if (FAILED(hr))
+//        SAFE_RELEASE(m_pHairLightingTexture);
+//    return hr;
+//}
+//
+////-----------------------------------------------------------------------------
+//// Choose level of detail 
+////
+//// 0       = finest detail, all slices of original source textures
+//// ...
+//// i       = reduced number of slices, N / (1 << i)
+//// i + f   = odd slices fade to clear, texLOD[2*j+1] = tex[2*j+1] * (1-f) + clear * f
+////           even slices compensate,   texLOD[2*j] = (tex[2*j+1] * f) OVER tex[2*j]
+//// i + 1   = reduced number of slices, N / (1 << (i+1))
+//// ...
+//// log2(N) = coarsest, one slice with composite of all source textures
+////-----------------------------------------------------------------------------
+//HRESULT CXBFur::SetLevelOfDetail(float fLevelOfDetail)
+//{
+//    // Choose number of LOD slices
+//    if (fLevelOfDetail < 0.f)
+//    {
+//        m_fLevelOfDetail = 0.f;
+//        m_iLOD = 0;
+//        m_fLODFraction = 0.f;
+//    }
+//    else if (fLevelOfDetail > (float)m_dwLODMax)
+//    {
+//        m_fLevelOfDetail = (float)m_dwLODMax;
+//        m_iLOD = m_dwLODMax;
+//        m_fLODFraction = 0.f;
+//    }
+//    else
+//    {
+//        m_fLevelOfDetail = fLevelOfDetail;
+//        m_iLOD = (UINT)floorf(fLevelOfDetail);
+//        m_fLODFraction = fLevelOfDetail - (float)m_iLOD;
+//    }
+//    m_dwNumSlicesLOD = LevelOfDetailCount(m_iLOD);
+//    UINT index = LevelOfDetailIndex(m_iLOD);
+//    m_pSliceTextureLOD = m_apSliceTexture + index;
+//    return S_OK;
+//}
+//
+////-----------------------------------------------------------------------------
+////
+//// Generate level-of-detail textures by compositing together alternating layers.
+////
+////-----------------------------------------------------------------------------
+//HRESULT CXBFur::ComputeLevelOfDetailTextures()
+//{
+//    // All the textures must have the same number of mip levels.
+//    DWORD nMip = m_apSliceTexture[0]->GetLevelCount();
+//
+//    // save current back buffer and z buffer
+//    struct {
+//        IDirect3DSurface8 *pBackBuffer, *pZBuffer;
+//    } save;
+//    NxNgc::EngineGlobals.p_Device->GetRenderTarget(&save.pBackBuffer);
+//    NxNgc::EngineGlobals.p_Device->GetDepthStencilSurface(&save.pZBuffer);
+//
+//    // set render state for compositing textures
+//    NxNgc::EngineGlobals.p_Device->SetVertexShader(D3DFVF_XYZRHW|D3DFVF_TEX1);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_FOGENABLE, FALSE);
+//
+//    // use pixel shaders to composite two or three layers at a time
+//#pragma warning(push)
+//#pragma warning(disable: 4245)    // conversion from int to DWORD
+//    DWORD dwPS2 = 0;
+//    {
+//#include "comp2.inl"
+//        NxNgc::EngineGlobals.p_Device->CreatePixelShader(&psd, &dwPS2);
+//    }
+//    DWORD dwPS3 = 0;
+//    {
+//#include "comp3.inl"
+//        NxNgc::EngineGlobals.p_Device->CreatePixelShader(&psd, &dwPS3);
+//    }
+//#pragma warning(pop)
+//
+//    // set default texture stage states
+//    UINT xx; // texture stage index
+//    for (xx = 0; xx < 4; xx++)
+//    {
+//        NxNgc::EngineGlobals.p_Device->SetTexture(xx, NULL);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_COLOROP, D3DTOP_DISABLE);    // Are the COLOROP and ALPHAOP needed since we're using a pixel shader?
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);    // pass texture coords without transformation
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_TEXCOORDINDEX, 0);            // all the textures use the same tex coords
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_MAGFILTER, D3DTEXF_POINT);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_MINFILTER, D3DTEXF_POINT);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_MIPFILTER, D3DTEXF_POINT);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_MIPMAPLODBIAS, 0);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_MAXMIPLEVEL, 0);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_COLORKEYOP, D3DTCOLORKEYOP_DISABLE);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_COLORSIGN, 0);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_ALPHAKILL, D3DTALPHAKILL_DISABLE);
+//    }
+//    
+//    // Compute all the level-of-detail textures
+//    for (UINT iLOD = 1; m_dwNumSlices >> iLOD; iLOD++)
+//    {
+//        UINT nSliceSrc = LevelOfDetailCount(iLOD-1);
+//        LPDIRECT3DTEXTURE8 *apTextureSrc = m_apSliceTexture + LevelOfDetailIndex(iLOD - 1);
+//        UINT nSliceDst = LevelOfDetailCount(iLOD);
+//        LPDIRECT3DTEXTURE8 *apTextureDst = m_apSliceTexture + LevelOfDetailIndex(iLOD);
+//        
+//        // Composite source textures into LOD textures
+//        UINT iMipNotHandled = (UINT)-1;
+//        for (UINT iSliceDst = 0; iSliceDst < nSliceDst; iSliceDst++)
+//        {
+//            LPDIRECT3DTEXTURE8 pTextureDst = apTextureDst[iSliceDst];
+//            UINT nComp;
+//            if (iSliceDst == nSliceDst-1 && nSliceSrc > nSliceDst * 2)
+//            {
+//                // composite 3 textures into the top-most level when number of source textures is odd
+//                nComp = 3;
+//                NxNgc::EngineGlobals.p_Device->SetPixelShader(dwPS3);
+//            }
+//            else
+//            {
+//                // composite 2 textures (this is the default)
+//                nComp = 2;
+//                NxNgc::EngineGlobals.p_Device->SetPixelShader(dwPS2);
+//            }
+//            for (xx = 0; xx < nComp; xx++)
+//            {
+//                NxNgc::EngineGlobals.p_Device->SetTexture(xx, apTextureSrc[ iSliceDst * 2 + xx]);
+//                NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
+//                NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
+//            }
+//            for (; xx<4; xx++)
+//            {
+//                NxNgc::EngineGlobals.p_Device->SetTexture(xx, NULL);
+//                NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_COLOROP, D3DTOP_DISABLE);
+//                NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
+//            }
+//            for (UINT iMip = 0; iMip < nMip; iMip++)
+//            {
+//                DWORD width = m_dwSliceXSize / (1 << iMip);
+//                if (width == 0) width = 1;
+//                DWORD height = m_dwSliceZSize / (1 << iMip);
+//                if (height == 0) height = 1;
+//
+//                // Ngc render target must of be at least 16x16
+//                if (width*4 < 64 || width * height < 64)            
+//                {
+//                    iMipNotHandled = iMip;
+//                    break; // skip rest of coarser mipmaps and go to next slice
+//                }
+//
+//                // Use a screen space quad to do the compositing.
+//                struct quad {
+//                    float x, y, z, w;
+//                    float u, v;
+//                } aQuad[4] =
+//                {
+//                    {-0.5f,        -0.5f,         1.0f, 1.0f, 0.0f, 0.0f},
+//                    {width - 0.5f, -0.5f,         1.0f, 1.0f, 1.0f, 0.0f},
+//                    {-0.5f,        height - 0.5f, 1.0f, 1.0f, 0.0f, 1.0f},
+//                    {width - 0.5f, height - 0.5f, 1.0f, 1.0f, 1.0f, 1.0f}
+//                };
+//
+//                // get destination surface and set as render target
+//                IDirect3DSurface8 *pSurface;
+//                pTextureDst->GetSurfaceLevel(iMip, &pSurface);
+//                NxNgc::EngineGlobals.p_Device->SetRenderTarget(pSurface, NULL); // no depth buffering
+//                NxNgc::EngineGlobals.p_Device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, aQuad, sizeof(quad)); // one quad blends 2 or 3 textures
+//                pSurface->Release();
+//            }
+//        }
+//        if (iMipNotHandled > 0 && iMipNotHandled != -1)
+//        {
+//            // fill in the small mips with filtered versions of the previous levels
+//            for (UINT iSliceDst = 0; iSliceDst < nSliceDst; iSliceDst++)
+//            {
+//                LPDIRECT3DTEXTURE8 pTextureDst = apTextureDst[iSliceDst];
+//                GenerateMipmaps(pTextureDst, iMipNotHandled - 1, D3DTADDRESS_MIRROR, D3DTADDRESS_MIRROR);
+//            }
+//        }
+//    }
+//
+//    // clean up pixel shaders
+//    NxNgc::EngineGlobals.p_Device->SetPixelShader(0);
+//    NxNgc::EngineGlobals.p_Device->DeletePixelShader(dwPS2);
+//    NxNgc::EngineGlobals.p_Device->DeletePixelShader(dwPS3);
+//    
+//    // restore render states
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_LIGHTING, TRUE);
+//    NxNgc::EngineGlobals.p_Device->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL);
+//
+//    // restore texture stage states
+//    for (xx=0; xx<4; xx++)
+//    {
+//        NxNgc::EngineGlobals.p_Device->SetTexture(xx, NULL);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_COLOROP, D3DTOP_DISABLE);
+//        NxNgc::EngineGlobals.p_Device->SetTextureStageState(xx, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
+//    }
+//
+//    // restore back buffer and z buffer
+//    NxNgc::EngineGlobals.p_Device->SetRenderTarget(save.pBackBuffer, save.pZBuffer);
+//    save.pBackBuffer->Release();
+//    save.pZBuffer->Release();
+//    return S_OK;
+//}
+//
+////-----------------------------------------------------------------------------
+////  Divide alpha channel into color channel.  Leave alpha unchanged.
+////-----------------------------------------------------------------------------
+//HRESULT AlphaDivide(LPDIRECT3DTEXTURE8 pTexture)
+//{
+//    HRESULT hr;
+//    DWORD nMip = pTexture->GetLevelCount();
+//    for (UINT iMip = 0; iMip < nMip; iMip++)
+//    {
+//        D3DSURFACE_DESC desc;
+//        hr = pTexture->GetLevelDesc(iMip, &desc);
+//        if (FAILED(hr))
+//            return hr;
+//        if (desc.Format != D3DFMT_A8R8G8B8)
+//            return E_NOTIMPL;
+//        D3DLOCKED_RECT lockedRect;
+//        hr = pTexture->LockRect(iMip, &lockedRect, NULL, 0l);
+//        if (FAILED(hr))
+//            return hr;
+//        UINT dwPixelSize = 4;
+//        UINT nPixel = desc.Size / dwPixelSize;
+//        DWORD *pPixel = (DWORD *)lockedRect.pBits;
+//        while (nPixel--)
+//        {
+//            D3DXCOLOR c(*pPixel);
+//            if (c.a > 0.f)
+//            {
+//                D3DXCOLOR d;
+//                d.r = c.r / c.a;
+//                d.g = c.g / c.a;
+//                d.b = c.b / c.a;
+//                d.a = c.a;
+//                *pPixel = d;
+//            }
+//            pPixel++;
+//        }
+//        pTexture->UnlockRect(iMip);
+//    }
+//    return S_OK;
+//}
+//
+////-----------------------------------------------------------------------------
+////  Multiply alpha channel into color channel.  Leave alpha unchanged.
+////-----------------------------------------------------------------------------
+//HRESULT AlphaMultiply(LPDIRECT3DTEXTURE8 pTexture)
+//{
+//    HRESULT hr;
+//    DWORD nMip = pTexture->GetLevelCount();
+//    for (UINT iMip = 0; iMip < nMip; iMip++)
+//    {
+//        D3DSURFACE_DESC desc;
+//        hr = pTexture->GetLevelDesc(iMip, &desc);
+//        if (FAILED(hr))
+//            return hr;
+//        if (desc.Format != D3DFMT_A8R8G8B8)
+//            return E_NOTIMPL;
+//        D3DLOCKED_RECT lockedRect;
+//        hr = pTexture->LockRect(iMip, &lockedRect, NULL, 0l);
+//        if (FAILED(hr))
+//            return hr;
+//        UINT dwPixelSize = 4;
+//        UINT nPixel = desc.Size / dwPixelSize;
+//        DWORD *pPixel = (DWORD *)lockedRect.pBits;
+//        while (nPixel--)
+//        {
+//            D3DXCOLOR c(*pPixel);
+//            D3DXCOLOR d;
+//            d.r = c.r * c.a;
+//            d.g = c.g * c.a;
+//            d.b = c.b * c.a;
+//            d.a = c.a;
+//            *pPixel = d;
+//            pPixel++;
+//        }
+//        pTexture->UnlockRect(iMip);
+//    }
+//    return S_OK;
+//}
+//
+////-----------------------------------------------------------------------------
+////
+//// Copy textures to new texture format
+////
+////-----------------------------------------------------------------------------
+//HRESULT CXBFur::CompressNextTexture(D3DFORMAT fmtNew, UINT *pIndex)
+//{
+//    HRESULT hr;
+//    for (UINT iTexture = 0; iTexture < XBFUR_MAXSLICE * 2 - 1; iTexture++)    // convert all the slice textures
+//    {
+//        LPDIRECT3DTEXTURE8 pTextureDst = NULL;
+//        LPDIRECT3DTEXTURE8 pTextureSrc = m_apSliceTexture[iTexture];
+//        if (pTextureSrc == NULL)
+//            break;    // we're done
+//
+//        // See if this texture needs to be compressed
+//        D3DSURFACE_DESC desc0;
+//        pTextureSrc->GetLevelDesc(0, &desc0);
+//        if (desc0.Format == fmtNew)
+//            continue;    // we've already compressed this one, go to the next
+//
+//        // We've found a texture that needs to be compressed
+//        hr = CompressTexture(&pTextureDst, fmtNew, pTextureSrc);
+//        if (FAILED(hr))
+//            return hr;
+//        m_apSliceTexture[iTexture] = pTextureDst; // already addref'd
+//        pTextureSrc->Release();    // we're done with the old texture
+//        if (pIndex != NULL)
+//            *pIndex = iTexture;    // return index of the texture we just compressed
+//        return S_FALSE;    // more textures are pending
+//    }
+//    return S_OK;
+//}
+//
+//
+//#endif
+//
+//
+//static bool grass_mats = false;
+//static NxNgc::sMaterial*	p_grass_mats[16];
+//
+//
+//static void createGrassMats( void )
+//{
+//	if( !grass_mats )
+//	{
+//		grass_mats = true;
+//
+//		for( int layer = 0; layer < 8; ++layer )
+//		{
+//			NxNgc::sTexture *p_tex		= new NxNgc::sTexture();
+//			p_tex->pD3DTexture			= p_fur->m_apSliceTexture[layer];
+//			p_tex->pD3DPalette			= NULL;
+//
+//			
+//			NxNgc::sMaterial *p_mat	= new NxNgc::sMaterial();
+//
+//			p_mat->m_no_bfc				= false;
+//			p_mat->Passes				= 1;
+//			p_mat->pTex[0]				= p_tex;
+//
+//			if( layer < 3 )
+//			{
+//				p_mat->Flags[0]				= 0x00;
+//				p_mat->RegALPHA[0]			= NxNgc::vBLEND_MODE_DIFFUSE;
+//			}
+//			else
+//			{
+//				p_mat->Flags[0]				= 0x40;	// Transparent.
+//				p_mat->RegALPHA[0]			= NxNgc::vBLEND_MODE_BLEND;
+//			}
+//
+//			p_mat->UVAddressing[0]		= 0x00000000UL;
+//			p_mat->AlphaCutoff			= 1;
+//			p_mat->Color[0][0]			= 1.0f;
+//			p_mat->Color[0][1]			= 1.0f;
+//			p_mat->Color[0][2]			= 1.0f;
+//			p_mat->Color[0][3]			= 1.0f;
+//			p_mat->m_sorted				= false;
+//			p_mat->m_draw_order			= 0;
+//			p_mat->mp_wibble_vc_colors	= NULL;
+//
+//			p_grass_mats[layer]			= p_mat;
+//		}
+//	}
+//}
+//
+
+
+
+bool AddGrass( Nx::CNgcGeom *p_geom, NxNgc::sMesh *p_mesh )
+{
+#if 0
+	bool add_grass = false;
+	
+	if( p_mesh->mp_material && p_mesh->mp_material->m_grassify )
+	{
+		add_grass = true;
+	}
+	else
+	{
+		return false;
+	}
+
+	// At this stage we know we want to add grass.
+	Dbg_Assert( p_mesh->mp_material->m_grass_layers > 0 );
+//	float height_per_layer = p_mesh->mp_material->m_grass_height / p_mesh->mp_material->m_grass_layers;
+	
+	for( int layer = 0; layer < p_mesh->mp_material->m_grass_layers; ++layer )
+	{
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().BottomUpHeap());
+		NxNgc::sMesh *p_grass_mesh = p_mesh->Clone( false );
+		Mem::Manager::sHandle().PopContext();
+
+		// No longer any shadow cast on the base mesh.
+//		p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		
+		if( layer != 2 )
+			p_grass_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		
+		// Now point the mesh to a different material, first build the material checksum from the name of the parent
+		// material (Grass) and the name of the sub material (Grass_LayerX)...
+		char material_name[64];
+		sprintf( material_name, "Grass-Grass_Layer%d", layer );
+		uint32 material_checksum	= Crc::GenerateCRCCaseSensitive( material_name, strlen( material_name ));
+
+		// ...then use the checksum to lookup the material.
+		p_grass_mesh->mp_material	= p_geom->mp_scene->GetEngineScene()->GetMaterial( material_checksum );
+
+		// We need also to override the mesh pixel shader, which may have been setup for a multipass material.
+//		NxNgc::GetPixelShader( p_grass_mesh->mp_material, &p_grass_mesh->m_pixel_shader, &p_grass_mesh->m_pixel_shader_no_mcm );
+		
+		// Add transparent flag to the material.
+		p_grass_mesh->mp_material->Flags[0]		|= 0x40;
+
+		// Set the draw order to ensure meshes are drawn from the bottom up.
+		p_grass_mesh->mp_material->m_sorted		 = false;
+		p_grass_mesh->mp_material->m_draw_order	 = layer * 0.1f;
+		
+		// Go through and move all the vertices up a little, and calculate new texture coordinates based on the x-z space of the vertex positions.
+//		char *p_byte_dest;
+//		p_grass_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_byte_dest, 0 );
+//
+//		for( uint32 vert = 0; vert < p_grass_mesh->m_num_vertices; ++vert )
+//		{
+//			D3DXVECTOR3 *p_pos = (D3DXVECTOR3*)( p_byte_dest + ( vert * p_grass_mesh->m_vertex_stride ));
+//			p_pos->y += ( height_per_layer * ( layer + 1 ));
+//
+//			float u			= fabsf( p_pos->x ) / 48.0f;
+//			float v			= fabsf( p_pos->z ) / 48.0f;
+//			float *p_tex	= (float*)( p_byte_dest + p_grass_mesh->m_uv0_offset + ( vert * p_grass_mesh->m_vertex_stride ));
+//			p_tex[0]		= u;		
+//			p_tex[1]		= v;
+//		}
+
+		p_geom->AddMesh( p_grass_mesh );
+	}
+	
+/*	
+	
+	if( p_mesh->mp_material && p_mesh->mp_material->pTex[0] )
+	{
+		if( p_mesh->mp_material->Passes == 1 )
+		{
+			if( p_mesh->mp_material->pTex[0]->Checksum == 0x8d9b6a64 )
+				add_grass = true;
+		}
+	}
+
+	if( !add_grass )
+		return false;
+
+	createGrassMats();
+
+	// At this stage we know we want to add grass.
+	for( int layer = 0; layer < 6; ++layer )
+	{
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().BottomUpHeap());
+		NxNgc::sMesh *p_grass_mesh = p_mesh->Clone( false );
+		Mem::Manager::sHandle().PopContext();
+
+		// No longer any shadow cast on the base mesh.
+//		p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		
+		if( layer != 2 )
+			p_grass_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		
+		// Now point the mesh to a different material.
+		p_grass_mesh->mp_material = p_grass_mats[layer];
+
+		// Go through and move all the vertices up a little, and calculate new texture coordinates based on the x-z space of the vertex positions.
+		BYTE *p_byte_dest;
+		p_grass_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_byte_dest, 0 );
+
+		for( uint32 vert = 0; vert < p_grass_mesh->m_num_vertices; ++vert )
+		{
+			D3DXVECTOR3 *p_pos = (D3DXVECTOR3*)( p_byte_dest + ( vert * p_grass_mesh->m_vertex_stride ));
+			p_pos->y += ( 1.0f * ( layer + 1 ));
+
+			float u			= fabsf( p_pos->x ) / 48.0f;
+			float v			= fabsf( p_pos->z ) / 48.0f;
+			float *p_tex	= (float*)( p_byte_dest + p_grass_mesh->m_uv0_offset + ( vert * p_grass_mesh->m_vertex_stride ));
+			p_tex[0]		= u;		
+			p_tex[1]		= v;
+		}
+
+		p_geom->AddMesh( p_grass_mesh );
+	}
+*/
+#endif
+	return true;
+}
diff --git a/Code/Gfx/NGC/NX/grass.h b/Code/Gfx/NGC/NX/grass.h
new file mode 100644
index 0000000..ea8fe6c
--- /dev/null
+++ b/Code/Gfx/NGC/NX/grass.h
@@ -0,0 +1,151 @@
+//-----------------------------------------------------------------------------
+// File: XBFur.h
+//
+// Copyright (c) 2000-2001 Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+//#include "xfvf.h"
+#include 
+
+//#define XBFUR_MAXSLICE_LOG2 5
+//#define XBFUR_MAXSLICE (1 << XBFUR_MAXSLICE_LOG2)
+//
+//extern float g_fOneInch;
+//
+//#define FVF_XYZDIFF (D3DFVF_XYZ|D3DFVF_DIFFUSE)
+//
+//typedef struct sFVFT_XYZDIFF
+//{
+//    D3DVECTOR v;
+//    DWORD diff;
+//} FVFT_XYZDIFF;
+//
+//
+//// patch generation
+//
+//// a fuzz is a single hair follicle, blade of grass, etc.
+//struct Fuzz
+//{
+//    D3DVECTOR dp;            // velocity
+//    D3DVECTOR ddp;            // acceleration
+//    D3DXCOLOR colorBase;
+//    D3DXCOLOR colorTip;
+//};
+//
+//// a fuzz instance is a single instance of a fuzz
+//// located at x, z on the patch
+//// we create only a limited number of unique fuzzes
+//// and index the library with lidx.
+//struct FuzzInst
+//{
+//    float x, z;                // fuzz location
+//    int lidx;                // library index
+//};
+//
+//// a fur patch is a volume that holds fuzzes.
+//// xsize and zsize are chosen by the user
+//// ysize is calculated using the height of the 
+//// tallest fuzz
+//class CXBFur
+//{
+//    friend class CXBFurMesh;
+//public:
+//    DWORD m_dwSeed;            // patch seed
+//    
+//    float m_fXSize;            // patch size in world coords
+//    float m_fYSize;
+//    float m_fZSize;
+//
+//    // fuzz library
+//    DWORD m_dwNumSegments;    // # of segments in highest LOD
+//    Fuzz m_fuzzCenter;        // fuzz constant
+//    Fuzz m_fuzzRandom;        // random offset around center
+//    DWORD m_dwNumFuzzLib;    // # of fuzz in the library
+//    Fuzz *m_pFuzzLib;        // fuzz library
+//
+//    // fuzz instances
+//    DWORD m_dwNumFuzz;        // # of fuzz in this patch
+//    FuzzInst *m_pFuzz;
+//
+//    // patch volume
+//    DWORD m_dwNumSlices;    // # of layers in the volume
+//    DWORD m_dwSliceSize;        // width*height
+//    DWORD m_dwSliceXSize;        // width of volume texture slice
+//    DWORD m_dwSliceZSize;        // height of volume texture slice
+//    LPDIRECT3DTEXTURE8 m_apSliceTexture[XBFUR_MAXSLICE * 2 - 1];    // slices of volume texture
+//                    // ... followed by level-of-detail textures  N/2, N/4, N/8, ... 1
+//
+//    // LOD textures
+//    DWORD m_dwNumSlicesLOD; // number of slices in current level of detail
+//    float m_fLevelOfDetail;    // current LOD value
+//    DWORD m_iLOD;            // current integer LOD value
+//    float m_fLODFraction;    // fraction towards next coarser level-of-detail
+//    DWORD m_dwLODMax;        // maximum LOD index
+//    LPDIRECT3DTEXTURE8 *m_pSliceTextureLOD; // current level of detail pointer into m_apSliceTexture array
+//
+//    // hair lighting texture
+//    D3DMATERIAL8 m_HairLightingMaterial;
+//    LPDIRECT3DTEXTURE8 m_pHairLightingTexture;
+//
+//    // fin texture
+//    DWORD m_finWidth, m_finHeight;        // size of fin texture
+//    float m_fFinXFraction, m_fFinZFraction;    // portion of hair texture to put into fin
+//    LPDIRECT3DTEXTURE8 m_pFinTexture;    // texture projected from the side
+//
+//    CXBFur();
+//    ~CXBFur();
+//    void InitFuzz(DWORD nfuzz, DWORD nfuzzlib);
+//    void GenSlices(DWORD nslices, DWORD slicexsize, DWORD slicezsize);
+//    void GenFin(DWORD finWidth, DWORD finHeight, float fFinXFraction, float fFinZFraction);
+//    void GetLinesVertexBuffer(IDirect3DVertexBuffer8 **ppVB);
+//    void RenderLines();
+//    void Save(char *fname, int flags);
+//    void Load(char *fname);
+//    HRESULT SetHairLightingMaterial(D3DMATERIAL8 *pMaterial);
+//    void SetPatchSize(float x, float z)
+//    {
+//        m_fXSize = x;
+//        m_fZSize = z;
+//        InitFuzz(m_dwNumFuzz, m_dwNumFuzzLib);    // re-init the fuzz. automatically sets ysize
+//    };
+//    void SetFVel(float cx, float cy, float cz, float rx, float ry, float rz)
+//    {
+//        m_fuzzCenter.dp.x = cx; m_fuzzCenter.dp.y = cy; m_fuzzCenter.dp.z = cz;
+//        m_fuzzRandom.dp.x = rx; m_fuzzRandom.dp.y = ry; m_fuzzRandom.dp.z = rz;
+//    };
+//    void SetFAcc(float cx, float cy, float cz, float rx, float ry, float rz)
+//    {
+//        m_fuzzCenter.ddp.x = cx; m_fuzzCenter.ddp.y = cy; m_fuzzCenter.ddp.z = cz;
+//        m_fuzzRandom.ddp.x = rx; m_fuzzRandom.ddp.y = ry; m_fuzzRandom.ddp.z = rz;
+//    };
+//
+//    // fLevelOfDetail can range from 0 to log2(NumSlices)
+//    HRESULT SetLevelOfDetail(float fLevelOfDetail);
+//    HRESULT ComputeLevelOfDetailTextures();
+//    inline UINT LevelOfDetailCount(UINT iLOD)
+//    {
+//        return m_dwNumSlices >> iLOD;
+//    }
+//    inline UINT LevelOfDetailIndex(UINT iLOD)
+//    {
+//        UINT offset = 0;
+//        for (UINT i = 1; i <= iLOD; i++)
+//            offset += LevelOfDetailCount(i-1);
+//        return offset;
+//    }
+//    inline UINT TotalTextureCount()
+//    {
+//        UINT TextureCount = 0;
+//        for (UINT iLOD = 0; m_dwNumSlices >> iLOD; iLOD++)
+//            TextureCount += LevelOfDetailCount(iLOD);
+//        return TextureCount;
+//    }
+//
+//    // Compress textures one at a time until all are done.
+//    // Returns S_OK when all the textures are in fmtNew format.
+//    // Returns S_FALSE if there are textures still to be done.
+//    HRESULT CompressNextTexture(D3DFORMAT fmtNew, UINT *pTextureIndex);
+//};
+//
+//HRESULT FillHairLightingTexture(D3DMATERIAL8 *pMaterial, LPDIRECT3DTEXTURE8 pTexture);
+bool AddGrass( Nx::CNgcGeom *p_geom, NxNgc::sMesh *p_mesh );
+
diff --git a/Code/Gfx/NGC/NX/import.cpp b/Code/Gfx/NGC/NX/import.cpp
new file mode 100644
index 0000000..5b3df47
--- /dev/null
+++ b/Code/Gfx/NGC/NX/import.cpp
@@ -0,0 +1,224 @@
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "gfx/ngc/p_nxtexture.h"
+#include "mesh.h"
+#include "import.h"
+	
+namespace NxNgc
+{
+
+uint8 *pData, *TextureLoadPos;
+
+//----------------------------------------------------------------------------------------
+//		L O A D   T E X T U R E   F I L E
+//----------------------------------------------------------------------------------------
+
+void LoadTextureFile( const char* Filename, Lst::HashTable * p_texture_table )
+{
+	uint32 temp;
+	
+	Dbg_Message( "Loading texture file %s\n", Filename );
+
+	// Open the texture file.
+	void *p_FH = File::Open( Filename, "rb" );
+	if( !p_FH )
+	{
+		Dbg_Message( "Couldn't open texture file %s\n", Filename );
+//		return 0;
+		return;
+	}
+
+	// Read the texture file version.
+	int version;
+	File::Read( &version, sizeof( int ), 1, p_FH );
+
+	// Read the number of textures.	
+	int num_textures;
+	File::Read( &num_textures, sizeof( int ), 1, p_FH );
+	
+//	NumTextures				+= num_textures;
+
+	NxNgc::sTexture ** p_tex = new (Mem::Manager::sHandle().TopDownHeap()) NxNgc::sTexture *[num_textures];
+	unsigned short * p_resolvem = new (Mem::Manager::sHandle().TopDownHeap()) unsigned short[num_textures];
+	unsigned short * p_resolvec = new (Mem::Manager::sHandle().TopDownHeap()) unsigned short[num_textures];
+	int num_resolve = 0;
+
+	for( int t = 0; t < num_textures; ++t )
+	{
+		sTexture *p_texture = new sTexture;
+		
+		p_tex[t] = p_texture;
+
+		File::Read( &p_texture->Checksum,		sizeof( uint32 ), 1, p_FH );
+		File::Read( &temp,		sizeof( uint32 ), 1, p_FH );
+		p_texture->BaseWidth = (uint16)temp;
+		File::Read( &temp,		sizeof( uint32 ), 1, p_FH );
+		p_texture->BaseHeight = (uint16)temp;
+		File::Read( &temp,		sizeof( uint32 ), 1, p_FH );
+		p_texture->Levels = (uint16)temp;
+
+		p_texture->ActualWidth = ( p_texture->BaseWidth + 3 ) & ~3;
+		p_texture->ActualHeight = ( p_texture->BaseHeight + 3 ) & ~3;
+
+		int tex_format;
+		int channel;
+		int index;
+		File::Read( &tex_format, sizeof( uint32 ), 1, p_FH );
+		channel = ( tex_format >> 8 ) & 0xff;
+		index = ( tex_format >> 16 ) & 0xffff;
+		tex_format = tex_format & 0xff;
+
+		switch ( tex_format ) {
+			case 0:
+				p_texture->format = GX_TF_CMPR;
+				break;
+			case 1:
+				p_texture->format = GX_TF_CMPR;
+				break;
+			case 2:
+				p_texture->format = GX_TF_RGBA8;
+				break;
+			default:
+				Dbg_MsgAssert( false, ("Illegal texture format: %d\n", tex_format ));
+				break;
+		}
+
+		p_texture->flags = 0;
+
+		int has_holes;
+		File::Read( &has_holes, sizeof( uint32 ), 1, p_FH );
+		p_texture->flags |= has_holes ? NxNgc::sTexture::TEXTURE_FLAG_HAS_HOLES : 0;
+
+		uint8 * p8[8];
+		int bytes;
+		int mipbytes[8];
+
+		// Read color maps.
+		bytes = 0;
+		for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+		{
+			uint32 texture_level_data_size;
+			File::Read( &texture_level_data_size,			sizeof( uint32 ), 1, p_FH );
+
+			p8[mip_level] = new (Mem::Manager::sHandle().TopDownHeap()) uint8[texture_level_data_size];
+			mipbytes[mip_level] = texture_level_data_size;
+			bytes += texture_level_data_size;
+
+			File::Read( p8[mip_level], texture_level_data_size, 1, p_FH );
+		}
+		// Copy all textures & delete the originals.
+		Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+		p_texture->pTexelData = new uint8[bytes];
+		p_texture->byte_size = bytes;
+		Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+		uint8 * pTex = p_texture->pTexelData;
+		for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+			{
+			memcpy( pTex, p8[mip_level], mipbytes[mip_level] );
+			delete p8[mip_level];
+			pTex += mipbytes[mip_level];
+		}
+
+		// Read alpha maps.
+		if ( tex_format == 1 )
+		{
+			if ( channel == 0 )
+			{
+				bytes = 0;
+				for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+				{
+					uint32 texture_level_data_size;
+					File::Read( &texture_level_data_size,			sizeof( uint32 ), 1, p_FH );
+
+					p8[mip_level] = new (Mem::Manager::sHandle().TopDownHeap()) uint8[texture_level_data_size];
+					mipbytes[mip_level] = texture_level_data_size;
+					bytes += texture_level_data_size;
+
+					File::Read( p8[mip_level], texture_level_data_size, 1, p_FH );
+				}
+				// Copy all textures & delete the originals.
+				Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+				p_texture->pAlphaData = new uint8[bytes];
+				Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+				uint8 * pTex = p_texture->pAlphaData;
+				for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+				{
+					memcpy( pTex, p8[mip_level], mipbytes[mip_level] );
+					delete p8[mip_level];
+					pTex += mipbytes[mip_level];
+				}
+				p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA;
+			}
+			else
+			{
+				p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA;
+
+				p_resolvem[num_resolve] = t;
+				p_resolvec[num_resolve] = index;
+				num_resolve++;
+			}
+			switch ( channel )
+			{
+				case 0:
+				default:
+					p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN;
+					break;
+				case 1:
+					p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_RED;
+					break;
+				case 2:
+					p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_BLUE;
+					break;
+			}
+			p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER;
+		}
+		else
+		{
+			// No unique alpha map.
+			p_texture->pAlphaData = NULL;
+			p_texture->flags &= ~NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA;
+			p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN;
+		}
+
+		// Add this texture to the table.
+//		pTextureTable->PutItem( p_texture->Checksum, p_texture );
+		Nx::CNgcTexture *p_Ngc_texture = new Nx::CNgcTexture();
+		p_Ngc_texture->SetEngineTexture( p_texture );
+		if ( p_texture_table )
+		{
+			p_texture_table->PutItem( p_texture->Checksum, p_Ngc_texture );
+		}
+	}
+
+	// Resolve alpha maps.
+	for ( int r = 0; r < num_resolve; r++ )
+	{
+		p_tex[p_resolvem[r]]->pAlphaData = p_tex[p_resolvec[r]]->pAlphaData;
+		p_tex[p_resolvec[r]]->flags &= ~NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER;
+	}
+
+	File::Close( p_FH );
+
+	delete p_tex;
+	delete p_resolvem;
+	delete p_resolvec;
+
+//	return pTextureTable;
+}
+
+
+
+
+//void Error(int err)
+//{
+//	printf("Error = %d\n", err);
+//	exit(1);
+//}
+
+} // namespace NxNgc
+
diff --git a/Code/Gfx/NGC/NX/import.h b/Code/Gfx/NGC/NX/import.h
new file mode 100644
index 0000000..34b0a0e
--- /dev/null
+++ b/Code/Gfx/NGC/NX/import.h
@@ -0,0 +1,17 @@
+#ifndef __IMPORT_H
+#define __IMPORT_H
+
+#include 
+#include "texture.h"
+#include "gfx/nxtexture.h"
+
+namespace NxNgc
+{
+
+void LoadTextureFile( const char* Filename, Lst::HashTable * p_texture_table );
+
+} // namespace NxNgc
+
+
+#endif // __IMPORT_H
+
diff --git a/Code/Gfx/NGC/NX/instance.cpp b/Code/Gfx/NGC/NX/instance.cpp
new file mode 100644
index 0000000..dd3e1dc
--- /dev/null
+++ b/Code/Gfx/NGC/NX/instance.cpp
@@ -0,0 +1,1190 @@
+#include "nx_init.h"
+#include "instance.h"
+#include "anim.h"
+#include "render.h"
+#include "gfx\camera.h"
+#include "gfx\nx.h"
+#include "occlude.h"
+#include "charpipeline\skinning.h"
+#include "sys/ngc/p_gx.h"
+#include "sys/ngc/p_aram.h"
+#include "sys/ngc/p_dma.h"
+#include 
+#include "sys/ngc/p_buffer.h"
+#include 
+#include "sys/ngc/p_display.h"
+
+#include "dolphin/base/ppcwgpipe.h"
+#include "dolphin/gx/gxvert.h"
+
+bool g_in_cutscene = false;
+
+int g_twiddle = 900;
+
+int g_hash_items = 0;
+
+int g_count = 0;
+
+float g_tweak = 0.0f;
+
+float NEAR_Z = 50.0f;
+float FAR_Z = 150.0f;
+
+float MIN_LEN = 10.0f;
+float MAX_LEN = 80.0f;
+
+extern int gNewRender;
+extern int g_object;
+
+extern bool g_mc_hack;
+
+//#define DEBUG_SPHERES
+
+extern "C" {
+
+extern void TransformSingle	( ROMtx m, s16 * srcBase, s16 * dstBase, u32 count );
+extern void TransformDouble	( ROMtx m0, ROMtx m1, s16 * wtBase, s16 * srcBase, s16 * dstBase, u32 count );
+extern void TransformAcc	( ROMtx m, u16 count, s16 * srcBase, s16 * dstBase, u16 * indices, s16 * weights );
+extern void ConvertFixed196ToFloat	( s16 * p_source, float * p_dest, int count );
+extern void CalculateDotProducts	( s16 * p_xyz, u16 * p_index_list, float * p_dot, int count, float px, float py, float pz );
+extern void RenderShadows			( s16 * p_xyz, u16 * p_index_list, NxNgc::sShadowEdge * p_neighbor_list, float * p_dot, int count, float px, float py, float pz, float tx, float ty, float tz );
+
+}
+
+//static char doubleBuffer0[(100*1024)+32];
+//static char doubleBuffer1[(100*1024)+32];
+//static uint32 *p_double[2] = { (uint32 *)OSRoundUp32B(doubleBuffer0), (uint32 *)OSRoundUp32B(doubleBuffer1) };
+//
+//static volatile bool	dmaComplete;
+//
+//static void arqCallback( u32 pointerToARQRequest )
+//{
+//	dmaComplete = true;
+//}
+
+namespace NxNgc
+{
+
+
+//Lst::HashTable< CInstance > *pInstanceTable	= NULL;
+
+static Mth::Matrix	*pLastBoneTransforms	= NULL;
+CInstance			*pFirstInstance			= NULL;
+#if 0
+static CInstance	*pAnimInstance[256];
+static int			instanceCount = 0;
+#endif
+
+#define INSTANCE_ARRAY_SIZE	2048
+static CInstance *p_instances[INSTANCE_ARRAY_SIZE];
+int current_index = 0;
+
+void InitialiseInstanceTable( void )
+{
+//	if( pInstanceTable == NULL )
+//	{
+//		pInstanceTable = new Lst::HashTable( 8 );
+//	}
+}
+
+
+//bool frustum_check_sphere( NsVector * p_center, float radius )
+//{
+//	bool visible = true;
+//
+//	NsVector out;
+//
+//	EngineGlobals.local_to_camera.multiply( p_center, &out );
+//
+//	float x = out.x;
+//	float y = out.y;
+//	float z = out.z;
+//	float R = radius;
+//	
+//	if (RGetBoneTransforms();
+//	Mth::Matrix *p_mat2			= p_mesh2->GetBoneTransforms();
+	
+	// Setup some required pointers for both transforms.
+	uint32 p_mat1 = (uint32)p_mesh1->GetScene()->mp_dl->mp_object_header->m_num_skin_verts;
+	uint32 p_mat2 = (uint32)p_mesh2->GetScene()->mp_dl->mp_object_header->m_num_skin_verts;
+
+	if(( p_mat1 == 0 ) || ( p_mesh1->GetScene()->m_numHierarchyObjects > 0 ))
+	{
+		if(( p_mat2 == NULL ) || ( p_mesh2->GetScene()->m_numHierarchyObjects > 0 ))
+		{
+			int num_st1 = p_mesh1->GetScene()->m_num_filled_meshes - p_mesh1->GetScene()->m_num_opaque_meshes;
+			int num_st2 = p_mesh2->GetScene()->m_num_filled_meshes - p_mesh2->GetScene()->m_num_opaque_meshes;
+
+			if( num_st1 == num_st2 )
+			{
+				// Try sorting on the material draw order of the first semitransparent mesh.
+				if( num_st1 > 0 )
+				{
+					NxNgc::sMaterialHeader	*p_material1 = p_mesh1->GetScene()->mpp_mesh_list[p_mesh1->GetScene()->m_num_opaque_meshes]->mp_dl->m_material.p_header;
+					NxNgc::sMaterialHeader	*p_material2 = p_mesh2->GetScene()->mpp_mesh_list[p_mesh2->GetScene()->m_num_opaque_meshes]->mp_dl->m_material.p_header; 
+
+					if( p_material1 && p_material2 )
+					{
+						return ( p_material1->m_draw_order > p_material2->m_draw_order ) ? 1 : (( p_material1->m_draw_order < p_material2->m_draw_order ) ? -1 : 0 );
+					}
+				}
+				return 0;
+			}
+	
+			return ( num_st1 > num_st2 ) ? 1 : -1;
+		}
+		return 1;
+	}
+	else if(( p_mat2 == 0 ) || ( p_mesh2->GetScene()->m_numHierarchyObjects > 0 ))
+	{
+		return -1;
+	}
+
+	// At this stage we know both instances have bone transforms.
+	if( p_mat1 == p_mat2 )
+	{
+		int num_st1 = p_mesh1->GetScene()->m_num_filled_meshes - p_mesh1->GetScene()->m_num_opaque_meshes;
+		int num_st2 = p_mesh2->GetScene()->m_num_filled_meshes - p_mesh2->GetScene()->m_num_opaque_meshes;
+	
+		if( num_st1 == num_st2 )
+			return 0;
+	
+		return ( num_st1 > num_st2 ) ? 1 : -1;
+	}
+
+	return((uint32)p_mat1 > (uint32)p_mat2 ) ? 1 : -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_instance( CInstance *p_instance, uint32 flags )
+{
+//	// Seed the static pointer off to NULL, otherwise if there is only one object with bone transforms, it will never update.
+//	pLastBoneTransforms = NULL;
+//
+//	if( p_instance->GetActive())
+//	{
+//		// Check whether this instance is visible.
+//		set_frustum_bbox_transform( p_instance->GetTransform());
+//		float sphere[4];
+//		sphere[0] = p_instance->GetScene()->m_sphere_center.x;
+//		sphere[1] = p_instance->GetScene()->m_sphere_center.y;
+//		sphere[2] = p_instance->GetScene()->m_sphere_center.z;
+//		sphere[3] = p_instance->GetScene()->m_sphere_radius;
+//		if( frustum_check_sphere( sphere ))
+//		{
+//			if( !TestSphereAgainstOccluders( (Mth::Vector*)&p_instance->GetScene()->m_sphere_center, p_instance->GetScene()->m_sphere_radius ))
+//			{
+//				p_instance->Render( flags | vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT );
+//
+////				// Restore world transform to identity.
+////				D3DDevice_SetTransform( D3DTS_WORLD, &EngineGlobals.world_matrix );
+////				set_frustum_bbox_transform( NULL );
+//			}
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_instances( uint32 flags )
+{
+	int save_g_object = g_object;
+
+	if( current_index > 0 )
+	{
+		int i = 0;
+
+//		Mth::Matrix *pLast = NULL;
+//		ROMtx bone_mtx[80];
+
+		for( ; i < current_index; ++i )
+		{
+#ifndef __NOPT_FINAL__
+			g_object = p_instances[i]->m_object_num;
+#endif		// __NOPT_FINAL__
+//			if ( ( flags & NxNgc::vRENDER_TRANSFORM ) && ( p_instances[i]->GetFlags() & NxNgc::CInstance::INSTANCE_FLAG_TRANSFORM_ME ) )
+//			{
+////				p_instances[i]->Transform( 0 /*flags*/ | vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT, bone_mtx, pLast );
+////				pLast = p_instances[i]->GetBoneTransforms();
+//				p_instances[i]->ClearFlag( NxNgc::CInstance::INSTANCE_FLAG_TRANSFORM_ME );
+//			}
+
+			if(( p_instances[i]->GetBoneTransforms() == NULL ) || ( p_instances[i]->GetScene()->m_numHierarchyObjects > 0 ))
+			{
+				break;
+			}
+
+			if(( flags & vRENDER_OPAQUE ) || (( flags & vRENDER_SEMITRANSPARENT ) && ( flags & vRENDER_INSTANCE_PRE_WORLD_SEMITRANSPARENT )))
+			{
+				p_instances[i]->Render( flags );
+//				p_instances[i]->SetFlag( NxNgc::CInstance::INSTANCE_FLAG_TRANSFORM_ME );
+			}
+		}
+
+		for( ; i < current_index; ++i )
+		{
+#ifndef __NOPT_FINAL__
+			g_object = p_instances[i]->m_object_num;
+#endif		// __NOPT_FINAL__
+
+//			if(( p_instances[i]->GetBoneTransforms()) && ( p_instances[i]->GetScene()->m_numHierarchyObjects == 0 ))
+//				break;
+
+			if(( flags & vRENDER_OPAQUE ) || (( flags & vRENDER_SEMITRANSPARENT ) && ( flags & vRENDER_INSTANCE_POST_WORLD_SEMITRANSPARENT )))
+			{
+				p_instances[i]->Render( flags );
+			}
+		}
+	}
+	g_object = save_g_object;
+}
+
+void CInstance::Transform( uint32 flags, ROMtx * p_mtx_buffer, Mth::Matrix *p_mtx_last )
+{
+//#	ifdef __USE_PROFILER__
+////	Sys::VUProfiler->PushContext( 128,0,0 );
+//#	endif		// __USE_PROFILER__
+
+//	if ( gNewRender ) render_flags |= NxNgc::vRENDER_NEW_TEST;
+
+	if( GetBoneTransforms() && ( GetScene()->m_numHierarchyObjects == 0 ) )
+	{
+		// Has bone transforms, is therefore an animated object such as a skater, pedestrian etc.
+		mp_posNormRenderBuffer = GetPosNormalBuffer( 0 );
+
+		if ( !p_mtx_last || ( GetBoneTransforms() != p_mtx_last ) )
+		{
+			float * ps;
+			float * pd;
+			for( int i = 0; i < GetNumBones(); ++i )
+			{
+				ps = (float*)&GetBoneTransforms()[i];
+				pd = (float*)&p_mtx_buffer[i][0][0];
+
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+				ps++;
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+				ps++;
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+				ps++;
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+				*pd++ = *ps++;
+			}
+		}
+
+		// Setup some required pointers for both transforms.
+//		sMesh*	p_mesh = GetScene()->mpp_mesh_list[0];
+//		if ( GetScene()->m_num_opaque_entries )
+//		{
+//			p_mesh = GetScene()->m_opaque_meshes[0];
+//		}
+//		else if ( GetScene()->m_num_semitransparent_entries )
+//		{
+//			p_mesh = GetScene()->m_semitransparent_meshes[0];
+//		}
+//		else
+//		{
+//			Dbg_MsgAssert(0, ("No meshes in scene."));
+//			p_mesh = NULL;
+//		}
+
+		// Transform the single lists.
+		uint32*	p32 = (uint32 *)GetScene()->mp_dl->mp_object_header->m_skin.p_data;
+		s16*	p_pos = mp_posNormRenderBuffer;
+		for ( uint32 lp = 0; lp < GetScene()->mp_dl->mp_object_header->m_num_single_lists; lp++ ) {
+			uint32		pairs = *p32++;
+			uint32		mtx = *p32++;
+			s16*		p_pn = (s16 *)p32;
+			ROMtxPtr	p_bone = p_mtx_buffer[mtx];
+	
+			TransformSingle( p_bone, p_pn, p_pos, pairs == 1 ? 2 : pairs ); 
+			p_pos += (6 * pairs);
+			p32 = (uint32 *)&p_pn[pairs*6];
+		}
+
+		// Transform the double lists.
+		for ( uint32 lp = 0; lp < GetScene()->mp_dl->mp_object_header->m_num_double_lists; lp++ ) {
+			uint32		pairs = *p32++;
+			uint32		mtx = *p32++;
+			s16*		p_pn = (s16 *)p32;
+			ROMtxPtr	p_bone0 = p_mtx_buffer[mtx&255];
+			ROMtxPtr	p_bone1 = p_mtx_buffer[(mtx>>8)&255];
+			s16*		p_weight = (s16 *)&p_pn[(6*pairs)];
+	
+			TransformDouble( p_bone0, p_bone1, p_weight, p_pn, p_pos, pairs == 1 ? 2 : pairs ); 
+			p_pos += (6 * pairs);
+			p32 = (uint32 *)&p_weight[pairs*2];
+		}
+
+		// Transform the acc lists.
+		for ( uint32 lp = 0; lp < GetScene()->mp_dl->mp_object_header->m_num_add_lists; lp++ ) {
+			uint32		singles = *p32++;
+			uint32		mtx = *p32++;
+			s16*		p_pn = (s16 *)p32;
+			ROMtxPtr	p_bone = p_mtx_buffer[mtx];
+			s16*		p_weight = (s16 *)&p_pn[(6*singles)];
+			uint16*		p_index = (uint16 *)&p_weight[singles];
+			
+			TransformAcc( p_bone, singles, p_pn, mp_posNormRenderBuffer, p_index, p_weight ); 
+//			p32 = (uint32 *)&p_index[singles+(singles&1)];
+			p32 = (uint32 *)&p_index[singles];
+		}
+
+
+
+//		int num_vertex;
+//		if ( mp_scene->m_num_opaque_entries )
+//		{
+//			num_vertex = mp_scene->m_opaque_meshes[0]->m_num_vertex;
+//		}
+//		else if ( mp_scene->m_num_semitransparent_entries )
+//		{
+//			num_vertex = mp_scene->m_semitransparent_meshes[0]->m_num_vertex;
+//		}
+//		else
+//		{
+//			Dbg_MsgAssert(0, ("No meshes in scene."));
+//			num_vertex = 0;
+//		}
+//
+		int num_vertex = GetScene()->mp_dl->mp_object_header->m_num_skin_verts;
+		DCFlushRange ( mp_posNormRenderBuffer, sizeof ( s16 ) * 6 * ( num_vertex + 1 ) );
+	}
+//#	ifdef __USE_PROFILER__
+////	Sys::VUProfiler->PopContext(  );
+//#	endif		// __USE_PROFILER__
+}
+
+
+
+void CInstance::Render( uint32 flags )
+{
+	uint32 render_flags = flags;
+//	if ( gNewRender ) render_flags |= NxNgc::vRENDER_NEW_TEST;
+
+	NsMatrix	root;
+
+	root.setRightX( GetTransform()->GetRight()[X] );
+	root.setRightY( GetTransform()->GetUp()[X] );
+	root.setRightZ( GetTransform()->GetAt()[X] );
+
+	root.setUpX( GetTransform()->GetRight()[Y] ); 
+	root.setUpY( GetTransform()->GetUp()[Y] );    
+	root.setUpZ( GetTransform()->GetAt()[Y] );    
+
+	root.setAtX( GetTransform()->GetRight()[Z] ); 
+	root.setAtY( GetTransform()->GetUp()[Z] );    
+	root.setAtZ( GetTransform()->GetAt()[Z] );    
+
+	root.setPosX( GetTransform()->GetPos()[X] );
+	root.setPosY( GetTransform()->GetPos()[Y] );
+	root.setPosZ( GetTransform()->GetPos()[Z] );
+
+//	Mth::Vector world_sphere(GetScene()->m_sphere_center.x,GetScene()->m_sphere_center.y,GetScene()->m_sphere_center.z);
+//	world_sphere[W] = 1.0f;
+//	world_sphere *= *GetTransform();							// => use mp_transform
+//	if (TestSphereAgainstOccluders( &world_sphere, GetScene()->m_sphere_radius ))
+//	{
+//		return;
+//	}
+
+	// Create & upload current view matrix.
+	NsMatrix	tr;
+	tr.cat( EngineGlobals.world_to_camera, root );
+	GX::LoadPosMtxImm( (MtxPtr)&tr, GX_PNMTX0 );
+
+	// Need to force the current matrix to be just incase hierarchical models change it to 1.
+	if ( render_flags & vRENDER_SHADOW_1ST_PASS )
+	{
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+	}
+
+//	if ( !( render_flags & vRENDER_SHADOW_1ST_PASS ) )
+	{
+		//NsMatrix inv;
+		//inv.invert( root );
+		//GX::LoadNrmMtxImm((MtxPtr)&inv, GX_PNMTX0);
+//		tr.cat( EngineGlobals.camera, root );
+//		tr.invert( root/*EngineGlobals.world_to_camera*/ );
+	
+		Nx::CModelLights *p_lights = GetModel() ? GetModel()->GetModelLights() : NULL;
+		if( p_lights )
+		{
+			p_lights->UpdateEngine( GetTransform()->GetPos(), true );
+		}
+		else
+		{
+			Nx::CLightManager::sUpdateEngine();
+		}
+	}
+	MTXCopy ( (MtxPtr)&tr, EngineGlobals.current_uploaded );
+
+	EngineGlobals.local_to_camera.copy( tr );
+
+	EngineGlobals.object_pos.x = tr.getPosX();
+	EngineGlobals.object_pos.y = tr.getPosY();
+	EngineGlobals.object_pos.z = tr.getPosZ();
+
+
+
+
+#ifdef DEBUG_SPHERES
+	if( GetBoneTransforms() && ( GetScene()->m_numHierarchyObjects == 0 ) )
+	{
+		Mth::Vector sphere = GetScene()->mpp_mesh_list[0]->mp_dl->mp_object_header->m_sphere;
+		debug_render_sphere( sphere, (GXColor){255,0,0,255} );
+	}
+	else
+	{
+		if ( GetScene()->m_numHierarchyObjects && GetBoneTransforms() )
+		{
+			// Car or something...
+			Mth::Vector sphere = GetScene()->m_sphere;	//mpp_mesh_list[0]->mp_dl->mp_object_header->m_sphere;
+			debug_render_sphere( sphere, (GXColor){0,255,0,255} );
+		}
+		else
+		{
+			// No bones, just render normally. Must be a trashcan or something.
+			Mth::Vector sphere = GetScene()->mpp_mesh_list[0]->mp_dl->mp_object_header->m_sphere;
+			debug_render_sphere( sphere, (GXColor){0,0,255,255} );
+		}
+	}
+#endif		// DEBUG_SPHERES
+
+
+
+
+
+	GXLightObj light_obj[3];
+
+	// Always set ambient & diffuse colors.
+	GX::SetChanAmbColor( GX_COLOR0A0, NxNgc::EngineGlobals.ambient_light_color );
+//		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+//				GX::SetChanAmbColor( GX_COLOR1A1, NxNgc::EngineGlobals.ambient_light_color );
+
+	GX::InitLightColor( &light_obj[0], NxNgc::EngineGlobals.diffuse_light_color[0] );
+	GX::InitLightPos( &light_obj[0], NxNgc::EngineGlobals.light_x[0], NxNgc::EngineGlobals.light_y[0], NxNgc::EngineGlobals.light_z[0] );
+	GX::LoadLightObjImm( &light_obj[0], GX_LIGHT0 );
+
+	GX::InitLightColor( &light_obj[1], NxNgc::EngineGlobals.diffuse_light_color[1] );
+	GX::InitLightPos( &light_obj[1], NxNgc::EngineGlobals.light_x[1], NxNgc::EngineGlobals.light_y[1], NxNgc::EngineGlobals.light_z[1] );
+	GX::LoadLightObjImm( &light_obj[1], GX_LIGHT1 );
+
+	GX::InitLightColor( &light_obj[2], NxNgc::EngineGlobals.diffuse_light_color[2] );
+	GX::InitLightPos( &light_obj[2], NxNgc::EngineGlobals.light_x[2], NxNgc::EngineGlobals.light_y[2], NxNgc::EngineGlobals.light_z[2] );
+	GX::LoadLightObjImm( &light_obj[2], GX_LIGHT2 );
+
+	// Set channel control for diffuse.
+	GX::SetChanCtrl( GX_COLOR0, GX_ENABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT0|GX_LIGHT1|GX_LIGHT2, GX_DF_CLAMP, GX_AF_NONE );
+	GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+
+
+
+
+
+
+
+	if( GetBoneTransforms() && ( GetScene()->m_numHierarchyObjects == 0 ) )
+	{
+		// Has bone transforms, is therefore an animated object such as a skater, pedestrian etc.
+
+//		if ( ( flags & vRENDER_SHADOW_1ST_PASS ) && GetScene()->mp_scene_data && GetScene()->mp_scene_data->m_num_shadow_faces && !g_in_cutscene )
+//		{
+//			f32 p_pm[GX_PROJECTION_SZ];
+//			GX::GetProjectionv( p_pm );
+//			float value = p_pm[6] + ( (float)(-g_twiddle) ) * p_pm[5];
+//
+//			GXWGFifo.u8 = GX_LOAD_XF_REG;
+//			GXWGFifo.u16 = 0;
+//			GXWGFifo.u16 = 0x1025;
+//			GXWGFifo.f32 = value;
+//
+//
+//			GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_FRONT ); 
+//			GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+//			GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+//
+//			GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0,
+//										   GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0 );
+//
+//			GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0,	GX_CA_KONST, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+//													GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+//													GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevColorInOp( GX_TEVSTAGE0,		GX_CC_KONST, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+//													GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//
+//			GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//
+//			GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//
+////			GX::SetZMode( GX_TRUE, GX_GREATER, GX_FALSE );
+////			GX::SetZMode( GX_TRUE, GX_GREATER, GX_FALSE );
+//			GX::SetZMode(GX_TRUE, GX_LEQUAL, GX_FALSE);
+//
+//			GX::SetTevKColor( GX_KCOLOR0, (GXColor){4,4,4,4} );
+//			GX::SetTevKSel( GX_TEVSTAGE0, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A );
+//
+////			if ( !g_in_cutscene ) GX::SetClipMode( GX_CLIP_DISABLE );
+//			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//
+//
+//			float px = 0.0f;	//NxNgc::EngineGlobals.skater_shadow_dir.x;
+//			float py = -1.0f;    //NxNgc::EngineGlobals.skater_shadow_dir.y * 3.0f;
+//			float pz = 0.0f;    //NxNgc::EngineGlobals.skater_shadow_dir.z;
+//
+//			float l = sqrtf( ( px * px ) + ( py * py ) + ( pz * pz ) );
+//
+//			px /= l;
+//			py /= l;
+//			pz /= l;
+//
+////#define NEAR_Z 50.0f
+////#define FAR_Z 120.0f
+////
+////#define MIN_LEN 10.0f
+////#define MAX_LEN 100.0f
+//
+//			// Shorten length if:
+//			// 1. light vector & object vector are opposing.
+//			// AND
+//			// 2. camera is not above player.
+//			// AND
+//			// 3. camera is close to the object.
+//			float base_length = MAX_LEN;
+//
+////			float cx = NxNgc::EngineGlobals.camera.getPosX() - GetTransform()->GetPos()[X];
+////			float cy = NxNgc::EngineGlobals.camera.getPosY() - GetTransform()->GetPos()[Y];
+////			float cz = NxNgc::EngineGlobals.camera.getPosZ() - GetTransform()->GetPos()[Z];
+//
+////			float obj_len = sqrtf( ( cx * cx ) + /*( cy * cy ) +*/ ( cz * cz ) );
+//
+////
+////			// Fabricate a worst-case vector.
+////			// 1st, pick a point at the head of the skater.
+////			float wx = 0.0f;
+////			float wy = 12.0f * 7.0f;		// Assuming 7ft skater.
+////			float wz = 0.0f;
+////
+////			// 2nd, move towards camera by average value.
+////			wx += ( cx / obj_len ) * ( 12.0f * 3.0f );
+////			wy += ( cy / obj_len ) * ( 12.0f * 3.0f );
+////			wz += ( cz / obj_len ) * ( 12.0f * 3.0f );
+////
+//			
+////			float cam_dir_dot = ( px * cx ) + /*( py * cy ) +*/ ( pz * cz );
+////
+//////			OSReport( "Object len: %8.3f\n", obj_len );
+////
+////			if ( ( ( cam_dir_dot > -0.0f ) || ( obj_len <= NEAR_Z ) ) && ( cy < 100.0f ) )
+////			{
+////				// Opposing, get length.
+////				if ( obj_len < FAR_Z )
+////				{
+////					if ( obj_len > NEAR_Z )
+////					{
+////						// Close enough, adjust base length.
+////						base_length = MIN_LEN + ( ( ( obj_len - NEAR_Z ) * ( MAX_LEN - MIN_LEN ) ) / ( FAR_Z - NEAR_Z ) );
+////					}
+////					else
+////					{
+////						// Close enough, adjust base length.
+////						base_length = MIN_LEN;
+////					}
+////				}
+////			}
+////
+//			float length = NxNgc::EngineGlobals.skater_height + base_length;
+//
+//			root.setRightX( GetTransform()->GetRight()[X] );
+//			root.setRightY( GetTransform()->GetRight()[Y] );
+//			root.setRightZ( GetTransform()->GetRight()[Z] );
+//
+//			root.setUpX( GetTransform()->GetUp()[X] );
+//			root.setUpY( GetTransform()->GetUp()[Y] );
+//			root.setUpZ( GetTransform()->GetUp()[Z] );
+//
+//			root.setAtX( GetTransform()->GetAt()[X] );
+//			root.setAtY( GetTransform()->GetAt()[Y] );
+//			root.setAtZ( GetTransform()->GetAt()[Z] );
+//
+//			root.setPosX( 0.0f );
+//			root.setPosY( 0.0f );
+//			root.setPosZ( 0.0f );
+//
+//			NsVector in, out;
+//
+//			in.x = px;
+//			in.y = py;
+//			in.z = pz;
+//			root.multiply( &in, &out );
+//			px = out.x;
+//			py = out.y;
+//			pz = out.z;
+//
+//			float tx = px * g_tweak;
+//			float ty = py * g_tweak;
+//			float tz = pz * g_tweak;
+//
+//			px *= length;
+//			py *= length;
+//			pz *= length;
+//
+//			int num_shadow_faces = GetScene()->mp_scene_data->m_num_shadow_faces;
+//
+//			float p_dot[4096];
+//			CalculateDotProducts( mp_posNormRenderBuffer, GetScene()->mp_shadow_volume_mesh, p_dot, num_shadow_faces, px, py, pz );
+//#if 1
+//			RenderShadows( mp_posNormRenderBuffer, GetScene()->mp_shadow_volume_mesh, GetScene()->mp_shadow_edge, p_dot, num_shadow_faces, px, py, pz, tx, ty, tz );
+//#else
+//			float * p_xyz = new float[mp_scene->mp_dl->mp_object_header->m_num_skin_verts*3];
+//			ConvertFixed196ToFloat( mp_posNormRenderBuffer, p_xyz, mp_scene->mp_dl->mp_object_header->m_num_skin_verts );
+//
+//			for ( uint lp = 0; lp < GetScene()->mp_scene_data->m_num_shadow_faces; lp++ )
+//			{
+//				float dot = p_dot[lp];
+//
+//				if ( dot > 0 )
+//				{
+////					// See if we have to draw all sides.
+////					// Check 01.
+//					int shape = 0;
+//					if ( ( GetScene()->mp_shadow_edge[lp].neighbor[0] == -1 ) || ( p_dot[ GetScene()->mp_shadow_edge[lp].neighbor[0] ] <= 0 ) )
+//					{
+//						shape |= (1<<0);
+//					}
+//					// Check 12.
+//					if ( ( GetScene()->mp_shadow_edge[lp].neighbor[1] == -1 ) || ( p_dot[ GetScene()->mp_shadow_edge[lp].neighbor[1] ] <= 0 ) )
+//					{
+//						shape |= (1<<1);
+//					}
+//					// Check 23.
+//					if ( ( GetScene()->mp_shadow_edge[lp].neighbor[2] == -1 ) || ( p_dot[ GetScene()->mp_shadow_edge[lp].neighbor[2] ] <= 0 ) )
+//					{
+//						shape |= (1<<2);
+//					}
+//
+//					unsigned short idx0 = GetScene()->mp_shadow_volume_mesh[(lp*3)+0];
+//					unsigned short idx1 = GetScene()->mp_shadow_volume_mesh[(lp*3)+1];
+//					unsigned short idx2 = GetScene()->mp_shadow_volume_mesh[(lp*3)+2];
+//
+//					float x0 = p_xyz[(idx0*3)+0];
+//					float y0 = p_xyz[(idx0*3)+1];
+//					float z0 = p_xyz[(idx0*3)+2];
+//
+//					float x1 = p_xyz[(idx1*3)+0];
+//					float y1 = p_xyz[(idx1*3)+1];
+//					float z1 = p_xyz[(idx1*3)+2];
+//
+//					float x2 = p_xyz[(idx2*3)+0];
+//					float y2 = p_xyz[(idx2*3)+1];
+//					float z2 = p_xyz[(idx2*3)+2];
+//
+//					float x3 = x0 + px;
+//					float y3 = y0 + py;
+//					float z3 = z0 + pz;
+//	
+//					float x4 = x1 + px;
+//					float y4 = y1 + py;
+//					float z4 = z1 + pz;
+//	
+//					float x5 = x2 + px;
+//					float y5 = y2 + py;
+//					float z5 = z2 + pz;
+//
+//					GX::SetBlendMode ( GX_BM_BLEND, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_FALSE, GX_TRUE, GX_FALSE );
+//
+//					switch ( shape )
+//					{
+//						case 0:
+//							GX::Begin( GX_TRIANGLES, GX_VTXFMT0, 3 ); 
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::End();
+//							GX::Begin( GX_TRIANGLES, GX_VTXFMT0, 3 ); 
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::End();
+//							break;
+//						case 1:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 6 ); 
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::End();
+//							break;
+//						case 2:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 6 ); 
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::End();
+//							break;
+//						case 3:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 8 ); 
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::End();
+//							break;
+//						case 4:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 6 ); 
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::End();
+//							break;
+//						case 5:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 8 ); 
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::End();
+//							break;
+//						case 6:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 8 ); 
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::End();
+//							break;
+//						case 7:
+//						default:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 10 ); 
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::End();
+//							break;
+//					}
+//					GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_FALSE, GX_TRUE, GX_FALSE );
+//
+//					switch ( shape )
+//					{
+//						case 0:
+//							GX::Begin( GX_TRIANGLES, GX_VTXFMT0, 3 ); 
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::End();
+//							GX::Begin( GX_TRIANGLES, GX_VTXFMT0, 3 ); 
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::End();
+//							break;
+//						case 1:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 6 ); 
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::End();
+//							break;
+//						case 2:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 6 ); 
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::End();
+//							break;
+//						case 3:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 8 ); 
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::End();
+//							break;
+//						case 4:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 6 ); 
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::End();
+//							break;
+//						case 5:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 8 ); 
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::End();
+//							break;
+//						case 6:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 8 ); 
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::End();
+//							break;
+//						case 7:
+//						default:
+//							GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 10 ); 
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::Position3f32( x1, y1, z1 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x4, y4, z4 );
+//							GX::Position3f32( x3, y3, z3 );
+//							GX::Position3f32( x5, y5, z5 );
+//							GX::Position3f32( x0, y0, z0 );
+//							GX::Position3f32( x2, y2, z2 );
+//							GX::End();
+//							break;
+//					}
+//				}
+//			}
+//			delete p_xyz;
+//#endif
+//			GX::SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
+////			if ( !g_in_cutscene ) GX::SetClipMode( GX_CLIP_ENABLE );
+//			GX::SetProjectionv( p_pm );
+//		}
+//		else
+		{
+			GX::LoadNrmMtxImm((MtxPtr)&root, GX_PNMTX0);
+			// Render the scene & see if any meshes were drawn. If so, transform the
+			// vertex buffer for use when the GPU comes to actually render it..
+			NxNgc::EngineGlobals.poly_culling = false;
+
+			make_scene_visible( GetScene() );
+			GX::SetVtxAttrFmt( GX_VTXFMT2, GX_VA_POS, GX_POS_XYZ, GX_S16, 6 );
+			GX::SetVtxAttrFmt( GX_VTXFMT3, GX_VA_POS, GX_POS_XYZ, GX_S16, 6 );
+			GX::SetVtxAttrFmt( GX_VTXFMT4, GX_VA_POS, GX_POS_XYZ, GX_S16, 6 );
+			GX::SetVtxAttrFmt( GX_VTXFMT5, GX_VA_POS, GX_POS_XYZ, GX_S16, 6 );
+			GX::SetVtxAttrFmt( GX_VTXFMT6, GX_VA_POS, GX_POS_XYZ, GX_S16, 6 );
+			GX::SetVtxAttrFmt( GX_VTXFMT7, GX_VA_POS, GX_POS_XYZ, GX_S16, 6 );
+			render_scene( GetScene(), mp_posNormRenderBuffer, render_flags | vRENDER_NO_CULLING );
+			GX::SetVtxAttrFmt( GX_VTXFMT2, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );
+			GX::SetVtxAttrFmt( GX_VTXFMT3, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );
+			GX::SetVtxAttrFmt( GX_VTXFMT4, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );
+			GX::SetVtxAttrFmt( GX_VTXFMT5, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );
+			GX::SetVtxAttrFmt( GX_VTXFMT6, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );
+			GX::SetVtxAttrFmt( GX_VTXFMT7, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );
+		}
+	}
+	else
+	{
+		if( ( m_flags & CInstance::INSTANCE_FLAG_LIGHTING_ALLOWED ) == 0 ) render_flags |= NxNgc::vRENDER_LIT;
+		GX::LoadNrmMtxImm((MtxPtr)&tr, GX_PNMTX0);
+		if ( GetScene()->m_numHierarchyObjects && GetBoneTransforms() )
+		{
+			// Car or something...
+			NxNgc::EngineGlobals.poly_culling = true;
+			make_scene_visible( GetScene() );
+			render_scene( GetScene(), NULL, render_flags | vRENDER_NO_CULLING, GetBoneTransforms(), GetTransform(), GetNumBones() );
+		}
+		else
+		{
+			if( ( m_flags & CInstance::INSTANCE_FLAG_LIGHTING_ALLOWED ) == 0 ) render_flags |= NxNgc::vRENDER_LIT;
+			// No bones, just render normally. Must be a trashcan or something.
+			NxNgc::EngineGlobals.poly_culling = false;
+			make_scene_visible( GetScene() );
+//			int meshes_rendered = cull_scene( GetScene(), vRENDER_OCCLUDED, GetTransform() ); 
+			render_scene( GetScene(), NULL, render_flags );
+//			SetMeshesRendered( meshes_rendered );
+		}
+	}
+}
+
+
+
+void process_instances( void )
+{
+	current_index = 0;
+	
+	if ( g_mc_hack ) return;
+
+	// Seed the static pointer off to NULL, otherwise if there is only one object with bone transforms, it will never update.
+	pLastBoneTransforms = NULL;
+	
+	// First go through and build a list of the visible instances.
+	CInstance *p_instance = pFirstInstance;
+	while( p_instance )
+	{
+#ifndef __NOPT_FINAL__
+		p_instance->m_object_num = g_object;
+		g_object++;
+#endif		// __NOPT_FINAL__
+		if( p_instance->GetActive() && p_instance->GetScene()->mp_scene_data )
+		{
+			p_instances[current_index++] = p_instance;
+		}
+		p_instance = p_instance->GetNextInstance();
+	}
+
+	int save_g_object = g_object;
+
+	// Now sort the list based on bone transform and number of semitransparent objects in scene.
+	if( current_index > 0 )
+	{
+		if( current_index > 1 ) qsort( p_instances, current_index, sizeof( CInstance* ), sort_by_bone_transform );
+	
+		int i = 0;
+		Mth::Matrix *pLast = NULL;
+		ROMtx bone_mtx[60];
+
+		for( ; i < current_index; ++i )
+		{
+#ifndef __NOPT_FINAL__
+			g_object = p_instances[i]->m_object_num;
+#endif		// __NOPT_FINAL__
+
+			if(( p_instances[i]->GetBoneTransforms() == NULL ) || ( p_instances[i]->GetScene()->m_numHierarchyObjects > 0 ))
+				break;
+
+			p_instances[i]->Transform( 0 /*flags*/ | vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT, bone_mtx, pLast );
+			pLast = p_instances[i]->GetBoneTransforms();
+		}
+
+//		for( ; i < current_index; ++i )
+//		{
+//#ifndef __NOPT_FINAL__
+//			g_object = p_instances[i]->m_object_num;
+//#endif		// __NOPT_FINAL__
+//
+//			p_instances[i]->Transform( 0 /*flags*/ | vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT, NULL, NULL );
+//		}
+	}
+	g_object = save_g_object;
+}
+
+
+
+s16	*CInstance::GetPosNormalBuffer( int buffer )
+{
+	int num_vertex = mp_scene->mp_dl->mp_object_header->m_num_skin_verts;
+	Dbg_MsgAssert(num_vertex, ("No verts in scene."));
+
+	s16 * rv = (s16*)NsBuffer::alloc( sizeof(s16) * 6 * (num_vertex+1) ); 
+	return rv;
+}
+
+CInstance::CInstance( sScene *pScene, Mth::Matrix &transform, int numBones, Mth::Matrix *pBoneTransforms )
+{
+	SetTransform( transform );
+	mp_bone_transforms	= pBoneTransforms;
+	m_num_bones			= numBones;
+	mp_scene			= pScene;
+	m_active			= true;
+	m_flags				= 0;
+	mp_model			= NULL;
+
+	mp_next_instance	= pFirstInstance;
+	pFirstInstance		= this;
+
+	mp_posNormBuffer = NULL;
+	m_visibility_mask = 0xff;
+
+	// Check to see whether this instance is allowed to be lit or not (for non-skinned instances).
+	// Currently, we assume that instances with a model containing valid normals requires lighting, except in the
+	// situation where that model contains a mesh specifically flagged not to be lit.
+	if(( pBoneTransforms == NULL ) || ( GetScene()->m_numHierarchyObjects > 0 ))
+	{
+		for( int m = 0; m < pScene->m_num_filled_meshes; ++m )
+		{
+			NxNgc::sMesh *p_mesh = pScene->mpp_mesh_list[m];
+			if( !pScene->mp_scene_data->m_num_nrm )
+				return;
+			if( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<7) )
+				return;
+		}
+		m_flags |= CInstance::INSTANCE_FLAG_LIGHTING_ALLOWED;
+	}
+}
+
+
+
+
+CInstance::~CInstance()
+{
+	if( m_flags & INSTANCE_FLAG_DELETE_ATTACHED_SCENE )
+	{
+		if( mp_scene )
+		{
+			delete mp_scene;
+			mp_scene = NULL;
+		}
+	}
+	
+//	if ( mp_posNormBuffer )
+//	{
+//		delete  mp_posNormBuffer;
+//		mp_posNormBuffer = NULL;
+//	}
+
+	// Remove this instance from the list.
+	CInstance **pp_instance = &pFirstInstance;
+	while( *pp_instance )
+	{
+		if( *pp_instance == this )
+		{
+			*pp_instance = mp_next_instance;
+			break;
+		}
+		else
+		{
+			pp_instance = &(( *pp_instance )->mp_next_instance );
+		}
+	}
+}
+
+} // namespace NxNgc
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/NX/instance.h b/Code/Gfx/NGC/NX/instance.h
new file mode 100644
index 0000000..1e80fe6
--- /dev/null
+++ b/Code/Gfx/NGC/NX/instance.h
@@ -0,0 +1,88 @@
+#ifndef __INSTANCE_H
+#define __INSTANCE_H
+
+
+#include 
+#include 
+#include "scene.h"
+
+namespace NxNgc
+{
+
+class CInstance
+{
+public:
+	enum EInstanceFlag
+	{
+		INSTANCE_FLAG_DELETE_ATTACHED_SCENE	= 0x01,
+		INSTANCE_FLAG_TRANSFORM_ME			= 0x02,
+		INSTANCE_FLAG_LIGHTING_ALLOWED		= 0x04
+
+	};
+	
+	void		SetTransform( Mth::Matrix &transform )	{ m_transform = transform; }
+	Mth::Matrix	*GetTransform(void)						{ return &m_transform; }
+	int			GetNumBones( void )						{ return m_num_bones; }
+	Mth::Matrix	*GetBoneTransforms( void )				{ return mp_bone_transforms; }
+	void		SetBoneTransforms( Mth::Matrix * p )	{ mp_bone_transforms = p; }
+	sScene		*GetScene( void )						{ return mp_scene; }
+	void		SetActive( bool active )				{ m_active = (uint16)active; }
+	bool		GetActive( void )						{ return (bool)m_active; }
+	void		SetFlag( EInstanceFlag flag )			{ m_flags |= flag; }
+	uint32		GetFlags( void )						{ return m_flags; }
+	void		ClearFlag( EInstanceFlag flag )			{ m_flags &= ~flag; }
+	s16			*GetPosNormalBuffer( int buffer );
+	void		SetVisibility( uint32 mask )			{ m_visibility_mask	= mask; }
+	uint32		GetVisibility( void )					{ return m_visibility_mask; }
+	CInstance	*GetNextInstance( void )				{ return mp_next_instance; }
+
+	void		SetModel( Nx::CModel *p_model )			{ mp_model = p_model; }
+	Nx::CModel	*GetModel( void )						{ return mp_model; }
+
+	int			GetMeshesRendered( void )				{ return m_meshes_rendered; }
+	void		SetMeshesRendered( int n )				{ m_meshes_rendered = n; }
+
+	CInstance( sScene *pScene, Mth::Matrix &transform, int numBones, Mth::Matrix *pBoneTransforms );
+	~CInstance();
+
+	void		Transform( uint32 flags, ROMtx * p_mtx_buffer, Mth::Matrix *p_mtx_last );
+	void		Render( uint32 flags );
+
+	s16*		GetPosNormRenderBuffer() { return mp_posNormRenderBuffer; }
+	void		SetPosNormRenderBuffer( s16* p ) { mp_posNormRenderBuffer = p; }
+
+	#ifndef __NOPT_FINAL__
+	int			m_object_num;
+	#endif		// __NOPT_FINAL__
+
+private:
+	uint32		m_flags;
+	Mth::Matrix	m_transform;
+	Mth::Matrix	*mp_bone_transforms;
+	int			m_num_bones;
+	uint16		m_active;
+	uint16		m_meshes_rendered;
+	sScene		*mp_scene;
+
+	uint32		m_visibility_mask;
+
+	Nx::CModel	*mp_model;		// Required in order to get pointer to CXboxLights structure at render time.
+
+	s16*		mp_posNormBuffer;
+	s16*		mp_posNormRenderBuffer;
+
+	CInstance	*mp_next_instance;
+};
+
+
+void	InitialiseInstanceTable( void );
+void	render_instance( CInstance *p_instance, uint32 flags );
+void	render_instances( uint32 flags );
+void	process_instances( void );
+
+
+} // namespace NxNgc
+
+
+#endif // __INSTANCE_H
+
diff --git a/Code/Gfx/NGC/NX/light.cpp b/Code/Gfx/NGC/NX/light.cpp
new file mode 100644
index 0000000..2026e35
--- /dev/null
+++ b/Code/Gfx/NGC/NX/light.cpp
@@ -0,0 +1,326 @@
+#include 
+#include "light.h"
+
+namespace NxNgc
+{
+
+
+// Statics
+CLightGroup		CLightGroup::s_default_lights;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CLightGroup::CLightGroup()
+{
+	// Don't use new lights unless someone sets them
+	m_flags = 0;
+
+	// Init to default lights
+	m_light_vector = s_default_lights.m_light_vector;
+
+	m_diffuse_base_color = s_default_lights.m_diffuse_base_color;
+	m_diffuse_mod_color = s_default_lights.m_diffuse_base_color;
+
+	m_ambient_base_color = s_default_lights.m_ambient_base_color;
+	m_ambient_mod_color = s_default_lights.m_ambient_base_color;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CLightGroup::~CLightGroup()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::GetDirection(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (m_flags & (mUSE_DIFFUSE_0 << idx))
+	{
+		return m_light_vector[idx];
+	} else {
+		return s_default_lights.m_light_vector[idx];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::GetBaseAmbientColor() const
+{
+	if (m_flags & mUSE_AMBIENT)
+	{
+		return m_ambient_base_color;
+	} else {
+		return s_default_lights.m_ambient_base_color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::GetBaseDiffuseColor(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (m_flags & (mUSE_DIFFUSE_0 << idx))
+	{
+		return m_diffuse_base_color[idx];
+	} else {
+		return s_default_lights.m_diffuse_base_color[idx];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::GetAmbientColor() const
+{
+	if (m_flags & (mUSE_AMBIENT | mMODULATING_AMBIENT))
+	{
+		return m_ambient_mod_color;
+	} else {
+		return s_default_lights.m_ambient_base_color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::GetDiffuseColor(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (m_flags & ((mUSE_DIFFUSE_0 | mMODULATING_DIFFUSE_0) << idx))
+	{
+		return m_diffuse_mod_color[idx];
+	} else {
+		return s_default_lights.m_diffuse_base_color[idx];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::SetDirection(int idx, const Mth::Vector & direction)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+	//Dbg_Assert(m_flags & (mUSE_DIFFUSE_0 << idx));
+	m_flags |= (mUSE_DIFFUSE_0 << idx);  // turn on light if it isn't already on
+
+	m_light_vector[idx] = direction;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::SetBaseAmbientColor(const Mth::Vector & color)
+{
+	//Dbg_Assert(m_flags & mUSE_AMBIENT);
+	m_flags |= mUSE_AMBIENT;		// turn on light if it isn't already on
+
+	m_ambient_base_color = color;
+
+	if (!(m_flags & mMODULATING_AMBIENT))
+	{
+		m_ambient_mod_color = color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::SetBaseDiffuseColor(int idx, const Mth::Vector & color)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+	//Dbg_Assert(m_flags & (mUSE_DIFFUSE_0 << idx));
+	m_flags |= (mUSE_DIFFUSE_0 << idx);  // turn on light if it isn't already on
+
+	m_diffuse_base_color[idx] = color;
+
+	if (!(m_flags & (mMODULATING_DIFFUSE_0 << idx)))
+	{
+		m_diffuse_mod_color[idx] = color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::EnableAmbientLight(bool enable)
+{
+	if (enable)
+	{
+		m_flags |= mUSE_AMBIENT;
+	} else {
+		m_flags &= ~mUSE_AMBIENT;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::EnableDiffuseLight(int idx, bool enable)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (enable)
+	{
+		m_flags |= (mUSE_DIFFUSE_0 << idx);
+	} else {
+		m_flags &= ~(mUSE_DIFFUSE_0 << idx);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CLightGroup::IsAmbientLightEnabled() const
+{
+	return (m_flags & mUSE_AMBIENT);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CLightGroup::IsDiffuseLightEnabled(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	return (m_flags & (mUSE_DIFFUSE_0 << idx));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CLightGroup::SetAmbientBrightness(float brightness)
+{
+	m_ambient_mod_color = GetBaseAmbientColor() * brightness;
+
+	m_flags |= mMODULATING_AMBIENT;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool					CLightGroup::SetDiffuseBrightness(int idx, float brightness)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	m_diffuse_mod_color[idx] = GetBaseDiffuseColor(idx) * brightness;
+
+	m_flags |= (mMODULATING_DIFFUSE_0 << idx);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::sGetDefaultDirection(int idx)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	return s_default_lights.m_light_vector[idx];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::sGetDefaultAmbientColor()
+{
+	return s_default_lights.m_ambient_base_color;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &		CLightGroup::sGetDefaultDiffuseColor(int idx)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	return s_default_lights.m_diffuse_base_color[idx];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::sSetDefaultDirection(int idx, const Mth::Vector & direction)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	s_default_lights.m_light_vector[idx] = direction;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::sSetDefaultAmbientColor(const Mth::Vector & color)
+{
+	s_default_lights.m_ambient_base_color = color;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void					CLightGroup::sSetDefaultDiffuseColor(int idx, const Mth::Vector & color)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	s_default_lights.m_diffuse_base_color[idx] = color;
+}
+
+} // namespace NxNgc
+
+
diff --git a/Code/Gfx/NGC/NX/light.h b/Code/Gfx/NGC/NX/light.h
new file mode 100644
index 0000000..4ca3138
--- /dev/null
+++ b/Code/Gfx/NGC/NX/light.h
@@ -0,0 +1,122 @@
+#ifndef __LIGHT_H
+#define __LIGHT_H
+
+#include	
+#include	
+
+namespace NxNgc
+{
+
+// Forward declarations
+class CLightGroup;
+
+class CBaseLightGroup
+{
+public:
+								CBaseLightGroup();
+	virtual						~CBaseLightGroup();
+
+	virtual const Mth::Vector &	GetDirection(int idx) const;
+
+	virtual const Mth::Vector &	GetAmbientColor() const;
+	virtual const Mth::Vector &	GetDiffuseColor(int idx) const;
+
+protected:
+	// Directional light vectors
+	Mth::Vector					m_light_vector[2];
+
+	// Light base colors
+	Mth::Vector 				m_diffuse_base_color[2];
+	Mth::Vector 				m_ambient_base_color;
+
+	friend CLightGroup;
+};
+
+class CLightGroup/* : public CBaseLightGroup*/
+{
+
+public:
+	// Constants
+	enum
+	{
+		MAX_DIFFUSE_LIGHTS = 2,
+	};
+
+								CLightGroup();
+								~CLightGroup();
+	//virtual						~CLightGroup();
+
+	const Mth::Vector &			GetDirection(int idx) const;
+	//virtual const Mth::Vector &	GetDirection(int idx) const;
+
+	const Mth::Vector &			GetBaseAmbientColor() const;
+	const Mth::Vector &			GetBaseDiffuseColor(int idx) const;
+
+	const Mth::Vector &			GetAmbientColor() const;
+	const Mth::Vector &			GetDiffuseColor(int idx) const;
+	//virtual const Mth::Vector &	GetAmbientColor() const;
+	//virtual const Mth::Vector &	GetDiffuseColor(int idx) const;
+
+	void						SetDirection(int idx, const Mth::Vector & direction);
+	void						SetBaseAmbientColor(const Mth::Vector & color);
+	void						SetBaseDiffuseColor(int idx, const Mth::Vector & color);
+
+	// Enable lights
+	void						EnableAmbientLight(bool enable);
+	void						EnableDiffuseLight(int idx, bool enable);
+	bool						IsAmbientLightEnabled() const;
+	bool						IsDiffuseLightEnabled(int idx) const;
+
+	// Set brightness of lights
+	bool						SetAmbientBrightness(float brightness);
+	bool						SetDiffuseBrightness(int idx, float brightness);
+
+	// Default lights
+	static const Mth::Vector &	sGetDefaultDirection(int idx);
+	static const Mth::Vector &	sGetDefaultAmbientColor();
+	static const Mth::Vector &	sGetDefaultDiffuseColor(int idx);
+
+	static void					sSetDefaultDirection(int idx, const Mth::Vector & direction);
+	static void					sSetDefaultAmbientColor(const Mth::Vector & color);
+	static void					sSetDefaultDiffuseColor(int idx, const Mth::Vector & color);
+
+protected:
+	// Internal flags.
+	enum {
+		mUSE_AMBIENT				= 0x0001,					// Use ambient light
+		mUSE_DIFFUSE_0				= 0x0002,					// Use diffuse light 0
+		mUSE_DIFFUSE_1				= 0x0004,					// rest of diffuse must follow 0
+		mMODULATING_AMBIENT			= 0x0008,					// setting ambient brightness
+		mMODULATING_DIFFUSE_0		= 0x0010,					// setting diffuse brightness 0
+		mMODULATING_DIFFUSE_1		= 0x0020,					// rest of diffuse must follow 0
+	};
+
+	uint32					m_flags;
+
+	// Directional light vectors
+	Mth::Vector				m_light_vector[2];
+
+	// Light base colors
+	Mth::Vector 			m_diffuse_base_color[2];
+	Mth::Vector 			m_ambient_base_color;
+
+	// Light modulated colors (modified base colors)
+	Mth::Vector 			m_diffuse_mod_color[2];
+	Mth::Vector 			m_ambient_mod_color;
+
+	// Default lights
+//	static Mth::Vector		s_default_directions[2];
+//	static CBaseLightGroup	s_default_colors;
+	static CLightGroup		s_default_lights;
+
+}; // class CLightGroup
+
+
+
+} // namespace NxNgc
+
+
+
+#endif // __LIGHT_H
+
+
diff --git a/Code/Gfx/NGC/NX/line.cpp b/Code/Gfx/NGC/NX/line.cpp
new file mode 100644
index 0000000..24a8aca
--- /dev/null
+++ b/Code/Gfx/NGC/NX/line.cpp
@@ -0,0 +1,132 @@
+#include "line.h"
+#include "render.h"
+
+
+namespace NxPs2
+{
+
+
+void BeginLines2D(uint32 rgba)
+{
+//	// dma tag
+//	dma::BeginTag(dma::cnt, 0);
+//
+//	vu1::Buffer = vu1::Loc;
+//
+//	// GS context
+//	gs::BeginPrim(ABS,0,0);
+//	gs::Reg1(gs::ALPHA_1, PackALPHA(0,1,0,1,0));
+//	gs::Reg1(gs::RGBAQ,   (uint64)rgba);
+//	gs::EndPrim(0);
+//
+//	// unpack giftag
+//	vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+//	gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(GSPrim));
+//
+//	// begin unpack for coordinates
+//	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+}
+
+
+
+void DrawLine2D(float x0, float y0, float z0, float x1, float y1, float z1)
+{
+//	vif::StoreV4_32(0x6C00+(sint32)(x0*16),0x7200+(sint32)(y0*16),0xFFFFFF,0);
+//	vif::StoreV4_32(0x6C00+(sint32)(x1*16),0x7200+(sint32)(y1*16),0xFFFFFF,0);
+//
+//	if (((dma::pLoc - gif::pTag)>>4) >= 250)
+//	{
+//		vif::EndUNPACK();
+//		gif::EndTagImmediate(1,0);
+//		vif::MSCAL(VU1_ADDR(Parser));
+//		vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+//		gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(GSPrim));
+//		vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+//	}
+//
+}
+
+
+void EndLines2D(void)
+{
+//	vif::EndUNPACK();
+//	gif::EndTagImmediate(1,0);
+//	vif::MSCAL(VU1_ADDR(Parser));
+//	dma::EndTag();
+}
+
+
+
+void BeginLines3D(uint32 rgba)
+{
+//	// dma tag
+//	dma::BeginTag(dma::cnt, 0);
+//
+//	vu1::Buffer = vu1::Loc;
+//
+//	// VU context
+//	vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));
+//	vu1::StoreVec(ViewportScale);		// VF10
+//	vu1::StoreVec(ViewportOffset);		// VF11
+//	vu1::StoreMat(WorldToFrustum);		// VF12-15
+//	vu1::EndPrim(0);
+//
+//	// GS context
+//	gs::BeginPrim(ABS,0,0);
+//	gs::Reg1(gs::ALPHA_1, PackALPHA(0,1,0,1,0));
+//	gs::Reg1(gs::RGBAQ,   (uint64)rgba);
+//	gs::EndPrim(0);
+//
+//	// all lines will be rendered with simple culling
+//	vif::ITOP(CULL);
+//
+//	// unpack giftag
+//	vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+//	gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(ProjLine));
+//
+//	// offset mode
+//	vif::STMOD(1);
+//
+//	// begin unpack for coordinates
+//	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+}
+
+
+void DrawLine3D(float x0, float y0, float z0, float x1, float y1, float z1)
+{
+//	vif::StoreV4_32((sint32)(x0*256.0f), (sint32)(y0*256.0f), (sint32)(z0*256.0f), 0);
+//	vif::StoreV4_32((sint32)(x1*256.0f), (sint32)(y1*256.0f), (sint32)(z1*256.0f), 0);
+//
+//	if (((dma::pLoc - gif::pTag)>>4) >= 250)
+//	{
+//		vif::EndUNPACK();
+//		gif::EndTagImmediate(1,1);
+//		vif::STMOD(0);
+//		vif::MSCAL(VU1_ADDR(Parser));
+//		vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+//		gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(ProjLine));
+//		vif::STMOD(1);
+//		vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+//	}
+//
+}
+
+
+void EndLines3D(void)
+{
+//	vif::EndUNPACK();
+//
+//	if (vif::UnpackSize)
+//	{
+//		gif::EndTagImmediate(1,1);
+//		vif::STMOD(0);
+//		vif::MSCAL(VU1_ADDR(Parser));
+//	}
+//	else
+//		dma::pLoc -= 24;
+//
+//	dma::EndTag();
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGC/NX/line.h b/Code/Gfx/NGC/NX/line.h
new file mode 100644
index 0000000..0c1e8e1
--- /dev/null
+++ b/Code/Gfx/NGC/NX/line.h
@@ -0,0 +1,21 @@
+#ifndef __LINE_H
+#define __LINE_H
+
+#include "types.h"
+
+namespace NxNgc
+{
+
+
+void BeginLines2D(uint32 rgba);
+void DrawLine2D(float x0, float y0, float z0, float x1, float y1, float z1);
+void EndLines2D(void);
+void BeginLines3D(uint32 rgba);
+void DrawLine3D(float x0, float y0, float z0, float x1, float y1, float z1);
+void EndLines3D(void);
+
+} // namespace NxNgc
+
+
+#endif // __LINE_H
+
diff --git a/Code/Gfx/NGC/NX/material.cpp b/Code/Gfx/NGC/NX/material.cpp
new file mode 100644
index 0000000..093a4c1
--- /dev/null
+++ b/Code/Gfx/NGC/NX/material.cpp
@@ -0,0 +1,1619 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+//#include "import.h"
+#include "material.h"
+#include "scene.h"
+#include "render.h"
+#include "gfx/ngc/p_nxtexture.h"
+#include "nx_init.h"
+
+#include 
+#include 
+
+//int gMatBytes = 0;
+
+extern u16 colorMap[];
+
+bool gCorrectColor = false;
+
+extern bool gMeshUseCorrection;
+extern bool gMeshUseAniso;
+
+int g_mat = 0;
+
+namespace NxNgc
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//sMaterial::sMaterial( void )
+//{
+//	m_num_wibble_vc_anims	= 0;
+//	mp_wibble_vc_params		= NULL;
+//	mp_wibble_vc_colors		= NULL;
+//	m_uv_wibble				= false;
+//	for( int p = 0; p < MAX_PASSES; ++p )
+//	{
+//		Flags[p]			 = 0;
+//		mp_UVWibbleParams[p] = NULL;
+//	}
+//}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//sMaterial::~sMaterial( void )
+//{
+////	if( mp_wibble_vc_params	)
+////	{
+////		for( uint32 i = 0; i < m_num_wibble_vc_anims; ++i )
+////		{
+////			delete [] mp_wibble_vc_params[i].mp_keyframes;
+////		}
+////		delete [] mp_wibble_vc_params;
+////	}
+////	if( mp_wibble_vc_colors	)
+////	{
+////		delete [] mp_wibble_vc_colors;
+////	}
+//}
+
+
+
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void sMaterial::figure_wibble_uv( void )
+//{
+//	float uoff, voff, t;
+//
+//	m_wibble_uv_time	= (float)Tmr::GetTime();
+//	t					= m_wibble_uv_time * 0.001f;
+//	m_wibble_uv_update	= 0;
+//
+//	for( uint32 p = 0; p < Passes; ++p )
+//	{
+//		// Figure out UV offsets for wibbling if required.
+//		if( Flags[p] & MATFLAG_UV_WIBBLE )
+//		{
+//			m_wibble_uv_update |= ( 1 << p );
+//		
+//			uoff	= ( t * m_pUVControl->UVWibbleParams[p].m_UVel ) + ( m_pUVControl->UVWibbleParams[p].m_UAmplitude * sinf( m_pUVControl->UVWibbleParams[p].m_UFrequency * t + m_pUVControl->UVWibbleParams[p].m_UPhase ));
+////			uoff	-= (float)(int)uoff;
+//	
+//			voff	= ( t * m_pUVControl->UVWibbleParams[p].m_VVel ) + ( m_pUVControl->UVWibbleParams[p].m_VAmplitude * sinf( m_pUVControl->UVWibbleParams[p].m_VFrequency * t + m_pUVControl->UVWibbleParams[p].m_VPhase ));
+////			voff	-= (float)(int)voff;
+//
+//			m_pUVControl->UVWibbleOffset[p][0]	= uoff;
+//			m_pUVControl->UVWibbleOffset[p][1]	= voff;
+//		}
+//	}
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void sMaterial::figure_wibble_vc( void )
+//{
+//	// The vertex color wibble flag is placed in pass 0.
+//	if( Flags[0] & MATFLAG_VC_WIBBLE )
+//	{
+//		int current_time = (int)Tmr::GetTime();
+//
+//		for( uint32 i = 0; i < m_num_wibble_vc_anims; ++i )
+//		{
+//			struct sVCWibbleParams	*p_sequence		= mp_wibble_vc_params + i;
+//			
+//			// Get phase-shift.
+//			int						phase_shift		= p_sequence->m_phase;
+//
+//			// Time parameters.
+//			int						num_keys		= p_sequence->m_num_keyframes;
+//			int						start_time		= p_sequence->mp_keyframes[0].m_time;
+//			int						end_time		= p_sequence->mp_keyframes[num_keys - 1].m_time;
+//			int						period			= end_time - start_time;
+//			int						time			= start_time + (( current_time + phase_shift ) % period );
+//
+//			// Locate the keyframe.
+//			int key;
+//			for( key = num_keys - 1; key >= 0; --key )
+//			{
+//				if( time >= p_sequence->mp_keyframes[key].m_time )
+//				{
+//					break;
+//				}
+//			}
+//			
+//			// Parameter expressing how far we are between between this keyframe and the next.
+//			float					t				= (float)( time - start_time ) / (float)period;
+//
+//			// Interpolate the color.
+//			GXColor	rgba;
+//			rgba.r = (uint8)((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.r ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.r ));
+//			rgba.g = (uint8)((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.g ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.g ));
+//			rgba.b = (uint8)((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.b ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.b ));
+//			rgba.a = (uint8)((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.a ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.a ));
+//
+//			mp_wibble_vc_colors[i] = *((uint32*)&rgba);
+//		}
+//	}
+//}
+//	
+//static int	stage_id = 0;	// 0-15
+//static int	tev_id = 0;					// 0-3
+//static int	uv_id = 0;					// 0-7
+//static int	map_id = 0;					// 0-3
+//static int	layer_id = 1;					// 1-3
+//static bool correct_color = false;
+//
+//static void multi_start ( int passes )
+//{
+//	// Set everything to 0.
+//	stage_id	= 0;
+//	tev_id		= 0;
+//	uv_id		= 0;
+//	map_id		= 0;
+//	layer_id	= 1;
+//
+//	// Don't forget these!
+////	GXSetNumChans(2);
+////	GX::SetNumTexGens(2);
+////	GX::SetNumTevStages(2);
+//
+//	if ( gCorrectColor && gMeshUseCorrection )
+//	{
+//		GXTexObj	texObj;
+//		GXInitTexObj( &texObj, colorMap, 256, 256, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE );
+//		GXLoadTexObj( &texObj, GX_TEXMAP7 );
+//
+//		// Tev 0: Use rasterization lookup table (RG)
+//		// Note: Blue = 0 and Alpha = 0;
+//		GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//		GX::SetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC);
+//		GX::SetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
+//		GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG1);
+//		GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG1);
+//		stage_id++;
+//
+//		// Tev 1: Use rasterization lookup table (BA),
+//		// then add previous tev (RG + BA)
+//		GX::SetTevSwapMode( GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP3 );
+//		GX::SetTevColorIn(GX_TEVSTAGE1, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C1);
+//		GX::SetTevAlphaIn(GX_TEVSTAGE1, GX_CA_TEXA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A1);
+//		GX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG2);
+//		GX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG2);
+//		stage_id++;
+//		correct_color = true;
+//	}
+//	else
+//	{
+//		correct_color = false;
+//	}
+//
+////	// Create 2 dummy stages.
+////	GXTevStageID s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+////
+////	GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+////	GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+////	GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+////	GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+////	GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+////	GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+////	stage_id++;
+////
+////	s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+////	
+////	GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR0A0 );
+////	GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+////	GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG2 );
+////	GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG2 );
+////	GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+////	GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+////	stage_id++;
+//
+////	if ( !gCorrectColor || !gMeshUseCorrection )
+////	{
+//////		// Create 2 dummy stages.
+//////		GXTevStageID s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+//////
+//////		GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR0A0 );
+//////		GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//////		GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG2 );
+//////		GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG2 );
+//////		GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//////		GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+//////		stage_id++;
+////		correct_color = false;
+////	}
+//}
+//
+//static void multi_add_layer ( BlendModes blend, int raw_fix )
+//{
+//	// Set inputs.
+//	GXTevStageID s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+//	
+//	int reg_id = layer_id;	//( layer_id > 1 ) ? 1 : layer_id;	//( tev_id == 3 ) ? 2 : tev_id;		// CPREV, C0, C1, C0.
+//  //  int reg_id = layer_id;	//( layer_id == 3 ) ? 2 : layer_id;		// CPREV, C0, C1, C0.
+//
+////	GXTevAlphaArg newa = (GXTevAlphaArg)(((int)GX_CA_APREV)+layer_id);
+//	GXTevColorArg newc = (GXTevColorArg)(((int)GX_CC_CPREV)+(reg_id*2));
+//	GXTevColorArg newca= (GXTevColorArg)(((int)GX_CC_APREV)+(reg_id*2));
+//
+//
+//    // out_reg = (d (op) ((1.0 - c)*a + c*b) + bias) * scale;
+//
+//	int fix = ( raw_fix >= 128 ) ? 255 : raw_fix * 2;
+//
+//	switch ( blend ) {
+//		case vBLEND_MODE_ADD:
+//		case vBLEND_MODE_ADD_FIXED:
+//			// Add using texture or fixed alpha.
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, GX_CC_ZERO, newc, newca, GX_CC_CPREV );
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_SUBTRACT:
+//		case vBLEND_MODE_SUB_FIXED:
+//			// Subtract using texture or fixed alpha.
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_SUB, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, GX_CC_ZERO, newc, newca, GX_CC_CPREV );
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_BLEND:
+//		case vBLEND_MODE_BLEND_FIXED:
+//			// Blend using texture or fixed alpha.
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, GX_CC_CPREV, newc, newca, GX_CC_ZERO );
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_MODULATE:
+//			// Modulate current layer with previous layer.
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, GX_CC_ZERO, newca, GX_CC_CPREV, GX_CC_ZERO );
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_MODULATE_FIXED:
+//			// Modulate current layer with fixed alpha.
+//			GX::SetTevKAlphaSel( s_id, (GXTevKAlphaSel)(((int)GX_TEV_KASEL_K0_A)+layer_id) );
+//			GX::SetTevKColorSel( s_id, (GXTevKColorSel)(((int)GX_TEV_KCSEL_K0_A)+layer_id) );
+//			GX::SetTevKColor( (GXTevKColorID)(((int)GX_KCOLOR0)+layer_id), (GXColor){fix,fix,fix,fix} );
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_CPREV, GX_CC_KONST, GX_CC_ZERO );
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_BRIGHTEN:
+//			// Modulate current layer with previous layer, & add current layer.
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, GX_CC_ZERO, newca, GX_CC_CPREV, GX_CC_CPREV );
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_BRIGHTEN_FIXED:
+//			// Modulate current layer with fixed alpha, & add current layer.
+//			GX::SetTevKAlphaSel( s_id, (GXTevKAlphaSel)(((int)GX_TEV_KASEL_K0_A)+layer_id) );
+//			GX::SetTevKColorSel( s_id, (GXTevKColorSel)(((int)GX_TEV_KCSEL_K0_A)+layer_id) );
+//			GX::SetTevKColor( (GXTevKColorID)(((int)GX_KCOLOR0)+layer_id), (GXColor){fix,fix,fix,fix} );
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_CPREV, GX_CC_KONST, GX_CC_CPREV );
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_DIFFUSE:
+//		default:
+//			// Replace with this texture. Shouldn't ever be used, but here for compatibility.
+//			GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//			GX::SetTevColorIn ( s_id, newc, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//			stage_id++;
+//			break;
+//	}
+////	layer_id++;
+//}
+//
+//static void multi_add_texture ( sTexture * p_texture, GXColor matcol, GXTexWrapMode umode, GXTexWrapMode vmode, BlendModes blend, int fix, uint32 flags, char * p_uv_slot, float k )
+//{
+//	if ( map_id >= 8 ) return;
+//	
+//	void * p_tex_data = p_texture->pTexelData;
+//	void * p_alpha_data = p_texture->has_alpha ? p_texture->pAlphaData : NULL;
+//	int width = p_texture->ActualWidth;
+//	int height = p_texture->ActualHeight;
+//	int levels = p_texture->Levels;
+//	int reg_id = ( tev_id > 1 ) ? 1 : tev_id;	//( tev_id == 3 ) ? 2 : tev_id;		// CPREV, C0, C1, C0.
+//	
+//	GXTevAlphaArg rasa;
+//	GXTevColorArg rasc;
+//	if ( correct_color )
+//	{
+//		rasa = GX_CA_A2;
+//		rasc = GX_CC_C2;
+//	}
+//	else
+//	{
+//		rasa = GX_CA_RASA;
+//		rasc = GX_CC_RASC;
+//	}
+//
+//	int cur_map = map_id + 1;
+//	int alpha_map = -1;
+//	bool uniqueMaterialColor = false;
+//
+//	if ( ( matcol.r == 128 ) && ( matcol.g == 128 ) && ( matcol.b == 128 ) && ( matcol.a == 255 ) )
+//	{
+//		uniqueMaterialColor = false;
+//	}
+//	else
+//	{
+//		uniqueMaterialColor = true;
+//	}
+//
+//	if ( p_alpha_data )
+//	{
+//		switch ( blend )
+//		{
+//			case vBLEND_MODE_ADD:
+//			case vBLEND_MODE_SUBTRACT:
+//			case vBLEND_MODE_BLEND:
+//			case vBLEND_MODE_BRIGHTEN:
+//			case vBLEND_MODE_MODULATE:
+//			case vBLEND_MODE_DIFFUSE:
+//				if ( cur_map < 8 )
+//				{
+//					alpha_map = cur_map;
+//					cur_map++;
+//				}
+//				break;
+//			default:
+//				break;
+//		}
+//	}
+//
+//	GXTexObj	texObj;
+//	GXInitTexObj( &texObj, p_tex_data, width, height, ((GXTexFmt)p_texture->format), umode, vmode, levels > 1 ? GX_TRUE : GX_FALSE );
+//	if ( levels > 1 )
+//	{
+//		if ( gMeshUseAniso && ( tev_id == 0 ) )
+//		{
+//			// If we're correcting the color, we also want ANISO_4.
+//			GXInitTexObjLOD( &texObj, GX_LIN_MIP_LIN, GX_LINEAR, 0.0f, levels - 1, k, GX_FALSE, GX_TRUE, GX_ANISO_4 );
+//		}
+//		else
+//		{
+//			GXInitTexObjLOD( &texObj, GX_LIN_MIP_LIN, GX_LINEAR, 0.0f, levels - 1, k, GX_FALSE, GX_TRUE, GX_ANISO_1 );
+//		}
+//	}
+//	else
+//	{
+//		GXInitTexObjLOD( &texObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, k, GX_FALSE, GX_TRUE, GX_ANISO_1 );
+//	}
+//	GXLoadTexObj( &texObj, (GXTexMapID)(((int)GX_TEXMAP0)+map_id) );
+//
+//	if ( alpha_map >= 0 )
+//	{
+//		switch ( blend )
+//		{
+//			case vBLEND_MODE_ADD:
+//			case vBLEND_MODE_SUBTRACT:
+//			case vBLEND_MODE_BLEND:
+//			case vBLEND_MODE_BRIGHTEN:
+//			case vBLEND_MODE_MODULATE:
+//			case vBLEND_MODE_DIFFUSE:
+//				GXTexObj	alphaObj;
+//				GXInitTexObj( &alphaObj, p_alpha_data, width, height, ((GXTexFmt)p_texture->format), umode, vmode, levels > 1 ? GX_TRUE : GX_FALSE );
+//				if ( levels > 1 )
+//				{
+//					if ( gMeshUseAniso && ( tev_id == 0 ) )
+//					{
+//						// If we're correcting the color, we also want ANISO_4.
+//						GXInitTexObjLOD( &alphaObj, GX_LIN_MIP_LIN, GX_LINEAR, 0.0f, levels - 1, k, GX_FALSE, GX_TRUE, GX_ANISO_4 );
+//					}
+//					else
+//					{
+//						GXInitTexObjLOD( &alphaObj, GX_LIN_MIP_LIN, GX_LINEAR, 0.0f, levels - 1, k, GX_FALSE, GX_TRUE, GX_ANISO_1 );
+//					}
+//				}
+//				else
+//				{
+//					GXInitTexObjLOD( &alphaObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, k, GX_FALSE, GX_TRUE, GX_ANISO_1 );
+//				}
+//				GXLoadTexObj( &alphaObj, (GXTexMapID)(((int)GX_TEXMAP0)+alpha_map) );
+//				break;
+//			default:
+//				p_alpha_data = NULL;
+//				break;
+//		}
+//	}
+//
+//	GXTexMapID t_id = (GXTexMapID)(((int)GX_TEXMAP0)+map_id);
+//	GXTexCoordID u_id = (GXTexCoordID)(((int)GX_TEXCOORD0)+uv_id);
+//	GXTevStageID s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+//
+//	// Modulate material color if required.
+//	if ( uniqueMaterialColor )
+//	{
+//		GX::SetTevKColor( (GXTevKColorID)(((int)GX_KCOLOR0)+tev_id), (GXColor){matcol.r,matcol.g,matcol.b,fix} );
+//		GX::SetTevKColorSel( s_id, (GXTevKColorSel)(((int)GX_TEV_KCSEL_K0)+tev_id) );
+//
+//		GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR0A0 );
+//		GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//		GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//		GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//		GX::SetTevAlphaIn ( s_id, rasa, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//		GX::SetTevColorIn ( s_id, GX_CC_ZERO, rasc, GX_CC_KONST, GX_CC_ZERO );
+//		stage_id++;
+//		s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+//	}
+//	
+//	// Set texture coordinates. Note: If p_tex is NULL, it means this layer is environment mapped.
+////	if ( !(flags & MATFLAG_ENVIRONMENT) )
+////	{
+//		*p_uv_slot = uv_id;
+//		uv_id++;
+////	} else {
+////		*p_uv_slot = -1;
+////	}
+//
+//	// Load this texture up into a temporary register for use later.
+//	// Note: conveniently, the 1st texture ends up in TEVPREV which means it will be fine
+//	// if no blends are performed.
+//	switch ( blend )
+//	{
+//		case vBLEND_MODE_ADD_FIXED:
+//		case vBLEND_MODE_SUB_FIXED:
+//		case vBLEND_MODE_BLEND_FIXED:
+//		case vBLEND_MODE_BRIGHTEN_FIXED:
+//		case vBLEND_MODE_MODULATE_FIXED:
+//			GX::SetTevKAlphaSel( s_id, (GXTevKAlphaSel)(((int)GX_TEV_KASEL_K0_A)+tev_id) );
+//			if ( !uniqueMaterialColor ) GX::SetTevKColor( (GXTevKColorID)(((int)GX_KCOLOR0)+tev_id), (GXColor){fix,fix,fix,fix} );
+//			GX::SetTevOrder( s_id, u_id, t_id, GX_COLOR0A0 );
+//            GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//			GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//			if ( uniqueMaterialColor )
+//			{
+//				GX::SetTevAlphaIn ( s_id, GX_CA_KONST, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//				GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_TEXC, (GXTevColorArg)(((int)GX_CC_CPREV)+(reg_id*2)), GX_CC_ZERO );
+//			}
+//			else
+//			{
+//				GX::SetTevAlphaIn ( s_id, GX_CA_KONST, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//				GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_TEXC, rasc, GX_CC_ZERO );
+//			}
+//			stage_id++;
+//			break;
+//		case vBLEND_MODE_ADD:
+//		case vBLEND_MODE_SUBTRACT:
+//		case vBLEND_MODE_BLEND:
+//		case vBLEND_MODE_BRIGHTEN:
+//		case vBLEND_MODE_MODULATE:
+//		case vBLEND_MODE_DIFFUSE:
+//		default:
+//			// We need to add a stage, if we have an alpha map.
+//			if ( alpha_map >= 0 )
+//			{
+//				// Set inputs.
+//				GXTexMapID a_id = (GXTexMapID)(((int)GX_TEXMAP0)+alpha_map);
+//
+//				GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP1 );       // Alpha map is in Green channel, so use this to swap it to the alpha channel.
+//				GX::SetTevOrder( s_id, u_id, a_id, GX_COLOR0A0 );
+//				GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//				GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//				if ( uniqueMaterialColor )
+//				{
+//					GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_TEXA, (GXTevAlphaArg)(((int)GX_CA_APREV)+reg_id), GX_CA_ZERO );
+//					GX::SetTevColorIn ( s_id, (GXTevColorArg)(((int)GX_CC_CPREV)+(reg_id*2)), GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//				}
+//				else
+//				{
+//					GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_TEXA, rasa, GX_CA_ZERO );
+//					GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//				}
+//				stage_id++;
+//				s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+//				GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//				GX::SetTevOrder( s_id, u_id, t_id, GX_COLOR0A0 );
+//				GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//				GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//				GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, (GXTevAlphaArg)(((int)GX_CA_APREV)+reg_id), GX_CA_TEXA, GX_CA_ZERO );
+//				if ( uniqueMaterialColor )
+//				{
+//					GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_TEXC, (GXTevColorArg)(((int)GX_CC_CPREV)+(reg_id*2)), GX_CC_ZERO );
+//				}
+//				else
+//				{
+//					GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_TEXC, rasc, GX_CC_ZERO );
+//				}
+//				stage_id++;
+//			} else {
+//				GX::SetTevOrder( s_id, u_id, t_id, GX_COLOR0A0 );
+//				GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//				GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//				GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+reg_id) );
+//				if ( uniqueMaterialColor )
+//				{
+//					GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_TEXA, (GXTevAlphaArg)(((int)GX_CA_APREV)+reg_id), GX_CA_ZERO );
+//					GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_TEXC, (GXTevColorArg)(((int)GX_CC_CPREV)+(reg_id*2)), GX_CC_ZERO );
+//				}
+//				else
+//				{
+//					GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_TEXA, rasa, GX_CA_ZERO );
+//					GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_TEXC, rasc, GX_CC_ZERO );
+//				}
+//				stage_id++;
+//			}
+//
+//			break;
+//	}
+//
+//	if ( tev_id > 0 ) {
+//		multi_add_layer ( blend, fix );
+//	} else {
+//		// Set blend mode for base layer.
+//		switch ( blend )
+//		{
+//			case vBLEND_MODE_ADD:
+//			case vBLEND_MODE_ADD_FIXED:
+//				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR );
+//				break;
+//			case vBLEND_MODE_SUBTRACT:
+//			case vBLEND_MODE_SUB_FIXED:
+//				GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ZERO, GX_BL_ZERO, GX_LO_CLEAR );
+//				break;
+//			case vBLEND_MODE_BLEND:
+//			case vBLEND_MODE_BLEND_FIXED:
+//				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR );
+//				break;
+//			case vBLEND_MODE_BRIGHTEN:
+//			case vBLEND_MODE_BRIGHTEN_FIXED:
+////				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ONE, GX_LO_CLEAR );
+//				GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+//				break;
+//			case vBLEND_MODE_MODULATE_FIXED:
+//			case vBLEND_MODE_MODULATE:
+////				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR );
+//				GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+//				break;
+//			case vBLEND_MODE_DIFFUSE:
+//			default:
+//				GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+//				break;
+//		}
+//	}
+//
+//	tev_id++;
+//	map_id = cur_map;
+//}
+//
+//static void multi_end ( BlendModes blend, uint32 alphacutoff )
+//{
+//	// Special case. If the base layer uses subtractive mode, we need to add a stage to
+//	// modulate the color & alpha.
+//	switch ( blend )
+//	{
+////		case vBLEND_MODE_DIFFUSE:
+////			GX::SetAlphaCompare(GX_GEQUAL, 0, GX_AOP_AND, GX_GEQUAL, 0 );
+////			if ( alphacutoff == 0 )
+////			{
+////				GXSetZCompLoc( GX_TRUE );
+////			}
+////			else
+////			{
+////				GXSetZCompLoc( GX_FALSE );		// Textures with holes will not write to the z buffer when a<=cutoff.
+////			}
+////			break;
+//		case vBLEND_MODE_SUB_FIXED:
+//		case vBLEND_MODE_SUBTRACT:
+//			{
+//				GXTevStageID s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+//
+//				GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR_NULL );
+//				GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//				GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//				GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//				GX::SetTevAlphaIn ( s_id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_APREV );
+//				GX::SetTevColorIn ( s_id, GX_CC_ZERO, GX_CC_CPREV, GX_CC_APREV, GX_CC_ZERO );
+//				stage_id++;
+//				//GX::SetAlphaCompare(GX_GEQUAL, 0, GX_AOP_AND, GX_GEQUAL, 0 );
+////				GXSetZCompLoc( GX_FALSE );
+//			}
+//			break;
+//		default:
+//			//GX::SetAlphaCompare(GX_GEQUAL, alphacutoff, GX_AOP_AND, GX_GEQUAL, alphacutoff );
+////			if ( alphacutoff == 0 )
+////			{
+////				GXSetZCompLoc( GX_TRUE );
+////			}
+////			else
+////			{
+////				GXSetZCompLoc( GX_FALSE );		// Textures with holes will not write to the z buffer when a<=cutoff.
+////			}
+//			break;
+//	}
+//
+////	if ( blend == vBLEND_MODE_DIFFUSE )
+////	{
+////		//Always pass pixels with multi-texturing as the base must be opaque.
+////		GX::SetAlphaCompare(GX_GEQUAL, 0, GX_AOP_AND, GX_GEQUAL, 0 );
+////		GXSetZCompLoc( GX_TRUE );
+////	}
+////	else
+////	{
+////	}
+//
+//	if ( correct_color )
+//	{
+//		GXTexCoordID u_id;
+//		// Convert color0 to s,t for 2-D texture lookup (RG)
+//		u_id = (GXTexCoordID)(((int)GX_TEXCOORD0)+uv_id);
+//		GX::SetTexCoordGen(u_id, GX_TG_SRTG, GX_TG_COLOR0, GX_IDENTITY);
+//		GX::SetTevOrder(GX_TEVSTAGE0, u_id, GX_TEXMAP7, GX_COLOR_NULL);
+//		uv_id++;
+//	
+//		// Convert color1 to s,t for 2-D texture lookup (BA)
+//		u_id = (GXTexCoordID)(((int)GX_TEXCOORD0)+uv_id);
+//		GX::SetTexCoordGen(u_id, GX_TG_SRTG, GX_TG_COLOR1, GX_IDENTITY);
+//		GX::SetTevOrder(GX_TEVSTAGE1, u_id, GX_TEXMAP7, GX_COLOR_NULL);
+//		uv_id++;
+//		GX::SetNumChans( 2 );
+//	}
+//	else
+//	{
+//		GX::SetNumChans( 1 );
+//	}
+//
+//	// Set final number of textures/stages.
+//	GX::SetNumTexGens( uv_id );
+//    GX::SetNumTevStages( stage_id );
+//}
+//
+////sMaterial					*Materials;
+//uint32						NumMaterials;
+////Lst::HashTable< sMaterial > *pMaterialTable	= NULL;
+//
+//void sMaterial::Submit( uint32 mesh_flags, GXColor mesh_color )
+//{
+//	// Figure uv and vc wibble updates if required.
+//	figure_wibble_uv();
+//	figure_wibble_vc();
+//
+//	
+//
+//	for ( uint layer = 0; layer < Passes; layer++ )
+//	{
+//		if( Flags[layer] & MATFLAG_UV_WIBBLE )
+//		{
+//			// Set up the texture generation matrix here.
+//			Mtx	m;
+//			MTXTrans( m, m_pUVControl->UVWibbleOffset[layer][0], m_pUVControl->UVWibbleOffset[layer][1], 0.0f );
+//			switch ( layer )
+//			{
+//				case 0:
+//					GX::LoadTexMtxImm( m, GX_TEXMTX0, GX_MTX2x4 );
+//					break;
+//				case 1:
+//					GX::LoadTexMtxImm( m, GX_TEXMTX1, GX_MTX2x4 );
+//					break;
+//				case 2:
+//					GX::LoadTexMtxImm( m, GX_TEXMTX2, GX_MTX2x4 );
+//					break;
+//				case 3:
+//					GX::LoadTexMtxImm( m, GX_TEXMTX3, GX_MTX2x4 );
+//					break;
+//				default:
+//					break;
+//				
+//			}
+//
+////			// Handle UV wibbling.
+////			D3DXMATRIX mat;
+////			mat._11 = 1.0f;					mat._12 = 0.0f; 
+////			mat._21 = 0.0f;					mat._22 = 1.0f;
+////			mat._31 = UVWibbleOffset[p][0];	mat._32 = UVWibbleOffset[p][1];
+////			mat._41 = 0.0f;					mat._42 = 0.0f;
+////			D3DDevice_SetTransform((D3DTRANSFORMSTATETYPE)( D3DTS_TEXTURE0 + p ), &mat );
+////			D3DDevice_SetTextureStageState( p, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 );
+////			D3DDevice_SetTextureStageState( p, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU | p );
+//		}
+//	}
+//
+////	// Generate display list.
+////
+////	multi_start ( Passes );
+////	for ( uint layer = 0; layer < Passes ; layer++ )
+////	{
+////		GXTexWrapMode u_mode;
+////		GXTexWrapMode v_mode;
+////		if ( Flags[layer] & MATFLAG_ENVIRONMENT )
+////		{
+////			u_mode = GX_REPEAT;
+////			v_mode = GX_REPEAT;
+////		}
+////		else
+////		{
+////			u_mode = UVAddressing[layer] & (1<<0)  ? GX_CLAMP : GX_REPEAT;
+////			v_mode = UVAddressing[layer] & (1<<1) ? GX_CLAMP : GX_REPEAT;
+////		}
+////
+////		if ( pTex[layer] )
+////		{
+////			multi_add_texture ( pTex[layer],
+////								matcol[layer],
+////								u_mode,
+////								v_mode,
+////								(BlendModes)blendMode[layer],
+////								fixAlpha[layer],
+////								Flags[layer],
+////								&uv_slot[layer],
+////								K[layer] );
+////		}
+////		else
+////		{
+////			// Untextured.
+////			GXTevStageID s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+////			BlendModes blend = (BlendModes)blendMode[layer];
+////			uint32 fix = fixAlpha[layer];
+////
+////			uv_slot[layer] = -1;
+////
+////			switch ( blend )
+////			{
+////				case vBLEND_MODE_ADD_FIXED:
+////				case vBLEND_MODE_SUB_FIXED:
+////				case vBLEND_MODE_BLEND_FIXED:
+////				case vBLEND_MODE_BRIGHTEN_FIXED:
+////				case vBLEND_MODE_MODULATE_FIXED:
+////					GX::SetTevKAlphaSel( s_id, (GXTevKAlphaSel)(((int)GX_TEV_KASEL_K0_A)+tev_id) );
+////					GX::SetTevKColor( (GXTevKColorID)(((int)GX_KCOLOR0)+tev_id), (GXColor){fix,fix,fix,fix} );
+////					GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR0A0 );
+////					GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+////					GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+////					GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+////					GX::SetTevAlphaIn ( s_id, GX_CA_KONST, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+////					GX::SetTevColorIn ( s_id, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+////					stage_id++;
+////					break;
+////				case vBLEND_MODE_ADD:
+////				case vBLEND_MODE_SUBTRACT:
+////				case vBLEND_MODE_BLEND:
+////				case vBLEND_MODE_BRIGHTEN:
+////				case vBLEND_MODE_MODULATE:
+////				case vBLEND_MODE_DIFFUSE:
+////				default:
+////					GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR0A0 );
+////					GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+////					GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+////					GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+////					GX::SetTevAlphaIn ( s_id, GX_CA_RASA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+////					GX::SetTevColorIn ( s_id, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+////					stage_id++;
+////					break;
+////			}
+////
+////			if ( tev_id > 0 ) {
+////				multi_add_layer ( blend, fix );
+////			} else {
+////				// Set blend mode for base layer.
+////				switch ( blend )
+////				{
+////					case vBLEND_MODE_ADD:
+////					case vBLEND_MODE_ADD_FIXED:
+////						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR );
+////						break;
+////					case vBLEND_MODE_SUBTRACT:
+////					case vBLEND_MODE_SUB_FIXED:
+////						GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ZERO, GX_BL_ZERO, GX_LO_CLEAR );
+////						break;
+////					case vBLEND_MODE_BLEND:
+////					case vBLEND_MODE_BLEND_FIXED:
+////						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR );
+////						break;
+////					case vBLEND_MODE_BRIGHTEN:
+////					case vBLEND_MODE_BRIGHTEN_FIXED:
+////						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ONE, GX_LO_CLEAR );
+////						break;
+////					case vBLEND_MODE_MODULATE_FIXED:
+////					case vBLEND_MODE_MODULATE:
+////						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR );
+////						break;
+////					case vBLEND_MODE_DIFFUSE:
+////					default:
+////						GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+////						break;
+////				}
+////			}
+////			tev_id++;
+////		}
+////	}
+////
+////	multi_end( (BlendModes)blendMode[0], AlphaCutoff );
+//
+//
+//	// Set the cull mode, but only if the calling code didn't override this by setting CULL_NONE.
+//	if ( EngineGlobals.poly_culling )
+//	{
+//		GX::SetCullMode ( m_no_bfc ? GX_CULL_NONE : GX_CULL_FRONT );
+//	}
+//	else
+//	{
+//		GX::SetCullMode ( GX_CULL_NONE );
+//	}
+//
+//#ifdef __USE_MAT_DL__
+//	if ( mp_display_list )
+//	{
+//		GXCallDisplayList ( mp_display_list, m_display_list_size ); 
+//	}
+//#else
+//	Initialize( mesh_flags, mesh_color );
+//#endif		// __USE_MAT_DL__
+//
+//	// See if we need to set the comp location (just check the base layer).
+//	if ( pTex[0] )
+//	{
+//		if ( pTex[0]->HasHoles )
+//		{
+//			GX::SetZCompLoc( GX_FALSE );
+//		}
+//		else
+//		{
+////			GX::SetZCompLoc( GX_FALSE );
+//			GX::SetZCompLoc( GX_TRUE );
+////			GX::SetAlphaCompare(GX_GEQUAL, 0, GX_AOP_AND, GX_GEQUAL, 0 );
+//		}
+//	}
+//	GX::SetAlphaCompare(GX_GEQUAL, AlphaCutoff, GX_AOP_AND, GX_GEQUAL, AlphaCutoff );
+//
+////	if ( mp_display_list )
+////	{
+////		GXCallDisplayList ( mp_display_list, m_display_list_size ); 
+////	}
+////	else
+////	{
+////		// If no material, default to purple, untextured.
+////		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+////		GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+////		GX::SetNumTexGens( 0 );
+////		GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+////		GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+////		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+////		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+////		GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+////		GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+////		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,0,255,255} );
+////		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+////		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+////	}
+//}
+//
+//void sMaterial::Initialize( uint32 mesh_flags, GXColor mesh_color )
+//{
+//#ifdef __USE_MAT_DL__
+//	// Generate display list.
+//	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+//#define DL_BUILD_SIZE (32*1024)
+//	uint8 * p_build_dl = new (Mem::Manager::sHandle().TopDownHeap()) uint8[DL_BUILD_SIZE];
+//	DCFlushRange ( p_build_dl, DL_BUILD_SIZE );
+//
+//	GXBeginDisplayList ( p_build_dl, DL_BUILD_SIZE );
+//	GXResetWriteGatherPipe();
+//#endif		// __USE_MAT_DL__
+//
+//	multi_start ( Passes );
+//	for ( uint layer = 0; layer < Passes ; layer++ )
+//	{
+//		GXTexWrapMode u_mode;
+//		GXTexWrapMode v_mode;
+//		if ( Flags[layer] & MATFLAG_ENVIRONMENT )
+//		{
+//			u_mode = GX_REPEAT;
+//			v_mode = GX_REPEAT;
+//		}
+//		else
+//		{
+//			u_mode = UVAddressing[layer] & (1<<0)  ? GX_CLAMP : GX_REPEAT;
+//			v_mode = UVAddressing[layer] & (1<<1) ? GX_CLAMP : GX_REPEAT;
+//		}
+//
+//		GXColor col;
+//
+//		if ( mesh_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE && !( Flags[layer] & 0x80 ) )
+//		{
+//			col = mesh_color;
+//		}
+//		else
+//		{
+//			col = matcol[layer];
+//		}
+//
+//		if ( pTex[layer] )
+//		{
+//			multi_add_texture ( pTex[layer],
+//								col,
+//								u_mode,
+//								v_mode,
+//								(BlendModes)blendMode[layer],
+//								fixAlpha[layer],
+//								Flags[layer],
+//								&uv_slot[layer],
+//								K[layer] );
+//		}
+//		else
+//		{
+//			// Untextured.
+//			GXTevStageID s_id = (GXTevStageID)(((int)GX_TEVSTAGE0)+stage_id);
+//			BlendModes blend = (BlendModes)blendMode[layer];
+//			uint32 fix = fixAlpha[layer];
+//
+//			uv_slot[layer] = -1;
+//
+//			switch ( blend )
+//			{
+//				case vBLEND_MODE_ADD_FIXED:
+//				case vBLEND_MODE_SUB_FIXED:
+//				case vBLEND_MODE_BLEND_FIXED:
+//				case vBLEND_MODE_BRIGHTEN_FIXED:
+//				case vBLEND_MODE_MODULATE_FIXED:
+//					GX::SetTevKAlphaSel( s_id, (GXTevKAlphaSel)(((int)GX_TEV_KASEL_K0_A)+tev_id) );
+//					GX::SetTevKColor( (GXTevKColorID)(((int)GX_KCOLOR0)+tev_id), (GXColor){fix,fix,fix,fix} );
+//					GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR0A0 );
+//					GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//					GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+//					GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+//					GX::SetTevAlphaIn ( s_id, GX_CA_KONST, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//					GX::SetTevColorIn ( s_id, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//					stage_id++;
+//					break;
+//				case vBLEND_MODE_ADD:
+//				case vBLEND_MODE_SUBTRACT:
+//				case vBLEND_MODE_BLEND:
+//				case vBLEND_MODE_BRIGHTEN:
+//				case vBLEND_MODE_MODULATE:
+//				case vBLEND_MODE_DIFFUSE:
+//				default:
+//					GX::SetTevOrder( s_id, GX_TEXCOORD_NULL, GX_TEX_DISABLE, GX_COLOR0A0 );
+//					GX::SetTevSwapMode( s_id, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//					GX::SetTevAlphaOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+//					GX::SetTevColorOp(s_id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, (GXTevRegID)(((int)GX_TEVPREV)+tev_id) );
+//					GX::SetTevAlphaIn ( s_id, GX_CA_RASA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//					GX::SetTevColorIn ( s_id, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//					stage_id++;
+//					break;
+//			}
+//
+//			if ( tev_id > 0 ) {
+//				multi_add_layer ( blend, fix );
+//			} else {
+//				// Set blend mode for base layer.
+//				switch ( blend )
+//				{
+//					case vBLEND_MODE_ADD:
+//					case vBLEND_MODE_ADD_FIXED:
+//						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR );
+//						break;
+//					case vBLEND_MODE_SUBTRACT:
+//					case vBLEND_MODE_SUB_FIXED:
+//						GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ZERO, GX_BL_ZERO, GX_LO_CLEAR );
+//						break;
+//					case vBLEND_MODE_BLEND:
+//					case vBLEND_MODE_BLEND_FIXED:
+//						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR );
+//						break;
+//					case vBLEND_MODE_BRIGHTEN:
+//					case vBLEND_MODE_BRIGHTEN_FIXED:
+//						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ONE, GX_LO_CLEAR );
+//						break;
+//					case vBLEND_MODE_MODULATE_FIXED:
+//					case vBLEND_MODE_MODULATE:
+//						GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR );
+//						break;
+//					case vBLEND_MODE_DIFFUSE:
+//					default:
+//						GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+//						break;
+//				}
+//			}
+//			tev_id++;
+//		}
+//	}
+//
+//	multi_end( (BlendModes)blendMode[0], AlphaCutoff );
+//
+//#ifdef __USE_MAT_DL__
+//	int size = GXEndDisplayList();
+//
+//	// If we already have a display list & this one will fit, just use the existing piece of memory.
+//	uint8 * p_dl;
+//	if ( mp_display_list && ( size <= m_display_list_size ) )
+//	{
+//		p_dl = mp_display_list;
+//	}
+//	else
+//	{
+//		if ( mp_display_list )
+//		{
+//			//gMatBytes -= m_display_list_size;
+//			delete mp_display_list;
+//		}
+//		p_dl = new uint8[size];
+//		//gMatBytes += size;
+//	}
+//
+//	memcpy ( p_dl, p_build_dl, size );
+//	DCFlushRange ( p_dl, size );
+//
+//	delete p_build_dl;
+//
+//	mp_display_list = p_dl;
+//	m_display_list_size = size;
+//	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+//#endif		// __USE_MAT_DL__
+//}
+
+
+
+
+sMaterial* GetMaterial( uint32 checksum, sScene *p_scene )
+{
+//	for ( int lp = 0; lp < p_scene->m_num_materials; lp++ )
+//	{
+//		if( p_scene->mp_material_array[lp].Checksum == checksum )
+//		{
+//			return &p_scene->mp_material_array[lp];
+//		}
+//	}
+	return NULL;
+}
+
+void InitializeMaterial( sMaterial* p_material )
+{
+//	p_material->Initialize();
+	p_material->uv_slot[0] = 0;
+	p_material->uv_slot[1] = 1;
+	p_material->uv_slot[2] = 2;
+	p_material->uv_slot[3] = 3;
+}
+
+#define MemoryRead( dst, size, num, src )	memcpy(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMaterial * LoadMaterialsFromMemory( void **pp_mem, Lst::HashTable< Nx::CTexture > *p_texture_table, int * p_num_materials )
+{
+	uint8	*p_data = (uint8*)( *pp_mem );
+	uint32 MMAG, MMIN, K, L, NumSeqs, seq, NumKeys;
+	uint32 i;
+
+	// Get number of materials.
+	uint32 new_materials;
+	MemoryRead( &new_materials, sizeof( uint32 ), 1, p_data );
+	
+	if ( p_num_materials ) *p_num_materials = new_materials;
+
+	// Create array of materials.
+	sMaterial * p_mat_array = new sMaterial[new_materials];
+	
+	// Loop over materials.
+	for( i = 0; i < new_materials; ++i )
+	{
+		// Create new material.
+		sMaterial *pMat = &p_mat_array[i];
+		
+		pMat->m_num_wibble_vc_anims	= 0;
+		pMat->mp_wibble_vc_params	= NULL;
+		pMat->mp_wibble_vc_colors	= NULL;
+
+		// Get material checksum.
+		MemoryRead( &pMat->Checksum, sizeof( uint32 ), 1, p_data );
+
+		// Get number of passes.
+		uint32 pass;
+		MemoryRead( &pass, sizeof( uint32 ), 1, p_data );
+		pMat->Passes = pass;
+
+		// Get alpha cutoff value.
+		uint32 ac;
+		MemoryRead( &ac, sizeof( uint32 ), 1, p_data );
+		pMat->AlphaCutoff = ac;		//( ac >= 128 ) ? 255 : ( ac * 2 );
+
+		// Get sorted flag.
+		bool sorted;
+		MemoryRead( &sorted, sizeof( bool ), 1, p_data );
+		pMat->m_sorted = sorted;
+		
+		// Get draw order.
+		MemoryRead( &pMat->m_draw_order, sizeof( float ), 1, p_data );
+		
+		// Get backface cull flag.
+		bool bfc;
+		MemoryRead( &bfc, sizeof( bool ), 1, p_data );
+		pMat->m_no_bfc = bfc;
+
+//		// Get grassify flag and (optionally) grassify data.
+//		bool grassify;
+//		MemoryRead( &grassify, sizeof( bool ), 1, p_data );
+//		pMat->m_grassify = (uint8)grassify;
+//		if( grassify )
+//		{
+//			MemoryRead( &pMat->m_grass_height, sizeof( float ), 1, p_data );
+//			int layers;
+//			MemoryRead( &layers, sizeof( int ), 1, p_data );
+//			pMat->m_grass_layers = (uint8)layers;
+//		}
+
+		GXColor mat_col[MAX_PASSES];
+
+		pMat->m_pUVControl = NULL;
+		for( uint32 pass = 0; pass < pMat->Passes; ++pass )
+		{
+			// Get texture checksum.
+			uint32 TextureChecksum;
+			MemoryRead( &TextureChecksum, sizeof( uint32 ), 1, p_data );
+
+			// Get material flags.
+			MemoryRead( &pMat->Flags[pass], sizeof( uint32 ), 1, p_data );
+
+			// Get ALPHA register value.
+			uint64 ra;
+			MemoryRead( &ra, sizeof( uint64 ), 1, p_data );
+			pMat->blendMode[pass] = ra & 0xff;
+			pMat->fixAlpha[pass] = ( ra >> 32 ) & 0xff;
+
+			// Backface cull test - if this is an alpha blended material, turn off backface culling.
+			if(( pass == 0 ) && (( ra & 0xFF ) != 0x00 ))
+			{
+				pMat->m_no_bfc = true;
+			}
+
+			// Get UV addressing types.
+			uint32 u_addressing, v_addressing;
+			MemoryRead( &u_addressing, sizeof( uint32 ), 1, p_data );
+			MemoryRead( &v_addressing, sizeof( uint32 ), 1, p_data );
+			pMat->UVAddressing[pass] = (( v_addressing << 1 ) | u_addressing );
+			
+			// Step past material colors, currently unsupported.
+			float col[9];
+			MemoryRead( col, sizeof( float ) * 9, 1, p_data );
+
+			mat_col[pass].r = (u8)((col[0] * 255.0f) + 0.5f);
+			mat_col[pass].g = (u8)((col[1] * 255.0f) + 0.5f);
+			mat_col[pass].b = (u8)((col[2] * 255.0f) + 0.5f);
+			mat_col[pass].a = 255;
+
+			// Read uv wibble data if present.
+			if( pMat->Flags[pass] & MATFLAG_UV_WIBBLE )
+			{
+				if ( !pMat->m_pUVControl ) pMat->m_pUVControl = new sUVControl;
+				MemoryRead( &( pMat->m_pUVControl->UVWibbleParams[pass] ), sizeof( sUVWibbleParams ), 1, p_data );
+			}
+
+			// Step past vc wibble data, currently unsupported.
+			if(( pass == 0 ) && ( pMat->Flags[0] & MATFLAG_VC_WIBBLE ))
+			{
+				MemoryRead( &NumSeqs, sizeof( uint32 ), 1, p_data );
+				pMat->m_num_wibble_vc_anims = NumSeqs;
+
+				// Create sequence data array.
+				pMat->mp_wibble_vc_params = new sVCWibbleParams[NumSeqs];
+				
+				// Create resultant color array.
+				pMat->mp_wibble_vc_colors = new uint32[NumSeqs];
+
+				for( seq = 0; seq < NumSeqs; ++seq )
+				{ 
+					MemoryRead( &NumKeys, sizeof( uint32 ), 1, p_data );
+
+					int phase;
+					MemoryRead( &phase, sizeof( int ), 1, p_data );
+
+					// Create array for keyframes.
+					pMat->mp_wibble_vc_params[seq].m_num_keyframes	= NumKeys;
+					pMat->mp_wibble_vc_params[seq].m_phase			= phase;
+					pMat->mp_wibble_vc_params[seq].mp_keyframes		= new sVCWibbleKeyframe[NumKeys];
+
+					// Read keyframes into array.
+					MemoryRead( pMat->mp_wibble_vc_params[seq].mp_keyframes, sizeof( sVCWibbleKeyframe ), NumKeys, p_data );
+
+					for ( uint lp = 0; lp < NumKeys; lp++ )
+					{
+						uint8 r = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.r;
+						uint8 g = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.g;
+						uint8 b = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.b;
+						uint8 a = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.a;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.r = b;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.g = r;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.b = g;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.a = a;
+					}
+				}
+			}
+
+			if( TextureChecksum )
+			{
+				// If textured, resolve texture checksum...
+				Nx::CNgcTexture *p_ngc_texture = static_cast( p_texture_table->GetItem( TextureChecksum ) );
+				sTexture *pTex = ( p_ngc_texture ) ? p_ngc_texture->GetEngineTexture() : NULL;
+
+				// Bail if checksum not found.
+				if( pTex == NULL )
+				{
+					Dbg_Message( "error: couldn't find texture checksum %08X\n", TextureChecksum );
+//					exit( 1 );
+					pMat->pTex[pass] = NULL;
+				}
+				else
+				{
+					// Set texture pointer.
+					pMat->pTex[pass] = pTex;
+				}
+
+				// Get mipmap info.
+				MemoryRead( &MMAG, sizeof( uint32 ), 1, p_data );
+				MemoryRead( &MMIN, sizeof( uint32 ), 1, p_data );
+
+				MemoryRead( &K, sizeof( uint32 ), 1, p_data );
+				MemoryRead( &L, sizeof( uint32 ), 1, p_data );
+				
+				// Default PS2 value for K appears to be -8.0f - we are interested in deviations from this value.
+				pMat->K[pass]	= ( *(float*)&K ) + 8.0f;
+			
+				// Dave note 08/03/02 - having MIPs selected earlier than normal seems to cause some problems, since Xbox
+				// MIP selection is so different to Ps2. Limit the k value such that Xbox can never select smaller MIPs
+				// earlier than it would do by default.
+				if( pMat->K[pass] > 0.0f )
+				{
+					pMat->K[pass] = 0.0f;
+				}
+			}
+			else
+			{
+				// ...otherwise just step past mipmap info.
+				pMat->pTex[pass] = NULL;
+				uint32 mip[4];
+				MemoryRead( mip, sizeof( uint32 ) * 4, 1, p_data );
+			}
+		}
+
+		// Generate material colors.
+		for( uint32 pass = 0; pass < pMat->Passes; ++pass )
+		{
+			if ( ( mat_col[0].r == mat_col[pass].r ) &&
+				 ( mat_col[0].g == mat_col[pass].g ) && 
+				 ( mat_col[0].b == mat_col[pass].b ) && 
+				 ( mat_col[0].a == mat_col[pass].a ) )
+			{
+				// Do nothing.
+				pMat->matcol[pass] = (GXColor){128,128,128,255};
+			}
+			else
+			{
+				// Work out the texture color.
+				GXColor tc;
+				float base_r = (float)(mat_col[0].r > 0 ? mat_col[0].r : 1 );
+				float base_g = (float)(mat_col[0].g > 0 ? mat_col[0].g : 1 );
+				float base_b = (float)(mat_col[0].b > 0 ? mat_col[0].b : 1 );
+				float base_a = (float)(mat_col[0].a > 0 ? mat_col[0].a : 1 );
+				float r = ( ((float)mat_col[pass].r) / base_r ) * 128.0f;
+				float g = ( ((float)mat_col[pass].g) / base_g ) * 128.0f;
+				float b = ( ((float)mat_col[pass].b) / base_b ) * 128.0f;
+				float a = ( ((float)mat_col[pass].a) / base_a ) * 255.0f;
+				int _r = (int)r;
+				int _g = (int)g;
+				int _b = (int)b;
+				int _a = (int)a;
+				if ( _r > 255 ) _r = 255;
+				if ( _g > 255 ) _g = 255;
+				if ( _b > 255 ) _b = 255;
+				if ( _a > 255 ) _a = 255;
+				tc.r = (u8)( _r );
+				tc.g = (u8)( _g );
+				tc.b = (u8)( _b );
+				tc.a = (u8)( _a );
+				pMat->matcol[pass] = tc;
+			}
+		}
+
+//		pMat->mp_display_list = NULL;
+//		pMat->Initialize();
+		pMat->uv_slot[0] = 0;
+		pMat->uv_slot[1] = 1;
+		pMat->uv_slot[2] = 2;
+		pMat->uv_slot[3] = 3;
+	}
+	*pp_mem = p_data;
+
+	return p_mat_array;
+}
+
+sMaterial *LoadMaterials( void *p_FH, Lst::HashTable< Nx::CTexture > *p_texture_table, int * p_num_materials )
+{
+	uint32 MMAG, MMIN, K, L, NumSeqs, seq, NumKeys;
+	uint32 i;
+
+	// Get number of materials.
+	uint32 new_materials;
+	File::Read( &new_materials, sizeof( uint32 ), 1, p_FH );
+	
+	if ( p_num_materials ) *p_num_materials = new_materials;
+
+	// Create array of materials.
+	sMaterial * p_mat_array = new sMaterial[new_materials];
+	
+	// Loop over materials.
+	for( i = 0; i < new_materials; ++i )
+	{
+		// Create new material.
+		sMaterial *pMat = &p_mat_array[i];
+		
+		pMat->m_num_wibble_vc_anims	= 0;
+		pMat->mp_wibble_vc_params	= NULL;
+		pMat->mp_wibble_vc_colors	= NULL;
+
+		// Get material checksum.
+		File::Read( &pMat->Checksum, sizeof( uint32 ), 1, p_FH );
+
+		// Get number of passes.
+		uint32 pass;
+		File::Read( &pass, sizeof( uint32 ), 1, p_FH );
+		pMat->Passes = pass;
+
+		// Get alpha cutoff value.
+		uint32 ac;
+		File::Read( &ac, sizeof( uint32 ), 1, p_FH );
+		pMat->AlphaCutoff = ac;		//( ac >= 128 ) ? 255 : ( ac * 2 );
+
+		// Get sorted flag.
+		bool sorted;
+		File::Read( &sorted, sizeof( bool ), 1, p_FH );
+		pMat->m_sorted = sorted;
+		
+		// Get draw order.
+		File::Read( &pMat->m_draw_order, sizeof( float ), 1, p_FH );
+		
+		// Get backface cull flag.
+		bool bfc;
+		File::Read( &bfc, sizeof( bool ), 1, p_FH );
+		pMat->m_no_bfc = bfc;
+
+//		// Get grassify flag and (optionally) grassify data.
+//		bool grassify;
+//		File::Read( &grassify, sizeof( bool ), 1, p_FH );
+//		pMat->m_grassify = (uint8)grassify;
+//		if( grassify )
+//		{
+//			File::Read( &pMat->m_grass_height, sizeof( float ), 1, p_FH );
+//			int layers;
+//			File::Read( &layers, sizeof( int ), 1, p_FH );
+//			pMat->m_grass_layers = (uint8)layers;
+//		}
+
+		GXColor mat_col[MAX_PASSES];
+
+		pMat->m_pUVControl = NULL;
+		for( uint32 pass = 0; pass < pMat->Passes; ++pass )
+		{
+			// Get texture checksum.
+			uint32 TextureChecksum;
+			File::Read( &TextureChecksum, sizeof( uint32 ), 1, p_FH );
+
+			// Get material flags.
+			File::Read( &pMat->Flags[pass], sizeof( uint32 ), 1, p_FH );
+
+			// Get ALPHA register value.
+			uint64 ra;
+			File::Read( &ra, sizeof( uint64 ), 1, p_FH );
+			pMat->blendMode[pass] = ra & 0xff;
+			pMat->fixAlpha[pass] = ( ra >> 32 ) & 0xff;
+
+			// Backface cull test - if this is an alpha blended material, turn off backface culling.
+			if(( pass == 0 ) && (( ra & 0xFF ) != 0x00 ))
+			{
+				pMat->m_no_bfc = true;
+			}
+
+			// Get UV addressing types.
+			uint32 u_addressing, v_addressing;
+			File::Read( &u_addressing, sizeof( uint32 ), 1, p_FH );
+			File::Read( &v_addressing, sizeof( uint32 ), 1, p_FH );
+			pMat->UVAddressing[pass] = (( v_addressing << 1 ) | u_addressing );
+			
+			// Step past material colors, currently unsupported.
+			float col[9];
+			File::Read( col, sizeof( float ) * 9, 1, p_FH );
+
+			mat_col[pass].r = (u8)((col[0] * 255.0f) + 0.5f);
+			mat_col[pass].g = (u8)((col[1] * 255.0f) + 0.5f);
+			mat_col[pass].b = (u8)((col[2] * 255.0f) + 0.5f);
+			mat_col[pass].a = 255;
+
+			// Read uv wibble data if present.
+			if( pMat->Flags[pass] & MATFLAG_UV_WIBBLE )
+			{
+				if ( !pMat->m_pUVControl ) pMat->m_pUVControl = new sUVControl;
+				File::Read( &( pMat->m_pUVControl->UVWibbleParams[pass] ), sizeof( sUVWibbleParams ), 1, p_FH );
+			}
+
+			// Step past vc wibble data, currently unsupported.
+			if(( pass == 0 ) && ( pMat->Flags[0] & MATFLAG_VC_WIBBLE ))
+			{
+				File::Read( &NumSeqs, sizeof( uint32 ), 1, p_FH );
+				pMat->m_num_wibble_vc_anims = NumSeqs;
+
+				// Create sequence data array.
+				pMat->mp_wibble_vc_params = new sVCWibbleParams[NumSeqs];
+				
+				// Create resultant color array.
+				pMat->mp_wibble_vc_colors = new uint32[NumSeqs];
+
+				for( seq = 0; seq < NumSeqs; ++seq )
+				{ 
+					File::Read( &NumKeys, sizeof( uint32 ), 1, p_FH );
+
+					int phase;
+					File::Read( &phase, sizeof( int ), 1, p_FH );
+
+					// Create array for keyframes.
+					pMat->mp_wibble_vc_params[seq].m_num_keyframes	= NumKeys;
+					pMat->mp_wibble_vc_params[seq].m_phase			= phase;
+					pMat->mp_wibble_vc_params[seq].mp_keyframes		= new sVCWibbleKeyframe[NumKeys];
+
+					// Read keyframes into array.
+					File::Read( pMat->mp_wibble_vc_params[seq].mp_keyframes, sizeof( sVCWibbleKeyframe ), NumKeys, p_FH );
+
+					for ( uint lp = 0; lp < NumKeys; lp++ )
+					{
+						uint8 r = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.r;
+						uint8 g = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.g;
+						uint8 b = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.b;
+						uint8 a = pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.a;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.r = b;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.g = r;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.b = g;
+						pMat->mp_wibble_vc_params[seq].mp_keyframes[lp].m_color.a = a;
+					}
+				}
+			}
+
+			if( TextureChecksum )
+			{
+				// If textured, resolve texture checksum...
+				Nx::CNgcTexture *p_ngc_texture = static_cast( p_texture_table->GetItem( TextureChecksum ) );
+				sTexture *pTex = ( p_ngc_texture ) ? p_ngc_texture->GetEngineTexture() : NULL;
+
+				// Bail if checksum not found.
+				if( pTex == NULL )
+				{
+					Dbg_Message( "error: couldn't find texture checksum %08X\n", TextureChecksum );
+//					exit( 1 );
+					pMat->pTex[pass] = NULL;
+				}
+				else
+				{
+					// Set texture pointer.
+					pMat->pTex[pass] = pTex;
+				}
+
+				// Get mipmap info.
+				File::Read( &MMAG, sizeof( uint32 ), 1, p_FH );
+				File::Read( &MMIN, sizeof( uint32 ), 1, p_FH );
+
+				File::Read( &K, sizeof( uint32 ), 1, p_FH );
+				File::Read( &L, sizeof( uint32 ), 1, p_FH );
+				
+				// Default PS2 value for K appears to be -8.0f - we are interested in deviations from this value.
+				pMat->K[pass]	= ( *(float*)&K ) + 8.0f;
+			
+				// Dave note 08/03/02 - having MIPs selected earlier than normal seems to cause some problems, since Xbox
+				// MIP selection is so different to Ps2. Limit the k value such that Xbox can never select smaller MIPs
+				// earlier than it would do by default.
+				if( pMat->K[pass] > 0.0f )
+				{
+					pMat->K[pass] = 0.0f;
+				}
+			}
+			else
+			{
+				// ...otherwise just step past mipmap info.
+				pMat->pTex[pass] = NULL;
+				uint32 mip[4];
+				File::Read( mip, sizeof( uint32 ) * 4, 1, p_FH );
+			}
+		}
+
+		// Generate material colors.
+		for( uint32 pass = 0; pass < pMat->Passes; ++pass )
+		{
+			if ( ( mat_col[0].r == mat_col[pass].r ) &&
+				 ( mat_col[0].g == mat_col[pass].g ) && 
+				 ( mat_col[0].b == mat_col[pass].b ) && 
+				 ( mat_col[0].a == mat_col[pass].a ) )
+			{
+				// Do nothing.
+				pMat->matcol[pass] = (GXColor){128,128,128,255};
+			}
+			else
+			{
+				// Work out the texture color.
+				GXColor tc;
+				float base_r = (float)(mat_col[0].r > 0 ? mat_col[0].r : 1 );
+				float base_g = (float)(mat_col[0].g > 0 ? mat_col[0].g : 1 );
+				float base_b = (float)(mat_col[0].b > 0 ? mat_col[0].b : 1 );
+				float base_a = (float)(mat_col[0].a > 0 ? mat_col[0].a : 1 );
+				float r = ( ((float)mat_col[pass].r) / base_r ) * 128.0f;
+				float g = ( ((float)mat_col[pass].g) / base_g ) * 128.0f;
+				float b = ( ((float)mat_col[pass].b) / base_b ) * 128.0f;
+				float a = ( ((float)mat_col[pass].a) / base_a ) * 255.0f;
+				int _r = (int)r;
+				int _g = (int)g;
+				int _b = (int)b;
+				int _a = (int)a;
+				if ( _r > 255 ) _r = 255;
+				if ( _g > 255 ) _g = 255;
+				if ( _b > 255 ) _b = 255;
+				if ( _a > 255 ) _a = 255;
+				tc.r = (u8)( _r );
+				tc.g = (u8)( _g );
+				tc.b = (u8)( _b );
+				tc.a = (u8)( _a );
+				pMat->matcol[pass] = tc;
+			}
+		}
+
+//		pMat->mp_display_list = NULL;
+//		pMat->Initialize();
+		pMat->uv_slot[0] = 0;
+		pMat->uv_slot[1] = 1;
+		pMat->uv_slot[2] = 2;
+		pMat->uv_slot[3] = 3;
+	}
+
+	return p_mat_array;
+}
+
+} // namespace NxNgc
+
diff --git a/Code/Gfx/NGC/NX/material.h b/Code/Gfx/NGC/NX/material.h
new file mode 100644
index 0000000..7353b91
--- /dev/null
+++ b/Code/Gfx/NGC/NX/material.h
@@ -0,0 +1,118 @@
+#ifndef __MATERIAL_H
+#define __MATERIAL_H
+
+#include 
+#include "texture.h"
+#include 
+
+//#define __USE_MAT_DL__		// Comment out to save memory.
+
+namespace NxNgc
+{
+
+// Material Flags
+#define MATFLAG_UV_WIBBLE   (1<<0)
+#define MATFLAG_VC_WIBBLE   (1<<1)
+#define MATFLAG_TEXTURED    (1<<2)
+#define MATFLAG_ENVIRONMENT (1<<3)
+#define MATFLAG_DECAL       (1<<4)
+#define MATFLAG_SMOOTH      (1<<5)
+#define MATFLAG_TRANSPARENT (1<<6)
+
+
+const uint32 MAX_PASSES = 4;
+
+	
+struct sUVWibbleParams
+{
+	float	m_UVel;
+	float	m_VVel;
+	float	m_UFrequency;
+	float	m_VFrequency;
+	float	m_UAmplitude;
+	float	m_VAmplitude;
+	float	m_UPhase;
+	float	m_VPhase;
+};
+
+struct sUVControl
+{
+	sUVWibbleParams	UVWibbleParams[MAX_PASSES];         // 128
+	float			UVWibbleOffset[MAX_PASSES][2];		// 32
+};
+
+struct sVCWibbleKeyframe
+{
+	int			m_time;
+	Image::RGBA	m_color;
+};
+
+
+
+struct sVCWibbleParams
+{
+	uint32				m_num_keyframes;
+	int					m_phase;
+	sVCWibbleKeyframe	*mp_keyframes;
+};
+
+
+
+
+struct sMaterial
+{
+//	sMaterial( void );
+//	~sMaterial( void );
+
+	void			Submit( uint32 mesh_flags, GXColor mesh_color );
+	void			Initialize( uint32 mesh_flags, GXColor mesh_color );
+	void			figure_wibble_uv( void );
+	void			figure_wibble_vc( void );
+		
+	uint32			Checksum;							// 4
+	uint8			Passes;                             // 1
+	uint8			m_sorted;                           // 1
+	uint8			m_no_bfc;                           // 1
+	uint8			AlphaCutoff;                        // 1
+	float			m_draw_order;                       // 4
+	uint32			Flags[MAX_PASSES];                  // 16
+	sTexture*		pTex[MAX_PASSES];                   // 16
+	uint8			blendMode[MAX_PASSES];              // 4
+	uint8			fixAlpha[MAX_PASSES];               // 4
+	uint8			UVAddressing[MAX_PASSES];           // 4
+	float			K[MAX_PASSES];						// 16
+
+	char			uv_slot[MAX_PASSES];				// 4
+	GXColor			matcol[4];							// 16
+
+#ifdef __USE_MAT_DL__
+	uint8*			mp_display_list;					// 4
+	uint16			m_display_list_size;				// 2
+#endif		// __USE_MAT_DL__
+	uint8			m_wibble_uv_update;					// 1
+	uint8			m_num_wibble_vc_anims;				// 1
+	uint16			pad;
+//	uint8			m_grassify;
+//	uint8			m_grass_layers;
+//	float			m_grass_height;
+
+	sUVControl*		m_pUVControl;						// 4
+
+	float			m_wibble_uv_time;					// 4
+	sVCWibbleParams	*mp_wibble_vc_params;				// 4
+	uint32			*mp_wibble_vc_colors;				// 4 (116) Max of eight banks of vertex color wibble infortation.
+};
+
+
+sMaterial * LoadMaterials( void *p_FH, Lst::HashTable< Nx::CTexture > *p_texture_table, int * p_num_materials );
+sMaterial * LoadMaterialsFromMemory( void **pp_mem, Lst::HashTable< Nx::CTexture > *p_texture_table, int * p_num_materials );
+void InitializeMaterial( sMaterial* p_material );
+
+//extern Lst::HashTable< sMaterial > *pMaterialTable;
+extern uint32 NumMaterials;
+
+} // namespace NxNgc
+
+#endif // __MATERIAL_H
+
+
diff --git a/Code/Gfx/NGC/NX/mesh.cpp b/Code/Gfx/NGC/NX/mesh.cpp
new file mode 100644
index 0000000..c5da7f2
--- /dev/null
+++ b/Code/Gfx/NGC/NX/mesh.cpp
@@ -0,0 +1,1470 @@
+#include 
+#include 
+
+#include 
+#include 
+#include "import.h"
+#include "scene.h"
+#include "mesh.h"
+#include "nx_init.h"
+#include "sys/ngc/p_aram.h"
+#include "sys/ngc/p_dma.h"
+#include 
+
+#include 
+
+extern bool gCorrectColor;
+
+namespace NxNgc
+{
+
+extern sint32 *pVertex;
+int TotalNumVertices;
+
+uint NumMeshes;
+
+
+// Globals used for cutscene head scaling.
+bool			s_meshScalingEnabled	= false;
+char*			s_pWeightIndices		= NULL;
+float*			s_pWeights				= NULL;
+Mth::Vector*	s_pBonePositions		= NULL;
+Mth::Vector*	s_pBoneScales			= NULL;
+int				s_currentVertIndex		= 0;
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetMeshScalingParameters( Nx::SMeshScalingParameters* pParams )
+{
+	Dbg_Assert( pParams );
+
+	s_meshScalingEnabled	= true;
+	s_pWeights				= pParams->pWeights;
+	s_pWeightIndices		= pParams->pWeightIndices;
+	s_pBoneScales			= pParams->pBoneScales;
+	s_pBonePositions		= pParams->pBonePositions;
+	s_currentVertIndex		= 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void DisableMeshScaling( void )
+{
+	s_meshScalingEnabled	= false;
+	s_pWeights				= NULL;
+	s_pWeightIndices		= NULL;
+	s_pBoneScales			= NULL;
+	s_pBonePositions		= NULL;
+	s_currentVertIndex		= 0;
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline Mth::Vector get_bone_scale( int bone_index )
+{
+	Mth::Vector returnVec( 1.0f, 1.0f, 1.0f, 1.0f );
+
+	if( bone_index >= 29 && bone_index <= 33 )
+	{
+		// this only works with the thps5 skeleton, whose
+		// head bones are between 29 and 33...
+		// (eventually, we can remove the subtract 29
+		// once the exporter is massaging the data correctly)
+		returnVec = s_pBoneScales[ bone_index - 29 ];
+		
+		// Y & Z are reversed...  odd!
+		Mth::Vector tempVec = returnVec;
+		returnVec[Y] = tempVec[Z];
+		returnVec[Z] = tempVec[Y];
+	}
+	else if( bone_index == -1 )
+	{
+		// implies that it's not weighted to a bone
+		return returnVec;
+	}
+	else
+	{
+		// implies that it's weighted to the wrong bone
+		return returnVec;
+	}
+	return returnVec;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline Mth::Vector get_bone_pos( int bone_index )
+{
+	Mth::Vector returnVec( 0.0f, 0.0f, 0.0f, 1.0f );
+	
+	if( bone_index >= 29 && bone_index <= 33 )
+	{
+		// this only works with the thps5 skeleton, whose
+		// head bones are between 29 and 33...
+		// (eventually, we can remove the subtract 29
+		// once the exporter is massaging the data correctly)
+		returnVec = s_pBonePositions[ bone_index - 29 ];
+	}
+	else if( bone_index == -1 )
+	{
+		// implies that it's not weighted to a bone
+		return returnVec;
+	}
+	else
+	{
+		// implies that it's weighted to the wrong bone
+		return returnVec;
+	}
+	returnVec[W] = 1.0f;
+
+	return returnVec;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ApplyMeshScaling( NxNgc::sObjectHeader * p_object )
+{
+	if( s_meshScalingEnabled )
+	{
+		float orig_pool[3*1500];
+		int pool_size = 0;
+	
+		// First, build an array from the skin data.
+	
+		// Singles.
+		uint32*	p32 = (uint32 *)p_object->m_skin.p_data;
+		for ( uint32 lp = 0; lp < p_object->m_num_single_lists; lp++ ) {
+			uint32		pairs = *p32++;
+			/*uint32		mtx = **/p32++;
+			s16*		p_pn = (s16 *)p32;
+	
+			for ( uint32 lp2 = 0; lp2 < pairs; lp2++ )
+			{
+				orig_pool[(pool_size*3)+0] = (float)p_pn[(lp2*6)+0] / (float)(1 << 6);
+				orig_pool[(pool_size*3)+1] = (float)p_pn[(lp2*6)+1] / (float)(1 << 6);
+				orig_pool[(pool_size*3)+2] = (float)p_pn[(lp2*6)+2] / (float)(1 << 6);
+				pool_size++;
+			}
+	
+			p32 = (uint32 *)&p_pn[pairs*6];
+		}
+	
+		// Doubles.
+		for ( uint32 lp = 0; lp < p_object->m_num_double_lists; lp++ ) {
+			uint32		pairs = *p32++;
+			/*uint32		mtx = **/p32++;
+			s16*		p_pn = (s16 *)p32;
+			s16*		p_weight = (s16 *)&p_pn[(6*pairs)];
+	
+			for ( uint32 lp2 = 0; lp2 < pairs; lp2++ )
+			{
+				orig_pool[(pool_size*3)+0] = (float)p_pn[(lp2*6)+0] / (float)(1 << 6);
+				orig_pool[(pool_size*3)+1] = (float)p_pn[(lp2*6)+1] / (float)(1 << 6);
+				orig_pool[(pool_size*3)+2] = (float)p_pn[(lp2*6)+2] / (float)(1 << 6);
+				pool_size++;
+			}
+	
+			p32 = (uint32 *)&p_weight[pairs*2];
+		}
+	
+		// Now scale the verts.
+		float * p_vertices = orig_pool;
+		for( int v = 0; v < pool_size; ++v, p_vertices += 3 )
+		{
+			float x = p_vertices[0];
+			float y = p_vertices[1];
+			float z = p_vertices[2];
+
+    		Mth::Vector origPos( x, y, z, 1.0f );
+
+			Mth::Vector bonePos0 = get_bone_pos( s_pWeightIndices[v * 3] );
+			Mth::Vector bonePos1 = get_bone_pos( s_pWeightIndices[v * 3 + 1] );
+			Mth::Vector bonePos2 = get_bone_pos( s_pWeightIndices[v * 3 + 2] );
+
+			// Need to scale each vert relative to its parent bone.
+			Mth::Vector localPos0 = origPos - bonePos0;
+			Mth::Vector localPos1 = origPos - bonePos1;
+			Mth::Vector localPos2 = origPos - bonePos2;
+
+			localPos0.Scale( get_bone_scale( s_pWeightIndices[v * 3] ) );
+			localPos1.Scale( get_bone_scale( s_pWeightIndices[v * 3 + 1] ) );
+			localPos2.Scale( get_bone_scale( s_pWeightIndices[v * 3 + 2] ) );
+
+			localPos0 += bonePos0;
+			localPos1 += bonePos1;
+			localPos2 += bonePos2;
+			
+			Mth::Vector scaledPos = ( localPos0 * s_pWeights[v * 3] ) +
+									( localPos1 * s_pWeights[v * 3 + 1] ) +
+									( localPos2 * s_pWeights[v * 3 + 2] );
+
+			p_vertices[0] = scaledPos[X];
+			p_vertices[1] = scaledPos[Y];
+			p_vertices[2] = scaledPos[Z];
+		} 	
+
+		// Now copy back.
+		pool_size = 0;
+	
+		// Singles.
+		p32 = (uint32 *)p_object->m_skin.p_data;
+		for ( uint32 lp = 0; lp < p_object->m_num_single_lists; lp++ ) {
+			uint32		pairs = *p32++;
+			/*uint32		mtx = **/p32++;
+			s16*		p_pn = (s16 *)p32;
+	
+			for ( uint32 lp2 = 0; lp2 < pairs; lp2++ )
+			{
+				p_pn[(lp2*6)+0] = (s16)(orig_pool[(pool_size*3)+0] * (float)(1 << 6 ));
+				p_pn[(lp2*6)+1] = (s16)(orig_pool[(pool_size*3)+1] * (float)(1 << 6 ));
+				p_pn[(lp2*6)+2] = (s16)(orig_pool[(pool_size*3)+2] * (float)(1 << 6 ));
+				pool_size++;
+			}
+	
+			p32 = (uint32 *)&p_pn[pairs*6];
+		}
+	
+		// Doubles.
+		for ( uint32 lp = 0; lp < p_object->m_num_double_lists; lp++ ) {
+			uint32		pairs = *p32++;
+			/*uint32		mtx = **/p32++;
+			s16*		p_pn = (s16 *)p32;
+			s16*		p_weight = (s16 *)&p_pn[(6*pairs)];
+	
+			for ( uint32 lp2 = 0; lp2 < pairs; lp2++ )
+			{
+				p_pn[(lp2*6)+0] = (s16)(orig_pool[(pool_size*3)+0] * (float)(1 << 6 ));
+				p_pn[(lp2*6)+1] = (s16)(orig_pool[(pool_size*3)+1] * (float)(1 << 6 ));
+				p_pn[(lp2*6)+2] = (s16)(orig_pool[(pool_size*3)+2] * (float)(1 << 6 ));
+				pool_size++;
+			}
+	
+			p32 = (uint32 *)&p_weight[pairs*2];
+		}
+	
+		// Accumulation.
+		for ( uint32 lp = 0; lp < p_object->m_num_add_lists; lp++ ) {
+			uint32		singles = *p32++;
+			/*uint32		mtx = **/p32++;
+			s16*		p_pn = (s16 *)p32;
+			s16*		p_weight = (s16 *)&p_pn[(6*singles)];
+			uint16*		p_index = (uint16 *)&p_weight[singles];
+	
+			for ( uint32 lp2 = 0; lp2 < singles; lp2++ )
+			{
+				int index = p_index[lp2];
+				p_pn[(lp2*6)+0] = (s16)(orig_pool[(index*3)+0] * (float)(1 << 6 ));
+				p_pn[(lp2*6)+1] = (s16)(orig_pool[(index*3)+1] * (float)(1 << 6 ));
+				p_pn[(lp2*6)+2] = (s16)(orig_pool[(index*3)+2] * (float)(1 << 6 ));
+			}
+	
+			p32 = (uint32 *)&p_index[singles];
+		}
+	}
+}
+
+
+
+
+
+
+
+
+sMesh::sMesh( void )
+{
+	m_flags				= 0;
+	SetActive( true );
+//	mp_vc_wibble_data	= NULL;
+//
+//	m_offset_x = 0.0f;
+//	m_offset_y = 0.0f;
+//	m_offset_z = 0.0f;
+
+	m_visibility_mask = 0xff;
+
+	mp_dl = NULL;
+
+	// It's the end of teh project - time for a hack!!!
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	if (p_skate_mod->m_requested_level == CRCD(0x9f2bafb7,"load_skateshop"))
+	{
+		m_base_color.r = 64;
+		m_base_color.g = 64;
+		m_base_color.b = 64;
+		m_base_color.a = 128;
+	}
+	else
+	{
+		m_base_color.r = 128;
+		m_base_color.g = 128;
+		m_base_color.b = 128;
+		m_base_color.a = 128;
+	}
+}
+
+
+
+sMesh::~sMesh( void )
+{
+	if( !( m_flags & MESH_FLAG_IS_INSTANCE ))
+	{
+		if( m_flags & MESH_FLAG_IS_CLONED )
+		{
+//			if ( m_localMeshIndex == 0 )
+//			{
+//				if( mp_posBuffer )
+//				{
+//					delete [] mp_posBuffer;
+//					mp_posBuffer = NULL;
+//				}
+//				if( mp_normBuffer )
+//				{
+//					delete [] mp_normBuffer;
+//					mp_normBuffer = NULL;
+//				}
+//				if( mp_colBuffer )
+//				{
+//					delete [] mp_colBuffer;
+//					mp_colBuffer = NULL;
+//				}
+//			}
+		}
+		else
+		{
+//			if ( mp_dl->mp_texture_dl )
+//			{
+//				delete [] mp_dl->mp_texture_dl;
+//			}
+
+//			if( mp_display_list	)
+//			{
+//				delete mp_display_list;
+//				mp_display_list = NULL;
+//			}
+
+//			if ( mp_dl && mp_dl->dl_owner )
+//			{
+//				if ( mp_dl->mp_material_dl )
+//				delete [] mp_dl->mp_material_dl;
+//			}
+
+//			// If we're the 0th mesh, we can delete the shared pools.
+//			if ( m_localMeshIndex == 0 )
+//			{
+//				if( mp_posBuffer )
+//				{
+//					delete [] mp_posBuffer;
+//					mp_posBuffer = NULL;
+//				}
+//				if( mp_normBuffer )
+//				{
+//					delete [] mp_normBuffer;
+//					mp_normBuffer = NULL;
+//				}
+//				if( mp_uvBuffer )
+//				{
+//					delete [] mp_uvBuffer;
+//					mp_uvBuffer = NULL;
+//				}
+//				if( mp_envBuffer )
+//				{
+//					delete [] mp_envBuffer;
+//					mp_envBuffer = NULL;
+//				}
+//				if( mp_colBuffer )
+//				{
+//					delete [] mp_colBuffer;
+//					mp_colBuffer = NULL;
+//				}
+//				if( mp_doubleWeight )
+//				{
+//					delete [] mp_doubleWeight;
+//					mp_doubleWeight = NULL;
+//				}
+//				if( mp_vc_wibble_data )
+//				{
+//					delete mp_vc_wibble_data;
+//					mp_vc_wibble_data = NULL;
+//				}
+//			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::wibble_normals( void )
+{
+//	// Angle in the range [-PI/16, PI/16], period is 1 second.
+//	float time = (float)Tmr::GetTime() * 0.0005f;
+//
+//	if( m_flags & MESH_FLAG_WATER_HACK )
+//	{
+//		BYTE		*p_byte;
+//		float		*p_normal;
+//		float		*p_pos;
+//		mp_vertex_buffer2->Lock( 0, 0, &p_byte, 0 );
+//		p_pos		= (float*)( p_byte + 0 );
+//		p_normal	= (float*)( p_byte + m_normal_offset );
+//
+//		for( uint32 i = 0; i < m_num_vertices; ++i )
+//		{
+//			float x				= p_pos[0] - m_sphere[0];
+//			float z				= p_pos[2] - m_sphere[2];
+//			
+//			float time_offset_x	= time + (( x / m_sphere_radius ) * 0.5f );
+//			float time_offset_z	= time + (( z / m_sphere_radius ) * 0.5f );
+//
+//			float angle_x		= ( Mth::PI * ( 1.0f / 64.0f ) * (float)fabs( sinf( time_offset_x * Mth::PI ))) - ( Mth::PI * ( 1.0f / 128.0f ));
+//			float angle_z		= ( Mth::PI * ( 1.0f / 64.0f ) * (float)fabs( sinf( time_offset_z * Mth::PI ))) - ( Mth::PI * ( 1.0f / 129.0f ));
+//			
+//			Mth::Vector	n( sinf( angle_x ), cosf(( angle_x + angle_z ) * 0.5f ), sinf( angle_z ));
+//			n.Normalize();
+//			
+//			p_normal[0]			= n[X];
+//			p_normal[1]			= n[Y];
+//			p_normal[2]			= n[Z];
+//			
+//			p_pos				= (float*)((BYTE*)p_pos + m_vertex_stride );
+//			p_normal			= (float*)((BYTE*)p_normal + m_vertex_stride );
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::wibble_vc( void )
+{
+//	if( mp_vc_wibble_data )
+//	{
+//		// Grab byte pointer to current 'write' vertex buffer.
+////		GXColor		*p_byte = (char *)mp_colBuffer;
+//		uint32		*p_color = mp_colBuffer;
+////		p_color = (GXColor*)( p_byte + m_diffuse_offset );
+//
+//		// Scan through each vertex, setting the new color.
+//		for( uint32 i = 0; i < m_num_vertex; ++i )
+//		{
+//			// An index of zero means no update for this vert.
+//			uint32 index	= mp_vc_wibble_data[i];
+//			if( ( index > 0 ) && ( m_flags & MESH_FLAG_VERTEX_COLOR_WIBBLE ) && mp_material->mp_wibble_vc_colors )
+//			{
+//				*p_color	= mp_material->mp_wibble_vc_colors[index - 1];
+//			}
+//			p_color++;
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::Submit( void )
+{
+//	wibble_vc();
+////	wibble_normals();
+//
+//	if ( mp_display_list )
+//	{
+//		GXCallDisplayList ( mp_display_list, m_display_list_size ); 
+//	}
+////	// Horribly inefficient, but will have to do for now until we get bucket rendering going.
+////	if( mp_material )
+////	{
+////		mp_material->Submit();
+////	}
+////	
+////	// Set pixel and vertex shader.
+////	D3DDevice_SetPixelShader( m_pixel_shader );
+////	D3DDevice_SetVertexShader( m_fvf_flags );
+////
+////	// Set the stream source.
+////	D3DDevice_SetStreamSource( 0, mp_vertex_buffer, m_vertex_stride );
+////
+////	// Submit.
+////	D3DDevice_DrawIndexedVertices( m_primitive_type, m_num_indices, mp_index_buffer );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMesh *sMesh::Clone( bool instance )
+{
+	sMesh *p_clone = new sMesh();
+
+	// Copy over basic details.
+	memcpy( p_clone, this, sizeof( sMesh ));
+	p_clone->m_flags &= ~(MESH_FLAG_CLONED_POS|MESH_FLAG_CLONED_COL);
+
+	if( instance )
+	{
+		p_clone->m_flags |= MESH_FLAG_IS_INSTANCE;
+	}
+	else
+	{
+		p_clone->m_flags |= MESH_FLAG_IS_CLONED;
+
+		// Clone the display list.
+		Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+		p_clone->mp_dl = (NxNgc::sDLHeader*)new char[sizeof(NxNgc::sDLHeader)+mp_dl->m_size];
+		Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+		memcpy( p_clone->mp_dl, mp_dl, sizeof(NxNgc::sDLHeader)+mp_dl->m_size );
+		p_clone->m_flags |= NxNgc::sMesh::MESH_FLAG_CLONED_DL;
+
+
+
+//		p_clone->m_flags |= MESH_FLAG_IS_INSTANCE;
+
+//		if ( m_localMeshIndex == 0 )
+//		{
+//			if ( mp_posBuffer )
+//			{
+//#ifdef SHORT_VERT
+//				p_clone->mp_posBuffer = new s16[3*m_num_vertex];
+//				memcpy( p_clone->mp_posBuffer, mp_posBuffer, 3 * m_num_vertex * sizeof( s16 ) );
+//				DCFlushRange( p_clone->mp_posBuffer, 3 * m_num_vertex * sizeof( s16 ) );
+//#else
+//				p_clone->mp_posBuffer = new float[3*m_num_vertex];
+//				memcpy( p_clone->mp_posBuffer, mp_posBuffer, 3 * m_num_vertex * sizeof( float ) );
+//				DCFlushRange( p_clone->mp_posBuffer, 3 * m_num_vertex * sizeof( float ) );
+//#endif		// SHORT_VERT 
+//			}
+//			if ( mp_normBuffer )
+//			{
+//				p_clone->mp_normBuffer = new s16[3*m_num_vertex];
+//				memcpy( p_clone->mp_normBuffer, mp_normBuffer, 3 * m_num_vertex * sizeof( s16 ) );
+//				DCFlushRange( p_clone->mp_normBuffer, 3 * m_num_vertex * sizeof( s16 ) );
+//			}
+//
+//			if ( mp_colBuffer )
+//			{
+//				p_clone->mp_colBuffer = new uint32[m_num_vertex];
+//				memcpy( p_clone->mp_colBuffer, mp_colBuffer, m_num_vertex * sizeof( uint32 ) );
+//				DCFlushRange( p_clone->mp_colBuffer, m_num_vertex * sizeof( uint32 ) );
+//			}
+//		}
+	}
+	return p_clone;
+
+//	Dbg_MsgAssert( false, ( "Not yet implmented!!!!!!!!" ) );
+//	return NULL;
+}
+
+
+
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void sMesh::Initialize( int num_vertices,
+//						float* p_positions,
+//#ifdef SHORT_VERT
+//						s16* p_positions16,
+//						int shift,
+//						float off_x,
+//						float off_y,
+//						float off_z,
+//#endif		// SHORT_VERT
+//						s16* p_normals,
+//						float* p_tex_coords,
+//						float* p_env_buffer,
+//						int num_tc_sets,
+//						uint32* p_colors,
+//						int num_indices,
+//						uint16* p_pos_indices,
+//						uint16* p_col_indices,
+//						uint16* p_uv_indices,
+//						unsigned long material_checksum,
+//						void *p_scene,
+//						int num_double,
+//						uint32 * p_double,
+//						int num_single,
+//						int localMeshIndex,
+//						GXPrimitive primitive_type,
+//						int bone_idx,
+//						char *p_vc_wibble_anims )
+//
+//{
+//	m_num_indices		= 0;
+//	m_vertex_stride		= 0;
+//	m_num_vertex		= num_vertices;
+//
+//	m_bone_idx			= bone_idx;
+//
+////	m_primitive_type	= D3DPT_TRIANGLESTRIP;
+////	m_fvf_flags			= 0;
+////	m_pixel_shader		= 0;
+//	
+////	mp_posBuffer = NULL;	//p_positions;
+////	mp_normBuffer = p_normals;
+////	mp_uvBuffer = NULL;	//p_tex_coords;
+////	mp_colBuffer = NULL;	//p_colors;
+////	mp_index_buffer = NULL;	//p_indices;
+//
+//	mp_normBuffer = p_normals;
+//	mp_uvBuffer = p_tex_coords;
+//	mp_colBuffer = p_colors;
+//	m_num_uv_sets = num_tc_sets;
+//
+//	m_numDouble = num_double;
+//	m_numSingle = num_single;
+//	mp_doubleWeight = p_double;
+//
+//	m_localMeshIndex = localMeshIndex;
+//
+//	// First thing to do is grab the material pointer for this mesh.
+//	mp_material			= ((sScene*)p_scene )->GetMaterial( material_checksum );
+////	if( mp_material == NULL )
+////	{
+////		exit( 0 );
+////
+////		// Try with the dummy material for now.
+////		// mp_material = GetMaterial( 0 );
+////	}
+//	
+////	if( num_indices == 0 )
+////	{
+////		return;
+////	}
+////	
+////	// Figure the min and max indices for this mesh.
+////	uint16 min_index	= p_indices[0];
+////	uint16 max_index	= p_indices[0];
+////	for( int i = 1; i < num_indices; ++i )
+////	{
+////		if( p_indices[i] > max_index )
+////		{
+////			max_index = p_indices[i];
+////		}
+////		else if( p_indices[i] < min_index )
+////		{
+////			min_index = p_indices[i];
+////		}
+////	}
+//	
+//	// See if any vertex color alpha valus are less than any of the texture's cutoff values.
+//	// If so, make sure that the texture is specified as having holes.
+//	for ( int lp = 0; lp < num_vertices; lp++ )
+//	{
+//		if ( ( p_colors[lp] & 255 ) < mp_material->AlphaCutoff )
+//		{
+//			for ( int pass = 0; pass < mp_material->Passes; pass++ )
+//			{
+////				if ( mp_material->pTex[pass] ) mp_material->pTex[pass]->HasHoles = 1;
+//				if ( mp_material->pTex[pass] )
+//				{
+//					if ( !mp_material->pTex[pass]->HasHoles )
+//					{
+//						mp_material->pTex[pass]->HasHoles = 1;
+//						OSReport( "Material found that was incorrectly flagged.\n" );
+//					}
+//				}
+//			}
+//			break;
+//		}
+//	}
+//
+//	// See if any of the material layers require environment mapping.
+//	mp_envBuffer = NULL;
+//	for ( int pass = 0; pass < mp_material->Passes; pass++ )
+//	{
+//		if ( mp_material->Flags[pass] & MATFLAG_ENVIRONMENT )
+//		{
+//			if ( p_env_buffer )
+//			{
+//				// Already allocated one, so just assign it.
+//				mp_envBuffer = p_env_buffer;
+//			}
+//			else
+//			{
+//				// Has environment mapping, so allocate an environment map uv buffer.
+//				mp_envBuffer = new float[2*num_vertices];
+//			}
+//			// Break so that we only allocate once.
+//			break;
+//		}
+//	}
+//
+//	// Set the bounding box.
+//	if ( p_positions )
+//	{
+//		// Non-skinned.
+//		for( int v = 0; v < num_vertices; ++v )
+//		{
+//			// Do bounding box setting.
+//			Mth::Vector p( p_positions[(v*3)+0], p_positions[(v*3)+1], p_positions[(v*3)+2] );
+//			m_bbox.AddPoint( p );
+//		}
+//	}
+//	else
+//	{
+//		// Skinned.
+//		uint32*	p32 = p_double;
+//		for ( int lp = 0; lp < num_double; lp++ ) {
+//			uint32		pairs = *p32++;
+//			/*uint32		mtx = **/p32++;
+//			float*		p_pn = (float *)p32;
+//			float*			p_weight = (float *)&p_pn[(6*pairs)];
+//
+//			for( uint v = 0; v < pairs; ++v )
+//			{
+//				// Do bounding box setting.
+//				Mth::Vector p( p_pn[(v*6)+0], p_pn[(v*6)+1], p_pn[(v*6)+2] );
+//				m_bbox.AddPoint( p );
+//			}
+//
+//			p32 = (uint32 *)&p_weight[pairs*2];
+//		}
+//	}
+//	float x = ( m_bbox.GetMax().GetX() + m_bbox.GetMin().GetX() ) / 2;
+//	float y = ( m_bbox.GetMax().GetY() + m_bbox.GetMin().GetY() ) / 2;
+//	float z = ( m_bbox.GetMax().GetZ() + m_bbox.GetMin().GetZ() ) / 2;
+//	float r = ( m_bbox.GetMax().GetX() - m_bbox.GetMin().GetX() ) * ( m_bbox.GetMax().GetX() - m_bbox.GetMin().GetX() );
+//	r += ( m_bbox.GetMax().GetY() - m_bbox.GetMin().GetY() ) * ( m_bbox.GetMax().GetY() - m_bbox.GetMin().GetY() );
+//	r += ( m_bbox.GetMax().GetZ() - m_bbox.GetMin().GetZ() ) * ( m_bbox.GetMax().GetZ() - m_bbox.GetMin().GetZ() );
+//	r = sqrtf( r );
+//	r = r / 2.0f;
+//	m_sphere[0] = x;
+//	m_sphere[1] = y;
+//	m_sphere[2] = z;
+//	m_sphere[3] = r;
+//
+//	// Set the position buffer & offset.
+//#ifdef SHORT_VERT
+//	mp_posBuffer = p_positions16;
+//#else
+//	mp_posBuffer = p_positions;
+//#endif		// SHORT_VERT
+//
+//	if ( p_positions )
+//	{
+//#ifdef SHORT_VERT
+//		// Non-skinned.
+//		switch ( shift )
+//		{
+//			case 1:
+//				m_vertex_format = GX_VTXFMT2;
+//				break;
+//			case 2:
+//				m_vertex_format = GX_VTXFMT3;
+//				break;
+//			case 3:
+//				m_vertex_format = GX_VTXFMT4;
+//				break;
+//			case 4:
+//				m_vertex_format = GX_VTXFMT5;
+//				break;
+//			case 5:
+//				m_vertex_format = GX_VTXFMT6;
+//				break;
+//			case 6:
+//				m_vertex_format = GX_VTXFMT7;
+//				break;
+//			case 0:
+//			default:
+//				m_vertex_format = GX_VTXFMT1;
+//				break;
+//		}
+//#else
+//		m_vertex_format = GX_VTXFMT6;
+//#endif		// SHORT_VERT
+//	}
+//	else
+//	{
+//		// Skinned.
+//		m_vertex_format = GX_VTXFMT7;
+//	}
+//
+//#ifdef SHORT_VERT
+//	m_offset_x = off_x;
+//	m_offset_y = off_y;
+//	m_offset_z = off_z;
+//#endif		// SHORT_VERT
+//
+////	if( max_index >= num_vertices )
+////	{
+////		// Error!
+////		exit( 0 );
+////	}
+//	
+//	// Now figure the total number of vertices required for this mesh, to span the min->max indices.
+////	uint16 vertices_for_this_mesh = max_index - min_index + 1;
+//
+//	// Create the index buffer (should be 16byte aligned for best performance).
+////	mp_index_buffer	= new uint16[num_indices];
+//
+//	// Set our count of the number of indices.
+//	m_num_indices = num_indices - 2;		// Only used for skinned - subtract count & trailing 0.
+//
+//	// Copy in index data, normaling the indices for this vertex buffer (i.e. so the lowest index will reference
+//	// vertex 0 in the buffer built specifically for this mesh).
+////	for( int i = 0; i < num_indices; ++i )
+////	{
+////		mp_index_buffer[i] = p_indices[i];	// - min_index;
+////	}
+//
+//	// Use the material flags to figure the vertex format.
+//	int vertex_size			= 3 * sizeof( float );
+//
+//	// Include weights (for weighted animation) if present.
+////	if( p_weights )
+////	{
+////		Dbg_Assert( p_matrix_indices );
+////		vertex_size	+= sizeof( float ) * 4;
+////	}
+//
+//	// Include indices (for weighted animation) if present.
+////	if( p_matrix_indices )
+////	{
+////		Dbg_Assert( p_weights );
+////		vertex_size	+= sizeof( uint16 ) * 4;
+////	}
+//	
+//	// Texture coordinates.
+//	uint32	tex_coord_pass	= 0;
+////	bool	env_mapped		= false;
+//	if( p_tex_coords )
+//	{
+//		for( uint32 pass = 0; pass < mp_material->Passes; ++pass )
+//		{
+////			if( mp_material->Flags[pass] & MATFLAG_ENVIRONMENT )
+////			{
+////				env_mapped		= true;
+////			}
+//
+//			// Only need UV's for this stage if it is *not* environment mapped.
+//			if(( mp_material->pTex[pass] ) /*&& ( !( mp_material->Flags[pass] & MATFLAG_ENVIRONMENT ))*/)
+//			{
+//				// Will need uv for this pass and all before it.
+//				tex_coord_pass	= pass + 1; 
+//			}
+//		}
+//	}
+//	else
+//	{
+//		for( uint32 pass = 0; pass < mp_material->Passes; ++pass )
+//		{
+////			if( mp_material->Flags[pass] & MATFLAG_ENVIRONMENT )
+////			{
+////				env_mapped		= true;
+////			}
+//		}
+//	}
+//
+//	if( tex_coord_pass > 0 )
+//	{
+//		vertex_size += 2 * sizeof( float ) * tex_coord_pass;
+//	}
+//
+//	// Assume no normals for now.
+//	if( 0 )
+//	{
+//		// We want vertex colors in this mesh,
+//		if( 0 )
+//		{
+//			// The raw vertex data does not contain vertex colors.
+//			exit( 0 );
+//		}
+//		else
+//		{
+//			// The raw vertex data does contain vertex colors.
+//		}
+//	}
+//
+//	bool use_colors = false;
+//	if( p_colors )
+//	{
+//		// We want vertex colors in this mesh,
+//		if( 0 )
+//		{
+//			// The raw vertex data does not contain vertex colors.
+//			exit( 0 );
+//		}
+//		else
+//		{
+//			// The raw vertex data does contain vertex colors.
+//			vertex_size	+= sizeof( uint32 );
+//			use_colors	= true;
+//		}
+//	}
+//	
+//	// Create the vertex color wibble array if data is present.
+//	if( p_vc_wibble_anims )
+//	{
+//		mp_vc_wibble_data = p_vc_wibble_anims;
+//	}
+//
+////	if ( p_double )
+////	{
+////		mp_doubleWeight = 1;
+////	}
+////	else
+////	{
+////		mp_doubleWeight = 0;
+////	}
+//
+//	m_primitive_type = primitive_type;
+//
+//	Build( num_indices, p_pos_indices, p_col_indices, p_uv_indices );
+//
+//	// Find the index list in the display list data.
+//	mp_index_buffer = NULL;
+//	{
+//		unsigned char * p8;
+//		unsigned char search[3];
+//
+//		p8 = (unsigned char *)mp_display_list;
+//
+//		search[0] = *p8++;
+//		search[1] = *p8++;
+//		for ( int ss = 2; ss < m_display_list_size; ss++ ) {
+//			search[2] = *p8++;
+//
+//			if (	( ( search[0] & 0xf8 ) == m_primitive_type ) &&
+//					( ( ( search[1] << 8 ) | search[2] ) == (int)*p_pos_indices ) ) {
+//				// Found it!!!
+//				if ( num_double )
+//				{
+//					// Skinned mesh.
+//					mp_index_buffer = (unsigned short *)p8;
+//				}
+//				else
+//				{
+//					// Non-skinned mesh.
+//					mp_index_buffer = (unsigned short *)&p8[-2];
+//				}
+//				break;
+//			}
+//			search[0] = search[1];
+//			search[1] = search[2];
+//		}
+//	}
+//
+////	if ( p_double )
+////	{
+////		double_bytes = dbytes;
+////		single_bytes = sbytes;
+////		
+////		if ( m_localMeshIndex == 0 )
+////		{
+////			NsARAM::HEAPTYPE heap = NsARAM::BOTTOMUP;
+////
+////			if ( Mem::Manager::sHandle().GetContextAllocator() == Mem::Manager::sHandle().SkaterGeomHeap(0) )
+////			{
+////				heap = NsARAM::SKATER0;
+////			}
+////			if ( Mem::Manager::sHandle().GetContextAllocator() == Mem::Manager::sHandle().SkaterGeomHeap(1) )
+////			{
+////				heap = NsARAM::SKATER1;
+////			}
+////
+////			//OSReport( "Allocating %d ARAM bytes %s\n", dbytes + sbytes, Mem::Manager::sHandle().GetContextName() );
+////			mp_doubleWeight = NsARAM::alloc( (dbytes + sbytes) * 4, heap );
+////			NsDMA::toARAM( mp_doubleWeight, p_double, ( dbytes + sbytes ) * 4 );
+////		}
+////	}
+////	else
+////	{
+////		mp_doubleWeight = 0;
+////		double_bytes = dbytes;
+////		single_bytes = sbytes;
+////	}
+//
+//	delete p_pos_indices;
+//}
+
+//void sMesh::Build( int num_indices, uint16* p_pos_indices, uint16* p_col_indices, uint16* p_uv_indices, bool rebuild )
+//{
+//	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+//	EngineGlobals.gpuBusy = true;
+//	// Turn the index list into a display list.
+//	mp_display_list = NULL;
+//#define DL_BUILD_SIZE (32*1024)
+//	uint8 * p_build_dl = new (Mem::Manager::sHandle().TopDownHeap()) uint8[DL_BUILD_SIZE];
+//	DCFlushRange ( p_build_dl, DL_BUILD_SIZE );
+//
+//	GXBeginDisplayList ( p_build_dl, DL_BUILD_SIZE );
+//	GXResetWriteGatherPipe();
+//
+//	GXClearVtxDesc();
+//	GXSetVtxDesc( GX_VA_POS, GX_INDEX16 );
+//	m_vertex_stride = 1;
+//	if ( mp_normBuffer || mp_doubleWeight )
+//	{
+//		GXSetVtxDesc( GX_VA_NRM, GX_INDEX16 );
+//		m_vertex_stride++;
+//	}
+//	if ( mp_colBuffer )
+//	{
+//		GXSetVtxDesc( GX_VA_CLR0, GX_INDEX16 );
+//		m_vertex_stride++;
+//		if ( !mp_doubleWeight && gCorrectColor )
+//		{
+//			// Before we go wading in and allow a 2nd color index list, see if it's necessary.
+//			// Check every poly in the mesh and see if the color difference is greater than a threshold.
+//			bool needs_fixing = false;
+//			uint32 color0;
+//			uint32 color1 = mp_colBuffer[p_col_indices[p_pos_indices[0]]];
+//			uint32 color2 = mp_colBuffer[p_col_indices[p_pos_indices[1]]];
+//			for ( int lp = 2; lp < num_indices; lp++ )
+//			{
+//				// Grab new color to form triangle.
+//				color0 = color1;
+//				color1 = color2;
+//				color2 = mp_colBuffer[p_col_indices[p_pos_indices[lp]]];
+//
+//				int diff0_a = abs( ( ( color1 >> 24 ) & 255 ) - ( ( color0 >> 24 ) & 255 ) );
+//				int diff1_a = abs( ( ( color2 >> 24 ) & 255 ) - ( ( color1 >> 24 ) & 255 ) );
+//				int diff2_a = abs( ( ( color0 >> 24 ) & 255 ) - ( ( color2 >> 24 ) & 255 ) );
+//				int diff0_r = abs( ( ( color1 >> 16 ) & 255 ) - ( ( color0 >> 16 ) & 255 ) );
+//				int diff1_r = abs( ( ( color2 >> 16 ) & 255 ) - ( ( color1 >> 16 ) & 255 ) );
+//				int diff2_r = abs( ( ( color0 >> 16 ) & 255 ) - ( ( color2 >> 16 ) & 255 ) );
+//				int diff0_g = abs( ( ( color1 >>  8 ) & 255 ) - ( ( color0 >>  8 ) & 255 ) );
+//				int diff1_g = abs( ( ( color2 >>  8 ) & 255 ) - ( ( color1 >>  8 ) & 255 ) );
+//				int diff2_g = abs( ( ( color0 >>  8 ) & 255 ) - ( ( color2 >>  8 ) & 255 ) );
+//				int diff0_b = abs( ( ( color1 >>  0 ) & 255 ) - ( ( color0 >>  0 ) & 255 ) );
+//				int diff1_b = abs( ( ( color2 >>  0 ) & 255 ) - ( ( color1 >>  0 ) & 255 ) );
+//				int diff2_b = abs( ( ( color0 >>  0 ) & 255 ) - ( ( color2 >>  0 ) & 255 ) );
+//
+//				int wdiff = diff0_a;
+//				if ( diff1_a > wdiff ) wdiff = diff1_a;
+//				if ( diff2_a > wdiff ) wdiff = diff2_a;
+//				if ( diff0_r > wdiff ) wdiff = diff0_r;
+//				if ( diff1_r > wdiff ) wdiff = diff1_r;
+//				if ( diff2_r > wdiff ) wdiff = diff2_r;
+//				if ( diff0_g > wdiff ) wdiff = diff0_g;
+//				if ( diff1_g > wdiff ) wdiff = diff1_g;
+//				if ( diff2_g > wdiff ) wdiff = diff2_g;
+//				if ( diff0_b > wdiff ) wdiff = diff0_b;
+//				if ( diff1_b > wdiff ) wdiff = diff1_b;
+//				if ( diff2_b > wdiff ) wdiff = diff2_b;
+//
+//				if ( wdiff > 8 ) needs_fixing = true;
+//			}
+//
+//			if ( needs_fixing )
+//			{
+//				m_flags |= MESH_FLAG_DOUBLE_COLOR_IDX;
+//				GXSetVtxDesc( GX_VA_CLR1, GX_INDEX16 );
+//				m_vertex_stride++;
+//			}
+//		}
+//	}
+//
+//	int coord = 0;
+//	for ( uint layer = 0; layer < mp_material->Passes; layer++ )
+//	{
+//		GXSetVtxDesc( (GXAttr)(((int)GX_VA_TEX0)+coord), GX_INDEX16 );
+//		m_vertex_stride++;
+//		coord++;
+//	}
+//
+//	if ( rebuild )
+//	{
+//		// Send coordinates.
+//		GXBegin( (GXPrimitive)m_primitive_type, (GXVtxFmt)m_vertex_format, num_indices ); 
+//		for ( int lp = 0; lp < num_indices; lp++ )
+//		{
+//			// Send coordinates.
+//			int pindex = p_pos_indices[lp];
+//			int cindex = p_col_indices[pindex];
+//			GXPosition1x16(pindex);
+//			if ( mp_normBuffer || mp_doubleWeight ) GXNormal1x16(pindex);
+//			if ( mp_colBuffer ) GXColor1x16(cindex);
+//			if ( m_flags & MESH_FLAG_DOUBLE_COLOR_IDX ) GXColor1x16(cindex);
+//			for ( uint layer = 0; layer < mp_material->Passes; layer++ )
+//			{
+//				int uindex = pindex;
+//				if ( p_uv_indices ) uindex = p_uv_indices[pindex+(layer*m_num_vertex)];
+//				GXTexCoord1x16(uindex);
+//			}
+//		}
+//		GXEnd();
+//	}
+//	else
+//	{
+//		// Parse index list & turn into gxbegin/end pairs.
+//		int v = 0;
+//		while ( p_pos_indices[v] )
+//		{
+//			uint16 num = p_pos_indices[v];
+//			v++;
+//
+//			GXBegin( (GXPrimitive)m_primitive_type, (GXVtxFmt)m_vertex_format, num ); 
+//
+//			for ( int vv = 0; vv < num; vv++ )
+//			{
+//				// Send coordinates.
+//				int pindex = p_pos_indices[v+vv];
+//				int cindex = p_col_indices[pindex];
+//				GXPosition1x16(pindex);
+//				if ( mp_normBuffer || mp_doubleWeight ) GXNormal1x16(pindex);
+//				if ( mp_colBuffer ) GXColor1x16(cindex);
+//				if ( m_flags & MESH_FLAG_DOUBLE_COLOR_IDX ) GXColor1x16(cindex);
+//				for ( uint layer = 0; layer < mp_material->Passes; layer++ )
+//				{
+//					int uindex = pindex;
+//					if ( p_uv_indices ) uindex = p_uv_indices[pindex+(layer*m_num_vertex)];
+//					GXTexCoord1x16(uindex);
+//				}
+//			}
+//			v += num;
+//			GXEnd();
+//		}			
+//	}
+//
+//	int size = GXEndDisplayList();
+//	
+//	mp_index_buffer = NULL;
+//
+//	uint8 * p_dl = new uint8[size];
+//	memcpy ( p_dl, p_build_dl, size );
+//	DCFlushRange ( p_dl, size );
+//
+//	delete p_build_dl;
+//
+//	mp_display_list = p_dl;
+//	m_display_list_size = size;
+//	EngineGlobals.gpuBusy = false;
+//
+//	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+//}
+//
+//void sMesh::Rebuild( int num_indices, uint16* p_indices )
+//{
+//	uint16 * p_temp_col_remap = NULL;
+//	uint16 * p_temp_uv_remap = NULL;
+//	p_temp_uv_remap = m_num_uv_sets ? new (Mem::Manager::sHandle().TopDownHeap()) uint16[m_num_vertex*m_num_uv_sets] : NULL;
+//	p_temp_col_remap = mp_colBuffer ? new (Mem::Manager::sHandle().TopDownHeap()) uint16[m_num_vertex] : NULL;
+//
+//	for ( int c = 0; c < m_num_vertex; c++ )
+//	{
+//		for ( int u = 0; u < m_num_uv_sets; u++ )
+//		{
+//			p_temp_uv_remap[(u*m_num_vertex)+c] = (u*m_num_vertex)+c;
+//		}
+//	}
+//
+//	for ( int c = 0; c < m_num_vertex; c++ )
+//	{
+//		p_temp_col_remap[c] = c;
+//	}
+//
+//	delete [] mp_display_list;
+//
+////	Build( num_indices, p_indices, p_indices, p_indices, true );
+//	Build( num_indices, p_indices, p_temp_col_remap, p_temp_uv_remap, true );
+//	// Update to new number of indices.
+//	m_num_indices = num_indices;
+//
+//	delete [] p_temp_col_remap;
+//	delete [] p_temp_uv_remap;
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::SetPosition( Mth::Vector &pos )
+{
+
+
+//	// Figure what we need to add to each vertex, based on current position.
+//	Mth::Vector offset(	pos[X] - m_offset_x,
+//						pos[Y] - m_offset_y,
+//						pos[Z] - m_offset_z );
+//	m_offset_x = pos[X];
+//	m_offset_y = pos[Y];
+//	m_offset_z = pos[Z];
+//
+//	// We also need to adjust the bounding box and sphere information for this mesh.
+//	m_sphere[0] += offset[X];
+//	m_sphere[1] += offset[Y];
+//	m_sphere[2] += offset[Z];
+//
+//	m_bbox.SetMin( m_bbox.GetMin() + offset );
+//	m_bbox.SetMax( m_bbox.GetMax() + offset );
+//
+//#ifndef SHORT_VERT
+//	if ( m_localMeshIndex != 0 ) return;
+//
+//	for( uint32 bv = 0; bv < m_num_vertex; ++bv )
+//	{
+//		mp_posBuffer[(bv*3)+0] += offset[X];
+//		mp_posBuffer[(bv*3)+1] += offset[Y];
+//		mp_posBuffer[(bv*3)+2] += offset[Z];
+//	}
+//	DCFlushRange( mp_posBuffer, 3 * sizeof( float ) * m_num_vertex );
+//#endif		// SHORT_VERT
+//
+}
+	
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::GetPosition( Mth::Vector *p_pos )
+{
+//	p_pos->Set( m_offset_x, m_offset_y, m_offset_z );
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::SetYRotation( Mth::ERot90 rot )
+{
+//	if( ( rot > Mth::ROT_0 ) && ( m_localMeshIndex == 0 ) )
+//	{
+//		s16 nx, nz;
+//#ifdef SHORT_VERT
+//		s16 xo = 0;
+//		s16 zo = 0;
+//		s16 x, z;
+//#else
+//		float x, z;
+//		float xo = m_offset_x;
+//		float zo = m_offset_z;
+//#endif		// SHORT_VERT 
+//		switch( rot )
+//		{
+//			case Mth::ROT_90:
+//			{
+//				if ( m_localMeshIndex == 0 )
+//				{
+//					for( uint32 v = 0; v < m_num_vertex; ++v )
+//					{
+//						x = mp_posBuffer[(v*3)+0] - xo;
+//						z = mp_posBuffer[(v*3)+2] - zo;
+//						mp_posBuffer[(v*3)+0] = z + xo;
+//						mp_posBuffer[(v*3)+2] = -x + zo;
+//						if ( mp_normBuffer )
+//						{
+//							nx = mp_normBuffer[(v*3)+0];
+//							nz = mp_normBuffer[(v*3)+2];
+//							mp_normBuffer[(v*3)+0] = nz;
+//							mp_normBuffer[(v*3)+2] = -nx;
+//						}
+//					}
+//				}
+//				// Adjust the bounding sphere information for this mesh.
+//				m_sphere[0]	-= m_offset_x;
+//				m_sphere[2]	-= m_offset_z;
+//				float t				= m_sphere[0];
+//				m_sphere[0]	= m_sphere[2] + m_offset_x;
+//				m_sphere[2]	= -t + m_offset_z;
+//
+//				// Adjust the bounding box information for this mesh.
+//				Mth::Vector m[4];
+//
+//				m[0].Set( m_bbox.GetMin()[X], 0.0f, m_bbox.GetMin()[Z] );
+//				m[1].Set( m_bbox.GetMin()[X], 0.0f, m_bbox.GetMax()[Z] );
+//				m[2].Set( m_bbox.GetMax()[X], 0.0f, m_bbox.GetMin()[Z] );
+//				m[3].Set( m_bbox.GetMax()[X], 0.0f, m_bbox.GetMax()[Z] );
+//
+//				for( int i = 0; i < 4; ++i )
+//				{
+//					m[i][X] -= m_offset_x;
+//					m[i][Z] -= m_offset_z;
+//					float t = m[i][X];
+//					m[i][X] = m[i][Z];
+//					m[i][Z] = -t;
+//					m[i][X] += m_offset_x;
+//					m[i][Z] += m_offset_z;
+//				}
+//
+//				Mth::Vector mmin( m[0][X], m_bbox.GetMin()[Y], m[0][Z] );
+//				Mth::Vector mmax( m[0][X], m_bbox.GetMax()[Y], m[0][Z] );
+//				for( int i = 1; i < 4; ++i )
+//				{
+//					if( m[i][X] < mmin[X] )
+//						mmin[X] = m[i][X];
+//					if( m[i][X] > mmax[X] )
+//						mmax[X] = m[i][X];
+//
+//					if( m[i][Z] < mmin[Z] )
+//						mmin[Z] = m[i][Z];
+//					if( m[i][Z] > mmax[Z] )
+//						mmax[Z] = m[i][Z];
+//				}
+//
+//				m_bbox.SetMin( mmin );
+//				m_bbox.SetMax( mmax );
+//				break;
+//			}
+//			case Mth::ROT_180:
+//			{
+//				if ( m_localMeshIndex == 0 )
+//				{
+//					for( uint32 v = 0; v < m_num_vertex; ++v )
+//					{
+//						x = mp_posBuffer[(v*3)+0] - xo;
+//						z = mp_posBuffer[(v*3)+2] - zo;
+//						mp_posBuffer[(v*3)+0] = -x + xo;
+//						mp_posBuffer[(v*3)+2] = -z + zo;
+//						if ( mp_normBuffer )
+//						{
+//							nx = mp_normBuffer[(v*3)+0];
+//							nz = mp_normBuffer[(v*3)+2];
+//							mp_normBuffer[(v*3)+0] = -nx;
+//							mp_normBuffer[(v*3)+2] = -nz;
+//						}
+//					}
+//				}
+//				// Adjust the bounding sphere information for this mesh.
+//				m_sphere[0]	-= m_offset_x;
+//				m_sphere[2]	-= m_offset_z;
+//				m_sphere[0]	= -m_sphere[0] + m_offset_x;
+//				m_sphere[2]	= -m_sphere[2] + m_offset_z;
+//
+//				// Adjust the bounding box information for this mesh.
+//				Mth::Vector m[4];
+//
+//				m[0].Set( m_bbox.GetMin()[X], 0.0f, m_bbox.GetMin()[Z] );
+//				m[1].Set( m_bbox.GetMin()[X], 0.0f, m_bbox.GetMax()[Z] );
+//				m[2].Set( m_bbox.GetMax()[X], 0.0f, m_bbox.GetMin()[Z] );
+//				m[3].Set( m_bbox.GetMax()[X], 0.0f, m_bbox.GetMax()[Z] );
+//
+//				for( int i = 0; i < 4; ++i )
+//				{
+//					m[i][X] -= m_offset_x;
+//					m[i][Z] -= m_offset_z;
+//					m[i][X] = -m[i][X];
+//					m[i][Z] = -m[i][Z];
+//					m[i][X] += m_offset_x;
+//					m[i][Z] += m_offset_z;
+//				}
+//
+//				Mth::Vector mmin( m[0][X], m_bbox.GetMin()[Y], m[0][Z] );
+//				Mth::Vector mmax( m[0][X], m_bbox.GetMax()[Y], m[0][Z] );
+//				for( int i = 1; i < 4; ++i )
+//				{
+//					if( m[i][X] < mmin[X] )
+//						mmin[X] = m[i][X];
+//					if( m[i][X] > mmax[X] )
+//						mmax[X] = m[i][X];
+//
+//					if( m[i][Z] < mmin[Z] )
+//						mmin[Z] = m[i][Z];
+//					if( m[i][Z] > mmax[Z] )
+//						mmax[Z] = m[i][Z];
+//				}
+//
+//				m_bbox.SetMin( mmin );
+//				m_bbox.SetMax( mmax );
+//				break;
+//			}
+//			case Mth::ROT_270:
+//			{
+//				if ( m_localMeshIndex == 0 )
+//				{
+//					for( uint32 v = 0; v < m_num_vertex; ++v )
+//					{
+//						x = mp_posBuffer[(v*3)+0] - xo;
+//						z = mp_posBuffer[(v*3)+2] - zo;
+//						mp_posBuffer[(v*3)+0] = -z + xo;
+//						mp_posBuffer[(v*3)+2] = x + zo;
+//						if ( mp_normBuffer )
+//						{
+//							nx = mp_normBuffer[(v*3)+0];
+//							nz = mp_normBuffer[(v*3)+2];
+//							mp_normBuffer[(v*3)+0] = -nz;
+//							mp_normBuffer[(v*3)+2] = nx;
+//						}
+//					}
+//				}
+//				// Adjust the bounding sphere information for this mesh.
+//				m_sphere[0]	-= m_offset_x;
+//				m_sphere[2]	-= m_offset_z;
+//				float t				= m_sphere[0];
+//				m_sphere[0]	= -m_sphere[2] + m_offset_x;
+//				m_sphere[2]	= t + m_offset_z;
+//
+//				// Adjust the bounding box information for this mesh.
+//				Mth::Vector m[4];
+//
+//				m[0].Set( m_bbox.GetMin()[X], 0.0f, m_bbox.GetMin()[Z] );
+//				m[1].Set( m_bbox.GetMin()[X], 0.0f, m_bbox.GetMax()[Z] );
+//				m[2].Set( m_bbox.GetMax()[X], 0.0f, m_bbox.GetMin()[Z] );
+//				m[3].Set( m_bbox.GetMax()[X], 0.0f, m_bbox.GetMax()[Z] );
+//
+//				for( int i = 0; i < 4; ++i )
+//				{
+//					m[i][X] -= m_offset_x;
+//					m[i][Z] -= m_offset_z;
+//					float t = m[i][X];
+//					m[i][X] = -m[i][Z];
+//					m[i][Z] = t;
+//					m[i][X] += m_offset_x;
+//					m[i][Z] += m_offset_z;
+//				}
+//
+//				Mth::Vector mmin( m[0][X], m_bbox.GetMin()[Y], m[0][Z] );
+//				Mth::Vector mmax( m[0][X], m_bbox.GetMax()[Y], m[0][Z] );
+//				for( int i = 1; i < 4; ++i )
+//				{
+//					if( m[i][X] < mmin[X] )
+//						mmin[X] = m[i][X];
+//					if( m[i][X] > mmax[X] )
+//						mmax[X] = m[i][X];
+//
+//					if( m[i][Z] < mmin[Z] )
+//						mmin[Z] = m[i][Z];
+//					if( m[i][Z] > mmax[Z] )
+//						mmax[Z] = m[i][Z];
+//				}
+//
+//				m_bbox.SetMin( mmin );
+//				m_bbox.SetMax( mmax );
+//				break;
+//			}
+//			default:
+//				// Do nothing.
+//				break;
+//		}
+//#ifdef SHORT_VERT
+//		DCFlushRange( mp_posBuffer, 3 * m_num_vertex * sizeof( s16 ) );
+//#else
+//		DCFlushRange( mp_posBuffer, 3 * m_num_vertex * sizeof( float ) );
+//#endif		// SHORT_VERT
+//		if ( mp_normBuffer ) DCFlushRange( mp_normBuffer, 3 * m_num_vertex * sizeof( s16 ) );
+//	}
+}
+	
+} // namespace NxNgc
+
+
+
+
diff --git a/Code/Gfx/NGC/NX/mesh.h b/Code/Gfx/NGC/NX/mesh.h
new file mode 100644
index 0000000..fe3d56d
--- /dev/null
+++ b/Code/Gfx/NGC/NX/mesh.h
@@ -0,0 +1,195 @@
+#ifndef __MESH_H
+#define __MESH_H
+
+#include 
+#include 
+#include 
+#include "material.h"
+#include 
+
+// Comment in for 16 bit vertices.
+//#define SHORT_VERT
+
+namespace NxNgc
+{
+
+struct sDLHeader;	// Forward reference.
+
+//#define MESHFLAG_TEXTURE     (1<<0)
+//#define MESHFLAG_COLOURS     (1<<1)
+//#define MESHFLAG_NORMALS     (1<<2)
+
+//#define OBJFLAG_TEXTURE      (1<<0)
+//#define OBJFLAG_COLOURS      (1<<1)
+//#define OBJFLAG_NORMALS      (1<<2)
+//#define OBJFLAG_TRANSPARENT	 (1<<3)
+
+struct sCASData
+{
+	uint32	mask;
+	uint32	data0;
+	uint32	data1;
+};
+	
+// Starting to turn this into a class, but since engine is still
+// C-style, I'm keeping everything public.  Garrett
+struct sMesh
+{
+public:
+	enum EMeshFlags
+	{
+		MESH_FLAG_IS_INSTANCE				= (1<<0),
+		MESH_FLAG_ACTIVE					= (1<<1),
+		MESH_FLAG_IS_CLONED					= (1<<2),
+		MESH_FLAG_VISIBLE					= (1<<3),		// Used for batch culling.
+		MESH_FLAG_CLONED_POS				= (1<<4),
+		MESH_FLAG_CLONED_COL				= (1<<5),
+		MESH_FLAG_INDEX_COUNT_SET			= (1<<6),
+		MESH_FLAG_NO_SKATER_SHADOW			= (1<<7),
+		MESH_FLAG_CLONED_DL					= (1<<8),
+	};
+
+					sMesh( void );
+					~sMesh( void );
+
+	// Functions
+	void			wibble_vc( void );
+	void			wibble_normals( void );
+	uint32			GetChecksum()	const { return Checksum; }
+	uint32			GetFlags()		const			{ return m_flags; }
+	
+	void			SetActive( bool active )		{ m_flags = ( m_flags & ~MESH_FLAG_ACTIVE ) | ( active ? MESH_FLAG_ACTIVE : 0 ); }
+	void			SetPosition( Mth::Vector &pos );
+	void			GetPosition( Mth::Vector *p_pos );
+	void			SetYRotation( Mth::ERot90 rot );
+	void			SetVisibility( uint32 mask )	{ m_visibility_mask	= mask; }
+	uint32			GetVisibility( void )			{ return m_visibility_mask; }
+	sMesh			*Clone( bool instance = false );
+//	void			Initialize( int num_vertices,
+//								float* p_positions,
+//#ifdef SHORT_VERT
+//								s16* p_positions16,
+//								int shift,
+//								float off_x,
+//								float off_y,
+//								float off_z,
+//#endif		// SHORT_VERT
+//								s16* p_normals,
+//								float* p_tex_coords,
+//								float* p_env_buffer,
+//								int num_tc_sets,
+//								uint32* p_colors,
+//								int num_indices,
+//								uint16* p_pos_indices,
+//								uint16* p_col_indices,
+//								uint16* p_uv_indices,
+//								unsigned long material_checksum,
+//								void *p_scene,
+//								int num_double,
+//								uint32* p_double,
+//								int num_single,
+//								int localMeshIndex,
+//								GXPrimitive primitive_type,
+//								int bone_idx,
+//								char *p_vc_wibble_anims  = NULL );
+
+	void			Build( int num_indices, uint16* p_pos_indices, uint16* p_col_indices, uint16* p_uv_indices, bool rebuild = false );
+	void			Rebuild( int num_indices, uint16* p_indices );
+
+	// Note: localMeshIndex is the nth instance of a mesh in a sector.
+	void			Submit( void );
+
+	// Members
+	uint32					Checksum;
+	uint16					m_flags;
+	int16					m_bone_idx;
+	uint32					m_visibility_mask;
+	float					m_bottom_y;	// Use this to cull meshes above the skater for shadows.
+
+//	sMaterial*				mp_material;
+//	
+//	uint16					m_num_indices;
+//	uint16					m_num_vertex;
+//	uint8					m_vertex_stride;
+//	uint8					m_num_uv_sets;
+//
+//	uint16					m_display_list_size;
+//	uint8					m_primitive_type;
+//	uint8					m_vertex_format;
+//	uint16					m_localMeshIndex;
+//
+//	uint16*					mp_index_buffer;
+//#ifdef SHORT_VERT
+//	s16*					mp_posBuffer;
+//#else
+//	float*					mp_posBuffer;
+//#endif		// SHORT_VERT
+//	float					m_offset_x;
+//	float					m_offset_y;
+//	float					m_offset_z;
+//	s16*					mp_normBuffer;
+//	float*					mp_uvBuffer;
+//	float*					mp_envBuffer;
+//	uint32*					mp_colBuffer;
+//
+//	uint8*					mp_display_list;
+//
+//	Mth::CBBox				m_bbox;
+//	float					m_sphere[4];
+//
+//	uint16					m_numDouble;
+//	uint16					m_numSingle;
+//	uint32*					mp_doubleWeight;
+//
+//	char					*mp_vc_wibble_data;
+//
+//	GXColor					m_material_color_override;
+
+//	Mth::Vector				m_sphere;
+
+	sDLHeader*				mp_dl;
+//	void *					mp_texture_dl;
+//	uint32					m_texture_dl_size;
+//
+//	bool					dl_owner;
+
+	GXColor					m_base_color;
+};
+
+/*
+// Starting to turn this into a class, but since engine is still
+// C-style, I'm keeping everything public.  Garrett
+struct sGroup
+{
+public:
+	// Functions
+	sMesh*			GetMeshArray(); 
+	uint			GetNumMeshes() const { return NumMeshes; }
+
+	// Members
+	uint32 ID;
+	uint8 *pUpload[2];
+	uint8 *pRender[2];
+	uint FirstMeshIndex;
+	uint NumMeshes;
+};
+
+
+sGroup*			NewMeshGroup(uint32 group_ID, uint32 num_meshes);
+sGroup*			LoadMeshGroup(void *p_FH);
+void			LoadVertices(void *p_FH, sMesh *pMesh, sMaterial *pMat);
+
+extern sMesh *Meshes;
+extern uint NumMeshes, NumMeshGroups;
+extern uint NumMeshesInGroup[32];
+extern int TotalNumVertices;
+*/
+
+void SetMeshScalingParameters( Nx::SMeshScalingParameters* pParams );
+void DisableMeshScaling( void );
+void ApplyMeshScaling( NxNgc::sObjectHeader * p_object );
+
+} // namespace NxNgc
+
+#endif // __MESH_H
+
diff --git a/Code/Gfx/NGC/NX/nx_init.cpp b/Code/Gfx/NGC/NX/nx_init.cpp
new file mode 100644
index 0000000..232970a
--- /dev/null
+++ b/Code/Gfx/NGC/NX/nx_init.cpp
@@ -0,0 +1,159 @@
+#include "nx_init.h"
+#include "instance.h"
+#include "render.h"
+#include "sys/ngc/p_buffer.h"
+
+
+namespace NxNgc
+{
+
+sEngineGlobals	EngineGlobals;
+	
+	
+
+
+void InitialiseRenderstates( void )
+{
+//	EngineGlobals.lighting_enabled		= false;
+//	D3DDevice_SetRenderState( D3DRS_LIGHTING, FALSE );
+//
+//	EngineGlobals.cull_mode				= D3DCULL_NONE;
+//	D3DDevice_SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );
+//
+//	EngineGlobals.dither_enable			= TRUE;
+//	D3DDevice_SetRenderState( D3DRS_DITHERENABLE, TRUE );
+//
+//	EngineGlobals.z_test_enabled		= TRUE;
+//	D3DDevice_SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );
+//
+//	EngineGlobals.z_write_enabled		= TRUE;
+//	D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
+//	
+//	EngineGlobals.alpha_blend_enable	= TRUE;
+//	D3DDevice_SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
+//
+//	EngineGlobals.alpha_test_enable		= TRUE;
+//	D3DDevice_SetRenderState( D3DRS_ALPHATESTENABLE, TRUE );
+//
+//	EngineGlobals.alpha_ref				= 0;
+//	D3DDevice_SetRenderState( D3DRS_ALPHAREF, 0x00 );
+//
+//	D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+//    D3DDevice_SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );
+//    D3DDevice_SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR );
+//
+//    D3DDevice_SetTextureStageState( 0, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP );
+//    D3DDevice_SetTextureStageState( 0, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP );
+//
+//    D3DDevice_SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL );
+//    D3DDevice_SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL );
+//    D3DDevice_SetRenderState( D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL );
+//
+//	// Set these up so they will get reset first time through.
+//	EngineGlobals.blend_mode_value			= 0xDEADBABE;
+//	EngineGlobals.blend_mode_fixed_alpha	= 0xDEADBABE;
+
+	init_render_system();
+}
+
+
+void InitialiseEngine( void )
+{
+
+//	D3DPRESENT_PARAMETERS   params;
+//	DWORD					video_flags = XGetVideoFlags();
+//
+//	ZeroMemory( ¶ms, sizeof( D3DPRESENT_PARAMETERS ));
+//
+//	// This setting required for any multisample presentation.
+//	params.SwapEffect						= D3DSWAPEFFECT_DISCARD;
+//
+//	// Let D3D create the depth-stencil buffer for us.
+//	params.EnableAutoDepthStencil			= TRUE;
+//
+//	// Select default refresh rate and presentation interval. Note: When we switch to the December SDK
+//	// we can use the ONE_OR_IMMEDIATE value (if the tearing looks okay).
+//	params.FullScreen_RefreshRateInHz		= D3DPRESENT_RATE_DEFAULT;
+//	params.FullScreen_PresentationInterval	= D3DPRESENT_INTERVAL_ONE;
+//
+//	// Set up the back buffer format.
+//	params.BackBufferCount					= 1;
+//	params.BackBufferWidth					= 640;
+//	params.BackBufferHeight					= 480;
+////	params.BackBufferFormat					= D3DFMT_LIN_R5G6B5;
+//	params.BackBufferFormat					= D3DFMT_X8R8G8B8;
+//
+//	// Set up the Z-stencil buffer format and multisample format.
+//	params.AutoDepthStencilFormat			= D3DFMT_D24S8;
+//    params.MultiSampleType					= D3DMULTISAMPLE_2_SAMPLES_MULTISAMPLE_LINEAR;
+//
+//	// Set flag for progrssive scan where appropriate.
+//	if( video_flags & XC_VIDEO_FLAGS_HDTV_480p )
+//	{
+//		params.Flags						= D3DPRESENTFLAG_PROGRESSIVE;
+//	}
+//	else
+//	{
+//		params.Flags						= D3DPRESENTFLAG_INTERLACED;
+//	}
+//	
+//	if( D3D_OK != Direct3D_CreateDevice(	D3DADAPTER_DEFAULT,
+//											D3DDEVTYPE_HAL,
+//											NULL,
+//											D3DCREATE_HARDWARE_VERTEXPROCESSING,	// Note: may want to consider adding the PUREDEVICE flag here also.
+//											¶ms,
+//											&EngineGlobals.p_Device ))
+//	{
+//		// Failed to start up engine. Bad!
+//		return;
+//	}
+//
+//	// Obtain pointers to the render and Z-stencil surfaces. Doing this increases their reference counts, so release
+//	// them following the operation.
+//	D3DDevice_GetRenderTarget( &EngineGlobals.p_RenderSurface );
+//	D3DDevice_GetDepthStencilSurface( &EngineGlobals.p_ZStencilSurface );
+//
+//    D3DResource_Release((D3DResource*)&EngineGlobals.p_RenderSurface );
+//    D3DResource_Release((D3DResource*)&EngineGlobals.p_ZStencilSurface );
+//
+	// Set default directional lights
+	EngineGlobals.light_x[0] = -0.5f;		// Dir0
+	EngineGlobals.light_y[0] = -0.8660254f;
+	EngineGlobals.light_z[0] = 0.0f;
+	EngineGlobals.diffuse_light_color[0] = (GXColor){143,143,143,255};
+	
+	EngineGlobals.light_x[1] = 1.0f;		// Dir1 
+	EngineGlobals.light_y[1] = 0.0f;
+	EngineGlobals.light_z[1] = 0.0f;
+	EngineGlobals.diffuse_light_color[1] = (GXColor){0,0,0,255};
+
+	// Set default ambient light.
+	EngineGlobals.ambient_light_color = (GXColor){150,150,150,255};
+
+	EngineGlobals.gpuBusy = false;
+
+	EngineGlobals.screen_brightness = 1.0f;
+
+	EngineGlobals.frameCount = 0;
+
+	EngineGlobals.use_480p			= false;
+	EngineGlobals.use_60hz			= false;
+	EngineGlobals.letterbox_active	= false;
+
+	EngineGlobals.reduceColors = true;
+
+	// Set our renderstate to a known state.
+	InitialiseRenderstates();
+
+	// Create our instance table.
+	InitialiseInstanceTable();
+
+	EngineGlobals.disableReset = false;
+	EngineGlobals.resetToIPL = false; 
+
+	NsBuffer::init( ( 1024 * 512 ) - ( 50 * 1024 ) );
+}
+
+
+} // namespace NxNgc
+
diff --git a/Code/Gfx/NGC/NX/nx_init.h b/Code/Gfx/NGC/NX/nx_init.h
new file mode 100644
index 0000000..609c8ae
--- /dev/null
+++ b/Code/Gfx/NGC/NX/nx_init.h
@@ -0,0 +1,99 @@
+#ifndef __NX_INIT_H
+#define __NX_INIT_H
+
+#include 
+#include 
+
+namespace NxNgc
+{
+
+void InitialiseEngine( void );
+
+typedef struct
+{
+//	IDirect3DDevice8*	p_Device;
+//	IDirect3DSurface8*	p_RenderSurface;
+//	IDirect3DSurface8*	p_ZStencilSurface;
+
+	uint32				blend_mode_value;
+	uint32				blend_mode_fixed_alpha;
+
+	// These renderstates should go in their own structure.
+	bool				alpha_blend_enable;
+	bool				alpha_test_enable;
+	uint32				alpha_ref;
+	bool				lighting_enabled;
+	bool				dither_enable;
+	bool				z_write_enabled;
+	bool				z_test_enabled;
+	int					cull_mode;
+	bool				poly_culling;
+
+	NsMatrix			local_to_camera;
+	NsMatrix			world_to_camera;
+	NsMatrix			camera;
+	NsMatrix			shadow_camera;
+	Vec					object_pos;
+	Mtx					current_uploaded;
+
+	float				tx;
+	float				ty;
+	float				sx;
+	float				sy;
+	float				cx;
+	float				cy;
+	float				near;
+	float				far;
+
+	GXColor				ambient_light_color;
+	GXColor				diffuse_light_color[3];
+	float				light_x[3];
+	float				light_y[3];
+	float				light_z[3];
+
+	float				screen_brightness;	// 0 = black, 1 = normal, 2 = saturated.
+
+	uint32				frameCount;
+
+	volatile bool		gpuBusy;
+
+	// ARAM defines.
+	#define aram_zero_size		1024
+	#define	aram_dsp_size		1024 * 1900
+	#define	aram_stream0_size	1024 * 32
+	#define aram_stream1_size	aram_stream0_size
+	#define aram_stream2_size	aram_stream1_size
+	#define aram_music_size		1024 * 32
+	
+	uint32				aram_zero;
+	uint32				aram_dsp;
+	uint32				aram_stream0;
+	uint32				aram_stream1;
+	uint32				aram_stream2;
+	uint32				aram_music;
+
+	bool				use_480p;
+	bool				use_60hz;
+	bool				use_widescreen;
+	bool				letterbox_active;
+
+	bool				reduceColors;
+
+	bool				disableReset;
+	bool				resetToIPL;
+
+	uint32				viewport;		// Expressed as a bit ( 1 << v ).
+
+	NsVector			skater_shadow_dir;
+	float				skater_height;
+	NsVector			ped_shadow_dir;
+}
+sEngineGlobals;
+
+extern sEngineGlobals EngineGlobals;
+
+
+
+} // namespace NxNgc
+
+#endif // __NX_INIT_H
diff --git a/Code/Gfx/NGC/NX/occlude.cpp b/Code/Gfx/NGC/NX/occlude.cpp
new file mode 100644
index 0000000..86bb4f9
--- /dev/null
+++ b/Code/Gfx/NGC/NX/occlude.cpp
@@ -0,0 +1,491 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		occlude.cpp												**
+**																			**
+**	Created:		04/02/02	-	dc										**
+**																			**
+**	Description:	Occlusion testing code									**
+**																			**
+*****************************************************************************/
+
+
+//#define	OCCLUDER_USES_SCRATCHPAD
+//#define	OCCLUDER_USES_VU0_MACROMODE
+//#define	OCCLUDER_USES_VU0_MICROMODE
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+#include 
+#include 
+#include "occlude.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+//extern D3DXMATRIX *p_bbox_transform;
+
+namespace NxNgc
+{
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+const uint32 MAX_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME = 4;
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// Structure used to store details of a single poly. A list of these will be built at geometry load time.
+struct sOcclusionPoly
+{
+	bool		in_use;		// Whether the poly is currently being used for occlusion.
+	bool		available;	// Whether the poly is available for selection for occlusion.
+	uint32		checksum;	// Name checksum of the occlusion poly.
+	Mth::Vector	verts[4];
+	Mth::Vector	normal;
+};
+	
+const uint32	MAX_OCCLUDERS				= 8;
+const uint32	MAX_VIEWS_PER_OCCLUDER		= 2;
+
+struct sOccluder
+{
+	static uint32		NumOccluders;
+	static sOccluder	Occluders[MAX_OCCLUDERS];
+
+	static void			add_to_stack( sOcclusionPoly *p_poly );
+	static void			sort_stack( void );
+	static void			tidy_stack( void );
+
+	sOcclusionPoly	*p_poly;
+	Mth::Vector		planes[5];
+	int				score[MAX_VIEWS_PER_OCCLUDER];	// Current rating on quality of occlusion - based on number of meshes occluded last frame.
+};
+
+const uint32	MAX_OCCLUSION_POLYS			= 512;
+uint32			NumOcclusionPolys			= 0;
+uint32			NextOcclusionPolyToCheck	= 0;
+int				CurrentView					= 0;
+sOcclusionPoly	OcclusionPolys[MAX_OCCLUSION_POLYS];
+
+uint32		sOccluder::NumOccluders				= 0;
+sOccluder	sOccluder::Occluders[MAX_OCCLUDERS];
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::add_to_stack( sOcclusionPoly *p_poly )
+{
+	if( NumOccluders < MAX_OCCLUDERS )
+	{
+		Dbg_Assert( p_poly->available );
+
+		Occluders[NumOccluders].p_poly	= p_poly;
+		p_poly->in_use					= true;
+
+		// Reset scores for all views.
+		memset( Occluders[NumOccluders].score, 0, sizeof( int ) * MAX_VIEWS_PER_OCCLUDER );
+
+		++NumOccluders;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int cmp( const void *p1, const void *p2 )
+{
+	// Sort based on the sum total of scores in all views.
+	int score1 = 0;
+	int score2 = 0;
+	for( uint v = 0; v < MAX_VIEWS_PER_OCCLUDER; ++v )
+	{
+		// Zero the score for any occlusion poly that is no longer available. This will force it out of the stack.
+		if(((sOccluder*)p1)->p_poly->available == false )
+			((sOccluder*)p1)->score[v] = 0;
+
+		if(((sOccluder*)p2)->p_poly->available == false )
+			((sOccluder*)p2)->score[v] = 0;
+
+		score1 += ((sOccluder*)p1)->score[v];
+		score2 += ((sOccluder*)p2)->score[v];
+	}
+
+	return score1 < score2 ? 1 : score1 > score2 ? -1 : 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::sort_stack( void )
+{
+	qsort( Occluders, NumOccluders, sizeof( sOccluder ), cmp );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::tidy_stack( void )
+{
+	if( NumOccluders > 0 )
+	{
+		// Sort in descending score order.
+		sort_stack();
+
+		// Count backwards so we know we get all the bad occluders.
+		for( int i = NumOccluders - 1; i >= 0; --i )
+		{
+			// If we have hit an occluder with zero meshes culled, cut off the stack at this point.
+			int total_score = 0;
+			for( uint v = 0; v < MAX_VIEWS_PER_OCCLUDER; ++v )
+			{
+				total_score += Occluders[i].score[v];
+			}
+			
+			if( total_score == 0 )
+			{
+				// No longer using this poly.
+				Occluders[i].p_poly->in_use = false;
+
+				// One less occluder to worry about.
+				--NumOccluders;
+			}
+			else
+			{
+				// Reset the good occluders.
+				Occluders[i].score[CurrentView] = 0;
+			}
+		}
+	}
+	
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to add an occlusion poly to the list. Likely to be called */
+/* as geometry is spooled in.                                     */
+/*                                                                */
+/******************************************************************/
+void AddOcclusionPoly( Mth::Vector &v0, Mth::Vector &v1, Mth::Vector &v2, Mth::Vector &v3, uint32 checksum )
+{
+	Dbg_Assert( NumOcclusionPolys < MAX_OCCLUSION_POLYS );	
+	
+	OcclusionPolys[NumOcclusionPolys].in_use	= false;
+	OcclusionPolys[NumOcclusionPolys].available	= true;
+	OcclusionPolys[NumOcclusionPolys].checksum	= checksum;
+	OcclusionPolys[NumOcclusionPolys].verts[0]	= v0;
+	OcclusionPolys[NumOcclusionPolys].verts[1]	= v1;
+	OcclusionPolys[NumOcclusionPolys].verts[2]	= v2;
+	OcclusionPolys[NumOcclusionPolys].verts[3]	= v3;
+	OcclusionPolys[NumOcclusionPolys].normal	= Mth::CrossProduct( v1 - v0, v3 - v0 );
+	OcclusionPolys[NumOcclusionPolys].normal.Normalize();
+	
+	++NumOcclusionPolys;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to toggle whether an occlusion poly can be used or not	  */
+/*                                                                */
+/******************************************************************/
+void EnableOcclusionPoly( uint32 checksum, bool available )
+{
+	for( uint32 i = 0; i < NumOcclusionPolys; ++i )
+	{
+		if( OcclusionPolys[i].checksum == checksum )
+		{
+			OcclusionPolys[i].available	= available;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to clear all occlusion polys (when a level is unloaded)   */
+/*                                                                */
+/******************************************************************/
+void RemoveAllOcclusionPolys( void )
+{
+	Dbg_Assert( NumOcclusionPolys < MAX_OCCLUSION_POLYS );	
+	
+ 	sOccluder::NumOccluders		= 0;
+	NumOcclusionPolys			= 0;
+	NextOcclusionPolyToCheck	= 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CheckForOptimalOccluders( Mth::Vector &cam_pos, Mth::Vector &view_direction )
+{
+	if( NumOcclusionPolys > 0 )
+	{
+		uint32 added	= 0;
+		uint32 checked	= 0;
+
+		while( added < MAX_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME )
+		{
+			// Given the current position of the camera, check through the unused, available occlusion polys to see if one scores higher
+			// than the lowest scoring occlusion poly in use.
+			sOcclusionPoly *poly_to_check = &OcclusionPolys[NextOcclusionPolyToCheck++];
+			if(( !poly_to_check->in_use ) && ( poly_to_check->available ))
+			{
+				sOccluder::add_to_stack( poly_to_check );
+				++added;
+			}
+			++checked;
+
+			// Ensure we are always checking within bounds.
+			if( NextOcclusionPolyToCheck >= NumOcclusionPolys )
+			{
+				NextOcclusionPolyToCheck = 0;
+			}
+
+			// Quit out if we have less available occluders than spaces to fill.
+			if( checked >= NumOcclusionPolys )
+			{
+				break;
+			}
+		}
+	}
+}
+
+
+//char scratch[16384];
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void BuildOccluders( Mth::Vector *p_cam_pos, int view )
+{
+//	for( uint32 i = 0; i < NumOcclusionPolys; ++i )
+//	{
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[0], OcclusionPolys[i].verts[1], 0, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[1], OcclusionPolys[i].verts[2], 0, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[2], OcclusionPolys[i].verts[3], 0, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[3], OcclusionPolys[i].verts[0], 0, 0, 1 );
+//	}
+	
+	// Set the current view - this will remain active until another call to this function.
+	Dbg_Assert( (uint32)view < MAX_VIEWS_PER_OCCLUDER );
+	CurrentView	= view;
+	
+	// Tidy up from last frame.
+	sOccluder::tidy_stack();
+	
+	// Cyclically add more occluders for checking.
+	CheckForOptimalOccluders( *p_cam_pos, *p_cam_pos );
+	
+	// Build all 5 planes for each occluder.
+	Mth::Vector u0, u1, p;
+
+	// The order in which the verts are used to build tha planes depends upon where the camera is in relation to the occlusion
+	// poly. We use the default order when the viewpoint is on the side of the poly on which the default poly normal faces.
+	for( uint32 i = 0; i < sOccluder::NumOccluders; ++i )
+	{
+		sOcclusionPoly *p_poly = sOccluder::Occluders[i].p_poly;
+
+//		Gfx::AddDebugLine( p_poly->verts[0], p_poly->verts[1], 0, 0, 1 );
+//		Gfx::AddDebugLine( p_poly->verts[1], p_poly->verts[2], 0, 0, 1 );
+//		Gfx::AddDebugLine( p_poly->verts[2], p_poly->verts[3], 0, 0, 1 );
+//		Gfx::AddDebugLine( p_poly->verts[3], p_poly->verts[0], 0, 0, 1 );
+		
+		if( Mth::DotProduct( *p_cam_pos - p_poly->verts[0], p_poly->normal ) >= 0.0f )
+		{
+			// Start with the front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
+			// are considered occluded. (1->3->4)...
+			u0						= p_poly->verts[2] - p_poly->verts[0];
+			u1						= p_poly->verts[3] - p_poly->verts[0];
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, p_poly->verts[0] );
+			sOccluder::Occluders[i].planes[0]	= p;
+
+			// ...then left (0->1->2)...
+			u0						= p_poly->verts[0] - *p_cam_pos;
+			u1						= p_poly->verts[1] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[1]	= p;
+
+			// ...then right (0->3->4)...
+			u0						= p_poly->verts[2] - *p_cam_pos;
+			u1						= p_poly->verts[3] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[2]	= p;
+
+			// ...then top (0->2->3)...
+			u0						= p_poly->verts[1] - *p_cam_pos;
+			u1						= p_poly->verts[2] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[3]	= p;
+
+			// ...then bottom (0->4->1)...
+			u0						= p_poly->verts[3] - *p_cam_pos;
+			u1						= p_poly->verts[0] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[4]	= p;
+		}
+		else
+		{
+			// Start with the front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
+			// are considered occluded. (1->4->3)...
+			u0						= p_poly->verts[3] - p_poly->verts[0];
+			u1						= p_poly->verts[2] - p_poly->verts[0];
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, p_poly->verts[0] );
+			sOccluder::Occluders[i].planes[0]	= p;
+
+			// ...then left (0->2->1)...
+			u0						= p_poly->verts[1] - *p_cam_pos;
+			u1						= p_poly->verts[0] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[1]	= p;
+
+			// ...then right (0->4->3)...
+			u0						= p_poly->verts[3] - *p_cam_pos;
+			u1						= p_poly->verts[2] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[2]	= p;
+
+			// ...then top (0->3->2)...
+			u0						= p_poly->verts[2] - *p_cam_pos;
+			u1						= p_poly->verts[1] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[3]	= p;
+
+			// ...then bottom (0->1->4)...
+			u0						= p_poly->verts[0] - *p_cam_pos;
+			u1						= p_poly->verts[3] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[4]	= p;
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool TestSphereAgainstOccluders( Mth::Vector *p_sphere, uint32 meshes )
+{
+	// Test against each occluder.
+	for( uint32 o = 0; o < sOccluder::NumOccluders; ++o )
+	{
+		bool occluded = true;
+
+		// Test against each plane in the occluder.
+		for( uint32 p = 0; p < 5; ++p )
+		{
+			float result =	( sOccluder::Occluders[o].planes[p][X] * p_sphere->GetX() ) +
+							( sOccluder::Occluders[o].planes[p][Y] * p_sphere->GetY() ) +
+							( sOccluder::Occluders[o].planes[p][Z] * p_sphere->GetZ() ) -
+							( sOccluder::Occluders[o].planes[p][W] );
+			if( result >= -p_sphere->GetW() )
+			{
+				// Outside of this plane, therefore not occluded by this occluder.
+				occluded = false;
+				break;
+			}
+		}
+
+		if( occluded )
+		{
+			// Inside all planes, therefore occluded. Increase score for this occluder.
+			sOccluder::Occluders[o].score[CurrentView] += meshes;
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/NGC/NX/occlude.h b/Code/Gfx/NGC/NX/occlude.h
new file mode 100644
index 0000000..636e052
--- /dev/null
+++ b/Code/Gfx/NGC/NX/occlude.h
@@ -0,0 +1,15 @@
+#ifndef	__OCCLUDE_H__
+#define	__OCCLUDE_H__
+
+namespace NxNgc
+{
+	void AddOcclusionPoly( Mth::Vector &v0, Mth::Vector &v1, Mth::Vector &v2, Mth::Vector &v3, uint32 checksum = 0 );
+	void EnableOcclusionPoly( uint32 checksum, bool available );
+	void RemoveAllOcclusionPolys( void );
+	void BuildOccluders( Mth::Vector *p_cam_pos, int view );
+	bool TestSphereAgainstOccluders( Mth::Vector *p_sphere, uint32 meshes = 1 );
+}
+
+#endif
+
+
diff --git a/Code/Gfx/NGC/NX/particles.cpp b/Code/Gfx/NGC/NX/particles.cpp
new file mode 100644
index 0000000..17c8367
--- /dev/null
+++ b/Code/Gfx/NGC/NX/particles.cpp
@@ -0,0 +1,372 @@
+//
+///*****************************************************************************
+//**																			**
+//**			              Neversoft Entertainment			                **
+//**																		   	**
+//**				   Copyright (C) 1999 - All Rights Reserved				   	**
+//**																			**
+//******************************************************************************
+//**																			**
+//**	Project:		GFX (Graphics Library)									**
+//**																			**
+//**	Module:			Graphics (GFX)		 									**
+//**																			**
+//**	File name:		particle.cpp											**
+//**																			**
+//**	Created:		03/27/02	-	dc										**
+//**																			**
+//**	Description:	Low level particle rendering code						**
+//**																			**
+//*****************************************************************************/
+//
+//int gParticleMem = 0;
+//
+///*****************************************************************************
+//**							  	  Includes									**
+//*****************************************************************************/
+//		
+//#include 
+//#include 
+//#include "render.h"
+//#include "particles.h"
+//#include "scene.h"
+//#include 
+//#include 
+//#include 
+//#include 
+//
+///*****************************************************************************
+//**								DBG Information								**
+//*****************************************************************************/
+//
+//
+///*****************************************************************************
+//**								  Externals									**
+//*****************************************************************************/
+//
+//namespace NxNgc
+//{
+//
+//
+///*****************************************************************************
+//**								   Defines									**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								Private Types								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								 Private Data								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								 Public Data								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Private Prototypes							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							   Private Functions							**
+//*****************************************************************************/
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+///*****************************************************************************
+//**							   Public Functions								**
+//*****************************************************************************/
+//
+//static BlendModes get_texture_blend( uint32 blend_checksum )
+//{
+//	BlendModes rv = NxNgc::vBLEND_MODE_DIFFUSE;
+//	switch ( blend_checksum )
+//	{
+//		case 0x54628ed7:		// Blend
+//			rv = NxNgc::vBLEND_MODE_BLEND;
+//			break;
+//		case 0x02e58c18:		// Add
+//			rv = NxNgc::vBLEND_MODE_ADD;
+//			break;
+//		case 0xa7fd7d23:		// Sub
+//		case 0xdea7e576:		// Subtract
+//			rv = NxNgc::vBLEND_MODE_SUBTRACT;
+//			break;
+//		case 0x40f44b8a:		// Modulate
+//			rv = NxNgc::vBLEND_MODE_MODULATE;
+//			break;
+//		case 0x68e77f40:		// Brighten
+//			rv = NxNgc::vBLEND_MODE_BRIGHTEN;
+//			break;
+//		case 0x18b98905:		// FixBlend
+//			rv = NxNgc::vBLEND_MODE_BLEND_FIXED;
+//			break;
+//		case 0xa86285a1:		// FixAdd
+//			rv = NxNgc::vBLEND_MODE_ADD_FIXED;
+//			break;
+//		case 0x0d7a749a:		// FixSub
+//		case 0x0eea99ff:		// FixSubtract
+//			rv = NxNgc::vBLEND_MODE_SUB_FIXED;
+//			break;
+//		case 0x90b93703:		// FixModulate
+//			rv = NxNgc::vBLEND_MODE_MODULATE_FIXED;
+//			break;
+//		case 0xb8aa03c9:		// FixBrighten
+//			rv = NxNgc::vBLEND_MODE_BRIGHTEN_FIXED;
+//			break;
+//		case 0x515e298e:		// Diffuse
+//		case 0x806fff30:		// None
+//			rv = NxNgc::vBLEND_MODE_DIFFUSE;
+//			break;
+//		default:
+//			Dbg_MsgAssert(0,("Illegal blend mode specified. Please use (fix)blend/add/sub/modulate/brighten or diffuse/none."));
+//			break;
+//	}
+//	return rv;
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//sParticleSystem::sParticleSystem( uint32 max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix )
+//{
+//	// Create a scene to store the mesh.
+//	mp_scene = new sScene();
+//
+//	// Get the texture.
+//
+//	uint32 flags = MATFLAG_TRANSPARENT;
+//	Nx::CTexture *p_texture;
+//	Nx::CNgcTexture *p_ngc_texture;
+//	NxNgc::sTexture * p_engine_texture = NULL;
+//
+//	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+//	p_ngc_texture = static_cast( p_texture );
+//	if ( p_ngc_texture )
+//	{
+//		p_engine_texture = p_ngc_texture->GetEngineTexture(); 
+//		flags |=MATFLAG_TEXTURED;
+//	}
+//
+//	// Create a material used to render the mesh.
+//	sMaterial *p_material				= new sMaterial();
+//	p_material->m_sorted				= 0;
+//	p_material->m_no_bfc				= 1;
+//	p_material->Flags[0]				= flags;		// 0x40 makes it semitransparent.
+//	p_material->Checksum				= 0x5c41d978;	// particle_system	//(uint32)rand() * (uint32)rand();
+//	p_material->Passes					= 1;
+//	p_material->pTex[0]					= p_engine_texture;
+//	p_material->blendMode[0]			= (uint8)get_texture_blend( blendmode_checksum );
+//	p_material->fixAlpha[0]				= fix & 255;
+//	p_material->UVAddressing[0]			= 0;
+//	p_material->matcol[0]				= (GXColor){128,128,128,255};
+//	p_material->AlphaCutoff				= 1;
+//	p_material->m_num_wibble_vc_anims	= 0;
+//	p_material->mp_wibble_vc_params		= NULL;
+//	p_material->mp_wibble_vc_colors		= NULL;
+//	p_material->m_pUVControl			= NULL;
+//
+//	// Create a hash table to store the material...
+//	mp_scene->mp_material_array = p_material;
+//	mp_scene->m_num_materials = 1;
+//
+//	// Initialize the material (builds the display list).
+//	InitializeMaterial( p_material );
+//
+//	// Create a mesh which will be used to render the system.
+//	mp_mesh = new sMesh();
+//
+//	// Create a position buffer.
+//#ifdef SHORT_VERT
+//	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+//	float *p_pos		= new float[max_particles * 3 * 4];
+//	Mem::Manager::sHandle().PopContext();
+//	s16 *p_pos16		= new s16[max_particles * 3 * 4];
+//	gParticleMem += max_particles * 3 * 4 * 2;
+//#else
+//	float *p_pos		= new float[max_particles * 3 * 4];
+//#endif		// SHORT_VERT
+//
+//	for( uint32 lp = 0; lp < ( max_particles * 3 * 4 ); ++lp )
+//	{
+//		p_pos[lp] = 1024.0f;
+//#ifdef SHORT_VERT
+//		p_pos16[lp] = (s16)(1024.0f*(1 << 0));
+//#endif		// SHORT_VERT
+//	}
+//	
+//	// Save pointer to bounding sphere so that the particle system can update it.
+//	mp_sphere = mp_mesh->m_sphere;
+//
+//	// Create an index buffer.
+//	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+//	uint16 *p_indices	= new uint16[(max_particles * 4)+2];
+//	gParticleMem += ( (max_particles * 4)+2 ) * 2;
+//	Mem::Manager::sHandle().PopContext();
+//
+//	// Fill the index buffer.
+//	uint16 *p16 = p_indices;
+//	*p16++ = max_particles * 4;
+//	for( uint32 i = 0; i < max_particles; ++i )
+//	{
+//		*p16++ = (uint16)i * 4 + 0;
+//		*p16++ = (uint16)i * 4 + 1;
+//		*p16++ = (uint16)i * 4 + 2;
+//		*p16++ = (uint16)i * 4 + 3;
+//	}
+//	*p16++ = 0;
+//	
+//	// Create texture coordinates.
+//	s16 *p_uv = NULL;
+//	if ( p_engine_texture )
+//	{
+//		p_uv = new s16[max_particles * 2 * 4];
+//		gParticleMem += max_particles * 2 * 4 * 2;
+//
+//		for( uint32 lp = 0; lp < max_particles; ++lp )
+//		{
+//			p_uv[(lp*2*4)+0] = (s16)(0.0f * ((float)(1<<10)));
+//			p_uv[(lp*2*4)+1] = (s16)(0.0f * ((float)(1<<10)));
+//			p_uv[(lp*2*4)+2] = (s16)(1.0f * ((float)(1<<10)));
+//			p_uv[(lp*2*4)+3] = (s16)(0.0f * ((float)(1<<10)));
+//			p_uv[(lp*2*4)+4] = (s16)(1.0f * ((float)(1<<10)));
+//			p_uv[(lp*2*4)+5] = (s16)(1.0f * ((float)(1<<10)));
+//			p_uv[(lp*2*4)+6] = (s16)(0.0f * ((float)(1<<10)));
+//			p_uv[(lp*2*4)+7] = (s16)(1.0f * ((float)(1<<10)));
+//		}
+//	}
+//
+//	// Create Color list.
+//	GXColor* p_color = new GXColor[max_particles * 4];
+//	gParticleMem += max_particles * 4 * 2;
+//
+//	for( uint32 cc = 0; cc < max_particles; ++cc )
+//	{
+//		p_color[(cc*4)+0] = (GXColor){128,128,128,255};
+//		p_color[(cc*4)+1] = (GXColor){128,128,128,255};
+//		p_color[(cc*4)+2] = (GXColor){128,128,128,255};
+//		p_color[(cc*4)+3] = (GXColor){128,128,128,255};
+//	}
+//	
+//	// Set up the mesh.
+//	mp_mesh->Initialize( max_particles * 4,		// Num vertices
+//						p_pos,					// Positions
+//#ifdef SHORT_VERT
+//						p_pos16,
+//						0,
+//						0.0f,
+//						0.0f,
+//						0.0f,
+//#endif		// SHORT_VERT
+//						NULL,					// Normals
+//						p_uv,					// Texture coordinates
+//						1,						// Number of tc sets
+//						(uint32*)p_color,		// Colors
+//						max_particles * 4,		// Num indices (4 per particle)
+//						p_indices,
+//						p_material->Checksum,	// Material checksum
+//						mp_scene,
+//						0,						// Double lists.
+//						NULL,
+//						0,						// Single lists.
+//						0,						// local index of 0 means data will be deleted.
+//						GX_QUADS,				// Type of mesh. 
+//						0,						// flags
+//						NULL );					// Wibble.
+//
+//#ifdef SHORT_VERT
+//	delete p_pos;
+//#endif		// SHORT_VERT
+//
+////	// Also adjust the bounding sphere and bounding box of the mesh.
+////	p_mesh->m_sphere_center = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
+////	p_mesh->m_sphere_radius = 360.0f;
+////	p_mesh->m_bbox.SetMin( Mth::Vector( -360.0f, -360.0f, -360.0f ));
+////	p_mesh->m_bbox.SetMax( Mth::Vector(  360.0f,  360.0f,  360.0f ));
+//	
+//	// Now add the mesh to the scene.
+//	mp_scene->CountMeshes( 1, &mp_mesh );
+//	mp_scene->CreateMeshArrays();
+//	mp_scene->AddMeshes( 1, &mp_mesh );
+//	mp_scene->SortMeshes();
+//
+//	// Create an instance and attach the mesh to it.
+//	Mth::Matrix temp;
+//	temp.GetRight().Set( 1.0f, 0.0f, 0.0f );
+//	temp.GetUp().Set( 0.0f, 1.0f, 0.0f );
+//	temp.GetAt().Set( 0.0f, 0.0f, 1.0f );
+//	temp.GetPos().Set( 0.0f, 0.0f, 0.0f );
+//	mp_instance = new NxNgc::CInstance( mp_scene, temp, 1, NULL );
+//
+//	mp_material = p_material;
+//
+//	mp_engine_texture = p_engine_texture;
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//sParticleSystem::~sParticleSystem()
+//{
+// 	// Delete the single sMesh from the scene.
+//	Dbg_Assert( mp_scene->m_num_opaque_entries == 0 );
+//	Dbg_Assert( mp_scene->m_num_semitransparent_entries == 1 );
+//	delete mp_scene->m_semitransparent_meshes[0];
+//	
+//	// Deleting the sScene will also remove the material table (which removes the material).
+//	DeleteScene( mp_scene );
+//
+//	if ( mp_instance ) delete mp_instance;
+////	mp_scene->RemoveMeshes( &mp_mesh );
+////	delete mp_mesh;
+////	delete mp_scene;
+////	delete mp_instance;
+//}
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void sParticleSystem::Render( void )
+//{
+////	render_scene( mp_scene, NULL, vRENDER_OPAQUE );
+//
+////	// Switch vertex buffers on the mesh.
+////	mp_scene->m_opaque_meshes[0]->SwapVertexBuffers();
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//#ifdef SHORT_VERT
+//s16 *sParticleSystem::GetVertexWriteBuffer( void )
+//#else
+//float *sParticleSystem::GetVertexWriteBuffer( void )
+//#endif		// SHORT_VERT
+//{
+//	return mp_scene->m_semitransparent_meshes[0]->mp_posBuffer;
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//GXColor *sParticleSystem::GetColorWriteBuffer( void )
+//{
+//	return (GXColor*)mp_scene->m_semitransparent_meshes[0]->mp_colBuffer;
+//}
+//
+//} // namespace NxNgc
+//
+//
+
diff --git a/Code/Gfx/NGC/NX/particles.h b/Code/Gfx/NGC/NX/particles.h
new file mode 100644
index 0000000..ca03652
--- /dev/null
+++ b/Code/Gfx/NGC/NX/particles.h
@@ -0,0 +1,45 @@
+#ifndef	__PARTICLE_H__
+#define	__PARTICLE_H__
+
+#include "gfx\Ngc\nx\scene.h"
+#include "gfx\Ngc\nx\instance.h"
+#include "gfx/ngc/nx/mesh.h"
+
+namespace NxNgc
+{
+
+
+struct sParticleSystem
+{
+public:
+				sParticleSystem( uint32 max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix );
+				~sParticleSystem( void );
+
+	void		Render();
+#ifdef SHORT_VERT
+	s16			*GetVertexWriteBuffer( void );
+#else
+	float		*GetVertexWriteBuffer( void );
+#endif		// SHORT_VERT
+	GXColor		*GetColorWriteBuffer( void );
+	sMaterial*	GetMaterial( void )			{ return mp_material; }
+	NxNgc::sTexture * GetTexture( void )	{ return mp_engine_texture; }
+
+	sScene*		mp_scene;
+
+	float* 		mp_sphere;
+
+	CInstance*	mp_instance;
+
+	sMaterial*	mp_material;
+
+	sMesh*		mp_mesh;
+
+	NxNgc::sTexture * mp_engine_texture;
+};
+
+}
+
+#endif
+
+
diff --git a/Code/Gfx/NGC/NX/render.cpp b/Code/Gfx/NGC/NX/render.cpp
new file mode 100644
index 0000000..5995eff
--- /dev/null
+++ b/Code/Gfx/NGC/NX/render.cpp
@@ -0,0 +1,2830 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "scene.h"
+#include "render.h"
+#include 
+#include 
+#include "occlude.h"
+#include 	"gfx/ngc/nx/mesh.h"
+#include "charpipeline\skinning.h"
+#include	"gfx\NxMiscFX.h"
+
+#include	
+#include	
+#include	
+#include	
+
+#include "dolphin/base/ppcwgpipe.h"
+#include "dolphin/gx/gxvert.h"
+
+extern int g_object;
+extern int g_view_object;
+extern int g_material;
+extern NxNgc::sScene * g_view_scene;
+
+//extern u16 colorMap[];
+
+bool g_skip_correctable = 0;
+
+#define SHADOW_TEXTURE_SIZE 256
+extern uint8 * shadowTextureData;
+extern uint16 shadowPalette[16];
+extern uint8 * zTextureData;
+extern uint8 * blurTextureData;
+NxNgc::sMaterial * p_last_material = NULL;
+uint32 last_color_override = 0;
+uint16	last_mesh_flags = 0;
+bool last_correct = true;
+bool reload_camera = false;
+
+Mtx g_matrix0;
+Mtx g_matrix90;
+Mtx g_matrix180;
+Mtx g_matrix270;
+
+Mth::Vector g_shadow_object_pos;
+
+int g_kill_layer = -1;
+
+extern "C"
+{
+extern void FakeEnvMap( ROMtx m, s16 * srcPos, s16 * srcNorm, float * dstBase, u32 count );
+extern void FakeEnvMapSkin( ROMtx m, s16 * srcPosNorm, float * dstBase, u32 count );
+extern void FakeEnvMapFloat( ROMtx m, float * srcPos, s16 * srcNorm, float * dstBase, u32 count );
+}
+
+static float * sLastUV = NULL;
+
+static bool first_mat = false;
+//static bool one_mat = false;
+
+extern uint16 shadowPalette[16];
+
+//bool gOverDraw = false;
+
+bool gMeshUseCorrection = false;
+bool gMeshUseAniso = false;
+
+const float CORRECTION_CUTOFF = ( 25.0f * 12.0f );
+const float ANISO_CUTOFF = ( 100.0f * 12.0f );
+
+int meshes_considered = 0;
+
+namespace NxNgc
+{
+
+Lst::HashTable< sTextureProjectionDetails >	*pTextureProjectionDetailsTable = NULL;
+
+NsCamera	current_cam;
+
+#ifdef SHORT_VERT
+static void cam_offset( float x, float y, float z )
+{
+	Mtx m;
+
+	MTXTrans( m, x, y, z );
+	MTXConcat( EngineGlobals.current_uploaded, m, m );
+
+    GX::LoadPosMtxImm( m, GX_PNMTX0 );
+}
+#endif		// SHORT_VERT
+
+//static void reflect_uvs( sMesh * p_mesh, float * p_tex, s16 * p_posBuffer, s16 * p_normBuffer, int step )
+//{
+//	if ( sLastUV == p_tex ) return;
+//
+//	// Inverse transform the camera position by the local to world matrix.
+//	NsMatrix mp;
+////	NsMatrix mi;
+//
+//	mp.copy( *((NsMatrix*)EngineGlobals.current_uploaded) );
+////	mp.copy( EngineGlobals.camera );
+//#ifdef SHORT_VERT
+//	NsVector v( p_mesh->m_offset_x, p_mesh->m_offset_y, p_mesh->m_offset_z );
+//	mp.translate( &v, NsMatrix_Combine_Post );
+//#endif		// SHORT_VERT
+////	mi.invert( mp );
+////	mp.setRight( mi.getRight() );
+////	mp.setUp( mi.getUp() );
+////	mp.setAt( mi.getAt() );
+//
+////	mp.invert();
+//
+////	mp.setPosX( mi.getPosX() );
+////	mp.setPosY( mi.getPosY() );
+////	mp.setPosZ( mi.getPosZ() );
+//	
+//	ROMtx sm;
+//
+//	sm[0][0] = mp.getRightX();
+//	sm[1][0] = mp.getRightY();
+//	sm[2][0] = mp.getRightZ();
+//					   
+//	sm[0][1] = mp.getUpX();
+//	sm[1][1] = mp.getUpY();
+//	sm[2][1] = mp.getUpZ();
+//					   
+//	sm[0][2] = mp.getAtX();
+//	sm[1][2] = mp.getAtY();
+//	sm[2][2] = mp.getAtZ();
+//			     
+//	sm[3][0] = mp.getPosX();
+//	sm[3][1] = mp.getPosY();
+//	sm[3][2] = mp.getPosZ();
+//
+//	if ( step == 6 )
+//	{
+//		// Skinned
+//		FakeEnvMapSkin( sm, p_posBuffer, p_tex, p_mesh->m_num_vertex );
+//	}
+//	else
+//	{
+//		// Not Skinned
+//		GQRSetup6( p_mesh->m_vertex_format - 1,		// Pos
+//				   GQR_TYPE_S16,
+//				   0,					// UV
+//				   GQR_TYPE_F32 );
+//
+//		GQRSetup7( 14,		// Normal
+//				   GQR_TYPE_S16,
+//				   0,					// UV
+//				   GQR_TYPE_F32 );
+//
+////		NsMatrix mn;
+////		mn.copy( mp );
+//////		mn.invert(;
+////		mn.setPos( 0.0f, 0.0f, 0.0f );
+////
+////		float shift = (float)(1<<(p_mesh->m_vertex_format-1));
+////		for ( int lp = 0; lp < p_mesh->m_num_vertex; lp++ )
+////		{
+////			NsVector in, tpos, tnorm;
+////			in.x = (((float)p_posBuffer[(lp*3)+0])/shift);
+////			in.y = (((float)p_posBuffer[(lp*3)+1])/shift);
+////			in.z = (((float)p_posBuffer[(lp*3)+2])/shift);
+////			mp.multiply( &in, &tpos );
+////			in.x = (((float)p_normBuffer[(lp*3)+0])/(float)(1<<14));
+////			in.y = (((float)p_normBuffer[(lp*3)+1])/(float)(1<<14));
+////			in.z = (((float)p_normBuffer[(lp*3)+2])/(float)(1<<14));
+////			mn.multiply( &in, &tnorm );
+//////			float z1 = 1.0f / 2000.0f;
+//////			p_tex[(lp*2)+0] = ( ( ( ( ( tpos.x / (tpos.z+(float)gAdd) ) * (float)gScale ) + tnorm.x ) + 1.0f ) * 0.5f ) * (float)gRepeat;
+//////			p_tex[(lp*2)+1] = ( ( ( ( ( tpos.y / (tpos.z+(float)gAdd) ) * (float)gScale )  + tnorm.y ) + 1.0f ) * 0.5f ) * (float)gRepeat;
+////			
+////			p_tex[(lp*2)+0] = ( ( ( ( ( tpos.x / ( tpos.z > 1 ? 1 : tpos.z ) ) + tnorm.x ) * 1.0f ) * 0.5f ) + 0.5f );
+////			p_tex[(lp*2)+1] = ( ( ( ( ( tpos.y / ( tpos.z > 1 ? 1 : tpos.z ) ) + tnorm.y ) * 1.0f ) * 0.5f ) + 0.5f );
+////
+//////			p_tex[(lp*2)+0] = ( ( ( ( tpos.x + ( tnorm.x * tpos.z ) ) * ( 0.5f * tpos.z ) ) * ( 3.0f * tpos.z ) ) + ( 0.5f * tpos.z ) );
+//////			p_tex[(lp*2)+1] = ( ( ( ( tpos.y + ( tnorm.y * tpos.z ) ) * ( 0.5f * tpos.z ) ) * ( 3.0f * tpos.z ) ) + ( 0.5f * tpos.z ) );
+////			
+//////			p_tex[(lp*2)+0] = ( ( tnorm.x ) + 1.0f ) * 0.5f;
+//////			p_tex[(lp*2)+1] = ( ( tnorm.y ) + 1.0f ) * 0.5f;
+////		}
+//
+//		FakeEnvMap( sm, p_posBuffer, p_normBuffer, p_tex, p_mesh->m_num_vertex );
+//
+//		// GQR back to normal.
+//		GQRSetup7( GQR_SCALE_128,		// Pos/Normal
+//				   GQR_TYPE_S16,
+//				   GQR_SCALE_128,
+//				   GQR_TYPE_S16 );
+//		GQRSetup6( GQR_SCALE_256,		// Weights
+//				   GQR_TYPE_U8,
+//				   GQR_SCALE_256,
+//				   GQR_TYPE_U8 );
+//	}
+//	sLastUV = p_tex;
+//}
+
+#ifndef SHORT_VERT
+//static void float_reflect_uvs( sMesh * p_mesh, float * p_tex, float * p_posBuffer, s16 * p_normBuffer, int step )
+//{
+//	if ( sLastUV == p_tex ) return;
+//
+//	// Inverse transform the camera position by the local to world matrix.
+//	NsMatrix mp;
+//
+//	mp.copy( *((NsMatrix*)EngineGlobals.current_uploaded) );
+//	
+//	ROMtx sm;
+//
+//	sm[0][0] = mp.getRightX();
+//	sm[1][0] = mp.getRightY();
+//	sm[2][0] = mp.getRightZ();
+//					   
+//	sm[0][1] = mp.getUpX();
+//	sm[1][1] = mp.getUpY();
+//	sm[2][1] = mp.getUpZ();
+//					   
+//	sm[0][2] = mp.getAtX();
+//	sm[1][2] = mp.getAtY();
+//	sm[2][2] = mp.getAtZ();
+//			     
+//	sm[3][0] = mp.getPosX();
+//	sm[3][1] = mp.getPosY();
+//	sm[3][2] = mp.getPosZ();
+//
+////	GQRSetup6( 0,					// Pos
+////			   GQR_TYPE_F32,
+////			   0,					// UV
+////			   GQR_TYPE_F32 );
+////
+////	GQRSetup7( 14,					// Normal
+////			   GQR_TYPE_S16,
+////			   0,					// UV
+////			   GQR_TYPE_F32 );
+//
+//	FakeEnvMapFloat( sm, p_posBuffer, p_normBuffer, p_tex, p_mesh->m_num_vertex );
+//
+////	// GQR back to normal.
+////	GQRSetup7( GQR_SCALE_128,		// Pos/Normal
+////			   GQR_TYPE_S16,
+////			   GQR_SCALE_128,
+////			   GQR_TYPE_S16 );
+////	GQRSetup6( GQR_SCALE_256,		// Weights
+////			   GQR_TYPE_U8,
+////			   GQR_SCALE_256,
+////			   GQR_TYPE_U8 );
+//	sLastUV = p_tex;
+//}
+#endif	// SHORT_VERT
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void init_render_system( void )
+{
+	pTextureProjectionDetailsTable = new Lst::HashTable< sTextureProjectionDetails >( 8 );
+
+	MTXRotDeg( g_matrix0, 'y', 0.0f );
+	MTXRotDeg( g_matrix90, 'y', 90.0f );
+	MTXRotDeg( g_matrix180, 'y', 180.0f );
+	MTXRotDeg( g_matrix270, 'y', 270.0f );
+}
+
+void set_blend_mode( uint64 mode )
+{
+	// Low 32 bits contain the mode, high 32 bits contain the fixed alpha value.
+	uint32 actual_mode	= (uint32)mode;
+//	uint32 fixed_alpha	= (uint32)( mode >> 32 );
+
+	// Only do something if the blend mode is changing.
+//	if( actual_mode != EngineGlobals.blend_mode_value )
+	{
+		switch( actual_mode )
+		{
+			case vBLEND_MODE_ADD:
+			case vBLEND_MODE_ADD_FIXED:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case vBLEND_MODE_SUBTRACT:
+			case vBLEND_MODE_SUB_FIXED:
+				GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ZERO, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case vBLEND_MODE_BLEND:
+			case vBLEND_MODE_BLEND_FIXED:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case vBLEND_MODE_BRIGHTEN:
+			case vBLEND_MODE_BRIGHTEN_FIXED:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case vBLEND_MODE_MODULATE_FIXED:
+			case vBLEND_MODE_MODULATE:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case vBLEND_MODE_DIFFUSE:
+			default:
+				GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+		}
+	}
+}
+
+
+
+void set_render_state( uint32 type, uint32 state )
+{
+	switch( type )
+	{
+		case RS_ZWRITEENABLE:
+		{
+			if( state > 0 )
+			{
+				if( EngineGlobals.z_write_enabled == FALSE )
+				{
+					GX::SetZMode ( EngineGlobals.z_test_enabled ? GX_TRUE : GX_FALSE, EngineGlobals.z_test_enabled ? GX_LEQUAL : GX_ALWAYS, GX_TRUE );
+					EngineGlobals.z_write_enabled = TRUE;
+				}
+			}
+			else
+			{
+				if( EngineGlobals.z_write_enabled == TRUE )
+				{
+					GX::SetZMode ( EngineGlobals.z_test_enabled ? GX_TRUE : GX_FALSE, EngineGlobals.z_test_enabled ? GX_LEQUAL : GX_ALWAYS, GX_FALSE );
+					EngineGlobals.z_write_enabled = FALSE;
+				}
+			}
+			break;
+		}
+
+		case RS_ZTESTENABLE:
+		{
+			if( state > 0 )
+			{
+				if( EngineGlobals.z_test_enabled == FALSE )
+				{
+					GX::SetZMode ( GX_TRUE, GX_LEQUAL, EngineGlobals.z_write_enabled ? GX_TRUE : GX_FALSE );
+					EngineGlobals.z_test_enabled = TRUE;
+				}
+			}
+			else
+			{
+				if( EngineGlobals.z_test_enabled == TRUE )
+				{
+					GX::SetZMode ( GX_FALSE, GX_ALWAYS, EngineGlobals.z_write_enabled ? GX_TRUE : GX_FALSE );
+					EngineGlobals.z_test_enabled = FALSE;
+				}
+			}
+			break;
+		}
+
+		case RS_ALPHACUTOFF:
+		{
+//			GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//			// Convert from state (where 1 means "render all pixels with alpha 1 or higher") to the D3D
+//			if( state != EngineGlobals.alpha_ref )
+//			{
+//				EngineGlobals.alpha_ref = state;
+//				if( state > 0 )
+//				{
+//					D3DDevice_SetRenderState( D3DRS_ALPHAFUNC,			D3DCMP_GREATEREQUAL );
+//					D3DDevice_SetRenderState( D3DRS_ALPHAREF,			state );
+//					D3DDevice_SetRenderState( D3DRS_ALPHATESTENABLE,	TRUE );
+//				}
+//				else
+//				{
+//					D3DDevice_SetRenderState( D3DRS_ALPHATESTENABLE,	FALSE );
+//				}
+//			}
+			break;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float frustum_sort_mid( Mth::Vector& p_sphere )
+{
+	NsVector	test_in;
+	NsVector	test_mid;
+	
+//	D3DXMATRIX *p_world_transform;
+//	
+//	// Build the composite transform if required.
+//	if( p_bbox_transform )
+//	{
+//		p_world_transform = p_bbox_transform;
+//	}
+//	else
+//	{
+//		p_world_transform = NULL;
+//	}
+	
+	test_in.x	= p_sphere[X];
+	test_in.y	= p_sphere[Y];
+	test_in.z	= p_sphere[Z];
+
+	EngineGlobals.local_to_camera.multiply( &test_in, &test_mid );
+	
+//	if( p_world_transform )
+//	{
+//		D3DXVec3Transform( &test_mid, &test_in, p_world_transform );
+//		test_in.x = test_mid.x;
+//		test_in.y = test_mid.y;
+//		test_in.z = test_mid.z;
+//	}
+//	D3DXVec3Transform( &test_mid, &test_in, &EngineGlobals.view_matrix );
+	return test_mid.z;
+}		
+
+
+
+struct sSortedMeshEntry
+{
+	sMesh				*p_mesh;
+	float				sort;
+	sSortedMeshEntry	*pNext;
+};
+
+
+//static sSortedMeshEntry	sortedMeshArray[1000];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int cmp( const void *p1, const void *p2 )
+{
+	return((sSortedMeshEntry*)p1)->sort < ((sSortedMeshEntry*)p2)->sort ? -1 : ((sSortedMeshEntry*)p1)->sort > ((sSortedMeshEntry*)p2)->sort ? 1 : 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void create_texture_projection_details( sTexture *p_texture, Nx::CNgcModel *p_model, sScene *p_scene )
+{
+	sTextureProjectionDetails *p_details = new sTextureProjectionDetails;
+
+	p_details->p_model		= p_model;
+	p_details->p_scene		= p_scene;
+	p_details->p_texture	= p_texture;
+	
+	// Flag this scene as receiving shadows.
+//	p_scene->m_flags		|= SCENE_FLAG_RECEIVE_SHADOWS;
+	
+	pTextureProjectionDetailsTable->PutItem((uint32)p_texture, p_details );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void destroy_texture_projection_details( sTexture *p_texture )
+{
+	sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->GetItem((uint32)p_texture );
+	if( p_details )
+	{
+		pTextureProjectionDetailsTable->FlushItem((uint32)p_texture );
+		delete p_details;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_texture_projection_camera( sTexture *p_texture, NsVector * p_pos, NsVector * p_at )
+{
+//	NsMatrix view_matrix;
+
+	sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->GetItem((uint32)p_texture );
+	if( p_details )
+	{
+		NsVector	up;
+		// Check for 'straight down' vector.
+		if(( p_pos->x == p_at->x ) && ( p_pos->z == p_at->z ))
+		{
+//			D3DXMatrixLookAtRH( &p_details->view_matrix, p_pos, p_at, &D3DXVECTOR3( 0.0f, 0.0f, 1.0f ));
+
+
+			up.set( 0.0f, 0.0f, 1.0f );
+
+		}
+		else
+		{
+			up.set( 0.0f, 1.0f, 0.0f );
+//			D3DXMatrixLookAtRH( &p_details->view_matrix, p_pos, p_at, &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ));
+		}
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+//		NsVector screen_right;
+//		NsVector screen_up;
+//		screen_right.cross( *p_at, up );
+//		screen_up.cross( screen_right, *p_at );
+//
+//		screen_right.normalize();
+//		screen_up.normalize();
+//
+//		p_details->view_matrix.setRight( &screen_right );
+//		p_details->view_matrix.setUp( &screen_up );
+//		p_details->view_matrix.setAt( p_at );
+//		p_details->view_matrix.setPos( p_pos );
+		p_details->view_matrix.lookAt( p_pos, &up, p_at );
+
+	
+
+//		D3DXMatrixOrthoRH( &p_details->projection_matrix, 96.0f, 96.0f, 1.0f, 512.0f );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//static D3DXMATRIX	world;
+//static D3DXMATRIX	view;
+//static D3DXMATRIX	projection;
+
+void set_camera( Mth::Matrix *p_matrix, Mth::Vector *p_position, float screen_angle, float aspect_ratio, float scale )
+{
+	NsMatrix	m;
+	NsFrame		f;
+
+	EngineGlobals.tx = 2.0f * tanf(screen_angle*8.72664626e-03f);		// tan of half (angle in radians)
+	EngineGlobals.ty = -(EngineGlobals.tx / aspect_ratio);
+	EngineGlobals.near	= -2.0;
+	EngineGlobals.far	= -40000.0;
+
+	current_cam.perspective( EngineGlobals.tx, -EngineGlobals.ty, -EngineGlobals.near, -EngineGlobals.far * 2 );
+
+//	if ( scale > 1.0f )
+//	{
+//		current_cam.SetViewMatrix( 2, 2, -0.999999f );
+//		current_cam.SetViewMatrix( 3, 2, 0.0f );
+//	}
+
+	EngineGlobals.tx = tanf( Mth::DegToRad( screen_angle * 0.5f )); ;		// tan of half (angle in radians)
+	EngineGlobals.ty = -(EngineGlobals.tx / aspect_ratio);
+	EngineGlobals.sx	= 1.0f/sqrtf(1.0f+1.0f/(EngineGlobals.tx*EngineGlobals.tx));
+	EngineGlobals.sy	= 1.0f/sqrtf(1.0f+1.0f/(EngineGlobals.ty*EngineGlobals.ty));
+	EngineGlobals.cx	= 1.0f/sqrtf(1.0f+EngineGlobals.tx*EngineGlobals.tx);
+	EngineGlobals.cy	= 1.0f/sqrtf(1.0f+EngineGlobals.ty*EngineGlobals.ty);
+//	EngineGlobals.near	= -2.0;
+//	EngineGlobals.far	= -40000.0;
+
+	m.setRight( -p_matrix->GetRight().GetX(), -p_matrix->GetRight().GetY(), -p_matrix->GetRight().GetZ() );
+	m.setUp( p_matrix->GetUp().GetX(), p_matrix->GetUp().GetY(), p_matrix->GetUp().GetZ() );
+	m.setAt( -p_matrix->GetAt().GetX(), -p_matrix->GetAt().GetY(), -p_matrix->GetAt().GetZ() );
+	m.setPos( p_position->GetX(), p_position->GetY(), p_position->GetZ() );
+
+	f.setModelMatrix( &m );
+	current_cam.setFrame( &f );
+	current_cam.begin();		// No need to end...
+
+	EngineGlobals.world_to_camera = *current_cam.getCurrent();
+	EngineGlobals.local_to_camera = *current_cam.getCurrent();
+	EngineGlobals.camera = m;
+
+	EngineGlobals.object_pos.x = 0.0f;
+	EngineGlobals.object_pos.y = 0.0f;
+	EngineGlobals.object_pos.z = 0.0f;
+
+	MTXCopy ( (MtxPtr)current_cam.getCurrent(), EngineGlobals.current_uploaded );
+
+
+//	NsVector wscale( 1.0f, 1.0f, 1.0f );	//scale, scale, scale );
+	NsVector wscale( scale, scale, scale );
+	NsMatrix ms;
+	ms.copy( m );
+	ms.scale( &wscale, NsMatrix_Combine_Pre );
+
+	f.setModelMatrix( &ms );
+	current_cam.setFrame( &f );
+	current_cam.end();
+	current_cam.begin();		// No need to end...
+
+//	NsMatrix ms;
+//	ms.setRight( m.getRightX() * scale, m.getRightY() * scale, m.getRightZ() * scale );
+//	ms.setUp( m.getUpX() * scale, m.getUpY() * scale, m.getUpZ() * scale );
+//	ms.setAt( m.getAtX() * scale, m.getAtY() * scale, m.getAtZ() * scale );
+//	ms.setPos( m.getPosX(), m.getPosY(), m.getPosZ() );
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/* Checks a bounding box against the current view frustum		  */
+/* (iBgnoring far clipping),										  */
+/* returns true if any part is visible.							  */
+/* Horribly inefficient at present.								  */
+/*                                                                */
+/******************************************************************/
+bool frustum_check_box( Mtx m, Mth::CBBox& box )
+{
+	Vec	test_in, test_out;
+	
+	uint32	cumulative_projection_space_outcode	= 0xFF;
+	float	min_x = box.GetMin().GetX();
+	float	min_y = box.GetMin().GetY();
+	float	min_z = box.GetMin().GetZ();
+	float	max_x = box.GetMax().GetX();
+	float	max_y = box.GetMax().GetY();
+	float	max_z = box.GetMax().GetZ();
+
+	for( uint32 v = 0; v < 8; ++v )
+	{
+		uint32 projection_space_outcode = 0;
+
+		test_in.x = ( v & 0x04 ) ? max_x : min_x;
+		test_in.y = ( v & 0x02 ) ? max_y : min_y;
+		test_in.z = ( v & 0x01 ) ? max_z : min_z;
+
+		// At this point it's important to check to see whether the point is in postive or negative z-space, since
+		// after the projection transform, both very large camera space z values and camera space z values where z < 0
+		// will give results with z > 1. (Camera space values in the range [0,near] give negative projection space z values).
+		MTXMultVec( m, &test_in, &test_out );
+
+//		if(( -test_mid.z < 0.0f ) && ( !EngineGlobals.is_orthographic ))
+//		{
+//			test_out.x = -test_out.x;
+//			test_out.y = -test_out.y;
+//		}
+
+		if( test_out.x > 1.0f )
+		{
+			projection_space_outcode |= 0x01;
+		}
+		else if( test_out.x < -1.0f )
+		{
+			projection_space_outcode |= 0x02;
+		}
+
+		if( test_out.y > 1.0f )
+		{
+			projection_space_outcode |= 0x04;
+		}
+		else if( test_out.y < -1.0f )
+		{
+			projection_space_outcode |= 0x08;
+		}
+
+		cumulative_projection_space_outcode	&= projection_space_outcode;
+
+		if( cumulative_projection_space_outcode == 0 )
+		{
+			// Early out.
+			return true;
+		}
+	}
+	return false;
+}
+
+bool frustum_check_sphere( Mtx m, Mth::Vector& p_sphere )
+{
+//	Vec in;
+//	Vec out;
+//
+//	in.x = p_sphere[X];
+//	in.y = p_sphere[Y];
+//	in.z = p_sphere[Z];
+//
+////	NsMatrix mat;
+////	mat.copy( EngineGlobals.local_to_camera );
+////	NsVector s( 0.5f, 0.5f, 0.5f );
+////	mat.scale( &s, NsMatrix_Combine_Post );
+////
+////
+////	mat.multiply( &in, &out );
+////	EngineGlobals.local_to_camera.multiply( &in, &out );
+//
+//	MTXMultVec( m, &in, &out );
+//
+//	float x = out.x;
+//	float y = out.y;
+//	float z = out.z;
+//	float R = p_sphere[W];
+//
+//	if ( R < ( z - EngineGlobals.near ) ) return false;
+//	if ( R < ( EngineGlobals.far - z ) ) return false;
+//
+//	float sx_z = EngineGlobals.sx * z;
+//	float cx_x = EngineGlobals.cx * x;
+//	if ( R < ( sx_z - cx_x ) ) return false;
+//	if ( R < ( sx_z + cx_x ) ) return false; 
+//
+//	float sy_z = EngineGlobals.sy * z;
+//	float cy_y = EngineGlobals.cy * y;
+//	if ( R < ( sy_z - cy_y ) ) return false;
+//	if ( R < ( sy_z + cy_y ) ) return false; 
+//
+//	return true;
+
+	Mth::CBBox bbox;
+	Mth::Vector p;
+	
+	p.Set( p_sphere[X] + p_sphere[W], p_sphere[Y], p_sphere[Z] ); bbox.AddPoint( p );
+	p.Set( p_sphere[X] - p_sphere[W], p_sphere[Y], p_sphere[Z] ); bbox.AddPoint( p );
+	p.Set( p_sphere[X] , p_sphere[Y]+ p_sphere[W], p_sphere[Z] ); bbox.AddPoint( p );
+	p.Set( p_sphere[X] , p_sphere[Y]- p_sphere[W], p_sphere[Z] ); bbox.AddPoint( p );
+	p.Set( p_sphere[X] , p_sphere[Y], p_sphere[Z]+ p_sphere[W] ); bbox.AddPoint( p );
+	p.Set( p_sphere[X] , p_sphere[Y], p_sphere[Z]- p_sphere[W] ); bbox.AddPoint( p );
+
+	return frustum_check_box( m, bbox );
+}
+
+/******************************************************************/
+/*                                                                */
+/* Checks a bounding box against the current view frustum		  */
+/* (iBgnoring far clipping),										  */
+/* returns true if any part is visible.							  */
+/* Horribly inefficient at present.								  */
+/*                                                                */
+/******************************************************************/
+bool frustum_check_box( Mth::CBBox& box )
+{
+	int				lp;
+	unsigned int	code;
+	unsigned int	codeAND;
+	f32				rx[8], ry[8], rz[8];
+	f32				p[GX_PROJECTION_SZ];
+	f32				vp[GX_VIEWPORT_SZ];
+	u32				clip_x;
+	u32				clip_y;
+	u32				clip_w;
+	u32				clip_h;
+	float			clip_l;
+	float			clip_t;
+	float			clip_r;
+	float			clip_b;
+	MtxPtr			view;
+	float			minx, miny, minz;
+	float			maxx, maxy, maxz;
+
+	GX::GetProjectionv( p );
+	GX::GetViewportv( vp );
+	GX::GetScissor ( &clip_x, &clip_y, &clip_w, &clip_h );
+	clip_l = (float)clip_x;
+	clip_t = (float)clip_y;
+	clip_r = (float)(clip_x + clip_w);
+	clip_b = (float)(clip_y + clip_h);
+
+	view = (MtxPtr)&EngineGlobals.local_to_camera;
+
+	minx = box.GetMin().GetX();
+	miny = box.GetMin().GetY();
+	minz = box.GetMin().GetZ();
+	maxx = box.GetMax().GetX();
+	maxy = box.GetMax().GetY();
+	maxz = box.GetMax().GetZ();
+	GX::Project ( minx, miny, minz, view, p, vp, &rx[0], &ry[0], &rz[0] );
+	GX::Project ( minx, maxy, minz, view, p, vp, &rx[1], &ry[1], &rz[1] );
+	GX::Project ( maxx, miny, minz, view, p, vp, &rx[2], &ry[2], &rz[2] );
+	GX::Project ( maxx, maxy, minz, view, p, vp, &rx[3], &ry[3], &rz[3] );
+	GX::Project ( minx, miny, maxz, view, p, vp, &rx[4], &ry[4], &rz[4] );
+	GX::Project ( minx, maxy, maxz, view, p, vp, &rx[5], &ry[5], &rz[5] );
+	GX::Project ( maxx, miny, maxz, view, p, vp, &rx[6], &ry[6], &rz[6] );
+	GX::Project ( maxx, maxy, maxz, view, p, vp, &rx[7], &ry[7], &rz[7] );
+
+	// Generate clip code. {page 178, Procedural Elements for Computer Graphics}
+	// 1001|1000|1010
+	//     |    |
+	// ----+----+----
+	// 0001|0000|0010
+	//     |    |
+	// ----+----+----
+	// 0101|0100|0110
+	//     |    |
+	//
+	// Addition: Bit 4 is used for z behind.
+
+	codeAND	= 0x001f;
+	for ( lp = 0; lp < 8; lp++ ) {
+		// Only check x/y if z is valid (if z is invalid, the x/y values will be garbage).
+		if ( rz[lp] > 1.0f   ) {
+			code = (1<<4);
+		} else {
+			code = 0;
+			if ( rx[lp] < clip_l ) code |= (1<<0);
+			if ( rx[lp] > clip_r ) code |= (1<<1);
+			if ( ry[lp] > clip_b ) code |= (1<<2);
+			if ( ry[lp] < clip_t ) code |= (1<<3);
+		}
+		codeAND	&= code;
+		if ( !codeAND ) return true;
+	}
+	if ( !codeAND ) return true;
+	return false;
+//	m_cull.clipCodeAND = codeAND;
+//	// If any bits are set in the AND code, the object is invisible.
+//	if ( codeAND ) {
+//		m_cull.visible = 0;
+//	}
+//	
+//	return !m_cull.visible;		// 0 = not culled, 1 = culled.
+
+
+//
+//	NsVector test_in, test_out;
+//	
+//	uint32	cumulative_projection_space_outcode	= 0xFF;
+//	float	min_x = box.GetMin().GetX();
+//	float	min_y = box.GetMin().GetY();
+//	float	min_z = box.GetMin().GetZ();
+//	float	max_x = box.GetMax().GetX();
+//	float	max_y = box.GetMax().GetY();
+//	float	max_z = box.GetMax().GetZ();
+//
+//	for( uint32 v = 0; v < 8; ++v )
+//	{
+//		uint32 projection_space_outcode = 0;
+//
+//		test_in.x = ( v & 0x04 ) ? max_x : min_x;
+//		test_in.y = ( v & 0x02 ) ? max_y : min_y;
+//		test_in.z = ( v & 0x01 ) ? max_z : min_z;
+//
+//		// At this point it's important to check to see whether the point is in postive or negative z-space, since
+//		// after the projection transform, both very large camera space z values and camera space z values where z < 0
+//		// will give results with z > 1. (Camera space values in the range [0,near] give negative projection space z values).
+//		EngineGlobals.local_to_camera.multiply( &test_in, &test_out );
+////		MTXMultVec( m, &test_in, &test_out );
+//
+////		if(( -test_mid.z < 0.0f ) && ( !EngineGlobals.is_orthographic ))
+////		{
+////			test_out.x = -test_out.x;
+////			test_out.y = -test_out.y;
+////		}
+//
+//		if( test_out.x > 1.0f )
+//		{
+//			projection_space_outcode |= 0x01;
+//		}
+//		else if( test_out.x < -1.0f )
+//		{
+//			projection_space_outcode |= 0x02;
+//		}
+//
+//		if( test_out.y > 1.0f )
+//		{
+//			projection_space_outcode |= 0x04;
+//		}
+//		else if( test_out.y < -1.0f )
+//		{
+//			projection_space_outcode |= 0x08;
+//		}
+//
+//		cumulative_projection_space_outcode	&= projection_space_outcode;
+//
+//		if( cumulative_projection_space_outcode == 0 )
+//		{
+//			// Early out.
+//			return true;
+//		}
+//	}
+//	return false;
+}
+
+/******************************************************************/
+/* Quick determination of if something is visible or not, uses	  */
+/* the previously calculated s and c vectors and the			  */
+/* WorldToCamera transform (note, no attempt is made to ensure	  */
+/* this is the same camera that the object will eventually be	  */
+/* rendered with.												  */
+/******************************************************************/
+bool IsVisible( Mth::Vector &sphere )
+{
+	NsVector in;
+	NsVector out;
+
+	in.x = sphere.GetX();
+	in.y = sphere.GetY();
+	in.z = sphere.GetZ();
+
+	EngineGlobals.world_to_camera.multiply( &in, &out );
+
+	float x = out.x;
+	float y = out.y;
+	float z = out.z;
+	float R = sphere[W];
+	
+	if(	//( -z + R < EngineGlobals.near ) ||
+//	   if ( R < ( -10000.0f/*EngineGlobals.far*/ - z ) ) return false;
+	   ( R < ( z - EngineGlobals.near ) ) ||
+	   ( R < ( EngineGlobals.far - z ) ) ||
+
+	   ( R < EngineGlobals.sy * z + EngineGlobals.cy * y ) ||
+	   ( R < EngineGlobals.sy * z - EngineGlobals.cy * y ) ||
+	   ( R < EngineGlobals.sx * z - EngineGlobals.cx * x ) ||
+	   ( R < EngineGlobals.sx * z + EngineGlobals.cx * x ))
+	{
+		return false;
+	}
+	else
+	{
+		if( TestSphereAgainstOccluders( &sphere ))
+		{
+			// Occluded.
+			return false;
+		}
+		else
+		{
+			return true;
+		}
+	}
+//	if (Rcat( m_matTexProj, matBiasScale );
+
+	// This is just for displaying the frustum lines.
+/*
+    // Convert from homogeneous texmap coords to worldspace
+    D3DXMATRIX matInvTexView, matInvTexProj;
+    D3DXMatrixInverse( &matInvTexView, NULL, &matTexView );
+    D3DXMatrixInverse( &matInvTexProj, NULL, &matTexProj );          
+
+    for( int i = 0; i < 8; i++ )
+    {
+        D3DXVECTOR4 vT( 0.0f, 0.0f, 0.0f, 1.0f );
+        vT.x = (i%2) * ( i&0x2 ? -1.0f : 1.0f );
+        vT.y = (i%2) * ( i&0x4 ? -1.0f : 1.0f );
+        vT.z = (i%2) * ( 1.0f );
+
+        D3DXVec4Transform( &vT, &vT, &matInvTexProj );
+        D3DXVec4Transform( &vT, &vT, &matInvTexView );
+
+        g_FrustumLines[i].x = vT.x / vT.w;
+        g_FrustumLines[i].y = vT.y / vT.w;
+        g_FrustumLines[i].z = vT.z / vT.w;
+    }
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_shadow_targets( void )
+{
+	GX::SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
+//	D3DXMATRIX stored_view_matrix		= EngineGlobals.view_matrix;
+//	D3DXMATRIX stored_projection_matrix	= EngineGlobals.projection_matrix;
+	
+	// Goes through the list of render target textures, rendering to each one in turn.
+	pTextureProjectionDetailsTable->IterateStart();
+	sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->IterateNext();
+//
+//	// Set the basic pixel shader that just copies a single color (in C0) over.
+//	EngineGlobals.pixel_shader_constants[0]		= 1.0f;
+//	EngineGlobals.pixel_shader_constants[1]		= 1.0f;
+//	EngineGlobals.pixel_shader_constants[2]		= 1.0f;
+//	EngineGlobals.pixel_shader_constants[3]		= 1.0f;
+//	EngineGlobals.upload_pixel_shader_constants	= true;
+//	set_pixel_shader( PixelShader3 );
+//	EngineGlobals.pixel_shader_override			= PixelShader3;
+//	
+	// Clear background of shadow.
+//	NsCamera cam;
+//	cam.orthographic( 0, 0, 640, 448 );
+//	cam.begin();
+//
+//	GX::SetZMode ( GX_FALSE, GX_ALWAYS, GX_TRUE );
+//	//NsPrim::box( 0, 0, 256, 256, (GXColor){128,128,128,255} );
+//	float zf = -( EngineGlobals.far + 0.5f );
+//	NsPrim::quad( 0, 0, zf, 640, 0, zf, 640, 448, zf, 0, 448, zf, (GXColor){128,128,128,255} );
+//	cam.end();
+
+	while( p_details )
+	{
+		if( p_details->p_model )
+		{
+			// Set the new render target.
+///			D3DDevice_SetRenderTarget( p_details->p_texture->pD3DSurface, NULL );
+//
+//			D3DDevice_Clear( 0, 0, D3DCLEAR_TARGET, 0, 1.0f, 0 );
+
+			// Set the view and projection transforms.
+//			EngineGlobals.view_matrix		= p_details->view_matrix;
+//			EngineGlobals.projection_matrix	= p_details->projection_matrix;
+//			EngineGlobals.is_orthographic	= true;
+
+			// Render all instances for the CGeom's contained in this model.
+			int num_geoms = p_details->p_model->GetNumGeoms();
+			for( int i = 0; i < num_geoms; ++i )
+			{
+				Nx::CNgcGeom *p_ngc_geom	= static_cast( p_details->p_model->GetGeomByIndex( i ));
+				CInstance *p_instance		= p_ngc_geom->GetInstance();
+				
+				if( p_instance->GetActive())
+				{
+					// Create the camera and attach a frame.
+					NsCamera cam;
+					//NsFrame frame;
+					//cam.setFrame( &frame );
+	
+					// Assume parallel projection in this case.
+					cam.orthographic();
+					cam.orthographic( 0, 0, 256, 256 );
+					cam.setViewWindow( 128, 128 );
+					
+					// Position the camera somewhere near the object, pointing directly at it.
+					float	at[3];
+					float	pos[3];
+	
+					at[0]	= p_instance->GetTransform()->GetPos()[X];
+					at[1]	= p_instance->GetTransform()->GetPos()[Y];
+					at[2]	= p_instance->GetTransform()->GetPos()[Z];
+	
+					pos[0]	= p_instance->GetTransform()->GetPos()[X];
+					pos[1]	= p_instance->GetTransform()->GetPos()[Y] + 64.0f;
+					pos[2]	= p_instance->GetTransform()->GetPos()[Z];
+	
+					g_shadow_object_pos = p_instance->GetTransform()->GetPos();
+					g_shadow_object_pos[Y] += ( 12.0f * 3.0f );		// Amount of leeway...
+
+					// Also need to take account of the view offset.
+					Mth::Vector object_up	= p_instance->GetTransform()->GetUp();
+					at[0]	+= object_up.GetX() * 36.0f;
+					at[1]	+= object_up.GetY() * 36.0f;
+					at[2]	+= object_up.GetZ() * 36.0f;
+					pos[0]	+= object_up.GetX() * 36.0f;
+					pos[1]	+= object_up.GetY() * 36.0f;
+					pos[2]	+= object_up.GetZ() * 36.0f;
+	
+					cam.pos( pos[0], pos[1], pos[2] );
+					cam.up( 1.0f, 0.0f, 0.0f );
+					cam.lookAt( at[0], at[1], at[2] );
+	
+//					EngineGlobals.world_to_camera.identity();
+//					EngineGlobals.world_to_camera.setPos( pos[0], pos[1], pos[2] );
+//					EngineGlobals.world_to_camera.setRight( 1.0f, 0.0f, 0.0f );
+//					EngineGlobals.world_to_camera.setUp( 0.0f, 0.0f, 1.0f );
+//					EngineGlobals.world_to_camera.setAt( 0.0f, -1.0f, 0.0f );
+	
+					cam.begin();
+					MTXCopy ( (MtxPtr)cam.getCurrent(), EngineGlobals.current_uploaded );
+	
+					EngineGlobals.world_to_camera.copy( *cam.getCurrent() );
+					EngineGlobals.shadow_camera.copy( *cam.getCurrent() );
+	
+					// Flag the scene as having the shadow version rendered.
+					p_instance->GetScene()->m_flags |= SCENE_FLAG_RENDERING_SHADOW;
+	
+					// Render the model.
+//					ROMtx bone_mtx[60];
+//					p_instance->Transform( vRENDER_SHADOW_1ST_PASS, bone_mtx, NULL );
+//					p_instance->ClearFlag( NxNgc::CInstance::INSTANCE_FLAG_TRANSFORM_ME );
+					p_instance->Render( vRENDER_SHADOW_1ST_PASS );
+	
+					// Clear the flag the scene as having the shadow version rendered.
+					p_instance->GetScene()->m_flags &= ~SCENE_FLAG_RENDERING_SHADOW;
+				
+					// Flag the scene as self shadowing.
+					p_instance->GetScene()->m_flags |= SCENE_FLAG_SELF_SHADOWS;
+				}
+			}
+		}
+		p_details = pTextureProjectionDetailsTable->IterateNext();
+	}
+//
+//	// Pixel shader override no longer required.
+//	EngineGlobals.pixel_shader_override = 0;
+//	
+//	// Restore the view and projection transforms.
+//	EngineGlobals.view_matrix		= stored_view_matrix;
+//	EngineGlobals.projection_matrix	= stored_projection_matrix;
+//	EngineGlobals.is_orthographic	= false;
+//
+//	// Restore the world transform.
+//	D3DDevice_SetTransform( D3DTS_WORLD, &EngineGlobals.world_matrix );
+//	
+//	// Restore the default render target.
+//	D3DDevice_SetRenderTarget( EngineGlobals.p_RenderSurface, EngineGlobals.p_ZStencilSurface );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_shadow_meshes( sScene *p_scene, int *p_mesh_indices, int num_meshes )
+{
+}
+
+
+void figure_wibble_vc( sScene *p_scene )
+{
+	if ( !p_scene->mp_scene_data ) return;
+
+	// The vertex color wibble flag is placed in pass 0.
+	NxNgc::sMaterialVCWibbleKeyHeader *	p_key_header	= p_scene->mp_vc_wibble; 
+	NxNgc::sMaterialVCWibbleKey *		p_key			= (NxNgc::sMaterialVCWibbleKey *)&p_key_header[1]; 
+
+	int current_time = (int)Tmr::GetTime();
+
+	uint32 * p_col = p_scene->mp_col_pool;
+
+	for ( int wibble = 0; wibble < p_scene->mp_scene_data->m_num_vc_wibbles; wibble++ )
+	{
+		int						num_keys		= p_key_header->m_num_frames;
+		int						phase_shift		= p_key_header->m_phase;
+
+		// Time parameters.
+		int						start_time		= p_key[0].m_time;
+		int						end_time		= p_key[(num_keys - 1)].m_time;
+		int						period			= end_time - start_time;
+		int						time			= start_time + (( current_time + phase_shift ) % period );
+
+		// Locate the keyframe.
+		int key;
+		for( key = num_keys - 1; key >= 0; --key )
+		{
+			if( time >= p_key[key].m_time )
+			{
+				break;
+			}
+		}
+		
+		// Parameter expressing how far we are between between this keyframe and the next.
+		float t = (float)( time - p_key[key].m_time ) / (float)( p_key[key+1].m_time - p_key[key].m_time );
+
+		// Interpolate the color.
+		GXColor	rgba;
+		rgba.r = (uint8)((( 1.0f - t ) * p_key[key].m_color.r ) + ( t * p_key[key + 1].m_color.r ));
+		rgba.g = (uint8)((( 1.0f - t ) * p_key[key].m_color.g ) + ( t * p_key[key + 1].m_color.g ));
+		rgba.b = (uint8)((( 1.0f - t ) * p_key[key].m_color.b ) + ( t * p_key[key + 1].m_color.b ));
+		rgba.a = (uint8)((( 1.0f - t ) * p_key[key].m_color.a ) + ( t * p_key[key + 1].m_color.a ));
+
+		p_col[wibble] = *((uint32*)&rgba);
+
+		// Next wibble sequence.
+		p_key_header	= (NxNgc::sMaterialVCWibbleKeyHeader *)&p_key[num_keys];
+		p_key			= (NxNgc::sMaterialVCWibbleKey *)&p_key_header[1]; 
+	}
+}
+
+			//sMaterialHeader * p_mat = p_mesh->mp_dl->m_material.p_header;
+			//sMaterialPassHeader * p_pass = (sMaterialPassHeader *)&p_mat[1];
+
+// Cached GX commands.
+
+float *	g_p_pos		= NULL;
+char *	g_p_col0	= NULL;
+//char *	g_p_col1	= NULL;
+GXColor g_col0		= (GXColor){0,0,0,0};
+//GXColor g_col1		= (GXColor){0,0,0,0};
+
+static void _reset( void )
+{
+	g_p_pos		= NULL;
+	g_p_col0	= NULL;
+//	g_p_col1	= NULL;
+	g_col0		= (GXColor){0,0,0,0};
+//	g_col1		= (GXColor){0,0,0,0};
+	GX::SetChanMatColor( GX_COLOR0A0, g_col0 );
+//	GX::SetChanMatColor( GX_COLOR0A0, g_col1 );
+}
+
+static void _set_pos( float * p_pos )
+{
+	if ( g_p_pos != p_pos )
+	{
+		GX::SetArray( GX_VA_POS, p_pos, sizeof( float ) * 3 );
+		g_p_pos = p_pos;
+	}
+}
+
+static void _set_col0( char * p_col0 )
+{
+	if ( g_p_col0 != p_col0 )
+	{
+		GX::SetArray( GX_VA_CLR0, p_col0, sizeof( uint32 ) );
+		g_p_col0 = p_col0;
+	}
+}
+
+//static void _set_col1( char * p_col1 )
+//{
+//	if ( g_p_col1 != p_col1 )
+//	{
+//		GX::SetArray( GX_VA_CLR1, p_col1, sizeof( uint32 ) );
+//		g_p_col1 = p_col1;
+//	}
+//}
+
+static void _set_mat0( GXColor col )
+{
+	if ( ( col.r != g_col0.r ) || ( col.g != g_col0.g ) || ( col.b != g_col0.b ) || ( col.a != g_col0.a ) )
+	{
+		g_col0 = col;
+		GX::SetChanMatColor( GX_COLOR0A0, col );
+	}
+}
+
+//static void _set_mat1( GXColor col )
+//{
+//	if ( ( col.r != g_col1.r ) || ( col.g != g_col1.g ) || ( col.b != g_col1.b ) || ( col.a != g_col1.a ) )
+//	{
+//		g_col1 = col;
+//		GX::SetChanMatColor( GX_COLOR1A1, col );
+//	}
+//}
+
+static void submit_mesh( sScene *p_scene, sMesh * p_mesh, s16* p_posNormBuffer, Mth::Matrix * p_bone_xform, Mth::Matrix * p_instance_xform, int * p_array_base, int * p_col_base, int * p_layer, float * p_pm, GXColor * p_base_color, bool * set_base_color, bool * p_world_fog, uint32 flags )
+{
+//	if ( g_skip_correctable && p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6) ) return;
+	if ( (int)p_mesh->mp_dl->m_material.p_header->m_layer_id == g_kill_layer ) return;
+
+	if ( !p_posNormBuffer )
+	{
+		// Deal with cloned pos pools.
+		if ( p_mesh->mp_dl->mp_pos_pool )
+		{
+			_set_pos( p_mesh->mp_dl->mp_pos_pool );
+		}
+		else
+		{
+			// Deal with verts outside of 65535.
+			_set_pos( &p_scene->mp_pos_pool[p_mesh->mp_dl->m_array_base*3] );
+		}
+	}
+
+	// Deal with cloned color pools.
+	if ( p_mesh->mp_dl->mp_col_pool )
+	{
+		_set_col0( &((char*)p_mesh->mp_dl->mp_col_pool)[0] );
+//		if ( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6) )
+//		{
+//			_set_col1( &((char*)p_mesh->mp_dl->mp_col_pool)[2] );
+//		}
+	}
+	else
+	{
+		// Deal with resetting cloned color pools.
+		// Assumes that this isn't a skinned object.
+		_set_col0( &((char*)p_scene->mp_col_pool)[0] );
+//		if ( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6) )
+//		{
+//			_set_col1( &((char*)p_scene->mp_col_pool)[2] );
+//		}
+	}
+
+	// Deal with zbias.
+	if ( (int)p_mesh->mp_dl->m_material.p_header->m_layer_id != *p_layer )
+	{
+		*p_layer = p_mesh->mp_dl->m_material.p_header->m_layer_id;
+
+		float layer = p_mesh->mp_dl->m_material.p_header->m_layer_id;
+
+		float value = p_pm[6] + ( 150.0f * layer ) * p_pm[5];
+
+		GXWGFifo.u8 = GX_LOAD_XF_REG;
+		GXWGFifo.u16 = 0;
+		GXWGFifo.u16 = 0x1025;
+		GXWGFifo.f32 = value;
+	}
+
+
+
+	// Billboard tweaking.
+//	enum BillboardType
+//	{
+//		BBT_NOT_BILLBOARD,
+//		BBT_SCREEN_ALIGNED,
+//		BBT_AXIAL_ALIGNED,
+//	};
+	
+//	if ( p_mesh->mp_mod )
+//	{
+//		Mtx t;
+//		Mtx m;
+//
+////		MTXTrans( m, p_mesh->mp_mod.offset->GetX(), p_mesh->mp_mod.offset->GetY(), p_mesh->mp_mod.offset->GetZ() );
+////
+////		MTXRotAxisDeg( 
+//
+//		float w = p_mesh->mp_mod->offset[W];
+//		Mth::ERot90 rot = *((Mth::ERot90*)&w);
+//
+//		if ( p_mesh->mp_mod->scale[X] > 1.0f ) OSReport( "X Scale > 1.0f: %8.3f\n", p_mesh->mp_mod->scale[X] );
+//		if ( p_mesh->mp_mod->scale[Y] > 1.0f ) OSReport( "Y Scale > 1.0f: %8.3f\n", p_mesh->mp_mod->scale[Y] );
+//		if ( p_mesh->mp_mod->scale[Z] > 1.0f ) OSReport( "Z Scale > 1.0f: %8.3f\n", p_mesh->mp_mod->scale[Z] );
+//
+//		switch ( rot )
+//		{
+//			default:
+//			case Mth::ROT_0:
+//				MTXScaleApply( g_matrix0, t, p_mesh->mp_mod->scale[X], p_mesh->mp_mod->scale[Y], p_mesh->mp_mod->scale[Z] );
+//				break;
+//			case Mth::ROT_90:
+//				MTXScaleApply( g_matrix90, t, p_mesh->mp_mod->scale[X], p_mesh->mp_mod->scale[Y], p_mesh->mp_mod->scale[Z] );
+//				break;
+//			case Mth::ROT_180:
+//				MTXScaleApply( g_matrix180, t, p_mesh->mp_mod->scale[X], p_mesh->mp_mod->scale[Y], p_mesh->mp_mod->scale[Z] );
+//				break;
+//			case Mth::ROT_270:
+//				MTXScaleApply( g_matrix270, t, p_mesh->mp_mod->scale[X], p_mesh->mp_mod->scale[Y], p_mesh->mp_mod->scale[Z] );
+//				break;
+//		}
+//		t[0][3] = p_mesh->mp_mod->offset[X];
+//		t[1][3] = p_mesh->mp_mod->offset[Y];
+//		t[2][3] = p_mesh->mp_mod->offset[Z];
+//		MTXConcat( EngineGlobals.current_uploaded, t, m );
+//
+//		GX::LoadPosMtxImm( m, GX_PNMTX0 );
+//		reload_camera = true;
+//	}
+
+	// Set Fog color if necessary.
+	bool mesh_world_fog = true;
+	if ( p_mesh->mp_dl->m_material.p_header )
+	{
+		switch ( p_mesh->mp_dl->m_material.p_header->m_base_blend )
+		{
+			case vBLEND_MODE_ADD:
+			case vBLEND_MODE_ADD_FIXED:
+			case vBLEND_MODE_SUBTRACT:
+			case vBLEND_MODE_SUB_FIXED:
+				mesh_world_fog = false;
+				break;
+			default:
+				break;
+		}
+	}
+	if ( mesh_world_fog != *p_world_fog )
+	{
+		*p_world_fog = mesh_world_fog;
+		if ( mesh_world_fog )
+		{
+			Nx::CFog::sSetFogColor();
+		}
+		else
+		{
+			GX::SetFogColor( (GXColor){0,0,0,0} );
+		}
+	}
+
+
+	MaterialSubmit( p_mesh, p_scene );
+
+	switch ( p_mesh->mp_dl->mp_object_header->m_billboard_type )
+	{
+		case 2:
+			{
+				NsMatrix*	p_matrix	= &EngineGlobals.world_to_camera;
+				NsVector	at = *p_matrix->getAt();
+//				at.x = -at.x;
+//				at.y = -at.y;
+//				at.z = -at.z;
+
+				NsVector screen_right;
+				NsVector screen_up;
+				NsVector screen_at;
+
+				NsMatrix	root;
+
+				// Create coordinate system based on camera at & pivot axis.
+				screen_up.set( p_mesh->mp_dl->mp_object_header->m_axis[X], p_mesh->mp_dl->mp_object_header->m_axis[Y], p_mesh->mp_dl->mp_object_header->m_axis[Z] );
+//				screen_right.cross( *p_matrix->getAt(), screen_up );
+				screen_right.cross( at, screen_up );
+				screen_at.cross( screen_up, screen_right );
+
+				screen_right.normalize();
+				screen_at.normalize();
+
+				root.setRightX( screen_right.x );
+				root.setRightY( screen_up.x    );
+				root.setRightZ( screen_at.x    );
+
+				root.setUpX( screen_right.y );
+				root.setUpY( screen_up.y    );
+				root.setUpZ( screen_at.y    );
+
+				root.setAtX( screen_right.z );
+				root.setAtY( screen_up.z    );
+				root.setAtZ( screen_at.z    );
+
+//				root.setRightX( screen_right.x );
+//				root.setRightY( screen_right.y );
+//				root.setRightZ( screen_right.z );
+//
+//				root.setUpX( screen_up.x );
+//				root.setUpY( screen_up.y );
+//				root.setUpZ( screen_up.z );
+//
+//				root.setAtX( screen_at.x );
+//				root.setAtY( screen_at.y );
+//				root.setAtZ( screen_at.z );
+
+				root.setPosX( p_mesh->mp_dl->mp_object_header->m_origin[X] );
+				root.setPosY( p_mesh->mp_dl->mp_object_header->m_origin[Y] );
+				root.setPosZ( p_mesh->mp_dl->mp_object_header->m_origin[Z] );
+
+				root.cat( EngineGlobals.world_to_camera, root );
+
+				GX::LoadPosMtxImm( (MtxPtr)&root, GX_PNMTX0 );
+				//GXSetCurrentMtx( GX_PNMTX1 );
+				reload_camera = true;
+			}
+			break;
+		case 1:
+			{
+				NsMatrix*	p_matrix	= &EngineGlobals.world_to_camera;
+
+				NsMatrix	root;
+
+				// Just transpose the matrix for screen-facing.
+				root.setRightX( p_matrix->getRightX() );
+				root.setRightY( p_matrix->getUpX()    );
+				root.setRightZ( p_matrix->getAtX()    );
+
+				root.setUpX( p_matrix->getRightY() );
+				root.setUpY( p_matrix->getUpY()    );
+				root.setUpZ( p_matrix->getAtY()    );
+
+				root.setAtX( p_matrix->getRightZ() );
+				root.setAtY( p_matrix->getUpZ()    );
+				root.setAtZ( p_matrix->getAtZ()    );
+
+				root.setPosX( p_mesh->mp_dl->mp_object_header->m_origin[X] );
+				root.setPosY( p_mesh->mp_dl->mp_object_header->m_origin[Y] );
+				root.setPosZ( p_mesh->mp_dl->mp_object_header->m_origin[Z] );
+
+				root.cat( EngineGlobals.world_to_camera, root );
+
+				GX::LoadPosMtxImm( (MtxPtr)&root, GX_PNMTX0 );
+				//GXSetCurrentMtx( GX_PNMTX1 );
+				reload_camera = true;
+			}
+			break;
+		default:
+			// Don't do anything - not a billboard.
+			//GXSetCurrentMtx( GX_PNMTX0 );
+			break;
+	}
+
+//	// Create array of bone matrices if required.
+//	NsMatrix	bone_xform[80];
+//	if ( p_scene->m_numHierarchyObjects && p_bone_xform )
+//	{
+//		for ( int bone = 0; bone < num_bones; bone++ )
+//		{
+//			Mth::Matrix temp = p_bone_xform[bone] * *p_instance_xform;
+//			NsMatrix	local;
+//
+//			local.setRightX( temp.GetRight()[X] );
+//			local.setRightY( temp.GetUp()[X] );
+//			local.setRightZ( temp.GetAt()[X] );
+//
+//			local.setUpX( temp.GetRight()[Y] ); 
+//			local.setUpY( temp.GetUp()[Y] );    
+//			local.setUpZ( temp.GetAt()[Y] );    
+//
+//			local.setAtX( temp.GetRight()[Z] ); 
+//			local.setAtY( temp.GetUp()[Z] );    
+//			local.setAtZ( temp.GetAt()[Z] );    
+//
+//			local.setPosX( temp.GetPos()[X] );
+//			local.setPosY( temp.GetPos()[Y] );
+//			local.setPosZ( temp.GetPos()[Z] );
+//
+//			bone_xform[bone].cat( EngineGlobals.world_to_camera, local );
+//		}
+//	}
+
+
+	if ( p_scene->m_numHierarchyObjects && p_bone_xform )
+	{
+		NsMatrix	bone_xform;
+		Mth::Matrix temp = p_bone_xform[p_mesh->m_bone_idx] * *p_instance_xform;
+		NsMatrix	local;
+
+		local.setRightX( temp.GetRight()[X] );
+		local.setRightY( temp.GetUp()[X] );
+		local.setRightZ( temp.GetAt()[X] );
+
+		local.setUpX( temp.GetRight()[Y] ); 
+		local.setUpY( temp.GetUp()[Y] );    
+		local.setUpZ( temp.GetAt()[Y] );    
+
+		local.setAtX( temp.GetRight()[Z] ); 
+		local.setAtY( temp.GetUp()[Z] );    
+		local.setAtZ( temp.GetAt()[Z] );    
+
+		local.setPosX( temp.GetPos()[X] );
+		local.setPosY( temp.GetPos()[Y] );
+		local.setPosZ( temp.GetPos()[Z] );
+
+		bone_xform.cat( EngineGlobals.world_to_camera, local );
+
+		GX::LoadPosMtxImm( (MtxPtr)&bone_xform, GX_PNMTX0 );
+		reload_camera = true;
+
+		GX::LoadNrmMtxImm((MtxPtr)&bone_xform, GX_PNMTX0);
+
+	}
+
+	if ( /*p_scene->mp_scene_data->m_num_nrm ||*/ p_posNormBuffer || ( p_scene->m_numHierarchyObjects && p_bone_xform ) )
+	{
+//		GXLightObj light_obj[3];
+//
+//		// Always set ambient & diffuse colors.
+//		GX::SetChanAmbColor( GX_COLOR0A0, NxNgc::EngineGlobals.ambient_light_color );
+////		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+////				GX::SetChanAmbColor( GX_COLOR1A1, NxNgc::EngineGlobals.ambient_light_color );
+//	
+//		GX::InitLightColor( &light_obj[0], NxNgc::EngineGlobals.diffuse_light_color[0] );
+//		GX::InitLightPos( &light_obj[0], NxNgc::EngineGlobals.light_x[0], NxNgc::EngineGlobals.light_y[0], NxNgc::EngineGlobals.light_z[0] );
+//		GX::LoadLightObjImm( &light_obj[0], GX_LIGHT0 );
+//	
+//		GX::InitLightColor( &light_obj[1], NxNgc::EngineGlobals.diffuse_light_color[1] );
+//		GX::InitLightPos( &light_obj[1], NxNgc::EngineGlobals.light_x[1], NxNgc::EngineGlobals.light_y[1], NxNgc::EngineGlobals.light_z[1] );
+//		GX::LoadLightObjImm( &light_obj[1], GX_LIGHT1 );
+//
+//		// Set channel control for diffuse.
+//		GX::SetChanCtrl( GX_COLOR0, GX_ENABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT0|GX_LIGHT1, GX_DF_CLAMP, GX_AF_NONE );
+//		GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+//
+////		if ( p_mesh->mp_dl->m_material.p_header->m_shininess > 0.0f )
+////		{
+////			// Set channel control for specular.
+////			Vec  ldir;
+////
+////			ldir.x = 0.0f;	//NxNgc::EngineGlobals.light_x[0] * ( 1.0f / 200000.0f );
+////			ldir.y = 0.0f;  //NxNgc::EngineGlobals.light_y[0] * ( 1.0f / 200000.0f );
+////			ldir.z = -1.0f;  //NxNgc::EngineGlobals.light_z[0] * ( 1.0f / 200000.0f );
+////
+////			GX::InitSpecularDirv( &light_obj[2], &ldir );
+////			GX::InitLightShininess( &light_obj[2], 8.0f );
+////			GX::InitLightColor( &light_obj[2], (GXColor){255,255,255,255} );
+////			GX::LoadLightObjImm( &light_obj[2], GX_LIGHT2 );
+////
+////			GX::SetChanCtrl( GX_COLOR1, GX_ENABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT2, GX_DF_NONE, GX_AF_SPEC );
+////			GX::SetChanCtrl( GX_ALPHA1, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+////		}
+//
+	}
+	else
+	{
+		if ( !*set_base_color )
+		{
+			GX::SetChanCtrl( GX_COLOR0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+			GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+////			if ( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6) )
+//			{
+//				GX::SetChanCtrl( GX_COLOR1, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//				GX::SetChanCtrl( GX_ALPHA1, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+//			}
+			*set_base_color = true;
+		}
+//		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+//		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){32,32,32,255} );
+		GXColor col;
+		col.r = p_mesh->m_base_color.r;
+		col.g = p_mesh->m_base_color.g;
+		col.b = p_mesh->m_base_color.b;
+		col.a = 255;
+
+////		if ( !(p_mesh->mp_dl->m_material.p_header->m_flags & (1<<7)) )
+//		if ( !(flags & vRENDER_LIT) )
+//		{
+//			// Not sure why we need to force this to be set...
+////			_set_mat0( col );
+////			GX::SetChanMatColor( GX_COLOR0A0, col );
+////			if ( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6) )
+//			{
+////				_set_mat1( col );
+////				GX::SetChanMatColor( GX_COLOR1A1, col );
+//			}
+////			*((uint32*)p_base_color) = *((uint32*)&col);
+//
+//			col.r = col.r >> 1;
+//			col.g = col.g >> 1;
+//			col.b = col.b >> 1;
+//		}
+
+//		if ( *((uint32*)p_base_color) !=  *((uint32*)&col) )
+		{
+//			p_mesh->m_base_color.a = 255;
+//			GX::SetChanMatColor( GX_COLOR0A0, col );
+
+			_set_mat0( col );
+//			if ( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6) )
+//			{
+////				GX::SetChanMatColor( GX_COLOR1A1, col );
+//				_set_mat1( (GXColor){col.b,col.a,0,0} );
+//			}
+//			*((uint32*)p_base_color) = *((uint32*)&col);
+		}
+
+//		GX::SetChanCtrl( GX_COLOR1A1, GX_DISABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//		GX::SetChanMatColor( GX_COLOR1A1, (GXColor){128,128,128,255} );
+//		GX::SetChanAmbColor( GX_COLOR1A1, (GXColor){128,128,128,255} );
+//		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){64,64,64,128} );
+//		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){64,64,64,128} );
+	}
+
+	// Major hack!!!
+//   if ( p_posNormBuffer )
+//	{
+//		unsigned char * p8 = (unsigned char *)&p_mesh->mp_dl[1];
+//		for ( uint lp = 0; lp < p_mesh->mp_dl->m_size; lp++ ) GXWGFifo.u8 = *p8++;
+//	}
+//	else
+//	{
+	/*if ( !p_mesh->mp_dl->mp_pos_pool )*/ GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size );
+//	}
+
+	if ( reload_camera )
+	{
+		GX::LoadPosMtxImm( EngineGlobals.current_uploaded, GX_PNMTX0 );
+//		GX::LoadPosMtxImm( (Mtx)current_cam.getCurrent(), GX_PNMTX0 );
+		reload_camera = false;
+	}
+
+//	// Reload color texture if necessary.
+//	if ( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<5) )
+//	{
+//		// Make sure color map is uploaded.
+//		GX::UploadTexture(	colorMap,
+//							64,	//COLOR_MAP_SIZE,
+//							64, //COLOR_MAP_SIZE,
+//							GX_TF_RGBA8,
+//							GX_CLAMP,
+//							GX_CLAMP,
+//							GX_FALSE,
+//							GX_LINEAR,
+//							GX_LINEAR,
+//							0.0f,
+//							0.0f,
+//							0.0f,
+//							GX_FALSE,
+//							GX_TRUE,
+//							GX_ANISO_1,
+//							GX_TEXMAP7 ); 
+//	}
+}
+
+void render_scene( sScene *p_scene, s16* p_posNormBuffer, uint32 flags, Mth::Matrix * p_bone_xform, Mth::Matrix * p_instance_xform, int num_bones )
+{
+	// Don't render dictionary scenes.
+	if( p_scene->m_is_dictionary )
+	{
+		return;
+	}
+	
+//	// This forces the texture to go into the unusable region.
+//	GX::UploadTexture(	colorMap,
+//						64,
+//						64,
+//						GX_TF_RGBA8,
+//						GX_CLAMP,
+//						GX_CLAMP,
+//						GX_FALSE,
+//						GX_LINEAR,
+//						GX_LINEAR,
+//						0.0f,
+//						0.0f,
+//						0.0f,
+//						GX_FALSE,
+//						GX_TRUE,
+//						GX_ANISO_1,
+//						GX_TEXMAP7 ); 
+
+	sLastUV = NULL;
+
+	first_mat = true;
+
+	p_last_material = NULL;
+	last_correct = false;
+//	if ( p_scene->m_numHierarchyObjects == 0 )
+//	{
+//		// Not hierarchical, set to matrix 0.
+//		GXSetCurrentMtx( GX_PNMTX0 );
+//	}
+
+	ResetMaterialChange();
+//	int meshes_rendered		= 0;
+
+	figure_wibble_vc( p_scene );
+
+#define MAX_SHADOW_MESHES 512
+
+//	int shadow_meshes = 0;
+//	sMesh *pShadow[MAX_SHADOW_MESHES];
+
+//	sMesh	*visible_mesh_array[2000];
+//	int		visible_mesh_array_index = 0;
+
+	// Increment the scene framestamp.
+//_scene->m_framestamp++;
+
+	// Set up the texture generation matrix here.
+//	Mtx light;
+//	MTXLightOrtho( light, -128.0f, 128.0f, -128.0f, 128.0f, 0.5f, 0.5f, 0.5f, 0.5f );
+//	MTXConcat( light, (Mtx)&EngineGlobals.shadow_camera, light );
+//#ifndef SHORT_VERT
+//	GX::LoadTexMtxImm( light, GX_TEXMTX7, GX_MTX3x4 );
+//#endif		// SHORT_VERT
+
+	if ( p_posNormBuffer )
+	{
+		GX::SetArray( GX_VA_POS,  p_posNormBuffer, sizeof( uint16 ) * 6 );
+		GX::SetArray( GX_VA_NRM,  &p_posNormBuffer[3], sizeof( uint16 ) * 6 );
+	}
+	else
+	{
+		GX::SetArray( GX_VA_POS,  p_scene->mp_pos_pool, sizeof( float ) * 3 );
+		GX::SetArray( GX_VA_NRM,  p_scene->mp_nrm_pool, sizeof( uint16 ) * 3 );
+	}
+	int array_base = 0;
+	int col_base = 2;
+	int layer = -1;
+	bool set_chan = false;
+	GXColor base_color = { 0, 0, 0, 0 };
+	bool world_fog = true;
+	Nx::CFog::sSetFogColor();
+	GX::SetChanMatColor( GX_COLOR0A0, base_color );
+	GX::SetChanMatColor( GX_COLOR1A1, base_color );
+	f32 pm[GX_PROJECTION_SZ];
+	GX::GetProjectionv( pm );
+
+
+//	if ( flags & vRENDER_NEW_TEST )
+	{
+		// Pool setup.
+		GX::SetArray( GX_VA_CLR0, &((char*)p_scene->mp_col_pool)[0], sizeof( uint32 ) );
+		GX::SetArray( GX_VA_CLR1, &((char*)p_scene->mp_col_pool)[2], sizeof( uint32 ) );
+
+//		GX::SetArray( GX_VA_CLR1, p_scene->mp_col_pool, sizeof( uint32 ) );
+//		GX::SetArray( GX_VA_TEX0, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+//
+//		GX::SetArray( GX_VA_TEX1, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+//		GX::SetArray( GX_VA_TEX2, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+//		GX::SetArray( GX_VA_TEX3, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+
+		GX::SetArray( GX_VA_TEX0, p_scene->mp_tex_pool, sizeof( uint16 ) * 2 );
+		GX::SetArray( GX_VA_TEX1, p_scene->mp_tex_pool, sizeof( uint16 ) * 2 );
+		GX::SetArray( GX_VA_TEX2, p_scene->mp_tex_pool, sizeof( uint16 ) * 2 );
+		GX::SetArray( GX_VA_TEX3, p_scene->mp_tex_pool, sizeof( uint16 ) * 2 );
+
+//		GXSetArray( GX_VA_TEX4, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+//		GXSetArray( GX_VA_TEX5, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+//		GXSetArray( GX_VA_TEX6, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+//		GXSetArray( GX_VA_TEX7, p_scene->mp_tex_pool, sizeof( float ) * 2 );
+		//GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0 );
+	}
+
+
+
+
+
+	if ( g_object == g_view_object ) g_view_scene = p_scene;
+
+	// Render all meshes.
+#	ifdef __USE_PROFILER__
+						Sys::VUProfiler->PushContext( 128,0,0 );
+#	endif		// __USE_PROFILER__
+
+//						{
+//							GX::SetFog( GX_FOG_NONE, 0.0f, 0.0f, 0.0f, 0.0f, (GXColor){0,0,0,0} );
+//							GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0 );
+//
+//							GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST,
+//												 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//
+//							GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST,
+//													 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+//													 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//
+//							GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+//
+//							GX::SetTevKSel( GX_TEVSTAGE0, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A );
+//							GX::SetTevKColor( GX_KCOLOR0, (GXColor){8,8,8,255} );
+//							GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+//							GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255});
+//							GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255});
+//
+//							GX::SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
+//							GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE ); 
+//						}
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Shadow Target.
+	///////////////////////////////////////////////////////////////////////////////////////////
+
+	if ( flags & vRENDER_SHADOW_1ST_PASS )
+	{
+//		GX::SetFog( GX_FOG_NONE, 0.0f, 0.0f, 0.0f, 0.0f, (GXColor){0,0,0,0} );
+		GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0 );
+
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST,
+							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );		// Replace
+
+		GX::SetTevKSel( GX_TEVSTAGE0, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A );
+		GX::SetTevKColor( GX_KCOLOR0, (GXColor){64,64,64,255} );
+		GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255});
+		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255});
+		GX::SetChanMatColor( GX_COLOR1A1, (GXColor){255,255,255,255});
+		GX::SetChanAmbColor( GX_COLOR1A1, (GXColor){255,255,255,255});
+		base_color = (GXColor){ 255, 255, 255, 255 };
+
+//		GX::SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
+		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE ); 
+
+		for ( int mesh = 0; mesh < p_scene->m_num_filled_meshes; mesh++ )
+		{
+			sMesh * p_mesh = p_scene->mpp_mesh_list[mesh];
+
+			if ( ( p_mesh->m_flags & sMesh::MESH_FLAG_VISIBLE ) &&
+				 ( ( g_object != g_view_object ) || ( &p_scene->mp_material_header[g_material] != p_mesh->mp_dl->m_material.p_header ) ) )
+			{
+				GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size );
+
+
+//				submit_mesh( p_scene, p_mesh, p_posNormBuffer, p_bone_xform, p_instance_xform, &array_base, &col_base, &layer, pm, &world_fog, flags );
+			}
+		}
+	}
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Opaque meshes.
+	///////////////////////////////////////////////////////////////////////////////////////////
+
+	_reset();
+	if ( flags & vRENDER_OPAQUE )
+	{
+//		GX::SetZMode( GX_TRUE, GX_LEQUAL, GX_TRUE );
+		for ( int mesh = 0; mesh < p_scene->m_num_opaque_meshes; mesh++ )
+		{
+			sMesh * p_mesh = p_scene->mpp_mesh_list[mesh];
+
+			if ( ( p_mesh->m_flags & sMesh::MESH_FLAG_VISIBLE ) &&
+				 ( ( g_object != g_view_object ) || ( &p_scene->mp_material_header[g_material] != p_mesh->mp_dl->m_material.p_header ) ) )
+			{
+				submit_mesh( p_scene, p_mesh, p_posNormBuffer, p_bone_xform, p_instance_xform, &array_base, &col_base, &layer, pm, &base_color, &set_chan, &world_fog, flags );
+			}
+		}
+	}
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Semitransparent meshes.
+	///////////////////////////////////////////////////////////////////////////////////////////
+
+	if ( flags & vRENDER_SEMITRANSPARENT )
+	{
+//		GX::SetZMode( GX_TRUE, GX_LEQUAL, GX_TRUE );
+		int opaque		= p_scene->m_num_opaque_meshes;
+		int presemi		= opaque + p_scene->m_num_pre_semitrans_meshes;
+		int dynsemi		= presemi + p_scene->m_num_dynamic_semitrans_meshes;
+		int postsemi	= dynsemi + p_scene->m_num_post_semitrans_meshes;
+
+		// Pre meshes.
+		_reset();
+		for ( int mesh = opaque; mesh < presemi; mesh++ )
+		{
+			sMesh * p_mesh = p_scene->mpp_mesh_list[mesh];
+
+			if ( ( p_mesh->m_flags & sMesh::MESH_FLAG_VISIBLE ) &&
+				 ( ( g_object != g_view_object ) || ( &p_scene->mp_material_header[g_material] != p_mesh->mp_dl->m_material.p_header ) ) )
+			{
+				submit_mesh( p_scene, p_mesh, p_posNormBuffer, p_bone_xform, p_instance_xform, &array_base, &col_base, &layer, pm, &base_color, &set_chan, &world_fog, flags );
+			}
+		}
+
+		// Dynamic meshes.
+		sSortedMeshEntry sortedMeshArray[1000];
+		int next_sorted_mesh_entry	= 0;
+
+		for ( int mesh = presemi; mesh < dynsemi; mesh++ )
+		{
+			sMesh * p_mesh = p_scene->mpp_mesh_list[mesh];
+
+			if ( ( p_mesh->m_flags & sMesh::MESH_FLAG_VISIBLE ) &&
+				 ( ( g_object != g_view_object ) || ( &p_scene->mp_material_header[g_material] != p_mesh->mp_dl->m_material.p_header ) ) )
+			{
+				sortedMeshArray[next_sorted_mesh_entry].p_mesh	= p_mesh;
+				sortedMeshArray[next_sorted_mesh_entry].sort	= frustum_sort_mid( p_mesh->mp_dl->m_sphere );
+				++next_sorted_mesh_entry;
+			}
+		}
+		qsort( sortedMeshArray, next_sorted_mesh_entry, sizeof( sSortedMeshEntry ), cmp );
+
+		_reset();
+		for( int m = 0; m < next_sorted_mesh_entry; ++m )
+		{
+			submit_mesh( p_scene, sortedMeshArray[m].p_mesh, p_posNormBuffer, p_bone_xform, p_instance_xform, &array_base, &col_base, &layer, pm, &base_color, &set_chan, &world_fog, flags );
+		}
+
+		// Post meshes.
+		_reset();
+		for ( int mesh = dynsemi; mesh < postsemi; mesh++ )
+		{
+			sMesh * p_mesh = p_scene->mpp_mesh_list[mesh];
+
+			if ( ( p_mesh->m_flags & sMesh::MESH_FLAG_VISIBLE ) &&
+				 ( ( g_object != g_view_object ) || ( &p_scene->mp_material_header[g_material] != p_mesh->mp_dl->m_material.p_header ) ) )
+			{
+				submit_mesh( p_scene, p_mesh, p_posNormBuffer, p_bone_xform, p_instance_xform, &array_base, &col_base, &layer, pm, &base_color, &set_chan, &world_fog, flags );
+			}
+		}
+	}
+
+
+
+	///////////////////////////////////////////////////////////////////////////////////////////
+	// Shadow Mesh.
+	///////////////////////////////////////////////////////////////////////////////////////////
+
+	if ( flags & vRENDER_SHADOW_2ND_PASS )
+	{
+		Nx::CFog::sEnableFog( false );
+		Mtx light;
+		MTXLightOrtho( light, -128.0f, 128.0f, -128.0f, 128.0f, 0.5f, 0.5f, 0.5f, 0.5f );
+		MTXConcat( light, (Mtx)&EngineGlobals.shadow_camera, light );
+		GX::LoadTexMtxImm( light, GX_TEXMTX7, GX_MTX3x4 );
+		GX::SetZMode( GX_TRUE, GX_LEQUAL, GX_FALSE );
+
+		GX::SetZCompLoc( GX_TRUE );
+		GX::UploadTexture(  shadowTextureData,
+							SHADOW_TEXTURE_SIZE,
+							SHADOW_TEXTURE_SIZE,
+							GX_TF_I4,
+							GX_CLAMP,
+							GX_CLAMP,
+							GX_FALSE,
+							GX_LINEAR,
+							GX_LINEAR,
+							0.0f,
+							0.0f,
+							0.0f,
+							GX_FALSE,
+							GX_TRUE,
+							GX_ANISO_1,
+							GX_TEXMAP0 ); 
+		GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE );
+
+		GX::UploadPalette( shadowPalette,
+						   GX_TL_RGB5A3,
+						   GX_TLUT_16,
+						   GX_TEXMAP0 );
+
+		GX::SetTexChanTevIndCull( 1, 0, 1, 0, GX_CULL_NONE );
+		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_POS, GX_FALSE, GX_PTIDENTITY );
+
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+		GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+//		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );		// Replace
+		GX::SetBlendMode( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_TEXMTX7, GX_TEXMTX7, GX_TEXMTX7, GX_TEXMTX7 );
+
+		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ONE, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ZERO,
+							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+
+
+//out_reg = (d (op) ((1.0 - c)*a + c*b) + bias) * scale;
+
+		GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+//		visible_mesh_array[visible_mesh_array_index++] = p_scene->m_semitransparent_meshes[e];
+//		for ( int mesh = 0; mesh < p_scene->m_num_filled_meshes; mesh++ )
+
+		layer = 5;		// Force subsequent materials to reset the layer.
+
+//		float value = pm[6] + ( 80.0f * p_mesh->mp_dl->m_material.p_header->m_layer_id ) * pm[5];
+		float value = pm[6] + ( 150.0f * 5 ) * pm[5];
+
+		GXWGFifo.u8 = GX_LOAD_XF_REG;
+		GXWGFifo.u16 = 0;
+		GXWGFifo.u16 = 0x1025;
+		GXWGFifo.f32 = value;
+
+//		for ( int mesh = 0; mesh < ( p_scene->m_num_opaque_meshes + p_scene->m_num_pre_semitrans_meshes ); mesh++ )
+		for ( int mesh = 0; mesh < ( p_scene->m_num_filled_meshes ); mesh++ )
+
+		{
+			sMesh * p_mesh = p_scene->mpp_mesh_list[mesh];
+
+//			if( frustum_check_sphere( light, p_scene->m_opaque_meshes[e]->m_sphere ))
+			float by;
+
+//			if ( p_mesh->mp_mod )
+//			{
+////				by = ( p_mesh->m_bottom_y * p_mesh->mp_mod->scale[Y] ) + p_mesh->mp_mod->offset[Y];
+//				by = p_mesh->mp_mod->offset[Y];
+//			}
+//			else
+//			{
+				by = p_mesh->m_bottom_y;
+//			}
+
+			if ( ( p_mesh->m_flags & sMesh::MESH_FLAG_VISIBLE ) &&
+				 frustum_check_sphere( light, p_mesh->mp_dl->m_sphere ) &&
+				 ( g_shadow_object_pos[Y] > by ) &&
+				 !( p_mesh->m_flags & sMesh::MESH_FLAG_NO_SKATER_SHADOW ) )
+			{
+
+//				if ( (int)p_mesh->mp_dl->m_material.p_header->m_layer_id == g_kill_layer ) return;
+
+				// Deal with cloned pos pools.
+				if ( p_mesh->mp_dl->mp_pos_pool )
+				{
+					array_base = -1;
+					GX::SetArray( GX_VA_POS, p_mesh->mp_dl->mp_pos_pool, sizeof( float ) * 3 );
+				}
+				else
+				{
+					// Deal with verts outside of 65535.
+					if ( p_mesh->mp_dl->m_array_base != (uint32)array_base )
+					{
+						array_base = p_mesh->mp_dl->m_array_base;
+
+						// Assumes that this isn't a skinned object.
+						GX::SetArray( GX_VA_POS, &p_scene->mp_pos_pool[p_mesh->mp_dl->m_array_base*3], sizeof( float ) * 3 );
+					}
+				}
+
+				GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size );
+
+
+//				submit_mesh( p_scene, p_mesh, p_posNormBuffer, p_bone_xform, p_instance_xform, &array_base, &col_base, &layer, pm, &world_fog, flags );
+			}
+		}
+		Nx::CFog::sEnableFog( true );
+		world_fog = true;
+	}
+
+
+
+
+
+
+
+
+//	// Render opaque meshes.
+//	if( (flags & vRENDER_OPAQUE) && !(flags & vRENDER_SHADOW_1ST_PASS) )
+//	{
+//		for( int e = 0; e < p_scene->m_num_opaque_entries; ++e )
+//		{
+//			if ( p_scene->m_opaque_meshes[e]->m_flags & sMesh::MESH_FLAG_VISIBLE )
+//			{
+//				sMesh * p_mesh = p_scene->m_opaque_meshes[e];
+//		
+////				set_render_state( RS_ZWRITEENABLE, p_mesh->m_zwrite );
+//
+//				if ( flags & vRENDER_NEW_TEST )
+//				{
+//					// Textured.
+//					// First pass material setup.
+//					sMaterialHeader * p_mat = p_mesh->mp_dl->m_material.p_header;
+//					sMaterialPassHeader * p_pass = (sMaterialPassHeader *)&p_mat[1];
+//
+//					if ( p_pass[0].m_texture.p_data )
+//					{
+//						if ( first_mat || !one_mat )
+//						{
+//							first_mat = false;
+//
+//							MaterialSubmit( p_mesh );
+//						}
+//						GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//					}
+//					else
+//					{
+//						// Untextured.
+//						if ( !(p_mesh->mp_dl->m_flags & 0x00200000) )
+//						{
+//							GXSetNumTevStages( 1 );
+//							GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//							GXSetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//							GXSetNumTexGens( 0 );
+//							GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//							GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+////							GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//							GXSetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//							GXSetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//							GXSetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+//							GXSetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//							GXSetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+////							GXSetChanMatColor( GX_COLOR0A0, p_pass->m_color );
+//
+//							GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//						}
+//					}
+//				}
+//				else
+//				{
+//					multi_mesh( p_mesh, p_posNormBuffer );
+//				}
+//
+//				// Now draw the opaque meshes with shadow mapped on them.
+//				if( p_scene->m_flags & SCENE_FLAG_RECEIVE_SHADOWS )
+//				{
+//					if( !( p_mesh->m_flags & sMesh::MESH_FLAG_NO_SKATER_SHADOW ) )
+//					{
+//						if( frustum_check_sphere( light, p_scene->m_opaque_meshes[e]->m_sphere ))
+//						{
+//							//if( frustum_check_box( light, p_scene->m_opaque_meshes[e]->m_bbox ))
+//							{
+//								if ( shadow_meshes <  MAX_SHADOW_MESHES )
+//								{
+//									pShadow[shadow_meshes] = p_mesh;
+//									shadow_meshes++;
+//								}
+//							}
+//						}
+//					}
+//				}
+//			}
+//		}			
+//	}			
+//
+//// Render semi-transparent meshes.
+//	if( (flags & vRENDER_SEMITRANSPARENT) && !(flags & vRENDER_SHADOW_1ST_PASS) )
+//	{
+//		int e = 0;
+//		int next_sorted_mesh_entry = 0;
+//		
+//		// Semitransparent rendering is done in three stages.
+//		// The first stage is meshes in the list up to the point where dynamic sorting starts.
+//		// The second stage is meshes in the list which use dynamic sorting.
+//		// The third stage is meshes in the list beyond the point where dynamic sorting ends.
+//		for( ; e < p_scene->m_first_dynamic_sort_entry; ++e )
+//		{
+//			if ( p_scene->m_semitransparent_meshes[e]->m_flags & sMesh::MESH_FLAG_VISIBLE )
+//			{
+//				sMesh * p_mesh = p_scene->m_semitransparent_meshes[e];
+//		
+////				set_render_state( RS_ZWRITEENABLE, p_mesh->m_zwrite );
+//
+//				++meshes_rendered;
+//
+//				if ( flags & vRENDER_NEW_TEST )
+//				{
+//					// Textured.
+//					// First pass material setup.
+//					sMaterialHeader * p_mat = p_mesh->mp_dl->m_material.p_header;
+//					sMaterialPassHeader * p_pass = (sMaterialPassHeader *)&p_mat[1];
+//
+//					if ( p_pass[0].m_texture.p_data )
+//					{
+//						if ( first_mat || !one_mat )
+//						{
+//							first_mat = false;
+//
+//							MaterialSubmit( p_mesh );
+//						}
+//
+//						GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//					}
+//					else
+//					{
+//						// Untextured.
+//						if ( !(p_mesh->mp_dl->m_flags & 0x00200000) )
+//						{
+//							GXSetNumTevStages( 1 );
+//							GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//							GXSetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//							GXSetNumTexGens( 0 );
+//							GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//							GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//							GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//							GXSetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//							GXSetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//							GXSetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+//							GXSetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//							GXSetChanMatColor( GX_COLOR0A0, p_pass->m_color );
+//
+//							GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//						}
+//					}
+//				}
+//				else
+//				{
+//					multi_mesh( p_mesh, p_posNormBuffer );
+//				}
+//			}
+//		}
+//
+//		if( p_scene->m_num_dynamic_sort_entries > 0 )
+//		{
+//			// Second stage - dynamically sorted meshes.
+//			int last_dynamic_sort_entry = p_scene->m_first_dynamic_sort_entry + p_scene->m_num_dynamic_sort_entries;
+//			for( ; e < last_dynamic_sort_entry; ++e )
+//			{
+//				if ( p_scene->m_semitransparent_meshes[e]->m_flags & sMesh::MESH_FLAG_VISIBLE )
+//				{
+//					++meshes_rendered;
+//					// Figure the midpoint of this mesh, and sort it into the render list.
+//					float midpoint = frustum_sort_mid( p_scene->m_semitransparent_meshes[e]->m_sphere );
+//
+////					// Add this mesh to the visible list.
+////					if( !( p_scene->m_semitransparent_meshes[e]->m_flags & sMesh::MESH_FLAG_NO_SKATER_SHADOW ))
+////					{
+////						visible_mesh_array[visible_mesh_array_index++] = p_scene->m_semitransparent_meshes[e];
+////					}
+//
+//					sortedMeshArray[next_sorted_mesh_entry].p_mesh	= p_scene->m_semitransparent_meshes[e];
+//					sortedMeshArray[next_sorted_mesh_entry].sort	= midpoint;
+//					++next_sorted_mesh_entry;
+//				}
+//			}
+//			if( next_sorted_mesh_entry > 0 )
+//			{
+//				// Sort the array into ascending sort order.
+//				qsort( sortedMeshArray, next_sorted_mesh_entry, sizeof( sSortedMeshEntry ), cmp );
+//		
+//				for( int m = 0; m < next_sorted_mesh_entry; ++m )
+//				{
+//					sMesh * p_mesh = sortedMeshArray[m].p_mesh;
+//
+////					set_render_state( RS_ZWRITEENABLE, p_mesh->m_zwrite );
+//
+//					if ( flags & vRENDER_NEW_TEST )
+//					{
+//						// Textured.
+//						// First pass material setup.
+//						sMaterialHeader * p_mat = p_mesh->mp_dl->m_material.p_header;
+//						sMaterialPassHeader * p_pass = (sMaterialPassHeader *)&p_mat[1];
+//
+//						if ( p_pass[0].m_texture.p_data )
+//						{
+//							if ( first_mat || !one_mat )
+//							{
+//								first_mat = false;
+//
+//								MaterialSubmit( p_mesh );
+//							}
+//
+//							GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//						}
+//						else
+//						{
+//							// Untextured.
+//							if ( !(p_mesh->mp_dl->m_flags & 0x00200000) )
+//							{
+//	    						GXSetNumTevStages( 1 );
+//								GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//								GXSetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//								GXSetNumTexGens( 0 );
+//								GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//								GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//								GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//								GXSetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//								GXSetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//								GXSetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+//								GXSetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//								GXSetChanMatColor( GX_COLOR0A0, p_pass->m_color );
+//
+//								GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//							}
+//						}
+//					}
+//					else
+//					{
+//						multi_mesh( p_mesh, p_posNormBuffer );
+//					}
+//				}
+//			}
+//		
+//			// Third stage - meshes after the dynamically sorted set.
+//			for( ; e < p_scene->m_num_semitransparent_entries; ++e )
+//			{
+//				if ( p_scene->m_semitransparent_meshes[e]->m_flags & sMesh::MESH_FLAG_VISIBLE )
+//				{
+//					sMesh * p_mesh = p_scene->m_semitransparent_meshes[e];
+//
+////					set_render_state( RS_ZWRITEENABLE, p_mesh->m_zwrite );
+//
+//					++meshes_rendered;
+//
+//					if ( flags & vRENDER_NEW_TEST )
+//					{
+//						// Textured.
+//						// First pass material setup.
+//						sMaterialHeader * p_mat = p_mesh->mp_dl->m_material.p_header;
+//						sMaterialPassHeader * p_pass = (sMaterialPassHeader *)&p_mat[1];
+//
+//						if ( p_pass[0].m_texture.p_data )
+//						{
+//							if ( first_mat || !one_mat )
+//							{
+//								first_mat = false;
+//
+//								MaterialSubmit( p_mesh );
+//							}
+//
+//							GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//						}
+//						else
+//						{
+//							// Untextured.
+//							if ( !(p_mesh->mp_dl->m_flags & 0x00200000) )
+//							{
+//								GXSetNumTevStages( 1 );
+//								GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//								GXSetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//								GXSetNumTexGens( 0 );
+//								GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//								GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//								GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//								GXSetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//								GXSetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//								GXSetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+//								GXSetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//								GXSetChanMatColor( GX_COLOR0A0, p_pass->m_color );
+//
+//								GX::CallDisplayList ( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size ); 
+//							}
+//						}
+//					}
+//					else
+//					{
+//						multi_mesh( p_mesh, p_posNormBuffer );
+//					}
+//				}
+//			}
+//		}
+//
+////		// Now draw the semitransparent meshes with shadow mapped on them.
+////		if( p_scene->m_flags & SCENE_FLAG_RECEIVE_SHADOWS )
+////		{
+////			render_shadow_meshes( p_scene, visible_mesh_array, visible_mesh_array_index );
+////		}
+//	}
+//
+////	if ( 0 )
+//	{
+//	// Render shadow meshes.
+//	if( flags & vRENDER_SHADOW_1ST_PASS )
+//	{
+//		int e;
+//		for( e = 0; e < p_scene->m_num_opaque_entries; ++e )
+//		{
+//			if ( p_scene->m_opaque_meshes[e]->m_flags & sMesh::MESH_FLAG_VISIBLE )
+//			{
+//				sMesh * p_mesh = p_scene->m_opaque_meshes[e];
+//		
+////				set_render_state( RS_ZWRITEENABLE, p_mesh->m_zwrite );
+//
+//				if ( p_posNormBuffer ) {
+//					// Skinned.
+//					multi_start_16 ( &p_posNormBuffer[0], &p_posNormBuffer[3], 6, 6 );
+//				} else {
+//					// Not skinned.
+//#ifdef SHORT_VERT
+//					cam_offset( p_mesh->m_offset_x, p_mesh->m_offset_y, p_mesh->m_offset_z );
+//					multi_start_16 ( p_mesh->mp_posBuffer, p_mesh->mp_normBuffer, 3, 3 );
+//#else
+//					multi_start ( p_mesh->mp_posBuffer, p_mesh->mp_normBuffer, 3, 3 );
+//#endif		// SHORT_VERT
+//				}
+//				
+//				multi_end( p_mesh, vRENDER_SHADOW_1ST_PASS );
+//			}
+//		}			
+//
+//		for( e = 0; e < p_scene->m_num_semitransparent_entries; ++e )
+//		{
+//			if ( p_scene->m_semitransparent_meshes[e]->m_flags & sMesh::MESH_FLAG_VISIBLE )
+//			{
+//				sMesh * p_mesh = p_scene->m_semitransparent_meshes[e];
+//
+////				set_render_state( RS_ZWRITEENABLE, p_mesh->m_zwrite );
+//
+//				if ( p_posNormBuffer ) {
+//					// Skinned.
+//					multi_start_16 ( &p_posNormBuffer[0], &p_posNormBuffer[3], 6, 6 );
+//				} else {
+//					// Not skinned.
+//#ifdef SHORT_VERT
+//					cam_offset( p_mesh->m_offset_x, p_mesh->m_offset_y, p_mesh->m_offset_z );
+//					multi_start_16 ( p_mesh->mp_posBuffer, p_mesh->mp_normBuffer, 3, 3 );
+//#else
+//					multi_start ( p_mesh->mp_posBuffer, p_mesh->mp_normBuffer, 3, 3 );
+//#endif		// SHORT_VERT
+//				}
+//
+//				multi_end( p_mesh, vRENDER_SHADOW_1ST_PASS );
+//			}
+//		}			
+//	}			
+//
+//	// Render shadows from list.
+//#if 1
+//	for ( int lp = 0; lp < shadow_meshes; lp++ )
+//	{
+//		GXTlutObj	palObj;
+//		GXInitTlutObj( &palObj, &shadowPalette, GX_TL_RGB5A3, 16 );
+//		GXLoadTlut ( &palObj, GX_TLUT0 );
+//
+//		// Set up shadow map texture
+//#define SHADOW_TEXTURE_SIZE 256
+//#define BLUR_TEXTURE_SIZE 64
+//		GXTexObj blurTexture;
+//		GXTexObj shadowTexture;
+//		GXInitTexObjCI(
+//			&blurTexture,
+//			blurTextureData,		//shadowTextureData,
+//			BLUR_TEXTURE_SIZE,		//SHADOW_TEXTURE_SIZE,
+//			BLUR_TEXTURE_SIZE,        //SHADOW_TEXTURE_SIZE,
+//			GX_TF_C4,
+//			GX_CLAMP,
+//			GX_CLAMP,
+//			GX_FALSE,
+//			GX_TLUT0 );
+////	GXInitTexObjLOD(&blurTexture, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+//		GXLoadTexObj( &blurTexture, GX_TEXMAP5 );
+//		GXInitTexObjCI(
+//			&shadowTexture,
+//			shadowTextureData,
+//			SHADOW_TEXTURE_SIZE,
+//			SHADOW_TEXTURE_SIZE,
+//			GX_TF_C4,
+//			GX_CLAMP,
+//			GX_CLAMP,
+//			GX_FALSE,
+//			GX_TLUT0 );
+////	GXInitTexObjLOD(&shadowTexture, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+//		GXLoadTexObj( &shadowTexture, GX_TEXMAP6 );
+//	
+//		// Offset the texture matrix.
+//#ifdef SHORT_VERT
+//		Mtx m;
+//		MTXTrans( m, pShadow[lp]->m_offset_x, pShadow[lp]->m_offset_y, pShadow[lp]->m_offset_z );
+//		MTXConcat( light, m, m );
+//		GXLoadTexMtxImm( m, GX_TEXMTX7, GX_MTX3x4 );
+//#endif		// SHORT_VERT
+//
+//		if ( p_posNormBuffer ) {
+//			// Skinned.
+//			multi_start_16 ( &p_posNormBuffer[0], &p_posNormBuffer[3], 6, 6 );
+//		} else {
+//			// Not skinned.
+//#ifdef SHORT_VERT
+//			cam_offset( pShadow[lp]->m_offset_x, pShadow[lp]->m_offset_y, pShadow[lp]->m_offset_z );
+//			multi_start_16 ( pShadow[lp]->mp_posBuffer, pShadow[lp]->mp_normBuffer, 3, 3 );
+//#else
+//			multi_start ( pShadow[lp]->mp_posBuffer, pShadow[lp]->mp_normBuffer, 3, 3 );
+//#endif		// SHORT_VERT
+//		}
+//
+//		multi_end( pShadow[lp], vRENDER_SHADOW_2ND_PASS );
+//	}
+//#endif
+//	}
+//
+////	if( flags & vRENDER_SEMITRANSPARENT )
+////	{
+////		// Sort semi-transparent meshes.
+////		int bucket_size = p_scene->m_num_semitransparent_entries * 2;
+////		sMesh * sorted_meshes[bucket_size];
+////		memset( sorted_meshes, 0, bucket_size * 4 );
+////		for( int e = 0; e < p_scene->m_num_semitransparent_entries; ++e )
+////		{
+////			if( p_scene->m_semitransparent_meshes[e]->m_flags & sMesh::MESH_FLAG_ACTIVE )
+////			{
+////				// Frustum cull this set of meshes, using the associated bounding box.
+////				float z;
+////				if( frustum_check_sphere( p_scene->m_semitransparent_meshes[e]->m_sphere, &z ))
+////				{
+////					int index = (int)( z / ( 20000.0f / bucket_size ) );
+////					if ( index < 0 ) index = 0;
+////					if ( index > (bucket_size - 1) ) index = (bucket_size - 1);
+////	
+////					if ( index >= (bucket_size / 2) )
+////					{
+////						// Search backwards to find an empty slot.
+////						while ( sorted_meshes[index] ) index--;
+////						if ( index >= 0 )
+////						{
+////							sorted_meshes[index] = p_scene->m_semitransparent_meshes[e];
+////						}
+////					}
+////					else
+////					{
+////						// Search forwards to find an empty slot.
+////						while ( sorted_meshes[index] ) index++;
+////						if ( index < bucket_size )
+////						{
+////							sorted_meshes[index] = p_scene->m_semitransparent_meshes[e];
+////						}
+////					}
+////				}
+////			}
+////		}
+////	
+////		// Render semi-transparent meshes.
+////		for( int e = 0; e < bucket_size; ++e )
+////		{
+////			if( sorted_meshes[e] )
+////			{
+////				++meshes_rendered;
+////	
+////				sMesh * p_mesh = sorted_meshes[e];
+////				
+////				set_render_state( RS_ZWRITEENABLE, p_mesh->m_zwrite );
+////
+//////				if ( p_mesh->mp_uvBuffer && p_mesh->mp_material->pTex[0] )
+////				{
+////					uint32 layer;
+////
+////					if ( p_posNormBuffer ) {
+////						// Skinned.
+////						multi_start ( &p_posNormBuffer[0], &p_posNormBuffer[3], 6, 6 );
+////					} else {
+////						// Not skinned.
+////						multi_start ( p_mesh->mp_posBuffer, p_mesh->mp_normBuffer, 3, 3 );
+////					}
+////					multi_add_color ( p_mesh->mp_colBuffer );
+////					int uv_set = 0;
+////					float * p_tex;
+////					for ( layer = 0; layer < p_mesh->mp_material->Passes ; layer++ )
+////					{
+////						GXTexWrapMode u_mode;
+////						GXTexWrapMode v_mode;
+////						if ( p_mesh->mp_material->Flags[layer] & MATFLAG_ENVIRONMENT )
+////						{
+////							p_tex = NULL;
+////							u_mode = GX_REPEAT;
+////							v_mode = GX_REPEAT;
+////						}
+////						else
+////						{
+////							p_tex = &p_mesh->mp_uvBuffer[2*uv_set];
+////							u_mode = p_mesh->mp_material->UVAddressing[layer] & (1<<0)  ? GX_CLAMP : GX_REPEAT;
+////							v_mode = p_mesh->mp_material->UVAddressing[layer] & (1<<1) ? GX_CLAMP : GX_REPEAT;
+////						}
+////						uv_set++;
+////
+////						multi_add_texture ( p_tex, p_mesh->m_num_uv_sets, p_mesh->mp_material->uv_slot[layer], p_mesh->mp_material->Flags[layer], layer );
+////					}
+////
+////					multi_end( p_mesh, 0 );
+////				}
+////			}
+////		}			
+////	}			
+//	
+//	if ( p_scene->m_numHierarchyObjects != 0 )
+//	{
+//		// Hierarchical, set to matrix 0.
+//		GXSetCurrentMtx( GX_PNMTX0 );
+//	}
+	GX::SetProjectionv( pm );
+	g_object++;
+}
+
+/******************************************************************/
+/*                                                                */
+/* Sets MESH_FLAG_VISIBLE if visible for all meshes in this scene */
+/*                                                                */
+/******************************************************************/
+int cull_scene( sScene *p_scene, uint32 flags )
+{
+	int meshes_visible = 0;
+
+	// Cull all meshes.
+	sMesh ** pp_mesh = p_scene->mpp_mesh_list;
+
+	for( int e = 0; e < p_scene->m_num_filled_meshes; ++e )
+	{
+		sMesh * p_mesh = pp_mesh[e];
+		if( ( p_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) && ( p_mesh->GetVisibility() & EngineGlobals.viewport ) )
+		{
+			Mth::Vector sphere = p_mesh->mp_dl->m_sphere;
+
+			// Frustum cull this set of meshes, using the associated bounding box.
+			if( frustum_check_sphere( sphere ))
+			{
+				// Sphere is visible.
+				if( ( flags & vRENDER_OCCLUDED ) )
+				{
+					if ( TestSphereAgainstOccluders( &sphere ) )
+					{
+						// Occluded.
+						p_mesh->m_flags &= ~sMesh::MESH_FLAG_VISIBLE;
+					}
+					else
+					{
+						// Not occluded.
+						p_mesh->m_flags |= sMesh::MESH_FLAG_VISIBLE;
+						++meshes_visible;
+					}
+
+				}
+				else
+				{
+					// Sphere being visible is enough.
+					p_mesh->m_flags |= sMesh::MESH_FLAG_VISIBLE;
+					++meshes_visible;
+				}
+			}
+			else
+			{
+				// Sphere not visible.
+				p_mesh->m_flags &= ~sMesh::MESH_FLAG_VISIBLE;
+			}
+		}
+		else
+		{
+			// Force to be not visible.
+			p_mesh->m_flags &= ~sMesh::MESH_FLAG_VISIBLE;
+		}
+	}
+	return meshes_visible;
+
+//	make_scene_visible( p_scene );
+//	return p_scene->m_num_filled_meshes;
+}
+
+/******************************************************************/
+/*                                                                */
+/* Sets MESH_FLAG_VISIBLE for all meshes in this scene.			  */
+/*                                                                */
+/******************************************************************/
+void make_scene_visible( sScene *p_scene )
+{
+	// Set all meshes to be visible.
+	sMesh ** pp_mesh = p_scene->mpp_mesh_list;
+
+	for( int e = 0; e < p_scene->m_num_filled_meshes; ++e )
+	{
+		if( ( pp_mesh[e]->m_flags & sMesh::MESH_FLAG_ACTIVE ) && ( pp_mesh[e]->GetVisibility() & EngineGlobals.viewport ) )
+		{
+			pp_mesh[e]->m_flags |= sMesh::MESH_FLAG_VISIBLE;
+		}
+	}
+}
+
+void render_begin( void )
+{
+
+}
+
+void render_end( void )
+{
+
+}
+
+} // namespace NxNgc
+
+
+
diff --git a/Code/Gfx/NGC/NX/render.h b/Code/Gfx/NGC/NX/render.h
new file mode 100644
index 0000000..1ecb098
--- /dev/null
+++ b/Code/Gfx/NGC/NX/render.h
@@ -0,0 +1,96 @@
+#ifndef __RENDER_H
+#define __RENDER_H
+
+#include 
+#include 
+#include 
+#include "mesh.h"
+#include "scene.h"
+#include 
+#include 
+
+#define		RS_ZWRITEENABLE			1
+#define		RS_ZTESTENABLE			2
+#define		RS_ALPHACUTOFF			3
+#define		RS_UVADDRESSMODE0		4
+#define		RS_UVADDRESSMODE1		5
+#define		RS_UVADDRESSMODE2		6
+#define		RS_UVADDRESSMODE3		7
+
+namespace NxNgc
+{
+	struct sTextureProjectionDetails
+	{
+		sTexture		*p_texture;
+		Nx::CNgcModel	*p_model;
+		sScene			*p_scene;
+		NsMatrix		view_matrix;
+		NsMatrix		projection_matrix;
+		NsMatrix		texture_projection_matrix;
+	};
+
+	extern Lst::HashTable< sTextureProjectionDetails > *pTextureProjectionDetailsTable;
+	
+	typedef enum
+	{
+		vBLEND_MODE_DIFFUSE,						// ( 0 - 0 ) * 0 + Src
+		vBLEND_MODE_ADD,							// ( Src - 0 ) * Src + Dst
+		vBLEND_MODE_ADD_FIXED,						// ( Src - 0 ) * Fixed + Dst
+		vBLEND_MODE_SUBTRACT,						// ( 0 - Src ) * Src + Dst
+		vBLEND_MODE_SUB_FIXED,						// ( 0 - Src ) * Fixed + Dst
+		vBLEND_MODE_BLEND,							// ( Src * Dst ) * Src + Dst	
+		vBLEND_MODE_BLEND_FIXED,					// ( Src * Dst ) * Fixed + Dst	
+		vBLEND_MODE_MODULATE,						// ( Dst - 0 ) * Src + 0
+		vBLEND_MODE_MODULATE_FIXED,					// ( Dst - 0 ) * Fixed + 0	
+		vBLEND_MODE_BRIGHTEN,						// ( Dst - 0 ) * Src + Dst
+		vBLEND_MODE_BRIGHTEN_FIXED,					// ( Dst - 0 ) * Fixed + Dst	
+		vBLEND_MODE_GLOSS_MAP,						// Specular = Specular * Src	- special mode for gloss mapping
+		vBLEND_MODE_BLEND_PREVIOUS_MASK,			// ( Src - Dst ) * Dst + Dst
+		vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK,	// ( Dst - Src ) * Dst + Src
+
+		vNUM_BLEND_MODES
+	} BlendModes; 
+
+	typedef enum
+	{
+		vRENDER_OPAQUE			= 1,						
+		vRENDER_SEMITRANSPARENT	= 2,
+		vRENDER_OCCLUDED		= 4,
+		vRENDER_NO_CULLING		= 8,		// Used for instances which have already been culled at a higher level
+		vRENDER_SHADOW_1ST_PASS = 16,
+		vRENDER_SHADOW_2ND_PASS = 32,
+		vRENDER_NEW_TEST		= 64,
+		vRENDER_INSTANCE_PRE_WORLD_SEMITRANSPARENT	= 128,		// Used to indicate that this instance rendering is happening prior to semitransparent world rendering
+		vRENDER_INSTANCE_POST_WORLD_SEMITRANSPARENT	= 256,		// Used to indicate that this instance rendering is happening after semitransparent world rendering
+		vRENDER_TRANSFORM		= 512,
+		vRENDER_LIT				= 1024,
+		vNUM_SCENE_RENDER_FLAGS
+	} SceneRenderFlags; 
+
+	void	init_render_system();
+	void	set_render_state( uint32 type, uint32 state );
+	void	set_blend_mode( uint64 mode );
+
+	void	create_texture_projection_details( sTexture *p_texture, Nx::CNgcModel *p_model, sScene *p_scene );
+	void	destroy_texture_projection_details( sTexture *p_texture );
+	void	set_texture_projection_camera( sTexture *p_texture, NsVector * p_pos, NsVector * p_at );
+	void	calculate_tex_proj_matrix( NsMatrix * p_tex_view_matrix, NsMatrix * p_tex_proj_matrix, NsMatrix * p_tex_transform_matrix, NsMatrix * p_world_matrix = NULL );
+	
+	void	set_camera( Mth::Matrix *p_matrix, Mth::Vector *p_position, float screen_angle, float aspect_ratio, float scale = 1.0f );
+	void	set_frustum_bbox_transform( Mth::Matrix *p_transform );
+	bool	frustum_check_box( Mth::CBBox& box );
+	bool	frustum_check_sphere( Mth::Vector& p_sphere );
+	bool	frustum_check_sphere( Mth::Vector& p_sphere, float * p_z );
+	void	render_scene( sScene *p_scene, s16* p_posNormBuffer, uint32 flags = ( vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT ), Mth::Matrix * p_bone_xform = NULL, Mth::Matrix * p_instance_xform = NULL, int num_bones = 0 );
+	bool	IsVisible( Mth::Vector &sphere );
+	void	render_shadow_targets();
+
+	int		cull_scene( sScene *p_scene, uint32 flags = 0 );
+	void	make_scene_visible( sScene *p_scene );
+
+	void	render_begin( void );
+	void	render_end( void );
+} // namespace NxNgc
+
+#endif // __RENDER_H
+
diff --git a/Code/Gfx/NGC/NX/scene.cpp b/Code/Gfx/NGC/NX/scene.cpp
new file mode 100644
index 0000000..c7a0b6d
--- /dev/null
+++ b/Code/Gfx/NGC/NX/scene.cpp
@@ -0,0 +1,1150 @@
+#include 
+
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "mesh.h"
+#include "scene.h"
+#include "render.h"
+#include 
+#include "nx_init.h"
+#include 
+#include 
+#include 
+
+#define ENV_MAP_SCROLL_SCALE ( 1.0f / 32768.0f )
+
+int g_dl = 1;
+
+bool gOverDraw = false;
+
+namespace NxNgc
+{
+
+static s32	last_audio_update = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void do_audio_update( void )
+{
+	s32 t = OSGetTick();
+	if(( t < last_audio_update ) || ( OSDiffTick( t, last_audio_update ) > (s32)( OS_TIMER_CLOCK / 60 )))
+	{
+		last_audio_update = t;
+		Pcm::PCMAudio_Update();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int sort_by_material_draw_order( const void *p1, const void *p2 )
+{
+	sMesh			*p_mesh1		= *((sMesh**)p1 );
+	sMesh			*p_mesh2		= *((sMesh**)p2 );
+	sMaterialHeader	*p_material1	= p_mesh1->mp_dl->m_material.p_header;
+	sMaterialHeader	*p_material2	= p_mesh2->mp_dl->m_material.p_header;
+	
+	Dbg_Assert( p_material1 != NULL );
+	Dbg_Assert( p_material2 != NULL );
+
+	if( p_material1->m_draw_order == p_material2->m_draw_order )
+	{
+		// Have to do some special case processing for the situation where two or more meshes have the same draw order, but only some are
+		// marked as being dynamically sorted. In such a situation the non dynamically sorted ones should come first.
+		if( ( p_material1->m_flags & (1<<0) ) == ( p_material2->m_flags & (1<<0) ) )
+		{
+			if( p_material1 == p_material2 )
+			{
+				// Same material, no further sorting required.
+				return 0;
+			}
+			else
+			{
+				// If the blend mode is the same, sort by material address.
+				if( p_material1->m_base_blend == p_material2->m_base_blend )
+				{
+					// If the pixel shaders are the same, sort by material address, otherwise sort by pixel shader value.
+					if( p_material1->m_texture_dl_id == p_material2->m_texture_dl_id )
+					{
+						return ((uint32)p_material1 > (uint32)p_material2 ) ? 1 : -1;
+					}
+					else
+					{
+						return ((uint32)p_material1->m_texture_dl_id < (uint32)p_material2->m_texture_dl_id ) ? 1 : -1;
+					}
+				}
+				else
+				{
+					return ((uint32)p_material1->m_base_blend > (uint32)p_material2->m_base_blend ) ? 1 : -1;
+				}
+			}
+		}
+		else if( p_material1->m_flags & (1<<0) )
+		{
+			return 1;
+		}
+		return -1;
+	}
+	return ( p_material1->m_draw_order > p_material2->m_draw_order ) ? 1 : -1;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define INDEX_WORKBUFFER_SIZE	1700
+#define MAX_STRIPS				1024
+#define MAX_INDEX_WIDTH			7
+
+// Maximum index width:
+// 1 position
+// 1 normal
+// 2 colors
+// 4 tex coord
+	
+static void hide_mesh( uint32 mask, sCASData *p_cas_data, uint32 num_entries, sMesh * p_mesh )
+{
+	uint16	index_workbuffer[INDEX_WORKBUFFER_SIZE][MAX_INDEX_WIDTH];
+	uint32	new_indices_index = 0;
+
+	int		index_strip_len[MAX_STRIPS];
+	int		num_strips = 0;
+
+	// Clear strips.
+	for ( int lp = 0; lp < MAX_STRIPS; lp++ )
+	{
+		index_strip_len[lp] = 0;
+	}
+
+	unsigned char * p_start = (unsigned char *)&p_mesh->mp_dl[1];
+	unsigned char * p_end = &p_start[p_mesh->mp_dl->m_size];
+	p_start = &p_start[p_mesh->mp_dl->m_index_offset];		// Skip to actual 1st GDBegin.
+	unsigned char * p8 = p_start;
+
+	uint8 begin_token = p8[0];		// Save token for use when rebuilding mesh.
+
+	int stride = p_mesh->mp_dl->m_index_stride;
+
+	int w;
+
+	while ( p8 < p_end )
+	{
+		if ( ( p8[0] & 0xf8 ) == GX_TRIANGLESTRIP )
+		{
+			// Found a triangle strip - parse it.
+			int num_verts = ( p8[1] << 8 ) | p8[2];
+			p8 += 3;		// Skip GDBegin
+
+			uint16 idx0[MAX_INDEX_WIDTH];
+			uint16 idx1[MAX_INDEX_WIDTH];
+			uint16 idx2[MAX_INDEX_WIDTH];
+
+			for ( w = 0; w < stride; w++ ) { idx1[w] = ( p8[0] << 8 ) | p8[1]; p8 += 2; }
+			for ( w = 0; w < stride; w++ ) { idx2[w] = ( p8[0] << 8 ) | p8[1]; p8 += 2; }
+
+			for ( int v = 2; v < num_verts; v++ )
+			{
+				// Read next index to form triangle.
+				for ( w = 0; w < stride; w++ ) idx0[w] = idx1[w];
+				for ( w = 0; w < stride; w++ ) idx1[w] = idx2[w];
+				for ( w = 0; w < stride; w++ ) { idx2[w] = ( p8[0] << 8 ) | p8[1]; p8 += 2; } 
+
+				// There shuold be no degenerate triangles.
+				// ...check against every CAS entry.
+				bool keep = true;
+				for( uint32 entry = 0; entry < num_entries; ++entry )
+				{
+					// Check this CAS entry has the correct mask...
+					if( p_cas_data[entry].mask & mask )
+					{
+						// ...and the right mesh...
+						uint32 mesh = p_cas_data[entry].data0 >> 16;
+	
+						// The CAS data is ordered first in increasing mesh size - so we can early out here if applicable.
+						if ( mesh > p_mesh->mp_dl->m_mesh_index )
+						{
+							break;
+						}
+	
+						if ( mesh == p_mesh->mp_dl->m_mesh_index )
+						{
+							// ...and the correct index.
+							uint32 i0 = ( p_cas_data[entry].data0 & 0xFFFF );
+	
+							// The CAS data is next ordered in increasing size of i0 - so we can early out here if applicable.
+							if ( i0 > idx0[0] )
+							{
+								break;
+							}
+	
+							uint32 i1 = ( p_cas_data[entry].data1 >> 16 );
+							uint32 i2 = ( p_cas_data[entry].data1 & 0xFFFF );
+							if ( ( i0 == idx0[0] ) && ( i1 == idx1[0] ) && ( i2 == idx2[0] ) )
+							{
+								keep = false;
+
+								break;
+							}
+						}
+					}
+				}
+
+				// Copy index.
+				if ( keep )
+				{
+					if ( index_strip_len[num_strips] )
+					{
+						// Already put 1st triangle in, just add index.
+						for ( w = 0; w < stride; w++ ) index_workbuffer[new_indices_index][w] = idx2[w];
+						new_indices_index++;
+						index_strip_len[num_strips]++;
+					}
+					else
+					{
+						// 1st triangle, so add all 3 verts.
+						for ( w = 0; w < stride; w++ ) index_workbuffer[new_indices_index][w] = idx0[w];
+						new_indices_index ++;
+						for ( w = 0; w < stride; w++ ) index_workbuffer[new_indices_index][w] = idx1[w];
+						new_indices_index ++;
+						for ( w = 0; w < stride; w++ ) index_workbuffer[new_indices_index][w] = idx2[w];
+						new_indices_index ++;
+						index_strip_len[num_strips] += 3;
+					}
+				}
+				else
+				{
+					// If we don't keep this tri & we already started a strip, we need
+					// to start a new strip.
+					if ( index_strip_len[num_strips] )
+					{
+						// Need to start a new strip.
+						num_strips++;
+					}
+				}
+			}
+			// Start a new strip if we put data in this one.
+			if ( index_strip_len[num_strips] )
+			{
+				// Need to start a new strip.
+				num_strips++;
+			}
+		}
+		else
+		{
+			break;
+		}
+	}
+	do_audio_update();
+
+	Dbg_MsgAssert( new_indices_index <= INDEX_WORKBUFFER_SIZE, ( "Too many indices in new mesh: %d\n", new_indices_index ) );
+
+	// See if we'll fit in the existing buffer.
+	uint32 new_size = ( 3 * num_strips ) + ( sizeof( uint16 ) * stride * new_indices_index ) + p_mesh->mp_dl->m_index_offset;
+
+//	OSReport( "New: %d Old: %d Diff: %d\n", new_size, p_mesh->mp_dl->m_size, p_mesh->mp_dl->m_size - new_size );
+
+//	if ( new_size > p_mesh->mp_dl->m_original_dl_size )
+//	{
+//		// Need to allocate a new buffer for this.
+//	}
+
+	if ( new_size <= p_mesh->mp_dl->m_original_dl_size )
+	{
+		p8 = p_start;
+		int buffer_index = 0;
+
+		for ( int strip = 0; strip < num_strips; strip++ )
+		{
+			*p8++ = begin_token;
+			*p8++ = (uint8)(index_strip_len[strip] >> 8 );
+			*p8++ = (uint8)(index_strip_len[strip] & 0xff );
+			for ( int index = 0; index < index_strip_len[strip]; index++ )
+			{
+				for ( w = 0; w < stride; w++ )
+				{
+					*p8++ = (uint8)(index_workbuffer[buffer_index][w] >> 8 );
+					*p8++ = (uint8)(index_workbuffer[buffer_index][w] & 0xff );
+				}
+				buffer_index++;
+			}
+		}
+		// Set new size of display list.
+		int new_rounded_size = ( new_size + 31 ) & ~31;
+		p_mesh->mp_dl->m_size = new_rounded_size;
+
+		// Pad the DL with 0s to avoid spurious crap at the end & flush cache.
+		int pad_size = new_rounded_size - new_size;
+		for ( int pad = 0; pad < pad_size; pad++ ) *p8++ = 0;
+
+		DCFlushRange( &p_mesh->mp_dl[1], new_rounded_size );
+	}
+	else
+	{
+#ifdef __NOPT_FINAL__
+		OSReport( "Warning: DL too big after hide_mesh - New: %d Old: %d Diff: %d\n", new_size, p_mesh->mp_dl->m_size, p_mesh->mp_dl->m_size - new_size );
+#else
+		Dbg_MsgAssert( false, ( "Error: DL too big after hide_mesh - New: %d Old: %d Diff: %d\n", new_size, p_mesh->mp_dl->m_size, p_mesh->mp_dl->m_size - new_size ) );
+#endif		// __NOPT_FINAL__
+	}
+	do_audio_update();
+}
+
+sScene::sScene( void )
+{
+	m_flags					= 0;
+
+	m_num_meshes			= 0;		// No meshes as yet.
+	m_num_filled_meshes		= 0;
+	mpp_mesh_list			= NULL;
+
+	mp_hierarchyObjects		= NULL;
+	m_numHierarchyObjects	= 0;
+
+	mp_scene_data			= NULL;
+	mp_dl					= NULL;
+
+	mp_blend_dl				= NULL;
+	mp_material_header		= NULL;
+
+	mp_hierarchyObjects		= NULL;
+	m_numHierarchyObjects	= 0;
+
+	m_is_dictionary			= false;
+}
+
+sScene::~sScene( void )
+{
+	// Remove the material table.
+//	if( mp_material_array )
+//	{
+//		delete mp_material_array;
+//	}
+//
+//	if( m_opaque_meshes != NULL )
+//	{
+//		delete [] m_opaque_meshes;
+//	}
+//	if( m_semitransparent_meshes != NULL )
+//	{
+//		delete [] m_semitransparent_meshes;
+//	}
+//
+//	if ( mp_hierarchyObjects )
+//	{
+//		delete [] mp_hierarchyObjects;
+//	}
+//
+
+//	// Go through, and see if any DLs got allocated.
+//	int dl = 0;
+//	for( uint s = 0; s < mp_scene_data->m_num_objects; ++s )
+//	{
+//		int num_mesh = mp_dl[dl].mp_object_header->m_num_meshes;
+//		for ( int m = 0; m < num_mesh; m++ )
+//		{
+//			if ( mp_dl[dl].mp_pos_pool )
+//			{
+//			}
+//		}
+//	}
+
+
+	if ( mp_scene_data && !( m_flags & SCENE_FLAG_CLONED_GEOM ) )
+	{
+		delete [] mp_scene_data;
+	}
+
+	if ( mpp_mesh_list )
+	{
+		delete [] mpp_mesh_list;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::AddMeshes( int num_meshes, sMesh **pp_meshes )
+{
+	// Add each mesh.
+	for( int m = 0; m < num_meshes; ++m )
+	{
+		if ( m_num_filled_meshes < m_num_meshes )
+		{
+			mpp_mesh_list[m_num_filled_meshes] = pp_meshes[m];
+			++m_num_filled_meshes;
+		}
+		else
+		{
+			Dbg_MsgAssert( false, ( "Too many meshes being added." ) );
+		}
+	}
+
+//	for( int m = 0; m < num_meshes; ++m )
+//	{
+//		sMaterialHeader *		p_material = pp_meshes[m]->mp_dl->m_material.p_header;
+//		sMaterialPassHeader *	p_pass = &mp_material_pass[p_material->m_pass_item];
+//
+//		bool transparent = (( p_material ) && ( p_pass->m_flags & (1<<3) ));
+//
+//		if ( transparent )
+//		{
+//			if ( m_num_filled_meshes < m_num_meshes )
+//			{
+//				mpp_mesh_list[m_num_filled_meshes] = pp_meshes[m];
+//				++m_num_filled_meshes;
+//			}
+//			else
+//			{
+//				Dbg_MsgAssert( false, ( "Too many meshes being added." ) );
+//			}
+//		}
+//	}
+//
+//	for( int m = 0; m < num_meshes; ++m )
+//	{
+//		sMaterialHeader *		p_material = pp_meshes[m]->mp_dl->m_material.p_header;
+//		sMaterialPassHeader *	p_pass = &mp_material_pass[p_material->m_pass_item];
+//
+//		bool transparent = (( p_material ) && ( p_pass->m_flags & (1<<3) ));
+//
+//		if ( !transparent )
+//		{
+//			if ( m_num_filled_meshes < m_num_meshes )
+//			{
+//				mpp_mesh_list[m_num_filled_meshes] = pp_meshes[m];
+//				++m_num_filled_meshes;
+//			}
+//			else
+//			{
+//				Dbg_MsgAssert( false, ( "Too many meshes being added." ) );
+//			}
+//		}
+//	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::CountMeshes( int num_meshes, sMesh **pp_meshes )
+{
+	m_num_meshes += (uint16)num_meshes;
+
+
+//	// Count each mesh.
+//	for( int m = 0; m < num_meshes; ++m )
+//	{
+//		++m_num_meshes;
+////		bool transparent = (( pp_meshes[m]->mp_material ) && ( pp_meshes[m]->mp_material->Flags[0] & 0x40 ));
+////		if( transparent )
+////		{
+////			++m_num_semitransparent_entries;
+////		}
+////		else
+////		{
+////			++m_num_opaque_entries;
+////		}
+//	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::CreateMeshArrays( void )
+{
+	if ( m_num_meshes > 0 )
+	{
+		mpp_mesh_list = new sMesh*[m_num_meshes];
+	}
+
+//	if( m_num_semitransparent_entries > 0 )
+//	{
+//		m_semitransparent_meshes = new sMesh*[m_num_semitransparent_entries];
+//	}
+//	
+//	if( m_num_opaque_entries > 0 )
+//	{
+//		m_opaque_meshes = new sMesh*[m_num_opaque_entries];
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::FigureBoundingVolumes( void )
+{
+//	// Figure bounding sphere assuming bounding box has already been set up (during individual mesh initialisation).
+//	Mth::Vector radial	= ( m_bbox.GetMax() - m_bbox.GetMin() ) * 0.5f;
+//	Mth::Vector center	= m_bbox.GetMin() + radial;
+//	m_sphere_center.set( center[X], center[Y], center[Z] );
+//	m_sphere_radius		= sqrtf(( radial[X] * radial[X] ) +	( radial[Y] * radial[Y] ) + ( radial[Z] * radial[Z] ));
+}
+
+
+
+
+
+
+
+
+void sScene::RemoveMeshes( int num_meshes, sMesh **pp_meshes )
+{
+	int num_opaque_entries_removed					= 0;
+	int num_pre_semitransparent_entries_removed		= 0;
+	int num_dynamic_semitransparent_entries_removed	= 0;
+	int num_post_semitransparent_entries_removed	= 0;
+	
+	int opaque		= m_num_opaque_meshes;
+	int presemi		= opaque + m_num_pre_semitrans_meshes;
+	int dynsemi		= presemi + m_num_dynamic_semitrans_meshes;
+	int postsemi	= dynsemi + m_num_post_semitrans_meshes;
+
+	for( int m = 0; m < num_meshes; ++m )
+	{
+		sMesh *p_mesh = pp_meshes[m];
+
+		bool found = false;
+		// Search Opaque
+		for ( int i = 0; i < opaque; i++ )
+		{
+			if( mpp_mesh_list[i] == p_mesh )
+			{
+				found = true;
+				mpp_mesh_list[i] = NULL;
+				++num_opaque_entries_removed;
+				--m_num_opaque_meshes;
+				break;
+			}
+		}
+		if( found ) continue;
+
+		// Search Pre Semitransparent
+		for ( int i = opaque; i < presemi; i++ )
+		{
+			if( mpp_mesh_list[i] == p_mesh )
+			{
+				found = true;
+				mpp_mesh_list[i] = NULL;
+				++num_pre_semitransparent_entries_removed;
+				--m_num_pre_semitrans_meshes;
+				break;
+			}
+		}
+
+		if( found ) continue;
+
+		// Search Dynamic Semitransparent
+		for ( int i = presemi; i < dynsemi; i++ )
+		{
+			if( mpp_mesh_list[i] == p_mesh )
+			{
+				found = true;
+				mpp_mesh_list[i] = NULL;
+				++num_dynamic_semitransparent_entries_removed;
+				--m_num_dynamic_semitrans_meshes;
+				break;
+			}
+		}
+
+		if( found ) continue;
+
+		// Search Post Semitransparent
+		for ( int i = dynsemi; i < postsemi; i++ )
+		{
+			if( mpp_mesh_list[i] == p_mesh )
+			{
+				found = true;
+				mpp_mesh_list[i] = NULL;
+				++num_post_semitransparent_entries_removed;
+				--m_num_post_semitrans_meshes;
+				break;
+			}
+		}
+
+		Dbg_Assert( found );	
+	}
+	
+	// Now go through and compact the arrays.
+
+//	int total = m_num_opaque_meshes + m_num_pre_semitrans_meshes + m_num_dynamic_semitrans_meshes + m_num_post_semitrans_meshes;
+	int total_removed = num_opaque_entries_removed +
+						num_pre_semitransparent_entries_removed +
+						num_dynamic_semitransparent_entries_removed	+
+						num_post_semitransparent_entries_removed;
+
+	for ( int lp = 0; lp < total_removed; lp++ )
+	{
+		for ( int i = 0; i < m_num_filled_meshes; i++ )
+		{
+			if( !mpp_mesh_list[i] )
+			{
+//				m_num_filled_meshes--;
+//				mpp_mesh_list[i] = mpp_mesh_list[m_num_filled_meshes];
+
+
+				// Only worth copying if there is anything beyond this mesh.
+				if( i < ( m_num_filled_meshes - 1 ))
+				{
+					memcpy( &mpp_mesh_list[i], &mpp_mesh_list[i + 1], sizeof( sMesh* ) * ( m_num_filled_meshes - ( i + 1 )));
+				}
+				m_num_filled_meshes--;
+				break;
+			}
+		}
+	}
+//	SortMeshes();
+//	m_num_filled_meshes -= total_removed;
+}
+
+
+
+void sScene::SortMeshes( void )
+{
+	// Sort the list of meshes.
+	qsort( mpp_mesh_list, m_num_filled_meshes, sizeof( sMesh* ), sort_by_material_draw_order );
+
+	if ( mp_scene_data )
+	{
+		m_num_opaque_meshes				= 0;
+		m_num_pre_semitrans_meshes		= 0;
+		m_num_dynamic_semitrans_meshes	= 0;
+		m_num_post_semitrans_meshes		= 0;
+
+		// Force opaque to be before semitrans.
+		sMesh *p_mesh[8192];
+		int mesh_entry = 0;
+		Dbg_MsgAssert( m_num_filled_meshes < 8192, ( "Too many meshes for temporary sort buffer." ) );
+
+		// Find 1st dynamic entry.
+		int first_dynamic = -1;
+		for( int i = 0; i < m_num_filled_meshes; ++i )
+		{
+			if ( mpp_mesh_list[i] )
+			{
+				bool sorted = mpp_mesh_list[i]->mp_dl->m_material.p_header->m_flags & (1<<0) ? true : false;
+
+				if ( ( first_dynamic == -1 ) && sorted ) first_dynamic = i;
+			}
+		}
+
+
+		for ( int pass = 0; pass < 4; pass++ )
+		{
+			for( int i = 0; i < m_num_filled_meshes; ++i )
+			{
+				if ( mpp_mesh_list[i] )
+				{
+					bool transparent = ( mp_material_pass[mpp_mesh_list[i]->mp_dl->m_material.p_header->m_pass_item].m_flags & (1<<3) );
+//					bool transparent = ( mpp_mesh_list[i]->mp_dl->m_material.p_header->m_flags & (1<<3) ) ? true : false;
+
+					bool sorted = mpp_mesh_list[i]->mp_dl->m_material.p_header->m_flags & (1<<0) ? true : false;
+
+					if ( ( first_dynamic == -1 ) && sorted ) first_dynamic = i;
+
+					switch ( pass )
+					{
+						case 0:		// Opaque
+							if ( !transparent )
+							{
+								p_mesh[mesh_entry] = mpp_mesh_list[i];
+								mpp_mesh_list[i] = NULL;
+								mesh_entry++;
+								m_num_opaque_meshes++;
+							}
+							break;
+						case 1:		// Pre semi
+							if ( transparent && !sorted && ( i < first_dynamic ) )
+							{
+//								if ( mpp_mesh_list[i]->mp_dl ) printf( "Pre Semi: %d, %8.3f\n", i, mpp_mesh_list[i]->mp_dl->m_material.p_header->m_draw_order );
+								p_mesh[mesh_entry] = mpp_mesh_list[i];
+								mpp_mesh_list[i] = NULL;
+								mesh_entry++;
+								m_num_pre_semitrans_meshes++;
+							}
+							break;
+						case 2:		// Dynamic semi
+							if ( transparent && sorted )
+							{
+//								if ( mpp_mesh_list[i]->mp_dl ) printf( "Dyn Semi: %d, %8.3f\n", i, mpp_mesh_list[i]->mp_dl->m_material.p_header->m_draw_order );
+								p_mesh[mesh_entry] = mpp_mesh_list[i];
+								mpp_mesh_list[i] = NULL;
+								mesh_entry++;
+								m_num_dynamic_semitrans_meshes++;
+							}
+							break;
+						case 3:		// Post semi (everything else)...
+//							if ( transparent && !sorted && ( ( i >= first_dynamic ) && ( first_dynamic != -1 ) ) )
+							{
+//								if ( mpp_mesh_list[i]->mp_dl ) printf( "Pst Semi: %d, %8.3f\n", i, mpp_mesh_list[i]->mp_dl->m_material.p_header->m_draw_order );
+								p_mesh[mesh_entry] = mpp_mesh_list[i];
+								mpp_mesh_list[i] = NULL;
+								mesh_entry++;
+								m_num_post_semitrans_meshes++;
+							}
+							break;
+						default:
+							Dbg_MsgAssert( false, ( "This should never happen." ) );
+							break;
+					}
+				}
+			}
+		}
+		Dbg_MsgAssert( m_num_filled_meshes == mesh_entry, ( "Sorted meshes differs from actual meshes." ) );
+
+//		float order = p_mesh[0]->mp_dl->m_material.p_header->m_draw_order;
+		for( int i = 0; i < m_num_filled_meshes; ++i )
+		{
+//			Dbg_MsgAssert( p_mesh[i]->mp_dl->m_material.p_header->m_draw_order >= order, ( "Out of order on entry %d: last=%f, current = %f", i, order, p_mesh[i]->mp_dl->m_material.p_header->m_draw_order ) );
+//			order = p_mesh[i]->mp_dl->m_material.p_header->m_draw_order;
+			mpp_mesh_list[i] = p_mesh[i];
+		}
+	}
+	else
+	{
+		m_num_opaque_meshes				= m_num_filled_meshes;
+		m_num_pre_semitrans_meshes		= 0;
+		m_num_dynamic_semitrans_meshes	= 0;
+		m_num_post_semitrans_meshes		= 0;
+	}
+}
+
+
+
+
+
+
+sMaterial *sScene::GetMaterial( uint32 checksum )
+{
+//	for ( int lp = 0; lp < m_num_materials; lp++ )
+//	{
+//		if( mp_material_array[lp].Checksum == checksum )
+//		{
+//			return &mp_material_array[lp];
+//		}
+//	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void sScene::HidePolys( uint32 mask, sCASData *p_cas_data, uint32 num_entries )
+{
+	if(( num_entries == 0 ) || ( mask == 0 ))
+	{
+		return;
+	}
+
+	// For each mesh, need to find all cas data which references verts in that mesh.
+	for( int m = 0; m < this->m_num_filled_meshes; ++m )
+	{
+		sMesh *p_mesh = mpp_mesh_list[m];
+
+		hide_mesh( mask, p_cas_data, num_entries, p_mesh );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sScene *LoadScene( const char *Filename, sScene *pScene )
+{
+	return NULL;
+}
+
+
+
+void DeleteScene( sScene *pScene )
+{
+//	// Iterate through the table of materials, deleting them.
+//	for ( int lp = 0; lp < pScene->m_num_materials; lp++ )
+//	{
+//		if( pScene->mp_material_array[lp].mp_wibble_vc_params	)
+//		{
+//			for( uint32 i = 0; i < pScene->mp_material_array[lp].m_num_wibble_vc_anims; ++i )
+//			{
+//				delete [] pScene->mp_material_array[lp].mp_wibble_vc_params[i].mp_keyframes;
+//			}
+//			delete [] pScene->mp_material_array[lp].mp_wibble_vc_params;
+//		}
+//		if( pScene->mp_material_array[lp].mp_wibble_vc_colors	)
+//		{
+//			delete [] pScene->mp_material_array[lp].mp_wibble_vc_colors;
+//		}
+//		if( pScene->mp_material_array[lp].m_pUVControl	)
+//		{
+//			delete [] pScene->mp_material_array[lp].m_pUVControl;
+//		}
+//	}
+//	delete pScene->mp_material_array;
+//	pScene->mp_material_array = NULL;
+//
+//	// Delete the scene itself.
+	delete pScene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void MaterialBuild(  sMesh * p_mesh, sScene * p_scene, bool bl, bool tx )
+{
+	if ( !p_mesh->mp_dl->m_material.p_header ) return;
+
+	// Construct the DLs.
+	uint32 size;
+
+	Mem::Manager::sHandle().TopDownHeap()->PushAlign( 32 );
+#define DL_BUILD_SIZE (8*1024)
+	uint8 * p_build_dl = new (Mem::Manager::sHandle().TopDownHeap()) uint8[DL_BUILD_SIZE];
+
+	// Build the texture upload DL.
+	DCFlushRange ( p_build_dl, DL_BUILD_SIZE );
+
+	GX::begin( p_build_dl, DL_BUILD_SIZE );
+
+//	multi_mesh( p_mesh, p_scene );
+
+	multi_mesh( p_mesh->mp_dl->m_material.p_header,
+				&p_scene->mp_material_pass[p_mesh->mp_dl->m_material.p_header->m_pass_item],
+				bl,
+				tx );
+
+	size = GX::end();
+
+	DCFlushRange ( p_build_dl, DL_BUILD_SIZE );
+
+	if ( size && ( size <= p_scene->mp_texture_dl[p_mesh->mp_dl->m_material.p_header->m_texture_dl_id].m_dl_size ) )
+	{
+//		p_mesh->mp_dl->mp_texture_dl = new uint8[size];
+
+		memcpy ( p_scene->mp_texture_dl[p_mesh->mp_dl->m_material.p_header->m_texture_dl_id].mp_dl, p_build_dl, size );
+		DCFlushRange ( p_scene->mp_texture_dl[p_mesh->mp_dl->m_material.p_header->m_texture_dl_id].mp_dl, size );
+
+		p_scene->mp_texture_dl[p_mesh->mp_dl->m_material.p_header->m_texture_dl_id].m_dl_size = (uint16)size;
+	}
+	else
+	{
+//		p_mesh->mp_dl->mp_texture_dl = NULL;
+//		p_mesh->mp_dl->m_texture_dl_size = 0;
+	}
+
+	Mem::Manager::sHandle().TopDownHeap()->PopAlign();
+
+	// Done constructing DL.
+	delete p_build_dl;
+}
+
+int g_material_id = -1;
+GXBool g_comploc = (GXBool)2;
+
+void ResetMaterialChange( void )
+{
+	g_material_id = -1; 
+	g_comploc = (GXBool)2; 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//static void MyGXProject ( 
+//    f32  x,          // model coordinates
+//    f32  y,
+//    f32  z,
+//    f32  mtx[3][4],  // model-view matrix
+//    f32* pm,         // projection matrix, as returned by GXGetProjectionv
+//    f32* vp,         // viewport, as returned by GXGetViewportv
+//    f32* sx,         // screen coordinates
+//    f32* sy,
+//    f32* sz ) 
+//{
+//    Vec   peye;
+//    f32   xc, yc, zc, wc;
+//
+//    ASSERTMSG(pm && vp && sx && sy && sz, GXERR_GET_NULL_PTR);
+//
+//    // transform to eye space
+//    peye.x = mtx[0][0]*x + mtx[0][1]*y + mtx[0][2]*z + mtx[0][3];
+//    peye.y = mtx[1][0]*x + mtx[1][1]*y + mtx[1][2]*z + mtx[1][3];
+//    peye.z = mtx[2][0]*x + mtx[2][1]*y + mtx[2][2]*z + mtx[2][3];
+//
+//	// My addition: Just a frig to stop stuff messing up as it gets close to the near plane.
+//	peye.z -= 512.0f;
+//	if ( peye.z > -1.0f ) peye.z = -1.0f;
+//
+//    // transform to clip space
+//    if (pm[0] == (f32)GX_PERSPECTIVE) { // perspective
+//        xc = peye.x * pm[1] + peye.z * pm[2];
+//        yc = peye.y * pm[3] + peye.z * pm[4];
+//        zc = peye.z * pm[5] + pm[6];
+//        wc = 1.0f / -peye.z;
+//    } else { // ortho
+//        xc = peye.x * pm[1] + pm[2];
+//        yc = peye.y * pm[3] + pm[4];
+//        zc = peye.z * pm[5] + pm[6];
+//        wc = 1.0f;
+//    }
+//
+//    // compute screen scale and offset
+//    *sx = xc * vp[2]/2 * wc + vp[0] + vp[2]/2;
+//    *sy = -yc * vp[3]/2 * wc + vp[1] + vp[3]/2;
+//    *sz = zc * (vp[5] - vp[4]) * wc + vp[5];
+//}
+//
+
+void MaterialSubmit( sMesh * p_mesh, sScene *pScene = NULL )
+{
+	if ( !pScene->mp_scene_data ) return;
+	if ( !p_mesh->mp_dl->m_material.p_header ) return;
+//	if ( !p_mesh->mp_dl->mp_texture_dl ) return;
+
+	if ( gOverDraw )
+	{
+		GX::SetFog( GX_FOG_NONE, 0.0f, 0.0f, 0.0f, 0.0f, (GXColor){0,0,0,0} );
+		GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0 );
+
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST,
+							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+		
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+		GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+
+		GX::SetTevKSel( GX_TEVSTAGE0, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A );
+		GX::SetTevKColor( GX_KCOLOR0, (GXColor){8,8,8,255} );
+		GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255});
+		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255});
+
+		GX::SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
+		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE ); 
+		return;
+	}
+
+
+
+
+
+
+
+//	if ( g_dl /*&& !( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<3) )*/ )
+//	if ( g_dl/* && !( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<3) )*/ )
+	if ( g_dl && !( p_mesh->mp_dl->m_material.p_header->m_flags & ((1<<3)|(1<<4)) ) )
+	{
+////		if ( p_mesh->mp_dl->m_material.p_header->m_material_dl_id != g_material_id )
+//		{
+//			g_material_id = p_mesh->mp_dl->m_material.p_header->m_material_dl_id;
+//
+//			if ( pScene->mp_blend_dl[g_material_id].mp_dl && pScene->mp_blend_dl[g_material_id].m_dl_size )
+//			{
+////				GX::CallDisplayList( pScene->mp_blend_dl[g_material_id].mp_dl, pScene->mp_blend_dl[g_material_id].m_dl_size );
+//				multi_mesh( p_mesh->mp_dl->m_material.p_header,
+//							&pScene->mp_material_pass[p_mesh->mp_dl->m_material.p_header->m_pass_item],
+//							true,
+//							false );
+//			}
+//		}
+
+		int tex_id = (int)p_mesh->mp_dl->m_material.p_header->m_texture_dl_id;
+		GX::CallDisplayList( pScene->mp_texture_dl[tex_id].mp_dl, pScene->mp_texture_dl[tex_id].m_dl_size );
+	}
+	else
+	{
+		multi_mesh( p_mesh->mp_dl->m_material.p_header,
+					&pScene->mp_material_pass[p_mesh->mp_dl->m_material.p_header->m_pass_item],
+					true,
+					true,
+					p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6) ? true : false,
+					p_mesh->mp_dl->mp_object_header->m_num_skin_verts ? false : true );
+	}
+
+	// See if we need to upload env mapping matrices.
+	sMaterialHeader * p_mat = p_mesh->mp_dl->m_material.p_header;
+	sMaterialPassHeader *p_pass = &pScene->mp_material_pass[p_mat->m_pass_item];
+
+	// Set Comploc
+	u8 alphacutoff = p_mesh->mp_dl->m_material.p_header->m_alpha_cutoff;
+	GXBool comploc;
+//	if ( p_pass->m_texture.p_data && ( p_pass->m_texture.p_data->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_HOLES ) )
+	if ( alphacutoff > 0 )
+	{
+		comploc = GX_FALSE;
+	}
+	else
+	{
+		comploc = GX_TRUE;
+	}
+	comploc = GX_FALSE;
+
+	if ( g_comploc != comploc )
+	{
+		g_comploc = comploc;
+		GX::SetZCompLoc( comploc );
+	}
+
+//	GX::SetAlphaCompare(GX_GEQUAL, alphacutoff, GX_AOP_AND, GX_GEQUAL, alphacutoff );
+//	GX::SetZCompLoc( GX_FALSE );
+	GX::SetAlphaCompare(GX_GEQUAL, alphacutoff, GX_AOP_AND, GX_GEQUAL, alphacutoff );
+
+	for ( int lp = 0; lp < p_mat->m_passes; lp++, p_pass++ )
+	{
+		if ( p_pass->m_flags & ( (1<<1) | (1<<2) ) )
+		{
+			// UV Wibbled or Environment mapped.
+			GXTexMtx mtx;
+			switch ( lp )
+			{
+				case 0:
+					mtx = GX_TEXMTX0;
+					break;
+				case 1:
+					mtx = GX_TEXMTX1;
+					break;
+				case 2:
+					mtx = GX_TEXMTX2;
+					break;
+				case 3:
+					mtx = GX_TEXMTX3;
+					break;
+				default:
+					mtx = GX_IDENTITY;
+					break;
+			}
+
+			if( p_pass->m_flags & (1<<1) )
+			{
+				// Env mapping.
+				Mtx s, t, e, mv;
+
+				MTXInvXpose((Mtx)&EngineGlobals.local_to_camera, mv );
+
+				// Project bounding sphere into screen space to get a metric to scroll the environment map.
+				f32				p[GX_PROJECTION_SZ];
+				f32				vp[GX_VIEWPORT_SZ];
+				float			rx, ry;	//, rz;
+
+				GX::GetProjectionv( p );
+				GX::GetViewportv( vp );
+
+				float x = EngineGlobals.local_to_camera.getPosX();
+				float y = EngineGlobals.local_to_camera.getPosY();
+//				float z = EngineGlobals.local_to_camera.getPosZ();
+
+				rx = -x;	//EngineGlobals.local_to_camera.getRightX()*x + EngineGlobals.local_to_camera.getRightY()*y + EngineGlobals.local_to_camera.getRightZ()*z;
+				ry = -y;	//EngineGlobals.local_to_camera.getUpX()*x + EngineGlobals.local_to_camera.getUpY()*y + EngineGlobals.local_to_camera.getUpZ()*z;
+
+
+//				MyGXProject( p_mesh->mp_dl->m_sphere[X],
+//						   p_mesh->mp_dl->m_sphere[Y],
+//						   p_mesh->mp_dl->m_sphere[Z],
+//						   (Mtx)&EngineGlobals.local_to_camera,
+//						   p, vp, &rx, &ry, &rz );
+
+				float u;
+				float v;
+
+				float ut = (float)p_pass->m_u_tile;
+				float vt = (float)p_pass->m_v_tile;
+				ut = ( ut * (1.0f / (float)(1<<12)) );
+				vt = ( vt * (1.0f / (float)(1<<12)) );
+
+				u = ( rx * ( ENV_MAP_SCROLL_SCALE * ut ) );
+				v = ( ry * ( ENV_MAP_SCROLL_SCALE * vt ) );
+
+				u -= (float)(int)u;		// Keep within +/-1.
+				v -= (float)(int)v;
+
+				// Create the rotational component.
+				MTXScale( s, 0.5f * ut, -0.5f * vt, 0.0f );
+				MTXTrans( t, 0.5f, 0.5f, 1.0f );
+				MTXConcat( t, s, e );
+
+				MTXConcat(e, mv, e);
+
+				e[0][3] += u;
+				e[1][3] += v;
+
+				GX::LoadTexMtxImm(e, mtx, GX_MTX2x4);
+			}
+			else
+			{
+				if ( p_pass->m_uv_enabled )
+				{
+					// Explicit matrix.
+					Mtx real;
+//					MTXCopy( p_pass->mp_explicit_wibble->m_matrix, real );
+
+//					real[0][3] = ( p_pass->mp_explicit_wibble->m_matrix[0][3] * -real[0][0] ) + ( p_pass->mp_explicit_wibble->m_matrix[1][3] * -real[0][1] );
+//					real[1][3] = ( p_pass->mp_explicit_wibble->m_matrix[0][3] * -real[1][0] ) + ( p_pass->mp_explicit_wibble->m_matrix[1][3] * -real[1][1] );
+
+					real[0][0] = ( (float)p_pass->m_uv_mat[0] ) * ( 1.0f / ((float)(1<<12)) );
+					real[0][1] = -( (float)p_pass->m_uv_mat[1] ) * ( 1.0f / ((float)(1<<12)) );
+					real[0][2] = 1.0f;
+					real[0][3] = ( (float)p_pass->m_uv_mat[2] ) * ( 1.0f / ((float)(1<<12)) ) - 1.0f;
+
+					real[1][0] = ( (float)p_pass->m_uv_mat[1] ) * ( 1.0f / ((float)(1<<12)) );
+					real[1][1] = ( (float)p_pass->m_uv_mat[0] ) * ( 1.0f / ((float)(1<<12)) );
+					real[1][2] = 1.0f;
+					real[1][3] = ( (float)p_pass->m_uv_mat[3] ) * ( 1.0f / ((float)(1<<12)) ) - 1.0f;
+					GX::LoadTexMtxImm( real, mtx, GX_MTX2x4 );
+				}
+				else
+				{
+					// Wibbled.
+					float uoff, voff, t;
+
+					t = (float)Tmr::GetTime() * 0.001f;
+
+					Mtx	m;
+
+					sMaterialUVWibble * p_uv = &pScene->mp_uv_wibble[p_pass->m_uv_wibble_index];
+
+					uoff	= ( t * p_uv->m_u_vel ) + ( p_uv->m_u_amp * sinf( p_uv->m_u_freq * t + p_uv->m_u_phase ));
+					voff	= ( t * p_uv->m_v_vel ) + ( p_uv->m_v_amp * sinf( p_uv->m_v_freq * t + p_uv->m_v_phase ));
+
+					// Reduce offset mod 16 and put it in the range -8 to +8.
+					uoff	+= 8.0f;
+					uoff	-= (float)(( (int)uoff >> 4 ) << 4 );
+					voff	+= 8.0f;
+					voff	-= (float)(( (int)voff >> 4 ) << 4 );
+
+					uoff = ( uoff < 0.0f ) ? ( uoff + 8.0f ) : ( uoff - 8.0f ); 
+					voff = ( voff < 0.0f ) ? ( voff + 8.0f ) : ( voff - 8.0f ); 
+
+					MTXTrans( m, uoff, voff, 0.0f );
+
+					GX::LoadTexMtxImm(m, mtx, GX_MTX2x4);
+				}
+			}
+		}
+	}
+}
+
+} // namespace NxNgc
+
diff --git a/Code/Gfx/NGC/NX/scene.h b/Code/Gfx/NGC/NX/scene.h
new file mode 100644
index 0000000..beee06b
--- /dev/null
+++ b/Code/Gfx/NGC/NX/scene.h
@@ -0,0 +1,133 @@
+#ifndef __SCENE_H
+#define __SCENE_H
+
+
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "mesh.h"
+#include "material.h"
+#include "anim.h"
+#include 
+#include 
+#include 
+
+namespace NxNgc
+{
+
+struct sMeshEntry
+{
+	sMesh*					mp_mesh;			// Pointer to mesh.
+	int						m_bbox;				// Bounding box index.
+};
+
+#define SCENE_FLAG_RENDERING_SHADOW		( 1 << 7 )
+#define SCENE_FLAG_RECEIVE_SHADOWS		( 1 << 8 )
+#define SCENE_FLAG_SELF_SHADOWS			( 1 << 9 )
+#define SCENE_FLAG_CLONED_GEOM			( 1 << 10 )
+
+struct sScene
+{
+								sScene( void );
+								~sScene( void );
+
+	sMaterial *					sScene::GetMaterial( uint32 checksum );
+	void						AddMeshes( int num_meshes, sMesh **pp_meshes );
+	void						CountMeshes( int num_meshes, sMesh **pp_meshes );
+	void						CreateMeshArrays( void );
+	void						RemoveMeshes( int num_meshes, sMesh **pp_meshes );
+	void						SortMeshes( void );
+	void						FigureBoundingVolumes( void );
+	void						HidePolys( uint32 mask, sCASData *p_cas_data, uint32 num_entries );
+
+//	uint32						m_flags;
+//	int							NumTextures;
+//	uint8						*pTexBuffer;
+//	uint8						*pTexDma;
+//	sTexture					*pTextures;
+//
+//	int							m_num_materials;
+//	sMaterial *					mp_material_array;
+//	
+//	// New style, with separate opaque and semitransparent mesh lists.
+//	sMesh						**m_opaque_meshes;
+//	int							m_num_opaque_entries;
+//	int							m_num_filled_opaque_entries;
+//	sMesh						**m_semitransparent_meshes;
+//	int							m_num_semitransparent_entries;
+//	int							m_num_filled_semitransparent_entries;
+//	int							m_first_dynamic_sort_entry;
+//	int							m_num_dynamic_sort_entries;
+////	int							m_num_bboxes;
+//	
+////	class CInstance				*pInstances;
+////
+////	sScene						*pNext;
+////
+////	static sScene				*pHead;
+//
+//	
+	bool						m_is_dictionary;
+//
+//	Mth::CBBox					m_bbox;	
+
+
+	// New stuff.
+	uint32						m_flags;
+
+	sSceneHeader *				mp_scene_data;		// This is all data for pointers below.
+	float *						mp_pos_pool;
+	s16 *						mp_nrm_pool;
+	uint32 *					mp_col_pool;
+	s16 *						mp_tex_pool;
+	sDLHeader *					mp_dl;
+	sMaterialDL *				mp_blend_dl;
+	sTextureDL *				mp_texture_dl;
+	sMaterialVCWibbleKeyHeader *mp_vc_wibble;
+	sMaterialHeader *			mp_material_header;
+	sMaterialUVWibble *			mp_uv_wibble;
+	sMaterialPassHeader *		mp_material_pass;
+	uint16 *					mp_shadow_volume_mesh;
+	sShadowEdge *				mp_shadow_edge;
+
+	Mth::Vector					m_sphere;
+
+	sMesh						**mpp_mesh_list;
+	uint16						m_num_meshes;
+	uint16						m_num_filled_meshes;
+
+	uint16						m_num_opaque_meshes;
+	uint16						m_num_pre_semitrans_meshes;
+	uint16						m_num_dynamic_semitrans_meshes;
+	uint16						m_num_post_semitrans_meshes;
+
+//	uint16						m_num_opaque_entries;
+//	uint16						m_num_semitrans_entries;
+
+	// For mesh heirarchies
+	Nx::CHierarchyObject*		mp_hierarchyObjects;						// array of hierarchy objects
+	int							m_numHierarchyObjects;						// number of hierarchy objects
+};
+
+
+sScene	*LoadScene( const char *Filename, sScene *pScene );
+void	DeleteScene( sScene *pScene );
+
+#define MATERIAL_GROUP_SU	(1<<0)
+#define MATERIAL_GROUP_CP	(1<<1)
+#define MATERIAL_GROUP_A	(1<<2)
+#define MATERIAL_GROUP_B	(1<<3)
+#define MATERIAL_GROUP_C	(1<<4)
+
+void ResetMaterialChange( void );
+void MaterialSubmit( sMesh * p_mesh, sScene *pScene = NULL );
+
+void MaterialBuild(  sMesh * p_mesh, sScene * p_scene, bool bl, bool tx ); 
+
+} // namespace NxNgc
+
+
+#endif // __SCENE_H
+
+
diff --git a/Code/Gfx/NGC/NX/sprite.cpp b/Code/Gfx/NGC/NX/sprite.cpp
new file mode 100644
index 0000000..6d12983
--- /dev/null
+++ b/Code/Gfx/NGC/NX/sprite.cpp
@@ -0,0 +1,528 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "scene.h"
+#include "render.h"
+#include "sprite.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+namespace NxNgc
+{
+
+
+/******************************************************************/
+/*                                                                */
+/* SDraw2D														  */
+/*                                                                */
+/******************************************************************/
+
+SDraw2D *SDraw2D::sp_2D_draw_list = NULL;
+
+
+SDraw2D::SDraw2D( float pri, bool hide )
+{
+	m_hidden = hide;
+	m_pri = pri;
+
+	mp_next = NULL;
+
+	// add to draw list
+	if( !m_hidden )
+	{
+		InsertDrawList();
+	}
+}
+
+
+
+SDraw2D::~SDraw2D()
+{
+	// Try removing from draw list
+	RemoveDrawList();
+}
+
+
+
+void SDraw2D::SetPriority( float pri )
+{
+	if (m_pri != pri)
+	{
+		m_pri = pri;
+
+		// By removing and re-inserting, we re-sort the list
+		if (!m_hidden)
+		{
+			RemoveDrawList();
+			InsertDrawList();
+		}
+	}
+}
+
+
+
+void SDraw2D::SetHidden( bool hide )
+{
+	if (m_hidden != hide)
+	{
+		m_hidden = hide;
+		if (hide)
+		{
+			RemoveDrawList();
+		} else {
+			InsertDrawList();
+		}
+	}
+}
+
+
+
+void SDraw2D::DrawAll()
+{
+	set_blend_mode( vBLEND_MODE_BLEND );
+
+	SDraw2D *pDraw = sp_2D_draw_list;
+	while (pDraw)
+	{
+		if (!pDraw->m_hidden)
+		{
+			pDraw->BeginDraw();
+			pDraw->Draw();
+			pDraw->EndDraw();
+		}
+		pDraw = pDraw->mp_next;
+	}
+}
+
+
+
+void SDraw2D::InsertDrawList()
+{
+	if (!sp_2D_draw_list ||							// Empty List
+		(m_pri <= sp_2D_draw_list->m_pri))	// Start List
+	{
+		mp_next = sp_2D_draw_list;
+		sp_2D_draw_list = this;
+	} else {				// Insert
+		SDraw2D *p_cur = sp_2D_draw_list;
+	
+		// Find where to insert
+		while(p_cur->mp_next)
+		{
+			if (m_pri <= p_cur->mp_next->m_pri)
+				break;
+
+			p_cur = p_cur->mp_next;
+		}
+
+		// Insert at this point
+		mp_next = p_cur->mp_next;
+		p_cur->mp_next = this;
+	}
+}
+
+
+
+void SDraw2D::RemoveDrawList()
+{
+	// Take out from draw list
+	if (sp_2D_draw_list == this)
+	{
+		sp_2D_draw_list = mp_next;
+	} 
+	else if (sp_2D_draw_list)
+	{
+		SDraw2D *p_cur = sp_2D_draw_list;
+
+		while(p_cur->mp_next)
+		{
+			if (p_cur->mp_next == this)
+			{
+				p_cur->mp_next = mp_next;
+				break;
+			}
+
+			p_cur = p_cur->mp_next;
+		}
+	}
+}
+
+	
+
+//typedef struct
+//{
+//	float		x, y, z;
+//	float		rhw;
+//	D3DCOLOR	col;
+//	float		u, v;
+//}
+//sSpriteVert;
+
+
+
+sSprite::sSprite( float pri ) : SDraw2D( pri, true )
+{
+	mp_texture = NULL;
+	
+//	if( D3D_OK != D3DDevice_CreateVertexBuffer(	sizeof( sSpriteVert ) * 6,
+//												0,										// Usage - ignored.
+//												0,										// FVF - ignored.
+//												0,										// Pool - ignored.
+//												&p_vertex_buffer ))
+//	{
+//		exit( 0 );
+//	}
+//
+//
+}
+
+sSprite::~sSprite()
+{
+//	p_vertex_buffer->Release();
+}
+
+
+
+
+
+void sSprite::BeginDraw( void )
+{
+	// Nothing required here right now.
+}
+
+//static inline void _GDWriteXFCmd(u16 addr, u32 val)
+//{
+//	GXWGFifo.u8 = GX_LOAD_XF_REG;
+//	GXWGFifo.u16 = 0; // 0 means one value follows
+//	GXWGFifo.u16 = addr;
+//	GXWGFifo.u32 = val;
+//}
+//
+//static inline void _GDWriteBPCmd(u32 regval)
+//{
+//	GXWGFifo.u8 = GX_LOAD_BP_REG;
+//	GXWGFifo.u32 = regval;
+//}
+//
+//void _SetGenMode( u8			nTexGens,
+//				 u8			nChans,
+//				 u8			nTevs,
+//				 u8			nInds,
+//				 GXCullMode	cm )
+//{
+//	static u8 cm2hw[] = { 0, 2, 1, 3 };
+//	_GDWriteXFCmd( XF_NUMTEX_ID, XF_NUMTEX( nTexGens ));
+//	_GDWriteXFCmd( XF_NUMCOLORS_ID, XF_NUMCOLORS( nChans ));
+//	_GDWriteBPCmd( GEN_MODE( nTexGens, nChans, 0, (nTevs-1),
+//							cm2hw[cm], nInds, 0, GEN_MODE_ID ));
+//}
+
+
+void sSprite::Draw( void )
+{
+	GXColor current_color;
+	current_color.a = (m_rgba&0xff);
+	if ( current_color.a == 0 ) return;
+	current_color.b = ((m_rgba&0xff00)>>8);
+	current_color.g = ((m_rgba&0xff0000)>>16);
+	current_color.r = ((m_rgba&0xff000000)>>24);
+
+	float start_screen_x = m_xpos;
+	float start_screen_y = m_ypos;
+
+	float x0,y0,x1,y1;
+
+	float u0 = 0.0f;
+	float v0 = 0.0f;
+	float u1 = 0.0f;
+	float v1 = 0.0f;
+
+	if ( mp_texture )
+	{
+		u0 = 0.0f;
+		v0 = 1.0f;
+		u1 = (float)mp_texture->BaseWidth / (float)mp_texture->ActualWidth;       
+		v1 = 1.0f - ( (float)mp_texture->BaseHeight / (float)mp_texture->ActualHeight ); 
+	}
+
+	// Check for flip
+	float abs_scale_x = m_scale_x;
+	float abs_scale_y = m_scale_y;
+	if (abs_scale_x < 0.0f)
+	{
+		float temp = u0;
+		u0 = u1;
+		u1 = temp;
+		abs_scale_x = -abs_scale_x;
+	}
+	if (abs_scale_y < 0.0f)
+	{
+		float temp = v0;
+		v0 = v1;
+		v1 = temp;
+		abs_scale_y = -abs_scale_y;
+	}
+
+	x0 = (-m_xhot * abs_scale_x);
+	y0 = (-m_yhot * abs_scale_y);
+
+	if ( mp_texture )
+	{
+		x1 = x0 + ( /*m_width*/mp_texture->BaseWidth * abs_scale_x);
+		y1 = y0 + ( /*m_height*/mp_texture->BaseHeight * abs_scale_y);
+	//	x1 = x0 + ( m_width * abs_scale_x);
+	//	y1 = y0 + ( m_height * abs_scale_y);
+	}
+	else
+	{
+		x1 = x0 + ( m_width * abs_scale_x);
+		y1 = y0 + ( m_height * abs_scale_y);
+	}
+	
+	Mth::Vector p0(x0, y0, 0.0f, 0.0f);
+	Mth::Vector p1(x1, y0, 0.0f, 0.0f);
+	Mth::Vector p2(x0, y1, 0.0f, 0.0f);
+	Mth::Vector p3(x1, y1, 0.0f, 0.0f);
+
+	if (m_rot != 0.0f)
+	{
+		p0.RotateZ(m_rot);
+		p1.RotateZ(m_rot);
+		p2.RotateZ(m_rot);
+		p3.RotateZ(m_rot);
+	}
+
+	p0[X] += start_screen_x;
+	p1[X] += start_screen_x;
+	p2[X] += start_screen_x;
+	p3[X] += start_screen_x;
+	
+	p0[Y] += start_screen_y;
+	p1[Y] += start_screen_y;
+	p2[Y] += start_screen_y;
+	p3[Y] += start_screen_y;
+
+	if( mp_texture == NULL )
+	{
+		// No alpha map.
+		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE ); 
+
+		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+		GX::SetTevColorInOp(	 GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+		GX::SetTevOrder( 		 GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL );
+
+//		GX::SetNumTexGens( 0 );
+//		GX::SetNumTevStages( 1 );
+		//GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY );
+//		GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+//		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+//		GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//		GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//		GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//		GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+	
+		// Set current vertex descriptor to enable position and color0.
+		// Both use 8b index to access their data arrays.
+		GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+	
+		// Set material color.
+		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+		GX::SetChanAmbColor( GX_COLOR0A0, current_color );
+	
+		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+	
+		// Send coordinates.
+		GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+			GX::Position3f32(p0[X], p0[Y], -1.0f);
+			GX::Position3f32(p1[X], p1[Y], -1.0f);
+			GX::Position3f32(p3[X], p3[Y], -1.0f);
+			GX::Position3f32(p2[X], p2[Y], -1.0f);
+		GX::End();
+	
+		return;
+	}
+	
+//	// Upload the texture.
+//	GXTexObj	texObj;
+//	GXInitTexObj(	&texObj,
+//					mp_texture->pTexelData,
+//					mp_texture->ActualWidth,
+//					mp_texture->ActualHeight,
+//					(GXTexFmt)mp_texture->format,
+//					GX_CLAMP,
+//					GX_CLAMP,
+//					GX_FALSE );
+//	GXLoadTexObj(	&texObj,
+//					GX_TEXMAP0 );
+
+	GX::UploadTexture( mp_texture->pTexelData,
+					   mp_texture->ActualWidth,
+					   mp_texture->ActualHeight,
+					   (GXTexFmt)mp_texture->format,
+					   GX_CLAMP,
+					   GX_CLAMP,
+					   GX_FALSE,
+					   GX_LINEAR,
+					   GX_LINEAR,
+					   0.0f,
+					   0.0f,
+					   0.0f,
+					   GX_FALSE,
+					   GX_FALSE,
+					   GX_ANISO_1,
+					   GX_TEXMAP0 );
+
+	if ( ( mp_texture->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA ) && mp_texture->pAlphaData )
+	{
+//		// Upload alpha map.
+//		GXTexObj	alphaObj;
+//		GXInitTexObj(	&alphaObj,
+//						mp_texture->pAlphaData,
+//						mp_texture->ActualWidth,
+//						mp_texture->ActualHeight,
+//						(GXTexFmt)mp_texture->format,
+//						GX_CLAMP,
+//						GX_CLAMP,
+//						GX_FALSE );
+//		GXLoadTexObj(	&alphaObj,
+//						GX_TEXMAP1 );
+
+		GX::UploadTexture( mp_texture->pAlphaData,
+						   mp_texture->ActualWidth,
+						   mp_texture->ActualHeight,
+						   (GXTexFmt)mp_texture->format,
+						   GX_CLAMP,
+						   GX_CLAMP,
+						   GX_FALSE,
+						   GX_LINEAR,
+						   GX_LINEAR,
+						   0.0f,
+						   0.0f,
+						   0.0f,
+						   GX_FALSE,
+						   GX_FALSE,
+						   GX_ANISO_1,
+						   GX_TEXMAP1 );
+
+		// Has alpha map.
+		GX::SetTexChanTevIndCull( 2, 1, 2, 0, GX_CULL_NONE ); 
+		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+		GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, mp_texture->ActualWidth, mp_texture->ActualHeight );
+		GX::SetTexCoordScale( GX_TEXCOORD1, GX_TRUE, mp_texture->ActualWidth, mp_texture->ActualHeight );
+		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+		GX::SetTexCoordGen( GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR0A0);
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVREG0,
+								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO,
+							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVREG0 );
+
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+								 GX_TEV_SWAP0, GX_TEV_SWAP1 );
+		GX::SetTevColorInOp( GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0,
+							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+	}
+	else
+	{
+		// No alpha map.
+		GX::SetTexChanTevIndCull( 1, 1, 1, 0, GX_CULL_NONE ); 
+		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+		GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, mp_texture->ActualWidth, mp_texture->ActualHeight );
+		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO,
+							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+	}
+
+	// Set current vertex descriptor to enable position and color0.
+	// Both use 8b index to access their data arrays.
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+	// Set material color.
+	GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+	GX::SetChanAmbColor( GX_COLOR0A0, current_color );
+
+	GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//	GX::SetNumChans( 1 );
+
+//	float uone = u1;		//(float)mp_texture->BaseWidth / (float)mp_texture->ActualWidth;
+//	float vzro = v1;		//( (float)mp_texture->BaseHeight / (float)mp_texture->ActualHeight );
+
+	// Send coordinates.
+	GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+		GX::Position3f32(p0[X], p0[Y], -1.0f);
+		GX::TexCoord2f32(u0, v0);
+		GX::Position3f32(p1[X], p1[Y], -1.0f);
+		GX::TexCoord2f32(u1, v0);
+		GX::Position3f32(p3[X], p3[Y], -1.0f);
+		GX::TexCoord2f32(u1, v1);
+		GX::Position3f32(p2[X], p2[Y], -1.0f);
+		GX::TexCoord2f32(u0, v1);
+	GX::End();
+}
+
+
+
+void sSprite::EndDraw( void )
+{
+//	if( mp_texture == NULL )
+//	{
+//		return;
+//	}
+//
+//	D3DDevice_SetPixelShader( 0 );
+//	set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+//	D3DDevice_SetRenderState( D3DRS_LIGHTING, FALSE );
+//
+//	D3DDevice_SetTexture( 0, (LPDIRECT3DBASETEXTURE8)mp_texture->pD3DTexture );
+//	if( mp_texture->pD3DPalette )
+//	{
+//		D3DDevice_SetPalette( 0, mp_texture->pD3DPalette );
+//	}
+//	
+//	D3DDevice_SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE2X );
+//	D3DDevice_SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_DIFFUSE );
+//	D3DDevice_SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_TEXTURE );
+//	D3DDevice_SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_MODULATE2X );
+//	D3DDevice_SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE );
+//	D3DDevice_SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_TEXTURE );
+//
+//	D3DDevice_SetStreamSource( 0, p_vertex_buffer, sizeof( sSpriteVert ));
+//	EngineGlobals.p_Device->DrawPrimitive( D3DPT_TRIANGLELIST, 0, 2 );
+}
+
+
+
+
+
+
+} // namespace NxNgc
+
+
diff --git a/Code/Gfx/NGC/NX/sprite.h b/Code/Gfx/NGC/NX/sprite.h
new file mode 100644
index 0000000..e7e5950
--- /dev/null
+++ b/Code/Gfx/NGC/NX/sprite.h
@@ -0,0 +1,76 @@
+#ifndef __SPRITE_H
+#define __SPRITE_H
+
+#include "texture.h"
+
+namespace NxNgc
+{
+
+struct SDraw2D
+{
+					SDraw2D( float pri = 0.0f, bool hide = true );
+	virtual			~SDraw2D( void );
+
+	void			SetPriority( float pri );
+	float			GetPriority( void ) const;
+
+	void			SetHidden( bool hide );
+	bool			IsHidden( void ) const;
+
+	// members
+	SDraw2D			*mp_next;
+
+	// Statics
+	static void		DrawAll( void );
+
+private:
+	void			InsertDrawList( void );
+	void			RemoveDrawList( void );
+
+	virtual void	BeginDraw( void ) = 0;
+	virtual void	Draw( void ) = 0;
+	virtual void	EndDraw( void ) = 0;
+
+	// Not even the derived classes should have direct access
+	bool			m_hidden;
+	float			m_pri;
+
+	// 2D draw list (sorted by priority);
+	static SDraw2D	*sp_2D_draw_list;
+};
+
+
+struct sSprite : public SDraw2D
+{
+	public:
+					sSprite( float pri = 0.0f );
+					~sSprite();
+
+	sTexture		*mp_texture;
+
+	float			m_xpos;
+	float			m_ypos;
+	uint16			m_width;
+	uint16			m_height;
+	float			m_scale_x;
+	float			m_scale_y;
+	float			m_xhot;
+	float			m_yhot;
+	float			m_rot;
+	uint32			m_rgba;
+
+private:
+
+//	IDirect3DVertexBuffer8	*p_vertex_buffer;
+	
+	void					BeginDraw();
+	void					Draw();
+	void					EndDraw(void);
+};
+
+
+} // namespace NxNgc
+
+
+#endif // __SPRITE_H
+
diff --git a/Code/Gfx/NGC/NX/texture.cpp b/Code/Gfx/NGC/NX/texture.cpp
new file mode 100644
index 0000000..507fa4f
--- /dev/null
+++ b/Code/Gfx/NGC/NX/texture.cpp
@@ -0,0 +1,255 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "chars.h"
+#include "texture.h"
+#include "sys/ngc/p_gx.h"
+
+namespace NxNgc
+{
+
+uint8*					TextureBuffer;
+//sTexture* Textures		= NULL;
+uint32 NumTextures		= 0;
+uint32 NumTextureGroups	= 0;
+Lst::HashTable< sTexture > *pTextureTable	= NULL;
+
+
+
+
+sTexture::sTexture()
+{
+	pTexelData = NULL;
+	pAlphaData = NULL;
+	pOldAlphaData = NULL;
+	flags = 0;
+}
+
+sTexture::~sTexture()
+{
+	if ( pTexelData )
+	{
+		delete pTexelData;
+	}
+	if ( pAlphaData && ( ( flags & TEXTURE_FLAG_CHANNEL_MASK ) == TEXTURE_FLAG_CHANNEL_GREEN ) )
+	{
+		delete pAlphaData;
+	}
+	if ( ( flags & TEXTURE_FLAG_OLD_DATA ) && pOldAlphaData )
+	{
+		delete pOldAlphaData;
+	}
+}
+
+
+
+// Eeeek - the .img contains PS2 specific register values for bit depth.
+// Use these values to convert them.
+#define PSMCT32		0x00
+#define PSMCT24		0x01
+#define PSMCT16		0x02
+#define PSMCT16S	0x0A
+#define PS_GPU24	0x12
+#define PSMT8		0x13
+#define PSMT4		0x14
+#define PSMT8H		0x1B
+#define PSMT4HL		0x24
+#define PSMT4HH		0x2C
+#define PSMZ32		0x30
+#define PSMZ24		0x31
+#define PSMZ16		0x32
+#define PSMZ16S		0x3A
+
+const static uint32 TEXTURE_VERSION = 2;
+
+sTexture *LoadTexture( const char *p_filename )
+{
+	sTexture *p_texture = NULL;
+
+	void *p_FH = File::Open( p_filename, "rb" );
+
+	if( p_FH )
+	{
+		uint32 version;
+		uint32 checksum;
+		uint32 width;
+		uint32 height;
+		uint32 depth;
+		uint32 levels;
+		uint32 rwidth;
+		uint32 rheight;
+		uint16 alphamap;
+		uint16 hasholes;
+
+		uint32 FileSize = File::GetFileSize(p_FH);
+
+		// Load the actual texture
+
+		p_texture = new sTexture();
+
+		File::Read( &version,  4, 1, p_FH );
+		File::Read( &checksum, 4, 1, p_FH );
+		File::Read( &width,    4, 1, p_FH );
+		File::Read( &height,   4, 1, p_FH );
+		File::Read( &depth,    4, 1, p_FH );
+		File::Read( &levels,   4, 1, p_FH );
+		File::Read( &rwidth,   4, 1, p_FH );
+		File::Read( &rheight,  4, 1, p_FH );
+		File::Read( &alphamap, 2, 1, p_FH );
+		File::Read( &hasholes, 2, 1, p_FH );
+
+		Dbg_MsgAssert( version <= TEXTURE_VERSION, ("Illegal texture version number: %d (The newest version we deal with is: %d)\n", version, TEXTURE_VERSION ));
+		Dbg_MsgAssert( depth == 32, ("We only deal with 32-bit textures.\n"));
+	
+		uint tsize = FileSize-(9*4);
+
+		p_texture->flags = 0;
+		p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN;
+
+		Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+		if ( tsize == ( width * height * 4 ) )
+		{
+			p_texture->format		= (uint8)GX_TF_RGBA8;
+		
+			p_texture->pTexelData = new uint8[tsize];
+			p_texture->byte_size = tsize;
+			Dbg_MsgAssert(p_texture->pTexelData, ("couldn't allocate buffer for texture file %s\n", p_filename));
+			File::Read( p_texture->pTexelData, 1, tsize, p_FH );
+		}
+		else
+		{
+			p_texture->format		= (uint8)GX_TF_CMPR;
+		
+			if ( alphamap )
+			{
+				tsize = tsize / 2;
+			}
+
+			p_texture->pTexelData = new uint8[tsize];
+			p_texture->byte_size = tsize;
+			Dbg_MsgAssert(p_texture->pTexelData, ("couldn't allocate buffer for texture file %s\n", p_filename));
+			File::Read( p_texture->pTexelData, 1, tsize, p_FH );
+
+			if ( alphamap )
+			{
+				p_texture->pAlphaData = new uint8[tsize];
+				Dbg_MsgAssert(p_texture->pAlphaData, ("couldn't allocate buffer for alpha file %s\n", p_filename));
+				File::Read( p_texture->pAlphaData, 1, tsize, p_FH );
+				p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA;
+			}
+		}
+		Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+
+		p_texture->Checksum		= checksum;
+		p_texture->BaseWidth	= width;
+		p_texture->BaseHeight	= height;
+		p_texture->Levels		= levels;
+		p_texture->ActualWidth	= rwidth;
+		p_texture->ActualHeight	= rheight;
+		p_texture->flags |= hasholes ? NxNgc::sTexture::TEXTURE_FLAG_HAS_HOLES : 0;
+
+		File::Close( p_FH );
+	}
+	
+	return p_texture;
+}
+
+
+
+
+
+sTexture* GetTexture( uint32 checksum )
+{
+	if( pTextureTable )
+	{
+		pTextureTable->IterateStart();
+		sTexture *p_tex = pTextureTable->IterateNext();
+		while( p_tex )
+		{
+			if( p_tex->Checksum == checksum )
+			{
+				return p_tex;
+			}
+			p_tex = pTextureTable->IterateNext();
+		}
+	}
+	return NULL;
+}
+
+
+
+
+
+bool sTexture::SetRenderTarget( int width, int height, int depth, int z_depth )
+{
+//	Checksum		= 0;;
+//	BaseWidth		= width;
+//	BaseHeight		= height;
+//	ActualWidth		= width;
+//	ActualHeight	= height;
+//	Levels			= 0;
+//	pAlphaData		= NULL;
+//	format			= GX_TF_RGBA8;
+//	
+//	pTexelData		= new uint8[width*height*4];
+//
+	return true;
+}
+
+extern int g_id;
+
+GXTexMapID sTexture::Upload( GXTexMapID id, uint8 flags, float k )
+{
+	GXTexMapID	rv = id;
+
+	// Texels.
+	GX::UploadTexture(  pTexelData,
+						ActualWidth,
+						ActualHeight,
+						((GXTexFmt)format),
+						flags & (1<<5) ? GX_CLAMP : GX_REPEAT,
+						flags & (1<<6) ? GX_CLAMP : GX_REPEAT,
+						Levels > 1 ? GX_TRUE : GX_FALSE,
+						Levels > 1 ? GX_LIN_MIP_LIN : GX_LINEAR,
+						GX_LINEAR,
+						0.0f,
+						Levels > 1 ? Levels - 1 : 0.0f,
+						Levels > 1 ? k : 0.0f,
+						GX_FALSE,
+						GX_TRUE,
+						GX_ANISO_1,
+						rv ); 
+	rv = (GXTexMapID)(((int)rv)+1);
+
+	// Alpha.
+	if ( flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA )
+	{
+		GX::UploadTexture(  pAlphaData,
+							ActualWidth,
+							ActualHeight,
+							((GXTexFmt)format),
+							flags & (1<<5) ? GX_CLAMP : GX_REPEAT,
+							flags & (1<<6) ? GX_CLAMP : GX_REPEAT,
+							Levels > 1 ? GX_TRUE : GX_FALSE,
+							Levels > 1 ? GX_LIN_MIP_LIN : GX_LINEAR,
+							GX_LINEAR,
+							0.0f,
+							Levels > 1 ? Levels - 1 : 0.0f,
+							Levels > 1 ? k : 0.0f,
+							GX_FALSE,
+							GX_TRUE,
+							GX_ANISO_1,
+							rv ); 
+		rv = (GXTexMapID)(((int)rv)+1);
+	}
+
+	return rv;
+}
+
+
+} // namespace NxNgc
+
+
diff --git a/Code/Gfx/NGC/NX/texture.h b/Code/Gfx/NGC/NX/texture.h
new file mode 100644
index 0000000..df13882
--- /dev/null
+++ b/Code/Gfx/NGC/NX/texture.h
@@ -0,0 +1,66 @@
+#ifndef __TEXTURE_H
+#define __TEXTURE_H
+
+#include 
+#include 
+
+namespace NxNgc
+{
+
+struct sTexture
+{
+	enum ETextureFlags
+	{
+		TEXTURE_FLAG_HAS_HOLES				= (1<<0),
+		TEXTURE_FLAG_HAS_ALPHA				= (1<<1),
+		TEXTURE_FLAG_CHANNEL_GREEN			= (1<<2),
+		TEXTURE_FLAG_CHANNEL_RED			= (1<<3),
+		TEXTURE_FLAG_CHANNEL_BLUE			= (1<<4),
+		TEXTURE_FLAG_SINGLE_OWNER			= (1<<5),
+		TEXTURE_FLAG_OLD_DATA				= (1<<6),
+		TEXTURE_FLAG_REPLACED				= (1<<7),
+
+		TEXTURE_FLAG_CHANNEL_MASK			= ( TEXTURE_FLAG_CHANNEL_GREEN | TEXTURE_FLAG_CHANNEL_RED | TEXTURE_FLAG_CHANNEL_BLUE )
+	};
+
+						sTexture();
+						~sTexture();
+	
+	bool				SetRenderTarget( int width, int height, int depth, int z_depth );
+
+	GXTexMapID			Upload( GXTexMapID id, uint8 flags, float k );
+
+	uint32				Checksum;
+	uint16				BaseWidth, BaseHeight;		// The size of the D3D texture (will be power of 2).
+	uint16				ActualWidth, ActualHeight;	// The size of the texture itself (may not be power of 2).
+	uint8				Levels;
+//	uint8				HasHoles;
+	uint8				format;
+//	uint8				has_alpha;
+//	uint16				channel;
+//	uint16				single_owner;			// 1 means single owner.
+	uint16				flags;
+	uint32				byte_size;
+	uint8*				pTexelData;
+	uint8*				pAlphaData;
+	uint8*				pOldAlphaData;
+};
+
+
+sTexture* GetTexture( uint32 checksum );
+
+sTexture	*LoadTexture( const char *p_filename );
+void LoadTextureGroup(int Group);
+
+extern uint8 *TextureBuffer;
+//extern sTexture *Textures;
+extern Lst::HashTable< sTexture > *pTextureTable;
+
+extern uint32 NumTextures, NumTextureGroups;
+extern uint NextTBP, LastCBP;
+
+} // namespace NxNgc
+
+#endif // __TEXTURE_H
+
+
diff --git a/Code/Gfx/NGC/NX/types.h b/Code/Gfx/NGC/NX/types.h
new file mode 100644
index 0000000..2e27463
--- /dev/null
+++ b/Code/Gfx/NGC/NX/types.h
@@ -0,0 +1,16 @@
+#ifndef __TYPES_H
+#define __TYPES_H
+
+typedef unsigned int 		uint;
+typedef unsigned long long	uint64;
+typedef unsigned int		uint32;
+typedef unsigned short		uint16;
+typedef unsigned char		uint8;
+
+typedef signed int 			sint;
+typedef signed long long	sint64;
+typedef signed int			sint32;
+typedef signed short		sint16;
+typedef signed char			sint8;
+
+#endif // __TYPES_H
diff --git a/Code/Gfx/NGC/blur.cpp b/Code/Gfx/NGC/blur.cpp
new file mode 100644
index 0000000..76505fa
--- /dev/null
+++ b/Code/Gfx/NGC/blur.cpp
@@ -0,0 +1,221 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		blur.cpp												**
+**																			**
+**	Created:		06/21/00	-	mjb										**
+**																			**
+**	Description:	Motion-Blur effect										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+//#include 
+//#include 
+//#include 
+//#include 
+//				 
+//#include 
+//
+///*****************************************************************************
+//**								DBG Information								**
+//*****************************************************************************/
+//
+//
+///*****************************************************************************
+//**								  Externals									**
+//*****************************************************************************/
+//
+//extern int skyFrameBit;
+//
+//namespace Gfx
+//{
+//
+///*****************************************************************************
+//**								   Defines									**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								Private Types								**
+//*****************************************************************************/
+//
+//typedef struct
+//{
+//	sceGifTag       giftag;
+//	u_int       	data_buf[0x80];
+//} GsClearData __attribute__((aligned(16)));
+//
+///*****************************************************************************
+//**								 Private Data								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								 Public Data								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Private Prototypes							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							   Private Functions							**
+//*****************************************************************************/
+//
+//void copy_frame(void)
+//{
+//	int i,nloop;
+//	GsClearData gcd;
+//
+//	nloop = 25;
+//
+//	SCE_GIF_CLEAR_TAG(&gcd.giftag);
+//	gcd.giftag.NLOOP = nloop;
+//	gcd.giftag.EOP = 1;
+//	gcd.giftag.NREG = 1;
+//	gcd.giftag.REGS0 = 0xe;	// A_D
+//
+//	i=0;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000001a; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000001; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000004c; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x000a0046; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000047; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000040; gcd.data_buf[i+1] = (u_int)0x00df0000; gcd.data_buf[i] = (u_int)0x027f0000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000042; gcd.data_buf[i+1] = (u_int)0x00000040; gcd.data_buf[i] = (u_int)0x00000064; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000008; gcd.data_buf[i+1] = (u_int)0x0000037c; gcd.data_buf[i] = (u_int)0x009fc00a; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000018; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000046; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000001; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000049; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000006; gcd.data_buf[i+1] = (u_int)0x00000006; gcd.data_buf[i] = (u_int)0xa8028000; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000014; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000060; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000003b; gcd.data_buf[i+1] = (u_int)0x00000080; gcd.data_buf[i] = (u_int)0x00000080; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000001; gcd.data_buf[i+1] = (u_int)0x3f800000; gcd.data_buf[i] = (u_int)0x80808080; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000000; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0000015e; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000001; gcd.data_buf[i] = (u_int)0x00100000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e002800; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e102800; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000003f; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00100000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000001; gcd.data_buf[i] = (u_int)0x00100000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e102800; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e102800; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000003f; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000008; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000005; i+=4;
+//
+//	FlushCache(0);
+//
+//	*D2_QWC = gcd.giftag.NLOOP+1;
+//
+//	*D2_MADR = (u_int)&gcd.giftag & 0x0fffffff;
+//	FlushCache(0);
+//	*D2_CHCR = (1 << 8) | 1;
+//
+//	while( (*D2_CHCR & 0x0100) || (*GIF_STAT & 0x0c00) || ((*GS_CSR & 0x4000) == 0) );
+//
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//void copy_frame1(void)
+//{
+//	int i,nloop;
+//	GsClearData gcd;
+//
+//	nloop = 25;
+//
+//	SCE_GIF_CLEAR_TAG(&gcd.giftag);
+//	gcd.giftag.NLOOP = nloop;
+//	gcd.giftag.EOP = 1;
+//	gcd.giftag.NREG = 1;
+//	gcd.giftag.REGS0 = 0xe;	// A_D
+//
+//	i=0;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000001a; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000001; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000004c; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x000a0000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000047; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000040; gcd.data_buf[i+1] = (u_int)0x00df0000; gcd.data_buf[i] = (u_int)0x027f0000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000042; gcd.data_buf[i+1] = (u_int)0x00000040; gcd.data_buf[i] = (u_int)0x00000064; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000008; gcd.data_buf[i+1] = (u_int)0x0000037c; gcd.data_buf[i] = (u_int)0x009fc00a; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000018; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00080008; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000046; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000001; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000049; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000006; gcd.data_buf[i+1] = (u_int)0x00000006; gcd.data_buf[i] = (u_int)0xa80288c0; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000014; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000060; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000003b; gcd.data_buf[i+1] = (u_int)0x00000080; gcd.data_buf[i] = (u_int)0x00000080; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000001; gcd.data_buf[i+1] = (u_int)0x3f800000; gcd.data_buf[i] = (u_int)0x80808080; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000000; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0000015e; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000001; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e002800; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e002800; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000003f; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00100000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000001; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000003; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e102800; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000004; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x0e002800; i+=4;
+//
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x0000003f; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000000; i+=4;
+//	gcd.data_buf[i+3] = (u_int)0x0; gcd.data_buf[i+2] = (u_int)0x00000008; gcd.data_buf[i+1] = (u_int)0x00000000; gcd.data_buf[i] = (u_int)0x00000005; i+=4;
+//
+//	FlushCache(0);
+//
+//	*D2_QWC = gcd.giftag.NLOOP+1;
+//
+//	*D2_MADR = (u_int)&gcd.giftag & 0x0fffffff;
+//	FlushCache(0);
+//	*D2_CHCR = (1 << 8) | 1;
+//
+//	while( (*D2_CHCR & 0x0100) || (*GIF_STAT & 0x0c00) || ((*GS_CSR & 0x4000) == 0) );
+//
+//}
+//
+///*****************************************************************************
+//**							   Public Functions								**
+//*****************************************************************************/
+//
+//void	Blur( void )
+//{
+//	if (skyFrameBit & 0x1)
+//	{
+//		copy_frame1();
+//	}
+//	else
+//	{
+//		copy_frame();
+//	}
+//
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//
+//} // namespace Gfx
+//
+//
\ No newline at end of file
diff --git a/Code/Gfx/NGC/p_NxGeom.cpp b/Code/Gfx/NGC/p_NxGeom.cpp
new file mode 100644
index 0000000..f836630
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxGeom.cpp
@@ -0,0 +1,2125 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxGeom.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  3/4/2002
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+static s32	last_audio_update = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void do_audio_update( void )
+{
+	s32 t = OSGetTick();
+	if(( t < last_audio_update ) || ( OSDiffTick( t, last_audio_update ) > (s32)( OS_TIMER_CLOCK / 60 )))
+	{
+		last_audio_update = t;
+		Pcm::PCMAudio_Update();
+	}
+}
+	
+	
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcGeom::CNgcGeom() : mp_init_mesh_list( NULL ), m_mesh_array( NULL ), m_visible( 0x55 )
+{
+	mp_instance = NULL;
+//	mp_sector	= NULL;
+	m_active	= true;
+	m_mod.offset[X] = 0.0f;
+	m_mod.offset[Y] = 0.0f;
+	m_mod.offset[Z] = 0.0f;
+	m_mod.offset[W] = 0.0f;
+	m_mod.scale[X] = 1.0f;
+	m_mod.scale[Y] = 1.0f;
+	m_mod.scale[Z] = 1.0f;
+	m_mod.scale[W] = 1.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcGeom::~CNgcGeom( void )
+{
+	DestroyMeshArray();
+
+	if( mp_instance != NULL )
+	{
+		delete mp_instance;
+		mp_instance = NULL;
+	}
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Mesh List Functions
+//
+// The mesh list is only for initialization of the CGeom.  As we find all the
+// meshes for the CGeoms, we add them to the temporary lists.  When we are
+// done, the list is copied into a permanent array.
+//
+// All the list functions use the TopDownHeap for memory allocation.
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::InitMeshList( void )
+{
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+	mp_init_mesh_list = new Lst::Head< NxNgc::sMesh >;
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::AddMesh( NxNgc::sMesh *mesh )
+{
+	Dbg_Assert( mp_init_mesh_list );
+
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+	Lst::Node< NxNgc::sMesh > *node = new Lst::Node< NxNgc::sMesh > (mesh);
+	mp_init_mesh_list->AddToTail( node );
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::CreateMeshArray( void )
+{
+	if( !m_mesh_array )
+	{
+		Dbg_Assert( mp_init_mesh_list );
+
+		m_num_mesh = mp_init_mesh_list->CountItems();
+		if (m_num_mesh)
+		{
+			Lst::Node< NxNgc::sMesh > *mesh, *next;
+
+			m_mesh_array = new NxNgc::sMesh*[m_num_mesh];
+			int k = 0;
+			for( mesh = mp_init_mesh_list->GetNext(); mesh; mesh = next )
+			{
+				next			= mesh->GetNext();
+				m_mesh_array[k]	= mesh->GetData();
+				delete mesh;
+				k++;
+			}
+		}
+
+		// Delete temporary list.
+		delete mp_init_mesh_list;
+		mp_init_mesh_list = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Lst::Head< NxNgc::sMesh >	*CNgcGeom::GetMeshList( void )
+{
+	return mp_init_mesh_list;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::RegisterMeshArray( bool just_count )
+{
+	// Tells the engine to count or add the meshes for this sector to the rendering list for supplied scene id.
+	if( just_count )
+	{
+		mp_scene->GetEngineScene()->CountMeshes( m_num_mesh, m_mesh_array );
+	}
+	else
+	{
+		mp_scene->GetEngineScene()->AddMeshes( m_num_mesh, m_mesh_array );
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::DestroyMeshArray( void )
+{
+	if( m_mesh_array )
+	{
+		// Tells the engine to remove the meshes for this sector from the rendering list for supplied scene id.
+		if( mp_scene && mp_scene->GetEngineScene())
+		{
+			mp_scene->GetEngineScene()->RemoveMeshes( m_num_mesh, m_mesh_array );
+		}
+
+		// Now actually go through and delete each mesh.
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxNgc::sMesh *p_mesh = m_mesh_array[m];
+
+			// Delete pools.
+			if ( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_CLONED_POS )
+			{
+				delete p_mesh->mp_dl->mp_pos_pool;
+			}
+
+			if ( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_CLONED_COL )
+			{
+				delete p_mesh->mp_dl->mp_col_pool;
+			}
+
+			// Delete copied dl if it's there.
+			if ( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_CLONED_DL )
+			{
+				delete p_mesh->mp_dl;
+			}
+
+			delete p_mesh;
+		}
+
+		delete [] m_mesh_array;
+		m_mesh_array = NULL;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Generates an entirely new engine scene.                        */
+/* Used when instance-cloning CGeoms.                             */
+/*                                                                */
+/******************************************************************/
+NxNgc::sScene* CNgcGeom::GenerateScene( void )
+{
+	NxNgc::sScene *p_scene = new NxNgc::sScene();
+
+	memcpy( p_scene, mp_scene->GetEngineScene(), sizeof( NxNgc::sScene ) );
+
+	p_scene->m_flags				= SCENE_FLAG_CLONED_GEOM;
+	p_scene->m_is_dictionary		= false;
+
+	p_scene->m_num_meshes			= 0;		// No meshes as yet.
+	p_scene->m_num_filled_meshes	= 0;
+	p_scene->mpp_mesh_list			= NULL;
+
+	p_scene->mp_hierarchyObjects	= NULL;
+	p_scene->m_numHierarchyObjects	= 0;
+
+	p_scene->CountMeshes( m_num_mesh, m_mesh_array );
+	p_scene->CreateMeshArrays();
+	p_scene->AddMeshes( m_num_mesh, m_mesh_array );
+	p_scene->SortMeshes();
+
+
+
+	// Set the bounding box and bounding sphere.
+//	p_scene->m_bbox = m_bbox;
+//	p_scene->FigureBoundingVolumes();
+	
+	return p_scene;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CNgcGeom::plat_load_geom_data(CMesh* pMesh, CModel* pModel, bool color_per_material)
+{
+//	// The skeleton must exist by this point (unless it's a hacked up car).
+//	int numBones;
+//	numBones = pModel->GetSkeleton() ? pModel->GetSkeleton()->GetNumBones() : 1;
+//	
+//	Mth::Matrix temp;
+//	CNgcMesh *p_Ngc_mesh = static_cast( pMesh );
+//	mp_instance = new NxNgc::CInstance( p_Ngc_mesh->GetScene()->GetEngineScene(), temp, numBones, pModel->GetBoneTransforms() );
+//
+//    return true;
+	
+	
+	// The skeleton must exist by this point (unless it's a hacked up car).
+	int numBones;
+	numBones = pModel->GetNumBones();
+	
+	Mth::Matrix temp;
+	CNgcMesh *p_Ngc_mesh = static_cast( pMesh );
+	
+	// Store a pointer to the CNgcMesh, used for obtaining CAS poly removal data.
+	mp_mesh = p_Ngc_mesh;
+
+//	CNgcModel *p_Ngc_model = static_cast( pModel );
+	NxNgc::CInstance *p_instance = new NxNgc::CInstance( p_Ngc_mesh->GetScene()->GetEngineScene(), temp, numBones, pModel->GetBoneTransforms());
+	SetInstance( p_instance );
+	p_instance->SetModel( pModel );
+
+    return true;
+
+
+//	int numBones;
+//	numBones = pModel->GetSkeleton() ? pModel->GetSkeleton()->GetNumBones() : 0;
+//
+//	NxNgc::Mat temp;
+//	NxNgc::MatIdentity(temp);
+//	Dbg_Assert( mp_instance == NULL );
+//
+//	Dbg_Assert( pMesh );
+//	Dbg_Assert( pMesh->GetTextureDictionary() );
+//	NxNgc::sScene* pScene = ((Nx::CNgcTexDict*)pMesh->GetTextureDictionary())->GetEngineTextureDictionary();
+//	Dbg_Assert( pScene );
+//
+//	if ( numBones )
+//	{
+//		mp_matrices = ((CNgcModel*)pModel)->GetMatrices();
+//	}
+//	mp_instance = new NxNgc::CInstance(pScene, temp, numBones, mp_matrices ); 
+//
+//	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_active( bool active )
+{
+	if( mp_instance )
+	{
+		mp_instance->SetActive( active );
+	}	
+	else
+	{
+		m_active = active;
+		if( m_mesh_array )
+		{
+			for( uint m = 0; m < m_num_mesh; ++m )
+			{
+				NxNgc::sMesh *p_mesh = m_mesh_array[m];
+				p_mesh->SetActive( active );
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::plat_is_active( void ) const
+{
+	if( mp_instance )
+	{
+		return mp_instance->GetActive();
+	}	
+	return m_active;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_world_position( const Mth::Vector& pos )
+{
+//	return;
+	// Ensure w component is correct.
+
+	if( mp_instance )
+	{
+		mp_instance->GetTransform()->SetPos( pos );
+	}
+	else
+	{
+		// Go through and adjust the individual meshes.
+//		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+//			NxNgc::sMesh *p_mesh = m_mesh_array[i];
+//			p_mesh->mp_mod = &m_mod;
+
+			Mth::Vector delta_pos = pos - m_mod.offset;
+
+			m_mod.offset[X] = pos[X];
+			m_mod.offset[Y] = pos[Y];
+			m_mod.offset[Z] = pos[Z];
+
+			int num = plat_get_num_render_verts();
+			Mth::Vector * p_verts = new Mth::Vector[num];
+			plat_get_render_verts( p_verts );
+			for ( int lp = 0; lp < num; lp++ )
+			{
+				p_verts[lp] += delta_pos;
+			}
+			plat_set_render_verts( p_verts );
+			delete p_verts;
+
+
+//			Mth::Vector delta_pos( pos[X] - m_offset[X], pos[Y] - m_offset[Y], pos[Z] - m_offset[Z], 1.0f );
+//			p_mesh->mp_dl->m_sphere[X] += delta_pos[X];
+//			p_mesh->mp_dl->m_sphere[Y] += delta_pos[Y];
+//			p_mesh->mp_dl->m_sphere[Z] += delta_pos[Z];
+//			p_mesh->SetPosition( proper_pos );
+		}
+
+//		if ( mp_scene->GetEngineScene()->mp_scene_data )
+//		{
+//			Mth::Vector proper_pos( pos[X] - m_offset[X], pos[Y] - m_offset[Y], pos[Z] - m_offset[Z], 1.0f );
+//	
+//			float * pos = mp_scene->GetEngineScene()->mp_pos_pool;
+//			for ( int lp = 0; lp += mp_scene->GetEngineScene()->mp_scene_data->m_num_pos; lp++ )
+//			{
+//				pos[0] += proper_pos[X];
+//				pos[1] += proper_pos[Y];
+//				pos[2] += proper_pos[Z];
+//				pos += 3;
+//			}
+//			m_offset[X] = proper_pos[X];
+//			m_offset[Y] = proper_pos[Y];
+//			m_offset[Z] = proper_pos[Z];
+//		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CNgcGeom::plat_get_world_position() const
+{
+	static Mth::Vector pos;
+	pos.Set( m_mod.offset[X], m_mod.offset[Y], m_mod.offset[Z], 1.0f );
+	return pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::CBBox & CNgcGeom::plat_get_bounding_box( void ) const
+{
+	Dbg_Assert(mp_coll_tri_data);
+	return mp_coll_tri_data->GetBBox();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	if( mp_instance )
+	{
+		NxNgc::sScene *p_scene = mp_instance->GetScene();
+		if( p_scene )
+		{
+			if ( mp_instance->GetBoneTransforms() && p_scene->m_numHierarchyObjects )
+			{
+				// Car or something...
+				p_scene->m_sphere = boundingSphere;
+			}
+			else
+			{
+				if ( p_scene->mpp_mesh_list[0] )
+				{
+					p_scene->mpp_mesh_list[0]->mp_dl->mp_object_header->m_sphere = boundingSphere;
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector CNgcGeom::plat_get_bounding_sphere( void ) const
+{
+	if( mp_instance )
+	{
+		NxNgc::sScene *p_scene = mp_instance->GetScene();
+		if( p_scene )
+		{
+			if ( mp_instance->GetBoneTransforms() && p_scene->m_numHierarchyObjects )
+			{
+				// Car or something...
+				return p_scene->m_sphere;
+			}
+			else
+			{
+				if ( p_scene->mpp_mesh_list[0] )
+				{
+					return p_scene->mpp_mesh_list[0]->mp_dl->mp_object_header->m_sphere;
+				}
+			}
+		}
+	}
+	
+	return Mth::Vector( 0.0f, 0.0f, 0.0f, 10000.0f );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* pBoneMatrices, int numBones)
+{
+	if( mp_instance )
+	{
+		mp_instance->SetTransform( *pRootMatrix );
+	}
+    return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_bone_matrix_data( Mth::Matrix* pBoneMatrices, int numBones )
+{
+	if( mp_instance )
+	{
+		Mth::Matrix* p_bone_matrices	= mp_instance->GetBoneTransforms();
+		memcpy( p_bone_matrices, pBoneMatrices, numBones * sizeof( Mth::Matrix ));
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::plat_hide_polys( uint32 mask )
+{
+	if( mp_mesh )
+	{
+		// Obtain a pointer to the Ngc scene.
+		NxNgc::sScene *p_engine_scene = GetInstance()->GetScene();
+
+		// Request the scene to hide the relevant polys.
+		p_engine_scene->HidePolys( mask, mp_mesh->GetCASData(), mp_mesh->GetNumCASData());
+	}
+	
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_visibility( uint32 mask )
+{
+	// Set values
+	m_visible = mask & 0xff;
+
+	if( mp_instance )
+	{
+		mp_instance->SetVisibility( mask & 0xff );
+	}	
+//	else
+//	{
+		if( m_mesh_array )
+		{
+			for( uint m = 0; m < m_num_mesh; ++m )
+			{
+				NxNgc::sMesh *p_mesh = m_mesh_array[m];
+				p_mesh->SetVisibility( mask & 0xff );
+			}
+		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 CNgcGeom::plat_get_visibility( void ) const
+{
+	return m_visible | 0xFFFFFF00;
+}
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_color( Image::RGBA rgba )
+{
+	if( mp_instance && mp_instance->GetBoneTransforms() )
+	{
+		if( mp_instance && mp_instance->GetScene())
+		{
+			for( int m = 0; m < mp_instance->GetScene()->m_num_filled_meshes; ++m )
+			{
+				NxNgc::sMesh			*p_mesh	= mp_instance->GetScene()->mpp_mesh_list[m];
+				NxNgc::sMaterialHeader	*p_mat	= p_mesh->mp_dl->m_material.p_header;
+				if( p_mat )
+				{
+					NxNgc::sMaterialPassHeader * p_pass = &mp_instance->GetScene()->mp_material_pass[p_mat->m_pass_item];
+					for ( int pass = 0; pass < p_mat->m_passes; pass++, p_pass++ )
+					{
+						p_pass->m_color.r = rgba.r;
+						p_pass->m_color.g = rgba.g;
+						p_pass->m_color.b = rgba.b;
+
+//						NxNgc::sTextureDL* p_dl = &mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id];
+//						GX::begin( p_dl->mp_dl, p_dl->m_dl_size );
+//						multi_mesh( p_mat, p_pass, true, true );
+//						p_dl->m_dl_size = GX::end();
+
+//						int size = mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id].m_dl_size;
+//						void * p_dl = mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id].mp_dl;
+//						GX::ChangeMaterialColor( (unsigned char *)p_dl, size, p_pass->m_color, pass );
+
+						p_mat->m_flags |= (1<<4);
+					}
+				}
+			}
+		}
+	}
+	else if( m_mesh_array != NULL )
+	{
+		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+			NxNgc::sMesh *p_mesh = m_mesh_array[i];
+			p_mesh->m_base_color.r = rgba.r;
+			p_mesh->m_base_color.g = rgba.g;
+			p_mesh->m_base_color.b = rgba.b;
+			p_mesh->m_base_color.a = rgba.a;
+		}
+	}
+
+//	// Oofa, nasty hack.
+//	if( mp_instance )
+//	{
+//		// Grab the engine scene from the geom, and set all meshes to the color.
+//		NxNgc::sScene *p_scene = mp_instance->GetScene();
+//		for( int i = 0; i < p_scene->m_num_opaque_entries; ++i )
+//		{
+//			NxNgc::sMesh *p_mesh = p_scene->m_opaque_meshes[i];
+//			p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+//			p_mesh->m_material_color_override.r = rgba.r;
+//			p_mesh->m_material_color_override.g = rgba.g;
+//			p_mesh->m_material_color_override.b = rgba.b;
+//			p_mesh->m_material_color_override.a = rgba.a;
+//		}
+//		for( int i = 0; i < p_scene->m_num_semitransparent_entries; ++i )
+//		{
+//			NxNgc::sMesh *p_mesh = p_scene->m_semitransparent_meshes[i];
+//			p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+//			p_mesh->m_material_color_override.r = rgba.r;
+//			p_mesh->m_material_color_override.g = rgba.g;
+//			p_mesh->m_material_color_override.b = rgba.b;
+//			p_mesh->m_material_color_override.a = rgba.a;
+//		}
+//	}
+//	else if( m_mesh_array != NULL )
+//	{
+//		for( uint32 i = 0; i < m_num_mesh; ++i )
+//		{
+//			NxNgc::sMesh *p_mesh = m_mesh_array[i];
+//			p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+//			p_mesh->m_material_color_override.r = rgba.r;
+//			p_mesh->m_material_color_override.g = rgba.g;
+//			p_mesh->m_material_color_override.b = rgba.b;
+//			p_mesh->m_material_color_override.a = rgba.a;
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_clear_color( void )
+{
+	if( mp_instance && mp_instance->GetBoneTransforms() )
+	{
+		if( mp_instance && mp_instance->GetScene())
+		{
+			for( int m = 0; m < mp_instance->GetScene()->m_num_filled_meshes; ++m )
+			{
+				NxNgc::sMesh			*p_mesh	= mp_instance->GetScene()->mpp_mesh_list[m];
+				NxNgc::sMaterialHeader	*p_mat	= p_mesh->mp_dl->m_material.p_header;
+				if( p_mat )
+				{
+					NxNgc::sMaterialPassHeader * p_pass = &mp_instance->GetScene()->mp_material_pass[p_mat->m_pass_item];
+					for ( int pass = 0; pass < p_mat->m_passes; pass++, p_pass++ )
+					{
+						p_pass->m_color.r = 128;
+						p_pass->m_color.g = 128;
+						p_pass->m_color.b = 128;
+//						NxNgc::sTextureDL* p_dl = &mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id];
+//						GX::begin( p_dl->mp_dl, p_dl->m_dl_size );
+//						multi_mesh( p_mat, p_pass, true, true );
+//						p_dl->m_dl_size = GX::end();
+
+//						int size = mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id].m_dl_size;
+//						void * p_dl = mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id].mp_dl;
+//						GX::ChangeMaterialColor( (unsigned char *)p_dl, size, p_pass->m_color, pass );
+						p_mat->m_flags |= (1<<4);
+					}
+				}
+			}
+		}
+	}
+	else if( m_mesh_array != NULL )
+	{
+		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+			NxNgc::sMesh *p_mesh = m_mesh_array[i];
+			p_mesh->m_base_color.r = 128;
+			p_mesh->m_base_color.g = 128;
+			p_mesh->m_base_color.b = 128;
+			p_mesh->m_base_color.a = 255;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CNgcGeom::plat_get_color( void ) const
+{
+	Image::RGBA rgba( 0, 0, 0, 0 );
+
+	if( mp_instance && mp_instance->GetBoneTransforms() )
+	{
+		if( mp_instance && mp_instance->GetScene())
+		{
+			if ( mp_instance->GetScene()->m_num_filled_meshes > 0 )
+			{
+				NxNgc::sMesh			*p_mesh	= mp_instance->GetScene()->mpp_mesh_list[0];
+				NxNgc::sMaterialHeader	*p_mat	= p_mesh->mp_dl->m_material.p_header;
+				if( p_mat )
+				{
+					NxNgc::sMaterialPassHeader * p_pass = &mp_instance->GetScene()->mp_material_pass[p_mat->m_pass_item];
+
+					rgba.r = p_pass->m_color.r;
+					rgba.g = p_pass->m_color.g;
+					rgba.b = p_pass->m_color.b;
+					rgba.a = p_pass->m_color.a;
+
+				}
+			}
+		}
+	}
+	else if( m_mesh_array != NULL )
+	{
+		NxNgc::sMesh *p_mesh = m_mesh_array[0];
+		rgba.r = p_mesh->m_base_color.r;
+		rgba.g = p_mesh->m_base_color.g;
+		rgba.b = p_mesh->m_base_color.b;
+		rgba.a = p_mesh->m_base_color.a;
+	}
+
+	return rgba;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::plat_set_material_color( uint32 mat_name_checksum, int pass, Image::RGBA rgba )
+{
+	bool something_changed = false;
+
+	if( mp_instance && mp_instance->GetScene())
+	{
+		for( int m = 0; m < mp_instance->GetScene()->m_num_filled_meshes; ++m )
+		{
+			NxNgc::sMesh			*p_mesh	= mp_instance->GetScene()->mpp_mesh_list[m];
+			NxNgc::sMaterialHeader	*p_mat	= p_mesh->mp_dl->m_material.p_header;
+			if( p_mat )
+			{
+				bool	want_this_material	= false;
+				int		adjusted_pass		= pass;
+
+				// We are searching for materials with a matching name. However there is a caveat in that the
+				// conversion process sometimes creates new materials for geometry flagged as 'render as separate', or for
+				// geometry which is mapped with only certain passes of a multipass material (or both cases).
+				// In such a case, the new material name checksum will differ from the original material name checksum,
+				// with the following bits having significance (i.e. consider bitflags = new_namechecksum - old_namechecksum ):
+				//
+				// Bits 3->6	Pass flags indicating which passes of the original material this material uses
+				// Bits 0->2	Absolute number ('render as separate' flagged geometry) indicating which single pass of the material this material represents.
+				if( p_mat->m_materialNameChecksum == mat_name_checksum )
+					want_this_material = true;
+				else if(( p_mat->m_materialNameChecksum > mat_name_checksum ) && (( p_mat->m_materialNameChecksum - mat_name_checksum ) <= 0x7F ))
+				{
+					uint32 checksum_diff		= p_mat->m_materialNameChecksum - mat_name_checksum;
+					int render_separate_pass	= checksum_diff & 0x07;
+					uint32 pass_flags			= checksum_diff >> 3;
+
+					if( render_separate_pass )
+					{
+						if( render_separate_pass == ( pass + 1 ))
+						{
+							want_this_material = true;
+
+							// Readjust the pass to zero, since this material was formed as a single pass of a multipass material.
+							adjusted_pass = 0;
+						}
+					}
+					else if( pass_flags )
+					{
+						// This material was created during scene conversion from another material with more passes.
+						if( pass_flags & ( 1 << pass ))
+						{
+							want_this_material = true;
+							for( int p = 0; p < pass; ++p )
+							{
+								if(( pass_flags & ( 1 << p )) == 0 )
+								{
+									// Readjust the pass down by 1, since this material was created as a subset of another material.
+									--adjusted_pass;
+								}
+							}
+						}
+					}
+				}
+
+				if( want_this_material )
+				{
+//					Dbg_Assert((uint32)adjusted_pass < p_mat->m_passes );
+		
+					if ( adjusted_pass < p_mat->m_passes )
+					{
+						NxNgc::sMaterialPassHeader * p_pass = &mp_instance->GetScene()->mp_material_pass[p_mat->m_pass_item+adjusted_pass];
+
+						if( !( p_pass->m_flags & (1<<4) ))		// COLOR_LOCKED
+						{
+							p_pass->m_color.r = rgba.r;
+							p_pass->m_color.g = rgba.g;
+							p_pass->m_color.b = rgba.b;
+
+//							void * p_dl = mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id].mp_dl;
+//							int size = mp_instance->GetScene()->mp_texture_dl[p_mat->m_texture_dl_id].m_dl_size;
+//
+//							GX::ChangeMaterialColor( (unsigned char *)p_dl, size, p_pass->m_color, adjusted_pass );
+
+							// Flag as being direct for ever more.
+							p_mat->m_flags |= (1<<4);
+
+							something_changed = true;
+						}
+					}
+				}
+			}
+		}
+	}
+	return something_changed;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcGeom *CNgcGeom::plat_clone( bool instance, CScene *p_dest_scene )
+{
+//	instance = true;
+
+	CNgcGeom *p_clone = NULL;
+
+	// This is a CNgcGeom 'hanging' from a sector. Create a new CNgcGeom which will store the CInstance.
+	p_clone = new CNgcGeom();
+	p_clone->mp_scene = mp_scene;
+
+	// Create new meshes for the clone.
+	p_clone->m_mesh_array	= m_num_mesh ? new NxNgc::sMesh*[m_num_mesh] : NULL;
+	p_clone->m_num_mesh		= m_num_mesh;
+//	NxNgc::sMesh * p_mesh0 = NULL;
+	for( uint32 m = 0; m < p_clone->m_num_mesh; ++m )
+	{
+//		if ( mp_instance )
+//		{
+//			// Instances don't need display lists cloning...
+//			p_clone->m_mesh_array[m] = m_mesh_array[m]->Clone( false );
+//		}
+//		else
+//		{
+			p_clone->m_mesh_array[m] = m_mesh_array[m]->Clone( instance );
+//		}
+
+//		if ( m_mesh_array[m]->m_localMeshIndex == 0 )
+//		{
+//			p_mesh0 = p_clone->m_mesh_array[m];
+//		}
+	}
+//	if ( p_mesh0 )
+//	{
+//		for( uint32 m = 0; m < p_clone->m_num_mesh; ++m )
+//		{
+//			if ( p_clone->m_mesh_array[m]->m_localMeshIndex != 0 )
+//			{
+//				p_clone->m_mesh_array[m]->mp_posBuffer = p_mesh0->mp_posBuffer;
+//				p_clone->m_mesh_array[m]->mp_normBuffer = p_mesh0->mp_normBuffer;
+//				p_clone->m_mesh_array[m]->mp_colBuffer = p_mesh0->mp_colBuffer;
+//			}
+//		}
+//	}
+
+	if( instance == false )
+	{
+		p_clone->SetActive( true );
+
+		// In this situation, we need to add the individual meshes to the scene.
+		// Grab a temporary workspace buffer.
+		Nx::CNgcScene *p_Ngc_scene						= static_cast( p_dest_scene );
+		NxNgc::sScene *p_scene								= p_Ngc_scene->GetEngineScene();
+		NxNgc::sMesh **p_temp_mesh_buffer				= ( p_scene->m_num_meshes > 0 ) ? new NxNgc::sMesh*[p_scene->m_num_meshes] : NULL;
+
+		// Set the scene pointer for the clone.
+		p_clone->SetScene( p_Ngc_scene );
+		
+		// Copy meshes over into the temporary workspace buffer.
+		for( int i = 0; i < p_scene->m_num_meshes; ++i )
+		{
+			p_temp_mesh_buffer[i] = p_scene->mpp_mesh_list[i];
+		}
+
+		// Store how many meshes were present.
+		int old_num_entries			= p_scene->m_num_meshes;
+		
+		// Delete current mesh arrays.
+		delete [] p_scene->mpp_mesh_list;
+
+		// Important to set these to NULL.
+		p_scene->mpp_mesh_list			= NULL;
+		
+		// Include new meshes in count.
+		p_scene->CountMeshes( m_num_mesh, m_mesh_array );
+
+		// Allocate new mesh arrays.
+		p_scene->CreateMeshArrays();
+
+		// Copy old mesh data back in.
+		for( int i = 0; i < old_num_entries; ++i )
+		{
+			p_scene->mpp_mesh_list[i] = p_temp_mesh_buffer[i];
+		}
+
+		// Remove temporary arrays.
+		delete [] p_temp_mesh_buffer;
+		
+		// Add new meshes.
+		p_scene->AddMeshes( p_clone->m_num_mesh, p_clone->m_mesh_array );
+
+		// Sort the meshes.
+		p_scene->SortMeshes();
+	}
+	else
+	{
+		// Create a new scene which will be attached via the instance.
+		p_clone->m_bbox			= m_bbox;
+		NxNgc::sScene *p_scene = p_clone->GenerateScene();
+
+		p_clone->SetActive( true );
+	
+		// Create the instance.
+		Mth::Matrix temp;
+		temp.Identity();
+		NxNgc::CInstance *p_instance = new NxNgc::CInstance( p_scene, temp, 1, NULL );
+		
+		// This instance will be the only object maintaining a reference to the attached scene, so we want to delete
+		// the scene when the instance gets removed.
+		p_instance->SetFlag( NxNgc::CInstance::INSTANCE_FLAG_DELETE_ATTACHED_SCENE );
+
+		// Hook the clone up to the instance.		
+		p_clone->SetInstance( p_instance );
+
+		p_clone->mp_scene = NULL;
+	}
+
+	return p_clone;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_model_lights( CModelLights* p_model_lights )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcGeom *CNgcGeom::plat_clone( bool instance, CModel* pDestModel )
+{
+	CNgcGeom *p_clone = NULL;
+
+	// This is a CNgcGeom 'hanging' from a sector. Create a new CNgcGeom which will store the CInstance.
+	Nx::CNgcScene *p_Ngc_scene = new CNgcScene;
+	p_Ngc_scene->SetEngineScene( mp_instance->GetScene() );
+
+	p_clone = new CNgcGeom();
+	p_clone->mp_scene = p_Ngc_scene;
+
+	int num_mesh = mp_instance->GetScene()->m_num_filled_meshes;
+
+	// Create new meshes for the clone.
+	p_clone->m_mesh_array	= num_mesh ? new NxNgc::sMesh*[num_mesh] : NULL;
+	p_clone->m_num_mesh		= num_mesh;
+	for( uint32 m = 0; m < p_clone->m_num_mesh; ++m )
+	{
+		p_clone->m_mesh_array[m] = mp_instance->GetScene()->mpp_mesh_list[m]->Clone( instance );
+	}
+	// Create a new scene which will be attached via the instance.
+	p_clone->m_bbox			= m_bbox;
+	NxNgc::sScene *p_scene = p_clone->GenerateScene();
+
+	// Kill the temp scene.
+	p_Ngc_scene->SetEngineScene( NULL );
+	delete p_Ngc_scene;
+	p_clone->mp_scene = NULL;
+
+	p_clone->SetActive( true );
+
+	int numBones;
+	numBones = pDestModel->GetNumBones();
+
+	Mth::Matrix * p_bone = new Mth::Matrix[numBones];
+
+	for ( int i = 0; i < numBones; i++ )
+	{
+		p_bone[i].Identity();
+	}
+
+//	((CNgcModel*)pDestModel)->GetInstance()->SetBoneTransforms( p_bone );
+
+	// Create the instance.
+	Mth::Matrix temp;
+	temp.Identity();
+	NxNgc::CInstance *p_instance = new NxNgc::CInstance( p_scene, temp, numBones, p_bone );
+	
+	((CNgcModel*)pDestModel)->SetInstance( p_instance );
+
+	// This instance will be the only object maintaining a reference to the attached scene, so we want to delete
+	// the scene when the instance gets removed.
+	p_instance->SetFlag( NxNgc::CInstance::INSTANCE_FLAG_DELETE_ATTACHED_SCENE );
+
+	// Hook the clone up to the instance.		
+	p_clone->SetInstance( p_instance );
+
+	return p_clone;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcGeom::plat_set_orientation(const Mth::Matrix& orient)
+{
+	if( mp_instance )
+	{
+		// Set values
+		mp_instance->GetTransform()->GetRight() = orient[X];
+		mp_instance->GetTransform()->GetUp() = orient[Y];
+		mp_instance->GetTransform()->GetAt() = orient[Z];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcGeom::plat_rotate_y(Mth::ERot90 rot)
+{
+	if( rot != Mth::ROT_0 )
+	{
+		if( mp_instance )
+		{
+			Mth::Matrix *p_matrix = mp_instance->GetTransform();
+
+			// Zero out translation component.
+			Mth::Matrix	instance_transform = *p_matrix;
+			instance_transform[W] = Mth::Vector(0.0f, 0.0f, 0.0f, 1.0f);
+
+			// Build rotation matrix.
+			Mth::Matrix	rot_mat;
+			float		rad = (float)((int)rot) * ( Mth::PI * 0.5f );
+			Mth::CreateRotateYMatrix( rot_mat, rad );
+
+			// Rotate matrix.
+			rot_mat	= instance_transform * rot_mat;
+
+			// Replace translation component.
+			rot_mat[W] = p_matrix->GetPos();
+
+			mp_instance->SetTransform( rot_mat );
+		}
+		else
+		{
+			*((Mth::ERot90*)&m_mod.offset[W]) = rot;
+
+			int num = plat_get_num_render_verts();
+			Mth::Vector * p_verts = new Mth::Vector[num];
+			plat_get_render_verts( p_verts );
+			for ( int lp = 0; lp < num; lp++ )
+			{
+				float x;
+				float z;
+				switch ( rot )
+				{
+					case Mth::ROT_90:
+						x = p_verts[lp][X] - m_mod.offset[X];
+						z = p_verts[lp][Z] - m_mod.offset[Z];
+						p_verts[lp][X] =  z + m_mod.offset[X];
+						p_verts[lp][Z] = -x + m_mod.offset[Z];
+						break;
+					case Mth::ROT_180:
+						x = p_verts[lp][X] - m_mod.offset[X];
+						z = p_verts[lp][Z] - m_mod.offset[Z];
+						p_verts[lp][X] = -x + m_mod.offset[X];
+						p_verts[lp][Z] = -z + m_mod.offset[Z];
+						break;
+					case Mth::ROT_270:
+						x = p_verts[lp][X] - m_mod.offset[X];
+						z = p_verts[lp][Z] - m_mod.offset[Z];
+						p_verts[lp][X] = -z + m_mod.offset[X];
+						p_verts[lp][Z] = x + m_mod.offset[Z];
+						break;
+					default:
+					case Mth::ROT_0:
+						// Do nothing.
+						break;
+				}
+			}
+			plat_set_render_verts( p_verts );
+			delete p_verts;
+		}
+	}
+	*((Mth::ERot90*)&m_mod.offset[W]) = rot;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcGeom::plat_set_transform(const Mth::Matrix& transform)
+{
+	// Set values
+	*mp_instance->GetTransform() = transform;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Matrix &	CNgcGeom::plat_get_transform() const
+{
+	return *mp_instance->GetTransform();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CNgcGeom::build_index_map( int * p_pos_index, int * p_col_index )
+{
+	int num_used = 0;
+	// Need to go through every index and find unique pos/color pairs.
+	if ( mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		for ( uint mesh = 0; mesh < m_num_mesh; mesh++ )
+		{
+			NxNgc::sMesh * p_mesh = m_mesh_array[mesh];
+
+			// Parse display list looking for this pos index, then get the color index.
+			unsigned char * p_start = (unsigned char *)&p_mesh->mp_dl[1];
+			unsigned char * p_end = &p_start[p_mesh->mp_dl->m_size];
+			p_start = &p_start[p_mesh->mp_dl->m_index_offset];		// Skip to actual 1st GDBegin.
+			unsigned char * p8 = p_start;
+
+			int stride = p_mesh->mp_dl->m_index_stride * 2;
+			int offset = p_mesh->mp_dl->m_color_offset * 2;
+
+			while ( p8 < p_end )
+			{
+				if ( ( p8[0] & 0xf8 ) == GX_TRIANGLESTRIP )
+				{
+					// Found a triangle strip - parse it.
+					int num_idx = ( p8[1] << 8 ) | p8[2];
+					p8 += 3;		// Skip GDBegin
+
+					for ( int v = 0; v < num_idx; v++ )
+					{
+						int index = ( ( p8[0] << 8 ) | p8[1] ) + p_mesh->mp_dl->m_array_base;
+						int cindex = ( p8[offset+0] << 8 ) | p8[offset+1];
+
+						// See if this is a unique pair.
+						bool found = false;
+						for ( int lp = 0; lp < num_used; lp++ )
+						{
+							if ( ( p_pos_index[lp] == index ) && ( p_col_index[lp] == cindex ) )
+							{
+								found = true;
+								break;
+							}
+						}
+						if ( !found )
+						{
+							p_pos_index[num_used] = index;
+							p_col_index[num_used] = cindex;
+							num_used++;
+						}
+						p8 += stride;
+					}
+				}
+				else
+				{
+					break;
+				}
+			}
+		}
+	}
+	return num_used;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#define MAX_INDEX 2500
+
+int CNgcGeom::plat_get_num_render_verts( void )
+{
+	if ( mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		NxNgc::sMesh * p_mesh = m_mesh_array[0];
+
+		if ( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_INDEX_COUNT_SET )
+		{
+			return p_mesh->mp_dl->m_num_indices;
+		}
+	}
+
+	int pos_index[MAX_INDEX];
+	int col_index[MAX_INDEX];
+
+	int num_verts = build_index_map( pos_index, col_index );
+//	Pcm::PCMAudio_Update();
+	do_audio_update();
+
+	return num_verts;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcGeom::plat_get_render_verts( Mth::Vector *p_verts )
+{
+	// See if pool has been copied already.
+	if ( mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		NxNgc::sMesh * p_mesh = m_mesh_array[0];
+
+		if ( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_CLONED_POS )
+		{
+			for ( int lp = 0; lp < p_mesh->mp_dl->m_num_indices; lp++ )
+			{
+				p_verts[lp][X] = p_mesh->mp_dl->mp_pos_pool[(lp*3)+0];
+				p_verts[lp][Y] = p_mesh->mp_dl->mp_pos_pool[(lp*3)+1];
+				p_verts[lp][Z] = p_mesh->mp_dl->mp_pos_pool[(lp*3)+2];
+				p_verts[lp][W] = 1.0f;
+			}
+//			Pcm::PCMAudio_Update();
+			do_audio_update();
+			return;
+		}
+	}
+
+	int pos_index[MAX_INDEX];
+	int col_index[MAX_INDEX];
+
+	int num_verts = build_index_map( pos_index, col_index );
+//	Pcm::PCMAudio_Update();
+	do_audio_update();
+
+	// Copy from original pool.
+	for ( int lp = 0; lp < num_verts; lp++ )
+	{
+		p_verts[lp][X] = mp_scene->GetEngineScene()->mp_pos_pool[(pos_index[lp]*3)+0];
+		p_verts[lp][Y] = mp_scene->GetEngineScene()->mp_pos_pool[(pos_index[lp]*3)+1];
+		p_verts[lp][Z] = mp_scene->GetEngineScene()->mp_pos_pool[(pos_index[lp]*3)+2];
+		p_verts[lp][W] = 1.0f;
+
+	}
+//	Pcm::PCMAudio_Update();
+	do_audio_update();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_get_render_colors( Image::RGBA *p_colors )
+{
+	// See if pool has been copied already.
+	if ( mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		NxNgc::sMesh * p_mesh = m_mesh_array[0];
+
+		if ( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_CLONED_COL )
+		{
+			for ( int lp = 0; lp < p_mesh->mp_dl->m_num_indices; lp++ )
+			{
+				p_colors[lp].r = ( p_mesh->mp_dl->mp_col_pool[lp] >> 24 ) & 0xff;
+				p_colors[lp].g = ( p_mesh->mp_dl->mp_col_pool[lp] >> 16 ) & 0xff;
+				p_colors[lp].b = ( p_mesh->mp_dl->mp_col_pool[lp] >>  8 ) & 0xff;
+				p_colors[lp].a = ( p_mesh->mp_dl->mp_col_pool[lp] >>  0 ) & 0xff;
+			}
+//			Pcm::PCMAudio_Update();
+			do_audio_update();
+			return;
+		}
+	}
+
+	int pos_index[MAX_INDEX];
+	int col_index[MAX_INDEX];
+
+	int num_verts = build_index_map( pos_index, col_index );
+//	Pcm::PCMAudio_Update();
+	do_audio_update();
+
+	// Copy from original pool.
+	for ( int lp = 0; lp < num_verts; lp++ )
+	{
+		p_colors[lp].r = ( mp_scene->GetEngineScene()->mp_col_pool[col_index[lp]] >> 24 ) & 0xff;
+		p_colors[lp].g = ( mp_scene->GetEngineScene()->mp_col_pool[col_index[lp]] >> 16 ) & 0xff;
+		p_colors[lp].b = ( mp_scene->GetEngineScene()->mp_col_pool[col_index[lp]] >>  8 ) & 0xff;
+		p_colors[lp].a = ( mp_scene->GetEngineScene()->mp_col_pool[col_index[lp]] >>  0 ) & 0xff;
+	}
+//	Pcm::PCMAudio_Update();
+	do_audio_update();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_render_verts( Mth::Vector *p_verts )
+{
+	// See if we need to create a new pool.
+	bool need_to_create = false;
+	if ( mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		NxNgc::sMesh * p_mesh = m_mesh_array[0];
+		if ( ( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_CLONED_POS ) == 0 )
+		{
+			need_to_create = true;
+		}
+	}
+
+	// Create new pos pool if necessary.
+	float * p_new_pos;
+	int num_verts;
+	if ( need_to_create && mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		int pos_index[MAX_INDEX];
+		int col_index[MAX_INDEX];
+
+		num_verts = build_index_map( pos_index, col_index );
+//		Pcm::PCMAudio_Update();
+		do_audio_update();
+
+		// Allocate space for new pool.
+		p_new_pos = new float[3*num_verts];
+
+		for ( uint mesh = 0; mesh < m_num_mesh; mesh++ )
+		{
+			NxNgc::sMesh * p_mesh = m_mesh_array[mesh];
+
+			if ( !p_mesh->mp_dl->mp_pos_pool )
+			{
+				p_mesh->mp_dl->mp_pos_pool = p_new_pos;
+
+				if ( mesh == 0 )
+				{
+					p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_CLONED_POS;
+					p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_INDEX_COUNT_SET;
+					p_mesh->mp_dl->m_num_indices = num_verts;
+				}
+
+				// Remap the DL.
+				// Parse display list looking for this pos index, then get the color index.
+				unsigned char * p_start = (unsigned char *)&p_mesh->mp_dl[1];
+				unsigned char * p_end = &p_start[p_mesh->mp_dl->m_size];
+				p_start = &p_start[p_mesh->mp_dl->m_index_offset];		// Skip to actual 1st GDBegin.
+				unsigned char * p8 = p_start;
+
+				int stride = p_mesh->mp_dl->m_index_stride * 2;
+				int offset = p_mesh->mp_dl->m_color_offset * 2;
+
+				while ( p8 < p_end )
+				{
+					if ( ( p8[0] & 0xf8 ) == GX_TRIANGLESTRIP )
+					{
+						// Found a triangle strip - parse it.
+						int num_idx = ( p8[1] << 8 ) | p8[2];
+						p8 += 3;		// Skip GDBegin
+
+						for ( int v = 0; v < num_idx; v++ )
+						{
+							// Find the remapped index.
+							int index = ( ( p8[0] << 8 ) | p8[1] ) + p_mesh->mp_dl->m_array_base;
+							int cindex = ( p8[offset+0] << 8 ) | p8[offset+1];
+
+							for ( int lp = 0; lp < num_verts; lp++ )
+							{
+								if ( ( pos_index[lp] == index ) && ( col_index[lp] == cindex ) )
+								{
+									p8[0] = ( lp >> 8 ) & 0xff;
+									p8[1] = lp & 0xff;
+									break;
+								}
+							}
+							p8 += stride;
+						}
+					}
+					else
+					{
+						break;
+					}
+				}
+				DCFlushRange( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size );
+			}
+			p_mesh->mp_dl->m_array_base = 0;
+
+//			Pcm::PCMAudio_Update();
+			do_audio_update();
+		}
+	}
+	else
+	{
+		p_new_pos = m_mesh_array[0]->mp_dl->mp_pos_pool;
+		num_verts = m_mesh_array[0]->mp_dl->m_num_indices;
+	}
+
+	// Copy actual data over.
+	Mth::CBBox bbox;
+	for ( int v = 0; v < num_verts; v++ )
+	{
+		p_new_pos[(v*3)+0] = p_verts[v][X];
+		p_new_pos[(v*3)+1] = p_verts[v][Y];
+		p_new_pos[(v*3)+2] = p_verts[v][Z];
+		bbox.AddPoint( p_verts[v] );
+	}
+	DCFlushRange( p_new_pos, sizeof( float ) * 3 * num_verts );
+
+	if ( mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		for ( uint mesh = 0; mesh < m_num_mesh; mesh++ )
+		{
+			NxNgc::sMesh * p_mesh = m_mesh_array[mesh];
+
+			// Now refigure the bounding sphere.
+			Mth::Vector sphere_center	= bbox.GetMin() + ( 0.5f * ( bbox.GetMax() - bbox.GetMin()));
+			p_mesh->mp_dl->m_sphere[X] = sphere_center[X];
+			p_mesh->mp_dl->m_sphere[Y] = sphere_center[Y];
+			p_mesh->mp_dl->m_sphere[Z] = sphere_center[Z];
+			p_mesh->mp_dl->m_sphere[W] = ( bbox.GetMax() - sphere_center ).Length(); 
+
+			p_mesh->m_bottom_y = bbox.GetMin()[Y];
+		}
+	}
+//	Pcm::PCMAudio_Update();
+	do_audio_update();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_render_colors( Image::RGBA *p_colors )
+{
+	// See if we need to create a new pool.
+	bool need_to_create = false;
+	if ( mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		NxNgc::sMesh * p_mesh = m_mesh_array[0];
+		if ( !( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_CLONED_COL ) )
+		{
+			need_to_create = true;
+		}
+	}
+
+	// Create new col pool if necessary.
+	uint32 * p_new_col;
+	int num_verts;
+	if ( need_to_create && mp_scene->GetEngineScene()->mp_scene_data )
+	{
+		int pos_index[MAX_INDEX];
+		int col_index[MAX_INDEX];
+
+		num_verts = build_index_map( pos_index, col_index );
+//		Pcm::PCMAudio_Update();
+		do_audio_update();
+
+		// Allocate space for new pool.
+		p_new_col = new uint32[num_verts];
+
+		for ( uint mesh = 0; mesh < m_num_mesh; mesh++ )
+		{
+			NxNgc::sMesh * p_mesh = m_mesh_array[mesh];
+
+			if ( !p_mesh->mp_dl->mp_col_pool )
+			{
+				p_mesh->mp_dl->mp_col_pool = p_new_col;
+
+				if ( mesh == 0 )
+				{
+					p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_CLONED_COL;
+					p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_INDEX_COUNT_SET;
+					p_mesh->mp_dl->m_num_indices = num_verts;
+				}
+
+				// Remap the DL.
+				// Parse display list looking for this pos index, then get the color index.
+				unsigned char * p_start = (unsigned char *)&p_mesh->mp_dl[1];
+				unsigned char * p_end = &p_start[p_mesh->mp_dl->m_size];
+				p_start = &p_start[p_mesh->mp_dl->m_index_offset];		// Skip to actual 1st GDBegin.
+				unsigned char * p8 = p_start;
+
+				int stride = p_mesh->mp_dl->m_index_stride * 2;
+				int offset = p_mesh->mp_dl->m_color_offset * 2;
+
+				while ( p8 < p_end )
+				{
+					if ( ( p8[0] & 0xf8 ) == GX_TRIANGLESTRIP )
+					{
+						// Found a triangle strip - parse it.
+						int num_idx = ( p8[1] << 8 ) | p8[2];
+						p8 += 3;		// Skip GDBegin
+
+						for ( int v = 0; v < num_idx; v++ )
+						{
+							// Find the remapped index.
+							int index = ( ( p8[0] << 8 ) | p8[1] ) + p_mesh->mp_dl->m_array_base;
+							int cindex = ( p8[offset+0] << 8 ) | p8[offset+1];
+
+							for ( int lp = 0; lp < num_verts; lp++ )
+							{
+								if ( ( pos_index[lp] == index ) && ( col_index[lp] == cindex ) )
+								{
+									p8[offset+0] = ( lp >> 8 ) & 0xff;
+									p8[offset+1] = lp & 0xff;
+									if ( p_mesh->mp_dl->m_material.p_header->m_flags & (1<<6)/*m_correctable*/ )
+									{
+										p8[offset+2] = ( lp >> 8 ) & 0xff;
+										p8[offset+3] = lp & 0xff;
+									}
+									break;
+								}
+							}
+							p8 += stride;
+						}
+					}
+					else
+					{
+						break;
+					}
+				}
+				DCFlushRange( &p_mesh->mp_dl[1], p_mesh->mp_dl->m_size );
+			}
+//			p_mesh->mp_dl->m_array_base = 0;
+
+//			Pcm::PCMAudio_Update();
+			do_audio_update();
+		}
+	}
+	else
+	{
+		p_new_col = m_mesh_array[0]->mp_dl->mp_col_pool;
+		num_verts = m_mesh_array[0]->mp_dl->m_num_indices;
+	}
+
+	// Copy actual data over.
+	for ( int v = 0; v < num_verts; v++ )
+	{
+		p_new_col[v] = ( p_colors[v].r << 24 ) | ( p_colors[v].g << 16 ) | ( p_colors[v].b << 8 ) | p_colors[v].a;
+	}
+	DCFlushRange( p_new_col, sizeof( uint32 ) * num_verts );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_scale(const Mth::Vector & scale)
+{
+	// Ensure w component is correct.
+
+	if( mp_instance )
+	{
+//		mp_instance->GetTransform()->SetScale( scale );
+	}
+	else
+	{
+		int num = plat_get_num_render_verts();
+		Mth::Vector * p_verts = new Mth::Vector[num];
+		plat_get_render_verts( p_verts );
+		for ( int lp = 0; lp < num; lp++ )
+		{
+			p_verts[lp][X] = ( ( p_verts[lp][X] - m_mod.offset[X] ) * scale[X] ) + m_mod.offset[X];
+			p_verts[lp][Y] = ( ( p_verts[lp][Y] - m_mod.offset[Y] ) * scale[Y] ) + m_mod.offset[Y];
+			p_verts[lp][Z] = ( ( p_verts[lp][Z] - m_mod.offset[Z] ) * scale[Z] ) + m_mod.offset[Z];
+		}
+		plat_set_render_verts( p_verts );
+		delete p_verts;
+
+//		// Go through and adjust the individual meshes.
+//		for( uint32 i = 0; i < m_num_mesh; ++i )
+//		{
+//			NxNgc::sMesh *p_mesh = m_mesh_array[i];
+//			p_mesh->mp_mod = &m_mod;
+//
+////			Mth::Vector delta_pos( pos[X] - m_offset[X], pos[Y] - m_offset[Y], pos[Z] - m_offset[Z], 1.0f );
+////			p_mesh->mp_dl->m_sphere[X] += delta_pos[X];
+////			p_mesh->mp_dl->m_sphere[Y] += delta_pos[Y];
+////			p_mesh->mp_dl->m_sphere[Z] += delta_pos[Z];
+////			p_mesh->SetPosition( proper_pos );
+//		}
+//
+////		if ( mp_scene->GetEngineScene()->mp_scene_data )
+////		{
+////			Mth::Vector proper_pos( pos[X] - m_offset[X], pos[Y] - m_offset[Y], pos[Z] - m_offset[Z], 1.0f );
+////	
+////			float * pos = mp_scene->GetEngineScene()->mp_pos_pool;
+////			for ( int lp = 0; lp += mp_scene->GetEngineScene()->mp_scene_data->m_num_pos; lp++ )
+////			{
+////				pos[0] += proper_pos[X];
+////				pos[1] += proper_pos[Y];
+////				pos[2] += proper_pos[Z];
+////				pos += 3;
+////			}
+////			m_offset[X] = proper_pos[X];
+////			m_offset[Y] = proper_pos[Y];
+////			m_offset[Z] = proper_pos[Z];
+////		}
+	}
+	m_mod.scale = scale;
+
+
+
+//	Dbg_MsgAssert( m_mesh_array, ( "Invalid for instanced sectors" ));
+//
+//	m_scale = scale;
+//
+//	if( m_mesh_array )
+//	{
+//		NxNgc::sMesh *p_mesh = m_mesh_array[0];
+//
+//		// Scale each vertex.
+//#ifdef SHORT_VERT
+//		for( uint32 v = 0; v < p_mesh->m_num_vertex; ++v )
+//		{
+//			p_mesh->mp_posBuffer[(v*3)+0] = (s16)( (float)p_mesh->mp_posBuffer[(v*3)+0] * scale[X] );
+//			p_mesh->mp_posBuffer[(v*3)+1] = (s16)( (float)p_mesh->mp_posBuffer[(v*3)+1] * scale[Y] );
+//			p_mesh->mp_posBuffer[(v*3)+2] = (s16)( (float)p_mesh->mp_posBuffer[(v*3)+2] * scale[Z] );
+//		}
+//		DCFlushRange( p_mesh->mp_posBuffer, 3 * sizeof( s16 ) * p_mesh->m_num_vertex );
+//#else
+//		for( uint32 v = 0; v < p_mesh->m_num_vertex; ++v )
+//		{
+//			p_mesh->mp_posBuffer[(v*3)+0] = ( ( p_mesh->mp_posBuffer[(v*3)+0] - p_mesh->m_offset_x ) * scale[X] ) + p_mesh->m_offset_x;
+//			p_mesh->mp_posBuffer[(v*3)+1] = ( ( p_mesh->mp_posBuffer[(v*3)+1] - p_mesh->m_offset_y ) * scale[Y] ) + p_mesh->m_offset_y;
+//			p_mesh->mp_posBuffer[(v*3)+2] = ( ( p_mesh->mp_posBuffer[(v*3)+2] - p_mesh->m_offset_z ) * scale[Z] ) + p_mesh->m_offset_z;
+//		}
+//		DCFlushRange( p_mesh->mp_posBuffer, 3 * sizeof( float ) * p_mesh->m_num_vertex );
+//#endif		// SHORT_VERT 
+//	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Mth::Vector CNgcGeom::plat_get_scale() const
+{
+	return m_mod.scale;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_uv_wibble_params( float u_vel, float u_amp, float u_freq, float u_phase,
+										   float v_vel, float v_amp, float v_freq, float v_phase )
+{
+//	if( m_mesh_array )
+//	{
+//		for( uint32 m = 0; m < m_num_mesh; ++m )
+//		{
+//			NxNgc::sMesh		*p_mesh	= m_mesh_array[m];
+//			NxNgc::sMaterial	*p_mat	= p_mesh->mp_material;
+//			if( p_mat )
+//			{
+//				// Find the first pass that wibbles.
+//				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+//				{
+//					if( p_mat->mp_UVWibbleParams[p] )
+//					{
+//						p_mat->mp_UVWibbleParams[p]->m_UVel			= u_vel;
+//						p_mat->mp_UVWibbleParams[p]->m_VVel			= v_vel;
+//						p_mat->mp_UVWibbleParams[p]->m_UAmplitude	= u_amp;
+//						p_mat->mp_UVWibbleParams[p]->m_VAmplitude	= v_amp;
+//						p_mat->mp_UVWibbleParams[p]->m_UFrequency	= u_freq;
+//						p_mat->mp_UVWibbleParams[p]->m_VFrequency	= v_freq;
+//						p_mat->mp_UVWibbleParams[p]->m_UPhase		= u_phase;
+//						p_mat->mp_UVWibbleParams[p]->m_VPhase		= v_phase;
+//						break;
+//					}
+//				}
+//			}
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_use_explicit_uv_wibble( bool yes )
+{
+//	if( mp_instance && mp_instance->GetScene())
+//	{
+//		for( int m = 0; m < mp_instance->GetScene()->m_num_mesh_entries; ++m )
+//		{
+//			NxNgc::sMesh		*p_mesh	= mp_instance->GetScene()->m_meshes[m];
+//			NxNgc::sMaterial	*p_mat	= p_mesh->mp_material;
+//			if( p_mat )
+//			{
+//				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+//				{
+//					if( p_mat->mp_UVWibbleParams[p] )
+//					{
+//						p_mat->m_flags[p] |= MATFLAG_EXPLICIT_UV_WIBBLE;
+//					}
+//				}
+//			}
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcGeom::plat_set_uv_wibble_offsets( float u_offset, float v_offset )
+{
+//	if( m_mesh_array )
+//	{
+//		for( uint32 m = 0; m < m_num_mesh; ++m )
+//		{
+//			NxNgc::sMesh		*p_mesh	= m_mesh_array[m];
+//			NxNgc::sMaterial	*p_mat	= p_mesh->mp_material;
+//			if( p_mat )
+//			{
+//				// Find the first pass that wibbles.
+//				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+//				{
+//					if( p_mat->mp_UVWibbleParams[p] )
+//					{
+//						p_mat->mp_UVWibbleParams[p]->m_UVMatrix[2] = u_offset;
+//						p_mat->mp_UVWibbleParams[p]->m_UVMatrix[3] = v_offset;
+//						plat_use_explicit_uv_wibble( true );
+//						break;
+//					}
+//				}
+//			}
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::plat_set_uv_wibble_offsets( uint32 mat_name_checksum, int pass, float u_offset, float v_offset )
+{
+//	if( m_mesh_array )
+//	{
+//		for( uint32 m = 0; m < m_num_mesh; ++m )
+//		{
+//			NxNgc::sMesh		*p_mesh	= m_mesh_array[m];
+//			NxNgc::sMaterial	*p_mat	= p_mesh->mp_material;
+//			if( p_mat && ( p_mat->m_name_checksum == mat_name_checksum ))
+//			{
+//				// Find the first pass that wibbles.
+////				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+////				{
+////					if( p_mat->mp_UVWibbleParams[p] )
+////					{
+////						p_mat->mp_UVWibbleParams[p]->m_UWibbleOffset = u_offset;
+////						p_mat->mp_UVWibbleParams[p]->m_VWibbleOffset = v_offset;
+////						plat_use_explicit_uv_wibble( true );
+////						break;
+////					}
+////				}
+//			}
+//		}
+//	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::plat_set_uv_matrix( uint32 mat_name_checksum, int pass, const Mth::Matrix& mat )
+{
+	if( mp_instance && mp_instance->GetScene())
+	{
+		for( int m = 0; m < mp_instance->GetScene()->m_num_filled_meshes; ++m )
+		{
+			NxNgc::sMesh			*p_mesh	= mp_instance->GetScene()->mpp_mesh_list[m];
+			NxNgc::sMaterialHeader	*p_mat	= p_mesh->mp_dl->m_material.p_header;
+			if( p_mat )
+			{
+				bool	want_this_material	= false;
+				int		adjusted_pass		= pass;
+
+				// We are searching for materials with a matching name. However there is a caveat in that the
+				// conversion process sometimes creates new materials for geometry flagged as 'render as separate', or for
+				// geometry which is mapped with only certain passes of a multipass material (or both cases).
+				// In such a case, the new material name checksum will differ from the original material name checksum,
+				// with the following bits having significance (i.e. consider bitflags = new_namechecksum - old_namechecksum ):
+				//
+				// Bits 3->6	Pass flags indicating which passes of the original material this material uses
+				// Bits 0->2	Absolute number ('render as separate' flagged geometry) indicating which single pass of the material this material represents.
+				if( p_mat->m_materialNameChecksum == mat_name_checksum )
+				{
+					want_this_material = true;
+				}
+				else if(( p_mat->m_materialNameChecksum > mat_name_checksum ) && (( p_mat->m_materialNameChecksum - mat_name_checksum ) <= 0x7F ))
+				{
+					uint32 checksum_diff		= p_mat->m_materialNameChecksum - mat_name_checksum;
+					int render_separate_pass	= checksum_diff & 0x07;
+					uint32 pass_flags			= checksum_diff >> 3;
+
+					if( render_separate_pass )
+					{
+						if( render_separate_pass == ( pass + 1 ))
+						{
+							want_this_material = true;
+
+							// Readjust the pass to zero, since this material was formed as a single pass of a multipass material.
+							adjusted_pass = 0;
+						}
+					}
+					else if( pass_flags )
+					{
+						// This material was created during scene conversion from another material with more passes.
+						if( pass_flags & ( 1 << pass ))
+						{
+							want_this_material = true;
+							for( int p = 0; p < pass; ++p )
+							{
+								if(( pass_flags & ( 1 << p )) == 0 )
+								{
+									// Readjust the pass down by 1, since this material was created as a subset of another material.
+									--adjusted_pass;
+								}
+							}
+						}
+					}
+				}
+
+				if( want_this_material )
+				{
+					if ( adjusted_pass < p_mat->m_passes )
+					{
+						// Create the wibble params if they don't exist already.
+						NxNgc::sMaterialPassHeader * p_pass = &mp_instance->GetScene()->mp_material_pass[p_mat->m_pass_item+adjusted_pass];
+
+						// Force this to be used.
+						p_pass->m_flags |= (1<<2);
+						p_pass->m_uv_enabled = 1;
+						p_mat->m_flags |= (1<<4);
+	//
+	//
+	//
+	////					// Set the matrix values.
+	//					p_pass->mp_explicit_wibble->m_matrix[0][0] = mat[0][0];
+	//					p_pass->mp_explicit_wibble->m_matrix[0][1] = mat[0][1];
+	//					p_pass->mp_explicit_wibble->m_matrix[1][0] = mat[1][0];
+	//					p_pass->mp_explicit_wibble->m_matrix[1][1] = mat[1][1];
+	//					p_pass->mp_explicit_wibble->m_matrix[2][0] = 1.0f;
+	//					p_pass->mp_explicit_wibble->m_matrix[2][1] = 1.0f;
+	//					p_pass->mp_explicit_wibble->m_matrix[0][3] = mat[3][0];
+	//					p_pass->mp_explicit_wibble->m_matrix[1][3] = mat[3][1];
+
+						p_pass->m_uv_mat[0] = (short)(mat[0][0] * ((float)(1<<12)));
+						p_pass->m_uv_mat[1] = (short)(mat[0][1] * ((float)(1<<12)));
+						p_pass->m_uv_mat[2] = (short)(mat[3][0] * ((float)(1<<12)));
+						p_pass->m_uv_mat[3] = (short)(mat[3][1] * ((float)(1<<12)));
+					}
+				}
+			}
+		}
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcGeom::plat_allocate_uv_matrix_params( uint32 mat_name_checksum, int pass )
+{
+	if( mp_instance && mp_instance->GetScene())
+	{
+		for( int m = 0; m < mp_instance->GetScene()->m_num_filled_meshes; ++m )
+		{
+			NxNgc::sMesh			*p_mesh	= mp_instance->GetScene()->mpp_mesh_list[m];
+			NxNgc::sMaterialHeader	*p_mat	= p_mesh->mp_dl->m_material.p_header;
+			if( p_mat )
+			{
+				bool	want_this_material	= false;
+				int		adjusted_pass		= pass;
+
+				// We are searching for materials with a matching name. However there is a caveat in that the
+				// conversion process sometimes creates new materials for geometry flagged as 'render as separate', or for
+				// geometry which is mapped with only certain passes of a multipass material (or both cases).
+				// In such a case, the new material name checksum will differ from the original material name checksum,
+				// with the following bits having significance (i.e. consider bitflags = new_namechecksum - old_namechecksum ):
+				//
+				// Bits 3->6	Pass flags indicating which passes of the original material this material uses
+				// Bits 0->2	Absolute number ('render as separate' flagged geometry) indicating which single pass of the material this material represents.
+				if( p_mat->m_materialNameChecksum == mat_name_checksum )
+					want_this_material = true;
+				else if(( p_mat->m_materialNameChecksum > mat_name_checksum ) && (( p_mat->m_materialNameChecksum - mat_name_checksum ) <= 0x7F ))
+				{
+					uint32 checksum_diff		= p_mat->m_materialNameChecksum - mat_name_checksum;
+					int render_separate_pass	= checksum_diff & 0x07;
+					uint32 pass_flags			= checksum_diff >> 3;
+
+					if( render_separate_pass )
+					{
+						if( render_separate_pass == ( pass + 1 ))
+						{
+							want_this_material = true;
+
+							// Readjust the pass to zero, since this material was formed as a single pass of a multipass material.
+							adjusted_pass = 0;
+						}
+					}
+					else if( pass_flags )
+					{
+						// This material was created during scene conversion from another material with more passes.
+						if( pass_flags & ( 1 << pass ))
+						{
+							want_this_material = true;
+							for( int p = 0; p < pass; ++p )
+							{
+								if(( pass_flags & ( 1 << p )) == 0 )
+								{
+									// Readjust the pass down by 1, since this material was created as a subset of another material.
+									--adjusted_pass;
+								}
+							}
+						}
+					}
+				}
+
+				if( want_this_material )
+				{
+					if ( adjusted_pass < p_mat->m_passes )
+					{
+						NxNgc::sMaterialPassHeader * p_pass = &mp_instance->GetScene()->mp_material_pass[p_mat->m_pass_item+adjusted_pass];
+	
+						p_pass->m_flags |= (1<<2);
+						p_pass->m_uv_enabled = 1;
+						p_pass->m_uv_mat[0] = (1<<12);
+						p_pass->m_uv_mat[1] = 0;
+						p_pass->m_uv_mat[2] = 0;
+						p_pass->m_uv_mat[3] = 0;
+					}
+
+//					if ( p_pass->mp_explicit_wibble == NULL )
+//					{
+//						p_pass->mp_explicit_wibble = new NxNgc::sExplicitMaterialUVWibble;
+//						p_pass->mp_explicit_wibble->m_matrix[0][0] = 1.0f;
+//						p_pass->mp_explicit_wibble->m_matrix[0][1] = 0.0f;
+//						p_pass->mp_explicit_wibble->m_matrix[0][2] = 1.0f;
+//						p_pass->mp_explicit_wibble->m_matrix[0][3] = 0.0f;
+//						p_pass->mp_explicit_wibble->m_matrix[1][0] = 0.0f;
+//						p_pass->mp_explicit_wibble->m_matrix[1][1] = 1.0f;
+//						p_pass->mp_explicit_wibble->m_matrix[1][2] = 1.0f;
+//						p_pass->mp_explicit_wibble->m_matrix[1][3] = 0.0f;
+//						p_pass->m_flags |= (1<<2);
+//						return true;
+//					}
+//					// Create the wibble params if they don't exist already.
+//					if( p_mat->mp_UVWibbleParams[pass] == NULL )
+//					{
+//						p_mat->mp_UVWibbleParams[pass]	= new NxNgc::sUVWibbleParams;
+//
+//						// Need to set flags to indicate that uv wibble is now in effect.
+//						p_mat->m_uv_wibble				= true;
+//						p_mat->m_flags[pass]		   |= MATFLAG_UV_WIBBLE | MATFLAG_EXPLICIT_UV_WIBBLE;
+//						return true;
+//					}
+				}
+			}
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if 0
+	if( mp_instance && mp_instance->GetScene())
+	{
+		for( int m = 0; m < mp_instance->GetScene()->m_num_filled_meshes; ++m )
+		{
+			NxNgc::sMesh			*p_mesh	= mp_instance->GetScene()->mpp_mesh_list[m];
+			NxNgc::sMaterialHeader	*p_mat	= p_mesh->mp_dl->m_material.p_header;
+			if( p_mat )
+			{
+				bool	want_this_material	= false;
+
+				// We are searching for materials with a matching name. However there is one caveat in that the
+				// conversion process sometimes creates new materials for geometry flagged as 'render as separate.
+				// In such a case, the new material name checksum = original name checksum + pass + 1.
+				if( p_mat->m_materialNameChecksum == mat_name_checksum )
+					want_this_material = true;
+				else if( p_mat->m_materialNameChecksum == ( mat_name_checksum + pass + 1 ) )
+				{
+					want_this_material = true;
+
+					pass = 0;
+				}
+
+				if( want_this_material )
+				{
+//					// Create the wibble params if they don't exist already.
+//					if( p_mat->mp_UVWibbleParams[pass] == NULL )
+//					{
+//						p_mat->mp_UVWibbleParams[pass]	= new NxNgc::sUVWibbleParams;
+//
+//						// Need to set flags to indicate that uv wibble is now in effect.
+//						p_mat->m_uv_wibble				= true;
+//						p_mat->m_flags[pass]		   |= MATFLAG_UV_WIBBLE | MATFLAG_EXPLICIT_UV_WIBBLE;
+//						return true;
+//					}
+				}
+			}
+		}
+	}
+#endif
+} // Nx
+				
+
diff --git a/Code/Gfx/NGC/p_NxGeom.h b/Code/Gfx/NGC/p_NxGeom.h
new file mode 100644
index 0000000..ee65db6
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxGeom.h
@@ -0,0 +1,125 @@
+	//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxGeom.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  3/5/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_GEOM_H__
+#define	__GFX_P_NX_GEOM_H__
+    
+#include "gfx/nxgeom.h"
+#include "gfx/Ngc/p_nxsector.h"
+#include "gfx/Ngc/p_nxmesh.h"
+
+namespace Mth
+{
+	class Matrix;
+}
+
+namespace NxNgc
+{
+	class CInstance;
+}
+						   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CGeom
+class CNgcGeom : public CGeom
+{
+                                      
+public:
+								CNgcGeom();
+	virtual 					~CNgcGeom();
+	void						SetInstance( NxNgc::CInstance *p_instance )		{ mp_instance = p_instance; }
+	NxNgc::CInstance			*GetInstance( void )								{ return mp_instance; }
+	void						InitMeshList();
+	void						ClearMeshList();
+	void						AddMesh( NxNgc::sMesh * );
+	Lst::Head< NxNgc::sMesh >	*GetMeshList();
+
+	void						CreateMeshArray();
+	bool						RegisterMeshArray( bool just_count );
+	void						DestroyMeshArray( void );
+
+	const Mth::CBBox &			GetBoundingBox( void )			{ return m_bbox; }
+	void						SetScene( CNgcScene *p_scene )	{ mp_scene = p_scene; }
+	NxNgc::sScene				*GenerateScene( void );
+
+	Mth::Vector *				GetOffset( void ) { return &m_mod.offset; }
+	Mth::Vector *				GetScale( void ) { return &m_mod.scale; }
+
+private:						// It's all private, as it is machine specific
+	virtual	bool					plat_load_geom_data( CMesh *pMesh, CModel *pModel, bool color_per_material );
+	virtual	void					plat_set_active( bool active );
+	virtual bool					plat_is_active( void ) const;
+	const Mth::CBBox &				plat_get_bounding_box( void ) const;
+	virtual void					plat_set_bounding_sphere( const Mth::Vector& boundingSphere );
+	virtual const Mth::Vector		plat_get_bounding_sphere( void ) const;
+	virtual void					plat_set_world_position( const Mth::Vector& pos );
+	virtual const Mth::Vector &		plat_get_world_position() const;
+	virtual bool					plat_render( Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones );
+	virtual void					plat_set_bone_matrix_data( Mth::Matrix* pBoneMatrices, int numBones );
+	virtual bool					plat_hide_polys( uint32 mask );
+	virtual uint32					plat_get_visibility( void ) const;
+	virtual	void					plat_set_visibility( uint32 mask );
+	virtual void					plat_set_color( Image::RGBA rgba );
+	virtual void					plat_clear_color( void );
+	virtual Image::RGBA				plat_get_color( void ) const;
+	virtual CNgcGeom				*CNgcGeom::plat_clone( bool instance, CScene *p_dest_scene = NULL );
+	virtual CNgcGeom				*CNgcGeom::plat_clone( bool instance, CModel* pDestModel );
+	virtual void					plat_set_orientation(const Mth::Matrix& orient);
+	virtual void 					plat_rotate_y(Mth::ERot90 rot);
+	virtual void					plat_set_transform(const Mth::Matrix& transform);
+	virtual const 					Mth::Matrix &	plat_get_transform() const;
+	virtual	int						plat_get_num_render_verts( void );
+	virtual	void					plat_get_render_verts( Mth::Vector *p_verts );
+	virtual	void					plat_get_render_colors( Image::RGBA *p_colors );
+	virtual void					plat_set_render_verts( Mth::Vector *p_verts );
+	virtual	void					plat_set_render_colors( Image::RGBA *p_colors );
+	virtual void					plat_set_model_lights( CModelLights* p_model_lights );
+	virtual void					plat_set_scale(const Mth::Vector& scale);
+	virtual Mth::Vector 			plat_get_scale() const;
+	virtual bool					plat_set_material_color( uint32 mat_name_checksum, int pass, Image::RGBA rgba );
+	virtual void					plat_set_uv_wibble_params( float u_vel, float u_amp, float u_freq, float u_phase, float v_vel, float v_amp, float v_freq, float v_phase );
+	virtual void					plat_use_explicit_uv_wibble( bool yes );
+	virtual void					plat_set_uv_wibble_offsets( float u_offset, float v_offset );
+	virtual bool					plat_set_uv_wibble_offsets( uint32 mat_name_checksum, int pass, float u_offset, float v_offset );
+	virtual bool					plat_set_uv_matrix( uint32 mat_name_checksum, int pass, const Mth::Matrix& mat );
+	virtual bool					plat_allocate_uv_matrix_params( uint32 mat_checksum, int pass );
+
+public:
+	Mth::CBBox						m_bbox;	
+	
+	CNgcScene *					mp_scene;
+	Lst::Head< NxNgc::sMesh >		*mp_init_mesh_list;   
+	NxNgc::sMesh **				m_mesh_array;
+	uint							m_num_mesh;
+	uint32							m_visible;
+	bool							m_active;
+
+private:
+	NxNgc::CInstance				*mp_instance;
+	CNgcMesh						*mp_mesh;		// Used for obtaining CAS poly removal data.
+	NxNgc::sMeshMod					m_mod;
+
+	int								build_index_map( int * p_pos_index, int * p_col_index );
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
+
diff --git a/Code/Gfx/NGC/p_NxImposter.cpp b/Code/Gfx/NGC/p_NxImposter.cpp
new file mode 100644
index 0000000..59a9594
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxImposter.cpp
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxImposter.cpp
+
+#include 	"gfx/ngc/p_NxGeom.h"
+#include 	"gfx/ngc/p_NxImposter.h"
+
+namespace Nx
+{
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CImposterGroup* CImposterManager::plat_create_imposter_group( void )
+{
+	return new CNgcImposterGroup;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::plat_pre_render_imposters( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::plat_post_render_imposters( void )
+{
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CImposterGroup
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcImposterGroup::CNgcImposterGroup()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcImposterGroup::~CNgcImposterGroup()
+{
+}
+
+
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGC/p_NxImposter.h b/Code/Gfx/NGC/p_NxImposter.h
new file mode 100644
index 0000000..32e81d8
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxImposter.h
@@ -0,0 +1,30 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.h
+
+#ifndef	__GFX_P_NX_IMPOSTER_H__
+#define	__GFX_P_NX_IMPOSTER_H__
+
+#include 	"gfx/NxImposter.h"
+
+namespace Nx
+{
+	
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CImposterGroup
+class CNgcImposterGroup : public CImposterGroup
+{
+	public:
+								CNgcImposterGroup();
+	virtual						~CNgcImposterGroup();
+
+
+	private:					// It's all private, as it is machine specific
+
+	// Machine specific members
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGC/p_NxLightMan.cpp b/Code/Gfx/NGC/p_NxLightMan.cpp
new file mode 100644
index 0000000..a643961
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxLightMan.cpp
@@ -0,0 +1,250 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLightMan.cpp - Ngc platform specific interface to CLightManager
+
+#include 
+#include "gfx/NxLightMan.h"
+#include "gfx/Ngc/p_NxLight.h"
+#include "gfx/Ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+namespace Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_plat_update_engine( void )
+{
+	int difr = (int)((float)s_world_lights.m_light_ambient_rgba.r * s_ambient_brightness);
+	int difg = (int)((float)s_world_lights.m_light_ambient_rgba.g * s_ambient_brightness);
+	int difb = (int)((float)s_world_lights.m_light_ambient_rgba.b * s_ambient_brightness);
+	NxNgc::EngineGlobals.ambient_light_color.r = difr > 255 ? 255 : (u8)difr;  
+	NxNgc::EngineGlobals.ambient_light_color.g = difg > 255 ? 255 : (u8)difg;  
+	NxNgc::EngineGlobals.ambient_light_color.b = difb > 255 ? 255 : (u8)difb;  
+
+//	NxNgc::EngineGlobals.ambient_light_color.r = NxNgc::EngineGlobals.ambient_light_color.r < 128 ? NxNgc::EngineGlobals.ambient_light_color.r * 2 : 255;
+//	NxNgc::EngineGlobals.ambient_light_color.g = NxNgc::EngineGlobals.ambient_light_color.g < 128 ? NxNgc::EngineGlobals.ambient_light_color.g * 2 : 255;
+//	NxNgc::EngineGlobals.ambient_light_color.b = NxNgc::EngineGlobals.ambient_light_color.b < 128 ? NxNgc::EngineGlobals.ambient_light_color.b * 2 : 255;
+
+	for( int i = 0; i < 2; ++i )
+	{
+		Image::RGBA	dif = CLightManager::sGetLightDiffuseColor( i );
+		int difr = (int)((float)dif.r * s_diffuse_brightness[i]);
+		int difg = (int)((float)dif.g * s_diffuse_brightness[i]);
+		int difb = (int)((float)dif.b * s_diffuse_brightness[i]);
+		NxNgc::EngineGlobals.diffuse_light_color[i].r = difr > 255 ? 255 : (u8)difr;
+		NxNgc::EngineGlobals.diffuse_light_color[i].g = difg > 255 ? 255 : (u8)difg;
+		NxNgc::EngineGlobals.diffuse_light_color[i].b = difb > 255 ? 255 : (u8)difb;
+
+//		NxNgc::EngineGlobals.diffuse_light_color[i].r = NxNgc::EngineGlobals.diffuse_light_color[i].r < 128 ? NxNgc::EngineGlobals.diffuse_light_color[i].r * 2 : 255;
+//		NxNgc::EngineGlobals.diffuse_light_color[i].g = NxNgc::EngineGlobals.diffuse_light_color[i].g < 128 ? NxNgc::EngineGlobals.diffuse_light_color[i].g * 2 : 255;
+//		NxNgc::EngineGlobals.diffuse_light_color[i].b = NxNgc::EngineGlobals.diffuse_light_color[i].b < 128 ? NxNgc::EngineGlobals.diffuse_light_color[i].b * 2 : 255;
+
+		Mth::Vector dir = CLightManager::sGetLightDirection( i );
+		dir.Normalize();
+		NxNgc::EngineGlobals.light_x[i] = dir[X] * 200000.0f;
+		NxNgc::EngineGlobals.light_y[i] = dir[Y] * 200000.0f;
+		NxNgc::EngineGlobals.light_z[i] = dir[Z] * 200000.0f;
+	}
+	// Set lighting.
+//	GXLightObj light_obj[2];
+//
+////	GX::SetChanAmbColor( GX_COLOR0A0, NxNgc::EngineGlobals.ambient_light_color );
+//
+////	GXInitLightColor( &light_obj[0], NxNgc::EngineGlobals.diffuse_light_color[0] );
+////	GXInitLightPos( &light_obj[0], NxNgc::EngineGlobals.light_x[0], NxNgc::EngineGlobals.light_y[0], NxNgc::EngineGlobals.light_z[0] );
+////	GXLoadLightObjImm( &light_obj[0], GX_LIGHT0 );
+////	GXInitSpecularDir( &light_obj[2], -NxNgc::EngineGlobals.light_x[0] / 200000.0f, -NxNgc::EngineGlobals.light_y[0] / 200000.0f, -NxNgc::EngineGlobals.light_z[0] / 200000.0f );
+////	GXInitLightShininess( &light_obj[2], 5.0f );
+////
+////	GXInitLightColor( &light_obj[1], NxNgc::EngineGlobals.diffuse_light_color[1] );
+////	GXInitLightPos( &light_obj[1], NxNgc::EngineGlobals.light_x[1], NxNgc::EngineGlobals.light_y[1], NxNgc::EngineGlobals.light_z[1] );
+////	GXLoadLightObjImm( &light_obj[1], GX_LIGHT1 );
+////	GXInitSpecularDir( &light_obj[2], -NxNgc::EngineGlobals.light_x[1] / 200000.0f, -NxNgc::EngineGlobals.light_y[1] / 200000.0f, -NxNgc::EngineGlobals.light_z[1] / 200000.0f );
+////	GXInitLightShininess( &light_obj[2], 5.0f );
+//
+//    Vec  ldir;
+//    f32  theta, phi;
+//
+//    // Light position
+//    theta = (f32)90 * Mth::PI / 180.0F;
+//    phi   = (f32)0   * Mth::PI / 180.0F;
+//    ldir.x = - 1.0F * cosf(phi) * sinf(theta);
+//    ldir.y = - 1.0F * sinf(phi);
+//    ldir.z = - 1.0F * cosf(phi) * cosf(theta);
+//
+//    // Convert light position into view space
+//    Vec  dir0;
+//    Vec  dir1;
+//    MTXMultVecSR(NxNgc::EngineGlobals.current_uploaded, &ldir, &dir0);
+//    MTXMultVecSR(NxNgc::EngineGlobals.current_uploaded, &ldir, &dir1);
+//
+//////	GXInitSpecularDir( &light_obj[0], NxNgc::EngineGlobals.light_x[0] / 200000.0f, NxNgc::EngineGlobals.light_y[0] / 200000.0f, NxNgc::EngineGlobals.light_z[0] / 200000.0f );
+////	GXInitSpecularDirv( &light_obj[0], &dir0 );
+////	GXInitLightShininess( &light_obj[0], 16.0f );
+////	GXInitLightColor( &light_obj[0], (GXColor){0,0,0,0}/*NxNgc::EngineGlobals.diffuse_light_color[0]*/ );
+////	GXLoadLightObjImm( &light_obj[0], GX_LIGHT0 );
+//
+////	GXInitSpecularDir( &light_obj[1], NxNgc::EngineGlobals.light_x[1] / 200000.0f, NxNgc::EngineGlobals.light_y[1] / 200000.0f, NxNgc::EngineGlobals.light_z[1] / 200000.0f );
+//	GXInitSpecularDirv( &light_obj[0], &dir0 );
+//	GXInitLightShininess( &light_obj[0], 5.0f );
+//	GXInitLightColor( &light_obj[0], NxNgc::EngineGlobals.diffuse_light_color[1] );
+//	GXLoadLightObjImm( &light_obj[0], GX_LIGHT0 );
+//
+//	GXSetChanAmbColor( GX_COLOR0A0, (GXColor){0,0,0,255} );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_plat_update_lights( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_set_light_ambient_color()
+{
+	int difr = (int)((float)s_world_lights.m_light_ambient_rgba.r * s_ambient_brightness);
+	int difg = (int)((float)s_world_lights.m_light_ambient_rgba.g * s_ambient_brightness);
+	int difb = (int)((float)s_world_lights.m_light_ambient_rgba.b * s_ambient_brightness);
+	NxNgc::EngineGlobals.ambient_light_color.r = difr > 255 ? 255 : (u8)difr;
+	NxNgc::EngineGlobals.ambient_light_color.g = difg > 255 ? 255 : (u8)difg;
+	NxNgc::EngineGlobals.ambient_light_color.b = difb > 255 ? 255 : (u8)difb;
+	
+	NxNgc::EngineGlobals.ambient_light_color.r = NxNgc::EngineGlobals.ambient_light_color.r < 128 ? NxNgc::EngineGlobals.ambient_light_color.r * 2 : 255;
+	NxNgc::EngineGlobals.ambient_light_color.g = NxNgc::EngineGlobals.ambient_light_color.g < 128 ? NxNgc::EngineGlobals.ambient_light_color.g * 2 : 255;
+	NxNgc::EngineGlobals.ambient_light_color.b = NxNgc::EngineGlobals.ambient_light_color.b < 128 ? NxNgc::EngineGlobals.ambient_light_color.b * 2 : 255;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CLightManager::s_plat_get_light_ambient_color()
+{
+	return s_world_lights.m_light_ambient_rgba;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_set_light_direction( int light_index )
+{
+	NxNgc::EngineGlobals.light_x[light_index] = -s_world_lights.m_light_direction[light_index][X];
+	NxNgc::EngineGlobals.light_y[light_index] = -s_world_lights.m_light_direction[light_index][Y];
+	NxNgc::EngineGlobals.light_z[light_index] = -s_world_lights.m_light_direction[light_index][Z];
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector & CLightManager::s_plat_get_light_direction( int light_index )
+{
+	static Mth::Vector dir;
+	dir.Set( s_world_lights.m_light_direction[light_index][X], s_world_lights.m_light_direction[light_index][Y], s_world_lights.m_light_direction[light_index][Z] );
+	return dir;
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_set_light_diffuse_color( int light_index )
+{
+	NxNgc::EngineGlobals.diffuse_light_color[light_index].r = (u8)((float)s_world_lights.m_light_diffuse_rgba[light_index].r * s_diffuse_brightness[light_index]);
+	NxNgc::EngineGlobals.diffuse_light_color[light_index].g = (u8)((float)s_world_lights.m_light_diffuse_rgba[light_index].g * s_diffuse_brightness[light_index]);
+	NxNgc::EngineGlobals.diffuse_light_color[light_index].b = (u8)((float)s_world_lights.m_light_diffuse_rgba[light_index].b * s_diffuse_brightness[light_index]);
+	
+	NxNgc::EngineGlobals.diffuse_light_color[light_index].r = NxNgc::EngineGlobals.diffuse_light_color[light_index].r < 128 ? NxNgc::EngineGlobals.diffuse_light_color[light_index].r * 2 : 255;
+	NxNgc::EngineGlobals.diffuse_light_color[light_index].g = NxNgc::EngineGlobals.diffuse_light_color[light_index].g < 128 ? NxNgc::EngineGlobals.diffuse_light_color[light_index].g * 2 : 255;
+	NxNgc::EngineGlobals.diffuse_light_color[light_index].b = NxNgc::EngineGlobals.diffuse_light_color[light_index].b < 128 ? NxNgc::EngineGlobals.diffuse_light_color[light_index].b * 2 : 255;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CLightManager::s_plat_get_light_diffuse_color( int light_index )
+{
+	return s_world_lights.m_light_diffuse_rgba[light_index];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_plat_update_colors( void )
+{
+	s_plat_set_light_ambient_color();
+	for( int i = 0; i < MAX_LIGHTS; ++i )
+	{
+		s_plat_set_light_diffuse_color( i );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CModelLights *CLightManager::s_plat_create_model_lights()
+{
+	return new CNgcModelLights;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_free_model_lights( CModelLights *p_model_lights )
+{
+	Dbg_Assert( p_model_lights );
+
+	delete p_model_lights;
+
+	return true;
+}
+
+
+
+} 
+ 
+
diff --git a/Code/Gfx/NGC/p_NxMesh.cpp b/Code/Gfx/NGC/p_NxMesh.cpp
new file mode 100644
index 0000000..bcf37de
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxMesh.cpp
@@ -0,0 +1,231 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxMesh.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+//extern Nx::CNgcScene *p_skater;
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int cmp( const void *p1, const void *p2 )
+{
+	NxNgc::sCASData *p_casdata1 = (NxNgc::sCASData*)p1;
+	NxNgc::sCASData *p_casdata2 = (NxNgc::sCASData*)p2;
+	
+	uint32 mesh1 = p_casdata1->data0 >> 16;
+	uint32 mesh2 = p_casdata2->data0 >> 16;
+	
+	if( mesh1 > mesh2 )
+	{
+		return 1;
+	}
+	else if( mesh1 < mesh2 )
+	{
+		return -1;
+	}
+	else
+	{
+		uint32 indexzero1 = p_casdata1->data0 & 0xFFFF;
+		uint32 indexzero2 = p_casdata2->data0 & 0xFFFF;
+		if( indexzero1 > indexzero2 )
+		{
+			return 1;
+		}
+		else if( indexzero1 < indexzero2 )
+		{
+			return -1;
+		}
+	}
+	return 0;
+}
+
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcMesh::build_casdata_table( const char* pFileName )
+{
+	void *pFile = File::Open( pFileName, "rb" );
+	Dbg_MsgAssert( pFile, ( "Couldn't open CAS data file %s\n", pFileName ));
+
+	uint32 version;
+	File::Read( &version, sizeof( uint32 ), 1, pFile );
+
+	if( version >= 2 )
+	{
+		File::Read( &m_CASRemovalMask, sizeof( uint32 ), 1, pFile );
+	}
+	
+	File::Read( &m_numCASData, sizeof( int ), 1, pFile );
+
+	if( m_numCASData > 0 )
+	{
+		// CAS flags.
+		mp_CASData = new NxNgc::sCASData[m_numCASData];
+		File::Read( &mp_CASData[0].mask,	sizeof( uint32 ) * 3 * m_numCASData, 1, pFile );
+	
+		// Sort the CAS data based first on mesh, then on the first tri index, lowest to highest. This allows some efficient early-out checking
+		// during the poly removal.
+		qsort( mp_CASData, m_numCASData, sizeof( NxNgc::sCASData ), cmp );
+	}
+
+	File::Close( pFile );
+	return ( m_numCASData > 0 );
+}
+
+#define MemoryRead( dst, size, num, src )	memcpy(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcMesh::build_casdata_table_from_memory( void **pp_mem )
+{
+	uint8 *p_data = (uint8*)( *pp_mem );
+
+	uint32 version;
+	MemoryRead( &version, sizeof( uint32 ), 1, p_data );
+
+	if( version >= 2 )
+	{
+		MemoryRead( &m_CASRemovalMask, sizeof( uint32 ), 1, p_data );
+	}
+	
+	MemoryRead( &m_numCASData, sizeof( int ), 1, p_data );
+
+	if( m_numCASData > 0 )
+	{
+		// CAS flags.
+		mp_CASData = new NxNgc::sCASData[m_numCASData];
+
+		for( uint32 i = 0; i < m_numCASData; ++i )
+		{
+			MemoryRead( &mp_CASData[i].mask, sizeof( uint32 ) * 3, 1, p_data );
+		}
+
+		// Sort the CAS data based first on mesh, then on the first tri index, lowest to highest. This allows some efficient early-out checking
+		// during the poly removal.
+		qsort( mp_CASData, m_numCASData, sizeof( NxNgc::sCASData ), cmp );
+	}
+	
+	// Set the data pointer to the new position on return.
+	*pp_mem = p_data;
+
+	return ( m_numCASData > 0 );
+}
+
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcMesh::CNgcMesh( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcMesh::CNgcMesh( const char *pMeshFileName )
+{
+	// Only do the CAS flag building for skinned objects.
+	if( strstr( pMeshFileName, "skin" ))
+	{
+		char CASFileName[256];
+		strcpy( CASFileName, pMeshFileName );
+		Str::LowerCase( CASFileName );
+		char *pExt = strstr( CASFileName, "skin.ngc" );
+		if( pExt )
+		{
+			Dbg_MsgAssert( pExt, ( "Couldn't find skin.ngc extension in %s", CASFileName ));
+			strcpy( pExt, "cas.ngc" );
+			build_casdata_table( CASFileName );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcMesh::~CNgcMesh( void )
+{
+	// unload textures
+//	NxNgc::DeleteTextures( mp_scene );
+
+	if( mp_CASData )
+	{
+		delete [] mp_CASData;
+	}
+	
+	if( mp_scene )
+	{
+		CEngine::sUnloadScene( mp_scene );
+//		NxNgc::DeleteScene( mp_engine_scene );
+	}
+}
+
+
+void CNgcMesh::SetScene( CNgcScene *p_scene )
+{
+	mp_scene = p_scene;
+	// Copy the hierarchy info from the scene so that above the p-line stuff can access it.
+	mp_hierarchyObjects = mp_scene->GetEngineScene()->mp_hierarchyObjects;
+	m_numHierarchyObjects = mp_scene->GetEngineScene()->m_numHierarchyObjects;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcMesh::SetCASData( uint8 *p_cas_data )
+{
+	if( p_cas_data )
+	{
+		build_casdata_table_from_memory((void**)&p_cas_data );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+				
+
diff --git a/Code/Gfx/NGC/p_NxMesh.h b/Code/Gfx/NGC/p_NxMesh.h
new file mode 100644
index 0000000..8c4bc8b
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxMesh.h
@@ -0,0 +1,66 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxMesh.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_MESH_H__
+#define	__GFX_P_NX_MESH_H__
+    
+#include "gfx/nxmesh.h"
+#include "p_nxscene.h"
+
+namespace NxNgc
+{
+	struct sScene;
+}
+			 
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CMesh
+    
+class CNgcMesh : public CMesh
+{
+                                      
+public:
+						CNgcMesh( void );
+						CNgcMesh( const char *pMeshFileName );
+	virtual 			~CNgcMesh();
+	void				SetScene( CNgcScene *p_scene );
+
+	void				SetTexDict( Nx::CTexDict *p_tex_dict )	{ mp_texDict = p_tex_dict; }
+	void				SetCASData( uint8 *p_cas_data );
+	CNgcScene			*GetScene( void )						{ return mp_scene; }
+
+	NxNgc::sCASData		*GetCASData( void )						{ return mp_CASData; }
+	uint32				GetNumCASData( void )					{ return m_numCASData; }
+
+protected:
+	bool				build_casdata_table(const char* pFileName);
+	bool				build_casdata_table_from_memory( void **pp_mem );
+	
+	NxNgc::sCASData		*mp_CASData;
+	uint32				m_numCASData;
+
+private:
+	CNgcScene			*mp_scene;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
+
diff --git a/Code/Gfx/NGC/p_NxSprite.cpp b/Code/Gfx/NGC/p_NxSprite.cpp
new file mode 100644
index 0000000..f0c99fa
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxSprite.cpp
@@ -0,0 +1,139 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxSprite.cpp
+
+#include 	"Gfx/Ngc/p_NxSprite.h"
+#include 	"Gfx/Ngc/p_NxTexture.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CSprite
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcSprite::CNgcSprite()
+{
+	mp_plat_sprite = new NxNgc::sSprite();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcSprite::~CNgcSprite()
+{
+	delete mp_plat_sprite;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcSprite::plat_initialize()
+{
+	plat_update_engine();
+	plat_update_priority();
+	plat_update_hidden();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcSprite::plat_update_hidden()
+{
+	// Take sprite on or off draw list
+	mp_plat_sprite->SetHidden( m_hidden );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcSprite::plat_update_engine()
+{
+	CNgcTexture *p_Ngc_texture = static_cast( mp_texture );
+
+	// Rebuild sprite primitives
+
+	if ( p_Ngc_texture )
+	{
+		mp_plat_sprite->mp_texture	= p_Ngc_texture->GetEngineTexture();
+	}
+	else
+	{
+		mp_plat_sprite->mp_texture	= NULL;
+	}
+
+	mp_plat_sprite->m_xpos		= m_pos_x;
+	mp_plat_sprite->m_ypos		= m_pos_y;
+	mp_plat_sprite->m_width		= m_width;
+	mp_plat_sprite->m_height	= m_height;
+	mp_plat_sprite->m_scale_x	= m_scale_x;
+	mp_plat_sprite->m_scale_y	= m_scale_y;
+
+	mp_plat_sprite->m_xhot		= (( m_anchor_x + 1.0f ) * 0.5f) * ( m_width );
+	mp_plat_sprite->m_yhot		= (( m_anchor_y + 1.0f ) * 0.5f) * ( m_height );
+
+	mp_plat_sprite->m_rot		= m_rotation;
+	mp_plat_sprite->m_rgba		= *((uint32 *) &m_rgba);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcSprite::plat_update_priority()
+{
+	if( mp_plat_sprite )
+	{
+		mp_plat_sprite->SetPriority( m_priority );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::plat_enable_constant_z_value(bool enable)
+{
+	//NxPs2::SSprite::EnableConstantZValue(enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::plat_set_constant_z_value(Nx::ZBufferValue z)
+{
+	//NxPs2::SSprite::SetConstantZValue(z);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::ZBufferValue	CSprite::plat_get_constant_z_value()
+{
+	//return NxPs2::SSprite::GetConstantZValue();
+	return 0;
+}
+
+
+} // Namespace Nx  			
+				
+				
+
diff --git a/Code/Gfx/NGC/p_NxSprite.h b/Code/Gfx/NGC/p_NxSprite.h
new file mode 100644
index 0000000..545c7c0
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxSprite.h
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_SPRITE_H__
+#define	__GFX_P_NX_SPRITE_H__
+
+#include 	"Gfx/NxSprite.h"
+#include 	"Gfx/Ngc/NX/texture.h"
+#include 	"Gfx/Ngc/NX/sprite.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CSprite
+class	CNgcSprite : public CSprite
+{
+public:
+								CNgcSprite();
+	virtual						~CNgcSprite();
+
+private:		// It's all private, as it is machine specific
+	virtual void				plat_initialize();
+
+	virtual void				plat_update_hidden();		// Tell engine of update
+	virtual void				plat_update_engine();		// Update engine primitives
+	virtual void				plat_update_priority();
+
+	NxNgc::sSprite *			mp_plat_sprite;
+};
+
+} // Namespace Nx  			
+
+#endif
+
diff --git a/Code/Gfx/NGC/p_NxTextured3dPoly.cpp b/Code/Gfx/NGC/p_NxTextured3dPoly.cpp
new file mode 100644
index 0000000..017f130
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxTextured3dPoly.cpp
@@ -0,0 +1,27 @@
+#include 	"Gfx/ngc/p_NxTextured3dPoly.h"
+
+namespace NxNgc
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTextured3dPoly
+
+CNgcTextured3dPoly::CNgcTextured3dPoly()
+{
+}
+
+CNgcTextured3dPoly::~CNgcTextured3dPoly()
+{
+}
+
+void CNgcTextured3dPoly::plat_set_texture(uint32 texture_checksum)
+{
+}	
+
+void CNgcTextured3dPoly::plat_render()
+{
+}
+
+} // Namespace NxNgc  			
+				
+				
diff --git a/Code/Gfx/NGC/p_NxTextured3dPoly.h b/Code/Gfx/NGC/p_NxTextured3dPoly.h
new file mode 100644
index 0000000..de2ff6e
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxTextured3dPoly.h
@@ -0,0 +1,23 @@
+#ifndef	__GFX_P_NX_TEXTURED_3D_POLY_H__
+#define	__GFX_P_NX_TEXTURED_3D_POLY_H__
+
+#include 	
+
+namespace NxNgc
+{
+
+// Machine specific implementation of CTextured3dPoly
+class	CNgcTextured3dPoly : public Nx::CTextured3dPoly
+{
+public:
+		   					CNgcTextured3dPoly();
+	virtual					~CNgcTextured3dPoly();
+private:
+	void					plat_render();
+	void					plat_set_texture(uint32 texture_checksum);
+};
+
+}	// namespace NxNgc
+
+#endif
+				   
diff --git a/Code/Gfx/NGC/p_NxViewport.cpp b/Code/Gfx/NGC/p_NxViewport.cpp
new file mode 100644
index 0000000..5a039db
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxViewport.cpp
@@ -0,0 +1,47 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxViewport.cpp
+
+#include 	"Gfx/NGC/p_NxViewport.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CViewport
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcViewport::CNgcViewport()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcViewport::CNgcViewport( const Mth::Rect* rect, Gfx::Camera* cam) :
+	CViewport(rect, cam)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcViewport::~CNgcViewport()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+} // Namespace Nx  			
+				
+				
+
diff --git a/Code/Gfx/NGC/p_NxViewport.h b/Code/Gfx/NGC/p_NxViewport.h
new file mode 100644
index 0000000..13a95b6
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxViewport.h
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxViewport.h
+
+#ifndef	__GFX_P_NX_VIEWPORT_H__
+#define	__GFX_P_NX_VIEWPORT_H__
+
+#include 	"Gfx/NxViewport.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CViewport
+class	CNgcViewport : public CViewport
+{
+public:
+								CNgcViewport();
+								CNgcViewport(const Mth::Rect* rect, Gfx::Camera* cam = NULL);
+	virtual						~CNgcViewport();
+
+private:		// It's all private, as it is machine specific
+	//virtual void				plat_initialize();
+
+};
+
+} // Namespace Nx  			
+
+#endif
+
diff --git a/Code/Gfx/NGC/p_NxWin2D.cpp b/Code/Gfx/NGC/p_NxWin2D.cpp
new file mode 100644
index 0000000..f743a91
--- /dev/null
+++ b/Code/Gfx/NGC/p_NxWin2D.cpp
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxWin2D.cpp
+
+#include 	"Gfx/Ngc/p_NxWin2D.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcWindow2D::CNgcWindow2D(int x, int y, int width, int height) : CWindow2D(x, y, width, height)
+{
+//	mp_plat_window = new NxNgc::SScissorWindow();
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcWindow2D::~CNgcWindow2D()
+{
+//	if (mp_plat_window)
+//	{
+//		delete mp_plat_window;
+//	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CNgcWindow2D::plat_update_engine()
+{
+	Dbg_Assert(m_xpos >= 0);
+	Dbg_Assert(m_ypos >= 0);
+
+	int x1 = m_xpos + m_width - 1;
+	int y1 = m_ypos + m_height - 1;
+
+	Dbg_Assert(x1 >= 0);
+	Dbg_Assert(y1 >= 0);
+
+//	mp_plat_window->SetScissor(m_xpos, m_ypos, x1, y1);
+	m_left		= m_xpos;
+	m_top		= m_ypos;
+	m_right 	= x1;
+	m_bottom	= y1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CWindow2DManager::s_plat_alloc_window2d_pool()
+{
+	for (int i = 0; i < vMAX_WINDOW_INSTANCES; i++)
+	{
+	   	CNgcWindow2D *p_window = new CNgcWindow2D;
+		p_window->mp_next = sp_window_list;
+		sp_window_list = p_window;
+	}
+}
+
+} // Namespace Nx  			
+				
+				
+
diff --git a/Code/Gfx/NGC/p_gfxman.cpp b/Code/Gfx/NGC/p_gfxman.cpp
new file mode 100644
index 0000000..2a03fa1
--- /dev/null
+++ b/Code/Gfx/NGC/p_gfxman.cpp
@@ -0,0 +1,215 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		p_gfxman.cpp											**
+**																			**
+**	Created:		07/26/99	-	mjb										**
+**																			**
+**	Description:	Graphics device manager									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+//#define SELECT_VIDEO_MODE
+// Some defines for gamma settings.
+#define gammaDefault	1.0f
+#define gammaDomain		256
+#define gammaRange		256.0f
+#define MIN_GAMMA		0.0f
+#define MAX_GAMMA		2.0f
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+//static D3DGAMMARAMP	gammaTable;
+//static float		gammaValueR = gammaDefault;	// Current red gamma value.
+//static float		gammaValueG = gammaDefault;	// Current green gamma value.
+//static float		gammaValueB = gammaDefault;	// Current blue gamma value.
+
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//static void gammaInitTable( void )
+//{
+//	// Create the gamma intensity lookup table.
+//	// It works like this: given a colour value r [0,255] do r' = gammaTable.red[r] etc.
+//	for( int i = 0; i < gammaDomain; ++i )
+//	{
+//		gammaTable.red[i]	= (BYTE)((float)gammaRange * pow(((float)i / (float)gammaDomain ), 1.0f / gammaValueR ));
+//		gammaTable.green[i]	= (BYTE)((float)gammaRange * pow(((float)i / (float)gammaDomain ), 1.0f / gammaValueG ));
+//		gammaTable.blue[i]	= (BYTE)((float)gammaRange * pow(((float)i / (float)gammaDomain ), 1.0f / gammaValueB ));
+//	}
+//}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void gammaSetValue( float gr, float gg, float gb )
+{
+//	gammaValueR = gr;
+//	gammaValueG = gg;
+//	gammaValueB = gb;
+//
+//	// Create the table.
+//	gammaInitTable();
+//
+//	// Pass the table along to the hardware.
+//	D3DDevice_SetGammaRamp( D3DSGR_NO_CALIBRATION, &gammaTable );
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void Manager::SetGammaNormalized( float fr, float fg, float fb )
+//{
+//	// Cap it....
+//	fr = ( fr < 0.0f ) ? 0.0f : (( fr > 1.0f ) ? 1.0f : fr );
+//	fg = ( fg < 0.0f ) ? 0.0f : (( fg > 1.0f ) ? 1.0f : fg );
+//	fb = ( fb < 0.0f ) ? 0.0f : (( fb > 1.0f ) ? 1.0f : fb );
+//
+//	// Scale it....
+//	fr *= MAX_GAMMA;
+//	fg *= MAX_GAMMA;
+//	fb *= MAX_GAMMA;
+//
+//	// Offset it.
+//	fr += 0.5f;
+//	fg += 0.5f;
+//	fb += 0.5f;
+//
+//	// f now goes from 0.5 to 2.5.
+//	gammaSetValue( fr, fg, fb );
+//}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::DumpVRAMUsage( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::ScreenShot( const char *fileroot )
+{
+//	char fileName[32];
+//	char fullFileName[64];
+//
+////	Skate3 code, disabled for now.
+//#	if 0
+//	
+//	// Try to find a good filename of the format filebasexxx.bmp. "Good" is defined here as one that isn't already used.
+//	RwFileFunctions* fileFunctions = RwOsGetFileInterface();
+//	for( int i = 0;; ++i )
+//	{
+//		sprintf( fileName, "screens\\%s%03d.bmp", fileroot, i );
+//
+//		// Found an unused one! Yay!
+//		if( !fileFunctions->rwfexist( fileName ))
+//		{
+//			sprintf( fullFileName, "d:\\data\\screens\\%s%03d.bmp", fileroot, i );
+//			break;
+//		}
+//	}
+//
+//	// Obtain the render surface.
+//	IDirect3DSurface8* p_render_target;
+//	D3DDevice_GetRenderTarget( &p_render_target );
+//
+//	// Get the surface description, just for s and g.
+//	D3DSURFACE_DESC surface_desc;
+//	p_render_target->GetDesc( &surface_desc );
+//
+//	// This is great - this function spits surfaces straight out into a file.
+//	HRESULT hr = XGWriteSurfaceToFile( p_render_target, fullFileName );
+//	Dbg_Assert( hr == S_OK );
+//
+//#	endif
+}
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace Gfx
diff --git a/Code/Gfx/NGC/p_loadscreen.cpp b/Code/Gfx/NGC/p_loadscreen.cpp
new file mode 100644
index 0000000..548994d
--- /dev/null
+++ b/Code/Gfx/NGC/p_loadscreen.cpp
@@ -0,0 +1,648 @@
+///*****************************************************************************
+//**																			**
+//**			              Neversoft Entertainment.			                **
+//**																		   	**
+//**				   Copyright (C) 2000 - All Rights Reserved				   	**
+//**																			**
+//******************************************************************************
+//**																			**
+//**	Project:		Gfx														**
+//**																			**
+//**	Module:			LoadScreen			 									**
+//**																			**
+//**	File name:		p_loadscreen.cpp										**
+//**																			**
+//**	Created by:		05/08/02	-	SPG										**
+//**																			**
+//**	Description:	PS2-specific loading screen calls						**
+//**																			**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  	  Includes									**
+//*****************************************************************************/
+//
+//#include 
+//
+//#include 
+//#include 
+//
+//#include 
+//
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//
+//#include "sys/ngc/p_profile.h"
+//
+//#include 
+//
+//NsProfile _gpu( "GPU", 256 );
+//NsProfile _cpu( "CPU", 256 );
+//static volatile int _open = 0;
+//
+//NsCamera			camera2D;
+//int heaperror = 0;
+//
+///*****************************************************************************
+//**								DBG Information								**
+//*****************************************************************************/
+//
+//extern void	(*pIconCallback)( void );
+//
+////extern void* copyXFB;
+////extern void* dispXFB;
+////extern void* myXFB1;
+////extern void* myXFB2;
+//
+//namespace LoadScreen
+//{
+//
+///*****************************************************************************
+//**								  Externals									**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**								   Defines									**
+//*****************************************************************************/
+//
+//#define vMAX_PIXEL_DATA_SIZE	( 32767 * 16 )
+//
+//#define	ENABLE_LOADING_ICON		1
+//
+//
+///*****************************************************************************
+//**								Private Types								**
+//*****************************************************************************/
+//
+//class LoadingIconData
+//{
+//public:
+//	int			m_X;
+//	int			m_Y;
+//	int			m_FrameDelay;
+//	NsTexture**	m_Rasters;
+//	int			m_NumFrames;
+//};
+//
+///*****************************************************************************
+//**								 Private Data								**
+//*****************************************************************************/
+//
+//static bool				s_loading_screen_on		= false;
+//static OSThread			s_load_icon_thread;
+//static OSAlarm			s_alarm;
+//static LoadingIconData	s_load_icon_data		= { 0 };
+////static char				s_load_icon_thread_stack[4096]	__attribute__ (( aligned( 16 )));
+//static volatile bool	s_terminate_thread		= false;
+//static volatile bool	s_terminate_thread_done	= false;
+//static bool				s_thread_exists			= false;
+//
+///*****************************************************************************
+//**								 Public Data								**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Private Prototypes							**
+//*****************************************************************************/
+//
+///*****************************************************************************
+//**							  Private Functions								**
+//*****************************************************************************/
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void s_thread_loading_icon_alarm_handler( OSAlarm* alarm, OSContext* context )
+//{
+//	// Check the thread has not been resumed yet...
+//	if( OSIsThreadSuspended( &s_load_icon_thread ))
+//	{
+//		OSResumeThread( &s_load_icon_thread );
+//	}
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void s_non_threaded_loading_icon( void )
+//{
+//	LoadingIconData* p_data = (LoadingIconData*)&s_load_icon_data;
+//
+//	static int			current_image = 0;
+//	static Tmr::Time	time = 0;
+//
+//	Tmr::Time			time_now = Tmr::GetVblanks();
+//
+//	if(( time_now < time ) || ( time_now > ( time +	p_data->m_FrameDelay )))
+//	{
+//		time = time_now;
+//
+//		NsTexture* p_texture = p_data->m_Rasters[current_image];
+//
+//		NsDisplay::begin();
+//
+//		NsRender::begin();
+//
+////		NsPrim::begin();
+//	    GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );		// 3 = positions & uvs.
+//		GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0 );
+//
+//		camera2D.orthographic( 0, 0, 640, 448 );
+//
+//		NsRender::setBlendMode( NsBlendMode_None, p_texture, (unsigned char)0 );
+//
+//		GXClearVtxDesc();
+//		GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
+//		GXSetVtxDesc( GX_VA_TEX0, GX_DIRECT );
+//		GX::SetChanMatColor( GX_COLOR0, (GXColor){ 128,128,128,128 });
+//		GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//
+//		p_texture->upload( NsTexture_Wrap_Clamp );
+//
+//		GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//		GXPosition3f32((float)p_data->m_X, (float)p_data->m_Y, -1.0f );
+//		GXTexCoord2f32( 0.0f, 0.0f );
+//		GXPosition3f32((float)p_data->m_X + (float)p_texture->m_width, (float)p_data->m_Y, -1.0f );
+//		GXTexCoord2f32( 1.0f, 0.0f );
+//		GXPosition3f32((float)p_data->m_X + (float)p_texture->m_width, (float)p_data->m_Y + (float)p_texture->m_height, -1.0f );
+//		GXTexCoord2f32( 1.0f, 1.0f );
+//		GXPosition3f32((float)p_data->m_X, (float)p_data->m_Y + (float)p_texture->m_height, -1.0f );
+//		GXTexCoord2f32( 0.0f, 1.0f );
+//		GXEnd();
+//
+////		NsPrim::end();
+//
+//		camera2D.end();
+//
+//		NsRender::end();
+//
+//		NsDisplay::end( false );
+//
+//		current_image = ( current_image + 1 ) % p_data->m_NumFrames ;
+//	}
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void* s_threaded_loading_icon( void* data )
+//{
+//	LoadingIconData* p_data = (LoadingIconData*)data;
+//
+//	int		current_image = 0;
+//
+//	while( 1 )
+//	{
+//		if( s_terminate_thread )
+//		{
+//			// This thread is done...
+//			s_terminate_thread_done	= true;
+//			return NULL;
+//		}
+//
+//		bool busy = /*NsModel::busy() | DVDError() |*/ heaperror;
+//
+//		// Don't want to do draw anything whilst display lists are being built. Come back in a bit.
+//		if( !busy )
+//		{
+//			NsTexture* p_texture = p_data->m_Rasters[current_image];
+//
+//			NsDisplay::begin();
+//			NsRender::begin();
+//
+//		    GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );		// 3 = positions & uvs.
+//			GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0 );
+//
+//			camera2D.orthographic( 0, 0, 640, 448 );
+//
+//			NsRender::setBlendMode( NsBlendMode_None, p_texture, (unsigned char)0 );
+//
+//			GXClearVtxDesc();
+//			GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
+//			GXSetVtxDesc( GX_VA_TEX0, GX_DIRECT );
+//			GX::SetChanMatColor( GX_COLOR0, (GXColor){ 128,128,128,128 });
+//			GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//
+//			p_texture->upload( NsTexture_Wrap_Clamp );
+//
+//			GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//			GXPosition3f32((float)p_data->m_X, (float)p_data->m_Y, -1.0f );
+//			GXTexCoord2f32( 0.0f, 0.0f );
+//			GXPosition3f32((float)p_data->m_X + (float)p_texture->m_width, (float)p_data->m_Y, -1.0f );
+//			GXTexCoord2f32( 1.0f, 0.0f );
+//			GXPosition3f32((float)p_data->m_X + (float)p_texture->m_width, (float)p_data->m_Y + (float)p_texture->m_height, -1.0f );
+//			GXTexCoord2f32( 1.0f, 1.0f );
+//			GXPosition3f32((float)p_data->m_X, (float)p_data->m_Y + (float)p_texture->m_height, -1.0f );
+//			GXTexCoord2f32( 0.0f, 1.0f );
+//			GXEnd();
+//
+//			camera2D.end();
+//
+//			NsRender::end();
+//			NsDisplay::end( false );
+//
+//			current_image = ( current_image + 1 ) % p_data->m_NumFrames ;
+//		}
+//
+//		// Go to sleep.
+//		if( busy )
+//		{
+//			// Come back shortly.
+//			OSSetAlarm( &s_alarm, OSMillisecondsToTicks( 50 ), s_thread_loading_icon_alarm_handler );
+//		}
+//		else
+//		{
+//			// Come back in the proper time.
+//			OSSetAlarm( &s_alarm, OSMillisecondsToTicks( s_load_icon_data.m_FrameDelay ), s_thread_loading_icon_alarm_handler );
+//		}
+//
+//		OSSuspendThread( &s_load_icon_thread );
+//	}
+//}
+//
+//
+///*****************************************************************************
+//**							  Public Functions								**
+//*****************************************************************************/
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void SetLoadingIconProperties( int x, int y, int frame_delay, int num_frames, char* basename, char* ext )
+//{
+////	int i;
+////
+////	if( s_load_icon_data.m_NumFrames > 0  )
+////	{   
+////		for( i = 0; i < s_load_icon_data.m_NumFrames; i++ )
+////		{
+////			delete s_load_icon_data.m_Rasters[i];
+////		}
+////		delete [] s_load_icon_data.m_Rasters;
+////	}
+////
+////	s_load_icon_data.m_X			= x;
+////	s_load_icon_data.m_Y			= y;
+////	s_load_icon_data.m_FrameDelay	= ( frame_delay * 1000 ) / 60;		// Convert from frames to milliseconds.
+//////	s_load_icon_data.m_FrameDelay	= frame_delay;
+////	s_load_icon_data.m_NumFrames	= num_frames;
+////	s_load_icon_data.m_Rasters		= new NsTexture*[s_load_icon_data.m_NumFrames];
+////
+////	for( i = 0; i < s_load_icon_data.m_NumFrames; i++ )
+////	{
+////		NsFile file;
+////
+////		char image_name[64];
+////		sprintf( image_name, "%s%d.%s", basename, i+1, ext );
+////
+//// 		file.open( image_name );
+////		int filesize		= file.size();
+////		uint8 *pData		= (uint8*)new uint8[filesize + 31];
+////		pData				= (uint8*)OSRoundUp32B( pData );
+////		file.read( pData, filesize );
+////		file.close();
+////
+////		NsTexture *pTex		= (NsTexture*)pData;
+////		pData				= (uint8*)&pTex[1];
+////		pTex->m_pImage		= pData;
+////		pData			   += ( pTex->m_width * pTex->m_height * pTex->m_depth ) / 8;
+////		pTex->m_pPalette	= pData;
+////
+////		s_load_icon_data.m_Rasters[i] = pTex;
+////	}
+//}
+//
+//void _start( void )
+//{
+//	_gpu.start();
+//	_open = 1;
+//}
+//
+//void _end( void )
+//{
+//	_gpu.stop();
+//	_open = 0;
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//void Display( char* filename, bool just_freeze, bool blank )
+//{
+////	float y = 0;
+////
+////	while ( 1 )
+////	{
+////		Nx::CEngine::sPreRender();		
+////					 
+//////		Nx::CEngine::sRenderWorld();		
+////
+//////--------------------------------------------------
+////
+////		NsCamera cam;
+////		cam.orthographic( 0, 0, 640, 448 );
+////
+////		// Draw the screen.
+////		NsPrim::begin();
+////
+////		cam.begin();
+////
+////		NsRender::setBlendMode( NsBlendMode_None, NULL, (unsigned char)0 );
+////
+////		NsPrim::box( 64, 64 + y, 640-64, 64+80 + y, (GXColor){0,32,64,128} );
+////
+////		y += 1.0f;
+////		if ( y > 256.0f ) y -= 256.0f;
+////
+////		cam.end();
+////
+////		NsPrim::end();
+////
+//////--------------------------------------------------
+////
+////		Nx::CEngine::sPostRender();		
+////	}
+//	
+////	Spt::SingletonPtr< Mlp::Manager > mlp_man;
+////	Tmr::Time start_time;
+////
+////#if 0
+////	NsDisplay::setRenderStartCallback( _start );
+////	NsDisplay::setRenderEndCallback( _end );
+////
+////	camera2D.orthographic ( 0, 0, 640, 448 );
+////	int col = 0;
+////
+////	NsDisplay::setRenderStartCallback( _start );
+////	NsDisplay::setRenderEndCallback( _end );
+////
+////	while ( 1 ) {
+////		NsDisplay::setBackgroundColor ( (GXColor){0,0,255,0} );
+////		col+=2;
+////		if (col == 256) col = 0;
+////
+////		// Draw the screen.
+////		NsDisplay::begin();
+////		NsRender::begin();
+////		NsPrim::begin();
+////
+////		camera2D.begin();
+////
+////		_cpu.start();
+////
+////		NsRender::setBlendMode( NsBlendMode_None, NULL, (unsigned char)128 );
+////
+////		
+////		GXClearVtxDesc();
+////		GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
+////
+////		// Set material color.
+////		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,col,128} );
+////		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+////
+////		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+////
+////		
+////		for (int lp = 0; lp < 3000; lp++) {
+////			float l = 64+col;
+////			float t = 64;
+////			float r = 64+2+col;
+////			float b = 64+2;
+//////			NsPrim::box ( 64+col, 64, 80+col, 80, (GXColor){255,255,col,128} );
+////		
+////			// Send coordinates.
+////			GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+////				GXPosition3f32(l, t, -1.0f);
+////				GXPosition3f32(r, t, -1.0f);
+////				GXPosition3f32(r, b, -1.0f);
+////				GXPosition3f32(l, b, -1.0f);
+////			GXEnd();
+////		}
+////
+////		_cpu.stop();
+////
+//////		while ( _open );
+////		_gpu.histogram( 576, 32, 608, 416, (GXColor){255,0,0,128} );
+////		_cpu.histogram( 544, 32, 576, 416, (GXColor){0,255,0,128} );
+////
+////		camera2D.end();
+////
+////		NsPrim::end();
+////		NsRender::end();
+////		NsDisplay::end( true );
+////	}
+////#endif
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////
+////	if( s_loading_screen_on )
+////	{
+////		return;
+////	}
+////
+////	start_time = Tmr::GetTime();
+////
+////	if( !just_freeze && !blank )
+////	{
+////		// Load in the screen.
+////		NsFile	file;
+////		NsTexture * pTex = (NsTexture *)file.load( filename );
+////
+////		// Skip data.
+////		uint8 * pData = (uint8 *)&pTex[1];
+////		pTex->m_pImage = pData;
+////		pData += ( pTex->m_width * pTex->m_height * pTex->m_depth ) / 8;
+////		pTex->m_pPalette = pData;
+////
+////		camera2D.orthographic ( 0, 0, 640, 448 );
+////		NsDisplay::setBackgroundColor ( (GXColor){0,0,0,0} );
+////
+////		for ( int lp = 0; lp < 2; lp++ )
+////		{
+////			// Draw the screen.
+////			NsDisplay::begin();
+////			NsRender::begin();
+////			NsPrim::begin();
+////
+////			camera2D.begin();
+////
+////			NsRender::setBlendMode( NsBlendMode_None, pTex, (unsigned char)0 );
+////			NsPrim::sprite ( pTex, 0.0f, 0.0f, -1.0f );
+////
+////			camera2D.end();
+////
+////			NsPrim::end();
+////			NsRender::end();
+////			NsDisplay::end( false );
+////		}
+////
+////		// Wait for it to finish.
+////		NsDisplay::flush();
+////
+////		// We're done.
+////		delete pTex;
+////
+////		// Start the thread to display the animated loading icon.
+////#		if ENABLE_LOADING_ICON
+//////		pIconCallback = s_non_threaded_loading_icon;
+////		BOOL rv = true;
+////
+////		s_terminate_thread = false;
+////
+////		memset( &s_load_icon_thread, 0, sizeof( OSThread ));
+////		rv = OSCreateThread(	&s_load_icon_thread,
+////								s_threaded_loading_icon,										// Entry function.
+////								&s_load_icon_data,												// Argument for start function.
+////								s_load_icon_thread_stack + sizeof( s_load_icon_thread_stack ),	// Stack base (stack grows down).
+////								sizeof( s_load_icon_thread_stack ),								// Stack size in bytes.
+////								1,																// Thread priority.
+////								OS_THREAD_ATTR_DETACH  );										// Thread attributes.
+////		Dbg_MsgAssert( rv, ( "Failed to create thread" ));
+////
+////		if( rv )
+////		{
+////			s_thread_exists = true;
+//////			OSResumeThread( &s_load_icon_thread );
+////			OSSetAlarm( &s_alarm, OSMillisecondsToTicks( s_load_icon_data.m_FrameDelay ), s_thread_loading_icon_alarm_handler );
+////		}
+////#		endif // ENABLE_LOADING_ICON
+////	}
+////	else
+////	{
+////		// Wait a couple of VBlanks for RW stuff to disappear
+////		// Mick: so there is no pending page flip
+////		while( Tmr::GetVblanks() - start_time < 2 );		
+////		
+////		// not loading, just freezing, maybe should copy current buffer over second buffer?
+////		if( blank )
+////		{
+////			// Need to clear the screen here.
+//////			RpSkySuspend( );	 // Turn or Renderwware inputs and stuff
+//////		    Flx::clearGsMem(0x00, 0x00, 0x00, 640, 448*2);
+//////			RpSkyResume( );		 // and trun em back on, with the screen nice and blank...
+////		}
+////	}
+////	s_loading_screen_on = true;
+////
+////	mlp_man->PauseDisplayTasks( true ); 
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//void Hide( void )
+//{
+//#	if ENABLE_LOADING_ICON
+//	if( s_thread_exists )
+//	{
+//		s_terminate_thread_done	= false;
+//		s_terminate_thread = true;
+//
+//		// Cancel the alarm and resume the thread.
+////		OSCancelAlarm( &s_alarm );
+////		OSResumeThread( &s_load_icon_thread );
+//
+//		while( !s_terminate_thread_done );
+//
+//		s_thread_exists = false;
+////		pIconCallback = NULL;
+//
+//		VISetBlack( TRUE );
+//		VIFlush();
+//		VIWaitForRetrace();
+//		NsDisplay::flush();
+//		VISetBlack( FALSE );
+//		VIFlush();
+//		VIWaitForRetrace();
+//	}
+//#	endif // ENABLE_LOADING_ICON
+//
+//	Spt::SingletonPtr< Mlp::Manager >	mlp_man;
+//	mlp_man->PauseDisplayTasks( false ); 
+//
+//	if( !s_loading_screen_on )
+//	{
+//		return;
+//	}
+//
+//	s_loading_screen_on = false;
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//void	StartProgressBar( char* tick_image, int num_ticks, int x, int y )
+//{
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//void	ProgressTick( int num_ticks )
+//{
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//void	EndProgressBar( void )
+//{
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//
+//} // namespace LoadScreen
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_memview.cpp b/Code/Gfx/NGC/p_memview.cpp
new file mode 100644
index 0000000..6419fde
--- /dev/null
+++ b/Code/Gfx/NGC/p_memview.cpp
@@ -0,0 +1,1494 @@
+//////////////////////////////////////////////////////
+// p_memview.cpp
+//
+// code for tracking memory usage, and displaying it in a graphical manner
+// keeps extra info about allocated blocks
+// including the call stack, so we can print out information
+// about specific allocated blocks
+// which we will select using the graphical memory browser
+//
+//
+// tried to use a little of the task system as possible
+// so we can run the inspector
+// without it messing with the heap it inspects
+//
+//
+
+
+extern char _mem_dump_start[];
+extern char _map_file_start[];
+extern char _symbols_start[];
+extern char _callstack_start[];
+extern char _code_end[];
+extern char	_std_mem_end[];
+extern char	_stack_size[];
+
+extern char __text_org[];
+extern char __data_org[];
+extern char __rodata_org[];
+extern char __bss_org[];
+
+extern char __rodata_orgend[];
+extern char __bss_objend[];
+extern char __text_objend[];
+
+//extern char _rwheapdebug_start[];
+
+			 
+#define	STACKDEPTH  30			 
+
+
+			 
+extern "C"				 
+{
+extern void* ENTRYPOINT;		
+}
+
+#include 
+#include 
+#include 					// needed for buttons
+#include 
+#include  			// needed for loading map file
+#include 
+
+#include 
+#include 
+
+
+#include 
+#include 
+#include 
+#include 
+
+// needed for some VerticalMenu specific debugging							
+#include 
+#include 
+#include 
+
+
+extern volatile int	test_vblanks;
+
+
+class CCallStack
+{
+public:
+	void Append(CCallStack *p);
+	void Remove();
+	void InitHead();
+	int	used;
+	int	size;
+	CCallStack *pNext;
+	CCallStack *pPrev;
+	int	addr[STACKDEPTH];
+	uint32	flags;
+	Mem::Allocator::BlockHeader * pBlock;	// pointer to block that has this callstack
+
+};
+
+CCallStack		free_list;	// list of created objects
+CCallStack		used_list;	// list of created objects
+
+// init a node, so it can act as the head			 
+inline void CCallStack::InitHead()
+{
+	pPrev = this;
+	pNext = this;
+}
+	
+// append node p to this node (after it)									 
+inline void CCallStack::Append(CCallStack *p)
+{
+
+	p->pNext = this->pNext;
+	p->pPrev = this;
+	this->pNext = p;
+	p->pNext->pPrev = p;
+}
+
+// simply unlink it from the list			   
+inline void CCallStack::Remove()
+{
+	pPrev->pNext = pNext;
+	pNext->pPrev = pPrev;
+}
+
+
+						
+//CCallStack * CallStack_FirstFree;
+//CCallStack * CallStack_FirstUsed; 
+
+static int MemView_Active = 0;
+
+
+
+#define	MAX_CALLSTACK (8192 * 8)		// we got 8 mb, woo woo.
+
+
+static float step = 128.0f;
+
+
+//static char HexByte(char a)
+//{
+//	if (a >= '0' && a <='9')
+//	{
+//		return a-'0';
+//	}
+//	if (a >= 'A' && a <='F')
+//	{
+//		return 10 + a-'A';
+//	}
+//	if (a >= 'a' && a <='f')
+//	{
+//		return 10 + a-'a';
+//	}
+//
+//	// should really be an error, but just ignore it and return 0
+//	// as this is only used for parsing the map file	
+//	return 0;
+//	
+//	
+//}
+//
+//
+//static int doneonce = 0;
+
+
+char *MemView_GetFunctionName(int pc, int *p_size)
+{
+//
+//	if (!pc)
+//	{
+//		return "NULL";
+//	}
+//				   
+//	// given an address, return the name of the function
+//	// does this by intially loading and buuilding a list of
+//	// all the start points, and names, of all the functions
+//	// by loading the skate3.map
+//
+//	
+//	static int symbols = 0;
+//	
+//	if (!doneonce)
+//	{
+//	
+////		mdl.m_fd = sceOpen( "host:ctrl_out.dat", SCE_RDWR );
+/////		sceRead( mdl.m_fd, mdl.m_recorded_data, 72000 * sizeof( Inp::RecordedData ));
+////		sceClose( mdl.m_fd );
+//
+//
+//		#ifdef	__NOPT_CDROM__OLD
+//		int pFP= sceOpen("host:..\\build\\ngpsgnu\\skate4c.map", SCE_RDONLY);		
+//		#else
+//		int pFP= sceOpen("host:..\\build\\ngpsgnu\\skate4.map", SCE_RDONLY);		
+//		#endif
+//
+//		if (!pFP)
+//		{
+//			return "(skate4.map not loaded yet)";
+//		}
+//
+//
+//		doneonce = 1;
+//	
+//		// Open the qb file and load it into memory.
+//		//int FileSize = ((skyFile*)pFP)->SOF		
+////		int FileSize = File::GetFileSize(pFP);
+//		char *pQB= _map_file_start ;
+//		sceRead(pFP,pQB,4000000);
+//		sceClose(pFP);
+//		
+//		// Now the file is loaded, we need to extract all the functions
+//		// so, search for the text 
+//		
+//		char *p = strstr(pQB,"address order");
+//		int	 *d = (int*)_symbols_start; 
+//		while (*p!=0x0a) p++;		// skip to start of next line
+//		p++;						// skip over 0a
+//		while (*p)
+//		{
+//			p++;					// skip over the space
+//			// the next 8 characters are the address in upper case hex
+//			int addr = 0;
+//			for (int i=0;i<8;i++)
+//			{
+//				addr <<= 4;
+//				addr += HexByte(*p++);
+//			}
+//			p+= 2;			// skip two spaces
+//			
+//			// the next 8 characters are the size in upper case hex
+//			int size = 0;
+//			for (int i=0;i<8;i++)
+//			{
+//				size <<= 4;
+//				size += HexByte(*p++);
+//			}
+//			
+//			p+= 2;			// skip two spaces
+//			
+//
+//			// only store symbols of non-zero size
+//			// otherwise, we get confused by having things like _bss_size in there
+//			// as they are not addresses, they just look like them, being so big...
+//			if (size || (addr >(int) __text_objend))
+//			{
+//				*d++ = addr;		// store the address of the symbol
+//				*d++ = (int)p;			// store the start of the symbol name
+//				symbols++;		 	// one more symbol
+//			}
+//			
+//			// search for first space, or CF, and replace with a 0
+//			// that way we ignore the "unmangled" version of the function
+//			while (*p && /**p!=' ' &&*/ *p!=0x0a && *p!='(' && *p != 0x0d) p++;	
+//			*p++ = 0;
+//			
+//
+//			// skip to LF, and replace the 			
+//			while (*p && *p!=0x0a) p++;		// skip to start of next line
+//			p++;						// skip over 0a, will now be at the space on next line
+//		}
+//	}
+//
+//	
+//	int	 *s = (int*)_symbols_start; 
+//
+//// just serach the table
+//// (might want a binary search, but no real need for speed)
+//	for (int i=0;i pc)	 		// if this one is above the pc
+//		{
+//			*p_size = addr-s[-2];		// calculate the size of the function
+//			return (char*) (s[-1]);		// then the previous one is the function
+//		}
+//		s += 2;	
+//	}
+		 
+	return "UNKNOWN";
+	
+}
+
+// Modifed version of Jamie's unwind stack function
+// ignores the fp, and just goes directly off the sp
+// seems to work much better (and faster too)
+int DumpUnwindStack( int iMaxDepth, int *pDest )
+{
+//	uint32* ra;
+//	uint64* sp;											// frame pointer
+//	ra = ((uint32*)DumpUnwindStack)+64;				// fake point in function to unwind from (
+//														// after the sd ra,0(sp), but before getting it back
+////	asm ( "daddu %0, $29, $0" : "=r" (sp) );			// get current sp
+//	sp = (uint64*)OSGetStackPointer();
+//
+//	if (!pDest)
+//	{
+//		printf("\n");
+//	}
+//		
+//	int icd = iMaxDepth;	   							// depth counter
+//	uint32* last_ra = NULL;
+//	while ( icd-- )
+//	{
+//			/* scan instruction*/
+//		uint32* pc = ra;								// current pc, somewehre in middle of function
+//		uint32 count = 4096;							// enought to cover large functions	(16k)
+//		while ( count-- )
+//		{
+//			uint32 ins = *pc;		 					// get 32 bit instruction
+//			if (((ins >> 16) & 0x7fff) == 0x7fbf)		// sd ra,offset(sp)  (or sq, for .C files)
+//			{
+//				uint32 offset = *(short*)pc;			// get offset (bottom 16 bits)
+//				ra = (uint32*)(sp[offset>>3]);					// >>3 as it's at 64 bit word pointer
+//				break;
+//			}
+//			pc--;
+//		}
+//		while ( count--)
+//		{
+//			uint32 ins = *pc;		 					// get 32 bit instruction
+//			if ((ins >> 16) == 0x27bd)					// addiu sp,sp,offset
+//			{
+//				int offset = *(short*)pc;				// get offset (bottom 16 bits)
+//				if (offset & 0x8000)
+//				{
+//					offset |= 0xffff0000;
+//				}
+//				sp = (uint64*)( (int)(sp) - (offset));	   
+//				break; 	
+//			}			
+//			pc--;
+//		}
+//
+////		if (last_ra == ra)
+////		{
+////			icd++;			// one more please....
+////		}
+////		else
+//		{
+//			last_ra = ra;
+//			if (pDest)
+//			{
+//				*pDest++ = (int)ra;
+//				*pDest = 0;
+//			}
+//			else
+//			{
+//				int size;
+//	//			printf ("sp = %p, ra = %p %s\n",sp,ra,MemView_GetFunctionName((int)ra));
+//				printf ("%p: %s\n",ra,MemView_GetFunctionName((int)ra,&size));
+//			}
+//		}
+//	    
+//		// test to see if we have recursed up all the way...
+//		if (abs(int((int)ra - (int)&ENTRYPOINT)) < 1024
+//		   || (int)ra &3 
+//		   || (int)ra < 0x100000
+//		   || (int)ra > (int)_code_end		// and check it's not totally crazy....
+//		   )
+//		{
+//			return 0;
+//		}
+//	
+//	}
+//	return iMaxDepth - icd;
+	return 0;
+}
+				   
+				   
+//        mD_L2           = nBit( vD_L2 ),
+//        mD_R2           = nBit( vD_R2 ),
+//        mD_L1           = nBit( vD_L1 ),
+//        mD_R1           = nBit( vD_R1 ),
+//        mD_TRIANGLE     = nBit( vD_TRIANGLE ),
+//	      mD_CIRCLE       = nBit( vD_CIRCLE ),
+//        mD_X            = nBit( vD_X ),
+//        mD_SQUARE       = nBit( vD_SQUARE ),
+//        mD_SELECT       = nBit( vD_SELECT ),
+//        mD_L3           = nBit( vD_L3 ),
+//        mD_R3           = nBit( vD_R3 ),
+//        mD_START        = nBit( vD_START ),
+//        mD_UP           = nBit( vD_UP ),
+//        mD_RIGHT        = nBit( vD_RIGHT ),
+//        mD_DOWN         = nBit( vD_DOWN ),
+//        mD_LEFT         = nBit( vD_LEFT ),
+
+
+void MemViewToggle()
+{
+	MemView_Active ^=1;
+}
+
+
+	 
+
+void MemView_Alloc( void *v)
+{
+#ifdef	__LINKED_LIST_HEAP__
+
+
+#ifdef	__NOPT_CDROM__OLD
+   return;
+#endif 	
+
+#endif
+}
+
+void MemView_Free( void *v)
+{
+#ifdef	__LINKED_LIST_HEAP__
+	// Need to remove it from the used list
+	// and add it back to the full list
+	
+	Mem::Allocator::BlockHeader *p = (Mem::Allocator::BlockHeader *)v;						  
+						  
+#ifdef	__NOPT_CDROM__OLD
+//   return;
+#endif 	
+
+	CCallStack *c = (CCallStack*)p->mp_debug_data;	
+	
+	if (!c)
+	{
+		// no debug data, so probably a re-alloc
+		// should probably handle those later
+		return;
+	}
+
+	// we clear it, in case this header is re-used later 
+	// I'm not entirely sure how well this will work
+	p->mp_debug_data = NULL;	
+	c->Remove();
+	free_list.Append(c);
+	
+#endif	
+}
+
+
+Mem::Allocator::BlockHeader *MemView_FindBlock( int addr)
+{
+#ifdef	__LINKED_LIST_HEAP__
+
+	
+	Mem::Allocator::BlockHeader *pSmallestBlock	= NULL;
+	uint32 smallest_block_size = 100000000;
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	for (Mem::Heap* heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+	{
+		Mem::Allocator::BlockHeader *pBlock = (Mem::Allocator::BlockHeader *) heap->find_block((void*)addr);	
+		if (pBlock)
+		{
+			if (pBlock->mSize < smallest_block_size)
+			{
+				smallest_block_size = pBlock->mSize;
+				pSmallestBlock = pBlock;
+			}
+		}
+	}
+	return pSmallestBlock;
+#else
+	return NULL;
+#endif
+}
+
+char * MemView_GetClassName(CCallStack *c)
+{
+#ifdef	__LINKED_LIST_HEAP__
+	int *ra = (int*)(c->addr[4]);
+	if (!ra) return NULL;
+	int count = STACKDEPTH-4;
+	while (count--)
+	{
+		int instruction = *ra++;
+		if (instruction >> 24 == 0x0c)
+		{
+			int code = (instruction & 0xffffff)<<2;
+			int size;
+			char *p = MemView_GetFunctionName(code,&size); 
+			// to tell if this is class or not
+			// we see if the text is of the form  
+			//    classname::classname (teminated by a 0)
+			// as that indicates that it is a constructor
+			// dude... this is where we need a regular expression....
+			char *end = p;
+			while (*end) end++;	   						// scan to end
+			while (end[-1] != ':' && end > p)	end--;	// skip to char after the last :
+			char *other = strstr(p,end);				// find fist occurance of end of string
+			if (other != end)							// if different, then this is it!!
+			{
+				return MemView_GetFunctionName(code,&size);
+				break;
+			}
+		}
+	}
+#endif
+	return NULL;
+}
+
+
+void MemView_DumpBlockInfo(int cursor)
+{
+#ifdef	__LINKED_LIST_HEAP__
+	
+	Mem::Allocator::BlockHeader *pBlock = MemView_FindBlock(cursor);
+	if (!pBlock)
+	{
+		// should search free blocks here???
+	}
+	// find this in the allocators used list
+	// and say if it is free, or not	
+	if (pBlock == NULL)
+	{
+//		if (cursor > (int)__text_org && cursor < (int)__bss_objend)		// check to see if in code/data
+//		{
+//			
+//			if (cursor < (int)__data_org)
+//				printf("Code: ");
+//			else if (cursor < (int)__rodata_org)
+//				printf("Data: ");
+//			else if (cursor < (int)__bss_org)
+//				printf("RO-Data: ");
+//			else 
+//				printf("BSS: ");
+//			
+//		
+//			int size;
+//			char *p_name = MemView_GetFunctionName(cursor,&size);
+//			printf ( "%s, size %d\n",p_name,size);
+//		}
+//		else
+		{
+			printf ("Block Not Found\n");
+		}
+	}
+	else
+	{
+		void * p_start = (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+		printf ("Block found, addr = %p, size = %d (Header = %d)\n",p_start,pBlock->mSize,Mem::Allocator::BlockHeader::sSize);
+		CCallStack *c = (CCallStack*)pBlock->mp_debug_data;
+		if (!c)
+		{
+			//printf ("Block with No Debug Info!!\n");
+		}
+		else
+		{
+			// assume this is a "new", then the fourth callstack ra will point to the 
+			// jal xxxxxx  instruction, where xxxxx is the constructor for the 
+			// or it might be sortly thereafter, so check 16 instructions
+			
+			char * classname = MemView_GetClassName(c);
+						
+			if (classname)
+			{
+				printf ("CLASS: %s\n",classname);		
+			}
+
+			// then list out the call stack (skipping the MemView_Alloc, as that's a given, and irrelevant);			
+		
+			int *p = c->addr + 1;
+			while (p[1])			// also skip the ENTRYPOINT, just go back to main()
+			{
+				int size;
+				printf ("%p: %s\n",(void*)*p,MemView_GetFunctionName(*p,&size));
+				p++;
+			}
+		}
+	}
+#endif
+}
+
+static	int	blockstart;
+
+static float cursor;
+
+void 		MemView_Display()
+{
+
+//#ifdef	__NOPT_CDROM__OLD
+//   return;
+//#endif 	
+//
+//
+//	if (!MemView_Active)
+//	{
+//		return;
+//	}
+//
+//	FlushCache( 0 );
+//	sceGsSyncPath( 0, 0 );
+//	
+//	//perfrom the copying
+//	// there are 512x256 words in the rectangle
+//	// and 32768*1024 bytes in memory
+//	// giving us a step of 256 (i.e, sample every 256th bytes)
+//	
+//	
+//	// The start of the middle line will be at 
+//	//       start + 512 * 2 * 128 * step;
+//	// then  start1 + 512 * 2 * 128 * step1
+//	// for them to be the same, start + 512 * 2 * 128 * step = start1 + 512 * 2 * 128 * step1
+//	// so start1 =  start + 512 * 2 * 128 * (step - step1)
+//	
+//	
+//
+//	blockstart = 0;
+//	int blockend = 0;
+//	
+//	static float last_start;				 
+//	
+//	float start = cursor - (512.0f * 2.0f * 128.0f * step);
+//	
+//	int i_cursor = (int)cursor;
+//	
+//	Mem::Allocator::BlockHeader *pBlock = MemView_FindBlock(i_cursor);
+//
+//	if (pBlock)
+//	{
+//		blockstart = (int)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+//		int size = pBlock->mSize;
+//		blockend = (int)((int)blockstart + size);
+//	}
+//	
+//	if (start != last_start)
+//	{
+//		last_start = start;
+//		printf ("\nCursor Addr = %p\n",(void*)i_cursor);   	
+//		MemView_DumpBlockInfo(i_cursor);
+//	}
+//	
+//	
+//	static int color = 10 + (10<<5) ;
+////	color ^= 5 << 10;
+//	
+//	float f_source = start;
+//	float f_off = 0.0f;
+////	uint16 *source = (uint16*)(intstart&~1);   		// converting from a float to a pointer... yowza!!!
+//	uint16 *dest  =  (uint16*)_mem_dump_start;
+//	for (int i=0;i<512*256-4096;i++)
+//	{
+//		uint16 *source = (uint16*)((int)(f_source + f_off) &~1);		
+//	
+//		uint32 word;
+//		if (source < (uint16*)0x00100000 || source >= (uint16*)(0x08000000))
+//		{
+//			word = (3<<10)+(3<<5)+(3);			// grey for outside of memory		
+//		}
+//		else
+//		{
+//			if (blockstart && (int)(source)>=blockstart && (int)(source)  (uint32)_code_end /*&& x < (uint32)_std_mem_end*/) // don't check for end now, as we have some debug heaps up there we want to include
+//		{
+//			// check to see if it points to one of the heap members
+//			
+//			uint32 *p_refs = (uint32*)_mem_dump_start;
+//			
+//		#if 0
+//			for (int i=0;i x)
+//					{
+//						high = mid;
+//					}
+//					else
+//					{
+//						// if the low point is already the same as the mid point
+//						// then the only way to go is up!
+//						// as this will only occur when low + 1 == high
+//						if (low == mid)
+//						{
+//							low = high;
+//						}
+//						else
+//						{
+//							low = mid;
+//						}
+//					}				
+//				}
+//				x -= 16;
+//			}
+//			x = oldx;
+//		#endif						  		
+//		}
+//	}
+//	
+//	
+//}
+#endif
+
+#ifdef	__LINKED_LIST_HEAP__
+static uint32 *p_used;		
+#endif
+
+int MemView_CountBlocks(Mem::Allocator::BlockHeader *p_header)
+{
+#ifdef	__LINKED_LIST_HEAP__
+	int num_used = 0;
+	while ( p_header )
+	{
+		void * p_start = (void*)((uint)p_header + Mem::Allocator::BlockHeader::sSize);
+		
+		*p_used++ = (uint32)p_start;	 		// store the start of the block
+		*p_used++ = 0;							// store a count
+		p_header = p_header->mp_next_used;
+		num_used++;
+	}
+	return num_used;
+#else
+	return 0;
+#endif
+}
+
+
+int blockCompFunc( const void *arg1, const void *arg2 )
+{
+	uint32 addr1 = (*(uint32*)arg1);
+	uint32 addr2 = (*(uint32*)arg2);
+
+	if ( addr1 == addr2 )
+	{
+		return 0;
+	}
+	else if ( addr1 < addr2 )
+	{
+		return 1;
+	}
+	else
+	{
+		return -1;
+	}
+
+}
+
+
+// Find memory leaks
+// the algorithm is quite simple:
+// 1) Make a list of all "used" memory blocks, and set their usage count to 0
+// 2) Scan all of the heap, and the stack, for each word that looks like a pointer, 
+//    check to see if it is in the list of "used", and increment the usage count if so
+// 3) Scan the list of used pointers, and check for any with usage == 0
+
+// NEED OT EXTEND FOR TOP_DOWN heap.....		
+		
+void MemView_FindLeaks()
+{
+//#ifdef	__LINKED_LIST_HEAP__
+//		p_used  =  (uint32*)_mem_dump_start;		
+//		num_used = 0;
+//		printf ("Counting blocks....");		
+//		num_used += MemView_CountBlocks(Mem::Manager::sHandle().BottomUpHeap()->first_block()); 
+//		num_used += MemView_CountBlocks(Mem::Manager::sHandle().TopDownHeap()->first_block()); 
+//		num_used += MemView_CountBlocks(Mem::Manager::sHandle().FrontEndHeap()->first_block()); 
+//		num_used += MemView_CountBlocks(Mem::Manager::sHandle().NetworkHeap()->first_block()); 
+//		num_used += MemView_CountBlocks(Mem::Manager::sHandle().ScriptHeap()->first_block()); 
+//		num_used += MemView_CountBlocks(Mem::Manager::sHandle().SkaterHeap(0)->first_block()); 
+////		num_used += MemView_CountBlocks(Mem::Manager::sHandle().DebugHeap()->first_block()); 
+//		printf (" %d\n",num_used);
+//		printf ("Sorting .....\n");			
+//		// Now we've done that, let's sort the list, so we can use a binary search later
+//		
+//		
+//		#if 1
+//		uint32 *p_top  =  (uint32*)_mem_dump_start;		
+//		for	(int i = 0;i= 10)
+		{
+			printf ("Stopping after %d refs\n",count);
+			return;
+		}
+		if (p_start >= p_end)
+		{
+			printf ("No more References Found in heap \n");
+			return;
+		}
+	}
+#endif
+}
+
+// Find the first block in the free list
+// if no free blocks, then return
+// scan all used blocks, and print out the info for all the blocks
+// that have an address above the first free block
+void MemView_DumpFragments(Mem::Heap *pHeap)
+{
+	#ifdef	__LINKED_LIST_HEAP__    
+
+	if (!pHeap->mFreeBlocks.m_count)
+	{
+		printf ("NO Fragmentation\n");
+		return;
+	}
+	
+	if (!pHeap->mp_context->mp_free_list)
+	{
+		printf ("!!!!!! No free list, but there are %d free blocks???\n",pHeap->mFreeBlocks.m_count);
+		return;
+	}
+
+	Mem::Allocator::BlockHeader *p_free = pHeap->mp_context->mp_free_list;
+	
+	while (p_free->mSize < 10000)
+	{
+		Mem::Allocator::BlockHeader *p_next = p_free->mpNext;
+		if (!p_next)
+		{
+			printf ("Did not find a free block >10K ?????\n");
+		}		
+		p_free = p_next;
+	}
+	
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+	
+	printf ("!!!!!! Free list starts at %p\n",p_free);
+	
+
+	// The first p_free will be the start of fragmentations
+	while (p_full)
+	{
+		if (p_full > p_free)
+		{
+			//printf ("\nFramgented Block\n\n");
+			void * p_start = (void*)((uint)p_full + Mem::Allocator::BlockHeader::sSize);
+			MemView_DumpBlockInfo((int)p_start);
+			for (int xx=0;xx<1000000;xx++);		// little delay, to allow printfs to work
+		}
+		p_full = p_full->mp_next_used;
+	}
+	#endif
+}
+
+void MemView_DumpHeap(Mem::Heap *pHeap, uint32 mask)
+{
+	#ifdef	__LINKED_LIST_HEAP__    
+
+//	Mem::Allocator::BlockHeader *p_free = pHeap->mp_context->mp_free_list;
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+	
+	// The first p_free will be the start of fragmentations
+	while (p_full)
+	{
+//		if (p_full > p_free)
+//		CCallStack *c = (CCallStack*)p_full->mp_debug_data;	
+//		if (!mask || !c || !(c->flags && mask))
+		{
+			printf ("\n");
+			void * p_start = (void*)((uint)p_full + Mem::Allocator::BlockHeader::sSize);
+			MemView_DumpBlockInfo((int)p_start);
+		}
+		p_full = p_full->mp_next_used;
+	}
+	#endif
+}
+
+
+
+void MemView_DumpBottomFragments()
+{
+
+	MemView_DumpFragments(Mem::Manager::sHandle().BottomUpHeap());
+}
+
+void MemView_DumpTopFragments()
+{
+
+	MemView_DumpFragments(Mem::Manager::sHandle().TopDownHeap());
+}
+
+
+
+/*
+class CCallStack
+{
+public:
+	void Append(CCallStack *p);
+	void Remove();
+	void InitHead();
+	int	used;
+	int	size;
+	CCallStack *pNext;
+	CCallStack *pPrev;
+	int	addr[STACKDEPTH];
+
+};
+*/
+
+struct SBlockType
+{
+	int	return_addr; 			// first meaningful return addr 
+	
+	int	size;					// size of block (if we want to sort by it
+	int	total;					// total size of this type
+	int	actual;					// actual total size, including headers
+	char *p_class;				// points to class node 
+	
+	int	count;
+};
+
+// scan throught the list of "used" blocks
+// and sort them into a list, organized by "type"
+// the "type" is determined by the first return address after
+// a callstack entry that is either "Malloc" or "Spt::Class::operator new"
+// the "type" is furthur sorted by either "size" or "Class"
+// where "size" is the size of the block (for a Malloc)
+// and "Class" is the type of class that constructed this block 
+
+#define	MAX_TYPES 10000
+
+
+void MemView_DumpAnalysis( SBlockType* blocks, int numBlocksToPrint )
+{
+#ifdef	__LINKED_LIST_HEAP__
+	// Sorts the types, and print out totals
+
+	int temp;
+
+	for (int i = 0; i < numBlocksToPrint; i++)
+	{
+		for (int j = i+1;jsize;	  	// size is the only thing we know for sure
+	int	return_addr = 0;				// default unknown return address
+	char *p_class = "not a class";
+	int latest = 1;
+	int i = 0;
+	
+	for ( i = 1; i < 8; i++ )
+	{
+		int xsize;
+
+		/*
+		// the types of call stack we may encounter:
+		// need to 
+			0x10be48: Mem::Heap::allocate                                                   
+			0x109914: Mem::Manager::New                                                     
+			0x1035b0: Spt::Class::operator new                                              
+			0x161094: Front::KeyboardControl::sCreateInstance   
+
+			0x10be48: Mem::Heap::allocate                                                   
+			0x109914: Mem::Manager::New                                                     
+			0x10a150: Malloc                                                                
+			0x222df8: _SkyBuildPktForUpLoadAlignedContiguousRectangle  
+
+			0x10be48: Mem::Heap::allocate                                                   
+			0x109914: Mem::Manager::New                                                     
+			0x10a210: Malloc_FreeList                                                       
+			0x257034: _rwFreeListAllocReal
+		*/
+
+		char *p_name = MemView_GetFunctionName(pCallStack->addr[i],&xsize);
+		if (!strcmp("Malloc",p_name) 
+			|| !strcmp("Spt::Class::operator new",p_name)
+			|| !strcmp("Mem::Manager::New",p_name)
+			|| !strcmp("_rwFreeListAllocReal",p_name))
+		{
+			latest = i;
+		}
+	}
+
+	if (latest != 1)
+	{
+		return_addr = pCallStack->addr[latest+1];
+	}
+
+	p_class = MemView_GetClassName(pCallStack);				// get class
+	// right, now we have all the info on this block
+	// let's see if we've got one just like it
+
+	//		if (!p_class && !MemView_GetFunctionName(return_addr,&temp))
+	/*		
+			if (!return_addr)
+			{
+				for (int i = 0;i>%s<<\n",i,MemView_GetFunctionName(p->addr[i],&temp));
+
+				}
+				return;
+
+			}
+	*/
+	
+									  
+	// check if it is a string, and print it out, if so					
+/*	
+	int temp;				  
+	if (!strcmp("Str::String::copy",MemView_GetFunctionName(return_addr,&temp)))
+	{
+		printf ("String <%s>\n",(char*)((char*)(pCallStack->pBlock)+32)); 
+	}
+	
+	
+	if (!strcmp("Front::VerticalMenu::sCreateInstance",MemView_GetFunctionName(return_addr,&temp)))
+	{
+		void *p_start  =  (void*)((char*)(pCallStack->pBlock)+32);
+		printf ("\nVertical Menu "); 
+		
+		Front::ScreenElement *pV =  (Front::ScreenElement *)p_start;
+		printf (" id = %s\n", Script::FindChecksumName(pV->GetID()));
+//		MemView_DumpBlockInfo((int)p_start);
+		
+	}	
+*/	
+
+	// check to see if this block is already included
+	for ( i = 0; i < num; i++ )
+	{
+		if ( pBlocks[i].p_class == p_class
+			/*&& pBlocks[i].size == size */
+			 && pBlocks[i].return_addr == return_addr )
+		{
+			pBlocks[i].count++;
+			pBlocks[i].total += size;
+			pBlocks[i].actual += size + Mem::Allocator::BlockHeader::sSize;
+			break;				
+		}
+	}
+
+	// if not, then add the block
+	if ( i == num )
+	{
+		pBlocks[i].p_class = p_class;
+		pBlocks[i].size = size;
+		pBlocks[i].total = size;
+		pBlocks[i].actual = size + Mem::Allocator::BlockHeader::sSize;
+		pBlocks[i].return_addr = return_addr;
+		pBlocks[i].count = 1;
+		num++;
+	}
+}
+
+#ifdef	__LINKED_LIST_HEAP__
+//static int bbb = 0;	   	// compiler patch var, see below
+#endif
+
+void MemView_AnalyzeBlocks(uint32 mask)
+{
+//#ifdef	__LINKED_LIST_HEAP__
+//	SBlockType  *pBlocks = (SBlockType  *)_mem_dump_start;	// temp memory
+//	int	num_blocks = 0;
+//	int num = 0;
+//
+//	printf ("\nAnalyzing blocks....\n");
+//	
+//	CCallStack *p = used_list.pNext;  
+//	while (p != &used_list)
+//	{
+//		// Get the actualy block we referred to
+////		Mem::Allocator::BlockHeader * pBlock = p->pBlock;
+////		void * p_start = (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+//		// Otionally check to see if it on the front end heap
+////		if (Mem::SameContext(p_start,Mem::Manager::sHandle().FrontEndHeap()))
+//		{
+//			if (!mask || !(p->flags & mask))
+//			{
+//				MemView_AnalyzeCallStack( p, pBlocks, num );			
+//				num_blocks++;
+//			}
+//		}
+//		p = p->pNext; 
+//	}
+//	
+//	printf ("%d types, in %d total blocks\n", num, num_blocks); 
+//
+//	MemView_DumpAnalysis( pBlocks, num );
+//	if (bbb)
+//	{
+//		MemView_DumpBottomFragments();			// just to get it compiling 
+//		MemView_DumpTopFragments();			// just to get it compiling 
+//	}		
+//#endif
+}
+
+
+void MemView_MarkBlocks(uint32 mask)
+{
+#ifdef	__LINKED_LIST_HEAP__
+
+	CCallStack *p = used_list.pNext;  
+	while (p != &used_list)
+	{
+		p->flags |= mask;
+		
+		p = p->pNext;
+	}
+#endif
+}
+
+
+
+void MemView_Input(uint buttons, uint makes, uint breaks)
+{
+
+	if (Config::CD())
+	{
+		return;
+	}
+
+//	if (makes & Inp::Data::mD_TRIANGLE)
+//	{
+//		MemView_Active = !MemView_Active;
+//	}
+
+
+	if (!MemView_Active)
+	{
+		return;
+	}
+
+					  
+	float step1 = step;
+	
+	float zoom = 1.1f;
+	
+	float scroll = 4.0f;
+
+	
+
+
+
+	if (buttons & Inp::Data::mD_LEFT)
+	{
+		step1 = step * zoom;
+	}
+	if (buttons & Inp::Data::mD_RIGHT)
+	{
+		step1 = step / zoom;
+	}
+	
+	if (buttons & Inp::Data::mD_UP)
+	{
+//		start = start - scroll * 512.0f * 2.0f * step;
+		cursor = cursor - scroll * 512.0f * 2.0f * step;
+	}
+	if (buttons & Inp::Data::mD_DOWN)
+	{
+//		start = start + scroll * 512.0f * 2.0f * step;
+		cursor = cursor + scroll * 512.0f * 2.0f * step;
+	}
+
+	if (buttons & Inp::Data::mD_L1)
+	{
+//		start = start - scroll * 512.0f * 2.0f * step / 256.0f;
+		cursor = cursor - scroll * 2.0f * 2.0f * step;
+	}
+	if (buttons & Inp::Data::mD_L2)
+	{
+//		start = start + scroll * 512.0f * 2.0f * step / 256.0f;
+		cursor = cursor + scroll * 2.0f * 2.0f * step;
+	}
+
+#define 	MMMIN 	(0.0078125f)
+	 				
+	if (step1 1024.0f)
+	{
+		step1 = 1024.0f;
+	}
+
+//	start = start + (512.0f * 2.0f * 128.0f * (step - step1));
+
+	step = step1;
+
+	if (makes & Inp::Data::mD_CIRCLE)
+	{
+		if (blockstart)
+		{
+			MemView_DumpRefs(blockstart);
+		}
+//		MemView_MarkBlocks(1);
+	}
+
+	// We don't look for leaks automatically now, so I'v put it on "SQUARE"	
+	if (makes & Inp::Data::mD_SQUARE)
+	{
+		MemView_FindLeaks();
+//	Mem::Manager& mem_man = Mem::Manager::sHandle();		MemView_DumpHeap(1);
+//	heap	= mem_man.TopDownHeap();
+//	MemView_DumpFragments(heap);
+//	MemView_DumpHeap(heap,1);
+
+	}
+
+	if (makes & Inp::Data::mD_X)
+	{
+		MemView_AnalyzeBlocks();
+	}
+
+	// Triangle = Dump Fragmentation
+/*	if (makes & Inp::Data::mD_TRIANGLE)
+	{
+		Mem::Manager& mem_man = Mem::Manager::sHandle();
+		Mem::Heap* heap = mem_man.BottomUpHeap();
+		Mem::Region* region = heap->ParentRegion();
+		printf ("BottomUp Frag %dK, %d Blocks\n",heap->mFreeMem.m_count / 1024, heap->mFreeBlocks.m_count);
+		printf ("Region %d/%d K", region->MemAvailable() / 1024, region->TotalSize() / 1024 );
+		MemView_DumpFragments(heap);
+	}
+*/	
+
+}
+
+void MemView_AnalyzeHeap(Mem::Heap *pHeap)
+{
+	if ( !pHeap )
+		return;
+	
+//#ifdef	__LINKED_LIST_HEAP__
+//	SBlockType  *pBlocks = (SBlockType  *)_mem_dump_start;	// temp memory
+//	int	num_blocks = 0;
+//	int num = 0;
+//
+//	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+//
+//	while (p_full)
+//	{
+//		CCallStack* pCallStack = (CCallStack*)p_full->mp_debug_data;
+//
+//		if ( pCallStack )
+//		{
+//			MemView_AnalyzeCallStack( pCallStack, pBlocks, num );
+//		}
+//		else
+//		{
+//			printf ("Block with No Debug Info!!\n");
+//		}
+//
+//		p_full = p_full->mp_next_used;
+//	}
+//
+//	printf ("%d types, in %d total blocks\n", num, num_blocks); 
+//
+//	MemView_DumpAnalysis( pBlocks, num );
+//#endif
+}
+  
+  
+
+
diff --git a/Code/Gfx/NGC/p_memview.h b/Code/Gfx/NGC/p_memview.h
new file mode 100644
index 0000000..bf6201d
--- /dev/null
+++ b/Code/Gfx/NGC/p_memview.h
@@ -0,0 +1,19 @@
+////
+
+#ifndef	__P_MEMVIEW_H__
+#define	__P_MEMVIEW_H__
+
+void MemView_Display();
+void MemView_Input(uint buttons, uint makes, uint breaks);
+void MemView_Alloc( void *v);
+void MemView_Free( void *v);
+void MemViewToggle();
+void MemView_FindLeaks();
+int DumpUnwindStack( int iMaxDepth, int *pDest );
+char *MemView_GetFunctionName(int pc, int *p_size);
+void MemView_DumpFragments(Mem::Heap *pHeap);
+void MemView_AnalyzeBlocks(uint32 mask = 0);
+void MemView_MarkBlocks(uint32 flags = 1 );
+
+#endif
+
diff --git a/Code/Gfx/NGC/p_nx.cpp b/Code/Gfx/NGC/p_nx.cpp
new file mode 100644
index 0000000..c81fbe1
--- /dev/null
+++ b/Code/Gfx/NGC/p_nx.cpp
@@ -0,0 +1,4734 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:						 		 									**
+**																			**
+**	File name:		gfx/ngc/p_nx.cpp										**
+**																			**
+**	Created:		01/16/2002	-	dc										**
+**																			**
+**	Description:	ngc platform specific interface to the engine			**
+**					This is ngc SPECIFIC!!!!!! If there is anything in		**
+**					here that is not ngc specific, then it needs to be		**
+**					in nx.cpp												**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include	
+
+#include 
+#include 
+
+#include	"gfx\camera.h"
+#include	"gfx\nx.h"
+#include	"gfx\nxquickanim.h"
+#include	"gfx\nxviewman.h"
+#include	"gfx\ngc\p_NxGeom.h"
+#include	"gfx\NGc\p_NxMesh.h"
+#include	"gfx\NxParticleMgr.h"
+#include	"gfx\NxWeather.h"
+#include	"gfx\ngc\p_nxWeather.h"
+#include	"gfx\NxMiscFX.h"
+#include	"gfx\NGc\p_NxModel.h"
+#include	"gfx\ngc\p_NxSector.h"
+#include	"gfx\ngc\p_NxScene.h"
+#include	"gfx\ngc\p_NxSprite.h"
+#include	"gfx\ngc\p_NxTexture.h"
+#include	"gfx\ngc\p_NxTextured3dPoly.h"
+#include	"gfx\ngc\p_NxNewParticleMgr.h"
+#include	"core\math.h"
+#include 	"sk\engine\SuperSector.h"					
+#include 	"gel\scripting\script.h"
+#include	"gfx\debuggfx.h"
+
+#include 	"gfx\ngc\nx\nx_init.h"
+#include 	"gfx\ngc\nx\import.h"
+#include 	"gfx\ngc\nx\material.h"
+#include 	"gfx\ngc\nx\mesh.h"
+#include 	"gfx\ngc\nx\render.h"
+#include 	"gfx\ngc\nx\occlude.h"
+#include 	"gfx\ngc\nx\scene.h"
+#include 	"gfx\ngc\nx\chars.h"
+#include	"gfx\ngc\nx\nx_init.h"
+
+#include	
+#include	
+#include	
+#include	
+#include	
+#include	
+
+#include	"sys/ngc/p_gx.h"
+
+extern bool gCorrectColor;
+
+static bool s_correctable = true;
+
+int gOffy = 0;
+
+int g_object = 0;
+int g_view_object = 0;
+NxNgc::sScene * g_view_scene = NULL;
+int g_material = -1;
+int g_scroll_material = 0;
+
+int g_mip = 0;
+int g_passes = -1;
+
+extern unsigned char uv_col_map[][2];
+
+extern u8 g_blur;
+u8 sBlur = 0;
+extern bool		gLoadingScreenActive;
+
+extern PADStatus padData[PAD_MAX_CONTROLLERS]; // game pad state
+
+extern int g_dl;
+
+extern bool g_in_cutscene;
+
+#define COLOR_MAP_SIZE 64
+	
+//u16 colorMap[(COLOR_MAP_SIZE*COLOR_MAP_SIZE)*2] __attribute__ (( aligned( 32 )));
+//bool color_map_created = false;
+
+//extern NxNgc::sMaterialHeader *	p_u_mat[2048];
+//extern int					u_mat_count[2048];
+//extern int					num_u_mat;
+//bool gPrintMatStats = false;
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if 0
+static u8 sample_pattern[12][2] =
+{
+//	{1,1},{3,3},{5,5},
+//	{11,1},{9,3},{7,5},
+//	{5,7},{3,9},{1,11},
+//	{7,7},{9,9},{11,11}
+	{3,2},{9,2},{3,10},
+	{3,2},{9,2},{9,10},
+	{3,2},{3,10},{9,10},
+	{9,2},{3,10},{9,10},
+};
+#endif
+
+extern int meshes_considered;
+
+#ifndef __NOPT_FINAL__
+bool gDumpHeap = false;
+#endif		// __NOPT_FINAL__
+
+#define SHADOW_TEXTURE_SIZE 256
+//#define BLUR_TEXTURE_SIZE 64
+//#define BLUR_BORDER 2
+//
+//#define BLUR_1 ( ( (float)BLUR_TEXTURE_SIZE - (float)BLUR_BORDER ) / (float)BLUR_TEXTURE_SIZE )
+//#define BLUR_0 ( (float)BLUR_BORDER / (float)BLUR_TEXTURE_SIZE )
+
+uint8 * shadowTextureData = NULL;
+//uint8 * volumeTextureData = NULL;
+//uint8 * zTextureDataH = NULL;
+//uint8 * zTextureDataL = NULL;
+//uint8 * screenTextureData = NULL;
+//uint8 * focusTextureData = NULL;
+//uint8 * blurTextureData = NULL;
+
+//int gFocus = 1;
+//
+// Set global palette for shadow & blur.
+
+uint16 shadowPalette[16] __attribute__ (( aligned( 32 ))) = {
+	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0x00 ),		// 0
+	GXPackedRGB5A3( 0x11, 0x11, 0x11, 0xff ),       // 1
+	GXPackedRGB5A3( 0x22, 0x22, 0x22, 0xff ),       // 2
+	GXPackedRGB5A3( 0x33, 0x33, 0x33, 0xff ),       // 3
+	GXPackedRGB5A3( 0x44, 0x44, 0x44, 0xff ),       // 4
+	GXPackedRGB5A3( 0x55, 0x55, 0x55, 0xff ),       // 5
+	GXPackedRGB5A3( 0x66, 0x66, 0x66, 0xff ),       // 6
+	GXPackedRGB5A3( 0x77, 0x77, 0x77, 0xff ),       // 7
+	GXPackedRGB5A3( 0x88, 0x88, 0x88, 0xff ),       // 8
+	GXPackedRGB5A3( 0x99, 0x99, 0x99, 0xff ),       // 9
+	GXPackedRGB5A3( 0xaa, 0xaa, 0xaa, 0xff ),       // 10
+	GXPackedRGB5A3( 0xbb, 0xbb, 0xbb, 0xff ),       // 11
+	GXPackedRGB5A3( 0xcc, 0xcc, 0xcc, 0xff ),       // 12
+	GXPackedRGB5A3( 0xdd, 0xdd, 0xdd, 0xff ),       // 13
+	GXPackedRGB5A3( 0xee, 0xee, 0xee, 0xff ),       // 14
+	GXPackedRGB5A3( 0xff, 0xff, 0xff, 0xff ),       // 15
+};
+
+//uint16 shadowPalette[16] __attribute__ (( aligned( 32 ))) = {
+//	GXPackedRGB5A3( 0xff, 0xff, 0xff, 0xff ),       // 15
+//	GXPackedRGB5A3( 0xee, 0xee, 0xee, 0xff ),       // 14
+//	GXPackedRGB5A3( 0xdd, 0xdd, 0xdd, 0xff ),       // 13
+//	GXPackedRGB5A3( 0xcc, 0xcc, 0xcc, 0xff ),       // 12
+//	GXPackedRGB5A3( 0xbb, 0xbb, 0xbb, 0xff ),       // 11
+//	GXPackedRGB5A3( 0xaa, 0xaa, 0xaa, 0xff ),       // 10
+//	GXPackedRGB5A3( 0x99, 0x99, 0x99, 0xff ),       // 9
+//	GXPackedRGB5A3( 0x88, 0x88, 0x88, 0xff ),       // 8
+//	GXPackedRGB5A3( 0x77, 0x77, 0x77, 0xff ),       // 7
+//	GXPackedRGB5A3( 0x66, 0x66, 0x66, 0xff ),       // 6
+//	GXPackedRGB5A3( 0x55, 0x55, 0x55, 0xff ),       // 5
+//	GXPackedRGB5A3( 0x44, 0x44, 0x44, 0xff ),       // 4
+//	GXPackedRGB5A3( 0x33, 0x33, 0x33, 0xff ),       // 3
+//	GXPackedRGB5A3( 0x22, 0x22, 0x22, 0xff ),       // 2
+//	GXPackedRGB5A3( 0x11, 0x11, 0x11, 0xff ),       // 1
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0x00 ),		// 0
+//};
+
+//uint16 zPalette[256] __attribute__ (( aligned( 32 ))) = {
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),		// 0x00
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0x01
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0x02
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0x03
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0x04
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0x05
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x06
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x07
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x08
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x09
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x0a
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x0b
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x0c
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x0d
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x0e
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x0f
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),		// 0x10
+//	GXPackedRGB5A3( 0x11, 0x11, 0x11, 0xff ),       // 0x11
+//	GXPackedRGB5A3( 0x12, 0x12, 0x12, 0xff ),       // 0x12
+//	GXPackedRGB5A3( 0x13, 0x13, 0x13, 0xff ),       // 0x13
+//	GXPackedRGB5A3( 0x14, 0x14, 0x14, 0xff ),       // 0x14
+//	GXPackedRGB5A3( 0x15, 0x15, 0x15, 0xff ),       // 0x15
+//	GXPackedRGB5A3( 0x16, 0x16, 0x16, 0xff ),       // 0x16
+//	GXPackedRGB5A3( 0x17, 0x17, 0x17, 0xff ),       // 0x17
+//	GXPackedRGB5A3( 0x18, 0x18, 0x18, 0xff ),       // 0x18
+//	GXPackedRGB5A3( 0x19, 0x19, 0x19, 0xff ),       // 0x19
+//	GXPackedRGB5A3( 0x1a, 0x1a, 0x1a, 0xff ),       // 0x1a
+//	GXPackedRGB5A3( 0x1b, 0x1b, 0x1b, 0xff ),       // 0x1b
+//	GXPackedRGB5A3( 0x1c, 0x1c, 0x1c, 0xff ),       // 0x1c
+//	GXPackedRGB5A3( 0x1d, 0x1d, 0x1d, 0xff ),       // 0x1d
+//	GXPackedRGB5A3( 0x1e, 0x1e, 0x1e, 0xff ),       // 0x1e
+//	GXPackedRGB5A3( 0x1f, 0x1f, 0x1f, 0xff ),       // 0x1f
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),		// 0x20
+//	GXPackedRGB5A3( 0x21, 0x21, 0x21, 0xff ),       // 0x21
+//	GXPackedRGB5A3( 0x22, 0x22, 0x22, 0xff ),       // 0x22
+//	GXPackedRGB5A3( 0x23, 0x23, 0x23, 0xff ),       // 0x23
+//	GXPackedRGB5A3( 0x24, 0x24, 0x24, 0xff ),       // 0x24
+//	GXPackedRGB5A3( 0x25, 0x25, 0x25, 0xff ),       // 0x25
+//	GXPackedRGB5A3( 0x26, 0x26, 0x26, 0xff ),       // 0x26
+//	GXPackedRGB5A3( 0x27, 0x27, 0x27, 0xff ),       // 0x27
+//	GXPackedRGB5A3( 0x28, 0x28, 0x28, 0xff ),       // 0x28
+//	GXPackedRGB5A3( 0x29, 0x29, 0x29, 0xff ),       // 0x29
+//	GXPackedRGB5A3( 0x2a, 0x2a, 0x2a, 0xff ),       // 0x2a
+//	GXPackedRGB5A3( 0x2b, 0x2b, 0x2b, 0xff ),       // 0x2b
+//	GXPackedRGB5A3( 0x2c, 0x2c, 0x2c, 0xff ),       // 0x2c
+//	GXPackedRGB5A3( 0x2d, 0x2d, 0x2d, 0xff ),       // 0x2d
+//	GXPackedRGB5A3( 0x2e, 0x2e, 0x2e, 0xff ),       // 0x2e
+//	GXPackedRGB5A3( 0x2f, 0x2f, 0x2f, 0xff ),       // 0x2f
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),		// 0x30
+//	GXPackedRGB5A3( 0x31, 0x31, 0x31, 0xff ),       // 0x31
+//	GXPackedRGB5A3( 0x32, 0x32, 0x32, 0xff ),       // 0x32
+//	GXPackedRGB5A3( 0x33, 0x33, 0x33, 0xff ),       // 0x33
+//	GXPackedRGB5A3( 0x34, 0x34, 0x34, 0xff ),       // 0x34
+//	GXPackedRGB5A3( 0x35, 0x35, 0x35, 0xff ),       // 0x35
+//	GXPackedRGB5A3( 0x36, 0x36, 0x36, 0xff ),       // 0x36
+//	GXPackedRGB5A3( 0x37, 0x37, 0x37, 0xff ),       // 0x37
+//	GXPackedRGB5A3( 0x38, 0x38, 0x38, 0xff ),       // 0x38
+//	GXPackedRGB5A3( 0x39, 0x39, 0x39, 0xff ),       // 0x39
+//	GXPackedRGB5A3( 0x3a, 0x3a, 0x3a, 0xff ),       // 0x3a
+//	GXPackedRGB5A3( 0x3b, 0x3b, 0x3b, 0xff ),       // 0x3b
+//	GXPackedRGB5A3( 0x3c, 0x3c, 0x3c, 0xff ),       // 0x3c
+//	GXPackedRGB5A3( 0x3d, 0x3d, 0x3d, 0xff ),       // 0x3d
+//	GXPackedRGB5A3( 0x3e, 0x3e, 0x3e, 0xff ),       // 0x3e
+//	GXPackedRGB5A3( 0x3f, 0x3f, 0x3f, 0xff ),       // 0x3f
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),		// 0x40
+//	GXPackedRGB5A3( 0x41, 0x41, 0x41, 0xff ),       // 0x41
+//	GXPackedRGB5A3( 0x42, 0x42, 0x42, 0xff ),       // 0x42
+//	GXPackedRGB5A3( 0x43, 0x43, 0x43, 0xff ),       // 0x43
+//	GXPackedRGB5A3( 0x44, 0x44, 0x44, 0xff ),       // 0x44
+//	GXPackedRGB5A3( 0x45, 0x45, 0x45, 0xff ),       // 0x45
+//	GXPackedRGB5A3( 0x46, 0x46, 0x46, 0xff ),       // 0x46
+//	GXPackedRGB5A3( 0x47, 0x47, 0x47, 0xff ),       // 0x47
+//	GXPackedRGB5A3( 0x48, 0x48, 0x48, 0xff ),       // 0x48
+//	GXPackedRGB5A3( 0x49, 0x49, 0x49, 0xff ),       // 0x49
+//	GXPackedRGB5A3( 0x4a, 0x4a, 0x4a, 0xff ),       // 0x4a
+//	GXPackedRGB5A3( 0x4b, 0x4b, 0x4b, 0xff ),       // 0x4b
+//	GXPackedRGB5A3( 0x4c, 0x4c, 0x4c, 0xff ),       // 0x4c
+//	GXPackedRGB5A3( 0x4d, 0x4d, 0x4d, 0xff ),       // 0x4d
+//	GXPackedRGB5A3( 0x4e, 0x4e, 0x4e, 0xff ),       // 0x4e
+//	GXPackedRGB5A3( 0x4f, 0x4f, 0x4f, 0xff ),       // 0x4f
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),		// 0x50
+//	GXPackedRGB5A3( 0x51, 0x51, 0x51, 0xff ),       // 0x51
+//	GXPackedRGB5A3( 0x52, 0x52, 0x52, 0xff ),       // 0x52
+//	GXPackedRGB5A3( 0x53, 0x53, 0x53, 0xff ),       // 0x53
+//	GXPackedRGB5A3( 0x54, 0x54, 0x54, 0xff ),       // 0x54
+//	GXPackedRGB5A3( 0x55, 0x55, 0x55, 0xff ),       // 0x55
+//	GXPackedRGB5A3( 0x56, 0x56, 0x56, 0xff ),       // 0x56
+//	GXPackedRGB5A3( 0x57, 0x57, 0x57, 0xff ),       // 0x57
+//	GXPackedRGB5A3( 0x58, 0x58, 0x58, 0xff ),       // 0x58
+//	GXPackedRGB5A3( 0x59, 0x59, 0x59, 0xff ),       // 0x59
+//	GXPackedRGB5A3( 0x5a, 0x5a, 0x5a, 0xff ),       // 0x5a
+//	GXPackedRGB5A3( 0x5b, 0x5b, 0x5b, 0xff ),       // 0x5b
+//	GXPackedRGB5A3( 0x5c, 0x5c, 0x5c, 0xff ),       // 0x5c
+//	GXPackedRGB5A3( 0x5d, 0x5d, 0x5d, 0xff ),       // 0x5d
+//	GXPackedRGB5A3( 0x5e, 0x5e, 0x5e, 0xff ),       // 0x5e
+//	GXPackedRGB5A3( 0x5f, 0x5f, 0x5f, 0xff ),       // 0x5f
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),		// 0x60
+//	GXPackedRGB5A3( 0x61, 0x61, 0x61, 0xff ),       // 0x61
+//	GXPackedRGB5A3( 0x62, 0x62, 0x62, 0xff ),       // 0x62
+//	GXPackedRGB5A3( 0x63, 0x63, 0x63, 0xff ),       // 0x63
+//	GXPackedRGB5A3( 0x64, 0x64, 0x64, 0xff ),       // 0x64
+//	GXPackedRGB5A3( 0x65, 0x65, 0x65, 0xff ),       // 0x65
+//	GXPackedRGB5A3( 0x66, 0x66, 0x66, 0xff ),       // 0x66
+//	GXPackedRGB5A3( 0x67, 0x67, 0x67, 0xff ),       // 0x67
+//	GXPackedRGB5A3( 0x68, 0x68, 0x68, 0xff ),       // 0x68
+//	GXPackedRGB5A3( 0x69, 0x69, 0x69, 0xff ),       // 0x69
+//	GXPackedRGB5A3( 0x6a, 0x6a, 0x6a, 0xff ),       // 0x6a
+//	GXPackedRGB5A3( 0x6b, 0x6b, 0x6b, 0xff ),       // 0x6b
+//	GXPackedRGB5A3( 0x6c, 0x6c, 0x6c, 0xff ),       // 0x6c
+//	GXPackedRGB5A3( 0x6d, 0x6d, 0x6d, 0xff ),       // 0x6d
+//	GXPackedRGB5A3( 0x6e, 0x6e, 0x6e, 0xff ),       // 0x6e
+//	GXPackedRGB5A3( 0x6f, 0x6f, 0x6f, 0xff ),       // 0x6f
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),		// 0x70
+//	GXPackedRGB5A3( 0x71, 0x71, 0x71, 0xff ),       // 0x71
+//	GXPackedRGB5A3( 0x72, 0x72, 0x72, 0xff ),       // 0x72
+//	GXPackedRGB5A3( 0x73, 0x73, 0x73, 0xff ),       // 0x73
+//	GXPackedRGB5A3( 0x74, 0x74, 0x74, 0xff ),       // 0x74
+//	GXPackedRGB5A3( 0x75, 0x75, 0x75, 0xff ),       // 0x75
+//	GXPackedRGB5A3( 0x76, 0x76, 0x76, 0xff ),       // 0x76
+//	GXPackedRGB5A3( 0x77, 0x77, 0x77, 0xff ),       // 0x77
+//	GXPackedRGB5A3( 0x78, 0x78, 0x78, 0xff ),       // 0x78
+//	GXPackedRGB5A3( 0x79, 0x79, 0x79, 0xff ),       // 0x79
+//	GXPackedRGB5A3( 0x7a, 0x7a, 0x7a, 0xff ),       // 0x7a
+//	GXPackedRGB5A3( 0x7b, 0x7b, 0x7b, 0xff ),       // 0x7b
+//	GXPackedRGB5A3( 0x7c, 0x7c, 0x7c, 0xff ),       // 0x7c
+//	GXPackedRGB5A3( 0x7d, 0x7d, 0x7d, 0xff ),       // 0x7d
+//	GXPackedRGB5A3( 0x7e, 0x7e, 0x7e, 0xff ),       // 0x7e
+//	GXPackedRGB5A3( 0x7f, 0x7f, 0x7f, 0xff ),       // 0x7f
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),		// 0x80
+//	GXPackedRGB5A3( 0x81, 0x81, 0x81, 0xff ),       // 0x81
+//	GXPackedRGB5A3( 0x82, 0x82, 0x82, 0xff ),       // 0x82
+//	GXPackedRGB5A3( 0x83, 0x83, 0x83, 0xff ),       // 0x83
+//	GXPackedRGB5A3( 0x84, 0x84, 0x84, 0xff ),       // 0x84
+//	GXPackedRGB5A3( 0x85, 0x85, 0x85, 0xff ),       // 0x85
+//	GXPackedRGB5A3( 0x86, 0x86, 0x86, 0xff ),       // 0x86
+//	GXPackedRGB5A3( 0x87, 0x87, 0x87, 0xff ),       // 0x87
+//	GXPackedRGB5A3( 0x88, 0x88, 0x88, 0xff ),       // 0x88
+//	GXPackedRGB5A3( 0x89, 0x89, 0x89, 0xff ),       // 0x89
+//	GXPackedRGB5A3( 0x8a, 0x8a, 0x8a, 0xff ),       // 0x8a
+//	GXPackedRGB5A3( 0x8b, 0x8b, 0x8b, 0xff ),       // 0x8b
+//	GXPackedRGB5A3( 0x8c, 0x8c, 0x8c, 0xff ),       // 0x8c
+//	GXPackedRGB5A3( 0x8d, 0x8d, 0x8d, 0xff ),       // 0x8d
+//	GXPackedRGB5A3( 0x8e, 0x8e, 0x8e, 0xff ),       // 0x8e
+//	GXPackedRGB5A3( 0x8f, 0x8f, 0x8f, 0xff ),       // 0x8f
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),		// 0x90
+//	GXPackedRGB5A3( 0x91, 0x91, 0x91, 0xff ),       // 0x91
+//	GXPackedRGB5A3( 0x92, 0x92, 0x92, 0xff ),       // 0x92
+//	GXPackedRGB5A3( 0x93, 0x93, 0x93, 0xff ),       // 0x93
+//	GXPackedRGB5A3( 0x94, 0x94, 0x94, 0xff ),       // 0x94
+//	GXPackedRGB5A3( 0x95, 0x95, 0x95, 0xff ),       // 0x95
+//	GXPackedRGB5A3( 0x96, 0x96, 0x96, 0xff ),       // 0x96
+//	GXPackedRGB5A3( 0x97, 0x97, 0x97, 0xff ),       // 0x97
+//	GXPackedRGB5A3( 0x98, 0x98, 0x98, 0xff ),       // 0x98
+//	GXPackedRGB5A3( 0x99, 0x99, 0x99, 0xff ),       // 0x99
+//	GXPackedRGB5A3( 0x9a, 0x9a, 0x9a, 0xff ),       // 0x9a
+//	GXPackedRGB5A3( 0x9b, 0x9b, 0x9b, 0xff ),       // 0x9b
+//	GXPackedRGB5A3( 0x9c, 0x9c, 0x9c, 0xff ),       // 0x9c
+//	GXPackedRGB5A3( 0x9d, 0x9d, 0x9d, 0xff ),       // 0x9d
+//	GXPackedRGB5A3( 0x9e, 0x9e, 0x9e, 0xff ),       // 0x9e
+//	GXPackedRGB5A3( 0x9f, 0x9f, 0x9f, 0xff ),       // 0x9f
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),		// 0xa0
+//	GXPackedRGB5A3( 0xa1, 0xa1, 0xa1, 0xff ),       // 0xa1
+//	GXPackedRGB5A3( 0xa2, 0xa2, 0xa2, 0xff ),       // 0xa2
+//	GXPackedRGB5A3( 0xa3, 0xa3, 0xa3, 0xff ),       // 0xa3
+//	GXPackedRGB5A3( 0xa4, 0xa4, 0xa4, 0xff ),       // 0xa4
+//	GXPackedRGB5A3( 0xa5, 0xa5, 0xa5, 0xff ),       // 0xa5
+//	GXPackedRGB5A3( 0xa6, 0xa6, 0xa6, 0xff ),       // 0xa6
+//	GXPackedRGB5A3( 0xa7, 0xa7, 0xa7, 0xff ),       // 0xa7
+//	GXPackedRGB5A3( 0xa8, 0xa8, 0xa8, 0xff ),       // 0xa8
+//	GXPackedRGB5A3( 0xa9, 0xa9, 0xa9, 0xff ),       // 0xa9
+//	GXPackedRGB5A3( 0xaa, 0xaa, 0xaa, 0xff ),       // 0xaa
+//	GXPackedRGB5A3( 0xab, 0xab, 0xab, 0xff ),       // 0xab
+//	GXPackedRGB5A3( 0xac, 0xac, 0xac, 0xff ),       // 0xac
+//	GXPackedRGB5A3( 0xad, 0xad, 0xad, 0xff ),       // 0xad
+//	GXPackedRGB5A3( 0xae, 0xae, 0xae, 0xff ),       // 0xae
+//	GXPackedRGB5A3( 0xaf, 0xaf, 0xaf, 0xff ),       // 0xaf
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),		// 0xb0
+//	GXPackedRGB5A3( 0xb1, 0xb1, 0xb1, 0xff ),       // 0xb1
+//	GXPackedRGB5A3( 0xb2, 0xb2, 0xb2, 0xff ),       // 0xb2
+//	GXPackedRGB5A3( 0xb3, 0xb3, 0xb3, 0xff ),       // 0xb3
+//	GXPackedRGB5A3( 0xb4, 0xb4, 0xb4, 0xff ),       // 0xb4
+//	GXPackedRGB5A3( 0xb5, 0xb5, 0xb5, 0xff ),       // 0xb5
+//	GXPackedRGB5A3( 0xb6, 0xb6, 0xb6, 0xff ),       // 0xb6
+//	GXPackedRGB5A3( 0xb7, 0xb7, 0xb7, 0xff ),       // 0xb7
+//	GXPackedRGB5A3( 0xb8, 0xb8, 0xb8, 0xff ),       // 0xb8
+//	GXPackedRGB5A3( 0xb9, 0xb9, 0xb9, 0xff ),       // 0xb9
+//	GXPackedRGB5A3( 0xba, 0xba, 0xba, 0xff ),       // 0xba
+//	GXPackedRGB5A3( 0xbb, 0xbb, 0xbb, 0xff ),       // 0xbb
+//	GXPackedRGB5A3( 0xbc, 0xbc, 0xbc, 0xff ),       // 0xbc
+//	GXPackedRGB5A3( 0xbd, 0xbd, 0xbd, 0xff ),       // 0xbd
+//	GXPackedRGB5A3( 0xbe, 0xbe, 0xbe, 0xff ),       // 0xbe
+//	GXPackedRGB5A3( 0xbf, 0xbf, 0xbf, 0xff ),       // 0xbf
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),		// 0xc0
+//	GXPackedRGB5A3( 0xc1, 0xc1, 0xc1, 0xff ),       // 0xc1
+//	GXPackedRGB5A3( 0xc2, 0xc2, 0xc2, 0xff ),       // 0xc2
+//	GXPackedRGB5A3( 0xc3, 0xc3, 0xc3, 0xff ),       // 0xc3
+//	GXPackedRGB5A3( 0xc4, 0xc4, 0xc4, 0xff ),       // 0xc4
+//	GXPackedRGB5A3( 0xc5, 0xc5, 0xc5, 0xff ),       // 0xc5
+//	GXPackedRGB5A3( 0xc6, 0xc6, 0xc6, 0xff ),       // 0xc6
+//	GXPackedRGB5A3( 0xc7, 0xc7, 0xc7, 0xff ),       // 0xc7
+//	GXPackedRGB5A3( 0xc8, 0xc8, 0xc8, 0xff ),       // 0xc8
+//	GXPackedRGB5A3( 0xc9, 0xc9, 0xc9, 0xff ),       // 0xc9
+//	GXPackedRGB5A3( 0xca, 0xca, 0xca, 0xff ),       // 0xca
+//	GXPackedRGB5A3( 0xcb, 0xcb, 0xcb, 0xff ),       // 0xcb
+//	GXPackedRGB5A3( 0xcc, 0xcc, 0xcc, 0xff ),       // 0xcc
+//	GXPackedRGB5A3( 0xcd, 0xcd, 0xcd, 0xff ),       // 0xcd
+//	GXPackedRGB5A3( 0xce, 0xce, 0xce, 0xff ),       // 0xce
+//	GXPackedRGB5A3( 0xcf, 0xcf, 0xcf, 0xff ),       // 0xcf
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),		// 0xd0
+//	GXPackedRGB5A3( 0xd1, 0xd1, 0xd1, 0xff ),       // 0xd1
+//	GXPackedRGB5A3( 0xd2, 0xd2, 0xd2, 0xff ),       // 0xd2
+//	GXPackedRGB5A3( 0xd3, 0xd3, 0xd3, 0xff ),       // 0xd3
+//	GXPackedRGB5A3( 0xd4, 0xd4, 0xd4, 0xff ),       // 0xd4
+//	GXPackedRGB5A3( 0xd5, 0xd5, 0xd5, 0xff ),       // 0xd5
+//	GXPackedRGB5A3( 0xd6, 0xd6, 0xd6, 0xff ),       // 0xd6
+//	GXPackedRGB5A3( 0xd7, 0xd7, 0xd7, 0xff ),       // 0xd7
+//	GXPackedRGB5A3( 0xd8, 0xd8, 0xd8, 0xff ),       // 0xd8
+//	GXPackedRGB5A3( 0xd9, 0xd9, 0xd9, 0xff ),       // 0xd9
+//	GXPackedRGB5A3( 0xda, 0xda, 0xda, 0xff ),       // 0xda
+//	GXPackedRGB5A3( 0xdb, 0xdb, 0xdb, 0xff ),       // 0xdb
+//	GXPackedRGB5A3( 0xdc, 0xdc, 0xdc, 0xff ),       // 0xdc
+//	GXPackedRGB5A3( 0xdd, 0xdd, 0xdd, 0xff ),       // 0xdd
+//	GXPackedRGB5A3( 0xde, 0xde, 0xde, 0xff ),       // 0xde
+//	GXPackedRGB5A3( 0xdf, 0xdf, 0xdf, 0xff ),       // 0xdf
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),		// 0xe0
+//	GXPackedRGB5A3( 0xe1, 0xe1, 0xe1, 0xff ),       // 0xe1
+//	GXPackedRGB5A3( 0xe2, 0xe2, 0xe2, 0xff ),       // 0xe2
+//	GXPackedRGB5A3( 0xe3, 0xe3, 0xe3, 0xff ),       // 0xe3
+//	GXPackedRGB5A3( 0xe4, 0xe4, 0xe4, 0xff ),       // 0xe4
+//	GXPackedRGB5A3( 0xe5, 0xe5, 0xe5, 0xff ),       // 0xe5
+//	GXPackedRGB5A3( 0xe6, 0xe6, 0xe6, 0xff ),       // 0xe6
+//	GXPackedRGB5A3( 0xe7, 0xe7, 0xe7, 0xff ),       // 0xe7
+//	GXPackedRGB5A3( 0xe8, 0xe8, 0xe8, 0xff ),       // 0xe8
+//	GXPackedRGB5A3( 0xe9, 0xe9, 0xe9, 0xff ),       // 0xe9
+//	GXPackedRGB5A3( 0xea, 0xea, 0xea, 0xff ),       // 0xea
+//	GXPackedRGB5A3( 0xeb, 0xeb, 0xeb, 0xff ),       // 0xeb
+//	GXPackedRGB5A3( 0xec, 0xec, 0xec, 0xff ),       // 0xec
+//	GXPackedRGB5A3( 0xed, 0xed, 0xed, 0xff ),       // 0xed
+//	GXPackedRGB5A3( 0xee, 0xee, 0xee, 0xff ),       // 0xee
+//	GXPackedRGB5A3( 0xef, 0xef, 0xef, 0xff ),       // 0xef
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),		// 0xf0
+//	GXPackedRGB5A3( 0xf1, 0xf1, 0xf1, 0xff ),       // 0xf1
+//	GXPackedRGB5A3( 0xf2, 0xf2, 0xf2, 0xff ),       // 0xf2
+//	GXPackedRGB5A3( 0xf3, 0xf3, 0xf3, 0xff ),       // 0xf3
+//	GXPackedRGB5A3( 0xf4, 0xf4, 0xf4, 0xff ),       // 0xf4
+//	GXPackedRGB5A3( 0xf5, 0xf5, 0xf5, 0xff ),       // 0xf5
+//	GXPackedRGB5A3( 0xf6, 0xf6, 0xf6, 0xff ),       // 0xf6
+//	GXPackedRGB5A3( 0xf7, 0xf7, 0xf7, 0xff ),       // 0xf7
+//	GXPackedRGB5A3( 0xf8, 0xf8, 0xf8, 0xff ),       // 0xf8
+//	GXPackedRGB5A3( 0xf9, 0xf9, 0xf9, 0xff ),       // 0xf9
+//	GXPackedRGB5A3( 0xfa, 0xfa, 0xfa, 0xff ),       // 0xfa
+//	GXPackedRGB5A3( 0xfb, 0xfb, 0xfb, 0xff ),       // 0xfb
+//	GXPackedRGB5A3( 0xfc, 0xfc, 0xfc, 0xff ),       // 0xfc
+//	GXPackedRGB5A3( 0xfd, 0xfd, 0xfd, 0xff ),       // 0xfd
+//	GXPackedRGB5A3( 0xfe, 0xfe, 0xfe, 0xff ),       // 0xfe
+//	GXPackedRGB5A3( 0xff, 0xff, 0xff, 0xff ),       // 0xff
+//};
+
+//uint16 zPaletteH[256] __attribute__ (( aligned( 32 ))) = {
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),		// 0x00
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x01
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x02
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x03
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x04
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x05
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x06
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x07
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x08
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x09
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x0a
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x0b
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x0c
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x0d
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x0e
+//	GXPackedRGB5A3( 0xf0, 0xf0, 0xf0, 0xff ),       // 0x0f
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),		// 0x10
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x11
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x12
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x13
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x14
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x15
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x16
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x17
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x18
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x19
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x1a
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x1b
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x1c
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x1d
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x1e
+//	GXPackedRGB5A3( 0xe0, 0xe0, 0xe0, 0xff ),       // 0x1f
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),		// 0x20
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x21
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x22
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x23
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x24
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x25
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x26
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x27
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x28
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x29
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x2a
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x2b
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x2c
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x2d
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x2e
+//	GXPackedRGB5A3( 0xd0, 0xd0, 0xd0, 0xff ),       // 0x2f
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),		// 0x30
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x31
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x32
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x33
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x34
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x35
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x36
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x37
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x38
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x39
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x3a
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x3b
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x3c
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x3d
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x3e
+//	GXPackedRGB5A3( 0xc0, 0xc0, 0xc0, 0xff ),       // 0x3f
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),		// 0x40
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x41
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x42
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x43
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x44
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x45
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x46
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x47
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x48
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x49
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x4a
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x4b
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x4c
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x4d
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x4e
+//	GXPackedRGB5A3( 0xb0, 0xb0, 0xb0, 0xff ),       // 0x4f
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),		// 0x50
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x51
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x52
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x53
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x54
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x55
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x56
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x57
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x58
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x59
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x5a
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x5b
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x5c
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x5d
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x5e
+//	GXPackedRGB5A3( 0xa0, 0xa0, 0xa0, 0xff ),       // 0x5f
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),		// 0x60
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x61
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x62
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x63
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x64
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x65
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x66
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x67
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x68
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x69
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x6a
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x6b
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x6c
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x6d
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x6e
+//	GXPackedRGB5A3( 0x90, 0x90, 0x90, 0xff ),       // 0x6f
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),		// 0x70
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x71
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x72
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x73
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x74
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x75
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x76
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x77
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x78
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x79
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x7a
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x7b
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x7c
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x7d
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x7e
+//	GXPackedRGB5A3( 0x80, 0x80, 0x80, 0xff ),       // 0x7f
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),		// 0x80
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x81
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x82
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x83
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x84
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x85
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x86
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x87
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x88
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x89
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x8a
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x8b
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x8c
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x8d
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x8e
+//	GXPackedRGB5A3( 0x70, 0x70, 0x70, 0xff ),       // 0x8f
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),		// 0x90
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x91
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x92
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x93
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x94
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x95
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x96
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x97
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x98
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x99
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x9a
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x9b
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x9c
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x9d
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x9e
+//	GXPackedRGB5A3( 0x60, 0x60, 0x60, 0xff ),       // 0x9f
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),		// 0xa0
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa1
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa2
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa3
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa4
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa5
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa6
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa7
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa8
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xa9
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xaa
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xab
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xac
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xad
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xae
+//	GXPackedRGB5A3( 0x50, 0x50, 0x50, 0xff ),       // 0xaf
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),		// 0xb0
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb1
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb2
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb3
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb4
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb5
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb6
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb7
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb8
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xb9
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xba
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xbb
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xbc
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xbd
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xbe
+//	GXPackedRGB5A3( 0x40, 0x40, 0x40, 0xff ),       // 0xbf
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),		// 0xc0
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc1
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc2
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc3
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc4
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc5
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc6
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc7
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc8
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xc9
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xca
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xcb
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xcc
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xcd
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xce
+//	GXPackedRGB5A3( 0x30, 0x30, 0x30, 0xff ),       // 0xcf
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),		// 0xd0
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd1
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd2
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd3
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd4
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd5
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd6
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd7
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd8
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xd9
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xda
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xdb
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xdc
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xdd
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xde
+//	GXPackedRGB5A3( 0x20, 0x20, 0x20, 0xff ),       // 0xdf
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),		// 0xe0
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe1
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe2
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe3
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe4
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe5
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe6
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe7
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe8
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xe9
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xea
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xeb
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xec
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xed
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xee
+//	GXPackedRGB5A3( 0x10, 0x10, 0x10, 0xff ),       // 0xef
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),		// 0xf0
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf1
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf2
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf3
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf4
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf5
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf6
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf7
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf8
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf9
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfa
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfb
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfc
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfd
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfe
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xff
+//};
+//
+//uint16 zPaletteL[256] __attribute__ (( aligned( 32 ))) = {
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),		// 0x00
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x01
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x02
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x03
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x04
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x05
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x06
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x07
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x08
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x09
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x0a
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x0b
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x0c
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x0d
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x0e
+//	GXPackedRGB5A3( 0x0f, 0x0f, 0x0f, 0xff ),       // 0x0f
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),		// 0x10
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x11
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x12
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x13
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x14
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x15
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x16
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x17
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x18
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x19
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x1a
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x1b
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x1c
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x1d
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x1e
+//	GXPackedRGB5A3( 0x0e, 0x0e, 0x0e, 0xff ),       // 0x1f
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),		// 0x20
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x21
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x22
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x23
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x24
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x25
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x26
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x27
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x28
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x29
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x2a
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x2b
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x2c
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x2d
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x2e
+//	GXPackedRGB5A3( 0x0d, 0x0d, 0x0d, 0xff ),       // 0x2f
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),		// 0x30
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x31
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x32
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x33
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x34
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x35
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x36
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x37
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x38
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x39
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x3a
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x3b
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x3c
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x3d
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x3e
+//	GXPackedRGB5A3( 0x0c, 0x0c, 0x0c, 0xff ),       // 0x3f
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),		// 0x40
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x41
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x42
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x43
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x44
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x45
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x46
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x47
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x48
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x49
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x4a
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x4b
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x4c
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x4d
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x4e
+//	GXPackedRGB5A3( 0x0b, 0x0b, 0x0b, 0xff ),       // 0x4f
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),		// 0x50
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x51
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x52
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x53
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x54
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x55
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x56
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x57
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x58
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x59
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x5a
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x5b
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x5c
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x5d
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x5e
+//	GXPackedRGB5A3( 0x0a, 0x0a, 0x0a, 0xff ),       // 0x5f
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),		// 0x60
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x61
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x62
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x63
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x64
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x65
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x66
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x67
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x68
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x69
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x6a
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x6b
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x6c
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x6d
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x6e
+//	GXPackedRGB5A3( 0x09, 0x09, 0x09, 0xff ),       // 0x6f
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),		// 0x70
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x71
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x72
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x73
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x74
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x75
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x76
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x77
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x78
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x79
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x7a
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x7b
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x7c
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x7d
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x7e
+//	GXPackedRGB5A3( 0x08, 0x08, 0x08, 0xff ),       // 0x7f
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),		// 0x80
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x81
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x82
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x83
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x84
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x85
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x86
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x87
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x88
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x89
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x8a
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x8b
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x8c
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x8d
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x8e
+//	GXPackedRGB5A3( 0x07, 0x07, 0x07, 0xff ),       // 0x8f
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),		// 0x90
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x91
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x92
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x93
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x94
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x95
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x96
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x97
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x98
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x99
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x9a
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x9b
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x9c
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x9d
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x9e
+//	GXPackedRGB5A3( 0x06, 0x06, 0x06, 0xff ),       // 0x9f
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),		// 0xa0
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa1
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa2
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa3
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa4
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa5
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa6
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa7
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa8
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xa9
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xaa
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xab
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xac
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xad
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xae
+//	GXPackedRGB5A3( 0x05, 0x05, 0x05, 0xff ),       // 0xaf
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),		// 0xb0
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb1
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb2
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb3
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb4
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb5
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb6
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb7
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb8
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xb9
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xba
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xbb
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xbc
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xbd
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xbe
+//	GXPackedRGB5A3( 0x04, 0x04, 0x04, 0xff ),       // 0xbf
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),		// 0xc0
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc1
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc2
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc3
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc4
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc5
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc6
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc7
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc8
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xc9
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xca
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xcb
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xcc
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xcd
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xce
+//	GXPackedRGB5A3( 0x03, 0x03, 0x03, 0xff ),       // 0xcf
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),		// 0xd0
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd1
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd2
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd3
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd4
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd5
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd6
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd7
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd8
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xd9
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xda
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xdb
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xdc
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xdd
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xde
+//	GXPackedRGB5A3( 0x02, 0x02, 0x02, 0xff ),       // 0xdf
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),		// 0xe0
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe1
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe2
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe3
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe4
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe5
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe6
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe7
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe8
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xe9
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xea
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xeb
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xec
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xed
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xee
+//	GXPackedRGB5A3( 0x01, 0x01, 0x01, 0xff ),       // 0xef
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),		// 0xf0
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf1
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf2
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf3
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf4
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf5
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf6
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf7
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf8
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xf9
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfa
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfb
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfc
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfd
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xfe
+//	GXPackedRGB5A3( 0x00, 0x00, 0x00, 0xff ),       // 0xff
+//};
+//
+//uint8 zPalette8[256] __attribute__ (( aligned( 32 ))) = {
+//	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
+//	0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
+//	0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
+//	0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
+//	0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
+//	0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
+//	0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
+//	0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
+//	0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+//	0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+//	0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
+//	0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
+//	0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
+//	0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
+//	0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
+//	0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
+//};
+//
+//#define _H(a)  (((a<<(8+6))&0xc000)|((a<<6)&0x00c0))
+//
+//uint16 zPalette8H[256] __attribute__ (( aligned( 32 ))) = {
+//	_H(0x00),_H(0x01),_H(0x02),_H(0x03),_H(0x04),_H(0x05),_H(0x06),_H(0x07),_H(0x08),_H(0x09),_H(0x0a),_H(0x0b),_H(0x0c),_H(0x0d),_H(0x0e),_H(0x0f),
+//	_H(0x10),_H(0x11),_H(0x12),_H(0x13),_H(0x14),_H(0x15),_H(0x16),_H(0x17),_H(0x18),_H(0x19),_H(0x1a),_H(0x1b),_H(0x1c),_H(0x1d),_H(0x1e),_H(0x1f),
+//	_H(0x20),_H(0x21),_H(0x22),_H(0x23),_H(0x24),_H(0x25),_H(0x26),_H(0x27),_H(0x28),_H(0x29),_H(0x2a),_H(0x2b),_H(0x2c),_H(0x2d),_H(0x2e),_H(0x2f),
+//	_H(0x30),_H(0x31),_H(0x32),_H(0x33),_H(0x34),_H(0x35),_H(0x36),_H(0x37),_H(0x38),_H(0x39),_H(0x3a),_H(0x3b),_H(0x3c),_H(0x3d),_H(0x3e),_H(0x3f),
+//	_H(0x40),_H(0x41),_H(0x42),_H(0x43),_H(0x44),_H(0x45),_H(0x46),_H(0x47),_H(0x48),_H(0x49),_H(0x4a),_H(0x4b),_H(0x4c),_H(0x4d),_H(0x4e),_H(0x4f),
+//	_H(0x50),_H(0x51),_H(0x52),_H(0x53),_H(0x54),_H(0x55),_H(0x56),_H(0x57),_H(0x58),_H(0x59),_H(0x5a),_H(0x5b),_H(0x5c),_H(0x5d),_H(0x5e),_H(0x5f),
+//	_H(0x60),_H(0x61),_H(0x62),_H(0x63),_H(0x64),_H(0x65),_H(0x66),_H(0x67),_H(0x68),_H(0x69),_H(0x6a),_H(0x6b),_H(0x6c),_H(0x6d),_H(0x6e),_H(0x6f),
+//	_H(0x70),_H(0x71),_H(0x72),_H(0x73),_H(0x74),_H(0x75),_H(0x76),_H(0x77),_H(0x78),_H(0x79),_H(0x7a),_H(0x7b),_H(0x7c),_H(0x7d),_H(0x7e),_H(0x7f),
+//	_H(0x80),_H(0x81),_H(0x82),_H(0x83),_H(0x84),_H(0x85),_H(0x86),_H(0x87),_H(0x88),_H(0x89),_H(0x8a),_H(0x8b),_H(0x8c),_H(0x8d),_H(0x8e),_H(0x8f),
+//	_H(0x90),_H(0x91),_H(0x92),_H(0x93),_H(0x94),_H(0x95),_H(0x96),_H(0x97),_H(0x98),_H(0x99),_H(0x9a),_H(0x9b),_H(0x9c),_H(0x9d),_H(0x9e),_H(0x9f),
+//	_H(0xa0),_H(0xa1),_H(0xa2),_H(0xa3),_H(0xa4),_H(0xa5),_H(0xa6),_H(0xa7),_H(0xa8),_H(0xa9),_H(0xaa),_H(0xab),_H(0xac),_H(0xad),_H(0xae),_H(0xaf),
+//	_H(0xb0),_H(0xb1),_H(0xb2),_H(0xb3),_H(0xb4),_H(0xb5),_H(0xb6),_H(0xb7),_H(0xb8),_H(0xb9),_H(0xba),_H(0xbb),_H(0xbc),_H(0xbd),_H(0xbe),_H(0xbf),
+//	_H(0xc0),_H(0xc1),_H(0xc2),_H(0xc3),_H(0xc4),_H(0xc5),_H(0xc6),_H(0xc7),_H(0xc8),_H(0xc9),_H(0xca),_H(0xcb),_H(0xcc),_H(0xcd),_H(0xce),_H(0xcf),
+//	_H(0xd0),_H(0xd1),_H(0xd2),_H(0xd3),_H(0xd4),_H(0xd5),_H(0xd6),_H(0xd7),_H(0xd8),_H(0xd9),_H(0xda),_H(0xdb),_H(0xdc),_H(0xdd),_H(0xde),_H(0xdf),
+//	_H(0xe0),_H(0xe1),_H(0xe2),_H(0xe3),_H(0xe4),_H(0xe5),_H(0xe6),_H(0xe7),_H(0xe8),_H(0xe9),_H(0xea),_H(0xeb),_H(0xec),_H(0xed),_H(0xee),_H(0xef),
+//	_H(0xf0),_H(0xf1),_H(0xf2),_H(0xf3),_H(0xf4),_H(0xf5),_H(0xf6),_H(0xf7),_H(0xf8),_H(0xf9),_H(0xfa),_H(0xfb),_H(0xfc),_H(0xfd),_H(0xfe),_H(0xff)
+//};
+//
+//#define _L(a)  (((a<<(8-2))&0x3f00)|((a>>2)&0x003f))
+////#define _L(a)  ((a<<8)|a)
+//
+//uint16 zPalette8L[256] __attribute__ (( aligned( 32 ))) = {
+//	_L(0x00),_L(0x01),_L(0x02),_L(0x03),_L(0x04),_L(0x05),_L(0x06),_L(0x07),_L(0x08),_L(0x09),_L(0x0a),_L(0x0b),_L(0x0c),_L(0x0d),_L(0x0e),_L(0x0f),
+//	_L(0x10),_L(0x11),_L(0x12),_L(0x13),_L(0x14),_L(0x15),_L(0x16),_L(0x17),_L(0x18),_L(0x19),_L(0x1a),_L(0x1b),_L(0x1c),_L(0x1d),_L(0x1e),_L(0x1f),
+//	_L(0x20),_L(0x21),_L(0x22),_L(0x23),_L(0x24),_L(0x25),_L(0x26),_L(0x27),_L(0x28),_L(0x29),_L(0x2a),_L(0x2b),_L(0x2c),_L(0x2d),_L(0x2e),_L(0x2f),
+//	_L(0x30),_L(0x31),_L(0x32),_L(0x33),_L(0x34),_L(0x35),_L(0x36),_L(0x37),_L(0x38),_L(0x39),_L(0x3a),_L(0x3b),_L(0x3c),_L(0x3d),_L(0x3e),_L(0x3f),
+//	_L(0x40),_L(0x41),_L(0x42),_L(0x43),_L(0x44),_L(0x45),_L(0x46),_L(0x47),_L(0x48),_L(0x49),_L(0x4a),_L(0x4b),_L(0x4c),_L(0x4d),_L(0x4e),_L(0x4f),
+//	_L(0x50),_L(0x51),_L(0x52),_L(0x53),_L(0x54),_L(0x55),_L(0x56),_L(0x57),_L(0x58),_L(0x59),_L(0x5a),_L(0x5b),_L(0x5c),_L(0x5d),_L(0x5e),_L(0x5f),
+//	_L(0x60),_L(0x61),_L(0x62),_L(0x63),_L(0x64),_L(0x65),_L(0x66),_L(0x67),_L(0x68),_L(0x69),_L(0x6a),_L(0x6b),_L(0x6c),_L(0x6d),_L(0x6e),_L(0x6f),
+//	_L(0x70),_L(0x71),_L(0x72),_L(0x73),_L(0x74),_L(0x75),_L(0x76),_L(0x77),_L(0x78),_L(0x79),_L(0x7a),_L(0x7b),_L(0x7c),_L(0x7d),_L(0x7e),_L(0x7f),
+//	_L(0x80),_L(0x81),_L(0x82),_L(0x83),_L(0x84),_L(0x85),_L(0x86),_L(0x87),_L(0x88),_L(0x89),_L(0x8a),_L(0x8b),_L(0x8c),_L(0x8d),_L(0x8e),_L(0x8f),
+//	_L(0x90),_L(0x91),_L(0x92),_L(0x93),_L(0x94),_L(0x95),_L(0x96),_L(0x97),_L(0x98),_L(0x99),_L(0x9a),_L(0x9b),_L(0x9c),_L(0x9d),_L(0x9e),_L(0x9f),
+//	_L(0xa0),_L(0xa1),_L(0xa2),_L(0xa3),_L(0xa4),_L(0xa5),_L(0xa6),_L(0xa7),_L(0xa8),_L(0xa9),_L(0xaa),_L(0xab),_L(0xac),_L(0xad),_L(0xae),_L(0xaf),
+//	_L(0xb0),_L(0xb1),_L(0xb2),_L(0xb3),_L(0xb4),_L(0xb5),_L(0xb6),_L(0xb7),_L(0xb8),_L(0xb9),_L(0xba),_L(0xbb),_L(0xbc),_L(0xbd),_L(0xbe),_L(0xbf),
+//	_L(0xc0),_L(0xc1),_L(0xc2),_L(0xc3),_L(0xc4),_L(0xc5),_L(0xc6),_L(0xc7),_L(0xc8),_L(0xc9),_L(0xca),_L(0xcb),_L(0xcc),_L(0xcd),_L(0xce),_L(0xcf),
+//	_L(0xd0),_L(0xd1),_L(0xd2),_L(0xd3),_L(0xd4),_L(0xd5),_L(0xd6),_L(0xd7),_L(0xd8),_L(0xd9),_L(0xda),_L(0xdb),_L(0xdc),_L(0xdd),_L(0xde),_L(0xdf),
+//	_L(0xe0),_L(0xe1),_L(0xe2),_L(0xe3),_L(0xe4),_L(0xe5),_L(0xe6),_L(0xe7),_L(0xe8),_L(0xe9),_L(0xea),_L(0xeb),_L(0xec),_L(0xed),_L(0xee),_L(0xef),
+//	_L(0xf0),_L(0xf1),_L(0xf2),_L(0xf3),_L(0xf4),_L(0xf5),_L(0xf6),_L(0xf7),_L(0xf8),_L(0xf9),_L(0xfa),_L(0xfb),_L(0xfc),_L(0xfd),_L(0xfe),_L(0xff)
+//};
+//
+//#define _64(a)	((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),\
+//				((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),\
+//				((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),\
+//				((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),\
+//				((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),\
+//				((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),\
+//				((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),\
+//				((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a),((a<<8)|a)
+//
+//#define _I(a)	((a<<8)|a)
+//
+//uint16 zPalette16[65536] __attribute__ (( aligned( 32 ))) = {
+//
+//	_64(0x00),_64(0x01),_64(0x02),_64(0x03),_64(0x04),_64(0x05),_64(0x06),_64(0x07),_64(0x08),_64(0x09),_64(0x0a),_64(0x0b),_64(0x0c),_64(0x0d),_64(0x0e),_64(0x0f),
+//	_64(0x10),_64(0x11),_64(0x12),_64(0x13),_64(0x14),_64(0x15),_64(0x16),_64(0x17),_64(0x18),_64(0x19),_64(0x1a),_64(0x1b),_64(0x1c),_64(0x1d),_64(0x1e),_64(0x1f),
+//	_64(0x20),_64(0x21),_64(0x22),_64(0x23),_64(0x24),_64(0x25),_64(0x26),_64(0x27),_64(0x28),_64(0x29),_64(0x2a),_64(0x2b),_64(0x2c),_64(0x2d),_64(0x2e),_64(0x2f),
+//	_64(0x30),_64(0x31),_64(0x32),_64(0x33),_64(0x34),_64(0x35),_64(0x36),_64(0x37),_64(0x38),_64(0x39),_64(0x3a),_64(0x3b),_64(0x3c),_64(0x3d),_64(0x3e),_64(0x3f),
+//	_64(0x40),_64(0x41),_64(0x42),_64(0x43),_64(0x44),_64(0x45),_64(0x46),_64(0x47),_64(0x48),_64(0x49),_64(0x4a),_64(0x4b),_64(0x4c),_64(0x4d),_64(0x4e),_64(0x4f),
+//	_64(0x50),_64(0x51),_64(0x52),_64(0x53),_64(0x54),_64(0x55),_64(0x56),_64(0x57),_64(0x58),_64(0x59),_64(0x5a),_64(0x5b),_64(0x5c),_64(0x5d),_64(0x5e),_64(0x5f),
+//	_64(0x60),_64(0x61),_64(0x62),_64(0x63),_64(0x64),_64(0x65),_64(0x66),_64(0x67),_64(0x68),_64(0x69),_64(0x6a),_64(0x6b),_64(0x6c),_64(0x6d),_64(0x6e),_64(0x6f),
+//	_64(0x70),_64(0x71),_64(0x72),_64(0x73),_64(0x74),_64(0x75),_64(0x76),_64(0x77),_64(0x78),_64(0x79),_64(0x7a),_64(0x7b),_64(0x7c),_64(0x7d),_64(0x7e),_64(0x7f),
+//	_64(0x80),_64(0x81),_64(0x82),_64(0x83),_64(0x84),_64(0x85),_64(0x86),_64(0x87),_64(0x88),_64(0x89),_64(0x8a),_64(0x8b),_64(0x8c),_64(0x8d),_64(0x8e),_64(0x8f),
+//	_64(0x90),_64(0x91),_64(0x92),_64(0x93),_64(0x94),_64(0x95),_64(0x96),_64(0x97),_64(0x98),_64(0x99),_64(0x9a),_64(0x9b),_64(0x9c),_64(0x9d),_64(0x9e),_64(0x9f),
+//	_64(0xa0),_64(0xa1),_64(0xa2),_64(0xa3),_64(0xa4),_64(0xa5),_64(0xa6),_64(0xa7),_64(0xa8),_64(0xa9),_64(0xaa),_64(0xab),_64(0xac),_64(0xad),_64(0xae),_64(0xaf),
+//	_64(0xb0),_64(0xb1),_64(0xb2),_64(0xb3),_64(0xb4),_64(0xb5),_64(0xb6),_64(0xb7),_64(0xb8),_64(0xb9),_64(0xba),_64(0xbb),_64(0xbc),_64(0xbd),_64(0xbe),_64(0xbf),
+//	_64(0xc0),_64(0xc1),_64(0xc2),_64(0xc3),_64(0xc4),_64(0xc5),_64(0xc6),_64(0xc7),_64(0xc8),_64(0xc9),_64(0xca),_64(0xcb),_64(0xcc),_64(0xcd),_64(0xce),_64(0xcf),
+//	_64(0xd0),_64(0xd1),_64(0xd2),_64(0xd3),_64(0xd4),_64(0xd5),_64(0xd6),_64(0xd7),_64(0xd8),_64(0xd9),_64(0xda),_64(0xdb),_64(0xdc),_64(0xdd),_64(0xde),_64(0xdf),
+//	_64(0xe0),_64(0xe1),_64(0xe2),_64(0xe3),_64(0xe4),_64(0xe5),_64(0xe6),_64(0xe7),_64(0xe8),_64(0xe9),_64(0xea),_64(0xeb),_64(0xec),_64(0xed),_64(0xee),_64(0xef),
+//	_64(0xf0),_64(0xf1),_64(0xf2),_64(0xf3),_64(0xf4),_64(0xf5),_64(0xf6),_64(0xf7),_64(0xf8),_64(0xf9),_64(0xfa),_64(0xfb),_64(0xfc),_64(0xfd),_64(0xfe),_64(0xff),
+//
+//	_64(0x00),_64(0x01),_64(0x02),_64(0x03),_64(0x04),_64(0x05),_64(0x06),_64(0x07),_64(0x08),_64(0x09),_64(0x0a),_64(0x0b),_64(0x0c),_64(0x0d),_64(0x0e),_64(0x0f),
+//	_64(0x10),_64(0x11),_64(0x12),_64(0x13),_64(0x14),_64(0x15),_64(0x16),_64(0x17),_64(0x18),_64(0x19),_64(0x1a),_64(0x1b),_64(0x1c),_64(0x1d),_64(0x1e),_64(0x1f),
+//	_64(0x20),_64(0x21),_64(0x22),_64(0x23),_64(0x24),_64(0x25),_64(0x26),_64(0x27),_64(0x28),_64(0x29),_64(0x2a),_64(0x2b),_64(0x2c),_64(0x2d),_64(0x2e),_64(0x2f),
+//	_64(0x30),_64(0x31),_64(0x32),_64(0x33),_64(0x34),_64(0x35),_64(0x36),_64(0x37),_64(0x38),_64(0x39),_64(0x3a),_64(0x3b),_64(0x3c),_64(0x3d),_64(0x3e),_64(0x3f),
+//	_64(0x40),_64(0x41),_64(0x42),_64(0x43),_64(0x44),_64(0x45),_64(0x46),_64(0x47),_64(0x48),_64(0x49),_64(0x4a),_64(0x4b),_64(0x4c),_64(0x4d),_64(0x4e),_64(0x4f),
+//	_64(0x50),_64(0x51),_64(0x52),_64(0x53),_64(0x54),_64(0x55),_64(0x56),_64(0x57),_64(0x58),_64(0x59),_64(0x5a),_64(0x5b),_64(0x5c),_64(0x5d),_64(0x5e),_64(0x5f),
+//	_64(0x60),_64(0x61),_64(0x62),_64(0x63),_64(0x64),_64(0x65),_64(0x66),_64(0x67),_64(0x68),_64(0x69),_64(0x6a),_64(0x6b),_64(0x6c),_64(0x6d),_64(0x6e),_64(0x6f),
+//	_64(0x70),_64(0x71),_64(0x72),_64(0x73),_64(0x74),_64(0x75),_64(0x76),_64(0x77),_64(0x78),_64(0x79),_64(0x7a),_64(0x7b),_64(0x7c),_64(0x7d),_64(0x7e),_64(0x7f),
+//	_64(0x80),_64(0x81),_64(0x82),_64(0x83),_64(0x84),_64(0x85),_64(0x86),_64(0x87),_64(0x88),_64(0x89),_64(0x8a),_64(0x8b),_64(0x8c),_64(0x8d),_64(0x8e),_64(0x8f),
+//	_64(0x90),_64(0x91),_64(0x92),_64(0x93),_64(0x94),_64(0x95),_64(0x96),_64(0x97),_64(0x98),_64(0x99),_64(0x9a),_64(0x9b),_64(0x9c),_64(0x9d),_64(0x9e),_64(0x9f),
+//	_64(0xa0),_64(0xa1),_64(0xa2),_64(0xa3),_64(0xa4),_64(0xa5),_64(0xa6),_64(0xa7),_64(0xa8),_64(0xa9),_64(0xaa),_64(0xab),_64(0xac),_64(0xad),_64(0xae),_64(0xaf),
+//	_64(0xb0),_64(0xb1),_64(0xb2),_64(0xb3),_64(0xb4),_64(0xb5),_64(0xb6),_64(0xb7),_64(0xb8),_64(0xb9),_64(0xba),_64(0xbb),_64(0xbc),_64(0xbd),_64(0xbe),_64(0xbf),
+//	_64(0xc0),_64(0xc1),_64(0xc2),_64(0xc3),_64(0xc4),_64(0xc5),_64(0xc6),_64(0xc7),_64(0xc8),_64(0xc9),_64(0xca),_64(0xcb),_64(0xcc),_64(0xcd),_64(0xce),_64(0xcf),
+//	_64(0xd0),_64(0xd1),_64(0xd2),_64(0xd3),_64(0xd4),_64(0xd5),_64(0xd6),_64(0xd7),_64(0xd8),_64(0xd9),_64(0xda),_64(0xdb),_64(0xdc),_64(0xdd),_64(0xde),_64(0xdf),
+//	_64(0xe0),_64(0xe1),_64(0xe2),_64(0xe3),_64(0xe4),_64(0xe5),_64(0xe6),_64(0xe7),_64(0xe8),_64(0xe9),_64(0xea),_64(0xeb),_64(0xec),_64(0xed),_64(0xee),_64(0xef),
+//	_64(0xf0),_64(0xf1),_64(0xf2),_64(0xf3),_64(0xf4),_64(0xf5),_64(0xf6),_64(0xf7),_64(0xf8),_64(0xf9),_64(0xfa),_64(0xfb),_64(0xfc),_64(0xfd),_64(0xfe),_64(0xff),
+//
+//	_64(0x00),_64(0x01),_64(0x02),_64(0x03),_64(0x04),_64(0x05),_64(0x06),_64(0x07),_64(0x08),_64(0x09),_64(0x0a),_64(0x0b),_64(0x0c),_64(0x0d),_64(0x0e),_64(0x0f),
+//	_64(0x10),_64(0x11),_64(0x12),_64(0x13),_64(0x14),_64(0x15),_64(0x16),_64(0x17),_64(0x18),_64(0x19),_64(0x1a),_64(0x1b),_64(0x1c),_64(0x1d),_64(0x1e),_64(0x1f),
+//	_64(0x20),_64(0x21),_64(0x22),_64(0x23),_64(0x24),_64(0x25),_64(0x26),_64(0x27),_64(0x28),_64(0x29),_64(0x2a),_64(0x2b),_64(0x2c),_64(0x2d),_64(0x2e),_64(0x2f),
+//	_64(0x30),_64(0x31),_64(0x32),_64(0x33),_64(0x34),_64(0x35),_64(0x36),_64(0x37),_64(0x38),_64(0x39),_64(0x3a),_64(0x3b),_64(0x3c),_64(0x3d),_64(0x3e),_64(0x3f),
+//	_64(0x40),_64(0x41),_64(0x42),_64(0x43),_64(0x44),_64(0x45),_64(0x46),_64(0x47),_64(0x48),_64(0x49),_64(0x4a),_64(0x4b),_64(0x4c),_64(0x4d),_64(0x4e),_64(0x4f),
+//	_64(0x50),_64(0x51),_64(0x52),_64(0x53),_64(0x54),_64(0x55),_64(0x56),_64(0x57),_64(0x58),_64(0x59),_64(0x5a),_64(0x5b),_64(0x5c),_64(0x5d),_64(0x5e),_64(0x5f),
+//	_64(0x60),_64(0x61),_64(0x62),_64(0x63),_64(0x64),_64(0x65),_64(0x66),_64(0x67),_64(0x68),_64(0x69),_64(0x6a),_64(0x6b),_64(0x6c),_64(0x6d),_64(0x6e),_64(0x6f),
+//	_64(0x70),_64(0x71),_64(0x72),_64(0x73),_64(0x74),_64(0x75),_64(0x76),_64(0x77),_64(0x78),_64(0x79),_64(0x7a),_64(0x7b),_64(0x7c),_64(0x7d),_64(0x7e),_64(0x7f),
+//	_64(0x80),_64(0x81),_64(0x82),_64(0x83),_64(0x84),_64(0x85),_64(0x86),_64(0x87),_64(0x88),_64(0x89),_64(0x8a),_64(0x8b),_64(0x8c),_64(0x8d),_64(0x8e),_64(0x8f),
+//	_64(0x90),_64(0x91),_64(0x92),_64(0x93),_64(0x94),_64(0x95),_64(0x96),_64(0x97),_64(0x98),_64(0x99),_64(0x9a),_64(0x9b),_64(0x9c),_64(0x9d),_64(0x9e),_64(0x9f),
+//	_64(0xa0),_64(0xa1),_64(0xa2),_64(0xa3),_64(0xa4),_64(0xa5),_64(0xa6),_64(0xa7),_64(0xa8),_64(0xa9),_64(0xaa),_64(0xab),_64(0xac),_64(0xad),_64(0xae),_64(0xaf),
+//	_64(0xb0),_64(0xb1),_64(0xb2),_64(0xb3),_64(0xb4),_64(0xb5),_64(0xb6),_64(0xb7),_64(0xb8),_64(0xb9),_64(0xba),_64(0xbb),_64(0xbc),_64(0xbd),_64(0xbe),_64(0xbf),
+//	_64(0xc0),_64(0xc1),_64(0xc2),_64(0xc3),_64(0xc4),_64(0xc5),_64(0xc6),_64(0xc7),_64(0xc8),_64(0xc9),_64(0xca),_64(0xcb),_64(0xcc),_64(0xcd),_64(0xce),_64(0xcf),
+//	_64(0xd0),_64(0xd1),_64(0xd2),_64(0xd3),_64(0xd4),_64(0xd5),_64(0xd6),_64(0xd7),_64(0xd8),_64(0xd9),_64(0xda),_64(0xdb),_64(0xdc),_64(0xdd),_64(0xde),_64(0xdf),
+//	_64(0xe0),_64(0xe1),_64(0xe2),_64(0xe3),_64(0xe4),_64(0xe5),_64(0xe6),_64(0xe7),_64(0xe8),_64(0xe9),_64(0xea),_64(0xeb),_64(0xec),_64(0xed),_64(0xee),_64(0xef),
+//	_64(0xf0),_64(0xf1),_64(0xf2),_64(0xf3),_64(0xf4),_64(0xf5),_64(0xf6),_64(0xf7),_64(0xf8),_64(0xf9),_64(0xfa),_64(0xfb),_64(0xfc),_64(0xfd),_64(0xfe),_64(0xff),
+//
+//	_64(0x00),_64(0x01),_64(0x02),_64(0x03),_64(0x04),_64(0x05),_64(0x06),_64(0x07),_64(0x08),_64(0x09),_64(0x0a),_64(0x0b),_64(0x0c),_64(0x0d),_64(0x0e),_64(0x0f),
+//	_64(0x10),_64(0x11),_64(0x12),_64(0x13),_64(0x14),_64(0x15),_64(0x16),_64(0x17),_64(0x18),_64(0x19),_64(0x1a),_64(0x1b),_64(0x1c),_64(0x1d),_64(0x1e),_64(0x1f),
+//	_64(0x20),_64(0x21),_64(0x22),_64(0x23),_64(0x24),_64(0x25),_64(0x26),_64(0x27),_64(0x28),_64(0x29),_64(0x2a),_64(0x2b),_64(0x2c),_64(0x2d),_64(0x2e),_64(0x2f),
+//	_64(0x30),_64(0x31),_64(0x32),_64(0x33),_64(0x34),_64(0x35),_64(0x36),_64(0x37),_64(0x38),_64(0x39),_64(0x3a),_64(0x3b),_64(0x3c),_64(0x3d),_64(0x3e),_64(0x3f),
+//	_64(0x40),_64(0x41),_64(0x42),_64(0x43),_64(0x44),_64(0x45),_64(0x46),_64(0x47),_64(0x48),_64(0x49),_64(0x4a),_64(0x4b),_64(0x4c),_64(0x4d),_64(0x4e),_64(0x4f),
+//	_64(0x50),_64(0x51),_64(0x52),_64(0x53),_64(0x54),_64(0x55),_64(0x56),_64(0x57),_64(0x58),_64(0x59),_64(0x5a),_64(0x5b),_64(0x5c),_64(0x5d),_64(0x5e),_64(0x5f),
+//	_64(0x60),_64(0x61),_64(0x62),_64(0x63),_64(0x64),_64(0x65),_64(0x66),_64(0x67),_64(0x68),_64(0x69),_64(0x6a),_64(0x6b),_64(0x6c),_64(0x6d),_64(0x6e),_64(0x6f),
+//	_64(0x70),_64(0x71),_64(0x72),_64(0x73),_64(0x74),_64(0x75),_64(0x76),_64(0x77),_64(0x78),_64(0x79),_64(0x7a),_64(0x7b),_64(0x7c),_64(0x7d),_64(0x7e),_64(0x7f),
+//	_64(0x80),_64(0x81),_64(0x82),_64(0x83),_64(0x84),_64(0x85),_64(0x86),_64(0x87),_64(0x88),_64(0x89),_64(0x8a),_64(0x8b),_64(0x8c),_64(0x8d),_64(0x8e),_64(0x8f),
+//	_64(0x90),_64(0x91),_64(0x92),_64(0x93),_64(0x94),_64(0x95),_64(0x96),_64(0x97),_64(0x98),_64(0x99),_64(0x9a),_64(0x9b),_64(0x9c),_64(0x9d),_64(0x9e),_64(0x9f),
+//	_64(0xa0),_64(0xa1),_64(0xa2),_64(0xa3),_64(0xa4),_64(0xa5),_64(0xa6),_64(0xa7),_64(0xa8),_64(0xa9),_64(0xaa),_64(0xab),_64(0xac),_64(0xad),_64(0xae),_64(0xaf),
+//	_64(0xb0),_64(0xb1),_64(0xb2),_64(0xb3),_64(0xb4),_64(0xb5),_64(0xb6),_64(0xb7),_64(0xb8),_64(0xb9),_64(0xba),_64(0xbb),_64(0xbc),_64(0xbd),_64(0xbe),_64(0xbf),
+//	_64(0xc0),_64(0xc1),_64(0xc2),_64(0xc3),_64(0xc4),_64(0xc5),_64(0xc6),_64(0xc7),_64(0xc8),_64(0xc9),_64(0xca),_64(0xcb),_64(0xcc),_64(0xcd),_64(0xce),_64(0xcf),
+//	_64(0xd0),_64(0xd1),_64(0xd2),_64(0xd3),_64(0xd4),_64(0xd5),_64(0xd6),_64(0xd7),_64(0xd8),_64(0xd9),_64(0xda),_64(0xdb),_64(0xdc),_64(0xdd),_64(0xde),_64(0xdf),
+//	_64(0xe0),_64(0xe1),_64(0xe2),_64(0xe3),_64(0xe4),_64(0xe5),_64(0xe6),_64(0xe7),_64(0xe8),_64(0xe9),_64(0xea),_64(0xeb),_64(0xec),_64(0xed),_64(0xee),_64(0xef),
+//	_64(0xf0),_64(0xf1),_64(0xf2),_64(0xf3),_64(0xf4),_64(0xf5),_64(0xf6),_64(0xf7),_64(0xf8),_64(0xf9),_64(0xfa),_64(0xfb),/*_64(0xfc),_64(0xfd),_64(0xfe),_64(0xff)*/
+//
+//	_I(0x00),
+//	_I(0x01),
+//	_I(0x02),
+//	_I(0x03),
+//	_I(0x04),
+//	_I(0x05),
+//	_I(0x06),
+//	_I(0x07),
+//	_I(0x08),
+//	_I(0x09),
+//	_I(0x0a),
+//	_I(0x0b),
+//	_I(0x0c),
+//	_I(0x0d),
+//	_I(0x0e),
+//	_I(0x0f),
+//	_I(0x10),
+//	_I(0x11),
+//	_I(0x12),
+//	_I(0x13),
+//	_I(0x14),
+//	_I(0x15),
+//	_I(0x16),
+//	_I(0x17),
+//	_I(0x18),
+//	_I(0x19),
+//	_I(0x1a),
+//	_I(0x1b),
+//	_I(0x1c),
+//	_I(0x1d),
+//	_I(0x1e),
+//	_I(0x1f),
+//	_I(0x20),
+//	_I(0x21),
+//	_I(0x22),
+//	_I(0x23),
+//	_I(0x24),
+//	_I(0x25),
+//	_I(0x26),
+//	_I(0x27),
+//	_I(0x28),
+//	_I(0x29),
+//	_I(0x2a),
+//	_I(0x2b),
+//	_I(0x2c),
+//	_I(0x2d),
+//	_I(0x2e),
+//	_I(0x2f),
+//	_I(0x30),
+//	_I(0x31),
+//	_I(0x32),
+//	_I(0x33),
+//	_I(0x34),
+//	_I(0x35),
+//	_I(0x36),
+//	_I(0x37),
+//	_I(0x38),
+//	_I(0x39),
+//	_I(0x3a),
+//	_I(0x3b),
+//	_I(0x3c),
+//	_I(0x3d),
+//	_I(0x3e),
+//	_I(0x3f),
+//	_I(0x40),
+//	_I(0x41),
+//	_I(0x42),
+//	_I(0x43),
+//	_I(0x44),
+//	_I(0x45),
+//	_I(0x46),
+//	_I(0x47),
+//	_I(0x48),
+//	_I(0x49),
+//	_I(0x4a),
+//	_I(0x4b),
+//	_I(0x4c),
+//	_I(0x4d),
+//	_I(0x4e),
+//	_I(0x4f),
+//	_I(0x50),
+//	_I(0x51),
+//	_I(0x52),
+//	_I(0x53),
+//	_I(0x54),
+//	_I(0x55),
+//	_I(0x56),
+//	_I(0x57),
+//	_I(0x58),
+//	_I(0x59),
+//	_I(0x5a),
+//	_I(0x5b),
+//	_I(0x5c),
+//	_I(0x5d),
+//	_I(0x5e),
+//	_I(0x5f),
+//	_I(0x60),
+//	_I(0x61),
+//	_I(0x62),
+//	_I(0x63),
+//	_I(0x64),
+//	_I(0x65),
+//	_I(0x66),
+//	_I(0x67),
+//	_I(0x68),
+//	_I(0x69),
+//	_I(0x6a),
+//	_I(0x6b),
+//	_I(0x6c),
+//	_I(0x6d),
+//	_I(0x6e),
+//	_I(0x6f),
+//	_I(0x70),
+//	_I(0x71),
+//	_I(0x72),
+//	_I(0x73),
+//	_I(0x74),
+//	_I(0x75),
+//	_I(0x76),
+//	_I(0x77),
+//	_I(0x78),
+//	_I(0x79),
+//	_I(0x7a),
+//	_I(0x7b),
+//	_I(0x7c),
+//	_I(0x7d),
+//	_I(0x7e),
+//	_I(0x7f),
+//	_I(0x80),
+//	_I(0x81),
+//	_I(0x82),
+//	_I(0x83),
+//	_I(0x84),
+//	_I(0x85),
+//	_I(0x86),
+//	_I(0x87),
+//	_I(0x88),
+//	_I(0x89),
+//	_I(0x8a),
+//	_I(0x8b),
+//	_I(0x8c),
+//	_I(0x8d),
+//	_I(0x8e),
+//	_I(0x8f),
+//	_I(0x90),
+//	_I(0x91),
+//	_I(0x92),
+//	_I(0x93),
+//	_I(0x94),
+//	_I(0x95),
+//	_I(0x96),
+//	_I(0x97),
+//	_I(0x98),
+//	_I(0x99),
+//	_I(0x9a),
+//	_I(0x9b),
+//	_I(0x9c),
+//	_I(0x9d),
+//	_I(0x9e),
+//	_I(0x9f),
+//	_I(0xa0),
+//	_I(0xa1),
+//	_I(0xa2),
+//	_I(0xa3),
+//	_I(0xa4),
+//	_I(0xa5),
+//	_I(0xa6),
+//	_I(0xa7),
+//	_I(0xa8),
+//	_I(0xa9),
+//	_I(0xaa),
+//	_I(0xab),
+//	_I(0xac),
+//	_I(0xad),
+//	_I(0xae),
+//	_I(0xaf),
+//	_I(0xb0),
+//	_I(0xb1),
+//	_I(0xb2),
+//	_I(0xb3),
+//	_I(0xb4),
+//	_I(0xb5),
+//	_I(0xb6),
+//	_I(0xb7),
+//	_I(0xb8),
+//	_I(0xb9),
+//	_I(0xba),
+//	_I(0xbb),
+//	_I(0xbc),
+//	_I(0xbd),
+//	_I(0xbe),
+//	_I(0xbf),
+//	_I(0xc0),
+//	_I(0xc1),
+//	_I(0xc2),
+//	_I(0xc3),
+//	_I(0xc4),
+//	_I(0xc5),
+//	_I(0xc6),
+//	_I(0xc7),
+//	_I(0xc8),
+//	_I(0xc9),
+//	_I(0xca),
+//	_I(0xcb),
+//	_I(0xcc),
+//	_I(0xcd),
+//	_I(0xce),
+//	_I(0xcf),
+//	_I(0xd0),
+//	_I(0xd1),
+//	_I(0xd2),
+//	_I(0xd3),
+//	_I(0xd4),
+//	_I(0xd5),
+//	_I(0xd6),
+//	_I(0xd7),
+//	_I(0xd8),
+//	_I(0xd9),
+//	_I(0xda),
+//	_I(0xdb),
+//	_I(0xdc),
+//	_I(0xdd),
+//	_I(0xde),
+//	_I(0xdf),
+//	_I(0xe0),
+//	_I(0xe1),
+//	_I(0xe2),
+//	_I(0xe3),
+//	_I(0xe4),
+//	_I(0xe5),
+//	_I(0xe6),
+//	_I(0xe7),
+//	_I(0xe8),
+//	_I(0xe9),
+//	_I(0xea),
+//	_I(0xeb),
+//	_I(0xec),
+//	_I(0xed),
+//	_I(0xee),
+//	_I(0xef),
+//	_I(0xf0),
+//	_I(0xf1),
+//	_I(0xf2),
+//	_I(0xf3),
+//	_I(0xf4),
+//	_I(0xf5),
+//	_I(0xf6),
+//	_I(0xf7),
+//	_I(0xf8),
+//	_I(0xf9),
+//	_I(0xfa),
+//	_I(0xfb),
+//	_I(0xfc),
+//	_I(0xfd),
+//	_I(0xfe),
+//	_I(0xff),
+//};
+
+//static uint16 sBlurBuffer[640*448];
+
+namespace NxNgc
+{
+
+	// extern void	test_render(Mth::Matrix* camera_orient, Mth::Vector* camera_pos);
+	// extern void	test_init();			
+
+} // namespace NxNgc
+
+
+namespace Nx
+{
+
+//	    // Set camera configuration
+//	    sc->light.cam.cfg = ( sc->projMode ) ?
+//	                        DefaultLightCamera1 : DefaultLightCamera0;
+//	    SetCamera(&sc->light.cam);
+//	    
+//	    // Light camera
+//	    sc->projMode           = 0;
+//	    sc->light.cam.theta    = 0;
+//	    sc->light.cam.phi      = 60;
+//	    sc->light.cam.distance = 3000.0F;
+//	
+//	
+//		static void SetCamera( MyCameraObj* cam )
+//		{
+//			f32  r_theta, r_phi;
+//			
+//			r_theta = (f32)cam->theta * PI / 180.0F;
+//			r_phi   = (f32)cam->phi   * PI / 180.0F;
+//		
+//			cam->cfg.location.x =
+//				cam->distance * cosf(r_theta) * cosf(r_phi);
+//			cam->cfg.location.y =
+//				cam->distance * sinf(r_theta) * cosf(r_phi);
+//			cam->cfg.location.z =
+//				cam->distance * sinf(r_phi);
+//		
+//			MTXLookAt(
+//				cam->view,
+//				&cam->cfg.location,
+//				&cam->cfg.up,
+//				&cam->cfg.target );    
+//		
+//			if ( cam->cfg.type == GX_PERSPECTIVE )
+//			{
+//				MTXFrustum(
+//					cam->proj,
+//					cam->cfg.top,
+//					- (cam->cfg.top),
+//					cam->cfg.left,
+//					- (cam->cfg.left),
+//					cam->cfg.znear,
+//					cam->cfg.zfar );
+//			}
+//			else // ( cam->cfg.type == GX_ORTHOGRAPHIC )
+//			{
+//				MTXOrtho(
+//					cam->proj,
+//					cam->cfg.top,
+//					- (cam->cfg.top),
+//					cam->cfg.left,
+//					- (cam->cfg.left),
+//					cam->cfg.znear,
+//					cam->cfg.zfar );
+//			}
+//			
+//			GXSetProjection(cam->proj, cam->cfg.type);
+//		}
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_start_engine( void )
+{
+    uint32	size;
+
+	NxNgc::InitialiseEngine();
+
+	
+	mp_particle_manager = new CNgcNewParticleManager;
+
+//	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+//
+//	// Allocate memory for shadow map
+	size = GX::GetTexBufferSize( SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE, GX_TF_I4, GX_FALSE, 0 );
+	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+    shadowTextureData = new uint8[size];
+	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+//
+//	size = GXGetTexBufferSize( 320, 224, GX_TF_Z8, GX_FALSE, 0 );
+//	zTextureDataH = new uint8[size];
+//	zTextureDataL = new uint8[size];
+//
+//	size = GXGetTexBufferSize( 320, 224, GX_TF_RGBA8, GX_FALSE, 0 );
+//	screenTextureData = new uint8[size];
+//
+//	size = GXGetTexBufferSize( 320, 224, GX_TF_RGBA8, GX_FALSE, 0 );
+//	focusTextureData = new uint8[size];
+//
+//	size = GXGetTexBufferSize( BLUR_TEXTURE_SIZE, BLUR_TEXTURE_SIZE, GX_TF_I4, GX_FALSE, 0 );
+//    blurTextureData = new uint8[size];
+//
+//	size = GXGetTexBufferSize( 640, 448, GX_TF_A8, GX_FALSE, 0 );
+//	volumeTextureData = new uint8[size];
+//
+//
+//	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+
+	if( NxNgc::EngineGlobals.use_widescreen )
+	{
+		Script::RunScript( "screen_setup_widescreen" );
+	}
+	mp_weather = new CNgcWeather;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_pre_render( void )
+{
+	//NsDisplay::begin();
+	//NsRender::begin();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_post_render( void )
+{
+	//NsRender::end();
+	//NsDisplay::end();
+//	D3DDevice_Swap( D3DSWAP_DEFAULT );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_render_world( void )
+{
+	if ( gLoadingScreenActive ) return;
+
+	NsBuffer::begin();
+
+	g_object = 0;
+	g_view_scene = NULL;
+//	if ( gPrintMatStats )
+//	{
+//		gPrintMatStats = false;
+//
+//		char *blend_text[] =
+//		{
+//			"DIFFUSE        ",
+//			"ADD            ",
+//			"ADD_FIXED      ",
+//			"SUBTRACT       ",
+//			"SUB_FIXED      ",
+//			"BLEND          ",
+//			"BLEND_FIXED    ",
+//			"MODULATE       ",
+//			"MODULATE_FIXED ",
+//			"BRIGHTEN       ",
+//			"BRIGHTEN_FIXED ",
+//		};
+//
+//		OSReport( "Unique blend modes: %d\n\n", num_u_mat );
+//
+//		for ( int um = 0; um < num_u_mat; um++ )
+//		{
+//			OSReport( "%d: (%4d) ", p_u_mat[um]->m_passes, u_mat_count[um] );
+//
+//			NxNgc::sMaterialPassHeader * p_u_pass = (NxNgc::sMaterialPassHeader *)&p_u_mat[um][1];
+//			for ( int p = 0; p < p_u_mat[um]->m_passes; p++ )
+//			{
+//				if ( p_u_pass[p].m_blend_mode <= 10 )
+//				{
+//					OSReport( "%s", blend_text[p_u_pass[p].m_blend_mode] );
+//				}
+//				else
+//				{
+//					OSReport( "      " );
+//				}
+//			}
+//			OSReport( "\n" );
+//		}
+//		
+//	}
+	
+	NsCamera cam;
+	Mtx light;
+	MTXIdentity( light );
+
+	NxNgc::EngineGlobals.gpuBusy = true;
+
+#ifndef __NOPT_FINAL__
+	if ( gDumpHeap )
+	{
+		Mem::Manager& mem_man = Mem::Manager::sHandle();
+	
+		OSReport ("MEM CONTEXT: %s\n",Mem::Manager::sHandle().GetContextName());
+		OSReport("Name            Used  Frag  Free   Blocks\n");
+		OSReport("--------------- ----- ----- ------ ------\n");
+		Mem::Heap* heap;
+		for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+		{		
+				Mem::Region* region = heap->ParentRegion();			
+				OSReport( "%12s: %6dK %6dK %6dK   %6d \n",
+						heap->GetName(),
+						heap->mUsedMem.m_count / 1024,
+						heap->mFreeMem.m_count / 1024,
+						region->MemAvailable() / 1024,
+						heap->mUsedBlocks.m_count
+						);										
+		}
+		gDumpHeap = false;
+	}
+#endif		// __NOPT_FINAL__
+
+//	NsPrim::end();
+//	NsRender::end();
+//
+////	if ( movies )
+////	{
+////		NsDisplay::end( false );
+////	}
+////	else
+////	{
+//		NsDisplay::end( true );
+////	}
+
+
+	NxNgc::process_instances();
+
+	NsDisplay::begin();
+//	NsDisplay::end();
+//	NsDisplay::begin();
+
+
+//	// Create color map if necessary.
+//	if ( !color_map_created )
+//	{
+//		color_map_created = true;
+//
+//		for ( int y = 0; y < COLOR_MAP_SIZE; y++ )
+//		{
+//			for ( int x = 0; x < COLOR_MAP_SIZE; x++ )
+//			{
+//				int r;
+//				int g;
+//
+//				r = 0;
+//				r += ( x / 4 ) * 32;
+//				r += ( x & 3 );
+//				r += ( ( y / 4 ) * ( COLOR_MAP_SIZE * 4 * 2 ) );
+//				r += ( ( y & 3 ) * 4 );
+//
+//				g = 16;
+//				g += ( x / 4 ) * 32;
+//				g += ( x & 3 );
+//				g += ( ( y / 4 ) * ( COLOR_MAP_SIZE * 4 * 2 ) );
+//				g += ( ( y & 3 ) * 4 );
+//
+//				// Red across.
+//				colorMap[r] = ( x * 256 ) / COLOR_MAP_SIZE;
+//				// Green down.
+//				colorMap[g] = ( ( ( y * 256 ) / COLOR_MAP_SIZE ) << 8 );
+//			}
+//		}
+//	}
+
+
+
+
+	// Clear to black with alpha of 124.
+	// This is important as the shadow volume stuff uses the alpha channel as a stencil buffer.
+	// With destination alpha, the RGBA channels each have 6 bits, so they are only accurate to multiples of 4.
+	// To save having to copy the alpha map to a texture, we do a 2-pass full-screen polygon.
+	// 1. And the alpha channel with 128. As it was set to 124, anything positive will have bit (128) set.
+	// 2. Blend alpha with framebuffer.
+//	NsDisplay::setBackgroundColor( (GXColor){0,0,0,124} );
+	NsDisplay::setBackgroundColor( (GXColor){0,0,0,255} );
+	NsRender::begin();
+	NsPrim::begin();
+
+//	cam.orthographic( 0, 0, 640, 448 );
+//	cam.begin();
+//
+//	GX::SetNumTevStages(1);
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetNumTexGens( 0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//	GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//	GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE, GX_TRUE, GX_FALSE, GX_FALSE );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+//	GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//	GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//	GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//
+//	NsPrim::quad(   0.0f,   0.0f, -9999.0f,
+//				  640.0f,   0.0f, -9999.0f,
+//				  640.0f, 448.0f, -9999.0f,
+//				    0.0f, 448.0f, -9999.0f,
+//				  (GXColor){128,128,128,0} );
+//
+//	cam.end();
+
+//	bool movies = Flx::Movie_Render();
+
+	//--------------------------------------------------------------
+	//--------------------------------------------------------------
+	//--------------------------------------------------------------
+
+#	ifdef __USE_PROFILER__
+	Sys::VUProfiler->PushContext( 128,128,0 );
+#	endif		// __USE_PROFILER__
+	if( sp_loaded_scenes[0] != NULL )
+	{
+		GX::SetZMode ( GX_FALSE, GX_ALWAYS, GX_FALSE );
+	
+
+
+
+	//	// 8bit mode
+	//	shCpFmt = GX_TF_Z8;
+	//	shFmt   = GX_TF_I8;
+	//
+		//-------------------------------------------
+		//  1st. pass
+		//  Make an image viewed from the light
+		//-------------------------------------------
+	
+		// Color update is disabled. Only Z will be updated.
+	//		GXSetColorUpdate(GX_DISABLE);
+	
+		// To draw "second" surfaces from the light
+		//GX::SetCullMode(GX_CULL_FRONT);
+	
+		// Set viewport for making shadow texture
+	//		GXSetViewport(0, 0, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE, 0.0F, 1.0F);
+	//		GXSetScissor(0, 0, (u32)SHADOW_TEXTURE_SIZE, (u32)SHADOW_TEXTURE_SIZE);
+	
+		// Set up the camera..
+		//NxNgc::set_camera( &( cur_camera->GetMatrix()), &( cur_camera->GetPos()), cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio());
+	
+		// Set render mode to use only constant color
+		// because we need only depth buffer
+	//		GXSetNumChans(1);
+		GX::SetChanCtrl(
+			GX_COLOR0A0,
+			GX_DISABLE,    // disable channel
+			GX_SRC_REG,    // amb source
+			GX_SRC_REG,    // mat source
+			GX_LIGHT_NULL, // light mask
+			GX_DF_CLAMP,   // diffuse function
+			GX_AF_NONE );
+	
+		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV,
+											   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+		GX::SetTevColorInOp( GX_TEVSTAGE0,	   GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV );
+		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	
+	
+		// Draw models
+	//		MTXCopy(sc->light.cam.view, tmo.view);
+	//		DrawModels(&tmo, sc->modelRot);
+	//	
+//		NxNgc::render_instances( true );
+		NxNgc::EngineGlobals.poly_culling = false;
+		NxNgc::render_shadow_targets();
+	
+	
+
+
+
+
+
+
+		// Draw line around border.
+		cam.orthographic( 0, 0, 640, 448 );
+		cam.begin();
+
+		GX::SetPointSize( 6, GX_TO_ONE );
+		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+											   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+										   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+		GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){0,0,0,255} );
+
+		GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+
+		for ( int vvv = 0; vvv < 8; vvv++ )
+		{
+			float min = -1.0f + ( (float)vvv / 4.0f );
+			float max = (float)SHADOW_TEXTURE_SIZE - ( (float)vvv / 4.0f );
+			GX::Begin( GX_LINESTRIP, GX_VTXFMT0, 5 ); 
+				GX::Position3f32(min, min, -1.0f);
+				GX::Position3f32(max, min, -1.0f);
+				GX::Position3f32(max, max, -1.0f);
+				GX::Position3f32(min, max, -1.0f);
+				GX::Position3f32(min, min, -1.0f);
+			GX::End();
+		}
+		cam.end();
+
+
+
+
+
+
+
+
+
+	//		// Copy shadow image into texture
+	//		GXSetTexCopySrc(0, 0, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE);
+	//		GXSetTexCopyDst(SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE, GX_TF_Z16, GX_FALSE);
+	//		GXCopyTex(zTextureData, GX_FALSE);
+	//
+	//		// Wait for finishing the copy task in the graphics pipeline
+	//		GXPixModeSync();
+	
+//		// Draw line around border.
+//
+//		cam.orthographic( 0, 0, 640, 448 );
+//		cam.begin();
+//
+//		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+//		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+//											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+//											   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//		GX::SetTevColorInOp( GX_TEVSTAGE0,	   GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC,
+//											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//		GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL );
+//
+//		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+//		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//	
+//		GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//	
+//		for ( int vvv = 0; vvv < 8; vvv++ )
+//		{
+//			float min = -1.0f + ( (float)vvv / 4.0f );
+//			float max = (float)SHADOW_TEXTURE_SIZE - ( (float)vvv / 4.0f );
+//			GX::Begin( GX_LINESTRIP, GX_VTXFMT0, 5 ); 
+//				GX::Position3f32(min, min, -1.0f);
+//				GX::Position3f32(max, min, -1.0f);
+//				GX::Position3f32(max, max, -1.0f);
+//				GX::Position3f32(min, max, -1.0f);
+//				GX::Position3f32(min, min, -1.0f);
+//			GX::End();
+//		}
+
+
+		// Copy shadow image into texture
+		GX::SetTexCopySrc(0, 0, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE);
+		GX::SetTexCopyDst(SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE, GX_CTF_R4, GX_FALSE);
+		GX::CopyTex(shadowTextureData, GX_FALSE);
+	
+		// Wait for finishing the copy task in the graphics pipeline
+		GX::PixModeSync();
+	
+		// Enable color update
+	//		GXSetColorUpdate(GX_ENABLE);
+	
+		// Restore culling mode to normal
+		//GX::SetCullMode(GX_CULL_BACK);
+	
+	
+	
+	
+	
+	
+	
+	
+	
+//		//--------------------------------------------------------------
+//		//--------------------------------------------------------------
+//		//--------------------------------------------------------------
+//	
+//		// Horrible hack - this should be somewhere else ASAP.
+//	
+//	
+//		GX::UploadTexture( shadowTextureData,
+//						   SHADOW_TEXTURE_SIZE,
+//						   SHADOW_TEXTURE_SIZE,
+//						   GX_TF_C4,
+//						   GX_CLAMP,
+//						   GX_CLAMP,
+//						   GX_FALSE,
+//						   GX_LINEAR,
+//						   GX_LINEAR,
+//						   0.0f,
+//						   0.0f,
+//						   0.0f,
+//						   GX_FALSE,
+//						   GX_FALSE,
+//						   GX_ANISO_1,
+//						   GX_TEXMAP0 );
+//
+//		GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE );
+//
+//		GX::UploadPalette( &shadowPalette,
+//						   GX_TL_RGB5A3,
+//						   GX_TLUT_16,
+//						   GX_TEXMAP0 ); 
+//
+//
+//		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+//		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+//	
+//		GX::SetTexChanTevIndCull( 1, 1, 1, 0, GX_CULL_NONE );
+//		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//
+//		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO,
+//								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+//								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO,
+//							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//
+//		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+//		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//	
+//		Mtx mv;
+//		MTXIdentity( mv );
+//		GX::LoadTexMtxImm(mv, GX_TEXMTX0, GX_MTX3x4);
+//	
+//		// Set current vertex descriptor to enable position and color0.
+//		// Both use 8b index to access their data arrays.
+//		GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+//	
+//		// Send coordinates.
+//	//		GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//	//			GXPosition3f32(-BLUR_BORDER, -BLUR_BORDER, -1.0f);
+//	//			GXTexCoord2f32(0.0f, 0.0f);
+//	//			GXPosition3f32(BLUR_TEXTURE_SIZE+BLUR_BORDER, -BLUR_BORDER, -1.0f);
+//	//			GXTexCoord2f32(1.0f, 0.0f);
+//	//			GXPosition3f32(BLUR_TEXTURE_SIZE+BLUR_BORDER, BLUR_TEXTURE_SIZE+BLUR_BORDER, -1.0f);
+//	//			GXTexCoord2f32(1.0f, 1.0f);
+//	//			GXPosition3f32(-BLUR_BORDER, BLUR_TEXTURE_SIZE+BLUR_BORDER, -1.0f);
+//	//			GXTexCoord2f32(0.0f, 1.0f);
+//	//		GXEnd();
+//	
+//	
+//	
+//		GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//			GX::Position3f32(0.0f, 0.0f, -1.0f);
+//			GX::TexCoord2f32(BLUR_0, BLUR_0);
+//			GX::Position3f32(BLUR_TEXTURE_SIZE, 0.0f, -1.0f);
+//			GX::TexCoord2f32(BLUR_1, BLUR_0);
+//			GX::Position3f32(BLUR_TEXTURE_SIZE, BLUR_TEXTURE_SIZE, -1.0f);
+//			GX::TexCoord2f32(BLUR_1, BLUR_1);
+//			GX::Position3f32(0.0f, BLUR_TEXTURE_SIZE, -1.0f);
+//			GX::TexCoord2f32(BLUR_0, BLUR_1);
+//		GX::End();
+//	
+//		// Draw line around blur texture.
+//		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+//		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//
+//		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+//								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+//								 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//
+//		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC,
+//							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//
+//		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+//		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//	
+//		GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//	
+//		GX::Begin( GX_LINESTRIP, GX_VTXFMT0, 5 ); 
+//			GX::Position3f32(0.0f, 0.0f, -1.0f);
+//			GX::Position3f32(BLUR_TEXTURE_SIZE-1, 0.0f, -1.0f);
+//			GX::Position3f32(BLUR_TEXTURE_SIZE-1, BLUR_TEXTURE_SIZE-1, -1.0f);
+//			GX::Position3f32(0.0f, BLUR_TEXTURE_SIZE-1, -1.0f);
+//			GX::Position3f32(0.0f, 0.0f, -1.0f);
+//		GX::End();
+//
+//		// Copy blur image into texture
+//		GX::SetTexCopySrc(0, 0, BLUR_TEXTURE_SIZE, BLUR_TEXTURE_SIZE);
+//		GX::SetTexCopyDst(BLUR_TEXTURE_SIZE, BLUR_TEXTURE_SIZE, GX_CTF_R4, GX_FALSE);
+//		GX::CopyTex(blurTextureData, GX_TRUE);
+//	
+//		// Wait for finishing the copy task in the graphics pipeline
+//		GX::PixModeSync();
+//	
+//	
+//	//		// Clear screen to black.
+//	//		GX::SetNumTevStages(1);
+//	//		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//	//		GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	//		GX::SetNumTexGens( 0 );
+//	//		GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//	//		GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//	//		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//	//		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//	//		GX::SetTevKColorSel( GX_TEVSTAGE0, GX_TEV_KCSEL_K0 );
+//	//		GX::SetTevKAlphaSel( GX_TEVSTAGE0, GX_TEV_KASEL_K0_A );
+//	//		GX::SetTevKColor( GX_KCOLOR0, (GXColor){0,0,0,255} );
+//	//		GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST );
+//	//		GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_KONST );
+//	//
+//	//		GXSetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//	//
+//	//		// Send coordinates.
+//	//		GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//	//			GXPosition3f32(0.0f,   0.0f,   -50000.0f);
+//	//			GXPosition3f32(640.0f, 0.0f,   -50000.0f);
+//	//			GXPosition3f32(640.0f, 480.0f, -50000.0f);
+//	//			GXPosition3f32(0.0f,   480.0f, -50000.0f);
+//	//		GXEnd();
+//	
+//		cam.end();
+
+		// Clear to bg color.
+		cam.orthographic( 0, 0, 640, 448 );
+		cam.begin();
+
+		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+		GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+		GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+											   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+										   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+		GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+
+		GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+
+		if ( g_in_cutscene )
+		{
+			// Bars must be black.
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){0,0,0,255} );
+			GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+				GX::Position3f32( -4.0f, -4.0f, -1.0f );
+				GX::Position3f32( -4.0f+648.0f, -4.0f, -1.0f );
+				GX::Position3f32( -4.0f+648.0f, -4.0f+488.0f, -1.0f );
+				GX::Position3f32( -4.0f, -4.0f+488.0f, -1.0f );
+			GX::End();
+
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){0x50,0x60,0x70,255} );
+			GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+				GX::Position3f32( -4.0f, 56.0f, -1.0f );
+				GX::Position3f32( -4.0f+648.0f, 56.0f, -1.0f );
+				GX::Position3f32( -4.0f+648.0f, 336.0f, -1.0f );
+				GX::Position3f32( -4.0f, 336.0f, -1.0f );
+			GX::End();
+		}
+		else
+		{
+			// Set to sky color.
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){0x50,0x60,0x70,255} );
+			GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+				GX::Position3f32( -4.0f, -4.0f, -1.0f );
+				GX::Position3f32( -4.0f+648.0f, -4.0f, -1.0f );
+				GX::Position3f32( -4.0f+648.0f, -4.0f+488.0f, -1.0f );
+				GX::Position3f32( -4.0f, -4.0f+488.0f, -1.0f );
+			GX::End();
+		}
+		cam.end();
+	}
+#	ifdef __USE_PROFILER__
+	Sys::VUProfiler->PopContext();
+#	endif		// __USE_PROFILER__
+
+	//--------------------------------------------------------------
+	//--------------------------------------------------------------
+	//--------------------------------------------------------------
+	int rendered = 0;
+	int considered = 0;
+	meshes_considered = 0;
+
+	Nx::CFog::sFogUpdate();
+
+	// Note: this method of setting up the camera must change
+	// so that the p_nx module does not reference things higher up the hierarchy.
+	int num_viewports = CViewportManager::sGetNumActiveViewports();
+	for( int v = 0; v < num_viewports; ++v )
+	{
+		NxNgc::render_begin();
+
+		NxNgc::EngineGlobals.viewport = ( 1 << v );
+		CViewport *p_cur_viewport = CViewportManager::sGetActiveViewport( v );
+		Gfx::Camera *cur_camera = NULL;
+		if ( p_cur_viewport ) cur_camera = p_cur_viewport->GetCamera();
+
+#	ifdef __USE_PROFILER__
+		Sys::VUProfiler->PushContext( 0,128,0 );
+#	endif		// __USE_PROFILER__
+		if( cur_camera && ( sp_loaded_scenes[0] != NULL ))
+		{
+//			NxNgc::EngineGlobals.viewport.X		= (DWORD)( p_cur_viewport->GetOriginX() * NxNgc::EngineGlobals.backbuffer_width );
+//			NxNgc::EngineGlobals.viewport.Y		= (DWORD)( p_cur_viewport->GetOriginY() * NxNgc::EngineGlobals.backbuffer_height );
+//			NxNgc::EngineGlobals.viewport.Width	= (DWORD)( p_cur_viewport->GetWidth() * NxNgc::EngineGlobals.backbuffer_width );
+//			NxNgc::EngineGlobals.viewport.Height	= (DWORD)( p_cur_viewport->GetHeight() * NxNgc::EngineGlobals.backbuffer_height );
+//			NxNgc::EngineGlobals.viewport.MinZ		= 0.0f;
+//			NxNgc::EngineGlobals.viewport.MaxZ		= 1.0f;
+//			D3DDevice_SetViewport( &NxNgc::EngineGlobals.viewport );
+
+			float vx = p_cur_viewport->GetOriginX() * 640.0f;
+			float vy = p_cur_viewport->GetOriginY() * 448.0f;
+			float vw = p_cur_viewport->GetWidth() * 640.0f;
+			float vh = p_cur_viewport->GetHeight() * 448.0f;
+			
+			if( NxNgc::EngineGlobals.letterbox_active )
+			{
+				vy += 448.0f / 8.0f;
+				vh -= 448.0f / 4.0f;
+			}
+
+            GX::SetViewport( vx, vy, vw, vh, 0.0f, 1.0f );
+            GX::SetScissor( (u32)vx, (u32)vy, (u32)vw, (u32)vh );
+
+			NxNgc::set_camera( &( cur_camera->GetMatrix()), &( cur_camera->GetPos()), cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio());
+
+			// There is no bounding box transform for rendering the world.
+	//		NxNgc::set_frustum_bbox_transform( NULL );
+
+			// Render objects of interest for the render target (shadow objects).
+	//		NxNgc::render_shadow_targets();
+
+			// Render the world. At this stage, non-sky scenes are rendered using just the opaque sections.
+
+			GX::LoadNrmMtxImm( light, GX_PNMTX0);
+			for( int i = 0; i < MAX_LOADED_SCENES; i++ )
+			{
+				if( sp_loaded_scenes[i] )
+				{
+					CNgcScene *pNgcScene = static_cast( sp_loaded_scenes[i] );
+
+					// If this is a sky scene, disable z buffer reads and writes, otherwise enable them.
+					if( pNgcScene->IsSky())
+					{
+						NxNgc::EngineGlobals.poly_culling = false;
+						// Set up the camera.
+						Mth::Vector	centre_pos( 0.0f, 0.0f, 0.0f );
+						NxNgc::set_camera( &( cur_camera->GetMatrix()), ¢re_pos, cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio(), ( 30000.0f / pNgcScene->GetEngineScene()->m_sphere[W] ) );
+	
+						GX::SetZMode ( GX_FALSE, GX_ALWAYS, GX_FALSE );
+	
+						uint32 flags = ( NxNgc::vRENDER_OPAQUE | NxNgc::vRENDER_SEMITRANSPARENT | NxNgc::vRENDER_LIT );
+
+						NxNgc::make_scene_visible( pNgcScene->GetEngineScene() );
+						NxNgc::render_scene( pNgcScene->GetEngineScene(), NULL, flags );
+					}
+					else
+					{
+						NxNgc::EngineGlobals.poly_culling = true;
+						// Set up the camera..
+						NxNgc::set_camera( &( cur_camera->GetMatrix()), &( cur_camera->GetPos()), cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio());
+
+						// Build relevant occlusion poly list, now that the camera is set.
+						NxNgc::BuildOccluders( &( cur_camera->GetPos()), v);
+
+						// Build relevant occlusion poly list, now that the camera is set.
+						GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_TRUE );
+
+						// Flag this scene as receiving shadows.
+						pNgcScene->GetEngineScene()->m_flags |= SCENE_FLAG_RECEIVE_SHADOWS;
+
+						uint32 flags = ( NxNgc::vRENDER_OPAQUE | NxNgc::vRENDER_OCCLUDED | NxNgc::vRENDER_LIT );
+
+						gCorrectColor = true;
+						rendered = NxNgc::cull_scene( pNgcScene->GetEngineScene(), NxNgc::vRENDER_OCCLUDED );
+						NxNgc::render_scene( pNgcScene->GetEngineScene(), NULL, flags );
+						considered = meshes_considered;
+						gCorrectColor = false;
+					}
+				}
+			}
+		}
+
+#	ifdef __USE_PROFILER__
+		Sys::VUProfiler->PopContext();
+#	endif		// __USE_PROFILER__
+
+//		// Process data for instances. Specifically, transform the verts.
+//		Sys::VUProfiler->PushContext( 0,128,128 );
+//		NxNgc::process_instances();
+//		Sys::VUProfiler->PopContext();
+
+		// Render all opaque instances.
+#	ifdef __USE_PROFILER__
+		Sys::VUProfiler->PushContext( 128,0,0 );
+#	endif		// __USE_PROFILER__
+		GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_TRUE );
+		NxNgc::render_instances( NxNgc::vRENDER_OPAQUE | NxNgc::vRENDER_TRANSFORM );
+		NxNgc::render_instances( NxNgc::vRENDER_SEMITRANSPARENT | NxNgc::vRENDER_INSTANCE_PRE_WORLD_SEMITRANSPARENT );
+#	ifdef __USE_PROFILER__
+		Sys::VUProfiler->PopContext();
+#	endif		// __USE_PROFILER__
+
+#	ifdef __USE_PROFILER__
+		Sys::VUProfiler->PushContext( 0,128,0 );
+#	endif		// __USE_PROFILER__
+
+		// Render all the non-sky semitransparent scene geometry.
+		NxNgc::EngineGlobals.poly_culling = true;
+		if( cur_camera && ( sp_loaded_scenes[0] != NULL ))
+		{
+			// There is no bounding box transform for rendering the world.
+	//		NxNgc::set_frustum_bbox_transform( NULL );
+
+			GX::LoadNrmMtxImm( light, GX_PNMTX0);
+			for( int i = 0; i < MAX_LOADED_SCENES; i++ )
+			{
+				if( sp_loaded_scenes[i] )
+				{
+					CNgcScene *pNgcScene = static_cast( sp_loaded_scenes[i] );
+
+					// Only interested in non-sky scenes, this time round.
+					if( !pNgcScene->IsSky())
+					{
+						// Set up the camera..
+						NxNgc::set_camera( &( cur_camera->GetMatrix()), &( cur_camera->GetPos()), cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio());
+
+						// Build relevant occlusion poly list, now that the camera is set.
+						GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_TRUE );
+
+
+						gCorrectColor = true;
+						//rendered += NxNgc::cull_scene( pNgcScene->GetEngineScene(), NxNgc::vRENDER_OCCLUDED );
+						uint32 flags;
+						flags = ( NxNgc::vRENDER_SEMITRANSPARENT | NxNgc::vRENDER_OCCLUDED | NxNgc::vRENDER_LIT );
+						NxNgc::render_scene( pNgcScene->GetEngineScene(), NULL, flags );
+						flags = ( NxNgc::vRENDER_SHADOW_2ND_PASS );
+						NxNgc::render_scene( pNgcScene->GetEngineScene(), NULL, flags );
+						//considered += meshes_considered;
+						gCorrectColor = false;
+						//OSReport( "Meshes rendered: %d of %d\n", rendered, considered );
+//					}
+//					else
+//					{
+//						// Draw sky after opaque world polys.
+//						// Set up the camera.
+//						Mth::Vector	centre_pos( 0.0f, 0.0f, 0.0f );
+//						NxNgc::set_camera( &( cur_camera->GetMatrix()), ¢re_pos, cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio(), 30.0f);
+//
+//						GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_FALSE );
+//
+//						gCorrectColor = true;
+//						NxNgc::render_scene( pNgcScene->GetEngineScene(), NULL );
+//						gCorrectColor = false;
+					}
+				}
+			}
+		}
+
+		GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_TRUE );
+		NxNgc::render_instances( NxNgc::vRENDER_SEMITRANSPARENT | NxNgc::vRENDER_INSTANCE_POST_WORLD_SEMITRANSPARENT );
+
+#	ifdef __USE_PROFILER__
+		Sys::VUProfiler->PopContext();
+#	endif		// __USE_PROFILER__
+
+		NxNgc::render_end();
+
+		// Render other stuff.
+		if( cur_camera && ( sp_loaded_scenes[0] != NULL ))
+		{
+			GX::SetFog( GX_FOG_NONE, 0.0f, 0.0f, 0.0f, 0.0f, (GXColor){0,0,0,0} );		// Turn fog off.
+			NxNgc::set_camera( &( cur_camera->GetMatrix()), &( cur_camera->GetPos()), cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio());
+		
+			GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_FALSE );
+			GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+			GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+			render_particles();
+			mp_weather->Process( Tmr::FrameLength() );
+			mp_weather->Render();
+
+			// New style particles. Update should probably be somewhere else.
+			Nx::CFog::sFogUpdate();		// Restore fog to its' former glory.
+			NxNgc::set_camera( &( cur_camera->GetMatrix()), &( cur_camera->GetPos()), cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio());
+			mp_particle_manager->UpdateParticles();
+			mp_particle_manager->RenderParticles();
+
+			GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_TRUE );
+			Nx::ShatterRender();
+			Nx::TextureSplatRender();
+			GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_FALSE );
+		}
+
+//		Gfx::DebugGfx_Draw();
+
+//		// Render all semi-transparent instances.
+//		GX::SetZMode ( GX_TRUE, GX_LEQUAL, GX_TRUE );
+//#	ifdef __USE_PROFILER__
+//		Sys::VUProfiler->PushContext( 128,0,0 );
+//#	endif		// __USE_PROFILER__
+//		NxNgc::render_instances( NxNgc::vRENDER_SHADOW_1ST_PASS );
+//#	ifdef __USE_PROFILER__
+//		Sys::VUProfiler->PopContext();
+//#	endif		// __USE_PROFILER__
+	
+	}
+
+	GX::SetViewport( 0.0f, 0.0f, 640.0f, 448.0f, 0.0f, 1.0f );
+	GX::SetScissor( 0, 0, 640, 448 );
+
+	// Horrible hack - this should be somewhere else ASAP.
+	GX::SetZMode ( GX_FALSE, GX_ALWAYS, GX_FALSE );
+
+	cam.orthographic( 0, 0, 640, 448 );
+	cam.begin();
+
+
+
+
+
+
+
+
+
+
+#ifndef __NOPT_FINAL__
+
+	// 2nd pad controls texture display.
+	static int tick;
+	static unsigned short last;
+	unsigned short current = padData[1].button;
+	unsigned short press = ( current ^ last ) & current;
+	last = current;
+
+	if ( press & ( PAD_BUTTON_UP | PAD_BUTTON_DOWN | PAD_BUTTON_X | PAD_BUTTON_Y ) )
+	{
+		tick = 0;
+	}
+
+
+	if ( current & ( PAD_BUTTON_UP | PAD_BUTTON_DOWN | PAD_BUTTON_X | PAD_BUTTON_Y ) )
+	{
+		if ( tick == 20 )
+		{
+			press |= current;
+		}
+		if ( ( ( ( tick - 20 ) % 2 ) == 0 ) && ( tick > 20 ) )
+		{
+			press |= current;
+		}
+
+		tick++;
+	}
+
+	if ( press & PAD_BUTTON_UP ) g_scroll_material++;
+	if ( press & PAD_BUTTON_DOWN ) g_scroll_material--;
+	if ( ( press & PAD_BUTTON_UP ) && ( current & PAD_BUTTON_B ) ) g_scroll_material+=99;
+	if ( ( press & PAD_BUTTON_DOWN ) && ( current & PAD_BUTTON_B ) ) g_scroll_material-=99;
+	if ( g_view_scene )
+	{
+		if ( g_scroll_material < 0 ) g_scroll_material = 0;
+		if ( g_scroll_material > ( g_view_scene->mp_scene_data->m_num_materials - 1 ) ) g_scroll_material = g_view_scene->mp_scene_data->m_num_materials - 1;
+	}
+
+	if ( press & PAD_BUTTON_X ) g_view_object++;
+	if ( press & PAD_BUTTON_Y ) g_view_object--;
+	if ( g_view_object < 0 ) g_view_object = 0;
+	if ( g_view_object > g_object ) g_view_object = g_object;
+
+	if ( press & PAD_BUTTON_A )
+	{
+		if ( g_material == -1 )
+		{
+			g_material = g_scroll_material;
+		}
+		else
+		{
+			g_material = -1;
+		}
+	}
+	if ( g_material != -1 )
+	{
+		g_material = g_scroll_material;
+	}
+
+	if ( press & PAD_TRIGGER_Z )
+	{
+		g_dl ^= 1;
+	}
+
+	if ( press & PAD_TRIGGER_R ) g_mip++;
+	if ( g_mip > 5 ) g_mip = 0;
+
+	if ( press & PAD_TRIGGER_L ) g_passes++;
+	if ( g_passes > 3 ) g_passes = -1;
+
+	if ( !g_view_scene )
+	{
+		// Draw Object number & "NO SCENE" info.
+		{
+			NxNgc::SText message;
+			Nx::CFont * p_cfont;
+			const char * p_font_name = "testtitle";
+			char p_text[128];
+
+			// We can only draw a text string if we have a font & a string.
+			p_cfont = Nx::CFontManager::sGetFont( p_font_name );
+			if ( !p_cfont )
+			{
+				Nx::CFontManager::sLoadFont( p_font_name );
+				p_cfont = Nx::CFontManager::sGetFont( p_font_name );
+			}
+			message.mp_string = p_text;
+			message.m_rgba = 0x808080ff;
+
+			Nx::CNgcFont * p_nfont = static_cast( p_cfont );
+			NxNgc::SFont * p_font = p_nfont->GetEngineFont();
+			message.mp_font = p_font;
+			message.m_xscale = 0.75f;
+			message.m_yscale = 1.2f;
+
+			message.m_color_override = false;
+
+			if ( p_font )
+			{
+				sprintf( p_text, "OBJ %d / %d - NOT VISIBLE", g_view_object, g_object + 1 );
+				message.m_xpos = 28.0f;
+				message.m_ypos = 72.0f;
+				message.DrawSingle();
+			}
+		}
+	}
+	else
+	{
+		if ( ( g_material >= 0 ) && ( g_material < g_view_scene->mp_scene_data->m_num_materials ) )
+		{
+			// Draw material.
+
+			NxNgc::sMaterialHeader * p_mat = &g_view_scene->mp_material_header[g_material];
+
+			uint8 save_passes = p_mat->m_passes;
+			p_mat->m_passes = ( g_passes == -1 ) ? p_mat->m_passes : ( g_passes + 1 );
+
+			// Adjust mip to render.
+			int mip_off[4] = { 0, 0, 0, 0 };
+			{
+				NxNgc::sMaterialPassHeader * p_pass = &g_view_scene->mp_material_pass[p_mat->m_pass_item];
+				for ( int tex = 0; tex < p_mat->m_passes; tex++, p_pass++ )
+				{
+					if ( p_pass->m_texture.p_data )
+					{
+						for ( int mm = 0; mm < g_mip; mm++ )
+						{
+							int mw = p_pass->m_texture.p_data->ActualWidth >> mm;
+							int mh = p_pass->m_texture.p_data->ActualHeight >> mm;
+							mip_off[tex] += ( mw * mh ) >> 1;
+						}
+						p_pass->m_texture.p_data->pTexelData += mip_off[tex];
+						p_pass->m_texture.p_data->pAlphaData += mip_off[tex];
+						p_pass->m_texture.p_data->ActualWidth >>= g_mip;
+						p_pass->m_texture.p_data->ActualHeight >>= g_mip;
+					}
+				}
+			}
+
+			multi_mesh( p_mat,
+						&g_view_scene->mp_material_pass[p_mat->m_pass_item],
+						true,
+						true );
+			p_mat->m_passes = save_passes;
+			// Adjust mip back.
+			{
+				NxNgc::sMaterialPassHeader * p_pass = &g_view_scene->mp_material_pass[p_mat->m_pass_item];
+				for ( int tex = 0; tex < p_mat->m_passes; tex++, p_pass++ )
+				{
+					if ( p_pass->m_texture.p_data )
+					{
+						p_pass->m_texture.p_data->pTexelData -= mip_off[tex];
+						p_pass->m_texture.p_data->pAlphaData -= mip_off[tex];
+						p_pass->m_texture.p_data->ActualWidth <<= g_mip;
+						p_pass->m_texture.p_data->ActualHeight <<= g_mip;
+					}
+				}
+			}
+
+			GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){64,64,64,128} );
+			GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){64,64,64,128} );
+
+			GX::SetVtxDesc( 5, GX_VA_POS,  GX_DIRECT,
+							   GX_VA_TEX0, GX_DIRECT,
+							   GX_VA_TEX1, GX_DIRECT,
+							   GX_VA_TEX2, GX_DIRECT,
+							   GX_VA_TEX3, GX_DIRECT );
+
+			GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+				GX::Position3f32(32.0f, 96.0f, -1.0f);
+				GX::TexCoord2f32(0.0f, 1.0f);
+				GX::TexCoord2f32(0.0f, 1.0f);
+				GX::TexCoord2f32(0.0f, 1.0f);
+				GX::TexCoord2f32(0.0f, 1.0f);
+				GX::Position3f32(160.0f, 96.0f, -1.0f);
+				GX::TexCoord2f32(1.0f, 1.0f);
+				GX::TexCoord2f32(1.0f, 1.0f);
+				GX::TexCoord2f32(1.0f, 1.0f);
+				GX::TexCoord2f32(1.0f, 1.0f);
+				GX::Position3f32(160.0f, 222.0f, -1.0f);
+				GX::TexCoord2f32(1.0f, 0.0f);
+				GX::TexCoord2f32(1.0f, 0.0f);
+				GX::TexCoord2f32(1.0f, 0.0f);
+				GX::TexCoord2f32(1.0f, 0.0f);
+				GX::Position3f32(32.0f, 222.0f, -1.0f);
+				GX::TexCoord2f32(0.0f, 0.0f);
+				GX::TexCoord2f32(0.0f, 0.0f);
+				GX::TexCoord2f32(0.0f, 0.0f);
+				GX::TexCoord2f32(0.0f, 0.0f);
+			GX::End();
+
+			float	tx[8] = { 32.0f, 98.0f, 164.0f, 230.0f, 296.0f, 362.0f, 428.0f, 494.0f };
+			float	ty[8] = { 230.0f, 230.0f, 230.0f, 230.0f, 230.0f, 230.0f, 230.0f, 230.0f };
+			int		tr = 0;
+
+			// Draw Object & material number.
+			{
+				NxNgc::SText message;
+				Nx::CFont * p_cfont;
+				const char * p_font_name = "testtitle";
+				char p_text[128];
+
+				// We can only draw a text string if we have a font & a string.
+				p_cfont = Nx::CFontManager::sGetFont( p_font_name );
+				if ( !p_cfont )
+				{
+					Nx::CFontManager::sLoadFont( p_font_name );
+					p_cfont = Nx::CFontManager::sGetFont( p_font_name );
+				}
+				message.mp_string = p_text;
+				message.m_rgba = 0x808080ff;
+
+				Nx::CNgcFont * p_nfont = static_cast( p_cfont );
+				NxNgc::SFont * p_font = p_nfont->GetEngineFont();
+				message.mp_font = p_font;
+				message.m_xscale = 0.75f;
+				message.m_yscale = 1.2f;
+
+				message.m_color_override = false;
+
+				if ( p_font )
+				{
+					char * p_pass_string = "***";
+					if ( g_passes == -1 ) p_pass_string = "ALL";
+					if ( g_passes == 0 ) p_pass_string = "P1";
+					if ( g_passes == 1 ) p_pass_string = "P2";
+					if ( g_passes == 2 ) p_pass_string = "P3";
+					if ( g_passes == 3 ) p_pass_string = "P4";
+					sprintf( p_text, "OBJ %d / %d - MAT %d / %d DO: %6.3f AC: %3d M: %d %s", g_view_object, g_object + 1, g_material, g_view_scene->mp_scene_data->m_num_materials, p_mat->m_draw_order, p_mat->m_alpha_cutoff, g_mip, p_pass_string );
+					message.m_xpos = 28.0f;
+					message.m_ypos = 72.0f;
+					message.DrawSingle();
+				}
+			}
+
+			NxNgc::sMaterialPassHeader * p_pass = &g_view_scene->mp_material_pass[p_mat->m_pass_item];
+			for ( int tex = 0; tex < p_mat->m_passes; tex++, p_pass++ )
+			{
+				if ( p_pass->m_texture.p_data )
+				{
+					// Draw texture.
+					int w = p_pass->m_texture.p_data->ActualWidth >> g_mip;
+					int h = p_pass->m_texture.p_data->ActualHeight >> g_mip;
+					int mip_off = 0;
+					for ( int mm = 0; mm < g_mip; mm++ )
+					{
+						int mw = p_pass->m_texture.p_data->ActualWidth >> mm;
+						int mh = p_pass->m_texture.p_data->ActualHeight >> mm;
+						mip_off += ( mw * mh ) >> 1;
+					}
+					GX::UploadTexture(  &p_pass->m_texture.p_data->pTexelData[mip_off],
+										w,
+										h,
+										GX_TF_CMPR,
+										GX_CLAMP,
+										GX_CLAMP,
+										GX_FALSE,
+										GX_LINEAR,
+										GX_LINEAR,
+										0.0f,
+										0.0f,
+										0.0f,
+										GX_FALSE,
+										GX_TRUE,
+										GX_ANISO_1,
+										GX_TEXMAP0 ); 
+					GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, w, h );
+
+					GX::SetTexChanTevIndCull( 1, 0, 1, 0, GX_CULL_NONE );
+					GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+					GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+					GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+					GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );		// Replace
+
+					GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+					GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+											 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+											 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+					GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+										 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+
+					GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+					GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+						GX::Position3f32(tx[tr], ty[tr], -1.0f);
+						GX::TexCoord2f32(0.0f, 1.0f);
+						GX::Position3f32(tx[tr] + 62.0f, ty[tr], -1.0f);
+						GX::TexCoord2f32(1.0f, 1.0f);
+						GX::Position3f32(tx[tr] + 62.0f, ty[tr] + 62.0f, -1.0f);
+						GX::TexCoord2f32(1.0f, 0.0f);
+						GX::Position3f32(tx[tr], ty[tr] + 62.0f, -1.0f);
+						GX::TexCoord2f32(0.0f, 0.0f);
+					GX::End();
+					if ( p_pass->m_texture.p_data->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA )
+					{
+						// Draw alpha.
+						int w = p_pass->m_texture.p_data->ActualWidth >> g_mip;
+						int h = p_pass->m_texture.p_data->ActualHeight >> g_mip;
+						int mip_off = 0;
+						for ( int mm = 0; mm < g_mip; mm++ )
+						{
+							int mw = p_pass->m_texture.p_data->ActualWidth >> mm;
+							int mh = p_pass->m_texture.p_data->ActualHeight >> mm;
+							mip_off += ( mw * mh ) >> 1;
+						}
+						GX::UploadTexture(  &p_pass->m_texture.p_data->pAlphaData[mip_off],
+											w,
+											h,
+											GX_TF_CMPR,
+											GX_CLAMP,
+											GX_CLAMP,
+											GX_FALSE,
+											GX_LINEAR,
+											GX_LINEAR,
+											0.0f,
+											0.0f,
+											0.0f,
+											GX_FALSE,
+											GX_TRUE,
+											GX_ANISO_1,
+											GX_TEXMAP0 ); 
+						GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, w, h );
+
+						GX::SetTexChanTevIndCull( 1, 0, 1, 0, GX_CULL_NONE );
+						GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+						GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+						GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+						GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );		// Replace
+
+						GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+
+						GXTevSwapSel alpha_swap = GX_TEV_SWAP1;
+						switch ( ( p_pass->m_texture.p_data->flags & NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_MASK ) )
+						{
+							case NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN:
+							default:
+								alpha_swap = GX_TEV_SWAP1;		// Green
+								break;
+							case NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_RED:
+								alpha_swap = GX_TEV_SWAP2;		// Red
+								break;
+							case NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_BLUE:
+								alpha_swap = GX_TEV_SWAP3;		// Blue
+								break;
+						}
+
+						GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+												 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+												 GX_TEV_SWAP0, alpha_swap );
+						GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+											 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+
+						GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+						GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+							GX::Position3f32(tx[tr+1], ty[tr+1], -1.0f);
+							GX::TexCoord2f32(0.0f, 1.0f);
+							GX::Position3f32(tx[tr+1] + 62.0f, ty[tr+1], -1.0f);
+							GX::TexCoord2f32(1.0f, 1.0f);
+							GX::Position3f32(tx[tr+1] + 62.0f, ty[tr+1] + 62.0f, -1.0f);
+							GX::TexCoord2f32(1.0f, 0.0f);
+							GX::Position3f32(tx[tr+1], ty[tr+1] + 62.0f, -1.0f);
+							GX::TexCoord2f32(0.0f, 0.0f);
+						GX::End();
+					}
+				}
+
+				// Draw pass info.
+				char * blendmode[14] = {
+					"DIFF",
+					"ADD",
+					"ADDF",
+					"SUB",
+					"SUBF",
+					"BLND",
+					"BLNDF",
+					"MOD",
+					"MODF",
+					"BRT",
+					"BRTF",
+					"GLOSS",
+					"BLPV",
+					"BLPVI"
+				};
+
+				NxNgc::SText message;
+				Nx::CFont * p_cfont;
+				const char * p_font_name = "testtitle";
+				char p_text[128];
+
+				// We can only draw a text string if we have a font & a string.
+				p_cfont = Nx::CFontManager::sGetFont( p_font_name );
+				if ( !p_cfont )
+				{
+					Nx::CFontManager::sLoadFont( p_font_name );
+					p_cfont = Nx::CFontManager::sGetFont( p_font_name );
+				}
+				message.mp_string = p_text;
+				message.m_rgba = 0x808080ff;
+
+				Nx::CNgcFont * p_nfont = static_cast( p_cfont );
+				NxNgc::SFont * p_font = p_nfont->GetEngineFont();
+				message.mp_font = p_font;
+				message.m_xscale = 0.75f;
+				message.m_yscale = 1.2f;
+
+				message.m_color_override = false;
+
+# define toupper(c) ( ( (c) >= 'a' ) && ( (c) <= 'z' ) ) ? (c) += ( 'A' - 'a' ) : (c)
+
+				if ( p_font )
+				{
+					if ( p_pass->m_texture.p_data )
+					{
+						sprintf( p_text, "%d,%d", p_pass->m_texture.p_data->ActualWidth, p_pass->m_texture.p_data->ActualHeight );
+						message.m_xpos = tx[tr];
+						message.m_ypos = ty[tr] + 66.0f + ( 16.0 * 0.0f );
+						message.DrawSingle();
+					}
+
+					sprintf( p_text, "%s", blendmode[p_pass->m_blend_mode] );
+					message.m_xpos = tx[tr];
+					message.m_ypos = ty[tr] + 66.0f + ( 16.0 * 1.0f );
+					message.DrawSingle();
+
+					sprintf( p_text, "%02x %02x", p_pass->m_color.r, p_pass->m_color.g );
+					p_text[0] = toupper( p_text[0] );
+					p_text[1] = toupper( p_text[1] );
+					p_text[3] = toupper( p_text[3] );
+					p_text[4] = toupper( p_text[4] );
+					message.m_xpos = tx[tr];
+					message.m_ypos = ty[tr] + 66.0f + ( 16.0 * 2.0f );
+					message.DrawSingle();
+
+					sprintf( p_text, "%02x %02x", p_pass->m_color.b, p_pass->m_color.a );
+					p_text[0] = toupper( p_text[0] );
+					p_text[1] = toupper( p_text[1] );
+					p_text[3] = toupper( p_text[3] );
+					p_text[4] = toupper( p_text[4] );
+					message.m_xpos = tx[tr];
+					message.m_ypos = ty[tr] + 66.0f + ( 16.0 * 3.0f );
+					message.DrawSingle();
+
+					sprintf( p_text, "%02x %02x", p_pass->m_alpha_fix, p_pass->m_flags );
+					p_text[0] = toupper( p_text[0] );
+					p_text[1] = toupper( p_text[1] );
+					p_text[3] = toupper( p_text[3] );
+					p_text[4] = toupper( p_text[4] );
+					message.m_xpos = tx[tr];
+					message.m_ypos = ty[tr] + 66.0f + ( 16.0 * 4.0f );
+					message.DrawSingle();
+
+					if ( p_pass->m_texture.p_data )
+					{
+						sprintf( p_text, "%3.1f %d", (float)(p_pass->m_k) * (1.0f / (float)(1<<8)), p_pass->m_texture.p_data->Levels );
+						message.m_xpos = tx[tr];
+						message.m_ypos = ty[tr] + 66.0f + ( 16.0 * 5.0f );
+						message.DrawSingle();
+					}
+				}
+				tr++;
+				if ( p_pass->m_texture.p_data && p_pass->m_texture.p_data->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA ) tr++;
+			}
+		}
+	}
+#endif
+
+//	// Draw texture.
+//	GX::UploadTexture(  shadowTextureData,
+//						SHADOW_TEXTURE_SIZE,
+//						SHADOW_TEXTURE_SIZE,
+//						GX_TF_I4,
+//						GX_CLAMP,
+//						GX_CLAMP,
+//						GX_FALSE,
+//						GX_LINEAR,
+//						GX_LINEAR,
+//						0.0f,
+//						0.0f,
+//						0.0f,
+//						GX_FALSE,
+//						GX_TRUE,
+//						GX_ANISO_1,
+//						GX_TEXMAP0 ); 
+//	GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE );
+//
+//	GX::UploadPalette( shadowPalette,
+//					   GX_TL_RGB5A3,
+//					   GX_TLUT_16,
+//					   GX_TEXMAP0 );
+//
+//	GX::SetTexChanTevIndCull( 1, 0, 1, 0, GX_CULL_NONE );
+//	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+//	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+//
+//	GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+//	GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );		// Replace
+//
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+//							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+//							 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+//						 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//
+//	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+//
+//	GX::Begin( GX_QUADS, GX_VTXFMT0, 4 );
+//		GX::Position3f32( 32.0f,  32.0f, -1.9f);
+//		GX::TexCoord2f32(0.0f, 0.0f);
+//		GX::Position3f32(288.0f,  32.0f, -1.9f);
+//		GX::TexCoord2f32(1.0f, 0.0f);
+//		GX::Position3f32(288.0f, 288.0f, -1.9f);
+//		GX::TexCoord2f32(1.0f, 1.0f);
+//		GX::Position3f32( 32.0f, 288.0f, -1.9f);
+//		GX::TexCoord2f32(0.0f, 1.0f);
+//	GX::End();
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+
+
+
+
+
+
+
+
+
+
+//	// Shadow volume stage 1.
+//	// And the alpha channel with 128. Pixels in shadow will have alpha of 128, not will be 0.
+//	GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+//	GX::SetBlendMode ( GX_BM_LOGIC, GX_BL_SRCALPHA, GX_BL_DSTALPHA, GX_LO_AND, GX_FALSE, GX_TRUE, GX_FALSE );
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_KONST, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+//							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+//							 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+//						 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevKColor( GX_KCOLOR0, (GXColor){0,0,0,128} );
+//	GX::SetTevKSel( GX_TEVSTAGE0, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A );
+//
+//	GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//
+//	GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//		GX::Position3f32(0.0f, 0.0f, -1.0f);
+//		GX::Position3f32(640.0f, 0.0f, -1.0f);
+//		GX::Position3f32(640.0f, 448.0f, -1.0f);
+//		GX::Position3f32(0.0f, 448.0f, -1.0f);
+//	GX::End();
+//
+//	// Shadow volume stage 2.
+//	// Blend alpha with shadow color.
+//	GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+//	GX::SetBlendMode( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_INVDSTALPHA, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_KONST, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+//							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+//							 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_KONST, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+//						 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevKColor( GX_KCOLOR0, (GXColor){0,0,0,128} );
+//	GX::SetTevKSel( GX_TEVSTAGE0, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A, GX_TEV_KCSEL_K0, GX_TEV_KASEL_K0_A );
+//
+//	GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//
+//	GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//		GX::Position3f32(0.0f, 0.0f, -1.0f);
+//		GX::Position3f32(640.0f, 0.0f, -1.0f);
+//		GX::Position3f32(640.0f, 448.0f, -1.0f);
+//		GX::Position3f32(0.0f, 448.0f, -1.0f);
+//	GX::End();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#if 0
+	// Copy framebuffer to 320x224 texture.
+	GXSetCopyFilter( GX_FALSE, NULL, GX_FALSE, NULL );
+	GXSetTexCopySrc(0, 0, 640, 448);
+	GXSetTexCopyDst(320, 224, GX_TF_Z8, GX_TRUE);
+	GXCopyTex(zTextureDataH, GX_FALSE);
+	GXPixModeSync();
+
+	GXSetCopyFilter( GX_FALSE, NULL, GX_FALSE, NULL );
+	GXSetTexCopySrc(0, 0, 640, 448);
+	GXSetTexCopyDst(320, 224, GX_CTF_Z8M, GX_TRUE);
+	GXCopyTex(zTextureDataL, GX_FALSE);
+	GXPixModeSync();
+
+	// Copy top-corner of screen.
+	GXSetCopyFilter( GX_FALSE, NULL, GX_FALSE, NULL );
+	GXSetTexCopySrc(0, 0, 320, 224);
+	GXSetTexCopyDst(320, 224, GX_TF_RGBA8, GX_FALSE);
+	GXCopyTex(screenTextureData, GX_FALSE);
+	GXPixModeSync();
+
+	// Copy screen and filter down.
+	GXSetCopyFilter( GX_TRUE, sample_pattern, GX_TRUE, GXNtsc480IntAa.vfilter );
+	GXSetTexCopySrc(0, 0, 640, 448);
+	GXSetTexCopyDst(320, 224, GX_TF_RGBA8, GX_TRUE);
+	GXCopyTex(focusTextureData, GX_FALSE);
+	GXPixModeSync();
+
+	// Shrink down the focus texture.
+	GXTexObj	focusTexture;
+	GXInitTexObj(
+		&focusTexture,
+		focusTextureData,
+		320,
+		224,
+		GX_TF_RGBA8,
+		GX_CLAMP,
+		GX_CLAMP,
+		GX_FALSE );
+	GXInitTexObjLOD(&focusTexture, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj( &focusTexture, GX_TEXMAP0 );
+
+	GX::SetNumChans(0);
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	GX::SetNumTevStages( 1 );
+	GX::SetNumTexGens( 1 );
+	GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+	GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );		// Replace
+
+	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+
+	GXSetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+	GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+		GXPosition3f32(0.0f, 0.0f, -1.0f);
+		GXTexCoord2f32(0.0f, 0.0f);
+		GXPosition3f32(320.0f, 0.0f, -1.0f);
+		GXTexCoord2f32(1.0f, 0.0f);
+		GXPosition3f32(320.0f, 224.0f, -1.0f);
+		GXTexCoord2f32(1.0f, 1.0f);
+		GXPosition3f32(0.0f, 224.0f, -1.0f);
+		GXTexCoord2f32(0.0f, 1.0f);
+	GXEnd();
+
+	GXSetCopyFilter( GX_TRUE, sample_pattern, GX_TRUE, GXNtsc480IntAa.vfilter );
+	GXSetTexCopySrc(0, 0, 320, 224);
+	GXSetTexCopyDst(160, 112, GX_TF_RGBA8, GX_TRUE);
+	GXCopyTex(focusTextureData, GX_FALSE);
+	GXPixModeSync();
+
+	// Draw top-corner screen area we wrote over.
+	GXTexObj	screenTexture;
+	GXInitTexObj(
+		&screenTexture,
+		screenTextureData,
+		320,
+		224,
+		GX_TF_RGBA8,
+		GX_CLAMP,
+		GX_CLAMP,
+		GX_FALSE );
+	GXInitTexObjLOD(&screenTexture, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj( &screenTexture, GX_TEXMAP0 );
+
+	GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+		GXPosition3f32(0.0f, 0.0f, -1.0f);
+		GXTexCoord2f32(0.0f, 0.0f);
+		GXPosition3f32(320.0f, 0.0f, -1.0f);
+		GXTexCoord2f32(1.0f, 0.0f);
+		GXPosition3f32(320.0f, 224.0f, -1.0f);
+		GXTexCoord2f32(1.0f, 1.0f);
+		GXPosition3f32(0.0f, 224.0f, -1.0f);
+		GXTexCoord2f32(0.0f, 1.0f);
+	GXEnd();
+
+	// Setup z textures
+	GXTexObj	zTextureH;
+	GXTexObj	zTextureL;
+	GXTlutObj	zPalH;
+	GXTlutObj	zPalL;
+
+	GXInitTlutObj( &zPalH, &zPalette8H, GX_TL_IA8, 256 );
+	GXLoadTlut ( &zPalH, GX_TLUT0 );
+
+	GXInitTlutObj( &zPalL, &zPalette8L, GX_TL_IA8, 256 );
+	GXLoadTlut ( &zPalL, GX_TLUT1 );
+
+	GXInitTexObjCI(
+		&zTextureH,
+		zTextureDataH,
+		320,
+		224,
+		GX_TF_C8,
+		GX_CLAMP,
+		GX_CLAMP,
+		GX_FALSE,
+		GX_TLUT0 );
+	GXInitTexObjLOD(&zTextureH, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj( &zTextureH, GX_TEXMAP0 );
+
+	GXInitTexObjCI(
+		&zTextureL,
+		zTextureDataL,
+		320,
+		224,
+		GX_TF_C8,
+		GX_CLAMP,
+		GX_CLAMP,
+		GX_FALSE,
+		GX_TLUT1 );
+	GXInitTexObjLOD(&zTextureL, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj( &zTextureL, GX_TEXMAP1 );
+
+	GXInitTexObj(
+		&focusTexture,
+		focusTextureData,
+		160,
+		112,
+		GX_TF_RGBA8,
+		GX_CLAMP,
+		GX_CLAMP,
+		GX_FALSE );
+	GXInitTexObjLOD(&focusTexture, GX_LINEAR, GX_LINEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+	GXLoadTexObj( &focusTexture, GX_TEXMAP2 );
+
+
+
+
+
+
+
+
+
+//	GXTexObj	zTexture;
+//	GXTlutObj	zPal;
+//
+//	GXInitTlutObj( &zPal, &zPalette16, GX_TL_IA8, 65535 );
+//	GXLoadTlut ( &zPal, GX_TLUT0 );
+//
+//	GXInitTexObjCI(
+//		&zTexture,
+//		zTextureData,
+//		320,
+//		224,
+//		GX_TF_C14X2,
+//		GX_CLAMP,
+//		GX_CLAMP,
+//		GX_FALSE,
+//		GX_TLUT0 );
+//	GXInitTexObjLOD(&zTexture, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+//	GXLoadTexObj( &zTexture, GX_TEXMAP0 );
+
+	GX::SetTexChanTevIndCull( 3, 0, 3, 0, GX_CULL_NONE ); 
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetTexCoordGen( GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetTexCoordGen( GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+
+//	GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE ); // Replace alpha
+//	GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );		// Replace
+	GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );		// Blend
+
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_TEXA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVREG1 );
+//	GX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVREG1 );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE1, GX_CA_APREV, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE1, GX_CC_CPREV, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG0 );
+//	GX::SetTevColorOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG0 );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE2, GX_CA_A1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE2, GX_CC_C1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//
+//	GX::SetTevKColorSel( GX_TEVSTAGE3, GX_TEV_KCSEL_K0_A );
+//	GX::SetTevKColor( GX_KCOLOR0, (GXColor){16,16,16,16} );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE3, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG1 );
+//	GX::SetTevColorOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG1 );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE3, GX_CA_ZERO, GX_CA_KONST, GX_CA_TEXA, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE3, GX_CC_ZERO, GX_CC_KONST, GX_CC_TEXC, GX_CC_ZERO );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE4, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE4, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE4, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE4, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE4, GX_CA_A0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A1 );
+//	GX::SetTevColorIn ( GX_TEVSTAGE4, GX_CC_C0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C1 );
+
+
+
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG0 );
+//	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVREG0 );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA );
+//	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+////	GX::SetTevAlphaIn ( GX_TEVSTAGE1, GX_CA_A0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA );
+////	GX::SetTevColorIn ( GX_TEVSTAGE1, GX_CC_C0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC );
+////	GX::SetTevAlphaIn ( GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA );
+////	GX::SetTevColorIn ( GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE1, GX_CA_A0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE1, GX_CC_C0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+
+
+
+
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ONE, GX_CC_ZERO, GX_CC_TEXC, GX_CC_ZERO );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVREG1 );
+//	GX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_DISABLE, GX_TEVREG1 );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE1, GX_CC_CPREV, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG0 );
+//	GX::SetTevColorOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG0 );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE2, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE2, GX_CC_C1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//
+//	GX::SetTevKColorSel( GX_TEVSTAGE3, GX_TEV_KCSEL_K0_A );
+//	GX::SetTevKColor( GX_KCOLOR0, (GXColor){16,16,16,16} );
+//
+//	GX::SetTevOrder(GX_TEVSTAGE3, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE3, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE3, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE3, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE3, GX_CC_ONE, GX_CC_ZERO, GX_CC_TEXC, GX_CC_C0 );
+
+
+
+
+	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD1, GX_TEXMAP1, GX_COLOR_NULL);
+	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG0 );
+	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG0 );
+	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_TEXA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+
+	GX::SetTevSwapMode( GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+	GX::SetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG1 );
+	GX::SetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVREG1 );
+	GX::SetTevAlphaIn ( GX_TEVSTAGE1, GX_CA_TEXA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0 );
+	GX::SetTevColorIn ( GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+
+	GX::SetTevOrder(GX_TEVSTAGE2, GX_TEXCOORD2, GX_TEXMAP2, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+	GX::SetTevSwapMode( GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+	GX::SetTevAlphaOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+	GX::SetTevColorOp(GX_TEVSTAGE2, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+	GX::SetTevAlphaIn ( GX_TEVSTAGE2, GX_CA_A1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+	GX::SetTevColorIn ( GX_TEVSTAGE2, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE2, GX_CA_A1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE2, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+
+
+
+//    GXSetTevSwapModeTable( GX_TEV_SWAP1, GX_CH_RED, GX_CH_RED, GX_CH_RED, GX_CH_RED );
+//    GXSetTevSwapModeTable( GX_TEV_SWAP2, GX_CH_GREEN, GX_CH_GREEN, GX_CH_GREEN, GX_CH_GREEN );
+//    GXSetTevSwapModeTable( GX_TEV_SWAP3, GX_CH_BLUE, GX_CH_BLUE, GX_CH_BLUE, GX_CH_BLUE );
+
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
+//	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+
+
+
+//	if ( gFocus )
+//	{
+//		// Set current vertex descriptor to enable position and color0.
+//		// Both use 8b index to access their data arrays.
+//		GXSetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+//
+//		GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//			GXPosition3f32(0.0f, 0.0f, -1.0f);
+//			GXTexCoord2f32(0.0f, 0.0f);
+//			GXPosition3f32(640.0f, 0.0f, -1.0f);
+//			GXTexCoord2f32(1.0f, 0.0f);
+//			GXPosition3f32(640.0f, 448.0f, -1.0f);
+//			GXTexCoord2f32(1.0f, 1.0f);
+//			GXPosition3f32(0.0f, 448.0f, -1.0f);
+//			GXTexCoord2f32(0.0f, 1.0f);
+//		GXEnd();
+//		
+//	}
+#endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+
+	GX::SetZMode ( GX_FALSE, GX_ALWAYS, GX_FALSE );
+	NxNgc::set_blend_mode( NxNgc::vBLEND_MODE_ADD );
+	NxNgc::SDraw2D::DrawAll();
+
+//	if ( NsDisplay::shouldReset() && NxNgc::EngineGlobals.disableReset )
+//	{
+//		GX::SetNumTevStages(1);
+//		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//		GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//		GX::SetNumTexGens( 0 );
+//		GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//		GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//		GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//		GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+//		GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+//		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//		GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){0,0,0,255} );
+//		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//
+//		GXSetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//
+//		GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//			GXPosition3f32(128.0f, 224.0f - 64.0f, -1.0f);
+//			GXPosition3f32(640.0f - 128.0f, 224.0f - 64.0f, -1.0f);
+//			GXPosition3f32(640.0f - 128.0f, 224.0f + 64.0f, -1.0f);
+//			GXPosition3f32(128.0f, 224.0f + 64.0f, -1.0f);
+//		GXEnd();
+//
+//		Script::RunScript( "ngc_reset" );
+//	}
+
+//	// Set up shadow map texture
+//	GXTexObj shadowTexture;
+//	GXInitTexObj(
+//		&shadowTexture,
+//		shadowTextureData,
+//		SHADOW_TEXTURE_SIZE,
+//		SHADOW_TEXTURE_SIZE,
+//		GX_TF_RGBA8,
+//		GX_CLAMP,
+//		GX_CLAMP,
+//		GX_FALSE );
+//	GXInitTexObjLOD(&shadowTexture, GX_NEAR, GX_NEAR, 0, 0, 0, 0, 0, GX_ANISO_1);
+//	GXLoadTexObj( &shadowTexture, GX_TEXMAP0 );
+//
+//    GXSetNumChans(1);
+//	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX3x4, GX_TG_TEX0, GX_IDENTITY );
+//
+//    GX::SetNumTevStages(1);
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+//	GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetNumTexGens( 1 );
+//	GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//	GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//	GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//	GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+//	GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO );
+//	GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO );
+//	GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//	GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+//	GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//
+//    Mtx mv;
+//	MTXIdentity( mv );
+//    GX::LoadTexMtxImm(mv, GX_TEXMTX0, GX_MTX3x4);
+//    GX::LoadTexMtxImm(mv, GX_TEXMTX1, GX_MTX3x4);
+//
+//	// Set current vertex descriptor to enable position and color0.
+//	// Both use 8b index to access their data arrays.
+//	GXSetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+//
+//	// Send coordinates.
+//	GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//		GXPosition3f32(320, 32, -1.0f);
+//		GXTexCoord2f32(0.0f, 0.0f);
+//		GXPosition3f32(576, 32, -1.0f);
+//		GXTexCoord2f32(1.0f, 0.0f);
+//		GXPosition3f32(576, 288, -1.0f);
+//		GXTexCoord2f32(1.0f, 1.0f);
+//		GXPosition3f32(320, 288, -1.0f);
+//		GXTexCoord2f32(0.0f, 1.0f);
+//	GXEnd();
+
+
+	// Blur.
+//	if ( sBlur > 0 )
+//	{
+//		
+//	}
+//
+//sBlurBuffer
+
+	
+	
+	
+	
+	
+	
+	
+//	GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, 64, 64 );
+//	
+//	GX::SetTexChanTevIndCull( 1, 0, 1, 0, GX_CULL_NONE );
+//	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+//	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+//	
+//	GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+//	GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );		// Replace
+//	
+//	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP7, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//	GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+//							 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+//							 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//	GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+//						 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+//	
+//	GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//	GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//	GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//	
+//	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+//	
+//	GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//		GX::Position3f32(64.0f, 64.0f, -1.0f);
+//		GX::TexCoord2f32(0.0f, 0.0f);
+//		GX::Position3f32(64.0f + 64.0f, 64.0f, -1.0f);
+//		GX::TexCoord2f32(1.0f, 0.0f);
+//		GX::Position3f32(64.0f + 64.0f, 64.0f + 64.0f, -1.0f);
+//		GX::TexCoord2f32(1.0f, 1.0f);
+//		GX::Position3f32(64.0f, 64.0f + 64.0f, -1.0f);
+//		GX::TexCoord2f32(0.0f, 1.0f);
+//	GX::End();
+//	
+	
+	
+	
+	
+	
+	
+	
+	cam.end();
+	NsPrim::end();
+	NsRender::end();
+
+//	if ( movies )
+//	{
+//		NsDisplay::end( false );
+//	}
+//	else
+//	{
+		//NsDisplay::end( true );
+//	}
+	NsBuffer::end();
+
+	NxNgc::EngineGlobals.gpuBusy = false;
+
+	NxNgc::EngineGlobals.frameCount = ( NxNgc::EngineGlobals.frameCount + 1 ) & 1;
+
+	NsDisplay::end(true);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene	*	CEngine::s_plat_create_scene(const char *p_name, CTexDict *p_tex_dict, bool add_super_sectors)
+{
+	// Create scene class instance
+	CNgcScene	*p_Ngc_scene	= new CNgcScene;
+	CScene		*p_new_scene	= p_Ngc_scene;
+	p_new_scene->SetInSuperSectors( add_super_sectors );
+	p_new_scene->SetIsSky( false );
+
+	// Create a new sScene so the engine can track assets for this scene.
+	NxNgc::sScene *p_engine_scene = new NxNgc::sScene();
+	p_Ngc_scene->SetEngineScene( p_engine_scene );
+
+	return p_new_scene;
+}
+
+#define MemoryRead( dst, size, num, src )	memcpy(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int s_plat_scene_size( void *p_mem, CTexDict *p_tex_dict, bool add_super_sectors, bool is_sky, bool is_dictionary )
+{
+	void * p_start = p_mem;
+
+	// Setup pointer to actual data.
+	NxNgc::sSceneHeader * p_scene_header = (NxNgc::sSceneHeader *)p_mem;
+	unsigned int * p32 = (unsigned int *)&p_scene_header[1];
+
+	// Setup material DL pointer.
+	NxNgc::sMaterialDL * blend_dl = (NxNgc::sMaterialDL *)p32; 
+	NxNgc::sTextureDL * tex_dl = (NxNgc::sTextureDL *)&blend_dl[p_scene_header->m_num_blend_dls];
+	unsigned char * p8 = (unsigned char *)&tex_dl[p_scene_header->m_num_materials];
+
+	int bytes = ( p_scene_header->m_num_blend_dls * sizeof( NxNgc::sMaterialDL ) ) + ( p_scene_header->m_num_materials * sizeof( NxNgc::sTextureDL ) );
+	int rounded_bytes = ( bytes + 31 ) & ~31;
+	int pad_bytes = rounded_bytes - bytes;
+
+	p8 = &p8[pad_bytes];
+
+	// Assign blend dl pointers.
+	for ( uint bdl = 0; bdl < p_scene_header->m_num_blend_dls; bdl++ )
+	{
+		p8 += blend_dl[bdl].m_dl_size;
+	}
+
+	// Assign texture dl pointers.
+	for ( uint tdl = 0; tdl < p_scene_header->m_num_materials; tdl++ )
+	{
+		p8 += tex_dl[tdl].m_dl_size;
+	}
+	p32 = (unsigned int *)p8;
+
+	// Point past pool data.
+	NxNgc::sObjectHeader* p_data = (NxNgc::sObjectHeader*)&((char*)p32)[p_scene_header->m_num_pool_bytes];
+
+	// Setup VC wibble data.
+	NxNgc::sMaterialVCWibbleKeyHeader *	p_key_header	= (NxNgc::sMaterialVCWibbleKeyHeader *)p32; 
+	NxNgc::sMaterialVCWibbleKey *		p_key			= (NxNgc::sMaterialVCWibbleKey *)&p_key_header[1]; 
+	for ( int vc = 0; vc < p_scene_header->m_num_vc_wibbles; vc++ )
+	{
+		p_key_header = (NxNgc::sMaterialVCWibbleKeyHeader *)&p_key[p_key_header->m_num_frames]; 
+		p_key = (NxNgc::sMaterialVCWibbleKey *)&p_key_header[1];  
+	}
+	p32 = (uint32 *)p_key_header;
+
+	// Setup material header data.
+	NxNgc::sMaterialHeader *material_header = (NxNgc::sMaterialHeader *)p32;
+	p32 = (uint32 *)&material_header[p_scene_header->m_num_materials];
+
+	// Setup UV wibble data.
+	NxNgc::sMaterialUVWibble *uv_wibble = (NxNgc::sMaterialUVWibble *)p32;
+	p32 = (uint32 *)&uv_wibble[p_scene_header->m_num_uv_wibbles];
+
+	// Setup material pass data.
+	NxNgc::sMaterialPassHeader *material_pass = (NxNgc::sMaterialPassHeader *)p32;
+	p32 = (uint32 *)&material_pass[p_scene_header->m_num_pass_items];
+
+	if ( p_scene_header->m_num_pos )
+	{
+		p32 = &((unsigned int *)p32)[p_scene_header->m_num_pos*3];
+	}
+
+	if ( p_scene_header->m_num_nrm )
+	{
+		p32 = &((unsigned int *)p32)[p_scene_header->m_num_nrm*3];
+	}
+
+	if ( p_scene_header->m_num_col )
+	{
+		p32 = &((unsigned int *)p32)[p_scene_header->m_num_col];
+	}
+
+	if ( p_scene_header->m_num_tex )
+	{
+		p32 = &((unsigned int *)p32)[p_scene_header->m_num_tex*2];
+	}
+
+	if ( p_scene_header->m_num_shadow_faces )
+	{
+		p32 = (unsigned int *)(&((unsigned short *)p32)[p_scene_header->m_num_shadow_faces*3]);
+		p32 = (unsigned int *)(&((NxNgc::sShadowEdge *)p32)[p_scene_header->m_num_shadow_faces]);
+	}
+
+	for( uint s = 0; s < p_scene_header->m_num_objects; ++s )
+	{
+		int num_mesh = p_data->m_num_meshes;
+
+		char * p_skin = (char *)&p_data[1];
+		int nbytes = p_data->m_skin.num_bytes;
+		NxNgc::sDLHeader* p_dl = (NxNgc::sDLHeader*)&p_skin[nbytes];
+
+		for( int m = 0; m < num_mesh; ++m )
+		{
+			if ( p_dl->m_size )
+			{
+				p_dl = (NxNgc::sDLHeader*)&(((char *)&p_dl[1])[p_dl->m_size]);
+			}
+		}
+
+		p_data = (NxNgc::sObjectHeader*)p_dl;
+	}
+
+	// Point up hierarchy.
+	uint32 * p_h = (uint32 *)p_data;
+	uint32 num_hobj = *p_h++;
+
+	CHierarchyObject * p_end = (CHierarchyObject *)p_h;
+
+	p_end = &p_end[num_hobj];
+
+	return (int)p_end - (int)p_start;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static CScene * s_plat_load_scene_guts( void *p_mem, CTexDict *p_tex_dict, bool add_super_sectors, bool is_sky, bool is_dictionary )
+{
+	if ( s_correctable ) gCorrectColor = true;
+
+	CSector*		pSector;
+	CNgcSector*	pNgcSector;
+
+	// Create scene class instance.
+	CScene	*new_scene;
+	if( add_super_sectors )
+	{
+		// Use default size sector table.
+		new_scene = new CNgcScene();
+	}
+	else
+	{
+		// Use minimum size sector table.
+		new_scene = new CNgcScene( 1 );
+	}
+
+	new_scene->SetInSuperSectors( add_super_sectors );
+	new_scene->SetIsSky( is_sky );
+
+	// Get a scene id from the engine.
+	CNgcScene *p_new_ngc_scene = static_cast( new_scene );
+
+	// Create a new sScene so the engine can track assets for this scene.
+	NxNgc::sScene *p_engine_scene = new NxNgc::sScene();
+	p_new_ngc_scene->SetEngineScene( p_engine_scene );
+
+	// Set the dictionary flag.
+	p_engine_scene->m_is_dictionary	= is_dictionary;
+
+
+
+//------------------------------------------------------------------------------------
+
+	// Setup pointer to actual data.
+	p_engine_scene->mp_scene_data = (NxNgc::sSceneHeader *)p_mem;
+	unsigned int * p32 = (unsigned int *)&p_engine_scene->mp_scene_data[1];
+
+	// Setup material DL pointer.
+	Dbg_MsgAssert( p_engine_scene->mp_scene_data->m_num_blend_dls < 256, ( "Too many (>255) unique material DLs (%d)\n", p_engine_scene->mp_scene_data->m_num_blend_dls ) );
+	p_engine_scene->mp_blend_dl = (NxNgc::sMaterialDL *)p32; 
+	p_engine_scene->mp_texture_dl = (NxNgc::sTextureDL *)&p_engine_scene->mp_blend_dl[p_engine_scene->mp_scene_data->m_num_blend_dls];
+	unsigned char * p8 = (unsigned char *)&p_engine_scene->mp_texture_dl[p_engine_scene->mp_scene_data->m_num_materials];
+
+	int bytes = ( p_engine_scene->mp_scene_data->m_num_blend_dls * sizeof( NxNgc::sMaterialDL ) ) + ( p_engine_scene->mp_scene_data->m_num_materials * sizeof( NxNgc::sTextureDL ) );
+	int rounded_bytes = ( bytes + 31 ) & ~31;
+	int pad_bytes = rounded_bytes - bytes;
+
+	p8 = &p8[pad_bytes];
+
+	// Assign blend dl pointers.
+	for ( uint bdl = 0; bdl < p_engine_scene->mp_scene_data->m_num_blend_dls; bdl++ )
+	{
+		p_engine_scene->mp_blend_dl[bdl].mp_dl = p8;
+		p8 += p_engine_scene->mp_blend_dl[bdl].m_dl_size;
+	}
+
+	// Assign texture dl pointers.
+	for ( uint tdl = 0; tdl < p_engine_scene->mp_scene_data->m_num_materials; tdl++ )
+	{
+		p_engine_scene->mp_texture_dl[tdl].mp_dl = p8;
+		p8 += p_engine_scene->mp_texture_dl[tdl].m_dl_size;
+	}
+	p32 = (unsigned int *)p8;
+
+	// Point past pool data.
+	NxNgc::sObjectHeader* p_data = (NxNgc::sObjectHeader*)&((char*)p32)[p_engine_scene->mp_scene_data->m_num_pool_bytes];
+
+	// Setup VC wibble data.
+	p_engine_scene->mp_vc_wibble = (NxNgc::sMaterialVCWibbleKeyHeader *)p32;
+	NxNgc::sMaterialVCWibbleKeyHeader *	p_key_header	= p_engine_scene->mp_vc_wibble;
+	NxNgc::sMaterialVCWibbleKey *		p_key			= (NxNgc::sMaterialVCWibbleKey *)&p_key_header[1]; 
+	for ( int vc = 0; vc < p_engine_scene->mp_scene_data->m_num_vc_wibbles; vc++ )
+	{
+		p_key_header = (NxNgc::sMaterialVCWibbleKeyHeader *)&p_key[p_key_header->m_num_frames]; 
+		p_key = (NxNgc::sMaterialVCWibbleKey *)&p_key_header[1];  
+	}
+	p32 = (uint32 *)p_key_header;
+
+	// Setup material header data.
+	p_engine_scene->mp_material_header = (NxNgc::sMaterialHeader *)p32;
+	p32 = (uint32 *)&p_engine_scene->mp_material_header[p_engine_scene->mp_scene_data->m_num_materials];
+
+	// Setup UV wibble data.
+	p_engine_scene->mp_uv_wibble = (NxNgc::sMaterialUVWibble *)p32;
+	p32 = (uint32 *)&p_engine_scene->mp_uv_wibble[p_engine_scene->mp_scene_data->m_num_uv_wibbles];
+
+	// Setup material pass data.
+	p_engine_scene->mp_material_pass = (NxNgc::sMaterialPassHeader *)p32;
+	p32 = (uint32 *)&p_engine_scene->mp_material_pass[p_engine_scene->mp_scene_data->m_num_pass_items];
+
+	// Setup material data texture pointers.
+	NxNgc::sMaterialHeader * p_mat = p_engine_scene->mp_material_header;
+	for ( unsigned int lp = 0; lp < p_engine_scene->mp_scene_data->m_num_materials; lp++ )
+	{
+		p_mat->m_alpha_cutoff = p_mat->m_alpha_cutoff >= 128 ? 255 : p_mat->m_alpha_cutoff * 2;
+		// Need to compensate for possible errors in alpha map.
+//		int cut = (int)p_mat->m_alpha_cutoff - 16;
+//		if ( cut < 0 ) cut = 0;
+//		p_mat->m_alpha_cutoff = cut;
+
+		// Setup texture pointer.
+		NxNgc::sMaterialPassHeader * p_pass = &p_engine_scene->mp_material_pass[p_mat->m_pass_item];
+
+		// Copy base pass blend mode to material.
+		p_mat->m_base_blend = p_pass->m_blend_mode;
+
+		for ( int pass = 0; pass < p_mat->m_passes; pass++ )
+		{
+			// If textured, resolve texture checksum...
+			Nx::CNgcTexture *p_ngc_texture = static_cast( p_tex_dict->GetTexLookup()->GetItem( p_pass->m_texture.checksum ) );
+			p_pass->m_texture.p_data = ( p_ngc_texture ) ? p_ngc_texture->GetEngineTexture() : NULL;
+
+			// Temp HACK
+//			p_pass->m_k = (1<<8);
+//			p_pass->m_u_tile = (1<<12);
+//			p_pass->m_v_tile = (1<<12);
+//			p_pass->m_uv_enabled = 0;
+//			p_pass->m_uv_mat[0] = (1<<12);
+//			p_pass->m_uv_mat[1] = 0;
+//			p_pass->m_uv_mat[2] = 0;
+//			p_pass->m_uv_mat[3] = 0;
+
+
+
+
+
+
+
+//			if ( p_pass->m_texture.checksum && !p_ngc_texture )
+//			{
+//				OSReport( "Failed to hook up texture!!!!!!!!!!! %08x\n", p_pass->m_texture.checksum );
+//			}
+
+//			// Adjust K value.
+//			p_pass->m_mip_k += 8;
+
+			// Switch color values.
+//			u8 r;
+//			u8 g;
+//			u8 b;
+//			u8 a;
+//			r = p_pass->m_color.r;
+//			g = p_pass->m_color.g;
+//			b = p_pass->m_color.b;
+//			a = p_pass->m_color.a;
+//
+//			p_pass->m_color.r = b;
+//			p_pass->m_color.g = g;
+//			p_pass->m_color.b = r;
+//			p_pass->m_color.a = a;
+
+//			p_pass->m_color.a = 255;
+
+//			p_pass->m_alpha_fix = ( p_pass->m_alpha_fix ) >= 128 ? 255 : p_pass->m_alpha_fix * 2;
+
+			// Next pass structure.
+			++p_pass;
+		}
+
+		// Scan texture DL and patch texture address pointers.
+		// Search for SetMode0/1 SetImage0/1/2/3.
+		NxNgc::sTextureDL * p_dl = &p_engine_scene->mp_texture_dl[lp]; 
+		p_pass = &p_engine_scene->mp_material_pass[p_mat->m_pass_item];
+
+//		int search_size = ( p_dl->m_dl_size > ((5*5)+4) ) ? p_dl->m_dl_size - ((5*5)+4) : 0;
+
+//		for ( int s = 0; s < search_size ; s++ )
+		//for ( int s = 0; s < (int)p_dl->m_dl_size ; s++ )
+
+		GX::ResolveDLTexAddr( p_dl, p_pass, p_mat->m_passes );
+
+//		GX::begin( p_dl->mp_dl, p_dl->m_dl_size );
+//		multi_mesh( p_mat, p_pass, true, true );
+//		p_dl->m_dl_size = GX::end();
+
+		++p_mat;
+	}
+
+	// Setup vertex data.
+	p_engine_scene->mp_pos_pool	= NULL;
+	p_engine_scene->mp_nrm_pool	= NULL;
+	p_engine_scene->mp_col_pool	= NULL;
+	p_engine_scene->mp_tex_pool	= NULL;
+
+	if ( p_engine_scene->mp_scene_data->m_num_pos )
+	{
+		p_engine_scene->mp_pos_pool = (float *)p32;
+		p32 = &((unsigned int *)p32)[p_engine_scene->mp_scene_data->m_num_pos*3];
+	}
+
+	if ( p_engine_scene->mp_scene_data->m_num_col )
+	{
+		p_engine_scene->mp_col_pool = (unsigned int *)p32;
+		p32 = &((unsigned int *)p32)[p_engine_scene->mp_scene_data->m_num_col];
+	}
+
+	if ( p_engine_scene->mp_scene_data->m_num_tex )
+	{
+		p_engine_scene->mp_tex_pool = (s16 *)p32;
+		p32 = (unsigned int *)&p_engine_scene->mp_tex_pool[p_engine_scene->mp_scene_data->m_num_tex*2];
+	}
+
+	if ( p_engine_scene->mp_scene_data->m_num_nrm )
+	{
+		p_engine_scene->mp_nrm_pool = (s16 *)p32;
+		p32 = (unsigned int*)&p_engine_scene->mp_nrm_pool[p_engine_scene->mp_scene_data->m_num_nrm*3];
+	}
+
+	if ( p_engine_scene->mp_scene_data->m_num_shadow_faces )
+	{
+		p_engine_scene->mp_shadow_volume_mesh = (uint16*)p32;
+		p32 = (unsigned int *)(&((unsigned short *)p32)[p_engine_scene->mp_scene_data->m_num_shadow_faces*3]);
+		p_engine_scene->mp_shadow_edge = (NxNgc::sShadowEdge *)p32; 
+		p32 = (unsigned int *)(&((NxNgc::sShadowEdge *)p32)[p_engine_scene->mp_scene_data->m_num_shadow_faces]);
+	}
+
+	// Calculate radius.
+	p_engine_scene->m_sphere[X] = 0.0f;
+	p_engine_scene->m_sphere[Y] = 0.0f;
+	p_engine_scene->m_sphere[Z] = 0.0f;
+	p_engine_scene->m_sphere[W] = 0.0f;
+
+	for ( uint32 lp = 0; lp < p_engine_scene->mp_scene_data->m_num_pos; lp++ )
+	{
+		NsVector v;
+		v.x = p_engine_scene->mp_pos_pool[(lp*3)+0];
+		v.y = p_engine_scene->mp_pos_pool[(lp*3)+1];
+		v.z = p_engine_scene->mp_pos_pool[(lp*3)+2];
+		float l = v.length();
+
+		if ( l > p_engine_scene->m_sphere[W] ) p_engine_scene->m_sphere[W] = l;
+	}
+
+	// Setup display list pointer.
+	p_engine_scene->mp_dl = (NxNgc::sDLHeader *)&(((char*)&p_data[1])[p_data->m_skin.num_bytes]);
+
+	for( uint s = 0; s < p_engine_scene->mp_scene_data->m_num_objects; ++s )
+	{
+		// Create a new sector to hold the incoming details.
+		pSector						= p_new_ngc_scene->CreateSector();
+		pNgcSector					= static_cast( pSector );
+
+		// Generate a hanging geom for the sector, used for creating level objects etc.
+		CNgcGeom	*p_Ngc_geom	= new CNgcGeom();
+		p_Ngc_geom->SetScene( p_new_ngc_scene );
+		pNgcSector->SetGeom( p_Ngc_geom );
+
+		// Prepare CNgcGeom for receiving data.
+		p_Ngc_geom->InitMeshList();
+
+		// Load sector data.
+		p_data = pNgcSector->LoadFromFile( p_data );
+		new_scene->AddSector( pSector );
+
+//		if( ( p_data = pNgcSector->LoadFromFile( p_file, p_data ) ) )
+//		{
+//			new_scene->AddSector( pSector );
+//		}
+	}
+
+	// At this point we can process any scaling that may need to be applied to the positions.
+	if ( p_engine_scene->mp_dl->mp_object_header->m_skin.p_data )
+	{
+		NxNgc::ApplyMeshScaling( p_engine_scene->mp_dl->mp_object_header );
+	}
+
+	// Point up hierarchy.
+	uint32 * p_h = (uint32 *)p_data;
+	uint32 num_hobj = *p_h++;
+
+	if ( num_hobj )
+	{
+		p_engine_scene->mp_hierarchyObjects = (CHierarchyObject *)p_h;
+		p_engine_scene->m_numHierarchyObjects = num_hobj;
+		//p32 = (uint32 *)&p_engine_scene->mp_hierarchyObjects[num_hobj];
+
+//		p_engine_scene->mp_hierarchyObjects = new CHierarchyObject[num_hobj];
+//
+//		File::Read( p_engine_scene->mp_hierarchyObjects, sizeof( CHierarchyObject ), num_hobj, p_file );
+//
+//		p_engine_scene->m_numHierarchyObjects = num_hobj;
+
+		// Fix up hierarchical object sphere.
+		Lst::HashTable< Nx::CSector > *p_sector_list = new_scene->GetSectorList();
+		if( p_sector_list )
+		{
+			p_sector_list->IterateStart();	
+			Nx::CSector *p_sector = p_sector_list->IterateNext();
+			while( p_sector )
+			{
+				pNgcSector = static_cast( p_sector );
+				CNgcGeom *p_Ngc_geom = static_cast( pNgcSector->GetGeom() );
+
+				Lst::Head< NxNgc::sMesh >	*p_mesh_list = p_Ngc_geom->GetMeshList();
+				int num_mesh = p_mesh_list->CountItems();
+				if (num_mesh)
+				{
+					Lst::Node< NxNgc::sMesh > *mesh = p_mesh_list->GetNext();
+					mesh->GetData()->mp_dl->mp_object_header->m_sphere[W] *= 2.0f;  
+				}
+				p_sector = p_sector_list->IterateNext();
+			}
+		}
+	}
+
+	if ( s_correctable ) gCorrectColor = false;
+
+	return new_scene;
+
+//------------------------------------------------------------------------------------
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//	// Open the scene file.
+//	void* p_file = File::Open( p_name, "rb" );
+//	if( !p_file )
+//	{
+//		Dbg_MsgAssert( p_file, ( "Couldn't open scene file %s\n", p_name ));
+//		if ( s_correctable ) gCorrectColor = false;
+//		return NULL;
+//	}
+//	
+//	// Version numbers.
+//	uint32 mat_version, mesh_version, vert_version;
+//	File::Read( &mat_version, sizeof( uint32 ), 1, p_file );
+//	File::Read( &mesh_version, sizeof( uint32 ), 1, p_file );
+//	File::Read( &vert_version, sizeof( uint32 ), 1, p_file );
+//	Dbg_Message( "material version %d\n", mat_version );
+//	Dbg_Message( "mesh version %d\n", mesh_version );
+//	Dbg_Message( "vertex version %d\n", vert_version );
+//
+//	// Import materials (they will now be associated at the engine-level with this scene).
+//	p_engine_scene->mp_material_array = NxNgc::LoadMaterials( p_file, p_tex_dict->GetTexLookup(), &p_engine_scene->m_num_materials );
+//
+//	// Read number of sectors.
+//	int num_sectors;
+//	File::Read( &num_sectors, sizeof( int ), 1, p_file );
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//	NxNgc::sDLHeader* p_data = (NxNgc::sDLHeader*)p_engine_scene->mp_dl;
+//
+//	for( int s = 0; s < num_sectors; ++s )
+//	{
+//		// Create a new sector to hold the incoming details.
+//		pSector						= p_new_ngc_scene->CreateSector();
+//		pNgcSector					= static_cast( pSector );
+//		
+//		// Generate a hanging geom for the sector, used for creating level objects etc.
+//		CNgcGeom	*p_Ngc_geom	= new CNgcGeom();
+//		p_Ngc_geom->SetScene( p_new_ngc_scene );
+//		pNgcSector->SetGeom( p_Ngc_geom );
+//		
+//		// Prepare CNgcGeom for receiving data.
+//		p_Ngc_geom->InitMeshList();
+//		
+//		// Load sector data.
+//		p_data = pNgcSector->LoadFromFile( p_file, p_data );
+//		new_scene->AddSector( pSector );
+//
+////		if( ( p_data = pNgcSector->LoadFromFile( p_file, p_data ) ) )
+////		{
+////			new_scene->AddSector( pSector );
+////		}
+//	}
+//
+//	// Load hierarchy.
+//	uint32 num_hobj;
+//	File::Read( &num_hobj, sizeof( uint32 ), 1, p_file );
+//
+//	if ( num_hobj )
+//	{
+//		p_engine_scene->mp_hierarchyObjects = new CHierarchyObject[num_hobj];
+//
+//		File::Read( p_engine_scene->mp_hierarchyObjects, sizeof( CHierarchyObject ), num_hobj, p_file );
+//
+//		p_engine_scene->m_numHierarchyObjects = num_hobj;
+//	}
+//
+//	// At this point get the engine scene to figure it's bounding volumes.
+//	p_engine_scene->FigureBoundingVolumes();
+//	
+//	File::Close( p_file );
+//
+//	if ( s_correctable ) gCorrectColor = false;
+//
+//	return new_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CScene *CEngine::s_plat_load_scene_from_memory( void *p_mem, CTexDict *p_tex_dict, bool add_super_sectors, bool is_sky, bool is_dictionary )
+{
+	int size = s_plat_scene_size( p_mem, p_tex_dict, add_super_sectors, is_sky, is_dictionary );
+
+	int mem_available;
+	bool need_to_pop = false;
+	if ( g_in_cutscene )
+	{
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+		mem_available = Mem::Manager::sHandle().Available();
+		if ( size < ( mem_available - ( 40 * 1024 ) ) )
+		{
+			need_to_pop = true;
+		}
+		else
+		{
+			Mem::Manager::sHandle().PopContext();
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( size < ( mem_available - ( 5 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( size < ( mem_available - ( 40 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+				}
+			}
+		}
+	}
+
+	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+	char * p_scene_data = new char[size]; 
+	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+
+	if ( need_to_pop )
+	{
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	memcpy( p_scene_data, p_mem, size );
+	DCFlushRange( p_scene_data, size );
+
+	return s_plat_load_scene_guts( p_scene_data, p_tex_dict, add_super_sectors, is_sky, is_dictionary );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CScene*	CEngine::s_plat_load_scene( const char *p_name, CTexDict *p_tex_dict, bool add_super_sectors, bool is_sky, bool is_dictionary )
+{
+	Dbg_Message( "loading scene from file %s\n", p_name );
+
+	// Open the scene file.
+	char gdname[128];
+	sprintf( gdname, "%s", p_name );
+	void* p_gd_file = File::Open( gdname, "rb" );
+	if( !p_gd_file )
+	{
+		Dbg_MsgAssert( p_gd_file, ( "Couldn't open scene file %s\n", gdname ));
+		if ( s_correctable ) gCorrectColor = false;
+		return NULL;
+	}
+
+	// Read all data.
+	int size = File::GetFileSize( p_gd_file );
+	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+	char * p_scene_data = new char[size]; 
+	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+	File::Read( p_scene_data, size, 1, p_gd_file );
+	
+	File::Close( p_gd_file );
+
+	DCFlushRange( p_scene_data, size );
+	return s_plat_load_scene_guts( p_scene_data, p_tex_dict, add_super_sectors, is_sky, is_dictionary );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_add_scene( CScene *p_scene, const char *p_filename )
+{
+	// Function to incrementally add geometry to a scene - should NOT be getting called on GameCube.
+	Dbg_Assert( 0 );
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_unload_scene( CScene *p_scene )
+{
+	Dbg_MsgAssert( p_scene,( "Trying to delete a NULL scene" ));
+
+	CNgcScene *p_ngc_scene = (CNgcScene*)p_scene;
+
+	// Ask the engine to remove the associated meshes for each sector in the scene.
+	p_ngc_scene->DestroySectorMeshes();
+
+	// Get the engine specific scene data and pass it to the engine to delete.
+	NxNgc::DeleteScene( p_ngc_scene->GetEngineScene());
+	p_ngc_scene->SetEngineScene( NULL );
+	
+	return true;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModel*		CEngine::s_plat_init_model( void )
+{
+	CNgcModel* pModel = new CNgcModel;
+
+	return pModel;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_uninit_model(CModel* pModel)
+{
+	Dbg_Assert( pModel );
+
+	delete pModel;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMesh* CEngine::s_plat_load_mesh( const char* pMeshFileName, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume )
+{
+	s_correctable = false;
+	// Load the scene.
+	Nx::CScene *p_scene = Nx::CEngine::s_plat_load_scene( pMeshFileName, pTexDict, false, false, false );
+	p_scene->SetID(Script::GenerateCRC( pMeshFileName )); 	// store the checksum of the scene name
+	p_scene->SetTexDict( pTexDict );
+	p_scene->PostLoad( pMeshFileName );
+	
+	// Disable any scaling.
+	NxNgc::DisableMeshScaling();
+	
+	CNgcMesh *pMesh = new CNgcMesh( pMeshFileName );
+	Nx::CNgcScene *p_Ngc_scene = static_cast( p_scene );
+	pMesh->SetScene( p_Ngc_scene );
+	pMesh->SetTexDict( pTexDict );
+	s_correctable = true;
+	return pMesh;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CMesh* CEngine::s_plat_load_mesh( uint32 id, uint32 *p_model_data, int model_data_size, uint8 *p_cas_data, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume )
+{
+	// Convert the id into a usable string.
+	Dbg_Assert( id > 0 );
+	char id_as_string[16];
+	sprintf( id_as_string, "%d\n", id );
+
+	// Load the scene.
+	Nx::CScene *p_scene = Nx::CEngine::s_plat_load_scene_from_memory( p_model_data, pTexDict, false, false, false );
+
+	// Store the checksum of the scene name.
+	p_scene->SetID( Script::GenerateCRC( id_as_string ));
+
+	p_scene->SetTexDict( pTexDict );
+	p_scene->PostLoad( id_as_string );
+	
+	// Disable any scaling.
+	NxNgc::DisableMeshScaling();
+	
+	CNgcMesh *pMesh = new CNgcMesh();
+
+	// Set CAS data for mesh.
+	pMesh->SetCASData( p_cas_data );
+
+	Nx::CNgcScene *p_Ngc_scene = static_cast( p_scene );
+	pMesh->SetScene( p_Ngc_scene );
+	pMesh->SetTexDict( pTexDict );
+	return pMesh;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CEngine::s_plat_unload_mesh(CMesh* pMesh)
+{
+	Dbg_Assert( pMesh );
+
+	delete pMesh;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_mesh_scaling_parameters( SMeshScalingParameters* pParams )
+{
+	NxNgc::SetMeshScalingParameters( pParams );
+}
+
+
+
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//CTexDict* CEngine::s_plat_load_textures( const char* p_name )
+//{
+////	NxNgc::LoadTextureFile( p_name );
+//	return NULL;
+//}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSprite *	CEngine::s_plat_create_sprite(CWindow2D *p_window)
+{
+	return new CNgcSprite;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_destroy_sprite( CSprite *p_sprite )
+{
+	delete p_sprite;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextured3dPoly *	CEngine::s_plat_create_textured_3d_poly()
+{
+	return new NxNgc::CNgcTextured3dPoly;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_destroy_textured_3d_poly(CTextured3dPoly *p_poly)
+{
+	delete p_poly;
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CTexture *CEngine::s_plat_create_render_target_texture( int width, int height, int depth, int z_depth )
+{
+	// Create the CNgcTexture (just a container class for the NxNgc::sTexture).
+	CNgcTexture *p_texture = new CNgcTexture();
+
+	// Create the NxNgc::sTexture.
+	NxNgc::sTexture *p_engine_texture = new NxNgc::sTexture;
+	p_texture->SetEngineTexture( p_engine_texture );
+	
+	// Set the texture as a render target.
+	p_engine_texture->SetRenderTarget( width, height, depth, z_depth );
+	
+	return p_texture;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::s_plat_project_texture_into_scene( Nx::CTexture *p_texture, Nx::CModel *p_model, Nx::CScene *p_scene )
+{
+	Nx::CNgcTexture		*p_ngc_texture	= static_cast( p_texture );
+	Nx::CNgcModel		*p_ngc_model	= static_cast( p_model );
+//	Nx::CNgcScene		*p_ngc_scene	= static_cast( p_scene );
+//	NxNgc::create_texture_projection_details( p_ngc_texture->GetEngineTexture(), p_ngc_model, p_ngc_scene->GetEngineScene());
+	NxNgc::create_texture_projection_details( p_ngc_texture->GetEngineTexture(), p_ngc_model, NULL);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::s_plat_set_projection_texture_camera( Nx::CTexture *p_texture, Gfx::Camera *p_camera )
+{
+	Nx::CNgcTexture		*p_ngc_texture	= static_cast( p_texture );
+	NsVector			pos( p_camera->GetPos()[X], p_camera->GetPos()[Y], p_camera->GetPos()[Z] );
+	NsVector			at( pos.x + p_camera->GetMatrix()[Z][X], pos.y + p_camera->GetMatrix()[Z][Y], pos.z + p_camera->GetMatrix()[Z][Z] );
+	
+	NxNgc::set_texture_projection_camera( p_ngc_texture->GetEngineTexture(), &pos, &at );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::s_plat_stop_projection_texture( Nx::CTexture *p_texture )
+{
+	Nx::CNgcTexture *p_ngc_texture = static_cast( p_texture );
+	NxNgc::destroy_texture_projection_details( p_ngc_texture->GetEngineTexture());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_add_occlusion_poly( uint32 num_verts, Mth::Vector *p_vert_array, uint32 checksum )
+{
+	if( num_verts == 4 )
+	{
+		NxNgc::AddOcclusionPoly( p_vert_array[0], p_vert_array[1], p_vert_array[2], p_vert_array[3], checksum );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_enable_occlusion_poly( uint32 checksum, bool enable )
+{
+	NxNgc::EnableOcclusionPoly( checksum, enable );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_remove_all_occlusion_polys( void )
+{
+	NxNgc::RemoveAllOcclusionPolys();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// returns true if the sphere at "center", with the "radius"
+// is visible to the current camera
+// (note, currently this is the last frame's camera on PS2)
+
+bool CEngine::s_plat_is_visible( Mth::Vector&	center, float radius  )
+{
+	Mth::Vector v;
+	v[X] = center[X];
+	v[Y] = center[Y];
+	v[Z] = center[Z];
+	v[W] = radius;
+
+	return NxNgc::IsVisible( v );
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const char* CEngine::s_plat_get_platform_extension( void )
+{
+	// String literals are statically allocated so can be returned safely, (Bjarne, p90)
+	return "ngc";
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom* CEngine::s_plat_init_geom( void )
+{
+	CNgcGeom* pGeom = new CNgcGeom;
+
+	return pGeom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CEngine::s_plat_uninit_geom(CGeom* pGeom)
+{
+	Dbg_Assert( pGeom );
+
+	delete pGeom;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CQuickAnim* CEngine::s_plat_init_quick_anim()
+{
+	CQuickAnim* pQuickAnim = new CQuickAnim;
+
+	return pQuickAnim;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::s_plat_uninit_quick_anim(CQuickAnim* pQuickAnim)
+{
+	Dbg_Assert( pQuickAnim );
+
+	delete pQuickAnim;
+
+	return;
+}
+
+/******************************************************************/
+// Wait for any pending asyncronous rendering to finish, so rendering
+// data can be unloaded
+/******************************************************************/
+
+void CEngine::s_plat_finish_rendering()
+{
+	// TODO:  Flush pending rendering, so data can be unloaded
+	NsDisplay::flush();
+} 
+
+/******************************************************************/
+// Set the amount that the previous frame is blended with this frame
+// 0 = none	  	(just see current frame) 	
+// 128 = 50/50
+// 255 = 100% 	(so you only see the previous frame)												  
+/******************************************************************/
+
+void CEngine::s_plat_set_screen_blur(uint32 amount )
+{
+//	g_blur = ( amount * 8 ) / 255;
+//	sBlur = amount;
+} 
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	s_plat_get_num_soundtracks()
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* s_plat_get_soundtrack_name( int soundtrack_number )
+{
+	return NULL;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int	CEngine::s_plat_get_num_soundtracks( void )
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* CEngine::s_plat_get_soundtrack_name( int soundtrack_number )
+{
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_max_multipass_distance(float dist)
+{
+//	NxPs2::render::sMultipassMaxDist = dist;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_letterbox( bool letterbox )
+{
+	// Letterbox mode is designed for use on a regular 4:3 screen.
+	// It should use the same, wider viewing angle as for widescreen mode, but shrink the resultant image down
+	// vertically by 25%.
+	if( letterbox )
+	{
+		if( NxNgc::EngineGlobals.letterbox_active == false )
+		{
+			// Need to adjust the screen y offset and multiplier to ensure sprites are scaled properly for this mode.
+//			NxNgc::EngineGlobals.screen_conv_y_offset		+= ( NxXbox::EngineGlobals.backbuffer_height / 4 ) / 2;
+//			NxNgc::EngineGlobals.screen_conv_y_multiplier	= 0.75f;
+			NxNgc::EngineGlobals.letterbox_active			= true;
+		}
+	}
+	else
+	{
+		if( NxNgc::EngineGlobals.letterbox_active == true )
+		{
+			// Restore the screen y offset and multiplier.
+//			NxNgc::EngineGlobals.screen_conv_y_offset		-= ( NxXbox::EngineGlobals.backbuffer_height / 4 ) / 2;
+//			NxNgc::EngineGlobals.screen_conv_y_multiplier	= 1.0f;
+			NxNgc::EngineGlobals.letterbox_active			= false;
+		}
+	}
+} 
+ 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_color_buffer_clear( bool clear )
+{
+}
+
+
+
+} // namespace Nx
+
+
diff --git a/Code/Gfx/NGC/p_nxanimcache.cpp b/Code/Gfx/NGC/p_nxanimcache.cpp
new file mode 100644
index 0000000..b156896
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxanimcache.cpp
@@ -0,0 +1,47 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_NxAnimCache.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  5/06/2002
+//****************************************************************************
+
+#include 
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcAnimCache::CNgcAnimCache( int lookupTableSize ) : CAnimCache( lookupTableSize )
+{
+	// Machine specific code here ............
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcAnimCache::~CNgcAnimCache()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+				
+
diff --git a/Code/Gfx/NGC/p_nxanimcache.h b/Code/Gfx/NGC/p_nxanimcache.h
new file mode 100644
index 0000000..0a6f1e2
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxanimcache.h
@@ -0,0 +1,37 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_NxAnimCache.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  5/06/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NXANIMCACHE_H__
+#define	__GFX_P_NXANIMCACHE_H__
+    
+#include 
+
+namespace Nx
+{
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CGeom
+    
+class CNgcAnimCache : public CAnimCache
+{
+                                      
+public:
+						CNgcAnimCache( int lookupTableSize );
+	virtual 			~CNgcAnimCache();
+
+private:				// It's all private, as it is machine specific
+};
+
+} // Nx
+
+#endif 
+
diff --git a/Code/Gfx/NGC/p_nxfont.cpp b/Code/Gfx/NGC/p_nxfont.cpp
new file mode 100644
index 0000000..528a666
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxfont.cpp
@@ -0,0 +1,264 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.cpp
+
+#include 	"gfx/Ngc/p_nxfont.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CFont
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcFont::CNgcFont() : mp_plat_font(NULL)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcFont::~CNgcFont()
+{
+	if (mp_plat_font)
+	{
+		plat_unload();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcFont::plat_load(const char *filename)
+{
+	mp_plat_font = NxNgc::LoadFont(filename);
+
+	return (mp_plat_font != NULL);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcFont::plat_set_spacings(int charSpacing, int spaceSpacing)
+{
+	mp_plat_font->mCharSpacing = charSpacing;
+	if (spaceSpacing > 0)
+		mp_plat_font->mSpaceSpacing = spaceSpacing;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcFont::plat_set_rgba_table(Image::RGBA *pTab)
+{
+	for (int i = 0; i < 16; i++)
+		mp_plat_font->mRGBATab[i] = *((uint32 *) &pTab[i]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcFont::plat_mark_as_button_font(bool isButton)
+{
+	NxNgc::pButtonsFont = (isButton) ? mp_plat_font : NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcFont::plat_unload()
+{
+	NxNgc::UnloadFont(mp_plat_font);
+	mp_plat_font = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 CNgcFont::plat_get_default_height() const
+{
+	Dbg_Assert(mp_plat_font);
+
+	return mp_plat_font->GetDefaultHeight();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 CNgcFont::plat_get_default_base() const
+{
+	Dbg_Assert(mp_plat_font);
+
+	return mp_plat_font->GetDefaultBase();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void CNgcFont::plat_begin_text(uint32 rgba, float Scale)
+//{
+//	Dbg_Assert(mp_plat_font);
+//
+//	mp_plat_font->BeginText(rgba, Scale);
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void CNgcFont::plat_draw_string(char *String, float x0, float y0)
+//{
+//	Dbg_Assert(mp_plat_font);
+//
+//	mp_plat_font->DrawString(String, x0, y0);
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void CNgcFont::plat_end_text(void)
+//{
+//	Dbg_Assert(mp_plat_font);
+//
+//	mp_plat_font->EndText();
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcFont::plat_query_string(char *String, float &width, float &height) const
+{
+	Dbg_Assert(mp_plat_font);
+
+	mp_plat_font->QueryString(String, width, height);
+}
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcText::CNgcText()
+{
+	mp_plat_text = new NxNgc::SText();
+
+	plat_initialize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcText::~CNgcText()
+{
+	if( mp_plat_text )
+	{
+		delete mp_plat_text;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcText::plat_initialize()
+{
+	plat_update_engine();
+	plat_update_priority();
+	plat_update_hidden();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcText::plat_update_hidden()
+{
+	mp_plat_text->SetHidden(m_hidden);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcText::plat_update_engine()
+{
+	CNgcFont *p_Ngc_font = static_cast( mp_font );
+
+	mp_plat_text->mp_string	= m_string;
+	if( p_Ngc_font)
+	{
+		mp_plat_text->mp_font = p_Ngc_font->GetEngineFont();
+	}
+
+	mp_plat_text->m_xpos	= m_xpos;
+	mp_plat_text->m_ypos	= m_ypos;
+	mp_plat_text->m_xscale	= m_xscale;
+	mp_plat_text->m_yscale	= m_yscale;
+	mp_plat_text->m_rgba	= *((uint32 *) &m_rgba);
+	mp_plat_text->m_color_override = m_color_override;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcText::plat_update_priority( void )
+{
+	// Update draw list
+	mp_plat_text->SetPriority( m_priority );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CTextMan::s_plat_alloc_text_pool( void )
+{
+	for( int i = 0; i < vMAX_TEXT_INSTANCES; ++i )
+	{
+	   	CNgcText *p_text		= new CNgcText;
+		p_text->mp_next			= sp_dynamic_text_list;
+		sp_dynamic_text_list	= p_text;
+	}
+}
+
+
+
+
+
+
+} // Namespace Nx  			
+				
+				
+
diff --git a/Code/Gfx/NGC/p_nxfont.h b/Code/Gfx/NGC/p_nxfont.h
new file mode 100644
index 0000000..c29432f
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxfont.h
@@ -0,0 +1,77 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.h
+
+#ifndef	__GFX_P_NX_FONT_H__
+#define	__GFX_P_NX_FONT_H__
+
+#include 	"gfx/nxfont.h"
+#include 	"gfx/Ngc/nx/chars.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CFont
+class CNgcFont : public CFont
+{
+public:
+								CNgcFont();
+	virtual						~CNgcFont();
+	NxNgc::SFont				*GetEngineFont() const;
+
+private:		// It's all private, as it is machine specific
+	virtual	bool				plat_load(const char *filename);
+	virtual void				plat_set_spacings(int charSpacing, int spaceSpacing);
+	virtual void				plat_set_rgba_table(Image::RGBA *pTab);
+	virtual void 				plat_mark_as_button_font(bool isButton);
+	virtual void				plat_unload();
+
+	virtual	uint32				plat_get_default_height() const;
+	virtual	uint32				plat_get_default_base() const;
+//	virtual void				plat_begin_text(uint32 rgba, float Scale);
+//	virtual void				plat_draw_string(char *String, float x0, float y0);
+//	virtual void				plat_end_text(void);
+	virtual void				plat_query_string(char *String, float &width, float &height) const;
+
+	// Machine specific members
+	NxNgc::SFont *				mp_plat_font;		// Pointer to engine font
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+class	CNgcText : public CText
+{
+public:
+								CNgcText();
+	virtual						~CNgcText();
+
+private:
+	//
+	virtual void				plat_initialize();
+
+	virtual void				plat_update_hidden();		// Tell engine of update
+	virtual void				plat_update_engine();		// Update engine primitives
+	virtual void				plat_update_priority();
+
+	// Machine specific members
+	NxNgc::SText				*mp_plat_text;		// Pointer to engine text
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline NxNgc::SFont *CNgcFont::GetEngineFont() const
+{
+	return mp_plat_font;
+}
+
+
+} // Namespace Nx  			
+
+#endif
+
diff --git a/Code/Gfx/NGC/p_nxfontman.cpp b/Code/Gfx/NGC/p_nxfontman.cpp
new file mode 100644
index 0000000..6c520e9
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxfontman.cpp
@@ -0,0 +1,106 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:						 		 									**
+**																			**
+**	File name:		gfx/Ngc/p_nxfontman.cpp								**
+**																			**
+**	Created:		01/16/2002	-	dc										**
+**																			**
+**	Description:	Ngc platform specific interface to the Font Manager	**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include	"gfx\nx.h"
+#include	"gfx\NxFontMan.h"
+#include	"gfx\Ngc\p_NxFont.h"
+#include 	"gfx\Ngc\nx\chars.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+namespace Nx
+{
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CFont* CFontManager::s_plat_load_font( const char* pName )
+{
+	CNgcFont *p_new_font = new CNgcFont;
+	p_new_font->Load( pName );
+	return p_new_font;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFontManager::s_plat_unload_font( CFont* pFont )
+{
+	pFont->Unload();
+}
+
+} // namespace Nx
+ 
+
diff --git a/Code/Gfx/NGC/p_nxlight.cpp b/Code/Gfx/NGC/p_nxlight.cpp
new file mode 100644
index 0000000..d18f52b
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxlight.cpp
@@ -0,0 +1,307 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLight.cpp - Ngc platform specific interface to CModelLights
+
+#include 
+
+#include 
+#include 
+#include "sys/ngc/p_gx.h"
+
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcModelLights::CNgcModelLights()
+{
+	m_flags = 0;
+
+	EnableAmbientLight( false );
+	for( int i = 0; i < CLightManager::MAX_LIGHTS; ++i )
+	{
+		EnableDiffuseLight( i, false );
+	}
+	
+	// Set valid default direction.
+	m_diffuse_direction[0].Set( 0.0f, 1.0f, 0.0f );
+	m_diffuse_direction[1].Set( 0.0f, 1.0f, 0.0f );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcModelLights::~CNgcModelLights()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcModelLights::plat_update_brightness()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcModelLights::plat_update_engine( Mth::Vector & pos, bool add_scene_light )
+{
+	if( m_flags & mUSE_MODEL_AMBIENT )
+	{
+		// Use the local ambient color, modulate it with the local ambient brightness.
+		int difr = (int)((float)m_ambient_color.r * m_ambient_brightness);
+		int difg = (int)((float)m_ambient_color.g * m_ambient_brightness);
+		int difb = (int)((float)m_ambient_color.b * m_ambient_brightness);
+		NxNgc::EngineGlobals.ambient_light_color.r = difr > 255 ? 255 : (u8)difr; 
+		NxNgc::EngineGlobals.ambient_light_color.g = difg > 255 ? 255 : (u8)difg; 
+		NxNgc::EngineGlobals.ambient_light_color.b = difb > 255 ? 255 : (u8)difb; 
+	}
+	else
+	{
+		// Use the default ambient color, but modulate it with the local ambient brightness.
+		Image::RGBA	amb = CLightManager::sGetLightAmbientColor();
+		int difr = (int)((float)amb.r * m_ambient_brightness);
+		int difg = (int)((float)amb.g * m_ambient_brightness);
+		int difb = (int)((float)amb.b * m_ambient_brightness);
+		NxNgc::EngineGlobals.ambient_light_color.r = difr > 255 ? 255 : (u8)difr;  
+		NxNgc::EngineGlobals.ambient_light_color.g = difg > 255 ? 255 : (u8)difg;  
+		NxNgc::EngineGlobals.ambient_light_color.b = difb > 255 ? 255 : (u8)difb;  
+	}
+
+//	NxNgc::EngineGlobals.ambient_light_color.r = NxNgc::EngineGlobals.ambient_light_color.r < 128 ? NxNgc::EngineGlobals.ambient_light_color.r * 2 : 255;
+//	NxNgc::EngineGlobals.ambient_light_color.g = NxNgc::EngineGlobals.ambient_light_color.g < 128 ? NxNgc::EngineGlobals.ambient_light_color.g * 2 : 255;
+//	NxNgc::EngineGlobals.ambient_light_color.b = NxNgc::EngineGlobals.ambient_light_color.b < 128 ? NxNgc::EngineGlobals.ambient_light_color.b * 2 : 255;
+
+	for( int i = 0; i < 2; ++i )
+	{
+		if( m_flags & (( i == 0 ) ? mUSE_MODEL_DIFFUSE_0 : mUSE_MODEL_DIFFUSE_1 ))
+		{
+			// Use the local directional color, modulate it with the local directional brightness.
+			int difr = (int)((float)m_diffuse_color[i].r * m_diffuse_brightness[i] * 0.5f);
+			int difg = (int)((float)m_diffuse_color[i].g * m_diffuse_brightness[i] * 0.5f);
+			int difb = (int)((float)m_diffuse_color[i].b * m_diffuse_brightness[i] * 0.5f);
+			NxNgc::EngineGlobals.diffuse_light_color[i].r = difr > 255 ? 255 : (u8)difr;
+			NxNgc::EngineGlobals.diffuse_light_color[i].g = difg > 255 ? 255 : (u8)difg;
+			NxNgc::EngineGlobals.diffuse_light_color[i].b = difb > 255 ? 255 : (u8)difb;
+
+			// Use the local direction.
+			m_diffuse_direction[i].Normalize();
+			NxNgc::EngineGlobals.light_x[i] = m_diffuse_direction[i][X] * 200000.0f;
+			NxNgc::EngineGlobals.light_y[i] = m_diffuse_direction[i][Y] * 200000.0f;
+			NxNgc::EngineGlobals.light_z[i] = m_diffuse_direction[i][Z] * 200000.0f;
+		}
+		else
+		{
+			// Use the default directional color, but modulate it with the local directional brightness.
+			Image::RGBA	dif = CLightManager::sGetLightDiffuseColor( i );
+			int difr = (int)((float)dif.r * m_diffuse_brightness[i] * 0.5f);
+			int difg = (int)((float)dif.g * m_diffuse_brightness[i] * 0.5f);
+			int difb = (int)((float)dif.b * m_diffuse_brightness[i] * 0.5f);
+			NxNgc::EngineGlobals.diffuse_light_color[i].r = difr > 255 ? 255 : (u8)difr;
+			NxNgc::EngineGlobals.diffuse_light_color[i].g = difg > 255 ? 255 : (u8)difg;
+			NxNgc::EngineGlobals.diffuse_light_color[i].b = difb > 255 ? 255 : (u8)difb;
+
+			// Use the default direction.
+			Mth::Vector dir = CLightManager::sGetLightDirection( i );
+			dir.Normalize();
+			NxNgc::EngineGlobals.light_x[i] = dir[X] * 200000.0f;
+			NxNgc::EngineGlobals.light_y[i] = dir[Y] * 200000.0f;
+			NxNgc::EngineGlobals.light_z[i] = dir[Z] * 200000.0f;
+		}
+//		NxNgc::EngineGlobals.diffuse_light_color[i].r = NxNgc::EngineGlobals.diffuse_light_color[i].r < 128 ? NxNgc::EngineGlobals.diffuse_light_color[i].r * 2 : 255;
+//		NxNgc::EngineGlobals.diffuse_light_color[i].g = NxNgc::EngineGlobals.diffuse_light_color[i].g < 128 ? NxNgc::EngineGlobals.diffuse_light_color[i].g * 2 : 255;
+//		NxNgc::EngineGlobals.diffuse_light_color[i].b = NxNgc::EngineGlobals.diffuse_light_color[i].b < 128 ? NxNgc::EngineGlobals.diffuse_light_color[i].b * 2 : 255;
+	}
+
+	if( add_scene_light )
+	{
+		Nx::CSceneLight *p_scene_light = CLightManager::sGetOptimumSceneLight( pos );
+		if( p_scene_light )
+		{
+			Mth::Vector light_pos = p_scene_light->GetLightPosition();
+
+			float dist	= Mth::Distance( pos, light_pos );
+			float ratio	= dist * p_scene_light->GetLightReciprocalRadius();
+
+			light_pos = ( pos - light_pos ).Normalize();
+
+			// Figure the direction...
+			NxNgc::EngineGlobals.light_x[2] = -light_pos[X] * 200000.0f;
+			NxNgc::EngineGlobals.light_y[2] = -light_pos[Y] * 200000.0f;
+			NxNgc::EngineGlobals.light_z[2] = -light_pos[Z] * 200000.0f;
+
+			// ...and the color.
+			ratio = sqrtf( 1.0f - ratio ) * p_scene_light->GetLightIntensity();
+			int difr = (int)((float)p_scene_light->GetLightColor().r * ratio * 0.5f);
+			int difg = (int)((float)p_scene_light->GetLightColor().g * ratio * 0.5f);
+			int difb = (int)((float)p_scene_light->GetLightColor().b * ratio * 0.5f);
+			NxNgc::EngineGlobals.diffuse_light_color[2].r	= difr > 255 ? 255 : (u8)difr;
+			NxNgc::EngineGlobals.diffuse_light_color[2].g	= difg > 255 ? 255 : (u8)difg;
+			NxNgc::EngineGlobals.diffuse_light_color[2].b	= difb > 255 ? 255 : (u8)difb;
+		}
+		else
+		{
+			// Disbale this light by setting zero color.
+			NxNgc::EngineGlobals.diffuse_light_color[2].r = 0;
+			NxNgc::EngineGlobals.diffuse_light_color[2].g = 0;
+			NxNgc::EngineGlobals.diffuse_light_color[2].b = 0;
+		}
+	}
+
+//	// Set lighting.
+//	GXLightObj light_obj[2];
+//
+//	GX::SetChanAmbColor( GX_COLOR0A0, NxNgc::EngineGlobals.ambient_light_color );
+//	GX::SetChanAmbColor( GX_COLOR1A1, NxNgc::EngineGlobals.ambient_light_color );
+//
+//	GXInitLightColor( &light_obj[0], NxNgc::EngineGlobals.diffuse_light_color[0] );
+//	GXInitLightPos( &light_obj[0], NxNgc::EngineGlobals.light_x[0], NxNgc::EngineGlobals.light_y[0], NxNgc::EngineGlobals.light_z[0] );
+//	GXLoadLightObjImm( &light_obj[0], GX_LIGHT0 );
+//
+//	GXInitLightColor( &light_obj[1], NxNgc::EngineGlobals.diffuse_light_color[1] );
+//	GXInitLightPos( &light_obj[1], NxNgc::EngineGlobals.light_x[1], NxNgc::EngineGlobals.light_y[1], NxNgc::EngineGlobals.light_z[1] );
+//	GXLoadLightObjImm( &light_obj[1], GX_LIGHT1 );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcModelLights::plat_set_light_ambient_color( const Image::RGBA &rgba )
+{
+	m_ambient_color = rgba;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CNgcModelLights::plat_get_light_ambient_color() const
+{
+	return m_ambient_color;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcModelLights::plat_set_light_direction( int light_index, const Mth::Vector &direction )
+{
+	m_diffuse_direction[light_index] = direction;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector &	CNgcModelLights::plat_get_light_direction( int light_index ) const
+{
+	if( plat_is_diffuse_light_enabled( light_index ))
+		return m_diffuse_direction[light_index];
+	else
+		return Nx::CLightManager::sGetLightDirection( light_index );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcModelLights::plat_set_light_diffuse_color( int light_index, const Image::RGBA &rgba )
+{
+	m_diffuse_color[light_index] = rgba;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CNgcModelLights::plat_get_light_diffuse_color( int light_index ) const
+{
+	return m_diffuse_color[light_index];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcModelLights::plat_enable_ambient_light( bool enable )
+{
+	if( enable )
+		m_flags |= mUSE_MODEL_AMBIENT;
+	else
+		m_flags &= ~mUSE_MODEL_AMBIENT;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcModelLights::plat_enable_diffuse_light( int light_index, bool enable )
+{
+	if( enable )
+		m_flags |= ( light_index == 0 ) ? mUSE_MODEL_DIFFUSE_0 : mUSE_MODEL_DIFFUSE_1;
+	else
+		m_flags &= ~(( light_index == 0 ) ? mUSE_MODEL_DIFFUSE_0 : mUSE_MODEL_DIFFUSE_1 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcModelLights::plat_is_ambient_light_enabled() const
+{
+	return ( m_flags & mUSE_MODEL_AMBIENT ) > 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcModelLights::plat_is_diffuse_light_enabled( int light_index ) const
+{
+	return (( light_index == 0 ) ? (( m_flags & mUSE_MODEL_DIFFUSE_0 ) > 0 ) : (( m_flags & mUSE_MODEL_DIFFUSE_1 ) > 0 ));
+}
+
+} 
+ 
+
diff --git a/Code/Gfx/NGC/p_nxlight.h b/Code/Gfx/NGC/p_nxlight.h
new file mode 100644
index 0000000..0bb84df
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxlight.h
@@ -0,0 +1,56 @@
+///////////////////////////////////////////////////////////////////////////////////
+// p_NxLight.H - Neversoft Engine, Rendering portion, Platform dependent interface
+
+#ifndef	__GFX_P_NX_LIGHT_H__
+#define	__GFX_P_NX_LIGHT_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CNgcModelLights
+class	CNgcModelLights : public CModelLights
+{
+public:
+								CNgcModelLights();
+	virtual						~CNgcModelLights();
+
+private:	
+
+	// Platform-specific calls
+	virtual void				plat_update_engine( Mth::Vector & pos, bool add_scene_light );
+	virtual bool				plat_set_light_ambient_color(const Image::RGBA &rgba);
+	virtual bool				plat_set_light_direction(int light_index, const Mth::Vector &direction);
+	virtual bool				plat_set_light_diffuse_color(int light_index, const Image::RGBA &rgba);
+	virtual Image::RGBA			plat_get_light_ambient_color() const;
+	virtual const Mth::Vector &	plat_get_light_direction(int light_index) const;
+	virtual Image::RGBA			plat_get_light_diffuse_color(int light_index) const;
+
+	virtual void				plat_update_brightness();
+
+	virtual void				plat_enable_ambient_light(bool enable);
+	virtual void				plat_enable_diffuse_light(int light_index, bool enable);
+	virtual bool				plat_is_ambient_light_enabled() const;
+	virtual bool				plat_is_diffuse_light_enabled(int light_index) const;
+
+	uint32						m_flags;
+	Image::RGBA					m_ambient_color;
+	Image::RGBA					m_diffuse_color[CLightManager::MAX_LIGHTS];
+	Mth::Vector					m_diffuse_direction[CLightManager::MAX_LIGHTS];
+};
+
+
+}
+
+
+#endif
+
+
diff --git a/Code/Gfx/NGC/p_nxloadscreen.cpp b/Code/Gfx/NGC/p_nxloadscreen.cpp
new file mode 100644
index 0000000..cc63f72
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxloadscreen.cpp
@@ -0,0 +1,569 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLoadScreen.cpp - PS2 platform specific interface for the load screen
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+//
+
+#include	"gfx\Nx.h"
+#include	"gfx\NxLoadScreen.h"
+#include	"gfx\ngc\p_NxTexture.h"
+#include	"gfx\ngc\p_NxSprite.h"
+#include	"gfx\ngc\NX\sprite.h"
+#include	"gfx\ngc\NX\render.h"
+#include 
+
+#include	"core\macros.h"
+#include	"sys/config/config.h"
+
+#include 
+#include 
+#include 
+#include 
+#include "gfx/ngc/nx/nx_init.h"
+#include	
+#include 
+
+bool		gReload = false;
+bool		gLoadingLoadScreen = false;
+bool		gLoadingBarActive = false;
+bool		gLoadingScreenActive = false;
+int			gLoadBarTotalFrames;		// Number of frames it takes for loading bar to go to 100%
+int			gLoadBarNumFrames;			// Number of frames so far
+int			gLoadBarX = 150;			// Bar position
+int			gLoadBarY = 400;
+int			gLoadBarWidth = 340;		// Bar size
+int			gLoadBarHeight = 20;
+int			gLoadBarStartColor[4] = {  255,    0,   0,  128 };
+int			gLoadBarDeltaColor[4] = { -255,  255,   0,  128 };
+int			gLoadBarBorderWidth = 5;	// Border width
+int			gLoadBarBorderHeight = 5;	// Border height
+int			gLoadBarBorderColor[4] = {  40, 40, 40, 128 };
+
+//static bool				s_loading_screen_on		= false;
+static OSThread			s_load_icon_thread;
+static OSAlarm			s_alarm;
+//static LoadingIconData	s_load_icon_data		= { 0 };
+//static char				s_load_icon_thread_stack[4096]	__attribute__ (( aligned( 16 )));
+static volatile bool	s_terminate_thread		= false;
+static volatile bool	s_terminate_thread_done	= false;
+bool				s_thread_exists			= false;
+
+extern char * g_p_buffer;
+
+namespace	Nx
+{
+
+
+Nx::CNgcTexture	*sp_load_screen_texture;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void s_thread_loading_icon_alarm_handler( OSAlarm* alarm, OSContext* context )
+{
+	// Check the thread has not been resumed yet...
+	if( OSIsThreadSuspended( &s_load_icon_thread ))
+	{
+		OSResumeThread( &s_load_icon_thread );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void* s_threaded_loading_icon( void* data )
+{
+	while( 1 )
+	{
+		if( s_terminate_thread )
+		{
+			// This thread is done...
+			s_terminate_thread_done	= true;
+			return NULL;
+		}
+
+		gLoadBarNumFrames++;
+
+		bool busy = NxNgc::EngineGlobals.gpuBusy;
+
+//		// Don't want to do draw anything whilst display lists are being built. Come back in a bit.
+//		if( !busy )
+		{
+//			NsTexture* p_texture = p_data->m_Rasters[current_image];
+
+//			NsDisplay::begin();
+//			NsRender::begin();
+//			NsPrim::begin();
+//
+////		    GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0 );		// 3 = positions & uvs.
+////			GXSetVtxAttrFmt( GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0 );
+////
+//			NsCamera camera2D;
+//			camera2D.orthographic( 0, 0, 640, 448 );
+//
+//			camera2D.begin();
+//
+////			NsRender::setBlendMode( NsBlendMode_None, NULL, (unsigned char)0 );
+////
+////			GXClearVtxDesc();
+////			GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
+////			GXSetVtxDesc( GX_VA_TEX0, GX_DIRECT );
+////			GX::SetChanMatColor( GX_COLOR0, (GXColor){ 128,128,128,128 });
+////			GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+////
+////			p_texture->upload( NsTexture_Wrap_Clamp );
+////
+////			GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+////			GXPosition3f32((float)p_data->m_X, (float)p_data->m_Y, -1.0f );
+////			GXTexCoord2f32( 0.0f, 0.0f );
+////			GXPosition3f32((float)p_data->m_X + (float)p_texture->m_width, (float)p_data->m_Y, -1.0f );
+////			GXTexCoord2f32( 1.0f, 0.0f );
+////			GXPosition3f32((float)p_data->m_X + (float)p_texture->m_width, (float)p_data->m_Y + (float)p_texture->m_height, -1.0f );
+////			GXTexCoord2f32( 1.0f, 1.0f );
+////			GXPosition3f32((float)p_data->m_X, (float)p_data->m_Y + (float)p_texture->m_height, -1.0f );
+////			GXTexCoord2f32( 0.0f, 1.0f );
+////			GXEnd();
+//
+//			GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+//			GX::SetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//			GX::SetNumTexGens( 0 );
+//			GX::SetNumTevStages( 1 );
+//			GX::SetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+//			GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+//			GX::SetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+//			GX::SetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_RASA, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
+//			GX::SetTevColorIn ( GX_TEVSTAGE0, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO );
+//
+//			// Set current vertex descriptor to enable position and color0.
+//			// Both use 8b index to access their data arrays.
+//			GXClearVtxDesc();
+//			GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
+//			GXSetVtxDesc( GX_VA_CLR0, GX_DIRECT );
+//
+//			// Set material color.
+//			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//
+//			GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//
+//			GX::SetCullMode ( GX_CULL_NONE );
+//
+//			int cur_width = (gLoadBarWidth - 1);
+//			if (gLoadBarNumFrames < gLoadBarTotalFrames)
+//			{
+//				cur_width = (cur_width * gLoadBarNumFrames) / gLoadBarTotalFrames;
+//			}
+//
+//			int x1 = gLoadBarX;
+//			int y1 = gLoadBarY;
+//			int x2 = x1 + cur_width;
+//			int y2 = y1 + (gLoadBarHeight - 1);
+//
+//			int end_color[4];
+//			if (gLoadBarNumFrames < gLoadBarTotalFrames)
+//			{
+//				end_color[0] = gLoadBarStartColor[0] + ((gLoadBarDeltaColor[0] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+//				end_color[1] = gLoadBarStartColor[1] + ((gLoadBarDeltaColor[1] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+//				end_color[2] = gLoadBarStartColor[2] + ((gLoadBarDeltaColor[2] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+//				end_color[3] = gLoadBarStartColor[3] + ((gLoadBarDeltaColor[3] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+//			} else {
+//				end_color[0] = gLoadBarStartColor[0] + gLoadBarDeltaColor[0];
+//				end_color[1] = gLoadBarStartColor[1] + gLoadBarDeltaColor[1];
+//				end_color[2] = gLoadBarStartColor[2] + gLoadBarDeltaColor[2];
+//				end_color[3] = gLoadBarStartColor[3] + gLoadBarDeltaColor[3];
+//			}
+//
+//			int border_x1 = x1 - gLoadBarBorderWidth;
+//			int border_y1 = y1 - gLoadBarBorderHeight;
+//			int border_x2 = x1 + (gLoadBarWidth - 1) + gLoadBarBorderWidth;
+//			int border_y2 = y2 + gLoadBarBorderHeight;
+//
+//			u32 bc = gLoadBarBorderColor[3]|(gLoadBarBorderColor[2]<<8)|(gLoadBarBorderColor[1]<<16)|(gLoadBarBorderColor[0]<<24);
+//			u32 sc = gLoadBarStartColor[3]|(gLoadBarStartColor[2]<<8)|(gLoadBarStartColor[1]<<16)|(gLoadBarStartColor[0]<<24); 
+//			u32 ec = end_color[3]|(end_color[2]<<8)|(end_color[1]<<16)|(end_color[0]<<24);
+//
+//			// Border
+//			GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//				GXPosition3f32(border_x1, border_y1, -1.0f);
+//				GXColor1u32( bc );
+//				GXPosition3f32(border_x1, border_y2, -1.0f);
+//				GXColor1u32( bc );
+//				GXPosition3f32(border_x2, border_y2, -1.0f);
+//				GXColor1u32( bc );
+//				GXPosition3f32(border_x2, border_y1, -1.0f);
+//				GXColor1u32( bc );
+//			GXEnd();
+//			
+//			// Bar
+//			GXBegin( GX_QUADS, GX_VTXFMT0, 4 ); 
+//				GXPosition3f32(x1, y1, -1.0f);
+//				GXColor1u32( sc );
+//				GXPosition3f32(x1, y2, -1.0f);
+//				GXColor1u32( sc );
+//				GXPosition3f32(x2, y2, -1.0f);
+//				GXColor1u32( ec );
+//				GXPosition3f32(x2, y1, -1.0f);
+//				GXColor1u32( ec );
+//			GXEnd();
+//
+//			camera2D.end();
+//
+//			NsPrim::end();
+//			NsRender::end();
+//			NsDisplay::end( false );
+//
+////			current_image = ( current_image + 1 ) % p_data->m_NumFrames ;
+		}
+
+		// Go to sleep.
+		if( busy )
+		{
+			// Come back shortly.
+			OSSetAlarm( &s_alarm, OSMillisecondsToTicks( 500 ), s_thread_loading_icon_alarm_handler );
+		}
+		else
+		{
+			// Come back in the proper time.
+			OSSetAlarm( &s_alarm, OSMillisecondsToTicks( (long long int)( 1000.0f / 60.0f ) ), s_thread_loading_icon_alarm_handler );
+		}
+
+		OSSuspendThread( &s_load_icon_thread );
+	}
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+#define USE_SPRITES 0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_display(const char* filename, bool just_freeze, bool blank)
+{
+	// See if we need to just flag that a loading screen is active to disable draws.
+	if ( just_freeze )
+	{
+		gLoadingScreenActive = true;
+		return;
+	}
+
+	// See if we need to clear the screen to black.
+	if ( blank )
+	{
+		for ( int lp = 0; lp < 2; lp++ )
+		{
+			NsDisplay::begin();
+			NsRender::begin();
+			NsRender::end();
+			NsDisplay::end();
+		}
+
+		gLoadingScreenActive = true;
+		return;
+	}
+
+	NxNgc::EngineGlobals.gpuBusy = true;
+
+	char name[256];
+	char pre[256];
+
+	File::PreMgr* pre_mgr = File::PreMgr::Instance();
+
+	// Hmm seems we need to prepend 'images\\' to the start of this string...
+	strcpy( name, "images\\" );
+	switch( Config::GetLanguage())
+	{
+		case Config::LANGUAGE_GERMAN:
+			strcpy( pre, filename );
+			strcat( pre, ".prd" );
+			pre_mgr->LoadPre( pre, false );
+			strcat( name, "grimages_ngc\\" );
+			break;
+		case Config::LANGUAGE_FRENCH:
+			strcpy( pre, filename );
+			strcat( pre, ".prf" );
+			pre_mgr->LoadPre( pre, false );
+			strcat( name, "frimages_ngc\\" );
+			break;
+		default:
+			break;
+	}
+	strcat( name, filename );
+	
+	// ...and append '.img.ngc' to the end.
+	strcat( name, ".img.ngc" );
+	
+	// Load the texture a few lines at a time & render to the display.
+	gLoadingLoadScreen = true;
+	void *p_FH = File::Open( name, "rb" );
+
+	if( p_FH )
+	{
+		uint32 version;
+		uint32 checksum;
+		uint32 width;
+		uint32 height;
+		uint32 depth;
+		uint32 levels;
+		uint32 rwidth;
+		uint32 rheight;
+		uint16 alphamap;
+		uint16 hasholes;
+
+		// Load the actual texture
+
+		File::Read( &version,  4, 1, p_FH );
+		File::Read( &checksum, 4, 1, p_FH );
+		File::Read( &width,    4, 1, p_FH );
+		File::Read( &height,   4, 1, p_FH );
+		File::Read( &depth,    4, 1, p_FH );
+		File::Read( &levels,   4, 1, p_FH );
+		File::Read( &rwidth,   4, 1, p_FH );
+		File::Read( &rheight,  4, 1, p_FH );
+		File::Read( &alphamap, 2, 1, p_FH );
+		File::Read( &hasholes, 2, 1, p_FH );
+
+//		Dbg_MsgAssert( version <= TEXTURE_VERSION, ("Illegal texture version number: %d (The newest version we deal with is: %d)\n", version, TEXTURE_VERSION ));
+		Dbg_MsgAssert( depth == 32, ("We only deal with 32-bit textures.\n"));
+
+#define LINES 16
+
+		// Load in 8 line chunks.
+//		uint16 temp_buffer[width*LINES*2];
+//		uint32 tex_buffer[width*LINES];
+
+		uint16 * temp_buffer = (uint16 *)g_p_buffer;
+		uint32 * tex_buffer = (uint32 *)&temp_buffer[width*LINES*2];
+
+		NsDisplay::begin();
+		NsRender::begin();
+
+		GX::PokeColorUpdate( GX_TRUE );
+		GX::PokeAlphaUpdate( GX_FALSE );
+		uint yy = 0;
+		while ( yy < height )
+		{
+			int lines = ( height - yy ) > LINES ? LINES : ( height - yy );
+
+			File::Read( temp_buffer, width * 4 * lines, 1, p_FH );
+
+			if ( gReload )
+			{
+				gReload = false;
+				yy = 0;
+				File::Close( p_FH );
+				void *p_FH = File::Open( name, "rb" );
+				File::Read( &version,  4, 1, p_FH );
+				File::Read( &checksum, 4, 1, p_FH );
+				File::Read( &width,    4, 1, p_FH );
+				File::Read( &height,   4, 1, p_FH );
+				File::Read( &depth,    4, 1, p_FH );
+				File::Read( &levels,   4, 1, p_FH );
+				File::Read( &rwidth,   4, 1, p_FH );
+				File::Read( &rheight,  4, 1, p_FH );
+				File::Read( &alphamap, 2, 1, p_FH );
+				File::Read( &hasholes, 2, 1, p_FH );
+				NsDisplay::begin();
+				NsRender::begin();
+				continue;
+			}
+
+			// Need to swizzle data we just read.
+			for ( int lp = 0; lp < lines; lp += 4 )
+			{
+				int xx = lp * width;
+				for ( uint lp2 = 0; lp2 < width; lp2 += 4 )
+				{
+					for ( int lp3 = 0; lp3 < 16; lp3++ )
+					{
+						uint16 p0 = temp_buffer[lp3+(lp2*8)+(xx*2)];
+						uint16 p1 = temp_buffer[16+lp3+(lp2*8)+(xx*2)];
+						tex_buffer[(lp3&3)+(width*(lp3>>2))+lp2+xx] =	( ( p0 << 16 ) & 0xff000000 ) |
+																		( ( p0 << 16 ) & 0x00ff0000 ) | 
+																		( ( p1 <<  0 ) & 0x0000ff00 ) | 
+																		( ( p1 <<  0 ) & 0x000000ff );
+					}
+				}
+			}
+
+			for ( int yp = 0; yp < lines; yp++ )
+			{
+				uint y_pos = ( ( height - 1 ) - ( yy + yp ) );
+				for ( uint xp = 0; xp < width; xp++ )
+				{
+					GX::PokeARGB( xp, y_pos, tex_buffer[(width*yp)+xp] );
+				}
+			}
+
+			yy += lines;
+		}
+		NsRender::end();
+		NsDisplay::end(false);
+
+		File::Close( p_FH );
+	}
+	gLoadingLoadScreen = false;
+
+	switch( Config::GetLanguage())
+	{
+		case Config::LANGUAGE_GERMAN:
+		case Config::LANGUAGE_FRENCH:
+			pre_mgr->UnloadPre( pre );
+			break;
+		default:
+			break;
+	}
+//	NsDisplay::flush();
+
+	NxNgc::EngineGlobals.gpuBusy = false;
+
+	gLoadingScreenActive = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_hide()
+{
+	if( s_thread_exists )
+	{
+		s_terminate_thread_done	= false;
+		s_terminate_thread = true;
+
+		// Cancel the alarm and resume the thread.
+//		OSCancelAlarm( &s_alarm );
+//		OSResumeThread( &s_load_icon_thread );
+
+		while( !s_terminate_thread_done );
+
+		s_thread_exists = false;
+//		pIconCallback = NULL;
+
+		//VISetBlack( TRUE );
+		//VIFlush();
+		//VIWaitForRetrace();
+		//NsDisplay::flush();
+		//VISetBlack( FALSE );
+		//VIFlush();
+		//VIWaitForRetrace();
+	}
+
+//#if !USE_SPRITES
+//	NxPs2::EnableFlipCopy(true);
+//#endif // USE_SPRITES
+	gLoadingBarActive = false;
+	gLoadingScreenActive = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_clear()
+{
+//	if (sp_load_screen_sprite)
+//	{
+//		CEngine::sDestroySprite(sp_load_screen_sprite);
+//		sp_load_screen_sprite = NULL;
+//	}
+//
+//	if (sp_load_screen_texture)
+//	{
+//		delete sp_load_screen_texture;
+//		sp_load_screen_texture = NULL;
+//	}
+	gLoadingBarActive = false;
+	gLoadingScreenActive = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_start_loading_bar(float seconds)
+{
+//	NsDisplay::flush();
+
+	gLoadBarTotalFrames = (int) (seconds * 1.15f * Config::FPS());
+	gLoadBarNumFrames = 0;
+
+//
+////	NxPs2::StartLoadingBar((int) (seconds * Config::FPS()));
+//
+//	// Start the thread to display the animated loading icon.
+////	pIconCallback = s_non_threaded_loading_icon;
+//	BOOL rv = true;
+//
+//	s_terminate_thread = false;
+//
+//	memset( &s_load_icon_thread, 0, sizeof( OSThread ));
+//	rv = OSCreateThread(	&s_load_icon_thread,
+//							s_threaded_loading_icon,										// Entry function.
+//							NULL,															// Argument for start function.
+//							s_load_icon_thread_stack + sizeof( s_load_icon_thread_stack ),	// Stack base (stack grows down).
+//							sizeof( s_load_icon_thread_stack ),								// Stack size in bytes.
+//							1,																// Thread priority.
+//							OS_THREAD_ATTR_DETACH  );										// Thread attributes.
+//	Dbg_MsgAssert( rv, ( "Failed to create thread" ));
+//
+//	if( rv )
+//	{
+//		s_thread_exists = true;
+////		OSResumeThread( &s_load_icon_thread );
+//		OSSetAlarm( &s_alarm, OSMillisecondsToTicks( (long long int)( 1000.0f / 60.0f ) ), s_thread_loading_icon_alarm_handler );
+//	}
+	gLoadingBarActive = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_update_bar_properties()
+{
+	// Bar size and position
+	gLoadBarX = s_bar_x;
+	gLoadBarY = s_bar_y;
+	gLoadBarWidth = s_bar_width;
+	gLoadBarHeight = s_bar_height;
+
+	// Bar colors
+	gLoadBarStartColor[0] = s_bar_start_color.r;
+	gLoadBarStartColor[1] = s_bar_start_color.g;
+	gLoadBarStartColor[2] = s_bar_start_color.b;
+	gLoadBarStartColor[3] = s_bar_start_color.a;
+	gLoadBarDeltaColor[0] = (int) s_bar_end_color.r - gLoadBarStartColor[0];
+	gLoadBarDeltaColor[1] = (int) s_bar_end_color.g - gLoadBarStartColor[1];
+	gLoadBarDeltaColor[2] = (int) s_bar_end_color.b - gLoadBarStartColor[2];
+	gLoadBarDeltaColor[3] = (int) s_bar_end_color.a - gLoadBarStartColor[3];
+
+	// Border size
+	gLoadBarBorderWidth = s_bar_border_width;
+	gLoadBarBorderHeight = s_bar_border_height;
+
+	// Border color
+	gLoadBarBorderColor[0] = s_bar_border_color.r;
+	gLoadBarBorderColor[1] = s_bar_border_color.g;
+	gLoadBarBorderColor[2] = s_bar_border_color.b;
+	gLoadBarBorderColor[3] = s_bar_border_color.a;
+}
+
+} 
+ 
+
+
+
diff --git a/Code/Gfx/NGC/p_nxmiscfx.cpp b/Code/Gfx/NGC/p_nxmiscfx.cpp
new file mode 100644
index 0000000..c435007
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxmiscfx.cpp
@@ -0,0 +1,1658 @@
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include "dolphin/base/ppcwgpipe.h"
+#include "dolphin/gx/gxvert.h"
+
+namespace Nx
+{
+
+#define	DRAW_DEBUG_LINES		0
+
+#	if DRAW_DEBUG_LINES
+static float debugpos[10240*6];
+static GXColor debugcol[10240];
+static int nLines = 0;
+#	endif		// DRAW_DEBUG_LINES
+
+#define ADD_LINE(_s,_e,_r,_g,_b) {	debugpos[(nLines*6)+0] = _s[X];	\
+									debugpos[(nLines*6)+1] = _s[Y];    \
+									debugpos[(nLines*6)+2] = _s[Z];    \
+									debugpos[(nLines*6)+3] = _e[X];    \
+									debugpos[(nLines*6)+4] = _e[Y];    \
+									debugpos[(nLines*6)+5] = _e[Z];    \
+									debugcol[nLines] = (GXColor){_r,_g,_b,255};			\
+									nLines++; }							\
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sNgcScreenFlashVert
+{
+	float		x, y, z;
+	GXColor		col;
+	float		u, v;
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sNgcVert
+{
+	Mth::Vector		pos;
+	GXColor			col;
+	s16				u, v;
+};
+
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#if 0
+struct sNgcSplatInstanceDetails : public sSplatInstanceDetails
+{
+	// Platform specific part.
+	NxNgc::CInstance	*mp_instance;
+//	NxNgc::sMaterialHeader		m_mat;
+//	NxNgc::sMaterialPassHeader	m_pass;
+	NxNgc::sTexture *	mp_texture;
+	sNgcVert			m_verts[SPLAT_POLYS_PER_MESH * 3];
+};
+#endif		// 0
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sNgcShatterInstanceDetails : public sShatterInstanceDetails
+{
+	// Platform specific part.
+
+						sNgcShatterInstanceDetails( int num_tris, NxNgc::sMesh *p_mesh, NxNgc::sScene *p_scene, GXVtxFmt format );
+						~sNgcShatterInstanceDetails( void );
+	
+	NxNgc::sMesh		*mp_mesh;
+	NxNgc::sScene		*mp_scene;
+	sNgcVert			*mp_vertex_buffer;
+	GXVtxFmt			m_vertex_format;
+};
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sNgcShatterInstanceDetails::sNgcShatterInstanceDetails( int num_tris, NxNgc::sMesh *p_mesh, NxNgc::sScene *p_scene, GXVtxFmt format ) : sShatterInstanceDetails( num_tris )
+{
+	mp_mesh				= p_mesh;
+	mp_scene			= p_scene;
+	mp_vertex_buffer	= new sNgcVert[num_tris * 3];
+	m_vertex_format		= format;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sNgcShatterInstanceDetails::~sNgcShatterInstanceDetails( void )
+{
+	delete [] mp_vertex_buffer;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#if 0
+sNgcSplatInstanceDetails * getDetailsForTextureSplat( NxNgc::sTexture *p_texture )
+{
+
+	sNgcSplatInstanceDetails *p_Ngc_details;
+
+	Dbg_Assert( p_splat_details_table );
+	
+	// Check to see whether we have a scene already created for this type of texture splat.
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	while( p_details )
+	{
+		p_Ngc_details					= static_cast( p_details );
+//		if( p_Ngc_details->m_pass.m_texture.p_data == p_texture )
+		if( p_Ngc_details->mp_texture == p_texture )
+		{
+			// This scene contains a material with the required texture, so use this scene.
+			return p_Ngc_details;
+		}
+		p_details = p_splat_details_table->IterateNext();
+	}
+	
+	// Create an (opaque) material used to render the mesh.
+	p_Ngc_details = new sNgcSplatInstanceDetails;
+
+	p_Ngc_details->mp_texture				= p_texture;
+
+	p_Ngc_details->m_highest_active_splat	= 0;
+	memset( p_Ngc_details->m_lifetimes, 0, sizeof( int ) * SPLAT_POLYS_PER_MESH );
+
+	for( int v = 0; v < SPLAT_POLYS_PER_MESH * 3; ++v )
+	{
+		p_Ngc_details->m_verts[v].pos = Mth::Vector(0.0f, 0.0f, 0.0f, 1.0f);
+		p_Ngc_details->m_verts[v].col = (GXColor){ 0x80, 0x80, 0x80, 0xff };
+	}
+	
+	p_splat_details_table->PutItem((uint32)p_Ngc_details, p_Ngc_details );
+
+	return p_Ngc_details;
+}
+#endif		// 0
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool subdivide_tri_stack( sNgcVert **p_write, NxNgc::sMesh *p_mesh )
+{
+	// If there are elements on the stack, pop off the top three vertices and subdivide if necessary.
+	if( triSubdivideStack.IsEmpty())
+	{
+		return false;
+	}
+
+	// Three temporary buffers.
+	sNgcVert v0;
+	sNgcVert v1;
+	sNgcVert v2;
+	
+	sNgcVert	*p_v0 = &v0;
+	sNgcVert	*p_v1 = &v1;
+	sNgcVert	*p_v2 = &v2;
+	
+	// Stack is LIFO, so Pop() off in reverse order.
+	triSubdivideStack.Pop( p_v2 );
+	triSubdivideStack.Pop( p_v1 );
+	triSubdivideStack.Pop( p_v0 );
+	
+	// Calculate the area of this tri.
+	Mth::Vector p(	p_v1->pos[X] - p_v0->pos[X], p_v1->pos[Y] - p_v0->pos[Y], p_v1->pos[Z] - p_v0->pos[Z] );
+	Mth::Vector q(	p_v2->pos[X] - p_v0->pos[X], p_v2->pos[Y] - p_v0->pos[Y], p_v2->pos[Z] - p_v0->pos[Z] );
+	Mth::Vector r(( p[Y] * q[Z] ) - ( q[Y] * p[Z] ), ( p[Z] * q[X] ) - ( q[Z] * p[X] ), ( p[X] * q[Y] ) - ( q[X] * p[Y] ));
+	float area_squared = r.LengthSqr();
+
+	if( area_squared > shatterAreaTest )
+	{
+		// Three temporary buffers.
+		sNgcVert i01 = v0;
+		sNgcVert i12 = v0;
+		sNgcVert i20 = v0;
+
+		// Deal with positions (always present).
+		i01.pos[X] = p_v0->pos[X] + (( p_v1->pos[X] - p_v0->pos[X] ) * 0.5f );
+		i01.pos[Y] = p_v0->pos[Y] + (( p_v1->pos[Y] - p_v0->pos[Y] ) * 0.5f );
+		i01.pos[Z] = p_v0->pos[Z] + (( p_v1->pos[Z] - p_v0->pos[Z] ) * 0.5f );
+		      
+		i12.pos[X] = p_v1->pos[X] + (( p_v2->pos[X] - p_v1->pos[X] ) * 0.5f );
+		i12.pos[Y] = p_v1->pos[Y] + (( p_v2->pos[Y] - p_v1->pos[Y] ) * 0.5f );
+		i12.pos[Z] = p_v1->pos[Z] + (( p_v2->pos[Z] - p_v1->pos[Z] ) * 0.5f );
+		      
+		i20.pos[X] = p_v2->pos[X] + (( p_v0->pos[X] - p_v2->pos[X] ) * 0.5f );
+		i20.pos[Y] = p_v2->pos[Y] + (( p_v0->pos[Y] - p_v2->pos[Y] ) * 0.5f );
+		i20.pos[Z] = p_v2->pos[Z] + (( p_v0->pos[Z] - p_v2->pos[Z] ) * 0.5f );
+
+		// Deal with colors (not always present).
+//		if ( p_mesh->mp_colBuffer )
+		{
+			i01.col.r		= v0.col.r + (u8)(( v1.col.r - v0.col.r ) >> 1 );
+			i12.col.r		= v1.col.r + (u8)(( v2.col.r - v1.col.r ) >> 1 );
+			i20.col.r		= v2.col.r + (u8)(( v0.col.r - v2.col.r ) >> 1 );
+									   						
+			i01.col.g		= v0.col.g + (u8)(( v1.col.g - v0.col.g ) >> 1 );
+			i12.col.g		= v1.col.g + (u8)(( v2.col.g - v1.col.g ) >> 1 );
+			i20.col.g		= v2.col.g + (u8)(( v0.col.g - v2.col.g ) >> 1 );
+									   						
+			i01.col.b		= v0.col.b + (u8)(( v1.col.b - v0.col.b ) >> 1 );
+			i12.col.b		= v1.col.b + (u8)(( v2.col.b - v1.col.b ) >> 1 );
+			i20.col.b		= v2.col.b + (u8)(( v0.col.b - v2.col.b ) >> 1 );
+									   						
+			i01.col.a		= v0.col.a + (u8)(( v1.col.a - v0.col.a ) >> 1 );
+			i12.col.a		= v1.col.a + (u8)(( v2.col.a - v1.col.a ) >> 1 );
+			i20.col.a		= v2.col.a + (u8)(( v0.col.a - v2.col.a ) >> 1 );
+		}
+
+		// Deal with uv0 (not always present).
+//		if ( p_mesh->mp_uvBuffer )
+		{
+			i01.u		= v0.u + (( v1.u - v0.u ) >> 1 );
+			i01.v		= v0.v + (( v1.v - v0.v ) >> 1 );
+			i12.u		= v1.u + (( v2.u - v1.u ) >> 1 );
+			i12.v		= v1.v + (( v2.v - v1.v ) >> 1 );
+			i20.u		= v2.u + (( v0.u - v2.u ) >> 1 );
+			i20.v		= v2.v + (( v0.v - v2.v ) >> 1 );
+		}
+		
+		// Push the four new tris onto the stack.
+		triSubdivideStack.Push( &v0 );
+		triSubdivideStack.Push( &i01 );
+		triSubdivideStack.Push( &i20 );
+
+		triSubdivideStack.Push( &i01 );
+		triSubdivideStack.Push( &v1 );
+		triSubdivideStack.Push( &i12 );
+
+		triSubdivideStack.Push( &i01 );
+		triSubdivideStack.Push( &i12 );
+		triSubdivideStack.Push( &i20 );
+
+		triSubdivideStack.Push( &i20 );
+		triSubdivideStack.Push( &i12 );
+		triSubdivideStack.Push( &v2 );
+	}
+	else
+	{
+		// Just copy the tri into the next available slot.
+		**p_write = v0;
+		(*p_write)++;
+		**p_write = v1;
+		(*p_write)++;
+		**p_write = v2;
+		(*p_write)++;
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool same_side( Mth::Vector &p1, Mth::Vector &p2, Mth::Vector &a, Mth::Vector &b )
+{
+	Mth::Vector cp1 = Mth::CrossProduct( b - a, p1 - a );
+	Mth::Vector cp2 = Mth::CrossProduct( b - a, p2 - a );
+	if( Mth::DotProduct( cp1, cp2 ) >= 0.0f )
+		return true;
+    else
+		return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool point_in_triangle( Mth::Vector &p, Mth::Vector &a, Mth::Vector &b, Mth::Vector &c )
+{
+	if( same_side( p, a, b, c ) && same_side( p, b, a, c ) && same_side( p, c, a, b ))
+		return true;
+    else
+		return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline bool line_segment_intersection( float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4 )
+{
+	float den = (( y4 - y3 ) * ( x2 - x1 )) - (( x4 - x3 ) * ( y2 - y1 ));
+
+	if( den == 0.0f )
+	{
+		// Parallel lines.
+		return false;
+	}
+	
+	float num_a = (( x4 - x3 ) * ( y1 - y3 )) - (( y4 - y3 ) * ( x1 - x3 ));
+	float num_b = (( x2 - x1 ) * ( y1 - y3 )) - (( y2 - y1 ) * ( x1 - x3 ));
+
+	num_a /= den;
+	num_b /= den;
+
+	if(( num_a <= 1.0f ) && ( num_b <= 1.0f ))
+		return true;
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline bool tri_texture_intersect( float u0, float v0, float u1, float v1, float u2, float v2 )
+{
+	// Trivial check to see if all three points are outside range of texture.
+	if(( u0 < -1.0f ) && ( u1 < -1.0f ) && ( u2 < -1.0f ))
+		return false;
+	if(( u0 > 1.0f ) && ( u1 > 1.0f ) && ( u2 > 1.0f ))
+		return false;
+	if(( v0 < -1.0f ) && ( v1 < -1.0f ) && ( v2 < -1.0f ))
+		return false;
+	if(( v0 > 1.0f ) && ( v1 > 1.0f ) && ( v2 > 1.0f ))
+		return false;
+	
+	// Check that at least one corner of the texture falls within the tri.
+	Mth::Vector texture_square[4] = {	Mth::Vector( -1.0f, -1.0f, 0.0f, 0.0f ),
+										Mth::Vector(  1.0f, -1.0f, 0.0f, 0.0f ),
+										Mth::Vector(  1.0f,  1.0f, 0.0f, 0.0f ),
+										Mth::Vector( -1.0f,  1.0f, 0.0f, 0.0f )};
+
+	Mth::Vector a( u0, v0, 0.0f );
+	Mth::Vector b( u1, v1, 0.0f );
+	Mth::Vector c( u2, v2, 0.0f );
+
+	for( int p = 0; p < 4; ++p )
+	{
+		if( point_in_triangle( texture_square[p], a, b, c ))
+		{
+			return true;
+		}
+	}
+
+	// No corners of the texture fall within the tri. There are 3 possible explanations:
+	// 1) The tri intersects the texture, but does not contain a texture corner.
+	// 2) The texture lies entirely outside of the tri.
+	// 3) The tri falls entirely within the texture.
+	// Given the relatively small size of the textures, case (3) is extremely unlikely.
+
+	// Perform a trivial check to see whether a corner of the tri lies within the texture. This will catch (3) and sometimes (1).
+	if(( u0 >= -1.0f ) && ( u0 <= 1.0f ) && ( v0 >= -1.0f ) && ( v0 <= 1.0f ))
+		return true;
+	if(( u1 >= -1.0f ) && ( u1 <= 1.0f ) && ( v1 >= -1.0f ) && ( v1 <= 1.0f ))
+		return true;
+	if(( u2 >= -1.0f ) && ( u2 <= 1.0f ) && ( v2 >= -1.0f ) && ( v2 <= 1.0f ))
+		return true;
+
+	// Perform the complete check to see if any line segment forming the tri intersects any line segment forming the texture.
+	for( int p = 0; p < 4; ++p )
+	{
+		int q = ( p + 1 ) % 4;
+		if( line_segment_intersection( u0, v0, u1, v1, texture_square[p][X], texture_square[p][Y], texture_square[q][X], texture_square[q][Y] ))
+			return true;
+	}
+	
+	return false;
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_screen_flash_render( sScreenFlashDetails *p_details )
+{
+	// Create an (opaque) material used to render the mesh.
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= NULL;
+	pass.m_flags			= (1<<5) | (1<<6);		// clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)NxNgc::vBLEND_MODE_BLEND;
+	pass.m_alpha_fix		= (unsigned char)0; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	// Get viewport details.
+	CViewport *p_vp = CViewportManager::sGetActiveViewport( p_details->m_viewport );
+
+	sNgcScreenFlashVert verts[4];
+
+	verts[0].x	= p_vp->GetOriginX() * 640;
+	verts[0].y	= p_vp->GetOriginY() * 448;
+	verts[0].z	= p_details->m_z;
+	
+	verts[1].x	= verts[0].x + ( p_vp->GetWidth() * 640 );
+	verts[1].y	= verts[0].y;
+	verts[1].z	= verts[0].z;
+
+	verts[2].x	= verts[0].x + ( p_vp->GetWidth() * 640 );
+	verts[2].y	= verts[0].y + ( p_vp->GetHeight() * 448 );
+	verts[2].z	= verts[0].z;
+
+	verts[3].x	= verts[0].x;
+	verts[3].y	= verts[0].y + ( p_vp->GetHeight() * 448 );
+	verts[3].z	= verts[0].z;
+
+	for( int v = 0; v < 4; ++v )
+	{
+		verts[v].col = (GXColor){ p_details->m_current.r, p_details->m_current.g, p_details->m_current.b, p_details->m_current.a };
+	}
+
+	if( p_details->mp_texture )
+	{
+		verts[0].u	= 0.0f;
+		verts[0].v	= 0.0f;
+		verts[1].u	= 1.0f;
+		verts[1].v	= 0.0f;
+		verts[2].u	= 1.0f;
+		verts[2].v	= 1.0f;
+		verts[3].u	= 0.0f;
+		verts[3].v	= 1.0f;
+
+		Nx::CNgcTexture *p_Ngc_texture = static_cast( p_details->mp_texture );
+		pass.m_texture.p_data = p_Ngc_texture->GetEngineTexture();
+		pass.m_flags |= (1<<0);
+	}
+	GX::SetZMode ( GX_FALSE, GX_ALWAYS, GX_FALSE );
+
+	NxNgc::multi_mesh( &mat, &pass, true, false );
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Render the triangles
+	if( p_details->mp_texture )
+	{
+		GX::SetVtxDesc( 3, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+	}
+	else
+	{
+		GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+	}
+	GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+	for (int i = 0; i < 4; i++ )
+	{
+		GX::Position3f32( verts[i].x, verts[i].y, verts[i].z );
+		GX::Color1u32(*((u32*)&(verts[i].col)));
+		if( p_details->mp_texture ) GX::TexCoord2f32( verts[i].u, verts[i].v );
+	}
+	GX::End();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_initialize( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_cleanup( void )
+{
+#if 0
+	sNgcSplatInstanceDetails *p_Ngc_details;
+
+	Dbg_Assert( p_splat_details_table );
+	
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	while( p_details )
+	{
+		p_Ngc_details = static_cast( p_details );
+
+		//delete p_Ngc_details->mp_material;
+		
+		p_details = p_splat_details_table->IterateNext();
+
+		p_splat_details_table->FlushItem((uint32)p_Ngc_details );
+		delete p_Ngc_details;
+	}
+#endif		// 0
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_reset_poly( sSplatInstanceDetails *p_details, int index )
+{
+#if 0
+	// Cast the details to Ngc details.
+	sNgcSplatInstanceDetails *p_Ngc_details = static_cast( p_details );
+	
+	// Force this poly to be degenerate.
+	p_Ngc_details->m_verts[index * 3 + 1]	= p_Ngc_details->m_verts[index * 3];
+	p_Ngc_details->m_verts[index * 3 + 2]	= p_Ngc_details->m_verts[index * 3];
+#endif		// 0
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool plat_texture_splat( Nx::CSector **pp_sectors, Nx::CCollStatic **pp_collision, Mth::Vector& start, Mth::Vector& end, float size, float lifetime, Nx::CTexture *p_texture, Nx::sSplatTrailInstanceDetails *p_trail_details )
+{
+#if 0
+	Mth::Matrix view_matrix, ortho_matrix, projection_matrix;
+
+#	if DRAW_DEBUG_LINES
+//	Gfx::AddDebugLine( start, end, MAKE_RGB( 200, 200, 0 ), MAKE_RGB( 200, 200, 0 ), 1 );
+	ADD_LINE( start, end, 200, 200, 0 );
+#	endif // DRAW_DEBUG_LINES
+	
+	// The length of the start->end line defines the view depth of the frustum.
+	Mth::Vector	splat_vector	= end - start;
+	float		view_depth		= splat_vector.Length();
+	splat_vector.Normalize();
+	
+	// Calculate the parallel projection matrix. Generally the projection vector will tend to point downwards, so we use a
+	// random vector in the x-z plane to define the up vector for the projection. However if this splat is part of a trail,
+	// use the previous point to generate the up vector.
+	if( p_trail_details )
+	{
+		Mth::Vector up( start[X] - p_trail_details->m_last_pos[X], start[Y] - p_trail_details->m_last_pos[Y], start[Z] - p_trail_details->m_last_pos[Z], 0.0f );
+
+		// The height of the viewport is defined by the distance between the two points.
+		float height = up.Length() * 0.5f;
+
+		start	-= up * 0.5f;
+		end		-= up * 0.5f;
+
+		up.Normalize();
+
+		Mth::CreateMatrixLookAt( view_matrix, start, end, up );
+		Mth::CreateMatrixOrtho( ortho_matrix, size, height, 0.1f, view_depth );
+	}
+	else if( fabsf( splat_vector[Y] ) > 0.5f )
+	{
+		float angle = ((float)rand() * 2.0f * Mth::PI ) / (float)RAND_MAX;
+		Mth::CreateMatrixLookAt( view_matrix, start, end, Mth::Vector( sinf( angle ), 0.0f, cosf( angle ), 0.0f ));
+		Mth::CreateMatrixOrtho( ortho_matrix , size, size, 0.1f, view_depth );
+	}
+	else
+	{
+		Mth::CreateMatrixLookAt( view_matrix, start, end, Mth::Vector( 0.0f, 1.0f, 0.0f, 0.0f ));
+		Mth::CreateMatrixOrtho( ortho_matrix , size, size, 0.1f, view_depth );
+	}
+
+	projection_matrix = view_matrix * ortho_matrix;
+
+	// Pointer to the mesh we will be modifying. (Don't want to set the pointer up until we know for
+	// sure that we will be adding some polys).
+	sNgcSplatInstanceDetails	*p_details		= NULL;
+	sNgcVert					*p_target_verts	= NULL;
+
+	Nx::CSector *p_sector;
+	while( (p_sector = *pp_sectors) )
+	{
+		Nx::CNgcGeom *p_Ngc_geom = static_cast( p_sector->GetGeom());
+
+		if( p_Ngc_geom )
+		{
+#			if DRAW_DEBUG_LINES
+			Mth::Vector min = p_Ngc_geom->GetBoundingBox().GetMin();
+			Mth::Vector max = p_Ngc_geom->GetBoundingBox().GetMax();
+
+			Mth::Vector box[8];
+			box[0] = box[1] = box[2] = box[3] = max;
+			box[1][X] = min[X];
+			box[2][Y] = min[Y];
+			box[3][Z] = min[Z];
+			box[5] = box[6] = box[7] = box[4] = min;;
+			box[5][X] = max[X];
+			box[6][Y] = max[Y];
+			box[7][Z] = max[Z];
+
+			for ( int i = 1; i < 4; i++ )
+			{
+//				Gfx::AddDebugLine( box[0], box[i], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+				ADD_LINE( box[0], box[i], 200, 0, 0 );
+			}
+			for ( int i = 5; i < 8; i++ )
+			{
+//				Gfx::AddDebugLine( box[4], box[i], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+				ADD_LINE( box[4], box[i], 200, 0, 0 );
+			}
+//			Gfx::AddDebugLine( box[1], box[6], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//			Gfx::AddDebugLine( box[1], box[7], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//			Gfx::AddDebugLine( box[2], box[5], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//			Gfx::AddDebugLine( box[2], box[7], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//			Gfx::AddDebugLine( box[3], box[5], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//			Gfx::AddDebugLine( box[3], box[6], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			ADD_LINE( box[1], box[6], 200, 0, 0 );
+			ADD_LINE( box[1], box[7], 200, 0, 0 );
+			ADD_LINE( box[2], box[5], 200, 0, 0 );
+			ADD_LINE( box[2], box[7], 200, 0, 0 );
+			ADD_LINE( box[3], box[5], 200, 0, 0 );
+			ADD_LINE( box[3], box[6], 200, 0, 0 );
+#			endif // DRAW_DEBUG_LINES
+			
+			// For each mesh in the geom...
+			for( uint32 m = 0; m < p_Ngc_geom->m_num_mesh; ++m )
+			{
+				NxNgc::sMesh *p_mesh = p_Ngc_geom->m_mesh_array[m];
+				
+				NxNgc::sScene *p_scene = p_Ngc_geom->mp_scene->GetEngineScene();
+
+				// Not allowed on meshes which are flagged not to shadow.
+				if( p_mesh->m_flags & NxNgc::sMesh::MESH_FLAG_NO_SKATER_SHADOW )
+					continue;
+				
+				// Count indices.
+				int num_indices = 0;
+				unsigned char * p_start = (unsigned char *)&p_mesh->mp_dl[1];
+//				uint32 cp = (p_start[2]<<24)|(p_start[3]<<16)|(p_start[4]<<8)|p_start[5];
+				unsigned char * p_end = &p_start[p_mesh->mp_dl->m_size];
+				p_start = &p_start[p_mesh->mp_dl->m_index_offset];		// Skip to actual 1st GDBegin.
+				unsigned char * p8 = p_start;
+
+				int stride = p_mesh->mp_dl->m_index_stride;
+
+				unsigned char * p_index_buffer  = &p8[1];
+
+				while ( p8 < p_end )
+				{
+					if ( ( p8[0] & 0xf8 ) == GX_TRIANGLESTRIP )
+					{
+						// Found a triangle strip - parse it.
+						int num_verts = ( p8[1] << 8 ) | p8[2];
+						p8 += 3;		// Skip GDBegin
+
+						num_indices += num_verts;
+
+						p8 += stride * 2 * num_verts;
+					}
+					else
+					{
+						break;
+					}
+				}
+
+				if ( p_index_buffer )
+				{
+					// Create the index buffer.
+					uint polys = 0;
+					uint16 idxbuf[2048*3];		// 12k.
+					uint16 * p_dest = idxbuf;
+					uint8 * p_source = (uint8*)p_index_buffer;
+					uint16 total = 0;
+					while ( total < num_indices )
+					{
+						uint16 num = ( p_source[0] << 8 ) | p_source[1];
+						p_source += 2;
+						uint16 i0;
+						uint16 i1 = ( p_source[0] << 8 ) | p_source[1];
+						p_source += stride * 2;
+						uint16 i2 = ( p_source[0] << 8 ) | p_source[1];
+						p_source += stride * 2;
+						for ( int vv = 2; vv < num; vv++ )
+						{
+							i0 = i1;
+							i1 = i2;
+							i2 = ( p_source[0] << 8 ) | p_source[1];
+							p_source += stride * 2;
+							*p_dest++ = i0;
+							*p_dest++ = i1;
+							*p_dest++ = i2;
+							polys++;
+						}
+						p_source += 1;
+						total += num + 1;		// Count each index + the count.
+					}
+
+					// Now scan through each non-degenerate tri, checking the verts to see if they are within scope.
+					for( uint32 i = 0; i < polys; ++i )
+					{
+						// Wrap the indices round.
+						uint16 index0 = idxbuf[(i*3)+0];
+						uint16 index1 = idxbuf[(i*3)+1];
+						uint16 index2 = idxbuf[(i*3)+2];
+
+						if(( index0 != index1 ) && ( index0 != index2 ) && ( index1 != index2 ))
+						{
+							Mth::Vector v0( p_scene->mp_pos_pool[(index0*3)+0],
+											p_scene->mp_pos_pool[(index0*3)+1],
+											p_scene->mp_pos_pool[(index0*3)+2] );
+							Mth::Vector v1( p_scene->mp_pos_pool[(index1*3)+0],
+											p_scene->mp_pos_pool[(index1*3)+1],
+											p_scene->mp_pos_pool[(index1*3)+2] );
+							Mth::Vector v2( p_scene->mp_pos_pool[(index2*3)+0],
+											p_scene->mp_pos_pool[(index2*3)+1],
+											p_scene->mp_pos_pool[(index2*3)+2] );
+
+							v0[W] = v1[W] = v2[W] = 1.0f;		// Make sure they are points, not vectors
+
+	//						OSReport( "Looking at: (%6.3f, %6.3f, %6.3f) (%6.3f, %6.3f, %6.3f) (%6.3f, %6.3f, %6.3f)\n", v0[X], v0[Y], v0[Z], v1[X], v1[Y], v1[Z], v2[X], v2[Y], v2[Z] );
+
+							Mth::Vector uvprojections[3];
+							uvprojections[0] = v0 * projection_matrix;
+							uvprojections[1] = v1 * projection_matrix;
+							uvprojections[2] = v2 * projection_matrix;
+
+							// Check that they are in the frustum
+//							if(( uvprojections[0][X] < -1.0f ) && ( uvprojections[1][X] < -1.0f ) && ( uvprojections[2][X] < -1.0f ))
+//								continue;
+//							if(( uvprojections[0][Y] < -1.0f ) && ( uvprojections[1][Y] < -1.0f ) && ( uvprojections[2][Y] < -1.0f ))
+//								continue;
+//							if(( uvprojections[0][X] > 1.0f ) && ( uvprojections[1][X] > 1.0f ) && ( uvprojections[2][X] > 1.0f ))
+//								continue;
+//							if(( uvprojections[0][Y] > 1.0f ) && ( uvprojections[1][Y] > 1.0f ) && ( uvprojections[2][Y] > 1.0f ))
+//								continue;
+							if(( uvprojections[0][Z] < -1.0f ) && ( uvprojections[1][Z] < -1.0f ) && ( uvprojections[2][Z] < -1.0f ))
+								continue;
+							if(( uvprojections[0][Z] > 1.0f ) && ( uvprojections[1][Z] > 1.0f ) && ( uvprojections[2][Z] > 1.0f ))
+								continue;
+
+							// Okay, this tri lies within the projection frustum. Now check that it intersects the texture
+							if( !tri_texture_intersect( uvprojections[0][X], uvprojections[0][Y],
+														uvprojections[1][X], uvprojections[1][Y],
+														uvprojections[2][X], uvprojections[2][Y] ))
+							{
+								continue;
+							}
+
+//#ifdef SHORT_VERT
+							Mth::Vector	s = v1 - v0;
+							Mth::Vector	t = v2 - v0;
+							Mth::Vector normal = Mth::CrossProduct( s, t );
+							normal.Normalize();
+
+							v0 -= normal;
+							v1 -= normal;
+							v2 -= normal;
+//#endif		// SHORT_VERT 
+
+	//						OSReport( "Added: (%5.1f, %5.1f, %5.1f) (%5.1f, %5.1f, %5.1f) (%5.1f, %5.1f, %5.1f)\n", v0[X], v0[Y], v0[Z], v1[X], v1[Y], v1[Z], v2[X], v2[Y], v2[Z] );
+
+							// Okay, this tri lies within the projection frustum. Get a pointer to the mesh used for rendering texture splats
+							// with the given texture. (Note this will create a new instance to handle texture splats of this texture if one
+							// does not already exist).
+							if( p_target_verts == NULL )
+							{
+								CNgcTexture *p_Ngc_texture	= static_cast( p_texture );
+								p_details						= getDetailsForTextureSplat( p_Ngc_texture->GetEngineTexture());
+								p_target_verts					= p_details->m_verts;
+								Dbg_Assert( p_target_verts );
+							}
+
+							// If we have trails, scale up the mapping by one pixel in all directions.
+							// (effectively 2 pixels in u and 2 pixels in v ).
+							float up_one_u_pixel;
+							float up_one_v_pixel;
+							if( p_trail_details )
+							{
+								up_one_u_pixel = 1.0f - ( 1.0f / ( p_texture->GetWidth() / 2.0f ) );
+								up_one_v_pixel = 1.0f - ( 1.0f / ( p_texture->GetHeight() / 2.0f ) );
+							}
+							else
+							{
+								up_one_u_pixel = 1.0f;
+								up_one_v_pixel = 1.0f;
+							}
+
+							// Scan through the lifetimes, finding a 'dead' poly (lifetime == 0), or the oldest.
+							uint32 idx						= p_details->GetOldestSplat();
+
+							// Convert lifetime from seconds to milliseconds.
+							p_details->m_lifetimes[idx]		= (int)( lifetime * 1000.0f );
+
+							// Set up the corresponding vertices. First write the positions.
+							uint32 index					= idx * 3;
+							p_target_verts[index + 0].pos	= v0;
+							p_target_verts[index + 1].pos	= v1;
+							p_target_verts[index + 2].pos	= v2;
+
+							// Then the uv's.
+							p_target_verts[index + 0].u		= (s16)( ( ( ( uvprojections[0][X] * 0.5f ) + 0.5f ) * up_one_u_pixel ) * 256.0f );
+							p_target_verts[index + 0].v		= (s16)( ( ( ( uvprojections[0][Y] * 0.5f ) + 0.5f ) * up_one_v_pixel ) * 256.0f );
+							p_target_verts[index + 1].u		= (s16)( ( ( ( uvprojections[1][X] * 0.5f ) + 0.5f ) * up_one_u_pixel ) * 256.0f );
+							p_target_verts[index + 1].v		= (s16)( ( ( ( uvprojections[1][Y] * 0.5f ) + 0.5f ) * up_one_v_pixel ) * 256.0f );
+							p_target_verts[index + 2].u		= (s16)( ( ( ( uvprojections[2][X] * 0.5f ) + 0.5f ) * up_one_u_pixel ) * 256.0f );
+							p_target_verts[index + 2].v		= (s16)( ( ( ( uvprojections[2][Y] * 0.5f ) + 0.5f ) * up_one_v_pixel ) * 256.0f );
+
+							// Now the colors
+							p_target_verts[index + 0].col = (GXColor){128,128,128,255};		// p_coll_geom->GetVertexRGBA(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 0));
+							p_target_verts[index + 1].col = (GXColor){128,128,128,255};     // p_coll_geom->GetVertexRGBA(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 1));
+							p_target_verts[index + 2].col = (GXColor){128,128,128,255};     // p_coll_geom->GetVertexRGBA(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 2));
+//.col
+
+							Mth::Vector	*p_v0 = &( p_target_verts[index + 0].pos );
+							Mth::Vector	*p_v1 = &( p_target_verts[index + 1].pos );
+							Mth::Vector	*p_v2 = &( p_target_verts[index + 2].pos );
+							Mth::Vector pv(	p_v1->GetX() - p_v0->GetX(), p_v1->GetY() - p_v0->GetY(), p_v1->GetZ() - p_v0->GetZ() );
+							Mth::Vector qv(	p_v2->GetX() - p_v0->GetX(), p_v2->GetY() - p_v0->GetY(), p_v2->GetZ() - p_v0->GetZ() );
+							Mth::Vector r(( pv[Y] * qv[Z] ) - ( qv[Y] * pv[Z] ), ( pv[Z] * qv[X] ) - ( qv[Z] * pv[X] ), ( pv[X] * qv[Y] ) - ( qv[X] * pv[Y] ));
+							float area_squared = r.LengthSqr();
+				
+							// Set the shatter test to ensure that we don't subdivide too far. Note that each successive subdivision will quarter
+							// the area of each triangle, which means the area *squared* of each triangle will become 1/16th of the previous value.
+							shatterAreaTest = area_squared / 128.0f;
+				
+							triSubdivideStack.Reset();
+							triSubdivideStack.SetBlockSize( sizeof(sNgcVert) );
+							triSubdivideStack.Push( &p_target_verts[index + 0] );
+							triSubdivideStack.Push( &p_target_verts[index + 1] );
+							triSubdivideStack.Push( &p_target_verts[index + 2] );
+				
+							// Allocate a block of memory into which the subdivision stack will write the results.
+							Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+							sNgcVert		*p_array		= new sNgcVert[1024];
+							sNgcVert		*p_array_start	= p_array;
+							sNgcVert		*p_array_loop	= p_array;
+							//memset( p_array, 0, sizeof(sNgcVert) * 1024 );
+							Mem::Manager::sHandle().PopContext();
+				
+							while( subdivide_tri_stack( &p_array, NULL ));
+				
+							// Ensure we haven't overrun the buffer.
+							Dbg_Assert((uint32)p_array - (uint32)p_array_loop < ( sizeof(sNgcVert) * 1024 ));
+				
+							float oo_up_one_u_pixel = 1.0f / up_one_u_pixel;
+							float oo_up_one_v_pixel = 1.0f / up_one_v_pixel;
+				
+				//			int vert_idx = 0;
+							bool no_polys = true;
+				
+							while( p_array_loop != p_array )
+							{
+								Dbg_Assert(((uint32) p_array_loop) < ((uint32) p_array));
+				//				Dbg_Message("Looking at vert %d", vert_idx);
+								// Add this triangle, *if* it is valid.
+								if( tri_texture_intersect(((p_array_loop[0].u * oo_up_one_u_pixel) - 0.5f) * 2.0f,
+														  ((p_array_loop[0].v * oo_up_one_v_pixel) - 0.5f) * 2.0f,
+														  ((p_array_loop[1].u * oo_up_one_u_pixel) - 0.5f) * 2.0f,
+														  ((p_array_loop[1].v * oo_up_one_v_pixel) - 0.5f) * 2.0f,
+														  ((p_array_loop[2].u * oo_up_one_u_pixel) - 0.5f) * 2.0f,
+														  ((p_array_loop[2].v * oo_up_one_v_pixel) - 0.5f) * 2.0f ))
+				//				if( tri_texture_intersect((p_array_loop[0].u * 2.0f * oo_up_one_u_pixel) - 1.0f,
+				//										  (p_array_loop[0].v * 2.0f * oo_up_one_v_pixel) - 1.0f,
+				//										  (p_array_loop[1].u * 2.0f * oo_up_one_u_pixel) - 1.0f,
+				//										  (p_array_loop[1].v * 2.0f * oo_up_one_v_pixel) - 1.0f,
+				//										  (p_array_loop[2].u * 2.0f * oo_up_one_u_pixel) - 1.0f,
+				//										  (p_array_loop[2].v * 2.0f * oo_up_one_v_pixel) - 1.0f ))
+								{
+				//					Dbg_Message("Accepted vert %d", vert_idx);
+									// Convert lifetime from seconds to milliseconds.
+									p_details->m_lifetimes[idx]		= (int)( lifetime * 1000.0f );
+				
+									p_target_verts[index + 0].pos	= p_array_loop[0].pos;
+									p_target_verts[index + 1].pos	= p_array_loop[1].pos;
+									p_target_verts[index + 2].pos	= p_array_loop[2].pos;
+				
+									p_target_verts[index + 0].u		= p_array_loop[0].u;
+									p_target_verts[index + 0].v		= p_array_loop[0].v;
+									p_target_verts[index + 1].u		= p_array_loop[1].u;
+									p_target_verts[index + 1].v		= p_array_loop[1].v;
+									p_target_verts[index + 2].u		= p_array_loop[2].u;
+									p_target_verts[index + 2].v		= p_array_loop[2].v;
+				
+									p_target_verts[index + 0].col	= p_array_loop[0].col;
+									p_target_verts[index + 1].col	= p_array_loop[1].col;
+									p_target_verts[index + 2].col	= p_array_loop[2].col;
+				
+									idx								= p_details->GetOldestSplat();
+									index							= idx * 3;
+									Dbg_Assert((index + 2) < (SPLAT_POLYS_PER_MESH * 3));
+									no_polys 						= false;
+								}
+				
+								p_array_loop					+= 3;
+				//				vert_idx++;
+							}
+				
+							delete [] p_array_start;
+				
+							// Check if all the new polys were rejected.  Don't know why this happens, but we want
+							// to get rid of the original texture.
+							if (no_polys)
+							{
+								plat_texture_splat_reset_poly( p_details, idx );
+							}
+				
+				
+
+	//						OSReport( "Add U0: %6.1f %6.1f\n", p_target_verts[index + 0].u, p_target_verts[index + 0].v );
+	//						OSReport( "Add U1: %6.1f %6.1f\n", p_target_verts[index + 1].u, p_target_verts[index + 1].v );
+	//						OSReport( "Add U2: %6.1f %6.1f\n", p_target_verts[index + 2].u, p_target_verts[index + 2].v );
+
+	#						if DRAW_DEBUG_LINES
+	//						D3DXVECTOR3* p_d3dvert;
+	//						p_d3dvert = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index0 ));
+	//						Mth::Vector v0( p_d3dvert->x, p_d3dvert->y, p_d3dvert->z );
+	//						p_d3dvert = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index1 ));
+	//						Mth::Vector v1( p_d3dvert->x, p_d3dvert->y, p_d3dvert->z );
+	//						p_d3dvert = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index2 ));
+	//						Mth::Vector v2( p_d3dvert->x, p_d3dvert->y, p_d3dvert->z );
+	//						Gfx::AddDebugLine( v0, v1, MAKE_RGB( 0, 200, 200 ), MAKE_RGB( 0, 200, 200 ), 1 );
+	//						Gfx::AddDebugLine( v1, v2, MAKE_RGB( 0, 200, 200 ), MAKE_RGB( 0, 200, 200 ), 1 );
+	//						Gfx::AddDebugLine( v2, v0, MAKE_RGB( 0, 200, 200 ), MAKE_RGB( 0, 200, 200 ), 1 );
+
+	//						v0[Y] += 10;
+	//						v1[Y] += 10;
+	//						v2[Y] += 10;
+
+							ADD_LINE( v0, v1, 0, 200, 200 );
+							ADD_LINE( v1, v2, 0, 200, 200 );
+							ADD_LINE( v2, v0, 0, 200, 200 );
+	#						endif // DRAW_DEBUG_LINES
+						}
+					}
+				}
+			}
+		}
+		++pp_sectors;
+	}
+#endif		// 0
+	
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_render( void )
+{
+#if 0
+//#	if DRAW_DEBUG_LINES
+//#	endif 		//DRAW_DEBUG_LINES
+//
+
+
+	sNgcSplatInstanceDetails *p_Ngc_details;
+
+	Dbg_Assert( p_splat_details_table );
+
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	while( p_details )
+	{
+		p_Ngc_details = static_cast( p_details );
+
+		if( p_Ngc_details->m_highest_active_splat >= 0 )
+		{
+//			NxNgc::multi_mesh( &p_Ngc_details->m_mat, &p_Ngc_details->m_pass, true, true );
+
+			GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+		
+//			NxNgc::sMaterialHeader mat;
+//			NxNgc::sMaterialPassHeader pass;
+//		
+//			mat.m_checksum			= 0;
+//			mat.m_passes			= 1;
+//			mat.m_alpha_cutoff		= 0;
+//			mat.m_flags				= 0;
+//			mat.m_material_dl_id	= 0;
+//			mat.m_draw_order		= 0;
+//			mat.m_pass_item			= 0;
+//			mat.m_texture_dl_id		= 0;
+//		
+//			pass.m_texture.p_data	= p_Ngc_details->mp_texture;
+//			pass.m_flags			= (1<<0)|(1<<5)|(1<<6);
+//			pass.m_filter			= 0;
+//			pass.m_blend_mode		= (unsigned char)NxNgc::vBLEND_MODE_BLEND;
+//			pass.m_alpha_fix		= 128;
+//			pass.m_uv_wibble_index	= 0;
+//			pass.m_color			= (GXColor){128,128,128,255};
+//			pass.m_k				= 0;
+//			pass.m_u_tile			= 0;
+//			pass.m_v_tile			= 0;
+//		
+//			multi_mesh( &mat, &pass, true, true );
+
+
+
+
+
+//			GX::SetPointSize( 6, GX_TO_ONE );
+//
+//			GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+//			GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+//			GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+//			GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+//
+//			GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+//												   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV,
+//												   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//
+//			GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_RASC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+//											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+//			GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+//			GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//			GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,0,255,255} );
+//
+//			GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+//
+//			sNgcVert *p_vert_array = p_Ngc_details->m_verts;
+//			for (int i = 0; i < ( p_details->m_highest_active_splat + 1 ); i++, p_vert_array += 3)
+//			{
+////				printf( "Splat Tri:\n%8.3f %8.3f %8.3f\n", p_vert_array[0].pos[X], p_vert_array[0].pos[Y], p_vert_array[0].pos[Z] );
+////				printf( "%8.3f %8.3f %8.3f\n", p_vert_array[1].pos[X], p_vert_array[1].pos[Y], p_vert_array[1].pos[Z] );
+////				printf( "%8.3f %8.3f %8.3f\n", p_vert_array[2].pos[X], p_vert_array[2].pos[Y], p_vert_array[2].pos[Z] );
+//				GX::Begin( GX_LINES, GX_VTXFMT0, 2 ); 
+//				GX::Position3f32( p_vert_array[0].pos[X], p_vert_array[0].pos[Y], p_vert_array[0].pos[Z] );
+//				GX::Position3f32( p_vert_array[1].pos[X], p_vert_array[1].pos[Y], p_vert_array[1].pos[Z] );
+//				GX::End(); 
+//				GX::Begin( GX_LINES, GX_VTXFMT0, 2 ); 
+//				GX::Position3f32( p_vert_array[1].pos[X], p_vert_array[1].pos[Y], p_vert_array[1].pos[Z] );
+//				GX::Position3f32( p_vert_array[2].pos[X], p_vert_array[2].pos[Y], p_vert_array[2].pos[Z] );
+//				GX::End(); 
+//				GX::Begin( GX_LINES, GX_VTXFMT0, 2 ); 
+//				GX::Position3f32( p_vert_array[2].pos[X], p_vert_array[2].pos[Y], p_vert_array[2].pos[Z] );
+//				GX::Position3f32( p_vert_array[0].pos[X], p_vert_array[0].pos[Y], p_vert_array[0].pos[Z] );
+//				GX::End(); 
+//			}
+////			GX::End();
+//
+//
+
+
+
+
+
+			GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+			GX::UploadTexture(  p_Ngc_details->mp_texture->pTexelData,
+								p_Ngc_details->mp_texture->ActualWidth,
+								p_Ngc_details->mp_texture->ActualHeight,
+								GX_TF_CMPR,
+								GX_CLAMP,
+								GX_CLAMP,
+								GX_FALSE,
+								GX_LINEAR,
+								GX_LINEAR,
+								0.0f,
+								0.0f,
+								0.0f,
+								GX_FALSE,
+								GX_TRUE,
+								GX_ANISO_1,
+								GX_TEXMAP0 ); 
+			GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, p_Ngc_details->mp_texture->ActualWidth, p_Ngc_details->mp_texture->ActualHeight );
+		
+			GX::SetTexChanTevIndCull( 1, 0, 1, 0, GX_CULL_NONE );
+			GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+			GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+		
+			GX::SetAlphaCompare(GX_ALWAYS, 0, GX_AOP_AND, GX_ALWAYS, 0 );
+			GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );		// Replace
+//			GX::SetBlendMode( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+		
+			GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+			GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO,
+									 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV,
+									 GX_TEV_SWAP0, GX_TEV_SWAP0 );
+			GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_TEXC, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO,
+								 GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_DISABLE, GX_TEVPREV );
+
+
+			f32 pm[GX_PROJECTION_SZ];
+			GX::GetProjectionv( pm );
+			float value = pm[6] + ( 150.0f * 5 ) * pm[5];
+
+			GXWGFifo.u8 = GX_LOAD_XF_REG;
+			GXWGFifo.u16 = 0;
+			GXWGFifo.u16 = 0x1025;
+			GXWGFifo.f32 = value;
+
+
+//			GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+//			GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+			// Render the triangles
+			GX::SetVtxDesc( 3, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+//			GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+			GX::Begin( GX_TRIANGLES, GX_VTXFMT6, ( p_details->m_highest_active_splat + 1 ) * 3 ); 
+			sNgcVert *p_vert_array = p_Ngc_details->m_verts;
+			for (int i = 0; i < ( p_details->m_highest_active_splat + 1 ); i++, p_vert_array += 3)
+			{
+				GX::Position3f32( p_vert_array[0].pos[X], p_vert_array[0].pos[Y], p_vert_array[0].pos[Z] );
+				GX::Color1u32(*((u32*)&(p_vert_array[0].col)));
+				GX::TexCoord2s16( p_vert_array[0].u, p_vert_array[0].v );
+
+				GX::Position3f32( p_vert_array[1].pos[X], p_vert_array[1].pos[Y], p_vert_array[1].pos[Z] );
+				GX::Color1u32(*((u32*)&(p_vert_array[1].col)));
+				GX::TexCoord2s16( p_vert_array[1].u, p_vert_array[1].v );
+
+				GX::Position3f32( p_vert_array[2].pos[X], p_vert_array[2].pos[Y], p_vert_array[2].pos[Z] );
+				GX::Color1u32(*((u32*)&(p_vert_array[2].col)));
+				GX::TexCoord2s16( p_vert_array[2].u, p_vert_array[2].v );
+
+//				GX::Position3f32( 0.0f, 0.0f, 0.0f );
+//				GX::Position3f32( 0.0f, 0.0f, 0.0f );
+//				GX::Position3f32( 0.0f, 0.0f, 0.0f );
+//				GX::Position3f32( 0.0f, 0.0f, 0.0f );
+//  			GX::Position3f32( 0.0f, 0.0f, 0.0f );
+//				GX::Position3f32( 0.0f, 0.0f, 0.0f );
+			}
+			GX::End();
+		}
+		
+		p_details = p_splat_details_table->IterateNext();
+	}
+#endif		// 0
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter_initialize( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter_cleanup( void )
+{
+	sNgcShatterInstanceDetails *p_Ngc_details;
+
+	Dbg_Assert( p_shatter_details_table );
+	
+	p_shatter_details_table->IterateStart();
+	sShatterInstanceDetails *p_details = p_shatter_details_table->IterateNext();
+	while( p_details )
+	{
+		p_Ngc_details = static_cast( p_details );
+		
+		p_details = p_shatter_details_table->IterateNext();
+
+		p_shatter_details_table->FlushItem((uint32)p_Ngc_details );
+		delete p_Ngc_details;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter( CGeom *p_geom )
+{
+#if 1
+	CNgcGeom *p_Ngc_geom = static_cast( p_geom );
+
+	// For each mesh in the geom...
+	for( uint32 m = 0; m < p_Ngc_geom->m_num_mesh; ++m )
+	{
+		NxNgc::sMesh *p_mesh = p_Ngc_geom->m_mesh_array[m];
+
+		NxNgc::sScene *p_scene = p_Ngc_geom->mp_scene->GetEngineScene();
+
+		// Count indices.
+		int num_indices = 0;
+		unsigned char * p_start = (unsigned char *)&p_mesh->mp_dl[1];
+		uint32 cp = (p_start[2]<<24)|(p_start[3]<<16)|(p_start[4]<<8)|p_start[5];
+		unsigned char * p_end = &p_start[p_mesh->mp_dl->m_size];
+		p_start = &p_start[p_mesh->mp_dl->m_index_offset];		// Skip to actual 1st GDBegin.
+		unsigned char * p8 = p_start;
+
+		int stride = p_mesh->mp_dl->m_index_stride;
+
+		unsigned char * p_index_buffer  = &p8[1];
+
+		int off = 2 + ( ( cp & ((1<<11)|(1<<12)) ) ? 2 : 0 );
+
+		GXVtxFmt format = GX_VTXFMT2;
+
+		while ( p8 < p_end )
+		{
+			if ( ( p8[0] & 0xf8 ) == GX_TRIANGLESTRIP )
+			{
+				format = (GXVtxFmt)(p8[0] & 7);
+				// Found a triangle strip - parse it.
+				int num_verts = ( p8[1] << 8 ) | p8[2];
+				p8 += 3;		// Skip GDBegin
+
+				num_indices += num_verts;
+
+				p8 += stride * 2 * num_verts;
+			}
+			else
+			{
+				break;
+			}
+		}
+
+		if( num_indices >= 3 )
+		{
+			triSubdivideStack.SetBlockSize( sizeof( sNgcVert ) );
+
+			// Create the index buffer.
+			uint polys = 0;
+			uint16 idxbuf[1024*3];		// 6k.
+			uint16 cidxbuf[1024*3];		// 6k.
+			uint16 tidxbuf[1024*3];		// 6k.
+			uint16 * p_dest = idxbuf;
+			uint16 * p_cdest = cidxbuf;
+			uint16 * p_tdest = tidxbuf;
+			uint8 * p_source = (uint8*)p_index_buffer;
+			uint16 total = 0;
+
+			while ( total < num_indices )
+			{
+				uint16 num = ( p_source[0] << 8 ) | p_source[1];
+				p_source += 2;
+				uint16 i0, ci0, ti0;
+				uint16 i1 = ( p_source[0] << 8 ) | p_source[1];
+				uint16 ci1 = ( p_source[off+0] << 8 ) | p_source[off+1];
+				uint16 ti1 = ( p_source[off+2] << 8 ) | p_source[off+3];
+				p_source += stride * 2;
+				uint16 i2 = ( p_source[0] << 8 ) | p_source[1];
+				uint16 ci2 = ( p_source[off+0] << 8 ) | p_source[off+1];
+				uint16 ti2 = ( p_source[off+2] << 8 ) | p_source[off+3];
+				p_source += stride * 2;
+				for ( int vv = 2; vv < num; vv++ )
+				{
+					i0 = i1;
+					i1 = i2;
+					i2 = ( p_source[0] << 8 ) | p_source[1];
+					ci0 = ci1;
+					ci1 = ci2;
+					ci2 = ( p_source[off+0] << 8 ) | p_source[off+1];
+					ti0 = ti1;
+					ti1 = ti2;
+					ti2 = ( p_source[off+2] << 8 ) | p_source[off+3];
+					p_source += stride * 2;
+					*p_dest++ = i0;
+					*p_dest++ = i1;
+					*p_dest++ = i2;
+					*p_cdest++ = ci0;
+					*p_cdest++ = ci1;
+					*p_cdest++ = ci2;
+					*p_tdest++ = ti0;
+					*p_tdest++ = ti1;
+					*p_tdest++ = ti2;
+					polys++;
+				}
+				p_source += 1;
+				total += num + 1;		// Count each index + the count.
+			}
+
+			// First scan through each non-degenerate tri, counting them to see how many verts we'll need.
+			// We also have to figure the area of the tris here, since we need to calculate the worst case given the requirements for subdivision.
+			uint32 valid_tris	= 0;
+			for( uint32 i = 0; i < polys; ++i )
+			{
+				// Wrap the indices round.
+				uint16 index0 = idxbuf[(i*3)+0];
+				uint16 index1 = idxbuf[(i*3)+1];
+				uint16 index2 = idxbuf[(i*3)+2];
+
+				uint16 cindex0 = cidxbuf[(i*3)+0];
+				uint16 cindex1 = cidxbuf[(i*3)+1];
+				uint16 cindex2 = cidxbuf[(i*3)+2];
+
+				uint16 tindex0 = tidxbuf[(i*3)+0];
+				uint16 tindex1 = tidxbuf[(i*3)+1];
+				uint16 tindex2 = tidxbuf[(i*3)+2];
+
+				if(( index0 != index1 ) && ( index0 != index2 ) && ( index1 != index2 ))
+				{
+					++valid_tris;
+
+					sNgcVert v0;
+					sNgcVert v1;
+					sNgcVert v2;
+
+					if ( p_mesh->mp_dl->mp_pos_pool )
+					{
+						v0.pos.Set( p_mesh->mp_dl->mp_pos_pool[(index0*3)+0],
+									p_mesh->mp_dl->mp_pos_pool[(index0*3)+1],
+									p_mesh->mp_dl->mp_pos_pool[(index0*3)+2] );
+						v1.pos.Set( p_mesh->mp_dl->mp_pos_pool[(index1*3)+0],
+									p_mesh->mp_dl->mp_pos_pool[(index1*3)+1],
+									p_mesh->mp_dl->mp_pos_pool[(index1*3)+2] );
+						v2.pos.Set( p_mesh->mp_dl->mp_pos_pool[(index2*3)+0],
+									p_mesh->mp_dl->mp_pos_pool[(index2*3)+1],
+									p_mesh->mp_dl->mp_pos_pool[(index2*3)+2] );
+					}
+					else
+					{
+						v0.pos.Set( p_scene->mp_pos_pool[(index0*3)+0],
+									p_scene->mp_pos_pool[(index0*3)+1],
+									p_scene->mp_pos_pool[(index0*3)+2] );
+						v1.pos.Set( p_scene->mp_pos_pool[(index1*3)+0],
+									p_scene->mp_pos_pool[(index1*3)+1],
+									p_scene->mp_pos_pool[(index1*3)+2] );
+						v2.pos.Set( p_scene->mp_pos_pool[(index2*3)+0],
+									p_scene->mp_pos_pool[(index2*3)+1],
+									p_scene->mp_pos_pool[(index2*3)+2] );
+					}
+
+					if ( p_scene->mp_col_pool )
+					{
+						if ( p_mesh->mp_dl->mp_col_pool )
+						{
+							v0.col = *((GXColor*)&p_mesh->mp_dl->mp_col_pool[cindex0]);
+							v1.col = *((GXColor*)&p_mesh->mp_dl->mp_col_pool[cindex1]);
+							v2.col = *((GXColor*)&p_mesh->mp_dl->mp_col_pool[cindex2]);
+						}
+						else
+						{
+							v0.col = *((GXColor*)&p_scene->mp_col_pool[cindex0]);
+							v1.col = *((GXColor*)&p_scene->mp_col_pool[cindex1]);
+							v2.col = *((GXColor*)&p_scene->mp_col_pool[cindex2]);
+						}
+					}
+
+					if ( p_scene->mp_tex_pool )
+					{
+						v0.u = p_scene->mp_tex_pool[(tindex0*2)+0];
+						v0.v = p_scene->mp_tex_pool[(tindex0*2)+1];
+						v1.u = p_scene->mp_tex_pool[(tindex1*2)+0];
+						v1.v = p_scene->mp_tex_pool[(tindex1*2)+1];
+						v2.u = p_scene->mp_tex_pool[(tindex2*2)+0];
+						v2.v = p_scene->mp_tex_pool[(tindex2*2)+1];
+					}
+
+					v0.pos[W] = v1.pos[W] = v2.pos[W] = 1.0f;		// Make sure they are points, not vectors
+					
+					// Push this tri onto the stack.
+					triSubdivideStack.Push( &v0 );
+					triSubdivideStack.Push( &v1 );
+					triSubdivideStack.Push( &v2 );
+
+					// Figure the area of this tri.
+					Mth::Vector p( v1.pos[X] - v0.pos[X], v1.pos[Y] - v0.pos[Y], v1.pos[Z] - v0.pos[Z], 0.0f );
+					Mth::Vector q( v2.pos[X] - v0.pos[X], v2.pos[Y] - v0.pos[Y], v2.pos[Z] - v0.pos[Z], 0.0f );
+					Mth::Vector r(( p[Y] * q[Z] ) - ( q[Y] * p[Z] ), ( p[Z] * q[X] ) - ( q[Z] * p[X] ), ( p[X] * q[Y] ) - ( q[X] * p[Y] ), 0.0f);
+					float area_squared = r.LengthSqr();
+					if( area_squared > shatterAreaTest )
+					{
+						// We will need to subdivide - each subdivision will result in an area one quarter the previous area
+						// (and thusly the square of the area will be one sixteenth the previous area).
+						int num_extra_tris = 1;
+						while( area_squared > shatterAreaTest )
+						{
+							num_extra_tris *= 4;
+							area_squared *= ( 1.0f / 16.0f );
+						}
+					
+						// This original tri will not be added...
+						--valid_tris;
+
+						// ...however, the subdivided versions will.
+						valid_tris += num_extra_tris;
+					}
+				}
+			}
+
+			if( valid_tris == 0 )
+			{
+				continue;
+			}
+			
+			// Create a tracking structure for this mesh.
+			sNgcShatterInstanceDetails *p_details		= new sNgcShatterInstanceDetails( valid_tris, p_mesh, p_scene, format );
+			sNgcVert				   *p_write_vertex	= p_details->mp_vertex_buffer;
+			uint32						details_index	= 0;
+
+			Mth::Vector					spread_center	= shatterVelocity * -shatterSpreadFactor;
+			float						base_speed		= shatterVelocity.Length();
+
+			spread_center[X] += p_mesh->mp_dl->m_sphere[0];
+			spread_center[Y] += p_mesh->mp_dl->m_sphere[1];
+			spread_center[Z] += p_mesh->mp_dl->m_sphere[2];
+			
+			// Add the tracking structure to the table.
+			p_shatter_details_table->PutItem((uint32)p_details, p_details );
+			
+			// Process-subdivide the entire stack.
+			sNgcVert *p_copy_vertex = p_write_vertex;
+			while( subdivide_tri_stack( &p_write_vertex, p_mesh ));
+					
+			// Copy the (possibly subdivided) vertex data over.
+			while( p_copy_vertex < p_write_vertex )
+			{
+				Mth::Vector *p_vert0 = &p_copy_vertex[0].pos;
+				Mth::Vector *p_vert1 = &p_copy_vertex[1].pos;
+				Mth::Vector *p_vert2 = &p_copy_vertex[2].pos;
+				
+				// Calculate position as the midpoint of the three vertices per poly.
+				p_details->mp_positions[details_index][X] = ( p_vert0->GetX() + p_vert1->GetX() + p_vert2->GetX() ) * ( 1.0f / 3.0f );
+				p_details->mp_positions[details_index][Y] = ( p_vert0->GetY() + p_vert1->GetY() + p_vert2->GetY() ) * ( 1.0f / 3.0f );
+				p_details->mp_positions[details_index][Z] = ( p_vert0->GetZ() + p_vert1->GetZ() + p_vert2->GetZ() ) * ( 1.0f / 3.0f );
+
+				// Calculate the vector  back from the bounding box of the object. Then use this to figure the 'spread' of the
+				// shards by calculating the vector from this position to the center of each shard.
+				float speed = base_speed + ( base_speed * (( shatterVelocityVariance * rand() ) / RAND_MAX ));
+				p_details->mp_velocities[details_index] = ( p_details->mp_positions[details_index] - spread_center ).Normalize( speed );
+
+				Mth::Vector axis( -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ));
+				axis.Normalize();
+				p_details->mp_matrices[details_index].Ident();
+				p_details->mp_matrices[details_index].Rotate( axis, 0.1f * ((float)rand() / RAND_MAX ));
+
+				p_copy_vertex += 3;
+						
+				++details_index;
+			}
+		}
+	}
+#endif
+}
+
+
+
+/******************************************************************************
+ *
+ * 
+ *****************************************************************************/
+void plat_shatter_update( sShatterInstanceDetails *p_details, float framelength )
+{
+	sNgcShatterInstanceDetails *p_Ngc_details = static_cast( p_details );
+	
+	sNgcVert *p_vert_data = p_Ngc_details->mp_vertex_buffer;
+	
+	for( int i = 0; i < p_details->m_num_triangles; ++i )
+	{
+		Mth::Vector *p_v0	= &p_vert_data[(i*3)+0].pos;
+		Mth::Vector *p_v1	= &p_vert_data[(i*3)+1].pos;
+		Mth::Vector *p_v2	= &p_vert_data[(i*3)+2].pos;
+		// To move the shatter pieces:
+		// 1) subtract position from each vertex
+		// 2) rotate
+		// 3) update position with velocity
+		// 4) add new position to each vertex
+
+		// The matrix holds 3 vectors at once.
+		Mth::Matrix m;
+		m[X].Set( p_v0->GetX() - p_details->mp_positions[i][X], p_v0->GetY() - p_details->mp_positions[i][Y], p_v0->GetZ() - p_details->mp_positions[i][Z] );
+		m[Y].Set( p_v1->GetX() - p_details->mp_positions[i][X], p_v1->GetY() - p_details->mp_positions[i][Y], p_v1->GetZ() - p_details->mp_positions[i][Z] );
+		m[Z].Set( p_v2->GetX() - p_details->mp_positions[i][X], p_v2->GetY() - p_details->mp_positions[i][Y], p_v2->GetZ() - p_details->mp_positions[i][Z] );
+         
+		m[X].Rotate( p_details->mp_matrices[i] );
+		m[Y].Rotate( p_details->mp_matrices[i] );
+		m[Z].Rotate( p_details->mp_matrices[i] );
+
+		// Update the position and velocity of the shatter piece, dealing with bouncing if necessary.
+		p_details->UpdateParameters( i, framelength );
+      
+		m[X] += p_details->mp_positions[i]; 
+		m[Y] += p_details->mp_positions[i]; 
+		m[Z] += p_details->mp_positions[i];
+
+		p_v0->Set( m[X][X], m[X][Y], m[X][Z] );
+		p_v1->Set( m[Y][X], m[Y][Y], m[Y][Z] );
+		p_v2->Set( m[Z][X], m[Z][Y], m[Z][Z] );
+	}
+}
+
+
+
+/******************************************************************************
+ *
+ * 
+ *****************************************************************************/
+void plat_shatter_render( sShatterInstanceDetails *p_details )
+{
+	sNgcShatterInstanceDetails *p_Ngc_details = static_cast( p_details );
+
+	Dbg_Assert( p_Ngc_details );
+
+//	p_Ngc_details->mp_mesh->mp_material->Submit( 0, (GXColor){0,0,0,0} );
+
+	NxNgc::MaterialSubmit( p_Ngc_details->mp_mesh, p_Ngc_details->mp_scene );
+	GX::SetChanCtrl( GX_COLOR0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+	GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+//	GX::SetChanMatColor( GX_COLOR0A0, p_Ngc_details->mp_mesh->m_base_color );
+	GX::SetChanMatColor( GX_COLOR0A0, (GXColor){128,128,128,255} );
+
+//	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_IDENTITY );
+
+	// Render the triangles
+	GX::SetVtxDesc( 3, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+	GX::Begin( GX_TRIANGLES, p_Ngc_details->m_vertex_format, p_Ngc_details->m_num_triangles * 3 ); 
+	sNgcVert *p_vert_array = p_Ngc_details->mp_vertex_buffer;
+	for (int i = 0; i < p_Ngc_details->m_num_triangles; i++, p_vert_array += 3)
+	{
+		GX::Position3f32( p_vert_array[0].pos[X], p_vert_array[0].pos[Y], p_vert_array[0].pos[Z] );
+		GX::Color1u32(*((u32*)&(p_vert_array[0].col)));
+		GX::TexCoord2s16( p_vert_array[0].u, p_vert_array[0].v );
+
+		GX::Position3f32( p_vert_array[1].pos[X], p_vert_array[1].pos[Y], p_vert_array[1].pos[Z] );
+		GX::Color1u32(*((u32*)&(p_vert_array[1].col)));
+		GX::TexCoord2s16( p_vert_array[1].u, p_vert_array[1].v );
+
+		GX::Position3f32( p_vert_array[2].pos[X], p_vert_array[2].pos[Y], p_vert_array[2].pos[Z] );
+		GX::Color1u32(*((u32*)&(p_vert_array[2].col)));
+		GX::TexCoord2s16( p_vert_array[2].u, p_vert_array[2].v );
+	}
+	GX::End();
+}
+
+	
+	
+///////////////////////////////////////////////////////////////////
+//
+// FOG
+//
+///////////////////////////////////////////////////////////////////
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static bool		fogEnabled			= false;
+static GXColor	fogColor			= (GXColor){ 0, 0, 0, 0 };
+static float	fogNear				= 0.0f;
+static float	fogFar				= 0.0f;
+
+void		CFog::s_plat_enable_fog(bool enable)
+{
+	if( enable != fogEnabled )
+	{
+		// If we're disabling, reset to default values.
+		if ( !enable )
+		{
+			fogColor	= (GXColor){ 0, 0, 0, 0 };
+			fogNear		= 0.0f;
+			fogFar		= 0.0f;
+		}
+
+		fogEnabled = enable;
+	}
+}
+
+void		CFog::s_plat_set_fog_exponent(float exponent)
+{
+//	Dbg_Message("Stub: CFog::SetFogExponent()");
+}
+
+void		CFog::s_plat_set_fog_rgba(Image::RGBA rgba)
+{
+	fogColor.r = rgba.r;
+	fogColor.g = rgba.g;
+	fogColor.b = rgba.b;
+	fogColor.a = rgba.a;
+//	fogFar = 1000.0f * ( 128.0f / (float)fogColor.a );
+	fogFar = 20000.0f * ( 128.0f / (float)fogColor.a ); 
+}
+
+void		CFog::s_plat_set_fog_near_distance(float distance)
+{
+	fogNear = distance;
+//	fogFar = 1000.0f * ( 128.0f / (float)fogColor.a );
+	fogFar = 20000.0f * ( 128.0f / (float)fogColor.a ); 
+}
+
+void		CFog::s_plat_set_fog_color( void )
+{
+	GX::SetFogColor( fogColor );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_fog_update( void )
+{
+	if( fogEnabled )
+	{
+		GX::SetFog( GX_FOG_EXP, fogNear, fogFar, 2.0f, 20000.0f, fogColor );
+	}
+	else
+	{
+		GX::SetFog( GX_FOG_NONE, 0.0f, 0.0f, 0.0f, 0.0f, fogColor );
+	}
+}
+	
+
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGC/p_nxmodel.cpp b/Code/Gfx/NGC/p_nxmodel.cpp
new file mode 100644
index 0000000..bed3edd
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxmodel.cpp
@@ -0,0 +1,369 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxModel.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/21/2002
+//****************************************************************************
+
+#include 
+
+#include "gfx/nxmodel.h"
+#include "gfx/skeleton.h"
+#include 
+#include "gfx/Ngc/p_nxmodel.h"
+#include "gfx/Ngc/p_nxscene.h"
+#include "gfx/Ngc/nx/import.h"
+#include "gfx/Ngc/nx/render.h"
+#include "gfx/Ngc/p_nxgeom.h"
+
+#include 
+
+			   
+#include 
+#include 
+
+int			test_num_bones			= 0;
+Mth::Matrix	*p_test_bone_matrices	= NULL;
+Mth::Matrix	test_root_matrix;
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CNgcModel::plat_init_skeleton( int numBones )
+{
+//	if ( !mp_instance ) return false;
+//	Mth::Matrix * p_bone = new Mth::Matrix[numBones];
+//
+//	mp_instance->SetBoneTransforms( p_bone );
+//	for ( int i = 0; i < numBones; i++ )
+//	{
+//		p_bone[i].Identity();
+//	}
+    
+	return true;
+}
+
+
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//	
+///*
+//	bool CNgcModel::plat_load_file(const char* p_fileName)
+//{
+//	// Machine specific code here ............
+//	
+//	// TODO:  Make this more generalized
+//
+//	// Load in the texture dictionary for the model.
+//	Lst::HashTable< NxNgc::sTexture > *p_texture_table = NxNgc::LoadTextureFile( "models/testskin/testskin.tex.xbx" );
+//
+//    return true;
+//}
+//*/
+//
+//
+//bool CNgcModel::plat_load_mesh( CMesh* pMesh )
+//{
+//	// The skeleton must exist by this point (unless it's a hacked up car).
+//	int numBones;
+//	numBones = mp_skeleton ? mp_skeleton->GetNumBones() : 1;
+//	
+//	Mth::Matrix temp;
+//	CNgcMesh *p_Ngc_mesh = static_cast( pMesh );
+//	mp_instance = new NxNgc::CInstance( p_Ngc_mesh->GetScene()->GetEngineScene(), temp, numBones, mp_boneTransforms );
+//
+//    return true;
+//}
+//	
+//	
+//	
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//bool CNgcModel::plat_unload_mesh( void )
+//{
+//	if ( mp_instance != NULL )
+//	{
+//		delete mp_instance;
+//		mp_instance = NULL;
+//	}
+//
+//	return true;
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//	
+//bool CNgcModel::plat_set_render_mode(ERenderMode mode)
+//{
+//	// Machine specific code here ............
+//    return true;
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//	
+//bool CNgcModel::plat_set_color(uint8 r, uint8 g, uint8 b, uint8 a)
+//{
+//	// Machine specific code here ............
+//    return true;
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//bool CNgcModel::plat_set_visibility(uint32 mask)
+//{
+//    return true;
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//bool CNgcModel::plat_set_active( bool active )
+//{
+//	if( mp_instance )
+//	{
+//		mp_instance->SetActive( active );
+//	}
+//	return true;
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//bool CNgcModel::plat_set_scale(float scaleFactor)
+//{
+//	// Machine specific code here ............
+//    return true;
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//bool CNgcModel::plat_replace_texture(char* p_srcFileName, char* p_dstFileName)
+//{
+//	// Machine specific code here ............
+//    return true;
+//}
+//
+//
+//
+//void ConvertMatrix(Mth::Matrix* pMatrix, void *pMat)
+//{
+//}
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//bool CNgcModel::plat_render( Mth::Matrix *pRootMatrix, Mth::Matrix *pBoneMatrices, int numBones )
+//{
+//	if( mp_instance )
+//	{
+//		mp_instance->SetTransform( *pRootMatrix );
+//	}
+//    return true;
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CNgcModel::plat_prepare_materials( void )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CNgcModel::plat_refresh_materials( void )
+{
+	int numGeoms = GetNumGeoms();
+	for ( int i = 0; i < numGeoms; i++ )
+	{
+		NxNgc::CInstance *p_instance = NULL;
+		CNgcGeom * p_ngc_geom = static_cast( mp_geom[i] ); 
+		p_instance = p_ngc_geom->GetInstance();
+		NxNgc::sScene *p_scene = p_instance->GetScene();
+
+		// Setup material data texture pointers.
+		for ( unsigned int lp = 0; lp < p_scene->mp_scene_data->m_num_materials; lp++ )
+		{
+			NxNgc::sMaterialHeader * p_mat = &p_scene->mp_material_header[lp];
+			NxNgc::sTextureDL * p_dl = &p_scene->mp_texture_dl[lp]; 
+			NxNgc::sMaterialPassHeader * p_pass = &p_scene->mp_material_pass[p_mat->m_pass_item];
+
+			GX::ResolveDLTexAddr( p_dl, p_pass, p_mat->m_passes );
+
+//			GX::begin( p_dl->mp_dl, p_dl->m_dl_size );
+//			multi_mesh( p_mat, p_pass, true, true );
+//			p_dl->m_dl_size = GX::end();
+
+			// See if any textures have alpha, but the texture DL doesn't set it up. If so, flag material as
+			// not using display list.
+			bool direct = false;
+			for ( int p = 0; p < p_mat->m_passes; p++, p_pass++ )
+			{
+				if ( p_pass->m_texture.p_data )
+				{
+//					bool current_alpha = ( p_pass->m_texture.p_data->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA ) ? true : false;
+//					bool dl_alpha = p_dl->m_alpha_offset[p] ? true : false;
+//					if ( current_alpha != dl_alpha )
+//					{
+//						// Must use direct setup mode.
+//						direct = true;
+//					}
+					if ( p_pass->m_texture.p_data->flags & NxNgc::sTexture::TEXTURE_FLAG_REPLACED )
+					{
+						direct = true;
+					}
+
+				}
+			}
+			if ( direct )
+			{
+				p_mat->m_flags |= (1<<3);
+			}
+			else
+			{
+				p_mat->m_flags &= ~(1<<3);
+			}
+		}
+	}
+
+	return true;
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcModel::CNgcModel()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcModel::~CNgcModel()
+{
+	if ( mp_instance && mp_instance->GetBoneTransforms() )
+	{
+		delete mp_instance->GetBoneTransforms();
+		mp_instance->SetBoneTransforms( NULL );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CNgcModel::plat_get_bounding_sphere()
+{
+	Mth::Vector sphere, sphere1, sum, diff;
+	float dist;
+
+	// this should probably never happen
+	if (m_numGeoms == 0)
+		return Mth::Vector(0.0f, 0.0f, 0.0f, 0.0f);
+
+	// combine the spheres of all geoms
+	// (this should really be done once at load time)
+
+	// start with first sphere
+	sphere = mp_geom[0]->GetBoundingSphere();
+
+	// loop over remaining spheres, expanding as necessary
+	for (int i=1; iGetBoundingSphere();
+
+		// centre-to-centre vector, and distance
+		diff = sphere1-sphere;
+		dist = diff.Length();
+
+		// test for sphere1 inside sphere
+		if (dist+sphere1[3] <= sphere[3])
+			continue;			// keep sphere
+
+		// test for sphere inside sphere1
+		if (dist+sphere[3] <= sphere1[3])
+		{
+			sphere = sphere1;	// replace sphere
+			continue;
+		}
+
+		// otherwise make a larger sphere that contains both
+		sum       = sphere+sphere1;
+		sphere    = 0.5f * (sum + (diff[3]/dist) * diff);
+		sphere[3] = 0.5f * (dist + sum[3]);
+	}
+
+	return sphere;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcModel::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	// loop over all spheres
+	for ( int i = 0; i < m_numGeoms; i++ )
+	{
+		mp_geom[i]->SetBoundingSphere( boundingSphere );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+
+				
+
diff --git a/Code/Gfx/NGC/p_nxmodel.h b/Code/Gfx/NGC/p_nxmodel.h
new file mode 100644
index 0000000..1d23ad0
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxmodel.h
@@ -0,0 +1,64 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxModel.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  1/8/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_MODEL_H__
+#define	__GFX_P_NX_MODEL_H__
+    
+#include "gfx/nxmodel.h"
+#include "gfx/Ngc/nx/instance.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CModel
+    
+class CNgcModel : public CModel
+{
+public:
+						CNgcModel();
+	virtual 			~CNgcModel();
+
+	NxNgc::CInstance    *GetInstance() { return mp_instance; }
+	void				SetInstance( NxNgc::CInstance * p ) { mp_instance = p; }
+private:				// It's all private, as it is machine specific
+//	bool				plat_load_mesh( CMesh* pMesh );
+//	bool				plat_unload_mesh();
+//	bool				plat_set_render_mode(ERenderMode mode);
+//	bool				plat_set_color(uint8 r, uint8 g, uint8 b, uint8 a);
+//	bool				plat_set_visibility(uint32 mask);
+//	bool				plat_set_active( bool active );
+//    bool				plat_set_scale( float scaleFactor );
+//    bool				plat_replace_texture( char* p_srcFileName, char* p_dstFileName );
+//	bool				plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones);
+
+	bool				plat_init_skeleton( int numBones );
+
+	bool				plat_prepare_materials( void );
+	bool				plat_refresh_materials( void );
+
+	Mth::Vector 		plat_get_bounding_sphere();
+	void				plat_set_bounding_sphere( const Mth::Vector& boundingSphere );
+
+	NxNgc::CInstance	*mp_instance;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
+
diff --git a/Code/Gfx/NGC/p_nxnewparticle.cpp b/Code/Gfx/NGC/p_nxnewparticle.cpp
new file mode 100644
index 0000000..a8f3573
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxnewparticle.cpp
@@ -0,0 +1,791 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticle.cpp										**
+**																			**
+**	Created by:		3/25/03	-	SPG											**
+**																			**
+**	Description:	Ngc new parametric particle system						**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include "gfx/ngc/p_nxparticle.h"
+
+#include "dolphin/base/ppcwgpipe.h"
+#include "dolphin/gx/gxvert.h"
+#include 
+
+
+extern "C"
+{
+
+extern float ReciprocalEstimate_ASM( float f );
+
+extern void RenderNewParticles( Nx::CParticleStream * p_stream, float lifetime, float midpercent, bool use_mid_color, Image::RGBA * p_color0, Mth::Vector * p0, Mth::Vector * sr, Mth::Vector * su, float * p_params, float nearz );
+
+}
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+//static int rand_seed;
+//static int rand_a	= 314159265;
+//static int rand_b	= 178453311;
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//static void seed_particle_rnd( int s, int a, int b )
+//{
+//	rand_seed		= s;
+//	rand_a			= a;
+//	rand_b			= b;
+//}
+//
+//
+//
+///******************************************************************/
+///*                                                                */
+///*                                                                */
+///******************************************************************/
+//static int particle_rnd( int n )
+//{
+//	rand_seed	= rand_seed * rand_a + rand_b;
+//	rand_a		= ( rand_a ^ rand_seed ) + ( rand_seed >> 4 );
+//	rand_b		+= ( rand_seed >> 3 ) - 0x10101010L;
+//	return (int)(( rand_seed & 0xffff ) * n ) >> 16;
+//}
+//
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CParticleStream::AdvanceSeed( int num_places )
+{
+//	// Seed the random number generator back to the current seed.
+//	seed_particle_rnd( m_rand_seed, m_rand_a, m_rand_b );
+//
+//	// Each particle will call the random function four times.
+//	for( int i = 0; i < ( num_places * 4 ); i++ )
+//	{
+//		particle_rnd( 1 );
+//	}
+//
+//	m_rand_seed = rand_seed;
+//	m_rand_a	= rand_a;
+//	m_rand_b	= rand_b;
+
+	// Seed the random number generator back to the current seed.
+	uint32 rand_current = m_rand_current;
+
+	// Each particle will call the random function four times.
+	for( int i = 0; i < ( num_places * 4 ); i++ )
+	{
+		rand_current = ( rand_current & 1 ) ? ( rand_current >> 1 ) ^ 0x3500 : ( rand_current >> 1 );
+	}
+
+	m_rand_current = rand_current;
+}
+	
+	
+	
+//inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcNewParticle::plat_render( void )
+{
+	CParticleStream* p_stream;
+	int i;
+
+	// Process the streams.
+	if( m_params.m_EmitRate && ( !m_emitting || ( m_params.m_EmitRate != mp_newest_stream->m_rate )))
+	{	
+		if( m_num_streams < m_max_streams )
+		{
+			// Add new stream to cyclic buffer
+			m_num_streams++;
+			mp_newest_stream++;
+			if( mp_newest_stream == mp_stream + m_max_streams )
+			{
+				mp_newest_stream = mp_stream;
+			}
+
+			// Initialise new stream.
+			mp_newest_stream->m_rate			= m_params.m_EmitRate;
+			mp_newest_stream->m_interval		= 1.0f / m_params.m_EmitRate;
+			mp_newest_stream->m_oldest_age		= 0.0f;
+			mp_newest_stream->m_num_particles	= 0;
+//			mp_newest_stream->m_rand_seed		= rand();
+//			mp_newest_stream->m_rand_a			= 314159265;
+//			mp_newest_stream->m_rand_b			= 178453311;
+			mp_newest_stream->m_rand_current	= rand() & ( 16384 - 1 );
+			if ( mp_newest_stream->m_rand_current == 0 ) mp_newest_stream->m_rand_current = 1;
+			m_emitting = true;
+		}
+		else
+		{
+			m_emitting = false;
+		}
+	}
+	else
+	{
+		m_emitting = m_params.m_EmitRate;
+	}
+
+	if( !m_num_streams )
+		return;
+
+	// Age all streams.
+	for( i = 0, p_stream = mp_oldest_stream; i < m_num_streams; ++i )
+	{
+		// Increase age of oldest particle.
+		p_stream->m_oldest_age += 1.0f / 60.0f;
+
+		// Step pointer within cyclic buffer.
+		p_stream++;
+		if( p_stream == mp_stream + m_max_streams )
+		{
+			p_stream = mp_stream;
+		}
+	}
+
+	// Births into newest stream.
+	if( m_emitting )
+	{
+		// How many particles so far emitted?
+		mp_newest_stream->m_num_particles = (int)( mp_newest_stream->m_oldest_age * mp_newest_stream->m_rate + 1.0f );
+	}
+
+	// Deaths from oldest stream.
+	if( mp_oldest_stream->m_oldest_age > m_params.m_Lifetime )
+	{
+		// Work out number dead.
+		int particles_dead = (int)(( mp_oldest_stream->m_oldest_age - m_params.m_Lifetime ) * mp_oldest_stream->m_rate + 1.0f );
+
+		// Remove dead particles.
+		mp_oldest_stream->m_num_particles -= particles_dead;
+
+		// Should we keep processing the oldest stream?
+		if( mp_oldest_stream->m_num_particles > 0 || ( m_num_streams == 1 && m_emitting ))
+		{
+			// Adjust age of oldest particle.
+			mp_oldest_stream->m_oldest_age -= (float)particles_dead * mp_oldest_stream->m_interval;
+
+			// Advance seed.
+			mp_oldest_stream->AdvanceSeed( particles_dead );
+		}
+		else
+		{
+			// Remove oldest stream and wrap in cyclic buffer if necessary.
+			m_num_streams--;
+			mp_oldest_stream++;
+			if( mp_oldest_stream == mp_stream + m_max_streams )
+			{
+				mp_oldest_stream = mp_stream;
+			}
+			if( !m_num_streams )
+				return;
+		}
+	}
+
+//	// Now render the streams. after checking the bounding sphere is visible.
+//	D3DXVECTOR3	center( m_bsphere[X], m_bsphere[Y], m_bsphere[Z] );
+//	if( !NxNgc::frustum_check_sphere( ¢er, m_bsphere[W] ))
+//	{
+//		return;
+//	}
+	if( !m_params.m_LocalCoord )
+	{
+		if ( !NxNgc::frustum_check_sphere( m_bsphere ) ) return;
+	}
+ //   if ( NxNgc::TestSphereAgainstOccluders( &sphere ) )
+
+	GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+	NxNgc::sMaterialHeader mat;
+	NxNgc::sMaterialPassHeader pass;
+
+	mat.m_checksum			= 0;
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 0;
+	mat.m_flags				= 0;
+//	mat.m_material_dl_id	= 0;
+	mat.m_draw_order		= 0;
+	mat.m_pass_item			= 0;
+	mat.m_texture_dl_id		= 0;
+//	mat.m_shininess			= 0;
+//	mat.m_specular_color	= (GXColor){255,255,255,255};
+
+	pass.m_texture.p_data	= mp_texture;
+	pass.m_flags			= (1<<0)|(1<<5)|(1<<6);
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= m_blend;
+	pass.m_alpha_fix		= m_fix;
+	pass.m_uv_wibble_index	= 0;
+	pass.m_color			= (GXColor){64,64,64,255};
+	pass.m_k				= 0;
+	pass.m_u_tile			= 0;
+	pass.m_v_tile			= 0;
+	pass.m_uv_enabled		= false;
+
+	multi_mesh( &mat, &pass, true, true );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	Mth::Vector sr;
+	Mth::Vector su;
+	sr[X] = screen_right.x;
+	sr[Y] = screen_right.y;
+	sr[Z] = screen_right.z;
+	sr[W] = 1.0f;
+
+	su[X] = screen_up.x;
+	su[Y] = screen_up.y;
+	su[Z] = screen_up.z;
+	su[W] = 1.0f;
+
+	GQRSetup6( GQR_SCALE_64,		// Pos
+			   GQR_TYPE_S16,
+			   GQR_SCALE_64,
+			   GQR_TYPE_S16 );
+	GQRSetup7( 14,		// Normal
+			   GQR_TYPE_S16,
+			   14,
+			   GQR_TYPE_S16 );
+
+	f32				pm[GX_PROJECTION_SZ];
+	f32				vp[GX_VIEWPORT_SZ];
+
+	GX::GetProjectionv( pm );
+	GX::GetViewportv( vp );
+	NsMatrix * p_mtx = &NxNgc::EngineGlobals.local_to_camera;
+
+	float params[6];
+	params[0] = p_mtx->getAtX();
+	params[1] = p_mtx->getAtY();
+	params[2] = p_mtx->getPosZ();
+	params[3] = p_mtx->getAtZ();
+	params[4] = pm[1];
+	params[5] = vp[2];
+
+	// Construct a packet with data for each stream.
+	for( i = 0, p_stream = mp_oldest_stream; i < m_num_streams; i++, p_stream++ )
+	{
+		Dbg_MsgAssert( p_stream->m_num_particles < 65536, ( "particle limit reached" ));
+
+		// Wrap at end of cyclic buffer.
+		if( p_stream == mp_stream + m_max_streams )
+		{
+			p_stream = mp_stream;
+		}
+
+#if 1
+		RenderNewParticles( p_stream,
+							m_params.m_Lifetime,
+							m_params.m_ColorMidpointPct,
+							m_params.m_UseMidcolor,
+							&m_params.m_Color[0],
+							&m_s0,
+							&sr,
+							&su,
+							params,
+							256.0f );
+
+#else
+		float t				= p_stream->m_oldest_age;
+		float midpoint_time = m_params.m_Lifetime * ( m_params.m_ColorMidpointPct * 0.01f );
+
+		// Seed the random number generators for this stream.
+//		seed_particle_rnd( p_stream->m_rand_seed, p_stream->m_rand_a, p_stream->m_rand_b );
+		uint32 rand_current = p_stream->m_rand_current;
+
+		for( int p = 0; p < p_stream->m_num_particles; ++p )
+		{
+			// Generate random vector. Each component in the range [1.0, 2.0].
+//			Mth::Vector r( 1.0f + ((float)particle_rnd( 16384 ) / 16384 ),
+//						   1.0f + ((float)particle_rnd( 16384 ) / 16384 ),
+//						   1.0f + ((float)particle_rnd( 16384 ) / 16384 ),
+//						   1.0f + ((float)particle_rnd( 16384 ) / 16384 ));
+
+			Mth::Vector r;
+			r[X] = 1.0f + ((float)( rand_current = ( ( rand_current & 1 ) ? ( rand_current >> 1 ) ^ 0x3500 : ( rand_current >> 1 ) ) ) / 16384.0f );
+			r[Y] = 1.0f + ((float)( rand_current = ( ( rand_current & 1 ) ? ( rand_current >> 1 ) ^ 0x3500 : ( rand_current >> 1 ) ) ) / 16384.0f );
+			r[Z] = 1.0f + ((float)( rand_current = ( ( rand_current & 1 ) ? ( rand_current >> 1 ) ^ 0x3500 : ( rand_current >> 1 ) ) ) / 16384.0f );
+			r[W] = 1.0f + ((float)( rand_current = ( ( rand_current & 1 ) ? ( rand_current >> 1 ) ^ 0x3500 : ( rand_current >> 1 ) ) ) / 16384.0f );
+
+			float color_interpolator;
+			Image::RGBA	col0, col1;
+
+			if( m_params.m_UseMidcolor )
+			{
+				if( t > midpoint_time )
+				{
+					color_interpolator	= ( t - midpoint_time ) * ReciprocalEstimate_ASM( m_params.m_Lifetime - midpoint_time );
+					col0				= m_params.m_Color[1];
+					col1				= m_params.m_Color[2];
+				}
+				else
+				{
+					color_interpolator	= t * ReciprocalEstimate_ASM( midpoint_time );
+					col0				= m_params.m_Color[0];
+					col1				= m_params.m_Color[1];
+				}
+			}
+			else 
+			{
+				color_interpolator		= t * ReciprocalEstimate_ASM( m_params.m_Lifetime );
+				col0					= m_params.m_Color[0];
+				col1					= m_params.m_Color[2];
+			}
+
+
+//; Calculate the position of the particle, from:
+// pos = ( m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )) + ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 )).Scale( r );
+			Mth::Vector pos = ( m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )) + ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 )).Scale( r );
+
+			GXColor matcol;
+			matcol.r = col0.r + (uint8)( ( (float)col1.r - (float)col0.r ) * color_interpolator );
+			matcol.g = col0.g + (uint8)( ( (float)col1.g - (float)col0.g ) * color_interpolator );
+			matcol.b = col0.b + (uint8)( ( (float)col1.b - (float)col0.b ) * color_interpolator );
+			matcol.a = col0.a + (uint8)( ( (float)col1.a - (float)col0.a ) * color_interpolator );
+
+			GX::SetChanMatColor( GX_COLOR0A0, matcol );
+			GX::SetChanAmbColor( GX_COLOR0A0, matcol );
+
+//			NsVector sr;
+//			sr.x = screen_right.x * pos[W];
+//			sr.y = screen_right.y * pos[W];
+//			sr.z = screen_right.z * pos[W];
+//
+//			float sx, sy, sz;
+//			float tx, ty, tz;
+//			float sc;
+//			GX::Project( pos[X], pos[Y], pos[Z], (Mtx)p_mtx, pm, vp, &sx, &sy, &sz );
+//			GX::Project( pos[X]+sr.x, pos[Y]+sr.y, pos[Z]+sr.z, (Mtx)p_mtx, pm, vp, &tx, &ty, &tz );
+//			sc = tx - sx;	//sqrtf( ( ( tx - sx ) * ( tx - sx ) ) + ( ( ty - sy ) * ( ty - sy ) ) + ( ( tz - sz ) * ( tz - sz ) ) );
+
+			// Calculate size.
+//			float x = p_mtx->getRightX()*pos[W];
+			float z = p_mtx->getAtX()*pos[X] + p_mtx->getAtY()*pos[Y] + p_mtx->getAtZ()*pos[Z] + p_mtx->getPosZ();
+			float xc = pos[W] * pm[1];	// + z * pm[2];
+			float wc = ( 1.0f / z );
+			float sc = xc * vp[2] * wc;	// + vp[0] + vp[2]/2;
+
+			sc = fabsf( sc );
+
+			int size = (int)(sc * 6.0f);
+			if ( size <= 255 )
+			{
+				GX::SetPointSize( size, GX_TO_ONE );
+				GX::Begin( GX_POINTS, GX_VTXFMT0, 1 ); 
+				GXWGFifo.f32 = pos[X];
+				GXWGFifo.f32 = pos[Y];
+				GXWGFifo.f32 = pos[Z];
+				GXWGFifo.f32 = 0.0f;
+				GXWGFifo.f32 = 0.0f;
+				GX::End();
+			}
+			else
+			{
+				if ( sc > 256.0f )
+				{
+					pos[W] = ( pos[W] * 256.0f ) / sc;
+				}
+
+				NsVector sr;
+				sr.x = screen_right.x * pos[W];
+				sr.y = screen_right.y * pos[W];
+				sr.z = screen_right.z * pos[W];
+				NsVector su;
+				su.x = screen_up.x * pos[W];
+				su.y = screen_up.y * pos[W];
+				su.z = screen_up.z * pos[W];
+		
+				float v0x = pos[X] - su.x;
+				float v0y = pos[Y] - su.y;
+				float v0z = pos[Z] - su.z;
+	
+				float v1x = pos[X] + su.x;
+				float v1y = pos[Y] + su.y;
+				float v1z = pos[Z] + su.z;
+
+				// Send coordinates.
+				GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+					GXWGFifo.f32 = v1x - sr.x;
+					GXWGFifo.f32 = v1y - sr.y;
+					GXWGFifo.f32 = v1z - sr.z;
+					GXWGFifo.f32 = 0.0f;
+					GXWGFifo.f32 = 0.0f;
+
+					GXWGFifo.f32 = v1x + sr.x;
+					GXWGFifo.f32 = v1y + sr.y;
+					GXWGFifo.f32 = v1z + sr.z;
+					GXWGFifo.f32 = 1.0f;
+					GXWGFifo.f32 = 0.0f;
+
+					GXWGFifo.f32 = v0x + sr.x;
+					GXWGFifo.f32 = v0y + sr.y;
+					GXWGFifo.f32 = v0z + sr.z;
+					GXWGFifo.f32 = 1.0f;
+					GXWGFifo.f32 = 1.0f;
+
+					GXWGFifo.f32 = v0x - sr.x;
+					GXWGFifo.f32 = v0y - sr.y;
+					GXWGFifo.f32 = v0z - sr.z;
+					GXWGFifo.f32 = 0.0f;
+					GXWGFifo.f32 = 1.0f;
+				GX::End();
+			}
+
+
+
+			// Reduce t by particle interval.
+			t -= p_stream->m_interval;
+		}
+#endif
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcNewParticle::update_position( void )
+{
+	// Convert 3-point -> PVA format
+	float t1 = m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f;
+	float t2 = m_params.m_Lifetime;
+	Mth::Vector u, a_;
+
+	Mth::Vector x0	= m_params.m_BoxPos[0];
+	x0[3]			= m_params.m_Radius[0];
+	Mth::Vector x1	= m_params.m_BoxPos[1];
+	x1[3]			= m_params.m_Radius[1];
+	Mth::Vector x2	= m_params.m_BoxPos[2];
+	x2[3]			= m_params.m_Radius[2];
+
+	if( m_params.m_UseMidpoint )
+	{
+		u  = ( t2 * t2 * ( x1 - x0 ) - t1 * t1 * ( x2 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+		a_ = ( t1 * ( x2 - x0 ) - t2 * ( x1 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set( 0, 0, 0, 0 );
+	}
+
+	m_p0 = x0 - 1.5f * m_s0;
+	m_p1 = u  - 1.5f * m_s1;
+	m_p2 = a_ - 1.5f * m_s2;
+	m_p0[3] = x0[3] - 1.5f * m_s0[3];
+	m_p1[3] = u[3]  - 1.5f * m_s1[3];
+	m_p2[3] = a_[3] - 1.5f * m_s2[3];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcNewParticle::plat_update( void )
+{
+
+	if (m_params.m_LocalCoord)
+	{
+		update_position();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcNewParticle::plat_build( void )
+{
+	// Adjust some of the stream params to improve performance.
+	m_params.m_EmitRate	= m_params.m_EmitRate * 0.25f;
+	
+	// Initialise streams.
+	m_max_streams		= 5;
+	m_num_streams		= 0;
+
+	mp_stream			= new CParticleStream[m_max_streams]; 
+	mp_newest_stream	= mp_stream + m_max_streams - 1;
+	mp_oldest_stream	= mp_stream;
+	m_emitting			= false;
+
+	// Create a (semi-transparent) material used to render the mesh.
+//	mp_material			= new NxNgc::sMaterial;
+//	memset( mp_material, 0, sizeof( NxNgc::sMaterial ));
+
+//	mp_material->m_flags[0]		= MATFLAG_TRANSPARENT | MATFLAG_TEXTURED;
+//	mp_material->m_passes		= 1;
+//	mp_material->m_alpha_cutoff	= 1;
+//	mp_material->m_no_bfc		= true;
+//	mp_material->m_color[0][0]	= 0.5f;
+//	mp_material->m_color[0][1]	= 0.5f;
+//	mp_material->m_color[0][2]	= 0.5f;
+//	mp_material->m_color[0][3]	= m_params.m_FixedAlpha * ( 1.0f /  128.0f );
+//	mp_material->m_reg_alpha[0]	= NxNgc::GetBlendMode( m_params.m_BlendMode );
+//
+//	// Get texture.
+//	Nx::CTexture*		p_texture;
+//	Nx::CNgcTexture*	p_Ngc_texture;
+//	mp_material->mp_tex[0]	= NULL;
+//	p_texture				= Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( m_params.m_Texture );
+//	p_Ngc_texture			= static_cast( p_texture );
+//	if( p_Ngc_texture )
+//	{
+//		mp_material->mp_tex[0] = p_Ngc_texture->GetEngineTexture();
+//	}
+
+	// Have already set m_Lifetime, which is called m_life in newflat.
+
+	// x-component will be overwritten by vu1 code, so can store midtime there
+//	*(float *)&m_systemDmaData.m_tagx = m_params.m_UseMidcolor ?
+//										m_params.m_Lifetime * m_params.m_ColorMidpointPct * 0.01f :
+//										0.0f;
+
+
+	// Set Texture & blend mode.
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( m_params.m_Texture );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+	m_blend = get_texture_blend( m_params.m_BlendMode ); 
+	m_fix = m_params.m_FixedAlpha;
+
+
+	// and now a load of redundant duplication of data, which should later be removed...
+
+	// Convert 3-point -> PVA format.
+	float t1 = m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f;
+	float t2 = m_params.m_Lifetime;
+	Mth::Vector x0,x1,x2,u,a_;
+
+	x0    = m_params.m_BoxDims[0];
+	x0[3] = m_params.m_RadiusSpread[0];
+	x1    = m_params.m_BoxDims[1];
+	x1[3] = m_params.m_RadiusSpread[1];
+	x2    = m_params.m_BoxDims[2];
+	x2[3] = m_params.m_RadiusSpread[2];
+
+	if( m_params.m_UseMidpoint )
+	{
+		u  = ( t2 * t2 * ( x1 - x0 ) - t1 * t1 * ( x2 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+		a_ = ( t1 * ( x2 - x0 ) - t2 * ( x1 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set(0,0,0,0);
+	}
+
+	m_s0 = x0;
+	m_s1 = u;
+	m_s2 = a_;
+
+	x0    = m_params.m_BoxPos[0];
+	x0[3] = m_params.m_Radius[0];
+	x1    = m_params.m_BoxPos[1];
+	x1[3] = m_params.m_Radius[1];
+	x2    = m_params.m_BoxPos[2];
+	x2[3] = m_params.m_Radius[2];
+
+	if( m_params.m_UseMidpoint )
+	{
+		u  =  ( t2 * t2 * ( x1 - x0 ) - t1 * t1 * ( x2 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+		a_ =  ( t1 * ( x2 - x0 ) - t2 * ( x1 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set(0,0,0,0);
+	}
+
+	m_p0	= x0 - 1.5f * m_s0;
+	m_p1	= u  - 1.5f * m_s1;
+	m_p2	= a_ - 1.5f * m_s2;
+	m_p0[3]	= x0[3] - 1.5f * m_s0[3];
+	m_p1[3]	= u[3]  - 1.5f * m_s1[3];
+	m_p2[3]	= a_[3] - 1.5f * m_s2[3];
+
+	update_position();
+
+	// Color.
+	if( m_params.m_UseMidcolor )
+	{
+//		float q0 = 100.0f / ( m_params.m_Lifetime * m_params.m_ColorMidpointPct );
+//		float q1 = 100.0f / ( m_params.m_Lifetime * ( 100.0f - m_params.m_ColorMidpointPct ));
+
+//		m_systemDmaData.m_c0[0] = ((float)m_params.m_Color[1].r - (float)m_params.m_Color[0].r) * q0;
+//		m_systemDmaData.m_c0[1] = ((float)m_params.m_Color[1].g - (float)m_params.m_Color[0].g) * q0;
+//		m_systemDmaData.m_c0[2] = ((float)m_params.m_Color[1].b - (float)m_params.m_Color[0].b) * q0;
+//		m_systemDmaData.m_c0[3] = ((float)m_params.m_Color[1].a - (float)m_params.m_Color[0].a) * q0;
+
+//		m_systemDmaData.m_c1[0] = (float)m_params.m_Color[1].r;
+//		m_systemDmaData.m_c1[1] = (float)m_params.m_Color[1].g;
+//		m_systemDmaData.m_c1[2] = (float)m_params.m_Color[1].b;
+//		m_systemDmaData.m_c1[3] = (float)m_params.m_Color[1].a;
+
+//		m_systemDmaData.m_c2[0] = ((float)m_params.m_Color[2].r - (float)m_params.m_Color[1].r) * q1;
+//		m_systemDmaData.m_c2[1] = ((float)m_params.m_Color[2].g - (float)m_params.m_Color[1].g) * q1;
+//		m_systemDmaData.m_c2[2] = ((float)m_params.m_Color[2].b - (float)m_params.m_Color[1].b) * q1;
+//		m_systemDmaData.m_c2[3] = ((float)m_params.m_Color[2].a - (float)m_params.m_Color[1].a) * q1;
+	}
+	else // else suppress mid-colour
+	{
+//		float q = 1.0f / m_params.m_Lifetime;
+
+//		m_systemDmaData.m_c1[0] = (float)m_params.m_Color[0].r;
+//		m_systemDmaData.m_c1[1] = (float)m_params.m_Color[0].g;
+//		m_systemDmaData.m_c1[2] = (float)m_params.m_Color[0].b;
+//		m_systemDmaData.m_c1[3] = (float)m_params.m_Color[0].a;
+
+//		m_systemDmaData.m_c2[0] = ((float)m_params.m_Color[2].r - (float)m_params.m_Color[0].r) * q;
+//		m_systemDmaData.m_c2[1] = ((float)m_params.m_Color[2].g - (float)m_params.m_Color[0].g) * q;
+//		m_systemDmaData.m_c2[2] = ((float)m_params.m_Color[2].b - (float)m_params.m_Color[0].b) * q;
+//		m_systemDmaData.m_c2[3] = ((float)m_params.m_Color[2].a - (float)m_params.m_Color[0].a) * q;
+	}
+
+	// Rotation matrix.
+//	m_rotation = m_params.m_RotMatrix;
+	m_rotation.Identity();
+
+	#if 0
+	// invert rotation and apply to spatial params
+	// leaving this code a bit shoddy and slow until full transition to new-style params
+	Mth::Matrix mat;
+	mat=m_rotation;
+	mat.Transpose();
+	Mth::Vector vec;
+
+	vec = m_systemDmaData.m_p0 + 1.5f * m_systemDmaData.m_s0;
+	vec *= mat;
+	m_systemDmaData.m_p0 = vec - 1.5f * m_systemDmaData.m_s0;
+
+	vec = m_systemDmaData.m_p1 + 1.5f * m_systemDmaData.m_s1;
+	vec *= mat;
+	m_systemDmaData.m_p1 = vec - 1.5f * m_systemDmaData.m_s1;
+
+	vec = m_systemDmaData.m_p2 + 1.5f * m_systemDmaData.m_s2;
+	vec *= mat;
+	m_systemDmaData.m_p2 = vec - 1.5f * m_systemDmaData.m_s2;
+	#endif
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcNewParticle::plat_destroy( void )
+{
+	if( mp_stream )
+	{
+		delete [] mp_stream;
+	}
+
+//	if( mp_material )
+//	{
+//		delete mp_material;
+//	}
+}
+
+
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxnewparticle.h b/Code/Gfx/NGC/p_nxnewparticle.h
new file mode 100644
index 0000000..bc892bb
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxnewparticle.h
@@ -0,0 +1,130 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticle.h										**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	Ngc implementation of new parametric particle system	**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_Ngc_P_NXNEWPARTICLE_H__
+#define __GFX_Ngc_P_NXNEWPARTICLE_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Nx
+{
+
+                        
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CParticleStream
+{
+public:
+	int						m_num_particles;
+	float					m_rate;
+	float					m_interval;
+	float					m_oldest_age;
+//	uint32					m_rand_seed;
+//	uint32					m_rand_a;
+//	uint32					m_rand_b;
+	uint32					m_rand_current;
+	void					AdvanceSeed( int num_places );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CNgcNewParticle : public CNewParticle
+{
+	bool				m_emitting;
+	int					m_max_streams;
+	int					m_num_streams;
+	CParticleStream*	mp_stream;
+	CParticleStream*	mp_newest_stream;
+	CParticleStream*	mp_oldest_stream;
+	Mth::Matrix 		m_rotation;
+	Mth::Matrix			m_new_matrix;
+//	NxNgc::sMaterial*	mp_material;
+	NxNgc::sTexture *	mp_texture;
+	uint8				m_blend;
+	uint8				m_fix;
+
+	Mth::Vector			m_s0;
+	Mth::Vector			m_s1;
+	Mth::Vector			m_s2;
+	Mth::Vector			m_p0;
+	Mth::Vector			m_p1;
+	Mth::Vector			m_p2;
+
+protected:
+	void	plat_build( void );
+	void	plat_destroy( void );
+	void	plat_render( void );
+	void	plat_update( void );
+	void	update_position( void );
+};
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_Ngc_P_NXNEWPARTICLE_H__
+
+
+
diff --git a/Code/Gfx/NGC/p_nxnewparticlemgr.cpp b/Code/Gfx/NGC/p_nxnewparticlemgr.cpp
new file mode 100644
index 0000000..6262da6
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxnewparticlemgr.cpp
@@ -0,0 +1,83 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticleMgr.cpp									**
+**																			**
+**	Created by:		3/25/03	-	SPG											**
+**																			**
+**	Description:	Ngc-specific parametric particle system manager			**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+CNewParticle*	CNgcNewParticleManager::plat_create_particle( void )
+{
+	return new CNgcNewParticle;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxnewparticlemgr.h b/Code/Gfx/NGC/p_nxnewparticlemgr.h
new file mode 100644
index 0000000..f4a9de3
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxnewparticlemgr.h
@@ -0,0 +1,77 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5 													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_nxnewparticlemgr.h									**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	Ngc-specific new parametric particle system manager		**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_NGC_P_NXNEWPARTICLEMGR_H__
+#define __GFX_NGC_P_NXNEWPARTICLEMGR_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CNgcNewParticleManager : public CNewParticleManager
+{
+protected:
+	virtual	CNewParticle*	plat_create_particle( void );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_NGC_P_NXNEWPARTICLEMGR_H__
+
+
diff --git a/Code/Gfx/NGC/p_nxparticle.cpp b/Code/Gfx/NGC/p_nxparticle.cpp
new file mode 100644
index 0000000..e5de492
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticle.cpp
@@ -0,0 +1,168 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/Ngc/p_nxparticleflat.h"
+#include "gfx/Ngc/p_nxparticleshaded.h"
+#include "gfx/Ngc/p_nxparticlesmooth.h"
+#include "gfx/Ngc/p_nxparticleglow.h"
+#include "gfx/Ngc/p_nxparticlestar.h"
+#include "gfx/Ngc/p_nxparticlesmoothstar.h"
+#include "gfx/Ngc/p_nxparticleline.h"
+#include "gfx/Ngc/p_nxparticleribbon.h"
+#include "gfx/Ngc/p_nxparticleribbontrail.h"
+#include "gfx/Ngc/p_nxparticlesmoothribbon.h"
+#include "gfx/Ngc/p_nxparticleglowribbontrail.h"
+
+
+namespace Nx
+{
+
+NxNgc::BlendModes get_texture_blend( uint32 blend_checksum )
+{
+	NxNgc::BlendModes rv = NxNgc::vBLEND_MODE_DIFFUSE;
+	switch ( blend_checksum )
+	{
+		case 0x54628ed7:		// Blend
+			rv = NxNgc::vBLEND_MODE_BLEND;
+			break;
+		case 0x02e58c18:		// Add
+			rv = NxNgc::vBLEND_MODE_ADD;
+			break;
+		case 0xa7fd7d23:		// Sub
+		case 0xdea7e576:		// Subtract
+			rv = NxNgc::vBLEND_MODE_SUBTRACT;
+			break;
+		case 0x40f44b8a:		// Modulate
+			rv = NxNgc::vBLEND_MODE_MODULATE;
+			break;
+		case 0x68e77f40:		// Brighten
+			rv = NxNgc::vBLEND_MODE_BRIGHTEN;
+			break;
+		case 0x18b98905:		// FixBlend
+			rv = NxNgc::vBLEND_MODE_BLEND_FIXED;
+			break;
+		case 0xa86285a1:		// FixAdd
+			rv = NxNgc::vBLEND_MODE_ADD_FIXED;
+			break;
+		case 0x0d7a749a:		// FixSub
+		case 0x0eea99ff:		// FixSubtract
+			rv = NxNgc::vBLEND_MODE_SUB_FIXED;
+			break;
+		case 0x90b93703:		// FixModulate
+			rv = NxNgc::vBLEND_MODE_MODULATE_FIXED;
+			break;
+		case 0xb8aa03c9:		// FixBrighten
+			rv = NxNgc::vBLEND_MODE_BRIGHTEN_FIXED;
+			break;
+		case 0x515e298e:		// Diffuse
+		case 0x806fff30:		// None
+			rv = NxNgc::vBLEND_MODE_DIFFUSE;
+			break;
+		default:
+			Dbg_MsgAssert(0,("Illegal blend mode specified. Please use (fix)blend/add/sub/modulate/brighten or diffuse/none."));
+			break;
+	}
+	return rv;
+}
+
+CParticle* plat_create_particle( uint32 checksum, uint32 type_checksum, int max_particles, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	// Types to add:
+	// 1. x Flat color quad
+	// 2. x Gouraud quad
+	// 3. x Quad with center point & outer color
+	// 4. x n sided glow (center color + outer color)
+	// 5. x n sided 2 layer glow ( center color + mid color + outer color)
+	// 6. x n spiked star (center color + mid color + spike color)
+	// 7. Lines (current color + previous color)
+	// 8. Ribbons - volumetric lines made from quads (current color + previous color)
+
+	switch ( type_checksum )
+	{
+		case 0xf4d8d486:		// Shaded
+			{
+				CNgcParticleShaded* p_particle = new CNgcParticleShaded( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x8addac1f:		// Smooth
+			{
+				CNgcParticleSmooth* p_particle = new CNgcParticleSmooth( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x15834eea:		// Glow
+			{
+				CNgcParticleGlow* p_particle = new CNgcParticleGlow( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x3624a5eb:		// Star
+			{
+				CNgcParticleStar* p_particle = new CNgcParticleStar( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x97cb7a9:		// SmoothStar
+			{
+				CNgcParticleSmoothStar* p_particle = new CNgcParticleSmoothStar( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x2eeb4b09:		// Line
+			{
+				CNgcParticleLine* p_particle = new CNgcParticleLine( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0xee6fc5b:		// Ribbon
+			{
+				CNgcParticleRibbon* p_particle = new CNgcParticleRibbon( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0xc4d5a4cb:		// RibbonTrail
+			{
+				CNgcParticleRibbonTrail* p_particle = new CNgcParticleRibbonTrail( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x3f109fcc:		// SmoothRibbon
+			{
+				CNgcParticleSmoothRibbon* p_particle = new CNgcParticleSmoothRibbon( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x7ec7252d:		// GlowRibbonTrail
+			{
+				CNgcParticleGlowRibbonTrail* p_particle = new CNgcParticleGlowRibbonTrail( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0xaab555bb:		// Flat
+		default:
+			{
+				CNgcParticleFlat* p_particle = new CNgcParticleFlat( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+	}
+}
+
+} // Nx
+
+				
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticle.h b/Code/Gfx/NGC/p_nxparticle.h
new file mode 100644
index 0000000..dd4b7e3
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticle.h
@@ -0,0 +1,31 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLE_H__
+#define	__GFX_P_NX_PARTICLE_H__
+    
+#include "gfx/nxparticle.h"
+#include "gfx/ngc/nx/render.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+NxNgc::BlendModes get_texture_blend( uint32 blend_checksum );
+	
+} // Nx
+
+#endif 
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleflat.cpp b/Code/Gfx/NGC/p_nxparticleflat.cpp
new file mode 100644
index 0000000..0e2f406
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleflat.cpp
@@ -0,0 +1,298 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleflat.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleFlat::CNgcParticleFlat()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleFlat::CNgcParticleFlat( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	m_start_color.r = 128;
+	m_start_color.g = 128;
+	m_start_color.b = 128;
+	m_start_color.a = 128;
+	m_mid_color.r = 128;
+	m_mid_color.g = 128;
+	m_mid_color.b = 128;
+	m_mid_color.a = 128;
+	m_end_color.r = 128;
+	m_end_color.g = 128;
+	m_end_color.b = 128;
+	m_end_color.a = 128;
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleFlat::~CNgcParticleFlat()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleFlat::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleFlat::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleFlat::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleFlat::plat_get_num_particle_colors( void ) { return 1; }
+int CNgcParticleFlat::plat_get_num_vertex_lists( void ) { return 1; }
+void CNgcParticleFlat::plat_set_sr( int entry, uint8 value ) { m_start_color.r = value; }
+void CNgcParticleFlat::plat_set_sg( int entry, uint8 value ) { m_start_color.g = value; }
+void CNgcParticleFlat::plat_set_sb( int entry, uint8 value ) { m_start_color.b = value; }
+void CNgcParticleFlat::plat_set_sa( int entry, uint8 value ) { m_start_color.a = value; }
+void CNgcParticleFlat::plat_set_mr( int entry, uint8 value ) { m_mid_color.r = value; }
+void CNgcParticleFlat::plat_set_mg( int entry, uint8 value ) { m_mid_color.g = value; }
+void CNgcParticleFlat::plat_set_mb( int entry, uint8 value ) { m_mid_color.b = value; }
+void CNgcParticleFlat::plat_set_ma( int entry, uint8 value ) { m_mid_color.a = value; }
+void CNgcParticleFlat::plat_set_er( int entry, uint8 value ) { m_end_color.r = value; }
+void CNgcParticleFlat::plat_set_eg( int entry, uint8 value ) { m_end_color.g = value; }
+void CNgcParticleFlat::plat_set_eb( int entry, uint8 value ) { m_end_color.b = value; }
+void CNgcParticleFlat::plat_set_ea( int entry, uint8 value ) { m_end_color.a = value; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleFlat::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	if ( mp_engine_texture )
+	{
+		GX::SetVtxDesc( 3, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+	}
+	else
+	{
+		GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+	}
+	if ( m_num_particles > 0 ) GX::Begin( GX_QUADS, GX_VTXFMT0, m_num_particles * 4 );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		VECScale((Vec*)&screen_right, (Vec*)&ss_right, w );
+		VECScale((Vec*)&screen_up, (Vec*)&ss_up, h );
+	
+		tmp[0]		= pos - ss_right + ss_up;
+		tmp[1]		= pos + ss_right + ss_up;		
+		tmp[2]		= pos + ss_right - ss_up;		
+		tmp[3]		= pos - ss_right - ss_up;		
+	
+		GXColor color;
+		GXColor *p_col0;
+		GXColor *p_col1;
+		
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = &m_start_color;
+				p_col1 = &m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = &m_mid_color;
+				p_col1 = &m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = &m_start_color;
+			p_col1 = &m_end_color;
+		}
+
+		GXColor start = *p_col0;
+		GXColor end = *p_col1;
+
+		color.r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+		color.g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+		color.b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+		color.a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+	
+		GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+		GX::Color1u32( *((uint32*)&color) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.0f, 0.0f );
+
+		GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+		GX::Color1u32( *((uint32*)&color) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 1.0f, 0.0f );
+
+		GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+		GX::Color1u32( *((uint32*)&color) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 1.0f, 1.0f );
+
+		GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+		GX::Color1u32( *((uint32*)&color) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.0f, 1.0f );
+	}
+
+	if ( m_num_particles > 0 ) GX::End();
+}
+
+} // Nx
+
diff --git a/Code/Gfx/NGC/p_nxparticleflat.h b/Code/Gfx/NGC/p_nxparticleflat.h
new file mode 100644
index 0000000..a838ddc
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleflat.h
@@ -0,0 +1,82 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEFLAT_H__
+#define	__GFX_P_NX_PARTICLEFLAT_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleFlat : public CParticle
+{
+public:
+							CNgcParticleFlat();
+							CNgcParticleFlat( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleFlat();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	GXColor					m_start_color;				// Start color for each corner.
+	GXColor					m_mid_color;				// Mid color for each corner.
+	GXColor					m_end_color;				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleglow.cpp b/Code/Gfx/NGC/p_nxparticleglow.cpp
new file mode 100644
index 0000000..f382ea5
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleglow.cpp
@@ -0,0 +1,319 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleGlow.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleGlow::CNgcParticleGlow()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleGlow::CNgcParticleGlow( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleGlow::~CNgcParticleGlow()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlow::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlow::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlow::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleGlow::plat_get_num_particle_colors( void ) { return 3; }
+int CNgcParticleGlow::plat_get_num_vertex_lists( void ) { return 1; }
+void CNgcParticleGlow::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleGlow::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleGlow::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleGlow::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleGlow::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleGlow::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleGlow::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleGlow::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleGlow::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleGlow::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleGlow::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleGlow::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlow::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		VECScale((Vec*)&screen_right, (Vec*)&ss_right, w );
+		VECScale((Vec*)&screen_up, (Vec*)&ss_up, h );
+	
+		GXColor color[3];
+		GXColor *p_col0;
+		GXColor *p_col1;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		tmp[0]  = pos;
+		tmp[0] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[0] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		tmp[2]  = pos;
+		tmp[2] += ss_right * sinf( Mth::DegToRad( 0.0f ) );
+		tmp[2] += ss_up    * cosf( Mth::DegToRad( 0.0f ) );
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[1]  = pos;
+			tmp[1] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+			tmp[1] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+
+			tmp[3]  = pos;
+			tmp[3] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+			tmp[3] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+
+			GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 5 );
+
+			GX::Position3f32( pos[X], pos[Y], pos[Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::End();
+
+			tmp[0] = tmp[1];
+			tmp[2] = tmp[3];
+		}
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleglow.h b/Code/Gfx/NGC/p_nxparticleglow.h
new file mode 100644
index 0000000..880a37c
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleglow.h
@@ -0,0 +1,87 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEGlow_H__
+#define	__GFX_P_NX_PARTICLEGlow_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleGlow : public CParticle
+{
+public:
+							CNgcParticleGlow();
+							CNgcParticleGlow( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleGlow();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+//	NxNgc::sParticleSystem*	mp_engine_particle;
+	int						m_num_segments;
+	float					m_split;
+	GXColor					m_start_color[3];			// Start color for each corner.
+	GXColor					m_mid_color[3];				// Mid color for each corner.
+	GXColor					m_end_color[3];				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleglowribbontrail.cpp b/Code/Gfx/NGC/p_nxparticleglowribbontrail.cpp
new file mode 100644
index 0000000..dbf05d5
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleglowribbontrail.cpp
@@ -0,0 +1,424 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleGlowRibbonTrail.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleGlowRibbonTrail::CNgcParticleGlowRibbonTrail()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleGlowRibbonTrail::CNgcParticleGlowRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	m_start_color = new GXColor[m_num_vertex_buffers+3];
+	m_mid_color = new GXColor[m_num_vertex_buffers+3];
+	m_end_color = new GXColor[m_num_vertex_buffers+3];
+	for ( int lp = 0; lp < (m_num_vertex_buffers+3); lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleGlowRibbonTrail::~CNgcParticleGlowRibbonTrail()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+	delete [] m_start_color;
+	delete [] m_mid_color;
+	delete [] m_end_color;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlowRibbonTrail::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlowRibbonTrail::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlowRibbonTrail::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleGlowRibbonTrail::plat_get_num_particle_colors( void ) { return m_num_vertex_buffers + 3; }
+int CNgcParticleGlowRibbonTrail::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CNgcParticleGlowRibbonTrail::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleGlowRibbonTrail::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleGlowRibbonTrail::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the ribbontrail.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->getAtX(), p_matrix->getAtY(), p_matrix->getAtZ(), 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	GXColor color[3];
+	GXColor *p_col0;
+	GXColor *p_col1;
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	for ( lp = 0, p_particle = mp_particle_array; lp < m_num_particles; lp++, p_particle++ )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		Mth::Vector	pos[2];
+		p_v = &mp_vertices[0][lp*3];
+		pos[0].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		p_v = &mp_vertices[1][lp*3];
+		pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos[1] - pos[0];
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+		Mth::Vector tmp[4];
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = &m_start_color[3];
+				p_col1 = &m_mid_color[3];
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = &m_mid_color[3];
+				p_col1 = &m_end_color[3];
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = &m_start_color[3];
+			p_col1 = &m_end_color[3];
+		}
+
+		GXColor start = *p_col0++;
+		GXColor end = *p_col1++;
+
+		color[0].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+		color[0].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+		color[0].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+		color[0].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+		tmp[0]		= pos[0] + ( perp_vec * w * m_split );
+		tmp[1]		= pos[0] - ( perp_vec * w * m_split );
+
+		GX::Begin( GX_QUADS, GX_VTXFMT0, 4 * ( m_num_vertex_buffers - 1 ) );
+
+		for ( int c = 1; c < m_num_vertex_buffers; c++ )
+		{
+			start = *p_col0++;
+			end = *p_col1++;
+
+			color[1].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[1].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[1].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[1].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+			if ( c > 1 )
+			{
+				p_v = &mp_vertices[c][lp*3];
+				pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				part_vec = pos[1] - pos[0];
+				perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+			}
+
+			tmp[2]		= pos[1] - ( perp_vec * w * m_split );
+			tmp[3]		= pos[1] + ( perp_vec * w * m_split );
+
+			GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			color[0] = color[1];
+			pos[0] = pos[1];
+			tmp[0] = tmp[3];
+			tmp[1] = tmp[2];
+		}
+
+		GX::End();
+
+		// Draw the glow.
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		p_v = &mp_vertices[0][lp*3];
+		pos[0].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		tmp[0]  = pos[0];
+		tmp[0] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[0] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		tmp[2]  = pos[0];
+		tmp[2] += ss_right * sinf( Mth::DegToRad( 0.0f ) );
+		tmp[2] += ss_up    * cosf( Mth::DegToRad( 0.0f ) );
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[1]  = pos[0];
+			tmp[1] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+			tmp[1] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+
+			tmp[3]  = pos[0];
+			tmp[3] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+			tmp[3] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+
+			GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 5 );
+
+			GX::Position3f32( pos[0][X], pos[0][Y], pos[0][Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::End();
+
+			tmp[0] = tmp[1];
+			tmp[2] = tmp[3];
+		}
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleglowribbontrail.h b/Code/Gfx/NGC/p_nxparticleglowribbontrail.h
new file mode 100644
index 0000000..b4880a9
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleglowribbontrail.h
@@ -0,0 +1,86 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEGlowRibbonTrail_H__
+#define	__GFX_P_NX_PARTICLEGlowRibbonTrail_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleGlowRibbonTrail : public CParticle
+{
+public:
+							CNgcParticleGlowRibbonTrail();
+							CNgcParticleGlowRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleGlowRibbonTrail();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	int						m_num_segments;
+	float					m_split;
+	GXColor*				m_start_color;				// Start color for each corner.
+	GXColor*				m_mid_color;				// Mid color for each corner.
+	GXColor*				m_end_color;				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleline.cpp b/Code/Gfx/NGC/p_nxparticleline.cpp
new file mode 100644
index 0000000..263dfa6
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleline.cpp
@@ -0,0 +1,272 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleLine.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleLine::CNgcParticleLine()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleLine::CNgcParticleLine( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffers.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	m_start_color = new GXColor[m_num_vertex_buffers];
+	m_mid_color = new GXColor[m_num_vertex_buffers];
+	m_end_color = new GXColor[m_num_vertex_buffers];
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleLine::~CNgcParticleLine()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleLine::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleLine::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleLine::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleLine::plat_get_num_particle_colors( void ) { return 2; }
+int CNgcParticleLine::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CNgcParticleLine::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleLine::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleLine::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleLine::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleLine::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleLine::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleLine::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleLine::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleLine::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleLine::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleLine::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleLine::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleLine::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v0;
+	float			*p_v1;
+
+	GXColor color[2];
+	GXColor *p_col0;
+	GXColor *p_col1;
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	if ( m_num_particles > 0 ) GX::Begin( GX_LINES, GX_VTXFMT0, m_num_particles * 2 );
+	for ( lp = 0, p_particle = mp_particle_array, p_v0 = mp_vertices[0], p_v1 = mp_vertices[(m_num_vertex_buffers-1)]; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 2; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+		Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+	
+		GX::Position3f32( pos0[X], pos0[Y], pos0[Z] );
+		GX::Color1u32( *((uint32*)&color[0]) );
+	
+		GX::Position3f32( pos1[X], pos1[Y], pos1[Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+	}
+	if ( m_num_particles > 0 ) GX::End();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleline.h b/Code/Gfx/NGC/p_nxparticleline.h
new file mode 100644
index 0000000..31037a9
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleline.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLELine_H__
+#define	__GFX_P_NX_PARTICLELine_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleLine : public CParticle
+{
+public:
+							CNgcParticleLine();
+							CNgcParticleLine( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleLine();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	GXColor*				m_start_color;				// Start color for each corner.
+	GXColor*				m_mid_color;				// Mid color for each corner.
+	GXColor*				m_end_color;				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleribbon.cpp b/Code/Gfx/NGC/p_nxparticleribbon.cpp
new file mode 100644
index 0000000..cbd9819
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleribbon.cpp
@@ -0,0 +1,292 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleRibbon.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleRibbon::CNgcParticleRibbon()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleRibbon::CNgcParticleRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	for ( int lp = 0; lp < 2; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleRibbon::~CNgcParticleRibbon()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbon::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbon::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbon::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleRibbon::plat_get_num_particle_colors( void ) { return 2; }
+int CNgcParticleRibbon::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CNgcParticleRibbon::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleRibbon::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleRibbon::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleRibbon::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleRibbon::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleRibbon::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleRibbon::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleRibbon::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleRibbon::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleRibbon::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleRibbon::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleRibbon::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbon::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v0;
+	float			*p_v1;
+
+	GXColor color[2];
+	GXColor *p_col0;
+	GXColor *p_col1;
+
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+	Mth::Vector at( p_matrix->getAtX(), p_matrix->getAtY(), p_matrix->getAtZ(), 0.0f );
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	if ( m_num_particles > 0 ) GX::Begin( GX_QUADS, GX_VTXFMT0, m_num_particles * 4 );
+	
+	for ( lp = 0, p_particle = mp_particle_array, p_v0 = mp_vertices[0], p_v1 = mp_vertices[(m_num_vertex_buffers-1)]; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+		
+		Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+		Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos1 - pos0;
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector tmp[4];
+	
+		tmp[0]		= pos0 + ( perp_vec * w );
+		tmp[1]		= pos0 - ( perp_vec * w );
+		tmp[2]		= pos1 - ( perp_vec * w );
+		tmp[3]		= pos1 + ( perp_vec * w );
+	
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 2; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+		GX::Color1u32( *((uint32*)&color[0]) );
+
+		GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+		GX::Color1u32( *((uint32*)&color[0]) );
+
+		GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+
+		GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+	}
+	
+	if ( m_num_particles > 0 ) GX::End();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleribbon.h b/Code/Gfx/NGC/p_nxparticleribbon.h
new file mode 100644
index 0000000..d87294e
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleribbon.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLERibbon_H__
+#define	__GFX_P_NX_PARTICLERibbon_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleRibbon : public CParticle
+{
+public:
+							CNgcParticleRibbon();
+							CNgcParticleRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleRibbon();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	GXColor					m_start_color[2];			// Start color for each corner.
+	GXColor					m_mid_color[2];				// Mid color for each corner.
+	GXColor					m_end_color[2];				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleribbontrail.cpp b/Code/Gfx/NGC/p_nxparticleribbontrail.cpp
new file mode 100644
index 0000000..a1fdcbf
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleribbontrail.cpp
@@ -0,0 +1,322 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleRibbonTrail.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleRibbonTrail::CNgcParticleRibbonTrail()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleRibbonTrail::CNgcParticleRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	m_start_color = new GXColor[m_num_vertex_buffers];
+	m_mid_color = new GXColor[m_num_vertex_buffers];
+	m_end_color = new GXColor[m_num_vertex_buffers];
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleRibbonTrail::~CNgcParticleRibbonTrail()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+	delete [] m_start_color;
+	delete [] m_mid_color;
+	delete [] m_end_color;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbonTrail::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbonTrail::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbonTrail::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleRibbonTrail::plat_get_num_particle_colors( void ) { return m_num_vertex_buffers; }
+int CNgcParticleRibbonTrail::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CNgcParticleRibbonTrail::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleRibbonTrail::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleRibbonTrail::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleRibbonTrail::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleRibbonTrail::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleRibbonTrail::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleRibbonTrail::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleRibbonTrail::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleRibbonTrail::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleRibbonTrail::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleRibbonTrail::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleRibbonTrail::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleRibbonTrail::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	GXColor color[2];
+	GXColor *p_col0;
+	GXColor *p_col1;
+
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+	Mth::Vector at( p_matrix->getAtX(), p_matrix->getAtY(), p_matrix->getAtZ(), 0.0f );
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	for ( lp = 0, p_particle = mp_particle_array; lp < m_num_particles; lp++, p_particle++ )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+		
+		Mth::Vector	pos[2];
+		p_v = &mp_vertices[0][lp*3];
+		pos[0].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		p_v = &mp_vertices[1][lp*3];
+		pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos[1] - pos[0];
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector tmp[4];
+	
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		GXColor start = *p_col0++;
+		GXColor end = *p_col1++;
+
+		color[0].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+		color[0].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+		color[0].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+		color[0].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+		tmp[0]		= pos[0] + ( perp_vec * w );
+		tmp[1]		= pos[0] - ( perp_vec * w );
+
+		GX::Begin( GX_QUADS, GX_VTXFMT0, 4 * ( m_num_vertex_buffers - 1 ) );
+
+		for ( int c = 1; c < m_num_vertex_buffers; c++ )
+		{
+			start = *p_col0++;
+			end = *p_col1++;
+
+			color[1].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[1].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[1].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[1].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+			if ( c > 1 )
+			{
+				p_v = &mp_vertices[c][lp*3];
+				pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				part_vec = pos[1] - pos[0];
+				perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+			}
+
+			tmp[2]		= pos[1] - ( perp_vec * w );
+			tmp[3]		= pos[1] + ( perp_vec * w );
+
+			GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			color[0] = color[1];
+			pos[0] = pos[1];
+			tmp[0] = tmp[3];
+			tmp[1] = tmp[2];
+		}
+		GX::End();
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleribbontrail.h b/Code/Gfx/NGC/p_nxparticleribbontrail.h
new file mode 100644
index 0000000..10aca3c
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleribbontrail.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLERibbonTrail_H__
+#define	__GFX_P_NX_PARTICLERibbonTrail_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleRibbonTrail : public CParticle
+{
+public:
+							CNgcParticleRibbonTrail();
+							CNgcParticleRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleRibbonTrail();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	GXColor*				m_start_color;				// Start color for each corner.
+	GXColor*				m_mid_color;				// Mid color for each corner.
+	GXColor*				m_end_color;				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleshaded.cpp b/Code/Gfx/NGC/p_nxparticleshaded.cpp
new file mode 100644
index 0000000..cd81f0f
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleshaded.cpp
@@ -0,0 +1,305 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleShaded.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleShaded::CNgcParticleShaded()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleShaded::CNgcParticleShaded( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	for ( int lp = 0; lp < 4; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleShaded::~CNgcParticleShaded()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleShaded::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleShaded::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleShaded::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleShaded::plat_get_num_particle_colors( void ) { return 4; }
+int CNgcParticleShaded::plat_get_num_vertex_lists( void ) { return 1; }
+void CNgcParticleShaded::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleShaded::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleShaded::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleShaded::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleShaded::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleShaded::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleShaded::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleShaded::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleShaded::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleShaded::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleShaded::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleShaded::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleShaded::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	if ( mp_engine_texture )
+	{
+		GX::SetVtxDesc( 3, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+	}
+	else
+	{
+		GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+	}
+	if ( m_num_particles > 0 ) GX::Begin( GX_QUADS, GX_VTXFMT0, m_num_particles * 4 );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		VECScale((Vec*)&screen_right, (Vec*)&ss_right, w );
+		VECScale((Vec*)&screen_up, (Vec*)&ss_up, h );
+	
+		tmp[0]		= pos - ss_right + ss_up;
+		tmp[1]		= pos + ss_right + ss_up;		
+		tmp[2]		= pos + ss_right - ss_up;		
+		tmp[3]		= pos - ss_right - ss_up;		
+	
+		GXColor color[4];
+		GXColor *p_col0;
+		GXColor *p_col1;
+		
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 4; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+	
+		GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+		GX::Color1u32( *((uint32*)&color[0]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.0f, 0.0f );
+
+		GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 1.0f, 0.0f );
+
+		GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+		GX::Color1u32( *((uint32*)&color[2]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 1.0f, 1.0f );
+
+		GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+		GX::Color1u32( *((uint32*)&color[3]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.0f, 1.0f );
+	}
+
+	if ( m_num_particles > 0 ) GX::End();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGC/p_nxparticleshaded.h b/Code/Gfx/NGC/p_nxparticleshaded.h
new file mode 100644
index 0000000..f2a3835
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticleshaded.h
@@ -0,0 +1,83 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEShaded_H__
+#define	__GFX_P_NX_PARTICLEShaded_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleShaded : public CParticle
+{
+public:
+							CNgcParticleShaded();
+							CNgcParticleShaded( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleShaded();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	GXColor					m_start_color[4];			// Start color for each corner.
+	GXColor					m_mid_color[4];				// Mid color for each corner.
+	GXColor					m_end_color[4];				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlesmooth.cpp b/Code/Gfx/NGC/p_nxparticlesmooth.cpp
new file mode 100644
index 0000000..88471ae
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlesmooth.cpp
@@ -0,0 +1,314 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleSmooth.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmooth::CNgcParticleSmooth()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmooth::CNgcParticleSmooth( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	for ( int lp = 0; lp < 2; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmooth::~CNgcParticleSmooth()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmooth::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmooth::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmooth::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleSmooth::plat_get_num_particle_colors( void ) { return 2; }
+int CNgcParticleSmooth::plat_get_num_vertex_lists( void ) { return 1; }
+void CNgcParticleSmooth::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleSmooth::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleSmooth::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleSmooth::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleSmooth::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleSmooth::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleSmooth::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleSmooth::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleSmooth::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleSmooth::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleSmooth::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleSmooth::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmooth::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	if ( mp_engine_texture )
+	{
+		GX::SetVtxDesc( 3, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+	}
+	else
+	{
+		GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+	}
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		VECScale((Vec*)&screen_right, (Vec*)&ss_right, w );
+		VECScale((Vec*)&screen_up, (Vec*)&ss_up, h );
+	
+		tmp[0]		= pos - ss_right + ss_up;
+		tmp[1]		= pos + ss_right + ss_up;		
+		tmp[2]		= pos + ss_right - ss_up;		
+		tmp[3]		= pos - ss_right - ss_up;		
+	
+		GXColor color[2];
+		GXColor *p_col0;
+		GXColor *p_col1;
+		
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 2; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+		GX::Begin( GX_TRIANGLEFAN, GX_VTXFMT0, 6 );
+	
+		GX::Position3f32( pos[X], pos[Y], pos[Z] );
+		GX::Color1u32( *((uint32*)&color[0]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.5f, 0.5f );
+
+		GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.0f, 0.0f );
+
+		GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 1.0f, 0.0f );
+
+		GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 1.0f, 1.0f );
+
+		GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.0f, 1.0f );
+
+		GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+		if ( mp_engine_texture ) GX::TexCoord2f32( 0.0f, 0.0f );
+
+		GX::End();
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlesmooth.h b/Code/Gfx/NGC/p_nxparticlesmooth.h
new file mode 100644
index 0000000..d31cfbc
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlesmooth.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLESmooth_H__
+#define	__GFX_P_NX_PARTICLESmooth_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleSmooth : public CParticle
+{
+public:
+							CNgcParticleSmooth();
+							CNgcParticleSmooth( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleSmooth();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	GXColor					m_start_color[2];			// Start color for each corner.
+	GXColor					m_mid_color[2];				// Mid color for each corner.
+	GXColor					m_end_color[2];				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlesmoothribbon.cpp b/Code/Gfx/NGC/p_nxparticlesmoothribbon.cpp
new file mode 100644
index 0000000..0b8c6fc
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlesmoothribbon.cpp
@@ -0,0 +1,298 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleSmoothRibbon.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmoothRibbon::CNgcParticleSmoothRibbon()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmoothRibbon::CNgcParticleSmoothRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	for ( int lp = 0; lp < 4; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmoothRibbon::~CNgcParticleSmoothRibbon()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothRibbon::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothRibbon::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothRibbon::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleSmoothRibbon::plat_get_num_particle_colors( void ) { return 4; }
+int CNgcParticleSmoothRibbon::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CNgcParticleSmoothRibbon::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleSmoothRibbon::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleSmoothRibbon::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleSmoothRibbon::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleSmoothRibbon::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleSmoothRibbon::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleSmoothRibbon::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleSmoothRibbon::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleSmoothRibbon::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleSmoothRibbon::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleSmoothRibbon::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleSmoothRibbon::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothRibbon::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v0;
+	float			*p_v1;
+
+	GXColor color[4];
+	GXColor *p_col0;
+	GXColor *p_col1;
+
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+	Mth::Vector at( p_matrix->getAtX(), p_matrix->getAtY(), p_matrix->getAtZ(), 0.0f );
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+	
+	for ( lp = 0, p_particle = mp_particle_array, p_v0 = mp_vertices[0], p_v1 = mp_vertices[(m_num_vertex_buffers-1)]; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+		
+		Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+		Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos1 - pos0;
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector tmp[4];
+	
+		tmp[0]		= pos0 + ( perp_vec * w );
+		tmp[1]		= pos0 - ( perp_vec * w );
+		tmp[2]		= pos1 - ( perp_vec * w );
+		tmp[3]		= pos1 + ( perp_vec * w );
+	
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 4; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 6 );
+
+		GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+
+		GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+		GX::Color1u32( *((uint32*)&color[2]) );
+
+		GX::Position3f32( pos0[X], pos0[Y], pos0[Z] );
+		GX::Color1u32( *((uint32*)&color[0]) );
+
+		GX::Position3f32( pos1[X], pos1[Y], pos1[Z] );
+		GX::Color1u32( *((uint32*)&color[3]) );
+		
+		GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+		GX::Color1u32( *((uint32*)&color[1]) );
+
+		GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+		GX::Color1u32( *((uint32*)&color[2]) );
+
+		GX::End();
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlesmoothribbon.h b/Code/Gfx/NGC/p_nxparticlesmoothribbon.h
new file mode 100644
index 0000000..69306a7
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlesmoothribbon.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLESmoothRibbon_H__
+#define	__GFX_P_NX_PARTICLESmoothRibbon_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleSmoothRibbon : public CParticle
+{
+public:
+							CNgcParticleSmoothRibbon();
+							CNgcParticleSmoothRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleSmoothRibbon();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	GXColor					m_start_color[4];			// Start color for each corner.
+	GXColor					m_mid_color[4];				// Mid color for each corner.
+	GXColor					m_end_color[4];				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlesmoothstar.cpp b/Code/Gfx/NGC/p_nxparticlesmoothstar.cpp
new file mode 100644
index 0000000..5ff3281
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlesmoothstar.cpp
@@ -0,0 +1,321 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleSmoothStar.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmoothStar::CNgcParticleSmoothStar()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmoothStar::CNgcParticleSmoothStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleSmoothStar::~CNgcParticleSmoothStar()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothStar::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothStar::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothStar::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleSmoothStar::plat_get_num_particle_colors( void ) { return 3; }
+int CNgcParticleSmoothStar::plat_get_num_vertex_lists( void ) { return 1; }
+void CNgcParticleSmoothStar::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleSmoothStar::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleSmoothStar::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleSmoothStar::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleSmoothStar::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleSmoothStar::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleSmoothStar::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleSmoothStar::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleSmoothStar::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleSmoothStar::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleSmoothStar::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleSmoothStar::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleSmoothStar::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		VECScale((Vec*)&screen_right, (Vec*)&ss_right, w );
+		VECScale((Vec*)&screen_up, (Vec*)&ss_up, h );
+	
+		GXColor color[3];
+		GXColor *p_col0;
+		GXColor *p_col1;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		tmp[1]  = pos;
+		tmp[1] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[1] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[0]  = pos;
+			tmp[0] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) ) * m_split;
+			tmp[0] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) ) * m_split;
+			
+			tmp[3]  = pos;
+			tmp[3] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 2 ) ) ) ) * m_split;
+			tmp[3] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 2 ) ) ) ) * m_split;
+			
+			tmp[2]  = pos;
+			tmp[2] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+			tmp[2] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+
+			GX::Begin( GX_TRIANGLEFAN, GX_VTXFMT0, 6 );
+
+			GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::Position3f32( tmp[3][X], tmp[3][Y], tmp[3][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::Position3f32( pos[X], pos[Y], pos[Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::End();
+
+			tmp[1] = tmp[3];
+		}
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlesmoothstar.h b/Code/Gfx/NGC/p_nxparticlesmoothstar.h
new file mode 100644
index 0000000..a0609d9
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlesmoothstar.h
@@ -0,0 +1,86 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLESmoothStar_H__
+#define	__GFX_P_NX_PARTICLESmoothStar_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleSmoothStar : public CParticle
+{
+public:
+							CNgcParticleSmoothStar();
+							CNgcParticleSmoothStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleSmoothStar();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	int						m_num_segments;
+	float					m_split;
+	GXColor					m_start_color[3];			// SmoothStart color for each corner.
+	GXColor					m_mid_color[3];				// Mid color for each corner.
+	GXColor					m_end_color[3];				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlestar.cpp b/Code/Gfx/NGC/p_nxparticlestar.cpp
new file mode 100644
index 0000000..3c23dd3
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlestar.cpp
@@ -0,0 +1,311 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/Ngc/nx/render.h"
+#include 
+#include 
+#include "gfx/Ngc/nx/mesh.h"
+#include "gfx/ngc/nx/nx_init.h"
+#include "sys/ngc/p_gx.h"
+
+#include "gfx/Ngc/p_nxparticleStar.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleStar::CNgcParticleStar()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleStar::CNgcParticleStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxNgc::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_engine_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = (uint8)get_texture_blend( blendmode_checksum );
+	m_fix = fix;
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcParticleStar::~CNgcParticleStar()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleStar::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleStar::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleStar::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CNgcParticleStar::plat_get_num_particle_colors( void ) { return 3; }
+int CNgcParticleStar::plat_get_num_vertex_lists( void ) { return 1; }
+void CNgcParticleStar::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CNgcParticleStar::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CNgcParticleStar::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CNgcParticleStar::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CNgcParticleStar::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CNgcParticleStar::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CNgcParticleStar::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CNgcParticleStar::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CNgcParticleStar::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CNgcParticleStar::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CNgcParticleStar::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CNgcParticleStar::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcParticleStar::plat_render( void )
+{
+	NxNgc::sMaterialHeader		mat;
+	NxNgc::sMaterialPassHeader	pass;
+
+	// Header.
+	mat.m_checksum			= 0xa9db601e;   // particle 
+	mat.m_passes			= 1;
+	mat.m_alpha_cutoff		= 1;
+	mat.m_flags				= (1<<1);		// 2 sided.
+//	mat.m_shininess			= 0.0f;
+
+	// Pass 0.
+	pass.m_texture.p_data	= mp_engine_texture;
+	pass.m_flags			= ( mp_engine_texture ? (1<<0) : 0 ) | (1<<5) | (1<<6);		// textured, clamped.
+	pass.m_filter			= 0;
+	pass.m_blend_mode		= (unsigned char)m_blend;
+	pass.m_alpha_fix		= (unsigned char)m_fix; 
+	pass.m_k				= 0;
+	pass.m_color.r			= 128;
+	pass.m_color.g			= 128;
+	pass.m_color.b			= 128;
+	pass.m_color.a			= 255;
+
+	NxNgc::multi_mesh( &mat, &pass, true, true );
+
+	GX::SetTexCoordGen( GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_FALSE, GX_PTIDENTITY );
+	GX::SetCurrMtxPosTex03( GX_PNMTX0, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY, GX_IDENTITY );
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[3];
+	
+		VECScale((Vec*)&screen_right, (Vec*)&ss_right, w );
+		VECScale((Vec*)&screen_up, (Vec*)&ss_up, h );
+	
+		GXColor color[3];
+		GXColor *p_col0;
+		GXColor *p_col1;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			GXColor start = *p_col0++;
+			GXColor end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		tmp[0]  = pos;
+		tmp[0] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[0] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[1]  = pos;
+			tmp[1] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+			tmp[1] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+
+			tmp[2]  = pos;
+			tmp[2] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+			tmp[2] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+
+			GX::Begin( GX_TRIANGLESTRIP, GX_VTXFMT0, 4 );
+
+			GX::Position3f32( pos[X], pos[Y], pos[Z] );
+			GX::Color1u32( *((uint32*)&color[0]) );
+
+			GX::Position3f32( tmp[0][X], tmp[0][Y], tmp[0][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[1][X], tmp[1][Y], tmp[1][Z] );
+			GX::Color1u32( *((uint32*)&color[1]) );
+
+			GX::Position3f32( tmp[2][X], tmp[2][Y], tmp[2][Z] );
+			GX::Color1u32( *((uint32*)&color[2]) );
+
+			GX::End();
+
+			tmp[0] = tmp[1];
+		}
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGC/p_nxparticlestar.h b/Code/Gfx/NGC/p_nxparticlestar.h
new file mode 100644
index 0000000..4586f14
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxparticlestar.h
@@ -0,0 +1,86 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEStar_H__
+#define	__GFX_P_NX_PARTICLEStar_H__
+    
+#include "gfx/Ngc/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CNgcParticleStar : public CParticle
+{
+public:
+							CNgcParticleStar();
+							CNgcParticleStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CNgcParticleStar();
+
+//	NxNgc::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint8					m_blend;
+	uint8					m_fix;
+	uint8					m_pad0;
+	uint8					m_pad1;
+
+	int						m_num_segments;
+	float					m_split;
+	GXColor					m_start_color[3];			// Start color for each corner.
+	GXColor					m_mid_color[3];				// Mid color for each corner.
+	GXColor					m_end_color[3];				// End color for each corner.
+	NxNgc::sTexture *		mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxscene.cpp b/Code/Gfx/NGC/p_nxscene.cpp
new file mode 100644
index 0000000..2d540d7
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxscene.cpp
@@ -0,0 +1,138 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.cpp
+
+#include 	"gfx/Ngc/p_NxScene.h"
+#include 	"gfx/Ngc/p_NxSector.h"
+#include 	"gfx/Ngc/p_NxGeom.h"
+
+namespace Nx
+{
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcScene::CNgcScene( int sector_table_size ) : CScene( sector_table_size )
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcScene::DestroySectorMeshes( void )
+{
+	// Iterate through the list of sectors, removing each one in turn, and requesting the engine to
+	// remove the meshes.
+	mp_sector_table->IterateStart();
+	CSector* pSector = mp_sector_table->IterateNext();
+	while( pSector )
+	{
+		// Access platform dependent data.
+//		CNgcSector* pNgcSector = static_cast(pSector);
+
+		// Remove this mesh array from the engine.
+//		pNgcSector->DestroyMeshArray();
+
+		pSector = mp_sector_table->IterateNext();
+	}
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CScene
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcScene::plat_post_load()
+{
+	// Now turn the temporary mesh lists into mesh arrays.
+	mp_sector_table->IterateStart();
+	CSector* pSector = mp_sector_table->IterateNext();
+	while( pSector )
+	{
+		CNgcGeom *p_Ngc_geom = static_cast(pSector->GetGeom());
+
+		p_Ngc_geom->CreateMeshArray();
+
+		// First time through we just want to count the meshes,
+		p_Ngc_geom->RegisterMeshArray( true );
+
+		pSector = mp_sector_table->IterateNext();
+	}
+
+	// Now we have counted all the meshes, tell the engine to create the arrays to hold them.
+	GetEngineScene()->CreateMeshArrays();
+	
+	// Now go through and actually add the meshes.
+	mp_sector_table->IterateStart();
+	pSector = mp_sector_table->IterateNext();
+	while( pSector )
+	{
+		// Access platform dependent data.
+		CNgcGeom *p_Ngc_geom = static_cast(pSector->GetGeom());
+
+		p_Ngc_geom->RegisterMeshArray( false );
+
+		pSector = mp_sector_table->IterateNext();
+	}
+
+	// Now all meshes are registered, tell the engine to sort them.
+	GetEngineScene()->SortMeshes();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcScene::plat_load_textures(const char* p_name)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcScene::plat_load_collision(const char* p_name)
+{
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcScene::plat_unload_add_scene( void )
+{
+	// Not sure what this is supposed to do, but added for now to remove annoying stub output.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Create an empty sector
+CSector	*			CNgcScene::plat_create_sector()
+{
+	CNgcSector *p_Ngc_sector	= new CNgcSector();
+	return p_Ngc_sector;
+}
+
+
+
+} // Namespace Nx  			
+				
+				
+
diff --git a/Code/Gfx/NGC/p_nxscene.h b/Code/Gfx/NGC/p_nxscene.h
new file mode 100644
index 0000000..ae7aedc
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxscene.h
@@ -0,0 +1,41 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_SCENE_H__
+#define	__GFX_P_NX_SCENE_H__
+
+#include "Gfx/Nx.h"
+#include "Gfx/Ngc/nx/scene.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CScene
+class	CNgcScene : public CScene
+{
+public:
+
+								CNgcScene( int sector_table_size = 10 );
+	NxNgc::sScene *			GetEngineScene() const						{ return mp_engine_scene; }
+	void						SetEngineScene( NxNgc::sScene *p_scene )	{ mp_engine_scene = p_scene; }
+	void						DestroySectorMeshes( void );
+
+private:		// It's all private, as it is machine specific
+	virtual void				plat_post_load();	
+	virtual bool				plat_load_textures( const char *p_name );	// load textures 
+	virtual bool				plat_load_collision( const char *p_name );	// load collision data
+	virtual bool				plat_unload_add_scene( void );
+	virtual	CSector	*			plat_create_sector();	 					// empty sector
+
+
+	NxNgc::sScene				*mp_engine_scene;
+
+};
+
+} // Namespace Nx  			
+
+#endif
+
diff --git a/Code/Gfx/NGC/p_nxsector.cpp b/Code/Gfx/NGC/p_nxsector.cpp
new file mode 100644
index 0000000..045bd33
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxsector.cpp
@@ -0,0 +1,980 @@
+
+///////////////////////////////////////////////////////////////////////////////
+// p_NxSector.cpp
+
+#include	
+
+#include 	"gfx/ngc/p_NxSector.h"
+#include 	"gfx/ngc/p_NxGeom.h"
+#include 	"gfx/Image/ImageBasic.h"
+#include 	"gfx/ngc/nx/render.h"
+#include 	"gfx/ngc/nx/mesh.h"
+#include 	"gfx/NxMiscFX.h"
+#include 	"gfx\ngc\nx\nx_init.h"
+#include	
+
+//NxNgc::sMesh *	p_u_mat[256];
+//int				u_mat_count[256];
+//int				num_u_mat = 0;
+
+namespace Nx
+{
+
+#ifdef SHORT_VERT
+
+static int round_float( float f, int shift, float off, float cen )
+{
+	float mul = ((float)( 1 << shift ));
+	int i_f = (int)( ( f - cen ) * mul );
+
+	int adjust = 0;
+	if ( i_f > 0 )
+	{
+		adjust = 1;
+	}
+//	if ( i_f < 0 )
+//	{
+//		adjust = -1;
+//	}
+
+	i_f = (int)( ( f - off ) * mul );
+	i_f += adjust;
+	return i_f;
+
+//	float mul = ((float)( 1 << shift ));
+//	int i_f = (int)( f * mul );
+//	float d_f = ( f * mul ) - ((float)i_f);
+//
+//	if ( d_f >= 0.0f )
+//	{
+//		if( d_f < 0.5f )
+//		{
+//			return i_f;
+//		}
+//		else
+//		{
+//			return i_f + 1;
+//		}
+//	}
+//	else
+//	{
+//		if( d_f < -0.5f )
+//		{
+//			return i_f;
+//		}
+//		else
+//		{
+//			return i_f - 1;
+//		}
+//	}
+}
+#endif		// SHORT_VERT
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//CNgcSector::CNgcSector() : mp_init_mesh_list( NULL ), m_mesh_array( NULL ), m_visible( 0xDEADBEEF )
+CNgcSector::CNgcSector()
+{
+	m_pos_offset.Set( 0.0f, 0.0f, 0.0f );
+//	m_active = true;						// default to be active....
+//	mp_scene = NULL;
+}
+
+
+
+
+#define MemoryRead( dst, size, num, src )	memcpy(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+NxNgc::sObjectHeader* CNgcSector::LoadFromMemory( NxNgc::sObjectHeader* p_data )
+{
+//	uint8		*p_file_data	= (uint8*)( *pp_mem );
+//
+//	Dbg_Assert( mp_geom );
+//
+//	CNgcGeom *p_geom = static_cast( mp_geom );
+//	
+//	// Read sector checksum.
+//	uint32 sector_checksum;
+//	MemoryRead( §or_checksum, sizeof( uint32 ), 1, p_file_data );
+//	
+//	SetChecksum( sector_checksum );
+//
+//	// Read bone index.
+//	int bone_idx;
+//	MemoryRead( &bone_idx, sizeof( int ), 1, p_file_data );
+//
+//	// Read sector flags.
+//	uint32 flags;
+//	MemoryRead( &flags, sizeof( int ), 1, p_file_data );
+//	m_flags = flags;
+//
+//	// Read number of meshes.
+//	int num_mesh;
+//	MemoryRead( &num_mesh, sizeof( uint ), 1, p_file_data );
+//	p_geom->m_num_mesh = num_mesh;
+//
+//	// Read bounding box.
+//	float bbox[6];
+//	MemoryRead( &bbox[0], sizeof( float ), 6, p_file_data );
+//	Mth::Vector	inf( bbox[0], bbox[1], bbox[2] );
+//	Mth::Vector	sup( bbox[3], bbox[4], bbox[5] );
+//	p_geom->m_bbox.Set( inf, sup );
+//		
+//	// Read bounding sphere.
+//	float bsphere[4];
+//	MemoryRead( &bsphere[0], sizeof( float ), 4, p_file_data );
+//		
+//	// Read num vertices.
+//	int num_vertices;
+//	MemoryRead( &num_vertices, sizeof( int ), 1, p_file_data );
+//	
+//	// Read vertex data stride.
+//	int vertex_data_stride;
+//	MemoryRead( &vertex_data_stride, sizeof( int ), 1, p_file_data );
+//	
+//	// Only read vertex/normal pools if skinned.
+//	int dbytes = 0;
+//	int sbytes = 0;
+//	float* p_vertex_positions = NULL;
+//	s16* p_vertex_normals = NULL;
+//	uint32* p_double = NULL;
+//	uint32* p_temp = NULL;
+//	int num_double = 0;
+//	int num_single = 0;
+//#ifdef SHORT_VERT
+//	s16* p_vertex_positions16 = NULL;
+//	int shift = 0;
+//	float off_x = 0.0f;
+//	float off_y = 0.0f;
+//	float off_z = 0.0f;
+//#endif		// SHORT_VERT
+//	if ( !(m_flags & 0x10) )
+//	{
+//		// Grab a buffer for the raw vertex data position stream, and read it.
+//#ifdef SHORT_VERT
+//		p_vertex_positions = new (Mem::Manager::sHandle().TopDownHeap()) float[num_vertices * 3];
+//#else
+//		p_vertex_positions = new float[num_vertices * 3];
+//#endif		// SHORT_VERT
+//		MemoryRead( p_vertex_positions, sizeof( float ) * 3, num_vertices, p_file_data );
+//
+//#ifdef SHORT_VERT
+//		int lp;
+//
+//		// Calculate the largest dimension.
+//		float smallest_x = p_vertex_positions[0];
+//		float smallest_y = p_vertex_positions[1];
+//		float smallest_z = p_vertex_positions[2];
+//		float largest_x = p_vertex_positions[0];
+//		float largest_y = p_vertex_positions[1];
+//		float largest_z = p_vertex_positions[2];
+//		for ( lp = 1; lp < num_vertices; lp++ )
+//		{
+//			if ( p_vertex_positions[(lp*3)+0] > largest_x ) largest_x = p_vertex_positions[(lp*3)+0];
+//			if ( p_vertex_positions[(lp*3)+1] > largest_y ) largest_y = p_vertex_positions[(lp*3)+1];
+//			if ( p_vertex_positions[(lp*3)+2] > largest_z ) largest_z = p_vertex_positions[(lp*3)+2];
+//			if ( p_vertex_positions[(lp*3)+0] < smallest_x ) smallest_x = p_vertex_positions[(lp*3)+0];
+//			if ( p_vertex_positions[(lp*3)+1] < smallest_y ) smallest_y = p_vertex_positions[(lp*3)+1];
+//			if ( p_vertex_positions[(lp*3)+2] < smallest_z ) smallest_z = p_vertex_positions[(lp*3)+2];
+//		}
+//		float biggest = largest_x;
+//		if ( largest_y > biggest ) biggest = largest_y;
+//		if ( largest_z > biggest ) biggest = largest_z;
+//		if ( -smallest_x > biggest ) biggest = -smallest_x;
+//		if ( -smallest_y > biggest ) biggest = -smallest_y;
+//		if ( -smallest_z > biggest ) biggest = -smallest_z;
+//
+//		float c_x = ( smallest_x + largest_x ) / 2.0f;
+//		float c_y = ( smallest_y + largest_y ) / 2.0f;
+//		float c_z = ( smallest_z + largest_z ) / 2.0f;
+//
+//		// Work out the shift amount.
+//		if ( biggest > ( ( 1 << 15 ) - 1 ) )
+//		{
+//			// Work out how to offset it.
+//			off_x = ( smallest_x + largest_x ) / 2.0f;
+//			off_y = ( smallest_y + largest_y ) / 2.0f;
+//			off_z = ( smallest_z + largest_z ) / 2.0f;
+//			biggest = ( largest_x - smallest_x ) / 2.0f;
+//			if ( ( ( largest_y - smallest_y ) / 2.0f ) > biggest ) biggest = ( ( largest_y - smallest_y ) / 2.0f );
+//			if ( ( ( largest_z - smallest_z ) / 2.0f ) > biggest ) biggest = ( ( largest_z - smallest_z ) / 2.0f );
+//
+//			if ( ( largest_x - smallest_x ) > 65535 )
+//			{
+//				OSReport( "Cannot deal with meshes larger than 65535 X units, this mesh is %6.3f\n", ( largest_x - smallest_x ) );
+//				while ( 1 == 1 );
+//			}
+//
+//			if ( ( largest_y - smallest_y ) > 65535 )
+//			{
+//				OSReport( "Cannot deal with meshes larger than 65535 Y units, this mesh is %6.3f\n", ( largest_y - smallest_y ) );
+//				while ( 1 == 1 );
+//			}
+//
+//			if ( ( largest_z - smallest_z ) > 65535 )
+//			{
+//				OSReport( "Cannot deal with meshes larger than 65535 Z units, this mesh is %6.3f\n", ( largest_z - smallest_z ) );
+//				while ( 1 == 1 );
+//			}
+//			shift = 0;
+//		}
+//
+//		if ( biggest > ( ( 1 << 14 ) - 1 ) )
+//		{
+//			shift = 0;
+//		}
+//		else
+//		if ( biggest > ( ( 1 << 13 ) - 1 ) )
+//		{
+//			shift = 1;
+//		}
+//		else
+//		if ( biggest > ( ( 1 << 12 ) - 1 ) )
+//		{
+//			shift = 2;
+//		}
+//		else
+//		if ( biggest > ( ( 1 << 11 ) - 1 ) )
+//		{
+//			shift = 3;
+//		}
+//		else
+//		if ( biggest > ( ( 1 << 10 ) - 1 ) )
+//		{
+//			shift = 4;
+//		}
+//		else
+//		{
+//			shift = 5;
+//		}
+////		if ( biggest > ( ( 1 << 9 ) - 1 ) )
+////		{
+////			shift = 5;
+////		}
+////		else
+////		{
+////			shift = 6;
+////		}
+//
+//		// Now, we need to squeeze the vertex positions down to s16s (1:9:6)
+//		p_vertex_positions16 = new s16[num_vertices * 3];
+//		for ( lp = 0; lp < num_vertices; lp++ )
+//		{
+//			p_vertex_positions16[(lp*3)+0] = (s16)( round_float( p_vertex_positions[(lp*3)+0], shift, off_x, c_x ) );
+//			p_vertex_positions16[(lp*3)+1] = (s16)( round_float( p_vertex_positions[(lp*3)+1], shift, off_y, c_y ) );
+//			p_vertex_positions16[(lp*3)+2] = (s16)( round_float( p_vertex_positions[(lp*3)+2], shift, off_z, c_z ) );
+//		}
+//#endif		// SHORT_VERT
+//
+//		// Grab a buffer for the raw vertex data normal stream (if present), and read it.
+//		p_vertex_normals = ( m_flags & 0x04 ) ? new s16[num_vertices * 3] : NULL;
+//		if( p_vertex_normals )
+//		{
+//			MemoryRead( p_vertex_normals, sizeof( s16 ) * 3, num_vertices, p_file_data );
+//		}
+//	} else {
+//		int bytes = 0;
+//		// This is a skinned model, read in the double data.
+//		MemoryRead( &bytes, sizeof( int ), 1, p_file_data );
+//		MemoryRead( &num_double, sizeof( int ), 1, p_file_data );
+//
+//		dbytes = ( bytes + 3 ) / 4;
+//		p_temp = new uint32[dbytes];
+//		MemoryRead( p_temp, bytes, 1, p_file_data );
+//
+//		// Read the single data.
+//		MemoryRead( &bytes, sizeof( int ), 1, p_file_data );
+//		MemoryRead( &num_single, sizeof( int ), 1, p_file_data );
+//
+//		sbytes = ( bytes + 3 ) / 4;
+//		p_double = new /*(Mem::Manager::sHandle().TopDownHeap())*/ uint32[dbytes + sbytes];
+//		memcpy( p_double, p_temp, dbytes * 4 );
+//		MemoryRead( &p_double[dbytes], bytes, 1, p_file_data );
+//
+//		delete p_temp;
+//	}
+//
+////	// Grab a buffer for the raw vertex data weights stream (if present), and read it.
+////	float* p_vertex_weights = ( m_flags & 0x10 ) ? new float[num_vertices * 3] : NULL;
+////	if( p_vertex_weights )
+////	{
+////		MemoryRead( p_vertex_weights, sizeof( float ) * 3, num_vertices, p_file_data );
+////	}
+////	
+////	// Grab a buffer for the raw vertex data bone indices stream (if present), and read it.
+////	uint16* p_vertex_bone_indices = ( m_flags & 0x10 ) ? new uint16[num_vertices * 3] : NULL;
+////	if( p_vertex_bone_indices )
+////	{
+////		MemoryRead( p_vertex_bone_indices, sizeof( uint16 ) * 3, num_vertices, p_file_data );
+////	}
+//
+//	uint16 * p_temp_col_remap = NULL;
+//	uint16 * p_temp_uv_remap = NULL;
+//
+//	// Grab a buffer for the raw vertex texture coordinate stream (if present), and read it.
+//	int		num_tc_sets			= 0;
+//	float*	p_vertex_tex_coords	= NULL;
+//	if( m_flags & 0x01 )
+//	{
+//		MemoryRead( &num_tc_sets, sizeof( int ), 1, p_file_data );
+//		
+//		float * p_temp_tex_coords = new (Mem::Manager::sHandle().TopDownHeap()) float[num_vertices * 2 * num_tc_sets];
+//		p_vertex_tex_coords = new float[num_vertices * 2 * num_tc_sets];
+//		MemoryRead( p_temp_tex_coords, sizeof( float ) * 2 * num_tc_sets, num_vertices, p_file_data );
+//
+//		// Convert to friendlier non-interleaved format.
+//		for ( int lp = 0; lp < num_vertices; lp++ )
+//		{
+//			for ( int lp2 = 0; lp2 < num_tc_sets; lp2++ )
+//			{
+//				p_vertex_tex_coords[(lp2*2*num_vertices)+(lp*2)+0] = p_temp_tex_coords[(lp2*2)+(lp*2*num_tc_sets)+0];
+//				p_vertex_tex_coords[(lp2*2*num_vertices)+(lp*2)+1] = p_temp_tex_coords[(lp2*2)+(lp*2*num_tc_sets)+1];
+//			}
+//		}
+//
+//		// Create temp uv mapping buffer.
+//		p_temp_uv_remap = num_tc_sets ? new (Mem::Manager::sHandle().TopDownHeap()) uint16[num_vertices*num_tc_sets] : NULL;
+//
+//		for ( int c = 0; c < num_vertices; c++ )
+//		{
+//			for ( int u = 0; u < num_tc_sets; u++ )
+//			{
+//				p_temp_uv_remap[(u*num_vertices)+c] = (u*num_vertices)+c;
+//			}
+//		}
+//
+//		bool reduce = true;
+//		if ( m_flags & 0x04 ) reduce = false;	// No uv reduction if normals are present.
+//
+//		if ( reduce )
+//		{
+//			// Copy back to temp coords.
+//			memcpy( p_temp_tex_coords, p_vertex_tex_coords, num_vertices * num_tc_sets * 2 * sizeof( float ) );
+//			delete [] p_vertex_tex_coords;
+//			float * p_unique_tex_coords = new (Mem::Manager::sHandle().TopDownHeap()) float[num_vertices * 2 * num_tc_sets];
+//
+//			// Now, squeeze down to only unique uvs.
+//			int numUnique = 0;
+//			for ( int lp = 0; lp < num_vertices; lp++ )
+//			{
+//				for ( int lp2 = 0; lp2 < num_tc_sets; lp2++ )
+//				{
+//					bool found = false;
+//					for ( int lp3 = 0; lp3 < numUnique; lp3++ )
+//					{
+//						if ( ( p_unique_tex_coords[(lp3*2)+0] == p_temp_tex_coords[(lp2*2*num_vertices)+(lp*2)+0] ) &&
+//							 ( p_unique_tex_coords[(lp3*2)+1] == p_temp_tex_coords[(lp2*2*num_vertices)+(lp*2)+1] ) )
+//						{
+//							// Found a match.
+//							p_temp_uv_remap[(lp2*num_vertices)+lp] = lp3;
+//							found = true;
+//							break;
+//						}
+//					}
+//					if ( !found )
+//					{
+//						p_unique_tex_coords[(numUnique*2)+0] = p_temp_tex_coords[(lp2*2*num_vertices)+(lp*2)+0];
+//						p_unique_tex_coords[(numUnique*2)+1] = p_temp_tex_coords[(lp2*2*num_vertices)+(lp*2)+1];
+//						p_temp_uv_remap[(lp2*num_vertices)+lp] = numUnique;
+//						numUnique++;
+//					}
+//				}
+//			}
+//			// Allocate only the space we need.
+//			p_vertex_tex_coords = new float[numUnique * 2];
+//			memcpy( p_vertex_tex_coords, p_unique_tex_coords, numUnique * 2 * sizeof( float ) );
+//			delete [] p_unique_tex_coords;
+//		}
+//
+//		delete p_temp_tex_coords;
+//	}
+//	
+//	// Grab a buffer for the raw vertex colors stream (if present), and read it.
+//	uint32* p_vertex_colors = ( m_flags & 0x02 ) ? new uint32[num_vertices] : NULL;
+//	if( p_vertex_colors )
+//	{
+//		MemoryRead( p_vertex_colors, sizeof( uint32 ), num_vertices, p_file_data );
+//	
+//		// Create temp col mapping buffer.
+//		p_temp_col_remap = p_vertex_colors ? new (Mem::Manager::sHandle().TopDownHeap()) uint16[num_vertices] : NULL;
+//
+//		for ( int c = 0; c < num_vertices; c++ )
+//		{
+//			p_temp_col_remap[c] = c;
+//		}
+//
+//		bool reduce = true;
+//		if ( m_flags & 0x04 ) reduce = false;	// No color reduction if normals are present.
+//		if ( m_flags & 0x800 ) reduce = false;	// No color reduction if vc wibble data is present.
+//		if ( !NxNgc::EngineGlobals.reduceColors ) reduce = false;	// No color reduction if vc wibble data is present.
+//
+//		if ( reduce )
+//		{
+//			uint32 * p_temp_colors = new (Mem::Manager::sHandle().TopDownHeap()) uint32[num_vertices];
+//			memcpy( p_temp_colors, p_vertex_colors, num_vertices * 4 );
+//			delete [] p_vertex_colors;
+//			uint32* p_unique_colors = new (Mem::Manager::sHandle().TopDownHeap()) uint32[num_vertices];
+//
+//			// Now, squeeze down to only unique uvs.
+//			int numUnique = 0;
+//			for ( int lp = 0; lp < num_vertices; lp++ )
+//			{
+//				bool found = false;
+//				for ( int lp2 = 0; lp2 < numUnique; lp2++ )
+//				{
+//					if ( p_unique_colors[lp2] == p_temp_colors[lp] )
+//					{
+//						// Found a match.
+//						p_temp_col_remap[lp] = lp2;
+//						found = true;
+//						break;
+//					}
+//				}
+//				if ( !found )
+//				{
+//					p_unique_colors[numUnique] = p_temp_colors[lp];
+//					p_temp_col_remap[lp] = numUnique;
+//					numUnique++;
+//				}
+//			}
+//			// Allocate only the space we need.
+//			p_vertex_colors = new uint32[numUnique];
+//			memcpy( p_vertex_colors, p_unique_colors, numUnique * 4 );
+//			delete [] p_unique_colors;
+//			delete p_temp_colors;
+//		}
+//	}
+//
+//	// Grab a buffer for the raw vertex colors stream (if present), and read it.
+//	char* p_vc_wibble_indices = ( m_flags & 0x800 ) ? new char[num_vertices] : NULL;
+//	if( p_vc_wibble_indices )
+//	{
+//		MemoryRead( p_vc_wibble_indices, sizeof( char ), num_vertices, p_file_data );
+//	}
+//	
+//	NxNgc::sMesh*	p_mesh0 = NULL;
+//
+//	for( uint m = 0; m < p_geom->m_num_mesh; ++m )
+//	{
+//		unsigned long	material_checksum;
+//		uint32			flags;
+//		int				num_indices;
+//		float			sphere[4];
+//
+//		NxNgc::sMesh*	p_mesh = new NxNgc::sMesh;
+//		if ( m == 0 )
+//		{
+//			p_mesh0 = p_mesh;
+//			p_mesh0->mp_envBuffer = NULL;
+//		}
+//		
+//		MemoryRead( &material_checksum,	sizeof( unsigned long ), 1, p_file_data );
+//		MemoryRead( &flags,	sizeof( uint32 ), 1, p_file_data );
+//		MemoryRead( sphere, sizeof( float ), 4, p_file_data );
+//		MemoryRead( &num_indices, sizeof( int ), 1, p_file_data );
+//
+//		uint16* p_indices = new uint16[num_indices];
+//		MemoryRead( p_indices, sizeof( uint16 ), num_indices, p_file_data );
+//
+//		// Deal with skater shadow flag.
+//		if( flags & (0x4000|0x400) )
+//		{
+//			p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+//		}
+//		// Deal with vertex color wibble flag.
+//		if( flags & (0x800) )
+//		{
+//			p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_VERTEX_COLOR_WIBBLE;
+//		}
+//		
+//		if( m_flags & (0x80000) )
+//		{
+//			p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_GRASS;
+//		}
+//		
+//		// Create the mesh from the raw data.
+//		p_mesh->Initialize( num_vertices,
+//							p_vertex_positions,
+//#ifdef SHORT_VERT
+//							p_vertex_positions16,
+//							shift,
+//							off_x,
+//							off_y,
+//							off_z,
+//#endif		// SHORT_VERT
+//							p_vertex_normals,
+//							p_vertex_tex_coords,
+//							p_mesh0->mp_envBuffer,
+//							num_tc_sets,
+//							p_vertex_colors,
+//							num_indices,
+//							p_indices,		// pos
+//							p_temp_col_remap,		// color
+//							p_temp_uv_remap,		// uv
+//							material_checksum,
+//							p_geom->mp_scene->GetEngineScene(),
+//							num_double,
+//							p_double,
+//							num_single,
+//							m,
+//							GX_TRIANGLESTRIP,
+//							bone_idx,
+//							p_vc_wibble_indices );
+//
+//		// Assign env buffer to mesh 0 if assigned.
+//		if ( ( m > 0 ) && p_mesh->mp_envBuffer ) p_mesh0->mp_envBuffer = p_mesh->mp_envBuffer;
+//		
+//		// Add the mesh to the sector.
+//		p_geom->AddMesh( p_mesh );
+//	
+//		// Done with the raw index data.
+////		delete[] p_indices;
+
+//		if ( p_data->m_size )
+//		{
+//			// Setup the DL.
+//			p_mesh->mp_dl = p_data;
+//			p_data = (NxNgc::sDLHeader*)(&((char*)&p_data[1])[p_data->m_size]);
+//
+//			p_mesh->mp_dl->mp_material_dl = NULL;
+//			p_mesh->mp_dl->m_material_dl_size = 0;
+//
+//			// Find the material.
+//			NxNgc::sMaterialHeader * p_mat_list = p_geom->mp_scene->GetEngineScene()->mp_material_list;
+//			bool found = false;
+//			for ( unsigned int lp = 0; lp < p_geom->mp_scene->GetEngineScene()->mp_scene_data->m_num_materials; lp++ )
+//			{
+//				if ( p_mat_list->m_checksum == p_mesh->mp_dl->m_material.checksum )
+//				{
+//					// Found it!
+//					p_mesh->mp_dl->m_material.p_header = p_mat_list;
+//
+//					// Construct the DL.
+//					Mem::Manager::sHandle().TopDownHeap()->PushAlign( 32 );
+//#define DL_BUILD_SIZE (4*1024)
+//					uint8 * p_build_dl = new (Mem::Manager::sHandle().TopDownHeap()) uint8[DL_BUILD_SIZE];
+//					DCFlushRange ( p_build_dl, DL_BUILD_SIZE );
+//
+//					GXBeginDisplayList ( p_build_dl, DL_BUILD_SIZE );
+//					GXResetWriteGatherPipe();
+//
+////					// ---------------- Begin Generate display list.
+//////					GXSetNumTevStages( 1 );
+////					GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+////					GXSetTevSwapMode( GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0 );
+//////					GXSetNumTexGens( 0 );
+////					GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+////					GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+////////					GXSetAlphaCompare(GX_GREATER, 0, GX_AOP_AND, GX_GREATER, 0 );
+////					GXSetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR );
+////					GXSetTevAlphaIn ( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA );
+////					GXSetTevColorIn ( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC );
+////					GXSetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+////					GXSetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//
+//					NxNgc::sMaterialHeader * p_mat = p_mesh->mp_dl->m_material.p_header;
+//					NxNgc::sMaterialPassHeader * p_pass = (NxNgc::sMaterialPassHeader *)&p_mat[1];
+//
+//					int lp;
+//
+//					NxNgc::MaterialBegin(); for ( lp = 0; lp < p_mat->m_passes; lp++ ) NxNgc::MaterialLayer( p_pass, lp, MATERIAL_GROUP_CP );	NxNgc::MaterialEnd( &p_mesh->mp_dl->m_tev_stages, &p_mesh->mp_dl->m_tex_gens );
+//					NxNgc::MaterialBegin(); for ( lp = 0; lp < p_mat->m_passes; lp++ ) NxNgc::MaterialLayer( p_pass, lp, MATERIAL_GROUP_SU );   NxNgc::MaterialEnd( &p_mesh->mp_dl->m_tev_stages, &p_mesh->mp_dl->m_tex_gens );
+//					NxNgc::MaterialBegin(); for ( lp = 0; lp < p_mat->m_passes; lp++ ) NxNgc::MaterialLayer( p_pass, lp, MATERIAL_GROUP_B );    NxNgc::MaterialEnd( &p_mesh->mp_dl->m_tev_stages, &p_mesh->mp_dl->m_tex_gens );
+//					NxNgc::MaterialBegin(); for ( lp = 0; lp < p_mat->m_passes; lp++ ) NxNgc::MaterialLayer( p_pass, lp, MATERIAL_GROUP_C );    NxNgc::MaterialEnd( &p_mesh->mp_dl->m_tev_stages, &p_mesh->mp_dl->m_tex_gens );
+//					NxNgc::MaterialBegin(); for ( lp = 0; lp < p_mat->m_passes; lp++ ) NxNgc::MaterialLayer( p_pass, lp, MATERIAL_GROUP_A );    NxNgc::MaterialEnd( &p_mesh->mp_dl->m_tev_stages, &p_mesh->mp_dl->m_tex_gens );
+//
+//					GXSetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+//					GXSetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+//					GXSetAlphaCompare(GX_GREATER, p_mat->m_alpha_cutoff, GX_AOP_AND, GX_GREATER, p_mat->m_alpha_cutoff );
+//					// ---------------- End Generate display list.
+//
+//					uint32 size = GXEndDisplayList();
+//
+////					// If we already have a display list & this one will fit, just use the existing piece of memory.
+////					uint8 * p_dl;
+////					if ( mp_display_list && ( size <= m_display_list_size ) )
+////					{
+////						p_dl = mp_display_list;
+////					}
+////					else
+////					{
+////						if ( mp_display_list )
+////						{
+////							//gMatBytes -= m_display_list_size;
+////							delete mp_display_list;
+////						}
+//						p_mesh->mp_dl->mp_material_dl = new uint8[size];
+//						//gMatBytes += size;
+////					}
+//
+//					memcpy ( p_mesh->mp_dl->mp_material_dl, p_build_dl, size );
+//					DCFlushRange ( p_mesh->mp_dl->mp_material_dl, size );
+//
+//					delete p_build_dl;
+//
+//					p_mesh->mp_dl->m_material_dl_size = size;
+//					Mem::Manager::sHandle().TopDownHeap()->PopAlign();
+//
+//
+//
+//
+//
+//					found = true;
+//					break;
+//				}
+//				p_mat_list = (NxNgc::sMaterialHeader *)&(((char*)(&p_mat_list[1]))[p_mat_list->m_skip_bytes]);
+//			}
+//			if ( !found )
+//			{
+//				Dbg_MsgAssert( 0, ( "Unable to find material checksum 0x%08x\n", p_mesh->mp_dl->m_material.checksum ));
+//				p_mesh->mp_dl->m_material.p_header = NULL;
+//			}
+//		}
+//	}
+
+//	if ( p_temp_col_remap ) delete [] p_temp_col_remap;
+//	if ( p_temp_uv_remap ) delete [] p_temp_uv_remap;
+////	if ( p_double ) delete p_double;
+//
+//	// Done with the raw vertex data.
+////	delete[] p_vertex_colors;
+////	delete[] p_vertex_tex_coords;
+////	delete[] p_vertex_normals;
+//#ifdef SHORT_VERT
+//	delete[] p_vertex_positions;
+//#endif		// SHORT_VERT
+//
+//	// Set the data pointer to the new position on return.
+//	*pp_mem = p_file_data;
+//
+//	return p_data;
+
+	Dbg_MsgAssert( false, ( "Not yet implented!!!!!!!" ) );
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+NxNgc::sObjectHeader* CNgcSector::LoadFromFile( NxNgc::sObjectHeader* p_data )
+{
+	Dbg_Assert( mp_geom );
+
+	CNgcGeom *p_geom = static_cast( mp_geom );
+	
+	// Hook up vars.
+	p_geom->m_num_mesh = p_data->m_num_meshes;
+//  	p_geom->mp_scene->GetEngineScene()->mp_dl->mp_object_header = p_data;
+
+	char * p_skin = (char *)&p_data[1];
+	int nbytes = p_data->m_skin.num_bytes;
+	p_data->m_skin.p_data = p_skin;
+	NxNgc::sDLHeader* p_dl = (NxNgc::sDLHeader*)&p_skin[nbytes];
+
+	m_flags = p_dl->m_flags;
+	SetChecksum( p_dl->m_checksum );
+
+	for( uint m = 0; m < p_geom->m_num_mesh; ++m )
+	{
+		NxNgc::sMesh*	p_mesh = new NxNgc::sMesh;
+
+		p_geom->AddMesh( p_mesh );
+
+		// Setup mesh flags.
+		if ( *((uint32*)(&p_dl->mp_col_pool)) & ( 0x00000100 | 0x00000400 ) )
+		{
+			p_mesh->m_flags |= NxNgc::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		}
+		p_dl->mp_col_pool = NULL;
+
+		// Setup bottom y.
+		p_mesh->m_bottom_y = *((float*)(&p_dl->mp_pos_pool));
+		p_dl->mp_pos_pool = NULL;
+
+		if ( p_dl->m_size )
+		{
+			// Setup the DL.
+			p_mesh->mp_dl = p_dl;
+			p_mesh->mp_dl->mp_object_header = p_data;
+			p_mesh->m_bone_idx = p_mesh->mp_dl->mp_object_header->m_bone_index;
+
+			// Fix up the bounding sphere for billboards.
+			switch ( p_mesh->mp_dl->mp_object_header->m_billboard_type )
+			{
+				case 1:
+				case 2:
+					p_mesh->mp_dl->m_sphere[X] += p_mesh->mp_dl->mp_object_header->m_origin[X];
+					p_mesh->mp_dl->m_sphere[Y] += p_mesh->mp_dl->mp_object_header->m_origin[Y];
+					p_mesh->mp_dl->m_sphere[Z] += p_mesh->mp_dl->mp_object_header->m_origin[Z];
+					break;
+				default:
+					break;
+			}
+
+			p_dl = (NxNgc::sDLHeader*)&(((char *)&p_dl[1])[p_dl->m_size]);
+
+			// Find the material.
+			NxNgc::sMaterialHeader * p_mat_list = p_geom->mp_scene->GetEngineScene()->mp_material_header;
+			bool found = false;
+			for ( unsigned int lp = 0; lp < p_geom->mp_scene->GetEngineScene()->mp_scene_data->m_num_materials; lp++ )
+			{
+				if ( p_mat_list->m_checksum == p_mesh->mp_dl->m_material.checksum )
+				{
+					// Found it!
+					p_mesh->mp_dl->m_material.p_header = p_mat_list;
+
+//					NxNgc::MaterialBuild( p_mesh, p_geom->mp_scene->GetEngineScene(), false, true ); 
+
+					found = true;
+					break;
+				}
+				++p_mat_list;
+			}
+			if ( !found )
+			{
+				Dbg_MsgAssert( 0, ( "Unable to find material checksum 0x%08x\n", p_mesh->mp_dl->m_material.checksum ));
+				p_mesh->mp_dl->m_material.p_header = NULL;
+			}
+		}
+	}
+
+	return (NxNgc::sObjectHeader*)p_dl;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CSector
+// and we will also have a CNgcSector, CNgcSector, even a CPcSector
+// maybe in the future we will have a CPS3Sector?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcSector::plat_set_color(Image::RGBA rgba)
+{
+	// Set values
+	m_rgba = rgba;
+
+	// Engine call here
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcSector::plat_clear_color()
+{
+	// Set to white
+	plat_set_color(Image::RGBA(128, 128, 128, 128));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcSector::plat_set_visibility(uint32 mask)
+{
+//	// Set values
+//	m_visible = mask;
+//
+//	// Engine call here
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcSector::plat_set_active( bool on )
+{
+//	// Set values
+//	m_active = on;
+//
+//	if( m_mesh_array )
+//	{
+//		for( uint m = 0; m < m_num_mesh; ++m )
+//		{
+//			NxNgc::sMesh *p_mesh = m_mesh_array[m];
+//			p_mesh->SetActive( on );
+//		}
+//	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//bool CNgcSector::plat_is_active() const
+//{
+//	return m_active;
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ void CNgcSector::plat_set_world_position(const Mth::Vector& pos)
+ {
+/*
+	Mth::Vector new_offset = pos - m_pos_offset;
+
+	// Go through and adjust the individual meshes.
+	for( uint32 i = 0; i < m_num_mesh; ++i )
+	{
+		NxNgc::sMesh *p_mesh = m_mesh_array[i];
+		p_mesh->Move( new_offset );
+	}
+*/
+ }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::CBBox &CNgcSector::plat_get_bounding_box( void ) const
+{
+	static Mth::CBBox dummy;
+	
+	//	return m_bbox;
+	return dummy;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CNgcSector::plat_get_world_position() const
+{
+	return m_pos_offset;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//void CNgcSector::plat_set_y_rotation(Mth::ERot90 rot)
+//{
+//	// Engine call here
+//	// Garrett: TEMP just set the world matrix
+//	Mth::Matrix rot_mat;
+//	CreateRotateYMatrix(rot_mat, (float) rot * (Mth::PI * 0.5f));
+//
+//	m_world_matrix[X] = rot_mat[X];
+//	m_world_matrix[Y] = rot_mat[Y];
+//	m_world_matrix[Z] = rot_mat[Z];
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CNgcSector::plat_set_shatter(bool on)	
+{
+	if( on && mp_geom )
+	{
+		Shatter( mp_geom );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector * CNgcSector::plat_clone(bool instance, CScene *p_dest_scene)
+{
+	CNgcSector *p_Ngc_sector = new CNgcSector();
+
+	/*
+
+
+	// Copies over much of the standard stuff, individual stuff will be overwritten later.
+	CopyMemory( p_Ngc_sector, this, sizeof( CNgcSector ));
+
+	if( instance )
+	{
+		Dbg_Assert( 0 );
+	}
+	else
+	{
+		// Need to create a new set of meshes. First create the mesh pointer array...
+		p_Ngc_sector->m_mesh_array = new NxNgc::sMesh*[m_num_mesh];
+
+		// ...then clone the meshes themselves.
+		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+			p_Ngc_sector->m_mesh_array[i] = m_mesh_array[i]->Clone();
+		}
+
+		// Grab a temporary workspace buffer.
+		NxNgc::sScene *p_scene								= ( static_cast( p_dest_scene ))->GetEngineScene();
+		NxNgc::sMesh **p_temp_opaque_mesh_buffer			= new NxNgc::sMesh*[p_scene->m_num_opaque_entries];
+		NxNgc::sMesh **p_temp_semitransparent_mesh_buffer	= new NxNgc::sMesh*[p_scene->m_num_semitransparent_entries];
+
+		// Copy meshes over into the temporary workspace buffer.
+		for( int i = 0; i < p_scene->m_num_opaque_entries; ++i )
+		{
+			p_temp_opaque_mesh_buffer[i] = p_scene->m_opaque_meshes[i];
+		}
+		for( int i = 0; i < p_scene->m_num_semitransparent_entries; ++i )
+		{
+			p_temp_semitransparent_mesh_buffer[i] = p_scene->m_semitransparent_meshes[i];
+		}
+
+		// Delete current mesh arrays.
+		delete [] p_scene->m_opaque_meshes;
+		delete [] p_scene->m_semitransparent_meshes;
+
+		// Include new meshes in count.
+		p_scene->CountMeshes( p_Ngc_sector->m_num_mesh, p_Ngc_sector->m_mesh_array );
+
+		// Allocate new mesh arrays.
+		p_scene->CreateMeshArrays();
+
+		// Copy old mesh data back in.
+		for( int i = 0; i < p_scene->m_num_opaque_entries; ++i )
+		{
+			p_scene->m_opaque_meshes[i] = p_temp_opaque_mesh_buffer[i];
+		}
+		for( int i = 0; i < p_scene->m_num_semitransparent_entries; ++i )
+		{
+			p_scene->m_semitransparent_meshes[i] = p_temp_semitransparent_mesh_buffer[i];
+		}
+
+		// Add new meshes.
+		p_scene->AddMeshes( p_Ngc_sector->m_num_mesh, p_Ngc_sector->m_mesh_array );
+
+		// Sort the meshes.
+		p_scene->SortMeshes();
+	}
+*/
+	return p_Ngc_sector;
+}
+
+
+
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGC/p_nxsector.h b/Code/Gfx/NGC/p_nxsector.h
new file mode 100644
index 0000000..6c44251
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxsector.h
@@ -0,0 +1,55 @@
+#ifndef	__GFX_P_NX_SECTOR_H__
+#define	__GFX_P_NX_SECTOR_H__
+
+#include 	
+#include 	
+#include 	"gfx\NxSector.h"
+#include 	"gfx\Image\ImageBasic.h"
+
+#include 	"gfx\ngc\p_nxscene.h"
+#include 	"gfx\ngc\nx\mesh.h"
+#include 	"gfx\ngc\nx\geomnode.h"
+
+#include 	
+
+namespace NxNgc
+{
+	class CGeomNode;
+}
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CSector
+class CNgcSector : public CSector
+{
+	public:
+								CNgcSector();
+
+	NxNgc::sObjectHeader*		LoadFromFile( NxNgc::sObjectHeader* p_data );
+	NxNgc::sObjectHeader*		LoadFromMemory( NxNgc::sObjectHeader* p_data );
+	
+private:		// It's all private, as it is machine specific
+	virtual void				plat_set_color(Image::RGBA rgba);
+	virtual void				plat_clear_color();
+	virtual void				plat_set_visibility(uint32 mask);
+	virtual void				plat_set_active(bool on);	
+	virtual void				plat_set_world_position(const Mth::Vector& pos);
+	virtual const Mth::CBBox	&plat_get_bounding_box( void ) const;
+	virtual const Mth::Vector &	plat_get_world_position() const;
+	virtual void				plat_set_shatter( bool on );
+	virtual CSector *			plat_clone(bool instance, CScene *p_dest_scene);
+
+	int								m_flags;
+
+	Mth::Vector						m_pos_offset;
+
+	Image::RGBA						m_rgba;
+};
+
+} // Namespace Nx  			
+
+#endif
\ No newline at end of file
diff --git a/Code/Gfx/NGC/p_nxtexman.cpp b/Code/Gfx/NGC/p_nxtexman.cpp
new file mode 100644
index 0000000..ea74e98
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxtexman.cpp
@@ -0,0 +1,69 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxTexMan.cpp - Ngc platform specific interface to CTexMan
+//
+
+#include 
+
+#include "gfx/NxTexMan.h"
+#include "gfx/Ngc/p_NxTexture.h"
+#include "gfx/Ngc/nx/import.h"
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexDict* CTexDictManager::s_plat_load_texture_dictionary( const char *p_tex_dict_name, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup )
+{
+//	NxNgc::LoadTextureFile( p_tex_dict_name );
+	return new CNgcTexDict( p_tex_dict_name, forceTexDictLookup );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexDict* CTexDictManager::s_plat_load_texture_dictionary( uint32 checksum, uint32 *p_data, int data_size, bool is_level_data, uint32 texDictOffset, bool is_skin, bool forceTexDictLookup )
+{
+	CNgcTexDict *p_dict = new CNgcTexDict( checksum );
+	p_dict->LoadTextureDictionaryFromMemory( p_data );
+	return p_dict;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexDict* CTexDictManager::s_plat_create_texture_dictionary( uint32 checksum )
+{
+	return new CNgcTexDict( checksum );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CTexDictManager::s_plat_unload_texture_dictionary( CTexDict* p_tex_dict )
+{
+	delete p_tex_dict;
+	return true;
+}
+
+
+} 
+ 
+
diff --git a/Code/Gfx/NGC/p_nxtexture.cpp b/Code/Gfx/NGC/p_nxtexture.cpp
new file mode 100644
index 0000000..40673b6
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxtexture.cpp
@@ -0,0 +1,773 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxTexture.cpp
+
+#include 	"Gfx/Nx.h"
+#include 	"Gfx/Ngc/p_NxTexture.h"
+#include 	"Gfx/Ngc/NX/import.h"
+
+extern bool g_in_cutscene;
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTexture
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcTexture::CNgcTexture() :  mp_texture( NULL )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcTexture::~CNgcTexture()
+{
+	if( mp_texture )
+	{
+		delete mp_texture;
+	}
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexture::plat_load_texture( const char *p_texture_name, bool sprite, bool alloc_vram )
+{
+	char filename[256];
+
+	strcpy( filename, p_texture_name );
+	
+	// append '.img.ngc' to the end.
+	strcat( filename, ".img.ngc" );
+	
+	mp_texture = NxNgc::LoadTexture( filename );
+	
+	return mp_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexture::plat_replace_texture( CTexture *p_texture )
+{						  
+	Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+
+	Dbg_Assert(p_texture);
+	
+	CNgcTexture *p_Ngc_texture = static_cast( p_texture );
+
+	// Go through and copy the texture.
+	NxNgc::sTexture *p_src	= p_Ngc_texture->GetEngineTexture();
+	NxNgc::sTexture *p_dst	= GetEngineTexture();
+
+	// Couple of problem cases.
+//	Dbg_MsgAssert( p_src->format == p_dst->format, ( "Cannot replace textures of different formats.\n" ));
+//	if ( p_src->pAlphaData && !p_dst->pAlphaData )
+//	{
+//		Dbg_MsgAssert( false, ( "Cannot assign a texture with alpha to a texture without alpha.\n" ));
+//	}
+//	if ( !p_src->pAlphaData && p_dst->pAlphaData )
+//	{
+//		Dbg_MsgAssert( false, ( "Cannot assign a texture without alpha to a texture with alpha.\n" ));
+//	}
+
+	// Delete & re-allocate alpha space if new image is bigger, or not single owner alpha, or not channel 0.
+	if ( p_src->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA )
+	{
+		// See if this texture needs to be stored in pOldAlpha.
+		if ( !( p_dst->flags & NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER ) &&
+			 ( ( p_dst->flags & NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_MASK ) == NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN ) )
+		{
+			p_dst->pOldAlphaData = p_dst->pAlphaData;
+			p_dst->flags |= NxNgc::sTexture::TEXTURE_FLAG_OLD_DATA;
+		}
+
+		if ( ( p_dst->byte_size < p_src->byte_size ) ||
+			 !( p_dst->flags & NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER ) ||
+			 ( ( p_dst->flags & NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_MASK ) != NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN ) )
+		{
+			if( p_dst->pAlphaData &&
+				( p_dst->byte_size < p_src->byte_size ) &&
+				( p_dst->flags & NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER ) &&
+				( ( p_dst->flags & NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_MASK ) == NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN ) )
+			{
+				delete [] p_dst->pAlphaData;
+			}
+			p_dst->pAlphaData = new uint8[p_src->byte_size];
+		}
+	}
+
+	// Delete & re-allocate texture space if new image is bigger.
+	if ( p_dst->byte_size < p_src->byte_size )
+	{
+		if( p_dst->pTexelData )
+		{
+			delete [] p_dst->pTexelData;
+		}
+		p_dst->pTexelData = new uint8[p_src->byte_size];
+		p_dst->byte_size = p_src->byte_size;
+	}
+
+	p_dst->BaseWidth	= p_src->BaseWidth;
+	p_dst->BaseHeight	= p_src->BaseHeight;
+	p_dst->ActualWidth	= p_src->ActualWidth;
+	p_dst->ActualHeight	= p_src->ActualHeight;
+
+	p_dst->flags		= ( p_dst->flags & ~NxNgc::sTexture::TEXTURE_FLAG_HAS_HOLES ) | ( p_src->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_HOLES );
+
+	// Copy the pixel data.
+	memcpy( p_dst->pTexelData, p_src->pTexelData, p_src->byte_size );
+	DCFlushRange ( p_dst->pTexelData, p_src->byte_size );
+
+	// Copy the alpha data if necessary.
+	if ( p_src->pAlphaData && p_dst->pAlphaData )
+	{
+		memcpy( p_dst->pAlphaData, p_src->pAlphaData, p_src->byte_size );
+		DCFlushRange ( p_dst->pAlphaData, p_src->byte_size );
+		p_dst->flags		= ( p_dst->flags & ~NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA ) | ( p_src->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA );
+		p_dst->flags		= ( p_dst->flags & ~NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_MASK ) | NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN;
+		p_dst->flags		= p_dst->flags | NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER;
+	}
+
+	// If the replacement texture doesn't have alpha, but the original does, twiddle the flag.
+	if ( p_dst->pAlphaData && !p_src->pAlphaData )
+	{
+		p_dst->flags		= ( p_dst->flags & ~NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA );
+	}
+	
+	// Replacement texture does not mip-map.
+	p_dst->Levels = 0;
+
+	// So that if the regular texture is 32-bit, it can have a CMPR texture copied over it.
+	p_dst->format		= p_src->format;
+
+	// Flag this texture as having been replaced.
+	p_dst->flags |= NxNgc::sTexture::TEXTURE_FLAG_REPLACED;
+
+	Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexture::plat_add_to_vram( void )
+{								 	
+	// Meaningless on Ngc, added to remove annoying debug stub output.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexture::plat_remove_from_vram( void )
+{								 	
+	// Meaningless on Ngc, added to remove annoying debug stub output.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CNgcTexture::plat_get_width() const
+{
+	if( mp_texture )
+	{
+		return mp_texture->BaseWidth;
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CNgcTexture::plat_get_height() const
+{
+	if( mp_texture )
+	{
+		return mp_texture->BaseHeight;
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CNgcTexture::plat_get_bitdepth() const
+{
+	switch ( ((GXTexFmt)mp_texture->format) )
+	{
+		case GX_TF_I4:
+		case GX_TF_IA4:
+		case GX_TF_CMPR:
+			return 4;
+			break;
+
+		case GX_TF_I8:
+		case GX_TF_IA8:
+		case GX_TF_A8:
+		case GX_TF_Z8:
+			return 8;
+			break;
+		
+		case GX_TF_RGB565:
+		case GX_TF_RGB5A3:
+		case GX_TF_Z16:
+			return 16;
+			break;
+
+		default:
+		case GX_TF_RGBA8:
+		case GX_TF_Z24X8:
+			return 32;
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CNgcTexture::plat_get_num_mipmaps() const
+{
+	return mp_texture->Levels;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CNgcTexture::plat_is_transparent() const
+{
+	return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTexDict
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcTexDict::CNgcTexDict( uint32 checksum ) : CTexDict( checksum )
+{
+	// Load nothing
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNgcTexDict::CNgcTexDict( const char *p_tex_dict_name, bool forceTexDictLookup ) : CTexDict( p_tex_dict_name, true )
+{
+	 LoadTextureDictionary( p_tex_dict_name, forceTexDictLookup );	// the derived class will does this
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNgcTexDict::~CNgcTexDict()
+{
+	UnloadTextureDictionary();				// the derived class does this
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexDict::LoadTextureDictionary( const char *p_tex_dict_name, bool forceTexDictLookup )
+{
+	NxNgc::LoadTextureFile( p_tex_dict_name, mp_texture_lookup );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexDict::LoadTextureDictionaryFromMemory( void *p_mem )
+{
+	Nx::LoadTextureFileFromMemory( &p_mem, mp_texture_lookup );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexDict::UnloadTextureDictionary()
+{
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexture *CNgcTexDict::plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram )
+{
+	CNgcTexture *p_texture = new CNgcTexture;
+	if( !p_texture->LoadTexture( p_texture_name, sprite ))
+	{
+		Dbg_Error("Can't load texture %s", p_texture_name);
+	}
+	return p_texture;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexture *CNgcTexDict::plat_reload_texture( const char *p_texture_name )
+{
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexDict::plat_unload_texture( CTexture *p_texture )
+{
+	delete p_texture;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcTexDict::plat_add_texture( CTexture *p_texture )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CNgcTexDict::plat_remove_texture( CTexture *p_texture )
+{
+	return false;
+}
+
+
+
+#define MemoryRead( dst, size, num, src )	memcpy(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void LoadTextureFileFromMemory( void **pp_mem, Lst::HashTable *p_texture_table )
+{
+	uint8 *p_data = (uint8*)( *pp_mem );
+
+	uint32 temp;
+	
+	// Open the texture file.
+//	void *p_FH = File::Open( Filename, "rb" );
+//	if( !p_FH )
+//	{
+//		Dbg_Message( "Couldn't open texture file %s\n", Filename );
+////		return 0;
+//		return;
+//	}
+
+	// Read the texture file version.
+	int version;
+	MemoryRead( &version, sizeof( int ), 1, p_data );
+
+	// Read the number of textures.	
+	int num_textures;
+	MemoryRead( &num_textures, sizeof( int ), 1, p_data );
+	
+//	NumTextures				+= num_textures;
+
+	int bytes;
+	bool need_to_pop;
+
+	NxNgc::sTexture ** p_tex = new (Mem::Manager::sHandle().TopDownHeap()) NxNgc::sTexture *[num_textures];
+	unsigned short * p_resolvem = new (Mem::Manager::sHandle().TopDownHeap()) unsigned short[num_textures];
+	unsigned short * p_resolvec = new (Mem::Manager::sHandle().TopDownHeap()) unsigned short[num_textures];
+	int num_resolve = 0;
+
+	int mem_available;
+	for( int t = 0; t < num_textures; ++t )
+	{
+		need_to_pop = false;
+		bytes = sizeof( NxNgc::sTexture );
+		if ( g_in_cutscene )
+		{
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( bytes < ( mem_available - ( 5 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+					Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+					mem_available = Mem::Manager::sHandle().Available();
+					if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+					{
+						need_to_pop = true;
+					}
+					else
+					{
+						Mem::Manager::sHandle().PopContext();
+					}
+				}
+			}
+		}
+
+		NxNgc::sTexture *p_texture = new NxNgc::sTexture;
+		
+		if ( need_to_pop )
+		{
+			Mem::Manager::sHandle().PopContext();
+		}
+
+		p_tex[t] = p_texture;
+
+		MemoryRead( &p_texture->Checksum,		sizeof( uint32 ), 1, p_data );
+		MemoryRead( &temp,		sizeof( uint32 ), 1, p_data );
+		p_texture->BaseWidth = (uint16)temp;
+		MemoryRead( &temp,		sizeof( uint32 ), 1, p_data );
+		p_texture->BaseHeight = (uint16)temp;
+		MemoryRead( &temp,		sizeof( uint32 ), 1, p_data );
+		p_texture->Levels = (uint16)temp;
+
+		p_texture->ActualWidth = ( p_texture->BaseWidth + 3 ) & ~3;
+		p_texture->ActualHeight = ( p_texture->BaseHeight + 3 ) & ~3;
+
+		int tex_format;
+		int channel;
+		int index;
+		MemoryRead( &tex_format, sizeof( uint32 ), 1, p_data );
+		channel = ( tex_format >> 8 ) & 0xff;
+		index = ( tex_format >> 16 ) & 0xffff;
+		tex_format = tex_format & 0xff;
+
+		switch ( tex_format ) {
+			case 0:
+				p_texture->format = GX_TF_CMPR;
+				break;
+			case 1:
+				p_texture->format = GX_TF_CMPR;
+				break;
+			case 2:
+				p_texture->format = GX_TF_RGBA8;
+				break;
+			default:
+				Dbg_MsgAssert( false, ("Illegal texture format: %d\n", tex_format ));
+				break;
+		}
+
+		p_texture->flags = 0;
+
+		int has_holes;
+		MemoryRead( &has_holes, sizeof( uint32 ), 1, p_data );
+		p_texture->flags |= has_holes ? NxNgc::sTexture::TEXTURE_FLAG_HAS_HOLES : 0;
+
+		uint8 * p8[8];
+		int mipbytes[8];
+
+		// Read color maps.
+		bytes = 0;
+		for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+		{
+			uint32 texture_level_data_size;
+			MemoryRead( &texture_level_data_size,			sizeof( uint32 ), 1, p_data );
+
+			p8[mip_level] = new (Mem::Manager::sHandle().TopDownHeap()) uint8[texture_level_data_size];
+			mipbytes[mip_level] = texture_level_data_size;
+			bytes += texture_level_data_size;
+
+			MemoryRead( p8[mip_level], texture_level_data_size, 1, p_data );
+		}
+		// Copy all textures & delete the originals.
+
+		need_to_pop = false;
+		if ( g_in_cutscene )
+		{
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( bytes < ( mem_available - ( 5 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+					Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+					mem_available = Mem::Manager::sHandle().Available();
+					if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+					{
+						need_to_pop = true;
+					}
+					else
+					{
+						Mem::Manager::sHandle().PopContext();
+					}
+				}
+			}
+		}
+
+		Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+		p_texture->pTexelData = new uint8[bytes];
+		p_texture->byte_size = bytes;
+		Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+
+		if ( need_to_pop )
+		{
+			Mem::Manager::sHandle().PopContext();
+		}
+
+		uint8 * pTex = p_texture->pTexelData;
+		for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+		{
+			memcpy( pTex, p8[mip_level], mipbytes[mip_level] );
+			delete p8[mip_level];
+			pTex += mipbytes[mip_level];
+		}
+
+		// Read alpha maps.
+		if ( tex_format == 1 )
+		{
+			if ( channel == 0 )
+			{
+				bytes = 0;
+				for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+				{
+					uint32 texture_level_data_size;
+					MemoryRead( &texture_level_data_size,			sizeof( uint32 ), 1, p_data );
+
+					p8[mip_level] = new (Mem::Manager::sHandle().TopDownHeap()) uint8[texture_level_data_size];
+					mipbytes[mip_level] = texture_level_data_size;
+					bytes += texture_level_data_size;
+
+					MemoryRead( p8[mip_level], texture_level_data_size, 1, p_data );
+				}
+				// Copy all textures & delete the originals.
+
+				need_to_pop = false;
+				if ( g_in_cutscene )
+				{
+					Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+					mem_available = Mem::Manager::sHandle().Available();
+					if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+					{
+						need_to_pop = true;
+					}
+					else
+					{
+						Mem::Manager::sHandle().PopContext();
+						Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+						mem_available = Mem::Manager::sHandle().Available();
+						if ( bytes < ( mem_available - ( 5 * 1024 ) ) )
+						{
+							need_to_pop = true;
+						}
+						else
+						{
+							Mem::Manager::sHandle().PopContext();
+							Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+							mem_available = Mem::Manager::sHandle().Available();
+							if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+							{
+								need_to_pop = true;
+							}
+							else
+							{
+								Mem::Manager::sHandle().PopContext();
+							}
+						}
+					}
+				}
+
+				Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+				p_texture->pAlphaData = new uint8[bytes];
+				Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+
+				if ( need_to_pop )
+				{
+					Mem::Manager::sHandle().PopContext();
+				}
+
+				uint8 * pTex = p_texture->pAlphaData;
+				for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+				{
+					memcpy( pTex, p8[mip_level], mipbytes[mip_level] );
+					delete p8[mip_level];
+					pTex += mipbytes[mip_level];
+				}
+				p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA;
+			}
+			else
+			{
+				p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA;
+
+				p_resolvem[num_resolve] = t;
+				p_resolvec[num_resolve] = index;
+				num_resolve++;
+			}
+			switch ( channel )
+			{
+				case 0:
+				default:
+					p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN;
+					break;
+				case 1:
+					p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_RED;
+					break;
+				case 2:
+					p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_BLUE;
+					break;
+			}
+			p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER;
+		}
+		else
+		{
+			// No unique alpha map.
+			p_texture->pAlphaData = NULL;
+			p_texture->flags &= ~NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA;
+			p_texture->flags |= NxNgc::sTexture::TEXTURE_FLAG_CHANNEL_GREEN;
+		}
+
+		// Add this texture to the table.
+//		pTextureTable->PutItem( p_texture->Checksum, p_texture );
+		need_to_pop = false;
+
+		bytes = sizeof( Nx::CNgcTexture );
+		if ( g_in_cutscene )
+		{
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( bytes < ( mem_available - ( 5 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+					Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+					mem_available = Mem::Manager::sHandle().Available();
+					if ( bytes < ( mem_available - ( 40 * 1024 ) ) )
+					{
+						need_to_pop = true;
+					}
+					else
+					{
+						Mem::Manager::sHandle().PopContext();
+					}
+				}
+			}
+		}
+
+		Nx::CNgcTexture *p_Ngc_texture = new Nx::CNgcTexture();
+
+		if ( need_to_pop )
+		{
+			Mem::Manager::sHandle().PopContext();
+		}
+
+		p_Ngc_texture->SetEngineTexture( p_texture );
+		if ( p_texture_table )
+		{
+			p_texture_table->PutItem( p_texture->Checksum, p_Ngc_texture );
+		}
+	}
+
+	// Resolve alpha maps.
+	for ( int r = 0; r < num_resolve; r++ )
+	{
+		p_tex[p_resolvem[r]]->pAlphaData = p_tex[p_resolvec[r]]->pAlphaData;
+		p_tex[p_resolvec[r]]->flags &= ~NxNgc::sTexture::TEXTURE_FLAG_SINGLE_OWNER;
+	}
+
+	delete p_tex;
+	delete p_resolvem;
+	delete p_resolvec;
+//	return pTextureTable;
+}
+
+} // Namespace Nx  			
+				
+				
+
diff --git a/Code/Gfx/NGC/p_nxtexture.h b/Code/Gfx/NGC/p_nxtexture.h
new file mode 100644
index 0000000..44f4b0b
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxtexture.h
@@ -0,0 +1,106 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_TEXTURE_H__
+#define	__GFX_P_NX_TEXTURE_H__
+
+#include 	"Gfx/NxTexture.h"
+#include 	"Gfx/Ngc/nx/texture.h"
+#include 	"Gfx/Ngc/nx/material.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CTexture
+class	CNgcTexture : public CTexture
+{
+public:
+								CNgcTexture();
+	virtual						~CNgcTexture();
+
+	NxNgc::sTexture			*GetEngineTexture() const;
+	void						SetEngineTexture( NxNgc::sTexture *p_texture )	{ mp_texture = p_texture; }
+
+
+private:		// It's all private, as it is machine specific
+	virtual bool				plat_load_texture( const char *p_texture_name, bool sprite, bool alloc_vram );
+	virtual bool				plat_replace_texture( CTexture *p_texture );
+	virtual bool				plat_add_to_vram( void );
+	virtual bool				plat_remove_from_vram( void );
+
+	virtual uint16				plat_get_width() const;
+	virtual uint16				plat_get_height() const;
+	virtual uint8				plat_get_bitdepth() const;
+	virtual uint8				plat_get_num_mipmaps() const;
+	virtual bool				plat_is_transparent() const;
+
+	// The actual data in the engine
+	NxNgc::sTexture *			mp_texture;
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline NxNgc::sTexture	*CNgcTexture::GetEngineTexture() const
+{
+	return mp_texture;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// Machine specific implementation of the CMaterial
+class	CNgcMaterial : public CMaterial
+{
+public:
+								CNgcMaterial();
+	virtual						~CNgcMaterial();
+
+private:
+	Image::RGBA					plat_get_rgba() const;
+	void						plat_set_rgba(Image::RGBA rgba);
+	void						plat_set_texture();
+
+	Image::RGBA					m_rgba;
+
+	// The actual data in the engine
+	NxNgc::sMaterial *			mp_material;
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// Machine specific implementation of the CTexDict
+class	CNgcTexDict : public CTexDict
+{
+public:
+								CNgcTexDict( uint32 checksum );		// loads nothing
+								CNgcTexDict(const char *p_tex_dict_name, bool forceTexDictLookup = false);
+	virtual						~CNgcTexDict();
+
+	bool						LoadTextureDictionary(const char *p_tex_dict_name, bool forceTexDictLookup = false);
+	bool						LoadTextureDictionaryFromMemory( void *p_mem );
+	bool						UnloadTextureDictionary();
+
+private:
+	// Platform-specific calls
+	virtual CTexture *			plat_load_texture( const char *p_texture_name, bool sprite, bool alloc_vram );
+	virtual CTexture *			plat_reload_texture(const char *p_texture_name);
+	virtual bool				plat_unload_texture(CTexture *p_texture);
+	virtual void				plat_add_texture(CTexture *p_texture);
+	virtual bool				plat_remove_texture(CTexture *p_texture);
+
+//	NxNgc::sScene *			mp_tex_dict;		// Platform-dependent data
+
+};
+
+void LoadTextureFile( const char *Filename, Lst::HashTable *p_texture_table );
+void LoadTextureFileFromMemory( void **pp_mem, Lst::HashTable *p_texture_table );
+
+} // Namespace Nx  			
+
+#endif
+
diff --git a/Code/Gfx/NGC/p_nxviewman.cpp b/Code/Gfx/NGC/p_nxviewman.cpp
new file mode 100644
index 0000000..1bbfff3
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxviewman.cpp
@@ -0,0 +1,32 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxViewMan.cpp - Ngc platform specific interface to CViewportManager
+//
+// This is Ngc SPECIFIC!!!!!!  So might get a bit messy
+
+#include 
+
+#include "gfx/NxViewMan.h"
+#include "gfx/NGC/p_NxViewport.h"
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewport *		CViewportManager::s_plat_create_viewport(const Mth::Rect* rect, Gfx::Camera* cam)
+{
+	return new CNgcViewport(rect, cam);
+}
+
+} 
+ 
+
diff --git a/Code/Gfx/NGC/p_nxweather.cpp b/Code/Gfx/NGC/p_nxweather.cpp
new file mode 100644
index 0000000..3d199f8
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxweather.cpp
@@ -0,0 +1,1279 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxWeather.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  6/2/2003
+//****************************************************************************
+
+#include 
+#include "gfx/ngc/nx/render.h"
+#include "gfx/ngc/nx/nx_init.h"
+
+#include "gfx/ngc/nx/line.h"
+#include 
+#include 
+
+#include "gfx/ngc/nx/mesh.h"
+
+#include "gfx/ngc/p_nxweather.h"
+#include "gfx/ngc/p_nxparticle.h"
+
+#include 
+#include 
+
+#include 
+#include 
+#include "gfx/nx.h"
+#include 
+
+#include "dolphin/base/ppcwgpipe.h"
+#include "dolphin/gx/gxvert.h"
+
+#define FEELER_START 50000.0f
+
+unsigned char grid_bytes[45*1024];
+
+#define RENDER_DIST 16
+//#define RAIN_HEIGHT 2000
+//#define RAIN_FRAMES 40
+//#define RAIN_LENGTH 100.0f
+#define SEQ_START 411		// Between 1 and 4095.
+#define SEQ_MASK 0x0240
+//#define SEQ_MASK 0x0ca0
+//#define NUM_DROPS_PER_FRAME 25
+
+//1-3: 0x03
+//1-7: 0x06
+//1-15: 0x0C
+//1-31: 0x14
+//1-63: 0x30
+//1-127: 0x60
+//1-255: 0xB8
+//1-511: 0x0110
+//1-1023: 0x0240
+//1-2047: 0x0500
+//1-4095: 0x0CA0
+//1-8191: 0x1B00
+//1-16383: 0x3500
+//1-32767: 0x6000
+//1-65535: 0xB400
+//0x00012000
+//0x00020400
+//0x00072000
+//0x00090000
+//0x00140000
+//0x00300000
+//0x00400000
+//0x00D80000
+//0x01200000
+//0x03880000
+//0x07200000
+//0x09000000
+//0x14000000
+//0x32800000
+//0x48000000
+//0xA3000000
+
+
+//#define DEBUG_LINES
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcWeather::CNgcWeather()
+{
+	mp_roof_height_index = NULL;
+	mp_rain_texture = NULL;
+	mp_snow_texture = NULL;
+
+	m_rain_blend = get_texture_blend( CRCD(0xa86285a1,"fixadd") );		// FixAdd / 64
+	m_splash_blend = get_texture_blend( CRCD(0xa86285a1,"fixadd") );		// FixAdd / 64
+	m_snow_blend = get_texture_blend( CRCD(0xa86285a1,"fixadd") );		// FixAdd / 64
+
+	m_rain_blend_fix = 64;
+	m_splash_blend_fix = 64;
+	m_snow_blend_fix = 64;
+
+	m_rain_rate = 0.0f;
+	m_splash_rate = 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CNgcWeather::~CNgcWeather()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcWeather::plat_update_grid( void )
+{
+	m_system_active = false;
+	// Get super sector manager.
+	SSec::Manager *ss_man;
+	Mth::Line line;
+	ss_man = Nx::CEngine::sGetNearestSuperSectorManager( line );		// Line is ignored, 1st manager is returned.
+	if ( !ss_man ) return;
+
+	// Calculate the size of the world in cels.
+	int min_x = ( ((int)ss_man->GetWorldBBox()->GetMin()[X]) / WEATHER_CEL_SIZE ) - 1;
+	int max_x = ( ((int)ss_man->GetWorldBBox()->GetMax()[X]) / WEATHER_CEL_SIZE ) + 1;
+	int min_z = ( ((int)ss_man->GetWorldBBox()->GetMin()[Z]) / WEATHER_CEL_SIZE ) - 1;
+	int max_z = ( ((int)ss_man->GetWorldBBox()->GetMax()[Z]) / WEATHER_CEL_SIZE ) + 1;
+
+	// Define a maximum...
+	if ( ( max_x - min_x ) > 350 )
+	{
+		int wid = ( max_x - min_x );
+		int excess = ( wid - 350 );
+		min_x += ( excess / 2 );
+		max_x -= ( excess / 2 );
+	}
+
+	if ( ( max_z - min_z ) > 300 )
+	{
+		int wid = ( max_z - min_z );
+		int excess = ( wid - 300 );
+		min_z += ( excess / 2 );
+		max_z -= ( excess / 2 );
+	}
+
+	// This is the actual width;
+	m_width = ( max_x - min_x ) + 1;
+	m_height = ( max_z - min_z ) + 1;
+
+	// Allocate a new piece of memory for the grid.
+//	if ( mp_roof_height_index ) delete mp_roof_height_index;
+//	mp_roof_height_index = new unsigned char[m_width * m_height];
+
+//	mp_roof_height_index = grid_bytes;
+
+	m_min_x = ( min_x * WEATHER_CEL_SIZE );
+	m_min_z = ( min_z * WEATHER_CEL_SIZE );
+
+	// Temporary buffer for the raw array.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	unsigned char * p8 = new unsigned char[m_width*m_height];
+	Mem::Manager::sHandle().PopContext();
+
+	// Go through and get the height for each cel corner.
+	CFeeler feeler;
+	int num_heights = 0;
+	int xx, zz;
+	for ( zz = min_z; zz <= max_z; zz++ )
+	{
+		for ( xx = min_x; xx <= max_x; xx++ )
+		{
+			// The cel to fill.
+			int cel = ( ( zz - min_z ) * m_width ) + ( xx - min_x );
+
+			// The position to check.
+			Mth::Vector pos;
+			pos[X] = (float)( xx * WEATHER_CEL_SIZE );
+			pos[Y] = FEELER_START;
+			pos[Z] = (float)( zz * WEATHER_CEL_SIZE );
+			pos[W] = 1.0f;
+
+			feeler.SetStart( pos );
+			pos[Y] = -FEELER_START;
+			feeler.SetEnd( pos );
+
+			// Get the y position.
+			float y;
+			if ( feeler.GetCollision( false, false ) )		// No movables, nearest collision.
+			{
+				y = feeler.GetPoint()[Y];
+			}
+			else
+			{
+				y = FEELER_START;
+			}
+
+			// See if a close enough y pos already exists.
+			int found = -1;
+			for ( int lp = 0; lp < num_heights; lp++ )
+			{
+				if ( fabsf( ( m_roof_height[lp] - y ) ) < HEIGHT_TOLERANCE )
+				{
+					found = lp;
+					break;
+				}
+			}
+
+			// Fill in the cel.
+			if ( found != -1 )
+			{
+				// Existing height.
+				p8[cel] = found;
+			}
+			else
+			{
+				// New height.
+				p8[cel] = num_heights;
+				m_roof_height[num_heights] = y;
+				num_heights++;
+			}
+		}
+	}
+
+	// Work out highest height for each cel.
+	for ( zz = min_z; zz <= max_z; zz++ )
+	{
+		for ( xx = min_x; xx <= max_x; xx++ )
+		{
+			// The cel to fill.
+			int cel = ( ( zz - min_z ) * m_width ) + ( xx - min_x );
+			int celx = ( ( zz - min_z ) * m_width ) + ( ( xx + 1 ) - min_x );
+			int celz = ( ( ( zz + 1 ) - min_z ) * m_width ) + ( xx - min_x );
+			int celxz = ( ( ( zz + 1 ) - min_z ) * m_width ) + ( ( xx + 1 ) - min_x );
+
+			if ( m_roof_height[p8[celx]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celx];
+			if ( m_roof_height[p8[celz]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celz];
+			if ( m_roof_height[p8[celxz]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celxz];
+		}
+	}
+
+	// Create a sparse array.
+	mp_roof_row = (sRowEntry *)grid_bytes;
+	mp_roof_height_index = (unsigned char *)&mp_roof_row[m_height];
+
+	// 0 = offset
+	// 1 = width
+	// 2 = index
+
+	unsigned short index = 0;
+	for ( zz = 0; zz <= m_height; zz++ )
+	{
+		unsigned short start = 0;
+		unsigned short end = m_width - 1;
+
+		// Scan to find the start.
+		bool start_set = false;
+		for ( xx = 0; xx < m_width; xx++ )
+		{
+			int cel = ( zz * m_width ) + xx;
+
+			if ( m_roof_height[p8[cel]] != FEELER_START )
+			{
+				if ( !start_set )
+				{
+					// Set start value.
+					start = xx;
+					start_set = true;
+				}
+				else
+				{
+					// Set end value.
+					end = xx;
+				}
+
+			}
+		}
+
+		// Copy data & set row entry.
+		if ( start < end )
+		{
+			mp_roof_row[zz].start = start;
+			mp_roof_row[zz].end = end;
+			mp_roof_row[zz].index = index;
+			for ( xx = start; xx <= end ; xx++ )
+			{
+				int cel = ( zz * m_width ) + xx;
+
+				mp_roof_height_index[index] = p8[cel];
+				index++;
+			}
+		}
+		else
+		{
+			// Row doesn't exist.
+			mp_roof_row[zz].start = 16384;
+			mp_roof_row[zz].end = 0;
+			mp_roof_row[zz].index = 0;
+		}
+	}
+
+	delete p8;
+
+#ifndef __NOPT_FINAL__
+	int new_size = ( m_height * 6 ) + index;
+
+	printf( "Grid Size: Old: %d New: %d Num Heights: %d\n", ( m_width * m_height ), new_size, num_heights );
+#endif		// __NOPT_FINAL__
+//	printf( "Num Weather Grid Heights: %d (%d x %d)\n", num_heights, m_width, m_height );
+
+	// Set all drip time counters to 255.
+	// 255 means the drop won't be rendered.
+	// Setting to anything other than 255 will mean that it will increment to 255 and stop.
+	for ( int lp = 0; lp < ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ); lp++ )
+	{
+		m_drop_time[lp] = 255;
+		m_x_offset[lp] = Mth::Rnd( 255 );
+		m_z_offset[lp] = Mth::Rnd( 255 );
+	}
+	m_active_drops = 0;
+
+	m_seq = SEQ_START;
+
+	Nx::CTexture *p_texture;
+	Nx::CNgcTexture *p_Ngc_texture;
+
+	// Set Rain Texture.
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( CRCD(0x45c7eb0f,"splash") );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_rain_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	// Set Snow Texture.
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( CRCD(0xc97c5a4c,"snow") );
+	p_Ngc_texture = static_cast( p_texture );
+	if ( p_Ngc_texture )
+	{
+		mp_snow_texture = p_Ngc_texture->GetEngineTexture(); 
+	}
+
+	m_system_active = true;
+
+	// Zero out the splashes.
+	for ( int sp = 0; sp < NUM_SPLASH_ACTIVE; sp++ )
+	{
+		m_splash_current_life[sp] = 0;
+	}
+	m_current_splash = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcWeather::plat_process( float delta_time )
+{
+	if ( !m_system_active ) return;
+
+	if ( m_raining )
+	{
+		// It's raining.
+		float rate = m_rain_drops_per_frame;
+		if ( rate > (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_rain_frames ) ) rate = (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_rain_frames );
+
+		float last = m_rain_rate;
+		m_rain_rate += rate;
+		int new_drops = (int)m_rain_rate - (int)last;
+
+		for ( int lp = 0; lp < new_drops; lp++ )
+		{
+			// If this cel is not inactive, we caught up with ourselves.
+			if ( m_drop_time[m_seq] != 255 ) break;
+
+			// Setup the drop.
+			m_drop_time[m_seq] = ( 254 - m_rain_frames );
+			m_x_offset[m_seq] = Mth::Rnd( 255 );
+			m_z_offset[m_seq] = Mth::Rnd( 255 );
+			m_active_drops++;
+
+			// Calculate next sequence value. Notice hack to get entry 0 into the sequence.
+			switch ( m_seq )
+			{
+				case SEQ_START:
+					m_seq = 0;
+					break;
+				case 0:
+					m_seq = SEQ_START;
+					// Fall through to default case...
+				default:
+					if ( m_seq & 1 )
+					{
+						m_seq = ( m_seq >> 1 ) ^ SEQ_MASK;
+					}
+					else
+					{
+						m_seq = ( m_seq >> 1 );
+					}
+					break;
+			}
+		}
+	}
+	else
+	{
+		// It's snowing.
+		float rate = m_snow_flakes_per_frame;
+		if ( rate > (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_snow_frames ) ) rate = (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_snow_frames );
+
+		float last = m_snow_rate;
+		m_snow_rate += rate;
+		int new_drops = (int)m_snow_rate - (int)last;
+
+		for ( int lp = 0; lp < new_drops; lp++ )
+		{
+			// If this cel is not inactive, we caught up with ourselves.
+			if ( m_drop_time[m_seq] != 255 ) break;
+
+			// Setup the drop.
+			m_drop_time[m_seq] = ( 254 - m_snow_frames );
+			m_x_offset[m_seq] = Mth::Rnd( 255 );
+			m_z_offset[m_seq] = Mth::Rnd( 255 );
+			m_active_drops++;
+
+			// Calculate next sequence value. Notice hack to get entry 0 into the sequence.
+			switch ( m_seq )
+			{
+				case SEQ_START:
+					m_seq = 0;
+					break;
+				case 0:
+					m_seq = SEQ_START;
+					// Fall through to default case...
+				default:
+					if ( m_seq & 1 )
+					{
+						m_seq = ( m_seq >> 1 ) ^ SEQ_MASK;
+					}
+					else
+					{
+						m_seq = ( m_seq >> 1 );
+					}
+					break;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcWeather::plat_render_snow( float skx, float skz )
+{
+	// Early out if no drops to draw.
+	if ( !m_active_drops ) return;
+
+	// Get skater position.
+	int x = (int)( ( skx - m_min_x ) / WEATHER_CEL_SIZE );
+	int z = (int)( ( skz - m_min_z ) / WEATHER_CEL_SIZE );
+
+	// Calculate area to render.
+	int sx = x - RENDER_DIST;
+	int ex = x + DROP_SIZE;	//RENDER_DIST;
+	int sz = z - RENDER_DIST;
+	int ez = z + DROP_SIZE;	//RENDER_DIST;
+
+	// Clip z values.
+	if ( ez < 0 ) return;
+	if ( sz > ( m_height - 1 ) ) return;
+
+//	if ( sz < 0 ) sz = 0;
+//	if ( ez > ( m_height - 1 ) ) ez = ( m_height - 1 );
+
+	GX::UploadTexture(  mp_snow_texture->pTexelData,
+						mp_snow_texture->ActualWidth,
+						mp_snow_texture->ActualHeight,
+						GX_TF_CMPR,
+						GX_CLAMP,
+						GX_CLAMP,
+						GX_FALSE,
+						GX_LINEAR,
+						GX_LINEAR,
+						0.0f,
+						0.0f,
+						0.0f,
+						GX_FALSE,
+						GX_TRUE,
+						GX_ANISO_1,
+						GX_TEXMAP0 ); 
+	GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, mp_snow_texture->ActualWidth, mp_snow_texture->ActualHeight );
+
+	if ( mp_snow_texture->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA )
+	{
+		GX::UploadTexture(  mp_snow_texture->pAlphaData,
+							mp_snow_texture->ActualWidth,
+							mp_snow_texture->ActualHeight,
+							GX_TF_CMPR,
+							GX_CLAMP,
+							GX_CLAMP,
+							GX_FALSE,
+							GX_LINEAR,
+							GX_LINEAR,
+							0.0f,
+							0.0f,
+							0.0f,
+							GX_FALSE,
+							GX_TRUE,
+							GX_ANISO_1,
+							GX_TEXMAP1 ); 
+		GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, mp_snow_texture->ActualWidth, mp_snow_texture->ActualHeight );
+		GX::SetTexChanTevIndCull( 2, 1, 2, 0, GX_CULL_NONE );
+	}
+	else
+	{
+		GX::SetTexChanTevIndCull( 1, 1, 1, 0, GX_CULL_NONE );
+	}
+
+	GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR0A0 );
+
+	GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+										   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+										   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+	GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO, 
+									   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+
+	if ( mp_snow_texture->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA )
+	{
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+											   GX_TEV_SWAP0, GX_TEV_SWAP1 );
+
+		GX::SetTevColorInOp( GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV,
+										   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+
+	}
+
+	switch ( m_snow_blend )
+	{
+		case NxNgc::vBLEND_MODE_ADD:
+		case NxNgc::vBLEND_MODE_ADD_FIXED:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_SUBTRACT:
+		case NxNgc::vBLEND_MODE_SUB_FIXED:
+			GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ZERO, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_BLEND:
+		case NxNgc::vBLEND_MODE_BLEND_FIXED:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_BRIGHTEN:
+		case NxNgc::vBLEND_MODE_BRIGHTEN_FIXED:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_MODULATE_FIXED:
+		case NxNgc::vBLEND_MODE_MODULATE:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_BLEND_PREVIOUS_MASK:
+		case NxNgc::vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+		case NxNgc::vBLEND_MODE_DIFFUSE:
+		default:
+			GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+	}
+
+	GX::SetChanCtrl( GX_COLOR0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+	GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+	
+	switch ( m_snow_blend )
+	{
+		case NxNgc::vBLEND_MODE_ADD_FIXED:
+		case NxNgc::vBLEND_MODE_SUB_FIXED:
+		case NxNgc::vBLEND_MODE_BLEND_FIXED:
+		case NxNgc::vBLEND_MODE_BRIGHTEN_FIXED:
+		case NxNgc::vBLEND_MODE_MODULATE_FIXED:
+			{
+				uint8 fix = m_snow_blend_fix >= 128 ? 255 : m_snow_blend_fix * 2;
+				GX::SetChanMatColor( GX_COLOR0A0, (GXColor){fix,fix,fix,fix} );
+			}
+			break;
+		default:
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+			break;
+	}
+	
+	GXColor col = (GXColor){ m_snow_color.r, m_snow_color.g, m_snow_color.b, m_snow_color.a };
+	GX::SetChanAmbColor( GX_COLOR0A0, col );
+
+
+
+
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+	float minx = m_min_x;
+	float minz = m_min_z;
+//	float rlength = m_rain_length;
+	
+	// Calculate drop height list.
+	int lp;
+	float y_off[256];
+
+	for ( lp = ( 254 - m_snow_frames ); lp < 256; lp++ )
+	{
+		y_off[lp] = m_snow_height - ( ( (float)( ( lp - 254 ) + m_snow_frames ) / (float)m_snow_frames ) * m_snow_height );
+	}
+
+	// Calculate xz offset list.
+	float xz_off[256];
+
+	for ( lp = 0; lp < 256; lp++ )
+	{
+		xz_off[lp] = ( (float)lp * (float)( WEATHER_CEL_SIZE / 2 ) ) / 255.0f;
+	}
+
+	// Calculate sine wave.
+	float sin_off[256];
+
+	for ( lp = 0; lp < 256; lp++ )
+	{
+		sin_off[lp] = sinf( Mth::PI * 2.0f * ( (float)lp / 256.0f ) ) * ( WEATHER_CEL_SIZE / 2 );
+//		sin_off[lp] = sinf( Mth::PI * 2.0f * (float)lp ) * 128.0f;
+	}
+
+
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+	NsVector	up( 0.0f, 1.0f, 0.0f );
+	NsVector screen_right;
+	NsVector screen_up;
+	screen_right.cross( *p_matrix->getAt(), up );
+	screen_up.cross( screen_right, *p_matrix->getAt());
+
+	screen_right.normalize();
+	screen_up.normalize();
+	
+	screen_right.x *= m_snow_size;
+	screen_right.y *= m_snow_size;
+	screen_right.z *= m_snow_size;
+
+	screen_up.x *= m_snow_size;
+	screen_up.y *= m_snow_size;
+	screen_up.z *= m_snow_size;
+
+	for ( int lzz = sz; lzz <= ez; lzz++ )
+	{
+		int zz = ( lzz < 0 ) ? 0 : ( lzz > ( m_height - 1 ) ) ? ( m_height - 1 ) : lzz;
+
+		if ( mp_roof_row[zz].start == 16384 ) continue;
+
+//		// Calculate actual span to scan.
+//		int rsx = sx > mp_roof_row[zz].start ? sx : mp_roof_row[zz].start;
+//		int rex = ex < mp_roof_row[zz].end ? ex : mp_roof_row[zz].end;
+//
+//		// Start position.
+//		float vx = ( rsx * WEATHER_CEL_SIZE ) + minx;
+//		float vz = ( zz * WEATHER_CEL_SIZE ) + minz;
+
+		float vx = ( (float)sx * (float)WEATHER_CEL_SIZE ) + minx;
+		float vz = ( (float)zz * (float)WEATHER_CEL_SIZE ) + minz;
+
+		int cel = mp_roof_row[zz].index + ( sx - mp_roof_row[zz].start );
+
+		int drop_cel_z = ( ( zz & ( DROP_SIZE - 1 ) ) << DROP_SIZE_SHIFT );
+
+//		for ( int xx = rsx; xx <= rex; xx++, cel++ )
+		for ( int lxx = sx; lxx <= ex; lxx++, cel++ )
+		{
+			int xx = ( lxx > mp_roof_row[zz].start ) ? ( ( lxx < mp_roof_row[zz].end ) ? lxx : mp_roof_row[zz].end ) : mp_roof_row[zz].start;
+
+			// Get the current drop value. Skip this one if it's inactive.
+			int drop_cel = drop_cel_z + ( xx & ( DROP_SIZE - 1 ) );
+
+			vx += (float)WEATHER_CEL_SIZE;
+			float vy = m_roof_height[mp_roof_height_index[cel]];
+
+			for ( int d = 0; d < DROP_LAYERS; d++, drop_cel += ( DROP_SIZE * DROP_SIZE ) )
+			{
+				if ( m_drop_time[drop_cel] == 255 ) continue;
+
+				// Create the position for rendering.
+				float v0x = vx + xz_off[m_x_offset[drop_cel]] + sin_off[(m_z_offset[drop_cel]+m_drop_time[drop_cel])&255];
+				float v0y = vy + y_off[m_drop_time[drop_cel]] + m_snow_size;
+				float v0z = vz + xz_off[m_z_offset[drop_cel]] + sin_off[(m_x_offset[drop_cel]+m_drop_time[drop_cel])&255];
+
+				float v1x = v0x + screen_up.x;
+				float v1y = v0y + screen_up.y;
+				float v1z = v0z + screen_up.z;
+
+				v0x -= screen_up.x;
+				v0y -= screen_up.y;
+				v0z -= screen_up.z;
+
+				// Send coordinates.
+				GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+					GXWGFifo.f32 = v1x - screen_right.x;
+					GXWGFifo.f32 = v1y - screen_right.y;
+					GXWGFifo.f32 = v1z - screen_right.z;
+					GXWGFifo.f32 = 0.0f;
+					GXWGFifo.f32 = 0.0f;
+
+					GXWGFifo.f32 = v1x + screen_right.x;
+					GXWGFifo.f32 = v1y + screen_right.y;
+					GXWGFifo.f32 = v1z + screen_right.z;
+					GXWGFifo.f32 = 1.0f;
+					GXWGFifo.f32 = 0.0f;
+
+					GXWGFifo.f32 = v0x + screen_right.x;
+					GXWGFifo.f32 = v0y + screen_right.y;
+					GXWGFifo.f32 = v0z + screen_right.z;
+					GXWGFifo.f32 = 1.0f;
+					GXWGFifo.f32 = 1.0f;
+
+					GXWGFifo.f32 = v0x - screen_right.x;
+					GXWGFifo.f32 = v0y - screen_right.y;
+					GXWGFifo.f32 = v0z - screen_right.z;
+					GXWGFifo.f32 = 0.0f;
+					GXWGFifo.f32 = 1.0f;
+//					GX::Position3f32( v1x - screen_right.x, v1y - screen_right.y, v1z - screen_right.z );
+//					GX::Position3f32( v1x + screen_right.x, v1y + screen_right.y, v1z + screen_right.z );
+//					GX::Position3f32( v0x + screen_right.x, v0y + screen_right.y, v0z + screen_right.z );
+//					GX::Position3f32( v0x - screen_right.x, v0y - screen_right.y, v0z - screen_right.z );
+				GX::End();
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcWeather::plat_render_rain( float skx, float skz )
+{
+	// Early out if no drops to draw.
+	if ( !m_active_drops ) return;
+
+	// Get skater position.
+	int x = (int)( ( skx - m_min_x ) / WEATHER_CEL_SIZE );
+	int z = (int)( ( skz - m_min_z ) / WEATHER_CEL_SIZE );
+
+	// Calculate area to render.
+	int sx = x - RENDER_DIST;
+	int ex = x + DROP_SIZE;	//RENDER_DIST;
+	int sz = z - RENDER_DIST;
+	int ez = z + DROP_SIZE;	//RENDER_DIST;
+
+	// Clip z values.
+	if ( ez < 0 ) return;
+	if ( sz > ( m_height - 1 ) ) return;
+
+//	if ( sz < 0 ) sz = 0;
+//	if ( ez > ( m_height - 1 ) ) ez = ( m_height - 1 );
+
+	GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+	GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+
+	GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+										   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_ENABLE, GX_TEVPREV,
+										   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+	GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC,
+									   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_4, GX_ENABLE, GX_TEVPREV );
+
+	// Set blend mode for base layer.
+	switch ( m_rain_blend )
+	{
+		case NxNgc::vBLEND_MODE_ADD:
+		case NxNgc::vBLEND_MODE_ADD_FIXED:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_SUBTRACT:
+		case NxNgc::vBLEND_MODE_SUB_FIXED:
+			GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ZERO, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_BLEND:
+		case NxNgc::vBLEND_MODE_BLEND_FIXED:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_BRIGHTEN:
+		case NxNgc::vBLEND_MODE_BRIGHTEN_FIXED:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_MODULATE_FIXED:
+		case NxNgc::vBLEND_MODE_MODULATE:
+			GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+		case NxNgc::vBLEND_MODE_BLEND_PREVIOUS_MASK:
+		case NxNgc::vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+		case NxNgc::vBLEND_MODE_DIFFUSE:
+		default:
+			GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+			break;
+	}
+
+	GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+	
+	switch ( m_rain_blend )
+	{
+		case NxNgc::vBLEND_MODE_ADD_FIXED:
+		case NxNgc::vBLEND_MODE_SUB_FIXED:
+		case NxNgc::vBLEND_MODE_BLEND_FIXED:
+		case NxNgc::vBLEND_MODE_BRIGHTEN_FIXED:
+		case NxNgc::vBLEND_MODE_MODULATE_FIXED:
+			{
+				uint8 fix = m_rain_blend_fix >= 128 ? 255 : m_rain_blend_fix * 2;
+				GX::SetChanMatColor( GX_COLOR0A0, (GXColor){fix,fix,fix,fix} );
+			}
+			break;
+		default:
+			GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+			break;
+	}
+
+	GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_CLR0, GX_DIRECT );
+
+	float minx = m_min_x;
+	float minz = m_min_z;
+	float rlength = m_rain_length;
+	
+	// Calculate drop height list.
+	int lp;
+	float y_off[256];
+
+	for ( lp = ( 254 - m_rain_frames ); lp < 256; lp++ )
+	{
+		y_off[lp] = ( m_rain_height - ( (float)( ( lp - 254 ) + m_rain_frames ) / (float)m_rain_frames ) * m_rain_height );
+	}
+
+	// Calculate xz offset list.
+	float xz_off[256];
+
+	for ( lp = 0; lp < 256; lp++ )
+	{
+		xz_off[lp] = ( (float)lp * (float)WEATHER_CEL_SIZE ) / 255.0f;
+	}
+
+	GXColor colb = (GXColor){ m_rain_bottom_color.r, m_rain_bottom_color.g, m_rain_bottom_color.b, m_rain_bottom_color.a };
+	GXColor colt = (GXColor){ m_rain_top_color.r, m_rain_top_color.g, m_rain_top_color.b, m_rain_top_color.a };
+
+	for ( int lzz = sz; lzz <= ez; lzz++ )
+	{
+		int zz = ( lzz < 0 ) ? 0 : ( lzz > ( m_height - 1 ) ) ? ( m_height - 1 ) : lzz;
+
+		if ( mp_roof_row[zz].start == 16384 ) continue;
+
+//		// Calculate actual span to scan.
+//		int rsx = sx > mp_roof_row[zz].start ? sx : mp_roof_row[zz].start;
+//		int rex = ex < mp_roof_row[zz].end ? ex : mp_roof_row[zz].end;
+//
+//		// Start position.
+//		float vx = ( rsx * WEATHER_CEL_SIZE ) + minx;
+//		float vz = ( zz * WEATHER_CEL_SIZE ) + minz;
+
+		float vx = (float)( sx << WEATHER_CEL_SIZE_SHIFT ) + minx;
+		float vz = (float)( zz << WEATHER_CEL_SIZE_SHIFT ) + minz;
+
+		int cel = mp_roof_row[zz].index + ( sx - mp_roof_row[zz].start );
+
+		int drop_cel_z = ( ( zz & ( DROP_SIZE - 1 ) ) << DROP_SIZE_SHIFT );
+
+//		for ( int xx = rsx; xx <= rex; xx++, cel++ )
+		for ( int lxx = sx; lxx <= ex; lxx++, cel++ )
+		{
+			int xx = ( lxx > mp_roof_row[zz].start ) ? ( ( lxx < mp_roof_row[zz].end ) ? lxx : mp_roof_row[zz].end ) : mp_roof_row[zz].start;
+
+			// Get the current drop value. Skip this one if it's inactive.
+			int drop_cel = drop_cel_z + ( xx & ( DROP_SIZE - 1 ) );
+
+			vx += (float)WEATHER_CEL_SIZE;
+			float vy = m_roof_height[mp_roof_height_index[cel]];
+
+			for ( int d = 0; d < DROP_LAYERS; d++, drop_cel += ( DROP_SIZE * DROP_SIZE ) )
+			{
+				if ( m_drop_time[drop_cel] == 255 ) continue;
+
+				// Create the position for rendering.
+				float v0x = vx + xz_off[m_x_offset[drop_cel]];
+				float v0y = vy + y_off[m_drop_time[drop_cel]]; 
+				float v0z = vz + xz_off[m_z_offset[drop_cel]];
+
+				float v1y = v0y + rlength;
+
+				// Send coordinates.
+				GX::Begin( GX_LINES, GX_VTXFMT0, 2 ); 
+					GX::Position3f32( v0x, v0y, v0z );
+					GX::Color1u32( *((uint32*)&colb) );
+					GX::Position3f32( v0x, v1y, v0z );
+					GX::Color1u32( *((uint32*)&colt) );
+				GX::End();
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcWeather::plat_render_splashes( float skx, float skz )
+{
+	// Create a new splash if required.
+	float last = m_splash_rate;
+	m_splash_rate += m_splash_per_frame;
+	int new_splashes = (int)m_splash_rate - (int)last;
+
+	if ( new_splashes )
+	{
+		CFeeler feeler;
+		Mth::Vector pos;
+
+		pos[X] = NxNgc::EngineGlobals.camera.getPosX();	// + ( Mth::Rnd( ( WEATHER_CEL_SIZE * RENDER_DIST * 2 ) ) ) - ( WEATHER_CEL_SIZE * RENDER_DIST );
+		pos[Y] = FEELER_START;
+		pos[Z] = NxNgc::EngineGlobals.camera.getPosZ();	// + ( Mth::Rnd( ( WEATHER_CEL_SIZE * RENDER_DIST * 2 ) ) ) - ( WEATHER_CEL_SIZE * RENDER_DIST ); 
+		pos[W] = 1.0f;
+
+		Mth::Vector dir;
+		dir[X] = skx - NxNgc::EngineGlobals.camera.getPosX();
+		dir[Y] = 0.0f;
+		dir[Z] = skz - NxNgc::EngineGlobals.camera.getPosZ();
+		dir[W] = 1.0f;
+		dir.Normalize();
+
+		// Add distance.
+		float r1 = (float)Mth::Rnd( 32768 ) / 32768.0f;
+		pos += ( dir * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 ) );
+
+		// Add lateral.
+		Mth::Vector lat;
+		lat[X] = dir[Z];
+		lat[Y] = 0.0f;
+		lat[Z] = -dir[X];
+		lat[W] = 1.0f;
+
+		float r2 = 1.0f - ( (float)Mth::Rnd( 32768 ) / 32768.0f );
+		if ( m_current_splash & 1 )
+		{
+			pos += ( lat * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 * r2 ) );
+		}
+		else
+		{
+			pos += ( lat * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 * -r2 ) );
+		}
+
+		feeler.SetStart( pos );
+		pos[Y] = -FEELER_START;
+		feeler.SetEnd( pos );
+
+		// Get the y position.
+		float y;
+		if ( feeler.GetCollision( false, false ) )		// No movables, nearest collision.
+		{
+			y = feeler.GetPoint()[Y];
+		}
+		else
+		{
+			y = FEELER_START;
+		}
+
+		m_splash_x[m_current_splash] = pos[X];
+		m_splash_y[m_current_splash] = y + m_splash_size;
+		m_splash_z[m_current_splash] = pos[Z];
+		m_splash_current_life[m_current_splash] = m_splash_life;
+
+		m_current_splash++;
+		m_current_splash %= NUM_SPLASH_ACTIVE;
+	}
+	
+	// Count active splashes.
+	int splashes = 0;
+	for ( int spl = 0; spl < NUM_SPLASH_ACTIVE; spl++ )
+	{
+		if ( m_splash_current_life[spl] != 0 ) splashes++;
+	}
+
+	if ( splashes )
+	{
+		GX::UploadTexture(  mp_rain_texture->pTexelData,
+							mp_rain_texture->ActualWidth,
+							mp_rain_texture->ActualHeight,
+							GX_TF_CMPR,
+							GX_CLAMP,
+							GX_CLAMP,
+							GX_FALSE,
+							GX_LINEAR,
+							GX_LINEAR,
+							0.0f,
+							0.0f,
+							0.0f,
+							GX_FALSE,
+							GX_TRUE,
+							GX_ANISO_1,
+							GX_TEXMAP0 ); 
+		GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, mp_rain_texture->ActualWidth, mp_rain_texture->ActualHeight );
+
+		if ( mp_rain_texture->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA )
+		{
+			GX::UploadTexture(  mp_rain_texture->pAlphaData,
+								mp_rain_texture->ActualWidth,
+								mp_rain_texture->ActualHeight,
+								GX_TF_CMPR,
+								GX_CLAMP,
+								GX_CLAMP,
+								GX_FALSE,
+								GX_LINEAR,
+								GX_LINEAR,
+								0.0f,
+								0.0f,
+								0.0f,
+								GX_FALSE,
+								GX_TRUE,
+								GX_ANISO_1,
+								GX_TEXMAP1 ); 
+			GX::SetTexCoordScale( GX_TEXCOORD0, GX_TRUE, mp_rain_texture->ActualWidth, mp_rain_texture->ActualHeight );
+			GX::SetTexChanTevIndCull( 2, 1, 2, 0, GX_CULL_NONE );
+		}
+		else
+		{
+			GX::SetTexChanTevIndCull( 1, 1, 1, 0, GX_CULL_NONE );
+		}
+
+		GX::SetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR0A0 );
+
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+											   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO, 
+										   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+
+		if ( mp_rain_texture->flags & NxNgc::sTexture::TEXTURE_FLAG_HAS_ALPHA )
+		{
+			GX::SetTevAlphaInOpSwap( GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO,
+												   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+												   GX_TEV_SWAP0, GX_TEV_SWAP1 );
+
+			GX::SetTevColorInOp( GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_CPREV,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
+
+		}
+
+		switch ( m_splash_blend )
+		{
+			case NxNgc::vBLEND_MODE_ADD:
+			case NxNgc::vBLEND_MODE_ADD_FIXED:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case NxNgc::vBLEND_MODE_SUBTRACT:
+			case NxNgc::vBLEND_MODE_SUB_FIXED:
+				GX::SetBlendMode ( GX_BM_SUBTRACT, GX_BL_ZERO, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case NxNgc::vBLEND_MODE_BLEND:
+			case NxNgc::vBLEND_MODE_BLEND_FIXED:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case NxNgc::vBLEND_MODE_BRIGHTEN:
+			case NxNgc::vBLEND_MODE_BRIGHTEN_FIXED:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case NxNgc::vBLEND_MODE_MODULATE_FIXED:
+			case NxNgc::vBLEND_MODE_MODULATE:
+				GX::SetBlendMode ( GX_BM_BLEND, GX_BL_DSTCLR, GX_BL_ZERO, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+			case NxNgc::vBLEND_MODE_BLEND_PREVIOUS_MASK:
+			case NxNgc::vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+			case NxNgc::vBLEND_MODE_DIFFUSE:
+			default:
+				GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_TRUE );
+				break;
+		}
+
+//		GX::SetChanCtrl( GX_COLOR0A0, GX_DISABLE, GX_SRC_VTX, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+		GX::SetChanCtrl( GX_COLOR0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+		GX::SetChanCtrl( GX_ALPHA0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE ); 
+
+		switch ( m_splash_blend )
+		{
+			case NxNgc::vBLEND_MODE_ADD_FIXED:
+			case NxNgc::vBLEND_MODE_SUB_FIXED:
+			case NxNgc::vBLEND_MODE_BLEND_FIXED:
+			case NxNgc::vBLEND_MODE_BRIGHTEN_FIXED:
+			case NxNgc::vBLEND_MODE_MODULATE_FIXED:
+				{
+					uint8 fix = m_splash_blend_fix >= 128 ? 255 : m_splash_blend_fix * 2;
+					GX::SetChanMatColor( GX_COLOR0A0, (GXColor){fix,fix,fix,fix} );
+				}
+				break;
+			default:
+				GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+				break;
+		}
+
+		GXColor col = (GXColor){ m_splash_color.r, m_splash_color.g, m_splash_color.b, m_splash_color.a };
+		GX::SetChanAmbColor( GX_COLOR0A0, col );
+
+
+
+
+
+		GX::SetVtxDesc( 2, GX_VA_POS, GX_DIRECT, GX_VA_TEX0, GX_DIRECT );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		NsMatrix*	p_matrix	= &NxNgc::EngineGlobals.camera;
+
+		NsVector	up( 0.0f, 1.0f, 0.0f );
+		NsVector screen_right;
+		NsVector screen_up;
+		screen_right.cross( *p_matrix->getAt(), up );
+		screen_up.cross( screen_right, *p_matrix->getAt());
+
+		screen_right.normalize();
+		screen_up.normalize();
+
+		screen_right.x *= m_splash_size;
+		screen_right.y *= m_splash_size;
+		screen_right.z *= m_splash_size;
+
+		screen_up.x *= m_splash_size;
+		screen_up.y *= m_splash_size;
+		screen_up.z *= m_splash_size;
+
+		// Render the splashes.
+	//	int u = mp_rain_texture->GetWidth() << 4;
+	//	int v = mp_rain_texture->GetHeight() << 4;
+	//	uint32 col = *((uint32 *)&m_splash_color); 
+
+		for ( int spl = 0; spl < NUM_SPLASH_ACTIVE; spl++ )
+		{
+			if ( m_splash_current_life[spl] == 0 ) continue;
+
+			// Interpolate color.
+			GXColor	icol;
+			icol.r = (uint8)( ( (int)m_splash_color.r * m_splash_current_life[spl] ) / m_splash_life );
+			icol.g = (uint8)( ( (int)m_splash_color.g * m_splash_current_life[spl] ) / m_splash_life );
+			icol.b = (uint8)( ( (int)m_splash_color.b * m_splash_current_life[spl] ) / m_splash_life );
+			icol.a = (uint8)( ( (int)m_splash_color.a * m_splash_current_life[spl] ) / m_splash_life );
+
+			float v0x = m_splash_x[spl] - screen_up.x;
+			float v0y = m_splash_y[spl] - screen_up.y;
+			float v0z = m_splash_z[spl] - screen_up.z;
+
+			float v1x = m_splash_x[spl] + screen_up.x; 
+			float v1y = m_splash_y[spl] + screen_up.y; 
+			float v1z = m_splash_z[spl] + screen_up.z; 
+
+			// Draw splashes...
+			GX::SetChanMatColor( GX_COLOR0A0, icol );
+			GX::Begin( GX_QUADS, GX_VTXFMT0, 4 ); 
+				GXWGFifo.f32 = v1x - screen_right.x;
+				GXWGFifo.f32 = v1y - screen_right.y;
+				GXWGFifo.f32 = v1z - screen_right.z;
+				GXWGFifo.f32 = 0.0f;
+				GXWGFifo.f32 = 0.0f;
+	
+				GXWGFifo.f32 = v1x + screen_right.x;
+				GXWGFifo.f32 = v1y + screen_right.y;
+				GXWGFifo.f32 = v1z + screen_right.z;
+				GXWGFifo.f32 = 1.0f;
+				GXWGFifo.f32 = 0.0f;
+	
+				GXWGFifo.f32 = v0x + screen_right.x;
+				GXWGFifo.f32 = v0y + screen_right.y;
+				GXWGFifo.f32 = v0z + screen_right.z;
+				GXWGFifo.f32 = 1.0f;
+				GXWGFifo.f32 = 1.0f;
+	
+				GXWGFifo.f32 = v0x - screen_right.x;
+				GXWGFifo.f32 = v0y - screen_right.y;
+				GXWGFifo.f32 = v0z - screen_right.z;
+				GXWGFifo.f32 = 0.0f;
+				GXWGFifo.f32 = 1.0f;
+			GX::End();
+
+			m_splash_current_life[spl]--;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CNgcWeather::plat_render( void )
+{
+	if ( !m_system_active ) return;
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = pSkate->GetSkater(0);
+
+	float skx = pSkater->m_pos[X];
+	float skz = pSkater->m_pos[Z];
+
+	GX::LoadPosMtxImm( (MtxPtr)&NxNgc::EngineGlobals.world_to_camera, GX_PNMTX0 );
+	GX::SetZMode( GX_TRUE, GX_LEQUAL, GX_FALSE );
+
+	if ( m_raining )
+	{
+		plat_render_splashes( skx, skz );
+		plat_render_rain( skx, skz );
+	}
+	else
+	{
+		plat_render_snow( skx, skz );
+	}
+
+	// Increment rain drop/snow flake positions.
+	if ( m_active_drops )
+	{
+		for ( int lp = 0; lp < ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ); lp++ )
+		{
+			switch ( m_drop_time[lp] )
+			{
+				case 255:
+					// Inactive.
+					break;
+				case 254:
+					// About to become inactive, so decrement active drops.
+					m_active_drops--;
+					// Deliberately falls through...
+				default:
+					// Increment time.
+					m_drop_time[lp]++;
+					break;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcWeather::plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_rain_blend = get_texture_blend( blendmode_checksum );
+	m_rain_blend_fix = fix;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcWeather::plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_splash_blend = get_texture_blend( blendmode_checksum );
+	m_splash_blend_fix = fix;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNgcWeather::plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_snow_blend = get_texture_blend( blendmode_checksum );
+	m_snow_blend_fix = fix;
+}
+
+} // Nx
+
+
+
+
+
diff --git a/Code/Gfx/NGC/p_nxweather.h b/Code/Gfx/NGC/p_nxweather.h
new file mode 100644
index 0000000..0a29829
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxweather.h
@@ -0,0 +1,105 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_WEATHER_H__
+#define	__GFX_P_NX_WEATHER_H__
+    
+#include "gfx/nxweather.h"
+
+namespace Nx
+{
+
+//#define WEATHER_GRID_W 65
+//#define WEATHER_GRID_H 65
+//
+//#define WEATHER_CEL_W 32
+//#define WEATHER_CEL_H 32
+
+#define WEATHER_CEL_SIZE 128
+#define WEATHER_CEL_SIZE_SHIFT 7
+#define HEIGHT_TOLERANCE 30.0f
+#define DROP_SIZE 16
+#define DROP_SIZE_SHIFT 4
+#define DROP_LAYERS 4
+
+#define NUM_SPLASH_ACTIVE 32
+
+typedef struct
+{
+	unsigned short start;
+	unsigned short end;
+	unsigned short index;
+} sRowEntry;
+
+class CNgcWeather : public CWeather
+{
+public:
+							CNgcWeather();
+	virtual 				~CNgcWeather();
+
+private:					// It's all private, as it is machine specific
+	void					plat_update_grid( void );
+	void					plat_process( float delta_time );
+	void					plat_render( void );
+	void					plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix );
+	void					plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix );
+	void					plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix );
+
+	void					plat_render_splashes( float skx, float skz );
+	void					plat_render_rain( float skx, float skz );
+	void					plat_render_snow( float skx, float skz );
+
+	NxNgc::sTexture *	mp_rain_texture;
+	NxNgc::sTexture *	mp_snow_texture;
+
+	unsigned char *		mp_roof_height_index;
+	sRowEntry *			mp_roof_row;
+	float				m_roof_height[256];
+	int					m_width;
+	int					m_height;
+
+	float				m_min_x;
+	float				m_min_z;
+
+	float				m_rain_rate;
+	float				m_splash_rate;
+	float				m_snow_rate;
+
+	Mth::Vector			m_grid_base;
+
+	unsigned char		m_drop_time[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+	unsigned char		m_x_offset[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+	unsigned char		m_z_offset[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+
+	uint32				m_seq;
+
+	uint8				m_rain_blend;
+	uint8				m_splash_blend;
+	uint8				m_snow_blend;
+
+	uint8				m_rain_blend_fix;
+	uint8				m_splash_blend_fix;
+	uint8				m_snow_blend_fix;
+
+	float				m_splash_x[NUM_SPLASH_ACTIVE];
+	float				m_splash_y[NUM_SPLASH_ACTIVE];
+	float				m_splash_z[NUM_SPLASH_ACTIVE];
+	int					m_splash_current_life[NUM_SPLASH_ACTIVE]; 
+	int					m_current_splash;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif
+
+
diff --git a/Code/Gfx/NGC/p_nxwin2D.h b/Code/Gfx/NGC/p_nxwin2D.h
new file mode 100644
index 0000000..5ef772e
--- /dev/null
+++ b/Code/Gfx/NGC/p_nxwin2D.h
@@ -0,0 +1,51 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxWin2D.h
+
+#ifndef	__GFX_P_NX_WIN2D_H__
+#define	__GFX_P_NX_WIN2D_H__
+
+#include 	"Gfx/NxWin2D.h"
+#include 	"Gfx/Ngc/NX/sprite.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CWindow2D
+class	CNgcWindow2D : public CWindow2D
+{
+public:
+								CNgcWindow2D(int x = 0, int y = 0, int width = 640, int height = 448);
+								CNgcWindow2D(const Mth::Rect & win_rect);
+	virtual						~CNgcWindow2D();
+
+//	NxNgc::SScissorWindow *		GetEngineWindow() const;
+
+private:
+	//
+	virtual void				plat_update_engine();		// Update engine primitives
+
+	// Machine specific members
+//	NxNgc::SScissorWindow *		mp_plat_window;				// Pointer to engine window
+	short						m_left, m_right, m_top, m_bottom;
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//inline NxNgc::SScissorWindow *	CNgcWindow2D::GetEngineWindow() const
+//{
+//	return mp_plat_window;
+//}
+
+} // Namespace Nx  			
+
+#endif
+
diff --git a/Code/Gfx/NGPS/NX/Makefile b/Code/Gfx/NGPS/NX/Makefile
new file mode 100644
index 0000000..43c1439
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/Makefile
@@ -0,0 +1,91 @@
+
+TOP         = /usr/local/sce/ee
+LIBDIR      = $(TOP)/lib
+INCDIR      = $(TOP)/include
+
+TARGET      = main
+OBJS        = crt0.o        \
+              $(TARGET).o   \
+              dma.o         \
+              vif.o         \
+              vu.o          \
+              gif.o         \
+              gs.o          \
+              pcrtc.o       \
+              dmacalls.o    \
+              mikemath.o    \
+              render.o      \
+              import.o      \
+              texture.o     \
+              material.o    \
+              mesh.o        \
+              nx_init.o     \
+              line.o        \
+              chars.o       \
+              microcode.o   \
+              /skate4/code/sys/file/ngps/p_filesys.o    \
+              /skate4/code/core/debug/Debug.o           \
+              /skate4/code/core/debug/Assert.o          \
+
+LCFILE      = app.cmd
+
+LIBS        = $(LIBDIR)/libsn.a    \
+              $(LIBDIR)/libgraph.a \
+              $(LIBDIR)/libdma.a   \
+              $(LIBDIR)/libdev.a   \
+              $(LIBDIR)/libpad.a   \
+              $(LIBDIR)/libpkt.a   \
+              $(LIBDIR)/libvu0.a
+
+PREFIX      = ee
+AS          = $(PREFIX)-gcc
+CC          = $(PREFIX)-gcc
+LD          = $(PREFIX)-gcc
+DVPASM      = ps2dvpas
+OBJDUMP     = $(PREFIX)-objdump
+RUN         = ps2run -r
+RM          = del
+
+CFLAGS      = -O0 -g  -Wall -Werror -fno-common
+CXXFLAGS    = -O0 -Wall -Werror -fno-exceptions -fno-common
+ASFLAGS     = -c -xassembler-with-cpp
+DVPASMFLAGS = -g -W
+LDFLAGS     = -Wl,-Map,$(TARGET).map -nostartfiles -L$(LIBDIR) -lm
+TMPFLAGS    =
+
+.SUFFIXES: .c .s .cc .dsm .vsm
+
+all: $(TARGET).elf
+
+main.elf: $(OBJS) $(LIBS)
+	$(LD) -o $@ -T $(LCFILE) $(OBJS) $(LIBS) $(LDFLAGS)
+
+crt0.o: crt0.s
+	$(AS) $(ASFLAGS) $(TMPFLAGS) -o $@ $<
+
+.s.o:
+	$(AS) $(ASFLAGS) $(TMPFLAGS) -I$(INCDIR) -o $@ $<
+
+.dsm.o:
+	$(DVPASM) $(DVPASMFLAGS) -I$(INCDIR) -o $@ $<
+
+.vsm.o:
+	$(DVPASM) $(DVPASMFLAGS) -I$(INCDIR) -o $@ $<
+
+.cpp.o:
+	$(CC) $(CFLAGS) $(TMPFLAGS) -I$(INCDIR) -I/skate4/code -I/skate4/code/core/STL \
+    -I/usr/local/sce/common/include -D__PLAT_NGPS__ -c $< -o $*.o \
+
+
+.cc.o:
+	$(CC) $(CXXFLAGS) $(TMPFLAGS) -I$(INCDIR) -c $< -o $*.o
+
+run: $(TARGET).elf
+	$(RUN) $(TARGET).elf
+
+clean:
+	$(RM) *.o
+	$(RM) *.map
+	$(RM) *.dis
+	$(RM) *.elf
+
diff --git a/Code/Gfx/NGPS/NX/asmdma.dsm b/Code/Gfx/NGPS/NX/asmdma.dsm
new file mode 100644
index 0000000..1f92d22
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/asmdma.dsm
@@ -0,0 +1,276 @@
+
+
+;------------------------------------------------------------------------------------------------------------------------------------
+; post-process fog effect
+; (this will be replaced by a vu1 routine)
+
+.global FogDma
+.global FogPalette
+
+.align 7	; align to multiple of 8 qwords (2^7 bytes) to give fastest dma transfer
+FogDma:
+
+
+		; preswizzled 256-colour palette, 32-bit entries
+
+
+DMAcnt	*
+
+	DIRECT *
+	
+		; set registers for palette transfer to vram
+		.quad	0x00000000_0000000E_10000000_00000004		; GifTag	(GS_A_D,1,PACKED,0,0,0,4)
+		.quad	0x00000000_00000050_00013000_00000000		; BITBLTBUF	(0,0,0,0x3000,1,PSMCT32)
+		.quad	0x00000000_00000051_00000000_00000000		; TRXPOS	(0,0,0,0,0)
+		.quad	0x00000000_00000052_00000010_00000010		; TRXREG	(16,16)
+		.quad   0x00000000_00000053_00000000_00000000		; TRXDIR	(0)
+		
+		; image mode giftag for palette
+		.quad	0x00000000_00000000_08000000_00000040		; GifTag	(0,0,IMAGE,0,0,0,64)
+
+FogPalette:
+
+		; the palette itself (which is filled in at run-time)
+		.rept	256
+		.int	0
+		.endr
+
+
+
+		.quad	0x00000000_0000000E_10AB4000_00008008		; GifTag	(GS_A_D,1,PACKED,SPRITE|TME|ABE|FST,1,1,8)
+		.quad	0x00000000_0000003F_00000000_00000000		; TEXFLUSH	(0)
+		.quad	0x00000000_00000014_00000000_00000001		; TEX1_1	(1,0,NEAREST,NEAREST,0,0,0)
+		.quad	0x00000000_00000008_00000000_00003F73		; CLAMP_1	(REGION_REPEAT,REPEAT,0x3F7,0,0,0)
+		.quad	0x00000000_00000040_01BF0000_027F0000		; SCISSOR_1	(0,447,0,639)
+		.quad	0x00000000_00000042_00000000_00000044		; ALPHA_1	(Cs,Cd,As,Cd,0)
+		.quad	0x00000000_00000047_00000000_00050000		; TEST_1	(ZGEQUAL,1,0,0,0,0,0,0)
+		.quad	0x00000000_0000004C_00000000_000A0000		; FRAME_1	(0,10,PSMCT32,0)
+		.quad	0x00000000_0000004E_00000001_010000D2		; ZBUF_1	(0xD2,PSMZ24,1)
+		
+	.EndDirect
+	
+.EndDmaData
+
+
+
+; break screen into halves, otherwise source texture > 1024 in width
+; left half
+DMAcall *,HalfScreen
+
+	DIRECT *
+	
+		; set up source and dest coordinate origins
+		.quad	0x00000000_0000000E_10AB4000_00008002		; GifTag	(GS_A_D,1,PACKED,SPRITE|TME|ABE|FST,1,1,1)
+		.quad	0x00000000_00000006_2006000E_A9351A40		; TEX0_1	(0x1A40,20,PSMT8,10,10,RGBA,DECAL,0x3000,PSMCT32,CSM1,0,1)
+		.quad	0x00000000_00000018_00000000_00001400		; XYOFFSET_1(0x1400,0)
+		
+	.EndDirect
+
+.EndDmaData
+
+
+
+; right half
+DMAcall *,HalfScreen
+
+	DIRECT *
+	
+		; set up source and dest coordinate origins
+		.quad	0x00000000_0000000E_10AB4000_00008002		; GifTag	(GS_A_D,1,PACKED,SPRITE|TME|ABE|FST,1,1,1)
+		.quad	0x00000000_00000006_2006000E_A9351AE0		; TEX0_1	(0x1AE0,20,PSMT8,10,10,RGBA,DECAL,0x3000,PSMCT32,CSM1,0,1)
+		.quad	0x00000000_00000018_00000000_00000000		; XYOFFSET_1(0,0)
+		
+	.EndDirect
+
+.EndDmaData
+
+
+
+; restore and return
+DMAret *
+
+	DIRECT *
+	
+		; restore any GS regs that we shouldn't have knackered
+		.quad	0x00000000_0000000E_10000000_00008001		; GifTag	(GS_A_D,1,PACKED,0,0,1,1)
+		.quad	0x00000000_00000008_00000000_00000000		; CLAMP_1	(REPEAT,REPEAT,0,0,0,0)
+
+	.EndDirect
+
+.EndDmaData
+
+
+
+; apply fogging effect to half of the screen (left or right)
+
+.align 7		; align to 8 qwords
+HalfScreen:
+
+; This innocent-looking piece of assembler generates 280K of dma data!
+; (It will eventually be replaced with a small VU1 progam that generates it on the fly, saving memory and dma bandwidth.)
+
+DMAret *
+
+	DIRECT *
+
+.if 0	 ; Change to 1 to get the full-screen fog back, and lose 280K of memory
+		.quad	0x00000000_00005353_44000000_0000A300		; GifTag	(UV|XYZ2<<4|UV<<8|XYZ2<<12,4,REGLIST,0,0,1,8960)
+
+		; loop over quarter-pages		
+		qpageX = 0
+		.rept 10
+
+			; use diagonally facing quarter-page as the source		
+			qpageU = (qpageX^0x200)*2+8
+		
+			qpageY = 0
+			.rept 28
+			
+				; use diagonally facing quarter-page as the source		
+				qpageV = (qpageY^0x100)*2+8
+				
+				X = qpageX+0x1400
+				U = qpageU
+				.rept 4
+				
+					Y = qpageY
+					V = qpageV+0x20
+					.rept 4
+					
+						; even column
+						.short U+0x40,V,0,0
+						.short X,Y,0xFFFF,0x0080
+						.short U+0xC0,V+0x20,0,0
+						.short X+0x80,Y+0x20,0xFFFF,0x0080
+						
+						; odd column
+						.short U,V+0x40,0,0
+						.short X,Y+0x20,0xFFFF,0x0080
+						.short U+0x80,V+0x60,0,0
+						.short X+0x80,Y+0x40,0xFFFF,0x0080
+						
+						Y = Y+0x40
+						V = V+0x80
+					.endr
+				
+					X = X+0x80
+					U = U+0x100
+				.endr
+		
+				qpageY = qpageY+0x100
+			.endr
+		
+			qpageX = qpageX+0x200
+		.endr
+.else
+; Mick:  Just bunged in something to make it not crash
+		.quad	0x00000000_0000000E_10000000_00008001		; GifTag	(GS_A_D,1,PACKED,0,0,1,1)
+		.quad	0x00000000_00000008_00000000_00000000		; CLAMP_1	(REPEAT,REPEAT,0,0,0,0)
+.endif
+	
+	.EndDirect
+
+.EndDmaData
+
+;------------------------------------------------------------------------------------------------------------------------------------
+
+.if 0
+
+; BILLBOARD TEST
+
+.global BillboardDma
+.global BillboardContext
+.global BillboardMats
+
+.align 4
+BillboardDma:
+DMAret *
+
+	UNPACK	1, 1, V4_32, 0, *
+	
+	; VU context... 32
+	.quad	0x0000000C_0000000F_10000020_3400000C
+BillboardContext:
+	.quad	0,0,0,0
+BillboardMats:
+	.quad	0,0,0,0									; world to camera matrix
+	.quad	0,0,0,0									; camera to screen matrix
+	
+	; axial billboards... 86
+	.quad	0x00000024_00000512_300E4056_35C0000C
+	
+	.float	0,0,1,0									; stq0
+	.int	90,45,15,128							; rgba0
+	.float	0,100,0,0								; xyz
+	.float	1,0,1,0									; stq1
+	.int	30,15,5,128								; rgba1
+	.float	10,100,0,0								; w/2,h/2
+	.float	0,1,1,0									; stq2
+	.int	90,45,15,128							; rgba2
+	.float	0,0,0,0									; tx,ty,tz
+	.float	1,1,1,0									; stq3
+	.int	30,15,5,128								; rgba3
+	.float	0,1,0,0									; axis
+	
+	.float	0,0,1,0									; stq0
+	.int	90,45,15,128							; rgba0
+	.float	70.71,270.71,0,0						; xyz
+	.float	1,0,1,0									; stq1
+	.int	30,15,5,128								; rgba1
+	.float	10,100,0,0								; w/2,h/2
+	.float	0,1,1,0									; stq2
+	.int	90,45,15,128							; rgba2
+	.float	0,0,0,0									; tx,ty,tz
+	.float	1,1,1,0									; stq3
+	.int	30,15,5,128								; rgba3
+	.float	0.7071,0.7071,0,0						; axis
+	
+	.float	0,0,1,0									; stq0
+	.int	90,45,15,128							; rgba0
+	.float	-70.71,270.71,0,0						; xyz
+	.float	1,0,1,0									; stq1
+	.int	30,15,5,128								; rgba1
+	.float	10,100,0,0								; w/2,h/2
+	.float	0,1,1,0									; stq2
+	.int	90,45,15,128							; rgba2
+	.float	0,0,0,0									; tx,ty,tz
+	.float	1,1,1,0									; stq3
+	.int	30,15,5,128								; rgba3
+	.float	-0.7071,0.7071,0,0						; axis
+	
+	; screen-aligned billboards... 84
+	.quad	0x00000018_00000512_302E4054_35C08008
+	
+	.float	0,0,1,0									; stq0
+	.int	0,100,0,64								; rgba0
+	.float	141.42,341.42,0,0						; xyz
+	.float	1,0,1,0									; stq1
+	.int	0,100,0,64								; rgba1
+	.float	60,60,0,0								; w/2,h/2
+	.float	0,1,1,0									; stq2
+	.int	0,100,0,64								; rgba2
+	.float	0,0,10,0								; tx,ty,tz
+	.float	1,1,1,0									; stq3
+	.int	0,100,0,64								; rgba3
+	.float	0,0,0,0									; axis
+	
+	.float	0,0,1,0									; stq0
+	.int	0,100,0,64								; rgba0
+	.float	-141.42,341.42,0,0						; xyz
+	.float	1,0,1,0									; stq1
+	.int	0,100,0,64								; rgba1
+	.float	60,60,0,0								; w/2,h/2
+	.float	0,1,1,0									; stq2
+	.int	0,100,0,64								; rgba2
+	.float	0,0,10,0								; tx,ty,tz
+	.float	1,1,1,0									; stq3
+	.int	0,100,0,64								; rgba3
+	.float	0,0,0,0									; axis
+	.EndUnpack
+
+.EndDmaData
+
+.endif
+
+;------------------------------------------------------------------------------------------------------------------------------------
+
diff --git a/Code/Gfx/NGPS/NX/asmdma.h b/Code/Gfx/NGPS/NX/asmdma.h
new file mode 100644
index 0000000..ab728b8
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/asmdma.h
@@ -0,0 +1,12 @@
+#ifndef __ASMDMA_H
+#define __ASMDMA_H
+
+extern uint32 FogPalette[256];
+extern uint   FogDma;
+
+extern uint BillboardDma;
+extern uint BillboardContext;
+extern uint BillboardMats;
+
+#endif //__ASMDMA_H
+
diff --git a/Code/Gfx/NGPS/NX/chars.cpp b/Code/Gfx/NGPS/NX/chars.cpp
new file mode 100644
index 0000000..1b87bbf
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/chars.cpp
@@ -0,0 +1,755 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "chars.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "vu1code.h"
+#include "texturemem.h"
+#include "render.h"
+#include "switches.h"
+
+/*
+
+
+	
+.fnt file format (by Ryan)
+--------------------------
+
+	4	File size in bytes
+	4	Number of characters
+	4	Default height
+	4	Default base
+	
+	?	Character table (see below)
+	?	Texture (see below)
+
+	Character
+	2	Baseline (how many pixels down relative to top of image)
+	2	Ascii value
+
+	Texture
+	4	Size of texture
+	2	Width
+	2	Height
+	2	Bit depth
+	6	Padding
+	W*H	Raw data
+	0-3	Padding for uint32 alignment
+	1K	Palette data
+	4	Number of subtextures
+	?	Subtexture table (see below)
+
+	Subtexture
+	2	X
+	2	Y
+	2	W
+	2	H
+
+	
+	
+	
+font->vram upload format
+------------------------
+
+
+	GIFtag PACKED 4
+	BITBLTBUF ?
+	TRXPOS 0
+	TRXREG w,h
+	TRXDIR 0
+	
+	GIFtag IMAGE (w*h+15)/16
+	... (tex data)
+	
+	GIFtag PACKED 4
+	BITBLTBUF ?
+	TRXPOS 0
+	TRXREG w,h
+	TRXDIR 0
+	
+	GIFtag IMAGE 64
+	... (clut data)
+	
+	
+	
+	total size in bytes = 12*16+((w*h+15)/16)*16+1024 = 1216+((w*h+15)/16)*16
+	
+	this will be referenced as follows:
+	
+	dma ref
+	NOP
+	DIRECT 76+(w*h+15)/16
+	
+	
+*/
+
+
+namespace NxPs2
+{
+
+
+float CurrentScale;
+uint32 FontVramStart;
+uint32 FontVramBase;
+SFont *pFontList;
+SFont *pButtonsFont = NULL;
+
+
+// Garrett: Now uses both 648K buffers at once, so we'd need to undo this if we want to
+// implement double buffering.
+const uint32 FontVramSize = 0xA20 * 2;	// 648K * 2
+
+extern TextureMemoryLayout TexMemLayout;
+
+SFont *SText::spOverrideFont = NULL;
+
+void * OpenExtended(const char *Filename)
+{
+	char *ExtendedFilename;
+
+	// allocate memory for extended name
+	ExtendedFilename = (char *)Mem::Malloc(strlen(Filename)+17);	// 12 for "fonts/" plus 4 for ".fnt" plus 1 for \0
+	Dbg_MsgAssert(ExtendedFilename, ("couldn't malloc memory for extended filename fonts/%s", Filename));
+
+	// copy and extend name
+	strcpy(ExtendedFilename, "fonts/");
+	strcat(ExtendedFilename, Filename);
+	strcat(ExtendedFilename, ".fnt.ps2");
+
+	// open file
+	void *pFile = File::Open(ExtendedFilename, "rb");
+	Dbg_MsgAssert(pFile>=0, ("couldn't open file %s", Filename));
+
+	// free memory
+	Mem::Free(ExtendedFilename);
+
+	return pFile;
+}
+
+
+
+
+
+SFont * LoadFont(const char *Filename)
+{
+	SFont *pFont;
+	SChar *pChar;
+	uint8 *pData;
+	void *pFile;
+	int i,j,Len,NumChars,Width,Height,NumQWords,NumBytes;
+	uint32 TW,TH,TBW;
+	uint32 temp;
+
+		  
+	uint8*	old_dma_loc = dma::pLoc;		  
+
+	// open the font file
+	pFile = OpenExtended(Filename);
+
+
+	// allocate memory for the font structure
+	pFont = (SFont *)Mem::Malloc(sizeof(SFont));
+	Dbg_MsgAssert(pFont, ("couldn't malloc memory for SFont"));
+
+
+	// allocate a temporary buffer
+	uint8 FontBuf[2048];
+	Dbg_MsgAssert(FontBuf, ("couldn't malloc memory for FontBuf"));
+
+
+	// load file header
+	Len = File::Read(FontBuf, 1, 16, pFile);
+	Dbg_MsgAssert(Len==16, ("couldn't read file header from font file %s", Filename));
+	NumChars			 = ((uint32 *)FontBuf)[1];
+	pFont->DefaultHeight = ((uint32 *)FontBuf)[2] << 4;		// *16 for texel coords
+	pFont->DefaultBase	 = ((uint32 *)FontBuf)[3] << 4;		// *16 for texel coords
+
+
+	// clear character map to zero
+	memset(pFont->Map, 0, 256);
+	memset(pFont->SpecialMap, 0, 32);
+
+
+	// allocate memory for character table
+	pFont->pChars = (SChar *)Mem::Malloc(NumChars*sizeof(SChar));
+	Dbg_MsgAssert(pFont->pChars, ("couldn't malloc memory for font character table"));
+
+
+	// load character map and character table
+	Len = File::Read(FontBuf, 1, NumChars<<2, pFile);
+	Dbg_MsgAssert(Len==(NumChars<<2), ("couldn't read character table in font file %s", Filename));
+
+	for (i=0,pChar=pFont->pChars,pData=FontBuf; iBaseline = ((uint16 *)pData)[0] << 4;		// *16 for texel coords
+		sint16 ascii_val = ((sint16 *)pData)[1];
+		if (ascii_val >= 0)
+			pFont->Map[(uint8) ascii_val] = i;
+		else
+		{
+			Dbg_Assert(ascii_val >= -32)
+			pFont->SpecialMap[(uint8) (-ascii_val - 1)] = i;
+		}
+	}
+
+	// now, if there is a null character in the font, make characters that could not be found
+	// in the font display that instead of 'A'
+	if (pFont->SpecialMap[31] != 0)
+	{
+		for (i = 0; i < 256; i++) 
+		{
+			if (pFont->Map[i] == 0 && i != 'A' && i != 'a') pFont->Map[i] = pFont->SpecialMap[31];
+			if (i < 31 && pFont->SpecialMap[i] == 0) pFont->SpecialMap[i] = pFont->SpecialMap[31];
+		}
+	}	
+	
+	// load texture header
+	Len = File::Read(FontBuf, 1, 16, pFile);
+	Dbg_MsgAssert(Len==16, ("couldn't read texture header from font file %s", Filename));
+	Width  = ((uint16 *)FontBuf)[2];
+	Height = ((uint16 *)FontBuf)[3];
+
+
+	// allocate memory for vif stream
+	pFont->VifSize = 1216 + (((Width*Height+15)>>4)<<4);
+	pFont->pVifData = (uint8 *)Mem::Malloc(pFont->VifSize);
+	Dbg_MsgAssert(pFont->pVifData, ("couldn't malloc memory for vif data"));
+	Dbg_MsgAssert(((uint32)pFont->pVifData & 15) == 0, ("vif data not qword aligned"));
+
+
+
+	// build dma data in this buffer
+	dma::pLoc = pFont->pVifData;
+
+	// Calculate memory needed
+	int min_width = Width, min_height = Height;
+	TexMemLayout.getMinSize(min_width, min_height, PSMT8);
+	pFont->m_NumTexVramBlocks = TexMemLayout.getImageSize(min_width, min_height, PSMT8);
+	pFont->m_NumClutVramBlocks = 4;
+
+
+	// set base pointers and texture buffer width
+	pFont->m_CBP = FontVramBase;
+	pFont->m_TBP = FontVramBase + pFont->m_NumClutVramBlocks;
+	TBW = (Width+63) >> 6;
+	if (TBW<2)
+		TBW=2;
+
+
+	// texture upload format:
+	//
+	// GIFtag PACKED 4
+	// BITBLTBUF ?
+	// TRXPOS 0
+	// TRXREG w,h
+	// TRXDIR 0
+	//
+	// GIFtag IMAGE (w*h+15)/16
+	// ... (tex data)
+
+	// texture upload header
+	NumQWords = (Width*Height+15)>>4;
+	gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+	pFont->mp_TexBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,pFont->m_TBP,TBW,PSMT8));
+	gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+	gs::Reg2(gs::TRXREG,	PackTRXREG(Width,Height));
+	gs::Reg2(gs::TRXDIR,	0);
+	gif::Tag2(0, 0, IMAGE, 0, 0, 0, NumQWords);
+
+	// read texture bitmap data
+	NumBytes = (Width*Height+3)&0xFFFFFFFC;
+	Len = File::Read(dma::pLoc, 1, NumBytes, pFile);
+	Dbg_MsgAssert(Len==NumBytes, ("couldn't read texture bitmap from font file %s", Filename));
+	dma::pLoc += NumQWords<<4;
+
+
+	// clut upload format:
+	//
+	// GIFtag PACKED 4
+	// BITBLTBUF ?
+	// TRXPOS 0
+	// TRXREG w,h
+	// TRXDIR 0
+	//
+	// GIFtag IMAGE 64
+	// ... (clut data)
+
+	// clut upload header
+	gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+	pFont->mp_ClutBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,pFont->m_CBP,1,PSMCT32));
+	gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+	gs::Reg2(gs::TRXREG,	PackTRXREG(16,16));
+	gs::Reg2(gs::TRXDIR,	0);
+	gif::Tag2(0,0,IMAGE,0,0,0,64);
+
+	// read clut bitmap data
+	Len = File::Read(dma::pLoc, 1, 1024, pFile);
+	Dbg_MsgAssert(Len==1024, ("couldn't read clut bitmap from font file %s", Filename));
+	
+	// swizzle the clut data
+	for (i=0; i<256; i+=32)
+		for (j=0; j<8; j++)
+		{
+			temp = ((uint32 *)dma::pLoc)[i+j+8];
+			((uint32 *)dma::pLoc)[i+j+8] = ((uint32 *)dma::pLoc)[i+j+16];
+			((uint32 *)dma::pLoc)[i+j+16] = temp;
+		}
+	
+	// step dma location
+	dma::pLoc += 1024;
+
+
+	// skip numsubtextures, and load subtextures
+	Len = File::Read(FontBuf, 1, (NumChars<<3)+4, pFile);
+	Dbg_MsgAssert(Len==(NumChars<<3)+4, ("couldn't read subtexture table from font file %s", Filename));
+	for (i=0,pChar=pFont->pChars,pData=FontBuf+4; iu0 = (((uint16 *)pData)[0] << 4) + 8;
+		pChar->v0 = (((uint16 *)pData)[1] << 4) + 8;
+		pChar->u1 = pChar->u0 + (((uint16 *)pData)[2] << 4);
+		pChar->v1 = pChar->v0 + (((uint16 *)pData)[3] << 4);
+	}
+
+	// set font GS context
+	for (TW=0; (1<RegTEX0 = PackTEX0(pFont->m_TBP,TBW,PSMT8,TW,TH,1,MODULATE,pFont->m_CBP,PSMCT32,0,0,1);
+	pFont->RegTEX1 = PackTEX1(1,0,LINEAR,LINEAR,0,0,0);
+
+
+	// update vram base pointer
+	//FontVramBase = (((pFont->TBP<<8) + (TBW << 6)*Height + 0x1FFF) & 0xFFFFE000) >> 8;
+	FontVramBase +=	(pFont->m_NumTexVramBlocks + pFont->m_NumClutVramBlocks);
+	Dbg_MsgAssert(FontVramBase < (FontVramStart + FontVramSize), ("Font: 2D VRAM Buffer Full: %x.", FontVramBase));
+
+
+	// add font to font list
+	pFont->pNext = pFontList;
+	pFontList = pFont;
+
+	// we're done with the font file now
+	File::Close(pFile);
+	
+	// And restore the CurrentDMALoc, which we were just using temporarily.
+	dma::pLoc = old_dma_loc;	  
+
+	// this will serve as the default spacing
+	pFont->mSpaceSpacing = (pFont->pChars[pFont->Map['I']].u1 - pFont->pChars[pFont->Map['I']].u0) >> 4;
+	
+	return pFont;
+}
+
+
+
+void UnloadFont(SFont *pFont)
+{
+	SFont *pPrevFont;
+	int found=0;
+
+	// find font and unchain from list
+	if (pFontList == pFont)
+	{
+		found=1;
+		pFontList = pFontList->pNext;
+	}
+	else
+	{
+		for (pPrevFont=pFontList; pPrevFont->pNext; pPrevFont=pPrevFont->pNext)
+		{
+			if (pPrevFont->pNext == pFont)
+			{
+				found=1;
+				pPrevFont->pNext = pFont->pNext;
+				break;
+			}
+		}
+	}
+
+	Dbg_MsgAssert(found, ("attempt to unload font which has not been loaded"));
+
+	// free memory
+	Mem::Free(pFont->pChars);
+	Mem::Free(pFont->pVifData);
+	Mem::Free(pFont);
+
+	// and re-do VRAM allocation
+	VRAMNeedsUpdating();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SFont::ReallocateVRAM()
+{
+	// calculate TBP
+	m_TBP = FontVramBase;
+	FontVramBase += m_NumTexVramBlocks;
+
+	// calculate CBP
+	m_CBP = FontVramBase;
+	FontVramBase += m_NumClutVramBlocks;
+
+	// bail if VRAM would become over-packed
+	if (FontVramBase > (FontVramStart + FontVramSize))
+	{
+		Dbg_MsgAssert(0, ("SingleTexture Realloc: 2D VRAM Buffer Full: %x.", FontVramBase));
+	}
+
+	// Modify the data
+	gs::ModifyTBP(gs::TEX0_1, (uint32 *) &RegTEX0, m_TBP, m_CBP);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SFont::UpdateDMA()
+{
+	// Modify the packets
+	gs::ModifyTBP(gs::BITBLTBUF, mp_TexBitBltBuf, m_TBP);
+	if (mp_ClutBitBltBuf)
+	{
+		gs::ModifyTBP(gs::BITBLTBUF, mp_ClutBitBltBuf, m_CBP);
+	}
+}
+
+uint32 SFont::GetDefaultHeight() const
+{
+	return DefaultHeight;
+}
+
+uint32 SFont::GetDefaultBase() const
+{
+	return DefaultBase;
+}
+
+void SFont::QueryString(char *String, float &width, float &height)
+{
+	SChar *pChar;
+	char *pLetter;
+	uint16 u0,v0,x0,u1,v1,x1;
+
+	x0 = 0;
+
+	//sint16 char_spacing = 16 + (mCharSpacing<<4);
+	sint16 char_spacing = mCharSpacing<<4;
+	sint16 space_spacing = mSpaceSpacing<<4;
+	
+	for (pLetter=String;; pLetter++)
+	{
+		pChar = NULL;
+		// may be overridden by the '\b' tag
+		SFont *p_font = this;
+		
+		// acount for tags (might be multiple ones in a row)
+		bool got_char_tag = false; // tag resulting in output of character
+		while (*pLetter == '\\' && !got_char_tag)
+		{
+			pLetter++;
+			if (*pLetter == '\\')
+				break;
+
+			switch(*pLetter)
+			{
+				case '\\':
+					got_char_tag = true;
+					break;
+				case 'c':
+				case 'C':
+					pLetter += 2; // skip over "c#"
+					break;
+				case 's':
+				case 'S':
+				{
+					pLetter++; // skip "s"
+					uint digit = Str::DehexifyDigit(pLetter);
+					pChar = pChars + SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				case 'b':
+				case 'B':
+				{
+					pLetter++; // skip "b"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					// switch over to buttons font, the regular font will be used again on the next character
+
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				case 'm':
+				case 'M':
+				{
+					pLetter++; // skip "m"
+					char button_char = Nx::CFontManager::sMapMetaCharacterToButton(pLetter);
+					uint digit = Str::DehexifyDigit(&button_char);
+					
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				default:
+					Dbg_MsgAssert(0, ("unknown tag %c", *pLetter));
+					break;
+			}
+		} // end while
+		
+		if (*pLetter == '\0') break;
+
+		if (*pLetter != ' ' || pChar)
+		{
+			if (!pChar)
+				pChar = p_font->pChars + p_font->Map[(uint8)*pLetter];
+			u0 = pChar->u0;
+			v0 = pChar->v0;
+			u1 = pChar->u1;
+			v1 = pChar->v1;
+			x1 = x0+u1-u0;
+		}
+		else
+		{
+			x1 = x0 + space_spacing;
+		}
+		
+		x0 = x1+char_spacing;
+	}
+
+	width  = (float)x0/16.0f;
+	height = (float)DefaultHeight/16.0f;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SText::SText(float pri) : SDraw2D(pri, true)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SText::~SText()
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SText::BeginDraw()
+{
+	//printf("Trying to draw: %s with %x at (%f, %f) of scale %f\n", mp_string, m_rgba, m_xpos, m_ypos, m_scale);
+
+	// Subsequent processing within Draw() will use this font
+	// Draw() may call this function to temporarily switch fonts
+	SFont *p_font = (spOverrideFont) ? spOverrideFont : mp_font;
+	
+	// dma tag
+	dma::BeginTag(dma::cnt, 0);
+
+	vu1::Buffer = vu1::Loc;
+
+	Dbg_Assert(GetScissorWindow());
+
+	// GS context
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::SCISSOR_1, GetScissorWindow()->GetScissorReg());
+	gs::Reg1(gs::TEX0_1,	 p_font->RegTEX0);
+	gs::Reg1(gs::TEX1_1,	 p_font->RegTEX1);
+	gs::Reg1(gs::ALPHA_1,	 PackALPHA(0,1,0,1,0));
+	gs::Reg1(gs::COLCLAMP,	 PackCOLCLAMP(1));
+	gs::Reg1(gs::RGBAQ,	 (uint64)m_rgba);
+	gs::EndPrim(0);
+
+	// unpack giftag
+	vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+	gif::BeginTagImmediate(gs::UV|gs::XYZ2<<4, 2, PACKED, SPRITE|TME|FST|ABE, 1, VU1_ADDR(GSPrim));
+
+	// begin unpack for uv/xyz's
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SText::Draw()
+{
+	SChar *pChar = NULL;
+	char *pLetter;
+	uint16 u0,v0,u1,v1;
+	uint32 x0,y0,x1,y1,yt,z;
+
+	Dbg_Assert(GetScissorWindow());
+
+	x0 = (int)(m_xpos * s_screen_scale_x * 16.0f) + XOFFSET + ((GetScissorWindow()->GetScissorReg() & 0x7FF) << 4);
+	y0 = (int)(m_ypos * s_screen_scale_y * 16.0f) + YOFFSET + ((GetScissorWindow()->GetScissorReg() >> 32 & 0x7FF) << 4);
+	z = s_constant_z_enabled ? s_constant_z : GetZValue();
+
+	//sint16 char_spacing = 16 + (mp_font->mCharSpacing<<4);
+	float char_spacing = (float) (mp_font->mCharSpacing<<4) * m_xscale * s_screen_scale_x;
+	float space_spacing = (float) (mp_font->mSpaceSpacing<<4) * m_xscale * s_screen_scale_x;
+	
+	// XXX
+	//printf("   spacing=%f, %d\n", char_spacing, mp_font->mCharSpacing);
+
+	for (pLetter=mp_string;; pLetter++)
+	{
+		pChar = NULL;
+		SFont *p_font = mp_font;
+		
+		// acount for tags (might be multiple ones in a row)
+		bool got_char_tag = false; // tag resulting in output of character
+		while (*pLetter == '\\' && !got_char_tag)
+		{
+			pLetter++;
+
+			switch(*pLetter)
+			{
+				case '\\':
+					got_char_tag = true;
+					break;
+				case 'c':
+				case 'C':
+				{
+					pLetter++;	// skip "c"					
+					uint digit = Str::DehexifyDigit(pLetter);
+					pLetter++; // skip "#"
+
+					// BIG MIKE	(remove #if below, replace active_color_gs_register_thingy with GS code)
+					#if 1
+					// set active color from font
+					uint32	temp_rgba = m_rgba;
+					if (digit != 0 && !m_color_override)
+					{
+						m_rgba = mp_font->mRGBATab[digit-1];
+					}
+					// This method is not optimal, but it's not done very often
+					// we just turn off text, and turn it on with new colors
+					EndDraw();
+					BeginDraw();
+					m_rgba = temp_rgba;
+					#endif
+
+					break;
+				}
+				case 's':
+				case 'S':
+				{
+					pLetter++;	// skip "s"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					pChar = mp_font->pChars + mp_font->SpecialMap[digit];
+					got_char_tag = true;
+					
+					break;
+				}
+				case 'b':
+				case 'B':
+				{
+					// 'B' stands for button, accesses the button font
+
+					pLetter++; // skip "b"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					// switch to the buttons font!
+					
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					
+					spOverrideFont = p_font;
+					EndDraw();
+					BeginDraw();
+					// we can now return to using the regular font (no override)
+					spOverrideFont = NULL;
+					
+					break;
+				}
+				default:
+					Dbg_MsgAssert(0, ("unknown tag %c", *pLetter));
+					break;
+			}
+		} // end while
+
+		if (*pLetter == '\0') break;
+		
+		if (*pLetter != ' ' || pChar)
+		{
+			if (!pChar)
+				pChar = p_font->pChars + p_font->Map[(uint8) *pLetter];
+			yt = y0 + (int)((float)(p_font->DefaultBase - pChar->Baseline) * m_yscale * s_screen_scale_y);
+			u0 = pChar->u0;
+			v0 = pChar->v0;
+			u1 = pChar->u1;
+			v1 = pChar->v1;
+			x1 = x0+(int)((float)(u1-u0)*m_xscale * s_screen_scale_x);
+			y1 = yt+(int)((float)(v1-v0)*m_yscale * s_screen_scale_y);
+		}
+		else
+		{
+			x0 += (int) (space_spacing + char_spacing);
+			continue;
+		}
+
+		if (!NeedsCulling() || !((x0 | x1 | y0 | y1) & 0xFFFF0000))
+		{
+			vif::StoreV4_32(u0,v0,0,0);
+			vif::StoreV4_32(x0,yt,z,0);
+			vif::StoreV4_32(u1,v1,0,0);
+			vif::StoreV4_32(x1,y1,z,0);
+		}
+
+		x0 = x1 + (int) char_spacing;
+
+		// kick and start a new buffer if this one is full
+		if (((dma::pLoc - gif::pTag)>>4) >= 250)
+		{
+			vif::EndUNPACK();
+			gif::EndTagImmediate(1);
+			vif::MSCAL(VU1_ADDR(Parser));
+			vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+			gif::BeginTagImmediate(gs::UV|gs::XYZ2<<4, 2, PACKED, SPRITE|TME|FST|ABE, 1, VU1_ADDR(GSPrim));
+			vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+		}
+		
+		if (p_font != mp_font)
+		{
+			// we just used the button font, so return to the regular one
+			EndDraw();
+			BeginDraw();
+		}
+	} // end for
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SText::EndDraw(void)
+{
+	vif::EndUNPACK();
+	gif::EndTagImmediate(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	dma::EndTag();
+}
+
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/chars.h b/Code/Gfx/NGPS/NX/chars.h
new file mode 100644
index 0000000..e120f7b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/chars.h
@@ -0,0 +1,94 @@
+#ifndef __CHARS_H
+#define __CHARS_H
+
+#include 
+
+namespace NxPs2
+{
+
+
+typedef struct
+{
+	uint16 u0,v0,u1,v1;
+	uint16 Baseline;
+}
+SChar;
+
+
+struct SFont
+{
+public:
+	uint32		GetDefaultHeight() const;
+	uint32		GetDefaultBase() const;
+
+	void		QueryString(char *String, float &width, float &height);
+
+	void		ReallocateVRAM();
+	void		UpdateDMA();
+
+	//char Name[16];
+	uint32		DefaultHeight, DefaultBase;
+	SChar		*pChars;
+	uint8		Map[256];
+	uint8		SpecialMap[32];
+	uint8		*pVifData;
+	uint32		VifSize;
+	uint64		RegTEX0, RegTEX1;
+	uint32 		*mp_TexBitBltBuf;		// Note these register pointers point to data in the DMA buffer,
+	uint32 		*mp_ClutBitBltBuf;		// so they should only be used for writing, and only after drawing is done.
+	uint16		m_NumTexVramBlocks;
+	uint16		m_NumClutVramBlocks;
+	uint16		m_TBP;
+	uint16		m_CBP;
+	sint16		mCharSpacing;
+	sint16		mSpaceSpacing;
+	uint32		mRGBATab[16];
+	SFont		*pNext;
+};
+
+
+
+SFont *		LoadFont(const char *Filename);
+void		UnloadFont(SFont *);
+void		SetTextWindow(uint16 x0, uint16 x1, uint16 y0, uint16 y1);
+
+
+struct SText : public SDraw2D
+{
+public:
+					SText(float pri = 0.0f);
+	virtual			~SText();
+
+	SFont			*mp_font;
+
+	char			*mp_string;
+	float			m_xpos;
+	float			m_ypos;
+	float			m_xscale;
+	float			m_yscale;
+	uint32			m_rgba;
+	bool			m_color_override;
+	
+	// used in conjunction with BeginDraw()
+	// if set, use specified font instead of mp_font
+	// if not, use mp_font
+	static SFont *	spOverrideFont;
+
+private:
+
+	void			BeginDraw();
+	void			Draw(void);
+	void			EndDraw(void);
+};
+
+extern uint32		FontVramStart;
+extern uint32		FontVramBase;
+extern const uint32	FontVramSize;
+extern SFont		*pFontList;
+extern SFont *		pButtonsFont;
+
+
+} // namespace NxPs2
+
+
+#endif // __CHARS_H
diff --git a/Code/Gfx/NGPS/NX/dma.cpp b/Code/Gfx/NGPS/NX/dma.cpp
new file mode 100644
index 0000000..b77cca0
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/dma.cpp
@@ -0,0 +1,1267 @@
+#include 
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gs.h"
+#include "group.h"
+#include "render.h"
+#include "vu1code.h"
+#include "switches.h"
+#include "geomnode.h"
+
+/*
+
+ Mick Notes:
+
+ The calls the the DMA functions suggest that you are immediately executing something.
+ You are not.  You are simply building a list of DMA commands that get executed the next frame
+ 
+ The DMA list is the primary mechanism for initiating rendering on the PS2.
+ 
+ To render something (like a line), you "simply" need to generate the appropiate DMA
+ packets that contain GIF packets, containing GS primitives, and then link this into the 
+ main DMA list.
+ 
+ Things such as world sectors have pre-build DMA lists that contain commands
+ to upload lists of vertices to VU1 micro-memory, and then upload lists of
+ triangles, and then trigger the appropiate VU1 microcode to transform and render them.
+ 
+ These pre-built lists are linked in each frame with the appropiate transformation 
+ matricies that are needed based on the current camera position and the current
+ object position (if it is a moving object)
+ 
+ Thus, each object is mostly pre-build, and so rendering it requires very little CPU time.
+
+ Each frame, we build a series of dynamic DMA lists.
+ 
+ There are two lists for each group (see group.cpp).
+ 
+ The first list is the texture upload.  This is pretty simple, just uploading a few textures.
+ Texture uploading happens asyncrnously with rendering, so we can be executing a DMA list
+ of rendering stuff at the same time as we are uploading the textures for the next group.   
+
+ A lot of the DMA code is in DMA.H, as it is small inline functions.  
+
+ The Runtime buffer is dynamically allocated in InitializeEngine().  Last time I looked it
+ was 512K in size (note though, this is increased to 16MB for the debugging wireframe mode)
+ 
+ The Runtime DMA lists are double buffered. You are executing one whilst building the next one.
+ 
+ The pRunTimeBuffer is split in two, and pointers to the base of each half are stored in 
+ pList[0] and pList[1]   
+
+ The integer varaible "Field" is 0 or 1, and indicates which field (odd or even) we are rendering to.  
+ 
+ The DMA lists are build using a global variable char * pLoc.  
+ 
+ At the start of a frame, pLoc is initilized like:
+ 
+ 	dma::pLoc = dma::pList[Field];
+
+ So it points to the start of one of our 256K DMA buffers (remember the other is being executed)
+ 
+ DMA packets are now built using calls to the functions here and in DMA.H, for example: 
+ 
+	dma::Tag(dma::ref, ((uint8 *)&MPGEnd-(uint8 *)&MPGStart+15)/16, (uint)&MPGStart);
+	vif::NOP();
+	vif::NOP();
+	
+ (the above simply transfers the microcode from RAM to VU1 micromem)	
+																					   
+ Now, it's important to note that DMA execution does NOT simply start at pList[Field] and
+ then run all the way through a frame's worth of data.  There are actually several DMA lists,
+ which are fired off individually.  Some of these (the groups) are logically connected so that 
+ the end of one group's DMA (and the GS/VU activity it triggers) causes an interrupt,
+ which the CPU handles by starting the DMA for another group (see interrupt.cpp)
+
+ After the groups, there is the "immediate mode" DMA list, triggered by pEpilogue->pRender[Field]
+ this is simply a raw DMA list that you can put anything you want into.  It starts at the value
+ of pLoc after RenderWorld has been called (a terrible hack, as noted in the code :)
+ 
+ All of the "immediate mode" rendering is considered to be part of pEpilogue.  It is terminated 
+ by the RenderEpilogue() function, which just links in a final interrupt trigger, which will 
+ get picked up by GsHandler(), and sGroup::pRenderGroup will be set to NULL, which is what
+ WaitForRendering() uses to determine if rendering has finished.
+ 
+ 						  
+						  
+
+*/
+
+
+
+namespace NxPs2
+{
+
+
+
+// begin a subroutine
+
+void dma::BeginSub(eTag ID)
+{
+	Align(0,16);
+	pSub = pLoc;
+	dma::ID = ID;
+	vu1::Loc = vu1::Buffer = 0;
+}
+
+
+// end a subroutine and return its address
+
+uint64 dma::EndSub(void)
+{
+	uint64 GosubTag;
+	Align(0,16);
+	GosubTag = ((uint64)pSub<<32) | ((uint64)ID<<28) | (vu1::Loc & 0x3FF) << 16;
+	Dbg_MsgAssert(ID!=refe && ID!=refs, ("refe and refs not supported in dma tag!"));
+	if (ID==ref)
+	{
+		GosubTag |= (pLoc - pSub) >> 4;
+	}
+	return GosubTag;
+}
+
+
+// call a subroutine (using a dma::call or a dma::ref)
+
+void dma::Gosub(uint Num, uint Path)
+{
+	register uint64 Tag = Gosubs[Num];
+
+	Store64(Tag);
+
+	switch (Path)
+	{
+	case 1:
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		vu1::Loc += (Tag>>16) & 0x3FF;			// VUMem size
+		break;
+
+	case 2:
+		vif::NOP();
+		vif::NOP();
+		break;
+
+	case 3:
+		pLoc += 8;								// no need to write anything since TTE=0
+		break;
+
+	#ifdef __PLAT_NGPS__
+	default:
+		printf("error: dma::Gosub() called with unrecognised path number\n");
+		exit(1);
+	#endif
+	}
+
+}
+
+
+
+// begin a 3D subroutine
+
+void dma::BeginSub3D(void)
+{
+	Align(0,16);
+	pSub = pLoc;
+	vu1::Loc = vu1::Buffer = 0;
+}
+
+
+// end a 3D subroutine and return its address
+
+uint8 *dma::EndSub3D(void)
+{
+	((uint16 *)pSub)[1] |= vu1::Loc&0x3FF;
+	return pSub;
+}
+
+
+// call a 3D subroutine (always using a dma::call)
+
+void dma::Gosub3D(uint8 *pSub, uint RenderFlags)
+{
+	BeginTag(call,(uint)pSub);
+	vif::BASE(vu1::Loc);
+	vif::OFFSET(0);
+	vif::ITOP(RenderFlags);
+	EndTag();
+	vu1::Loc += ((uint16 *)pSub)[1];
+}
+
+
+
+// dma list traversal function
+uint8 *dma::NextTag(uint8 *pTag, bool stepInto)
+{
+	Dbg_MsgAssert((*(uint32 *)pTag&0x80000000)==0, ("IRQ bit set in dma tag"));
+	Dbg_MsgAssert(*(uint32 *)pTag!=refe<<28 && *(uint32 *)pTag!=refs<<28, ("refe and refs not supported in dma tag!"));
+	switch (*(uint32 *)pTag>>28)
+	{
+	case cnt:
+		return pTag + 16 + ((uint)((uint16 *)pTag)[0] << 4);
+
+	case next:
+		return ((uint8 **)pTag)[1];
+
+	case ref:
+		return pTag + 16;
+
+	case call:
+		Dbg_MsgAssert(sp<2, ("dma call stack overflow"));
+		if (stepInto)
+		{
+			Stack[sp++] = pTag + 16 + ((uint)((uint16 *)pTag)[0] << 4);
+			return ((uint8 **)pTag)[1];
+		}
+		else
+		{
+			return pTag + 16 + ((uint)((uint16 *)pTag)[0] << 4);
+		}
+
+	case ret:
+		Dbg_MsgAssert(sp>0, ("dma call stack underflow"));
+		return Stack[--sp];
+
+	case end:
+	default:
+		return NULL;
+	}
+}
+
+
+#ifdef __PLAT_NGPS__
+
+// auxilliary comparison function for dma sort
+int dma::Cmp(const void *p1, const void *p2)
+{
+	return ((SSortElement *)p1)->z < ((SSortElement *)p2)->z ? -1 :
+		   ((SSortElement *)p1)->z > ((SSortElement *)p2)->z ? +1 :
+																0 ;
+}
+
+
+#if 0
+
+// original version
+
+// sort a dma list of mesh packets on z
+// the z is stored in the unused ADDR word of the first cnt tag of the mesh packet
+
+uint8 *dma::SortGroup(uint8 *pList)
+{
+	int num_elements;
+	SSortElement *p_element;
+	uint8 *p_tag, *p_end_tag, *p_prev_tag=NULL;
+	eTag ID;
+
+	// set array base at start of scratchpad
+	SSortElement *p_array = (SSortElement *)0x70000000;
+
+	// copy the addresses and z-values into array
+	p_element = p_array;
+	p_tag = pList;
+	sp = 0;				// starting at top level of dma list
+    while ((ID = (eTag)(*(uint32 *)p_tag>>28)) != end)
+	{
+		if (ID == cnt)
+		{
+			p_element->address = p_tag;
+			p_element->z = ((float *)p_tag)[1];
+			p_element++;
+		}
+		p_tag = NextTag(p_tag, false);
+	}
+
+	// check array fits within scratchpad
+	num_elements = p_element-p_array;
+	Dbg_MsgAssert(num_elements*sizeof(SSortElement)<=16384, ("Can't fit array in scratchpad"));
+
+	// record address of end tag
+	p_end_tag = p_tag;
+
+	// sort the array
+	qsort(p_array, num_elements, sizeof(SSortElement), Cmp);
+
+	// reorder the dma list according to the sorted array
+	for (p_element=p_array; p_elementaddress;
+
+		do
+		{
+			p_prev_tag = p_tag;
+			p_tag = NextTag(p_tag, false);
+			ID = (eTag)(*(uint32 *)p_tag >> 28);
+		} while (ID!=cnt && ID!=end);
+
+		((uint8 **)p_prev_tag)[1] = (p_element+1)->address;
+	}
+
+	// patch up the final dma::next tag to point to the dma::end tag
+	((uint8 **)p_prev_tag)[1] = p_end_tag;
+
+	// chain through the whole list to adjust vu1 base pointers
+	vu1::Loc = 0;
+	for (p_tag = p_array->address; *(uint32 *)p_tag>>28 != end; p_tag = NextTag(p_tag, false))
+	{
+		//if (*(uint32 *)p_tag & 0x03FF0000)
+		if (((uint8 *)p_tag)[11] == 0x03)
+		{
+			((uint16 *)p_tag)[4] &= ~0x3FF;
+			((uint16 *)p_tag)[4] |= vu1::Loc;
+			vu1::Loc += ((uint16 *)p_tag)[1];
+		}
+	}
+
+	// return the address of the head tag
+	return p_array->address;
+}
+
+#else
+
+// new version:
+// the list is sorted in segments corresponding to viewports
+
+// sort a dma list of mesh packets on z
+// the z is stored in the unused ADDR word of the first cnt tag of the mesh packet
+
+uint8 *dma::SortGroup(uint8 *pList)
+{
+	int num_elements, i, num_segments;
+	SSortElement *p_element, *p_segment[4];
+	uint8 *p_tag, *p_end_tag, *p_prev_tag=NULL;
+	eTag ID;
+
+	// set array base at start of scratchpad
+	SSortElement *p_array = (SSortElement *)0x70000000;
+
+	// copy the addresses and z-values into array
+	p_element = p_array;
+	p_tag = pList;
+	sp = 0;				// starting at top level of dma list
+	num_segments = 0;
+    while ((ID = (eTag)(*(uint32 *)p_tag>>28)) != end)
+	{
+		for (i=0; iaddress = p_tag;
+			p_element->z = ((float *)p_tag)[1];
+			p_element++;
+		}
+		p_tag = NextTag(p_tag, false);
+	}
+
+	// check array fits within scratchpad
+	num_elements = p_element-p_array;
+	Dbg_MsgAssert(num_elements*sizeof(SSortElement)<=16384, ("Can't fit array in scratchpad"));
+
+	// record address of end tag
+	p_end_tag = p_tag;
+
+	// sort the array in segments
+	if (num_segments)
+	{
+		for (i=0; iaddress;
+
+		do
+		{
+			p_prev_tag = p_tag;
+			p_tag = NextTag(p_tag, false);
+			ID = (eTag)(*(uint32 *)p_tag >> 28);
+		} while (ID!=cnt && ID!=end);
+
+		((uint8 **)p_prev_tag)[1] = (p_element+1)->address;
+	}
+
+	// patch up the final dma::next tag to point to the dma::end tag
+	((uint8 **)p_prev_tag)[1] = p_end_tag;
+
+	// chain through the whole list to adjust vu1 base pointers
+	vu1::Loc = 0;
+	for (p_tag = p_array->address; *(uint32 *)p_tag>>28 != end; p_tag = NextTag(p_tag, false))
+	{
+		if (*(uint32 *)p_tag & 0x3FF0000)
+		{
+			((uint16 *)p_tag)[4] &= ~0x3FF;
+			((uint16 *)p_tag)[4] |= vu1::Loc;
+			vu1::Loc += ((uint16 *)p_tag)[1];
+		}
+	}
+
+	// return the address of the head tag
+	return p_array->address;
+}
+
+#endif
+
+
+#endif
+
+
+void dma::BeginList(void *pGroup)
+{
+#ifdef __PLAT_NGPS__
+	// assume group isn't used
+	((sGroup *)pGroup)->Used[render::Field] = false;
+
+	// set the dma list pointer
+	((sGroup *)pGroup)->pRender[render::Field] = pLoc;
+
+	// VIF1 and VU1 setup
+	BeginTag(cnt, 0xFF000000);		// bit of a cheat, so it will stay at the start of any sorted list
+	vif::FLUSH();
+	vif::STMASK(0);
+	vif::STMOD(0);
+	vif::STCYCL(1,1);
+	vif::BASE(0);
+	vif::OFFSET(0);
+	vif::MSCAL(VU1_ADDR(Setup));
+	EndTag();
+
+	dma::Tag(dma::next, 0, 0);
+	vif::NOP();
+	vif::NOP();
+
+	((sGroup *)pGroup)->vu1_loc = 0;
+	((sGroup *)pGroup)->p_tag = pTag;
+#endif
+}
+
+
+void dma::EndList(void *pGroup)
+{
+
+	SetList(pGroup);
+
+	// end dma list for this group
+	BeginTag(end, 0);
+	#if USE_INTERRUPTS
+	//vif::BASE(((sGroup *)pGroup)->vu1_loc);
+	vif::BASE(vu1::Loc);
+	vif::OFFSET(0);
+	vu1::Loc = 0;   							// must do this as a relative prim for a sortable list...
+	gs::BeginPrim(REL,0,0);
+	gs::Reg1(gs::SIGNAL, PackSIGNAL(1,1));		// signal the end of rendering this group
+	gs::EndPrim(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	#endif
+	EndTag();
+	((uint16 *)pTag)[1] |= vu1::Loc & 0x3FF;	// must write some code for doing this automatically
+}
+
+
+void dma::ReallySetList(void *pGroup)
+{
+
+	// finish with the previous dma context
+	if (sp_group)
+	{
+		// ensure the last tag was a 'next'...
+
+		// get the tag ID
+		uint ID = *(uint32 *)pTag>>28;
+
+		// take care of 'refe' and 'refs'
+		Dbg_MsgAssert(ID!=refe && ID!=refs, ("refe and refs not supported in dma tag!"));
+
+		// take care of 'call' and 'ref'
+		if (ID==call || ID==ref)
+		{
+			Tag(next, 0, 0);
+			vif::NOP();
+			vif::NOP();
+		}
+
+		// take care of 'cnt'
+		else if (ID==cnt)
+		{
+			pTag[3] = next<<4;
+		}
+
+		// 'end' and 'ret' won't have anything after them in the same context
+		// and 'next' is fine as it is
+
+		// save the vu1 location and dma tag location
+		((sGroup *)sp_group)->vu1_loc = vu1::Loc;
+		((sGroup *)sp_group)->p_tag = pTag;
+	}
+
+	// change bucket
+	sp_group = pGroup;
+
+	// set up the new bucket
+	if (pGroup)
+	{
+		// restore the vu1 location and dma tag location
+		vu1::Loc = ((sGroup *)pGroup)->vu1_loc;
+		pTag = ((sGroup *)pGroup)->p_tag;
+
+		// patch the pointer of the dangling 'next' tag
+		((uint32 *)pTag)[1] = (uint32)pLoc;
+	}
+}
+
+
+
+
+int dma::GetDmaSize(uint8 *pTag)
+{
+	return (*(uint16 *)pTag + 1) << 4;	// (QWC+1)*16 bytes
+}
+
+
+
+int dma::GetNumVertices(uint8 *pTag)
+{
+	// get start and end of dma packet
+	uint8 *p_start = pTag + 8;
+	uint8 *p_end   = pTag + 16 + (*(uint16 *)pTag << 4);
+
+	// parse vifcodes
+	uint8 *p_code = p_start;
+	int   num_verts = 0;
+	do
+	{
+		if (((p_code[3] & 0x7F) == 0x05) && (p_code[0] == 1))		// look for STMOD(1)
+		{
+			p_code = vif::NextCode(p_code);
+			Dbg_MsgAssert((p_code[3] & 0x7E)==0x6C, ("0x%08X: expected UNPACK V4_16 or V4_32", *(uint32 *)p_code));
+			num_verts += p_code[2];
+		}
+
+		p_code=vif::NextCode(p_code);
+	}
+	while (p_code < p_end);
+
+	return num_verts;
+}
+
+
+
+int dma::GetNumTris(uint8 *pTag)
+{
+	// get start and end of dma packet
+	uint8 *p_start = pTag + 8;
+	uint8 *p_end   = pTag + 16 + (*(uint16 *)pTag << 4);
+
+	// parse vifcodes
+	uint8 *p_code = p_start;
+	int   num_tris = 0;
+	int   num_verts;
+	do
+	{
+		if (((p_code[3] & 0x7F) == 0x05) && (p_code[0] == 1))		// look for STMOD(1)
+		{
+			p_code = vif::NextCode(p_code);
+			Dbg_MsgAssert((p_code[3] & 0x7E)==0x6C, ("0x%08X: expected UNPACK V4_16 or V4_32", *(uint32 *)p_code));
+			num_verts = p_code[2];
+
+			// loop over verts, counting the adc bits which are zero
+			if (p_code[3] & 0x01)	// V4_16
+			{
+				uint16 *p_adc = ((uint16 *)p_code)+5;
+				for (int i=0; i> 2;
+			for (i=0; i> (p_code[3] & 0x03);
+			break;
+		}
+
+		p_code=vif::NextCode(p_code);
+	}
+	while (p_code < p_end);
+
+	return bit_length;
+}
+
+
+void dma::ExtractXYZs(uint8 *pTag, uint8 *pArray)
+{
+	// get start and end of dma packet
+	uint8 *p_start = pTag + 8;
+	uint8 *p_end   = pTag + 16 + (*(uint16 *)pTag << 4);
+
+	// parse vifcodes
+	uint8 *p_code = p_start;
+	int i, num_words;
+	sint32 *p_dest = (sint32 *)pArray;
+
+	do
+	{
+		if ((*(uint32 *)p_code & 0x7F000001) == 0x05000001)
+		{
+			p_code = vif::NextCode(p_code);
+			Dbg_MsgAssert((p_code[3] & 0x7E)==0x6C, ("0x%08X: expected UNPACK V4_16 or V4_32", *(uint32 *)p_code));
+
+			num_words = (int)((((uint32)p_code[2]-1)&0xFF)+1) << 2;
+
+			if ((p_code[3] & 0x7F) == 0x6C)
+			{
+				// 32 bit
+				sint32 *p_source = (sint32 *)(p_code+4);
+				for (i=0; i1 && seenMSCAL)
+			{
+				// look for giftag
+				if (unpack_num==-1)
+				{
+					p_giftag = (uint32 *)p_code + 1;
+					nloop = p_giftag[0] & 0x7FFF;
+					nreg  = p_giftag[1] >> 28;
+				}
+
+				// look for vertex elements
+				else
+				{
+					p_unpack[unpack_num] = p_code;
+				}
+
+				// next element
+				unpack_num++;
+
+			}
+
+		}
+		else
+		{
+			if ((p_code[3] & 0x7F) == 0x14)		// mscal
+			{
+				seenMSCAL = true;
+			}
+		}
+
+		// step to next vifcode
+		p_code = vif::NextCode(p_code);
+
+		// have we found all 5 unpacks of a vertex packet?
+		if (unpack_num==5)
+		{
+			// perform compression...
+
+			// set element pointers
+			p_texcrds_source = p_texcrds_dest = (uint32 *)(p_unpack[0]+4);
+			p_weights_source = p_weights_dest = (uint8  *)(p_unpack[1]+4);
+			p_normal_source	 = p_normal_dest  = (uint16 *)(p_unpack[2]+4);
+			p_colour_source	 = p_colour_dest  = (uint32 *)(p_unpack[3]+4);
+			p_coords_source	 = p_coords_dest  = (uint32 *)(p_unpack[4]+4);
+
+			// set datasize for tex coords
+			texcrds_size = ((p_unpack[0][3] & 0x07) == 0x04) ? 2 : 1;
+
+			// loop over source vertices
+			num_squeezed = 0;
+			for (i=0; i> 4;
+}
+
+
+
+
+//---------------------------------
+//		S T A T I C   D A T A
+//---------------------------------
+
+uint8 *		dma::pBase;			// base of dynamic DMA buffer for this frame
+uint8 *		dma::pLoc;			// current position in it that we are building DMA packets
+uint8 *		dma::pTag;
+uint8 *		dma::pPrebuiltBuffer;
+uint8 *		dma::pDummyBuffer;		// (Mick) used to simulate memory usage
+uint8 *		dma::pRuntimeBuffer;
+uint8 *		dma::pList[2];
+uint64 *	dma::Gosubs;
+uint8 *		dma::pSub;
+dma::eTag	dma::ID;
+int    		dma::sp;
+uint8 *		dma::Stack[2];
+void *		dma::sp_group;
+int			dma::size;
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/dma.h b/Code/Gfx/NGPS/NX/dma.h
new file mode 100644
index 0000000..59151da
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/dma.h
@@ -0,0 +1,321 @@
+#ifndef __DMA_H
+#define __DMA_H
+
+#include "render.h"
+
+
+namespace NxPs2
+{
+
+
+class CGeomNode;
+
+
+
+#define PREBUILT_DMA_BUFFER_SIZE 32768		    		
+
+// Random crashes in the engine can frequntly be traced to DMA buffer overflow
+// We probably need to add some cheking for this.
+//#define SPLIT_SCREEN_DMA_BUFFER_SIZE  	(675000 * 2)			// split screen needs more
+//#define NON_DEBUG_DMA_BUFFER_SIZE  		(500000 * 2)			// non split screen needs less
+#define NON_DEBUG_DMA_BUFFER_SIZE  		((int)((675000 * 2)&0xffffffe0))			// non split screen needs less
+#define DEBUG_DMA_BUFFER_SIZE  			((int)(16256000 & 0xffffffe0))				// Mick, 12 MB for debugging....
+
+class dma
+{
+
+public:
+
+	enum eTag
+	{	refe = 0,
+		cnt  = 1,
+		next = 2,
+		ref  = 3,
+		refs = 4,
+		call = 5,
+		ret  = 6,
+		end  = 7
+	};
+
+	struct SSortElement
+	{
+		float z;
+		uint8 *address;
+	};
+
+	//-------------------------------------------
+	//		S T A T I C   F U N C T I O N S
+	//-------------------------------------------
+
+	static void   Align(uint Offset, uint Boundary);
+	static void   Align();
+	static void   Store32(uint32 Data);
+	static void   Store32(uint32 Data1, uint32 Data2 );
+	static void   Store32(uint32 Data1, uint32 Data2, uint32 Data3 );
+	static void   Store32(uint32 Data1, uint32 Data2, uint32 Data3, uint32 Data4 );
+	static void   Store64(uint64 Data);
+	static void   Tag(eTag ID, uint QWC, uint ADDR);
+	static void   BeginTag(eTag ID, uint ADDR);
+	static void   EndTag(void);
+	static void   BeginSub(eTag ID);
+	static uint64 EndSub(void);
+	static void   Gosub(uint Num, uint Path);
+	static void   BeginSub3D(void);
+	static uint8 *EndSub3D(void);
+	static void   Gosub3D(uint8 *pSub, uint RenderFlags);
+	static uint8 *NextTag(uint8 *pTag, bool stepInto);
+	static int    Cmp(const void *p1, const void *p2);
+	static uint8 *SortGroup(uint8 *pList);
+	static void   BeginList(void *pGroup);
+	static void   EndList(void *pGroup);
+	static void   SetList(void *pGroup);
+	static void   ReallySetList(void *pGroup);
+	static int    GetDmaSize(uint8 *pTag);
+	static int    GetNumVertices(uint8 *pTag);
+	static int    GetNumTris(uint8 *pTag);
+	static void   Copy(uint8 *pTag, uint8 *pDest);
+	static int    GetBitLengthXYZ(uint8 *pTag);
+	static void   TransferValues(uint8 *pTag, uint8 *pArray, int size, int dir, uint32 vifcodeMask, uint32 vifcodePattern);
+	static void   TransferColours(uint8 *pTag, uint8 *pArray, int dir);
+	static void   ExtractXYZs(uint8 *pTag, uint8 *pArray);
+	static void   ReplaceXYZs(uint8 *pTag, uint8 *pArray, bool skipW = false);
+	static void   ExtractRGBAs(uint8 *pTag, uint8 *pArray);
+	static void   ReplaceRGBAs(uint8 *pTag, uint8 *pArray);
+	static void   ExtractSTs(uint8 *pTag, uint8 *pArray);
+	static void   ReplaceSTs(uint8 *pTag, uint8 *pArray);
+	static void   TransformSTs(uint8 *pTag, const Mth::Matrix &mat);
+	static void	  ConvertXYZToFloat(Mth::Vector &vec, sint32 *p_xyz);
+	static void	  ConvertXYZToFloat(Mth::Vector &vec, sint32 *p_xyz, const Mth::Vector ¢er);
+	static void	  ConvertXYZToFloat(Mth::Vector &vec, sint32 *p_xyz, const sint32 *p_center);
+	static void	  ConvertSTToFloat(float & s, float & t, sint32 *p_st);
+	static void	  ConvertFloatToXYZ(sint32 *p_xyz, Mth::Vector &vec);
+	static void	  ConvertFloatToXYZ(sint32 *p_xyz, Mth::Vector &vec, const Mth::Vector ¢er);
+	static void	  ConvertFloatToXYZ(sint32 *p_xyz, Mth::Vector &vec, const sint32 *p_center);
+	static void	  ConvertFloatToST(sint32 *p_st, float & s, float & t);
+	static void   SqueezeADC(uint8 *pTag);
+	static void   SqueezeNOP(uint8 *pTag);
+
+
+	//---------------------------------
+	//		S T A T I C   D A T A
+	//---------------------------------
+
+	static uint8 *		pBase;
+	static uint8 *		pLoc;
+	static uint8 *		pTag;
+	static uint8 *		pPrebuiltBuffer;
+	static uint8 *		pDummyBuffer;
+	static uint8 *		pRuntimeBuffer;
+	static uint8 *		pList[2];
+	static uint64 *		Gosubs;
+	static uint8 *		pSub;
+	static eTag			ID;
+	static int			sp;
+	static uint8 *		Stack[2];
+	static void *		sp_group;
+	static int			size;
+
+}; // class dma
+
+
+// -------------------------------------------------
+//        INLINE FUNCTIONS
+// -------------------------------------------------
+
+
+
+// align to Boundary, then add Offset
+// Boundary must be a power of 2
+
+inline void dma::Align(uint Offset, uint Boundary)
+{
+	uint8 *NewDmaLoc = (uint8 *)((((uint)pLoc - Offset + Boundary - 1) & ((uint)(-(int)Boundary))) + Offset);
+	while (pLoc < NewDmaLoc)
+		*pLoc++ = 0;
+}
+
+
+// quick version for dma list building; assumes pLoc already word-aligned and you want it quadword-aligned
+
+inline void dma::Align()
+{
+	while ((uint)pLoc & 0xF)
+	{
+		*(uint32 *)pLoc = 0;
+		pLoc += 4;
+	}
+}
+
+
+
+
+
+// store a word
+
+inline void dma::Store32(uint32 Data)
+{
+	#if 0
+	*(uint32 *)pLoc = Data;
+	pLoc += 4;
+	#else
+	uint32 *p_loc = (uint32*)pLoc;
+	p_loc[0] = Data;
+	pLoc = (uint8*) (p_loc + 1);
+	#endif	
+}
+
+// Store two words, quicker this way, as we only have to update pLoc once
+inline void dma::Store32(uint32 Data1, uint32 Data2)
+{
+	uint32 *p_loc = (uint32*)pLoc;
+	p_loc[0] = Data1;
+	p_loc[1] = Data2;
+	pLoc = (uint8*) (p_loc + 2);
+}
+
+// Store three words, quicker this way, as we only have to update pLoc once
+inline void dma::Store32(uint32 Data1, uint32 Data2, uint32 Data3)
+{
+	uint32 *p_loc = (uint32*)pLoc;
+	p_loc[0] = Data1;
+	p_loc[1] = Data2;
+	p_loc[2] = Data3;
+	pLoc = (uint8*) (p_loc + 3);
+}
+
+// Store four words, quicker this way, as we only have to update pLoc once
+inline void dma::Store32(uint32 Data1, uint32 Data2, uint32 Data3, uint32 Data4)
+{
+	uint32 *p_loc = (uint32*)pLoc;
+	p_loc[0] = Data1;
+	p_loc[1] = Data2;
+	p_loc[2] = Data3;
+	p_loc[3] = Data4;
+	pLoc = (uint8*) (p_loc + 4);
+}
+
+
+// store a dword
+
+inline void dma::Store64(uint64 Data)
+{
+	((uint32 *)pLoc)[0] = (uint32)Data;		// break into 2 words
+	((uint32 *)pLoc)[1] = (uint32)(Data>>32);	// because pLoc is only word-aligned
+	pLoc += 8;
+}
+
+
+
+//--------------------------
+//		D M A   T A G S
+//--------------------------
+
+
+
+// generic source chain tag
+//
+//	 63  62                        32 31  30  28 27 26 25     16 15            0
+//	ÚÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÂÄÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+//	³SPR³          ADDR          0000³IRQ³  ID  ³ PCE ³    -    ³      QWC      ³
+//	ÀÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+inline void dma::Tag(eTag ID, uint QWC, uint ADDR)
+{
+	// assumes pLoc already qword aligned
+	pTag = pLoc;
+	Store32(ID<<28 | QWC, ADDR);
+}
+
+
+// Begin-End style
+
+inline void dma::BeginTag(eTag ID, uint ADDR)
+{
+	// assumes pLoc already qword aligned
+	pTag = pLoc;									// record tag location for patching later
+	Store32(ID<<28, ADDR);
+}
+
+
+// Begin-End style
+
+inline void dma::EndTag(void)
+{
+	uint ID = (*(uint32 *)pTag >> 28) & 7;				// get the tag ID field
+	Align();											// align to qword boundary
+	if (ID!=ref && ID!=refe && ID!=refs)				// check that the QWC is patchable
+	{
+		((uint16 *)pTag)[0] = (pLoc - pTag - 8) >> 4;
+	}
+}
+
+
+inline void	 dma::ConvertXYZToFloat(Mth::Vector &vec, sint32 *p_xyz)
+{
+	vec[X] = ((float) *(p_xyz++)) * RECIPROCAL_SUB_INCH_PRECISION;
+	vec[Y] = ((float) *(p_xyz++)) * RECIPROCAL_SUB_INCH_PRECISION;
+	vec[Z] = ((float) *(p_xyz)  ) * RECIPROCAL_SUB_INCH_PRECISION;
+	vec[W] = 1.0f;
+}
+
+inline void	 dma::ConvertXYZToFloat(Mth::Vector &vec, sint32 *p_xyz, const Mth::Vector ¢er)
+{
+	vec[X] = (((float) *(p_xyz++)) * RECIPROCAL_SUB_INCH_PRECISION) + center[X];
+	vec[Y] = (((float) *(p_xyz++)) * RECIPROCAL_SUB_INCH_PRECISION) + center[Y];
+	vec[Z] = (((float) *(p_xyz)  ) * RECIPROCAL_SUB_INCH_PRECISION) + center[Z];
+	vec[W] = 1.0f;
+}
+
+inline void	 dma::ConvertXYZToFloat(Mth::Vector &vec, sint32 *p_xyz, const sint32 *p_center)
+{
+	vec[X] = ((float) (*(p_xyz++) + *(p_center++))) * RECIPROCAL_SUB_INCH_PRECISION;
+	vec[Y] = ((float) (*(p_xyz++) + *(p_center++))) * RECIPROCAL_SUB_INCH_PRECISION;
+	vec[Z] = ((float) (*(p_xyz)   + *(p_center)  )) * RECIPROCAL_SUB_INCH_PRECISION;
+	vec[W] = 1.0f;
+}
+
+inline void	 dma::ConvertSTToFloat(float & s, float & t, sint32 *p_st)
+{
+	s = ((float) *(p_st++)) * (1.0f / 4096.0f);
+	t = ((float) *(p_st)  ) * (1.0f / 4096.0f);
+}
+
+inline void	 dma::ConvertFloatToXYZ(sint32 *p_xyz, Mth::Vector &vec)
+{
+	*(p_xyz++) = (sint32) (vec[X] * SUB_INCH_PRECISION);
+	*(p_xyz++) = (sint32) (vec[Y] * SUB_INCH_PRECISION);
+	*(p_xyz)   = (sint32) (vec[Z] * SUB_INCH_PRECISION);
+}
+
+inline void	 dma::ConvertFloatToXYZ(sint32 *p_xyz, Mth::Vector &vec, const sint32 *p_center)
+{
+	*(p_xyz++) = ((sint32) (vec[X] * SUB_INCH_PRECISION)) - *(p_center++);
+	*(p_xyz++) = ((sint32) (vec[Y] * SUB_INCH_PRECISION)) - *(p_center++);
+	*(p_xyz)   = ((sint32) (vec[Z] * SUB_INCH_PRECISION)) - *(p_center);
+}
+
+inline void	 dma::ConvertFloatToST(sint32 *p_st, float & s, float & t)
+{
+	*(p_st++) = (sint32) (s * 4096.0f);
+	*(p_st)   = (sint32) (t * 4096.0f);
+}
+
+inline void dma::SetList(void *pGroup)
+{
+	// do nothing if we're already in the right dma context
+	// (Mick) This part has been moved to an inline function 
+	// since the overhead of setting up the stack frame 
+	// is large, this is a more efficient way of taking advantage of
+	// group coherency.   This represents a 25% speed improvmeent for this function
+	// just 0.25% of a frame.  But every little helps.
+	if (sp_group != pGroup)
+	{
+		ReallySetList(pGroup);
+	}
+}
+
+} // namespace NxPs2
+
+
+#endif // __DMA_H
+
diff --git a/Code/Gfx/NGPS/NX/dmacalls.cpp b/Code/Gfx/NGPS/NX/dmacalls.cpp
new file mode 100644
index 0000000..23a759d
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/dmacalls.cpp
@@ -0,0 +1,472 @@
+#include 
+#include 
+#include 
+#include 
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "material.h"
+#include "dmacalls.h"
+#include "vu1code.h"
+#include "switches.h"
+#include "render.h"
+
+namespace NxPs2
+{
+
+
+uint8 *pSubroutine;
+
+
+uint32	*p_patch_PRMODE;
+uint32	*p_patch_ALPHA;
+uint32	*p_patch_ALPHA2;
+
+//-----------------------------------------------------------
+//		H A N D L I N G   D M A   S U B R O U T I N E S
+//-----------------------------------------------------------
+
+
+// begin a subroutine
+
+uint8 *BeginDmaSubroutine(void)
+{
+	dma::Align(0,16);
+	pSubroutine = dma::pLoc;
+	vu1::Loc = vu1::Buffer = 0;
+	return pSubroutine;
+}
+
+
+// end a subroutine and return its address
+
+void EndDmaSubroutine(void)
+{
+	((uint16 *)pSubroutine)[1] |= vu1::Loc&0x3FF;
+}
+
+#if 0
+
+// call a subroutine, Path1
+
+void Gosub1(uint Num)
+{
+	uint8 *pSub = Subroutines[Num];
+	dma::Tag(dma::call, 0, (uint)pSub);
+	vif::BASE(vu1::Loc);
+	vif::OFFSET(0);
+	vu1::Loc += ((uint16 *)pSub)[1];
+}
+
+
+// call a subroutine, Path2
+
+void Gosub2(uint Num)
+{
+	dma::Tag(dma::call, 0, (uint)Subroutines[Num]);
+	vif::NOP();
+	vif::NOP();
+}
+
+#endif
+
+
+
+//-----------------------------------------
+//		D M A   S U B R O U T I N E S
+//-----------------------------------------
+
+
+
+void BuildDmaSubroutines(void)
+{
+	//-------------------------------
+	//		C L E A R   V R A M
+	//-------------------------------
+
+	dma::BeginSub(dma::next);
+	dma::BeginTag(dma::end,0);
+	vif::BeginDIRECT();
+	gif::BeginTag2(gs::A_D, 1, PACKED, SPRITE, 1);
+	gs::Reg2(gs::FRAME_1,		PackFRAME(0,16,PSMCT32,0));
+	gs::Reg2(gs::ZBUF_1,		PackZBUF(0,0,1));
+	gs::Reg2(gs::TEST_1,		PackTEST(0,0,0,0,0,0,1,ZALWAYS));
+	gs::Reg2(gs::XYOFFSET_1,	PackXYOFFSET(0,0));
+	gs::Reg2(gs::SCISSOR_1,		PackSCISSOR(0,0x7FF,0,0x7FF));
+	gs::Reg2(gs::PRMODECONT,	PackPRMODECONT(1));
+	gs::Reg2(gs::SCANMSK,		PackSCANMSK(0));
+	gs::Reg2(gs::FBA_1,			PackFBA(0));
+	gs::Reg2(gs::DTHE,			PackDTHE(0));
+	gs::Reg2(gs::TEXA,			PackTEXA(0x00,0,0x80));
+	gs::Reg2(gs::RGBAQ,			PackRGBAQ(0,0,0,0,0));
+	gs::Reg2(gs::XYZ2,			PackXYZ(0,0,0));
+	gs::Reg2(gs::XYZ2,			PackXYZ(0x3FFF,0x3FFF,0));
+	gs::Reg2(gs::TEXFLUSH,		0);
+	gif::EndTag2(1);
+	vif::EndDIRECT();
+	vif::FLUSH();
+	dma::EndTag();
+	dma::Gosubs[CLEAR_VRAM] = dma::EndSub();
+
+
+
+	//---------------------------------------
+	//		F L I P   A N D   C L E A R
+	//---------------------------------------
+
+	#define CLEAR_COLOUR 0x00706050
+	
+	uint32 screen_w = HRES << 4;
+	uint32 screen_h = VRES << 4;
+
+	// begin Path2 subroutine
+	dma::BeginSub(dma::ref);
+
+	// wait in case something's still drawing from last frame
+	vif::FLUSHA();
+
+	// send everything PATH2
+	vif::BeginDIRECT();
+
+	// register setup
+	gif::BeginTag2(gs::A_D, 1, PACKED, 0, 0);
+	p_patch_ALPHA = (uint32*) (dma::pLoc + 4);
+	gs::Reg2(gs::ALPHA_1,		PackALPHA(0,1,2,1,128));		// tweak this alpha for motion blur!
+	gs::Reg2(gs::DIMX,			PackDIMX(-4,0,-3,1,2,-2,3,-1,-3,1,-4,0,3,-1,2,-2));
+	gs::Reg2(gs::DTHE,			PackDTHE(1));
+	gs::Reg2(gs::FBA_1,			PackFBA(0));
+	gs::Reg2(gs::FRAME_1,		PackFRAME(DISPLAY_START,HRES/64,PSMCT16S,0x00000000));
+	gs::Reg2(gs::PRMODECONT,	PackPRMODECONT(1));
+	gs::Reg2(gs::SCANMSK,		PackSCANMSK(0));
+	gs::Reg2(gs::SCISSOR_1,		PackSCISSOR(0,HRES - 1,0,VRES - 1));
+	gs::Reg2(gs::TEST_1,		PackTEST(0,0,0,0,0,0,1,ZALWAYS));
+	gs::Reg2(gs::TEX0_1,		PackTEX0(0,HRES/64,PSMCT32,10,10,1,DECAL,0,0,0,0,0));
+	gs::Reg2(gs::TEX1_1,		PackTEX1(1,0,NEAREST,NEAREST,0,0,0));
+	gs::Reg2(gs::TEXFLUSH,		0);
+	gs::Reg2(gs::XYOFFSET_1,	PackXYOFFSET(0,0));
+	gs::Reg2(gs::ZBUF_1,		PackZBUF(ZBUFFER_START,PSMZ24,0));
+	gif::EndTag2(0);
+
+	// using a set of screen-high textured sprites, copy draw buffer to disp buffer and clear z-buffer
+	gif::BeginTag2(gs::UV|gs::XYZ2<<4, 2, PACKED, SPRITE|TME|FST|ABE, 1);
+	for (uint32 x=0x0000; x
+#include "math.h"
+#include "fx.h"
+#include "asmdma.h"
+
+
+namespace NxPs2
+{
+
+
+
+void Fx::SetupFogPalette(uint32 FogRGBA, float Exponent)
+{
+	int i,j;
+	float t, alpha = (float)(int)(FogRGBA>>24);
+
+	for (i=255,t=1.0f/256.0f; i>=0; i--,t+=1.0f/256.0f)
+	{
+		j = (i&0xE7) | (i&0x10)>>1 | (i&0x08)<<1;
+		FogPalette[j] = (FogRGBA & 0x00FFFFFF) | (uint32)(int)(alpha * powf(t,Exponent) + 0.5f)<<24;
+	}
+}
+
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/fx.h b/Code/Gfx/NGPS/NX/fx.h
new file mode 100644
index 0000000..14138de
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/fx.h
@@ -0,0 +1,17 @@
+
+#ifndef __NX_NGPS_FX_H
+#define __NX_NGPS_FX_H
+
+namespace NxPs2
+{
+
+namespace Fx
+{
+
+void SetupFogPalette(uint32 FogRGBA, float Exponent);
+
+} // namespace Fx
+
+} // namespace NxPs2
+
+#endif // __NX_NGPS_FX_H
diff --git a/Code/Gfx/NGPS/NX/geomnode.cpp b/Code/Gfx/NGPS/NX/geomnode.cpp
new file mode 100644
index 0000000..b001de7
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/geomnode.cpp
@@ -0,0 +1,4177 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		THPS4													**
+**																			**
+**	Module:			geomnode				 								**
+**																			**
+**	File name:		geomnode.cpp											**
+**																			**
+**	Created by:		00/00/00	-	mrd										**
+**																			**
+**	Description:	process-in-place geometry nodes for PS2					**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+//#include 
+#include "render.h"
+#include "group.h"
+#include "geomnode.h"
+#include "vu1context.h"
+#include "occlude.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gs.h"
+#include "vu1code.h"
+#include "light.h"
+#include "line.h"
+#include "dmacalls.h"
+#include "scene.h"
+#include "texture.h"
+#include "switches.h"
+
+
+// Mick - Defining this __GEOM_STATS__ will enable bunch of counters in CGeomNode::Render
+// 
+#define	__GEOM_STATS__
+
+#define	__ASSERT_ON_ZERO_RADIUS__  0
+
+#define	OCCLUSION_CACHE 0
+
+// Just in case we forgot to take it out, this does it automatically on final burns   
+#ifndef	__NOPT_ASSERT__
+#undef	__GEOM_STATS__
+#endif   
+   
+#ifdef	__GEOM_STATS__
+#define	GEOMSTATINC(x) {x++;}
+#else
+#define	GEOMSTATINC(x) {}
+#endif
+
+
+//float	gClipDist = 0.1f;
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace NxPs2
+{
+
+
+
+
+int geom_stats_total=0;
+int geom_stats_inactive =0;
+int geom_stats_sky=0;
+int geom_stats_transformed=0;
+int geom_stats_skeletal=0;
+int geom_stats_camera_sphere=0;
+int geom_stats_clipcull=0;
+int geom_stats_culled=0;
+int geom_stats_leaf_culled=0;
+int geom_stats_boxcheck=0;
+int geom_stats_occludecheck=0;
+int geom_stats_occluded=0;
+int geom_stats_colored=0;
+int geom_stats_leaf=0;
+int geom_stats_wibbleUV=0;
+int geom_stats_wibbleVC=0;
+int geom_stats_sendcontext=0;
+int geom_stats_sorted=0;
+int geom_stats_shadow=0;
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+//#define	__LOD_MULTIPASS__
+
+#define SHADOW_CAM_FRUSTUM_HALF_WIDTH	64.0f
+#define SHADOW_CAM_FRUSTUM_HALF_HEIGHT	64.0f
+
+#define GET_FLAG(FLAG) (m_flags & (FLAG) ? true : false)
+#define SET_FLAG(FLAG) m_flags = yes ? m_flags|FLAG : m_flags&~FLAG
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+CGeomNode CGeomNode::sWorld;
+CGeomNode CGeomNode::sDatabase;
+
+CGeomNode *GpInstanceNode=NULL;
+
+#ifdef	__NOPT_ASSERT__
+// Mick: patch variables for debugging toggle of multi-passes
+uint32		gPassMask1 = 0; 		// 1<<6 | 1<<7  (0x40, 0x80)
+uint32		gPassMask0 = 0; 		// 1<<6 | 1<<7  (0x40, 0x80)
+#endif 
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+#ifdef __PLAT_NGPS__
+
+// This is only used in a couple of places, and it's 2% of CPU time in a release build
+// so I'm making it inline for now to improve cache coherency 
+// TODO: would be a good candidate for VU0 optimization
+// assuming the ViewVolume could be stored in VU mem or regs?
+inline bool CGeomNode::SphereIsOutsideViewVolume(Mth::Vector& s)
+{
+	return
+		s[3] < s[2] - render::Near						||
+		s[3] < render::sx * s[2] - render::cx * s[0]	||
+		s[3] < render::sx * s[2] + render::cx * s[0]	||
+		s[3] < render::sy * s[2] + render::cy * s[1] 	||
+		s[3] < render::sy * s[2] - render::cy * s[1]	||
+		s[3] < render::Far - s[2];
+}
+
+
+// Same with this
+inline bool CGeomNode::SphereIsInsideOuterVolume(Mth::Vector& s)
+{
+	return
+		s[3] < render::Near - s[2]						&&
+		s[3] < -render::Sx * s[2] + render::Cx * s[0]	&&
+		s[3] < -render::Sx * s[2] - render::Cx * s[0]	&&
+		s[3] < -render::Sy * s[2] - render::Cy * s[1]	&&
+		s[3] < -render::Sy * s[2] + render::Cy * s[1]	&&
+		s[3] < s[2] - render::Far;
+}
+
+
+bool CGeomNode::BoxIsOutsideViewVolume(Mth::Vector& b, Mth::Vector& s)
+{
+	return ((render::FrustumFlagsPlus[0]  && (s[0]+b[0] <= render::CameraToWorld[3][0])) ||
+			(render::FrustumFlagsPlus[1]  && (s[1]+b[1] <= render::CameraToWorld[3][1])) ||
+			(render::FrustumFlagsPlus[2]  && (s[2]+b[2] <= render::CameraToWorld[3][2])) ||
+			(render::FrustumFlagsMinus[0] && (s[0]-b[0] >= render::CameraToWorld[3][0])) ||
+			(render::FrustumFlagsMinus[1] && (s[1]-b[1] >= render::CameraToWorld[3][1])) ||
+			(render::FrustumFlagsMinus[2] && (s[2]-b[2] >= render::CameraToWorld[3][2])));
+}
+
+
+inline bool CGeomNode::NeedsClipping(Mth::Vector& s)
+{
+	return true;
+}
+
+
+void CGeomNode::LinkDma(uint8 *p_newTail)
+{
+	dma::Tag(dma::next, 0, 0);	// will be patched up by next call to LinkDma()
+	vif::NOP();
+	vif::NOP();
+	((uint *)u3.mp_group->pListEnd[render::Field])[1] = (uint)p_newTail;
+	u3.mp_group->pListEnd[render::Field] = dma::pLoc - 16;
+}
+
+// returns the number of leaf nodes
+int CGeomNode::ProcessNodeInPlace(uint8 *p_baseAddress)
+{
+	int leaves = 0;
+	
+	if (!(m_flags & NODEFLAG_LEAF))
+	{
+		leaves++;
+		// convert transform offset to pointer
+		if ((int)u2.mp_transform != -1)
+		{
+			u2.mp_transform = (Mth::Matrix *)( (int)u2.mp_transform + p_baseAddress );
+		}
+		else
+		{
+			u2.mp_transform = (Mth::Matrix *)0;
+		}
+	}
+
+	uint32	tex = GetTextureChecksum();
+	SetTextureChecksum(0);  			// clear it in case we don't find it
+
+	// convert group checksum to pointer
+	if ((m_flags & NODEFLAG_LEAF) && u1.mp_dma)
+	{
+		sGroup *pGroup;
+		for (pGroup=sGroup::pHead; pGroup; pGroup=pGroup->pNext)
+			if (pGroup->Checksum == (uint)u3.mp_group)
+				break;
+		Dbg_MsgAssert(pGroup, ("Couldn't group checksum #%d\n", (uint)u3.mp_group));
+		u3.mp_group = pGroup;
+		
+		// Resolve the group+texture checksum into a texture pointer		
+//		printf ("%x\n",GetTextureChecksum());
+		if (tex)
+		{
+			sGroup * p_group = GetGroup();
+			sScene * p_scene = p_group->pScene;
+			sTexture *p_textures = p_scene->pTextures;
+			int num_textures = p_scene->NumTextures;
+			for (int i=0;iChecksum)
+				{
+					//printf ("Found %dx%dx%d %x mips at 0x%x (%d)\n",p_textures[i].GetWidth(),p_textures[i].GetHeight(),p_textures[i].GetClutBitdepth(),p_textures[i].GetNumMipmaps(),*(p_textures+i),i);
+					SetTextureChecksum((uint32)(p_textures+i));
+					break;  
+				}
+			}
+//			printf ("-> 0x%x\n",GetTextureChecksum());
+		}
+		
+	}
+
+	if (m_flags & NODEFLAG_LEAF)
+	{
+		// convert dma offset to pointer
+		if ((int)u1.mp_dma != -1)
+		{
+			u1.mp_dma = (uint)u1.mp_dma + p_baseAddress;
+		}
+		else
+		{
+			u1.mp_dma = (uint8 *)0;
+		}
+	}
+	else
+	{
+		// convert child offset to pointer
+		if ((int)u1.mp_child != -1)
+		{
+			u1.mp_child = (CGeomNode *)( (int)u1.mp_child + p_baseAddress );
+		}
+		else
+		{
+			u1.mp_child = (CGeomNode *)0;
+		}
+	}
+
+	// convert sibling offset to pointer
+	if ((int)mp_sibling != -1)
+	{
+		mp_sibling = (CGeomNode *)( (int)mp_sibling + p_baseAddress );
+	}
+	else
+	{
+		mp_sibling = (CGeomNode *)0;
+	}
+
+	// convert LOD offset to pointer
+	if ((int)mp_next_LOD != -1)
+	{
+		mp_next_LOD = (CGeomNode *)( (int)mp_next_LOD + p_baseAddress );
+	}
+	else
+	{
+		mp_next_LOD = (CGeomNode *)0;
+	}
+
+	// convert uv wibble offset to pointer
+	if (m_flags & NODEFLAG_UVWIBBLE)
+	{
+		if (!(m_flags & NODEFLAG_ENVMAPPED) || !(m_flags & NODEFLAG_LEAF))
+		{
+			Dbg_MsgAssert ((int)mp_uv_wibble!=-1, ("Null uv wibble pointer on a uv wibbled node"));
+			mp_uv_wibble = (float *)( (int)mp_uv_wibble + p_baseAddress );
+			Dbg_MsgAssert (!(((uint32)mp_uv_wibble) & 0xf0000003),("Corrupt UV wibble offset (0x%x) in node %s\n",((int)mp_uv_wibble - (int)p_baseAddress), Script::FindChecksumName(m_checksum)));
+		}
+		else
+		{
+			mp_uv_wibble = (float *)0;
+			m_flags &= ~NODEFLAG_UVWIBBLE;
+		}
+	}
+	
+	// convert vc wibble offset to pointer
+	if ((int)mp_vc_wibble != -1)
+	{
+		// first convert the wibble offset itself
+		mp_vc_wibble = (uint32 *)( (int)mp_vc_wibble + p_baseAddress );
+
+		// now the mesh info
+		uint32 *p_meshdata = mp_vc_wibble;
+		int seq, num_seqs, vert, num_verts;
+
+		num_seqs = *p_meshdata++;
+		for (seq=0; seqmp_sibling)
+		{
+			leaves += p_child->ProcessNodeInPlace(p_baseAddress);
+		}
+	}
+
+	// recursively process any LODs
+	if (mp_next_LOD)
+	{
+		(void) mp_next_LOD->ProcessNodeInPlace(p_baseAddress);
+	}
+	
+	return leaves;
+}
+
+
+
+void CGeomNode::BecomesChildOf(CGeomNode *p_parent)
+{
+	if (m_flags & NODEFLAG_LEAF)
+	{
+		mp_sibling = NULL;
+		m_flags &= ~NODEFLAG_LEAF;
+	}
+	else
+	{
+		mp_sibling = p_parent->u1.mp_child;
+	}
+	p_parent->u1.mp_child = this;
+}
+
+
+bool CGeomNode::RemoveFrom(CGeomNode *p_parent)
+{
+	if ((m_flags & NODEFLAG_LEAF) || !p_parent->u1.mp_child)
+	{
+		return false;
+	}
+		
+	if (p_parent->u1.mp_child == this)
+	{
+		p_parent->u1.mp_child = mp_sibling;
+		return true;
+	}
+	else
+	{
+		for (CGeomNode *p_node=p_parent->u1.mp_child; p_node->mp_sibling; p_node=p_node->mp_sibling)
+		{
+			if (p_node->mp_sibling == this)
+			{
+				p_node->mp_sibling = mp_sibling;
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+
+bool CGeomNode::RemoveFromChildrenOf(CGeomNode *p_parent)
+{
+	if ((m_flags & NODEFLAG_LEAF) || !p_parent->u1.mp_child)
+	{
+		return false;
+	}
+
+	for (CGeomNode *p_node=p_parent->u1.mp_child; p_node; p_node=p_node->mp_sibling)
+	{
+		if (RemoveFrom(p_node))
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+#endif // #ifdef __PLAT_NGPS__
+
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+
+#if OCCLUSION_CACHE
+static bool set_valid_flag = false;
+#endif
+
+
+CGeomNode::CGeomNode()
+{
+	m_flags = ALL_VISIBILITY_BITS;
+
+	
+#if OCCLUSION_CACHE
+	if( set_valid_flag )
+	{
+		set_valid_flag = false;
+		m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
+	}
+	else
+	{
+		set_valid_flag = true;
+	}
+#endif
+	m_bounding_sphere[0] = 0.0f;
+	m_bounding_sphere[1] = 0.0f;
+	m_bounding_sphere[2] = 0.0f;
+	m_bounding_sphere[3] = 1.0e+30f;
+
+	m_bounding_box[0] = 1.0e+30f;
+	m_bounding_box[1] = 1.0e+30f;
+	m_bounding_box[2] = 1.0e+30f;
+
+	m_LOD_far_dist	= MAX_LOD_DIST;
+
+	u2.mp_transform	= NULL;
+	u1.mp_child		= NULL;
+	mp_sibling		= NULL;
+	mp_next_LOD		= NULL;
+	//u1.mp_dma			= NULL;
+	u3.mp_group		= NULL;
+	mp_uv_wibble	= NULL;
+	mp_vc_wibble	= NULL;
+
+	m_num_bones		   = 0;
+	m_bone_index	   = -1;
+	u4.mp_bone_transforms = NULL;
+
+	m_colour = 0x80808080;
+}
+
+
+void CGeomNode::Init()
+{
+	m_flags = ALL_VISIBILITY_BITS;
+
+	m_bounding_sphere[0] = 0.0f;
+	m_bounding_sphere[1] = 0.0f;
+	m_bounding_sphere[2] = 0.0f;
+	m_bounding_sphere[3] = 1.0e+30f;
+
+	m_bounding_box[0] = 1.0e+30f;
+	m_bounding_box[1] = 1.0e+30f;
+	m_bounding_box[2] = 1.0e+30f;
+
+	m_LOD_far_dist	= MAX_LOD_DIST;
+
+	u2.mp_transform	= NULL;
+	u1.mp_child		= NULL;
+	mp_sibling		= NULL;
+	mp_next_LOD		= NULL;
+	//u1.mp_dma			= NULL;
+	u3.mp_group		= NULL;
+	mp_uv_wibble	= NULL;
+	mp_vc_wibble	= NULL;
+
+	m_num_bones		   = 0;
+	m_bone_index	   = -1;
+	u4.mp_bone_transforms = NULL;
+
+	m_colour = 0x80808080;
+}
+
+
+CGeomNode::CGeomNode(const CGeomNode &node)
+{
+	#if 1
+	memcpy(this,&node,sizeof(CGeomNode));
+	#else
+	m_flags				= node.m_flags;
+	m_bounding_sphere	= node.m_bounding_sphere;
+	u2.mp_transform		= node.u2.mp_transform;
+	u1.mp_child			= node.u1.mp_child;
+	mp_sibling			= node.mp_sibling;
+	mp_next_LOD			= node.mp_next_LOD;
+	m_LOD_far_dist		= node.m_LOD_far_dist;
+	//u1.mp_dma				= node.u1.mp_dma;
+	u3.mp_group			= node.u3.mp_group;
+	mp_uv_wibble		= node.mp_uv_wibble;
+	mp_vc_wibble		= node.mp_vc_wibble;
+	m_num_bones			= node.m_num_bones;
+	m_bone_index		= node.m_bone_index;
+	u4.mp_bone_transforms	= node.u4.mp_bone_transforms;
+	m_colour			= node.m_colour;
+	#endif
+}
+
+
+
+#ifdef __PLAT_NGPS__
+
+
+
+//#define	DUMP_NODE_STRUCTURE
+
+
+#ifdef	DUMP_NODE_STRUCTURE
+static int node_level = 0;
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeomNode::RenderWorld(CVu1Context& ctxt, uint32 renderFlags)
+{
+	GEOMSTATINC(geom_stats_total);
+
+	
+// Don't render anything if it's not going to be displayed	
+	if (!FlipCopyEnabled())
+	{
+		return;
+	}
+	
+	// render children
+	CGeomNode *p_child;
+	for (p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+	{
+		p_child->RenderScene(ctxt, renderFlags);
+	}
+	
+#ifdef	__NOPT_ASSERT__
+	m_flags |= NODEFLAG_WAS_RENDERED;		// Set flag saying it was rendered
+#endif
+
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeomNode::RenderScene(CVu1Context& ctxt, uint32 renderFlags)
+{
+	CVu1Context *p_ctxt;
+
+#ifdef	__NOPT_ASSERT__
+	m_flags &= ~NODEFLAG_WAS_RENDERED;		// Clear flag saying it was rendered
+#endif
+
+	GEOMSTATINC(geom_stats_total);
+
+
+	// cull the node if not active
+	if (!((m_flags & NODEFLAG_ACTIVE) && (m_flags & render::ViewportMask)))
+	{
+		GEOMSTATINC(geom_stats_inactive);
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+		return;
+	}
+
+	// is it the root node of a sky?
+	if (m_flags & NODEFLAG_SKY)
+	{
+		GEOMSTATINC(geom_stats_sky);
+		// reserve space from dma list for new trans context
+		p_ctxt = ctxt.Localise();
+
+		p_ctxt->SetupAsSky();
+		RenderAsSky(*p_ctxt, renderFlags);
+		dma::SetList(NULL);			// not quite sure why we need this, but we do
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+		return;
+	}
+
+	// is it really an object node in disguise?
+	if (m_flags & NODEFLAG_OBJECT)
+	{
+		RenderObject(ctxt, renderFlags);
+		return;
+	}
+
+	// get this node's transform context
+	if (m_flags & NODEFLAG_TRANSFORMED)
+	{
+		// copy the parent vu1 context
+		p_ctxt = ctxt.LocaliseExtend();
+
+		// set up the parts of the context that will depend on the new matrix
+		p_ctxt->StandardSetup(*u2.mp_transform);   
+
+		// Propogate the 'transformed' flag when recursing.
+		renderFlags |= RENDERFLAG_TRANSFORMED;
+	}
+	else
+	{
+		p_ctxt = &ctxt;
+	}
+
+	// make a note of the root node for a skeletal model
+	if (m_flags & NODEFLAG_SKELETAL)
+	{
+		GpInstanceNode = this;
+		renderFlags |= RENDERFLAG_SKELETAL;
+	}
+
+
+	// applying a colour to a node
+	if (m_flags & NODEFLAG_COLOURED)
+	{
+		GEOMSTATINC(geom_stats_colored);
+		// if there's no local context, copy the parent's
+		if (p_ctxt == &ctxt)
+		{
+			p_ctxt = ctxt.Localise();
+		}
+	
+		// copy node's colour to vu1 context
+		p_ctxt->SetColour(this);
+		renderFlags |= RENDERFLAG_COLOURED;
+	}
+
+	// Check for light group
+	if (u3.mp_light_group && u3.mp_light_group != ctxt.GetLights())
+	{
+		// if there's no local context, copy the global one
+		if (p_ctxt == &ctxt || !p_ctxt->IsExtended())
+		{
+			p_ctxt = ctxt.LocaliseExtend();
+		}
+
+		p_ctxt->SetLights(u3.mp_light_group);
+	}
+
+
+// If we got here, then it's been rendered, so set the flag
+#ifdef	__NOPT_ASSERT__
+	m_flags |= NODEFLAG_WAS_RENDERED;		// Set flag saying it was rendered
+#endif
+
+	// render any children
+	for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+	{
+		p_child->RenderObject(*p_ctxt, renderFlags);
+	}
+
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeomNode::RenderObject(CVu1Context& ctxt, uint32 renderFlags)
+{
+	CVu1Context *p_ctxt;
+
+#ifdef	__NOPT_ASSERT__
+	m_flags &= ~NODEFLAG_WAS_RENDERED;		// Clear flag saying it was rendered
+#endif
+
+	GEOMSTATINC(geom_stats_total);
+
+
+
+
+	#if	__ASSERT_ON_ZERO_RADIUS__
+	Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
+	#endif
+
+	// cull the node if not active
+	if (!((m_flags & NODEFLAG_ACTIVE) && (m_flags & render::ViewportMask)))
+	{
+		GEOMSTATINC(geom_stats_inactive);
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+	    return;
+	}
+
+#if OCCLUSION_CACHE
+	bool last_occlusion_check_valid = ( m_flags & NODEFLAG_LAST_OCCLUSION_VALID );
+	if( last_occlusion_check_valid )
+	{
+		// Clear the valid flag.
+		m_flags &= ~NODEFLAG_LAST_OCCLUSION_VALID;
+
+		// Was this object occluded last frame? If so, occlude this frame also.
+		if( m_flags & NODEFLAG_LAST_OCCLUSION_TRUE )
+		{
+			m_flags &= ~NODEFLAG_LAST_OCCLUSION_TRUE;
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+		return;
+		}
+	}
+#endif
+
+	// get this node's transform context
+	if (m_flags & NODEFLAG_TRANSFORMED)
+	{
+		// copy the parent vu1 context
+		p_ctxt = ctxt.LocaliseExtend();
+
+		// set up the parts of the context that will depend on the new matrix
+		p_ctxt->StandardSetup(*u2.mp_transform);   
+
+		// Propogate the 'transformed' flag when recursing.
+		renderFlags |= RENDERFLAG_TRANSFORMED;
+	}
+	else
+	{
+		p_ctxt = &ctxt;
+	}
+
+	// check for skeletal mesh
+	if ((renderFlags & RENDERFLAG_SKELETAL) && (m_bone_index >= 0))
+	{
+		Dbg_MsgAssert(GpInstanceNode, ("GpInstanceNode is NULL"));
+
+		GEOMSTATINC(geom_stats_skeletal);
+		if (p_ctxt == &ctxt)
+		{
+			p_ctxt = ctxt.Localise();
+		}
+	
+		p_ctxt->HierarchicalSetup(GpInstanceNode->u4.mp_bone_transforms[m_bone_index]);
+	}
+
+	Mth::Vector camera_sphere;
+	GEOMSTATINC(geom_stats_camera_sphere);
+	camera_sphere = m_bounding_sphere;
+	camera_sphere[W] = 1.0f;
+	camera_sphere *= *p_ctxt->GetMatrix();
+	camera_sphere[W] = m_bounding_sphere[W];
+
+
+	// view-volume bounding sphere tests, based on parent render flags
+	// MickNote:  I experimented with only clipping the leaf nodes, but this was slightly
+	// slower, as the higher level culling must cull enough to justify the overhead)
+	if ( renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
+	{
+		GEOMSTATINC(geom_stats_clipcull);
+		if (SphereIsOutsideViewVolume(camera_sphere))
+		{
+			GEOMSTATINC(geom_stats_culled);
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+			return;
+		}
+
+		if (SphereIsInsideOuterVolume(camera_sphere))
+		{
+			renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
+		}
+	}
+
+	// view-volume bounding box test
+	if ((renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL)) &&
+		!((renderFlags & RENDERFLAG_TRANSFORMED) || m_flags & NODEFLAG_TRANSFORMED))
+	{
+		GEOMSTATINC(geom_stats_boxcheck);
+		if (BoxIsOutsideViewVolume(*(Mth::Vector *)&m_bounding_box, m_bounding_sphere))
+		{
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+			return;
+		}
+
+	}
+
+	// Occlusion test.
+	if (m_bounding_sphere[W] > 1.0e20f)
+	{
+		// Don't bother if sphere is stupidly large.
+	}
+#if OCCLUSION_CACHE
+	else if( !last_occlusion_check_valid )
+#else
+	else
+#endif	
+	{
+		
+#if OCCLUSION_CACHE
+		m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
+#endif
+		if (m_flags & NODEFLAG_TRANSFORMED)					// root of a transformed branch
+		{
+			GEOMSTATINC(geom_stats_occludecheck);
+			Mth::Vector world_sphere(m_bounding_sphere);
+			world_sphere[W] = 1.0f;
+			world_sphere *= *u2.mp_transform;							// => use u2.mp_transform
+			if (TestSphereAgainstOccluders( &world_sphere, m_bounding_sphere[W] ))
+			{
+				GEOMSTATINC(geom_stats_occluded);
+#if OCCLUSION_CACHE
+				m_flags |= NODEFLAG_LAST_OCCLUSION_TRUE;
+#endif
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+				return;
+			}
+		}
+		else if (renderFlags & RENDERFLAG_TRANSFORMED)				// deeper within a transformed branch
+		{
+		}
+		else														// not transformed => use world position as-is
+		{
+			GEOMSTATINC(geom_stats_occludecheck);
+			if (TestSphereAgainstOccluders( &m_bounding_sphere, m_bounding_sphere[W] ))
+			{
+				GEOMSTATINC(geom_stats_occluded);
+#if OCCLUSION_CACHE
+				m_flags |= NODEFLAG_LAST_OCCLUSION_TRUE;
+#endif				
+				#ifdef	DUMP_NODE_STRUCTURE
+					node_level--;
+				#endif
+				return;
+			}
+		}
+	}
+
+
+	#if !STENCIL_SHADOW
+	// shadow test
+	if (renderFlags & RENDERFLAG_SHADOW)
+	{
+		if (m_flags & (NODEFLAG_TRANSFORMED))
+		{
+			renderFlags &= ~RENDERFLAG_SHADOW;
+		}
+		else if (( m_bounding_sphere[X] + m_bounding_box[X] ) < ( render::ShadowCameraPosition[X] - SHADOW_CAM_FRUSTUM_HALF_WIDTH  )	||
+				 ( m_bounding_sphere[X] - m_bounding_box[X] ) > ( render::ShadowCameraPosition[X] + SHADOW_CAM_FRUSTUM_HALF_WIDTH  )	||
+				 ( m_bounding_sphere[Z] + m_bounding_box[Z] ) < ( render::ShadowCameraPosition[Z] - SHADOW_CAM_FRUSTUM_HALF_HEIGHT )	||
+				 ( m_bounding_sphere[Z] - m_bounding_box[Z] ) > ( render::ShadowCameraPosition[Z] + SHADOW_CAM_FRUSTUM_HALF_HEIGHT ))
+		{
+			renderFlags &= ~RENDERFLAG_SHADOW;
+		}
+	}
+	#endif
+
+	// applying a colour to a node
+	if (m_flags & NODEFLAG_COLOURED)
+	{
+		GEOMSTATINC(geom_stats_colored);
+		// if there's no local context, copy the parent's
+		if (p_ctxt == &ctxt)
+		{
+			p_ctxt = ctxt.Localise();
+		}
+
+		// copy node's colour to vu1 context
+		p_ctxt->SetColour(this);
+		renderFlags |= RENDERFLAG_COLOURED;
+	}
+
+	// Check for light group
+	if (u3.mp_light_group && u3.mp_light_group != ctxt.GetLights())
+	{
+		// if there's no local context, copy the global one
+		if (p_ctxt == &ctxt || !p_ctxt->IsExtended())
+		{
+			p_ctxt = ctxt.LocaliseExtend();
+		}
+
+		p_ctxt->SetLights(u3.mp_light_group);
+	}
+
+
+	// Check if we should use another LOD (Garrett: Logically, this should be right, but I still want to streamline it)
+	float camera_dist = -camera_sphere[Z];
+	CGeomNode *p_child = NULL;
+	CGeomNode *p_lod;
+	for (p_lod=this; p_lod; p_lod = p_lod->mp_next_LOD)
+	{
+		if (p_lod->m_LOD_far_dist > camera_dist)
+		{
+			p_child = p_lod->u1.mp_child;
+			break;
+		}
+	}
+
+// If we got here, then it's been rendered, so set the flag
+#ifdef	__NOPT_ASSERT__
+	m_flags |= NODEFLAG_WAS_RENDERED;		// Set flag saying it was rendered
+#endif
+
+	// render any children
+	if (renderFlags & RENDERFLAG_TRANSFORMED)
+	{
+		for (; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->RenderTransformedLeaf(*p_ctxt, renderFlags);
+		}
+	}
+	else
+	{
+		for (; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->RenderNonTransformedLeaf(*p_ctxt, renderFlags);
+		}
+	}
+
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeomNode::RenderTransformedLeaf(CVu1Context& ctxt, uint32 renderFlags)
+{
+	CVu1Context *p_ctxt;
+
+	#if	__ASSERT_ON_ZERO_RADIUS__
+	Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
+	#endif
+
+#ifdef	__NOPT_ASSERT__
+	m_flags &= ~NODEFLAG_WAS_RENDERED;		// Clear flag saying it was rendered
+#endif
+
+	GEOMSTATINC(geom_stats_total);
+
+
+#if OCCLUSION_CACHE
+	bool last_occlusion_check_valid = ( m_flags & NODEFLAG_LAST_OCCLUSION_VALID );
+	if( last_occlusion_check_valid )
+	{
+		// Clear the valid flag.
+		m_flags &= ~NODEFLAG_LAST_OCCLUSION_VALID;
+
+		// Was this object occluded last frame? If so, occlude this frame also.
+		if( m_flags & NODEFLAG_LAST_OCCLUSION_TRUE )
+		{
+			m_flags &= ~NODEFLAG_LAST_OCCLUSION_TRUE;
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+		return;
+		}
+	}
+#endif
+
+	Mth::Vector camera_sphere;
+	GEOMSTATINC(geom_stats_camera_sphere);
+	camera_sphere = m_bounding_sphere;
+	camera_sphere[W] = 1.0f;
+	camera_sphere *= *ctxt.GetMatrix();
+	camera_sphere[W] = m_bounding_sphere[W];
+
+
+	// view-volume bounding sphere tests, based on parent render flags
+	// MickNote:  I experimented with only clipping the leaf nodes, but this was slightly
+	// slower, as the higher level culling must cull enough to justify the overhead)
+	if ( renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
+	{
+		GEOMSTATINC(geom_stats_clipcull);
+		if (SphereIsOutsideViewVolume(camera_sphere))
+		{
+			GEOMSTATINC(geom_stats_culled);
+			GEOMSTATINC(geom_stats_leaf_culled);
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+			return;
+		}
+
+		if (SphereIsInsideOuterVolume(camera_sphere))
+		{
+			renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
+		}
+	}
+
+
+	// Occlusion test.
+#if OCCLUSION_CACHE
+	if( !last_occlusion_check_valid )
+#endif	
+	{
+#if OCCLUSION_CACHE
+		m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
+#endif
+	}
+
+
+	#if !STENCIL_SHADOW
+	// shadow test
+	if (renderFlags & RENDERFLAG_SHADOW)
+	{
+		if (m_flags & (NODEFLAG_BILLBOARD | NODEFLAG_ENVMAPPED))
+		{
+			renderFlags &= ~RENDERFLAG_SHADOW;
+		}
+		else if (( m_bounding_sphere[X] + m_bounding_box[X] ) < ( render::ShadowCameraPosition[X] - SHADOW_CAM_FRUSTUM_HALF_WIDTH  )	||
+				 ( m_bounding_sphere[X] - m_bounding_box[X] ) > ( render::ShadowCameraPosition[X] + SHADOW_CAM_FRUSTUM_HALF_WIDTH  )	||
+				 ( m_bounding_sphere[Z] + m_bounding_box[Z] ) < ( render::ShadowCameraPosition[Z] - SHADOW_CAM_FRUSTUM_HALF_HEIGHT )	||
+				 ( m_bounding_sphere[Z] - m_bounding_box[Z] ) > ( render::ShadowCameraPosition[Z] + SHADOW_CAM_FRUSTUM_HALF_HEIGHT ))
+		{
+			renderFlags &= ~RENDERFLAG_SHADOW;
+		}
+	}
+	#endif
+
+	// get this node's transform context
+	p_ctxt = &ctxt;
+
+	// applying a colour to a node
+	if (m_flags & NODEFLAG_COLOURED)
+	{
+		GEOMSTATINC(geom_stats_colored);
+		p_ctxt = ctxt.Localise();
+
+		// copy node's colour to vu1 context
+		p_ctxt->SetColour(this);
+		renderFlags |= RENDERFLAG_COLOURED;
+	}
+
+	GEOMSTATINC(geom_stats_leaf);
+	#ifdef	__NOPT_ASSERT__
+	// Mick: check patch variables for debugging toggle of multi-passes
+	if ((m_flags & gPassMask1) != gPassMask0)
+	{	
+#ifdef	DUMP_NODE_STRUCTURE
+node_level--;
+#endif
+		return;
+	}
+	#endif
+
+	#ifdef	__LOD_MULTIPASS__
+	// if 2nd+ pass, check to see if distance from camera is greater than allowed for multipass
+	if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere[Z] > render::sMultipassMaxDist))
+	{
+#ifdef	DUMP_NODE_STRUCTURE
+node_level--;
+#endif
+		return;
+	}
+	#endif
+
+	if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED | NODEFLAG_VCWIBBLE))
+	{
+		if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED))
+		{
+			// if there's no local context, copy the global one
+			if (p_ctxt == &ctxt)
+			{
+				p_ctxt = ctxt.LocaliseExtend();
+			}
+
+			// uv wibble
+			if (m_flags & NODEFLAG_UVWIBBLE)
+			{
+				GEOMSTATINC(geom_stats_wibbleUV);
+				p_ctxt->WibbleUVs(mp_uv_wibble, m_flags&NODEFLAG_EXPLICIT_UVWIBBLE);
+			}
+
+			// environment mapping
+			if (m_flags & NODEFLAG_ENVMAPPED)
+			{
+				// reflection map vectors
+				p_ctxt->SetReflectionVecs((sint16)(uint)mp_uv_wibble, (sint16)(((uint)mp_uv_wibble)>>16));
+			}
+		}
+
+		// vc wibble
+		if (m_flags & NODEFLAG_VCWIBBLE)
+		{
+			GEOMSTATINC(geom_stats_wibbleVC);
+			WibbleVCs();
+		}
+	}
+
+	// set the dma context
+	dma::SetList(u3.mp_group);
+	u3.mp_group->Used[render::Field] = true;
+
+	// flag the texture as used
+	if (u4.mp_texture)
+	{
+		u4.mp_texture->m_render_count++;
+	}
+
+
+	Mth::Vector *p_trans = p_ctxt->GetTranslation();
+
+	// initial cnt tag, for sending row reg and z-sort key
+	dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere[Z]);
+	vif::NOP();
+	vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
+			   (int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
+			   (int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
+
+
+	// special case for billboards
+	if (m_flags & NODEFLAG_BILLBOARD)
+	{
+		// compute billboard basis vecs
+		Mth::Vector udir,vdir;
+		vdir = (*p_ctxt->GetMatrix())[1];
+		vdir[2] =  0.0f;
+		vdir[3] =  0.0f;
+		vdir.Normalize();
+		udir[0] =  vdir[1];
+		udir[1] = -vdir[0];
+		udir[2] =  0.0f;
+		udir[3] =  0.0f;
+
+		udir *= render::CameraToFrustum;
+		vdir *= render::CameraToFrustum;
+		udir[2] =  0.0f;
+		udir[3] =  0.0f;
+		vdir[2] =  0.0f;
+		vdir[3] =  0.0f;
+
+		// send vu1 context
+		dma::Tag(dma::next, 16, (uint)dma::pLoc+272);
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		uint vu1_loc = vu1::Loc;
+		vu1::Loc = 0;   							// must do this as a relative prim for a sortable list...
+		vu1::BeginPrim(REL, VU1_ADDR(L_VF20));
+		vu1::StoreVec(*(Vec *)&udir);
+		vu1::StoreVec(*(Vec *)&vdir);
+		vu1::StoreVec(*(Vec *)&render::IntViewportScale);
+		vif::StoreV4_32(0,0,0,0);
+		vif::StoreV4_32(0,0,0,0);
+		vu1::StoreVec(*(Vec *)&render::CameraToWorld[3]);
+		vu1::StoreVec(*(Vec *)ctxt.GetColour());
+		vu1::StoreVec(*(Vec *)&render::IntViewportOffset);
+		Mth::Matrix LocalToFrustum = (*p_ctxt->GetMatrix()) * render::CameraToFrustum;
+		vu1::CopyMat((float*)&LocalToFrustum);
+		vu1::EndPrim(0);
+
+		// gs context data, specifically the fog coefficient
+		float w,f;
+		if (renderFlags & RENDERFLAG_TRANSFORMED)
+		{
+			w = -(*p_ctxt->GetMatrix())[3][2];
+		}
+		else
+		{
+			// Context only contains WorldToCamera matrix, so convert world position to camera relative position
+			Mth::Vector camera_rel_pos = render::WorldToCamera.TransformAsPos(m_bounding_sphere);
+			w = -camera_rel_pos[2];
+		}
+		if ((w > render::FogNear) && render::EnableFog)	// Garrett: We have to check for EnableFog here because the VU1 code isn't
+		{
+			f = 1.0 + render::EffectiveFogAlpha * (render::FogNear/w - 1.0f);
+		}
+		else
+		{
+			f = 1.0f;
+		}
+		gs::BeginPrim(REL,0,0);
+		gs::Reg1(gs::FOG, PackFOG((int)(f*255.99f)));
+		gs::EndPrim(0);
+
+		dma::EndTag();
+		((uint16 *)dma::pTag)[1] |= vu1::Loc & 0x3FF;	// must write some code for doing this automatically
+		vu1::Loc += vu1_loc;
+
+		u3.mp_group->pVu1Context = NULL;
+	}
+
+	// send transform context if necessary
+	else if ((p_ctxt != u3.mp_group->pVu1Context) || (u3.mp_group->flags & GROUPFLAG_SORT))
+	{
+		GEOMSTATINC(geom_stats_sendcontext);
+		int qwc = p_ctxt->IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
+		dma::Tag(dma::ref, qwc|(qwc-1)<<16, p_ctxt->GetDma());
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		vu1::Loc += qwc-1;
+		u3.mp_group->pVu1Context = p_ctxt;
+	}
+
+	// render this node by adding to the chosen dma list
+	uint vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP :
+					 (renderFlags & RENDERFLAG_CULL) ? CULL : PROJ;
+	if (renderFlags & RENDERFLAG_COLOURED)
+		vu1_flags |= COLR;
+	if (render::EnableFog && (camera_sphere[3]-camera_sphere[2] > render::FogNear))
+		vu1_flags |= FOGE;
+	dma::pTag = dma::pLoc;		// must set this manually if we bypass the dma tag functions
+	dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
+	vif::BASE(vu1::Loc);
+	vif::ITOP(vu1_flags);
+	vu1::Loc += u2.m_dma_tag_lo32>>16;
+
+	if (m_flags & NODEFLAG_BLACKFOG)
+	{
+		dma::Gosub(SET_FOGCOL,2);
+		dma::pLoc -= 8;
+		vif::FLUSHA();
+		vif::NOP();
+	}
+
+	// must add a 'next' tag for sorted groups
+	if (u3.mp_group->flags & GROUPFLAG_SORT)
+	{
+		GEOMSTATINC(geom_stats_sorted);
+		dma::SetList(NULL);
+	}
+
+	#if !STENCIL_SHADOW
+	// render shadow
+	if ((renderFlags & RENDERFLAG_SHADOW) && !(m_flags & NODEFLAG_NOSHADOW))
+	{
+		GEOMSTATINC(geom_stats_shadow);
+		// set the dma context
+		dma::SetList(sGroup::pShadow);
+
+		// set row reg
+		dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere[Z]);
+		vif::NOP();
+		vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
+				   (int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
+				   (int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
+
+		// send transform context
+		dma::Tag(dma::ref, 18|17<<16, sGroup::pShadow->pVu1Context->GetDma());
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		vu1::Loc += 17;
+
+		// render this node
+		vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP|SHDW :
+					(renderFlags & RENDERFLAG_CULL) ? CULL|SHDW : PROJ|SHDW;
+		if (render::EnableFog && (camera_sphere[3]-camera_sphere[2] > render::FogNear))
+			vu1_flags |= FOGE;
+		dma::pTag = dma::pLoc;		// must set this manually if we bypass the dma tag functions
+		dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
+		vif::BASE(vu1::Loc);
+		vif::ITOP(vu1_flags);
+		vu1::Loc += u2.m_dma_tag_lo32>>16;
+
+		if (m_flags & NODEFLAG_BLACKFOG)
+		{
+			dma::Gosub(SET_FOGCOL,2);
+			dma::pLoc -= 8;
+			vif::FLUSHA();
+			vif::NOP();
+		}
+	}
+	#endif
+
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeomNode::RenderNonTransformedLeaf(CVu1Context& ctxt, uint32 renderFlags)
+{
+	float camera_sphere_z;
+
+	#if	__ASSERT_ON_ZERO_RADIUS__
+	Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
+	#endif	
+
+	// kick off the frustum culling tests in VU0
+	asm __volatile__("
+
+		lqc2		vf08,(%0)					# sphere (x,y,z,R)
+		lqc2		vf09,(%1)					# box (bx,by,bz,?)
+		vcallms		BothCullTests				# call microsubroutine
+
+	": : "r" (&m_bounding_sphere), "r" (&m_bounding_box) );
+
+
+#ifdef	__NOPT_ASSERT__
+	m_flags &= ~NODEFLAG_WAS_RENDERED;		// Clear flag saying it was rendered
+#endif
+
+	GEOMSTATINC(geom_stats_total);
+
+
+//Mick:  Minimal leaf rendering makes no real difference 
+// so I've removed this call while refactoring	
+
+	// check for minimal leaf node for a quicker render
+	if (!(m_flags & (NODEFLAG_COLOURED		|
+					 NODEFLAG_BILLBOARD		|
+					 NODEFLAG_UVWIBBLE		|
+					 NODEFLAG_VCWIBBLE		|
+					 NODEFLAG_ENVMAPPED))
+
+		&& !(renderFlags & (RENDERFLAG_COLOURED		|
+							#if !STENCIL_SHADOW
+							RENDERFLAG_SHADOW		|
+							#endif
+							RENDERFLAG_CLIP			|
+							RENDERFLAG_CULL)))
+	{
+		RenderMinimalLeaf(ctxt);
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+		return;
+	}
+
+
+#if OCCLUSION_CACHE
+	bool last_occlusion_check_valid = ( m_flags & NODEFLAG_LAST_OCCLUSION_VALID );
+	if( last_occlusion_check_valid )
+	{
+		// Clear the valid flag.
+		m_flags &= ~NODEFLAG_LAST_OCCLUSION_VALID;
+
+		// Was this object occluded last frame? If so, occlude this frame also.
+		if( m_flags & NODEFLAG_LAST_OCCLUSION_TRUE )
+		{
+			m_flags &= ~NODEFLAG_LAST_OCCLUSION_TRUE;
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+		return;
+		}
+	}
+#endif
+
+
+	// view-volume bounding sphere tests, based on parent render flags
+	// MickNote:  I experimented with only clipping the leaf nodes, but this was slightly
+	// slower, as the higher level culling must cull enough to justify the overhead)
+	if ( renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
+	{
+		GEOMSTATINC(geom_stats_clipcull);
+
+		if (CullAgainstViewFrustum())
+		{
+			GEOMSTATINC(geom_stats_culled);
+			GEOMSTATINC(geom_stats_leaf_culled);
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+			return;
+		}
+
+		if (!CullAgainstOuterFrustum())
+		{
+			renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
+		}
+	}
+
+
+	// Occlusion test.
+#if OCCLUSION_CACHE
+	if( !last_occlusion_check_valid )
+#endif	
+	{
+		
+#if OCCLUSION_CACHE
+		m_flags |= NODEFLAG_LAST_OCCLUSION_VALID;
+#endif
+		GEOMSTATINC(geom_stats_occludecheck);
+		if (TestSphereAgainstOccluders( &m_bounding_sphere, m_bounding_sphere[W] ))
+		{
+			GEOMSTATINC(geom_stats_occluded);
+#if OCCLUSION_CACHE
+			m_flags |= NODEFLAG_LAST_OCCLUSION_TRUE;
+#endif				
+			#ifdef	DUMP_NODE_STRUCTURE
+				node_level--;
+			#endif
+			return;
+		}
+	}
+
+
+	#if !STENCIL_SHADOW
+	// shadow test
+	if (renderFlags & RENDERFLAG_SHADOW)
+	{
+		if (m_flags & (NODEFLAG_BILLBOARD | NODEFLAG_ENVMAPPED))
+		{
+			renderFlags &= ~RENDERFLAG_SHADOW;
+		}
+		else if (( m_bounding_sphere[X] + m_bounding_box[X] ) < ( render::ShadowCameraPosition[X] - SHADOW_CAM_FRUSTUM_HALF_WIDTH  )	||
+				 ( m_bounding_sphere[X] - m_bounding_box[X] ) > ( render::ShadowCameraPosition[X] + SHADOW_CAM_FRUSTUM_HALF_WIDTH  )	||
+				 ( m_bounding_sphere[Z] + m_bounding_box[Z] ) < ( render::ShadowCameraPosition[Z] - SHADOW_CAM_FRUSTUM_HALF_HEIGHT )	||
+				 ( m_bounding_sphere[Z] - m_bounding_box[Z] ) > ( render::ShadowCameraPosition[Z] + SHADOW_CAM_FRUSTUM_HALF_HEIGHT ))
+		{
+			renderFlags &= ~RENDERFLAG_SHADOW;
+		}
+	}
+	#endif
+
+	// get this node's transform context
+	CVu1Context *p_ctxt = &ctxt;
+
+	// applying a colour to a node
+	if (m_flags & NODEFLAG_COLOURED)
+	{
+		GEOMSTATINC(geom_stats_colored);
+		p_ctxt = ctxt.Localise();
+
+		// copy node's colour to vu1 context
+		p_ctxt->SetColour(this);
+		renderFlags |= RENDERFLAG_COLOURED;
+	}
+
+	GEOMSTATINC(geom_stats_leaf);
+	#ifdef	__NOPT_ASSERT__
+	// Mick: check patch variables for debugging toggle of multi-passes
+	if ((m_flags & gPassMask1) != gPassMask0)
+	{	
+#ifdef	DUMP_NODE_STRUCTURE
+node_level--;
+#endif
+		return;
+	}
+	#endif
+
+
+	asm __volatile__("
+		qmfc2		$8,$vf05
+		sw			$8,(%0)
+	": : "r" (&camera_sphere_z) : "$8" );
+
+	#ifdef	__LOD_MULTIPASS__
+	// if 2nd+ pass, check to see if distance from camera is greater than allowed for multipass
+	if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere_z > render::sMultipassMaxDist))
+	{
+#ifdef	DUMP_NODE_STRUCTURE
+node_level--;
+#endif
+		return;
+	}
+	#endif
+
+	if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED | NODEFLAG_VCWIBBLE))
+	{
+		if (m_flags & (NODEFLAG_UVWIBBLE | NODEFLAG_ENVMAPPED))
+		{
+			// if there's no local context, copy the global one
+			if (p_ctxt == &ctxt)
+			{
+				p_ctxt = ctxt.LocaliseExtend();
+			}
+
+			// uv wibble
+			if (m_flags & NODEFLAG_UVWIBBLE)
+			{
+				GEOMSTATINC(geom_stats_wibbleUV);
+				p_ctxt->WibbleUVs(mp_uv_wibble, m_flags&NODEFLAG_EXPLICIT_UVWIBBLE);
+			}
+
+			// environment mapping
+			if (m_flags & NODEFLAG_ENVMAPPED)
+			{
+				// reflection map vectors
+				p_ctxt->SetReflectionVecs((sint16)(uint)mp_uv_wibble, (sint16)(((uint)mp_uv_wibble)>>16));
+			}
+		}
+
+		// vc wibble
+		if (m_flags & NODEFLAG_VCWIBBLE)
+		{
+			GEOMSTATINC(geom_stats_wibbleVC);
+			WibbleVCs();
+		}
+	}
+
+	// set the dma context
+	dma::SetList(u3.mp_group);
+	u3.mp_group->Used[render::Field] = true;
+
+	// flag the texture as used
+	if (u4.mp_texture)
+	{
+		u4.mp_texture->m_render_count++;
+	}
+
+
+	Mth::Vector *p_trans = p_ctxt->GetTranslation();
+
+	// initial cnt tag, for sending row reg and z-sort key
+	dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere_z);
+	vif::NOP();
+	vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
+			   (int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
+			   (int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
+
+
+	// special case for billboards
+	if (m_flags & NODEFLAG_BILLBOARD)
+	{
+		// compute billboard basis vecs
+		Mth::Vector udir,vdir;
+		vdir = (*p_ctxt->GetMatrix())[1];
+		vdir[2] =  0.0f;
+		vdir[3] =  0.0f;
+		vdir.Normalize();
+		udir[0] =  vdir[1];
+		udir[1] = -vdir[0];
+		udir[2] =  0.0f;
+		udir[3] =  0.0f;
+
+		udir *= render::CameraToFrustum;
+		vdir *= render::CameraToFrustum;
+		udir[2] =  0.0f;
+		udir[3] =  0.0f;
+		vdir[2] =  0.0f;
+		vdir[3] =  0.0f;
+
+		// send vu1 context
+		dma::Tag(dma::next, 16, (uint)dma::pLoc+272);
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		uint vu1_loc = vu1::Loc;
+		vu1::Loc = 0;   							// must do this as a relative prim for a sortable list...
+		vu1::BeginPrim(REL, VU1_ADDR(L_VF20));
+		vu1::StoreVec(*(Vec *)&udir);
+		vu1::StoreVec(*(Vec *)&vdir);
+		vu1::StoreVec(*(Vec *)&render::IntViewportScale);
+		vif::StoreV4_32(0,0,0,0);
+		vif::StoreV4_32(0,0,0,0);
+		vu1::StoreVec(*(Vec *)&render::CameraToWorld[3]);
+		vu1::StoreVec(*(Vec *)ctxt.GetColour());
+		vu1::StoreVec(*(Vec *)&render::IntViewportOffset);
+		Mth::Matrix LocalToFrustum = (*p_ctxt->GetMatrix()) * render::CameraToFrustum;
+		vu1::CopyMat((float*)&LocalToFrustum);
+		vu1::EndPrim(0);
+
+		// gs context data, specifically the fog coefficient
+		float w,f;
+		if (renderFlags & RENDERFLAG_TRANSFORMED)
+		{
+			w = -(*p_ctxt->GetMatrix())[3][2];
+		}
+		else
+		{
+			// Context only contains WorldToCamera matrix, so convert world position to camera relative position
+			Mth::Vector camera_rel_pos = render::WorldToCamera.TransformAsPos(m_bounding_sphere);
+			w = -camera_rel_pos[2];
+		}
+		if ((w > render::FogNear) && render::EnableFog)	// Garrett: We have to check for EnableFog here because the VU1 code isn't
+		{
+			f = 1.0 + render::EffectiveFogAlpha * (render::FogNear/w - 1.0f);
+		}
+		else
+		{
+			f = 1.0f;
+		}
+		gs::BeginPrim(REL,0,0);
+		gs::Reg1(gs::FOG, PackFOG((int)(f*255.99f)));
+		gs::EndPrim(0);
+
+		dma::EndTag();
+		((uint16 *)dma::pTag)[1] |= vu1::Loc & 0x3FF;	// must write some code for doing this automatically
+		vu1::Loc += vu1_loc;
+
+		u3.mp_group->pVu1Context = NULL;
+	}
+
+	// send transform context if necessary
+	else if ((p_ctxt != u3.mp_group->pVu1Context) || (u3.mp_group->flags & GROUPFLAG_SORT))
+	{
+		GEOMSTATINC(geom_stats_sendcontext);
+		int qwc = p_ctxt->IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
+		dma::Tag(dma::ref, qwc|(qwc-1)<<16, p_ctxt->GetDma());
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		vu1::Loc += qwc-1;
+		u3.mp_group->pVu1Context = p_ctxt;
+	}
+
+	// render this node by adding to the chosen dma list
+	uint vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP :
+					 (renderFlags & RENDERFLAG_CULL) ? CULL : PROJ;
+	if (renderFlags & RENDERFLAG_COLOURED)
+		vu1_flags |= COLR;
+	if (render::EnableFog && (m_bounding_sphere[W]-camera_sphere_z > render::FogNear))
+		vu1_flags |= FOGE;
+	dma::pTag = dma::pLoc;		// must set this manually if we bypass the dma tag functions
+	dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
+	vif::BASE(vu1::Loc);
+	vif::ITOP(vu1_flags);
+	vu1::Loc += u2.m_dma_tag_lo32>>16;
+
+	if (m_flags & NODEFLAG_BLACKFOG)
+	{
+		dma::Gosub(SET_FOGCOL,2);
+		dma::pLoc -= 8;
+		vif::FLUSHA();
+		vif::NOP();
+	}
+
+	// must add a 'next' tag for sorted groups
+	if (u3.mp_group->flags & GROUPFLAG_SORT)
+	{
+		GEOMSTATINC(geom_stats_sorted);
+		dma::SetList(NULL);
+	}
+
+	#if !STENCIL_SHADOW
+	// render shadow
+	if ((renderFlags & RENDERFLAG_SHADOW) && !(m_flags & NODEFLAG_NOSHADOW))
+	{
+		GEOMSTATINC(geom_stats_shadow);
+		// set the dma context
+		dma::SetList(sGroup::pShadow);
+
+		// set row reg
+		dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere_z);
+		vif::NOP();
+		vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
+				   (int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
+				   (int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
+
+		// send transform context
+		dma::Tag(dma::ref, 18|17<<16, sGroup::pShadow->pVu1Context->GetDma());
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		vu1::Loc += 17;
+
+		// render this node
+		vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP|SHDW :
+					(renderFlags & RENDERFLAG_CULL) ? CULL|SHDW : PROJ|SHDW;
+		if (render::EnableFog && (m_bounding_sphere[W]-camera_sphere_z > render::FogNear))
+			vu1_flags |= FOGE;
+		dma::pTag = dma::pLoc;		// must set this manually if we bypass the dma tag functions
+		dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
+		vif::BASE(vu1::Loc);
+		vif::ITOP(vu1_flags);
+		vu1::Loc += u2.m_dma_tag_lo32>>16;
+
+		if (m_flags & NODEFLAG_BLACKFOG)
+		{
+			dma::Gosub(SET_FOGCOL,2);
+			dma::pLoc -= 8;
+			vif::FLUSHA();
+			vif::NOP();
+		}
+	}
+	#endif
+
+#ifdef	DUMP_NODE_STRUCTURE
+	node_level--;
+#endif
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Mick:  I've moved this function here to improved I-Cache usage while rendering
+
+bool TestSphereAgainstOccluders( Mth::Vector *p_center, float radius, uint32 meshes )
+{
+//	return false;
+	// GJ:  This will catch bugs where a model's bounding sphere
+	// is uninitialized, return inconsistent values depending on
+	// the level you were in (this came up with the create-a-trick
+	// skater, which uses "ref models", which don't have valid bounding
+	// spheres)
+	#if	__ASSERT_ON_ZERO_RADIUS__
+	Dbg_MsgAssert( radius != 0.0f, ( "Wasn't expecting a radius of 0" ) );
+	#endif
+
+#if defined(OCCLUDER_USES_VU0_MACROMODE) || defined(OCCLUDER_USES_VU0_MICROMODE)
+
+	if (sOccluder::sUseVU0)
+	{
+#ifdef	OCCLUDER_USES_VU0_MACROMODE
+		bool result;
+
+		asm __volatile__(
+		"
+		.set noreorder
+		mfc1      $8, %3			 # radius
+		qmtc2     $8, vf9
+
+		vilwr.x   vi01, (vi00)		 # load number of occluders
+
+		lqc2      vf10, 0x0(%1)		 # p_center
+		ctc2      %2, $vi15			 # meshes
+
+		vaddw.x   vf31, vf00, vf00w  # Load 1.0f to VF31x
+
+		# Init loop
+		cfc2      $10,  $vi01		 # move number of occluders to $10
+		beq       $10,  $0, no_occlusions
+		viaddi    vi02, vi00, 1		 # load address of first occluder
+
+occ_loop:
+		viaddi    vi03, vi02, 0		 # and put here, too
+		vlqi.xyzw vf01, (vi03++)	 # load planes
+		li        $9, 5				 # number of planes
+
+plane_loop:
+		# dot product
+		vmul.xyz  vf11,vf01,vf10     # result = plane * center + plane_w [start]
+		vmulax.x  ACC, vf01,vf10x
+		vmaddaw.x ACC, vf31,vf01w	 # plane_w
+		vmaddax.x ACC, vf31,vf09x    # also add radius in
+		vmadday.x ACC, vf31,vf11y
+		vmaddz.x  vf11,vf31,vf11z    # result = plane * center + plane_w + radius [ready]
+
+		addi      $9, $9, -1         # decrement plane counter
+		viaddi    vi04, vi02, 5		 # calc score address (since we are waiting anyway)
+		vilwr.x   vi14, (vi04)		 # load score
+		vlqi.xyzw vf01, (vi03++)	 # load next planes
+		vnop
+
+		cfc2      $8, $vi17			 # transfer if result (MAC register)
+		andi	  $8, $8, 0x80		 # check result (v >= 0) not_occluded
+		beq		  $8, $0, done_occluder
+		nop
+
+		bne       $9, $0, plane_loop
+		nop
+
+		# got through all planes - found occluder
+found_occluder:
+		li		  %0, 1				 # store true for return value
+		viadd     vi14, vi14, vi15   # add meshes
+		b         done
+		viswr.x   vi14, (vi04)
+
+done_occluder:
+		addi      $10, $10, -1       # decrement occluder counter
+		bne       $10, $0, occ_loop
+		viaddi    vi02, vi02, 6		 # increment occluder address
+
+no_occlusions:
+		li		  %0, 0				 # store false for return value
+
+done:
+		.set reorder
+		": "=r" (result) : "r" (p_center), "r" (meshes), "f" (radius) : "$8", "$9", "$10");
+
+		return result;
+
+#endif	// OCCLUDER_USES_VU0_MACROMODE
+	
+#ifdef OCCLUDER_USES_VU0_MICROMODE
+
+		uint32 result, index;
+		Mth::Vector sphere(*p_center);
+		sphere[W] = radius;
+
+		// call vu0 microsubroutine
+		asm __volatile__("
+
+			lqc2		vf07,(%0)					# load sphere (x,y,z,R) into vf07 of vu0
+			vcallms		TestSphereAgainstOccluders	# call microsubroutine
+			vnop									# interlocking instruction, waits for vu0 completion
+			cfc2		$8,$vi01					# get boolean result from vu0
+			cfc2		$9,$vi06					# get index of first successful occluder
+			sw			$8,(%1)						# store boolean result
+			sw			$9,(%2)						# store index
+		
+		": : "r" (&sphere), "r" (&result), "r" (&index) : "$8","$9");
+
+		// keeping score
+		if (result)
+		{
+			sOccluder::Occluders[index].score += meshes;
+		}
+
+		return (bool)result;
+
+#endif	// OCCLUDER_USES_VU0_MICROMODE
+	}
+	else
+#endif // defined(OCCLUDER_USES_VU0_MACROMODE) || defined(OCCLUDER_USES_VU0_MICROMODE)
+
+	{
+		float center_x = p_center->GetX();
+		float center_y = p_center->GetY();
+		float center_z = p_center->GetZ();
+
+		sOccluder * p_occluder;
+		if (sOccluder::sUseScratchPad)
+		{
+			p_occluder = (sOccluder*)0x70000000;
+		} else {
+			p_occluder = &sOccluder::Occluders[0];
+		}
+
+		// Test against each occluder.
+		for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
+		{
+			Mth::Vector * p_plane = &(p_occluder->planes[0]);
+			// Test against each plane in the occluder.
+			for( uint32 p = 5; p > 0; --p )
+			{
+
+				float result =	( p_plane->GetX() * center_x ) +
+								( p_plane->GetY() * center_y ) +
+								( p_plane->GetZ() * center_z ) +
+								( p_plane->GetW() );
+
+				if( result >= -radius )
+				{
+					// Outside of this plane, therefore not occluded by this occluder.
+					goto	NOT_OCCLUDED;
+				}
+				p_plane++;
+			}
+
+			// Inside all five planes, therefore occluded. Increase score for this occluder.
+			p_occluder->score += meshes;
+			return true;
+
+	NOT_OCCLUDED:		
+			p_occluder++;
+		}
+
+		return false;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CGeomNode::CullAgainstViewFrustum()
+{
+
+	uint32 result;
+
+	#if 0
+
+	// test version... perform test and get results
+	asm __volatile__("
+
+		lqc2		vf08,(%0)					# sphere (x,y,z,R)
+		lqc2		vf09,(%1)					# box (bx,by,bz,?)
+		vcallms		ViewCullTest				# call microsubroutine
+		vnop									# interlocking instruction, waits for vu0 completion
+		cfc2		$8,$vi01					# get result
+		sw			$8,(%2)						# store result
+
+	": : "r" (&m_bounding_sphere), "r" (&m_bounding_box), "r" (&result) : "$8");
+
+
+	#else
+	
+	// faster version... pick up results from test which kicked off earlier
+	asm __volatile__("
+
+		vnop									# interlocking instruction, waits for vu0 completion
+		cfc2		$8,$vi01					# get result
+		sw			$8,(%0)						# store result
+
+	": : "r" (&result) : "$8");
+
+	#endif
+
+	return (bool)result;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CGeomNode::CullAgainstOuterFrustum()
+{
+
+	uint32 result;
+
+	#if 0
+	
+	// test version... perform test and get results
+	asm __volatile__("
+
+		lqc2		vf08,(%0)					# sphere (x,y,z,R)
+		lqc2		vf09,(%1)					# box (bx,by,bz,?)
+		vcallms		OuterCullTest				# call microsubroutine
+		vnop									# interlocking instruction, waits for vu0 completion
+		cfc2		$8,$vi01					# get result
+		sw			$8,(%2)						# store result
+
+	": : "r" (&m_bounding_sphere), "r" (&m_bounding_box), "r" (&result) : "$8");
+
+	#else
+	
+	// faster version... pick up results from test which kicked off earlier
+	asm __volatile__("
+
+		cfc2		$8,$vi11					# get result
+		sw			$8,(%0)						# store result
+
+	": : "r" (&result) : "$8");
+
+	#endif
+
+	return (bool)result;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeomNode::RenderMinimalLeaf(CVu1Context& ctxt)
+{
+	float camera_sphere_z;
+
+	#if	__ASSERT_ON_ZERO_RADIUS__
+	Dbg_MsgAssert( m_bounding_sphere[W] != 0.0f, ( "Wasn't expecting a radius of 0 for geomnode %s or a sub-mesh of it. \n perhaps it has isolated verts that need removing?",Script::FindChecksumName(GetChecksum()) ) );
+	#endif
+	
+	GEOMSTATINC(geom_stats_total);
+
+	GEOMSTATINC(geom_stats_occludecheck);
+	if (TestSphereAgainstOccluders( &m_bounding_sphere, m_bounding_sphere[W] ))
+	{
+		GEOMSTATINC(geom_stats_occluded);
+		return;
+	}
+
+	GEOMSTATINC(geom_stats_leaf);
+	#ifdef	__NOPT_ASSERT__
+	// Mick: check patch variables for debugging toggle of multi-passes
+	if ((m_flags & gPassMask1) != gPassMask0)
+	{	
+		return;
+	}
+	#endif
+
+
+	// set the dma context
+	dma::SetList(u3.mp_group);
+	u3.mp_group->Used[render::Field] = true;
+
+	// flag the texture as used
+	if (u4.mp_texture)
+	{
+		u4.mp_texture->m_render_count++;
+	}
+
+
+	Mth::Vector *p_trans = ctxt.GetTranslation();
+	
+
+	asm __volatile__("
+		qmfc2		$8,$vf05
+		sw			$8,(%0)
+	": : "r" (&camera_sphere_z) : "$8" );
+
+
+	#ifdef	__LOD_MULTIPASS__
+	// if 2nd+ pass, check to see if distance from camera is greater than allowed for multipass
+//		if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere[Z] > render::sMultipassMaxDist))
+	if ((m_flags & NODEFLAG_ZPUSH) && (-camera_sphere_z > render::sMultipassMaxDist))
+		return;
+	#endif
+
+	// initial cnt tag, for sending row reg and z-sort key
+	dma::Tag(dma::cnt, 1, *(uint32 *)&camera_sphere_z);
+	vif::NOP();
+	vif::STROW((int)(*p_trans)[0] + (int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
+			   (int)(*p_trans)[1] + (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
+			   (int)(*p_trans)[2] + (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
+
+	// send transform context if necessary
+	if ((&ctxt != u3.mp_group->pVu1Context) || (u3.mp_group->flags & GROUPFLAG_SORT))
+	{
+		GEOMSTATINC(geom_stats_sendcontext);
+		//Dbg_MsgAssert(!ctxt.IsExtended(), ("oh bugger"));
+		int qwc = ctxt.IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
+		dma::Tag(dma::ref, qwc|(qwc-1)<<16, ctxt.GetDma());
+		vif::BASE(vu1::Loc);
+		vif::OFFSET(0);
+		vu1::Loc += qwc-1;
+		u3.mp_group->pVu1Context = &ctxt;
+	}
+
+	// render this node by adding to the chosen dma list
+	dma::pTag = dma::pLoc;		// must set this manually if we bypass the dma tag functions
+	dma::Store32(u2.m_dma_tag_lo32,(uint32)u1.mp_dma);
+	vif::BASE(vu1::Loc);
+	if (render::EnableFog && (m_bounding_sphere[W]-camera_sphere_z > render::FogNear))
+		vif::ITOP(PROJ|FOGE);
+	else
+		vif::ITOP(PROJ);
+	vu1::Loc += u2.m_dma_tag_lo32>>16;
+
+	if (m_flags & NODEFLAG_BLACKFOG)
+	{
+		dma::Gosub(SET_FOGCOL,2);
+		dma::pLoc -= 8;
+		vif::FLUSHA();
+		vif::NOP();
+	}
+
+	// must add a 'next' tag for sorted groups
+	if (u3.mp_group->flags & GROUPFLAG_SORT)
+	{
+		GEOMSTATINC(geom_stats_sorted);
+		dma::SetList(NULL);
+	}
+	
+	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeomNode::RenderAsSky(CVu1Context& ctxt, uint32 renderFlags)
+{
+	CVu1Context *p_ctxt;
+
+	// cull the node if not active
+	if (!(m_flags & NODEFLAG_ACTIVE))
+	{
+		return;
+	}
+
+	// no support for transformed nodes yet...
+	p_ctxt = &ctxt;
+
+	Mth::Vector sphere;
+
+	// bounding sphere tests, based on parent render flags
+	if (renderFlags & (RENDERFLAG_CLIP | RENDERFLAG_CULL))
+	{
+		// get and transform centre of bounding sphere
+		sphere = m_bounding_sphere;
+		sphere[W] = 1.0f;
+		sphere *= *p_ctxt->GetMatrix();
+		sphere[W] = m_bounding_sphere[W];
+					  
+		if (SphereIsOutsideViewVolume(sphere))
+		{
+			return;
+		}
+
+		if (SphereIsInsideOuterVolume(sphere))
+		{
+			renderFlags &= ~(RENDERFLAG_CLIP|RENDERFLAG_CULL);
+		}
+
+		else if ((renderFlags & RENDERFLAG_CLIP) && !NeedsClipping(sphere))
+		{
+			renderFlags = renderFlags & ~RENDERFLAG_CLIP | RENDERFLAG_CULL;
+		}
+    }
+	
+	// applying a colour to a node (copied from Geomnode::Render)
+	if (m_flags & NODEFLAG_COLOURED)
+	{
+		GEOMSTATINC(geom_stats_colored);
+		// if there's no local context, copy the parent's
+		if (p_ctxt == &ctxt)
+		{
+			p_ctxt = ctxt.Localise();
+		}
+
+		// copy node's colour to vu1 context
+		p_ctxt->SetColour(this);
+		renderFlags |= RENDERFLAG_COLOURED;
+	}
+
+
+	// is it a leaf node?
+	if (m_flags & NODEFLAG_LEAF)
+	{
+		#ifdef	__NOPT_ASSERT__
+		// Mick: check patch variables for debugging toggle of multi-passes
+		if ((m_flags & gPassMask1) != gPassMask0)
+		{	
+			return;
+		}
+		#endif
+		
+		// uv wibble
+		if (m_flags & NODEFLAG_UVWIBBLE)
+		{
+			// if there's no local context, copy the global one
+			if (p_ctxt == &ctxt)
+			{
+				p_ctxt = ctxt.LocaliseExtend();
+			}
+
+			GEOMSTATINC(geom_stats_wibbleUV);
+			p_ctxt->WibbleUVs(mp_uv_wibble, m_flags&NODEFLAG_EXPLICIT_UVWIBBLE);
+		}
+
+		// set the dma context
+		dma::SetList(u3.mp_group);
+		u3.mp_group->Used[render::Field] = true;
+
+		// initial cnt tag, for sending row reg and z-sort key
+		dma::Tag(dma::cnt, 1, 0);
+		vif::NOP();
+		vif::STROW((int)(m_bounding_sphere[0]*SUB_INCH_PRECISION),
+				   (int)(m_bounding_sphere[1]*SUB_INCH_PRECISION),
+				   (int)(m_bounding_sphere[2]*SUB_INCH_PRECISION), 0);
+
+		// send transform context if necessary
+		if (p_ctxt != u3.mp_group->pVu1Context)
+		{
+			int qwc = p_ctxt->IsExtended() ? EXT_CTXT_SIZE+2 : STD_CTXT_SIZE+2;
+			dma::Tag(dma::ref, qwc|(qwc-1)<<16, p_ctxt->GetDma());
+			vif::BASE(vu1::Loc);
+			vif::OFFSET(0);
+			vu1::Loc += qwc-1;
+			u3.mp_group->pVu1Context = p_ctxt;
+		}
+
+		// render this node by adding to the chosen dma list
+		uint vu1_flags = (renderFlags & RENDERFLAG_CLIP) ? CLIP :
+						 (renderFlags & RENDERFLAG_CULL) ? CULL : PROJ;
+		if (renderFlags & RENDERFLAG_COLOURED)
+			vu1_flags |= COLR;
+		if (render::EnableFog)
+			vu1_flags |= FOGE;
+		dma::pTag = dma::pLoc;		// must set this manually if we bypass the dma tag functions
+		dma::Store32(u2.m_dma_tag_lo32, (uint32)u1.mp_dma);
+		vif::BASE(vu1::Loc);
+		vif::ITOP(vu1_flags);
+		vu1::Loc += u2.m_dma_tag_lo32>>16;
+
+		if (m_flags & NODEFLAG_BLACKFOG)
+		{
+			dma::Gosub(SET_FOGCOL,2);
+			dma::pLoc -= 8;
+			vif::FLUSHA();
+			vif::NOP();
+		}
+	}
+	else
+	{
+		// render any children
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->RenderAsSky(*p_ctxt, renderFlags);
+		}
+	}
+
+}
+
+
+
+void CGeomNode::SetBoneTransforms(Mth::Matrix *pMat)
+{
+
+	#if 0	// single-buffered
+	
+	// copy the given matrices into the matrix buffer
+	for (int i = 0; i < m_num_bones; i++)
+	{
+		u4.mp_bone_transforms[i] = pMat[i];
+	}
+
+	#else	// double-buffered
+	
+	int startIndex = m_field ? m_num_bones : 0;
+
+	// copy the given matrices into the correct half of the matrix buffer
+	for (int i = 0; i < m_num_bones; i++)
+	{
+		u4.mp_bone_transforms[startIndex+i] = pMat[i];
+	}
+
+	// toggle field
+	m_field = !m_field;
+
+	#endif
+	
+}
+
+
+
+#endif
+
+
+bool CGeomNode::IsLeaf()
+{
+	return GET_FLAG(NODEFLAG_LEAF);
+}
+
+
+bool CGeomNode::IsObject()
+{
+	return GET_FLAG(NODEFLAG_OBJECT);
+}
+
+
+bool CGeomNode::IsActive()
+{
+	return GET_FLAG(NODEFLAG_ACTIVE);
+}
+
+
+bool CGeomNode::IsTransformed()
+{
+	return GET_FLAG(NODEFLAG_TRANSFORMED);
+}
+
+
+bool CGeomNode::IsSky()
+{
+	return GET_FLAG(NODEFLAG_SKY);
+}
+
+
+bool CGeomNode::IsColored()
+{
+	return GET_FLAG(NODEFLAG_COLOURED);
+}
+
+
+bool CGeomNode::IsSkeletal()
+{
+	return GET_FLAG(NODEFLAG_SKELETAL);
+}
+
+
+bool CGeomNode::IsEnvMapped()
+{
+	return GET_FLAG(NODEFLAG_ENVMAPPED);
+}
+
+
+bool CGeomNode::IsUVWibbled()
+{
+	return GET_FLAG(NODEFLAG_UVWIBBLE);
+}
+
+bool CGeomNode::IsBillboard()
+{
+	return GET_FLAG(NODEFLAG_BILLBOARD);
+}
+
+
+#ifdef	__NOPT_ASSERT__
+bool CGeomNode::WasRendered()
+{
+	return GET_FLAG(NODEFLAG_WAS_RENDERED);
+}
+#endif
+
+void CGeomNode::SetLeaf(bool yes)
+{
+	SET_FLAG(NODEFLAG_LEAF);
+}
+
+
+void CGeomNode::SetObject(bool yes)
+{
+	SET_FLAG(NODEFLAG_OBJECT);
+}
+
+
+void CGeomNode::SetActive(bool yes)
+{
+	SET_FLAG(NODEFLAG_ACTIVE);
+}
+
+
+void CGeomNode::SetTransformed(bool yes)
+{
+	SET_FLAG(NODEFLAG_TRANSFORMED);
+}
+
+
+void CGeomNode::SetSky(bool yes)
+{
+	SET_FLAG(NODEFLAG_SKY);
+}
+
+
+void CGeomNode::SetZPush0(bool yes)
+{
+	SET_FLAG(NODEFLAG_ZPUSH0);
+}
+
+
+void CGeomNode::SetZPush1(bool yes)
+{
+	SET_FLAG(NODEFLAG_ZPUSH1);
+}
+
+
+void CGeomNode::SetZPush2(bool yes)
+{
+	SET_FLAG(NODEFLAG_ZPUSH2);
+}
+
+
+void CGeomNode::SetZPush3(bool yes)
+{
+	SET_FLAG(NODEFLAG_ZPUSH3);
+}
+
+
+void CGeomNode::SetNoShadow(bool yes)
+{
+	SET_FLAG(NODEFLAG_NOSHADOW);
+}
+
+
+void CGeomNode::SetUVWibbled(bool yes)
+{
+	SET_FLAG(NODEFLAG_UVWIBBLE);
+}
+
+
+void CGeomNode::SetVCWibbled(bool yes)
+{
+	SET_FLAG(NODEFLAG_VCWIBBLE);
+}
+
+
+void CGeomNode::SetColored(bool yes)
+{
+	SET_FLAG(NODEFLAG_COLOURED);
+}
+
+
+void CGeomNode::SetSkinned(bool yes)
+{
+	SET_FLAG(NODEFLAG_SKINNED);
+}
+
+
+void CGeomNode::SetBillboard(bool yes)
+{
+	SET_FLAG(NODEFLAG_BILLBOARD);
+}
+
+
+void CGeomNode::SetSkeletal(bool yes)
+{
+	SET_FLAG(NODEFLAG_SKELETAL);
+}
+
+
+void CGeomNode::SetEnvMapped(bool yes)
+{
+	SET_FLAG(NODEFLAG_ENVMAPPED);
+}
+
+
+void CGeomNode::SetBlackFog(bool yes)
+{
+	SET_FLAG(NODEFLAG_BLACKFOG);
+}
+
+
+uint32 CGeomNode::GetChecksum()
+{
+	return m_checksum;
+}
+
+
+void CGeomNode::SetChecksum(uint32 checksum)
+{
+	m_checksum = checksum;
+}
+
+
+void CGeomNode::SetBoundingSphere(float x, float y, float z, float R)
+{
+	m_bounding_sphere[0] = x;
+	m_bounding_sphere[1] = y;
+	m_bounding_sphere[2] = z;
+	m_bounding_sphere[3] = R;
+}
+
+
+void CGeomNode::SetBoundingBox(float x, float y, float z)
+{
+	m_bounding_box[0] = x;
+	m_bounding_box[1] = y;
+	m_bounding_box[2] = z;
+}
+
+
+uint8 *CGeomNode::GetDma()
+{
+	return (m_flags & NODEFLAG_LEAF) ? u1.mp_dma : NULL;
+}
+
+
+void CGeomNode::SetDma(uint8 *pDma)
+{
+	m_flags |= NODEFLAG_LEAF;
+	u1.mp_dma = pDma;
+}
+
+
+void CGeomNode::SetDmaTag(uint8 *pDma)
+{
+	m_flags |= NODEFLAG_LEAF;
+	u2.m_dma_tag_lo32 = dma::call<<28 | (((uint16 *)pDma)[1]&0x3FF)<<16 | 0;
+	u1.mp_dma = pDma;
+}
+
+
+CGeomNode *CGeomNode::GetChild()
+{
+	return (m_flags & NODEFLAG_LEAF) ? NULL : u1.mp_child;
+}
+
+
+void CGeomNode::SetChild(CGeomNode *pChild)
+{
+	m_flags &= ~NODEFLAG_LEAF;
+	u1.mp_child = pChild;
+}
+
+void CGeomNode::AddChild(CGeomNode *pChild)
+{
+	if (!(m_flags & NODEFLAG_LEAF) && u1.mp_child)
+	{
+		Dbg_Assert(!pChild->mp_sibling);
+
+		CGeomNode *p_last_child = u1.mp_child;
+		while (p_last_child->mp_sibling)
+		{
+			p_last_child = p_last_child->mp_sibling;
+		}
+
+		p_last_child->mp_sibling = pChild;
+	} else {
+		u1.mp_child = pChild;
+	}
+
+	m_flags &= ~NODEFLAG_LEAF;
+}
+
+
+CGeomNode *CGeomNode::GetSibling()
+{
+	return mp_sibling;
+}
+
+
+void CGeomNode::SetSibling(CGeomNode *pSibling)
+{
+	mp_sibling = pSibling;
+}
+
+void CGeomNode::SetSlaveLOD(CGeomNode *pLOD)
+{
+    CGeomNode *pLOD_list = this;
+
+	while (pLOD_list->mp_next_LOD) {
+		if (pLOD_list->mp_next_LOD->m_LOD_far_dist > pLOD->m_LOD_far_dist) {
+			pLOD->mp_next_LOD = pLOD_list->mp_next_LOD;
+			pLOD_list->mp_next_LOD = pLOD;
+			return;
+		}
+
+		pLOD_list = pLOD_list->mp_next_LOD;
+	}
+
+	// If we get here, then we are at the end of the list
+	pLOD_list->mp_next_LOD = pLOD; 
+}
+	
+void CGeomNode::SetLODFarDist(float LOD_far_dist)
+{
+	m_LOD_far_dist = LOD_far_dist;
+}
+
+float CGeomNode::GetLODFarDist()
+{
+	return m_LOD_far_dist;
+}
+
+sGroup *CGeomNode::GetGroup()
+{
+	return u3.mp_group;
+}
+
+
+void CGeomNode::SetGroup(sGroup *pGroup)
+{
+	u3.mp_group = pGroup;
+}
+
+
+Mth::Matrix& CGeomNode::GetMatrix()
+{
+	Dbg_MsgAssert(IsTransformed(), ("trying to access matrix of a non-transformed CGeomNode"));
+	Dbg_MsgAssert(u2.mp_transform, ("null matrix pointer"));
+	Dbg_MsgAssert(!(m_flags&NODEFLAG_LEAF), ("Transformed leaf node\n"));
+	return *u2.mp_transform;
+}
+
+
+void CGeomNode::SetMatrix(Mth::Matrix *p_mat)
+{
+	Dbg_MsgAssert(!(m_flags&NODEFLAG_LEAF), ("Transformed leaf node\n"));
+	u2.mp_transform = (Mth::Matrix *)p_mat;
+	SetTransformed(p_mat ? true : false);
+}
+
+
+void CGeomNode::SetUVWibblePointer(float *pData)
+{
+	mp_uv_wibble = pData;
+}
+
+
+void CGeomNode::SetVCWibblePointer(uint32 *pData)
+{
+	mp_vc_wibble = pData;
+}
+
+
+void CGeomNode::SetColor(uint32 rgba)
+{
+	if (rgba == 0x80808080)
+	{
+		// clear the colored falg if set to the neutral color
+		// as things render a lot quicker that way
+		// also, applying the color gives a slightly different
+		// value from not applying any color
+		SetColored(false);
+	}
+	
+	m_colour = rgba;
+}
+
+
+uint32 CGeomNode::GetColor()
+{
+	if (IsColored())
+	{
+		return m_colour;
+	} else {
+		return 0x80808080;
+	}
+}
+
+
+void CGeomNode::SetLightGroup(CLightGroup *p_light_group)
+{
+	if (!IsLeaf())
+	{
+		u3.mp_light_group = p_light_group;
+	}
+}
+
+
+CLightGroup * CGeomNode::GetLightGroup()
+{
+	if (!IsLeaf())
+	{
+		return u3.mp_light_group;
+	} else {
+		return NULL;
+	}
+}
+
+
+void CGeomNode::SetVisibility(uint8 mask)
+{
+	m_flags &= ~ALL_VISIBILITY_BITS;		// Take out old visibility bits
+	m_flags |= (mask << VISIBILITY_FLAG_BIT);
+}
+
+
+uint8 CGeomNode::GetVisibility()
+{
+	return (m_flags & ALL_VISIBILITY_BITS) >> VISIBILITY_FLAG_BIT;
+}
+
+
+void CGeomNode::SetBoneIndex(sint8 index)
+{
+	m_bone_index = index;
+}
+
+
+sint8 CGeomNode::GetBoneIndex()
+{
+	return m_bone_index;
+}
+
+void	CGeomNode::CountMetrics(CGeomMetrics *p_metrics)
+{
+	p_metrics->m_total++;
+	if (IsObject()) 
+	{
+		p_metrics->m_object++;				
+	}
+	if (IsLeaf())
+	{
+		p_metrics->m_leaf++;
+		p_metrics->m_verts += dma::GetNumVertices(u1.mp_dma);
+		p_metrics->m_polys += dma::GetNumTris(u1.mp_dma);
+	}
+	else
+	{
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->CountMetrics(p_metrics);
+		}
+	}
+	
+}
+
+
+static Mth::Vector	*p_verts = NULL; 
+
+void	CGeomNode::RenderWireframe(int mode)
+{
+#ifdef	__PLAT_NGPS__
+	int lines = 0;
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+	p_verts = new Mth::Vector[100000];
+	Mem::Manager::sHandle().PopContext();
+	RenderWireframeRecurse(mode, lines);
+	delete p_verts;
+	p_verts = NULL;
+#endif
+}
+
+void	CGeomNode::RenderWireframeRecurse(int mode, int &lines)
+{
+
+#ifdef	__PLAT_NGPS__
+	int sectors = 0;
+	int vert_count=0;
+
+	if (IsLeaf())
+	{
+		if (lines < 95000)  // clmap number of lines, otherwise we crash
+		{
+			sectors++;
+		
+			//
+			// Do renderable geometry
+			int verts = GetNumVerts();
+			if (verts>2)
+			{
+				vert_count += verts;  
+				  
+				#define	peak 500
+				int n=GetNumPolys();
+				int r,g,b;
+				r=g=b=0;
+				if (n <= peak )
+				{
+					r = (255 * (n)) / peak;  	// r ramps up (black to red)	
+				}
+				else if (n <= peak * 2)
+				{
+					r = 255;
+					b = (255 * (n - peak) / (peak));	// b&g ramps up (to white)
+					g = b;
+					
+				}
+				else
+				{
+					r = g = b = 255;
+				}
+				uint32 rgb = (b<<16)|(g<<8)|r;	
+	
+				
+				if (mode == 4)
+				{
+					// generate a random looking number that's basically a hash of some
+					// values in the node, so it stays the same
+					uint32 xxx = ((uint32)(this) * verts  * n);
+					NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & xxx));		
+				}
+				else
+				{
+					NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));		
+				}
+				
+				GetVerts(p_verts);
+				Mth::Vector *p_vert = p_verts+1;
+				for (int i = 1; i < verts; i++)
+				{
+					NxPs2::DrawLine3D((*p_vert)[X],(*p_vert)[Y],(*p_vert)[Z],p_vert[-1][X],p_vert[-1][Y],p_vert[-1][Z]);								
+					p_vert++;
+					lines++;
+				} // end for
+		
+				NxPs2::EndLines3D();
+			} // end if
+		}				
+	}
+	else
+	{
+		if (WasRendered())
+		{
+			for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+			{
+				p_child->RenderWireframeRecurse(mode, lines);
+			}
+		}
+	}
+#endif
+}
+
+
+int	CGeomNode::GetNumVerts()
+{
+	int num_verts = 0;
+
+	if (IsLeaf())
+	{
+		num_verts = dma::GetNumVertices(u1.mp_dma);
+
+	}
+	else
+	{
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				num_verts += mp_next_LOD->GetNumVerts();
+			}
+		}
+
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			num_verts += p_child->GetNumVerts();
+		}
+	}
+
+	return num_verts;
+}
+
+
+int	CGeomNode::GetNumPolys()
+{
+	int num_polys = 0;
+
+	if (IsLeaf())
+	{
+		num_polys = dma::GetNumTris(u1.mp_dma);
+
+	}
+	else
+	{
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				num_polys += mp_next_LOD->GetNumPolys();
+			}
+		}
+
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			num_polys += p_child->GetNumPolys();
+		}
+	}
+
+	return num_polys;
+}
+
+
+int	CGeomNode::GetNumBasePolys()
+{
+	int num_base_polys = 0;
+
+	if (IsLeaf())
+	{
+		if ((m_flags & NODEFLAG_ZPUSH) == 0)		// check it's the base layer
+		{
+			num_base_polys = dma::GetNumTris(u1.mp_dma);
+		}
+	}
+	else
+	{
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				num_base_polys += mp_next_LOD->GetNumBasePolys();
+			}
+		}
+
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			num_base_polys += p_child->GetNumBasePolys();
+		}
+	}
+
+	return num_base_polys;
+}
+
+
+int	CGeomNode::GetNumObjects()
+{
+	int num_objects = 0;
+
+	// Check self
+	if (IsObject())
+	{
+		num_objects++;
+	}
+
+	// Recursively check children
+	if (!IsLeaf())
+	{
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			num_objects += p_child->GetNumObjects();
+		}
+	}
+
+	return num_objects;
+}
+
+
+CGeomNode*	CGeomNode::GetObject(int num, bool root)
+{
+	static int found_so_far;
+
+	if (root)
+	{
+		found_so_far = -1;
+		//Dbg_Message("Start looking for #%d", num);
+	}
+
+	// Check self
+	if (IsObject())
+	{
+		if (++found_so_far == num)
+		{
+			//Dbg_Message("Found object #%d", num);
+			return this;
+		}
+
+		//Dbg_Message("Found object #%d; looking for #%d", found_so_far, num);
+	}
+
+	CGeomNode *p_found = NULL;
+
+	// Recursively check children
+	if (!IsLeaf())
+	{
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_found = p_child->GetObject(num, false);
+			if (p_found)
+			{
+				break;
+			}
+		}
+
+	}
+
+	if (root)
+	{
+		Dbg_Assert(p_found);
+	}
+
+	return p_found;
+}
+
+
+#ifdef __PLAT_WN32__
+
+void CGeomNode::Preprocess(uint8 *p_baseAddress)
+{
+	// recursively process any children
+	CGeomNode *p_child, *p_sibling;
+	if (!(m_flags & NODEFLAG_LEAF))
+	{
+		for (p_child=u1.mp_child; p_child; p_child=p_sibling)
+		{
+			p_sibling = p_child->mp_sibling;
+			p_child->Preprocess(p_baseAddress);
+		}
+	}
+
+	// recursively process any LODs
+	if (mp_next_LOD)
+	{
+		mp_next_LOD->Preprocess(p_baseAddress);
+	}
+
+	if (!(m_flags & NODEFLAG_LEAF))
+	{
+		// convert transform pointer to offset
+		if (u2.mp_transform)
+		{
+			u2.mp_transform = (Mth::Matrix *) ( (uint8 *)u2.mp_transform - p_baseAddress );
+		}
+		else
+		{
+			u2.mp_transform = (Mth::Matrix *)(-1);
+		}
+	}
+
+	if (m_flags & NODEFLAG_LEAF)
+	{
+		// convert dma pointer to offset
+		if (u1.mp_dma)
+		{
+			u1.mp_dma = (uint8 *)( u1.mp_dma - p_baseAddress );
+		}
+		else
+		{
+			u1.mp_dma = (uint8 *)(-1);
+		}
+	}
+	else
+	{
+		// convert child pointer to offset
+		if (u1.mp_child)
+		{
+			u1.mp_child = (CGeomNode *) ( (uint8 *)u1.mp_child - p_baseAddress );
+		}
+		else
+		{
+			u1.mp_child = (CGeomNode *)(-1);
+		}
+	}
+
+	// leave group checksum alone
+
+	// convert sibling pointer to offset
+	if (mp_sibling)
+	{
+		mp_sibling = (CGeomNode *) ( (uint8 *)mp_sibling - p_baseAddress );
+	}
+	else
+	{
+		mp_sibling = (CGeomNode *)(-1);
+	}
+
+	// convert LOD pointer to offset
+	if (mp_next_LOD)
+	{
+		mp_next_LOD = (CGeomNode *) ( (uint8 *)mp_next_LOD - p_baseAddress );
+	}
+	else
+	{
+		mp_next_LOD = (CGeomNode *)(-1);
+	}
+
+	// convert uv wibble pointer to offset
+	if (m_flags & NODEFLAG_UVWIBBLE)
+	{
+		if (!(m_flags & NODEFLAG_ENVMAPPED) || !(m_flags & NODEFLAG_LEAF))		// can't wibble environment-mapped leaves
+		{
+			mp_uv_wibble = (float *) ( (uint8 *)mp_uv_wibble - p_baseAddress );
+		}
+		else
+		{
+			mp_uv_wibble = (float *)(-1);
+		}
+	}
+
+	// convert vc wibble pointer to offset
+	if (m_flags & NODEFLAG_VCWIBBLE)
+	{
+		// first convert mesh info
+		uint32 *p_meshdata = mp_vc_wibble;
+		int seq, num_seqs, vert, num_verts;
+
+		num_seqs = *p_meshdata++;
+		for (seq=0; seqGetChild();
+	// iterate over siblings without recursion
+	while (p_child)	
+	{
+		
+		printf ("%2d:",SNL_Depth);
+		for (int i=i;iGetFlags());
+		
+		CGeomNode *p_next_child = p_child->GetSibling();
+		
+		// if child not a leaf, then just recurse down
+		if (! (p_child->GetFlags() & NODEFLAG_LEAF))
+		{
+			StripNiceLeaves(p_child);
+		}
+		else
+		{
+		
+			// it's a leaf node, so maybe strip it, and then carry on to the siblings
+			if (((p_child->GetFlags() & (NODEFLAG_ACTIVE		|
+				 NODEFLAG_SKY			|
+				 NODEFLAG_TRANSFORMED	|
+				 NODEFLAG_LEAF			|
+				 NODEFLAG_OBJECT		|
+				 NODEFLAG_COLOURED		|
+				 NODEFLAG_SKELETAL		|
+				 NODEFLAG_BILLBOARD		|
+				 NODEFLAG_UVWIBBLE		|
+				 NODEFLAG_VCWIBBLE		|
+				 NODEFLAG_ENVMAPPED))
+
+			==  (NODEFLAG_ACTIVE		|
+				 NODEFLAG_LEAF)))
+			
+			{
+				// strip it out (eventually add it to a quick-render list)
+				if (p_parent->GetChild() == p_child)
+				{
+					// just need to fix up the parent
+					p_parent->SetChild(p_next_child);
+				}
+				else
+				{
+					// find the elder sibling and fix up his sibling
+					CGeomNode *p_elder_sibling = p_parent->GetChild();
+					while (p_elder_sibling->GetSibling() != p_child)
+					{
+						p_elder_sibling = p_elder_sibling->GetSibling();
+						Dbg_Assert(p_elder_sibling);	// catch infinite loop
+					}
+					p_elder_sibling->SetSibling(p_next_child);							
+				}
+				// p_child is now an orphan
+				// TODO - add to quick render list ...
+			}
+		}
+		p_child = p_next_child;
+	}	
+	SNL_Depth--;
+}
+
+
+
+CGeomNode* CGeomNode::sProcessInPlace(uint8* pPipData, uint32 loadFlags)
+{
+	Dbg_MsgAssert(((int)(pPipData) & 0xf) == 0,("pPipData (0x%x) not multiple of 16 \n"
+													,(int)pPipData));
+
+	// Jump to beginning of CGeomNode data
+	pPipData += *(uint32 *)pPipData;
+	
+	// from header, get address of root node
+	CGeomNode *p_root_node = (CGeomNode *) (pPipData + *(uint32 *)pPipData);
+
+	Dbg_MsgAssert(((int)(p_root_node) & 0xf) == 0,("p_root_node (0x%x) not multiple of 16, .SCN.PS2 file corrupt... \n"
+													,(int)p_root_node));
+
+
+
+	// recursively process the tree
+	#ifdef	__NOPT_ASSERT__
+//	int leaves = 
+	#endif
+	p_root_node->ProcessNodeInPlace(pPipData);
+	#ifdef	__NOPT_ASSERT__
+//	printf (">>>>> Number of leaves in geom PIP file = %d\n",leaves);
+	#endif
+
+
+	//StripNiceLeaves(p_root_node);	
+
+	// add the root node to the world or to the database
+	p_root_node->BecomesChildOf((loadFlags & LOADFLAG_RENDERNOW) ? &sWorld : &sDatabase);
+
+	// set the root node's sky flag if loaded as a sky
+	if (loadFlags & LOADFLAG_SKY)
+	{
+		p_root_node->SetSky(true);
+	}
+
+	// return the root node pointer
+	return p_root_node;
+}
+
+void * CGeomNode::sGetHierarchyArray(uint8 *pPipData, int& size)
+{
+	Dbg_MsgAssert(((int)(pPipData) & 0xf) == 0,("pPipData (0x%x) not multiple of 16 \n"
+													,(int)pPipData));
+
+	uint32 *pPip32Data = (uint32 *) pPipData;
+
+	pPip32Data++;		// skip CGeomNode offset
+
+	// Get array pointer and size
+	void *p_array = (pPipData + *(pPip32Data++));
+	size = *pPip32Data;
+
+	if (size)
+	{
+		return p_array;
+	} else {
+		return NULL;
+	}
+}
+
+void CGeomNode::AddToTree(uint32 loadFlags)
+{
+	BecomesChildOf((loadFlags & LOADFLAG_RENDERNOW) ? &sWorld : &sDatabase);
+
+	// set the root node's sky flag if loaded as a sky
+	if (loadFlags & LOADFLAG_SKY)
+	{
+		SetSky(true);
+	}
+}
+
+void CGeomNode::Cleanup()
+{
+	// remove from any node it belongs to
+	RemoveFrom(&sWorld);
+	RemoveFrom(&sDatabase);
+}
+
+
+CGeomNode* CGeomNode::CreateInstance(Mth::Matrix *pMat, CGeomNode *p_parent)
+{
+	// copy node
+	CGeomNode *p_node = new CGeomNode(*this);
+
+	// flag as an instance
+	p_node->m_flags |= NODEFLAG_INSTANCE;
+
+	// set matrix
+	p_node->SetMatrix(pMat);
+
+	// add to world
+	if (p_parent)
+	{
+		p_node->BecomesChildOf(p_parent);
+	}
+	else
+	{
+		p_node->BecomesChildOf(&sWorld);
+	}
+
+	// activate!
+	p_node->SetActive(true);
+
+	// return new node
+	return p_node;
+}
+
+
+// skeletal version
+CGeomNode* CGeomNode::CreateInstance(Mth::Matrix *pMat, int numBones, Mth::Matrix *pBoneTransforms, CGeomNode *p_parent)
+{
+	// copy node
+	CGeomNode *p_node = new CGeomNode(*this);
+
+	// flag as a skeletal instance
+	p_node->m_flags |= NODEFLAG_SKELETAL|NODEFLAG_INSTANCE;
+
+	// set matrix and bone data
+	p_node->SetMatrix(pMat);
+	p_node->m_num_bones = numBones;
+	p_node->u4.mp_bone_transforms = pBoneTransforms;
+	p_node->m_field = 0;
+
+	// add to world
+	if (p_parent)
+	{
+		p_node->BecomesChildOf(p_parent);
+	}
+	else
+	{
+		p_node->BecomesChildOf(&sWorld);
+	}
+
+	// activate!
+	p_node->SetActive(true);
+
+	// return new node
+	return p_node;
+}
+
+
+void CGeomNode::DeleteInstance()
+{
+	Dbg_MsgAssert(m_flags&NODEFLAG_INSTANCE, ("attempt to delete a non-instance"));
+
+	bool removed;
+	removed = RemoveFrom(&sWorld);
+	removed = removed || RemoveFromChildrenOf(&sWorld);		// Check all cloned scenes, too
+
+	Dbg_MsgAssert(removed, ("Couldn't remove instanced CGeomNode from CGeomNode tree"));
+
+	delete this;
+}
+
+CGeomNode* CGeomNode::CreateCopy(CGeomNode *p_parent, bool root)
+{
+	// copy node
+	CGeomNode *p_node = new CGeomNode(*this);
+
+	// clear some pointers
+	p_node->mp_sibling = NULL;
+
+	// add to world if this is the top node
+	if (root)
+	{
+		if (p_parent)
+		{
+			p_node->BecomesChildOf(p_parent);
+		}
+		else
+		{
+			p_node->BecomesChildOf(&sWorld);
+		}
+	}
+
+	// activate!
+	p_node->SetActive(true);
+
+	// Also copy data and tree underneath
+	if (IsLeaf())
+	{
+		// Copy the dma data
+		Dbg_Assert(u1.mp_dma);
+		p_node->u1.mp_dma = new uint8[dma::GetDmaSize(u1.mp_dma)];
+		Dbg_Assert(!((uint32) p_node->u1.mp_dma & 0xf))
+		dma::Copy(u1.mp_dma, p_node->u1.mp_dma);
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				p_node->mp_next_LOD = mp_next_LOD->CreateCopy(NULL, false);
+			}
+		}
+
+		// Recursively copy children
+		p_node->u1.mp_child = NULL;
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_node->AddChild(p_child->CreateCopy(NULL, false));
+		}
+	}
+
+	// return new node
+	return p_node;
+}
+
+void WaitForRendering();
+
+// Recursivly deletes an object and all its children
+void CGeomNode::DeleteCopy(bool root)
+{
+	// Garrett: Kinda hacky, but gets the job done.  Make sure we aren't rendering before
+	// we release DMA buffers
+	WaitForRendering();  
+
+	Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to delete an instance"));
+
+	if (root)
+	{
+		bool removed;
+		removed = RemoveFrom(&sWorld);
+		removed = removed || RemoveFromChildrenOf(&sWorld);		// Check all cloned scenes, too
+
+		Dbg_MsgAssert(removed, ("Couldn't remove copied CGeomNode from CGeomNode tree"));
+	}
+
+	// Also delete data and tree underneath
+	if (IsLeaf())
+	{
+		// Delete the dma data
+		Dbg_Assert(u1.mp_dma);
+		delete [] u1.mp_dma;
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->DeleteCopy(false);
+			}
+		}
+
+		// Recursively delete children
+		// (Mick) restructured this because DeleteCopy will delete the object it is run on
+		CGeomNode *p_child=u1.mp_child;
+		while ( p_child)
+		{
+			CGeomNode *p_next=p_child->mp_sibling;
+			p_child->DeleteCopy(false);
+			p_child = p_next;
+		}
+	}
+
+	delete this;	   // <<<< Warning:  deletes 'this'
+}
+
+void CGeomNode::Translate(const Mth::Vector & delta_pos)
+{
+	Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to translate an instance"));
+
+	// Update bounding sphere
+	m_bounding_sphere[X] += delta_pos[X];
+	m_bounding_sphere[Y] += delta_pos[Y];
+	m_bounding_sphere[Z] += delta_pos[Z];
+
+#if 0
+	// Test for Park Editor
+	Dbg_Message("Translating by (%.8f, %.8f, %.8f)", delta_pos[X], delta_pos[Y], delta_pos[Z]);
+	int delta_x = (int) (delta_pos[X] * SUB_INCH_PRECISION);
+	int delta_y = (int) (delta_pos[Y] * SUB_INCH_PRECISION);
+	int delta_z = (int) (delta_pos[Z] * SUB_INCH_PRECISION);
+	Dbg_MsgAssert(!(delta_x & 0xF), ("Fraction in X: (%x, %x, %x)", delta_x, delta_y, delta_z));
+	Dbg_MsgAssert(!(delta_y & 0xF), ("Fraction in Y: (%x, %x, %x)", delta_x, delta_y, delta_z));
+	Dbg_MsgAssert(!(delta_z & 0xF), ("Fraction in Z: (%x, %x, %x)", delta_x, delta_y, delta_z));
+#endif
+
+	// Update whole tree
+	if (IsLeaf())
+	{
+		// Get the dma data
+		Dbg_Assert(u1.mp_dma);
+
+		if (dma::GetBitLengthXYZ(u1.mp_dma) == 32)
+		{
+			// Calculate int versions
+			int delta_x = (int) (delta_pos[X] * SUB_INCH_PRECISION);
+			int delta_y = (int) (delta_pos[Y] * SUB_INCH_PRECISION);
+			int delta_z = (int) (delta_pos[Z] * SUB_INCH_PRECISION);
+
+			int num_verts = dma::GetNumVertices(u1.mp_dma);
+
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+			int32 *p_verts = new int32[num_verts * 4];
+			dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_verts);		
+
+			for (int i = 0; i < num_verts; i++) {
+				p_verts[(i * 4) + 0] += delta_x;
+				p_verts[(i * 4) + 1] += delta_y;
+				p_verts[(i * 4) + 2] += delta_z;
+			}
+
+			dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_verts);		
+
+			delete [] p_verts;
+
+			Mem::Manager::sHandle().PopContext();
+		}
+		else
+		{
+			Dbg_MsgAssert(dma::GetBitLengthXYZ(u1.mp_dma)==16, ("oh dear, should've been either 16 or 32..."));
+		}
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->Translate(delta_pos);
+			}
+		}
+
+		// Recursively update children
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->Translate(delta_pos);
+		}
+	}
+}
+
+void CGeomNode::RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y, bool root)
+{
+	Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to translate an instance"));
+
+	if (root)
+	{
+		Translate(-world_origin);
+	}
+
+	// Update bounding box
+	// Since they are relative to width and height, we just need to possibly swap X and Z
+	if ((int) rot_y & 1)
+	{
+		float temp = m_bounding_box[X];
+		m_bounding_box[X] = m_bounding_box[Z];
+		m_bounding_box[Z] = temp;
+	}
+
+	// Also rotate bounding sphere position
+	m_bounding_sphere.RotateY90(rot_y);
+
+	// Update whole tree
+	if (IsLeaf())
+	{
+		// Delete the dma data
+		Dbg_Assert(u1.mp_dma);
+
+		int num_verts = dma::GetNumVertices(u1.mp_dma);
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+		int32 *p_verts = new int32[num_verts * 4];
+		dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_verts);		
+
+		for (int i = 0; i < num_verts; i++) {
+			Mth::RotateY90(rot_y,
+						   p_verts[(i * 4) + 0],
+						   p_verts[(i * 4) + 1],
+						   p_verts[(i * 4) + 2]);
+		}
+
+		dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_verts);		
+
+		delete [] p_verts;
+
+		Mem::Manager::sHandle().PopContext();
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->RotateY(world_origin, rot_y, false);
+			}
+		}
+
+		// Recursively update children
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->RotateY(world_origin, rot_y, false);
+		}
+	}
+
+	if (root)
+	{
+		Translate(world_origin);
+	}
+}
+
+void CGeomNode::Scale(const Mth::Vector & world_origin, const Mth::Vector & scale, bool root)
+{
+	Dbg_MsgAssert(!(m_flags & NODEFLAG_INSTANCE), ("attempt to scale an instance"));
+
+	if (root)
+	{
+		Translate(-world_origin);
+	}
+
+	// Update bounding box
+	m_bounding_box[X] *= scale[X];
+	m_bounding_box[Y] *= scale[Y];
+	m_bounding_box[Z] *= scale[Z];
+
+#if 0	// Don't do this since it is a local scale
+	// Also scale bounding sphere position
+	// Update bounding sphere
+	m_bounding_sphere[X] *= scale[X];
+	m_bounding_sphere[Y] *= scale[Y];
+	m_bounding_sphere[Z] *= scale[Z];
+#endif
+
+	// Garrett: The radius calculation is not accurate, but so far, it works.  It will always round up.
+	// We have to assume the largest radius
+	float radius_scale = sqrtf((scale[X] * scale[X]) + (scale[Y] * scale[Y]) + (scale[Z] + scale[Z]));
+	m_bounding_sphere[W] *= radius_scale;
+	// But see if the bounding box radius is smaller
+	float box_radius = sqrtf((m_bounding_box[X] * m_bounding_box[X]) + (m_bounding_box[Y] * m_bounding_box[Y]) + (m_bounding_box[Z] * m_bounding_box[Z]));
+	//Dbg_Message("Calculated sphere radius %f; Bounding box radius %f", m_bounding_sphere[W] , box_radius);
+	m_bounding_sphere[W] = Mth::Min(box_radius, m_bounding_sphere[W]);
+
+	// Update whole tree
+	if (IsLeaf())
+	{
+		// Get the dma data
+		Dbg_Assert(u1.mp_dma);
+
+		// Calculate int versions
+		int scale_x = (int) (scale[X] * SUB_INCH_PRECISION);
+		int scale_y = (int) (scale[Y] * SUB_INCH_PRECISION);
+		int scale_z = (int) (scale[Z] * SUB_INCH_PRECISION);
+
+		// Check to see that we didn't go to zero
+		Dbg_MsgAssert(scale_x, ("CGeomNode::Scale(): X scale went to 0 in integer conversion. Original value %f", scale[X]));
+		Dbg_MsgAssert(scale_y, ("CGeomNode::Scale(): Y scale went to 0 in integer conversion. Original value %f", scale[Y]));
+		Dbg_MsgAssert(scale_z, ("CGeomNode::Scale(): Z scale went to 0 in integer conversion. Original value %f", scale[Z]));
+
+		int num_verts = dma::GetNumVertices(u1.mp_dma);
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+		int32 *p_verts = new int32[num_verts * 4];
+		dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_verts);		
+
+		// SUB_INCH_PRECISION must be a whole number
+		Dbg_Assert( ((int) (SUB_INCH_PRECISION * 1000.0f)) == (((int) SUB_INCH_PRECISION) * 1000) );
+
+		for (int i = 0; i < num_verts; i++) {
+			p_verts[(i * 4) + 0] = (p_verts[(i * 4) + 0] * scale_x) / ((int) SUB_INCH_PRECISION);
+			p_verts[(i * 4) + 1] = (p_verts[(i * 4) + 1] * scale_y) / ((int) SUB_INCH_PRECISION);
+			p_verts[(i * 4) + 2] = (p_verts[(i * 4) + 2] * scale_z) / ((int) SUB_INCH_PRECISION);
+		}
+
+		dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_verts);		
+
+		delete [] p_verts;
+
+		Mem::Manager::sHandle().PopContext();
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->Scale(world_origin, scale, false);
+			}
+		}
+
+		// Recursively update children
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->Scale(world_origin, scale, false);
+		}
+	}
+
+	if (root)
+	{
+		Translate(world_origin);
+	}
+}
+
+void 		CGeomNode::GetVerts(Mth::Vector *p_verts, bool root)
+{
+	// Not thread safe
+	static int vert_index;
+	if (root)
+	{
+		vert_index = 0;
+	}
+
+	if (IsLeaf())
+	{
+		// Get the dma data
+		Dbg_Assert(u1.mp_dma);
+		int num_verts = dma::GetNumVertices(u1.mp_dma);
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+		int32 *p_raw_verts = (int32 *) &p_verts[vert_index];
+		//int32 *p_raw_verts = new int32[num_verts * 4];
+		dma::ExtractXYZs(u1.mp_dma, (uint8 *) p_raw_verts);		
+
+		// Convert center to integer
+		// (Mick:  Changed to always have a center offset, as even 32 bit coordinates have one
+		// previous, the code assumed that 32 bit coordinate were absolute world coords, and so
+		// did not add in the "center" offset
+		int32 center[4];
+		dma::ConvertFloatToXYZ(center,  m_bounding_sphere);
+
+		for (int i = 0; i < num_verts; i++, vert_index++) 
+		{
+			dma::ConvertXYZToFloat(p_verts[vert_index], &(p_raw_verts[i * 4]), center);
+		}
+
+//		delete [] p_raw_verts;
+
+		Mem::Manager::sHandle().PopContext();
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->GetVerts(p_verts, false);
+			}
+		}
+
+		// Get children verts
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->GetVerts(p_verts, false);
+		}
+	}
+}
+
+void 		CGeomNode::SetVerts(Mth::Vector *p_verts, bool root)
+{
+	// Not thread safe
+	static int vert_index;
+	if (root)
+	{
+		vert_index = 0;
+	}
+
+	if (IsLeaf())
+	{
+		// Get the dma data
+		Dbg_Assert(u1.mp_dma);
+		int num_verts = dma::GetNumVertices(u1.mp_dma);
+
+		// Recalculate the bounding data BEFORE setting the DMA data, since it will change the
+		// center of the bounding sphere.
+		recalculate_bounding_data(&p_verts[vert_index], num_verts);
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+		int32 *p_raw_verts = new int32[num_verts * 4];
+
+		// Convert center to integer
+		int32 center[4];
+		if (dma::GetBitLengthXYZ(u1.mp_dma) < 32)
+		{
+			dma::ConvertFloatToXYZ(center,  m_bounding_sphere);
+		}
+		
+
+		for (int i = 0; i < num_verts; i++, vert_index++) {
+			if (dma::GetBitLengthXYZ(u1.mp_dma) == 32)
+			{
+				dma::ConvertFloatToXYZ(&(p_raw_verts[i * 4]), p_verts[vert_index]);
+			} else {
+				dma::ConvertFloatToXYZ(&(p_raw_verts[i * 4]), p_verts[vert_index], center);
+			}
+		}
+
+		// Replaces only XYZs (not W)
+		dma::ReplaceXYZs(u1.mp_dma, (uint8 *) p_raw_verts, true);		
+
+		delete [] p_raw_verts;
+
+		Mem::Manager::sHandle().PopContext();
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->SetVerts(p_verts, false);
+			}
+		}
+
+		// Set children verts
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->SetVerts(p_verts, false);
+		}
+
+		// Recalculate the bounding data at this level using all the verts (assuming this is
+		// going to be the object node).
+		Dbg_MsgAssert(IsObject(), ("Don't know how to recalculate the bounding data for this node: Not an object"));
+		int num_verts = GetNumVerts();
+		recalculate_bounding_data(p_verts, num_verts);
+	}
+}
+
+void 		CGeomNode::recalculate_bounding_data(Mth::Vector *p_verts, int num_verts)
+{
+	Mth::Vector min, max, center, half_vec;
+	float radius;
+	int i;
+
+	// calculate bounding box for the mesh
+	min[0] = min[1] = min[2] = 1e30f;
+	max[0] = max[1] = max[2] = -1e30f;
+
+	for (i=0; i max[X])
+			max[X] = p_verts[i][X];
+		if (p_verts[i][Y] > max[Y])
+			max[Y] = p_verts[i][Y];
+		if (p_verts[i][Z] > max[Z])
+			max[Z] = p_verts[i][Z];
+	}
+
+	// calculate bounding sphere for the mesh
+	center = (max+min)*0.5f;
+	radius = 0.0f;
+	for (i=0; i radius)
+			radius = len;
+	}
+
+	// Update bounding box data
+	half_vec = 0.5f * (max - min);
+	m_bounding_box[X] = half_vec[X];
+	m_bounding_box[Y] = half_vec[Y];
+	m_bounding_box[Z] = half_vec[Z];
+
+	// Update bounding sphere data
+	m_bounding_sphere = center;
+	m_bounding_sphere[W] = radius;
+}
+
+void 		CGeomNode::GetColors(uint32 *p_colors, bool root)
+{
+	// Not thread safe
+	static int vert_index;
+	if (root)
+	{
+		vert_index = 0;
+	}
+
+	if (IsLeaf())
+	{
+		// Get the dma data
+		Dbg_Assert(u1.mp_dma);
+		int num_verts = dma::GetNumVertices(u1.mp_dma);
+
+		dma::ExtractRGBAs(u1.mp_dma, (uint8 *) &(p_colors[vert_index]));		
+		vert_index += num_verts;
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->GetColors(p_colors, false);
+			}
+		}
+
+		// Get children colors
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->GetColors(p_colors, false);
+		}
+	}
+}
+
+void 		CGeomNode::SetColors(uint32 *p_colors, bool root)
+{
+	// Not thread safe
+	static int vert_index;
+	if (root)
+	{
+		vert_index = 0;
+	}
+
+	if (IsLeaf())
+	{
+		// Set the dma data
+		Dbg_Assert(u1.mp_dma);
+		int num_verts = dma::GetNumVertices(u1.mp_dma);
+
+		dma::ReplaceRGBAs(u1.mp_dma, (uint8 *) &(p_colors[vert_index]));		
+		vert_index += num_verts;
+	} else {
+		// Check if we have LODs
+		if (IsObject())
+		{
+			if (mp_next_LOD)
+			{
+				mp_next_LOD->SetColors(p_colors, false);
+			}
+		}
+
+		// Set children colors
+		for (CGeomNode *p_child=u1.mp_child; p_child; p_child=p_child->mp_sibling)
+		{
+			p_child->SetColors(p_colors, false);
+		}
+	}
+}
+
+/*
+	vc wibble material data block format
+	------------------------------------
+
+	int num_seqs;
+	for (seq=0; seq end_time) time = end_time;
+
+		// locate the keyframe
+		for (key=num_keys-1,p_key=(float *)p_matdata+5*num_keys-5; key>=0; key--,p_key-=5)
+		{
+			if (time >= *(int *)p_key)
+			{
+				break;
+			}
+		}
+
+		// parameter expressing how far we are between between this keyframe and the next
+		t = (float)(time - ((int *)p_key)[0]) / (float)(((int *)p_key)[5] - ((int *)p_key)[0]);
+
+		#if 0
+		// for debugging a TT bug...
+		for (int i=1; i<=9; i++)
+			Dbg_MsgAssert(p_key[i]>=0 && p_key[i]<=255, ("sTime=%d, num_keys=%d, key=%d, p_key[1]=%g, p_key[2]=%g, p_key[3]=%g, p_key[4]=%g, p_key[5]=%g, p_key[6]=%g, p_key[7]=%g, p_key[8]=%g, p_key[9]=%g, time=%d, start_time=%d, end_time=%d, period=%d, t=%g", \
+														  render::sTime,	\
+														  num_keys,			\
+														  key,				\
+														  p_key[1],			\
+														  p_key[2],			\
+														  p_key[3],			\
+														  p_key[4],			\
+														  p_key[5],			\
+														  p_key[6],			\
+														  p_key[7],			\
+														  p_key[8],			\
+														  p_key[9],			\
+														  time,				\
+														  start_time,		\
+														  end_time,			\
+														  period,			\
+														  t));
+		#endif
+
+		// interpolate between colours
+		r = (1.0f-t) * p_key[1] + t * p_key[6];
+		g = (1.0f-t) * p_key[2] + t * p_key[7];
+		b = (1.0f-t) * p_key[3] + t * p_key[8];
+		a = (1.0f-t) * p_key[4] + t * p_key[9];
+
+		Dbg_MsgAssert(r>=0 && r<=255 && g>=0 && g<=255 && b>=0 && b<=255 && a>=0 && a<=255,
+					  ("wibbled colour r=%g, g=%g, b=%g, a=%g", r,g,b,a));
+
+		rgba  =	(uint32)(sint32)r     & 0x000000FF |
+				(uint32)(sint32)g<<8  & 0x0000FF00 |
+				(uint32)(sint32)b<<16 & 0x00FF0000 |
+				(uint32)(sint32)a<<24;
+
+		// get # vertices wibbled by this sequence
+		num_verts = *p_meshdata++;
+
+		// loop over wibbled vertices, overwriting colour in dma data
+		for (vert=0; vertm_flags & NODEFLAG_LEAF) && p_node->m_bounding_sphere[3] >= 1.0e+10f)
+	{
+		p_node = p_node->u1.mp_child;
+	}
+
+	sphere = p_node->m_bounding_sphere;
+
+	// then account for any siblings
+	while (p_node->mp_sibling)
+	{
+		p_node = p_node->mp_sibling;
+
+		Mth::Vector x0, x1;
+		if (p_node->m_bounding_sphere[3] > sphere[3])
+		{
+			x0 = p_node->m_bounding_sphere; 
+			x1 = sphere;
+		}
+		else
+		{
+			x0 = sphere;
+			x1 = p_node->m_bounding_sphere;
+		}
+		float r0=x0[3], r1=x1[3];
+
+		// (x0,r0) is the larger sphere, (x1,r1) the smaller
+		// d is the vector between centres
+		Mth::Vector d = x1 - x0;
+
+		// square of distance between centres
+		float D2 = DotProduct(d,d);
+
+		// (r0-r1)^2
+		float R2 = (r0-r1)*(r0-r1);
+
+		// m = max { (r1-r0+|d|)/2, 0 }
+		float m = 0.0f;
+		float D = sqrtf(D2);
+		if (R2-D2 < 0)
+		{
+			m = (r1-r0 + sqrtf(D2)) * 0.5f;
+		}
+
+		// normalise d
+		if (D>0)
+			d *= 1.0f/D;
+
+		sphere    = x0 + m * d;
+		sphere[3] = r0 + m;
+	}
+
+	return sphere;
+}
+
+Mth::CBBox CGeomNode::GetBoundingBox()
+{
+	CGeomNode *p_node = this;
+
+	while (!(p_node->m_flags & NODEFLAG_LEAF) && p_node->m_bounding_sphere[3] >= 1.0e+10f)
+	{
+		p_node = p_node->u1.mp_child;
+	}
+
+	Mth::Vector min_p(p_node->m_bounding_sphere);
+	Mth::Vector max_p(p_node->m_bounding_sphere);
+
+	//Dbg_Message("Bounding sphere (%.8f, %.8f, %.8f, %.8f)", p_node->m_bounding_sphere[X], p_node->m_bounding_sphere[Y], p_node->m_bounding_sphere[Z], p_node->m_bounding_sphere[W]);
+	//Dbg_Message("Bounding box (%.8f, %.8f, %.8f)", p_node->m_bounding_box[X], p_node->m_bounding_box[Y], p_node->m_bounding_box[Z]);
+
+	min_p[X] -= p_node->m_bounding_box[0];
+	min_p[Y] -= p_node->m_bounding_box[1];
+	min_p[Z] -= p_node->m_bounding_box[2];
+	min_p[W] = 1.0f;
+
+	max_p[X] += p_node->m_bounding_box[0];
+	max_p[Y] += p_node->m_bounding_box[1];
+	max_p[Z] += p_node->m_bounding_box[2];
+	min_p[W] = 1.0f;
+
+	return Mth::CBBox(min_p, max_p);
+}
+
+
+
+// interface to uv wibble control
+
+
+void CGeomNode::SetUVWibbleParams(float u_vel, float u_amp, float u_freq, float u_phase,
+								  float v_vel, float v_amp, float v_freq, float v_phase)
+{
+	Dbg_Assert(mp_uv_wibble);
+
+	mp_uv_wibble[0] = u_vel;
+	mp_uv_wibble[1] = v_vel;
+	mp_uv_wibble[2] = u_freq;
+	mp_uv_wibble[3] = v_freq;
+	mp_uv_wibble[4] = u_amp;
+	mp_uv_wibble[5] = v_amp;
+	mp_uv_wibble[6] = u_phase;
+	mp_uv_wibble[7] = v_phase;
+}
+
+
+
+void CGeomNode::UseExplicitUVWibble(bool yes)
+{
+	SET_FLAG(NODEFLAG_EXPLICIT_UVWIBBLE);
+}
+
+
+
+void CGeomNode::SetUVWibbleOffsets(float u_offset, float v_offset)
+{
+	Dbg_Assert(mp_uv_wibble);
+
+	mp_uv_wibble[8] = u_offset;
+	mp_uv_wibble[9] = v_offset;
+}
+
+
+
+void CGeomNode::SetUVOffset(uint32 material_name, int pass, float u_offset, float v_offset)
+{
+	Dbg_MsgAssert(0, ("SetUVOffset not supported for CGeomNodes"));
+}
+
+
+void CGeomNode::SetUVMatrix(uint32 material_name, int pass, Mth::Matrix &mat)
+{
+	Dbg_MsgAssert(0, ("SetUVOffset not supported for CGeomNodes"));
+}
+
+
+
+
+#endif
+
+
+
+
+
+
+
+
+
+} // namespace NxPs2
+
+
+
+
diff --git a/Code/Gfx/NGPS/NX/geomnode.h b/Code/Gfx/NGPS/NX/geomnode.h
new file mode 100644
index 0000000..d8fcad9
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/geomnode.h
@@ -0,0 +1,411 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		THPS4													**
+**																			**
+**	Module:			NxPs2					 								**
+**																			**
+**	File name:		geometry.h												**
+**																			**
+**	Created by:		02/08/02	-	mrd										**
+**																			**
+**	Description:	classes for PS2 geometry								**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_NGPS_NX_GEOMETRY_H
+#define __GFX_NGPS_NX_GEOMETRY_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+//#include "vu1context.h"
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define MAX_LOD_DIST			(10000000.0f)
+
+namespace Mth
+{
+	class CBBox;
+}
+
+namespace NxPs2
+{
+	// Forward declarations
+	struct	sGroup;
+	struct  sTexture;
+	class	CLightGroup;
+	class	CVu1Context;
+
+// For the flags, note the visibility bits that are reserved below
+#define VISIBILITY_FLAG_BIT		(24)
+#define NUM_VISIBILITY_BITS		(8)
+//#define ALL_VISIBILITY_BITS		(((1 << NUM_VISIBILITY_BITS) - 1) << VISIBILITY_FLAG_BIT)
+#define ALL_VISIBILITY_BITS		((0xFFFFFFFF >> (32 - NUM_VISIBILITY_BITS)) << VISIBILITY_FLAG_BIT)
+
+#define NODEFLAG_ACTIVE			(1<<0)
+#define NODEFLAG_LEAF			(1<<1)
+#define NODEFLAG_OBJECT			(1<<2)
+#define NODEFLAG_TRANSFORMED	(1<<3)
+#define NODEFLAG_COLOURED		(1<<4)
+#define NODEFLAG_SKY			(1<<5)
+#define NODEFLAG_ZPUSH0			(1<<6)
+#define NODEFLAG_ZPUSH1			(1<<7)
+#define NODEFLAG_NOSHADOW		(1<<8)
+#define NODEFLAG_UVWIBBLE		(1<<9)
+#define NODEFLAG_VCWIBBLE		(1<<10)
+#define NODEFLAG_SKINNED		(1<<11)
+#define NODEFLAG_SKELETAL		(1<<11)
+#define NODEFLAG_INSTANCE		(1<<12)
+#define NODEFLAG_OBJECT_LIGHTS	(1<<13)
+#define NODEFLAG_ENVMAPPED		(1<<14)
+#define NODEFLAG_BILLBOARD		(1<<15)
+#define NODEFLAG_EXPLICIT_UVWIBBLE		(1<<16)
+#define NODEFLAG_ZPUSH2			(1<<17)
+#define NODEFLAG_ZPUSH3			(1<<18)
+#define NODEFLAG_BLACKFOG		(1<<19)
+
+#define NODEFLAG_LAST_OCCLUSION_VALID	(1 << 20)
+#define NODEFLAG_LAST_OCCLUSION_TRUE	(1 << 21)
+
+#ifdef	__NOPT_ASSERT__
+#define NODEFLAG_WAS_RENDERED	(1 << 22) // True if rendered last frame.  Not always valid for leaf nodes
+#endif
+
+#if 0
+#define NODEFLAG_VISIBILITY_TEST_PERFORMED		( 1 << 22 )
+#define NODEFLAG_VISIBILITY_TEST_NOT_VISIBLE	( 1 << 23 )
+#endif
+
+#define NODEFLAG_VISIBILITY_0	(1<<(VISIBILITY_FLAG_BIT + 0))
+#define NODEFLAG_VISIBILITY_1	(1<<(VISIBILITY_FLAG_BIT + 1))
+#define NODEFLAG_VISIBILITY_2	(1<<(VISIBILITY_FLAG_BIT + 2))
+#define NODEFLAG_VISIBILITY_3	(1<<(VISIBILITY_FLAG_BIT + 3))
+#define NODEFLAG_VISIBILITY_4	(1<<(VISIBILITY_FLAG_BIT + 4))
+#define NODEFLAG_VISIBILITY_5	(1<<(VISIBILITY_FLAG_BIT + 5))
+#define NODEFLAG_VISIBILITY_6	(1<<(VISIBILITY_FLAG_BIT + 6))
+#define NODEFLAG_VISIBILITY_7	(1<<(VISIBILITY_FLAG_BIT + 7))
+
+#define NODEFLAG_ZPUSH			(NODEFLAG_ZPUSH0 | NODEFLAG_ZPUSH1 | NODEFLAG_ZPUSH2 | NODEFLAG_ZPUSH3)
+
+#define LOADFLAG_RENDERNOW		(1<<0)
+#define LOADFLAG_SKY			(1<<1)
+
+// put these here temporarily
+#define RENDERFLAG_CULL			(1<<0)
+#define RENDERFLAG_CLIP			(1<<1)
+#define RENDERFLAG_SHADOW		(1<<2)
+#define RENDERFLAG_COLOURED		(1<<3)
+#define RENDERFLAG_TRANSFORMED	(1<<4)
+#define RENDERFLAG_SKELETAL		(1<<5)
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+// from CClass, so they get zeroed automatically 
+class CGeomMetrics : public Spt::Class
+{
+public:
+	int 	m_total;
+	int		m_leaf;
+	int		m_object;
+	int		m_verts;
+	int		m_polys;
+};
+
+
+class CGeomNode
+{
+public:
+
+	#ifdef __PLAT_NGPS__
+#	if 0
+	void				RecursiveVisibilityTest( CVu1Context& ctxt, uint32 renderFlags );
+#	endif
+	void				RenderWorld(CVu1Context& ctxt, uint32 renderFlags);
+	void				RenderScene(CVu1Context& ctxt, uint32 renderFlags);
+	void				RenderObject(CVu1Context& ctxt, uint32 renderFlags);
+	void				RenderTransformedLeaf(CVu1Context& ctxt, uint32 renderFlags);
+	void				RenderNonTransformedLeaf(CVu1Context& ctxt, uint32 renderFlags);
+	void				RenderMinimalLeaf(CVu1Context& ctxt);
+	void				RenderAsSky(CVu1Context& ctxt, uint32 renderFlags);
+	void 				SetBoneTransforms(Mth::Matrix *pMat);
+
+	// These modify the actual DMA data
+	void				Translate(const Mth::Vector & delta_trans);		// delta since we don't store the original pos
+	void				RotateY(const Mth::Vector & world_origin, Mth::ERot90 rot_y, bool root = true);	// don't set root
+	void				Scale(const Mth::Vector & world_origin, const Mth::Vector & delta_scale, bool root = true); // delta since we don't store the original scale
+
+	void 				GetVerts(Mth::Vector *p_verts, bool root = true);		// don't set root on any of these
+	void 				GetColors(uint32 *p_colors, bool root = true);
+	void 				SetVerts(Mth::Vector *p_verts, bool root = true);
+	void 				SetColors(uint32 *p_colors, bool root = true);
+	#endif
+
+	bool				IsLeaf();
+	bool				IsActive();
+	bool				IsObject();
+	bool				IsTransformed();
+	bool				IsSky();
+	bool				IsColored();
+	bool				IsSkeletal();
+	bool				IsEnvMapped();
+	bool				IsUVWibbled();
+	bool				IsBillboard();
+
+#ifdef	__NOPT_ASSERT__
+	bool				WasRendered();
+#else
+	bool				WasRendered() {return false;};	
+#endif
+	
+
+	void				SetLeaf(bool yes);
+	void				SetActive(bool yes);
+	void				SetObject(bool yes);
+	void				SetTransformed(bool yes);
+	void				SetSky(bool yes);
+	void				SetZPush0(bool yes);
+	void				SetZPush1(bool yes);
+	void				SetZPush2(bool yes);
+	void				SetZPush3(bool yes);
+	void				SetNoShadow(bool yes);
+	void				SetUVWibbled(bool yes);
+	void				SetVCWibbled(bool yes);
+	void				SetColored(bool yes);
+	void				SetSkinned(bool yes);
+	void				SetBillboard(bool yes);
+	void				SetSkeletal(bool yes);
+	void				SetEnvMapped(bool yes);
+	void				SetBlackFog(bool yes);
+
+	void				SetChecksum(uint32 checksum);
+	void				SetBoundingSphere(float x, float y, float z, float R);	// change args to const Mth::Vector sphere
+	void				SetBoundingBox(float x, float y, float z);				// change args to const Mth::Vector box
+	void				SetDma(uint8 *pDma);
+	void				SetDmaTag(uint8 *pDma);
+	void				SetChild(CGeomNode *pChild);
+	void				AddChild(CGeomNode *pChild);
+	void				SetSibling(CGeomNode *pSibling);
+	void				SetSlaveLOD(CGeomNode *pLOD);
+	void				SetLODFarDist(float LOD_far_dist);
+	void				SetGroup(sGroup *pGroup);
+	void				SetMatrix(Mth::Matrix *p_mat);
+	void				SetUVWibblePointer(float *pData);
+	void				SetVCWibblePointer(uint32 *pData);
+	void				SetColor(uint32 rgba);
+	void				SetLightGroup(CLightGroup *p_light_group);
+	void				SetVisibility(uint8 mask);
+	void				SetBoneIndex(sint8 index);
+
+	// Texture pointer starts off as a texture checksum in SceneConv
+	void				SetTextureChecksum(uint32 checksum) {u4.mp_texture = (sTexture*)checksum;}
+	uint32				GetTextureChecksum() {return (uint32)u4.mp_texture;}
+	#ifdef __PLAT_NGPS__
+	void				SetTexture(sTexture *p_texture) {u4.mp_texture = p_texture;}
+	sTexture *			GetTexture() {return u4.mp_texture;}
+	#endif
+
+	void				SetUVWibbleParams(float u_vel, float u_amp, float u_freq, float u_phase,
+										  float v_vel, float v_amp, float v_freq, float v_phase);
+	void				UseExplicitUVWibble(bool yes);
+	void				SetUVWibbleOffsets(float u_offset, float v_offset);
+
+	void				SetUVOffset(uint32 material_name, int pass, float u_offset, float v_offset);
+	void 				SetUVMatrix(uint32 material_name, int pass, Mth::Matrix &mat);
+	
+	uint32				GetChecksum();
+	Mth::Matrix&		GetMatrix();
+	CGeomNode*			GetChild();
+	CGeomNode*			GetSibling();
+	uint8*				GetDma();
+	sGroup*				GetGroup();
+	uint32				GetColor();
+	float				GetLODFarDist();
+	CLightGroup *		GetLightGroup();
+	uint8				GetVisibility();
+	Mth::Vector			GetBoundingSphere();
+	Mth::CBBox			GetBoundingBox();
+	sint8				GetBoneIndex();
+
+	uint32				GetFlags(){return m_flags;}
+
+	void				CountMetrics(CGeomMetrics *p_metrics);
+	void				RenderWireframe(int mode);
+	void				RenderWireframeRecurse(int mode, int &lines);
+	
+								   
+	int					GetNumBones() { return m_num_bones; }
+	int					GetNumVerts();
+	int					GetNumPolys();
+	int					GetNumBasePolys();
+
+	// To get at the different objects in a heirarchy
+	int					GetNumObjects();
+	CGeomNode*			GetObject(int num, bool root = true);		// Don't set root
+
+
+	#ifdef __PLAT_WN32__
+	void 				Preprocess(uint8 *p_baseAddress);
+	#endif
+
+	#ifdef __PLAT_NGPS__
+	static CGeomNode*	sProcessInPlace(uint8 *pPipData, uint32 loadFlags);
+	static void *		sGetHierarchyArray(uint8 *pPipData, int& size);
+	void				AddToTree(uint32 loadFlags);
+	void				Cleanup();
+	
+	CGeomNode*			CreateInstance(Mth::Matrix *pMat, CGeomNode *p_parent = NULL);
+	CGeomNode*			CreateInstance(Mth::Matrix *pMat, int numBones, Mth::Matrix *pBoneTransforms, CGeomNode *p_parent = NULL);
+	void				DeleteInstance();
+	CGeomNode*			CreateCopy(CGeomNode *p_parent = NULL, bool root = true);				// Don't set root
+	void				DeleteCopy(bool root = true);											// Don't set root
+	#endif
+	
+	bool				CullAgainstViewFrustum();
+	bool				CullAgainstOuterFrustum();
+
+
+	void				Init();
+
+						CGeomNode();
+						CGeomNode(const CGeomNode &node);
+						~CGeomNode() {}
+
+
+						static CGeomNode	sWorld;
+						static CGeomNode	sDatabase;
+
+
+private:
+
+	#ifdef __PLAT_NGPS__
+	bool				SphereIsOutsideViewVolume(Mth::Vector &s);
+	bool				SphereIsInsideOuterVolume(Mth::Vector &s);
+	bool				BoxIsOutsideViewVolume(Mth::Vector& b, Mth::Vector& s);
+	bool				NeedsClipping(Mth::Vector &s);
+	void				LinkDma(uint8 *p_newTail);
+	int	 				ProcessNodeInPlace(uint8 *p_baseAddress);
+	void 				BecomesChildOf(CGeomNode *p_parent);
+	bool				RemoveFrom(CGeomNode *p_parent);
+	bool				RemoveFromChildrenOf(CGeomNode *p_parent);
+	void 				WibbleVCs();
+
+	void 				recalculate_bounding_data(Mth::Vector *p_verts, int num_verts);
+	#endif
+
+	Mth::Vector			m_bounding_sphere;
+	float				m_bounding_box[3];
+	uint32				m_flags;
+	union
+	{
+		CGeomNode *		mp_child;			// internal
+		uint8 *			mp_dma;				// leaf
+	} u1;
+	union
+	{
+		Mth::Matrix *	mp_transform;		// internal
+		uint32			m_dma_tag_lo32;		// leaf
+	} u2;
+	CGeomNode *			mp_sibling;
+	union
+	{
+		sGroup *		mp_group;			// leaf
+		CLightGroup *	mp_light_group;		// optional group of lights
+	} u3;
+	uint32				m_checksum;
+	float *				mp_uv_wibble;		// leaf
+	uint32 *			mp_vc_wibble;		// leaf
+	uint32				m_colour;
+	// used for skeletal models...
+	sint8				m_num_bones;		// skeletal, internal, instance
+	sint8				m_bone_index;		// skeletal, internal, source
+	uint16				m_field;			// skeletal, internal, instance
+	union 
+	{
+		Mth::Matrix *		mp_bone_transforms;	// skeletal, internal, instance
+		sTexture	*		mp_texture;			// Level Geometry leaf- pointer to the texture we use
+	} u4;
+	float				m_LOD_far_dist;
+	CGeomNode *			mp_next_LOD;
+											// stay quadword-aligned with padding if necessary
+};
+
+
+// Hierarchy information available to game.  It is found at the beginning of a geom.ps2 file,
+// although it is not used by the CGeomNodes.
+class CHierarchyObject
+{
+public:
+						CHierarchyObject();
+						~CHierarchyObject();
+
+	void				SetChecksum(uint32 checksum);
+	uint32				GetChecksum() const;
+	void				SetParentChecksum(uint32 checksum);
+	uint32				GetParentChecksum() const;
+	void				SetParentIndex(int16 index);
+	int16				GetParentIndex() const;
+	void				SetBoneIndex(sint8 index);
+	sint8				GetBoneIndex() const;
+
+	void				SetSetupMatrix(const Mth::Matrix& mat);
+	const Mth::Matrix &	GetSetupMatrix() const;
+
+	
+protected:
+	uint32				m_checksum;			// Object checksum
+	uint32				m_parent_checksum;	// Checksum of parent, or 0 if root object
+	int16				m_parent_index;		// Index of parent in the hierarchy array (or -1 if root object)
+	sint8				m_bone_index;		// The index of the bone matrix used on this object
+	uint8				m_pad_8;
+	uint32				m_pad_32;
+	Mth::Matrix			m_setup_matrix;		// Initial local to parent matrix
+};
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+//extern CGeomNode	*pShadowList[SHADOW_LIST_SIZE];
+extern uint			NumShadowListEntries;
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace NxPs2
+
+#endif	// __GFX_NGPS_NX_GEOMETRY_H
+
diff --git a/Code/Gfx/NGPS/NX/gif.cpp b/Code/Gfx/NGPS/NX/gif.cpp
new file mode 100644
index 0000000..33b5bde
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/gif.cpp
@@ -0,0 +1,202 @@
+#include 
+#include 
+#include "dma.h"
+#include "gif.h"
+#include "vif.h"
+#include "vu1.h"
+
+namespace NxPs2
+{
+
+
+
+/*	ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
+	º GIFtag format º
+	ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ
+
+
+	 31                                           16 15 14                                         0
+	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+	³                                               ³EOP                   NLOOP                    ³
+	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+	 63       60 59 58 57                            47 46 45                                     32
+	ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+	³   NREG    ³ FLG ³              PRIM              ³PRE                                         ³
+	ÀÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+	 95                                                       76 75       72 71       68 67       64
+	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄ¿
+	³                                                     ...   ³  [REG2]   ³  [REG1]   ³   REG0    ³
+	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+	 127                                                                                          96
+	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+	³                                                                                               ³
+	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+
+	ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ»
+	º PATH1 GIFtag format º
+	ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ
+
+
+	 31 30                   23 22                16 15 14                                         0
+	ÚÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+	³0 ³     NREG exponent     ³ << NREG            ³EOP                   NLOOP                    ³
+	ÀÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+	 63       60 59 58 57                            47 46 45    43 42                            32
+	ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+	³   NREG    ³ FLG ³              PRIM              ³PRE        ³              ADDR              ³
+	ÀÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+	 95                                                       76 75       72 71       68 67       64
+	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄ¿
+	³                                                     ...   ³  [REG2]   ³  [REG1]   ³   REG0    ³
+	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+	 127                                         112 111           106 105                        96
+	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+	³                                               ³      flags      ³            SIZE             ³
+	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+	flags:
+	106 -
+	107 -
+	108 -
+	109 -
+	110 - no ITOP	}
+	111 - no ITOP	} uses 2 flags because the parsing loop is quicker
+	
+
+
+*/
+
+
+
+//-------------------------------------------------
+//		G I F T A G   C O N S T R U C T I O N
+//-------------------------------------------------
+
+
+// PATH1
+
+
+void gif::Tag1(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Eop, uint NLoop, uint Addr)
+{
+	uint32 size = NReg * NLoop;
+	float NRegFloat  = (float)NReg * 1.1920928955e-07f;	// 2^-23
+	dma::Store32(*(uint32 *)&NRegFloat | Eop<<15 | NLoop,
+							NReg<<28 | Flg<<26 | Prim<<15 | Pre<<14 | Addr,
+							Regs,
+							size);
+}
+
+
+void gif::BeginTag1(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Addr)
+{
+	float NRegFloat = (float)NReg * 1.1920928955e-07f;	// 2^-23
+	pTag = dma::pLoc;
+	dma::Store32(*(uint32 *)&NRegFloat,
+							NReg<<28 | Flg<<26 | Prim<<15 | Pre<<14 | Addr,
+							Regs,
+							0);
+}
+
+
+void gif::BeginTag1_extended(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Addr, uint Step)
+{
+	float StepFloat = (float)Step * 1.1920928955e-07f;	// 2^-23
+	pTag = dma::pLoc;
+	dma::Store32(*(uint32 *)&StepFloat,
+					NReg<<28 | Flg<<26 | Prim<<15 | Pre<<14 | Addr,
+					Regs,
+					0);
+}
+
+
+void gif::EndTag1(uint Eop)
+{
+	uint32 size = vif::UnpackSize * vif::CycleLength;
+	((uint32 *)pTag)[0] |= Eop<<15 | vif::UnpackSize;
+	((uint32 *)pTag)[3] = size;
+	vu1::Loc += vif::UnpackSize * vif::CycleLength + 1;
+}
+
+
+void gif::BeginTagImmediate(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Addr)
+{
+	float NRegFloat = (float)NReg * 1.1920928955e-07f;	// 2^-23
+	pTag = dma::pLoc;
+	dma::Store32(*(uint32 *)&NRegFloat,
+							NReg<<28 | Flg<<26 | Prim<<15 | Pre<<14 | Addr,
+							Regs,
+							0);
+}
+
+
+void gif::EndTagImmediate(uint Eop)
+{
+	uint32 size = vif::UnpackSize;
+	uint NREG = pTag[7]>>4;
+	((uint32 *)pTag)[0] |= Eop<<15 | (vif::UnpackSize/NREG);
+	((uint32 *)pTag)[3] = size;
+	vu1::Loc += vif::UnpackSize+1;
+}
+
+
+
+
+// PATH2 or 3
+
+void gif::Tag2(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Eop, uint NLoop)
+{
+	dma::Store32(	Eop<<15 | NLoop,									
+					NReg<<28 | Flg<<26 | Prim<<15 | Pre<<14,
+					Regs,
+					0);
+}
+
+
+void gif::BeginTag2(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre)
+{
+	pTag = dma::pLoc;
+	dma::Store32(	0,
+					NReg<<28 | Flg<<26 | Prim<<15 | Pre<<14,
+					Regs,
+					0);
+}
+
+
+void gif::EndTag2(uint Eop)
+{
+	uint FLG  = pTag[7]>>2 & 3;
+	uint NREG = pTag[7]>>4;
+	uint NLOOP;
+	if (FLG==IMAGE)
+		NLOOP = (dma::pLoc - pTag - 16) / 16;
+	else
+		NLOOP = (dma::pLoc - pTag - 16) / (16*NREG);
+	*(uint32 *)pTag |= Eop<<15 | NLOOP;
+}
+
+
+//--------------------------------
+//		S T A T I C   D A T A
+//--------------------------------
+
+uint8 *gif::pTag;
+
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/gif.h b/Code/Gfx/NGPS/NX/gif.h
new file mode 100644
index 0000000..8e5c06b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/gif.h
@@ -0,0 +1,45 @@
+#ifndef __GIF_H
+#define __GIF_H
+
+
+namespace NxPs2
+{
+
+
+// GIFtag FLG definitions
+#define PACKED	0
+#define REGLIST	1
+#define IMAGE	2
+
+class gif
+{
+
+public:
+
+	//------------------------------------------
+	//		S T A T I C   F U N C T I O N S
+	//------------------------------------------
+
+	static void Tag1(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Eop, uint NLoop, uint Addr);
+	static void EndTag1(uint Eop);
+	static void BeginTag1(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Addr);
+	static void BeginTag1_extended(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Addr, uint Step);
+	static void EndTagImmediate(uint Eop);
+	static void BeginTagImmediate(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Addr);
+	static void Tag2(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre, uint Eop, uint NLoop);
+	static void EndTag2(uint Eop);
+	static void BeginTag2(uint32 Regs, uint NReg, uint Flg, uint Prim, uint Pre);
+
+	//--------------------------------
+	//		S T A T I C   D A T A
+	//--------------------------------
+
+	static uint8 *pTag;
+
+}; // class gif
+
+
+} // namespace NxPs2
+
+
+#endif // __GIF_H
diff --git a/Code/Gfx/NGPS/NX/group.cpp b/Code/Gfx/NGPS/NX/group.cpp
new file mode 100644
index 0000000..991d22e
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/group.cpp
@@ -0,0 +1,46 @@
+#include 
+#include "group.h"
+	
+/*
+
+ (Notes by Mick)
+ 
+ A Group is a set of textures, and a set of meshes or primitives that use those textures.
+ 
+ Rendering is performed one group at a time. 
+ 
+ The textures for a group are uploaded, and then it is rendered.
+ 
+ Whilst a group is being rendered, the uploading of the next group's textures
+ can start to be uploaded.
+ 
+ This all happens asyncronously, whilst the CPU is doing the calculations for the next frame.
+ 
+ The uploading/rendering cycle is syncronized by interrupts.
+ 
+ The groups are rendered in this order:
+ 
+ 1) PrologueGroup - set up in render.cpp - flips the display buffers and initializes VU1 microcode 
+ 2) The groups linked by PrologueGroup->pNext, which represent groups of textures and polygons
+ 3) pEpilogue, (the last in this list) - uploads fonts and sprites, allowing them to be rendered next
+
+ after the groups are rendered, we can render the 2D elements, namely the sprites and fonts (see render.cpp)
+
+*/
+				
+				
+namespace NxPs2
+{
+
+sGroup *sGroup::pHead;
+sGroup *sGroup::pTail;
+sGroup *sGroup::pShadow;
+sGroup *sGroup::pFog;
+sGroup *sGroup::pParticles;
+sGroup *sGroup::pEpilogue;
+volatile sGroup *sGroup::pUploadGroup;    // Group that is currently uploading
+volatile sGroup *sGroup::pRenderGroup;	  // Group that is currently rendering
+uint32 sGroup::VramBufferBase;
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/group.h b/Code/Gfx/NGPS/NX/group.h
new file mode 100644
index 0000000..e0aea5b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/group.h
@@ -0,0 +1,58 @@
+#ifndef __GROUP_H
+#define __GROUP_H
+
+
+namespace NxPs2
+{
+
+
+#define GROUPFLAG_TRANSPARENT	(1<<0)
+#define GROUPFLAG_SKY			(1<<1)
+#define GROUPFLAG_SORT			(1<<2)
+
+
+class CVu1Context;
+struct STransformContext;
+
+
+struct sGroup
+{
+public:
+	sGroup *pNext;
+
+	float  Priority;
+	uint32 Checksum;
+	uint32 flags;
+	uint8  *pUpload[2];
+	uint8  *pRender[2];
+	uint8  *pListEnd[2];
+	bool	Used[2];			// flag saying if this group is actually used this frame (has something in the pRender data)
+	struct sMesh  *pMeshes;
+	int    NumMeshes;
+	uint   vu1_loc;
+	uint8  *p_tag;
+
+	uint32 VramStart;
+	uint32 VramEnd;
+
+	struct sScene *pScene;
+
+	sGroup *pPrev;
+
+	static sGroup *pHead;
+	static sGroup *pTail;
+	static sGroup *pShadow;
+	static sGroup *pFog;
+	static sGroup *pParticles;
+	static sGroup *pEpilogue;
+	volatile static sGroup *pUploadGroup;
+	volatile static sGroup *pRenderGroup;
+	static uint32 VramBufferBase;
+	//STransformContext *pTransformContext;
+	CVu1Context *pVu1Context;
+	uint32	profile_color;					// color we display it as in the profiler
+};
+
+} // namespace NxPs2
+
+#endif // __GROUP_H
diff --git a/Code/Gfx/NGPS/NX/gs.cpp b/Code/Gfx/NGPS/NX/gs.cpp
new file mode 100644
index 0000000..51b2d8f
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/gs.cpp
@@ -0,0 +1,101 @@
+#include 
+#include "vu1code.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+
+
+namespace NxPs2
+{
+
+
+//--------------------------------------------------
+//		G S   P R I M   C O N S T R U C T I O N
+//--------------------------------------------------
+
+
+// begin
+void gs::BeginPrim(uint FLG, uint Prim, uint Pre)
+{
+	vif::STCYCL(1,1);
+	vif::UNPACK(0, V4_32, 1, FLG, UNSIGNED, 0);
+	gif::BeginTag1(gs::A_D, 1, PACKED, Prim, Pre, VU1_ADDR(GSPrim));
+	vif::BeginUNPACK(0, V3_32, FLG, UNSIGNED, 1);
+}
+
+
+// end
+void gs::EndPrim(uint Eop)
+{
+	vif::EndUNPACK();
+	gif::EndTag1(Eop);
+}
+
+
+
+
+// output 1 GS register DIRECT via PATH2, V4_32 format
+void gs::Reg2(eReg Reg, uint64 Data)
+{
+	dma::Store64(Data);
+	dma::Store64((uint64)Reg);
+}
+
+
+// version returning address of data
+uint32 *gs::Reg2_pData(eReg Reg, uint64 Data)
+{
+	uint32 *p_ret = (uint32 *) dma::pLoc;
+
+	dma::Store64(Data);
+	dma::Store64((uint64)Reg);
+
+	return p_ret;
+}
+
+
+// Modify the TBPs of GS registers
+void gs::ModifyTBP(eReg Reg, uint32 *p_reg_ptr, sint16 tbp1, sint16 tbp2, sint16 tbp3)
+{
+	switch (Reg)
+	{
+	case BITBLTBUF:		// only modifies DBP
+		*(++p_reg_ptr) &= ~(M14);				// DBP
+		*(  p_reg_ptr) |= (tbp1 & M14);
+		break;
+
+	case TEX0_1:
+	case TEX0_2:
+		*(  p_reg_ptr) &= ~(M14);				// TBP
+		*(  p_reg_ptr) |= (tbp1 & M14);
+		if (tbp2 >= 0)
+		{
+			*(++p_reg_ptr) &= ~(M14 << 5);		// CBP
+			*(  p_reg_ptr) |= (tbp2 & M14) << 5;
+		}
+		break;
+
+	case TEX2_1:
+	case TEX2_2:
+		*(++p_reg_ptr) &= ~(M14 << 5);			// CBP
+		*(  p_reg_ptr) |= (tbp1 & M14) << 5;
+		break;
+
+	case MIPTBP1_1:
+	case MIPTBP1_2:
+	case MIPTBP2_1:
+	case MIPTBP2_2:
+	default:
+		#ifdef __PLAT_NGPS__
+		Dbg_MsgAssert(0, ("gs::ModifyTBP(): Can't modify register %x", Reg));
+		#endif
+		break;
+	}
+}
+
+} // namespace NxPs2
+
+
+
diff --git a/Code/Gfx/NGPS/NX/gs.h b/Code/Gfx/NGPS/NX/gs.h
new file mode 100644
index 0000000..4ad085d
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/gs.h
@@ -0,0 +1,639 @@
+#ifndef __GS_H
+#define __GS_H
+
+#include "dma.h"
+
+//---------------------------------------------------
+//		R E G I S T E R   D E F I N I T I O N S
+//---------------------------------------------------
+
+
+
+// mask definitions for packing macros
+#define M01	0x0000000000000001
+#define M02	0x0000000000000003
+#define M03	0x0000000000000007
+#define M04	0x000000000000000F
+#define M05	0x000000000000001F
+#define M06	0x000000000000003F
+#define M08	0x00000000000000FF
+#define M09	0x00000000000001FF
+#define M10	0x00000000000003FF
+#define M11	0x00000000000007FF
+#define M12	0x0000000000000FFF
+#define M14	0x0000000000003FFF
+#define M16	0x000000000000FFFF
+#define M24	0x0000000000FFFFFF
+#define M32	0x00000000FFFFFFFF
+#define M64	0xFFFFFFFFFFFFFFFF
+
+
+//----------------------------------------------------------------------------------------
+//		G E N E R A L   P U R P O S E   R E G I S T E R   P A C K I N G   M A C R O S
+//----------------------------------------------------------------------------------------
+
+#define PackALPHA(A,B,C,D,FIX)				\
+(											\
+	( (uint64) (A)		& M02 )	<< 0	|	\
+	( (uint64) (B)		& M02 )	<< 2	|	\
+	( (uint64) (C)		& M02 )	<< 4	|	\
+	( (uint64) (D)		& M02 )	<< 6	|	\
+	( (uint64) (FIX)	& M08 )	<< 32		\
+)
+
+#define PackBITBLTBUF(SBP,SBW,SPSM,DBP,DBW,DPSM)\
+(											\
+	( (uint64) (SBP)	& M14 )	<< 0	|	\
+	( (uint64) (SBW)	& M06 )	<< 16	|	\
+	( (uint64) (SPSM)	& M06 )	<< 24	|	\
+	( (uint64) (DBP)	& M14 )	<< 32	|	\
+	( (uint64) (DBW)	& M06 )	<< 48	|	\
+	( (uint64) (DPSM)	& M06 )	<< 56		\
+)
+
+#define PackCLAMP(WMS,WMT,MINU,MAXU,MINV,MAXV)\
+(											\
+	( (uint64) (WMS)	& M02 )	<< 0	|	\
+	( (uint64) (WMT)	& M02 )	<< 2	|	\
+	( (uint64) (MINU)	& M10 )	<< 4	|	\
+	( (uint64) (MAXU)	& M10 )	<< 14	|	\
+	( (uint64) (MINV)	& M10 )	<< 24	|	\
+	( (uint64) (MAXV)	& M10 )	<< 34		\
+)
+
+#define PackCOLCLAMP(CLAMP)					\
+(											\
+	( (uint64) (CLAMP)	& M01 )	<< 0		\
+)
+
+#define PackDIMX(DM00,DM01,DM02,DM03,DM10,DM11,DM12,DM13,DM20,DM21,DM22,DM23,DM30,DM31,DM32,DM33)\
+(											\
+	( (uint64) (DM00)	& M03 )	<< 0	|	\
+	( (uint64) (DM01)	& M03 )	<< 4	|	\
+	( (uint64) (DM02)	& M03 )	<< 8	|	\
+	( (uint64) (DM03)	& M03 )	<< 12	|	\
+	( (uint64) (DM10)	& M03 )	<< 16	|	\
+	( (uint64) (DM11)	& M03 )	<< 20	|	\
+	( (uint64) (DM12)	& M03 )	<< 24	|	\
+	( (uint64) (DM13)	& M03 )	<< 28	|	\
+	( (uint64) (DM20)	& M03 )	<< 32	|	\
+	( (uint64) (DM21)	& M03 )	<< 36	|	\
+	( (uint64) (DM22)	& M03 )	<< 40	|	\
+	( (uint64) (DM23)	& M03 )	<< 44	|	\
+	( (uint64) (DM30)	& M03 )	<< 48	|	\
+	( (uint64) (DM31)	& M03 )	<< 52	|	\
+	( (uint64) (DM32)	& M03 )	<< 56	|	\
+	( (uint64) (DM33)	& M03 )	<< 60		\
+)
+
+#define PackDTHE(DTHE)						\
+(											\
+	( (uint64) (DTHE)	& M01 )	<< 0		\
+)
+
+#define PackFBA(FBA)						\
+(											\
+	( (uint64) (FBA)	& M01 )	<< 0		\
+)
+
+#define PackFOG(F)							\
+(											\
+	( (uint64) (F)		& M08 )	<< 56		\
+)
+
+#define PackFOGCOL(FCR,FCG,FCB)				\
+(											\
+	( (uint64) (FCR)	& M08 )	<< 0	|	\
+	( (uint64) (FCG)	& M08 )	<< 8	|	\
+	( (uint64) (FCB)	& M08 )	<< 16		\
+)
+
+#define PackFRAME(FBP,FBW,PSM,FBMSK)		\
+(											\
+	( (uint64) (FBP)	& M09 )	<< 0	|	\
+	( (uint64) (FBW)	& M06 )	<< 16	|	\
+	( (uint64) (PSM)	& M06 )	<< 24	|	\
+	( (uint64) (FBMSK)	& M32 )	<< 32		\
+)
+
+#define PackHWREG(DATA)						\
+(											\
+	( (uint64) (DATA)	& M64 )	<< 0		\
+)
+
+#define PackLABEL(ID,IDMSK)					\
+(											\
+	( (uint64) (ID)		& M32 )	<< 0	|	\
+	( (uint64) (IDMSK)	& M32 )	<< 32		\
+)
+
+#define PackMIPTBP1(TBP1,TBW1,TBP2,TBW2,TBP3,TBW3)\
+(											\
+	( (uint64) (TBP1)	& M14 )	<< 0	|	\
+	( (uint64) (TBW1)	& M06 )	<< 14	|	\
+	( (uint64) (TBP2)	& M14 )	<< 20	|	\
+	( (uint64) (TBW2)	& M06 )	<< 34	|	\
+	( (uint64) (TBP3)	& M14 )	<< 40	|	\
+	( (uint64) (TBW3)	& M06 )	<< 54		\
+)
+
+#define PackMIPTBP2(TBP4,TBW4,TBP5,TBW5,TBP6,TBW6)\
+(											\
+	( (uint64) (TBP4)	& M14 )	<< 0	|	\
+	( (uint64) (TBW4)	& M06 )	<< 14	|	\
+	( (uint64) (TBP5)	& M14 )	<< 20	|	\
+	( (uint64) (TBW5)	& M06 )	<< 34	|	\
+	( (uint64) (TBP6)	& M14 )	<< 40	|	\
+	( (uint64) (TBW6)	& M06 )	<< 54		\
+)
+
+#define PackPABE(PABE)						\
+(											\
+	( (uint64) (PABE)	& M01 )	<< 0		\
+)
+
+#define PackPRIM(PRIM,IIP,TME,FGE,ABE,AA1,FST,CTXT,FIX)\
+(											\
+	( (uint64) (PRIM)	& M03 )	<< 0	|	\
+	( (uint64) (IIP)	& M01 )	<< 3	|	\
+	( (uint64) (TME)	& M01 )	<< 4	|	\
+	( (uint64) (FGE)	& M01 )	<< 5	|	\
+	( (uint64) (ABE)	& M01 )	<< 6	|	\
+	( (uint64) (AA1)	& M01 )	<< 7	|	\
+	( (uint64) (FST)	& M01 )	<< 8	|	\
+	( (uint64) (CTXT)	& M01 )	<< 9	|	\
+	( (uint64) (FIX)	& M01 )	<< 10		\
+)
+
+#define PackPRMODE(IIP,TME,FGE,ABE,AA1,FST,CTXT,FIX)\
+(											\
+	( (uint64) (IIP)	& M01 )	<< 3	|	\
+	( (uint64) (TME)	& M01 )	<< 4	|	\
+	( (uint64) (FGE)	& M01 )	<< 5	|	\
+	( (uint64) (ABE)	& M01 )	<< 6	|	\
+	( (uint64) (AA1)	& M01 )	<< 7	|	\
+	( (uint64) (FST)	& M01 )	<< 8	|	\
+	( (uint64) (CTXT)	& M01 )	<< 9	|	\
+	( (uint64) (FIX)	& M01 )	<< 10		\
+)
+
+#define PackPRMODECONT(AC)					\
+(											\
+	( (uint64) (AC)		& M01 )	<< 0		\
+)
+
+#define PackRGBAQ(R,G,B,A,Q)				\
+(											\
+	( (uint64) (R)		& M08 )	<< 0	|	\
+	( (uint64) (G)		& M08 )	<< 8	|	\
+	( (uint64) (B)		& M08 )	<< 16	|	\
+	( (uint64) (A)		& M08 )	<< 24	|	\
+	( (uint64) (Q)		& M32 )	<< 32		\
+)
+
+#define PackSCANMSK(MSK)					\
+(											\
+	( (uint64) (MSK)	& M01 )	<< 0		\
+)
+
+#define PackSCISSOR(SCAX0,SCAX1,SCAY0,SCAY1)\
+(											\
+	( (uint64) (SCAX0)	& M11 )	<< 0	|	\
+	( (uint64) (SCAX1)	& M11 )	<< 16	|	\
+	( (uint64) (SCAY0)	& M11 )	<< 32	|	\
+	( (uint64) (SCAY1)	& M11 )	<< 48		\
+)
+
+#define PackSIGNAL(ID,IDMSK)				\
+(											\
+	( (uint64) (ID)		& M32 )	<< 0	|	\
+	( (uint64) (IDMSK)	& M32 )	<< 32		\
+)
+
+#define PackST(S,T)							\
+(											\
+	( (uint64) (S)		& M32 )	<< 0	|	\
+	( (uint64) (T)		& M32 )	<< 32		\
+)
+
+#define PackTEST(ATE,ATST,AREF,AFAIL,DATE,DATM,ZTE,ZTST)\
+(											\
+	( (uint64) (ATE)	& M01 )	<< 0	|	\
+	( (uint64) (ATST)	& M03 )	<< 1	|	\
+	( (uint64) (AREF)	& M08 )	<< 4	|	\
+	( (uint64) (AFAIL)	& M02 )	<< 12	|	\
+	( (uint64) (DATE)	& M01 )	<< 14	|	\
+	( (uint64) (DATM)	& M01 )	<< 15	|	\
+	( (uint64) (ZTE)	& M01 )	<< 16	|	\
+	( (uint64) (ZTST)	& M02 )	<< 17		\
+)
+
+#define PackTEX0(TBP0,TBW,PSM,TW,TH,TCC,TFX,CBP,CPSM,CSM,CSA,CLD)\
+(											\
+	( (uint64) (TBP0)	& M14 )	<< 0	|	\
+	( (uint64) (TBW)	& M06 )	<< 14	|	\
+	( (uint64) (PSM)	& M06 )	<< 20	|	\
+	( (uint64) (TW)		& M04 )	<< 26	|	\
+	( (uint64) (TH)		& M04 )	<< 30	|	\
+	( (uint64) (TCC)	& M01 )	<< 34	|	\
+	( (uint64) (TFX)	& M02 )	<< 35	|	\
+	( (uint64) (CBP)	& M14 )	<< 37	|	\
+	( (uint64) (CPSM)	& M04 )	<< 51	|	\
+	( (uint64) (CSM)	& M01 )	<< 55	|	\
+	( (uint64) (CSA)	& M05 )	<< 56	|	\
+	( (uint64) (CLD)	& M03 )	<< 61		\
+)
+
+#define PackTEX1(LCM,MXL,MMAG,MMIN,MTBA,L,K)\
+(											\
+	( (uint64) (LCM)	& M01 )	<< 0	|	\
+	( (uint64) (MXL)	& M03 )	<< 2	|	\
+	( (uint64) (MMAG)	& M01 )	<< 5	|	\
+	( (uint64) (MMIN)	& M03 )	<< 6	|	\
+	( (uint64) (MTBA)	& M01 )	<< 9	|	\
+	( (uint64) (L)		& M02 )	<< 19	|	\
+	( (uint64) (K)		& M12 )	<< 32		\
+)
+
+#define PackTEX2(PSM,CBP,CPSM,CSM,CSA,CLD)	\
+(											\
+	( (uint64) (PSM)	& M06 )	<< 20	|	\
+	( (uint64) (CBP)	& M14 )	<< 37	|	\
+	( (uint64) (CPSM)	& M04 )	<< 51	|	\
+	( (uint64) (CSM)	& M01 )	<< 55	|	\
+	( (uint64) (CSA)	& M05 )	<< 56	|	\
+	( (uint64) (CLD)	& M03 )	<< 61		\
+)
+
+#define PackTEXFLUSH(VAL)					\
+(											\
+	( (uint64) (VAL)	& M64 )	<< 0		\
+)
+
+#define PackTEXA(TA0,AEM,TA1)				\
+(											\
+	( (uint64) (TA0)	& M08 )	<< 0	|	\
+	( (uint64) (AEM)	& M01 )	<< 15	|	\
+	( (uint64) (TA1)	& M08 )	<< 32		\
+)
+
+#define PackTEXCLUT(CBW,COU,COV)			\
+(											\
+	( (uint64) (CBW)	& M06 )	<< 0	|	\
+	( (uint64) (COU)	& M06 )	<< 6	|	\
+	( (uint64) (COV)	& M10 )	<< 12		\
+)
+
+#define PackTRXDIR(XDIR)					\
+(											\
+	( (uint64) (XDIR)	& M02 )	<< 0		\
+)
+
+#define PackTRXPOS(SSAX,SSAY,DSAX,DSAY,DIR)	\
+(											\
+	( (uint64) (SSAX)	& M11 )	<< 0	|	\
+	( (uint64) (SSAY)	& M11 )	<< 16	|	\
+	( (uint64) (DSAX)	& M11 )	<< 32	|	\
+	( (uint64) (DSAY)	& M11 )	<< 48	|	\
+	( (uint64) (DIR)	& M02 )	<< 59		\
+)
+
+#define PackTRXREG(RRW,RRH)					\
+(											\
+	( (uint64) (RRW)	& M12 )	<< 0	|	\
+	( (uint64) (RRH)	& M12 )	<< 32		\
+)
+
+#define PackUV(U,V)							\
+(											\
+	( (uint64) (U)		& M14 )	<< 0	|	\
+	( (uint64) (V)		& M14 )	<< 16		\
+)
+
+#define PackXYOFFSET(OFX,OFY)				\
+(											\
+	( (uint64) (OFX)	& M16 )	<< 0	|	\
+	( (uint64) (OFY)	& M16 )	<< 32		\
+)
+
+#define PackXYZ(X,Y,Z)						\
+(											\
+	( (uint64) (X)		& M16 )	<< 0	|	\
+	( (uint64) (Y)		& M16 )	<< 16	|	\
+	( (uint64) (Z)		& M32 )	<< 32		\
+)
+
+#define PackXYZF(X,Y,Z,F)					\
+(											\
+	( (uint64) (X)		& M16 )	<< 0	|	\
+	( (uint64) (Y)		& M16 )	<< 16	|	\
+	( (uint64) (Z)		& M24 )	<< 32	|	\
+	( (uint64) (F)		& M08 )	<< 56		\
+)
+
+#define PackZBUF(ZBP,PSM,ZMSK)				\
+(											\
+	( (uint64) (ZBP)	& M09 )	<< 0	|	\
+	( (uint64) (PSM)	& M04 )	<< 24	|	\
+	( (uint64) (ZMSK)	& M01 )	<< 32		\
+)
+
+
+//----------------------------------------------------------------------------------------
+//		P R I V I L E G E D   R E G I S T E R   P A C K I N G   M A C R O S
+//----------------------------------------------------------------------------------------
+
+
+#define PackBGCOLOR(R,G,B)					\
+(											\
+	( (uint64) (R)		& M08 )	<< 0	|	\
+	( (uint64) (G)		& M08 )	<< 8	|	\
+	( (uint64) (B)		& M08 )	<< 16		\
+)
+
+#define PackBUSDIR(DIR)						\
+(											\
+	( (uint64) (DIR)	& M01 )	<< 0		\
+)
+
+#define PackCSR(SIGNAL,FINISH,HSINT,VSINT,FLUSH,RESET,NFIELD,FIELD,FIFO,REV,ID)\
+(											\
+	( (uint64) (SIGNAL)	& M01 )	<< 0	|	\
+	( (uint64) (FINISH)	& M01 )	<< 1	|	\
+	( (uint64) (HSINT)	& M01 )	<< 2	|	\
+	( (uint64) (VSINT)	& M01 )	<< 3	|	\
+	( (uint64) (FLUSH)	& M01 )	<< 8	|	\
+	( (uint64) (RESET)	& M01 )	<< 9	|	\
+	( (uint64) (NFIELD)	& M01 )	<< 12	|	\
+	( (uint64) (FIELD)	& M01 )	<< 13	|	\
+	( (uint64) (FIFO)	& M02 )	<< 14	|	\
+	( (uint64) (REV)	& M08 )	<< 16	|	\
+	( (uint64) (ID)		& M08 )	<< 24		\
+)
+
+#define PackDISPFB(FBP,FBW,PSM,DBX,DBY)		\
+(											\
+	( (uint64) (FBP)	& M09 )	<< 0	|	\
+	( (uint64) (FBW)	& M06 )	<< 9	|	\
+	( (uint64) (PSM)	& M05 )	<< 15	|	\
+	( (uint64) (DBX)	& M11 )	<< 32	|	\
+	( (uint64) (DBY)	& M11 )	<< 43		\
+)
+
+#define PackDISPLAY(DX,DY,MAGH,MAGV,DW,DH)	\
+(											\
+	( (uint64) (DX)		& M12 )	<< 0	|	\
+	( (uint64) (DY)		& M11 )	<< 12	|	\
+	( (uint64) (MAGH)	& M04 )	<< 23	|	\
+	( (uint64) (MAGV)	& M02 )	<< 27	|	\
+	( (uint64) (DW)		& M12 )	<< 32	|	\
+	( (uint64) (DH)		& M11 )	<< 44		\
+)
+
+#define PackEXTBUF(EXBP,EXBW,FBIN,WFFMD,EMODA,EMODC,WDX,WDY)\
+(											\
+	( (uint64) (EXBP)	& M14 )	<< 0	|	\
+	( (uint64) (EXBW)	& M06 )	<< 14	|	\
+	( (uint64) (FBIN)	& M02 )	<< 20	|	\
+	( (uint64) (WFFMD)	& M01 )	<< 22	|	\
+	( (uint64) (EMODA)	& M02 )	<< 23	|	\
+	( (uint64) (EMODC)	& M02 )	<< 25	|	\
+	( (uint64) (WDX)	& M11 )	<< 32	|	\
+	( (uint64) (WDY)	& M11 )	<< 43		\
+)
+
+#define PackEXTDATA(SX,SY,SMPH,SMPV,WW,WH)	\
+(											\
+	( (uint64) (SX)		& M12 )	<< 0	|	\
+	( (uint64) (SY)		& M11 )	<< 12	|	\
+	( (uint64) (SMPH)	& M04 )	<< 23	|	\
+	( (uint64) (SMPV)	& M02 )	<< 27	|	\
+	( (uint64) (WW)		& M12 )	<< 32	|	\
+	( (uint64) (WH)		& M11 )	<< 44		\
+)
+
+#define PackEXTWRITE(WRITE)					\
+(											\
+	( (uint64) (WRITE)	& M01 )	<< 0		\
+)
+
+#define PackIMR(SIGMSK,FINISHMSK,HSMSK,VSMSK)\
+(											\
+	( (uint64) (SIGMSK)	& M01 )	<< 8	|	\
+	( (uint64) (FINISHMSK)& M01)<< 9	|	\
+	( (uint64) (HSMSK)	& M01 )	<< 10	|	\
+	( (uint64) (VSMSK)	& M01 )	<< 11	|	\
+	( (uint64)            M04 ) << 12		\
+)
+
+#define PackPMODE(EN1,EN2,CRTMD,MMOD,AMOD,SLBG,ALP)\
+(											\
+	( (uint64) (EN1)	& M01 )	<< 0	|	\
+	( (uint64) (EN2)	& M01 )	<< 1	|	\
+	( (uint64) (CRTMOD)	& M03 )	<< 2	|	\
+	( (uint64) (MMOD)	& M01 )	<< 5	|	\
+	( (uint64) (AMOD)	& M01 )	<< 6	|	\
+	( (uint64) (SLBG)	& M01 )	<< 7	|	\
+	( (uint64) (ALP)	& M08 )	<< 8		\
+)
+
+#define PackSIGLBLID(SIGID,LBLID)			\
+(											\
+	( (uint64) (SIGID)	& M32 )	<< 0	|	\
+	( (uint64) (LBLID)	& M32 )	<< 32		\
+)
+
+#define PackSMODE2(INT,FFMD,DPMS)			\
+(											\
+	( (uint64) (INT)	& M01 )	<< 0	|	\
+	( (uint64) (FFMD)	& M01 )	<< 1	|	\
+	( (uint64) (DPMS)	& M02 )	<< 2		\
+)
+
+
+
+
+//------------------------------------------------------
+//		R E G I S T E R   F I E L D   V A L U E S
+//------------------------------------------------------
+
+
+// drawing primitives
+#define	POINT		0
+#define	LINE		1
+#define	LINESTRIP	2
+#define	TRIANGLE	3
+#define	TRISTRIP	4
+#define	TRIFAN		5
+#define	SPRITE		6
+
+// PRIM bit masks   (GS Manual p. 116)
+#define	IIP			(1<<3)		// Gouraud Shading
+#define	TME			(1<<4)		// Texture Mapping
+#define	FGE			(1<<5)		// Fogging
+#define	ABE			(1<<6)		// Alpha Blending
+#define	AA1			(1<<7)		// Pass 1 Antialiasing
+#define	FST			(1<<8)		// UV values used (UV Register)
+#define	CTXT		(1<<9)		// Context 2 used
+#define	FIX			(1<<10)		// Fixed Fragment Value Control
+
+// pixel storage modes
+#define PSMCT32		0x00
+#define PSMCT24		0x01
+#define PSMCT16		0x02
+#define PSMCT16S	0x0A
+#define PS_GPU24	0x12
+#define PSMT8		0x13
+#define PSMT4		0x14
+#define PSMT8H		0x1B
+#define PSMT4HL		0x24
+#define PSMT4HH		0x2C
+#define PSMZ32		0x30
+#define PSMZ24		0x31
+#define PSMZ16		0x32
+#define PSMZ16S		0x3A
+
+// depth test methods
+#define ZNEVER		0
+#define ZALWAYS		1
+#define ZGEQUAL		2
+#define ZGREATER	3
+
+// alpha test methods
+#define	ANEVER		0
+#define	AALWAYS		1
+#define ALESS		2
+#define ALEQUAL		3
+#define	AEQUAL		4
+#define	AGEQUAL		5
+#define	AGREATER	6
+#define	ANOTEQUAL	7
+
+// alpha fail processing
+#define	KEEP		0
+#define	FB_ONLY		1
+#define	ZB_ONLY		2
+#define	RGB_ONLY	3
+
+// texture functions
+#define MODULATE	0
+#define DECAL		1
+#define HIGHLIGHT	2
+#define HIGHLIGHT2	3
+
+// texture filters
+#define NEAREST					0
+#define LINEAR					1
+#define NEAREST_MIPMAP_NEAREST	2
+#define NEAREST_MIPMAP_LINEAR	3
+#define LINEAR_MIPMAP_NEAREST	4
+#define LINEAR_MIPMAP_LINEAR	5
+
+// texture wrap modes
+#define REPEAT			0
+#define CLAMP			1
+#define REGION_CLAMP	2
+#define REGION_REPEAT	3
+
+namespace NxPs2
+{
+
+class gs
+{
+
+public:
+
+	enum eReg
+	{
+		PRIM		= 0x00,		// GS p. 116  Drawing Primitive Setting (AC == 1)
+		RGBAQ		= 0x01,		// GS p. 119  Vertex Color Setting
+		ST			= 0x02,		// GS p. 123  Specification of Vertex Texture Coordinates (sets S and T)  FST == 0
+		UV			= 0x03,		// GS p. 135  Specification of Vertex Texture Coordinates (sets U and V)  FST == 1
+		XYZF2		= 0x04,		// GS p. 139  Setting for Vertex Coordinate Values (Fog coefficient)
+		XYZ2		= 0x05,		// GS p. 137  Setting for Vertex Coordinate Values
+		TEX0_1		= 0x06,		// GS p. 125  Texture Information Setting (type of textures to be used)
+		TEX0_2		= 0x07,		//            (mode, dimensions, blendfunc, CLUT)
+		CLAMP_1		= 0x08,		// GS p. 102  Texture wrap mode
+		CLAMP_2		= 0x09,
+		FOG			= 0x0A,		// GS p. 108  Vertex Fog Value Setting (per vert)
+		XYZF3		= 0x0C,		// GS p. 140  Setting for Vertex Coordinate Values (without Drawing Kick) (Fog coefficient)
+		XYZ3		= 0x0D,		// GS p. 138  Setting for Vertex Coordinate Values (without Drawing Kick)
+		A_D			= 0x0E,		// Undocumented???
+		NOP			= 0x0F,
+		TEX1_1		= 0x14,		// GS p. 127  Texture Information Setting (LOD info)
+		TEX1_2		= 0x15,
+		TEX2_1		= 0x16,		// GS p. 128  Texture Information Setting (Format/CLUT)
+		TEX2_2		= 0x17,
+		XYOFFSET_1	= 0x18,		// GS p. 136  Offset Value setting (prim to window coords)
+		XYOFFSET_2	= 0x19,
+		PRMODECONT	= 0x1A,		// GS p. 118  Specification of Primitive Attribute Setting Method
+		PRMODE		= 0x1B,		// GS p. 117  Setting for Attributes of Drawing Primitives (AC == 0)
+		TEXCLUT		= 0x1C,		// GS p. 130  CLUT Position Specification (CLUT offset in buffer)
+		SCANMSK		= 0x22,		// GS p. 120  Raster Address Mask Setting
+		MIPTBP1_1	= 0x34,		// GS p. 113  MIPMAP Information Setting (Level 1 to 3)
+		MIPTBP1_2	= 0x35,
+		MIPTBP2_1	= 0x36,		// GS p. 114  MIPMAP Information Setting (Level 4 to 6)
+		MIPTBP2_2	= 0x37,
+		TEXA		= 0x3B,		// GS p. 129  Texture Alpha Value Setting (sets alpha when alpha not 8-bit)
+		FOGCOL		= 0x3D,		// GS p. 109  Distant Fog Color Setting
+		TEXFLUSH	= 0x3F,		// GS p. 131  Texture Page Buffer Disabling (wait for drawing to complete)
+		SCISSOR_1	= 0x40,		// GS p. 121  Setting for Scissoring Area
+		SCISSOR_2	= 0x41,
+		ALPHA_1		= 0x42,		// GS p. 100  Alpha Blending Setting (Context 1)
+		ALPHA_2		= 0x43,
+		DIMX		= 0x44,		// GS p. 104  Dither Matrix Setting
+		DTHE		= 0x45,		// GS p. 105  Dither Control (0 off / 1 on)
+		COLCLAMP	= 0x46,		// GS p. 103  Color Clamp Control  (0 mask / 1 clamp)
+		TEST_1		= 0x47,		// GS p. 124  Pixel Test Control (Alpha test/Depth Test method and enable/disable, etc.)
+		TEST_2		= 0x48,
+		PABE		= 0x49,		// GS p. 115  Alpha Blending Control in Units of Pixels
+		FBA_1		= 0x4A,		// GS p. 106  Alpha Correction Value  (Sets fixed alpha value)
+		FBA_2		= 0x4B,
+		FRAME_1		= 0x4C,		// GS p. 110  Frame Buffer Setting
+		FRAME_2		= 0x4D,
+		ZBUF_1		= 0x4E,		// GS p. 141  Z Buffer Setting
+		ZBUF_2		= 0x4F,
+		BITBLTBUF	= 0x50,		// GS p. 101  Setting for transmission between buffers
+		TRXPOS		= 0x51,		// GS p. 133  Specification of Transmission Areas in Buffers (direction)
+		TRXREG		= 0x52,		// GS p. 134  Specification of Transmission Areas in Buffers
+		TRXDIR		= 0x53,		// GS p. 132  Activation of Transmission between Buffers
+		HWREG		= 0x54,		// GS p. 111  Data Port for Transmission between Buffers
+		SIGNAL		= 0x60,		// GS p. 122  SIGNAL Event Occurrence Request
+		FINISH		= 0x61,		// GS p. 107  Finish Event Occurance Request
+		LABEL		= 0x62,		// GS p. 112  LABEL Event Occurrence Request
+		A_D_NOP		= 0x7F		// Undocumented???
+	};
+
+	static void BeginPrim(uint FLG, uint Prim, uint Pre);
+	static void EndPrim(uint Eop);
+	static void Reg1(eReg Reg, uint64 Data);
+	static void Reg2(eReg Reg, uint64 Data);
+	static uint32 *gs::Reg2_pData(eReg Reg, uint64 Data);
+	static void ModifyTBP(eReg Reg, uint32 *p_reg_ptr, sint16 tbp1, sint16 tbp2 = -1, sint16 tbp3 = -1);
+
+}; // class gs
+
+
+
+// output 1 GS register via PATH1, V3_32 format
+inline void gs::Reg1(eReg Reg, uint64 Data)
+{
+//	dma::Store64(Data);
+//	dma::Store32((uint64)Reg);
+	uint32 *p_loc = (uint32*)dma::pLoc;
+	p_loc[0] = (uint32)Data;
+	p_loc[1] = Data>>32;
+	p_loc[2] = Reg;
+	dma::pLoc = (uint8*) (p_loc + 3);
+
+}
+
+
+} // namespace NxPs2
+
+
+
+
+
+#endif // __GS_H
+
diff --git a/Code/Gfx/NGPS/NX/immediate.cpp b/Code/Gfx/NGPS/NX/immediate.cpp
new file mode 100644
index 0000000..eaf4828
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/immediate.cpp
@@ -0,0 +1,534 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       immediate.cpp
+//* OWNER:          Garrett Jost
+//* CREATION DATE:  7/19/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include "gfx/ngps/nx/vu1code.h"
+#include "gfx/ngps/nx/mesh.h"
+#include "gfx/ngps/nx/sprite.h"
+#include "gfx/ngps/nx/switches.h"
+
+#include "gfx/ngps/nx/immediate.h"
+
+namespace NxPs2
+{
+
+uint64 CImmediateMode::sGetTextureBlend( uint32 blend_checksum, int fix )
+{
+	uint64 rv = PackALPHA(0,0,0,0,0);
+	switch ( blend_checksum )
+	{
+		case 0x54628ed7:		// Blend
+			rv = PackALPHA(0,1,0,1,0);
+			break;
+		case 0x02e58c18:		// Add
+			rv = PackALPHA(0,2,0,1,0);
+			break;
+		case 0xa7fd7d23:		// Sub
+		case 0xdea7e576:		// Subtract
+			rv = PackALPHA(2,0,0,1,0);
+			break;
+		case 0x40f44b8a:		// Modulate
+			rv = PackALPHA(1,2,0,2,0);
+			break;
+		case 0x68e77f40:		// Brighten
+			rv = PackALPHA(1,2,0,1,0);
+			break;
+		case 0x18b98905:		// FixBlend
+			rv = PackALPHA(0,1,2,1,fix);
+			break;
+		case 0xa86285a1:		// FixAdd
+			rv = PackALPHA(0,2,2,1,fix);
+			break;
+		case 0x0d7a749a:		// FixSub
+		case 0x0eea99ff:		// FixSubtract
+			rv = PackALPHA(2,0,2,1,fix);
+			break;
+		case 0x90b93703:		// FixModulate
+			rv = PackALPHA(1,2,2,2,fix);
+			break;
+		case 0xb8aa03c9:		// FixBrighten
+			rv = PackALPHA(1,2,2,1,fix);
+			break;
+		case 0x515e298e:		// Diffuse
+		case 0x806fff30:		// None
+			rv = PackALPHA(0,0,0,0,0);
+			break;
+		default:
+			Dbg_MsgAssert(0,("Illegal blend mode specified. Please use (fix)blend/add/sub/modulate/brighten or diffuse/none."));
+			break;
+	}
+	return rv;
+}
+
+void CImmediateMode::sViewportInit()
+{
+	// start a cnt tag
+	dma::BeginTag(dma::cnt, 0);
+	vif::STROW((int)render::RowRegI[0], (int)render::RowRegI[1], (int)render::RowRegI[2], (int)render::RowRegI[3]);
+
+	// VU context, uploading the view transform data
+	vu1::BeginPrim(ABS, VU1_ADDR(L_VF09));	  				// Begin packed register upload to VF10 
+	vu1::StoreVec(*(Vec *)&render::AltFrustum);				// VF09
+	vu1::StoreVec(*(Vec *)&render::InverseViewportScale);	// VF10
+	vu1::StoreVec(*(Vec *)&render::InverseViewportOffset);	// VF11
+//	vu1::StoreMat(*(Mat *)&render::AdjustedWorldToFrustum);	// VF12-15
+	vu1::StoreMat(*(Mat *)&render::AdjustedWorldToViewport);// VF12-15
+	vu1::EndPrim(0);										// End upload
+
+	// GS context
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::CLAMP_1,		PackCLAMP(CLAMP,CLAMP,0,0,0,0));		// may want to make REPEAT an option
+	gs::Reg1(gs::FRAME_1,		PackFRAME(FRAME_START,HRES/64,PSMCT32,0xFF000000));
+	gs::Reg1(gs::SCISSOR_1,		render::reg_SCISSOR);
+	gs::Reg1(gs::XYOFFSET_1,	render::reg_XYOFFSET);
+	gs::Reg1(gs::TEST_1, 		PackTEST(0,0,0,0,0,0,1,ZGEQUAL));
+	gs::Reg1(gs::COLCLAMP,		PackCOLCLAMP(1));
+	gs::Reg1(gs::ZBUF_1,		PackZBUF(ZBUFFER_START,PSMZ24,1));
+	gs::EndPrim(0);
+
+	// end the dma tag
+	dma::EndTag();
+}
+
+void CImmediateMode::sTextureGroupInit(uint unpackFLG)
+{
+	vif::STROW((int)render::RowRegI[0], (int)render::RowRegI[1], (int)render::RowRegI[2], (int)render::RowRegI[3]);
+
+	// VU context, uploading the view transform data
+	vu1::BeginPrim(unpackFLG, VU1_ADDR(L_VF10));	  				// Begin packed register upload to VF10 
+	vu1::StoreVec(*(Vec *)&render::InverseViewportScale);	// VF10
+	vu1::StoreVec(*(Vec *)&render::InverseViewportOffset);	// VF11
+//	vu1::StoreMat(*(Mat *)&render::AdjustedWorldToFrustum);	// VF12-15
+	vu1::StoreMat(*(Mat *)&render::AdjustedWorldToViewport);// VF12-15
+	vu1::EndPrim(0);										// End upload
+}
+
+void CImmediateMode::sSetZPush(float zpush)
+{
+	Mth::Matrix localToViewport(render::AdjustedWorldToViewport);
+	localToViewport[0][2] += localToViewport[0][3] * zpush * 64.0f;
+	localToViewport[1][2] += localToViewport[1][3] * zpush * 64.0f;
+	localToViewport[2][2] += localToViewport[2][3] * zpush * 64.0f;
+	localToViewport[3][2] += localToViewport[3][3] * zpush * 64.0f;
+
+	// VU context, uploading the view transform data
+	vu1::BeginPrim(ABS, VU1_ADDR(L_VF12));	  				// Begin packed register upload to VF12 
+	vu1::StoreMat(*(Mat *)&localToViewport);				// VF12-15
+	vu1::EndPrim(0);										// End upload
+}
+
+void CImmediateMode::sClearZPush()
+{
+	// VU context, uploading the view transform data
+	vu1::BeginPrim(ABS, VU1_ADDR(L_VF12));	  				// Begin packed register upload to VF12 
+	vu1::StoreMat(*(Mat *)&render::AdjustedWorldToViewport);// VF12-15
+	vu1::EndPrim(0);										// End upload
+}
+
+void CImmediateMode::sStartPolyDraw( SSingleTexture * p_engine_texture, uint64 blend, uint unpackFLG, bool clip )
+{
+	// GS context
+	gs::BeginPrim(unpackFLG,0,0);
+	gs::Reg1(gs::ALPHA_1,		blend);
+
+	if ( p_engine_texture )
+	{
+		gs::Reg1(gs::TEX0_1,	p_engine_texture->m_RegTEX0);
+		gs::Reg1(gs::TEX1_1,	p_engine_texture->m_RegTEX1);
+	}
+	gs::EndPrim(0);
+
+	// Set render flags.
+	vif::ITOP(clip ? CLIP : CULL);
+}
+
+//
+// Starts a poly draw with pre-packed texture registers (probably stolen from some other dma list)
+//
+void CImmediateMode::sStartPolyDraw( uint32 * p_packed_texture_regs, int num_texture_regs, uint unpackFLG, bool clip )
+{
+	// GS context
+	if (p_packed_texture_regs)
+	{
+		gs::BeginPrim(unpackFLG,0,0);
+		for (int i = 0; i < num_texture_regs; i++)
+		{
+			NxPs2::dma::Store32(*(p_packed_texture_regs++));
+			NxPs2::dma::Store32(*(p_packed_texture_regs++));
+			NxPs2::dma::Store32(*(p_packed_texture_regs++));
+		}
+		gs::EndPrim(0);
+	}
+
+	// Set render flags.
+	vif::ITOP(clip ? CLIP : CULL);
+}
+
+void CImmediateMode::sDrawQuadTexture( SSingleTexture * p_engine_texture, const Mth::Vector& vert0, const Mth::Vector& vert1,
+									   const Mth::Vector& vert2, const Mth::Vector& vert3,
+									   uint32 col0, uint32 col1, uint32 col2, uint32 col3 )
+{
+	// begin a batch of vertices
+	// Note: UV must always be 1st, Pos must always be last.
+	if ( p_engine_texture )
+	{
+		//BeginModelPrim(gs::XYZ2<<8|gs::RGBAQ<<4|gs::UV, 3, TRISTRIP|IIP|ABE|TME|FST, 1, VU1_ADDR(Proj));
+		vif::STCYCL(1,3);
+		vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+		gif::BeginTag1(gs::XYZF2<<8|gs::RGBAQ<<4|gs::UV, 3, PACKED, TRISTRIP|IIP|ABE|TME|FST, 1, VU1_ADDR(Proj));
+	}
+	else
+	{
+		//BeginModelPrim(gs::XYZ2<<4|gs::RGBAQ, 2, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+		vif::STCYCL(1,2);
+		vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+		gif::BeginTag1(gs::XYZF2<<4|gs::RGBAQ, 2, PACKED, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+	}
+
+	int loc = 1;
+
+	if ( p_engine_texture )
+	{
+		int u = p_engine_texture->GetWidth() << 4;
+		int v = p_engine_texture->GetHeight() << 4;
+
+		vif::BeginUNPACK(0, V2_16, ABS, UNSIGNED, loc);
+		vif::StoreV2_16(0,0);
+		vif::StoreV2_16(u,0);
+		vif::StoreV2_16(0,v);
+		vif::StoreV2_16(u,v);
+		vif::EndUNPACK();
+		loc++;
+	}
+
+	vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, loc);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col3);
+	vif::StoreS_32(col2);
+	vif::EndUNPACK();
+	loc++;
+
+	vif::STMOD(OFFSET_MODE);
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, loc);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert3[X]*SUB_INCH_PRECISION), (sint32)(vert3[Y]*SUB_INCH_PRECISION), (sint32)(vert3[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert2[X]*SUB_INCH_PRECISION), (sint32)(vert2[Y]*SUB_INCH_PRECISION), (sint32)(vert2[Z]*SUB_INCH_PRECISION), 0);
+	vif::EndUNPACK();
+	loc++;
+	vif::STMOD(NORMAL_MODE);
+
+	// finish batch of vertices
+	//EndModelMultiPrim();
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+void CImmediateMode::sDrawTri( const Mth::Vector& vert0, const Mth::Vector& vert1, const Mth::Vector& vert2,
+							   uint32 col0, uint32 col1, uint32 col2, uint unpackFLG )
+{
+	// begin a batch of vertices
+	// Note: ST must always be 1st, Pos must always be last.
+	//BeginModelPrim(gs::XYZ2<<4|gs::RGBAQ, 2, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+	vif::STCYCL(1,2);
+	vif::UNPACK(0,V4_32,1,unpackFLG,UNSIGNED,0);
+	gif::BeginTag1(gs::XYZF2<<4|gs::RGBAQ, 2, PACKED, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+
+	int loc = 1;
+
+	vif::BeginUNPACK(0, V4_8, unpackFLG, UNSIGNED, loc);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col2);
+	vif::EndUNPACK();
+	loc++;
+
+	vif::STMOD(OFFSET_MODE);
+	vif::BeginUNPACK(0, V4_32, unpackFLG, SIGNED, loc);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0xC000);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0xC000);
+	vif::StoreV4_32((sint32)(vert2[X]*SUB_INCH_PRECISION), (sint32)(vert2[Y]*SUB_INCH_PRECISION), (sint32)(vert2[Z]*SUB_INCH_PRECISION), 0);
+	vif::EndUNPACK();
+	vif::STMOD(NORMAL_MODE);
+	loc++;
+
+	// finish batch of vertices
+	//EndModelMultiPrim();
+	//EndModelPrim(1);
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+void CImmediateMode::sDrawTriUV( const Mth::Vector& vert0, const Mth::Vector& vert1, const Mth::Vector& vert2,
+								 float u0, float v0, float u1, float v1, float u2, float v2,
+								 uint32 col0, uint32 col1, uint32 col2, uint unpackFLG )
+{
+	// dummy gs context
+	gs::BeginPrim(unpackFLG,0,0);
+	gs::Reg1(gs::A_D_NOP, 0);
+	gs::EndPrim(0);
+
+	// gif tag
+	vif::UNPACK(0,V4_32,1,unpackFLG,UNSIGNED,0);
+	gif::BeginTag1(gs::XYZF2<<8|gs::RGBAQ<<4|gs::ST, 3, PACKED, TRISTRIP|IIP|ABE|TME, 1, VU1_ADDR(PTex) | (0x6 << 11) /* turns off UV correction */);
+
+	// each vertex has 3 elements
+	vif::STCYCL(1,3);
+
+	// ST
+	vif::UNPACK(0, V2_32, 3, unpackFLG, UNSIGNED, 1);
+	vif::StoreV2_32((sint32) (u0 * 4096.0f), (sint32) (v0 * 4096.0f));
+	vif::StoreV2_32((sint32) (u1 * 4096.0f), (sint32) (v1 * 4096.0f));
+	vif::StoreV2_32((sint32) (u2 * 4096.0f), (sint32) (v2 * 4096.0f));
+
+	// RGBA
+	vif::UNPACK(0, V4_8, 3, unpackFLG, UNSIGNED, 2);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col2);
+
+	// XYZ
+	vif::STMOD(OFFSET_MODE);
+	vif::UNPACK(0, V4_32, 3, unpackFLG, SIGNED, 3);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0xC000);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0xC000);
+	vif::StoreV4_32((sint32)(vert2[X]*SUB_INCH_PRECISION), (sint32)(vert2[Y]*SUB_INCH_PRECISION), (sint32)(vert2[Z]*SUB_INCH_PRECISION), 0);
+	vif::STMOD(NORMAL_MODE);
+
+	// finish batch of vertices
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+void CImmediateMode::sDraw5QuadTexture( SSingleTexture * p_engine_texture, const Mth::Vector& vert0, const Mth::Vector& vert1,
+									    const Mth::Vector& vert2, const Mth::Vector& vert3, const Mth::Vector& vert4,
+									    uint32 col0, uint32 col1 )
+{
+	// begin a batch of vertices
+	// Note: UV must always be 1st, Pos must always be last.
+	if ( p_engine_texture )
+	{
+		//BeginModelPrim(gs::XYZ2<<8|gs::RGBAQ<<4|gs::UV, 3, TRISTRIP|IIP|ABE|TME|FST, 1, VU1_ADDR(Proj));
+		vif::STCYCL(1,3);
+		vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+		gif::BeginTag1(gs::XYZF2<<8|gs::RGBAQ<<4|gs::ST, 3, PACKED, TRISTRIP|IIP|ABE|TME|FST, 1, VU1_ADDR(Proj));
+	}
+	else
+	{
+		//BeginModelPrim(gs::XYZ2<<4|gs::RGBAQ, 2, TRIFAN|IIP|ABE, 1, VU1_ADDR(Proj));
+		vif::STCYCL(1,2);
+		vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+		gif::BeginTag1(gs::XYZF2<<4|gs::RGBAQ, 2, PACKED, TRIFAN|IIP|ABE, 1, VU1_ADDR(Proj));
+	}
+
+	int loc = 1;
+
+	if ( p_engine_texture )
+	{
+		int u = p_engine_texture->GetWidth() << 4;
+		int v = p_engine_texture->GetHeight() << 4;
+
+		vif::BeginUNPACK(0, V2_16, ABS, UNSIGNED, loc);
+		vif::StoreV2_16(u>>1,v>>1);
+		vif::StoreV2_16(0,0);
+		vif::StoreV2_16(u,0);
+		vif::StoreV2_16(0,v);
+		vif::StoreV2_16(u,v);
+		vif::StoreV2_16(0,0);
+		vif::EndUNPACK();
+		loc++;
+	}
+
+	vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, loc);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col1);
+	vif::EndUNPACK();
+	loc++;
+
+	vif::STMOD(OFFSET_MODE);
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, loc);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert2[X]*SUB_INCH_PRECISION), (sint32)(vert2[Y]*SUB_INCH_PRECISION), (sint32)(vert2[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert3[X]*SUB_INCH_PRECISION), (sint32)(vert3[Y]*SUB_INCH_PRECISION), (sint32)(vert3[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert4[X]*SUB_INCH_PRECISION), (sint32)(vert4[Y]*SUB_INCH_PRECISION), (sint32)(vert4[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::EndUNPACK();
+	loc++;
+	vif::STMOD(NORMAL_MODE);
+
+	// finish batch of vertices
+	//EndModelMultiPrim();
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+void CImmediateMode::sDrawGlowSegment( const Mth::Vector& vert0, const Mth::Vector& vert1,
+									   const Mth::Vector& vert2, const Mth::Vector& vert3, const Mth::Vector& vert4,
+									   uint32 col0, uint32 col1, uint32 col2 )
+{
+	// begin a batch of vertices
+	// Note: UV must always be 1st, Pos must always be last.
+	//BeginModelPrim(gs::XYZ2<<4|gs::RGBAQ, 2, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+	vif::STCYCL(1,2);
+	vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+	gif::BeginTag1(gs::XYZF2<<4|gs::RGBAQ, 2, PACKED, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+
+	int loc = 1;
+
+	vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, loc);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col2);
+	vif::StoreS_32(col2);
+	vif::EndUNPACK();
+	loc++;
+
+	vif::STMOD(OFFSET_MODE);
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, loc);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert2[X]*SUB_INCH_PRECISION), (sint32)(vert2[Y]*SUB_INCH_PRECISION), (sint32)(vert2[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert3[X]*SUB_INCH_PRECISION), (sint32)(vert3[Y]*SUB_INCH_PRECISION), (sint32)(vert3[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert4[X]*SUB_INCH_PRECISION), (sint32)(vert4[Y]*SUB_INCH_PRECISION), (sint32)(vert4[Z]*SUB_INCH_PRECISION), 0);
+	vif::EndUNPACK();
+	loc++;
+	vif::STMOD(NORMAL_MODE);
+
+	// finish batch of vertices
+	//EndModelMultiPrim();
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+void CImmediateMode::sDrawStarSegment( const Mth::Vector& vert0, const Mth::Vector& vert1,
+									   const Mth::Vector& vert2, const Mth::Vector& vert3,
+									   uint32 col0, uint32 col1, uint32 col2 )
+{
+	// begin a batch of vertices
+	// Note: UV must always be 1st, Pos must always be last.
+	//BeginModelPrim(gs::XYZ2<<4|gs::RGBAQ, 2, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+	vif::STCYCL(1,2);
+	vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+	gif::BeginTag1(gs::XYZF2<<4|gs::RGBAQ, 2, PACKED, TRISTRIP|IIP|ABE, 1, VU1_ADDR(Proj));
+
+	int loc = 1;
+
+	vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, loc);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col2);
+	vif::EndUNPACK();
+	loc++;
+
+	vif::STMOD(OFFSET_MODE);
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, loc);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert2[X]*SUB_INCH_PRECISION), (sint32)(vert2[Y]*SUB_INCH_PRECISION), (sint32)(vert2[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert3[X]*SUB_INCH_PRECISION), (sint32)(vert3[Y]*SUB_INCH_PRECISION), (sint32)(vert3[Z]*SUB_INCH_PRECISION), 0);
+	vif::EndUNPACK();
+	loc++;
+	vif::STMOD(NORMAL_MODE);
+
+	// finish batch of vertices
+	//EndModelMultiPrim();
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+void CImmediateMode::sDrawSmoothStarSegment( const Mth::Vector& vert0, const Mth::Vector& vert1,
+											 const Mth::Vector& vert2, const Mth::Vector& vert3, const Mth::Vector& vert4,
+											 uint32 col0, uint32 col1, uint32 col2 )
+{
+	// begin a batch of vertices
+	// Note: UV must always be 1st, Pos must always be last.
+	//BeginModelPrim(gs::XYZ2<<4|gs::RGBAQ, 2, TRIFAN|IIP|ABE, 1, VU1_ADDR(Proj));
+	vif::STCYCL(1,2);
+	vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+	gif::BeginTag1(gs::XYZF2<<4|gs::RGBAQ, 2, PACKED, TRIFAN|IIP|ABE, 1, VU1_ADDR(Proj));
+
+	int loc = 1;
+
+	vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, loc);
+	vif::StoreS_32(col1);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col2);
+	vif::StoreS_32(col2);
+	vif::StoreS_32(col2);
+	vif::StoreS_32(col0);
+	vif::EndUNPACK();
+	loc++;
+
+	vif::STMOD(OFFSET_MODE);
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, loc);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert2[X]*SUB_INCH_PRECISION), (sint32)(vert2[Y]*SUB_INCH_PRECISION), (sint32)(vert2[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert3[X]*SUB_INCH_PRECISION), (sint32)(vert3[Y]*SUB_INCH_PRECISION), (sint32)(vert3[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert4[X]*SUB_INCH_PRECISION), (sint32)(vert4[Y]*SUB_INCH_PRECISION), (sint32)(vert4[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::EndUNPACK();
+	loc++;
+	vif::STMOD(NORMAL_MODE);
+
+	// finish batch of vertices
+	//EndModelMultiPrim();
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+void CImmediateMode::sDrawLine( const Mth::Vector& vert0, const Mth::Vector& vert1, uint32 col0, uint32 col1 )
+{
+	// begin a batch of vertices
+	// Note: UV must always be 1st, Pos must always be last.
+	//BeginModelPrim(gs::XYZ2<<4|gs::RGBAQ, 2, LINE|IIP|ABE, 1, VU1_ADDR(Proj));
+	vif::STCYCL(1,2);
+	vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+	gif::BeginTag1(gs::XYZ2<<4|gs::RGBAQ, 2, PACKED, LINE|IIP|ABE, 1, VU1_ADDR(Line));
+
+	int loc = 1;
+
+	vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, loc);
+	vif::StoreS_32(col0);
+	vif::StoreS_32(col1);
+	vif::EndUNPACK();
+	loc++;
+
+	vif::STMOD(OFFSET_MODE);
+	vif::BeginUNPACK(1, V4_32, ABS, SIGNED, loc);
+	vif::StoreV4_32((sint32)(vert0[X]*SUB_INCH_PRECISION), (sint32)(vert0[Y]*SUB_INCH_PRECISION), (sint32)(vert0[Z]*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(vert1[X]*SUB_INCH_PRECISION), (sint32)(vert1[Y]*SUB_INCH_PRECISION), (sint32)(vert1[Z]*SUB_INCH_PRECISION), 0);
+	vif::EndUNPACK();
+	loc++;
+	vif::STMOD(NORMAL_MODE);
+
+	// finish batch of vertices
+	//EndModelMultiPrim();
+	gif::EndTag1(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+}
+
+}
+
+
+
diff --git a/Code/Gfx/NGPS/NX/immediate.h b/Code/Gfx/NGPS/NX/immediate.h
new file mode 100644
index 0000000..456236b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/immediate.h
@@ -0,0 +1,69 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       immediate.h
+//* OWNER:          Garrett Jost
+//* CREATION DATE:  7/19/2002
+//****************************************************************************
+
+#ifndef	__IMMEDIATE_H__
+#define	__IMMEDIATE_H__
+    
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+namespace NxPs2
+{
+// Forward declarations
+struct SSingleTexture;
+
+class CImmediateMode
+{
+public:
+	// Init functions
+	static void sViewportInit();
+	static void sTextureGroupInit(uint unpackFLG);
+
+	static void sSetZPush(float zpush = 1.0e-33f);
+	static void sClearZPush();
+
+	// Draw start functions
+	static void sStartPolyDraw( SSingleTexture * p_engine_texture, uint64 blend, uint unpackFLG, bool clip = false );
+	static void sStartPolyDraw( uint32 * p_packed_texture_regs, int num_texture_regs, uint unpackFLG, bool clip = false );
+
+	// Draw functions
+	static void sDrawQuadTexture( SSingleTexture * p_engine_texture, const Mth::Vector& vert0, const Mth::Vector& vert1,
+								  const Mth::Vector& vert2, const Mth::Vector& vert3,
+								  uint32 col0, uint32 col1, uint32 col2, uint32 col3 );
+	static void sDrawTri( const Mth::Vector& vert0, const Mth::Vector& vert1, const Mth::Vector& vert2,
+						  uint32 col0, uint32 col1, uint32 col2, uint unpackFLG );
+	static void sDrawTriUV( const Mth::Vector& vert0, const Mth::Vector& vert1, const Mth::Vector& vert2,
+							float u0, float v0, float u1, float v1, float u2, float v2,
+							uint32 col0, uint32 col1, uint32 col2, uint unpackFLG );
+	static void sDraw5QuadTexture( SSingleTexture * p_engine_texture, const Mth::Vector& vert0, const Mth::Vector& vert1,
+								   const Mth::Vector& vert2, const Mth::Vector& vert3, const Mth::Vector& vert4,
+								   uint32 col0, uint32 col1 );
+	static void sDrawGlowSegment( const Mth::Vector& vert0, const Mth::Vector& vert1,
+								  const Mth::Vector& vert2, const Mth::Vector& vert3, const Mth::Vector& vert4,
+								  uint32 col0, uint32 col1, uint32 col2 );
+	static void sDrawStarSegment( const Mth::Vector& vert0, const Mth::Vector& vert1,
+								  const Mth::Vector& vert2, const Mth::Vector& vert3,
+								  uint32 col0, uint32 col1, uint32 col2 );
+	static void sDrawSmoothStarSegment( const Mth::Vector& vert0, const Mth::Vector& vert1,
+										const Mth::Vector& vert2, const Mth::Vector& vert3, const Mth::Vector& vert4,
+										uint32 col0, uint32 col1, uint32 col2 );
+	static void sDrawLine( const Mth::Vector& vert0, const Mth::Vector& vert1, uint32 col0, uint32 col1 );
+
+	static uint64 sGetTextureBlend( uint32 blend_checksum, int fix );
+
+protected:
+};
+
+}
+
+#endif 
+
+
+
+
diff --git a/Code/Gfx/NGPS/NX/instance.cpp b/Code/Gfx/NGPS/NX/instance.cpp
new file mode 100644
index 0000000..0208a7e
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/instance.cpp
@@ -0,0 +1,277 @@
+#include "instance.h"
+#include "scene.h"
+#include "dma.h"
+#include "mesh.h"
+#include 
+
+namespace NxPs2
+{
+
+
+CInstance::CInstance(sScene *pScene, Mth::Matrix transform, bool color_per_material)
+{
+	SetTransform(transform);
+	mp_bone_transforms = NULL;
+	m_num_bones = 0;
+	m_flags = INSTANCEFLAG_ACTIVE;
+	mp_next = pScene->pInstances;
+	pScene->pInstances = this;
+	m_lastFrame = 0;
+	m_field = 0;
+	mp_scene = pScene;
+	mp_light_group = NULL;
+	if (color_per_material)
+	{
+		m_flags |= INSTANCEFLAG_COLORPERMATERIAL;
+		mp_color_array = new uint32[mp_scene->NumMeshes];
+		for (int i = 0; i < mp_scene->NumMeshes; i++)
+		{
+			mp_color_array[i] = 0x80808080;
+		}
+	}
+	else
+	{
+		m_colour = 0x80808080;
+	}
+}
+
+
+CInstance::CInstance(sScene *pScene, Mth::Matrix transform, bool color_per_material, int numBones, Mth::Matrix *pBoneTransforms)
+{
+	SetTransform(transform);
+	mp_bone_transforms = pBoneTransforms;
+	m_num_bones = numBones;
+	m_flags = INSTANCEFLAG_ACTIVE;
+	mp_next = pScene->pInstances;
+	pScene->pInstances = this;
+	m_lastFrame = 0;
+	m_field = 0;
+	mp_scene = pScene;
+	mp_light_group = NULL;
+	if (color_per_material)
+	{
+		m_flags |= INSTANCEFLAG_COLORPERMATERIAL;
+		mp_color_array = new uint32[mp_scene->NumMeshes];
+		for (int i = 0; i < mp_scene->NumMeshes; i++)
+		{
+			mp_color_array[i] = 0x80808080;
+		}
+	}
+	else
+	{
+		m_colour = 0x80808080;
+	}
+}
+
+
+CInstance::~CInstance()
+{
+	if (HasColorPerMaterial())
+	{
+		delete [] mp_color_array;
+	}
+
+	// remove from list structure...
+	// head of instance list
+	if (mp_scene->pInstances == this)
+	{
+		mp_scene->pInstances = mp_next;
+	}
+	// body of instance list
+	else
+	{
+		CInstance *pInstance;
+		for (pInstance=mp_scene->pInstances; pInstance; pInstance=pInstance->mp_next)
+			if (pInstance->mp_next == this)
+				break;
+		Dbg_MsgAssert(pInstance, ("couldn't locate CInstance for deletion"));
+		pInstance->mp_next = mp_next;
+	}
+}
+
+
+void CInstance::SetActive(bool Active)
+{
+	if (Active)
+	{
+		m_flags |= INSTANCEFLAG_ACTIVE;
+	}
+	else
+	{
+		m_flags &= ~INSTANCEFLAG_ACTIVE;
+	}
+}
+
+void CInstance::EnableShadow(bool Enabled)
+{
+	if (Enabled)
+	{
+		m_flags |= INSTANCEFLAG_CASTSSHADOW;
+	}
+	else
+	{
+		m_flags &= ~INSTANCEFLAG_CASTSSHADOW;
+	}
+}
+
+void CInstance::SetWireframe(bool Wireframe)
+{
+	if (Wireframe)
+	{
+		m_flags |= INSTANCEFLAG_WIREFRAME;
+	}
+	else
+	{
+		m_flags &= ~INSTANCEFLAG_WIREFRAME;
+	}
+}
+
+Mth::Matrix* CInstance::GetBoneTransforms(void)
+{
+	return m_field ? mp_bone_transforms : ( mp_bone_transforms + m_num_bones );
+}
+
+void CInstance::SetBoneTransforms(Mth::Matrix* pMat)
+{
+	// GJ:  toggle field, but only the first time
+	// (otherwise, the double-buffer gets messed
+	// up if SetBoneTransforms() is called more
+	// than once per frame...)
+	uint8 thisFrame = ( render::Frame & 0xff );
+	if ( m_lastFrame != thisFrame )
+	{
+		m_field = !m_field;
+		m_lastFrame = thisFrame;
+	}
+
+	int startIndex = m_field ? 0 : m_num_bones;
+
+	Mth::Matrix* pSrc = pMat;
+	Mth::Matrix* pDst = mp_bone_transforms + startIndex;
+
+	// copy the given matrices into the correct half of the matrix double-buffer
+	for ( int i = 0; i < m_num_bones; i++ )
+	{
+		*pDst++ = *pSrc++;
+	}
+}
+
+
+void CInstance::SetColor(uint32 rgba)
+{
+	Dbg_Assert(!HasColorPerMaterial());
+
+	m_colour = rgba;
+}
+
+uint32 CInstance::GetColor()
+{
+	Dbg_Assert(!HasColorPerMaterial());
+
+	return m_colour;
+}
+
+void CInstance::SetMaterialColor(uint32 material_name, int pass, uint32 rgba)
+{
+	Dbg_Assert(HasColorPerMaterial());
+
+	int i;
+	sMesh *pMesh;
+
+	// Array is in mesh order
+	for (pMesh = mp_scene->pMeshes, i = 0; i < mp_scene->NumMeshes; pMesh++, i++)
+	{
+		if ((pMesh->MaterialName==material_name) && (pMesh->Pass == pass))
+		{
+			mp_color_array[i] = rgba;
+		}
+	}
+}
+
+void CInstance::SetMaterialColorByIndex(int mesh_idx, uint32 rgba)
+{
+	Dbg_Assert(HasColorPerMaterial());
+	Dbg_MsgAssert(mesh_idx < mp_scene->NumMeshes, ("Mesh index %d is out of range", mesh_idx));
+
+	mp_color_array[mesh_idx] = rgba;
+}
+
+uint32 CInstance::GetMaterialColor(uint32 material_name, int pass)
+{
+	Dbg_Assert(HasColorPerMaterial());
+
+	int i;
+	sMesh *pMesh;
+
+	// Array is in mesh order
+	for (pMesh = mp_scene->pMeshes, i = 0; i < mp_scene->NumMeshes; pMesh++, i++)
+	{
+		if ((pMesh->MaterialName==material_name) && (pMesh->Pass == pass))
+		{
+			return mp_color_array[i];
+		}
+	}
+
+	return 0x80808080;
+}
+
+uint32 CInstance::GetMaterialColorByIndex(int mesh_idx)
+{
+	Dbg_Assert(HasColorPerMaterial());
+
+	return mp_color_array[mesh_idx];
+}
+
+Mth::Vector CInstance::GetBoundingSphere()
+{
+
+	if (m_flags & INSTANCEFLAG_EXPLICITSPHERE)
+	{
+		return m_bounding_sphere;
+	}
+	else
+	{
+		Mth::Vector sphere(mp_scene->Sphere);
+		sphere[3] += 24.0f;
+		return sphere;
+	}
+}
+
+void CInstance::SetBoundingSphere(float x, float y, float z, float r)
+{
+	//printf( "Stub:  SetBoundingSphere %f %f %f %f\n", x, y, z, r );
+	m_bounding_sphere.Set(x,y,z,r);
+	m_flags |= INSTANCEFLAG_EXPLICITSPHERE;
+}
+
+
+void CInstance::SqueezeADC()
+{
+	int i, num_meshes = mp_scene->NumMeshes;
+	sMesh *p_mesh = mp_scene->pMeshes;
+
+	for (i=0; ipSubroutine);
+		dma::SqueezeNOP(p_mesh->pSubroutine);
+	}
+}
+
+
+
+// uv manipulation for skins
+
+void CInstance::SetUVOffset(uint32 material_name, int pass, float u_offset, float v_offset)
+{
+	mp_scene->SetUVOffset(material_name, pass, u_offset, v_offset);
+}
+
+
+void CInstance::SetUVMatrix(uint32 material_name, int pass, const Mth::Matrix &mat)
+{
+	mp_scene->SetUVMatrix(material_name, pass, mat);
+}
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/instance.h b/Code/Gfx/NGPS/NX/instance.h
new file mode 100644
index 0000000..8acd6ce
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/instance.h
@@ -0,0 +1,123 @@
+#ifndef __INSTANCE_H
+#define __INSTANCE_H
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include "scene.h"
+#include 
+
+
+namespace Script
+{
+	extern int GetInt(uint32, bool);
+}
+
+namespace NxPs2
+{
+
+// Forward declarations
+class	CLightGroup;
+
+#define INSTANCEFLAG_ACTIVE				(1<<0)
+#define INSTANCEFLAG_CASTSSHADOW		(1<<1)
+#define INSTANCEFLAG_EXPLICITSPHERE		(1<<2)
+#define INSTANCEFLAG_WIREFRAME			(1<<3)
+#define INSTANCEFLAG_COLORPERMATERIAL	(1<<4)
+
+class CInstance
+{
+public:
+	void SetTransform(Mth::Matrix transform) { m_transform = transform; }
+	Mth::Matrix *GetTransform(void) { return &m_transform; }
+	int GetNumBones(void) { return m_num_bones; }
+	Mth::Matrix *GetBoneTransforms(void);
+	void SetBoneTransforms(Mth::Matrix *pMat);
+	CInstance *GetNext() { return mp_next; }
+	sScene *GetScene() { return mp_scene; }
+	void SetActive(bool Active);
+	void EnableShadow(bool Enabled);
+	void SetWireframe(bool Wireframe);
+	bool IsActive(void) const; 
+	bool CastsShadow(void) const; 
+	bool IsWireframe(void) const; 
+	bool HasColorPerMaterial(void) const; 
+	void SetColor(uint32 rgba);
+	void SetMaterialColor(uint32 material_name, int pass, uint32 rgba);
+	void SetMaterialColorByIndex(int mesh_idx, uint32 rgba);
+	uint32 GetColor();
+	uint32 GetMaterialColor(uint32 material_name, int pass);
+	uint32 GetMaterialColorByIndex(int mesh_idx);
+	void SetLightGroup(CLightGroup *p_light_group);
+	CLightGroup *GetLightGroup();
+	Mth::Vector GetBoundingSphere();
+	void SetBoundingSphere(float x, float y, float z, float r);
+	void SqueezeADC();
+	void SetUVOffset(uint32 material_name, int pass, float u_offset, float v_offset);
+	void SetUVMatrix(uint32 material_name, int pass, const Mth::Matrix &mat);
+	CInstance(sScene *pScene, Mth::Matrix transform, bool color_per_material);
+	CInstance(sScene *pScene, Mth::Matrix transform, bool color_per_material, int numBones, Mth::Matrix *pBoneTransforms);
+	~CInstance();
+
+private:
+	Mth::Matrix m_transform;
+	Mth::Matrix *mp_bone_transforms;
+	Mth::Vector m_bounding_sphere;
+	uint32 m_flags;
+	union
+	{
+		uint32 m_colour;
+		uint32 *mp_color_array;			// Use this only when INSTANCEFLAG_COLORPERMATERIAL is set; in Mesh order
+	};
+	int m_num_bones;
+	CInstance *mp_next;
+	sScene *mp_scene;
+	CLightGroup *mp_light_group;		// optional group of lights
+	uint8 m_lastFrame;
+	char m_field;
+};
+
+
+//  IsActive needs to be inline, otherwise it takes 1% of rendering cpu time
+inline bool CInstance::IsActive(void) const
+{
+	return (m_flags & INSTANCEFLAG_ACTIVE) ? true : false;
+}
+
+inline bool CInstance::CastsShadow(void) const
+{
+	return (m_flags & INSTANCEFLAG_CASTSSHADOW) ? true : false;
+}
+
+inline bool CInstance::IsWireframe(void) const
+{
+	#ifdef	__NOPT_ASSERT__
+	if (Script::GetInt(CRCD(0xb4ff073e,"wireframe_skins"),false))
+	{
+		return true;
+	}
+	#endif
+	return (m_flags & INSTANCEFLAG_WIREFRAME) ? true : false;
+}
+
+inline bool CInstance::HasColorPerMaterial(void) const
+{
+	return m_flags & INSTANCEFLAG_COLORPERMATERIAL;
+}
+
+inline void CInstance::SetLightGroup(CLightGroup *p_light_group)
+{
+	mp_light_group = p_light_group;
+}
+
+inline CLightGroup * CInstance::GetLightGroup()
+{
+	return mp_light_group;
+}
+
+} // namespace NxPs2
+
+
+#endif // __INSTANCE_H
+
diff --git a/Code/Gfx/NGPS/NX/interrupts.cpp b/Code/Gfx/NGPS/NX/interrupts.cpp
new file mode 100644
index 0000000..d5d4747
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/interrupts.cpp
@@ -0,0 +1,361 @@
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "group.h"
+#include "render.h"
+#include "gs.h"
+#include "switches.h"
+#include 
+
+//#undef	__USE_PROFILER__
+
+
+/*
+
+ Mick Notes:
+
+ All interrupts are set up in the "InitialiseEngine" function of nx_init.cpp
+ 
+ There are three types of interrupt covered here
+ 
+ 1) GIF Interrupt - Used to signal the completion of texture upload	to VRAM
+ 2) GS  Interrupt - Used to signal the completion of GS rendering (drawing polygons)
+ 3) VIF Interrupt - NOT USED, could be used like the GS interrupt, but unreliable.
+
+ There are two types of activity going on in parallel: Upload and Render
+ 
+ Whenever one of these finishes, it causes an interrupt. 
+ 
+ There is no way of knowing in advance what order the tasks will finish in
+ so we must handle all possible cases. 
+
+ At any time, either upload or render might be "Stalled".  In this case we are waiting
+ for something before we can start rendering or uploading a new group.  Stalls are not
+ good, as the relevent processor is sat idle.  We want to minimize stalls as much as
+ possible.  
+ 
+ Stalls happen when:
+ 
+  - The initial render group is waiting for its textures to upload.  
+    (?) The renderer is always stalled on the first group 
+  - An upload group want to use some VRAM that is used by a group 
+    that is currently rendering. 
+  - A render group is waiting for it's textures to upload. This could
+    happen if a group renders quicker than the next groups uploads.
+
+*/
+   
+   
+   
+namespace NxPs2
+{
+
+
+#define START_UPLOAD()																\
+{																					\
+	*D2_QWC = 0;																	\
+	*D2_TADR = (uint)sGroup::pUploadGroup->pUpload[!render::Field];							\
+	*D2_CHCR = 0x105;																\
+	UploadStalled = 0;																\
+}
+
+#if GS_INTERRUPT
+#define START_RENDER()																\
+{																					\
+	*D1_QWC = 0;																	\
+	*D1_TADR = (uint)sGroup::pRenderGroup->pRender[!render::Field];							\
+	*D1_CHCR = 0x145;								   								\
+	RenderStalled = 0;																\
+}
+#endif
+
+#if VIF1_INTERRUPT
+#define START_RENDER()																\
+{																					\
+if (sGroup::pRenderGroup != sGroup::pHead)											\
+		*VIF1_FBRST = 1<<3;															\
+	else																			\
+	{																				\
+		*D1_QWC = 0;																\
+		*D1_TADR = (uint)sGroup::pRenderGroup->pRender[!render::Field];						\
+		*D1_CHCR = 0x145;								   							\
+	}																				\
+}
+#endif
+
+#define NO_VRAM_CONFLICT()															\
+(																					\
+	sGroup::pRenderGroup->VramStart >= sGroup::pUploadGroup->VramEnd ||				\
+	sGroup::pUploadGroup->VramStart >= sGroup::pRenderGroup->VramEnd				\
+)
+
+
+volatile uint32 UploadStalled, RenderStalled;
+
+
+
+// handler called when an upload finishes
+
+int GifHandler(int Cause)
+{
+	if (!sGroup::pUploadGroup)		// Mick:  Might not actually be anything to upload, if p_memview was uploading
+		goto EXIT;
+	
+	// we've finished uploading a group
+	sGroup::pUploadGroup = sGroup::pUploadGroup->pNext;
+	while (sGroup::pUploadGroup && !sGroup::pUploadGroup->Used[!render::Field])		// if exists, but not used
+	{
+		sGroup::pUploadGroup = sGroup::pUploadGroup->pNext;	// then skip groups until reach end, or find one that is used
+	}
+
+
+#	ifdef __USE_PROFILER__
+	Sys::DMAProfiler->PopContext();	 
+#	endif // __USE_PROFILER__
+
+	// is there another group to upload?
+	if (sGroup::pUploadGroup)
+	{
+
+		
+		// was there a stalled render?
+		if (RenderStalled)
+		{
+			// kick off the stalled render
+#	ifdef __USE_PROFILER__
+				Sys::VUProfiler->PushContext( sGroup::pRenderGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+			START_RENDER();
+
+			// is the next upload safe?
+			if (NO_VRAM_CONFLICT())
+			{
+				// kick off the next upload
+#	ifdef __USE_PROFILER__
+				Sys::DMAProfiler->PushContext( sGroup::pUploadGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+				START_UPLOAD();
+			}
+			else
+			{
+				// stall the next upload
+				UploadStalled = 1;
+			}
+		}
+
+		else// there's a render in progress
+		{
+			// since we just finished an upload, then the render in progress MUST
+			// be using the previously uploaded group
+			// so there is no way that we can start a new upload until that render finishes.
+			// so, stall the next upload
+			UploadStalled = 1;
+		}
+	}
+
+	else// no more groups to upload
+	{
+		// was the final render stalled?
+		if (RenderStalled)
+		{
+			// kick it off
+#	ifdef __USE_PROFILER__
+				Sys::VUProfiler->PushContext(  sGroup::pRenderGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+			START_RENDER();
+		}
+	}
+
+EXIT:
+	ExitHandler();
+	return 0;
+}
+
+
+
+
+// handler called when a render finishes (gs version)
+
+int GsHandler(int Cause)
+{
+	// mask GS interrupts
+	*GS_IMR = PackIMR(1,1,1,1);
+
+	// reset SIGNAL event bit
+	*GS_CSR = *GS_CSR & ~(uint64)0x30F | 1;
+
+	// reset signal bit
+	*GS_SIGLBLID &= ~(uint64)1;
+
+	// maybe it was a false alarm...
+	if (!sGroup::pRenderGroup)	// if nothing currently rendering, then we've finished!!
+		goto EXIT;
+	
+	// we've finished rendering a group, so find the next group to render
+	sGroup::pRenderGroup = sGroup::pRenderGroup->pNext;
+	while (sGroup::pRenderGroup && !sGroup::pRenderGroup->Used[!render::Field])		// if exists, but not used
+	{
+		sGroup::pRenderGroup = sGroup::pRenderGroup->pNext;	// then skip groups until reach end, or find one that is used
+	}
+
+	#	ifdef __USE_PROFILER__
+	Sys::VUProfiler->PopContext();	 
+	#	endif // __USE_PROFILER__
+
+	
+	// is there another group to render?
+	if (sGroup::pRenderGroup)
+	{
+		// was there a stalled upload?
+		if (UploadStalled)
+		{
+			// is the next render waiting for the stalled upload?
+			// (i.e., they both are the same group)
+			if (sGroup::pRenderGroup == sGroup::pUploadGroup)
+			{
+				// kick off the stalled upload
+#	ifdef __USE_PROFILER__
+				Sys::DMAProfiler->PushContext(  sGroup::pUploadGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+				START_UPLOAD();
+
+				// but stall the next render
+				// as this upload group is needed for this render 
+				RenderStalled = 2;
+			}
+			else
+			{
+				// kick off the next render, as it's from a previous group
+#	ifdef __USE_PROFILER__
+				Sys::VUProfiler->PushContext(  sGroup::pRenderGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+				START_RENDER();
+
+				// is the next upload safe?
+				if (NO_VRAM_CONFLICT())
+				{
+					// kick off the next upload
+#	ifdef __USE_PROFILER__
+					Sys::DMAProfiler->PushContext(  sGroup::pUploadGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+					START_UPLOAD();
+				}
+			}
+		}
+
+		// is there an upload in progress?
+		// (Mick: Does it make sense to also check to see if the upload group is the same as the render group?
+		// or is that always the case, and that's why Mike is not checking it?)
+		else if (sGroup::pUploadGroup)
+		{
+			// stall the next render
+			RenderStalled = 1;
+		}
+
+		// no uploads left
+		else
+		{
+			// kick off the final render
+#	ifdef __USE_PROFILER__
+				Sys::VUProfiler->PushContext(  sGroup::pRenderGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+			START_RENDER();
+		}
+	}
+
+EXIT:
+	// re-enable GS interrupts
+	*GS_IMR = PackIMR(0,1,1,1);
+
+	ExitHandler();
+	return 0;
+}
+
+
+
+
+
+// handler called when a render finishes (vif version)
+// (Mick:  not currently used, ignore for now)
+int Vif1Handler(int Cause)
+{
+	// make sure the mark register gives the go-ahead
+	if (*VIF1_MARK != 1)
+		goto EXIT;
+
+	// maybe it was a false alarm...
+	if (!sGroup::pRenderGroup)
+		goto EXIT;
+	
+	// we've finished rendering a group
+	sGroup::pRenderGroup = sGroup::pRenderGroup->pNext;		// get next group
+	
+	// is there another group to render?
+	if (sGroup::pRenderGroup)
+	{
+		// was there a stalled upload?
+		if (UploadStalled)
+		{
+			// is the next render waiting for the stalled upload?
+			if (sGroup::pRenderGroup == sGroup::pUploadGroup)
+			{
+				// kick off the stalled upload
+#	ifdef __USE_PROFILER__
+				Sys::DMAProfiler->PushContext(  sGroup::pUploadGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+				START_UPLOAD();
+				UploadStalled = 0;
+
+				// but stall the next render
+				RenderStalled = 2;
+			}
+			else
+			{
+				// kick off the next render
+#	ifdef __USE_PROFILER__
+				Sys::VUProfiler->PushContext(  sGroup::pRenderGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+				START_RENDER();
+
+				// is the next upload safe?
+				if (NO_VRAM_CONFLICT())
+				{
+					// kick off the next upload
+#	ifdef __USE_PROFILER__
+					Sys::DMAProfiler->PushContext(  sGroup::pUploadGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+					START_UPLOAD();
+					UploadStalled = 0;
+				}
+			}
+		}
+
+		// is there an upload in progress?
+		else if (sGroup::pUploadGroup)
+		{
+			// stall the next render
+			RenderStalled = 1;
+		}
+
+		// no uploads left
+		else
+		{
+			// kick off the final render
+#	ifdef __USE_PROFILER__
+				Sys::VUProfiler->PushContext(  sGroup::pRenderGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+			START_RENDER();
+		}
+	}
+
+EXIT:
+	ExitHandler();
+	return 0;
+}
+
+
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/interrupts.h b/Code/Gfx/NGPS/NX/interrupts.h
new file mode 100644
index 0000000..ea6befe
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/interrupts.h
@@ -0,0 +1,112 @@
+#ifndef __INTERRUPTS_H
+#define __INTERRUPTS_H
+
+namespace NxPs2
+{
+
+int TextureSyncHandler(int Cause);
+int DMATextureSyncHandler(int Cause);
+int GifHandler(int Cause);
+int GsHandler(int Cause);
+int Vif1Handler(int Cause);
+
+extern volatile uint32 UploadStalled, RenderStalled;
+
+// Inlines
+
+// If you need to use floating point in an interrupt, you MUST use the functions saveFloatRegs()
+// and restoreFloatRegs() or some equivalent.
+
+//----------------------------------------------------------------------------
+//
+inline void saveFloatRegs(unsigned int *buffer)
+{
+    asm volatile ("
+      swc1    $f00,0x00(%0)
+      swc1    $f01,0x04(%0)
+      swc1    $f02,0x08(%0)
+      swc1    $f03,0x0c(%0)
+      swc1    $f04,0x10(%0)
+      swc1    $f05,0x14(%0)
+      swc1    $f06,0x18(%0)
+      swc1    $f07,0x1c(%0)
+
+      swc1    $f08,0x20(%0)
+      swc1    $f09,0x24(%0)
+      swc1    $f10,0x28(%0)
+      swc1    $f11,0x2c(%0)
+      swc1    $f12,0x30(%0)
+      swc1    $f13,0x34(%0)
+      swc1    $f14,0x38(%0)
+      swc1    $f15,0x3c(%0)
+
+      swc1    $f16,0x40(%0)
+      swc1    $f17,0x44(%0)
+      swc1    $f18,0x48(%0)
+      swc1    $f19,0x4c(%0)
+      swc1    $f20,0x50(%0)
+      swc1    $f21,0x54(%0)
+      swc1    $f22,0x58(%0)
+      swc1    $f23,0x5c(%0)
+
+      swc1    $f24,0x60(%0)
+      swc1    $f25,0x64(%0)
+      swc1    $f26,0x68(%0)
+      swc1    $f27,0x6c(%0)
+      swc1    $f28,0x70(%0)
+      swc1    $f29,0x74(%0)
+      swc1    $f30,0x78(%0)
+      swc1    $f31,0x7c(%0)
+
+    ": : "r" (buffer));
+}
+
+//----------------------------------------------------------------------------
+//
+inline void restoreFloatRegs(unsigned int *buffer)
+{
+    asm volatile ("
+      lwc1    $f00,0x00(%0)
+      lwc1    $f01,0x04(%0)
+      lwc1    $f02,0x08(%0)
+      lwc1    $f03,0x0c(%0)
+      lwc1    $f04,0x10(%0)
+      lwc1    $f05,0x14(%0)
+      lwc1    $f06,0x18(%0)
+      lwc1    $f07,0x1c(%0)
+
+      lwc1    $f08,0x20(%0)
+      lwc1    $f09,0x24(%0)
+      lwc1    $f10,0x28(%0)
+      lwc1    $f11,0x2c(%0)
+      lwc1    $f12,0x30(%0)
+      lwc1    $f13,0x34(%0)
+      lwc1    $f14,0x38(%0)
+      lwc1    $f15,0x3c(%0)
+
+      lwc1    $f16,0x40(%0)
+      lwc1    $f17,0x44(%0)
+      lwc1    $f18,0x48(%0)
+      lwc1    $f19,0x4c(%0)
+      lwc1    $f20,0x50(%0)
+      lwc1    $f21,0x54(%0)
+      lwc1    $f22,0x58(%0)
+      lwc1    $f23,0x5c(%0)
+
+      lwc1    $f24,0x60(%0)
+      lwc1    $f25,0x64(%0)
+      lwc1    $f26,0x68(%0)
+      lwc1    $f27,0x6c(%0)
+      lwc1    $f28,0x70(%0)
+      lwc1    $f29,0x74(%0)
+      lwc1    $f30,0x78(%0)
+      lwc1    $f31,0x7c(%0)
+
+    ": : "r" (buffer));
+}
+
+} // namespace NxPs2
+
+
+#endif // __INTERRUPTS_H
+
diff --git a/Code/Gfx/NGPS/NX/light.cpp b/Code/Gfx/NGPS/NX/light.cpp
new file mode 100644
index 0000000..048c0c2
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/light.cpp
@@ -0,0 +1,59 @@
+#include 
+#include "light.h"
+
+namespace NxPs2
+{
+
+
+// Statics
+CLightGroup		CLightGroup::s_default_lights;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CLightGroup::CLightGroup()
+{
+	if (this != &s_default_lights)	   // can't initialize from itself
+	{
+		// Don't use new lights unless someone sets them
+		m_flags = 0;
+	
+		// Init to default lights
+		m_light_vector = s_default_lights.m_light_vector;
+	
+		m_diffuse_base_color = s_default_lights.m_diffuse_base_color;
+		m_diffuse_mod_color = s_default_lights.m_diffuse_base_color;
+	
+		m_ambient_base_color = s_default_lights.m_ambient_base_color;
+		m_ambient_mod_color = s_default_lights.m_ambient_base_color;
+	}
+	else
+	{
+		m_light_vector[0].Set(0,-1,0);
+		m_light_vector[1].Set(0,-1,0);
+		m_light_vector[2].Set(0,-1,0);
+		m_diffuse_base_color[0].Set() ;
+		m_diffuse_base_color[1].Set() ;
+		m_diffuse_base_color[2].Set() ;
+		m_diffuse_mod_color[0].Set()  ;
+		m_diffuse_mod_color[1].Set()  ;
+		m_diffuse_mod_color[2].Set()  ;
+		m_ambient_base_color.Set() ;
+		m_ambient_mod_color.Set()  ;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CLightGroup::~CLightGroup()
+{
+}
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/light.h b/Code/Gfx/NGPS/NX/light.h
new file mode 100644
index 0000000..c69f298
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/light.h
@@ -0,0 +1,417 @@
+#ifndef __LIGHT_H
+#define __LIGHT_H
+
+#include	
+#include	
+
+namespace NxPs2
+{
+
+// Forward declarations
+class CLightGroup;
+
+#if 0
+class CBaseLightGroup
+{
+public:
+								CBaseLightGroup();
+	virtual						~CBaseLightGroup();
+
+	virtual const Mth::Vector &	GetDirection(int idx) const;
+
+	virtual const Mth::Vector &	GetAmbientColor() const;
+	virtual const Mth::Vector &	GetDiffuseColor(int idx) const;
+
+protected:
+	// Directional light vectors
+	Mth::Vector					m_light_vector[MAX_DIFFUSE_LIGHTS];
+
+	// Light base colors
+	Mth::Vector 				m_diffuse_base_color[MAX_DIFFUSE_LIGHTS];
+	Mth::Vector 				m_ambient_base_color;
+
+	friend CLightGroup;
+};
+#endif
+
+class CLightGroup/* : public CBaseLightGroup*/
+{
+
+	friend class CVu1Context;
+public:
+	// Constants
+	enum
+	{
+		MAX_DIFFUSE_LIGHTS = 3,
+	};
+
+								CLightGroup();
+								~CLightGroup();
+	//virtual						~CLightGroup();
+
+	const Mth::Vector &			GetDirection(int idx) const;
+	//virtual const Mth::Vector &	GetDirection(int idx) const;
+
+	const Mth::Vector &			GetBaseAmbientColor() const;
+	const Mth::Vector &			GetBaseDiffuseColor(int idx) const;
+
+	const Mth::Vector &			GetAmbientColor() const;
+	const Mth::Vector &			GetDiffuseColor(int idx) const;
+	//virtual const Mth::Vector &	GetAmbientColor() const;
+	//virtual const Mth::Vector &	GetDiffuseColor(int idx) const;
+
+	void						SetDirection(int idx, const Mth::Vector & direction);
+	void						SetBaseAmbientColor(const Mth::Vector & color);
+	void						SetBaseDiffuseColor(int idx, const Mth::Vector & color);
+
+	// Enable lights
+	void						EnableAmbientLight(bool enable);
+	void						EnableDiffuseLight(int idx, bool enable);
+	bool						IsAmbientLightEnabled() const;
+	bool						IsDiffuseLightEnabled(int idx) const;
+
+	// Set brightness of lights
+	bool						SetAmbientBrightness(float brightness);
+	bool						SetDiffuseBrightness(int idx, float brightness);
+
+	// Default lights
+	static const Mth::Vector &	sGetDefaultDirection(int idx);
+	static const Mth::Vector &	sGetDefaultAmbientColor();
+	static const Mth::Vector &	sGetDefaultDiffuseColor(int idx);
+
+	static void					sSetDefaultDirection(int idx, const Mth::Vector & direction);
+	static void					sSetDefaultAmbientColor(const Mth::Vector & color);
+	static void					sSetDefaultDiffuseColor(int idx, const Mth::Vector & color);
+
+protected:
+	// Internal flags.
+	enum {
+		mUSE_AMBIENT				= 0x0001,					// Use ambient light
+		mUSE_DIFFUSE_0				= 0x0002,					// Use diffuse light 0
+		mUSE_DIFFUSE_1				= 0x0004,					// rest of diffuse must follow 0
+		mUSE_DIFFUSE_2				= 0x0008,					// rest of diffuse must follow 0
+		mMODULATING_AMBIENT			= 0x0010,					// setting ambient brightness
+		mMODULATING_DIFFUSE_0		= 0x0020,					// setting diffuse brightness 0
+		mMODULATING_DIFFUSE_1		= 0x0040,					// rest of diffuse must follow 0
+		mMODULATING_DIFFUSE_2		= 0x0040,					// rest of diffuse must follow 0
+	};
+
+	uint32					m_flags;
+
+	// Directional light vectors
+	Mth::Vector				m_light_vector[MAX_DIFFUSE_LIGHTS];
+
+	// Light base colors
+	Mth::Vector 			m_diffuse_base_color[MAX_DIFFUSE_LIGHTS];
+	Mth::Vector 			m_ambient_base_color;
+
+	// Light modulated colors (modified base colors)
+	Mth::Vector 			m_diffuse_mod_color[MAX_DIFFUSE_LIGHTS];
+	Mth::Vector 			m_ambient_mod_color;
+
+	// Default lights
+//	static Mth::Vector		s_default_directions[MAX_DIFFUSE_LIGHTS];
+//	static CBaseLightGroup	s_default_colors;
+	static CLightGroup		s_default_lights;
+
+}; // class CLightGroup
+
+// Inline functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::GetDirection(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (m_flags & (mUSE_DIFFUSE_0 << idx))
+	{
+		return m_light_vector[idx];
+	} else {
+		return s_default_lights.m_light_vector[idx];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::GetBaseAmbientColor() const
+{
+	if (m_flags & mUSE_AMBIENT)
+	{
+		return m_ambient_base_color;
+	} else {
+		return s_default_lights.m_ambient_base_color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::GetBaseDiffuseColor(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (m_flags & (mUSE_DIFFUSE_0 << idx))
+	{
+		return m_diffuse_base_color[idx];
+	} else {
+		return s_default_lights.m_diffuse_base_color[idx];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::GetAmbientColor() const
+{
+	if (m_flags & (mUSE_AMBIENT | mMODULATING_AMBIENT))
+	{
+		return m_ambient_mod_color;
+	} else {
+		return s_default_lights.m_ambient_base_color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::GetDiffuseColor(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (m_flags & ((mUSE_DIFFUSE_0 | mMODULATING_DIFFUSE_0) << idx))
+	{
+		return m_diffuse_mod_color[idx];
+	} else {
+		return s_default_lights.m_diffuse_base_color[idx];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::SetDirection(int idx, const Mth::Vector & direction)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+	//Dbg_Assert(m_flags & (mUSE_DIFFUSE_0 << idx));
+	m_flags |= (mUSE_DIFFUSE_0 << idx);  // turn on light if it isn't already on
+
+	m_light_vector[idx] = direction;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::SetBaseAmbientColor(const Mth::Vector & color)
+{
+	//Dbg_Assert(m_flags & mUSE_AMBIENT);
+// Mick:  turning on and off lights should be done ABOVE the p_line
+// Mick:	m_flags |= mUSE_AMBIENT;		// turn on light if it isn't already on
+
+	m_ambient_base_color = color;
+
+	if (!(m_flags & mMODULATING_AMBIENT))
+	{
+		m_ambient_mod_color = color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::SetBaseDiffuseColor(int idx, const Mth::Vector & color)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+	//Dbg_Assert(m_flags & (mUSE_DIFFUSE_0 << idx));
+// Mick:  turning on and off lights should be done ABOVE the p_line
+// Mick:	m_flags |= (mUSE_DIFFUSE_0 << idx);  // turn on light if it isn't already on
+
+	m_diffuse_base_color[idx] = color;
+
+	if (!(m_flags & (mMODULATING_DIFFUSE_0 << idx)))
+	{
+		m_diffuse_mod_color[idx] = color;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::EnableAmbientLight(bool enable)
+{
+	if (enable)
+	{
+		m_flags |= mUSE_AMBIENT;
+	} else {
+		m_flags &= ~mUSE_AMBIENT;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::EnableDiffuseLight(int idx, bool enable)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	if (enable)
+	{
+		m_flags |= (mUSE_DIFFUSE_0 << idx);
+	} else {
+		m_flags &= ~(mUSE_DIFFUSE_0 << idx);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CLightGroup::IsAmbientLightEnabled() const
+{
+	return (m_flags & mUSE_AMBIENT);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CLightGroup::IsDiffuseLightEnabled(int idx) const
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	return (m_flags & (mUSE_DIFFUSE_0 << idx));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CLightGroup::SetAmbientBrightness(float brightness)
+{
+	m_ambient_mod_color = GetBaseAmbientColor() * brightness;
+
+	m_flags |= mMODULATING_AMBIENT;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CLightGroup::SetDiffuseBrightness(int idx, float brightness)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	m_diffuse_mod_color[idx] = GetBaseDiffuseColor(idx) * brightness;
+
+	m_flags |= (mMODULATING_DIFFUSE_0 << idx);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::sGetDefaultDirection(int idx)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	return s_default_lights.m_light_vector[idx];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::sGetDefaultAmbientColor()
+{
+	return s_default_lights.m_ambient_base_color;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Vector &		CLightGroup::sGetDefaultDiffuseColor(int idx)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	return s_default_lights.m_diffuse_base_color[idx];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::sSetDefaultDirection(int idx, const Mth::Vector & direction)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	s_default_lights.m_light_vector[idx] = direction;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::sSetDefaultAmbientColor(const Mth::Vector & color)
+{
+	s_default_lights.m_ambient_base_color = color;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CLightGroup::sSetDefaultDiffuseColor(int idx, const Mth::Vector & color)
+{
+	Dbg_Assert(idx < MAX_DIFFUSE_LIGHTS);
+
+	s_default_lights.m_diffuse_base_color[idx] = color;
+}
+
+
+
+
+
+} // namespace NxPs2
+
+
+
+#endif // __LIGHT_H
+
diff --git a/Code/Gfx/NGPS/NX/line.cpp b/Code/Gfx/NGPS/NX/line.cpp
new file mode 100644
index 0000000..94d6780
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/line.cpp
@@ -0,0 +1,184 @@
+#include 
+#include "line.h"
+#include "render.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "sprite.h"
+#include "switches.h"
+#include "vu1code.h"
+
+
+namespace NxPs2
+{
+
+
+void BeginLines2D(uint32 rgba)
+{
+	// dma tag
+	dma::BeginTag(dma::cnt, 0);
+
+	vu1::Buffer = vu1::Loc;
+
+	// GS context
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::ALPHA_1, PackALPHA(0,1,0,1,0));
+	gs::Reg1(gs::RGBAQ,   (uint64)rgba);
+	gs::EndPrim(0);
+
+	// unpack giftag
+	vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+	gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(GSPrim));
+
+	// begin unpack for coordinates
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+}
+
+
+
+void DrawLine2D(float x0, float y0, float z0, float x1, float y1, float z1)
+{
+	vif::StoreV4_32(XOFFSET+(sint32)((x0 * SDraw2D::GetScreenScaleX() * 16.0f)),
+					YOFFSET+(sint32)((y0 * SDraw2D::GetScreenScaleY() * 16.0f)),0xFFFFFF,0);
+	vif::StoreV4_32(XOFFSET+(sint32)((x1 * SDraw2D::GetScreenScaleX() * 16.0f)),
+					YOFFSET+(sint32)((y1 * SDraw2D::GetScreenScaleY() * 16.0f)),0xFFFFFF,0);
+
+	if (((dma::pLoc - gif::pTag)>>4) >= 250)
+	{
+		vif::EndUNPACK();
+		gif::EndTagImmediate(1);
+		vif::MSCAL(VU1_ADDR(Parser));
+		vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+		gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(GSPrim));
+		vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+	}
+
+}
+
+
+void EndLines2D(void)
+{
+	vif::EndUNPACK();
+	gif::EndTagImmediate(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	dma::EndTag();
+}
+
+
+
+void BeginLines3D(uint32 rgba)
+{
+	
+	// dma tag  - this will encompass ALL the lines up to the next EndLines3D
+	dma::BeginTag(dma::cnt, 0);
+
+	vu1::Buffer = vu1::Loc;
+
+	// VU context, uploading the view transform data
+	vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));	  	// Begin packed register upload to VF10 
+	//vu1::StoreVec(*(Vec *)&render::ViewportScale);			// VF10
+	vu1::StoreVec(*(Vec *)&render::InverseIntViewportScale);			// VF10
+
+//	vu1::StoreVec(ViewportOffset);							// VF11
+
+//	float z_push = 2.40741243e-34;							// was e-35
+//	vif::StoreV4_32F(render::ViewportOffset[0],
+//					 render::ViewportOffset[1],
+//					 render::ViewportOffset[2]+z_push,
+//					 render::ViewportOffset[3]);			// VF11
+	
+	Mth::Vector temp_inv_viewport_offset;
+	temp_inv_viewport_offset[0] = -render::InverseIntViewportScale[0] * render::IntViewportOffset[0] * render::InverseIntViewportScale[3];
+	temp_inv_viewport_offset[1] = -render::InverseIntViewportScale[1] * render::IntViewportOffset[1] * render::InverseIntViewportScale[3];
+	temp_inv_viewport_offset[2] = -render::InverseIntViewportScale[2] * (render::IntViewportOffset[2]+1.0e-34f) * render::InverseIntViewportScale[3];
+	temp_inv_viewport_offset[3] = 0.0f;
+	vu1::StoreVec(*(Vec *)&temp_inv_viewport_offset);
+
+	vu1::StoreMat(*(Mat *)&render::AdjustedWorldToIntViewport);	// VF12-15
+	vu1::EndPrim(0);										// End upload
+
+	// GS context, setting color
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::ALPHA_1, PackALPHA(0,1,0,1,0));
+	gs::Reg1(gs::RGBAQ,   (uint64)rgba);
+	gs::EndPrim(0);
+
+	// all lines will be rendered with simple culling
+	vif::ITOP(CULL);
+
+	// unpack giftag
+	vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+	gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(Line));
+
+	// offset mode
+	vif::STMOD(1);
+
+	// begin unpack for coordinates
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+}
+
+
+void	ChangeLineColor(uint32 rgba)
+{
+
+// End the previous batch of lines
+		EndLines3D();
+		
+// Like starting a new batch, but witout the VU context		
+		dma::BeginTag(dma::cnt, 0);
+
+		// GS context
+		// Sets up the GS registers for color and alpha
+		gs::BeginPrim(ABS,0,0);
+		gs::Reg1(gs::ALPHA_1, PackALPHA(0,1,0,1,0));
+		gs::Reg1(gs::RGBAQ,   (uint64)rgba);
+		gs::EndPrim(0);
+		
+		vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+		gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(Line));
+		vif::STMOD(1);
+		vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+	
+}
+
+
+void DrawLine3D(float x0, float y0, float z0, float x1, float y1, float z1)
+{
+	vif::StoreV4_32((sint32)(x0*SUB_INCH_PRECISION), (sint32)(y0*SUB_INCH_PRECISION), (sint32)(z0*SUB_INCH_PRECISION), 0);
+	vif::StoreV4_32((sint32)(x1*SUB_INCH_PRECISION), (sint32)(y1*SUB_INCH_PRECISION), (sint32)(z1*SUB_INCH_PRECISION), 0);
+
+	if (((dma::pLoc - gif::pTag)>>4) >= 250)
+	{
+		vif::EndUNPACK();
+		gif::EndTagImmediate(1);
+		vif::STMOD(0);
+		vif::MSCAL(VU1_ADDR(Parser));
+		vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+		gif::BeginTagImmediate(gs::XYZ2, 1, PACKED, LINE|ABE, 1, VU1_ADDR(Line));
+		vif::STMOD(1);
+		vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+	}
+
+}
+
+
+void EndLines3D(void)
+{
+	vif::EndUNPACK();
+
+	if (vif::UnpackSize)
+	{
+		gif::EndTagImmediate(1);
+		vif::STMOD(0);
+		vif::MSCAL(VU1_ADDR(Parser));
+	}
+	else
+		dma::pLoc -= 24;
+
+	dma::EndTag();
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/line.h b/Code/Gfx/NGPS/NX/line.h
new file mode 100644
index 0000000..f95eb43
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/line.h
@@ -0,0 +1,21 @@
+#ifndef __LINE_H
+#define __LINE_H
+
+
+namespace NxPs2
+{
+
+
+void BeginLines2D(uint32 rgba);
+void DrawLine2D(float x0, float y0, float z0, float x1, float y1, float z1);
+void EndLines2D(void);
+void BeginLines3D(uint32 rgba);
+void ChangeLineColor(uint32 rgba);
+void DrawLine3D(float x0, float y0, float z0, float x1, float y1, float z1);
+void EndLines3D(void);
+
+} // namespace NxPs2
+
+
+#endif // __LINE_H
+
diff --git a/Code/Gfx/NGPS/NX/loadscreen.cpp b/Code/Gfx/NGPS/NX/loadscreen.cpp
new file mode 100644
index 0000000..1a66061
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/loadscreen.cpp
@@ -0,0 +1,219 @@
+#include 
+#include 
+#include 
+#include 
+#include "dma.h"
+#include "vif.h"
+#include "gif.h"
+#include "gs.h"
+#include "dmacalls.h"
+#include "group.h"
+#include "nx_init.h"
+#include "render.h"
+#include "interrupts.h"
+#include "switches.h"
+#include "sprite.h"
+
+
+namespace NxPs2
+{
+
+
+// Callback handler for loading bar
+static int s_loading_bar_handler_id = -1;
+
+/////////////////////////////////////////////////////////////////////////////
+// Loading bar
+
+static uint128 p_VifData[64];
+
+int			gLoadBarTotalFrames;		// Number of frames it takes for loading bar to go to 100%
+int			gLoadBarNumFrames;			// Number of frames so far
+float		gLoadBarX = 150.0f;			// Bar position
+float		gLoadBarY = 400.0f;
+float		gLoadBarWidth = 340.0f;		// Bar size
+float		gLoadBarHeight = 20.0f;
+int			gLoadBarStartColor[4] = {  255,    0,   0,  128 };
+int			gLoadBarDeltaColor[4] = { -255,  255,   0,  128 };
+float		gLoadBarBorderWidth = 5.0f;	// Border width
+float		gLoadBarBorderHeight = 5.0f;	// Border height
+int			gLoadBarBorderColor[4] = {  40, 40, 40, 128 };
+
+
+static int s_loading_bar_handler(int ca)
+{
+	// If writing to a variable, make sure it is volatile (or game will crash)
+	
+	// inc frame count    
+	gLoadBarNumFrames++;
+
+	//-----------------------------------------
+	//		DRAW BAR
+	//-----------------------------------------
+	sceGsSyncPath(0, 0);
+	if (sGroup::pRenderGroup)	// Can't handle this condition
+	{
+		ExitHandler();
+		return 0;
+	}
+
+	// Save floats
+    unsigned int floatBuffer[32];
+    saveFloatRegs(floatBuffer);
+
+	// Save all these because we could already be in the middle of building packets
+	uint8*	old_dma_loc = dma::pLoc;		  
+	uint8*	old_dma_tag = dma::pTag;
+	uint8*	old_gif_tag = gif::pTag;
+	uint8*	old_vif_code = vif::pVifCode;
+
+	dma::pLoc = (uint8 *) p_VifData;
+
+	// begin Path3 subroutine
+	dma::BeginTag(dma::cnt, 0);
+
+	// send everything PATH3
+	//vif::BeginDIRECT();
+	vif::NOP();
+	vif::NOP();
+
+	// GS prim sets up all the registers
+	gif::BeginTag2(gs::A_D, 1, PACKED, 0, 0);
+	gs::Reg2(gs::ALPHA_1,		PackALPHA(0,1,0,1,128));
+	gs::Reg2(gs::CLAMP_1,		PackCLAMP(REPEAT,REPEAT,0,0,0,0));
+	gs::Reg2(gs::COLCLAMP,		PackCOLCLAMP(1));
+	//gs::Reg2(gs::DTHE,			PackDTHE(0));
+	gs::Reg2(gs::DIMX,			PackDIMX(-4,0,-3,1,2,-2,3,-1,-3,1,-4,0,3,-1,2,-2));
+	gs::Reg2(gs::DTHE,			PackDTHE(1));
+	//gs::Reg2(gs::FBA_1,			PackFBA(0));
+	gs::Reg2(gs::FRAME_1,		PackFRAME(DISPLAY_START,HRES/64,PSMCT16S,0xFF000000));	// draw directly to display buffer
+	gs::Reg2(gs::PRMODECONT,	PackPRMODECONT(1));
+	gs::Reg2(gs::SCANMSK,		PackSCANMSK(0));
+	gs::Reg2(gs::SCISSOR_1,	 	PackSCISSOR(0,HRES - 1,0,VRES - 1));
+	gs::Reg2(gs::TEST_1,		PackTEST(0,0,0,0,0,0,1,ZALWAYS));
+	gs::Reg2(gs::XYOFFSET_1,	PackXYOFFSET(0x0, 0x0));
+	gs::Reg2(gs::ZBUF_1,		PackZBUF(ZBUFFER_START,PSMZ24,1));
+	gif::EndTag2(1);
+
+	// Find scaled values (mainly for PAL)
+	NxPs2::SDraw2D::CalcScreenScale();		// May not have been calculated yet
+	float scaledBarX = gLoadBarX * NxPs2::SDraw2D::GetScreenScaleX();
+	float scaledBarY = gLoadBarY * NxPs2::SDraw2D::GetScreenScaleY();
+	float scaledBarWidth = gLoadBarWidth * NxPs2::SDraw2D::GetScreenScaleX();
+	float scaledBarHeight = gLoadBarHeight * NxPs2::SDraw2D::GetScreenScaleY();
+	float scaledBarBorderWidth = gLoadBarBorderWidth * NxPs2::SDraw2D::GetScreenScaleX();
+	float scaledBarBorderHeight = gLoadBarBorderHeight * NxPs2::SDraw2D::GetScreenScaleY();
+
+	int cur_width = (int) ((scaledBarWidth - 1.0f) * 16.0f);
+	if (gLoadBarNumFrames < gLoadBarTotalFrames)
+	{
+		cur_width = (cur_width * gLoadBarNumFrames) / gLoadBarTotalFrames;
+	}
+
+	int x1 = (int) (scaledBarX * 16.0f);
+	int y1 = (int) (scaledBarY * 16.0f);
+	int x2 = x1 + cur_width;
+	int y2 = y1 + (int) ((scaledBarHeight - 1.0f) * 16.0f);
+
+	int end_color[4];
+	if (gLoadBarNumFrames < gLoadBarTotalFrames)
+	{
+		end_color[0] = gLoadBarStartColor[0] + ((gLoadBarDeltaColor[0] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+		end_color[1] = gLoadBarStartColor[1] + ((gLoadBarDeltaColor[1] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+		end_color[2] = gLoadBarStartColor[2] + ((gLoadBarDeltaColor[2] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+		end_color[3] = gLoadBarStartColor[3] + ((gLoadBarDeltaColor[3] * gLoadBarNumFrames) / gLoadBarTotalFrames);
+	} else {
+		end_color[0] = gLoadBarStartColor[0] + gLoadBarDeltaColor[0];
+		end_color[1] = gLoadBarStartColor[1] + gLoadBarDeltaColor[1];
+		end_color[2] = gLoadBarStartColor[2] + gLoadBarDeltaColor[2];
+		end_color[3] = gLoadBarStartColor[3] + gLoadBarDeltaColor[3];
+	}
+
+	int border_x1 = x1 - (int) (scaledBarBorderWidth * 16.0f);
+	int border_y1 = y1 - (int) (scaledBarBorderHeight * 16.0f);
+	int border_x2 = x1 + (int) (((scaledBarWidth - 1) * 16.0f) + (scaledBarBorderWidth * 16.0f));
+	int border_y2 = y2 + (int) (scaledBarBorderHeight * 16.0f);
+
+	gif::BeginTag2(gs::XYZ2<<4|gs::RGBAQ, 2, PACKED, TRISTRIP|IIP|FST|AA1/*|ABE*/, 1);
+
+	// Border
+	vif::StoreV4_32(gLoadBarBorderColor[0], gLoadBarBorderColor[1],
+					gLoadBarBorderColor[2], gLoadBarBorderColor[3]);	// RGBAQ
+	vif::StoreV4_32(border_x1, border_y1, 0, 0x8000);					// XYZ2
+	vif::StoreV4_32(gLoadBarBorderColor[0], gLoadBarBorderColor[1],
+					gLoadBarBorderColor[2], gLoadBarBorderColor[3]);	// RGBAQ
+	vif::StoreV4_32(border_x1, border_y2, 0, 0x8000);					// XYZ2
+	vif::StoreV4_32(gLoadBarBorderColor[0], gLoadBarBorderColor[1],
+					gLoadBarBorderColor[2], gLoadBarBorderColor[3]);	// RGBAQ
+	vif::StoreV4_32(border_x2, border_y1, 0, 0);						// XYZ2
+	vif::StoreV4_32(gLoadBarBorderColor[0], gLoadBarBorderColor[1],
+					gLoadBarBorderColor[2], gLoadBarBorderColor[3]);	// RGBAQ
+	vif::StoreV4_32(border_x2, border_y2, 0, 0);						// XYZ2
+
+	// Bar
+	vif::StoreV4_32(gLoadBarStartColor[0], gLoadBarStartColor[1],
+					gLoadBarStartColor[2], gLoadBarStartColor[3]);			// RGBAQ
+	vif::StoreV4_32( x1,  y1, 0, 0x8000);									// XYZ2
+	vif::StoreV4_32(gLoadBarStartColor[0], gLoadBarStartColor[1],
+					gLoadBarStartColor[2], gLoadBarStartColor[3]);			// RGBAQ
+	vif::StoreV4_32( x1,  y2, 0, 0x8000);									// XYZ2
+	vif::StoreV4_32(end_color[0], end_color[1], end_color[2], end_color[3]);// RGBAQ
+	vif::StoreV4_32( x2,  y1, 0, 0);										// XYZ2
+	vif::StoreV4_32(end_color[0], end_color[1], end_color[2], end_color[3]);// RGBAQ
+	vif::StoreV4_32( x2,  y2, 0, 0);										// XYZ2
+
+	gif::EndTag2(1);
+
+	//vif::EndDIRECT();
+
+	dma::EndTag();
+
+	// done
+	iFlushCache(WRITEBACK_DCACHE);
+
+	// kick dma to upload textures from this group
+	*D2_QWC = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+	*D2_TADR = (uint)p_VifData;			// address of 1st tag
+	*D2_CHCR = 0x145;					// start transfer, tte=1, chain mode, from memory
+	sceGsSyncPath(0, 0);
+
+	// Restore used variables
+	dma::pLoc = old_dma_loc;		  
+	dma::pTag = old_dma_tag;
+	gif::pTag = old_gif_tag;
+	vif::pVifCode = old_vif_code;
+
+    // restore floats
+    restoreFloatRegs(floatBuffer);
+
+	ExitHandler();			// Mick: Sony requires interrupts to end with an ExitHandler() call
+							// this enables interrupts	
+	return 0;
+}
+
+void StartLoadingBar(int frames)
+{
+	//Dbg_Assert(s_loading_bar_handler_id < 0);
+
+	if (s_loading_bar_handler_id < 0)
+	{
+		gLoadBarTotalFrames = frames;
+		gLoadBarNumFrames = 0;
+
+		s_loading_bar_handler_id = AddIntcHandler(INTC_VBLANK_S, s_loading_bar_handler, -1);
+		Dbg_MsgAssert (( s_loading_bar_handler_id >= 0 ),( "Failed to add loading bar handler" )); 
+	}
+}
+
+void RemoveLoadingBar()
+{
+	if (s_loading_bar_handler_id >= 0)
+	{
+		RemoveIntcHandler(INTC_VBLANK_S, s_loading_bar_handler_id);
+		s_loading_bar_handler_id = -1;
+	}
+}
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/loadscreen.h b/Code/Gfx/NGPS/NX/loadscreen.h
new file mode 100644
index 0000000..ff1e0e2
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/loadscreen.h
@@ -0,0 +1,30 @@
+#ifndef __NXPS2_LOADSCREEN_H
+#define __NXPS2_LOADSCREEN_H
+
+
+namespace NxPs2
+{
+
+// Enable and disable loading bar interrupt
+void StartLoadingBar(int frames);
+void RemoveLoadingBar();
+
+
+// Loading bar data
+extern int		gLoadBarTotalFrames;	// Number of frames it takes for loading bar to go to 100%
+extern int		gLoadBarNumFrames;		// Number of frames so far
+extern float	gLoadBarX;				// Bar position
+extern float	gLoadBarY;
+extern float	gLoadBarWidth;			// Bar size
+extern float	gLoadBarHeight;
+extern int		gLoadBarStartColor[4];	// 0 = red; 1 = green; 2 = blue; 3 = alpha
+extern int		gLoadBarDeltaColor[4];	// 0 = delta red; 1 = delta green; 2 = delta blue; 3 = delta alpha
+extern float	gLoadBarBorderWidth;	// Border width
+extern float	gLoadBarBorderHeight;	// Border height
+extern int		gLoadBarBorderColor[4];	// 0 = red; 1 = green; 2 = blue; 3 = alpha
+
+
+
+} // namespace NxPs2
+
+#endif // __NXPS2_LOADSCREEN_H
diff --git a/Code/Gfx/NGPS/NX/maintest.cpp b/Code/Gfx/NGPS/NX/maintest.cpp
new file mode 100644
index 0000000..6ac30cb
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/maintest.cpp
@@ -0,0 +1,253 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "render.h"
+#include "line.h"
+#include "nx_init.h"
+#include "chars.h"
+#include "group.h"
+#include "sprite.h"
+#include "switches.h"
+#include	"gfx/nx.h"
+#include	"gfx/NxParticleMgr.h"
+#include	"gfx/NxTextured3dPoly.h"
+#include	"gfx/ngps/p_NxLightMan.h"
+
+
+namespace Sys
+{
+	extern void Render_Profiler();		
+}
+
+
+namespace Gfx
+{
+	void DebugGfx_Draw( void );
+}
+
+namespace Tmr
+{
+	void VSync();	
+}
+
+namespace NxPs2
+{
+
+/*
+void LineTest(void);
+void CharTest(void);
+*/
+
+SFont *pMyFont1, *pMyFont2;
+
+
+
+int maintest(int argc, char *argv[])
+{
+	return 0;
+}
+
+
+
+
+void test_init()
+{
+	InitialiseEngine();
+}
+
+// I'm forward declaring this, so the compiler does not try to inline it
+volatile sGroup *get_render_group();
+void	WaitForRendering();
+void patch_texture_dma();
+
+
+void	test_render_start_frame()
+{
+
+//------------------------------------------------------------
+
+#if 0
+// MICK: Note, this is done in the main loop now, for profiling reasons
+	// wait for rendering completion
+	NxPs2::WaitForRendering();
+	// wait for vsync
+	Tmr::VSync();
+#endif
+  
+	// now the dma list has finished, we can swap dma buffers
+	render::Field ^= 1;
+
+	// kick off dma from previous frame (if there was one)
+	if (render::Frame > 0)
+		SendDisplayList();
+
+}
+
+
+
+// wait for pRenderGroup to be null
+// note curius use of a seperate function to read pRenderGroup
+// due to compler getting confused if we just read it directly
+// even though it is volatile, it does not re-read it			   
+void	WaitForRendering()
+{
+	while (get_render_group())
+		;
+
+	// Update the 2D DMA lists
+	Update2DDMA();
+	
+}
+
+void StuffAfterGSFinished()
+{
+
+	Nx::CPs2LightManager::sUpdateEngine();
+
+	// particle systems effectively have their transform double-buffered for dma purposes
+	// so now is the time to do the buffer flip by updating the transforms
+	Nx::CEngine::sGetParticleManager()->UpdateParticles();
+
+	// patch up texture DMA
+	patch_texture_dma();	
+
+
+}
+
+					
+volatile sGroup *get_render_group()
+{
+	return sGroup::pRenderGroup;
+}
+
+
+
+void	test_render(Mth::Matrix* camera_orient, Mth::Vector* camera_pos, float view_angle, float screen_aspect)
+{
+	//RenderPrologue();	 	// should this be in test_render_start_frame? (Garrett: Answer is no, it won't work)
+
+	// render everything
+	RenderWorld(camera_orient, camera_pos, view_angle, screen_aspect);
+	//LineTest();
+	//CharTest();
+}
+
+void	test_render_end_frame()
+{
+	SetTextWindow(0,HRES-1,0,VRES-1);						// a full-screen clipping window
+	Gfx::DebugGfx_Draw( );
+
+	// Make sure 2D VRAM is up-to-date
+// Mick: Moved to CEngine::s_plat_render_world, which is earlier in the frame
+// so it happens before we draw the texture splats.
+//	Reallocate2DVRAM();
+
+	SDraw2D::DrawAll();
+	#ifdef		__USE_PROFILER__
+	Sys::Render_Profiler();		
+	#endif
+	RenderEpilogue();
+	
+	// set frame for next render
+	render::Frame++;
+}
+
+
+
+void LineTest(void)
+{
+/*	
+	int xo,yo,zo;
+
+	// do some 3D lines with r=0xFF, g=0x00, b=0x00, a=0x80
+	BeginLines3D(0x800000FF);
+
+	for (xo=-130; xo<=110; xo+=80)
+		for (yo=-90; yo<=70; yo+=80)
+			for (zo=-130; zo<=110; zo+=80)
+			{
+				DrawLine3D(-10+xo, 10+yo, 10+zo, 10+xo, 10+yo, 10+zo);
+				DrawLine3D(-10+xo,-10+yo, 10+zo, 10+xo,-10+yo, 10+zo);
+				DrawLine3D(-10+xo, 10+yo,-10+zo, 10+xo, 10+yo,-10+zo);
+				DrawLine3D(-10+xo,-10+yo,-10+zo, 10+xo,-10+yo,-10+zo);
+				
+				DrawLine3D(-10+xo, 10+yo, 10+zo,-10+xo,-10+yo, 10+zo);
+				DrawLine3D( 10+xo, 10+yo, 10+zo, 10+xo,-10+yo, 10+zo);
+				DrawLine3D(-10+xo, 10+yo,-10+zo,-10+xo,-10+yo,-10+zo);
+				DrawLine3D( 10+xo, 10+yo,-10+zo, 10+xo,-10+yo,-10+zo);
+				
+				DrawLine3D(-10+xo, 10+yo, 10+zo,-10+xo, 10+yo,-10+zo);
+				DrawLine3D( 10+xo, 10+yo, 10+zo, 10+xo, 10+yo,-10+zo);
+				DrawLine3D(-10+xo,-10+yo, 10+zo,-10+xo,-10+yo,-10+zo);
+				DrawLine3D( 10+xo,-10+yo, 10+zo, 10+xo,-10+yo,-10+zo);
+			}
+
+	EndLines3D();
+
+	// do some 2D lines with r=0xFF, g=0xFF, b=0xFF, a=0x80
+	BeginLines2D(0x80FFFFFF);
+
+	// "LINE"
+	DrawLine2D(100,100,0, 100,150,0);
+	DrawLine2D(100,150,0, 150,150,0);
+	DrawLine2D(160,100,0, 160,150,0);
+	DrawLine2D(170,100,0, 170,150,0);
+	DrawLine2D(170,100,0, 220,150,0);
+	DrawLine2D(220,100,0, 220,150,0);
+	DrawLine2D(230,100,0, 230,150,0);
+	DrawLine2D(230,100,0, 280,100,0);
+	DrawLine2D(230,125,0, 260,125,0);
+	DrawLine2D(230,150,0, 280,150,0);
+
+	// "TEST"
+	DrawLine2D(320,100,0, 370,100,0);
+	DrawLine2D(345,100,0, 345,150,0);
+	DrawLine2D(380,100,0, 380,150,0);
+	DrawLine2D(380,100,0, 430,100,0);
+	DrawLine2D(380,125,0, 410,125,0);
+	DrawLine2D(380,150,0, 430,150,0);
+	DrawLine2D(440,100,0, 490,100,0);
+	DrawLine2D(440,125,0, 490,125,0);
+	DrawLine2D(440,150,0, 490,150,0);
+	DrawLine2D(440,100,0, 440,125,0);
+	DrawLine2D(490,125,0, 490,150,0);
+	DrawLine2D(500,100,0, 550,100,0);
+	DrawLine2D(525,100,0, 525,150,0);
+
+	EndLines2D();
+*/
+}
+
+
+
+
+void CharTest(void)
+{
+#if 0
+	float Width,Height;
+
+	SetFont("small");
+	QueryString("Hello World", &Width, &Height);	// width and height in pixels
+
+	SetTextWindow(0,639,0,447);						// a full-screen clipping window
+
+	BeginText(0x403070A0, 1.5);						// r=0xA0, g=0x70, b=0x30, a=0x40, scale=1.5
+	DrawString("Score",  30, 30);
+	DrawString("000000", 100, 30);
+	EndText();
+
+	BeginText(0x40A06080, 1.5);
+	DrawString("Skate4", 520, 400);
+	EndText();
+#endif
+}
+
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/material.cpp b/Code/Gfx/NGPS/NX/material.cpp
new file mode 100644
index 0000000..a2b2f78
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/material.cpp
@@ -0,0 +1,217 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "material.h"
+#include "dma.h"
+#include "vif.h"
+#include "gs.h"
+#include "scene.h"
+#include "switches.h"
+
+
+
+namespace NxPs2
+{
+
+
+//----------------------------------------------------------------------------------------
+//		L O A D   M A T E R I A L S
+//----------------------------------------------------------------------------------------
+
+void * LoadMaterials(void *pFile, sScene *pScene, uint32 texDictOffset)
+{
+	sMaterial *pMat;
+	sTexture *pTex;
+	uint32 TextureChecksum, GroupChecksum;
+	uint32 MXL, MMAG, MMIN, K, L, NumSeqs, seq, NumKeys;
+	int i;
+
+	// get number of materials
+	MEM_Read(&pScene->NumMaterials, sizeof(uint32), 1, pFile);
+
+	// allocate storage for materials
+	pScene->pMaterials = (sMaterial *)Mem::Malloc(pScene->NumMaterials*sizeof(sMaterial));
+	Dbg_MsgAssert(pScene->pMaterials, ("couldn't allocate memory for materials"));
+
+	// loop over materials
+	for (i=0,pMat=pScene->pMaterials; iNumMaterials; i++,pMat++)
+	{
+		// get material checksum
+		MEM_Read(&pMat->Checksum, sizeof(uint32), 1, pFile);
+
+		if (sMaterial::Version >= 5)
+		{
+			// get material flags (moved up here in version 5)
+			MEM_Read(&pMat->Flags, 1, sizeof(uint32), pFile);
+		}
+
+		// get Aref
+		if (sMaterial::Version >= 2)
+		{
+			if (sMaterial::Version >= 3)
+			{
+				MEM_Read(&pMat->Aref, 1, 4, pFile);
+			}
+			else
+			{
+				MEM_Read(&pMat->Aref, 1, 1, pFile);
+			}
+
+		}
+		else
+		{
+			pMat->Aref = 1;
+		}
+
+		if (sMaterial::Version >= 5)
+		{
+			if (pMat->Flags & MATFLAG_ANIMATED_TEX)
+			{
+				Dbg_Assert( 0 );
+				/*
+				NxAnimatedTexture* anim_tex;
+	
+				anim_tex = &material->m_Passes[0].m_AnimatedTexture;
+				pOutFile->Write((const char*) &anim_tex->m_NumKeyframes, sizeof( int ), false);
+				pOutFile->Write((const char*) &anim_tex->m_Period, sizeof( int ), false);
+				pOutFile->Write((const char*) &anim_tex->m_Iterations, sizeof( int ), false);
+				pOutFile->Write((const char*) &anim_tex->m_Phase, sizeof( int ), false);
+				for( j = 0; j < anim_tex->m_NumKeyframes; j++ )
+				{
+					NxAnimatedTextureKeyframe* frame;
+	
+					frame = &anim_tex->m_Keyframes[j];
+					pOutFile->Write((const char*) &frame->m_Time, sizeof( unsigned int ), false);
+	
+					tex_checksum = material->m_Passes[0].GetTextureChecksum( j, Utils::vPLATFORM_PS2 );
+					pOutFile->Write((const char*) &tex_checksum, sizeof( unsigned long ), false);
+				}*/
+				
+			}
+			else
+			{
+				// get texture checksum
+				MEM_Read(&TextureChecksum, 1, sizeof(uint32), pFile);
+			}
+		}
+		else
+		{
+			// get texture checksum
+			MEM_Read(&TextureChecksum, 1, sizeof(uint32), pFile);
+		}
+		
+
+		if (sMaterial::Version >= 3)
+		{
+			// get group checksum
+			MEM_Read(&GroupChecksum, 1, sizeof(uint32), pFile);
+
+			GroupChecksum += texDictOffset;
+		}
+
+		if (sMaterial::Version < 5)
+		{
+			// get material flags (moved above in version 5 because the parser needs the flags
+			// to correctly parse subsequent data
+			MEM_Read(&pMat->Flags, 1, sizeof(uint32), pFile);
+		}
+
+		// get ALPHA register value
+		MEM_Read(&pMat->RegALPHA, 1, sizeof(uint64), pFile);
+
+		// get CLAMP register value
+		if (sMaterial::Version >= 2)
+		{
+			uint32 ClampU,ClampV;
+			MEM_Read(&ClampU, 1, sizeof(uint32), pFile);
+			MEM_Read(&ClampV, 1, sizeof(uint32), pFile);
+			pMat->RegCLAMP = PackCLAMP(ClampU,ClampV,0,0,0,0);
+		}
+		else
+			pMat->RegCLAMP = PackCLAMP(REPEAT,REPEAT,0,0,0,0);
+
+		// step past material colours, currently unsupported
+		MEM_Seek(pFile, 36, SEEK_CUR);
+
+		// step past uv wibble data, currently unsupported
+		if (pMat->Flags & MATFLAG_UV_WIBBLE)
+		{
+			MEM_Seek(pFile, 40, SEEK_CUR);
+		}
+
+		// step past vc wibble data, currently unsupported
+		if (pMat->Flags & MATFLAG_VC_WIBBLE)
+		{
+			MEM_Read(&NumSeqs, sizeof(uint32), 1, pFile);
+			for (seq=0; seq= 3)
+			{
+				pTex = ResolveTextureChecksum(TextureChecksum, GroupChecksum);
+				Dbg_MsgAssert(pTex, ("error: couldn't find texture checksum %08X with group checksum %08X\n",
+																			TextureChecksum, GroupChecksum));
+			}
+			else
+			{
+				pTex = ResolveTextureChecksum(TextureChecksum);
+				Dbg_MsgAssert(pTex, ("error: couldn't find texture checksum %08X\n", TextureChecksum));
+			}
+
+			// set texture pointer
+			pMat->pTex = pTex;
+
+			// get mipmap info
+			MXL  = pTex->MXL;
+			MEM_Read(&MMAG, sizeof(uint32), 1, pFile);
+			MEM_Read(&MMIN, sizeof(uint32), 1, pFile);
+			MEM_Read(&K, sizeof(uint32), 1, pFile);
+			K = (int) ((*(float *)&K) * 16.0f) & 0xFFF;
+			if (sMaterial::Version > 1)
+				pMat->RegTEX1 = PackTEX1(0,MXL,MMAG,MMIN,0,0,K);
+			else
+			{
+				MEM_Read(&L, sizeof(uint32), 1, pFile);
+				pMat->RegTEX1 = PackTEX1(0,MXL,MMAG,MMIN,0,L,K);
+			}
+		}
+		// otherwise just step past mipmap info
+		else
+		{
+			pMat->pTex = 0;
+			MEM_Seek(pFile, 12+4*(sMaterial::Version==1), SEEK_CUR);
+		}
+
+		// get reflection map scale values
+		if (sMaterial::Version >= 4)
+		{
+			float temp;
+			MEM_Read(&temp, sizeof(float), 1, pFile);
+			pMat->RefMapScaleU = (sint16)(int)(temp * 256.0f);
+			MEM_Read(&temp, sizeof(float), 1, pFile);
+			pMat->RefMapScaleV = (sint16)(int)(temp * 256.0f);
+		}
+
+	}
+	return pFile;
+}
+
+
+uint32 sMaterial::Version;
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/material.h b/Code/Gfx/NGPS/NX/material.h
new file mode 100644
index 0000000..6677a5e
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/material.h
@@ -0,0 +1,45 @@
+#ifndef __MATERIAL_H
+#define __MATERIAL_H
+
+
+namespace NxPs2
+{
+
+// Material Flags
+#define MATFLAG_UV_WIBBLE   	(1<<0)
+#define MATFLAG_VC_WIBBLE   	(1<<1)
+#define MATFLAG_TEXTURED    	(1<<2)
+#define MATFLAG_ENVIRONMENT 	(1<<3)
+#define MATFLAG_DECAL       	(1<<4)
+#define MATFLAG_SMOOTH      	(1<<5)
+#define MATFLAG_TRANSPARENT		(1<<6)
+#define MATFLAG_ONE_SIDED 		(1<<7)
+#define MATFLAG_INVISIBLE 		(1<<8)
+#define MATFLAG_TWO_SIDED 		(1<<9)
+#define MATFLAG_SPECULAR 		(1<<10)
+#define MATFLAG_ANIMATED_TEX	(1<<11)
+#define MATFLAG_FORCE_ALPHA		(1<<13)
+
+
+struct sMaterial
+{
+	uint32	Flags;
+	uint32	Checksum;
+	struct	sTexture *pTex;
+	uint64	RegALPHA, RegTEX1, RegCLAMP;
+	uint8	Aref;
+	float*	pUVWibbleInfo;
+	uint32*	pVCWibbleInfo;
+	sint16	RefMapScaleU, RefMapScaleV;
+
+	static uint32 Version;
+};
+
+
+void * LoadMaterials(void *pFile, struct sScene *pScene, uint32 texDictOffset);
+
+
+} // namespace NxPs2
+
+#endif // __MATERIAL_H
+
diff --git a/Code/Gfx/NGPS/NX/mesh.cpp b/Code/Gfx/NGPS/NX/mesh.cpp
new file mode 100644
index 0000000..1b57013
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/mesh.cpp
@@ -0,0 +1,1219 @@
+#include 
+#include 
+#include 
+#include 
+#include "group.h"
+#include "scene.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "texture.h"
+#include "material.h"
+#include "mesh.h"
+#include "vu1code.h"
+#include "dmacalls.h"
+#include "render.h"
+#include 
+#include 
+#include 
+#include 
+										  
+namespace NxPs2
+{
+
+
+#ifdef	__NOPT_ASSERT__
+extern uint32		gPassMask1; 		// 1<<6 | 1<<7  (0x40, 0x80)
+extern uint32		gPassMask0; 		// 1<<6 | 1<<7  (0x40, 0x80)
+#endif 
+
+
+#if 0
+
+//----------------------------------------------------------------------------------------
+//		Mesh Group Functions
+//----------------------------------------------------------------------------------------
+
+sMesh *sGroup::GetMeshArray()
+{
+	return &(Meshes[FirstMeshIndex]);
+}
+
+
+sGroup* NewMeshGroup(uint32 group_ID, uint32 num_meshes)
+{
+	uint32 i;
+	int Group = -1;
+
+	// resolve ID
+	for (i=0; ipWeights;
+	s_pWeightIndices = pParams->pWeightIndices;
+	s_pBoneScales = pParams->pBoneScales;
+	s_pBonePositions = pParams->pBonePositions;
+	s_currentVertIndex = 0;
+}
+
+void DisableMeshScaling()
+{
+	s_meshScalingEnabled = false;
+	s_pWeights = NULL;
+	s_pWeightIndices = NULL;
+	s_pBoneScales = NULL;
+	s_pBonePositions = NULL;
+	s_currentVertIndex = 0;
+}
+
+//----------------------------------------------------------------------------------------
+//		L O A D   M E S H E S
+//----------------------------------------------------------------------------------------
+
+void * LoadMeshes(void *pFile, sScene *pScene, bool IsSkin, bool IsInstanceable, uint32 texDictOffset)
+{
+	int i;
+
+	// initialise sScene bounding sphere to avoid rewriting it multiple times
+	pScene->Sphere[3] = -1.0f;
+
+	int skinnedVertexCount=0;
+
+	// iterate over mesh groups
+	for (i=0; iNumGroups; i++)
+	{
+		pFile = LoadMeshGroup(pFile, pScene, IsSkin, IsInstanceable, texDictOffset, skinnedVertexCount);
+	}
+	// Check hierarchy data
+	int num_hier = -1;
+	MEM_Read(&num_hier, sizeof(int), 1, pFile);
+	Dbg_MsgAssert(num_hier == 0, ("num_hier = %d", num_hier));
+	
+	// clear it out for future use
+	DisableMeshScaling();
+	
+	return pFile;
+}
+
+
+
+
+void *LoadMeshGroup(void *pFile, sScene *pScene, bool IsSkin, bool IsInstanceable, uint32 texDictOffset, int& skinnedVertexCount)
+{
+	sMesh *pMesh;
+	sMaterial *pMat;
+	sGroup *pGroup;
+	uint32 MaterialChecksum, GroupChecksum;
+	int NumMeshesThisGroup,i,j;
+	Mth::Vector sphere;
+
+	// get group checksum and number of meshes
+	MEM_Read(&GroupChecksum, sizeof(uint32), 1, pFile);
+	MEM_Read(&NumMeshesThisGroup, sizeof(uint32), 1, pFile);
+
+	GroupChecksum += texDictOffset;
+
+	// resolve checksum
+	for (pGroup=sGroup::pHead; pGroup; pGroup=pGroup->pNext)
+		if (pGroup->Checksum == GroupChecksum)
+			break;
+	Dbg_MsgAssert(pGroup, ("Couldn't find group checksum #%0X\n", GroupChecksum));
+
+	// set mesh info in the group
+	pGroup->pMeshes = pScene->pMeshes+pScene->NumMeshes;
+	pGroup->NumMeshes = NumMeshesThisGroup;
+
+	// loop over meshes
+	for (i=0,pMesh=pGroup->pMeshes; iChecksum, sizeof(uint32), 1, pFile);
+
+		// get object sphere for mesh version 2 onwards
+		if (sMesh::Version >= 2)
+		{
+			unsigned int lod_stuff_not_used_here;
+			MEM_Read(&lod_stuff_not_used_here, sizeof(unsigned int), 1, pFile);
+			MEM_Read(&lod_stuff_not_used_here, sizeof(unsigned int), 1, pFile);
+
+			// Just process parent/child stuff
+			int num_child, hier_data;
+			MEM_Read(&hier_data, sizeof(unsigned int), 1, pFile);
+			MEM_Read(&num_child, sizeof(unsigned int), 1, pFile);
+			for (int j = 0; j < num_child; j++)
+			{
+				MEM_Read(&hier_data, sizeof(unsigned int), 1, pFile);
+			}
+
+			MEM_Read(&sphere, sizeof(Mth::Vector), 1, pFile);
+
+			// write sphere into sScene
+			if (pScene->Sphere[3] < 0.0f)
+				pScene->Sphere = sphere;
+
+			#if 0
+			// GJ:  the following assertion is no longer valid...
+			// there are occasionally multiple NxObjects inside a
+			// single MDL file, and each mesh will have a different
+			// bounding sphere depending on which NxObject is belongs
+			// to...  Mike agreed to rewrite the bounding sphere
+			// calculation code so that all meshes will have a
+			// new bounding sphere that encompasses all the individual
+			// meshes' bounding spheres...
+
+			// check object spheres from different meshes match ok
+			Dbg_MsgAssert((pScene->Sphere[0]==sphere[0] && pScene->Sphere[1]==sphere[1] &&
+						   pScene->Sphere[2]==sphere[2] && pScene->Sphere[3]==sphere[3]),
+						  ("object bounding sphere differs between meshes"));
+			#else
+			// ...so, if the object spheres differ, just combine them...
+			if (pScene->Sphere[0]!=sphere[0] || pScene->Sphere[1]!=sphere[1] ||
+				pScene->Sphere[2]!=sphere[2] || pScene->Sphere[3]!=sphere[3])
+			{
+				//printf("\n\n");
+				//printf("**********************************************\n");
+				//printf("*                                            *\n");
+				//printf("*  Tell Mike if you see this line printing!  *\n");
+				//printf("*                                            *\n");
+				//printf("**********************************************\n");
+				//printf("\n\n");
+				//printf("combining spheres (%g,%g,%g,%g) and (%g,%g,%g,%g)\n",
+				//	   pScene->Sphere[0], pScene->Sphere[1], pScene->Sphere[2], pScene->Sphere[3],
+				//	   sphere[0], sphere[1], sphere[2], sphere[3]);
+
+				Mth::Vector x0, x1, d;
+				if (pScene->Sphere[3] > sphere[3])
+				{
+					x0 = pScene->Sphere; 
+					x1 = sphere;
+				}
+				else
+				{
+					x0 = sphere;
+					x1 = pScene->Sphere;
+				}
+				float r0=x0[3], r1=x1[3];
+
+				// (x0,r0) is the larger sphere, (x1,r1) the smaller
+				// d is the vector between centres
+				d = x1 - x0;
+
+				// square of distance between centres
+				float D2 = DotProduct(d,d);
+
+				// (r0-r1)^2
+				float R2 = (r0-r1)*(r0-r1);
+
+				// m = max { (r1-r0+|d|)/2, 0 }
+				float m = 0.0f;
+				if (R2-D2 < 0)
+				{
+					m = (r1-r0 + sqrtf(D2)) * 0.5f;
+				}
+
+				pScene->Sphere    = x0 + m * d;
+				pScene->Sphere[3] = r0 + m;
+
+				//printf("result is (%g,%g,%g,%g)\n",
+				//	   pScene->Sphere[0], pScene->Sphere[1], pScene->Sphere[2], pScene->Sphere[3]);
+			
+				//Dbg_MsgAssert( 0, ( "Please tell Mike if you see this assert firing off!" ) );
+			}
+			#endif
+		}
+
+		// get material checksum and look it up
+		MEM_Read(&MaterialChecksum, sizeof(uint32), 1, pFile);
+		for (j=0,pMat=pScene->pMaterials; jNumMaterials; j++,pMat++)
+			if (pMat->Checksum == MaterialChecksum)
+				break;
+		Dbg_MsgAssert(pMat->Checksum==MaterialChecksum, ("couldn't find material with checksum %08X\n", MaterialChecksum));
+		pMesh->pMaterial = pMat;
+
+		// get mesh flags
+		MEM_Read(&pMesh->Flags, sizeof(uint32), 1, pFile);
+
+		// make it active
+		pMesh->SetActive(true);
+
+		// get bounding volume data
+		MEM_Read(&pMesh->ObjBox, sizeof(float) * 3, 1, pFile);
+		MEM_Read(&pMesh->Box,    sizeof(float) * 3, 1, pFile);
+		MEM_Read(&pMesh->Sphere, sizeof(float) * 4, 1, pFile);
+
+		// pass number
+		if (sMesh::Version >= 5)
+		{
+			MEM_Read(&pMesh->Pass, sizeof(int), 1, pFile);
+		}
+
+		// material name
+		if (sMesh::Version >= 6)
+		{
+			MEM_Read(&pMesh->MaterialName, sizeof(int), 1, pFile);
+		}
+		
+		// import vertices of this mesh
+		pFile = LoadVertices(pFile, pMesh, pMat, pGroup, skinnedVertexCount);
+
+		// end the dma tag
+		dma::EndTag();
+
+		// end the model subroutine
+		pMesh->pSubroutine = dma::EndSub3D();
+	}
+
+	// add to total number for scene
+	pScene->NumMeshes += NumMeshesThisGroup;
+	return pFile;
+}
+
+
+
+//----------------------------------------------------------------------------------------
+//		L O A D   V E R T I C E S
+//----------------------------------------------------------------------------------------
+
+int UnpackOffset;
+
+void * LoadVertices(void *pFile, sMesh *pMesh, sMaterial *pMat, sGroup *pGroup, int& skinnedIndexCount)
+{
+	uint32 NumVertices;
+	uint REGS, NREG, PRIM, ADDR;
+	sTexture *pTex;
+	uint32 i;
+	uint8  VertexData[64];
+	int    STOffset, ColourOffset, NormalOffset, SkinOffset, XYZOffset, VertexSize;
+	int    texture_width=0, texture_height=0;
+
+	// get number of vertices for this mesh
+	MEM_Read(&NumVertices, sizeof(uint32), 1, pFile);
+
+	// skip mesh if it has no vertices
+	if (NumVertices==0)
+		return pFile;
+
+	// work out giftag fields
+	REGS = gs::XYZF2;
+	NREG = 1;
+	PRIM = TRISTRIP|ABE|FGE;
+	ADDR = VU1_ADDR(Proj);
+	if (pMesh->Flags & MESHFLAG_COLOURS)
+	{
+		REGS = REGS<<4 | gs::RGBAQ;
+		NREG++;
+	}
+	if (pMesh->Flags & MESHFLAG_NORMALS)
+	{
+		if (pMat->Flags & MATFLAG_ENVIRONMENT)
+		{
+			ADDR = VU1_ADDR(Refl);
+			REGS = REGS<<4 | gs::ST;
+			PRIM |= TME;
+		}
+		else
+		{
+			REGS = REGS<<4 | gs::NOP;
+		}
+		NREG++;
+	}
+	if ((pMesh->Flags & MESHFLAG_TEXTURE) && !(pMat->Flags & MATFLAG_ENVIRONMENT))
+	{
+		REGS = REGS<<4 | gs::ST;
+		NREG++;
+		PRIM |= TME;
+		ADDR = VU1_ADDR(PTex);
+	}
+	if (pMat->Flags & MATFLAG_SMOOTH)
+		PRIM |= IIP;
+
+	// override everything for skinned models!
+	if (pMesh->Flags & MESHFLAG_SKINNED)
+	{
+		if (pMat->RegCLAMP & PackCLAMP(3,3,0,0,0,0))
+		{
+			// clamped case, send texture coords as 32 bits floating point STs
+			REGS = gs::XYZ2<<16 | gs::RGBAQ<<12 | gs::NOP<<8 | gs::NOP<<4 | gs::ST;
+			NREG = 5;
+			PRIM |= TME;
+			ADDR = VU1_ADDR(Skin);
+		}
+		else
+		{
+			// standard case, non-clamped, compress texture coords to 16 bit fixed point UVs
+			REGS = gs::XYZ2<<16 | gs::RGBAQ<<12 | gs::NOP<<8 | gs::NOP<<4 | gs::UV;
+			NREG = 5;
+			PRIM |= TME|FST;
+			ADDR = VU1_ADDR(Skin);
+		}
+	}
+
+	// output GS context for material
+	gs::BeginPrim(REL,0,0);
+	gs::Reg1(gs::ALPHA_1,	pMat->RegALPHA);
+	gs::Reg1(gs::TEST_1, PackTEST(1,AGEQUAL,pMat->Aref,KEEP,0,0,1,ZGEQUAL));
+
+	// texture registers if necessary
+	if (pMat->Flags & MATFLAG_TEXTURED)
+	{
+		pTex = pMat->pTex;
+		gs::Reg1(gs::TEX0_1,	pTex->RegTEX0);
+		gs::Reg1(gs::TEX1_1,	pMat->RegTEX1);
+		gs::Reg1(gs::CLAMP_1,	pMat->RegCLAMP);
+		if (pTex->MXL > 0)
+		{
+			gs::Reg1(gs::MIPTBP1_1,pTex->RegMIPTBP1);
+			if (pTex->MXL > 3)
+			{
+				gs::Reg1(gs::MIPTBP2_1,pTex->RegMIPTBP2);
+			}
+		}
+
+		// extract texture dimensions
+		texture_width  = 1 << ((pTex->RegTEX0 >> 26) & 0xF);
+		texture_height = 1 << ((pTex->RegTEX0 >> 30) & 0xF);
+	}
+	gs::EndPrim(0);
+
+	// set maximum vu buffer size
+	vu1::MaxBuffer = pMesh->Flags&MESHFLAG_SKINNED ? 240 : 240;
+
+	// begin a batch of vertices
+	BeginModelMultiPrim(REGS, NREG, PRIM, NumVertices, ADDR);
+
+	// work out vertex size and data offsets
+	VertexSize = (pMesh->Flags & MESHFLAG_SKINNED) ? 8 : 16;
+	XYZOffset = SkinOffset = NormalOffset = ColourOffset = STOffset = 0;
+	if (pMesh->Flags & MESHFLAG_SKINNED)
+	{
+		VertexSize   += 8;
+		XYZOffset    += 8;
+	}
+	if (pMesh->Flags & MESHFLAG_NORMALS)
+	{
+		VertexSize   += 4;
+		XYZOffset    += 4;
+		SkinOffset   += 4;
+	}
+	if (pMesh->Flags & MESHFLAG_COLOURS)
+	{
+		VertexSize   += 4;
+		XYZOffset    += 4;
+		SkinOffset   += 4;
+		NormalOffset += 4;
+	}
+	if (pMesh->Flags & MESHFLAG_TEXTURE)
+	{
+		int size = (pMesh->Flags & MESHFLAG_SKINNED) ? 4 : 8;
+		VertexSize   += size;
+		XYZOffset    += size;
+		SkinOffset   += size;
+		NormalOffset += size;
+		ColourOffset += size;
+	}
+	// loop over vertices
+	for (i=0; iFlags & MESHFLAG_SKINNED)
+		{
+			// texture coords
+			UnpackOffset = 1;
+			if ((pMesh->Flags & MESHFLAG_TEXTURE) && !(pMat->Flags & MATFLAG_ENVIRONMENT))
+			{
+				if (pMat->RegCLAMP & PackCLAMP(3,3,0,0,0,0))
+				{
+					VertexSTFloat(VertexData+STOffset);
+				}
+				else
+				{
+					VertexUV(VertexData+STOffset, texture_width<<4, texture_height<<4);
+				}
+			}
+
+			// weights
+			UnpackOffset = 2;
+			VertexWeights(VertexData+SkinOffset);
+			
+			// normal and transform offsets
+			UnpackOffset = 3;
+			VertexSkinNormal(VertexData+NormalOffset, VertexData+SkinOffset+4);
+
+			// colour
+			UnpackOffset = 4;
+			if (pMesh->Flags & MESHFLAG_COLOURS)
+			{
+				VertexRGBA(VertexData+ColourOffset);
+			}
+
+			// position
+			UnpackOffset = 5;
+			VertexXYZ(VertexData+XYZOffset, true, skinnedIndexCount);
+		}
+		else
+		{
+			// texture coords
+			if ((pMesh->Flags & MESHFLAG_TEXTURE) && !(pMat->Flags & MATFLAG_ENVIRONMENT))
+			{
+				VertexST16(VertexData+STOffset);
+			}
+
+			// normal normals
+			if (pMesh->Flags & MESHFLAG_NORMALS)
+			{
+				VertexNormal(VertexData+NormalOffset);
+			}
+
+			// colour
+			if (pMesh->Flags & MESHFLAG_COLOURS)
+			{
+				VertexRGBA(VertexData+ColourOffset);
+			}
+
+			// position
+			VertexXYZ(VertexData+XYZOffset, false, skinnedIndexCount);
+		}
+
+		EndVertex();
+		
+		// current vert being processed
+		s_currentVertIndex++;
+	}
+
+	// accumulate number of vertices
+	sMesh::TotalNumVertices += NumVertices;
+
+	// finish batch of vertices
+	EndModelMultiPrim();
+	return pFile;
+}
+
+
+
+
+
+
+//---------------------------------------------------------
+//		M O D E L   P R I M   C O N S T R U C T I O N
+//---------------------------------------------------------
+
+
+uint   MeshRegs, MeshNReg, MeshPrim, MeshNLoop, MeshAddr;
+uint   NumVerticesThisBuffer, NumOutputVertices;
+uint32 *pColour;
+sint32 *pST32;
+sint16 *pST16, *pVertex, *pNormal, *pSkinData;
+uint8  *pWeights;
+uint16 *pUV;
+
+
+
+// begin model prim (i.e. begin a vertex packet)
+void BeginModelPrim(uint32 Regs, uint NReg, uint Prim, uint Pre, uint Addr)
+{
+	vif::STCYCL(1,NReg);
+	vif::UNPACK(0,V4_32,1,REL,UNSIGNED,0);
+	gif::BeginTag1(Regs, NReg, PACKED, Prim, Pre, Addr);
+}
+
+
+// end model prim (i.e. end a vertex packet)
+void EndModelPrim(uint Eop)
+{
+	gif::EndTag1(Eop);
+}
+
+
+// begin model prim (i.e. begin a vertex packet)
+void BeginModelPrimImmediate(uint32 Regs, uint NReg, uint Prim, uint Pre, uint Addr)
+{
+	vif::STCYCL(1,NReg);
+	vif::UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+	gif::BeginTag1(Regs, NReg, PACKED, Prim, Pre, Addr);
+}
+
+
+// end model prim (i.e. end a vertex packet)
+void EndModelPrimImmediate(uint Eop)
+{
+	gif::EndTag1(Eop);
+}
+
+
+// begin a model prim which may span multiple VU1 buffers
+void BeginModelMultiPrim(uint32 Regs, uint NReg, uint Prim, uint NLoop, uint Addr)
+{
+	// record tag info
+	MeshRegs  = Regs;
+	MeshNReg  = NReg;
+	MeshPrim  = Prim;
+	MeshNLoop = NLoop;
+	MeshAddr  = Addr;
+
+	// work out number of vertices to unpack first
+	// (and go ahead with it for now, even if there's only room for 1 vertex)
+	NumVerticesThisBuffer = (vu1::MaxBuffer - ((vu1::Loc-vu1::Buffer)&0x3FF) - 1) / NReg;
+	if (NumVerticesThisBuffer > MeshNLoop)
+		NumVerticesThisBuffer = MeshNLoop;
+
+	BeginModelPrim(Regs, NReg, Prim, 1, Addr);
+	NumOutputVertices = 0;
+}
+
+
+void EndModelMultiPrim(void)
+{
+	EndModelPrim(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	vif::FLUSH();
+}
+
+
+int RestartTristrip=0;
+
+void BeginVertex(sMesh *pMesh, sMaterial *pMat)
+{
+	if (NumOutputVertices == NumVerticesThisBuffer)
+	{
+		// end the model prim
+		if ((pMesh->Flags & MESHFLAG_SKINNED) && (768-((vu1::Buffer+vif::UnpackSize*vif::CycleLength+1)&1023) < vu1::MaxBuffer))
+		{
+			EndModelPrim(0);
+			vif::UNPACK(0, V4_32, 1, REL, UNSIGNED, 0);
+			gif::Tag1(0, 0, PACKED, 0, 0, 1, 0, VU1_ADDR(Jump));		// reset address to 0
+			vu1::Loc = 0;
+
+			// start VU1 execution
+			vif::MSCAL(VU1_ADDR(Parser));
+			vif::FLUSH();
+		}
+		else
+		{
+			EndModelPrim(1);
+
+			// start VU1 execution
+			vif::MSCAL(VU1_ADDR(Parser));
+			vif::FLUSH();
+		}
+
+		// reduce number still to go
+		MeshNLoop -= NumOutputVertices;
+
+		// work out number of vertices to unpack next
+		NumVerticesThisBuffer = vu1::MaxBuffer / MeshNReg;
+		if (NumVerticesThisBuffer > MeshNLoop)
+			NumVerticesThisBuffer = MeshNLoop;
+
+		// start a new buffalo'd
+
+		// add a dummy gs context
+		gs::BeginPrim(REL,0,0);
+		gs::Reg1(gs::A_D_NOP,0);
+		gs::EndPrim(0);
+
+		BeginModelPrim(MeshRegs, MeshNReg, MeshPrim, 1, MeshAddr);
+		NumOutputVertices = 0;
+
+		// signal tristrip restart
+		RestartTristrip=1;
+	}
+}
+
+
+void VertexST16(uint8 *pData)
+{
+	static sint16 s0,t0,s1,t1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::BeginUNPACK(0, V2_16, REL, SIGNED, UnpackOffset);
+		pST16 = (sint16 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pST16++ = s0;
+			*pST16++ = t0;
+			*pST16++ = s1;
+			*pST16++ = t1;
+		}
+		dma::pLoc = (uint8 *)pST16 + NumVerticesThisBuffer * 4;
+		vif::EndUNPACK();
+	}
+	*pST16++ = (sint16) (int) (((float *)pData)[0] * 4096.0f);
+	*pST16++ = (sint16) (int) (((float *)pData)[1] * 4096.0f);
+	s0=s1, s1=pST16[-2];
+	t0=t1, t1=pST16[-1];
+
+	UnpackOffset++;
+}
+
+
+void VertexSTFloat(uint8 *pData)
+{
+	static sint32 s0,t0,s1,t1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::STMASK(0xE0);
+		vif::BeginUNPACK(1, V2_32, REL, SIGNED, UnpackOffset);
+		pST32 = (sint32 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pST32++ = s0;
+			*pST32++ = t0;
+			*pST32++ = s1;
+			*pST32++ = t1;
+		}
+		dma::pLoc = (uint8 *)pST32 + NumVerticesThisBuffer * 8;
+		vif::EndUNPACK();
+	}
+	*(float *)pST32++ = (float)((sint16 *)pData)[0] * 0.000244140625f;
+	*(float *)pST32++ = (float)((sint16 *)pData)[1] * 0.000244140625f;
+	s0=s1, s1=pST32[-2];
+	t0=t1, t1=pST32[-1];
+
+	UnpackOffset++;
+}
+
+
+void VertexUV(uint8 *pData, int textureWidth16, int textureHeight16)
+{
+	static uint16 u0,v0,u1,v1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::BeginUNPACK(0, V2_16, REL, UNSIGNED, UnpackOffset);
+		pUV = (uint16 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pUV++ = u0;
+			*pUV++ = v0;
+			*pUV++ = u1;
+			*pUV++ = v1;
+		}
+		dma::pLoc = (uint8 *)pUV + NumVerticesThisBuffer * 4;
+		vif::EndUNPACK();
+	}
+	*pUV++ = (uint16)((int)((float)((sint16 *)pData)[0] * (float)textureWidth16  * 0.000244140625f) + 0x00002000);
+	*pUV++ = (uint16)((int)((float)((sint16 *)pData)[1] * (float)textureHeight16 * 0.000244140625f) + 0x00002000);
+	u0=u1, u1=pUV[-2];
+	v0=v1, v1=pUV[-1];
+
+	UnpackOffset++;
+}
+
+
+void VertexNormal(uint8 *pData)
+{
+	static sint16 nx0,ny0,nz0,nx1,ny1,nz1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::BeginUNPACK(0, V3_16, REL, SIGNED, UnpackOffset);
+		pNormal = (sint16 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pNormal++ = nx0;
+			*pNormal++ = ny0;
+			*pNormal++ = nz0;
+			*pNormal++ = nx1;
+			*pNormal++ = ny1;
+			*pNormal++ = nz1;
+		}
+		dma::pLoc = (uint8 *)pNormal + NumVerticesThisBuffer * 6;
+		vif::EndUNPACK();
+	}
+	*pNormal++ = ((sint16 *)pData)[0];
+	*pNormal++ = ((sint16 *)pData)[1];
+	//*pNormal++ = ((sint16 *)pData)[2];
+	sint16 coord = (sint16)sqrtf(32767.0f*32767.0f - (float)((sint16 *)pData)[0] * (float)((sint16 *)pData)[0]
+												   - (float)((sint16 *)pData)[1] * (float)((sint16 *)pData)[1]);
+	if (((sint16 *)pData)[0] & 0x0001)
+	{
+		coord = -coord;
+	}
+	*pNormal++ = coord;
+	nx0=nx1, nx1=pNormal[-3];
+	ny0=ny1, ny1=pNormal[-2];
+	nz0=nz1, nz1=pNormal[-1];
+
+	UnpackOffset++;
+}
+
+
+#if 0
+
+void VertexWeights(uint8 *pData)
+{
+	static sint16 wa0,wb0,wc0,wa1,wb1,wc1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::BeginUNPACK(0, V3_16, REL, SIGNED, UnpackOffset);
+		pWeights = (sint16 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pWeights++ = wa0;
+			*pWeights++ = wb0;
+			*pWeights++ = wc0;
+			*pWeights++ = wa1;
+			*pWeights++ = wb1;
+			*pWeights++ = wc1;
+		}
+		dma::pLoc = (uint8 *)pWeights + NumVerticesThisBuffer * 6;
+		vif::EndUNPACK();
+	}
+	*pWeights++ = ((sint16 *)pData)[0];
+	*pWeights++ = ((sint16 *)pData)[1];
+	*pWeights++ = 0x7FFF - pWeights[-1] - pWeights[-2];
+	wa0=wa1, wa1=pWeights[-3];
+	wb0=wb1, wb1=pWeights[-2];
+	wc0=wc1, wc1=pWeights[-1];
+
+	UnpackOffset++;
+}
+
+#else
+
+void VertexWeights(uint8 *pData)
+{
+	static uint8 wa0,wb0,wc0,wa1,wb1,wc1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::BeginUNPACK(0, V3_8, REL, UNSIGNED, UnpackOffset);
+		pWeights = dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pWeights++ = wa0;
+			*pWeights++ = wb0;
+			*pWeights++ = wc0;
+			*pWeights++ = wa1;
+			*pWeights++ = wb1;
+			*pWeights++ = wc1;
+		}
+		dma::pLoc = pWeights + NumVerticesThisBuffer * 3;
+		vif::EndUNPACK();
+	}
+	wa0=wa1;
+	wb0=wb1;
+	wc0=wc1;
+	wa1 = (uint8)(sint8)(((float)((sint16 *)pData)[0] + 0.5f) * 0.007818608f);
+	wb1 = (uint8)(sint8)(((float)((sint16 *)pData)[1] + 0.5f) * 0.007818608f);
+	if (wb1==0)
+	{
+		wa1=255, wb1=1;
+	}
+	wc1 = 256 - wa1 - wb1;
+	*pWeights++ = wa1;
+	*pWeights++ = wb1;
+	*pWeights++ = wc1;
+
+	UnpackOffset++;
+}
+
+#endif
+
+
+
+void VertexSkinNormal(uint8 *pNormData, uint8 *pTOData)
+{
+	static sint16 nx0,ny0,nz0,nx1,ny1,nz1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::BeginUNPACK(0, V3_16, REL, SIGNED, UnpackOffset);
+		pSkinData = (sint16 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pSkinData++ = nx0;
+			*pSkinData++ = ny0;
+			*pSkinData++ = nz0;
+			*pSkinData++ = nx1;
+			*pSkinData++ = ny1;
+			*pSkinData++ = nz1;
+		}
+		dma::pLoc = (uint8 *)pSkinData + NumVerticesThisBuffer * 6;
+		vif::EndUNPACK();
+	}
+
+	*pSkinData++ = (sint16) (((sint32) ((sint16 *)pNormData)[0]) & 0xFFFFFC00 | (((uint32)((uint8 *)pTOData)[0]) << 2));
+	*pSkinData++ = (sint16) (((sint32) ((sint16 *)pNormData)[1]) & 0xFFFFFC00 | (((uint32)((uint8 *)pTOData)[1]) << 2));
+	//*pSkinData++ = (sint16) (((sint32) ((sint16 *)pNormData)[2]) & 0xFFFFFC00 | (((uint32)((uint8 *)pTOData)[2]) << 2));
+	sint16 coord = (sint16)sqrtf(32767.0f*32767.0f - (float)((sint16 *)pNormData)[0] * (float)((sint16 *)pNormData)[0]
+												   - (float)((sint16 *)pNormData)[1] * (float)((sint16 *)pNormData)[1]);
+	if (((sint16 *)pNormData)[0] & 0x0001)
+	{
+		coord = -coord;
+	}
+	*pSkinData++ = (sint16) ((sint32)coord & 0xFFFFFC00) | (((uint8 *)pTOData)[2] << 2);
+
+	nx0=nx1, nx1=pSkinData[-3];
+	ny0=ny1, ny1=pSkinData[-2];
+	nz0=nz1, nz1=pSkinData[-1];
+
+	UnpackOffset++;
+}
+
+
+
+void VertexRGBA(uint8 *pData)
+{
+	static uint32 rgba0,rgba1;
+
+	if (NumOutputVertices==0)
+	{
+		vif::BeginUNPACK(0, V4_8, REL, UNSIGNED, UnpackOffset);
+		pColour = (uint32 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pColour++ = rgba0;
+			*pColour++ = rgba1;
+		}
+		dma::pLoc = (uint8 *)pColour + NumVerticesThisBuffer * 4;
+		vif::EndUNPACK();
+	}
+	*pColour++ = ((uint32 *)pData)[0];
+	rgba0=rgba1, rgba1=pColour[-1];
+
+	UnpackOffset++;
+}
+
+Mth::Vector get_bone_scale( int bone_index )
+{
+	Mth::Vector returnVec( 1.0f, 1.0f, 1.0f, 1.0f );
+
+	if ( bone_index >= 29 && bone_index <= 33 )
+	{
+		// this only works with the thps5 skeleton, whose
+		// head bones are between 29 and 33...
+		// (eventually, we can remove the subtract 29
+		// once the exporter is massaging the data correctly)
+		returnVec = s_pBoneScales[ bone_index - 29 ];
+		
+		// Y & Z are reversed...  odd!
+		Mth::Vector tempVec = returnVec;
+		returnVec[Y] = tempVec[Z];
+		returnVec[Z] = tempVec[Y];
+	}
+	else if ( bone_index == -1 )
+	{
+		// implies that it's not weighted to a bone
+		return returnVec;
+	}
+	else
+	{
+		// implies that it's weighted to the wrong bone
+		return returnVec;
+	}
+
+	return returnVec;
+}
+
+Mth::Vector get_bone_pos( int bone_index )
+{
+	Mth::Vector returnVec( 0.0f, 0.0f, 0.0f, 1.0f );
+	
+	if ( bone_index >= 29 && bone_index <= 33 )
+	{
+		// this only works with the thps5 skeleton, whose
+		// head bones are between 29 and 33...
+		// (eventually, we can remove the subtract 29
+		// once the exporter is massaging the data correctly)
+		returnVec = s_pBonePositions[ bone_index - 29 ];
+	}
+	else if ( bone_index == -1 )
+	{
+		// implies that it's not weighted to a bone
+		return returnVec;
+	}
+	else
+	{
+		// implies that it's weighted to the wrong bone
+		return returnVec;
+	}
+
+	// need to get into fixed point
+	returnVec.Scale( 16.0f );
+	returnVec[W] = 1.0f;
+
+	return returnVec;
+}
+
+void VertexXYZ(uint8 *pData, bool IsSkin, int& skinnedVertexCount)
+{
+	static sint64 x0,y0,z0,x1,y1,z1,x2,y2,z2;
+	sint64 det0,det1,det2;
+
+	if (NumOutputVertices==0)
+	{
+		vif::STMOD(1);
+		vif::BeginUNPACK(0, V4_16, REL, SIGNED, UnpackOffset);
+		pVertex = (sint16 *)dma::pLoc;
+		if (RestartTristrip)
+		{
+			*pVertex++ = x1;
+			*pVertex++ = y1;
+			*pVertex++ = z1;
+			*pVertex++ = 0x8000;
+			*pVertex++ = x2;
+			*pVertex++ = y2;
+			*pVertex++ = z2;
+			*pVertex++ = 0x8000;
+		}
+		dma::pLoc = (uint8 *)pVertex + NumVerticesThisBuffer * 8;
+		vif::EndUNPACK();
+		vif::STMOD(0);
+	}
+	
+	if (IsSkin)
+	{
+		if ( s_meshScalingEnabled )
+		{
+			float x = (float)((sint16 *)pData)[0];
+			float y = (float)((sint16 *)pData)[1];
+			float z = (float)((sint16 *)pData)[2];
+
+    		Mth::Vector origPos( x, y, z, 1.0f );
+
+			Mth::Vector bonePos0 = get_bone_pos( s_pWeightIndices[s_currentVertIndex * 3] );
+			Mth::Vector bonePos1 = get_bone_pos( s_pWeightIndices[s_currentVertIndex * 3 + 1] );
+			Mth::Vector bonePos2 = get_bone_pos( s_pWeightIndices[s_currentVertIndex * 3 + 2] );
+
+			// need to scale each vert relative to its parent bone
+			Mth::Vector localPos0 = origPos - bonePos0;
+			Mth::Vector localPos1 = origPos - bonePos1;
+			Mth::Vector localPos2 = origPos - bonePos2;
+			localPos0.Scale( get_bone_scale( s_pWeightIndices[s_currentVertIndex * 3] ) );
+			localPos1.Scale( get_bone_scale( s_pWeightIndices[s_currentVertIndex * 3 + 1] ) );
+			localPos2.Scale( get_bone_scale( s_pWeightIndices[s_currentVertIndex * 3 + 2] ) );
+			localPos0 += bonePos0;
+			localPos1 += bonePos1;
+			localPos2 += bonePos2;
+			
+			Mth::Vector scaledPos = ( localPos0 * s_pWeights[s_currentVertIndex * 3] )
+				+ ( localPos1 * s_pWeights[s_currentVertIndex * 3 + 1] )
+				+ ( localPos2 * s_pWeights[s_currentVertIndex * 3 + 2] );
+
+			x = scaledPos[X];
+			y = scaledPos[Y];
+			z = scaledPos[Z];
+
+			*pVertex++ = (sint16)x;
+			*pVertex++ = (sint16)y;
+			*pVertex++ = (sint16)z;
+		}
+		else
+		{
+			*pVertex++ = (((sint16 *)pData)[0] * 1);
+			*pVertex++ = (((sint16 *)pData)[1] * 1);
+			*pVertex++ = (((sint16 *)pData)[2] * 1);
+		}
+	}
+	else
+	{
+		*pVertex++ = (sint16) (((float *)pData)[0] * SUB_INCH_PRECISION);
+		*pVertex++ = (sint16) (((float *)pData)[1] * SUB_INCH_PRECISION);
+		*pVertex++ = (sint16) (((float *)pData)[2] * SUB_INCH_PRECISION);
+	}
+	
+	if (IsSkin)
+	{
+		// GJ:  if it's a skinned model, then check to see
+		// if the skinnedVertexCount matches one of the ones
+		// that requires a pointer to the ADC bit
+		AddCASFlag( skinnedVertexCount, (uint16*)pVertex );
+	}
+	
+	skinnedVertexCount++;
+
+	if (IsSkin)
+	{
+		*pVertex++ = ((uint16 *)pData)[3] ? 0x8000 : 0x0000;
+	}
+	else
+	{
+		*pVertex++ = ((uint32 *)pData)[3] ? 0x8000 : 0x0000;
+	}
+	
+	// advance vertex queue and cull triangle if zero area
+	x0=x1, y0=y1, z0=z1;
+	x1=x2, y1=y2, z1=z2;
+	x2=pVertex[-4], y2=pVertex[-3], z2=pVertex[-2];
+	det0 = y1*z2+y2*z0+y0*z1-y2*z1-y0*z2-y1*z0;
+	det1 = z1*x2+z2*x0+z0*x1-z2*x1-z0*x2-z1*x0;
+	det2 = x1*y2+x2*y0+x0*y1-x2*y1-x0*y2-x1*y0;
+	if (det0*det0 + det1*det1 + det2*det2 == 0)
+		pVertex[-1] = 0x8000;
+
+	UnpackOffset++;
+}
+
+
+void EndVertex(void)
+{
+	if (NumOutputVertices==0 && RestartTristrip)
+	{
+		RestartTristrip = 0;
+	}
+
+	NumOutputVertices++;
+}
+
+
+
+
+void sMesh::SetActive(bool active)
+{
+	if (active)
+	{
+		Flags |= MESHFLAG_ACTIVE;
+	}
+	else
+	{
+		Flags &= ~MESHFLAG_ACTIVE;
+	}
+}
+
+
+// Note, Meshflags and passmasks are off by 2 bits
+//#define MESHFLAG_PASS_BIT_0	(1<<8)
+//#define MESHFLAG_PASS_BIT_1	(1<<9)
+
+//uint32		gPassMask1 = 0; 		// 1<<6 | 1<<7  (0x40, 0x80)
+//uint32		gPassMask0 = 0; 		// 1<<6 | 1<<7  (0x40, 0x80)
+
+bool sMesh::IsActive()
+{
+	#ifdef	__NOPT_ASSERT__
+	// Mick: debug check for global pass on/off
+	// Note, shifting flags by 2, so mesh flags match geomnode flags
+	// Eventually, meshes will be replaced by leaf nodes in CGeomNode tree
+//	if (((Flags>>2) & gPassMask1) != gPassMask0)
+//	{	
+//		return false;
+//	}
+	#endif
+	
+
+	
+	return (Flags&MESHFLAG_ACTIVE) ? true : false;
+}
+
+const int MAX_CAS_DATA_LOOKUP = 10000;
+
+// array of pointers to sCASData*
+sCASData** sCASDataLookupTable = NULL;
+
+void CreateCASDataLookupTable()
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+	Dbg_MsgAssert( sCASDataLookupTable == NULL, ( "CAS data lookup table already exists" ) );
+
+	sCASDataLookupTable = (sCASData**)Mem::Malloc( MAX_CAS_DATA_LOOKUP * sizeof(sCASData*) );
+
+	for ( int i = 0; i < MAX_CAS_DATA_LOOKUP; i++ )
+	{
+		sCASDataLookupTable[i] = NULL;
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+void DestroyCASDataLookupTable()
+{
+	if ( sCASDataLookupTable )
+	{
+		Mem::Free( sCASDataLookupTable );
+		sCASDataLookupTable = NULL;
+	}
+}
+
+void SetCASDataLookupData( int index, sCASData* pData )
+{
+	Dbg_MsgAssert( sCASDataLookupTable != NULL, ( "CAS data lookup table doesn't exist" ) );
+
+	Dbg_MsgAssert( index >= 0 && index < MAX_CAS_DATA_LOOKUP, ( "Out of range lookup index %d (must be between 0 and %d)", index, MAX_CAS_DATA_LOOKUP ) );
+
+	Dbg_MsgAssert( sCASDataLookupTable[index] == NULL, ( "Lookup index %d already used", index, MAX_CAS_DATA_LOOKUP ) );
+
+	sCASDataLookupTable[index] = pData;
+}
+
+void AddCASFlag( int skinnedVertexIndex, uint16* pADCBit )
+{
+	if ( !sCASDataLookupTable )
+	{
+		return;
+	}
+
+	Dbg_MsgAssert( skinnedVertexIndex >= 0 && skinnedVertexIndex < MAX_CAS_DATA_LOOKUP, ( "Out of range lookup index %d (must be between 0 and %d", skinnedVertexIndex, MAX_CAS_DATA_LOOKUP ) );
+
+	if ( sCASDataLookupTable[skinnedVertexIndex] != NULL )
+	{
+		sCASDataLookupTable[skinnedVertexIndex]->pADCBit = pADCBit;
+	}
+}
+
+int sMesh::TotalNumVertices;
+uint32 sMesh::Version;
+
+
+
+} // namespace NxPs2
+
+
diff --git a/Code/Gfx/NGPS/NX/mesh.h b/Code/Gfx/NGPS/NX/mesh.h
new file mode 100644
index 0000000..eaeabf7
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/mesh.h
@@ -0,0 +1,109 @@
+#ifndef __MESH_H
+#define __MESH_H
+
+
+#include 
+
+namespace Nx
+{
+	struct SMeshScalingParameters;
+}
+					  
+namespace NxPs2
+{
+
+
+#define MESHFLAG_TEXTURE		(1<<0)
+#define MESHFLAG_COLOURS		(1<<1)
+#define MESHFLAG_NORMALS		(1<<2)
+#define MESHFLAG_ST16			(1<<3)
+#define MESHFLAG_SKINNED		(1<<4)
+#define MESHFLAG_AXIAL			(1<<6)
+#define MESHFLAG_SHORTAXIS		(1<<7)
+#define MESHFLAG_PASS_BIT_0		(1<<8)
+#define MESHFLAG_PASS_BIT_1		(1<<9)
+#define MESHFLAG_NO_SHADOW		(1<<10)
+#define MESHFLAG_ACTIVE			(1<<16)
+#define MESHFLAG_UNLIT			(1<<17)
+#define MESHFLAG_COLOR_LOCKED	(1<<18)
+#define MESHFLAG_BILLBOARD		(1<<23)
+#define MESHFLAG_SINGLESIDED	(1<<24)
+#define MESHFLAG_PASS_BIT_2		(1<<25)
+#define MESHFLAG_PASS_BIT_3		(1<<26)
+
+#define MESHFLAG_PASS_BITS		(MESHFLAG_PASS_BIT_0 | MESHFLAG_PASS_BIT_1 | MESHFLAG_PASS_BIT_2 | MESHFLAG_PASS_BIT_3)
+
+
+#define OBJFLAG_TEXTURE			(1<<0)
+#define OBJFLAG_COLOURS			(1<<1)
+#define OBJFLAG_NORMALS			(1<<2)
+#define OBJFLAG_TRANSPARENT		(1<<3)
+
+
+struct sGroup;
+struct sMaterial;
+
+struct sCASData
+{
+	uint32	mask;
+//	int		vertIndex;	
+	uint16* pADCBit;
+};
+
+void CreateCASDataLookupTable();
+void DestroyCASDataLookupTable();
+void SetCASDataLookupData( int index, sCASData* pData );
+
+void AddCASFlag(int skinnedVertexIndex, uint16* pADCBit);
+
+struct sMesh
+{
+public:
+	uint32			GetChecksum() const { return Checksum; }
+	uint32			GetFlags() const { return Flags; }
+	void			SetActive(bool active);
+	bool			IsActive(void);
+
+	uint32 Checksum, Flags;
+	sMaterial *pMaterial;
+	uint8 *pSubroutine;
+	Mth::Vector ObjBox,Box,Sphere;
+	uint32*	pVCWibbleInfo;
+	int Pass;
+	uint32 MaterialName;
+
+	static int TotalNumVertices;
+	static uint32 Version;
+};
+
+// GJ:  for doing cutscene head scaling
+void SetMeshScalingParameters( Nx::SMeshScalingParameters* pParams );
+
+void * LoadMeshes(void *pFile, struct sScene *pScene, bool IsSkin, bool IsInstanceable, uint32 texDictOffset);
+void * LoadMeshGroup(void *pFile, struct sScene *pScene, bool IsSkin, bool IsInstanceable, uint32 texDictOffset, int& skinnedVertexIndex );
+void * LoadVertices(void *pFile, sMesh *pMesh, struct sMaterial *pMat, sGroup *pGroup, int& skinnedVertexIndex);
+void BeginModelPrim(uint32 Regs, uint NReg, uint Prim, uint Pre, uint Addr);
+void EndModelPrim(uint Eop);
+void BeginModelPrimImmediate(uint32 Regs, uint NReg, uint Prim, uint Pre, uint Addr);
+void EndModelPrimImmediate(uint Eop);
+void BeginModelMultiPrim(uint32 Regs, uint NReg, uint Prim, uint NLoop, uint Addr);
+void EndModelMultiPrim(void);
+void BeginVertex(sMesh *pMesh, struct sMaterial *pMat);
+void VertexST16(uint8 *pData);
+void VertexSTFloat(uint8 *pData);
+void VertexUV(uint8 *pData, int textureWidth16, int textureHeight16);
+void VertexNormal(uint8 *pData);
+void VertexWeights(uint8 *pData);
+void VertexSkinNormal(uint8 *pNormData, uint8 *pTOData);
+void VertexRGBA(uint8 *pData);
+void VertexXYZ(uint8 *pData, bool IsSkin, int& skinnedVertexIndex);
+void EndVertex(void);
+
+
+extern uint NumMeshesInGroup[32];
+
+
+} // namespace NxPs2
+
+#endif // __MESH_H
+
diff --git a/Code/Gfx/NGPS/NX/mikemath.cpp b/Code/Gfx/NGPS/NX/mikemath.cpp
new file mode 100644
index 0000000..d0b45b0
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/mikemath.cpp
@@ -0,0 +1,414 @@
+
+
+//--------------------------------------------------------------
+//		V E C T O R   A N D   M A T R I X   R O U T I N E S
+//--------------------------------------------------------------
+
+					 
+#include 
+#include 
+#include "mikemath.h"
+
+namespace NxPs2
+{
+
+
+// copy a vector
+// vd = vs
+void VecCopy(Vec vd, Vec vs)
+{
+	vd[0]=vs[0], vd[1]=vs[1], vd[2]=vs[2], vd[3]=vs[3];
+}
+
+
+// set a vector
+// v = (x,y,z,w)
+void VecSet(Vec v, float x, float y, float z, float w)
+{
+	v[0]=x, v[1]=y, v[2]=z, v[3]=w;
+}
+
+
+// add two vectors to make another
+// vd = v0+v1
+void VecSum(Vec vd, Vec v0, Vec v1)
+{
+	int i;
+	for (i=0; i<4; i++)
+		vd[i] = v0[i]+v1[i];
+}
+
+
+// add one vector onto another
+// vd += vs
+void VecAdd(Vec vd, Vec vs)
+{
+	int i;
+	for (i=0; i<4; i++)
+		vd[i] += vs[i];
+}
+
+
+// subtract two vectors to make another
+// vd = v0-v1
+void VecDifference(Vec vd, Vec v0, Vec v1)
+{
+	int i;
+	for (i=0; i<4; i++)
+		vd[i] = v0[i]-v1[i];
+}
+
+
+// subtract one vector from another
+// vd -= vs
+void VecSubtract(Vec vd, Vec vs)
+{
+	int i;
+	for (i=0; i<4; i++)
+		vd[i] -= vs[i];
+}
+
+
+// negate a vector
+// v = -v
+void VecNegate(Vec v)
+{
+	int i;
+	for (i=0; i<4; i++)
+		v[i] = -v[i];
+}
+
+
+// multiply a vector by a matrix
+// v = v * M
+void VecTransform(Vec v, Mat M)
+{
+	int i,j;
+	float sum;
+	Vec TempVec;
+	for (i=0; i<4; i++)
+	{
+		sum = 0.0;
+		for (j=0; j<4; j++)
+			sum += v[j]*M[j][i];
+		TempVec[i] = sum;
+	}
+	VecCopy(v,TempVec);
+}
+
+
+// product of a vector by a matrix
+// vd = vs * M
+void VecTransformed(Vec vd, Vec vs, Mat M)
+{
+	int i,j;
+	float sum;
+	for (i=0; i<4; i++)
+	{
+		sum = 0.0;
+		for (j=0; j<4; j++)
+			sum += vs[j]*M[j][i];
+		vd[i] = sum;
+	}
+}
+
+
+// rotate a vector through an angle about an axis
+void VecRotate(Vec v, float Angle, char Axis)
+{
+	Mat M;
+	MatRotation(M, Angle, Axis);
+	VecTransform(v, M);
+}
+
+
+// normalise a vector
+// v = v / |v|
+void VecNormalise(Vec v)
+{
+	int i;
+	float RecipLength = 1.0 / sqrtf(v[0]*v[0] + v[1]*v[1] + v[2]*v[2] + v[3]*v[3]);
+	for (i=0; i<4; i++)
+		v[i] *= RecipLength;
+}
+
+
+// scale a vector
+// v *= k
+void VecScale(Vec v, float k)
+{
+	int i;
+	for (i=0; i<4; i++)
+		v[i] *= k;
+}
+
+
+// scaled vector
+// vd = k*vs
+void VecScaled(Vec vd, float k, Vec vs)
+{
+	int i;
+	for (i=0; i<4; i++)
+		vd[i] = k*vs[i];
+}
+
+
+// add scaled vector
+// vd += k*vs
+void VecAddScaled(Vec vd, float k, Vec vs)
+{
+	int i;
+	for (i=0; i<4; i++)
+		vd[i] += k*vs[i];
+}
+
+
+// weighted mean of 2 vectors
+// v = (k0*v0 + k1*v1)/(k0+k1)
+void VecWeightedMean2(Vec v, float k0, Vec v0, float k1, Vec v1)
+{
+	Vec Temp;
+	VecScaled(v,k0,v0);
+	VecScaled(Temp,k1,v1);
+	VecAdd(v,Temp);
+	VecScale(v,1.0f/(k0+k1));
+}
+
+
+// weighted mean of 3 vectors
+// v = (k0*v0 + k1*v1 + k2*v2)/(k0+k1+k2)
+void VecWeightedMean3(Vec v, float k0, Vec v0, float k1, Vec v1, float k2, Vec v2)
+{
+	Vec Temp;
+	VecScaled(v,k0,v0);
+	VecScaled(Temp,k1,v1);
+	VecAdd(v,Temp);
+	VecScaled(Temp,k2,v2);
+	VecAdd(v,Temp);
+	VecScale(v,1.0f/(k0+k1+k2));
+}
+
+
+// cross product
+void CrossProduct(Vec vd, Vec v0, Vec v1)
+{
+	vd[0] = v0[1]*v1[2] - v0[2]*v1[1];
+	vd[1] = v0[2]*v1[0] - v0[0]*v1[2];
+	vd[2] = v0[0]*v1[1] - v0[1]*v1[0];
+}
+
+
+// dot product (3 components)
+float DotProduct3(Vec v0, Vec v1)
+{
+	return v0[0]*v1[0] + v0[1]*v1[1] + v0[2]*v1[2];
+}
+
+
+// dot product (4 components)
+float DotProduct4(Vec v0, Vec v1)
+{
+	return v0[0]*v1[0] + v0[1]*v1[1] + v0[2]*v1[2] + v0[3]*v1[3];
+}
+
+
+// length of a vector (3 components)
+float VecLength3(Vec v)
+{
+	return sqrtf(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
+}
+
+
+// copy a matrix
+// Md = Ms
+//void MatCopy(Mat Md, Mat Ms)
+//{
+//    int i,j;
+//    for (i=0; i<4; i++)
+//        for (j=0; j<4; j++)
+//            Md[i][j] = Ms[i][j];
+//	*(Mth::Matrix*)Md = *(Mth::Matrix*)Ms;
+//}
+
+
+// set a matrix
+// M = ((m00,m01,m02,m03), (m10,m11,m12,m13), (m20,m21,m22,m23), (m30,m31,m32,m33))
+void MatSet(Mat M, float m00, float m01, float m02, float m03,
+				   float m10, float m11, float m12, float m13,
+				   float m20, float m21, float m22, float m23,
+				   float m30, float m31, float m32, float m33)
+{
+	M[0][0]=m00, M[0][1]=m01, M[0][2]=m02, M[0][3]=m03;
+	M[1][0]=m10, M[1][1]=m11, M[1][2]=m12, M[1][3]=m13;
+	M[2][0]=m20, M[2][1]=m21, M[2][2]=m22, M[2][3]=m23;
+	M[3][0]=m30, M[3][1]=m31, M[3][2]=m32, M[3][3]=m33;
+}
+
+
+
+
+// zero a matrix
+// set M to 4x4 zero matrix
+void MatZero(Mat M)
+{
+    int i,j;
+    for (i=0; i<4; i++)
+        for (j=0; j<4; j++)
+            M[i][j] = 0.0;
+}
+
+
+// set identity matrix
+// set M to 4x4 identity
+void MatIdentity(Mat M)
+{
+    int i,j;
+    for (i=0; i<4; i++)
+        for (j=0; j<4; j++)
+            M[i][j] = (i==j) ? 1.0 : 0.0;
+}
+
+
+// transpose a matrix
+// M = MT
+void MatTranspose(Mat M)
+{
+	float temp;
+	int i,j;
+	for (i=0; i<4; i++)
+		for (j=i+1; j<4; j++)
+			temp=M[i][j], M[i][j]=M[j][i], M[j][i]=temp;
+}
+
+
+// transpose a matrix
+// Md = MsT
+void MatTransposed(Mat Md, Mat Ms)
+{
+	int i,j;
+	for (i=0; i<4; i++)
+		for (j=0; j<4; j++)
+			Md[i][j]=Ms[j][i];
+}
+
+
+// make a diagonal matrix from the elements of v
+void MatDiagonal(Mat M, Vec v)
+{
+	int i;
+	MatZero(M);
+	for (i=0; i<4; i++)
+		M[i][i] = v[i];
+}
+
+
+// scale all elements of a matrix
+void MatScale(Mat M, float k)
+{
+	int i,j;
+	for (i=0; i<4; i++)
+		for (j=0; j<4; j++)
+			M[i][j] *= k;
+}
+
+
+// set M to matrix for rotation through Angle (radians) about Axis ('x','y' or 'z')
+void MatRotation(Mat M, float Angle, char Axis)
+{
+    float s,c;
+    int i0=(int)((Axis+1-'x'))%3, i1=(int)((Axis+2-'x'))%3;
+    s=sinf(Angle), c=cosf(Angle);
+    MatIdentity(M);
+    M[i0][i0] = M[i1][i1] = c;
+    M[i1][i0] = -(M[i0][i1] = s);
+}
+
+
+// set M to matrix for translation by v
+void MatTranslation(Mat M, Vec v)
+{
+    MatIdentity(M);
+    VecCopy(M[3],v);
+}
+
+
+// multiply two matrices to make another
+// set AB = A * B
+void MatProduct(Mat AB, Mat A, Mat B)
+{
+	
+	#if 1	
+	sceVu0MulMatrix((sceVu0FVECTOR*)AB,(sceVu0FVECTOR*)B,(sceVu0FVECTOR*)A);   
+	return;
+	#else 
+	
+	float sum;
+    int i,j,k;
+    for (i=0; i<4; i++)
+        for (j=0; j<4; j++)
+        {
+            sum=0.0;
+            for (k=0; k<4; k++)
+                sum += A[i][k]*B[k][j];
+            AB[i][j] = sum;
+        }
+	#endif		
+}
+
+
+// premulitplication of one matrix by another
+// A = B * A
+void MatPremultiply(Mat A, Mat B)
+{
+    Mat TempMat;
+    MatProduct(TempMat,B,A);
+    MatCopy(A,TempMat);
+}
+
+
+// postmultiplication of one matrix by another
+// A = A * B
+void MatPostmultiply(Mat A, Mat B)
+{
+    Mat TempMat;
+    MatProduct(TempMat,A,B);
+    MatCopy(A,TempMat);
+}
+
+
+// invert a 4x4 matrix
+// Md = inv(Ms)
+// Note: this is not a general matrix invert: it only works for matrices which incorporate rotation and translation.
+void MatInverse(Mat Md, Mat Ms)
+{
+	int i,j;
+	Mat InvT, InvR;
+	// set up negative of original translation
+	MatIdentity(InvT);
+	InvT[3][0] = -Ms[3][0];
+	InvT[3][1] = -Ms[3][1];
+	InvT[3][2] = -Ms[3][2];
+	// set up transpose of original rotation
+	MatIdentity(InvR);
+	for (i=0; i<3; i++)
+		for (j=0; j<3; j++)
+			InvR[i][j] = Ms[j][i];
+	// take product
+	MatProduct(Md,InvT,InvR);
+}
+
+
+// build a matrix from 3 Euler angles
+// M = Rz(Angles[2]) * Rx(Angles[0]) * Ry(Angles[1])
+void MatEuler(Mat M, Vec Angles)
+{
+	Mat Temp;
+	MatRotation(M, Angles[2], 'z');
+	MatRotation(Temp, Angles[0], 'x');
+	MatPostmultiply(M, Temp);
+	MatRotation(Temp, Angles[1], 'y');
+	MatPostmultiply(M, Temp);
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/mikemath.h b/Code/Gfx/NGPS/NX/mikemath.h
new file mode 100644
index 0000000..27e807e
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/mikemath.h
@@ -0,0 +1,64 @@
+#ifndef __MIKEMATH_H
+#define __MIKEMATH_H
+
+#include 
+#include 
+
+namespace NxPs2
+{
+
+// typedefs
+typedef float Vec[4] nAlign(128);		// Structurally equivalent to Mth::Vector
+typedef float Mat[4][4] nAlign(128);	// Structurally equivalent to Mth::Matrix
+
+// function prototypes
+void VecCopy(Vec vd, Vec vs);
+void VecSet(Vec v, float x, float y, float z, float w);
+void VecSum(Vec vd, Vec v0, Vec v1);
+void VecAdd(Vec vd, Vec vs);
+void VecDifference(Vec vd, Vec v0, Vec v1);
+void VecSubtract(Vec vd, Vec vs);
+void VecNegate(Vec v);
+void VecTransform(Vec v, Mat M);
+void VecTransformed(Vec vd, Vec vs, Mat M);
+void VecRotate(Vec v, float Angle, char Axis);
+void VecNormalise(Vec v);
+void VecScale(Vec v, float k);
+void VecScaled(Vec vd, float k, Vec vs);
+void VecAddScaled(Vec vd, float k, Vec vs);
+void VecWeightedMean2(Vec v, float k0, Vec v0, float k1, Vec v1);
+void VecWeightedMean3(Vec v, float k0, Vec v0, float k1, Vec v1, float k2, Vec v2);
+void CrossProduct(Vec vd, Vec v0, Vec v1);
+float DotProduct3(Vec v0, Vec v1);
+float DotProduct4(Vec v0, Vec v1);
+float VecLength3(Vec v);
+void MatCopy(Mat Md, Mat Ms);
+void MatSet(Mat M, float m00, float m01, float m02, float m03,
+				   float m10, float m11, float m12, float m13,
+				   float m20, float m21, float m22, float m23,
+				   float m30, float m31, float m32, float m33);
+void MatZero(Mat M);
+void MatIdentity(Mat M);
+void MatTranspose(Mat M);
+void MatTransposed(Mat Md, Mat Ms);
+void MatDiagonal(Mat M, Vec v);
+void MatScale(Mat M, float k);
+void MatRotation(Mat M, float Angle, char Axis);
+void MatTranslation(Mat M, Vec v);
+void MatProduct(Mat AB, Mat A, Mat B);
+void MatPremultiply(Mat A, Mat B);
+void MatPostmultiply(Mat A, Mat B);
+void MatInverse(Mat Md, Mat Ms);
+void MatEuler(Mat M, Vec Angles);
+
+
+inline void MatCopy(Mat Md, Mat Ms)
+{
+	*(Mth::Matrix*)Md = *(Mth::Matrix*)Ms;
+}
+
+
+} // namespace NxPs2
+					  
+					  
+#endif // __MIKEMATH_H
diff --git a/Code/Gfx/NGPS/NX/nx_init.cpp b/Code/Gfx/NGPS/NX/nx_init.cpp
new file mode 100644
index 0000000..fa2fcc3
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/nx_init.cpp
@@ -0,0 +1,434 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "vu1code.h"
+#include "vu0code.h"
+#include "gif.h"
+#include "gs.h"
+#include "dmacalls.h"
+#include "pcrtc.h"
+#include "render.h"
+#include "chars.h"
+#include "scene.h"
+#include "group.h"
+#include "interrupts.h"
+#include "switches.h"
+#include "instance.h"
+#include "geomnode.h"
+#include "texture.h"
+#include "resource.h"
+#include "fx.h"
+
+namespace NxPs2
+{
+
+extern	void	WaitForRendering();
+
+// Interrupts
+static	void	StartInterrupts();
+static	void	StopInterrupts();
+
+Mth::Matrix GmyMat[4];
+
+// Interrupt Handler IDs
+#if USE_INTERRUPTS
+	
+#if GIF_INTERRUPT
+static int sGifHandlerId = -1;
+#endif
+
+#if GS_INTERRUPT
+static int sGsHandlerId = -1;
+#endif
+
+#if VIF1_INTERRUPT
+static int sVif1HandlerId = -1;
+#endif
+
+#endif
+
+//////////////////////////////////////////////////////////////////
+// void InitialiseEngine(void)
+//
+// Sets up the PS2 engine, down to the hardware level
+//  - Resets the hardware, using sce library calls, setting video mode
+//  - sets up the PCRTC registers (display buffer conficuration, and read circuit anti-aliasing)
+//  - Initilizes memory for DMA buffers
+//  - Builds the standard DMA subroutines (clear VRAM, page flip, etc)
+//  - adds GS, VIF1 and GIF interrupts
+//  - creates an empty "scene" and an empty group as the default things to render
+
+
+void InitialiseEngine(void)
+{
+    // Mick:  Make it safe to call more than once
+    // Needed for a last minute loading screen hack TT#12480
+	static bool done = false;
+	if (done)
+	{
+		return;
+	}
+	done = true;
+	
+	// Initialize devices
+	sceDmaReset(1);
+	sceGsResetPath();
+    sceDevVu1PutDBit(1);	// sceGsResetPath will clear the D-bit, so need to switch it on
+    //sceDevVu0PutTBit(1);
+
+	// setup PCRTC registers - switch off display until vram has been cleared
+	SetupPCRTC(0, SCE_GS_FIELD);
+
+	// Clear resoruces
+	CSystemResources::sInitResources();
+
+	// allocate dma list memory (prebuilt & run-time) and subroutine table
+	dma::pPrebuiltBuffer = (uint8 *)Mem::Calloc(PREBUILT_DMA_BUFFER_SIZE/16,16);
+	Dbg_MsgAssert(dma::pPrebuiltBuffer, ("couldn't malloc memory for prebuilt dma buffer"));
+	
+	if (Config::GotExtraMemory())
+	{
+		// Allocate a dummy buffer so that the current heap's usage is the same
+		// as for when there is not extra memory.
+		dma::pDummyBuffer = (uint8 *)Mem::Calloc(NON_DEBUG_DMA_BUFFER_SIZE/16,16);
+		
+		// But for the actual runtime buffer allocate a massive buffer off the debug heap.
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+		dma::pRuntimeBuffer = (uint8 *)Mem::Calloc(DEBUG_DMA_BUFFER_SIZE/16,16);
+		Mem::Manager::sHandle().PopContext();
+		
+		Dbg_MsgAssert(dma::pRuntimeBuffer, ("couldn't malloc memory for runtime dma buffer"));
+		// set runtime dma list pointers (they are double buffered, using half the buffer each)
+		dma::pList[0] = dma::pRuntimeBuffer;
+		dma::pList[1] = dma::pRuntimeBuffer + DEBUG_DMA_BUFFER_SIZE/2;
+	}
+	else
+	{
+		dma::pRuntimeBuffer = (uint8 *)Mem::Calloc(NON_DEBUG_DMA_BUFFER_SIZE/16,16);
+		Dbg_MsgAssert(dma::pRuntimeBuffer, ("couldn't malloc memory for runtime dma buffer"));
+		// set runtime dma list pointers (they are double buffered, using half the buffer each)
+		dma::pList[0] = dma::pRuntimeBuffer;
+		dma::pList[1] = dma::pRuntimeBuffer + NON_DEBUG_DMA_BUFFER_SIZE/2;
+	}
+	dma::size = NON_DEBUG_DMA_BUFFER_SIZE/2;  // normal sized (half the buffer)
+	
+		
+	dma::Gosubs = (uint64 *)Mem::Calloc(NUM_SUBROUTINES, sizeof(uint64));
+	Dbg_MsgAssert(dma::Gosubs, ("couldn't malloc memory for dma gosub table"));
+	//Subroutines = (uint8 **)Mem::Calloc(256, sizeof(uint8 *));
+	//Dbg_MsgAssert(Subroutines, ("couldn't malloc memory for dma gosub table"));
+
+	// initialise dma pointer for use by BuildDmaSubroutines
+	dma::pLoc = dma::pPrebuiltBuffer;
+
+	// prebuild dma subroutines (into dma::pPrebuiltBuffer)
+	BuildDmaSubroutines();
+
+	// Enable interrupts
+	StartInterrupts();
+
+	// wait for v-sync
+	sceGsSyncV(0);
+
+	// clear vram
+	FlushCache(WRITEBACK_DCACHE);
+	*D1_QWC  = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+	*D1_TADR = dma::Gosubs[CLEAR_VRAM] >> 32;
+	*D1_CHCR = 0x145;					// start transfer, tte=1, chain mode, from memory
+	sceGsSyncPath(0, 0);
+
+	// setup PCRTC registers - vram is cleared so switch on display
+	SetupPCRTC(1, SCE_GS_FIELD);
+
+	// upload vu0 code
+	*D0_QWC  = ((uint8 *)MPGEnd0-(uint8 *)MPGStart0+15)/16;
+	*D0_MADR = (uint)MPGStart0;
+	*D0_CHCR = 0x100;					// start transfer, normal mode
+	sceGsSyncPath(0, 0);
+
+	// empty scene list
+	sScene::pHead = NULL;
+
+	// create the shadow group
+	sGroup::pShadow = (sGroup *)Mem::Malloc(sizeof(sGroup));
+	memset(sGroup::pShadow,0,sizeof(sGroup));
+	Dbg_MsgAssert(sGroup::pShadow, ("couldn't allocate shadow group\n"));
+	sGroup::pShadow->Priority = 1500.001f;
+	sGroup::pShadow->pMeshes = NULL;
+	sGroup::pShadow->NumMeshes = 0;
+	#if STENCIL_SHADOW
+	sGroup::pShadow->VramStart = 0x3740;
+	#else
+	sGroup::pShadow->VramStart = 0x3E00;
+	#endif
+	sGroup::pShadow->VramEnd   = 0x4000;
+	sGroup::pShadow->flags = 0;
+	sGroup::pShadow->profile_color = 0xFFFFFF;		// bright white = shadow group
+
+	// create the fog group
+	sGroup::pFog = (sGroup *)Mem::Malloc(sizeof(sGroup));
+	memset(sGroup::pFog,0,sizeof(sGroup));
+	Dbg_MsgAssert(sGroup::pFog, ("couldn't allocate fog group\n"));
+	sGroup::pFog->Priority = 9999.0f;
+	sGroup::pFog->pMeshes = NULL;
+	sGroup::pFog->NumMeshes = 0;
+	sGroup::pFog->VramStart = 0x3000;
+	sGroup::pFog->VramEnd   = 0x4000;
+	sGroup::pFog->flags = 0;
+	sGroup::pFog->pScene = NULL;
+	sGroup::pFog->profile_color = 0x303030;			// dark grey = fog group
+	
+	// create the particle group
+	sGroup::pParticles = (sGroup *)Mem::Malloc(sizeof(sGroup));
+	memset(sGroup::pParticles,0,sizeof(sGroup));
+	Dbg_MsgAssert(sGroup::pParticles, ("couldn't allocate particle group\n"));
+	sGroup::pParticles->Priority = 10000.0f;
+	sGroup::pParticles->pMeshes = NULL;
+	sGroup::pParticles->NumMeshes = 0;
+	sGroup::pParticles->VramStart = 0x3000;
+	sGroup::pParticles->VramEnd   = 0x4000;
+	sGroup::pParticles->flags = 0;
+	sGroup::pParticles->pScene = NULL;
+	sGroup::pParticles->profile_color = 0x0;		// black = particle group
+
+	// create epilogue group
+	sGroup::pEpilogue = (sGroup *)Mem::Malloc(sizeof(sGroup));
+	memset(sGroup::pEpilogue,0,sizeof(sGroup));
+	Dbg_MsgAssert(sGroup::pEpilogue, ("couldn't allocate epilogue group\n"));
+	sGroup::pEpilogue->Priority = 1000000;
+	sGroup::pEpilogue->pMeshes = NULL;
+	sGroup::pEpilogue->NumMeshes = 0;
+	sGroup::pEpilogue->VramStart = 0x0000;	// pretend it uses all of vram
+	sGroup::pEpilogue->VramEnd   = 0x4000;	// so there's no buffer address worries
+	sGroup::pEpilogue->profile_color = 0x800000;		// blue = epilogue group
+
+	// link predefined groups together
+	sGroup::pShadow->pNext = sGroup::pFog;
+	sGroup::pFog->pNext = sGroup::pParticles;
+	sGroup::pParticles->pNext = sGroup::pEpilogue;
+	sGroup::pEpilogue->pNext = NULL;
+	sGroup::pShadow->pPrev = NULL;
+	sGroup::pFog->pPrev = sGroup::pShadow;
+	sGroup::pParticles->pPrev = sGroup::pFog;
+	sGroup::pEpilogue->pPrev = sGroup::pParticles;
+	sGroup::pHead = sGroup::pShadow;
+	sGroup::pTail = sGroup::pEpilogue;
+
+	// initialise vram group buffering scheme
+	sGroup::VramBufferBase = 0x2BC0;
+
+	// initialise font vram base pointer
+	FontVramStart = FontVramBase = 0x2BC0;	// a cheat
+
+	// initialise empty font list
+	pFontList = NULL;
+
+
+
+	// activate the world!
+	CGeomNode::sWorld.SetActive(true);
+
+	// set up fog palette
+//	Fx::SetupFogPalette(0x40806060, 10.0f);
+}
+
+void 	AllocateExtraDMA(bool allocate)
+{
+	static void * s_extra = NULL;
+	static void * old_pList1;
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+
+	if (allocate)
+	{
+		Dbg_MsgAssert(!s_extra,("Tried to allocate extra DMA when already allocated"));
+		old_pList1 = dma::pList[1];
+		s_extra = (uint8 *)Mem::Calloc(NON_DEBUG_DMA_BUFFER_SIZE/16,16);	  // same size as the entire double buffer
+		dma::pList[1] = (uint8 *)s_extra;
+		dma::size = NON_DEBUG_DMA_BUFFER_SIZE;  // double sized
+	}
+	else
+	{
+		if (s_extra)
+		{
+			dma::pList[1] = (uint8 *)old_pList1;
+			Mem::Free(s_extra);
+			s_extra = NULL;
+			dma::size = NON_DEBUG_DMA_BUFFER_SIZE/2;  // normal sized
+		}
+	}
+	
+	dma::pLoc = 0 ;  	// reset pLoc as it might not be valid.  This will catch this function being called in the wrong place
+	
+	Mem::Manager::sHandle().PopContext();	
+}
+
+
+//////////////////////////////////////////////////////////////////
+// void ResetEngine(void)
+//
+// Sets up the PS2 engine, down to the hardware level
+//  - Resets the hardware, using sce library calls, setting video mode
+//  - sets up the PCRTC registers (display buffer conficuration, and read circuit anti-aliasing)
+//  - clears VRAM, which also sets the TEXA register
+//  - resets GS, VIF1 and GIF interrupts
+
+void ResetEngine(void)
+{
+	// initialize devices
+	sceDmaReset(1);
+	sceGsResetPath();
+    sceDevVu1PutDBit(1);	// sceGsResetPath will clear the D-bit, so need to switch it on
+    //sceDevVu0PutTBit(1);
+
+	// Don't call sceGsResetGraph() anymore.  It interferes with SetupPCRTC().
+	//// re-initialize the GS
+	//if (Config::NTSC())
+	//{
+	//	sceGsResetGraph(0, SCE_GS_INTERLACE, SCE_GS_NTSC, SCE_GS_FIELD);
+	//} else {
+	//	sceGsResetGraph(0, SCE_GS_INTERLACE, SCE_GS_PAL, SCE_GS_FIELD);
+	//}
+
+	// setup PCRTC registers (don't need this call anymore; the last call is all that is necessary)
+	//SetupPCRTC(0, SCE_GS_FIELD);
+
+	// restart interrupts
+	StartInterrupts();
+
+	// wait for v-sync
+	sceGsSyncV(0);
+
+	// clear vram (Garrett: This is the ONLY place that TEXA is ever set!  Caused many weird bugs if not called.)
+	FlushCache(WRITEBACK_DCACHE);
+	*D1_QWC  = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+	*D1_TADR = dma::Gosubs[CLEAR_VRAM] >> 32;
+	*D1_CHCR = 0x145;					// start transfer, tte=1, chain mode, from memory
+	sceGsSyncPath(0, 0);
+
+	// setup PCRTC registers - vram is cleared so switch on display
+	SetupPCRTC(1, SCE_GS_FIELD);
+
+}
+
+//////////////////////////////////////////////////////////////////
+// void SuspendEngine(void)
+//
+// Suspends the PS2 engine by waiting for any rendering to finish
+// and then shuts down the interrupts.
+
+void SuspendEngine(void)
+{
+	// Let the engine finish what it was doing first
+	WaitForRendering();
+
+	// stop interrupts
+	StopInterrupts();
+}
+
+
+//////////////////////////////////////////////////////////////////
+// void StartInterrupts(void)
+//
+// Starts all the interrupts for the PS2 engine.
+
+static void StartInterrupts(void)
+{
+	#if USE_INTERRUPTS
+	
+	#if GIF_INTERRUPT
+    // install interrupt handler for texture upload completion
+	if ((sGifHandlerId = AddDmacHandler(DMAC_GIF, GifHandler, 0)) == -1)
+	{
+		printf("Couldn't register dmac handler\n");
+		exit(1);
+	}
+	*D_STAT = 1 << DMAC_GIF;
+	EnableDmac(DMAC_GIF);
+	#endif
+
+	#if GS_INTERRUPT
+    // install interrupt handler for render completion
+	if ((sGsHandlerId = AddIntcHandler(INTC_GS, GsHandler, 0)) == -1)
+	{
+		printf("Couldn't register GS handler\n");
+		exit(1);
+	}
+	*GS_IMR = PackIMR(0,1,1,1);		// SIGNAL, FINISH, HSync, VSync
+	EnableIntc(INTC_GS);
+	#endif
+	
+	#if VIF1_INTERRUPT
+    // install interrupt handler for render completion
+	if ((sVif1HandlerId = AddIntcHandler(INTC_VIF1, Vif1Handler, 0)) == -1)
+	{
+		printf("Couldn't register vif1 handler\n");
+		exit(1);
+	}
+	*VIF1_ERR = 0;
+	EnableIntc(INTC_VIF1);
+	#endif
+
+	// set intermittent mode transfer
+	*GIF_MODE = 0x0;
+	#endif
+
+}
+
+
+
+//////////////////////////////////////////////////////////////////
+// void StopInterrupts(void)
+//
+// Stops all the interrupts for the PS2 engine.
+
+static void StopInterrupts(void)
+{
+	#if USE_INTERRUPTS
+	
+	#if GIF_INTERRUPT
+    // remove interrupt handler for texture upload completion
+	DisableDmac(DMAC_GIF);
+	if (sGifHandlerId >= 0)
+	{
+		RemoveDmacHandler(DMAC_GIF, sGifHandlerId);
+		sGifHandlerId = -1;
+	}
+	#endif
+
+	#if GS_INTERRUPT
+    // remove interrupt handler for render completion
+	DisableIntc(INTC_GS);
+	if (sGsHandlerId >= 0)
+	{
+		RemoveIntcHandler(INTC_GS, sGsHandlerId);
+		sGsHandlerId = -1;
+	}
+	#endif
+	
+	#if VIF1_INTERRUPT
+    // remove interrupt handler for render completion
+	DisableIntc(INTC_VIF1);
+	if (sVif1HandlerId >= 0)
+	{
+		RemoveIntcHandler(INTC_VIF1, sVif1HandlerId);
+		sVif1HandlerId = -1;
+	}
+	#endif
+
+	#endif
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/nx_init.h b/Code/Gfx/NGPS/NX/nx_init.h
new file mode 100644
index 0000000..5363649
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/nx_init.h
@@ -0,0 +1,16 @@
+#ifndef __NX_INIT_H
+#define __NX_INIT_H
+
+namespace NxPs2
+{
+
+void InitialiseEngine(void);
+void ResetEngine(void);
+void SuspendEngine(void);
+void AllocateExtraDMA(bool allocate);
+
+} // namespace NxPs2
+
+
+#endif // __NX_INIT_H
+
diff --git a/Code/Gfx/NGPS/NX/occlude.cpp b/Code/Gfx/NGPS/NX/occlude.cpp
new file mode 100644
index 0000000..d3cb3c5
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/occlude.cpp
@@ -0,0 +1,569 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		occlude.cpp												**
+**																			**
+**	Created:		04/02/02	-	dc										**
+**																			**
+**	Description:	Occlusion testing code									**
+**																			**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+#include 
+#include 
+//#include 
+#include "occlude.h"
+#include "resource.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+//extern D3DXMATRIX *p_bbox_transform;
+
+namespace NxPs2
+{
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static bool sCopiedOccludersOffline = false;		// tells that occluders have been copied to a fast ram and the
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+uint32			NumOcclusionPolys			= 0;
+uint32			NextOcclusionPolyToCheck	= 0;
+sOcclusionPoly	OcclusionPolys[MAX_OCCLUSION_POLYS];
+
+uint32		sOccluder::NumOccluders				= 0;
+sOccluder	sOccluder::Occluders[MAX_OCCLUDERS];
+bool		sOccluder::sUseVU0 = false;
+bool		sOccluder::sUseScratchPad = false;
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::add_to_stack( sOcclusionPoly *p_poly )
+{
+	if( NumOccluders < MAX_OCCLUDERS )
+	{
+		Occluders[NumOccluders].p_poly	= p_poly;
+		p_poly->in_use					= true;
+		++NumOccluders;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int cmp( const void *p1, const void *p2 )
+{
+	// Zero the score for any occlusion poly that is no longer available. This will force it out of the stack.
+	if(((sOccluder*)p1)->p_poly->available == false )
+		((sOccluder*)p1)->score = 0;
+
+	if(((sOccluder*)p2)->p_poly->available == false )
+		((sOccluder*)p2)->score = 0;
+
+	return((sOccluder*)p1)->score < ((sOccluder*)p2)->score ? 1 : ((sOccluder*)p1)->score > ((sOccluder*)p2)->score ? -1 : 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::sort_stack( void )
+{
+	qsort( Occluders, NumOccluders, sizeof( sOccluder ), cmp );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::tidy_stack( void )
+{
+	if( NumOccluders > 0 )
+	{
+		// Sort in descending score order.
+		sort_stack();
+		
+
+		int total = 0;
+		int demoted = 0;
+		// Count backwards so we know we get all the bad occluders.
+		for( int i = NumOccluders - 1; i >= 0; --i )
+		{
+			// If we have hit an occluder with zero meshes culled, cut off the stack at this point.
+			// also, if the stack is full, then demote the lowest scoring 4
+			if( Occluders[i].score == 0  || i > (int)(MAX_OCCLUDERS-MIN_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME))
+			{
+				// No longer using this poly.
+				Occluders[i].p_poly->in_use = false;
+
+				// One less occluder to worry about.
+				--NumOccluders;
+				demoted++;
+			}
+			else
+			{
+				total += Occluders[i].score;
+				// Reset the good occluders.
+				Occluders[i].score = 0;
+			}
+		}
+//		printf ("\nOccluded %d objects with %d Occluders (demoted %d)\n",total,NumOccluders,demoted);
+	}
+	
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to add an occlusion poly to the list. Likely to be called */
+/* as geometry is spooled in.                                     */
+/*                                                                */
+/******************************************************************/
+void AddOcclusionPoly( Mth::Vector &v0, Mth::Vector &v1, Mth::Vector &v2, Mth::Vector &v3, uint32 checksum )
+{
+//	printf (" Adding occlusion polygons, currently %d\n",NumOcclusionPolys);
+	Dbg_MsgAssert( NumOcclusionPolys < MAX_OCCLUSION_POLYS,("Too many (%d) occlusion polygons",NumOcclusionPolys) );	
+	
+	OcclusionPolys[NumOcclusionPolys].in_use	= false;
+	OcclusionPolys[NumOcclusionPolys].available	= true;
+	OcclusionPolys[NumOcclusionPolys].checksum	= checksum;
+	OcclusionPolys[NumOcclusionPolys].verts[0]	= v0;
+	OcclusionPolys[NumOcclusionPolys].verts[1]	= v1;
+	OcclusionPolys[NumOcclusionPolys].verts[2]	= v2;
+	OcclusionPolys[NumOcclusionPolys].verts[3]	= v3;
+	OcclusionPolys[NumOcclusionPolys].normal	= Mth::CrossProduct( v1 - v0, v3 - v0 );
+	OcclusionPolys[NumOcclusionPolys].normal.Normalize();
+	
+	++NumOcclusionPolys;
+
+    sCopiedOccludersOffline = false;		// current scores now invalid
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to toggle whether an occlusion poly can be used or not	  */
+/*                                                                */
+/******************************************************************/
+void EnableOcclusionPoly( uint32 checksum, bool available )
+{
+	for( uint32 i = 0; i < NumOcclusionPolys; ++i )
+	{
+		if( OcclusionPolys[i].checksum == checksum )
+		{
+			OcclusionPolys[i].available	= available;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to clear all occlusion polys (when a level is unloaded)   */
+/*                                                                */
+/******************************************************************/
+void RemoveAllOcclusionPolys( void )
+{
+	Dbg_Assert( NumOcclusionPolys < MAX_OCCLUSION_POLYS );	
+	
+ 	sOccluder::NumOccluders		= 0;
+	NumOcclusionPolys			= 0;
+	NextOcclusionPolyToCheck	= 0;
+
+    sCopiedOccludersOffline = false;		// current scores now invalid
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CheckForOptimalOccluders( Mth::Vector &cam_pos, Mth::Vector &view_direction )
+{
+	if( NumOcclusionPolys > 0 )
+	{
+		uint32 added	= 0;
+		uint32 checked	= 0;
+
+		while( added < MAX_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME )
+		{
+			// Given the current position of the camera, check through the unused occlusion polys to see if one scores higher
+			// than the lowest scoring occlusion poly in use.
+			sOcclusionPoly *poly_to_check = &OcclusionPolys[NextOcclusionPolyToCheck++];
+			if(( !poly_to_check->in_use ) && ( poly_to_check->available ))
+			{
+				sOccluder::add_to_stack( poly_to_check );
+				++added;
+			}
+			++checked;
+
+			// Ensure we are always checking within bounds.
+			if( NextOcclusionPolyToCheck >= NumOcclusionPolys )
+			{
+				NextOcclusionPolyToCheck = 0;
+			}
+
+			// Quit out if we have less available occluders than spaces to fill.
+			if( checked >= NumOcclusionPolys )
+			{
+				break;
+			}
+		}
+	}
+}
+
+
+//char scratch[16384];
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void BuildOccluders( Mth::Vector *p_cam_pos )
+{
+#ifdef	OCCLUDER_USES_VU0_MACROMODE
+	if (sCopiedOccludersOffline)
+	{
+		sOccluder * p_occluder = &sOccluder::Occluders[0];
+		uint128 * p_vu0_mem = (uint128*)0x11004060;     // Start of first score
+
+		for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
+		{
+			p_occluder->score = *((uint32*) (p_vu0_mem));
+			p_occluder++;
+			p_vu0_mem += 6;
+		}
+
+		sCopiedOccludersOffline = false;	// we are now up-to-date
+	}
+#endif
+	for( uint32 i = 0; i < NumOcclusionPolys; ++i )
+	{
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[0], OcclusionPolys[i].verts[1], 0xFFFFFFFFUL, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[1], OcclusionPolys[i].verts[2], 0xFFFFFFFFUL, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[2], OcclusionPolys[i].verts[3], 0xFFFFFFFFUL, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[3], OcclusionPolys[i].verts[0], 0xFFFFFFFFUL, 0, 1 );
+	}
+	
+	// Tidy up from last frame.
+	sOccluder::tidy_stack();
+	
+	// Cyclically add more occluders for checking.
+	CheckForOptimalOccluders( *p_cam_pos, *p_cam_pos );
+	
+	// Build all 5 planes for each occluder.
+	Mth::Vector u0, u1, p;
+
+	// The order in which the verts are used to build tha planes depends upon where the camera is in relation to the occlusion
+	// poly. We use the default order when the viewpoint is on the side of the poly on which the default poly normal faces.
+	for( uint32 i = 0; i < sOccluder::NumOccluders; ++i )
+	{
+		sOcclusionPoly *p_poly = sOccluder::Occluders[i].p_poly;
+		if( Mth::DotProduct( *p_cam_pos - p_poly->verts[0], p_poly->normal ) >= 0.0f )
+		{
+			// Start with left (0->1->2)...
+			u0						= p_poly->verts[0] - *p_cam_pos;
+			u1						= p_poly->verts[1] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[0]	= p;
+
+			// ...then right (0->3->4)...
+			u0						= p_poly->verts[2] - *p_cam_pos;
+			u1						= p_poly->verts[3] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[1]	= p;
+
+			// ...then top (0->2->3)...
+			u0						= p_poly->verts[1] - *p_cam_pos;
+			u1						= p_poly->verts[2] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[2]	= p;
+
+			// ...then bottom (0->4->1)...
+			u0						= p_poly->verts[3] - *p_cam_pos;
+			u1						= p_poly->verts[0] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[3]	= p;
+
+			// ...then front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
+			// are considered occluded. (1->3->4)...
+			u0						= p_poly->verts[2] - p_poly->verts[0];
+			u1						= p_poly->verts[3] - p_poly->verts[0];
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, p_poly->verts[0] );
+			sOccluder::Occluders[i].planes[4]	= p;
+		}
+		else
+		{
+			// Start with left (0->2->1)...
+			u0						= p_poly->verts[1] - *p_cam_pos;
+			u1						= p_poly->verts[0] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[0]	= p;
+
+			// ...then right (0->4->3)...
+			u0						= p_poly->verts[3] - *p_cam_pos;
+			u1						= p_poly->verts[2] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[1]	= p;
+
+			// ...then top (0->3->2)...
+			u0						= p_poly->verts[2] - *p_cam_pos;
+			u1						= p_poly->verts[1] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[2]	= p;
+
+			// ...then bottom (0->1->4)...
+			u0						= p_poly->verts[0] - *p_cam_pos;
+			u1						= p_poly->verts[3] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[3]	= p;
+
+			// ...then front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
+			// are considered occluded. (1->4->3)...
+			u0						= p_poly->verts[3] - p_poly->verts[0];
+			u1						= p_poly->verts[2] - p_poly->verts[0];
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= -Mth::DotProduct( p, p_poly->verts[0] );
+			sOccluder::Occluders[i].planes[4]	= p;
+		}
+	}
+
+
+	if (sOccluder::sUseScratchPad)
+	{
+		// Copy the occlusion polygons to the scratchpad
+		// as we will read them a zillion times during rendering
+		sOccluder * p_occluder = &sOccluder::Occluders[0];
+		sOccluder * p_scratch = (sOccluder*)0x70000000;
+	//	sOccluder * p_scratch = (sOccluder*)scratch;
+		for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
+		{
+			*p_scratch = *p_occluder;
+			p_occluder++;
+			p_scratch++;
+		}
+		Dbg_MsgAssert((void*)p_scratch < (void*)0x70004000,("Scratchpad overflow"));  // 16k scratchpad.  Nice.
+	}
+
+#ifdef	OCCLUDER_USES_VU0_MACROMODE
+	sOccluder * p_occluder = &sOccluder::Occluders[0];
+	uint128 * p_vu0_mem = (uint128*)0x11004000;		// VU0 data memory
+
+	// Copies the number of occulders into X
+	*((uint32*) (p_vu0_mem++)) = sOccluder::NumOccluders;
+
+	for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
+	{
+		*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[0]));
+		*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[1]));
+		*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[2]));
+		*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[3]));
+		*(p_vu0_mem++) = *((uint128*) &(p_occluder->planes[4]));
+		*((uint32*) (p_vu0_mem++)) = p_occluder->score;
+		p_occluder++;
+	}
+
+	FlushCache(WRITEBACK_DCACHE);
+
+	Dbg_MsgAssert((void*)p_vu0_mem < (void*)0x11005000,("VU0 memory overflow"));  // 4k vu0 mem.  Nice.
+
+    sCopiedOccludersOffline = true;			// so we know to copy the scores back (technically, this should be in
+											// TestSphereAgainstOccluders(), but we only want to write this var once
+#endif
+
+
+
+
+#ifdef	OCCLUDER_USES_VU0_MICROMODE
+
+	// copy occlusion planes to VUMem0
+	sOccluder * p_occluder = &sOccluder::Occluders[0];
+// Mick: removed this to fix a warning in final build.
+//	float * p_vumem0 = (float *)0x11004000;		// VUMem0
+	Mth::Vector * p_plane;
+	asm ("viaddi vi01,vi00,0": : : "$8");
+	for( uint32 o = sOccluder::NumOccluders; o > 0 ; --o )
+	{
+		p_plane = &(p_occluder->planes[0]);
+		for( uint32 p = 5; p > 0; --p )
+		{
+			#if 0
+			*p_vumem0++ = p_plane->GetX();
+			*p_vumem0++ = p_plane->GetY();
+			*p_vumem0++ = p_plane->GetZ();
+			*p_vumem0++ = p_plane->GetW();
+			#else
+			// this way we don't need the FlushCache()
+			asm __volatile__("
+
+				lq		$8,0(%0)
+				qmtc2	$8,vf01
+				vsqi	vf01,(vi01++)
+
+			": : "r" (p_plane) : "$8");
+			#endif
+			p_plane++;
+		}
+		p_occluder++;
+	}
+// Mick - removed this assertions as it is no longer valid
+//	Dbg_MsgAssert((void*)p_vumem0 < (void*)0x11004FA0,("VUMem0 overflow"));		// 50 occluders max
+
+	// flush the cache so VU0 can work on the data now.... not needed anymore
+	//FlushCache(WRITEBACK_DCACHE);
+
+	// call vu0 microsubroutine to rearrange data into a special format
+	asm __volatile__("
+
+		#sync									# wait for write-back of the mem we just wrote to
+		lw			$8,(%0)						# t0 = num occluders
+		ctc2		$8,$vi01					# move it to vi01 in vu0
+		vcallms		InitialiseOccluders			# call microsubroutine to rearrange vu0 memory
+		vnop									# interlocking instruction, waits for vu0 completion
+	
+	": : "r" (&sOccluder::NumOccluders) : "$8");
+
+
+#endif
+
+
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool OccludeUseVU0()
+{
+	return sOccluder::sUseVU0 = CSystemResources::sRequestResource(CSystemResources::vVU0_MEMORY);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void OccludeDisableVU0()
+{
+	if (sOccluder::sUseVU0)
+	{
+		CSystemResources::sFreeResource(CSystemResources::vVU0_MEMORY);
+		sOccluder::sUseVU0 = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool OccludeUseScratchPad()
+{
+	return sOccluder::sUseScratchPad = CSystemResources::sRequestResource(CSystemResources::vSCRATCHPAD_MEMORY);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void OccludeDisableScratchPad()
+{
+	if (sOccluder::sUseScratchPad)
+	{
+		CSystemResources::sFreeResource(CSystemResources::vSCRATCHPAD_MEMORY);
+		sOccluder::sUseScratchPad = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace NxXbox
diff --git a/Code/Gfx/NGPS/NX/occlude.h b/Code/Gfx/NGPS/NX/occlude.h
new file mode 100644
index 0000000..fb26ce9
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/occlude.h
@@ -0,0 +1,68 @@
+#ifndef	__OCCLUDE_H__
+#define	__OCCLUDE_H__
+
+//#define	OCCLUDER_USES_VU0_MACROMODE
+#define	OCCLUDER_USES_VU0_MICROMODE
+
+
+namespace NxPs2
+{
+	void AddOcclusionPoly( Mth::Vector &v0, Mth::Vector &v1, Mth::Vector &v2, Mth::Vector &v3, uint32 checksum = 0 );
+	void EnableOcclusionPoly( uint32 checksum, bool available );
+	void RemoveAllOcclusionPolys( void );
+	void BuildOccluders( Mth::Vector *p_cam_pos );
+	bool TestSphereAgainstOccluders( Mth::Vector *p_center, float radius, uint32 meshes = 1 );
+
+	bool OccludeUseVU0();
+	void OccludeDisableVU0();
+
+	bool OccludeUseScratchPad();
+	void OccludeDisableScratchPad();
+
+
+const uint32 MAX_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME = 4;
+const uint32 MIN_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME = 4;
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// Structure used to store details of a single poly. A list of these will be built at geometry load time.
+struct sOcclusionPoly
+{
+	bool		in_use;
+	bool		available;	// Whether the poly is available for selection for occlusion.
+	uint32		checksum;	// Name checksum of the occlusion poly.
+	Mth::Vector	verts[4];
+	Mth::Vector	normal;
+};
+	
+const uint32	MAX_OCCLUDERS				= 12;
+
+struct sOccluder
+{
+	static uint32		NumOccluders;
+	static sOccluder	Occluders[MAX_OCCLUDERS];
+	static bool			sUseVU0;
+	static bool			sUseScratchPad;
+
+	static void			add_to_stack( sOcclusionPoly *p_poly );
+	static void			sort_stack( void );
+	static void			tidy_stack( void );
+
+	sOcclusionPoly	*p_poly;
+	Mth::Vector		planes[5];
+	int				score;			// Current rating on quality of occlusion - based on number of meshes occluded last frame.
+};
+
+const uint32	MAX_OCCLUSION_POLYS			= 128; 		// not currently useing that many
+extern uint32			NumOcclusionPolys;
+extern uint32			NextOcclusionPolyToCheck;
+extern sOcclusionPoly	OcclusionPolys[];
+
+
+
+}
+
+#endif
+
diff --git a/Code/Gfx/NGPS/NX/pcrtc.cpp b/Code/Gfx/NGPS/NX/pcrtc.cpp
new file mode 100644
index 0000000..abdff62
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/pcrtc.cpp
@@ -0,0 +1,109 @@
+#include 
+#include 
+#include 
+#include "pcrtc.h"
+#include "gs.h"
+#include "switches.h"
+#include "render.h"
+
+namespace NxPs2
+{
+
+///////////////////////////////////////////////////////////////////////////////
+// void SetupPCRTC(void)
+//
+// Sets up the read circuit blending that provides anti-aliasing
+// also sets the background color (to 0,0,0)
+//
+// Note, this is dependent on HRES and VRES for horizontal and vertical resolution
+
+void SetupPCRTC(int Enable, int FFMode, int HRes, int VRes)		// HRes and VRes default to 0
+{
+	// initialize the GS once with sceGsResetGraph().  It is the only way to set NTSC or PAL mode.
+	static bool inited = false;
+	if (!inited)
+	{
+		sceGsSyncV(0);
+		if (Config::NTSC())
+		{
+			sceGsResetGraph(0, SCE_GS_INTERLACE, SCE_GS_NTSC, SCE_GS_FIELD);
+		} else {
+			sceGsResetGraph(0, SCE_GS_INTERLACE, SCE_GS_PAL, SCE_GS_FIELD);
+		}
+
+		inited = true;
+	}
+
+	// zero resolution => use default res
+	if (!HRes)
+		HRes = HRES;
+	if (!VRes)
+		VRes = VRES;
+
+	tGS_SMODE2   smode2;
+	tGS_PMODE    pmode;
+	tGS_DISPFB1  dispfb1;
+	tGS_DISPLAY1 display1;
+	tGS_DISPFB2  dispfb2;
+	tGS_DISPLAY2 display2;
+	tGS_BGCOLOR  bgcolor;
+
+	// sync mode (SMODE2 register)
+	smode2.INT  = SCE_GS_INTERLACE;
+	smode2.FFMD = FFMode;
+	smode2.DPMS = 0;
+
+	pmode.EN1   = 1;				// enable Read Circuit 1
+	pmode.EN2   = 1;				// enable Read Circuit 2
+	pmode.CRTMD = 1;				// always 1
+	pmode.MMOD  = 1;				// use value in ALP for blending
+	pmode.AMOD  = 0;				// OUT1 alpha output selection (irrelevant for now)
+	pmode.SLBG  = Enable?0:1;		// blend with the output of Read Circuit 2 if Enable=1, or with BGCOLOR if Enable=0
+	pmode.ALP 	= Enable?128:0;		// alpha=0.5 if Enable=1, or 0.0 if Enable=0
+	pmode.p0	= 0;				// GS manual states that unused bits of PMODE should be set to zero
+	pmode.p1	= 0;
+
+	// Read Circuit 2 (DISPFB2 and DISPLAY2 registers)
+	dispfb2.FBP = (HRes*VRes/2048);
+	dispfb2.FBW = HRes/64;    //   *2 this to also display 1/2 of next frame
+ 	dispfb2.PSM = PSMCT16S;
+	dispfb2.DBX = 240-240/1;		// 1 is the scale
+	dispfb2.DBY = 224-224/1;		// 1 is the scale
+
+	if (Config::NTSC())
+	{
+		display2.DX   = 0x27C;		// horizontal offset }
+		display2.DY   = 0x32;		// vertical offset   }
+	} else {						//					 } see SCE_GS_SET_DISPLAY_ macros in eestruct.h
+		display2.DX   = 0x290;		// horizontal offset }
+		display2.DY   = 0x48;		// vertical offset	 }
+	}
+	display2.MAGH = 2560/HRes-1;	// see GS manual V5.0, p86
+	display2.MAGV = 0;				// *1 vertical magnification (reg value plus 1)
+	display2.DW   = 2560-1;
+	display2.DH	  = VRes-1;
+	
+	// Read Circuit 1 (DISPFB1 and DISPLAY1 registers)
+	//... copy Read Circuit 2 and then shift by 1 row to blend the fields
+	dispfb1  = *(tGS_DISPFB1 *)&dispfb2;
+	display1 = *(tGS_DISPLAY1 *)&display2;
+	display1.DY -= 1;				// offset by 1 scanline, to blend the fields together
+
+	// background colour (Sony says should always be black, unfortunately)
+	bgcolor.R = 0;
+	bgcolor.G = 0;
+	bgcolor.B = 0;
+
+	// write values to GS
+	sceGsSyncV(0);
+	*GS_SMODE2	 = *(volatile u_long *)&smode2;
+	*GS_DISPFB1	 = *(volatile u_long *)&dispfb1;
+	*GS_DISPLAY1 = *(volatile u_long *)&display1;
+	*GS_DISPFB2	 = *(volatile u_long *)&dispfb2;
+	*GS_DISPLAY2 = *(volatile u_long *)&display2;
+	*GS_BGCOLOR  = *(volatile u_long *)&bgcolor;
+	*GS_PMODE	 = *(volatile u_long *)&pmode;
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/pcrtc.h b/Code/Gfx/NGPS/NX/pcrtc.h
new file mode 100644
index 0000000..699ed94
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/pcrtc.h
@@ -0,0 +1,12 @@
+#ifndef __PCRTC_H
+#define __PCRTC_H
+
+namespace NxPs2
+{
+
+void SetupPCRTC(int Enable, int FFMode, int HRes=0, int VRes=0);
+
+} // namespace NxPs2
+
+#endif // __PCRTC_H
+
diff --git a/Code/Gfx/NGPS/NX/render.cpp b/Code/Gfx/NGPS/NX/render.cpp
new file mode 100644
index 0000000..d543d93
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/render.cpp
@@ -0,0 +1,2346 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "mikemath.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "vu1code.h"
+#include "dmacalls.h"
+#include "line.h"
+#include "chars.h"
+#include "texture.h"
+#include "material.h"
+#include "group.h"
+#include "scene.h"
+#include "mesh.h"
+#include "nx_init.h"
+#include "render.h"
+#include "interrupts.h"
+#include "switches.h"
+#include "sprite.h"
+#include "instance.h"
+#include "geomnode.h"
+#include "occlude.h"
+#include "resource.h"
+#include "vu1context.h"
+#include "light.h"
+#include "fx.h"
+#include "asmdma.h"
+#include "vu1\newvu1code.h"
+
+#include "gfx\nx.h"
+#include "gfx\ngps\p_nxscene.h"
+#include "gfx\ngps\p_nxtexture.h"
+
+
+//#undef	__USE_PROFILER__
+
+
+namespace Inp
+{
+	extern uint32	gDebugButtons[];
+	extern uint32	gDebugMakes[];
+
+}
+
+namespace NxPs2
+{
+
+extern uint32	*p_patch_PRMODE;
+extern uint32 gPassMask1;
+
+
+extern int geom_stats_total;
+extern int geom_stats_inactive ;
+extern int geom_stats_sky;
+extern int geom_stats_transformed;
+extern int geom_stats_skeletal;
+extern int geom_stats_camera_sphere;
+extern int geom_stats_clipcull;
+extern int geom_stats_culled;
+extern int geom_stats_leaf_culled;
+extern int geom_stats_boxcheck;
+extern int geom_stats_occludecheck;
+extern int geom_stats_occluded;
+extern int geom_stats_occludecheck;
+extern int geom_stats_occluded;
+extern int geom_stats_occludecheck;
+extern int geom_stats_occluded;
+extern int geom_stats_colored;
+extern int geom_stats_leaf;
+extern int geom_stats_wibbleUV;
+extern int geom_stats_wibbleVC;
+extern int geom_stats_sendcontext;
+extern int geom_stats_sorted;
+extern int geom_stats_shadow;
+
+// Mick:  Letterboxing on the ps2 is a post-processing
+// step one frame ahead in the rendering pipeline (as
+// compared to things like setting the FOV), so 
+// it must be delayed by 1 frame to synchronize it
+// with such events.
+bool ActualDoLetterbox = false;
+bool DoLetterbox = false;
+
+int	DMAOverflowOK  = false;
+
+bool DoFlipCopy = true;
+sGroup PrologueGroup;
+float SkaterY;
+
+int	SingleRender = 0;
+
+// skinning globals
+Mat SkaterTransform;
+Mat BoneTransforms[64];
+int NumBones=0;
+
+
+#if STENCIL_SHADOW
+void CastStencilShadow(CInstance *pInstance, sGroup *p_group, Mth::Vector &ShadowVec, Mth::Vector &TweakVec);
+#else
+void CastShadow(CInstance *pInstance, sGroup *p_group);
+#endif
+
+void EnableFlipCopy(bool flip)
+{
+	DoFlipCopy = flip;
+}
+
+bool FlipCopyEnabled()
+{
+	return DoFlipCopy;
+}
+
+//////////////////////////////////////////////////////////////////
+// void RenderPrologue(void)
+//
+// called at the start of a frame
+// sets up the initial DMA list
+// by setting up a prologue "group"
+// consisting of an empty upload portion, and 
+// a render portion that flips the visible buffers,
+// and sets up various vif settings
+// finally we initilize 	vu1::Loc = vu1::Buffer=0;
+//
+// Note, Field will be 0 or 1, depending on which buffer we are drawing into
+
+void RenderPrologue(void)
+{
+	#ifdef	__NOPT_ASSERT__
+	// a nasty patch by Mick to allow us to see the extra passes in flat shaded mode
+	if (gPassMask1)
+	{
+		if (Inp::gDebugMakes[0] & (1<<5))			  // check for Circle pressed
+		{
+			*p_patch_PRMODE ^= 1;
+		}
+	}
+	else
+	{
+		*p_patch_PRMODE = 1;
+	}
+	#endif
+	
+	
+	
+	// record the time
+	render::sTime = (int)Tmr::GetTime();
+					 
+	// set dma pointer to the start of the dma buffer
+	dma::pLoc = dma::pList[render::Field];
+	dma::pBase = dma::pLoc;
+
+	PrologueGroup.profile_color = 0x008080;		// yellow = prologue group
+
+	// prologue render begins here
+	PrologueGroup.pRender[render::Field] = dma::pLoc;
+	PrologueGroup.Used[render::Field] = true; 			// prologue always used
+
+	// call the DMA routine to flip frame buffers and clear the draw buffer
+	// currently the flip is accomplished by rendering a full screen sprite from the
+	// draw buffer to the display buffer.  This converts it from 32-bit to 16-bit (dithered)
+	// and means there is no actual buffer switching going on
+	// the draw buffer is always at the same address.
+	// When the game is running at 60fps, this copying takes place during the vblank.
+	
+	//static bool last_flip_copy = true;
+	if (DoFlipCopy /*&& last_flip_copy*/)	  // DoFlipCopy is set/cleared by the loading screen code (p_NxLoadScreen.cpp)
+	{
+		if (ActualDoLetterbox)
+			dma::Gosub(FLIP_N_CLEAR_LETTERBOX,2);
+		else
+			dma::Gosub(FLIP_N_CLEAR,2);
+		ActualDoLetterbox=DoLetterbox;
+	}
+	else
+	{
+		dma::Gosub(CLEAR_ZBUFFER,2);
+	}
+	//last_flip_copy = DoFlipCopy;
+
+	// set the GS to the default rendering state
+	dma::Gosub(SET_RENDERSTATE,2);
+	dma::Gosub(SET_FOGCOL,2);
+
+	// upload VU1 microcode	(the code in microcode.dsm)
+	// This is done every frame, at the start of the frame
+	// it actually only needs doing once, but it does not really take
+	// any time, and doing it every frame gives us the flexibility to uploaded new
+	// microcode at a later stage in rendering.
+	// for now, all the microcode fits in the 16K code area of VU1
+	dma::Tag(dma::ref, ((uint8 *)&MPGEnd-(uint8 *)&MPGStart+15)/16, (uint)&MPGStart);
+	vif::NOP();
+	vif::NOP();
+	
+	// VIF1 and VU1 setup
+	dma::BeginTag(dma::end, 0);
+	vif::FLUSH();
+	vif::STMASK(0);
+	vif::STMOD(0);
+	vif::STCYCL(1,1);
+	vif::BASE(0);
+	vif::OFFSET(0);
+	vif::MSCAL(VU1_ADDR(Setup));
+	dma::EndTag();
+	
+	// prepare all groups for rendering
+	for (sGroup *p_group=sGroup::pHead; p_group; p_group=p_group->pNext)
+	{
+		// initialise the dma list
+		dma::BeginList(p_group);
+
+		// assume textures aren't used until something actually gets rendered
+		p_group->Used[render::Field] = false;
+	}
+	dma::sp_group = NULL;
+
+
+	// while the dma context is null, set up an empty upload for the particles group and fog group
+	sGroup::pParticles->pUpload[render::Field] = dma::pLoc;
+	sGroup::pFog->pUpload[render::Field] = dma::pLoc;
+	dma::Tag(dma::end, 0, 0);
+	vif::NOP();
+	vif::NOP();
+	
+	// signal that the particle group is used
+	sGroup::pParticles->Used[render::Field] = true;
+
+	// reset current marker number
+	render::sMarkerIndex = 0;
+
+	// count particles
+	render::sTotalNewParticles = 0;
+}
+
+
+
+void RenderWorld(Mth::Matrix* camera_orient, Mth::Vector* camera_pos, float view_angle, float screen_aspect)
+{
+	Mth::Matrix transform(*camera_orient);
+	transform[3] = *camera_pos;
+
+	// need the transform to be well-formed as a 4x4 matrix
+	transform[0][3] = 0.0f;
+	transform[1][3] = 0.0f;
+	transform[2][3] = 0.0f;
+	transform[3][3] = 1.0f;
+
+	Mth::Rect viewport1(0.0f, 0.0f, 1.0f, 1.0f);
+	RenderViewport(0, transform, viewport1, view_angle, screen_aspect, -1.0f, -100000.0f);
+}
+
+void RenderInitImmediateMode()
+{
+	dma::SetList(sGroup::pEpilogue);
+
+	// epilogue render (a terrible hack to put this here!)
+	//sGroup::pEpilogue->pRender[render::Field] = dma::pLoc;
+	sGroup::pEpilogue->Used[render::Field] = true;			// epilogue always rendered
+
+	// VIF1 and VU1 setup
+	dma::BeginTag(dma::cnt, 0);
+	vif::FLUSH();
+	vif::STROW((int)render::RowRegI[0], (int)render::RowRegI[1], (int)render::RowRegI[2], (int)render::RowRegI[3]);
+	vif::STMASK(0);
+	vif::STMOD(0);
+	vif::STCYCL(1,1);
+	vif::BASE(0);
+	vif::OFFSET(0);
+	vif::MSCAL(VU1_ADDR(Setup));
+	vu1::Loc = vu1::Buffer=0;
+	//vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));
+	//vu1::StoreVec(*(Vec *)&render::ViewportScale);				// VF10
+	//vu1::StoreVec(*(Vec *)&render::ViewportOffset);				// VF11
+	//vu1::StoreMat(*(Mat *)&render::AdjustedWorldToFrustum);		// VF12-15
+	//vu1::StoreMat(*(Mat *)&render::AdjustedWorldToViewport);	// VF16-19
+	//vu1::EndPrim(0);
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::XYOFFSET_1, PackXYOFFSET(XOFFSET, YOFFSET));
+	gs::Reg1(gs::SCISSOR_1,	 PackSCISSOR(0,HRES - 1,0,VRES - 1));
+	gs::Reg1(gs::TEST_1, PackTEST(0,0,0,0,0,0,1,ZGEQUAL));
+	gs::Reg1(gs::CLAMP_1,PackCLAMP(CLAMP,CLAMP,0,0,0,0));
+	gs::Reg1(gs::PRMODECONT,	PackPRMODECONT(1));
+	gs::EndPrim(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	dma::EndTag();
+}
+
+void RenderSwitchImmediateMode()
+{
+	dma::SetList(sGroup::pEpilogue);
+}
+
+
+// camera_transform must be a well-formed 4x4 matrix
+void RenderViewport(uint viewport_num, const Mth::Matrix& camera_transform, const Mth::Rect& viewport_rec, float view_angle,
+					float viewport_aspect, float near, float far, bool just_setup)
+{
+	// set up a load of persistent data for current viewport
+	render::SetupVars(viewport_num, camera_transform, viewport_rec, view_angle, viewport_aspect, near, far);
+
+	// Perform the DMA seperately, as the actual VARS might be re-set by update_render_vars called from plat_transform_to_screen
+	render::SetupVarsDMA(viewport_num, near, far);
+
+		// old and new style renders
+		#	ifdef __USE_PROFILER__
+			Sys::CPUProfiler->PushContext( 255, 0, 0 );	 	// Red (Under Yellow) = OldStyle Rendering	(Skins and stuff)
+		#	endif // __USE_PROFILER__
+			render::OldStyle();
+		#	ifdef __USE_PROFILER__
+			Sys::CPUProfiler->PopContext( );
+		#	endif // __USE_PROFILER__
+		
+		
+		#	ifdef __USE_PROFILER__
+			Sys::CPUProfiler->PushContext( 0, 255, 0 );		// Green (Under Yellow) = NewStyle rendering (Environment)
+		#	endif // __USE_PROFILER__
+			
+			render::NewStyle(just_setup);
+		#	ifdef __USE_PROFILER__
+			Sys::CPUProfiler->PopContext(  );
+		#	endif // __USE_PROFILER__
+}
+
+// Uncomment the next line to use the color-per-mesh feature
+#define USE_COLOR_PER_MESH
+
+void render::OldStyle()
+{
+	// Don't render anything if it's not going to be displayed	
+	if (!FlipCopyEnabled())
+	{
+		return;
+	}
+	
+	float x,y,z,R;
+	sMesh *pMesh;
+	sGroup *p_group;
+	int j;
+	uint32 vu1_flags;
+	uint32 colour;
+	float colourComponent;
+
+	Mth::Matrix LocalToWorld;
+	Mth::Matrix LocalToCamera;
+	Mth::Matrix LocalToFrustum;
+	Mth::Matrix LocalToViewport;
+	Mth::Matrix RootToWorld;
+	Mth::Matrix RootToCamera;
+	Mth::Matrix RootToFrustum;
+	Mth::Matrix RootToViewport;
+	Mth::Matrix LightMatrix;
+	Mth::Matrix ColourMatrix;
+
+	Mth::Vector ambientColour;
+	Mth::Vector diffuseColour0;
+	Mth::Vector diffuseColour1;
+	Mth::Vector diffuseColour2;
+	Mth::Vector colouredAmbientColour;
+	Mth::Vector colouredDiffuseColour0;
+	Mth::Vector colouredDiffuseColour1;
+	Mth::Vector colouredDiffuseColour2;
+	Mth::Vector v;
+
+// Mick: Initialize these local matricies to known values....
+	LocalToWorld.Zero();
+	LocalToCamera.Zero();
+	LocalToFrustum.Zero();
+	LocalToViewport.Zero();
+	RootToWorld.Zero();
+	RootToCamera.Zero();
+	RootToFrustum.Zero();
+	RootToViewport.Zero();
+	LightMatrix.Zero();
+// And the vectors...	
+	ambientColour.Set(0.0f,0.0f,0.0f,1.0f);
+	diffuseColour0.Set(0.0f,0.0f,0.0f,1.0f);
+	diffuseColour1.Set(0.0f,0.0f,0.0f,1.0f);
+	diffuseColour2.Set(0.0f,0.0f,0.0f,1.0f);
+	colouredAmbientColour.Set(0.0f,0.0f,0.0f,1.0f);
+	colouredDiffuseColour0.Set(0.0f,0.0f,0.0f,1.0f);
+	colouredDiffuseColour1.Set(0.0f,0.0f,0.0f,1.0f);
+	colouredDiffuseColour2.Set(0.0f,0.0f,0.0f,1.0f);
+	v.Set(0.0f,0.0f,0.0f,1.0f);
+	
+
+	
+	#if STENCIL_SHADOW
+	// prepare stencil shadow dma list for generating the stencil buffer
+	dma::SetList(sGroup::pShadow);
+
+	// send the GS setup
+	dma::BeginTag(dma::cnt, 0);
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::XYOFFSET_2,	PackXYOFFSET(XOFFSET, YOFFSET));
+	gs::Reg1(gs::ZBUF_2,		PackZBUF(ZBUFFER_START,PSMZ24,1));
+	gs::Reg1(gs::SCISSOR_2,		PackSCISSOR(0,HRES - 1,0,VRES - 1));
+	gs::Reg1(gs::FRAME_2,		PackFRAME(0x1BA,HRES/64,PSMCT16S,0x00000000));
+	//gs::Reg1(gs::FRAME_2,		PackFRAME(FRAME_START,HRES/64,PSMCT32,0x00000000));
+	gs::Reg1(gs::ALPHA_2,		PackALPHA(0,2,2,1,128));
+	gs::Reg1(gs::FBA_2,			PackFBA(0));
+	gs::Reg1(gs::PABE,			PackPABE(0));
+	gs::Reg1(gs::COLCLAMP,		PackCOLCLAMP(0));
+	gs::Reg1(gs::PRMODECONT,	PackPRMODECONT(1));
+	gs::Reg1(gs::DTHE,			PackDTHE(0));
+	gs::Reg1(gs::TEST_2,		PackTEST(0,0,0,0,0,0,1,ZALWAYS));
+	gs::Reg1(gs::PRIM,			PackPRIM(SPRITE,0,0,0,0,0,0,1,0));			// clear the stencil buffer
+	gs::Reg1(gs::RGBAQ,			PackRGBAQ(0,0,0,0,0));
+	gs::Reg1(gs::XYZ2,			PackXYZ(XOFFSET,YOFFSET,0));
+	gs::Reg1(gs::XYZ2,			PackXYZ(0x10000-XOFFSET,0x10000-YOFFSET,0));
+	gs::Reg1(gs::TEST_2,		PackTEST(0,0,0,0,0,0,1,ZGREATER));
+	gs::Reg1(gs::FRAME_2,		PackFRAME(0x1BA,HRES/64,PSMCT16S,0xFF000000));
+	gs::EndPrim(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	dma::EndTag();
+	#else
+	// prepare shadow dma list for generating the shadow texture
+	dma::SetList(sGroup::pShadow);
+
+	// send the GS setup
+	dma::BeginTag(dma::cnt, 0);
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::XYOFFSET_2,	PackXYOFFSET(0x7800,0x7800));
+	gs::Reg1(gs::ZBUF_2,		PackZBUF(0,PSMZ16,1));
+	gs::Reg1(gs::TEST_2,		PackTEST(0,0,0,0,0,0,1,ZALWAYS));
+	gs::Reg1(gs::SCISSOR_2,		PackSCISSOR(0,255,0,255));
+	gs::Reg1(gs::FRAME_2,		PackFRAME(0x1F0,4,PSMCT16,0x00000000));
+	//gs::Reg1(gs::FRAME_2,		PackFRAME(0x000,HRES/64,PSMCT32,0x00000000));
+	gs::Reg1(gs::FBA_2,			PackFBA(0));
+	gs::Reg1(gs::PRIM,			PackPRIM(SPRITE,0,0,0,0,0,0,1,0));			// clear the texture
+	gs::Reg1(gs::RGBAQ,			PackRGBAQ(0,0,0,0,0));
+	gs::Reg1(gs::XYZ2,			PackXYZ(0x7800,0x7800,0));
+	gs::Reg1(gs::XYZ2,			PackXYZ(0x8800,0x8800,0));
+	gs::Reg1(gs::FBA_2,			PackFBA(1));								// set A on all pixels we touch
+	gs::Reg1(gs::SCISSOR_2,		PackSCISSOR(1,254,1,254));					// don't touch edge of texture to avoid streaks
+	gs::Reg1(gs::PRMODECONT,	PackPRMODECONT(0));
+	gs::Reg1(gs::PRMODE,		PackPRMODE(0,0,0,0,0,0,1,0));
+	gs::EndPrim(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	dma::EndTag();
+	#endif
+
+
+	// assume no dma context
+	dma::SetList(NULL);
+
+	// render old-style groups
+	for (p_group=sGroup::pHead; p_group!=sGroup::pEpilogue; p_group=p_group->pNext)
+	{
+		// skip the old render mechanism if the group belongs to a pip-style scene
+		if (p_group==sGroup::pShadow || !p_group->pScene || p_group->pScene->Flags & SCENEFLAG_USESPIP)
+		{
+			continue;
+		}
+
+		dma::SetList(p_group);
+
+		dma::BeginTag(dma::cnt, 0);
+
+			// VIF1 and VU1 setup
+			vif::FLUSH();
+			vif::STMASK(0);
+			vif::STMOD(0);
+			vif::STCYCL(1,1);
+			vif::STCOL(0x3F800000,0,0,0);
+			vif::BASE(0);
+			vif::OFFSET(0);
+			vif::MSCAL(VU1_ADDR(Setup));
+			vu1::Loc = vu1::Buffer=0;
+
+			// constant part of vu1 context data
+			vu1::BeginPrim(ABS, VU1_ADDR(L_VF09));
+//			vu1::StoreVec(*(Vec *)&render::AltFrustum);				// VF09
+//			vu1::StoreVec(*(Vec *)&render::InverseViewportScale);	// VF10
+//			vu1::StoreVec(*(Vec *)&render::InverseViewportOffset);	// VF11
+			vu1::CopyQuads((uint32*)&render::AltFrustum, 
+							(uint32*)&render::InverseViewportScale,
+							(uint32*)&render::InverseViewportOffset
+							);
+			vu1::EndPrim(0);
+
+			// send the GS viewport context
+			gs::BeginPrim(ABS, 0, 0);
+			gs::Reg1(gs::XYOFFSET_1, render::reg_XYOFFSET);
+			gs::Reg1(gs::SCISSOR_1,  render::reg_SCISSOR);
+			gs::EndPrim(1);
+			vif::MSCAL(VU1_ADDR(Parser));
+
+		dma::EndTag();
+
+
+		Mth::Matrix refl_temp;
+		Mth::Matrix  	ReflVecs;
+		refl_temp[0] = Mth::Vector(1.0f, 0.0f, 0.0f, 0.0f);
+		refl_temp[1] = Mth::Vector(0.0f, 1.0f, 0.0f, 0.0f);
+		refl_temp[2] = Mth::Vector(0.0f, 0.0f, 1.4f, 0.0f);
+		refl_temp[3] = Mth::Vector(0.0f, 0.0f, 0.0f, 0.0f);
+
+		// this part handles the instanced non-skinned models
+		if (p_group->pScene && (p_group->pScene->Flags & SCENEFLAG_INSTANCEABLE) &&
+			p_group->pMeshes && !(p_group->pMeshes->Flags & MESHFLAG_SKINNED))
+		{
+			for (CInstance *pInstance=p_group->pScene->pInstances; pInstance; pInstance=pInstance->GetNext())
+			{
+				// make sure the instance is active
+				if (!pInstance->IsActive())
+				{
+					continue;
+				}
+
+				LocalToWorld = *(Mth::Matrix *)pInstance->GetTransform();
+				LocalToCamera = LocalToWorld * render::WorldToCamera;
+				LocalToFrustum = LocalToWorld * render::WorldToFrustum;
+				LocalToViewport = LocalToFrustum * render::FrustumToViewport;
+
+				// set up reflection map transform
+				sceVu0MulMatrix((sceVu0FVECTOR*)&ReflVecs,
+								(sceVu0FVECTOR*)&refl_temp,
+								(sceVu0FVECTOR*)&LocalToCamera);
+
+				// send VIF/VU context
+				dma::BeginTag(dma::cnt, 0);
+				vif::STROW(0,0,0,0);
+				vu1::BeginPrim(ABS, VU1_ADDR(L_VF12));
+				vu1::StoreMat(*(Mat *)&LocalToViewport);	// VF12-15
+				vu1::StoreMat(*(Mat *)&ReflVecs);			// VF16-19
+				vu1::EndPrim(0);
+
+				//gs::BeginPrim(ABS,0,0);
+				//gs::Reg1(gs::FOGCOL, PackFOGCOL(0x60,0x40,0xC0));
+				//gs::EndPrim(0);
+
+				dma::EndTag();
+
+				// traverse array of meshes in this group
+				for (j=0,pMesh=p_group->pMeshes; jNumMeshes; j++,pMesh++)
+				{
+					// skip if invisible
+					if (!pMesh->IsActive())
+					{
+						continue;
+					}
+
+					// do nothing if mesh doesn't have a dma subroutine
+					if (!pMesh->pSubroutine)
+					{
+						continue;
+					}
+
+					v = pMesh->Sphere;
+					v[3] = 1.0f;
+					v *= LocalToCamera;
+					x = v[0];
+					y = v[1];
+					z = v[2];
+					R = pMesh->Sphere[3];
+
+					// if all outside view volume then cull
+					if (RpSubroutine, PROJ);
+					}
+
+					// otherwise cull at the vertex level
+					else
+					{
+						dma::Gosub3D(pMesh->pSubroutine, CLIP);
+					}
+					
+					p_group->Used[render::Field] = true;	  	// Note, should have culling before here
+					
+
+				}
+
+			}
+
+		}
+
+
+		// this part handles the instanced skinned models
+		if (p_group->pScene && (p_group->pScene->Flags & SCENEFLAG_INSTANCEABLE) &&
+			p_group->pMeshes && (p_group->pMeshes->Flags & MESHFLAG_SKINNED))
+		{
+			for (CInstance *pInstance=p_group->pScene->pInstances; pInstance; pInstance=pInstance->GetNext())
+			{
+				// make sure the instance is active
+				if (!pInstance->IsActive())
+				{
+					continue;
+				}
+
+				// shadow
+				if (pInstance->CastsShadow())
+				{
+					#if STENCIL_SHADOW
+					Mth::Vector shadow_vec(20.0f, -100.0f, 0.0f, 0.0f);
+					Mth::Vector tweak_vec(0.4f, -2.0f, 0.0f, 0.0f);
+					CastStencilShadow(pInstance, p_group, shadow_vec, tweak_vec);
+					#else
+					CastShadow(pInstance, p_group);
+					#endif
+				}
+
+				// mat for bounding sphere test
+				RootToWorld = *(Mth::Matrix *)pInstance->GetTransform();
+				RootToCamera = RootToWorld * render::WorldToCamera;
+
+				// get bounding sphere
+				v = pInstance->GetBoundingSphere();
+				R = v[3];
+				v[3] = 1.0f;
+				v *= RootToCamera;
+				x = v[0];
+				y = v[1];
+				z = v[2];
+
+				// if all outside view volume then cull
+				if (RIsWireframe())
+				{
+					vu1_flags |= WIRE;
+				}
+
+				p_group->Used[render::Field] = true;	  	// Note, should have culling before here					
+
+				dma::BeginTag(dma::cnt, 0);
+
+
+				// adjust matrix for 0:8 fixed point weights format (used to be 1:15)
+				RootToCamera[0] *= 128.0f;
+				RootToCamera[1] *= 128.0f;
+				RootToCamera[2] *= 128.0f;
+
+				// send vu1 context
+				RootToFrustum = RootToCamera * render::CameraToFrustum;
+				RootToViewport = RootToFrustum * render::FrustumToIntViewport;
+
+				CLightGroup *pLightGroup = pInstance->GetLightGroup();
+
+				if (pLightGroup)
+				{
+					const Mth::Vector &light_dir0 = pLightGroup->GetDirection(0);
+					const Mth::Vector &light_dir1 = pLightGroup->GetDirection(1);
+					const Mth::Vector &light_dir2 = pLightGroup->GetDirection(2);
+
+					LightMatrix[0][0] = light_dir0[0];
+					LightMatrix[1][0] = light_dir0[1];
+					LightMatrix[2][0] = light_dir0[2];
+					LightMatrix[0][1] = light_dir1[0];
+					LightMatrix[1][1] = light_dir1[1];
+					LightMatrix[2][1] = light_dir1[2];
+					LightMatrix[0][2] = light_dir2[0];
+					LightMatrix[1][2] = light_dir2[1];
+					LightMatrix[2][2] = light_dir2[2];
+				} else {
+					const Mth::Vector &light_dir0 = CLightGroup::sGetDefaultDirection(0);
+					const Mth::Vector &light_dir1 = CLightGroup::sGetDefaultDirection(1);
+					const Mth::Vector &light_dir2 = CLightGroup::sGetDefaultDirection(2);
+
+					LightMatrix[0][0] = light_dir0[0];
+					LightMatrix[1][0] = light_dir0[1];
+					LightMatrix[2][0] = light_dir0[2];
+					LightMatrix[0][1] = light_dir1[0];
+					LightMatrix[1][1] = light_dir1[1];
+					LightMatrix[2][1] = light_dir1[2];
+					LightMatrix[0][2] = light_dir2[0];
+					LightMatrix[1][2] = light_dir2[1];
+					LightMatrix[2][2] = light_dir2[2];
+				}
+
+				Mth::Matrix temp = *(Mth::Matrix *)pInstance->GetTransform();
+				temp[0].Normalize();							// do something sensible with a scaled matrix
+				temp[1].Normalize();
+				temp[2].Normalize();
+				LightMatrix = temp * LightMatrix;
+
+				if (pLightGroup)
+				{
+					ambientColour  = pLightGroup->GetAmbientColor();
+					diffuseColour0 = pLightGroup->GetDiffuseColor(0);
+					diffuseColour1 = pLightGroup->GetDiffuseColor(1);
+					diffuseColour2 = pLightGroup->GetDiffuseColor(2);
+				}
+				else
+				{
+					ambientColour  = CLightGroup::sGetDefaultAmbientColor();
+					diffuseColour0 = CLightGroup::sGetDefaultDiffuseColor(0);
+					diffuseColour1 = CLightGroup::sGetDefaultDiffuseColor(1);
+					diffuseColour2 = CLightGroup::sGetDefaultDiffuseColor(2);
+				}
+
+				// apply instance colour
+
+#ifdef USE_COLOR_PER_MESH
+				if (pInstance->HasColorPerMaterial())
+				{
+					// Get first color for now
+					colour = pInstance->GetMaterialColorByIndex(0);
+				}
+				else
+				{
+					colour = pInstance->GetColor();
+				}
+#else
+				colour = pInstance->GetColor();
+#endif
+				colourComponent = (float)(colour & 0xFF) * 0.0078125f;
+				colouredAmbientColour[0]  = ambientColour[0]  * colourComponent;
+				colouredDiffuseColour0[0] = diffuseColour0[0] * colourComponent;
+				colouredDiffuseColour1[0] = diffuseColour1[0] * colourComponent;
+				colouredDiffuseColour2[0] = diffuseColour2[0] * colourComponent;
+				colourComponent = (float)(colour>>8 & 0xFF) * 0.0078125f;
+				colouredAmbientColour[1]  = ambientColour[1]  * colourComponent;
+				colouredDiffuseColour0[1] = diffuseColour0[1] * colourComponent;
+				colouredDiffuseColour1[1] = diffuseColour1[1] * colourComponent;
+				colouredDiffuseColour2[1] = diffuseColour2[1] * colourComponent;
+				colourComponent = (float)(colour>>16 & 0xFF) * 0.0078125f;
+				colouredAmbientColour[2]  = ambientColour[2]  * colourComponent;
+				colouredDiffuseColour0[2] = diffuseColour0[2] * colourComponent;
+				colouredDiffuseColour1[2] = diffuseColour1[2] * colourComponent;
+				colouredDiffuseColour2[2] = diffuseColour2[2] * colourComponent;
+
+				LightMatrix[3]  = colouredAmbientColour  * 1.0f/(128.0f*8388608.0f);
+				ColourMatrix[0] = colouredDiffuseColour0 * 1.0f/(128.0f*8388608.0f);
+				ColourMatrix[1] = colouredDiffuseColour1 * 1.0f/(128.0f*8388608.0f);
+				ColourMatrix[2] = colouredDiffuseColour2 * 1.0f/(128.0f*8388608.0f);
+
+				// vu context data
+				vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));
+				
+//				vu1::StoreVec(*(Vec *)&render::InverseIntViewportScale);	// VF10
+//				vu1::StoreVec(*(Vec *)&render::InverseIntViewportOffset);	// VF11
+				vu1::CopyQuads((uint32*)&render::InverseIntViewportScale,
+							   (uint32*)&render::InverseIntViewportOffset);
+					
+				vu1::StoreMat(*(Mat *)&RootToFrustum);			// VF12-15
+				vu1::StoreMat(*(Mat *)&RootToViewport);			// VF16-19
+				vu1::StoreMat(*(Mat *)&LightMatrix);			// VF20-23
+				vu1::StoreMat(*(Mat *)&ColourMatrix);			// VF24-27
+				vu1::EndPrim(0);
+
+				// gs context data, specifically the fog coefficient
+				float w,f;
+				w = -RootToCamera[3][2];
+				if ((w > FogNear) && EnableFog)	// Garrett: We have to check for EnableFog here because the VU1 code isn't
+				{
+					f = 1.0 + EffectiveFogAlpha * (FogNear/w - 1.0f);
+				}
+				else
+				{
+					f = 1.0f;
+				}
+				gs::BeginPrim(ABS,0,0);
+				gs::Reg1(gs::FOG, PackFOG((int)(f*255.99f)));
+				//gs::Reg1(gs::FOGCOL, PackFOGCOL(0x60,0x40,0xC0));
+				gs::EndPrim(1);
+
+				vif::MSCAL(VU1_ADDR(Parser));
+
+				// get free run of the VUMem
+				vif::FLUSHA();
+
+				dma::EndTag();
+
+				// send transforms
+				vu1::Loc = 0;
+				dma::Tag(dma::ref, pInstance->GetNumBones()<<2, (uint)pInstance->GetBoneTransforms());
+				vif::NOP();
+				vif::UNPACK(0,V4_32,pInstance->GetNumBones()<<2,ABS,SIGNED,0);
+
+				dma::BeginTag(dma::cnt, 0);
+
+				vif::ITOP(pInstance->GetNumBones()<<2);
+				vif::MSCAL(VU1_ADDR(ReformatXforms));
+
+				// not using pre-translate
+				// don't forget to switch off the offset mode on xyz unpack if using the row reg for something else
+				vif::STROW(0,0,0,0);
+
+				dma::EndTag();
+
+				// traverse colour-unlocked meshes
+				for (j=0,pMesh=p_group->pMeshes; jNumMeshes; j++,pMesh++)
+				{
+					// skip if invisible, no subroutine, or not affected by dynamic colouring
+					if (!pMesh->IsActive() || !pMesh->pSubroutine || (pMesh->Flags & MESHFLAG_COLOR_LOCKED))
+					{
+						continue;
+					}
+
+#ifdef USE_COLOR_PER_MESH
+					uint32 mesh_color;
+					// Garrett: Thought I had the material color array in the right order, but the render order
+					// is different.  Right now I'm just searching out each color, but I'll need to go back
+					// and see if it is possible to put the color array in render order (i.e. is the render order
+					// static).
+					if (pInstance->HasColorPerMaterial() && (colour != (mesh_color = pInstance->GetMaterialColor(pMesh->MaterialName, pMesh->Pass))))
+					{
+						colour = mesh_color;
+						colourComponent = (float)(colour & 0xFF) * 0.0078125f;
+						colouredAmbientColour[0]  = ambientColour[0]  * colourComponent;
+						colouredDiffuseColour0[0] = diffuseColour0[0] * colourComponent;
+						colouredDiffuseColour1[0] = diffuseColour1[0] * colourComponent;
+						colouredDiffuseColour2[0] = diffuseColour2[0] * colourComponent;
+						colourComponent = (float)(colour>>8 & 0xFF) * 0.0078125f;
+						colouredAmbientColour[1]  = ambientColour[1]  * colourComponent;
+						colouredDiffuseColour0[1] = diffuseColour0[1] * colourComponent;
+						colouredDiffuseColour1[1] = diffuseColour1[1] * colourComponent;
+						colouredDiffuseColour2[1] = diffuseColour2[1] * colourComponent;
+						colourComponent = (float)(colour>>16 & 0xFF) * 0.0078125f;
+						colouredAmbientColour[2]  = ambientColour[2]  * colourComponent;
+						colouredDiffuseColour0[2] = diffuseColour0[2] * colourComponent;
+						colouredDiffuseColour1[2] = diffuseColour1[2] * colourComponent;
+						colouredDiffuseColour2[2] = diffuseColour2[2] * colourComponent;
+
+						LightMatrix[3]  = colouredAmbientColour  * 1.0f/(128.0f*8388608.0f);
+						ColourMatrix[0] = colouredDiffuseColour0 * 1.0f/(128.0f*8388608.0f);
+						ColourMatrix[1] = colouredDiffuseColour1 * 1.0f/(128.0f*8388608.0f);
+						ColourMatrix[2] = colouredDiffuseColour2 * 1.0f/(128.0f*8388608.0f);
+
+						// change lighting context
+						dma::BeginTag(dma::cnt, 0);
+						vif::BASE(256);
+						vif::OFFSET(0);
+						vu1::Loc = 256;
+						vu1::BeginPrim(ABS, VU1_ADDR(L_VF20));
+						vu1::StoreMat(*(Mat *)&LightMatrix);			// VF20-23
+						vu1::StoreMat(*(Mat *)&ColourMatrix);			// VF24-27
+						vu1::EndPrim(1);
+						vif::MSCAL(VU1_ADDR(ParseInit));
+						vif::FLUSH();
+						dma::EndTag();
+					}
+#endif // USE_COLOR_PER_MESH
+
+					// render the mesh
+					vu1::Loc = vu1::Buffer = 256;
+					dma::BeginTag(dma::call,(uint)pMesh->pSubroutine);
+					vif::BASE(256);
+					vif::OFFSET(0);
+					vif::MSCAL(VU1_ADDR(Setup));
+					vif::ITOP(vu1_flags);
+					vif::FLUSH();
+					dma::EndTag();
+					vu1::Loc += ((uint16 *)pMesh->pSubroutine)[1];
+				}
+
+
+				// change lighting context
+				dma::BeginTag(dma::cnt, 0);
+				vif::BASE(256);
+				vif::OFFSET(0);
+				vu1::Loc = 256;
+				LightMatrix[3]  = ambientColour  * 1.0f/(128.0f*8388608.0f);
+				ColourMatrix[0] = diffuseColour0 * 1.0f/(128.0f*8388608.0f);
+				ColourMatrix[1] = diffuseColour1 * 1.0f/(128.0f*8388608.0f);
+				ColourMatrix[2] = diffuseColour2 * 1.0f/(128.0f*8388608.0f);
+				vu1::BeginPrim(ABS, VU1_ADDR(L_VF20));
+				vu1::StoreMat(*(Mat *)&LightMatrix);			// VF20-23
+				vu1::StoreMat(*(Mat *)&ColourMatrix);			// VF24-27
+				vu1::EndPrim(1);
+				vif::MSCAL(VU1_ADDR(ParseInit));
+				vif::FLUSH();
+				dma::EndTag();
+
+				// traverse colour-locked meshes
+				for (j=0,pMesh=p_group->pMeshes; jNumMeshes; j++,pMesh++)
+				{
+					// skip if invisible, no subroutine, or affected by dynamic colouring
+					if (!pMesh->IsActive() || !pMesh->pSubroutine || !(pMesh->Flags & MESHFLAG_COLOR_LOCKED))
+					{
+						continue;
+					}
+
+					// render the mesh
+					vu1::Loc = vu1::Buffer = 256;
+					dma::BeginTag(dma::call,(uint)pMesh->pSubroutine);
+					vif::BASE(256);
+					vif::OFFSET(0);
+					vif::MSCAL(VU1_ADDR(Setup));
+					vif::ITOP(vu1_flags);
+					vif::FLUSH();
+					dma::EndTag();
+					vu1::Loc += ((uint16 *)pMesh->pSubroutine)[1];
+				}
+
+
+			}
+
+		}
+
+	}
+
+
+	#if STENCIL_SHADOW
+	// postprocess stencil buffer
+	dma::SetList(sGroup::pShadow);
+
+	// send the GS setup
+	dma::BeginTag(dma::cnt, 0);
+	gs::BeginPrim(ABS,0,0);
+
+	gs::Reg1(gs::TEXFLUSH,	0);
+
+	gs::Reg1(gs::COLCLAMP,	PackCOLCLAMP(1));
+	gs::Reg1(gs::TEST_1,	PackTEST(0,0,0,0,0,0,1,ZALWAYS));
+	gs::Reg1(gs::TEX0_1,	PackTEX0(0x3740,HRES/64,PSMCT16S,10,10,1,DECAL,0,0,0,0,0));
+	gs::Reg1(gs::TEX1_1,	PackTEX1(1,0,0,0,0,0,0));
+	gs::Reg1(gs::ALPHA_1,	PackALPHA(2,1,0,1,0));
+	gs::Reg1(gs::TEXA,		PackTEXA(ShadowDensity, 1, ShadowDensity));
+	gs::Reg1(gs::ZBUF_1,	PackZBUF(ZBUFFER_START,PSMZ24,1));
+
+	gs::Reg1(gs::PRIM,		PackPRIM(SPRITE,0,1,0,1,0,1,0,0));
+	gs::Reg1(gs::UV,		PackUV(0x0008,0x0008));
+	gs::Reg1(gs::XYZ2,		PackXYZ(XOFFSET,YOFFSET,0));
+	gs::Reg1(gs::UV,		PackUV((HRES<<4)+8, (VRES<<4)+8));
+	gs::Reg1(gs::XYZ2,		PackXYZ(XOFFSET+(HRES<<4), YOFFSET+(VRES<<4), 0));//0x10000-XOFFSET,0x10000-YOFFSET,0));
+
+	gs::Reg1(gs::TEXA,		PackTEXA(0x00,0,0x80));
+	gs::Reg1(gs::ZBUF_1,	PackZBUF(ZBUFFER_START,PSMZ24,0));
+
+	gs::EndPrim(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	dma::EndTag();
+	#endif
+
+
+
+	// assume no dma context
+	dma::SetList(NULL);
+
+}
+
+void patch_texture_dma()
+{
+	Nx::CScene *p_nxscene =  Nx::CEngine::sGetMainScene();
+	if (p_nxscene)
+	{
+		Nx::CPs2TexDict *p_tex_dict = (Nx::CPs2TexDict *)p_nxscene->GetTexDict();
+		if (p_tex_dict)
+		{
+			sScene *p_scene = p_tex_dict->GetEngineTextureDictionary();
+		
+		
+			sTexture *p_textures = p_scene->pTextures;
+			int num_textures = p_scene->NumTextures;
+		
+			
+		//	int total_referenced = 0;
+		//	int num_unique = 0;
+			int i;
+			for (i=0;ipNext)
+	{
+		if ((p_group!=sGroup::pShadow) && p_group->pScene && (p_group->pScene->Flags & SCENEFLAG_USESPIP))
+		{
+			// send the GS viewport context
+			dma::SetList(p_group);
+
+			// mark our place if it's a (the?) sorted group
+			// remember to assert there's no overflow
+			if (p_group->flags & GROUPFLAG_SORT)
+			{
+				//printf("Marking location %08X, p_group==%08X, flags=%08X\n", (int)dma::pLoc, (int)p_group, p_group->flags);
+				Dbg_MsgAssert(sMarkerIndex < MAX_SORTED_LIST_MARKERS, ("too many sorted list markers - tell Mike"));
+				render::sSortedListMarker[render::sMarkerIndex++] = (int)dma::pLoc;
+			}
+
+			dma::BeginTag(dma::cnt, 0xFE000000);
+			vif::BASE(vu1::Loc);
+			vif::OFFSET(0);
+			uint vu1_loc = vu1::Loc;
+			vu1::Loc = 0;   							// must do this as a relative prim for a sortable list...
+
+			// constant part of vu1 context data
+			vu1::BeginPrim(REL, VU1_ADDR(L_VF09));
+			vu1::StoreVec(*(Vec *)&render::AltFrustum);				// VF09
+			if (p_group->flags & GROUPFLAG_SKY)
+			{
+				vu1::StoreVec(*(Vec *)&render::SkyInverseViewportScale);	// VF10
+				vu1::StoreVec(*(Vec *)&render::SkyInverseViewportOffset);	// VF11
+			}
+			else
+			{
+				vu1::StoreVec(*(Vec *)&render::InverseViewportScale);	// VF10
+				vu1::StoreVec(*(Vec *)&render::InverseViewportOffset);	// VF11
+			}
+			vu1::EndPrim(0);
+
+			gs::BeginPrim(REL, 0, 0);
+			gs::Reg1(gs::XYOFFSET_1, render::reg_XYOFFSET);
+			gs::Reg1(gs::SCISSOR_1,  render::reg_SCISSOR);
+			gs::EndPrim(1);
+			vif::MSCAL(VU1_ADDR(Parser));
+			dma::EndTag();
+			((uint16 *)dma::pTag)[1] |= vu1::Loc & 0x3FF;	// must write some code for doing this automatically
+			vu1::Loc += vu1_loc;
+
+			if (p_group->flags & GROUPFLAG_SORT)
+			{
+				dma::Tag(dma::cnt,0,0);
+				vif::NOP();
+				vif::NOP();
+			}
+
+			// assume no vu1 context for each group
+			p_group->pVu1Context = NULL;
+		}
+
+	}
+
+	// reserve space from dma list for new trans context
+	dma::SetList(NULL);
+	//CVu1Context *p_ctxt = new(dma::pLoc) CVu1Context;
+	CVu1Context *p_ctxt = (CVu1Context *)dma::pLoc;
+	//dma::pLoc += sizeof(CVu1Context);
+	dma::pLoc += (STD_CTXT_SIZE+7)*16;
+	p_ctxt->Init();
+	Mth::Matrix I;
+	I.Ident();
+	p_ctxt->StandardSetup(I);
+
+	// shadow stuff
+	sGroup::pShadow->Used[render::Field] = true;
+
+	#if !STENCIL_SHADOW
+
+	// while the dma context is null, set up an empty upload for the shadow group
+	sGroup::pShadow->pUpload[render::Field] = dma::pLoc;
+	dma::Tag(dma::end, 0, 0);
+	vif::NOP();
+	vif::NOP();
+	
+	// ...and also a vu1 context for the shadow group
+	CVu1Context *p_shdw_ctxt = new(dma::pLoc) CVu1Context(*p_ctxt);
+	dma::pLoc += sizeof(CVu1Context);
+	p_shdw_ctxt->InitExtended();
+	p_shdw_ctxt->SetShadowVecs(SkaterY);
+
+	// add a z-push
+	p_shdw_ctxt->AddZPush(16.0f);
+
+
+	dma::SetList(sGroup::pShadow);
+
+	// initial cnt tag, for sending row reg and z-sort key
+	Mth::Vector *p_trans = p_shdw_ctxt->GetTranslation();
+	dma::Tag(dma::cnt, 1, 0);
+	vif::NOP();
+	vif::STROW((int)(*p_trans)[0], (int)(*p_trans)[1], (int)(*p_trans)[2], 0);
+
+	// send transform context
+	dma::Tag(dma::ref, (EXT_CTXT_SIZE+2)|(EXT_CTXT_SIZE+1)<<16, p_shdw_ctxt->GetDma());
+	vif::BASE(vu1::Loc);
+	vif::OFFSET(0);
+	vu1::Loc += EXT_CTXT_SIZE+1;
+	sGroup::pShadow->pVu1Context = p_shdw_ctxt;
+
+	// send the GS context for the shadow list
+	dma::BeginTag(dma::cnt, 0);
+
+	vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));
+	vu1::StoreVec(*(Vec *)&render::InverseViewportScale);	// VF10
+	vu1::StoreVec(*(Vec *)&render::InverseViewportOffset);	// VF11
+	vu1::EndPrim(0);
+
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::TEXFLUSH,		PackTEXFLUSH(0));
+	gs::Reg1(gs::FBA_2,			PackFBA(0));
+	gs::Reg1(gs::TEX0_2,		PackTEX0(0x3E00,4,PSMCT16,8,8,1,DECAL,0,0,0,0,0));
+	gs::Reg1(gs::TEX1_2,		PackTEX1(1,0,NEAREST,NEAREST,0,0,0));
+	gs::Reg1(gs::CLAMP_2,		PackCLAMP(CLAMP,CLAMP,0,0,0,0));
+	gs::Reg1(gs::FRAME_2,		PackFRAME(FRAME_START,HRES/64,PSMCT32,0xFF000000));
+	gs::Reg1(gs::ZBUF_2,		PackZBUF(ZBUFFER_START,PSMZ24,1));
+	gs::Reg1(gs::XYOFFSET_2,	reg_XYOFFSET);
+	gs::Reg1(gs::SCISSOR_2,		reg_SCISSOR);
+	gs::Reg1(gs::ALPHA_2,		PackALPHA(2,1,2,1,ShadowDensity));
+	gs::Reg1(gs::TEST_2,		PackTEST(1,AGEQUAL,0x80,KEEP,0,0,1,ZGEQUAL));
+	gs::Reg1(gs::PRMODECONT,	PackPRMODECONT(0));
+	gs::Reg1(gs::PRMODE,		PackPRMODE(0,1,0,1,0,0,1,0));
+	gs::EndPrim(0);
+	dma::EndTag();
+	#endif
+
+
+	geom_stats_total=0;
+	geom_stats_inactive =0;
+	geom_stats_sky=0;
+	geom_stats_transformed=0;
+	geom_stats_skeletal=0;
+	geom_stats_camera_sphere=0;
+	geom_stats_clipcull=0;
+	geom_stats_culled=0;
+	geom_stats_leaf_culled=0;
+	geom_stats_clipcull=0;
+	geom_stats_boxcheck=0;
+	geom_stats_occludecheck=0;
+	geom_stats_occluded=0;
+	geom_stats_colored=0;
+	geom_stats_leaf=0;
+	geom_stats_wibbleUV=0;
+	geom_stats_wibbleVC=0;
+	geom_stats_sendcontext=0;
+	geom_stats_sorted=0;
+	geom_stats_shadow=0;
+
+	// render the world
+	if (!just_setup)
+	{
+		
+		#ifdef __NOPT_ASSERT__
+		const int span = 60;
+		static int avg[span];
+		static int slot = 0;
+		Tmr::CPUCycles	start = Tmr::GetTimeInCPUCycles();
+		#endif
+
+		#if STENCIL_SHADOW
+		CGeomNode::sWorld.RenderWorld(*p_ctxt, RENDERFLAG_CLIP);
+		#else
+		CGeomNode::sWorld.RenderWorld(*p_ctxt, RENDERFLAG_CLIP | RENDERFLAG_SHADOW);
+		#endif
+		
+		
+		#ifdef __NOPT_ASSERT__		
+		Tmr::CPUCycles len = Tmr::GetTimeInCPUCycles() - start;
+		int ticks = len;
+		avg[slot++] = ticks;
+		if (slot == span) slot = 0;
+		int tot = 0;
+		for (int a=0;aGetItem((uint32)p_texture );
+//	if( p_details )
+	{
+		render::ShadowCameraPosition[0] = p_pos->GetX();
+		render::ShadowCameraPosition[1] = p_pos->GetY();
+		render::ShadowCameraPosition[2] = p_pos->GetZ();
+	}
+}
+#endif
+
+
+
+
+
+
+
+// Added by Mick
+// quick determination of if something is visible or not
+// uses the previously calculated s and c vectors
+// and the WorldToCamera transform
+// (note, no attempt is made to ensure this is the same camera that the object
+// will eventually be rendered with)
+bool	IsVisible(Mth::Vector ¢er, float radius)
+{
+
+
+	float x,y,z;
+	Mth::Vector v(center);
+
+	v[3] = 1.0f;		// needed?
+	v *= render::WorldToCamera;
+	x = v[0];
+	y = v[1];
+	z = v[2];
+
+	// if all outside view volume then cull
+	if (radiuspSubroutine);
+	vif::BASE(vu1::Loc);
+	vif::OFFSET(0);
+	vif::ITOP(RenderFlags);
+	dma::EndTag();
+	vu1::Loc += ((uint16 *)(pMesh->pSubroutine))[1];
+}
+
+
+void RenderEpilogue(void)
+{
+	sGroup *p_group;
+
+	// ---------------------------------------------
+	// tag onto end of particle group
+	dma::SetList(sGroup::pParticles);
+
+	// restore vu1 code (and buffering scheme) at end of particles group
+	dma::Tag(dma::ref, ((uint8 *)&MPGEnd-(uint8 *)&MPGStart+15)/16, (uint)&MPGStart);
+	vif::NOP();
+	vif::NOP();
+	dma::BeginTag(dma::cnt, 0);
+	vif::FLUSH();
+	vif::STMASK(0);
+	vif::STMOD(0);
+	vif::STCYCL(1,1);
+	vif::BASE(0);
+	vif::OFFSET(0);
+	vif::MSCAL(VU1_ADDR(Setup));
+	vu1::Loc = vu1::Buffer = 0;
+	dma::EndTag();
+
+	dma::SetList(NULL);
+	// ---------------------------------------------
+	
+	// add a GS SIGNAL primitive to each group
+	for (p_group=sGroup::pHead; p_group; p_group=p_group->pNext)
+	{
+		// end the dma list
+		dma::EndList(p_group);
+	}
+
+	dma::SetList(NULL);
+
+	// epilogue VRAM upload (fonts and sprites)
+
+	// Upload the fonts, so we can draw 2D text on screen	
+	sGroup::pEpilogue->pUpload[render::Field] = dma::pLoc;
+	uint32 *p = (uint32*)(dma::pLoc);
+	for (SFont *pFont=pFontList; pFont; pFont=pFont->pNext)
+	{
+	   	// Mick: we build this upload list directly, to cut down on the number of writes
+		*p++ = (dma::ref << 28) + ((pFont->VifSize+15)>>4); //		dma::Tag(dma::ref, (pFont->VifSize+15)>>4, (uint)pFont->pVifData);
+		*p++ = (uint32) pFont->pVifData;
+		*p++ = 0;    //		vif::NOP();
+		*p++ = 0;    //		vif::NOP();
+	}
+
+	// Upload the "Single Textures", the 2D screen elements (sprites)
+	for (SSingleTexture *pTexture=SSingleTexture::sp_stexture_list; pTexture; pTexture=pTexture->mp_next)
+	{
+		if (pTexture->IsInVRAM())
+		{
+			*p++ = (dma::call << 28) + (0); //	dma::Tag(dma::call, 0, (uint)pTexture->mp_VifData);
+			*p++ = (uint32) pTexture->mp_VifData;
+			*p++ = 0;    //		vif::NOP();
+			*p++ = 0;    //		vif::NOP();
+		}
+	}
+	dma::pLoc = (uint8*)p;
+
+	
+	// (?) writing to TEXFLUSH will stall the gs until all the fonts and sprites
+	// have been uploaded.
+	// since we are not double buffering anything now, we have to wait until
+	// everything is in VRAM before we can start rendering any 2D
+	// this is a potential GS hiccup, but removing it would be tricky
+	// and possibly not worth the effort
+	// Garrett: Now the 2D uses both buffers at once.
+		
+	dma::BeginTag(dma::end, 0);
+	vif::NOP();
+	vif::NOP();
+	gif::Tag2(gs::A_D,1,PACKED,0,0,1,1);
+	gs::Reg2(gs::TEXFLUSH,	0);
+	dma::EndTag();
+
+
+	// We need the scratchpad in SortGroup()
+	bool got_scratch = CSystemResources::sRequestResource(CSystemResources::vSCRATCHPAD_MEMORY);
+	Dbg_Assert(got_scratch);
+
+	// sort the dma lists of transparent groups
+	for (p_group=sGroup::pHead; p_group!=sGroup::pEpilogue; p_group=p_group->pNext)
+	{
+		if ((p_group->flags & GROUPFLAG_SORT) 	   // Mick, most groups are not SORT, so this test first is the fastest
+			&& p_group!=sGroup::pShadow
+			&& p_group->pScene 
+			&& (p_group->pScene->Flags & SCENEFLAG_USESPIP)
+			)
+		{
+			p_group->pRender[render::Field] = dma::SortGroup(p_group->pRender[render::Field]);
+		}
+	}
+
+	if (got_scratch)
+	{
+		CSystemResources::sFreeResource(CSystemResources::vSCRATCHPAD_MEMORY);
+	}
+
+	// swap epilogue and particle texture upload pointers (a cheat!)
+	uint8 *temp_ptr = sGroup::pParticles->pUpload[render::Field];
+	sGroup::pParticles->pUpload[render::Field] = sGroup::pEpilogue->pUpload[render::Field];
+	sGroup::pEpilogue->pUpload[render::Field] = temp_ptr;
+	
+	int size = (int)dma::pLoc - (int)dma::pBase;
+	static int max = 0;
+	static int max2 = 0;
+	if (size > max) max = size;
+	if (size > max2) max2 = size;
+	static int tick = 0;
+	tick++; 
+	tick &= 0x1f;
+	if (!tick)
+	{
+	  //  printf ("%6d, %6d, %6d (of %6d)\n",size,max2,max, dma::size);
+		max2 -= 50000;
+	}
+
+	Dbg_MsgAssert(DMAOverflowOK || size < (dma::size*98/100), ("DMA Buffer Overflow used %d\nShow screenshot to Mick (see .bmp saved, above)",size)); 
+	
+	if (DMAOverflowOK)
+	{
+		DMAOverflowOK--;
+	}
+	
+}
+
+
+void SendDisplayList(void)
+{
+
+// Mick:  Mike added this just before he left, but did not check it in
+//	sceGsSyncPath(0, 0);
+
+	// patch up the DMA list
+// Moved to StuffAfterGSFinished
+//	patch_texture_dma();	
+	
+	// Writeback data from D-Cache to Memory
+	FlushCache(WRITEBACK_DCACHE);
+
+#	ifdef __USE_PROFILER__
+// Initial render group does not cause an interrupt
+// so don't push context
+//			Sys::VUProfiler->PushContext( 80,80, 228 );	   									
+#	endif // __USE_PROFILER__
+	// kick prologue render
+	*D1_QWC  = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+	*D1_TADR = (uint)PrologueGroup.pRender[!render::Field];	// address of 1st tag
+	*D1_CHCR = 0x145;					// start transfer, tte=1, chain mode, from memory
+	sceGsSyncPath(0, 0);
+
+	#if USE_INTERRUPTS
+	
+	// use interrupts
+	sGroup::pUploadGroup = sGroup::pHead;
+	sGroup::pRenderGroup = sGroup::pUploadGroup;
+	
+
+//	SingleRender = 0;
+
+	#if 0  // debugging code to force all gorups to be executed, even if empty
+	sGroup * p_group = (sGroup*)sGroup::pUploadGroup; // Skip prologue and sky
+	while (p_group->pNext)		// all except the last one
+	{
+		p_group->Used[!render::Field] = 1;
+		p_group = p_group->pNext;
+	}
+	#endif
+
+
+	// Some debugging code to only render/upload a single selectable group
+	// so we can see what is taking all the time
+	if (SingleRender)
+	{
+		sGroup * p_group = (sGroup*)sGroup::pUploadGroup; // Skip prologue and sky
+		int g = 0;
+		while (p_group->pNext)		// all except the last one
+		{
+			g++;
+			if (g > 2 && g != SingleRender)
+			{
+					p_group->Used[!render::Field] = 0;			
+			}
+			p_group = p_group->pNext;
+		}
+	}
+	
+	UploadStalled  = 0;
+	RenderStalled  = 1;
+	FlushCache(WRITEBACK_DCACHE);
+
+#	ifdef __USE_PROFILER__
+	Sys::DMAProfiler->PushContext(  sGroup::pUploadGroup->profile_color );	   									
+#	endif // __USE_PROFILER__
+
+
+	// kick off first upload, should trigger all the rest by interrupt
+	*D2_QWC = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+	*D2_TADR = (uint)sGroup::pUploadGroup->pUpload[!render::Field];	// address of 1st tag
+	*D2_CHCR = 0x105;					// start transfer, tte=0, chain mode, from memory
+
+
+
+	#else
+	
+	// iterate over groups, uploading textures then rendering meshes in each
+	// last group will be epilogue group
+	for (sGroup *p_group=sGroup::pHead; p_group!=sGroup::pEpilogue; p_group=p_group->pNext)
+	{
+		if (p_group->Used[!render::Field])
+		{
+			// kick dma to upload textures from this group
+			*D2_QWC = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+			*D2_TADR = (uint)p_group->pUpload[!render::Field];	// address of 1st tag
+			*D2_CHCR = 0x105;					// start transfer, tte=0, chain mode, from memory
+			sceGsSyncPath(0, 0);
+
+			// kick dma to render scene
+			*D1_QWC = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+			*D1_TADR = (uint)p_group->pRender[!render::Field];	// address of 1st tag
+			*D1_CHCR = 0x145;					// start transfer, tte=1, chain mode, from memory
+			sceGsSyncPath(0, 0);
+		}
+	}
+
+	// kick dma to upload textures from this group
+	*D2_QWC = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+	*D2_TADR = (uint)sGroup::pEpilogue->pUpload[!render::Field];	// address of 1st tag
+	*D2_CHCR = 0x105;					// start transfer, tte=0, chain mode, from memory
+	sceGsSyncPath(0, 0);
+
+	// kick dma to render scene
+	*D1_QWC = 0;						// must zero QWC because the first action will be to use current MADR & QWC
+	*D1_TADR = (uint)sGroup::pEpilogue->pRender[!render::Field];	// address of 1st tag
+	*D1_CHCR = 0x145;					// start transfer, tte=1, chain mode, from memory
+	sceGsSyncPath(0, 0);
+	#endif
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+static bool sUpdate2DDma = false;
+static bool	sVRAMNeedsUpdating = false;
+
+
+void	Clear2DVRAM()
+{
+	// Reset VRAM pointer
+	FontVramBase = FontVramStart;
+
+	// And set flag
+	sUpdate2DDma = true;
+}
+
+void	VRAMNeedsUpdating()
+{
+	sVRAMNeedsUpdating = true;
+}
+
+void	Reallocate2DVRAM()
+{
+	if (!sVRAMNeedsUpdating)
+	{
+		return;
+	}
+
+	// Reset VRAM pointer
+	Clear2DVRAM();
+
+	for (SFont *pFont=pFontList; pFont; pFont=pFont->pNext)
+	{
+		pFont->ReallocateVRAM();
+	}
+
+	for (SSingleTexture *pTexture=SSingleTexture::sp_stexture_list; pTexture; pTexture=pTexture->mp_next)
+	{
+		pTexture->ReallocateVRAM();
+	}
+
+	// And set flags
+	sUpdate2DDma = true;
+	sVRAMNeedsUpdating = false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void	Update2DDMA()
+{
+	// Only go through the lists if it was requested
+	if (!sUpdate2DDma)
+		return;
+
+	// Go through each item
+	for (SFont *pFont=pFontList; pFont; pFont=pFont->pNext)
+	{
+		pFont->UpdateDMA();
+	}
+
+	for (SSingleTexture *pTexture=SSingleTexture::sp_stexture_list; pTexture; pTexture=pTexture->mp_next)
+	{
+		pTexture->UpdateDMA();
+	}
+
+	// Clear flag
+	sUpdate2DDma = false;
+}
+
+
+
+
+
+
+#if STENCIL_SHADOW
+
+void CastStencilShadow(CInstance *pInstance, sGroup *p_group, Mth::Vector &ShadowVec, Mth::Vector &TweakVec)
+{
+	sShadowVolumeHeader * p_shadow = pInstance->GetScene()->mp_shadow_volume_header;
+	if (!p_shadow)
+		return;
+
+	Mth::Matrix RootToFrustum,RootToViewport, WorldToRoot;
+
+	RootToFrustum = *(Mth::Matrix *)pInstance->GetTransform() * render::WorldToFrustum;
+	RootToViewport = RootToFrustum * render::FrustumToIntViewport;
+
+	WorldToRoot = *(Mth::Matrix *)pInstance->GetTransform();
+	WorldToRoot.Transpose();
+	ShadowVec *= WorldToRoot;
+	TweakVec *= WorldToRoot;
+
+	dma::SetList(sGroup::pShadow);
+
+	dma::BeginTag(dma::cnt, 0);
+	vu1::BeginPrim(ABS, VU1_ADDR(L_VF14));
+	vu1::StoreVec(*(Vec *)&ShadowVec);			// VF14, shadow vec
+	vu1::StoreVec(*(Vec *)&TweakVec);			// VF15, tweak vec
+	vu1::StoreMat(*(Mat *)&RootToViewport);		// VF16-19
+	vu1::EndPrim(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	vif::FLUSH();
+	dma::EndTag();
+
+	// send transforms
+	vu1::Loc = 0;
+	dma::Tag(dma::ref, pInstance->GetNumBones()<<2, (uint)pInstance->GetBoneTransforms());
+	vif::STCYCL(1,1);
+	vif::UNPACK(0,V4_32,pInstance->GetNumBones()<<2,ABS,SIGNED,0);
+
+	// send data
+	dma::BeginTag(dma::cnt, 0);
+	vif::STCYCL(7,21);
+
+	sShadowVertex * p_vertex = p_shadow->p_vertex;
+	sShadowConnect * p_connect = p_shadow->p_connect;
+	sShadowNeighbor * p_neighbor = p_shadow->p_neighbor;
+
+	int vert, num_faces;
+	for (uint32 i=0; inum_faces; i++)
+	{
+		if (i%36==0)
+		{
+			vu1::Loc = 256;
+			vif::BASE(256);
+			vif::OFFSET(0);
+			vif::MSCAL(VU1_ADDR(Setup));
+		}
+
+		if (i%12==0)
+		{
+			num_faces = p_shadow->num_faces-i;
+			if (num_faces>12)
+				num_faces=12;
+
+
+			vif::UNPACK(0, V4_32, 7*num_faces, ABS, UNSIGNED, 0);
+		}
+
+		if ((i%12==11) || (i==p_shadow->num_faces-1))
+		{
+			vif::StoreV4_32(0x3480800A, 0x21224000, 0x00000051, 0x8000);
+		}
+		else
+		{
+			vif::StoreV4_32(0x3480800A, 0x21224000, 0x00000051, 0x0000);
+		}
+
+		uint32 * p32 = (uint32 *)dma::pLoc;
+
+		vert = p_connect[i].corner[0];
+		*p32++ = *((uint32 *)(&p_vertex[vert].x));
+		*p32++ = *((uint32 *)(&p_vertex[vert].y));
+		*p32++ = *((uint32 *)(&p_vertex[vert].z));
+		*p32++ = *((uint32 *)(&p_vertex[vert].idx));
+
+		vert = p_connect[i].corner[1];
+		*p32++ = *((uint32 *)(&p_vertex[vert].x));
+		*p32++ = *((uint32 *)(&p_vertex[vert].y));
+		*p32++ = *((uint32 *)(&p_vertex[vert].z));
+		*p32++ = *((uint32 *)(&p_vertex[vert].idx));
+
+		vert = p_connect[i].corner[2];
+		*p32++ = *((uint32 *)(&p_vertex[vert].x));
+		*p32++ = *((uint32 *)(&p_vertex[vert].y));
+		*p32++ = *((uint32 *)(&p_vertex[vert].z));
+		*p32++ = *((uint32 *)(&p_vertex[vert].idx));
+
+		vert = p_neighbor[i].edge[0];
+		*p32++ = *((uint32 *)(&p_vertex[vert].x));
+		*p32++ = *((uint32 *)(&p_vertex[vert].y));
+		*p32++ = *((uint32 *)(&p_vertex[vert].z));
+		*p32++ = *((uint32 *)(&p_vertex[vert].idx));
+
+		vert = p_neighbor[i].edge[1];
+		*p32++ = *((uint32 *)(&p_vertex[vert].x));
+		*p32++ = *((uint32 *)(&p_vertex[vert].y));
+		*p32++ = *((uint32 *)(&p_vertex[vert].z));
+		*p32++ = *((uint32 *)(&p_vertex[vert].idx));
+
+		vert = p_neighbor[i].edge[2];
+		*p32++ = *((uint32 *)(&p_vertex[vert].x));
+		*p32++ = *((uint32 *)(&p_vertex[vert].y));
+		*p32++ = *((uint32 *)(&p_vertex[vert].z));
+		*p32++ = *((uint32 *)(&p_vertex[vert].idx));
+
+		dma::pLoc = (uint8 *)p32;
+
+		vu1::Loc += 21;
+
+		if ((i%12 == 11) || (i==p_shadow->num_faces-1))
+		{
+			vif::MSCAL(VU1_ADDR(ShadowVolumeSkin));
+		}
+	}
+
+	dma::EndTag();
+
+	dma::SetList(p_group);
+}
+
+#else
+
+void CastShadow(CInstance *pInstance, sGroup *p_group)
+{
+	int j;
+	sMesh *pMesh;
+	Mth::Matrix ProjMat, temp;
+
+	// set up projection matrix
+	ProjMat = *pInstance->GetTransform();
+	SkaterY = ProjMat[3][1];
+	ProjMat[3].Set(-36.0f/**SUB_INCH_PRECISION*/*ProjMat[1][0],
+				   -36.0f/**SUB_INCH_PRECISION*/*ProjMat[1][1],
+				   -36.0f/**SUB_INCH_PRECISION*/*ProjMat[1][2], 1.0f);
+	temp[0].Set(32.0f/**RECIPROCAL_SUB_INCH_PRECISION*/, 0.0f, 0.0f, 0.0f);
+	temp[1].Set(0.0f, 0.0f, 0.0f, 0.0f);
+	temp[2].Set(0.0f, 32.0f/**RECIPROCAL_SUB_INCH_PRECISION*/, 0.0f, 0.0f);
+	temp[3].Set(8421376.0f, 8421376.0f, 0.0f, 1.0f);
+	ProjMat *= temp;
+
+	dma::SetList(sGroup::pShadow);
+	dma::BeginTag(dma::cnt, 0);
+	vu1::BeginPrim(ABS, VU1_ADDR(L_VF12));
+	vu1::StoreMat(*(Mat *)&ProjMat);		// VF16-19
+	vu1::EndPrim(1);
+
+	vif::MSCAL(VU1_ADDR(Parser));
+
+	// not using pre-translate
+	// don't forget to switch off the offset mode on xyz unpack if using the row reg for somthing else
+	vif::STROW(0,0,0,0);
+
+	// get free run of the VUMem
+	vif::FLUSH();
+
+	dma::EndTag();
+
+	// send transforms
+	vu1::Loc = 0;
+	dma::Tag(dma::ref, pInstance->GetNumBones()<<2, (uint)pInstance->GetBoneTransforms());
+	vif::NOP();
+	vif::UNPACK(0,V4_32,pInstance->GetNumBones()<<2,ABS,SIGNED,0);
+
+	dma::Tag(dma::cnt, 0, 0);
+	vif::ITOP(pInstance->GetNumBones()<<2);
+	vif::MSCAL(VU1_ADDR(ReformatXforms));
+
+	// traverse array of meshes in the model
+	for (j=0,pMesh=p_group->pMeshes; jNumMeshes; j++,pMesh++)
+	{
+		// skip if invisible
+		if (!pMesh->IsActive())
+		{
+			continue;
+		}
+
+		// do nothing if mesh doesn't have a dma subroutine
+		if (!pMesh->pSubroutine)
+		{
+			continue;
+		}
+
+		// render the mesh
+		vu1::Loc = vu1::Buffer = 256;
+		dma::BeginTag(dma::call,(uint)pMesh->pSubroutine);
+		vif::BASE(256);
+		vif::OFFSET(0);
+		vif::MSCAL(VU1_ADDR(Setup));
+		vif::ITOP(SHDW);
+		vif::FLUSH();
+		dma::EndTag();
+		vu1::Loc += ((uint16 *)pMesh->pSubroutine)[1];
+	}
+
+	dma::Tag(dma::cnt, 0, 0);
+	vif::FLUSH();
+	vif::NOP();
+
+	dma::SetList(p_group);
+}
+
+#endif
+
+
+
+
+namespace render
+{
+	Mth::Matrix WorldToCamera;
+	Mth::Matrix WorldToCameraRotation;		// shouldn't need this
+	Mth::Matrix WorldToFrustum;
+	Mth::Matrix WorldToViewport;
+	Mth::Matrix WorldToIntViewport;
+	Mth::Matrix CameraToWorld;
+	Mth::Matrix CameraToWorldRotation;
+	Mth::Matrix CameraToFrustum;
+	Mth::Matrix CameraToViewport;
+	Mth::Matrix FrustumToViewport;
+	Mth::Matrix FrustumToIntViewport;
+	Mth::Matrix AdjustedWorldToCamera;
+	Mth::Matrix AdjustedWorldToFrustum;
+	Mth::Matrix AdjustedWorldToViewport;
+	Mth::Matrix AdjustedWorldToIntViewport;
+	Mth::Matrix SkyWorldToCamera;
+	Mth::Matrix SkyWorldToFrustum;
+	Mth::Matrix SkyWorldToViewport;
+	Mth::Matrix SkyFrustumToViewport;
+
+	Mth::Vector ViewportScale;
+	Mth::Vector ViewportOffset;
+	Mth::Vector IntViewportScale;
+	Mth::Vector IntViewportOffset;
+	Mth::Vector InverseViewportScale;
+	Mth::Vector InverseViewportOffset;
+	Mth::Vector InverseIntViewportScale;
+	Mth::Vector InverseIntViewportOffset;
+	Mth::Vector SkyViewportScale;
+	Mth::Vector SkyViewportOffset;
+	Mth::Vector SkyInverseViewportScale;
+	Mth::Vector SkyInverseViewportOffset;
+	Mth::Vector RowReg;
+	Mth::Vector RowRegI;
+	Mth::Vector RowRegF;
+	#if !STENCIL_SHADOW
+	Mth::Vector ShadowCameraPosition;
+	#endif
+	Mth::Vector AltFrustum;
+	Mth::Vector CameraPosition;
+
+	uint64 reg_XYOFFSET;
+	uint64 reg_SCISSOR;
+
+	float sx,sy,cx,cy,tx,ty,Sx,Sy,Cx,Cy,Tx,Ty,Near,Far;
+	//float sMultipassMaxDist = 1000000000.0f;
+	float sMultipassMaxDist = 4000.0f;
+
+	uint Frame, Field;
+	uint ViewportMask;
+
+	uint RenderVarFrame = 0xFFFFFFFF; 
+	uint RenderVarViewport = 0xFFFFFFFF;
+
+	bool FrustumFlagsPlus[3], FrustumFlagsMinus[3];
+
+	int sSortedListMarker[MAX_SORTED_LIST_MARKERS];
+	int sMarkerIndex;
+	int sTime;
+	int sTotalNewParticles;
+
+	float FogNear=3000.0f, FogAlpha=0.80f;
+	uint32 FogCol=0xFF4070;
+	bool EnableFog=false;
+	float EffectiveFogAlpha;
+
+	uint32 *p_patch_FOGCOL;
+
+	uint8 ShadowDensity = 48;
+}
+
+
+
+void render::SetupVars(uint viewport_num, const Mth::Matrix& camera_transform, const Mth::Rect& viewport_rec, float view_angle,
+					   float viewport_aspect, float near, float far)
+{
+
+	// fog variables
+	EffectiveFogAlpha = FogAlpha;
+	if (FogAlpha < 0.003f)
+	{
+		EffectiveFogAlpha = 0.003f;
+	}
+	if (FogAlpha > 0.997f)
+	{
+		EffectiveFogAlpha = 0.997f;
+	}
+	if (!EnableFog)		// TT5158: ZPush is affected by FogAlpha, so having a very low number causes z-fighting.  Garrett
+	{
+		EffectiveFogAlpha = 0.75f;
+	}
+	*p_patch_FOGCOL = FogCol;
+
+
+	// Check if we already set up the current variables
+	if (VarsUpToDate(viewport_num))
+	{
+		return;
+	}
+
+	// camera transform
+	CameraToWorld = camera_transform;
+
+	Dbg_MsgAssert(CameraToWorld[0][3] == 0.0f, ("CameraToWorld[0][3] is %f, not 0.0", CameraToWorld[0][3]));
+	Dbg_MsgAssert(CameraToWorld[1][3] == 0.0f, ("CameraToWorld[1][3] is %f, not 0.0", CameraToWorld[1][3]));
+	Dbg_MsgAssert(CameraToWorld[2][3] == 0.0f, ("CameraToWorld[2][3] is %f, not 0.0", CameraToWorld[2][3]));
+	Dbg_MsgAssert(CameraToWorld[3][3] == 1.0f, ("CameraToWorld[3][3] is %f, not 1.0", CameraToWorld[3][3]));
+
+	// need the transform to be well-formed as a 4x4 matrix
+	//CameraToWorld[0][3] = 0.0f;
+	//CameraToWorld[1][3] = 0.0f;
+	//CameraToWorld[2][3] = 0.0f;
+	//CameraToWorld[3][3] = 1.0f;
+
+	CameraToWorldRotation = CameraToWorld;
+	CameraToWorldRotation[3].Set(0.0f, 0.0f, 0.0f, 1.0f);
+
+	// world view transform
+	WorldToCameraRotation.Transpose(CameraToWorldRotation);
+	WorldToCamera = CameraToWorld;
+	WorldToCamera.InvertUniform();
+
+	// massage to get ROW register value
+	RowReg = SUB_INCH_PRECISION * WorldToCamera[3] * CameraToWorldRotation;
+	RowRegI[0] = (float)(int)RowReg[0];
+	RowRegI[1] = (float)(int)RowReg[1];
+	RowRegI[2] = (float)(int)RowReg[2];
+	RowRegI[3] = 0.0f;
+	RowRegF = RowReg - RowRegI;
+	RowRegF[3] = 0.0f;
+
+	// massage to get modified matrix
+	AdjustedWorldToCamera = WorldToCamera;
+	AdjustedWorldToCamera[3] = RowRegF * WorldToCameraRotation;
+	AdjustedWorldToCamera[3] *= RECIPROCAL_SUB_INCH_PRECISION;
+	AdjustedWorldToCamera[3][3] = 1.0f;
+
+	// view frustum
+	tx = tanf(view_angle*8.72664626e-03f);		// tan of half (angle in radians)
+	ty = -(tx / viewport_aspect);
+	sx	= 1.0f/sqrtf(1.0f+1.0f/(tx*tx));
+	sy	= 1.0f/sqrtf(1.0f+1.0f/(ty*ty));
+	cx	= 1.0f/sqrtf(1.0f+tx*tx);
+	cy	= 1.0f/sqrtf(1.0f+ty*ty);
+	Near = near;
+	Far  = far;
+
+	// outer frustum
+	Tx = tx * (620.0f/(((float) HRES) * 0.5f * viewport_rec.GetWidth()));
+	Ty = ty * (524.0f/(((float) VRES) * 0.5f * viewport_rec.GetHeight()));
+	Sx	= 1.0f/sqrtf(1.0f+1.0f/(Tx*Tx));
+	Sy	= 1.0f/sqrtf(1.0f+1.0f/(Ty*Ty));
+	Cx	= 1.0f/sqrtf(1.0f+Tx*Tx);
+	Cy	= 1.0f/sqrtf(1.0f+Ty*Ty);
+
+	// frustum transform
+	CameraToFrustum.Zero();
+	CameraToFrustum[0][0] = 1.0f/Tx;
+	CameraToFrustum[1][1] = 1.0f/Ty;
+	CameraToFrustum[2][2] = (near+far) / (far-near);
+	CameraToFrustum[3][2] = 2.0f * near * far / (near-far);
+	CameraToFrustum[2][3] = -1.0f;
+	AdjustedWorldToFrustum = AdjustedWorldToCamera * CameraToFrustum;
+	WorldToFrustum = WorldToCamera * CameraToFrustum;
+
+	// viewport (scale and offset from outer frustum)
+	ViewportScale.Set( 620.0f, 524.0f, 0.999f*powf(2,22), EffectiveFogAlpha);
+	ViewportOffset.Set(2048.0f, 2048.0f, 1.5f*powf(2,23), 0);
+	IntViewportScale.Set(620.0f, 524.0f, 0.999f*powf(2,-95), powf(2,32));
+	IntViewportOffset.Set(2048.0f+powf(2,19), 2048.0f+powf(2,19), 1.5f*powf(2,-94), powf(2,-32));
+
+	// viewport transform
+	FrustumToViewport.Zero();
+	FrustumToViewport[0][0] = ViewportScale[0];
+	FrustumToViewport[1][1] = ViewportScale[1];
+	FrustumToViewport[2][2] = ViewportScale[2];
+	FrustumToViewport[3][3] = ViewportScale[3];
+	FrustumToViewport[3][0] = ViewportOffset[0];
+	FrustumToViewport[3][1] = ViewportOffset[1];
+	FrustumToViewport[3][2] = ViewportOffset[2];
+	AdjustedWorldToViewport = AdjustedWorldToFrustum * FrustumToViewport;
+	WorldToViewport = WorldToFrustum * FrustumToViewport;
+	CameraToViewport = CameraToFrustum * FrustumToViewport;
+
+	FrustumToIntViewport.Zero();
+	FrustumToIntViewport[0][0] = IntViewportScale[0];
+	FrustumToIntViewport[1][1] = IntViewportScale[1];
+	FrustumToIntViewport[2][2] = IntViewportScale[2];
+	FrustumToIntViewport[3][3] = IntViewportScale[3];
+	FrustumToIntViewport[3][0] = IntViewportOffset[0];
+	FrustumToIntViewport[3][1] = IntViewportOffset[1];
+	FrustumToIntViewport[3][2] = IntViewportOffset[2];
+	AdjustedWorldToIntViewport = AdjustedWorldToFrustum * FrustumToIntViewport;
+	WorldToIntViewport = WorldToFrustum * FrustumToIntViewport;
+
+
+	// sky transforms
+	//SkyViewportScale.Set(1900.0f, 1900.0f, powf(2,-102), powf(2,32));		// convert these to constants
+	//SkyViewportOffset.Set(2048.0f+powf(2,19), 2048.0f+powf(2,19), 1.5f*powf(2,-101), powf(2,-32));	// convert these to constants
+	SkyViewportScale.Set( 620.0f, 524.0f, 0.01f, EffectiveFogAlpha);
+	SkyViewportOffset.Set(2048.0f, 2048.0f, 1.0f, 0);
+
+	SkyWorldToCamera = AdjustedWorldToCamera;
+	SkyWorldToCamera[3][0] = 0;
+	SkyWorldToCamera[3][1] = 0;
+	SkyWorldToCamera[3][2] = 0;
+	SkyWorldToFrustum = SkyWorldToCamera * CameraToFrustum;
+	SkyFrustumToViewport = FrustumToViewport;
+	SkyFrustumToViewport[2][2] = SkyViewportScale[2];
+	SkyFrustumToViewport[3][2] = SkyViewportOffset[2];
+	SkyWorldToViewport = SkyWorldToFrustum * SkyFrustumToViewport;
+
+	// used in 3D clipping
+	AltFrustum[0] = Near;
+	AltFrustum[1] = Far;
+	AltFrustum[2] = 620.0f/(((float) HRES) * 0.5f * viewport_rec.GetWidth());
+	AltFrustum[3] = 524.0f/(((float) VRES) * 0.5f * viewport_rec.GetHeight());
+
+	// invert the viewport scale and offset
+	InverseViewportScale[0]  = 1.0f / ViewportScale[0];
+	InverseViewportScale[1]  = 1.0f / ViewportScale[1];
+	InverseViewportScale[2]  = 1.0f / ViewportScale[2];
+	InverseViewportScale[3]  = 1.0f / ViewportScale[3];
+
+	InverseViewportOffset[0] = -InverseViewportScale[0] * ViewportOffset[0] * InverseViewportScale[3];
+	InverseViewportOffset[1] = -InverseViewportScale[1] * ViewportOffset[1] * InverseViewportScale[3];
+	InverseViewportOffset[2] = -InverseViewportScale[2] * ViewportOffset[2] * InverseViewportScale[3];
+	InverseViewportOffset[3] = -InverseViewportScale[3] * ViewportOffset[3] * InverseViewportScale[3];
+	InverseViewportOffset[3] = FogNear;
+	
+	InverseIntViewportScale[0]  = 1.0f / IntViewportScale[0];
+	InverseIntViewportScale[1]  = 1.0f / IntViewportScale[1];
+	InverseIntViewportScale[2]  = 1.0f / IntViewportScale[2];
+	InverseIntViewportScale[3]  = 1.0f / IntViewportScale[3];
+
+	InverseIntViewportOffset[0] = -InverseIntViewportScale[0] * IntViewportOffset[0] * InverseIntViewportScale[3];
+	InverseIntViewportOffset[1] = -InverseIntViewportScale[1] * IntViewportOffset[1] * InverseIntViewportScale[3];
+	InverseIntViewportOffset[2] = -InverseIntViewportScale[2] * IntViewportOffset[2] * InverseIntViewportScale[3];
+	InverseIntViewportOffset[3] = 0.0f;
+	
+	SkyInverseViewportScale[0]  = 1.0f / SkyViewportScale[0];
+	SkyInverseViewportScale[1]  = 1.0f / SkyViewportScale[1];
+	SkyInverseViewportScale[2]  = 1.0f / SkyViewportScale[2];
+	SkyInverseViewportScale[3]  = 1.0f / SkyViewportScale[3];
+
+	SkyInverseViewportOffset[0] = -SkyInverseViewportScale[0] * SkyViewportOffset[0] * SkyInverseViewportScale[3];
+	SkyInverseViewportOffset[1] = -SkyInverseViewportScale[1] * SkyViewportOffset[1] * SkyInverseViewportScale[3];
+	SkyInverseViewportOffset[2] = -SkyInverseViewportScale[2] * SkyViewportOffset[2] * SkyInverseViewportScale[3];
+	SkyInverseViewportOffset[3] = 0.0f;
+	
+
+
+	// generate the GS register values we'll need for the viewport
+	reg_XYOFFSET = PackXYOFFSET((int)(16.0f * (2048.0f - ((float) HRES) * (viewport_rec.GetOriginX() + 0.5f * viewport_rec.GetWidth()))),
+								(int)(16.0f * (2048.0f - ((float) VRES) * (viewport_rec.GetOriginY() + 0.5f * viewport_rec.GetHeight()))));
+	reg_SCISSOR  = PackSCISSOR( (int)(((float) HRES) * viewport_rec.GetOriginX()),
+								(int)(((float) HRES) * (viewport_rec.GetOriginX() + viewport_rec.GetWidth())) - 1,
+								(int)(((float) VRES) * viewport_rec.GetOriginY()),
+								(int)(((float) VRES) * (viewport_rec.GetOriginY() + viewport_rec.GetHeight())) - 1);
+
+
+	// inverse frustum transform
+	//ViewFrustumToCamera.Zero();
+	//ViewFrustumToCamera[0][0] = tx;
+	//ViewFrustumToCamera[1][1] = ty;
+	//ViewFrustumToCamera[2][3] = (near - far) / (2.0f * near * far);
+	//ViewFrustumToCamera[3][2] = -1.0f;
+	//ViewFrustumToCamera[3][3] = -(near + far) / (2.0f * near * far);
+
+	// get world coords of the far frustum vertices
+	Mth::Vector vFTL, vFTR, vFBL, vFBR;
+	vFTL.Set( tx*far, ty*far, far, 1.0f);
+	vFTL *= CameraToWorld;
+	vFTR.Set(-tx*far, ty*far, far, 1.0f);
+	vFTR *= CameraToWorld;
+	vFBL.Set( tx*far,-ty*far, far, 1.0f);
+	vFBL *= CameraToWorld;
+	vFBR.Set(-tx*far,-ty*far, far, 1.0f);
+	vFBR *= CameraToWorld;
+
+	Mth::Vector pos(WorldToCamera[3]);
+	for (int i=0; i<3; i++)
+	{
+		FrustumFlagsPlus[i]  = (vFTL[i]>=pos[i] && vFTR[i]>=pos[i] && vFBL[i]>=pos[i] && vFBR[i]>=pos[i]);
+		FrustumFlagsMinus[i] = (vFTL[i]<=pos[i] && vFTR[i]<=pos[i] && vFBL[i]<=pos[i] && vFBR[i]<=pos[i]);
+	}
+
+
+    // Set ViewportMask.  Will only draw CGeomNodes that have a non-zero result with (ViewportMask & Visibility)
+	ViewportMask = (1 << (VISIBILITY_FLAG_BIT + viewport_num));
+
+	// Mark current viewport and frame
+	RenderVarFrame = Frame;
+	RenderVarViewport = viewport_num;
+
+}
+
+void	render::SetupVarsDMA(uint viewport_num, float near, float far)
+{
+
+
+	// initialisation for new bounding volume tests
+	Mth::Matrix Normals;
+
+	asm ("vmaxw vf31,vf00,vf00w": :);
+
+	// view frustum	 R,  L,  B,  T
+	Normals[0].Set(-cx, cx,  0,  0);
+	Normals[1].Set(  0,  0, cy,-cy);
+	Normals[2].Set(-sx,-sx,-sy,-sy);
+	Normals[3].Set(  0,  0,  0,  0);
+
+	Normals = WorldToCamera * Normals;
+
+	asm __volatile__("
+
+		lqc2		vf10,(%0)
+		lqc2		vf11,(%1)
+		lqc2		vf12,(%2)
+		lqc2		vf16,(%3)
+		vabs		vf13,vf10
+		vabs		vf14,vf11
+		vabs		vf15,vf12
+
+	": : "r" (&Normals[0]), "r" (&Normals[1]), "r" (&Normals[2]), "r" (&Normals[3]));
+
+	// both frusta    ?,   ?    N,   F,
+	Normals[0].Set(   0,   0,   0,   0);
+	Normals[1].Set(   0,   0,   0,   0);
+	Normals[2].Set(   0,   0,  -1,   1);
+	Normals[3].Set(   0,   0,near,-far);
+
+	Normals = WorldToCamera * Normals;
+
+	asm __volatile__("
+
+		lqc2		vf17,(%0)
+		lqc2		vf18,(%1)
+		lqc2		vf19,(%2)
+		lqc2		vf23,(%3)
+		vabs		vf20,vf17
+		vabs		vf21,vf18
+		vabs		vf22,vf19
+
+	": : "r" (&Normals[0]), "r" (&Normals[1]), "r" (&Normals[2]), "r" (&Normals[3]));
+
+
+	// outer frustum R,  L,  B,  T
+	Normals[0].Set(-Cx, Cx,  0,  0);
+	Normals[1].Set(  0,  0, Cy,-Cy);
+	Normals[2].Set(-Sx,-Sx,-Sy,-Sy);
+	Normals[3].Set(  0,  0,  0,  0);
+
+	Normals = WorldToCamera * Normals;
+
+	asm __volatile__("
+
+		lqc2		vf24,(%0)
+		lqc2		vf25,(%1)
+		lqc2		vf26,(%2)
+		lqc2		vf30,(%3)
+		vabs		vf27,vf24
+		vabs		vf28,vf25
+		vabs		vf29,vf26
+
+	": : "r" (&Normals[0]), "r" (&Normals[1]), "r" (&Normals[2]), "r" (&Normals[3]));
+
+
+
+
+	#if OLD_FOG
+	// link in the dma list that does fogging postprocessing
+	dma::SetList(sGroup::pFog);
+	dma::Tag(dma::call, 0, (uint)&FogDma);
+	vif::NOP();
+	vif::NOP();
+	sGroup::pFog->Used[render::Field] = true;
+
+	dma::Gosub(SET_RENDERSTATE,2);
+	dma::Gosub(SET_FOGCOL,2);
+	#endif
+
+
+	// upload new vu1 code and set up double-buffering
+	dma::SetList(sGroup::pParticles);
+	dma::Tag(dma::ref, ((uint8 *)&NewMPGEnd-(uint8 *)&NewMPGStart+15)/16, (uint)&NewMPGStart);
+	vif::BASE(0);
+	vif::OFFSET(32);
+	
+	// send vu1 data for particle setup
+	dma::BeginTag(dma::cnt, 0);
+	vif::FLUSH();
+	vif::BeginDIRECT();
+	gif::Tag2(gs::A_D,1,PACKED,0,0,1,3);
+	gs::Reg2(gs::SCISSOR_1, render::reg_SCISSOR);
+	gs::Reg2(gs::XYOFFSET_1, render::reg_XYOFFSET);
+	gs::Reg2(gs::ZBUF_1, PackZBUF(ZBUFFER_START,PSMZ24,1));
+	vif::EndDIRECT();
+	vif::UNPACK(0, V4_32, 7, REL, SIGNED, 0);
+	vu1::StoreMat(*(Mat *)&render::WorldToIntViewport);			// view transform
+	vif::StoreV4_32F(320.0f, 320.0f, 0.0f, 0.0f);				// kvec
+	vif::StoreV4_32(0x39005000, 0x39006000, 0, 0x3F800000);		// NTL cull test vec - w-component limits tan of apparent radius
+	vif::StoreV4_32(0x3900B000, 0x3900A000, 0, 0);				// BR cull test vec
+	vif::MSCAL(8);		// init sprites
+	dma::EndTag();
+	dma::SetList(NULL);
+}
+
+
+
+// Checks to see if the render variables are current
+bool render::VarsUpToDate(uint viewport_num)
+{
+	return (RenderVarFrame == Frame) && (RenderVarViewport == viewport_num);
+}
+
+// Invalidates the render variables
+void render::InvalidateVars()
+{
+	RenderVarFrame = 0xFFFFFFFF; 
+	RenderVarViewport = 0xFFFFFFFF;
+}
+
+// a quick hack for Dave...
+void DrawRectangle(int x0, int y0, int x1, int y1, uint32 rgba)
+{
+	dma::SetList(sGroup::pEpilogue);
+	dma::BeginTag(dma::cnt, 0);
+	vif::FLUSH();
+	vif::BeginDIRECT();
+	gif::BeginTag2(gs::A_D, 1, PACKED, SPRITE|ABE, 1);
+	gs::Reg2(gs::ALPHA_1,	PackALPHA(0,1,0,1,0));
+	gs::Reg2(gs::RGBAQ,		(uint64)rgba);
+	gs::Reg2(gs::XYZ2,		PackXYZ(x0,y0,0xFFFFFF));
+	gs::Reg2(gs::XYZ2,		PackXYZ(x1,y1,0xFFFFFF));
+	gif::EndTag2(1);
+	vif::EndDIRECT();
+	dma::EndTag();
+}
+
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/render.h b/Code/Gfx/NGPS/NX/render.h
new file mode 100644
index 0000000..cba23cb
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/render.h
@@ -0,0 +1,144 @@
+#ifndef __RENDER_H
+#define __RENDER_H
+
+
+#include "mikemath.h"
+#include 
+
+namespace NxPs2
+{
+
+
+#define SUB_INCH_PRECISION 16.0f
+#define RECIPROCAL_SUB_INCH_PRECISION 0.0625f
+
+
+typedef struct
+{
+	float tx,ty;
+	float n,f;
+}
+sFrustum;
+
+
+void RenderPrologue(void);
+void RenderInitImmediateMode();
+
+void RenderWorld(Mth::Matrix* camera_orient, Mth::Vector* camera_pos, float view_angle, float screen_aspect);
+void RenderEpilogue(void);
+
+void RenderSwitchImmediateMode();
+
+void RenderViewport(uint viewport_num, const Mth::Matrix& camera_transform, const Mth::Rect & viewport_rec, float view_angle,
+					float viewport_aspect, float nearz, float farz, bool just_setup=false);
+
+bool IsVisible(Mth::Vector ¢er, float radius);
+void UpdateCamera(Mat *camera_orient, Vec *camera_pos);
+void SendDisplayList(void);
+void RenderMesh(struct sMesh *pMesh, uint RenderFlags);
+void SkinTest(void);
+void SetTextureProjectionCamera( Mth::Vector *p_pos, Mth::Vector *p_at );
+void DrawRectangle(int x0, int y0, int x1, int y1, uint32 rgba);
+
+void Clear2DVRAM();
+void VRAMNeedsUpdating();
+void Reallocate2DVRAM();
+void Update2DDMA();
+
+void EnableFlipCopy(bool flip);
+bool FlipCopyEnabled();
+
+
+extern uint  Frame, Field;
+extern Mat SkaterTransform;
+extern Mat BoneTransforms[64];
+extern int NumBones;
+
+extern int	SingleRender;
+extern bool DoLetterbox;
+extern int	DMAOverflowOK;
+
+
+
+
+namespace render
+{
+	void SetupVars(uint viewport_num, const Mth::Matrix& camera_transform, const Mth::Rect& viewport_rec, float view_angle,
+				   float viewport_aspect, float near_z, float far_z);
+#ifndef __PLAT_WN32__
+	void SetupVarsDMA(uint viewport_num, float near, float far);	
+#endif		// __PLAT_WN32__
+	bool VarsUpToDate(uint viewport_num);
+	void InvalidateVars();
+
+	void OldStyle();
+	void NewStyle(bool just_setup = false);
+
+	extern Mth::Matrix WorldToCamera;
+	extern Mth::Matrix WorldToCameraRotation;
+	extern Mth::Matrix WorldToFrustum;
+	extern Mth::Matrix WorldToViewport;
+	extern Mth::Matrix WorldToIntViewport;
+	extern Mth::Matrix CameraToWorld;
+	extern Mth::Matrix CameraToWorldRotation;
+	extern Mth::Matrix CameraToFrustum;
+	extern Mth::Matrix CameraToViewport;
+	extern Mth::Matrix FrustumToViewport;
+	extern Mth::Matrix FrustumToIntViewport;
+	extern Mth::Matrix AdjustedWorldToCamera;
+	extern Mth::Matrix AdjustedWorldToFrustum;
+	extern Mth::Matrix AdjustedWorldToViewport;
+	extern Mth::Matrix AdjustedWorldToIntViewport;
+	extern Mth::Matrix SkyWorldToCamera;
+	extern Mth::Matrix SkyWorldToFrustum;
+	extern Mth::Matrix SkyWorldToViewport;
+	extern Mth::Matrix SkyFrustumToViewport;
+
+	extern Mth::Vector ViewportScale;
+	extern Mth::Vector ViewportOffset;
+	extern Mth::Vector IntViewportScale;
+	extern Mth::Vector IntViewportOffset;
+	extern Mth::Vector InverseViewportScale;
+	extern Mth::Vector InverseViewportOffset;
+	extern Mth::Vector InverseIntViewportScale;
+	extern Mth::Vector InverseIntViewportOffset;
+	extern Mth::Vector SkyViewportScale;
+	extern Mth::Vector SkyViewportOffset;
+	extern Mth::Vector SkyInverseViewportScale;
+	extern Mth::Vector SkyInverseViewportOffset;
+	extern Mth::Vector RowReg;
+	extern Mth::Vector RowRegI;
+	extern Mth::Vector RowRegF;
+	extern Mth::Vector ShadowCameraPosition;
+	extern Mth::Vector AltFrustum;
+	extern Mth::Vector CameraPosition;
+
+	extern uint64 reg_XYOFFSET;
+	extern uint64 reg_SCISSOR;
+
+	extern float sx,sy,cx,cy,tx,ty,Sx,Sy,Cx,Cy,Tx,Ty,Near,Far;
+	extern float sMultipassMaxDist;
+
+	extern uint Frame, Field;
+	extern uint ViewportMask;
+
+	extern bool FrustumFlagsPlus[3], FrustumFlagsMinus[3];
+
+	#define MAX_SORTED_LIST_MARKERS 32
+	extern int sSortedListMarker[MAX_SORTED_LIST_MARKERS];
+	extern int sMarkerIndex;
+	extern int sTime;
+	extern int sTotalNewParticles;
+
+	extern float FogNear, FogAlpha;
+	extern uint32 FogCol;
+	extern uint32 *p_patch_FOGCOL;
+	extern bool EnableFog;
+	extern float EffectiveFogAlpha;
+	extern uint8 ShadowDensity;
+}
+
+
+} // namespace NxPs2
+
+#endif // __RENDER_H
diff --git a/Code/Gfx/NGPS/NX/resource.cpp b/Code/Gfx/NGPS/NX/resource.cpp
new file mode 100644
index 0000000..738addb
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/resource.cpp
@@ -0,0 +1,61 @@
+#include 
+#include 
+#include "resource.h"
+
+namespace NxPs2
+{
+
+
+bool			CSystemResources::s_resource_used[vNUM_RESOURCES];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 			CSystemResources::sInitResources()
+{
+	for (int i = 0; i < vNUM_RESOURCES; i++)
+	{
+		s_resource_used[i] = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool 			CSystemResources::sRequestResource(EResourceType resource)
+{
+	Dbg_Assert(resource < vNUM_RESOURCES);
+	
+	bool old_result;
+
+	// We need to do this with interrupts off, just in case
+	DI();
+	old_result = s_resource_used[resource];
+	s_resource_used[resource] = true;			// We know it is reserved now
+	EI();
+
+	return !old_result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 			CSystemResources::sFreeResource(EResourceType resource)
+{
+	Dbg_Assert(resource < vNUM_RESOURCES);
+	Dbg_Assert(s_resource_used[resource]);
+
+	// We need to do this with interrupts off, just in case
+	DI();
+	s_resource_used[resource] = false;
+	EI();
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/resource.h b/Code/Gfx/NGPS/NX/resource.h
new file mode 100644
index 0000000..5ecc630
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/resource.h
@@ -0,0 +1,42 @@
+#ifndef __RESOURCE_H
+#define __RESOURCE_H
+
+#include	
+#include	
+
+namespace NxPs2
+{
+
+// This static class locks certain system resources (like scratchpad and VU0 memory)
+// for code to use exclusively.  Right now, there is no mechanism to lock out just
+// parts of memory, but it may be added in the future if needed.
+class CSystemResources
+{
+
+public:
+	// Resource types
+	enum EResourceType {
+		vVU0_MEMORY = 0,
+		vSCRATCHPAD_MEMORY,
+		vNUM_RESOURCES
+	};
+
+	//-------------------------------------------
+
+	static void sInitResources();								// Call only at the beginning!
+
+	static bool sRequestResource(EResourceType resource); 		// Returns true if it was able to lock it
+	static void sFreeResource(EResourceType resource);
+
+private:
+	static bool s_resource_used[vNUM_RESOURCES];
+
+};
+
+
+} // namespace NxPs2
+
+
+
+#endif // __RESOURCE_H
+
diff --git a/Code/Gfx/NGPS/NX/scene.cpp b/Code/Gfx/NGPS/NX/scene.cpp
new file mode 100644
index 0000000..27318fe
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/scene.cpp
@@ -0,0 +1,295 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "material.h"
+#include "mesh.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "scene.h"
+#include "mikemath.h"
+#include "vu1code.h"
+#include "switches.h"
+#include 
+#include 
+
+#include 
+
+namespace NxPs2
+{
+
+bool load_scene(uint8* pData, int dataSize, sScene *pScene, bool IsSkin, bool IsInstanceable, uint32 texDictOffset, bool doShadowVolume)
+{
+	pScene->mp_shadow_volume_header = NULL;
+
+	// make sure there's no dma list running
+	sceGsSyncPath(0,0);
+
+	// Load the scene file onto the dma buffer so we can use it as a memory mapped file
+	void *pMemory = pData;
+	void *pFile = pMemory;
+	
+	int	mesh_size = dataSize * 10/100 + 2000;		// adding the 2000 to cope with very small files						
+										
+	// version numbers
+	uint32 vert_version;
+	MEM_Read(&sMaterial::Version, sizeof(uint32), 1, pFile);
+	MEM_Read(&sMesh::Version, sizeof(uint32), 1, pFile);
+	MEM_Read(&vert_version, sizeof(uint32), 1, pFile);
+
+	// allocate and import materials
+	pFile = LoadMaterials(pFile, pScene, texDictOffset);
+
+	// get number of mesh groups
+	int NumGroups;
+	MEM_Read(&NumGroups, sizeof(uint32), 1, pFile);
+	if ( NumGroups != pScene->NumGroups )
+	{
+		Dbg_Message( "Scene mismatch in number of groups" );
+		return false;
+	}
+
+	int total_num_meshes = 0;
+	// get number of meshes and allocate storage for meshes
+	if (sMesh::Version >= 4)
+	{
+		MEM_Read(&total_num_meshes, sizeof(uint32), 1, pFile);
+		mesh_size = total_num_meshes * sizeof(sMesh);
+	}
+	
+	if ( pScene->NumMeshes != 0 )
+	{
+		Dbg_Message( "pScene->NumMeshes is %d when loading scene, texDictOffset = %d", pScene->NumMeshes, texDictOffset );
+		return false;
+	}
+										  
+	pScene->pMeshes = (sMesh *)Mem::Malloc(mesh_size);
+
+	if ( pScene->pMeshes == NULL )
+	{
+		Dbg_Message( "couldn't allocate memory for meshes" );
+		return false;
+	}
+
+	// allocate prebuilt dma buffer
+//	int	dma_size = Mem::Available()-128;
+
+	int	dma_size = dataSize;  // Really should be around 80%, based on observations, but we know it's no bigger
+	
+	if (dma_size < 300000)
+	{
+		dma_size = dma_size *110/100;	 	// Patch for ironman problem
+	}
+			 
+	pScene->pMeshDma = (uint8 *)Mem::Malloc(dma_size);
+	if ( pScene->pMeshDma == NULL )
+	{
+		Dbg_Message( "couldn't allocate memory for mesh dma" );
+		return false;
+	}
+	
+	// set dma buffer
+	dma::pLoc = pScene->pMeshDma;
+
+	// allocate and import meshes
+	sMesh::TotalNumVertices=0;
+	pFile = LoadMeshes(pFile, pScene, IsSkin, IsInstanceable, texDictOffset);
+
+//	printf ("DMA Mesh (used %d,  file size = %d, NumMeshes = %d, total_num_meshes = %d, available %d)\n",
+//	(int)(dma::pLoc-pScene->pMeshDma), dataSize, pScene->NumMeshes, total_num_meshes, dma_size);
+	
+	Dbg_MsgAssert( (int)(dma::pLoc-pScene->pMeshDma) < dma_size,("DMA overflow all available memory (used %d, available %d)\n",(int)(dma::pLoc-pScene->pMeshDma),dma_size));
+	
+	
+	if ( (int)(pScene->NumMeshes*sizeof(sMesh)) > mesh_size )
+	{
+		Dbg_Message( "Mesh overflow file = %s, file size = %d, guessed = %d, actually %d", dataSize, mesh_size, pScene->NumMeshes * sizeof(sMesh) );
+		return false;
+	}
+										
+	// reduce dma memory to fit
+	Mem::Manager::sHandle().ReallocateShrink( dma::pLoc-pScene->pMeshDma, pScene->pMeshDma);
+	
+	// reduce mesh memory to fit
+	if (sMesh::Version < 4)
+	{
+		Mem::Manager::sHandle().ReallocateShrink( pScene->NumMeshes*sizeof(sMesh), pScene->pMeshes);
+	}
+	
+	// Hook up shadow volume data.
+	pScene->mp_shadow_volume_header = (sShadowVolumeHeader *)pFile;
+
+	#if STENCIL_SHADOW
+	if ( pScene->mp_shadow_volume_header && pScene->mp_shadow_volume_header->byte_size )
+	{
+		int size = pScene->mp_shadow_volume_header->byte_size + sizeof ( sShadowVolumeHeader );
+		char * p8 = new char[size];
+		memcpy( p8, pScene->mp_shadow_volume_header, size );
+		pScene->mp_shadow_volume_header = (sShadowVolumeHeader *)p8;
+		pScene->mp_shadow_volume_header->p_vertex = (sShadowVertex *)&pScene->mp_shadow_volume_header[1];
+		pScene->mp_shadow_volume_header->p_connect = (sShadowConnect *)&pScene->mp_shadow_volume_header->p_vertex[pScene->mp_shadow_volume_header->num_verts];
+		pScene->mp_shadow_volume_header->p_neighbor = (sShadowNeighbor *)&pScene->mp_shadow_volume_header->p_connect[pScene->mp_shadow_volume_header->num_faces];
+	}
+	else
+	{
+		pScene->mp_shadow_volume_header = NULL;
+	}
+	#else
+	pScene->mp_shadow_volume_header = NULL;
+	#endif
+
+	// deal with instances
+	pScene->pInstances = NULL;
+	if (IsInstanceable)
+	{
+		pScene->Flags |= SCENEFLAG_INSTANCEABLE;
+	}
+
+	// success
+	return true;
+}
+
+// load scene from a file
+sScene* LoadScene(const char* Filename, sScene *pScene, bool IsSkin, bool IsInstanceable, uint32 texDictOffset, bool doShadowVolume)
+{
+	int data_size;
+	uint8* pData = (uint8*)File::LoadAlloc(Filename,&data_size,dma::pRuntimeBuffer,NON_DEBUG_DMA_BUFFER_SIZE);	
+	
+	// theoretically, the data pointer returned is
+	// exactly the same as the dma::pRuntimeBuffer
+	Dbg_MsgAssert( pData == dma::pRuntimeBuffer, ( "Unexpected data pointer was found" ) );
+
+	bool success = load_scene(pData, data_size, pScene, IsSkin, IsInstanceable, texDictOffset, doShadowVolume);
+	if ( !success )
+	{
+		Dbg_MsgAssert( 0, ( "Load scene from file %s failed", Filename ) );
+	}
+
+	return pScene;
+}
+
+// load scene from a data buffer, rather than opening a file
+sScene* LoadScene(uint32* pData, int dataSize, sScene *pScene, bool IsSkin, bool IsInstanceable, uint32 texDictOffset, bool doShadowVolume)
+{
+	Dbg_MsgAssert( dataSize <= NON_DEBUG_DMA_BUFFER_SIZE, ( "Data to copy is too large to fit in buffer" ) ); 
+
+	// copy over the data
+	memcpy( dma::pRuntimeBuffer, pData, dataSize );
+	
+	bool success = load_scene((uint8*)pData, dataSize, pScene, IsSkin, IsInstanceable, texDictOffset, doShadowVolume);
+	if ( !success )
+	{
+		Dbg_MsgAssert( 0, ( "Load scene from data buffer failed" ) );
+	}
+
+	return pScene;
+}
+
+void DeleteScene(sScene *pScene)
+{
+	// deallocate assets
+	Mem::Free(pScene->pMeshDma);
+	Mem::Free(pScene->pMaterials);
+	Mem::Free(pScene->pMeshes);
+	Mem::Free(pScene->mp_shadow_volume_header);
+	// and what of groups?
+
+	// don't unlink or free, because the textures still exist
+
+	// GJ:  a little pre-emptive debugging, since scenes
+	// and textures aren't necessarily destroyed at the same time
+	pScene->pMeshDma = NULL;
+	pScene->pMaterials = NULL;
+	pScene->pMeshes = NULL;
+
+	Dbg_MsgAssert(!pScene->pInstances, ("Trying to delete sScene that still has CInstances attached to it.  Must delete CInstances first."));
+}
+
+
+
+void sScene::SetUVOffset(uint32 material_name, int pass, float u_offset, float v_offset)
+{
+	#if 1
+	sMesh *pMesh;
+	Mth::Matrix mat;
+	int i;
+
+	mat.Identity();
+	mat[3][0] = u_offset;
+	mat[3][1] = v_offset;
+
+	for (pMesh=this->pMeshes, i=0; iNumMeshes; pMesh++, i++)
+	{
+		if (pMesh->MaterialName==material_name && pMesh->Pass==pass)
+		{
+			dma::TransformSTs(pMesh->pSubroutine, mat);
+		}
+	}
+	#else
+	
+	// just some test code for me to play around with...
+
+	sMesh *pMesh;
+	Mth::Matrix mat;
+	int i;
+
+	mat.Identity();
+
+	//mat[0][0] = cosf(2.0f * 3.14159265f * u_offset);
+	//mat[0][1] = sinf(2.0f * 3.14159265f * u_offset);
+	//mat[1][0] = -mat[0][1];
+	//mat[1][1] = mat[0][0];
+
+	if (u_offset>0.0f)
+		mat[0][0] = 1.01f;
+	else if (u_offset<0.0f)
+		mat[0][0] = 1.0f/1.01f;
+	if (v_offset>0.0f)
+		mat[1][1] = 1.01f;
+	else if (v_offset<0.0f)
+		mat[1][1] = 1.0f/1.01f;
+
+	for (pMesh=this->pMeshes, i=0; iNumMeshes; pMesh++, i++)
+	{
+		if (pMesh->MaterialName==material_name && pMesh->Pass==pass)
+		{
+			dma::TransformSTs(pMesh->pSubroutine, mat);
+		}
+	}
+	#endif
+}
+
+
+
+
+void sScene::SetUVMatrix(uint32 material_name, int pass, const Mth::Matrix &mat)
+{
+	sMesh *pMesh;
+	int i;
+
+	for (pMesh=this->pMeshes, i=0; iNumMeshes; pMesh++, i++)
+	{
+		if (pMesh->MaterialName==material_name && pMesh->Pass==pass)
+		{
+			dma::TransformSTs(pMesh->pSubroutine, mat);
+		}
+	}
+}
+
+
+
+
+sScene *sScene::pHead;
+uint8  *sScene::pData;
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/scene.h b/Code/Gfx/NGPS/NX/scene.h
new file mode 100644
index 0000000..66a7ed3
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/scene.h
@@ -0,0 +1,91 @@
+#ifndef __SCENE_H
+#define __SCENE_H
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+namespace NxPs2
+{
+
+
+#define SCENEFLAG_INSTANCEABLE (1<<0)
+#define SCENEFLAG_USESPIP      (1<<1)
+
+
+struct sShadowVertex
+{
+	float	x;
+	float	y;
+	float	z;
+	int		idx;
+};
+
+struct sShadowConnect
+{
+	uint16	corner[3];
+};
+
+struct sShadowNeighbor
+{
+	sint16	edge[3];
+};
+
+struct sShadowVolumeHeader
+{
+	uint32				version;
+	uint32				num_faces;
+	uint32				num_verts;
+	uint32				byte_size;
+	sShadowVertex *		p_vertex;
+	sShadowConnect *	p_connect;
+	sShadowNeighbor *	p_neighbor;
+	uint32				pad;
+};
+
+struct sScene
+{
+	Mth::Vector			Sphere;
+
+	uint32				Flags;
+	int					NumGroups;
+
+	int					NumTextures;
+	uint8				*pTexBuffer;
+	uint8				*pTexDma;
+	struct sTexture		*pTextures;
+
+	int					NumMaterials;
+	struct sMaterial	*pMaterials;
+
+	int 				NumMeshes;
+	uint8 				*pMeshDma;
+	struct sMesh		*pMeshes;
+	class  CInstance	*pInstances;
+
+	sScene				*pNext;
+
+	static sScene		*pHead;
+	static uint8		*pData;
+
+	sShadowVolumeHeader	*mp_shadow_volume_header;
+public:
+	void SetUVOffset(uint32 material_name, int pass, float u_offset, float v_offset);
+	void SetUVMatrix(uint32 material_name, int pass, const Mth::Matrix &mat);
+};
+
+struct sCASData;
+
+sScene *LoadScene(uint32 *pData, int dataSize, sScene *pScene, bool IsSkin, bool Instanced, uint32 texDictOffset, bool doShadowVolume);
+sScene *LoadScene(const char *Filename, sScene *pScene, bool IsSkin, bool Instanced, uint32 texDictOffset, bool doShadowVolume);
+void DeleteScene(sScene *pScene);
+
+
+} // namespace NxPs2
+
+
+#endif // __SCENE_H
+
diff --git a/Code/Gfx/NGPS/NX/sprite.cpp b/Code/Gfx/NGPS/NX/sprite.cpp
new file mode 100644
index 0000000..1d17c9d
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/sprite.cpp
@@ -0,0 +1,1569 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "sprite.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "vu1code.h"
+#include "texturemem.h"
+#include "render.h"
+#include "switches.h"
+#include 
+
+/*
+
+
+	
+.img file format
+--------------------------
+	
+Offset		Size (Bytes)	Field
+---------------------------------------------------------------
+0x00			4			Version (currently 1)
+0x04			4			Basename of file in CRC format
+0x08			4			PS2 TW (log2 Width)
+0x0c			4			PS2 TH (log2 Height)
+0x10			4			PS2 PSM (Bit depth)
+0x14			4			PS2 CPSM (CLUT bit depth)
+0x18			4			PS2 MXL (Number of mipmaps)
+0x1c			2			Original image width
+0x1e			2			Original image height
+0x20			Upto 1K		CLUT data
+0x20+CLUT_size	W*H*depth		Texture mipmaps
+
+	
+	
+	
+font->vram upload format
+------------------------
+
+
+	GIFtag PACKED 4
+	BITBLTBUF ?
+	TRXPOS 0
+	TRXREG w,h
+	TRXDIR 0
+	
+	GIFtag IMAGE (w*h+15)/16
+	... (tex data)
+	
+	GIFtag PACKED 4
+	BITBLTBUF ?
+	TRXPOS 0
+	TRXREG w,h
+	TRXDIR 0
+	
+	GIFtag IMAGE 64
+	... (clut data)
+	
+	
+	
+	total size in bytes = 12*16+((w*h+15)/16)*16+1024 = 1216+((w*h+15)/16)*16
+	
+	this will be referenced as follows:
+	
+	dma ref
+	NOP
+	DIRECT 76+(w*h+15)/16
+	
+	
+*/
+
+
+namespace NxPs2
+{
+
+/******************************************************************/
+/*                                                                */
+/* SSingleTexture												  */
+/*                                                                */
+/******************************************************************/
+
+SSingleTexture *	SSingleTexture::sp_stexture_list = NULL;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SSingleTexture::SSingleTexture()
+{
+	Dbg_Error("Must supply a filename when creating a SSingleTexture");
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// .img.ps2 buffer
+
+SSingleTexture::SSingleTexture(uint8* p_buffer, int buffer_size, bool sprite, bool alloc_vram, bool load_screen) :
+	m_flags(0),
+	m_usage_count(0)
+{
+	#ifdef			__NOPT_ASSERT__
+	strcpy(m_name,"texture from buffer");
+	#endif
+
+	mp_TexBitBltBuf = mp_ClutBitBltBuf = NULL;
+
+	// Set flags
+	if (load_screen)
+	{
+		m_flags |= mLOAD_SCREEN;
+	}
+	if (sprite)
+	{
+		m_flags |= mSPRITE;
+	}
+	if ((sprite && alloc_vram) && !load_screen)
+	{
+		m_flags |= mALLOC_VRAM;
+	}
+
+	// open the font file
+	if (!sprite)
+	{
+		Dbg_Assert(!alloc_vram);
+	}
+	
+	mp_PixelData = mp_ClutData = NULL;
+	m_PixelDataSize = m_ClutDataSize = 0;
+	
+	mp_TexBuffer = (uint8*)Mem::Malloc( buffer_size );
+	Dbg_Assert( mp_TexBuffer );
+	memcpy( mp_TexBuffer, p_buffer, buffer_size );
+	m_TexBufferSize = buffer_size;
+	
+	// Initialize all the data
+	InitTexture(false);
+
+	// add texture to texture list
+	if (sprite && !load_screen)
+	{
+		mp_next = sp_stexture_list;
+		sp_stexture_list = this;
+	}
+
+	if (load_screen) {
+		FlipTextureVertical();
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// .img.ps2 file
+
+SSingleTexture::SSingleTexture(const char *Filename, bool sprite, bool alloc_vram, bool load_screen) :
+	m_flags(0),
+	m_usage_count(0)
+{
+	char ExtendedFilename[128];
+		  
+	#ifdef			__NOPT_ASSERT__
+	strcpy(m_name,Filename);
+	#endif
+
+	mp_TexBitBltBuf = mp_ClutBitBltBuf = NULL;
+
+	// Set flags
+	if (load_screen)
+	{
+		m_flags |= mLOAD_SCREEN;
+	}
+	if (sprite)
+	{
+		m_flags |= mSPRITE;
+	}
+	if ((sprite && alloc_vram) && !load_screen)
+	{
+		m_flags |= mALLOC_VRAM;
+	}
+
+	// open the font file
+	if (sprite)
+	{
+//		strcpy(ExtendedFilename, "images/");
+//		strcat(ExtendedFilename, Filename);
+		strcpy(ExtendedFilename, Filename);
+	} else {
+		Dbg_Assert(!alloc_vram);
+
+		strcpy(ExtendedFilename, Filename);
+	}
+	strcat(ExtendedFilename, ".img.ps2");
+	
+	mp_PixelData = mp_ClutData = NULL;
+	m_PixelDataSize = m_ClutDataSize = 0;
+	
+	#if 0
+	void *pFile = File::Open(ExtendedFilename, "rb");
+	// get file size and allocate texture buffer
+	m_TexBufferSize = File::GetFileSize(pFile);
+	mp_TexBuffer = (uint8 *)Mem::Malloc(m_TexBufferSize);
+	File::Read(mp_TexBuffer, 1, m_TexBufferSize, pFile);
+	File::Close(pFile);
+	#else
+	mp_TexBuffer = (uint8*)File::LoadAlloc(ExtendedFilename, &m_TexBufferSize);
+	#endif
+	
+	// Initialize all the data
+	InitTexture(false);
+
+	// add texture to texture list
+	if (sprite && !load_screen)
+	{
+		mp_next = sp_stexture_list;
+		sp_stexture_list = this;
+	}
+
+	if (load_screen) {
+		FlipTextureVertical();
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// raw buffer
+
+SSingleTexture::SSingleTexture(uint8* p_buffer, int width, int height, int bitdepth, int clut_bitdepth, int mipmaps,
+							   bool sprite, bool alloc_vram, bool load_screen):
+	m_flags(0),
+	m_usage_count(0)
+{
+	#ifdef			__NOPT_ASSERT__
+	strcpy(m_name,"texture from raw data");
+	#endif
+
+	mp_TexBitBltBuf = mp_ClutBitBltBuf = NULL;
+
+	// Set flags
+	if (load_screen)
+	{
+		m_flags |= mLOAD_SCREEN;
+	}
+	if (sprite)
+	{
+		m_flags |= mSPRITE;
+	}
+	if ((sprite && alloc_vram) && !load_screen)
+	{
+		m_flags |= mALLOC_VRAM;
+	}
+
+	// open the font file
+	if (!sprite)
+	{
+		Dbg_Assert(!alloc_vram);
+	}
+	
+	mp_PixelData = mp_ClutData = NULL;
+	m_PixelDataSize = m_ClutDataSize = 0;
+	mp_TexBuffer = NULL;		// Since we aren't allocating any memory
+	m_TexBufferSize = 0;
+	
+	m_checksum = 0;
+	m_orig_width = width;
+	m_orig_height = height;
+
+	// Initialize all the data
+	//InitTexture(false);
+	uint32 TW, TH, PSM, CPSM, MXL;
+
+	TW = TextureMemoryLayout::numBits(width - 1);		// Rounds up to the nearest 2^N
+	TH = TextureMemoryLayout::numBits(height - 1);
+
+	PSM = bitdepth_to_psm(bitdepth);
+	CPSM = bitdepth_to_psm(clut_bitdepth);
+
+	MXL = mipmaps - 1;
+
+	// Raw data is inverted vertically from the way we expect textures.
+	m_flags |= mINVERTED;
+
+	setup_reg_and_dma(p_buffer, TW, TH, PSM, CPSM, MXL, false);
+
+	// add texture to texture list
+	if (sprite && !load_screen)
+	{
+		mp_next = sp_stexture_list;
+		sp_stexture_list = this;
+	}
+
+	if (load_screen) {
+		FlipTextureVertical();
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// copy constructor
+
+SSingleTexture::SSingleTexture(const SSingleTexture & src_texture)
+{
+	// Copy checksum and flags
+	m_checksum = src_texture.m_checksum;
+	m_flags = src_texture.m_flags;
+
+	#ifdef			__NOPT_ASSERT__
+	strcpy(m_name,src_texture.m_name);
+	#endif
+
+	// Init some of the data before we call InitTexture()
+	m_usage_count = 0;
+	mp_TexBitBltBuf = mp_ClutBitBltBuf = NULL;
+	mp_PixelData = mp_ClutData = NULL;
+	m_PixelDataSize = m_ClutDataSize = 0;
+
+	// Copy file buffer
+	m_TexBufferSize = src_texture.m_TexBufferSize;
+	if (m_TexBufferSize && src_texture.mp_TexBuffer)
+	{
+		mp_TexBuffer = (uint8 *)Mem::Malloc(m_TexBufferSize);
+		memcpy(mp_TexBuffer, src_texture.mp_TexBuffer, m_TexBufferSize);
+
+		// This routine will initialize and allocate the rest
+		InitTexture(true);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't copy SSingleTexture that was created with a raw buffer"));
+		//InitTexture(true);
+	}
+
+	// add texture to texture list
+	if ((m_flags & mSPRITE) && !(m_flags & mLOAD_SCREEN))
+	{
+		mp_next = sp_stexture_list;
+		sp_stexture_list = this;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SSingleTexture::~SSingleTexture()
+{
+	// find texture and unchain from list
+	if (sp_stexture_list == this)
+	{
+		sp_stexture_list = mp_next;
+	} else if (sp_stexture_list) {
+		SSingleTexture *p_texture = sp_stexture_list;
+
+		while(p_texture->mp_next)
+		{
+			if (p_texture->mp_next == this)
+			{
+				p_texture->mp_next = mp_next;
+				break;
+			}
+
+			p_texture = p_texture->mp_next;
+		}
+	}
+
+	// free memory
+	if (mp_VifData) Mem::Free(mp_VifData);
+	if (mp_TexBuffer) Mem::Free(mp_TexBuffer);
+
+	Dbg_MsgAssert(m_usage_count == 0, ("Trying to remove SSingleTexture %x that is still being used", m_checksum));
+
+	// and re-do VRAM allocation
+	VRAMNeedsUpdating();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+#define STEX_USE_CALL 1
+
+const static uint32 TEXTURE_VERSION = 1;
+
+TextureMemoryLayout TexMemLayout;
+
+bool SSingleTexture::InitTexture(bool copy)
+{
+	uint32 TW, TH, PSM, CPSM, MXL = 0;
+
+	uint8 *pData = mp_TexBuffer;
+
+	uint32 version;
+	version = *(uint32 *)pData, pData+=4;
+	Dbg_MsgAssert(version >= TEXTURE_VERSION, ("Wrong version in .img file: %d. Game uses: %d", version, TEXTURE_VERSION));
+	m_checksum = *(uint32 *)pData, pData+=4;
+
+	// get dimensions
+	TW = *(uint32 *)pData, pData+=4;
+	TH = *(uint32 *)pData, pData+=4;
+
+	// get pixel storage modes
+	PSM  = *(uint32 *)pData, pData+=4;	// for texels
+	CPSM = *(uint32 *)pData, pData+=4;	// for clut
+
+	// get maximum mipmap level
+	if (version >= 2)
+	{
+		MXL  = *(uint32 *)pData, pData+=4;
+	}
+	Dbg_Assert(MXL == 0);
+	m_orig_width = *(uint16 *)pData, pData+=2;
+	m_orig_height = *(uint16 *)pData, pData+=2;
+
+	// bail if it's a non-texture
+	Dbg_MsgAssert((TW != 0xFFFFFFFF), ("No texture in .img file\n"));
+
+	// quadword-align
+	pData = (uint8 *)(((uint)(pData+15)) & 0xFFFFFFF0);
+
+	return setup_reg_and_dma(pData, TW, TH, PSM, CPSM, MXL, copy);
+}
+
+bool SSingleTexture::setup_reg_and_dma(uint8 *pData, uint32 TW, uint32 TH, uint32 PSM, uint32 CPSM, uint32 MXL, bool copy)
+{
+	uint TBW;
+	uint Width, Height, NumTexBytes, NumTexQWords, NumClutBytes, NumClutQWords, NumVramBytes;
+	uint BitsPerTexel, BitsPerClutEntry, PaletteSize;
+	uint NextTBP;
+	int j,k;
+
+	uint8 *old_dma_loc = dma::pLoc;		  
+
+	// initialise base pointers
+	if (IsInVRAM())
+	{
+		NextTBP = FontVramBase;
+	} else {
+		NextTBP = FontVramStart;
+	}
+
+	// bits per texel and palette size
+	switch (PSM)
+	{
+	case PSMCT32:
+		BitsPerTexel = 32;
+		PaletteSize  = 0;
+		break;
+	case PSMCT24:
+		BitsPerTexel = 24;
+		PaletteSize  = 0;
+		break;
+	case PSMCT16:
+		BitsPerTexel = 16;
+		PaletteSize  = 0;
+		break;
+	case PSMT8:
+		BitsPerTexel = 8;
+		PaletteSize  = 256;
+		break;
+	case PSMT4:
+		BitsPerTexel = 4;
+		PaletteSize  = 16;
+		break;
+	default:
+		printf("Unknown PSM %d in texture file\n", PSM);
+		exit(1);
+	}
+
+	// bits per clut entry
+	if (BitsPerTexel < 16)
+		switch (CPSM)
+		{
+		case PSMCT32:
+			BitsPerClutEntry = 32;
+			break;
+		case PSMCT16:
+			BitsPerClutEntry = 16;
+			break;
+		default:
+			printf("Unknown CPSM %d in texture file\n", PSM);
+			exit(1);
+		}
+	else
+		BitsPerClutEntry = 0;
+
+	// rearrange 256-colour cluts according to requirements of CSM1
+	if (!copy && PSM==PSMT8)
+	{
+		uint32 temp32;
+		uint16 temp16;
+		for (j=0; j<256; j+=32)
+			for (k=0; k<8; k++)
+				if (CPSM==PSMCT32)
+				{
+					temp32 = ((uint32 *)pData)[j+k+8];
+					((uint32 *)pData)[j+k+8] = ((uint32 *)pData)[j+k+16];
+					((uint32 *)pData)[j+k+16] = temp32;
+				}
+				else
+				{
+					temp16 = ((uint16 *)pData)[j+k+8];
+					((uint16 *)pData)[j+k+8] = ((uint16 *)pData)[j+k+16];
+					((uint16 *)pData)[j+k+16] = temp16;
+				}
+	}
+
+	// calculate texture dimensions
+	Width = (TW>=0 ? 1<=0 ? 1<> 6;
+	if (BitsPerTexel<16 && TBW<2)
+		TBW = 2;
+
+	// Calculate memory needed
+	int min_width = Width, min_height = Height;
+	TexMemLayout.getMinSize(min_width, min_height, PSM);
+	m_NumTexVramBlocks = TexMemLayout.getImageSize(min_width, min_height, PSM);
+	NumVramBytes = m_NumTexVramBlocks * 256;
+	//NumVramBytes = (TBW << 6) * Height * BitsPerTexel >> 3;
+
+	// calculate sizes within file
+	NumClutBytes  = PaletteSize * BitsPerClutEntry >> 3;
+	NumClutQWords = NumClutBytes >> 4;
+	NumTexBytes  = Width * Height * BitsPerTexel >> 3;
+	NumTexQWords = NumTexBytes >> 4;
+
+	//Dbg_Message("Texture size (%d, %d) of bitdepth %d and BufferWidth %d", Width, Height, BitsPerTexel, TBW);
+	//Dbg_MsgAssert(calcVramBytes <= NumVramBytes, ("Need more memory: %d vs. needed %d", NumVramBytes, calcVramBytes));
+
+	// allocate memory for vif stream
+#if STEX_USE_CALL
+	m_VifSize = 272;
+#else
+	m_VifSize = 1024 + NumClutBytes + NumTexBytes; //256; //1216 + (((Width*Height+15)>>4)<<4);
+#endif // STEX_USE_CALL
+	mp_VifData = (uint8 *)Mem::Malloc(m_VifSize);
+	Dbg_MsgAssert(mp_VifData, ("couldn't malloc memory for vif data"));
+	Dbg_MsgAssert(((uint32)mp_VifData & 15) == 0, ("vif data not qword aligned"));
+
+	// build dma data in this buffer
+	dma::pLoc = mp_VifData;
+
+	// calculate TBP
+	m_TBP = NextTBP;
+
+	// calculate next TBP
+	//NextTBP = (((TBP<<8)+NumVramBytes+0x1FFF) & 0xFFFFE000) >> 8;
+	NextTBP = m_TBP + m_NumTexVramBlocks;
+
+	// calculate CBP
+	m_CBP = NextTBP;
+	if (BitsPerTexel >= 16)
+		m_NumClutVramBlocks = 0;
+	else if (BitsPerTexel == 4)
+		m_NumClutVramBlocks = 4;
+	else
+		m_NumClutVramBlocks = (CPSM==PSMCT32 ? 4 : 4);
+
+	NextTBP += m_NumClutVramBlocks;
+
+	if (IsInVRAM() && Script::GetInt( "vram_debug", false ))
+	{
+		Dbg_Message("VRAM Used = %d blocks, texture Size = %d blocks, blocks left = %d out of %d", 
+				NextTBP - FontVramStart, m_NumTexVramBlocks + m_NumClutVramBlocks, FontVramSize - (NextTBP - FontVramStart),FontVramSize);  										
+	}
+							  
+	// bail if VRAM would become over-packed
+	if (IsInVRAM() && (NextTBP > (FontVramStart + FontVramSize)))
+	{
+		Dbg_MsgAssert(0, ("SingleTexture: 2D VRAM Buffer Full: %x.", NextTBP));
+		//pData += NumClutBytes;
+		//pData = (uint8 *)(((uint)(pData+NumTexBytes+15)) & 0xFFFFFFF0);
+		// And restore the CurrentDMALoc, which we were just using temporarily.
+		dma::pLoc = old_dma_loc;		  
+		return false;
+	}
+
+	// add clut upload to dma list if necessary
+	if (BitsPerTexel < 16)
+	{
+#if STEX_USE_CALL
+		dma::BeginTag(dma::cnt,0);
+		vif::NOP();
+		vif::DIRECT(6);
+		gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+		mp_ClutBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,m_CBP,1,CPSM));
+		gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+		if (PSM==PSMT4)
+			gs::Reg2(gs::TRXREG,PackTRXREG(8,2));
+		else
+			gs::Reg2(gs::TRXREG,PackTRXREG(16,16));
+		gs::Reg2(gs::TRXDIR,	0);
+		gif::Tag2(0,0,IMAGE,0,0,0,NumClutQWords);
+		dma::EndTag();
+		dma::Tag(dma::ref, NumClutQWords, (uint)pData);
+		vif::NOP();
+		vif::DIRECT(NumClutQWords);
+#else
+		// clut upload header
+		gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+		mp_ClutBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,m_CBP,1,CPSM));
+		gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+		if (PSM==PSMT4)
+			gs::Reg2(gs::TRXREG,PackTRXREG(8,2));
+		else
+			gs::Reg2(gs::TRXREG,PackTRXREG(16,16));
+		gs::Reg2(gs::TRXDIR,	0);
+		gif::Tag2(0,0,IMAGE,0,0,0,NumClutQWords);
+
+		// read clut bitmap data
+		memcpy(dma::pLoc, pData, NumClutBytes);
+
+		// step dma location
+		dma::pLoc += NumClutBytes;
+#endif // STEX_USE_CALL
+
+		// Save pointer
+		mp_ClutData = pData;
+		m_ClutDataSize = NumClutBytes;
+
+		// step past clut data, and quadword-align
+		pData = (uint8 *)(((uint)(pData+NumClutBytes+15)) & 0xFFFFFFF0);
+	}
+
+#if STEX_USE_CALL
+	// add texture upload to dma list
+	dma::BeginTag(dma::cnt,0);
+	vif::NOP();
+	vif::DIRECT(6);
+	gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+	mp_TexBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,m_TBP,TBW,PSM));
+	gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+	gs::Reg2(gs::TRXREG,	PackTRXREG(Width,Height));
+	gs::Reg2(gs::TRXDIR,	0);
+	gif::Tag2(0, 0, IMAGE, 0, 0, 0, NumTexQWords);
+	dma::EndTag();
+	dma::Tag(dma::ref, NumTexQWords, (uint)pData);
+	vif::NOP();
+	vif::DIRECT(NumTexQWords);
+#else
+	// texture upload header
+	gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+	mp_TexBitBltBuf = gs::Reg2_pData(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,m_TBP,TBW,PSM));
+	gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+	gs::Reg2(gs::TRXREG,	PackTRXREG(Width,Height));
+	gs::Reg2(gs::TRXDIR,	0);
+	gif::Tag2(0, 0, IMAGE, 0, 0, 0, NumTexQWords);
+
+	// read texture bitmap data
+	memcpy(dma::pLoc, pData, NumTexBytes);
+	dma::pLoc += NumTexQWords<<4;
+#endif // STEX_USE_CALL
+
+	// Handle mipmaps, if any (but don't make any DMA packets for them)
+	if (!(m_flags & mSPRITE) && (MXL > 0))
+	{
+		for (j = 1; j < (int) MXL; j++)
+		{
+			int mip_width = (TW>=0 ? 1<> j;
+			int mip_height = (TH>=0 ? 1<> j;
+
+			// Add in mip sizes
+			NumTexBytes += mip_width * mip_height * BitsPerTexel >> 3;
+		}
+	}
+
+	// Save pointer
+	mp_PixelData = pData;
+	m_PixelDataSize = NumTexBytes;
+
+	// make entry in texture table
+	m_RegTEX0    = PackTEX0(m_TBP,TBW,PSM,TW,TH,1,MODULATE,m_CBP,CPSM,0,0,1);
+	m_RegTEX1	 = PackTEX1(1,0,LINEAR,LINEAR,0,0,0);
+
+	// update vram base pointer
+	if (IsInVRAM())
+	{
+		FontVramBase = NextTBP;
+	}
+
+#if STEX_USE_CALL
+	// Since this data is called, do a return
+	dma::Tag(dma::ret, 0, 0);
+	vif::NOP();
+	vif::NOP();
+#endif // STEX_USE_CALL
+
+	#ifdef	__NOPT_ASSERT__
+	uint ActualSize = (uint) dma::pLoc - (uint) mp_VifData;
+	Dbg_MsgAssert(ActualSize <= m_VifSize, ("Did not allocate enough DMA space for textures: allocated=%d actual=%d", m_VifSize, ActualSize));
+	//Dbg_Message("Texture packet size = %d", (int) dma::pLoc - (int) pVifData);
+	#endif
+
+	// And restore the CurrentDMALoc, which we were just using temporarily.
+	dma::pLoc = old_dma_loc;		  
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Need to do this to textures that are copied directly into the frame buffer
+//
+
+void	SSingleTexture::FlipTextureVertical()
+{
+	int startrow = 0;
+	int endrow = m_orig_height - 1;
+	int rowsize = (m_orig_width * GetBitdepth()) / 8;
+
+	uint8 *p_temp_buffer = new uint8[rowsize];
+	while (startrow < endrow)
+	{
+		// Swap rows
+		memcpy(p_temp_buffer,						&(mp_PixelData[startrow * rowsize]),rowsize);
+		memcpy(&(mp_PixelData[startrow * rowsize]),	&(mp_PixelData[endrow * rowsize]),	rowsize);
+		memcpy(&(mp_PixelData[endrow * rowsize]),	p_temp_buffer,						rowsize);
+
+		startrow++;
+		endrow--;
+	}
+
+	delete p_temp_buffer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+bool SSingleTexture::AddToVRAM()
+{
+	Dbg_MsgAssert(!(m_flags & mAUTO_ALLOC), ("Can't use AddToVRAM() on a auto allocated texture"));
+
+	#ifdef			__NOPT_ASSERT__
+//	printf ("++++ Adding %s ++++\n",m_name);
+	#endif 
+	m_flags |= mALLOC_VRAM;
+
+	VRAMNeedsUpdating();
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+bool SSingleTexture::RemoveFromVRAM()
+{
+	Dbg_MsgAssert(!(m_flags & mAUTO_ALLOC), ("Can't use RemoveFromVRAM() on a auto allocated texture"));
+
+	#ifdef			__NOPT_ASSERT__
+//	printf ("---- removing %s ----\n",m_name);
+	#endif 
+	m_flags &= ~mALLOC_VRAM;
+
+	VRAMNeedsUpdating();
+
+	return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSingleTexture::SetAutoAllocate()
+{
+	m_flags |= mAUTO_ALLOC;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSingleTexture::inc_usage_count()
+{
+	m_usage_count++;
+	m_flags |= mALLOC_VRAM;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSingleTexture::dec_usage_count()
+{
+	if (--m_usage_count == 0)
+	{
+		m_flags &= ~mALLOC_VRAM;
+	}
+
+	Dbg_MsgAssert(m_usage_count >= 0, ("Usage Count negative on  SSingleTexture %x", m_checksum));
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSingleTexture::UseTexture(bool use)
+{
+	Dbg_MsgAssert(m_flags & mAUTO_ALLOC, ("Can't use UseTexture() on a non-auto allocated texture"));
+
+	bool old_alloc = IsInVRAM();
+
+	// Update usage
+	if (use)
+	{
+		inc_usage_count();
+	}
+	else
+	{
+		dec_usage_count();
+	}
+
+	// See if we need to update DMA
+	if (old_alloc != use)
+	{
+		VRAMNeedsUpdating();
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+uint16	SSingleTexture::GetOrigWidth() const
+{
+	return m_orig_width;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+uint16	SSingleTexture::GetOrigHeight() const
+{
+	return m_orig_height;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void	SSingleTexture::ReallocateVRAM()
+{
+	// Don't realloc load screens
+	if (!IsInVRAM()) return;
+
+	// calculate TBP
+	m_TBP = FontVramBase;
+	FontVramBase += m_NumTexVramBlocks;
+
+	// calculate CBP
+	m_CBP = FontVramBase;
+	FontVramBase += m_NumClutVramBlocks;
+
+	// bail if VRAM would become over-packed
+	if (FontVramBase > (FontVramStart + FontVramSize))
+	{
+		Dbg_MsgAssert(0, ("SingleTexture Realloc: 2D VRAM Buffer Full: %x.", FontVramBase));
+	}
+
+	// Modify the data
+	gs::ModifyTBP(gs::TEX0_1, (uint32 *) &m_RegTEX0, m_TBP, m_CBP);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void	SSingleTexture::UpdateDMA()
+{
+	// Modify the packets
+	gs::ModifyTBP(gs::BITBLTBUF, mp_TexBitBltBuf, m_TBP);
+	if (mp_ClutBitBltBuf)
+	{
+		gs::ModifyTBP(gs::BITBLTBUF, mp_ClutBitBltBuf, m_CBP);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// The following Get() calls are very inefficient.  They should only be
+// used when necessary (i.e. initialization).
+//
+uint16	SSingleTexture::GetWidth() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &m_RegTEX0);
+	uint16 TW = tex0.TW;
+	//uint16 TW = (uint16) ((RegTEX0 >> 26) & M04);
+
+	return 1 << TW;
+}
+
+uint16	SSingleTexture::GetHeight() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &m_RegTEX0);
+	uint16 TH = tex0.TH;
+	//uint16 TH = (uint16) ((RegTEX0 >> 30) & M04);
+
+	return 1 << TH;
+}
+
+uint8	SSingleTexture::GetBitdepth() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &m_RegTEX0);
+	uint PSM = tex0.PSM;
+
+	switch (PSM)
+	{
+	case PSMCT32:
+		return 32;
+		break;
+	case PSMCT24:
+		return 24;
+		break;
+	case PSMCT16:
+		return 16;
+		break;
+	case PSMT8:
+		return 8;
+		break;
+	case PSMT4:
+		return 4;
+		break;
+	default:
+		return 0;
+	}
+}
+
+uint8	SSingleTexture::GetClutBitdepth() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &m_RegTEX0);
+	uint CPSM = tex0.CPSM;
+
+	switch (CPSM)
+	{
+	case PSMCT32:
+		return 32;
+		break;
+	case PSMCT24:
+		return 24;
+		break;
+	case PSMCT16:
+		return 16;
+		break;
+	default:
+		return 0;
+	}
+}
+
+uint8	SSingleTexture::GetNumMipmaps() const
+{
+	return 1;		// By definition for this class
+}
+
+bool	SSingleTexture::IsTransparent() const
+{
+	//Dbg_Error("Not working yet");
+	return false;
+}
+
+uint32	SSingleTexture::bitdepth_to_psm(int bitdepth)
+{
+	switch (bitdepth)
+	{
+	case 32:
+		return PSMCT32;
+		break;
+	case 24:
+		return PSMCT24;
+		break;
+	case 16:
+		return PSMCT16;
+		break;
+	case 8:
+		return PSMT8;
+		break;
+	case 4:
+		return PSMT4;
+		break;
+	default:
+		Dbg_MsgAssert(0, ("Bitdepth %d is not supported on PS2", bitdepth));
+		return PSMCT32;
+		break;
+	}
+}
+
+#ifdef			__NOPT_ASSERT__
+void	SSingleTexture::sPrintAllTextureInfo()
+{
+	for (SSingleTexture *pTexture=SSingleTexture::sp_stexture_list; pTexture; pTexture=pTexture->mp_next)
+	{
+		Dbg_Message("Texture name %s: using vram: %d, blocks %d", pTexture->m_name, pTexture->IsInVRAM(),
+					pTexture->m_NumTexVramBlocks + pTexture->m_NumClutVramBlocks);
+	}
+}
+
+void	SSingleTexture::sPrintAllocatedTextureInfo()
+{
+	for (SSingleTexture *pTexture=SSingleTexture::sp_stexture_list; pTexture; pTexture=pTexture->mp_next)
+	{
+		if (!pTexture->IsInVRAM())
+		{
+			continue;
+		}
+
+		Dbg_Message("Texture name %s: using vram: , blocks %d", pTexture->m_name,
+					pTexture->m_NumTexVramBlocks + pTexture->m_NumClutVramBlocks);
+	}
+}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/* SScissorWindow												  */
+/*                                                                */
+/******************************************************************/
+
+SScissorWindow DefaultScissorWindow;
+
+void SetTextWindow(uint16 x0, uint16 x1, uint16 y0, uint16 y1)
+{
+	DefaultScissorWindow.SetScissor(x0, y0, x1, y1);
+}
+
+void SScissorWindow::SetScissor(uint16 x0, uint16 y0, uint16 x1, uint16 y1)
+{
+	m_RegSCISSOR = (uint64)x0 | (uint64)x1<<16 | (uint64)y0<<32 | (uint64)y1<<48;
+}
+
+/******************************************************************/
+/*                                                                */
+/* SDraw2D														  */
+/*                                                                */
+/******************************************************************/
+
+float		SDraw2D::s_screen_scale_x = 1.0f;
+float		SDraw2D::s_screen_scale_y = 1.0f;
+
+bool		SDraw2D::s_constant_z_enabled = false;
+uint32		SDraw2D::s_constant_z = 0x7FFFFF;
+
+SDraw2D *	SDraw2D::sp_2D_sorted_draw_list = NULL;
+SDraw2D *	SDraw2D::sp_2D_zbuffer_draw_list = NULL;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SDraw2D::SDraw2D(float pri, bool hide) :
+	m_hidden(hide),
+	m_use_zbuffer(false),
+	m_pri(pri),
+	m_z(0xFFFFFF),
+	mp_scissor_window(&DefaultScissorWindow)
+{
+	mp_next = NULL;
+
+	// add to sorted draw list
+	if (!m_hidden)
+	{
+		InsertSortedDrawList(&sp_2D_sorted_draw_list);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SDraw2D::~SDraw2D()
+{
+	// Try removing from draw list
+	if (m_use_zbuffer)
+	{
+		RemoveDrawList(&sp_2D_zbuffer_draw_list);
+	} else {
+		RemoveDrawList(&sp_2D_sorted_draw_list);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::SetPriority(float pri)
+{
+	if (m_pri != pri)
+	{
+		m_pri = pri;
+
+		// By removing and re-inserting, we re-sort the list
+		if (!m_hidden)
+		{
+			if (m_use_zbuffer)
+			{
+				RemoveDrawList(&sp_2D_zbuffer_draw_list);
+			} else {
+				RemoveDrawList(&sp_2D_sorted_draw_list);
+			}
+			InsertSortedDrawList(&sp_2D_sorted_draw_list);
+		}
+
+		m_use_zbuffer = false;
+		m_z = 0xFFFFFF;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::SetZValue(uint32 z)
+{
+	if (m_z != z)
+	{
+		m_z = z;
+
+		// Switch lists, if necessary
+		if (!m_use_zbuffer && !m_hidden)
+		{
+			RemoveDrawList(&sp_2D_sorted_draw_list);
+			InsertDrawList(&sp_2D_zbuffer_draw_list);
+		}
+
+		m_use_zbuffer = true;
+		m_pri = 0.0f;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::SetHidden(bool hide)
+{
+	if (m_hidden != hide)
+	{
+		m_hidden = hide;
+		if (m_use_zbuffer)
+		{
+			if (hide)
+			{
+				RemoveDrawList(&sp_2D_zbuffer_draw_list);
+			} else {
+				InsertDrawList(&sp_2D_zbuffer_draw_list);
+			}
+		} else {
+			if (hide)
+			{
+				RemoveDrawList(&sp_2D_sorted_draw_list);
+			} else {
+				InsertSortedDrawList(&sp_2D_sorted_draw_list);
+			}
+		}
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::SetScissorWindow(SScissorWindow *p_scissor)
+{
+	if (p_scissor)
+	{
+		mp_scissor_window = p_scissor;
+	} else {
+		mp_scissor_window = &DefaultScissorWindow;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::DrawAll()
+{
+	// Figure out screen scale (for PAL mode)
+	CalcScreenScale();
+
+	// Init certain GS registers for 2D drawing
+	dma::BeginTag(dma::cnt, 0);
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::CLAMP_1,PackCLAMP(CLAMP,CLAMP,0,0,0,0));		// Man, I hope this is the last time I fix the CLAMP bug
+	gs::Reg1(gs::XYOFFSET_1, PackXYOFFSET(XOFFSET, YOFFSET));
+	gs::Reg1(gs::TEST_1, PackTEST(1,AGREATER,0,KEEP,0,0,1,ZGEQUAL));
+	gs::EndPrim(0);
+	dma::EndTag();
+
+	// Draw items needing ZBuffer first
+	DrawList(sp_2D_zbuffer_draw_list);
+	if (s_constant_z_enabled)
+	{
+		DrawList(sp_2D_sorted_draw_list);
+	}
+
+	// Turn off ZBuffer
+	dma::BeginTag(dma::cnt, 0);
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::TEST_1, PackTEST(0,0,0,0,0,0,1,ZGEQUAL));
+	gs::EndPrim(0);
+	dma::EndTag();
+
+	// Draw the sorted list now if we aren't using constant Z
+	if (!s_constant_z_enabled)
+	{
+		DrawList(sp_2D_sorted_draw_list);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::DrawList(SDraw2D *pDraw)
+{
+	while (pDraw)
+	{
+		if (!pDraw->m_hidden)
+		{
+			pDraw->BeginDraw();
+			pDraw->Draw();
+			pDraw->EndDraw();
+		}
+		pDraw = pDraw->mp_next;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::EnableConstantZValue(bool enable)
+{
+	s_constant_z_enabled = enable;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::SetConstantZValue(uint32 z)
+{
+	s_constant_z = z;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+uint32 SDraw2D::GetConstantZValue()
+{
+	return s_constant_z;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Figure out screen scale (mainly for PAL mode)
+//
+
+void SDraw2D::CalcScreenScale()
+{
+	if (Config::NTSC())
+	{
+		s_screen_scale_x = 1.0f;
+		s_screen_scale_y = 1.0f;
+	} else {
+		s_screen_scale_x = ((float) HRES_PAL) / ((float) HRES_NTSC);
+		s_screen_scale_y = ((float) VRES_PAL) / ((float) VRES_NTSC);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::InsertSortedDrawList(SDraw2D **p_list)
+{
+	Dbg_Assert(!m_use_zbuffer);
+
+	if (!*p_list ||							// Empty List
+		(m_pri <= (*p_list)->m_pri))		// Start List
+	{
+		mp_next = *p_list;
+		*p_list = this;
+	} else {				// Insert
+		SDraw2D *p_cur = *p_list;
+	
+		// Find where to insert
+		while(p_cur->mp_next)
+		{
+			if (m_pri <= p_cur->mp_next->m_pri)
+				break;
+
+			p_cur = p_cur->mp_next;
+		}
+
+		// Insert at this point
+		mp_next = p_cur->mp_next;
+		p_cur->mp_next = this;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::InsertDrawList(SDraw2D **p_list)
+{
+	if (!*p_list)							// Empty List
+	{
+		mp_next = *p_list;
+		*p_list = this;
+	} else {								// Insert at end of list
+		SDraw2D *p_cur = *p_list;
+
+		while(p_cur->mp_next)
+		{
+			p_cur = p_cur->mp_next;
+		}
+
+		mp_next = p_cur->mp_next;
+		p_cur->mp_next = this;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SDraw2D::RemoveDrawList(SDraw2D **p_list)
+{
+	// Take out from draw list
+	if (*p_list == this)
+	{
+		*p_list = mp_next;
+	} 
+	else if (*p_list) 
+	{
+		SDraw2D *p_cur = *p_list;
+
+		while(p_cur->mp_next)
+		{
+			if (p_cur->mp_next == this)
+			{
+				p_cur->mp_next = mp_next;
+				break;
+			}
+
+			p_cur = p_cur->mp_next;
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/* SSprite														  */
+/*                                                                */
+/******************************************************************/
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SSprite::SSprite(float pri) : SDraw2D(pri, true)
+{
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+SSprite::~SSprite()
+{
+	// Deallocates the underlying texture, if there was one
+	SetHidden(true);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSprite::SetHidden(bool hide)
+{
+	bool old_hide = IsHidden();
+
+	// Do the actual hide call
+	SDraw2D::SetHidden(hide);
+
+	// Make sure vram status is correct if we changed hidden states
+	if (mp_texture && (old_hide != hide))
+	{
+		mp_texture->UseTexture(!hide);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSprite::SetTexture(SSingleTexture *p_texture)
+{
+	// Set auto VRAM allocate
+	if (p_texture && (mp_texture != p_texture))
+	{
+		p_texture->SetAutoAllocate();
+
+		// Change usage counts if we aren't hidden
+		if (!IsHidden())
+		{
+			p_texture->UseTexture(true);
+			if (mp_texture)
+			{
+				mp_texture->UseTexture(false);
+			}
+		}
+	}
+
+	mp_texture = p_texture;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSprite::BeginDraw()
+{
+
+	// dma tag
+	dma::BeginTag(dma::cnt, 0);
+
+	vu1::Buffer = vu1::Loc;
+
+	int tme_flag = (mp_texture) ? TME : 0;
+
+	Dbg_Assert(GetScissorWindow());
+
+	// GS context
+	gs::BeginPrim(ABS,0,0);
+	gs::Reg1(gs::SCISSOR_1, GetScissorWindow()->GetScissorReg());
+	if (mp_texture)
+	{
+		Dbg_MsgAssert(mp_texture->IsInVRAM(), ("Can't draw sprite when its texture 0x%x, (%s) is not in VRAM.",mp_texture->m_checksum,mp_texture->m_name));
+
+		gs::Reg1(gs::TEX0_1,	 mp_texture->m_RegTEX0);
+		gs::Reg1(gs::TEX1_1,	 mp_texture->m_RegTEX1);
+	}
+	gs::Reg1(gs::ALPHA_1,	 PackALPHA(0,1,0,1,0));
+	gs::Reg1(gs::COLCLAMP,	 PackCOLCLAMP(1));
+	gs::Reg1(gs::RGBAQ,	 (uint64)m_rgba);
+	gs::EndPrim(0);
+
+	// unpack giftag
+	vif::UNPACK(0, V4_32, 1, ABS, UNSIGNED, 0);
+	if (m_rot == 0.0f)
+	{
+		gif::BeginTagImmediate(gs::UV|gs::XYZ2<<4, 2, PACKED, SPRITE|tme_flag|FST|ABE, 1, VU1_ADDR(GSPrim));
+	} else {
+		gif::BeginTagImmediate(gs::UV|gs::XYZ2<<4, 2, PACKED, TRISTRIP|tme_flag|FST|ABE, 1, VU1_ADDR(GSPrim));
+	}
+
+	// begin unpack for uv/xyz's
+	vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 1);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSprite::Draw()
+{
+	Dbg_Assert(GetScissorWindow());
+
+	const int start_screen_x = XOFFSET + ((GetScissorWindow()->GetScissorReg() & 0x7FF) << 4) + ((int) (m_xpos * s_screen_scale_x * 16.0f));
+	const int start_screen_y = YOFFSET + ((GetScissorWindow()->GetScissorReg() >> 32 & 0x7FF) << 4) + ((int) (m_ypos * s_screen_scale_y * 16.0f));
+
+	uint16 u0,v0,u1,v1;
+	float x0,y0,x1,y1;
+	uint32 z;
+
+	uint16 tex_height = (mp_texture) ? mp_texture->GetHeight() : m_height;
+
+	u0 = /* 8 +*/ (0);
+	v0 = /*-8 +*/ (tex_height << 4);
+	u1 = /* 8 +*/ (m_width << 4);
+	v1 = /*-8 +*/ ((tex_height - m_height) << 4);
+
+	// Check for flip
+	float abs_scale_x = m_scale_x;// * s_screen_scale_x;
+	float abs_scale_y = m_scale_y;// * s_screen_scale_y;
+	if (abs_scale_x < 0.0f)
+	{
+		uint16 temp = u0;
+		u0 = u1;
+		u1 = temp;
+		abs_scale_x = -abs_scale_x;
+	}
+	if (abs_scale_y < 0.0f)
+	{
+		uint16 temp = v0;
+		v0 = v1;
+		v1 = temp;
+		abs_scale_y = -abs_scale_y;
+	}
+
+	x0 = -m_xhot * 16.0f * abs_scale_x;
+	y0 = -m_yhot * 16.0f * abs_scale_y;
+
+	x1 = x0 + ((float) (u1 - u0) * abs_scale_x);
+	y1 = y0 + ((float) (v0 - v1) * abs_scale_y);
+
+	z = s_constant_z_enabled ? s_constant_z : GetZValue();
+
+	// Flip V if texture is inverted in VRAM
+	if (mp_texture && mp_texture->IsInverted())
+	{
+		uint16 temp = v0;
+		v0 = v1;
+		v1 = temp;
+	}
+
+	// Check for rotation
+	if (m_rot != 0.0f)
+	{
+		Mth::Vector p0(x0, y0, 0.0f, 0.0f);
+		Mth::Vector p1(x1, y0, 0.0f, 0.0f);
+		Mth::Vector p2(x0, y1, 0.0f, 0.0f);
+		Mth::Vector p3(x1, y1, 0.0f, 0.0f);
+
+		p0.RotateZ(m_rot);
+		p1.RotateZ(m_rot);
+		p2.RotateZ(m_rot);
+		p3.RotateZ(m_rot);
+
+//printf("Before (%d, %d) - (%d, %d)\n", x0, y0, x1, y1);
+//printf("After (%d, %d) - (%d, %d)\n", (int) p0.GetX(), (int) p0.GetY(), (int) p3.GetX(), (int) p3.GetY());
+
+		int xi0 = ((int) (p0.GetX() * s_screen_scale_x)) + start_screen_x;
+		int xi1 = ((int) (p1.GetX() * s_screen_scale_x)) + start_screen_x;
+		int xi2 = ((int) (p2.GetX() * s_screen_scale_x)) + start_screen_x;
+		int xi3 = ((int) (p3.GetX() * s_screen_scale_x)) + start_screen_x;
+		int yi0 = ((int) (p0.GetY() * s_screen_scale_y)) + start_screen_y;
+		int yi1 = ((int) (p1.GetY() * s_screen_scale_y)) + start_screen_y;
+		int yi2 = ((int) (p2.GetY() * s_screen_scale_y)) + start_screen_y;
+		int yi3 = ((int) (p3.GetY() * s_screen_scale_y)) + start_screen_y;
+
+		// Cull, if necessary
+		if (NeedsCulling() && ((xi0 | xi1 | xi2 | xi3 | yi0 | yi1 | yi2 | yi3) & 0xFFFF0000))
+		{
+			return;
+		}
+
+		// Do a TRISTRIP
+		vif::StoreV4_32(u0,v0,0,0);
+		vif::StoreV4_32(xi0, yi0, z, 0);
+		vif::StoreV4_32(u1,v0,0,0);
+		vif::StoreV4_32(xi1, yi1, z, 0);
+		vif::StoreV4_32(u0,v1,0,0);
+		vif::StoreV4_32(xi2, yi2, z, 0);
+		vif::StoreV4_32(u1,v1,0,0);
+		vif::StoreV4_32(xi3, yi3, z, 0);
+	} else {
+		// Just outputting two points for a SPRITE
+		int xi0 = ((int) (x0 * s_screen_scale_x)) + start_screen_x;
+		int xi1 = ((int) (x1 * s_screen_scale_x)) + start_screen_x;
+		int yi0 = ((int) (y0 * s_screen_scale_y)) + start_screen_y;
+		int yi1 = ((int) (y1 * s_screen_scale_y)) + start_screen_y;
+
+		// Cull, if necessary
+		if (NeedsCulling() && ((xi0 | xi1 | yi0 | yi1) & 0xFFFF0000))
+		{
+			return;
+		}
+
+		vif::StoreV4_32(u0,v0,0,0);
+		vif::StoreV4_32(xi0,yi0,z,0);
+		vif::StoreV4_32(u1,v1,0,0);
+		vif::StoreV4_32(xi1,yi1,z,0);
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+//
+
+void SSprite::EndDraw(void)
+{
+	vif::EndUNPACK();
+	gif::EndTagImmediate(1);
+	vif::MSCAL(VU1_ADDR(Parser));
+	dma::EndTag();
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/sprite.h b/Code/Gfx/NGPS/NX/sprite.h
new file mode 100644
index 0000000..f59bc77
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/sprite.h
@@ -0,0 +1,267 @@
+#ifndef __SPRITE_H
+#define __SPRITE_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+namespace NxPs2
+{
+
+
+// This is a temporary structure until we can merge the 2D textures
+// with texture groups
+struct SSingleTexture
+{
+public:
+	// Flags
+	enum
+	{
+		mLOAD_SCREEN		= 0x0001,
+		mALLOC_VRAM			= 0x0002,
+		mAUTO_ALLOC			= 0x0004,
+		mSPRITE				= 0x0008,
+		mINVERTED			= 0x0010,
+	};
+					SSingleTexture();
+					SSingleTexture(uint8* p_buffer, int buffer_size, bool sprite, bool alloc_vram, bool load_screen = false);	// .img.ps2 buffer
+					SSingleTexture(const char *Filename, bool sprite, bool alloc_vram, bool load_screen = false);				// .img.ps2 file
+					SSingleTexture(uint8* p_buffer, int width, int height, int bitdepth, int clut_bitdepth, int mipmaps,
+								   bool sprite, bool alloc_vram, bool load_screen = false);		// raw buffer
+					SSingleTexture(const SSingleTexture & src_texture);		// Copy constructor
+					~SSingleTexture();
+
+	bool			InitTexture(bool copy);
+	void			FlipTextureVertical();
+
+	bool			IsInVRAM() const;
+
+	// Manual allocate
+	bool			AddToVRAM();
+	bool			RemoveFromVRAM();
+
+	// Auto allocate
+	void			SetAutoAllocate();
+	void			UseTexture(bool use);
+
+	uint16			GetOrigWidth() const;
+	uint16			GetOrigHeight() const;
+
+	uint16			GetWidth() const;
+	uint16			GetHeight() const;
+	uint8			GetBitdepth() const;
+	uint8			GetClutBitdepth() const;
+	uint8			GetNumMipmaps() const;
+	bool			IsTransparent() const;
+	bool			IsInverted() const;
+
+	uint8 *			GetTextureBuffer() const { return mp_PixelData; }
+	uint32			GetTextureBufferSize() const { return m_PixelDataSize; }
+	uint8 *			GetClutBuffer() const { return mp_ClutData; }
+	uint32			GetClutBufferSize() const { return m_ClutDataSize; }
+
+	void			ReallocateVRAM();
+	void			UpdateDMA();
+
+	uint32			m_checksum;
+	uint32			m_flags;
+	uint8 *			mp_VifData;
+	uint8 *			mp_TexBuffer;
+	uint8 *			mp_PixelData;
+	uint8 *			mp_ClutData;
+	uint32			m_PixelDataSize;
+	uint32			m_ClutDataSize;
+	uint32			m_VifSize;
+	int				m_TexBufferSize;
+	uint64			m_RegTEX0, m_RegTEX1;
+	uint32 *		mp_TexBitBltBuf;		// Note these register pointers point to data in the DMA buffer,
+	uint32 *		mp_ClutBitBltBuf;		// so they should only be used for writing, and only after drawing is done.
+
+	uint16			m_orig_width, m_orig_height;
+	uint16			m_NumTexVramBlocks;
+	uint16			m_NumClutVramBlocks;
+	uint16			m_TBP;
+	uint16			m_CBP;
+
+	SSingleTexture	*mp_next;
+
+	#ifdef			__NOPT_ASSERT__
+	char			m_name[128];
+	#endif
+
+	#ifdef			__NOPT_ASSERT__
+	static void		sPrintAllTextureInfo();
+	static void		sPrintAllocatedTextureInfo();
+	#endif
+
+	static SSingleTexture	*sp_stexture_list;
+
+protected:
+
+	bool			setup_reg_and_dma(uint8 *pData, uint32 TW, uint32 TH, uint32 PSM, uint32 CPSM, uint32 MXL, bool copy);
+
+	// Update usage for auto VRAM allocate
+	void			inc_usage_count();
+	void			dec_usage_count();
+
+	static uint32	bitdepth_to_psm(int bitdepth);
+
+	int				m_usage_count;
+};
+
+struct SScissorWindow
+{
+	void			SetScissorReg(uint64 reg);
+	void			SetScissor(uint16 x0, uint16 y0, uint16 x1, uint16 y1);
+	uint64			GetScissorReg() const { return m_RegSCISSOR; }
+
+private:
+	uint64			m_RegSCISSOR;
+};
+
+struct SDraw2D
+{
+					SDraw2D(float pri = 0.0f, bool hide = true);
+	virtual			~SDraw2D();
+
+	void			SetPriority(float pri);
+	float			GetPriority(void) const;
+
+	void			SetZValue(uint32 z);
+	uint32			GetZValue(void) const;
+
+	virtual void	SetHidden(bool hide);		// Virtual because we need to check any vram issues
+	bool			IsHidden(void) const;
+
+	bool			NeedsCulling() const;
+
+	void			SetScissorWindow(SScissorWindow *p_scissor);
+	SScissorWindow *GetScissorWindow() const { return mp_scissor_window; }
+
+	// Statics
+	static void		DrawAll(void);
+
+	static void		EnableConstantZValue(bool enable);
+	static void		SetConstantZValue(uint32 z);
+	static uint32	GetConstantZValue();
+
+	static void		CalcScreenScale();
+	static float	GetScreenScaleX();
+	static float	GetScreenScaleY();
+
+protected:
+	// Scale for PAL conversion
+	static float	s_screen_scale_x;
+	static float	s_screen_scale_y;
+
+	// Constant Z
+	static bool		s_constant_z_enabled;
+	static uint32	s_constant_z;
+
+private:
+	void			InsertSortedDrawList(SDraw2D **p_list);
+	void			InsertDrawList(SDraw2D **p_list);
+	void			RemoveDrawList(SDraw2D **p_list);
+
+	static void		DrawList(SDraw2D *p_list);
+
+	virtual void	BeginDraw(void) = 0;
+	virtual void	Draw(void) = 0;
+	virtual void	EndDraw(void) = 0;
+
+	// Not even the derived classes should have direct access
+	bool			m_hidden;
+	bool			m_use_zbuffer;
+	float			m_pri;
+	uint32			m_z;
+	SScissorWindow *mp_scissor_window;
+
+	// members
+	SDraw2D			*mp_next;
+
+	// 2D draw list (sorted by priority)
+	static SDraw2D	*sp_2D_sorted_draw_list;
+	// 2D zbuffer draw list
+	static SDraw2D	*sp_2D_zbuffer_draw_list;
+};
+
+
+struct SSprite : public SDraw2D
+{
+public:
+					SSprite(float pri = 0.0f);
+	virtual			~SSprite();
+
+	virtual void	SetHidden(bool hide);		// Virtual because we need to check any vram issues
+
+	void			SetTexture(SSingleTexture *p_texture);
+
+	float			m_xpos;
+	float			m_ypos;
+	uint16			m_width;
+	uint16			m_height;
+	float			m_scale_x;
+	float			m_scale_y;
+	float			m_xhot;
+	float			m_yhot;
+	float			m_rot;
+	uint32			m_rgba;
+
+protected:
+
+	SSingleTexture	*mp_texture;
+
+private:
+	void			BeginDraw(void);
+	void			Draw(void);
+	void			EndDraw(void);
+};
+
+
+//
+// Inlines
+//
+
+inline bool		SSingleTexture::IsInVRAM() const
+{
+	return m_flags & mALLOC_VRAM;
+}
+
+inline bool		SSingleTexture::IsInverted() const
+{
+	return m_flags & mINVERTED;
+}
+
+inline float	SDraw2D::GetScreenScaleX()
+{
+	return s_screen_scale_x;
+}
+
+inline float	SDraw2D::GetScreenScaleY()
+{
+	return s_screen_scale_y;
+}
+
+inline uint32	SDraw2D::GetZValue() const
+{
+	return m_z;
+}
+
+inline bool		SDraw2D::IsHidden(void) const
+{
+	return m_hidden;
+}
+
+inline bool		SDraw2D::NeedsCulling() const
+{
+	return m_use_zbuffer;		// Assuming that only items using the ZBuffer need it
+}
+
+extern uint32		FontVramStart;
+extern uint32		FontVramBase;
+extern const uint32	FontVramSize;
+
+
+} // namespace NxPs2
+
+
+#endif // __CHARS_H
diff --git a/Code/Gfx/NGPS/NX/switches.h b/Code/Gfx/NGPS/NX/switches.h
new file mode 100644
index 0000000..6acbd1e
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/switches.h
@@ -0,0 +1,60 @@
+#ifndef __SWITCHES_H
+#define __SWITCHES_H
+
+#ifdef __PLAT_NGPS__
+#include 
+#endif // __PLAT_NGPS__
+
+#define	malloc(x) error Old Memory Function
+#define	calloc(x) error Old Memory Function
+#define	realloc(x) error Old Memory Function
+#define	free(x) error Old Memory Function
+
+
+namespace NxPs2
+{
+
+#define USE_INTERRUPTS 1
+#define	GIF_INTERRUPT  1
+#define VIF1_INTERRUPT 0
+#define GS_INTERRUPT   1
+
+#define TIMING_BARS 0
+
+#define C32 1
+#define C16 0
+#define Z24 1
+#define Z16 0
+
+#ifdef __PLAT_NGPS__
+
+#define HRES_NTSC	(640)
+#define VRES_NTSC	(448)
+#define HRES_PAL	(512)
+#define VRES_PAL	(512)
+
+#define HRES (Config::NTSC() ? HRES_NTSC : HRES_PAL)
+#define VRES (Config::NTSC() ? VRES_NTSC : VRES_PAL)
+
+#define XOFFSET ((2048 - (HRES >> 1)) << 4)
+#define YOFFSET ((2048 - (VRES >> 1)) << 4)
+
+// Start and size of buffers in 2K Word size blocks
+#define FRAME_START		(0)
+#define FRAME_SIZE		(((HRES * VRES) * 4) / (2048 * 4))		// 32-bit frame buffer
+#define DISPLAY_START	(FRAME_START + FRAME_SIZE)
+#define DISPLAY_SIZE	(((HRES * VRES) * 2) / (2048 * 4))		// 16-bit display buffer
+#define ZBUFFER_START	(DISPLAY_START + DISPLAY_SIZE)
+#define ZBUFFER_SIZE	(((HRES * VRES) * 4) / (2048 * 4))		// 32-bit aligned ZBuffer
+
+#endif // __PLAT_NGPS__
+
+#define FTOI_TRICK 1
+
+#define STENCIL_SHADOW 0
+#define OLD_FOG 0
+
+} // namespace NxPs2
+
+#endif // __SWITCHES_H
+
diff --git a/Code/Gfx/NGPS/NX/texture.cpp b/Code/Gfx/NGPS/NX/texture.cpp
new file mode 100644
index 0000000..53de18a
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/texture.cpp
@@ -0,0 +1,1135 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "texturemem.h"
+#include "dma.h"
+#include "vif.h"
+#include "gif.h"
+#include "gs.h"
+#include "group.h"
+#include "scene.h"
+#include "switches.h"
+#include 
+#include 
+
+
+// K: I put this back to 0 for the moment because Conv4to32 is corrupting memory
+// by writing past the end of the temporary swizzle buffer, which causes a hang 
+// when switching themes in the park editor.
+// Garrett: Should be fixed now.
+#define SWIZZLE_IN_LOAD 1
+
+namespace NxPs2
+{
+
+// static array of pointers to sGroup
+// (so that we can pre-allocate our sGroups
+// to avoid memory fragmentation)
+const int vMAX_TEX_GROUPS = 32;
+static sGroup* sp_temp_groups[vMAX_TEX_GROUPS];
+
+extern TextureMemoryLayout TexMemLayout;
+
+//------------------------------------------------------------------------------------
+//		L O A D   T E X T U R E S
+//------------------------------------------------------------------------------------
+//
+// Performs 2 tasks - it adds texture register settings to the array Textures for all
+// textures in the texture file buffer (increasing NumTextures accordingly), and it
+// creates a dma subroutine to upload all the textures in the file buffer to vram.
+// Currently it handles all the packing (poorly!), but this will eventually be done by
+// a preprocess in the tools pipeline.
+
+bool load_textures(sScene* pScene, uint8* pTexBuffer, bool IsSkin, bool IsInstanceable, bool UsesPip, uint32 texDictOffset)
+{
+	Dbg_MsgAssert( pScene, ( "No scene pointer" ) );
+	Dbg_MsgAssert( pTexBuffer, ( "No tex buffer pointer" ) );
+
+	int total_num_textures=0;
+
+#if 0
+	// make sure there's no dma list running
+	sceGsSyncPath(0,0);
+#else
+	// GJ:  Calling "sceGsSyncPath()" instead
+	// of "WaitForRendering()" will cause the boardshop
+	// to crash when going into the "Change Deck" menu.
+	// Neither Mick nor Mike seem to know why this
+	// would be the case, so it's probably worth 
+	// investigating at some point
+	void WaitForRendering();
+	WaitForRendering();
+#endif
+
+//	Dbg_Message("loading textures from file %s\n", Filename);
+
+	// initialise members
+	pScene->NumTextures	= 0;
+	pScene->NumMeshes	= 0;
+	pScene->pMeshes		= NULL;
+	pScene->Flags		= 0;
+	pScene->pInstances	= NULL;
+
+	// link it in
+	pScene->pNext = sScene::pHead;
+	sScene::pHead = pScene;
+
+	// set flags
+	if (UsesPip)
+	{
+		pScene->Flags |= SCENEFLAG_USESPIP;
+	}
+	
+	// set the tex buffer pointer
+	pScene->pTexBuffer = pTexBuffer;
+
+	// set data pointer
+	sScene::pData = pScene->pTexBuffer;
+
+	// version number
+	sTexture::sVersion = *(uint32 *)sScene::pData, sScene::pData+=4;
+//	printf("Texture version %d\n", sTexture::sVersion);
+
+	// get number of texture groups
+	pScene->NumGroups = *(uint32 *)sScene::pData, sScene::pData+=4;
+
+	// get total number of textures, version >= 3
+	if (sTexture::sVersion >= 3)
+	{
+		total_num_textures = *(int *)sScene::pData, sScene::pData+=4;
+	}
+
+
+	// allocate storage for textures
+	if (sTexture::sVersion >= 3)
+	{
+		// if there are no textures, it will assert in Mem::Malloc
+		// so catch this assert earlier here.....
+		// really there should be some more elegant handling of scenes with no textures, but they are sufficently rare.
+		//Dbg_MsgAssert(total_num_textures>0,("Need at least one texture in %s (it has none)\n",Filename));
+		if( total_num_textures > 0 )
+		{
+			pScene->pTextures = (sTexture *)Mem::Malloc(total_num_textures*sizeof(sTexture));
+		}
+		else
+		{
+			pScene->pTextures = NULL;
+		}
+	}
+	else
+	{
+		pScene->pTextures = (sTexture *)Mem::Malloc(2000*sizeof(sTexture));
+	}
+
+	if ( !((total_num_textures == 0 ) || ( pScene->pTextures )) )
+	{
+		Dbg_Message( "couldn't allocate memory for textures" );
+		return false;
+	}
+	
+	Dbg_MsgAssert( pScene->NumGroups < vMAX_TEX_GROUPS, ( "Too many tex groups for static array of sGroup pointers (increase vMAX_TEX_GROUPS from %d)", vMAX_TEX_GROUPS ) );
+	for (int i=0; iNumGroups; i++)
+	{
+		// pre-allocate space for texture groups...
+		sp_temp_groups[i] = (sGroup *)Mem::Malloc(sizeof(sGroup));
+		memset(sp_temp_groups[i],0,sizeof(sGroup));
+	}
+
+	// allocate prebuilt dma buffer for textures
+	#if 0
+	int		available = Mem::Available()-128;			  // -128 is needed otherwise it fails trying to allocate all free mem in a region	
+	if (available > 500000)
+	{
+		available = 500000;	 							  // clamp to 500k if we have loads of memory.  No sure why, maybe just to speed up loading when in debug mode
+	}
+	#else
+	// We don't want to allocate all memory as it messes with memory debugging
+	// so 
+	// only seem to need T*256 + G*48
+	// hmm, somethings seems to be T*512 for some reason....
+	// but upped it a bit to be safe
+	// These only get used for peds and stuff now
+	int available = total_num_textures * 512 + pScene->NumGroups * 128  + 512;  // bumped up multipliers, and added 512 byte extra
+	#endif
+	
+	
+	pScene->pTexDma = (uint8 *)Mem::Malloc(available);	  // Allocate all available memeory on current context 
+
+	// set dma pointer to new buffer
+	dma::pLoc = pScene->pTexDma;
+
+	// test
+	sGroup::VramBufferBase = 0x2BC0;
+
+	// iterate over groups
+	for (int i=0; iNumGroups; i++)
+	{
+		LoadTextureGroup(NULL, pScene, sp_temp_groups[i], IsSkin, IsInstanceable, UsesPip, texDictOffset);
+		sp_temp_groups[i] = NULL;
+	}
+
+	//Dbg_Message( "Dma usage = %d Textures = %d, Groups = %d", (int)(dma::pLoc-pScene->pTexDma), total_num_textures, pScene->NumGroups );
+
+	if ( (int)(dma::pLoc-pScene->pTexDma) >= available )
+	{
+		Dbg_Message( "Texture dictionary used more memory (%d) than available (%d)", (int)(dma::pLoc-pScene->pTexDma), available );
+		return false;
+	}
+
+
+
+
+	// resize dma buffer so it just fits
+	pScene->pTexDma = (uint8 *)Mem::ReallocateShrink(dma::pLoc-pScene->pTexDma, pScene->pTexDma);
+
+	// success!
+	return true;
+}
+
+// load textures from a file
+sScene *LoadTextures(const char *Filename, bool IsSkin, bool IsInstanceable, bool UsesPip, uint32 texDictOffset, uint32 *p_size)
+{
+//	Dbg_Message( "LoadTextures: file = %s", Filename );
+	
+	sScene* pScene = (sScene*)Mem::Malloc(sizeof(sScene));
+	Dbg_MsgAssert(pScene, ("couldn't allocate scene"));
+
+	uint8* pTexBuffer = (uint8*)File::LoadAlloc( Filename, (int*) p_size );
+
+	bool success = load_textures(pScene, pTexBuffer, IsSkin, IsInstanceable, UsesPip, texDictOffset);
+	if ( !success )
+	{
+		Dbg_MsgAssert( 0, ( "Load textures from file %s failed", Filename ) );
+	}
+
+	return pScene;
+}
+
+// load textures from a data buffer, rather than opening a file
+sScene *LoadTextures(uint32* pData, int dataSize, bool IsSkin, bool IsInstanceable, bool UsesPip, uint32 texDictOffset)
+{
+//	Dbg_Message( "LoadTextures: data = %p size = %d", pData, dataSize );
+	
+	sScene* pScene = (sScene*)Mem::Malloc(sizeof(sScene));
+	Dbg_MsgAssert(pScene, ("couldn't allocate scene"));
+
+	uint8* pTexBuffer = (uint8*)Mem::Malloc(dataSize);
+	Dbg_MsgAssert(pTexBuffer, ("couldn't allocate tex buffer"));
+	
+	// copy over the data
+	memcpy( pTexBuffer, pData, dataSize );
+
+	bool success = load_textures(pScene, pTexBuffer, IsSkin, IsInstanceable, UsesPip, texDictOffset);
+	if ( !success )
+	{
+		Dbg_MsgAssert( 0, ( "Load textures from data buffer failed" ) );
+	}
+
+	return pScene;
+}
+
+void LoadTextureGroup(void *pFile, sScene *pScene, sGroup *pGroup, bool IsSkin, bool IsInstanceable, bool UsesPip, uint32 texDictOffset)
+{
+	sTexture *pTex, *pOriginalTexture=NULL;
+	uint32 TW, TH, PSM, CPSM;
+	uint TBP[7], TBW[7], CBP;
+	uint Width[7], Height[7], NumTexBytes[7], NumTexQWords[7], NumClutBytes, NumClutQWords, NumVramBytes[7];
+	uint BitsPerTexel, BitsPerClutEntry, PaletteSize, AdjustedWidth[7], AdjustedHeight[7];
+	uint PageWidth, PageHeight, AdjustedBitsPerTexel, TexCount=0;
+	int i,j,k,NumTexturesThisGroup,MXL;
+	uint NextTBP, LastCBP;
+	uint8 *pTextureSource;
+	static int Shit=0;
+
+	Dbg_MsgAssert( pScene, ( "No pScene" ) );
+	Dbg_MsgAssert( pGroup, ( "No pre-allocated pGroup was specified" ) );
+
+	// set the scene it belongs to
+	pGroup->pScene = pScene;
+
+	pGroup->profile_color = 0x00c000;		// green = static group	 (world, cars)
+	if (IsInstanceable)
+	{
+		pGroup->profile_color = 0x808000;		// cyan = instancable static group (cars, gameobjs)
+	}
+	if (IsSkin)
+	{
+		pGroup->profile_color = 0x000080;		// red = skin group
+		if (IsInstanceable)
+		{
+			pGroup->profile_color = 0x800080;		// magenta = instancable skinned group 
+		}
+	}
+
+
+	// zero the mesh pointer in case no meshes get loaded
+	pGroup->pMeshes = NULL;
+	pGroup->NumMeshes = 0;
+
+	// prepare to build dma info for this group
+	dma::Align(0,16);
+	pGroup->pUpload[0] = pGroup->pUpload[1] = dma::pLoc;
+
+	// get group checksum
+	pGroup->Checksum = *(uint32 *)sScene::pData;
+	pGroup->Checksum += texDictOffset;
+	sScene::pData+=4;
+
+	// get group flags if present
+	if (sTexture::sVersion >= 2)
+	{
+		pGroup->flags = *(uint32 *)sScene::pData, sScene::pData+=4;
+	}
+	else if (pGroup->Checksum>=1000 & pGroup->Checksum<2000)
+	{
+		pGroup->flags = GROUPFLAG_SKY;
+	}
+	else
+	{
+		pGroup->flags = 0;
+	}
+
+	// get group priority if present
+	if (sTexture::sVersion >= 4)
+	{
+		pGroup->Priority = *(float *)sScene::pData, sScene::pData+=4;
+	}
+
+	else
+	{
+		// set its priority manually
+		if (UsesPip)
+		{
+			switch (pGroup->flags & (GROUPFLAG_SKY|GROUPFLAG_TRANSPARENT))
+			{
+			case 0:
+				pGroup->Priority = 0;
+				break;
+			case GROUPFLAG_SKY:
+				pGroup->Priority = -100;
+				break;
+			case GROUPFLAG_TRANSPARENT:
+				pGroup->Priority = 7500;
+				break;
+			case GROUPFLAG_TRANSPARENT|GROUPFLAG_SKY:
+				pGroup->Priority = -99;
+				break;
+			}
+		}
+		if (IsSkin)
+		{
+			pGroup->Priority = 5000+Shit;
+		}
+		if (IsInstanceable)
+		{
+			pGroup->Priority = 2500+Shit;
+		}
+	}
+
+	// if it has the lowest priority, make a new head
+	if (sGroup::pHead==NULL || pGroup->Priority < sGroup::pHead->Priority)
+	{
+		pGroup->pNext = sGroup::pHead;
+		pGroup->pPrev = NULL;
+		sGroup::pHead = pGroup;
+
+		if (pGroup->pNext)
+			pGroup->pNext->pPrev = pGroup;
+		else
+			sGroup::pTail = pGroup;
+	}
+
+	// othwerwise if it has the highest priority, make a new tail
+	else if (pGroup->Priority >= sGroup::pTail->Priority)
+	{
+		pGroup->pPrev = sGroup::pTail;
+		pGroup->pNext = NULL;
+		sGroup::pTail->pNext = pGroup;
+		sGroup::pTail = pGroup;
+	}
+
+	// otherwise find its immediate superior, priority-wise, in the group list
+	else
+	{
+		sGroup *pSup;
+		for (pSup=sGroup::pHead; pSup; pSup=pSup->pNext)
+		{
+			if (pGroup->Priority < pSup->Priority)
+				break;
+		}
+
+		Dbg_MsgAssert(pSup, ("error inserting into group list\n"));
+
+		pGroup->pNext = pSup;
+		pGroup->pPrev = pSup->pPrev;
+		pSup->pPrev->pNext = pGroup;
+		pSup->pPrev = pGroup;
+	}
+
+	// get number of textures
+	NumTexturesThisGroup = *(uint32 *)sScene::pData, sScene::pData+=4;
+
+	// set vram usage for this group
+	pGroup->VramStart = sGroup::VramBufferBase;
+	pGroup->VramEnd   = sGroup::VramBufferBase+0x0A20;
+
+	// advance buffer for next group
+	sGroup::VramBufferBase ^= 0x1E20;
+
+	// initialise base pointers
+	NextTBP = pGroup->VramStart;
+	LastCBP = pGroup->VramEnd;
+
+	// loop over textures
+	for (i=0,pTex=pScene->pTextures+pScene->NumTextures; i= 5)
+		{
+			pTex->Flags = *(uint32 *)sScene::pData, sScene::pData+=4;
+		}
+
+		// get texture checksum
+		pTex->Checksum = *(uint32 *)sScene::pData, sScene::pData+=4;
+
+		// set group checksum
+		pTex->GroupChecksum = pGroup->Checksum;
+
+		// get dimensions
+		TW = *(uint32 *)sScene::pData, sScene::pData+=4;
+		TH = *(uint32 *)sScene::pData, sScene::pData+=4;
+
+		// get pixel storage modes
+		PSM  = *(uint32 *)sScene::pData, sScene::pData+=4;	// for texels
+		CPSM = *(uint32 *)sScene::pData, sScene::pData+=4;	// for clut
+
+		// get maximum mipmap level
+		MXL  = *(uint32 *)sScene::pData, sScene::pData+=4;
+
+		// clear buffer pointers
+		pTex->mp_texture_buffer = NULL;
+		pTex->mp_clut_buffer = NULL;
+		pTex->m_texture_buffer_size = 0;
+		pTex->m_clut_buffer_size = 0;
+
+		// bail if it's a non-texture
+		if (TW == 0xFFFFFFFF)
+		{
+			printf("non-texture at number %d\n", i);
+			// there won't be any more data for this texture
+			continue;
+		}
+
+		// detect duplicated texture, signalled by MXL of -1
+		if (MXL<0)
+		{
+			// find the original texture
+			pOriginalTexture = ResolveTextureChecksum(pTex->Checksum);
+
+			// adjust MXL
+			MXL &= 0x7FFFFFFF;
+
+			// signal duplicate
+			pTex->Flags &= ~TEXFLAG_ORIGINAL;
+		}
+		else
+		{
+			// not a duplicate, must be the original
+			pTex->Flags |= TEXFLAG_ORIGINAL;
+		}
+
+		// quadword-align (original only)
+		if (pTex->Flags & TEXFLAG_ORIGINAL)
+		{
+			sScene::pData = (uint8 *)(((uint)(sScene::pData+15)) & 0xFFFFFFF0);
+		}
+
+		// bits per texel and palette size
+		switch (PSM)
+		{
+		case PSMCT32:
+			BitsPerTexel = 32;
+			PaletteSize  = 0;
+			break;
+		case PSMCT24:
+			BitsPerTexel = 24;
+			PaletteSize  = 0;
+			break;
+		case PSMCT16:
+			BitsPerTexel = 16;
+			PaletteSize  = 0;
+			break;
+		case PSMT8:
+			BitsPerTexel = 8;
+			PaletteSize  = 256;
+			break;
+		case PSMT4:
+			BitsPerTexel = 4;
+			PaletteSize  = 16;
+			break;
+		default:
+			printf("Unknown PSM %d at index %d in texture file\n", PSM, i);
+			exit(1);
+		}
+
+		// adjust bits per texel for 24-bit
+		AdjustedBitsPerTexel = BitsPerTexel;
+		if (BitsPerTexel == 24)
+		{
+			AdjustedBitsPerTexel = 32;
+		}
+
+		// bits per clut entry
+		if (BitsPerTexel < 16)
+			switch (CPSM)
+			{
+			case PSMCT32:
+				BitsPerClutEntry = 32;
+				break;
+			case PSMCT16:
+				BitsPerClutEntry = 16;
+				break;
+			default:
+				printf("Unknown CPSM %d in texture file\n", PSM);
+				exit(1);
+			}
+		else
+			BitsPerClutEntry = 0;
+
+		// rearrange original 256-colour cluts according to requirements of CSM1
+		if (PSM==PSMT8 && (pTex->Flags & TEXFLAG_ORIGINAL))
+		{
+			uint32 temp32;
+			uint16 temp16;
+			for (j=0; j<256; j+=32)
+				for (k=0; k<8; k++)
+					if (CPSM==PSMCT32)
+					{
+						temp32 = ((uint32 *)sScene::pData)[j+k+8];
+						((uint32 *)sScene::pData)[j+k+8] = ((uint32 *)sScene::pData)[j+k+16];
+						((uint32 *)sScene::pData)[j+k+16] = temp32;
+					}
+					else
+					{
+						temp16 = ((uint16 *)sScene::pData)[j+k+8];
+						((uint16 *)sScene::pData)[j+k+8] = ((uint16 *)sScene::pData)[j+k+16];
+						((uint16 *)sScene::pData)[j+k+16] = temp16;
+					}
+		}
+
+		// calculate texture dimensions
+		for (j=0; j<=MXL; j++)
+		{
+			Width[j]  = (TW>=0 ? 1<> j;
+			Height[j] = (TH>=0 ? 1<> j;
+			TBW[j] = (Width[j]+63) >> 6;
+			if (BitsPerTexel<16 && TBW[j]<2)
+				TBW[j] = 2;
+		}
+
+		// get page size
+		switch (PSM)
+		{
+		case PSMCT32:
+		case PSMCT24:
+			PageWidth  = 64;
+			PageHeight = 32;
+			break;
+		case PSMCT16:
+			PageWidth  = 64;
+			PageHeight = 64;
+			break;
+		case PSMT8:
+			PageWidth  = 128;
+			PageHeight = 64;
+			break;
+		case PSMT4:
+			PageWidth  = 128;
+			PageHeight = 128;
+			break;
+		default:
+			printf("Unknown PSM %d at index %d in texture file\n", PSM, i);
+			exit(1);
+		}
+
+		// calculate adjusted dimensions based on vram page size
+		for (j=0; j<=MXL; j++)
+		{
+			AdjustedWidth[j]  = Width[j];
+			AdjustedHeight[j] = Height[j];
+
+			if (AdjustedWidth[j] < PageWidth && AdjustedHeight[j] > PageHeight)
+			{
+				AdjustedWidth[j] = PageWidth;
+			}
+
+			if (AdjustedWidth[j] > PageWidth && AdjustedHeight[j] < PageHeight)
+			{
+				AdjustedHeight[j] = PageHeight;
+			}
+
+			if (TBW[j]<<6 > AdjustedWidth[j])
+			{
+				AdjustedWidth[j] = TBW[j]<<6;
+			}
+		}
+
+		// calculate sizes within file
+		NumClutBytes  = PaletteSize * BitsPerClutEntry >> 3;
+		NumClutQWords = NumClutBytes >> 4;
+		for (j=0; j<=MXL; j++)
+		{
+			NumTexBytes[j]  = Width[j] * Height[j] * BitsPerTexel >> 3;
+			NumVramBytes[j] = AdjustedWidth[j] * AdjustedHeight[j] * AdjustedBitsPerTexel >> 3;
+			NumTexQWords[j] = (NumTexBytes[j]+15) >> 4;
+		}
+
+		// calculate TBP
+		TBP[0] = NextTBP;
+		for (j=1; j<=MXL; j++)
+			TBP[j] = (((TBP[j-1]<<8)+NumVramBytes[j-1]+0x1FFF) & 0xFFFFE000) >> 8;
+
+		// calculate CBP
+		if (BitsPerTexel >= 16)
+			CBP = LastCBP;
+		else if (BitsPerTexel == 4)
+			CBP = LastCBP - 1;
+		else
+			CBP = LastCBP - (CPSM==PSMCT32 ? 4 : 2);
+
+		// calculate next TBP
+		NextTBP = (((TBP[MXL]<<8)+NumVramBytes[MXL]+0x1FFF) & 0xFFFFE000) >> 8;
+
+		// bail if VRAM would become over-packed
+		if (NextTBP > CBP)
+		{
+			printf("no room for texture %d\n", i);
+			if (pTex->Flags & TEXFLAG_ORIGINAL)
+			{
+				sScene::pData += NumClutBytes;
+				for (j=0; j<=MXL; j++)
+				{
+					sScene::pData = (uint8 *)(((uint)(sScene::pData+NumTexBytes[j]+15)) & 0xFFFFFFF0);
+				}
+			}
+			continue;
+		}
+
+		// add clut upload to dma list if necessary
+		if (BitsPerTexel < 16)
+		{
+			// source
+			if (pTex->Flags & TEXFLAG_ORIGINAL)
+			{
+				pTextureSource = sScene::pData;
+			}
+			else
+			{
+				pTextureSource = pOriginalTexture->mp_clut_buffer;
+			}
+
+			// save buffer pointer
+			pTex->mp_clut_buffer = pTextureSource;
+			pTex->m_clut_buffer_size = NumClutBytes;
+
+			// add dma
+			dma::BeginTag(dma::cnt,0);
+			vif::NOP();
+			vif::NOP();
+			gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+			gs::Reg2(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,CBP,1,CPSM));
+			gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+			if (PSM==PSMT4)
+				gs::Reg2(gs::TRXREG,PackTRXREG(8,2));
+			else
+				gs::Reg2(gs::TRXREG,PackTRXREG(16,16));
+			gs::Reg2(gs::TRXDIR,	0);
+			gif::Tag2(0,0,IMAGE,0,0,0,NumClutQWords);
+			dma::EndTag();
+			dma::Tag(dma::ref, NumClutQWords, (uint)pTextureSource);
+			vif::NOP();
+			vif::NOP();
+		}
+
+		if (pTex->Flags & TEXFLAG_ORIGINAL)
+		{
+			// step past clut data, and quadword-align
+			sScene::pData = (uint8 *)(((uint)(sScene::pData+NumClutBytes+15)) & 0xFFFFFFF0);
+
+			// save buffer pointer
+			pTex->mp_texture_buffer = sScene::pData;
+		}
+		else
+		{
+			pTex->mp_texture_buffer = pOriginalTexture->mp_texture_buffer;
+		}
+		pTextureSource = pTex->mp_texture_buffer;
+
+
+		// offset to improve caching
+		for (j=0; j<=MXL; j++)
+		{
+			if (TexCount & 1)
+			{
+				if ((BitsPerTexel==32 || BitsPerTexel==8)
+						&& (Width[j]  <= (PageWidth>>1))
+						&& (Height[j] <= PageHeight)
+					||
+					(BitsPerTexel==16 || BitsPerTexel==4)
+						&& (Width[j]  <=  PageWidth)
+						&& (Height[j] <= (PageHeight>>1)))
+				{
+					TBP[j] += 16;
+					//printf("better caching!\n");
+				}
+			}
+			TexCount++;
+		}
+
+#if SWIZZLE_IN_LOAD
+		bool swizzleTex = BitsPerTexel <= 8;
+#endif // SWIZZLE_IN_LOAD
+
+		// loop over mipmaps
+		for (j=0; j<=MXL; j++)
+		{
+#if SWIZZLE_IN_LOAD
+			int swizzle_tbw = TBW[j];
+			int swizzle_width = Width[j];
+			int swizzle_height = Height[j];
+			uint32 swizzlePSM = PSM;
+			bool swizzleMip = false;
+
+			if (swizzleTex)
+			{
+				switch (PSM)
+				{
+				case PSMT8:
+					{
+						if (TexMemLayout.CanConv8to32(Width[j], Height[j]))
+						{
+							if (pTex->Flags & TEXFLAG_ORIGINAL)
+							{
+								Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+								uint8 *pTextureSwizzle = (uint8 *) Mem::Malloc(Width[j] * Height[j]);
+								Mem::Manager::sHandle().PopContext();
+
+								TexMemLayout.Conv8to32(Width[j], Height[j], pTextureSource, pTextureSwizzle);
+								memcpy(pTextureSource, pTextureSwizzle, Width[j] * Height[j]);
+
+								Mem::Free(pTextureSwizzle);
+							}
+
+							// Change size to 32-bit version
+							swizzle_width /= 2;
+							swizzle_height /= 2;
+
+							// Recalculate Buffer Width since 8-bit version can be artificially high
+							swizzle_tbw = swizzle_width / 64;
+							if (swizzle_tbw == 0)
+								swizzle_tbw = 1;
+
+							swizzleMip = true;
+							swizzlePSM=PSMCT32;	// swizzle
+							pTex->Flags |= TEXFLAG_MIP_SWIZZLE_32BIT(j);
+						}
+						break;
+					}
+				case PSMT4:
+					{
+						if (TexMemLayout.CanConv4to32(Width[j], Height[j]))
+						{
+							//Dbg_Message("Swizzling texture checksum %x", pTex->Checksum);
+
+							if (pTex->Flags & TEXFLAG_ORIGINAL)
+							{
+								Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+								uint8 *pTextureSwizzle = (uint8 *) Mem::Malloc(Width[j] * Height[j] / 2);
+								Mem::Manager::sHandle().PopContext();
+
+								TexMemLayout.Conv4to32(Width[j], Height[j], pTextureSource, pTextureSwizzle);
+								memcpy(pTextureSource, pTextureSwizzle, Width[j] * Height[j] / 2);
+
+								Mem::Free(pTextureSwizzle);
+							}
+
+							// Change size to 32-bit version
+							swizzle_width /= 2;
+							swizzle_height /= 4;
+
+							// Recalculate Buffer Width since 4-bit version can be artificially high
+							swizzle_tbw = swizzle_width / 64;
+							if (swizzle_tbw == 0)
+								swizzle_tbw = 1;
+
+							swizzleMip = true;
+							swizzlePSM=PSMCT32;	// swizzle
+							pTex->Flags |= TEXFLAG_MIP_SWIZZLE_32BIT(j);
+						}
+						else if (TexMemLayout.CanConv4to16(Width[j], Height[j]))
+						{
+							if (pTex->Flags & TEXFLAG_ORIGINAL)
+							{
+								Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+								uint8 *pTextureSwizzle = (uint8 *) Mem::Malloc(Width[j] * Height[j] / 2);
+								Mem::Manager::sHandle().PopContext();
+
+								TexMemLayout.Conv4to16(Width[j], Height[j], pTextureSource, pTextureSwizzle);
+								memcpy(pTextureSource, pTextureSwizzle, Width[j] * Height[j] / 2);
+
+								Mem::Free(pTextureSwizzle);
+							}
+
+							// Change size to 16-bit version
+							swizzle_width /= 2;
+							swizzle_height /= 2;
+
+							// Recalculate Buffer Width since 4-bit version can be artificially high
+							swizzle_tbw = swizzle_width / 64;
+							if (swizzle_tbw == 0)
+								swizzle_tbw = 1;
+
+							swizzleMip = true;
+							swizzlePSM=PSMCT16;	// swizzle
+							pTex->Flags |= TEXFLAG_MIP_SWIZZLE_16BIT(j);
+						}
+						break;
+					}
+
+				default:
+					break;
+				}
+			}
+#endif // SWIZZLE_IN_LOAD
+
+
+			// add texture upload to dma list for this mipmap level
+			dma::BeginTag(dma::cnt,0);
+			vif::NOP();
+			vif::NOP();
+			gif::Tag2(gs::A_D,1,PACKED,0,0,0,4);
+#if SWIZZLE_IN_LOAD
+			gs::Reg2(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,TBP[j],swizzle_tbw,swizzlePSM));
+			gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+			gs::Reg2(gs::TRXREG,	PackTRXREG(swizzle_width,swizzle_height));
+#else
+			gs::Reg2(gs::BITBLTBUF,PackBITBLTBUF(0,0,0,TBP[j],TBW[j],PSM));
+			gs::Reg2(gs::TRXPOS,	PackTRXPOS(0,0,0,0,0));
+			gs::Reg2(gs::TRXREG,	PackTRXREG(Width[j],Height[j]));
+#endif // SWIZZLE_IN_LOAD
+			gs::Reg2(gs::TRXDIR,	0);
+			
+			if (j==0)
+			{
+				pTex->mp_dma = dma::pLoc;
+				pTex->m_quad_words = NumTexQWords[j];
+				pTex->m_render_count = 0;
+			}
+			
+			
+			gif::Tag2(0, 0, IMAGE, 0, 0, 0, NumTexQWords[j]);  			// 0..3 words, NumTexQWords is in the lower 15 bits of the first word [0]
+			dma::EndTag();									   			// Just Aligns upo to QW boundry
+			dma::Tag(dma::ref, NumTexQWords[j], (uint)pTextureSource);  // 0..1 (after alignment) NumTexQWords goes in word 1
+			vif::NOP();
+			vif::NOP();
+
+			// step past tex data for this level
+			pTextureSource += NumTexBytes[j];
+		}
+
+		pTex->m_texture_buffer_size = (uint32) pTextureSource - (uint32) pTex->mp_texture_buffer;
+
+		if (pTex->Flags & TEXFLAG_ORIGINAL)
+		{
+			sScene::pData = pTextureSource;
+		}
+
+		// make entry in texture table
+		pTex->MXL        = MXL;
+		pTex->RegTEX0    = PackTEX0(TBP[0],TBW[0],PSM,TW,TH,(PSM!=PSMCT24),MODULATE,CBP,CPSM,0,0,1);
+		pTex->RegMIPTBP1 = PackMIPTBP1(TBP[1],TBW[1],TBP[2],TBW[2],TBP[3],TBW[3]);
+		pTex->RegMIPTBP2 = PackMIPTBP2(TBP[4],TBW[4],TBP[5],TBW[5],TBP[6],TBW[6]);
+
+		// advance texture base pointer to 1st page after texture
+		// and reduce clut base pointer to 1st page before clut (if there was one)
+		TBP[0] = NextTBP;
+		LastCBP = CBP;
+
+	}
+
+	// add to total for the scene
+	pScene->NumTextures += NumTexturesThisGroup;
+
+	// texflush so we can use textures
+	dma::BeginTag(dma::end, 0);
+	vif::NOP();
+	vif::NOP();
+	gif::Tag2(gs::A_D,1,PACKED,0,0,1,1);
+	gs::Reg2(gs::TEXFLUSH,	0);
+	dma::EndTag();
+
+	if (UsesPip==false)
+	{
+		Shit++;
+	}
+
+}
+
+
+
+
+void DeleteTextures(sScene *pScene)
+{
+	sScene *pPrev;
+	sGroup *pGroup, *pDead;
+
+	// deallocate assets
+	Mem::Free(pScene->pTexBuffer);
+	Mem::Free(pScene->pTexDma);
+	if( pScene->pTextures != NULL )
+	{
+		Mem::Free(pScene->pTextures);
+	}
+
+	// deallocate groups
+	pGroup = sGroup::pHead;
+	while (pGroup)
+	{
+		if (pGroup->pScene == pScene)
+		{
+			if (pGroup == sGroup::pHead)
+			{
+				pGroup->pNext->pPrev = NULL;
+				sGroup::pHead = pGroup->pNext;
+			}
+			else if (pGroup == sGroup::pTail)
+			{
+				pGroup->pPrev->pNext = NULL;
+				sGroup::pTail = pGroup->pPrev;
+			}
+			else
+			{
+				pGroup->pNext->pPrev = pGroup->pPrev;
+				pGroup->pPrev->pNext = pGroup->pNext;
+			}
+			pDead = pGroup;
+			pGroup = pGroup->pNext;
+			Mem::Free(pDead);
+		}
+		else
+			pGroup = pGroup->pNext;
+	}
+
+	// unlink
+	if (pScene==sScene::pHead)
+		sScene::pHead = pScene->pNext;
+	else
+	{
+		for (pPrev=sScene::pHead; pPrev; pPrev=pPrev->pNext)
+			if (pPrev->pNext == pScene)
+				break;
+		Dbg_MsgAssert(pPrev, ("couldn't find scene to delete\n"));
+
+		pPrev->pNext = pScene->pNext;
+	}
+
+	// free
+	Mem::Free(pScene);
+}
+
+
+
+
+
+sTexture *ResolveTextureChecksum(uint32 TextureChecksum)
+{
+	sScene *pScene;
+	sTexture *pTex=NULL;
+	int i;
+
+	for (pScene=sScene::pHead; pScene; pScene=pScene->pNext)
+	{
+		for (i=0,pTex=pScene->pTextures; iNumTextures; i++,pTex++)
+			if ((pTex->Checksum == TextureChecksum) && (pTex->Flags & TEXFLAG_ORIGINAL))
+				return pTex;
+	}
+
+	return NULL;
+}
+
+
+
+sTexture *ResolveTextureChecksum(uint32 TextureChecksum, uint32 GroupChecksum)
+{
+	sScene *pScene;
+	sTexture *pTex=NULL;
+	int i;
+
+	for (pScene=sScene::pHead; pScene; pScene=pScene->pNext)
+	{
+		for (i=0,pTex=pScene->pTextures; iNumTextures; i++,pTex++)
+			if ((pTex->Checksum == TextureChecksum) && (pTex->GroupChecksum == GroupChecksum))
+				return pTex;
+	}
+
+	return NULL;
+}
+
+
+
+uint32 sTexture::sVersion;
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// The following Get() calls are very inefficient.  They should only be
+// used when necessary (i.e. initialization).
+//
+uint16	sTexture::GetWidth() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &RegTEX0);
+	uint16 TW = tex0.TW;
+	//uint16 TW = (uint16) ((RegTEX0 >> 26) & M04);
+
+	return 1 << TW;
+}
+
+uint16	sTexture::GetHeight() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &RegTEX0);
+	uint16 TH = tex0.TH;
+	//uint16 TH = (uint16) ((RegTEX0 >> 30) & M04);
+
+	return 1 << TH;
+}
+
+uint8	sTexture::GetBitdepth() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &RegTEX0);
+	uint PSM = tex0.PSM;
+
+	switch (PSM)
+	{
+	case PSMCT32:
+		return 32;
+		break;
+	case PSMCT24:
+		return 24;
+		break;
+	case PSMCT16:
+		return 16;
+		break;
+	case PSMT8:
+		return 8;
+		break;
+	case PSMT4:
+		return 4;
+		break;
+	default:
+		return 0;
+	}
+}
+
+uint8	sTexture::GetClutBitdepth() const
+{
+	sceGsTex0 tex0 = *((sceGsTex0 *) &RegTEX0);
+	uint CPSM = tex0.CPSM;
+
+	switch (CPSM)
+	{
+	case PSMCT32:
+		return 32;
+		break;
+	case PSMCT24:
+		return 24;
+		break;
+	case PSMCT16:
+		return 16;
+		break;
+	default:
+		return 0;
+	}
+}
+
+uint8	sTexture::GetNumMipmaps() const
+{
+	return MXL + 1;
+}
+
+bool	sTexture::HasSwizzleMip() const
+{
+	for (uint mip_idx = 0; mip_idx <= MXL; mip_idx++)
+	{
+		if (Flags & TEXFLAG_MIP_SWIZZLE(mip_idx))
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+void	sTexture::ReplaceTextureData(uint8 *p_texture_data)
+{
+	uint mip_width, mip_height, mip_NumTexBytes;
+
+	int bitdepth = GetBitdepth();
+	int width = GetWidth();
+	int height = GetHeight();
+
+	uint8 *pTextureSource = mp_texture_buffer;
+
+	// calculate texture dimensions
+	for (uint j=0; j<=MXL; j++)
+	{
+		mip_width  = width >> j;
+		mip_height = height >> j;
+		mip_NumTexBytes  = mip_width * mip_height * bitdepth >> 3;
+
+		// Do the actual copies, swizzling if necessary
+		if (Flags & TEXFLAG_MIP_SWIZZLE_16BIT(j))
+		{
+			switch (bitdepth)
+			{
+			case 4:
+				TexMemLayout.Conv4to16(mip_width, mip_height, p_texture_data, pTextureSource);
+				break;
+			case 8:
+			default:
+				Dbg_MsgAssert(0, ("Can't swizzle a texture of bitdepth %d to 16-bit", bitdepth));
+				break;
+			}
+		}
+		else if (Flags & TEXFLAG_MIP_SWIZZLE_32BIT(j))
+		{
+			switch (bitdepth)
+			{
+			case 8:
+				TexMemLayout.Conv8to32(mip_width, mip_height, p_texture_data, pTextureSource);
+				break;
+			case 4:
+				TexMemLayout.Conv4to32(mip_width, mip_height, p_texture_data, pTextureSource);
+				break;
+			default:
+				Dbg_MsgAssert(0, ("Can't swizzle a texture of bitdepth %d to 32-bit", bitdepth));
+				break;
+			}
+		}
+		else
+		{
+			memcpy(pTextureSource, p_texture_data, mip_NumTexBytes);
+		}
+
+		// step past tex data for this level
+		pTextureSource += mip_NumTexBytes;
+		p_texture_data += mip_NumTexBytes;
+	}
+
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/texture.h b/Code/Gfx/NGPS/NX/texture.h
new file mode 100644
index 0000000..45cb79b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/texture.h
@@ -0,0 +1,86 @@
+#ifndef __TEXTURE_H
+#define __TEXTURE_H
+
+
+namespace NxPs2
+{
+
+
+#define TEXTURE_BUFFER_SIZE 4194304
+
+////////////////////////
+// Flags
+#define TEXFLAG_ORIGINAL			(1<<0)
+#define TEXFLAG_ANIMATED			(1<<1)
+#define TEXFLAG_MIP0_SWIZZLE_16BIT	(1<<18)			// The swizzle bit flags go all the way to the end of the Flag variable
+#define TEXFLAG_MIP1_SWIZZLE_16BIT	(1<<19)
+#define TEXFLAG_MIP2_SWIZZLE_16BIT	(1<<20)
+#define TEXFLAG_MIP3_SWIZZLE_16BIT	(1<<21)
+#define TEXFLAG_MIP4_SWIZZLE_16BIT	(1<<22)
+#define TEXFLAG_MIP5_SWIZZLE_16BIT	(1<<23)
+#define TEXFLAG_MIP6_SWIZZLE_16BIT	(1<<24)
+#define TEXFLAG_MIP0_SWIZZLE_32BIT	(1<<25)
+#define TEXFLAG_MIP1_SWIZZLE_32BIT	(1<<26)
+#define TEXFLAG_MIP2_SWIZZLE_32BIT	(1<<27)
+#define TEXFLAG_MIP3_SWIZZLE_32BIT	(1<<28)
+#define TEXFLAG_MIP4_SWIZZLE_32BIT	(1<<29)
+#define TEXFLAG_MIP5_SWIZZLE_32BIT	(1<<30)
+#define TEXFLAG_MIP6_SWIZZLE_32BIT	(1<<31)
+
+#define TEXFLAG_MIP0_SWIZZLE		(TEXFLAG_MIP0_SWIZZLE_16BIT | TEXFLAG_MIP0_SWIZZLE_32BIT)
+
+#define TEXFLAG_MIP_SWIZZLE_16BIT(m)	(TEXFLAG_MIP0_SWIZZLE_16BIT << m)
+#define TEXFLAG_MIP_SWIZZLE_32BIT(m)	(TEXFLAG_MIP0_SWIZZLE_32BIT << m)
+#define TEXFLAG_MIP_SWIZZLE(m)			(TEXFLAG_MIP0_SWIZZLE << m)
+
+struct sGroup;
+
+struct sTexture
+{
+	uint16			GetWidth() const;
+	uint16			GetHeight() const;
+	uint8			GetBitdepth() const;
+	uint8			GetClutBitdepth() const;
+	uint8			GetNumMipmaps() const;
+
+	uint8 *			GetTextureBuffer() const { return mp_texture_buffer; }
+	uint32			GetTextureBufferSize() const { return m_texture_buffer_size; }
+	uint8 *			GetClutBuffer() const { return mp_clut_buffer; }
+	uint32			GetClutBufferSize() const { return m_clut_buffer_size; }
+
+	bool			HasSwizzleMip() const;
+	void			ReplaceTextureData(uint8 *p_texture_data);
+
+	uint32 Checksum, GroupChecksum, MXL;
+	uint64 RegTEX0, RegMIPTBP1, RegMIPTBP2;
+
+	uint8  *mp_texture_buffer;
+	uint32 m_texture_buffer_size;
+	uint8  *mp_clut_buffer;
+	uint32 m_clut_buffer_size;
+
+	//bool   Original;
+	uint32 Flags;
+
+	static uint32 sVersion;
+	
+	
+	uint8		*	mp_dma;	 			// pointer to DMA list if we want to patch up this texture
+	uint32		m_render_count;			// count of how many times this has been rendered this frame
+	uint32 		m_quad_words;			// number of quad words to upload in first pass
+};
+
+
+
+struct sScene *LoadTextures(uint32* pData, int dataSize, bool IsSkin=false, bool IsInstanceable=false, bool UsesPip=false, uint32 texDictOffset=0);
+struct sScene *LoadTextures(const char *Filename, bool IsSkin=false, bool IsInstanceable=false, bool UsesPip=false, uint32 texDictOffset=0, uint32 *p_size = NULL);
+void LoadTextureGroup(void *pFile, sScene *pScene, sGroup *pGroup, bool IsSkin, bool IsInstanceable, bool UsesPip, uint32 texDictOffset);
+void DeleteTextures(sScene *pScene);
+sTexture *ResolveTextureChecksum(uint32 TextureChecksum);
+sTexture *ResolveTextureChecksum(uint32 TextureChecksum, uint32 GroupChecksum);
+
+
+} // namespace NxPs2
+
+#endif // __TEXTURE_H
+
diff --git a/Code/Gfx/NGPS/NX/texturemem.cpp b/Code/Gfx/NGPS/NX/texturemem.cpp
new file mode 100644
index 0000000..c93be10
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/texturemem.cpp
@@ -0,0 +1,1250 @@
+#include 
+#include "texture.h"
+#include "texturemem.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+
+#include 
+
+#define min(x, y) (((x) > (y))? (y): (x))
+#define max(x, y) (((x) < (y))? (y): (x))
+
+namespace NxPs2
+{
+
+const TextureMemoryLayout::PixelFormatLayout TextureMemoryLayout::pixelFormat[NUM_PIXEL_TYPES] = {
+    // PIXEL_32
+    {   { 64, 32 },     // page size
+        { 8, 8 },       // block size
+        true       },   // adjacentBlockWidth
+    // PIXEL_16
+    {   { 64, 64 },     // page size
+        { 16, 8 },      // block size
+        false      },   // adjacentBlockWidth
+    // PIXEL_8
+    {   { 128, 64 },    // page size
+        { 16, 16 },     // block size
+        true       },   // adjacentBlockWidth
+    // PIXEL_4
+    {   { 128, 128 },   // page size
+        { 32, 16 },     // block size
+        false      },   // adjacentBlockWidth
+};
+
+const int TextureMemoryLayout::blockArrangement[4][8] = {
+    {  0,  1,  4,  5, 16, 17, 20, 21 },
+    {  2,  3,  6,  7, 18, 19, 22, 23 },
+    {  8,  9, 12, 13, 24, 25, 28, 29 },
+    { 10, 11, 14, 15, 26, 27, 30, 31 }
+};
+
+//----------------------------------------------------------------------------
+//
+TextureMemoryLayout::TextureMemoryLayout()
+{
+    for (int i = PIXEL_32; i < NUM_PIXEL_TYPES; i++)
+        initMinSize(minSize[i], PixelType(i));
+}
+
+//----------------------------------------------------------------------------
+//
+void
+TextureMemoryLayout::initMinSize(MinSizeArray& mArray, PixelType pType)
+{
+    int tw, th;
+
+    for (th = 0; th < MAX_PIXEL_SIZE_BITS; th++) {
+        for (tw = 0; tw < MAX_PIXEL_SIZE_BITS; tw++) {
+            mArray[tw][th].width = 1 << tw;
+            mArray[tw][th].height = 1 << th;
+
+            setMinSize(mArray[tw][th].width, mArray[tw][th].height, pType);
+        }
+    }
+}
+
+//----------------------------------------------------------------------------
+//
+void
+TextureMemoryLayout::setMinSize(int& width, int& height, PixelType pType)
+{
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    // All widths and heights are powers of 2
+
+    // If texture dimension is bigger than a page dimension, then
+    // make sure whole pages are allocated and exit.
+    if (width > pf.pageSize.width) {
+        height = Mth::Max(height, pf.pageSize.height);
+        return;
+    } else if (height > pf.pageSize.height) {
+        width = Mth::Max(width, pf.pageSize.width);
+        return;
+    }
+
+    // Make sure width and height are at least block size
+    if (width < pf.blockSize.width)
+        width = pf.blockSize.width;
+    if (height < pf.blockSize.height)
+        height = pf.blockSize.height;
+
+#if 0
+    // Now we are only dealing with sub-pages.  Make sure
+    // sizes don't go beyond allowable aspect ratios
+    
+    TexSize twoBlock;
+
+    if (pf.adjacentBlockWidth) {    // width
+        twoBlock.width = pf.blockSize.width * 2;
+        twoBlock.height = pf.blockSize.height;
+    } else {                        // height
+        twoBlock.width = pf.blockSize.width;
+        twoBlock.height = pf.blockSize.height * 2;
+    }
+#endif
+
+    // It just happens that, given a sub-page,
+    // the height cannot be bigger than the
+    // width and the width cannot be more than
+    // double the height
+    if (height > width)
+        width = height;
+    if (width > (height * 2))
+        height = width / 2;
+}
+
+//----------------------------------------------------------------------------
+//
+TextureMemoryLayout::PixelType
+TextureMemoryLayout::modeToPixelType(int pixMode)
+{
+    switch(pixMode) {
+    case PSMCT32:
+    case PSMCT24:
+        return PIXEL_32;
+
+    case PSMCT16:
+        return PIXEL_16;
+
+    case PSMT8:
+        return PIXEL_8;
+
+    case PSMT4:
+        return PIXEL_4;
+
+    default:
+        Dbg_MsgAssert(0, ("TextureMemoryLayout: PixelMode %d not supported.", pixMode));
+        return PIXEL_INVALID;
+    }
+}
+
+//----------------------------------------------------------------------------
+//
+void
+TextureMemoryLayout::getMinSize(int& width, int& height, int pixMode)
+{
+    PixelType pType = modeToPixelType(pixMode);
+
+    // Now get the minimum size
+    int tw = numBits(width) - 1;
+    int th = numBits(height) - 1;
+
+    width = minSize[pType][tw][th].width;
+    height = minSize[pType][tw][th].height;
+}
+
+//----------------------------------------------------------------------------
+// Returns size in blocks.  Must calculate the minimum width and height first.
+int
+TextureMemoryLayout::getImageSize(int width, int height, int pixMode)
+{
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+    int bw, bh;
+
+    bw = width / pf.blockSize.width;
+    bh = height / pf.blockSize.height;
+
+    return bw * bh;
+}
+
+//----------------------------------------------------------------------------
+//
+int
+TextureMemoryLayout::getBlockNumber(int blockX, int blockY, int pixMode)
+{
+
+    switch (pixMode) {
+    case PSMCT32:
+    case PSMCT24:
+    case PSMT8:
+        return blockArrangement[blockY][blockX];
+
+    case PSMCT16:
+    case PSMT4:
+        return blockArrangement[blockX][blockY];
+
+    default:
+        Dbg_MsgAssert(0, ("TextureMemoryLayout: getBlockNumber %d not supported.", pixMode));
+        return -1;
+    }
+}
+
+//----------------------------------------------------------------------------
+//
+void
+TextureMemoryLayout::getPosFromBlockNumber(int& posX, int& posY, int blockNumber, int pixMode)
+{
+    int blockRow = -1, blockCol = -1;
+
+    for (int i = 0; i < 4; i++)
+        for (int j = 0; j < 8; j++)
+            if (blockArrangement[i][j] == blockNumber) {
+                blockRow = i;
+                blockCol = j;
+                break;
+            }
+
+    if (blockRow < 0)
+        Dbg_MsgAssert(0, ("TextureMemoryLayout: getPosFromBlockNumber"));
+
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    switch (pixMode) {
+    case PSMCT32:
+    case PSMCT24:
+    case PSMT8:
+        posX = blockCol * pf.blockSize.width;
+        posY = blockRow * pf.blockSize.height;
+        break;
+
+    case PSMCT16:
+    case PSMT4:
+        posX = blockRow * pf.blockSize.width;
+        posY = blockCol * pf.blockSize.height;
+        break;
+
+    default:
+        Dbg_MsgAssert(0, ("TextureMemoryLayout: getPosFromBlockNumber %d not supported.", pixMode));
+    }
+}
+
+//----------------------------------------------------------------------------
+//
+int
+TextureMemoryLayout::getBlockOffset(int bufferWidth, int posX, int posY, int pixMode)
+{
+    int horizPages, vertPages;
+    int numPages = 0;
+    int blockNumber = 0;
+    
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    // First figure out how many pages are above
+    vertPages = posY / pf.pageSize.height;
+    if (vertPages) {
+        horizPages = bufferWidth / pf.pageSize.width;
+        posY -= vertPages * pf.pageSize.height;
+        numPages += vertPages * horizPages;
+    }
+
+    // Now how many are to the left
+    horizPages = posX / pf.pageSize.width;
+    if (horizPages) {
+        posX -= horizPages * pf.pageSize.width;
+        numPages += horizPages;
+    }
+
+    // Now figure out where we are within the page
+    int horizBlocks, vertBlocks;
+    horizBlocks = posX / pf.blockSize.width;
+    vertBlocks = posY / pf.blockSize.height;
+    blockNumber = getBlockNumber(horizBlocks, vertBlocks, pixMode);
+
+    return (numPages * 32) + blockNumber;
+}
+
+//----------------------------------------------------------------------------
+//
+void
+TextureMemoryLayout::getPosFromBlockOffset(int& posX, int& posY, int blockOffset, int bufferWidth, int pixMode)
+{
+    int horizPages, vertPages;
+    
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    posX = posY = 0;
+
+    // First figure out how many pages are above
+    int horizPageWidth = bufferWidth / pf.pageSize.width;
+    if (horizPageWidth) {
+        vertPages = blockOffset / (horizPageWidth * 32);
+        if (vertPages) {
+            posY += vertPages * pf.pageSize.height;
+            blockOffset -= vertPages * horizPageWidth * 32;
+        }
+    }
+
+    // Now how many are to the left
+    horizPages = blockOffset / 32;
+    if (horizPages) {
+        posX += horizPages * pf.pageSize.width;
+        blockOffset -= horizPages * 32;
+    }
+
+    // Now figure out where we are within the page
+    int blockPosX, blockPosY;
+    getPosFromBlockNumber(blockPosX, blockPosY, blockOffset, pixMode);
+    
+    posX += blockPosX;
+    posY += blockPosY;
+}
+
+//----------------------------------------------------------------------------
+//
+bool
+TextureMemoryLayout::isPageSize(int width, int height, int pixMode)
+{
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    return !(width % pf.pageSize.width) && !(height % pf.pageSize.height);
+}
+
+//----------------------------------------------------------------------------
+//
+bool
+TextureMemoryLayout::isBlockSize(int width, int height, int pixMode)
+{
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    return !(width % pf.blockSize.width) && !(height % pf.blockSize.height);
+}
+
+//----------------------------------------------------------------------------
+//
+bool
+TextureMemoryLayout::isMinPageSize(int width, int height, int pixMode)
+{
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    getMinSize(width, height, pixMode);
+
+    return (width >= pf.pageSize.width) && (height >= pf.pageSize.height);
+}
+
+//----------------------------------------------------------------------------
+//
+bool
+TextureMemoryLayout::isMinBlockSize(int width, int height, int pixMode)
+{
+    PixelType pType = modeToPixelType(pixMode);
+    const PixelFormatLayout &pf = pixelFormat[pType];
+
+    getMinSize(width, height, pixMode);
+
+    return (width >= pf.blockSize.width) && (height >= pf.blockSize.height);
+}
+
+//----------------------------------------------------------------------------
+//
+int
+TextureMemoryLayout::numBits(unsigned int size)
+{
+    int bits = 0;
+
+    while (size > 0) {
+        size >>= 1;
+        bits++;
+    }
+
+    return bits;
+}
+
+
+/////////////////////////////////////////////////////////////
+// GS Simulator code
+//
+
+uint32 *p_gsmem;
+int gsmem_size = 0;
+
+int block16[32] =
+{
+	 0,  2,  8, 10,
+	 1,  3,  9, 11,
+	 4,  6, 12, 14,
+	 5,  7, 13, 15,
+	16, 18, 24, 26,
+	17, 19, 25, 27,
+	20, 22, 28, 30,
+	21, 23, 29, 31
+};
+
+int columnWord16[32] = 
+{
+	 0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,
+	 2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15
+};
+
+int columnHalf16[32] =
+{
+	0, 0, 0, 0, 0, 0, 0, 0,  1, 1, 1, 1, 1, 1, 1, 1,
+	0, 0, 0, 0, 0, 0, 0, 0,  1, 1, 1, 1, 1, 1, 1, 1
+};
+
+
+void readTexPSMCT16(int dbp, int dbw, int dsax, int dsay, int rrw, int rrh, void *data)
+{
+	//dbw >>= 1;
+	unsigned short *src = (unsigned short *)data;
+	int startBlockPos = dbp * 64;
+
+	for(int y = dsay; y < dsay + rrh; y++)
+	{
+		int pageY = y / 64;
+		int py = y - (pageY * 64);
+		int blockY = py / 8;
+		int by = py - blockY * 8;
+
+		int column = by / 2;
+		int cy = by - column * 2;
+
+		for(int x = dsax; x < dsax + rrw; x++)
+		{
+			int pageX = x / 64;
+			int page  = pageX + pageY * dbw;
+
+			int px = x - (pageX * 64);
+
+			int blockX = px / 16;
+			int block  = block16[blockX + blockY * 4];
+
+			int bx = px - blockX * 16;
+
+			int cx = bx;
+			int cw = columnWord16[cx + cy * 16];
+			int ch = columnHalf16[cx + cy * 16];
+
+			int gs_index = startBlockPos + page * 2048 + block * 64 + column * 16 + cw;
+			Dbg_MsgAssert(gs_index < gsmem_size, ("GS simulator memory size: %d; addressing: %d", gsmem_size, gs_index));
+			unsigned short *dst = (unsigned short *)&p_gsmem[gs_index];
+			*src = dst[ch];
+			src++;
+		}
+	}
+}
+
+int block4[32] =
+{
+	 0,  2,  8, 10,
+	 1,  3,  9, 11,
+	 4,  6, 12, 14,
+	 5,  7, 13, 15,
+	16, 18, 24, 26,
+	17, 19, 25, 27,
+	20, 22, 28, 30,
+	21, 23, 29, 31
+};
+
+int columnWord4[2][128] =
+{
+	{
+		 0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,
+		 2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,
+
+		 8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,
+		10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7
+	},
+	{
+		 8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,
+		10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,
+
+		 0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,
+		 2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15
+	}
+};
+
+int columnByte4[128] =
+{
+	0, 0, 0, 0, 0, 0, 0, 0,  2, 2, 2, 2, 2, 2, 2, 2,  4, 4, 4, 4, 4, 4, 4, 4,  6, 6, 6, 6, 6, 6, 6, 6,
+	0, 0, 0, 0, 0, 0, 0, 0,  2, 2, 2, 2, 2, 2, 2, 2,  4, 4, 4, 4, 4, 4, 4, 4,  6, 6, 6, 6, 6, 6, 6, 6,
+
+	1, 1, 1, 1, 1, 1, 1, 1,  3, 3, 3, 3, 3, 3, 3, 3,  5, 5, 5, 5, 5, 5, 5, 5,  7, 7, 7, 7, 7, 7, 7, 7,
+	1, 1, 1, 1, 1, 1, 1, 1,  3, 3, 3, 3, 3, 3, 3, 3,  5, 5, 5, 5, 5, 5, 5, 5,  7, 7, 7, 7, 7, 7, 7, 7
+};
+
+void writeTexPSMT4(int dbp, int dbw, int dsax, int dsay, int rrw, int rrh, void *data)
+{
+	dbw >>= 1;
+	unsigned char *src = (unsigned char *)data;
+	int startBlockPos = dbp * 64;
+
+	bool odd = false;
+
+	for(int y = dsay; y < dsay + rrh; y++)
+	{
+		int pageY = y / 128;
+		int py = y - (pageY * 128);
+		int blockY = py / 16;
+		int by = py - blockY * 16;
+
+		int column = by / 4;
+		int cy = by - column * 4;
+
+		for(int x = dsax; x < dsax + rrw; x++)
+		{
+			int pageX = x / 128;
+			int page  = pageX + pageY * dbw;
+
+			int px = x - (pageX * 128);
+
+			int blockX = px / 32;
+			int block  = block4[blockX + blockY * 4];
+
+			int bx = px - blockX * 32;
+
+			int cx = bx;
+			int cw = columnWord4[column & 1][cx + cy * 32];
+			int cb = columnByte4[cx + cy * 32];
+
+			int gs_index = startBlockPos + page * 2048 + block * 64 + column * 16 + cw;
+			Dbg_MsgAssert(gs_index < gsmem_size, ("GS simulator memory size: %d; addressing: %d", gsmem_size, gs_index));
+			unsigned char *dst = (unsigned char *)&p_gsmem[gs_index];
+
+			if(cb & 1)
+			{
+				if(odd)
+					dst[cb >> 1] = (dst[cb >> 1] & 0x0f) | ((*src) & 0xf0);
+				else
+					dst[cb >> 1] = (dst[cb >> 1] & 0x0f) | (((*src) << 4) & 0xf0);
+			}
+			else
+			{
+				if(odd)
+					dst[cb >> 1] = (dst[cb >> 1] & 0xf0) | (((*src) >> 4) & 0x0f);
+				else
+					dst[cb >> 1] = (dst[cb >> 1] & 0xf0) | ((*src) & 0x0f);
+			}
+
+			if(odd)
+				src++;
+
+			odd = !odd;
+		}
+	}
+}
+
+// -------------------------------------------------------------------------------------------
+//
+//
+int TextureMemoryLayout::BlockConv4to32(u_char *p_input, u_char *p_output) {
+
+	static int lut[] = {
+		// even column
+		0, 68, 8,  76, 16, 84, 24, 92,
+		1, 69, 9,  77, 17, 85, 25, 93,
+		2, 70, 10, 78, 18, 86, 26, 94,
+		3, 71, 11, 79, 19, 87, 27, 95,
+		4, 64, 12, 72, 20, 80, 28, 88,
+		5, 65, 13, 73, 21, 81, 29, 89,
+		6, 66, 14, 74, 22, 82, 30, 90,
+		7, 67, 15, 75, 23, 83, 31, 91,
+
+		32, 100, 40, 108, 48, 116, 56, 124,
+		33, 101, 41, 109, 49, 117, 57, 125,
+		34, 102, 42, 110, 50, 118, 58, 126,
+		35, 103, 43, 111, 51, 119, 59, 127,
+		36, 96,  44, 104, 52, 112, 60, 120,
+		37, 97,  45, 105, 53, 113, 61, 121,
+		38, 98,  46, 106, 54, 114, 62, 122,
+		39, 99,  47, 107, 55, 115, 63, 123,
+
+
+		// odd column
+		4, 64, 12, 72, 20, 80, 28, 88,
+		5, 65, 13, 73, 21, 81, 29, 89,
+		6, 66, 14, 74, 22, 82, 30, 90,
+		7, 67, 15, 75, 23, 83, 31, 91,
+		0, 68, 8,  76, 16, 84, 24, 92,
+		1, 69, 9,  77, 17, 85, 25, 93,
+		2, 70, 10, 78, 18, 86, 26, 94,
+		3, 71, 11, 79, 19, 87, 27, 95,
+
+		36, 96,  44, 104, 52, 112, 60, 120,
+		37, 97,  45, 105, 53, 113, 61, 121,
+		38, 98,  46, 106, 54, 114, 62, 122,
+		39, 99,  47, 107, 55, 115, 63, 123,
+		32, 100, 40, 108, 48, 116, 56, 124,
+		33, 101, 41, 109, 49, 117, 57, 125,
+		34, 102, 42, 110, 50, 118, 58, 126,
+		35, 103, 43, 111, 51, 119, 59, 127
+	};
+
+	unsigned int i, j, k, i0, i1, i2;
+	unsigned int index0, index1;
+	unsigned char c_in, c_out, *pIn;
+
+	pIn = p_input;
+
+	// for first step, we only think for a single block. (4bits, 32x16)
+	index1 = 0;
+
+	for(k = 0; k < 4; k++) {
+		index0 = (k % 2) * 128;
+
+		for(i = 0; i < 16; i++) {
+
+			for(j = 0; j < 4; j++) {
+
+				c_out = 0x00;
+
+				// lower 4bit.
+				i0 = lut[index0++];
+				i1 = i0 / 2;
+				i2 = (i0 & 0x1) * 4;
+				c_in = (pIn[i1] & (0x0f << i2)) >> i2;
+				c_out = c_out | c_in;
+
+				// uppper 4bit
+				i0 = lut[index0++];
+				i1 = i0 / 2;
+				i2 = (i0 & 0x1) * 4;
+				c_in = (pIn[i1] & (0x0f << i2)) >> i2;
+				c_out = c_out | ((c_in << 4) & 0xf0);
+
+				p_output[index1++] = c_out;
+			}
+		}
+		pIn += 64;
+	}
+
+	return 0;
+}
+
+
+// -------------------------------------------------------------------------------------------
+//
+//
+int TextureMemoryLayout::BlockConv8to32(u_char *p_input, u_char *p_output) {
+
+	static int lut[] = {
+		// even column
+		0, 36, 8,  44,
+		1, 37, 9,  45,
+		2, 38, 10, 46,
+		3, 39, 11, 47,
+		4, 32, 12, 40,
+		5, 33, 13, 41,
+		6, 34, 14, 42,
+		7, 35, 15, 43,
+
+		16, 52, 24, 60,
+		17, 53, 25, 61,
+		18, 54, 26, 62,
+		19, 55, 27, 63, 
+		20, 48, 28, 56,
+		21, 49, 29, 57,
+		22, 50, 30, 58,
+		23, 51, 31, 59,
+
+		// odd column
+		4, 32, 12, 40,
+		5, 33, 13, 41,
+		6, 34, 14, 42,
+		7, 35, 15, 43,
+		0, 36, 8,  44,
+		1, 37, 9,  45,
+		2, 38, 10, 46,
+		3, 39, 11, 47,
+
+		20, 48, 28, 56,
+		21, 49, 29, 57,
+		22, 50, 30, 58,
+		23, 51, 31, 59,
+		16, 52, 24, 60,
+		17, 53, 25, 61,
+		18, 54, 26, 62,
+		19, 55, 27, 63
+	};
+
+	unsigned int i, j, k, i0;
+	unsigned int index0, index1;
+	unsigned char *pIn;
+
+	pIn = p_input;
+
+	// for first step, we only think for a single block. (4bits, 32x16)
+	index1 = 0;
+
+	for(k = 0; k < 4; k++) {
+
+		index0 = (k % 2) * 64;
+
+		for(i = 0; i < 16; i++) {
+			for(j = 0; j < 4; j++) {
+				i0 = lut[index0++];
+				p_output[index1++] = pIn[i0];
+			}
+		}
+		pIn += 64;
+	}
+
+	return 0;
+}
+
+
+// -------------------------------------------------------------------------------
+// send page size 4bit texture and get each block
+// 
+//
+//
+#define PSMT4_BLOCK_WIDTH 32
+#define PSMT4_BLOCK_HEIGHT 16
+
+#define PSMCT32_BLOCK_WIDTH 8
+#define PSMCT32_BLOCK_HEIGHT 8
+
+int TextureMemoryLayout::PageConv4to32(int width, int height, u_char *p_input, u_char *p_output) {
+
+	static u_int block_table4[] = {
+		0,  2,  8, 10,
+		1,  3,  9, 11,
+		4,  6, 12, 14,
+		5,  7, 13, 15,
+		16, 18, 24, 26,
+		17, 19, 25, 27,
+		20, 22, 28, 30,
+		21, 23, 29, 31
+	};
+
+	static u_int block_table32[] = {
+		0,  1,  4,  5, 16, 17, 20, 21,
+		2,  3,  6,  7, 18, 19, 22, 23,
+		8,  9, 12, 13, 24, 25, 28, 29,
+		10, 11, 14, 15, 26, 27, 30, 31
+	};
+
+	u_int *index32_h, *index32_v, in_block_nb;
+
+	u_char input_block[16 * 16], output_block[16 * 16];
+	u_char *pi0, *pi1, *po0, *po1;
+	int index0, index1, i, j, k;
+	int n_width, n_height, input_page_line_size;
+	int output_page_line_size;
+
+
+
+
+
+	// --- create table for output 32bit buffer ---
+	index32_h = (u_int*) malloc(8 * 4 * sizeof(u_int));
+	index32_v = (u_int*) malloc(8 * 4 * sizeof(u_int));
+	index0 = 0;
+	for(i = 0; i < 4; i++) {
+		for(j = 0; j < 8; j++) {
+			index1 = block_table32[index0];
+			index32_h[index1] = j;
+			index32_v[index1] = i;
+			index0++;
+		}
+	}
+
+
+
+
+	n_width = width / 32;
+	n_height = height / 16;
+
+	memset(input_block, 0, 16 *16);
+	memset(output_block, 0, 16 * 16);
+
+	input_page_line_size = 128 / 2;    // PSMT4 page width (byte)
+	output_page_line_size = 256;       // PSMCT32 page width (byte)
+
+	// now assume copying from page top. 
+	for(i = 0; i < n_height; i++) {
+
+		for(j = 0; j < n_width; j++) {
+
+			pi0 = input_block;
+			pi1 = p_input + 16 * i * input_page_line_size + j * 16;
+
+			in_block_nb = block_table4[i * n_width + j];
+
+			for(k = 0; k < PSMT4_BLOCK_HEIGHT; k++) {
+				memcpy(pi0, pi1, PSMT4_BLOCK_WIDTH / 2); // copy full 1 line of 1 block.
+				pi0 += PSMT4_BLOCK_WIDTH / 2;
+				pi1 += input_page_line_size;
+			}
+
+			BlockConv4to32(input_block, output_block);
+
+			po0 = output_block;
+			po1 = p_output + 8 * index32_v[in_block_nb] * output_page_line_size + index32_h[in_block_nb] * 32;
+			for(k = 0; k < PSMCT32_BLOCK_HEIGHT; k++) {
+				memcpy(po1, po0, PSMCT32_BLOCK_WIDTH * 4);
+				po0 += PSMCT32_BLOCK_WIDTH * 4;
+				po1 += output_page_line_size;   
+			}
+
+		}
+	}
+
+	free(index32_h);
+	free(index32_v);
+
+	return 0;
+}
+
+
+
+// -------------------------------------------------------------------------------
+// send page size 8bit texture and get each block
+// 
+//
+//
+#define PSMT8_BLOCK_WIDTH  16
+#define PSMT8_BLOCK_HEIGHT 16
+
+
+
+int TextureMemoryLayout::PageConv8to32(int width, int height, u_char *p_input, u_char *p_output) {
+
+	static u_int block_table8[] = {
+		0,  1,  4,  5, 16, 17, 20, 21,
+		2,  3,  6,  7, 18, 19, 22, 23,
+		8,  9, 12, 13, 24, 25, 28, 29,
+		10, 11, 14, 15, 26, 27, 30, 31
+	};
+
+	static u_int block_table32[] = {
+		0,  1,  4,  5, 16, 17, 20, 21,
+		2,  3,  6,  7, 18, 19, 22, 23,
+		8,  9, 12, 13, 24, 25, 28, 29,
+		10, 11, 14, 15, 26, 27, 30, 31
+	};
+
+	u_int *index32_h, *index32_v, in_block_nb;
+
+	u_char input_block[16 * 16], output_block[16 * 16];
+	u_char *pi0, *pi1, *po0, *po1;
+	int index0, index1, i, j, k;
+	int n_width, n_height, input_page_line_size;
+	int output_page_line_size;
+
+
+
+	// --- create table for output 32bit buffer ---
+	index32_h = (u_int*) malloc(8 * 4 * sizeof(u_int));
+	index32_v = (u_int*) malloc(8 * 4 * sizeof(u_int));
+	index0 = 0;
+	for(i = 0; i < 4; i++) {
+		for(j = 0; j < 8; j++) {
+			index1 = block_table32[index0];
+			index32_h[index1] = j;
+			index32_v[index1] = i;
+			index0++;
+		}
+	}
+
+
+	// how many blocks we should calc (width/height)
+	n_width = width / PSMT8_BLOCK_WIDTH;
+	n_height = height / PSMT8_BLOCK_HEIGHT;
+
+	memset(input_block, 0, 16 *16);
+	memset(output_block, 0, 16 * 16);
+
+	input_page_line_size  = 128;    // PSMT8 page width (byte)
+	output_page_line_size = 256;    // PSMCT32 page width (byte)
+
+	// now assume copying from page top. 
+	for(i = 0; i < n_height; i++) {
+
+		for(j = 0; j < n_width; j++) {
+
+			pi0 = input_block;
+			pi1 = p_input + PSMT8_BLOCK_HEIGHT * i * input_page_line_size + j * PSMT8_BLOCK_WIDTH; // byte
+
+			in_block_nb = block_table8[i * n_width + j];
+
+			for(k = 0; k < PSMT8_BLOCK_HEIGHT; k++) {
+				memcpy(pi0, pi1, PSMT8_BLOCK_WIDTH); // copy full 1 line of 1 block.
+				pi0 += PSMT8_BLOCK_WIDTH;  
+				pi1 += input_page_line_size;
+			}
+
+			BlockConv8to32(input_block, output_block);
+
+			po0 = output_block;
+			po1 = p_output + 8 * index32_v[in_block_nb] * output_page_line_size + index32_h[in_block_nb] * 32;
+			for(k = 0; k < PSMCT32_BLOCK_HEIGHT; k++) {
+				memcpy(po1, po0, PSMCT32_BLOCK_WIDTH * 4);
+				po0 += PSMCT32_BLOCK_WIDTH * 4;
+				po1 += output_page_line_size;   
+			}
+
+		}
+	}
+
+	free(index32_h);
+	free(index32_v);
+
+	return 0;
+}
+
+
+// -------------------------------------------------------------
+//
+//
+
+#define PSMT4_PAGE_WIDTH    128
+#define PSMT4_PAGE_HEIGHT   128
+#define PSMCT32_PAGE_WIDTH  64
+#define PSMCT32_PAGE_HEIGHT 32
+
+static bool sCanConvert4to32[10][10] =
+{
+#if 1	// This one only allows for dimensions that are easy to calculate the 32-bit dimensions
+	//  1      2      4      8     16     32     64     128    256    512
+	{ false, false, false, false, false, false, false, false, false, false },	// 1
+	{ false, false, false, false, false, false, false, false, false, false },	// 2
+	{ false, false, false, false, false, false, false, false, false, false },	// 4
+	{ false, false, false, false, false, false , false, false, false, false },	// 8
+	{ false, false, false, false, false, false , false, false, false, false },	// 16
+	{ false, false, false, false, false, true , false, false, false, false },	// 32
+	{ false, false, false, false, false, false, true , false, false, false },	// 64
+	{ false, false, false, false, false, false, false, true , true , true  },	// 128
+	{ false, false, false, false, false, false, false, true , true , true  },	// 256
+	{ false, false, false, false, false, false, false, true , true , true  },	// 512
+#else
+	//  1      2      4      8     16     32     64     128    256    512
+	{ false, false, false, false, false, false, false, false, false, false },	// 1
+	{ false, false, false, false, false, false, false, false, false, false },	// 2
+	{ false, false, false, false, false, false, false, false, false, false },	// 4
+	{ false, false, false, false, false, true , false, false, false, false },	// 8
+	{ false, false, false, false, false, true , false, false, false, false },	// 16
+	{ false, false, false, false, false, true , true , false, false, false },	// 32
+	{ false, false, false, false, false, true , true , true , false, false },	// 64
+	{ false, false, false, false, false, false, true , true , true , true  },	// 128
+	{ false, false, false, false, false, false, false, true , true , true  },	// 256
+	{ false, false, false, false, false, false, false, true , true , true  },	// 512
+#endif
+};
+
+bool TextureMemoryLayout::CanConv4to32(int width, int height)
+{
+    int tw = numBits(width) - 1;
+    int th = numBits(height) - 1;
+
+	return sCanConvert4to32[th][tw];
+}
+
+bool TextureMemoryLayout::Conv4to32(int width, int height, u_char *p_input, u_char *p_output) {
+
+
+	int i, j, k;
+	int n_page_h, n_page_w, n_page4_width_byte, n_page32_width_byte;
+	u_char *pi0, *pi1, *po0, *po1;
+	int n_input_width_byte, n_output_width_byte, n_input_height, n_output_height;
+	u_char input_page[PSMT4_PAGE_WIDTH / 2 * PSMT4_PAGE_HEIGHT];
+	u_char output_page[PSMCT32_PAGE_WIDTH * 4 * PSMCT32_PAGE_HEIGHT];
+
+	#ifdef __NOPT_ASSERT__
+	int output_buffer_size = width * height / 2;
+	#endif
+
+	// ----- check width -----
+	for(i = 0; i < 11; i++) {
+		if(width == (0x400 >> i)) break;
+	}
+	if(i == 11) {
+		fprintf(stderr, "Error : width is not 2^n\n");
+		return false;
+	}
+
+	//printf("input_page: %d\n", PSMT4_PAGE_WIDTH / 2 * PSMT4_PAGE_HEIGHT);
+	//printf("output_page: %d\n", PSMCT32_PAGE_WIDTH * 4 * PSMCT32_PAGE_HEIGHT);
+
+	memset(input_page, 0, PSMT4_PAGE_WIDTH / 2 * PSMT4_PAGE_HEIGHT);
+	memset(output_page, 0, PSMCT32_PAGE_WIDTH * 4 * PSMCT32_PAGE_HEIGHT);
+
+	// ----- check height -----
+	for(i = 0; i < 11; i++) {
+		if(height == (0x400 >> i)) break;
+	}
+	if(i == 11) {
+		fprintf(stderr, "Error : width is not 2^n\n");
+		return false;
+	}
+
+
+	n_page_w = (width - 1) / PSMT4_PAGE_WIDTH + 1;
+	n_page_h = (height - 1) / PSMT4_PAGE_HEIGHT + 1;
+
+	n_page4_width_byte = PSMT4_PAGE_WIDTH / 2;
+	n_page32_width_byte = PSMCT32_PAGE_WIDTH * 4;
+
+	//printf("n_page_w : %d\n", n_page_w );
+	//printf("n_page_h : %d\n", n_page_h );
+
+	//printf("n_page4_width_byte : %d\n", n_page4_width_byte );
+	//printf("n_page32_width_byte : %d\n", n_page32_width_byte );
+
+
+	// --- set in/out buffer size (for image smaller than one page) ---
+	if(n_page_w == 1) {
+		n_input_width_byte = width / 2;
+		n_output_height = width / 4;
+	} else {
+		n_input_width_byte = n_page4_width_byte;
+		n_output_height = PSMCT32_PAGE_HEIGHT;
+	}
+
+	if(n_page_h == 1) {
+		n_input_height = height;
+		n_output_width_byte = height * 2;
+	} else {
+		n_input_height = PSMT4_PAGE_HEIGHT;
+		n_output_width_byte = n_page32_width_byte;
+	}
+
+
+	for(i = 0; i < n_page_h; i++) {
+		for(j = 0; j < n_page_w; j++) {
+			pi0 = p_input + (n_input_width_byte * PSMT4_PAGE_HEIGHT) * n_page_w * i 
+				+ n_input_width_byte * j;
+			pi1 = input_page;
+
+			for(k = 0; k < n_input_height; k++) {
+				memcpy(pi1, pi0, n_input_width_byte);
+				pi0 += n_input_width_byte * n_page_w;
+				pi1 += n_page4_width_byte;
+			}
+
+			PageConv4to32(PSMT4_PAGE_WIDTH, PSMT4_PAGE_HEIGHT, input_page, output_page);
+
+			po0 = p_output + (n_output_width_byte * PSMCT32_PAGE_HEIGHT) * n_page_w * i
+				+ n_output_width_byte * j;
+			po1 = output_page;
+			for(k = 0; k < n_output_height; k++) {
+				Dbg_MsgAssert(((int) (po0 - p_output) + n_output_width_byte) <= output_buffer_size, ("Went outside output buffer for texture of size (%d, %d)", width, height));
+				memcpy(po0, po1, n_output_width_byte);
+				po0 += n_output_width_byte * n_page_w;
+				po1 += n_page32_width_byte;
+			}          
+		}
+	}
+
+	return CanConv4to32(width, height);
+}
+
+
+// -------------------------------------------------------------
+//
+//
+
+#define PSMT8_PAGE_WIDTH    128
+#define PSMT8_PAGE_HEIGHT   64
+
+static bool sCanConvert8to32[10][10] =
+{
+	//  1      2      4      8     16     32     64     128    256    512
+	{ false, false, false, false, false, false, false, false, false, false },	// 1
+	{ false, false, false, false, false, false, false, false, false, false },	// 2
+	{ false, false, false, false, true , true , true , true , true , false },	// 4
+	{ false, false, false, false, true , true , true , true , true , false },	// 8
+	{ false, false, false, false, true , true , true , true , true , false },	// 16
+	{ false, false, false, false, true , true , true , true , true , false },	// 32
+	{ false, false, false, false, true , true , true , true , true , true  },	// 64
+	{ false, false, false, false, false, false, false, true , true , true  },	// 128
+	{ false, false, false, false, false, false, false, true , true , true  },	// 256
+	{ false, false, false, false, false, false, false, true , true , false },	// 512
+};
+
+bool TextureMemoryLayout::CanConv8to32(int width, int height)
+{
+    int tw = numBits(width) - 1;
+    int th = numBits(height) - 1;
+
+	return sCanConvert8to32[th][tw];
+}
+
+bool TextureMemoryLayout::Conv8to32(int width, int height, u_char *p_input, u_char *p_output) {
+
+	int i, j, k;
+	int n_page_h, n_page_w, n_page8_width_byte, n_page32_width_byte;
+	int n_input_width_byte, n_output_width_byte, n_input_height, n_output_height;
+	u_char *pi0, *pi1, *po0, *po1;
+	u_char input_page[PSMT8_PAGE_WIDTH * PSMT8_PAGE_HEIGHT];
+	u_char output_page[PSMCT32_PAGE_WIDTH * 4 * PSMCT32_PAGE_HEIGHT];
+
+	#ifdef __NOPT_ASSERT__
+	int output_buffer_size = width * height;
+	#endif
+	
+	// ----- check width -----
+	for(i = 0; i < 11; i++) {
+		if(width == (0x400 >> i)) break;
+	}
+	if(i == 11) {
+		fprintf(stderr, "Error : width is not 2^n\n");
+		return false;
+	}
+
+	// ----- check height -----
+	for(i = 0; i < 11; i++) {
+		if(height == (0x400 >> i)) break;
+	}
+	if(i == 11) {
+		fprintf(stderr, "Error : width is not 2^n\n");
+		return false;
+	}
+
+	memset(input_page, 0, PSMT8_PAGE_WIDTH * PSMT8_PAGE_HEIGHT);
+	memset(output_page, 0, PSMCT32_PAGE_WIDTH * 4 * PSMCT32_PAGE_HEIGHT);
+
+	n_page_w = (width - 1) / PSMT8_PAGE_WIDTH + 1;
+	n_page_h = (height - 1) / PSMT8_PAGE_HEIGHT + 1;
+
+	n_page8_width_byte = PSMT8_PAGE_WIDTH;
+	n_page32_width_byte = PSMCT32_PAGE_WIDTH * 4;
+
+	// --- set in/out buffer size (for image smaller than one page) ---
+	if(n_page_w == 1) {
+		n_input_width_byte = width;
+		n_output_width_byte = width * 2;
+	} else {
+		n_input_width_byte = n_page8_width_byte;
+		n_output_width_byte = n_page32_width_byte;
+	}
+
+	if(n_page_h == 1) {
+		n_input_height = height;
+		n_output_height = height / 2;
+	} else {
+		n_input_height = PSMT8_PAGE_HEIGHT;
+		n_output_height = PSMCT32_PAGE_HEIGHT;
+	}
+
+	// --- conversion ---
+	for(i = 0; i < n_page_h; i++) {
+		for(j = 0; j < n_page_w; j++) {
+			pi0 = p_input + (n_input_width_byte * PSMT8_PAGE_HEIGHT) * n_page_w * i 
+				+ n_input_width_byte * j;
+			pi1 = input_page;
+
+			for(k = 0; k < n_input_height; k++) {
+				memcpy(pi1, pi0, n_input_width_byte);
+				pi0 += n_input_width_byte * n_page_w;
+				pi1 += n_page8_width_byte;
+			}
+
+			// --- convert a page ---
+			PageConv8to32(PSMT8_PAGE_WIDTH, PSMT8_PAGE_HEIGHT, input_page, output_page);
+
+			po0 = p_output + (n_output_width_byte * n_output_height) * n_page_w * i
+				+ n_output_width_byte * j;
+			po1 = output_page;
+			for(k = 0; k < n_output_height; k++) {
+				Dbg_MsgAssert(((int) (po0 - p_output) + n_output_width_byte) <= output_buffer_size, ("Went outside output buffer for texture of size (%d, %d)", width, height));
+				memcpy(po0, po1, n_output_width_byte);
+				po0 += n_output_width_byte * n_page_w;
+				po1 += n_page32_width_byte;
+			}          
+		}
+	}
+
+	return CanConv4to16(width, height);
+}
+
+// -------------------------------------------------------------
+//
+//
+
+static bool sCanConvert4to16[10][10] =
+{
+	//  1      2      4      8     16     32     64     128    256    512
+	{ false, false, false, false, false, false, false, false, false, false },	// 1
+	{ false, false, false, false, false, false, false, false, false, false },	// 2
+	{ false, false, false, false, false, false, false, false, false, false },	// 4
+	{ false, false, false, false, true , true , true , true , true , false },	// 8
+	{ false, false, false, false, true , true , true , true , true , false },	// 16
+	{ false, false, false, false, true , true , true , true , true , false },	// 32
+	{ false, false, false, false, true , true , true , true , true , false },	// 64
+	{ false, false, false, false, true , true , true , true , true , true  },	// 128
+	{ false, false, false, false, false, false, false, true , true , true  },	// 256
+	{ false, false, false, false, false, false, false, true , true , true  },	// 512
+};
+
+bool TextureMemoryLayout::CanConv4to16(int width, int height)
+{
+    int tw = numBits(width) - 1;
+    int th = numBits(height) - 1;
+
+	return sCanConvert4to16[th][tw];
+}
+
+bool TextureMemoryLayout::Conv4to16(int width, int height, u_char *p_input, u_char *p_output) {
+
+
+	int i;
+
+	// ----- check width -----
+	for(i = 0; i < 11; i++) {
+		if(width == (0x400 >> i)) break;
+	}
+	if(i == 11) {
+		fprintf(stderr, "Error : width is not 2^n\n");
+		return false;
+	}
+
+	// ----- check height -----
+	for(i = 0; i < 11; i++) {
+		if(height == (0x400 >> i)) break;
+	}
+	if(i == 11) {
+		fprintf(stderr, "Error : width is not 2^n\n");
+		return false;
+	}
+	
+	// Find size needed for buffers
+	int min_4_width = width;
+	int min_4_height = height;
+	int min_16_width = width / 2;
+	int min_16_height = height / 2;
+	getMinSize(min_4_width, min_4_height, PSMT4);
+	getMinSize(min_16_width, min_16_height, PSMCT16);
+
+	int blocks_needed = max(getImageSize(min_4_width, min_4_height, PSMT4), getImageSize(min_16_width, min_16_height, PSMCT16));
+	int words_needed = blocks_needed * 64;
+
+	// Allocate memory
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	p_gsmem = new uint32[words_needed];
+	gsmem_size = words_needed;
+	Mem::Manager::sHandle().PopContext();
+
+	// Swizzle through GS simulator code
+	writeTexPSMT4(0, max(width / 128, 1) * 2, 0, 0, width, height, p_input);
+	readTexPSMCT16(0, max(width / 2 / 128, 1) * 2, 0, 0, width / 2, height / 2, p_output);
+
+	// Free memory
+	delete [] p_gsmem;
+	p_gsmem = NULL;
+	gsmem_size = 0;
+
+	return CanConv4to16(width, height);
+}
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/texturemem.h b/Code/Gfx/NGPS/NX/texturemem.h
new file mode 100644
index 0000000..6138d42
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/texturemem.h
@@ -0,0 +1,109 @@
+#ifndef __TEXTUREMEM_H
+#define __TEXTUREMEM_H
+
+
+namespace NxPs2
+{
+
+class TextureMemoryLayout {
+public:
+
+    ///
+    enum {
+        MAX_PIXEL_SIZE_BITS = 10
+    };
+
+    
+    /// PIXEL_16S won't work with the algorithms here because
+    /// of it's wierd layout.  Hopefully we won't need it.
+    
+    /// Pixel type.  Will add more types later when needed.
+    enum PixelType {
+        PIXEL_INVALID = -1,
+        PIXEL_32 = 0,       // Covers 32 and 24 bit
+        PIXEL_16,
+        PIXEL_8,
+        PIXEL_4,
+        NUM_PIXEL_TYPES
+    };
+    
+    ///
+    struct TexSize {
+        int width;
+        int height;
+    };
+
+    ///
+    struct PixelFormatLayout {
+        TexSize pageSize;
+        TexSize blockSize;
+        
+        bool adjacentBlockWidth;  // if not width, then height
+    };
+
+    ///
+    typedef TexSize MinSizeArray[MAX_PIXEL_SIZE_BITS][MAX_PIXEL_SIZE_BITS];
+    
+    ///
+    TextureMemoryLayout();
+
+    ///
+    void getMinSize(int& width, int& height, int pixMode);
+    ///
+    int getImageSize(int width, int height, int pixMode);
+    ///
+    int getBlockNumber(int blockX, int blockY, int pixMode);
+    ///
+    int getBlockOffset(int bufferWidth, int posX, int posY, int pixMode);
+    ///
+    void getPosFromBlockNumber(int& posX, int& posY, int blockNumber, int pixMode);
+    ///
+    void getPosFromBlockOffset(int& posX, int& posY, int blockOffset, int bufferWidth, int pixMode);
+    ///
+    bool isPageSize(int width, int height, int pixMode);
+    ///
+    bool isBlockSize(int width, int height, int pixMode);
+    ///
+    bool isMinPageSize(int width, int height, int pixMode);
+    ///
+    bool isMinBlockSize(int width, int height, int pixMode);
+
+	// Converts memory layout (not format)
+    int BlockConv4to32(uint8 *p_input, uint8 *p_output);
+	int BlockConv8to32(uint8 *p_input, uint8 *p_output);
+	int PageConv4to32(int width, int height, uint8 *p_input, uint8 *p_output);
+	int PageConv8to32(int width, int height, uint8 *p_input, uint8 *p_output);
+	bool Conv4to32(int width, int height, uint8 *p_input, uint8 *p_output);
+	bool Conv8to32(int width, int height, uint8 *p_input, uint8 *p_output);
+	bool Conv4to16(int width, int height, uint8 *p_input, uint8 *p_output);
+
+	bool CanConv4to32(int width, int height);
+	bool CanConv8to32(int width, int height);
+	bool CanConv4to16(int width, int height);
+
+    ///
+    static int numBits(unsigned int size);
+    ///
+    static PixelType modeToPixelType(int pMode);
+
+private:
+    ///
+    MinSizeArray minSize[NUM_PIXEL_TYPES];
+
+    ///
+    static const PixelFormatLayout pixelFormat[NUM_PIXEL_TYPES];
+    ///
+    static const int blockArrangement[4][8];
+
+    ///
+    void initMinSize(MinSizeArray& mArray, PixelType pType);
+    ///
+    void setMinSize(int& width, int& height, PixelType pType);
+
+};
+
+} // namespace NxPs2
+
+
+#endif // __TEXTUREMEM_H
+
diff --git a/Code/Gfx/NGPS/NX/types.h b/Code/Gfx/NGPS/NX/types.h
new file mode 100644
index 0000000..fab24c4
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/types.h
@@ -0,0 +1,16 @@
+#ifndef __TYPES_H
+#define __TYPES_H
+
+typedef unsigned int 	uint;
+typedef unsigned long	uint64;
+typedef unsigned int	uint32;
+typedef unsigned short	uint16;
+typedef unsigned char	uint8;
+
+typedef signed int 		sint;
+typedef signed long		sint64;
+typedef signed int		sint32;
+typedef signed short	sint16;
+typedef signed char		sint8;
+
+#endif // __TYPES_H
diff --git a/Code/Gfx/NGPS/NX/vif.cpp b/Code/Gfx/NGPS/NX/vif.cpp
new file mode 100644
index 0000000..e0c24be
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vif.cpp
@@ -0,0 +1,95 @@
+#include 
+#include 
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+
+
+namespace NxPs2
+{
+
+
+uint8 *vif::NextCode(uint8 *pVifcode)
+{
+	if ((pVifcode[3] & 0x60) != 0x60)
+	{
+		switch (pVifcode[3] & 0x7F)
+		{
+		case 0x00:	// NOP
+		case 0x02:	// OFFSET
+		case 0x03:	// BASE
+		case 0x04:	// ITOP
+		case 0x05:	// STMOD
+		case 0x06:	// MSKPATH3
+		case 0x07:	// MARK
+		case 0x10:	// FLUSHE
+		case 0x11:	// FLUSH
+		case 0x13:	// FLUSHA
+		case 0x14:	// MSCAL
+		case 0x15:	// MSCALF
+		case 0x17:	// MSCNT
+			return pVifcode + 4;
+
+		case 0x01:	// STCYCL
+			vif::CL = pVifcode[0];
+			vif::WL = pVifcode[1];
+			return pVifcode + 4;
+
+		case 0x20:	// STMASK
+			return pVifcode + 8;
+
+		case 0x30:	// STROW
+		case 0x31:	// STCOL
+			return pVifcode + 20;
+
+		case 0x4A:	// MPG
+			return pVifcode + (pVifcode[2] << 3) + 4;
+
+		case 0x50:	// DIRECT
+		case 0x51:	// DIRECTHL
+			return pVifcode + (((uint16 *)pVifcode)[0] << 4) + 4;
+
+		default:	// undefined vifcode
+			return pVifcode;
+		}
+	}
+	else			// UNPACK
+	{
+		uint vn = (pVifcode[3] >> 2) & 3;
+		uint vl = pVifcode[3] & 3;
+		uint num = pVifcode[2];
+		uint dimension = vn+1;
+		uint bitlength = 32>>vl;
+		if (vif::WL <= vif::CL)
+		{
+			return pVifcode + 4 + (((bitlength * dimension * num + 31) >> 5) << 2);
+		}
+		else
+		{
+			uint rem = num % vif::WL;
+			uint n = vif::CL * (num/vif::WL) + (rem > vif::CL ? vif::CL : rem);
+			return pVifcode + 4 + (((bitlength * dimension * n + 31) >> 5) << 2);
+		}
+	}
+}
+
+
+
+
+//--------------------------------
+//		S T A T I C   D A T A
+//--------------------------------
+
+uint  vif::UnpackSize;
+uint  vif::CycleLength;
+uint  vif::BitLength;
+uint  vif::Dimension;
+uint8 *vif::pVifCode;
+uint  vif::WL;
+uint  vif::CL;
+
+
+} // namespace NxPs2
+
+
+
diff --git a/Code/Gfx/NGPS/NX/vif.h b/Code/Gfx/NGPS/NX/vif.h
new file mode 100644
index 0000000..4b8c252
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vif.h
@@ -0,0 +1,623 @@
+#ifndef __VIF_H
+#define __VIF_H
+
+#include "dma.h"
+#include "vu1.h"
+
+namespace NxPs2
+{
+
+// VIF1_MODE settings
+#define NORMAL_MODE		0
+#define OFFSET_MODE		1
+#define DIFFERENCE_MODE	2
+
+// unpack formats
+#define	S_32	0x0
+#define	S_16	0x1
+#define	S_8		0x2
+#define	V2_32	0x4
+#define	V2_16	0x5
+#define	V2_8	0x6
+#define	V3_32	0x8
+#define	V3_16	0x9
+#define	V3_8	0xA
+#define	V4_32	0xC
+#define	V4_16	0xD
+#define	V4_8	0xE
+#define	V4_5	0xF
+
+// unpack FLG values
+#define	ABS	0
+#define REL 1
+
+// unpack USN values
+#define SIGNED		0
+#define UNSIGNED	1
+
+
+
+class vif
+{
+
+public:
+
+	//------------------------------------------
+	//		S T A T I C   F U N C T I O N S
+	//------------------------------------------
+
+	// vifcodes
+	static void Code(uint8 CMD, uint8 NUM, uint16 IMMEDIATE);
+	static void BASE(uint16 BASE);
+	static void DIRECT(uint16 SIZE);
+	static void DIRECTHL(uint16 SIZE);
+	static void FLUSH(void);
+	static void FLUSHA(void);
+	static void FLUSHE(void);
+	static void ITOP(uint16 ADDR);
+	static void MARK(uint16 MARK);
+	static void MPG(uint8 SIZE, uint16 LOADADDR);
+	static void MSCAL(uint16 EXECADDR);
+	static void MSCALF(uint16 EXECADDR);
+	static void MSCNT(void);
+	static void MSKPATH3(uint16 MASK);
+	static void NOP(void);
+	static void NOPi(void);
+	static void OFFSET(uint16 OFFSET);
+	static void STCOL(uint32 C0, uint32 C1, uint32 C2, uint32 C3);
+	static void STCYCL(uint16 WL, uint16 CL);
+	static void STMASK(uint32 MASK);
+	static void STMOD(uint16 MODE);
+	static void STROW(uint32 R0, uint32 R1, uint32 R2, uint32 R3);
+	static void UNPACK(uint8 m, uint8 vnvl, uint8 SIZE, uint16 FLG, uint16 USN, uint16 ADDR);
+
+	// vifcodes, begin-end style
+	static void BeginDIRECT(void);
+	static void EndDIRECT(void);
+	static void BeginDIRECTHL(void);
+	static void EndDIRECTHL(void);
+	static void BeginMPG(uint16 LOADADDR);
+	static void EndMPG(void);
+	static void BeginUNPACK(uint8 m, uint8 vnvl, uint16 FLG, uint16 USN, uint16 ADDR);
+	static void EndUNPACK(void);
+
+	// storing data for various unpack formats
+	static void StoreS_32(uint32 x);
+	static void StoreS_16(uint16 x);
+	static void StoreS_8(uint8 x);
+	static void StoreV2_32(uint32 x, uint32 y);
+	static void StoreV2_16(uint16 x, uint16 y);
+	static void StoreV2_8(uint8 x, uint8 y);
+	static void StoreV3_32(uint32 x, uint32 y, uint32 z);
+	static void StoreV3_16(uint16 x, uint16 y, uint16 z);
+	static void StoreV3_8(uint8 x, uint8 y, uint8 z);
+	static void StoreV4_32F(float x, float y, float z, float w);
+	static void StoreV4_32(sint32 x, sint32 y, sint32 z, sint32 w);
+	static void StoreV4_16(sint16 x, sint16 y, sint16 z, sint16 w);
+	static void StoreV4_8(sint8 x, sint8 y, sint8 z, sint8 w);
+	static void StoreV4_5(uint8 R, uint8 G, uint8 B, uint8 A);
+
+	// vifcode parsing
+	static uint8 *vif::NextCode(uint8 *pVifcode);
+
+
+	//--------------------------------
+	//		S T A T I C   D A T A
+	//--------------------------------
+
+	static uint  UnpackSize;
+	static uint  CycleLength;
+	static uint  BitLength;
+	static uint  Dimension;
+	static uint8 *pVifCode;
+	static uint  WL;
+	static uint  CL;
+
+
+}; // class vif
+
+
+
+//--------------------------
+//		V I F C O D E S
+//--------------------------
+
+
+
+// generic vifcode
+//
+//	 31   24 23   16 15            0
+//	ÚÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+//	³  CMD  ³  NUM  ³   IMMEDIATE   ³
+//	ÀÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+
+
+inline void vif::Code(uint8 CMD, uint8 NUM, uint16 IMMEDIATE)
+{
+	dma::Store32((uint32)CMD<<24 | (uint32)NUM<<16 | (uint32)IMMEDIATE);
+}
+
+
+
+// Sets VIF1_BASE to lower 10 bits of BASE.
+
+inline void vif::BASE(uint16 BASE)
+{
+	vif::Code(0x03, 0, BASE);
+}
+
+
+
+// Transfers SIZE 128-bit data via PATH2.
+
+inline void vif::DIRECT(uint16 SIZE)
+{
+	vif::Code(0x50, 0, SIZE);
+}
+
+
+
+// Transfers SIZE 128-bit data via PATH2, stalling till PATH3 IMAGE mode transfer is complete.
+
+inline void vif::DIRECTHL(uint16 SIZE)
+{
+	vif::Code(0x51, 0, SIZE);
+}
+
+
+
+// Waits for PATH1/PATH2 transfers to end, after VU1 microprogram has ended.
+
+inline void vif::FLUSH(void)
+{
+	vif::Code(0x11, 0, 0);
+}
+
+
+
+// Waits till no PATH3 transfer request, after the end of VU1 microprogram and PATH1/PATH2 transfer.
+
+inline void vif::FLUSHA(void)
+{
+	vif::Code(0x13, 0, 0);
+}
+
+
+
+// Waits for VU0/VU1 microprogram to end.
+
+inline void vif::FLUSHE(void)
+{
+	vif::Code(0x10, 0, 0);
+}
+
+
+
+// Sets VIFn_ITOPS to lower to bits of ADDR.
+
+inline void vif::ITOP(uint16 ADDR)
+{
+	vif::Code(0x04, 0, ADDR);
+}
+
+
+
+// Sets VIFn_MARK to MARK.
+
+inline void vif::MARK(uint16 MARK)
+{
+	vif::Code(0x07, 0, MARK);
+}
+
+
+
+// Waits for end of microprogram and transfers SIZE 64-bit data to MicroMem address LOADADDR (low 11 bits).
+
+inline void vif::MPG(uint8 SIZE, uint16 LOADADDR)
+{
+	vif::Code(0x4A, SIZE, LOADADDR);
+}
+
+
+
+// Waits for end of microprogram, and activates microprogram at address EXECADDR (low 11 bits).
+
+inline void vif::MSCAL(uint16 EXECADDR)
+{
+	vif::Code(0x14, 0, EXECADDR);
+	vu1::Buffer = vu1::Loc;
+}
+
+
+
+// Waits for end of bot microprogram and PATH1/PATH2 transfers,
+// and activates microprogram at address EXECADDR (low 11 bits).
+
+inline void vif::MSCALF(uint16 EXECADDR)
+{
+	vif::Code(0x15, 0, EXECADDR);
+}
+
+
+
+// Waits for end of microprogram, and activates microprogram at address held in PC.
+
+inline void vif::MSCNT(void)
+{
+	vif::Code(0x17, 0, 0);
+}
+
+
+
+// MASK==1 => disables transfer via PATH3.
+
+inline void vif::MSKPATH3(uint16 MASK)
+{
+	vif::Code(0x06, 0, MASK<<15);
+}
+
+
+
+// Does nothing.
+
+inline void vif::NOP(void)
+{
+	vif::Code(0x00, 0, 0);
+}
+
+
+
+// Interrupt version.
+
+inline void vif::NOPi(void)
+{
+	vif::Code(0x80, 0, 0);
+}
+
+
+
+// Sets VIF1_OFST to lower 10 bits of OFFSET.
+// DBF flag of VIF1_STAT is cleared and VIF1_BASE is copied to VIF1_TOPS.
+
+inline void vif::OFFSET(uint16 OFFSET)
+{
+	vif::Code(0x02, 0, OFFSET);
+}
+
+
+
+// Sets VIFn_C0-VIFn_C3 to C0-C3.
+
+inline void vif::STCOL(uint32 C0, uint32 C1, uint32 C2, uint32 C3)
+{
+	vif::Code(0x31, 0, 0);
+	dma::Store32(C0, C1, C2, C3);
+}
+
+
+
+// Sets VIFn_CYCLE.
+// CL>=WL gives skipping write;
+// CLCL?WL:CL;
+}
+
+
+
+// Sets VIFn_MASK to MASK = (m15,...,m0) with m0 at the low end of the word.
+//
+//					 W	 Z	 Y	 X
+//					---------------
+// write cycle=1	m0	m1	m2	m3
+// write cycle=2	m4	m5	m6	m7
+// write cycle=3	m8	m9	m10	m11
+// write cycle>=4	m15	m14	m13	m12
+//
+// m[n]=0 => decompressed data written as-is;
+// m[n]=1 => row register written;
+// m[n]=2 => column register written;
+// m[n]=3 => write masked.
+
+inline void vif::STMASK(uint32 MASK)
+{
+	vif::Code(0x20, 0, 0);
+	dma::Store32(MASK);
+}
+
+
+
+// Sets VIFn_MODE to lower 2 bits of MODE.
+
+inline void vif::STMOD(uint16 MODE)
+{
+	vif::Code(0x05, 0, MODE);
+}
+
+
+
+// Sets VIFn_R0-VIFn_R3 to R0-R3.
+
+inline void vif::STROW(uint32 R0, uint32 R1, uint32 R2, uint32 R3)
+{
+	vif::Code(0x30, 0, 0);
+	dma::Store32(R0,R1,R2,R3);
+}
+
+
+
+// Decompresses data to VUMem+ADDR.
+// SIZE is the number of decompressed data;
+// vnvl is the format of compressed data (S_8, V4_32, etc);
+// destination in VUMem is ADDR (if FLG==0 on VU1) or VIF1_TOPS+ADDR (if FLG==1 on VU1);
+// USN==0 => sign extension, USN==1 => padding with 0's;
+// m==1 => masking write using VIFn_MASK.
+
+inline void vif::UNPACK(uint8 m, uint8 vnvl, uint8 SIZE, uint16 FLG, uint16 USN, uint16 ADDR)
+{
+	vif::Code(0x60|m<<4|vnvl, SIZE, FLG<<15 | USN<<14 | ((vu1::Loc+ADDR)&0x03FF));
+	UnpackSize = SIZE;
+}
+
+
+
+
+
+//--------------------------------------------------------------
+//		V I F C O D E S   ( B E G I N - E N D   S T Y L E )
+//--------------------------------------------------------------
+
+
+// Auto-size version of DIRECT.
+// Alignment necessary because data must align to 128-bit boundary.
+
+inline void vif::BeginDIRECT(void)
+{
+	dma::Align(12,16);
+	pVifCode = dma::pLoc;
+	vif::Code(0x50, 0, 0);
+}
+
+
+
+// Patch up the IMMEDIATE field of a DIRECT generated using BeginDIRECT().
+// Alignment necessary because vif will interpret till 16-byte boundary as data.
+
+inline void vif::EndDIRECT(void)
+{
+	dma::Align(0,16);
+	((uint16 *)pVifCode)[0] = (dma::pLoc - pVifCode - 4)/16;
+}
+
+
+
+// Auto-size version of DIRECTHL.
+// Alignment necessary because data must align to 16-byte boundary.
+
+inline void vif::BeginDIRECTHL(void)
+{
+	dma::Align(12,16);
+	pVifCode = dma::pLoc;
+	vif::Code(0x51, 0, 0);
+}
+
+
+
+// Patch up the IMMEDIATE field of a DIRECTHL generated using BeginDIRECTHL().
+// Alignment necessary because vif will interpret till 16-byte boundary as data.
+
+inline void vif::EndDIRECTHL(void)
+{
+	dma::Align(0,16);
+	((uint16 *)pVifCode)[0] = (dma::pLoc - pVifCode - 4)/16;
+}
+
+
+
+// Auto-size version of MPG.
+// Alignment necessary because data must align to 8-byte boundary.
+
+inline void vif::BeginMPG(uint16 LOADADDR)
+{
+	dma::Align(4,8);
+	pVifCode = dma::pLoc;
+	vif::Code(0x4A, 0, LOADADDR);
+}
+
+
+
+// Patch up the NUM field of an MPG generated using BeginMPG().
+// Alignment necessary because vif will interpret till 8-byte boundary as data.
+
+inline void vif::EndMPG(void)
+{
+	dma::Align(0,8);
+	pVifCode[2] = (dma::pLoc - pVifCode - 4)/8;
+}
+
+
+
+// Auto-size version of UNPACK.
+
+inline void vif::BeginUNPACK(uint8 m, uint8 vnvl, uint16 FLG, uint16 USN, uint16 ADDR)
+{
+	pVifCode = dma::pLoc;
+	BitLength = 32 >> (vnvl & 3);
+	Dimension = (vnvl>>2 & 3) + 1;
+	vif::Code(0x60 | m<<4 | vnvl, 0, FLG<<15 | USN<<14 | ((vu1::Loc+ADDR)&0x03FF) );
+}
+
+
+
+// Patch up the NUM field of an UNPACK generated using BeginUNPACK().
+// Alignment is necessary because some data formats won't fill out to a 4-byte boundary.
+
+inline void vif::EndUNPACK(void)
+{
+	UnpackSize = ((dma::pLoc - pVifCode - 4) << 3) / (Dimension * BitLength);
+	dma::Align(0,4);
+	if (UnpackSize==0)
+		dma::pLoc -= 4;				// no data, rewind over vifcode
+	else if (UnpackSize < 256)
+		pVifCode[2] = UnpackSize;	// normal usage
+	else if (UnpackSize == 256)
+		pVifCode[2] = 0;				// 0 represents 256
+	else
+	{
+		printf("unpack size greater than 256\n");
+		#ifdef __PLAT_NGPS__
+		exit(1);
+		#endif
+	}
+}
+
+
+
+
+
+
+//-----------------------------------------------------------------
+//		S T O R I N G   C O M P R E S S E D   V I F   D A T A
+//-----------------------------------------------------------------
+
+
+// scalars
+
+inline void vif::StoreS_32(uint32 x)
+{
+	*(uint32 *)dma::pLoc = x;
+	dma::pLoc += 4;
+}
+
+
+inline void vif::StoreS_16(uint16 x)
+{
+	*(uint16 *)dma::pLoc = x;
+	dma::pLoc += 2;
+}
+
+
+inline void vif::StoreS_8(uint8 x)
+{
+	*dma::pLoc++ = x;
+}
+
+
+
+// 2-vectors
+
+inline void vif::StoreV2_32(uint32 x, uint32 y)
+{
+	((uint32 *)dma::pLoc)[0] = x;
+	((uint32 *)dma::pLoc)[1] = y;
+	dma::pLoc += 8;
+}
+
+
+inline void vif::StoreV2_16(uint16 x, uint16 y)
+{
+	((uint16 *)dma::pLoc)[0] = x;
+	((uint16 *)dma::pLoc)[1] = y;
+	dma::pLoc += 4;
+}
+
+
+inline void vif::StoreV2_8(uint8 x, uint8 y)
+{
+	((uint8 *)dma::pLoc)[0] = x;
+	((uint8 *)dma::pLoc)[1] = y;
+	dma::pLoc += 2;
+}
+
+
+
+// 3-vectors
+
+inline void vif::StoreV3_32(uint32 x, uint32 y, uint32 z)
+{
+	((uint32 *)dma::pLoc)[0] = x;
+	((uint32 *)dma::pLoc)[1] = y;
+	((uint32 *)dma::pLoc)[2] = z;
+	dma::pLoc += 12;
+}
+
+
+inline void vif::StoreV3_16(uint16 x, uint16 y, uint16 z)
+{
+	((uint16 *)dma::pLoc)[0] = x;
+	((uint16 *)dma::pLoc)[1] = y;
+	((uint16 *)dma::pLoc)[2] = z;
+	dma::pLoc += 6;
+}
+
+
+inline void vif::StoreV3_8(uint8 x, uint8 y, uint8 z)
+{
+	((uint8 *)dma::pLoc)[0] = x;
+	((uint8 *)dma::pLoc)[1] = y;
+	((uint8 *)dma::pLoc)[2] = z;
+	dma::pLoc[2] = z;
+	dma::pLoc += 3;
+}
+
+
+
+// 4-vectors
+
+inline void vif::StoreV4_32F(float x, float y, float z, float w)
+{
+	((float *)dma::pLoc)[0] = x;
+	((float *)dma::pLoc)[1] = y;
+	((float *)dma::pLoc)[2] = z;
+	((float *)dma::pLoc)[3] = w;
+	dma::pLoc += 16;
+}
+
+
+inline void vif::StoreV4_32(sint32 x, sint32 y, sint32 z, sint32 w)
+{
+	((sint32 *)dma::pLoc)[0] = x;
+	((sint32 *)dma::pLoc)[1] = y;
+	((sint32 *)dma::pLoc)[2] = z;
+	((sint32 *)dma::pLoc)[3] = w;
+	dma::pLoc += 16;
+}
+
+
+inline void vif::StoreV4_16(sint16 x, sint16 y, sint16 z, sint16 w)
+{
+	((sint16 *)dma::pLoc)[0] = x;
+	((sint16 *)dma::pLoc)[1] = y;
+	((sint16 *)dma::pLoc)[2] = z;
+	((sint16 *)dma::pLoc)[3] = w;
+	dma::pLoc += 8;
+}
+
+
+inline void vif::StoreV4_8(sint8 x, sint8 y, sint8 z, sint8 w)
+{
+	((sint8 *)dma::pLoc)[0] = x;
+	((sint8 *)dma::pLoc)[1] = y;
+	((sint8 *)dma::pLoc)[2] = z;
+	((sint8 *)dma::pLoc)[3] = w;
+	dma::pLoc += 4;
+}
+
+
+inline void vif::StoreV4_5(uint8 R, uint8 G, uint8 B, uint8 A)
+{
+	*(uint16 *)dma::pLoc = (A&0x80)<<8 | (B&0xF8)<<7 | (G&0xF8)<<2 | (R&0xF8)>>3;
+	dma::pLoc += 2;
+}
+
+
+
+
+} // namespace NxPs2
+
+
+#endif // __VIF_H
+
diff --git a/Code/Gfx/NGPS/NX/vu0code.dsm b/Code/Gfx/NGPS/NX/vu0code.dsm
new file mode 100644
index 0000000..7898994
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu0code.dsm
@@ -0,0 +1,613 @@
+
+.global MPGStart0
+.global MPGEnd0
+
+.global	TestFunc
+.global	InitialiseOccluders
+.global TestSphereAgainstOccluders
+.global RayTriangleCollision
+.global BatchRayTriangleCollision
+.global ViewCullTest
+.global OuterCullTest
+.global BothCullTests
+
+; align to a 2^4=16 byte boundary, so it can be the target of a dma::ref
+.align	4
+
+MPGStart0:
+
+MPG 0, *
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+
+TestFunc:
+
+		; just a little test func to make sure I understand how to communicate with a vu0 microprogram
+
+		; integer register test
+		NOP								IADD		VI02,VI01,VI01
+		
+		; float register test
+		ADDw		VF02,VF01,VF00w		NOP
+		
+		; memory test
+		NOP								LQ			VF03,0(VI00)
+		ADDw		VF03,VF03,VF00w		NOP
+		
+		; quit
+		NOP[E]							NOP
+		NOP								NOP
+
+
+
+InitialiseOccluders:
+
+		; this function will be optimised later... not a priority now since only called once per frame (per camera)
+
+		; assumes an initial state where the 5 planes of occluder 0 are at addresses 0,1,2,3,4
+		; occluder 1 at 5,6,7,8,9, etc, and the number of occluders is in VI01
+
+		
+		; get address of 1st spare occluder
+		NOP								IADD		VI05,VI01,VI01
+		NOP								IADD		VI05,VI05,VI05
+		NOP								IADD		VI05,VI05,VI01
+		
+		; set up a sentinel plane that's colossally far away
+		NOP								LOI			-1.0e+35
+		MULi		VF01,VF00,I			NOP
+		
+		; add 1 sentinel occluder (5 sentinel planes)
+		NOP								SQI			VF01,(VI05++)
+		NOP								SQI			VF01,(VI05++)
+		NOP								SQI			VF01,(VI05++)
+		NOP								SQI			VF01,(VI05++)
+		NOP								SQI			VF01,(VI05++)
+		
+		; rearrange memory
+		NOP								IADDIU		VI02,VI00,0
+		NOP								MFIR.xyzw	VF31,VI00
+		
+MemLoop:
+		; load 4 occluders, 5 planes each
+
+		NOP								LQ			VF01,0(VI02)
+		NOP								LQ			VF02,5(VI02)
+		NOP								LQ			VF03,10(VI02)
+		NOP								LQ			VF04,15(VI02)
+		NOP								LQ			VF05,1(VI02)
+		NOP								LQ			VF06,6(VI02)
+		NOP								LQ			VF07,11(VI02)
+		NOP								LQ			VF08,16(VI02)
+		NOP								LQ			VF09,2(VI02)
+		NOP								LQ			VF10,7(VI02)
+		NOP								LQ			VF11,12(VI02)
+		NOP								LQ			VF12,17(VI02)
+		NOP								LQ			VF13,3(VI02)
+		NOP								LQ			VF14,8(VI02)
+		NOP								LQ			VF15,13(VI02)
+		NOP								LQ			VF16,18(VI02)
+		NOP								LQ			VF17,4(VI02)
+		NOP								LQ			VF18,9(VI02)
+		NOP								LQ			VF19,14(VI02)
+		NOP								LQ			VF20,19(VI02)
+		
+		
+		; transpose VF01,VF02,VF03,VF04
+		
+		NOP								MOVE		VF21,VF01
+		NOP								MOVE		VF22,VF02
+		NOP								MOVE		VF23,VF03
+		
+		ADDx.y		VF01,VF31,VF02x		NOP
+		ADDx.z		VF01,VF31,VF03x		NOP
+		ADDx.w		VF01,VF31,VF04x		NOP
+		
+		ADDy.x		VF02,VF31,VF21y		NOP
+		ADDy.z		VF02,VF31,VF03y		NOP
+		ADDy.w		VF02,VF31,VF04y		NOP
+		
+		ADDz.x		VF03,VF31,VF21z		NOP
+		ADDz.y		VF03,VF31,VF22z		NOP
+		ADDz.w		VF03,VF31,VF04z		NOP
+		
+		ADDw.x		VF04,VF31,VF21w		NOP
+		ADDw.y		VF04,VF31,VF22w		NOP
+		ADDw.z		VF04,VF31,VF23w		NOP
+		
+		
+		; transpose VF05,VF06,VF07,VF08
+		
+		NOP								MOVE		VF21,VF05
+		NOP								MOVE		VF22,VF06
+		NOP								MOVE		VF23,VF07
+		
+		ADDx.y		VF05,VF31,VF06x		NOP
+		ADDx.z		VF05,VF31,VF07x		NOP
+		ADDx.w		VF05,VF31,VF08x		NOP
+		
+		ADDy.x		VF06,VF31,VF21y		NOP
+		ADDy.z		VF06,VF31,VF07y		NOP
+		ADDy.w		VF06,VF31,VF08y		NOP
+		
+		ADDz.x		VF07,VF31,VF21z		NOP
+		ADDz.y		VF07,VF31,VF22z		NOP
+		ADDz.w		VF07,VF31,VF08z		NOP
+		
+		ADDw.x		VF08,VF31,VF21w		NOP
+		ADDw.y		VF08,VF31,VF22w		NOP
+		ADDw.z		VF08,VF31,VF23w		NOP
+		
+		
+		; transpose VF09,VF10,VF11,VF12
+		
+		NOP								MOVE		VF21,VF09
+		NOP								MOVE		VF22,VF10
+		NOP								MOVE		VF23,VF11
+		
+		ADDx.y		VF09,VF31,VF10x		NOP
+		ADDx.z		VF09,VF31,VF11x		NOP
+		ADDx.w		VF09,VF31,VF12x		NOP
+		
+		ADDy.x		VF10,VF31,VF21y		NOP
+		ADDy.z		VF10,VF31,VF11y		NOP
+		ADDy.w		VF10,VF31,VF12y		NOP
+		
+		ADDz.x		VF11,VF31,VF21z		NOP
+		ADDz.y		VF11,VF31,VF22z		NOP
+		ADDz.w		VF11,VF31,VF12z		NOP
+		
+		ADDw.x		VF12,VF31,VF21w		NOP
+		ADDw.y		VF12,VF31,VF22w		NOP
+		ADDw.z		VF12,VF31,VF23w		NOP
+		
+		
+		; transpose VF13,VF14,VF15,VF16
+		
+		NOP								MOVE		VF21,VF13
+		NOP								MOVE		VF22,VF14
+		NOP								MOVE		VF23,VF15
+		
+		ADDx.y		VF13,VF31,VF14x		NOP
+		ADDx.z		VF13,VF31,VF15x		NOP
+		ADDx.w		VF13,VF31,VF16x		NOP
+		
+		ADDy.x		VF14,VF31,VF21y		NOP
+		ADDy.z		VF14,VF31,VF15y		NOP
+		ADDy.w		VF14,VF31,VF16y		NOP
+		
+		ADDz.x		VF15,VF31,VF21z		NOP
+		ADDz.y		VF15,VF31,VF22z		NOP
+		ADDz.w		VF15,VF31,VF16z		NOP
+		
+		ADDw.x		VF16,VF31,VF21w		NOP
+		ADDw.y		VF16,VF31,VF22w		NOP
+		ADDw.z		VF16,VF31,VF23w		NOP
+		
+		
+		; transpose VF17,VF18,VF19,VF20
+		
+		NOP								MOVE		VF21,VF17
+		NOP								MOVE		VF22,VF18
+		NOP								MOVE		VF23,VF19
+		
+		ADDx.y		VF17,VF31,VF18x		NOP
+		ADDx.z		VF17,VF31,VF19x		NOP
+		ADDx.w		VF17,VF31,VF20x		NOP
+		
+		ADDy.x		VF18,VF31,VF21y		NOP
+		ADDy.z		VF18,VF31,VF19y		NOP
+		ADDy.w		VF18,VF31,VF20y		NOP
+		
+		ADDz.x		VF19,VF31,VF21z		NOP
+		ADDz.y		VF19,VF31,VF22z		NOP
+		ADDz.w		VF19,VF31,VF20z		NOP
+		
+		ADDw.x		VF20,VF31,VF21w		NOP
+		ADDw.y		VF20,VF31,VF22w		NOP
+		ADDw.z		VF20,VF31,VF23w		NOP
+		
+		
+		; store the rearranged occluders
+		
+		NOP								SQI			VF04,(VI02++)
+		NOP								SQI			VF01,(VI02++)
+		NOP								SQI			VF02,(VI02++)
+		NOP								SQI			VF03,(VI02++)
+		NOP								SQI			VF08,(VI02++)
+		NOP								SQI			VF05,(VI02++)
+		NOP								SQI			VF06,(VI02++)
+		NOP								SQI			VF07,(VI02++)
+		NOP								SQI			VF12,(VI02++)
+		NOP								SQI			VF09,(VI02++)
+		NOP								SQI			VF10,(VI02++)
+		NOP								SQI			VF11,(VI02++)
+		NOP								SQI			VF16,(VI02++)
+		NOP								SQI			VF13,(VI02++)
+		NOP								SQI			VF14,(VI02++)
+		NOP								SQI			VF15,(VI02++)
+		NOP								SQI			VF20,(VI02++)
+		NOP								SQI			VF17,(VI02++)
+		NOP								SQI			VF18,(VI02++)
+		NOP								SQI			VF19,(VI02++)
+		
+		
+		; loop if we haven't just processed the sentinel planes
+		
+		NOP								ISUB		VI01,VI02,VI05
+		NOP								NOP
+		NOP								IBLTZ		VI01,MemLoop
+		NOP								NOP
+		
+		; quit
+		NOP[E]							NOP
+		NOP								NOP
+		
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+
+TestSphereAgainstOccluders:		
+
+		; sphere is in VF07 as (x,y,z,R)
+		; for each plane p, we must compute px*x + py*w + pz*z + pw + R
+		; uses a sentinel occluder to terminate
+
+		NOP								IADDIU		VI02,VI00,0			; initialise pointer
+
+		; loop prologue
+		ADD.y		VF05,VF00,VF00		LQI			VF04,(VI02++)
+		NOP								LQI			VF01,(VI02++)
+		NOP								LQI			VF02,(VI02++)
+		NOP								LQI			VF03,(VI02++)
+		ADDAw		ACC,VF04,VF07w		LQI			VF04,(VI02++)
+		MADDAx		ACC,VF01,VF07x		LQI			VF01,(VI02++)
+		MADDAy		ACC,VF02,VF07y		LQI			VF02,(VI02++)
+		MADDz		VF00,VF03,VF07z		IADDIU		VI10,VI00,0xF0		; VI10 = mask for Sxyzw FMAC flags
+		
+		; main loop, tests 4 occluders (20 planes) per pass
+Loop:	ADDw.y		VF05,VF05,VF00w		LQI			VF03,(VI02++)
+		ADDAw		ACC,VF04,VF07w		LQI			VF04,(VI02++)
+		MADDAx		ACC,VF01,VF07x		LQI			VF01,(VI02++)
+		MADDAy		ACC,VF02,VF07y		FMAND		VI01,VI10
+		MADDz		VF00,VF03,VF07z		LQI			VF02,(VI02++)
+		ADDw.y		VF05,VF05,VF00w		LQI			VF03,(VI02++)
+		ADDAw		ACC,VF04,VF07w		LQI			VF04,(VI02++)
+		MADDAx		ACC,VF01,VF07x		LQI			VF01,(VI02++)
+		MADDAy		ACC,VF02,VF07y		FMAND		VI01,VI01
+		MADDz		VF00,VF03,VF07z		LQI			VF02,(VI02++)
+		ADDw.y		VF05,VF05,VF00w		LQI			VF03,(VI02++)
+		ADDAw		ACC,VF04,VF07w		LQI			VF04,(VI02++)
+		MADDAx		ACC,VF01,VF07x		LQI			VF01,(VI02++)
+		MADDAy		ACC,VF02,VF07y		FMAND		VI01,VI01
+		MADDz		VF00,VF03,VF07z		LQI			VF02,(VI02++)
+		ADDw.y		VF05,VF05,VF00w		LQI			VF03,(VI02++)
+		ADDAw		ACC,VF04,VF07w		LQI			VF04,(VI02++)
+		MADDAx		ACC,VF01,VF07x		LQI			VF01,(VI02++)
+		MADDAy		ACC,VF02,VF07y		FMAND		VI01,VI01
+		MADDz		VF00,VF03,VF07z		LQI			VF02,(VI02++)
+		FTOI0.y		VF06,VF05			LQI			VF03,(VI02++)
+		ADDAw		ACC,VF04,VF07w		LQI			VF04,(VI02++)
+		MADDAx		ACC,VF01,VF07x		LQI			VF01,(VI02++)
+		MADDAy		ACC,VF02,VF07y		FMAND		VI01,VI01
+		NOP								IBEQ		VI01,VI00,Loop
+		MADDz		VF00,VF03,VF07z		LQI			VF02,(VI02++)
+		
+		; (macflags & 0x80) => possible occluder in 1st slot of group of 4
+		NOP								ISUBIU		VI10,VI01,0x80
+		NOP								MTIR		VI06,VF06y
+		NOP								IBGEZ		VI10,TestForSentinel
+		NOP								ISUBIU		VI03,VI02,22
+		
+		; (macflags & 0x40) => possible occluder in 2nd slot of group of 4
+		NOP								ISUBIU		VI10,VI01,0x40
+		NOP								IADDIU		VI06,VI06,1
+		NOP								IBGEZ		VI10,TestForSentinel
+		NOP								ISUBIU		VI03,VI02,17
+		
+		; (macflags & 0x20) => possible occluder in 3rd slot of group of 4
+		NOP								ISUBIU		VI10,VI01,0x20
+		NOP								IADDIU		VI06,VI06,1
+		NOP								IBGEZ		VI10,TestForSentinel
+		NOP								ISUBIU		VI03,VI02,12
+		
+		; (macflags & 0x10) => possible occluder in 4th slot of group of 4
+		NOP								IADDIU		VI06,VI06,1
+		NOP								ISUBIU		VI03,VI02,7
+		
+TestForSentinel:
+		; sentinel if VI03==VI05, else occluded
+		NOP[E]							ISUB		VI01,VI03,VI05
+		NOP								ISUBIU		VI06,VI06,4
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+BatchRayTriangleCollision:
+		NOP								ILW.y		VI04, 0(VI00)		; total number of collision tests
+		NOP								ILW.x		VI05, 0(VI00)		; starting index
+
+		NOP                 			IADDIU 		VI01, VI00, 0		; init number of found collisions
+		NOP                 			IADDIU 		VI03, VI00, 1		; init address
+		NOP                 			IADD 		VI06, VI00, VI03	; init output address (writes over input)
+
+		NOP								ISUB		VI04, VI04, VI05	; decrement the start index from the array size
+
+		NOP								LQ			VF06, 2(VI03)		; v0
+		NOP								LQ			VF08, 4(VI03)		; v2
+		NOP								LQ			VF07, 3(VI03)		; v1
+		NOP								LQ			VF04, 0(VI03)		; rayStart
+		NOP								LQ			VF05, 1(VI03)		; rayDir
+   
+NextCollision:
+		SUB.xyz     VF10, VF08, VF06	NOP							   	; edge2 = v2 - v0
+		SUB.xyz     VF09, VF07, VF06	MFIR.w		VF17w, VI05   		; edge1 = v1 - v0  ; store index in VF17w (output)
+		SUB.xyz     VF13, VF04, VF06	FSSET 0							; tvec = rayStart - v0
+		ADDW.x      VF02, VF00, VF00w	LOI 1.00001						; Load 1.0f to VF02x ; load 1 + EPSILON into I
+
+		OPMULA.xyz  ACC, VF05, VF10		NOP								; Cross product of ray normal and edge2 (pvec)
+		OPMSUB.xyz  VF12,VF10, VF05		NOP								; Second part of cross product
+		OPMULA.xyz  ACC, VF13, VF09     IADDIU      VI14, VI00, 0x80    ; qvec = crossProd(tvec, edge1) ; Set the mask X sign flag
+		OPMSUB.xyz  VF14,VF09, VF13     IADDIU      VI13, VI00, 0x10	; Set the mask W sign flag
+		SUBi.x		VF03,VF00, I		NOP								; put -(1 + EPSILON) in VF03x
+
+		MUL.xyz     VF11,VF09, VF12		NOP								; det = edge1 * pvec [start]
+		MUL.xyz     VF15,VF13, VF12		NOP								; u = tvec * pvec [start]
+		MULAx.x     ACC, VF09, VF12x	IADDI		VI04, VI04, -1		; decrement number of collision tests
+		MADDAy.x    ACC, VF02, VF11y	IADDIU 		VI03, VI03, 5		; increment address
+		MADDz.x     VF11,VF02, VF11z	NOP								; det = edge1 * pvec [ready]
+		MULAx.x     ACC, VF02, VF15x	LOI 0.00001						; load 1 + EPSILON into I
+		MADDAy.x    ACC, VF02, VF15y	LQ			VF04, 0(VI03)		; next rayStart
+		MUL.xyz		VF17,VF10, VF14		NOP								; t = edge2 * qvec [start]
+		SUBi.x      VF01,VF11, I  		DIV 	    Q,   VF00w,VF11x	; If det < EPSILON ; Q = 1.0f / det
+
+		MADDz.x     VF15,VF02, VF15z	LQ			VF06, 2(VI03)		; u = tvec * pvec [ready] ; next v0
+		MULAx.x		ACC, VF10, VF14x	LQ			VF08, 4(VI03)		; next v2
+		MADDAy.x	ACC, VF02, VF17		LQ			VF07, 3(VI03)		; next v1
+        MUL.xyz		VF16,VF05, VF14     FMAND 		VI15,VI14 			; v = rayDir * qvec [start] ; check if det result (MAC register)
+        MADDz.x		VF17,VF02, VF17z 	IBNE 		VI15,VI00,BatchRayTriDone	; t = edge2 * qvec [ready]
+		MULAx.x		ACC, VF05, VF14x	LQ			VF05, 1(VI03)		; next rayDir
+		MULQ.x		VF15,VF15, Q        NOP								; u = (tvec * pvec) / det
+		MADDAy.x	ACC, VF02, VF16		NOP
+		MADDz.x		VF16,VF02, VF16z    NOP								; v = rayDir * qvec [ready]
+		NOP								NOP								
+
+		ADDx.x		VF00,VF15, VF03x	FMAND 		VI15,VI14			; if u > 1 + EPSILON ; check if u result (MAC register)  
+		NOP								IBNE		VI15,VI00,BatchRayTriDone
+		MULQ.x		VF16,VF16, Q		NOP								; v = (rayDir * qvec) / det;
+		MULQ.x		VF17,VF17, Q        NOP								; t = (edge2 * qvec) / det
+        NOP								FMAND 		VI15,VI14      		; check if u > 1 + EPSILON result (MAC register)
+        NOP							    IBEQ		VI15,VI00,BatchRayTriDone
+
+        ADDA.x		ACC ,VF15, VF16   	FMAND 		VI15,VI14      		; check if v < 0 result (MAC register)
+        MADDw.x     VF31,VF03, VF00w    IBNE 		VI15,VI00,BatchRayTriDone ; if (u+v) > 1 + EPSILON [start]
+
+		NOP								NOP
+		SUB.x		VF00,VF17, VF00		NOP
+		NOP								NOP
+        SUBx.w		VF00,VF00, VF17 	FMAND 		VI15,VI14     		; if t > 1 [start] ; check if u+v > 1 + EPSILON result (MAC register)
+		NOP								IBEQ 		VI15,VI00,BatchRayTriDone	
+
+		NOP							    FMAND 		VI15,VI14			; check if t < 0 result (MAC register) 	  	
+        NOP                             IBNE 		VI15,VI00,BatchRayTriDone
+
+        NOP								FMAND 		VI15,VI13          	; check if t > 1 result (MAC register)
+        NOP                             IBNE 		VI15,VI00,BatchRayTriDone
+		NOP								NOP
+
+		NOP								SQI.xw		VF17, (VI06++)		; store distance (x) and index (w)
+		NOP                 			IADDIU 		VI01, VI01, 1		; inc number of found collisions
+
+BatchRayTriDone:
+        NOP                             IBNE 		VI04, VI00,NextCollision
+		NOP								IADDIU		VI05, VI05, 1		; increment index for output
+
+		NOP[T]							NOP
+		NOP								NOP
+
+RayTriangleCollision:
+
+		SUB.xyz     VF10, VF08, VF06	NOP								; edge2 = v2 - v0
+		SUB.xyz     VF09, VF07, VF06	FSSET 0							; edge1 = v1 - v0 
+		SUB.xyz     VF13, VF04, VF06	NOP								; tvec = rayStart - v0
+		ADDW.x      VF02, VF00, VF00w	LOI 1.00001						; Load 1.0f to VF02x ; load 1 + EPSILON into I
+
+		OPMULA.xyz  ACC, VF05, VF10		NOP								; Cross product of ray normal and edge2 (pvec)
+		OPMSUB.xyz  VF12,VF10, VF05		NOP								; Second part of cross product
+		OPMULA.xyz  ACC, VF13, VF09     IADDIU      VI14, VI00, 0x80    ; qvec = crossProd(tvec, edge1) ; Set the mask X sign flag
+		OPMSUB.xyz  VF14,VF09, VF13     IADDIU      VI13, VI00, 0x10	; Set the mask W sign flag
+		SUBi.x		VF03,VF00, I		IADDIU 		VI02, VI00, 0		; put -(1 + EPSILON) in VF03x ; store 0 for return value
+
+		MUL.xyz     VF11,VF09, VF12		NOP								; det = edge1 * pvec [start]
+		MUL.xyz     VF15,VF13, VF12		NOP								; u = tvec * pvec [start]
+		MULAx.x     ACC, VF09, VF12x	NOP
+		MADDAy.x    ACC, VF02, VF11y	NOP
+		MADDz.x     VF11,VF02, VF11z	NOP								; det = edge1 * pvec [ready]
+		MULAx.x     ACC, VF02, VF15x	LOI 0.00001						; load 1 + EPSILON into I
+		MADDAy.x    ACC, VF02, VF15y	NOP
+		MUL.xyz		VF17,VF10, VF14		NOP								; t = edge2 * qvec [start]
+		SUBi.x      VF01,VF11, I  		DIV 	    Q,   VF00w,VF11x	; If det < EPSILON ; Q = 1.0f / det
+
+		MADDz.x     VF15,VF02, VF15z	NOP								; u = tvec * pvec [ready]
+		MULAx.x		ACC, VF10, VF14x	NOP
+		MADDAy.x	ACC, VF02, VF17		NOP
+        MUL.xyz		VF16,VF05, VF14     FMAND 		VI15,VI14 			; v = rayDir * qvec [start] ; check if det result (MAC register)
+        MADDz.x		VF17,VF02, VF17z 	IBNE 		VI15,VI00,vu0RayTriDone	; t = edge2 * qvec [ready]
+		MULAx.x		ACC, VF05, VF14x	NOP
+		MULQ.x		VF15,VF15, Q        NOP								; u = (tvec * pvec) / det
+		MADDAy.x	ACC, VF02, VF16		NOP
+		MADDz.x		VF16,VF02, VF16z    NOP								; v = rayDir * qvec [ready]
+		NOP								NOP								
+
+		ADDx.x		VF00,VF15, VF03x	FMAND 		VI15,VI14			; if u > 1 + EPSILON ; check if u result (MAC register)  
+		NOP								IBNE		VI15,VI00,vu0RayTriDone
+		MULQ.x		VF16,VF16, Q		NOP								; v = (rayDir * qvec) / det;
+		MULQ.x		VF17,VF17, Q        NOP								; t = (edge2 * qvec) / det
+        NOP								FMAND 		VI15,VI14      		; check if u > 1 + EPSILON result (MAC register)
+        NOP							    IBEQ		VI15,VI00,vu0RayTriDone
+
+        ADDA.x		ACC ,VF15, VF16   	FMAND 		VI15,VI14      		; check if v < 0 result (MAC register)
+        MADDw.x     VF31,VF03, VF00w    IBNE 		VI15,VI00,vu0RayTriDone ; if (u+v) > 1 + EPSILON [start]
+
+		NOP								NOP
+		SUB.x		VF00,VF17, VF00		NOP
+		NOP								NOP
+        SUBx.w		VF00,VF00, VF17 	FMAND 		VI15,VI14     		; if t > 1 [start] ; check if u+v > 1 + EPSILON result (MAC register)
+		NOP								IBEQ 		VI15,VI00,vu0RayTriDone	
+
+		NOP							    FMAND 		VI15,VI14			; check if t < 0 result (MAC register) 	  	
+        NOP                             IBNE 		VI15,VI00,vu0RayTriDone
+
+        NOP								FMAND 		VI15,VI13          	; check if t > 1 result (MAC register)
+        NOP                             IBNE 		VI15,VI00,vu0RayTriDone
+		NOP								NOP
+
+	;qmfc2	$9, $vf17			 # move t to $9
+	;sw		$9, 0(%1)			 # and write out
+		NOP								IADDIU 		VI02, VI00, 1		; store 1 for return value
+
+vu0RayTriDone:
+
+		NOP[E]							NOP
+		NOP								NOP
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; new bounding volume / frustum intersection tests
+
+
+		; VF08 = (x0,y0,z0,r)
+		; VF09 = (bx,by,bz,?)
+		
+		; VF10 = (nx)
+		; VF11 = (ny)
+		; VF12 = (nz)
+		; VF13 = (|nx|)
+		; VF14 = (|ny|)
+		; VF15 = (|nz|)
+		; VF16 = (nw)
+		
+		; VF17 = (nx)
+		; VF18 = (ny)
+		; VF19 = (nz)
+		; VF20 = (|nx|)
+		; VF21 = (|ny|)
+		; VF22 = (|nz|)
+		; VF23 = (nw)
+		
+		; VF24 = (nx)
+		; VF25 = (ny)
+		; VF26 = (nz)
+		; VF27 = (|nx|)
+		; VF28 = (|ny|)
+		; VF29 = (|nz|)
+		; VF30 = (nw)
+		
+		; VF31 = (1)
+
+		
+ViewCullTest:
+		
+		ADDAx		ACC,VF16,VF00x		NOP
+		MADDAx		ACC,VF10,VF08x		NOP
+		MADDAy		ACC,VF11,VF08y		NOP
+		MADDAz		ACC,VF12,VF08z		NOP
+		MADDw		VF00,VF31,VF08w		NOP
+		MADDAx		ACC,VF13,VF09x		NOP
+		MADDAy		ACC,VF14,VF09y		NOP
+		MADDz		VF00,VF15,VF09z		NOP
+		
+		ADDAx.xy	ACC,VF23,VF00x		FMOR		VI01,VI00
+		MADDAx.xy	ACC,VF17,VF08x		NOP
+		MADDAy.xy	ACC,VF18,VF08y		NOP
+		MADDAz.xy	ACC,VF19,VF08z		FMOR		VI01,VI01
+		MADDw.xy	VF00,VF31,VF08w		NOP
+		MADDAx.xy	ACC,VF20,VF09x		NOP
+		MADDAy.xy	ACC,VF21,VF09y		NOP
+		MADDz.xy	VF00,VF22,VF09z		NOP
+		
+		NOP								FMOR		VI01,VI01
+		NOP								NOP
+		NOP								NOP
+		NOP[E]							FMOR		VI01,VI01
+		NOP								NOP
+		
+
+OuterCullTest:
+
+		ADDAx.zw	ACC,VF23,VF00x		NOP
+		MADDAx.zw	ACC,VF17,VF08x		NOP
+		MADDAy.zw	ACC,VF18,VF08y		NOP
+		MADDAz.zw	ACC,VF19,VF08z		NOP
+		MSUBw.zw	VF00,VF31,VF08w		NOP
+		MSUBAx.zw	ACC,VF20,VF09x		NOP
+		MSUBAy.zw	ACC,VF21,VF09y		NOP
+		MSUBz.zw	VF00,VF22,VF09z		NOP
+		
+		ADDAx		ACC,VF30,VF00x		FMOR		VI01,VI00
+		MADDAx		ACC,VF24,VF08x		NOP
+		MADDAy		ACC,VF25,VF08y		NOP
+		MADDAz		ACC,VF26,VF08z		FMOR		VI01,VI01
+		MSUBw		VF00,VF31,VF08w		NOP
+		MSUBAx		ACC,VF27,VF09x		NOP
+		MSUBAy		ACC,VF28,VF09y		NOP
+		MSUBz		VF00,VF29,VF09z		NOP
+		
+		NOP								FMOR		VI01,VI01
+		NOP								NOP
+		NOP								NOP
+		NOP[E]							FMOR		VI01,VI01
+		NOP								NOP
+
+
+BothCullTests:
+		
+		MULAx.zw	ACC,VF20,VF09x		NOP
+		MADDAy.zw	ACC,VF21,VF09y		NOP
+		MADDz.zw	VF01,VF22,VF09z		NOP
+		
+		ADDAx		ACC,VF16,VF00x		NOP
+		MADDAx		ACC,VF10,VF08x		NOP
+		MADDAy		ACC,VF11,VF08y		NOP
+		MADDAz		ACC,VF12,VF08z		NOP
+		MADDw		VF00,VF31,VF08w		NOP
+		MADDAx		ACC,VF13,VF09x		NOP
+		MADDAy		ACC,VF14,VF09y		NOP
+		MADDz		VF00,VF15,VF09z		NOP
+		
+		ADDAx.zw	ACC,VF23,VF00x		FMOR		VI01,VI00
+		MADDAx.zw	ACC,VF17,VF08x		NOP
+		MADDAy.zw	ACC,VF18,VF08y		NOP
+		MADDAz.zw	ACC,VF19,VF08z		FMOR		VI01,VI01
+		MADDx.z		VF05,VF00,VF00x		NOP
+		MADDw.zw	VF00,VF31,VF08w		NOP
+		MADDw.zw	VF00,VF01,VF00w		NOP
+		MSUBw.zw	VF00,VF31,VF08w		NOP
+		MSUBw.zw	VF00,VF01,VF00w		NOP
+		
+		ADDAx		ACC,VF30,VF00x		FMOR		VI01,VI01
+		MADDAx		ACC,VF24,VF08x		FMOR		VI01,VI01
+		MADDAy		ACC,VF25,VF08y		FMOR		VI11,VI00
+		MADDAz		ACC,VF26,VF08z		FMOR		VI11,VI11
+		MSUBw		VF00,VF31,VF08w		NOP
+		MSUBAx		ACC,VF27,VF09x		NOP
+		MSUBAy		ACC,VF28,VF09y		NOP
+		MSUBz		VF00,VF29,VF09z		NOP
+		
+		SUBz.x		VF05,VF00,VF05z		FMOR		VI11,VI11
+		NOP								NOP
+		NOP								NOP
+		NOP								FMOR		VI11,VI11
+		
+		NOP[E]							NOP
+		NOP								NOP
+
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+
+.EndMPG
+
+MPGEnd0:
diff --git a/Code/Gfx/NGPS/NX/vu0code.h b/Code/Gfx/NGPS/NX/vu0code.h
new file mode 100644
index 0000000..16ce036
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu0code.h
@@ -0,0 +1,17 @@
+#ifndef __VU0CODE_H
+#define __VU0CODE_H
+
+extern uint MPGStart0[8]; 		// Mick:  The [8] is a patch to make the compiler not assume it is in ldata
+extern uint MPGEnd0[8];
+
+extern uint TestFunc;
+extern uint InitialiseOccluders;
+extern uint TestSphereAgainstOccluders;
+extern uint RayTriangleCollision;
+extern uint BatchRayTriangleCollision;
+extern uint ViewCullTest;
+extern uint OuterCullTest;
+extern uint BothCullTests;
+
+#endif //__VU0CODE_H
+
diff --git a/Code/Gfx/NGPS/NX/vu1.cpp b/Code/Gfx/NGPS/NX/vu1.cpp
new file mode 100644
index 0000000..f97bf93
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1.cpp
@@ -0,0 +1,136 @@
+#include 
+#include 
+#include "mikemath.h"
+#include "dma.h"
+#include "vif.h"
+#include "vu1.h"
+#include "gif.h"
+#include "gs.h"
+#include "vu1code.h"
+
+namespace NxPs2
+{
+
+
+//---------------------------------------------------
+//		V U   P R I M   C O N S T R U C T I O N
+//---------------------------------------------------
+
+
+// begin
+
+#if 0
+// original version
+void vu1::BeginPrim(uint FLG, uint Addr)
+{
+	vif::STCYCL(1,1);
+	vif::BeginUNPACK(0, V4_32, FLG, SIGNED, 0);
+	gif::BeginTag1(gs::NOP, 1, PACKED, 0, 0, Addr);
+}
+#else
+// a quicker version
+void vu1::BeginPrim(uint FLG, uint Addr)
+{
+	register uint32 *p_dest = (uint32 *)dma::pLoc;
+	*p_dest++ = 0x01000101;
+	vif::pVifCode = (uint8 *)p_dest;
+	*p_dest++ = 0x6C000000 | FLG<<15 | vu1::Loc&0x03FF;
+	gif::pTag = (uint8 *)p_dest;
+	*p_dest++ = 0x34000000;
+	*p_dest++ = 0x10000000 | Addr;
+	*p_dest   = 0x0000000F;
+	dma::pLoc = (uint8 *)(p_dest+2);
+}
+#endif
+
+// end
+
+#if 0
+// original version
+void vu1::EndPrim(uint Eop)
+{
+	vif::EndUNPACK();
+	vif::UnpackSize--;			// a fudge to ignore the giftag which was inside the unpack
+	gif::EndTag1(Eop);
+}
+#else
+// a quicker version
+void vu1::EndPrim(uint Eop)
+{
+	register uint unpack_size = (dma::pLoc - vif::pVifCode - 4) >> 4;
+	Dbg_MsgAssert(unpack_size>0, ("Error: unpack size 0 not supported for vu prims\n"));
+	Dbg_MsgAssert(unpack_size<=256, ("Error: unpack size greater than 256\n"));
+	vif::pVifCode[2] = unpack_size;
+	((uint32 *)gif::pTag)[0] |= Eop<<15 | (unpack_size-1);
+	((uint32 *)gif::pTag)[3] = unpack_size - 1;
+	vu1::Loc += unpack_size;
+}
+#endif
+
+
+// output vector
+
+void vu1::StoreVec(Vec v)
+{
+	vif::StoreV4_32F(v[0], v[1], v[2], v[3]);
+}
+
+
+// output matrix
+
+void vu1::StoreMat(Mat m)
+{
+	#if 1
+	// we basically need to copy 16 32 bit words
+	register uint32 * source = (uint32*)&m[0][0];
+	register uint32 * dest = (uint32*)dma::pLoc;
+	// 16 copies
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	*dest++ = *source++;
+	
+	dma::pLoc = (uint8*) dest;
+	
+	#else
+	vif::StoreV4_32F(m[0][0], m[0][1], m[0][2], m[0][3]);
+	vif::StoreV4_32F(m[1][0], m[1][1], m[1][2], m[1][3]);
+	vif::StoreV4_32F(m[2][0], m[2][1], m[2][2], m[2][3]);
+	vif::StoreV4_32F(m[3][0], m[3][1], m[3][2], m[3][3]);
+	#endif
+}
+
+
+// routine has a breakpoint for debugging
+
+void vu1::BreakpointPrim(uint FLG, uint Eop)
+{
+	vif::UNPACK(0, V4_32, 1, FLG, SIGNED, 0);
+	gif::Tag1(0, 0, 0, 0, 0, Eop, 0, VU1_ADDR(Breakpoint));
+	Loc += 1;
+}
+
+
+//---------------------------------
+//		S T A T I C   D A T A
+//---------------------------------
+
+uint vu1::Loc;
+uint vu1::Buffer;
+uint vu1::MaxBuffer;
+
+
+} // namespace NxPs2
+
diff --git a/Code/Gfx/NGPS/NX/vu1.h b/Code/Gfx/NGPS/NX/vu1.h
new file mode 100644
index 0000000..a5ed99b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1.h
@@ -0,0 +1,171 @@
+#ifndef __VU_H
+#define __VU_H
+
+#include	
+#include	
+
+// simplify VU1 call addresses
+#ifdef __PLAT_NGPS__
+#define VU1_ADDR(Label) ((uint)&(Label)/8)
+#endif
+#ifdef __PLAT_WN32__
+#define VU1_ADDR(Label) (Label)
+#endif
+
+namespace NxPs2
+{
+
+class vu1
+{
+
+public:
+
+	//-------------------------------------------
+	//		S T A T I C   F U N C T I O N S
+	//-------------------------------------------
+
+	static void BeginPrim(uint FLG, uint Addr);
+	static void EndPrim(uint Eop);
+	static void BreakpointPrim(uint FLG, uint Eop);
+	static void StoreVec(float v[4]);
+	static void CopyQuads(uint32 *p0);
+	static void CopyQuads(uint32 *p0,uint32 *p1);
+	static void CopyQuads(uint32 *p0,uint32 *p1,uint32 *p2);
+	static void CopyQuads(uint32 *p0,uint32 *p1,uint32 *p2, uint32 *p3);
+	static void StoreVecAligned(float v[4]);
+	static void CopyMat(float *p_mat);
+	static void CopyMatAligned(float *p_mat);
+	static void StoreMat(float m[4][4]);
+	static void StoreMatAligned(float m[4][4]);
+
+	//---------------------------------
+	//		S T A T I C   D A T A
+	//---------------------------------
+
+	static uint Loc;
+	static uint Buffer;
+	static uint MaxBuffer;
+
+}; // class vu1
+
+
+#ifdef __PLAT_NGPS__
+
+inline void asm_copy_vec(void* m0, void* m1)
+{
+	asm __volatile__("
+	lq    $6,0x0(%1)
+	sq    $6,0x0(%0)
+	": : "r" (m0) , "r" (m1):"$6");
+}
+
+inline void vu1::StoreVecAligned(float v[4])
+{
+	asm_copy_vec(dma::pLoc,v);
+	dma::pLoc += 16;
+}
+
+inline void vu1::StoreMatAligned(float m[4][4])
+{
+	*(Mth::Matrix*)dma::pLoc = *(Mth::Matrix*)(&m[0][0]);
+	dma::pLoc += 64;
+}
+
+inline void vu1::CopyMatAligned(float *p_mat)
+{
+	*(Mth::Matrix*)dma::pLoc = *((Mth::Matrix*)(p_mat));
+	dma::pLoc += 64;
+}
+
+inline void vu1::CopyMat(float *p_mat)
+{
+	uint32 *p = (uint32*)dma::pLoc;
+	uint32 *p2 = (uint32*)p_mat;
+	for (int i=0;i<16;i++)
+		*p++ = *p2++;
+	dma::pLoc = (uint8*)p;
+}
+
+
+
+inline void vu1::CopyQuads(uint32 *p0)
+{
+	uint32	* p = (uint32*)dma::pLoc;
+	p[0] = p0[0];
+	p[1] = p0[1];
+	p[2] = p0[2];
+	p[3] = p0[3];
+	dma::pLoc = (uint8*)(p+4);
+}
+
+inline void vu1::CopyQuads(uint32 *p0, uint32 *p1)
+{
+	uint32	* p = (uint32*)dma::pLoc;
+	p[0] = p0[0];
+	p[1] = p0[1];
+	p[2] = p0[2];
+	p[3] = p0[3];
+	p[4] = p1[0];
+	p[5] = p1[1];
+	p[6] = p1[2];
+	p[7] = p1[3];
+	dma::pLoc = (uint8*)(p+8);
+}
+
+inline void vu1::CopyQuads(uint32 *p0, uint32 *p1, uint32*p2)
+{
+	uint32	* p = (uint32*)dma::pLoc;
+	p[0] = p0[0];
+	p[1] = p0[1];
+	p[2] = p0[2];
+	p[3] = p0[3];
+	p[4] = p1[0];
+	p[5] = p1[1];
+	p[6] = p1[2];
+	p[7] = p1[3];
+	p[8] = p2[0];
+	p[9] = p2[1];
+	p[10] = p2[2];
+	p[11] = p2[3];
+	dma::pLoc = (uint8*)(p+12);
+}
+
+inline void vu1::CopyQuads(uint32 *p0, uint32 *p1, uint32 *p2, uint32 *p3)
+{
+	uint32	* p = (uint32*)dma::pLoc;
+	p[0] = p0[0];
+	p[1] = p0[1];
+	p[2] = p0[2];
+	p[3] = p0[3];
+	p[4] = p1[0];
+	p[5] = p1[1];
+	p[6] = p1[2];
+	p[7] = p1[3];
+	p[8] = p2[0];
+	p[9] = p2[1];
+	p[10] = p2[2];
+	p[11] = p2[3];
+	p[12] = p3[0];
+	p[13] = p3[1];
+	p[14] = p3[2];
+	p[15] = p3[3];
+	dma::pLoc = (uint8*)(p+16);
+}
+
+	
+//	static void StoreQuads(uint32 *p0,uint32 *p1);
+//	static void StoreQuads(uint32 *p0,uint32 *p1,uint32 *p2);
+//	static void StoreQuads(uint32 *p0,uint32 *p1,uint32 *p2, uint32 *p0);
+
+
+
+#endif
+
+
+
+} // namespace NxPs2
+
+
+
+#endif // __VU_H
+
diff --git a/Code/Gfx/NGPS/NX/vu1/defs.vsm b/Code/Gfx/NGPS/NX/vu1/defs.vsm
new file mode 100644
index 0000000..a394fde
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1/defs.vsm
@@ -0,0 +1,5 @@
+
+		.equr	zero,	VF00x
+		.equr	one,	VF00w
+
+
diff --git a/Code/Gfx/NGPS/NX/vu1/jumptab.vsm b/Code/Gfx/NGPS/NX/vu1/jumptab.vsm
new file mode 100644
index 0000000..54a6547
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1/jumptab.vsm
@@ -0,0 +1,23 @@
+
+JumpTable:
+; load at micromem address 0
+
+		; initially the branch addresses will be static
+		; but they can later be written dynamically by a vu code caching system
+		NOP								B			Init
+		NOP								NOP
+		NOP								B			ProcessPacket
+		NOP								NOP
+		NOP								B			VU1Context
+		NOP								NOP
+		NOP								B			GSContext
+		NOP								NOP
+		NOP								B			SpritesInit
+		NOP								NOP
+		NOP								B			Sprites
+		NOP								NOP
+		;NOP								B			ScreenAlignedBillboards
+		;NOP								NOP
+		;NOP								B			AxialBillboards
+		;NOP								NOP
+
diff --git a/Code/Gfx/NGPS/NX/vu1/main.vsm b/Code/Gfx/NGPS/NX/vu1/main.vsm
new file mode 100644
index 0000000..daa999b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1/main.vsm
@@ -0,0 +1,156 @@
+
+;	ÉÍÍÍÍÍÍÍÍÍÍÍÍ»
+;	º tag format º
+;	ÈÍÍÍÍÍÍÍÍÍÍÍͼ
+;
+;	This follows GIFtag format (EE User's Manual section 7.2.2), but with some added fields.
+;
+;
+;
+;	 31 30                   23 22                16 15 14                                         0
+;	ÚÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³0 ³     STEP exponent     ³ STEP mantissa      ³EOP                   NLOOP                    ³
+;	ÀÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;	 63       60 59 58 57                            47 46 45    43 42                            32
+;	ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³   NREG    ³ FLG ³              PRIM              ³PRE        ³              ADDR              ³
+;	ÀÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;	 95                                                       76 75       72 71       68 67       64
+;	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³                                                     ...   ³  [REG2]   ³  [REG1]   ³   REG0    ³
+;	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;	 127                                         112 111                                          96
+;	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³                                               ³                      SIZE                     ³
+;	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;
+
+
+Init:	NOP[E]							IADDIU		VI02,VI00,0			; initialise VI02
+		NOP								NOP
+
+ProcessPacket:
+		NOP								LQI			VF01,(VI02++)		; get 1st tag
+		NOP								ISUBIU		VI13,VI02,1			; VI13 = start address of current packet
+		NOP								IADDIU		VI15,VI00,0			; set EOP=0
+		NOP								XITOP		VI14				; get run-time render flags from VIF1_ITOP
+
+MainLoop:
+		NOP								IBLTZ		VI15,KickPacket		; kick if EOP==1
+		ADDw		VF02,VF01,VF00w		MTIR		VI01,VF01y			; VI01 = ADDR, jumptable address. 'ADDw' is for extracting STEP
+		NOP								MTIR		VI15,VF01x			; VI15 = EOP:NLOOP
+		NOP								MTIR		VI06,VF01w			; VI06 = SIZE = NREG*NLOOP, size of prim (excl. tag)
+		NOP								IADD		VI05,VI02,VI06		; VI05 = end pointer for prim
+		NOP								JR			VI01				; jump via jumptable
+		NOP								MTIR		VI04,VF02x			; VI04 = STEP (branch delay slot)
+
+
+KickPacket:
+		; kick and stop
+		NOP[E]							XGKICK		VI13				; kick the processed packet to the GS
+		NOP								ISUBIU		VI02,VI02,1			; undo last postincrement (so VI02 points to next packet)
+
+
+NextPrim:
+		NOP								LQI			VF01,(VI02++)		; get next tag
+		NOP								B			MainLoop			; loop
+		NOP								NOP
+
+
+
+
+; SIZE field must be NREG*NLOOP - 1
+; STEP mantissa must generate the value n*3-27 where VFn is the first reg in the context
+
+
+VU1Context:
+
+		NOP								IADDIU		VI01,VI04,VU1Context; eventually optimise this away and just do...
+		NOP								JR			VI01				; JR VI04
+		SUBA.w		ACC,VF00,VF00		LOI			0x44800000			; 2^10
+
+
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF10,(VI02++)
+		NOP								LOI			0x45000000			; 2^11
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF11,(VI02++)
+		NOP								LOI			0x45800000			; 2^12
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF12,(VI02++)
+		NOP								LOI			0x46000000			; 2^13
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF13,(VI02++)
+		NOP								LOI			0x46800000			; 2^14
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF14,(VI02++)
+		NOP								LOI			0x47000000			; 2^15
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF15,(VI02++)
+		NOP								LOI			0x47800000			; 2^16
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF16,(VI02++)
+		NOP								LOI			0x48000000			; 2^17
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF17,(VI02++)
+		NOP								LOI			0x48800000			; 2^18
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF18,(VI02++)
+		NOP								LOI			0x49000000			; 2^19
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF19,(VI02++)
+		NOP								LOI			0x49800000			; 2^20
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF20,(VI02++)
+		NOP								LOI			0x4A000000			; 2^21
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF21,(VI02++)
+		NOP								LOI			0x4A800000			; 2^22
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF22,(VI02++)
+		NOP								LOI			0x4B000000			; 2^23
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF23,(VI02++)
+		NOP								LOI			0x4B800000			; 2^24
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF24,(VI02++)
+		NOP								LOI			0x4C000000			; 2^25
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF25,(VI02++)
+		NOP								LOI			0x4C800000			; 2^26
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF26,(VI02++)
+		NOP								LOI			0x4D000000			; 2^27
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF27,(VI02++)
+		NOP								LOI			0x4D800000			; 2^28
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF28,(VI02++)
+		NOP								LOI			0x4E000000			; 2^29
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF29,(VI02++)
+		NOP								LOI			0x4E800000			; 2^30
+		NOP								IBEQ		VI02,VI05,NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF30,(VI02++)
+		NOP								LOI			0x4F000000			; 2^31
+		NOP								B			NextPrim
+		MADDAi.w	ACC,VF00,I			LQI			VF31,(VI02++)
+		
+
+
+
+
+
+GSContext:
+		NOP								LQ			VF01,0(VI05)
+		NOP								B			MainLoop
+		NOP								IADDIU		VI02,VI05,1
+
diff --git a/Code/Gfx/NGPS/NX/vu1/newvu1code.dsm b/Code/Gfx/NGPS/NX/vu1/newvu1code.dsm
new file mode 100644
index 0000000..ffa8540
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1/newvu1code.dsm
@@ -0,0 +1,27 @@
+
+.global NewMPGStart
+.global NewMPGEnd
+
+.include "defs.vsm"
+
+; align to a 2^7=128 byte boundary for faster dma transfer
+.align	7
+
+NewMPGStart:
+
+.DmaData ParticleVU1CodeStart
+
+	MPG 0, *
+	
+		.include "jumptab.vsm"
+		.include "main.vsm"
+		.include "particle.vsm"
+		;.include "billboard.vsm"
+
+	.EndMPG
+	
+.EndDmaData
+NewMPGEnd:
+
+; force rebuild
+
diff --git a/Code/Gfx/NGPS/NX/vu1/newvu1code.h b/Code/Gfx/NGPS/NX/vu1/newvu1code.h
new file mode 100644
index 0000000..d2ce43b
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1/newvu1code.h
@@ -0,0 +1,12 @@
+#ifndef __NEWVU1CODE_H
+#define __NEWVU1CODE_H
+
+#ifdef __PLAT_NGPS__
+
+extern uint NewMPGStart       __attribute__((section(".vudata")));
+extern uint NewMPGEnd         __attribute__((section(".vudata")));
+
+#endif
+
+#endif //__NEWVU1CODE_H
+
diff --git a/Code/Gfx/NGPS/NX/vu1/particle.vsm b/Code/Gfx/NGPS/NX/vu1/particle.vsm
new file mode 100644
index 0000000..3084a1f
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1/particle.vsm
@@ -0,0 +1,299 @@
+
+; -----------------
+; VU1 PARTICLE CODE
+; -----------------
+
+
+; to do:
+; - lift restriction on number of particles per system (ie allow one system to span multiple VU1 'buffers')
+; - cyclic paths?
+
+
+; notes:
+; the arrangement of VU1 memory accesses should be done in consideration of the low amount of VIF1 activity per system
+
+
+; VU1's (crap) random number generator uses this algorithm:
+;
+; R = 0x3F800000		|
+;     R<<1 & 0x007FFFFF	|
+;     (R>>4 ^ R>>22) & 1
+;
+; This is augmented by a few xor's and a history of 4 seeds (which we sort of need anyway, to do random vectors),
+; to give fairly decent random numbers. If VF13 holds the 4-vector of seeds, the following code steps to the next
+; set of 4 values:
+;
+;		RNEXT.x		VF13,R
+;		RXOR		R,VF13y
+;		RGET.y		VF13,R
+;		RXOR		R,VF13z
+;		RGET.z		VF13,R
+;		RXOR		R,VF13w
+;		RGET.w		VF13,R
+;
+; But this code is also efficiently emulated on the cpu, which is crucial.
+
+
+
+GS_BUF0 = 512
+GS_BUF1 = 768
+OUT_MAXNUM = 42
+
+
+
+
+; output sprite format is (STQ,RGBA,XYZ2,UV,XYZ2)
+
+
+		; per stream
+		.equr	t,		VF09			; (t,dt,num_particles,?,)
+		.equr	seed,	VF10			; (Rx,Ry,Rz,Rw)
+		
+		; per system
+		.equr	uv1,	VF11			; (u1,v1,?,?)
+		.equr	uv0,	VF12			; (u0,v0,?,?)
+		.equr	p0,		VF13			; t^0 position coefficient
+		.equr	p1,		VF14			; t^1 position coefficient
+		.equr	p2,		VF15			; t^2 position coefficient
+		.equr	s0,		VF17			; t^0 spread coefficient
+		.equr	s1,		VF18			; t^1 spread coefficient
+		.equr	s2,		VF19			; t^2 spread coefficient
+		.equr	c0,		VF21			; t^0 colour coefficient
+		.equr	c1,		VF22			; t^1 colour coefficient
+		.equr	c2,		VF23			; t^1 colour coefficient
+		.equr	tag,	VF24			; GIFtag for particle GS packets
+
+		; constant
+		.equr	kvec,	VF25			; scale vec (kx,ky,0,0)
+		.equr	NTLvec,	VF26			; near-top-left cull testing vec
+		.equr	BRvec,	VF27			; far-bottom-right cull testing vec
+		.equr	mat0,	VF28			; view matrix row 0
+		.equr	mat1,	VF29			; view matrix row 1
+		.equr	mat2,	VF30			; view matrix row 2
+		.equr	mat3,	VF31			; view matrix row 3
+
+		; integer regs
+		.equr	Input,	VI02
+		.equr	Output,	VI03
+		.equr	Flip,	VI12
+		.equr	Packet,	VI13
+		
+
+
+		.scope
+
+
+SpritesInit:
+
+		; get input buffer address
+		NOP								XTOP		Input
+		
+		; load constant data
+		NOP								LQI			mat0,  (Input++)	; view matrix row 0
+		NOP								LQI			mat1,  (Input++)	; view matrix row 1
+		NOP								LQI			mat2,  (Input++)	; view matrix row 2
+		NOP								LQI			mat3,  (Input++)	; view matrix row 3
+		NOP								LQI			kvec,  (Input++)	; scale vec (kx,ky,0,0)
+		NOP								LQI			NTLvec,(Input++)	; near-top-left cull testing vec
+		NOP								LQI			BRvec,(Input++)		; bottom-right cull testing vec
+
+		NOP[E]							IADDIU		Packet,VI00,GS_BUF0
+		NOP								IADDIU		Flip,Packet,GS_BUF1
+
+
+
+Sprites:
+
+		NOP								NOP
+		NOP								NOP
+
+		; get input buffer address
+		NOP								XTOP		Input
+		
+		; kick GS context and step over it
+		NOP								ILW.x		VI01,0(Input)
+		NOP								XGKICK		Input
+		NOP								ISUBIU		Input,Input,0x7FFF
+		NOP								IADD		Input,Input,VI01
+
+		; load the context data for the system
+		NOP								LQI			uv0,(Input++)		; (u0,v0,u1,v1)
+		NOP								LQI			p0, (Input++)		; t^0 position coefficient
+		NOP								LQI			p1, (Input++)		; t^1 position coefficient
+		NOP								LQI			p2, (Input++)		; t^2 position coefficient
+		NOP								LQI			s0, (Input++)		; t^0 spread coefficient
+		NOP								LQI			s1, (Input++)		; t^1 spread coefficient
+		NOP								LQI			s2, (Input++)		; t^2 spread coefficient
+		NOP								LQI			c0, (Input++)		; 
+		NOP								LQI			c1, (Input++)		; 
+		NOP								LQI			c2, (Input++)		; 
+		NOP								LQI			tag,(Input++)		; GIFtag for particle GS packets
+
+
+		; a little bit of reformatting
+		NOP								MR32		uv1,uv0
+		NOP								MR32		uv1,uv1
+
+		; copy tag to packet buffers
+		NOP								SQ			tag,GS_BUF0(VI00)
+		NOP								SQ			tag,GS_BUF1(VI00)
+
+@StreamLoop:
+
+		; load stream data
+		NOP								LQI			t,(Input++)			; t_oldest, dt, num_particles, terminator
+		NOP								LQI			seed,(Input++)
+
+		; seed initialisation
+		NOP								RINIT		R,seed.w
+
+		; To start with, just pack each buffer full until we run out. This will usually end with a half-full buffer.
+		; Later we can work out the ideal packet size to even out the cost.
+		
+		; get num particles
+		NOP								MTIR		VI06,t.z
+
+@PacketLoop:
+
+		; set output pointer
+		NOP								IADDIU		Output,Packet,1
+		
+		; see if they'll all fit
+		NOP								ISUBIU		VI01,VI06,OUT_MAXNUM
+		NOP								NOP
+		NOP								IBGTZ		VI01,@WontFit
+		NOP								NOP
+
+@WillFit:
+		NOP								B			@Reduce
+		NOP								IADDIU		VI05,VI06,0
+
+@WontFit:
+		NOP								IADDIU		VI05,VI00,OUT_MAXNUM
+
+@Reduce:
+		NOP								ISUB		VI06,VI06,VI05
+		NOP								IBEQ		VI05,VI00,@NextStream
+		NOP								IADDIU		VI01,VI05,0x4000
+		NOP								IADDIU		VI01,VI01,0x4000
+		NOP								ISW.x		VI01,0(Packet)
+
+@ParticleLoop:
+
+		; generate new seed vector
+		NOP								RNEXT.x		seed,R
+		NOP								RXOR		R,seed.y
+		NOP								RGET.y		seed,R
+		NOP								RXOR		R,seed.z
+		NOP								RGET.z		seed,R
+		NOP								RXOR		R,seed.w
+		NOP								RGET.w		seed,R
+
+		; compute p0+s0*seed + t(p1+s1*seed) + t^2(p2+s2*seed) + t^3(p3+s3*seed)
+		; = (((p3+s3*seed)t + p2+s2*seed)t + p1+s1*seed)t + p0+s0*seed
+
+		ADDAx		ACC,p2,zero			NOP
+		MADD		VF02,s2,seed		NOP
+		NOP								NOP
+		ADDAx		ACC,p1,zero			NOP
+		MADDA		ACC,s1,seed			NOP
+		MADDx		VF02,VF02,t.x		NOP
+		NOP								NOP
+		ADDAx		ACC,p0,zero			NOP
+		MADDA		ACC,s0,seed			NOP
+		MADDx		VF02,VF02,t.x		NOP
+		
+		; VF02 = (x,y,z,r)
+		
+		; transform and add corner offsets
+		ADDAx		ACC,mat3,zero		NOP
+		MADDAx		ACC,mat0,VF02x		NOP
+		MADDAy		ACC,mat1,VF02y		NOP
+		MADDAz		ACC,mat2,VF02z		NOP
+		MSUBw		VF01,kvec,VF02w		NOP
+		MADDw.xyz	VF02,kvec,VF02w		NOP
+		
+		; VF01 = (x'-kx*r, y'-ky*r, z', w)
+		; VF02 = (x'+kx*r, y'+ky*r, z', r)
+		
+		; calc 1/w
+		NOP								DIV			Q,one,VF01w
+		NOP								WAITQ
+		
+		; prepare for cull tests, move r to NTL vec
+		NOP								MOVE.w		NTLvec,VF02
+		
+		; generate screen coords of corners
+		MULq.xyz	VF01,VF01,Q			NOP
+		MULq		VF02,VF02,Q			NOP
+
+		; VF01 = (X0,Y0,Z0,w)
+		; VF02 = (X1,Y1,Z1,r/w) - r/w is only used for mipmapping
+
+		; get (GS) Q
+		NOP								MR32.z		VF03,VF02
+
+		; compute colour
+		SUB.x		VF06,t,tag			NOP			; subtract mid time
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								FMOR		VI01,VI00
+		NOP								IBEQ		VI01,VI00,tPos
+		NOP								NOP
+		
+tNeg:	ADDAx		ACC,c1,zero			NOP
+		MADDx		VF06,c0,VF06x		NOP
+		NOP								B			tDone
+		NOP								NOP
+		
+tPos:	ADDAx		ACC,c1,zero			NOP
+		MADDx		VF06,c2,VF06x		NOP
+
+tDone:	FTOI0		VF06,VF06			NOP
+
+		; decrement t (remember we started with the oldest particle!)
+		SUBy.x		t,t,t.y				NOP
+		
+		; cull tests
+		SUB.xyw		VF00,VF01,NTLvec	NOP
+		SUB.xy		VF00,BRvec,VF02		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								FMOR		VI01,VI00
+		NOP								FMOR		VI01,VI01
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		VF02,VI01
+		
+		; store stuff
+		NOP								SQI.z		VF03,(Output++)
+		NOP								SQI			VF06,(Output++)
+		NOP								SQI.xy		uv0, (Output++)
+		NOP								SQI.xyz		VF01,(Output++)
+		NOP								SQI			uv1, (Output++)
+		NOP								SQI			VF02,(Output++)
+		
+		; dec counter and loop inner
+		NOP								ISUBIU		VI05,VI05,1
+		NOP								NOP
+		NOP								IBNE		VI05,VI00,@ParticleLoop
+		NOP								NOP
+
+		; kick and flip
+		NOP								XGKICK		Packet
+		NOP								ISUB		Packet,Flip,Packet
+
+		; loop outer
+		NOP								IBGTZ		VI06,@PacketLoop
+		NOP								NOP
+		
+@NextStream:
+		NOP								MTIR		VI01,t.w
+		NOP								NOP
+		NOP								IBGEZ		VI01,@StreamLoop
+		NOP								NOP
+
+@Done:	NOP[E]							NOP
+		NOP								NOP
+
+		.endscope
diff --git a/Code/Gfx/NGPS/NX/vu1code.dsm b/Code/Gfx/NGPS/NX/vu1code.dsm
new file mode 100644
index 0000000..256a7d4
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1code.dsm
@@ -0,0 +1,5809 @@
+;
+; set tab stops to 4 to read this file
+;
+;-----------------------------------------------------------------------------------------------------------------------------
+; The result of assembling this file is a set of vif packets with vifcode MPG, used to upload
+; microcode to MicroMem1, the 16K program memory of VU1. There is only one MPG assembler
+; directive, but this is clever enough to split the assembled code into multiple packets when
+; the maximum MPG packet size (256 64-bit instruction slots, 2K of program) is exceeded.
+; There will be the need to manage multiple sets of microcode, once there's more than 16K
+; total.
+;
+;-----------------------------------------------------------------------------------------------------------------------------
+;
+; Very brief overview of rendering scheme
+; ---------------------------------------
+;
+; I decided where possible to take a 'process-in-place' approach to basic triangle rendering.
+; e.g. a pair of texture coordinates loaded to address 100 in VU memory will end up being
+; output as a pair of texture coordinates from the same address, on its way to the GS. This
+; is efficient because it reduces the amount of data copying, as sometimes the VU doesn't
+; need to touch the data - e.g. vertex colours can sometimes pass straight through without
+; further processing.
+;
+; This means the data should be at least triple-buffered for maximum efficiency, since there
+; are 3 processes that can run in parallel - sending data into memory via VIF1, processing
+; the data using VU1, and sending data out through the GIF to the GS. For maximum flexibility,
+; I have chosen to use VUMem1 (the VU1 data memory) as a cyclic buffer, letting the data
+; packets themselves ensure that the triple-buffering rule is not violated. (This is an
+; original approach - generally people use fixed buffers.)
+;
+; The result of processing renderable data in VU1 will be a GS packet (see EE User's Manual,
+; section 7.2, "Data Format"). Each GS packet is composed of GS primitives which begin with
+; a GIFtag. Following through to the max on the 'process-in-place' philosophy, I consider the
+; incoming geometry data to also consist of packets made up of primitives, each with a tag.
+; The tag is a superset of the GIFtag, appearing to the GIF as a valid GIFtag but making use
+; of the many unused bits of the GIFtag to signal additional info to VU1, eg the size of the
+; packet, the address of the microcode that should be used to process the packet, etc.
+; (This is also a pretty nifty idea, although using the spare bits is common practice.)
+;
+; There are 3 varieties of primitive - a VU prim, containing contextual VU data that will be
+; loaded into floating point VU1 registers (which doesn't need kicking to the GS), a GS prim,
+; containing contextual GS data that won't be touched by the VU but will be kicked directly
+; to the GS where it will set some of the GS registers, and finally a geometry prim which
+; contains renderable geometry and will be processed by the rendering code into a GS prim
+; that outputs something on the screen. But all varieties of prim use a common tag format so
+; they can be freely mixed in the data and parsed by a common piece of code.
+;
+;-----------------------------------------------------------------------------------------------------------------------------
+; Registers used in the main parsing loop and in the renderers (all except for the clipping code)
+;
+; Integer registers:
+; VI00 - the constant zero register, always zero (doesn't change if you write something else to it)
+; VI01 - mainly temporary values, also hardwired as the result of the FCAND, FCEQ, FCOR instructions
+; VI02 - the data pointer as the prims are parsed
+; VI03 - an auxilliary data pointer, so that data can be read from one vertex while writing to another
+; VI04 - number of quadwords in each vertex of the current prim
+; VI05 - end address for the current prim
+; VI06 - size of the current prim
+; VI07 - \
+; VI08 - \\
+; VI09 - -- temporaries
+; VI10 - //
+; VI11 - /
+; VI12 - address of current tag
+; VI13 - address of packet start
+; VI14 - render flags
+; VI15 - EOP:NLOOP from current tag
+
+; VF00 - the constant register (0,0,0,1) (doesn't change if you write something else to it)
+; VF01 - temporary value
+; VF02 - temporary value
+; VF03 - temporary value
+; VF04 - temporary value
+; VF05 - temporary value
+; VF06 - temporary value
+; VF07 - temporary value
+; VF08 - temporary value
+; VF09 - (Near, Far, k/(xRes/2), k/(yRes/2)) where k=viewport_scale_x, should be 2048 but is 1900 because of clipper problem
+; VF10 - inverse viewport scale vector
+; VF11 - inverse viewport offset vector
+; VF12 - row 0, local to viewport transform
+; VF13 - row 1, local to viewport transform
+; VF14 - row 2, local to viewport transform
+; VF15 - row 3, local to viewport transform
+; VF26 - lightsource 2 colour (r,g,b,?)
+; VF17 - row 0, reflection map transform
+; VF18 - row 1, reflection map transform
+; VF19 - row 2, reflection map transform
+; VF20 - light vectors, x components
+; VF21 - light vectors, y components
+; VF22 - light vectors, z components
+; VF23 - ambient colour (r,g,b,?)
+; VF24 - lightsource 0 colour (r,g,b,?)
+; VF25 - lightsource 1 colour (r,g,b,?)
+; VF16 - texture projection scale vector
+; VF27 - texture projection offset vector
+; VF28 - saves the z-components of the view matrix during a z-push
+; VF29 - \
+; VF30 - - temporaries used in skinning code
+; VF31 - /
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+CULL=0x01	; per-triangle view culling
+CLIP=0x02	; full 3D clipping of triangles
+SHDW=0x04	; skinned=>cast shadow into texture; non-skinned=>render mesh with projected shadow texture on it
+COLR=0x08	; apply colour at vertices
+FOGE=0x10	; calculate per-vertex fog coefficient
+WIRE=0x20	; render skinned as wireframe (but doesn't render all edges)
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+; Make the very start and end of the file available to the linker, so the dma packet that
+; sends the code (using a dma ref tag) can be constructed.
+.global MPGStart
+.global MPGEnd
+
+; here's a list of all the entry points into the microcode so they're available to the engine
+.global	Setup
+.global	Jump
+.global Breakpoint
+.global	ParseInit
+.global	Parser
+.global	L_VF09
+.global	L_VF10
+.global	L_VF11
+.global	L_VF12
+.global	L_VF13
+.global	L_VF14
+.global	L_VF15
+.global	L_VF16
+.global	L_VF17
+.global	L_VF18
+.global	L_VF19
+.global	L_VF20
+.global	L_VF21
+.global	L_VF22
+.global	L_VF23
+.global	L_VF24
+.global	L_VF25
+.global	L_VF26
+.global	L_VF27
+.global	L_VF28
+.global	L_VF29
+.global	L_VF30
+.global	L_VF31
+.global	GSPrim
+.global Sprites
+.global SpriteCull
+.global ReformatXforms
+.global ShadowVolumeSkin
+
+; These entry points are currently not used, because the entry points are being generated in
+; the scene converter which doesn't have the linker information available for the microcode.
+; Instead there is a temporary jump table at the top of program memory, branching to each
+; routine via fixed known locations.
+.global Proj
+.global PTex
+.global Refl
+.global Line
+.global Skin
+
+; align to a 2^4=16 byte boundary, so it can be the target of a dma::ref
+.align	4
+
+; label so the engine knows where to start dma'ing the microcode from
+MPGStart:
+
+; The MPG directive (ended by .EndMPG at the bottom of the file) is the assembler mechanism
+; for constructing a vif packet using the MPG vifcode, which tells the vif to upload the
+; subsequent data as vu microcode. But it's cleverer than that, because (a) it will split
+; the data into multiple MPG vif packets if the maximum size for MPG is exceeded (2K), and
+; (b) all labels between the MPG and the .EndMPG will be reduced so that not only are they
+; relative to the start of the MPG block, but also act as if any extra MPG vifcodes inserted
+; into the data didn't really exist... as if the assembler output really contained just the
+; microcode and not the extra vifcodes, just like it will be when it reaches MicroMem1 (the
+; program memory of VU1).
+
+MPG 0, *
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; Jump table. (This can later be eliminated with a mechanism for supplying vu1 label
+; addresses to the scene converter.)
+
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								B			Breakpoint
+		NOP								NOP
+		NOP								B			ParseInit
+		NOP								NOP
+		NOP								B			Parser
+		NOP								NOP
+		NOP								B			L_VF09
+		NOP								NOP
+		NOP								B			L_VF10
+		NOP								NOP
+		NOP								B			L_VF11
+		NOP								NOP
+		NOP								B			L_VF12
+		NOP								NOP
+		NOP								B			L_VF13
+		NOP								NOP
+		NOP								B			L_VF14
+		NOP								NOP
+		NOP								B			L_VF15
+		NOP								NOP
+		NOP								B			L_VF16
+		NOP								NOP
+		NOP								B			L_VF17
+		NOP								NOP
+		NOP								B			L_VF18
+		NOP								NOP
+		NOP								B			L_VF19
+		NOP								NOP
+		NOP								B			L_VF20
+		NOP								NOP
+		NOP								B			L_VF21
+		NOP								NOP
+		NOP								B			L_VF22
+		NOP								NOP
+		NOP								B			L_VF23
+		NOP								NOP
+		NOP								B			L_VF24
+		NOP								NOP
+		NOP								B			L_VF25
+		NOP								NOP
+		NOP								B			L_VF26
+		NOP								NOP
+		NOP								B			L_VF27
+		NOP								NOP
+		NOP								B			L_VF28
+		NOP								NOP
+		NOP								B			L_VF29
+		NOP								NOP
+		NOP								B			L_VF30
+		NOP								NOP
+		NOP								B			L_VF31
+		NOP								NOP
+		NOP								B			GSPrim
+		NOP								NOP
+Proj:	NOP								B			Proj1
+		NOP								NOP
+PTex:	NOP								B			PTex1
+		NOP								NOP
+Refl:	NOP								B			Refl1
+		NOP								NOP
+Line:	NOP								B			Line1
+		NOP								NOP
+Skin:	NOP								B			Skin1
+		NOP								NOP
+Light:	NOP								B			Light1
+		NOP								IADDIU		VI10,VI00,0
+LightT:	NOP								B			Light1
+		NOP								IADDIU		VI10,VI00,1
+WibbleT:NOP								B			WibbleT1
+		NOP								NOP
+LWibT:	NOP								B			LWibT1
+		NOP								IADDIU		VI10,VI00,1
+AddZPush:NOP							B			ZPush
+		NOP								LOI			16
+SubZPush:NOP							B			RestoreZPush
+		NOP								NOP
+Setup:	NOP[E]							XTOP		VI02				; initialise input pointer and halt
+		NOP								NOP
+Jump:	NOP								B			JumpToIt
+		NOP								NOP
+SCAB:	NOP								B			ScreenAlignedBillboards
+		NOP								NOP
+LAB:	NOP								B			LongAxisBillboards
+		NOP								NOP
+SHAB:	NOP								B			ShortAxisBillboards
+		NOP								NOP
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+JumpToIt:	; set new value for data pointer
+
+		NOP								MTIR		VI02,VF01z
+		NOP								XTOP		VI01
+		NOP								IADD		VI02,VI02,VI01
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+Breakpoint:	; for debugging purposes
+
+		NOP[D]							B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+;-----------------------------------------------------------------------------------------------------------------------------
+;
+;
+;
+;	ÉÍÍÍÍÍÍÍÍÍÍÍÍ»
+;	º tag format º
+;	ÈÍÍÍÍÍÍÍÍÍÍÍͼ
+;
+;	This follows GIFtag format (EE User's Manual section 7.2.2), but with some added fields.
+;
+;
+;
+;	 31 30                   23 22                16 15 14                                         0
+;	ÚÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³0 ³     NREG exponent     ³ NREG mantissa      ³EOP                   NLOOP                    ³
+;	ÀÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;	 63       60 59 58 57                            47 46 45    43 42                            32
+;	ÚÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³   NREG    ³ FLG ³              PRIM              ³PRE  FLAGS ³              ADDR              ³
+;	ÀÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;	 95                                                       76 75       72 71       68 67       64
+;	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³                                                     ...   ³  [REG2]   ³  [REG1]   ³   REG0    ³
+;	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;	 127                                         112 111                                          96
+;	ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
+;	³                                               ³                      SIZE                     ³
+;	ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
+;
+;
+;
+; Added fields:
+;
+; bits 22-16:	the mantissa of ((float)NREG*powf(2,-23))
+; bits 23-30:	the exponent of ((float)NREG*powf(2,-23))
+; bit  31:		zero, the a sign bit for treating the x-component as a float
+;
+; bits 32-42:	ADDR, the vu1 code address for processing the current primitive
+; bits 43-45:	FLAGS, 43=unused, 44=u-clamp, 45=v-clamp
+;
+; bits 96-111:	SIZE=NREG*NLOOP
+;
+; bits 112-127:	unused
+;
+;-----------------------------------------------------------------------------------------------------------------------------
+; This is the main loop that parses the incoming packet data. Each iteration of PrimLoop will do this:
+; - set VI04 to NREG, the vertex step size
+; - set VI05 to the end address for the current prim
+; - set VI06 to NREG*NLOOP, the size of the current prim
+; - set VI15 to hold EOP in its sign bit
+; - jump the the address of the renderer that will process the prim
+; The two entry points are Parser, which maintains the previous value of the data pointer VI02,
+; and ParseInit, which first initialises VI02 to the value in VIF1_TOP.
+
+
+ParseInit:
+		NOP								XTOP		VI02				; initialise VI02
+
+Parser: NOP								LQI			VF01,(VI02++)		; get 1st tag
+		NOP								ISUBIU		VI13,VI02,1			; VI13 = start address of current packet
+		NOP								XITOP		VI14				; get run-time render flags from VIF1_ITOP
+
+PrimLoop:
+		ADDw.x		VF02,VF01,VF00w		MTIR		VI15,VF01x			; VI15=EOP:NLOOP, 'ADDw' is for extracting NREG
+		FTOI0.y		VF02,VF01			MTIR		VI01,VF01y			; VI01 = ADDR, renderer address
+		NOP								MTIR		VI06,VF01w			; VI06 = SIZE = NREG*NLOOP, size of prim (excl. tag)
+		NOP								IADD		VI05,VI02,VI06		; VI05 = end pointer for prim
+		NOP								JR			VI01				; jump to renderer
+		NOP								MTIR		VI04,VF02x			; VI04 = NREG (branch delay slot)
+
+NextPrim:
+		NOP								IBGEZ		VI15,PrimLoop		; loop if EOP==0
+		NOP								NOP
+
+KickPacket:
+
+		; kick and stop
+		NOP[E]							XGKICK		VI13				; kick the processed packet to the GS
+		NOP								ISUBIU		VI02,VI02,1			; undo last postincrement (VI02 points to next packet)
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; Process a VU1 prim
+; ------------------
+; by loading the designated floating point registers.
+; It loads registers consecutively, starting from any register (VF09 or higher) and ending on
+; any of VF11, VF15, VF19, VF23, VF27 or VF31. The decision to load in batches of 4 was because
+; one often wants to load matrices as contextual data, and also to save having to put a test
+; (plus the necessary delay slots) after each individual register load... it just means having
+; to sometimes pad out the VU1 context to a 4-register boundary.
+
+L_VF09:	NOP								LQI			VF09,(VI02++)		; entry point for loading VF09, etc
+L_VF10:	NOP								LQI			VF10,(VI02++)
+L_VF11:	NOP								LQI			VF11,(VI02++)
+
+		MULw.w		VF24,VF11,VF00w		DIV			Q,VF00w,VF10w
+		SUB.w		VF11,VF00,VF00		IBEQ		VI02,VI05,VUPrimEnd
+		MULq.w		VF23,VF00,Q			WAITQ							; VF23w = f0
+		
+L_VF12:	NOP								LQI			VF12,(VI02++)
+L_VF13:	NOP								LQI			VF13,(VI02++)
+L_VF14:	NOP								LQI			VF14,(VI02++)
+L_VF15:	NOP								LQI			VF15,(VI02++)
+
+		NOP								IADDIU		VI01,VI02,1
+		NOP								IBEQ		VI02,VI05,VUPrimEnd
+		NOP								NOP
+
+		NOP								IBEQ		VI01,VI05,VUPrimEnd
+L_VF16:	NOP								LQI			VF16,(VI02++)
+L_VF17:	NOP								LQI			VF17,(VI02++)
+L_VF18:	NOP								LQI			VF18,(VI02++)
+L_VF19:	NOP								LQI			VF19,(VI02++)
+
+		NOP								NOP
+		NOP								IBEQ		VI02,VI05,VUPrimEnd
+		NOP								NOP
+
+L_VF20:	NOP								LQI			VF20,(VI02++)
+L_VF21:	NOP								LQI			VF21,(VI02++)
+L_VF22:	NOP								LQI			VF22,(VI02++)
+L_VF23:	NOP								LQI.xyz		VF23,(VI02++)
+
+		NOP								NOP
+		NOP								IBEQ		VI02,VI05,VUPrimEnd
+		NOP								NOP
+
+L_VF24:	NOP								LQI.xyz		VF24,(VI02++)
+L_VF25:	NOP								LQI			VF25,(VI02++)
+L_VF26:	NOP								LQI			VF26,(VI02++)
+L_VF27:	NOP								LQI			VF27,(VI02++)
+
+		NOP								NOP
+		NOP								IBEQ		VI02,VI05,VUPrimEnd
+		NOP								NOP
+
+L_VF28:	NOP								LQI			VF28,(VI02++)
+L_VF29:	NOP								LQI			VF29,(VI02++)
+L_VF30:	NOP								LQI			VF30,(VI02++)
+L_VF31:	NOP								LQI			VF31,(VI02++)
+
+VUPrimEnd:
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; process a GS prim
+; -----------------
+; by simply stepping the data pointer over it.
+
+GSPrim:	NOP								LQ			VF01,0(VI05)		; prefetch next tag
+		NOP								ISUBIU		VI12,VI02,1			; save the current tag address (see clipping code)
+		NOP								B			NextPrim			; go back for next prim
+		NOP								IADDIU		VI02,VI05,1			; step data pointer past next tag
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+CullPrim:
+		NOP								ISUBIU		VI01,VI00,1
+		NOP								MFIR.z		VF01,VI01
+		NOP								SQ.z		VF01,-1(VI02)
+		NOP								IADD		VI02,VI02,VI06
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; zpush
+; -----
+; m_localToViewport[0][2] += m_localToViewport[0][3] * zPush * I / FogAlpha;
+; m_localToViewport[1][2] += m_localToViewport[1][3] * zPush * I / FogAlpha;
+; m_localToViewport[2][2] += m_localToViewport[2][3] * zPush * I / FogAlpha;
+; m_localToViewport[3][2] += m_localToViewport[3][3] * zPush * I / FogAlpha;
+; on entry, zPush is held in VF01w (from the tag)
+
+		; new version, frees up VF12-15
+		; still needs optimising
+
+ZPush:
+		MULi.w		VF02,VF10,I			NOP
+
+		SUB.w		VF28,VF00,VF00		NOP
+		ADDz.x		VF28,VF00,VF12z		NOP
+		ADDz.y		VF28,VF00,VF13z		NOP
+		ADDz.z		VF28,VF00,VF14z		NOP
+		ADDz.w		VF28,VF28,VF15z		NOP
+		
+		MULw.w		VF04,VF12,VF02w		MR32.z		VF02,VF01
+		MULw.w		VF05,VF13,VF02w		NOP
+		MULw.w		VF06,VF14,VF02w		NOP
+		MULw.w		VF07,VF15,VF02w		NOP
+		ADDA.z		ACC,VF00,VF12		NOP
+		MADDw.z		VF12,VF02,VF04		NOP
+		ADDA.z		ACC,VF00,VF13		NOP
+		MADDw.z		VF13,VF02,VF05		NOP
+		ADDA.z		ACC,VF00,VF14		LQI			VF01,(VI02++)
+		MADDw.z		VF14,VF02,VF06		NOP
+		ADDA.z		ACC,VF00,VF15		B			NextPrim
+		MADDw.z		VF15,VF02,VF07		NOP
+
+RestoreZPush:
+
+		ADDx.z		VF12,VF00,VF28x		NOP
+		ADDy.z		VF13,VF00,VF28y		NOP
+		ADDz.z		VF14,VF00,VF28z		NOP
+		ADDw.z		VF15,VF00,VF28w		NOP
+
+		NOP								LQI			VF01,(VI02++)
+		NOP								B			NextPrim
+		NOP								NOP
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+; vertex projection
+; -----------------
+; just transforms and projects the vertex coords, used for non-textured meshes
+
+Proj1:
+		; first some hackery added in as a bit of an afterthought, to support single-sided and colouring of meshes
+
+		NOP								IADDIU		VI11,VI00,0x0800
+		NOP								IAND		VI01,VI01,VI11
+		NOP								ISUBIU		VI10,VI00,FOGE+1
+		NOP								IAND		VI14,VI14,VI10
+		NOP								IBNE		VI01,VI00,SingleSided
+		NOP								IADDIU		VI01,VI00,Label00
+
+Label00:NOP								IADDIU		VI01,VI00,COLR
+		NOP								IAND		VI01,VI14,VI01
+		NOP								ISUB		VI14,VI14,VI01
+		NOP								IBNE		VI01,VI00,ApplyColour
+		NOP								IADDIU		VI01,VI00,Label0
+		
+		; now branch to the appropriate rendering code
+
+Label0:	NOP								IBEQ		VI14,VI00,Proj2
+		NOP								IADDIU		VI01,VI00,CULL
+		
+		NOP								IADDIU		VI11,VI00,CLIP
+		NOP								IBEQ		VI14,VI01,Cull
+		NOP								IADDIU		VI01,VI00,Cull
+		
+		NOP								IBEQ		VI14,VI11,Clip
+		NOP								ISW.w		VI01,-1(VI02)
+		
+		NOP								B			CullPrim
+		NOP								NOP
+		
+	
+
+Proj2:
+
+		.if 1
+		
+		; fog version
+		
+		; f = min(f0+k/w,1) = f0+(1/w)(1-f0)min(w0,w)
+		; where k=w0(1-f0)
+		
+		NOP								LOI			0x45000FFF
+		MULi.w		VF25,VF00,I			NOP								; VF25w = 2^11 + 1 - 2^-12
+		SUBw.w		VF25,VF25,VF23w		NOP								; VF25w = 2^11 + 1-f0 - 2^-12
+		NOP								LOI			8
+		ADDi.w		VF26,VF25,I			NOP								; VF26w = VF25w + ADC
+		
+
+
+
+		.if 0
+
+		NOP								IADDIU		VI03,VI02,0
+		
+LoopP:
+		NOP								IADD		VI03,VI03,VI04
+		NOP								LQ.xyzw		VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ITOF4.xyz	VF02,VF01			MTIR		VI07,VF01w
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								DIV			Q,VF23w,VF03w
+		NOP								NOP
+		NOP								NOP
+		MINI.w		VF03,VF03,VF24w		NOP
+		NOP								IBLTZ		VI07,CullP
+		NOP								NOP
+		MULAw		ACC,VF00,VF25w		NOP
+		MADDq.xyzw	VF04,VF03,Q			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		FTOI4.xyz	VF04,VF04			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								SQ.xyzw		VF04,-1(VI02)
+		NOP								IBNE		VI02,VI05,LoopP
+		NOP								NOP
+
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+CullP:	MULAw		ACC,VF00,VF26w		NOP
+		MADDq.xyzw	VF04,VF03,Q			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		FTOI4.xyz	VF04,VF04			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								SQ.xyzw		VF04,-1(VI02)
+		NOP								IBNE		VI02,VI05,LoopP
+		NOP								NOP
+
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		
+		.else
+		
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ.xyzw		VF01,-1(VI03)
+		ITOF4.xyz	VF02,VF01			MTIR		VI07,VF01w
+		MADDAx		ACC,VF12,VF02x		IADD		VI03,VI03,VI04
+		MADDAy		ACC,VF13,VF02y		LQ.xyzw		VF01,-1(VI03)
+		MADDz		VF04,VF14,VF02z		NOP
+		ITOF4.xyz	VF02,VF01			DIV			Q,VF23w,VF04w
+		MINI.w		VF04,VF04,VF24w		IADD		VI03,VI03,VI04
+		ADDAx		ACC,VF15,VF00x		IBLTZ		VI07,CullP0
+		MADDAx		ACC,VF12,VF02x		MTIR		VI07,VF01w
+		MADDAy		ACC,VF13,VF02y		LQ.xyzw		VF01,-1(VI03)
+		MADDz		VF03,VF14,VF02z		NOP
+		MULAw		ACC,VF00,VF25w		NOP
+		
+LoopP:	MADDq.xyzw	VF05,VF04,Q			MOVE.xyz	VF04,VF03
+		ITOF4.xyz	VF02,VF01			IADD		VI02,VI02,VI04
+		MINI.w		VF04,VF03,VF24w		DIV			Q,VF23w,VF03w
+		ADDAx		ACC,VF15,VF00x		IADD		VI03,VI03,VI04
+		FTOI4.xyz	VF05,VF05			IBLTZ		VI07,CullP
+		MADDAx		ACC,VF12,VF02x		MTIR		VI07,VF01w
+		MADDAy		ACC,VF13,VF02y		LQ.xyzw		VF01,-1(VI03)
+		MADDz		VF03,VF14,VF02z		IBNE		VI02,VI05,LoopP
+		MULAw		ACC,VF00,VF25w		SQ.xyzw		VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullP0:	MADDAy		ACC,VF13,VF02y		LQ.xyzw		VF01,-1(VI03)
+		MADDz		VF03,VF14,VF02z		B			LoopP
+		MULAw		ACC,VF00,VF26w		NOP
+
+CullP:	MADDAy		ACC,VF13,VF02y		LQ.xyzw		VF01,-1(VI03)
+		MADDz		VF03,VF14,VF02z		IBNE		VI02,VI05,LoopP
+		MULAw		ACC,VF00,VF26w		SQ.xyzw		VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+
+		.endif
+
+
+
+
+		.if 0
+		
+		; unoptimised version
+		
+		NOP								IADDIU		VI03,VI02,0			; source ptr = dest ptr
+
+LoopP:
+		NOP								IADD		VI03,VI03,VI04		; step source ptr
+		NOP								LQ.xyz		VF01,-1(VI03)		; get vertex coords
+		ITOF4.xyz	VF01,VF01			NOP								; vertex coords to float
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF01y		NOP								; row 1 view transform
+		MADDz		VF02,VF14,VF01z		NOP								; row 2 view transform
+		NOP								DIV			Q,VF00w,VF02w		; calc 1/w
+		NOP								WAITQ
+		MULq.xyz	VF03,VF02,Q			NOP								; homogeneous divide
+		NOP								IADD		VI02,VI02,VI04		; step dest ptr
+		NOP								SQ.xyz		VF03,-1(VI02)		; store screen coords
+		NOP								IBNE		VI02,VI05,LoopP		; loop
+		NOP								NOP
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+		.endif
+		.else
+		.if 1
+		
+		; fairly optimised version
+		; 7 cycles per vertex
+		
+		; loop prologue
+		NOP								IADD		VI03,VI02,VI04									; init/step source ptr
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,-1(VI03)		; row 3 view transform		; get 1st vertex
+		ITOF4.xyz	VF02,VF01			IADD		VI03,VI03,VI04		; 1st vertex to float		; step source ptr
+		MADDAx		ACC,VF12,VF02x		LQ.xyz		VF01,-1(VI03)		; row 0 view transform		; get 2nd vertex
+		MADDAy		ACC,VF13,VF02y		IADD		VI03,VI03,VI04		; row 1 view transform		; step source ptr
+		MADDz		VF04,VF14,VF02z		NOP								; row 2 view transform
+		ITOF4.xyz	VF02,VF01			LQ.xyz		VF01,-1(VI03)		; 2nd vertex to float		; get 3rd vertex
+		ADDAx		ACC,VF15,VF00x		DIV			Q,VF00w,VF04w		; row 3 view transform		; calc 1/w
+		MADDAx		ACC,VF12,VF02x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF02y		NOP                             ; row 1 view transform
+		MADDz		VF03,VF14,VF02z		NOP                             ; row 2 view transform
+		ITOF4.xyz	VF02,VF01			NOP								; 3rd vertex to float
+
+		; projection loop		
+LoopP:	NOP								IADD		VI03,VI03,VI04									; step source ptr
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,-1(VI03)		; row 3 view transform		; get vertex coords
+		MULq.xyz	VF05,VF04,Q			DIV			Q,VF00w,VF03w		; homogeneous div (xyz)/w	; calc 1/w
+		MADDAx		ACC,VF12,VF02x		IADD		VI02,VI02,VI04		; row 0 view transform		; step destination ptr
+		MADDAy		ACC,VF13,VF02y		MOVE.xyz	VF04,VF03			; row 1 view transform		; advance vertex queue
+		MADDz		VF03,VF14,VF02z		IBNE		VI02,VI05,LoopP		; row 2 view transform		; loop
+		ITOF4.xyz	VF02,VF01			SQ.xyz		VF05,-1(VI02)		; vertex coord to float		; store screen coords
+		
+		NOP								B			NextPrim										; go back for next prim
+		NOP								LQI			VF01,(VI02++)									; prefetch next tag
+
+		.else
+		
+		; very optimised version
+		; 6 cycles per vertex, but uses a lot of code space
+		
+		; loop prologue
+		NOP								IADD		VI01,VI02,VI04
+		NOP								IADD		VI03,VI01,VI04
+		NOP								LQ.xyz		VF01,-1(VI03)									; get vertex 1
+		NOP								LQ.xyz		VF05,-1(VI01)									; get vertex 0
+		ITOF4.xyz	VF02,VF01			IADD		VI01,VI03,VI04		; vertex 1 to float
+		ITOF4.xyz	VF06,VF05			IADD		VI03,VI01,VI04		; vertex 0 to float
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,-1(VI03)									; get vertex 3
+		MADDAx		ACC,VF12,VF02x		LQ.xyz		VF05,-1(VI01)									; get vertex 2
+		MADDAy		ACC,VF13,VF02y		IADD		VI03,VI03,VI04
+		MADDz		VF02,VF14,VF02z		IADDIU		VI01,VI00,1
+		ADDAx		ACC,VF15,VF00x		IAND		VI07,VI15,VI01		; test for NLOOP odd
+		MADDAx		ACC,VF12,VF06x		NOP
+		MADDAy		ACC,VF13,VF06y		NOP
+		MADDz		VF07,VF14,VF06z		ERCPR		P,VF02w
+		ITOF4.xyz	VF01,VF01			NOP								; vertex 3 to float
+		ITOF4.xyz	VF06,VF05			LQ.xyz		VF05,-1(VI03)		; vertex 2 to float			; get vertex 4
+		ADDAx		ACC,VF15,VF00x		DIV			Q,VF00w,VF07w
+		MADDAx		ACC,VF12,VF01x		NOP
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04
+		MADDz		VF03,VF14,VF01z		IBEQ		VI07,VI00,LoopP
+		ADDAx		ACC,VF15,VF00x		NOP
+		NOP								ISUB		VI05,VI05,VI04		; finish 1 vertex early if NLOOP odd
+	
+LoopP:	MADDAx		ACC,VF12,VF06x		LQ.xyz		VF01,-1(VI03)		; row 0 vertex transform B	; get vertex A
+		MADDAy		ACC,VF13,VF06y		IADD		VI03,VI03,VI04		; row 1 vertex transform B	; step source ptr
+		MULq.xyz	VF04,VF07,Q			ERCPR		P,VF03w				; homogeneous div (xyz)/wB'	; calculate 1/wA'
+		MADDz		VF07,VF14,VF06z		MFP.w		VF02,P				; row 2 vertex transform B	; get 1/wA
+		ITOF4.xyz	VF01,VF01			IADD		VI02,VI02,VI04		; vertex A to float			; step destination ptr
+		ITOF4.xyz	VF06,VF05			LQ.xyz		VF05,-1(VI03)		; vertex B' to float		; get vertex B
+		ADDAx		ACC,VF15,VF00x		SQ.xyz		VF04,-1(VI02)		; row 3 vertex transform A	; store screen coords B'
+
+		MULw.xyz	VF04,VF02,VF02w		DIV			Q,VF00w,VF07w		; homogeneous div (xyz)/wA	; calc 1/wB
+		MADDAx		ACC,VF12,VF01x		IADD		VI02,VI02,VI04		; row 0 vertex transform A	; step destination ptr
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04		; row 1 vertex transform A	; step source ptr
+		MADDz		VF02,VF14,VF01z		IBEQ		VI02,VI05,QuitP		; row 2 vertex transform A	; continue or quit
+		ADDAx		ACC,VF15,VF00x		SQ.xyz		VF04,-1(VI02)		; row 3 vertex transform B'	; store screen coords A
+
+		MADDAx		ACC,VF12,VF06x		LQ.xyz		VF01,-1(VI03)		; row 0 vertex transform B'	; get vertex A'
+		MADDAy		ACC,VF13,VF06y		IADD		VI03,VI03,VI04		; row 1 vertex transform B' ; step source ptr
+		MULq.xyz	VF04,VF07,Q			ERCPR		P,VF02w				; homogeneous div (xyz)/wB	; calc 1/wA
+		MADDz		VF07,VF14,VF06z		MFP.w		VF03,P				; row 2 vertex transform B'	; get 1/wA'
+		ITOF4.xyz	VF01,VF01			IADD		VI02,VI02,VI04		; vertex A' to float		; step destination ptr
+		ITOF4.xyz	VF06,VF05			LQ.xyz		VF05,-1(VI03)		; vertex B to float			; get vertex B'
+		ADDAx		ACC,VF15,VF00x		SQ.xyz		VF04,-1(VI02)		; row 3 vertex transform A'	; store screen coords B
+
+		MULw.xyz	VF04,VF03,VF03w		DIV			Q,VF00w,VF07w		; homogeneous div (xyz)/wA'	; calc 1/wB'
+		MADDAx		ACC,VF12,VF01x		IADD		VI02,VI02,VI04		; row 0 vertex transform A'	; step destination ptr
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04		; row 1 vertex transform A'	; step source ptr
+		MADDz		VF03,VF14,VF01z		IBNE		VI02,VI05,LoopP		; row 2 vertex transform A'	; loop or quit
+		ADDAx		ACC,VF15,VF00x		SQ.xyz		VF04,-1(VI02)		; row 3 vertex transform B	; store screen coords A'
+
+QuitP:	NOP								IBEQ		VI07,VI00,EndP									; finish if NLOOP was even
+		NOP								NOP
+		MULq.xyz	VF04,VF07,Q			IADD		VI02,VI02,VI04		; homogeneous div (xyz)/wB	; step source ptr
+		NOP								SQ.xyz		VF04,-1(VI02)									; store screen coords B
+
+EndP:	NOP								B			NextPrim										; back for next prim
+		NOP								LQI			VF01,(VI02++)									; prefetch next tag
+
+		.endif
+		.endif
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; triangle culling
+; ----------------
+; the per-triangle culling version of Proj
+
+Cull:
+
+
+		.if 1
+		
+		; fog version
+		
+		NOP								LOI			0x45000FFF
+		MULi.w		VF25,VF00,I			NOP								; VF25w = 2^11 + 1 - 2^-12
+		SUBw.w		VF25,VF25,VF23w		NOP								; VF25w = 2^11 + 1-f0 - 2^-12
+		
+		NOP								IADDIU		VI10,VI00,0x4000
+		NOP								IADDIU		VI10,VI10,0x4000
+
+
+		.if 0
+		
+		NOP								IADDIU		VI03,VI02,0
+
+LoopK:	NOP								IADD		VI03,VI03,VI04
+		NOP								LQ.xyzw		VF01,-1(VI03)
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								NOP
+		ITOF4.xyz	VF02,VF01			MTIR		VI07,VF01w
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULA		ACC,VF10,VF03		DIV			Q,VF23w,VF03w
+		MADDw		VF04,VF11,VF03w		NOP
+		MINI.w		VF03,VF03,VF24		NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF04xyz,VF04w		NOP
+		MULAw		ACC,VF00,VF25w		NOP
+		MADDq.xyzw	VF05,VF03,Q			NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								IBNE		VI01,VI00,CullK
+		FTOI4.xyz	VF06,VF05			MTIR		VI01,VF05w
+		NOP								IOR			VI01,VI01,VI07
+		NOP								MFIR.w		VF06,VI01
+		NOP								NOP
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopK
+		NOP								SQ.xyzw		VF06,-1(VI02)
+
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullK:	NOP								IOR			VI01,VI01,VI10
+		NOP								MFIR.w		VF06,VI01
+		NOP								NOP
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopK
+		NOP								SQ.xyzw		VF06,-1(VI02)
+
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+
+		.else
+		
+		NOP								IADDIU		VI03,VI02,0
+		
+		
+		NOP								IADD		VI03,VI03,VI04
+		NOP								LQ			VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ITOF4.xyz	VF01,VF01			NOP
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF01x		NOP
+		MADDAy		ACC,VF13,VF01y		MTIR		VI06,VF01w
+		MADDz		VF02,VF14,VF01z		NOP
+		NOP								NOP
+		
+		NOP								IADD		VI03,VI03,VI04
+		NOP								LQ			VF01,-1(VI03)
+		ADDx.xyz	VF04,VF02,VF00x		DIV			Q,VF23w,VF02w
+		MULA		ACC,VF10,VF02		NOP
+		MADDw		VF03,VF11,VF02w		NOP
+		ITOF4.xyz	VF01,VF01			NOP
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF01x		IADDIU		VI07,VI06,0
+		MADDAy		ACC,VF13,VF01y		MTIR		VI06,VF01w
+		MADDz		VF02,VF14,VF01z		NOP
+		CLIPw.xyz	VF03xyz,VF03w		NOP
+		
+LoopK:	MULAw		ACC,VF00,VF25w		IADD		VI03,VI03,VI04
+		MADDq		VF05,VF04,Q			LQ			VF01,-1(VI03)
+		ADDx.xyz	VF04,VF02,VF00x		DIV			Q,VF23w,VF02w
+		MULA		ACC,VF10,VF02		IADD		VI02,VI02,VI04
+		MADDw		VF03,VF11,VF02w		FCAND		VI01,0x03FFFF
+		ITOF4.xyz	VF01,VF01			IBNE		VI01,VI00,CullK
+		MINIw.w		VF04,VF02,VF24w		MTIR		VI01,VF05w
+		FTOI4.xyz	VF06,VF05			IOR			VI01,VI01,VI07
+		ADDAx		ACC,VF15,VF00x		MFIR.w		VF06,VI01
+		MADDAx		ACC,VF12,VF01x		IADDIU		VI07,VI06,0
+		MADDAy		ACC,VF13,VF01y		MTIR		VI06,VF01w
+		MADDz		VF02,VF14,VF01z		IBNE		VI02,VI05,LoopK
+		CLIPw.xyz	VF03xyz,VF03w		SQ			VF06,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullK:	FTOI4.xyz	VF06,VF05			IOR			VI01,VI01,VI10
+		ADDAx		ACC,VF15,VF00x		MFIR.w		VF06,VI01
+		MADDAx		ACC,VF12,VF01x		IADDIU		VI07,VI06,0
+		MADDAy		ACC,VF13,VF01y		MTIR		VI06,VF01w
+		MADDz		VF02,VF14,VF01z		IBNE		VI02,VI05,LoopK
+		CLIPw.xyz	VF03xyz,VF03w		SQ			VF06,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+		
+		
+		
+		.endif
+
+
+		.else
+
+		.if 0
+		; unoptimised
+		
+		NOP								IADDIU		VI03,VI02,0			; source ptr = dest ptr
+		FTOI15.w	VF05,VF00			NOP								; set VF05w=0x8000 (for ADC bit)
+
+LoopK:	NOP								IADD		VI03,VI03,VI04		; step source ptr
+		NOP								LQ.xyz		VF01,-1(VI03)		; get vertex coords
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ITOF4.xyz	VF02,VF01			NOP								; vertex coords to float
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF02x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF02y		NOP								; row 1 view transform
+		MADDz		VF03,VF14,VF02z		NOP								; row 2 view transform
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULA		ACC,VF10,VF03		DIV			Q,VF00w,VF03w		; inv viewport scale	; calc 1/w
+		MADDw		VF06,VF11,VF03w		NOP								; inv viewport offset
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF06xyz,VF06w		NOP								; generate outcodes
+		NOP								NOP
+		MULq.xyz	VF05,VF03,Q			NOP								; homogeneous divide
+		NOP								IADD		VI02,VI02,VI04		; step dest ptr
+		NOP								FCAND		VI01,0x03FFFF		; test last 3 outcodes
+		NOP								IBNE		VI01,VI00,CullK		; cull if all out
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopK		; loop
+		NOP								SQ.xyz		VF05,-1(VI02)		; store screen coords
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+CullK:	NOP								IBNE		VI02,VI05,LoopK		; loop
+		NOP								SQ			VF05,-1(VI02)		; store screen coords
+		
+		.else
+		; optimised
+		
+		FTOI15.w	VF05,VF00			IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,-1(VI03)
+		ITOF4.xyz	VF02,VF01			IADD		VI03,VI03,VI04
+		MADDAx		ACC,VF12,VF02x		LQ.xyz		VF01,-1(VI03)
+		MADDAy		ACC,VF13,VF02y		IADD		VI03,VI03,VI04
+		MADDz		VF04,VF14,VF02z		NOP
+		ITOF4.xyz	VF02,VF01			LQ.xyz		VF01,-1(VI03)
+		MULA		ACC,VF10,VF04		DIV			Q,VF00w,VF04w
+		MADDw		VF06,VF11,VF04w		NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		NOP
+		
+LoopK:	CLIPw.xyz	VF06xyz,VF06w		IADD		VI03,VI03,VI04
+		MULq.xyz	VF05,VF04,Q			IADD		VI02,VI02,VI04
+		ITOF4.xyz	VF02,VF01			LQ.xyz		VF01,-1(VI03)
+		MULA		ACC,VF10,VF03		DIV			Q,VF00w,VF03w
+		MADDw		VF06,VF11,VF03w		FCAND		VI01,0x03FFFF
+		ADDAx		ACC,VF15,VF00x		IBNE		VI01,VI00,CullK
+		MADDAx		ACC,VF12,VF02x		MOVE.xyz	VF04,VF03
+		MADDAy		ACC,VF13,VF02y		IBNE		VI02,VI05,LoopK
+		MADDz		VF03,VF14,VF02z		SQ.xyz		VF05,-1(VI02)
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+CullK:	MADDAy		ACC,VF13,VF02y		IBNE		VI02,VI05,LoopK
+		MADDz		VF03,VF14,VF02z		SQ			VF05,-1(VI02)
+		
+		.endif
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+		
+		.endif
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; vertex projection with perspective texturing
+; --------------------------------------------
+; transforms and projects vertex coords, and applies perspective to texture coords
+
+
+
+		
+PTex1:
+
+		; first some hackery added in as a bit of an afterthought, to support single-sided and colouring of meshes
+		; and switch between VU1-fogging and standard version of rendering code
+		
+		NOP								IADDIU		VI10,VI00,0x0800
+		NOP								IAND		VI10,VI01,VI10
+		NOP								IADDIU		VI11,VI00,0x3000
+		NOP								IAND		VI11,VI01,VI11
+		NOP								IBNE		VI10,VI00,SingleSided
+		NOP								IADDIU		VI01,VI00,Label1
+Label1:
+		NOP								IADDIU		VI01,VI00,COLR
+		NOP								IAND		VI01,VI14,VI01
+		NOP								ISUB		VI14,VI14,VI01
+		NOP								IBNE		VI01,VI00,ApplyColour
+		NOP								IADDIU		VI01,VI00,Label2
+Label2:
+
+		; test for fog enable
+		NOP								MFIR.y		VF02,VI11
+		NOP								IADDIU		VI01,VI00,FOGE
+		NOP								IAND		VI01,VI14,VI01
+		NOP								ISUB		VI14,VI14,VI01		; clear FOGE flag
+		ITOF12.y	VF02,VF02			IBNE		VI01,VI00,PTexFog
+		NOP								IADDIU		VI01,VI00,3
+		NOP								LOI			0x302E4000
+		ADDAi.y		ACC,VF00,I			LOI			0x2A800000
+		MADDi.y		VF01,VF02,I			IBEQ		VI04,VI01,FGE0		; keep uv-clamp flags
+		NOP								LOI			0x4F800000
+		MULi.y		VF01,VF01,I			NOP
+FGE0:	NOP								SQ.y		VF01,-1(VI02)		; clear FGE bit
+
+
+		NOP								IBEQ		VI14,VI00,PTex2
+		NOP								IADDIU		VI01,VI00,CULL
+		
+		NOP								IADDIU		VI11,VI00,CLIP
+		NOP								IBEQ		VI14,VI01,CullPTex
+		NOP								IADDIU		VI01,VI00,CullPTex
+		
+		NOP								IBEQ		VI14,VI11,Clip
+		NOP								ISW.w		VI01,-1(VI02)
+		
+		NOP								B			Shadow
+		NOP								NOP
+
+
+PTexFog:
+		
+		NOP								IBEQ		VI14,VI00,PTex2F
+		NOP								IADDIU		VI01,VI00,CULL
+		
+		NOP								IADDIU		VI11,VI00,CLIP
+		NOP								IBEQ		VI14,VI01,CullPTexF
+		NOP								IADDIU		VI01,VI00,CullPTexF
+		
+		NOP								IBEQ		VI14,VI11,Clip
+		NOP								ISW.w		VI01,-1(VI02)
+		
+		NOP								B			Shadow
+		NOP								NOP
+		
+	
+
+		
+PTex2F:
+		; fog version
+		
+		NOP								LOI			0x45000FFF
+		MULi.w		VF25,VF00,I			NOP								; VF25w = 2^11 + 1 - 2^-12
+		SUBw.w		VF25,VF25,VF23w		NOP								; VF25w = 2^11 + 1-f0 - 2^-12
+		NOP								LOI			8
+		ADDi.w		VF26,VF25,I			NOP								; VF26w = VF25w + ADC
+
+
+
+		.if 0
+		
+		; unoptimised
+		
+		NOP								IADDIU		VI03,VI02,0
+		NOP								MR32.z		VF07,VF00
+
+		
+LoopPTF:NOP								LQ.xy		VF06,0(VI03)
+		NOP								IADD		VI03,VI03,VI04
+		NOP								LQ.xyzw		VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		ITOF12.xy	VF07,VF06			NOP
+		ITOF4.xyz	VF02,VF01			MTIR		VI07,VF01w
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								DIV			Q,VF23w,VF03w
+		MINI.w		VF03,VF03,VF24		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								IBLTZ		VI07,PTCullF
+		NOP								NOP
+		MULAw		ACC,VF00,VF25w		NOP
+		MULq.xyz	VF08,VF07,Q			NOP
+		MADDq		VF04,VF03,Q			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								SQ.xyz		VF08,0(VI02)
+		FTOI4.xyz	VF04,VF04			NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopPTF
+		NOP								SQ.xyzw		VF04,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+PTCullF:MULAw		ACC,VF00,VF26w		NOP
+		MULq.xyz	VF08,VF07,Q			NOP
+		MADDq		VF04,VF03,Q			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								SQ.xyz		VF08,0(VI02)
+		FTOI4.xyz	VF04,VF04			NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopPTF
+		NOP								SQ.xyzw		VF04,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		
+		.else
+		
+		; optimised
+		
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ			VF01,-1(VI03)
+		ITOF4.xyz	VF01,VF01			LQ.xy		VF07,0(VI02)
+		MADDAx		ACC,VF12,VF01x		NOP
+		MADDAy		ACC,VF13,VF01y		LQ.xy		VF05,0(VI03)
+		MADDz		VF03,VF14,VF01z		IADD		VI03,VI03,VI04
+		MINI.w		VF03,VF03,VF24		DIV			Q,VF23w,VF03w
+		ITOF12.xy	VF07,VF07			MTIR		VI07,VF01w
+		ITOF12.xy	VF06,VF05			LQ			VF01,-1(VI03)
+		ITOF4.xyz	VF01,VF01			NOP
+		ADDAx		ACC,VF15,VF00x		MR32.z		VF07,VF00
+		
+LoopPTF:MADDAx		ACC,VF12,VF01x		LQ.xy		VF05,0(VI03)
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04
+		MULq		VF04,VF03,Q			IBLTZ		VI07,PTCullF
+		MADDz		VF03,VF14,VF01z		MTIR		VI07,VF01w
+		MULq.xyz	VF08,VF07,Q			LQ			VF01,-1(VI03)
+		ADDx.xy		VF07,VF06,VF00x		IADDIU		VI01,VI02,0
+		FTOI4.xyz	VF04,VF04			IADD		VI02,VI02,VI04
+		ADD.w		VF04,VF04,VF25		DIV			Q,VF23w,VF03w
+		ITOF4.xyz	VF01,VF01			SQ.xyz		VF08,0(VI01)
+		MINI.w		VF03,VF03,VF24		NOP
+		ITOF12.xy	VF06,VF05			IBNE		VI02,VI05,LoopPTF
+		ADDAx		ACC,VF15,VF00x		SQ			VF04,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+PTCullF:MULq.xyz	VF08,VF07,Q			LQ			VF01,-1(VI03)
+		ADDx.xy		VF07,VF06,VF00x		IADDIU		VI01,VI02,0
+		FTOI4.xyz	VF04,VF04			IADD		VI02,VI02,VI04
+		ADD.w		VF04,VF04,VF26		DIV			Q,VF23w,VF03w
+		ITOF4.xyz	VF01,VF01			SQ.xyz		VF08,0(VI01)
+		MINI.w		VF03,VF03,VF24		NOP
+		ITOF12.xy	VF06,VF05			IBNE		VI02,VI05,LoopPTF
+		ADDAx		ACC,VF15,VF00x		SQ			VF04,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		
+		
+		
+		.endif
+		
+		
+		
+		
+		; non-fogged version
+		
+PTex2:
+
+
+		.if 0
+		; unoptimised version
+		
+		NOP								IADDIU		VI03,VI02,0			; source ptr = dest ptr
+		NOP								MR32.z		VF04,VF00			; set 1 in (s,t,1)
+
+LoopPT:	NOP								LQ.xy		VF04,0(VI03)		; get texture coords
+		NOP								IADD		VI03,VI03,VI04		; step source ptr
+		NOP								LQ.xyz		VF01,-1(VI03)		; get vertex coords
+		NOP								NOP
+		NOP								NOP
+		ITOF12.xy	VF04,VF04			NOP								; texture coords to float
+		ITOF4.xyz	VF01,VF01			NOP								; vertex coords to float
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF01y		NOP								; row 1 view transform		
+		MADDz		VF02,VF14,VF01z		NOP								; row 2 view transform
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								DIV			Q,VF23w,VF02w		; calc 1/w
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULq.xyz	VF05,VF04,Q			NOP								; homogeneous div (st1)/w
+		MULq.xyz	VF03,VF02,Q			NOP								; homogeneous div (xyz)/w
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		FTOI4.xyz	VF03,VF03			NOP
+		NOP								NOP
+		NOP								SQ.xyz		VF05,0(VI02)		; store texture coords
+		NOP								IADD		VI02,VI02,VI04		; step dest ptr
+		NOP								SQ.xyz		VF03,-1(VI02)		; store screen coords
+		NOP								IBNE		VI02,VI05,LoopPT	; loop
+		NOP								NOP
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+		
+		.else
+		
+		; optimised
+		
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,-1(VI03)
+		ITOF4.xyz	VF01,VF01			LQ.xy		VF05,0(VI02)
+		MADDAx		ACC,VF12,VF01x		LQ.xy		VF06,0(VI03)
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04
+		MADDz		VF03,VF14,VF01z		LQ.xyz		VF01,-1(VI03)
+		ITOF12.xy	VF07,VF05			NOP
+		ITOF4.xyz	VF01,VF01			MR32.z		VF07,VF00
+		ADDAx		ACC,VF15,VF00x		DIV			Q,VF23w,VF03w
+		MADDAx		ACC,VF12,VF01x		LQ.xy		VF05,0(VI03)
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04
+		MADDz		VF02,VF14,VF01z		LQ.xyz		VF01,-1(VI03)
+		MULq.xyz	VF08,VF07,Q			WAITQ
+		MULq.xyz	VF04,VF03,Q			NOP
+		
+LoopPT:	ITOF12.xy	VF07,VF06			MOVE.xy		VF06,VF05
+		ITOF4.xyz	VF01,VF01			DIV			Q,VF23w,VF02w
+		ADDx.xyz	VF03,VF02,VF00x		SQ.xyz		VF08,0(VI02)
+		FTOI4.xyz	VF04,VF04			IADD		VI02,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF01x		LQ.xy		VF05,0(VI03)
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04
+		MADDz		VF02,VF14,VF01z		LQ.xyz		VF01,-1(VI03)
+		MULq.xyz	VF08,VF07,Q			IBNE		VI02,VI05,LoopPT
+		MULq.xyz	VF04,VF03,Q			SQ.xyz		VF04,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		
+		.endif
+		
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; triangle culling and perspective texturing
+
+
+CullPTexF:
+
+		; fog version
+		
+		NOP								LOI			0x45000FFF
+		MULi.w		VF25,VF00,I			NOP								; VF25w = 2^11 + 1 - 2^-12
+		SUBw.w		VF25,VF25,VF23w		NOP								; VF25w = 2^11 + 1-f0 - 2^-12
+		
+		NOP								IADDIU		VI10,VI00,0x4000
+		NOP								IADDIU		VI10,VI10,0x4000
+		
+		
+		.if 0
+		; unoptimised
+		
+		NOP								IADDIU		VI03,VI02,0
+		NOP								MR32.z		VF07,VF00
+
+		
+LoopKPTF:
+		NOP								LQ.xy		VF07,0(VI03)
+		NOP								IADD		VI03,VI03,VI04
+		NOP								LQ.xyzw		VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		ITOF12.xy	VF07,VF07			NOP
+		ITOF4.xyz	VF02,VF01			MTIR		VI07,VF01w
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULA		ACC,VF10,VF03		DIV			Q,VF23w,VF03w
+		MADDw		VF04,VF11,VF03w		NOP
+		MINI.w		VF03,VF03,VF24		NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF04xyz,VF04w		NOP
+		MULAw		ACC,VF00,VF25w		NOP
+		MULq.xyz	VF08,VF07,Q			NOP
+		MADDq		VF05,VF03,Q			NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								IBNE		VI01,VI00,CullKPTF
+		FTOI4.xyz	VF06,VF05			SQ.xyz		VF08,0(VI02)
+		NOP								MTIR		VI11,VF05w
+		NOP								IOR			VI11,VI11,VI07
+		NOP								MFIR.w		VF06,VI11
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopKPTF
+		NOP								SQ.xyzw		VF06,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullKPTF:
+		NOP								MTIR		VI11,VF05w
+		NOP								IOR			VI11,VI11,VI10
+		NOP								MFIR.w		VF06,VI11
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopKPTF
+		NOP								SQ.xyzw		VF06,-1(VI02)
+
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+
+		.else
+		; optimised
+
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ			VF04,-1(VI03)
+		ITOF4.xyz	VF04,VF04			LQ.xy		VF07,0(VI02)
+		MADDAx		ACC,VF12,VF04x		MTIR		VI07,VF04w
+		MADDAy		ACC,VF13,VF04y		LQ.xy		VF06,0(VI03)
+		MADDz		VF04,VF14,VF04z		IADD		VI03,VI03,VI04
+		ITOF12.xy	VF07,VF07			LQ			VF01,-1(VI03)
+		MULA		ACC,VF10,VF04		DIV			Q,VF23w,VF04w
+		MADDw		VF03,VF11,VF04w		MR32.z		VF07,VF00
+		ITOF4.xyz	VF01,VF01			NOP
+		MINI.w		VF04,VF04,VF24		NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF01x		NOP
+		MADDAy		ACC,VF13,VF01y		NOP
+		MADDz		VF02,VF14,VF01z		NOP
+		CLIPw.xyz	VF03xyz,VF03w		NOP
+		
+LoopKPTF:
+		MULq.xyz	VF08,VF07,Q			MTIR		VI06,VF01w
+		ITOF12.xy	VF07,VF06			LQ.xy		VF06,0(VI03)
+		MULAw		ACC,VF00,VF25w		IADD		VI03,VI03,VI04
+		MADDq		VF05,VF04,Q			LQ			VF01,-1(VI03)
+		ADDx.xyz	VF04,VF02,VF00x		FCAND		VI01,0x03FFFF
+		MULA		ACC,VF10,VF02		DIV			Q,VF23w,VF02w
+		MADDw		VF03,VF11,VF02w		SQ.xyz		VF08,0(VI02)
+		ITOF4.xyz	VF01,VF01			IBNE		VI01,VI00,CullKPTF
+		MINI.w		VF04,VF02,VF24		MTIR		VI11,VF05w
+		FTOI4.xyz	VF05,VF05			IOR			VI11,VI11,VI07
+		ADDAx		ACC,VF15,VF00x		MFIR.w		VF05,VI11
+		MADDAx		ACC,VF12,VF01x		IADD		VI02,VI02,VI04
+		MADDAy		ACC,VF13,VF01y		IADDIU		VI07,VI06,0
+		MADDz		VF02,VF14,VF01z		IBNE		VI02,VI05,LoopKPTF
+		CLIPw.xyz	VF03xyz,VF03w		SQ.xyzw		VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullKPTF:
+		FTOI4.xyz	VF05,VF05			IOR			VI11,VI11,VI10
+		ADDAx		ACC,VF15,VF00x		MFIR.w		VF05,VI11
+		MADDAx		ACC,VF12,VF01x		IADD		VI02,VI02,VI04
+		MADDAy		ACC,VF13,VF01y		IADDIU		VI07,VI06,0
+		MADDz		VF02,VF14,VF01z		IBNE		VI02,VI05,LoopKPTF
+		CLIPw.xyz	VF03xyz,VF03w		SQ.xyzw		VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+		
+		.endif
+		
+		
+
+
+		
+		; non-fogged version
+
+CullPTex:
+
+		.if 0
+		; unoptimised
+		
+		NOP								IADDIU		VI03,VI02,0			; source ptr = dest ptr
+		FTOI15.w	VF05,VF00			NOP								; set VF05w=0x8000 (for ADC bit)
+		NOP								MR32.z		VF07,VF00			; set 1 in (s,t,1)
+
+LoopKPT:NOP								LQ.xy		VF07,0(VI03)		; get tex coords
+		NOP								IADD		VI03,VI03,VI04		; step source ptr
+		NOP								LQ.xyz		VF01,-1(VI03)		; get vertex coords
+		NOP								NOP
+		NOP								NOP
+		ITOF12.xy	VF07,VF07			NOP								; tex coords to float
+		ITOF4.xyz	VF02,VF01			NOP								; vertex coords to float
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF02x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF02y		NOP								; row 1 view transform
+		MADDz		VF03,VF14,VF02z		NOP								; row 2 view transform
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULA		ACC,VF10,VF03		DIV			Q,VF23w,VF03w		; inv viewport scale	; calc 1/w
+		MADDw		VF06,VF11,VF03w		NOP								; inv viewport offset
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF06xyz,VF06w		NOP								; generate outcodes
+		NOP								NOP
+		MULq.xyz	VF08,VF07,Q			NOP								; homogeneous divide (st1)/w
+		MULq.xyz	VF05,VF03,Q			NOP								; homogeneous divide (xyz)/w
+		NOP								NOP
+		NOP								FCAND		VI01,0x03FFFF		; test last 3 outcodes
+		NOP								SQ.xyz		VF08,0(VI02)
+		FTOI4.xyz	VF05,VF05			IADD		VI02,VI02,VI04		; step dest ptr
+		NOP								IBNE		VI01,VI00,CullKPT	; cull if all out
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopKPT	; loop
+		NOP								SQ.xyz		VF05,-1(VI02)		; store screen coords
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+CullKPT:NOP								IBNE		VI02,VI05,LoopKPT	; loop
+		NOP								SQ			VF05,-1(VI02)		; store screen coords
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		.else
+		
+		; optimised
+		
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,-1(VI03)
+		ITOF4.xyz	VF01,VF01			LQ.xy		VF05,0(VI02)
+		MADDAx		ACC,VF12,VF01x		LQ.xy		VF06,0(VI03)
+		MADDAy		ACC,VF13,VF01y		IADD		VI03,VI03,VI04
+		MADDz		VF03,VF14,VF01z		LQ.xyz		VF01,-1(VI03)
+		ITOF12.xy	VF07,VF05			NOP
+		MULA		ACC,VF10,VF03		NOP
+		MADDw		VF02,VF11,VF03w		DIV			Q,VF23w,VF03w
+		ITOF4.xyz	VF01,VF01			MR32.z		VF07,VF00
+		ADDAx		ACC,VF15,VF00x		ISUBIU		VI01,VI00,1
+		CLIPw.xyz	VF02xyz,VF02w		MFIR.w		VF04,VI01
+		MADDAx		ACC,VF12,VF01x		NOP
+		MADDAy		ACC,VF13,VF01y		NOP
+		
+LoopKPT:MADDz		VF02,VF14,VF01z		LQ.xy		VF05,0(VI03)
+		MULq.xyz	VF08,VF07,Q			IADD		VI03,VI03,VI04
+		MULq.xyz	VF04,VF03,Q			LQ.xyz		VF01,-1(VI03)
+		ITOF12.xy	VF07,VF06			FCAND		VI01,0x03FFFF
+		MULA		ACC,VF10,VF02		DIV			Q,VF23w,VF02w
+		MADDw		VF03,VF11,VF02w		MOVE.xy		VF06,VF05
+		ITOF4.xyz	VF01,VF01			SQ.xyz		VF08,0(VI02)
+		FTOI4.xyz	VF04,VF04			IADD		VI02,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		IBNE		VI01,VI00,CullKPT
+		CLIPw.xyz	VF03xyz,VF03w		MOVE.xyz	VF03,VF02
+		MADDAx		ACC,VF12,VF01x		IBNE		VI02,VI05,LoopKPT
+		MADDAy		ACC,VF13,VF01y		SQ.xyz		VF04,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+CullKPT:MADDAx		ACC,VF12,VF01x		IBNE		VI02,VI05,LoopKPT
+		MADDAy		ACC,VF13,VF01y		SQ			VF04,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		.endif
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+WibbleT1:
+
+		.if 0
+		; unoptimised version
+		
+		NOP								IADDIU		VI03,VI02,0
+		
+LoopW:	NOP								LQ.xy		VF01,0(VI03)
+		NOP								IADD		VI03,VI03,VI04
+		NOP								NOP
+		NOP								NOP
+		ITOF12.xy	VF02,VF01			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ADD.xy		VF03,VF02,VF27		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		FTOI12.xy	VF04,VF03			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								SQ.xy		VF04,0(VI02)
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopW
+		NOP								NOP
+		
+		.else
+		; optimised version
+		
+		
+		NOP								LQ.xy		VF04,0(VI02)
+		ITOF12.xy	VF04,VF04			IADD		VI03,VI02,VI04
+		ADD.xy		VF04,VF04,VF27		LQ.xy		VF03,0(VI03)
+		ITOF12.xy	VF03,VF03			IADD		VI03,VI03,VI04
+		FTOI12.xy	VF04,VF04			LQ.xy		VF02,0(VI03)
+		ADD.xy		VF03,VF03,VF27		IADD		VI03,VI03,VI04
+		ITOF12.xy	VF02,VF02			ISUB		VI05,VI05,VI04
+		
+LoopW:	NOP								LQ.xy		VF01,0(VI03)
+		NOP								SQ.xy		VF04,0(VI02)
+		FTOI12.xy	VF04,VF03			IADD		VI03,VI03,VI04
+		ADD.xy		VF03,VF02,VF27      IBNE		VI02,VI05,LoopW
+		ITOF12.xy	VF02,VF01			IADD		VI02,VI02,VI04
+		
+		NOP								IADD		VI05,VI05,VI04
+		
+		.endif
+		
+		NOP								B			PTex1
+		NOP								ISUB		VI02,VI02,VI06
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+LWibT1:
+
+		; optimised version
+		
+		NOP								LQ.xy		VF04,0(VI02)
+		ITOF12.xy	VF04,VF04			IADD		VI03,VI02,VI04
+		ADD.xy		VF04,VF04,VF27		LQ.xy		VF03,0(VI03)
+		ITOF12.xy	VF03,VF03			IADD		VI03,VI03,VI04
+		FTOI12.xy	VF04,VF04			LQ.xy		VF02,0(VI03)
+		ADD.xy		VF03,VF03,VF27		IADD		VI03,VI03,VI04
+		ITOF12.xy	VF02,VF02			ISUB		VI05,VI05,VI04
+		
+LoopLW:	NOP								LQ.xy		VF01,0(VI03)
+		NOP								SQ.xy		VF04,0(VI02)
+		FTOI12.xy	VF04,VF03			IADD		VI03,VI03,VI04
+		ADD.xy		VF03,VF02,VF27      IBNE		VI02,VI05,LoopLW
+		ITOF12.xy	VF02,VF01			IADD		VI02,VI02,VI04
+		
+		NOP								IADD		VI05,VI05,VI04
+		
+		NOP								B			Light1
+		NOP								ISUB		VI02,VI02,VI06
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; reflection mapping
+
+Refl1:
+
+		NOP								IADDIU		VI11,VI00,0x0800
+		NOP								IAND		VI01,VI01,VI11
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,SingleSided
+		NOP								IADDIU		VI01,VI00,LabelR0
+
+LabelR0:
+		NOP								IADDIU		VI01,VI00,COLR
+		NOP								IAND		VI01,VI14,VI01
+		NOP								ISUB		VI14,VI14,VI01
+		NOP								IBNE		VI01,VI00,ApplyColour
+		NOP								IADDIU		VI01,VI00,LabelR1
+
+LabelR1:
+		NOP								IADDIU		VI01,VI00,CLIP
+		NOP								IAND		VI01,VI14,VI01
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,ReflClip
+		NOP								NOP
+
+
+		.if 0
+		; unoptimised
+
+		NOP								IADDIU		VI03,VI02,0
+		NOP								MR32.z		VF04,VF00
+		NOP								LOI			0.5
+
+LoopR: 	NOP								LQ.xyz		VF05,0(VI03)		; get normal
+		NOP								IADD		VI03,VI03,VI04		; step source pointer
+		NOP								LQ.xyz		VF01,-1(VI03)		; get vertex
+		NOP								NOP
+		ITOF15.xyz	VF05,VF05			NOP								; VF05 = n (unit, in model space)
+		NOP								NOP
+		ITOF4.xyz	VF02,VF01			NOP
+		
+		; compute 0.5*(nx+vx/vz+1, ny+vy/vz+1)
+		MULAx.xyz	ACC,VF17,VF02x		NOP								; transform v
+		MADDAy.xyz	ACC,VF18,VF02y		NOP
+		MADDz.xyz	VF02,VF19,VF02z		NOP
+		MULAx.xy	ACC,VF17,VF05x		NOP								; transform n
+		MADDAy.xy	ACC,VF18,VF05y		NOP
+		MADDz.xy	VF03,VF19,VF05z		NOP
+		NOP								DIV			Q,VF00w,VF02z		; calc 1/vz'
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ADDAi.xy	ACC,VF03,I			NOP
+		MSUBq.xy	VF04,VF02,Q			NOP
+
+		NOP								SQ.xyz		VF04,0(VI02)
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopR
+		NOP								NOP
+
+		NOP								B			Proj1
+		NOP								ISUB		VI02,VI02,VI06
+
+		.else
+		; optimised
+		NOP								LQ.xyz		VF02,2(VI02)
+		NOP								LQ.xyz		VF04,0(VI02)
+		ITOF4.xyz	VF02,VF02			MR32.z		VF03,VF00
+		ITOF15.xyz	VF05,VF04			LOI			0.5
+		MULAx.xyz	ACC,VF17,VF02x		LQ.xyz		VF01,5(VI02)
+		MADDAy.xyz	ACC,VF18,VF02y		LQ.xyz		VF04,3(VI02)
+		MADDz.xyz	VF02,VF19,VF02z		NOP
+		MULAx.xy	ACC,VF17,VF05x		NOP
+		ITOF4.xyz	VF01,VF01			DIV			Q,VF00w,VF02z
+		MADDAy.xy	ACC,VF18,VF05y		NOP
+		MADDz.xy    VF27,VF19,VF05z		NOP
+		ITOF15.xyz	VF05,VF04			NOP
+		
+LoopR:	MULAx.xyz	ACC,VF17,VF01x		MOVE.xy		VF03,VF02
+		MADDAy.xyz	ACC,VF18,VF01y		NOP
+		MADDz.xyz	VF02,VF19,VF01z		LQ.xyz		VF01,8(VI02)
+		ADDAi.xy	ACC,VF27,I			LQ.xyz		VF04,6(VI02)
+		MSUBq.xy	VF03,VF03,Q			IADD		VI02,VI02,VI04
+		MULAx.xy	ACC,VF17,VF05x		NOP
+		ITOF4.xyz	VF01,VF01			DIV			Q,VF00w,VF02z
+		MADDAy.xy	ACC,VF18,VF05y		NOP
+		MADDz.xy    VF27,VF19,VF05z		IBNE		VI02,VI05,LoopR
+		ITOF15.xyz	VF05,VF04			SQ.xyz		VF03,-3(VI02)
+		
+		NOP								B			Proj2
+		NOP								ISUB		VI02,VI02,VI06
+
+		.endif
+		
+		
+		
+		
+ReflClip:
+
+		.if 0
+		; unoptimised
+		
+		NOP								IADDIU		VI03,VI02,0
+		NOP								LOI			0.5
+
+
+LoopRC:	NOP								LQ.xyz		VF04,0(VI03)		; get normal
+		NOP								IADD		VI03,VI03,VI04		; step source pointer
+		NOP								LQ.xyz		VF01,-1(VI03)		; get vertex
+		NOP								NOP
+		ITOF15.xyz	VF04,VF04			NOP								; VF01 = n (unit, in model space)
+		NOP								NOP
+		ITOF4.xyz	VF01,VF01			NOP
+		
+		; compute ((nx'+0.5)*vz'-vx', (ny'+0.5)*vz'-vy', vz', 1/vz')
+		MULAx.xyz	ACC,VF17,VF01x		NOP								; transform v
+		MADDAy.xyz	ACC,VF18,VF01y		NOP
+		MADDz.xyz	VF02,VF19,VF01z		NOP
+		ADDAi.xy	ACC,VF00,I			NOP
+		MADDAx.xy	ACC,VF17,VF04x		NOP								; transform n
+		MADDAy.xy	ACC,VF18,VF04y		NOP
+		MADDz.xy	VF03,VF19,VF04z		NOP
+		NOP								DIV			Q,VF00w,VF02z
+		SUBA.xy		ACC,VF00,VF02		MOVE.z		VF04,VF02
+		MADDz.xy	VF04,VF03,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULq.w		VF04,VF00,Q			NOP
+		
+		NOP								SQ			VF04,0(VI02)
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopRC
+		NOP								NOP
+
+		NOP								ISUB		VI02,VI02,VI06
+
+		NOP								IADDIU		VI01,VI00,ReflPostClip
+		
+		NOP								B			Clip
+		NOP								ISW.w		VI01,-1(VI02)
+		
+		.else
+		; optimised
+		
+		NOP								LQ.xyz		VF02,2(VI02)
+		NOP								LQ.xyz		VF03,0(VI02)
+		ITOF4.xyz	VF02,VF02			LOI			0.5
+		ITOF15.xyz	VF04,VF03			NOP
+		MULAx.xyz	ACC,VF17,VF02x		LQ.xyz		VF03,3(VI02)
+		MADDAy.xyz	ACC,VF18,VF02y		LQ.xyz		VF01,5(VI02)
+		MADDz.xyz	VF02,VF19,VF02z		NOP
+		ADDAi.xy	ACC,VF00,I			NOP
+		MADDAx.xy	ACC,VF17,VF04x		NOP
+		MADDAy.xy	ACC,VF18,VF04y		NOP
+		
+LoopRC:	MADDz.xy	VF05,VF19,VF04z		DIV			Q,VF00w,VF02z
+		ITOF4.xyz	VF01,VF01			IADD		VI02,VI02,VI04
+		ITOF15.xyz	VF04,VF03			NOP
+		SUBA.xy		ACC,VF00,VF02		MOVE.z		VF05,VF02
+		MADDz.xy	VF05,VF05,VF02z		NOP
+		MULAx.xyz	ACC,VF17,VF01x		NOP
+		MADDAy.xyz	ACC,VF18,VF01y		NOP
+		MULq.w		VF05,VF00,Q			LQ.xyz		VF03,3(VI02)
+		MADDz.xyz	VF02,VF19,VF01z		LQ.xyz		VF01,5(VI02)
+		ADDAi.xy	ACC,VF00,I			NOP
+		MADDAx.xy	ACC,VF17,VF04x		IBNE		VI02,VI05,LoopRC
+		MADDAy.xy	ACC,VF18,VF04y		SQ			VF05,-3(VI02)
+		
+		NOP								ISUB		VI02,VI02,VI06
+
+		NOP								IADDIU		VI01,VI00,ReflPostClip
+		
+		NOP								B			Clip
+		NOP								ISW.w		VI01,-1(VI02)
+		
+		
+		.endif
+
+
+
+
+ReflPostClip:
+
+		.if 1
+		
+		; fog version
+
+		
+		
+		NOP								LOI			0x45000FFF
+		MULi.w		VF25,VF00,I			NOP								; VF25w = 2^11 + 1 - 2^-12
+		SUBw.w		VF25,VF25,VF23w		NOP								; VF25w = 2^11 + 1-f0 - 2^-12
+		
+		NOP								IADDIU		VI10,VI00,0x4000
+		NOP								IADDIU		VI10,VI10,0x4000
+		
+		
+		.if 0
+		; unoptimised
+		
+		NOP								IADDIU		VI03,VI02,0
+
+		
+LoopRPC:NOP								LQ			VF07,0(VI03)
+		NOP								IADD		VI03,VI03,VI04
+		NOP								LQ			VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		MULw.xyz	VF08,VF07,VF07w		NOP								; homogeneous divide (str)/r
+		ITOF4.xyz	VF02,VF01			MTIR		VI07,VF01w
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULA		ACC,VF10,VF03		DIV			Q,VF23w,VF03w
+		MADDw		VF04,VF11,VF03w		NOP
+		MINI.w		VF03,VF03,VF24		NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF04xyz,VF04w		NOP
+		MULAw		ACC,VF00,VF25w		NOP
+		MADDq		VF05,VF03,Q			NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								IBNE		VI01,VI00,CullRPC
+		FTOI4.xyz	VF06,VF05			SQ.xyz		VF08,0(VI02)
+		NOP								MTIR		VI11,VF05w
+		NOP								IOR			VI11,VI11,VI07
+		NOP								MFIR.w		VF06,VI11
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopRPC
+		NOP								SQ.xyzw		VF06,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullRPC:NOP								MTIR		VI11,VF05w
+		NOP								IOR			VI11,VI11,VI10
+		NOP								MFIR.w		VF06,VI11
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopRPC
+		NOP								SQ.xyzw		VF06,-1(VI02)
+
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+		.else
+		; optimised
+		
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ			VF04,-1(VI03)
+		ITOF4.xyz	VF04,VF04			LQ			VF07,0(VI02)
+		MADDAx		ACC,VF12,VF04x		MTIR		VI07,VF04w
+		MADDAy		ACC,VF13,VF04y		LQ			VF06,0(VI03)
+		MADDz		VF04,VF14,VF04z		IADD		VI03,VI03,VI04
+		ADDx		VF07,VF07,VF00x		LQ			VF01,-1(VI03)
+		MULA		ACC,VF10,VF04		DIV			Q,VF23w,VF04w
+		MADDw		VF03,VF11,VF04w		NOP
+		ITOF4.xyz	VF01,VF01			NOP
+		MINI.w		VF04,VF04,VF24		NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF01x		NOP
+		MADDAy		ACC,VF13,VF01y		NOP
+		MADDz		VF02,VF14,VF01z		NOP
+		CLIPw.xyz	VF03xyz,VF03w		NOP
+		
+LoopRPC:MULw.xyz	VF08,VF07,VF07w		MTIR		VI06,VF01w
+		ADDx		VF07,VF06,VF00x		LQ			VF06,0(VI03)
+		MULAw		ACC,VF00,VF25w		IADD		VI03,VI03,VI04
+		MADDq		VF05,VF04,Q			LQ			VF01,-1(VI03)
+		ADDx.xyz	VF04,VF02,VF00x		FCAND		VI01,0x03FFFF
+		MULA		ACC,VF10,VF02		DIV			Q,VF23w,VF02w
+		MADDw		VF03,VF11,VF02w		SQ.xyz		VF08,0(VI02)
+		ITOF4.xyz	VF01,VF01			IBNE		VI01,VI00,CullRPC
+		MINI.w		VF04,VF02,VF24		MTIR		VI11,VF05w
+		FTOI4.xyz	VF05,VF05			IOR			VI11,VI11,VI07
+		ADDAx		ACC,VF15,VF00x		MFIR.w		VF05,VI11
+		MADDAx		ACC,VF12,VF01x		IADD		VI02,VI02,VI04
+		MADDAy		ACC,VF13,VF01y		IADDIU		VI07,VI06,0
+		MADDz		VF02,VF14,VF01z		IBNE		VI02,VI05,LoopRPC
+		CLIPw.xyz	VF03xyz,VF03w		SQ.xyzw		VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullRPC:FTOI4.xyz	VF05,VF05			IOR			VI11,VI11,VI10
+		ADDAx		ACC,VF15,VF00x		MFIR.w		VF05,VI11
+		MADDAx		ACC,VF12,VF01x		IADD		VI02,VI02,VI04
+		MADDAy		ACC,VF13,VF01y		IADDIU		VI07,VI06,0
+		MADDz		VF02,VF14,VF01z		IBNE		VI02,VI05,LoopRPC
+		CLIPw.xyz	VF03xyz,VF03w		SQ.xyzw		VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+		
+		
+		.endif
+		
+		
+		
+		.else
+		
+		; original version
+
+
+		.if 1
+		; unoptimised
+
+		FTOI15.w	VF05,VF00			NOP								; set VF05w=0x8000 (for ADC bit)
+
+LoopRPC:NOP								LQ			VF07,0(VI02)		; get tex coords
+		NOP								LQ.xyz		VF01,2(VI02)		; get vertex coords
+		NOP								NOP
+		NOP								NOP
+		MULw.xyz	VF08,VF07,VF07w		NOP								; homogeneous divide (str)/r
+		ITOF4.xyz	VF02,VF01			NOP								; vertex coords to float
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF02x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF02y		NOP								; row 1 view transform
+		MADDz		VF03,VF14,VF02z		NOP								; row 2 view transform
+		NOP								SQ.xyz		VF08,0(VI02)
+		NOP								IADD		VI02,VI02,VI04		; step dest ptr
+		NOP								NOP
+		MULA		ACC,VF10,VF03		DIV			Q,VF00w,VF03w		; inv viewport scale	; calc 1/w
+		MADDw		VF06,VF11,VF03w		NOP								; inv viewport offset
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF06xyz,VF06w		NOP								; generate outcodes
+		NOP								NOP
+		MULq.xyz	VF05,VF03,Q			NOP								; homogeneous divide (xyz)/w
+		NOP								NOP
+		NOP								FCAND		VI01,0x03FFFF		; test last 3 outcodes
+		NOP								IBNE		VI01,VI00,CullRPC	; cull if one was out
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopRPC	; loop
+		NOP								SQ.xyz		VF05,-1(VI02)		; store screen coords
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+CullRPC:NOP								IBNE		VI02,VI05,LoopRPC	; loop
+		NOP								SQ			VF05,-1(VI02)		; store screen coords
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		.else
+		; optimised
+		
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,2(VI02)
+		ITOF4.xyz	VF02,VF01			LQ.xyz		VF01,5(VI02)
+		MADDAx		ACC,VF12,VF02x		LQ			VF07,0(VI02)
+		MADDAy		ACC,VF13,VF02y		IADDIU		VI01,VI00,0x4000
+		MADDz		VF03,VF14,VF02z		IADDIU		VI01,VI01,0x4000
+		MULw.xyz	VF08,VF07,VF07w		MFIR.w		VF05,VI01
+		ITOF4.xyz	VF02,VF01			LQ			VF07,3(VI02)
+		MULA		ACC,VF10,VF03		NOP
+		MADDw		VF06,VF11,VF03w		DIV			Q,VF00w,VF03w
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		
+LoopRPC:MADDAy		ACC,VF13,VF02y		MOVE.xyz	VF04,VF03
+		CLIPw.xyz	VF06xyz,VF06w		LQ.xyz		VF01,8(VI02)
+		MADDz		VF03,VF14,VF02z		IADD		VI02,VI02,VI04
+		MULw.xyz	VF08,VF07,VF07w		SQ.xyz		VF08,-3(VI02)
+		MULq.xyz	VF05,VF04,Q			LQ			VF07,3(VI02)
+		ITOF4.xyz	VF02,VF01			FCAND		VI01,0x03FFFF
+		MULA		ACC,VF10,VF03		IBNE		VI01,VI00,CullRPC
+		MADDw		VF06,VF11,VF03w		DIV			Q,VF00w,VF03w
+		ADDAx		ACC,VF15,VF00x		IBNE		VI02,VI05,LoopRPC
+		MADDAx		ACC,VF12,VF02x		SQ.xyz		VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+CullRPC:ADDAx		ACC,VF15,VF00x		IBNE		VI02,VI05,LoopRPC
+		MADDAx		ACC,VF12,VF02x		SQ			VF05,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		.endif
+		
+		.endif
+
+
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; lighting (2 diffuse + ambient)
+
+Light1:
+		.if 0
+		; unoptimised version
+		
+		NOP								IADDIU		VI03,VI02,0
+		
+LoopL:	NOP								IADD		VI03,VI03,VI04
+		NOP								LQ.xyz		VF01,-3(VI03)
+		NOP								LQ.xyz		VF08,-2(VI03)
+		NOP								NOP
+		NOP								NOP
+		ITOF15.xyz	VF02,VF01			NOP
+		ITOF0.xyz	VF08,VF08			NOP
+		NOP								NOP
+		NOP								NOP
+		MULAx.xyz	ACC,VF20,VF02x		NOP
+		MADDAy.xyz	ACC,VF21,VF02y		NOP
+		MADDz.xyz	VF03,VF22,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MAXx.xyz	VF04,VF03,VF00x		NOP
+		NOP								NOP
+		NOP								NOP
+		ADDAx.xyz	ACC,VF23,VF00x		NOP
+		MADDAx.xyz	ACC,VF24,VF04x		NOP
+		MADDAy.xyz	ACC,VF25,VF04y		NOP
+		MADDz.xyz	VF05,VF26,VF04z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MUL.xyz		VF05,VF05,VF08		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								LOI			255
+		MINIi.xyz	VF06,VF05,I			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		FTOI0.xyz	VF07,VF06			NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopL
+		NOP								SQ.xyz		VF07,-2(VI02)
+		
+
+		.else
+		; optimised version
+		
+		NOP								IADD		VI03,VI02,VI04
+		NOP								LQ.xyz		VF02,-3(VI03)
+		ITOF15.xyz	VF02,VF02			LQ.xyz		VF06,-2(VI03)
+		MULAx.xyz	ACC,VF20,VF02x		IADD		VI03,VI03,VI04
+		MADDAy.xyz	ACC,VF21,VF02y		LQ.xyz		VF01,-3(VI03)
+		MADDz.xyz	VF02,VF22,VF02z		LQ.xyz		VF05,-2(VI03)
+		ITOF0.xyz	VF06,VF06			NOP
+		MAXx.xyz	VF02,VF02,VF00x		NOP
+		ADDAx.xyz	ACC,VF23,VF00x		NOP
+		ITOF15.xyz	VF01,VF01			NOP
+		MADDAx.xyz	ACC,VF24,VF02x		NOP
+		MADDAy.xyz	ACC,VF25,VF02y		NOP
+		MADDz.xyz	VF03,VF26,VF02z		NOP
+		MULAx.xyz	ACC,VF20,VF01x		NOP
+		MADDAy.xyz	ACC,VF21,VF01y		NOP
+		MADDz.xyz	VF02,VF22,VF01z		IADD		VI03,VI03,VI04
+		MUL.xyz		VF03,VF03,VF06		LQ.xyz		VF01,-3(VI03)
+		ITOF0.xyz	VF06,VF05			LQ.xyz		VF05,-2(VI03)
+		MAXx.xyz	VF02,VF02,VF00x		NOP
+		ADDAx.xyz	ACC,VF23,VF00x      LOI			255
+		
+LoopL:	ITOF15.xyz	VF01,VF01			NOP
+		MINIi.xyz	VF04,VF03,I			NOP
+		MADDAx.xyz	ACC,VF24,VF02x		NOP
+		MADDAy.xyz	ACC,VF25,VF02y		NOP
+		MADDz.xyz	VF03,VF26,VF02z		NOP
+		MULAx.xyz	ACC,VF20,VF01x		NOP
+		MADDAy.xyz	ACC,VF21,VF01y		NOP
+		MADDz.xyz	VF02,VF22,VF01z		IADD		VI03,VI03,VI04
+		FTOI0.xyz	VF04,VF04			IADD		VI02,VI02,VI04
+		MUL.xyz		VF03,VF03,VF06		LQ.xyz		VF01,-3(VI03)
+		ITOF0.xyz	VF06,VF05			LQ.xyz		VF05,-2(VI03)
+		MAXx.xyz	VF02,VF02,VF00x		IBNE		VI02,VI05,LoopL
+		ADDAx.xyz	ACC,VF23,VF00x		SQ.xyz		VF04,-2(VI02)
+		
+		.endif
+		
+		NOP								IADDIU		VI11,VI00,0x3000
+		NOP								IAND		VI11,VI01,VI11
+		
+		NOP								ISUBIU		VI08,VI00,COLR+1
+		NOP								IAND		VI14,VI14,VI08
+		
+		NOP								IBNE		VI10,VI00,Label2
+		NOP								ISUB		VI02,VI05,VI06
+		NOP								B			Label0
+		NOP								NOP
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; applying material colour
+
+ApplyColour:
+
+		.if 0
+		; unoptimised version
+		
+		NOP								IADDIU		VI03,VI02,0
+		
+LoopAC:	NOP								IADD		VI03,VI03,VI04
+		NOP								LQ.xyz		VF01,-2(VI03)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ITOF0.xyz	VF02,VF01			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MUL.xyz		VF03,VF02,VF16		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								LOI			255
+		MINIi.xyz	VF04,VF03,I			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		FTOI0.xyz	VF05,VF04			NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopAC
+		NOP								SQ.xyz		VF05,-2(VI02)
+		
+		.else
+		; optimised version
+		
+		NOP								IADD		VI03,VI02,VI04
+		NOP								LQ.xyz		VF01,-2(VI03)
+		ITOF0.xyz	VF02,VF01			IADD		VI03,VI03,VI04
+		MUL.xyz		VF03,VF02,VF16		LQ.xyz		VF01,-2(VI03)
+		ITOF0.xyz	VF02,VF01			LOI			255
+		MINIi.xyz	VF04,VF03,I			IADD		VI03,VI03,VI04
+		MUL.xyz		VF03,VF02,VF16		LQ.xyz		VF01,-2(VI03)
+		ITOF0.xyz	VF02,VF01			IADD		VI03,VI03,VI04
+		FTOI0.xyz	VF05,VF04			LQ.xyz		VF01,-2(VI03)
+		
+LoopAC:	MINIi.xyz	VF04,VF03,I			IADD		VI03,VI03,VI04
+		MUL.xyz		VF03,VF02,VF16		IADD		VI02,VI02,VI04
+		ITOF0.xyz	VF02,VF01			LQ.xyz		VF01,-2(VI03)
+		NOP								IBNE		VI02,VI05,LoopAC
+		FTOI0.xyz	VF05,VF04			SQ.xyz		VF05,-2(VI02)
+		
+		.endif
+
+		NOP								JR			VI01
+		NOP								ISUB		VI02,VI02,VI06
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+		
+SingleSided:
+
+		.if 0
+
+		NOP								IADDIU		VI03,VI02,0
+		ADDw.x		VF08,VF00,VF00w		NOP
+		FTOI15.w	VF08,VF00			NOP
+		
+Loop_SS:OPMULA.xyz	ACC,VF02,VF03		NOP
+		OPMSUB.xyz	VF04,VF03,VF02		NOP
+		ADDx		VF02,VF03,VF00x		NOP
+		NOP								IADD		VI03,VI03,VI04
+		NOP								LQ			VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ITOF4.xyz	VF03,VF01			MTIR		VI10,VF01w
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MUL.xyz		VF05,VF04,VF03		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ADDAy.x		ACC,VF05,VF05y		NOP
+		MADDz.x		VF00,VF08,VF05z		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								FMEQ		VI10,VI10
+		NOP								IBEQ		VI10,VI00,Cull_SS
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,Loop_SS
+		NOP								NOP
+		
+		NOP								JR			VI01
+		NOP								ISUB		VI02,VI02,VI06
+		
+Cull_SS:NOP								IBNE		VI02,VI05,Loop_SS
+		NOP								SQ.w		VF08,-1(VI02)
+		
+		NOP								JR			VI01
+		NOP								ISUB		VI02,VI02,VI06
+		
+		.else
+		
+		ADDw.x		VF08,VF00,VF00w		IADD		VI03,VI02,VI04
+		FTOI15.w	VF08,VF00			LQ			VF02,-1(VI03)
+		NOP								IADD		VI03,VI03,VI04
+		ITOF4.xyz	VF02,VF02			LQ			VF01,-1(VI03)
+		ITOF4		VF03,VF01			MTIR		VI10,VF02w
+		
+Loop_SS:FTOI4.w		VF03,VF03			IADD		VI03,VI03,VI04
+		MUL.xyz		VF05,VF04,VF03		LQ			VF01,-1(VI03)
+		OPMULA.xyz	ACC,VF02,VF03		IADD		VI02,VI02,VI04
+		OPMSUB.xyz	VF04,VF03,VF02		FMEQ		VI10,VI10
+		ADDx		VF02,VF03,VF00x		IBEQ		VI10,VI00,Cull_SS
+		ITOF4		VF03,VF01			MTIR		VI10,VF03w
+		ADDAy.x		ACC,VF05,VF05y		IBNE		VI02,VI05,Loop_SS
+		MADDz.x		VF00,VF08,VF05z		NOP
+		
+		NOP								JR			VI01
+		NOP								ISUB		VI02,VI02,VI06
+		
+Cull_SS:ADDAy.x		ACC,VF05,VF05y		IBNE		VI02,VI05,Loop_SS
+		MADDz.x		VF00,VF08,VF05z		SQ.w		VF08,-1(VI02)
+		
+		NOP								JR			VI01
+		NOP								ISUB		VI02,VI02,VI06
+		
+		.endif
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+; lines
+
+Line1:	NOP								IBEQ		VI03,VI00,Proj1+16
+		NOP								NOP
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; line culling
+
+CullLine:
+
+		.if 0
+		; unoptimised
+		
+		NOP								IADDIU		VI03,VI02,0			; source ptr = dest ptr
+		FTOI15.w	VF05,VF00			NOP								; set VF05w=0x8000 (for ADC bit)
+
+LoopKL:	NOP								IADD		VI03,VI03,VI04		; step source ptr
+		NOP								LQ.xyz		VF01,-1(VI03)		; get vertex coords
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		ITOF4.xyz	VF02,VF01			NOP								; vertex coords to float
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF02x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF02y		NOP								; row 1 view transform
+		MADDz		VF03,VF14,VF02z		NOP								; row 2 view transform
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULA		ACC,VF10,VF03		DIV			Q,VF00w,VF03w		; inv viewport scale	; calc 1/w
+		MADDw		VF06,VF11,VF03w		NOP								; inv viewport offset
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF06xyz,VF06w		NOP								; generate outcodes
+		NOP								NOP
+		MULq.xyz	VF05,VF03,Q			NOP								; homogeneous divide
+		NOP								IADD		VI02,VI02,VI04		; step dest ptr
+		NOP								FCAND		VI01,0x000FFF		; test last 3 outcodes
+		NOP								IBNE		VI01,VI00,CullKL		; cull if all out
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopKL	; loop
+		NOP								SQ.xyz		VF05,-1(VI02)		; store screen coords
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+CullKL:	NOP								IBNE		VI02,VI05,LoopKL	; loop
+		NOP								SQ			VF05,-1(VI02)		; store screen coords
+		
+		.else
+		; optimised
+		
+		FTOI15.w	VF05,VF00			IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF15,VF00x		LQ.xyz		VF01,-1(VI03)
+		ITOF4.xyz	VF02,VF01			IADD		VI03,VI03,VI04
+		MADDAx		ACC,VF12,VF02x		LQ.xyz		VF01,-1(VI03)
+		MADDAy		ACC,VF13,VF02y		IADD		VI03,VI03,VI04
+		MADDz		VF04,VF14,VF02z		NOP
+		ITOF4.xyz	VF02,VF01			LQ.xyz		VF01,-1(VI03)
+		MULA		ACC,VF10,VF04		DIV			Q,VF00w,VF04w
+		MADDw		VF06,VF11,VF04w		NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF02x		NOP
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		NOP
+		
+LoopKL:	CLIPw.xyz	VF06xyz,VF06w		IADD		VI03,VI03,VI04
+		MULq.xyz	VF05,VF04,Q			IADD		VI02,VI02,VI04
+		ITOF4.xyz	VF02,VF01			LQ.xyz		VF01,-1(VI03)
+		MULA		ACC,VF10,VF03		DIV			Q,VF00w,VF03w
+		MADDw		VF06,VF11,VF03w		FCAND		VI01,0x000FFF
+		ADDAx		ACC,VF15,VF00x		IBNE		VI01,VI00,CullKL
+		MADDAx		ACC,VF12,VF02x		MOVE.xyz	VF04,VF03
+		MADDAy		ACC,VF13,VF02y		IBNE		VI02,VI05,LoopKL
+		MADDz		VF03,VF14,VF02z		SQ.xyz		VF05,-1(VI02)
+		
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+CullKL:	MADDAy		ACC,VF13,VF02y		IBNE		VI02,VI05,LoopKL
+		MADDz		VF03,VF14,VF02z		SQ			VF05,-1(VI02)
+		
+		.endif
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+Shadow:
+		.if 0
+		; unoptimised version
+
+		NOP								IADDIU		VI03,VI02,0
+		NOP								LOI			0.5
+		MULi.w		VF04,VF00,I			NOP
+		NOP								MOVE.z		VF04,VF00
+		FTOI15.w	VF01,VF00			NOP
+		NOP								IADDIU		VI09,VI00,0x1F
+
+LoopSH:	NOP                             IADD		VI03,VI03,VI04
+		NOP								LQ.xyz		VF01,-1(VI03)
+		NOP								NOP
+		NOP								IAND		VI08,VI07,VI09
+		NOP								NOP
+		ITOF4.xyz	VF02,VF01			NOP
+		NOP								NOP
+		NOP								NOP
+		ADDAx.xy	ACC,VF27,VF00x		NOP
+		MADDx.x		VF03,VF16,VF02x		NOP
+		MADDz.y		VF03,VF16,VF02z		NOP
+		NOP								NOP
+		NOP								NOP
+		ADDy.z		VF04,VF27,VF02y		NOP
+		SUBw.xy		VF04,VF03,VF04w		NOP
+		FTOI12.xy	VF03,VF03			NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF04xyz,VF04w		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								SQ.xy		VF03,0(VI02)
+		NOP								FCGET		VI07
+		NOP								IAND		VI01,VI01,VI07
+		NOP								IADD		VI02,VI02,VI04
+		NOP								IBNE		VI01,VI00,CullSH
+		NOP								IAND		VI01,VI07,VI08
+		NOP								IBNE		VI02,VI05,LoopSH
+		NOP								NOP
+		
+		NOP								B			EndSH
+		NOP								NOP
+		
+CullSH:	NOP								IBNE		VI02,VI05,LoopSH
+		NOP								SQ.w		VF01,-1(VI02)
+		
+		.else
+		; optimised version
+		
+		FTOI15.w	VF01,VF00			IADD		VI03,VI02,VI04
+		ADDAx.xy	ACC,VF27,VF00x		LQ.xyz		VF01,-1(VI03)
+		ITOF4.xyz	VF02,VF01			IADD		VI03,VI03,VI04
+		MADDx.x		VF03,VF16,VF02x		LQ.xyz		VF01,-1(VI03)
+		MADDz.y		VF03,VF16,VF02z		LOI			0.5
+		MULi.w		VF04,VF00,I			IADDIU		VI09,VI00,0x1F
+		ADDy.z		VF04,VF27,VF02y		NOP
+		SUBw.xy		VF04,VF03,VF04w		NOP
+		FTOI12.xy	VF03,VF03			NOP
+		ITOF4.xyz	VF02,VF01			NOP
+		CLIPw.xyz	VF04xyz,VF04w       MOVE.z		VF04,VF00
+		
+LoopSH:	ADDAx.xy	ACC,VF27,VF00x		SQ.xy		VF03,0(VI02)
+		MADDx.x		VF03,VF16,VF02x		IAND		VI08,VI07,VI09
+		MADDz.y		VF03,VF16,VF02z		IADD		VI03,VI03,VI04
+		NOP								FCGET		VI07
+		NOP								LQ.xyz		VF01,-1(VI03)
+		ADDy.z		VF04,VF27,VF02y		IAND		VI01,VI01,VI07
+		SUBw.xy		VF04,VF03,VF04w		IADD		VI02,VI02,VI04
+		FTOI12.xy	VF03,VF03			IBEQ		VI01,VI00,KeepSH
+		ITOF4.xyz	VF02,VF01			IAND		VI01,VI07,VI08
+		NOP								IBNE		VI02,VI05,LoopSH
+		CLIPw.xyz	VF04xyz,VF04w		SQ.w		VF01,-1(VI02)
+		
+		NOP								B			EndSH
+		NOP								NOP
+		
+KeepSH:	CLIPw.xyz	VF00xyz,VF00w		IBNE		VI02,VI05,LoopSH
+		CLIPw.xyz	VF04xyz,VF04w		NOP
+		
+		.endif
+		
+EndSH:	NOP                             ISUBIU		VI14,VI14,SHDW
+		NOP								B			PTex1
+		NOP								ISUB		VI02,VI02,VI06
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+ReformatXforms:
+
+		NOP								LQ.xyz		VF01,3(VI00)
+		MULx.w		VF02,VF00,VF01x		XITOP		VI06
+		MULy.w		VF03,VF00,VF01y		IADDIU		VI01,VI00,0
+		
+LoopRT:	MULz.w		VF04,VF00,VF01z		LQ.xyz		VF01,7(VI01)
+		NOP								IADDIU		VI01,VI01,4
+		NOP								SQ.w		VF02,-4(VI01)
+		NOP								SQ.w		VF03,-3(VI01)
+		MULx.w		VF02,VF00,VF01x		IBNE		VI01,VI06,LoopRT
+		MULy.w		VF03,VF00,VF01y		SQ.w		VF04,-2(VI01)
+		
+		NOP[E]							NOP
+		NOP								NOP
+		
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+; vertex format
+; -------------
+; (s,t,1,?)
+; (w0,w1,w2,?)
+; (nx:to0, ny:to1, nz:to2, ?)
+; (r,g,b,a)
+; (x,y,z,adc)
+
+
+
+Skin1:
+		;---------------------------------------------------------------
+		; wireframe
+		
+		; 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 58 47
+		; s  x  x  x  x  x  x  x  x  m  m  m  m  m  m  m  m
+		;                   <-------------PRIM------------->
+		;                         1  0  0  0  0  0  0  1  0
+		
+		NOP								IADDIU		VI01,VI00,WIRE
+		NOP								IAND		VI01,VI14,VI01
+		NOP								NOP
+		NOP								IBEQ		VI01,VI00,SkipWireframe
+		NOP								NOP
+		
+		NOP								ISUBIU		VI14,VI14,WIRE
+		
+		NOP								LQ.y		VF01,-1(VI02)
+		NOP								RINIT		R,VF01y
+		NOP								RGET.y		VF02,R
+		NOP								DIV			Q,VF00w,VF02y
+		MULq.y		VF03,VF01,Q			WAITQ
+		ADDAx.y		ACC,VF01,VF00x		LOI			0.0078125
+		MSUBAi.y	ACC,VF03,I			LOI			0.0625
+		MSUBi.y		VF04,VF03,I			NOP
+		NOP								SQ.y		VF04,-1(VI02)
+
+SkipWireframe:
+		;---------------------------------------------------------------
+	
+		NOP								IBNE		VI14,VI00,CullSkin
+		NOP								IADDIU		VI01,VI00,SHDW
+		
+		
+		.if 0
+		; unoptimised
+
+LoopS:	NOP								LQ.xyz		VF02,2(VI02)		; get normal & offsets
+		NOP								LQ.xyz		VF03,1(VI02)		; get weights
+		NOP								NOP
+		NOP								NOP
+		NOP								MTIR		VI08,VF02x										; offset of M0
+		NOP								MTIR		VI09,VF02y										; offset of M1
+		ITOF15.xyz	VF02,VF02			MTIR		VI10,VF02z			; normal to float			; offset of M2
+		ITOF15.xyz	VF03,VF03			LQ			VF05,0(VI08)		; weights to float			; get M0 row 0
+		NOP								LQ			VF06,1(VI08)									; get M0 row 1
+		NOP								LQ			VF07,2(VI08)									; get M0 row 2
+		NOP								NOP
+		MULAx.xyz	ACC,VF05,VF02x		LQ			VF08,0(VI09)		; nx*(M0 row 0)				; get M1 row 0
+		MADDAy.xyz	ACC,VF06,VF02y		LQ			VF30,0(VI10)		; ny*(M0 row 1)				; get M2 row 0
+		MADDz.xyz	VF02,VF07,VF02z		NOP								; nz*(M0 row 2)
+		MULAx		ACC,VF05,VF03x		NOP								; row 0 w0*M0
+		MADDAy		ACC,VF08,VF03y		NOP								; add row 0 w1*M1
+		MADDz		VF05,VF30,VF03z		NOP								; add row 0 w2*M2
+		MULAx.xyz	ACC,VF20,VF02x		LQ			VF08,1(VI09)		; lighting dot prods x part	; get M1 row 1
+		MADDAy.xyz	ACC,VF21,VF02y		LQ			VF30,1(VI10)		; lighting dot prods y part	; get M2 row 1
+		MADDz.xyz	VF02,VF22,VF02z		NOP								; lighting dot prods z part
+		MULAx		ACC,VF06,VF03x		MR32.z		VF31,VF05			; row 1 w0*M0
+		MADDAy		ACC,VF08,VF03y		LQ.xyz		VF01,4(VI02)		; add row 1 w1*M1			; get xyz
+		MADDz		VF06,VF30,VF03z		LQ.xyz		VF29,3(VI02)		; add row 1 w2*M2			; get rgb
+		MAXx.xyz	VF02,VF02,VF00x		LQ			VF08,2(VI09)		; clamp dot prods at 0		; get M1 row 2
+		NOP								LQ			VF30,2(VI10)									; get M2 row 2
+		NOP								MR32.y		VF31,VF31
+		MULAx		ACC,VF07,VF03x		MR32.z		VF31,VF06			; row 2 w0*M0
+		MADDAy		ACC,VF08,VF03y		NOP								; add row 2 w1*M1
+		MADDz		VF07,VF30,VF03z		NOP								; add row 2 w2*M2
+		ITOF4.xyz	VF01,VF01			NOP
+		ITOF0.xyz	VF29,VF29			NOP
+		ADDAx.xyz	ACC,VF23,VF00x		NOP								; ambient colour
+		MADDAx.xyz	ACC,VF24,VF02x		MR32.xy		VF31,VF31			; add diffuse 0
+		MADDAy.xyz	ACC,VF25,VF02y		MR32.z		VF31,VF07			; add diffuse 1
+		MADDz.xyz	VF02,VF26,VF02z		NOP								; add diffuse 2
+		MULAx.xyz	ACC,VF05,VF01x		NOP								; add x*(M row 0)
+		MADDAy.xyz	ACC,VF06,VF01y		NOP								; add y*(M row 1)
+		MADDAz.xyz	ACC,VF07,VF01z		NOP								; add z*(M row 2)
+		MADDw.xyz	VF01,VF31,VF00w		NOP								; M row 3
+		ADDAw.xyz	ACC,VF00,VF00w		NOP
+		MADD.xyz	VF02,VF02,VF29		NOP
+		ADDAx		ACC,VF19,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF16,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF17,VF01y		NOP								; row 1 view transform
+		MADDz		VF01,VF18,VF01z		LOI			1.00003039837		; row 2 view transform
+		MINIi.xyz	VF02,VF02,I			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								DIV			Q,VF00w,VF01w
+		NOP								SQ.xyz		VF02,3(VI02)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULq.xyz	VF01,VF01,Q			NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopS
+		NOP								SQ.xyz		VF01,-1(VI02)
+		
+		.else
+		; optimised
+		
+		NOP								LQ.xyz		VF01,2(VI02)
+		NOP								LQ.xyz		VF03,1(VI02)
+		NOP								MTIR		VI08,VF01x
+		ITOF15.xyz	VF02,VF01			LQ			VF05,0(VI08)
+		ITOF15.xyz	VF03,VF03			LQ			VF06,1(VI08)
+		MULAx.xyz	ACC,VF05,VF02x		LQ			VF07,2(VI08)
+		MADDAy.xyz	ACC,VF06,VF02y		MTIR		VI09,VF01y
+		MADDz.xyz	VF02,VF07,VF02z		LQ			VF08,0(VI09)
+		MULAx		ACC,VF05,VF03x		MTIR		VI10,VF01z
+		MADDAy		ACC,VF08,VF03y		LQ			VF30,0(VI10)
+		MADDz		VF05,VF30,VF03z		LQ			VF08,1(VI09)
+		MULAx.xyz	ACC,VF20,VF02x		LQ			VF30,1(VI10)
+		MADDAy.xyz	ACC,VF21,VF02y		NOP
+		MADDz.xyz	VF02,VF22,VF02z		MR32.z		VF31,VF05
+		MULAx		ACC,VF06,VF03x		LQ			VF04,2(VI09)
+		MADDAy		ACC,VF08,VF03y		LQ			VF29,2(VI10)
+		MADDz		VF06,VF30,VF03z		LOI			0x3F8000FF										; I = 1+255/(2^23)
+
+
+LoopS:	MAXx.xyz	VF02,VF02,VF00x		LQ.xyz		VF01,4(VI02)		; clamp dot prods at 0		; get vertex
+		MULAx		ACC,VF07,VF03x		NOP								;  row 2 M0*w0
+		MADDAy		ACC,VF04,VF03y		MR32.y		VF31,VF31			; +row 2 M1*w1				; VF31 = (?,(M)30,?)
+		MADDz		VF07,VF29,VF03z		MR32.z		VF31,VF06			; +row 2 M2*w2				; VF31 = (?,(M)30,(M)31)
+		ITOF4.xyz	VF01,VF01			LQ.xyz		VF29,3(VI02)		; vertex to float			; get rgb
+		ADDAx.xyz	ACC,VF23,VF00x		NOP								;  ambient
+		MADDAx.xyz	ACC,VF24,VF02x		NOP								; +diffuse 0
+		MADDAy.xyz	ACC,VF25,VF02y		MR32.xy		VF31,VF31			; +diffuse 1				; VF31 = ((M)30,(M)31,?)
+		MADDz.xyz	VF04,VF26,VF02z		NOP								; +diffuse 2
+		ITOF0.xyz	VF29,VF29			MR32.z		VF31,VF07			; rgb to float				; VF31 = M row 3
+		MULAx.xyz	ACC,VF05,VF01x		LQ.xyz		VF02,7(VI02)		;  x * (M row 0)			; get normal and offsets
+		MADDAy.xyz	ACC,VF06,VF01y		LQ.xyz		VF03,6(VI02)		; +y * (M row 1)			; get weights
+		MADDAz.xyz	ACC,VF07,VF01z		NOP								; +z * (M row 2)
+		MADDw.xyz	VF01,VF31,VF00w		NOP								; +1 * (M row 3)
+		ADDAw.xyz	ACC,VF00,VF00w		MTIR		VI08,VF02x			;  (1,1,1)					; offset of M0
+		MADD.xyz	VF04,VF04,VF29		LQ			VF05,0(VI08)		; +illum * rgb				; get M0 row 0
+		ADDAx		ACC,VF19,VF00x		LQ			VF06,1(VI08)		; row 3 view transform		; get M0 row 1
+		MADDAx		ACC,VF16,VF01x		MTIR		VI09,VF02y			; row 0 view transform		; offset of M1
+		ITOF15.xyz	VF02,VF02			MTIR		VI10,VF02z			; normal to float			; offset of M2
+		MADDAy		ACC,VF17,VF01y		LQ			VF07,2(VI08)		; row 1 view transform		; get M0 row 2
+		MADDz		VF01,VF18,VF01z		LQ			VF08,0(VI09)		; row 2 view transform		; get M1 row 0
+		MINIi.xyz	VF04,VF04,I			NOP								; clamp rgb at 255
+		ITOF15.xyz	VF03,VF03			NOP								; weights to float
+		MULAx.xyz	ACC,VF05,VF02x		LQ			VF30,0(VI10)		;  nx * (M0 row 0)			; get M2 row 0
+		MADDAy.xyz	ACC,VF06,VF02y		DIV			Q,VF00w,VF01w		; +ny * (M0 row 1)			; calc 1/w
+		MADDz.xyz	VF02,VF07,VF02z		SQ.xyz		VF04,3(VI02)		; +nz * (M0 row 2)			; store colour
+		MULAx		ACC,VF05,VF03x		NOP								;  row 0 M0*w0
+		MADDAy		ACC,VF08,VF03y		LQ			VF08,1(VI09)		; +row 0 M1*w1				; get M1 row 1
+		MADDz		VF05,VF30,VF03z		LQ			VF30,1(VI10)		; +row 0 M2*w2				; get M2 row 1
+		MULAx.xyz	ACC,VF20,VF02x		NOP								; lighting dot prods x part
+		MADDAy.xyz	ACC,VF21,VF02y		IADD		VI02,VI02,VI04		; lighting dot prods y part	; step pointer
+		MULq.xyz	VF01,VF01,Q			LQ			VF04,2(VI09)		; homogeneous div (xyz)/w	; get M1 row 2
+		MADDz.xyz	VF02,VF22,VF02z		LQ			VF29,2(VI10)		; lighting dot prods z part	; get M2 row 2
+		MULAx		ACC,VF06,VF03x		MR32.z		VF31,VF05			;  row 1 M0*w0				; VF31 = (?,?,(M)30)
+		MADDAy		ACC,VF08,VF03y		IBNE		VI02,VI05,LoopS		; +row 1 M1*w1				; loop
+		MADDz		VF06,VF30,VF03z		SQ.xyz		VF01,-1(VI02)		; +row 1 M2*w2				; store screen coords
+		
+		.endif
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+		
+		
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+CullSkin:
+		NOP								NOP								; need this cycle here
+		NOP								IBEQ		VI14,VI01,ShadowSkin
+		NOP								NOP
+		
+		; new version
+		
+		.if 0
+		; unoptimised
+		
+		FTOI15.w	VF03,VF00			NOP
+		
+		
+LoopKS:	NOP								LQ.xyz		VF02,2(VI02)		; get normal & offsets
+		NOP								LQ.xyz		VF03,1(VI02)		; get weights
+		NOP								NOP
+		NOP								NOP
+		NOP								MTIR		VI08,VF02x										; offset of M0
+		NOP								MTIR		VI09,VF02y										; offset of M1
+		ITOF15.xyz	VF02,VF02			MTIR		VI10,VF02z			; normal to float			; offset of M2
+		ITOF15.xyz	VF03,VF03			LQ			VF05,0(VI08)		; weights to float			; get M0 row 0
+		NOP								LQ			VF06,1(VI08)									; get M0 row 1
+		NOP								LQ			VF07,2(VI08)									; get M0 row 2
+		NOP								NOP
+		MULAx.xyz	ACC,VF05,VF02x		LQ			VF08,0(VI09)		; nx*(M0 row 0)				; get M1 row 0
+		MADDAy.xyz	ACC,VF06,VF02y		LQ			VF30,0(VI10)		; ny*(M0 row 1)				; get M2 row 0
+		MADDz.xyz	VF02,VF07,VF02z		NOP								; nz*(M0 row 2)
+		MULAx		ACC,VF05,VF03x		NOP								; row 0 w0*M0
+		MADDAy		ACC,VF08,VF03y		NOP								; add row 0 w1*M1
+		MADDz		VF05,VF30,VF03z		NOP								; add row 0 w2*M2
+		MULAx.xyz	ACC,VF20,VF02x		LQ			VF08,1(VI09)		; lighting dot prods x part	; get M1 row 1
+		MADDAy.xyz	ACC,VF21,VF02y		LQ			VF30,1(VI10)		; lighting dot prods y part	; get M2 row 1
+		MADDz.xyz	VF02,VF22,VF02z		NOP								; lighting dot prods z part
+		MULAx		ACC,VF06,VF03x		MR32.z		VF31,VF05			; row 1 w0*M0
+		MADDAy		ACC,VF08,VF03y		LQ.xyz		VF01,4(VI02)									; add row 1 w1*M1
+		MADDz		VF06,VF30,VF03z		LQ.xyz		VF29,3(VI02)									; add row 1 w2*M2
+		MAXx.xyz	VF02,VF02,VF00x		LQ			VF08,2(VI09)		; clamp dot prods at 0		; get M1 row 2
+		NOP								LQ			VF30,2(VI10)									; get M2 row 2
+		NOP								NOP
+		MULAx		ACC,VF07,VF03x		NOP
+		MADDAy		ACC,VF08,VF03y		MR32.y		VF31,VF31
+		MADDz		VF07,VF30,VF03z		MR32.z		VF31,VF06
+		ITOF4.xyz	VF01,VF01			NOP
+		ITOF0.xyz	VF29,VF29			NOP
+		ADDAx.xyz	ACC,VF23,VF00x		NOP								; ambient colour
+		MADDAx.xyz	ACC,VF24,VF02x		MR32.xy		VF31,VF31			; add diffuse 0
+		MADDAy.xyz	ACC,VF25,VF02y		MR32.z		VF31,VF07			; add diffuse 1
+		MADDz.xyz	VF02,VF26,VF02z		NOP								; add diffuse 2
+		MULAx.xyz	ACC,VF05,VF01x		NOP								; add x*(M row 0)
+		MADDAy.xyz	ACC,VF06,VF01y		NOP								; add y*(M row 1)
+		MADDAz.xyz	ACC,VF07,VF01z		NOP								; add z*(M row 2)
+		MADDw.xyz	VF01,VF31,VF00w		NOP								; M row 3
+		ADDAw.xyz	ACC,VF00,VF00w		NOP
+		MADD.xyz	VF02,VF02,VF29		NOP
+		ADDAx		ACC,VF19,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF16,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF17,VF01y		NOP								; row 1 view transform
+		MADDz		VF01,VF18,VF01z		LOI			1.00003039837		; row 2 view transform
+		MINIi.xyz	VF02,VF02,I			NOP
+		NOP								NOP
+		NOP								NOP
+		MULA		ACC,VF10,VF01		DIV			Q,VF00w,VF01w
+		MADDw		VF05,VF11,VF01w		SQ.xyz		VF02,3(VI02)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF05xyz,VF05w		NOP
+		NOP								NOP
+		MULq.xyz	VF03,VF01,Q			NOP
+		NOP								IADD		VI02,VI02,VI04
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								IBNE		VI01,VI00,CullKS
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopKS
+		NOP								SQ.xyz		VF03,-1(VI02)
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullKS:	NOP								IBNE		VI02,VI05,LoopKS
+		NOP								SQ			VF03,-1(VI02)
+		
+		.else
+		; optimised
+
+		FTOI15.w	VF04,VF00			LQ.xyz		VF01,2(VI02)		; set ADC bit
+		NOP								LQ.xyz		VF03,1(VI02)
+		NOP								MTIR		VI08,VF01x
+		NOP								LQ			VF05,0(VI08)
+		ITOF15.xyz	VF02,VF01			LQ			VF06,1(VI08)
+		ITOF15.xyz	VF03,VF03			LQ			VF07,2(VI08)
+		MULAx.xyz	ACC,VF05,VF02x		MTIR		VI09,VF01y
+		MADDAy.xyz	ACC,VF06,VF02y		MTIR		VI10,VF01z
+		MADDz.xyz	VF02,VF07,VF02z		LQ			VF08,0(VI09)
+		MULAx		ACC,VF05,VF03x		LQ			VF30,0(VI10)
+		MADDAy		ACC,VF08,VF03y		NOP
+		MADDz		VF05,VF30,VF03z		LQ			VF31,1(VI09)
+		MULAx.xyz	ACC,VF20,VF02x		LQ			VF29,1(VI10)
+		MADDAy.xyz	ACC,VF21,VF02y		LOI			0x3F8000FF										; I = 1+255/(2^23)
+		MADDz.xyz	VF02,VF22,VF02z		LQ			VF08,2(VI09)
+		MULAx		ACC,VF06,VF03x		LQ			VF30,2(VI10)
+		
+		
+LoopKS:	MADDAy		ACC,VF31,VF03y		MR32.z		VF31,VF05			; +row 1 M1*w1				; VF31 = (?,?,(M)30)
+		MADDz		VF06,VF29,VF03z		NOP								; +row 1 M2*w2
+		MAXx.xyz	VF02,VF02,VF00x		LQ.xyz		VF01,4(VI02)		; clamp dot prods at 0		; get vertex
+		MULAx		ACC,VF07,VF03x		LQ.xyz		VF29,3(VI02)		;  row 2 M0*w0				; get rgb
+		MADDAy		ACC,VF08,VF03y		MR32.y		VF31,VF31			; +row 2 M1*w1				; VF31 = (?,(M)30,?)
+		MADDz		VF07,VF30,VF03z		MR32.z		VF31,VF06			; +row 2 M2*w2				; VF31 = (?,(M)30,(M)31)
+		ITOF4.xyz	VF01,VF01			NOP								; vertex to float
+		ITOF0.xyz	VF29,VF29			NOP								; rgb to float
+		ADDAx.xyz	ACC,VF23,VF00x		NOP								; ambient
+		MADDAx.xyz	ACC,VF24,VF02x		MR32.xy		VF31,VF31			; +diffuse 0				; VF31 = ((M)30,(M)31,?)
+		MADDAy.xyz	ACC,VF25,VF02y		MR32.z		VF31,VF07			; +diffuse 1				; VF31 = M row 3
+		MADDz.xyz	VF04,VF26,VF02z		NOP								; +diffuse 2
+		MULAx.xyz	ACC,VF05,VF01x		LQ.xyz		VF02,7(VI02)		;  x * (M row 0)			; get normal and offsets
+		MADDAy.xyz	ACC,VF06,VF01y		NOP								; +y * (M row 1)
+		MADDAz.xyz	ACC,VF07,VF01z		NOP								; +z * (M row 2)
+		MADDw.xyz	VF01,VF31,VF00w		LQ.xyz		VF03,6(VI02)		; +1 * (M row 3)			; get weights
+		ADDAw.xyz	ACC,VF00,VF00w		NOP								;  (1,1,1)
+		MADD.xyz	VF04,VF04,VF29		MTIR		VI08,VF02x			; +illum * rgb				; offset of M0
+		ADDAx		ACC,VF19,VF00x		LQ			VF05,0(VI08)		; row 3 view transform		; get M0 row 0
+		MADDAx		ACC,VF16,VF01x		LQ			VF06,1(VI08)		; row 0 view transform		; get M0 row 1
+		MADDAy		ACC,VF17,VF01y		MTIR		VI09,VF02y			; row 1 view transform		; offset of M1
+		MADDz		VF01,VF18,VF01z		MTIR		VI10,VF02z			; row 2 view transform		; offset of M2
+		MINIi.xyz	VF04,VF04,I			LQ			VF07,2(VI08)		; clamp rgb at 255			; get M0 row 2
+		ITOF15.xyz	VF02,VF02			LQ			VF08,0(VI09)		; normal to float			; get M1 row 0
+		ITOF15.xyz	VF03,VF03			NOP								; weights to float
+		MULA		ACC,VF10,VF01		DIV			Q,VF00w,VF01w		; inverse viewport scale	; calc 1/w
+		MADDw		VF31,VF11,VF01w		SQ.xyz		VF04,3(VI02)		; inverse viewport offset	; store colour
+		MULAx.xyz	ACC,VF05,VF02x		LQ			VF30,0(VI10)		;  nx * (M0 row 0)			; get M2 row 0
+		MADDAy.xyz	ACC,VF06,VF02y		NOP								; +ny * (M0 row 1)
+		MADDz.xyz	VF02,VF07,VF02z		NOP								; +nz * (M0 row 2)
+		CLIPw.xyz	VF31xyz,VF31w		LQ			VF31,1(VI09)		; generate clip codes		; get M1 row 1
+		MULAx		ACC,VF05,VF03x		LQ			VF29,1(VI10)		;  row 0 M0*w0				; get M2 row 1
+		MADDAy		ACC,VF08,VF03y		IADD		VI02,VI02,VI04		; +row 0 M1*w1				; step pointer
+		MADDz		VF05,VF30,VF03z		LQ			VF08,2(VI09)		; +row 0 M2*w2				; get M1 row 2
+		MULq.xyz	VF04,VF01,Q			FCAND		VI01,0x03FFFF		; homogeneous div (xyz)/w	; last 3 clip codes
+		MULAx.xyz	ACC,VF20,VF02x		IBNE		VI01,VI00,CullKS	; lighting dot prods x part	; cull if any non-zero
+		MADDAy.xyz	ACC,VF21,VF02y		LQ			VF30,2(VI10)		; lighting dot prods y part	; get M2 row 2
+		MADDz.xyz	VF02,VF22,VF02z		IBNE		VI02,VI05,LoopKS	; lighting dot prods z part	; loop
+		MULAx		ACC,VF06,VF03x		SQ.xyz		VF04,-1(VI02)		;  row 1 M0*w0				; store screen coords
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+CullKS:	MADDz.xyz	VF02,VF22,VF02z		IBNE		VI02,VI05,LoopKS	; lighting dot prods z part ; loop
+		MULAx		ACC,VF06,VF03x		SQ			VF04,-1(VI02)       ;  row 1 M0*w0				; store screen coords
+		
+		.endif
+		
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; shadow version
+
+ShadowSkin:
+
+		.if 0
+		; unoptimised
+		
+LoopSS:
+		NOP								ILW.x		VI08,2(VI02)		; get primary transform offset
+		NOP								LQ.xyz		VF01,4(VI02)		; get vertex coords
+		ITOF4.xyz	VF01,VF01			NOP								; vertex coords to float
+		NOP								LQ			VF04,0(VI08)		; get M0 row 0
+		NOP								LQ			VF05,1(VI08)        ; get M0 row 1
+		NOP								LQ			VF06,2(VI08)		; get M0 row 2
+		ADDAw.x		ACC,VF00,VF04w		NOP								; ACCx = (M0)30
+		ADDAw.y		ACC,VF00,VF05w		NOP								; ACCy = (M0)31
+		ADDAw.z		ACC,VF00,VF06w		NOP								; ACCz = (M0)32
+		MADDAx.xyz	ACC,VF04,VF01x		NOP								; +x * M0 row 0
+		MADDAy.xyz	ACC,VF05,VF01y		NOP								; +y * M0 row 1
+		MADDz.xyz	VF01,VF06,VF01z		NOP								; +z * M0 row 2
+		NOP								IADD		VI02,VI02,VI04		; step pointer
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF01y		NOP								; row 1 view transform
+		MADDz		VF01,VF14,VF01z		NOP								; row 2 view transform
+		NOP								DIV			Q,VF00w,VF01w		; calc 1/w
+		MULq.xyz	VF01,VF01,Q			WAITQ							; homogeneous div (xyz)/w
+		NOP								IBNE		VI02,VI05,LoopSS	; loop
+		NOP								SQ.xyz		VF01,-1(VI02)		; store screen coords
+		
+		.else
+		; optimised
+		
+		NOP								ILW.x		VI08,2(VI02)
+		NOP								LQ			VF01,4(VI02)
+		ITOF4.xyz	VF02,VF01			LQ			VF08,3(VI08)
+		ADDAx.xyz	ACC,VF08,VF00		LQ			VF05,0(VI08)
+		MADDAx.xyz	ACC,VF05,VF02x		LQ			VF06,1(VI08)
+		MADDAy.xyz	ACC,VF06,VF02y		LQ			VF07,2(VI08)
+		MADDz.xyz	VF02,VF07,VF02z		ILW.x		VI08,7(VI02)
+		ADDAx		ACC,VF15,VF00x		LQ			VF01,9(VI02)
+		MADDAx		ACC,VF12,VF02x		LQ			VF05,0(VI08)
+		MADDAy		ACC,VF13,VF02y		LQ			VF06,1(VI08)
+		MADDz		VF03,VF14,VF02z		LQ			VF07,2(VI08)
+		
+LoopSS:	ITOF4.xyz	VF02,VF01			ILW.x		VI08,12(VI02)
+		ADDAw.x		ACC,VF00,VF05w		IADD		VI02,VI02,VI04
+		ADDAw.y		ACC,VF00,VF06w		DIV			Q,VF00w,VF03w
+		ADDAw.z		ACC,VF00,VF07w		NOP
+		MADDAx.xyz	ACC,VF05,VF02x		NOP
+		MADDAy.xyz	ACC,VF06,VF02y		LQ			VF01,9(VI02)
+		MADDz.xyz	VF02,VF07,VF02z		LQ			VF05,0(VI08)
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP
+		MULq.xyz	VF04,VF03,Q			LQ			VF06,1(VI08)
+		MADDAx		ACC,VF12,VF02x		LQ			VF07,2(VI08)
+		MADDAy		ACC,VF13,VF02y		NOP
+		MADDz		VF03,VF14,VF02z		IBNE		VI02,VI05,LoopSS
+		NOP								SQ.xyz		VF04,-1(VI02)
+		
+		.endif
+
+		NOP								B			NextPrim
+		NOP								LQI			VF01,(VI02++)
+
+
+
+
+
+
+
+
+
+
+
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+
+		; context data:
+		
+		; VF14 = shadow vec (magnitude defines length of shadow polys), in body coords
+		; VF15 = tweak vec (small, parallel to shadow vec), in body coords
+		; VF16-19 = body to frustum transform
+
+
+		
+		;      3-----0-----5
+		;		\   / \   /
+		;        \ /   \ /
+		;         1-----2
+		;          \   /
+		;			\ /
+		;            4
+
+
+ShadowVolumeSkin:
+
+		; set up VF07 = VF14x*VF16 + VF14y*VF17 + VF14z*VF18 + VF19
+		MULAx		ACC,VF16,VF14x		NOP
+		MADDAy		ACC,VF17,VF14y		NOP
+		MADDAz		ACC,VF18,VF14z		NOP
+		MADDw		VF07,VF19,VF00w		NOP
+		
+		; set up VF08 = VF15x*VF16 + VF15y*VF17 + VF15z*VF18 + VF19
+		MULAx		ACC,VF16,VF15x		NOP
+		MADDAy		ACC,VF17,VF15y		NOP
+		MADDAz		ACC,VF18,VF15z		NOP
+		MADDw		VF08,VF19,VF00w		IADDIU		VI09,VI00,0x10		; mask for Sw FMAC flag
+
+		NOP								IADDIU		VI08, VI00, 0
+
+LoopSVS:
+
+		NOP								LQ			VF20,1(VI02)		; | Load vector 0
+		NOP								LQ			VF21,2(VI02)        ; | Load vector 1 
+		NOP								LQ			VF22,3(VI02)        ; | Load vector 2 
+		NOP								LQ			VF23,4(VI02)        ; | Load vector 3 
+		NOP								LQ			VF24,5(VI02)        ; | Load vector 4 
+		
+		NOP								MTIR		VI01,VF20w			; | Load up matrix 0 index
+		NOP								LQ			VF04,3(VI01)
+		NOP								LQ			VF01,0(VI01)
+		NOP								LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		; v0
+		ADDAx.xyz	ACC,VF04,VF00x		MTIR		VI01,VF21w          ; | Load up matrix 1 index 
+		MADDAx.xyz	ACC,VF01,VF20x		LQ			VF04,3(VI01)
+		MADDAy.xyz	ACC,VF02,VF20y		LQ			VF01,0(VI01)
+		MADDz.xyz	VF20,VF03,VF20z		LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		; v1
+		ADDAx.xyz	ACC,VF04,VF00x		MTIR		VI01,VF22w          ; | Load up matrix 2 index 
+		MADDAx.xyz	ACC,VF01,VF21x		LQ			VF04,3(VI01)
+		MADDAy.xyz	ACC,VF02,VF21y		LQ			VF01,0(VI01)
+		MADDz.xyz	VF21,VF03,VF21z		LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		; v2
+		ADDAx.xyz	ACC,VF04,VF00x		MTIR		VI01,VF23w          ; | Load up matrix 3 index 
+		MADDAx.xyz	ACC,VF01,VF22x		LQ			VF04,3(VI01)
+		MADDAy.xyz	ACC,VF02,VF22y		LQ			VF01,0(VI01)
+		MADDz.xyz	VF22,VF03,VF22z		LQ			VF02,1(VI01)
+		SUB.xyz		VF26,VF21,VF20      LQ			VF03,2(VI01)
+
+		; v3
+		ADDAx.xyz	ACC,VF04,VF00x		LQ			VF25,6(VI02)        ; | Load vector 5
+		MADDAx.xyz	ACC,VF01,VF23x		MTIR		VI01,VF24w          ; | Load up matrix 4 index 
+		SUB.xyz		VF27,VF22,VF20		LQ			VF04,3(VI01)
+		MADDAy.xyz	ACC,VF02,VF23y		LQ			VF01,0(VI01)
+		MADDz.xyz	VF23,VF03,VF23z		LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		; v4
+		OPMULA.xyz	ACC,VF26,VF27		NOP
+		OPMSUB.xyz	VF05,VF27,VF26		NOP
+		ADDAx.xyz	ACC,VF04,VF00x		MTIR		VI01,VF25w          ; | Load up matrix 5 index 
+		MADDAx.xyz	ACC,VF01,VF24x		LQ			VF04,3(VI01)
+		MADDAy.xyz	ACC,VF02,VF24y		LQ			VF01,0(VI01)
+		MUL.xyz		VF05,VF05,VF15		LQ			VF02,1(VI01)
+		MADDz.xyz	VF24,VF03,VF24z		LQ			VF03,2(VI01)
+		
+		; v5
+		ADDAx.xyz	ACC,VF04,VF00x		NOP
+		MADDAx.xyz	ACC,VF01,VF25x		NOP
+		ADDy.x		VF05,VF05,VF05y		NOP
+		MADDAy.xyz	ACC,VF02,VF25y		NOP
+		MADDz.xyz	VF25,VF03,VF25z		NOP
+		
+											   
+		SUB.xyz		VF02,VF21,VF23		NOP
+		ADDz.x		VF00,VF05,VF05z		NOP
+		SUB.xyz		VF06,VF22,VF24		NOP
+		SUB.xyz		VF05,VF21,VF24		NOP
+		OPMULA.xyz	ACC,VF02,VF26		ILW.w		VI15,0(VI02)
+		SUB.xyz		VF04,VF25,VF20		FSAND		VI01,2
+		OPMSUB.xyz	VF02,VF26,VF02		IBEQ		VI01,VI00,CullPrism
+		OPMULA.xyz	ACC,VF05,VF06		NOP
+		OPMSUB.xyz	VF03,VF06,VF05		NOP
+		OPMULA.xyz	ACC,VF04,VF27		NOP
+		OPMSUB.xyz	VF04,VF27,VF04		NOP
+
+		; compute and project v0,v1,v2 and their translates
+		MULAx		ACC,VF16,VF22x		NOP
+		MADDAy		ACC,VF17,VF22y		NOP
+		MADDAz		ACC,VF18,VF22z		NOP
+		MADDw		VF22,VF08,VF00w		NOP
+		MADDw		VF25,VF07,VF00w		NOP
+		
+		
+		MULA		ACC,VF10,VF22		NOP
+		MADDw		VF12,VF11,VF22w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF12xyz,VF12w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x00003F
+		NOP								IBNE		VI01,VI00,CullPrism
+		NOP								NOP
+		
+		
+		MULAx		ACC,VF16,VF21x		NOP
+		MADDAy		ACC,VF17,VF21y		NOP
+		MADDAz		ACC,VF18,VF21z		ERCPR		P,VF22w
+		MADDw		VF21,VF08,VF00w		DIV			Q,VF00w,VF25w
+		MADDw		VF24,VF07,VF00w		NOP
+		
+		
+		
+		MULAx		ACC,VF16,VF20x		MFIR.w      VF22,VI00
+		MADDAy		ACC,VF17,VF20y		NOP
+		MADDAz		ACC,VF18,VF20z		NOP
+		MADDw		VF20,VF08,VF00w		NOP
+		MADDw		VF23,VF07,VF00w		NOP
+
+		MULA		ACC,VF10,VF24		NOP
+		MADDw		VF01,VF11,VF24w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF01xyz,VF01w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x00003F
+		NOP								IBNE		VI01,VI00,CullPrism
+		NOP								NOP
+
+		
+		MULA		ACC,VF10,VF25		NOP
+		MADDw		VF12,VF11,VF25w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF12xyz,VF12w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x00003F
+		NOP								IBNE		VI01,VI00,CullPrism
+		NOP								NOP
+
+
+		MULA		ACC,VF10,VF21		NOP
+		MADDw		VF12,VF11,VF21w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF12xyz,VF12w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x00003F
+		NOP								IBNE		VI01,VI00,CullPrism
+		NOP								NOP
+		
+		
+		MULq.xyz	VF25,VF25,Q			DIV			Q,VF00w,VF21w
+		MUL.xyz		VF02,VF02,VF15		NOP
+		MUL.xyz		VF03,VF03,VF15		NOP
+		MUL.xyz		VF04,VF04,VF15		NOP
+		FTOI15.w	VF21,VF00			MFP.w		VF01,P
+		ADDy.x		VF02,VF02,VF02y		ERCPR		P,VF24w
+		ADDx.y		VF03,VF03,VF03x		SQ.xyz		VF25,6(VI02)
+		
+		MULA		ACC,VF10,VF23		NOP
+		MADDw		VF12,VF11,VF23w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF12xyz,VF12w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x00003F
+		NOP								IBNE		VI01,VI00,CullPrism
+		NOP								NOP
+		
+		
+		
+		; vf20-25 = verts.	 
+		
+		MULA		ACC,VF10,VF20		NOP
+		MADDw		VF12,VF11,VF20w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF12xyz,VF12w		NOP
+		NOP								NOP			; needs to be 3 nops
+		NOP								NOP
+		NOP								NOP
+		NOP								FCAND		VI01,0x00003F
+		NOP								IBNE		VI01,VI00,CullPrism
+		NOP								NOP
+		
+
+		
+		MULq.xyz	VF21,VF21,Q			DIV			Q,VF00w,VF20w
+		
+		
+		
+		
+		
+		
+		
+		MULw.xyz	VF22,VF22,VF01w		SQ.w		VF21,2(VI02)
+		ADDx.z		VF04,VF04,VF04x		NOP
+		MULx.w		VF05,VF00,VF25x		NOP
+		ADDz.x		VF02,VF02,VF02z		SQ			VF21,4(VI02)
+		ADDz.y		VF03,VF03,VF03z		SQ			VF22,16(VI02)
+		ADDy.z		VF04,VF04,VF04y		SQ.xyz		VF22,2(VI02)
+		MULq.xyz	VF20,VF20,Q			DIV			Q,VF00w,VF23w
+		SUB.w		VF23,VF00,VF00		IADDIU      VI10, VI00, 0	;FSAND		VI10,2			; get adc results (4 cycles after mac)
+		ADDy.x		VF05,VF00,VF25y		IADDIU      VI11, VI00, 0  ;FSAND		VI11,2
+		MULx.w		VF22,VF00,VF22x		IADDIU      VI12, VI00, 0  ;FSAND		VI12,2
+		ADDy.x		VF22,VF00,VF22y		MFP.w		VF01,P
+		MULx.w		VF26,VF00,VF21x		NOP	;ISUBIU		VI10,VI10,1
+		ADDy.x		VF26,VF00,VF21y		NOP ;ISUBIU		VI11,VI11,1
+
+		MULq.xyz	VF23,VF23,Q			NOP ;ISUBIU		VI12,VI12,1
+		
+;		SUB.w		VF23,VF00,VF00		FSAND		VI10,2			; get adc results (4 cycles after mac)
+;		ADDy.x		VF05,VF00,VF25y		FSAND		VI11,2
+;		MULx.w		VF22,VF00,VF22x		FSAND		VI12,2
+;		ADDy.x		VF22,VF00,VF22y		MFP.w		VF01,P
+;		MULx.w		VF26,VF00,VF21x		ISUBIU		VI10,VI10,1
+;		ADDy.x		VF26,VF00,VF21y		ISUBIU		VI11,VI11,1
+;		
+;		MULq.xyz	VF23,VF23,Q			ISUBIU		VI12,VI12,1
+		MULw.xyz	VF24,VF24,VF01w		SQ.xyz		VF21,12(VI02)
+		
+		NOP								SQ			VF23,10(VI02)
+		NOP								MFIR.w		VF23,VI12
+		NOP								ISW.w		VI10,12(VI02)
+		NOP								ISW.w		VI11,6(VI02)
+
+		; backface testing and colours (8 tests)
+		SUB.xw		VF01,VF22,VF26		MR32.xw		VF04,VF24
+		SUB.xw		VF02,VF05,VF26		MFIR.w		VF20,VI10
+		NOP								MFIR.w		VF24,VI11
+		NOP								MR32.xw		VF06,VF23
+		
+		
+		
+		
+		SUB.xw		VF03,VF05,VF04		MFIR.w		VF25,VI12
+		MULAx.w		ACC,VF01,VF02x		SQ			VF23,18(VI02)
+		MSUBx.w		VF00,VF02,VF01x		SQ			VF20,14(VI02)
+
+		SUB.xw		VF01,VF06,VF04		MR32.xw		VF20,VF20
+		MULAx.w		ACC,VF03,VF02x		SQ			VF24,8(VI02)
+		MSUBx.w		VF00,VF02,VF03x		SQ			VF25,20(VI02)
+
+		SUB.xw		VF02,VF06,VF26		FMAND		VI01,VI09
+		MULAx.w		ACC,VF03,VF01x		ISUBIU		VI01,VI01,8
+		MSUBx.w		VF00,VF01,VF03x		ISW.xyz		VI01,5(VI02)
+
+		SUB.xw		VF03,VF20,VF26		FMAND		VI01,VI09
+		MULAx.w		ACC,VF02,VF01x		ISUBIU		VI01,VI01,8
+		MSUBx.w		VF00,VF01,VF02x		ISW.xyz		VI01,7(VI02)
+
+		SUB.xw		VF01,VF20,VF22		FMAND		VI01,VI09
+		MULAx.w		ACC,VF02,VF03x		ISUBIU		VI01,VI01,8
+		MSUBx.w		VF00,VF03,VF02x		ISW.xyz		VI01,9(VI02)
+
+		SUB.xw		VF02,VF06,VF22		FMAND		VI01,VI09
+		MULAx.w		ACC,VF01,VF03x		ISUBIU		VI01,VI01,8
+		MSUBx.w		VF00,VF03,VF01x		ISW.xyz		VI01,11(VI02)
+
+		SUB.xw		VF03,VF06,VF05		FMAND		VI01,VI09
+		MULAx.w		ACC,VF01,VF02x		ISUBIU		VI01,VI01,8
+		MSUBx.w		VF00,VF02,VF01x		ISW.xyz		VI01,13(VI02)
+
+		NOP								FMAND		VI01,VI09
+		MULAx.w		ACC,VF03,VF02x		ISUBIU		VI01,VI01,8
+		MSUBx.w		VF00,VF02,VF03x		ISW.xyz		VI01,15(VI02)
+
+		NOP								FMAND		VI01,VI09
+		NOP								ISUBIU		VI01,VI01,8
+		NOP								ISW.xyz		VI01,17(VI02)
+
+		NOP								FMAND		VI01,VI09
+		NOP								ISUBIU		VI01,VI01,8
+		NOP								ISW.xyz		VI01,19(VI02)
+
+		NOP								IADDIU		VI08, VI00, 1
+		NOP								XGKICK		VI02
+		
+CullPrism:
+
+		NOP								IBGEZ		VI15,LoopSVS
+		NOP								IADDIU		VI02,VI02,21
+
+		NOP								IBEQ		VI08, VI00, DummyKick
+		NOP								NOP
+		
+		NOP[E]							NOP
+		NOP								NOP
+
+DummyKick:
+		NOP								ISUBIU		VI01, VI02, 1
+		NOP								IADDIU		VI08, VI00, 0x4000
+		NOP								IADDIU      VI08, VI08, 0x4000
+		NOP								ISW.x		VI08, 0(VI01)
+		NOP								XGKICK		VI01
+
+		NOP[E]							NOP
+		NOP								NOP
+
+
+
+
+
+
+		.if 0
+		; original unoptimised version
+ 
+LoopSVS:
+		; v0
+		NOP								LQ			VF20,0(VI02)
+		NOP								MTIR		VI01,VF20w
+		NOP								LQ			VF04,3(VI01)
+		NOP								LQ			VF01,0(VI01)
+		NOP								LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		ADDAx.xyz	ACC,VF04,VF00x		NOP
+		MADDAx.xyz	ACC,VF01,VF20x		NOP
+		MADDAy.xyz	ACC,VF02,VF20y		NOP
+		MADDz.xyz	VF20,VF03,VF20z		NOP
+
+		; v1
+		NOP								LQ			VF21,1(VI02)
+		NOP								MTIR		VI01,VF21w
+		NOP								LQ			VF04,3(VI01)
+		NOP								LQ			VF01,0(VI01)
+		NOP								LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		ADDAx.xyz	ACC,VF04,VF00x		NOP
+		MADDAx.xyz	ACC,VF01,VF21x		NOP
+		MADDAy.xyz	ACC,VF02,VF21y		NOP
+		MADDz.xyz	VF21,VF03,VF21z		NOP
+
+		; v2
+		NOP								LQ			VF22,2(VI02)
+		NOP								MTIR		VI01,VF22w
+		NOP								LQ			VF04,3(VI01)
+		NOP								LQ			VF01,0(VI01)
+		NOP								LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		ADDAx.xyz	ACC,VF04,VF00x		NOP
+		MADDAx.xyz	ACC,VF01,VF22x		NOP
+		MADDAy.xyz	ACC,VF02,VF22y		NOP
+		MADDz.xyz	VF22,VF03,VF22z		NOP
+
+
+		; generate n021
+		SUB.xyz		VF05,VF20,VF22		NOP
+		SUB.xyz		VF06,VF21,VF22		NOP
+		OPMULA.xyz	ACC,VF05,VF06		NOP
+		OPMSUB.xyz	VF01,VF06,VF05		NOP
+
+		; dot with light vector
+		MUL.xyz		VF01,VF01,VF15		NOP
+		ADDy.x		VF01,VF01,VF01y		NOP
+		ADDz.x		VF01,VF01,VF01z		MOVE		VF02,VF01
+		
+		; cull if dot product negative
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF02			FSAND		VI01,2
+		NOP								IBEQ		VI01,VI00,CullPrism
+		NOP								NOP
+
+
+		; v3
+		NOP								LQ			VF23,3(VI02)
+		NOP								MTIR		VI01,VF23w
+		NOP								LQ			VF04,3(VI01)
+		NOP								LQ			VF01,0(VI01)
+		NOP								LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		ADDAx.xyz	ACC,VF04,VF00x		NOP
+		MADDAx.xyz	ACC,VF01,VF23x		NOP
+		MADDAy.xyz	ACC,VF02,VF23y		NOP
+		MADDz.xyz	VF23,VF03,VF23z		NOP
+
+		; v4
+		NOP								LQ			VF24,4(VI02)
+		NOP								MTIR		VI01,VF24w
+		NOP								LQ			VF04,3(VI01)
+		NOP								LQ			VF01,0(VI01)
+		NOP								LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		ADDAx.xyz	ACC,VF04,VF00x		NOP
+		MADDAx.xyz	ACC,VF01,VF24x		NOP
+		MADDAy.xyz	ACC,VF02,VF24y		NOP
+		MADDz.xyz	VF24,VF03,VF24z		NOP
+
+		; v5
+		NOP								LQ			VF25,5(VI02)
+		NOP								MTIR		VI01,VF25w
+		NOP								LQ			VF04,3(VI01)
+		NOP								LQ			VF01,0(VI01)
+		NOP								LQ			VF02,1(VI01)
+		NOP								LQ			VF03,2(VI01)
+
+		ADDAx.xyz	ACC,VF04,VF00x		NOP
+		MADDAx.xyz	ACC,VF01,VF25x		NOP
+		MADDAy.xyz	ACC,VF02,VF25y		NOP
+		MADDz.xyz	VF25,VF03,VF25z		NOP
+		
+		
+		
+
+		; generate n013
+		SUB.xyz		VF06,VF20,VF21		NOP
+		SUB.xyz		VF05,VF23,VF21		NOP
+		OPMULA.xyz	ACC,VF05,VF06		NOP
+		OPMSUB.xyz	VF02,VF06,VF05		NOP
+
+		; generate n241
+		SUB.xyz		VF06,VF22,VF24		NOP
+		SUB.xyz		VF05,VF21,VF24		NOP
+		OPMULA.xyz	ACC,VF05,VF06		NOP
+		OPMSUB.xyz	VF03,VF06,VF05		NOP
+
+		; generate n052
+		SUB.xyz		VF06,VF20,VF25		NOP
+		SUB.xyz		VF05,VF22,VF25		NOP
+		OPMULA.xyz	ACC,VF05,VF06		NOP
+		OPMSUB.xyz	VF04,VF06,VF05		NOP
+
+
+		; take dot products with light vec
+		MUL.xyz		VF02,VF02,VF15		NOP
+		MUL.xyz		VF03,VF03,VF15		NOP
+		MUL.xyz		VF04,VF04,VF15		NOP
+		
+		NOP								NOP
+		ADDy.x		VF02,VF02,VF02y		NOP
+		ADDx.y		VF03,VF03,VF03x		NOP
+		ADDx.z		VF04,VF04,VF04x		NOP
+
+		NOP								NOP
+		ADDz.x		VF02,VF02,VF02z		IADDIU		VI10,VI00,0x80
+		ADDz.y		VF03,VF03,VF03z		IADDIU		VI11,VI00,0x40
+		ADDy.z		VF04,VF04,VF04y		IADDIU		VI12,VI00,0x20
+		
+		; get ADC results
+		NOP								NOP
+		NOP								FMAND		VI10,VI10
+		NOP								FMAND		VI11,VI11
+		NOP								FMAND		VI12,VI12
+		
+		NOP								ISUBIU		VI10,VI10,1
+		NOP								ISUBIU		VI11,VI11,1
+		NOP								ISUBIU		VI12,VI12,1
+
+
+		; compute and project v0,v1,v2 and their translates
+		MULAx		ACC,VF16,VF20x		NOP
+		MADDAy		ACC,VF17,VF20y		NOP
+		MADDAz		ACC,VF18,VF20z		NOP
+		MADDw		VF20,VF08,VF00w		NOP
+		MADDw		VF23,VF07,VF00w		NOP
+		NOP								DIV			Q,VF00w,VF20w
+		MULq.xyz	VF20,VF20,Q			WAITQ
+		NOP								DIV			Q,VF00w,VF23w
+		MULq.xyz	VF23,VF23,Q			WAITQ
+
+		MULAx		ACC,VF16,VF21x		NOP
+		MADDAy		ACC,VF17,VF21y		NOP
+		MADDAz		ACC,VF18,VF21z		NOP
+		MADDw		VF21,VF08,VF00w		NOP
+		MADDw		VF24,VF07,VF00w		NOP
+		NOP								DIV			Q,VF00w,VF21w
+		MULq.xyz	VF21,VF21,Q			WAITQ
+		NOP								DIV			Q,VF00w,VF24w
+		MULq.xyz	VF24,VF24,Q			WAITQ
+
+		MULAx		ACC,VF16,VF22x		NOP
+		MADDAy		ACC,VF17,VF22y		NOP
+		MADDAz		ACC,VF18,VF22z		NOP
+		MADDw		VF22,VF08,VF00w		NOP
+		MADDw		VF25,VF07,VF00w		NOP
+		NOP								DIV			Q,VF00w,VF22w
+		MULq.xyz	VF22,VF22,Q			WAITQ
+		NOP								DIV			Q,VF00w,VF25w
+		MULq.xyz	VF25,VF25,Q			WAITQ
+
+
+		;   0
+		;  /|\
+		; 2---1
+		; | | |
+		; | 3 |
+		; |/ \|
+		; 5---4
+		;
+		; adc's a(01), b(12), c(20)
+		;
+		; 2, 1, 5, 4, 3, 1, 0, 2, 3, 5
+		; 1, 1, b, b, 0, a, a, 0, c, c
+
+		; store positions and adc's
+		FTOI15.w	VF22,VF00			NOP
+		NOP								SQ			VF22,1(VI02)
+		FTOI15.w	VF21,VF00			NOP
+		NOP								SQ			VF21,3(VI02)
+		
+		FTOI15.w	VF25,VF00			NOP
+;		NOP								MFIR.w		VF25,VI11
+		NOP								SQ			VF25,5(VI02)
+		FTOI15.w	VF24,VF00			NOP
+;		NOP								MFIR.w		VF24,VI11
+		NOP								SQ			VF24,7(VI02)
+		
+		;FTOI15.w	VF23,VF00			NOP
+		NOP								MFIR.w		VF23,VI00
+		NOP								SQ			VF23,9(VI02)
+		
+;		NOP								MFIR.w		VF21,VI10
+		FTOI15.w	VF21,VF00			NOP
+		NOP								SQ			VF21,11(VI02)
+		FTOI15.w	VF20,VF00			NOP
+;		NOP								MFIR.w		VF20,VI10
+		NOP								SQ			VF20,13(VI02)
+		
+		;FTOI15.w	VF22,VF00			NOP
+		NOP								MFIR.w		VF22,VI00
+		NOP								SQ			VF22,15(VI02)
+		
+;		NOP								MFIR.w		VF23,VI12
+		FTOI15.w	VF23,VF00			NOP
+		NOP								SQ			VF23,17(VI02)
+;		NOP								MFIR.w		VF25,VI12
+		FTOI15.w	VF25,VF00			NOP
+		NOP								SQ			VF25,19(VI02)
+
+
+		
+		; backface testing and colours (8 tests)
+		NOP								IADDIU		VI10,VI00,0x10
+		NOP								IADDIU		VI11,VI00,0x08
+		
+		; 215
+		SUB.xyz		VF01,VF22,VF21		NOP
+		SUB.xyz		VF02,VF25,VF21		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyzw	VI01,4(VI02)
+		
+		; 145
+		SUB.xyz		VF01,VF21,VF24		NOP
+		SUB.xyz		VF02,VF25,VF24		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyzw	VI01,6(VI02)
+		
+		; 543
+		SUB.xyz		VF01,VF25,VF24		NOP
+		SUB.xyz		VF02,VF23,VF24		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyzw	VI01,8(VI02)
+		
+		; 134
+		SUB.xyz		VF01,VF21,VF23		NOP
+		SUB.xyz		VF02,VF24,VF23		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyzw	VI01,10(VI02)
+		
+		; 103
+		SUB.xyz		VF01,VF21,VF20		NOP
+		SUB.xyz		VF02,VF23,VF20		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyzw	VI01,12(VI02)
+		
+		; 012
+		SUB.xyz		VF01,VF20,VF21		NOP
+		SUB.xyz		VF02,VF22,VF21		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyzw	VI01,14(VI02)
+		
+		; 023
+		SUB.xyz		VF01,VF20,VF22		NOP
+		SUB.xyz		VF02,VF23,VF22		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyzw	VI01,16(VI02)
+
+		; 253
+		SUB.xyz		VF01,VF22,VF25		NOP
+		SUB.xyz		VF02,VF23,VF25		NOP
+		OPMULA.xyz	ACC,VF01,VF02		NOP
+		OPMSUB.xyz	VF03,VF02,VF01		MOVE		VF01,VF02
+		MULz.w		VF00,VF00,VF03z		MOVE		VF01,VF02
+		;NOP								NOP
+		;NOP								NOP
+		;NOP								NOP
+		FTOI0		VF02,VF01			FMAND		VI01,VI10
+		NOP								ISUB		VI01,VI01,VI11
+		NOP								ISW.xyz		VI01,18(VI02)
+
+NextPrism:		
+		; loop control
+		NOP								IADDIU		VI02,VI02,20
+		NOP								NOP
+
+
+		.endif
+
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+; clip triangle if not already culled and if part of it may be in the view frustum and another part in the outer frustum
+; i.e. if clip if (ADC==0 && viewAND==0 && outerOR!=0)
+
+; ADC bit should be set for any of the following:
+;  - ADC set already
+;  - outerOR!=0 (i.e. if culling renderer would cull it)
+;  - viewAND!=0 (trivial rejection)
+
+Clip:
+
+		; reconstruct world-to-frustum transform
+		MULA		ACC,VF10,VF12		NOP
+		MADDw		VF04,VF11,VF12w		NOP
+		MULA		ACC,VF10,VF13		NOP
+		MADDw		VF05,VF11,VF13w		NOP
+		MULA		ACC,VF10,VF14		NOP
+		MADDw		VF06,VF11,VF14w		NOP
+		MULA		ACC,VF10,VF15		NOP
+		MADDw		VF07,VF11,VF15w		NOP
+		
+		.if 0
+		;---------------------------------------------------------
+		.if 0
+		; optimised version
+		
+		; loop prologue
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF07,VF00x		LQ			VF01,-1(VI03)
+		ITOF4.xyz	VF02,VF01			NOP
+		MADDAx		ACC,VF04,VF02x		NOP
+		MADDAy		ACC,VF05,VF02y		NOP
+		MADDz		VF03,VF06,VF02z		NOP
+		CLIPw.xyz	VF03xyz,VF03w		IADDIU		VI10,VI00,0
+		
+		; main clip-testing loop
+LoopC:	ADDAw.xyz	ACC,VF00,VF03w		MTIR		VI07,VF01w
+		MULAw.w		ACC,VF03,VF00w		IADD		VI03,VI03,VI04
+		MADDAz.x	ACC,VF03,VF09z		LQ			VF01,-1(VI03)
+		MADDAw.y	ACC,VF03,VF09w		FCOR		VI01,0xFEFBEF
+		MSUBAx.z	ACC,VF09,VF03x		ISUB		VI07,VI07,VI01
+		MSUBAy.w	ACC,VF09,VF03y		FCOR		VI01,0xFDF7DF
+		ITOF4.xyz	VF02,VF01			ISUB		VI07,VI07,VI01
+		MADDx		VF00,VF00,VF00x		FCAND		VI01,0x03FFFF
+		NOP								ISUB		VI07,VI07,VI01
+		ADDAx		ACC,VF07,VF00x		IADD		VI02,VI02,VI04
+		MADDAx		ACC,VF04,VF02x		IAND		VI08,VI10,VI11
+		MADDAy		ACC,VF05,VF02y		IADDIU		VI11,VI10,0
+		MADDz		VF03,VF06,VF02z		FMOR		VI10,VI00
+		NOP								IAND		VI01,VI08,VI10
+		NOP								ISUB		VI07,VI07,VI01
+		NOP								IBNE		VI02,VI05,LoopC
+		CLIPw.xyz	VF03xyz,VF03w		ISW.w		VI07,-1(VI02)
+		
+		;---------------------------------------------------------
+		.else
+		; unoptimised version
+
+		; initialise source pointer
+		NOP								IADDIU		VI03,VI02,0
+		
+LoopC:
+		; step source pointer
+		NOP								IADD		VI03,VI03,VI04
+		
+		; load vertex
+		NOP								LQ			VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		
+		; convert to float
+		ITOF4.xyz	VF02,VF01			NOP
+		
+		; get ADC field
+		NOP								MTIR		VI07,VF01w
+		
+		; transform to outer volume
+		ADDAx		ACC,VF07,VF00x		NOP
+		MADDAx		ACC,VF04,VF02x		NOP
+		MADDAy		ACC,VF05,VF02y		NOP
+		MADDz		VF03,VF06,VF02z		NOP
+		
+		; step destination pointer
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								NOP
+		
+		; generate clip codes
+		CLIPw.xyz	VF03xyz,VF03w		NOP
+
+		; generate pre-AND and advance outcode queue
+		NOP								IAND		VI08,VI10,VI11
+		NOP								IADDIU		VI11,VI10,0
+		NOP								NOP
+		
+		; generate view-AND.z, combine with ADC
+		NOP								FCOR		VI01,0xFEFBEF		; near
+		NOP								ISUB		VI07,VI07,VI01
+		NOP								FCOR		VI01,0xFDF7DF		; far
+		NOP								ISUB		VI07,VI07,VI01
+		
+		; get outer-OR.xyz, combine with ADC
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								ISUB		VI07,VI07,VI01
+
+		; generate flags for view-AND.xy
+		ADDAw.xyz	ACC,VF00,VF03w		NOP
+		MULAw.w		ACC,VF03,VF00w		NOP
+		MADDAz.x	ACC,VF03,VF09z		NOP
+		MADDAw.y	ACC,VF03,VF09w		NOP
+		MSUBAx.z	ACC,VF09,VF03x		NOP
+		MSUBAy.w	ACC,VF09,VF03y		NOP
+		MADDx		VF00,VF00,VF00x		NOP			; result is (w+Sx*x,w+Sy*y,w-Sx*x,w-Sy*y)
+		
+		; get flags for view-AND.xy
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								FMOR		VI10,VI00
+		
+		; generate view-AND.xy, combine with ADC
+		NOP								IAND		VI01,VI08,VI10
+		NOP								ISUB		VI07,VI07,VI01
+		
+		; store computed w component
+		NOP								ISW.w		VI07,-1(VI02)
+		
+		; loop control
+		NOP								IBNE		VI02,VI05,LoopC
+		NOP								NOP
+		
+		.endif
+		;---------------------------------------------------------
+		.else
+		; backface cull version
+		
+		.if 0
+		; unoptimised version
+		
+		; initialise source pointer
+		NOP								IADDIU		VI03,VI02,0
+		
+		; set ADC mask
+		NOP								IADDIU		VI09,VI00,0x4000
+		NOP								IADDIU		VI09,VI09,0x4000
+		
+LoopC:
+		; step source pointer
+		NOP								IADD		VI03,VI03,VI04
+		
+		; load vertex
+		NOP								LQ			VF01,-1(VI03)
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		
+		; convert to float
+		ITOF4.xyz	VF02,VF01			NOP
+		
+		; get ADC field
+		NOP								MTIR		VI14,VF01w
+		NOP								IAND		VI07,VI14,VI09
+		
+		; transform to outer volume
+		ADDAx		ACC,VF07,VF00x		NOP
+		MADDAx		ACC,VF04,VF02x		NOP
+		MADDAy		ACC,VF05,VF02y		NOP
+		MADDz		VF03,VF06,VF02z		NOP
+		
+		; step destination pointer
+		NOP								IADD		VI02,VI02,VI04
+		NOP								NOP
+		NOP								NOP
+		
+		; generate clip codes
+		CLIPw.xyz	VF03xyz,VF03w		NOP
+
+		; generate pre-AND and advance outcode queue
+		NOP								IAND		VI08,VI10,VI11
+		NOP								IADDIU		VI11,VI10,0
+		NOP								NOP
+		
+		; generate view-AND.z, combine with ADC
+		NOP								FCOR		VI01,0xFEFBEF		; near
+		NOP								ISUB		VI07,VI07,VI01
+		NOP								FCOR		VI01,0xFDF7DF		; far
+		NOP								ISUB		VI07,VI07,VI01
+		
+		; get outer-OR.xyz, combine with ADC
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								ISUB		VI07,VI07,VI01
+
+		; generate flags for view-AND.xy
+		ADDAw.xyz	ACC,VF00,VF03w		NOP
+		MULAw.w		ACC,VF03,VF00w		NOP
+		MADDAz.x	ACC,VF03,VF09z		NOP
+		MADDAw.y	ACC,VF03,VF09w		NOP
+		MSUBAx.z	ACC,VF09,VF03x		NOP
+		MSUBAy.w	ACC,VF09,VF03y		NOP
+		MADDx		VF00,VF00,VF00x		NOP			; result is (w+Sx*x,w+Sy*y,w-Sx*x,w-Sy*y)
+		
+		; get flags for view-AND.xy
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								FMOR		VI10,VI00
+		
+		; inc by 1
+		NOP								IADDIU		VI07,VI07,1
+		
+		; generate view-AND.xy, combine with ADC
+		NOP								IAND		VI01,VI08,VI10
+		NOP								ISUB		VI07,VI07,VI01
+		
+		; will it be clipped?
+		NOP								NOP
+		NOP								IBNE		VI07,VI00,WontBeClipped
+		NOP								NOP
+		
+		; set both adc and clip-bit (0x4000)
+		NOP								ISUBIU		VI01,VI14,0x4000
+		NOP								ISW.w		VI01,-1(VI02)
+WontBeClipped:
+		
+		; loop control
+		NOP								IBNE		VI02,VI05,LoopC
+		NOP								NOP
+		
+		.else
+		; optimised version
+		;
+		
+		; loop prologue
+		NOP								IADD		VI03,VI02,VI04
+		ADDAx		ACC,VF07,VF00x		LQ			VF01,-1(VI03)
+		ITOF4.xyz	VF02,VF01			IADDIU		VI09,VI00,0x4000
+		MADDAx		ACC,VF04,VF02x		IADDIU		VI09,VI09,0x4000
+		MADDAy		ACC,VF05,VF02y		NOP
+		MADDz		VF03,VF06,VF02z		NOP
+		
+		; main clip-testing loop
+LoopC:	NOP								IBEQ		VI02,VI05,AllClipped
+		CLIPw.xyz	VF03xyz,VF03w		MTIR		VI14,VF01w
+		ADDAw.xyz	ACC,VF00,VF03w		IAND		VI07,VI14,VI09
+		MULAw.w		ACC,VF03,VF00w		IADD		VI03,VI03,VI04
+		MADDAz.x	ACC,VF03,VF09z		LQ			VF01,-1(VI03)
+		MADDAw.y	ACC,VF03,VF09w		FCOR		VI01,0xFEFBEF
+		MSUBAx.z	ACC,VF09,VF03x		ISUB		VI07,VI07,VI01
+		MSUBAy.w	ACC,VF09,VF03y		FCOR		VI01,0xFDF7DF
+		ITOF4.xyz	VF02,VF01			ISUB		VI07,VI07,VI01
+		MADDx		VF00,VF00,VF00x		FCAND		VI01,0x03FFFF
+		NOP								ISUB		VI07,VI07,VI01
+		ADDAx		ACC,VF07,VF00x		IADD		VI02,VI02,VI04
+		MADDAx		ACC,VF04,VF02x		IAND		VI08,VI10,VI11
+		MADDAy		ACC,VF05,VF02y		IADDIU		VI11,VI10,0
+		MADDz		VF03,VF06,VF02z		FMOR		VI10,VI00
+		NOP								IAND		VI01,VI08,VI10
+		NOP								ISUB		VI07,VI07,VI01
+		NOP								IADDIU		VI07,VI07,1
+		NOP								NOP
+		NOP								IBNE		VI07,VI00,LoopC
+		NOP								ISUBIU		VI01,VI14,0x4000
+		NOP								IBNE		VI02,VI05,LoopC
+		NOP								ISW.w		VI01,-1(VI02)
+		
+		.endif
+		.endif
+		;---------------------------------------------------------
+		
+AllClipped:
+		; reset pointer
+		NOP								ISUB		VI02,VI02,VI06
+		
+		; set the EOP bit of the previous tag
+		NOP								ILW.x		VI01,0(VI12)
+		NOP								IADDIU		VI01,VI01,0x4000
+		NOP								IADDIU		VI01,VI01,0x4000
+		NOP								ISW.x		VI01,0(VI12)
+		
+		; set fan buffer base
+		NOP								ISUBIU		VI12,VI13,288		; MAX_VU1_BUFFER - # regs to save
+		
+		; kick the context (which might be just a dummy giftag)
+		; this stalls until the GS has finished with the memory we want to use as the fan buffer
+		NOP								XGKICK		VI13
+		
+		; the fan buffer is now guaranteed not to be in use by the GS or DMAC
+		
+		; save some registers
+		NOP								SQ			VF20,-12(VI12)
+		NOP								SQ			VF21,-11(VI12)
+		NOP								SQ			VF22,-10(VI12)
+		NOP								SQ			VF23,-9(VI12)
+		NOP								SQ			VF24,-8(VI12)
+		NOP								SQ			VF25,-7(VI12)
+		NOP								SQ			VF26,-6(VI12)
+		NOP								SQ			VF27,-5(VI12)
+		NOP								SQ			VF28,-4(VI12)
+		NOP								SQ			VF29,-3(VI12)
+		NOP								SQ			VF30,-2(VI12)
+		NOP								SQ			VF31,-1(VI12)
+		
+		; set new giftag pointer (the preclipped tristrip)
+		NOP								ISUBIU		VI13,VI02,1
+		
+		; output pointer = fan buffer base
+		NOP								IADDIU		VI03,VI12,0
+		
+
+		; frustum planes:
+		;
+		; 0x0020 far
+		; 0x0010 near
+		; 0x0008 top
+		; 0x0004 bottom
+		; 0x0002 left
+		; 0x0001 right
+		;
+		;   1---------0
+		;   |\   3   /|
+		;   | *-----* |
+		;   | | (5) | |
+		;   |1|  4  |0|
+		;   | |     | |
+		;   | *-----* |
+		;   |/   2   \|
+		;   *---------*
+
+
+		; registers used:
+		;
+		; VF20: p[j0], m[j0]
+		; VF21: p[j1], m[j1]
+		; VF22: p[j2], m[j2]
+		; VF23: p[j3], m[j3]
+		; VF24: p[j4], m[j4]
+		; VF25: p[j5], m[j5]
+		;
+		; VF26: x[i0], o[i0]
+		; VF27: x[i1], o[i1]
+		; VF28: x[i2], o[i2]
+		;
+		; VF30: e0, flags(e0), just the w-component
+		; VF31: e1, flags(e1)
+		;
+		;
+		; VI12: fan buffer base
+
+
+		; skip the 1st 2 vertices
+		NOP								IADD		VI02,VI02,VI04
+		NOP								IADD		VI02,VI02,VI04
+		NOP								ISUB		VI01,VI02,VI05
+		NOP								IADD		VI08,VI02,VI04
+		NOP								IBGEZ		VI01,PostClip
+		NOP								ILW.w		VI01,-1(VI08)
+
+		; loop over strip, clipping the triangles that are marked with clip bit 0x4000
+ClipLoop:
+		NOP								IBEQ		VI02,VI05,KickFans
+		NOP								IADDIU		VI08,VI00,0x4000
+		NOP								IADD		VI02,VI02,VI04
+		NOP								IAND		VI08,VI01,VI08
+		NOP								IADD		VI01,VI02,VI04
+		NOP								IBEQ		VI08,VI00,ClipLoop
+		NOP								ILW.w		VI01,-1(VI01)
+
+
+		; go ahead with clipping...
+		
+
+		;-------------------------------
+		; load vertex coords from memory
+		;-------------------------------
+
+		NOP								LQ			VF07,-1(VI02)
+		NOP								ISUB		VI01,VI02,VI04
+		NOP								LQ			VF06,-1(VI01)
+		NOP								ISUB		VI01,VI01,VI04
+		NOP								LQ			VF05,-1(VI01)
+		
+		
+		;-----------------
+		; convert to float
+		;-----------------
+		
+		ITOF4.xyz	VF07,VF07			NOP
+		ITOF4.xyz	VF06,VF06			NOP
+		ITOF4.xyz	VF05,VF05			NOP
+
+		;------------------------
+		; apply frustum transform
+		;------------------------
+
+		; reconstruct world-to-frustum transform
+		MULA		ACC,VF10,VF15		NOP
+		MADDw		VF04,VF11,VF15w		NOP
+		MULA		ACC,VF10,VF12		NOP
+		MADDw		VF01,VF11,VF12w		NOP
+		MULA		ACC,VF10,VF13		NOP
+		MADDw		VF02,VF11,VF13w		NOP
+		MULA		ACC,VF10,VF14		NOP
+		MADDw		VF03,VF11,VF14w		NOP
+		
+		ADDAx		ACC,VF04,VF00x		NOP
+		MADDAx		ACC,VF01,VF05x		NOP
+		MADDAy		ACC,VF02,VF05y		NOP
+		MADDz		VF26,VF03,VF05z		NOP
+
+		ADDAx		ACC,VF04,VF00x		NOP
+		MADDAx		ACC,VF01,VF06x		NOP
+		MADDAy		ACC,VF02,VF06y		NOP
+		MADDz		VF27,VF03,VF06z		NOP
+
+		ADDAx		ACC,VF04,VF00x		NOP
+		MADDAx		ACC,VF01,VF07x		NOP
+		MADDAy		ACC,VF02,VF07y		NOP
+		MADDz		VF28,VF03,VF07z		NOP
+
+
+		; reorder the vertices
+		;
+		.if 0
+		
+		; zero the swap flags
+		NOP								IADDIU		VI10,VI00,0
+		
+		; set up mask 0x0E
+		NOP								IADDIU		VI07,VI00,0x0E
+		
+		; compare VF05 to VF06
+		MAX.xyz		VF01,VF05,VF06		NOP
+		SUB.xyz		VF00,VF05,VF06		NOP
+		NOP								NOP
+		NOP								NOP
+		SUB.xyz		VF00,VF01,VF06		NOP
+		
+		; Z
+		NOP								FMAND 		VI01,VI07
+		
+		; ~Z
+		NOP								ISUB		VI01,VI07,VI01
+		
+		; ~Z & -(~Z)
+		NOP								ISUB		VI08,VI00,VI01
+		NOP								IAND		VI01,VI01,VI08
+		
+		; (~Z & -(~Z)) & S
+		NOP								FMAND		VI01,VI01
+		NOP								IBNE		VI01,VI00,NoSwap0
+		NOP								NOP
+		
+		ADDx.xyz	VF05,VF06,VF00x		MOVE.xyz	VF06,VF05
+		ADDx		VF26,VF27,VF00x		MOVE		VF27,VF26
+		NOP								IADDIU		VI10,VI10,1
+NoSwap0:
+		
+		; compare VF05 to VF07
+		MAX.xyz		VF02,VF05,VF07		NOP
+		SUB.xyz		VF00,VF05,VF07		NOP
+		NOP								NOP
+		NOP								NOP
+		SUB.xyz		VF00,VF02,VF07		NOP
+		
+		; Z
+		NOP								FMAND 		VI01,VI07
+		
+		; ~Z
+		NOP								ISUB		VI01,VI07,VI01
+		
+		; ~Z & -(~Z)
+		NOP								ISUB		VI08,VI00,VI01
+		NOP								IAND		VI01,VI01,VI08
+		
+		; (~Z & -(~Z)) & S
+		NOP								FMAND		VI01,VI01
+		NOP								IBNE		VI01,VI00,NoSwap1
+		NOP								NOP
+		
+		ADDx.xyz	VF05,VF07,VF00x		MOVE.xyz	VF07,VF05
+		ADDx		VF26,VF28,VF00x		MOVE		VF28,VF26
+		NOP								IADDIU		VI10,VI10,2
+NoSwap1:
+		
+		; compare VF06 to VF07
+		MAX.xyz		VF03,VF06,VF07		NOP
+		SUB.xyz		VF00,VF06,VF07		NOP
+		NOP								NOP
+		NOP								NOP
+		SUB.xyz		VF00,VF03,VF07		NOP
+		
+		; Z
+		NOP								FMAND 		VI01,VI07
+		
+		; ~Z
+		NOP								ISUB		VI01,VI07,VI01
+		
+		; ~Z & -(~Z)
+		NOP								ISUB		VI08,VI00,VI01
+		NOP								IAND		VI01,VI01,VI08
+		
+		; (~Z & -(~Z)) & S
+		NOP								FMAND		VI01,VI01
+		NOP								IBNE		VI01,VI00,NoSwap2
+		NOP								NOP
+		
+		ADDx.xyz	VF06,VF07,VF00x		MOVE.xyz	VF07,VF06
+		ADDx		VF27,VF28,VF00x		MOVE		VF28,VF27
+		NOP								IADDIU		VI10,VI10,4
+NoSwap2:
+
+		; save the swap flags
+		NOP								MFIR.x		VF30,VI10
+		
+		.else
+		
+		MAX.xyz		VF01,VF05,VF06		IADDIU		VI09,VI00,0x0E		; set up mask 0x0E
+		SUB.xyz		VF00,VF05,VF06		NOP
+		MAX.xyz		VF02,VF05,VF07		NOP
+		MAX.xyz		VF03,VF06,VF07		NOP
+		NOP								NOP
+		SUB.xyz		VF00,VF01,VF06		FMAND 		VI01,VI09			; Z
+		SUB.xyz		VF00,VF05,VF07		ISUB		VI07,VI09,VI01		; ~Z
+		NOP								ISUB		VI01,VI00,VI07
+		NOP								IAND		VI07,VI07,VI01		; ~Z & -(~Z)
+		NOP								FMAND		VI07,VI07			; (~Z & -(~Z)) & S
+		SUB.xyz		VF00,VF02,VF07		FMAND 		VI01,VI09			; Z
+		SUB.xyz		VF00,VF06,VF07		ISUB		VI08,VI09,VI01		; ~Z
+		NOP								ISUB		VI01,VI00,VI08
+		NOP								IAND		VI08,VI08,VI01		; ~Z & -(~Z)
+		NOP								FMAND		VI08,VI08			; (~Z & -(~Z)) & S
+		SUB.xyz		VF00,VF03,VF07		FMAND 		VI01,VI09			; Z
+		NOP								ISUB		VI09,VI09,VI01		; ~Z
+		NOP								ISUB		VI01,VI00,VI09
+		NOP								IAND		VI09,VI09,VI01		; ~Z & -(~Z)
+		NOP								FMAND		VI09,VI09			; (~Z & -(~Z)) & S
+
+		ADDx.xyz	VF01,VF05,VF00x		IBEQ		VI07,VI00,NoSwap0
+		NOP								IADDIU		VI01,VI00,0
+		
+		ADDx.xyz	VF05,VF06,VF00x		ISUB		VI08,VI08,VI09		; swap VF05 with VF06
+		ADDx		VF26,VF27,VF00x		MOVE		VF27,VF26
+		NOP								IADD		VI09,VI08,VI09		; and swap flags VI08 with VI09
+		ADDx.xyz	VF06,VF01,VF00x		ISUB		VI08,VI09,VI08
+		NOP								IADDIU		VI01,VI01,1			; set swap flag 0
+NoSwap0:
+		NOP								IBEQ		VI08,VI00,NoSwap1
+		NOP								NOP
+		
+		ADDx.xyz	VF05,VF07,VF00x		MOVE.xyz	VF07,VF05			; swap VF05 with VF07
+		ADDx		VF26,VF28,VF00x		MOVE		VF28,VF26
+		NOP								IADDIU		VI01,VI01,2			; set swap flag 1
+NoSwap1:
+		NOP								IBEQ		VI09,VI00,NoSwap2
+		NOP								NOP
+		
+		ADDx.xyz	VF06,VF07,VF00x		MOVE.xyz	VF07,VF06			; swap VF06 with VF07
+		ADDx		VF27,VF28,VF00x		MOVE		VF28,VF27
+		NOP								IADDIU		VI01,VI01,4			; set swap flag 2
+NoSwap2:
+		NOP								MFIR.x		VF30,VI01			; save the swap flags
+		
+		.endif
+		;------------------------------------------------------------------------
+		
+		
+		;--------------------------
+		; apply full view transform
+		;--------------------------
+		
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF05x		NOP
+		MADDAy		ACC,VF13,VF05y		NOP
+		MADDz		VF05,VF14,VF05z		NOP
+
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF06x		NOP
+		MADDAy		ACC,VF13,VF06y		NOP
+		MADDz		VF06,VF14,VF06z		NOP
+
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF07x		NOP
+		MADDAy		ACC,VF13,VF07y		NOP
+		MADDz		VF07,VF14,VF07z		NOP
+
+
+
+		;---------------------------
+		; classify triangle vertices
+		;---------------------------
+
+		ADDx		VF01,VF26,VF00x		BAL			VI14,ClassifyTriangleVertex
+		SUB.xyz		VF02,VF00,VF26		MR32.z		VF26,VF26
+		NOP								MFIR.w		VF26,VI01
+		
+		ADDx		VF01,VF27,VF00x		BAL			VI14,ClassifyTriangleVertex
+		SUB.xyz     VF02,VF00,VF27		MR32.z		VF27,VF27
+		NOP								MFIR.w		VF27,VI01
+		
+		ADDx		VF01,VF28,VF00x		BAL			VI14,ClassifyTriangleVertex
+		SUB.xyz     VF02,VF00,VF28		MR32.z		VF28,VF28
+		NOP								MFIR.w		VF28,VI01
+
+
+		;--------------------------
+		; classify frustum vertices
+		;--------------------------
+		
+		; in frustum coords, the 8 vertices of the frustum are
+		; FTL	( f, f, f,-f)  ->  (-1,-1,-1)	0x8000
+		; FTR	(-f, f, f,-f)  ->  ( 1,-1,-1)	0x4000
+		; FBL	( f,-f, f,-f)  ->  (-1, 1,-1)	0x2000
+		; FBR	(-f,-f, f,-f)  ->  ( 1, 1,-1)	0x1000
+		; NTL	( n, n,-n,-n)  ->  (-1,-1, 1)	0x0800
+		; NTR	(-n, n,-n,-n)  ->  ( 1,-1, 1)	0x0400
+		; NBL	( n,-n,-n,-n)  ->  (-1, 1, 1)	0x0200
+		; NBR	(-n,-n,-n,-n)  ->  ( 1, 1, 1)	0x0100
+		
+		; for point classification we can work entirely in frustum coords,
+		; using just the x,y and w of each point (which form a right-handed set)
+		
+		; calculate normal to plane of triangle	(in frustum xyw-space)
+		SUB.xyz		VF01,VF27,VF26		NOP
+		SUB.xyz		VF02,VF28,VF26		NOP
+		
+		;ADDw.xyz	VF04,VF00,VF00w		NOP								; set VF04 = (1,1,1,1)...
+		;NOP								MOVE.w		VF04,VF00
+		MAXw		VF04,VF00,VF00w		NOP
+		
+		NOP								NOP
+		OPMULA.xyz	ACC,VF02,VF01		NOP
+		OPMSUB.xyz	VF01,VF01,VF02		NOP
+		
+		SUBA		ACC,VF00,VF00		NOP
+		
+		; set VF03 = (nx*x0, ny*y0, nz*w0, ?)
+		MUL.xyz		VF03,VF01,VF26		NOP
+		
+		; set VF01 = (nx+ny-nz, -nx+ny-nz, nx-ny-nz, -nx-ny-nz)
+		MADDAx.xz	ACC,VF04,VF01x		NOP
+		MSUBAx.yw	ACC,VF04,VF01x		NOP
+		MADDAy.xy	ACC,VF04,VF01y		NOP
+		MSUBAy.zw	ACC,VF04,VF01y		NOP
+		MSUBz		VF01,VF04,VF01z		NOP
+		
+		; set ACC = (n.x0, n.x0, n.x0, n.x0)
+		MULAx		ACC,VF04,VF03x		NOP
+		MADDAy		ACC,VF04,VF03y		NOP
+		MADDAz		ACC,VF04,VF03z		NOP
+		
+		; calculate (n.x0) - far * VF01
+		MSUBy		VF00,VF01,VF09y		NOP
+		
+		; calculate (n.x0) - near * VF01
+		MSUBx		VF00,VF01,VF09x		NOP
+		
+		; classify 8 vertices of frustum wrt plane of triangle
+		ADDx.x		VF01,VF00,VF28x		IADDIU		VI08,VI00,0x0F10
+		ADDx.y		VF01,VF00,VF26x		IADDIU		VI01,VI00,0x00F0	; Sxyzw MAC flags
+		ADDx.z		VF01,VF00,VF27x		FMAND		VI10,VI01
+		ADDz.x		VF03,VF00,VF28z		FMAND		VI01,VI01
+		ADDz.y		VF03,VF00,VF26z		IADD		VI10,VI10,VI10
+		ADDz.z		VF03,VF00,VF27z		IADD		VI10,VI10,VI10
+        ADDy.x		VF02,VF00,VF28y		IADD		VI10,VI10,VI10
+		ADDy.y		VF02,VF00,VF26y		IADD		VI10,VI10,VI10
+		ADDy.z		VF02,VF00,VF27y		IOR			VI10,VI10,VI01
+
+
+		; frustum outcodes now in VI10
+
+
+		;------------------------------------
+		; trivial rejection of triangle plane
+		;------------------------------------
+		
+		SUB.w		VF01,VF00,VF00		IADDIU		VI01,VI00,0x0FF0
+		SUB.xyz		VF20,VF03,VF01		IBEQ		VI10,VI00,ClipNext	; reject if whole frustum is on '0' side of trianlge plane
+		ADD.xyz		VF21,VF03,VF01		IADDIU		VI06,VI00,0			; zero output vertex count		
+		SUB.xyz		VF22,VF03,VF02		IBEQ		VI10,VI01,ClipNext	; reject if whole frustum is on '1' side of triangle plane
+		ADD.xyz		VF23,VF03,VF02		IADD		VI10,VI10,VI10		; shift another 4 bits left
+		ADDx.xyz	VF24,VF03,VF09x		IADD		VI10,VI10,VI10
+		SUBAy.xyz	ACC,VF00,VF09y		IADD		VI10,VI10,VI10
+		MSUBw.xyz	VF25,VF03,VF00w		IADD		VI10,VI10,VI10
+		
+		;-------------------------------------------
+		; initialise frustum in standard orientation
+		;-------------------------------------------
+		
+		; VecSet(p[0],  w0-x0,  w1-x1,  w2-x2, 0),	m[0] = 0x5501;	; right
+		; VecSet(p[1],  w0+x0,  w1+x1,  w2+x2, 0),	m[1] = 0xAA02;	; left
+		; VecSet(p[2],  w0-y0,  w1-y1,  w2-y2, 0),	m[2] = 0x3304;	; bottom
+		; VecSet(p[3],  w0+y0,  w1+y1,  w2+y2, 0),	m[3] = 0xCC08;	; top
+		; VecSet(p[4],  w0+n ,  w1+n ,  w2+n , 0),	m[4] = 0x0F10;	; near
+		; VecSet(p[5], -w0-f , -w1-f , -w2-f , 0),	m[5] = 0xF020;	; far
+
+		
+		NOP								LOI			0x3F805501
+		ADDi.w		VF20,VF01,I			LOI			0x3F80AA02
+		ADDi.w		VF21,VF01,I			LOI			0x3F803304
+		ADDi.w		VF22,VF01,I			LOI			0x3F80CC08
+		ADDi.w		VF23,VF01,I			LOI			0x3F800F10
+		ADDi.w		VF24,VF01,I			LOI			0x3F80F020
+		ADDi.w		VF25,VF01,I			ISUBIU		VI11,VI00,0x0100 	; 0xFF00
+		
+
+
+		;------------------------------------------
+		; put a straddling edge in the primary face
+		;------------------------------------------
+
+		; while (((c & m[j4])==0) || ((c & m[j4])==(0xFF00 & m[j4])))
+		; {
+		;	jt=j2, j2=j5, j5=j3, j3=j4, j4=jt;
+		; }
+		
+WhileA:	NOP								IAND		VI07,VI10,VI08
+		NOP								IAND		VI01,VI11,VI08
+		NOP								IBEQ		VI07,VI00,RotateA
+		ADDx		VF01,VF22,VF00x		NOP
+		NOP								IBNE		VI01,VI07,EndWhileA
+		NOP								NOP
+RotateA:ADDx		VF22,VF25,VF00x		MTIR		VI08,VF22w
+		ADDx		VF25,VF23,VF00x		B			WhileA
+		ADDx		VF23,VF24,VF00x		MOVE		VF24,VF01
+EndWhileA:
+		
+
+		;-------------------------------------------
+		; rotate straddling edge into secondary face
+		;-------------------------------------------
+
+		; while ((c & m[j3] & m[j4]) != (m[j1] & m[j3] & m[j4]))
+		; {
+		;	jt=j2, j2=j0, j0=j3, j3=j1, j1=jt;
+		; }
+
+		NOP								MTIR		VI07,VF23w			; m[j3]
+		NOP								IAND		VI07,VI07,VI08		; m[j3] & m[j4]
+		NOP								MTIR		VI11,VF21w			; m[j1]
+
+WhileB:	NOP								IAND		VI01,VI11,VI07		; m[j1] & m[j3] & m[j4]
+		NOP								IAND		VI07,VI07,VI10		; c & m[j3] & m[j4]
+		ADDx		VF01,VF22,VF00x		NOP
+		NOP								IBEQ		VI07,VI01,EndWhileB
+		NOP								IAND		VI07,VI11,VI08
+		ADDx		VF22,VF20,VF00x		MTIR		VI11,VF22w
+		ADDx		VF20,VF23,VF00x		B			WhileB
+		ADDx		VF23,VF21,VF00x		MOVE		VF21,VF01
+EndWhileB:
+
+
+
+		;------------------------------------------------
+		; roll the frustum classifier bits into the masks
+		;------------------------------------------------
+
+		NOP								IADDIU		VI10,VI10,0xFF
+		NOP								LOI			0x4B400000			; 2^23+2^22
+		
+		NOP								MTIR		VI01,VF20w
+		NOP								IAND		VI01,VI01,VI10
+		NOP								MFIR.w		VF20,VI01
+		NOP								MTIR		VI01,VF21w
+		NOP								IAND		VI01,VI01,VI10
+		NOP								MFIR.w		VF21,VI01
+		ITOF0.w		VF20,VF20			MTIR		VI01,VF22w
+		NOP								IAND		VI01,VI01,VI10
+		NOP								MFIR.w		VF22,VI01
+		ITOF0.w		VF21,VF21			MTIR		VI01,VF23w
+		ADDi.w		VF20,VF20,I			IAND		VI01,VI01,VI10
+		NOP								MFIR.w		VF23,VI01
+		ITOF0.w		VF22,VF22			MTIR		VI01,VF24w
+		ADDi.w		VF21,VF21,I			IAND		VI01,VI01,VI10
+		NOP								MFIR.w		VF24,VI01
+		ITOF0.w		VF23,VF23			MTIR		VI01,VF25w
+		ADDi.w		VF22,VF22,I			IAND		VI01,VI01,VI10
+		NOP								MFIR.w		VF25,VI01
+		ITOF0.w		VF24,VF24			NOP
+		ADDi.w		VF23,VF23,I			NOP
+		ITOF0.w		VF25,VF25			NOP
+		ADDi.w		VF24,VF24,I			NOP
+		ADDi.w		VF25,VF25,I			NOP
+		
+		
+
+		;----------------------------------------------
+		; classify initial straddling edge wrt triangle
+		;----------------------------------------------
+		
+		OPMULA.xyz	ACC,VF23,VF24		NOP
+		OPMSUB.xyz	VF31,VF24,VF23		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								IADDIU		VI01,VI00,0x00E0	; Sxyz MAC flags
+		NOP								FMAND		VI01,VI01
+		NOP								MFIR.w		VF31,VI01
+
+		; advance one face to fill the classification queue
+		NOP								BAL			VI14,NextFrustumFace
+		NOP								NOP
+
+
+; ; GENERAL ALGORITHM
+;
+; ; mark our place in the triangle
+; vT0 = vT;
+;
+; ; are we starting at a triangle vertex inside the frustum?
+; if (vT inside F)
+;	goto vTinsideF;
+;
+; ; find the first edge of the frustum poly with respect to which which the triangle vertex is out
+; while (vT outside edge(vF,vF->next))
+;	vF = vF->next;
+; while (vT inside edge(vF,vF->next))
+;	vF = vF->next;
+;
+; ; mark our place in the frustum poly
+; vF0 = vF;
+;
+; ; find an intersection, or determine there isn't one and quit
+; while (1)
+; {
+;	while (vT->next outside edge(vF,vF->next))
+;		vT = vT->next;
+;
+;	do
+;	{
+;		if ( vF        inside edge(vT,vT->next) &&
+;		     vF->next outside edge(vT,vT->next) &&
+;		     vT       outside edge(vF,vF->next))
+;		    goto Intersection;
+;		vF = vF->next;
+;		if (vF == vF0)
+;			goto Reject;
+;	}
+;	while (vT->next inside edge(vF,vF->next));
+; }
+;
+; Intersection:
+;
+; while (1)
+; {
+;
+;	Output(Intersection(edge(vF,vF->next), edge(vT,vT->next));
+;
+;	while (vT->next inside F)
+;	{
+;		vT = vT->next;
+;		if (vT==vT0)
+;			goto Finish;
+; vTinsideF:
+;		Output(vT);
+;	}
+;
+;	do {
+;		vF = vF->next;
+;	} while ((vF inside edge(vT,vT->next)) || (vF->next outside edge(vT,vT->next)));
+;
+;	Output(Intersection(edge(vT,vT->next), edge(vF,vF->next));
+;
+;	while (vF->next inside T)
+;	{
+;		vF = vF->next;
+;		Output(vF);
+;	}
+;
+;	do {
+;		vT = vT->next;
+;		if (vT==vT0)
+;			goto Finish;
+;	} while ((vT inside edge(vF,vF->next)) || (vT->next outside edge(vF,vF->next)));
+;
+; }
+;
+; Finish:
+
+		; are we starting at an in-vertex?
+		NOP								MTIR		VI07,VF26w
+		NOP								IADDIU		VI10,VI00,0x80		; mark our place in the triangle
+		NOP								IBEQ		VI07,VI00,StartIn
+		NOP								NOP
+
+		; find the first edge of the frustum poly with respect to which which the triangle vertex is out
+FindEdge1:
+		NOP								MTIR		VI01,VF23w
+		NOP								IAND		VI01,VI01,VI07
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,NextFrustumFace
+		NOP								IADDIU		VI14,VI00,FindEdge1
+
+FindEdge2:
+		NOP								MTIR		VI01,VF23w
+		NOP								IAND		VI01,VI01,VI07
+		NOP								NOP
+		NOP								IBEQ		VI01,VI00,NextFrustumFace
+		NOP								IADDIU		VI14,VI00,FindEdge2
+
+		; mark our place in the frustum poly
+		NOP								MTIR		VI11,VF23w
+		NOP								MTIR		VI07,VF23w
+
+		; find an intersection, or determine there isn't one and quit
+FindIntersection:
+		NOP								MTIR		VI08,VF27w
+		NOP								IAND		VI01,VI08,VI07
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,NextTriangleVertex
+		NOP								IADDIU		VI14,VI00,FindIntersection
+
+While3:
+		NOP								MTIR		VI01,VF26w
+		NOP								IAND		VI01,VI01,VI07
+		NOP								MTIR		VI07,VF31w
+		NOP								IBEQ		VI01,VI00,NoIntersectionYet
+		NOP								MTIR		VI01,VF30w
+		
+		NOP								IAND		VI01,VI01,VI10
+		NOP								IAND		VI07,VI07,VI10
+		NOP								ISUB		VI01,VI01,VI07
+		NOP								NOP
+		NOP								IBLTZ		VI01,While4
+		NOP								NOP
+		
+NoIntersectionYet:
+		
+		NOP								BAL			VI14,NextFrustumFace
+		NOP								NOP
+		NOP								MTIR		VI07,VF23w
+		NOP								IAND		VI01,VI08,VI07
+		NOP								IBEQ		VI07,VI11,ClipNext	; reject
+		NOP								NOP
+		
+		NOP								IBNE		VI01,VI00,FindIntersection
+		NOP								NOP
+
+		NOP								B			While3
+		NOP								NOP
+
+
+
+
+
+
+
+
+
+
+
+While4:	; output entering intersection
+		NOP								BAL			VI14,OutputType2
+		NOP								NOP
+
+		; output any vertices of triangle inside frustum
+OutputTriVerts:
+		NOP								MTIR		VI01,VF27w
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,EndOutputTriVerts
+		NOP								NOP
+
+		NOP								BAL			VI14,NextTriangleVertex
+		NOP								NOP
+		
+StartIn:NOP								B			OutputType1
+		NOP								IADDIU		VI14,VI00,OutputTriVerts
+EndOutputTriVerts:
+
+		; traverse frustum poly to find leaving intersection
+		
+		NOP								MTIR		VI01,VF31w
+		
+FindLeavingIntersection:
+		NOP								BAL			VI14,NextFrustumFace
+		NOP								IAND		VI07,VI01,VI10
+		NOP								IAND		VI01,VI01,VI10
+		NOP								ISUB		VI09,VI07,VI01
+		NOP								NOP
+		NOP								IBLEZ		VI09,FindLeavingIntersection
+		NOP								NOP
+		
+		; output leaving intersection
+		NOP								BAL			VI14,OutputType2
+		NOP								NOP
+		
+		NOP								MTIR		VI01,VF31w
+		NOP								NOP
+		
+		; output any vertices of frustum poly inside triangle
+OutputFrustumVerts:
+
+		NOP								IBNE		VI01,VI00,EndOutputFrustumVerts
+		NOP								NOP
+		
+		NOP								BAL			VI14,OutputType3
+		NOP								NOP
+		
+		NOP								B			NextFrustumFace
+		NOP								IADDIU		VI14,VI00,OutputFrustumVerts
+EndOutputFrustumVerts:
+
+		; traverse triangle to find entering intersection
+		NOP								MTIR		VI09,VF23w
+		NOP								MTIR		VI07,VF27w
+		NOP								IAND		VI07,VI07,VI09
+
+FindEnteringIntersection:
+		NOP								IADDIU		VI01,VI07,0
+		NOP								MTIR		VI07,VF28w
+		NOP								IAND		VI07,VI07,VI09
+		NOP								BAL			VI14,NextTriangleVertex
+		NOP								ISUB		VI01,VI01,VI07
+		
+		NOP								IBLEZ		VI01,FindEnteringIntersection
+		NOP								NOP
+		
+		NOP								B			While4
+		NOP								NOP
+
+
+
+		;------------------------------------
+		; classify triangle vertex subroutine
+		;------------------------------------
+		
+		
+		; classify a triangle vertex with respect to the 6 planes of the frustum
+		; this info could possibly be retained from pass 1
+		
+ClassifyTriangleVertex:
+
+		NOP								IADDIU		VI01,VI00,0x0010	; Sw MAC flag
+		CLIPw.xyz	VF01,VF01w			FMAND		VI01,VI01
+		CLIPw.xyz	VF02,VF01w			NOP
+		NOP								IADDIU		VI07,VI00,0x003F
+		NOP								IBEQ		VI01,VI00,wPos
+		NOP								FCGET		VI01
+		NOP								FCGET		VI01
+		NOP								ISUB		VI01,VI07,VI01
+wPos:	NOP								JR			VI14
+		NOP								IAND		VI01,VI01,VI07
+		
+
+		;--------------------------------
+		; next triangle vertex subroutine
+		;--------------------------------
+
+;NextTriangleVertex()
+;{
+;	it=i0, i0=i1, i1=i2, i2=it;
+;}
+
+NextTriangleVertex:
+
+		NOP								ISUBIU		VI10,VI10,0x30
+		NOP								MOVE		VF01,VF26
+		NOP								IBLTZ		VI10,ProcessFan
+		NOP								MOVE		VF26,VF27
+		NOP								MOVE		VF27,VF28
+		NOP								JR			VI14
+		NOP								MOVE		VF28,VF01
+		
+
+		;-----------------------------
+		; next frustum face subroutine
+		;-----------------------------
+		
+
+;   1---------0
+;   |\   3   /|
+;   | *-----* |
+;   | | (5) | |
+;   |1|  4  |0|
+;   | |     | |
+;   | *-----* |
+;   |/   2   \|
+;   *---------*
+;
+;NextFrustumFace()
+;{
+;	; advance edge classification queue
+;	e0 = e1;
+;
+;	; rotate frustum in 1 of 3 ways
+;	jt=j3, j3=j4;
+;	if (m[j4] & m[j0])
+;		j4=j0, j0=jt, jt=j1, j1=j2;
+;	else if (m[j4] & m[j2])
+;		j4=j2;
+;	else
+;		j4=j1, j1=jt, jt=j0, j0=j2;
+;	j2=j5, j5=jt;
+;
+;	; classify new straddling edge wrt triangle
+;	CrossProduct(e1, p[j2], p[j4]);
+;}
+
+
+NextFrustumFace:
+
+		ADDx		VF01,VF23,VF00x		MTIR		VI09,VF24w
+		ADDx		VF23,VF24,VF00x		MTIR		VI01,VF20w
+		NOP								IAND		VI01,VI09,VI01
+		NOP								MOVE.w		VF30,VF31
+		NOP								IBEQ		VI01,VI00,NFF1
+		NOP								MTIR		VI01,VF22w
+		
+		OPMULA.xyz	ACC,VF23,VF20		MOVE		VF24,VF20
+		OPMSUB.xyz	VF31,VF20,VF23		MOVE		VF20,VF01
+		ADDx		VF01,VF21,VF00x		B			NFF3
+		ADDx		VF21,VF22,VF00x		IADDIU		VI01,VI00,0x00E0	; Sxyz MAC flags
+
+NFF1:	NOP								IAND		VI01,VI09,VI01
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,NFF2
+		NOP								IADDIU		VI01,VI00,0x00E0	; Sxyz MAC flags
+
+		OPMULA.xyz	ACC,VF23,VF21		MOVE		VF24,VF21
+		OPMSUB.xyz	VF31,VF21,VF23		MOVE		VF21,VF01
+		ADDx		VF01,VF20,VF00x		B			NFF3
+		ADDx		VF20,VF22,VF00x		NOP
+
+NFF2:	OPMULA.xyz	ACC,VF23,VF22		MOVE		VF24,VF22
+		OPMSUB.xyz	VF31,VF22,VF23		NOP
+
+NFF3:	ADDx		VF00,VF31,VF00x		FMAND		VI01,VI01
+		ADDx		VF22,VF25,VF00x		JR			VI14
+		ADDx		VF25,VF01,VF00x		MFIR.w		VF31,VI01
+
+
+		;--------------------------
+		; vertex output subroutines
+		;--------------------------
+
+
+OutputWeightsFromAcc:
+
+		MADDx		VF01,VF00,VF00x		NOP
+
+OutputWeights:
+
+		NOP								IADDIU		VI06,VI06,1
+		NOP								IADDIU		VI03,VI03,3
+		NOP								JR			VI14
+		NOP								SQ			VF01,-2(VI03)
+		
+
+
+
+OutputType1:
+
+		NOP								ISUBIU		VI01,VI10,0x50
+		NOP								NOP
+		ADDw.y		VF01,VF00,VF00w		IBEQ		VI01,VI00,OutputWeights
+		ADDx.xzw	VF01,VF00,VF00x		NOP								; case i0=1 : VF01 = (0,1,0,1)
+		ADDw.z		VF01,VF00,VF00w		IBLTZ		VI01,OutputWeights
+		ADDx.xyw	VF01,VF00,VF00x		NOP								; case i0=2 : VF01 = (0,0,1,1)
+		ADDw.x		VF01,VF00,VF00w		B			OutputWeights
+		ADDx.yzw	VF01,VF00,VF00x		NOP								; case i0=0 : VF01 = (1,0,0,1)
+
+
+
+
+;OutputIntersectionType2(Vec x[3])
+;{
+;	; x = ((a0.p)x1-(a1.p)x0)/(x0-x1).p
+;	Vec Result;
+;	VecWeightedMean2(Result, -p[j4][i1], x[i0], p[j4][i0], x[i1]);
+;	Output(Result,&pClippedPoly);
+;}
+
+OutputType2:
+
+		ADDw.xyz	VF01,VF00,VF00w		ISUBIU		VI01,VI10,0x50		; VF01 = (1,1,1,?)
+		NOP								MOVE.w		VF01,VF00			; VF01 = (1,1,1,1)
+		SUBA		ACC,VF00,VF00		IBEQ		VI01,VI00,Type2B	; ACC  = (0,0,0,0)
+		NOP								NOP
+		NOP								IBLTZ		VI01,Type2C
+		NOP								NOP
+
+Type2A:	MADDAy.yw	ACC,VF01,VF23y		B			OutputWeightsFromAcc; ACC  = (0,VF23y,0,VF23y)
+		MSUBAz.xw	ACC,VF01,VF23z		NOP								; ACC  = (-VF23z,VF23y,0,VF23y-VF23z)
+		
+Type2B:	MADDAz.zw	ACC,VF01,VF23z		B			OutputWeightsFromAcc; ACC  = (0,0,VF23z,VF23z)
+		MSUBAx.yw	ACC,VF01,VF23x		NOP								; ACC  = (0,-VF23x,VF23z,VF23z-VF23x)
+		
+Type2C:	MADDAx.xw	ACC,VF01,VF23x		B			OutputWeightsFromAcc; ACC  = (VF23x,0,0,VF23x)
+		MSUBAy.zw	ACC,VF01,VF23y		NOP								; ACC  = (VF23x,0,-VF23y,VF23x-VF23y)
+
+
+
+OutputType3:
+		
+		MULAx.w		ACC,VF00,VF31x		MR32.xy		VF01,VF31
+		MADDAy.w	ACC,VF00,VF31y		NOP
+		NOP								NOP
+
+		MADDz.w		VF01,VF00,VF31z		B			OutputWeights
+		ADDx.z		VF01,VF00,VF31x		NOP								; VF01 = (VF31y, VF31z, VF31x, VF31x+VF31y+VF31z)
+		
+
+
+
+
+ProcessFan:
+
+		NOP								IBEQ		VI06,VI00,ClipNext	; if nothing was output, just forget it
+		NOP								ILW.y		VI08,0(VI13)
+
+		; get colours and texcoords from the source triangle
+		NOP								ISUB		VI01,VI02,VI04
+		NOP								ISUB		VI10,VI01,VI04
+		NOP								ISUB		VI11,VI10,VI04
+		NOP								LQ			VF23,-2(VI10)
+		NOP								LQ			VF24,-2(VI01)
+		NOP								LQ			VF25,-2(VI02)
+		NOP								LQ.xyz		VF20,0(VI11)
+		NOP								LQ.xyz		VF21,0(VI10)
+		NOP								LQ.xyz		VF22,0(VI01)
+		
+		; convert rgba to float and test for reflection mapping
+		ITOF0		VF23,VF23			ISUBIU		VI10,VI08,Refl
+		ITOF0		VF24,VF24			IADDIU		VI01,VI00,0x0FF
+		ITOF0		VF25,VF25			IAND		VI01,VI10,VI01
+		NOP								XITOP		VI11
+		NOP								IBEQ		VI01,VI00,NoConvST
+		NOP								IADDIU		VI01,VI00,SHDW
+		
+		; convert st to float and supplement with 1, and check whether it's a shadow projection
+		ITOF12.xy	VF20,VF20			IAND		VI01,VI01,VI11
+		ITOF12.xy	VF21,VF21			MR32.z		VF20,VF00
+		ITOF12.xy	VF22,VF22			IBNE		VI01,VI00,ClampedInUV
+		ADDw.z		VF21,VF00,VF00w		MR32.z		VF22,VF00
+		
+
+		; reduce texture coordinates...
+		
+		; get u-clamp flag and start floating point calculation on both coords
+		ADDw.xy		VF01,VF00,VF00w		LOI			-0.33333333
+		MULAi.xy	ACC,VF20,I			IADDIU		VI01,VI00,0x1000
+		MADDAi.xy	ACC,VF21,I			IAND		VI01,VI08,VI01
+		MADDAi.xy	ACC,VF22,I			LOI			0xCB400000	; -2^23-2^22
+		MADDAi.xy	ACC,VF01,I			IBNE		VI01,VI00,ClampedInU
+		MSUBAi.xy	ACC,VF01,I          IADDIU		VI01,VI00,0x2000
+		
+		; reduce texture coords in s
+		MADDw.x		VF20,VF20,VF00w		NOP
+		MADDw.x		VF21,VF21,VF00w		NOP
+		MADDw.x		VF22,VF22,VF00w		NOP
+		
+ClampedInU:
+
+		; get v-clamp flag
+		NOP								IAND		VI01,VI08,VI01
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,ClampedInV
+		NOP								NOP
+		
+		; reduce texture coords in t
+		MADDw.y		VF20,VF20,VF00w		NOP
+		MADDw.y		VF21,VF21,VF00w		NOP
+		MADDw.y		VF22,VF22,VF00w		NOP
+		
+ClampedInV:
+ClampedInUV:
+NoConvST:
+
+
+		; get giftag and replace NLOOP, NREG and PRIM fields
+		NOP								LQ.y		VF01,0(VI13)											; VF01y = NREG:FLG:PRIM:PRE:000
+		NOP								LOI			0x53400000												; 2^39+2^38
+		ADDi.y		VF03,VF00,I			LOI			196616													; 3*2^16+8
+		ADDi.y		VF04,VF00,I			LOI			0x3F800412			; XYZ2:RGBA:STQ
+		ITOF12.y	VF01,VF01			IADDIU		VI11,VI08,0			; VF01y = float(NREG:FLG:PRIM:PRE)	; save GIFTAGy
+		SUBA.y		ACC,VF03,VF01		IADDIU		VI08,VI03,0			; ACCy = 2^39+2^38-float(NREG)		; end pointer
+		MSUBAw.y	ACC,VF03,VF00w		IADD		VI01,VI06,VI06		; ACCy = -float(NREG)				; 2 * vertex count
+		MADDAw.y	ACC,VF01,VF00w		IADD		VI01,VI01,VI06		; ACCy = float(FLG:PRIM:PRE)		; 3 * vertex count
+		MADDw.y		VF01,VF04,VF00w		ISUB		VI03,VI03,VI01		; VF01y = float(3:FLG:fanPRIM:PRE)	; address of giftag
+		NOP								MFIR.x		VF01,VI06			; EOP:NLOOP
+		ADDi.z		VF01,VF00,I			NOP
+		FTOI12.y	VF01,VF01			NOP								; VF01y = 3:FLG:fanPRIM:PRE:000
+		
+		NOP								SQ.xyz		VF01,0(VI03)
+		
+		
+		;------------------------------------------------------------------------
+		; reorder the colours and texcoords
+		.if 1
+		
+		; retrieve the swap flags
+		NOP								MTIR		VI10,VF30x
+		
+		NOP								IADDIU		VI01,VI00,1
+		NOP								IAND		VI01,VI01,VI10
+		NOP								IADDIU		VI07,VI00,2
+		NOP								IBEQ		VI01,VI00,NoSwap3
+		NOP								IAND		VI07,VI07,VI10
+		
+		ADDx		VF23,VF24,VF00x		MOVE		VF24,VF23
+		ADDx		VF20,VF21,VF00x		MOVE		VF21,VF20
+NoSwap3:
+
+		NOP								IADDIU		VI01,VI00,4
+		NOP								IBEQ		VI07,VI00,NoSwap4
+		NOP								IAND		VI01,VI01,VI10
+		
+		ADDx		VF23,VF25,VF00x		MOVE		VF25,VF23
+		ADDx		VF20,VF22,VF00x		MOVE		VF22,VF20
+NoSwap4:
+		
+		NOP								NOP
+		NOP								IBEQ		VI01,VI00,NoSwap5
+		NOP								NOP
+		
+		ADDx		VF24,VF25,VF00x		MOVE		VF25,VF24
+		ADDx		VF21,VF22,VF00x		MOVE		VF22,VF21
+NoSwap5:
+		
+		.endif
+		;------------------------------------------------------------------------
+		
+		; prepare reflection-map test
+		NOP								ISUBIU		VI11,VI10,Refl
+		NOP								IADDIU		VI01,VI00,0x0FF
+		NOP								IAND		VI11,VI11,VI01
+		
+		; fog setup
+		NOP								DIV			Q,VF00w,VF10w
+		ADDq.x		VF08,VF00,Q			WAITQ
+		
+		NOP								LOI			0x45000FFF
+		ADDi.y		VF08,VF00,I			NOP								; VF08y = 2^11 + 1 - 2^-12
+		SUBq.y		VF08,VF08,Q			NOP								; VF08y = 2^11 + 1-f0 - 2^-12
+		
+		NOP								LQ.w		VF01,-8(VI12)
+		NOP								MR32.z		VF08,VF01			; VF08 = FogNear
+		
+FanLoop:
+		NOP								LQ			VF04,1(VI03)
+		
+		MULz.w		VF08,VF04,VF08z		NOP
+		
+		MULAx		ACC,VF05,VF04x		ERCPR		P,VF04w
+		MADDAy		ACC,VF06,VF04y		NOP
+		MADDz		VF03,VF07,VF04z		IADDIU		VI01,VI00,0x0010	; Sw FMAC flag
+		
+		MULAx		ACC,VF23,VF04x		FMAND		VI01,VI01
+		MADDAy		ACC,VF24,VF04y		NOP
+		MADDz		VF02,VF25,VF04z		DIV			Q,VF08x,VF03w
+		
+		MULAx.xyz	ACC,VF20,VF04x		NOP
+		MADDAy.xyz	ACC,VF21,VF04y		NOP
+		MADDz.xyz	VF01,VF22,VF04z		LOI			1.0039				; fudgefactor to compensate for RGBA rounding error
+
+
+		NOP								IBNE		VI01,VI00,NonStandard
+		NOP								NOP
+
+		MINI.w		VF03,VF03,VF08		NOP
+
+		NOP								B			Standard
+		NOP								NOP
+
+NonStandard:
+
+		MAX.w		VF03,VF03,VF08		NOP
+
+
+Standard:
+
+		MULi		VF02,VF02,I			NOP
+		MULAy		ACC,VF00,VF08y		NOP
+		MADDq.xyzw	VF03,VF03,Q			WAITQ
+
+		
+		
+		; test for reflection mapping
+		NOP								IBNE		VI11,VI00,StandardDivST
+		NOP								LOI			8388608
+		NOP								DIV			Q,VF00w,VF01z
+		NOP								WAITQ
+		
+StandardDivST:
+		MULq.xyz	VF01,VF01,Q			NOP
+		FTOI4.xyz	VF03,VF03			WAITP
+		ADDAi.xyz	ACC,VF00,I			MFP.w		VF04,P
+		MULAi.w		ACC,VF00,I			IADDIU		VI03,VI03,3
+		MADDw		VF02,VF02,VF04w		SQ.xyz		VF01,-2(VI03)
+		NOP								SQ			VF03,0(VI03)
+		NOP								IBNE		VI03,VI08,FanLoop
+		NOP								SQ			VF02,-1(VI03)
+		
+		NOP								IADDIU		VI03,VI03,1			; add 1 for giftag
+
+
+		; temporary overflow check
+		NOP								ISUB		VI01,VI03,VI12
+		NOP								ISUBIU		VI01,VI01,263		; MAX_VU1_BUFFER - # saved regs - 25
+		NOP								NOP
+		NOP								IBGEZ		VI01,KickFans
+		NOP								NOP
+
+		
+		; go back for next triangle
+ClipNext:
+		NOP								IADD		VI01,VI02,VI04
+		NOP								IBNE		VI02,VI05,ClipLoop
+		NOP								ILW.w		VI01,-1(VI01)
+		
+
+KickFans:
+		; add a terminal giftag
+		NOP								IBEQ		VI03,VI12,PostClip
+		NOP								NOP
+		NOP								IADDIU		VI01,VI00,0x4000
+		NOP								IADDIU		VI01,VI01,0x4000
+		NOP								ISW.x		VI01,0(VI03)
+		
+		; kick the fan buffer
+		NOP								XGKICK		VI12
+		
+		; are there any more source triangles?
+		NOP								IBEQ		VI02,VI05,PostClip
+		NOP								NOP
+		
+		; stall VU till fan buffer is free
+		NOP								XGKICK		VI03
+		
+		; reset output pointer and go back for more
+		NOP								IADDIU		VI03,VI12,0
+		NOP								B			ClipNext
+		NOP								NOP
+
+PostClip:
+		; restore some registers
+		NOP								LQ			VF20,-12(VI12)
+		NOP								LQ			VF21,-11(VI12)
+		NOP								LQ			VF22,-10(VI12)
+		NOP								LQ			VF23,-9(VI12)
+		NOP								LQ			VF24,-8(VI12)
+		NOP								LQ			VF25,-7(VI12)
+		NOP								LQ			VF26,-6(VI12)
+		NOP								LQ			VF27,-5(VI12)
+		NOP								LQ			VF28,-4(VI12)
+		NOP								LQ			VF29,-3(VI12)
+		NOP								LQ			VF30,-2(VI12)
+		NOP								LQ			VF31,-1(VI12)
+		
+		; get renderer address
+		NOP								ILW.w		VI01,0(VI13)
+		
+		; reset pointer
+		NOP								IADDIU		VI02,VI13,1
+		
+		; restore render flags
+		NOP								XITOP		VI14
+		
+		; jump to postclip pass
+		NOP								JR			VI01
+		NOP								NOP
+
+
+
+
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+
+; -------------
+; PARTICLE CODE
+; -------------
+
+
+Sprites:
+
+		.if 0
+
+		NOP								IADDIU		VI03,VI02,0
+		NOP								MR32.xyz	VF08,VF00			; upper left texcoords (0,0,1)
+		ADDw.xyz	VF29,VF00,VF00w		NOP								; lower right texcoords (1,1,1)
+		NOP								MFIR.w		VF05,VI00			; clear adc bit
+		
+SpriteLoop:
+
+		NOP								IADD		VI03,VI03,VI04		; step source pointer
+		NOP								LQ			VF01,-1(VI03)		; get vertex
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF01y		NOP								; row 1 view transform
+		MADDz		VF02,VF14,VF01z		NOP								; row 2 view transform
+		MULw.xyz	VF03,VF30,VF01w		NOP								; viewport scale time size parameter
+		NOP								NOP
+		NOP								NOP
+		NOP								DIV			Q,VF00w,VF02w		; calc 1/w
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULAq.xyz	ACC,VF02,Q			NOP								; homogeneous div
+		MSUBq.xyz	VF04,VF03,Q			NOP								; calc upper left vertex
+		MADDq.xyz	VF05,VF03,Q			NOP								; calc lower right vertex
+		NOP								NOP
+		NOP								SQ.xyz		VF29,1(VI02)		; store lower right texcoords
+		NOP								SQ			VF04,0(VI02)		; store upper left vertex
+		NOP								IADD		VI02,VI02,VI04		; step destination pointer
+		NOP								SQ			VF05,-1(VI02)		; store lower right vertex
+		NOP								IBEQ		VI02,VI05,SpriteDone; break
+		NOP								NOP
+		
+		
+		NOP								IADD		VI03,VI03,VI04		; step source pointer
+		NOP								LQ			VF01,-1(VI03)		; get vertex
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF01y		NOP								; row 1 view transform
+		MADDz		VF02,VF14,VF01z		NOP								; row 2 view transform
+		MULw.xyz	VF03,VF30,VF01w		NOP								; viewport scale time size parameter
+		NOP								NOP
+		NOP								NOP
+		NOP								DIV			Q,VF00w,VF02w		; calc 1/w
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MULAq.xyz	ACC,VF02,Q			NOP								; homogeneous div
+		MADDq.xyz	VF04,VF03,Q			NOP								; calc lower right vertex
+		MSUBq.xyz	VF05,VF03,Q			NOP								; calc upper left vertex
+		NOP								NOP
+		NOP								SQ.xyz		VF08,1(VI02)		; store upper left texcoords
+		NOP								SQ			VF04,0(VI02)		; store lower right vertex
+		NOP								IADD		VI02,VI02,VI04		; step destination pointer
+		NOP								SQ			VF05,-1(VI02)		; store upper left vertex
+		NOP								IBNE		VI02,VI05,SpriteLoop; loop
+		NOP								NOP
+		
+		
+SpriteDone:
+
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+
+		.else
+		
+		; optimised version
+
+		ADDAx		ACC,VF15,VF00x		LQ			VF03,3(VI02)
+		MADDAx		ACC,VF12,VF03x		LQ			VF01,7(VI02)
+		MADDAy		ACC,VF13,VF03y		NOP
+		MADDz		VF02,VF14,VF03z		NOP
+		MULw.xyz	VF04,VF30,VF03w		DIV			Q,VF00w,VF02w
+		ADDw.xyz	VF29,VF00,VF00w		MR32.xyz	VF08,VF00
+		ADDAx		ACC,VF15,VF00x		MFIR.w		VF04,VI00
+		MADDAx		ACC,VF12,VF01x		MFIR.w		VF05,VI00
+		MADDAy		ACC,VF13,VF01y		NOP
+		MADDz		VF03,VF14,VF01z		NOP
+		
+SpriteLoop:
+		
+		MULw.xyz	VF05,VF30,VF01w		NOP
+		MULAq.xyz	ACC,VF02,Q			IADD		VI02,VI02,VI04
+		MADDq.xyz	VF02,VF04,Q			LQ			VF01,7(VI02)
+		MSUBq.xyz	VF04,VF04,Q			DIV			Q,VF00w,VF03w
+		NOP								SQ.xyz		VF29,-3(VI02)
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF01x		SQ			VF02,-4(VI02)
+		MADDAy		ACC,VF13,VF01y		IBEQ		VI02,VI05,SpriteDone
+		MADDz		VF02,VF14,VF01z		SQ			VF04,-1(VI02)
+
+
+		MULw.xyz	VF04,VF30,VF01w		NOP
+		MULAq.xyz	ACC,VF03,Q			IADD		VI02,VI02,VI04
+		MSUBq.xyz	VF03,VF05,Q			LQ			VF01,-1(VI02)
+		MADDq.xyz	VF05,VF05,Q			DIV			Q,VF00w,VF02w
+		NOP								SQ.xyz		VF08,-3(VI02)
+		ADDAx		ACC,VF15,VF00x		NOP
+		MADDAx		ACC,VF12,VF01x		SQ			VF03,-4(VI02)
+		MADDAy		ACC,VF13,VF01y		IBNE		VI02,VI05,SpriteLoop
+		MADDz		VF03,VF14,VF01z		SQ			VF05,-1(VI02)
+		
+SpriteDone:
+
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+
+		.endif
+
+
+
+
+
+SpriteCull:
+
+		.if 0
+
+		NOP								MR32.xyz	VF08,VF00			; upper left texcoords (0,0,1)
+		ADDw.xyz	VF29,VF00,VF00w		NOP								; lower right texcoords (1,1,1)
+		
+SpriteCullLoop:
+
+		NOP								IADD		VI02,VI02,VI04
+		NOP								LQ			VF01,-1(VI02)		; get xyzr
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF01y		NOP								; row 1 view transform
+		MADDAz		ACC,VF14,VF01z		NOP								; row 2 view transform
+		MSUBw		VF02,VF31,VF01w		NOP								; 1st vertex frustum coords
+		MADDw		VF03,VF31,VF01w		NOP								; 2nd vertex frustum coords
+		NOP								NOP
+		NOP								NOP
+		MULAw		ACC,VF11,VF02w		DIV			Q,VF00w,VF02w
+		MADD		VF04,VF10,VF02		NOP								; apply viewport scale to 1st vertex
+		MADD		VF05,VF10,VF03		NOP								; apply viewport scale to 2nd vertex
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF04xyz,VF04w		NOP
+		CLIPw.xyz	VF05xyz,VF05w		NOP
+		MULq.xyz	VF02,VF02,Q			NOP
+		MULq.xyz	VF03,VF03,Q			NOP
+		NOP								SQ.xyz		VF29,-3(VI02)
+		NOP								FCAND		VI01,0x000FFF
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		VF03,VI01
+		NOP								SQ			VF02,-4(VI02)
+		NOP								NOP
+		NOP								IBEQ		VI02,VI05,SpriteCullDone
+		NOP								SQ			VF03,-1(VI02)
+
+
+		NOP								IADD		VI02,VI02,VI04
+		NOP								LQ			VF01,-1(VI02)		; get xyzr
+		NOP								NOP
+		NOP								NOP
+		ADDAx		ACC,VF15,VF00x		NOP								; row 3 view transform
+		MADDAx		ACC,VF12,VF01x		NOP								; row 0 view transform
+		MADDAy		ACC,VF13,VF01y		NOP								; row 1 view transform
+		MADDAz		ACC,VF14,VF01z		NOP								; row 2 view transform
+		MADDw		VF02,VF31,VF01w		NOP								; 1st vertex frustum coords
+		MSUBw		VF03,VF31,VF01w		NOP								; 2nd vertex frustum coords
+		NOP								NOP
+		NOP								NOP
+		MULAw		ACC,VF11,VF02w		DIV			Q,VF00w,VF02w
+		MADD		VF04,VF10,VF02		NOP								; apply viewport scale to 1st vertex
+		MADD		VF05,VF10,VF03		NOP								; apply viewport scale to 2nd vertex
+		NOP								NOP
+		NOP								NOP
+		CLIPw.xyz	VF04xyz,VF04w		NOP
+		CLIPw.xyz	VF05xyz,VF05w		NOP
+		MULq.xyz	VF02,VF02,Q			NOP
+		MULq.xyz	VF03,VF03,Q			NOP
+		NOP								SQ.xyz		VF08,-3(VI02)
+		NOP								FCAND		VI01,0x000FFF
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		VF03,VI01
+		NOP								SQ			VF02,-4(VI02)
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,SpriteCullLoop
+		NOP								SQ			VF03,-1(VI02)
+
+SpriteCullDone:
+
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+
+		.else
+
+		ADDAx		ACC,VF15,VF00x		LQ			VF01,3(VI02)
+		MADDAx		ACC,VF12,VF01x		MR32.z		VF29,VF00
+		MADDAy		ACC,VF13,VF01y		MR32.xyz	VF08,VF00
+		MADDAz		ACC,VF14,VF01z		NOP
+		MADDw		VF02,VF31,VF01w		NOP
+		MSUBw		VF03,VF31,VF01w		MR32.y		VF29,VF29
+		MULAw		ACC,VF11,VF02w		LQ			VF01,7(VI02)
+		MADD		VF06,VF10,VF02		DIV			Q,VF00w,VF02w
+		MADD		VF07,VF10,VF03		NOP
+		ADDAx		ACC,VF15,VF00x		MR32.x		VF29,VF29
+		MADDAx		ACC,VF12,VF01x		NOP
+
+SpriteCullLoop:
+
+		CLIPw.xyz	VF06xyz,VF06w		IADD		VI02,VI02,VI04
+		CLIPw.xyz	VF07xyz,VF07w		NOP
+		MADDAy		ACC,VF13,VF01y		NOP
+		MADDAz		ACC,VF14,VF01z		SQ.xyz		VF29,-3(VI02)
+		MSUBw		VF04,VF31,VF01w		NOP
+		MADDw		VF05,VF31,VF01w		FCAND		VI01,0x000FFF
+		MULq.xyz	VF02,VF02,Q			IADDIU		VI01,VI01,0x7FFF
+		MULq.xyz	VF03,VF03,Q			LQ			VF01,7(VI02)
+		MULAw		ACC,VF11,VF04w		MFIR.w		VF03,VI01
+		MADD		VF06,VF10,VF04		DIV			Q,VF00w,VF04w
+		MADD		VF07,VF10,VF05		SQ			VF02,-4(VI02)
+		ADDAx		ACC,VF15,VF00x		IBEQ		VI02,VI05,SpriteCullDone
+		MADDAx		ACC,VF12,VF01x		SQ			VF03,-1(VI02)
+
+
+		CLIPw.xyz	VF06xyz,VF06w		IADD		VI02,VI02,VI04
+		CLIPw.xyz	VF07xyz,VF07w		NOP
+		MADDAy		ACC,VF13,VF01y		NOP
+		MADDAz		ACC,VF14,VF01z		SQ.xyz		VF08,-3(VI02)
+		MADDw		VF02,VF31,VF01w		NOP
+		MSUBw		VF03,VF31,VF01w		FCAND		VI01,0x000FFF
+		MULq.xyz	VF04,VF04,Q			IADDIU		VI01,VI01,0x7FFF
+		MULq.xyz	VF05,VF05,Q			LQ			VF01,7(VI02)
+		MULAw		ACC,VF11,VF02w		MFIR.w		VF05,VI01
+		MADD		VF06,VF10,VF02		DIV			Q,VF00w,VF02w
+		MADD		VF07,VF10,VF03		SQ			VF04,-4(VI02)
+		ADDAx		ACC,VF15,VF00x		IBNE		VI02,VI05,SpriteCullLoop
+		MADDAx		ACC,VF12,VF01x		SQ			VF05,-1(VI02)
+
+SpriteCullDone:
+
+		NOP								B			NextPrim			; go back for next prim
+		NOP								LQI			VF01,(VI02++)		; prefetch next tag
+
+		.endif
+		
+
+;-----------------------------------------------------------------------------------------------------------------------------
+
+; ------------------
+; VU1 BILLBOARD CODE
+; ------------------
+
+; The most general data format for billboards:
+
+; input              output       
+; (s0,t0)            (s0,t0,1,0)   
+; (r0,g0,b0,a0)      (r0,g0,b0,a0) 
+; (x,y,z)            (X0,Y0,Z0,0)  
+; (s1,t1)            (s1,t1,1,0)   
+; (r1,g1,b1,a1)      (r1,g1,b1,a1) 
+; (w,h)              (X1,Y1,Z1,0)  
+; (s2,t2)            (s2,t2,1,0)   
+; (r2,g2,b2,a2)      (r2,g2,b2,a2) 
+; (tx,ty,ty)         (X2,Y2,Z2,0)  
+; (s3,t3)            (s3,t3,1,0)   
+; (r3,g3,b3,a3)      (r3,g3,b3,a3) 
+; (ax,ay,az)         (X3,Y3,Z3,0)  
+
+; screen aligned billboards omit the axis vector (ax,ay,az)
+; and various optimised types can omit much of the rest of the data.
+
+
+		.include "vu1/defs.vsm"
+
+
+
+
+
+		; float regs
+		.equr	Tag,	VF01
+		
+		.equr	pvw,	VF01
+		.equr	xyz0,	VF02
+		.equr	xyz1,	VF03
+		.equr	xyz2,	VF04
+		.equr	xyz3,	VF05
+		.equr	dim,	VF06
+		.equr	pvl,	VF07
+		.equr	axis,	VF08
+		.equr	wdir,	VF13
+		.equr	vdir2,	VF13
+		.equr	viewvec,VF14
+		.equr	trans,	VF15
+		.equr	stq0,	VF16
+		.equr	stq1,	VF17
+		.equr	stq2,	VF18
+		.equr	stq3,	VF19
+		
+		.equr	udir,	VF20
+		.equr	vdir,	VF21
+		.equr	vscale,	VF22
+		.equr	cam,	VF25
+		.equr	col,	VF26
+		.equr	voff,	VF27
+		.equr	matWF0,	VF28
+		.equr	matWF1,	VF29
+		.equr	matWF2,	VF30
+		.equr	matWF3,	VF31
+
+		; integer regs
+		.equr	Input,	VI02
+		.equr	Output,	VI03
+		.equr	Step,	VI04
+		.equr	End,	VI05
+		
+
+
+		.scope
+
+ScreenAlignedBillboards:
+
+		NOP								IADDIU		VI01,VI00,COLR
+		NOP								IAND		VI01,VI14,VI01
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,ApplyColourBillboard
+		NOP								IADDIU		VI01,VI00,Label3
+Label3:
+
+
+		; double v basis vec
+		ADD			vdir2,vdir,vdir		NOP
+		
+		; init output pointer
+		NOP								IADDIU		Output,Input,0
+		
+@Loop:
+
+		; load geometric values
+		NOP								LQ			pvw,2(Input)
+		NOP								LQ			dim,5(Input)
+		NOP								LQ			pvl,8(Input)
+		
+		; transform world position of pivot by matWF:
+		ADDAx		ACC,matWF3,zero		NOP
+		MADDAx		ACC,matWF0,pvw.x	NOP
+		MADDAy		ACC,matWF1,pvw.y	NOP
+		MADDAz		ACC,matWF2,pvw.z	NOP
+		
+		; offset by pivot's local coords
+		MSUBAx		ACC,udir,pvl.x		NOP
+		MSUBAy		ACC,vdir,pvl.y		NOP
+		
+		; generate the 4 corners in frustum coords
+		MSUBAy		ACC,vdir,dim.y		NOP
+		MSUBx		xyz0,udir,dim.x		NOP
+		MADDx		xyz1,udir,dim.x		NOP
+		MADDAy		ACC,vdir2,dim.y		NOP
+		MSUBx		xyz2,udir,dim.x		NOP
+		MADDx		xyz3,udir,dim.x		NOP
+		
+		; culling tests
+		CLIPw.xyz	xyz0.xyz,xyz0.w		NOP
+		CLIPw.xyz	xyz1.xyz,xyz1.w		NOP
+		CLIPw.xyz	xyz2.xyz,xyz2.w		NOP
+		CLIPw.xyz	xyz3.xyz,xyz3.w		NOP
+		
+		; calc 1/w
+		NOP								DIV			Q,voff.w,xyz0.w
+		NOP								WAITQ
+		
+		; transform to homogeneous viewport coords
+		MULAw.xyz	ACC,voff,xyz0.w		NOP
+		MADD.xyz	xyz0,vscale,xyz0	NOP
+		MADD.xyz	xyz1,vscale,xyz1	NOP
+		MADD.xyz	xyz2,vscale,xyz2	NOP
+		MADD.xyz	xyz3,vscale,xyz3	NOP
+		
+		; projection
+		MULq.xyz	xyz0,xyz0,Q			NOP
+		MULq.xyz	xyz1,xyz1,Q			NOP
+		MULq.xyz	xyz2,xyz2,Q			NOP
+		MULq.xyz	xyz3,xyz3,Q			NOP
+		
+		; culling results
+		NOP								ISUBIU		VI01,VI00,1
+		NOP								MFIR.w		xyz0,VI01
+		NOP								MFIR.w		xyz1,VI01
+		NOP								FCAND		VI01,0xFFFFC0
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		xyz2,VI01
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		xyz3,VI01
+		
+		; store corners
+		NOP								SQ			xyz0, 2(Output)
+		NOP								SQ			xyz1, 5(Output)
+		NOP								SQ			xyz2, 8(Output)
+		NOP								SQ			xyz3,11(Output)
+
+		; step pointers and loop
+		NOP								IADD		Input,Input,Step
+		NOP								IADD		Output,Output,Step
+		NOP								NOP
+		NOP								IBNE		Output,End,@Loop
+		NOP								NOP
+		
+		; go back for more
+		NOP								LQI			Tag,(Output++)
+		NOP								B			NextPrim
+		NOP								IADDIU		Input,Output,0
+
+		.endscope
+		
+		
+		
+		.scope
+		
+LongAxisBillboards:
+
+		NOP								IADDIU		VI01,VI00,COLR
+		NOP								IAND		VI01,VI14,VI01
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,ApplyColourBillboard
+		NOP								IADDIU		VI01,VI00,Label4
+Label4:
+
+		; init output pointer
+		NOP								IADDIU		Output,Input,0
+
+@Loop:
+
+		; load geometric values
+		NOP								LQ			pvw,  2(Input)
+		NOP								LQ			dim,  5(Input)
+		NOP								LQ			pvl,  8(Input)
+		NOP								LQ			axis,11(Input)
+		
+		; get view vector in world space
+		SUB.xyz		viewvec,pvw,cam		NOP
+		
+		; generate transverse axis in world space
+		OPMULA.xyz	ACC,viewvec,axis	NOP
+		OPMSUB.xyz	trans,axis,viewvec	NOP
+		NOP								ERLENG		P,trans
+		NOP								WAITP
+		NOP								MFP.w		trans,P
+		MULw.xyz	trans,trans,trans.w	NOP
+		
+		; generate wdir
+		OPMULA.xyz	ACC,trans,axis		NOP
+		OPMSUB.xyz	wdir,axis,trans		NOP
+		
+		; transform to frustum coords
+		MULAx		ACC,matWF0,trans.x	NOP
+		MADDAy		ACC,matWF1,trans.y	NOP
+		MADDz		trans,matWF2,trans.z	NOP
+		
+		MULAx		ACC,matWF0,axis.x	NOP
+		MADDAy		ACC,matWF1,axis.y	NOP
+		MADDz		axis,matWF2,axis.z	NOP
+		
+		MULAx		ACC,matWF0,wdir.x	NOP
+		MADDAy		ACC,matWF1,wdir.y	NOP
+		MADDz		wdir,matWF2,wdir.z	NOP
+		
+		; transform world position of pivot by matWF:
+		ADDAx		ACC,matWF3,zero		NOP
+		MADDAx		ACC,matWF0,pvw.x	NOP
+		MADDAy		ACC,matWF1,pvw.y	NOP
+		MADDAz		ACC,matWF2,pvw.z	NOP
+		
+		; offset by pivot's local coords
+		MSUBAx		ACC,trans,pvl.x		NOP
+		MSUBAy		ACC,axis,pvl.y		NOP
+		MSUBAz		ACC,wdir,pvl.z		NOP
+		
+		; generate the 2 'left' corners in frustum coords
+		MSUBAx.xy	ACC,trans,dim.x		NOP
+		MSUBy		xyz0,axis,dim.y		NOP
+		MADDy		xyz2,axis,dim.y		NOP
+		
+		; generate the 2 'right' corners in frustum coords
+		MADDAx.xy	ACC,trans,dim.x		NOP
+		MADDAx.xy	ACC,trans,dim.x		NOP
+		MSUBy		xyz1,axis,dim.y		NOP
+		MADDy		xyz3,axis,dim.y		NOP
+		
+		; culling tests
+		CLIPw.xyz	xyz0.xyz,xyz0.w		NOP
+		CLIPw.xyz	xyz1.xyz,xyz1.w		NOP
+		CLIPw.xyz	xyz2.xyz,xyz2.w		NOP
+		CLIPw.xyz	xyz3.xyz,xyz3.w		NOP
+		
+		; transform to homogeneous viewport coords
+		MULAw.xyz	ACC,voff,xyz0.w		NOP
+		MADD.xyz	xyz0,vscale,xyz0	NOP
+		MADD.xyz	xyz1,vscale,xyz1	NOP
+		MULAw.xyz	ACC,voff,xyz2.w		NOP
+		MADD.xyz	xyz2,vscale,xyz2	NOP
+		MADD.xyz	xyz3,vscale,xyz3	NOP
+		
+		; load texcoords
+		NOP								LQ.xyz		stq0,0(Input)
+		NOP								LQ.xyz		stq1,3(Input)
+		NOP								LQ.xyz		stq2,6(Input)
+		NOP								LQ.xyz		stq3,9(Input)
+		
+		; homogeneous divs for 'top' corners
+		NOP								DIV			Q,voff.w,xyz0.w
+		NOP								WAITQ
+		MULq.xyz	xyz0,xyz0,Q			NOP
+		MULq.xyz	xyz1,xyz1,Q			NOP
+		MULq.xyz	stq0,stq0,Q			NOP
+		MULq.xyz	stq1,stq1,Q			NOP
+		
+		; homogeneous divs for 'bottom' corners
+		NOP								DIV			Q,voff.w,xyz2.w
+		NOP								WAITQ
+		MULq.xyz	xyz2,xyz2,Q			NOP
+		MULq.xyz	xyz3,xyz3,Q			NOP
+		MULq.xyz	stq2,stq2,Q			NOP
+		MULq.xyz	stq3,stq3,Q			NOP
+		
+		; culling results
+		NOP								ISUBIU		VI01,VI00,1
+		NOP								MFIR.w		xyz0,VI01
+		NOP								MFIR.w		xyz1,VI01
+		NOP								FCAND		VI01,0xFFFFC0
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		xyz2,VI01
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		xyz3,VI01
+		
+		; store corners
+		NOP								SQ			xyz0, 2(Output)
+		NOP								SQ			xyz1, 5(Output)
+		NOP								SQ			xyz2, 8(Output)
+		NOP								SQ			xyz3,11(Output)
+
+		; store texcoords
+		NOP								SQ.xyz		stq0,0(Output)
+		NOP								SQ.xyz		stq1,3(Output)
+		NOP								SQ.xyz		stq2,6(Output)
+		NOP								SQ.xyz		stq3,9(Output)
+
+		; step pointers and loop
+		NOP								IADD		Input,Input,Step
+		NOP								IADD		Output,Output,Step
+		NOP								NOP
+		NOP								IBNE		Output,End,@Loop
+		NOP								NOP
+		
+		; go back for more
+		NOP								LQI			Tag,(Output++)
+		NOP								B			NextPrim
+		NOP								IADDIU		Input,Output,0
+
+		.endscope
+		
+		
+		
+		
+		.scope
+		
+ShortAxisBillboards:
+
+		NOP								IADDIU		VI01,VI00,COLR
+		NOP								IAND		VI01,VI14,VI01
+		NOP								NOP
+		NOP								IBNE		VI01,VI00,ApplyColourBillboard
+		NOP								IADDIU		VI01,VI00,Label5
+Label5:
+
+		; init output pointer
+		NOP								IADDIU		Output,Input,0
+
+@Loop:
+
+		; load geometric values
+		NOP								LQ			pvw,  2(Input)
+		NOP								LQ			dim,  5(Input)
+		NOP								LQ			pvl,  8(Input)
+		NOP								LQ			axis,11(Input)
+		
+		; get view vector in world space
+		SUB.xyz		viewvec,pvw,cam		NOP
+		
+		; generate transverse axis in world space
+		OPMULA.xyz	ACC,axis,viewvec	NOP
+		OPMSUB.xyz	trans,viewvec,axis	NOP
+		NOP								ERLENG		P,trans
+		NOP								WAITP
+		NOP								MFP.w		trans,P
+		MULw.xyz	trans,trans,trans.w	NOP
+		
+		; generate wdir
+		OPMULA.xyz	ACC,trans,axis		NOP
+		OPMSUB.xyz	wdir,axis,trans		NOP
+		
+		; transform to frustum coords
+		MULAx		ACC,matWF0,trans.x	NOP
+		MADDAy		ACC,matWF1,trans.y	NOP
+		MADDz		trans,matWF2,trans.z	NOP
+		
+		MULAx		ACC,matWF0,axis.x	NOP
+		MADDAy		ACC,matWF1,axis.y	NOP
+		MADDz		axis,matWF2,axis.z	NOP
+		
+		MULAx		ACC,matWF0,wdir.x	NOP
+		MADDAy		ACC,matWF1,wdir.y	NOP
+		MADDz		wdir,matWF2,wdir.z	NOP
+		
+		; transform world position of pivot by matWF:
+		ADDAx		ACC,matWF3,zero		NOP
+		MADDAx		ACC,matWF0,pvw.x	NOP
+		MADDAy		ACC,matWF1,pvw.y	NOP
+		MADDAz		ACC,matWF2,pvw.z	NOP
+		
+		; offset by pivot's local coords
+		MSUBAy		ACC,trans,pvl.y		NOP
+		MSUBAx		ACC,axis,pvl.x		NOP
+		MSUBAz		ACC,wdir,pvl.z		NOP
+		
+		; generate the 2 'left' corners in frustum coords
+		MSUBAy.xy	ACC,trans,dim.y		NOP
+		MSUBx		xyz0,axis,dim.x		NOP
+		MADDx		xyz2,axis,dim.x		NOP
+		
+		; generate the 2 'right' corners in frustum coords
+		MADDAy.xy	ACC,trans,dim.y		NOP
+		MADDAy.xy	ACC,trans,dim.y		NOP
+		MSUBx		xyz1,axis,dim.x		NOP
+		MADDx		xyz3,axis,dim.x		NOP
+		
+		; culling tests
+		CLIPw.xyz	xyz0.xyz,xyz0.w		NOP
+		CLIPw.xyz	xyz1.xyz,xyz1.w		NOP
+		CLIPw.xyz	xyz2.xyz,xyz2.w		NOP
+		CLIPw.xyz	xyz3.xyz,xyz3.w		NOP
+		
+		; transform to homogeneous viewport coords
+		MULAw.xyz	ACC,voff,xyz0.w		NOP
+		MADD.xyz	xyz0,vscale,xyz0	NOP
+		MADD.xyz	xyz1,vscale,xyz1	NOP
+		MULAw.xyz	ACC,voff,xyz2.w		NOP
+		MADD.xyz	xyz2,vscale,xyz2	NOP
+		MADD.xyz	xyz3,vscale,xyz3	NOP
+		
+		; load texcoords
+		NOP								LQ.xyz		stq0,0(Input)
+		NOP								LQ.xyz		stq1,3(Input)
+		NOP								LQ.xyz		stq2,6(Input)
+		NOP								LQ.xyz		stq3,9(Input)
+		
+		; homogeneous divs for 'top' corners
+		NOP								DIV			Q,voff.w,xyz0.w
+		NOP								WAITQ
+		MULq.xyz	xyz0,xyz0,Q			NOP
+		MULq.xyz	xyz1,xyz1,Q			NOP
+		MULq.xyz	stq0,stq0,Q			NOP
+		MULq.xyz	stq1,stq1,Q			NOP
+		
+		; homogeneous divs for 'bottom' corners
+		NOP								DIV			Q,voff.w,xyz2.w
+		NOP								WAITQ
+		MULq.xyz	xyz2,xyz2,Q			NOP
+		MULq.xyz	xyz3,xyz3,Q			NOP
+		MULq.xyz	stq2,stq2,Q			NOP
+		MULq.xyz	stq3,stq3,Q			NOP
+		
+		; culling results
+		NOP								ISUBIU		VI01,VI00,1
+		NOP								MFIR.w		xyz0,VI01
+		NOP								MFIR.w		xyz1,VI01
+		NOP								FCAND		VI01,0xFFFFC0
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		xyz2,VI01
+		NOP								FCAND		VI01,0x03FFFF
+		NOP								IADDIU		VI01,VI01,0x7FFF
+		NOP								MFIR.w		xyz3,VI01
+		
+		; store corners
+		NOP								SQ			xyz0, 2(Output)
+		NOP								SQ			xyz1, 5(Output)
+		NOP								SQ			xyz2, 8(Output)
+		NOP								SQ			xyz3,11(Output)
+
+		; store texcoords
+		NOP								SQ.xyz		stq0,0(Output)
+		NOP								SQ.xyz		stq1,3(Output)
+		NOP								SQ.xyz		stq2,6(Output)
+		NOP								SQ.xyz		stq3,9(Output)
+
+		; step pointers and loop
+		NOP								IADD		Input,Input,Step
+		NOP								IADD		Output,Output,Step
+		NOP								NOP
+		NOP								IBNE		Output,End,@Loop
+		NOP								NOP
+		
+		; go back for more
+		NOP								LQI			Tag,(Output++)
+		NOP								B			NextPrim
+		NOP								IADDIU		Input,Output,0
+
+		.endscope
+		
+		
+; applying material colour to a billboard
+ApplyColourBillboard:
+
+		.if 0
+		; unoptimised version
+
+		NOP								LOI			8388608
+		ADDAi		ACC,VF00,I			LOI			8388863
+		
+LoopACB:NOP								LQ.xyz		VF01,1(VI02)
+		NOP								IADDIU		VI02,VI02,3
+		NOP								NOP
+		NOP								NOP
+		ITOF0.xyz	VF02,VF01			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MADD.xyz	VF03,VF02,col		NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								NOP
+		MINIi.xyz	VF04,VF03,I			NOP
+		NOP								NOP
+		NOP								NOP
+		NOP								IBNE		VI02,VI05,LoopACB
+		NOP								SQ.xyz		VF04,-2(VI02)
+		
+		.else
+		; optimised version
+		
+		NOP								LOI			8388608
+		ADDAi		ACC,VF00,I			LQ.xyz		VF03,1(VI02)
+		ITOF0.xyz	VF03,VF03			LQ.xyz		VF02,4(VI02)
+		MADD.xyz	VF03,VF03,col		LOI			8388863
+		ITOF0.xyz	VF02,VF02			LQ.xyz		VF01,7(VI02)
+		
+LoopACB:MINIi.xyz	VF04,VF03,I			IADDIU		VI02,VI02,3
+		MADD.xyz	VF03,VF02,col		NOP
+		ITOF0.xyz	VF02,VF01			LQ.xyz		VF01,7(VI02)
+		NOP								IBNE		VI02,VI05,LoopACB
+		NOP								SQ.xyz		VF04,-2(VI02)
+		
+		.endif
+
+		NOP								JR			VI01
+		NOP								ISUB		VI02,VI02,VI06
+		
+;-----------------------------------------------------------------------------------------------------------------------------
+
+		; Can use this to see how much micromem is left. (The assembler warns if the code overflows.)
+		;.rept 93
+		;NOP								NOP
+		;.endr
+
+
+.EndMPG
+
+MPGEnd:
+
+
+
diff --git a/Code/Gfx/NGPS/NX/vu1code.h b/Code/Gfx/NGPS/NX/vu1code.h
new file mode 100644
index 0000000..3f99af2
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1code.h
@@ -0,0 +1,111 @@
+#ifndef __MICROCODE_H
+#define __MICROCODE_H
+
+// the __attribute__ things are there so the code can compile using GP relative
+// otherwise, the compiler assumes these go in the .lit4 section, and we get a link error, as they don't
+
+#ifdef __PLAT_NGPS__
+
+extern uint MPGStart       __attribute__((section(".vudata")));
+extern uint MPGEnd         __attribute__((section(".vudata")));
+extern uint Setup          __attribute__((section(".vudata")));
+extern uint Jump           __attribute__((section(".vudata")));
+extern uint Breakpoint     __attribute__((section(".vudata")));
+extern uint ParseInit      __attribute__((section(".vudata")));
+extern uint Parser         __attribute__((section(".vudata")));
+extern uint L_VF09         __attribute__((section(".vudata")));
+extern uint L_VF10         __attribute__((section(".vudata")));
+extern uint L_VF11         __attribute__((section(".vudata")));
+extern uint L_VF12         __attribute__((section(".vudata")));
+extern uint L_VF13         __attribute__((section(".vudata")));
+extern uint L_VF14         __attribute__((section(".vudata")));
+extern uint L_VF15         __attribute__((section(".vudata")));
+extern uint L_VF16         __attribute__((section(".vudata")));
+extern uint L_VF17         __attribute__((section(".vudata")));
+extern uint L_VF18         __attribute__((section(".vudata")));
+extern uint L_VF19         __attribute__((section(".vudata")));
+extern uint L_VF20         __attribute__((section(".vudata")));
+extern uint L_VF21         __attribute__((section(".vudata")));
+extern uint L_VF22         __attribute__((section(".vudata")));
+extern uint L_VF23         __attribute__((section(".vudata")));
+extern uint L_VF24         __attribute__((section(".vudata")));
+extern uint L_VF25         __attribute__((section(".vudata")));
+extern uint L_VF26         __attribute__((section(".vudata")));
+extern uint L_VF27         __attribute__((section(".vudata")));
+extern uint L_VF28         __attribute__((section(".vudata")));
+extern uint L_VF39         __attribute__((section(".vudata")));
+extern uint L_VF30         __attribute__((section(".vudata")));
+extern uint L_VF31         __attribute__((section(".vudata")));
+extern uint GSPrim         __attribute__((section(".vudata")));
+extern uint Proj           __attribute__((section(".vudata")));
+extern uint PTex           __attribute__((section(".vudata")));
+extern uint Refl           __attribute__((section(".vudata")));
+extern uint Line           __attribute__((section(".vudata")));
+extern uint Skin           __attribute__((section(".vudata")));
+extern uint Sprites        __attribute__((section(".vudata")));
+extern uint SpriteCull     __attribute__((section(".vudata")));
+extern uint ReformatXforms __attribute__((section(".vudata")));
+extern uint ShadowVolumeSkin __attribute__((section(".vudata")));
+
+#endif
+
+#ifdef __PLAT_WN32__
+
+//#define Setup		 0
+//#define Jump		 2
+#define Breakpoint	 4
+#define ParseInit	 6
+#define Parser		 8
+#define L_VF09		10	// (Near, Far, k/(xRes/2), k/(yRes/2)) where k=viewport_scale_x, should be 2048 but is 1900 because of clipper problem
+#define L_VF10		12	// inverse viewport scale vector
+#define L_VF11		14	// inverse viewport offset vector
+#define L_VF12		16	// row 0, local to viewport transform
+#define L_VF13		18	// row 1, local to viewport transform
+#define L_VF14		20	// row 2, local to viewport transform
+#define L_VF15		22	// row 3, local to viewport transform
+#define L_VF16		24	// lightsource 2 colour (r,g,b,?)
+#define L_VF17		26	// row 0, reflection map transform
+#define L_VF18		28	// row 1, reflection map transform
+#define L_VF19		30	// row 2, reflection map transform
+#define L_VF20		32	// light vectors, x components
+#define L_VF21		34	// light vectors, y components
+#define L_VF22		36	// light vectors, z components
+#define L_VF23		38	// ambient colour (r,g,b,?)
+#define L_VF24		40	// lightsource 0 colour (r,g,b,?)
+#define L_VF25		42	// lightsource 1 colour (r,g,b,?)
+#define L_VF26		44	// texture projection scale vector
+#define L_VF27		46	// texture projection offset vector
+#define L_VF28		48	// saves the z-components of the view matrix during a z-push
+#define L_VF29		50	// temp skinning
+#define L_VF30		52	// temp skinning
+#define L_VF31		54	// temp skinning
+#define GSPrim		56
+#define Proj		58
+#define PTex		60
+#define Refl		62
+#define Line		64
+#define Skin		66
+#define Light		68
+#define LightT		70
+#define WibbleT		72
+#define LWibT		74
+#define AddZPush	76
+#define SubZPush	78
+#define Setup		80
+#define Jump		82
+#define SCAB		84
+#define LAB			86
+#define SHAB		88
+
+#endif
+
+#define PROJ 0x00
+#define CULL 0x01		// per-triangle view culling
+#define CLIP 0x02		// full 3D clipping of triangles
+#define SHDW 0x04		// skinned=>cast shadow into texture; non-skinned=>render mesh with projected shadow texture on it
+#define COLR 0x08		// apply colour at vertices
+#define FOGE 0x10		// calculate per-vertex fog coefficient
+#define WIRE 0x20		// render skinned as wireframe (but doesn't render all edges)
+
+#endif //__MICROCODE_H
+
diff --git a/Code/Gfx/NGPS/NX/vu1context.cpp b/Code/Gfx/NGPS/NX/vu1context.cpp
new file mode 100644
index 0000000..e5745c9
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1context.cpp
@@ -0,0 +1,455 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		THPS4													**
+**																			**
+**	Module:			vu1context				 								**
+**																			**
+**	File name:		vu1context.cpp											**
+**																			**
+**	Created by:		00/00/00	-	mrd										**
+**																			**
+**	Description:	sends contextual setup data to vu1 float registers		**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include "geomnode.h"
+#include "vu1context.h"
+#include "vu1code.h"
+#include "render.h"
+#include "dma.h"
+#include "vif.h"
+#include "gif.h"
+#include "gs.h"
+#include "light.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace NxPs2
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+CVu1Context::CVu1Context()
+{
+	Dbg_Assert("Don't use this constructor!");
+}
+
+
+void CVu1Context::Init()
+{
+	uint8 *dma_ploc = dma::pLoc;
+
+	//dma::pLoc = (uint8 *)&m_stmask;
+	dma::pLoc = (uint8 *)&m_stmask;
+	vu1::Loc = 0;
+
+	// set vifcodes
+	vif::STMASK(0);				// mp_light_group = NULL; in a very cheeky way
+	vif::STCYCL(1,1);
+	vif::UNPACK(0,V4_32,STD_CTXT_SIZE+1,REL,UNSIGNED,0);
+
+	// set m_giftag
+	gif::Tag1(gs::NOP, 1, PACKED, 0, 0, 0, STD_CTXT_SIZE, VU1_ADDR(L_VF12));
+
+	//SetExtended(false);
+
+	dma::pLoc = dma_ploc;
+}
+
+
+void CVu1Context::InitExtended()
+{
+	uint8 *dma_ploc = dma::pLoc;
+
+	//dma::pLoc = (uint8 *)&m_stmask;
+	dma::pLoc = (uint8 *)&m_stmask;
+	vu1::Loc = 0;
+
+	// set vifcodes
+	vif::STMASK(0);				// mp_light_group = NULL; in a very cheeky way
+	vif::STCYCL(1,1);
+	vif::UNPACK(0,V4_32,EXT_CTXT_SIZE+1,REL,UNSIGNED,0);
+
+	// set m_giftag
+	gif::Tag1(gs::NOP, 1, PACKED, 0, 0, 0, EXT_CTXT_SIZE, VU1_ADDR(L_VF12));
+
+	SetExtended(true);
+
+	update_lights();
+
+	dma::pLoc = dma_ploc;
+}
+
+
+CVu1Context::CVu1Context(const CVu1Context &ctxt)
+{
+	// Mick: Since this does not contain any pointers, it's safe to just copy it
+	// this cuts down on unwarrented initialization of the various vectors and stuff
+	// and should be fast as we are quad word aligned.
+	// Garrett: Even though we have a pointer to a light group, it is still OK to
+	// use memcpy() since this class doesn't manage the instance it points to.
+
+	if (ctxt.IsExtended())
+	{
+		memcpy(this, &ctxt, (EXT_CTXT_SIZE+7)*16);
+	}
+	else
+	{
+		// reduced version
+		memcpy(this, &ctxt, (STD_CTXT_SIZE+7)*16);
+	}
+
+}
+
+
+void CVu1Context::StandardSetup(Mth::Matrix &localToWorld)
+{
+	// get the local to camera matrix
+	m_matrix = localToWorld * render::WorldToCamera;
+
+	// get the massaged matrix values
+	Mth::Matrix inv = m_matrix;
+	inv.InvertUniform();
+	m_translation = -inv[3] * SUB_INCH_PRECISION;
+	m_translation[0] = float(int(m_translation[0]));
+	m_translation[1] = float(int(m_translation[1]));
+	m_translation[2] = float(int(m_translation[2]));
+
+	Mth::Matrix adjusted_matrix = m_matrix;
+	//m_adjustedMatrix = m_matrix;
+	adjusted_matrix[3] = -m_translation * m_matrix * RECIPROCAL_SUB_INCH_PRECISION;
+
+	// generate the vu1 context data
+	//m_localToFrustum  = m_adjustedMatrix * render::CameraToFrustum;
+	Mth::Matrix temp;
+	sceVu0MulMatrix((sceVu0FVECTOR*)&temp,
+					(sceVu0FVECTOR*)&render::CameraToFrustum,
+					(sceVu0FVECTOR*)&adjusted_matrix);   	
+	//m_localToFrustum = temp;
+	
+//	m_localToViewport = m_localToFrustum * render::FrustumToViewport;
+	sceVu0MulMatrix((sceVu0FVECTOR*)&m_localToViewport,
+					(sceVu0FVECTOR*)&render::FrustumToViewport,
+					(sceVu0FVECTOR*)&temp);
+
+	// Update light directions
+	if (IsExtended())
+	{
+		update_lights();
+	}
+}
+
+
+
+CVu1Context *CVu1Context::Localise()
+{
+	dma::SetList(NULL);
+	CVu1Context *p_new_context = (CVu1Context*)dma::pLoc;
+
+	if (IsExtended())
+	{
+		memcpy(dma::pLoc, this, sizeof(CVu1Context));
+		dma::pLoc += sizeof(CVu1Context);
+	}
+	else
+	{
+		// reduced version
+		memcpy(dma::pLoc, this, (STD_CTXT_SIZE+7)*16);
+		dma::pLoc += (STD_CTXT_SIZE+7)*16;
+	}
+
+	return p_new_context;
+}
+
+
+
+
+CVu1Context *CVu1Context::LocaliseExtend()
+{
+	dma::SetList(NULL);
+	CVu1Context *p_new_context = (CVu1Context*)dma::pLoc;
+
+	if (IsExtended())
+	{
+		memcpy(dma::pLoc, this, sizeof(CVu1Context));
+	}
+	else
+	{
+		memcpy(dma::pLoc, this, (STD_CTXT_SIZE+7)*16);
+		p_new_context->InitExtended();
+	}
+	dma::pLoc += sizeof(CVu1Context);
+
+	return p_new_context;
+}
+
+
+
+
+void CVu1Context::HierarchicalSetup(Mth::Matrix &localToParent)
+{
+	// get the local to camera matrix
+	m_matrix = localToParent * m_matrix;
+
+	// get the massaged matrix values
+	Mth::Matrix temp = m_matrix;
+	m_translation = -(temp.InvertUniform())[3] * SUB_INCH_PRECISION;
+	m_translation[0] = float(int(m_translation[0]));
+	m_translation[1] = float(int(m_translation[1]));
+	m_translation[2] = float(int(m_translation[2]));
+
+	Mth::Matrix adjusted_matrix = m_matrix;
+	//m_adjustedMatrix = m_matrix;
+	adjusted_matrix[3] = -m_translation * m_matrix * RECIPROCAL_SUB_INCH_PRECISION;
+
+	// generate the vu1 context data
+//	m_localToFrustum  = m_adjustedMatrix * render::CameraToFrustum;
+	sceVu0MulMatrix((sceVu0FVECTOR*)&temp,
+					(sceVu0FVECTOR*)&render::CameraToFrustum,
+					(sceVu0FVECTOR*)&adjusted_matrix);   	
+	//m_localToFrustum = temp;
+	
+//	m_localToViewport = m_localToFrustum * render::FrustumToViewport;
+	sceVu0MulMatrix((sceVu0FVECTOR*)&m_localToViewport,
+					(sceVu0FVECTOR*)&render::FrustumToViewport,
+					(sceVu0FVECTOR*)&temp);
+
+	// Update light directions
+	if (IsExtended())
+	{
+		update_lights();
+	}
+}
+
+// Mick: This is taking 1.5% of our precious CPU time
+void CVu1Context::update_lights()
+{
+	Mth::Matrix temp;
+	temp[W].Set(0.0f,0.0f,0.0f,1.0f);
+
+	// light vectors
+	if (mp_light_group)
+	{
+		temp[0] = mp_light_group->GetDirection(0);
+		temp[1] = mp_light_group->GetDirection(1);
+		temp[2] = mp_light_group->GetDirection(2);
+	
+		temp *= render::WorldToCamera;
+		temp.Transpose();
+		
+		sceVu0MulMatrix((sceVu0FVECTOR*)&m_lightVecsX,
+						(sceVu0FVECTOR*)&temp,
+						(sceVu0FVECTOR*)&m_matrix);   	
+	
+		m_ambientColour = mp_light_group->GetAmbientColor() * 1.0f/128.0f;
+		m_lightColour0  = mp_light_group->GetDiffuseColor(0) * 1.0f/128.0f;
+		m_lightColour1  = mp_light_group->GetDiffuseColor(1) * 1.0f/128.0f;
+		m_lightColour2  = mp_light_group->GetDiffuseColor(2) * 1.0f/128.0f;
+	}
+	else
+	{
+		temp[0] = CLightGroup::sGetDefaultDirection(0);
+		temp[1] = CLightGroup::sGetDefaultDirection(1);
+		temp[2] = CLightGroup::sGetDefaultDirection(2);
+
+		temp *= render::WorldToCamera;
+		temp.Transpose();
+		
+		sceVu0MulMatrix((sceVu0FVECTOR*)&m_lightVecsX,
+						(sceVu0FVECTOR*)&temp,
+						(sceVu0FVECTOR*)&m_matrix);   	
+	
+		m_ambientColour = CLightGroup::sGetDefaultAmbientColor() * 1.0f/128.0f;
+		m_lightColour0  = CLightGroup::sGetDefaultDiffuseColor(0) * 1.0f/128.0f;
+		m_lightColour1  = CLightGroup::sGetDefaultDiffuseColor(1) * 1.0f/128.0f;
+		m_lightColour2  = CLightGroup::sGetDefaultDiffuseColor(2) * 1.0f/128.0f;
+	}
+
+	
+}
+
+
+void CVu1Context::SetupAsSky()
+{
+	// get the local to camera matrix
+	m_matrix = render::WorldToCamera;
+
+	// get the massaged matrix values
+	m_matrix[3].Set(0,0,0,1);
+	m_translation.Set(0,0,0,1);
+
+	Mth::Matrix adjusted_matrix = m_matrix;
+	//m_adjustedMatrix = m_matrix;
+	adjusted_matrix.ScaleLocal(Mth::Vector(RECIPROCAL_SUB_INCH_PRECISION, RECIPROCAL_SUB_INCH_PRECISION, RECIPROCAL_SUB_INCH_PRECISION, 1.0f));
+
+	// generate the vu1 context data
+	//m_viewportScale   = render::SkyViewportScale;
+	//m_viewportOffset  = render::SkyViewportOffset;
+	//m_localToFrustum  = adjusted_matrix * render::CameraToFrustum;
+	m_localToViewport = adjusted_matrix * render::CameraToFrustum * render::SkyFrustumToViewport;
+}
+
+
+void CVu1Context::AddZPush(float zPush)
+{
+	m_localToViewport[0][2] += m_localToViewport[0][3] * zPush * 16.0f * render::InverseViewportScale[3];
+	m_localToViewport[1][2] += m_localToViewport[1][3] * zPush * 16.0f * render::InverseViewportScale[3];
+	m_localToViewport[2][2] += m_localToViewport[2][3] * zPush * 16.0f * render::InverseViewportScale[3];
+	m_localToViewport[3][2] += m_localToViewport[3][3] * zPush * 16.0f * render::InverseViewportScale[3];
+}
+
+
+void CVu1Context::WibbleUVs(float *pData, uint Explicit)
+{
+	if (Explicit)
+	{
+		m_texOffset[0] = pData[8];
+		m_texOffset[1] = pData[9];
+	}
+	else
+	{
+		// Get number of vblanks, but reduce it to 24 bits so we don't lose accuracy in the float conversion.
+		// This will introduce a glitch every 6 days, but that's preferrable to the loss of accuracy.
+		int vblanks = Tmr::GetVblanks() & 0x00FFFFFF;
+
+		// convert to seconds as float
+		float t = (float)vblanks * (1.0f/60.0f);
+		float angle;
+
+		// u offset
+		angle = pData[2] * t + pData[6];
+		angle -= (2.0f*Mth::PI) * (float)(int)(angle * (0.5f/Mth::PI));		// reduce angle mod 2pi
+		m_texOffset[0] = pData[0] * t + pData[4] * sinf(angle);
+		m_texOffset[0] += 8.0f;
+		m_texOffset[0] -= (float)(((int)m_texOffset[0] >> 4) << 4);			// reduce offset mod 16
+		if (m_texOffset[0] < 0.0f)											// and put it in the range -8 to +8
+			m_texOffset[0] += 8.0f;
+		else
+			m_texOffset[0] -= 8.0f;
+
+		// v offset
+		angle = pData[3] * t + pData[7];
+		angle -= (2.0f*Mth::PI) * (float)(int)(angle * (0.5f/Mth::PI));		// reduce angle mod 2pi
+		m_texOffset[1] = pData[1] * t + pData[5] * sinf(angle);
+		m_texOffset[1] += 8.0f;
+		m_texOffset[1] -= (float)(((int)m_texOffset[1] >> 4) << 4);			// reduce offset mod 16
+		if (m_texOffset[1] < 0.0f)											// and put it in the range -8 to +8
+			m_texOffset[1] += 8.0f;
+		else
+			m_texOffset[1] -= 8.0f;
+
+	}
+}
+
+
+void CVu1Context::SetShadowVecs(float y)
+{
+	m_texScale  = Mth::Vector(0.0078125f, 0.0078125f, 0.0f, 0.0f);
+	m_texOffset = Mth::Vector(0.5f-0.0078125f*(render::ShadowCameraPosition[X]+RECIPROCAL_SUB_INCH_PRECISION*m_translation[X]),
+							  0.5f-0.0078125f*(render::ShadowCameraPosition[Z]+RECIPROCAL_SUB_INCH_PRECISION*m_translation[Z]),
+							  -((y+36.0f)+RECIPROCAL_SUB_INCH_PRECISION*m_translation[Y]),
+							  0.0f);
+}
+
+
+void CVu1Context::SetColour(CGeomNode *pNode)
+{
+	uint32 rgba = pNode->GetColor();
+   	m_texScale[X] = (float)(rgba     & 0xFF) * 0.0078125f;
+	m_texScale[Y] = (float)(rgba>> 8 & 0xFF) * 0.0078125f;
+	m_texScale[Z] = (float)(rgba>>16 & 0xFF) * 0.0078125f;
+	m_texScale[W] = (float)(rgba>>24       ) * 0.0078125f;
+}
+
+
+Mth::Vector *CVu1Context::GetColour()
+{
+	return &m_texScale;
+}
+
+
+void CVu1Context::SetLights(CLightGroup *pLightGroup)
+{
+	mp_light_group = pLightGroup;
+	update_lights();
+}
+
+
+void CVu1Context::SetReflectionVecs(sint16 uScale, sint16 vScale)
+{
+	Mth::Matrix temp;
+	float uf,vf;
+	uf = float(uScale)*(0.5f/256.0f);
+	vf = float(vScale)*(0.5f/256.0f);
+
+	temp[0] = Mth::Vector(uf, 0,  0,  0);
+	temp[1] = Mth::Vector(0,  vf, 0,  0);
+	temp[2] = Mth::Vector(0,  0,  1.4,0);
+	temp.Transpose();
+
+	sceVu0MulMatrix((sceVu0FVECTOR*)&m_reflVecsX,
+					(sceVu0FVECTOR*)&temp,
+					(sceVu0FVECTOR*)&m_matrix);   	
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace NxPs2
+
+
+
+
diff --git a/Code/Gfx/NGPS/NX/vu1context.h b/Code/Gfx/NGPS/NX/vu1context.h
new file mode 100644
index 0000000..2c6fc1d
--- /dev/null
+++ b/Code/Gfx/NGPS/NX/vu1context.h
@@ -0,0 +1,184 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		THPS4													**
+**																			**
+**	Module:			vu1context				 								**
+**																			**
+**	File name:		vu1context.h											**
+**																			**
+**	Created by:		00/00/00	-	mrd										**
+**																			**
+**	Description:	sends contextual setup data to vu1 float registers		**
+**																			**
+*****************************************************************************/
+
+#ifndef __NGPS_NX_VU1CONTEXT_H
+#define __NGPS_NX_VU1CONTEXT_H
+
+#ifdef __PLAT_NGPS__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace NxPs2
+{
+
+
+#define STD_CTXT_SIZE 5
+#define EXT_CTXT_SIZE 16
+
+
+
+class CVu1Context
+{
+public:
+
+	CVu1Context();
+	void Init();
+	void InitExtended();
+	CVu1Context(const CVu1Context &node);
+	~CVu1Context() {}
+
+	void			StandardSetup(Mth::Matrix &localToWorld);
+	void			HierarchicalSetup(Mth::Matrix &localToParent);
+	void			SetupAsSky();
+	void			AddZPush(float zPush);
+	void			WibbleUVs(float *pData, uint Explicit);
+	void			SetShadowVecs(float y);
+	void			SetColour(CGeomNode *pNode);
+	void			SetAmbient(uint32 rgba);
+	void			SetLights(CLightGroup *pLightGroup);
+	void			SetReflectionVecs(sint16 uScale, sint16 vScale);
+	void			SetExtended(bool yes);
+
+	CVu1Context		*Localise();
+	CVu1Context		*LocaliseExtend();
+
+
+	Mth::Matrix 	*GetMatrix();
+	Mth::Vector		*GetTranslation();
+	int				GetDma();
+	Mth::Vector		*GetColour();
+	CLightGroup		*GetLights();
+	bool			IsExtended() const;
+
+private:
+	void			update_lights();
+
+	// non-dma data
+	Mth::Matrix		m_matrix;
+	Mth::Vector		m_translation;
+
+	// dma data... don't rearrange this part!
+	uint32			m_stmask;
+	CLightGroup *	mp_light_group;
+	uint32			m_stcycl;
+	uint32			m_unpack;
+	uint128			m_giftag;
+	Mth::Matrix		m_localToViewport;
+	Mth::Vector		m_texScale;
+	Mth::Vector		m_reflVecsX;
+	Mth::Vector		m_reflVecsY;
+	Mth::Vector		m_reflVecsZ;
+	Mth::Vector		m_lightVecsX;
+	Mth::Vector		m_lightVecsY;
+	Mth::Vector		m_lightVecsZ;
+	Mth::Vector		m_ambientColour;
+	Mth::Vector		m_lightColour0;
+	Mth::Vector		m_lightColour1;
+	Mth::Vector		m_lightColour2;
+	Mth::Vector		m_texOffset;
+
+	// keep the size a whole number of a quadwords
+};
+
+
+inline Mth::Matrix *CVu1Context::GetMatrix()
+{
+	return &m_matrix;
+}
+
+
+inline Mth::Vector *CVu1Context::GetTranslation()
+{
+	return &m_translation;
+}
+
+inline int CVu1Context::GetDma()
+{
+	return (int)&m_stmask;
+}
+
+inline CLightGroup *CVu1Context::GetLights()
+{
+	return mp_light_group;
+}
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline void CVu1Context::SetExtended(bool yes)
+{
+	if (yes)
+	{
+		m_stmask |= 1;
+	}
+	else
+	{
+		m_stmask &= ~1;
+	}
+}
+
+
+inline bool CVu1Context::IsExtended() const
+{
+	return (m_stmask & 1) ? true : false;
+}
+										
+										
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace NxPs2
+
+#endif	// __PLAT_NGPS__
+
+#endif	// __NGPS_NX_VU1CONTEXT_H
+
+
diff --git a/Code/Gfx/NGPS/PaletteGen.cpp b/Code/Gfx/NGPS/PaletteGen.cpp
new file mode 100644
index 0000000..0783050
--- /dev/null
+++ b/Code/Gfx/NGPS/PaletteGen.cpp
@@ -0,0 +1,979 @@
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+
+namespace Nx
+{
+
+#define	RED	1
+#define	GREEN	2
+#define BLUE	3
+#define ALPHA	4
+
+#define MAXDEPTH 5
+#define MAXCOLOR 256
+
+#define ColorRealLengthSq(a)	((a)->red*(a)->red + (a)->green*(a)->green + (a)->blue*(a)->blue + (a)->alpha*(a)->alpha)
+#define	ColorRealFromColorInt(o,i)										\
+{                                                                        \
+    (o)->red   =                                                         \
+        (((float)(((i)->r))) * (   (float)((1.0/255.0))));           \
+    (o)->green =                                                         \
+        (((float)(((i)->g))) * ( (float)((1.0/255.0))));           \
+    (o)->blue  =                                                         \
+        (((float)(((i)->b))) * (  (float)((1.0/255.0))));           \
+    (o)->alpha =                                                         \
+        (((float)(((i)->a))) * ( (float)((1.0/255.0))));           \
+}                                                                        \
+
+#define ColorRealScale(o,a,scale)										\
+{                                                                        \
+    (o)->red   = (((a)->red) * (   scale));                              \
+    (o)->green = (((a)->green) * ( scale));                              \
+    (o)->blue  = (((a)->blue) * (  scale));                              \
+    (o)->alpha = (((a)->alpha) * ( scale));                              \
+}                                                                        \
+
+#define ColorRealAdd(o,a,b)                                        \
+{                                                                        \
+    (o)->red   = (((a)->red) + (   (b)->red));                           \
+    (o)->green = (((a)->green) + ( (b)->green));                         \
+    (o)->blue  = (((a)->blue) + (  (b)->blue));                          \
+    (o)->alpha = (((a)->alpha) + ( (b)->alpha));                         \
+}                                                                        \
+
+#define ColorRealSub(o,a,b)                                        \
+{                                                                        \
+    (o)->red   = (((a)->red) - (   (b)->red));                           \
+    (o)->green = (((a)->green) - ( (b)->green));                         \
+    (o)->blue  = (((a)->blue) - (  (b)->blue));                          \
+    (o)->alpha = (((a)->alpha) - ( (b)->alpha));                         \
+}                                                                        \
+
+typedef uint32 OctantMap;
+
+/* local */
+static OctantMap    *p_splice = NULL;
+
+/*************************************************************************/
+static LeafNode    *
+InitLeaf(LeafNode * Leaf)
+{
+    Dbg_Assert( Leaf );
+
+    Leaf->palIndex = 0;
+    Leaf->weight = 0.0f;
+    Leaf->ac.red = 0.0f;
+    Leaf->ac.green = 0.0f;
+    Leaf->ac.blue = 0.0f;
+    Leaf->ac.alpha = 0.0f;
+    Leaf->m2 = 0.0f;
+    
+	return Leaf;
+}
+
+/*************************************************************************/
+static BranchNode  *
+InitBranch(BranchNode * Branch)
+{
+    int                 i;
+
+    Dbg_Assert( Branch );
+
+    for (i = 0; i < 16; i++)
+    {
+        Branch->dir[i] = (OctNode *)NULL;
+    }
+
+    return Branch;
+}
+
+/*************************************************************************/
+static OctNode     *
+CreateCube( void )
+{
+    OctNode            *cube;
+
+	cube = (OctNode*) Mem::Calloc( 1, sizeof( OctNode ));
+    return(cube);
+}
+
+/*************************************************************************/
+static              OctantMap
+GetOctAdr(Image::RGBA * c)
+{
+    int                 cs = 8 - MAXDEPTH;
+
+    Dbg_Assert(c);
+
+    return((p_splice[c->r >> cs] << 3) | (p_splice[c->g >> cs] << 2) |
+             (p_splice[c->b >> cs] << 1) | (p_splice[c->a >> cs] << 0));
+}
+
+/*************************************************************************/
+static OctNode     *
+AllocateToLeaf(PalQuant * pq, OctNode * root, OctantMap Octs, int depth)
+{
+
+    Dbg_Assert(pq);
+    Dbg_Assert(root);
+
+    /* return leaf */
+    if (depth == 0)
+    {
+        return(root);
+    }
+
+    /* populate branch */
+    if (!root->Branch.dir[Octs & 15])
+    {
+        OctNode            *node;
+
+        node = CreateCube();//(pq->cubefreelist);
+        root->Branch.dir[Octs & 15] = node;
+        if (depth == 1)
+        {
+            InitLeaf(&node->Leaf);
+        }
+        else
+        {
+            InitBranch(&node->Branch);
+        }
+    }
+
+    return(AllocateToLeaf
+             (pq, root->Branch.dir[Octs & 15], Octs >> 4, depth - 1));
+}
+
+/*************************************************************************/
+void
+PalQuantAddImage(PalQuant *pq, uint8 *img, int width, int height, int image_bpp, float weight)
+{
+    int     i, size;//, stride;
+    //unsigned char		*pixels;    
+
+    Dbg_Assert(pq);
+    Dbg_Assert(img);
+
+    //pixels = (unsigned char*) texture->buf;//GetTexelData( 0 );    
+	size = width * height;
+
+    switch (image_bpp / 8)
+    {
+		case 3:
+		{			
+#if 0
+			Image::RGBA         color;
+            ColorReal         rColor;
+            OctNode            *leaf;
+            OctantMap           Octs;
+
+			color24* colors= (color24*)img;
+			for( i = 0; i < size; i++ )
+			{
+				color.red = colors[i].r;
+				color.green = colors[i].g;
+				color.blue = colors[i].b;
+				color.alpha = 255;			
+			
+				/* build down to leaf */
+				Octs = GetOctAdr(&color);
+				leaf = AllocateToLeaf(pq, pq->root, Octs, MAXDEPTH);
+
+				ColorRealFromColorInt(&rColor, &color);
+				leaf->Leaf.weight += weight;
+				ColorRealScale(&rColor, &rColor, weight);
+				ColorRealAdd(&leaf->Leaf.ac, &leaf->Leaf.ac,
+							  &rColor);
+				leaf->Leaf.m2 += weight*ColorRealLengthSq(&rColor);
+			}
+#endif
+			Dbg_Assert(0);
+			break;
+		}
+		case 4:
+		{
+			Image::RGBA         color;
+            ColorReal         rColor;
+            OctNode            *leaf;
+            OctantMap           Octs;
+
+			uint32* p_colors= (uint32*)img;
+			for( i = 0; i < size; i++ )
+			{
+				color = *((Image::RGBA *) &p_colors[i]);
+			
+				/* build down to leaf */
+				Octs = GetOctAdr(&color);
+				leaf = AllocateToLeaf(pq, pq->root, Octs, MAXDEPTH);
+
+				ColorRealFromColorInt(&rColor, &color);
+				leaf->Leaf.weight += weight;
+				ColorRealScale(&rColor, &rColor, weight);
+				ColorRealAdd(&leaf->Leaf.ac, &leaf->Leaf.ac,
+							  &rColor);
+				leaf->Leaf.m2 += weight*ColorRealLengthSq(&rColor);
+			}
+			break;
+		}
+
+        default:
+			break;
+    }    
+}
+
+/*************************************************************************/
+
+/*************************************************************************/
+static void
+assignindex(OctNode * root, Image::RGBA * origin, int depth, box * region,
+            int palIndex)
+{
+    int                 width, dr, dg, db, da, dR, dG, dB, dA;
+    int                 i;
+
+    Dbg_Assert(origin);
+    Dbg_Assert(region);
+
+    if (!root)
+        return;
+
+    width = 1 << depth;
+
+    dr = origin->r - region->col1.r;
+    dg = origin->g - region->col1.g;
+    db = origin->b - region->col1.b;
+    da = origin->a - region->col1.a;
+    if (dr >= 0 || dg >= 0 || db >= 0 || da >= 0)
+    {
+        return;
+    }
+
+    dR = region->col0.r - origin->r;
+    dG = region->col0.g - origin->g;
+    dB = region->col0.b - origin->b;
+    dA = region->col0.a - origin->a;
+    if (dR >= width || dG >= width || dB >= width || dA >= width)
+    {
+        return;
+    }
+
+    /* wholly inside region and a leaf? */
+    if (dr <= -width && dg <= -width && db <= -width && da <= -width)
+        if (dR <= 0 && dG <= 0 && dB <= 0 && dA <= 0)
+            if (depth == 0)
+            {
+                root->Leaf.palIndex = (unsigned char) palIndex;
+                return;
+            }
+
+    /* try children */
+    depth--;
+    for (i = 0; i < 16; i++)
+    {
+        Image::RGBA              suborigin;
+
+        suborigin.r = origin->r + (((i >> 3) & 1) << depth);
+        suborigin.g = origin->g + (((i >> 2) & 1) << depth);
+        suborigin.b = origin->b + (((i >> 1) & 1) << depth);
+        suborigin.a = origin->a + (((i >> 0) & 1) << depth);
+
+        assignindex(root->Branch.dir[i], &suborigin, depth, region, palIndex);
+    }    
+}
+
+/*************************************************************************/
+
+/* Assign palIndex to leaves */
+static void
+nAssign(int palIndex, OctNode * root, box * cube)
+{
+    Image::RGBA              origin;
+
+    Dbg_Assert(root);
+    Dbg_Assert(cube);
+
+    origin.r = 0;
+    origin.g = 0;
+    origin.b = 0;
+    origin.a = 0;
+    assignindex(root, &origin, MAXDEPTH, cube, palIndex);    
+}
+
+/*************************************************************************/
+static void
+addvolume(OctNode * root, Image::RGBA * origin, int depth, box * region,
+          LeafNode * volume)
+{
+    int                 width, dr, dg, db, da, dR, dG, dB, dA;
+    int                 i;
+
+    Dbg_Assert(origin);
+    Dbg_Assert(region);
+
+    if (!root)
+        return;
+
+    width = 1 << depth;
+
+    dr = origin->r - region->col1.r;
+    dg = origin->g - region->col1.g;
+    db = origin->b - region->col1.b;
+    da = origin->a - region->col1.a;
+    if (dr >= 0 || dg >= 0 || db >= 0 || da >= 0)
+    {
+        return;
+    }
+
+    dR = region->col0.r - origin->r;
+    dG = region->col0.g - origin->g;
+    dB = region->col0.b - origin->b;
+    dA = region->col0.a - origin->a;
+    if (dR >= width || dG >= width || dB >= width || dA >= width)
+    {
+        return;
+    }
+
+    /* wholly inside region? */
+    if (dr <= -width && dg <= -width && db <= -width && da <= -width)
+        if (dR <= 0 && dG <= 0 && dB <= 0 && dA <= 0)
+#ifndef CACHEWEIGHTS
+            if (depth == 0)    /* we need to visit each leaf */
+#endif
+            {
+                volume->weight += root->Leaf.weight;
+                ColorRealAdd(&volume->ac, &volume->ac, &root->Leaf.ac);
+                volume->m2 += root->Leaf.m2;
+                return;
+            }
+
+    /* try children */
+    depth--;
+    for (i = 0; i < 16; i++)
+    {
+        Image::RGBA              suborigin;
+
+        suborigin.r = origin->r + (((i >> 3) & 1) << depth);
+        suborigin.g = origin->g + (((i >> 2) & 1) << depth);
+        suborigin.b = origin->b + (((i >> 1) & 1) << depth);
+        suborigin.a = origin->a + (((i >> 0) & 1) << depth);
+
+        addvolume(root->Branch.dir[i], &suborigin, depth, region, volume);
+    }    
+}
+
+/*************************************************************************/
+
+/* Compute sum over a box of any given statistic */
+static LeafNode    *
+nVol(LeafNode * Vol, OctNode * root, box * cube)
+{
+    Image::RGBA              origin;
+
+    Dbg_Assert(root);
+    Dbg_Assert(cube);
+
+    origin.r = 0;
+    origin.g = 0;
+    origin.b = 0;
+    origin.a = 0;
+    InitLeaf(Vol);
+    addvolume(root, &origin, MAXDEPTH, cube, Vol);
+
+    return(Vol);
+}
+
+/*************************************************************************/
+
+/* Compute the weighted variance of a box */
+
+/* NB: as with the raw statistics, this is really the variance * size */
+static              float
+nVar(OctNode * root, box * cube)
+{
+    LeafNode            Node;
+
+    Dbg_Assert(root);
+    Dbg_Assert(cube);
+
+    nVol(&Node, root, cube);
+
+    return(Node.m2 - (ColorRealLengthSq(&Node.ac) / Node.weight));
+}
+
+/*************************************************************************/
+
+/* We want to minimize the sum of the variances of two subboxes.
+ * The sum(c^2) terms can be ignored since their sum over both subboxes
+ * is the same (the sum for the whole box) no matter where we split.
+ * The remaining terms have a minus sign in the variance formula,
+ * so we drop the minus sign and MAXIMIZE the sum of the two terms.
+ */
+static              float
+nMaximize(OctNode * root, box * cube, int dir, int * cut,
+          LeafNode * whole)
+{
+    box                 infcube;
+    LeafNode            left, right;
+    float              maxsum, val, lastval;
+    int             i;
+
+    Dbg_Assert(root);
+    Dbg_Assert(cube);
+    Dbg_Assert(cut);
+    Dbg_Assert(whole);
+
+    lastval = maxsum = 0.0f;
+    *cut = -1;
+    infcube = *cube;
+    switch (dir)
+    {
+        case RED:
+            for (i = cube->col0.r; i < cube->col1.r; i++)
+            {
+                infcube.col1.r = (unsigned char) i;
+
+                nVol(&left, root, &infcube);
+                ColorRealSub(&right.ac, &whole->ac, &left.ac);
+                right.weight = whole->weight - left.weight;
+                if ((left.weight > 0.0f) && (right.weight > 0.0f))
+                {
+                    val = ColorRealLengthSq(&left.ac) / left.weight;
+                    val += ColorRealLengthSq(&right.ac) / right.weight;
+
+                    if (val > maxsum)
+                    {
+                        maxsum = val;
+                        *cut = i;
+                    }
+                    else if (val < lastval)
+                    {
+                        /* we've past the peak */
+                        break;
+                    }
+
+                    lastval = val;
+                }
+            }
+            break;
+
+        case GREEN:
+            for (i = cube->col0.g; i < cube->col1.g; i++)
+            {
+                infcube.col1.g = (unsigned char) i;
+
+                nVol(&left, root, &infcube);
+                ColorRealSub(&right.ac, &whole->ac, &left.ac);
+                right.weight = whole->weight - left.weight;
+                if ((left.weight > 0.0f) && (right.weight > 0.0f))
+                {
+                    val = ColorRealLengthSq(&left.ac) / left.weight;
+                    val += ColorRealLengthSq(&right.ac) / right.weight;
+
+                    if (val > maxsum)
+                    {
+                        maxsum = val;
+                        *cut = i;
+                    }
+                    else if (val < lastval)
+                    {
+                        /* we've past the peak */
+                        break;
+                    }
+
+                    lastval = val;
+                }
+            }
+            break;
+
+        case BLUE:
+            for (i = cube->col0.b; i < cube->col1.b; i++)
+            {
+                infcube.col1.b = (unsigned char) i;
+
+                nVol(&left, root, &infcube);
+                ColorRealSub(&right.ac, &whole->ac, &left.ac);
+                right.weight = whole->weight - left.weight;
+                if ((left.weight > 0.0f) && (right.weight > 0.0f))
+                {
+                    val = ColorRealLengthSq(&left.ac) / left.weight;
+                    val += ColorRealLengthSq(&right.ac) / right.weight;
+
+                    if (val > maxsum)
+                    {
+                        maxsum = val;
+                        *cut = i;
+                    }
+                    else if (val < lastval)
+                    {
+                        /* we've past the peak */
+                        break;
+                    }
+
+                    lastval = val;
+                }
+            }
+            break;
+
+        case ALPHA:
+            for (i = cube->col0.a; i < cube->col1.a; i++)
+            {
+                infcube.col1.a = (unsigned char) i;
+
+                nVol(&left, root, &infcube);
+                ColorRealSub(&right.ac, &whole->ac, &left.ac);
+                right.weight = whole->weight - left.weight;
+                if ((left.weight > 0.0f) && (right.weight > 0.0f))
+                {
+                    val = ColorRealLengthSq(&left.ac) / left.weight;
+                    val += ColorRealLengthSq(&right.ac) / right.weight;
+
+                    if (val > maxsum)
+                    {
+                        maxsum = val;
+                        *cut = i;
+                    }
+                    else if (val < lastval)
+                    {
+                        /* we've past the peak */
+                        break;
+                    }
+
+                    lastval = val;
+                }
+            }
+            break;
+    }
+
+    return(maxsum);
+}
+
+/*************************************************************************/
+static              bool
+nCut(OctNode * root, box * set1, box * set2)
+{
+    int             cutr, cutg, cutb, cuta;
+    float              maxr, maxg, maxb, maxa;
+    LeafNode            whole;
+
+    Dbg_Assert(root);
+    Dbg_Assert(set1);
+    Dbg_Assert(set2);
+
+    nVol(&whole, root, set1);
+    maxr = nMaximize(root, set1, RED, &cutr, &whole);
+    maxg = nMaximize(root, set1, GREEN, &cutg, &whole);
+    maxb = nMaximize(root, set1, BLUE, &cutb, &whole);
+    maxa = nMaximize(root, set1, ALPHA, &cuta, &whole);
+
+    /* did we find any splits? */
+    if ( (maxr == 0.0f) &&
+         (maxg == 0.0f) &&
+         (maxb == 0.0f) &&
+         (maxa == 0.0f) )
+    {
+        return false;
+    }
+
+    *set2 = *set1;
+
+    /* NEED TO CHECK FOR ALPHA TOO */
+
+    if (maxr >= maxg)
+    {
+        if (maxr >= maxb)
+        {
+            if (maxr >= maxa)
+            {
+                set1->col1.r = set2->col0.r = (unsigned char) cutr;
+            }
+            else
+            {
+                set1->col1.a = set2->col0.a = (unsigned char) cuta;
+            }
+        }
+        else
+        {
+            if (maxb >= maxa)
+            {
+                set1->col1.b = set2->col0.b = (unsigned char) cutb;
+            }
+            else
+            {
+                set1->col1.a = set2->col0.a = (unsigned char) cuta;
+            }
+        }
+    }
+    else if (maxg >= maxb)
+    {
+        if (maxg >= maxa)
+        {
+            set1->col1.g = set2->col0.g = (unsigned char) cutg;
+        }
+        else
+        {
+            set1->col1.a = set2->col0.a = (unsigned char) cuta;
+        }
+    }
+    else if (maxb >= maxa)
+    {
+        set1->col1.b = set2->col0.b = (unsigned char) cutb;
+    }
+    else
+    {
+        set1->col1.a = set2->col0.a = (unsigned char) cuta;
+    }
+
+
+    return true;
+}
+
+/*************************************************************************/
+#ifdef CACHEWEIGHTS
+static LeafNode    *
+CalcNodeWeights(OctNode * root, int depth)
+{
+    LeafNode           *Leaf;
+    int                 i;
+
+    Leaf = NULL;
+    if (root)
+    {
+        Leaf = &root->Leaf;
+
+        /* is it a branch? */
+        if (depth > 0)
+        {
+            InitLeaf(Leaf);
+            for (i = 0; i < 16; i++)
+            {
+                LeafNode           *SubNode =
+                    CalcNodeWeights(root->Branch.dir[i], depth - 1);
+
+                if (SubNode)
+                {
+                    Leaf->weight += SubNode->weight;
+                    ColorRealAdd(&Leaf->ac, &Leaf->ac, &SubNode->ac);
+                    Leaf->m2 += SubNode->m2;
+                }
+            }
+        }
+    }
+
+    return(Leaf);
+}
+#endif
+
+/*************************************************************************/
+static int
+CountLeafs(OctNode * root, int depth)
+{
+    int                 i, n;
+
+    n = 0;
+    if (root)
+    {
+        /* is it a branch? */
+        if (depth > 0)
+        {
+            for (i = 0; i < 16; i++)
+            {
+                n += CountLeafs(root->Branch.dir[i], depth - 1);
+            }
+        }
+        else
+        {
+            n = 1;
+        }
+    }
+
+    return(n);
+}
+
+/*************************************************************************/
+static int
+CountNodes(OctNode * root, int depth)
+{
+    int                 i, n;
+
+    n = 0;
+    if (root)
+    {
+        n = 1;
+
+        /* is it a branch? */
+        if (depth > 0)
+        {
+            for (i = 0; i < 16; i++)
+            {
+                n += CountNodes(root->Branch.dir[i], depth - 1);
+            }
+        }
+    }
+
+    return(n);
+}
+
+/*************************************************************************/
+
+/* 
+ * Note the use of 255.9999f value when generating the scale variable.
+ * This is used instead of 255.0f to prevent rounding errors.
+ */
+
+#define node2pal(rgb, node)                                              \
+{                                                                        \
+    int             quantize;                                        \
+    float              scale = ( ((node)->weight > 0) ?                 \
+                                  (255.9999f / (node)->weight) :         \
+                                  (float) 0 );                          \
+                                                                         \
+    quantize = (int) ((node)->ac.red * scale);                       \
+    (rgb)->r = (unsigned char) quantize;                                     \
+    quantize = (int) ((node)->ac.green * scale);                     \
+    (rgb)->g = (unsigned char) quantize;                                   \
+    quantize = (int) ((node)->ac.blue * scale);                      \
+    (rgb)->b = (unsigned char) quantize;                                    \
+    quantize = (int) ((node)->ac.alpha * scale);                     \
+    (rgb)->a = (unsigned char) quantize;                                   \
+}                                                                        \
+
+/*************************************************************************/
+int
+PalQuantResolvePalette(Image::RGBA * palette, int maxcols, PalQuant * pq)
+{
+    int             numcols, uniquecols, i, k, next;
+    
+#if (defined(RWDEBUG))
+    if (256 < maxcols)
+    {
+        RWMESSAGE(("256 < %d == maxcols", (int) maxcols));
+    }
+#endif /* (defined(RWDEBUG)) */
+
+
+    Dbg_Assert(palette);
+    Dbg_Assert(maxcols <= 256);
+    Dbg_Assert(pq);
+
+    numcols = maxcols;
+    uniquecols = CountLeafs(pq->root, MAXDEPTH);
+    if (numcols > uniquecols)
+    {
+        numcols = uniquecols;
+    }
+
+#ifdef CACHEWEIGHTS
+    /* cache weightings at every node */
+    CalcNodeWeights(pq->root, MAXDEPTH);
+#endif
+
+    /* divide and conquer */
+    pq->Mcube[0].col0.r = 0;
+    pq->Mcube[0].col0.g = 0;
+    pq->Mcube[0].col0.b = 0;
+    pq->Mcube[0].col0.a = 0;
+    pq->Mcube[0].col1.r = 1 << MAXDEPTH;
+    pq->Mcube[0].col1.g = 1 << MAXDEPTH;
+    pq->Mcube[0].col1.b = 1 << MAXDEPTH;
+    pq->Mcube[0].col1.a = 1 << MAXDEPTH;
+    pq->Mvv[0] = nVar(pq->root, &pq->Mcube[0]);
+    for (i = 1; i < numcols; i++)
+    {
+        float               maxvar;
+
+        /* find best box to split */
+        next = -1;
+        maxvar = 0.0f;
+        for (k = 0; k < i; k++)
+        {
+            if (pq->Mvv[k] > maxvar)
+            {
+                maxvar = pq->Mvv[k];
+                next = k;
+            }
+        }
+
+        /* stop if we couldn't find a box to split */
+        if (next == -1)
+        {
+            break;
+        }
+
+        /* split box */
+        if (nCut(pq->root, &pq->Mcube[next], &pq->Mcube[i]))
+        {
+            /* volume test ensures we won't try to cut one-cell box */
+            pq->Mvv[next] = nVar(pq->root, &pq->Mcube[next]);
+            pq->Mvv[i] = nVar(pq->root, &pq->Mcube[i]);
+        }
+        else
+        {
+            /* don't try to split this box again */
+            pq->Mvv[next] = 0.0f;
+            i--;
+        }
+    }
+
+    /* extract the new palette */
+    for (k = 0; k < maxcols; k++)
+    {
+        if (k < numcols)
+        {
+            LeafNode            Node;
+
+            nAssign(k, pq->root, &pq->Mcube[k]);
+            nVol(&Node, pq->root, &pq->Mcube[k]);
+            node2pal(&palette[k], &Node);
+        }
+        else
+        {
+            palette[k].r = 0;
+            palette[k].g = 0;
+            palette[k].b = 0;
+            palette[k].a = 0;
+        }
+    }
+
+    return(numcols);
+}
+
+#if 0
+/*************************************************************************/
+static              unsigned char
+GetIndex(OctNode * root, OctantMap Octs, int depth)
+{
+    unsigned char             result;
+
+    Dbg_Assert(root);
+
+    if (depth == 0)
+    {
+        result = root->Leaf.palIndex;
+    }
+    else
+    {
+        result = GetIndex(root->Branch.dir[Octs & 15], Octs >> 4, depth - 1);
+    }
+
+    return(result);
+}
+#endif
+
+/*************************************************************************/
+bool
+PalQuantInit(PalQuant * pq)
+{
+    int                 i, j, maxval;
+
+    Dbg_Assert(pq);
+    Dbg_Assert(!p_splice);
+
+	p_splice = (OctantMap *) Mem::Malloc(sizeof(OctantMap) * MAXCOLOR);
+
+    /* lookup mapping (8) bit-patterns to every 4th bit b31->b00 (least to most) */
+    maxval = 1 << MAXDEPTH;
+    for (i = 0; i < maxval; i++)
+    {
+        OctantMap           mask = 0;
+
+        for (j = 0; j < MAXDEPTH; j++)
+        {
+            mask |= (i & (1 << j)) ? (1 << ((MAXDEPTH - 1 - j) * 4)) : 0;
+        }
+        p_splice[i] = mask;
+    }
+
+    pq->Mcube = (box *) Mem::Calloc(sizeof(box), MAXCOLOR);
+    pq->Mvv = (float *) Mem::Calloc(sizeof(float), MAXCOLOR);
+
+    pq->root = CreateCube();
+    InitBranch(&pq->root->Branch);
+
+    return true;
+}
+
+/*************************************************************************/
+static void
+DeleteOctTree(PalQuant * pq, OctNode * root, int depth)
+{
+    int                 i;
+
+    Dbg_Assert(pq);
+
+    if (root)
+    {
+        /* is it a branch? */
+        if (depth > 0)
+        {
+            for (i = 0; i < 16; i++)
+            {
+                DeleteOctTree(pq, root->Branch.dir[i], depth - 1);
+            }
+        }
+
+		Mem::Free(root);
+    }
+}
+
+/*************************************************************************/
+void
+PalQuantTerm(PalQuant * pq)
+{
+
+    Dbg_Assert(pq);
+    Dbg_Assert(p_splice);
+
+
+    DeleteOctTree(pq, pq->root, MAXDEPTH);
+    pq->root = (OctNode *)NULL;
+
+    Mem::Free( pq->Mvv );
+    Mem::Free( pq->Mcube );
+	Mem::Free( p_splice );
+	p_splice = NULL;
+}
+
+/*************************************************************************/
+void GeneratePalette( Image::RGBA *p_palette, uint8* src_image, int width, int height, int image_bpp, int max_colors )
+{
+	int num_colors;
+	PalQuant pal_quant;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+	if( !PalQuantInit( &pal_quant ))
+	{
+		return;
+	}
+
+	PalQuantAddImage( &pal_quant, src_image, width, height, image_bpp, 1 );	
+	
+	// Create a palette for all of the mips
+	num_colors = PalQuantResolvePalette( p_palette, max_colors, &pal_quant );
+
+	// And swizzle for the PS2
+	Image::RGBA temp_rgba;
+	for (int j=0; j<256; j+=32)
+	{
+		for (int k=0; k<8; k++)
+		{
+			temp_rgba = p_palette[j+k+8];
+			p_palette[j+k+8] = p_palette[j+k+16];
+			p_palette[j+k+16] = temp_rgba;
+		}
+	}
+
+	
+	PalQuantTerm( &pal_quant );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+
+} // Namespace Nx  			
+
diff --git a/Code/Gfx/NGPS/PaletteGen.h b/Code/Gfx/NGPS/PaletteGen.h
new file mode 100644
index 0000000..399b0ed
--- /dev/null
+++ b/Code/Gfx/NGPS/PaletteGen.h
@@ -0,0 +1,85 @@
+#ifndef __NX_NGPS_PALETTEGEN_H__
+#define __NX_NGPS_PALETTEGEN_H__
+
+#include 
+
+/* caching weights at every node improves performance at expense of memory */
+#define CACHEWEIGHTSx
+
+namespace Nx
+{
+
+/****************************************************************************
+ Global Types
+ */
+
+typedef struct
+{
+	float	red;
+	float	green;
+	float	blue;
+	float	alpha;
+} ColorReal;
+
+typedef struct
+{
+	Image::RGBA col0;	/* min value, inclusive */
+	Image::RGBA col1;	/* max value, exclusive */
+} box;
+
+
+
+#ifdef CACHEWEIGHTS
+typedef struct OctNode OctNode;
+#else
+typedef union OctNode OctNode;
+#endif
+
+typedef struct LeafNode LeafNode;
+struct LeafNode
+{
+	float weight;
+	ColorReal ac;
+	float m2;
+	unsigned char palIndex;
+};
+
+typedef struct BranchNode BranchNode;
+struct BranchNode
+{
+	OctNode *dir[16];
+};
+
+#ifdef CACHEWEIGHTS
+struct OctNode
+#else
+union OctNode
+#endif
+{
+	LeafNode   Leaf;
+	BranchNode Branch;
+};
+
+/* working data */
+typedef struct
+{
+	box *Mcube;
+	float *Mvv;
+	OctNode *root;	
+}  PalQuant;
+
+/****************************************************************************
+ Function prototypes
+ */
+
+bool PalQuantInit(PalQuant *pq);
+void PalQuantTerm(PalQuant *pq);
+
+void PalQuantAddImage(PalQuant *pq, uint8 *img, int width, int height, int image_bpp, float weight);
+int PalQuantResolvePalette(unsigned char *palette, int maxcols, PalQuant *pq, int pbpp);
+void GeneratePalette( Image::RGBA *p_palette, uint8* src_image, int width, int height, int image_bpp, int max_colors );
+
+
+} // Namespace Nx  			
+
+#endif	// __NX_NGPS_PALETTEGEN_H__
diff --git a/Code/Gfx/NGPS/p_NxAnimCache.cpp b/Code/Gfx/NGPS/p_NxAnimCache.cpp
new file mode 100644
index 0000000..9792a73
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxAnimCache.cpp
@@ -0,0 +1,46 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_NxAnimCache.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  5/06/2002
+//****************************************************************************
+
+#include 
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2AnimCache::CPs2AnimCache( int lookupTableSize ) : CAnimCache( lookupTableSize )
+{
+	// Machine specific code here ............
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2AnimCache::~CPs2AnimCache()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+				
diff --git a/Code/Gfx/NGPS/p_NxAnimCache.h b/Code/Gfx/NGPS/p_NxAnimCache.h
new file mode 100644
index 0000000..e6c7850
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxAnimCache.h
@@ -0,0 +1,36 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_NxAnimCache.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  5/06/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NXANIMCACHE_H__
+#define	__GFX_P_NXANIMCACHE_H__
+    
+#include 
+
+namespace Nx
+{
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CGeom
+    
+class CPs2AnimCache : public CAnimCache
+{
+                                      
+public:
+						CPs2AnimCache( int lookupTableSize );
+	virtual 			~CPs2AnimCache();
+
+private:				// It's all private, as it is machine specific
+};
+
+} // Nx
+
+#endif 
diff --git a/Code/Gfx/NGPS/p_NxFont.cpp b/Code/Gfx/NGPS/p_NxFont.cpp
new file mode 100644
index 0000000..9974280
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxFont.cpp
@@ -0,0 +1,257 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.cpp
+
+#include 	"Gfx/NGPS/p_NxFont.h"
+#include 	"Gfx/NGPS/p_NxWin2D.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CFont
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Font::CPs2Font() : mp_plat_font(NULL)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Font::~CPs2Font()
+{
+	if (mp_plat_font)
+	{
+		plat_unload();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CPs2Font::plat_load(const char *filename)
+{
+	mp_plat_font = NxPs2::LoadFont(filename);
+
+	return (mp_plat_font != NULL);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Font::plat_set_spacings(int charSpacing, int spaceSpacing)
+{
+	mp_plat_font->mCharSpacing = charSpacing;
+	if (spaceSpacing > 0)
+		mp_plat_font->mSpaceSpacing = spaceSpacing;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Font::plat_set_rgba_table(Image::RGBA *pTab)
+{
+	for (int i = 0; i < 16; i++)
+		mp_plat_font->mRGBATab[i] = *((uint32 *) &pTab[i]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Font::plat_mark_as_button_font(bool isButton)
+{
+	NxPs2::pButtonsFont = (isButton) ? mp_plat_font : NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Font::plat_unload()
+{
+	NxPs2::UnloadFont(mp_plat_font);
+	mp_plat_font = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32		CPs2Font::plat_get_default_height() const
+{
+	Dbg_Assert(mp_plat_font);
+
+	return mp_plat_font->GetDefaultHeight();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32		CPs2Font::plat_get_default_base() const
+{
+	Dbg_Assert(mp_plat_font);
+
+	return mp_plat_font->GetDefaultBase();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Font::plat_query_string(char *String, float &width, float &height) const
+{
+	Dbg_Assert(mp_plat_font);
+
+	mp_plat_font->QueryString(String, width, height);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Text::CPs2Text(CWindow2D *p_window) : CText(p_window)
+{
+	mp_plat_text = new NxPs2::SText();
+
+	plat_initialize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Text::~CPs2Text()
+{
+	if (mp_plat_text)
+	{
+		delete mp_plat_text;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Text::plat_initialize()
+{
+	plat_update_engine();
+	plat_update_priority();
+	plat_update_hidden();
+	plat_update_window();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Text::plat_update_hidden()
+{
+	mp_plat_text->SetHidden(m_hidden);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Text::plat_update_engine()
+{
+	CPs2Font *p_ps2_font = static_cast(mp_font);
+
+	mp_plat_text->mp_string	= m_string;
+	if (p_ps2_font) {
+		mp_plat_text->mp_font	= p_ps2_font->GetEngineFont();
+	}
+
+	mp_plat_text->m_xpos	= m_xpos;
+	mp_plat_text->m_ypos	= m_ypos;
+	mp_plat_text->m_xscale	= m_xscale;
+	mp_plat_text->m_yscale	= m_yscale;
+	mp_plat_text->m_rgba	= *((uint32 *) &m_rgba);
+	mp_plat_text->m_color_override = m_color_override;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Text::plat_update_priority()
+{
+	// Update draw list
+	if (m_use_zbuffer)
+	{
+		mp_plat_text->SetZValue((uint32) m_zvalue);
+	} else {
+		mp_plat_text->SetPriority(m_priority);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Text::plat_update_window()
+{
+	CPs2Window2D *p_ps2_window = static_cast(mp_window);
+
+	if (p_ps2_window)
+	{
+		mp_plat_text->SetScissorWindow(p_ps2_window->GetEngineWindow());
+	} else {
+		mp_plat_text->SetScissorWindow(NULL);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CTextMan::s_plat_alloc_text_pool()
+{
+   	CPs2Text *p_text_array = new CPs2Text[vMAX_TEXT_INSTANCES];
+
+	for (int i = 0; i < vMAX_TEXT_INSTANCES; i++)
+	{
+//	   	CPs2Text *p_text = new CPs2Text(NULL);
+	   	CPs2Text *p_text = &p_text_array[i];
+		p_text->mp_next = sp_dynamic_text_list;
+		sp_dynamic_text_list = p_text;
+	}
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxFont.h b/Code/Gfx/NGPS/p_NxFont.h
new file mode 100644
index 0000000..268a20b
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxFont.h
@@ -0,0 +1,73 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.h
+
+#ifndef	__GFX_P_NX_FONT_H__
+#define	__GFX_P_NX_FONT_H__
+
+#include 	"Gfx/NxFont.h"
+#include 	"Gfx/NGPS/NX/chars.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CFont
+class	CPs2Font : public CFont
+{
+public:
+								CPs2Font();
+	virtual						~CPs2Font();
+
+	NxPs2::SFont *				GetEngineFont() const;
+
+private:		// It's all private, as it is machine specific
+	virtual	bool				plat_load(const char *filename);
+	virtual void				plat_set_spacings(int charSpacing, int spaceSpacing);
+	virtual void				plat_set_rgba_table(Image::RGBA *pTab);
+	virtual void 				plat_mark_as_button_font(bool isButton);
+	virtual void				plat_unload();
+
+	virtual	uint32				plat_get_default_height() const;
+	virtual	uint32				plat_get_default_base() const;
+	virtual void				plat_query_string(char *String, float &width, float &height) const;
+
+	// Machine specific members
+	NxPs2::SFont *				mp_plat_font;		// Pointer to engine font
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+class	CPs2Text : public CText
+{
+public:
+								CPs2Text(CWindow2D *p_window = NULL);
+	virtual						~CPs2Text();
+
+private:
+	//
+	virtual void				plat_initialize();
+
+	virtual void				plat_update_hidden();		// Tell engine of update
+	virtual void				plat_update_engine();		// Update engine primitives
+	virtual void				plat_update_priority();
+	virtual void				plat_update_window();
+
+	// Machine specific members
+	NxPs2::SText *				mp_plat_text;		// Pointer to engine text
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline NxPs2::SFont *		CPs2Font::GetEngineFont() const
+{
+	return mp_plat_font;
+}
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGPS/p_NxFontMan.cpp b/Code/Gfx/NGPS/p_NxFontMan.cpp
new file mode 100644
index 0000000..f3a3c42
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxFontMan.cpp
@@ -0,0 +1,48 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxFontMan.cpp - PS2 platform specific interface to the Font Manager
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+//
+
+#include	"gfx\nx.h"
+#include	"gfx\NxFontMan.h"
+#include	"gfx\NGPS\p_NxFont.h"
+
+#include 	"gfx\ngps\nx\chars.h"
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFont	*	CFontManager::s_plat_load_font(const char *pName)
+{
+	CPs2Font *p_new_font;
+
+	p_new_font = new CPs2Font;
+	p_new_font->Load(pName);
+
+	return p_new_font;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFontManager::s_plat_unload_font(CFont *pFont)
+{
+	pFont->Unload();
+}
+
+} 
+ 
diff --git a/Code/Gfx/NGPS/p_NxGeom.cpp b/Code/Gfx/NGPS/p_NxGeom.cpp
new file mode 100644
index 0000000..deae43c
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxGeom.cpp
@@ -0,0 +1,1111 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxGeom.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  3/4/2002
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+								
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Geom::CPs2Geom()
+{
+	// Machine specific code here ............
+	mp_oldInstance = NULL;
+	mp_instance = NULL;
+	mp_matrices = NULL;
+	m_rootMatrix.Ident();	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Geom::~CPs2Geom()
+{
+	if ( mp_oldInstance != NULL )
+	{
+		delete mp_oldInstance;
+		mp_oldInstance = NULL;
+	}
+
+	if ( mp_instance != NULL )
+	{
+		if ( m_cloned == vINSTANCE )
+		{
+			mp_instance->DeleteInstance();
+		}
+		else if ( m_cloned == vCOPY )
+		{
+			mp_instance->DeleteCopy();
+		}
+		mp_instance = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Geom::plat_load_geom_data(CMesh* pMesh, CModel* pModel, bool color_per_material)
+{
+	int numBones;
+	numBones = pModel->GetNumBones();
+
+	Mth::Matrix temp;
+	temp.Identity();
+	Dbg_Assert( mp_oldInstance == NULL );
+
+	// attempting to move skins over to .geom.ps2
+	mp_matrices = ((CPs2Model*)pModel)->GetMatrices();
+    if ( ((CPs2Mesh*)pMesh)->GetGeomNode() )
+	{
+		if ( numBones )
+		{
+			mp_instance = ((CPs2Mesh*)pMesh)->GetGeomNode()->CreateInstance( &m_rootMatrix, numBones, (Mth::Matrix *)mp_matrices );
+		}
+		else
+		{
+			mp_instance = ((CPs2Mesh*)pMesh)->GetGeomNode()->CreateInstance( &m_rootMatrix );
+		}
+	}
+	else
+	{
+		Dbg_Assert( pMesh );
+		Dbg_Assert( pMesh->GetTextureDictionary() );
+		NxPs2::sScene* pScene = ((Nx::CPs2TexDict*)pMesh->GetTextureDictionary())->GetEngineTextureDictionary();
+		Dbg_Assert( pScene );
+
+		if ( numBones )
+		{
+			mp_oldInstance = new NxPs2::CInstance(pScene, temp, color_per_material, numBones, mp_matrices );
+		}
+		else
+		{
+			mp_oldInstance = new NxPs2::CInstance(pScene, temp, color_per_material);
+		}
+	}
+
+	// remember the source mesh, so that we can do poly-hiding on it
+	mp_sourceMesh = (CPs2Mesh*)pMesh;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_finalize()
+{
+	if (mp_oldInstance)
+	{
+		// only supported on old-style instances
+		mp_oldInstance->SqueezeADC();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom *	CPs2Geom::plat_clone(bool instance, CScene* pDestScene)
+{
+	Dbg_MsgAssert(mp_oldInstance == NULL, ("Wrong version of CPs2Geom::plat_clone() for CInstances"));
+	Dbg_MsgAssert(mp_matrices == NULL, ("Wrong version of CPs2Geom::plat_clone() for matrix arrays"));
+
+	CPs2Scene *p_ps2_dest_scene = static_cast(pDestScene);
+	NxPs2::CGeomNode *p_scene_geom = (p_ps2_dest_scene) ? p_ps2_dest_scene->GetEngineCloneScene() : NULL;
+
+	// Copy into new sector
+	CPs2Geom *p_new_geom = new CPs2Geom(*this);
+
+	// Copy engine data
+	if (p_new_geom && mp_instance)
+	{
+		if (instance)
+		{
+			p_new_geom->mp_instance = mp_instance->CreateInstance(&p_new_geom->m_rootMatrix, p_scene_geom);
+		}
+		else
+		{
+			p_new_geom->mp_instance = mp_instance->CreateCopy(p_scene_geom);
+			//p_new_geom->mp_instance->SetMatrix(&p_new_geom->m_rootMatrix);
+		}
+	}
+
+	return p_new_geom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom *	CPs2Geom::plat_clone(bool instance, CModel* pDestModel)
+{
+	Dbg_MsgAssert(mp_instance == NULL, ("Wrong version of CPs2Geom::plat_clone() for CGeomNodes"));
+	Dbg_Assert(mp_oldInstance);
+
+	// Copy into new geom
+	CPs2Geom *p_new_geom = new CPs2Geom(*this);
+
+	Dbg_Assert(p_new_geom);
+
+	int numBones;
+	numBones = pDestModel->GetNumBones();
+
+	Mth::Matrix temp;
+	temp.Identity();
+
+	p_new_geom->mp_matrices = ((CPs2Model*)pDestModel)->GetMatrices();
+
+	NxPs2::sScene* pScene = mp_oldInstance->GetScene();
+	Dbg_Assert( pScene );
+
+	if ( numBones )
+	{
+		p_new_geom->mp_oldInstance = new NxPs2::CInstance(pScene, temp, mp_oldInstance->HasColorPerMaterial(), numBones, p_new_geom->mp_matrices );
+	}
+	else
+	{
+		p_new_geom->mp_oldInstance = new NxPs2::CInstance(pScene, temp, mp_oldInstance->HasColorPerMaterial());
+	}
+
+	Dbg_Assert(p_new_geom->mp_oldInstance);
+
+	// Copy the colors from instance
+	if (mp_oldInstance->HasColorPerMaterial())
+	{
+		int num_colors = mp_oldInstance->GetScene()->NumMeshes;
+		for (int i = 0; i < num_colors; i++)
+		{
+			p_new_geom->mp_oldInstance->SetMaterialColorByIndex(i, mp_oldInstance->GetMaterialColorByIndex(i));
+		}
+	}
+	else
+	{
+		p_new_geom->mp_oldInstance->SetColor(mp_oldInstance->GetColor());
+	}
+
+	return p_new_geom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CPs2Geom::plat_get_checksum()
+{
+	Dbg_Assert(mp_instance);
+
+	return mp_instance->GetChecksum();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_color(Image::RGBA rgba)
+{
+	// Engine call here
+	if ( mp_instance )
+	{
+		mp_instance->SetColored(true);
+//		printf( "Color 0x%x\n", *((uint32 *) &rgba ));
+		mp_instance->SetColor(*((uint32 *) &rgba));
+	}
+	else if (mp_oldInstance)
+	{
+		if (mp_oldInstance->HasColorPerMaterial())
+		{
+			for (int i = 0; i < mp_oldInstance->GetScene()->NumMeshes; i++)
+			{
+				mp_oldInstance->SetMaterialColorByIndex(i, *((uint32 *) &rgba));
+			}
+		}
+		else
+		{
+			mp_oldInstance->SetColor(*((uint32 *) &rgba));
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA CPs2Geom::plat_get_color() const
+{
+	// Engine call here
+	if ( mp_instance && mp_instance->IsColored() )
+	{
+		uint32 raw_data = mp_instance->GetColor();
+		return *((Image::RGBA *) &raw_data);
+	}
+	else if (mp_oldInstance)
+	{
+		uint32 raw_data = mp_oldInstance->GetColor();
+		return *((Image::RGBA *) &raw_data);
+	}
+	else
+	{
+		return Image::RGBA(128, 128, 128, 128);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_clear_color()
+{
+	// Engine call here
+	if ( mp_instance )
+	{
+		mp_instance->SetColored(false);
+	} else {
+		// Set to white
+		plat_set_color(Image::RGBA(128, 128, 128, 128));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CPs2Geom::plat_set_material_color(uint32 mat_checksum, int pass, Image::RGBA rgba)
+{
+	if (mp_oldInstance)
+	{
+		if (mp_oldInstance->HasColorPerMaterial())
+		{
+			mp_oldInstance->SetMaterialColor(mat_checksum, pass, *((uint32 *) &rgba));
+		}
+		else
+		{
+//			Dbg_Message( "%s %d doesn't have multicolor set", Script::FindChecksumName(mat_checksum), pass );
+		}
+		return true;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Trying to set the material color on a Geom that doesn't support it"));
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA	CPs2Geom::plat_get_material_color(uint32 mat_checksum, int pass)
+{
+	if (mp_oldInstance && mp_oldInstance->HasColorPerMaterial())
+	{
+		uint32 raw_data = mp_oldInstance->GetMaterialColor(mat_checksum, pass);
+		return *((Image::RGBA *) &raw_data);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Trying to get the material color on a Geom that doesn't support it"));
+		return Image::RGBA(128, 128, 128, 128);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_visibility(uint32 mask)
+{
+	// Engine call here
+	if (mp_instance)
+	{
+		mp_instance->SetVisibility(mask & 0xFF);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CPs2Geom::plat_get_visibility() const
+{
+	// Engine call here
+	if ( mp_instance )
+	{
+		return mp_instance->GetVisibility() | 0xFFFFFF00;		// To keep format compatible
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_active( bool active )
+{
+	if ( mp_oldInstance )
+	{
+		if ( active != mp_oldInstance->IsActive() )
+		{
+			mp_oldInstance->SetActive( active );
+		}
+	}
+
+	// New engine
+	if ( mp_instance )
+	{
+		if ( active != mp_instance->IsActive() )
+		{
+			mp_instance->SetActive( active );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Geom::plat_is_active() const
+{
+	if ( mp_instance )
+	{
+		return mp_instance->IsActive();
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::CBBox & CPs2Geom::plat_get_bounding_box() const
+{
+#if 0	// Enable this after THPS4 and do something about needing a static
+	if ( mp_instance )
+	{
+		static Mth::CBBox bbox;
+		bbox = mp_instance->GetBoundingBox();
+		return bbox;
+	} 
+	else
+#endif
+	{
+		// Garrett: TODO: change to renderable bounding box
+		Dbg_Assert(mp_coll_tri_data);
+		return mp_coll_tri_data->GetBBox();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector CPs2Geom::plat_get_bounding_sphere() const
+{
+	if ( mp_instance )
+	{
+		// CGeomNode-style
+		return mp_instance->GetBoundingSphere();
+	}
+	else if ( mp_oldInstance )
+	{
+		// CInstance-style
+		return mp_oldInstance->GetBoundingSphere();
+	}
+	else
+	{
+		return Mth::Vector(0.0f, 0.0f, 0.0f, 1.0e+10f);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	if ( mp_instance )
+	{
+		// CGeomNode-style
+		mp_instance->SetBoundingSphere( boundingSphere[X], boundingSphere[Y], boundingSphere[Z], boundingSphere[W] );
+	}
+	else if ( mp_oldInstance )
+	{
+		// CInstance-style
+		mp_oldInstance->SetBoundingSphere( boundingSphere[X], boundingSphere[Y], boundingSphere[Z], boundingSphere[W] );
+	}
+	else
+	{
+		Dbg_MsgAssert( 0, ( "Shouldn't get here" ) );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_world_position(const Mth::Vector& pos)
+{
+	// Garrett: we may need to do this in integer format instead to make it lossless
+	Mth::Vector delta_pos(pos - m_rootMatrix[W]);
+
+	// Set values
+	m_rootMatrix[W][X] = pos[X];
+	m_rootMatrix[W][Y] = pos[Y];
+	m_rootMatrix[W][Z] = pos[Z];	
+	//m_rootMatrix[W][W] = 1.0f;
+
+	// Engine call here
+	if (m_cloned != vCOPY)
+	{
+		update_engine_matrix();
+	} else {
+		mp_instance->Translate(delta_pos);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CPs2Geom::plat_get_world_position() const
+{
+	return m_rootMatrix[W];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_orientation(const Mth::Matrix& orient)
+{
+	// Set values
+	m_rootMatrix[X] = orient[X];
+	m_rootMatrix[Y] = orient[Y];
+	m_rootMatrix[Z] = orient[Z];
+
+	// Engine call here
+	if (m_cloned != vCOPY)
+	{
+		update_engine_matrix();
+	} else {
+		Dbg_MsgAssert(0, ("Don't call SetOrientation() on a CGeom copy"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Matrix &	CPs2Geom::plat_get_orientation() const
+{
+	Dbg_MsgAssert(0, ("CPs2Geom::plat_get_orientation() not implemented yet"));
+	return m_rootMatrix;			// This is not correct
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_rotate_y(Mth::ERot90 rot)
+{
+	// Engine call here
+	// Garrett: TEMP just set the world matrix
+	Mth::Matrix orig_rot_mat(m_rootMatrix), rot_mat;
+	float rad = (float) ((int) rot) * (Mth::PI * 0.5f);
+	CreateRotateYMatrix(rot_mat, rad);
+
+	orig_rot_mat[W] = Mth::Vector(0.0f, 0.0f, 0.0f, 1.0f);
+
+	rot_mat = orig_rot_mat * rot_mat;
+	//Dbg_Message("[X] (%f, %f, %f, %f)", rot_mat[X][X], rot_mat[X][Y],  rot_mat[X][Z],  rot_mat[X][W]);
+	//Dbg_Message("[Y] (%f, %f, %f, %f)", rot_mat[Y][X], rot_mat[Y][Y],  rot_mat[Y][Z],  rot_mat[Y][W]);
+	//Dbg_Message("[Z] (%f, %f, %f, %f)", rot_mat[Z][X], rot_mat[Z][Y],  rot_mat[Z][Z],  rot_mat[Z][W]);
+	//Dbg_Message("[W] (%f, %f, %f, %f)", rot_mat[W][X], rot_mat[W][Y],  rot_mat[W][Z],  rot_mat[W][W]);
+	
+	m_rootMatrix[X] = rot_mat[X];
+	m_rootMatrix[Y] = rot_mat[Y];
+	m_rootMatrix[Z] = rot_mat[Z];
+
+	if (m_cloned != vCOPY)
+	{
+		update_engine_matrix();
+	}
+	else
+	{
+		// Garrett: We may need to change the world pos to integer to avoid float losses
+		mp_instance->RotateY(m_rootMatrix[W], rot);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_transform(const Mth::Matrix& transform)
+{
+	// Set values
+	m_rootMatrix = transform;
+
+	// Engine call here
+	if (m_cloned != vCOPY)
+	{
+		update_engine_matrix();
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Don't call SetTransform() on a CGeom copy"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Matrix &	CPs2Geom::plat_get_transform() const
+{
+	return m_rootMatrix;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_scale(const Mth::Vector & scale)
+{
+	// Engine call here
+	Mth::Matrix orig_rot_mat(m_rootMatrix);
+
+	// orientation
+	m_rootMatrix[X] *= scale[X];
+	m_rootMatrix[Y] *= scale[Y];
+	m_rootMatrix[Z] *= scale[Z];
+
+#if 0		// Don't do this now since it is a local space scale
+	// position (since this is what will happen in the copy geometry)
+	m_rootMatrix[Mth::POS][X] *= scale[X];
+	m_rootMatrix[Mth::POS][Y] *= scale[Y];
+	m_rootMatrix[Mth::POS][Z] *= scale[Z];
+#endif
+
+	if (m_cloned != vCOPY)
+	{
+		update_engine_matrix();
+	}
+	else
+	{
+		Mth::Vector delta_scale;
+
+		delta_scale[X] = scale[X] /  orig_rot_mat[X].Length();
+		delta_scale[Y] = scale[Y] /  orig_rot_mat[Y].Length();
+		delta_scale[Z] = scale[Z] /  orig_rot_mat[Z].Length();
+
+		mp_instance->Scale(m_rootMatrix[W], delta_scale);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Mth::Vector CPs2Geom::plat_get_scale() const
+{
+	Mth::Vector scale;
+
+	scale[X] = m_rootMatrix[X].Length();
+	scale[Y] = m_rootMatrix[Y].Length();
+	scale[Z] = m_rootMatrix[Z].Length();
+
+	return scale;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::update_engine_matrix()
+{
+	if (m_cloned != vCOPY)
+	{
+		mp_instance->SetMatrix(&m_rootMatrix);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// not needed anymore
+#if 0
+void ConvertMatrix(Mth::Matrix* pMatrix, Mth::Matrix* pMat)
+{
+	/*
+	// right
+	(*pMat)[0][0] = (*pMatrix)[0][0];
+	(*pMat)[0][1] = (*pMatrix)[0][1];
+	(*pMat)[0][2] = (*pMatrix)[0][2];
+	(*pMat)[0][3] = (*pMatrix)[0][3];
+
+	// up
+	(*pMat)[1][0] = (*pMatrix)[1][0];
+	(*pMat)[1][1] = (*pMatrix)[1][1];
+	(*pMat)[1][2] = (*pMatrix)[1][2];
+	(*pMat)[1][3] = (*pMatrix)[1][3];
+
+	// at
+	(*pMat)[2][0] = (*pMatrix)[2][0];
+	(*pMat)[2][1] = (*pMatrix)[2][1];
+	(*pMat)[2][2] = (*pMatrix)[2][2];
+	(*pMat)[2][3] = (*pMatrix)[2][3];
+
+	// position
+	(*pMat)[3][0] = (*pMatrix)[3][0];
+	(*pMat)[3][1] = (*pMatrix)[3][1];
+	(*pMat)[3][2] = (*pMatrix)[3][2];
+	(*pMat)[3][3] = (*pMatrix)[3][3];
+
+	*/
+	
+	// the copy operator is more efficient
+	// (on PS2, does 4 quad copies.  Other platform does the same as above
+	*pMat  = *pMatrix;				 
+
+	// clear out the final column
+	(*pMat)[0][3] = 0.0f;//(*pMatrix)[3][0];
+	(*pMat)[1][3] = 0.0f;//(*pMatrix)[3][0];
+	(*pMat)[2][3] = 0.0f;//(*pMatrix)[3][0];
+	(*pMat)[3][3] = 1.0f;//(*pMatrix)[3][0];
+		
+	#if 0
+	// right
+	(*pMat)[0][0] = 1.0f;//(*pMatrix)[0][0];
+	(*pMat)[0][1] = 0.0f;//(*pMatrix)[1][0];
+	(*pMat)[0][2] = 0.0f;//(*pMatrix)[2][0];
+	(*pMat)[0][3] = 0.0f;//(*pMatrix)[3][0];
+
+	// up
+	(*pMat)[1][0] = 0.0f;//(*pMatrix)[0][1];
+	(*pMat)[1][1] = 1.0f;//(*pMatrix)[1][1];
+	(*pMat)[1][2] = 0.0f;//(*pMatrix)[2][1];
+	(*pMat)[1][3] = 0.0f;//(*pMatrix)[3][1];
+
+	// at
+	(*pMat)[2][0] = 0.0f;//(*pMatrix)[0][2];
+	(*pMat)[2][1] = 0.0f;//(*pMatrix)[1][2];
+	(*pMat)[2][2] = 1.0f;//(*pMatrix)[2][2];
+	(*pMat)[2][3] = 0.0f;//(*pMatrix)[3][2];
+
+	// position
+//	(*pMat)[3][0] = (*pMatrix)[3][0];
+//	(*pMat)[3][1] = (*pMatrix)[3][1];
+//	(*pMat)[3][2] = (*pMatrix)[3][2];
+//	(*pMat)[3][3] = 1.0f;//(*pMatrix)[3][3];
+	#endif
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Geom::plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* pBoneMatrices, int numBones)
+{
+	// implies it's got a skin
+	if ( mp_oldInstance )
+	{
+		Dbg_Assert( mp_oldInstance );
+		
+		if ( numBones > 0 )
+		{
+			mp_oldInstance->SetBoneTransforms( pBoneMatrices ); 
+		}
+
+		mp_oldInstance->SetTransform(*pRootMatrix);
+	
+		Dbg_MsgAssert( numBones <= mp_oldInstance->GetNumBones(), ( "Bone mismatch Trying to render %d bones, but model was initialized with %d bones", 
+																	numBones, 
+																	mp_oldInstance->GetNumBones() ) );
+	}
+
+	if ( mp_instance )
+	{
+		m_rootMatrix = *pRootMatrix;
+
+		if ( numBones )
+		{
+			mp_instance->SetBoneTransforms( pBoneMatrices );
+		}
+		
+		Dbg_MsgAssert( numBones <= mp_instance->GetNumBones(), ( "Bone mismatch Trying to render %d bones, but model was initialized with %d bones", 
+																	numBones, 
+																	mp_instance->GetNumBones() ) );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Geom::plat_hide_polys( uint32 mask )
+{
+	if ( mp_instance )
+	{
+	// Only supporting old style geom for now
+	//	mp_instance->HidePolys( mask );
+	}
+	else
+	{
+		Dbg_Assert( mp_sourceMesh );
+		NxPs2::CGeomNode* pGeomNode = mp_sourceMesh->GetGeomNode();
+		if ( pGeomNode )
+		{
+		// Only supporting old style geom for now
+		//	pGeomNode->HidePolys( mask );
+		}
+		else
+		{
+			mp_sourceMesh->HidePolys( mask );
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Geom::plat_enable_shadow( bool enabled )
+{
+	if ( mp_instance )
+	{
+		// only supporting old style geom for now
+	}
+	else
+	{
+		mp_oldInstance->EnableShadow( enabled );
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_model_lights(CModelLights *p_model_lights)
+{
+	NxPs2::CLightGroup *p_light_group;
+	if (p_model_lights)
+	{
+		CPs2ModelLights *p_ps2_model_lights;
+		p_ps2_model_lights = static_cast(p_model_lights);
+		p_light_group = p_ps2_model_lights->GetEngineLightGroup();
+	} else {
+		p_light_group = NULL;
+	}
+
+
+	// Model lights only work on CGeomNodes
+	if (mp_instance)
+	{
+		Dbg_Assert(!mp_instance->IsLeaf());
+		mp_instance->SetLightGroup(p_light_group);
+	}
+	else if (mp_oldInstance)
+	{
+		mp_oldInstance->SetLightGroup(p_light_group);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CPs2Geom::plat_get_num_render_verts()
+{
+	Dbg_Assert(mp_instance);
+
+	return mp_instance->GetNumVerts();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_get_render_verts(Mth::Vector *p_verts)
+{
+	Dbg_Assert(mp_instance);
+
+	mp_instance->GetVerts(p_verts);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CPs2Geom::plat_get_num_render_polys()								
+{
+	return mp_instance->GetNumPolys();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CPs2Geom::plat_get_num_render_base_polys()								
+{
+	return mp_instance->GetNumBasePolys();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_get_render_colors(Image::RGBA *p_colors)
+{
+	Dbg_Assert(mp_instance);
+
+	mp_instance->GetColors((uint32 *) p_colors);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_render_verts(Mth::Vector *p_verts)
+{
+	Dbg_Assert(mp_instance);
+
+	mp_instance->SetVerts(p_verts);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_render_colors(Image::RGBA *p_colors)
+{
+	Dbg_Assert(mp_instance);
+
+	mp_instance->SetColors((uint32 *) p_colors);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_uv_wibble_params(float u_vel, float u_amp, float u_freq, float u_phase,
+										 float v_vel, float v_amp, float v_freq, float v_phase)
+{
+	Dbg_Assert(mp_instance);
+
+	NxPs2::CGeomNode *p_node = mp_instance;
+
+	// Get leaf
+	while (!p_node->IsLeaf())
+	{
+		p_node = p_node->GetChild();
+	}
+
+	// Do on all siblings
+	while (p_node)
+	{
+		if (p_node->IsUVWibbled())
+		{
+			p_node->SetUVWibbleParams(u_vel, u_amp, u_freq, u_phase, v_vel, v_amp, v_freq, v_phase);
+		}
+
+		p_node = p_node->GetSibling();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_use_explicit_uv_wibble(bool yes)
+{
+	Dbg_Assert(mp_instance);
+
+	NxPs2::CGeomNode *p_node = mp_instance;
+
+	// Get leaf
+	while (!p_node->IsLeaf())
+	{
+		p_node = p_node->GetChild();
+	}
+
+	// Do on all siblings
+	while (p_node)
+	{
+		if (p_node->IsUVWibbled())
+		{
+			p_node->UseExplicitUVWibble(yes);
+		}
+
+		p_node = p_node->GetSibling();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Geom::plat_set_uv_wibble_offsets(float u_offset, float v_offset)
+{
+	Dbg_Assert(mp_instance);
+
+	NxPs2::CGeomNode *p_node = mp_instance;
+
+	// Get leaf
+	while (!p_node->IsLeaf())
+	{
+		p_node = p_node->GetChild();
+	}
+
+	// Do on all siblings
+	while (p_node)
+	{
+		if (p_node->IsUVWibbled())
+		{
+			p_node->SetUVWibbleOffsets(u_offset, v_offset);
+			// If we are setting the offsets, then we should want to use the explicit mode
+			p_node->UseExplicitUVWibble(true);
+		}
+
+		p_node = p_node->GetSibling();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Geom::plat_set_uv_wibble_offsets(uint32 mat_checksum, int pass, float u_offset, float v_offset)
+{
+	if (mp_oldInstance)
+	{
+		mp_oldInstance->SetUVOffset(mat_checksum, pass, u_offset, v_offset);
+		return true;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't set the UV offset with material checksum on CGeomNode"));
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Geom::plat_set_uv_matrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat)
+{
+	if (mp_oldInstance)
+	{
+		mp_oldInstance->SetUVMatrix(mat_checksum, pass, mat);
+		return true;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't set the UV matrix with material checksum on CGeomNode"));
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+				
diff --git a/Code/Gfx/NGPS/p_NxGeom.h b/Code/Gfx/NGPS/p_NxGeom.h
new file mode 100644
index 0000000..0ebd757
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxGeom.h
@@ -0,0 +1,154 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxGeom.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  3/5/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_GEOM_H__
+#define	__GFX_P_NX_GEOM_H__
+    
+#include "core/math.h"
+
+#include "gfx/nxgeom.h"
+
+namespace NxPs2
+{
+	class CInstance;
+	class CGeomNode;
+}
+						   
+namespace Nx
+{
+	class CPs2Mesh;
+	class CModelLights;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CGeom
+    
+class CPs2Geom : public CGeom
+{
+                                      
+public:
+						CPs2Geom();
+	virtual 			~CPs2Geom();
+
+	void				SetEngineObject(NxPs2::CGeomNode *p_object);
+	NxPs2::CGeomNode *	GetEngineObject() const;
+
+	Mth::Vector			GetBoundingSphere();
+
+
+private:				// It's all private, as it is machine specific
+	virtual CGeom *		plat_clone(bool instance, CScene* pDestScene=NULL);
+	virtual CGeom *		plat_clone(bool instance, CModel* pDestModel);
+
+	virtual	bool		plat_load_geom_data(CMesh* pMesh, CModel* pModel, bool color_per_material);
+	virtual void		plat_finalize();
+
+	virtual uint32		plat_get_checksum();
+
+	virtual	void		plat_set_color(Image::RGBA rgba);
+	virtual	Image::RGBA	plat_get_color() const;
+	virtual	void		plat_clear_color();
+
+	virtual bool		plat_set_material_color(uint32 mat_checksum, int pass, Image::RGBA rgba);
+	virtual Image::RGBA	plat_get_material_color(uint32 mat_checksum, int pass);
+
+	virtual	void		plat_set_visibility(uint32 mask);
+	virtual	uint32		plat_get_visibility() const;
+
+	virtual	void		plat_set_active(bool active);
+	virtual	bool		plat_is_active() const;
+
+	virtual const Mth::CBBox &	plat_get_bounding_box() const;
+
+	virtual void	plat_set_bounding_sphere( const Mth::Vector& boundingSphere );
+	virtual const Mth::Vector	plat_get_bounding_sphere() const;
+
+	virtual void		plat_set_world_position(const Mth::Vector& pos);
+	virtual const Mth::Vector &	plat_get_world_position() const;
+
+	virtual void		plat_set_orientation(const Mth::Matrix& orient);
+	virtual const Mth::Matrix &	plat_get_orientation() const;
+
+	virtual void 		plat_rotate_y(Mth::ERot90 rot);
+
+	virtual void		plat_set_transform(const Mth::Matrix& transform);
+	virtual const Mth::Matrix &	plat_get_transform() const;
+
+	virtual void		plat_set_scale(const Mth::Vector& scale);
+	virtual Mth::Vector plat_get_scale() const;
+
+	virtual void		plat_set_model_lights(CModelLights *);
+
+	virtual bool		plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones);
+	virtual bool		plat_hide_polys( uint32 mask );
+	virtual bool		plat_enable_shadow( bool enabled );
+
+	virtual	int 		plat_get_num_render_verts();								// - returns number of renderable verts
+	virtual	int 		plat_get_num_render_polys();
+	virtual	int 		plat_get_num_render_base_polys();
+	virtual	void 		plat_get_render_verts(Mth::Vector *p_verts);				// - gets a single array of the verts
+	virtual	void 		plat_get_render_colors(Image::RGBA *p_colors);				// - gets an array of vertex colors
+	virtual void 		plat_set_render_verts(Mth::Vector *p_verts);				// - sets the verts after modification
+	virtual	void 		plat_set_render_colors(Image::RGBA *p_colors);				// - sets the colors after modification
+
+	// Wibble functions
+	virtual void		plat_set_uv_wibble_params(float u_vel, float u_amp, float u_freq, float u_phase,
+												  float v_vel, float v_amp, float v_freq, float v_phase);
+	virtual void		plat_use_explicit_uv_wibble(bool yes);
+	virtual void		plat_set_uv_wibble_offsets(float u_offset, float v_offset);
+	virtual bool		plat_set_uv_wibble_offsets(uint32 mat_checksum, int pass, float u_offset, float v_offset);
+	virtual bool		plat_set_uv_matrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat);
+
+	// local functions
+	void 				update_engine_matrix();
+
+private:
+	// old format
+	NxPs2::CInstance*	mp_oldInstance;
+	
+	// access to the scene means access to the polys for hiding purposes
+	// this code will all be thrown away once we convert to the new geomnode
+	// stuff anyway
+	CPs2Mesh*			mp_sourceMesh;			
+
+	// new format
+	NxPs2::CGeomNode*	mp_instance;
+	Mth::Matrix			m_rootMatrix;
+
+	Mth::Matrix *		mp_matrices;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CPs2Geom::SetEngineObject(NxPs2::CGeomNode *p_object)
+{
+	mp_instance = p_object;
+	Dbg_MsgAssert(((int)(mp_instance) & 0xf) == 0,("mp_instance (0x%x) not multiple of 16 (Maybe .SCN corrupted on export?)\n"
+													,(int)mp_instance));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline NxPs2::CGeomNode *	CPs2Geom::GetEngineObject() const
+{
+	return mp_instance;
+}
+
+} // Nx
+
+#endif 
diff --git a/Code/Gfx/NGPS/p_NxImposter.cpp b/Code/Gfx/NGPS/p_NxImposter.cpp
new file mode 100644
index 0000000..f1960ed
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxImposter.cpp
@@ -0,0 +1,69 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxImposter.cpp
+
+#include 	"gfx/ngps/p_NxGeom.h"
+#include 	"gfx/ngps/p_NxImposter.h"
+
+namespace Nx
+{
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CImposterGroup* CImposterManager::plat_create_imposter_group( void )
+{
+	return new CPs2ImposterGroup;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::plat_pre_render_imposters( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::plat_post_render_imposters( void )
+{
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CImposterGroup
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CPs2ImposterGroup::CPs2ImposterGroup()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CPs2ImposterGroup::~CPs2ImposterGroup()
+{
+}
+
+
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxImposter.h b/Code/Gfx/NGPS/p_NxImposter.h
new file mode 100644
index 0000000..83cb2ba
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxImposter.h
@@ -0,0 +1,30 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.h
+
+#ifndef	__GFX_P_NX_IMPOSTER_H__
+#define	__GFX_P_NX_IMPOSTER_H__
+
+#include 	"gfx/NxImposter.h"
+
+namespace Nx
+{
+	
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CImposterGroup
+class CPs2ImposterGroup : public CImposterGroup
+{
+	public:
+								CPs2ImposterGroup();
+	virtual						~CPs2ImposterGroup();
+
+
+	private:					// It's all private, as it is machine specific
+
+	// Machine specific members
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGPS/p_NxLight.cpp b/Code/Gfx/NGPS/p_NxLight.cpp
new file mode 100644
index 0000000..51bb686
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxLight.cpp
@@ -0,0 +1,252 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLight.cpp - PS2 platform specific interface to CModelLights
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+
+#include 
+
+#include 
+#include 
+
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2ModelLights::CPs2ModelLights()
+{
+	mp_light_group = new NxPs2::CLightGroup;
+
+	plat_enable_ambient_light(false);
+	for (int i = 0; i < CLightManager::MAX_LIGHTS; i++)
+	{
+		plat_enable_diffuse_light(i, false);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2ModelLights::~CPs2ModelLights()
+{
+	if (mp_light_group)
+	{
+		delete mp_light_group;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2ModelLights::plat_update_engine( Mth::Vector & pos, bool add_scene_light )
+{
+	// Figure Scene Lighting if required.
+	if( add_scene_light )
+	{
+		// Copy the object position into our position.  If this pointer is set to NULL,
+		// we probably won't find any Scene Lights.
+		if (mp_pos)
+		{
+			pos = *mp_pos;
+		}
+		else
+		{
+			Dbg_MsgAssert(0, ("No object position pointer for scene lights"));
+		}
+
+		// We should check if any calcs are necessary first by seeing if this position is
+		// "visible", but for now, we'll do them all.
+		Nx::CSceneLight *p_scene_light = CLightManager::sGetOptimumSceneLight( pos );
+		if( p_scene_light )
+		{
+			Dbg_Assert(mp_light_group);
+
+			Mth::Vector light_pos = p_scene_light->GetLightPosition();
+
+			float dist	= Mth::Distance( pos, light_pos );
+			float ratio	= dist * p_scene_light->GetLightReciprocalRadius();
+
+			light_pos = - ( pos - light_pos ).Normalize();
+
+			// Figure the direction...
+			mp_light_group->SetDirection(SCENE_LIGHT_INDEX, light_pos);
+
+			// ...and the color.
+			ratio = sqrtf( 1.0f - ratio ) * p_scene_light->GetLightIntensity();
+			Mth::Vector color(ratio * p_scene_light->GetLightColor().r,
+							  ratio * p_scene_light->GetLightColor().g,
+							  ratio * p_scene_light->GetLightColor().b,
+							  0);
+
+			mp_light_group->SetBaseDiffuseColor(SCENE_LIGHT_INDEX, color);
+
+			plat_enable_diffuse_light(SCENE_LIGHT_INDEX, true);
+		}
+		else
+		{
+#if 0
+			// Disable this light by setting zero color.
+			Mth::Vector color(0, 0, 0, 0);
+
+			mp_light_group->SetBaseDiffuseColor(SCENE_LIGHT_INDEX, color);
+#endif
+			plat_enable_diffuse_light(SCENE_LIGHT_INDEX, false);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2ModelLights::plat_update_brightness()
+{
+	mp_light_group->SetAmbientBrightness(m_ambient_brightness);
+
+	for (int i = 0; i < CLightManager::MAX_LIGHTS; i++)
+	{
+		mp_light_group->SetDiffuseBrightness(i, m_diffuse_brightness[i]);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2ModelLights::plat_set_light_ambient_color(const Image::RGBA &rgba)
+{
+	Mth::Vector color(((float) rgba.r),
+					  ((float) rgba.g),
+					  ((float) rgba.b),
+					  0);
+
+	mp_light_group->SetBaseAmbientColor(color);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA		CPs2ModelLights::plat_get_light_ambient_color() const
+{
+	Mth::Vector color = mp_light_group->GetBaseAmbientColor();
+
+	Image::RGBA rgba(color[0], color[1], color[2], 0);
+	
+	return rgba;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2ModelLights::plat_set_light_direction(int light_index, const Mth::Vector &direction)
+{
+	mp_light_group->SetDirection(light_index, direction);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CPs2ModelLights::plat_get_light_direction(int light_index) const
+{
+	return mp_light_group->GetDirection(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2ModelLights::plat_set_light_diffuse_color(int light_index, const Image::RGBA &rgba)
+{
+	Mth::Vector color(((float) rgba.r),
+					  ((float) rgba.g),
+					  ((float) rgba.b),
+					  0);
+
+	mp_light_group->SetBaseDiffuseColor(light_index, color);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA		CPs2ModelLights::plat_get_light_diffuse_color(int light_index) const
+{
+	Mth::Vector color = mp_light_group->GetBaseDiffuseColor(light_index);
+
+	Image::RGBA rgba(color[0], color[1], color[2], 0);
+	
+	return rgba;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2ModelLights::plat_enable_ambient_light(bool enable)
+{
+	mp_light_group->EnableAmbientLight(enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2ModelLights::plat_enable_diffuse_light(int light_index, bool enable)
+{
+	mp_light_group->EnableDiffuseLight(light_index, enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2ModelLights::plat_is_ambient_light_enabled() const
+{
+	return mp_light_group->IsAmbientLightEnabled();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2ModelLights::plat_is_diffuse_light_enabled(int light_index) const
+{
+	return mp_light_group->IsDiffuseLightEnabled(light_index);
+}
+
+} 
+ 
diff --git a/Code/Gfx/NGPS/p_NxLight.h b/Code/Gfx/NGPS/p_NxLight.h
new file mode 100644
index 0000000..0f8ec54
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxLight.h
@@ -0,0 +1,77 @@
+///////////////////////////////////////////////////////////////////////////////////
+// p_NxLight.H - Neversoft Engine, Rendering portion, Platform dependent interface
+
+#ifndef	__GFX_P_NX_LIGHT_H__
+#define	__GFX_P_NX_LIGHT_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+
+namespace NxPs2
+{
+	class CLightGroup;
+}
+
+namespace Nx
+{
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CPs2ModelLights
+class	CPs2ModelLights : public CModelLights
+{
+public:
+								CPs2ModelLights();
+	virtual						~CPs2ModelLights();
+
+	NxPs2::CLightGroup *		GetEngineLightGroup() const;
+
+private:	
+	// Constants
+	enum
+	{
+		SCENE_LIGHT_INDEX = 2,
+	};
+
+	// The engine light group
+	NxPs2::CLightGroup *		mp_light_group;
+
+	// Platform-specific calls
+	//virtual void				plat_update_lights();
+	virtual bool				plat_set_light_ambient_color(const Image::RGBA &rgba);
+	virtual bool				plat_set_light_direction(int light_index, const Mth::Vector &direction);
+	virtual bool				plat_set_light_diffuse_color(int light_index, const Image::RGBA &rgba);
+	virtual Image::RGBA			plat_get_light_ambient_color() const;
+	virtual const Mth::Vector &	plat_get_light_direction(int light_index) const;
+	virtual Image::RGBA			plat_get_light_diffuse_color(int light_index) const;
+
+	virtual void				plat_update_engine( Mth::Vector & pos, bool add_scene_light );
+
+	virtual void				plat_update_brightness();
+
+	virtual void				plat_enable_ambient_light(bool enable);
+	virtual void				plat_enable_diffuse_light(int light_index, bool enable);
+	virtual bool				plat_is_ambient_light_enabled() const;
+	virtual bool				plat_is_diffuse_light_enabled(int light_index) const;
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline NxPs2::CLightGroup *	CPs2ModelLights::GetEngineLightGroup() const
+{
+	return mp_light_group;
+}
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NGPS/p_NxLightMan.cpp b/Code/Gfx/NGPS/p_NxLightMan.cpp
new file mode 100644
index 0000000..4d4cbed
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxLightMan.cpp
@@ -0,0 +1,276 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLightMan.cpp - PS2 platform specific interface to CLightManager
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+
+#include 
+
+#include 
+#include 
+#include 
+
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void			CLightManager::s_plat_update_engine( void )
+{
+	CPs2LightManager::sUpdateEngine();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CLightManager::s_plat_update_lights()
+{
+	s_plat_set_light_ambient_color();
+	for (int i = 0; i < MAX_LIGHTS; i++)
+	{
+		s_plat_set_light_direction(i);
+		s_plat_set_light_diffuse_color(i);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CLightManager::s_plat_update_colors()
+{
+	s_plat_set_light_ambient_color();
+	for (int i = 0; i < MAX_LIGHTS; i++)
+	{
+		s_plat_set_light_diffuse_color(i);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CLightManager::s_plat_set_light_ambient_color()
+{
+	Mth::Vector color(((float) s_world_lights.m_light_ambient_rgba.r),
+					  ((float) s_world_lights.m_light_ambient_rgba.g),
+					  ((float) s_world_lights.m_light_ambient_rgba.b),
+					  0);
+
+	NxPs2::CLightGroup::sSetDefaultAmbientColor(color);
+
+	//color *= s_ambient_brightness;
+	//NxPs2::CLightGroup::sSetDefaultModAmbientColor(color);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA		CLightManager::s_plat_get_light_ambient_color()
+{
+	Mth::Vector color(NxPs2::CLightGroup::sGetDefaultAmbientColor());
+
+	Image::RGBA rgb;
+
+	rgb.r = (uint8) color[X];
+	rgb.g = (uint8) color[Y];
+	rgb.b = (uint8) color[Z];
+	rgb.a = 0x80;
+
+	return rgb;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CLightManager::s_plat_set_light_direction(int light_index)
+{
+	NxPs2::CLightGroup::sSetDefaultDirection(light_index, s_world_lights.m_light_direction[light_index]);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector & CLightManager::s_plat_get_light_direction(int light_index)
+{
+	return NxPs2::CLightGroup::sGetDefaultDirection(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CLightManager::s_plat_set_light_diffuse_color(int light_index)
+{
+	Mth::Vector color(((float) s_world_lights.m_light_diffuse_rgba[light_index].r),
+					  ((float) s_world_lights.m_light_diffuse_rgba[light_index].g),
+					  ((float) s_world_lights.m_light_diffuse_rgba[light_index].b),
+					  0);
+
+	NxPs2::CLightGroup::sSetDefaultDiffuseColor(light_index, color);
+	
+	//color *= s_diffuse_brightness[light_index];
+	//NxPs2::CLightGroup::sSetDefaultModDiffuseColor(light_index, color);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA		CLightManager::s_plat_get_light_diffuse_color(int light_index)
+{
+	Mth::Vector color(NxPs2::CLightGroup::sGetDefaultDiffuseColor(light_index));
+
+	Image::RGBA rgb;
+
+	rgb.r = (uint8) color[X];
+	rgb.g = (uint8) color[Y];
+	rgb.b = (uint8) color[Z];
+	rgb.a = 0x80;
+
+	return rgb;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelLights *	CLightManager::s_plat_create_model_lights()
+{
+	CModelLights *p_model_lights = new CPs2ModelLights;
+
+	if (p_model_lights)
+	{
+		CPs2LightManager::sAddToModelLightsList(p_model_lights);
+	}
+
+	return p_model_lights;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CLightManager::s_plat_free_model_lights(CModelLights *p_model_lights)
+{
+	Dbg_Assert(p_model_lights);
+
+	CPs2LightManager::sRemoveFromModelLightsList(p_model_lights);
+
+	delete p_model_lights;
+
+	return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CPs2LightManager
+
+// Model light list
+Lst::Head< CModelLights > CPs2LightManager::s_model_lights_list;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2LightManager::sUpdateEngine( void )
+{
+	s_update_model_lights();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2LightManager::sAddToModelLightsList(CModelLights *p_model_lights)
+{
+	Dbg_Assert(p_model_lights);
+
+	Lst::Node< CModelLights > *node = new Lst::Node< CModelLights > (p_model_lights);
+	if (node)
+	{
+		s_model_lights_list.AddToTail(node);
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2LightManager::sRemoveFromModelLightsList(CModelLights *p_model_lights)
+{
+	Dbg_Assert(p_model_lights);
+
+	Lst::Node< CModelLights > *obj_node;
+
+	for(obj_node = s_model_lights_list.GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		if (obj_node->GetData() == p_model_lights)
+		{
+			delete obj_node;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2LightManager::s_update_model_lights()
+{
+	Mth::Vector dummy_pos(0, 0, 0, 1);		// Function requires this to be passed in, even though not used
+	Lst::Node< CModelLights > *obj_node;
+
+	for(obj_node = s_model_lights_list.GetNext(); obj_node; obj_node = obj_node->GetNext())
+	{
+		CModelLights *p_model_lights = obj_node->GetData();
+		
+		Dbg_Assert(p_model_lights);
+
+		p_model_lights->UpdateEngine(dummy_pos, true);
+	}
+}
+
+} 
+ 
diff --git a/Code/Gfx/NGPS/p_NxLightMan.h b/Code/Gfx/NGPS/p_NxLightMan.h
new file mode 100644
index 0000000..1b1c24a
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxLightMan.h
@@ -0,0 +1,50 @@
+///////////////////////////////////////////////////////////////////////////////////
+// p_NxLightMan.H - Neversoft Engine, Rendering portion, Platform dependent interface
+
+#ifndef	__GFX_P_NX_LIGHT_MAN_H__
+#define	__GFX_P_NX_LIGHT_MAN_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+#include 
+
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CPs2LightManager
+class	CPs2LightManager : public CLightManager
+{
+public:
+
+	// Does any once-per-frame light update
+	static void					sUpdateEngine( void );
+
+	// Model Lights List functions
+	static bool					sAddToModelLightsList(CModelLights *p_model_lights);
+	static bool					sRemoveFromModelLightsList(CModelLights *p_model_lights);
+
+private:	
+
+	// once-per-frame update functions called from sUpdateEngine()
+	static void					s_update_model_lights();
+
+	// Model light list
+	static Lst::Head< CModelLights > s_model_lights_list;
+};
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NGPS/p_NxLoadScreen.cpp b/Code/Gfx/NGPS/p_NxLoadScreen.cpp
new file mode 100644
index 0000000..65b98b7
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxLoadScreen.cpp
@@ -0,0 +1,233 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLoadScreen.cpp - PS2 platform specific interface for the load screen
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+//
+
+#include	"gfx\Nx.h"
+#include	"gfx\NxLoadScreen.h"
+#include	"gfx\NGPS\p_NxTexture.h"
+#include	"gfx\NGPS\p_NxSprite.h"
+#include	"gfx\NGPS\NX\sprite.h"
+#include	"gfx\NGPS\NX\render.h"
+#include	"gfx\NGPS\NX\loadscreen.h"
+#include	"gfx\NGPS\NX\dma.h"
+#include	"gfx\NGPS\NX\dmacalls.h"
+#include	"gfx\NGPS\NX\nx_init.h"
+
+#include	"gel\movies\NGPS\disp.h"  // for clear screen
+
+#include	"core\macros.h"
+#include 
+#include 
+#include 
+
+#include 
+
+namespace NxPs2
+{
+	void	WaitForRendering();
+}
+
+
+namespace	Nx
+{
+
+
+Nx::CPs2Texture	*sp_load_screen_texture;
+Nx::CPs2Sprite	*sp_load_screen_sprite;
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+#define USE_SPRITES 0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_display(const char* filename, bool just_freeze, bool blank)
+{
+	// Engine stuff
+	Dbg_Assert(!sp_load_screen_texture);
+	Dbg_Assert(!sp_load_screen_sprite);
+
+	char adjustedFilename[144];
+
+	if (!just_freeze && !blank)
+	{
+		sp_load_screen_texture = new CPs2Texture(true);
+
+		if (Config::NTSC())
+		{
+			strcpy(adjustedFilename, filename);
+		} else {
+			// Find correct directory to use based on language
+			switch (Config::GetLanguage())
+			{
+			case Config::LANGUAGE_FRENCH:
+				sprintf(adjustedFilename, "PALimages/FRImages/%s", filename);
+				break;
+			case Config::LANGUAGE_SPANISH:
+				sprintf(adjustedFilename, "PALimages/SPImages/%s", filename);
+				break;
+			case Config::LANGUAGE_GERMAN:
+				sprintf(adjustedFilename, "PALimages/GRImages/%s", filename);
+				break;
+			case Config::LANGUAGE_ITALIAN:
+				sprintf(adjustedFilename, "PALimages/ITImages/%s", filename);
+				break;
+			default:
+				sprintf(adjustedFilename, "PALimages/%s", filename);
+				break;
+			}
+
+			// Since LoadTexture() will assert if the file doesn't exist, we need to test ahead of time
+			char ExtendedFilename[160];
+			sprintf(ExtendedFilename, "images/%s.img.ps2", adjustedFilename);
+
+			if (!File::Exist(ExtendedFilename))
+			{
+				// Can't find it, so lets try the base directory before giving up
+				sprintf(adjustedFilename, "PALimages/%s", filename);
+			}
+		}
+
+		if (!sp_load_screen_texture->LoadTexture(adjustedFilename, true))
+		{
+			Dbg_Error("Can't load texture %s", adjustedFilename);
+		}
+	
+	#if USE_SPRITES
+		sp_load_screen_sprite = static_cast(CEngine::sCreateSprite());
+		sp_load_screen_sprite->SetTexture(sp_load_screen_texture);
+		sp_load_screen_sprite->SetPos(0, 0);
+		sp_load_screen_sprite->SetScale(1, 1);
+		sp_load_screen_sprite->SetRGBA(Image::RGBA(128, 128, 128, 128));
+		sp_load_screen_sprite->SetHidden(false);
+	#else
+		// Copy directly into display buffer
+		sceGsLoadImage gs_image;
+		NxPs2::SSingleTexture *p_engine_texture = sp_load_screen_texture->GetSingleTexture();
+	
+		FlushCache( 0 );
+		sceGsSyncPath( 0, 0 );
+		
+		int display_addr = (SCREEN_CONV_X( 640 ) * SCREEN_CONV_Y( 448 ) * 4) / (64 * 4);
+	
+		const int download_parts = 2;
+		int partial_height = sp_load_screen_texture->GetHeight() / download_parts;
+		int partial_size = sp_load_screen_texture->GetWidth() * partial_height * 2;
+		for (int i = 0; i < download_parts; i++)
+		{
+			sceGsSetDefLoadImage( &gs_image, display_addr, SCREEN_CONV_X( 640 )/64, SCE_GS_PSMCT16S,
+							   0, partial_height * i, sp_load_screen_texture->GetWidth(), partial_height );
+					
+			FlushCache( 0 );		
+			sceGsExecLoadImage( &gs_image, ( u_long128 * )(p_engine_texture->mp_PixelData + (partial_size * i)) );
+			sceGsSyncPath( 0, 0 );
+		}
+	
+		// Done with texture
+		delete sp_load_screen_texture;
+		sp_load_screen_texture = NULL;
+	}
+	
+	if (blank)
+	{
+		// This is esentially the same as playing a movie, without playing a movie
+		// the ResetEngine handles clearing the screen 
+		NxPs2::SuspendEngine();
+		NxPs2::ResetEngine();		
+	}
+	
+	
+	
+	NxPs2::EnableFlipCopy(false);
+#endif // USE_SPRITES
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_start_loading_bar(float seconds)
+{
+	NxPs2::StartLoadingBar((int) (seconds * Config::FPS()));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_hide()
+{
+	NxPs2::RemoveLoadingBar();
+	//Tmr::VSync();
+#if !USE_SPRITES
+	NxPs2::EnableFlipCopy(true);
+#endif // USE_SPRITES
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_update_bar_properties()
+{
+	// Bar size and position
+	NxPs2::gLoadBarX = s_bar_x;
+	NxPs2::gLoadBarY = s_bar_y;
+	NxPs2::gLoadBarWidth = s_bar_width;
+	NxPs2::gLoadBarHeight = s_bar_height;
+
+	// Bar colors
+	NxPs2::gLoadBarStartColor[0] = s_bar_start_color.r;
+	NxPs2::gLoadBarStartColor[1] = s_bar_start_color.g;
+	NxPs2::gLoadBarStartColor[2] = s_bar_start_color.b;
+	NxPs2::gLoadBarStartColor[3] = s_bar_start_color.a;
+	NxPs2::gLoadBarDeltaColor[0] = (int) s_bar_end_color.r - NxPs2::gLoadBarStartColor[0];
+	NxPs2::gLoadBarDeltaColor[1] = (int) s_bar_end_color.g - NxPs2::gLoadBarStartColor[1];
+	NxPs2::gLoadBarDeltaColor[2] = (int) s_bar_end_color.b - NxPs2::gLoadBarStartColor[2];
+	NxPs2::gLoadBarDeltaColor[3] = (int) s_bar_end_color.a - NxPs2::gLoadBarStartColor[3];
+
+	// Border size
+	NxPs2::gLoadBarBorderWidth = s_bar_border_width;
+	NxPs2::gLoadBarBorderHeight = s_bar_border_height;
+
+	// Border color
+	NxPs2::gLoadBarBorderColor[0] = s_bar_border_color.r;
+	NxPs2::gLoadBarBorderColor[1] = s_bar_border_color.g;
+	NxPs2::gLoadBarBorderColor[2] = s_bar_border_color.b;
+	NxPs2::gLoadBarBorderColor[3] = s_bar_border_color.a;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_clear()
+{
+	if (sp_load_screen_sprite)
+	{
+		CEngine::sDestroySprite(sp_load_screen_sprite);
+		sp_load_screen_sprite = NULL;
+	}
+
+	if (sp_load_screen_texture)
+	{
+		delete sp_load_screen_texture;
+		sp_load_screen_texture = NULL;
+	}
+
+	NxPs2::RemoveLoadingBar();
+}
+
+} 
+ 
diff --git a/Code/Gfx/NGPS/p_NxMesh.cpp b/Code/Gfx/NGPS/p_NxMesh.cpp
new file mode 100644
index 0000000..18562f3
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxMesh.cpp
@@ -0,0 +1,373 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxMesh.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#include 
+
+#include 
+								  
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Mesh::build_casdata_table(uint8* p_casData)
+{
+	#ifdef __NOPT_ASSERT__
+	uint32 version = *((uint32*)p_casData);
+	#endif
+	p_casData += sizeof(uint32);
+
+	Dbg_MsgAssert( version >= 2, ( "Obsolete version of CAS file.  Please re-export." ) )
+		
+	m_CASRemovalMask = *((uint32*)p_casData);
+	p_casData += sizeof(uint32);
+
+	m_numCASData = *((int*)p_casData);
+	p_casData += sizeof(int);
+
+	// temporary lookup table, for the duration of the scene loading
+	NxPs2::CreateCASDataLookupTable();
+
+	if ( m_numCASData > 0 )
+	{
+		// create-a-skater flags
+		mp_CASData = new NxPs2::sCASData[m_numCASData];
+		
+		for ( int i = 0; i < m_numCASData; i++ )
+		{
+			mp_CASData[i].mask = *((uint32*)p_casData);
+			p_casData += sizeof(uint32);
+
+			// this will get converted into a pointer to the DMA data
+			int vertIndex = *((int*)p_casData);
+			p_casData += sizeof(int);
+			
+			NxPs2::SetCASDataLookupData( vertIndex, &mp_CASData[i] );
+		}		
+	}
+
+	return ( m_numCASData > 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Mesh::build_casdata_table(const char* pFileName)
+{
+	void *pFile = File::Open(pFileName, "rb");
+	Dbg_MsgAssert(pFile, ("Couldn't open CAS data file %s\n", pFileName));
+
+	int file_size = File::GetFileSize(pFile);
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	uint8* p_fileBuffer = (uint8*)Mem::Malloc( file_size );
+	Mem::Manager::sHandle().PopContext();
+
+	File::Read(p_fileBuffer, file_size, 1, pFile);
+	File::Close( pFile );
+
+	bool success = build_casdata_table( p_fileBuffer );
+
+	Mem::Free(p_fileBuffer);
+	
+	// some debugging information for the artists
+	if ( Script::GetInteger( "DebugCAS", Script::NO_ASSERT ) )
+	{
+		printf( "%s removes %08x: [", pFileName, m_CASRemovalMask );
+		for ( int i = 0; i < 32; i++ )
+		{
+			uint32 mask = ( 1 << i );
+			if ( m_CASRemovalMask & mask )
+			{
+				printf( " %d", i );
+			}
+		}
+		printf( "]\n" );
+	}
+
+	return success;
+}
+	
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool use_new_geom_format( const char* pMeshFileName )
+{
+	// "reetest" is a quick kludge to get the skeletal trees
+	// up and running...  eventually, pass this in as a parameter
+	// from the nx.cpp level...
+	if ( strstr( pMeshFileName, "eh_" ) || strstr( pMeshFileName, "reetest" ) )
+	{
+		// if it's a vehicle, then use the new geomnode format
+		return true;
+	}
+	else
+	{
+		return false; 
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Mesh::CPs2Mesh(uint32* pModelData, int modelDataSize, uint8* pCASData, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume)
+{
+	Dbg_Assert( pTexDict );
+
+	mp_texDict = pTexDict;
+	
+	// polys should only be hidden once
+	m_polysAlreadyHidden = false;
+
+	m_isPipped = false;
+	m_numCASData = 0;
+
+	// don't do this for models
+	if ( isSkin && pCASData )
+	{
+		build_casdata_table( pCASData );
+	}
+	
+	// GJ:  The geomnodes blow off the texDictOffset, 
+	// which means it won't work with skater models, 
+	// or the 10 board gameobjs in the boardshop.
+	if ( !isSkin && texDictOffset == 0 )
+	{		 
+		// since we're not actually opening a PIP file
+		// we don't need to keep track of it
+		m_isPipped = false;
+
+		// this should already be in GEOM format,
+		// by the time the data arrives here...
+		// (we're not loading up the Pip through the
+		// normal Pip::Load process, so we need
+		// to remember the pointer to the pip data
+		// so that we can delete it later)
+		mp_pipData = (uint8*)Mem::Malloc( modelDataSize );
+		Dbg_Assert( mp_pipData );
+		
+		memcpy( mp_pipData, pModelData, modelDataSize );
+
+		// process data in place
+		// final arg (0) says to hold it in a database to be rendered later
+		// (as opposed to rendering it unconditionally, LOADFLAG_RENDERNOW)
+		mp_geomNode = NxPs2::CGeomNode::sProcessInPlace( mp_pipData, 0 );
+
+		// Get hierarchy array
+		mp_hierarchyObjects = (CHierarchyObject*) NxPs2::CGeomNode::sGetHierarchyArray(mp_pipData, m_numHierarchyObjects);
+	}
+	else
+	{
+		NxPs2::sScene* pTextureDictionary = ((Nx::CPs2TexDict*)mp_texDict)->GetEngineTextureDictionary();
+	
+		NxPs2::LoadScene( pModelData, modelDataSize, pTextureDictionary, true, true, texDictOffset, doShadowVolume );
+
+		mp_geomNode = NULL;
+	}
+
+	// temporary lookup table is no longer needed
+	NxPs2::DestroyCASDataLookupTable();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Mesh::CPs2Mesh(const char* pMeshFileName, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume)
+{
+	Dbg_Assert( pTexDict );
+
+	m_isPipped = false;
+	mp_texDict = pTexDict;
+	
+	m_numCASData = 0;
+
+	// don't do this for models
+	if ( isSkin )
+	{
+		char CASFileName[256];
+		strcpy(CASFileName, pMeshFileName);
+		Str::LowerCase( CASFileName );
+		char* pExt = strstr( CASFileName, "skin.ps2" );
+		if ( pExt )
+		{
+			Dbg_MsgAssert( pExt, ("Couldn't find skin.ps2 extension in %s", CASFileName));
+			strcpy(pExt, "cas.ps2");
+			build_casdata_table( CASFileName );
+		}
+	}
+	
+	// GJ:  The geomnodes blow off the texDictOffset, 
+	// which means it won't work with skater models, 
+	// or the 10 board gameobjs in the boardshop.
+	if ( !isSkin && texDictOffset == 0 )
+	{		 
+		// change the file name so that it's using the new Geom
+		// file format rather than Mdl.
+		char msg[512];
+		strcpy( msg, pMeshFileName );
+		char* pDot = strstr( msg, "." );
+		Dbg_Assert( pDot );
+		*pDot = NULL;
+		sprintf( pDot, ".geom.%s", Nx::CEngine::sGetPlatformExtension() );
+		m_pipFileName = msg;
+		m_isPipped = true;
+
+//		printf( "about to load pip file %s %d\n", m_pipFileName.getString(), texDictOffset );
+
+		// Pip::Load the new file
+		uint8* pPipData = (uint8*)Pip::Load( m_pipFileName.getString() );
+
+		Dbg_Assert( pPipData );
+
+		// process data in place
+		// final arg (0) says to hold it in a database to be rendered later
+		// (as opposed to rendering it unconditionally, LOADFLAG_RENDERNOW)
+		mp_geomNode = NxPs2::CGeomNode::sProcessInPlace( pPipData, 0 );
+
+		// Find CGeomNodeObjects
+//		if ( use_new_geom_format(pMeshFileName) )
+		{
+			// Get hierarchy array
+			mp_hierarchyObjects = (CHierarchyObject *) NxPs2::CGeomNode::sGetHierarchyArray(pPipData, m_numHierarchyObjects);
+		}
+	}
+	else
+	{
+		NxPs2::sScene* pTextureDictionary = ((Nx::CPs2TexDict*)mp_texDict)->GetEngineTextureDictionary();
+	
+		NxPs2::LoadScene( pMeshFileName, pTextureDictionary, true, true, texDictOffset, doShadowVolume );
+
+		mp_geomNode = NULL;
+	}
+
+	// temporary lookup table is no longer needed
+	NxPs2::DestroyCASDataLookupTable();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Mesh::~CPs2Mesh()
+{
+	//if (mp_geomNodeObjects)
+	//{
+	//	delete[] mp_geomNodeObjects;
+	//}
+
+	if ( mp_geomNode )
+	{
+		// clean up the node we created
+		Dbg_Assert( mp_geomNode );
+		mp_geomNode->Cleanup();
+
+		// Pip::Unload the file
+		if ( m_isPipped )
+		{
+			Pip::Unload( m_pipFileName.getString() );
+		}
+
+		// the file was not loaded using Pip::Load()
+		if ( mp_pipData )
+		{
+			Mem::Free( mp_pipData );
+			mp_pipData = NULL;
+		}
+	}
+	else
+	{
+		NxPs2::sScene* pScene = ((Nx::CPs2TexDict*)mp_texDict)->GetEngineTextureDictionary();
+	
+		// unload scene
+		NxPs2::DeleteScene( pScene );
+	}
+
+	if ( mp_CASData )
+	{
+		delete[] mp_CASData;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Mesh::HidePolys( uint32 mask )
+{
+	int count = 0;
+
+	// this function should only be called once per mesh
+	// because after the ADC bits are squeezed,
+	// the pointers are no longer valid...
+	// (was causing a crash in the secret level
+	// with random peds that were doing poly removal)
+	if ( m_polysAlreadyHidden )
+	{
+		return;
+	}
+
+//	printf( "Hiding polys with mask 0x%08x\n", mask);
+
+	for ( int j = 0; j < m_numCASData; j++ )
+	{
+		Dbg_Assert( mp_CASData );
+
+		if ( mp_CASData[j].mask & mask )
+		{
+			if ( mp_CASData[j].pADCBit )
+			{
+				*mp_CASData[j].pADCBit = 0xC000;
+			}
+			count++;
+		}
+	}
+
+//	printf( "Hiding %d polys\n", count );
+
+	m_polysAlreadyHidden = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+				
diff --git a/Code/Gfx/NGPS/p_NxMesh.h b/Code/Gfx/NGPS/p_NxMesh.h
new file mode 100644
index 0000000..d8bd0a0
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxMesh.h
@@ -0,0 +1,64 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxMesh.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_MESH_H__
+#define	__GFX_P_NX_MESH_H__
+    
+#include "gfx/nxmesh.h"
+
+#include 
+			
+namespace NxPs2
+{
+	class	CGeomNode;
+	struct	sCASData;
+}
+			 
+namespace Nx
+{
+
+	class	CHierarchyObject;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CMesh
+    
+class CPs2Mesh : public CMesh
+{
+                                      
+public:
+	CPs2Mesh( uint32* pModelData, int modelDataSize, uint8* pCASData, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume );
+	CPs2Mesh( const char* pMeshFileName, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume );
+	virtual 			~CPs2Mesh();
+
+	// temporary?
+	NxPs2::CGeomNode*	GetGeomNode() { return mp_geomNode; }
+	void				HidePolys( uint32 mask );
+
+protected:
+	NxPs2::CGeomNode*	mp_geomNode;
+	bool				m_polysAlreadyHidden:1;
+	bool				m_isPipped:1;
+	uint8*				mp_pipData;	// for pip-style geoms that are loaded from a data buffer
+	Str::String			m_pipFileName;
+
+	bool				build_casdata_table(uint8* pCASData);
+	bool				build_casdata_table(const char* pFileName);
+	
+	NxPs2::sCASData*	mp_CASData;
+	int					m_numCASData;
+};
+
+
+} // Nx
+
+#endif 
diff --git a/Code/Gfx/NGPS/p_NxModel.cpp b/Code/Gfx/NGPS/p_NxModel.cpp
new file mode 100644
index 0000000..00bf981
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxModel.cpp
@@ -0,0 +1,148 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxModel.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/21/2001
+//****************************************************************************
+
+#include 
+
+#include 
+#include "gfx/nxgeom.h"
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CPs2Model::plat_init_skeleton( int numBones )
+{
+	Dbg_Assert( mp_matrices == NULL );
+
+	// matrix buffer is double-buffered
+	mp_matrices = new Mth::Matrix[numBones * 2];
+	for ( int i = 0; i < numBones * 2; i++ )
+	{
+		mp_matrices[i].Identity();
+	}
+    
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CPs2Model::plat_get_bounding_sphere()
+{
+	Mth::Vector sphere, sphere1, sum, diff;
+	float dist;
+
+	// this should probably never happen
+	if (m_numGeoms == 0)
+		return Mth::Vector(0.0f, 0.0f, 0.0f, 0.0f);
+
+	// combine the spheres of all geoms
+	// (this should really be done once at load time)
+
+	// start with first sphere
+	sphere = mp_geom[0]->GetBoundingSphere();
+
+	// loop over remaining spheres, expanding as necessary
+	for (int i=1; iGetBoundingSphere();
+
+		// centre-to-centre vector, and distance
+		diff = sphere1-sphere;
+		dist = diff.Length();
+
+		// test for sphere1 inside sphere
+		if (dist+sphere1[3] <= sphere[3])
+			continue;			// keep sphere
+
+		// test for sphere inside sphere1
+		if (dist+sphere[3] <= sphere1[3])
+		{
+			sphere = sphere1;	// replace sphere
+			continue;
+		}
+
+		// otherwise make a larger sphere that contains both
+		sum       = sphere+sphere1;
+		sphere    = 0.5f * (sum + (diff[3]/dist) * diff);
+		sphere[3] = 0.5f * (dist + sum[3]);
+	}
+
+	return sphere;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Model::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	// loop over all spheres
+	for ( int i = 0; i < m_numGeoms; i++ )
+	{
+		mp_geom[i]->SetBoundingSphere( boundingSphere );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2Model::CPs2Model()
+{
+	mp_matrices = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2Model::~CPs2Model()
+{	
+	if ( mp_matrices != NULL )
+	{
+		delete[] mp_matrices;
+		mp_matrices = NULL;
+	}
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Matrix* CPs2Model::GetMatrices()
+{
+	return mp_matrices;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+
+				
diff --git a/Code/Gfx/NGPS/p_NxModel.h b/Code/Gfx/NGPS/p_NxModel.h
new file mode 100644
index 0000000..e7cf0b3
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxModel.h
@@ -0,0 +1,56 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxModel.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  1/8/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_MODEL_H__
+#define	__GFX_P_NX_MODEL_H__
+    
+#include "gfx/nxmodel.h"
+
+
+namespace NxPs2
+{
+	class CInstance;             
+	struct sScene;
+}
+							   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CModel
+    
+class CPs2Model : public CModel
+{
+                                      
+public:
+	CPs2Model();
+	virtual				~CPs2Model();
+	Mth::Matrix*		GetMatrices();
+
+private:				// It's all private, as it is machine specific
+	bool				plat_init_skeleton( int numBones );
+	Mth::Vector 		plat_get_bounding_sphere();
+	void				plat_set_bounding_sphere( const Mth::Vector& boundingSphere );
+
+private:
+	Mth::Matrix*		mp_matrices;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
diff --git a/Code/Gfx/NGPS/p_NxScene.cpp b/Code/Gfx/NGPS/p_NxScene.cpp
new file mode 100644
index 0000000..5b55bbf
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxScene.cpp
@@ -0,0 +1,387 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.cpp
+
+#include 	"Gfx/nx.h"
+#include 	"Gfx/NGPS/p_NxGeom.h"
+#include 	"Gfx/NGPS/p_NxScene.h"
+#include 	"Gfx/NGPS/p_NxSector.h"
+#include 	"Gfx/NGPS/NX/geomnode.h"
+
+#include 
+
+#include 
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Scene::CPs2Scene()
+{
+	mp_plat_scene = NULL;
+	mp_plat_clone_scene = NULL;
+	mp_plat_add_scene = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Scene::~CPs2Scene()
+{
+	if (mp_plat_clone_scene)
+	{
+		// Have to delete the sectors here first
+		mp_sector_table->IterateStart();
+		CSector *p_sector;
+		while ((p_sector = mp_sector_table->IterateNext()))
+		{
+			p_sector->clear_flags(CSector::mIN_SUPER_SECTORS);	// Tells cloned sectors it is OK to go away (and SuperSectors will be dead, anyway)
+			delete p_sector;
+		}
+		delete mp_sector_table;
+		mp_sector_table = NULL;
+
+		mp_plat_clone_scene->Cleanup();
+		delete mp_plat_clone_scene;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NxPs2::CGeomNode *	CPs2Scene::GetEngineScene() const
+{
+	return mp_plat_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::SetEngineScene(NxPs2::CGeomNode *p_scene)
+{
+	mp_plat_scene = p_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NxPs2::CGeomNode *	CPs2Scene::GetEngineCloneScene() const
+{
+	return mp_plat_clone_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::SetEngineCloneScene(NxPs2::CGeomNode *p_scene)
+{
+	mp_plat_clone_scene = p_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NxPs2::CGeomNode *	CPs2Scene::GetEngineAddScene() const
+{
+	return mp_plat_add_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::SetEngineAddScene(NxPs2::CGeomNode *p_scene)
+{
+	mp_plat_add_scene = p_scene;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CScene
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::create_sectors(NxPs2::CGeomNode *p_node)
+{
+	if (!p_node)
+		return;
+
+	while (p_node)
+	{
+		// Recursively traverse tree, treating the object-level nodes as leaves.
+		// From each object, create a sector.
+		// (Mick: optimized tail recursion, after suggestion from John Brandwood)
+		if (p_node->IsObject())			// Create CSector from object
+		{
+			CSector *pSector;
+			CPs2Sector *pPs2Sector;		// platform dependent class
+			CPs2Geom *pGeom;
+	
+			// Find Sector
+			pSector = GetSector(p_node->GetChecksum());
+			Dbg_MsgAssert(!pSector, ("We already have a sector with checksum %x", p_node->GetChecksum()));
+	
+			// If it doesn't exist yet, create it
+			if (!pSector)
+			{
+				pSector = new CPs2Sector;		// Create platform specific data
+				pSector->SetChecksum(p_node->GetChecksum());
+	
+				pGeom = new CPs2Geom;
+				pGeom->SetEngineObject(p_node);
+	
+				pSector->SetGeom(pGeom);
+	
+				AddSector(pSector);
+	
+				//Dbg_Message("Creating new sector %x", p_node->GetChecksum());
+			}
+	
+			// Access platform dependent data
+			pPs2Sector = static_cast(pSector);
+	
+			// Add node to sector
+			//pPs2Sector->SetEngineObject(p_node);
+			pPs2Sector->SetEngineObject(NULL);
+		} else {	// Traverse children and siblings
+			create_sectors(p_node->GetChild());
+		}
+	
+		p_node = p_node->GetSibling();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::add_sectors(NxPs2::CGeomNode *p_node)
+{
+	if (!p_node)
+		return;
+
+	// Recursively traverse tree, treating the object-level nodes as leaves.
+	// From each object, create a sector.
+	if (p_node->IsObject())			// Create CSector from object
+	{
+		CSector *pSector, *p_found_sector;
+		CPs2Sector *pPs2Sector;		// platform dependent class
+		CPs2Geom *pGeom;
+
+		// Find Sector
+		p_found_sector = GetSector(p_node->GetChecksum());
+		//Dbg_MsgAssert(!pSector, ("We already have a sector with checksum %x", p_node->GetChecksum()));
+
+		// Even if it does exist, create it
+		pSector = new CPs2Sector;		// Create platform specific data
+		pSector->SetChecksum(p_node->GetChecksum());
+
+		pGeom = new CPs2Geom;
+		pGeom->SetEngineObject(p_node);
+
+		pSector->SetGeom(pGeom);
+
+		#ifdef	__NOPT_ASSERT__
+		CSector *p_old_sector =
+		#endif
+		ReplaceSector(pSector);
+
+		Dbg_Assert(p_old_sector == p_found_sector);
+		//Dbg_Message("Creating new sector %x", p_node->GetChecksum());
+
+		// Access platform dependent data
+		pPs2Sector = static_cast(pSector);
+
+		// Add node to sector
+		pPs2Sector->SetEngineObject(NULL);
+	} else {	// Traverse children and siblings
+		add_sectors(p_node->GetChild());
+	}
+
+	add_sectors(p_node->GetSibling());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::delete_sectors(NxPs2::CGeomNode *p_node)
+{
+	if (!p_node)
+		return;
+
+	// Recursively traverse tree, treating the object-level nodes as leaves.
+	// From each object, delete the sector.
+	if (p_node->IsObject())			// Create CSector from object
+	{
+		// Find Sector
+		CSector *pSector = GetSector(p_node->GetChecksum());
+		Dbg_MsgAssert(pSector, ("Cant find a sector with checksum %x to delete", p_node->GetChecksum()));
+
+		DeleteSector(pSector);
+	} else {	// Traverse children and siblings
+		delete_sectors(p_node->GetChild());
+	}
+
+	delete_sectors(p_node->GetSibling());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::plat_post_load()
+{
+	// Generate CSectors first
+	// Garrett: TEMP HACK: We don't want to traverse siblings, so we send down the child.
+	//          This works because we know there won't be any data in the root node.
+	//          THIS WILL GO AWAY VERY SOON.  DON'T COPY THIS HACK WITHOUT THIS MESSAGE.
+	create_sectors(mp_plat_scene->GetChild());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Scene::plat_post_add()
+{
+	// Generate CSectors first
+	// Garrett: TEMP HACK: We don't want to traverse siblings, so we send down the child.
+	//          This works because we know there won't be any data in the root node.
+	//          THIS WILL GO AWAY VERY SOON.  DON'T COPY THIS HACK WITHOUT THIS MESSAGE.
+	add_sectors(mp_plat_add_scene->GetChild());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2Scene::plat_load_textures(const char *p_name)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2Scene::plat_load_collision(const char *p_name)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2Scene::plat_add_collision(const char *p_name)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2Scene::plat_unload_add_scene()
+{
+	if (mp_plat_add_scene)
+	{
+		sceGsSyncPath(0, 0);
+
+		// Delete CSectors first
+		// Garrett: TEMP HACK: We don't want to traverse siblings, so we send down the child.
+		//          This works because we know there won't be any data in the root node.
+		//          THIS WILL GO AWAY VERY SOON.  DON'T COPY THIS HACK WITHOUT THIS MESSAGE.
+		//delete_sectors(mp_plat_add_scene->GetChild());
+
+		// clean up the node we created
+		mp_plat_add_scene->Cleanup();
+
+		// Pip::Unload the file
+		Pip::Unload(m_add_scene_filename);
+		m_add_scene_filename[0] = '\0';
+
+		return true;
+	}
+
+	return false;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Create an empty sector
+CSector	*			CPs2Scene::plat_create_sector()
+{
+	CPs2Sector * p_sector = new CPs2Sector(); 
+
+	return p_sector;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void				CPs2Scene::plat_set_majority_color(Image::RGBA rgba)
+{
+	if (mp_plat_scene)
+	{
+		// Set the color of the root node
+		mp_plat_scene->SetColored(true);
+		mp_plat_scene->SetColor(*((uint32 *) &rgba));
+	}
+	if (mp_plat_clone_scene)
+	{
+		// Set the color of the root clone node
+		mp_plat_clone_scene->SetColored(true);
+		mp_plat_clone_scene->SetColor(*((uint32 *) &rgba));
+	}
+}
+		 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA			CPs2Scene::plat_get_majority_color() const
+{
+	uint32 color = mp_plat_scene->GetColor();
+	return *((Image::RGBA *) &color);
+}
+	
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxScene.h b/Code/Gfx/NGPS/p_NxScene.h
new file mode 100644
index 0000000..aa98875
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxScene.h
@@ -0,0 +1,59 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_SCENE_H__
+#define	__GFX_P_NX_SCENE_H__
+
+#include 	"Gfx/nxscene.h"
+
+namespace NxPs2
+{
+	class CGeomNode;
+}
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CScene
+class	CPs2Scene : public CScene
+{
+public:
+								CPs2Scene();
+	virtual						~CPs2Scene();
+
+	NxPs2::CGeomNode *			GetEngineScene() const;
+	void						SetEngineScene(NxPs2::CGeomNode *p_scene);
+
+	NxPs2::CGeomNode *			GetEngineAddScene() const;
+	void						SetEngineAddScene(NxPs2::CGeomNode *p_scene);
+
+	NxPs2::CGeomNode *			GetEngineCloneScene() const;
+	void						SetEngineCloneScene(NxPs2::CGeomNode *p_scene);
+
+private:		
+	virtual void				plat_post_load();	
+	virtual void				plat_post_add();	
+	virtual bool				plat_load_textures(const char *p_name);		// load textures 
+	virtual bool				plat_load_collision(const char *p_name);	// load collision data
+	virtual bool				plat_add_collision(const char *p_name);		// add collision data
+	virtual bool				plat_unload_add_scene();					// unloads previous add scene
+	virtual	CSector	*			plat_create_sector();	 					// empty sector
+	virtual void				plat_set_majority_color(Image::RGBA rgba);	// set the most common sector color
+	virtual Image::RGBA			plat_get_majority_color() const;			// get the most common sector color
+	
+
+	void						create_sectors(NxPs2::CGeomNode *p_node);
+	void						add_sectors(NxPs2::CGeomNode *p_node);
+	void						delete_sectors(NxPs2::CGeomNode *p_node);
+
+	NxPs2::CGeomNode *			mp_plat_scene;		// Platform-dependent data
+	NxPs2::CGeomNode *			mp_plat_clone_scene;
+	NxPs2::CGeomNode *			mp_plat_add_scene;
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGPS/p_NxSector.cpp b/Code/Gfx/NGPS/p_NxSector.cpp
new file mode 100644
index 0000000..ddecc8e
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxSector.cpp
@@ -0,0 +1,292 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxSector.cpp
+
+#include 	"Gfx/nx.h"
+#include 	"Gfx/NGPS/p_NxGeom.h"
+#include 	"Gfx/NGPS/p_NxSector.h"
+#include 	"Gfx/NxMiscFX.h"
+#include 	
+#include 	"gfx\ngps\nx\geomnode.h"
+
+#include 	"gel/collision/collision.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Sector::CPs2Sector() :
+	mp_plat_object(NULL), 
+	m_rgba(Image::RGBA(128, 128, 128, 128)),
+	m_shatter(false)
+{
+	m_world_matrix.Ident();
+	m_active = true;						// default to be active....
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Sector::~CPs2Sector()
+{
+	if (mp_geom)
+	{
+		//CPs2Geom *p_ps2_geom = static_cast(mp_geom);
+		//p_ps2_geom->SetEngineObject(NULL);		// Hack so that CGeom doesn't try to free the CGeomNode
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CSector
+// and we will also have a CXboxSector, CNgcSector, even a CPcSector
+// maybe in the future we will have a CPS3Sector?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::plat_set_color(Image::RGBA rgba)
+{
+	// Set values
+	m_rgba = rgba;
+
+	// Engine call here
+	if (mp_plat_object)
+	{
+		mp_plat_object->SetColored(true);
+		mp_plat_object->SetColor(*((uint32 *) &rgba));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA CPs2Sector::plat_get_color() const
+{
+	return m_rgba;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::plat_clear_color()
+{
+#if 0
+	// Set to white
+	plat_set_color(Image::RGBA(128, 128, 128, 128));
+#endif
+	// Engine call here
+	if (mp_plat_object)
+	{
+		mp_plat_object->SetColored(false);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::plat_set_visibility(uint32 mask)
+{
+	// Engine call here
+	if (mp_plat_object)
+	{
+		mp_plat_object->SetVisibility(mask & 0xFF);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CPs2Sector::plat_get_visibility() const
+{
+	if ( mp_plat_object )
+	{
+		return mp_plat_object->GetVisibility() | 0xFFFFFF00;		// To keep format compatible
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::plat_set_active(bool on)	
+{
+	// Set values
+	if (m_active != on)
+	{
+		m_active = on;
+
+		if (mp_plat_object)
+		{
+			// Engine call here
+			mp_plat_object->SetActive(on);
+		}
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Sector::plat_is_active() const
+{
+	return m_active;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::CBBox & CPs2Sector::plat_get_bounding_box() const
+{
+	// Garrett: TODO: change to renderable bounding box
+	Dbg_Assert(mp_coll_sector);
+	Dbg_Assert(mp_coll_sector->GetGeometry());
+	return mp_coll_sector->GetGeometry()->GetBBox();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::plat_set_world_position(const Mth::Vector& pos)
+{
+	 // Set values
+	 m_world_matrix[W] = pos;
+
+	 // Engine call here
+	 // TEMP, just update matrix of CGeomNode
+	 update_engine_matrix();
+ }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CPs2Sector::plat_get_world_position() const
+{
+	return m_world_matrix[W];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::plat_set_y_rotation(Mth::ERot90 rot)
+{
+	// Engine call here
+	// Garrett: TEMP just set the world matrix
+	Mth::Matrix orig_rot_mat(m_world_matrix), rot_mat;
+	float rad = (float) ((int) rot) * (Mth::PI * 0.5f);
+	CreateRotateYMatrix(rot_mat, rad);
+
+	orig_rot_mat[W] = Mth::Vector(0.0f, 0.0f, 0.0f, 1.0f);
+
+	rot_mat = orig_rot_mat * rot_mat;
+	//Dbg_Message("[X] (%f, %f, %f, %f)", rot_mat[X][X], rot_mat[X][Y],  rot_mat[X][Z],  rot_mat[X][W]);
+	//Dbg_Message("[Y] (%f, %f, %f, %f)", rot_mat[Y][X], rot_mat[Y][Y],  rot_mat[Y][Z],  rot_mat[Y][W]);
+	//Dbg_Message("[Z] (%f, %f, %f, %f)", rot_mat[Z][X], rot_mat[Z][Y],  rot_mat[Z][Z],  rot_mat[Z][W]);
+	//Dbg_Message("[W] (%f, %f, %f, %f)", rot_mat[W][X], rot_mat[W][Y],  rot_mat[W][Z],  rot_mat[W][W]);
+	
+	m_world_matrix[X] = rot_mat[X];
+	m_world_matrix[Y] = rot_mat[Y];
+	m_world_matrix[Z] = rot_mat[Z];
+
+	update_engine_matrix();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::update_engine_matrix()
+{
+	if ( mp_plat_object )
+	{
+		mp_plat_object->SetMatrix(&m_world_matrix);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2Sector::plat_set_shatter(bool on)	
+{
+	// Set values
+	m_shatter = on;
+
+	// Engine call here
+	if( on && mp_geom )
+	{
+		Shatter( mp_geom );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2Sector::plat_get_shatter() const
+{
+	return m_shatter;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector * CPs2Sector::plat_clone(bool instance, CScene *p_dest_scene)
+{
+	// Copy into new sector
+	CPs2Sector *p_new_sector = new CPs2Sector(*this);
+
+	// Copy engine data
+	if (p_new_sector && mp_plat_object)
+	{
+		if (instance)
+		{
+			p_new_sector->mp_plat_object = mp_plat_object->CreateInstance(&p_new_sector->m_world_matrix);
+		} else {
+			p_new_sector->mp_plat_object = mp_plat_object->CreateInstance(&p_new_sector->m_world_matrix);
+			Dbg_Message("CPs2Sector: Can't create copy of CGeomNode yet");
+			//p_new_sector->mp_plat_object = mp_plat_object->CreateCopy();
+		}
+	}
+
+	return p_new_sector;
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxSector.h b/Code/Gfx/NGPS/p_NxSector.h
new file mode 100644
index 0000000..43d080f
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxSector.h
@@ -0,0 +1,96 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxSector.h
+
+#ifndef	__GFX_P_NX_SECTOR_H__
+#define	__GFX_P_NX_SECTOR_H__
+
+#include 	
+
+#include 	
+#include 	
+
+namespace NxPs2
+{
+	class CGeomNode;
+}
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CSector
+// and we will also have a CXboxSector, CNgcSector, even a CPcSector
+// maybe in the future we will have a CPS3Sector?
+class	CPs2Sector : public CSector
+{
+public:
+								CPs2Sector();
+	virtual						~CPs2Sector();
+
+	void						SetEngineObject(NxPs2::CGeomNode *p_object);
+	NxPs2::CGeomNode *			GetEngineObject() const;
+
+private:		// It's all private, as it is machine specific
+	virtual void				plat_set_color(Image::RGBA rgba);
+	virtual	Image::RGBA			plat_get_color() const;
+	virtual void				plat_clear_color();
+
+	virtual void				plat_set_visibility(uint32 mask);
+	virtual	uint32				plat_get_visibility() const;
+
+	virtual void				plat_set_active(bool on);	
+	virtual	bool				plat_is_active() const;
+
+	virtual const Mth::CBBox &	plat_get_bounding_box() const;
+
+	virtual void				plat_set_world_position(const Mth::Vector& pos);
+	virtual const Mth::Vector &	plat_get_world_position() const;
+
+	virtual void				plat_set_y_rotation(Mth::ERot90 rot);
+
+	virtual	void				plat_set_shatter(bool on);
+	virtual	bool				plat_get_shatter() const;
+	
+	virtual CSector *			plat_clone(bool instance, CScene *p_dest_scene = NULL);
+
+	// local functions
+	void 						update_engine_matrix();
+
+	NxPs2::CGeomNode *			mp_plat_object;
+
+	Mth::Matrix					m_world_matrix;
+
+	Image::RGBA					m_rgba;
+	bool						m_active;
+	bool						m_shatter;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Inlines
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CPs2Sector::SetEngineObject(NxPs2::CGeomNode *p_object)
+{
+	mp_plat_object = p_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline NxPs2::CGeomNode *	CPs2Sector::GetEngineObject() const
+{
+	return mp_plat_object;
+}
+
+} // Namespace Nx  			
+
+#endif
\ No newline at end of file
diff --git a/Code/Gfx/NGPS/p_NxSprite.cpp b/Code/Gfx/NGPS/p_NxSprite.cpp
new file mode 100644
index 0000000..907c786
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxSprite.cpp
@@ -0,0 +1,160 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxSprite.cpp
+
+#include 	"Gfx/NGPS/p_NxSprite.h"
+#include 	"Gfx/NGPS/p_NxTexture.h"
+#include 	"Gfx/NGPS/p_NxWin2D.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CSprite
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Sprite::CPs2Sprite(CWindow2D *p_window) : CSprite(p_window)
+{
+	mp_plat_sprite = new NxPs2::SSprite();
+
+	plat_initialize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Sprite::~CPs2Sprite()
+{
+	delete mp_plat_sprite;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Sprite::plat_initialize()
+{
+	plat_update_engine();
+	plat_update_priority();
+	plat_update_hidden();
+	plat_update_window();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Sprite::plat_update_hidden()
+{
+	// Take sprite on or off draw list
+	mp_plat_sprite->SetHidden(m_hidden);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Sprite::plat_update_engine()
+{
+	CPs2Texture *p_ps2_texture = static_cast(mp_texture);
+
+	// Rebuild sprite primitives
+	if (p_ps2_texture)
+	{
+		mp_plat_sprite->SetTexture(p_ps2_texture->GetSingleTexture());
+	} else {
+		mp_plat_sprite->SetTexture(NULL);
+	}
+
+	mp_plat_sprite->m_xpos		= m_pos_x;
+	mp_plat_sprite->m_ypos		= m_pos_y;
+	mp_plat_sprite->m_width		= m_width;
+	mp_plat_sprite->m_height	= m_height;
+	mp_plat_sprite->m_scale_x	= m_scale_x;
+	mp_plat_sprite->m_scale_y	= m_scale_y;
+
+	mp_plat_sprite->m_xhot		= ((m_anchor_x + 1.0f) * 0.5f) * (m_width);
+	mp_plat_sprite->m_yhot		= ((m_anchor_y + 1.0f) * 0.5f) * (m_height);
+
+	mp_plat_sprite->m_rot		= m_rotation;
+	mp_plat_sprite->m_rgba		= *((uint32 *) &m_rgba);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Sprite::plat_update_priority()
+{
+	// Update draw list
+	if (m_use_zbuffer)
+	{
+		mp_plat_sprite->SetZValue((uint32) m_zvalue);
+	} else {
+		mp_plat_sprite->SetPriority(m_priority);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Sprite::plat_update_window()
+{
+	CPs2Window2D *p_ps2_window = static_cast(mp_window);
+
+	if (p_ps2_window)
+	{
+		mp_plat_sprite->SetScissorWindow(p_ps2_window->GetEngineWindow());
+	} else {
+		mp_plat_sprite->SetScissorWindow(NULL);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::plat_enable_constant_z_value(bool enable)
+{
+	NxPs2::SSprite::EnableConstantZValue(enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::plat_set_constant_z_value(Nx::ZBufferValue z)
+{
+	NxPs2::SSprite::SetConstantZValue(z);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::ZBufferValue	CSprite::plat_get_constant_z_value()
+{
+	return NxPs2::SSprite::GetConstantZValue();
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxSprite.h b/Code/Gfx/NGPS/p_NxSprite.h
new file mode 100644
index 0000000..80b88a4
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxSprite.h
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_SPRITE_H__
+#define	__GFX_P_NX_SPRITE_H__
+
+#include 	"Gfx/NxSprite.h"
+#include 	"Gfx/NGPS/NX/texture.h"
+#include 	"Gfx/NGPS/NX/sprite.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CSprite
+class	CPs2Sprite : public CSprite
+{
+public:
+								CPs2Sprite(CWindow2D *p_window = NULL);
+	virtual						~CPs2Sprite();
+
+private:		// It's all private, as it is machine specific
+	virtual void				plat_initialize();
+
+	virtual void				plat_update_hidden();		// Tell engine of update
+	virtual void				plat_update_engine();		// Update engine primitives
+	virtual void				plat_update_priority();
+	virtual void				plat_update_window();
+
+	NxPs2::SSprite *			mp_plat_sprite;
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGPS/p_NxTexMan.cpp b/Code/Gfx/NGPS/p_NxTexMan.cpp
new file mode 100644
index 0000000..28c43c4
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxTexMan.cpp
@@ -0,0 +1,80 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxTexMan.cpp - PS2 platform specific interface to CTexMan
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+
+#include 
+
+#include "gfx/NxTexMan.h"
+#include "gfx/NGPS/p_NxTexture.h"
+
+#include 
+
+namespace NxPs2
+{
+	void WaitForRendering();
+}
+
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict *		CTexDictManager::s_plat_load_texture_dictionary(const char *p_tex_dict_name, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup)
+{
+	return new CPs2TexDict(p_tex_dict_name, is_level_data, texDictOffset, isSkin, forceTexDictLookup);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict *		CTexDictManager::s_plat_load_texture_dictionary(uint32 checksum, uint32* pData, int dataSize, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup)
+{
+	CPs2TexDict* pTexDict = new CPs2TexDict( checksum );
+	pTexDict->LoadTextureDictionary(NULL, pData, dataSize, is_level_data, texDictOffset, isSkin, forceTexDictLookup);
+	return pTexDict;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict *		CTexDictManager::s_plat_create_texture_dictionary(uint32 checksum)
+{
+	return new CPs2TexDict(checksum);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CTexDictManager::s_plat_unload_texture_dictionary(CTexDict *p_tex_dict)
+{
+	#if 0
+	sceGsSyncPath(0, 0);		// To make sure it isn't being used
+	#else
+	NxPs2::WaitForRendering();
+	#endif
+	
+	delete p_tex_dict;
+
+	return true;
+}
+
+
+} 
+ 
diff --git a/Code/Gfx/NGPS/p_NxTexture.cpp b/Code/Gfx/NGPS/p_NxTexture.cpp
new file mode 100644
index 0000000..c43b999
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxTexture.cpp
@@ -0,0 +1,2265 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxTexture.cpp
+
+#include 	"Gfx/gfxutils.h"
+#include 	"Gfx/Nx.h"
+#include 	"Gfx/NGPS/p_NxTexture.h"
+#include 	"Gfx/NGPS/PaletteGen.h"
+#include	"Gfx/NGPS/nx/scene.h"
+#include	"Gfx/NGPS/nx/sprite.h"
+#include	
+#include	
+#include	
+
+#define min(x, y) (((x) > (y))? (y): (x))
+#define max(x, y) (((x) < (y))? (y): (x))
+
+#define USE_FAST_COLOR_FIND 1
+#define PRINT_TIMES 0
+#define PRINT_FAST_TIMES 0
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTexture
+
+bool				CPs2Texture::s_tables_initialized = false;
+uint8				CPs2Texture::s_clut8_index_to_offset_table[256];
+uint8				CPs2Texture::s_clut8_offset_to_index_table[256];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Texture::CPs2Texture(bool loading_screen) :
+	m_transparent(false),
+	m_loading_screen(loading_screen),
+	mp_single_texture(NULL),
+	mp_group_texture(NULL),
+	mp_orig_temp_32bit_image(NULL),
+	mp_cur_temp_32bit_image(NULL),
+	mp_temp_32bit_single_texture(NULL)
+{
+	m_width = m_height = m_bitdepth = m_clut_bitdepth = m_num_mipmaps = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Texture::CPs2Texture(const CPs2Texture & src_texture) :
+	CTexture(src_texture)
+{
+	Dbg_MsgAssert(src_texture.mp_group_texture == NULL, ("Can't copy a CPs2Texture that is in a texture group"));
+
+	m_width = src_texture.m_width;
+	m_height = src_texture.m_height;
+	m_bitdepth = src_texture.m_bitdepth;
+	m_clut_bitdepth = src_texture.m_clut_bitdepth;
+	m_num_mipmaps = src_texture.m_num_mipmaps;
+
+	m_transparent = src_texture.m_transparent;
+	m_loading_screen = src_texture.m_loading_screen;
+
+	mp_group_texture = NULL;
+
+	if (src_texture.mp_orig_temp_32bit_image)
+	{
+		mp_orig_temp_32bit_image = new uint32[m_width * m_height];
+		memcpy(mp_orig_temp_32bit_image, src_texture.mp_orig_temp_32bit_image, m_width * m_height * sizeof(uint32));
+	}
+	else
+	{
+		mp_orig_temp_32bit_image = NULL;
+	}
+
+	if (src_texture.mp_cur_temp_32bit_image)
+	{
+		mp_cur_temp_32bit_image = new uint32[m_width * m_height];
+		memcpy(mp_cur_temp_32bit_image, src_texture.mp_cur_temp_32bit_image, m_width * m_height * sizeof(uint32));
+	}
+	else
+	{
+		mp_cur_temp_32bit_image = NULL;
+	}
+
+	if (src_texture.mp_single_texture)
+	{
+		mp_single_texture = new NxPs2::SSingleTexture(*src_texture.mp_single_texture);
+	}
+	else
+	{
+		mp_single_texture = NULL;
+	}
+
+	if (src_texture.mp_temp_32bit_single_texture)
+	{
+		mp_temp_32bit_single_texture = new NxPs2::SSingleTexture(*src_texture.mp_temp_32bit_single_texture);
+	}
+	else
+	{
+		mp_temp_32bit_single_texture = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Texture::~CPs2Texture()
+{
+	// delete the actual single texture, if any
+	if (mp_single_texture)
+	{
+		delete mp_single_texture;
+	}
+
+	if (mp_orig_temp_32bit_image)
+	{
+		delete mp_orig_temp_32bit_image;
+	}
+
+	if (mp_cur_temp_32bit_image)
+	{
+		delete mp_cur_temp_32bit_image;
+	}
+
+	if (mp_temp_32bit_single_texture)
+	{
+		delete mp_temp_32bit_single_texture;
+	}
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	CPs2Texture::sInitTables()
+{
+    if (!s_tables_initialized)
+	{
+		uint i, j, idx;
+
+		idx = 0;
+        for(i = 0; i < 256; i+=32)
+		{
+            for(j = i; j < i+8; j++)
+                s_clut8_index_to_offset_table[j] = (uint8) idx++;
+            for(j = i+16; j < i+16+8; j++)
+                s_clut8_index_to_offset_table[j] = (uint8) idx++;
+            for(j = i+8;  j < i+8+8;  j++)
+                s_clut8_index_to_offset_table[j] = (uint8) idx++;
+            for(j = i+24; j < i+24+8; j++)
+                s_clut8_index_to_offset_table[j] = (uint8) idx++;
+        }
+
+		idx = 0;
+		for(i = 0; i < 256; i+=32)
+		{
+			for(j = i; j < i+8; j++)
+				s_clut8_offset_to_index_table[idx++] = (uint8) j;
+			for(j = i+16; j < i+16+8; j++)
+				s_clut8_offset_to_index_table[idx++] = (uint8) j;
+			for(j = i+8;  j < i+8+8;  j++)
+				s_clut8_offset_to_index_table[idx++] = (uint8) j;
+			for(j = i+24; j < i+24+8; j++)
+				s_clut8_offset_to_index_table[idx++] = (uint8) j;
+		}
+	
+        s_tables_initialized = true;
+	}
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram)
+{
+	Dbg_Assert(mp_group_texture == NULL);
+
+	// call engine
+	mp_single_texture = new NxPs2::SSingleTexture(p_texture_name, sprite, alloc_vram, m_loading_screen);
+	if (mp_single_texture)
+	{
+		m_width			= mp_single_texture->GetOrigWidth();	// Garrett: This is probably not what we want, but CSprite
+		m_height		= mp_single_texture->GetOrigHeight();  // needs this.  Probably will need to change CTexture.
+		m_bitdepth		= mp_single_texture->GetBitdepth();
+		m_clut_bitdepth	= mp_single_texture->GetClutBitdepth();
+		m_num_mipmaps	= mp_single_texture->GetNumMipmaps();
+		m_transparent	= mp_single_texture->IsTransparent();
+	}
+
+	return mp_single_texture != NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, bool sprite, bool alloc_vram)
+{
+	Dbg_Assert(mp_group_texture == NULL);
+
+	// call engine
+	mp_single_texture = new NxPs2::SSingleTexture(p_buffer, buffer_size, sprite, alloc_vram, m_loading_screen);
+	if (mp_single_texture)
+	{
+		m_width			= mp_single_texture->GetOrigWidth();	// Garrett: This is probably not what we want, but CSprite
+		m_height		= mp_single_texture->GetOrigHeight();  // needs this.  Probably will need to change CTexture.
+		m_bitdepth		= mp_single_texture->GetBitdepth();
+		m_clut_bitdepth	= mp_single_texture->GetClutBitdepth();
+		m_num_mipmaps	= mp_single_texture->GetNumMipmaps();
+		m_transparent	= mp_single_texture->IsTransparent();
+	}
+
+	return mp_single_texture != NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_replace_texture(CTexture *p_texture)
+{								 	
+	Dbg_Assert(p_texture);
+	Dbg_Assert(mp_group_texture);
+
+	CPs2Texture *p_ps2_texture = static_cast(p_texture);
+	Dbg_Assert(p_ps2_texture->mp_single_texture);
+
+	if ((m_width != p_ps2_texture->m_width) ||
+		(m_height != p_ps2_texture->m_height) ||
+		(m_bitdepth != p_ps2_texture->m_bitdepth) ||
+		(m_num_mipmaps != p_ps2_texture->m_num_mipmaps))
+	{
+		Dbg_Message("Original size (%d, %d); Replacement size (%d, %d)", m_width, m_height, p_ps2_texture->m_width, p_ps2_texture->m_height);
+		Dbg_Message("Original bitdepth %d; Replacement bitdepth %d", m_bitdepth, p_ps2_texture->m_bitdepth);
+		Dbg_Message("Original num mipmaps %d; Replacement num mipmaps %d", m_num_mipmaps, p_ps2_texture->m_num_mipmaps);
+		return false;
+	}
+
+#if 0
+	if ((m_bitdepth <= 8) && (m_clut_bitdepth < p_ps2_texture->m_clut_bitdepth))
+	{
+		Dbg_Message("Original clut bitdepth %d; Replacement clut bitdepth %d", m_clut_bitdepth, p_ps2_texture->m_clut_bitdepth);
+		return false;
+	}
+#endif
+
+	// Copy texture
+	uint8 *p_orig_tex_buffer = mp_group_texture->GetTextureBuffer();
+	uint8 *p_new_tex_buffer = p_ps2_texture->mp_single_texture->GetTextureBuffer();
+	if (mp_group_texture->HasSwizzleMip())
+	{
+		mp_group_texture->ReplaceTextureData(p_new_tex_buffer);
+	}
+	else
+	{
+		memcpy(p_orig_tex_buffer, p_new_tex_buffer, p_ps2_texture->mp_single_texture->GetTextureBufferSize());
+	}
+
+	// And clut (if any)
+	uint32 *p_orig_clut_buffer = (uint32 *) mp_group_texture->GetClutBuffer();
+	if (p_orig_clut_buffer)
+	{
+		uint32 *p_new_clut_buffer = (uint32 *) p_ps2_texture->mp_single_texture->GetClutBuffer();
+		Dbg_Assert(p_new_clut_buffer);
+
+		if (m_clut_bitdepth == p_ps2_texture->m_clut_bitdepth)
+		{
+			memcpy(p_orig_clut_buffer, p_new_clut_buffer, p_ps2_texture->mp_single_texture->GetClutBufferSize());
+		} else if ((m_clut_bitdepth == 32) && (p_ps2_texture->m_clut_bitdepth == 16))
+		{
+			uint16 *p_clut_16_buffer = (uint16 *) p_new_clut_buffer;
+			Image::RGBA new_color;
+			new_color.a = 0x80;
+
+			int num_colors = p_ps2_texture->mp_single_texture->GetClutBufferSize() / 2;
+			for (int i = 0; i < num_colors; i++)
+			{
+				// Convert from 16 to 32 bit
+				new_color.r = ((*(p_clut_16_buffer)   >>  0) & 0x1f) << 3;
+				new_color.g = ((*(p_clut_16_buffer)   >>  5) & 0x1f) << 3;
+				new_color.b = ((*(p_clut_16_buffer++) >> 10) & 0x1f) << 3;
+
+				//Dbg_Message("Replacing color #%d %x with %x", i, *p_orig_clut_buffer, *((uint32 *) &new_color));
+				*(p_orig_clut_buffer++) = *((uint32 *) &new_color);
+			}
+		} else if ((m_clut_bitdepth == 16) && (p_ps2_texture->m_clut_bitdepth == 32))
+		{
+			uint16 *p_clut_16_buffer = (uint16 *) p_orig_clut_buffer;
+			uint16 new_color;
+			Image::RGBA *p_full_color;
+
+			int num_colors = p_ps2_texture->mp_single_texture->GetClutBufferSize() / 4;
+			for (int i = 0; i < num_colors; i++)
+			{
+				p_full_color = (Image::RGBA *) p_new_clut_buffer++;
+				new_color = 0x8000 | (p_full_color->r >> 3) | ( (p_full_color->g >> 3) << 5 ) | ( (p_full_color->b >> 3) << 10 );
+				*(p_clut_16_buffer++) = new_color;		
+			}
+		} else {
+			Dbg_Message("Can't handle this combination: clut bitdepth %d; Replacement clut bitdepth %d", m_clut_bitdepth, p_ps2_texture->m_clut_bitdepth);
+			return false;
+		}
+
+		//Dbg_Message("Original clut size %d; Replacement clut size %d", mp_group_texture->GetClutBufferSize(), p_ps2_texture->mp_single_texture->GetClutBufferSize());
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_generate_32bit_image(bool renderable, bool store_original)
+{
+	Dbg_MsgAssert(!mp_cur_temp_32bit_image, ("Can't generate temp 32-bit image: one already exists"));
+
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_MsgAssert(m_clut_bitdepth == 32, ("Clut bitdepth is only %d, not 32", m_clut_bitdepth));
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(s_tables_initialized);
+
+	// Allocate buffer
+	mp_cur_temp_32bit_image = new uint32[m_width * m_height];
+	uint32 *p_pixel_buffer = mp_cur_temp_32bit_image;
+
+	// Get source buffers
+	uint8 *p_orig_tex_buffer = mp_single_texture->GetTextureBuffer();
+	// And clut (if any)
+	uint32 *p_orig_clut_buffer = (uint32 *) mp_single_texture->GetClutBuffer();
+
+	// Move texture buffer to start (and flip y)
+	p_orig_tex_buffer += (m_height - 1) * m_width;
+
+	for (uint h = 0; h < m_height; h++)
+	{
+		for (uint w = 0; w < m_width; w++)
+		{
+			// Copies from 8-bit to 32-bit
+			*p_pixel_buffer++ = p_orig_clut_buffer[ s_clut8_index_to_offset(p_orig_tex_buffer[w]) ];
+		}
+		p_orig_tex_buffer -= m_width;	// Negative because were flipping the lines
+	}
+
+	// Copy to original buffer
+	if (store_original)
+	{
+		mp_orig_temp_32bit_image = new uint32[m_width * m_height];
+		memcpy(mp_orig_temp_32bit_image, mp_cur_temp_32bit_image, m_width * m_height * sizeof(uint32));
+	}
+
+	// Allocate a SSingleTexture
+	if (renderable)
+	{
+		mp_temp_32bit_single_texture = new NxPs2::SSingleTexture((uint8 *) mp_cur_temp_32bit_image, m_width, m_height, 32,
+																 m_clut_bitdepth, m_num_mipmaps, true, true, m_loading_screen);
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_put_32bit_image_into_texture(bool new_palette)
+{
+	Dbg_MsgAssert(mp_cur_temp_32bit_image, ("No temp 32-bit image"));
+
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_Assert(m_clut_bitdepth == 32);
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(s_tables_initialized);
+
+	// Generate a new palette
+	if (new_palette)
+	{
+		GeneratePalette((Image::RGBA *) mp_single_texture->GetClutBuffer(), (uint8 *) mp_cur_temp_32bit_image, m_width, m_height, 32, 1 << m_bitdepth);
+	}
+
+	// Get buffers
+	uint8 *p_tex_buffer = mp_single_texture->GetTextureBuffer();
+	uint32 *p_pixel_buffer = mp_cur_temp_32bit_image;
+
+	// Move texture buffer to start (and flip y)
+	p_tex_buffer += (m_height - 1) * m_width;
+
+#if USE_FAST_COLOR_FIND
+#if PRINT_FAST_TIMES
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif // PRINT_FAST_TIMES
+
+	setup_fast_clut_color_find(false);
+
+#if PRINT_FAST_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	Dbg_Message("plat_put_32bit_image_into_texture fast setup time %d us", end_time - start_time);
+#endif // PRINT_FAST_TIMES
+#endif // USE_FAST_COLOR_FIND
+
+	for (uint h = 0; h < m_height; h++)
+	{
+		for (uint w = 0; w < m_width; w++)
+		{
+			// Copies from 32-bit to 8-bit
+#if USE_FAST_COLOR_FIND
+			p_tex_buffer[w] = s_clut8_offset_to_index( fast_find_closest_clut_color( *((Image::RGBA *) p_pixel_buffer++) ) );
+#else
+			p_tex_buffer[w] = s_clut8_offset_to_index( find_closest_clut_color( *((Image::RGBA *) p_pixel_buffer++) ) );
+#endif // USE_FAST_COLOR_FIND
+		}
+		p_tex_buffer -= m_width;		// Negative because were flipping the lines
+	}
+
+#if USE_FAST_COLOR_FIND
+	cleanup_fast_clut_color_find();
+
+#if PRINT_FAST_TIMES
+	end_time = Tmr::GetTimeInUSeconds();
+	Dbg_Message("plat_put_32bit_image_into_texture fast total time %d us", end_time - start_time);
+#endif // PRINT_FAST_TIMES
+#endif // USE_FAST_COLOR_FIND
+
+	// Delete buffers
+	delete [] mp_cur_temp_32bit_image;
+	mp_cur_temp_32bit_image = NULL;
+
+	if (mp_orig_temp_32bit_image)
+	{
+		delete [] mp_orig_temp_32bit_image;
+		mp_orig_temp_32bit_image = NULL;
+	}
+
+	if (mp_temp_32bit_single_texture)
+	{
+		delete mp_temp_32bit_single_texture;
+		mp_temp_32bit_single_texture = NULL;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::s_get_nearest_pixels_from_32bit_texture(Image::RGBA p_nearest[][2], uint32 *p_buffer,
+															 uint16 x_pos, uint16 y_pos, uint16 width, uint16 height)
+{
+	int next_x = (x_pos >= (width - 1)) ? 0 : 1;		// Clamp x if at end
+	bool clamp_y = (y_pos >= (height - 1));				// And same for y
+
+	p_buffer += (y_pos * width) + x_pos;
+
+	// First row
+	p_nearest[0][0] = *((Image::RGBA *) p_buffer);
+	p_nearest[0][1] = *((Image::RGBA *) &p_buffer[next_x]);
+
+	if (!clamp_y)
+	{
+		p_buffer += width;
+	}
+
+	// Second row
+	p_nearest[1][0] = *((Image::RGBA *) p_buffer);
+	p_nearest[1][1] = *((Image::RGBA *) &p_buffer[next_x]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::s_get_region(uint8 *p_buffer, uint16 x_pos, uint16 y_pos, uint16 tex_width, uint16 tex_height,
+								  uint8 *p_region, uint16 region_width, uint16 region_height, uint16 bitdepth)
+{
+	int bytes_per_pixel = bitdepth / 8;
+	int copy_bytes;
+	int width_in_bytes;
+	int stride_in_bytes;
+
+	// Clamp region values
+	int clamp_region_width = min(region_width, tex_width - x_pos);
+	int clamp_region_height = min(region_height, tex_height - y_pos);
+	Dbg_Assert(clamp_region_width >= 0);
+	Dbg_Assert(clamp_region_height >= 0);
+
+	// Move texture buffer to start
+	if (bytes_per_pixel)
+	{
+		p_buffer += ((y_pos * tex_width) + x_pos) * bytes_per_pixel;
+		copy_bytes = clamp_region_width * bytes_per_pixel;
+		width_in_bytes = region_width * bytes_per_pixel;
+		stride_in_bytes = tex_width * bytes_per_pixel;
+	}
+	else // 4-bit
+	{
+		p_buffer += ((y_pos * tex_width) + x_pos) / 2;
+		copy_bytes = clamp_region_width / 2;
+		width_in_bytes = region_width / 2;
+		stride_in_bytes = tex_width / 2;
+	}
+
+	//Dbg_Message("s_get_region(): xpos %d, ypos %d, tex_width %d, tex_height %d, region_width %d, region_height %d, width_in_bytes %d, stride_in_bytes %d, bitdepth %d",
+	//			x_pos, y_pos, tex_width, tex_height, region_width, region_height, width_in_bytes, stride_in_bytes, bitdepth);
+	for (int h = 0; h < clamp_region_height; h++)
+	{
+		// Copy each line
+		memcpy(p_region, p_buffer, copy_bytes);
+		p_region += width_in_bytes;
+		p_buffer += stride_in_bytes;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::s_put_region(uint8 *p_buffer, uint16 x_pos, uint16 y_pos, uint16 tex_width, uint16 tex_height,
+								  uint8 *p_region, uint16 region_width, uint16 region_height, uint16 bitdepth)
+{
+	int bytes_per_pixel = bitdepth / 8;
+	int copy_bytes;
+	int width_in_bytes;
+	int stride_in_bytes;
+
+	// Clamp region values
+	int clamp_region_width = min(region_width, tex_width - x_pos);
+	int clamp_region_height = min(region_height, tex_height - y_pos);
+	Dbg_Assert(clamp_region_width >= 0);
+	Dbg_Assert(clamp_region_height >= 0);
+
+	// Move texture buffer to start
+	if (bytes_per_pixel)
+	{
+		p_buffer += ((y_pos * tex_width) + x_pos) * bytes_per_pixel;
+		copy_bytes = clamp_region_width * bytes_per_pixel;
+		width_in_bytes = region_width * bytes_per_pixel;
+		stride_in_bytes = tex_width * bytes_per_pixel;
+	}
+	else // 4-bit
+	{
+		p_buffer += ((y_pos * tex_width) + x_pos) / 2;
+		copy_bytes = clamp_region_width / 2;
+		width_in_bytes = region_width / 2;
+		stride_in_bytes = tex_width / 2;
+	}
+
+	//Dbg_Message("s_put_region(): xpos %d, ypos %d, tex_width %d, tex_height %d, region_width %d, region_height %d, width_in_bytes %d, stride_in_bytes %d, bitdepth %d",
+	//			x_pos, y_pos, tex_width, tex_height, region_width, region_height, width_in_bytes, stride_in_bytes, bitdepth);
+	for (int h = 0; h < clamp_region_height; h++)
+	{
+		// Copy each line
+		memcpy(p_buffer, p_region, copy_bytes);
+		p_region += width_in_bytes;
+		p_buffer += stride_in_bytes;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::s_scale_texture(uint8 *p_buffer, uint16 width, uint16 height,
+									 uint8 *p_result_buffer, uint16 new_width, uint16 new_height, uint16 bitdepth)
+{
+	Dbg_MsgAssert(bitdepth > 8, ("Can't scale a paletted texture.  Convert to unpaletted first."));
+	Dbg_Assert(bitdepth == 32);		// We'll worry about 16 and 24 bit later.
+
+#if PRINT_TIMES
+	static uint32 total_time = 0;
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	float orig_coord[2] = { 0.0f, 0.0f };
+	float delta_dim[2];
+	float delta_coord[2] = { 0.0f, 0.0f };
+	uint16 orig_index[2] = { 0, 0 };
+
+	Image::RGBA closest_pixels[2][2];
+	float pixel_portion[2][2];
+	Image::RGBA combined_pixel;
+
+	delta_dim[X] = (float) width / (float) new_width;
+	delta_dim[Y] = (float) height / (float) new_height;
+
+	for (int h = 0; h < new_height; h++)
+	{
+		orig_index[Y] = (uint16) orig_coord[Y];			// Get integer version
+		delta_coord[Y] = orig_coord[Y] - orig_index[Y];	// And now get fractional part
+
+		for (int w = 0; w < new_width; w++)
+		{
+			orig_index[X] = (uint16) orig_coord[X];			// Get integer version
+			delta_coord[X] = orig_coord[X] - orig_index[X];	// And now get fractional part
+
+			// Calculate the pixel portions
+			pixel_portion[0][0] = (1.0f - delta_coord[X]) * (1.0f - delta_coord[Y]);
+			pixel_portion[0][1] =        (delta_coord[X]) * (1.0f - delta_coord[Y]);
+			pixel_portion[1][0] = (1.0f - delta_coord[X]) *        (delta_coord[Y]);
+			pixel_portion[1][1] =        (delta_coord[X]) *        (delta_coord[Y]);
+				
+			Dbg_MsgAssert((orig_index[X] + 0) < width, ("X coord out of range: %d", orig_index[X]));
+			Dbg_MsgAssert((orig_index[Y] + 0) < height, ("Y coord out of range: %d", orig_index[Y]));
+
+			// Get the 4 nearest pixels
+			s_get_nearest_pixels_from_32bit_texture(closest_pixels, (uint32 *) p_buffer, orig_index[X], orig_index[Y], width, height);
+
+			// Calculate each color
+			combined_pixel.r = (uint8) ((pixel_portion[0][0] * (float) closest_pixels[0][0].r) +
+									    (pixel_portion[0][1] * (float) closest_pixels[0][1].r) +
+									    (pixel_portion[1][0] * (float) closest_pixels[1][0].r) +
+									    (pixel_portion[1][1] * (float) closest_pixels[1][1].r));
+			combined_pixel.g = (uint8) ((pixel_portion[0][0] * (float) closest_pixels[0][0].g) +
+									    (pixel_portion[0][1] * (float) closest_pixels[0][1].g) +
+									    (pixel_portion[1][0] * (float) closest_pixels[1][0].g) +
+									    (pixel_portion[1][1] * (float) closest_pixels[1][1].g));
+			combined_pixel.b = (uint8) ((pixel_portion[0][0] * (float) closest_pixels[0][0].b) +
+									    (pixel_portion[0][1] * (float) closest_pixels[0][1].b) +
+									    (pixel_portion[1][0] * (float) closest_pixels[1][0].b) +
+									    (pixel_portion[1][1] * (float) closest_pixels[1][1].b));
+			combined_pixel.a = (uint8) ((pixel_portion[0][0] * (float) closest_pixels[0][0].a) +
+									    (pixel_portion[0][1] * (float) closest_pixels[0][1].a) +
+									    (pixel_portion[1][0] * (float) closest_pixels[1][0].a) +
+									    (pixel_portion[1][1] * (float) closest_pixels[1][1].a));
+
+			// And copy
+			*((uint32 *) p_result_buffer)++ = *((uint32 *) &(combined_pixel));
+
+			// Advance to next coord
+			orig_coord[X] += delta_dim[X];
+		}
+
+		orig_coord[X] = 0.0f;
+		orig_coord[Y] += delta_dim[Y];
+	}
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	total_time += end_time - start_time;
+	Dbg_Message("scale_texture Update time %d us; Total Time %d", end_time - start_time, total_time);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::s_combine_adjacent_borders(uint8 *p_first_rect, uint8 *p_second_rect, uint16 first_width,
+												uint16 first_height, uint16 second_stride, float first_portion,
+												int split_axis, uint16 bitdepth)
+{
+	Dbg_MsgAssert(bitdepth > 8, ("Can't adjust the border of a paletted texture.  Convert to unpaletted first."));
+	Dbg_Assert(bitdepth == 32);		// We'll worry about 16 and 24 bit later.
+
+	Image::RGBA *p_rect_pixel[2] = { (Image::RGBA *) p_first_rect, (Image::RGBA *) p_second_rect };
+	float second_portion = 1.0f - first_portion;
+
+	int num_pixels, inc_pixels;
+
+	// Init
+	if (split_axis == X)
+	{
+		num_pixels = first_width;
+		inc_pixels = 1;
+	}
+	else
+	{
+		Dbg_Assert(first_width == second_stride);
+
+		num_pixels = first_height;
+		inc_pixels = second_stride;
+	}
+
+	// Combine the pixels
+	for (int pidx = 0; pidx < num_pixels; pidx++)
+	{
+		p_rect_pixel[1]->r = (uint8) ( (first_portion * (float) p_rect_pixel[0]->r) + (second_portion * (float) p_rect_pixel[1]->r) );
+		p_rect_pixel[1]->g = (uint8) ( (first_portion * (float) p_rect_pixel[0]->g) + (second_portion * (float) p_rect_pixel[1]->g) );
+		p_rect_pixel[1]->b = (uint8) ( (first_portion * (float) p_rect_pixel[0]->b) + (second_portion * (float) p_rect_pixel[1]->b) );
+		p_rect_pixel[1]->a = (uint8) ( (first_portion * (float) p_rect_pixel[0]->a) + (second_portion * (float) p_rect_pixel[1]->a) );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static const int ALPHA_WEIGHT = 2;    // how much more weight to give to alpha
+uint8 	CPs2Texture::find_closest_clut_color(Image::RGBA rgba)
+{
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_Assert(m_clut_bitdepth == 32);
+	Dbg_Assert(mp_single_texture);
+
+	int clutsize = 1 << m_bitdepth;
+
+	Image::RGBA *p_clut_buffer = (Image::RGBA *) mp_single_texture->GetClutBuffer();
+
+	Dbg_Assert(p_clut_buffer);
+
+    int delR, delG, delB, delA;
+    int bestDist, curDist;
+    uint8 bestIdx = 0;
+
+    bestDist = 0x7FFFFFFF;
+
+    for (int i = 0; i < clutsize; i++)
+	{
+        delR = (int) (p_clut_buffer)->r   - (int) rgba.r;
+        delG = (int) (p_clut_buffer)->g   - (int) rgba.g;
+        delB = (int) (p_clut_buffer)->b   - (int) rgba.b;
+        delA = (int) (p_clut_buffer++)->a - (int) rgba.a;
+
+        // Calculate 4-dimensional distance between color components
+        curDist = (delR * delR) + (delG * delG) + (delB * delB) +
+                  (delA * delA * ALPHA_WEIGHT);
+
+        // Choose the index with the smallest distance from src color
+        if (curDist < bestDist)
+		{
+            bestDist = curDist;
+            bestIdx = i;
+        }
+    }
+
+    return bestIdx;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Holds clut index array start and size
+struct SClutEntries
+{
+	uint16	m_clut_buffer_index;
+	uint8	m_num_clut_entries;
+};
+
+static const int s_axis_level_bits = 3;			// Significant bits used to break down each color into the tree
+static const int s_axis_levels = 1 << s_axis_level_bits;
+static bool s_use_alpha_axis;
+static uint8 *sp_clut_index_buffer = NULL;
+
+//static int s_num_color_searches;
+//static int s_num_color_compares;
+
+// Array is of form [R][G][B][A]
+static SClutEntries s_palette_octree[s_axis_levels][s_axis_levels][s_axis_levels][s_axis_levels];
+
+void 	CPs2Texture::setup_fast_clut_color_find(bool use_alpha)
+{
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_Assert(m_clut_bitdepth == 32);
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(!sp_clut_index_buffer);
+
+	s_use_alpha_axis = use_alpha;
+
+	int clutsize = 1 << m_bitdepth;
+	int range = clutsize >> s_axis_level_bits;
+	int alpha_axis_levels = (s_use_alpha_axis) ? s_axis_levels : 1;
+	int index_buffer_size = s_axis_levels * s_axis_levels * s_axis_levels * alpha_axis_levels * (clutsize / 2);	// Could be made smaller
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	sp_clut_index_buffer = new uint8[index_buffer_size];
+	Mem::Manager::sHandle().PopContext();
+
+	int min_level[4];
+	int max_level[4];
+
+	const int overlap = (range >> 1);
+	const int min_start = -overlap;
+	const int max_start = range + overlap;
+	const int max_alpha_start = (s_use_alpha_axis) ? max_start : clutsize;
+
+	int buffer_index = 0;
+
+	min_level[0] = min_start;
+	max_level[0] = max_start;
+
+	for (int r = 0; r < s_axis_levels; r++)
+	{
+		min_level[1] = min_start;
+		max_level[1] = max_start;
+
+		for (int g = 0; g < s_axis_levels; g++)
+		{
+			min_level[2] = min_start;
+			max_level[2] = max_start;
+
+			for (int b = 0; b < s_axis_levels; b++)
+			{
+				min_level[3] = min_start;
+				max_level[3] = max_alpha_start;
+
+				for (int a = 0; a < alpha_axis_levels; a++)
+				{
+					SClutEntries *p_palette_node = &s_palette_octree[r][g][b][a];
+					p_palette_node->m_clut_buffer_index = buffer_index;
+					p_palette_node->m_num_clut_entries = 0;
+					Image::RGBA *p_clut_buffer = (Image::RGBA *) mp_single_texture->GetClutBuffer();
+
+					Dbg_MsgAssert(buffer_index < (index_buffer_size - clutsize), ("Running out of index buffer space"));
+
+					for (int index = 0; index < clutsize; index++, p_clut_buffer++)
+					{
+						if ((p_clut_buffer->r >= min_level[0]) && (p_clut_buffer->r <= max_level[0]) &&
+							(p_clut_buffer->g >= min_level[1]) && (p_clut_buffer->g <= max_level[1]) &&
+							(p_clut_buffer->b >= min_level[2]) && (p_clut_buffer->b <= max_level[2]) &&
+							(p_clut_buffer->a >= min_level[3]) && (p_clut_buffer->a <= max_level[3]))
+						{
+							p_palette_node->m_num_clut_entries++;
+							sp_clut_index_buffer[buffer_index++] = index;
+						}
+
+					} // for index
+
+					min_level[3] += range;
+					max_level[3] += range;
+
+				} // for a
+
+				min_level[2] += range;
+				max_level[2] += range;
+
+			} // for b
+
+			min_level[1] += range;
+			max_level[1] += range;
+
+		} // for g
+
+		min_level[0] += range;
+		max_level[0] += range;
+
+	} // for r
+
+	//s_num_color_searches = 0;
+	//s_num_color_compares = 0;
+}
+
+
+uint8 	CPs2Texture::fast_find_closest_clut_color(Image::RGBA rgba)
+{
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_Assert(m_clut_bitdepth == 32);
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(sp_clut_index_buffer);
+
+	int range_bits = m_bitdepth - s_axis_level_bits;
+
+	int r_index = rgba.r >> range_bits;
+	int g_index = rgba.g >> range_bits;
+	int b_index = rgba.b >> range_bits;
+	int a_index = (s_use_alpha_axis) ? rgba.a >> range_bits : 0;
+
+	SClutEntries *p_palette_node = &s_palette_octree[r_index][g_index][b_index][a_index];
+
+	//s_num_color_searches++;
+	if (p_palette_node->m_num_clut_entries == 0)
+	{
+		//s_num_color_compares += 256;
+		return find_closest_clut_color(rgba);
+	}
+	else
+	{
+		uint8 *p_palette_index = &sp_clut_index_buffer[p_palette_node->m_clut_buffer_index];
+		Image::RGBA *p_clut_buffer = (Image::RGBA *) mp_single_texture->GetClutBuffer();
+		Dbg_Assert(p_clut_buffer);
+
+		int delR, delG, delB, delA;
+		int bestDist, curDist;
+		uint8 bestIdx = 0;
+
+		bestDist = 0x7FFFFFFF;
+
+		for (int entry_index = 0; entry_index < p_palette_node->m_num_clut_entries; entry_index++, p_palette_index++)
+		{
+			Image::RGBA *p_clut_color = &p_clut_buffer[*p_palette_index];
+
+			delR = (int) p_clut_color->r - (int) rgba.r;
+			delG = (int) p_clut_color->g - (int) rgba.g;
+			delB = (int) p_clut_color->b - (int) rgba.b;
+			delA = (int) p_clut_color->a - (int) rgba.a;
+
+			// Calculate 4-dimensional distance between color components
+			curDist = (delR * delR) + (delG * delG) + (delB * delB) +
+					  (delA * delA * ALPHA_WEIGHT);
+
+			// Choose the index with the smallest distance from src color
+			if (curDist < bestDist)
+			{
+				bestDist = curDist;
+				bestIdx = *p_palette_index;
+			}
+		}
+
+		//s_num_color_compares += p_palette_node->m_num_clut_entries;
+
+		return bestIdx;
+	}
+}
+
+void 	CPs2Texture::cleanup_fast_clut_color_find()
+{
+	Dbg_Assert(sp_clut_index_buffer);
+
+	//Dbg_Message("Average color compares per search: %f", (float) s_num_color_compares / (float) s_num_color_searches);
+
+	delete [] sp_clut_index_buffer;
+	sp_clut_index_buffer = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::copy_region_to_32bit_buffer(uint32 *p_pixel_buffer, uint16 x_pos, uint16 y_pos,
+												 uint16 width, uint16 height)
+{
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_MsgAssert(m_clut_bitdepth == 32, ("Clut bitdepth is only %d, not 32", m_clut_bitdepth));
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(s_tables_initialized);
+
+#if PRINT_TIMES
+	static uint32 total_time = 0;
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	if (mp_cur_temp_32bit_image)
+	{
+		// Get source buffer
+		uint32 *p_orig_tex_buffer = mp_cur_temp_32bit_image;
+
+		// Move texture buffer to start
+		p_orig_tex_buffer += (y_pos * m_width) + x_pos;
+
+		for (uint h = 0; h < height; h++)
+		{
+			memcpy(p_pixel_buffer, p_orig_tex_buffer, width * sizeof(uint32));
+			p_pixel_buffer += width;
+			p_orig_tex_buffer += m_width;
+		}
+	}
+	else
+	{
+		// Get source buffers
+		uint8 *p_orig_tex_buffer = mp_single_texture->GetTextureBuffer();
+		// And clut (if any)
+		uint32 *p_orig_clut_buffer = (uint32 *) mp_single_texture->GetClutBuffer();
+
+		// Move texture buffer to start (and flip y)
+		p_orig_tex_buffer += ((m_height - 1 - y_pos) * m_width) + x_pos;
+
+		for (uint h = 0; h < height; h++)
+		{
+			for (uint w = 0; w < width; w++)
+			{
+				// Copies from 8-bit to 32-bit
+				*p_pixel_buffer++ = p_orig_clut_buffer[ s_clut8_index_to_offset(p_orig_tex_buffer[w]) ];
+			}
+			p_orig_tex_buffer -= m_width;	// Negative because were flipping the lines
+		}
+	}
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	total_time += end_time - start_time;
+	Dbg_Message("copy_region_to_32bit_buffer Update time %d us; Total Time %d", end_time - start_time, total_time);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::blit_32bit_buffer_to_texture(uint32 *p_pixel_buffer, uint16 x_pos, uint16 y_pos,
+												  uint16 width, uint16 height)
+{
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_Assert(m_clut_bitdepth == 32);
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(s_tables_initialized);
+
+#if PRINT_TIMES
+	static uint32 total_time = 0;
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	if (mp_cur_temp_32bit_image)
+	{
+		// Get source buffer
+		uint32 *p_tex_buffer = mp_cur_temp_32bit_image;
+
+		// Move texture buffer to start
+		p_tex_buffer += (y_pos * m_width) + x_pos;
+
+		for (uint h = 0; h < height; h++)
+		{
+			memcpy(p_tex_buffer, p_pixel_buffer, width * sizeof(uint32));
+			p_pixel_buffer += width;
+			p_tex_buffer += m_width;
+		}
+	}
+	else
+	{
+		// Get source buffers
+		uint8 *p_tex_buffer = mp_single_texture->GetTextureBuffer();
+
+		// Move texture buffer to start (and flip y)
+		p_tex_buffer += ((m_height - 1 - y_pos) * m_width) + x_pos;
+
+		for (uint h = 0; h < height; h++)
+		{
+			for (uint w = 0; w < width; w++)
+			{
+				// Copies from 32-bit to 8-bit
+				p_tex_buffer[w] = s_clut8_offset_to_index( find_closest_clut_color( *((Image::RGBA *) p_pixel_buffer++) ) );
+			}
+			p_tex_buffer -= m_width;		// Negative because were flipping the lines
+		}
+	}
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	total_time += end_time - start_time;
+	Dbg_Message("blit_32bit_buffer_to_texture Update time %d us; Total Time %d", end_time - start_time, total_time);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2Texture::set_32bit_pixel_to_texture(uint32 pixel, uint16 x_pos, uint16 y_pos)
+{
+	// For now, just coded up the 8-bit image with 32-bit clut
+	Dbg_Assert(m_bitdepth == 8);
+	Dbg_Assert(m_clut_bitdepth == 32);
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(s_tables_initialized);
+
+	if (mp_cur_temp_32bit_image)
+	{
+		// Set pixel in buffer
+		mp_cur_temp_32bit_image[(y_pos * m_width) + x_pos] = pixel;
+	}
+	else
+	{
+		// Get source buffers
+		uint8 *p_tex_buffer = mp_single_texture->GetTextureBuffer();
+
+		// Move texture buffer to start (and flip y)
+		p_tex_buffer[((m_height - 1 - y_pos) * m_width) + x_pos] = s_clut8_offset_to_index( find_closest_clut_color( *((Image::RGBA *) &pixel) ) );
+	}
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_offset(int x_pixels, int y_pixels, bool use_fill_color, Image::RGBA fill_color)
+{
+	int region_start_pos[2];
+	int region_end_pos[2];
+	int region_width;
+	int region_height;
+
+	// Figure out X info
+	if (x_pixels >= 0)
+	{
+		region_start_pos[X] = 0;
+		region_end_pos[X] = x_pixels;
+		region_width = m_width - x_pixels;
+	}
+	else
+	{
+		region_start_pos[X] = -x_pixels;
+		region_end_pos[X] = 0;
+		region_width = m_width - (-x_pixels);
+	}
+
+	// Figure out Y info
+	if (y_pixels >= 0)
+	{
+		region_start_pos[Y] = 0;
+		region_end_pos[Y] = y_pixels;
+		region_height = m_height - y_pixels;
+	}
+	else
+	{
+		region_start_pos[Y] = -y_pixels;
+		region_end_pos[Y] = 0;
+		region_height = m_height - (-y_pixels);
+	}
+
+	// Copy texture region into temp buffers in 32-bit format
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	uint32 *p_texture_buffer = new uint32[region_width * region_height];
+	Mem::Manager::sHandle().PopContext();
+
+	// Get section that will be in offset texture
+	copy_region_to_32bit_buffer(p_texture_buffer, region_start_pos[X], region_start_pos[Y], region_width, region_height);
+
+	// And put that region back into texture
+	blit_32bit_buffer_to_texture(p_texture_buffer, region_end_pos[X], region_end_pos[Y], region_width, region_height);
+
+	//static uint32 total_time = 0;
+	//uint32 start_time = Tmr::GetTimeInUSeconds();
+
+	// Check if we have a fill color
+	if (use_fill_color)
+	{
+		Dbg_MsgAssert(0, ("Fill color not implemented yet"));
+	}
+	else
+	{
+		for (int h = 0, y_pos = -region_end_pos[Y]; h < m_height; h++, y_pos++)
+		{
+			int y_clamp_pos = max(y_pos, 0);
+			y_clamp_pos = min(y_clamp_pos, region_height - 1);
+			int y_buffer_index = y_clamp_pos * region_width;
+
+			for (int w = 0, x_pos = -region_end_pos[X]; w < m_width; w++, x_pos++)
+			{
+				// Check if we are within the new region
+				if ((x_pos >= 0) && (x_pos < region_width) &&
+					(y_pos >= 0) && (y_pos < region_height))
+				{
+					continue;
+				}
+
+				int x_clamp_pos = max(x_pos, 0);
+				x_clamp_pos = min(x_clamp_pos, region_width - 1);
+
+				set_32bit_pixel_to_texture(p_texture_buffer[y_buffer_index + x_clamp_pos], w, h);
+			}
+		}
+	}
+
+	//uint32 end_time = Tmr::GetTimeInUSeconds();
+	//total_time += end_time - start_time;
+	//Dbg_Message("offset_texture fill Update time %d us; Total Time %d", end_time - start_time, total_time);
+
+	delete [] p_texture_buffer;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_adjust_region(uint16 x_pos, uint16 y_pos, uint16 width, uint16 height,
+										int split_axis, uint16 start_point, uint16 end_point)
+{								 	
+	// Copy original rectangle in a temp buffer in 32-bit format (needed for interpolation)
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	uint32 *p_orig_region_pixels = new uint32[width * height];
+	Mem::Manager::sHandle().PopContext();
+
+	copy_region_to_32bit_buffer(p_orig_region_pixels, x_pos, y_pos, width, height);
+
+	float first_rect_portion;
+	uint16 rect_pos[2][2];
+	uint16 rect_new_pos[2];
+	uint16 rect_orig_width[2];
+	uint16 rect_orig_height[2];
+	uint16 rect_new_width[2];
+	uint16 rect_new_height[2];
+
+	// Figure out first rectangle
+	rect_pos[0][X] = 0;
+	rect_pos[0][Y] = 0;
+
+	if (split_axis == X)
+	{
+		Dbg_Assert(start_point >= x_pos);
+		Dbg_Assert(start_point <= x_pos + width);
+		Dbg_Assert(end_point >= x_pos);
+		Dbg_Assert(end_point <= x_pos + width);
+
+		// Dimensions of first rectangle
+		rect_orig_width[0] = start_point - x_pos + 1;
+		rect_orig_height[0] = height;
+		rect_new_width[0] = end_point - x_pos + 1;
+		rect_new_height[0] = height;
+		
+		// Second rectangle
+		rect_pos[1][X] = start_point - x_pos;
+		rect_pos[1][Y] = 0;
+		rect_new_pos[X] = end_point - x_pos;
+		rect_new_pos[Y] = 0;
+
+		rect_orig_width[1] = width - (start_point - x_pos);
+		rect_orig_height[1] = height;
+		rect_new_width[1] = width - (end_point - x_pos);
+		rect_new_height[1] = height;
+
+		// Figure out portion of first rectangle to whole region
+		first_rect_portion = (float) rect_new_width[0] / (float) width;
+	}
+	else
+	{
+		Dbg_Assert(start_point >= y_pos);
+		Dbg_Assert(start_point <= y_pos + height);
+		Dbg_Assert(end_point >= y_pos);
+		Dbg_Assert(end_point <= y_pos + height);
+
+		// Dimensions of first rectangle
+		rect_orig_width[0] = width;
+		rect_orig_height[0] = start_point - y_pos + 1;
+		rect_new_width[0] = width;
+		rect_new_height[0] = end_point - y_pos + 1;
+		
+		// Second rectangle
+		rect_pos[1][X] = 0;
+		rect_pos[1][Y] = start_point - y_pos;
+		rect_new_pos[X] = 0;
+		rect_new_pos[Y] = end_point - y_pos;
+
+		rect_orig_width[1] = width;
+		rect_orig_height[1] = height - (start_point - y_pos);
+		rect_new_width[1] = width;
+		rect_new_height[1] = height - (end_point - y_pos);
+
+		// Figure out portion of first rectangle to whole region
+		first_rect_portion = (float) rect_new_height[0] / (float) height;
+	}
+
+	// Make room for the original and new rectangles
+	uint32 *p_orig_rect_pixels[2];
+	uint32 *p_new_rect_pixels[2];
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	p_orig_rect_pixels[0] = new uint32[rect_orig_width[0] * rect_orig_height[0]];
+	p_orig_rect_pixels[1] = new uint32[rect_orig_width[1] * rect_orig_height[1]];
+
+	p_new_rect_pixels[0] = new uint32[rect_new_width[0] * rect_new_height[0]];
+	p_new_rect_pixels[1] = new uint32[rect_new_width[1] * rect_new_height[1]];
+	Mem::Manager::sHandle().PopContext();
+
+	// Get the original rectangles
+	s_get_region((uint8 *) p_orig_region_pixels, rect_pos[0][X], rect_pos[0][Y], width, height,
+				 (uint8 *) p_orig_rect_pixels[0], rect_orig_width[0], rect_orig_height[0], 32);
+	s_get_region((uint8 *) p_orig_region_pixels, rect_pos[1][X], rect_pos[1][Y], width, height,
+				 (uint8 *) p_orig_rect_pixels[1], rect_orig_width[1], rect_orig_height[1], 32);
+
+	// Scale each rectangle
+	s_scale_texture((uint8 *) p_orig_rect_pixels[0], rect_orig_width[0], rect_orig_height[0],
+					(uint8 *) p_new_rect_pixels[0], rect_new_width[0], rect_new_height[0], 32);
+	s_scale_texture((uint8 *) p_orig_rect_pixels[1], rect_orig_width[1], rect_orig_height[1],
+					(uint8 *) p_new_rect_pixels[1], rect_new_width[1], rect_new_height[1], 32);
+
+	// Combine two adjacent side lines into 1 (and put into the second rectangle since it will overwrite the first)
+	s_combine_adjacent_borders((uint8 *) p_new_rect_pixels[0], (uint8 *) p_new_rect_pixels[1],
+										 rect_new_width[0], rect_new_height[0], rect_new_width[1], first_rect_portion, split_axis, 32);
+
+	// Put new rectangles back into region
+	s_put_region((uint8 *) p_orig_region_pixels, rect_pos[0][X], rect_pos[0][Y], width, height,
+				 (uint8 *) p_new_rect_pixels[0], rect_new_width[0], rect_new_height[0], 32);
+	s_put_region((uint8 *) p_orig_region_pixels, rect_new_pos[X], rect_new_pos[Y], width, height,
+				 (uint8 *) p_new_rect_pixels[1], rect_new_width[1], rect_new_height[1], 32);
+
+	// And put region back into texture
+	blit_32bit_buffer_to_texture(p_orig_region_pixels, x_pos, y_pos, width, height);
+
+	// Free all the buffers
+	delete [] p_orig_rect_pixels[0];
+	delete [] p_orig_rect_pixels[1];
+
+	delete [] p_new_rect_pixels[0];
+	delete [] p_new_rect_pixels[1];
+
+	delete [] p_orig_region_pixels;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_pull_to_edge(uint16 point, int axis, int num_pixels)
+{
+#if PRINT_TIMES
+	static uint32 total_time = 0;
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	uint32 *p_texture_buffer = NULL;
+	uint32 *p_scaled_texture_buffer = NULL;
+
+	uint16 rect_pos[2];
+	uint16 rect_orig_width;
+	uint16 rect_orig_height;
+	uint16 rect_new_width;
+	uint16 rect_new_height;
+	uint16 scaled_offset;
+
+	if (num_pixels == 0)
+	{
+		// Nothing to do
+		return true;
+	}
+
+	// Cap num_pixels on low end
+	if ((point + num_pixels) <= 0)
+	{
+		num_pixels = -point + 1;
+	}
+
+	Dbg_Assert((point + num_pixels) >= 0);
+
+	if (axis == X)
+	{
+		Dbg_Assert(point < m_width);
+
+		// Cap num_pixels on high end
+		if ((point + num_pixels) >= m_width)
+		{
+			num_pixels = m_width - point - 1;
+		}
+
+		Dbg_Assert((point + num_pixels) < m_width);
+
+		bool left = (num_pixels < 0);
+
+		if (left)
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = 0;
+			rect_pos[Y] = 0;
+			rect_orig_width = point + 1;
+			rect_orig_height = m_height;
+
+			// Figure out new rectangle
+			rect_new_width = point + (-num_pixels) + 1;
+			rect_new_height = m_height;
+
+			scaled_offset = -num_pixels;
+		}
+		else
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = point;
+			rect_pos[Y] = 0;
+			rect_orig_width = (m_width - point);
+			rect_orig_height = m_height;
+
+			// Figure out new rectangle
+			rect_new_width = (m_width - point) + num_pixels;
+			rect_new_height = m_height;
+
+			scaled_offset = 0;
+		}
+	}
+	else
+	{
+		Dbg_Assert(point < m_height);
+
+		// Cap num_pixels on high end
+		if ((point + num_pixels) >= m_height)
+		{
+			num_pixels = m_height - point - 1;
+		}
+
+		Dbg_Assert((point + num_pixels) < m_height);
+
+		bool top = (num_pixels < 0);
+
+		if (top)
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = 0;
+			rect_pos[Y] = 0;
+			rect_orig_width = m_width;
+			rect_orig_height = point + 1;
+
+			// Figure out new rectangle
+			rect_new_width = m_width;
+			rect_new_height = point + (-num_pixels) + 1;
+
+			scaled_offset = -num_pixels * m_width;
+		}
+		else
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = 0;
+			rect_pos[Y] = point;
+			rect_orig_width = m_width;
+			rect_orig_height = (m_height - point);
+
+			// Figure out new rectangle
+			rect_new_width = m_width;
+			rect_new_height = (m_height - point) + num_pixels;
+
+			scaled_offset = 0;
+		}
+	}
+
+	// Copy texture region into temp buffers in 32-bit format
+	if (Mem::Manager::sHandle().CutsceneTopDownHeap())
+	{
+		// GJ FIX 9/9/03 FOR SK5:TT13114 - "Failing to allocate 
+		// face texture memory in cutscenes"
+		// if the cutscene top down heap exists, we want to use
+		// that first, because there won't be much on the
+		// top down heap during cutscenes...
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneTopDownHeap());
+	}
+	else
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	}
+	p_texture_buffer = new uint32[rect_orig_width * rect_orig_height];
+	p_scaled_texture_buffer = new uint32[rect_new_width * rect_new_height];
+	Mem::Manager::sHandle().PopContext();
+
+	//Dbg_Message("Copying region from (%d, %d) of size (%d, %d)", rect_pos[X], rect_pos[Y], rect_orig_width, rect_orig_height);
+	copy_region_to_32bit_buffer(p_texture_buffer, rect_pos[X], rect_pos[Y], rect_orig_width, rect_orig_height);
+
+	// Scale the rectangle
+	s_scale_texture((uint8 *) p_texture_buffer, rect_orig_width, rect_orig_height,
+					(uint8 *) p_scaled_texture_buffer, rect_new_width, rect_new_height, 32);
+
+	//Dbg_Message("Pull: Original size (%d, %d), new size (%d, %d)", rect_orig_width, rect_orig_height, rect_new_width, rect_new_height);
+	// Crop the rectangle and put back into original buffer
+	s_put_region((uint8 *) p_texture_buffer, 0, 0, rect_orig_width, rect_orig_height,
+				 (uint8 *) (p_scaled_texture_buffer + scaled_offset), rect_new_width, rect_new_height, 32);
+
+	// And put region back into texture
+	blit_32bit_buffer_to_texture(p_texture_buffer, rect_pos[X], rect_pos[Y], rect_orig_width, rect_orig_height);
+
+	// Free buffers
+	if (p_texture_buffer)
+	{
+		delete [] p_texture_buffer;
+	}
+	if (p_scaled_texture_buffer)
+	{
+		delete [] p_scaled_texture_buffer;
+	}
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	total_time += end_time - start_time;
+	Dbg_Message("pull_to_edge Update time %d us; Total Time %d", end_time - start_time, total_time);
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_push_to_point(uint16 point, int axis, int num_pixels, bool use_fill_color, Image::RGBA fill_color)
+{
+#if PRINT_TIMES
+	static uint32 total_time = 0;
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	uint32 *p_texture_buffer = NULL;
+	uint32 *p_scaled_texture_buffer = NULL;
+
+	uint16 rect_pos[2];
+	uint16 rect_new_offset[2];
+	uint16 rect_orig_width;
+	uint16 rect_orig_height;
+	uint16 rect_new_width;
+	uint16 rect_new_height;
+
+	if (num_pixels == 0)
+	{
+		// Nothing to do
+		return true;
+	}
+	//Dbg_Message("Moving %d pixels along %d axis starting at %d", num_pixels, axis, point);
+
+	// Cap num_pixels on low end
+	if ((point + num_pixels) <= 0)
+	{
+		num_pixels = -point + 1;
+	}
+
+	Dbg_Assert((point + num_pixels) >= 0);
+
+	if (axis == X)
+	{
+		Dbg_Assert(point < m_width);
+
+		// Cap num_pixels on high end
+		if ((point + num_pixels) >= m_width)
+		{
+			num_pixels = m_width - point - 1;
+		}
+
+		Dbg_Assert((point + num_pixels) < m_width);
+
+		bool left = (num_pixels < 0);
+
+		if (left)
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = point;
+			rect_pos[Y] = 0;
+			rect_orig_width = (m_width - point);
+			rect_orig_height = m_height;
+
+			// Figure out new rectangle
+			rect_new_offset[X] = 0;
+			rect_new_offset[Y] = 0;
+			rect_new_width = (m_width - point) - (-num_pixels);
+			rect_new_height = m_height;
+		}
+		else
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = 0;
+			rect_pos[Y] = 0;
+			rect_orig_width = point + 1;
+			rect_orig_height = m_height;
+
+			// Figure out new rectangle
+			rect_new_offset[X] = num_pixels;
+			rect_new_offset[Y] = 0;
+			rect_new_width = point - num_pixels + 1;
+			rect_new_height = m_height;
+		}
+	}
+	else
+	{
+		Dbg_Assert(point < m_height);
+
+		// Cap num_pixels on high end
+		if ((point + num_pixels) >= m_height)
+		{
+			num_pixels = m_height - point - 1;
+		}
+
+		Dbg_Assert((point + num_pixels) < m_height);
+
+		bool top = (num_pixels < 0);
+
+		if (top)
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = 0;
+			rect_pos[Y] = point;
+			rect_orig_width = m_width;
+			rect_orig_height = (m_height - point);
+
+			// Figure out new rectangle
+			rect_new_offset[X] = 0;
+			rect_new_offset[Y] = 0;
+			rect_new_width = m_width;
+			rect_new_height = (m_height - point) - (-num_pixels);
+		}
+		else
+		{
+			// Figure out orig rectangle
+			rect_pos[X] = 0;
+			rect_pos[Y] = 0;
+			rect_orig_width = m_width;
+			rect_orig_height = point + 1;
+
+			// Figure out new rectangle
+			rect_new_offset[X] = 0;
+			rect_new_offset[Y] = num_pixels;
+			rect_new_width = m_width;
+			rect_new_height = point - num_pixels + 1;
+		}
+	}
+
+	Dbg_Assert(rect_new_width);
+	Dbg_Assert(rect_new_height);
+
+	// Copy texture region into temp buffers in 32-bit format
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	p_texture_buffer = new uint32[rect_orig_width * rect_orig_height];
+	p_scaled_texture_buffer = new uint32[rect_new_width * rect_new_height];
+	Mem::Manager::sHandle().PopContext();
+
+	//Dbg_Message("Copying region from (%d, %d) of size (%d, %d)", rect_pos[X], rect_pos[Y], rect_orig_width, rect_orig_height);
+	copy_region_to_32bit_buffer(p_texture_buffer, rect_pos[X], rect_pos[Y], rect_orig_width, rect_orig_height);
+
+	// Scale the rectangle
+	s_scale_texture((uint8 *) p_texture_buffer, rect_orig_width, rect_orig_height,
+					(uint8 *) p_scaled_texture_buffer, rect_new_width, rect_new_height, 32);
+
+	// Check if we have a fill color
+	if (use_fill_color)
+	{
+		int num_pixels = rect_orig_width * rect_orig_height;
+
+		// Just brute force it for now
+		// Garrett: This doesn't work since p_texture_buffer is only a subset of the texture
+		Dbg_MsgAssert(0, ("Fill color not implemented properly yet"));
+		for (int i = 0; i < num_pixels; i++)
+		{
+			p_texture_buffer[i] = *((uint32 *) &fill_color);
+		}
+	}
+
+	// Put smaller rectangle back into original buffer
+	s_put_region((uint8 *) p_texture_buffer, rect_new_offset[X], rect_new_offset[Y], rect_orig_width, rect_orig_height,
+				 (uint8 *) p_scaled_texture_buffer, rect_new_width, rect_new_height, 32);
+
+	// And put region back into texture
+	blit_32bit_buffer_to_texture(p_texture_buffer, rect_pos[X], rect_pos[Y], rect_orig_width, rect_orig_height);
+
+	// Cover up old pixels
+	if (!use_fill_color)
+	{
+		if (axis == X)
+		{
+			bool use_left = (num_pixels > 0);
+			int scaled_coord = (use_left) ? 0 : rect_new_width - 1; 
+
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+			uint32 *p_strip_buffer = new uint32[m_height];
+			Mem::Manager::sHandle().PopContext();
+			for (int pidx = 0; pidx < m_height; pidx++)
+			{
+				p_strip_buffer[pidx] = p_scaled_texture_buffer[(pidx * rect_new_width) + scaled_coord];
+			}
+
+			int start = (use_left) ? 0 : m_width - (-num_pixels);
+			int end = (use_left) ? num_pixels - 1 : m_width - 1;
+
+			for (int line = start; line <= end; line++)
+			{
+				blit_32bit_buffer_to_texture(p_strip_buffer, line, 0, 1, m_height);
+			}
+
+			delete [] p_strip_buffer;
+		}
+		else
+		{
+			bool use_top = (num_pixels > 0);
+			int scaled_coord = (use_top) ? 0 : rect_new_height - 1; 
+
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+			uint32 *p_strip_buffer = new uint32[m_width];
+			Mem::Manager::sHandle().PopContext();
+			memcpy(p_strip_buffer, &(p_scaled_texture_buffer[scaled_coord * rect_new_width]), m_width * sizeof(uint32));
+
+			int start = (use_top) ? 0 : m_height - (-num_pixels);
+			int end = (use_top) ? num_pixels - 1 : m_height - 1;
+
+			for (int line = start; line <= end; line++)
+			{
+				blit_32bit_buffer_to_texture(p_strip_buffer, 0, line, m_width, 1);
+			}
+
+			delete [] p_strip_buffer;
+		}
+	}
+
+	// Free buffers
+	if (p_texture_buffer)
+	{
+		delete [] p_texture_buffer;
+	}
+	if (p_scaled_texture_buffer)
+	{
+		delete [] p_scaled_texture_buffer;
+	}
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	total_time += end_time - start_time;
+	Dbg_Message("push_to_point Update time %d us; Total Time %d", end_time - start_time, total_time);
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_adjust_brightness(float brightness_scale, bool force_adjust_current)
+{
+#if PRINT_TIMES
+	static uint32 total_time = 0;
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	Dbg_Assert(mp_single_texture);
+	Dbg_MsgAssert(mp_cur_temp_32bit_image, ("For now, AdjustBrightness() only supports 32-bit mode"));
+
+	Image::RGBA *p_src_pixels;
+	Image::RGBA *p_dst_pixels = (Image::RGBA *) mp_cur_temp_32bit_image;
+
+	// Figure out the source
+	if (mp_orig_temp_32bit_image && !force_adjust_current)
+	{
+		p_src_pixels = (Image::RGBA *) mp_orig_temp_32bit_image;
+	}
+	else
+	{
+		p_src_pixels = (Image::RGBA *) mp_cur_temp_32bit_image;
+	}
+
+	// Adjust each pixel
+	int num_pixels = m_width * m_height;
+	for (int i = 0; i < num_pixels; i++, p_src_pixels++, p_dst_pixels++)
+	{
+		p_dst_pixels->r = (uint8) min(p_src_pixels->r * brightness_scale, 255);
+		p_dst_pixels->g = (uint8) min(p_src_pixels->g * brightness_scale, 255);
+		p_dst_pixels->b = (uint8) min(p_src_pixels->b * brightness_scale, 255);
+	}
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	total_time += end_time - start_time;
+	Dbg_Message("adjust_brightness(%f) Update time %d us; Total Time %d", brightness_scale, end_time - start_time, total_time);
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_adjust_hsv(float h, float s, float v, bool force_adjust_current)
+{
+#if PRINT_TIMES
+	static uint32 total_time = 0;
+	uint32 start_time = Tmr::GetTimeInUSeconds();
+#endif
+
+	Dbg_Assert(mp_single_texture);
+	Dbg_MsgAssert(mp_cur_temp_32bit_image, ("For now, AdjustHSV() only supports 32-bit mode"));
+
+	Dbg_MsgAssert((h >= 0.0f) && (h <= 360.0f), ("h is out of range: %f", h));
+	Dbg_MsgAssert(s >= 0.0f, ("s is negative: %f", s));
+	Dbg_MsgAssert(v >= 0.0f, ("v is negative: %f", v));
+
+	Image::RGBA *p_src_pixels;
+	Image::RGBA *p_dst_pixels = (Image::RGBA *) mp_cur_temp_32bit_image;
+
+	// Figure out the source
+	if (mp_orig_temp_32bit_image && !force_adjust_current)
+	{
+		p_src_pixels = (Image::RGBA *) mp_orig_temp_32bit_image;
+	}
+	else
+	{
+		p_src_pixels = (Image::RGBA *) mp_cur_temp_32bit_image;
+	}
+
+	// Adjust each pixel
+	float pixel_h, pixel_s, pixel_v;
+	float pixel_r, pixel_g, pixel_b;
+	int num_pixels = m_width * m_height;
+	for (int i = 0; i < num_pixels; i++, p_src_pixels++, p_dst_pixels++)
+	{
+		// Convert
+		Gfx::inlineRGBtoHSV(p_src_pixels->r / 255.0f, p_src_pixels->g / 255.0f, p_src_pixels->b / 255.0f, pixel_h, pixel_s, pixel_v);
+
+		// Adjust
+		pixel_h += h;
+		if (pixel_h > 360.0f)
+		{
+			pixel_h -= 360.0f;
+		}
+		pixel_s = Mth::Min(pixel_s * s, 1.0f);
+		pixel_v = Mth::Min(pixel_v * v, 1.0f);
+
+		// Convert back
+		Gfx::inlineHSVtoRGB(pixel_r, pixel_g, pixel_b, pixel_h, pixel_s, pixel_v);
+		p_dst_pixels->r = (unsigned char)( pixel_r * 255.0f + 0.5f );
+		p_dst_pixels->g = (unsigned char)( pixel_g * 255.0f + 0.5f );
+		p_dst_pixels->b = (unsigned char)( pixel_b * 255.0f + 0.5f );
+	}
+
+#if PRINT_TIMES
+	uint32 end_time = Tmr::GetTimeInUSeconds();
+	total_time += end_time - start_time;
+	Dbg_Message("adjust_hsv(%f) Update time %d us; Total Time %d", s, end_time - start_time, total_time);
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_add_to_vram()
+{								 	
+	Dbg_MsgAssert(mp_single_texture, ("CPs2Texture::plat_add_to_vram() only works for sSingleTexture types"));
+
+	return mp_single_texture->AddToVRAM();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_remove_from_vram()
+{								 	
+	Dbg_MsgAssert(mp_single_texture, ("CPs2Texture::plat_remove_from_vram() only works for sSingleTexture types"));
+
+	return mp_single_texture->RemoveFromVRAM();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CPs2Texture::plat_get_width() const
+{
+	return m_width;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CPs2Texture::plat_get_height() const
+{
+	return m_height;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CPs2Texture::plat_get_bitdepth() const
+{
+	return m_bitdepth;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CPs2Texture::plat_get_palette_bitdepth() const
+{
+	return m_clut_bitdepth;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CPs2Texture::plat_get_num_mipmaps() const
+{
+	return m_num_mipmaps;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_is_transparent() const
+{
+	return m_transparent;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CPs2Texture::plat_combine_textures(CTexture *p_texture, bool palette_gen)
+{
+	CPs2Texture *p_ps2_texture = static_cast(p_texture);
+
+	// Make sure both textures have same attributes
+	Dbg_Assert(m_width == p_ps2_texture->m_width);
+	Dbg_Assert(m_height == p_ps2_texture->m_height);
+	Dbg_Assert(m_bitdepth == p_ps2_texture->m_bitdepth);
+	Dbg_Assert(m_clut_bitdepth == p_ps2_texture->m_clut_bitdepth);
+
+	Dbg_Assert((m_bitdepth == 8) || (m_bitdepth == 32));		// Maybe support more later
+	Dbg_Assert(mp_single_texture);
+	Dbg_Assert(p_ps2_texture->mp_single_texture);
+
+	bool paletted = (m_bitdepth <= 8);
+	uint32 *p_texture1_buffer = NULL;
+	uint32 *p_texture2_buffer = NULL;
+	Image::RGBA *p_src;
+	Image::RGBA *p_dst;
+
+	if (paletted)
+	{
+		// Try temp 32-bit images first
+		p_dst = (Image::RGBA *) mp_cur_temp_32bit_image;
+		p_src = (Image::RGBA *) p_ps2_texture->mp_cur_temp_32bit_image;
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+		// Copy original textures iton temp buffers in 32-bit format
+		if (!p_dst)
+		{
+			p_texture1_buffer = new uint32[m_width * m_height];
+			copy_region_to_32bit_buffer(p_texture1_buffer, 0, 0, m_width, m_height);
+			p_dst = (Image::RGBA *) p_texture1_buffer;
+		}
+
+		if (!p_src)
+		{
+			p_texture2_buffer = new uint32[p_ps2_texture->m_width * p_ps2_texture->m_height];
+			p_ps2_texture->copy_region_to_32bit_buffer(p_texture2_buffer, 0, 0, p_ps2_texture->m_width, p_ps2_texture->m_height);
+			p_src = (Image::RGBA *) p_texture2_buffer;
+		}
+
+		Mem::Manager::sHandle().PopContext();
+	}
+	else
+	{
+		p_src = (Image::RGBA *) p_ps2_texture->mp_single_texture->GetTextureBuffer();
+		p_dst = (Image::RGBA *) mp_single_texture->GetTextureBuffer();
+	}
+
+	// Do the actual alpha blend
+	uint8 om_src_alpha;
+	int size = m_width * m_height;
+
+	for (int i = 0; i < size; i++, p_src++, p_dst++)
+	{
+		om_src_alpha = 0x80 - p_src->a;
+
+		p_dst->r = (uint8) ( ( ((int) p_src->r * p_src->a) + ((int) p_dst->r * om_src_alpha) ) >> 7 /* divide by 0x80 */);
+		p_dst->g = (uint8) ( ( ((int) p_src->g * p_src->a) + ((int) p_dst->g * om_src_alpha) ) >> 7 /* divide by 0x80 */);
+		p_dst->b = (uint8) ( ( ((int) p_src->b * p_src->a) + ((int) p_dst->b * om_src_alpha) ) >> 7 /* divide by 0x80 */);
+		p_dst->a = max(p_src->a, p_dst->a);	// Choose the highest alpha (since we don't want solid pixels to become transparent)
+	}
+
+	// Go back to original bitdepth if necessary
+	if (paletted && !mp_cur_temp_32bit_image)
+	{
+		Dbg_Assert(m_clut_bitdepth == 32);
+
+		// Generate a new palette
+		if (palette_gen)
+		{
+			GeneratePalette((Image::RGBA *) mp_single_texture->GetClutBuffer(), (uint8 *) p_texture1_buffer, m_width, m_height, 32, 1 << m_bitdepth);
+		}
+
+		// And repalettize and put back into texture
+		blit_32bit_buffer_to_texture(p_texture1_buffer, 0, 0, m_width, m_height);
+
+	}
+
+	// Free buffers
+	if (p_texture1_buffer)
+	{
+		delete [] p_texture1_buffer;
+	}
+	if (p_texture2_buffer)
+	{
+		delete [] p_texture2_buffer;
+	}
+
+	return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTexDict
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2TexDict::CPs2TexDict(uint32 checksum) : CTexDict(checksum)
+{
+	// Load nothing
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2TexDict::CPs2TexDict(const char *p_tex_dict_name, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup) : CTexDict(p_tex_dict_name, !is_level_data)
+{								  
+	LoadTextureDictionary(p_tex_dict_name, NULL, 0, is_level_data, texDictOffset, isSkin, forceTexDictLookup);	// the derived class will does this
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2TexDict::~CPs2TexDict()
+{
+	UnloadTextureDictionary();				// the derived class does this
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NxPs2::sScene *	CPs2TexDict::GetEngineTextureDictionary() const
+{
+	//printf( "Returning scene from GetEngineTextureDictionary %08x\n", (uint32)mp_tex_dict );
+
+	return mp_tex_dict;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPs2TexDict::add_textures_to_hash_table()
+{
+	for (int i = 0; i < mp_tex_dict->NumTextures; i++)
+	{
+		// Make sure we don't add duplicate textures.  We can have the same texture in two
+		// different texture groups (for sorting purposes).  This will break texture
+		// replacement, though.
+		if (!GetTexture(mp_tex_dict->pTextures[i].Checksum))
+		{
+			CPs2Texture *p_texture = new CPs2Texture;
+
+			p_texture->m_checksum		= mp_tex_dict->pTextures[i].Checksum;
+			p_texture->m_width			= mp_tex_dict->pTextures[i].GetWidth();
+			p_texture->m_height			= mp_tex_dict->pTextures[i].GetHeight();
+			p_texture->m_bitdepth		= mp_tex_dict->pTextures[i].GetBitdepth();
+			p_texture->m_clut_bitdepth	= mp_tex_dict->pTextures[i].GetClutBitdepth();
+			p_texture->m_num_mipmaps	= mp_tex_dict->pTextures[i].GetNumMipmaps();
+
+			p_texture->mp_group_texture = &(mp_tex_dict->pTextures[i]);
+
+			AddTexture(p_texture);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPs2TexDict::LoadTextureDictionary(const char *p_tex_dict_name, uint32* pData, int dataSize, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup)
+{
+	// set up the texture dictionary's main parameters
+
+	bool IsSkin;
+	bool IsInstanceable;
+	bool UsesPip;
+
+	if ( is_level_data )
+	{
+		// for level data
+		IsSkin = false;
+		IsInstanceable = false;
+		UsesPip = true;
+	}
+	else if ( !isSkin && texDictOffset==0 )
+	{
+		// for non-skinned models (unless they 
+		// do texture replacement, such as the
+		// boards in the skateshop do)
+		IsSkin = false;
+		IsInstanceable = false;
+		UsesPip = true;
+	}   
+	else
+	{
+		// for all skinned models, and for
+		// non-skinned models that do
+		// texture replacement)
+		IsSkin = true;
+		IsInstanceable = true;
+		UsesPip = false;
+	}
+
+	if ( !is_level_data && isSkin )
+	{
+		// add all skinned model textures
+		// to the hash table
+		forceTexDictLookup = true;
+	}
+
+	if ( p_tex_dict_name )
+	{
+		// either the filename OR the data pointer should have been specified, but not both
+		Dbg_MsgAssert( !pData, ( "You can't specify both a filename %s AND a data pointer", p_tex_dict_name ) )
+		
+		// p_tex_dict_name is assumed to be the full path name of the texture dictionary
+		mp_tex_dict = NxPs2::LoadTextures(p_tex_dict_name, IsSkin, IsInstanceable, UsesPip, texDictOffset, &m_file_size );
+		Dbg_Assert( mp_tex_dict );
+	}
+	else
+	{
+		Dbg_MsgAssert( pData, ( "No data pointer specified" ) );
+		
+		m_file_size = dataSize;
+		mp_tex_dict = NxPs2::LoadTextures(pData, dataSize, IsSkin, IsInstanceable, UsesPip, texDictOffset );
+		Dbg_Assert( mp_tex_dict );
+	}
+
+	// Add textures to hash table (just do it for non-level dictionaries now)
+	// should this go on bottom up heap?
+	if ( forceTexDictLookup )
+	{
+		add_textures_to_hash_table();
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2TexDict::UnloadTextureDictionary()
+{
+	NxPs2::DeleteTextures(mp_tex_dict);
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *		CPs2TexDict::plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram)
+{
+	CPs2Texture *p_texture = new CPs2Texture;
+	if (!p_texture->LoadTexture(p_texture_name, sprite, alloc_vram))
+	{
+		Dbg_Error("Can't load texture %s", p_texture_name);
+	}
+
+	return p_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *		CPs2TexDict::plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram)
+{
+	CPs2Texture *p_texture = new CPs2Texture;
+	if (!p_texture->LoadTextureFromBuffer(p_buffer, buffer_size, texture_checksum, sprite, alloc_vram))
+	{
+		Dbg_MsgAssert(0, ("Can't load texture from buffer %s", Script::FindChecksumName(texture_checksum)));
+	}
+
+	return p_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *		CPs2TexDict::plat_reload_texture(const char *p_texture_name)
+{
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2TexDict::plat_unload_texture(CTexture *p_texture)
+{
+	delete p_texture;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2TexDict::plat_add_texture(CTexture *p_texture)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CPs2TexDict::plat_remove_texture(CTexture *p_texture)
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *		CPs2TexDict::plat_copy_texture(uint32 new_texture_checksum, CTexture *p_texture)
+{
+	CPs2Texture *p_ps2_texture = static_cast(p_texture);
+
+	return new CPs2Texture(*p_ps2_texture);
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxTexture.h b/Code/Gfx/NGPS/p_NxTexture.h
new file mode 100644
index 0000000..7990440
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxTexture.h
@@ -0,0 +1,244 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_TEXTURE_H__
+#define	__GFX_P_NX_TEXTURE_H__
+
+#include 	
+#include 	"gfx/NGPS/NX/texture.h"
+#include 	"gfx/NGPS/NX/material.h"
+#include 	"gfx/NGPS/NX/scene.h"
+#include 	"gfx/NGPS/NX/sprite.h"
+
+namespace Nx
+{
+
+// Forward declarations
+class CPs2TexDict;
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CTexture
+class	CPs2Texture : public CTexture
+{
+public:
+								CPs2Texture(bool loading_screen = false);
+								CPs2Texture(const CPs2Texture & src_texture);		// Copy constructor
+	virtual						~CPs2Texture();
+
+	NxPs2::SSingleTexture *		GetSingleTexture() const;
+	NxPs2::sTexture *			GetGroupTexture() const;
+
+	static void					sInitTables();
+
+private:		// It's all private, as it is machine specific
+
+	uint8 						find_closest_clut_color(Image::RGBA rgba);
+
+	void						setup_fast_clut_color_find(bool use_alpha = true);
+	uint8 						fast_find_closest_clut_color(Image::RGBA rgba);
+	void 						cleanup_fast_clut_color_find();
+
+	void						copy_region_to_32bit_buffer(uint32 *p_pixel_buffer, uint16 x_pos, uint16 y_pos,
+															uint16 width, uint16 height);
+	void						blit_32bit_buffer_to_texture(uint32 *p_pixel_buffer, uint16 x_pos, uint16 y_pos,
+															 uint16 width, uint16 height);
+	void						set_32bit_pixel_to_texture(uint32 pixel, uint16 x_pos, uint16 y_pos);
+
+	// Plat functions
+	virtual bool				plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram);
+	virtual bool				plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, bool sprite, bool alloc_vram);
+	virtual bool				plat_replace_texture(CTexture *p_texture);
+
+	virtual bool				plat_generate_32bit_image(bool renderble = false, bool store_original = false);
+	virtual bool				plat_put_32bit_image_into_texture(bool new_palette = false);
+
+	virtual bool				plat_offset(int x_pixels, int y_pixels, bool use_fill_color, Image::RGBA fill_color);
+
+	virtual bool				plat_adjust_region(uint16 x_pos, uint16 y_pos, uint16 width, uint16 height,
+												   int split_axis, uint16 start_point, uint16 end_point);
+
+	virtual bool				plat_pull_to_edge(uint16 point, int axis, int num_pixels);
+	virtual bool				plat_push_to_point(uint16 point, int axis, int num_pixels, bool use_fill_color,
+												   Image::RGBA fill_color);
+
+	virtual bool				plat_adjust_brightness(float brightness_scale, bool force_adjust_current = false);
+	virtual bool				plat_adjust_hsv(float h, float s, float v, bool force_adjust_current = false);
+
+	virtual bool				plat_add_to_vram();
+	virtual bool				plat_remove_from_vram();
+
+	virtual uint16				plat_get_width() const;
+	virtual uint16				plat_get_height() const;
+	virtual uint8				plat_get_bitdepth() const;
+	virtual uint8				plat_get_palette_bitdepth() const;
+	virtual uint8				plat_get_num_mipmaps() const;
+	virtual bool				plat_is_transparent() const;
+
+	virtual bool				plat_combine_textures(CTexture *p_texture, bool palette_gen);
+
+	// Static functions
+	static uint8 				s_clut8_index_to_offset(uint8 PS2idx);
+	static uint8 				s_clut8_offset_to_index(uint8 offset);
+
+	static inline uint32		s_get_pixel_from_32bit_texture(uint32 *p_buffer, uint16 x_pos, uint16 y_pos, uint16 stride);
+	static void					s_get_nearest_pixels_from_32bit_texture(Image::RGBA p_nearest[][2], uint32 *p_buffer,
+																		uint16 x_pos, uint16 y_pos, uint16 width, uint16 height);
+	
+	static void					s_get_region(uint8 *p_buffer, uint16 x_pos, uint16 y_pos, uint16 tex_width, uint16 tex_height,
+											 uint8 *p_region, uint16 region_width, uint16 region_height, uint16 bitdepth);
+	static void					s_put_region(uint8 *p_buffer, uint16 x_pos, uint16 y_pos, uint16 tex_width, uint16 tex_height,
+											 uint8 *p_region, uint16 region_width, uint16 region_height, uint16 bitdepth);
+
+	static void					s_scale_texture(uint8 *p_buffer, uint16 width, uint16 height,
+												uint8 *p_result_buffer, uint16 new_width, uint16 new_height, uint16 bitdepth);
+
+	static void					s_combine_adjacent_borders(uint8 *p_first_rect, uint8 *p_second_rect, uint16 first_width,
+														   uint16 first_height, uint16 second_stride, float first_portion,
+														   int split_axis, uint16 bitdepth);
+
+	uint16						m_width;
+	uint16						m_height;
+	uint8						m_bitdepth;
+	uint8						m_clut_bitdepth;
+	uint8						m_num_mipmaps;
+	bool						m_transparent;
+
+	// Tells is this texture is used as loading screen (so it isn't allocated VRAM, etc)
+	bool						m_loading_screen;
+
+	// The actual data in the engine (can only be one or the other
+	NxPs2::SSingleTexture *		mp_single_texture;	// Uses SSingleTexture
+	NxPs2::sTexture *			mp_group_texture;	// Uses sTexture (part of texture group
+
+	// Temp 32-bit texture images for texture manipulations (so we don't need to palettize every operation)
+	uint32 *					mp_orig_temp_32bit_image;		// Original
+	uint32 *					mp_cur_temp_32bit_image;		// Current
+	NxPs2::SSingleTexture *		mp_temp_32bit_single_texture;	// So we can draw it
+
+	// PS2 clut index conversions
+	static bool					s_tables_initialized;
+    static uint8				s_clut8_index_to_offset_table[256];
+    static uint8				s_clut8_offset_to_index_table[256];
+	
+	// Friends
+	friend CPs2TexDict;
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline NxPs2::SSingleTexture *	CPs2Texture::GetSingleTexture() const
+{
+	if (mp_temp_32bit_single_texture)
+	{
+		return mp_temp_32bit_single_texture;		// This can be a problem if plat_put_32bit_image_into_texture()
+													// is called before the corresponding CSprite is deleted.
+	}
+	else
+	{
+		return mp_single_texture;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline NxPs2::sTexture *		CPs2Texture::GetGroupTexture() const
+{
+	return mp_group_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32					CPs2Texture::s_get_pixel_from_32bit_texture(uint32 *p_buffer, uint16 x_pos, uint16 y_pos,
+																			uint16 stride)
+{
+	return p_buffer[(y_pos * stride) + x_pos];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint8 					CPs2Texture::s_clut8_index_to_offset(uint8 PS2idx)
+{
+    return s_clut8_index_to_offset_table[PS2idx];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint8 					CPs2Texture::s_clut8_offset_to_index(uint8 offset)
+{
+    return s_clut8_offset_to_index_table[offset];
+}
+
+//////////////////////////////////////////////////////////////////////////////////
+// Machine specific implementation of the CMaterial
+class	CPs2Material : public CMaterial
+{
+public:
+								CPs2Material();
+	virtual						~CPs2Material();
+
+private:
+	virtual Image::RGBA			plat_get_rgba() const;
+	virtual void				plat_set_rgba(Image::RGBA rgba);
+	virtual void				plat_set_texture();
+
+	Image::RGBA					m_rgba;
+
+	// The actual data in the engine
+	NxPs2::sMaterial *			mp_material;
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// Machine specific implementation of the CTexDict
+class	CPs2TexDict : public CTexDict
+{
+public:
+								CPs2TexDict(uint32 checksum);		// loads nothing
+								CPs2TexDict(uint32* pData, int dataSize, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup = false);
+								CPs2TexDict(const char *p_tex_dict_name, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup = false);
+	virtual						~CPs2TexDict();
+
+	NxPs2::sScene *				GetEngineTextureDictionary() const;
+
+public:
+	// made this public so that the tex dict manager needs to be able to 
+	// create a texture dictionary and load up its textures in two separate steps...
+	bool						LoadTextureDictionary(const char *p_tex_dict_name, uint32* pData, int dataSize, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup = false);
+
+private:
+	void						add_textures_to_hash_table();
+	bool						UnloadTextureDictionary();
+
+	// Platform-specific calls
+	virtual CTexture *			plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram);
+	virtual CTexture *			plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram);
+	virtual CTexture *			plat_reload_texture(const char *p_texture_name);
+	virtual bool				plat_unload_texture(CTexture *p_texture);
+	virtual void				plat_add_texture(CTexture *p_texture);
+	virtual bool				plat_remove_texture(CTexture *p_texture);
+	virtual CTexture *			plat_copy_texture(uint32 new_texture_checksum, CTexture *p_texture);
+	//virtual CTexture *			plat_combine_textures(uint32 new_texture_checksum, CTexture *p_texture1, CTexture *p_texture2);
+
+	NxPs2::sScene *				mp_tex_dict;		// Platform-dependent data
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGPS/p_NxTextured3dPoly.cpp b/Code/Gfx/NGPS/p_NxTextured3dPoly.cpp
new file mode 100644
index 0000000..7829666
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxTextured3dPoly.cpp
@@ -0,0 +1,39 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace NxPs2
+{
+
+CPs2Textured3dPoly::CPs2Textured3dPoly()
+{
+}
+
+CPs2Textured3dPoly::~CPs2Textured3dPoly()
+{
+}
+
+void CPs2Textured3dPoly::plat_set_texture(uint32 texture_checksum)
+{
+	Nx::CTexture *p_texture = Nx::CTexDictManager::sp_sprite_tex_dict->GetTexture(texture_checksum);
+	Dbg_MsgAssert(p_texture, ("no texture found for sprite"));
+	Nx::CPs2Texture *p_ps2_texture = static_cast( p_texture );
+	Dbg_MsgAssert(p_ps2_texture,("NULL p_ps2_texture"));
+	mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	Dbg_MsgAssert(mp_engine_texture,("NULL mp_engine_texture"));
+}	
+
+void CPs2Textured3dPoly::plat_render()
+{
+	CImmediateMode::sStartPolyDraw( mp_engine_texture, PackALPHA(0,0,0,0,0), ABS );
+	
+	CImmediateMode::sDrawQuadTexture(mp_engine_texture, mp_pos[0], mp_pos[1], mp_pos[2], mp_pos[3], 0x80808080,0x80808080,0x80808080,0x80808080);
+}
+
+
+} // Namespace NxPs2
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxTextured3dPoly.h b/Code/Gfx/NGPS/p_NxTextured3dPoly.h
new file mode 100644
index 0000000..283479e
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxTextured3dPoly.h
@@ -0,0 +1,26 @@
+#ifndef	__GFX_P_NX_TEXTURED_3D_POLY_H__
+#define	__GFX_P_NX_TEXTURED_3D_POLY_H__
+
+#include 
+#include 
+
+namespace NxPs2
+{
+
+// Machine specific implementation of CTextured3dPoly
+class	CPs2Textured3dPoly : public Nx::CTextured3dPoly
+{
+public:
+							CPs2Textured3dPoly();
+	virtual					~CPs2Textured3dPoly();
+private:
+	void					plat_render();
+	void					plat_set_texture(uint32 texture_checksum);
+	
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+}	// namespace NxPs2
+
+#endif
+				   
diff --git a/Code/Gfx/NGPS/p_NxViewMan.cpp b/Code/Gfx/NGPS/p_NxViewMan.cpp
new file mode 100644
index 0000000..459195f
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxViewMan.cpp
@@ -0,0 +1,31 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxViewMan.cpp - PS2 platform specific interface to CViewportManager
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+
+#include 
+
+#include "gfx/NxViewMan.h"
+#include "gfx/NGPS/p_NxViewport.h"
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewport *		CViewportManager::s_plat_create_viewport(const Mth::Rect* rect, Gfx::Camera* cam)
+{
+	return new CPs2Viewport(rect, cam);
+}
+
+} 
+ 
diff --git a/Code/Gfx/NGPS/p_NxViewport.cpp b/Code/Gfx/NGPS/p_NxViewport.cpp
new file mode 100644
index 0000000..f6aad3e
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxViewport.cpp
@@ -0,0 +1,154 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxViewport.cpp
+
+#include 	"Gfx/NxViewMan.h"
+#include 	"Gfx/NGPS/p_NxViewport.h"
+
+#include 	"Gfx/NGPS/NX/render.h"
+#include 	"Gfx/NGPS/NX/sprite.h"
+#include 	"Gfx/NGPS/NX/switches.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CViewport
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Viewport::CPs2Viewport()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Viewport::CPs2Viewport( const Mth::Rect* rect, Gfx::Camera* cam) :
+	CViewport(rect, cam)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Viewport::~CPs2Viewport()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CPs2Viewport::update_render_vars()
+{
+	int view_idx = CViewportManager::sGetActiveViewportNumber(this);
+	Dbg_MsgAssert(view_idx >= 0, ("Can't find viewport in active viewport list"));
+
+	if (!NxPs2::render::VarsUpToDate(view_idx) && mp_camera)
+	{
+		Mth::Matrix cam_matrix(mp_camera->GetMatrix());
+		cam_matrix[W] = mp_camera->GetPos();
+
+		NxPs2::render::SetupVars(view_idx, cam_matrix, GetRect(), mp_camera->GetAdjustedHFOV(), GetAspectRatio(),
+							  /*-mp_camera->GetNearClipPlane(), -mp_camera->GetFarClipPlane()*/ -1.0f, -100000.0f);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float			CPs2Viewport::plat_transform_to_screen_coord(const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z)
+{
+	float zdistance;
+	float scale;
+
+	update_render_vars();
+
+	// First check frustum
+	Mth::Vector screen_pos(NxPs2::render::WorldToFrustum.Transform(world_pos));
+
+	//if ((Tmr::GetVblanks() % 200) == 0)
+	//{
+	//	Dbg_Message("Homogenious (%g, %g, %g, %g)", screen_pos[X], screen_pos[Y], screen_pos[Z], screen_pos[W]);
+	//}
+
+	if ((screen_pos[X] > screen_pos[W]) || (screen_pos[X] < -screen_pos[W]) ||
+		(screen_pos[Y] > screen_pos[W]) || (screen_pos[Y] < -screen_pos[W]) ||
+		(screen_pos[Z] > screen_pos[W]) || (screen_pos[Z] < -screen_pos[W]))
+	{
+		return -1.0f;
+	} else {
+		zdistance = -screen_pos[Z]; //(screen_pos[Z] - screen_pos[W]) / (2.0f * screen_pos[W]);
+	}
+
+	// Now find screen coordinates
+	screen_pos = NxPs2::render::WorldToIntViewport.Transform(world_pos);
+
+	//if ((Tmr::GetVblanks() % 200) == 0)
+	//{
+	//	Dbg_Message("Converted (%f, %f, %f, %f) to (%g, %g, %g, %g)", world_pos[X], world_pos[Y], world_pos[Z], world_pos[W],
+	//																  screen_pos[X], screen_pos[Y], screen_pos[Z], screen_pos[W]);
+	//}
+
+	// Get into homogenious space
+	float oow = 1.0f / screen_pos[W];
+	screen_pos *= oow;
+
+	// Convert the int back to float
+	screen_pos_x = (((float) ( *((int *) &(screen_pos[X])) & 0xFFFF )) - XOFFSET) / 16.0f;
+	screen_pos_y = (((float) ( *((int *) &(screen_pos[Y])) & 0xFFFF )) - YOFFSET) / 16.0f;
+	screen_pos_z = (ZBufferValue) ( *((int *) &(screen_pos[Z])) & 0xFFFFFF );
+
+	// Convert back to NTSC, if necessary
+	screen_pos_x /= NxPs2::SDraw2D::GetScreenScaleX();
+	screen_pos_y /= NxPs2::SDraw2D::GetScreenScaleY();
+
+	//if ((Tmr::GetVblanks() % 200) == 0)
+	//{
+	//	Dbg_Message("Divide by W (%g, %g, %x, %g) Z distance: %g", screen_pos_x, screen_pos_y, screen_pos_z, screen_pos[W], zdistance);
+	//}
+
+	// Calculate scale
+	scale = CViewportManager::sGet2DIn3DSpaceNoscaleDistance() / zdistance;
+	scale = Mth::Min(scale, CViewportManager::sGet2DIn3DSpaceMaxScale());
+	scale = Mth::Max(scale, CViewportManager::sGet2DIn3DSpaceMinScale());
+
+#if 0		// This scales 1 sprite or text pixel to an inch!
+	float hfov;
+	if (mp_camera)
+	{
+		hfov = mp_camera->GetAdjustedHFOV();
+	} else {
+		hfov = CViewportManager::sGetScreenAngle();
+	}
+
+	// Calculate scale
+	float h = ((float) HRES) / (tanf(Mth::DegToRad(hfov / 2.0f)));
+	scale = h / zdistance;
+
+	if ((Tmr::GetVblanks() % 300) == 0)
+	{
+		Dbg_Message("H %f, ZDistance %f, Scale %f, HFOC %f", h, zdistance, scale, hfov);
+	}
+#endif
+
+	return scale;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxViewport.h b/Code/Gfx/NGPS/p_NxViewport.h
new file mode 100644
index 0000000..ea87e35
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxViewport.h
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxViewport.h
+
+#ifndef	__GFX_P_NX_VIEWPORT_H__
+#define	__GFX_P_NX_VIEWPORT_H__
+
+#include 	"Gfx/NxViewport.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CViewport
+class	CPs2Viewport : public CViewport
+{
+public:
+								CPs2Viewport();
+								CPs2Viewport(const Mth::Rect* rect, Gfx::Camera* cam = NULL);
+	virtual						~CPs2Viewport();
+
+protected:
+	void						update_render_vars();
+
+private:		// It's all private, as it is machine specific
+	virtual float				plat_transform_to_screen_coord(const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z);
+
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGPS/p_NxWin2D.cpp b/Code/Gfx/NGPS/p_NxWin2D.cpp
new file mode 100644
index 0000000..88683ae
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxWin2D.cpp
@@ -0,0 +1,80 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxWin2D.cpp
+
+#include 	"Gfx/NGPS/p_NxWin2D.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Window2D::CPs2Window2D(int x, int y, int width, int height) : CWindow2D(x, y, width, height)
+{
+	mp_plat_window = new NxPs2::SScissorWindow();
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2Window2D::~CPs2Window2D()
+{
+	if (mp_plat_window)
+	{
+		delete mp_plat_window;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CPs2Window2D::plat_update_engine()
+{
+	int x0 = (int) (((float) m_xpos) * NxPs2::SDraw2D::GetScreenScaleX());
+	int y0 = (int) (((float) m_ypos) * NxPs2::SDraw2D::GetScreenScaleY());
+
+	Dbg_Assert(x0 >= 0);
+	Dbg_Assert(y0 >= 0);
+
+	int x1 = x0 + (int) (((float) m_width ) * NxPs2::SDraw2D::GetScreenScaleX()) - 1;
+	int y1 = y0 + (int) (((float) m_height) * NxPs2::SDraw2D::GetScreenScaleY()) - 1;
+
+	Dbg_Assert(x1 >= 0);
+	Dbg_Assert(y1 >= 0);
+
+	mp_plat_window->SetScissor(x0, y0, x1, y1);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CWindow2DManager::s_plat_alloc_window2d_pool()
+{
+	for (int i = 0; i < vMAX_WINDOW_INSTANCES; i++)
+	{
+	   	CPs2Window2D *p_window = new CPs2Window2D;
+		p_window->mp_next = sp_window_list;
+		sp_window_list = p_window;
+	}
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/NGPS/p_NxWin2D.h b/Code/Gfx/NGPS/p_NxWin2D.h
new file mode 100644
index 0000000..4c6d473
--- /dev/null
+++ b/Code/Gfx/NGPS/p_NxWin2D.h
@@ -0,0 +1,49 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxWin2D.h
+
+#ifndef	__GFX_P_NX_WIN2D_H__
+#define	__GFX_P_NX_WIN2D_H__
+
+#include 	"Gfx/NxWin2D.h"
+#include 	"Gfx/NGPS/NX/sprite.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CWindow2D
+class	CPs2Window2D : public CWindow2D
+{
+public:
+								CPs2Window2D(int x = 0, int y = 0, int width = 640, int height = 448);
+								CPs2Window2D(const Mth::Rect & win_rect);
+	virtual						~CPs2Window2D();
+
+	NxPs2::SScissorWindow *		GetEngineWindow() const;
+
+private:
+	//
+	virtual void				plat_update_engine();		// Update engine primitives
+
+	// Machine specific members
+	NxPs2::SScissorWindow *		mp_plat_window;				// Pointer to engine window
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline NxPs2::SScissorWindow *	CPs2Window2D::GetEngineWindow() const
+{
+	return mp_plat_window;
+}
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/NGPS/p_gfxman.cpp b/Code/Gfx/NGPS/p_gfxman.cpp
new file mode 100644
index 0000000..1e402d8
--- /dev/null
+++ b/Code/Gfx/NGPS/p_gfxman.cpp
@@ -0,0 +1,784 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		p_gfxman.cpp											**
+**																			**
+**	Created:		07/26/99	-	mjb										**
+**																			**
+**	Description:	Graphics device manager									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+extern "C"				 
+{
+
+#include 
+#include 
+#include 
+}
+
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+#define NUMPIXELS ( width * height )
+
+#define HEADSIZE 54
+#define IMAGESIZE ( NUMPIXELS * 3 )
+#define BITMAPSIZE ( IMAGESIZE + HEADSIZE )
+
+// Macro to store 32-bit data to unaligned address.
+#define SET_UINT32( addr, x ) ( \
+	*(( uint16 * )( addr )) = ( x ), \
+	*(( uint16 * )(( addr ) + 2 )) = (( x ) >> 16 ) \
+)
+
+void SetupBMPHeader(uint8 *bmp, int width, int height)
+{
+	// Initialize the BMP header structure.
+	// Bitmapfileheader
+	*bmp						= 'B';              // Type
+	*( bmp + 1 )				= 'M';
+	SET_UINT32( bmp + 2, BITMAPSIZE );				// Size
+	*( uint16 * )( bmp + 6 )	= 0;                // Reserved
+	SET_UINT32( bmp + 10, HEADSIZE );				// Offset
+
+	// Bitmapinfoheader
+	SET_UINT32( bmp + 14, 40 );						// Bitmapinfoheader size
+	SET_UINT32( bmp + 18, width );					// Width
+	SET_UINT32( bmp + 22, height );					// Height
+	*( uint16 * )( bmp + 26 )	= 1;      			// Planes
+	*( uint16 * )( bmp + 28 )	= 24;     			// Bitcount
+	SET_UINT32( bmp + 30, 0 );						// Compression
+	SET_UINT32( bmp + 34, IMAGESIZE );				// Image size in bytes
+	SET_UINT32( bmp + 38, 4740 );					// X Pels per metre
+	SET_UINT32( bmp + 42, 4740 );					// Y Pels per metre
+	SET_UINT32( bmp + 46, 0 );						// ClrUsed
+	SET_UINT32( bmp + 50, 0 );						// ClrImportant
+
+}
+
+
+//--------------------------------------------------------------------------------------------
+// File: main.c
+// Date: August 19, 2000
+// Author: George Bain @ Sony Computer Entertainment Europe
+// Description: Store GS local memory -> Main/SPR memory 
+// Notes: - read StoreTextureVIF1() and DumpImage() for more information
+//        - pause screen using "square + cross buttons" to avoid blur
+//
+//--------------------------------------------------------------------------------------------
+
+//--------------------------------------------------------------------------------------------
+// I N C L U D E S
+//--------------------------------------------------------------------------------------------
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include	
+
+
+//--------------------------------------------------------------------------------------------
+// D E F I N E S
+//--------------------------------------------------------------------------------------------
+
+#define SCRN_W (640)
+#define SCRN_H (224)
+#define SCRN_CENTER_X (2048.0f)
+#define SCRN_CENTER_Y (2048.0f)
+#define SCRN_Z (512.0f)
+#define ZBUF_MAX (16777000.0f)
+#define ZBUF_MIN (1.0f)
+#define ZCLIP_MAX (16777216.0f)
+#define ZCLIP_MIN (1.0f)
+#define ASPECT_X (1.0f)
+#define ASPECT_Y (((float)SCRN_H*4.0f)/((float)SCRN_W*3.0f))
+#define STRING_SIZE   (64)
+#define OFFX (((4096-SCRN_W)/2)<<4)
+#define OFFY (((4096-SCRN_H)/2)<<4)
+#define PI (3.141592f)
+
+#define RAD_TO_DEG(x) (x * 180.0f / PI)
+#define DEG_TO_RAD(x) (x * PI / 180.0f)
+
+#define ROT_SPEED (1.0f)
+#define SET_VECTOR(_p,_x,_y,_z,_w)  ((_p)[0] = _x, (_p)[1] = _y, (_p)[2] = _z, (_p)[3] = _w)
+#define TRANS (10.0f)
+#define SPR_MEM (0x70000000)
+#define UNCACHED_MEM (0x20000000)
+#define DMA_SPR (0x80000000)
+#define DMA_MEM (0x0FFFFFFF)
+#define CUBE_VERT (36)
+
+#define GIFTAG_NLOOP (CUBE_VERT)
+#define GS_PRIM_SHADED (1)
+#define GS_PRIM_ALPHA (1)
+#define GIFTAG_EOP (1)
+#define GIFTAG_PRE (1)
+#define GS_PRIM_TEX (1)
+
+#define RGB_FRAME (1)
+
+#define GIFTAG_MAX_NLOOP (32767)
+#define IMAGE_W (256)
+#define IMAGE_H (256)
+#define STORE_IMAGE_W (SCRN_W)
+#define STORE_IMAGE_H (SCRN_H)
+#define IMAGE_ADDR ( ((SCRN_W*SCRN_H*4)*3) / 256 )
+#define CLUT_CSM1_MODE (0)
+#define CLUT_CSM2_MODE (1)
+
+//--------------------------------------------------------------------------------------------
+// S T R U C T U R E S
+//--------------------------------------------------------------------------------------------
+
+typedef struct rect_tag
+{
+
+  short x, y;
+  short w, h;
+
+}
+RECT;
+
+typedef struct timimage_tag
+{
+
+  u_int mode;
+  u_int id;
+  u_int flag;
+  u_int cbnum;
+  RECT crect;
+  u_int *caddr;
+  u_int pbnum;
+  RECT prect;
+  u_int *paddr;
+
+}
+TIM_IMAGE;
+
+
+//--------------------------------------------------------------------------------------------
+// G L O B A L S
+//--------------------------------------------------------------------------------------------
+
+sceVu0FVECTOR camera_p = { 0.0f, 0.0f, -300.0f, 0.0f };
+sceVu0FVECTOR camera_zd = { 0.0f, 0.0f, 1.0f, 1.0f };
+sceVu0FVECTOR camera_yd = { 0.0f, 1.0f, 0.0f, 1.0f };
+sceVu0FVECTOR light0 = { 1.0f, 0.0f, 0.3f, 0.0f };
+sceVu0FVECTOR light1 = { 0.0f, 1.0f, 0.3f, 0.0f };
+sceVu0FVECTOR light2 = { 0.0f, 0.0f, 1.0f, 0.0f };
+sceVu0FVECTOR color0 = { 0.3f, 0.3f, 0.3f, 1.0f };
+sceVu0FVECTOR color1 = { 0.3f, 0.3f, 0.3f, 1.0f };
+sceVu0FVECTOR color2 = { 0.4f, 0.4f, 0.4f, 1.0f };
+sceVu0FVECTOR ambient = { 0.2f, 0.2f, 0.2f, 0.0f };
+sceVu0FVECTOR obj_trans = { 0.0f, 0.0f, 0.0f, 0.0f };
+sceVu0FVECTOR obj_rot = { 0.0f, 0.0f, 0.0f, 0.0f };
+sceVu0FMATRIX local_world;
+sceVu0FMATRIX world_view;
+sceVu0FMATRIX view_screen;
+sceVu0FMATRIX local_screen;
+sceVu0FMATRIX normal_light;
+sceVu0FMATRIX light_color;
+sceVu0FMATRIX local_light;
+sceVu0FMATRIX work;
+sceGsDBuff db;
+sceDmaChan *dmaGIF = NULL;
+sceDmaChan *dmaVIF1 = NULL;
+float delta = ROT_SPEED;
+u_int paddata;
+u_char rdata[32];
+u_long128 pad_dma_buf[scePadDmaBufferMax] __attribute__ ( ( aligned( 64 ) ) );
+TIM_IMAGE texture;
+
+
+//--------------------------------------------------------------------------------------------
+// P R O T O T Y P E S
+//--------------------------------------------------------------------------------------------
+
+void InitPad( void );
+void InitSystem( void );
+void InitGraphics( void );
+void ControlInput( void );
+void SetAlphaBlend( void );
+void SetTexFilter( void );
+void SetTextureInfo( void );
+void LoadTexture( u_long128 * base_addr, short pixel_mode, short addr, short w, short h,
+		  short dest_x, short dest_y );
+void ClearVRAM( u_char r, u_char g, u_char b, u_char a );
+void CubePacket( sceVu0FVECTOR * vertex, sceVu0FVECTOR * normal, sceVu0FVECTOR * color,
+		 sceVu0FVECTOR * st );
+void sceVu0NormalColorVector( sceVu0IVECTOR c0, sceVu0FMATRIX local_light,
+			      sceVu0FMATRIX light_color, sceVu0FVECTOR v0, sceVu0FVECTOR c1 );
+void ReadTIM( u_char * file, TIM_IMAGE * tim, short clut_store_mode );
+void StoreTextureVIF1( u_long128 * base_addr, short start_addr, short pixel_mode, short x,
+		       short y, short w, short h, short frame_width );
+void DumpImage( int frame_cnt, int oddeven );
+
+
+//--------------------------------------------------------------------------------------------
+// Function: DumpImage()
+// Description: Uses StoreTextureVIF1() to grab both the odd and even frame buffers in GS local
+//              memory.  A 24-bit TIM image is written to a file called "image.tim".
+// Paramaters:  int frame_cnt: odd or even frame
+//              int oddeven: odd or even frame (Vsync)
+// Returns: none
+// Notes: N/A
+//--------------------------------------------------------------------------------------------
+
+/*
+void DumpImage( int frame_cnt, int oddeven )
+{
+
+  u_long128 *dst1 = NULL, *dst2 = NULL;
+  int fd;
+  int cnt = 0;
+  int index = 0;
+  u_int bnum __attribute__ ( ( aligned( 16 ) ) );
+  u_short pixel_head[4] __attribute__ ( ( aligned( 16 ) ) );
+  u_int header[3] __attribute__ ( ( aligned( 16 ) ) );
+
+  // create image file
+  fd = sceOpen( "host:image.tim", SCE_WRONLY | SCE_TRUNC | SCE_CREAT );
+
+  header[0] = 0x10;		// id
+  header[1] = 0x03;		// flag 2= 16bit 3 = 24bit
+
+  // write header
+  sceWrite( fd, &header[0], 4 );
+  sceWrite( fd, &header[1], 4 );
+
+  bnum = ( ( STORE_IMAGE_W * STORE_IMAGE_H * 3 ) * 2 ) + 12;	// BNUM
+  pixel_head[0] = 0;		//x
+  pixel_head[1] = 0;		//y
+  pixel_head[2] = 960;		//w
+  pixel_head[3] = 448;		//h
+
+  // write pixel header information
+  sceWrite( fd, &bnum, 4 );
+  sceWrite( fd, &pixel_head[0], 2 );
+  sceWrite( fd, &pixel_head[1], 2 );
+  sceWrite( fd, &pixel_head[2], 2 );
+  sceWrite( fd, &pixel_head[3], 2 );
+
+  // allocate some memory
+  dst1 = ( u_long128 * ) memalign( 64, STORE_IMAGE_W * STORE_IMAGE_H * 3 );
+  dst2 = ( u_long128 * ) memalign( 64, STORE_IMAGE_W * STORE_IMAGE_H * 3 );
+
+  StoreTextureVIF1( dst1, 0, SCE_GS_PSMCT24, 0, 0, STORE_IMAGE_W, STORE_IMAGE_H, SCRN_W );
+  StoreTextureVIF1( dst2, 2240, SCE_GS_PSMCT24, 0, 0, STORE_IMAGE_W, STORE_IMAGE_H, SCRN_W );
+
+  index = 0;
+
+  for ( cnt = 0; cnt < STORE_IMAGE_H; cnt++ )
+    {
+
+      if ( ( ( frame_cnt == NULL ) && ( oddeven == NULL ) )
+	   || ( ( frame_cnt ) && ( oddeven ) ) )
+	{
+	  sceWrite( fd, dst1 + index, STORE_IMAGE_W * 3 );
+	  sceWrite( fd, dst2 + index, STORE_IMAGE_W * 3 );
+	}
+      else
+	{
+	  sceWrite( fd, dst2 + index, STORE_IMAGE_W * 3 );
+	  sceWrite( fd, dst1 + index, STORE_IMAGE_W * 3 );
+	}
+
+
+      index += 120;		// (STORE_IMAGE_W * 3)/16;
+      printf( "written line:%d\n", cnt );
+
+    }
+
+  free( dst1 );
+  free( dst2 );
+
+  sceClose( fd );
+
+
+}				// end DumpImage
+
+*/
+
+
+
+//--------------------------------------------------------------------------------------------
+// Function:    StoreTextureVIF1()
+// Description: Stores texture data from GS to main memory or scratchpad memory.  See notes on
+//              data flow
+// Paramaters:  u_long128* base_addr: base address of stored texture data in MAIN/SPR memory
+//              short start_addr: start address in GS memory to transfer from
+//              short pixel_mode: pixel mode of stored data
+//              short x: x location in GS memory
+//              short y: y location in GS memory
+//              short w: width of stored image
+//              short h: height of stored image
+//							short frame_width: width of frame buffer
+// Returns:     N/A
+// Notes: -     data flow of store image
+//              	- wait for all 3 PATHS to GIF to be complete (FLUSHA vif code)
+//              	- disable PATH3 transfer using MSKPATH3 vifcode (mask)
+//              	- set BITBLTBUF register (source parameters)
+//              	- set TRXPOS register (x,y postion)
+//              	- set TRXREG register (width and height)
+//              	- set FINISH register (set event)
+//              	- set TRXDIR register (LOCAL->HOST)
+//              	- get previous Interrupt Mask Control (IMR)
+//              	- enable the FINISH event
+//              	- send GIF packet to GS
+//              	- wait for DMA to be complete
+//              	- wait for FINISH event to be generated  (all drawing is complete)
+//              	- change direction of the GS bus
+//              	- change direction of VIF1-FIFO (VIF1_STAT.FDR)
+//              	- DMA GS data to main/spr memory
+//              	- wait for DMA to be complete
+//              	- restore direction of the GS bus
+//              	- restore direction of VIF1-FIFO 
+//              	- restore previous Interrupt Mask Control (IMR)
+//              	- enable the FINISH event
+//              	- enable PATH3 transfer using MSKPATH3 vifcode
+//--------------------------------------------------------------------------------------------
+
+void StoreTextureVIF1( u_long128 * base_addr, short start_addr, short pixel_mode, short x,
+		       short y, short w, short h, short frame_width )
+{
+
+  int texture_qwc;
+  sceVif1Packet vif1_pkt;
+  u_long128 settup_base[10];
+  int buff_width;
+  static u_int enable_path3[4] __attribute__ ( ( aligned( 16 ) ) ) = {
+    0x06000000,
+    0x00000000,
+    0x00000000,
+    0x00000000,
+  };
+
+  // get quad word count for image
+  if ( pixel_mode == SCE_GS_PSMCT32 )
+    texture_qwc = ( w * h * 32 ) >> 7;
+  else if ( pixel_mode == SCE_GS_PSMCT24 )
+    texture_qwc = ( w * h * 24 ) >> 7;
+  else if ( pixel_mode == SCE_GS_PSMCT16 )
+    texture_qwc = ( w * h * 16 ) >> 7;
+  else if ( pixel_mode == SCE_GS_PSMT8 )
+    texture_qwc = ( w * h * 8 ) >> 7;
+  else
+    texture_qwc = ( w * h * 4 ) >> 7;
+
+  if ( texture_qwc > GIFTAG_MAX_NLOOP )
+    {
+      printf( "ERROR: Texture QWC is greater then GIFTAG_NLOOP! line:(%d), file:(%s)\n", __LINE__,
+	       __FILE__ ); 
+			exit( 0 );
+    }
+
+  buff_width = frame_width >> 6;
+
+  if ( buff_width <= 0 )
+    buff_width = 1;
+
+  // set base address of GIF packet
+  sceVif1PkInit( &vif1_pkt, &settup_base[0] );
+  sceVif1PkReset( &vif1_pkt );
+
+  // will start transfer with VIF code and GS data will follow
+  sceVif1PkAddCode( &vif1_pkt, SCE_VIF1_SET_NOP( 0 ) );
+  // disable PATH 3 transfer
+  sceVif1PkAddCode( &vif1_pkt, SCE_VIF1_SET_MSKPATH3( 0x8000, 0 ) );
+  // wait for all 3 PATHS to GS to be complete
+  sceVif1PkAddCode( &vif1_pkt, SCE_VIF1_SET_FLUSHA( 0 ) );
+  // transfer 6 QW's to GS
+  sceVif1PkAddCode( &vif1_pkt, SCE_VIF1_SET_DIRECT( 6, 0 ) );
+
+  // GIF tag for texture settings         
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GIF_SET_TAG( 5, GIFTAG_EOP, NULL, NULL, SCE_GIF_PACKED, 1 ) );
+  sceVif1PkAddGsData( &vif1_pkt, 0xEL );
+
+  // set transmission between buffers
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_SET_BITBLTBUF( start_addr, buff_width, pixel_mode,	// SRC
+						       NULL, NULL, NULL ) );	// DEST
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_BITBLTBUF );
+
+  // set transmission area between buffers        ( source x,y  dest x,y  and direction )
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_SET_TRXPOS( x, y, 0, 0, 0 ) );
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_TRXPOS );
+
+  // set size of transmission area 
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_SET_TRXREG( w, h ) );
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_TRXREG );
+
+  // set FINISH event occurrence request
+  sceVif1PkAddGsData( &vif1_pkt, ( u_long ) ( 0x0 ) );
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_FINISH );
+
+  // set transmission direction  ( LOCAL -> HOST Transmission )
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_SET_TRXDIR( 1 ) );
+  sceVif1PkAddGsData( &vif1_pkt, SCE_GS_TRXDIR );
+
+  // get packet size in quad words        
+  sceVif1PkTerminate( &vif1_pkt );
+
+
+
+
+  // get current IMR status
+//  u_long prev_imr = 0;
+//  prev_imr = sceGsPutIMR( sceGsGetIMR(  ) | 0x0200 );    // <<<<<  Mick, removed.
+
+
+  // set the FINISH event
+  DPUT_GS_CSR( GS_CSR_FINISH_M );
+
+
+
+  // DMA from memory and start DMA transfer
+  FlushCache( WRITEBACK_DCACHE );
+  
+	DPUT_D1_QWC( 0x7 );
+  
+	DPUT_D1_MADR( ( u_int ) vif1_pkt.pBase & DMA_MEM );
+
+  
+	DPUT_D1_CHCR( 1 | ( 1 << 8 ) );
+
+  asm __volatile__( " sync.l " );
+
+
+  // check if DMA is complete (STR=0)
+  while ( DGET_D1_CHCR(  ) & 0x0100 );
+//  printf( " 5 GS registers set\n" );
+
+  // check if FINISH event occured
+  while ( ( DGET_GS_CSR(  ) & GS_CSR_FINISH_M ) == 0 );
+//  printf( " Finish event complete\n" );
+
+
+
+  // change VIF1-FIFO transfer direction (VIF1 -> MAIN MEM or SPR)
+  *VIF1_STAT = 0x00800000;
+
+
+  // change GS bus direction (LOCAL->HOST)
+  DPUT_GS_BUSDIR( ( u_long ) 0x00000001 );
+
+
+//  printf( " Changed VIF1 and GS direction complete\n" );
+
+  // DMA to memory and start DMA transfer
+  FlushCache( WRITEBACK_DCACHE );
+  
+	DPUT_D1_QWC( texture_qwc );
+  
+	DPUT_D1_MADR( ( u_int ) base_addr & DMA_MEM );
+
+  
+	DPUT_D1_CHCR( 0 | ( 1 << 8 ) );
+  
+	asm __volatile__( " sync.l " );
+
+  // check if DMA is complete (STR=0)
+  while ( DGET_D1_CHCR(  ) & 0x0100 );
+//  printf( " Transferred:(%d) QW from GS -> MEM complete\n", texture_qwc );
+
+
+
+  // change VIF1-FIFO transfer direction (MAIN MEM or SPR -> VIF1)
+  *VIF1_STAT = 0;
+
+  // change GS bus direction (HOST->LOCAL)
+  DPUT_GS_BUSDIR( ( u_long ) 0 );
+
+
+  // restore previous IMR status
+//  sceGsPutIMR( prev_imr );	 // <<<<<  Mick, removed.
+
+  
+  
+  // set the FINISH event
+  DPUT_GS_CSR( GS_CSR_FINISH_M );
+//  printf( " Restore VIF1 and GS direction complete\n" );
+
+  // MSKPATH3 is now enabled to allow transfer via PATH3
+  DPUT_VIF1_FIFO( *( u_long128 * ) enable_path3 );
+
+//  printf( " Restore PATH3 direction complete\n" );
+
+
+}				// end StoreTextureVIF1
+
+
+
+//----------------------------------------------EOF-------------------------------------------
+
+
+
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+namespace NxPs2
+{
+	void	WaitForRendering();
+}
+
+namespace Gfx
+{
+
+
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void	Manager::DumpVRAMUsage( void )
+{
+	printf ("ERROR: DumpVRAMUsage is no longer supported, due to dynamic textures\n");	
+}
+
+
+void 	save_to_pc(const char *fileroot,uint8* pixelBuffer)
+{
+		// Try to find a good filename of the format filebasexxx.bmp.  "Good" is
+		// defined here as one that isn't already used.
+		char fileName[ 132 ];
+		int i = 0;
+		while ( TRUE ) {
+			sprintf( fileName, "Screens\\%s%03d.bmp", fileroot, i );
+	
+			// Found an unused one!  Yay!
+			if ( FALSE == File::Exist( fileName ))
+				break;
+	
+			i++;
+		}
+		printf ("Saving Screenshot %s\n",fileName);
+		// Write out the file.	
+		void *rwfd;
+		rwfd = File::Open( fileName, "wb" );
+		Dbg_MsgAssert(rwfd, ("Couldn't open %s for writing on the PC.", fileName));
+		File::Write(pixelBuffer,1,640*448*3+HEADSIZE,rwfd);
+		File::Close( rwfd );
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ScreenShot( const char *fileroot )
+{
+
+
+	#if 1
+
+	bool	compact = true;
+
+	FlushCache( 0 );
+	sceGsSyncPath( 0, 0 );
+
+	NxPs2::WaitForRendering(); 		// This will also ensure the DMA buffers are free
+	sceGsSyncPath( 0, 0 );
+
+
+
+	uint8 *pixelBuffer;
+	// Get a huge chunk of memory and dump the video buffer into it.
+	pixelBuffer = NxPs2::dma::pRuntimeBuffer + HEADSIZE + 10;
+
+	// for compact mode, we need 640*448*31/32*3 + 640*448/32*4  + 54 bytes = 833280 + 35840 + 54 = 869174 bytes
+
+	int lines=32;			// lines per StoreImage (we need to do it in at last two)
+	for (int i=0;i<448/lines;i++)
+	{
+		// not got that much memory, so we convert the buffer in place
+		uint32 *p_pixel4 =  (uint32*)( pixelBuffer +i*640*3*lines );
+		
+		StoreTextureVIF1(
+		(u_long128*)( p_pixel4 ),
+		0,  // offset....
+		SCE_GS_PSMCT32,
+		0,
+		i*lines,
+		640,
+		lines,
+		640 );
+
+		uint8 *p_pixel3 =  (uint8*) p_pixel4;
+		uint8 r, g, b;
+		for (int x = 0;x < 640*lines;x++)
+		{
+			// 
+			r = ((( *p_pixel4 ) >> 16 ) & 0xff ) ;
+			g = ((( *p_pixel4 ) >> 8 ) & 0xff ) ;
+			b = ((( *p_pixel4++ )  ) & 0xff ) ;
+			
+			*p_pixel3++ = r;
+			*p_pixel3++ = g;
+			*p_pixel3++ = b;
+		}
+	}
+	
+	// flip the buffer upside down in place
+	uint8*		top_line = pixelBuffer;
+	uint8*		bot_line = pixelBuffer + 640*3*447;
+	for (int line = 0;line <224; line++)
+	{
+		for (int b = 0;b<640*3;b++)
+		{
+			uint8 t = top_line[b];
+			top_line[b] = bot_line[b];
+			bot_line[b] = t;
+		}
+		top_line += 640*3;
+		bot_line -= 640*3;
+	}
+
+	// and insert the header
+	SetupBMPHeader((uint8*)NxPs2::dma::pRuntimeBuffer+10,640,448);
+
+	// Finally, I'm going to copy the whole pile of crap down 10 bytes
+	// in case anything that saves it relies on it being 16 byte aligned
+	uint8 *p1 = (uint8*)NxPs2::dma::pRuntimeBuffer+10;
+	uint8 *p2 = (uint8*)NxPs2::dma::pRuntimeBuffer;
+	for (int i=0;i<640*448*3+HEADSIZE;i++)
+	{
+		*p2++ = *p1++;
+	}  
+	pixelBuffer = (uint8*)NxPs2::dma::pRuntimeBuffer; 
+
+	if (Script::GetInt("memcard_screenshots"))
+	{
+		printf ("STUBBED!!! Saving Screenshot to TH4MC???\n");
+//		CFuncs::SaveDataFile("TH4MC", NxPs2::dma::pRuntimeBuffer, 640*448*3+HEADSIZE);
+	}
+	else
+	{
+		save_to_pc(fileroot,pixelBuffer);	
+	}
+	
+	if (!compact)
+	{
+		// Release the memory for the video buffer.
+		delete( pixelBuffer );
+	}	
+	
+	#else
+		printf ("Screenshot functionality stubbed out\n");
+	
+	#endif
+
+	
+    FlushCache(0);
+
+
+}
+
+// Called byte dumpshots
+void Manager::DumpMemcardScreeenshots()
+{
+/*
+	char name[100];
+	for (int i=0;i<12;i++)
+	{
+		sprintf(name,"TH4MC%03d",i);
+		printf ("Checking for %s \n",name);
+		if (CFuncs::LoadDataFile(name, NxPs2::dma::pRuntimeBuffer, 640*448*3+HEADSIZE))
+		{
+			save_to_pc("MemCard", NxPs2::dma::pRuntimeBuffer);
+		}
+		else
+		{
+			printf ("not there\n");
+		}
+	}
+*/	
+	printf ("DumpMemcardScreeenshots STUBBED\n");
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
diff --git a/Code/Gfx/NGPS/p_memview.cpp b/Code/Gfx/NGPS/p_memview.cpp
new file mode 100644
index 0000000..b6ae395
--- /dev/null
+++ b/Code/Gfx/NGPS/p_memview.cpp
@@ -0,0 +1,1716 @@
+//////////////////////////////////////////////////////
+// p_memview.cpp
+//
+// code for tracking memory usage, and displaying it in a graphical manner
+// keeps extra info about allocated blocks
+// including the call stack, so we can print out information
+// about specific allocated blocks
+// which we will select using the graphical memory browser
+//
+//
+// tried to use a little of the task system as possible
+// so we can run the inspector
+// without it messing with the heap it inspects
+//
+//
+
+
+extern char _mem_dump_start[];
+extern char _map_file_start[];
+extern char _symbols_start[];
+extern char _callstack_start[];
+extern char _code_end[];
+extern char	_std_mem_end[];
+extern char	_stack_size[];
+
+extern char __text_org[];
+extern char __data_org[];
+extern char __rodata_org[];
+extern char __bss_org[];
+
+extern char __rodata_orgend[];
+extern char __bss_objend[];
+extern char __text_objend[];
+
+extern char _debug_heap_start[];
+extern char _script_heap_start[];
+
+
+//extern char _rwheapdebug_start[];
+
+			 
+#define	STACKDEPTH  30			 
+
+int 	mem_strings;
+
+
+namespace Nx
+{
+	extern void	_debug_change_2d_scale(float x);
+
+}
+			 
+extern "C"				 
+{
+extern char ENTRYPOINT[];		
+
+
+#include 
+#include 
+#include 
+}
+
+#include 
+#include 
+#include 					// needed for buttons
+#include 
+#include  			// needed for loading map file
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+#include 
+#include 
+
+// needed for some VerticalMenu specific debugging							
+#include 
+#include 
+#include 
+
+
+extern volatile int	test_vblanks;
+
+
+class CCallStack
+{
+public:
+	void Append(CCallStack *p);
+	void Remove();
+	void InitHead();
+	int	used;
+	int	size;
+	CCallStack *pNext;
+	CCallStack *pPrev;
+	int	addr[STACKDEPTH];
+	uint32	flags;
+	Mem::Allocator::BlockHeader * pBlock;	// pointer to block that has this callstack
+
+};
+
+CCallStack		free_list;	// list of created objects
+CCallStack		used_list;	// list of created objects
+
+// init a node, so it can act as the head			 
+inline void CCallStack::InitHead()
+{
+	pPrev = this;
+	pNext = this;
+}
+	
+// append node p to this node (after it)									 
+inline void CCallStack::Append(CCallStack *p)
+{
+
+	p->pNext = this->pNext;
+	p->pPrev = this;
+	this->pNext = p;
+	p->pNext->pPrev = p;
+}
+
+// simply unlink it from the list			   
+inline void CCallStack::Remove()
+{
+	pPrev->pNext = pNext;
+	pNext->pPrev = pPrev;
+}
+
+
+						
+//CCallStack * CallStack_FirstFree;
+//CCallStack * CallStack_FirstUsed; 
+
+static int MemView_Active = 0;
+
+
+
+#define	MAX_CALLSTACK (8192 * 8)		// we got 8 mb, woo woo.
+
+
+static float step = 128.0f;
+
+
+static char HexByte(char a)
+{
+	if (a >= '0' && a <='9')
+	{
+		return a-'0';
+	}
+	if (a >= 'A' && a <='F')
+	{
+		return 10 + a-'A';
+	}
+	if (a >= 'a' && a <='f')
+	{
+		return 10 + a-'a';
+	}
+
+	// should really be an error, but just ignore it and return 0
+	// as this is only used for parsing the map file	
+	return 0;
+	
+	
+}
+
+
+static int doneonce = 0;
+
+
+char *MemView_GetFunctionName(int pc, int *p_size)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return "NULL";
+	}
+		
+	if (!pc)
+	{
+		return "NULL";
+	}
+				   
+	// given an address, return the name of the function
+	// does this by intially loading and buuilding a list of
+	// all the start points, and names, of all the functions
+	// by loading the skate3.map
+
+	
+	static int symbols = 0;
+	
+	if (!doneonce)
+	{
+	
+//		mdl.m_fd = sceOpen( "host:ctrl_out.dat", SCE_RDWR );
+///		sceRead( mdl.m_fd, mdl.m_recorded_data, 72000 * sizeof( Inp::RecordedData ));
+//		sceClose( mdl.m_fd );
+
+
+		char map_name[256];
+		sprintf (map_name,"host0:..\\build\\ngpsgnu\\%s.map", Config::GetElfName());
+		printf ("Map name = %s\n",map_name);
+
+		int map_file_handle;
+		map_file_handle= sceOpen(map_name, SCE_RDONLY);		
+
+		if (map_file_handle < 0)
+		{
+			return "(map file not loaded yet)";
+		}
+
+
+		doneonce = 1;
+	
+		char *pQB= _map_file_start ;
+		
+		//sceRead(map_file_handle,pQB,4000000);
+		long HedSize=sceLseek(map_file_handle, 0, SCE_SEEK_END);
+		sceLseek(map_file_handle, 0, SCE_SEEK_SET);
+		sceRead(map_file_handle,pQB,HedSize);
+		sceClose(map_file_handle);
+		// Now the file is loaded, we need to extract all the functions
+		// so, search for the text 
+		
+		char *p = strstr(pQB,"0");	// Find the first address
+		int	 *d = (int*)_symbols_start; 
+		while (*p)
+		{
+			// the next 8 characters are the address in lower case hex
+			int addr = 0;
+			for (int i=0;i<8;i++)
+			{
+				addr <<= 4;
+				addr += HexByte(*p++);
+			}
+			p+= 1;			// skip the space
+			
+			// the next 8 characters are the size in lower case hex
+			int size = 0;
+			for (int i=0;i<8;i++)
+			{
+				size <<= 4;
+				size += HexByte(*p++);
+			}
+			
+			// skip white spaces
+			while( *p == ' ' )
+			{
+				p++;
+			}
+			
+			int alignment = 0;
+			do
+			{
+				alignment <<= 4;
+				alignment += HexByte(*p++);
+			} while( *p != ' ' );
+
+			// skip white spaces
+			while( *p == ' ' )
+			{
+				p++;
+			}
+
+			// only store symbols of non-zero size
+			// otherwise, we get confused by having things like _bss_size in there
+			// as they are not addresses, they just look like them, being so big...
+			if (size || (addr >(int) __text_objend))
+			{
+				if( alignment == 0 )
+				{
+					*d++ = addr;		// store the address of the symbol
+					*d++ = (int)p;			// store the start of the symbol name
+					symbols++;		 	// one more symbol
+				}
+			}
+			
+			// search for first space, or CF, and replace with a 0
+			// that way we ignore the "unmangled" version of the function
+			while (*p && /**p!=' ' &&*/ *p!=0x0a && *p!='(' && *p != 0x0d) p++;	
+			*p++ = 0;
+			
+
+			// skip to LF, and replace the 			
+			while (*p && *p!=0x0a) p++;		// skip to start of next line
+			p++;						// skip over 0a, will now be at the space on next line
+		}
+
+		uint32 *p_top  =  (uint32*)_symbols_start;
+		for	(int i = 0;i pc)	 		// if this one is above the pc
+		{
+			*p_size = addr-s[-2];		// calculate the size of the function
+			return (char*) (s[-1]);		// then the previous one is the function
+		}
+		s += 2;	
+	}
+		 
+	return "UNKNOWN";
+	
+}
+
+// Track the call stack by looking the instructions to
+// see where the ra was stored on the stack
+// iMadDepth is the number of address to trace back
+// pDest is the location to put the results, whcih are stored as
+//	dw	ra,0
+// (not sure why the zero is there...)
+int DumpUnwindStack( int iMaxDepth, int *pDest )
+{
+	if (!Config::GotExtraMemory())
+	{
+		return 0;
+	}
+	
+	uint32* ra;
+	uint64* sp;											// frame pointer
+	ra = ((uint32*)DumpUnwindStack)+64;				// fake point in function to unwind from (
+														// after the sd ra,0(sp), but before getting it back
+	asm ( "daddu %0, $29, $0" : "=r" (sp) );			// get current sp
+
+	if (!pDest)						   	
+	{
+		printf("\n");
+	}
+		
+	int icd = iMaxDepth;	   							// depth counter
+	uint32* last_ra = NULL;
+	while ( icd-- )
+	{
+			/* scan instruction*/
+		uint32* pc = ra;								// current pc, somewehre in middle of function
+		uint32 count = 4096;							// enought to cover large functions	(16k)
+		while ( count-- )
+		{
+			uint32 ins = *pc;		 					// get 32 bit instruction
+			if (((ins >> 16) & 0x7fff) == 0x7fbf)		// sd ra,offset(sp)  (or sq, for .C files)
+			{
+				uint32 offset = *(short*)pc;			// get offset (bottom 16 bits)
+				ra = (uint32*)(sp[offset>>3]);					// >>3 as it's at 64 bit word pointer
+				break;
+			}
+			pc--;
+		}
+		while ( count--)
+		{
+			uint32 ins = *pc;		 					// get 32 bit instruction
+			if ((ins >> 16) == 0x27bd)					// addiu sp,sp,offset
+			{
+				int offset = *(short*)pc;				// get offset (bottom 16 bits)
+				if (offset & 0x8000)
+				{
+					offset |= 0xffff0000;
+				}
+				sp = (uint64*)( (int)(sp) - (offset));	   
+				break; 	
+			}			
+			pc--;
+		}
+
+//		if (last_ra == ra)
+//		{
+//			icd++;			// one more please....
+//		}
+//		else
+		{
+			last_ra = ra;
+			if (pDest)
+			{
+				*pDest++ = (int)ra;
+				*pDest = 0;
+			}
+			else
+			{
+				int size;
+	//			printf ("sp = %p, ra = %p %s\n",sp,ra,MemView_GetFunctionName((int)ra));
+				printf ("%p: %s\n",ra,MemView_GetFunctionName((int)ra,&size));
+			}
+		}
+	    
+		// test to see if we have recursed up all the way...
+		if (abs(int((int)ra - (int)&ENTRYPOINT)) < 1024
+		   || (int)ra &3 
+		   || (int)ra < 0x100000
+		   || (int)ra > (int)_code_end		// and check it's not totally crazy....
+		   )
+		{
+			return 0;
+		}
+	
+	}
+	return iMaxDepth - icd;
+}
+				   
+				   
+//        mD_L2           = nBit( vD_L2 ),
+//        mD_R2           = nBit( vD_R2 ),
+//        mD_L1           = nBit( vD_L1 ),
+//        mD_R1           = nBit( vD_R1 ),
+//        mD_TRIANGLE     = nBit( vD_TRIANGLE ),
+//	      mD_CIRCLE       = nBit( vD_CIRCLE ),
+//        mD_X            = nBit( vD_X ),
+//        mD_SQUARE       = nBit( vD_SQUARE ),
+//        mD_SELECT       = nBit( vD_SELECT ),
+//        mD_L3           = nBit( vD_L3 ),
+//        mD_R3           = nBit( vD_R3 ),
+//        mD_START        = nBit( vD_START ),
+//        mD_UP           = nBit( vD_UP ),
+//        mD_RIGHT        = nBit( vD_RIGHT ),
+//        mD_DOWN         = nBit( vD_DOWN ),
+//        mD_LEFT         = nBit( vD_LEFT ),
+
+
+void MemViewToggle()
+{
+	MemView_Active ^=1;
+}
+
+
+	 
+
+void MemView_Alloc( void *v)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+	
+#ifdef	__LINKED_LIST_HEAP__
+
+	Mem::Allocator::BlockHeader *p = (Mem::Allocator::BlockHeader *)v;	
+	
+	static int cleared = 0;
+	if (!cleared)
+	{
+		cleared = 1;
+		free_list.InitHead();
+		used_list.InitHead();
+		CCallStack *p = (CCallStack *)_callstack_start;		
+		for (int i=0;iRemove();
+	used_list.Append(c);
+
+	DumpUnwindStack(STACKDEPTH-1,c->addr);	   				// stick the call stack in there
+	c->size = p->mSize;
+
+	c->flags = 0;
+ 
+	p->mp_debug_data = (void*)c;		 					// and store it in the block header
+	c->pBlock = p;											// store pointer back
+	
+#endif
+}
+
+void MemView_Free( void *v)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+	
+#ifdef	__LINKED_LIST_HEAP__
+	// Need to remove it from the used list
+	// and add it back to the full list
+	
+	Mem::Allocator::BlockHeader *p = (Mem::Allocator::BlockHeader *)v;						  
+						  
+	CCallStack *c = (CCallStack*)p->mp_debug_data;	
+	
+	if (!c)
+	{
+		// no debug data, so probably a re-alloc
+		// should probably handle those later
+		return;
+	}
+
+	// we clear it, in case this header is re-used later 
+	// I'm not entirely sure how well this will work
+	p->mp_debug_data = NULL;	
+	c->Remove();
+	free_list.Append(c);
+	
+#endif	
+}
+
+
+Mem::Allocator::BlockHeader *MemView_FindBlock( int addr)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return NULL;
+	}
+
+#ifdef	__LINKED_LIST_HEAP__
+
+	
+	Mem::Allocator::BlockHeader *pSmallestBlock	= NULL;
+	uint32 smallest_block_size = 100000000;
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	for (Mem::Heap* heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+	{
+		Mem::Allocator::BlockHeader *pBlock = (Mem::Allocator::BlockHeader *) heap->find_block((void*)addr);	
+		if (pBlock)
+		{
+			if (pBlock->mSize < smallest_block_size)
+			{
+				smallest_block_size = pBlock->mSize;
+				pSmallestBlock = pBlock;
+			}
+		}
+	}
+	return pSmallestBlock;
+#else
+	return NULL;
+#endif
+}
+
+char * MemView_GetClassName(CCallStack *c)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return NULL;
+	}
+
+#ifdef	__LINKED_LIST_HEAP__
+	int *ra = (int*)(c->addr[4]);
+	if (!ra) return NULL;
+	int count = STACKDEPTH-4;
+	while (count--)
+	{
+		int instruction = *ra++;
+		if (instruction >> 24 == 0x0c)
+		{
+			int code = (instruction & 0xffffff)<<2;
+			int size;
+			char *p = MemView_GetFunctionName(code,&size); 
+			// to tell if this is class or not
+			// we see if the text is of the form  
+			//    classname::classname (teminated by a 0)
+			// as that indicates that it is a constructor
+			// dude... this is where we need a regular expression....
+			char *end = p;
+			while (*end) end++;	   						// scan to end
+			while (end[-1] != ':' && end > p)	end--;	// skip to char after the last :
+			char *other = strstr(p,end);				// find fist occurance of end of string
+			if (other != end)							// if different, then this is it!!
+			{
+				return MemView_GetFunctionName(code,&size);
+				break;
+			}
+		}
+	}
+#endif
+	return NULL;
+}
+
+
+void MemView_DumpBlockInfo(int cursor)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+
+#ifdef	__LINKED_LIST_HEAP__
+	
+	Mem::Allocator::BlockHeader *pBlock = MemView_FindBlock(cursor);
+	if (!pBlock)
+	{
+		// should search free blocks here???
+	}
+	// find this in the allocators used list
+	// and say if it is free, or not	
+	if (pBlock == NULL)
+	{
+		if (cursor > (int)__text_org && cursor < (int)__bss_objend)		// check to see if in code/data
+		{
+			
+			if (cursor < (int)__data_org)
+				printf("Code: ");
+			else if (cursor < (int)__rodata_org)
+				printf("Data: ");
+			else if (cursor < (int)__bss_org)
+				printf("RO-Data: ");
+			else 
+				printf("BSS: ");
+			
+		
+			int size;
+			char *p_name = MemView_GetFunctionName(cursor,&size);
+			printf ( "%s, size %d\n",p_name,size);
+		}
+		else
+		{
+			printf ("Block Not Found\n");
+		}
+	}
+	else
+	{
+		void * p_start = (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+		printf ("Block found, addr = %p, size = %d (Header = %d)\n",p_start,pBlock->mSize,Mem::Allocator::BlockHeader::sSize);
+		CCallStack *c = (CCallStack*)pBlock->mp_debug_data;
+		if (!c)
+		{
+			printf ("Block with No Debug Info!!\n");
+		}
+		else
+		{
+			// assume this is a "new", then the fourth callstack ra will point to the 
+			// jal xxxxxx  instruction, where xxxxx is the constructor for the 
+			// or it might be sortly thereafter, so check 16 instructions
+			
+			char * classname = MemView_GetClassName(c);
+						
+			if (classname)
+			{
+				printf ("CLASS: %s\n",classname);		
+			}
+
+			// then list out the call stack (skipping the MemView_Alloc, as that's a given, and irrelevant);			
+		
+			int *p = c->addr + 1;
+			while (p[1])			// also skip the ENTRYPOINT, just go back to main()
+			{
+				int size;
+				printf ("%p: %s\n",(void*)*p,MemView_GetFunctionName(*p,&size));
+				p++;
+			}
+		}
+	}
+#endif
+}
+
+static	int	blockstart;
+
+static float cursor;
+
+void 		MemView_Display()
+{
+#ifdef	__DEBUG_CODE__
+
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+
+	if (Config::CD())
+	{
+		return;
+	}	
+
+
+	if (!MemView_Active)
+	{
+		return;
+	}
+
+	FlushCache( 0 );
+	sceGsSyncPath( 0, 0 );
+	
+	//perfrom the copying
+	// there are 512x256 words in the rectangle
+	// and 32768*1024 bytes in memory
+	// giving us a step of 256 (i.e, sample every 256th bytes)
+	
+	
+	// The start of the middle line will be at 
+	//       start + 512 * 2 * 128 * step;
+	// then  start1 + 512 * 2 * 128 * step1
+	// for them to be the same, start + 512 * 2 * 128 * step = start1 + 512 * 2 * 128 * step1
+	// so start1 =  start + 512 * 2 * 128 * (step - step1)
+	
+	
+
+	blockstart = 0;
+	int blockend = 0;
+	
+	static float last_start;				 
+	
+	float start = cursor - (512.0f * 2.0f * 128.0f * step);
+	
+	int i_cursor = (int)cursor;
+	
+	Mem::Allocator::BlockHeader *pBlock = MemView_FindBlock(i_cursor);
+
+	if (pBlock)
+	{
+		blockstart = (int)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+		int size = pBlock->mSize;
+		blockend = (int)((int)blockstart + size);
+	}
+	
+	if (start != last_start)
+	{
+		last_start = start;
+		printf ("\nCursor Addr = %p\n",(void*)i_cursor);   	
+		MemView_DumpBlockInfo(i_cursor);
+	}
+	
+	
+	static int color = 10 + (10<<5) ;
+//	color ^= 5 << 10;
+	
+	float f_source = start;
+	float f_off = 0.0f;
+//	uint16 *source = (uint16*)(intstart&~1);   		// converting from a float to a pointer... yowza!!!
+	uint16 *dest  =  (uint16*)_mem_dump_start;
+	for (int i=0;i<512*256-4096;i++)
+	{
+		uint16 *source = (uint16*)((int)(f_source + f_off) &~1);		
+	
+		uint32 word;
+		if (source < (uint16*)0x00100000 || source >= (uint16*)(0x08000000))
+		{
+			word = (3<<10)+(3<<5)+(3);			// grey for outside of memory		
+		}
+		else
+		{
+			word = *source;
+			
+//			#if 0 		// only display the unused memor
+//			if (word !=0 && word !=0x5555)
+//			{
+//				word = 0x8;		// a used word, set to some dark color
+//			}
+//			#endif
+			
+			if (blockstart && (int)(source)>=blockstart && (int)(source)  (uint32)_code_end /*&& x < (uint32)_std_mem_end*/) // don't check for end now, as we have some debug heaps up there we want to include
+		{
+			// check to see if it points to one of the heap members
+			
+			uint32 *p_refs = (uint32*)_mem_dump_start;
+			
+		#if 0
+			for (int i=0;i x)
+					{
+						high = mid;
+					}
+					else
+					{
+						// if the low point is already the same as the mid point
+						// then the only way to go is up!
+						// as this will only occur when low + 1 == high
+						if (low == mid)
+						{
+							low = high;
+						}
+						else
+						{
+							low = mid;
+						}
+					}				
+				}
+				x -= 16;
+			}
+			x = oldx;
+		#endif						  		
+		}
+	}
+	
+	
+}
+#endif
+
+#ifdef	__LINKED_LIST_HEAP__
+static uint32 *p_used;		
+#endif
+
+int MemView_CountBlocks(Mem::Allocator::BlockHeader *p_header)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return 0;
+	}
+
+#ifdef	__LINKED_LIST_HEAP__
+	int num_used = 0;
+	while ( p_header )
+	{
+		void * p_start = (void*)((uint)p_header + Mem::Allocator::BlockHeader::sSize);
+		
+		*p_used++ = (uint32)p_start;	 		// store the start of the block
+		*p_used++ = 0;							// store a count
+		p_header = p_header->mp_next_used;
+		num_used++;
+	}
+	return num_used;
+#else
+	return 0;
+#endif
+}
+
+
+int blockCompFunc( const void *arg1, const void *arg2 )
+{
+	uint32 addr1 = (*(uint32*)arg1);
+	uint32 addr2 = (*(uint32*)arg2);
+
+	if ( addr1 == addr2 )
+	{
+		return 0;
+	}
+	else if ( addr1 < addr2 )
+	{
+		return 1;
+	}
+	else
+	{
+		return -1;
+	}
+
+}
+
+
+// Find memory leaks
+// the algorithm is quite simple:
+// 1) Make a list of all "used" memory blocks, and set their usage count to 0
+// 2) Scan all of the heap, and the stack, for each word that looks like a pointer, 
+//    check to see if it is in the list of "used", and increment the usage count if so
+// 3) Scan the list of used pointers, and check for any with usage == 0
+
+void MemView_FindLeaks()
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+	
+#ifdef	__LINKED_LIST_HEAP__
+		p_used  =  (uint32*)_mem_dump_start;		
+		num_used = 0;
+		printf ("Counting blocks....");		
+  		Mem::Heap * p_heap = Mem::Manager::sHandle().FirstHeap();
+		while (p_heap)
+		{
+			num_used += MemView_CountBlocks(p_heap->first_block()); 
+			p_heap = Mem::Manager::sHandle().NextHeap(p_heap);
+		}
+
+		printf (" %d\n",num_used);
+		printf ("Sorting .....\n");			
+		// Now we've done that, let's sort the list, so we can use a binary search later
+		
+		
+		#if 1
+		uint32 *p_top  =  (uint32*)_mem_dump_start;		
+		for	(int i = 0;i= 10)
+		{
+			printf ("Stopping after %d refs\n",count);
+			return;
+		}
+		if (p_start >= p_end)
+		{
+			printf ("No more References Found in heap \n");
+			return;
+		}
+	}
+#endif
+}
+
+// Find the first block in the free list
+// if no free blocks, then return
+// scan all used blocks, and print out the info for all the blocks
+// that have an address above the first free block
+void MemView_DumpFragments(Mem::Heap *pHeap)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+
+	#ifdef	__LINKED_LIST_HEAP__    
+
+	if (!pHeap->mFreeBlocks.m_count)
+	{
+		printf ("NO Fragmentation\n");
+		return;
+	}
+	
+	if (!pHeap->mp_context->mp_free_list)
+	{
+		printf ("!!!!!! No free list, but there are %d free blocks???\n",pHeap->mFreeBlocks.m_count);
+		return;
+	}
+
+	Mem::Allocator::BlockHeader *p_free = pHeap->mp_context->mp_free_list;
+	
+	while (p_free->mSize < 10000)
+	{
+		Mem::Allocator::BlockHeader *p_next = p_free->mpNext;
+		if (!p_next)
+		{
+			printf ("Did not find a free block >10K ?????\n");
+			return;
+		}		
+		p_free = p_next;
+	}
+	
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+	
+	printf ("!!!!!! Free list starts at %p\n",p_free);
+	
+
+	// The first p_free will be the start of fragmentations
+	while (p_full)
+	{
+		if (p_full > p_free)
+		{
+			printf ("\nFramgented Block\n\n");
+			void * p_start = (void*)((uint)p_full + Mem::Allocator::BlockHeader::sSize);
+			MemView_DumpBlockInfo((int)p_start);
+			for (int xx=0;xx<1000000;xx++);		// little delay, to allow printfs to work
+		}
+		p_full = p_full->mp_next_used;
+	}
+	#endif
+}
+
+void MemView_DumpHeap(Mem::Heap *pHeap, uint32 mask)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+
+	#ifdef	__LINKED_LIST_HEAP__    
+
+//	Mem::Allocator::BlockHeader *p_free = pHeap->mp_context->mp_free_list;
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+	
+	// The first p_free will be the start of fragmentations
+	while (p_full)
+	{
+//		if (p_full > p_free)
+//		CCallStack *c = (CCallStack*)p_full->mp_debug_data;	
+//		if (!mask || !c || !(c->flags && mask))
+		{
+			printf ("\n");
+			void * p_start = (void*)((uint)p_full + Mem::Allocator::BlockHeader::sSize);
+			MemView_DumpBlockInfo((int)p_start);
+		}
+		p_full = p_full->mp_next_used;
+	}
+	#endif
+}
+
+
+
+void MemView_DumpBottomFragments()
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+
+	MemView_DumpFragments(Mem::Manager::sHandle().BottomUpHeap());
+}
+
+void MemView_DumpTopFragments()
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+
+	MemView_DumpFragments(Mem::Manager::sHandle().TopDownHeap());
+}
+
+
+
+/*
+class CCallStack
+{
+public:
+	void Append(CCallStack *p);
+	void Remove();
+	void InitHead();
+	int	used;
+	int	size;
+	CCallStack *pNext;
+	CCallStack *pPrev;
+	int	addr[STACKDEPTH];
+
+};
+*/
+
+struct SBlockType
+{
+	int	return_addr; 			// first meaningful return addr 
+	
+	int	size;					// size of block (if we want to sort by it
+	int	total;					// total size of this type
+	int	actual;					// actual total size, including headers
+	char *p_class;				// points to class node 
+	
+	int	count;
+};
+
+// scan throught the list of "used" blocks
+// and sort them into a list, organized by "type"
+// the "type" is determined by the first return address after
+// a callstack entry that is either "Malloc" or "Spt::Class::operator new"
+// the "type" is furthur sorted by either "size" or "Class"
+// where "size" is the size of the block (for a Malloc)
+// and "Class" is the type of class that constructed this block 
+
+#define	MAX_TYPES 10000
+
+
+void MemView_DumpAnalysis( SBlockType* blocks, int numBlocksToPrint )
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+	
+#ifdef	__LINKED_LIST_HEAP__
+	// Sorts the types, and print out totals
+
+	int temp;
+
+	for (int i = 0; i < numBlocksToPrint; i++)
+	{
+		for (int j = i+1;jsize;	  	// size is the only thing we know for sure
+	int	return_addr = 0;				// default unknown return address
+	char *p_class = "not a class";
+	int latest = 1;
+	int i = 0;
+	
+	for ( i = 1; i < 8; i++ )
+	{
+		int xsize;
+
+		/*
+		// the types of call stack we may encounter:
+		// need to 
+			0x10be48: Mem::Heap::allocate                                                   
+			0x109914: Mem::Manager::New                                                     
+			0x1035b0: Spt::Class::operator new                                              
+			0x161094: Front::KeyboardControl::sCreateInstance   
+
+		*/
+
+		char *p_name = MemView_GetFunctionName(pCallStack->addr[i],&xsize);
+		if (!strcmp("Malloc",p_name) 
+			|| !strcmp("Spt::Class::operator new",p_name)
+			|| !strcmp("Mem::Manager::New",p_name)
+			|| !strcmp("Mem::AllocRegion::AllocRegion",p_name)
+			|| !strcmp("ReallocateShrink",p_name)
+			|| !strcmp("Mem::Manager::ReallocateShrink",p_name)
+			|| !strcmp("ReallocateDown",p_name)
+			|| !strcmp("Pip::Load",p_name)
+			|| !strcmp("Pip::LoadPre",p_name)
+			|| !strcmp("Calloc",p_name)
+			
+			)
+		{
+			latest = i;
+		}
+	}
+
+	if (latest != 1)
+	{
+		return_addr = pCallStack->addr[latest+1];
+	}
+
+	p_class = MemView_GetClassName(pCallStack);				// get class
+	// right, now we have all the info on this block
+	// let's see if we've got one just like it
+
+	//		if (!p_class && !MemView_GetFunctionName(return_addr,&temp))
+	/*		
+			if (!return_addr)
+			{
+				for (int i = 0;i>%s<<\n",i,MemView_GetFunctionName(p->addr[i],&temp));
+
+				}
+				return;
+
+			}
+	*/
+	
+									  
+	// check if it is a string, and print it out, if so					
+
+	
+	int temp;				  
+	if (!strcmp("Str::String::copy",MemView_GetFunctionName(return_addr,&temp)))
+	{
+		printf ("Str::String   <%s>\n",(char*)((char*)(pCallStack->pBlock)+32)); 
+		mem_strings++;
+	}	
+	else if (!strcmp("Script::CreateString",MemView_GetFunctionName(return_addr,&temp)))
+	{
+		printf ("Script String <%s>\n",(char*)((char*)(pCallStack->pBlock)+32)); 
+		mem_strings++;
+	}
+
+	
+	
+
+	// Analyze the contents of the block
+	// counting zero, and unused (0x555555) words
+//	uint32 *p
+
+
+	int dummy;
+	// check to see if this block is already included
+	for ( i = 0; i < num; i++ )
+	{
+		//int diff = pBlocks[i].return_addr - 
+		if ( /* pBlocks[i].p_class == p_class*/
+			 /*&& pBlocks[i].size == size */
+			 /*&& pBlocks[i].return_addr == return_addr*/
+			 // just check if allocated by the same function
+			 
+			 MemView_GetFunctionName(pBlocks[i].return_addr,&dummy) == MemView_GetFunctionName(return_addr,&dummy)
+			  
+			  )
+		{
+			pBlocks[i].count++;
+			pBlocks[i].total += size;
+			pBlocks[i].actual += size + Mem::Allocator::BlockHeader::sSize;
+			break;				
+		}
+	}
+	
+	if (return_addr == NULL)
+	{
+		
+		printf ("\nUntraceable Block");   	
+		MemView_DumpBlockInfo(((int)(pCallStack->pBlock)+32));	
+	}
+	
+
+	// if not, then add the block
+	if ( i == num )
+	{
+		pBlocks[i].p_class = p_class;
+		pBlocks[i].size = size;
+		pBlocks[i].total = size;
+		pBlocks[i].actual = size + Mem::Allocator::BlockHeader::sSize;
+		pBlocks[i].return_addr = return_addr;
+		pBlocks[i].count = 1;
+		num++;
+	}
+}
+
+#ifdef	__LINKED_LIST_HEAP__
+static int bbb = 0;	   	// compiler patch var, see below
+#endif
+
+void MemView_AnalyzeBlocks(uint32 mask)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+	
+#ifdef	__LINKED_LIST_HEAP__
+	SBlockType  *pBlocks = (SBlockType  *)_mem_dump_start;	// temp memory
+	int	num_blocks = 0;
+	int num = 0;
+
+	printf ("\nAnalyzing blocks....\n");
+	
+	mem_strings = 0;
+					
+	CCallStack *p = used_list.pNext;  
+	while (p != &used_list)
+	{
+		// Get the actualy block we referred to
+//		Mem::Allocator::BlockHeader * pBlock = p->pBlock;
+//		void * p_start = (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+		// Otionally check to see if it on the front end heap
+//		if (Mem::SameContext(p_start,Mem::Manager::sHandle().FrontEndHeap()))
+		{
+			if (!mask || !(p->flags & mask))
+			{
+				MemView_AnalyzeCallStack( p, pBlocks, num );			
+				num_blocks++;
+			}
+		}
+		p = p->pNext; 
+	}
+	printf("\n  THERE ARE %d STRINGS (above) \n\n",mem_strings);
+	
+	
+	printf ("%d types, in %d total blocks\n", num, num_blocks); 
+
+	MemView_DumpAnalysis( pBlocks, num );
+	if (bbb)
+	{
+		MemView_DumpBottomFragments();			// just to get it compiling 
+		MemView_DumpTopFragments();			// just to get it compiling 
+	}		
+#endif
+}
+
+
+void MemView_MarkBlocks(uint32 mask)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+	
+#ifdef	__LINKED_LIST_HEAP__
+
+	CCallStack *p = used_list.pNext;  
+	while (p != &used_list)
+	{
+		p->flags |= mask;
+		
+		p = p->pNext;
+	}
+#endif
+}
+
+
+
+void MemView_Input(uint buttons, uint makes, uint breaks)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+
+	if (Config::CD())
+	{
+		return;
+	}	
+
+//	if (makes & Inp::Data::mD_TRIANGLE)
+//	{
+//		MemView_Active = !MemView_Active;
+//	}
+
+
+	if (!MemView_Active)
+	{
+		// Stupid hook up of scaling buttons for overhead view
+
+		#ifdef	__DEBUG_CODE__  	
+		if (buttons & Inp::Data::mD_CIRCLE)
+			Nx::_debug_change_2d_scale(1.11);
+		if (buttons & Inp::Data::mD_SQUARE)
+			Nx::_debug_change_2d_scale(0.9);
+		#endif		
+	
+	
+	
+		return;
+	}
+
+					  
+	float step1 = step;
+	
+	float zoom = 1.1f;
+	
+	float scroll = 4.0f;
+
+	
+
+
+
+	if (buttons & Inp::Data::mD_LEFT)
+	{
+		step1 = step * zoom;
+	}
+	if (buttons & Inp::Data::mD_RIGHT)
+	{
+		step1 = step / zoom;
+	}
+	
+	if (buttons & Inp::Data::mD_UP)
+	{
+//		start = start - scroll * 512.0f * 2.0f * step;
+		cursor = cursor - scroll * 512.0f * 2.0f * step;
+	}
+	if (buttons & Inp::Data::mD_DOWN)
+	{
+//		start = start + scroll * 512.0f * 2.0f * step;
+		cursor = cursor + scroll * 512.0f * 2.0f * step;
+	}
+
+	if (buttons & Inp::Data::mD_L1)
+	{
+//		start = start - scroll * 512.0f * 2.0f * step / 256.0f;
+		cursor = cursor - scroll * 2.0f * 2.0f * step;
+	}
+	if (buttons & Inp::Data::mD_L2)
+	{
+//		start = start + scroll * 512.0f * 2.0f * step / 256.0f;
+		cursor = cursor + scroll * 2.0f * 2.0f * step;
+	}
+
+#define 	MMMIN 	(0.0078125f)
+	 				
+	if (step1 1024.0f)
+	{
+		step1 = 1024.0f;
+	}
+
+//	start = start + (512.0f * 2.0f * 128.0f * (step - step1));
+
+	step = step1;
+
+	if (makes & Inp::Data::mD_CIRCLE)
+	{
+		if (blockstart)
+		{
+			MemView_DumpRefs(blockstart);
+		}
+//		MemView_MarkBlocks(1);
+	}
+
+	// We don't look for leaks automatically now, so I'v put it on "SQUARE"	
+	if (makes & Inp::Data::mD_SQUARE)
+	{
+		MemView_FindLeaks();
+//	Mem::Manager& mem_man = Mem::Manager::sHandle();		MemView_DumpHeap(1);
+//	heap	= mem_man.TopDownHeap();
+//	MemView_DumpFragments(heap);
+//	MemView_DumpHeap(heap,1);
+
+	}
+
+	if (makes & Inp::Data::mD_X)
+	{
+//		MemView_AnalyzeBlocks();
+	}
+
+	// Triangle = Dump Fragmentation
+/*	if (makes & Inp::Data::mD_TRIANGLE)
+	{
+		Mem::Manager& mem_man = Mem::Manager::sHandle();
+		Mem::Heap* heap = mem_man.BottomUpHeap();
+		Mem::Region* region = heap->ParentRegion();
+		printf ("BottomUp Frag %dK, %d Blocks\n",heap->mFreeMem.m_count / 1024, heap->mFreeBlocks.m_count);
+		printf ("Region %d/%d K", region->MemAvailable() / 1024, region->TotalSize() / 1024 );
+		MemView_DumpFragments(heap);
+	}
+*/	
+
+}
+
+void MemView_AnalyzeHeap(Mem::Heap *pHeap)
+{
+	if (!Config::GotExtraMemory())
+	{
+		return;
+	}
+	
+	if ( !pHeap )
+		return;
+	
+#ifdef	__LINKED_LIST_HEAP__
+	SBlockType  *pBlocks = (SBlockType  *)_mem_dump_start;	// temp memory
+	int	num_blocks = 0;
+	int num = 0;
+
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+
+	while (p_full)
+	{
+		CCallStack* pCallStack = (CCallStack*)p_full->mp_debug_data;
+		
+		if ( pCallStack )
+		{
+			MemView_AnalyzeCallStack( pCallStack, pBlocks, num );
+		}
+		else
+		{
+			printf ("Block with No Debug Info!!\n");
+		}
+
+		p_full = p_full->mp_next_used;
+	}
+	printf("\n  THERE ARE %d STRINGS (above) \n\n",mem_strings);
+
+	printf ("%d types, in %d total blocks\n", num, num_blocks); 
+	
+	MemView_DumpAnalysis( pBlocks, num );
+#endif
+}
+  
+  
+
diff --git a/Code/Gfx/NGPS/p_memview.h b/Code/Gfx/NGPS/p_memview.h
new file mode 100644
index 0000000..3d21e42
--- /dev/null
+++ b/Code/Gfx/NGPS/p_memview.h
@@ -0,0 +1,21 @@
+////
+
+#ifndef	__P_MEMVIEW_H__
+#define	__P_MEMVIEW_H__
+
+void MemView_Display();
+void MemView_Input(uint buttons, uint makes, uint breaks);
+void MemView_Alloc( void *v);
+void MemView_Free( void *v);
+void MemViewToggle();
+void MemView_FindLeaks();
+int DumpUnwindStack( int iMaxDepth, int *pDest );
+char *MemView_GetFunctionName(int pc, int *p_size);
+void MemView_DumpFragments(Mem::Heap *pHeap);
+void MemView_AnalyzeBlocks(uint32 mask = 0);
+void MemView_MarkBlocks(uint32 flags = 1 );
+void MemView_DumpHeap(Mem::Heap *pHeap, uint32 mask =0);
+void MemView_AnalyzeHeap(Mem::Heap *pHeap);
+
+#endif
+
diff --git a/Code/Gfx/NGPS/p_nx.cpp b/Code/Gfx/NGPS/p_nx.cpp
new file mode 100644
index 0000000..12cbd01
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nx.cpp
@@ -0,0 +1,891 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_nx.cpp - PS2 platform specific interface to the engine
+//
+// This is PS2 SPECIFIC!!!!!!  So might get a bit messy
+//
+// If there is anything in here that is not PS2 specific, then it needs to 
+// be in nx.cpp
+
+#include	"gfx\nx.h"
+#include	"gfx\NxViewMan.h"
+#include	"gfx\NxMiscFX.h"
+#include	"gfx\NxParticleMgr.h"
+#include	"gfx\NxQuickAnim.h"
+#include	"gfx\NGPS\p_NxGeom.h"
+#include	"gfx\NGPS\p_NxLightMan.h"
+#include	"gfx\NGPS\p_NxMesh.h"
+#include	"gfx\NGPS\p_NxModel.h"
+#include	"gfx\NGPS\p_NxSector.h"
+#include	"gfx\NGPS\p_NxScene.h"
+#include	"gfx\NGPS\p_NxSprite.h"
+#include	"gfx\NGPS\p_NxTexture.h"
+#include	"gfx\NGPS\p_NxTextured3dPoly.h"
+#include	"gfx\NGPS\p_NxNewParticleMgr.h"
+#include	"core\math.h"
+#include    "gel\collision\collision.h"
+
+#include 	"gfx\ngps\nx\nx_init.h"
+#include 	"gfx\ngps\nx\material.h"
+#include 	"gfx\ngps\nx\mesh.h"
+#include 	"gfx\ngps\nx\occlude.h"
+#include 	"gfx\ngps\nx\scene.h"
+#include 	"gfx\ngps\nx\chars.h"
+#include 	"gfx\ngps\nx\render.h"
+#include 	"gfx\ngps\nx\geomnode.h"
+#include 	"gfx\ngps\nx\immediate.h"
+#include 	"gfx\ngps\p_nxweather.h"
+#include 	"gfx\ngps\nx\switches.h"
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace NxPs2
+{
+extern void	test_render_start_frame();
+extern void	test_render(Mth::Matrix* camera_orient, Mth::Vector* camera_pos,  float view_angle, float screen_aspect);
+extern void	test_render_end_frame();
+extern void	test_init();			
+extern bool IsVisible(Mth::Vector ¢er, float radius);
+extern void	WaitForRendering();
+extern uint32	*p_patch_ALPHA;
+extern uint32	*p_patch_ALPHA2;
+} // namespace NxPs2
+
+
+namespace	Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CEngine::s_plat_start_engine()
+{
+	NxPs2::InitialiseEngine();
+	mp_particle_manager = new CPs2NewParticleManager;
+	Nx::CPs2Texture::sInitTables();
+	mp_weather = new CPs2Weather;
+}
+
+
+void 		CEngine::s_plat_pre_render()
+{
+// Moved to StuffAfterGSFinished
+//	CPs2LightManager::sUpdateEngine();
+//
+//	// particle systems effectively have their transform double-buffered for dma purposes
+//	// so now is the time to do the buffer flip by updating the transforms
+//	mp_particle_manager->UpdateParticles();
+
+	NxPs2::test_render_start_frame();
+}
+
+void 		CEngine::s_plat_post_render()
+{
+	// bit of a hack, see below
+//	Gfx::Camera *cur_camera = CViewportManager::sGetCamera( 0 );
+//	if (cur_camera)
+	{
+
+		
+		
+		NxPs2::test_render_end_frame();
+	}
+}
+
+//void shadow_test( float * p_sn, uint16 * p_index_list, int count, float * dir )
+//{
+//	// Test...
+//
+//	asm __volatile__(
+//		"
+//		.set noreorder
+//
+//		lqc2	vf09, 0x0(%1)		# light dir
+//
+//		lqc2	vf10, 0x0(%0)		# surface normal
+//		addi	%2, %2, -1
+//
+//dot_loop:
+//		vmul.xyz	vf13,	vf09,	vf10	# result = plane * center + plane_w [start]
+//		addiu		%0,		%0,		12
+//		lqc2		vf10,	0x4(%0)			# surface normal
+//		vaddy.x		vf13,	vf13,	vf13y
+//		vaddz.x		vf13,	vf13,	vf13z
+//
+//		qmfc2		$8,		vf13
+//		sw			$8,		0x0(%0)
+//		addiu		%0,		%0,		4
+//
+//		bne			%2,		$0,		dot_loop
+//		addi		%2,		%2,		-1
+//done:
+//
+//		.set reorder
+//		": : "r" (p_sn), "r" (dir), "r" (count) : "$8" ); 
+////
+////		": "=r" (p_dot) : "r" (p_sn) : "$8", "$9", "$10");
+//	
+//
+////#		lhu		$8, 0(%2)
+////#		lhu		$9, 2(%2)
+////#		lhu		$10, 4(%2)
+////#		addiu	%2, %2, 6
+////#
+////#		add		$8, $8, %1
+////#		add		$9, $9, %1
+////#		add		$10, $10, %1
+////#		lqc2	vf10, 0x0($8)		# p0
+////#		lqc2	vf11, 0x0($8)		# p1
+////#		lqc2	vf12, 0x0($8)		# p2
+//
+//}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CEngine::s_plat_render_world()
+{
+	NxPs2::RenderPrologue();	 	// should this be in test_render_start_frame? (Garrett: Answer is no, it won't work)
+	NxPs2::RenderInitImmediateMode();
+
+
+//#define NUM_TRIS 1851
+////	float	xyz[(NUM_TRIS+2)*3];
+//	float	sn[NUM_TRIS*4];
+//	uint16	list[NUM_TRIS*3];
+//	float	vec[3] = { 1.0f, 1.5f, 2.0f };
+//
+//	for ( int lp = 0; lp < NUM_TRIS; lp++ )
+//	{
+//		sn[(lp*4)+0] = 6.0f;
+//		sn[(lp*4)+1] = 7.0f;
+//		sn[(lp*4)+2] = 8.0f;
+//
+////		xyz[(lp*3)+0] = ((float)(lp/2));
+////		xyz[(lp*3)+1] = ((float)(lp&1));
+////		xyz[(lp*3)+2] = 0.0f;
+//
+//		list[(lp*3)+0] = ( lp + 0 ) * 12;
+//		list[(lp*3)+1] = ( lp + 1 ) * 12;
+//		list[(lp*3)+2] = ( lp + 2 ) * 12;
+//	}
+//
+//	Tmr::CPUCycles cycles = Tmr::GetTimeInCPUCycles();
+//	shadow_test( sn, list, NUM_TRIS, vec );
+//	cycles = Tmr::GetTimeInCPUCycles() - cycles;
+//	printf( "CPU Cycles for shadow test: %d\n", cycles );
+
+	#if 0
+	// (Mike) see if there are any viewports to render, and return if there aren't
+	// (Mike) ...perhaps make this trivial by having a CViewportManager::sGetNumActiveCameras() ?
+	bool no_viewports = true;
+	for (int view_idx = 0; view_idx < CViewportManager::sGetNumActiveViewports(); view_idx++)
+	{
+		if (CViewportManager::sGetActiveViewport(view_idx)->GetCamera())
+		{
+			no_viewports = false;
+			break;
+		}
+	}
+	if (no_viewports)
+	{
+		return;
+	}
+	// (Mike) now we know there's something to render...
+	#endif
+
+
+/*
+	#if 1
+	// normal 4:3 TV	
+	float view_angle = 72.0f;   			// The angle prepended by the width of the screen
+	float screen_aspect = 4.0f/3.0f;		// physical ration of width to height on the screen	(4:3 or 16:9)
+	#else 
+	// anamorphic widescreen
+	float view_angle = 80.0f;   			// The angle prepended by the width of the screen
+	float screen_aspect = 16.0f/9.0f;	// physical ratio of width to height on the screen	(4:3 or 16:9)
+	#endif
+*/
+//	CViewportManager::sSetScreenMode(vSPLIT_V);
+	
+	
+
+	bool got_vu0 = NxPs2::OccludeUseVU0();
+	Dbg_Assert(got_vu0);
+	
+	// Note: this method of setting up the camera must change
+	// so that the p_nx module does not reference things higher up the hierarchy
+	for (int view_idx = 0; view_idx < CViewportManager::sGetNumActiveViewports(); view_idx++)
+	{
+		Gfx::Camera *cur_camera;
+		CViewport *p_cur_viewport = CViewportManager::sGetActiveViewport(view_idx);
+		
+		Dbg_MsgAssert(p_cur_viewport,("Unable to get viewport %d",view_idx));
+		cur_camera = p_cur_viewport->GetCamera();
+		
+		if (cur_camera)
+		{
+			
+			// Build the occluders first now that we know where the camera is for this frame.
+			NxPs2::BuildOccluders( &( cur_camera->GetPos()));
+
+			// Make camera matrix
+			Mth::Matrix cam_matrix(cur_camera->GetMatrix());
+			cam_matrix[W] = cur_camera->GetPos();
+
+			// Garrett: For some reason, only these clipping values work
+			NxPs2::RenderViewport(view_idx, cam_matrix, p_cur_viewport->GetRect(), cur_camera->GetAdjustedHFOV(), p_cur_viewport->GetAspectRatio(),
+								  /*-cur_camera->GetNearClipPlane(), -cur_camera->GetFarClipPlane()*/ -1.0f, -100000.0f, s_render_mode > 1);
+#	ifdef __USE_PROFILER__
+			Sys::CPUProfiler->PushContext( 0, 0, 255 );	 	// Blue (Under Yellow) = Immediate mode rnedering
+#	endif // __USE_PROFILER__
+
+			// Draw the immediate mode stuff
+			NxPs2::RenderSwitchImmediateMode();
+			NxPs2::CImmediateMode::sViewportInit();
+
+// Mick: Moved this here from test_render_end_frame
+			NxPs2::Reallocate2DVRAM();
+
+			if (NxPs2::FlipCopyEnabled()) // TT 13470, 13471, 11061 Particle systems displayed when screen is blank
+			{
+			
+				Nx::render_particles();
+				mp_particle_manager->RenderParticles();
+	
+				// Render Weather effects.
+				mp_weather->Process( Tmr::FrameLength() );
+				mp_weather->Render();
+	
+				//Nx::CTextured3dPoly::sRenderAll();
+				TextureSplatRender();
+				ShatterRender();			// Make sure this is always the last immediate draw since it switches DMA lists
+			}
+						
+			NxPs2::RenderSwitchImmediateMode();
+
+#	ifdef __USE_PROFILER__
+			Sys::CPUProfiler->PopContext(  );	 
+#	endif // __USE_PROFILER__
+
+			if (s_render_mode)
+			{
+#ifdef __NOPT_ASSERT__
+				for (int i = 0; i < MAX_LOADED_SCENES; i++)
+				{
+					if (sp_loaded_scenes[i])
+					{
+						sp_loaded_scenes[i]->DebugRenderCollision(s_debug_ignore_1, s_debug_ignore_0);
+					}
+				}
+#endif
+
+				const Lst::Head& composite_obj_list = Nx::CEngine::sGetMovableObjects();
+				Lst::Node* p_composite_node = composite_obj_list.GetNext();
+				while( p_composite_node )
+				{
+					Obj::CCompositeObject* p_composite_object = static_cast(p_composite_node->GetData());
+					Dbg_MsgAssert(p_composite_object, ("Node in CObject list wasn't a CCompositeObject"));
+
+					Obj::CCollisionComponent* p_collision_component = GetCollisionComponentFromObject( p_composite_object );
+					if ( p_collision_component )
+					{
+						CCollObj* p_coll_obj = p_collision_component->GetCollision();
+						if (p_coll_obj)
+						{
+							p_coll_obj->DebugRender( s_debug_ignore_1, s_debug_ignore_0 );
+						}
+					}
+
+					p_composite_node = p_composite_node->GetNext();
+				}
+			}	
+		}
+		else
+		{
+			//printf ("RENDERING FRAME WITH NO CAMERA (Nothing rendered)\n");
+		}
+		
+		// Screen flash rendering.
+		Nx::ScreenFlashRender( view_idx, 0 );
+	}
+
+	//NxPs2::RenderEpilogueStart();
+	
+	if (got_vu0)
+	{
+		NxPs2::OccludeDisableVU0();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene	*	CEngine::s_plat_create_scene(const char *p_name, CTexDict *p_tex_dict, bool add_super_sectors)
+{
+	// Create scene class instance
+	CPs2Scene *p_ps2_scene = new CPs2Scene;
+
+	NxPs2::CGeomNode *p_geomnode = new NxPs2::CGeomNode;
+	p_geomnode->AddToTree(LOADFLAG_RENDERNOW);
+	p_geomnode->SetActive(true);
+	p_ps2_scene->SetEngineCloneScene(p_geomnode);
+
+	CScene *new_scene = p_ps2_scene;
+	new_scene->SetInSuperSectors(add_super_sectors);
+	new_scene->SetIsSky(false);
+
+	return new_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene *CEngine::s_plat_load_scene_from_memory( void *p_mem, CTexDict *p_tex_dict, bool add_super_sectors, bool is_sky, bool is_dictionary )
+{
+	// GJ:  This is a stub function that should never be called by the PS2.
+	// Currently, it's only needed by the X-box and the NGC
+	// because all models are treated as "scenes" on those platforms.
+	// On the PS2, models are treated as CPS2Mesh-es...
+
+	Dbg_MsgAssert( 0, ( "This function is not supported on the PS2 right now" ) );
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene	*	CEngine::s_plat_load_scene(const char *p_name, CTexDict *p_tex_dict,
+									   bool add_super_sectors, bool is_sky, bool is_dictionary)
+{
+	// Create scene class instance
+	CPs2Scene *p_ps2_scene = new CPs2Scene;
+	CScene *new_scene = p_ps2_scene;
+	new_scene->SetInSuperSectors(add_super_sectors);
+	new_scene->SetIsSky(is_sky);
+
+	#ifdef __NOPT_ASSERT__
+	CPs2TexDict *p_ps2_tex_dict = static_cast(p_tex_dict);
+	Dbg_MsgAssert(p_ps2_tex_dict,("Invalid texture dictionsary for %s",p_name));
+	#endif
+	
+	// load scene
+	// Pip::Load the new file
+	uint8 *p_pipData = (uint8*)Pip::Load(p_name);
+
+	// 1st section is shadow volume data.
+//	p_ps2_scene->mp_shadow_volume_header = (Nx::sShadowVolumeHeader*)p_pipData;
+//	p_ps2_scene->mp_shadow_volume_header->p_vertex = (Nx::sShadowVertex *)&p_ps2_scene->mp_shadow_volume_header[1];
+//		p_ps2_scene->mp_shadow_volume_header->p_connect = (Nx::sShadowConnect *)&p_ps2_scene->mp_shadow_volume_header->p_vertex[p_ps2_scene->mp_shadow_volume_header->num_verts];
+//	p_ps2_scene->mp_shadow_volume_header->p_neighbor = (Nx::sShadowNeighbor *)&p_ps2_scene->mp_shadow_volume_header->p_connect[p_ps2_scene->mp_shadow_volume_header->num_faces];
+//
+//	uint8 * p_geom_data = (uint8*)p_ps2_scene->mp_shadow_volume_header->p_vertex;
+//	p_geom_data = &p_geom_data[p_ps2_scene->mp_shadow_volume_header->byte_size];
+	// PJR
+	// Process my data here.
+
+	// set up load flags
+	uint32 load_flags = 0;
+	if (!is_dictionary)
+	{
+		// render it unconditionally as opposed to holding in a database to be instanced later)
+		load_flags |= LOADFLAG_RENDERNOW;
+	} else {
+		Dbg_MsgAssert(!add_super_sectors, ("Why would you need collision SuperSectors for a scene dictionary?"));
+	}
+	if (is_sky)
+	{
+		load_flags |= LOADFLAG_SKY;
+	}
+
+	// process data in place
+	NxPs2::CGeomNode *p_geomnode = NxPs2::CGeomNode::sProcessInPlace(p_pipData, load_flags);
+//	NxPs2::CGeomNode *p_geomnode = NxPs2::CGeomNode::sProcessInPlace(p_geom_data, load_flags);
+	
+	Dbg_MsgAssert(((int)(p_geomnode) & 0xf) == 0,("p_geomnode (0x%x) not multiple of 16 after sProcessInPlace\n"
+													,(int)p_geomnode));
+
+	
+	p_ps2_scene->SetEngineScene(p_geomnode);
+
+	return new_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_add_scene(CScene *p_scene, const char *p_filename)
+{
+	Dbg_MsgAssert(/*!p_scene->is_dictionary &&*/ !p_scene->IsSky(), ("Can't add to sky or dictionary scene."));
+
+	// load scene
+	// Pip::Load the new file
+	uint8 *p_pipData = (uint8*)Pip::Load(p_filename);
+
+	Dbg_MsgAssert(p_pipData, ("s_plat_add_scene(): Can't open file %s", p_filename));
+
+	// process data in place
+	NxPs2::CGeomNode *p_geomnode = NxPs2::CGeomNode::sProcessInPlace(p_pipData, LOADFLAG_RENDERNOW);
+
+	CPs2Scene *p_ps2_scene = static_cast(p_scene);
+	p_ps2_scene->SetEngineAddScene(p_geomnode);
+
+	return p_geomnode != NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_unload_scene(CScene *p_scene)
+{
+
+	Dbg_MsgAssert(p_scene,("Trying to delete a NULL scene"));
+
+	CPs2Scene * p_ps2_scene = (CPs2Scene*)p_scene;
+
+	NxPs2::CGeomNode *p_geomnode = p_ps2_scene->GetEngineScene();
+
+	if (p_geomnode)
+	{
+		// clean up the node we created
+		p_geomnode->Cleanup();
+
+		// Pip::Unload the file
+		Pip::Unload(p_scene->GetSceneFilename());
+	}
+	
+	// Clone scene node
+#if 0		// Moved this into ~CPs2Scene so that this CGeomNode isn't destroyed before the cloned sectors
+	NxPs2::CGeomNode *p_clone_geomnode = p_ps2_scene->GetEngineCloneScene();
+	if (p_clone_geomnode)
+	{
+		p_clone_geomnode->Cleanup();
+		delete p_clone_geomnode;
+	}
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModel*		CEngine::s_plat_init_model(void)
+{
+	CPs2Model* pModel = new CPs2Model;
+
+	return pModel;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_uninit_model(CModel* pModel)
+{
+	Dbg_Assert( pModel );
+
+	delete pModel;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom*	CEngine::s_plat_init_geom(void)
+{
+	CPs2Geom* pGeom = new CPs2Geom;
+
+	return pGeom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CEngine::s_plat_uninit_geom(CGeom* pGeom)
+{
+	Dbg_Assert( pGeom );
+
+	delete pGeom;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CQuickAnim* CEngine::s_plat_init_quick_anim()
+{
+	CQuickAnim* pQuickAnim = new CQuickAnim;
+
+	return pQuickAnim;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::s_plat_uninit_quick_anim(CQuickAnim* pQuickAnim)
+{
+	Dbg_Assert( pQuickAnim );
+
+	delete pQuickAnim;
+
+	return;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMesh*	CEngine::s_plat_load_mesh(uint32 id, uint32* pModelData, int modelDataSize, uint8* pCASData, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume)
+{
+	CPs2Mesh* pMesh = new CPs2Mesh(pModelData, modelDataSize, pCASData, pTexDict, texDictOffset, isSkin, doShadowVolume);
+
+	return pMesh;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMesh*	CEngine::s_plat_load_mesh(const char* pMeshFileName, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume)
+{
+	CPs2Mesh* pMesh = new CPs2Mesh(pMeshFileName, pTexDict, texDictOffset, isSkin, doShadowVolume);
+
+	return pMesh;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CEngine::s_plat_unload_mesh(CMesh* pMesh)
+{
+	Dbg_Assert( pMesh );
+
+	delete pMesh;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CEngine::s_plat_set_mesh_scaling_parameters( SMeshScalingParameters* pParams )
+{
+	NxPs2::SetMeshScalingParameters( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSprite *	CEngine::s_plat_create_sprite(CWindow2D *p_window)
+{
+	return new CPs2Sprite(p_window);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_destroy_sprite(CSprite *p_sprite)
+{
+	delete p_sprite;
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextured3dPoly *	CEngine::s_plat_create_textured_3d_poly()
+{
+	return new NxPs2::CPs2Textured3dPoly;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_destroy_textured_3d_poly(CTextured3dPoly *p_poly)
+{
+	delete p_poly;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CTexture *CEngine::s_plat_create_render_target_texture( int width, int height, int depth, int z_depth )
+{
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::s_plat_project_texture_into_scene( Nx::CTexture *p_texture, Nx::CModel *p_model, Nx::CScene *p_scene )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::s_plat_set_projection_texture_camera( Nx::CTexture *p_texture, Gfx::Camera *p_camera )
+{
+	#if !STENCIL_SHADOW
+	// Currently assumes a top down view. So all we need to store is the position of the camera.
+	// The width and height of the view frustum (this is an orthographic camera) are constant.
+	Mth::Vector pos = p_camera->GetPos();
+	NxPs2::SetTextureProjectionCamera( &pos, &pos );
+	#endif
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_stop_projection_texture( Nx::CTexture *p_texture )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_add_occlusion_poly( uint32 num_verts, Mth::Vector *p_vert_array, uint32 checksum )
+{
+	if( num_verts == 4 )
+	{
+		NxPs2::AddOcclusionPoly( p_vert_array[0], p_vert_array[1], p_vert_array[2], p_vert_array[3], checksum );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_enable_occlusion_poly( uint32 checksum, bool enable )
+{
+	NxPs2::EnableOcclusionPoly( checksum, enable );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_remove_all_occlusion_polys( void )
+{
+	NxPs2::RemoveAllOcclusionPolys();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// returns true if the sphere at "center", with the "radius"
+// is visible to the current camera
+// (note, currently this is the last frame's camera on PS2)
+bool CEngine::s_plat_is_visible( Mth::Vector&	center, float radius  )
+{
+	return NxPs2::IsVisible(center,radius);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_max_multipass_distance(float dist)
+{
+	NxPs2::render::sMultipassMaxDist = dist;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char *		CEngine::s_plat_get_platform_extension()
+{
+	return "PS2";	// String literals are statically allocated so can be returned safely, (Bjarne, p90)
+}
+
+
+/******************************************************************/
+// Wait for any pending asyncronous rendering to finish, so rendering
+// data can be unloaded
+/******************************************************************/
+
+void CEngine::s_plat_finish_rendering()
+{
+		NxPs2::WaitForRendering();		// PS2 Specific, wait for prior frame's DMA to finish	
+} 
+
+/******************************************************************/
+// Set the amount that the previous frame is blended with this frame
+// 0 = none	  	(just see current frame) 	
+// 128 = 50/50
+// 255 = 100% 	(so you only see the previous frame)												  
+/******************************************************************/
+
+void CEngine::s_plat_set_screen_blur(uint32 amount )
+{
+	amount = (255-amount)/2;
+	uint32 alpha = *NxPs2::p_patch_ALPHA;
+	alpha &= 0xffffff00;
+	alpha |= amount; 
+	*NxPs2::p_patch_ALPHA = alpha;	
+	*NxPs2::p_patch_ALPHA2 = alpha;	
+} 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CEngine::s_plat_get_num_soundtracks()
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* CEngine::s_plat_get_soundtrack_name( int soundtrack_number )
+{
+	return NULL;
+}
+
+
+
+// Add some PS2 specific debug info to the metrics script structure
+void	CEngine::s_plat_get_metrics(Script::CStruct * p_info)
+{
+
+	// get the scene, and get the root 
+
+	Script::CStruct *p_ps2 = new Script::CStruct();
+	
+	NxPs2::CGeomMetrics * p_metrics = new NxPs2::CGeomMetrics;
+
+	CPs2Scene * p_ps2_scene = (CPs2Scene *) sGetMainScene();
+	if (p_ps2_scene->GetEngineScene())
+	{
+		p_ps2_scene->GetEngineScene()->CountMetrics(p_metrics);
+		p_ps2->AddInteger(CRCD(0xd55cd658,"NULLEngineScene"),0);
+	}	
+	else
+	{
+		// K: Added this so that the script refresh_poly_count in lighttool.q
+		// can display "N/A" for the values.
+		p_ps2->AddInteger(CRCD(0xd55cd658,"NULLEngineScene"),1);
+	}
+
+	p_ps2->AddInteger(CRCD(0x3dd01ea1,"total"), p_metrics->m_total);        
+	p_ps2->AddInteger(CRCD(0x3960ff18,"leaf"),  p_metrics->m_leaf);  
+	p_ps2->AddInteger(CRCD(0x4de5330c,"objects"),  p_metrics->m_object);  
+	p_ps2->AddInteger(CRCD(0x94927839,"vert"),  p_metrics->m_verts);  
+	p_ps2->AddInteger(CRCD(0x169ee151,"poly"),  p_metrics->m_polys);  
+	p_ps2->AddFloat(CRCD(0x58785e10,"verts_per_poly"),  (float)p_metrics->m_verts/(float)p_metrics->m_polys);  
+	p_ps2->AddFloat(CRCD(0x3b75d7cc,"polys_per_object"),  (float)p_metrics->m_polys/(float)p_metrics->m_object);  
+	p_ps2->AddFloat(CRCD(0xbc56249d,"polys_per_mesh"),  (float)p_metrics->m_polys/(float)p_metrics->m_leaf);  
+		   
+	p_info->AddStructurePointer(CRCD(0x26861025,"scene"),p_ps2);
+	
+	delete	p_metrics;
+
+
+}
+
+
+void CEngine::s_plat_set_letterbox( bool letterbox )
+{
+	NxPs2::DoLetterbox = letterbox;
+} 
+ 
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_color_buffer_clear( bool clear )
+{
+}
+
+
+
+}
\ No newline at end of file
diff --git a/Code/Gfx/NGPS/p_nxmiscfx.cpp b/Code/Gfx/NGPS/p_nxmiscfx.cpp
new file mode 100644
index 0000000..0da1f2a
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxmiscfx.cpp
@@ -0,0 +1,1483 @@
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+#define	DRAW_DEBUG_LINES		0
+#define PRINT_SHATTER_MEMORY	0
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sNGPSVert
+{
+	Mth::Vector		pos;
+	Image::RGBA		col;
+	float			u, v;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sNGPSSplatInstanceDetails : public sSplatInstanceDetails
+{
+	// Platform specific part.
+	NxPs2::SSingleTexture	*mp_texture;
+	sNGPSVert				m_verts[SPLAT_POLYS_PER_MESH * 3];
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sNGPSShatterInstanceDetails : public sShatterInstanceDetails
+{
+	// Platform specific part.
+							sNGPSShatterInstanceDetails( int num_tris, NxPs2::CGeomNode *p_geom_node );
+							~sNGPSShatterInstanceDetails( void );
+
+	static int				sQueryMemoryNeeded(int num_tris);
+
+	NxPs2::CGeomNode *		mp_geom_node;		// Corresponding source mesh
+	sNGPSVert *				mp_vert_array;		// Array of vertices
+
+	uint32 *				mp_tex_regs;		// packed texture registers
+	int						m_num_tex_regs;		// number of texture registers
+};
+
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sNGPSShatterInstanceDetails::sNGPSShatterInstanceDetails( int num_tris, NxPs2::CGeomNode *p_geom_node ) : sShatterInstanceDetails( num_tris )
+{
+	mp_geom_node = p_geom_node;
+
+#if PRINT_SHATTER_MEMORY
+	Dbg_Message("Allocating %d bytes for vert array of %d tris", sizeof(sNGPSVert) * 3 * num_tris, num_tris);
+#endif
+	mp_vert_array = new sNGPSVert[num_tris * 3];
+
+	// Get texture info
+	if (p_geom_node->GetGroup())		// Garrett: Need to find the real way to figure out if a mesh
+	{									// is textures or not (can always look at the GIFtag)
+		uint32 *p_dma = (uint32 *) p_geom_node->GetDma();
+
+		p_dma += 9;			// Go to vif::UNPACK() for texture regs
+
+		m_num_tex_regs = (*p_dma >> 16) & 0xf;
+		mp_tex_regs = p_dma + 1;
+#if 0
+		Dbg_Message("Found %d texture registers for type %x", m_num_tex_regs, (*p_dma >> 24));
+		for (int i = 0; i < m_num_tex_regs; i++)
+		{
+			Dbg_Message(" - Register %x", mp_tex_regs[(i * 3) + 2]);
+		}
+#endif
+	} else {
+		mp_tex_regs = NULL;
+		m_num_tex_regs = 0;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sNGPSShatterInstanceDetails::~sNGPSShatterInstanceDetails( void )
+{
+	delete [] mp_vert_array;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			sNGPSShatterInstanceDetails::sQueryMemoryNeeded(int num_tris)
+{
+	int size = sShatterInstanceDetails::s_query_memory_needed(num_tris);
+
+	size += (sizeof(sNGPSShatterInstanceDetails) - sizeof(sShatterInstanceDetails));
+	size += sizeof(sNGPSVert) * num_tris * 3;
+
+	return size;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sNGPSSplatInstanceDetails * getDetailsForTextureSplat( NxPs2::SSingleTexture *p_texture )
+{
+	sNGPSSplatInstanceDetails *p_ngps_details;
+
+	Dbg_Assert( p_splat_details_table );
+	
+	// Check to see whether we have a scene already created for this type of texture splat.
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	while( p_details )
+	{
+		p_ngps_details					= static_cast( p_details );
+
+		// If this one matches, return it...
+		if (p_ngps_details->mp_texture == p_texture)
+		{
+			return p_ngps_details;
+		}
+		p_details = p_splat_details_table->IterateNext();
+	}
+
+	// Check to see that we have memory first
+	if (Mem::Available() < (int) ((sizeof(sNGPSSplatInstanceDetails) * 3) / 2))
+	{
+		return NULL;
+	}
+
+	p_ngps_details = new sNGPSSplatInstanceDetails;
+	p_ngps_details->m_highest_active_splat	= 0;
+	p_ngps_details->mp_texture = p_texture;
+	memset( p_ngps_details->m_lifetimes, 0, sizeof( int ) * SPLAT_POLYS_PER_MESH );
+	
+	for( int v = 0; v < SPLAT_POLYS_PER_MESH * 3; ++v )
+	{
+		p_ngps_details->m_verts[v].pos = Mth::Vector(0.0f, 0.0f, 0.0f, 1.0f);
+		p_ngps_details->m_verts[v].col = Image::RGBA( 0x80, 0x80, 0x80, 0x80 );
+	}
+
+	p_splat_details_table->PutItem((uint32)p_ngps_details, p_ngps_details );
+
+	return p_ngps_details;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool subdivide_tri_stack( float shatterArea, sNGPSVert **p_write, NxPs2::CGeomNode *p_node )
+{
+	// If there are elements on the stack, pop off the top three vertices and subdivide if necessary.
+	if( triSubdivideStack.IsEmpty())
+	{
+		return false;
+	}
+
+	// Three temporary buffers.
+	sNGPSVert v0;
+	sNGPSVert v1;
+	sNGPSVert v2;
+	
+	// Stack is LIFO, so Pop() off in reverse order.
+	triSubdivideStack.Pop( &v2 );
+	triSubdivideStack.Pop( &v1 );
+	triSubdivideStack.Pop( &v0 );
+	
+	// Calculate the area of this tri.
+	Mth::Vector p(	v1.pos[X] - v0.pos[X], v1.pos[Y] - v0.pos[Y], v1.pos[Z] - v0.pos[Z] );
+	Mth::Vector q(	v2.pos[X] - v0.pos[X], v2.pos[Y] - v0.pos[Y], v2.pos[Z] - v0.pos[Z] );
+	Mth::Vector r(( p[Y] * q[Z] ) - ( q[Y] * p[Z] ), ( p[Z] * q[X] ) - ( q[Z] * p[X] ), ( p[X] * q[Y] ) - ( q[X] * p[Y] ));
+	float area_squared = r.LengthSqr();
+
+	if( area_squared > shatterArea )
+	{
+		// We need to subdivide this tri. Calculate the three intermediate points.
+		int block_size = triSubdivideStack.GetBlockSize();
+
+		// Three temporary buffers.
+		sNGPSVert i01;
+		sNGPSVert i12;
+		sNGPSVert i20;
+
+		memcpy( &i01, &v0, block_size );
+		memcpy( &i12, &v1, block_size );
+		memcpy( &i20, &v2, block_size );
+
+		// Deal with positions (always present).
+		i01.pos[X] = v0.pos[X] + (( v1.pos[X] - v0.pos[X] ) * 0.5f );
+		i01.pos[Y] = v0.pos[Y] + (( v1.pos[Y] - v0.pos[Y] ) * 0.5f );
+		i01.pos[Z] = v0.pos[Z] + (( v1.pos[Z] - v0.pos[Z] ) * 0.5f );
+
+		i12.pos[X] = v1.pos[X] + (( v2.pos[X] - v1.pos[X] ) * 0.5f );
+		i12.pos[Y] = v1.pos[Y] + (( v2.pos[Y] - v1.pos[Y] ) * 0.5f );
+		i12.pos[Z] = v1.pos[Z] + (( v2.pos[Z] - v1.pos[Z] ) * 0.5f );
+
+		i20.pos[X] = v2.pos[X] + (( v0.pos[X] - v2.pos[X] ) * 0.5f );
+		i20.pos[Y] = v2.pos[Y] + (( v0.pos[Y] - v2.pos[Y] ) * 0.5f );
+		i20.pos[Z] = v2.pos[Z] + (( v0.pos[Z] - v2.pos[Z] ) * 0.5f );
+
+		// Deal with colors (not always present).
+		//if( p_node->m_diffuse_offset > 0 )
+		{
+			uint8	*p_v0col	= (uint8*)( &(v0.col));
+			uint8	*p_v1col	= (uint8*)( &(v1.col));
+			uint8	*p_v2col	= (uint8*)( &(v2.col));
+			uint8	*p_i01col	= (uint8*)( &(i01.col));
+			uint8	*p_i12col	= (uint8*)( &(i12.col));
+			uint8	*p_i20col	= (uint8*)( &(i20.col));
+		
+			for( int i = 0; i < 4; ++i )
+			{
+				p_i01col[i]		= p_v0col[i] + (char)(( p_v1col[i] - p_v0col[i] ) * 0.5f );
+				p_i12col[i]		= p_v1col[i] + (char)(( p_v2col[i] - p_v1col[i] ) * 0.5f );
+				p_i20col[i]		= p_v2col[i] + (char)(( p_v0col[i] - p_v2col[i] ) * 0.5f );
+			}
+		}
+
+		// Deal with uv0 (not always present).
+		//if( p_node->m_uv0_offset > 0 )
+		{
+			float	*p_v0uv		= (float*)( &v0.u );
+			float	*p_v1uv		= (float*)( &v1.u );
+			float	*p_v2uv		= (float*)( &v2.u );
+			float	*p_i01uv	= (float*)( &i01.u );
+			float	*p_i12uv	= (float*)( &i12.u );
+			float	*p_i20uv	= (float*)( &i20.u );
+		
+			// We know that v is contiguous to u
+			for( int i = 0; i < 2; ++i )
+			{
+				p_i01uv[i]		= p_v0uv[i] + (( p_v1uv[i] - p_v0uv[i] ) * 0.5f );
+				p_i12uv[i]		= p_v1uv[i] + (( p_v2uv[i] - p_v1uv[i] ) * 0.5f );
+				p_i20uv[i]		= p_v2uv[i] + (( p_v0uv[i] - p_v2uv[i] ) * 0.5f );
+			}
+		}
+		
+		// Push the four new tris onto the stack.
+		triSubdivideStack.Push( &v0 );
+		triSubdivideStack.Push( &i01 );
+		triSubdivideStack.Push( &i20 );
+
+		triSubdivideStack.Push( &i01 );
+		triSubdivideStack.Push( &v1 );
+		triSubdivideStack.Push( &i12 );
+
+		triSubdivideStack.Push( &i01 );
+		triSubdivideStack.Push( &i12 );
+		triSubdivideStack.Push( &i20 );
+
+		triSubdivideStack.Push( &i20 );
+		triSubdivideStack.Push( &i12 );
+		triSubdivideStack.Push( &v2 );
+	}
+	else
+	{
+		// Don't need to subdivide this tri.
+		int block_size = triSubdivideStack.GetBlockSize();
+
+		// Just copy the tri into the next available slot.
+		memcpy( *p_write, &v0, block_size );
+		(*p_write)++;
+		memcpy( *p_write, &v1, block_size );
+		(*p_write)++;
+		memcpy( *p_write, &v2, block_size );
+		(*p_write)++;
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool same_side( Mth::Vector &p1, Mth::Vector &p2, Mth::Vector &a, Mth::Vector &b )
+{
+	Mth::Vector cp1 = Mth::CrossProduct( b - a, p1 - a );
+	Mth::Vector cp2 = Mth::CrossProduct( b - a, p2 - a );
+	if( Mth::DotProduct( cp1, cp2 ) >= 0.0f )
+		return true;
+    else
+		return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool point_in_triangle( Mth::Vector &p, Mth::Vector &a, Mth::Vector &b, Mth::Vector &c )
+{
+	if( same_side( p, a, b, c ) && same_side( p, b, a, c ) && same_side( p, c, a, b ))
+		return true;
+    else
+		return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline bool line_segment_intersection( float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4 )
+{
+	float den = (( y4 - y3 ) * ( x2 - x1 )) - (( x4 - x3 ) * ( y2 - y1 ));
+
+	if( den == 0.0f )
+	{
+		// Parallel lines.
+		return false;
+	}
+	
+	float num_a = (( x4 - x3 ) * ( y1 - y3 )) - (( y4 - y3 ) * ( x1 - x3 ));
+	float num_b = (( x2 - x1 ) * ( y1 - y3 )) - (( y2 - y1 ) * ( x1 - x3 ));
+
+	num_a /= den;
+	num_b /= den;
+
+	if(( num_a <= 1.0f ) && ( num_b <= 1.0f ))
+		return true;
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline bool tri_texture_intersect( float u0, float v0, float u1, float v1, float u2, float v2 )
+{
+	// Trivial check to see if all three points are outside range of texture.
+	if(( u0 < -1.0f ) && ( u1 < -1.0f ) && ( u2 < -1.0f ))
+		return false;
+	if(( u0 > 1.0f ) && ( u1 > 1.0f ) && ( u2 > 1.0f ))
+		return false;
+	if(( v0 < -1.0f ) && ( v1 < -1.0f ) && ( v2 < -1.0f ))
+		return false;
+	if(( v0 > 1.0f ) && ( v1 > 1.0f ) && ( v2 > 1.0f ))
+		return false;
+	
+	// Check that at least one corner of the texture falls within the tri.
+	Mth::Vector texture_square[4] = {	Mth::Vector( -1.0f, -1.0f, 0.0f, 0.0f ),
+										Mth::Vector(  1.0f, -1.0f, 0.0f, 0.0f ),
+										Mth::Vector(  1.0f,  1.0f, 0.0f, 0.0f ),
+										Mth::Vector( -1.0f,  1.0f, 0.0f, 0.0f )};
+
+	Mth::Vector a( u0, v0, 0.0f );
+	Mth::Vector b( u1, v1, 0.0f );
+	Mth::Vector c( u2, v2, 0.0f );
+
+	for( int p = 0; p < 4; ++p )
+	{
+		if( point_in_triangle( texture_square[p], a, b, c ))
+		{
+			return true;
+		}
+	}
+
+	// No corners of the texture fall within the tri. There are 3 possible explanations:
+	// 1) The tri intersects the texture, but does not contain a texture corner.
+	// 2) The texture lies entirely outside of the tri.
+	// 3) The tri falls entirely within the texture.
+	// Given the relatively small size of the textures, case (3) is extremely unlikely.
+
+	// Perform a trivial check to see whether a corner of the tri lies within the texture. This will catch (3) and sometimes (1).
+	if(( u0 >= -1.0f ) && ( u0 <= 1.0f ) && ( v0 >= -1.0f ) && ( v0 <= 1.0f ))
+		return true;
+	if(( u1 >= -1.0f ) && ( u1 <= 1.0f ) && ( v1 >= -1.0f ) && ( v1 <= 1.0f ))
+		return true;
+	if(( u2 >= -1.0f ) && ( u2 <= 1.0f ) && ( v2 >= -1.0f ) && ( v2 <= 1.0f ))
+		return true;
+
+	// Perform the complete check to see if any line segment forming the tri intersects any line segment forming the texture.
+	for( int p = 0; p < 4; ++p )
+	{
+		int q = ( p + 1 ) % 4;
+		if( line_segment_intersection( u0, v0, u1, v1, texture_square[p][X], texture_square[p][Y], texture_square[q][X], texture_square[q][Y] ))
+			return true;
+	}
+	
+	return false;
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_screen_flash_render( sScreenFlashDetails *p_details )
+{
+	// Get viewport details.
+	CViewport *p_vp = CViewportManager::sGetActiveViewport( p_details->m_viewport );
+	
+	// Centre of screen is (0x8000, 0x8000), unit is 1/16 pixel.
+	
+	int x0 = XOFFSET + (int)( p_vp->GetOriginX() * (HRES << 4) );
+	int y0 = YOFFSET + (int)( p_vp->GetOriginY() * (VRES << 4) );
+	int x1 = x0 + (int)( p_vp->GetWidth() * (HRES << 4) );
+	int y1 = y0 + (int)( p_vp->GetHeight() * (VRES << 4) );
+
+	uint32 rgba = p_details->m_current.r | ((uint32)p_details->m_current.g << 8 ) | ((uint32)p_details->m_current.b << 16 ) | ((uint32)p_details->m_current.a << 24 );
+	
+	NxPs2::DrawRectangle( x0, y0, x1, y1, rgba );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_initialize( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_cleanup( void )
+{
+	sNGPSSplatInstanceDetails *p_ngps_details;
+
+	Dbg_Assert( p_splat_details_table );
+	
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	while( p_details )
+	{
+		p_ngps_details = static_cast( p_details );
+		
+		p_details = p_splat_details_table->IterateNext();
+
+		p_splat_details_table->FlushItem((uint32)p_ngps_details );
+		delete p_ngps_details;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_reset_poly( sSplatInstanceDetails *p_details, int index )
+{
+	// Cast the details to NGPS details.
+	sNGPSSplatInstanceDetails *p_ngps_details = static_cast( p_details );
+	
+	// Force this poly to be degenerate.
+	p_ngps_details->m_verts[index * 3 + 1]	= p_ngps_details->m_verts[index * 3];
+	p_ngps_details->m_verts[index * 3 + 2]	= p_ngps_details->m_verts[index * 3];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool plat_texture_splat( Nx::CSector **pp_sectors, Nx::CCollStatic **pp_collision, Mth::Vector& start, Mth::Vector& end,
+						 float size, float lifetime, Nx::CTexture *p_texture, Nx::sSplatTrailInstanceDetails *p_trail_details )
+{
+	Mth::Matrix view_matrix, ortho_matrix, projection_matrix;
+
+#	if DRAW_DEBUG_LINES
+	Gfx::AddDebugLine( start, end, MAKE_RGB( 200, 200, 0 ), MAKE_RGB( 200, 200, 0 ), 1 );
+#	endif // DRAW_DEBUG_LINES
+	
+	// The length of the start->end line defines the view depth of the frustum.
+	Mth::Vector	splat_vector	= end - start;
+	float		view_depth		= splat_vector.Length();
+	splat_vector.Normalize();
+	
+	// Calculate the parallel projection matrix. Generally the projection vector will tend to point downwards, so we use a
+	// random vector in the x-z plane to define the up vector for the projection. However if this splat is part of a trail,
+	// use the previous point to generate the up vector.
+	if( p_trail_details )
+	{
+		Mth::Vector up( start[X] - p_trail_details->m_last_pos[X], start[Y] - p_trail_details->m_last_pos[Y], start[Z] - p_trail_details->m_last_pos[Z], 0.0f );
+		Dbg_MsgAssert((up[X] != 0.0f) || (up[Z] != 0.0f), ("Up vector has no length"));
+		//Dbg_Message("Splat up vector (%.8f, %.8f, %.8f)", up[X], up[Y], up[Z]);
+
+		// The height of the viewport is defined by the distance between the two points.
+		float height = up.Length() * 0.5f;
+
+		start	-= up * 0.5f;
+		end		-= up * 0.5f;
+
+		up.Normalize();
+
+		Mth::CreateMatrixLookAt( view_matrix, start, end, up );
+		Mth::CreateMatrixOrtho( ortho_matrix, size, height, 0.1f, view_depth );
+	}
+	else if( fabsf( splat_vector[Y] ) > 0.5f )
+	{
+		float angle = ((float)rand() * 2.0f * Mth::PI ) / (float)RAND_MAX;
+		Mth::CreateMatrixLookAt( view_matrix, start, end, Mth::Vector( sinf( angle ), 0.0f, cosf( angle ), 0.0f ));
+		Mth::CreateMatrixOrtho( ortho_matrix , size, size, 0.1f, view_depth );
+	}
+	else
+	{
+		Mth::CreateMatrixLookAt( view_matrix, start, end, Mth::Vector( 0.0f, 1.0f, 0.0f, 0.0f ));
+		Mth::CreateMatrixOrtho( ortho_matrix , size, size, 0.1f, view_depth );
+	}
+
+	projection_matrix = view_matrix * ortho_matrix;
+
+	// Find texture info
+	Dbg_Assert(p_texture);
+	CPs2Texture *p_ps2_texture = static_cast(p_texture);
+	NxPs2::SSingleTexture *p_single_texture = p_ps2_texture->GetSingleTexture();
+	Dbg_Assert(p_single_texture);
+
+	// Pointer to the mesh we will be modifying. (Don't want to set the pointer up until we know for
+	// sure that we will be adding some polys).
+	sNGPSSplatInstanceDetails	*p_details		= NULL;
+	sNGPSVert					*p_target_verts	= NULL;
+
+#if DRAW_DEBUG_LINES
+	Nx::CSector *p_sector;
+	while(( p_sector = *pp_sectors ))
+	{
+		Nx::CPs2Geom *p_ngps_geom = static_cast( p_sector->GetGeom());
+
+		if( p_ngps_geom )
+		{
+			Mth::Vector min = p_ngps_geom->GetBoundingBox().GetMin();
+			Mth::Vector max = p_ngps_geom->GetBoundingBox().GetMax();
+
+			Mth::Vector box[8];
+			box[0] = box[1] = box[2] = box[3] = max;
+			box[1][X] = min[X];
+			box[2][Y] = min[Y];
+			box[3][Z] = min[Z];
+			box[5] = box[6] = box[7] = box[4] = min;;
+			box[5][X] = max[X];
+			box[6][Y] = max[Y];
+			box[7][Z] = max[Z];
+
+			for ( int i = 1; i < 4; i++ )
+			{
+				Gfx::AddDebugLine( box[0], box[i], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			}
+			for ( int i = 5; i < 8; i++ )
+			{
+				Gfx::AddDebugLine( box[4], box[i], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			}
+			Gfx::AddDebugLine( box[1], box[6], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[1], box[7], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[2], box[5], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[2], box[7], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[3], box[5], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[3], box[6], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+		}
+
+		++pp_sectors;
+	}
+#endif // DRAW_DEBUG_LINES
+
+	// Make initial line bounding box.  (Possibly pass previous one in if this takes too long)
+	Mth::CBBox line_bbox( start );
+	line_bbox.AddPoint( end );
+
+	//Dbg_Message("Using line (%f, %f, %f) - (%f, %f, %f)", start[X], start[Y], start[Z], end[X], end[Y], end[Z]);
+
+	Nx::CCollStatic *p_collision;
+	while(( p_collision = *(pp_collision++) ))
+	{
+		// Since the collision is static, we can assume the collision geometry data is in world space
+		Nx::CCollObjTriData *p_coll_geom = p_collision->GetGeometry();
+		
+		// narrow down list of possible faces
+		uint num_faces;
+		FaceIndex *p_face_indexes;
+		p_face_indexes = p_coll_geom->FindIntersectingFaces(line_bbox, num_faces);
+		Dbg_Assert(p_face_indexes);
+
+		Mth::Vector uvprojections[3];
+
+		for (uint fidx = 0; fidx < num_faces; fidx++, p_face_indexes++)
+		{
+			// Make sure it is collidable and visible
+			if (p_coll_geom->GetFaceFlags(*p_face_indexes) & (mFD_NON_COLLIDABLE | mFD_INVISIBLE))
+			{
+				continue;
+			}
+
+			Mth::Vector v0(p_coll_geom->GetRawVertexPos(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 0)));
+			Mth::Vector v1(p_coll_geom->GetRawVertexPos(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 1)));
+			Mth::Vector v2(p_coll_geom->GetRawVertexPos(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 2)));
+			v0[W] = v1[W] = v2[W] = 1.0f;		// Make sure they are points, not vectors
+
+#if 1
+			uvprojections[0] = v0 * projection_matrix;
+			uvprojections[1] = v1 * projection_matrix;
+			uvprojections[2] = v2 * projection_matrix;
+#else
+			// Test each component
+			uvprojections[0] = v0 * view_matrix;
+			uvprojections[1] = v1 * view_matrix;
+			uvprojections[2] = v2 * view_matrix;
+
+			Dbg_Message("Looking at possible splat poly vert 0 (%f, %f, %f, %f)", v0[X], v0[Y], v0[Z], v0[W]);
+			Dbg_Message("Looking at possible splat poly vert 1 (%f, %f, %f, %f)", v1[X], v1[Y], v1[Z], v1[W]);
+			Dbg_Message("Looking at possible splat poly vert 2 (%f, %f, %f, %f)", v2[X], v2[Y], v2[Z], v2[W]);
+
+			Dbg_Message("Converted vert 0 to (%f, %f, %f)", uvprojections[0][X], uvprojections[0][Y], uvprojections[0][Z]);
+			Dbg_Message("Converted vert 1 to (%f, %f, %f)", uvprojections[1][X], uvprojections[1][Y], uvprojections[1][Z]);
+			Dbg_Message("Converted vert 2 to (%f, %f, %f)", uvprojections[2][X], uvprojections[2][Y], uvprojections[2][Z]);
+
+			uvprojections[0] = uvprojections[0] * ortho_matrix;
+			uvprojections[1] = uvprojections[1] * ortho_matrix;
+			uvprojections[2] = uvprojections[2] * ortho_matrix;
+
+			Dbg_Message("Converted vert 0 again to (%f, %f, %f)", uvprojections[0][X], uvprojections[0][Y], uvprojections[0][Z]);
+			Dbg_Message("Converted vert 1 again to (%f, %f, %f)", uvprojections[1][X], uvprojections[1][Y], uvprojections[1][Z]);
+			Dbg_Message("Converted vert 2 again to (%f, %f, %f)", uvprojections[2][X], uvprojections[2][Y], uvprojections[2][Z]);
+#endif
+
+			// Check that they are in the frustum
+//			if(( uvprojections[0][X] < -1.0f ) && ( uvprojections[1][X] < -1.0f ) && ( uvprojections[2][X] < -1.0f ))
+//				continue;
+//			if(( uvprojections[0][Y] < -1.0f ) && ( uvprojections[1][Y] < -1.0f ) && ( uvprojections[2][Y] < -1.0f ))
+//				continue;
+//			if(( uvprojections[0][X] > 1.0f ) && ( uvprojections[1][X] > 1.0f ) && ( uvprojections[2][X] > 1.0f ))
+//				continue;
+//			if(( uvprojections[0][Y] > 1.0f ) && ( uvprojections[1][Y] > 1.0f ) && ( uvprojections[2][Y] > 1.0f ))
+//				continue;
+			if(( uvprojections[0][Z] < -1.0f ) && ( uvprojections[1][Z] < -1.0f ) && ( uvprojections[2][Z] < -1.0f ))
+				continue;
+			if(( uvprojections[0][Z] > 1.0f ) && ( uvprojections[1][Z] > 1.0f ) && ( uvprojections[2][Z] > 1.0f ))
+				continue;
+
+			// Okay, this tri lies within the projection frustum. Now check that it intersects the texture
+			if( !tri_texture_intersect( uvprojections[0][X], uvprojections[0][Y],
+										uvprojections[1][X], uvprojections[1][Y],
+										uvprojections[2][X], uvprojections[2][Y] ))
+			{
+				continue;
+			}
+
+			//Dbg_Message("Found splat poly vert 0 (%f, %f, %f) uv (%f, %f)", v0[X], v0[Y], v0[Z], uvprojections[0][X], uvprojections[0][Y]);
+			//Dbg_Message("Found splat poly vert 1 (%f, %f, %f) uv (%f, %f)", v1[X], v1[Y], v1[Z], uvprojections[1][X], uvprojections[1][Y]);
+			//Dbg_Message("Found splat poly vert 2 (%f, %f, %f) uv (%f, %f)", v2[X], v2[Y], v2[Z], uvprojections[2][X], uvprojections[2][Y]);
+
+			// Check if we have a line or point.  For some reason, the rendering code will crash
+			// with this (I think it is the clipping).  Since we really don't want it anyways,
+			// just skip them.
+			int equal_axis = 0;
+			if ((v0[X] == v1[X]) && (v1[X] == v2[X]))	equal_axis++;
+			if ((v0[Y] == v1[Y]) && (v1[Y] == v2[Y]))	equal_axis++;
+			if ((v0[Z] == v1[Z]) && (v1[Z] == v2[Z]))	equal_axis++;
+			if (equal_axis > 1)
+			{
+				//Dbg_Message("Skipping line poly...");
+				continue;
+			}
+
+			// Okay, this tri lies within the projection frustum. Get a pointer to the mesh used for rendering texture splats
+			// with the given texture. (Note this will create a new instance to handle texture splats of this texture if one
+			// does not already exist).
+			if( p_target_verts == NULL )
+			{
+				p_details						= getDetailsForTextureSplat(p_single_texture);
+				if ( p_details == NULL )
+				{
+					continue;
+				}
+
+				p_target_verts					= p_details->m_verts;
+				Dbg_Assert( p_target_verts );
+			}
+
+			// If we have trails, scale up the mapping by one pixel in all directions.
+			// (effectively 2 pixels in u and 2 pixels in v ).
+			float up_one_u_pixel;
+			float up_one_v_pixel;
+			if( p_trail_details )
+			{
+				up_one_u_pixel = 1.0f - ( 1.0f / ( p_texture->GetWidth() / 2.0f ) );
+				up_one_v_pixel = 1.0f - ( 1.0f / ( p_texture->GetHeight() / 2.0f ) );
+			}
+			else
+			{
+				up_one_u_pixel = 1.0f;
+				up_one_v_pixel = 1.0f;
+			}
+
+			// Scan through the lifetimes, finding a 'dead' poly (lifetime == 0), or the oldest.
+			uint32 idx						= p_details->GetOldestSplat();
+
+			// Convert lifetime from seconds to milliseconds.
+			p_details->m_lifetimes[idx]		= (int)( lifetime * 1000.0f );
+
+			// Set up the corresponding vertices. First write the positions.
+			uint32 index					= idx * 3;
+			Dbg_Assert((index + 2) < (SPLAT_POLYS_PER_MESH * 3));
+			p_target_verts[index + 0].pos	= v0;
+			p_target_verts[index + 1].pos	= v1;
+			p_target_verts[index + 2].pos	= v2;
+			//p_target_verts[index + 0].pos[Y] += ( 12.0f * (float)rand() / RAND_MAX );
+			//p_target_verts[index + 1].pos[Y] += ( 12.0f * (float)rand() / RAND_MAX );
+			//p_target_verts[index + 2].pos[Y] += ( 12.0f * (float)rand() / RAND_MAX );
+
+			// Then the uv's.
+			p_target_verts[index + 0].u		= ( (uvprojections[0][X] * 0.5f) + 0.5f ) * up_one_u_pixel;
+			p_target_verts[index + 0].v		= ( (uvprojections[0][Y] * 0.5f) + 0.5f ) * up_one_v_pixel;
+			p_target_verts[index + 1].u		= ( (uvprojections[1][X] * 0.5f) + 0.5f ) * up_one_u_pixel;
+			p_target_verts[index + 1].v		= ( (uvprojections[1][Y] * 0.5f) + 0.5f ) * up_one_v_pixel;
+			p_target_verts[index + 2].u		= ( (uvprojections[2][X] * 0.5f) + 0.5f ) * up_one_u_pixel;
+			p_target_verts[index + 2].v		= ( (uvprojections[2][Y] * 0.5f) + 0.5f ) * up_one_v_pixel;
+
+			// Now the colors
+			uint8 intensity0 = p_coll_geom->GetVertexIntensity(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 0));
+			uint8 intensity1 = p_coll_geom->GetVertexIntensity(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 1));
+			uint8 intensity2 = p_coll_geom->GetVertexIntensity(p_coll_geom->GetFaceVertIndex(*p_face_indexes, 2));
+			p_target_verts[index + 0].col = Image::RGBA(intensity0, intensity0, intensity0, 0x80);
+			p_target_verts[index + 1].col = Image::RGBA(intensity1, intensity1, intensity1, 0x80);
+			p_target_verts[index + 2].col = Image::RGBA(intensity2, intensity2, intensity2, 0x80);
+
+			Mth::Vector	*p_v0 = &( p_target_verts[index + 0].pos );
+			Mth::Vector	*p_v1 = &( p_target_verts[index + 1].pos );
+			Mth::Vector	*p_v2 = &( p_target_verts[index + 2].pos );
+			Mth::Vector pv(	p_v1->GetX() - p_v0->GetX(), p_v1->GetY() - p_v0->GetY(), p_v1->GetZ() - p_v0->GetZ() );
+			Mth::Vector qv(	p_v2->GetX() - p_v0->GetX(), p_v2->GetY() - p_v0->GetY(), p_v2->GetZ() - p_v0->GetZ() );
+			Mth::Vector r(( pv[Y] * qv[Z] ) - ( qv[Y] * pv[Z] ), ( pv[Z] * qv[X] ) - ( qv[Z] * pv[X] ), ( pv[X] * qv[Y] ) - ( qv[X] * pv[Y] ));
+			float area_squared = r.LengthSqr();
+
+			// Set the shatter test to ensure that we don't subdivide too far. Note that each successive subdivision will quarter
+			// the area of each triangle, which means the area *squared* of each triangle will become 1/16th of the previous value.
+			float shatterArea = area_squared / 128.0f;
+
+			triSubdivideStack.Clear();
+			triSubdivideStack.SetBlockSize( sizeof(sNGPSVert) );
+			triSubdivideStack.Push( &p_target_verts[index + 0] );
+			triSubdivideStack.Push( &p_target_verts[index + 1] );
+			triSubdivideStack.Push( &p_target_verts[index + 2] );
+
+			// Allocate a block of memory into which the subdivision stack will write the results.
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+			sNGPSVert		*p_array		= new sNGPSVert[100];
+			sNGPSVert		*p_array_start	= p_array;
+			sNGPSVert		*p_array_loop	= p_array;
+			//memset( p_array, 0, sizeof(sNGPSVert) * 100 );
+			Mem::Manager::sHandle().PopContext();
+
+			while( subdivide_tri_stack( shatterArea, &p_array, NULL ));
+
+			// Ensure we haven't overrun the buffer.
+			Dbg_Assert((uint32)p_array - (uint32)p_array_loop < ( sizeof(sNGPSVert) * 100 ));
+
+			float oo_up_one_u_pixel = 1.0f / up_one_u_pixel;
+			float oo_up_one_v_pixel = 1.0f / up_one_v_pixel;
+
+//			int vert_idx = 0;
+			bool no_polys = true;
+
+			while( p_array_loop != p_array )
+			{
+				Dbg_Assert(((uint32) p_array_loop) < ((uint32) p_array));
+//				Dbg_Message("Looking at vert %d", vert_idx);
+				// Add this triangle, *if* it is valid.
+				if( tri_texture_intersect(((p_array_loop[0].u * oo_up_one_u_pixel) - 0.5f) * 2.0f,
+										  ((p_array_loop[0].v * oo_up_one_v_pixel) - 0.5f) * 2.0f,
+										  ((p_array_loop[1].u * oo_up_one_u_pixel) - 0.5f) * 2.0f,
+										  ((p_array_loop[1].v * oo_up_one_v_pixel) - 0.5f) * 2.0f,
+										  ((p_array_loop[2].u * oo_up_one_u_pixel) - 0.5f) * 2.0f,
+										  ((p_array_loop[2].v * oo_up_one_v_pixel) - 0.5f) * 2.0f ))
+//				if( tri_texture_intersect((p_array_loop[0].u * 2.0f * oo_up_one_u_pixel) - 1.0f,
+//										  (p_array_loop[0].v * 2.0f * oo_up_one_v_pixel) - 1.0f,
+//										  (p_array_loop[1].u * 2.0f * oo_up_one_u_pixel) - 1.0f,
+//										  (p_array_loop[1].v * 2.0f * oo_up_one_v_pixel) - 1.0f,
+//										  (p_array_loop[2].u * 2.0f * oo_up_one_u_pixel) - 1.0f,
+//										  (p_array_loop[2].v * 2.0f * oo_up_one_v_pixel) - 1.0f ))
+				{
+//					Dbg_Message("Accepted vert %d", vert_idx);
+					// Convert lifetime from seconds to milliseconds.
+					p_details->m_lifetimes[idx]		= (int)( lifetime * 1000.0f );
+
+					p_target_verts[index + 0].pos	= p_array_loop[0].pos;
+					p_target_verts[index + 1].pos	= p_array_loop[1].pos;
+					p_target_verts[index + 2].pos	= p_array_loop[2].pos;
+
+					p_target_verts[index + 0].u		= p_array_loop[0].u;
+					p_target_verts[index + 0].v		= p_array_loop[0].v;
+					p_target_verts[index + 1].u		= p_array_loop[1].u;
+					p_target_verts[index + 1].v		= p_array_loop[1].v;
+					p_target_verts[index + 2].u		= p_array_loop[2].u;
+					p_target_verts[index + 2].v		= p_array_loop[2].v;
+
+					p_target_verts[index + 0].col	= p_array_loop[0].col;
+					p_target_verts[index + 1].col	= p_array_loop[1].col;
+					p_target_verts[index + 2].col	= p_array_loop[2].col;
+
+					idx								= p_details->GetOldestSplat();
+					index							= idx * 3;
+					Dbg_Assert((index + 2) < (SPLAT_POLYS_PER_MESH * 3));
+					no_polys 						= false;
+				}
+
+				p_array_loop					+= 3;
+//				vert_idx++;
+			}
+
+			delete [] p_array_start;
+
+			// Check if all the new polys were rejected.  Don't know why this happens, but we want
+			// to get rid of the original texture.
+			if (no_polys)
+			{
+				plat_texture_splat_reset_poly( p_details, idx );
+			}
+		}
+	}
+	
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_render( void )
+{
+	sNGPSSplatInstanceDetails *p_ngps_details;
+
+	Dbg_Assert( p_splat_details_table );
+
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+
+	bool set_zpush = (p_details != NULL);
+	if (set_zpush)
+	{
+		NxPs2::CImmediateMode::sSetZPush(24.0f);
+	}
+
+	while( p_details )
+	{
+
+		// See if we have anything to draw first
+		if (p_details->m_highest_active_splat >= 0)
+		{
+			p_ngps_details = static_cast( p_details );
+
+			// Init drawing, since we know we have to draw
+#if 1
+			NxPs2::CImmediateMode::sStartPolyDraw(p_ngps_details->mp_texture, PackALPHA(0,1,0,1,0), ABS, true);
+#else
+			if ((Tmr::GetVblanks() % 600) < 300)
+			{
+				NxPs2::CImmediateMode::sStartPolyDraw(p_ngps_details->mp_texture, PackALPHA(0,0,0,0,0), ABS, true);
+			} else {
+				NxPs2::CImmediateMode::sStartPolyDraw(p_ngps_details->mp_texture, PackALPHA(0,0,0,0,0), ABS, false);
+			}
+#endif
+
+			// Render the triangles
+			sNGPSVert *p_vert_array = p_ngps_details->m_verts;
+			for (int i = 0; i <= p_details->m_highest_active_splat; i++, p_vert_array += 3)
+			{
+				NxPs2::CImmediateMode::sDrawTriUV(p_vert_array[0].pos, p_vert_array[1].pos, p_vert_array[2].pos,
+												  p_vert_array[0].u, p_vert_array[0].v,
+												  p_vert_array[1].u, p_vert_array[1].v,
+												  p_vert_array[2].u, p_vert_array[2].v,
+												  *((uint32 *) &p_vert_array[0].col),
+												  *((uint32 *) &p_vert_array[1].col),
+												  *((uint32 *) &p_vert_array[2].col),
+												  ABS);
+
+#if DRAW_DEBUG_LINES
+				Gfx::AddDebugLine( p_vert_array[0].pos, p_vert_array[1].pos, MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+				Gfx::AddDebugLine( p_vert_array[1].pos, p_vert_array[2].pos, MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+				Gfx::AddDebugLine( p_vert_array[2].pos, p_vert_array[0].pos, MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+#endif
+			}
+		}
+		
+		p_details = p_splat_details_table->IterateNext();
+	}
+
+	if (set_zpush)
+	{
+		NxPs2::CImmediateMode::sClearZPush();
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter_initialize( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter_cleanup( void )
+{
+	sNGPSShatterInstanceDetails *p_ngps_details;
+
+	Dbg_Assert( p_shatter_details_table );
+	
+	p_shatter_details_table->IterateStart();
+	sShatterInstanceDetails *p_details = p_shatter_details_table->IterateNext();
+	while( p_details )
+	{
+		p_ngps_details = static_cast( p_details );
+		
+		p_details = p_shatter_details_table->IterateNext();
+
+		p_shatter_details_table->FlushItem((uint32)p_ngps_details );
+		delete p_ngps_details;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const int MAX_GEOM_NODES = 10;
+
+void find_geom_leaves( NxPs2::CGeomNode *p_node, NxPs2::CGeomNode **leaf_array, int & num_nodes)
+{
+	if (p_node->IsLeaf())
+	{
+		Dbg_Assert(num_nodes < MAX_GEOM_NODES);
+
+		leaf_array[num_nodes++] = p_node;
+		return;
+	} else {
+		NxPs2::CGeomNode *p_child;
+		for (p_child = p_node->GetChild(); p_child; p_child = p_child->GetSibling())
+		{
+			find_geom_leaves(p_child, leaf_array, num_nodes);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int get_num_extra_shatter_tris(float shatterArea, const Mth::Vector & pos0, const Mth::Vector & pos1, const Mth::Vector & pos2)
+{
+	int num_extra_tris = 0;
+
+	// Figure the area of this tri.
+	Mth::Vector p( pos1[X] - pos0[X], pos1[Y] - pos0[Y], pos1[Z] - pos0[Z], 0.0f );
+	Mth::Vector q( pos2[X] - pos0[X], pos2[Y] - pos0[Y], pos2[Z] - pos0[Z], 0.0f );
+	Mth::Vector r(( p[Y] * q[Z] ) - ( q[Y] * p[Z] ), ( p[Z] * q[X] ) - ( q[Z] * p[X] ), ( p[X] * q[Y] ) - ( q[X] * p[Y] ), 0.0f);
+	float area_squared = r.LengthSqr();
+
+	if( area_squared > shatterArea )
+	{
+		//Dbg_Message("Area of triangle: %f", sqrtf(area_squared));
+
+		// We will need to subdivide - each subdivision will result in an area one quarter the previous area
+		// (and thusly the square of the area will be one sixteenth the previous area).
+		num_extra_tris = 1;
+		while( area_squared > shatterArea )
+		{
+			num_extra_tris *= 4;
+			area_squared *= ( 1.0f / 16.0f );
+		}
+
+		// This original tri will not be added...
+		// ...however, the subdivided versions will.
+		num_extra_tris--;
+		//Dbg_Message("Adding %d triangles", num_extra_tris);
+	}
+
+	return num_extra_tris;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void plat_shatter( CGeom *p_geom )
+{
+	CPs2Geom *p_ps2_geom = static_cast( p_geom );
+	NxPs2::CGeomNode *p_root_node = p_ps2_geom->GetEngineObject();
+	Dbg_Assert(p_root_node);
+
+	// Get Time of Day color
+	Image::RGBA geom_rgba = Nx::CEngine::sGetMainScene()->GetMajorityColor();
+
+	// Put all the meshes in an array
+	int num_meshes = 0;
+	NxPs2::CGeomNode *mesh_array[MAX_GEOM_NODES];
+	find_geom_leaves(p_root_node, mesh_array, num_meshes);
+
+	// For each mesh in the geom...
+	for( int m = 0; m < num_meshes; ++m )
+	{
+		NxPs2::CGeomNode *p_node = mesh_array[m];
+
+		// Since these are extra passes 99% of the time, Environment Mapped CGeomNodes are rejected
+		if (p_node->IsEnvMapped())
+		{
+			continue;
+		}
+
+		uint8 *p_dma = p_node->GetDma();
+
+		int num_verts = NxPs2::dma::GetNumVertices(p_dma);
+		if( num_verts >= 3 )
+		{
+			bool short_xyz = (NxPs2::dma::GetBitLengthXYZ(p_dma) == 16);
+			Mth::Vector mesh_center = p_node->GetBoundingSphere();
+
+			// Set the block size for this mesh.
+			triSubdivideStack.SetBlockSize( sizeof(sNGPSVert) );
+
+			// Get DMA arrays
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+			sint32 *p_vert_array = new sint32[4 * num_verts];
+			sint32 *p_uv_array = new sint32[2 * num_verts];
+			uint32 *p_rgb_array = new uint32[num_verts];
+#if PRINT_SHATTER_MEMORY
+			Dbg_Message("Mesh #%d; Verts %d; CGeomNode pointer %x checksum %x", m, num_verts, p_node, p_node->GetChecksum());
+			Dbg_Message("Allocating %d bytes temporarily for DMA", 7 * num_verts * sizeof(uint32));
+#endif
+			Mem::Manager::sHandle().PopContext();
+
+			// Copy DMA data
+			NxPs2::dma::ExtractXYZs(p_dma, (uint8 *) p_vert_array);
+			NxPs2::dma::ExtractSTs(p_dma, (uint8 *) p_uv_array);
+			NxPs2::dma::ExtractRGBAs(p_dma, (uint8 *) p_rgb_array);
+
+			Dbg_Assert(p_vert_array[(0 * 4) + 3] & 0x8000);
+			Dbg_Assert(p_vert_array[(1 * 4) + 3] & 0x8000);
+
+			sNGPSVert v0, v1, v2;
+
+			if (short_xyz)
+			{
+				NxPs2::dma::ConvertXYZToFloat(v0.pos, &(p_vert_array[0 * 4]), mesh_center);
+				NxPs2::dma::ConvertXYZToFloat(v1.pos, &(p_vert_array[1 * 4]), mesh_center);
+			} else {
+				NxPs2::dma::ConvertXYZToFloat(v0.pos, &(p_vert_array[0 * 4]));
+				NxPs2::dma::ConvertXYZToFloat(v1.pos, &(p_vert_array[1 * 4]));
+			}
+			NxPs2::dma::ConvertSTToFloat(v0.u, v0.v, &(p_uv_array[0 * 2]));
+			NxPs2::dma::ConvertSTToFloat(v1.u, v1.v, &(p_uv_array[1 * 2]));
+
+			v0.col = *((Image::RGBA *) &(p_rgb_array[0]));
+			v0.col.Blend128(geom_rgba);
+			v1.col = *((Image::RGBA *) &(p_rgb_array[1]));
+			v1.col.Blend128(geom_rgba);
+
+			int orig_tris = 0;
+			int valid_tris = 0;
+
+			//Dbg_Message("Number of verts: %d", num_verts);
+
+			for (int idx = 2; idx < num_verts; idx++)
+			{
+				if (short_xyz)
+				{
+					NxPs2::dma::ConvertXYZToFloat(v2.pos, &(p_vert_array[idx * 4]), mesh_center);
+				} else {
+					NxPs2::dma::ConvertXYZToFloat(v2.pos, &(p_vert_array[idx * 4]));
+				}
+				NxPs2::dma::ConvertSTToFloat(v2.u, v2.v, &(p_uv_array[idx * 2]));
+				v2.col = *((Image::RGBA *) &(p_rgb_array[idx]));
+				v2.col.Blend128(geom_rgba);
+
+				// Dont make triangle if vert has ADC bit set
+				if (!(p_vert_array[(idx * 4) + 3] & 0x8000))		// if adc bit not set
+				{
+					orig_tris++;
+					valid_tris++;
+
+					// Push this tri onto the stack.
+					triSubdivideStack.Push( &v0 );
+					triSubdivideStack.Push( &v1 );
+					triSubdivideStack.Push( &v2 );
+
+					//Dbg_Message("Triangle (%f, %f, %f, %f) - (%f, %f, %f, %f) - (%f, %f, %f, %f)", v0.pos[X], v0.pos[Y], v0.pos[Z], v0.pos[W],
+					//			 v1.pos[X], v1.pos[Y], v1.pos[Z], v1.pos[W], v2.pos[X], v2.pos[Y], v2.pos[Z], v2.pos[W]);
+					//Dbg_Message("UVs (%f, %f) - (%f, %f) - (%f, %f)", v0.u, v0.v, v1.u, v1.v, v2.u, v2.v);
+					//Dbg_Message("Colors (%d, %d, %d, %d) - (%d, %d, %d, %d) - (%d, %d, %d, %d)", v0.col.r, v0.col.g, v0.col.b, v0.col.a,
+					//			 v1.col.r, v1.col.g, v1.col.b, v1.col.a, v2.col.r, v2.col.g, v2.col.b, v2.col.a);
+
+					// Add extra triangles that we need
+					valid_tris += get_num_extra_shatter_tris(shatterAreaTest, v0.pos, v1.pos, v2.pos);
+				}
+
+				v0 = v1;
+				v1 = v2;
+			}
+
+			// Free DMA buffers
+			delete [] p_vert_array;
+			delete [] p_uv_array;
+			delete [] p_rgb_array;
+
+			if( valid_tris == 0 )
+			{
+				continue;
+			}
+
+			float newShatterArea = shatterAreaTest;
+
+			// Make sure there is memory available and that we don't take it all
+			int free_mem = Mem::Available();
+			int needed_mem = (sNGPSShatterInstanceDetails::sQueryMemoryNeeded(valid_tris) * 3) / 2; 
+			if (free_mem < needed_mem)
+			{
+				// Calculate how much bigger we need to make the area
+				Dbg_Message("Old needed memory %d free memory %d for %d tris", needed_mem, free_mem, valid_tris);
+				float mem_ratio = ((float) needed_mem) / ((float) free_mem);
+				int iratio = (int) (mem_ratio + 0.99f);		// Round up
+				iratio = (iratio + 3) & ~3;					// Round up to the nearest divisible by 4
+				newShatterArea = newShatterArea * (iratio * iratio);	// And square the ratio
+
+				Dbg_Message("mem ratio %f iratio %d", mem_ratio, iratio);
+
+				const Mth::Vector *pos0, *pos1, *pos2;
+				valid_tris = orig_tris;
+				for (int idx = 0; idx < orig_tris; idx++)
+				{
+					pos0 = &(((const sNGPSVert *) triSubdivideStack.Peek((idx * 3) + 0))->pos);
+					pos1 = &(((const sNGPSVert *) triSubdivideStack.Peek((idx * 3) + 1))->pos);
+					pos2 = &(((const sNGPSVert *) triSubdivideStack.Peek((idx * 3) + 2))->pos);
+				   
+					// Add extra triangles that we need
+					valid_tris += get_num_extra_shatter_tris(newShatterArea, *pos0, *pos1, *pos2);
+				}
+				needed_mem = (sNGPSShatterInstanceDetails::sQueryMemoryNeeded(valid_tris) * 3) / 2; 
+				Dbg_Message("New needed memory %d free memory %d for %d tris", needed_mem, free_mem, valid_tris);
+
+				// See if this one is smaller, otherwise, give up
+				if (free_mem < needed_mem)
+				{
+					triSubdivideStack.Clear();
+					continue;
+				}
+
+				Dbg_Message("Increased shatter area from %f to %f", shatterAreaTest, newShatterArea);
+			}
+
+			//Dbg_Message("Making %d triangles", valid_tris);
+
+			// Create a tracking structure for this mesh.
+			sNGPSShatterInstanceDetails *p_details		= new sNGPSShatterInstanceDetails( valid_tris, p_node );
+			sNGPSVert	 				*p_write_vertex	= p_details->mp_vert_array;
+			uint32						details_index	= 0;
+
+#if PRINT_SHATTER_MEMORY
+			Dbg_Message("Allocated %d bytes for shatter of %d tris", sNGPSShatterInstanceDetails::sQueryMemoryNeeded(valid_tris), valid_tris);
+#endif
+
+			Mth::Vector					spread_center	= shatterVelocity * -shatterSpreadFactor;
+			float						base_speed		= shatterVelocity.Length();
+
+			spread_center[X] += mesh_center[X];
+			spread_center[Y] += mesh_center[Y];
+			spread_center[Z] += mesh_center[Z];
+			spread_center[W] = 1.0f;
+			
+			// Add the tracking structure to the table.
+			p_shatter_details_table->PutItem((uint32)p_details, p_details );
+			
+			// Process-subdivide the entire stack.
+			sNGPSVert *p_copy_vertex = p_write_vertex;
+			while( subdivide_tri_stack( newShatterArea, &p_write_vertex, p_node ));
+
+			// Copy the (possibly subdivided) vertex data over.
+			while( p_copy_vertex < p_write_vertex )
+			{
+				Mth::Vector *p_vert0 = &((p_copy_vertex + 0)->pos);
+				Mth::Vector *p_vert1 = &((p_copy_vertex + 1)->pos);
+				Mth::Vector *p_vert2 = &((p_copy_vertex + 2)->pos);
+				
+				// Calculate position as the midpoint of the three vertices per poly.
+				p_details->mp_positions[details_index][X] = ( p_vert0->GetX() + p_vert1->GetX() + p_vert2->GetX() ) * ( 1.0f / 3.0f );
+				p_details->mp_positions[details_index][Y] = ( p_vert0->GetY() + p_vert1->GetY() + p_vert2->GetY() ) * ( 1.0f / 3.0f );
+				p_details->mp_positions[details_index][Z] = ( p_vert0->GetZ() + p_vert1->GetZ() + p_vert2->GetZ() ) * ( 1.0f / 3.0f );
+				p_details->mp_positions[details_index][W] = 1.0f;
+
+				// Calculate the vector  back from the bounding box of the object. Then use this to figure the 'spread' of the
+				// shards by calculating the vector from this position to the center of each shard.
+				float speed = base_speed + ( base_speed * (( shatterVelocityVariance * rand() ) / RAND_MAX ));
+				p_details->mp_velocities[details_index] = ( p_details->mp_positions[details_index] - spread_center ).Normalize( speed );
+
+				Mth::Vector axis( -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), 0.0f);
+				axis.Normalize();
+				p_details->mp_matrices[details_index].Ident();
+				p_details->mp_matrices[details_index].Rotate( axis, 0.1f * ((float)rand() / RAND_MAX ));
+
+				p_copy_vertex += 3;
+				//p_copy_u += 3;
+				//p_copy_v += 3;
+						
+				++details_index;
+			}
+		}
+	}
+
+}
+
+
+
+/******************************************************************************
+ *
+ * 
+ *****************************************************************************/
+void plat_shatter_update( sShatterInstanceDetails *p_details, float framelength )
+{
+	sNGPSShatterInstanceDetails *p_ps2_details = static_cast( p_details );
+	
+	// Load up initial three vertex pointers.
+	sNGPSVert *p_v0	= p_ps2_details->mp_vert_array;
+	sNGPSVert *p_v1	= p_ps2_details->mp_vert_array + 1;
+	sNGPSVert *p_v2	= p_ps2_details->mp_vert_array + 2;
+	
+	for( int i = 0; i < p_details->m_num_triangles; ++i )
+	{
+		// To move the shatter pieces:
+		// 1) subtract position from each vertex
+		// 2) rotate
+		// 3) update position with velocity
+		// 4) add new position to each vertex
+
+		// The matrix holds 3 vectors at once.
+		Mth::Matrix m;
+		m[X].Set( p_v0->pos[X] - p_details->mp_positions[i][X], p_v0->pos[Y] - p_details->mp_positions[i][Y], p_v0->pos[Z] - p_details->mp_positions[i][Z] );
+		m[Y].Set( p_v1->pos[X] - p_details->mp_positions[i][X], p_v1->pos[Y] - p_details->mp_positions[i][Y], p_v1->pos[Z] - p_details->mp_positions[i][Z] );
+		m[Z].Set( p_v2->pos[X] - p_details->mp_positions[i][X], p_v2->pos[Y] - p_details->mp_positions[i][Y], p_v2->pos[Z] - p_details->mp_positions[i][Z] );
+         
+		m[X].Rotate( p_details->mp_matrices[i] );
+		m[Y].Rotate( p_details->mp_matrices[i] );
+		m[Z].Rotate( p_details->mp_matrices[i] );
+
+		// Update the position and velocity of the shatter piece, dealing with bouncing if necessary.
+		p_details->UpdateParameters( i, framelength );
+      
+		m[X] += p_details->mp_positions[i]; 
+		m[Y] += p_details->mp_positions[i]; 
+		m[Z] += p_details->mp_positions[i];
+
+		p_v0->pos[X] = m[X][X]; p_v0->pos[Y] = m[X][Y]; p_v0->pos[Z] = m[X][Z];
+		p_v1->pos[X] = m[Y][X]; p_v1->pos[Y] = m[Y][Y]; p_v1->pos[Z] = m[Y][Z];
+		p_v2->pos[X] = m[Z][X]; p_v2->pos[Y] = m[Z][Y]; p_v2->pos[Z] = m[Z][Z];
+
+		p_v0 = p_v0 + 3;
+		p_v1 = p_v1 + 3;
+		p_v2 = p_v2 + 3;
+	}
+}
+
+
+
+/******************************************************************************
+ *
+ * 
+ *****************************************************************************/
+void plat_shatter_render( sShatterInstanceDetails *p_details )
+{
+	//Dbg_Message("In plat_shatter_render()");
+	sNGPSShatterInstanceDetails *p_ps2_details = static_cast( p_details );
+
+	Dbg_Assert( p_ps2_details );
+
+	if (p_ps2_details->m_num_triangles == 0)
+		return;
+
+	bool textured = (p_ps2_details->mp_tex_regs != NULL);
+	if (textured)
+	{
+		NxPs2::sGroup *p_group = p_ps2_details->mp_geom_node->GetGroup();
+		NxPs2::dma::SetList(p_group);
+		p_group->Used[NxPs2::render::Field] = true;
+		p_ps2_details->mp_geom_node->GetTexture()->m_render_count++;
+	}
+
+	Mth::Vector sort_pos = p_ps2_details->mp_vert_array[0].pos;			// just use 1st vert of 1st tri
+	sort_pos *= NxPs2::render::WorldToCamera;
+	sort_pos[2] = -1000.0f;
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, *(uint32 *)&sort_pos[2]);		// z-sort key
+	NxPs2::vif::BASE(NxPs2::vu1::Loc);
+	NxPs2::vif::OFFSET(0);
+	uint vu1_loc = NxPs2::vu1::Loc;
+	NxPs2::vu1::Loc = 0;						// must do this as a relative prim for a sortable list...
+
+	if (textured)
+	{
+		NxPs2::CImmediateMode::sTextureGroupInit(REL);
+		NxPs2::CImmediateMode::sStartPolyDraw(p_ps2_details->mp_tex_regs, p_ps2_details->m_num_tex_regs, REL, false);
+	}
+	else
+	{
+		NxPs2::CImmediateMode::sStartPolyDraw(NULL, PackALPHA(0,1,0,1,0), REL, false);
+	}
+
+	// Render the triangles
+	sNGPSVert *p_vert_array = p_ps2_details->mp_vert_array;
+	for (int i = 0; i < p_ps2_details->m_num_triangles; i++, p_vert_array += 3)
+	{
+		if (textured)
+		{
+			NxPs2::CImmediateMode::sDrawTriUV(p_vert_array[0].pos, p_vert_array[1].pos, p_vert_array[2].pos,
+											  p_vert_array[0].u, p_vert_array[0].v,
+											  p_vert_array[1].u, p_vert_array[1].v,
+											  p_vert_array[2].u, p_vert_array[2].v,
+											  *((uint32 *) &p_vert_array[0].col),
+											  *((uint32 *) &p_vert_array[1].col),
+											  *((uint32 *) &p_vert_array[2].col),
+											  REL);
+		} else {
+			NxPs2::CImmediateMode::sDrawTri(p_vert_array[0].pos, p_vert_array[1].pos, p_vert_array[2].pos,
+											*((uint32 *) &p_vert_array[0].col),
+											*((uint32 *) &p_vert_array[1].col),
+											*((uint32 *) &p_vert_array[2].col),
+											REL);
+		}
+
+// this may break if put back in...
+//#if DRAW_DEBUG_LINES
+//		Gfx::AddDebugLine( p_vert_array[0].pos, p_vert_array[1].pos, MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//		Gfx::AddDebugLine( p_vert_array[1].pos, p_vert_array[2].pos, MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//		Gfx::AddDebugLine( p_vert_array[2].pos, p_vert_array[0].pos, MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+//#endif
+	}
+
+	NxPs2::dma::EndTag();
+	((uint16 *)NxPs2::dma::pTag)[1] |= NxPs2::vu1::Loc & 0x3FF;	// must write some code for doing this automatically
+	NxPs2::vu1::Loc += vu1_loc;
+
+	if (p_ps2_details->mp_geom_node->GetGroup()->flags & GROUPFLAG_SORT)
+	{
+		NxPs2::dma::Tag(NxPs2::dma::cnt,0,0);
+		NxPs2::vif::NOP();
+		NxPs2::vif::NOP();
+		NxPs2::dma::SetList(NULL);
+	}
+
+}
+
+///////////////////////////////////////////////////////////////////
+//
+// FOG
+//
+///////////////////////////////////////////////////////////////////
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::s_plat_enable_fog(bool enable)
+{
+	NxPs2::render::EnableFog = enable;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::s_plat_set_fog_near_distance(float distance)
+{
+	NxPs2::render::FogNear = distance;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::s_plat_set_fog_exponent(float exponent)
+{
+	Dbg_Message("Stub: CFog::SetFogExponent()");
+	//NxPs2::Fx::SetupFogPalette(*((unsigned int *) &m_rgba), exponent);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::s_plat_set_fog_rgba(Image::RGBA rgba)
+{
+	//NxPs2::Fx::SetupFogPalette(*((unsigned int *) &rgba), m_exponent);
+
+	// Set alpha (and clamp between 0.0 and 1.0)
+	float f_alpha = (float) rgba.a / 128.0f;
+	f_alpha = Mth::Min(f_alpha, 1.0f);
+	f_alpha = Mth::Max(f_alpha, 0.0f);
+	NxPs2::render::FogAlpha = f_alpha;
+
+	// Set color
+	NxPs2::render::FogCol = *((uint32 *) &rgba) & 0xFFFFFF;	// mask out alpha
+}
+
+void		CFog::s_plat_set_fog_color( void )
+{
+	// Doesn't need to do anything on PS2.
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_fog_update( void )
+{
+}
+	
+
+
+} // Nx
diff --git a/Code/Gfx/NGPS/p_nxnewparticle.cpp b/Code/Gfx/NGPS/p_nxnewparticle.cpp
new file mode 100644
index 0000000..d15def0
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxnewparticle.cpp
@@ -0,0 +1,546 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticle.cpp										**
+**																			**
+**	Created by:		3/25/03	-	SPG											**
+**																			**
+**	Description:	PS2 new parametric particle system						**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+void	CPs2NewParticle::plat_render( void )
+{
+	CParticleStream *p_stream;
+	int i;
+
+	//--------------------------------------------------------------------------------------------------------------------
+	// process the streams
+	if (m_params.m_EmitRate && (!m_emitting || (m_params.m_EmitRate != mp_newest_stream->m_rate)))
+	{	
+		if (m_num_streams < m_max_streams)
+		{
+			// add new stream to cyclic buffer
+			m_num_streams++;
+			mp_newest_stream++;
+			if (mp_newest_stream == mp_stream + m_max_streams)
+			{
+				mp_newest_stream = mp_stream;
+			}
+
+			// initialise new stream
+			mp_newest_stream->m_rate			= m_params.m_EmitRate;
+			mp_newest_stream->m_interval		= 1.0f/m_params.m_EmitRate;
+			mp_newest_stream->m_oldest_age		= 0.0f;
+			mp_newest_stream->m_num_particles	= 0;
+			mp_newest_stream->m_seed[0]			= rand();
+			mp_newest_stream->m_seed[1]			= rand();
+			mp_newest_stream->m_seed[2]			= rand();
+			mp_newest_stream->m_seed[3]			= rand();
+			m_emitting = true;
+		}
+		else
+		{
+			m_emitting = false;
+		}
+	}
+	else
+	{
+		m_emitting = m_params.m_EmitRate;
+	}
+
+	if (!m_num_streams)
+		return;
+
+	// age all streams
+	for (i=0, p_stream=mp_oldest_stream; im_oldest_age += 1.0f/60.0f;
+
+		// step pointer within cyclic buffer
+		p_stream++;
+		if (p_stream == mp_stream + m_max_streams)
+		{
+			p_stream = mp_stream;
+		}
+	}
+
+	// births into newest stream
+	if (m_emitting)
+	{
+		// how many particles so far emitted?
+		mp_newest_stream->m_num_particles = (int)(mp_newest_stream->m_oldest_age * mp_newest_stream->m_rate + 1.0f);
+	}
+
+	// deaths from oldest stream
+	if (mp_oldest_stream->m_oldest_age > m_params.m_Lifetime)
+	{
+		// work out number dead
+		int particles_dead = (int)((mp_oldest_stream->m_oldest_age - m_params.m_Lifetime) * mp_oldest_stream->m_rate + 1.0f);
+
+		// remove dead particles
+		mp_oldest_stream->m_num_particles -= particles_dead;
+
+		// should we keep processing the oldest stream?
+		if (mp_oldest_stream->m_num_particles>0 || (m_num_streams==1 && m_emitting))
+		{
+			// adjust age of oldest particle
+			mp_oldest_stream->m_oldest_age -= (float)particles_dead * mp_oldest_stream->m_interval;
+
+			// advance seed
+			mp_oldest_stream->AdvanceSeed(particles_dead);
+		}
+		else
+		{
+			// remove oldest stream and wrap in cyclic buffer if necessary
+			m_num_streams--;
+			mp_oldest_stream++;
+			if (mp_oldest_stream == mp_stream + m_max_streams)
+			{
+				mp_oldest_stream = mp_stream;
+			}
+			if (!m_num_streams)
+				return;
+		}
+	}
+
+
+	//--------------------------------------------------------------------------------------------------------------------
+	// now render the streams
+
+	if (mp_engine_texture)
+	{
+		// Mick:  If this is textured, we reset the TEX0, TEX1 regs as the texture might move
+		// Since 2D textures are dynamically packed every frame
+		*(uint64 *)&m_systemDmaData.m_GScontext[8] = mp_engine_texture->m_RegTEX0;
+		*(uint64 *)&m_systemDmaData.m_GScontext[12] = mp_engine_texture->m_RegTEX1;
+	}
+
+	// FOG
+	Mth::Vector x = 0.5f * (m_params.m_BoxPos[0] + m_params.m_BoxPos[2]);
+	x[3] = 1.0f;
+	x *= NxPs2::render::WorldToFrustum;
+	float f;
+	if ((x[3] > NxPs2::render::FogNear) && NxPs2::render::EnableFog)	// Garrett: We have to check for EnableFog here because the VU1 code isn't
+	{
+		f = 1.0 + NxPs2::render::EffectiveFogAlpha * (NxPs2::render::FogNear/x[3] - 1.0f);
+	}
+	else
+	{
+		f = 1.0f;
+	}
+	m_systemDmaData.m_GScontext[21] = ((uint32)(int)(f*255.99f)) << 24;
+
+	// set the group
+	NxPs2::dma::SetList(NxPs2::sGroup::pParticles);
+
+	// the system and streams will be loaded to a double-buffered input area of VUMem1
+	// ref the system data, and include an unpack for the system and streams:
+	NxPs2::dma::Tag(NxPs2::dma::ref, (sizeof(CSystemDmaData)>>4), (uint)&m_systemDmaData);
+	NxPs2::vif::STCYCL(1,1);
+	NxPs2::vif::UNPACK(0, V4_32, (sizeof(CSystemDmaData)>>4)+m_num_streams*2, REL, SIGNED, 0);
+
+	// construct a packet with data for each stream
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	for (i=0,p_stream=mp_oldest_stream; im_num_particles<65536, ("particle limit reached (65536)"));
+
+		// wrap at end of cyclic buffer
+		if (p_stream == mp_stream + m_max_streams)
+		{
+			p_stream = mp_stream;
+		}
+
+		((float  *)NxPs2::dma::pLoc)[0] = p_stream->m_oldest_age;
+		((float  *)NxPs2::dma::pLoc)[1] = p_stream->m_interval;
+		((uint32 *)NxPs2::dma::pLoc)[2] = p_stream->m_num_particles;
+		((uint32 *)NxPs2::dma::pLoc)[3] = (p_stream==mp_newest_stream) ? 0x8000 : 0;
+		((uint32 *)NxPs2::dma::pLoc)[4] = p_stream->m_seed[0];
+		((uint32 *)NxPs2::dma::pLoc)[5] = p_stream->m_seed[1];
+		((uint32 *)NxPs2::dma::pLoc)[6] = p_stream->m_seed[2];
+		((uint32 *)NxPs2::dma::pLoc)[7] = p_stream->m_seed[3];
+
+		//printf("stream %d, num particles=%d, oldest age=%f\n", s, p_stream->m_num_particles, p_stream->m_oldest_age);
+
+		// step dma pointer
+		NxPs2::dma::pLoc += 8*4;
+
+		// count particles
+		NxPs2::render::sTotalNewParticles += p_stream->m_num_particles;
+	}
+
+	NxPs2::vif::MSCAL(10);	// sprites
+	NxPs2::dma::EndTag();
+	NxPs2::dma::SetList(NxPs2::sGroup::pEpilogue);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2NewParticle::update_position( void )
+{
+	// convert 3-point -> PVA format
+	float t1 = m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f;
+	float t2 = m_params.m_Lifetime;
+	Mth::Vector x0,x1,x2,u,a_;
+
+	x0    = m_params.m_BoxPos[0];
+	x0[3] = m_params.m_Radius[0];
+	x1    = m_params.m_BoxPos[1];
+	x1[3] = m_params.m_Radius[1];
+	x2    = m_params.m_BoxPos[2];
+	x2[3] = m_params.m_Radius[2];
+
+	if (m_params.m_UseMidpoint)
+	{
+		u  =  ( t2*t2*(x1 - x0) - t1*t1*(x2 - x0) ) / ( t1*t2*(t2 - t1) );
+		a_ =  ( t1*(x2 - x0) - t2*(x1 - x0) ) / ( t1*t2*(t2 - t1) );
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set(0,0,0,0);
+	}
+
+	m_systemDmaData.m_p0 = x0 - 1.5f * m_systemDmaData.m_s0;
+	m_systemDmaData.m_p1 = u  - 1.5f * m_systemDmaData.m_s1;
+	m_systemDmaData.m_p2 = a_ - 1.5f * m_systemDmaData.m_s2;
+	m_systemDmaData.m_p0[3] = x0[3] - 1.5f * m_systemDmaData.m_s0[3];
+	m_systemDmaData.m_p1[3] = u[3]  - 1.5f * m_systemDmaData.m_s1[3];
+	m_systemDmaData.m_p2[3] = a_[3] - 1.5f * m_systemDmaData.m_s2[3];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2NewParticle::plat_update( void )
+{
+	if (m_params.m_LocalCoord)
+	{
+		update_position();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2NewParticle::plat_build( void )
+{
+
+	// initialise streams
+	m_max_streams = 5;
+	m_num_streams = 0;
+	mp_stream = new CParticleStream[m_max_streams]; 
+	mp_newest_stream = mp_stream + m_max_streams - 1;
+	mp_oldest_stream = mp_stream;
+	m_emitting = false;
+
+
+	// giftag for gs context
+	m_systemDmaData.m_GScontext[0] = 0x00008005;
+	m_systemDmaData.m_GScontext[1] = 0x10000000;
+	m_systemDmaData.m_GScontext[2] = 0x0000000E;
+	m_systemDmaData.m_GScontext[3] = 0x00000000;
+
+	// ALPHA_1 register
+	uint64 AlphaReg = 0;
+	uint8 fix=64;
+	switch (m_params.m_BlendMode)
+	{
+		case 0x54628ed7:		// Blend
+			AlphaReg = PackALPHA(0,1,0,1,0);
+			break;
+		case 0x02e58c18:		// Add
+			AlphaReg = PackALPHA(0,2,0,1,0);
+			break;
+		case 0xa7fd7d23:		// Sub
+		case 0xdea7e576:		// Subtract
+			AlphaReg = PackALPHA(2,0,0,1,0);
+			break;
+		case 0x40f44b8a:		// Modulate
+			AlphaReg = PackALPHA(1,2,0,2,0);
+			break;
+		case 0x68e77f40:		// Brighten
+			AlphaReg = PackALPHA(1,2,0,1,0);
+			break;
+		case 0x18b98905:		// FixBlend
+			AlphaReg = PackALPHA(0,1,2,1,fix);
+			break;
+		case 0xa86285a1:		// FixAdd
+			AlphaReg = PackALPHA(0,2,2,1,fix);
+			break;
+		case 0x0d7a749a:		// FixSub
+		case 0x0eea99ff:		// FixSubtract
+			AlphaReg = PackALPHA(2,0,2,1,fix);
+			break;
+		case 0x90b93703:		// FixModulate
+			AlphaReg = PackALPHA(1,2,2,2,fix);
+			break;
+		case 0xb8aa03c9:		// FixBrighten
+			AlphaReg = PackALPHA(1,2,2,1,fix);
+			break;
+		case 0x515e298e:		// Diffuse
+		case 0x806fff30:		// None
+			AlphaReg = PackALPHA(0,0,0,0,0);
+			break;
+		default:
+			Dbg_MsgAssert(0,("Illegal blend mode specified. Please use (fix)blend/add/sub/modulate/brighten or diffuse/none."));
+			break;
+	}
+	m_systemDmaData.m_GScontext[6] = 0x00000042;
+	*(uint64 *)&m_systemDmaData.m_GScontext[4] = AlphaReg;
+
+	// TEX0_1 and TEX1_1 registers
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( m_params.m_Texture );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+	//printf("m_Texture = %08X\n", m_params.m_Texture);
+	//printf("p_texture = %08X\n", p_texture);
+	//printf("p_ps2_texture = %08X\n", p_ps2_texture);
+	//printf("mp_engine_texture = %08X\n", mp_engine_texture);
+
+	m_systemDmaData.m_GScontext[10] = 0x00000006;
+
+	m_systemDmaData.m_GScontext[14] = 0x00000014;
+
+	if (mp_engine_texture)
+	{
+		//printf("\n\nTEX0_1 = %08X%08X\n\n\n",
+		//					(uint32)(mp_engine_texture->m_RegTEX0>>32),
+		//					(uint32)(mp_engine_texture->m_RegTEX0));
+
+		// texture coords
+		m_systemDmaData.m_u0 = 8;
+		m_systemDmaData.m_v0 = (mp_engine_texture->GetHeight()<<4) - 8;
+		m_systemDmaData.m_u1 = (mp_engine_texture->GetWidth() <<4) - 8;
+		m_systemDmaData.m_v1 = 8;
+	}
+
+	// TEST_1
+	m_systemDmaData.m_GScontext[18] = 0x00000047;
+	m_systemDmaData.m_GScontext[16] = 0x0005000B | m_params.m_AlphaCutoff<<4;
+
+	// FOG
+	m_systemDmaData.m_GScontext[22] = 0x0000000A;
+
+	// giftag for particles
+	m_systemDmaData.m_tagy = 0x60BB4000;
+	m_systemDmaData.m_tagz = 0x00535312;
+
+
+
+	// have already set m_Lifetime, which is called m_life in newflat
+
+	// x-component will be overwritten by vu1 code, so can store midtime there
+	*(float *)&m_systemDmaData.m_tagx = m_params.m_UseMidcolor ?
+										m_params.m_Lifetime * m_params.m_ColorMidpointPct * 0.01f :
+										0.0f;
+
+	// and now a load of redundant duplication of data, which should later be removed...
+
+	// convert 3-point -> PVA format
+	float t1 = m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f;
+	float t2 = m_params.m_Lifetime;
+	Mth::Vector x0,x1,x2,u,a_;
+
+	//printf("\n\n\n\n\n%g,%g,%g\n%g,%g,%g\n\n\n\n\n",
+	//	   m_params.m_BoxDims[0][3], m_params.m_BoxDims[1][3], m_params.m_BoxDims[2][3],
+	//	   m_params.m_BoxPos[0][3], m_params.m_BoxPos[1][3], m_params.m_BoxPos[2][3]);
+
+	x0    = m_params.m_BoxDims[0];
+	x0[3] = m_params.m_RadiusSpread[0];
+	x1    = m_params.m_BoxDims[1];
+	x1[3] = m_params.m_RadiusSpread[1];
+	x2    = m_params.m_BoxDims[2];
+	x2[3] = m_params.m_RadiusSpread[2];
+
+	if (m_params.m_UseMidpoint)
+	{
+		u  = ( t2*t2*(x1 - x0) - t1*t1*(x2 - x0) ) / ( t1*t2*(t2 - t1) );
+		a_ = ( t1*(x2 - x0) - t2*(x1 - x0) ) / ( t1*t2*(t2 - t1) );
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set(0,0,0,0);
+	}
+
+	m_systemDmaData.m_s0 = x0;
+	m_systemDmaData.m_s1 = u;
+	m_systemDmaData.m_s2 = a_;
+
+	update_position();
+
+
+	// colour
+	if (m_params.m_UseMidcolor)
+	{
+		float q0 = 100.0f / (m_params.m_Lifetime * m_params.m_ColorMidpointPct);
+		float q1 = 100.0f / (m_params.m_Lifetime * (100.0f - m_params.m_ColorMidpointPct));
+
+		m_systemDmaData.m_c0[0] = ((float)m_params.m_Color[1].r - (float)m_params.m_Color[0].r) * q0;
+		m_systemDmaData.m_c0[1] = ((float)m_params.m_Color[1].g - (float)m_params.m_Color[0].g) * q0;
+		m_systemDmaData.m_c0[2] = ((float)m_params.m_Color[1].b - (float)m_params.m_Color[0].b) * q0;
+		m_systemDmaData.m_c0[3] = ((float)m_params.m_Color[1].a - (float)m_params.m_Color[0].a) * q0;
+
+		m_systemDmaData.m_c1[0] = (float)m_params.m_Color[1].r;
+		m_systemDmaData.m_c1[1] = (float)m_params.m_Color[1].g;
+		m_systemDmaData.m_c1[2] = (float)m_params.m_Color[1].b;
+		m_systemDmaData.m_c1[3] = (float)m_params.m_Color[1].a;
+
+		m_systemDmaData.m_c2[0] = ((float)m_params.m_Color[2].r - (float)m_params.m_Color[1].r) * q1;
+		m_systemDmaData.m_c2[1] = ((float)m_params.m_Color[2].g - (float)m_params.m_Color[1].g) * q1;
+		m_systemDmaData.m_c2[2] = ((float)m_params.m_Color[2].b - (float)m_params.m_Color[1].b) * q1;
+		m_systemDmaData.m_c2[3] = ((float)m_params.m_Color[2].a - (float)m_params.m_Color[1].a) * q1;
+
+	}
+	else // else suppress mid-colour
+	{
+		float q = 1.0f / m_params.m_Lifetime;
+
+		m_systemDmaData.m_c1[0] = (float)m_params.m_Color[0].r;
+		m_systemDmaData.m_c1[1] = (float)m_params.m_Color[0].g;
+		m_systemDmaData.m_c1[2] = (float)m_params.m_Color[0].b;
+		m_systemDmaData.m_c1[3] = (float)m_params.m_Color[0].a;
+
+		m_systemDmaData.m_c2[0] = ((float)m_params.m_Color[2].r - (float)m_params.m_Color[0].r) * q;
+		m_systemDmaData.m_c2[1] = ((float)m_params.m_Color[2].g - (float)m_params.m_Color[0].g) * q;
+		m_systemDmaData.m_c2[2] = ((float)m_params.m_Color[2].b - (float)m_params.m_Color[0].b) * q;
+		m_systemDmaData.m_c2[3] = ((float)m_params.m_Color[2].a - (float)m_params.m_Color[0].a) * q;
+	}
+
+
+
+	// rotation matrix
+	//m_rotation = m_params.m_RotMatrix;
+	m_rotation.Identity();
+
+	#if 0
+	// invert rotation and apply to spatial params
+	// leaving this code a bit shoddy and slow until full transition to new-style params
+	Mth::Matrix mat;
+	mat=m_rotation;
+	mat.Transpose();
+	Mth::Vector vec;
+
+	vec = m_systemDmaData.m_p0 + 1.5f * m_systemDmaData.m_s0;
+	vec *= mat;
+	m_systemDmaData.m_p0 = vec - 1.5f * m_systemDmaData.m_s0;
+
+	vec = m_systemDmaData.m_p1 + 1.5f * m_systemDmaData.m_s1;
+	vec *= mat;
+	m_systemDmaData.m_p1 = vec - 1.5f * m_systemDmaData.m_s1;
+
+	vec = m_systemDmaData.m_p2 + 1.5f * m_systemDmaData.m_s2;
+	vec *= mat;
+	m_systemDmaData.m_p2 = vec - 1.5f * m_systemDmaData.m_s2;
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CPs2NewParticle::plat_destroy( void )
+{
+	
+	// Bit of a patch, but need to wait until particle system DMA packet has
+	// been used before we can delete it, otherwise memory might get corrupted
+	// in normal gameplay particle systems are rarely created
+	// but in "FireFight" it shows up a lot more.
+	// This really should be handled at a lower level
+	Nx::CEngine::sFinishRendering();
+	
+	
+	if( mp_stream )
+	{
+		delete [] mp_stream;
+	}
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxnewparticle.h b/Code/Gfx/NGPS/p_nxnewparticle.h
new file mode 100644
index 0000000..b16fd1a
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxnewparticle.h
@@ -0,0 +1,127 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticle.h										**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	PS2 implementation of new parametric particle system	**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_NGPS_P_NXNEWPARTICLE_H__
+#define __GFX_NGPS_P_NXNEWPARTICLE_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+
+namespace Nx
+{
+
+                        
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+
+class CParticleStream
+{
+public:
+	int						m_num_particles;
+	float					m_rate;
+	float					m_interval;
+	float					m_oldest_age;
+	uint32					m_seed[4];
+	void					AdvanceSeed(int num_places);
+};
+
+class CSystemDmaData
+{
+public:
+	uint32		m_GScontext[24];
+	uint32		m_u0,  m_v0,  m_u1,  m_v1;
+	Mth::Vector	m_p0;
+	Mth::Vector	m_p1;
+	Mth::Vector	m_p2;
+	Mth::Vector	m_s0;
+	Mth::Vector	m_s1;
+	Mth::Vector	m_s2;
+	Mth::Vector	m_c0;
+	Mth::Vector	m_c1;
+	Mth::Vector	m_c2;
+	uint32		m_tagx,m_tagy,m_tagz,m_tagw;
+} nAlign(128);
+
+
+class CPs2NewParticle : public CNewParticle
+{
+private:
+	void				update_position();
+
+	bool				m_emitting;
+	int					m_max_streams;
+	int					m_num_streams;
+	CParticleStream*	mp_stream;
+	CParticleStream*	mp_newest_stream;
+	CParticleStream*	mp_oldest_stream;
+	Mth::Matrix 		m_rotation;
+	CSystemDmaData		m_systemDmaData;
+	NxPs2::SSingleTexture*	mp_engine_texture;
+
+
+protected:
+	void	plat_render( void );
+	void	plat_update( void );
+	void	plat_build( void );
+	void	plat_destroy( void );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_NGPS_P_NXNEWPARTICLE_H__
+
+
diff --git a/Code/Gfx/NGPS/p_nxnewparticlemgr.cpp b/Code/Gfx/NGPS/p_nxnewparticlemgr.cpp
new file mode 100644
index 0000000..8f0e9ba
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxnewparticlemgr.cpp
@@ -0,0 +1,85 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticleMgr.cpp									**
+**																			**
+**	Created by:		3/25/03	-	SPG											**
+**																			**
+**	Description:	Ps2-specific parametric particle system manager			**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+CNewParticle*	CPs2NewParticleManager::plat_create_particle( void )
+{
+	return new CPs2NewParticle;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxnewparticlemgr.h b/Code/Gfx/NGPS/p_nxnewparticlemgr.h
new file mode 100644
index 0000000..3314429
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxnewparticlemgr.h
@@ -0,0 +1,77 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5 													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_nxnewparticlemgr.h									**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	PS2-specific new parametric particle system manager		**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_NGPS__P_NXNEWPARTICLEMGR_H__
+#define __GFX_NGPS__P_NXNEWPARTICLEMGR_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CPs2NewParticleManager : public CNewParticleManager
+{
+protected:
+	virtual	CNewParticle*	plat_create_particle( void );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_NGPS__P_NXNEWPARTICLEMGR_H__
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticle.cpp b/Code/Gfx/NGPS/p_nxparticle.cpp
new file mode 100644
index 0000000..e55fa83
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticle.cpp
@@ -0,0 +1,138 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/vu1code.h"
+
+
+#include "gfx/ngps/nx/mesh.h"
+#include "gfx/ngps/p_nxparticleflat.h"
+#include "gfx/ngps/p_nxparticleshaded.h"
+#include "gfx/ngps/p_nxparticlesmooth.h"
+#include "gfx/ngps/p_nxparticleglow.h"
+#include "gfx/ngps/p_nxparticlestar.h"
+#include "gfx/ngps/p_nxparticlesmoothstar.h"
+#include "gfx/ngps/p_nxparticleline.h"
+#include "gfx/ngps/p_nxparticleribbon.h"
+#include "gfx/ngps/p_nxparticleribbontrail.h"
+#include "gfx/ngps/p_nxparticlesmoothribbon.h"
+#include "gfx/ngps/p_nxparticleglowribbontrail.h"
+#include "gfx/ngps/p_nxparticlenewflat.h"
+
+
+namespace Nx
+{
+
+
+CParticle* plat_create_particle( uint32 checksum, uint32 type_checksum, int max_particles, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	// Types to add:
+	// 1. x Flat color quad
+	// 2. x Gouraud quad
+	// 3. x Quad with center point & outer color
+	// 4. x n sided glow (center color + outer color)
+	// 5. x n sided 2 layer glow ( center color + mid color + outer color)
+	// 6. x n spiked star (center color + mid color + spike color)
+	// 7. Lines (current color + previous color)
+	// 8. Ribbons - volumetric lines made from quads (current color + previous color)
+
+	switch ( type_checksum )
+	{
+		case 0xf4d8d486:		// Shaded
+			{
+				CPs2ParticleShaded* p_particle = new CPs2ParticleShaded( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x8addac1f:		// Smooth
+			{
+				CPs2ParticleSmooth* p_particle = new CPs2ParticleSmooth( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x15834eea:		// Glow
+			{
+				CPs2ParticleGlow* p_particle = new CPs2ParticleGlow( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x3624a5eb:		// Star
+			{
+				CPs2ParticleStar* p_particle = new CPs2ParticleStar( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x97cb7a9:		// SmoothStar
+			{
+				CPs2ParticleSmoothStar* p_particle = new CPs2ParticleSmoothStar( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x2eeb4b09:		// Line
+			{
+				CPs2ParticleLine* p_particle = new CPs2ParticleLine( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0xee6fc5b:		// Ribbon
+			{
+				CPs2ParticleRibbon* p_particle = new CPs2ParticleRibbon( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0xc4d5a4cb:		// RibbonTrail
+			{
+				CPs2ParticleRibbonTrail* p_particle = new CPs2ParticleRibbonTrail( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x3f109fcc:		// SmoothRibbon
+			{
+				CPs2ParticleSmoothRibbon* p_particle = new CPs2ParticleSmoothRibbon( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0x7ec7252d:		// GlowRibbonTrail
+			{
+				CPs2ParticleGlowRibbonTrail* p_particle = new CPs2ParticleGlowRibbonTrail( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+		case 0xdedfc057:		// NewFlat
+			{
+				CPs2ParticleNewFlat* p_particle = new CPs2ParticleNewFlat( checksum, max_streams, texture_checksum, blendmode_checksum, fix );
+				return static_cast( p_particle );
+			}
+		case 0xaab555bb:		// Flat
+		default:
+			{
+				CPs2ParticleFlat* p_particle = new CPs2ParticleFlat( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+				return static_cast( p_particle );
+			}
+			break;
+	}
+}
+
+} // Nx
+
+				
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticle.h b/Code/Gfx/NGPS/p_nxparticle.h
new file mode 100644
index 0000000..55197d5
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticle.h
@@ -0,0 +1,23 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLE_H__
+#define	__GFX_P_NX_PARTICLE_H__
+    
+#include "gfx/nxparticle.h"
+                   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+
+#endif 
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleflat.cpp b/Code/Gfx/NGPS/p_nxparticleflat.cpp
new file mode 100644
index 0000000..b2f221d
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleflat.cpp
@@ -0,0 +1,397 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleflat.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleFlat::CPs2ParticleFlat()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleFlat::CPs2ParticleFlat( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	m_start_color.r = 128;
+	m_start_color.g = 128;
+	m_start_color.b = 128;
+	m_start_color.a = 128;
+	m_mid_color.r = 128;
+	m_mid_color.g = 128;
+	m_mid_color.b = 128;
+	m_mid_color.a = 128;
+	m_end_color.r = 128;
+	m_end_color.g = 128;
+	m_end_color.b = 128;
+	m_end_color.a = 128;
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleFlat::~CPs2ParticleFlat()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleFlat::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleFlat::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleFlat::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleFlat::plat_get_num_particle_colors( void ) { return 1; }
+int CPs2ParticleFlat::plat_get_num_vertex_lists( void ) { return 1; }
+void CPs2ParticleFlat::plat_set_sr( int entry, uint8 value ) { m_start_color.r = value; }
+void CPs2ParticleFlat::plat_set_sg( int entry, uint8 value ) { m_start_color.g = value; }
+void CPs2ParticleFlat::plat_set_sb( int entry, uint8 value ) { m_start_color.b = value; }
+void CPs2ParticleFlat::plat_set_sa( int entry, uint8 value ) { m_start_color.a = value >> 1; }
+void CPs2ParticleFlat::plat_set_mr( int entry, uint8 value ) { m_mid_color.r = value; }
+void CPs2ParticleFlat::plat_set_mg( int entry, uint8 value ) { m_mid_color.g = value; }
+void CPs2ParticleFlat::plat_set_mb( int entry, uint8 value ) { m_mid_color.b = value; }
+void CPs2ParticleFlat::plat_set_ma( int entry, uint8 value ) { m_mid_color.a = value >> 1; }
+void CPs2ParticleFlat::plat_set_er( int entry, uint8 value ) { m_end_color.r = value; }
+void CPs2ParticleFlat::plat_set_eg( int entry, uint8 value ) { m_end_color.g = value; }
+void CPs2ParticleFlat::plat_set_eb( int entry, uint8 value ) { m_end_color.b = value; }
+void CPs2ParticleFlat::plat_set_ea( int entry, uint8 value ) { m_end_color.a = value >> 1; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+#if 0
+
+void CPs2ParticleFlat::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		tmp[0]		= pos - ss_right + ss_up;
+		tmp[1]		= pos + ss_right + ss_up;		
+		tmp[2]		= pos + ss_right - ss_up;		
+		tmp[3]		= pos - ss_right - ss_up;		
+	
+		Image::RGBA color;
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+		
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = &m_start_color;
+				p_col1 = &m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = &m_mid_color;
+				p_col1 = &m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = &m_start_color;
+			p_col1 = &m_end_color;
+		}
+
+		Image::RGBA start = *p_col0;
+		Image::RGBA end = *p_col1;
+
+		color.r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+		color.g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+		color.b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+		color.a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+	
+		NxPs2::CImmediateMode::sDrawQuadTexture( mp_engine_texture, tmp[0], tmp[1], tmp[2], tmp[3],
+												 *((uint32 *) &color),
+												 *((uint32 *) &color),
+												 *((uint32 *) &color),
+												 *((uint32 *) &color));
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+#else
+
+void CPs2ParticleFlat::plat_render( void )
+{
+	if (m_num_particles <= 0)
+		return;
+
+	Dbg_MsgAssert(mp_engine_texture, ("no support for non-textured sprites yet..."));
+
+	int i,j=0;
+	CParticleEntry *p_particle;
+	float *p_v;
+	float *p_xyzr=NULL;
+	uint32 *p_rgba=NULL;
+
+	// add a dma packet
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+
+	// VU context
+	NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));
+	NxPs2::vu1::StoreVec(*(NxPs2::Vec *)&NxPs2::render::InverseIntViewportScale);
+	NxPs2::vu1::StoreVec(*(NxPs2::Vec *)&NxPs2::render::InverseIntViewportOffset);
+	NxPs2::vu1::StoreMat(*(NxPs2::Mat *)&NxPs2::render::WorldToIntViewport);	// VF12-15
+	NxPs2::vu1::EndPrim(0);
+	NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF30));
+	NxPs2::vif::StoreV4_32F(640.0f, 480.0f, 0.0f, 0.0f);						// VF30
+	NxPs2::vif::StoreV4_32F(NxPs2::render::IntViewportScale[0]/NxPs2::render::Tx,	// VF31
+							NxPs2::render::IntViewportScale[1]/NxPs2::render::Ty,
+							0.0f, 0.0f);
+	NxPs2::vu1::EndPrim(0);
+
+	// GS context
+	NxPs2::gs::BeginPrim(ABS, 0, 0);
+	NxPs2::gs::Reg1(NxPs2::gs::ALPHA_1,	m_blend);
+	NxPs2::gs::Reg1(NxPs2::gs::TEX0_1,	mp_engine_texture->m_RegTEX0);
+	NxPs2::gs::Reg1(NxPs2::gs::TEX1_1,	mp_engine_texture->m_RegTEX1);
+	NxPs2::gs::Reg1(NxPs2::gs::ST,		PackST(0x3F800000,0x3F800000));
+	NxPs2::gs::Reg1(NxPs2::gs::RGBAQ,	PackRGBAQ(0,0,0,0,0x3F800000));
+	NxPs2::gs::EndPrim(0);
+
+	for (i=0, p_particle=mp_particle_array, p_v=mp_vertices; i 80)
+				j = 80;
+				
+			NxPs2::BeginModelPrimImmediate(NxPs2::gs::XYZ2		|
+										   NxPs2::gs::ST<<4		|
+										   NxPs2::gs::RGBAQ<<8	|
+										   NxPs2::gs::XYZ2<<12,
+										   4, SPRITE|ABE|TME, 1, VU1_ADDR(SpriteCull));
+
+			// create an unpack for the colours
+			NxPs2::vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, 3);
+			p_rgba = (uint32 *)NxPs2::dma::pLoc;
+			NxPs2::dma::pLoc += j * 4;
+			NxPs2::vif::EndUNPACK();
+
+			// and one for the positions (& sizes)
+			NxPs2::vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 4);
+			p_xyzr = (float *)NxPs2::dma::pLoc;
+			NxPs2::dma::pLoc += j * 16;
+			NxPs2::vif::EndUNPACK();
+
+			NxPs2::EndModelPrimImmediate(1);
+
+			NxPs2::vif::MSCAL(VU1_ADDR(Parser));
+		}
+
+		float t = p_particle->m_time / p_particle->m_life;
+
+		// just use the width for now...
+		float width = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * t );
+		
+		// store position
+		*p_xyzr++ = p_v[0]+m_pos[0];
+		*p_xyzr++ = p_v[1]+m_pos[1];
+		*p_xyzr++ = p_v[2]+m_pos[2];
+		*p_xyzr++ = width;
+
+		Image::RGBA start, end, colour;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( t < m_mid_time )
+			{
+				start = m_start_color;
+				end   = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				t /= m_mid_time;
+			}
+			else
+			{
+				start = m_mid_color;
+				end   = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				t = ( t - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			start = m_start_color;
+			end   = m_end_color;
+		}
+
+		// compute interpolated colour
+		colour.r = start.r + (uint8) (int) ( ((float)( end.r - start.r )) * t );
+		colour.g = start.g + (uint8) (int) ( ((float)( end.g - start.g )) * t );
+		colour.b = start.b + (uint8) (int) ( ((float)( end.b - start.b )) * t );
+		colour.a = start.a + (uint8) (int) ( ((float)( end.a - start.a )) * t );
+
+		// store colour
+		*p_rgba++ = *(uint32 *)&colour;
+	}
+
+	// restore transform
+	NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF12));		// was L_VF16
+	NxPs2::vu1::StoreMat(*(NxPs2::Mat *)&NxPs2::render::AdjustedWorldToViewport);		// VF16-19
+	NxPs2::vu1::EndPrim(1);
+	NxPs2::vif::MSCAL(VU1_ADDR(Parser));
+
+	// end the dma tag
+	NxPs2::dma::EndTag();
+
+}
+
+#endif
+
+} // Nx
+
diff --git a/Code/Gfx/NGPS/p_nxparticleflat.h b/Code/Gfx/NGPS/p_nxparticleflat.h
new file mode 100644
index 0000000..8052383
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleflat.h
@@ -0,0 +1,80 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEFLAT_H__
+#define	__GFX_P_NX_PARTICLEFLAT_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleFlat : public CParticle
+{
+public:
+							CPs2ParticleFlat();
+							CPs2ParticleFlat( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleFlat();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color;				// Start color for each corner.
+	Image::RGBA				m_mid_color;				// Mid color for each corner.
+	Image::RGBA				m_end_color;				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleglow.cpp b/Code/Gfx/NGPS/p_nxparticleglow.cpp
new file mode 100644
index 0000000..94e3bdb
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleglow.cpp
@@ -0,0 +1,290 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleGlow.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleGlow::CPs2ParticleGlow()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleGlow::CPs2ParticleGlow( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleGlow::~CPs2ParticleGlow()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlow::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlow::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlow::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleGlow::plat_get_num_particle_colors( void ) { return 3; }
+int CPs2ParticleGlow::plat_get_num_vertex_lists( void ) { return 1; }
+void CPs2ParticleGlow::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleGlow::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleGlow::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleGlow::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleGlow::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleGlow::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleGlow::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleGlow::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleGlow::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleGlow::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleGlow::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleGlow::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlow::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		Image::RGBA color[3];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		tmp[0]  = pos;
+		tmp[0] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[0] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		tmp[2]  = pos;
+		tmp[2] += ss_right * sinf( Mth::DegToRad( 0.0f ) );
+		tmp[2] += ss_up    * cosf( Mth::DegToRad( 0.0f ) );
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[1]  = pos;
+			tmp[1] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+			tmp[1] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+
+			tmp[3]  = pos;
+			tmp[3] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+			tmp[3] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+
+			NxPs2::CImmediateMode::sDrawGlowSegment( pos, tmp[0], tmp[1], tmp[2], tmp[3],
+													 *((uint32 *) &color[0]),
+													 *((uint32 *) &color[1]),
+													 *((uint32 *) &color[2]));
+			tmp[0] = tmp[1];
+			tmp[2] = tmp[3];
+		}
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleglow.h b/Code/Gfx/NGPS/p_nxparticleglow.h
new file mode 100644
index 0000000..7940f7c
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleglow.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEGlow_H__
+#define	__GFX_P_NX_PARTICLEGlow_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleGlow : public CParticle
+{
+public:
+							CPs2ParticleGlow();
+							CPs2ParticleGlow( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleGlow();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+	int						m_num_segments;
+	float					m_split;
+	Image::RGBA				m_start_color[3];			// Start color for each corner.
+	Image::RGBA				m_mid_color[3];				// Mid color for each corner.
+	Image::RGBA				m_end_color[3];				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleglowribbontrail.cpp b/Code/Gfx/NGPS/p_nxparticleglowribbontrail.cpp
new file mode 100644
index 0000000..ad6756f
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleglowribbontrail.cpp
@@ -0,0 +1,398 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleGlowRibbonTrail.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleGlowRibbonTrail::CPs2ParticleGlowRibbonTrail()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleGlowRibbonTrail::CPs2ParticleGlowRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	m_start_color = new Image::RGBA[m_num_vertex_buffers+3];
+	m_mid_color = new Image::RGBA[m_num_vertex_buffers+3];
+	m_end_color = new Image::RGBA[m_num_vertex_buffers+3];
+	for ( int lp = 0; lp < (m_num_vertex_buffers+3); lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleGlowRibbonTrail::~CPs2ParticleGlowRibbonTrail()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+	delete [] m_start_color;
+	delete [] m_mid_color;
+	delete [] m_end_color;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlowRibbonTrail::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlowRibbonTrail::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlowRibbonTrail::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleGlowRibbonTrail::plat_get_num_particle_colors( void ) { return m_num_vertex_buffers + 3; }
+int CPs2ParticleGlowRibbonTrail::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CPs2ParticleGlowRibbonTrail::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleGlowRibbonTrail::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleGlowRibbonTrail::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleGlowRibbonTrail::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleGlowRibbonTrail::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the ribbontrail.
+	
+//	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+//	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+//	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+//
+//	// Concatenate p_matrix with the emmission angle to create the direction.
+//	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+//
+//	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+//	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+//	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+//	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+//
+//	screen_right.Normalize();
+//	screen_up.Normalize();
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	Image::RGBA color[3];
+	Image::RGBA *p_col0;
+	Image::RGBA *p_col1;
+
+	for ( lp = 0, p_particle = mp_particle_array; lp < m_num_particles; lp++, p_particle++ )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		Mth::Vector	pos[2];
+		p_v = &mp_vertices[0][lp*3];
+		pos[0].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		p_v = &mp_vertices[1][lp*3];
+		pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos[1] - pos[0];
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+		Mth::Vector tmp[4];
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = &m_start_color[3];
+				p_col1 = &m_mid_color[3];
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = &m_mid_color[3];
+				p_col1 = &m_end_color[3];
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = &m_start_color[3];
+			p_col1 = &m_end_color[3];
+		}
+
+		Image::RGBA start = *p_col0++;
+		Image::RGBA end = *p_col1++;
+
+		color[0].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+		color[0].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+		color[0].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+		color[0].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+		tmp[0]		= pos[0] + ( perp_vec * w * m_split );
+		tmp[1]		= pos[0] - ( perp_vec * w * m_split );
+
+		for ( int c = 1; c < m_num_vertex_buffers; c++ )
+		{
+			start = *p_col0++;
+			end = *p_col1++;
+
+			color[1].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[1].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[1].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[1].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+			if ( c > 1 )
+			{
+				p_v = &mp_vertices[c][lp*3];
+				pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				part_vec = pos[1] - pos[0];
+				perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+			}
+
+			tmp[2]		= pos[1] - ( perp_vec * w * m_split );
+			tmp[3]		= pos[1] + ( perp_vec * w * m_split );
+
+			NxPs2::CImmediateMode::sDrawQuadTexture( mp_engine_texture, tmp[0], tmp[1], tmp[2], tmp[3], *((uint32 *) &color[0]), *((uint32 *) &color[0]), *((uint32 *) &color[1]), *((uint32 *) &color[1]) );
+
+			color[0] = color[1];
+			pos[0] = pos[1];
+			tmp[0] = tmp[3];
+			tmp[1] = tmp[2];
+		}
+
+		// Draw the glow.
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		p_v = &mp_vertices[0][lp*3];
+		pos[0].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		tmp[0]  = pos[0];
+		tmp[0] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[0] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		tmp[2]  = pos[0];
+		tmp[2] += ss_right * sinf( Mth::DegToRad( 0.0f ) );
+		tmp[2] += ss_up    * cosf( Mth::DegToRad( 0.0f ) );
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[1]  = pos[0];
+			tmp[1] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+			tmp[1] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+
+			tmp[3]  = pos[0];
+			tmp[3] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+			tmp[3] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) );
+
+			NxPs2::CImmediateMode::sDrawGlowSegment( pos[0], tmp[0], tmp[1], tmp[2], tmp[3],
+													 *((uint32 *) &color[0]),
+													 *((uint32 *) &color[1]),
+													 *((uint32 *) &color[2]));
+			tmp[0] = tmp[1];
+			tmp[2] = tmp[3];
+		}
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleglowribbontrail.h b/Code/Gfx/NGPS/p_nxparticleglowribbontrail.h
new file mode 100644
index 0000000..2ca1e6f
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleglowribbontrail.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEGlowRibbonTrail_H__
+#define	__GFX_P_NX_PARTICLEGlowRibbonTrail_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleGlowRibbonTrail : public CParticle
+{
+public:
+							CPs2ParticleGlowRibbonTrail();
+							CPs2ParticleGlowRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleGlowRibbonTrail();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	int						m_num_segments;
+	float					m_split;
+	Image::RGBA*			m_start_color;				// Start color for each corner.
+	Image::RGBA*			m_mid_color;				// Mid color for each corner.
+	Image::RGBA*			m_end_color;				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleline.cpp b/Code/Gfx/NGPS/p_nxparticleline.cpp
new file mode 100644
index 0000000..b3d568e
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleline.cpp
@@ -0,0 +1,254 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleLine.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleLine::CPs2ParticleLine()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleLine::CPs2ParticleLine( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffers.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	m_start_color = new Image::RGBA[m_num_vertex_buffers];
+	m_mid_color = new Image::RGBA[m_num_vertex_buffers];
+	m_end_color = new Image::RGBA[m_num_vertex_buffers];
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleLine::~CPs2ParticleLine()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleLine::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleLine::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleLine::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleLine::plat_get_num_particle_colors( void ) { return 2; }
+int CPs2ParticleLine::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CPs2ParticleLine::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleLine::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleLine::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleLine::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleLine::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleLine::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleLine::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleLine::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleLine::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleLine::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleLine::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleLine::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleLine::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v0;
+	float			*p_v1;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	Image::RGBA color[2];
+	Image::RGBA *p_col0;
+	Image::RGBA *p_col1;
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v0 = mp_vertices[0], p_v1 = mp_vertices[(m_num_vertex_buffers-1)]; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 2; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+		Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+	
+		NxPs2::CImmediateMode::sDrawLine( pos0, pos1, *((uint32 *) &color[0]), *((uint32 *) &color[1]) );
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleline.h b/Code/Gfx/NGPS/p_nxparticleline.h
new file mode 100644
index 0000000..8eb8ed4
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleline.h
@@ -0,0 +1,82 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLELine_H__
+#define	__GFX_P_NX_PARTICLELine_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleLine : public CParticle
+{
+public:
+							CPs2ParticleLine();
+							CPs2ParticleLine( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleLine();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA*			m_start_color;				// Start color for each corner.
+	Image::RGBA*			m_mid_color;				// Mid color for each corner.
+	Image::RGBA*			m_end_color;				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlenewflat.cpp b/Code/Gfx/NGPS/p_nxparticlenewflat.cpp
new file mode 100644
index 0000000..604162b
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlenewflat.cpp
@@ -0,0 +1,547 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+#include "gfx/ngps/nx/group.h"
+
+#include "gfx/ngps/p_nxparticlenewflat.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleNewFlat::CPs2ParticleNewFlat()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleNewFlat::CPs2ParticleNewFlat( uint32 checksum, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix )
+{
+	m_checksum = checksum;
+	m_max_streams = max_streams;
+	m_num_streams = 0;
+
+	// Get the texture.
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Allocate space for span information & streams. 
+	m_num_spans = 1;		// Currently defaulting to 1 span.
+	mp_span = new CParticleSpan[m_num_spans];
+	mp_stream = new CParticleStream[m_max_streams]; 
+
+	mp_newest_stream = mp_stream + m_max_streams - 1;
+	mp_oldest_stream = mp_stream;
+	m_life = 0.5f * (m_life_min + m_life_max);
+	m_emitting = false;
+
+
+	// set up system dma data, apart from new coefficients, which are calculated in plat_build_path()
+	m_systemDmaData.m_GScontext[0] = 0x00008004;
+	m_systemDmaData.m_GScontext[1] = 0x10000000;
+	m_systemDmaData.m_GScontext[2] = 0x0000000E;
+	m_systemDmaData.m_GScontext[3] = 0x00000000;
+
+	*(uint64 *)&m_systemDmaData.m_GScontext[4] = m_blend;
+	m_systemDmaData.m_GScontext[6] = 0x00000042;
+
+	if (mp_engine_texture)
+	{
+		m_systemDmaData.m_u0 = 8;
+		m_systemDmaData.m_v0 = (mp_engine_texture->GetHeight()<<4) - 8;
+		m_systemDmaData.m_u1 = (mp_engine_texture->GetWidth() <<4) - 8;
+		m_systemDmaData.m_v1 = 8;
+		//m_systemDmaData.m_GScontext[16] = ((mp_engine_texture->GetHeight()<<4) - 8) << 16 | 8;
+
+		*(uint64 *)&m_systemDmaData.m_GScontext[8] = mp_engine_texture->m_RegTEX0;
+		*(uint64 *)&m_systemDmaData.m_GScontext[12] = mp_engine_texture->m_RegTEX1;
+	}
+	m_systemDmaData.m_GScontext[10] = 0x00000006;
+	m_systemDmaData.m_GScontext[14] = 0x00000014;
+
+	m_systemDmaData.m_GScontext[16] = 0x0005000B /* | m_alphaCutoff<<4 */;	//101 0000 xxxx xxxx 1011
+	m_systemDmaData.m_GScontext[18] = 0x00000047;
+
+	m_systemDmaData.m_tagy = 0x60AB4000;
+	m_systemDmaData.m_tagz = 0x00434312;
+
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleNewFlat::~CPs2ParticleNewFlat()
+{
+	delete [] mp_span;
+	delete [] mp_stream;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleNewFlat::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleNewFlat::plat_set_position( int entry, int list, float x, float y, float z )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleNewFlat::plat_add_position( int entry, int list, float x, float y, float z )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleNewFlat::plat_get_num_particle_colors( void ) { return 1; };
+int CPs2ParticleNewFlat::plat_get_num_vertex_lists( void ) { return 0; };
+void CPs2ParticleNewFlat::plat_set_sr( int entry, uint8 value ) { m_start_color.r = value; }
+void CPs2ParticleNewFlat::plat_set_sg( int entry, uint8 value ) { m_start_color.g = value; }
+void CPs2ParticleNewFlat::plat_set_sb( int entry, uint8 value ) { m_start_color.b = value; }
+void CPs2ParticleNewFlat::plat_set_sa( int entry, uint8 value ) { m_start_color.a = value >> 1; }
+void CPs2ParticleNewFlat::plat_set_mr( int entry, uint8 value ) { m_mid_color.r = value; }
+void CPs2ParticleNewFlat::plat_set_mg( int entry, uint8 value ) { m_mid_color.g = value; }
+void CPs2ParticleNewFlat::plat_set_mb( int entry, uint8 value ) { m_mid_color.b = value; }
+void CPs2ParticleNewFlat::plat_set_ma( int entry, uint8 value ) { m_mid_color.a = value >> 1; }
+void CPs2ParticleNewFlat::plat_set_er( int entry, uint8 value ) { m_end_color.r = value; }
+void CPs2ParticleNewFlat::plat_set_eg( int entry, uint8 value ) { m_end_color.g = value; }
+void CPs2ParticleNewFlat::plat_set_eb( int entry, uint8 value ) { m_end_color.b = value; }
+void CPs2ParticleNewFlat::plat_set_ea( int entry, uint8 value ) { m_end_color.a = value >> 1; }
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+
+void CPs2ParticleNewFlat::plat_render( void )
+{
+	CParticleStream *p_stream;
+	int i;
+
+
+	//--------------------------------------------------------------------------------------------------------------------
+	// process the streams
+	if (m_emit_rate && (!m_emitting || (m_emit_rate != mp_newest_stream->m_rate)))
+	{	
+		if (m_num_streams < m_max_streams)
+		{
+			// add new stream to cyclic buffer
+			m_num_streams++;
+			mp_newest_stream++;
+			if (mp_newest_stream == mp_stream + m_max_streams)
+			{
+				mp_newest_stream = mp_stream;
+			}
+
+			// initialise new stream
+			mp_newest_stream->m_rate			= m_emit_rate;
+			mp_newest_stream->m_interval		= 1.0f/m_emit_rate;
+			mp_newest_stream->m_oldest_age		= 0.0f;
+			mp_newest_stream->m_num_particles	= 0;
+			mp_newest_stream->m_seed[0]			= rand();
+			mp_newest_stream->m_seed[1]			= rand();
+			mp_newest_stream->m_seed[2]			= rand();
+			mp_newest_stream->m_seed[3]			= rand();
+			m_emitting = true;
+		}
+		else
+		{
+			m_emitting = false;
+		}
+	}
+	else
+	{
+		m_emitting = m_emit_rate;
+	}
+
+	if (!m_num_streams)
+		return;
+
+	// age all streams
+	for (i=0, p_stream=mp_oldest_stream; im_oldest_age += 1.0f/60.0f;
+
+		// step pointer within cyclic buffer
+		p_stream++;
+		if (p_stream == mp_stream + m_max_streams)
+		{
+			p_stream = mp_stream;
+		}
+	}
+
+	// births into newest stream
+	if (m_emitting)
+	{
+		// how many particles so far emitted?
+		mp_newest_stream->m_num_particles = (int)(mp_newest_stream->m_oldest_age * mp_newest_stream->m_rate + 1.0f);
+	}
+
+	// deaths from oldest stream
+	if (mp_oldest_stream->m_oldest_age > m_life)
+	{
+		// work out number dead
+		int particles_dead = (int)((mp_oldest_stream->m_oldest_age - m_life) * mp_oldest_stream->m_rate + 1.0f);
+
+		// remove dead particles
+		mp_oldest_stream->m_num_particles -= particles_dead;
+
+		// should we keep processing the oldest stream?
+		if (mp_oldest_stream->m_num_particles>0 || (m_num_streams==1 && m_emitting))
+		{
+			// adjust age of oldest particle
+			mp_oldest_stream->m_oldest_age -= (float)particles_dead * mp_oldest_stream->m_interval;
+
+			// advance seed
+			mp_oldest_stream->AdvanceSeed(particles_dead);
+		}
+		else
+		{
+			// remove oldest stream and wrap in cyclic buffer if necessary
+			m_num_streams--;
+			mp_oldest_stream++;
+			if (mp_oldest_stream == mp_stream + m_max_streams)
+			{
+				mp_oldest_stream = mp_stream;
+			}
+			if (!m_num_streams)
+				return;
+		}
+	}
+
+
+	//--------------------------------------------------------------------------------------------------------------------
+	// now render the streams
+
+	if (mp_engine_texture)
+	{
+		// Mick:  If this is textured, we reset the TEX0, TEX1 regs as the texture might move
+		// Since 2D textures are dynamically packed every frame
+		*(uint64 *)&m_systemDmaData.m_GScontext[8] = mp_engine_texture->m_RegTEX0;
+		*(uint64 *)&m_systemDmaData.m_GScontext[12] = mp_engine_texture->m_RegTEX1;
+	}
+
+	// matrix is system rotation times view transform
+	m_systemDmaData.m_matrix = m_rotation * NxPs2::render::WorldToIntViewport;
+
+	// in the per-frame setup dma we must have:
+	// dma tag
+	// base
+	// offset
+
+	// set the group
+	NxPs2::dma::SetList(NxPs2::sGroup::pParticles);
+
+	// the system and streams will be loaded to a double-buffered input area of VUMem1
+	// ref the system data, and include an unpack for the system and streams:
+	NxPs2::dma::Tag(NxPs2::dma::ref, (sizeof(CSystemDmaDataFlat)>>4), (uint)&m_systemDmaData);
+	NxPs2::vif::STCYCL(1,1);
+	NxPs2::vif::UNPACK(0, V4_32, (sizeof(CSystemDmaDataFlat)>>4)+m_num_streams*2, REL, SIGNED, 0);
+
+	// construct a packet with data for each stream
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	for (i=0,p_stream=mp_oldest_stream; im_oldest_age;
+		((float  *)NxPs2::dma::pLoc)[1] = p_stream->m_interval;
+		((uint32 *)NxPs2::dma::pLoc)[2] = p_stream->m_num_particles;
+		((uint32 *)NxPs2::dma::pLoc)[3] = (p_stream==mp_newest_stream) ? 0x8000 : 0;
+		((uint32 *)NxPs2::dma::pLoc)[4] = p_stream->m_seed[0];
+		((uint32 *)NxPs2::dma::pLoc)[5] = p_stream->m_seed[1];
+		((uint32 *)NxPs2::dma::pLoc)[6] = p_stream->m_seed[2];
+		((uint32 *)NxPs2::dma::pLoc)[7] = p_stream->m_seed[3];
+
+		//printf("stream %d, num particles=%d, oldest age=%f\n", s, p_stream->m_num_particles, p_stream->m_oldest_age);
+
+		// step dma pointer
+		NxPs2::dma::pLoc += 8*4;
+
+		// count particles
+		NxPs2::render::sTotalNewParticles += p_stream->m_num_particles;
+	}
+
+	NxPs2::vif::MSCAL(10);	// sprites
+	NxPs2::dma::EndTag();
+	NxPs2::dma::SetList(NxPs2::sGroup::pEpilogue);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleNewFlat::plat_build_path( void )
+{
+	float midTime;
+
+	// set m_life
+	m_life = 0.5f * (m_life_min + m_life_max);
+
+	// set clamped local midtime variable
+	if (m_mid_time <= 0.0f)
+	{
+		midTime = 0.0f;
+	}
+	else if (m_mid_time >= 100.0f)
+	{
+		midTime = m_life;
+	}
+	else
+	{
+		midTime = m_mid_time * 0.01f * m_life;
+	}
+
+	// x-component will be overwritten by vu1 code, so can store midtime there
+	*(float *)&m_systemDmaData.m_tagx = midTime;
+
+
+	// constant part
+	m_systemDmaData.m_s0x = m_emit_w;
+	m_systemDmaData.m_s0y = 0.0f;
+	m_systemDmaData.m_s0z = m_emit_h;
+	m_systemDmaData.m_s0w = 0.0f;
+
+	m_systemDmaData.m_c0x = m_pos[0] - 1.5f * m_systemDmaData.m_s0x;
+	m_systemDmaData.m_c0y = m_pos[1] - 1.5f * m_systemDmaData.m_s0y;
+	m_systemDmaData.m_c0z = m_pos[2] - 1.5f * m_systemDmaData.m_s0z;
+	m_systemDmaData.m_c0w = m_sw     - 1.5f * m_systemDmaData.m_s0w;
+
+
+	// linear part
+	Mth::Vector vel(m_tx,m_ty,m_tz,0.0f);
+	float speed = 60.0f * 0.5 * (m_speed_min + m_speed_max);
+	vel.Normalize();
+	vel *= speed;
+	m_systemDmaData.m_s1x = speed * Mth::PI/2.0f * m_emit_angle_spread;
+	m_systemDmaData.m_s1y = 60.0f * (m_speed_max - m_speed_min);
+	m_systemDmaData.m_s1z = speed * Mth::PI/2.0f * m_emit_angle_spread;
+	m_systemDmaData.m_s1w = 0.0f;
+
+	m_systemDmaData.m_c1x = vel[0]               - 1.5f * m_systemDmaData.m_s1x;
+	m_systemDmaData.m_c1y = vel[1]               - 1.5f * m_systemDmaData.m_s1y;
+	m_systemDmaData.m_c1z = vel[2]               - 1.5f * m_systemDmaData.m_s1z;
+	m_systemDmaData.m_c1w = (m_ew-m_sw) / m_life - 1.5f * m_systemDmaData.m_s1w;
+
+
+	// quadratic part
+	m_systemDmaData.m_s2x = 0.0f;
+	m_systemDmaData.m_s2y = 0.0f;
+	m_systemDmaData.m_s2z = 0.0f;
+	m_systemDmaData.m_s2w = 0.0f;
+
+	m_systemDmaData.m_c2x = m_fx*0.5f*(60.0f*60.0f) - 1.5f * m_systemDmaData.m_s2x;
+	m_systemDmaData.m_c2y = m_fy*0.5f*(60.0f*60.0f) - 1.5f * m_systemDmaData.m_s2y;
+	m_systemDmaData.m_c2z = m_fz*0.5f*(60.0f*60.0f) - 1.5f * m_systemDmaData.m_s2z;
+	m_systemDmaData.m_c2w = 0.0f                    - 1.5f * m_systemDmaData.m_s2w;
+
+
+	// cubic part
+	//m_systemDmaData.m_s3x = 0.0f;
+	//m_systemDmaData.m_s3y = 0.0f;
+	//m_systemDmaData.m_s3z = 0.0f;
+	//m_systemDmaData.m_s3w = 0.0f;
+
+	//m_systemDmaData.m_c3x = 0.0f - 1.5f * m_systemDmaData.m_s3x;
+	//m_systemDmaData.m_c3y = 0.0f - 1.5f * m_systemDmaData.m_s3y;
+	//m_systemDmaData.m_c3z = 0.0f - 1.5f * m_systemDmaData.m_s3z;
+	//m_systemDmaData.m_c3w = 0.0f - 1.5f * m_systemDmaData.m_s3w;
+	
+
+	// colour
+	if (m_mid_time >= 0.0f)		// if using mid-colour
+	{
+		float q0 = 1.0f / midTime;
+		float q1 = 1.0f / (m_life - midTime);
+
+		m_systemDmaData.m_c0r = ((float)m_mid_color.r - (float)m_start_color.r) * q0;
+		m_systemDmaData.m_c0g = ((float)m_mid_color.g - (float)m_start_color.g) * q0;
+		m_systemDmaData.m_c0b = ((float)m_mid_color.b - (float)m_start_color.b) * q0;
+		m_systemDmaData.m_c0a = ((float)m_mid_color.a - (float)m_start_color.a) * q0;
+
+		m_systemDmaData.m_c1r = (float)m_mid_color.r;
+		m_systemDmaData.m_c1g = (float)m_mid_color.g;
+		m_systemDmaData.m_c1b = (float)m_mid_color.b;
+		m_systemDmaData.m_c1a = (float)m_mid_color.a;
+
+		m_systemDmaData.m_c2r = ((float)m_end_color.r - (float)m_mid_color.r) * q1;
+		m_systemDmaData.m_c2g = ((float)m_end_color.g - (float)m_mid_color.g) * q1;
+		m_systemDmaData.m_c2b = ((float)m_end_color.b - (float)m_mid_color.b) * q1;
+		m_systemDmaData.m_c2a = ((float)m_end_color.a - (float)m_mid_color.a) * q1;
+	}
+	else						// else suppress mid-colour
+	{
+		float q = 1.0f / m_life;
+
+		m_systemDmaData.m_c1r = (float)m_start_color.r;
+		m_systemDmaData.m_c1g = (float)m_start_color.g;
+		m_systemDmaData.m_c1b = (float)m_start_color.b;
+		m_systemDmaData.m_c1a = (float)m_start_color.a;
+
+		m_systemDmaData.m_c2r = ((float)m_end_color.r - (float)m_start_color.r) * q;
+		m_systemDmaData.m_c2g = ((float)m_end_color.g - (float)m_start_color.g) * q;
+		m_systemDmaData.m_c2b = ((float)m_end_color.b - (float)m_start_color.b) * q;
+		m_systemDmaData.m_c2a = ((float)m_end_color.a - (float)m_start_color.a) * q;
+	}
+
+
+	// rotation matrix, hardwired to the identity for now
+	m_rotation.Identity();
+
+	// temp test
+	//m_rotation[0][0] =  0.8;
+	//m_rotation[0][1] =  0.6;
+	//m_rotation[1][0] = -0.6;
+	//m_rotation[1][1] =  0.8;
+
+	// invert rotation and apply to spatial params
+	// leaving this code a bit shoddy and slow until full transition to new-style params
+	Mth::Matrix mat;
+	mat=m_rotation;
+	mat.Transpose();
+	Mth::Vector vec;
+
+	vec[0] = m_systemDmaData.m_c0x + 1.5f * m_systemDmaData.m_s0x;
+	vec[1] = m_systemDmaData.m_c0y + 1.5f * m_systemDmaData.m_s0y;
+	vec[2] = m_systemDmaData.m_c0z + 1.5f * m_systemDmaData.m_s0z;
+	vec[3] = 0;
+	vec *= mat;
+	m_systemDmaData.m_c0x = vec[0] - 1.5f * m_systemDmaData.m_s0x;
+	m_systemDmaData.m_c0y = vec[1] - 1.5f * m_systemDmaData.m_s0y;
+	m_systemDmaData.m_c0z = vec[2] - 1.5f * m_systemDmaData.m_s0z;
+
+	vec[0] = m_systemDmaData.m_c1x + 1.5f * m_systemDmaData.m_s1x;
+	vec[1] = m_systemDmaData.m_c1y + 1.5f * m_systemDmaData.m_s1y;
+	vec[2] = m_systemDmaData.m_c1z + 1.5f * m_systemDmaData.m_s1z;
+	vec[3] = 0;
+	vec *= mat;
+	m_systemDmaData.m_c1x = vec[0] - 1.5f * m_systemDmaData.m_s1x;
+	m_systemDmaData.m_c1y = vec[1] - 1.5f * m_systemDmaData.m_s1y;
+	m_systemDmaData.m_c1z = vec[2] - 1.5f * m_systemDmaData.m_s1z;
+	
+	vec[0] = m_systemDmaData.m_c2x + 1.5f * m_systemDmaData.m_s2x;
+	vec[1] = m_systemDmaData.m_c2y + 1.5f * m_systemDmaData.m_s2y;
+	vec[2] = m_systemDmaData.m_c2z + 1.5f * m_systemDmaData.m_s2z;
+	vec[3] = 0;
+	vec *= mat;
+	m_systemDmaData.m_c2x = vec[0] - 1.5f * m_systemDmaData.m_s2x;
+	m_systemDmaData.m_c2y = vec[1] - 1.5f * m_systemDmaData.m_s2y;
+	m_systemDmaData.m_c2z = vec[2] - 1.5f * m_systemDmaData.m_s2z;
+
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CParticleStream::AdvanceSeed(int num_places)
+{
+	for (int i=0; i>4 ^ m_seed[3]>>22) & 1);
+		m_seed[1] ^= m_seed[0];
+		m_seed[2] ^= m_seed[1];
+		m_seed[3] ^= m_seed[2];
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleNewFlat::GetNumParticles()
+{
+	int i, total=0;
+	CParticleStream *p_stream;
+
+	for (i=0, p_stream=mp_oldest_stream; im_num_particles;
+
+		// step pointer within cyclic buffer
+		p_stream++;
+		if (p_stream == mp_stream + m_max_streams)
+		{
+			p_stream = mp_stream;
+		}
+	}
+
+	return total;
+}
+
+
+} // Nx
+
diff --git a/Code/Gfx/NGPS/p_nxparticlenewflat.h b/Code/Gfx/NGPS/p_nxparticlenewflat.h
new file mode 100644
index 0000000..8593ba4
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlenewflat.h
@@ -0,0 +1,131 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLENEWFLAT_H__
+#define	__GFX_P_NX_PARTICLENEWFLAT_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+class CParticleSpan
+{
+public:
+	float					m_sx,m_sy,m_sz;			// Start, mid & end points of span path.
+	float					m_mx,m_my,m_mz;			// This should really be the coefficients you need to
+	float					m_ex,m_ey,m_ez;			// describe the path, not the raw position data.
+	unsigned char			m_sr,m_sg,m_sb,m_sa;	// Start color
+	unsigned char			m_er,m_eg,m_eb,m_ea;	// End color
+};
+
+class CParticleStream
+{
+public:
+	int						m_num_particles;
+	float					m_rate;
+	float					m_interval;
+	float					m_oldest_age;
+	uint32					m_seed[4];
+	void					AdvanceSeed(int num_places);
+};
+
+class CSystemDmaDataFlat
+{
+public:
+	uint32					m_GScontext[20];
+	uint32					m_u0,  m_v0,  m_u1,  m_v1;
+	float					m_c0x, m_c0y, m_c0z, m_c0w;
+	float					m_c1x, m_c1y, m_c1z, m_c1w;
+	float					m_c2x, m_c2y, m_c2z, m_c2w;
+	float					m_s0x, m_s0y, m_s0z, m_s0w;
+	float					m_s1x, m_s1y, m_s1z, m_s1w;
+	float					m_s2x, m_s2y, m_s2z, m_s2w;
+	float					m_c0r, m_c0g, m_c0b, m_c0a;
+	float					m_c1r, m_c1g, m_c1b, m_c1a;
+	float					m_c2r, m_c2g, m_c2b, m_c2a;
+	uint32					m_tagx,m_tagy,m_tagz,m_tagw;
+	Mth::Matrix				m_matrix;
+} nAlign(128);
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleNewFlat : public CParticle
+{
+public:
+							CPs2ParticleNewFlat();
+							CPs2ParticleNewFlat( uint32 checksum, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix );
+	virtual 				~CPs2ParticleNewFlat();
+
+	int						GetNumParticles();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+	void					plat_build_path( void );
+		
+	Image::RGBA				m_start_color;				// Start color for each corner.
+	Image::RGBA				m_mid_color;				// Mid color for each corner.
+	Image::RGBA				m_end_color;				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+
+	uint64					m_blend;
+
+	int						m_max_streams;			// Maximum number of particle streams.
+	int						m_num_streams;			// Number of active streams.
+	int						m_num_spans;			// Number of span segments.
+
+	CParticleSpan*			mp_span;
+	CParticleStream*		mp_stream;
+	CParticleStream*		mp_newest_stream;
+	CParticleStream*		mp_oldest_stream;
+	float					m_life;
+	bool					m_emitting;
+
+
+	Mth::Matrix				m_rotation;
+
+	// dma data
+	CSystemDmaDataFlat			m_systemDmaData;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleribbon.cpp b/Code/Gfx/NGPS/p_nxparticleribbon.cpp
new file mode 100644
index 0000000..e830be8
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleribbon.cpp
@@ -0,0 +1,284 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleRibbon.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleRibbon::CPs2ParticleRibbon()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleRibbon::CPs2ParticleRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 2; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleRibbon::~CPs2ParticleRibbon()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;	
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbon::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbon::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbon::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleRibbon::plat_get_num_particle_colors( void ) { return 2; }
+int CPs2ParticleRibbon::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CPs2ParticleRibbon::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleRibbon::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleRibbon::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleRibbon::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleRibbon::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleRibbon::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleRibbon::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleRibbon::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleRibbon::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleRibbon::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleRibbon::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleRibbon::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbon::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+//	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+//	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+//	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+//
+//	// Concatenate p_matrix with the emmission angle to create the direction.
+//	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+//
+//	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+//	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+//	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+//	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+//
+//	screen_right.Normalize();
+//	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v0;
+	float			*p_v1;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	Image::RGBA color[2];
+	Image::RGBA *p_col0;
+	Image::RGBA *p_col1;
+
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v0 = mp_vertices[0], p_v1 = mp_vertices[(m_num_vertex_buffers-1)]; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+		Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos1 - pos0;
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+//		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+//		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+//		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+//	
+		tmp[0]		= pos0 + ( perp_vec * w );
+		tmp[1]		= pos0 - ( perp_vec * w );
+		tmp[2]		= pos1 - ( perp_vec * w );
+		tmp[3]		= pos1 + ( perp_vec * w );
+	
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 2; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		NxPs2::CImmediateMode::sDrawQuadTexture( mp_engine_texture, tmp[0], tmp[1], tmp[2], tmp[3], *((uint32 *) &color[0]), *((uint32 *) &color[0]), *((uint32 *) &color[1]), *((uint32 *) &color[1]) );
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleribbon.h b/Code/Gfx/NGPS/p_nxparticleribbon.h
new file mode 100644
index 0000000..a0de0be
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleribbon.h
@@ -0,0 +1,82 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLERibbon_H__
+#define	__GFX_P_NX_PARTICLERibbon_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleRibbon : public CParticle
+{
+public:
+							CPs2ParticleRibbon();
+							CPs2ParticleRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleRibbon();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color[2];			// Start color for each corner.
+	Image::RGBA				m_mid_color[2];				// Mid color for each corner.
+	Image::RGBA				m_end_color[2];				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleribbontrail.cpp b/Code/Gfx/NGPS/p_nxparticleribbontrail.cpp
new file mode 100644
index 0000000..b3025f0
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleribbontrail.cpp
@@ -0,0 +1,312 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleRibbonTrail.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleRibbonTrail::CPs2ParticleRibbonTrail()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleRibbonTrail::CPs2ParticleRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	m_start_color = new Image::RGBA[m_num_vertex_buffers];
+	m_mid_color = new Image::RGBA[m_num_vertex_buffers];
+	m_end_color = new Image::RGBA[m_num_vertex_buffers];
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleRibbonTrail::~CPs2ParticleRibbonTrail()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;	
+	delete [] m_start_color;
+	delete [] m_mid_color;
+	delete [] m_end_color;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbonTrail::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbonTrail::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbonTrail::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleRibbonTrail::plat_get_num_particle_colors( void ) { return m_num_vertex_buffers; }
+int CPs2ParticleRibbonTrail::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CPs2ParticleRibbonTrail::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleRibbonTrail::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleRibbonTrail::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleRibbonTrail::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleRibbonTrail::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleRibbonTrail::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleRibbonTrail::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleRibbonTrail::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleRibbonTrail::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleRibbonTrail::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleRibbonTrail::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleRibbonTrail::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleRibbonTrail::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+//	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+//	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+//	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+//
+//	// Concatenate p_matrix with the emmission angle to create the direction.
+//	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+//
+//	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+//	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+//	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+//	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+//
+//	screen_right.Normalize();
+//	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	Image::RGBA color[2];
+	Image::RGBA *p_col0;
+	Image::RGBA *p_col1;
+
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+
+	for ( lp = 0, p_particle = mp_particle_array; lp < m_num_particles; lp++, p_particle++ )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		Mth::Vector	pos[2];
+		p_v = &mp_vertices[0][lp*3];
+		pos[0].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		p_v = &mp_vertices[1][lp*3];
+		pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos[1] - pos[0];
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		
+		Mth::Vector tmp[4];
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		Image::RGBA start = *p_col0++;
+		Image::RGBA end = *p_col1++;
+
+		color[0].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+		color[0].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+		color[0].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+		color[0].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+		tmp[0]		= pos[0] + ( perp_vec * w );
+		tmp[1]		= pos[0] - ( perp_vec * w );
+
+		for ( int c = 1; c < m_num_vertex_buffers; c++ )
+		{
+			start = *p_col0++;
+			end = *p_col1++;
+
+			color[1].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[1].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[1].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[1].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+			if ( c > 1 )
+			{
+				p_v = &mp_vertices[c][lp*3];
+				pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				part_vec = pos[1] - pos[0];
+				perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+			}
+
+			tmp[2]		= pos[1] - ( perp_vec * w );
+			tmp[3]		= pos[1] + ( perp_vec * w );
+
+			NxPs2::CImmediateMode::sDrawQuadTexture( mp_engine_texture, tmp[0], tmp[1], tmp[2], tmp[3], *((uint32 *) &color[0]), *((uint32 *) &color[0]), *((uint32 *) &color[1]), *((uint32 *) &color[1]) );
+
+			color[0] = color[1];
+			pos[0] = pos[1];
+			tmp[0] = tmp[3];
+			tmp[1] = tmp[2];
+		}
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleribbontrail.h b/Code/Gfx/NGPS/p_nxparticleribbontrail.h
new file mode 100644
index 0000000..896b1cc
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleribbontrail.h
@@ -0,0 +1,82 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLERibbonTrail_H__
+#define	__GFX_P_NX_PARTICLERibbonTrail_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleRibbonTrail : public CParticle
+{
+public:
+							CPs2ParticleRibbonTrail();
+							CPs2ParticleRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleRibbonTrail();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA*			m_start_color;				// Start color for each corner.
+	Image::RGBA*			m_mid_color;				// Mid color for each corner.
+	Image::RGBA*			m_end_color;				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleshaded.cpp b/Code/Gfx/NGPS/p_nxparticleshaded.cpp
new file mode 100644
index 0000000..349bb73
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleshaded.cpp
@@ -0,0 +1,271 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleShaded.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleShaded::CPs2ParticleShaded()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleShaded::CPs2ParticleShaded( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 4; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleShaded::~CPs2ParticleShaded()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleShaded::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleShaded::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleShaded::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleShaded::plat_get_num_particle_colors( void ) { return 4; }
+int CPs2ParticleShaded::plat_get_num_vertex_lists( void ) { return 1; }
+void CPs2ParticleShaded::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleShaded::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleShaded::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleShaded::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleShaded::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleShaded::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleShaded::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleShaded::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleShaded::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleShaded::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleShaded::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleShaded::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleShaded::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		tmp[0]		= pos - ss_right + ss_up;
+		tmp[1]		= pos + ss_right + ss_up;		
+		tmp[2]		= pos + ss_right - ss_up;		
+		tmp[3]		= pos - ss_right - ss_up;		
+	
+		Image::RGBA color[4];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+		
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 4; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+		NxPs2::CImmediateMode::sDrawQuadTexture( mp_engine_texture, tmp[0], tmp[1], tmp[2], tmp[3],
+												 *((uint32 *) &color[0]),
+												 *((uint32 *) &color[1]),
+												 *((uint32 *) &color[2]),
+												 *((uint32 *) &color[3]));
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticleshaded.h b/Code/Gfx/NGPS/p_nxparticleshaded.h
new file mode 100644
index 0000000..a22113f
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticleshaded.h
@@ -0,0 +1,81 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEShaded_H__
+#define	__GFX_P_NX_PARTICLEShaded_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleShaded : public CParticle
+{
+public:
+							CPs2ParticleShaded();
+							CPs2ParticleShaded( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleShaded();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color[4];			// Start color for each corner.
+	Image::RGBA				m_mid_color[4];				// Mid color for each corner.
+	Image::RGBA				m_end_color[4];				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlesmooth.cpp b/Code/Gfx/NGPS/p_nxparticlesmooth.cpp
new file mode 100644
index 0000000..772aa33
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlesmooth.cpp
@@ -0,0 +1,270 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleSmooth.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmooth::CPs2ParticleSmooth()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmooth::CPs2ParticleSmooth( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 2; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmooth::~CPs2ParticleSmooth()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmooth::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmooth::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmooth::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleSmooth::plat_get_num_particle_colors( void ) { return 2; }
+int CPs2ParticleSmooth::plat_get_num_vertex_lists( void ) { return 1; }
+void CPs2ParticleSmooth::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleSmooth::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleSmooth::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleSmooth::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleSmooth::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleSmooth::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleSmooth::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleSmooth::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleSmooth::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleSmooth::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleSmooth::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleSmooth::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmooth::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		tmp[0]		= pos - ss_right + ss_up;
+		tmp[1]		= pos + ss_right + ss_up;		
+		tmp[2]		= pos + ss_right - ss_up;		
+		tmp[3]		= pos - ss_right - ss_up;		
+	
+		Image::RGBA color[2];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+		
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 2; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+		NxPs2::CImmediateMode::sDraw5QuadTexture( mp_engine_texture, pos, tmp[0], tmp[1], tmp[2], tmp[3],
+												  *((uint32 *) &color[0]),
+												  *((uint32 *) &color[1]));
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlesmooth.h b/Code/Gfx/NGPS/p_nxparticlesmooth.h
new file mode 100644
index 0000000..aa30a82
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlesmooth.h
@@ -0,0 +1,82 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLESmooth_H__
+#define	__GFX_P_NX_PARTICLESmooth_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleSmooth : public CParticle
+{
+public:
+							CPs2ParticleSmooth();
+							CPs2ParticleSmooth( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleSmooth();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color[2];			// Start color for each corner.
+	Image::RGBA				m_mid_color[2];				// Mid color for each corner.
+	Image::RGBA				m_end_color[2];				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlesmoothribbon.cpp b/Code/Gfx/NGPS/p_nxparticlesmoothribbon.cpp
new file mode 100644
index 0000000..a4553f3
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlesmoothribbon.cpp
@@ -0,0 +1,285 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleSmoothRibbon.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmoothRibbon::CPs2ParticleSmoothRibbon()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmoothRibbon::CPs2ParticleSmoothRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 4; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmoothRibbon::~CPs2ParticleSmoothRibbon()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;	
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothRibbon::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothRibbon::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothRibbon::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleSmoothRibbon::plat_get_num_particle_colors( void ) { return 4; }
+int CPs2ParticleSmoothRibbon::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CPs2ParticleSmoothRibbon::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleSmoothRibbon::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleSmoothRibbon::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleSmoothRibbon::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleSmoothRibbon::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleSmoothRibbon::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleSmoothRibbon::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleSmoothRibbon::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleSmoothRibbon::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleSmoothRibbon::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleSmoothRibbon::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleSmoothRibbon::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothRibbon::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+//	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+//	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+//	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+//
+//	// Concatenate p_matrix with the emmission angle to create the direction.
+//	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+//
+//	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+//	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+//	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+//	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+//
+//	screen_right.Normalize();
+//	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v0;
+	float			*p_v1;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	Image::RGBA color[4];
+	Image::RGBA *p_col0;
+	Image::RGBA *p_col1;
+
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v0 = mp_vertices[0], p_v1 = mp_vertices[(m_num_vertex_buffers-1)]; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+		Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+
+		Mth::Vector	part_vec = pos1 - pos0;
+		Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+		perp_vec.Normalize();
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+//		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+//		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+//		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+//	
+		tmp[0]		= pos0 + ( perp_vec * w );
+		tmp[1]		= pos0 - ( perp_vec * w );
+		tmp[2]		= pos1 - ( perp_vec * w );
+		tmp[3]		= pos1 + ( perp_vec * w );
+	
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 4; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		NxPs2::CImmediateMode::sDrawQuadTexture( mp_engine_texture, pos0, tmp[0], tmp[3], pos1, *((uint32 *) &color[0]), *((uint32 *) &color[1]), *((uint32 *) &color[2]), *((uint32 *) &color[3]) );
+		NxPs2::CImmediateMode::sDrawQuadTexture( mp_engine_texture, pos0, tmp[1], tmp[2], pos1, *((uint32 *) &color[0]), *((uint32 *) &color[1]), *((uint32 *) &color[2]), *((uint32 *) &color[3]) );
+	}
+	
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlesmoothribbon.h b/Code/Gfx/NGPS/p_nxparticlesmoothribbon.h
new file mode 100644
index 0000000..68aa49b
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlesmoothribbon.h
@@ -0,0 +1,82 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLESmoothRibbon_H__
+#define	__GFX_P_NX_PARTICLESmoothRibbon_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleSmoothRibbon : public CParticle
+{
+public:
+							CPs2ParticleSmoothRibbon();
+							CPs2ParticleSmoothRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleSmoothRibbon();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	//	void*					mp_display_list;
+//	int						m_display_list_size;
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color[4];			// Start color for each corner.
+	Image::RGBA				m_mid_color[4];				// Mid color for each corner.
+	Image::RGBA				m_end_color[4];				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlesmoothstar.cpp b/Code/Gfx/NGPS/p_nxparticlesmoothstar.cpp
new file mode 100644
index 0000000..8e5d6ca
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlesmoothstar.cpp
@@ -0,0 +1,290 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleSmoothStar.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmoothStar::CPs2ParticleSmoothStar()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmoothStar::CPs2ParticleSmoothStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+	
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleSmoothStar::~CPs2ParticleSmoothStar()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothStar::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothStar::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothStar::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleSmoothStar::plat_get_num_particle_colors( void ) { return 3; }
+int CPs2ParticleSmoothStar::plat_get_num_vertex_lists( void ) { return 1; }
+void CPs2ParticleSmoothStar::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleSmoothStar::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleSmoothStar::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleSmoothStar::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleSmoothStar::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleSmoothStar::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleSmoothStar::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleSmoothStar::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleSmoothStar::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleSmoothStar::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleSmoothStar::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleSmoothStar::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleSmoothStar::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[4];
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		Image::RGBA color[3];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			Image::RGBA SmoothStart = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = SmoothStart.r + (uint8)(( ((float)( end.r - SmoothStart.r )) * terp ));
+			color[c].g = SmoothStart.g + (uint8)(( ((float)( end.g - SmoothStart.g )) * terp ));
+			color[c].b = SmoothStart.b + (uint8)(( ((float)( end.b - SmoothStart.b )) * terp ));
+			color[c].a = SmoothStart.a + (uint8)(( ((float)( end.a - SmoothStart.a )) * terp ));
+		}
+
+		tmp[1]  = pos;
+		tmp[1] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[1] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[0]  = pos;
+			tmp[0] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) ) * m_split;
+			tmp[0] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) ) * m_split;
+			
+			tmp[3]  = pos;
+			tmp[3] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 2 ) ) ) ) * m_split;
+			tmp[3] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 2 ) ) ) ) * m_split;
+			
+			tmp[2]  = pos;
+			tmp[2] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+			tmp[2] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+
+			NxPs2::CImmediateMode::sDrawSmoothStarSegment( tmp[0], pos, tmp[1], tmp[2], tmp[3],
+														  *((uint32 *) &color[0]),
+														  *((uint32 *) &color[1]),
+														  *((uint32 *) &color[2]));
+			tmp[1] = tmp[3];
+		}
+	}
+	
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlesmoothstar.h b/Code/Gfx/NGPS/p_nxparticlesmoothstar.h
new file mode 100644
index 0000000..7df32a1
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlesmoothstar.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLESmoothStar_H__
+#define	__GFX_P_NX_PARTICLESmoothStar_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleSmoothStar : public CParticle
+{
+public:
+							CPs2ParticleSmoothStar();
+							CPs2ParticleSmoothStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleSmoothStar();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+	int						m_num_segments;
+	float					m_split;
+	Image::RGBA				m_start_color[3];			// SmoothStart color for each corner.
+	Image::RGBA				m_mid_color[3];				// Mid color for each corner.
+	Image::RGBA				m_end_color[3];				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlestar.cpp b/Code/Gfx/NGPS/p_nxparticlestar.cpp
new file mode 100644
index 0000000..5ca5efe
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlestar.cpp
@@ -0,0 +1,286 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxparticleStar.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleStar::CPs2ParticleStar()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleStar::CPs2ParticleStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum = checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+//	// Create the engine representation.
+//	mp_engine_particle = new NxPs2::sParticleSystem( max_particles, texture_checksum, blendmode_checksum, fix );
+//
+
+	// Get the texture.
+
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_engine_texture = NULL;
+
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_engine_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set blendmode.
+	m_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+
+	m_mid_time = -1.0f;
+
+	m_num_segments = num_segments;
+	m_split = split;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2ParticleStar::~CPs2ParticleStar()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+//	delete mp_engine_particle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleStar::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleStar::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleStar::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CPs2ParticleStar::plat_get_num_particle_colors( void ) { return 3; }
+int CPs2ParticleStar::plat_get_num_vertex_lists( void ) { return 1; }
+void CPs2ParticleStar::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CPs2ParticleStar::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CPs2ParticleStar::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CPs2ParticleStar::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CPs2ParticleStar::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CPs2ParticleStar::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CPs2ParticleStar::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CPs2ParticleStar::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CPs2ParticleStar::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CPs2ParticleStar::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CPs2ParticleStar::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CPs2ParticleStar::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2ParticleStar::plat_render( void )
+{
+	if (m_num_particles == 0)
+		return;
+
+	// Draw the particles.
+	
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	//Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraOrientation;
+	Mth::Matrix* p_matrix = (Mth::Matrix*)&NxPs2::render::CameraToWorldRotation;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->GetAt()[X], p_matrix->GetAt()[Y], p_matrix->GetAt()[Z], 0.0f );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+	
+	int				lp;
+	CParticleEntry	*p_particle;
+	float			*p_v;
+
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( mp_engine_texture, m_blend, ABS );
+
+	for ( lp = 0, p_particle = mp_particle_array, p_v = mp_vertices; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+	{
+		float terp = p_particle->m_time / p_particle->m_life;
+
+		float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		float h = p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+		// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+		Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+		Mth::Vector	ss_right, ss_up;	//, ss_pos;
+		Mth::Vector tmp[3];
+	
+		ss_right	= screen_right * w;
+		ss_up		= screen_up * h;
+	
+		Image::RGBA color[3];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+
+		if ( m_mid_time >= 0.0f )
+		{
+			if ( terp < m_mid_time )
+			{
+				p_col0 = m_start_color;
+				p_col1 = m_mid_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = terp / m_mid_time;
+			}
+			else
+			{
+				p_col0 = m_mid_color;
+				p_col1 = m_end_color;
+				// Adjust interpolation for this half of the color blend.
+				terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+			}
+		}
+		else
+		{
+			// No mid color specified.
+			p_col0 = m_start_color;
+			p_col1 = m_end_color;
+		}
+
+		for ( int c = 0; c < 3; c++ )
+		{
+			Image::RGBA start = *p_col0++;
+			Image::RGBA end = *p_col1++;
+
+			color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+			color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+			color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+			color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+		}
+
+		tmp[0]  = pos;
+		tmp[0] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+		tmp[0] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+		for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+		{
+			tmp[1]  = pos;
+			tmp[1] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+			tmp[1] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+
+			tmp[2]  = pos;
+			tmp[2] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+			tmp[2] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+
+			NxPs2::CImmediateMode::sDrawStarSegment( pos, tmp[0], tmp[1], tmp[2],
+													 *((uint32 *) &color[0]),
+													 *((uint32 *) &color[1]),
+													 *((uint32 *) &color[2]));
+			tmp[0] = tmp[1];
+		}
+	}
+
+	NxPs2::dma::EndTag();
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxparticlestar.h b/Code/Gfx/NGPS/p_nxparticlestar.h
new file mode 100644
index 0000000..4e540e5
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxparticlestar.h
@@ -0,0 +1,84 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLEStar_H__
+#define	__GFX_P_NX_PARTICLEStar_H__
+    
+#include "gfx/ngps/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CPs2ParticleStar : public CParticle
+{
+public:
+							CPs2ParticleStar();
+							CPs2ParticleStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CPs2ParticleStar();
+
+//	NxPs2::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+//	void*					mp_display_list;
+//	int						m_display_list_size;
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+
+//	NxPs2::sParticleSystem*	mp_engine_particle;
+	int						m_num_segments;
+	float					m_split;
+	Image::RGBA				m_start_color[3];			// Start color for each corner.
+	Image::RGBA				m_mid_color[3];				// Mid color for each corner.
+	Image::RGBA				m_end_color[3];				// End color for each corner.
+	NxPs2::SSingleTexture*	mp_engine_texture;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxweather.cpp b/Code/Gfx/NGPS/p_nxweather.cpp
new file mode 100644
index 0000000..d3d6af5
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxweather.cpp
@@ -0,0 +1,1189 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxWeather.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  6/2/2003
+//****************************************************************************
+
+#include 
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+
+#include "gfx/ngps/nx/line.h"
+#include 
+#include 
+
+#include "gfx/ngps/nx/immediate.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#include "gfx/ngps/nx/mesh.h"
+
+#include "gfx/ngps/p_nxweather.h"
+
+#include 
+#include 
+
+#include 
+#include 
+#include "gfx/nx.h"
+
+#include "gfx/ngps/nx/render.h"
+#include "gfx/ngps/nx/dma.h"
+#include "gfx/ngps/nx/vif.h"
+#include "gfx/ngps/nx/vu1.h"
+#include "gfx/ngps/nx/gif.h"
+#include "gfx/ngps/nx/gs.h"
+#include "gfx/ngps/nx/sprite.h"
+#include "gfx/ngps/nx/switches.h"
+#include "gfx/ngps/nx/vu1code.h"
+
+#define FEELER_START 50000.0f
+
+#define _STCYCL(a,b) ((uint32)0x01<<24 | (uint32)0<<16 | (uint32)((a)<<8|(b)))
+#define _UNPACK(m,v,s,f,u,a) (((0x60|(m)<<4|(v))<<24) | ((s)<<16) | ((f)<<15 | (u)<<14 | ((NxPs2::vu1::Loc+(a))&0x03FF)))
+
+#define _NREG(n) ((float)(n) * 1.1920928955e-07f)
+#define _PRIM(n,f,p,r,a) (n)<<28 | (f)<<26 | (p)<<15 | (r)<<14 | (a)
+
+#define _BEGINUNPACK(m,v,f,u,a,nbytes)																				\
+					((uint32)(0x60 | (m)<<4 | (v))<<24 |														\
+					(uint32)(( ((nbytes) << 3) / ( (((v)>>2 & 3) + 1) * (32 >> ((v) & 3)) ) ))<<16 |			\
+					(uint32)((f)<<15 | (u)<<14 | ((NxPs2::vu1::Loc+(a))&0x03FF)))
+
+
+#define _STMOD(o) ((uint32)0x05<<24 | (uint32)0<<16 | (uint32)(o)) 
+
+
+#define _SIZE(v,nbytes) ((uint32)(( ((nbytes) << 3) / ( (((v)>>2 & 3) + 1) * (32 >> ((v) & 3)) ) )))
+
+#define _PARSER ((uint32)0x14<<24 | (uint32)0<<16 | (uint32)VU1_ADDR(Parser)) 
+
+#define NUM_LINES_PER_BATCH 80
+#define NUM_SPRITES_PER_BATCH 80
+
+#define PRECISION_SHIFT 4
+
+unsigned char grid_bytes[70*1024];
+
+#define RENDER_DIST 16
+//#define RAIN_HEIGHT 2000
+//#define RAIN_FRAMES 40
+//#define RAIN_LENGTH 100.0f
+#define SEQ_START 411		// Between 1 and 4095.
+#define SEQ_MASK 0x0240
+//#define SEQ_MASK 0x0ca0
+//#define NUM_DROPS_PER_FRAME 25
+
+//1-3: 0x03
+//1-7: 0x06
+//1-15: 0x0C
+//1-31: 0x14
+//1-63: 0x30
+//1-127: 0x60
+//1-255: 0xB8
+//1-511: 0x0110
+//1-1023: 0x0240
+//1-2047: 0x0500
+//1-4095: 0x0CA0
+//1-8191: 0x1B00
+//1-16383: 0x3500
+//1-32767: 0x6000
+//1-65535: 0xB400
+//0x00012000
+//0x00020400
+//0x00072000
+//0x00090000
+//0x00140000
+//0x00300000
+//0x00400000
+//0x00D80000
+//0x01200000
+//0x03880000
+//0x07200000
+//0x09000000
+//0x14000000
+//0x32800000
+//0x48000000
+//0xA3000000
+
+
+//#define DEBUG_LINES
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2Weather::CPs2Weather()
+{
+	mp_roof_height_index = NULL;
+	mp_rain_texture = NULL;
+
+	m_rain_blend = NxPs2::CImmediateMode::sGetTextureBlend( CRCD(0xa86285a1,"fixadd"), 64 );		// FixAdd / 64
+	m_splash_blend = NxPs2::CImmediateMode::sGetTextureBlend( CRCD(0xa86285a1,"fixadd"), 64 );		// FixAdd / 64
+	m_snow_blend = NxPs2::CImmediateMode::sGetTextureBlend( CRCD(0xa86285a1,"fixadd"), 64 );		// FixAdd / 64
+
+	m_rain_rate = 0.0f;
+	m_splash_rate = 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CPs2Weather::~CPs2Weather()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2Weather::plat_update_grid( void )
+{
+	m_system_active = false;
+	// Get super sector manager.
+	SSec::Manager *ss_man;
+	Mth::Line line;
+	ss_man = Nx::CEngine::sGetNearestSuperSectorManager( line );		// Line is ignored, 1st manager is returned.
+	if ( !ss_man ) return;
+
+	// Calculate the size of the world in cels.
+	int min_x = ( ((int)ss_man->GetWorldBBox()->GetMin()[X]) / WEATHER_CEL_SIZE ) - 1;
+	int max_x = ( ((int)ss_man->GetWorldBBox()->GetMax()[X]) / WEATHER_CEL_SIZE ) + 1;
+	int min_z = ( ((int)ss_man->GetWorldBBox()->GetMin()[Z]) / WEATHER_CEL_SIZE ) - 1;
+	int max_z = ( ((int)ss_man->GetWorldBBox()->GetMax()[Z]) / WEATHER_CEL_SIZE ) + 1;
+
+	// Define a maximum...
+	if ( ( max_x - min_x ) > 350 )
+	{
+		int wid = ( max_x - min_x );
+		int excess = ( wid - 350 );
+		min_x += ( excess / 2 );
+		max_x -= ( excess / 2 );
+	}
+
+	if ( ( max_z - min_z ) > 300 )
+	{
+		int wid = ( max_z - min_z );
+		int excess = ( wid - 300 );
+		min_z += ( excess / 2 );
+		max_z -= ( excess / 2 );
+	}
+
+	// This is the actual width;
+	m_width = ( max_x - min_x ) + 1;
+	m_height = ( max_z - min_z ) + 1;
+
+	// Allocate a new piece of memory for the grid.
+//	if ( mp_roof_height_index ) delete mp_roof_height_index;
+//	mp_roof_height_index = new unsigned char[m_width * m_height];
+
+//	mp_roof_height_index = grid_bytes;
+
+	m_min_x = ( min_x * WEATHER_CEL_SIZE );
+	m_min_z = ( min_z * WEATHER_CEL_SIZE );
+
+	// Temporary buffer for the raw array.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	unsigned char * p8 = new unsigned char[m_width*m_height];
+	Mem::Manager::sHandle().PopContext();
+
+	// Go through and get the height for each cel corner.
+	CFeeler feeler;
+	int num_heights = 0;
+	int xx, zz;
+	for ( zz = min_z; zz <= max_z; zz++ )
+	{
+		for ( xx = min_x; xx <= max_x; xx++ )
+		{
+			// The cel to fill.
+			int cel = ( ( zz - min_z ) * m_width ) + ( xx - min_x );
+
+			// The position to check.
+			Mth::Vector pos;
+			pos[X] = (float)( xx * WEATHER_CEL_SIZE );
+			pos[Y] = FEELER_START;
+			pos[Z] = (float)( zz * WEATHER_CEL_SIZE );
+			pos[W] = 1.0f;
+
+			feeler.SetStart( pos );
+			pos[Y] = -FEELER_START;
+			feeler.SetEnd( pos );
+
+			// Get the y position.
+			sint32 y;
+			if ( feeler.GetCollision( false, false ) )		// No movables, nearest collision.
+			{
+				y = (sint32)( feeler.GetPoint()[Y] * SUB_INCH_PRECISION );
+			}
+			else
+			{
+				y = (sint32)( FEELER_START * SUB_INCH_PRECISION );
+			}
+
+			// See if a close enough y pos already exists.
+			int found = -1;
+			for ( int lp = 0; lp < num_heights; lp++ )
+			{
+				if ( abs( ( m_roof_height[lp] - y ) ) < HEIGHT_TOLERANCE )
+				{
+					found = lp;
+					break;
+				}
+			}
+
+			// Fill in the cel.
+			if ( found != -1 )
+			{
+				// Existing height.
+				p8[cel] = found;
+			}
+			else
+			{
+				// New height.
+				p8[cel] = num_heights;
+				m_roof_height[num_heights] = y;
+				num_heights++;
+			}
+		}
+	}
+
+	// Work out highest height for each cel.
+	for ( zz = min_z; zz <= max_z; zz++ )
+	{
+		for ( xx = min_x; xx <= max_x; xx++ )
+		{
+			// The cel to fill.
+			int cel = ( ( zz - min_z ) * m_width ) + ( xx - min_x );
+			int celx = ( ( zz - min_z ) * m_width ) + ( ( xx + 1 ) - min_x );
+			int celz = ( ( ( zz + 1 ) - min_z ) * m_width ) + ( xx - min_x );
+			int celxz = ( ( ( zz + 1 ) - min_z ) * m_width ) + ( ( xx + 1 ) - min_x );
+
+			if ( m_roof_height[p8[celx]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celx];
+			if ( m_roof_height[p8[celz]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celz];
+			if ( m_roof_height[p8[celxz]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celxz];
+		}
+	}
+
+	// Create a sparse array.
+	mp_roof_row = (sRowEntry *)grid_bytes;
+	mp_roof_height_index = (unsigned char *)&mp_roof_row[m_height];
+
+	// 0 = offset
+	// 1 = width
+	// 2 = index
+
+	unsigned short index = 0;
+	for ( zz = 0; zz <= m_height; zz++ )
+	{
+		unsigned short start = 0;
+		unsigned short end = m_width - 1;
+
+		// Scan to find the start.
+		bool start_set = false;
+		for ( xx = 0; xx < m_width; xx++ )
+		{
+			int cel = ( zz * m_width ) + xx;
+
+			if ( m_roof_height[p8[cel]] != (sint32)( FEELER_START * SUB_INCH_PRECISION ) )
+			{
+				if ( !start_set )
+				{
+					// Set start value.
+					start = xx;
+					start_set = true;
+				}
+				else
+				{
+					// Set end value.
+					end = xx;
+				}
+
+			}
+		}
+
+		// Copy data & set row entry.
+		if ( start < end )
+		{
+			mp_roof_row[zz].start = start;
+			mp_roof_row[zz].end = end;
+			mp_roof_row[zz].index = index;
+			for ( xx = start; xx <= end ; xx++ )
+			{
+				int cel = ( zz * m_width ) + xx;
+
+				mp_roof_height_index[index] = p8[cel];
+				index++;
+			}
+		}
+		else
+		{
+			// Row doesn't exist.
+			mp_roof_row[zz].start = 16384;
+			mp_roof_row[zz].end = 0;
+			mp_roof_row[zz].index = 0;
+		}
+	}
+
+	delete p8;
+
+	int new_size = ( m_height * 6 ) + index;
+
+	printf( "Grid Size: Old: %d New: %d Num Heights: %d\n", ( m_width * m_height ), new_size, num_heights );
+//	printf( "Num Weather Grid Heights: %d (%d x %d)\n", num_heights, m_width, m_height );
+
+	// Set all drip time counters to 255.
+	// 255 means the drop won't be rendered.
+	// Setting to anything other than 255 will mean that it will increment to 255 and stop.
+	for ( int lp = 0; lp < ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ); lp++ )
+	{
+		m_drop_time[lp] = 255;
+		m_x_offset[lp] = Mth::Rnd( 255 );
+		m_z_offset[lp] = Mth::Rnd( 255 );
+	}
+	m_active_drops = 0;
+
+	m_seq = SEQ_START;
+
+	// Get the texture.
+	Nx::CTexture *p_texture;
+	Nx::CPs2Texture *p_ps2_texture;
+	mp_rain_texture = NULL;
+
+	// Set Rain Texture.
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( CRCD(0x45c7eb0f,"splash") );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_rain_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	// Set Snow Texture.
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( CRCD(0xc97c5a4c,"snow") );
+	p_ps2_texture = static_cast( p_texture );
+	if ( p_ps2_texture )
+	{
+		mp_snow_texture = p_ps2_texture->GetSingleTexture(); 
+	}
+
+	m_system_active = true;
+
+	// Zero out the splashes.
+	for ( int sp = 0; sp < NUM_SPLASH_ACTIVE; sp++ )
+	{
+		m_splash_current_life[sp] = 0;
+	}
+	m_current_splash = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2Weather::plat_process( float delta_time )
+{
+	if ( !m_system_active ) return;
+
+	if ( m_raining )
+	{
+		// It's raining.
+		float rate = m_rain_drops_per_frame;
+		if ( rate > (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_rain_frames ) ) rate = (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_rain_frames );
+
+		float last = m_rain_rate;
+		m_rain_rate += rate;
+		int new_drops = (int)m_rain_rate - (int)last;
+
+		for ( int lp = 0; lp < new_drops; lp++ )
+		{
+			// If this cel is not inactive, we caught up with ourselves.
+			if ( m_drop_time[m_seq] != 255 ) break;
+
+			// Setup the drop.
+			m_drop_time[m_seq] = ( 254 - m_rain_frames );
+			m_x_offset[m_seq] = Mth::Rnd( 255 );
+			m_z_offset[m_seq] = Mth::Rnd( 255 );
+			m_active_drops++;
+
+			// Calculate next sequence value. Notice hack to get entry 0 into the sequence.
+			switch ( m_seq )
+			{
+				case SEQ_START:
+					m_seq = 0;
+					break;
+				case 0:
+					m_seq = SEQ_START;
+					// Fall through to default case...
+				default:
+					if ( m_seq & 1 )
+					{
+						m_seq = ( m_seq >> 1 ) ^ SEQ_MASK;
+					}
+					else
+					{
+						m_seq = ( m_seq >> 1 );
+					}
+					break;
+			}
+		}
+	}
+	else
+	{
+		// It's snowing.
+		float rate = m_snow_flakes_per_frame;
+		if ( rate > (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_snow_frames ) ) rate = (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_snow_frames );
+
+		float last = m_snow_rate;
+		m_snow_rate += rate;
+		int new_drops = (int)m_snow_rate - (int)last;
+
+		for ( int lp = 0; lp < new_drops; lp++ )
+		{
+			// If this cel is not inactive, we caught up with ourselves.
+			if ( m_drop_time[m_seq] != 255 ) break;
+
+			// Setup the drop.
+			m_drop_time[m_seq] = ( 254 - m_snow_frames );
+			m_x_offset[m_seq] = Mth::Rnd( 255 );
+			m_z_offset[m_seq] = Mth::Rnd( 255 );
+			m_active_drops++;
+
+			// Calculate next sequence value. Notice hack to get entry 0 into the sequence.
+			switch ( m_seq )
+			{
+				case SEQ_START:
+					m_seq = 0;
+					break;
+				case 0:
+					m_seq = SEQ_START;
+					// Fall through to default case...
+				default:
+					if ( m_seq & 1 )
+					{
+						m_seq = ( m_seq >> 1 ) ^ SEQ_MASK;
+					}
+					else
+					{
+						m_seq = ( m_seq >> 1 );
+					}
+					break;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2Weather::plat_render_snow( float skx, float skz )
+{
+	// Early out if no drops to draw.
+	if ( !m_active_drops ) return;
+
+	// Get skater position.
+	int x = (int)( ( skx - m_min_x ) / WEATHER_CEL_SIZE );
+	int z = (int)( ( skz - m_min_z ) / WEATHER_CEL_SIZE );
+
+	// Calculate area to render.
+	int sx = x - RENDER_DIST;
+	int ex = x + DROP_SIZE;	//RENDER_DIST;
+	int sz = z - RENDER_DIST;
+	int ez = z + DROP_SIZE;	//RENDER_DIST;
+
+	// Clip z values.
+	if ( ez < 0 ) return;
+	if ( sz > ( m_height - 1 ) ) return;
+
+//	if ( sz < 0 ) sz = 0;
+//	if ( ez > ( m_height - 1 ) ) ez = ( m_height - 1 );
+
+#ifdef DEBUG_LINES
+	NxPs2::BeginLines3D( 0xffffffff );
+#else
+	// add a dma packet
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+
+	// VU context
+	NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));
+	NxPs2::vu1::StoreVec(*(NxPs2::Vec *)&NxPs2::render::InverseIntViewportScale);
+	NxPs2::vu1::StoreVec(*(NxPs2::Vec *)&NxPs2::render::InverseIntViewportOffset);
+	NxPs2::vu1::StoreMat(*(NxPs2::Mat *)&NxPs2::render::WorldToIntViewport);	// VF12-15
+	NxPs2::vu1::EndPrim(0);
+	NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF30));
+	NxPs2::vif::StoreV4_32F(640.0f, 480.0f, 0.0f, 0.0f);						// VF30
+	NxPs2::vif::StoreV4_32F(NxPs2::render::IntViewportScale[0]/NxPs2::render::Tx,	// VF31
+							NxPs2::render::IntViewportScale[1]/NxPs2::render::Ty,
+							0.0f, 0.0f);
+	NxPs2::vu1::EndPrim(0);
+
+	// GS context
+	NxPs2::gs::BeginPrim(ABS, 0, 0);
+	NxPs2::gs::Reg1(NxPs2::gs::ALPHA_1,	m_snow_blend);
+	NxPs2::gs::Reg1(NxPs2::gs::TEX0_1,	mp_snow_texture->m_RegTEX0);
+	NxPs2::gs::Reg1(NxPs2::gs::TEX1_1,	mp_snow_texture->m_RegTEX1);
+	NxPs2::gs::Reg1(NxPs2::gs::ST,		PackST(0x3F800000,0x3F800000));
+	NxPs2::gs::Reg1(NxPs2::gs::RGBAQ,	PackRGBAQ(0,0,0,0,0x3F800000));
+	NxPs2::gs::EndPrim(0);
+
+#endif		// DEBUG_LINES
+
+	float minx = m_min_x;
+	float minz = m_min_z;
+//	sint32 rlength = (sint32)( m_rain_length * SUB_INCH_PRECISION );
+	
+	// Calculate drop height list.
+	int lp;
+	float y_off[256];
+
+	for ( lp = ( 254 - m_snow_frames ); lp < 256; lp++ )
+	{
+		y_off[lp] = m_snow_height - ( ( (float)( ( lp - 254 ) + m_snow_frames ) / (float)m_snow_frames ) * m_snow_height );
+	}
+
+	// Calculate xz offset list.
+	float xz_off[256];
+
+	for ( lp = 0; lp < 256; lp++ )
+	{
+		xz_off[lp] = ( (float)lp * (float)( WEATHER_CEL_SIZE / 2 ) ) / 255.0f;
+	}
+
+	// Calculate sine wave.
+	float sin_off[256];
+
+	for ( lp = 0; lp < 256; lp++ )
+	{
+		sin_off[lp] = sinf( Mth::PI * 2.0f * ( (float)lp / 256.0f ) ) * ( WEATHER_CEL_SIZE / 2 );
+//		sin_off[lp] = sinf( Mth::PI * 2.0f * (float)lp ) * 128.0f;
+	}
+
+	int flakes = 0;
+	uint32 * p_col = NULL;
+	float * p_xyz = NULL;
+//	uint32 * p_last_loc = NULL;
+//	uint32 * p_tag = NULL;
+
+	for ( int lzz = sz; lzz <= ez; lzz++ )
+	{
+		int zz = ( lzz < 0 ) ? 0 : ( lzz > ( m_height - 1 ) ) ? ( m_height - 1 ) : lzz;
+
+		if ( mp_roof_row[zz].start == 16384 ) continue;
+
+//		// Calculate actual span to scan.
+//		int rsx = sx > mp_roof_row[zz].start ? sx : mp_roof_row[zz].start;
+//		int rex = ex < mp_roof_row[zz].end ? ex : mp_roof_row[zz].end;
+//
+//		// Start position.
+//		sint32 vx = ( (sint32)rsx * WEATHER_CEL_SIZE * (sint32)SUB_INCH_PRECISION ) + minx;
+//		sint32 vz = ( (sint32)zz * WEATHER_CEL_SIZE * (sint32)SUB_INCH_PRECISION ) + minz;
+
+		float vx = ( (float)sx * (float)WEATHER_CEL_SIZE ) + minx;
+		float vz = ( (float)zz * (float)WEATHER_CEL_SIZE ) + minz;
+
+		int cel = mp_roof_row[zz].index + ( sx - mp_roof_row[zz].start );
+
+		int drop_cel_z = ( ( zz & ( DROP_SIZE - 1 ) ) << DROP_SIZE_SHIFT );
+
+//		for ( int xx = rsx; xx <= rex; xx++, cel++ )
+		for ( int lxx = sx; lxx <= ex; lxx++, cel++ )
+		{
+			int xx = ( lxx > mp_roof_row[zz].start ) ? ( ( lxx < mp_roof_row[zz].end ) ? lxx : mp_roof_row[zz].end ) : mp_roof_row[zz].start;
+
+			// Get the current drop value. Skip this one if it's inactive.
+			int drop_cel = drop_cel_z + ( xx & ( DROP_SIZE - 1 ) );
+
+//			vx += ( WEATHER_CEL_SIZE << PRECISION_SHIFT );
+			vx += (float)WEATHER_CEL_SIZE;
+			float vy = (float)( m_roof_height[mp_roof_height_index[cel]] >> PRECISION_SHIFT );
+
+			for ( int d = 0; d < DROP_LAYERS; d++, drop_cel += ( DROP_SIZE * DROP_SIZE ) )
+			{
+				if ( m_drop_time[drop_cel] == 255 ) continue;
+
+				// Create the position for rendering.
+				float v0x = vx + xz_off[m_x_offset[drop_cel]] + sin_off[(m_z_offset[drop_cel]+m_drop_time[drop_cel])&255];
+				float v0y = vy + y_off[m_drop_time[drop_cel]] + m_snow_size;
+				float v0z = vz + xz_off[m_z_offset[drop_cel]] + sin_off[(m_x_offset[drop_cel]+m_drop_time[drop_cel])&255];
+
+//				sint32 v1y = v0y + rlength;
+
+#ifdef DEBUG_LINES
+				NxPs2::DrawLine3D( v0[X], v0[Y], v0[Z], v1[X], v1[Y], v1[Z] );
+#else
+				if ( flakes == 0 )
+				{
+					NxPs2::BeginModelPrimImmediate(NxPs2::gs::XYZ2		|
+												   NxPs2::gs::ST<<4		|
+												   NxPs2::gs::RGBAQ<<8	|
+												   NxPs2::gs::XYZ2<<12,
+												   4, SPRITE|ABE|TME, 1, VU1_ADDR(SpriteCull));
+
+					// create an unpack for the colours
+					NxPs2::vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, 3);
+					p_col = (uint32 *)NxPs2::dma::pLoc;
+					NxPs2::dma::pLoc += NUM_SPRITES_PER_BATCH * 4;
+					NxPs2::vif::EndUNPACK();
+
+					// and one for the positions (& sizes)
+					NxPs2::vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 4);
+					p_xyz = (float *)NxPs2::dma::pLoc;
+					NxPs2::dma::pLoc += NUM_SPRITES_PER_BATCH * 16;
+					NxPs2::vif::EndUNPACK();
+
+					NxPs2::EndModelPrimImmediate(1);
+
+					NxPs2::vif::MSCAL(VU1_ADDR(Parser));
+				
+					flakes = NUM_SPRITES_PER_BATCH;
+				}
+
+				*p_col++ = *((uint32 *)&m_snow_color);
+
+				p_xyz[0] = v0x;
+				p_xyz[1] = v0y;
+				p_xyz[2] = v0z;
+				p_xyz[3] = m_snow_size;
+				p_xyz += 4;
+
+				flakes--;
+
+#endif		// DEBUG_LINES
+			}
+		}
+	}
+//	printf( "Drop comparison: Counted: %d Calculated: %d\n", drops, m_active_drops );
+
+	// Fix the last packet.
+//	if ( flakes == NUM_SPRITES_PER_BATCH )
+//	{
+//		NxPs2::vu1::Loc -= _SIZE(V4_32,16*NUM_SPRITES_PER_BATCH) * 2 + 1;
+//		p_loc = p_last_loc;
+//	}
+//	else if ( lines )
+//	{
+	if ( flakes )
+	{
+		// Fill in with degenerate lines.
+		for ( int l = 0; l < flakes; l++ )
+		{
+			p_xyz[0] = 0;
+			p_xyz[1] = 0;
+			p_xyz[2] = 0;
+			p_xyz[3] = 0;
+
+			p_xyz += 4;
+		}
+	}
+
+#ifdef DEBUG_LINES
+	NxPs2::EndLines3D();
+#else
+	// restore transform
+	NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF12));		// was L_VF16
+	NxPs2::vu1::StoreMat(*(NxPs2::Mat *)&NxPs2::render::AdjustedWorldToViewport);		// VF16-19
+	NxPs2::vu1::EndPrim(1);
+	NxPs2::vif::MSCAL(VU1_ADDR(Parser));
+
+	// end the dma tag
+	NxPs2::dma::EndTag();
+#endif		// DEBUG_LINES
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2Weather::plat_render_rain( float skx, float skz )
+{
+	// Early out if no drops to draw.
+	if ( !m_active_drops ) return;
+
+	// Get skater position.
+	int x = (int)( ( skx - m_min_x ) / WEATHER_CEL_SIZE );
+	int z = (int)( ( skz - m_min_z ) / WEATHER_CEL_SIZE );
+
+	// Calculate area to render.
+	int sx = x - RENDER_DIST;
+	int ex = x + DROP_SIZE;	//RENDER_DIST;
+	int sz = z - RENDER_DIST;
+	int ez = z + DROP_SIZE;	//RENDER_DIST;
+
+	// Clip z values.
+	if ( ez < 0 ) return;
+	if ( sz > ( m_height - 1 ) ) return;
+
+//	if ( sz < 0 ) sz = 0;
+//	if ( ez > ( m_height - 1 ) ) ez = ( m_height - 1 );
+
+#ifdef DEBUG_LINES
+	NxPs2::BeginLines3D( 0xffffffff );
+#else
+	NxPs2::BeginLines3D( 0xffffffff );
+	NxPs2::EndLines3D();
+	NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+	NxPs2::CImmediateMode::sStartPolyDraw( NULL, m_rain_blend, ABS );
+	register uint32 *p_loc = (uint32*)NxPs2::dma::pLoc;
+#endif		// DEBUG_LINES
+
+	sint32 minx = (sint32)( m_min_x * SUB_INCH_PRECISION );
+	sint32 minz = (sint32)( m_min_z * SUB_INCH_PRECISION );
+	sint32 rlength = (sint32)( m_rain_length * SUB_INCH_PRECISION );
+	
+	// Calculate drop height list.
+	int lp;
+	sint32 y_off[256];
+
+	for ( lp = ( 254 - m_rain_frames ); lp < 256; lp++ )
+	{
+		y_off[lp] = (sint32)( ( m_rain_height - ( ( (float)( ( lp - 254 ) + m_rain_frames ) / (float)m_rain_frames ) * m_rain_height ) ) * SUB_INCH_PRECISION );
+	}
+
+	// Calculate xz offset list.
+	sint32 xz_off[256];
+
+	for ( lp = 0; lp < 256; lp++ )
+	{
+		xz_off[lp] = (sint32)( ( ( (float)lp * (float)WEATHER_CEL_SIZE * SUB_INCH_PRECISION ) / 255.0f ) );
+	}
+
+	int lines = 0;
+	uint32 * p_col = NULL;
+	sint32 * p_xyz = NULL;
+	uint32 * p_last_loc = NULL;
+//	uint32 * p_tag = NULL;
+
+	for ( int lzz = sz; lzz <= ez; lzz++ )
+	{
+		int zz = ( lzz < 0 ) ? 0 : ( lzz > ( m_height - 1 ) ) ? ( m_height - 1 ) : lzz;
+
+		if ( mp_roof_row[zz].start == 16384 ) continue;
+
+//		// Calculate actual span to scan.
+//		int rsx = sx > mp_roof_row[zz].start ? sx : mp_roof_row[zz].start;
+//		int rex = ex < mp_roof_row[zz].end ? ex : mp_roof_row[zz].end;
+//
+//		// Start position.
+//		sint32 vx = ( (sint32)rsx * WEATHER_CEL_SIZE * (sint32)SUB_INCH_PRECISION ) + minx;
+//		sint32 vz = ( (sint32)zz * WEATHER_CEL_SIZE * (sint32)SUB_INCH_PRECISION ) + minz;
+
+		sint32 vx = ( (sint32)sx << ( WEATHER_CEL_SIZE_SHIFT + PRECISION_SHIFT ) ) + minx;
+		sint32 vz = ( (sint32)zz << ( WEATHER_CEL_SIZE_SHIFT + PRECISION_SHIFT ) ) + minz;
+
+		int cel = mp_roof_row[zz].index + ( sx - mp_roof_row[zz].start );
+
+		int drop_cel_z = ( ( zz & ( DROP_SIZE - 1 ) ) << DROP_SIZE_SHIFT );
+
+//		for ( int xx = rsx; xx <= rex; xx++, cel++ )
+		for ( int lxx = sx; lxx <= ex; lxx++, cel++ )
+		{
+			int xx = ( lxx > mp_roof_row[zz].start ) ? ( ( lxx < mp_roof_row[zz].end ) ? lxx : mp_roof_row[zz].end ) : mp_roof_row[zz].start;
+
+			// Get the current drop value. Skip this one if it's inactive.
+			int drop_cel = drop_cel_z + ( xx & ( DROP_SIZE - 1 ) );
+
+			vx += ( WEATHER_CEL_SIZE << PRECISION_SHIFT );
+			sint32 vy = m_roof_height[mp_roof_height_index[cel]];
+
+			for ( int d = 0; d < DROP_LAYERS; d++, drop_cel += ( DROP_SIZE * DROP_SIZE ) )
+			{
+				if ( m_drop_time[drop_cel] == 255 ) continue;
+
+				// Create the position for rendering.
+				sint32 v0x = vx + xz_off[m_x_offset[drop_cel]];
+				sint32 v0y = vy + y_off[m_drop_time[drop_cel]]; 
+				sint32 v0z = vz + xz_off[m_z_offset[drop_cel]];
+
+				sint32 v1y = v0y + rlength;
+
+#ifdef DEBUG_LINES
+				NxPs2::DrawLine3D( v0[X], v0[Y], v0[Z], v1[X], v1[Y], v1[Z] );
+#else
+				if ( lines == 0 )
+				{
+					p_last_loc = p_loc;
+
+					p_loc[0] = _STCYCL(1,2);
+					p_loc[1] = _UNPACK(0,V4_32,1,ABS,UNSIGNED,0);
+//					p_tag = &p_loc[2];
+					*(float*)&p_loc[2] = _NREG(2);
+					p_loc[2] |= (1<<15) | _SIZE(V4_32,16*2*NUM_LINES_PER_BATCH);
+
+					p_loc[3] = _PRIM(2, PACKED, LINE|IIP|ABE, 1, VU1_ADDR(Line));
+					p_loc[4] = NxPs2::gs::XYZ2<<4|NxPs2::gs::RGBAQ;
+					p_loc[5] = ( _SIZE(V4_32,16*2*NUM_LINES_PER_BATCH) * 2 ); 
+					p_loc[6] = _BEGINUNPACK(0, V4_8, ABS, UNSIGNED, 1, 4*2*NUM_LINES_PER_BATCH); 
+
+					lines = NUM_LINES_PER_BATCH;
+
+					p_col = &p_loc[7];
+					p_loc = &p_loc[7+(2*NUM_LINES_PER_BATCH)];
+
+					p_loc[0] = _STMOD(OFFSET_MODE); 
+					p_loc[1] = _BEGINUNPACK(1, V4_32, ABS, SIGNED, 2, 16*2*NUM_LINES_PER_BATCH);
+
+					p_xyz = (sint32 *)&p_loc[2];
+					p_loc = &p_loc[2+(8*NUM_LINES_PER_BATCH)];
+				
+					p_loc[0] = _STMOD(NORMAL_MODE); 
+	
+					// finish batch of vertices
+//					p_tag[0] |= (1<<15) | SIZE(V4_32,16*2*NUM_LINES_PER_BATCH);
+					NxPs2::vu1::Loc += _SIZE(V4_32,16*2*NUM_LINES_PER_BATCH) * 2 + 1;
+	
+					p_loc[1] = _PARSER;
+					NxPs2::vu1::Buffer = NxPs2::vu1::Loc;
+	
+					p_loc = &p_loc[2];
+
+					// Fill color regardless.
+					uint32 col0 = *((uint32 *)&m_rain_bottom_color); 
+					uint32 col1 = *((uint32 *)&m_rain_top_color);    
+//					for ( int lp = 0; lp < ( NUM_LINES_PER_BATCH / 4 ); lp++ )
+//					{
+//						p_col[0] = col0;
+//						p_col[1] = col1;
+//
+//						p_col[2] = col0;
+//						p_col[3] = col1;
+//					
+//						p_col[4] = col0;
+//						p_col[5] = col1;
+//					
+//						p_col[6] = col0;
+//						p_col[7] = col1;
+//
+//						p_col += 8;
+//					}
+
+					for ( int lp = 0; lp < NUM_LINES_PER_BATCH; lp++ )
+					{
+						p_col[0] = col0;
+						p_col[1] = col1;
+
+						p_col += 2;
+					}
+				}
+
+//				p_col[0] = col0;
+//				p_col[1] = col1;
+
+				p_xyz[0] = v0x;
+				p_xyz[1] = v0y;
+				p_xyz[2] = v0z;
+				p_xyz[3] = 0;
+				p_xyz[4] = v0x;
+				p_xyz[5] = v1y;
+				p_xyz[6] = v0z;
+				p_xyz[7] = 0;
+
+//				p_col += 2;
+				p_xyz += 8;
+
+				lines --;
+#endif		// DEBUG_LINES
+			}
+		}
+	}
+//	printf( "Drop comparison: Counted: %d Calculated: %d\n", drops, m_active_drops );
+
+	// Fix the last packet.
+	if ( lines == NUM_LINES_PER_BATCH )
+	{
+		NxPs2::vu1::Loc -= _SIZE(V4_32,16*2*NUM_LINES_PER_BATCH) * 2 + 1;
+		p_loc = p_last_loc;
+	}
+	else if ( lines )
+	{
+		// Fill in with degenerate lines.
+		for ( int l = 0; l < lines; l++ )
+		{
+			p_xyz[0] = 0;
+			p_xyz[1] = 0;
+			p_xyz[2] = 0;
+			p_xyz[3] = 0;
+			p_xyz[4] = 0;
+			p_xyz[5] = 0;
+			p_xyz[6] = 0;
+			p_xyz[7] = 0;
+
+			p_xyz += 8;
+		}
+	}
+
+#ifdef DEBUG_LINES
+	NxPs2::EndLines3D();
+#else
+	NxPs2::dma::pLoc = (uint8*)p_loc;
+	NxPs2::dma::EndTag();
+
+
+
+
+
+#endif		// DEBUG_LINES
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2Weather::plat_render_splashes( float skx, float skz )
+{
+	// Create a new splash if required.
+	float last = m_splash_rate;
+	m_splash_rate += m_splash_per_frame;
+	int new_splashes = (int)m_splash_rate - (int)last;
+
+	if ( new_splashes )
+	{
+		CFeeler feeler;
+		Mth::Vector pos;
+
+		pos[X] = NxPs2::render::CameraToWorld.GetPos()[X];	// + ( Mth::Rnd( ( WEATHER_CEL_SIZE * RENDER_DIST * 2 ) ) ) - ( WEATHER_CEL_SIZE * RENDER_DIST );
+		pos[Y] = FEELER_START;
+		pos[Z] = NxPs2::render::CameraToWorld.GetPos()[Z];	// + ( Mth::Rnd( ( WEATHER_CEL_SIZE * RENDER_DIST * 2 ) ) ) - ( WEATHER_CEL_SIZE * RENDER_DIST ); 
+		pos[W] = 1.0f;
+
+		Mth::Vector dir;
+		dir[X] = skx - NxPs2::render::CameraToWorld.GetPos()[X];
+		dir[Y] = 0.0f;
+		dir[Z] = skz - NxPs2::render::CameraToWorld.GetPos()[Z];
+		dir[W] = 1.0f;
+		dir.Normalize();
+
+		// Add distance.
+		float r1 = (float)Mth::Rnd( 32768 ) / 32768.0f;
+		pos += ( dir * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 ) );
+
+		// Add lateral.
+		Mth::Vector lat;
+		lat[X] = dir[Z];
+		lat[Y] = 0.0f;
+		lat[Z] = -dir[X];
+		lat[W] = 1.0f;
+
+		float r2 = 1.0f - ( (float)Mth::Rnd( 32768 ) / 32768.0f );
+		if ( m_current_splash & 1 )
+		{
+			pos += ( lat * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 * r2 ) );
+		}
+		else
+		{
+			pos += ( lat * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 * -r2 ) );
+		}
+
+		feeler.SetStart( pos );
+		pos[Y] = -FEELER_START;
+		feeler.SetEnd( pos );
+
+		// Get the y position.
+		float y;
+		if ( feeler.GetCollision( false, false ) )		// No movables, nearest collision.
+		{
+			y = feeler.GetPoint()[Y];
+		}
+		else
+		{
+			y = FEELER_START;
+		}
+
+		m_splash_x[m_current_splash] = pos[X];
+		m_splash_y[m_current_splash] = y + m_splash_size;
+		m_splash_z[m_current_splash] = pos[Z];
+		m_splash_current_life[m_current_splash] = m_splash_life;
+
+		m_current_splash++;
+		m_current_splash %= NUM_SPLASH_ACTIVE;
+	}
+	
+	// Count active splashes.
+	int splashes = 0;
+	for ( int spl = 0; spl < NUM_SPLASH_ACTIVE; spl++ )
+	{
+		if ( m_splash_current_life[spl] != 0 ) splashes++;
+	}
+
+	if ( splashes )
+	{
+		// add a dma packet
+		NxPs2::dma::BeginTag(NxPs2::dma::cnt, 0);
+
+		// VU context
+		NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF10));
+		NxPs2::vu1::StoreVec(*(NxPs2::Vec *)&NxPs2::render::InverseIntViewportScale);
+		NxPs2::vu1::StoreVec(*(NxPs2::Vec *)&NxPs2::render::InverseIntViewportOffset);
+		NxPs2::vu1::StoreMat(*(NxPs2::Mat *)&NxPs2::render::WorldToIntViewport);	// VF12-15
+		NxPs2::vu1::EndPrim(0);
+		NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF30));
+		NxPs2::vif::StoreV4_32F(640.0f, 480.0f, 0.0f, 0.0f);						// VF30
+		NxPs2::vif::StoreV4_32F(NxPs2::render::IntViewportScale[0]/NxPs2::render::Tx,	// VF31
+								NxPs2::render::IntViewportScale[1]/NxPs2::render::Ty,
+								0.0f, 0.0f);
+		NxPs2::vu1::EndPrim(0);
+
+		// GS context
+		NxPs2::gs::BeginPrim(ABS, 0, 0);
+		NxPs2::gs::Reg1(NxPs2::gs::ALPHA_1,	m_splash_blend);
+		NxPs2::gs::Reg1(NxPs2::gs::TEX0_1,	mp_rain_texture->m_RegTEX0);
+		NxPs2::gs::Reg1(NxPs2::gs::TEX1_1,	mp_rain_texture->m_RegTEX1);
+		NxPs2::gs::Reg1(NxPs2::gs::ST,		PackST(0x3F800000,0x3F800000));
+		NxPs2::gs::Reg1(NxPs2::gs::RGBAQ,	PackRGBAQ(0,0,0,0,0x3F800000));
+		NxPs2::gs::EndPrim(0);
+
+		// Render the splashes.
+	//	int u = mp_rain_texture->GetWidth() << 4;
+	//	int v = mp_rain_texture->GetHeight() << 4;
+	//	uint32 col = *((uint32 *)&m_splash_color); 
+
+		NxPs2::BeginModelPrimImmediate(NxPs2::gs::XYZ2		|
+									   NxPs2::gs::ST<<4		|
+									   NxPs2::gs::RGBAQ<<8	|
+									   NxPs2::gs::XYZ2<<12,
+									   4, SPRITE|ABE|TME, 1, VU1_ADDR(SpriteCull));
+
+		// create an unpack for the colours
+		NxPs2::vif::BeginUNPACK(0, V4_8, ABS, UNSIGNED, 3);
+		uint32 * p_col = (uint32 *)NxPs2::dma::pLoc;
+		NxPs2::dma::pLoc += splashes * 4;
+		NxPs2::vif::EndUNPACK();
+
+		// and one for the positions (& sizes)
+		NxPs2::vif::BeginUNPACK(0, V4_32, ABS, SIGNED, 4);
+		float * p_xyz = (float *)NxPs2::dma::pLoc;
+		NxPs2::dma::pLoc += splashes * 16;
+		NxPs2::vif::EndUNPACK();
+
+		NxPs2::EndModelPrimImmediate(1);
+
+		NxPs2::vif::MSCAL(VU1_ADDR(Parser));
+
+		for ( int spl = 0; spl < NUM_SPLASH_ACTIVE; spl++ )
+		{
+			if ( m_splash_current_life[spl] == 0 ) continue;
+
+			// Interpolate color.
+			Image::RGBA	icol;
+			icol.r = (uint8)( ( (int)m_splash_color.r * m_splash_current_life[spl] ) / m_splash_life );
+			icol.g = (uint8)( ( (int)m_splash_color.g * m_splash_current_life[spl] ) / m_splash_life );
+			icol.b = (uint8)( ( (int)m_splash_color.b * m_splash_current_life[spl] ) / m_splash_life );
+			icol.a = (uint8)( ( (int)m_splash_color.a * m_splash_current_life[spl] ) / m_splash_life );
+
+			// Draw splashes...
+			*p_col++ = *((uint32 *)&icol);
+
+			p_xyz[0] = m_splash_x[spl];
+			p_xyz[1] = m_splash_y[spl];
+			p_xyz[2] = m_splash_z[spl];
+			p_xyz[3] = m_splash_size;
+			p_xyz += 4;
+
+			m_splash_current_life[spl]--;
+		}
+
+		// restore transform
+		NxPs2::vu1::BeginPrim(ABS, VU1_ADDR(L_VF12));		// was L_VF16
+		NxPs2::vu1::StoreMat(*(NxPs2::Mat *)&NxPs2::render::AdjustedWorldToViewport);		// VF16-19
+		NxPs2::vu1::EndPrim(1);
+		NxPs2::vif::MSCAL(VU1_ADDR(Parser));
+
+		// end the dma tag
+		NxPs2::dma::EndTag();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CPs2Weather::plat_render( void )
+{
+	if ( !m_system_active ) return;
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = pSkate->GetSkater(0);
+	if (!pSkater) return;
+	
+	float skx = pSkater->m_pos[X];
+	float skz = pSkater->m_pos[Z];
+
+	if ( m_raining )
+	{
+		plat_render_splashes( skx, skz );
+		plat_render_rain( skx, skz );
+	}
+	else
+	{
+		plat_render_snow( skx, skz );
+	}
+
+	// Increment rain drop/snow flake positions.
+	if ( m_active_drops )
+	{
+		for ( int lp = 0; lp < ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ); lp++ )
+		{
+			switch ( m_drop_time[lp] )
+			{
+				case 255:
+					// Inactive.
+					break;
+				case 254:
+					// About to become inactive, so decrement active drops.
+					m_active_drops--;
+					// Deliberately falls through...
+				default:
+					// Increment time.
+					m_drop_time[lp]++;
+					break;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CPs2Weather::plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_rain_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CPs2Weather::plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_splash_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CPs2Weather::plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_snow_blend = NxPs2::CImmediateMode::sGetTextureBlend( blendmode_checksum, fix );
+}
+
+} // Nx
+
+
+
+
diff --git a/Code/Gfx/NGPS/p_nxweather.h b/Code/Gfx/NGPS/p_nxweather.h
new file mode 100644
index 0000000..1adebbf
--- /dev/null
+++ b/Code/Gfx/NGPS/p_nxweather.h
@@ -0,0 +1,100 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_WEATHER_H__
+#define	__GFX_P_NX_WEATHER_H__
+    
+#include "gfx/nxweather.h"
+
+namespace Nx
+{
+
+//#define WEATHER_GRID_W 65
+//#define WEATHER_GRID_H 65
+//
+//#define WEATHER_CEL_W 32
+//#define WEATHER_CEL_H 32
+
+#define WEATHER_CEL_SIZE 128
+#define WEATHER_CEL_SIZE_SHIFT 7
+#define HEIGHT_TOLERANCE (sint32)( 30.0f * SUB_INCH_PRECISION )
+#define DROP_SIZE 16
+#define DROP_SIZE_SHIFT 4
+#define DROP_LAYERS 4
+
+#define NUM_SPLASH_ACTIVE 32
+
+typedef struct
+{
+	unsigned short start;
+	unsigned short end;
+	unsigned short index;
+} sRowEntry;
+
+class CPs2Weather : public CWeather
+{
+public:
+							CPs2Weather();
+	virtual 				~CPs2Weather();
+
+private:					// It's all private, as it is machine specific
+	void					plat_update_grid( void );
+	void					plat_process( float delta_time );
+	void					plat_render( void );
+	void					plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix );
+	void					plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix );
+	void					plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix );
+
+	void					plat_render_splashes( float skx, float skz );
+	void					plat_render_rain( float skx, float skz );
+	void					plat_render_snow( float skx, float skz );
+
+	NxPs2::SSingleTexture*	mp_rain_texture;
+	NxPs2::SSingleTexture*	mp_snow_texture;
+
+	unsigned char *			mp_roof_height_index;
+	sRowEntry *				mp_roof_row;
+	sint32					m_roof_height[256];
+	int						m_width;
+	int						m_height;
+
+	float					m_min_x;
+	float					m_min_z;
+
+	float					m_rain_rate;
+	float					m_splash_rate;
+	float					m_snow_rate;
+
+	Mth::Vector				m_grid_base;
+
+	unsigned char			m_drop_time[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+	unsigned char			m_x_offset[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+	unsigned char			m_z_offset[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+
+	uint32					m_seq;
+
+	uint64					m_rain_blend;
+	uint64					m_splash_blend;
+	uint64					m_snow_blend;
+
+	float					m_splash_x[NUM_SPLASH_ACTIVE];
+	float					m_splash_y[NUM_SPLASH_ACTIVE];
+	float					m_splash_z[NUM_SPLASH_ACTIVE];
+	int						m_splash_current_life[NUM_SPLASH_ACTIVE]; 
+	int						m_current_splash;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif
+
diff --git a/Code/Gfx/NxAnimCache.cpp b/Code/Gfx/NxAnimCache.cpp
new file mode 100644
index 0000000..527f97a
--- /dev/null
+++ b/Code/Gfx/NxAnimCache.cpp
@@ -0,0 +1,60 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxAnimCache.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  5/06/2002
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+// (Mick Perforce: added line) 
+						
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::CBonedAnimFrameData* GetCachedAnim( uint32 animChecksum, bool assertOnFail )
+{
+	if ( animChecksum == 0 )
+	{
+		return NULL;
+	}
+
+	Ass::CAssMan* ass_man = Ass::CAssMan::Instance();
+	return (Gfx::CBonedAnimFrameData*)ass_man->GetAsset( animChecksum, assertOnFail );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
diff --git a/Code/Gfx/NxAnimCache.h b/Code/Gfx/NxAnimCache.h
new file mode 100644
index 0000000..416c82a
--- /dev/null
+++ b/Code/Gfx/NxAnimCache.h
@@ -0,0 +1,26 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxAnimCache.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  5/06/2002
+//****************************************************************************
+
+#ifndef	__GFX_NXANIMCACHE_H__
+#define	__GFX_NXANIMCACHE_H__
+                   
+#include 
+#include 
+
+namespace Gfx
+{
+	class CBonedAnimFrameData;
+};
+
+namespace Nx
+{
+
+Gfx::CBonedAnimFrameData* GetCachedAnim( uint32 animChecksum, bool assertOnFail = true );
+
+}
+
+#endif // __GFX_NXANIMCACHE_H__
diff --git a/Code/Gfx/NxFont.cpp b/Code/Gfx/NxFont.cpp
new file mode 100644
index 0000000..c9335d8
--- /dev/null
+++ b/Code/Gfx/NxFont.cpp
@@ -0,0 +1,573 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxFont.cpp
+
+#include "gfx/NxFont.h"
+
+namespace	Nx
+{
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+					 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFont::CFont()
+{
+	m_checksum = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFont::~CFont()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CFont::Load(const char *filename)
+{
+	return plat_load(filename);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFont::SetSpacings(int charSpacing, int spaceSpacing)
+{
+	plat_set_spacings(charSpacing, spaceSpacing);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFont::SetRGBATable(Image::RGBA *pTab)
+{
+	Dbg_Assert(pTab);
+	plat_set_rgba_table(pTab);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFont::MarkAsButtonFont(bool isButton)
+{
+	plat_mark_as_button_font(isButton);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::Unload()
+{
+	plat_unload();
+
+	m_checksum = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CFont::GetChecksum() const
+{
+	return m_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CFont::GetDefaultHeight() const
+{
+	return plat_get_default_height();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CFont::GetDefaultBase() const
+{
+	return plat_get_default_base();
+}
+
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::BeginText(uint32 rgba, float Scale)
+{
+	plat_begin_text(rgba, Scale);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::DrawString(char *String, float x0, float y0)
+{
+	plat_draw_string(String, x0, y0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::EndText(void)
+{
+	plat_end_text();
+}
+#endif // 0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::QueryString(char *String, float &width, float &height) const
+{
+	plat_query_string(String, width, height);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CFont::plat_load(const char *filename)
+{
+	printf ("STUB: PlatLoad\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFont::plat_set_spacings(int charSpacing, int spaceSpacing)
+{
+	printf("STUB A DUB DUB");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFont::plat_set_rgba_table(Image::RGBA *pTab)
+{
+	printf("STUB A DUB DUB");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFont::plat_mark_as_button_font(bool isButton)
+{
+	printf("STUB A DUB DUB");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::plat_unload()
+{
+	printf ("STUB: PlatUnload\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CFont::plat_get_default_height() const
+{
+	printf ("STUB: PlatGetDefaultHeight\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CFont::plat_get_default_base() const
+{
+	printf ("STUB: PlatGetDefaultBase\n");
+	return 0;
+}
+
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::plat_begin_text(uint32 rgba, float Scale)
+{
+	Dbg_Assert(0);	// Don't call anymore
+	printf ("STUB: PlatBeginText\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::plat_draw_string(char *String, float x0, float y0)
+{
+	Dbg_Assert(0);	// Don't call anymore
+	printf ("STUB: PlatDrawString\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::plat_end_text(void)
+{
+	Dbg_Assert(0);	// Don't call anymore
+	printf ("STUB: PlatEndText\n");
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CFont::plat_query_string(char *String, float &width, float &height) const
+{
+	printf ("STUB: PlatQueryString\n");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CText
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CText::CText(CWindow2D *p_window) : 
+	mp_font(NULL),
+	m_xpos(0.0f),
+	m_ypos(0.0f),
+	m_xscale(1.0f),
+	m_yscale(1.0f),
+	m_priority(0.0f),
+	m_rgba(128, 128, 128, 128),
+	m_color_override(false),
+	m_hidden(true),
+	m_use_zbuffer(false),
+	mp_window(p_window),
+	mp_next(NULL)
+{
+	#if						__STATIC_FONT_STRINGS__
+	m_string[0] = '\0';
+	#else
+	m_string = NULL;
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CText::~CText() 
+{
+	#if						__STATIC_FONT_STRINGS__
+	#else
+	if (m_string)
+	{
+		delete [] m_string;
+	}
+	#endif	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetHidden(bool hide)
+{
+	m_hidden = hide;
+
+	plat_update_hidden();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetFont(CFont *p_font)
+{
+	mp_font = p_font;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetString(const char *p_string)
+{
+	Dbg_MsgAssert(strlen(p_string) < vMAX_TEXT_CHARS, ("CText: string too long %d", strlen(p_string)));
+
+	#if						__STATIC_FONT_STRINGS__
+	strcpy(m_string, p_string);
+	#else
+	if (!m_string)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		m_string = new char[vMAX_TEXT_CHARS];
+		Mem::Manager::sHandle().PopContext();
+	}
+	strcpy(m_string, p_string);
+	#endif
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::ClearString()
+{
+	#if						__STATIC_FONT_STRINGS__
+	#else
+	if (m_string)
+	{
+		delete m_string;
+		m_string = NULL;
+	}
+	#endif
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetPos(float x, float y)
+{
+	m_xpos = x;
+	m_ypos = y;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetScale(float scale_x, float scale_y)
+{
+	m_xscale = scale_x;
+	m_yscale = scale_y;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetPriority(float pri)
+{
+	m_priority = pri;
+	m_use_zbuffer = false;
+
+	plat_update_priority();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetZValue(ZBufferValue z)
+{
+	m_zvalue = z;
+	m_use_zbuffer = true;
+
+	plat_update_priority();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetRGBA(Image::RGBA rgba, bool colorOverride)
+{
+	m_rgba = rgba;
+	m_color_override = colorOverride;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::SetWindow(CWindow2D *p_window)
+{
+	mp_window = p_window;
+
+	plat_update_window();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::plat_initialize()
+{
+	printf ("STUB: PlatInitialize\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::plat_update_hidden()
+{
+	printf ("STUB: PlatUpdateHidden\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::plat_update_engine()
+{
+	printf ("STUB: PlatUpdateEngine\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::plat_update_priority()
+{
+	printf ("STUB: PlatUpdatePriority\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CText::plat_update_window()
+{
+	printf ("STUB: PlatUpdateWindow\n");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CTextMan
+
+CText *			CTextMan::sp_dynamic_text_list = NULL;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CTextMan::sAllocTextPool()
+{
+	s_plat_alloc_text_pool();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CText *			CTextMan::sGetTextInstance()
+{
+	CText *p_text = sp_dynamic_text_list;
+
+	if (p_text)
+	{
+		sp_dynamic_text_list = p_text->mp_next;
+		//p_text->AddToDrawList();
+	} else {
+		Dbg_MsgAssert(0, ("Out of CText Instances"));
+	}
+
+	return p_text;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CTextMan::sFreeTextInstance(CText *p_text)
+{
+	//p_text->RemoveFromDrawList();
+	p_text->SetHidden(true);
+
+	// Clear text, otherwise it hangs around
+	// it would get re-used, but it's fragmenting memory	
+	p_text->ClearString();
+
+	p_text->mp_next = sp_dynamic_text_list;
+	sp_dynamic_text_list = p_text;
+}
+
+}
+
+
diff --git a/Code/Gfx/NxFont.h b/Code/Gfx/NxFont.h
new file mode 100644
index 0000000..e65cd3b
--- /dev/null
+++ b/Code/Gfx/NxFont.h
@@ -0,0 +1,229 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxFont.h
+
+
+
+#ifndef	__GFX_NXFONT_H__
+#define	__GFX_NXFONT_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#define		__STATIC_FONT_STRINGS__ 0			// set to 1 to have the old statically allocated strings
+
+namespace Nx
+{
+
+// Forward declarations
+class CTextMan;
+class CWindow2D;
+
+//////////////////////////////////////////////////////////////////////////////
+// The CFont class is the platform independent abstract base class
+// of the platform specific CFont classes
+class	CFont
+{
+public:
+							CFont();
+	virtual					~CFont();
+
+	bool					Load(const char *filename);
+	void					SetSpacings(int charSpacing, int spaceSpacing);
+	void					SetRGBATable(Image::RGBA *pTab);
+	void					MarkAsButtonFont(bool isButton);
+	void					Unload();
+
+	uint32					GetChecksum() const;
+	uint32					GetDefaultHeight() const;
+	uint32					GetDefaultBase() const;
+	void					QueryString(char *String, float &width, float &height) const;
+
+	// Member variables (protected so the p-classes can access them)
+protected:
+	uint32					m_checksum;
+
+	// The virtual functions have a stub implementation.
+private:
+	virtual	bool			plat_load(const char *filename);
+	virtual void			plat_set_spacings(int charSpacing, int spaceSpacing);
+	virtual void			plat_set_rgba_table(Image::RGBA *pTab);
+	virtual void 			plat_mark_as_button_font(bool isButton);
+	virtual	void			plat_unload();
+
+	virtual	uint32			plat_get_default_height() const;
+	virtual	uint32			plat_get_default_base() const;
+	virtual void			plat_query_string(char *String, float &width, float &height) const;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Holds a string of text that needs to be displayed this frame
+class CText
+{
+public:
+							CText(CWindow2D *p_window = NULL);
+	virtual					~CText();
+
+	// If hidden, it won't be drawn that frame
+	void					SetHidden(bool hide);
+	bool					IsHidden() const;
+
+	// Font used for the text.
+	CFont *					GetFont() const;
+	void					SetFont(CFont *p_font);
+
+	// Actual text
+	const char *			GetString() const;
+	void					SetString(const char *p_string);
+	void					ClearString();
+
+	// Position on screen.  Assumes screen size of 640x448.
+	float					GetXPos() const;
+	float					GetYPos() const;
+	void					SetPos(float x, float y);
+
+	// Scale of text.
+	float					GetXScale() const;
+	float					GetYScale() const;
+	void					SetScale(float scale_x, float scale_y);
+
+	// Priority of text.  Higher priority text are
+	// drawn on top of lower priority text.
+	float					GetPriority() const;
+	void					SetPriority(float pri);
+	Nx::ZBufferValue		GetZValue() const;
+	void					SetZValue(Nx::ZBufferValue z);
+
+	// Color of text.
+	Image::RGBA				GetRGBA() const;
+	void					SetRGBA(Image::RGBA rgba, bool colorOverride);
+
+	// Clipping window
+	CWindow2D *				GetWindow() const;
+	void					SetWindow(CWindow2D *p_window);
+
+protected:
+	// Constants
+	enum {
+		vMAX_TEXT_CHARS = 96
+	};
+
+	CFont *					mp_font;
+	#if						__STATIC_FONT_STRINGS__
+	char					m_string[vMAX_TEXT_CHARS];
+	#else
+	char *					m_string;
+	#endif
+	float					m_xpos;
+	float					m_ypos;
+	float					m_xscale;
+	float					m_yscale;
+	float					m_priority;
+	ZBufferValue			m_zvalue;		  // for zbuffer sort
+	Image::RGBA				m_rgba;
+	bool					m_color_override; // colors encoded in text will be overridden if true
+
+	bool					m_hidden;
+	bool					m_use_zbuffer;
+
+	CWindow2D *				mp_window;
+
+	// For free list in CTextMan
+	CText *					mp_next;
+
+private:
+	//
+	virtual void			plat_initialize();
+
+	virtual void			plat_update_hidden();		// Tell engine of update
+	virtual void			plat_update_engine();		// Update engine primitives
+	virtual void			plat_update_priority();
+	virtual void			plat_update_window();
+
+	friend CTextMan;
+
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Static class for memory management of CTexts
+class CTextMan
+{
+public:
+	//
+	static void				sAllocTextPool();
+	static CText *			sGetTextInstance();
+	static void				sFreeTextInstance(CText *p_text);
+
+private:
+	// Constants
+	enum {
+		vMAX_TEXT_INSTANCES = 512
+	};
+
+	// Because it is static, it is declared here, but defined in p_NxFont.cpp
+	static void				s_plat_alloc_text_pool();
+	static CText *			s_plat_get_text_instance();
+	static void				s_plat_free_text_instance(CText *p_text);
+
+	// Array of Text requests
+	static CText *			sp_dynamic_text_list;
+};
+
+/////////////////////////////////////////////////////////
+// CText inline function
+inline bool					CText::IsHidden() const
+{
+	return m_hidden;
+}
+
+inline CFont *				CText::GetFont() const
+{
+	return mp_font;
+}
+
+inline float				CText::GetXPos() const
+{
+	return m_xpos;
+}
+
+inline float				CText::GetYPos() const
+{
+	return m_ypos;
+}
+
+inline float				CText::GetXScale() const
+{
+	return m_xscale;
+}
+
+inline float				CText::GetYScale() const
+{
+	return m_yscale;
+}
+
+inline float				CText::GetPriority() const
+{
+	return m_priority;
+}
+
+inline ZBufferValue			CText::GetZValue() const
+{
+	return m_zvalue;
+}
+
+inline Image::RGBA			CText::GetRGBA() const
+{
+	return m_rgba;
+}
+
+inline CWindow2D *			CText::GetWindow() const
+{
+	return mp_window;
+}
+
+}
+
+#endif // 
+
diff --git a/Code/Gfx/NxFontMan.cpp b/Code/Gfx/NxFontMan.cpp
new file mode 100644
index 0000000..3b44b40
--- /dev/null
+++ b/Code/Gfx/NxFontMan.cpp
@@ -0,0 +1,213 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+
+CFontManager::FontEntry 				CFontManager::s_font_tab[vMAX_FONT_ENTRIES];
+Lst::HashTable	CFontManager::s_font_lookup(4);
+char									CFontManager::s_meta_button_map[NUM_META_BUTTON_ENTRIES];
+bool									CFontManager::s_meta_button_map_initialized = false;
+
+
+void CFontManager::sLoadFont(const char *pName, int charSpacing, int spaceSpacing, Image::RGBA *pColorTab, bool isButtonFont)
+{
+	Dbg_Assert(strlen(pName) < FontEntry::vMAX_NAME_SIZE);
+
+	// See if this font is already loaded.
+	if ( sGetFont( pName ) )
+	{
+		sGetFont( pName )->SetSpacings(charSpacing, spaceSpacing); 
+		if (pColorTab) sGetFont( pName )->SetRGBATable(pColorTab); 
+		sGetFont( pName )->MarkAsButtonFont(isButtonFont); 
+		return;
+	}
+
+	for (int i = 0; i < vMAX_FONT_ENTRIES; i++)
+	{
+		if (!s_font_tab[i].mp_font)
+		{
+			s_font_tab[i].mp_font = s_plat_load_font(pName);
+			s_font_tab[i].mp_font->SetSpacings(charSpacing, spaceSpacing);
+			if (pColorTab)
+				s_font_tab[i].mp_font->SetRGBATable(pColorTab);
+			s_font_tab[i].mp_font->MarkAsButtonFont(isButtonFont);
+						
+			strcpy(s_font_tab[i].mName, pName);
+			s_font_lookup.PutItem(Script::GenerateCRC(pName), &s_font_tab[i]);
+			break;
+		}
+	}
+
+	if (!s_meta_button_map_initialized)
+	{
+		#ifdef __PLAT_XBOX__
+		Script::CArray *p_array = Script::GetArray("meta_button_map_xbox", Script::ASSERT);
+		#else
+		#ifdef __PLAT_NGC__
+		Script::CArray *p_array = Script::GetArray("meta_button_map_gamecube");
+		#else
+		Script::CArray *p_array = Script::GetArray("meta_button_map_ps2", Script::ASSERT);
+		#endif
+		#endif
+		
+		if ( p_array )
+		{
+			for (uint i = 0; i < NUM_META_BUTTON_ENTRIES; i++)
+			{
+				int index = (i < p_array->GetSize()) ? p_array->GetInteger(i) : 0;
+
+				if (index <= 9)
+					s_meta_button_map[i] = '0' + index;
+				else
+					s_meta_button_map[i] = 'a' + index - 10;
+			}
+
+			s_meta_button_map_initialized = true;
+		}
+	}
+}
+
+
+
+
+void CFontManager::sUnloadFont(const char *pName)
+{
+	for (int i = 0; i < vMAX_FONT_ENTRIES; i++)
+	{
+		if (strcmp(s_font_tab[i].mName, pName) == 0)
+		{
+			s_font_lookup.FlushItem(Script::GenerateCRC(pName));
+			
+			s_plat_unload_font(s_font_tab[i].mp_font);
+			s_font_tab[i].mp_font = NULL;
+			
+			break;
+		}
+	}
+}
+
+
+// returns pointer to font if loaded, NULL if not
+Nx::CFont *CFontManager::sGetFont(uint32 checksum)
+{
+	FontEntry *p_entry = s_font_lookup.GetItem(checksum);
+	if (p_entry)
+		return p_entry->mp_font;
+	else
+		return NULL;
+}
+
+// returns pointer to font if loaded, NULL if not
+Nx::CFont *CFontManager::sGetFont(const char *pName)
+{
+	return sGetFont(Script::GenerateCRC(pName));
+}
+
+// returns pointer to name if loaded, NULL if not
+const char *CFontManager::sTestFontLoaded(uint32 checksum)
+{
+	FontEntry *p_entry = s_font_lookup.GetItem(checksum);
+	if (p_entry)
+		return p_entry->mName;
+	else
+		return NULL;
+}
+
+
+
+
+char CFontManager::sMapMetaCharacterToButton(const char *pMetaChar)
+{
+	Dbg_MsgAssert(s_meta_button_map_initialized, ("meta button character table not initialized"));
+	return s_meta_button_map[Str::DehexifyDigit(pMetaChar)];
+}
+
+
+
+
+bool ScriptLoadFont(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	bool is_buttons_font = pParams->ContainsFlag("buttons_font");
+	
+	const char *p_name;
+	
+	
+	if (!pParams->GetText(NONAME, &p_name) && !is_buttons_font)
+		Dbg_MsgAssert(0, ("no font specified"));
+	
+	
+	if (is_buttons_font)
+	{
+		#ifdef __PLAT_XBOX__
+		p_name = "ButtonsXbox";
+		#else
+			#ifdef __PLAT_NGC__
+			p_name = "ButtonsNgc";
+			#else
+			p_name = "ButtonsPs2";
+			#endif
+		#endif
+	}
+
+	Mem::PushMemProfile((char*)p_name);
+	
+	float char_spacing = 0;
+	pParams->GetFloat("char_spacing", &char_spacing);
+	
+	float space_spacing = 0;
+	pParams->GetFloat("space_spacing", &space_spacing);
+	
+	Script::CArray *p_script_col_tab;
+
+   	// 15Jan02 JCB - Make this local to avoid fragmentation when allocating from the heap.
+	Image::RGBA rgba_tab[16];
+	Image::RGBA* p_rgba_tab = &rgba_tab[0];
+
+	if (pParams->GetArray("color_tab", &p_script_col_tab))
+	{
+		for (int i = 0; i < (int) p_script_col_tab->GetSize() && i < 16; i++)
+		{
+			Script::CArray *p_entry = p_script_col_tab->GetArray(i);
+			uint32 rgba = 0;
+			int size = p_entry->GetSize();
+			Dbg_MsgAssert(size >= 3 && size <= 4, ("wrong size %d for color array", size));
+			for (int j = 0; j < size; j++) 
+			{
+				rgba |= (p_entry->GetInteger(j) & 255) << (j*8);
+			}
+			p_rgba_tab[i] = *((Image::RGBA *) &rgba);
+		}
+	}
+	
+	Nx::CFontManager::sLoadFont(p_name, (int) char_spacing, (int) space_spacing, p_rgba_tab, is_buttons_font);
+
+	Mem::PopMemProfile();		
+
+	return true;
+}
+
+
+
+
+bool ScriptUnloadFont(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no font specified"));
+	
+	Nx::CFontManager::sUnloadFont(p_name);
+	return true;
+}
+
+
+
+
+}
+
diff --git a/Code/Gfx/NxFontMan.h b/Code/Gfx/NxFontMan.h
new file mode 100644
index 0000000..b0fac7c
--- /dev/null
+++ b/Code/Gfx/NxFontMan.h
@@ -0,0 +1,69 @@
+#ifndef __GFX_2D_FONTMAN_H__
+#define __GFX_2D_FONTMAN_H__
+
+#include 
+
+#include 
+
+namespace Script
+{
+	class CScriptStructure;
+	class CScript;
+}
+
+namespace Nx
+{
+
+class CFontManager
+{
+public:
+	static void					sLoadFont(const char *pName, int charSpacing = 0, 
+										  int spaceSpacing = 0, Image::RGBA *pColorTab = NULL,
+										  bool isButtonFont = false);
+	static void					sUnloadFont(const char *pName);
+	static Nx::CFont *			sGetFont(const char *pName);
+	static Nx::CFont *			sGetFont(uint32 checksum);
+
+	static const char *			sTestFontLoaded(uint32 checksum);
+	
+	static char					sMapMetaCharacterToButton(const char *pMetaChar);
+
+private:
+	// Constants
+	enum {
+		vMAX_FONT_ENTRIES 		= 16,
+		NUM_META_BUTTON_ENTRIES = 32,
+	};
+
+
+	struct FontEntry
+	{
+		enum {
+			vMAX_NAME_SIZE = 24
+		};
+
+								FontEntry() : mp_font(NULL) { }
+
+		char					mName[vMAX_NAME_SIZE];
+		Nx::CFont *				mp_font;
+	};
+	
+	static FontEntry 					s_font_tab[vMAX_FONT_ENTRIES];
+	static Lst::HashTable	s_font_lookup;
+
+	static char					s_meta_button_map[NUM_META_BUTTON_ENTRIES];
+	static bool					s_meta_button_map_initialized;
+	
+	// The platform dependent calls
+	static Nx::CFont *			s_plat_load_font(const char *pName);
+	static void					s_plat_unload_font(Nx::CFont *pFont);
+};
+
+
+
+bool ScriptLoadFont(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptUnloadFont(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+}
+
+#endif // FONTMAN
diff --git a/Code/Gfx/NxGeom.cpp b/Code/Gfx/NxGeom.cpp
new file mode 100644
index 0000000..5a3df3b
--- /dev/null
+++ b/Code/Gfx/NxGeom.cpp
@@ -0,0 +1,1093 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxGeom.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/27/2002
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom* 			CGeom::plat_clone(bool instance, CScene* pDestScene)
+{
+	printf ("STUB: PlatClone\n");
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom* 			CGeom::plat_clone(bool instance, CModel* pDestModel)
+{
+	printf ("STUB: PlatClone\n");
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CGeom::plat_load_geom_data(CMesh* pMesh, CModel* pModel, bool color_per_material)
+{
+	printf ("STUB: PlatLoadGeomData\n");
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_finalize()
+{
+	// do nothing...  this is only needed for the PS2 right now
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32			CGeom::plat_get_checksum()
+{
+	printf ("STUB: PlatGetChecksum\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_color(Image::RGBA rgba)
+{
+	printf ("STUB: PlatSetColor\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA		CGeom::plat_get_color() const
+{
+	printf ("STUB: PlatGetColor\n");
+	return Image::RGBA(0, 0, 0, 0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_clear_color()
+{
+	printf ("STUB: PlatClearColor\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CGeom::plat_set_material_color(uint32 mat_checksum, int pass, Image::RGBA rgba)
+{
+	printf ("STUB: PlatSetMaterialColor\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA		CGeom::plat_get_material_color(uint32 mat_checksum, int pass)
+{
+	printf ("STUB: PlatGetMaterialColor\n");
+	return Image::RGBA(0, 0, 0, 0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_visibility(uint32 mask)
+{
+	printf ("STUB: PlatSetVisibility\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32			CGeom::plat_get_visibility() const
+{
+	printf ("STUB: PlatGetVisibility\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_active(bool active)
+{
+	printf ("STUB: PlatSetActive\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CGeom::plat_is_active() const
+{
+	printf ("STUB: PlatIsActive\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::CBBox &	CGeom::plat_get_bounding_box() const
+{
+	static Mth::CBBox stub_bbox;
+	printf ("STUB: PlatGetBoundingBox\n");
+	return stub_bbox;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector CGeom::plat_get_bounding_sphere() const
+{
+	printf ("STUB: PlatGetBoundingSphere\n");
+	return Mth::Vector( 0.0f, 0.0f, 0.0f, 10000.0f );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_world_position(const Mth::Vector& pos)
+{
+	printf ("STUB: PlatSetWorldPosition\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CGeom::plat_get_world_position() const
+{
+	static Mth::Vector stub_vec;
+	printf ("STUB: PlatGetWorldPosition\n");
+	return stub_vec;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_orientation(const Mth::Matrix& orient)
+{
+	printf ("STUB: PlatSetOrientation\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Matrix & CGeom::plat_get_orientation() const
+{
+	static Mth::Matrix stub_mat;
+	printf ("STUB: PlatGetOrientation\n");
+	return stub_mat;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 			CGeom::plat_rotate_y(Mth::ERot90 rot)
+{
+	printf ("STUB: PlatRotateY\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_transform(const Mth::Matrix& transform)
+{
+	printf ("STUB: PlatSetTransform\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Matrix &	CGeom::plat_get_transform() const
+{
+	static Mth::Matrix stub_mat;
+	printf ("STUB: PlatGetTransform\n");
+	return stub_mat;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_scale(const Mth::Vector& scale)
+{
+	printf ("STUB: PlatSetScale\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector		CGeom::plat_get_scale() const
+{
+	printf ("STUB: PlatGetScale\n");
+	return Mth::Vector(0, 0, 0, 0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_model_lights(CModelLights *p_model_lights)
+{
+	printf ("STUB: PlatSetModelLights\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CGeom::plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones)
+{
+	printf ("STUB: PlatRender\n");
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CGeom::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	printf ("STUB: PlatSetBoundingSphere\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CGeom::plat_hide_polys( uint32 mask )
+{
+	printf ("STUB: PlatHidePolys\n");
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CGeom::plat_enable_shadow( bool enabled )
+{
+//	printf ("STUB: PlatEnableShadow\n");
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// - returns number of renderable verts in a C...Geom
+// this should be the same number of verts as is returned/expected by the following functions 
+int 		CGeom::plat_get_num_render_verts()								
+{
+	printf ("STUB: CGeom::plat_get_num_render_verts\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// - gets a single array of the verts positions as regular floating point Mth::Vectors
+// p_verts points to memory supplied by the caller
+void 		CGeom::plat_get_render_verts(Mth::Vector *p_verts)			
+{
+	printf ("STUB: CGeom::plat_get_render_verts\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int 		CGeom::plat_get_num_render_polys()								
+{
+	printf ("STUB: CGeom::plat_get_num_render_polys\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int 		CGeom::plat_get_num_render_base_polys()								
+{
+	printf ("STUB: CGeom::plat_get_num_render_base_polys\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// - gets a single array of the verts colors as RGBA
+// p_colors points to memory supplied by the caller
+void 		CGeom::plat_get_render_colors(Image::RGBA *p_colors)		// - gets an array of vertex colors
+{
+	printf ("STUB: CGeom::plat_get_render_colors\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// - sets the verts after modification
+void 		CGeom::plat_set_render_verts(Mth::Vector *p_verts)			  
+{
+	printf ("STUB: CGeom::plat_set_render_verts\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// - sets the colors after modification
+void 		CGeom::plat_set_render_colors(Image::RGBA *p_colors)				
+{
+	printf ("STUB: CGeom::plat_set_render_colors\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void		CGeom::plat_set_bone_matrix_data( Mth::Matrix* pBoneMatrices, int numBones )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CGeom::plat_set_uv_wibble_params(float u_vel, float u_amp, float u_freq, float u_phase,
+											 float v_vel, float v_amp, float v_freq, float v_phase)
+{
+	printf ("STUB: CGeom::plat_set_uv_wibble_params\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CGeom::plat_use_explicit_uv_wibble(bool yes)
+{
+	printf ("STUB: CGeom::plat_use_explicit_uv_wibble\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CGeom::plat_set_uv_wibble_offsets(float u_offset, float v_offset)
+{
+	printf ("STUB: CGeom::plat_set_uv_wibble_offsets\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CGeom::plat_set_uv_wibble_offsets(uint32 mat_checksum, int pass, float u_offset, float v_offset)
+{
+	printf ("STUB: CGeom::plat_set_uv_wibble_offsets\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CGeom::plat_set_uv_matrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat)
+{
+	printf ("STUB: CGeom::plat_set_uv_matrix\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CGeom::plat_allocate_uv_matrix_params(uint32 mat_checksum, int pass)
+{
+	// only needed by the xbox right now...
+
+	return true;
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom::CGeom()
+{
+	m_cloned = vORIGINAL;
+	mp_coll_tri_data = NULL;
+	mp_orig_render_colors = NULL;
+	m_multipleColorsEnabled = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom::~CGeom()
+{
+	if (m_cloned != vORIGINAL)			// delete cloned data
+	{
+		if (mp_coll_tri_data)
+		{
+			// Right now, cloning of collision isn't handled at the CGeom level
+			//delete mp_coll_tri_data;
+		}
+	}
+	
+	if (mp_orig_render_colors)
+	{
+		delete mp_orig_render_colors;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom*  CGeom::Clone(bool instance, CScene* pDestScene)
+{
+#ifdef __PLAT_NGPS__
+	// Garrett: Want to be able to disable copy just in case it opens up crashes on the Park Editor
+	if (Script::GetInt( "disable_clone_copy", false ))
+	{
+		instance = true;
+	}
+#endif
+
+	// Create new instance and clone stuff below p-line
+	CGeom* p_new_geom = plat_clone(instance, pDestScene);
+
+	if (p_new_geom)
+	{
+		p_new_geom->m_cloned = (instance) ? vINSTANCE : vCOPY;
+
+		if (mp_coll_tri_data)
+		{
+#if 1
+			// Garrett: Just copying the pointer now, since the CGeom version is just used for reference
+			p_new_geom->mp_coll_tri_data = mp_coll_tri_data;
+#else
+			// Make NULL for now until we can figure out a better way of cloning collision with CSector
+			p_new_geom->mp_coll_tri_data = NULL;
+			//p_new_geom->mp_coll_tri_data = mp_coll_tri_data->Clone(instance);
+			//Dbg_Assert(p_new_geom->mp_coll_tri_data);
+#endif
+		}
+		// Mick:  bit of a patch - attempt to fix crash from deleting thigns twice
+		p_new_geom->mp_orig_render_colors = NULL;
+	}
+
+	return p_new_geom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom* CGeom::Clone(bool instance, CModel* pDestModel)
+{
+	// Create new instance and clone stuff below p-line
+	CGeom* p_new_geom = plat_clone(instance, pDestModel);
+
+	if (p_new_geom)
+	{
+		p_new_geom->m_cloned = (instance) ? vINSTANCE : vCOPY;
+
+		Dbg_MsgAssert(!mp_coll_tri_data, ("Cloning of model geometry doesn't support collision now"));
+
+		// Mick:  bit of a patch - attempt to fix crash from deleting thigns twice
+		p_new_geom->mp_orig_render_colors = NULL;
+	}
+
+	return p_new_geom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::LoadGeomData( CMesh* pMesh, CModel* pModel, bool color_per_material )
+{
+	// All data created here is an instance of something else
+	m_cloned = vINSTANCE;
+	m_multipleColorsEnabled = color_per_material;
+
+	return plat_load_geom_data( pMesh, pModel, color_per_material );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::Finalize()
+{
+	plat_finalize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGeom::GetChecksum()
+{		
+	return plat_get_checksum();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetColor(Image::RGBA rgba)
+{		
+	plat_set_color(rgba);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::ClearColor()
+{		
+	plat_clear_color();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA	CGeom::GetColor() const
+{		
+	return plat_get_color();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::SetMaterialColor(uint32 mat_checksum, int pass, Image::RGBA rgba)
+{
+	return plat_set_material_color(mat_checksum, pass, rgba);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA	CGeom::GetMaterialColor(uint32 mat_checksum, int pass)
+{
+	return plat_get_material_color(mat_checksum, pass);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetVisibility(uint32 mask)
+{
+	plat_set_visibility(mask);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGeom::GetVisibility() const
+{
+	return plat_get_visibility();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetActive( bool active )
+{
+	plat_set_active( active );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::IsActive() const
+{
+	return plat_is_active();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::CBBox & CGeom::GetBoundingBox() const
+{
+	return plat_get_bounding_box();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector CGeom::GetBoundingSphere() const
+{
+	return plat_get_bounding_sphere();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetBoundingSphere( const Mth::Vector& boundingSphere )
+{
+	plat_set_bounding_sphere( boundingSphere );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetWorldPosition(const Mth::Vector& pos)
+{
+	plat_set_world_position(pos);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector & CGeom::GetWorldPosition() const
+{
+	return plat_get_world_position();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetOrientation(const Mth::Matrix& orient)
+{
+	plat_set_orientation(orient);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Matrix & CGeom::GetOrientation() const
+{
+	return plat_get_orientation();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::RotateY(Mth::ERot90 rot)
+{
+	plat_rotate_y(rot);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetScale(const Mth::Vector& scale)
+{
+	plat_set_scale(scale);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CGeom::GetScale() const
+{
+	return plat_get_scale();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetCollTriData(CCollObjTriData *p_coll_data)
+{
+	mp_coll_tri_data = p_coll_data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCollObjTriData* CGeom::GetCollTriData() const
+{
+	return mp_coll_tri_data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetModelLights(CModelLights *p_model_lights)
+{
+	plat_set_model_lights(p_model_lights);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::Render( Mth::Matrix* pMatrix, Mth::Matrix* ppBoneMatrices, int numBones )
+{
+	return plat_render( pMatrix, ppBoneMatrices, numBones );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CGeom::SetBoneMatrixData( Mth::Matrix* pBoneMatrices, int numBones )
+{
+	plat_set_bone_matrix_data( pBoneMatrices, numBones );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::HidePolys( uint32 mask )
+{
+	return plat_hide_polys( mask );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::EnableShadow( bool enabled )
+{
+	return plat_enable_shadow( enabled );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGeom::GetNumRenderVerts()								// - returns number of renderable verts
+{
+		return plat_get_num_render_verts();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::GetRenderVerts(Mth::Vector *p_verts)				// - gets a single array of the verts
+{
+		return plat_get_render_verts(p_verts);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::GetRenderColors(Image::RGBA *p_colors)				// - gets an array of vertex colors
+{
+		return plat_get_render_colors(p_colors);
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Return the total number of renerable polygons 
+int	CGeom::GetNumRenderPolys()
+{
+ 	return		plat_get_num_render_polys();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Return the total number of base polygons (excludes those generated for multipass)
+int	CGeom::GetNumRenderBasePolys()
+{
+ 	return		plat_get_num_render_base_polys();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Get the original render colors
+// if this is the first time this function was called
+// then we will allocate memory and store the colors
+void CGeom::GetOrigRenderColors(Image::RGBA *p_colors)				// - gets an array of vertex colors
+{
+	int verts = GetNumRenderVerts();
+	if (!mp_orig_render_colors)
+	{
+		// GJ:  this check was previously used to make sure the malloc caused no bottomup
+		// fragmentation during cutscenes.	however, it's no longer needed because of
+		// the new cutscene heap.  i'm going to leave the code here commented out, just in case
+//		#ifdef	__NOPT_ASSERT__
+//		Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+//		Dbg_MsgAssert( !skate_mod->GetMovieManager()->IsRolling(), ( "Can't create lights during cutscenes" ) );
+//		#endif
+
+		// Mick: Game specific optimization here
+		// because the "fake lights" are only used in non-net games
+		// we use the frontend/net heap to store the original colors
+		// as it is under utilized in a a single player game
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());		
+		mp_orig_render_colors = (Image::RGBA*) Mem::Malloc(verts * sizeof(Image::RGBA));
+		Mem::Manager::sHandle().PopContext();
+		
+		GetRenderColors(mp_orig_render_colors);
+	}
+	memcpy(p_colors,mp_orig_render_colors,verts * sizeof(Image::RGBA));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetRenderVerts(Mth::Vector *p_verts)				// - sets the verts after modification
+{
+		return plat_set_render_verts(p_verts);
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetRenderColors(Image::RGBA *p_colors)				// - sets the colors after modification
+{
+
+		return plat_set_render_colors(p_colors);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetUVWibbleParams(float u_vel, float u_amp, float u_freq, float u_phase,
+							  float v_vel, float v_amp, float v_freq, float v_phase)
+{
+	plat_set_uv_wibble_params(u_vel, u_amp, u_freq, u_phase, v_vel, v_amp, v_freq, v_phase);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::UseExplicitUVWibble(bool yes)
+{
+	plat_use_explicit_uv_wibble(yes);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGeom::SetUVWibbleOffsets(float u_offset, float v_offset)
+{
+	plat_set_uv_wibble_offsets(u_offset, v_offset);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::SetUVWibbleOffsets(uint32 mat_checksum, int pass, float u_offset, float v_offset)
+{
+	return plat_set_uv_wibble_offsets(mat_checksum, pass, u_offset, v_offset);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::SetUVMatrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat)
+{
+	return plat_set_uv_matrix(mat_checksum, pass, mat);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::AllocateUVMatrixParams(uint32 mat_checksum, int pass)
+{
+	return plat_allocate_uv_matrix_params(mat_checksum, pass);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGeom::MultipleColorsEnabled()
+{
+	return m_multipleColorsEnabled;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
diff --git a/Code/Gfx/NxGeom.h b/Code/Gfx/NxGeom.h
new file mode 100644
index 0000000..d2b1d4c
--- /dev/null
+++ b/Code/Gfx/NxGeom.h
@@ -0,0 +1,213 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxGeom.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_NXGEOM_H__
+#define	__GFX_NXGEOM_H__
+                   
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#ifndef	__CORE_ROT90_H
+    #include 
+#endif
+
+#ifdef __PLAT_NGC__
+#include "sys/ngc/p_gx.h"
+#endif		// __PLAT_NGC__
+
+namespace Mth
+{
+	class Matrix;
+	class Vector;
+	class CBBox;
+}
+			
+namespace Image
+{
+	struct RGBA;
+}
+
+namespace Nx
+{
+	class CScene;
+	class CMesh;
+	class CModel;
+	class CModelLights;
+	class CCollObjTriData;
+
+class CGeom : public Spt::Class
+{
+
+public:
+
+    // The basic interface to the geom object
+    // this is the machine independent part	
+    // machine independent range checking, etc can go here	
+						CGeom();
+    virtual				~CGeom();
+
+	CGeom *				Clone(bool instance, CScene* pDestScene=NULL);
+	CGeom *				Clone(bool instance, CModel* pDestModel);		// Clones a Geom from one model into a new model
+
+	bool				LoadGeomData(CMesh* pMesh, CModel* pModel, bool color_per_material);
+	void				Finalize();
+
+	uint32				GetChecksum();
+
+	void				SetColor(Image::RGBA rgba);
+	Image::RGBA			GetColor() const;
+	void				ClearColor();
+
+	// Can only be used on geometries that were created with color_per_material
+	bool				SetMaterialColor(uint32 mat_checksum, int pass, Image::RGBA rgba);
+	Image::RGBA			GetMaterialColor(uint32 mat_checksum, int pass);
+
+	void				SetVisibility(uint32 mask);
+	uint32				GetVisibility() const;
+
+	void				SetActive(bool active);
+	bool				IsActive() const;
+
+	const Mth::CBBox &	GetBoundingBox() const;
+
+	void				SetBoundingSphere(const Mth::Vector& boundingSphere);
+	const Mth::Vector	GetBoundingSphere() const;
+
+	void				SetWorldPosition(const Mth::Vector& pos);
+	const Mth::Vector &	GetWorldPosition() const;
+
+	void				SetOrientation(const Mth::Matrix& orient);
+	const Mth::Matrix &	GetOrientation() const;
+
+	void 				RotateY(Mth::ERot90 rot);
+
+	void				SetTransform(const Mth::Matrix& transform);		// Overrides both world pos and orientation
+	const Mth::Matrix &	GetTransform() const;
+
+	void				SetScale(const Mth::Vector& scale);
+	Mth::Vector			GetScale() const;
+
+	void				SetCollTriData(CCollObjTriData *);
+	CCollObjTriData *	GetCollTriData() const;
+
+	void				SetModelLights(CModelLights *);
+
+	bool                Render( Mth::Matrix* pMatrix, Mth::Matrix* ppBoneMatrices, int numBones );
+	void				SetBoneMatrixData( Mth::Matrix* pBoneMatrices, int numBones );
+	bool				HidePolys(uint32 mask);
+	bool				EnableShadow(bool enabled);
+
+	// used by CModelBuilder to figure out which color modulation function to use
+	bool				MultipleColorsEnabled();
+
+// Functions for getting and modifying the raw vertex information in a CGeom	
+	int 				GetNumRenderPolys();									// - returns number of renderable polys
+	int 				GetNumRenderBasePolys();							// - returns number of first pass polys
+	int 				GetNumRenderVerts();								// - returns number of renderable verts
+	void 				GetRenderVerts(Mth::Vector *p_verts);				// - gets a single array of the verts
+	void 				GetOrigRenderColors(Image::RGBA *p_colors);			// - gets original render colors, storing them if this si the first time
+	void 				GetRenderColors(Image::RGBA *p_colors);				// - gets an array of vertex colors
+	void 				SetRenderVerts(Mth::Vector *p_verts);				// - sets the verts after modification
+	void 				SetRenderColors(Image::RGBA *p_colors);				// - sets the colors after modification
+
+	// Wibble functions
+	void				SetUVWibbleParams(float u_vel, float u_amp, float u_freq, float u_phase,
+										  float v_vel, float v_amp, float v_freq, float v_phase);
+	void				UseExplicitUVWibble(bool yes);
+	void				SetUVWibbleOffsets(float u_offset, float v_offset);
+	bool				SetUVWibbleOffsets(uint32 mat_checksum, int pass, float u_offset, float v_offset);
+	bool				SetUVMatrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat);
+	bool				AllocateUVMatrixParams(uint32 mat_checksum, int pass);
+
+protected:
+	// Declares if and what type of clone it is
+	enum CloneType
+	{
+		vORIGINAL = 0,			// not cloned
+		vINSTANCE,
+		vCOPY,
+	};
+
+	CloneType			m_cloned;										// marks if cloned object
+	CCollObjTriData *	mp_coll_tri_data;								// Collision geom data
+
+	Image::RGBA		*	mp_orig_render_colors;
+	bool				m_multipleColorsEnabled;
+
+private:
+    // The virtual functions will have a stub implementation
+    // in p_nxgeom.cpp
+	virtual CGeom *		plat_clone(bool instance, CScene* pDestScene=NULL);
+	virtual CGeom *		plat_clone(bool instance, CModel* pDestModel);
+
+	virtual	bool		plat_load_geom_data(CMesh* pMesh, CModel* pModel, bool color_per_material);
+
+	virtual void		plat_finalize();
+
+	virtual uint32		plat_get_checksum();
+
+	virtual	void		plat_set_color(Image::RGBA rgba);
+	virtual	Image::RGBA	plat_get_color() const;
+	virtual	void		plat_clear_color();
+
+	virtual bool		plat_set_material_color(uint32 mat_checksum, int pass, Image::RGBA rgba);
+	virtual Image::RGBA	plat_get_material_color(uint32 mat_checksum, int pass);
+
+	virtual	void		plat_set_visibility(uint32 mask);
+	virtual	uint32		plat_get_visibility() const;
+
+	virtual	void		plat_set_active(bool active);
+	virtual	bool		plat_is_active() const;
+
+	virtual const Mth::CBBox &	plat_get_bounding_box() const;
+	
+	virtual void		plat_set_bounding_sphere(const Mth::Vector& boundingSphere);
+	virtual const Mth::Vector	plat_get_bounding_sphere() const;
+
+	virtual void		plat_set_world_position(const Mth::Vector& pos);
+	virtual const Mth::Vector &	plat_get_world_position() const;
+
+	virtual void		plat_set_orientation(const Mth::Matrix& orient);
+	virtual const Mth::Matrix &	plat_get_orientation() const;
+
+	virtual void 		plat_rotate_y(Mth::ERot90 rot);
+
+	virtual void		plat_set_transform(const Mth::Matrix& transform);
+	virtual const Mth::Matrix &	plat_get_transform() const;
+
+	virtual void		plat_set_scale(const Mth::Vector& scale);
+	virtual Mth::Vector plat_get_scale() const;
+
+	virtual void		plat_set_model_lights(CModelLights *);
+
+	virtual bool		plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones);
+	virtual void		plat_set_bone_matrix_data( Mth::Matrix* pBoneMatrices, int numBones );
+	virtual bool		plat_hide_polys(uint32 mask);
+	virtual bool		plat_enable_shadow( bool enabled );
+
+	virtual	int 		plat_get_num_render_polys();
+	virtual	int 		plat_get_num_render_base_polys();
+	virtual	int 		plat_get_num_render_verts();								// - returns number of renderable verts
+	virtual	void 		plat_get_render_verts(Mth::Vector *p_verts);				// - gets a single array of the verts
+	virtual	void 		plat_get_render_colors(Image::RGBA *p_colors);				// - gets an array of vertex colors
+	virtual void 		plat_set_render_verts(Mth::Vector *p_verts);				// - sets the verts after modification
+	virtual	void 		plat_set_render_colors(Image::RGBA *p_colors);				// - sets the colors after modification
+
+	virtual void		plat_set_uv_wibble_params(float u_vel, float u_amp, float u_freq, float u_phase,
+												  float v_vel, float v_amp, float v_freq, float v_phase);
+	virtual void		plat_use_explicit_uv_wibble(bool yes);
+	virtual void		plat_set_uv_wibble_offsets(float u_offset, float v_offset);
+	virtual bool		plat_set_uv_wibble_offsets(uint32 mat_checksum, int pass, float u_offset, float v_offset);
+	virtual bool		plat_set_uv_matrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat);
+	virtual bool		plat_allocate_uv_matrix_params(uint32 mat_checksum, int pass);
+};
+
+}
+
+#endif // __GFX_NXGEOM_H__
+
diff --git a/Code/Gfx/NxHierarchy.h b/Code/Gfx/NxHierarchy.h
new file mode 100644
index 0000000..f1b6e12
--- /dev/null
+++ b/Code/Gfx/NxHierarchy.h
@@ -0,0 +1,136 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxHierarchy.h
+//* OWNER:          Garrett Jost
+//* CREATION DATE:  7/26/2002
+//****************************************************************************
+
+#ifndef	__GFX_NXHIERARCHY_H__
+#define	__GFX_NXHIERARCHY_H__
+
+                           
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Nx
+{
+
+// Hierarchy information available to game.  It is found at the beginning of a geom. file
+// although it is not used by the geometry directly.
+class CHierarchyObject
+{
+public:
+						CHierarchyObject();
+						~CHierarchyObject();
+
+	void				SetChecksum(uint32 checksum);
+	uint32				GetChecksum() const;
+	void				SetParentChecksum(uint32 checksum);
+	uint32				GetParentChecksum() const;
+	void				SetParentIndex(int16 index);
+	int16				GetParentIndex() const;
+	void				SetBoneIndex(sint8 index);
+	sint8				GetBoneIndex() const;
+
+	void				SetSetupMatrix(const Mth::Matrix& mat);
+	const Mth::Matrix &	GetSetupMatrix() const;
+
+	//static CHierarchyObject*	sGetHierarchyArray(uint8 *pPipData, int& size);
+	
+protected:
+	uint32				m_checksum;			// Object checksum
+	uint32				m_parent_checksum;	// Checksum of parent, or 0 if root object
+	int16				m_parent_index;		// Index of parent in the hierarchy array (or -1 if root object)
+	sint8				m_bone_index;		// The index of the bone matrix used on this object
+	uint8				m_pad_8;
+	uint32				m_pad_32;
+	Mth::Matrix			m_setup_matrix;		// Initial local to parent matrix
+};
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline CHierarchyObject::CHierarchyObject()
+{
+	m_checksum = 0;
+	m_parent_checksum = 0;
+
+	m_parent_index = -1;
+	m_bone_index = 0;
+
+	m_setup_matrix.Ident();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline CHierarchyObject::~CHierarchyObject()
+{
+}
+
+inline void					CHierarchyObject::SetChecksum(uint32 checksum)
+{
+	m_checksum = checksum;
+}
+
+inline uint32				CHierarchyObject::GetChecksum() const
+{
+	return m_checksum;
+}
+
+inline void					CHierarchyObject::SetParentChecksum(uint32 checksum)
+{
+	m_parent_checksum = checksum;
+}
+
+inline uint32				CHierarchyObject::GetParentChecksum() const
+{
+	return m_parent_checksum;
+}
+
+inline void					CHierarchyObject::SetParentIndex(int16 index)
+{
+	m_parent_index = index;
+}
+
+inline int16				CHierarchyObject::GetParentIndex() const
+{
+	return m_parent_index;
+}
+
+inline void					CHierarchyObject::SetBoneIndex(sint8 index)
+{
+	m_bone_index = index;
+}
+
+inline sint8				CHierarchyObject::GetBoneIndex() const
+{
+	return m_bone_index;
+}
+
+inline void					CHierarchyObject::SetSetupMatrix(const Mth::Matrix& mat)
+{
+	m_setup_matrix = mat;
+}
+
+inline const Mth::Matrix &	CHierarchyObject::GetSetupMatrix() const
+{
+	return m_setup_matrix;
+}
+
+}
+
+#endif // __GFX_NXMESH_H__
+
+
diff --git a/Code/Gfx/NxImposter.cpp b/Code/Gfx/NxImposter.cpp
new file mode 100644
index 0000000..fc7440e
--- /dev/null
+++ b/Code/Gfx/NxImposter.cpp
@@ -0,0 +1,445 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:			Nx Imposter												**
+**																			**
+**	File name:		gfx/NxImposter.cpp										**
+**																			**
+**	Created by:		03/13/03	-	dc										**
+**																			**
+**	Description:	Imposter and Imposter Management code					**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC nximposter
+// @module nximposter | None
+// @subindex Scripting Database
+// @index script | nximposter
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CImposterManager::CImposterManager( void )
+{
+	mp_group_table						= new Lst::HashTable< CImposterGroup >( 8 );
+	m_max_imposters_to_redraw_per_frame	= 1;
+}
+
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CImposterManager::~CImposterManager( void )
+{
+	delete mp_group_table;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::Cleanup( void )
+{
+	// Goes through and removes all imposter groups.
+	mp_group_table->IterateStart();
+	CImposterGroup *p_imposter_group = mp_group_table->IterateNext();
+	while( p_imposter_group )
+	{
+		CImposterGroup *p_next	= mp_group_table->IterateNext();
+		delete p_imposter_group;
+		p_imposter_group		= p_next;
+	}
+	mp_group_table->FlushAllItems();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::AddGeomToImposter( uint32 group_checksum, Nx::CGeom *p_geom )
+{
+	// See if this imposter group exists already, create it if not.
+	CImposterGroup *p_group = mp_group_table->GetItem( group_checksum );
+	if( p_group == NULL )
+	{
+		p_group = plat_create_imposter_group();
+		mp_group_table->PutItem( group_checksum, p_group );
+
+		// Set the bounding box to be that of the 'founding' CGeom.
+		p_group->SetCompositeBoundingBox( p_geom->GetBoundingBox());
+	}
+	else
+	{
+		// Add the bounding box details of this CGeom.
+		p_group->AddBoundingBox( p_geom->GetBoundingBox());
+	}
+
+	// Add the specified CGeom to the CImposterGroup's list.
+	p_group->AddGeom( p_geom );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::RemoveGeomFromImposter( uint32 group_checksum, Nx::CGeom *p_geom )
+{
+	CImposterGroup *p_group = mp_group_table->GetItem( group_checksum );
+	if( p_group )
+	{
+		p_group->RemoveGeom( p_geom );
+	}
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::ProcessImposters( void )
+{
+	int imposters_drawn = 0;
+
+	mp_group_table->IterateStart();
+	CImposterGroup *p_imposter_group = mp_group_table->IterateNext();
+	while( p_imposter_group )
+	{
+		// Once per frame process function, for platform-specific tasks.
+		p_imposter_group->Process();
+
+		// Determine whether this imposter is no longer visible, or is outside the switch distance.
+		float d = p_imposter_group->CheckDistance();
+
+		if( d <= p_imposter_group->GetSwitchDistance())
+		{
+			// This imposter is no longer outside the switch distance, or is not visible. Remove the imposter polygon if it exists.
+			p_imposter_group->RemoveImposterPolygon();
+		}
+		else
+		{
+			// This imposter is outside the switch distance. Create the imposter polygon if it doesn't exist...
+			if( p_imposter_group->ImposterPolygonExists() == false )
+			{
+				if( imposters_drawn < m_max_imposters_to_redraw_per_frame )
+				{
+					p_imposter_group->CreateImposterPolygon();
+					++imposters_drawn;
+				}
+			}
+			else
+			{
+				// ...Otherwise determine whether the imposter polygon requires updating.
+				if( imposters_drawn < m_max_imposters_to_redraw_per_frame )
+				{
+					bool drawn = p_imposter_group->UpdateImposterPolygon();
+					if( drawn )
+						++imposters_drawn;
+				}
+			}
+		}
+		p_imposter_group = mp_group_table->IterateNext();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::DrawImposters( void )
+{
+	plat_pre_render_imposters();
+
+	mp_group_table->IterateStart();
+	CImposterGroup *p_imposter_group = mp_group_table->IterateNext();
+	while( p_imposter_group )
+	{
+		if( p_imposter_group->ImposterPolygonExists())
+		{
+			p_imposter_group->DrawImposterPolygon();
+		}
+		p_imposter_group = mp_group_table->IterateNext();
+	}
+
+	plat_post_render_imposters();
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CImposterGroup::CImposterGroup( void )
+{
+	m_switch_distance			= 960.0f;
+	mp_geom_list				= new Lst::Head < Nx::CGeom >;
+	m_imposter_polygon_exists	= false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CImposterGroup::~CImposterGroup( void )
+{
+	// Remove all nodes from the table.
+	Lst::Node *p_node, *p_next;
+	for( p_node = mp_geom_list->GetNext(); p_node; p_node = p_next )
+	{
+		p_next = p_node->GetNext();
+		delete p_node;
+	}
+
+	// Remove the table itself.
+	delete mp_geom_list;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::AddGeom( CGeom *p_geom )
+{
+	// Create a new node.
+	Lst::Node *p_new_node = new Lst::Node( p_geom );
+
+	// Link in the new node.
+	mp_geom_list->AddToTail( p_new_node );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::RemoveGeom( CGeom *p_geom )
+{
+	Lst::Node *p_node, *p_next;
+	for( p_node = mp_geom_list->GetNext(); p_node; p_node = p_next )
+	{
+		p_next = p_node->GetNext();
+
+		if( p_node->GetData() == p_geom )
+		{
+			delete p_node;
+			break;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::SetCompositeBoundingBox( const Mth::CBBox & bbox )
+{
+	m_composite_bbox			= bbox;
+	m_composite_bbox_mid		= m_composite_bbox.GetMin() + ( 0.5f * ( m_composite_bbox.GetMax() - m_composite_bbox.GetMin()));
+	m_composite_bsphere_radius	= ( m_composite_bbox.GetMax() - m_composite_bbox_mid ).Length();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::AddBoundingBox( const Mth::CBBox & bbox )
+{
+	m_composite_bbox.AddPoint( bbox.GetMin());
+	m_composite_bbox.AddPoint( bbox.GetMax());
+
+	m_composite_bbox_mid		= m_composite_bbox.GetMin() + ( 0.5f * ( m_composite_bbox.GetMax() - m_composite_bbox.GetMin()));
+	m_composite_bsphere_radius	= ( m_composite_bbox.GetMax() - m_composite_bbox_mid ).Length();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::Process( void )
+{
+	return plat_process();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float CImposterGroup::CheckDistance( void )
+{
+	return plat_check_distance();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::CreateImposterPolygon( void )
+{
+	plat_create_imposter_polygon();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::RemoveImposterPolygon( void )
+{
+	plat_remove_imposter_polygon();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CImposterGroup::UpdateImposterPolygon( void )
+{
+	++m_update_count;
+	return plat_update_imposter_polygon();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::DrawImposterPolygon( void )
+{
+	plat_draw_imposter_polygon();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::plat_create_imposter_polygon( void )
+{
+	printf( "STUB: plat_create_imposter_polygon\n");
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::plat_remove_imposter_polygon( void )
+{
+	printf( "STUB: plat_remove_imposter_polygon\n");
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CImposterGroup::plat_update_imposter_polygon( void )
+{
+	printf( "STUB: plat_update_imposter_polygon\n");
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::plat_draw_imposter_polygon( void )
+{
+	printf( "STUB: plat_draw_imposter_polygon\n");
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float CImposterGroup::plat_check_distance( void )
+{
+	printf( "STUB: plat_check_distance\n");
+	return 0.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterGroup::plat_process( void )
+{
+	printf( "STUB: plat_process\n");
+}
+
+}
+
diff --git a/Code/Gfx/NxImposter.h b/Code/Gfx/NxImposter.h
new file mode 100644
index 0000000..55b043c
--- /dev/null
+++ b/Code/Gfx/NxImposter.h
@@ -0,0 +1,109 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NxImposter.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_IMPOSTER_H__
+#define	__GFX_NX_IMPOSTER_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+/////////////////////////////////////////////////////////////////////////
+// Forward declarations
+
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CImposterGroup
+{
+	public:
+								CImposterGroup( void );
+	virtual						~CImposterGroup();
+
+	void						AddGeom( CGeom *p_geom );
+	void						RemoveGeom( CGeom *p_geom );
+								
+	Mth::CBBox &				GetCompositeBoundingBox( void )					{ return m_composite_bbox; }
+	void						SetCompositeBoundingBox( const Mth::CBBox & bbox );
+	void						AddBoundingBox( const Mth::CBBox & bbox );
+	float						GetSwitchDistance( void )						{ return m_switch_distance; }
+	float						CheckDistance( void );
+	void						Process( void );			// Called once per frame for platform specific processing.
+
+	void						CreateImposterPolygon( void );
+	void						RemoveImposterPolygon( void );
+	bool						UpdateImposterPolygon( void );
+	void						DrawImposterPolygon( void );
+
+	bool						ImposterPolygonExists( void )					{ return m_imposter_polygon_exists; }
+
+	protected:
+
+	float						m_switch_distance;			// Distance at which the imposter switches in/out.
+	Mth::CBBox					m_composite_bbox;			// Composite bounding box from all geometey added to the group.
+	Mth::Vector					m_composite_bbox_mid;		// Mid point of composite bounding box (for speed).
+	float						m_composite_bsphere_radius;	// Composite bounding sphere radius (mid point is same as box).
+	Lst::Head  *		mp_geom_list;				// List of geometry for this imposter group.
+	Mth::Vector					m_cam_pos;					// The camera position at the time of creation.
+	int							m_tex_width, m_tex_height;	// Current imposter texture width and height.
+	int							m_update_count;				// Incremented each update, used to allow intermittent checks.
+	bool						m_imposter_polygon_exists;
+
+	private:
+
+	// The virtual functions have a stub implementation.
+	virtual void				plat_create_imposter_polygon( void );
+	virtual void				plat_remove_imposter_polygon( void );
+	virtual bool				plat_update_imposter_polygon( void );
+	virtual void				plat_draw_imposter_polygon( void );
+	virtual float				plat_check_distance( void );
+	virtual void				plat_process( void );
+};
+	
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CImposterManager
+{
+public:
+										CImposterManager( void );
+										~CImposterManager();
+
+	void								Cleanup( void );
+	void								AddGeomToImposter( uint32 group_checksum, Nx::CGeom *p_geom );
+	void								RemoveGeomFromImposter( uint32 group_checksum, Nx::CGeom *p_geom );
+	void								ProcessImposters( void );
+	void								DrawImposters( void );
+
+	CImposterGroup*						plat_create_imposter_group( void );
+	void								plat_pre_render_imposters( void );
+	void								plat_post_render_imposters( void );
+
+protected:
+
+private:
+
+	Lst::HashTable< CImposterGroup >	*mp_group_table;
+	int									m_max_imposters_to_redraw_per_frame;
+
+
+};
+
+
+///////////////////////////////////////////////////////////////////////
+//
+// Inline functions
+//
+}
+#endif
\ No newline at end of file
diff --git a/Code/Gfx/NxLight.cpp b/Code/Gfx/NxLight.cpp
new file mode 100644
index 0000000..39cf4cd
--- /dev/null
+++ b/Code/Gfx/NxLight.cpp
@@ -0,0 +1,381 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxLight.cpp
+
+
+#include 
+
+#include "gfx/NxLight.h"
+
+namespace	Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////
+// CModelLights
+
+/////////////////////////////////////////////////////////////////////////////
+// These functions are the platform independent part of the interface.
+					 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelLights::CModelLights()
+{
+	m_brightness = m_brightness_target = 0.0f;
+
+	m_ambient_brightness = 1.0f;
+
+	for (int i = 0; i < CLightManager::MAX_LIGHTS; i++)
+	{
+		m_diffuse_brightness[i] = 1.0f;
+	}
+
+	mp_pos = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelLights::~CModelLights()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::SetLightAmbientColor(const Image::RGBA &rgba)
+{
+	//m_light_ambient_rgba = rgba;
+
+	bool success = plat_set_light_ambient_color(rgba);
+
+	// need to call this whenever the color/brightness changes
+	plat_update_brightness();
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA			CModelLights::GetLightAmbientColor()
+{
+	return plat_get_light_ambient_color();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::SetLightDirection(int light_index, const Mth::Vector &direction)
+{
+	Dbg_Assert(light_index < CLightManager::MAX_LIGHTS);
+
+	//m_light_direction[light_index] = direction;
+
+	return plat_set_light_direction(light_index, direction);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CModelLights::GetLightDirection(int light_index)
+{
+	Dbg_Assert(light_index < CLightManager::MAX_LIGHTS);
+
+	return plat_get_light_direction(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::SetLightDiffuseColor(int light_index, const Image::RGBA &rgba)
+{
+	Dbg_Assert(light_index < CLightManager::MAX_LIGHTS);
+
+	//m_light_diffuse_rgba[light_index] = rgba;
+
+	bool success = plat_set_light_diffuse_color(light_index, rgba);
+
+	// need to call this whenever the color/brightness changes
+	plat_update_brightness();
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA			CModelLights::GetLightDiffuseColor(int light_index)
+{
+	Dbg_Assert(light_index < CLightManager::MAX_LIGHTS);
+
+	return plat_get_light_diffuse_color(light_index);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* The 'add_scene_light' parameter is used optionally to		  */
+/* determine whether a nearby Scene Light should be added to the  */
+/* lighting calc (based on the 'pos' position).					  */
+/*                                                                */
+/******************************************************************/
+void				CModelLights::UpdateEngine( Mth::Vector & pos, bool add_scene_light )
+{
+	plat_update_engine( pos, add_scene_light );
+}
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CModelLights::EnableAmbientLight(bool enable)
+{
+	plat_enable_ambient_light(enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CModelLights::EnableDiffuseLight(int light_index, bool enable)
+{
+	plat_enable_diffuse_light(light_index, enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::IsAmbientLightEnabled() const
+{
+	return plat_is_ambient_light_enabled();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::IsDiffuseLightEnabled(int light_index) const
+{
+	return plat_is_diffuse_light_enabled(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::SetBrightness(float brightness)
+{
+	m_brightness_target = brightness;
+
+	// We've found the target brightness, so move actual brightness towards this to avoid sudden unnatural transitions.
+	const float BRIGHTNESS_SPEED = 0.02f;
+	if( m_brightness < m_brightness_target )
+	{
+		m_brightness += BRIGHTNESS_SPEED;
+		if( m_brightness > m_brightness_target )
+		{
+			m_brightness = m_brightness_target;
+		}
+	}
+	else if( m_brightness > m_brightness_target )
+	{
+		m_brightness -= BRIGHTNESS_SPEED;
+		if( m_brightness < m_brightness_target )
+		{
+			m_brightness = m_brightness_target;
+		}
+	}
+
+	m_ambient_brightness = 1.0f + ((2.0f * m_brightness) - 1.0f) * CLightManager::s_world_lights.m_ambient_light_modulation_factor;
+	if (m_ambient_brightness < 0.0f)
+	{
+		m_ambient_brightness = 0.0f;
+	}
+
+	for (int i = 0; i < CLightManager::MAX_LIGHTS; i++)
+	{
+		m_diffuse_brightness[i] = 1.0f + ((2.0f * m_brightness) - 1.0f) * CLightManager::s_world_lights.m_diffuse_light_modulation_factor[i];
+		if (m_diffuse_brightness[i] < 0.0f)
+		{
+			m_diffuse_brightness[i] = 0.0f;
+		}
+	}
+
+	// need to call this whenever the color/brightness changes
+	plat_update_brightness();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CModelLights::UpdateBrightness()
+{
+	m_ambient_brightness = 1.0f + ((2.0f * m_brightness) - 1.0f) * CLightManager::s_world_lights.m_ambient_light_modulation_factor;
+	if (m_ambient_brightness < 0.0f)
+	{
+		m_ambient_brightness = 0.0f;
+	}
+
+	for (int i = 0; i < CLightManager::MAX_LIGHTS; i++)
+	{
+		m_diffuse_brightness[i] = 1.0f + ((2.0f * m_brightness) - 1.0f) * CLightManager::s_world_lights.m_diffuse_light_modulation_factor[i];
+		if (m_diffuse_brightness[i] < 0.0f)
+		{
+			m_diffuse_brightness[i] = 0.0f;
+		}
+	}
+
+	// GJ: the normal PS2 lighting code assumes that the collision 
+	// code will be run once a frame to copy over the m_ambient_base_color 
+	// into the m_ambient_mod_color.  unfortunately, the cutscene objects' 
+	// ref objects don't go through this code, so I had to add this function
+	// to explicitly copy over the data...  there's probably a cleaner way
+	// to do it, but I didn't want to break the existing code.
+	
+	// need to call this whenever the color/brightness changes
+    plat_update_brightness();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::SetPositionPointer(Mth::Vector *p_pos)
+{
+	mp_pos = p_pos;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector *		CModelLights::GetPositionPointer()
+{
+	return mp_pos;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// These functions are the stubs of the platform dependent part of the interface.
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CModelLights::plat_set_light_ambient_color(const Image::RGBA &rgba)
+{
+	printf ("STUB: CModelLights::SetLightAmbientColor\n");
+
+	return false;
+}
+
+bool				CModelLights::plat_set_light_direction(int light_index, const Mth::Vector &direction)
+{
+	printf ("STUB: CModelLights::SetLightDirection\n");
+
+	return false;
+}
+
+bool				CModelLights::plat_set_light_diffuse_color(int light_index, const Image::RGBA &rgba)
+{
+	printf ("STUB: CModelLights::SetLightDiffuseColor\n");
+
+	return false;
+}
+
+Image::RGBA			CModelLights::plat_get_light_ambient_color() const
+{
+	printf ("STUB: CModelLights::GetLightAmbientColor\n");
+
+	return Image::RGBA(0, 0, 0, 0);
+}
+
+const Mth::Vector &	CModelLights::plat_get_light_direction(int light_index) const
+{
+	printf ("STUB: CModelLights::GetLightDirection\n");
+
+	static Mth::Vector stub(0, 0, 0, 0);
+
+	return stub;
+}
+
+Image::RGBA			CModelLights::plat_get_light_diffuse_color(int light_index) const
+{
+	printf ("STUB: CModelLights::GetLightDiffuseColor\n");
+
+	return Image::RGBA(0, 0, 0, 0);
+}
+
+void				CModelLights::plat_update_brightness()
+{
+	printf ("STUB: CModelLights::UpdateBrightness\n");
+}
+
+void				CModelLights::plat_enable_ambient_light(bool enable)
+{
+	printf ("STUB: CModelLights::EnableAmbientLight\n");
+}
+
+void				CModelLights::plat_enable_diffuse_light(int light_index, bool enable)
+{
+	printf ("STUB: CModelLights::EnableDiffuseLight\n");
+}
+
+bool				CModelLights::plat_is_ambient_light_enabled() const
+{
+	printf ("STUB: CModelLights::IsAmbientLightEnabled\n");
+
+	return false;
+}
+
+bool				CModelLights::plat_is_diffuse_light_enabled(int light_index) const
+{
+	printf ("STUB: CModelLights::IsDiffuseLightEnabled\n");
+
+	return false;
+}
+
+void CModelLights::plat_update_engine( Mth::Vector & pos, bool add_scene_light )
+{
+	printf ("STUB: CModelLights::plat_update_engine\n");
+}
+
+}
+
+
diff --git a/Code/Gfx/NxLight.h b/Code/Gfx/NxLight.h
new file mode 100644
index 0000000..0fe8757
--- /dev/null
+++ b/Code/Gfx/NxLight.h
@@ -0,0 +1,101 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NxLight.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_LIGHT_H__
+#define	__GFX_NX_LIGHT_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+
+
+namespace Nx
+{
+
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CModelLights
+class	CModelLights
+{
+public:
+								CModelLights();
+	virtual						~CModelLights();
+
+	// lights
+	bool						SetLightAmbientColor(const Image::RGBA &rgba);
+	Image::RGBA					GetLightAmbientColor();
+
+	bool						SetLightDirection(int light_index, const Mth::Vector &direction);
+	const Mth::Vector &			GetLightDirection(int light_index);
+
+	bool						SetLightDiffuseColor(int light_index, const Image::RGBA &rgba);
+	Image::RGBA					GetLightDiffuseColor(int light_index);
+
+	// Upload values to engine (where applicable)
+	void						UpdateEngine( Mth::Vector & pos, bool add_scene_light = false );
+	
+	// Enable lights
+	void						EnableAmbientLight(bool enable);
+	void						EnableDiffuseLight(int light_index, bool enable);
+	bool						IsAmbientLightEnabled() const;
+	bool						IsDiffuseLightEnabled(int light_index) const;
+
+	// Sets the modulation brightness
+	bool						SetBrightness(float brightness);
+	float						GetBrightness() const;
+	void						UpdateBrightness();
+
+	// Position pointer
+	bool						SetPositionPointer(Mth::Vector *p_pos);
+	Mth::Vector *				GetPositionPointer();
+
+protected:	
+	// Internal flags.
+	enum {
+		mUSE_MODEL_AMBIENT		= 0x0001,					// Use ambient light
+		mUSE_MODEL_DIFFUSE_0	= 0x0002,					// Use diffuse light 0
+		mUSE_MODEL_DIFFUSE_1	= 0x0004,					// rest of diffuse must follow 0
+	};
+
+	//Image::RGBA					m_light_ambient_rgba;
+	//Mth::Vector					m_light_direction[CLightManager::MAX_LIGHTS];
+	//Image::RGBA					m_light_diffuse_rgba[CLightManager::MAX_LIGHTS];
+
+	// Modulation brightness
+	float						m_brightness;
+	float						m_brightness_target;
+	float						m_ambient_brightness;
+	float						m_diffuse_brightness[CLightManager::MAX_LIGHTS];
+
+	// Position pointer (for scene lights)
+	Mth::Vector *				mp_pos;
+
+private:
+	// Platform-specific calls
+	virtual bool				plat_set_light_ambient_color(const Image::RGBA &rgba);
+	virtual bool				plat_set_light_direction(int light_index, const Mth::Vector &direction);
+	virtual bool				plat_set_light_diffuse_color(int light_index, const Image::RGBA &rgba);
+	virtual Image::RGBA			plat_get_light_ambient_color() const;
+	virtual const Mth::Vector &	plat_get_light_direction(int light_index) const;
+	virtual Image::RGBA			plat_get_light_diffuse_color(int light_index) const;
+
+	virtual void				plat_update_engine( Mth::Vector & pos, bool add_scene_light );
+
+	virtual void				plat_update_brightness();
+
+	virtual void				plat_enable_ambient_light(bool enable);
+	virtual void				plat_enable_diffuse_light(int light_index, bool enable);
+	virtual bool				plat_is_ambient_light_enabled() const;
+	virtual bool				plat_is_diffuse_light_enabled(int light_index) const;
+};
+
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NxLightMan.cpp b/Code/Gfx/NxLightMan.cpp
new file mode 100644
index 0000000..ad04cf2
--- /dev/null
+++ b/Code/Gfx/NxLightMan.cpp
@@ -0,0 +1,1362 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxLightMan.cpp
+
+// start autoduck documentation
+// @DOC NxLightMan
+// @module NxLightMan | None
+// @subindex Scripting Database
+// @index script | NxLightMan
+
+#include 
+#include 
+#include 
+
+#include  // for AddDebugLine( )
+#include 
+#include 
+#include 
+#include 
+
+// This should eventually be removed, only needed for a call to GamePaused, which should not be
+// called from here anyway.
+#include 
+
+#include 
+#include 
+
+#include "gfx/NxLightMan.h"
+
+namespace	Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////
+// CLightManager
+
+// World lights
+CLightManager::SLightSet CLightManager::s_world_lights =
+{
+					Image::RGBA(72, 72, 72, 0x80),															// Ambient RGBA
+					{ Mth::Vector(0.5f, 0.8660254f, 0.0f, 0.0f), Mth::Vector(0.0f, 1.0f, 0.0f, 0.0f) },		// Light direction
+					{ Image::RGBA(75, 75, 75, 0x80), Image::RGBA(0, 0, 0, 0x80) },							// Diffuse RGBA
+					0.5f,																					// Ambient modulation
+					{ 0.7f, 1.0f }																			// Diffuse modulation
+};
+CLightManager::SLightSet CLightManager::s_world_lights_stack[WORLD_LIGHTS_STACK_SIZE];
+int						 CLightManager::s_world_lights_stack_index = -1;
+
+// The following are temp variables.  These should be done per model
+float				CLightManager::s_brightness = 1.0f;
+float				CLightManager::s_brightness_target = 1.0f;
+
+float				CLightManager::s_min_brightness = 0.0f;
+float				CLightManager::s_ambient_brightness = 1.0f;
+float				CLightManager::s_diffuse_brightness[MAX_LIGHTS] = { 1.0f, 1.0f };
+
+CSceneLight			CLightManager::s_scene_lights[MAX_SCENE_LIGHTS];
+int					CLightManager::s_num_scene_lights	= 0;
+
+SVCLight			CLightManager::sp_vc_lights[MAX_VC_LIGHTS];
+int					CLightManager::s_num_vc_lights = 0;
+uint16 *			CLightManager::sp_fake_lights_nodes=NULL;
+int					CLightManager::s_num_fake_lights_nodes=0;
+int					CLightManager::s_next_fake_light_node_to_process=0;
+int					CLightManager::s_fake_lights_period=0;
+int					CLightManager::s_fake_lights_current_time=0;
+Script::CStruct *	CLightManager::sp_fake_lights_params=NULL;
+
+
+/////////////////////////////////////////////////////////////////////////////
+// These functions are the platform independent part of the interface.
+					 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void				CLightManager::sUpdateEngine( void )
+{
+	s_plat_update_engine();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CLightManager::sClearLights()
+{
+	s_ambient_brightness = 1.0f;
+
+	s_world_lights.m_light_ambient_rgba = Image::RGBA(0, 0, 0, 0x80);
+	for (int i = 0; i < MAX_LIGHTS; i++)
+	{
+		s_world_lights.m_light_direction[i] = Mth::Vector(0.0f, 1.0f, 0.0f, 0.0f);
+		s_world_lights.m_light_diffuse_rgba[i] = Image::RGBA(0, 0, 0, 0x80);
+
+		s_diffuse_brightness[i] = 1.0f;
+	}
+
+	s_plat_update_lights();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CLightManager::sSetLightAmbientColor(const Image::RGBA rgba)
+{
+	s_world_lights.m_light_ambient_rgba = rgba;
+
+	return s_plat_set_light_ambient_color();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA			CLightManager::sGetLightAmbientColor()
+{
+	//return s_light_ambient_rgba;
+	return s_plat_get_light_ambient_color();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CLightManager::sSetLightDirection(int light_index, const Mth::Vector &direction)
+{
+	Dbg_Assert(light_index < MAX_LIGHTS);
+
+	s_world_lights.m_light_direction[light_index] = direction;
+
+	return s_plat_set_light_direction(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CLightManager::sGetLightDirection(int light_index)
+{
+	Dbg_Assert(light_index < MAX_LIGHTS);
+
+	//return s_light_direction[light_index];
+	return s_plat_get_light_direction(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CLightManager::sSetLightDiffuseColor(int light_index, const Image::RGBA rgba)
+{
+	s_world_lights.m_light_diffuse_rgba[light_index] = rgba;
+
+	return s_plat_set_light_diffuse_color(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA			CLightManager::sGetLightDiffuseColor(int light_index)
+{
+	Dbg_Assert(light_index < MAX_LIGHTS);
+
+	//return s_light_diffuse_rgba[light_index];
+	return s_plat_get_light_diffuse_color(light_index);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CLightManager::sSetAmbientLightModulationFactor(float factor)
+{
+	s_world_lights.m_ambient_light_modulation_factor = factor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CLightManager::sSetDiffuseLightModulationFactor(int light_index, float factor)
+{
+	Dbg_Assert(light_index < MAX_LIGHTS);
+
+	s_world_lights.m_diffuse_light_modulation_factor[light_index] = factor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float				CLightManager::sGetAmbientLightModulationFactor()
+{
+	return s_world_lights.m_ambient_light_modulation_factor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float				CLightManager::sGetDiffuseLightModulationFactor(int light_index)
+{
+	Dbg_Assert(light_index < MAX_LIGHTS);
+
+	return s_world_lights.m_diffuse_light_modulation_factor[light_index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CLightManager::sPushWorldLights()
+{
+	if (++s_world_lights_stack_index < WORLD_LIGHTS_STACK_SIZE)
+	{
+		// Put on top of stack
+		s_world_lights_stack[s_world_lights_stack_index] = s_world_lights;
+	}
+	else
+	{
+		--s_world_lights_stack_index;
+		Dbg_MsgAssert(0, ("World light stack full"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CLightManager::sPopWorldLights()
+{
+	if (s_world_lights_stack_index >= 0)
+	{
+		// Get from top of stack and update the engine
+		s_world_lights = s_world_lights_stack[s_world_lights_stack_index--];
+		s_plat_update_lights();
+
+		return true;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("World light stack empty"));
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelLights *		CLightManager::sCreateModelLights()
+{
+	return s_plat_create_model_lights();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CLightManager::sFreeModelLights(CModelLights *p_model_lights)
+{
+	return s_plat_free_model_lights(p_model_lights);
+}
+
+/******************************************************************/
+/*                                                                */
+/*   This will eventually be done through CModel                  */
+/*                                                                */
+/******************************************************************/
+
+bool				CLightManager::sSetBrightness(float brightness)
+{
+#if 0
+	// Don't do anything now
+	return true;
+#endif
+
+	s_brightness_target = brightness;
+
+	// We've found the target brightness, so move actual brightness towards this to avoid sudden unnatural transitions.
+	const float BRIGHTNESS_SPEED = 0.02f;
+	if( s_brightness < s_brightness_target )
+	{
+		s_brightness += BRIGHTNESS_SPEED;
+		if( s_brightness > s_brightness_target )
+		{
+			s_brightness = s_brightness_target;
+		}
+	}
+	else if( s_brightness > s_brightness_target )
+	{
+		s_brightness -= BRIGHTNESS_SPEED;
+		if( s_brightness < s_brightness_target )
+		{
+			s_brightness = s_brightness_target;
+		}
+	}
+
+	s_ambient_brightness = 1.0f + ((2.0f * s_brightness) - 1.0f) * s_world_lights.m_ambient_light_modulation_factor;
+	if (s_ambient_brightness < s_min_brightness)
+	{
+		s_ambient_brightness = s_min_brightness;
+	}
+
+	for (int i = 0; i < MAX_LIGHTS; i++)
+	{
+		s_diffuse_brightness[i] = 1.0f + ((2.0f * s_brightness) - 1.0f) * s_world_lights.m_diffuse_light_modulation_factor[i];
+		if (s_diffuse_brightness[i] < s_min_brightness)
+		{
+			s_diffuse_brightness[i] = s_min_brightness;
+		}
+	}
+
+	s_plat_update_colors();
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSceneLight * CLightManager::sAddSceneLight( void )
+{
+	if( s_num_scene_lights < MAX_SCENE_LIGHTS )
+	{
+		++s_num_scene_lights;
+		return &s_scene_lights[s_num_scene_lights - 1];
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::sClearSceneLights( void )
+{
+	s_num_scene_lights = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::sClearVCLights()
+{
+
+// Need to flush the entries from each sector list before we "ignore" the light
+// otherwise the hash items from one level hang around for another level, confusing us.
+	
+	for( int i = 0; i < s_num_vc_lights; ++i )
+	{
+		sp_vc_lights[i].ResetSectorList();
+	}
+	
+	s_num_vc_lights=0;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CLightManager::sAddVCLight(uint32 name, Mth::Vector &pos, float intensity,
+								Image::RGBA col, float outer_radius)
+{
+	Dbg_MsgAssert(s_num_vc_lights < MAX_VC_LIGHTS, ("Too many level lights that affect geometry (%d max)",MAX_VC_LIGHTS));
+	if (s_num_vc_lights < MAX_VC_LIGHTS)
+	{
+		SVCLight *p_new_light = &sp_vc_lights[s_num_vc_lights++];
+	
+		p_new_light->SetNameChecksum( name );
+		p_new_light->SetPosition( pos );
+	
+		p_new_light->SetIntensity( intensity * 0.01f );
+		p_new_light->SetColor( col );
+	
+		// Setting the radius will implicitly calculate the sector list for this light.
+		p_new_light->SetRadius( outer_radius );
+	
+		// Calculate which sectors will be affected.
+		p_new_light->CalculateSectorList();
+	
+		if( intensity > 0.0f )
+		{
+			s_apply_lighting( Nx::CEngine::sGetMainScene(), name );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::sClearCurrentFakeLightsCommand()
+{
+	if (sp_fake_lights_nodes)
+	{
+		Mem::Free(sp_fake_lights_nodes);
+		sp_fake_lights_nodes=NULL;
+	}	
+	s_num_fake_lights_nodes=0;
+	s_next_fake_light_node_to_process=0;
+	
+	s_fake_lights_period=0;
+	s_fake_lights_current_time=0;
+	
+	if (sp_fake_lights_params)
+	{
+		delete sp_fake_lights_params;
+		sp_fake_lights_params=NULL;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Gets called from ScriptFakeLights in cfuncs.cpp
+void CLightManager::sFakeLights(const uint16 *p_nodes, int numNodes, int period, Script::CStruct *pParams)
+{
+	sClearCurrentFakeLightsCommand();
+
+	sp_fake_lights_params=new Script::CStruct;
+	sp_fake_lights_params->AppendStructure(pParams);
+	
+	if (numNodes)
+	{
+		sp_fake_lights_nodes=(uint16*)Mem::Malloc(numNodes*sizeof(uint16));
+		for (int i=0; iGamePaused())
+	{
+		return;
+	}
+
+	// If finished, clean up & stop.	
+	if (s_fake_lights_current_time > s_fake_lights_period)
+	{
+		sClearCurrentFakeLightsCommand();
+		return;
+	}	
+	
+	// Calculate how many nodes ought to have been processed by now, based on the ratio of s_fake_lights_current_time
+	// to s_fake_lights_period
+	int required_num_nodes_processed;
+	if (s_fake_lights_period)
+	{
+		required_num_nodes_processed = (s_num_fake_lights_nodes * s_fake_lights_current_time) / s_fake_lights_period;
+	}	
+	else
+	{
+		required_num_nodes_processed = s_num_fake_lights_nodes;
+	}
+	
+	// Call sFakeLight for each node starting from where it got to last time, until the required number have
+	// been processed.
+	for (; s_next_fake_light_node_to_processGetChecksum(CRCD(0xa1dc81f9,"Name"),&id);
+		sFakeLight(id,sp_fake_lights_params);
+	}
+	
+	++s_fake_lights_current_time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSceneLight *CLightManager::sGetSceneLight( uint32 checksum )
+{
+	for( int l = 0; l < s_num_scene_lights; ++l )
+	{
+		if( s_scene_lights[l].GetNameChecksum() == checksum )
+		{
+			return &s_scene_lights[l];
+		}
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSceneLight * CLightManager::sGetOptimumSceneLight( Mth::Vector & pos )
+{
+	if( s_num_scene_lights > 0 )
+	{
+		float	best_ratio		= 1.0f;
+		int		best_index		= -1;
+
+		for( int l = 0; l < s_num_scene_lights; ++l )
+		{
+			// Only interested in lights that are active.
+			if( s_scene_lights[l].GetLightIntensity() > 0.0f )
+			{
+				float dist = Mth::Distance( pos, s_scene_lights[l].GetLightPosition());
+				if( dist < s_scene_lights[l].GetLightRadius())
+				{
+					// Potentially a usable light.
+					float ratio = dist * s_scene_lights[l].GetLightReciprocalRadius();
+					if( ratio < best_ratio )
+					{
+						// I wonder whether we should also consider intensity here? A further light, with a higher
+						// intensity may have more effect than a nearer light with smaller intensity.
+						best_ratio = ratio;
+						best_index = l;
+					}
+				}
+			}
+		}
+
+		if( best_index >= 0 )
+		{
+			return &s_scene_lights[best_index];
+		}
+	}
+	return NULL;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////
+// The script functions
+
+// @script | SetLightAmbientColor | Sets the global light ambient color
+// @parm int | r | Red
+// @parm int | g | Green
+// @parm int | b | Blue
+bool ScriptSetLightAmbientColor(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int r, g, b;
+	if (!pParams->GetInteger("r", &r))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'r' color"));
+	}
+	if (!pParams->GetInteger("g", &g))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'g' color"));
+	}
+	if (!pParams->GetInteger("b", &b))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'b' color"));
+	}
+	
+	Image::RGBA rgb(r, g, b, 0x80);
+
+	return CLightManager::sSetLightAmbientColor(rgb);
+}
+
+
+// @script | SetLightDirection | Sets the unit direction vector of a global light
+// @parm int | index | Light number
+// @parm vector | direction | Unit direction vector (overrides heading and pitch)
+// @parm float | heading | Heading angle (in degrees)
+// @parm float | pitch | Pitch angle (in degrees)
+bool ScriptSetLightDirection(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int index;
+	if (!pParams->GetInteger(CRCD(0x7f8c98fe,"index"), &index))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'index' of light"));
+	}
+
+	float heading, pitch;
+	Mth::Vector direction(0, 0, 1, 0);
+	if (pParams->GetVector(CRCD(0xc1b52e4c,"direction"), &direction))
+	{
+		direction[W] = 0.0f;		// This is the only way to force this to be a vector (as opposed to a point)
+		//Dbg_Message("************ direction (%f, %f, %f)", direction[X], direction[Y], direction[Z]);
+	} else if (pParams->GetFloat(CRCD(0xfd4bc03e,"heading"), &heading) && pParams->GetFloat(CRCD(0xd8604126,"pitch"), &pitch)) {
+		direction.RotateX(Mth::DegToRad(pitch));
+		direction.RotateY(Mth::DegToRad(heading));
+		//Dbg_Message("************ heading and pitch direction (%f, %f, %f)", direction[X], direction[Y], direction[Z]);
+	} else {
+		Dbg_MsgAssert(0, ("Can't find 'direction' or 'heading' and 'pitch' of light"));
+	}
+
+	return CLightManager::sSetLightDirection(index, direction);
+}
+
+
+// @script | SetLightDiffuseColor | Sets the diffuse color of a global light
+// @parm int | index | Light number
+// @parm int | r | Red
+// @parm int | g | Green
+// @parm int | b | Blue
+bool ScriptSetLightDiffuseColor(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int index;
+	if (!pParams->GetInteger(CRCD(0x7f8c98fe,"index"), &index))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'index' of light"));
+	}
+
+	int r, g, b;
+	if (!pParams->GetInteger(CRCD(0x93f60062,"r"), &r))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'r' color"));
+	}
+	if (!pParams->GetInteger(CRCD(0xfe2be489,"g"), &g))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'g' color"));
+	}
+	if (!pParams->GetInteger(CRCD(0x8e411006,"b"), &b))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'b' color"));
+	}
+	
+	Image::RGBA rgb(r, g, b, 0x80);
+
+	return CLightManager::sSetLightDiffuseColor(index, rgb);
+}
+
+
+// @script | GetLightCurrentColor | Gets the rgb values for ambient and directional lights
+bool ScriptGetLightCurrentColor(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int r=0, g=0, b=0;
+    Image::RGBA rgb(r, g, b, 0x80);
+    
+    rgb = CLightManager::sGetLightAmbientColor();
+
+    pScript->GetParams()->AddInteger(CRCD(0xed664897,"ambient_red"), rgb.r);
+    pScript->GetParams()->AddInteger(CRCD(0x908bf647,"ambient_green"), rgb.g);
+    pScript->GetParams()->AddInteger(CRCD(0x5f4fba78,"ambient_blue"), rgb.b);
+
+    rgb = CLightManager::sGetLightDiffuseColor(0);
+
+    pScript->GetParams()->AddInteger(CRCD(0xe213aeb7,"red_0"), rgb.r);
+    pScript->GetParams()->AddInteger(CRCD(0x6ef19631,"green_0"), rgb.g);
+    pScript->GetParams()->AddInteger(CRCD(0x5ec44213,"blue_0"), rgb.b);
+
+    rgb = CLightManager::sGetLightDiffuseColor(1);
+
+    pScript->GetParams()->AddInteger(CRCD(0x95149e21,"red_1"), rgb.r);
+    pScript->GetParams()->AddInteger(CRCD(0x19f6a6a7,"green_1"), rgb.g);
+    pScript->GetParams()->AddInteger(CRCD(0x29c37285,"blue_1"), rgb.b);
+    
+    /*float heading=0, pitch=0;
+    Mth::Vector direction(0, 0, 1, 0);
+
+    direction = CLightManager::sGetLightDirection(0);
+
+    //printf("x=%i y=%i z=%i", direction[0], direction[1], direction[2]);
+
+    //Do the opposite of these two lines
+    //direction.RotateX(Mth::DegToRad(pitch));
+    //direction.RotateY(Mth::DegToRad(heading));
+        
+    pScript->GetParams()->AddInteger("pitch_0", pitch);
+    pScript->GetParams()->AddInteger("heading_0", heading);
+    
+    */
+
+    return true;
+}
+
+// @script | PushWorldLights | Pushes current world lights onto stack, so they can be restored later.
+bool ScriptPushWorldLights(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CLightManager::sPushWorldLights();
+	return true;
+}
+
+// @script | PopWorldLights | Restores last pushed world lights to the current world lights.
+bool ScriptPopWorldLights(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	return CLightManager::sPopWorldLights();
+}
+
+// @script | DrawDirectionalLightLines | Draws two lines to represent the directional lights
+bool ScriptDrawDirectionalLightLines(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	float heading;
+	pParams->GetFloat( CRCD(0xfd4bc03e,"heading"), &heading, Script::ASSERT );
+
+    float pitch;
+	pParams->GetFloat( CRCD(0xd8604126,"pitch"), &pitch, Script::ASSERT );
+
+    pitch = -pitch * Mth::PI / 180.0f;
+    heading = ((-heading+90) * Mth::PI / 180.0f);
+    
+    int r, g, b;
+    pParams->GetInteger( CRCD(0x93f60062,"r"), &r, Script::ASSERT );
+    pParams->GetInteger( CRCD(0xfe2be489,"g"), &g, Script::ASSERT );
+    pParams->GetInteger( CRCD(0x8e411006,"b"), &b, Script::ASSERT );
+    Image::RGBA rgb(r, g, b, 0x80);
+    //rgb = CLightManager::sGetLightDiffuseColor(0);
+
+    Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+	
+	Mth::Vector pos = pSkater->GetPos();
+	float x = pos.GetX();
+	float y = ( pos.GetY() + 75 );
+	float z = pos.GetZ();
+
+    float hypyh = 50;
+    
+    float y0 = ( (sin(pitch) * hypyh) + y );
+    float hypxz = (cos(pitch) * hypyh);
+    float x0 = ( (cos(heading) * hypxz) + x );
+    float z0 = ( (sin(heading) * hypxz) + z );
+    
+    Mth::Vector skaterpos(x, y, z, 0);
+    Mth::Vector direction0(x0, y0, z0, 0);
+    
+    //printf("x=%i y=%i z=%i\n", direction0[0], direction0[1], direction0[2]);
+    
+    Gfx::AddDebugLine( skaterpos, direction0, MAKE_RGB( rgb.r, rgb.g, rgb.b ), MAKE_RGB( rgb.r, rgb.g, rgb.b ), 1 );
+    
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SVCLight *CLightManager::s_get_vclight_from_checksum( uint32 cs )
+{
+	for( int i = 0; i < s_num_vc_lights; ++i )
+	{
+		if( sp_vc_lights[i].m_checksum == cs )
+		{
+			return &sp_vc_lights[i];
+		}
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SVCLight::SVCLight( void )
+{
+	mp_sector_list = new Lst::HashTable< Nx::CSector >( 4 );
+
+	m_radius	= 0.0f;
+	m_intensity = 0.0f;
+
+	// Default the color to pure white.
+	m_color.r	= 0x80;
+	m_color.g	= 0x80;
+	m_color.b	= 0x80;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SVCLight::~SVCLight( void )
+{
+	ResetSectorList();
+	delete mp_sector_list;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SVCLight::SetRadius( float radius )
+{
+	Dbg_Assert( radius >= 0.0f );
+
+	if( radius != m_radius )
+	{
+		m_radius = radius;
+
+		// Deal with the SceneLight that may have been created from this node.
+		Nx::CSceneLight *p_light = Nx::CLightManager::sGetSceneLight( m_checksum );
+		if( p_light )
+		{
+			p_light->SetLightRadius( radius );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SVCLight::SetIntensity( float i )
+{
+	Dbg_Assert(( i >= 0.0f ) && ( i <= 1.0f ));
+
+	if( i != m_intensity )
+	{
+		m_intensity = i;
+
+		// Deal with the SceneLight that may have been created from this node.
+		Nx::CSceneLight *p_light = Nx::CLightManager::sGetSceneLight( m_checksum );
+		if( p_light )
+		{
+			p_light->SetLightIntensity( i );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SVCLight::SetColor( Image::RGBA col )
+{
+	m_color = col;
+
+	// Deal with the SceneLight that may have been created from this node.
+	Nx::CSceneLight *p_light = Nx::CLightManager::sGetSceneLight( m_checksum );
+	if( p_light )
+	{
+		p_light->SetLightColor( col );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Lst::HashTable< Nx::CSector > *SVCLight::GetSectorList( void )
+{
+	return mp_sector_list;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SVCLight::ResetSectorList( void )
+{
+	mp_sector_list->FlushAllItems();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool SVCLight::AffectsSector( Nx::CSector *p_sector )
+{
+	Nx::CSector *p_match = mp_sector_list->GetItem((uint32)p_sector );
+    return ( p_match != NULL );	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SVCLight::CalculateSectorList( void )
+{
+	// First reset the sector list.
+	ResetSectorList();
+	
+	Nx::CScene *p_scene = Nx::CEngine::sGetMainScene();
+	if( p_scene )
+	{
+		Lst::HashTable< Nx::CSector > *p_sector_list = p_scene->GetSectorList();
+		if( p_sector_list )
+		{
+			p_sector_list->IterateStart();	
+			Nx::CSector *p_sector = p_sector_list->IterateNext();
+			while( p_sector )
+			{
+				// Only consider this sector if it is not in the "NoLevelLights" lightgroup.
+				// and not in the Indoor group
+				if( p_sector->GetLightGroup() != CRCD( 0xed82767b, "NoLevelLights" )
+					&& p_sector->GetLightGroup() != CRCD(0xec6542d3,"indoor"))
+				{
+					Nx::CGeom *p_geom = p_sector->GetGeom();
+					if( p_geom )
+					{
+						// See of the radius of the light will intersect the radius of the bounding box of the geometry.
+						Mth::CBBox	bbox	= p_sector->GetBoundingBox();
+						Mth::Vector	mid		= ( bbox.GetMax() + bbox.GetMin()) / 2.0f;
+						float		r		= ( bbox.GetMax() - mid ).Length();
+						float		dist	= ( mid - m_pos ).Length();
+						if( dist < ( r + m_radius ))
+						{
+							mp_sector_list->PutItem((uint32)p_sector, p_sector );
+						}
+					}
+				}
+				p_sector = p_sector_list->IterateNext();
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_recalculate_sector_lighting( Nx::CSector *p_sector )
+{
+	Nx::CGeom *p_geom = p_sector->GetGeom();
+	if( p_geom == NULL )
+	{
+		return;
+	}
+
+	const int MAX_LIGHTS_AFFECTING_SECTOR = 8;
+
+	int			current_lights_affecting_sector = 0;
+	int			light_index[MAX_LIGHTS_AFFECTING_SECTOR];
+
+	// Build a list of lights that affect this sector, to the maximum of MAX_LIGHTS_AFFECTING_SECTOR.
+	for( int l = 0; l < s_num_vc_lights; ++l )
+	{
+		if(( sp_vc_lights[l].m_intensity > 0.0f ) && sp_vc_lights[l].AffectsSector( p_sector ))
+		{
+			light_index[current_lights_affecting_sector] = l;
+			if( ++current_lights_affecting_sector >= MAX_LIGHTS_AFFECTING_SECTOR )
+			{
+				break;
+			}
+		}
+	}
+
+	if( current_lights_affecting_sector == 0 )
+	{
+		// No lights are affecting this sector, which can happen in the situation where the last remaining
+		// light affecting a sector is turned off. In this case we just want to restore the original
+		// vertex colors.
+		int verts = p_geom->GetNumRenderVerts();
+		if( verts > 0 )
+		{
+			Image::RGBA	*p_colors	= new Image::RGBA[verts];
+			p_geom->GetOrigRenderColors( p_colors );
+			p_geom->SetRenderColors( p_colors );
+			delete [] p_colors;
+		}
+	}
+	else if( current_lights_affecting_sector > 0 )
+	{
+		int verts = p_geom->GetNumRenderVerts();
+		if( verts > 0 )
+		{
+			Mth::Vector	*p_verts	= new Mth::Vector[verts];
+			Image::RGBA	*p_colors	= new Image::RGBA[verts];
+
+			p_geom->GetRenderVerts( p_verts );
+			p_geom->GetOrigRenderColors( p_colors );
+					
+			Image::RGBA *p_color = p_colors;
+			Mth::Vector *p_vert = p_verts;
+			for( int i = 0; i < verts; ++i )
+			{
+				float ir = 0.0f;
+				float ig = 0.0f;
+				float ib = 0.0f;
+			
+				for( int l = 0; l < current_lights_affecting_sector; ++l )
+				{
+					SVCLight *p_current_light = &sp_vc_lights[light_index[l]];
+
+					float dist_sqr	= ( *p_vert - p_current_light->m_pos ).LengthSqr();
+					if( dist_sqr < ( p_current_light->m_radius * p_current_light->m_radius ))
+					{
+						float dist		= sqrtf( dist_sqr );
+						float intensity	= p_current_light->m_intensity * (( p_current_light->m_radius - dist ) / p_current_light->m_radius );
+
+						ir += intensity * (float)p_current_light->m_color.r;
+						ig += intensity * (float)p_current_light->m_color.g;
+						ib += intensity * (float)p_current_light->m_color.b;
+					}
+				}
+
+				// Apply to each r,g,b idividually, so we don't loose original color.
+				Image::RGBA rgb = *p_color;
+				float v = (float)rgb.r + ir;
+				rgb.r = (uint8)(( v > 255.0f ) ? 255.0f : v );
+							
+				v = (float)rgb.g + ig;
+				rgb.g = (uint8)(( v > 255.0f ) ? 255.0f : v );
+
+				v = (float)rgb.b + ib;
+				rgb.b = (uint8)(( v > 255.0f ) ? 255.0f : v );
+
+				*p_color = rgb;
+
+				++p_color;
+				++p_vert;
+			}
+
+			p_geom->SetRenderColors( p_colors );
+						
+			delete [] p_verts;
+			delete [] p_colors;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_apply_lighting( Nx::CScene * p_scene, uint32 checksum )
+{
+	// Now requires a checksum.
+	if( checksum == 0 )
+		return;
+
+	// Now deal with geometry.
+	SVCLight *p_scene_light = s_get_vclight_from_checksum( checksum );
+	if( p_scene_light == NULL )
+	{
+		return;
+	}
+
+	Lst::HashTable< Nx::CSector > * p_sector_list = p_scene_light->GetSectorList();
+	if( p_sector_list )
+	{
+		p_sector_list->IterateStart();	
+		Nx::CSector *p_sector = p_sector_list->IterateNext();		
+		while( p_sector )
+		{
+			Nx::CGeom *p_geom = p_sector->GetGeom();
+			if( p_geom )
+			{
+				s_recalculate_sector_lighting( p_sector );
+			}
+			p_sector = p_sector_list->IterateNext();
+		}
+	}
+
+#	if 0
+	// Set up an array of lights, (eventually) culled from the node array.
+	s_num_vc_lights = 0;
+
+	Script::CArray *p_nodearray=Script::GetArray("NodeArray");
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *p_node_struct=p_nodearray->GetStructure(i);
+		Dbg_MsgAssert(p_node_struct,("Error getting node from node array for rail generation"));
+
+		uint32 class_checksum = 0;		
+		p_node_struct->GetChecksum( CRCD(0x12b4e660, "Class"), &class_checksum );
+		if (class_checksum == CRCD(0xa0e52802,"LevelLight"))
+		{
+			SkateScript::GetPosition(p_node_struct,&sp_vc_lights[s_num_vc_lights].m_pos);
+			
+			sp_vc_lights[s_num_vc_lights].m_value = 100.0f;  		// default values
+			sp_vc_lights[s_num_vc_lights].m_radius = 300.0f;
+			
+			p_node_struct->GetFloat(CRCD(0x2689291c,"Brightness"),&sp_vc_lights[s_num_vc_lights].m_value);	
+			p_node_struct->GetFloat(CRCD(0xc48391a5,"Radius"),&sp_vc_lights[s_num_vc_lights].m_radius);	
+			
+			s_num_vc_lights++;
+		}
+	}
+	Dbg_Message( "Found %d LevelLights\n", s_num_vc_lights );
+
+	if( s_num_vc_lights == 0 )
+	{
+		Dbg_Message( "Did not find any LevelLight nodes, so using Pedestrian nodes....\n" );
+
+		// Scan through the node array creating all things that need to be created.
+		Script::CArray *p_nodearray=Script::GetArray("NodeArray");
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			Script::CStruct *p_node_struct=p_nodearray->GetStructure(i);
+			Dbg_MsgAssert(p_node_struct,("Error getting node from node array for rail generation"));
+	
+			uint32 class_checksum = 0;		
+			p_node_struct->GetChecksum( CRCD(0x12b4e660, "Class"), &class_checksum );
+			if (class_checksum == CRCD(0xa0dfac98,"Pedestrian"))
+			{
+				SkateScript::GetPosition(p_node_struct,&sp_vc_lights[s_num_vc_lights].m_pos);
+				sp_vc_lights[s_num_vc_lights].m_value = 100.0f;
+				sp_vc_lights[s_num_vc_lights].m_radius = 300.0f;
+				s_num_vc_lights++;
+			}
+		}
+	}
+
+	// Go through each object, find the nearest light that intersects the object
+	// and the  apply lighting from that
+	Lst::HashTable< Nx::CSector > * p_sector_list = p_scene->GetSectorList();
+	
+	Image::RGBA rgb;
+	
+	if (p_sector_list)
+	{
+		p_sector_list->IterateStart();	
+		Nx::CSector *p_sector = p_sector_list->IterateNext();		
+		while(p_sector)
+		{
+
+			Nx::CGeom 	*p_geom = p_sector->GetGeom();
+			
+			if (p_geom)
+			{
+				// Need to find a light (if any) such that the radius of the light
+				// will intersect the radius of the bounding box of the geometry
+				Mth::CBBox bbox = p_sector->GetBoundingBox();
+				Mth::Vector	mid = (bbox.GetMax() + bbox.GetMin())/2.0f;
+				float	radius = (bbox.GetMax() - bbox.GetMin()).Length();
+
+				int best_light = -1;
+				float best_dist = 100000000000.0f;				
+				for (int light = 0; light < s_num_vc_lights; light++)
+				{
+					float dist = (mid - sp_vc_lights[light].m_pos).Length();					
+					if (dist < best_dist && dist < (radius+sp_vc_lights[light].m_radius))
+					{
+						best_dist = dist;
+						best_light = light;
+					}
+				}
+				
+				if (best_light != -1)
+				{
+					
+					Mth::Vector	light_pos = sp_vc_lights[best_light].m_pos;
+					float	light_radius = sp_vc_lights[best_light].m_radius;
+					float	light_radius_squared = light_radius * light_radius;
+					float	light_value = sp_vc_lights[best_light].m_value * amount;
+					
+					
+					//
+					// Do renderable geometry
+					int verts = p_geom->GetNumRenderVerts();
+				
+					if (verts)
+					{
+						Mth::Vector	*p_verts = new Mth::Vector[verts];
+						Image::RGBA	*p_colors = new Image::RGBA[verts];
+						p_geom->GetRenderVerts(p_verts);
+						// Note: getting the original render colors will allocate lots of memory
+						// to store all the origianl colors the firs tiem it is called
+						//p_geom->GetOrigRenderColors(p_colors);
+						
+						// For lighting, we get the current colors, so needs to be applied in conjunction with a compressVC
+						p_geom->GetRenderColors(p_colors);
+					
+						Image::RGBA *p_color = p_colors;
+						Mth::Vector *p_vert = p_verts;
+						for (int i = 0; i < verts; i++)
+						{
+				//			CalculateVertexLighting(*p_vert, *p_color);
+
+							float dist_sqr = (*p_vert - light_pos).LengthSqr();
+							float intensity = (light_radius_squared-dist_sqr)/light_radius_squared * light_value;
+							if (intensity<0.0f)
+							{
+								intensity = 0.0f;
+							}
+							
+							rgb = *p_color;
+							float v;
+
+							// Apply to each r,g,b idividually, so we don't loose original color							
+							v = (float) rgb.r + intensity;
+							if (v > 255.0f) v = 255.0f;
+							rgb.r = (uint8) v; 
+							
+							v = (float) rgb.g + intensity;
+							if (v > 255.0f) v = 255.0f;
+							rgb.g = (uint8) v; 
+							
+							v = (float) rgb.b + intensity;
+							if (v > 255.0f) v = 255.0f;
+							rgb.b = (uint8) v; 
+							
+							
+							 
+							*p_color = rgb;		//(*p_color & 0xff000000) | color;
+							//*(uint32*)p_color = Mth::Rnd(32767);		//(*p_color & 0xff000000) | color;
+				
+							p_color++;
+							p_vert++;
+						} // end for
+				
+						p_geom->SetRenderColors(p_colors);
+						
+						delete [] p_verts;
+						delete [] p_colors;
+					} // end if
+					else
+					{
+						// debuggery
+						//p_geom->SetColor(Image::RGBA(0,0,100,0));
+					}
+				}
+			}
+			p_sector = p_sector_list->IterateNext();
+		}
+	}
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// K: Factored this out so that the FakeLights command could also adjust a set of prefixed nodes.
+void CLightManager::sFakeLight(uint32 id, Script::CStruct *pParams)
+{
+	Nx::CScene *p_scene = Nx::CEngine::sGetMainScene();
+	
+	float	amount, radius;
+	int		red, grn, blu;
+
+	// Get the SVCLight created from this node.
+	SVCLight *p_light = s_get_vclight_from_checksum( id );
+	if( p_light )
+	{
+		// Determine the intensity required.
+		if( pParams->GetFloat( CRCD(0x9e497fc6,"percent"), &amount ))
+		{
+			// Want to adjust the intensity. Convert from percentage to [0.0, 1.0].
+			amount = amount * 0.01f;
+
+			// Adjust the light intensity for this amount.
+			p_light->SetIntensity( amount );
+		}
+
+		if( pParams->GetFloat( CRCD(0x999151b,"outer_radius"), &radius ))
+		{
+			p_light->SetRadius( radius );
+
+			// Changing the radius requires that the sector list be rebuilt.
+			p_light->CalculateSectorList();
+		}
+
+		if( pParams->GetInteger( CRCD(0x59ea070,"red"), &red ))
+		{
+			// We must have a green and blue value too.
+			pParams->GetInteger( CRCD(0x2f6511de,"green"), &grn, true );
+			pParams->GetInteger( CRCD(0x61c9354b,"blue"), &blu, true );
+
+			Image::RGBA col( red, grn, blu, 0xFF );
+
+			// Adjust the light color.
+			p_light->SetColor( col );
+		}
+
+		s_apply_lighting( p_scene, id );
+	}
+	else
+	{
+		// Perhaps there is just a light for objects?
+		Nx::CSceneLight *p_obj_light = Nx::CLightManager::sGetSceneLight( id );
+		if( p_obj_light )
+		{
+			// Determine the intensity required.
+			if( pParams->GetFloat( CRCD(0x9e497fc6,"percent"), &amount ))
+			{
+				// Want to adjust the intensity. Convert from percentage to [0.0, 1.0].
+				amount = amount * 0.01f;
+
+				// Adjust the light intensity for this amount.
+				p_obj_light->SetLightIntensity( amount );
+			}
+
+			if( pParams->GetFloat( CRCD(0x999151b,"outer_radius"), &radius ))
+			{
+				p_obj_light->SetLightRadius( radius );
+			}
+
+			if( pParams->GetInteger( CRCD(0x59ea070,"red"), &red ))
+			{
+				// We must have a green and blue value too.
+				pParams->GetInteger( CRCD(0x2f6511de,"green"), &grn, true );
+				pParams->GetInteger( CRCD(0x61c9354b,"blue"), &blu, true );
+
+				Image::RGBA col( red, grn, blu, 0xFF );
+
+				// Adjust the light color.
+				p_obj_light->SetLightColor( col );
+			}
+		}
+	}
+}
+
+}
+
+
diff --git a/Code/Gfx/NxLightMan.h b/Code/Gfx/NxLightMan.h
new file mode 100644
index 0000000..3bf64db
--- /dev/null
+++ b/Code/Gfx/NxLightMan.h
@@ -0,0 +1,233 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NxLightMan.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_LIGHT_MAN_H__
+#define	__GFX_NX_LIGHT_MAN_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+
+#ifndef __CORE_LIST_HASHTABLE_H
+#include 
+#endif
+
+#ifndef	__GFX_NXSECTOR_H__
+#include 
+#endif
+
+
+namespace Script
+{
+	class CScriptStructure;
+	class CScript;
+	class CStruct;
+}
+
+namespace Nx
+{
+
+// Forward declarations
+class CModelLights;
+class CSector;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CSceneLight
+{
+public:
+
+	inline void						SetLightPosition( Mth::Vector &pos )	{ m_pos = pos; }
+	inline Mth::Vector &			GetLightPosition( void )				{ return m_pos; }
+
+	inline void						SetNameChecksum( uint32 cs )			{ m_name_checksum = cs; }
+	inline uint32					GetNameChecksum( void )					{ return m_name_checksum; }
+
+	inline void						SetLightColor( Image::RGBA col )		{ m_color = col; }
+	inline Image::RGBA &			GetLightColor( void )					{ return m_color; }
+
+	inline void						SetLightIntensity( float i )			{ m_intensity = i; }
+	inline float					GetLightIntensity( void )				{ return m_intensity; }
+
+	inline void						SetLightRadius( float r )				{ m_radius = r; m_reciprocal_radius = 1.0f / r; }
+	inline float					GetLightRadius( void )					{ return m_radius; }
+	inline float					GetLightReciprocalRadius( void )		{ return m_reciprocal_radius; }
+
+protected:
+
+	Mth::Vector						m_pos;
+	float							m_radius;
+	float							m_reciprocal_radius;
+	float							m_intensity;				// Intensity in range [0.0, 1.0].
+	Image::RGBA						m_color;
+	uint32							m_name_checksum;			// Provides a link between the light and the node that created it.
+};
+
+
+struct SVCLight
+{
+	Mth::Vector						m_pos;
+	float							m_intensity;		// [0.0, 1.0].
+	float							m_radius;
+	uint32							m_checksum;			// Checksum of the name of the node that corresponds to this light.
+	Image::RGBA						m_color;
+	Lst::HashTable< Nx::CSector >	*mp_sector_list;	// List of all the sectors that this light can affect.
+
+									SVCLight();
+									~SVCLight();
+
+
+	void							SetPosition( Mth::Vector & pos )			{ m_pos = pos; }
+	void							SetNameChecksum( uint32 cs )				{ m_checksum = cs; }
+	void							SetIntensity( float i );
+	void							SetColor( Image::RGBA col );
+	void							SetRadius( float radius );
+	void							ResetSectorList( void );
+	void							CalculateSectorList( void );
+	bool							AffectsSector( Nx::CSector *p_sector );
+	Lst::HashTable< Nx::CSector >	*GetSectorList( void );
+};
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CLightManager
+class	CLightManager
+{
+public:
+
+	// Constants
+	enum
+	{
+		MAX_LIGHTS				= 2,
+		MAX_SCENE_LIGHTS		= 256,	// These are static, designer placed lights in the level.
+		MAX_VC_LIGHTS			= 150,
+		WORLD_LIGHTS_STACK_SIZE	= 4,
+	};
+
+	// Upload lights to the engine (if applicable)
+	static void					sUpdateEngine( void );
+
+	// Create and destroy Lights
+	static void					sClearLights();
+
+	// World lights
+	static bool					sSetLightAmbientColor(const Image::RGBA rgba);
+	static Image::RGBA			sGetLightAmbientColor();
+
+	static bool					sSetLightDirection(int light_index, const Mth::Vector &direction);
+	static const Mth::Vector &	sGetLightDirection(int light_index);
+
+	static bool					sSetLightDiffuseColor(int light_index, const Image::RGBA rgba);
+	static Image::RGBA			sGetLightDiffuseColor(int light_index);
+
+	static void					sSetAmbientLightModulationFactor(float factor);
+	static float				sGetAmbientLightModulationFactor();
+	static void					sSetDiffuseLightModulationFactor(int light_index, float factor);
+	static float				sGetDiffuseLightModulationFactor(int light_index);
+
+	// Push and pop world lights on stack
+	static void					sPushWorldLights();
+	static bool					sPopWorldLights();
+
+	// Model Lights
+	static CModelLights *		sCreateModelLights();
+	static bool					sFreeModelLights(CModelLights *p_model_lights);
+
+	// This is here temporarily
+	static bool					sSetBrightness(float brightness);
+
+	// Static scene lights.
+	static CSceneLight *		sAddSceneLight( void );
+	static CSceneLight *		sGetSceneLight( uint32 checksum );
+	static CSceneLight *		sGetOptimumSceneLight( Mth::Vector & pos );
+	static void					sClearSceneLights( void );
+
+	static void					sClearVCLights();
+	static void					sAddVCLight(uint32 name, Mth::Vector &pos, float intensity,
+											Image::RGBA col, float outer_radius);
+
+	static void					sClearCurrentFakeLightsCommand();	
+	static void					sUpdateVCLights();
+	static void					sFakeLight(uint32 id, Script::CStruct *pParams);
+	static void					sFakeLights(const uint16 *p_nodes, int numNodes, int period, Script::CStruct *pParams);
+	
+protected:	
+
+	// Set of lights
+	struct SLightSet
+	{
+		// Ambient and diffuse lights
+		Image::RGBA				m_light_ambient_rgba;
+		Mth::Vector				m_light_direction[MAX_LIGHTS];
+		Image::RGBA				m_light_diffuse_rgba[MAX_LIGHTS];
+
+		// Modulation info
+		float					m_ambient_light_modulation_factor;
+		float					m_diffuse_light_modulation_factor[MAX_LIGHTS];
+	};
+
+	// World lights
+	static SLightSet			s_world_lights;						// Current world lights
+	static SLightSet			s_world_lights_stack[WORLD_LIGHTS_STACK_SIZE];
+	static int					s_world_lights_stack_index;			// index to top of stack (-1 if empty)
+
+	static CSceneLight			s_scene_lights[MAX_SCENE_LIGHTS];
+	static int					s_num_scene_lights;
+
+	static SVCLight				sp_vc_lights[MAX_VC_LIGHTS];
+	static int					s_num_vc_lights;
+
+	static uint16 *				sp_fake_lights_nodes;
+	static int					s_num_fake_lights_nodes;
+	static int					s_next_fake_light_node_to_process;
+	static int					s_fake_lights_period;
+	static int					s_fake_lights_current_time;
+	static Script::CStruct *	sp_fake_lights_params;
+	
+	// These are here temporarily
+	static float				s_brightness;
+	static float				s_brightness_target;
+	static float				s_min_brightness;
+	static float				s_ambient_brightness;
+	static float				s_diffuse_brightness[MAX_LIGHTS];
+
+	static SVCLight *			s_get_vclight_from_checksum( uint32 cs );
+	static void					s_recalculate_sector_lighting( Nx::CSector *p_sector );
+	static void					s_apply_lighting( Nx::CScene * p_scene, uint32 checksum = 0 );
+
+	// Platform-specific calls
+	static void					s_plat_update_engine();
+	static void					s_plat_update_lights();
+	static void					s_plat_update_colors();
+	static bool					s_plat_set_light_ambient_color();
+	static bool					s_plat_set_light_direction(int light_index);
+	static bool					s_plat_set_light_diffuse_color(int light_index);
+	static Image::RGBA			s_plat_get_light_ambient_color();
+	static const Mth::Vector &	s_plat_get_light_direction(int light_index);
+	static Image::RGBA			s_plat_get_light_diffuse_color(int light_index);
+
+	static CModelLights *		s_plat_create_model_lights();
+	static bool					s_plat_free_model_lights(CModelLights *p_model_lights);
+
+	// Friends
+	friend CModelLights;
+	friend CSceneLight;
+};
+
+bool ScriptSetLightAmbientColor(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetLightDirection(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetLightDiffuseColor(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetLightCurrentColor(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptPushWorldLights(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptPopWorldLights(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptDrawDirectionalLightLines(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NxLoadScreen.cpp b/Code/Gfx/NxLoadScreen.cpp
new file mode 100644
index 0000000..c66ad72
--- /dev/null
+++ b/Code/Gfx/NxLoadScreen.cpp
@@ -0,0 +1,316 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxLoadScreen.cpp
+
+// start autoduck documentation
+// @DOC NxLoadScreen
+// @module NxLoadScreen | None
+// @subindex Scripting Database
+// @index script | NxLoadScreen
+
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Nx
+{
+
+bool			CLoadScreen::s_active = false;		// Set to true when loading screen is enabled
+float			CLoadScreen::s_load_time;			// Amount of time it takes for loading bar to go to 100%
+int				CLoadScreen::s_bar_x = 258;			// Bar position
+int				CLoadScreen::s_bar_y = 400;
+int				CLoadScreen::s_bar_width = 140;		// Bar size
+int				CLoadScreen::s_bar_height = 8;
+Image::RGBA		CLoadScreen::s_bar_start_color = Image::RGBA(0, 76, 129, 128);
+Image::RGBA		CLoadScreen::s_bar_end_color = Image::RGBA(172, 211, 115, 128);
+int				CLoadScreen::s_bar_border_width = 5;// Border width
+int				CLoadScreen::s_bar_border_height = 5;// Border height
+Image::RGBA		CLoadScreen::s_bar_border_color = Image::RGBA(40, 40, 40, 128);
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sDisplay(const char* filename, bool just_freeze, bool blank)
+{
+	// Get rid of old stuff, if any
+	s_clear();
+
+	s_plat_display(filename, just_freeze, blank);
+
+	//sStartLoadingBar(38.0f);
+
+	s_active = true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sStartLoadingBar(float seconds)
+{
+	if (seconds <= 0.0f)
+	{
+		return;
+	}
+
+	s_load_time = seconds;
+
+	s_plat_update_bar_properties();			// Just in case
+	s_plat_start_loading_bar(seconds);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void CLoadScreen::sHide()
+{
+	// Turn off drawing
+	s_plat_hide();
+
+	// Get rid of old stuff
+	s_clear();
+
+	s_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sSetLoadingBarPos(int x, int y)
+{
+	s_bar_x = x;
+	s_bar_y = y;
+
+	s_plat_update_bar_properties();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sSetLoadingBarSize(int width, int height)
+{
+	s_bar_width = width;
+	s_bar_height = height;
+
+	s_plat_update_bar_properties();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sSetLoadingBarStartColor(Image::RGBA color)
+{
+	s_bar_start_color = color;
+
+	s_plat_update_bar_properties();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sSetLoadingBarEndColor(Image::RGBA color)
+{
+	s_bar_end_color = color;
+
+	s_plat_update_bar_properties();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sSetLoadingBarBorder(int width, int height)
+{
+	s_bar_border_width = width;
+	s_bar_border_height = height;
+
+	s_plat_update_bar_properties();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::sSetLoadingBarBorderColor(Image::RGBA color)
+{
+	s_bar_border_color = color;
+
+	s_plat_update_bar_properties();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_clear()
+{
+	s_plat_clear();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// The script functions
+
+// @script | SetLoadingBarPos | Sets the position of the loading bar
+// @parm int | x | X position of bar
+// @parm int | y | Y position of bar
+bool ScriptSetLoadingBarPos(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int x, y;
+	if (!pParams->GetInteger("x", &x))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'x' position of bar"));
+	}
+	if (!pParams->GetInteger("y", &y))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'y' position of bar"));
+	}
+
+	CLoadScreen::sSetLoadingBarPos(x, y);
+
+	return true;
+}
+
+// @script | SetLoadingBarSize | Sets the size of the loading bar
+// @parm int | width | Width of bar
+// @parm int | height | Heigh of bar
+bool ScriptSetLoadingBarSize(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int width, height;
+	if (!pParams->GetInteger("width", &width))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'width' of bar"));
+	}
+	if (!pParams->GetInteger("height", &height))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'height' of bar"));
+	}
+
+	CLoadScreen::sSetLoadingBarSize(width, height);
+
+	return true;
+}
+
+// @script | SetLoadingBarStartColor | Sets the color of the left side of the loading bar
+// @parm int | r | Red
+// @parm int | g | Green
+// @parm int | b | Blue
+bool ScriptSetLoadingBarStartColor(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int r, g, b;
+	if (!pParams->GetInteger("r", &r))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'r' color"));
+	}
+	if (!pParams->GetInteger("g", &g))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'g' color"));
+	}
+	if (!pParams->GetInteger("b", &b))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'b' color"));
+	}
+	
+	Image::RGBA rgb(r, g, b, 0x80);
+
+	CLoadScreen::sSetLoadingBarStartColor(rgb);
+
+	return true;
+}
+
+// @script | SetLoadingBarEndColor | Sets the color of the right side of the loading bar
+// @parm int | r | Red
+// @parm int | g | Green
+// @parm int | b | Blue
+bool ScriptSetLoadingBarEndColor(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int r, g, b;
+	if (!pParams->GetInteger("r", &r))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'r' color"));
+	}
+	if (!pParams->GetInteger("g", &g))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'g' color"));
+	}
+	if (!pParams->GetInteger("b", &b))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'b' color"));
+	}
+	
+	Image::RGBA rgb(r, g, b, 0x80);
+
+	CLoadScreen::sSetLoadingBarEndColor(rgb);
+
+	return true;
+}
+
+// @script | SetLoadingBarBorder | Sets the size of the loading bar border
+// @parm int | width | Width of bar border
+// @parm int | height | Heigh of bar border
+bool ScriptSetLoadingBarBorder(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int width, height;
+	if (!pParams->GetInteger("width", &width))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'width' of bar border"));
+	}
+	if (!pParams->GetInteger("height", &height))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'height' of bar border"));
+	}
+
+	CLoadScreen::sSetLoadingBarBorder(width, height);
+
+	return true;
+}
+
+// @script | SetLoadingBarBorderColor | Sets the color of the loading bar border
+// @parm int | r | Red
+// @parm int | g | Green
+// @parm int | b | Blue
+bool ScriptSetLoadingBarBorderColor(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int r, g, b;
+	if (!pParams->GetInteger("r", &r))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'r' color"));
+	}
+	if (!pParams->GetInteger("g", &g))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'g' color"));
+	}
+	if (!pParams->GetInteger("b", &b))
+	{
+		Dbg_MsgAssert(0, ("Can't find 'b' color"));
+	}
+	
+	Image::RGBA rgb(r, g, b, 0x80);
+
+	CLoadScreen::sSetLoadingBarBorderColor(rgb);
+
+	return true;
+}
+
+}
+
diff --git a/Code/Gfx/NxLoadScreen.h b/Code/Gfx/NxLoadScreen.h
new file mode 100644
index 0000000..182acdc
--- /dev/null
+++ b/Code/Gfx/NxLoadScreen.h
@@ -0,0 +1,64 @@
+#ifndef __GFX_NX_LOAD_SCREEN_H__
+#define __GFX_NX_LOAD_SCREEN_H__
+
+#include 
+
+namespace Script
+{
+	class CScriptStructure;
+	class CScript;
+}
+
+namespace Nx
+{
+
+class CLoadScreen
+{
+public:
+	static void					sDisplay(const char* filename, bool just_freeze, bool blank);
+	static void					sStartLoadingBar(float seconds);
+	static void					sHide();
+	static bool					sIsActive() { return s_active; }
+
+	static void					sSetLoadingBarPos(int x, int y);
+	static void					sSetLoadingBarSize(int width, int height);
+	static void					sSetLoadingBarStartColor(Image::RGBA color);
+	static void					sSetLoadingBarEndColor(Image::RGBA color);
+	static void					sSetLoadingBarBorder(int width, int height);
+	static void					sSetLoadingBarBorderColor(Image::RGBA color);
+
+private:
+	static void					s_clear();		// Clears out old memory (called by both Display and Hide)
+
+	// The platform dependent calls
+	static void					s_plat_display(const char *filename, bool just_freeze, bool blank);
+	static void					s_plat_start_loading_bar(float seconds);
+	static void					s_plat_hide();
+
+	static void					s_plat_update_bar_properties();
+
+	static void					s_plat_clear();
+
+	static bool					s_active;			// Set to true when loading screen is enabled
+	static float				s_load_time;		// Amount of time it takes for loading bar to go to 100%
+	static int					s_bar_x;			// Bar position
+	static int					s_bar_y;
+	static int					s_bar_width;		// Bar size
+	static int					s_bar_height;
+	static Image::RGBA			s_bar_start_color;
+	static Image::RGBA			s_bar_end_color;
+	static int					s_bar_border_width;	// Border width
+	static int					s_bar_border_height;// Border height
+	static Image::RGBA			s_bar_border_color;
+};
+
+bool ScriptSetLoadingBarPos(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetLoadingBarSize(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetLoadingBarStartColor(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetLoadingBarEndColor(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetLoadingBarBorder(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetLoadingBarBorderColor(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+}
+
+#endif //__GFX_NX_LOAD_SCREEN_H__
diff --git a/Code/Gfx/NxMesh.cpp b/Code/Gfx/NxMesh.cpp
new file mode 100644
index 0000000..e9ae8e9
--- /dev/null
+++ b/Code/Gfx/NxMesh.cpp
@@ -0,0 +1,216 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxMesh.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#ifdef __PLAT_NGC__
+#include 
+#endif		// __PLAT_NGC__
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+						
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMesh::CMesh()
+{
+	m_CASRemovalMask = 0;
+
+	// In case it isn't loaded below the p-line
+	mp_hierarchyObjects = NULL;
+	m_numHierarchyObjects = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMesh::~CMesh()
+{
+	// Remove Collision
+	if (mp_coll_objects)
+	{
+		Pip::Unload(m_coll_filename);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CMesh::LoadCollision(const char *p_name)
+{
+	
+	// for now collision is kind of assumed to be platform independent 
+	strcpy(m_coll_filename, p_name);
+	char *p_ext = strstr(m_coll_filename, ".");
+	if (p_ext)
+	{
+		strcpy(p_ext, ".col.");
+	} else {
+		strcat(m_coll_filename, ".col.");
+	}
+	strcat(m_coll_filename, CEngine::sGetPlatformExtension());
+
+//	Dbg_Message ( "Loading collision %s....", m_coll_filename );
+
+	Mem::PushMemProfile((char*)m_coll_filename);
+
+	uint8 *p_base_addr = (uint8 *) Pip::Load(m_coll_filename);
+	if (p_base_addr)
+	{
+		Nx::CCollObjTriData::SReadHeader *p_header = (Nx::CCollObjTriData::SReadHeader *) p_base_addr;
+		p_base_addr += sizeof(Nx::CCollObjTriData::SReadHeader);
+
+//		Dbg_Message ( "Version # %d header sizeof %d", p_header->m_version, sizeof(Nx::CCollObjTriData));
+#ifdef __PLAT_NGC__
+		Dbg_Message ( "Number of objects: %d verts: %d faces: %d", p_header->m_num_objects, p_header->m_total_num_verts, p_header->m_total_num_faces );
+#else
+//		Dbg_Message ( "Number of objects: %d verts: %d faces: %d", p_header->m_num_objects, p_header->m_total_num_verts, p_header->m_total_num_faces_large + p_header->m_total_num_faces_small);
+//		Dbg_Message ( "Small (%d) verts: %d Large (%d) verts: %d", Nx::CCollObjTriData::GetVertSmallElemSize(), p_header->m_total_num_verts_small, Nx::CCollObjTriData::GetVertElemSize(), p_header->m_total_num_verts_large);
+#endif		// __PLAT_NGC__
+		Dbg_MsgAssert(p_header->m_version >= 9, ("Collision version must be at least 9."));
+
+		// reserve space for objects
+		m_num_coll_objects = p_header->m_num_objects;
+		mp_coll_objects = (Nx::CCollObjTriData *) p_base_addr;
+
+		// Calculate base addresses for vert and face arrays
+		uint8 *p_base_vert_addr = (uint8 *) (mp_coll_objects + m_num_coll_objects);
+#ifndef __PLAT_NGC__
+		p_base_vert_addr = (uint8 *)(((uint)(p_base_vert_addr+15)) & 0xFFFFFFF0);	// Align to 128 bit boundary
+#ifdef FIXED_POINT_VERTICES
+		uint8 *p_base_intensity_addr = p_base_vert_addr + (p_header->m_total_num_verts_large * Nx::CCollObjTriData::GetVertElemSize() +
+														   p_header->m_total_num_verts_small * Nx::CCollObjTriData::GetVertSmallElemSize());
+		uint8 *p_base_face_addr = p_base_intensity_addr + p_header->m_total_num_verts;
+		p_base_face_addr = (uint8 *)(((uint)(p_base_face_addr+3)) & 0xFFFFFFFC);	// Align to 32 bit boundary
+#else
+		uint8 *p_base_intensity_addr = NULL;
+		uint8 *p_base_face_addr = p_base_vert_addr + (p_header->m_total_num_verts * Nx::CCollObjTriData::GetVertElemSize());
+		p_base_face_addr = (uint8 *)(((uint)(p_base_face_addr+15)) & 0xFFFFFFF0);	// Align to 128 bit boundary
+#endif // FIXED_POINT_VERTICES
+#else
+		uint8 *p_base_face_addr = p_base_vert_addr + (p_header->m_total_num_faces * Nx::CCollObjTriData::GetVertElemSize());
+		p_base_face_addr = (uint8 *)(((uint)(p_base_face_addr+3)) & 0xFFFFFFFC);	// Align to 32 bit boundary
+#endif		// __PLAT_NGC__
+
+		// Calculate addresses for BSP arrays
+#ifndef __PLAT_NGC__
+		uint8 *p_node_array_size = p_base_face_addr + (p_header->m_total_num_faces_large * Nx::CCollObjTriData::GetFaceElemSize() +
+													   p_header->m_total_num_faces_small * Nx::CCollObjTriData::GetFaceSmallElemSize());
+		p_node_array_size += ( p_header->m_total_num_faces_large & 1 ) ? 2 : 0;
+#else
+		uint8 *p_node_array_size = p_base_face_addr + ( p_header->m_total_num_faces * Nx::CCollObjTriData::GetFaceElemSize() );
+		p_node_array_size += ( p_header->m_total_num_faces & 1 ) ? 2 : 0;
+#endif		// __PLAT_NGC__
+		uint8 *p_base_node_addr = p_node_array_size + 4;
+		uint8 *p_base_face_idx_addr = p_base_node_addr + *((int *) p_node_array_size);
+
+		// Read objects
+		for (int oidx = 0; oidx < p_header->m_num_objects; oidx++)
+		{
+#ifdef __PLAT_NGC__
+			CScene * p_scene = static_cast( (static_cast( this ))->GetScene() );
+			mp_coll_objects[oidx].InitCollObjTriData(p_scene, p_base_vert_addr, NULL, p_base_face_addr, p_base_node_addr, p_base_face_idx_addr);
+#else
+			mp_coll_objects[oidx].InitCollObjTriData(NULL, p_base_vert_addr, p_base_intensity_addr, p_base_face_addr,
+													 p_base_node_addr, p_base_face_idx_addr);
+#endif		// __PLAT_NGC__
+			mp_coll_objects[oidx].InitBSPTree();
+
+			// Add to mesh bbox
+			m_collision_bbox.AddPoint(mp_coll_objects[oidx].GetBBox().GetMin());
+			m_collision_bbox.AddPoint(mp_coll_objects[oidx].GetBBox().GetMax());
+		}
+
+//		Dbg_Message("Mesh bounding box: min (%f, %f, %f) max (%f, %f, %f)", 
+//					m_collision_bbox.GetMin()[X], m_collision_bbox.GetMin()[Y], m_collision_bbox.GetMin()[Z], 
+//					m_collision_bbox.GetMax()[X], m_collision_bbox.GetMax()[Y], m_collision_bbox.GetMax()[Z]);
+
+	} else {
+		Dbg_Error ( "Could not open collision file\n" );
+		return false;
+	}
+
+//	Dbg_Message ( "successfully loaded collision" );
+
+	if (m_num_coll_objects > 0)
+	{
+#if 0
+		// Add to CSectors
+		for (int i = 0; i < m_num_coll_objects; i++)
+		{
+			CSector *p_sector = GetSector(mp_coll_objects[i].GetChecksum());
+			if (p_sector)	// Don't assert now since there may not be renderable data
+			{
+				Dbg_MsgAssert(p_sector, ("LoadCollision: Can't find CSector with checksum %x", mp_coll_objects[i].GetChecksum()));
+				p_sector->AddCollSector(&(mp_coll_objects[i]));
+			}
+		}
+#endif
+	}
+
+	Mem::PopMemProfile(/*(char*)m_coll_filename*/);
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CHierarchyObject* CMesh::GetHierarchy()
+{
+	return mp_hierarchyObjects;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CMesh::GetNumObjectsInHierarchy()
+{
+	return m_numHierarchyObjects;
+}
+
+
+} // Nx
+
diff --git a/Code/Gfx/NxMesh.h b/Code/Gfx/NxMesh.h
new file mode 100644
index 0000000..b10ff4b
--- /dev/null
+++ b/Code/Gfx/NxMesh.h
@@ -0,0 +1,72 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxMesh.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#ifndef	__GFX_NXMESH_H__
+#define	__GFX_NXMESH_H__
+
+                           
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+#include 
+
+namespace Nx
+{
+	class CTexDict;
+	class CCollObjTriData;
+	class CHierarchyObject;
+
+class CMesh : public Spt::Class
+{
+
+public:
+    // The basic interface to the model
+    // this is the machine independent part	
+    // machine independent range checking, etc can go here	
+	CMesh();
+    virtual				~CMesh();
+
+	bool				LoadCollision(const char *p_name);			// load mesh collision data
+	
+	Nx::CHierarchyObject*		GetHierarchy();
+	int							GetNumObjectsInHierarchy();
+	Nx::CCollObjTriData*		GetCollisionTriDataArray() const { return mp_coll_objects; }
+	int							GetCollisionTriDataArraySize() const { return m_num_coll_objects; }
+	const Mth::CBBox &			GetCollisionBBox() const { return m_collision_bbox; }
+
+public:
+	Nx::CTexDict*		GetTextureDictionary()
+	{
+		return mp_texDict;
+	}
+	uint32				GetRemovalMask()
+	{
+		return m_CASRemovalMask;
+	}
+
+protected:
+	Nx::CTexDict*		mp_texDict;
+
+	char						m_coll_filename[128];						// collision filename (kept around for unload)
+	int							m_num_coll_objects;							// non-cloned collision
+	Nx::CCollObjTriData *		mp_coll_objects;
+	Mth::CBBox					m_collision_bbox;							// Bounding box of whole mesh
+
+	uint32						m_CASRemovalMask;
+
+	// For mesh heirarchies
+	CHierarchyObject*			mp_hierarchyObjects;						// array of hierarchy objects
+	int							m_numHierarchyObjects;						// number of hierarchy objects
+};
+
+}
+
+#endif // __GFX_NXMESH_H__
+
+
diff --git a/Code/Gfx/NxMiscFX.cpp b/Code/Gfx/NxMiscFX.cpp
new file mode 100644
index 0000000..3592fb5
--- /dev/null
+++ b/Code/Gfx/NxMiscFX.cpp
@@ -0,0 +1,831 @@
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "NxMiscFX.h"
+
+
+namespace Nx
+{
+
+Lst::HashTable< sScreenFlashDetails >		*p_screen_flash_details_table	= NULL;
+Lst::HashTable< sSplatInstanceDetails >		*p_splat_details_table			= NULL;
+Lst::HashTable< sSplatTrailInstanceDetails >*p_splat_trail_details_table	= NULL;
+Lst::HashTable< sShatterInstanceDetails >	*p_shatter_details_table		= NULL;
+
+static const float	DEFAULT_AREA_TEST			= 288.0f;
+static const float	DEFAULT_VELOCITY_VARIANCE	= 0.0f;
+static const float	DEFAULT_SPREAD_FACTOR		= 1.0f;
+static const float	DEFAULT_LIFETIME			= 4.0f;
+static const float	DEFAULT_BOUNCE				= -10000.0f;
+static const float	DEFAULT_BOUNCE_AMPLITUDE	= 0.8f;
+
+Mth::Vector			shatterVelocity;
+float				shatterAreaTest			= DEFAULT_AREA_TEST * DEFAULT_AREA_TEST;
+float				shatterVelocityVariance	= DEFAULT_VELOCITY_VARIANCE;
+float				shatterSpreadFactor		= DEFAULT_SPREAD_FACTOR;
+float				shatterLifetime			= DEFAULT_LIFETIME;
+float				shatterBounce			= DEFAULT_BOUNCE;
+float				shatterBounceAmplitude	= DEFAULT_BOUNCE_AMPLITUDE;
+sTriSubdivideStack	triSubdivideStack;
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int sSplatInstanceDetails::GetOldestSplat( void )
+{
+	int oldest		= m_lifetimes[0];
+	int oldest_idx	= 0;
+	
+	for( uint32 idx = 0; idx < SPLAT_POLYS_PER_MESH; ++idx )
+	{
+		if( m_lifetimes[idx] == 0 )
+		{
+			return idx;
+		}
+		else if( m_lifetimes[idx] < oldest )
+		{
+			oldest		= m_lifetimes[idx];
+			oldest_idx	= idx;
+		}
+	}
+
+	// If we get here there wasn't a 'dead' splat, so return the oldest.
+	return oldest_idx;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sTriSubdivideStack::Reset( void )
+{
+	m_offset		= 0;
+	m_block_size	= 0;
+
+	memset( m_data, 0x03, TRI_SUBDIVIDE_STACK_SIZE );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sTriSubdivideStack::Clear( void )
+{
+	m_offset		= 0;
+	m_block_size	= 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sTriSubdivideStack::Push( void *p_data )
+{
+	Dbg_Assert( m_offset + m_block_size < TRI_SUBDIVIDE_STACK_SIZE );
+	
+	memcpy( m_data + m_offset, p_data, m_block_size );
+	m_offset += m_block_size;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sTriSubdivideStack::Pop( void* p_data )
+{
+	Dbg_Assert( m_offset >= m_block_size );
+	
+	m_offset -= m_block_size;
+	memcpy( p_data, m_data + m_offset, m_block_size );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const void * sTriSubdivideStack::Peek( uint index )
+{
+	int offset = index * m_block_size;
+	Dbg_MsgAssert( offset < m_offset, ("Index %d is beyond end offset %d", index, m_offset) );
+	
+	return m_data + offset;
+}
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sShatterInstanceDetails::sShatterInstanceDetails( int num_tris )
+{
+	//Dbg_Message("Allocating %d bytes for position arrays of %d tris", (sizeof(Mth::Vector) * 2 + sizeof(Mth::Matrix)) * num_tris, num_tris);
+
+	mp_positions		= new Mth::Vector[num_tris];
+	mp_velocities		= new Mth::Vector[num_tris];
+	mp_matrices			= new Mth::Matrix[num_tris];
+	m_num_triangles		= num_tris;
+
+	m_gravity			= 128.0f;
+	m_lifetime			= shatterLifetime;
+	m_bounce_level		= shatterBounce;
+	m_bounce_amplitude	= shatterBounceAmplitude;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sShatterInstanceDetails::~sShatterInstanceDetails( void )
+{
+	delete [] mp_positions;
+	delete [] mp_velocities;
+	delete [] mp_matrices;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sShatterInstanceDetails::UpdateParameters( int index, float timestep )
+{
+	Dbg_Assert( index < m_num_triangles );
+	
+	mp_positions[index]	+= mp_velocities[index] * timestep;
+
+	if(( mp_positions[index][Y] < m_bounce_level ) && ( mp_velocities[index][Y] < 0.0f ))
+	{
+		// Hit the floor. Bounce back up.
+		mp_positions[index][Y]	= m_bounce_level + ( m_bounce_level - mp_positions[index][Y] );
+		mp_velocities[index][Y]	= mp_velocities[index][Y] * -m_bounce_amplitude;
+
+		// And figure a new rotation matrix.
+		Mth::Vector axis( -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ));
+		axis.Normalize();
+		mp_matrices[index].Ident();
+		mp_matrices[index].Rotate( axis, 0.1f * ((float)rand() / RAND_MAX ));
+	}
+
+	mp_velocities[index][Y]	-= m_gravity * timestep;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int	sShatterInstanceDetails::s_query_memory_needed(int num_tris)
+{
+	int size = sizeof(sShatterInstanceDetails);
+	size += (2 * sizeof(Mth::Vector) + sizeof(Mth::Matrix)) * num_tris;
+
+	return size;
+}
+
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void MiscFXInitialize( void )
+{
+	if( p_screen_flash_details_table == NULL )
+	{
+		p_screen_flash_details_table = new Lst::HashTable< sScreenFlashDetails >( 4 );
+	}
+
+	if( p_splat_details_table == NULL )
+	{
+		p_splat_details_table = new Lst::HashTable< sSplatInstanceDetails >( 4 );
+	}
+
+	if( p_splat_trail_details_table == NULL )
+	{
+		p_splat_trail_details_table = new Lst::HashTable< sSplatTrailInstanceDetails >( 4 );
+	}
+	
+	if( p_shatter_details_table == NULL )
+	{
+		p_shatter_details_table = new Lst::HashTable< sShatterInstanceDetails >( 4 );
+	}
+	
+	plat_texture_splat_initialize();
+	plat_shatter_initialize();
+
+	triSubdivideStack.Reset();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void MiscFXCleanup( void )
+{
+	// Can cleanup the screen flash details here, since they are not platform specific.
+	p_screen_flash_details_table->IterateStart();
+	sScreenFlashDetails *p_details = p_screen_flash_details_table->IterateNext();
+	while( p_details )
+	{
+		sScreenFlashDetails *p_delete	= p_details;
+		p_details						= p_screen_flash_details_table->IterateNext();
+
+		p_screen_flash_details_table->FlushItem((uint32)p_delete );
+		delete p_delete;
+	}
+	
+	// Ditto for the tetxure splat trail details.
+	KillAllTextureSplats();
+
+	// Clean up the shatter details.
+	plat_shatter_cleanup();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void KillAllTextureSplats( void )
+{
+	// This was split into a separate function because it can also be used after turning geometry off,
+	// to ensure no splats are 'floating'.
+	p_splat_trail_details_table->IterateStart();
+	sSplatTrailInstanceDetails *p_trail_details = p_splat_trail_details_table->IterateNext();
+	while( p_trail_details )
+	{
+		sSplatTrailInstanceDetails *p_delete	= p_trail_details;
+		p_trail_details							= p_splat_trail_details_table->IterateNext();
+
+		p_splat_trail_details_table->FlushItem((uint32)p_delete );
+		delete p_delete;
+	}
+	
+	// Call the platform specific cleanup function.
+	plat_texture_splat_cleanup();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void AddScreenFlash( int viewport, Image::RGBA from, Image::RGBA to, float duration, float z, uint32 flags, const char *p_texture_name )
+{
+	Replay::WriteScreenFlash(viewport, from, to, duration, z, flags);
+	
+	// Ensure the supplied viewport is within bounds.
+#ifdef __NOPT_ASSERT__
+	int num_viewports = CViewportManager::sGetNumActiveViewports();
+	Dbg_Assert( viewport < num_viewports );
+#endif		// __NOPT_ASSERT__
+
+	// Resolve the texture name if present.
+	CTexture *p_texture = NULL;
+	if( p_texture_name )
+	{
+	}
+
+	// Store these details.
+	sScreenFlashDetails *p_flash = new sScreenFlashDetails;
+	p_flash->m_from		= from;
+	p_flash->m_to		= to;
+	p_flash->m_duration	= duration;
+	p_flash->m_lifetime	= duration;
+	p_flash->m_z		= z;
+	p_flash->m_flags	= flags;
+	p_flash->mp_texture	= p_texture;
+	p_flash->m_viewport	= viewport;
+
+	p_screen_flash_details_table->PutItem((uint32)p_flash, p_flash );
+
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ScreenFlashUpdate( void )
+{
+	float framelength = Tmr::FrameLength();
+
+	if( p_screen_flash_details_table )
+	{
+		p_screen_flash_details_table->IterateStart();
+		sScreenFlashDetails *p_details = p_screen_flash_details_table->IterateNext();
+		while( p_details )
+		{
+			sScreenFlashDetails *p_delete = NULL;
+
+			// Don't process if paused.
+			if( !Mdl::FrontEnd::Instance()->GamePaused() || ( p_details->m_flags & Nx::SCREEN_FLASH_FLAG_IGNORE_PAUSE ))
+			{
+				p_details->m_lifetime -= framelength;
+				if( p_details->m_lifetime > 0.0f )
+				{
+					// Calculate the current color.
+					float mult				= p_details->m_lifetime / p_details->m_duration;
+					p_details->m_current.r	= p_details->m_to.r + (int)(((float)p_details->m_from.r - (float)p_details->m_to.r ) * mult );
+					p_details->m_current.g	= p_details->m_to.g + (int)(((float)p_details->m_from.g - (float)p_details->m_to.g ) * mult );
+					p_details->m_current.b	= p_details->m_to.b + (int)(((float)p_details->m_from.b - (float)p_details->m_to.b ) * mult );
+					p_details->m_current.a	= p_details->m_to.a + (int)(((float)p_details->m_from.a - (float)p_details->m_to.a ) * mult );
+				}
+				else
+				{
+					p_delete = p_details;
+				}
+			}	
+
+			p_details = p_screen_flash_details_table->IterateNext();
+
+			if( p_delete )
+			{
+				p_screen_flash_details_table->FlushItem((uint32)p_delete );
+				delete p_delete;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ScreenFlashRender( int viewport, uint32 flags )
+{
+	p_screen_flash_details_table->IterateStart();
+	sScreenFlashDetails *p_details = p_screen_flash_details_table->IterateNext();
+	while( p_details )
+	{
+		if( viewport == p_details->m_viewport )
+		{
+			if(( flags & Nx::SCREEN_FLASH_FLAG_BEHIND_PANEL ) == ( p_details->m_flags & Nx::SCREEN_FLASH_FLAG_BEHIND_PANEL ))
+			{
+				plat_screen_flash_render( p_details );
+			}
+		}
+		p_details = p_screen_flash_details_table->IterateNext();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void TextureSplatUpdate( void )
+{
+	// Don't process if paused.
+	if( Mdl::FrontEnd::Instance()->GamePaused() && !Replay::RunningReplay())
+	{
+		return;
+	}	
+
+	if( p_splat_details_table )
+	{
+		int framelength_in_ms = (int)( Tmr::FrameLength() * 1000.0f );
+		
+		p_splat_details_table->IterateStart();
+		sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+		while( p_details )
+		{
+			// Initialize to -1 to indicate no active splats so far.
+			p_details->m_highest_active_splat = -1;
+			
+			for( int i = 0; i < SPLAT_POLYS_PER_MESH; ++i )
+			{
+				if( p_details->m_lifetimes[i] > 0 )
+				{
+					p_details->m_lifetimes[i] -= framelength_in_ms;
+					if( p_details->m_lifetimes[i] <= 0 )
+					{
+						// Make sure to set lifetime back to exactly zero to indicate ready for re-use.
+						p_details->m_lifetimes[i] = 0;
+
+						// This splat has just 'expired'. Reset this poly.
+						plat_texture_splat_reset_poly( p_details, i );
+					}
+					else
+					{
+						p_details->m_highest_active_splat = i;
+					}
+				}
+			}
+			p_details = p_splat_details_table->IterateNext();
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool TextureSplat( Mth::Vector& splat_start, Mth::Vector& splat_end, float size, float lifetime, const char *p_texture_name, uint32 trail )
+{
+	Replay::WriteTextureSplat(splat_start,splat_end,size,lifetime,p_texture_name,trail);
+	
+	// If this is a flagged as a trail splat, see if we have a trail instance already that matches.
+	sSplatTrailInstanceDetails *p_trail_details = NULL;
+	if( trail > 0 )
+	{
+		uint32 time = Tmr::GetTime();
+
+		p_splat_trail_details_table->IterateStart();
+		p_trail_details = p_splat_trail_details_table->IterateNext();
+		while( p_trail_details )
+		{
+			sSplatTrailInstanceDetails *p_delete = NULL;
+
+			if(( time < p_trail_details->m_last_pos_added_time ) || (( time - p_trail_details->m_last_pos_added_time ) > 500 ))
+			{
+				// This trail is over half a second old - remove it.
+				p_delete = p_trail_details;
+			}
+			else
+			{
+				// This trail had a point added within the last half second, so check it for bone ID.
+				if( p_trail_details->m_trail_id == trail )
+				{
+					// ID's match, so check it for proximity. Seems like fast moving reverts can cause successive calls to be up to 5.5 feet away.
+					float sq_dist = ( splat_start - p_trail_details->m_last_pos ).LengthSqr();
+					if( sq_dist < ( 66.0f * 66.0f ))
+					{
+						// This trail had a point added within the last half second that is within 3 feet of this point. Select this trail.
+						break;
+					}
+				}
+			}
+			
+			p_trail_details = p_splat_trail_details_table->IterateNext();
+
+			if( p_delete )
+			{
+				p_splat_trail_details_table->FlushItem((uint32)p_delete );
+				delete p_delete;
+			}
+		}
+
+		// If there were no trail details, create a new instance and just return.
+		if( p_trail_details == NULL )
+		{
+			p_trail_details = new sSplatTrailInstanceDetails;
+			p_trail_details->m_trail_id				= trail;
+			p_trail_details->m_last_pos				= splat_start;
+			p_trail_details->m_last_pos_added_time	= time;
+			p_splat_trail_details_table->PutItem((uint32)p_trail_details, p_trail_details );
+			return true;
+		}
+	}
+	
+	// Convert the name string to a checksum.
+	uint32 texture_checksum = Crc::GenerateCRCFromString( p_texture_name );
+	
+	// Obtain a pointer to the texture.
+	Nx::CTexture *p_texture	= Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	Dbg_MsgAssert( p_texture, ("Couldn't find texture %s in particle texture dictionary", p_texture_name) );
+
+	Mth::Line is( splat_start, splat_end );
+
+	// Make initial line bounding box.
+	Mth::CBBox line_bbox( is.m_start );
+	line_bbox.AddPoint( is.m_end );
+	
+	// Get a list of all collision sectors within all super sectors that the line intersects.
+	// This is a very fast call, but likely to provide many collision sectors that are not actually within the
+	// bounding box of the line.
+	SSec::Manager	*ss_man				= Nx::CEngine::sGetNearestSuperSectorManager( is );
+	Nx::CCollStatic	**pp_coll_obj_list	= ss_man->GetIntersectingCollSectors( is );
+
+	// Can't create blood splat if no objects found.
+	if( !pp_coll_obj_list )
+	{
+		return false;
+	}
+
+	// The sector table is used to pass a platform independent set of CSector pointers to the below p-line code.
+	// Garrett: Also added static collision table, just in case CSector points to a CCollMulti
+	#define SECTOR_TABLE_SIZE		127
+	static CSector* sector_table[SECTOR_TABLE_SIZE + 1];
+	static Nx::CCollStatic* collision_table[SECTOR_TABLE_SIZE + 1];
+
+	int				sector_table_index = 0;
+	Nx::CCollStatic *p_coll_obj;
+	while(( p_coll_obj = *pp_coll_obj_list ))
+	{
+		if( p_coll_obj->GetGeometry())
+		{
+			// Determine whether this collision sector actually falls within the bounding box of our line.
+			if( p_coll_obj->GetGeometry()->GetBBox().Intersect( line_bbox ))
+			{
+				uint32 checksum = p_coll_obj->GetChecksum();
+
+				// From the checksum we can get the CSector (the renderable version) from the hash table of the scene.
+				Nx::CSector *p_sector = Nx::CEngine::sGetSector( checksum );
+
+				// Store the pointer in the table.
+				Dbg_Assert( sector_table_index < SECTOR_TABLE_SIZE );
+				collision_table[sector_table_index] = p_coll_obj;
+				sector_table[sector_table_index++] = p_sector;
+			}
+			++pp_coll_obj_list;
+		}
+	}
+
+	// Mark the end of the sector table with a NULL pointer.
+	if( sector_table_index > 0 )
+	{
+		sector_table[sector_table_index] = NULL;
+		collision_table[sector_table_index] = NULL;
+
+		// Make sure we can can calculate an up vector.  If not, just move on.
+		bool rv;
+		if ((trail == 0) || (splat_start[X] != p_trail_details->m_last_pos[X]) || (splat_start[Z] != p_trail_details->m_last_pos[Z]))
+		{
+			rv = plat_texture_splat( sector_table, collision_table, splat_start, splat_end, size, lifetime, p_texture, p_trail_details );
+		} else {
+			rv = false;
+		}
+
+		// Update the trail details if present.
+		if( p_trail_details )
+		{
+			p_trail_details->m_last_pos	= splat_start;
+			p_trail_details->m_last_pos_added_time	= Tmr::GetTime();
+		}
+
+		return rv;
+	}
+
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void TextureSplatRender( void )
+{
+
+	
+	plat_texture_splat_render();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ShatterSetParams( Mth::Vector& velocity, float area_test, float velocity_variance, float spread_factor, float lifetime, float bounce, float bounce_amplitude )
+{
+	Replay::WriteShatterParams(velocity, area_test, velocity_variance, spread_factor, lifetime, bounce, bounce_amplitude);
+
+	shatterVelocity			= velocity;
+	shatterAreaTest			= ( area_test == 0.0f ) ? ( DEFAULT_AREA_TEST * DEFAULT_AREA_TEST ) : ( area_test * area_test );
+	shatterVelocityVariance	= ( velocity_variance == 0.0f ) ? DEFAULT_VELOCITY_VARIANCE : velocity_variance;
+	shatterSpreadFactor		= ( spread_factor == 0.0f ) ? DEFAULT_SPREAD_FACTOR : spread_factor;
+	shatterLifetime			= ( lifetime == 0.0f ) ? DEFAULT_LIFETIME : lifetime;
+	shatterBounce			= ( bounce == 0.0f ) ? DEFAULT_BOUNCE : bounce;
+	shatterBounceAmplitude	= ( bounce_amplitude == 0.0f ) ? DEFAULT_BOUNCE_AMPLITUDE : bounce_amplitude;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Shatter( CGeom *p_geom )
+{
+	plat_shatter( p_geom );
+
+	// After shattering the subdivision stack should be empty.
+	Dbg_Assert( triSubdivideStack.IsEmpty());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ShatterUpdate( void )
+{
+	// Don't process if paused.
+	if( Mdl::FrontEnd::Instance()->GamePaused() && !Replay::RunningReplay())
+	{
+		return;
+	}	
+	if (Replay::Paused())
+	{
+		return;
+	}
+		
+	if( p_shatter_details_table )
+	{
+		float framelength = Tmr::FrameLength();
+		
+		p_shatter_details_table->IterateStart();
+		sShatterInstanceDetails *p_details = p_shatter_details_table->IterateNext();
+		while( p_details )
+		{
+			sShatterInstanceDetails *p_delete = NULL;
+			
+			p_details->m_lifetime -= framelength;
+			if( p_details->m_lifetime <= 0.0f )
+			{
+				// Remove this entry from the table and destroy at the bottom of the loop.
+				p_delete = p_details;
+			}
+			else
+			{
+				plat_shatter_update( p_details, framelength );
+			}
+			p_details = p_shatter_details_table->IterateNext();
+
+			if( p_delete )
+			{
+				p_shatter_details_table->FlushItem((uint32)p_delete );
+				delete p_delete;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ShatterRender( void )
+{
+	if( p_shatter_details_table )
+	{
+		p_shatter_details_table->IterateStart();
+		sShatterInstanceDetails *p_details = p_shatter_details_table->IterateNext();
+		while( p_details )
+		{
+			plat_shatter_render( p_details );
+			p_details = p_shatter_details_table->IterateNext();
+		}
+	}
+}
+
+
+///////////////////////////////////////////////////////////////////
+//
+// FOG
+//
+///////////////////////////////////////////////////////////////////
+
+bool		CFog::s_enabled = false;
+float		CFog::s_near_distance = 3000.0f;
+float		CFog::s_exponent = 10.0f;
+Image::RGBA	CFog::s_rgba(0x60, 0x60, 0x80, 0x66);
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::sEnableFog(bool enable)
+{
+	s_enabled = enable;
+	s_plat_enable_fog(enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::sSetFogNearDistance(float distance)
+{
+	s_near_distance = distance;
+	s_plat_set_fog_near_distance(distance);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::sSetFogExponent(float exponent)
+{
+	s_exponent = exponent;
+	s_plat_set_fog_exponent(exponent);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::sSetFogRGBA(Image::RGBA rgba)
+{
+	s_rgba = rgba;
+	s_plat_set_fog_rgba(rgba);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::sSetFogColor( void )
+{
+	s_plat_set_fog_color();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CFog::sFogUpdate( void )
+{
+	s_plat_fog_update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CFog::sIsFogEnabled()
+{
+	return s_enabled;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float		CFog::sGetFogNearDistance()
+{
+	return s_near_distance;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float		CFog::sGetFogExponent()
+{
+	return s_exponent;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA	CFog::sGetFogRGBA()
+{
+	return s_rgba;
+}
+
+} // Nx
diff --git a/Code/Gfx/NxMiscFX.h b/Code/Gfx/NxMiscFX.h
new file mode 100644
index 0000000..e65f4e5
--- /dev/null
+++ b/Code/Gfx/NxMiscFX.h
@@ -0,0 +1,205 @@
+#ifndef	__GFX_NXMISCFX_H__
+#define	__GFX_NXMISCFX_H__
+
+#include 
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+enum eScreenFlashFlags
+{
+	SCREEN_FLASH_FLAG_BEHIND_PANEL	= 0x01,
+	SCREEN_FLASH_FLAG_ADDITIVE		= 0x02,
+	SCREEN_FLASH_FLAG_SUBTRACTIVE	= 0x04,
+	SCREEN_FLASH_FLAG_IGNORE_PAUSE	= 0x08
+};
+	
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sScreenFlashDetails
+{
+	int			m_viewport;
+	Image::RGBA	m_from;
+	Image::RGBA	m_to;
+	Image::RGBA	m_current;
+	uint32		m_flags;
+	float		m_duration;
+	float		m_lifetime;
+	float		m_z;
+	CTexture*	mp_texture;
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CFog
+{
+public:
+	static void			sEnableFog(bool enable);
+	static void			sSetFogExponent(float exponent);
+	static void			sSetFogNearDistance(float distance);
+	static void			sSetFogRGBA(Image::RGBA rgba);
+	static void			sSetFogColor( void );
+	static void			sFogUpdate( void );
+
+	static bool			sIsFogEnabled();
+	static float		sGetFogNearDistance();
+	static float		sGetFogExponent();
+	static Image::RGBA	sGetFogRGBA();
+
+private:
+	static void			s_plat_enable_fog(bool enable);
+	static void			s_plat_set_fog_near_distance(float distance);
+	static void			s_plat_set_fog_exponent(float exponent);
+	static void			s_plat_set_fog_rgba(Image::RGBA rgba);
+	static void			s_plat_set_fog_color( void );
+	static void			s_plat_fog_update( void );
+
+	// Keep the numbers here, just in case they need to be mangled below the p-line
+	static bool			s_enabled;
+	static float		s_near_distance;
+	static float		s_exponent;
+	static Image::RGBA	s_rgba;
+};
+
+
+#define SPLAT_POLYS_PER_MESH	256
+	
+/******************************************************************/
+/*                                                                */
+/* Platform independent structure for holding details on a set    */
+/* of texture splats. The platform-specific code will most likely */
+/* derive a structure from this to maintain additional details.   */
+/*                                                                */
+/******************************************************************/
+struct sSplatInstanceDetails
+{
+	int					m_lifetimes[SPLAT_POLYS_PER_MESH];
+	int					m_highest_active_splat;				// Can be used to dynamically optimise rendering lists.
+	int					GetOldestSplat( void );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Platform independent structure for holding details on a single */
+/* trail texture splats.										  */
+/*                                                                */
+/******************************************************************/
+struct sSplatTrailInstanceDetails
+{
+	Mth::Vector			m_last_pos;
+	uint32				m_trail_id;				// Keyed from the bone checksum.
+	uint32				m_last_pos_added_time;	// In milliseconds.
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sShatterInstanceDetails
+{
+						sShatterInstanceDetails( int num_tris );
+	virtual				~sShatterInstanceDetails( void );
+	void				UpdateParameters( int index, float timestep );
+	
+	Mth::Vector*		mp_positions;
+	Mth::Vector*		mp_velocities;
+	Mth::Vector*		mp_normals;    // For 'fake' lighting.
+	Mth::Matrix*		mp_matrices;
+	float				m_lifetime;	
+	float				m_gravity;	
+	float				m_bounce_level;
+	float				m_bounce_amplitude;
+	int					m_num_triangles;
+
+protected:
+	static int			s_query_memory_needed(int num_tris);
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sTriSubdivideStack
+{
+	static const int	TRI_SUBDIVIDE_STACK_SIZE	= 16 * 1024;
+
+	void				Reset( void );
+	void				Clear( void );
+	bool				IsEmpty( void )				{ return m_offset == 0; }
+	void				SetBlockSize( int size )	{ m_block_size = size; }
+	int					GetBlockSize( void )		{ return m_block_size; }
+	void				Pop( void *p_data );
+	void				Push( void *p_data );
+	const void *		Peek( uint index );
+
+	private:	
+	int					m_offset;
+	int					m_block_size;
+	char				m_data[TRI_SUBDIVIDE_STACK_SIZE];
+};
+
+
+
+extern Lst::HashTable< sSplatInstanceDetails >			*p_splat_details_table;
+extern Lst::HashTable< sSplatTrailInstanceDetails >		*p_splat_trail_details_table;
+extern Lst::HashTable< sShatterInstanceDetails >		*p_shatter_details_table;
+extern Mth::Vector										shatterVelocity;
+extern float											shatterAreaTest;
+extern float											shatterVelocityVariance;
+extern float											shatterSpreadFactor;
+extern float											shatterLifetime;
+extern float											shatterBounce;
+
+extern sTriSubdivideStack								triSubdivideStack;
+
+void MiscFXInitialize( void );
+void MiscFXCleanup( void );
+
+void AddScreenFlash( int viewport, Image::RGBA from, Image::RGBA to, float duration, float z, uint32 flags, const char *p_texture_name );
+void ScreenFlashUpdate( void );
+void ScreenFlashRender( int viewport, uint32 flags );
+
+void TextureSplatRender( void );
+void TextureSplatUpdate( void );
+bool TextureSplat( Mth::Vector& splat_start, Mth::Vector& splat_end, float size, float lifetime, const char *p_texture_name, uint32 trail = 0 );
+void KillAllTextureSplats( void );
+
+void ShatterSetParams( Mth::Vector& velocity, float area_test, float velocity_variance, float spread_factor, float lifetime, float bounce, float bounce_amplitude );
+void Shatter( CGeom *p_geom );
+void ShatterUpdate( void );
+void ShatterRender( void );
+
+void plat_screen_flash_render( sScreenFlashDetails *p_details );
+
+void plat_texture_splat_initialize( void );
+void plat_texture_splat_cleanup( void );
+void plat_texture_splat_render( void );
+void plat_texture_splat_reset_poly( sSplatInstanceDetails *p_details, int index );
+bool plat_texture_splat( Nx::CSector **pp_sectors, Nx::CCollStatic **pp_collision, Mth::Vector& start, Mth::Vector& end, float size, float lifetime, Nx::CTexture *p_texture, Nx::sSplatTrailInstanceDetails *p_trail_details = NULL );
+
+void plat_shatter_initialize( void );
+void plat_shatter_cleanup( void );
+void plat_shatter( CGeom *p_geom );
+void plat_shatter_update( sShatterInstanceDetails *p_details, float framelength );
+void plat_shatter_render( sShatterInstanceDetails *p_details );
+
+} // Nx
+
+#endif // __GFX_NXMISCFX_H__
diff --git a/Code/Gfx/NxModel.cpp b/Code/Gfx/NxModel.cpp
new file mode 100644
index 0000000..5b21660
--- /dev/null
+++ b/Code/Gfx/NxModel.cpp
@@ -0,0 +1,1656 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxModel.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/21/2001
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Nx
+{
+	
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_init_skeleton( int numBones )
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_set_render_mode(ERenderMode mode)
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_set_color(uint8 r, uint8 g, uint8 b, uint8 a)
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_set_visibility(uint32 mask)
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_set_active(bool active)
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_set_scale(float scaleFactor)
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones)
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_replace_texture(char* p_srcFileName, char* p_dstFileName)
+{
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_prepare_materials( void )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CModel::plat_refresh_materials( void )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CModel::plat_get_bounding_sphere()
+{
+	if ( m_numBones > 0 )
+	{
+		// the original hack for skinned models
+		return Mth::Vector(0.0f, 36.0f, 0.0f, 48.0f);		
+	}
+	else
+	{
+		// the original hack for non-skinned models
+		return Mth::Vector(0.0f, 36.0f, 0.0f, 1200.0f);		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	// Do nothing by default... 
+	// (this is only needed by the PS2 version...)
+
+	// TODO:  Set the appropriate bounding sphere that will be returned
+	// by plat_get_bounding_sphere...
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModel::CModel()
+{
+    m_numBones = 0;
+    mp_skeletonMatrices = NULL;
+
+    SetRenderMode( vNONE );
+
+	m_numGeoms = 0;
+
+	m_primaryMeshName = CRCD(0x6607dd3e,"Uninitialized");
+
+	m_active = true;
+	m_hidden = false;
+	m_scale = Mth::Vector( 1.0f, 1.0f, 1.0f );
+	m_scalingEnabled = false;
+	m_shadowEnabled = false;
+	m_doShadowVolume = false;
+
+	mp_model_lights = NULL;
+
+	m_boundingSphereCached = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModel::~CModel()
+{
+	if (mp_model_lights)
+	{
+		CLightManager::sFreeModelLights(mp_model_lights);
+	}
+
+	ClearGeoms();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+
+// GJ 10/23/02:  This function didn't seem to be used, 
+// so I commented it out for now
+
+void CModel::ResetScale()
+{
+	SetScale( Mth::Vector( 1.0f, 1.0f, 1.0f ) );
+
+	if ( mp_skeleton )
+	{
+		mp_skeleton->ResetScale();
+	}
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetSkeleton( Gfx::CSkeleton* pSkeleton )
+{
+
+	if (!mp_skeletonMatrices)
+	{
+		m_numBones = pSkeleton->GetNumBones();
+	
+		plat_init_skeleton( pSkeleton->GetNumBones() );
+	
+		mp_skeletonMatrices = pSkeleton->GetMatrices();
+	}
+	else
+	{
+		Dbg_MsgAssert(m_numBones == pSkeleton->GetNumBones(),("Mismatch in number of bones"));
+		
+	}
+
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::Render( Mth::Matrix* pMatrix, bool no_anim, Gfx::CSkeleton* pSkeleton )
+{
+	// don't display the skin, unless we're
+	// in the standard "textured" mode
+	bool is_textured = m_renderMode == vTEXTURED; 
+
+	int numGeoms = GetNumGeoms();
+	for ( int i = 0; i < numGeoms; i++ )
+	{
+		Dbg_Assert( GetGeomByIndex(i) );
+		GetGeomByIndex(i)->SetActive( is_textured && m_active && (m_geomActiveMask&(1<ScaleLocal(m_scale);
+	}
+       
+	// make sure the skeleton matches up...
+	if ( pSkeleton )					    
+	{
+		Dbg_MsgAssert( m_numBones != 0, ( "Trying to update a model with incorrect number of bones (expected %d, found %d)", m_numBones, pSkeleton->GetNumBones() ) );
+		Dbg_Assert( mp_skeletonMatrices );
+	}
+	else
+	{
+		Dbg_MsgAssert( m_numBones == 0, ( "Trying to update a model with incorrect number of bones (expected %d, found %d)", 0, m_numBones ) );
+		Dbg_Assert( mp_skeletonMatrices == NULL );
+	}
+
+    switch ( m_renderMode )
+    {
+        case vTEXTURED:
+        {
+			if ( pSkeleton )
+            {
+				(*pMatrix)[Mth::RIGHT][W] = 0.0f;
+				(*pMatrix)[Mth::UP][W] = 0.0f;
+				(*pMatrix)[Mth::AT][W] = 0.0f;
+				(*pMatrix)[Mth::POS][W] = 1.0f;
+
+				int numGeoms = GetNumGeoms();
+								
+				if ( no_anim )
+				{
+					// update root position without updating bones
+					for ( int i = 0; i < numGeoms; i++ )
+					{
+						Dbg_Assert( GetGeomByIndex(i) );
+						GetGeomByIndex(i)->Render( pMatrix, NULL, 0 );
+					}
+				}
+				else
+				{
+ 					// update both root position AND bones
+					for ( int i = 0; i < numGeoms; i++ )
+					{
+						Dbg_Assert( GetGeomByIndex(i) );
+						GetGeomByIndex(i)->Render( pMatrix, pSkeleton->GetMatrices(), pSkeleton->GetNumBones() );
+					}	
+				}
+			}
+			else
+			{
+				// the following should NOT be necessary,
+				// but it is.
+				(*pMatrix)[Mth::RIGHT][W] = 0.0f;
+				(*pMatrix)[Mth::UP][W] = 0.0f;
+				(*pMatrix)[Mth::AT][W] = 0.0f;
+				(*pMatrix)[Mth::POS][W] = 1.0f;
+				
+				int numGeoms = GetNumGeoms();
+				for ( int i = 0; i < numGeoms; i++ )
+				{
+					Dbg_Assert( GetGeomByIndex(i) );
+					GetGeomByIndex(i)->Render( pMatrix, NULL, 0 );
+				}
+			}
+        }
+        break;
+        
+		case vSKELETON:
+		{
+            if ( pSkeleton )
+            {
+				pSkeleton->Display( pMatrix, 0.5f, 0.5f, 1.0f );
+			}
+		}
+		break;
+
+        case vBBOX:
+        default:
+        {
+            Mth::Matrix matrix = *pMatrix;
+
+            // really, the AddDebugBox function should be
+            // able to draw with only one matrix, rather
+            // than matrix+pos.
+            matrix[Mth::POS] = Mth::Vector( 0, 0, 0 );
+
+            Mth::Vector pos = pMatrix->GetPos();
+
+            // set up bounding box
+            SBBox theBox;
+            theBox.m_max.Set(10.0f, 10.0f, 10.0f);
+            theBox.m_min.Set(-10.0f, -10.0f, -10.0f);    
+
+            // For now, draw a bounding box
+            Gfx::AddDebugBox( matrix, pos, &theBox, NULL, 1, NULL ); 
+		}
+        break;
+        
+		case vGOURAUD:
+        case vFLAT:
+        case vWIREFRAME:
+            Dbg_Assert( 0 );
+        break;
+
+        case vNONE:
+            // draw nothing...
+            break;
+        
+    }
+
+    return true;
+}
+                            
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetBoneMatrixData( Gfx::CSkeleton* pSkeleton )
+{
+	// GJ:  X-box doesn't copy over bone matrix data once per frame
+	// like the PS2 does...  so the following allows me to hijack the
+	// skater's model matrices and copy over the matrices
+	// from a different model... (for skater substitution in cutscenes,
+	// and perhaps for the bail board)
+
+	Dbg_Assert( pSkeleton );
+	
+	int numGeoms = GetNumGeoms();
+	for ( int i = 0; i < numGeoms; i++ )
+	{
+		Dbg_Assert( GetGeomByIndex(i) );
+		GetGeomByIndex(i)->SetBoneMatrixData( pSkeleton->GetMatrices(), pSkeleton->GetNumBones() );
+	}  
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::AddGeom(uint32 assetName, uint32 geomName, bool supportMultipleMaterialColors)
+{
+	// now that a new geom has been added to the model,
+	// consider the bounding sphere "dirty"
+	m_boundingSphereCached = false;
+
+	// for now, assume that if we're using this method of creating assets,
+	// then we'll be using the asset manager and a texDictOffset of 0
+    
+	bool success = false;
+
+	Dbg_MsgAssert( m_numGeoms < MAX_GEOMS, ( "Too many geoms for this model (max=%d)", MAX_GEOMS ) );
+
+	// the asset must already exist in the assman by this point!
+	// note that we're not loading the asset, only getting it
+	Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+
+	Nx::CMesh* pMesh = NULL;
+	pMesh = (Nx::CMesh*)ass_man->GetAsset( assetName, false );
+	if( !pMesh )
+	{
+		Dbg_MsgAssert( false,( "Could not get asset %08x", assetName ));
+	}	   		   
+
+	m_preloadedMeshNames[m_numPreloadedMeshes] = assetName;
+	m_numPreloadedMeshes++;
+
+	mp_geom[m_numGeoms] = Nx::CEngine::sInitGeom();
+
+	if ( mp_geom[m_numGeoms] )
+	{
+		mp_geom[m_numGeoms]->LoadGeomData(pMesh, this, supportMultipleMaterialColors);
+		mp_geomTexDict[m_numGeoms] = pMesh->GetTextureDictionary();
+		mp_geom[m_numGeoms]->EnableShadow(m_shadowEnabled);
+		m_geomName[m_numGeoms] = geomName;
+		m_geomActiveMask |= (1<SetModelLights(mp_model_lights);
+		}
+
+		m_numGeoms++;
+		
+		SetRenderMode( vTEXTURED );
+	}
+
+	// TODO:  The mesh file name represents
+	// mesh #0, but the mesh file name is
+	// already kind of kludge that will be
+	// cleaned up when CGeoms are properly
+	// implemented.
+	if ( m_numGeoms == 1 )
+	{
+		// doesn't have a filename because it came from a stream...
+		m_primaryMeshName = Script::GenerateCRC("data_from_stream");
+	}
+    
+	return success;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::AddGeom(const char* pMeshFileName, uint32 geomName, bool useAssetManager, uint32 texDictOffset, bool forceTexDictLookup, bool supportMultipleMaterialColors)
+{
+	// now that a new geom has been added to the model,
+	// consider the bounding sphere "dirty"
+	m_boundingSphereCached = false;
+
+    bool success = false;
+
+	Dbg_MsgAssert( m_numGeoms < MAX_GEOMS, ( "Too many geoms for this model (max=%d)", MAX_GEOMS ) );
+
+	// grabs the data from the asset manager if it's not already loaded
+	Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+
+	Nx::CMesh* pMesh = NULL;
+	if ( useAssetManager )
+	{
+		Ass::SSkinAssetLoadContext theContext;
+		theContext.forceTexDictLookup = forceTexDictLookup;
+		theContext.doShadowVolume = m_doShadowVolume;
+		theContext.texDictOffset = texDictOffset;
+
+		pMesh = (Nx::CMesh*)ass_man->LoadOrGetAsset( pMeshFileName, false, false, ass_man->GetDefaultPermanent(), 0, &theContext );
+		if( !pMesh )
+		{
+			Dbg_MsgAssert( false,( "Could not load asset %s", pMeshFileName ));
+		}	   		   
+
+		m_preloadedMeshNames[m_numPreloadedMeshes] = Script::GenerateCRC(pMeshFileName);
+		m_numPreloadedMeshes++;
+	}
+	else
+	{
+		Mem::PushMemProfile((char*)pMeshFileName);
+		pMesh = Nx::CEngine::sLoadMesh( pMeshFileName, texDictOffset, forceTexDictLookup, m_doShadowVolume );
+		Mem::PopMemProfile(/*(char*)pMeshFileName*/);
+		Dbg_MsgAssert( m_numMeshes < MAX_MESHES, ( "Too many meshes for this model (max=%d)", MAX_MESHES ) );
+		mp_mesh[m_numMeshes] = pMesh;
+		m_numMeshes++;
+	}
+
+	mp_geom[m_numGeoms] = Nx::CEngine::sInitGeom();
+
+	if ( mp_geom[m_numGeoms] )
+	{
+		mp_geom[m_numGeoms]->LoadGeomData(pMesh, this, supportMultipleMaterialColors);
+		mp_geomTexDict[m_numGeoms] = pMesh->GetTextureDictionary();
+		mp_geom[m_numGeoms]->EnableShadow(m_shadowEnabled);
+		m_geomName[m_numGeoms] = geomName;
+		m_geomActiveMask |= (1<SetModelLights(mp_model_lights);
+		}
+
+		m_numGeoms++;
+		
+		SetRenderMode( vTEXTURED );
+	}
+
+	// TODO:  The mesh file name represents
+	// mesh #0, but the mesh file name is
+	// already kind of kludge that will be
+	// cleaned up when CGeoms are properly
+	// implemented.
+	if ( m_numGeoms == 1 )
+	{
+		m_primaryMeshName = Script::GenerateCRC(pMeshFileName);
+	}
+    
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::AddGeom(CGeom* pGeom, uint32 geomName)
+{
+	// now that a new geom has been added to the model,
+	// consider the bounding sphere "dirty"
+	m_boundingSphereCached = false;
+
+    bool success = false;
+
+	Dbg_MsgAssert( m_numGeoms < MAX_GEOMS, ( "Too many geoms for this model (max=%d)", MAX_GEOMS ) );
+
+	mp_geom[m_numGeoms] = pGeom;
+	mp_geom[m_numGeoms]->EnableShadow(m_shadowEnabled);
+	m_geomName[m_numGeoms] = geomName;
+	m_geomActiveMask |= (1<SetModelLights(mp_model_lights);
+	}
+
+	m_numGeoms++;
+		
+	SetRenderMode( vTEXTURED );
+
+	// TODO:  The mesh file name represents
+	// mesh #0, but the mesh file name is
+	// already kind of kludge that will be
+	// cleaned up when CGeoms are properly
+	// implemented.
+	if ( m_numGeoms == 1 )
+	{
+		m_primaryMeshName = CRCD(0x9facc1a5,"TempMeshName");
+	}
+    
+	// Maybe need to pass in the collision volume instead?
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::RemovePolys(void)
+{
+	uint32 polyRemovalMask = GetPolyRemovalMask();
+
+	HidePolys( polyRemovalMask );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CModel::GetPolyRemovalMask()
+{
+	uint32 polyRemovalMask = 0;
+	
+	// loop through the meshes to collect the poly removal masks
+	for ( short i = 0; i < m_numMeshes; i++ )
+	{
+		polyRemovalMask |= ( mp_mesh[i]->GetRemovalMask() );
+	}
+
+	// also account for cutscene objects, which are loaded
+	// through the asset manager
+	for ( short i = 0; i < m_numPreloadedMeshes; i++ )
+	{
+		Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+		Nx::CMesh* pMesh = (Nx::CMesh*)ass_man->GetAsset( m_preloadedMeshNames[i], true );
+		if( pMesh )
+		{
+			polyRemovalMask |= pMesh->GetRemovalMask();
+		}
+	}
+	
+	return polyRemovalMask;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::HidePolys( uint32 polyRemovalMask )
+{
+	// loop through the existing models, and remove existing polys
+	for ( short i = 0; i < m_numGeoms; i++ )
+	{
+		mp_geom[i]->HidePolys( polyRemovalMask );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::CreateModelLights()
+{
+	Dbg_MsgAssert( !mp_model_lights, ("Model lights already exist") );
+
+	mp_model_lights = Nx::CLightManager::sCreateModelLights();
+
+	for ( short i = 0; i < m_numGeoms; i++ )
+	{
+		mp_geom[i]->SetModelLights( mp_model_lights );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::DestroyModelLights()
+{
+	Dbg_MsgAssert( mp_model_lights, ("No Model lights to destroy") );
+
+	Nx::CLightManager::sFreeModelLights( mp_model_lights );
+
+	mp_model_lights = NULL;
+	for ( short i = 0; i < m_numGeoms; i++ )
+	{
+		mp_geom[i]->SetModelLights( NULL );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModelLights * CModel::GetModelLights() const
+{
+	return mp_model_lights;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetRenderMode(ERenderMode mode)
+{
+    bool success = false;
+
+    if (mode != m_renderMode)
+    {
+        success = plat_set_render_mode(mode);
+        
+        if (success)
+        {
+            m_renderMode = mode;
+        }
+    }
+
+    return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ERenderMode CModel::GetRenderMode()
+{
+	return m_renderMode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::Hide( bool should_hide )
+{   
+	m_hidden = should_hide;
+	
+	SetActive( !should_hide );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetActive( bool active )
+{
+	if( m_hidden && active )
+	{
+		return false;									
+	}
+
+	// (Mick) if the active state is already set correctly, then just return
+	// (this assumes that the geoms are correctly set already)	
+	if (m_active == active)
+	{
+		return true;
+	}
+
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		Dbg_Assert( GetGeomByIndex(i) );
+		GetGeomByIndex(i)->SetActive( active );
+	}
+	
+	m_active = active;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom* CModel::GetGeom(uint32 geomName)
+{
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		// TODO:  there might be multiple geoms with
+		// the same checksum...
+		if ( geomName == m_geomName[i] )
+		{
+			Dbg_Assert( GetGeomByIndex( i ) );
+			return GetGeomByIndex( i );
+		}
+	}
+
+    return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::ReplaceTexture( uint32 geomName, const char* p_SrcTextureName, const char* p_DstTextureName )
+{
+	bool success = false;
+	
+	bool applyToAll = (geomName == vREPLACE_GLOBALLY);
+	
+	plat_prepare_materials();
+
+	// The lower level code is expecting the .PNG extension
+	// for the source texture name, so add it if's not there
+	char src_texture_name[512];
+	strcpy( src_texture_name, p_SrcTextureName );
+	Str::LowerCase( src_texture_name );
+	char* pSrcExt = strstr( src_texture_name, ".png" );
+	if ( !pSrcExt )
+	{
+		strcat( src_texture_name, ".png" );
+	}
+
+	// The lower level code is NOT expecting the .PNG extension
+	// for the destination texture name, so strip it out if it's 
+	// there (the artists sometimes accidentally leave the .PNG in there)...
+	char dest_texture_name[512];
+	strcpy( dest_texture_name, p_DstTextureName );
+	Str::LowerCase( dest_texture_name );
+	char* pDstExt = strstr( dest_texture_name, ".png" );
+	if ( pDstExt )
+	{
+		*pDstExt = NULL;
+	}
+
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		// m_geomName[i] = 0 will handle cutscene objects,
+		// for texture replacement on the hi-res heads
+
+		if ( geomName == m_geomName[i] || applyToAll || m_geomName[i] == 0 )
+		{
+			if ( mp_geomTexDict[i] && mp_geomTexDict[i]->ReplaceTexture( src_texture_name, dest_texture_name) )
+			{
+				success = true;
+			}
+		}
+	}
+
+	if ( !success && Script::GetInteger( CRCD(0x2a648514,"cas_artist") ) )
+	{
+		Dbg_Message( "Texture replacement of %s by %s failed!", p_SrcTextureName, dest_texture_name );
+	}
+
+	plat_refresh_materials();
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::ReplaceTexture( uint32 geomName, const char* p_SrcFileName, uint32 DstChecksum )
+{
+	bool success = false;
+	
+	bool applyToAll = (geomName == vREPLACE_GLOBALLY);
+	
+	plat_prepare_materials();
+
+	// The lower level code is expecting the .PNG extension
+	// for the source texture name, so add it if's not there
+	char src_texture_name[512];
+	strcpy( src_texture_name, p_SrcFileName );
+	Str::LowerCase( src_texture_name );
+	char* pSrcExt = strstr( src_texture_name, ".png" );
+	if ( !pSrcExt )
+	{
+		strcat( src_texture_name, ".png" );
+	}
+
+	CTexture *p_dest_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(DstChecksum);
+	if (!p_dest_texture)
+	{
+		Dbg_MsgAssert(0, ("Can't find destination texture %s", Script::FindChecksumName(DstChecksum)));
+	}
+
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		if ( geomName == m_geomName[i] || applyToAll )
+		{
+			if ( mp_geomTexDict[i] && mp_geomTexDict[i]->ReplaceTexture( src_texture_name, p_dest_texture) )
+			{
+				success = true;
+			}
+		}
+	}
+
+	if ( !success && Script::GetInteger( CRCD(0x2a648514,"cas_artist") ) )
+	{
+		Dbg_Message( "Texture replacement of %s by %s failed!", p_SrcFileName, Script::FindChecksumName(DstChecksum) );
+	}
+
+	plat_refresh_materials();
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::ClearColor( uint32 geomName )
+{
+	bool success = false;
+	
+	bool applyToAll = (geomName == vREPLACE_GLOBALLY);
+
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		if ( geomName == m_geomName[i] || applyToAll )
+		{
+			Dbg_MsgAssert( !mp_geom[i]->MultipleColorsEnabled(), ( "Wasn't expecting multiple material colors" ) );
+
+			mp_geom[i]->ClearColor();
+			success = true;
+		}
+	}
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::ModulateColor( uint32 geomName, float h, float s, float v )
+{
+	// hue = [0.0, 360.0f)
+	// satuation = [0.0, 1.0]
+	// value = [0.0, 1.0]
+
+	bool success = false;
+	
+	bool applyToAll = (geomName == vREPLACE_GLOBALLY);
+	
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		if ( geomName == m_geomName[i] || applyToAll )
+		{
+			if ( mp_geom[i]->MultipleColorsEnabled() )
+			{
+				// ignore geoms with the "multiple colors" flag
+				// (you're supposed to use SetColorWithParams() for that)
+			}
+			else
+			{
+				float r, g, b;
+				Gfx::HSVtoRGB( &r, &g, &b, h, s, v );
+
+				Image::RGBA theColor;
+				theColor.r = (unsigned char)( r * 255.0f + 0.5f );
+				theColor.g = (unsigned char)( g * 255.0f + 0.5f );
+				theColor.b = (unsigned char)( b * 255.0f + 0.5f );
+				theColor.a = 255;
+
+				Dbg_MsgAssert( !mp_geom[i]->MultipleColorsEnabled(), ( "Wasn't expecting multiple material colors" ) );
+
+				mp_geom[i]->SetColor( theColor );
+				success = true;
+			}
+		}
+	}
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetUVOffset(uint32 mat_checksum, int pass, float u_offset, float v_offset)
+{
+	bool success = false;
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		success |= mp_geom[i]->SetUVWibbleOffsets(mat_checksum, pass, u_offset, v_offset);
+	}
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetUVMatrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat)
+{
+	bool success = false;
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		success |= mp_geom[i]->SetUVMatrix(mat_checksum, pass, mat);
+	}
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::AllocateUVMatrixParams(uint32 mat_checksum, int pass)
+{
+	bool success = false;
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		success |= mp_geom[i]->AllocateUVMatrixParams(mat_checksum, pass);
+	}
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetColor( Script::CStruct* pParams, float h, float s, float v )
+{
+	// GJ:  This is a generic way of changing the colors in a model,
+	// without having to know whether it has a separate color per material
+	
+	// GJ TODO:  Ideally, this function would not reference CStruct,
+	// but would rather be passed the appropriate material/pass
+	// parameters from a higher level...
+
+	bool success = false;
+
+	uint32 geomName;
+	pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &geomName, Script::ASSERT );
+
+	float r, g, b;
+	Gfx::HSVtoRGB( &r, &g, &b, h, s, v );
+
+	Image::RGBA theColor;
+	theColor.r = (unsigned char)( r * 255.0f + 0.5f );
+	theColor.g = (unsigned char)( g * 255.0f + 0.5f );
+	theColor.b = (unsigned char)( b * 255.0f + 0.5f );
+	theColor.a = 255;
+
+	bool applyToAll = (geomName == vREPLACE_GLOBALLY);
+
+	int numGeoms = GetNumGeoms();
+	for ( int i = 0; i < numGeoms; i++ )
+	{
+		uint32 mat_checksum;
+		int pass = 0;
+		Script::CArray* pMaterialArray;
+		
+		if ( !mp_geom[i]->MultipleColorsEnabled() )
+		{
+			if ( geomName == m_geomName[i] || applyToAll )
+			{
+				mp_geom[i]->SetColor( theColor );
+				success = true;
+			}
+		}
+		else if ( pParams->GetArray( CRCD(0x64e8e94a,"materials"), &pMaterialArray, Script::NO_ASSERT ) )
+		{
+			for ( uint32 j = 0; j < pMaterialArray->GetSize(); j++ )
+			{
+				Script::CStruct* pMaterialStruct = pMaterialArray->GetStructure( j );
+				pMaterialStruct->GetChecksum( NONAME, &mat_checksum, Script::ASSERT );
+				pMaterialStruct->GetInteger( NONAME, &pass, Script::ASSERT ); 
+
+				success |= mp_geom[i]->SetMaterialColor(mat_checksum, pass, theColor);
+			}
+		}
+		else if ( pParams->GetChecksum( CRCD(0x83418a6a,"material"), &mat_checksum, Script::NO_ASSERT )	)
+		{
+			Script::CArray* pPassArray;
+			if ( pParams->GetArray( CRCD(0x318f2bdb,"pass"), &pPassArray, Script::NO_ASSERT ) )
+			{
+				for ( uint32 j = 0; j < pPassArray->GetSize(); j++ )
+				{
+					success |= mp_geom[i]->SetMaterialColor(mat_checksum, pPassArray->GetInteger(j), theColor);
+				}
+			}
+			else if ( pParams->GetInteger( CRCD(0x318f2bdb,"pass"), &pass, Script::ASSERT ) )
+			{
+				success |= mp_geom[i]->SetMaterialColor(mat_checksum, pass, theColor);
+			}
+		}
+		
+		// kludge for changing skin color
+		// (ideally this would be in script, but we would have to uglify the
+		// code to get around some of the model-building operations that
+		// currently overwrite the body substructure, such as when
+		// the head forces the body to a specific skin tone)
+		if ( ( geomName == CRCD(0x650fab6d,"skater_m_head") 
+			   || geomName == CRCD(0x0fc85bae,"skater_f_head") )
+			 && m_geomName[i] == CRCD(0x2457f44d,"body") )
+		{
+			Dbg_MsgAssert( !mp_geom[i]->MultipleColorsEnabled(), ( "Wasn't expecting color per material on body geom" ) );
+			this->ModulateColor( CRCD(0x2457f44d,"body"), h, s, v );
+		}
+	}	
+	
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::ClearColor( Script::CStruct* pParams )
+{
+	// GJ:  This is a generic way of changing the colors in a model,
+	// without having to know whether it has a separate color per material
+	
+	bool success = false;
+
+	uint32 geomName;
+	pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &geomName, Script::ASSERT );
+
+	bool applyToAll = (geomName == vREPLACE_GLOBALLY);
+
+	int numGeoms = GetNumGeoms();
+	for ( int i = 0; i < numGeoms; i++ )
+	{
+		uint32 mat_checksum;
+		int pass = 0;
+		Script::CArray* pMaterialArray;
+		
+		if ( !mp_geom[i]->MultipleColorsEnabled() )
+		{
+			if ( geomName == m_geomName[i] || applyToAll )
+			{
+				mp_geom[i]->ClearColor();
+				success = true;
+			}
+		}
+		else if ( pParams->GetArray( CRCD(0x64e8e94a,"materials"), &pMaterialArray, Script::NO_ASSERT ) )
+		{
+			Image::RGBA theColor;
+			theColor.r = 128;
+			theColor.g = 128;
+			theColor.b = 128;
+			theColor.a = 128;
+
+			for ( uint32 j = 0; j < pMaterialArray->GetSize(); j++ )
+			{
+				Script::CStruct* pMaterialStruct = pMaterialArray->GetStructure( j );
+				pMaterialStruct->GetChecksum( NONAME, &mat_checksum, Script::ASSERT );
+				pMaterialStruct->GetInteger( NONAME, &pass, Script::ASSERT ); 
+
+				success |= mp_geom[i]->SetMaterialColor(mat_checksum, pass, theColor);
+			}
+		}
+		else if ( pParams->GetChecksum( CRCD(0x83418a6a,"material"), &mat_checksum, Script::NO_ASSERT )	)
+		{
+			Image::RGBA theColor;
+			theColor.r = 128;
+			theColor.g = 128;
+			theColor.b = 128;
+			theColor.a = 128;
+			
+			Script::CArray* pPassArray;
+			if ( pParams->GetArray( CRCD(0x318f2bdb,"pass"), &pPassArray, Script::NO_ASSERT ) )
+			{
+				for ( uint32 j = 0; j < pPassArray->GetSize(); j++ )
+				{
+					success |= mp_geom[i]->SetMaterialColor(mat_checksum, pPassArray->GetInteger(j), theColor);
+				}
+			}
+			else if ( pParams->GetInteger( CRCD(0x318f2bdb,"pass"), &pass, Script::ASSERT ) )
+			{
+				success |= mp_geom[i]->SetMaterialColor(mat_checksum, pass, theColor);
+			}
+		}
+
+		// kludge for changing skin color
+		if ( ( geomName == CRCD(0x650fab6d,"skater_m_head") 
+			   || geomName == CRCD(0x0fc85bae,"skater_f_head") )
+			 && m_geomName[i] == CRCD(0x2457f44d,"body") )
+		{
+			Dbg_MsgAssert( !mp_geom[i]->MultipleColorsEnabled(), ( "Wasn't expecting color per material on body geom" ) );
+			mp_geom[i]->ClearColor();
+			success = true;
+		}
+	}
+	
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom* CModel::GetGeomByIndex(int index)
+{
+	Dbg_Assert( index >= 0 && index < m_numGeoms );
+
+    return mp_geom[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CModel::GetGeomNameByIndex(int index)
+{
+	Dbg_Assert( index >= 0 && index < m_numGeoms );
+
+    return m_geomName[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict* CModel::GetTexDictByIndex(int index)
+{
+	Dbg_Assert( index >= 0 && index < m_numGeoms );
+
+    return mp_geomTexDict[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CModel::GetNumGeoms() const
+{
+    return m_numGeoms;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CModel::GetGeomActiveMask() const
+{
+	// K: Used by replay code.
+	return m_geomActiveMask;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::SetGeomActiveMask(uint32 mask)
+{
+	// K: Used by replay code.
+	m_geomActiveMask=mask;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::Finalize()
+{
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		Dbg_Assert( GetGeomByIndex(i) );
+		GetGeomByIndex(i)->Finalize();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::ClearGeoms()
+{
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		Dbg_Assert( GetGeomByIndex(i) );
+		Nx::CEngine::sUninitGeom( GetGeomByIndex(i) );
+	}
+
+	m_numGeoms = 0;
+
+	// eliminate all the meshes
+	for ( short i = 0; i < m_numMeshes; i++ )
+	{
+		Nx::CEngine::sUnloadMesh( mp_mesh[i] );
+	}
+
+	m_numMeshes = 0;
+
+	m_numPreloadedMeshes = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::HideGeom( uint32 geomName, bool hidden )
+{
+	bool success = false;
+
+	bool applyToAll = (geomName == vREPLACE_GLOBALLY);
+	
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		if ( (geomName == m_geomName[i]) || applyToAll )
+		{
+			if ( hidden )
+			{
+				m_geomActiveMask &= ~(1<SetColor(rgba);
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetVisibility(uint32 mask)
+{
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		Dbg_Assert( GetGeomByIndex(i) );
+		GetGeomByIndex(i)->SetVisibility(mask);
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::SetScale( const Mth::Vector& scale )
+{
+	m_scale = scale;
+	
+	m_scalingEnabled = ( scale[X] != 1.0f || scale[Y] != 1.0f || scale[Z] != 1.0f );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::EnableScaling( bool enabled )
+{
+	m_scalingEnabled = enabled;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::EnableShadow( bool enabled )
+{
+	m_shadowEnabled = enabled;
+
+	int numGeoms = GetNumGeoms();
+	for ( short i = 0; i < numGeoms; i++ )
+	{
+		Dbg_Assert( GetGeomByIndex(i) );
+		GetGeomByIndex(i)->EnableShadow(enabled);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CModel::GetNumObjectsInHierarchy()
+{
+	// This function only works with single-CMesh items
+	if ( m_numPreloadedMeshes == 1 )
+	{
+		Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+		Nx::CMesh* pMesh = (Nx::CMesh*)ass_man->GetAsset( m_preloadedMeshNames[0] );
+		Dbg_MsgAssert( pMesh, ( "Couldn't find preloaded mesh[0] to get hierarchy from" ) );
+		return pMesh->GetNumObjectsInHierarchy();
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CHierarchyObject* CModel::GetHierarchy()
+{
+	// This function only works with single-CMesh items
+	if ( m_numPreloadedMeshes == 1 )
+	{
+		Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+		Nx::CMesh* pMesh = (Nx::CMesh*)ass_man->GetAsset( m_preloadedMeshNames[0] );
+		Dbg_MsgAssert( pMesh, ( "Couldn't find preloaded mesh[0] to get hierarchy from" ) );
+		return pMesh->GetHierarchy();
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CModel::GetBoundingSphere()
+{
+	// unless the dirty flag has been set
+	if ( m_boundingSphereCached )
+	{
+		return m_boundingSphere;
+	}
+
+	// recalculate the bounding sphere
+	m_boundingSphere = plat_get_bounding_sphere();
+	m_boundingSphereCached = true;
+
+	return m_boundingSphere;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::SetBoundingSphere( const Mth::Vector& boundingSphere )
+{
+	m_boundingSphereCached = false;
+
+	plat_set_bounding_sphere( boundingSphere );
+}					  
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CModel::ApplyFaceTexture( Gfx::CFaceTexture* pFaceTexture, const char* pSrcTexture, uint32 partChecksumToReplace )
+{
+	bool success = false;
+
+	Dbg_Assert( pFaceTexture );
+	Dbg_Assert( pFaceTexture->IsValid() );
+
+	//-------------------------------------------------------------
+	// step 1:  load up the temporary texture from the appearance's CFaceTexture
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+	bool alloc_vram = true;
+	uint32 temp_checksum = CRCD(0xb00b0dc0,"dummy");
+	Nx::CTexture* p_temp_texture = Nx::CTexDictManager::sp_sprite_tex_dict->LoadTextureFromBuffer(pFaceTexture->GetTextureData(), pFaceTexture->GetTextureSize(), temp_checksum, true, alloc_vram, false);
+	Dbg_Assert( p_temp_texture );
+
+	Nx::CTexture* p_temp_overlay = Nx::CTexDictManager::sp_sprite_tex_dict->LoadTexture(pFaceTexture->GetOverlayTextureName(), alloc_vram);
+	Dbg_Assert( p_temp_overlay );
+	
+	Mem::Manager::sHandle().PopContext();
+	//-------------------------------------------------------------
+
+	//-------------------------------------------------------------
+	// step 2:  massage the temporary texture here, based on the appearance's CFaceTexture
+	Nx::SFacePoints originalModelFacePoints;
+	Script::CStruct* pModelFacePoints = Script::GetStructure( CRCD(0xa435752c,"original_model_face_points"), Script::ASSERT );
+	GetFacePointsStruct( originalModelFacePoints, pModelFacePoints );
+	Nx::CFaceTexMassager::sSetModelFacePoints( originalModelFacePoints );
+
+	Nx::CFaceTexMassager::sSetFaceTextureOverlay( p_temp_overlay );
+
+	// this speeds things up a little because the palette doesn't get rebuilt
+	// (the image is not accurate, but it does end up matching the skintone more)
+	//bool do_palette_gen = false;
+	bool do_palette_gen = pFaceTexture->GetFacePoints().m_adjust_hsv;		// Rebuild the palette if HSV is modified
+
+	// this is a test for Nolan to see if the colors match better
+	// when the palette is generated
+//	do_palette_gen = Script::GetInt( CRCD(0x8b32fff0,"do_palette_gen"), Script::NO_ASSERT );
+
+	#ifdef	__NOPT_ASSERT__
+	bool massage_success = 
+	#endif
+	Nx::CFaceTexMassager::sMassageTexture( p_temp_texture,
+										   pFaceTexture->GetFacePoints(),
+										   do_palette_gen );
+
+	Dbg_Assert( massage_success );
+	//-------------------------------------------------------------
+
+	//-------------------------------------------------------------
+	// step 3:  do texture replacement on the actual model
+	Dbg_Assert( pSrcTexture );
+	success = this->ReplaceTexture( partChecksumToReplace, pSrcTexture, p_temp_texture->GetChecksum() );
+	//-------------------------------------------------------------
+
+	//-------------------------------------------------------------
+	// step 4:  get rid of the temporary texture
+	Nx::CTexDictManager::sp_sprite_tex_dict->UnloadTexture( p_temp_texture );
+	Nx::CTexDictManager::sp_sprite_tex_dict->UnloadTexture( p_temp_overlay );
+	//-------------------------------------------------------------
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CModel::EnableShadowVolume( bool enabled )
+{
+	// if true, any future geoms added to the 
+	// model will use shadow volumes
+	m_doShadowVolume = enabled;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+
diff --git a/Code/Gfx/NxModel.h b/Code/Gfx/NxModel.h
new file mode 100644
index 0000000..de70d5a
--- /dev/null
+++ b/Code/Gfx/NxModel.h
@@ -0,0 +1,220 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxModel.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/21/2001
+//****************************************************************************
+
+#ifndef	__GFX_NXMODEL_H__
+#define	__GFX_NXMODEL_H__
+
+                           
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+// Forward declarations
+namespace Gfx
+{
+	class	CFaceTexture;
+    class   CSkeleton;
+};
+
+namespace Script
+{
+	class	CStruct;
+};
+                  
+namespace Nx
+{
+
+// Forward declarations
+class	CGeom;
+class	CHierarchyObject;
+class	CMesh;
+class	CTexDict;
+class	CModelLights;
+
+// TODO:  Insert class description here...
+// This is the machine-independent representation of a model,
+// which is the graphical representation of some object
+
+enum ERenderMode
+{
+    vTEXTURED,
+	vSKELETON,
+    vGOURAUD,
+    vFLAT,
+    vWIREFRAME,
+    vBBOX,
+    vNONE
+};
+
+class CModel : public Spt::Class
+{
+public:
+	enum
+	{
+		// special flag for global texture replacement
+		vREPLACE_GLOBALLY = 0xc4e78e22			// all
+	};
+
+public:
+    // The basic interface to the model
+    // this is the machine independent part	
+    // machine independent range checking, etc can go here	
+	CModel();
+    virtual				~CModel();
+
+public:
+	bool				SetBoneMatrixData( Gfx::CSkeleton* pSkeleton );
+    bool                Render( Mth::Matrix* pMatrix, bool no_anim, Gfx::CSkeleton* pSkeleton );
+    uint32				GetFileName() { return m_primaryMeshName; }
+
+    // Grabs a specific geom object so that you can do part-specific operations
+    CGeom*     			GetGeom(uint32 geomName);
+	CGeom*				GetGeomByIndex(int index);
+	uint32				GetGeomNameByIndex(int index);
+	CTexDict*			GetTexDictByIndex(int index);
+	int					GetNumGeoms() const;
+	void				ClearGeoms();
+	void				Finalize();
+	uint32				GetGeomActiveMask() const;
+	void				SetGeomActiveMask(uint32 mask);
+	
+	bool				AddGeom(uint32 assetName, uint32 geomName, bool supportMultipleMaterialColors=false);
+	bool				AddGeom(const char* pMeshName, uint32 geomName, bool useAssetManager, uint32 texDictOffset=0, bool forceTexDictLookup=false, bool supportMultipleMaterialColors=false);
+	bool				HideGeom( uint32 geomName, bool hidden );
+    bool				GeomHidden( uint32 geomName );
+
+	// probably need to pass the collision as well
+	bool				AddGeom(Nx::CGeom* pGeom, uint32 geomName);
+
+	// model lights
+	bool				CreateModelLights();
+	bool				DestroyModelLights();
+	CModelLights *		GetModelLights() const;
+
+public:
+    bool                SetRenderMode(ERenderMode mode);
+	ERenderMode			GetRenderMode();
+	bool				SetColor(uint8 r, uint8 g, uint8 b, uint8 a);
+	bool				SetVisibility(uint32 mask);
+	void				ResetScale();
+	bool				SetScale(const Mth::Vector& scale);
+	Mth::Vector			GetScale() {return m_scale;}
+	void				EnableScaling( bool enabled );
+	bool				IsScalingEnabled() { return m_scalingEnabled; }
+	void				Hide( bool should_hide = true );
+	bool				IsHidden() { return m_hidden; }
+	bool				SetActive(bool active);
+	bool				GetActive() {return m_active && !m_hidden;}    
+	bool                ReplaceTexture( uint32 geomName, const char* p_SrcFileName, const char* p_DstFileName );
+	bool                ReplaceTexture( uint32 geomName, const char* p_SrcFileName, uint32 DstChecksum );
+	bool				SetUVOffset(uint32 mat_checksum, int pass, float u_offset, float v_offset);
+	bool				SetUVMatrix(uint32 mat_checksum, int pass, const Mth::Matrix &mat);
+	bool				AllocateUVMatrixParams(uint32 mat_checksum, int pass);
+    bool                SetSkeleton( Gfx::CSkeleton* pSkeleton );
+    Mth::Matrix*		GetBoneTransforms();
+    void				EnableShadow(bool enabled);
+    bool				RemovePolys();
+	uint32 				GetPolyRemovalMask();
+	void 				HidePolys( uint32 polyRemovalMask );
+
+	// way of changing object color (for general objects)
+	bool				ModulateColor( uint32 geomName, float h, float s, float v );
+	bool				ClearColor( uint32 geomName );
+	
+	// generic way of changing colors, without having to know whether
+	// you've got material colors or object colors...  (used from
+	// model builder)
+	bool				SetColor( Script::CStruct* pParams, float h, float s, float v );
+	bool				ClearColor( Script::CStruct* pParams );
+	
+	// apply face texture (kind of like a texture replacement)
+	bool				ApplyFaceTexture( Gfx::CFaceTexture* pFaceTexture, const char* pSrcTexture, uint32 geomName );
+
+    // phase this out, eventually...
+    int                 GetNumBones() {return m_numBones;}      // not really happy about this
+
+protected:
+    ERenderMode         m_renderMode;
+    
+protected:
+	// GJ:  skeleton related stuff...  I'm not sure
+	// if this is the appropriate place to store this
+	// data...  maybe model should be able to query the 
+	// composite object for this data?
+	int                 m_numBones;
+    Mth::Matrix*        mp_skeletonMatrices;
+
+public:
+	Mth::Vector			GetBoundingSphere();
+	void				SetBoundingSphere( const Mth::Vector& boundingSphere );
+
+	// for skeletal models, such as cars
+	Nx::CHierarchyObject* GetHierarchy();
+	int					GetNumObjectsInHierarchy();
+
+	void				EnableShadowVolume(bool enabled);
+
+protected:
+	// TODO:  replace this with a linked list
+	enum
+	{
+		MAX_GEOMS = 18
+	};
+	CGeom*				mp_geom[MAX_GEOMS];
+	Nx::CTexDict*		mp_geomTexDict[MAX_GEOMS];
+	uint32				m_geomName[MAX_GEOMS];
+	uint32				m_geomActiveMask;
+	Mth::Vector			m_scale;
+
+	CModelLights*		mp_model_lights;
+
+	enum
+	{
+		MAX_MESHES = 18
+	};
+
+	CMesh*				mp_mesh[MAX_MESHES];
+	uint32				m_preloadedMeshNames[MAX_MESHES];
+	uint32				m_primaryMeshName;
+
+	// remember the bounding sphere
+	Mth::Vector			m_boundingSphere;
+	
+	bool				m_boundingSphereCached:1;
+	bool				m_active:1;
+	bool				m_hidden:1;
+	bool				m_scalingEnabled:1;
+	bool				m_shadowEnabled:1;
+	bool				m_doShadowVolume:1;
+
+	short				m_numGeoms;
+	short				m_numMeshes;
+	short				m_numPreloadedMeshes;
+	
+private:
+    // The virtual functions will have a stub implementation
+    // in p_nxmodel.cpp
+	virtual	bool		plat_init_skeleton( int numBones );
+	virtual	bool		plat_set_render_mode(ERenderMode mode);
+	virtual	bool		plat_set_color(uint8 r, uint8 g, uint8 b, uint8 a);
+	virtual	bool		plat_set_visibility(uint32 mask);
+	virtual	bool		plat_set_active(bool active);
+    virtual bool        plat_set_scale(float scaleFactor);
+    virtual bool        plat_replace_texture(char* p_srcFileName, char* p_dstFileName);
+	virtual bool		plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones);
+	virtual bool		plat_prepare_materials( void );
+	virtual bool		plat_refresh_materials( void );
+	virtual Mth::Vector	plat_get_bounding_sphere();
+	virtual void		plat_set_bounding_sphere( const Mth::Vector& boundingSphere );
+};
+
+}
+
+#endif // __GFX_NXMODEL_H__
+
diff --git a/Code/Gfx/NxNewParticle.cpp b/Code/Gfx/NxNewParticle.cpp
new file mode 100644
index 0000000..d11fbc6
--- /dev/null
+++ b/Code/Gfx/NxNewParticle.cpp
@@ -0,0 +1,517 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		NxNewParticle.cpp										**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	New parametric particle system							**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static Mth::Vector extrema( Mth::Vector p0, Mth::Vector p1, Mth::Vector p2, float t1, float t2 )
+{
+	Mth::Vector e1, e2, u, a, p;
+	float		q;
+
+    e1	= t1 * ( p2 - p0 );
+    e2	= t2 * ( p1 - p0 );
+	q	= 1.0f / ( t1 * t2 * ( t2 - t1 )); 
+	u	= ( t2 * e2 - t1 * e1 ) * q;			// Intitial velocity.
+	a	= ( e1 - e2 ) * q;						// Twice initial acceleration.
+
+	for( int i = 0; i < 3; i++ )
+    {
+		float t	= -0.5f * u[i] / a[i];				// Time of extremum for given coordinate.
+		p[i]	= p0[i]  + u[i] * t + a[i] * t * t;	// Value of coordinate at time t.
+    }
+	p[3] = 0.0f;
+
+	return p;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static Mth::Vector componentwise_min( Mth::Vector& p0, Mth::Vector& p1 )
+{
+	Mth::Vector r = p0;
+
+	if( p1[X] < p0[X] )
+		r[X] = p1[X];
+	if( p1[Y] < p0[Y] )
+		r[Y] = p1[Y];
+	if( p1[Z] < p0[Z] )
+		r[Z] = p1[Z];
+	if( p1[W] < p0[W] )
+		r[W] = p1[W];
+
+	return r;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static Mth::Vector componentwise_max( Mth::Vector& p0, Mth::Vector& p1 )
+{
+	Mth::Vector r = p0;
+
+	if( p1[X] > p0[X] )
+		r[X] = p1[X];
+	if( p1[Y] > p0[Y] )
+		r[Y] = p1[Y];
+	if( p1[Z] > p0[Z] )
+		r[Z] = p1[Z];
+	if( p1[W] > p0[W] )
+		r[W] = p1[W];
+
+	return r;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+CParticleParams::CParticleParams( void )
+{
+	int i;
+
+	m_Type = CRCD(0xdedfc057,"NEWFLAT");
+	m_UseMidpoint = true;
+	m_LocalCoord = false;
+	for( i = 0; i < vNUM_BOXES; i++ )
+	{
+		m_Radius[i] = 1.0f;
+		m_RadiusSpread[i] = 0.0f;
+
+		m_BoxPos[i][X] = 0.0f;
+		m_BoxPos[i][Y] = 0.0f;
+		m_BoxPos[i][Z] = 0.0f;
+		m_BoxPos[i][W] = 0.0f;
+
+		m_BoxDims[i][X] = 20.0f;
+		m_BoxDims[i][Y] = 20.0f;
+		m_BoxDims[i][Z] = 20.0f;
+		m_BoxDims[i][W] = 0.0f;
+
+		m_LocalBoxPos[i][X] = 0.0f;
+		m_LocalBoxPos[i][Y] = 0.0f;
+		m_LocalBoxPos[i][Z] = 0.0f;
+		m_LocalBoxPos[i][W] = 0.0f;
+
+		m_Color[i].r = 128;
+		m_Color[i].g = 128;
+		m_Color[i].b = 128;
+		m_Color[i].a = 128;
+	}
+
+	m_MaxStreams = 2;
+	m_EmitRate = 300.0f;
+	m_Lifetime = 4.0f;
+	m_MidpointPct = 50.0f;
+	
+    m_UseMidcolor = false;
+	m_ColorMidpointPct = 50.0f;
+	m_BlendMode = CRCD(0xa86285a1,"FixAdd");
+	m_AlphaCutoff = 1;
+	m_Texture = 0;
+	m_FixedAlpha = 0;
+
+	m_LODDistance1 = 400;
+	m_LODDistance2 = 401;
+	m_SuspendDistance = 0;
+
+	m_Hidden = false;
+	m_Suspended = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNewParticle::CNewParticle( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNewParticle::~CNewParticle( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::CalculateBoundingVolumes( void )
+{
+	// Calculate the bounding box.
+	Mth::Vector p0, p1, p2;				// box centres
+	Mth::Vector s0, s1, s2;				// box dimensions
+	Mth::Vector p0_lo, p1_lo, p2_lo;	// corresponding minimum possible coords
+	Mth::Vector p0_hi, p1_hi, p2_hi;	// corresponding maximum possible coords
+	Mth::Vector AABB_min, AABB_max;		// AABB corners
+	Mth::Vector one( 1.0f, 1.0f, 1.0f, 1.0f );
+
+	p0 = m_params.m_BoxPos[0];
+	p1 = m_params.m_BoxPos[1];
+	p2 = m_params.m_BoxPos[2];
+
+	p0[W] = m_params.m_Radius[0];
+	p1[W] = m_params.m_Radius[1];
+	p2[W] = m_params.m_Radius[2];
+
+	s0 = 0.5f * m_params.m_BoxDims[0];
+	s1 = 0.5f * m_params.m_BoxDims[1];
+	s2 = 0.5f * m_params.m_BoxDims[2];
+
+	s0[W] = 0.5f * m_params.m_RadiusSpread[0];
+	s1[W] = 0.5f * m_params.m_RadiusSpread[1];
+	s2[W] = 0.5f * m_params.m_RadiusSpread[2];
+
+	p0_lo = p0 - s0 - p0[W]*one - s0[W]*one;
+	p1_lo = p1 - s1 - p1[W]*one - s1[W]*one; 
+	p2_lo = p2 - s2 - p2[W]*one - s2[W]*one;
+
+	p0_hi = p0 + s0 + p0[W]*one + s0[W]*one;
+	p1_hi = p1 + s1 + p1[W]*one + s1[W]*one;
+	p2_hi = p2 + s2 + p2[W]*one + s2[W]*one;
+
+	AABB_min = componentwise_min( p0_lo, p2_lo );
+	AABB_max = componentwise_max( p0_hi, p2_hi );
+
+	if( m_params.m_UseMidpoint )
+	{ 
+		Mth::Vector p;
+
+		p = extrema( p0_lo, p1_lo, p2_lo,
+					m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f,
+					m_params.m_Lifetime );
+
+		AABB_min = componentwise_min( AABB_min, p ); 
+	 
+		p = extrema( p0_hi, p1_hi, p2_hi,
+					m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f,
+					m_params.m_Lifetime );
+
+		AABB_max = componentwise_max( AABB_max, p ); 
+	}
+
+	// Set the bounding box.
+	m_bbox.Set( AABB_min, AABB_max );
+
+	// Now calculate the bounding sphere.
+	Mth::Vector diag		= ( AABB_max - AABB_min ) * 0.5f;
+	diag[W]					= 0.0f;
+	m_bsphere				= AABB_min + diag;
+	m_bsphere[W]			= diag.Length();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::Initialize( CParticleParams* params )
+{
+	m_params = *params;
+	CalculateBoundingVolumes();
+	plat_build();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CParticleParams*	CNewParticle::GetParameters( void )
+{
+	return &m_params;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::Render( void )
+{
+	if (!m_params.m_Hidden && !m_params.m_Suspended  )
+	{
+		plat_render();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::Update( void )
+{
+	if (!m_params.m_Hidden && !m_params.m_Suspended)
+	{
+		plat_update();
+	}
+	
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::Destroy( void )
+{
+	plat_destroy();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::Hide( bool  should_hide )
+{
+	if (m_params.m_Hidden != should_hide)
+	{
+		m_params.m_Hidden = should_hide;
+		plat_hide(should_hide);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+void	CNewParticle::Suspend( bool	should_suspend )
+{
+	if (m_params.m_Suspended != should_suspend)
+	{
+		// if we were not hidden, then hide/unhide based on the value of the suspend flag
+		if (!m_params.m_Hidden)
+		{
+			plat_hide(should_suspend);
+		}	
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetEmitRate( float emit_rate )
+{
+	m_params.m_EmitRate = emit_rate;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetLifetime( float lifetime )
+{
+	m_params.m_Lifetime = lifetime;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetMidpointPct( float midpoint_pct )
+{
+	m_params.m_MidpointPct = midpoint_pct;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetRadius( int which, float radius )
+{
+	Dbg_Assert( which < vNUM_BOXES );
+
+	m_params.m_Radius[which] = radius;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetBoxPos( int which, Mth::Vector* pos )
+{
+	Dbg_Assert( pos );
+	Dbg_Assert( which < vNUM_BOXES );
+
+	m_params.m_BoxPos[which] = *pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetBoxDims( int which, Mth::Vector* dims )
+{
+	Dbg_Assert( dims );
+	Dbg_Assert( which < vNUM_BOXES );
+
+	m_params.m_BoxDims[which] = *dims;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetColor( int which, Image::RGBA* color )
+{
+	Dbg_Assert( which < vNUM_BOXES );
+	Dbg_Assert( color );
+
+	m_params.m_Color[which] = *color;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::SetMidpointColorPct( float midpoint_pct )
+{
+	m_params.m_ColorMidpointPct = midpoint_pct;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::plat_render( void )
+{
+	Dbg_Printf( "STUB: plat_render\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::plat_update( void )
+{
+	Dbg_Printf( "STUB: plat_update\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::plat_build( void )
+{
+	Dbg_Printf( "STUB: plat_build\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::plat_destroy( void )
+{
+	Dbg_Printf( "STUB: plat_destroy\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CNewParticle::plat_hide( bool should_hide )
+{
+	Dbg_Printf( "STUB: plat_destroy\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/NxNewParticle.h b/Code/Gfx/NxNewParticle.h
new file mode 100644
index 0000000..bf57fa3
--- /dev/null
+++ b/Code/Gfx/NxNewParticle.h
@@ -0,0 +1,176 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		NxNewParticle.h											**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	New parametric particle system  	 					**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_NXNEWPARTICLE_H__
+#define __GFX_NXNEWPARTICLE_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Nx
+{
+
+						
+enum
+{
+	vBOX_START,
+	vBOX_MID,
+	vBOX_END,
+	vNUM_BOXES
+};
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CParticleParams
+{
+public:
+	CParticleParams( void );
+
+	// Particle Params
+	uint32			m_Name;
+	uint32			m_Type;	// NEWFLAT is the only one for now
+	bool			m_UseMidpoint;
+	float			m_Radius[vNUM_BOXES];
+	float			m_RadiusSpread[vNUM_BOXES];
+	int				m_MaxStreams;
+	float			m_EmitRate;
+	float			m_Lifetime;		// in seconds
+	float			m_MidpointPct;	// percent of lifetime at which midpoint occurs
+
+	// Spatial params
+	Mth::Matrix		m_RotMatrix;
+	Mth::Vector		m_BoxPos[vNUM_BOXES];
+	Mth::Vector		m_BoxDims[vNUM_BOXES];
+
+	// Local data (CParticleComponent is the only class that supports this)
+	bool			m_LocalCoord;
+	Mth::Vector		m_LocalBoxPos[vNUM_BOXES];
+
+	// Color params
+	bool			m_UseMidcolor;
+	float			m_ColorMidpointPct;	// percent of lifetime at which midcolor occurs
+	Image::RGBA		m_Color[vNUM_BOXES];
+
+	// Material params
+	uint32			m_BlendMode;
+	int				m_FixedAlpha;
+	int				m_AlphaCutoff;
+	uint32			m_Texture;
+
+	// LOD params
+	int				m_LODDistance1;
+	int				m_LODDistance2;
+	int				m_SuspendDistance;
+
+	// Hidden flag
+	bool			m_Hidden;
+	bool			m_Suspended;
+
+};
+
+class CNewParticle
+{
+public:
+	CNewParticle( void );
+	virtual	~CNewParticle( void );
+
+	void	Initialize( CParticleParams* params );
+	CParticleParams*	GetParameters( void );
+
+	void	SetEmitRate( float emit_rate );
+	void	SetLifetime( float lifetime );
+	void	SetMidpointPct( float midpoint_pct );
+	void	SetMidpointColorPct( float midpoint_pct );
+
+	void	SetRadius( int which, float radius );
+	void	SetBoxPos( int which, Mth::Vector* pos );
+	void	SetBoxDims( int which, Mth::Vector* dims );
+	void	SetColor( int which, Image::RGBA* color );
+	
+	void	CalculateBoundingVolumes( void );
+
+	void	Render( void );
+	void	Update( void );
+	void	Destroy( void );
+	void	Hide(bool should_hide);
+	void	Suspend( bool	should_suspend );
+	
+	uint32	GetName() {return m_params.m_Name;}
+	
+protected:
+	
+	CParticleParams	m_params;
+
+	Mth::CBBox		m_bbox;
+	Mth::Vector		m_bsphere;
+
+	virtual void	plat_build( void );
+	virtual void	plat_destroy( void );
+	virtual void	plat_hide( bool should_hide );
+	virtual	void	plat_render( void );
+	virtual	void	plat_update( void );
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_NXNEWPARTICLE_H__
+
+
diff --git a/Code/Gfx/NxNewParticleMgr.cpp b/Code/Gfx/NxNewParticleMgr.cpp
new file mode 100644
index 0000000..0c60c99
--- /dev/null
+++ b/Code/Gfx/NxNewParticleMgr.cpp
@@ -0,0 +1,197 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		NxNewParticleMgr.cpp									**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	New parametric particle system manager					**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+CNewParticle*	CNewParticleManager::plat_create_particle( void )
+{
+	Dbg_Assert( 0 );
+	Dbg_Printf( "Stub plat_create_particle\n" );
+	return NULL;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+CNewParticleManager::CNewParticleManager( void )
+{
+	mp_particle_table = new Lst::HashTable< CNewParticle >( 8 );
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNewParticleManager::~CNewParticleManager( void )
+{
+	delete mp_particle_table;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNewParticle*	CNewParticleManager::CreateParticle( CParticleParams* params, bool generate_name )
+{
+	CNewParticle* particle;
+	 
+	particle = plat_create_particle();
+	if( particle )
+	{
+		if( generate_name )
+		{
+			uint32 name;
+
+			name = params->m_Name;
+			// Ensure a unique name for this system
+			while(( mp_particle_table->GetItem( name, false ) != NULL ))
+			{
+				name++;
+			}
+
+			params->m_Name = name;
+		}
+
+		particle->Initialize( params );
+		mp_particle_table->PutItem( params->m_Name, particle );
+	}
+
+	return particle;
+}
+
+// Given an actual particle system
+// remove it from the Particle
+void	CNewParticleManager::KillParticle( CNewParticle* p_particle)
+{
+	Dbg_MsgAssert(p_particle,("NULL p_particle passed to KillParticle"));
+	Dbg_MsgAssert(mp_particle_table->GetItem(p_particle->GetName()) == p_particle,("entry in particle table for %s does not match where it came from",Script::FindChecksumName(p_particle->GetName())));	
+	mp_particle_table->FlushItem(p_particle->GetName());	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CNewParticleManager::RenderParticles( void )
+{
+	mp_particle_table->IterateStart();
+	CNewParticle *p_particle = mp_particle_table->IterateNext();
+	while( p_particle )
+	{
+		p_particle->Render();
+		p_particle = mp_particle_table->IterateNext();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CNewParticleManager::UpdateParticles( void )
+{
+	mp_particle_table->IterateStart();
+	CNewParticle *p_particle = mp_particle_table->IterateNext();
+	while( p_particle )
+	{
+		p_particle->Update();
+		p_particle = mp_particle_table->IterateNext();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 			CNewParticleManager::Cleanup( void )
+{
+	// Goes through and removes all particle systems.
+	mp_particle_table->IterateStart();
+	CNewParticle *p_particle = mp_particle_table->IterateNext();
+	while( p_particle )
+	{
+		CNewParticle *p_next	= mp_particle_table->IterateNext();
+		p_particle->Destroy();
+		delete p_particle;
+		p_particle		= p_next;
+	}
+	mp_particle_table->FlushAllItems();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/NxNewParticleMgr.h b/Code/Gfx/NxNewParticleMgr.h
new file mode 100644
index 0000000..bd66bae
--- /dev/null
+++ b/Code/Gfx/NxNewParticleMgr.h
@@ -0,0 +1,93 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		NxNewParticleMgr.h										**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	New parametric particle system manager					**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_NXNEWPARTICKEMGR_H__
+#define __GFX_NXNEWPARTICKEMGR_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CNewParticleManager
+{
+public:
+	CNewParticleManager( void );
+	virtual	~CNewParticleManager( void );
+
+	CNewParticle*	CreateParticle( CParticleParams* params, bool generate_name = false );
+	void			KillParticle( CNewParticle* p_particle );
+	void			RenderParticles( void );
+	void			UpdateParticles( void );
+	void			Cleanup( void );
+
+protected:
+
+	Lst::HashTable< CNewParticle >	*mp_particle_table;
+
+private:
+	virtual	CNewParticle*	plat_create_particle( void );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_NXNEWPARTICKEMGR_H__
+
+
diff --git a/Code/Gfx/NxQuickAnim.cpp b/Code/Gfx/NxQuickAnim.cpp
new file mode 100644
index 0000000..0920672
--- /dev/null
+++ b/Code/Gfx/NxQuickAnim.cpp
@@ -0,0 +1,464 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       quickanim.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/4/2003
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float quatDown(short theSource)
+{
+    return (float)(theSource / 16384.0f);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float transDown(short theSource, float scaleFactor)
+{
+    return (float)(theSource / scaleFactor);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void get_rotation_from_key( Gfx::CAnimQKey* p_in, Mth::Quat* pQuat, bool isHiRes )
+{
+	if ( isHiRes )
+	{
+		(*pQuat)[X] = ((Gfx::CHiResAnimQKey*)p_in)->qx;
+		(*pQuat)[Y] = ((Gfx::CHiResAnimQKey*)p_in)->qy;
+		(*pQuat)[Z] = ((Gfx::CHiResAnimQKey*)p_in)->qz;
+//  	(*pQuat)[W] = ((Gfx::CHiResAnimQKey*)p_in)->qw;
+	}
+	else
+	{
+		(*pQuat)[X] = quatDown( ((Gfx::CStandardAnimQKey*)p_in)->qx );
+		(*pQuat)[Y] = quatDown( ((Gfx::CStandardAnimQKey*)p_in)->qy );
+		(*pQuat)[Z] = quatDown( ((Gfx::CStandardAnimQKey*)p_in)->qz );
+//		(*pQuat)[W] = quatDown( ((Gfx::CStandardAnimQKey*)p_in)->qw );
+	}
+	
+	float qx = (*pQuat)[X];
+	float qy = (*pQuat)[Y];
+	float qz = (*pQuat)[Z];
+
+	// Dave note: added 09/12/02 - a simple check to ensure we don't try to take the square root of a negative
+	// number, which will hose Nan-sensitive platforms later on...
+	float sum	= 1.0f - qx * qx - qy * qy - qz * qz;
+	(*pQuat)[W] = sqrtf (( sum < 0.0f ) ? 0.0f : sum );
+	
+	if ( p_in->signBit )
+	{
+		(*pQuat)[W] = -(*pQuat)[W];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void get_translation_from_key( Gfx::CAnimTKey* p_in, Mth::Vector* pVector, bool isHiRes )
+{
+	if ( isHiRes )
+	{
+		(*pVector)[X] = ((Gfx::CHiResAnimTKey*)p_in)->tx;
+		(*pVector)[Y] = ((Gfx::CHiResAnimTKey*)p_in)->ty;
+		(*pVector)[Z] = ((Gfx::CHiResAnimTKey*)p_in)->tz;
+		(*pVector)[W] = 1.0f;
+	}
+	else
+	{
+		(*pVector)[X] = transDown( ((Gfx::CStandardAnimTKey*)p_in)->tx, 32.0f );
+		(*pVector)[Y] = transDown( ((Gfx::CStandardAnimTKey*)p_in)->ty, 32.0f );
+		(*pVector)[Z] = transDown( ((Gfx::CStandardAnimTKey*)p_in)->tz, 32.0f );
+		(*pVector)[W] = 1.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float get_alpha( float timeStamp1, float timeStamp2, float time )
+{
+	Dbg_MsgAssert(timeStamp1 <= time && timeStamp2 >= time, ( "%f should be within [%f %f]", time, timeStamp1, timeStamp2 ));
+    
+	return (( time - timeStamp1 ) / ( timeStamp2 - timeStamp1 ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+						  
+inline void interpolate_q_frame(Mth::Quat* p_out, Gfx::CAnimQKey* p_in1, Gfx::CAnimQKey* p_in2, float alpha, bool isHiRes)
+{
+	Dbg_Assert(p_out);
+	Dbg_Assert(p_in1);
+	Dbg_Assert(p_in2);
+
+	Mth::Quat	qIn1;
+	get_rotation_from_key( p_in1, &qIn1, isHiRes );
+
+	if ( alpha == 0.0f )
+	{
+		// don't need to slerp, because it's the start time
+		*p_out = qIn1; 
+		return;
+	}
+
+	Mth::Quat   qIn2;
+	get_rotation_from_key( p_in2, &qIn2, isHiRes );
+    
+	if ( alpha == 1.0f )
+	{
+		// don't need to slerp, because it's the end time
+		*p_out = qIn2; 
+		return;
+	}
+
+	// fast slerp, stolen from game developer magazine
+	*p_out = Mth::FastSlerp( qIn1, qIn2, alpha );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void interpolate_t_frame(Mth::Vector* p_out, Gfx::CAnimTKey* p_in1, Gfx::CAnimTKey* p_in2, float alpha, bool isHiRes)
+{
+	Dbg_Assert(p_out);
+	Dbg_Assert(p_in1);
+	Dbg_Assert(p_in2);
+    
+	// INTERPOLATE T-COMPONENT
+    Mth::Vector   tIn1;					  
+	get_translation_from_key( p_in1, &tIn1, isHiRes );
+
+	if ( alpha == 0.0f )
+	{
+		// don't need to lerp, because it's the start time
+		*p_out = tIn1; 
+		return;
+	}
+
+	Mth::Vector   tIn2;
+	get_translation_from_key( p_in2, &tIn2, isHiRes );
+
+	if ( alpha == 1.0f )
+	{
+		// don't need to slerp, because it's the end time
+		*p_out = tIn2; 
+		return;
+	}
+
+    /* Linearly interpolate positions */
+    *p_out = Mth::Lerp( tIn1, tIn2, alpha );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CQuickAnim::plat_get_interpolated_frames( Mth::Quat* pRotations, Mth::Vector* pTranslations, uint32* pSkipList, uint32 skipIndex, float time )
+{
+	Dbg_MsgAssert( mp_frameData, ( "No pointer to frame data" ) );
+
+/*
+	// this optimization doesn't really speed things up at all,
+	// since the time-consuming part is walking through the compressed
+	// key lists in CBonedAnimFrameData::GetCompressedInterpolatedFrames.
+	// i've commented this out for now until I have more time
+	// to figure out a better alternative...
+	
+	if ( m_quickAnimPointers.valid )
+	{
+		float timeStamp = time * 60.0f;
+		int numBones = mp_frameData->GetNumBones();
+		Mth::Quat* pCurrRotations = pRotations;
+		Mth::Vector* pCurrTranslations = pTranslations;
+
+		for ( int i = 0; i < numBones; i++ )
+		{
+			m_quickAnimPointers.qSkip[i] = false;
+
+			Gfx::CAnimQKey* pStartQKey = &m_quickAnimPointers.theStartQKey[i];
+			Gfx::CAnimQKey* pEndQKey = &m_quickAnimPointers.theEndQKey[i];
+
+			// if the two adjacent keys haven't changed,
+			// then we can use the optimization
+			if ( timeStamp >= (int)pStartQKey->timestamp && timeStamp <= (int)pEndQKey->timestamp )
+			{
+				float qAlpha = get_alpha( (float)pStartQKey->timestamp, (float)pEndQKey->timestamp, timeStamp );
+
+				interpolate_q_frame( pCurrRotations, 
+								 pStartQKey, 
+								 pEndQKey,
+								 qAlpha,
+								 false );
+
+				m_quickAnimPointers.qSkip[i] = true;
+			}
+
+			pCurrRotations++;
+
+			m_quickAnimPointers.tSkip[i] = false;
+
+			Gfx::CAnimTKey* pStartTKey = &m_quickAnimPointers.theStartTKey[i];
+			Gfx::CAnimTKey* pEndTKey = &m_quickAnimPointers.theEndTKey[i];
+
+			// if the two adjacent keys haven't changed,
+			// then we can use the optimization
+			if ( timeStamp >= (int)pStartTKey->timestamp && timeStamp <= (int)pEndTKey->timestamp )
+			{
+				float tAlpha = get_alpha( (float)pStartTKey->timestamp, (float)pEndTKey->timestamp, timeStamp );
+
+				interpolate_t_frame( pCurrTranslations, 
+								 pStartTKey, 
+								 pEndTKey,
+								 tAlpha,
+								 false );
+
+				m_quickAnimPointers.tSkip[i] = true;
+			}
+			
+			pCurrTranslations++;
+		}
+	}
+*/
+
+
+
+	m_quickAnimPointers.pSkipList	= pSkipList;
+	m_quickAnimPointers.skipIndex	= skipIndex;
+	
+	Dbg_MsgAssert( mp_frameData, ( "No frame data" ) );
+	mp_frameData->GetInterpolatedFrames(pRotations, pTranslations, time, this);
+	
+	m_quickAnimPointers.valid = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CQuickAnim::CQuickAnim()
+{
+	mp_frameData = NULL;
+	m_quickAnimPointers.valid = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CQuickAnim::~CQuickAnim()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CQuickAnim::Enable( bool enabled )
+{
+	m_quickAnimPointers.valid = enabled;
+
+	// if the cache is invalid, then regrab the pointer to the anim
+	if ( !enabled )
+	{
+		mp_frameData = Nx::GetCachedAnim( m_animAssetName, true );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CQuickAnim::SetAnimAssetName( uint32 animAssetName )
+{
+	m_animAssetName = animAssetName;
+	
+	// set the pointer to the animation
+	mp_frameData = Nx::GetCachedAnim( m_animAssetName, true );
+	
+	// invalidate the cache
+	Enable(false);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CQuickAnim::GetInterpolatedFrames( Mth::Quat* pRotations, Mth::Vector* pTranslations, uint32* pSkipList, uint32 skipIndex, float time )
+{
+	plat_get_interpolated_frames( pRotations, pTranslations, pSkipList, skipIndex, time );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CQuickAnim::GetInterpolatedHiResFrames( Mth::Quat* pRotations, Mth::Vector* pTranslations, float time )
+{
+	Dbg_MsgAssert( mp_frameData, ( "No pointer to frame data" ) );
+
+	mp_frameData->GetInterpolatedCameraFrames(pRotations, pTranslations, time, this);
+	
+	m_quickAnimPointers.valid = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CQuickAnim::GetNumBones()
+{
+	Dbg_MsgAssert( mp_frameData, ( "No pointer to frame data" ) );
+
+	return mp_frameData->GetNumBones();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CQuickAnim::ResetCustomKeys()
+{
+	Dbg_MsgAssert( mp_frameData, ( "No pointer to frame data" ) );
+
+	mp_frameData->ResetCustomKeys();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CQuickAnim::GetDuration()
+{
+	Dbg_MsgAssert( mp_frameData, ( "No pointer to frame data" ) );
+
+	return mp_frameData->GetDuration();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+uint32 CQuickAnim::GetBoneName( int i )
+{
+	Dbg_MsgAssert( mp_frameData, ( "No pointer to frame data" ) );
+
+	return mp_frameData->GetBoneName( i );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CQuickAnim::ProcessCustomKeys( float startTimeInclusive, float endTimeInclusive, Obj::CObject* pObject )
+{
+	Dbg_MsgAssert( mp_frameData, ( "No pointer to frame data" ) );
+
+	bool inclusive = true;
+	return mp_frameData->ProcessCustomKeys( startTimeInclusive, endTimeInclusive, pObject, inclusive );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
+
diff --git a/Code/Gfx/NxQuickAnim.h b/Code/Gfx/NxQuickAnim.h
new file mode 100644
index 0000000..40f7553
--- /dev/null
+++ b/Code/Gfx/NxQuickAnim.h
@@ -0,0 +1,116 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxQuickAnim.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/4/2003
+//****************************************************************************
+
+#ifndef	__GFX_NXQUICKANIM_H__
+#define	__GFX_NXQUICKANIM_H__
+
+// The CQuickAnim is a per-object interface to the CBonedAnimFrameData
+// (which is raw animation data).  Because multiple objects can be playing 
+// the same animation at the same time, we can't do anything fancy like 
+// caching pointers inside the CBonedAnimFrameData;  however, since each 
+// object has its own CQuickAnim, we can store pointers or decompress data
+// or whatever here.  I've decided to give this class a p-line interface
+// so that different platforms can implement different optimizations
+// based on whether speed or memory is the bottleneck.  							
+							
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Gfx
+{
+	class CBonedAnimFrameData;
+}
+
+namespace Obj
+{
+	class CObject;
+}
+
+namespace Mth
+{
+	class Quat;
+	class Vector;
+}
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class CQuickAnim : public Spt::Class
+{
+public:
+	CQuickAnim();
+	virtual ~CQuickAnim();
+
+public:
+	void						SetAnimAssetName( uint32 animAssetName );
+	void						GetInterpolatedFrames( Mth::Quat* pRotations, Mth::Vector* pTranslations, uint32* pSkipList, uint32 skipIndex, float time );
+	void						GetInterpolatedHiResFrames( Mth::Quat* pRotations, Mth::Vector* pTranslations, float time );
+	void						Enable(bool enabled);
+	int							GetNumBones();
+	void						ResetCustomKeys();
+	float						GetDuration();
+	uint32						GetBoneName( int i );
+	bool						ProcessCustomKeys( float startTimeInclusive, float endTimeExclusive, Obj::CObject* pObject );
+
+private:
+    // The virtual functions will have a stub implementation in p_nxquickanim.cpp
+	virtual	void				plat_get_interpolated_frames( Mth::Quat* pRotations, Mth::Vector* pTranslations, uint32* pSkipList, uint32 skipIndex, float time );
+
+protected:
+	Gfx::CBonedAnimFrameData*	mp_frameData;
+	uint32						m_animAssetName;
+
+public:
+	Gfx::SQuickAnimPointers		m_quickAnimPointers;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // namespace Gfx
+
+#endif	// __GFX_NXQUICKANIM_H__
diff --git a/Code/Gfx/NxScene.cpp b/Code/Gfx/NxScene.cpp
new file mode 100644
index 0000000..d2f7167
--- /dev/null
+++ b/Code/Gfx/NxScene.cpp
@@ -0,0 +1,1282 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NX.CPP - Platform independent interface to the platfrom specific engine code 
+
+#include "gfx/nx.h"
+#include "gfx/NxSector.h"
+#include "gfx/NxTexMan.h"
+
+#include "core/debug.h"
+
+#include 
+#include 
+
+
+#include 
+#include 
+
+#include 	   
+#include 	   
+#include 
+#include 
+
+#include 
+
+#ifdef	__PLAT_NGPS__
+// For wireframe debuging mode
+#include 
+#include 
+#include 
+#include 
+#endif
+
+#include 
+
+namespace	Nx
+{
+
+///////////////////////////////////////////////////////////////////
+// CScene definitions
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene::CScene( int sector_table_size ) : mp_sector_man(NULL)
+{
+	mp_sector_table = new Lst::HashTable< CSector >( sector_table_size );
+
+//	Dbg_MsgAssert(0,("secotr-tabel_size = %d\n",sector_table_size))
+
+	m_scene_filename[0] = '\0';
+	
+	// Initilizing supersector and collision crap to NULL, as 
+	// we might not have any (like for skys and background stuff)
+	mp_sector_man = NULL;
+	mp_coll_sectors = NULL;
+	mp_coll_sector_data = NULL;
+	m_num_coll_sectors = 0;
+
+	// Incremental update
+	m_add_scene_filename[0] = '\0';
+	mp_add_tex_dict = NULL; 
+	mp_add_coll_sectors = NULL;
+	mp_add_coll_sector_data = NULL;
+	m_num_add_coll_sectors = 0;
+
+	m_using_add_sectors = false;
+	mp_orig_sectors = new Lst::Head < CSector >;
+	mp_add_sectors = new Lst::Head < CSector >;
+
+	m_in_super_sectors = false;
+	m_sky = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene::~CScene()
+{
+
+	// Mick: iterate over all the sectors, and delete them
+	// (deleting the mp_sector_table just deletes the references to them)
+	if (mp_sector_table)
+	{
+		mp_sector_table->IterateStart();
+		CSector *p_sector;
+		while ((p_sector = mp_sector_table->IterateNext()))
+		{
+			p_sector->clear_flags(CSector::mIN_SUPER_SECTORS);	// Tells cloned sectors it is OK to go away (and SuperSectors will be dead, anyway)
+			delete p_sector;
+		}
+		delete mp_sector_table;
+	}
+	
+	// Remove SuperSectors
+	if (mp_sector_man)
+	{
+		delete mp_sector_man;
+	}
+	
+	// Remove Collision
+	if (mp_coll_sectors)
+	{
+		delete[] mp_coll_sectors;
+	}
+
+	if (mp_coll_sector_data)
+	{
+		Pip::Unload(m_coll_filename);
+	}
+
+	// And the toggle list heads
+	if (mp_orig_sectors)
+	{
+		delete mp_orig_sectors;
+	}
+
+	if (mp_add_sectors)
+	{
+		delete mp_add_sectors;
+	}
+
+	// Check if additional texture dictionary was loaded
+	if (mp_add_tex_dict) 
+	{
+		Nx::CTexDictManager::sUnloadTextureDictionary(mp_add_tex_dict);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::UnloadAddScene()
+{
+	if (m_using_add_sectors)
+	{
+	   	ToggleAddScene();
+	}
+
+	if (!plat_unload_add_scene())
+	{
+		return false;
+	}
+
+	if (mp_add_coll_sectors)
+	{
+		delete[] mp_add_coll_sectors;
+	}
+
+	if (mp_add_coll_sector_data)
+	{
+		Pip::Unload(m_add_coll_filename);
+	}
+
+	if (mp_orig_sectors)
+	{
+		Lst::Node< CSector > *obj_node, *next;
+
+		for(obj_node = mp_orig_sectors->GetNext(); obj_node; obj_node = next)
+		{
+			next = obj_node->GetNext();
+
+			delete obj_node;
+		}
+	}
+
+	if	(mp_add_sectors)
+	{
+		Lst::Node< CSector > *obj_node, *next;
+
+		for(obj_node = mp_add_sectors->GetNext(); obj_node; obj_node = next)
+		{
+			next = obj_node->GetNext();
+
+			DeleteSector(obj_node->GetData());		// And delete the sector itself
+			delete obj_node;
+		}
+	}
+
+	m_using_add_sectors = false;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector * 		CScene::GetSector(uint32 sector_checksum)
+{
+	return mp_sector_table->GetItem(sector_checksum);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 			CScene::AddSector(CSector *pSector)
+{
+	mp_sector_table->PutItem(pSector->GetChecksum(), pSector);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector *		CScene::ReplaceSector(CSector *pSector)
+{
+	CSector *p_old_sector = mp_sector_table->GetItem(pSector->GetChecksum());
+
+	// Possibly delete old sector
+	if (p_old_sector)
+	{
+		//if (p_old_sector->get_flags() & CSector::mCLONE)
+		//{
+		//	DeleteSector(p_old_sector);
+		//} else {
+			remove_sector_from_table(p_old_sector->GetChecksum());	// can't delete something that is PIP'ed in
+			p_old_sector->SetActive(false);
+		//}
+
+		// We also want this out of the SuperSectors
+		p_old_sector->set_flags(CSector::mREMOVE_FROM_SUPER_SECTORS);
+
+		// Also add to orig list
+		Lst::Node *node = new Lst::Node(p_old_sector);
+		mp_orig_sectors->AddToTail(node);
+
+		Dbg_Message("Replacing sector %x", pSector->GetChecksum());
+	} else {
+		Dbg_Message("Adding sector %x", pSector->GetChecksum());
+	}
+
+	// And add new one
+	// Garrett TODO: Should I check to see if there is a CCollObj here?  Or UpdateSuperSectors()?  Or does it matter?
+	pSector->set_flags(CSector::mADD_TO_SUPER_SECTORS);			// And do an UpdateSuperSectors() after all the calls
+	AddSector(pSector);
+
+	Lst::Node *node = new Lst::Node(pSector);
+	mp_add_sectors->AddToTail(node);
+
+	return p_old_sector;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::ToggleAddScene()
+{
+	if (mp_add_sectors->CountItems() == 0)
+	{
+		return false;
+	}
+
+	Lst::Head < CSector > *p_on_sectors, *p_off_sectors;
+	Lst::Node< CSector > *obj_node, *next;
+	CSector *p_sector;
+
+	// Do actual toggle
+	m_using_add_sectors = !m_using_add_sectors;
+
+	// Figure out which to turn on and off
+	if (m_using_add_sectors)
+	{
+		p_on_sectors = mp_add_sectors;
+		p_off_sectors = mp_orig_sectors;
+	} else {
+		p_on_sectors = mp_orig_sectors;
+		p_off_sectors = mp_add_sectors;
+	}
+
+	// Do off list first
+	for(obj_node = p_off_sectors->GetNext(); obj_node; obj_node = next)
+	{
+		next = obj_node->GetNext();
+		p_sector = obj_node->GetData();
+
+		p_sector->set_flags(CSector::mREMOVE_FROM_SUPER_SECTORS);
+		remove_sector_from_table(p_sector->GetChecksum());
+		p_sector->SetActive(false);
+		//Dbg_Message("Removed sector %x in toggle", p_sector->GetChecksum());
+	}
+
+	// Do on list
+	for(obj_node = p_on_sectors->GetNext(); obj_node; obj_node = next)
+	{
+		next = obj_node->GetNext();
+		p_sector = obj_node->GetData();
+
+		p_sector->set_flags(CSector::mADD_TO_SUPER_SECTORS);
+		AddSector(p_sector);
+		p_sector->SetActive(true);
+		//Dbg_Message("Added sector %x in toggle", p_sector->GetChecksum());
+	}
+
+	// And update the SuperSectors
+	UpdateSuperSectors(p_off_sectors);
+
+	return m_using_add_sectors;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 			CScene::CloneSector(uint32 orig_sector_checksum, CScene *p_dest_scene, bool instance, bool add_to_super_sectors)
+{
+	CSector *p_orig_sector = GetSector(orig_sector_checksum);
+
+	if (p_orig_sector)
+	{
+		return CloneSector(p_orig_sector, p_dest_scene, instance, add_to_super_sectors);
+	} else {
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 			CScene::CloneSector(CSector *p_orig_sector, CScene *p_dest_scene, bool instance, bool add_to_super_sectors)
+{
+	// Use current scene if dest is NULL
+	if (!p_dest_scene)
+	{
+		p_dest_scene = this;
+	}
+
+	CSector *p_new_sector = p_orig_sector->clone(instance, add_to_super_sectors, p_dest_scene);
+
+	if (p_new_sector)
+	{
+		p_dest_scene->AddSector(p_new_sector);
+
+		return p_new_sector->GetChecksum();
+	} else {
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool 			CScene::DeleteSector(uint32 sector_checksum)
+{
+	CSector *p_sector = GetSector(sector_checksum);
+
+	if (p_sector)
+	{
+		return DeleteSector(p_sector);
+	} else {
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool 			CScene::DeleteSector(CSector *p_sector)
+{
+	if (p_sector->get_flags() & CSector::mIN_SUPER_SECTORS)
+	{
+		p_sector->set_flags(CSector::mMARKED_FOR_DELETION);		// Can't delete, yet
+
+		return false;
+	} else {
+		if (GetSector(p_sector->GetChecksum()) == p_sector)		// Just in case we're doing this with AddSectors
+		{
+			mp_sector_table->FlushItem(p_sector->GetChecksum());
+		}
+
+		delete p_sector;
+
+		return true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CScene::PostLoad(const char *p_name)
+{
+	strcpy(m_scene_filename, p_name);	// needs name for Pip::Unload()
+
+	plat_post_load();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CScene::PostAdd(const char *p_name, Nx::CTexDict * p_tex_dict)
+{
+	strcpy(m_add_scene_filename, p_name);	// needs to store name somewhere for Pip::Unload(), if we use Pip
+	mp_add_tex_dict = p_tex_dict; 
+	m_using_add_sectors = true;
+
+	plat_post_add();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::read_collision(const char *p_name, char *p_pip_name, int &num_coll_sectors,
+									   CCollStaticTri * &p_coll_sectors, CCollObjTriData * &p_coll_sector_data, Mth::CBBox &bbox, bool is_net)
+{
+	// for now collision is kind of assumed to be platform independent 
+	sprintf(p_pip_name,"levels\\%s\\%s%s.col.%s",p_name,p_name,is_net?"_net":"",CEngine::sGetPlatformExtension());
+
+	Dbg_Message ( "Loading collision %s....", p_pip_name );
+	
+	bool	found_point = false;
+
+	uint8 *p_base_addr = (uint8 *) Pip::Load(p_pip_name);
+	if (p_base_addr)
+	{
+		Nx::CCollObjTriData::SReadHeader *p_header = (Nx::CCollObjTriData::SReadHeader *) p_base_addr;
+		p_base_addr += sizeof(Nx::CCollObjTriData::SReadHeader);
+
+#ifndef __PLAT_NGC__
+		Dbg_Message ( "Version # %d header sizeof %d", p_header->m_version, sizeof(Nx::CCollObjTriData));
+		Dbg_Message ( "Number of objects: %d verts: %d faces: %d", p_header->m_num_objects, p_header->m_total_num_verts, p_header->m_total_num_faces_large + p_header->m_total_num_faces_small);
+		Dbg_Message ( "Small verts: %d Large verts: %d", p_header->m_total_num_verts_small, p_header->m_total_num_verts_large);
+#endif		// __PLAT_NGC__
+		Dbg_MsgAssert(p_header->m_version >= 9, ("Collision version must be at least 9."));
+
+		// reserve space for objects
+		num_coll_sectors = p_header->m_num_objects;
+		p_coll_sector_data = (Nx::CCollObjTriData *) p_base_addr; //new Cld::CCollSector [m_num_coll_sectors];
+
+		// Calculate base addresses for vert and face arrays
+		uint8 *p_base_vert_addr = (uint8 *) (p_coll_sector_data + num_coll_sectors);
+#ifndef __PLAT_NGC__
+		p_base_vert_addr = (uint8 *)(((uint)(p_base_vert_addr+15)) & 0xFFFFFFF0);	// Align to 128 bit boundary
+#ifdef FIXED_POINT_VERTICES
+		uint8 *p_base_intensity_addr = p_base_vert_addr + (p_header->m_total_num_verts_large * Nx::CCollObjTriData::GetVertElemSize() +
+														   p_header->m_total_num_verts_small * Nx::CCollObjTriData::GetVertSmallElemSize());
+		uint8 *p_base_face_addr = p_base_intensity_addr + p_header->m_total_num_verts;
+		p_base_face_addr = (uint8 *)(((uint)(p_base_face_addr+3)) & 0xFFFFFFFC);	// Align to 32 bit boundary
+#else
+		uint8 *p_base_intensity_addr = NULL;
+		uint8 *p_base_face_addr = p_base_vert_addr + (p_header->m_total_num_verts * Nx::CCollObjTriData::GetVertElemSize());
+		p_base_face_addr = (uint8 *)(((uint)(p_base_face_addr+15)) & 0xFFFFFFF0);	// Align to 128 bit boundary
+#endif // FIXED_POINT_VERTICES
+#else
+		uint8 *p_base_intensity_addr = NULL;
+		uint8 *p_base_face_addr = p_base_vert_addr + (p_header->m_total_num_faces * Nx::CCollObjTriData::GetVertElemSize());
+		p_base_face_addr = (uint8 *)(((uint)(p_base_face_addr+3)) & 0xFFFFFFFC);	// Align to 32 bit boundary
+#endif		// __PLAT_NGC__
+
+		// Calculate addresses for BSP arrays
+#ifndef __PLAT_NGC__
+		uint8 *p_node_array_size = p_base_face_addr + (p_header->m_total_num_faces_large * Nx::CCollObjTriData::GetFaceElemSize() +
+													   p_header->m_total_num_faces_small * Nx::CCollObjTriData::GetFaceSmallElemSize());
+		p_node_array_size += ( p_header->m_total_num_faces_large & 1 ) ? 2 : 0;
+#else
+		uint8 *p_node_array_size = p_base_face_addr + ( p_header->m_total_num_faces * Nx::CCollObjTriData::GetFaceElemSize() );
+		p_node_array_size += ( p_header->m_total_num_faces & 1 ) ? 2 : 0;
+#endif		// __PLAT_NGC__
+		uint8 *p_base_node_addr = p_node_array_size + 4;
+		uint8 *p_base_face_idx_addr = p_base_node_addr + *((int *) p_node_array_size);
+
+		// Reserve space for collsion objects
+		p_coll_sectors = new CCollStaticTri[p_header->m_num_objects];
+
+		// Read objects
+		for (int oidx = 0; oidx < p_header->m_num_objects; oidx++)
+		{
+			p_coll_sector_data[oidx].InitCollObjTriData(this, p_base_vert_addr, p_base_intensity_addr, p_base_face_addr,
+														p_base_node_addr, p_base_face_idx_addr);
+			p_coll_sector_data[oidx].InitBSPTree();
+
+			p_coll_sectors[oidx].SetGeometry(&(p_coll_sector_data[oidx]));
+
+			if (p_coll_sector_data[oidx].GetNumFaces() > 0)	 // only add bbox if there are some faces in the object.....
+			{
+				// Add to scene bbox
+				bbox.AddPoint(p_coll_sector_data[oidx].GetBBox().GetMin());
+				bbox.AddPoint(p_coll_sector_data[oidx].GetBBox().GetMax());
+				found_point = true;
+			}
+		}
+		if (!found_point)
+		{
+			// if there was no collision, then set up a dummy 200 inch bounding box at the origin
+			bbox.AddPoint(Mth::Vector (-100,-100,-100));
+			bbox.AddPoint(Mth::Vector (100,100,100));
+		}
+	} 
+	else 
+	{
+		Dbg_Error ( "Could not open collision file\n" );
+		return false;
+	}
+
+	Dbg_Message ( "successfully read collision" );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::LoadCollision(const char *p_name, bool is_net)
+{
+	read_collision(p_name, m_coll_filename, m_num_coll_sectors, mp_coll_sectors, mp_coll_sector_data, m_collision_bbox, is_net);
+
+	Dbg_Message("Scene bounding box: min (%f, %f, %f) max (%f, %f, %f)", 
+				m_collision_bbox.GetMin()[X], m_collision_bbox.GetMin()[Y], m_collision_bbox.GetMin()[Z], 
+				m_collision_bbox.GetMax()[X], m_collision_bbox.GetMax()[Y], m_collision_bbox.GetMax()[Z]);
+
+	if (m_num_coll_sectors > 0)
+	{
+		// Create super sectors
+		if (m_in_super_sectors)
+		{
+			mp_sector_man = new SSec::Manager;
+			mp_sector_man->GenerateSuperSectors(m_collision_bbox);
+
+			mp_sector_man->AddCollisionToSuperSectors(mp_coll_sectors, m_num_coll_sectors);
+		}
+
+		// Add to CSectors
+		for (int i = 0; i < m_num_coll_sectors; i++)
+		{
+			CSector *p_sector = GetSector(mp_coll_sectors[i].GetChecksum());
+			if (!p_sector)	// Don't assert now since there may not be renderable data
+			{
+				// Collision info, but not renderable
+				// so, we still need to create a Sector, but with no renderable component
+				p_sector = CreateSector(); 			// create empty secotr
+				p_sector->SetChecksum(mp_coll_sectors[i].GetChecksum());
+				p_sector->SetActive(true);
+				AddSector(p_sector);
+			}
+			p_sector->AddCollSector(&(mp_coll_sectors[i]));
+			p_sector->set_flags(CSector::mIN_SUPER_SECTORS);
+		}
+
+	}
+
+	CEngine::sGetWeather()->UpdateGrid();
+
+	return plat_load_collision(p_name);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::AddCollision(const char *p_name)
+{
+	Mth::CBBox			add_collision_bbox;
+
+	if (mp_coll_sectors == NULL)
+	{
+		return false;			// collision not needed
+	}
+
+	read_collision(p_name, m_add_coll_filename, m_num_add_coll_sectors, mp_add_coll_sectors, mp_add_coll_sector_data, add_collision_bbox);
+
+	if ((add_collision_bbox.GetMin()[X] < m_collision_bbox.GetMin()[X]) ||
+		(add_collision_bbox.GetMax()[X] > m_collision_bbox.GetMax()[X]) ||
+		(add_collision_bbox.GetMin()[Z] < m_collision_bbox.GetMin()[Z]) ||
+		(add_collision_bbox.GetMax()[Z] > m_collision_bbox.GetMax()[Z]))
+	{
+		Dbg_Message("Add Scene bounding box: min (%f, %f, %f) max (%f, %f, %f)", 
+					add_collision_bbox.GetMin()[X], add_collision_bbox.GetMin()[Y], add_collision_bbox.GetMin()[Z], 
+					add_collision_bbox.GetMax()[X], add_collision_bbox.GetMax()[Y], add_collision_bbox.GetMax()[Z]);
+		Dbg_Message("Scene bounding box: min (%f, %f, %f) max (%f, %f, %f)", 
+					m_collision_bbox.GetMin()[X], m_collision_bbox.GetMin()[Y], m_collision_bbox.GetMin()[Z], 
+					m_collision_bbox.GetMax()[X], m_collision_bbox.GetMax()[Y], m_collision_bbox.GetMax()[Z]);
+		Dbg_MsgAssert(add_collision_bbox.Within(m_collision_bbox), ("Can't add collision outside of original scene"));
+	}
+
+	if (m_num_add_coll_sectors > 0)
+	{
+		// Add to CSectors
+		for (int i = 0; i < m_num_add_coll_sectors; i++)
+		{
+			CSector *p_sector = GetSector(mp_add_coll_sectors[i].GetChecksum());
+			if (!p_sector)	// Don't assert now since there may not be renderable data
+			{
+				Dbg_Message("We don't necessarily want to be creating invisible sectors on an incremental update");
+
+				// Collision info, but not renderable
+				// so, we still need to create a Sector, but with no renderable component
+				p_sector = CreateSector(); 			// create empty secotr
+				p_sector->SetChecksum(mp_add_coll_sectors[i].GetChecksum());
+				p_sector->SetActive(true);
+				p_sector->set_flags(CSector::mADD_TO_SUPER_SECTORS);
+				AddSector(p_sector);
+
+				Lst::Node *node = new Lst::Node(p_sector);
+				mp_add_sectors->AddToTail(node);
+			}
+			p_sector->AddCollSector(&(mp_add_coll_sectors[i]));
+			Dbg_Assert(p_sector->get_flags() & CSector::mADD_TO_SUPER_SECTORS);
+		}
+
+		// Now add to SuperSectors
+		UpdateSuperSectors(mp_orig_sectors);
+	}
+
+	return plat_add_collision(p_name);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::CreateCollision(const Mth::CBBox & scene_bbox)
+{
+	// Make sure we asked for it at scene creation
+	if (!m_in_super_sectors)
+	{
+		return false;
+	}
+
+	m_collision_bbox = scene_bbox;
+	mp_sector_man = new SSec::Manager;
+	mp_sector_man->GenerateSuperSectors(m_collision_bbox);
+
+	return true;
+}
+
+#ifdef __NOPT_ASSERT__
+void			CScene::DebugRenderCollision(uint32 ignore_1, uint32 ignore_0)
+{
+	if (Nx::CEngine::GetWireframeMode() == 4 || Nx::CEngine::GetWireframeMode() == 5)
+	{
+	
+		#ifdef	__PLAT_NGPS__
+		#if 1
+		
+		// Traverse the tree at the mesh level
+		// so we can see the actual meshes
+		
+		CPs2Scene * p_ps2_scene = (CPs2Scene *) Nx::CEngine::sGetMainScene();
+		p_ps2_scene->GetEngineScene()->RenderWireframe(Nx::CEngine::GetWireframeMode());
+
+		
+			 
+		#else	 
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+		Mth::Vector	*p_verts = new Mth::Vector[100000];
+		Mem::Manager::sHandle().PopContext();
+		
+		
+		Nx::CScene	*p_scene = Nx::CEngine::sGetMainScene();
+		// crappy wireframe rendering of renderable, so we can see if there is any invislbe junk there
+		Lst::HashTable< Nx::CSector > * p_sector_list = p_scene->GetSectorList();
+		
+		int sectors = 0;
+		int vert_count=0;
+		int lines = 0;
+		if (p_sector_list)
+		{
+			p_sector_list->IterateStart();	
+			Nx::CSector *p_sector = p_sector_list->IterateNext();		
+			while(p_sector && lines <100000)   // Can't draw too many lines in this mode for some reason
+			{
+	
+				if (p_sector->IsActive())
+				{
+			
+							 
+					Nx::CGeom 	*p_geom = p_sector->GetGeom();
+					if (p_geom)
+					{
+					
+						NxPs2::CGeomNode *p_node = (((CPs2Geom*)p_geom)->GetEngineObject());
+						if (p_node->WasRendered())
+						{
+							sectors++;
+						
+							//
+							// Do renderable geometry
+							int verts = p_geom->GetNumRenderVerts();
+							if ( verts > 3000)
+							{
+							//	printf ("Strange no of verts %d on %s\n",verts, Script::FindChecksumName(p_sector->GetChecksum()));
+							}
+							if (verts < 30000)
+							{
+								//NxPs2::BeginLines3D(0x80000000 + (0x00ff00ff));		// Magenta 
+								
+								vert_count += verts;  
+								  
+								#define	peak 500
+								int n=p_geom->GetNumRenderPolys();
+								int r,g,b;
+								r=g=b=0;
+								if (n <= peak )
+								{
+									r = (255 * (n)) / peak;  	// r ramps up (black to red)	
+								}
+								else if (n <= peak * 2)
+								{
+									r = 255;
+									b = (255 * (n - peak) / (peak));	// b&g ramps up (to white)
+									g = b;
+									
+								}
+								else
+								{
+									r = g = b = 255;
+								}
+								uint32 rgb = (b<<16)|(g<<8)|r;	
+
+								
+								if (Nx::CEngine::GetWireframeMode() == 4)
+								{
+									NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & p_sector->GetChecksum()));		
+								}
+								else
+								{
+									NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));		
+								}
+								
+								p_geom->GetRenderVerts(p_verts);
+								Mth::Vector *p_vert = p_verts+1;
+								for (int i = 1; i < verts; i++)
+								{
+									NxPs2::DrawLine3D((*p_vert)[X],(*p_vert)[Y],(*p_vert)[Z],p_vert[-1][X],p_vert[-1][Y],p_vert[-1][Z]);								
+									p_vert++;
+									lines++;
+								} // end for
+						
+								NxPs2::EndLines3D();
+							} // end if
+						}						
+					}
+				}
+				p_sector = p_sector_list->IterateNext();
+			}
+		}
+//		printf ("Renderable wireframe %d sectors, %d verts, %d lines\n",sectors, vert_count, lines);
+		delete [] p_verts;
+		#endif
+		#endif
+	}	
+	else
+	{
+		if (!m_in_super_sectors)
+		{
+			// if collision data is not being used, then just return
+			return;
+		}
+	
+		if (mp_coll_sectors)
+		{
+			for (int i = 0; i < m_num_coll_sectors; i++)
+			{
+				mp_coll_sectors[i].DebugRender(ignore_1,ignore_0);
+			}
+		} else {
+			// Try finding the collision through the sectors
+			mp_sector_table->IterateStart();
+			CSector *p_sector;
+			while ((p_sector = mp_sector_table->IterateNext()))
+			{
+				if (p_sector->GetCollSector())	  // Might not have it yet in park editor
+				{
+					p_sector->GetCollSector()->DebugRender(ignore_1, ignore_0);
+				}
+			}
+		}
+	}
+}
+#endif
+
+void			CScene::DebugCheckForHoles()
+{
+
+	for (int i = 0; i < m_num_coll_sectors; i++)
+	{
+		printf ("%4d/%d ",i,m_num_coll_sectors);
+		CCollStaticTri *p_static_tri = static_cast(&(mp_coll_sectors[i]));
+		Dbg_Assert(p_static_tri);
+		p_static_tri->CheckForHoles();
+	}
+
+}
+
+
+// Return the number of collision sectors in the scene
+int		CScene::GetNumCollSectors()
+{
+	return m_num_coll_sectors;
+}
+
+// return a pointer to static collision data
+// for a collision sector
+// used
+CCollStaticTri * CScene::GetCollStatic(int n)
+{
+	return &(mp_coll_sectors[n]);	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CScene::UpdateSuperSectors(Lst::Head *p_additional_remove_list)
+{
+	// Check if we even have SuperSectors
+	if (!mp_sector_man)
+	{
+		return;
+	}
+
+	Lst::Head< Nx::CCollStatic > coll_add_list;
+	Lst::Head< Nx::CCollStatic > coll_remove_list;
+	Lst::Head< Nx::CCollStatic > coll_update_list;
+	Lst::Head< CSector > 	  sector_delete_list;
+
+	/*
+	Mem::Heap *p_top_heap = Mem::Manager::sHandle().TopDownHeap();
+	Mem::Region *p_top_region = p_top_heap->mp_region;
+	Ryan("UpdateSuperSectors()\n");
+	Ryan("  free %d\n", p_top_heap->mFreeMem.m_count + p_top_region->MemAvailable());
+	Ryan("  used %d\n", p_top_heap->mUsedMem.m_count);
+	Ryan("  largest %d\n", p_top_heap->LargestFreeBlock());
+	*/
+
+	// Allocate temp list on high heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+	mp_sector_table->IterateStart();
+
+	Lst::Node< Nx::CCollStatic > *node;
+	CSector *p_sector;
+
+	// Make Add and remove list
+	while ((p_sector = mp_sector_table->IterateNext()))
+	{
+		// Add List
+		if (p_sector->get_flags() & CSector::mADD_TO_SUPER_SECTORS)
+		{
+			node = new Lst::Node< Nx::CCollStatic > ( p_sector->GetCollSector() );
+			coll_add_list.AddToTail(node);
+			p_sector->clear_flags(CSector::mADD_TO_SUPER_SECTORS);
+			p_sector->set_flags(CSector::mIN_SUPER_SECTORS);
+
+			//Dbg_Message("Adding sector %x and collision %x of checksum %x", p_sector, p_sector->GetCollSector(), p_sector->GetChecksum());
+			//Mth::CBBox b_box = p_sector->GetCollSector()->GetGeometry()->GetBBox();
+			//Dbg_Message("Adding sector %x and collision %x and bounding box [%f,%f,%f]-[%f,%f,%f]", p_sector->GetChecksum(), p_sector->GetCollSector()->GetChecksum(),
+			//			   b_box.GetMin()[X], b_box.GetMin()[Y], b_box.GetMin()[Z],
+			//			   b_box.GetMax()[X], b_box.GetMax()[Y], b_box.GetMax()[Z]);
+
+			Dbg_MsgAssert(!(p_sector->get_flags() & CSector::mINVALID_SUPER_SECTORS),
+						  ("Trying to add collision that needed updating"));
+		}
+
+		// Remove List
+		if (p_sector->get_flags() & (CSector::mMARKED_FOR_DELETION | CSector::mREMOVE_FROM_SUPER_SECTORS))
+		{
+			node = new Lst::Node< Nx::CCollStatic > ( p_sector->GetCollSector() );
+			coll_remove_list.AddToTail(node);
+
+			// Also make a sector delete list
+			if (p_sector->get_flags() & CSector::mMARKED_FOR_DELETION)
+			{
+				Lst::Node< CSector > *sec_node = new Lst::Node< CSector > (p_sector);
+				sector_delete_list.AddToTail(sec_node);
+			} else {
+				p_sector->clear_flags(CSector::mREMOVE_FROM_SUPER_SECTORS);
+			}
+
+			//Dbg_Message("Deleting sector %x and collision %x of checksum %x", p_sector, p_sector->GetCollSector(), p_sector->GetChecksum());
+			//Mth::CBBox b_box = p_sector->GetCollSector()->GetGeometry()->GetBBox();
+			//Dbg_Message("Deleting sector %x and collision %x and bounding box [%f,%f,%f]-[%f,%f,%f]", p_sector->GetChecksum(), p_sector->GetCollSector()->GetChecksum(),
+			//				b_box.GetMin()[X], b_box.GetMin()[Y], b_box.GetMin()[Z],
+			//				b_box.GetMax()[X], b_box.GetMax()[Y], b_box.GetMax()[Z]);
+			Dbg_MsgAssert(!(p_sector->get_flags() & CSector::mADD_TO_SUPER_SECTORS),
+						  ("Collision being both added and deleted from SuperSectors"));
+			Dbg_MsgAssert(p_sector->get_flags() & CSector::mIN_SUPER_SECTORS,
+						  ("Trying to remove sector from SuperSector that isn't in any SuperSector"));
+
+			Dbg_MsgAssert(!(p_sector->get_flags() & CSector::mINVALID_SUPER_SECTORS),
+						  ("Trying to remove collision that hasn't been updated"));
+		}
+
+		// Update List
+		if (p_sector->get_flags() & CSector::mINVALID_SUPER_SECTORS)
+		{
+			node = new Lst::Node< Nx::CCollStatic > ( p_sector->GetCollSector() );
+			coll_update_list.AddToTail(node);
+			p_sector->clear_flags(CSector::mINVALID_SUPER_SECTORS);
+
+			//Mth::CBBox b_box = p_sector->GetCollSector()->GetGeometry()->GetBBox();
+			//Dbg_Message("Updating sector %x and collision %x and bounding box [%f,%f,%f]-[%f,%f,%f]", p_sector->GetChecksum(), p_sector->GetCollSector()->GetChecksum(),
+			//			   b_box.GetMin()[X], b_box.GetMin()[Y], b_box.GetMin()[Z],
+			//			   b_box.GetMax()[X], b_box.GetMax()[Y], b_box.GetMax()[Z]);
+
+			Dbg_MsgAssert(p_sector->get_flags() & CSector::mIN_SUPER_SECTORS,
+						  ("Collision being updated that isn't in SuperSectors"));
+		}
+	}
+
+	// Check additional remove list
+	if (p_additional_remove_list)
+	{
+		Lst::Node< CSector > *obj_node, *next;
+
+		for(obj_node = p_additional_remove_list->GetNext(); obj_node; obj_node = next)
+		{
+			next = obj_node->GetNext();
+			p_sector = obj_node->GetData();
+
+			Dbg_Assert(!(p_sector->get_flags() & CSector::mMARKED_FOR_DELETION));		// Make sure it shouldn't be deleted
+
+			// Make sure it isn't already on the list
+			bool new_node = true;
+			if(coll_remove_list.CountItems() > 0)
+			{
+				Lst::Node *node_coll, *node_next;
+				for(node_coll = coll_remove_list.GetNext(); node_coll; node_coll = node_next)
+				{
+					node_next = node_coll->GetNext();
+					if (node_coll->GetData() == p_sector->GetCollSector())
+					{
+						new_node = false;
+						break;
+					}
+
+				}
+			}
+
+			if (new_node)
+			{
+				node = new Lst::Node< Nx::CCollStatic > ( p_sector->GetCollSector() );
+				coll_remove_list.AddToTail(node);
+				p_sector->clear_flags(CSector::mREMOVE_FROM_SUPER_SECTORS);
+				//Dbg_Message("Deleting additional sector %x and collision %x of checksum %x", p_sector, p_sector->GetCollSector(), p_sector->GetChecksum());
+				//Dbg_Message("Deleting additional sector %x and collision %x", p_sector->GetChecksum(), p_sector->GetCollSector()->GetChecksum());
+			}
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+
+	// Do the actual update
+	mp_sector_man->UpdateCollisionSuperSectors(coll_add_list, coll_remove_list, coll_update_list);
+
+	Lst::Node *node_coll, *node_next;
+
+	//
+	// Free lists
+	//
+	if(coll_add_list.CountItems() > 0)
+	{
+		for(node_coll = coll_add_list.GetNext(); node_coll; node_coll = node_next)
+		{
+			node_next = node_coll->GetNext();
+
+			delete node_coll;
+		}
+	}
+
+	if(coll_remove_list.CountItems() > 0)
+	{
+		for(node_coll = coll_remove_list.GetNext(); node_coll; node_coll = node_next)
+		{
+			node_next = node_coll->GetNext();
+
+			delete node_coll;
+		}
+	}
+
+	if(coll_update_list.CountItems() > 0)
+	{
+		for(node_coll = coll_update_list.GetNext(); node_coll; node_coll = node_next)
+		{
+			node_next = node_coll->GetNext();
+
+			delete node_coll;
+		}
+	}
+
+	// And finally delete the marked sectors
+	if(sector_delete_list.CountItems() > 0)
+	{
+		Lst::Node *node_sector, *node_sec_next;
+		for(node_sector = sector_delete_list.GetNext(); node_sector; node_sector = node_sec_next)
+		{
+			node_sec_next = node_sector->GetNext();
+
+			p_sector = node_sector->GetData();
+
+			mp_sector_table->FlushItem(p_sector->GetChecksum());
+
+			p_sector->clear_flags(CSector::mIN_SUPER_SECTORS);
+			delete p_sector;
+
+			delete node_sector;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CScene::ClearSuperSectors()
+{
+	// Check if we even have SuperSectors
+	if (!mp_sector_man)
+	{
+		return;
+	}
+
+	mp_sector_table->IterateStart();
+
+	CSector *p_sector;
+
+	// Check all sectors to make sure they need deleting
+	while ((p_sector = mp_sector_table->IterateNext()))
+	{
+		// Remove List
+		if (p_sector->get_flags() & CSector::mMARKED_FOR_DELETION)
+		{
+			//Dbg_Message("Deleting sector %x and collision %x of checksum %x", p_sector, p_sector->GetCollSector(), p_sector->GetChecksum());
+			Dbg_MsgAssert(!(p_sector->get_flags() & CSector::mADD_TO_SUPER_SECTORS),
+						  ("Collision being both added and deleted from SuperSectors"));
+			Dbg_MsgAssert(p_sector->get_flags() & CSector::mIN_SUPER_SECTORS,
+						  ("Trying to remove sector from SuperSector that isn't in any SuperSector"));
+
+			Dbg_MsgAssert(!(p_sector->get_flags() & CSector::mINVALID_SUPER_SECTORS),
+						  ("Trying to remove collision that hasn't been updated"));
+
+			p_sector->clear_flags(CSector::mIN_SUPER_SECTORS);
+			delete p_sector;
+		} else {
+			Dbg_MsgAssert(0, ("Trying to clear SuperSectors even though sector %x hasn't been marked for deletion", p_sector->GetChecksum()));
+		}
+	}
+
+	// Do the actual clear
+	mp_sector_man->ClearCollisionSuperSectors();
+
+	// And clear the hash table
+	mp_sector_table->FlushAllItems();
+}
+
+// Given a bounding box, then set the active flag of those 
+// sectors that intersect this box by at least a certain abount 
+void CScene::SetActiveInBox(Mth::CBBox &box, bool active, float min_intersect)
+{
+	
+	Mth::CBBox smaller_box = box;
+	
+	// later..
+//	smaller_box.Shrink(min_intersect);   
+	
+	mp_sector_table->IterateStart();
+	CSector *p_sector;
+	while ((p_sector = mp_sector_table->IterateNext()))
+	{
+//		printf ("Checking sector\n");
+		if (smaller_box.Intersect(p_sector->GetBoundingBox()))
+		{
+//			printf("Setting active = %d\n",active);
+			p_sector->SetActive(active);
+		}
+	}
+
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Create an empty sector
+CSector	*			CScene::CreateSector()
+{
+	CSector	*p_sector = plat_create_sector();
+	p_sector->mp_geom = NULL;
+	p_sector->mp_coll_sector = NULL;
+	return p_sector;
+}
+
+
+void	CScene::GetMetrics(Script::CStruct * p_info)
+{
+	p_info->AddString(CRCD(0xc3f4169a,"FileName"),GetSceneFilename());
+
+	int total_sectors = 0;
+	int render_sectors = 0;	
+	int render_verts = 0;	
+	int render_polys = 0;	
+	int render_base_polys = 0;	
+	// Iterate over the sectors, counting verious things	
+	Lst::HashTable< Nx::CSector > * p_sector_list = GetSectorList();
+	if (p_sector_list)
+	{
+		p_sector_list->IterateStart();	
+		Nx::CSector *p_sector = p_sector_list->IterateNext();		
+		while(p_sector)
+		{
+			total_sectors++;
+			Nx::CGeom 	*p_geom = p_sector->GetGeom();
+			if (p_geom)
+			{
+				// Do renderable geometry
+				render_sectors++;
+				render_verts += p_geom->GetNumRenderVerts();
+				render_polys += p_geom->GetNumRenderPolys();
+				render_base_polys += p_geom->GetNumRenderBasePolys();
+			}
+			p_sector = p_sector_list->IterateNext();
+		}
+	}
+	
+	p_info->AddInteger(CRCD(0x4a6bf967,"Sectors"),total_sectors);
+	p_info->AddInteger(CRCD(0xac30a9d,"ColSectors"),GetNumCollSectors());
+	p_info->AddInteger(CRCD(0x969d3af6,"Verts"),render_verts);
+	p_info->AddInteger(CRCD(0xd576df05,"Polys"),render_polys);
+	p_info->AddInteger(CRCD(0x1e3061ee,"BasePolys"),render_base_polys);
+
+	p_info->AddInteger(CRCD(0x5714c480,"TextureMemory"),GetTexDict()->GetFileSize());
+	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CScene::SetMajorityColor(Image::RGBA rgba)
+{
+	plat_set_majority_color(rgba);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA	CScene::GetMajorityColor() const
+{
+	return plat_get_majority_color();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CScene::plat_post_load()
+{
+	printf ("STUB: PostLoad\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CScene::plat_post_add()
+{
+	printf ("STUB: PostAdd\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::plat_load_textures(const char *p_name)
+{
+	printf ("STUB: LoadTextures\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::plat_load_collision(const char *p_name)
+{
+	printf ("STUB: LoadCollision\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::plat_add_collision(const char *p_name)
+{
+	printf ("STUB: AddCollision\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CScene::plat_unload_add_scene()
+{
+	printf ("STUB: UnloadAddScene\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector*			CScene::plat_create_sector()
+{
+	printf ("STUB: plat_create_sector\n");
+	return NULL;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CScene::plat_set_majority_color(Image::RGBA rgba)
+{
+	// STUB: only really needed on PS2, so ignorable on other platforms
+}
+		 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA			CScene::plat_get_majority_color() const
+{
+	// STUB: only really needed on PS2, so ignorable on other platforms
+	return Image::RGBA(0x80, 0x80, 0x80, 0x80);
+}
+
+
+
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/NxScene.h b/Code/Gfx/NxScene.h
new file mode 100644
index 0000000..9358464
--- /dev/null
+++ b/Code/Gfx/NxScene.h
@@ -0,0 +1,322 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NXSCENE.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_SCENE_H__
+#define	__GFX_NX_SCENE_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+#include 
+#include 	
+
+
+namespace Script
+{
+	class	CStruct;
+}
+
+namespace Nx
+{
+/////////////////////////////////////////////////////////////////////////
+// Forward declarations
+class	CSector;
+class	CTexDict;
+class	CCollStaticTri;
+class	CCollObjTriData;
+
+////////////////////////////////////////////////////////////////////////
+// CScene:
+//
+// Holds all the CSectors for a scene.
+//
+
+class		CScene
+{
+public:
+								CScene( int sector_table_size = 10 );
+	virtual						~CScene();
+
+	void						PostLoad(const char *p_name);
+	void						PostAdd(const char *p_name, Nx::CTexDict * p_tex_dict);
+	bool						LoadTextures(const char *p_name);			// load scene textures
+	bool						LoadCollision(const char *p_name, bool is_net=false);			// load scene collision data
+	bool						AddCollision(const char *p_name);			// add scene collision data
+	bool						CreateCollision(const Mth::CBBox & scene_bbox); // load scene collision data
+
+	bool						ToggleAddScene();							// Toggle the orig and add sectors
+	bool						UnloadAddScene();							// unloads previous add scene
+
+	bool						InSuperSectors() const;
+	void						SetInSuperSectors(bool);
+	bool						IsSky() const;
+	void						SetIsSky(bool);
+
+	CSector	*					GetSector(uint32 sector_checksum);			// get a sector pointer, based on the checksum of the name
+	void						AddSector(CSector *pSector);				// Add a sector to the list
+	Lst::HashTable *	GetSectorList() const;			  			// get the whole list
+
+	// Replacing a sector is for in-house testing.  Since PIP'ed CSectors can't be deleted individually,
+	// the replace will hide the old one.
+	CSector *					ReplaceSector(CSector *pSector);			// returns old sector, if any
+
+	// Cloning copies a sector, puts it in hash table, and returns its checksum (p_dest_scene of NULL uses current scene)
+	uint32						CloneSector(uint32 orig_sector_checksum, CScene *p_dest_scene = NULL,
+											bool instance = false, bool add_to_super_sectors = true);
+	uint32						CloneSector(CSector *p_orig_sector, CScene *p_dest_scene = NULL,
+											bool instance = false, bool add_to_super_sectors = true);
+
+	// creates an empty sector
+	CSector	*					CreateSector();
+	
+	
+	// Deletes a sector and takes it off the list
+	bool						DeleteSector(uint32 sector_checksum);
+	bool						DeleteSector(CSector *p_sector);
+
+	// Updates the SuperSectors with any clone or quickupdate changes
+	void						UpdateSuperSectors(Lst::Head *p_additional_remove_list = NULL);
+	void						ClearSuperSectors();						// note ALL sectors must already be marked for deletion
+
+	// allow access to the collision sectors												  
+	int							GetNumCollSectors();
+	CCollStaticTri * 			GetCollStatic(int n);
+
+
+	// Sets sectors active or inactive if they are inside a particular bounding box																								  
+	void 						SetActiveInBox(Mth::CBBox &box, bool active, float min_intersect);
+	
+	const Mth::CBBox &			GetRenderBoundingBox() const;				// get bounding box of scene renderables
+	const Mth::CBBox &			GetCollisionBoundingBox() const;			// get bounding box of scene collision
+	SSec::Manager *				GetSuperSectorManager() const;				// get the super sectors, if any
+
+	void						SetTexDict(CTexDict * p_tex_dict);			// set texture dictionary
+	CTexDict *					GetTexDict() const;							// set texture dictionary
+
+	void						SetID(uint32 id);							// Set name checksum ID
+	uint32						GetID() const;
+	const char *				GetSceneFilename() const;
+	const char *				GetAddSceneFilename() const;
+
+	void						DebugRenderCollision(uint32 ignore_1, uint32 ignore_0);						// render in wireframe
+	void						DebugCheckForHoles();
+
+	void						GetMetrics(Script::CStruct * p_info);
+
+	void						SetMajorityColor(Image::RGBA rgba);
+	Image::RGBA					GetMajorityColor() const;
+
+protected:
+	// read scene collision file
+	bool						read_collision(const char *p_name, char *p_pip_name, int &num_coll_sectors,
+											   CCollStaticTri * &p_coll_sectors, CCollObjTriData * &p_coll_sector_data,
+											   Mth::CBBox &bbox, bool is_net=false);
+
+	void						remove_sector_from_table(uint32 sector_checksum);
+
+	char						m_scene_filename[128];						// scene filename (kept around for unload)
+	uint32						m_id;										// checksum of name
+	
+	CTexDict *					mp_tex_dict;								// texture dictionary that this scene uses	
+	
+	bool						m_in_super_sectors;							// Is this scene in the super sectors
+	bool						m_sky;										// Tells if scene is a sky type
+
+	Mth::CBBox					m_render_bbox;								// scene renderable bounding box
+	Mth::CBBox					m_collision_bbox;							// scene collision bounding box
+	Lst::HashTable< CSector > *	mp_sector_table;							// All the CSector pointers
+	SSec::Manager *				mp_sector_man;								// SuperSector manager
+
+	char						m_coll_filename[128];						// collision filename (kept around for unload)
+	int							m_num_coll_sectors;							// non-cloned collision
+	CCollStaticTri *			mp_coll_sectors;							// Static collision objects
+	CCollObjTriData *			mp_coll_sector_data;						// Static collision object data
+
+	// For the incremental update code
+	char						m_add_scene_filename[128];
+	CTexDict *					mp_add_tex_dict; 
+	char						m_add_coll_filename[128];
+	int							m_num_add_coll_sectors;
+	CCollStaticTri *			mp_add_coll_sectors;
+	CCollObjTriData *			mp_add_coll_sector_data;
+
+	bool						m_using_add_sectors;
+	Lst::Head < CSector > *		mp_orig_sectors;
+	Lst::Head < CSector > *		mp_add_sectors;
+
+private:
+	////////////////////////////////////////////////////////////
+	// Platform specific function calls	
+	//
+	virtual void				plat_post_load();
+	virtual void				plat_post_add();
+	virtual bool				plat_load_textures(const char *p_name);		// load textures 
+	virtual bool				plat_load_collision(const char *p_name);	// load collision data
+	virtual bool				plat_add_collision(const char *p_name);		// add collision data
+	virtual bool				plat_unload_add_scene();					// unloads previous add scene
+	virtual CSector	*			plat_create_sector();  						// create empty sector
+	virtual void				plat_set_majority_color(Image::RGBA rgba);	// set the most common sector color
+	virtual Image::RGBA			plat_get_majority_color() const;			// get the most common sector color
+};
+
+///////////////////////////////////////////////////////////////////////
+//
+// Inline functions
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Lst::HashTable< CSector > * CScene::GetSectorList() const
+{
+	return mp_sector_table;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CScene::InSuperSectors() const
+{
+	return m_in_super_sectors;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CScene::SetInSuperSectors(bool s)
+{
+	m_in_super_sectors = s;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool					CScene::IsSky() const
+{
+	return m_sky;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void					CScene::SetIsSky(bool s)
+{
+	m_sky = s;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::CBBox &	CScene::GetRenderBoundingBox() const
+{
+	return m_render_bbox;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::CBBox &	CScene::GetCollisionBoundingBox() const
+{
+	return m_collision_bbox;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline SSec::Manager *		CScene::GetSuperSectorManager() const
+{
+	return mp_sector_man;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline 	void				CScene::SetTexDict(CTexDict * p_tex_dict)
+{
+	mp_tex_dict = p_tex_dict;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	CTexDict *			CScene::GetTexDict() const
+{
+	return mp_tex_dict;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline 	void				CScene::SetID(uint32 id)
+{
+	m_id = id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	uint32				CScene::GetID() const
+{
+	return m_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	const char *		CScene::GetSceneFilename() const
+{
+	return m_scene_filename;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	const char *		CScene::GetAddSceneFilename() const
+{
+	return m_add_scene_filename;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline void					CScene::remove_sector_from_table(uint32 sector_checksum)
+{
+	mp_sector_table->FlushItem(sector_checksum);
+}
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NxSector.cpp b/Code/Gfx/NxSector.cpp
new file mode 100644
index 0000000..f49407f
--- /dev/null
+++ b/Code/Gfx/NxSector.cpp
@@ -0,0 +1,919 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxSector.cpp
+
+#include "gfx/nx.h"
+#include "gfx/nxgeom.h"
+#include "gfx/nxsector.h"
+#include "gfx/nxflags.h"
+#include "gel/collision/collision.h"
+#include 
+
+namespace	Nx
+{
+///////////////////////////////////////////////////////////////////
+// CSector definitions
+
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+					 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector::CSector()
+{
+	m_flags = mCOLLIDE;
+	mp_geom = NULL;
+	mp_coll_sector = NULL;
+	m_rot_y = Mth::ROT_0;
+	uint32	light_group = CRCD(0xe3714dc1,"outdoor");			 // default to OutDoors
+	SetLightGroup(light_group);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector::~CSector()
+{
+	if (mp_coll_sector && (m_flags & mCLONE))	// cloned collision isn't in scene array
+	{
+		// Check if in SuperSectors
+		if (m_flags & mIN_SUPER_SECTORS)
+		{
+			Dbg_MsgAssert(0, ("Can't free CSector when it is still on the SuperSector lists"));
+		}
+
+		delete mp_coll_sector;
+	}
+
+	if (mp_geom /*&& (m_flags & mCLONE)*/)			// cloned geometry isn't in scene array
+	{
+		delete mp_geom;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CSector::GetChecksum() const
+{
+	return m_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetChecksum(uint32 c)
+{
+	m_checksum = c;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom *	CSector::GetGeom() const
+{
+	return mp_geom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetGeom(CGeom *p_geom)
+{
+	mp_geom = p_geom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetColor(Image::RGBA rgba)
+{		
+	if (mp_geom)
+	{
+		mp_geom->SetColor(rgba);
+	//} else {
+	//	plat_set_color(rgba);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::ClearColor()
+{		
+	if (mp_geom)
+	{
+		mp_geom->ClearColor();
+	//} else {
+	//	plat_clear_color();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA	CSector::GetColor() const
+{		
+	if (mp_geom)
+	{
+		return mp_geom->GetColor();
+	} else {
+		return Image::RGBA(0, 0, 0, 0);
+	//	return plat_get_color();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetVisibility(uint32 mask)
+{
+	if (mp_geom)
+	{
+		uint32 vis=mp_geom->GetVisibility();
+		if ( (vis^mask) & 0x01 )
+		{
+			Replay::WriteSectorVisibleStatus(m_checksum,mask&0x01);
+		}	
+		mp_geom->SetVisibility(mask);
+	//} else {
+	//	plat_set_visibility(mask);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CSector::GetVisibility() const
+{
+	if (mp_geom)
+	{
+		return mp_geom->GetVisibility();
+	} else {
+		return 0;
+	//	return plat_get_visibility();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetActive(bool on)
+{
+	// Do collision also
+	if (mp_coll_sector)
+	{
+		if (on) {
+			mp_coll_sector->ClearObjectFlags(mSD_KILLED);
+		} else {
+			mp_coll_sector->SetObjectFlags(mSD_KILLED);
+		}
+	}
+
+	if (mp_geom)
+	{
+		if (mp_geom->IsActive() != on)
+		{
+			Replay::WriteSectorActiveStatus(m_checksum,on);
+		}
+		mp_geom->SetActive(on);
+	//} else {
+	//	plat_set_active(on);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSector::IsActive() const
+{
+	if (mp_geom)
+	{
+		return mp_geom->IsActive();
+	} else {
+		Dbg_Assert(mp_coll_sector);
+
+		return !(mp_coll_sector->GetObjectFlags() & mSD_KILLED);
+	//	return plat_is_active();
+	}
+}
+
+void CSector::SetActiveAtReplayStart(bool on)
+{
+	if (on)
+	{
+		m_flags|=mACTIVE_AT_REPLAY_START;
+	}
+	else
+	{
+		m_flags&=~mACTIVE_AT_REPLAY_START;
+	}
+}
+
+bool CSector::GetActiveAtReplayStart() const
+{
+	return m_flags&mACTIVE_AT_REPLAY_START;
+}
+
+void CSector::SetVisibleAtReplayStart(bool on)
+{
+	if (on)
+	{
+		m_flags|=mVISIBLE_AT_REPLAY_START;
+	}
+	else
+	{
+		m_flags&=~mVISIBLE_AT_REPLAY_START;
+	}
+}
+
+bool CSector::GetVisibleAtReplayStart() const
+{
+	return m_flags&mVISIBLE_AT_REPLAY_START;
+}
+
+void CSector::SetStoredActive(bool on)
+{
+	if (on)
+	{
+		m_flags|=mSTORED_ACTIVE;
+	}
+	else
+	{
+		m_flags&=~mSTORED_ACTIVE;
+	}
+}
+
+bool CSector::GetStoredActive() const
+{
+	return m_flags&mSTORED_ACTIVE;
+}
+
+void CSector::SetStoredVisible(bool on)
+{
+	if (on)
+	{
+		m_flags|=mSTORED_VISIBLE;
+	}
+	else
+	{
+		m_flags&=~mSTORED_VISIBLE;
+	}
+}
+
+bool CSector::GetStoredVisible() const
+{
+	return m_flags&mSTORED_VISIBLE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::CBBox & CSector::GetBoundingBox() const
+{
+	if (mp_geom)
+	{
+		return mp_geom->GetBoundingBox();
+	} else {
+		Dbg_Assert(mp_coll_sector);
+
+		CCollObjTriData *p_coll_data = mp_coll_sector->GetGeometry();
+
+		Dbg_Assert(p_coll_data);
+
+		return p_coll_data->GetBBox();
+	//	return plat_get_bounding_box();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetWorldPosition(const Mth::Vector& pos)
+{
+	// Do collision first
+	if (mp_coll_sector)
+	{
+		mp_coll_sector->SetWorldPosition(pos);
+		if (m_flags & mIN_SUPER_SECTORS)
+		{
+			m_flags |= mINVALID_SUPER_SECTORS;
+		}
+
+	}
+
+	// Then Geometry
+	if (mp_geom)
+	{
+		mp_geom->SetWorldPosition(pos);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CSector::GetWorldPosition() const
+{
+	if (mp_geom)
+	{
+		return mp_geom->GetWorldPosition();
+	} else {
+		Dbg_Assert(mp_coll_sector);
+
+		return mp_coll_sector->GetWorldPosition();
+	//	return plat_get_world_position();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetYRotation(Mth::ERot90 rot)
+{
+	Mth::ERot90 delta_rot = (Mth::ERot90) (((rot + Mth::NUM_ROTS) - m_rot_y) % Mth::NUM_ROTS);
+	//Dbg_Message("SetYRotation: new rot %d sector rot %d delta rot %d", rot, m_rot_y, delta_rot);
+
+	// Do collision first
+	if (mp_coll_sector)
+	{
+		mp_coll_sector->RotateY(GetWorldPosition(), delta_rot);
+	}
+
+	if (mp_geom)
+	{
+		mp_geom->RotateY(delta_rot);
+	//} else {
+	//	plat_set_y_rotation(delta_rot);
+	}
+
+	// Now change value
+	m_rot_y = rot;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::ERot90	CSector::GetYRotation() const
+{
+	return m_rot_y;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetScale(const Mth::Vector & scale)
+{
+	// Do collision first
+	if (mp_coll_sector)
+	{
+		mp_coll_sector->Scale(GetWorldPosition(), scale);
+		if (m_flags & mIN_SUPER_SECTORS)
+		{
+			m_flags |= mINVALID_SUPER_SECTORS;
+		}
+
+	}
+
+	if (mp_geom)
+	{
+		mp_geom->SetScale(scale);
+	}
+
+	// Now change value
+	m_scale = scale;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CSector::GetScale() const
+{
+	return m_scale;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		CSector::GetNumCollVertices() const
+{
+	if (mp_coll_sector && mp_coll_sector->GetGeometry())
+	{
+		return mp_coll_sector->GetGeometry()->GetNumVerts();
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("SetRawCollVertices(): Can't find collision geometry"));
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::GetRawCollVertices(Mth::Vector *p_vert_array) const
+{
+	if (mp_coll_sector && mp_coll_sector->GetGeometry())
+	{
+		mp_coll_sector->GetGeometry()->GetRawVertices(p_vert_array);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("SetRawCollVertices(): Can't find collision geometry"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetRawCollVertices(const Mth::Vector *p_vert_array)
+{
+	if (mp_coll_sector && mp_coll_sector->GetGeometry())
+	{
+		mp_coll_sector->GetGeometry()->SetRawVertices(p_vert_array);
+
+		// Invalidate sector in SuperSectors
+		if (m_flags & mIN_SUPER_SECTORS)
+		{
+			Dbg_MsgAssert(!(m_flags & mREMOVE_FROM_SUPER_SECTORS), ("Can't change collision verts of a sector being removed"));
+			m_flags |= mINVALID_SUPER_SECTORS;
+		}
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("SetRawCollVertices(): Can't find collision geometry"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetCollidable(bool on)
+{
+	if (on) {
+		m_flags |= mCOLLIDE;
+	} else {
+		m_flags &= ~mCOLLIDE;
+	}
+
+	if (mp_coll_sector)
+	{
+		if (on) {
+			mp_coll_sector->ClearObjectFlags(mSD_NON_COLLIDABLE);
+		} else {
+			mp_coll_sector->SetObjectFlags(mSD_NON_COLLIDABLE);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSector::IsCollidable() const
+{
+	return (m_flags & mCOLLIDE) || IsActive();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetOccluder(bool on)
+{
+	Dbg_MsgAssert(mp_coll_sector,("Flagging a sector as occluder that has no collision info"));
+	if (mp_coll_sector)
+	{
+		if (!on)
+		{
+			mp_coll_sector->ClearObjectFlags(mSD_OCCLUDER);
+		}
+		else 
+		{
+			if (! (mp_coll_sector->GetObjectFlags() & mSD_OCCLUDER))
+			{
+				mp_coll_sector->SetObjectFlags(mSD_OCCLUDER);
+				
+				// we've just set some collision as an occluder for the first time
+				// so now would be a good time to tell the engine about these new occlusion polygons
+				
+				CCollStaticTri * p_coll_static = static_cast(mp_coll_sector);
+				Dbg_MsgAssert(p_coll_static,("World sector has non-static collision???"));
+				
+				p_coll_static->ProcessOcclusion();
+			}
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::SetShatter(bool on)
+{
+	Replay::WriteShatter(m_checksum,on);
+	plat_set_shatter(on);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSector::GetShatter() const
+{
+	return plat_get_shatter();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSector::AddCollSector(Nx::CCollStatic *p_coll_sector)
+{
+	Dbg_Assert(m_checksum == p_coll_sector->GetChecksum());
+
+	if (!mp_coll_sector)
+	{
+		mp_coll_sector = p_coll_sector;
+		if (mp_geom)
+		{
+			mp_geom->SetCollTriData(p_coll_sector->GetGeometry());
+		}
+
+		return true;
+	} else {
+		Dbg_MsgAssert(0, ("CSector::AddCollSector(): adding second collision sector to %x\n", m_checksum));
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CCollStatic *	CSector::GetCollSector() const
+{
+	return mp_coll_sector;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32		CSector::get_flags() const
+{
+	return m_flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSector::SetUVWibbleParams(float u_vel, float u_amp, float u_freq, float u_phase,
+											   float v_vel, float v_amp, float v_freq, float v_phase)
+{
+	if (mp_geom)
+	{
+		mp_geom->SetUVWibbleParams(u_vel, u_amp, u_freq, u_phase, v_vel, v_amp, v_freq, v_phase);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSector::UseExplicitUVWibble(bool yes)
+{
+	if (mp_geom)
+	{
+		mp_geom->UseExplicitUVWibble(yes);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSector::SetUVWibbleOffsets(float u_offset, float v_offset)
+{
+	if (mp_geom)
+	{
+		mp_geom->SetUVWibbleOffsets(u_offset, v_offset);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSector::set_flags(uint32 flags)
+{
+	m_flags |= flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSector::clear_flags(uint32 flags)
+{
+	m_flags &= ~flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector *	CSector::clone(bool instance, bool add_to_super_sectors, CScene *p_dest_scene)
+{
+	// Create new instance and clone stuff below p-line
+	CSector *p_new_sector = plat_clone(instance, p_dest_scene);
+
+	if (p_new_sector)
+	{
+		// Get new checksum
+		p_new_sector->m_checksum = CEngine::sGetUnusedSectorChecksum();
+
+		// Now clone stuff above p-line
+		p_new_sector->m_flags	= m_flags | mCLONE;		// mark as clone
+		p_new_sector->m_rot_y 	= m_rot_y;
+
+		if (mp_geom)
+		{
+			p_new_sector->mp_geom = mp_geom->Clone(instance, p_dest_scene);
+			Dbg_Assert(p_new_sector->mp_geom);
+		}
+
+		if (mp_coll_sector)
+		{
+			
+			// Note, static_cast seems to be essentially a macro
+			// so you must pass it a pointer, and not the result of an expression
+			// otherwise it will continually re-evaluate that expression
+			// which here would casue memory leaks.																			  
+			CCollObj *p_coll_obj = mp_coll_sector->Clone(false /*instance*/);	// Garrett: We're not even going to try to instamce it
+			if (p_coll_obj)
+			{
+				p_new_sector->mp_coll_sector = static_cast(p_coll_obj);
+
+				p_new_sector->mp_coll_sector->SetChecksum(p_new_sector->m_checksum);
+
+				p_new_sector->m_flags &= ~mIN_SUPER_SECTORS;		// Not in there yet
+
+				if (add_to_super_sectors)
+				{
+					p_new_sector->m_flags |= mADD_TO_SUPER_SECTORS;
+				}
+
+				Dbg_Assert(p_new_sector->mp_coll_sector);
+
+				// For now, just copy the CCollObjTriData pointer over
+				if (p_new_sector->mp_geom)
+				{
+					// Garrett: Commented assert out because the pointer is now copied in CGeom
+					//Dbg_Assert(p_new_sector->mp_geom->GetCollTriData() == NULL);
+					p_new_sector->mp_geom->SetCollTriData(p_coll_obj->GetGeometry());
+				}
+
+				Dbg_Assert(p_coll_obj->GetGeometry() != mp_coll_sector->GetGeometry());
+			}
+			else
+			{
+				p_new_sector->mp_coll_sector = NULL;
+				p_new_sector->m_flags &= ~mIN_SUPER_SECTORS;		// Not in there at all
+				if (p_new_sector->mp_geom)
+				{
+					p_new_sector->mp_geom->SetCollTriData(NULL);
+				}
+			}
+		}
+		
+		return p_new_sector;
+	} else {
+		return NULL;
+	}
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+						
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::plat_set_color(Image::RGBA rgba)
+{
+	printf ("STUB: PlatSetColor\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA CSector::plat_get_color() const
+{
+	printf ("STUB: PlatGetColor\n");
+	return Image::RGBA(0, 0, 0, 0);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::plat_clear_color()
+{
+	printf ("STUB: PlatClearColor\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::plat_set_visibility(uint32 mask)
+{
+	printf ("STUB: PlatSetVisibility\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CSector::plat_get_visibility() const
+{
+	printf ("STUB: PlatGetVisibility\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::plat_set_active(bool on)
+{
+	printf ("STUB: PlatSetActive\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSector::plat_is_active() const
+{
+	printf ("STUB: PlatIsActive\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ void	CSector::plat_set_world_position(const Mth::Vector& pos)
+ {
+	 printf ("STUB: PlatSetWorldPosition\n");
+ }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::CBBox &	CSector::plat_get_bounding_box() const
+{
+	static Mth::CBBox stub_bbox;
+	printf ("STUB: PlatGetBoundingBox\n");
+	return stub_bbox;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const Mth::Vector &	CSector::plat_get_world_position() const
+{
+	static Mth::Vector stub_vec;
+	printf ("STUB: PlatGetWorldPosition\n");
+	return stub_vec;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::plat_set_y_rotation(Mth::ERot90 rot)
+{
+	printf ("STUB: PlatSetYRotation\n");
+}
+#endif // 0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSector::plat_set_shatter(bool on)
+{
+	printf ("STUB: PlatSetShatter\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSector::plat_get_shatter() const
+{
+	printf ("STUB: PlatGetShatter\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSector *	CSector::plat_clone(bool instance, CScene *p_dest_scene)
+{
+	printf ("STUB: PlatClone\n");
+	return NULL;
+}
+
+}
+
+
diff --git a/Code/Gfx/NxSector.h b/Code/Gfx/NxSector.h
new file mode 100644
index 0000000..7bb3935
--- /dev/null
+++ b/Code/Gfx/NxSector.h
@@ -0,0 +1,219 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxSector.h
+
+
+
+#ifndef	__GFX_NXSECTOR_H__
+#define	__GFX_NXSECTOR_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 	
+
+namespace Mth {
+	class Vector;
+	class CBBox;
+}
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////
+// The Sector Story:
+//
+// To the designer, A "Sector" (previously a "world sector" is an
+// object in 3D Studio Max.  Typically this might be a tree, a wall
+// a section of a building, a parked car or any other static part of the world
+//
+// The designer wants to be able to:
+// -  load the sector as part of the scene, see it and collide with it
+// -  reference the sector by name
+// -  Turn it on and off (both collision and display)
+// -  make it visible and invisible in particular viewports for LOD reasons
+// -  change its color
+// -  change various attributes, like UV wibble speed.  
+//
+// To the engine programmer, the sector will be split into various bits of data
+// for optimization and platform specific reasons.  The geometry might be in different chunks
+// and the collision data might be seperate from the rendering data.  
+// The act of "turning off" a sector will be very different from platform to platform
+//
+// To get round this problem, we have a CSector class which acts as a facade
+// to the actual machine specific sector data.
+//
+// When the engine loads a scene, it will create a list of CSectors, one for each
+// sector in the scene.  
+// Each CSector will actually be a platfrom specific class derived from CSector,
+// like: CPs2Sector, CXboxSector or CNGC Sector
+// The derived class (like, CPs2Sector), contains the platform specific data that
+// identifies the sector in the engine.  On the PS2, this might have a list of the 
+// meshes that make up the sector, maybe a list of the materials, probably a pointer to the 
+// collision data, and maybe a pointer to a structure containing sector flags.
+// CSector provides functions like SetVisibility(), which call a platform specific
+// function (PlatSetVisibility()), which calls the actual engine specific
+// function, which performs whatever is necessary at a low level to make the 
+// sector visible/invisible (on the PS2, this might involve relinking a DMA chain,
+// on other platforms, it might just involve flipping a bit.)
+//
+// The "engine programmers" have to implement and maintain the platform
+// specific CPs2Sector (and CXBox.., etc) implementation.
+//
+// The "game programmers" simply use the CSector class, and will never need to know 
+// anything about the derived class, and their code will work across all three platforms.  
+//
+// The End
+/////////////////////////////////////////////////////////////////////////////////
+
+// Forward declaration
+class CGeom;
+class CScene;
+class CCollStatic;
+#ifdef	__PLAT_NGPS__		
+class CPs2Scene;
+#endif //	__PLAT_NGPS__		
+
+// The CSector class is the platform independent abstract base class
+// of the platform specific CSector classes
+class	CSector
+{
+public:
+// The basic interface to the sector
+// this is the machine independent part	
+// machine independ range checking, etc can go here	
+							CSector();
+	virtual					~CSector();
+
+	uint32					GetChecksum() const;
+	void					SetChecksum(uint32 c);
+
+	CGeom *					GetGeom() const;
+	void					SetGeom(CGeom *p_geom);
+
+	void					SetColor(Image::RGBA rgba);
+	Image::RGBA				GetColor() const;
+	void					ClearColor();
+	
+	void 					SetLightGroup(uint32 light_group) {m_light_group = light_group;}
+	uint32 					GetLightGroup() {return m_light_group;}
+
+	void					SetVisibility(uint32 mask);
+	uint32					GetVisibility() const;
+
+	void					SetActive(bool on);
+	bool					IsActive() const;
+
+	void					SetActiveAtReplayStart(bool on);
+	bool					GetActiveAtReplayStart() const;
+	void					SetVisibleAtReplayStart(bool on);
+	bool					GetVisibleAtReplayStart() const;
+	void					SetStoredActive(bool on);
+	bool					GetStoredActive() const;
+	void					SetStoredVisible(bool on);
+	bool					GetStoredVisible() const;
+	
+	const Mth::CBBox &		GetBoundingBox() const;
+
+	void					SetWorldPosition(const Mth::Vector& pos);
+	const Mth::Vector &		GetWorldPosition() const;
+
+	// Rotation is applied to meshes permanently
+	void					SetYRotation(Mth::ERot90 rot);
+	Mth::ERot90				GetYRotation() const;			// Gets applied rotation
+
+	// Scale is applied to meshes permanently
+	void					SetScale(const Mth::Vector& scale);
+	Mth::Vector				GetScale() const;				// Gets scale
+
+	// Change collision data of a sector
+	int						GetNumCollVertices() const;
+	void					GetRawCollVertices(Mth::Vector *p_vert_array) const;
+	void					SetRawCollVertices(const Mth::Vector *p_vert_array);
+
+	void					SetCollidable(bool on);
+	bool					IsCollidable() const;
+
+	void					SetShatter(bool on);
+	bool					GetShatter() const;
+
+	bool					AddCollSector(Nx::CCollStatic *p_coll_sector);
+	CCollStatic *			GetCollSector() const;
+
+	void					SetOccluder(bool on);
+	
+	// Wibble functions
+	void					SetUVWibbleParams(float u_vel, float u_amp, float u_freq, float u_phase,
+											  float v_vel, float v_amp, float v_freq, float v_phase);
+	void					UseExplicitUVWibble(bool yes);
+	void					SetUVWibbleOffsets(float u_offset, float v_offset);
+	
+protected:
+	// Internal sector flags.
+	enum {
+		mCOLLIDE					= 0x0001,
+		mCLONE						= 0x0002,
+		mADD_TO_SUPER_SECTORS		= 0x0004,					// Cloned sector needs adding to Super Sectors
+		mMARKED_FOR_DELETION		= 0x0008,					// Cloned object no longer used but not deleted yet
+		mREMOVE_FROM_SUPER_SECTORS	= 0x0010,					// Take out of Super Sectors w/o deleting
+		mINVALID_SUPER_SECTORS		= 0x0020,					// Tells if SuperSectors needs updating
+		mIN_SUPER_SECTORS			= 0x0040,					// Tells if in SuperSectors
+		// These 4 used by replay code
+		mACTIVE_AT_REPLAY_START		= 0x0080,					// Whether the sector is active at the start of the replay
+		mVISIBLE_AT_REPLAY_START	= 0x0100,					// Whether the sector is visible at the start of the replay
+		mSTORED_ACTIVE				= 0x0200,					// Stored active status, so that it can be restored at the end of the replay
+		mSTORED_VISIBLE				= 0x0400,					// Stored visible status, so that it can be restored at the end of the replay
+	};
+
+	uint32					get_flags() const;				// Get internal flags
+	void					set_flags(uint32 flags);		// Set internal flags
+	void					clear_flags(uint32 flags);		// Clear internal flags
+
+	uint32					m_checksum;
+	uint32					m_flags;
+	uint32					m_light_group;		// checksum of light group as specified in node array
+
+	Mth::ERot90				m_rot_y;		// since rotation already applied below p-line, we keep the value above
+	Mth::Vector				m_scale;		// since scale already applied below p-line, we keep the value above
+
+	CGeom *					mp_geom;		// The geometry of the sector
+	CCollStatic *			mp_coll_sector;	// Collision object for the sector
+
+private:
+	CSector *				clone(bool instance, bool add_to_super_sectors, CScene *p_dest_scene = NULL);	// must only be called through CScene
+
+	// The virtual functions will have a stub implementation
+	// in nxsector.cpp
+	//virtual	void			plat_set_color(Image::RGBA rgba);
+	//virtual	Image::RGBA		plat_get_color() const;
+	//virtual	void			plat_clear_color();
+
+	//virtual	void			plat_set_visibility(uint32 mask);
+	//virtual	uint32			plat_get_visibility() const;
+
+	//virtual	void			plat_set_active(bool on);
+	//virtual	bool			plat_is_active() const;
+
+	//virtual const Mth::CBBox &	plat_get_bounding_box() const;
+
+	//virtual void			plat_set_world_position(const Mth::Vector& pos);
+	//virtual const Mth::Vector &	plat_get_world_position() const;
+
+	//virtual void			plat_set_y_rotation(Mth::ERot90 rot);
+
+	virtual	void			plat_set_shatter(bool on);
+	virtual	bool			plat_get_shatter() const;
+
+	virtual CSector *		plat_clone(bool instance, CScene *p_dest_scene);
+
+	// CScene needs access to clone() and internal flags
+	friend CScene;
+#ifdef	__PLAT_NGPS__		
+	friend CPs2Scene;
+#endif //	__PLAT_NGPS__		
+};
+
+}
+
+#endif // 
+
diff --git a/Code/Gfx/NxSkinComponent.cpp b/Code/Gfx/NxSkinComponent.cpp
new file mode 100644
index 0000000..59d342c
--- /dev/null
+++ b/Code/Gfx/NxSkinComponent.cpp
@@ -0,0 +1,143 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxSkinComponent.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/21/2001
+//****************************************************************************
+
+#include "gfx/nxskincomponent.h"
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::plat_set_color(uint8 r, uint8 g, uint8 b, uint8 a)
+{
+    // STUB
+    printf("STUB FUNCTION called:  CSkinComponent::set_color %d %d %d %d", r, g, b, a);
+}
+         
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::plat_set_visibility(uint32 mask)
+{
+    // STUB
+    printf("STUB FUNCTION called:  CSkinComponent::set_visibility %08x", mask);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::plat_set_scale(float scaleFactor)
+{
+    // STUB
+    printf("STUB FUNCTION called:  CSkinComponent::set_scale %f", scaleFactor);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::plat_replace_texture(char* p_srcFileName, char* p_dstFileName)
+{
+    Dbg_Assert(p_srcFileName);
+    Dbg_Assert(p_dstFileName);
+    
+    // STUB
+    printf("STUB FUNCTION called:  CSkinComponent::replace_texture %s %s", p_srcFileName, p_dstFileName);
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkinComponent::CSkinComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkinComponent::~CSkinComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::SetColor(uint8 r, uint8 g, uint8 b, uint8 a)
+{
+    plat_set_color(r,g,b,a);
+}
+         
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::SetVisibility(uint32 mask)
+{
+    plat_set_visibility(mask);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::SetScale(float scaleFactor)
+{
+    plat_set_scale(scaleFactor);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkinComponent::ReplaceTexture(char* p_srcFileName, char* p_dstFileName)
+{
+    plat_replace_texture(p_srcFileName, p_dstFileName);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/NxSkinComponent.h b/Code/Gfx/NxSkinComponent.h
new file mode 100644
index 0000000..eb60510
--- /dev/null
+++ b/Code/Gfx/NxSkinComponent.h
@@ -0,0 +1,47 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       NxSkinComponent.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/21/2001
+//****************************************************************************
+
+#ifndef	__GFX_NXSKINCOMPONENT_H__
+#define	__GFX_NXSKINCOMPONENT_H__
+
+#include 
+
+namespace Nx
+{
+
+class CSkinComponent
+{
+
+public:
+    // The basic interface to the skin component
+    // this is the machine independent part	
+    // machine independent range checking, etc can go here
+    CSkinComponent();
+	virtual					~CSkinComponent();
+
+public:
+	void					SetColor(uint8 r, uint8 g, uint8 b, uint8 a);
+	void					SetVisibility(uint32 mask);
+	void					SetScale(float scaleFactor);
+    void                    ReplaceTexture(char* p_srcFileName, char* p_dstFileName);
+
+protected:
+    uint32                  m_componentName;
+
+private:
+    // The virtual functions will have a stub implementation
+    // in p_nxskincomponent.cpp
+	virtual	void			plat_set_color(uint8 r, uint8 g, uint8 b, uint8 a);
+	virtual	void			plat_set_visibility(uint32 mask);
+    virtual void            plat_set_scale(float scaleFactor);
+    virtual void            plat_replace_texture(char* p_srcFileName, char* p_dstFileName);
+};
+
+}
+
+#endif // __GFX_NXSKINCOMPONENT_H__
+
diff --git a/Code/Gfx/NxSprite.cpp b/Code/Gfx/NxSprite.cpp
new file mode 100644
index 0000000..a1aa0cd
--- /dev/null
+++ b/Code/Gfx/NxSprite.cpp
@@ -0,0 +1,320 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxSprite.cpp
+
+#include 
+
+#include "gfx/NxSprite.h"
+
+namespace	Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////
+// CSprite
+
+/////////////////////////////////////////////////////////////////////////////
+// These functions are the platform independent part of the interface.
+					 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSprite::CSprite(CWindow2D *p_window) :
+	mp_texture(NULL),
+	m_pos_x(-1.0f),
+	m_pos_y(-1.0f),
+	m_scale_x(1.0f),
+	m_scale_y(1.0f),
+	m_anchor_x(-1.0f),
+	m_anchor_y(-1.0f),
+	m_rotation(0.0f),
+	m_priority(0.0f),
+	m_width(0),
+	m_height(0),
+	m_rgba(0x80, 0x80, 0x80, 0x80),
+	mp_window(p_window)
+{
+	m_hidden = true;	// until we get more information
+	m_use_zbuffer = false;
+}
+
+CSprite::CSprite(CTexture *p_tex, float xpos, float ypos,
+				uint16 width, uint16 height, CWindow2D *p_window, Image::RGBA rgba,
+				float xscale, float yscale, float xanchor,
+				float yanchor, float rot, float pri, bool hide)
+{
+	Initialize(p_tex, xpos, ypos, width, height, p_window, rgba, xscale, yscale,
+			   xanchor, yanchor, rot, pri, hide);
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSprite::~CSprite()
+{
+	// TODO: Possibly free texture
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::Initialize(CTexture *p_tex, float xpos, float ypos,
+								uint16 width, uint16 height, CWindow2D *p_window,
+								Image::RGBA rgba, float xscale, float yscale, float xanchor,
+								float yanchor, float rot, float pri, bool hide)
+{
+	mp_texture = p_tex;
+	m_pos_x = xpos;
+	m_pos_y = ypos;
+	m_width = width;
+	m_height = height;
+	m_rgba = rgba;
+	m_scale_x = xscale;
+	m_scale_y = yscale;
+	m_anchor_x = xanchor;
+	m_anchor_y = yanchor;
+	m_rotation = rot;
+	m_priority = pri;
+
+	m_hidden = hide;
+	m_use_zbuffer = false;
+	mp_window = p_window;
+
+	plat_initialize();			// This calls nothing when called from the constructor
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetHidden(bool hide)
+{
+	m_hidden = hide;
+
+	plat_update_hidden();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetTexture(CTexture *p_texture)
+{
+	mp_texture = p_texture;
+	if (p_texture)
+	{
+		m_width = p_texture->GetWidth();
+		m_height = p_texture->GetHeight();
+	}
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetPos(float x, float y)
+{
+	m_pos_x = x;
+	m_pos_y = y;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetSize(uint16 width, uint16 height)
+{
+	m_width = width;
+	m_height = height;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetScale(float x, float y)
+{
+	m_scale_x = x;
+	m_scale_y = y;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetAnchor(float x, float y)
+{
+	m_anchor_x = x;
+	m_anchor_y = y;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetRotation(float rot)
+{
+	m_rotation = rot;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetPriority(float pri)
+{
+	m_priority = pri;
+	m_use_zbuffer = false;
+
+	plat_update_priority();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		 CSprite::SetZValue(ZBufferValue z)
+{
+	m_zvalue = z;
+	m_use_zbuffer = true;
+
+	plat_update_priority();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetRGBA(Image::RGBA rgba)
+{
+	m_rgba = rgba;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::SetWindow(CWindow2D *p_window)
+{
+	mp_window = p_window;
+
+	plat_update_window();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::EnableConstantZValue(bool enable)
+{
+	plat_enable_constant_z_value(enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::SetConstantZValue(Nx::ZBufferValue z)
+{
+	plat_set_constant_z_value(z);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::ZBufferValue	CSprite::GetConstantZValue()
+{
+	return plat_get_constant_z_value();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::plat_initialize()
+{
+	printf ("STUB: PlatInitialize\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::plat_update_hidden()
+{
+	printf ("STUB: PlatUpdateHidden\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::plat_update_engine()
+{
+	printf ("STUB: PlatUpdateEngine\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::plat_update_priority()
+{
+	printf ("STUB: PlatUpdatePriority\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CSprite::plat_update_window()
+{
+	printf ("STUB: PlatUpdateWindow\n");
+}
+
+}
+
+
diff --git a/Code/Gfx/NxSprite.h b/Code/Gfx/NxSprite.h
new file mode 100644
index 0000000..ae5a619
--- /dev/null
+++ b/Code/Gfx/NxSprite.h
@@ -0,0 +1,220 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NxSprite.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_SPRITE_H__
+#define	__GFX_NX_SPRITE_H__
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+#include 
+#include 
+#include 
+
+
+namespace Nx
+{
+
+// Forward declarations
+class CWindow2D;
+
+//////////////////////////////////////////////////////////////////////////////////
+// Nx::CSprite: Class for 2D sprites
+class	CSprite
+{
+public:
+							CSprite(CWindow2D *p_window = NULL);
+							CSprite(CTexture *p_tex, float xpos, float ypos,
+									uint16 width, uint16 height, CWindow2D *p_window = NULL,
+									Image::RGBA rgba = Image::RGBA(0x80, 0x80, 0x80, 0x80),
+									float xscale = 1.0f, float yscale = 1.0f,
+									float xanchor = -1.0f, float yanchor = -1.0f,
+									float rot = 0.0f, float pri = 0.0f, bool hide = false);
+	virtual					~CSprite();
+
+	// Initialization
+	void					Initialize(CTexture *p_tex, float xpos, float ypos,
+									   uint16 width, uint16 height, CWindow2D *p_window = NULL,
+									   Image::RGBA rgba = Image::RGBA(0x80, 0x80, 0x80, 0x80),
+									   float xscale = 1.0f, float yscale = 1.0f,
+									   float xanchor = -1.0f, float yanchor = -1.0f,
+									   float rot = 0.0f, float pri = 0.0f, bool hide = false);
+
+	// If hidden, it won't be drawn that frame
+	void					SetHidden(bool hide);
+	bool					IsHidden() const;
+
+	// Texture used for the sprite.  But it is optional.
+	CTexture *				GetTexture() const;
+	void					SetTexture(CTexture *p_texture);
+
+	// Position on screen.  Assumes screen size of 640x448.
+	float					GetXPos() const;
+	float					GetYPos() const;
+	void					SetPos(float x, float y);
+
+	// Actual width and height of sprite.  If textured, it will also
+	// use this for the texture width and height.
+	uint16					GetWidth() const;
+	uint16					GetHeight() const;
+	void					SetSize(uint16 w, uint16 h);
+
+	// Scale of sprite.  A negative scale will flip the axis.
+	float					GetXScale() const;
+	float					GetYScale() const;
+	void					SetScale(float x, float y);
+
+	// The anchor position of the sprite.  Basically, the origin within
+	// the sprite.  This point is going to be at the screen position.  It
+	// is also the point of rotation.  The range is [-1.0, 1.0], where
+	// (-1.0, -1.0) is the top left corner and (1.0, 1.0) is the bottom
+	// right corner.
+	float					GetXAnchor() const;
+	float					GetYAnchor() const;
+	void					SetAnchor(float x, float y);
+
+	// Rotation of sprite in radians
+	float					GetRotation() const;
+	void					SetRotation(float rot);
+
+	// Priority of sprite from 0.0 to 1.0.  Higher priority sprites are
+	// drawn on top of lower priority sprites.
+	float					GetPriority() const;
+	void					SetPriority(float pri);
+	Nx::ZBufferValue		GetZValue() const;
+	void					SetZValue(Nx::ZBufferValue z);
+
+	// Color of sprite.  All 4 corners are the same.
+	Image::RGBA				GetRGBA() const;
+	void					SetRGBA(Image::RGBA rgba);
+
+	// Clipping window
+	CWindow2D *				GetWindow() const;
+	void					SetWindow(CWindow2D *p_window);
+
+	// Constant z-buffer value (only affect sprites using a priority)
+	static void				EnableConstantZValue(bool enable);
+	static void				SetConstantZValue(Nx::ZBufferValue z);
+	static Nx::ZBufferValue	GetConstantZValue();
+
+protected:
+	CTexture *				mp_texture;				// Texture
+
+	float					m_pos_x;				// Position
+	float					m_pos_y;
+	float					m_scale_x;				// Scale
+	float					m_scale_y;
+	float					m_anchor_x;				// Anchor Position
+	float					m_anchor_y;
+
+	float					m_rotation;				// Rotation
+	float					m_priority;				// Priority
+	ZBufferValue			m_zvalue;				// for zbuffer sort
+
+	uint16					m_width;				// Size
+	uint16					m_height;
+
+	bool					m_hidden;				// Hidden
+	bool					m_use_zbuffer;
+	Image::RGBA				m_rgba;					// Color
+
+	CWindow2D *				mp_window;
+
+private:
+	virtual void			plat_initialize();
+
+	virtual void			plat_update_hidden();		// Tell engine of update
+	virtual void			plat_update_engine();		// Update engine primitives
+	virtual void			plat_update_priority();
+	virtual void			plat_update_window();
+
+	static void				plat_enable_constant_z_value(bool enable);
+	static void				plat_set_constant_z_value(Nx::ZBufferValue z);
+	static Nx::ZBufferValue	plat_get_constant_z_value();
+};
+
+/////////////////////////////////////////////////////////
+// CSprite inline function
+inline bool					CSprite::IsHidden() const
+{
+	return m_hidden;
+}
+
+inline CTexture *			CSprite::GetTexture() const
+{
+	return mp_texture;
+}
+
+inline float				CSprite::GetXPos() const
+{
+	return m_pos_x;
+}
+
+inline float				CSprite::GetYPos() const
+{
+	return m_pos_y;
+}
+
+inline uint16				CSprite::GetWidth() const
+{
+	return m_width;
+}
+
+inline uint16				CSprite::GetHeight() const
+{
+	return m_height;
+}
+
+inline float				CSprite::GetXScale() const
+{
+	return m_scale_x;
+}
+
+inline float				CSprite::GetYScale() const
+{
+	return m_scale_y;
+}
+
+inline float				CSprite::GetXAnchor() const
+{
+	return m_anchor_x;
+}
+
+inline float				CSprite::GetYAnchor() const
+{
+	return m_anchor_y;
+}
+
+inline float				CSprite::GetRotation() const
+{
+	return m_rotation;
+}
+
+inline float				CSprite::GetPriority() const
+{
+	return m_priority;
+}
+
+inline ZBufferValue			CSprite::GetZValue() const
+{
+	return m_zvalue;
+}
+
+inline Image::RGBA			CSprite::GetRGBA() const
+{
+	return m_rgba;
+}
+
+inline CWindow2D *			CSprite::GetWindow() const
+{
+	return mp_window;
+}
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NxTexMan.cpp b/Code/Gfx/NxTexMan.cpp
new file mode 100644
index 0000000..ea1442b
--- /dev/null
+++ b/Code/Gfx/NxTexMan.cpp
@@ -0,0 +1,808 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxTexMan.cpp
+
+// start autoduck documentation
+// @DOC nxtexman
+// @module nxtexman | None
+// @subindex Scripting Database
+// @index script | nxtexman
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "gfx/NxTexMan.h"
+#include "gfx/NxSprite.h"
+#include "gfx/Nx.h"
+
+#include 
+
+// for downloading faces
+#include 
+#include 
+#include 
+#include 
+
+namespace	Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////
+// CTexMan
+
+Lst::HashTable	CTexDictManager::s_tex_dict_lookup(4);
+
+// temporary sprite texture dictionary
+CTexDict *					CTexDictManager::sp_sprite_tex_dict = sCreateTextureDictionary("sprite");
+CTexDict *					CTexDictManager::sp_particle_tex_dict = sCreateTextureDictionary("particle");
+
+/////////////////////////////////////////////////////////////////////////////
+// These functions are the platform independent part of the interface.
+					 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict *			CTexDictManager::sCreateTextureDictionary(const char *p_tex_dict_name)
+{
+	CTexDict *p_dict;
+	uint32 checksum = Crc::GenerateCRCFromString(p_tex_dict_name);
+
+	p_dict = sGetTextureDictionary(checksum);
+
+	// Assert for now unless we can think of a reason to use with ref counts
+	Dbg_MsgAssert((p_dict == NULL), ("Texture dictionary %s already exists", p_tex_dict_name));
+
+	if (p_dict)	
+	{
+		// If already loaded, then just link up with it
+		p_dict->IncRefCount();
+	} else {
+		p_dict = s_plat_create_texture_dictionary(checksum);
+		s_tex_dict_lookup.PutItem(checksum, p_dict);
+	}
+	return p_dict;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict *			CTexDictManager::sLoadTextureDictionary(uint32 fileNameChecksum, uint32* pData, int dataSize, bool isLevelData, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup )
+{
+	Mem::PushMemProfile((char*)"TEX_data_buffer");
+
+	uint32 checksum = fileNameChecksum + texDictOffset;
+
+	CTexDict *p_dict;
+	p_dict = sGetTextureDictionary(checksum);
+
+	if (p_dict)	
+	{
+		// Mick: temporary assertion to track down annoyingly persisten phantom texture dcitionaries
+		// you can probably remove this
+		//Dbg_MsgAssert(0,("Not supposed to be multiply using tex dicts yet! (%s/%x)",texture_dict_name,texDictOffset));
+
+		// If already loaded, then just link up with it
+		p_dict->IncRefCount();
+	}
+	else
+	{
+		p_dict = s_plat_load_texture_dictionary(checksum, pData, dataSize, isLevelData, texDictOffset, isSkin, forceTexDictLookup);
+		p_dict->set_checksum(checksum);			// Since it just uses the base name
+		s_tex_dict_lookup.PutItem(checksum, p_dict);
+	}
+
+	Mem::PopMemProfile(/*(char*)"TEX_data_buffer"*/);
+
+	return p_dict;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict *			CTexDictManager::sLoadTextureDictionary(const char *p_tex_dict_name, bool isLevelData, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup )
+{
+	// append the extension
+	char	texture_dict_name[128];
+	sprintf(texture_dict_name,"%s.%s",p_tex_dict_name,CEngine::sGetPlatformExtension());
+
+	Mem::PushMemProfile((char*)p_tex_dict_name);
+	
+	// the texDictOffset prevents clashes when we need multiple
+	// separate versions of the same texture dictionary, such as
+	// for multiplayer create-a-skater parts (note:  peds should not
+	// use this system because they should share actual texture
+	// dictionaries)
+	uint32 checksum = Crc::GenerateCRCFromString(p_tex_dict_name) + texDictOffset;
+
+	CTexDict *p_dict;
+	p_dict = sGetTextureDictionary(checksum);
+
+	if (p_dict)	
+	{
+		// Mick: temporary assertion to track down annoyingly persisten phantom texture dcitionaries
+		// you can probably remove this
+		//Dbg_MsgAssert(0,("Not supposed to be multiply using tex dicts yet! (%s/%x)",texture_dict_name,texDictOffset));
+
+		// If already loaded, then just link up with it
+		p_dict->IncRefCount();
+	}
+	else
+	{
+		p_dict = s_plat_load_texture_dictionary(texture_dict_name, isLevelData, texDictOffset, isSkin, forceTexDictLookup);
+		p_dict->set_checksum(checksum);			// Since it just uses the base name
+		s_tex_dict_lookup.PutItem(checksum, p_dict);
+	}
+
+	Mem::PopMemProfile(/*(char*)p_tex_dict_name*/);
+
+	return p_dict;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CTexDictManager::sUnloadTextureDictionary(CTexDict *p_tex_dict)
+{
+	if (p_tex_dict->DecRefCount() == 0)
+	{
+		s_tex_dict_lookup.FlushItem(p_tex_dict->GetChecksum());  // Remove the reference to it before we delete it
+		s_plat_unload_texture_dictionary(p_tex_dict);			 // as deleting it can kill the checksum with memory trashing
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict *		  	CTexDictManager::sGetTextureDictionary(uint32 checksum)
+{
+	return s_tex_dict_lookup.GetItem(checksum);
+}
+
+
+// @script | LoadTexture | Loads a 2D sprite texture
+// @parm string |  | path and name of texture
+// @flag no_vram_alloc | won't allocate in vram
+bool ScriptLoadTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no texture specified"));
+
+	bool alloc_vram = true;
+	if (pParams->ContainsFlag(CRCD(0x3955ff2e,"no_vram_alloc")))
+	{
+		alloc_vram = false;
+	}
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->LoadTexture(p_name, true, alloc_vram);
+
+	return p_texture != NULL;
+}
+
+
+
+// @script | UnloadTexture | Unloads a 2D sprite texture
+// @parm string |  | name of texture
+bool ScriptUnloadTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	CTexture *p_texture = NULL;
+	const char *p_name;
+	uint32 checksum;
+
+	if (pParams->GetText(NONAME, &p_name))
+	{
+		// get texture based on string
+		p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(p_name);
+	}
+	else if (pParams->GetChecksum(NONAME, &checksum))
+	{							 
+		// get texture based on checksum
+		p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("no texture specified in %s", pScript->GetScriptInfo()));
+	}
+
+	if (p_texture)
+	{
+		CTexDictManager::sp_sprite_tex_dict->UnloadTexture(p_texture);
+	}
+	else
+	{
+		if ( !pParams->ContainsFlag( CRCD(0x3d92465e,"dont_assert") ) )
+		{
+			Dbg_MsgAssert(0, ("Can't find texture %s to unload", p_name));
+		}
+	}
+	
+	return true;
+}
+
+// @script | AddTextureToVram | Puts 2D sprite texture into VRAM (so it is drawable)
+// @parm string |  | name of texture
+bool ScriptAddTextureToVram(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+#if 0
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(p_name);
+	if (p_texture)
+	{
+		p_texture->AddToVram();
+	} else {
+		Dbg_MsgAssert(0, ("Can't find texture %s to add to vram", p_name));
+	}
+#endif
+
+	return true;
+}
+
+// @script | RemoveTextureFromVram | Removes 2D sprite texture from VRAM
+// @parm string |  | name of texture
+// @flag no_assert | won't assert on failure
+bool ScriptRemoveTextureFromVram(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+#if 0
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(p_name);
+	if (p_texture)
+	{
+		p_texture->RemoveFromVram();
+	} else if (!pParams->ContainsFlag(CRCD(0x512c7426,"no_assert"))) {
+		Dbg_MsgAssert(0, ("Can't find texture %s to remove from vram", p_name));
+	}
+#endif
+
+	return true;
+}
+
+// @script | LoadFaceTextureFromProfile | Loads a 2D sprite texture
+bool ScriptLoadFaceTextureFromProfile(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    bool alloc_vram = true;
+	uint32 checksum;
+    pParams->GetChecksum( "checksum", &checksum, Script::ASSERT );
+	
+    // Copy the downloaded face for use as a sprite
+    Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+    Obj::CSkaterProfile* pSkaterProfile = skate_mod->GetCurrentProfile();
+    Dbg_Assert( pSkaterProfile );
+    Dbg_MsgAssert( !pSkaterProfile->IsPro(), ( "Can only map face onto a custom skater.  UI must make the custom skater active before this point" ) );
+    Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+    Dbg_Assert( pAppearance );
+    Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+
+	Dbg_MsgAssert(pFaceTexture,("NULL pFaceTexture"));
+	Dbg_MsgAssert(pFaceTexture->IsValid(),("Invalid pFaceTexture"));
+
+    Nx::CTexture* p_texture = Nx::CTexDictManager::sp_sprite_tex_dict->LoadTextureFromBuffer(pFaceTexture->GetTextureData(), pFaceTexture->GetTextureSize(), checksum, true, alloc_vram);
+    Dbg_MsgAssert( p_texture, ( "Appearance has no face texture" ) );
+
+
+	return p_texture != NULL;
+}
+
+// @script | Generate32BitImage | Generates 32bit image data for texture
+// @parm name |  | name of texture
+// @parmopt int | renderable | 0 | If set to 1, make 32-bit image renderable in a sprite
+// @parmopt int | store_original | 0 | If set to 1, keeps original 32-bit image around that can be used with some functions
+bool ScriptGenerate32BitImage(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		int value;
+		bool renderable = false;
+		bool store_original = false;
+
+		if (pParams->GetInteger(CRCD(0xa5d7cfaa,"renderable"), &value) && (value == 1))
+		{
+			renderable = true;
+		}
+		if (pParams->GetInteger(CRCD(0xeefaf080,"store_original"), &value) && (value == 1))
+		{
+			store_original = true;
+		}
+
+		p_texture->Generate32BitImage(renderable, store_original);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to generate 32-bit image", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | OffsetTexture | Move texture (cropping the part that moves off and filling in the new parts with
+// either the old edge or a supplied fill color)
+// @parm name |  | name of texture
+// @parm int | x_offset | x offset in pixels
+// @parm int | y_offset | y offset in pixels
+// @parmopt int | fill_r | 128 | fill color red component
+// @parmopt int | fill_g | 128 | fill color green component
+// @parmopt int | fill_b | 128 | fill color blue component
+// @parmopt int | fill_a | 128 | fill color alpha component
+bool ScriptOffsetTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		int x_offset = 0;
+		int y_offset = 0;
+		bool use_fill_color = false;
+		int r = 128, g = 128, b = 128, a = 128;
+
+		pParams->GetInteger(CRCD(0xd83d589e,"x_offset"), &x_offset);
+		pParams->GetInteger(CRCD(0x14975800,"y_offset"), &y_offset);
+
+		use_fill_color |= pParams->GetInteger(CRCD(0xb7a78c53,"fill_r"), &r);
+		use_fill_color |= pParams->GetInteger(CRCD(0xda7a68b8,"fill_g"), &g);
+		use_fill_color |= pParams->GetInteger(CRCD(0xaa109c37,"fill_b"), &b);
+		use_fill_color |= pParams->GetInteger(CRCD(0x3319cd8d,"fill_a"), &a);
+
+		Image::RGBA fill_color(r, g, b, a);
+
+		p_texture->Offset(x_offset, y_offset, use_fill_color, fill_color);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to offset", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | AdjustTextureRegion | Stretches and shrinks a texture region from the start point to the end point
+// @parm name |  | name of texture
+// @parm int | xpos | X position of regiom
+// @parm int | ypos | Y position of regiom
+// @parm int | width | width of regiom
+// @parm int | height | height of regiom
+// @parm int | split_axis | axis where the split line crosses (0 = X, 1 = Y)
+// @parm int | start_point | split line start point on axis
+// @parm int | end_point | split line end point on axis
+bool ScriptAdjustTextureRegion(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		int x_pos = 0;
+		int y_pos = 0;
+		int width = p_texture->GetWidth();
+		int height = p_texture->GetHeight();
+		int split_axis = X;
+		int start_point = 0;
+		int end_point = 0;
+
+		pParams->GetInteger(CRCD(0xfa8972e,"xpos"), &x_pos);
+		pParams->GetInteger(CRCD(0xb714f04b,"ypos"), &y_pos);
+		pParams->GetInteger(CRCD(0x73e5bad0,"width"), &width);
+		pParams->GetInteger(CRCD(0xab21af0,"height"), &height);
+		pParams->GetInteger(CRCD(0xe679982e,"split_axis"), &split_axis);
+		pParams->GetInteger(CRCD(0x3d0f162a,"start_point"), &start_point);
+		pParams->GetInteger(CRCD(0x39c54bde,"end_point"), &end_point);
+
+		p_texture->AdjustRegion(x_pos, y_pos, width, height, split_axis, start_point, end_point);
+	} else {
+		Dbg_MsgAssert(0, ("Can't find texture %s to adjust", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | PullTextureToEdge | Pull texture from point along the axis by num_pixels (+ or - determines the direction)
+// and crop anything going outside the texture border.
+// @parm name |  | name of texture
+// @parm int | point | pull point on axis
+// @parm int | axis | axis to pull along (0 = X, 1 = Y)
+// @parm int | num_pixels | number of pixels (+ or - determines the direction)
+bool ScriptPullTextureToEdge(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		int point = 0;
+		int axis = X;
+		int num_pixels = 0;
+
+		pParams->GetInteger(CRCD(0x485a0cdb,"point"), &point);
+		pParams->GetInteger(CRCD(0x7af07905,"axis"), &axis);
+		pParams->GetInteger(CRCD(0x88cf948c,"num_pixels"), &num_pixels);
+
+		p_texture->PullToEdge(point, axis, num_pixels);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to pull", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+// @script | PushTextureToPoint | Push texture to point along the axis by num_pixels (+ or - determines the direction)
+// and either stretch the edge line or fill it in with a fill color.
+// @parm name |  | name of texture
+// @parm int | point | pull point on axis
+// @parm int | axis | axis to pull along (0 = X, 1 = Y)
+// @parm int | num_pixels | number of pixels (+ or - determines the direction)
+// @parmopt int | fill_r | 128 | fill color red component
+// @parmopt int | fill_g | 128 | fill color green component
+// @parmopt int | fill_b | 128 | fill color blue component
+// @parmopt int | fill_a | 128 | fill color alpha component
+bool ScriptPushTextureToPoint(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		int point = 0;
+		int axis = X;
+		int num_pixels = 0;
+		bool use_fill_color = false;
+		int r = 128, g = 128, b = 128, a = 128;
+
+		pParams->GetInteger(CRCD(0x485a0cdb,"point"), &point);
+		pParams->GetInteger(CRCD(0x7af07905,"axis"), &axis);
+		pParams->GetInteger(CRCD(0x88cf948c,"num_pixels"), &num_pixels);
+
+		use_fill_color |= pParams->GetInteger(CRCD(0xb7a78c53,"fill_r"), &r);
+		use_fill_color |= pParams->GetInteger(CRCD(0xda7a68b8,"fill_g"), &g);
+		use_fill_color |= pParams->GetInteger(CRCD(0xaa109c37,"fill_b"), &b);
+		use_fill_color |= pParams->GetInteger(CRCD(0x3319cd8d,"fill_a"), &a);
+
+		Image::RGBA fill_color(r, g, b, a);
+
+		p_texture->PushToPoint(point, axis, num_pixels, use_fill_color, fill_color);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to push", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | AdjustTextureBrightness | Scales the texture brightness by the supplied value
+// @parm name |  | name of texture
+// @parm float | brightness | Brightness value (1.0 = no change)
+// @parmopt int | adjust_current | 0 | If set to 1, adjusts the current image, even if the original image exists
+bool ScriptAdjustTextureBrightness(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		float brightness = 1.0f;
+		int adjust_current = 0;
+		bool force_adjust_current = false;
+
+		pParams->GetFloat(CRCD(0x2689291c,"brightness"), &brightness, true);
+		pParams->GetInteger(CRCD(0xc164842a,"adjust_current"), &adjust_current);
+		if (adjust_current == 1)
+		{
+			force_adjust_current = true;
+		}
+
+		p_texture->AdjustBrightness(brightness, force_adjust_current);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | AdjustTextureHSV | Scales the texture HSV by the supplied values
+// @parm name |  | name of texture
+// @parm float | h | Hue offset (0 - 360)
+// @parm float | s | Saturation scale (1.0 = no change)
+// @parm float | v | Brightness scale (1.0 = no change)
+// @parmopt int | adjust_current | 0 | If set to 1, adjusts the current image, even if the original image exists
+bool ScriptAdjustTextureHSV(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(NONAME, &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		float h = 0.0f;
+		float s = 1.0f;
+		float v = 1.0f;
+		int adjust_current = 0;
+		bool force_adjust_current = false;
+
+		bool got_params;
+		got_params = pParams->GetFloat(CRCD(0x6e94f918,"h"), &h);
+		got_params = pParams->GetFloat(CRCD(0xe4f130f4,"s"), &s) && got_params;
+		got_params = pParams->GetFloat(CRCD(0x949bc47b,"v"), &v) && got_params;
+
+		Dbg_MsgAssert(got_params, ("Need to supply values for h, s, and v"));
+		Dbg_MsgAssert((h >= 0.0f) && (h <= 360.0f), ("h must be in the range of 0-360"));
+		Dbg_MsgAssert(s >= 0.0f, ("s cannot be negative"));
+
+		pParams->GetInteger(CRCD(0xc164842a,"adjust_current"), &adjust_current);
+		if (adjust_current == 1)
+		{
+			force_adjust_current = true;
+		}
+
+		p_texture->AdjustHSV(h, s, v, force_adjust_current);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+// @script | CopyTexture | Copy 2D sprite texture into a new texture
+// @parm name | src | name of texture
+// @parm name | new | name of new texture
+bool ScriptCopyTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 checksum;
+	if (!pParams->GetChecksum(CRCD(0x9fbbdb72,"src"), &checksum))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_sprite_tex_dict->GetTexture(checksum);
+	if (p_texture)
+	{
+		uint32 new_checksum;
+		if (pParams->GetChecksum(CRCD(0x941cbbba,"new"), &new_checksum))
+		{
+			CTexDictManager::sp_sprite_tex_dict->CopyTexture(new_checksum, p_texture);
+		}
+		else
+		{
+			Dbg_MsgAssert(0, ("no new texture specified"));
+		}
+	} else {
+		Dbg_MsgAssert(0, ("Can't find texture %s to copy", Script::FindChecksumName(checksum)));
+	}
+
+	return true;
+}
+
+// @script | CombineTextures | Combine two textures
+// @parm name | src | name of orig texture (and final texture if no new texture is supplied)
+// @parm name | top | name of top additional texture
+// @parmopt name | new | | name of new destination texture
+// @parmopt flag | no_palette_gen | | use original palette (faster)
+bool ScriptCombineTextures(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 new_checksum = 0;
+	uint32 tex1_checksum;
+	uint32 tex2_checksum;
+	bool gen_palette = true;
+
+	if (!pParams->GetChecksum(CRCD(0x9fbbdb72,"src"), &tex1_checksum))
+	{
+		Dbg_MsgAssert(0, ("no original source texture specified"));
+	}
+	if (!pParams->GetChecksum(CRCD(0xe126e035,"top"), &tex2_checksum))
+	{
+		Dbg_MsgAssert(0, ("no top source texture specified"));
+	}
+	pParams->GetChecksum(CRCD(0x941cbbba,"new"), &new_checksum);
+	if (pParams->ContainsFlag(CRCD(0x5905256b,"no_palette_gen")))
+	{
+		gen_palette = false;
+	}
+
+	CTexture *p_texture1 = CTexDictManager::sp_sprite_tex_dict->GetTexture(tex1_checksum);
+	if (p_texture1)
+	{
+		CTexture *p_texture2 = CTexDictManager::sp_sprite_tex_dict->GetTexture(tex2_checksum);
+		if (p_texture2)
+		{
+			if (new_checksum)
+			{
+				// Make new texture
+				CTexDictManager::sp_sprite_tex_dict->CombineTextures(new_checksum, p_texture1, p_texture2, gen_palette);
+			}
+			else
+			{
+				// Just overwrite original texture
+				p_texture1->CombineTextures(p_texture2, gen_palette);
+			}
+		}
+		else
+		{
+			Dbg_MsgAssert(0, ("Can't find texture %s to combine", Script::FindChecksumName(tex2_checksum)));
+		}
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Can't find texture %s to combine", Script::FindChecksumName(tex1_checksum)));
+	}
+
+	return true;
+}
+
+bool ScriptLoadParticleTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no texture specified"));
+
+//	Dbg_MsgAssert(*p_name,("%s\n Empty name string for Particle Texture\n",pScript->GetScriptInfo()));
+
+	if (! *p_name)
+	{
+		Dbg_Message("WARNING:  empty name in LoadAllParticleTextures - proabably an old level\n");
+		return false;
+	}
+
+	
+	CTexture *p_texture = CTexDictManager::sp_particle_tex_dict->GetTexture(p_name);
+	
+	bool	perm = pParams->ContainsFlag(CRCD(0xd5928f25,"perm"));
+	
+	if (!p_texture)
+	{
+		p_texture = CTexDictManager::sp_particle_tex_dict->LoadTexture(p_name, true, true, perm);
+	}
+	else
+	{
+		// The testure is already there
+		// if we are on CD, we assume that's the one we want to load
+		// but if not, then we assume we want to re-load the texture
+		// for quick previewing by artists
+		// so we unload it, and load it up agian
+		#ifdef	__PLAT_NGPS__		
+		if (!Config::CD())
+		{	
+			if (Config::gGotExtraMemory)
+			{
+				// If we have the extra memory, then use that
+				// to avoid fragmentation when testing variants of perm textures
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+			}
+			perm = p_texture->IsPerm();
+			CTexDictManager::sp_particle_tex_dict->UnloadTexture(p_texture);
+			p_texture = CTexDictManager::sp_particle_tex_dict->LoadTexture(p_name, true, true, perm);
+			if (Config::gGotExtraMemory)
+			{
+				Mem::Manager::sHandle().PopContext();
+			}
+		}
+		#endif
+	}
+	
+	return p_texture != NULL;
+}
+
+
+
+
+bool ScriptUnloadParticleTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_particle_tex_dict->GetTexture(p_name);
+	if (p_texture)
+	{
+		p_texture->RemoveFromVram();
+		CTexDictManager::sp_particle_tex_dict->UnloadTexture(p_texture);
+	} else {
+
+		printf ("Can't find particle texture %s to unload", p_name);
+	}
+	return true;
+}
+
+bool ScriptLoadSFPTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_particle_tex_dict->LoadTexture(p_name, false);
+
+	return p_texture != NULL;
+}
+
+
+
+
+bool ScriptUnloadSFPTexture(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	const char *p_name;
+	if (!pParams->GetText(NONAME, &p_name))
+		Dbg_MsgAssert(0, ("no texture specified"));
+	
+	CTexture *p_texture = CTexDictManager::sp_particle_tex_dict->GetTexture(p_name);
+	if (p_texture)
+	{
+		CTexDictManager::sp_particle_tex_dict->UnloadTexture(p_texture);
+	} else {
+		Dbg_MsgAssert(0, ("Can't find texture %s to unload", p_name));
+	}
+	return true;
+}
+
+bool ScriptDumpTextures(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	#ifdef	__NOPT_ASSERT__
+	// Get the hashable that has the textures in it
+	Lst::HashTable * p_textures = CTexDictManager::sp_particle_tex_dict->GetTexLookup();
+	p_textures->PrintContents();
+
+	p_textures->IterateStart();
+	Nx::CTexture *p_tex = p_textures->IterateNext();		
+	while(p_tex)
+	{
+	
+		printf ("0x%8x: (%d x %d) %s\n",
+					p_tex->GetChecksum(),
+					 p_tex->GetWidth(),
+					 p_tex->GetHeight(),
+					 p_tex->GetName()
+					);  
+	
+		p_tex = p_textures->IterateNext();		
+	}
+	#endif
+	
+	return true;
+}
+
+
+void 	FlushParticleTextures(bool all)
+{
+   
+   CTexDictManager::sp_particle_tex_dict->FlushTextures(all); 
+	
+}
+
+
+} //end namespace Nx
+
+
diff --git a/Code/Gfx/NxTexMan.h b/Code/Gfx/NxTexMan.h
new file mode 100644
index 0000000..f1d6ff5
--- /dev/null
+++ b/Code/Gfx/NxTexMan.h
@@ -0,0 +1,84 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NxTexMan.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_TEX_MAN_H__
+#define	__GFX_NX_TEX_MAN_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+#include 
+
+
+namespace Script
+{
+	class CScriptStructure;
+	class CScript;
+}
+
+namespace Nx
+{
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CTexDictManager
+class	CTexDictManager
+{
+public:
+
+	// Create and destroy CTexDicts
+	static CTexDict *			sCreateTextureDictionary(const char *p_tex_dict_name);	// Creates an empty dictionary
+	static CTexDict *			sLoadTextureDictionary(uint32 checksum, uint32* pData, int dataSize, bool isLevelData, uint32 texDictOffset=0, bool isSkin=0, bool forceTexDictLookup=false );    // Loads dictionary from file
+	static CTexDict *			sLoadTextureDictionary(const char *p_tex_dict_name, bool isLevelData, uint32 texDictOffset=0, bool isSkin=0, bool forceTexDictLookup=false );    // Loads dictionary from file
+	static bool					sUnloadTextureDictionary(CTexDict *p_tex_dict);
+
+	static CTexDict *  			sGetTextureDictionary(uint32 checksum);		// Will not affect ref_count
+
+	// temporary sprite texture dictionary
+	static CTexDict *			sp_sprite_tex_dict;
+	static CTexDict *			sp_particle_tex_dict;
+
+private:	
+	static Lst::HashTable	s_tex_dict_lookup;
+
+	// Platform-specific calls
+	// The following two will only be called if a physical load or unload is done
+	static CTexDict *			s_plat_load_texture_dictionary(const char *p_tex_dict_name, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup = false );
+	static CTexDict *			s_plat_load_texture_dictionary(uint32 checksum, uint32* pData, int dataSize, bool is_level_data, uint32 texDictOffset, bool isSkin, bool forceTexDictLookup = false );
+	static CTexDict *			s_plat_create_texture_dictionary(uint32 checksum);
+	static bool					s_plat_unload_texture_dictionary(CTexDict *p_tex_dict);
+};
+
+bool ScriptLoadTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptUnloadTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptAddTextureToVram(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptRemoveTextureFromVram(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptLoadFaceTextureFromProfile(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGenerate32BitImage(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptOffsetTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptAdjustTextureRegion(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptPullTextureToEdge(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptPushTextureToPoint(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptAdjustTextureBrightness(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptAdjustTextureHSV(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCopyTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCombineTextures(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptLoadParticleTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptUnloadParticleTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptLoadSFPTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptUnloadSFPTexture(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptDumpTextures(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+
+void 	FlushParticleTextures(bool all);
+
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NxTexture.cpp b/Code/Gfx/NxTexture.cpp
new file mode 100644
index 0000000..2449812
--- /dev/null
+++ b/Code/Gfx/NxTexture.cpp
@@ -0,0 +1,1187 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxTexture.cpp
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+									 
+#include 
+#include 
+
+namespace	Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static uint32 s_convert_filename_to_checksum( const char* pFileName )
+{
+	// Find base name
+	int idx	= strlen(pFileName);
+	while ((idx > 0) && pFileName[idx - 1] != '\\' && pFileName[idx - 1] != '/')
+		--idx;
+
+	const char *p_base_name = &(pFileName[idx]);
+
+	return Crc::GenerateCRCFromString(p_base_name);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// CTexture
+
+/////////////////////////////////////////////////////////////////////////////
+// These functions are the platform independent part of the interface.
+					 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture::CTexture()
+{
+	#ifdef	__NOPT_ASSERT__
+	mp_name = NULL;
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture::CTexture(const CTexture & src_texture)
+{
+	m_checksum = src_texture.m_checksum;
+	m_perm = src_texture.m_perm;
+
+	#ifdef	__NOPT_ASSERT__
+	mp_name = new char[strlen("Copy of ")+strlen(src_texture.mp_name)+1];
+	sprintf (mp_name,"Copy of %s",src_texture.mp_name);	
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture::~CTexture()
+{
+	#ifdef	__NOPT_ASSERT__
+	if (mp_name)
+	{
+		delete [] mp_name;
+	}
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::LoadTexture(const char *p_texture_name, bool sprite, bool alloc_vram)
+{								 	
+#ifdef	__NOPT_ASSERT__
+	mp_name = new char[strlen(p_texture_name)+1];
+	sprintf (mp_name,"%s",p_texture_name);	
+#endif
+
+	char texture_name[512];
+	sprintf(texture_name,"%s",p_texture_name);
+	if (sprite)
+	{
+
+		if (p_texture_name[0]=='.'
+			&& p_texture_name[1]=='.'
+			&& p_texture_name[2]=='/')
+		{
+			// detected a ../ at the start of the file name, so use the full file name
+			sprintf(texture_name,"%s",p_texture_name+3);
+			
+		}
+		else
+		{
+			sprintf(texture_name,"images/%s",p_texture_name);
+		}
+	}
+	
+	m_checksum = s_convert_filename_to_checksum( texture_name );
+	
+	return plat_load_texture(texture_name, sprite, alloc_vram);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::LoadTextureFromBuffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram)
+{								 	
+#ifdef	__NOPT_ASSERT__
+	const char* p_name = Script::FindChecksumName(texture_checksum);
+	mp_name = new char[strlen(p_name)+1];
+	sprintf (mp_name,"%s",p_name);	
+#endif
+	m_checksum = texture_checksum;
+	return plat_load_texture_from_buffer(p_buffer, buffer_size, sprite, alloc_vram);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::ReplaceTexture(CTexture *p_texture)
+{								 	
+	return plat_replace_texture(p_texture);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::Generate32BitImage(bool renderable, bool store_original)
+{
+	return plat_generate_32bit_image(renderable, store_original);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::Put32BitImageIntoTexture(bool new_palette)
+{
+	return plat_put_32bit_image_into_texture(new_palette);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::Offset(int x_pixels, int y_pixels, bool use_fill_color, Image::RGBA fill_color)
+{
+	Dbg_MsgAssert(abs(x_pixels) < GetWidth(), ("x_pixels is out of range: %d", x_pixels));
+	Dbg_MsgAssert(abs(y_pixels) < GetHeight(), ("y_pixels is out of range: %d", y_pixels));
+
+	return plat_offset(x_pixels, y_pixels, use_fill_color, fill_color);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::AdjustRegion(uint16 x_pos, uint16 y_pos, uint16 width, uint16 height,
+							   int split_axis, uint16 start_point, uint16 end_point)
+{
+	Dbg_MsgAssert((split_axis >= X) && (split_axis <= Y), ("Split axis is out of range: %d", split_axis));
+	Dbg_MsgAssert(x_pos < GetWidth(), ("x_pos is out of range: %d", x_pos));
+	Dbg_MsgAssert(y_pos < GetHeight(), ("y_pos is out of range: %d", y_pos));
+	Dbg_MsgAssert(width <= GetWidth(), ("width is out of range: %d", width));
+	Dbg_MsgAssert(height <= GetHeight(), ("height is out of range: %d", height));
+
+	return plat_adjust_region(x_pos, y_pos, width, height, split_axis, start_point, end_point);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::PullToEdge(uint16 point, int axis, int num_pixels)
+{
+	Dbg_MsgAssert((axis >= X) && (axis <= Y), ("axis is out of range: %d", axis));
+
+	return plat_pull_to_edge(point, axis, num_pixels);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::PushToPoint(uint16 point, int axis, int num_pixels, bool use_fill_color, Image::RGBA fill_color)
+{
+	Dbg_MsgAssert((axis >= X) && (axis <= Y), ("axis is out of range: %d", axis));
+
+	return plat_push_to_point(point, axis, num_pixels, use_fill_color, fill_color);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::AdjustBrightness(float brightness_scale, bool force_adjust_current)
+{
+	return plat_adjust_brightness(brightness_scale, force_adjust_current);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::AdjustHSV(float h, float s, float v, bool force_adjust_current)
+{
+	return plat_adjust_hsv(h, s, v, force_adjust_current);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::AddToVram()
+{								 	
+	return plat_add_to_vram();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::RemoveFromVram()
+{								 	
+	return plat_remove_from_vram();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CTexture::GetChecksum() const
+{
+	return m_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CTexture::GetWidth() const
+{
+	return plat_get_width();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CTexture::GetHeight() const
+{
+	return plat_get_height();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CTexture::GetBitDepth() const
+{
+	return plat_get_bitdepth();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CTexture::GetPaletteBitDepth() const
+{
+	return plat_get_palette_bitdepth();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CTexture::GetNumMipmaps() const
+{
+	return plat_get_num_mipmaps();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::IsTransparent() const
+{
+	return plat_is_transparent();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::CombineTextures(CTexture *p_texture, bool palette_gen)
+{
+	return plat_combine_textures(p_texture, palette_gen);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions are provided here:
+// so engine implementors can leave certain functionality until later
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram)
+{
+	printf ("STUB: PlatLoadTexture\n");
+	Dbg_Assert(0);
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, bool sprite, bool alloc_vram)
+{
+	printf ("STUB: PlatLoadTextureFromBuffer\n");
+	Dbg_MsgAssert(0, ("This function was only supposed to be called on the PS2"));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_replace_texture(CTexture *p_texture)
+{								 	
+	printf ("STUB: PlatReplaceTexture\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_generate_32bit_image(bool renderable, bool store_original)
+{
+	printf ("STUB: PlatGenerate32BitImage\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_put_32bit_image_into_texture(bool new_palette)
+{
+	printf ("STUB: PlatPut32BitImageIntoTexture\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_offset(int x_pixels, int y_pixels, bool use_fill_color, Image::RGBA fill_color)
+{
+	printf ("STUB: PlatOffset\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_adjust_region(uint16 x_pos, uint16 y_pos, uint16 width, uint16 height,
+									 int split_axis, uint16 start_point, uint16 end_point)
+{								 	
+	printf ("STUB: PlatAdjustRegion\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_pull_to_edge(uint16 point, int axis, int num_pixels)
+{
+	printf ("STUB: PlatPullToEdge\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_push_to_point(uint16 point, int axis, int num_pixels, bool use_fill_color, Image::RGBA fill_color)
+{
+	printf ("STUB: PlatPushToPoint\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_adjust_brightness(float brightness_scale, bool force_adjust_current)
+{
+	printf ("STUB: PlatAdjustBrightness\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_adjust_hsv(float h, float s, float v, bool force_adjust_current)
+{
+	printf ("STUB: PlatAdjustHSV\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_add_to_vram()
+{								 	
+	printf ("STUB: PlatAddToVram\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_remove_from_vram()
+{								 	
+	printf ("STUB: PlatRemoveFromVram\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CTexture::plat_get_width() const
+{
+	printf ("STUB: PlatGetWidth\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CTexture::plat_get_height() const
+{
+	printf ("STUB: PlatGetHeight\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CTexture::plat_get_bitdepth() const
+{
+	printf ("STUB: PlatGetBitDepth\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CTexture::plat_get_palette_bitdepth() const
+{
+	printf ("STUB: PlatGetPaletteBitDepth\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CTexture::plat_get_num_mipmaps() const
+{
+	printf ("STUB: PlatGetNumMipmaps\n");
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_is_transparent() const
+{
+	printf ("STUB: PlatIsTransparent\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTexture::plat_combine_textures(CTexture *p_texture, bool palette_gen)
+{
+	printf ("STUB: PlatCombineTextures\n");
+	return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// CMaterial
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMaterial::CMaterial() : mp_texture(NULL)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA			CMaterial::GetRGBA() const
+{
+	return plat_get_rgba();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CMaterial::SetRGBA(Image::RGBA rgba)
+{
+	plat_set_rgba(rgba);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CMaterial::GetTexture() const
+{
+	return mp_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CMaterial::SetTexture(CTexture *tex)
+{
+	mp_texture = tex;
+	plat_set_texture(tex);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions:
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Image::RGBA 		CMaterial::plat_get_rgba() const
+{
+	printf ("STUB: PlatGetRGBA\n");
+	return Image::RGBA();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CMaterial::plat_set_rgba(Image::RGBA rgba)
+{
+	printf ("STUB: PlatSetRGBA\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CMaterial::plat_set_texture(CTexture *tex)
+{
+	printf ("STUB: PlatSetTexture\n");
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// CTexDict
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict::CTexDict(uint32 checksum, bool create_lookup_table)
+{
+	m_checksum = checksum;
+	m_ref_count = 1;	// Belongs to someone
+	
+	// Mick: for now, since most texture dictionaries don't need to reference
+	// the textures by name, we only created the lookup table when it is needed
+	// so we save memory	
+	// Gary: Well, actually, the create-a-skater texture dictionaries
+	// do need lookup tables, just in case there's texture
+	// replacement.  So, this constructor should be rewritten so
+	// that it takes a parameter telling it whether it should
+	// create the hashtable.
+	// Garrett: OK
+	if (create_lookup_table)
+	{
+		mp_texture_lookup = new Lst::HashTable(5);
+	}
+	else
+	{
+		mp_texture_lookup = NULL;
+	}
+
+	// Derived class loads the actual file
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict::CTexDict(const char* p_tex_dict_name, bool create_lookup_table)
+{
+	m_checksum = Crc::GenerateCRCFromString(p_tex_dict_name);
+	m_ref_count = 1;	// Just belong to one scene
+
+	// Mick: for now, since most texture dictionaries don't need to reference
+	// the textures by name, we only created the lookup table when it is needed
+	// so we save memory	
+	// Gary: Well, actually, the create-a-skater texture dictionaries
+	// do need lookup tables, just in case there's texture
+	// replacement.  So, this constructor should be rewritten so
+	// that it takes a parameter telling it whether it should
+	// create the hashtable.
+	// Garrett: OK
+	if (create_lookup_table)
+	{
+		mp_texture_lookup = new Lst::HashTable(5);
+	}
+	else
+	{
+		mp_texture_lookup = NULL;
+	}
+
+	// Derived class loads the actual file
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexDict::~CTexDict()
+{
+	// Unload everything
+	//plat_unload_texture_dictionary();					// the derived class does this
+
+	FlushTextures(true);
+
+	if (mp_texture_lookup)
+	{
+		delete mp_texture_lookup;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Flush all non permanent textures
+void CTexDict::FlushTextures(bool flush_all)
+{
+	if (mp_texture_lookup)
+	{
+		// Delete textures
+		mp_texture_lookup->IterateStart();
+		CTexture *p_texture = mp_texture_lookup->IterateNext();
+		while (p_texture)
+		{
+			CTexture *p_next = mp_texture_lookup->IterateNext();
+			if (flush_all || !p_texture->IsPerm())
+			{
+				#ifdef	__NOPT_ASSERT__
+				if (p_texture->GetName())
+				{
+//					printf ("deleting particle tex %s\n",p_texture->GetName());
+				}
+				#endif				
+				uint32 checksum = p_texture->GetChecksum();			
+				delete p_texture;
+				mp_texture_lookup->FlushItem(checksum);			
+			}
+			else
+			{
+							#ifdef	__NOPT_ASSERT__
+				if (p_texture->GetName())
+				{
+//					printf ("NOT deleting particle tex %s  perm = %d\n",p_texture->GetName(),p_texture->IsPerm());
+				}
+				#endif				
+
+			}
+			p_texture = p_next;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::LoadTexture(const char *p_texture_name, bool sprite, bool alloc_vram, bool perm)
+{
+	Mem::PushMemProfile((char*)p_texture_name);
+	
+	
+	CTexture *p_texture = plat_load_texture(p_texture_name, sprite, alloc_vram);
+
+	if (p_texture)
+	{
+		p_texture->SetPerm(perm);
+		mp_texture_lookup->PutItem(p_texture->GetChecksum(), p_texture);
+		
+	} else {
+		Dbg_Error("Texture %s not found", p_texture_name);
+	}
+
+	Mem::PopMemProfile();
+	return p_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture * 			CTexDict::LoadTextureFromBuffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram, bool perm)
+{
+	Mem::PushMemProfile((char*)"texture from buffer");
+	CTexture *p_texture = plat_load_texture_from_buffer(p_buffer, buffer_size, texture_checksum, sprite, alloc_vram);
+
+	if (p_texture)
+	{
+		p_texture->SetPerm(perm);
+		mp_texture_lookup->PutItem(p_texture->GetChecksum(), p_texture);
+		
+	} else {
+		Dbg_MsgAssert(0,("Couldn't load texture %s from buffer",Script::FindChecksumName(texture_checksum)));
+	}
+
+	Mem::PopMemProfile();
+	return p_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CTexDict::UnloadTexture(CTexture *p_texture)
+{
+	uint32 checksum = p_texture->GetChecksum();
+
+	// Because the texture might be still being used, we have to wait for the frame to 
+	// finish rendering
+	Nx::CEngine::sFinishRendering();
+
+	if (plat_unload_texture(p_texture))
+	{
+		mp_texture_lookup->FlushItem(checksum);
+		return true;
+	} else {
+		Dbg_Error("Cannot unload texture %x", checksum);
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CTexDict::AddTexture(CTexture *p_texture)
+{
+	Dbg_Assert(mp_texture_lookup);
+	Dbg_Assert(p_texture);
+
+	mp_texture_lookup->PutItem(p_texture->GetChecksum(), p_texture);
+
+	plat_add_texture(p_texture);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture* CTexDict::get_source_texture( const char* p_src_texture_name )
+{
+	uint32 src_checksum = Crc::GenerateCRCFromString( p_src_texture_name );
+	//src_checksum *= 24;
+
+	// look for the texture with that checksum
+	static int modifiers[] = { 2, 3, 4, 2 * 3, 3 * 4, 2 * 4, 2 * 3 * 4, -1 };
+	CTexture* pTexture = NULL;
+	for (int idx = 0; modifiers[idx] > 0; idx++)
+	{
+		uint32 calc_checksum = src_checksum * modifiers[idx];
+		pTexture = GetTexture( calc_checksum );
+
+		if (pTexture)
+		{
+			src_checksum *= calc_checksum;
+			break;
+		}
+	}
+
+	// If we haven't found the texture at this stage, it may be because an auto-generated MIP version was used.
+	// In which case, the name will have _auto32m0 appended to it. Try searching for this name instead.
+	if( pTexture == NULL )
+	{
+		char auto32_name[512];
+		strcpy( auto32_name, p_src_texture_name );
+
+		// Remove the .png, and add the new suffix.
+		int length = strlen( auto32_name );
+		if( length > 4 )
+		{
+			auto32_name[strlen( auto32_name ) - 4] = 0;
+
+			// Append the extra bit.
+			strcat( auto32_name, "_auto32m0.png" );
+		}
+		
+		src_checksum = Crc::GenerateCRCFromString( auto32_name );
+		for( int idx = 0; modifiers[idx] > 0; idx++ )
+		{
+			uint32 calc_checksum = src_checksum * modifiers[idx];
+			pTexture = GetTexture( calc_checksum );
+			if( pTexture )
+			{
+				src_checksum *= calc_checksum;
+				break;
+			}
+		}
+	}
+
+	return pTexture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTexDict::ReplaceTexture(const char* p_src_texture_name, const char* p_dst_texture_name)
+{
+	CTexture* pTexture = get_source_texture( p_src_texture_name );
+	
+	if ( pTexture )
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+#ifdef __NOPT_ASSERT__
+		if ( Script::GetInt( CRCD(0x2a648514,"cas_artist"), false ) )
+		{
+			Dbg_Message( "Replacing %s with %s here", p_src_texture_name, p_dst_texture_name );
+		}
+#endif
+
+		CTexture *p_new_texture = LoadTexture(p_dst_texture_name, false, false);
+		Dbg_MsgAssert(p_new_texture, ("Can't find replacement texture %s", p_dst_texture_name));
+
+		bool result = pTexture->ReplaceTexture( p_new_texture );
+		Dbg_MsgAssert(result, ("Can't replace texture %s with %s", p_src_texture_name, p_dst_texture_name));
+
+		// We're done with the new texture, get rid of it
+		UnloadTexture(p_new_texture);
+
+		Mem::Manager::sHandle().PopContext();
+
+		return result;
+	}
+	else
+	{
+		// GJ:  The texture checksum lookup table is only
+		// available for sprite tex dicts, not for CAS
+		// parts, so I've commented out the following for now...
+//		if ( Script::GetInt( "cas_artist", false ) )
+//		{
+//			Dbg_Message( "Couldn't find %s (%08x) in order to replace texture %s", p_src_texture_name, src_checksum, p_dst_texture_name );
+//		}
+		
+		Dbg_Assert( mp_texture_lookup );
+
+/*
+		// For debugging...
+		
+		if (mp_texture_lookup)
+		{
+			mp_texture_lookup->IterateStart();
+			CTexture *p_texture;
+			int count = 0;
+			while ((p_texture = mp_texture_lookup->IterateNext()))
+			{
+				Dbg_Message("Found Checksum #%d %08x", count, p_texture->GetChecksum());
+				count++;
+			}
+			Dbg_Assert( count );
+		}
+*/
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTexDict::ReplaceTexture( const char* p_src_texture_name, CTexture* p_dst_texture )
+{
+	CTexture* pTexture = get_source_texture( p_src_texture_name );
+	
+	if ( pTexture )
+	{
+		bool result = pTexture->ReplaceTexture( p_dst_texture );
+		Dbg_MsgAssert(result, ("Can't replace texture %s with CTexture %p", p_src_texture_name, p_dst_texture));
+
+		return result;
+	}
+	else
+	{
+		// GJ:  The texture checksum lookup table is only
+		// available for sprite tex dicts, not for CAS
+		// parts, so I've commented out the following for now...
+//		if ( Script::GetInt( "cas_artist", false ) )
+//		{
+//			Dbg_Message( "Couldn't find %s (%08x) in order to replace texture %s", p_src_texture_name, src_checksum, p_dst_texture_name );
+//		}
+		
+		Dbg_Assert( mp_texture_lookup );
+
+/*
+		// For debugging...
+		
+		if (mp_texture_lookup)
+		{
+			mp_texture_lookup->IterateStart();
+			CTexture *p_texture;
+			int count = 0;
+			while ((p_texture = mp_texture_lookup->IterateNext()))
+			{
+				Dbg_Message("Found Checksum #%d %08x", count, p_texture->GetChecksum());
+				count++;
+			}
+			Dbg_Assert( count );
+		}
+*/
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::CopyTexture(uint32 new_texture_checksum, CTexture *p_texture)
+{
+	CTexture *p_new_texture = plat_copy_texture(new_texture_checksum, p_texture);
+
+	if (p_new_texture)
+	{
+		p_new_texture->m_checksum = new_texture_checksum;
+		mp_texture_lookup->PutItem(new_texture_checksum, p_new_texture);
+	} else {
+		Dbg_Error("Could not create new CTexture %x", new_texture_checksum);
+	}
+
+	return p_new_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::CombineTextures(uint32 new_texture_checksum, CTexture *p_texture1, CTexture *p_texture2, bool palette_gen)
+{
+	CTexture *p_new_texture = CopyTexture(new_texture_checksum, p_texture1);
+
+	if (p_new_texture)
+	{
+		p_new_texture->CombineTextures(p_texture2, palette_gen);
+	} else {
+		Dbg_Error("Could not create new CTexture for CombineTexture of %x", new_texture_checksum);
+	}
+
+	return p_new_texture;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::GetTexture(uint32 checksum) const
+{
+	if ( !mp_texture_lookup )
+	{
+		Dbg_Error( "No texture lookup has been created for this texdict" );
+		return NULL;
+	}
+
+	return mp_texture_lookup->GetItem(checksum);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::GetTexture(const char *p_texture_name) const
+{
+	return GetTexture(s_convert_filename_to_checksum(p_texture_name));
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions:
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram)
+{
+	printf ("STUB: PlatLoadTexture\n");
+	Dbg_Assert(0);
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram)
+{
+	printf ("STUB: PlatLoadTextureFromBuffer\n");
+	Dbg_MsgAssert(0, ("This function was only supposed to be called on the PS2"));
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::plat_reload_texture(const char *p_texture_name)
+{
+	printf ("STUB: PlatReloadTexture\n");
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CTexDict::plat_unload_texture(CTexture *p_texture)
+{
+	printf ("STUB: PlatUnloadTexture\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CTexDict::plat_add_texture(CTexture *p_texture)
+{
+	printf ("STUB: PlatAddTexture\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CTexDict::plat_remove_texture(CTexture *p_texture)
+{
+	printf ("STUB: PlatRemoveTexture\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *			CTexDict::plat_copy_texture(uint32 new_texture_checksum, CTexture *p_texture)
+{
+	printf ("STUB: PlatCopyTexture\n");
+	return NULL;
+}
+
+}
+
+
diff --git a/Code/Gfx/NxTexture.h b/Code/Gfx/NxTexture.h
new file mode 100644
index 0000000..14bc7c0
--- /dev/null
+++ b/Code/Gfx/NxTexture.h
@@ -0,0 +1,268 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NxTexture.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_TEXTURE_H__
+#define	__GFX_NX_TEXTURE_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+#include 
+
+
+namespace Nx
+{
+
+class CTexDict;
+class CTexDictManager;
+
+//////////////////////////////////////////////////////////////////////////////////
+// Nx::CTexture replaces RwTexture
+class	CTexture
+{
+public:
+							CTexture();
+							CTexture(const CTexture & src_texture);		// Copy constructor
+	virtual					~CTexture();
+
+	bool					LoadTexture(const char *p_texture_name, bool sprite, bool alloc_vram = true);
+	bool					LoadTextureFromBuffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram = true);
+	bool					ReplaceTexture(CTexture *p_texture);
+
+	// Temp 32-bit texture image generation.  All the texture manipulation routines need to use
+	// 32-bit versions of the texture.  But to palettize every operation can be slow, so keeping
+	// the temp buffer around speeds things up greatly.
+	bool					Generate32BitImage(bool renderble = false, bool store_original = false);
+	bool					Put32BitImageIntoTexture(bool new_palette = false);
+
+	// Combines current texture with new texture (overwriting original one)
+	bool					CombineTextures(CTexture *p_texture, bool palette_gen = true);
+
+	// Move texture (cropping the part that moves off and filling in the new parts with either the old edge or a
+	// supplied fill color)
+	bool					Offset(int x_pixels, int y_pixels, bool use_fill_color = false,
+								   Image::RGBA fill_color = Image::RGBA(128, 128, 128, 128));
+
+	// This function will scale a rectangular section of a texture.  Each side of the texture
+	// will be stretched or shrunk from the start_point to the end_point along the split axis.
+	bool					AdjustRegion(uint16 x_pos, uint16 y_pos, uint16 width, uint16 height,
+										 int split_axis, uint16 start_point, uint16 end_point);
+
+	// Pull texture from point along the axis by num_pixels (+ or - determines the direction)
+	// and crop anything going outside the texture border.
+	bool					PullToEdge(uint16 point, int axis, int num_pixels);
+	// Similar to PullToEdge(), except it pushes the texture from the edge to the point.  Instead
+	// of cropping, it either stretches the edge line or fills it in with a fill color
+	bool					PushToPoint(uint16 point, int axis, int num_pixels, bool use_fill_color = false,
+										Image::RGBA fill_color = Image::RGBA(128, 128, 128, 128));
+
+	bool					AdjustBrightness(float brightness_scale, bool force_adjust_current = false);
+	bool					AdjustHSV(float h, float s, float v, bool force_adjust_current = false);
+
+	// These VRAM calls won't do anything on platforms that don't have separate
+	// texture memory
+	bool					AddToVram();
+	bool					RemoveFromVram();
+
+	uint32					GetChecksum() const;
+	uint16					GetWidth() const;
+	uint16					GetHeight() const;
+	uint8					GetBitDepth() const;
+	uint8					GetPaletteBitDepth() const;
+	uint8					GetNumMipmaps() const;
+	bool					IsTransparent() const;
+
+	void					SetPerm( bool perm) {m_perm = perm;}
+	bool					IsPerm() {return m_perm;}
+
+#ifdef	__NOPT_ASSERT__
+	char *					GetName() {return mp_name;}
+#else
+	char *					GetName() {return "WARNING NO NAME";}
+#endif	
+	
+
+protected:
+
+	uint32					m_checksum;
+
+	// So it can access set_checksum()
+	friend CTexDict;
+
+private:
+	virtual bool			plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram);
+	virtual bool			plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, bool sprite, bool alloc_vram);
+	virtual bool			plat_replace_texture(CTexture *p_texture);
+
+	virtual bool			plat_generate_32bit_image(bool renderble = false, bool store_original = false);
+	virtual bool			plat_put_32bit_image_into_texture(bool new_palette = false);
+
+	virtual bool			plat_offset(int x_pixels, int y_pixels, bool use_fill_color, Image::RGBA fill_color);
+
+	virtual bool			plat_adjust_region(uint16 x_pos, uint16 y_pos, uint16 width, uint16 height,
+											   int split_axis, uint16 start_point, uint16 end_point);
+
+	virtual bool			plat_pull_to_edge(uint16 point, int axis, int num_pixels);
+	virtual bool			plat_push_to_point(uint16 point, int axis, int num_pixels, bool use_fill_color,
+											   Image::RGBA fill_color);
+
+	virtual bool			plat_adjust_brightness(float brightness_scale, bool force_adjust_current = false);
+	virtual bool			plat_adjust_hsv(float h, float s, float v, bool force_adjust_current = false);
+
+	virtual bool			plat_add_to_vram();
+	virtual bool			plat_remove_from_vram();
+
+	virtual uint16			plat_get_width() const;
+	virtual uint16			plat_get_height() const;
+	virtual uint8			plat_get_bitdepth() const;
+	virtual uint8			plat_get_palette_bitdepth() const;
+	virtual uint8			plat_get_num_mipmaps() const;
+	virtual bool			plat_is_transparent() const;
+
+	virtual bool			plat_combine_textures(CTexture *p_texture, bool palette_gen);
+	
+	bool					m_perm;		// set to true if this texture should survive a level change 
+	
+	#ifdef	__NOPT_ASSERT__
+	char	*				mp_name;
+	#endif
+
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// Nx::CMaterial
+class	CMaterial
+{
+public:
+							CMaterial();
+	virtual					~CMaterial()	{}
+
+	Image::RGBA				GetRGBA() const;
+	void					SetRGBA(Image::RGBA rgba);
+	CTexture *				GetTexture() const;
+	void					SetTexture(CTexture *tex);
+
+protected:
+	uint32					m_checksum;
+	CTexture *				mp_texture;
+
+private:
+	virtual Image::RGBA		plat_get_rgba() const;
+	virtual void			plat_set_rgba(Image::RGBA rgba);
+	virtual void			plat_set_texture(CTexture *tex);
+
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// Nx::CTexDict
+class	CTexDict
+{
+public:
+	// The constructor and destructor start the loading and unloading
+							CTexDict(uint32 checksum, bool create_lookup_table = true);	// loads nothing
+							CTexDict(const char *p_tex_dict_name, bool create_lookup_table = false);
+	virtual					~CTexDict();
+
+	void					IncRefCount();
+	int16					DecRefCount();
+
+	// The load and unload functions probably will change, since
+	// we will only load and unload whole texture dictionaries.
+	// For now, they will only be used for the 2D textures.
+	CTexture *				LoadTexture(const char *p_texture_name, bool sprite, bool alloc_vram = true, bool perm = true);
+	CTexture *				LoadTextureFromBuffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram = true, bool perm = true);
+	CTexture *				ReloadTexture(const char *p_texture_name);
+	bool					UnloadTexture(CTexture *p_texture);
+	void					AddTexture(CTexture *p_texture);
+	bool					RemoveTexture(CTexture *p_texture);
+	bool					ReplaceTexture( const char* p_src_texture_name, const char* p_dst_texture_name );
+	bool					ReplaceTexture( const char* p_src_texture_name, CTexture* p_dst_texture );
+
+	CTexture *				CopyTexture(uint32 new_texture_checksum, CTexture *p_texture);
+	// Combine two textures to make a new texture
+	CTexture *				CombineTextures(uint32 new_texture_checksum, CTexture *p_texture1, CTexture *p_texture2, bool palette_gen = true);
+
+	uint32					GetChecksum() const;
+	CTexture *  			GetTexture(uint32 checksum) const;
+	CTexture *  			GetTexture(const char *p_texture_name) const;
+	
+	uint32					GetFileSize() {return m_file_size;}
+
+	Lst::HashTable *GetTexLookup( void ) { return mp_texture_lookup; }
+
+	void					FlushTextures(bool flush_all);	// flush textures, optionally flushing perm textures
+
+protected:
+	void					set_checksum(uint32 checksum);
+	CTexture*				get_source_texture( const char* p_src_texture_name );
+
+	uint32					m_checksum;
+	int16					m_ref_count;
+
+	Lst::HashTable *	mp_texture_lookup;
+
+	// So it can access set_checksum()
+	friend CTexDictManager;
+
+private:	
+	// Platform-specific calls
+	virtual CTexture *			plat_load_texture(const char *p_texture_name, bool sprite, bool alloc_vram);
+	virtual CTexture *			plat_load_texture_from_buffer(uint8* p_buffer, int buffer_size, uint32 texture_checksum, bool sprite, bool alloc_vram);
+	virtual CTexture *			plat_reload_texture(const char *p_texture_name);
+	virtual bool				plat_unload_texture(CTexture *p_texture);
+	virtual void				plat_add_texture(CTexture *p_texture);
+	virtual bool				plat_remove_texture(CTexture *p_texture);
+	virtual CTexture *			plat_copy_texture(uint32 new_texture_checksum, CTexture *p_texture);
+
+protected:
+	uint32						m_file_size;		// DEBUGGING USE ONLY, NOT GUARENTEED							 
+								 
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void			CTexDict::IncRefCount()
+{
+	m_ref_count++;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int16		CTexDict::DecRefCount()
+{
+	return --m_ref_count;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32		CTexDict::GetChecksum() const
+{
+	return m_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void			CTexDict::set_checksum(uint32 checksum)
+{
+	m_checksum = checksum;
+}
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/NxTextured3dPoly.cpp b/Code/Gfx/NxTextured3dPoly.cpp
new file mode 100644
index 0000000..ba702a5
--- /dev/null
+++ b/Code/Gfx/NxTextured3dPoly.cpp
@@ -0,0 +1,63 @@
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+static Lst::HashTable< Nx::CTextured3dPoly > s_poly_table(1);
+
+void CTextured3dPoly::sRenderAll()
+{
+	CTextured3dPoly *p_poly;
+	s_poly_table.IterateStart();
+	while(( p_poly = s_poly_table.IterateNext()))
+	{
+		p_poly->Render();
+	}
+}
+
+CTextured3dPoly::CTextured3dPoly()
+{
+	s_poly_table.PutItem((uint32)this,this);
+}
+
+CTextured3dPoly::~CTextured3dPoly()
+{
+	s_poly_table.FlushItem((uint32)this);
+}
+
+void CTextured3dPoly::SetTexture(uint32 texture_checksum)
+{
+	plat_set_texture(texture_checksum);
+}
+
+void CTextured3dPoly::SetTexture(const char *p_textureName)
+{
+	SetTexture(Crc::GenerateCRCFromString(p_textureName));
+}
+	
+void CTextured3dPoly::SetPos(const Mth::Vector &pos, float width, float height, const Mth::Vector &normal, float angle)
+{
+	Mth::Vector offx(1.0f,0.0f,0.0f);
+	Mth::Vector offz=Mth::CrossProduct(normal,offx);
+	
+	offx*=width/2.0f;
+	offz*=height/2.0f;
+	
+	mp_pos[0]=pos-offx-offz;
+	mp_pos[1]=pos+offx-offz;
+	mp_pos[2]=pos+offx+offz;
+	mp_pos[3]=pos-offx+offz;
+}
+
+void CTextured3dPoly::Render()
+{
+	plat_render();
+}	
+
+} // namespace Nx
+
diff --git a/Code/Gfx/NxTextured3dPoly.h b/Code/Gfx/NxTextured3dPoly.h
new file mode 100644
index 0000000..a529ad7
--- /dev/null
+++ b/Code/Gfx/NxTextured3dPoly.h
@@ -0,0 +1,47 @@
+//////////////////////////////////////////////////////////////////////////////////////////////
+// NxTextured3dPoly.h - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_TEXTURED_3D_POLY_H__
+#define	__GFX_NX_TEXTURED_3D_POLY_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __CORE_MATH_VECTOR_H
+#include 
+#endif
+
+namespace Nx
+{
+
+// Class for displaying a single textured 3d poly. Currently used by the pedestrian shadows, which
+// are just simple circular textures oriented to the ground.
+class	CTextured3dPoly
+{
+protected:
+	Mth::Vector mp_pos[4];
+	
+public:
+							CTextured3dPoly();
+	virtual					~CTextured3dPoly();
+
+	void					SetTexture(const char *p_textureName);
+	void 					SetTexture(uint32 texture_checksum);
+	
+	void					SetPos(const Mth::Vector &pos, float width, float height, const Mth::Vector &normal, float angle=0.0f);
+
+	void					Render();
+
+	static void				sRenderAll();
+		
+private:
+	virtual void			plat_render() {}
+	virtual void			plat_set_texture(uint32 texture_checksum) {}
+	
+};
+
+}
+
+#endif
+
diff --git a/Code/Gfx/NxViewMan.cpp b/Code/Gfx/NxViewMan.cpp
new file mode 100644
index 0000000..b1f2051
--- /dev/null
+++ b/Code/Gfx/NxViewMan.cpp
@@ -0,0 +1,658 @@
+
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Nx Viewport Manager										**
+**																			**
+**	File name:		gfx/nxviewman.cpp										**
+**																			**
+**	Created by:		04/30/02	-	grj										**
+**																			**
+**	Description:	Viewport Manager										**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC nxviewman
+// @module nxviewman | None
+// @subindex Scripting Database
+// @index script | nxviewman
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+// This defines the width to height ratio of the camera's *view* window, and
+// as such does not need to be changed for different screen modes or systems.
+const float vBASE_ASPECT_RATIO = 1.428566f;
+
+const float	CViewportManager::s_viewport_rects[vNUM_VIEWPORTS][4] =
+{
+//    x      y      w     h
+	{ 0.0f,  0.0f,  1.0f, 1.0f},	// vMAIN
+	{ 0.0f,  0.0f,  1.0f, 1.0f},	// vSECONDPLAYER (2nd player fullscreen)
+	{ 0.0f,  0.0f,  0.5f, 1.0f},	// vSPLIT_V_LEFT
+	{ 0.5f,  0.0f,  0.5f, 1.0f},	// vSPLIT_V_RIGHT
+	{ 0.0f,  0.0f,  1.0f, 0.5f},	// vSPLIT_H_TOP
+	{ 0.0f,  0.5f,  1.0f, 0.5f},	// vSPLIT_H_BOTTOM
+	{ 0.0f,  0.0f,  0.5f, 0.5f},	// vSPLIT_QUARTER_UL
+	{ 0.5f,  0.0f,  0.5f, 0.5f},	// vSPLIT_QUARTER_UR
+	{ 0.0f,  0.5f,  0.5f, 0.5f},	// vSPLIT_QUARTER_LL
+	{ 0.5f,  0.5f,  0.5f, 0.5f}		// vSPLIT_QUARTER_LR
+};
+
+const int 	CViewportManager::s_num_active_viewports[vNUM_SCREEN_MODES]
+							 = { 1, 2, 2, 4, 1, 1 };
+const int	CViewportManager::s_screen_mode_viewport_indexes[vNUM_SCREEN_MODES][vMAX_NUM_ACTIVE_VIEWPORTS]
+					= { { vMAIN,			 -1,				-1,				   -1 },				// vONE_CAM
+						{ vSPLIT_V_LEFT,	 vSPLIT_V_RIGHT,	-1,				   -1 },				// vSPLIT_V
+						{ vSPLIT_H_TOP,		 vSPLIT_H_BOTTOM,	-1,				   -1 },				// vSPLIT_H
+						{ vSPLIT_QUARTER_UL, vSPLIT_QUARTER_UR, vSPLIT_QUARTER_LL, vSPLIT_QUARTER_LR },	// vSPLIT_QUARTERS
+						{ vMAIN,			 -1,				-1,				   -1 },				// vHORSE1
+						{ -1,				 vSECONDPLAYER,		-1,				   -1 },				// vHORSE2
+					  };
+
+
+CViewport*	CViewportManager::sp_viewports[vNUM_VIEWPORTS];
+const int*	CViewportManager::sp_active_viewport_indexes = s_screen_mode_viewport_indexes[0];
+
+ScreenMode	CViewportManager::s_screen_mode;
+int			CViewportManager::s_num_active_viewports_cached=0;
+
+float		CViewportManager::s_default_angle;
+float		CViewportManager::s_screen_angle;
+float		CViewportManager::s_screen_angle_factor = 1.0f;
+float		CViewportManager::s_screen_aspect;
+
+float		CViewportManager::s_2D_in_3D_space_noscale_distance = 300.0f;
+float		CViewportManager::s_2D_in_3D_space_max_scale = 3.0f;
+float		CViewportManager::s_2D_in_3D_space_min_scale = 0.0f;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 		CViewportManager::sInit( void )
+{
+	// Create viewports
+	for ( uint32 i = 0; i < vNUM_VIEWPORTS; i++ )
+	{
+		Mth::Rect theRect( s_viewport_rects[i][0], s_viewport_rects[i][1], s_viewport_rects[i][2], s_viewport_rects[i][3] );
+
+		sp_viewports[i] = s_create_viewport(&theRect);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CViewportManager::sCleanup( void )
+{
+	for ( uint32 i = 0; i < vNUM_VIEWPORTS; i++ )
+	{
+		if ( sp_viewports[i] )
+		{
+			delete sp_viewports[i];
+			sp_viewports[i] = NULL;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewport*	CViewportManager::sGetActiveViewport(int view)
+{
+	Dbg_MsgAssert(view < s_num_active_viewports[s_screen_mode], ("Active viewport number is invalid %d", view));
+
+	int found_views = 0;
+
+	// Go through array, and don't count invalid viewports
+	for ( uint32 i = 0; i < vNUM_VIEWPORTS; i++ )
+	{
+		if (sp_active_viewport_indexes[i] >= 0)
+		{
+			if (found_views == view)
+			{
+				return sp_viewports[sp_active_viewport_indexes[i]];
+			}
+			found_views++;
+		}
+	}
+
+	Dbg_MsgAssert(0, ("Cant find active viewport # %d", view));
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			CViewportManager::sGetActiveViewportNumber(CViewport *p_viewport)
+{
+	int found_views = 0;
+
+	// Go through array, and don't count invalid viewports
+	for ( uint32 i = 0; i < vNUM_VIEWPORTS; i++ )
+	{
+		if (sp_active_viewport_indexes[i] >= 0)
+		{
+			if (sp_viewports[sp_active_viewport_indexes[i]] == p_viewport)
+			{
+				return found_views;
+			}
+			found_views++;
+		}
+	}
+
+	// Not found
+	return -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::Camera* CViewportManager::sGetCamera(int index)
+{
+	//Dbg_MsgAssert(sp_active_viewport_indexes[index] >= 0, ("Viewport index invalid %d from camera %d, screen mode %d ", sp_active_viewport_indexes[index], index, s_screen_mode));
+
+	if (sp_active_viewport_indexes[index] == -1)
+	{
+		return NULL;
+		//printf ("CAMERA WARNING: - Getting invalid camera %d when in screen mode %d (Returning default camera 0)\n",index,s_screen_mode);  
+		//index = 0;
+	}
+
+	return sp_viewports[sp_active_viewport_indexes[index]]->GetCamera();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CViewportManager::sSetCamera(int index, Gfx::Camera* pCamera)
+{
+	Dbg_Assert( index >= 0 && index < vMAX_NUM_ACTIVE_VIEWPORTS );
+	if (sp_active_viewport_indexes[index] == -1)
+	{
+		//printf ("CAMERA WARNING: - Setting invalid camera %d when in screen mode %d (Ignoring)\n",index,s_screen_mode);  
+		return;
+	}
+	sp_viewports[sp_active_viewport_indexes[index]]->SetCamera(pCamera);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CViewportManager::sSetCameraAllScreenModes(int index, Gfx::Camera* pCamera)
+{
+	for (int mode = 0; mode < vNUM_SCREEN_MODES; mode++)
+	{
+		if (s_screen_mode_viewport_indexes[mode][index] >= 0)
+		{
+			//Dbg_Message("******************* SetCameraAllScreenMode: Setting viewport mode %d camera #%d to Camera (%x)", mode, index, pCamera);
+			sp_viewports[s_screen_mode_viewport_indexes[mode][index]]->SetCamera(pCamera);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ScreenMode	CViewportManager::sGetScreenMode()
+{
+	return s_screen_mode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::Camera* CViewportManager::sGetActiveCamera(int cam)
+{
+	CViewport *p_viewport = sGetActiveViewport(cam);
+	Dbg_MsgAssert(p_viewport, ("sGetActiveCamera(): Active viewport index %d invalid for screen mode %d ", cam, s_screen_mode));
+
+	return p_viewport->GetCamera();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::Camera* CViewportManager::sGetClosestCamera(const Mth::Vector &pos, float *distParam)
+{
+	int i;
+	int numCams = sGetNumActiveCameras( );
+	Gfx::Camera *p_camera;
+	Gfx::Camera *p_closestCamera = NULL;
+	float closestDist = -1.0f;
+	float tempDist;
+
+	for ( i = 0; i < numCams; i++ )
+	{
+		p_camera = sGetActiveCamera( i );
+		if ( p_camera )
+		{
+			tempDist = Mth::DistanceSqr( pos, p_camera->GetPos() );
+			if ( ( closestDist < 0 ) || ( tempDist < closestDist ) )
+			{
+				closestDist = tempDist;
+				p_closestCamera = p_camera;
+			}
+		}
+	}
+
+	//Dbg_MsgAssert( p_closestCamera,( "No cameras or what?" ));
+	if ( p_closestCamera == NULL )
+	{
+		return NULL;
+	}
+
+	if ( distParam )
+	{
+		*distParam = sqrtf(closestDist);
+	}
+	return p_closestCamera;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			CViewportManager::sGetNumActiveCameras(void)
+{
+
+	return s_num_active_viewports[s_screen_mode];		// Same number of cams as there are viewports
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CViewportManager::sMarkCameraForDeletion(Gfx::Camera *pCamera)
+{
+	// Check all viewports in case the screen mode is changed after this
+	for ( uint32 i = 0; i < vNUM_VIEWPORTS; i++ )
+	{
+		if ( sp_viewports[i] )
+		{
+			if (sp_viewports[i]->MarkCameraForDeletion(pCamera))
+			{
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CViewportManager::sDeleteMarkedCameras()
+{
+	// Check all viewports in case the screen mode was changed
+	for ( uint32 i = 0; i < vNUM_VIEWPORTS; i++ )
+	{
+		if ( sp_viewports[i] )
+		{
+			sp_viewports[i]->DeleteMarkedCamera();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void    	CViewportManager::sSetScreenMode( ScreenMode mode )
+{
+	// Update active viewport indexes
+	sp_active_viewport_indexes = s_screen_mode_viewport_indexes[mode];
+
+	s_screen_mode = mode;
+	
+	s_num_active_viewports_cached = s_num_active_viewports[s_screen_mode];
+	
+	 
+	// Now update cameras
+	for ( int i = 0; i < s_num_active_viewports[mode]; i++ )
+	{
+		CViewport *p_viewport = sGetActiveViewport(i);
+		Dbg_MsgAssert(p_viewport, ("SetScreenMode: Couldn't find active viewport # %d for mode %d", i, mode));
+
+		// no camera has been bound
+		Gfx::Camera *p_camera = p_viewport->GetCamera();
+		if ( !p_camera )
+		{
+			//return;
+			continue;	   			// just continue, even if there is no camera set up yet.....
+		}
+
+		//Dbg_Message("******************* SetScreenMode: Setting Camera %d (%x) of mode %d", i, p_camera, mode);
+
+		// set up proper aspect ratio for camera
+		switch (mode)
+		{
+			case vSPLIT_V:
+				// camera "window" at same height, half width as full screen camera
+				//p_camera->SetFocalLength ( Script::GetFloat("Skater_Cam_Focal_Length"), vBASE_ASPECT_RATIO / 2.0f);
+				p_camera->SetHFOV(Script::GetFloat("Skater_Cam_Horiz_FOV") / 2.0f);
+				break;
+			case vSPLIT_H:
+				// camera "window" at same width, half height as full screen camera
+				//p_camera->SetFocalLength ( Script::GetFloat("Skater_Cam_Focal_Length") * 2.0f, vBASE_ASPECT_RATIO * 2.0f);
+				p_camera->SetHFOV(Script::GetFloat("Skater_Cam_Horiz_FOV"));
+				break;
+			default:
+				// camera "window" at same width, height as full screen camera
+				//p_camera->SetFocalLength ( Script::GetFloat("Skater_Cam_Focal_Length"), vBASE_ASPECT_RATIO);
+				p_camera->SetHFOV(Script::GetFloat("Skater_Cam_Horiz_FOV"));
+				break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewport *	CViewportManager::s_create_viewport(const Mth::Rect* rect, Gfx::Camera* cam)
+{
+	return s_plat_create_viewport(rect, cam);
+}
+
+
+// some fundamental things about the TV
+//
+// s_screen_aspect is the aspect ratio of the TV
+// this is the physical aspect ratio, and not the pixel ratio
+// thus it will be either 4:3 (4.0f/3.0f) or 16:9 (16.0f/9.0f) 
+//
+// s_screen_angle is the default view angle for a full screen viewport
+// currently (2/8/02) it is set at 72 degrees when screen_aspect is 4:3
+// when we have a wider aspect ratio, we want a wider screen angle
+// however, we don't want to go too wide, or we will render too much
+// 80 degrees is okay, providing us with reduced vertical coverage, but increased
+// horizontal coverage (so skater seems bigger on screen, and you can see more of the level)
+
+
+void				CViewportManager::sSetScreenAspect(float aspect)
+{
+	Dbg_MsgAssert(aspect > 0.1f && aspect <5.0f,("Unlikely value (%f) for screen aspect",aspect));
+	s_screen_aspect = aspect;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float				CViewportManager::sGetScreenAspect()
+{
+	return s_screen_aspect;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void				CViewportManager::sSetDefaultAngle(float angle)
+{
+	Dbg_MsgAssert(angle > 0.01f && angle < 180.0f,("Unlikely value (%f) for screen default angle",angle));
+	s_default_angle = angle;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float				CViewportManager::sGetDefaultAngle()
+{
+	return s_default_angle;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CViewportManager::sSetScreenAngle(float angle)
+{
+	if (angle == 0.0f)
+	{
+		angle = s_default_angle;
+	}
+
+	Dbg_MsgAssert(angle > 0.01f && angle < 180.0f,("Unlikely value (%f) for screen angle",angle));
+	s_screen_angle = angle;
+
+	s_screen_angle_factor = tanf(Mth::DegToRad(angle / 2.0f)) / tanf(Mth::DegToRad(s_default_angle / 2.0f));
+
+	//Dbg_Message("Screen angle %f; Screen angle factor %f", s_screen_angle, s_screen_angle_factor);
+
+	// Update cameras
+	// Garrett: This isn't the right way to do it since it won't touch all the possible cameras.
+	// It would be better to go through a list of all the cameras.
+	for (int view_idx = 0; view_idx < sGetNumActiveViewports(); view_idx++)
+	{
+		CViewport *p_cur_viewport = CViewportManager::sGetActiveViewport(view_idx);
+		if (p_cur_viewport)
+		{
+			Gfx::Camera *p_cur_camera = p_cur_viewport->GetCamera();
+
+			if (p_cur_camera)
+			{
+				p_cur_camera->UpdateAdjustedHFOV();
+			}
+		}
+	}
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float				CViewportManager::sGetScreenAngle()
+{
+	return s_screen_angle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float				CViewportManager::sGetScreenAngleFactor()
+{
+	return s_screen_angle_factor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CViewportManager::sSet2DIn3DSpaceNoscaleDistance(float distance)
+{
+	s_2D_in_3D_space_noscale_distance = distance;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CViewportManager::sSet2DIn3DSpaceMaxScale(float scale)
+{
+	s_2D_in_3D_space_max_scale = scale;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CViewportManager::sSet2DIn3DSpaceMinScale(float scale)
+{
+	s_2D_in_3D_space_min_scale = scale;
+}
+
+// @script | SetScreenMode | Sets the way the screen will be split up into separate
+// viewports in the game. If there is more than one viewport, and multiple skaters
+// are active, then each skater will get his own viewport
+// @uparm One_Camera | the mode to use - one of the following: 
+// One_Camera 
+// Split_Vertical 
+// Split_Horizontal 
+// Split_Quarters
+bool ScriptSetScreenMode(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+
+
+	uint32 mode;
+	pParams->GetChecksum(NONAME, &mode, true);
+
+	switch (mode)
+	{
+		case 0x6f70ea65: // "one_camera"
+			CViewportManager::sSetScreenMode(vONE_CAM);
+			break;
+		case 0x9b62e5a6: // "split_vertical"
+			CViewportManager::sSetScreenMode(vSPLIT_V);
+			break;
+		case 0xb07b580e: // "split_horizontal"
+			CViewportManager::sSetScreenMode(vSPLIT_H);
+			break;
+		case 0xb6624a73: // "split_quarters"
+			CViewportManager::sSetScreenMode(vSPLIT_QUARTERS);
+			break;
+		case 0x6f2d1231: // "horse1"
+			CViewportManager::sSetScreenMode(vHORSE1);
+			break;
+		case 0xf624438b: // "horse2"
+			CViewportManager::sSetScreenMode(vHORSE2);
+			break;
+		default:
+			Dbg_MsgAssert(0,( "invalid screen mode"));
+			break;
+	}
+	return true;
+}
+
+bool ScriptGetScreenMode(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+
+	Script::CStruct* p_return_params;
+
+	p_return_params = pScript->GetParams();
+	switch( CViewportManager::sGetScreenMode())
+	{
+		case vSPLIT_V:
+			p_return_params->AddChecksum( "screen_mode", Script::GenerateCRC( "split_vertical" ));
+			break;
+		case vSPLIT_H:
+			p_return_params->AddChecksum( "screen_mode", Script::GenerateCRC( "split_horizontal" ));
+			break;
+		case vSPLIT_QUARTERS:
+			p_return_params->AddChecksum( "screen_mode", Script::GenerateCRC( "split_quarters" ));
+			break;
+		case vONE_CAM:
+			p_return_params->AddChecksum( "screen_mode", Script::GenerateCRC( "one_camera" ));
+			break;
+		case vHORSE1:
+			p_return_params->AddChecksum( "screen_mode", Script::GenerateCRC( "horse1" ));
+			break;
+		case vHORSE2:
+			p_return_params->AddChecksum( "screen_mode", Script::GenerateCRC( "horse2" ));
+			break;
+			
+		default:
+			Dbg_MsgAssert(0,( "invalid screen mode"));
+			break;
+	}
+
+	return true;
+}
+
+// @script | Set2DIn3DSpaceParams | Sets the parameters for the 2D Screen Elements in 3D space
+// @parmopt float | noscale_distance | 100.0 | Distance at which the 3D scale is equal to 1.0 (in inches)
+// @parmopt float | max_scale | 3.0 | Maximum 3D scale
+// @parmopt float | min_scale | 0.0 | Minimum 3D scale
+bool ScriptSet2DIn3DSpaceParams(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	float distance, scale;
+
+	if (pParams->GetFloat(CRCD(0x33aa4915,"noscale_distance"), &distance))
+	{
+		CViewportManager::sSet2DIn3DSpaceNoscaleDistance(distance);
+	}
+
+	if (pParams->GetFloat(CRCD(0xa0a0db70,"max_scale"), &scale))
+	{
+		CViewportManager::sSet2DIn3DSpaceMaxScale(scale);
+	}
+
+	if (pParams->GetFloat(CRCD(0x774b6931,"min_scale"), &scale))
+	{
+		CViewportManager::sSet2DIn3DSpaceMinScale(scale);
+	}
+
+	return true;
+}
+
+}
+
diff --git a/Code/Gfx/NxViewMan.h b/Code/Gfx/NxViewMan.h
new file mode 100644
index 0000000..80fb9f3
--- /dev/null
+++ b/Code/Gfx/NxViewMan.h
@@ -0,0 +1,174 @@
+#ifndef __GFX_NX_VIEWMAN_H__
+#define __GFX_NX_VIEWMAN_H__
+
+#include 
+#include 
+
+namespace Script
+{
+	class CScriptStructure;
+	class CScript;
+}
+
+namespace Nx
+{
+
+extern const float vBASE_ASPECT_RATIO;
+
+enum ScreenMode
+{
+	vONE_CAM = 0,
+	vSPLIT_V,
+	vSPLIT_H,
+	vSPLIT_QUARTERS,
+	vHORSE1,
+	vHORSE2,
+	vNUM_SCREEN_MODES
+};
+
+enum ViewportType
+{
+	vMAIN = 0,
+	vSECONDPLAYER,		// 2nd player fullscreen, for horse
+	vSPLIT_V_LEFT,
+	vSPLIT_V_RIGHT,
+	vSPLIT_H_TOP,
+	vSPLIT_H_BOTTOM,
+	vSPLIT_QUARTER_UL,
+	vSPLIT_QUARTER_UR,
+	vSPLIT_QUARTER_LL,
+	vSPLIT_QUARTER_LR,
+	vNUM_VIEWPORTS
+};
+	
+class CViewportManager
+{
+public:
+	enum
+	{
+		vMAX_NUM_ACTIVE_VIEWPORTS	= 4,
+	};
+
+	static void					sInit(void);
+    static void                 sCleanup(void);
+
+	static CViewport*			sGetActiveViewport(int view = 0);
+	static int					sGetActiveViewportNumber(CViewport *p_viewport);
+	static int					sGetNumActiveViewports();
+
+	static Gfx::Camera*			sGetCamera(int index = 0);
+	static void					sSetCamera(int index, Gfx::Camera* pCamera);
+	static void					sSetCameraAllScreenModes(int index, Gfx::Camera* pCamera);
+
+	static Gfx::Camera*			sGetActiveCamera(int cam = 0);
+	static Gfx::Camera*			sGetClosestCamera(const Mth::Vector &pos, float *distParam = NULL);
+	static int					sGetNumActiveCameras();
+
+	static bool					sMarkCameraForDeletion(Gfx::Camera *pCamera);
+	static void					sDeleteMarkedCameras();
+
+	// it would be nice to have descriptions of what these 'modes' are...
+	// for now, i'm assuming screen mode 0 is where the camera is free from the skater.
+	static ScreenMode			sGetScreenMode();
+	static void					sSetScreenMode(ScreenMode mode);
+
+	// some fundamental things about the TV
+	static	void				sSetScreenAspect(float aspect);
+	static	float				sGetScreenAspect();
+	static	void				sSetDefaultAngle(float angle);
+	static	float				sGetDefaultAngle();
+	static	void				sSetScreenAngle(float angle);
+	static	float				sGetScreenAngle();
+	static	float				sGetScreenAngleFactor();
+
+	// some parameters for 2D objects in 3D space
+	static	void				sSet2DIn3DSpaceNoscaleDistance(float distance);
+	static	float				sGet2DIn3DSpaceNoscaleDistance();
+	static	void				sSet2DIn3DSpaceMaxScale(float scale);
+	static	float				sGet2DIn3DSpaceMaxScale();
+	static	void				sSet2DIn3DSpaceMinScale(float scale);
+	static	float				sGet2DIn3DSpaceMinScale();
+
+protected:
+	static CViewport *			s_create_viewport(const Mth::Rect* rect, Gfx::Camera* cam = NULL);
+
+private:
+	// The platform dependent calls
+	static CViewport *			s_plat_create_viewport(const Mth::Rect* rect, Gfx::Camera* cam);
+
+	static CViewport*			sp_viewports[vNUM_VIEWPORTS];
+	static const int*			sp_active_viewport_indexes;
+	static int					s_num_active_viewports_cached;
+
+	static const float			s_viewport_rects[vNUM_VIEWPORTS][4];
+	static const int 			s_num_active_viewports[vNUM_SCREEN_MODES];
+	static const int			s_screen_mode_viewport_indexes[vNUM_SCREEN_MODES][vMAX_NUM_ACTIVE_VIEWPORTS];
+
+	static ScreenMode			s_screen_mode;
+
+	static	float				s_screen_aspect;				 // aspect ratio, usually 4/3 (1.3333333) or 16/9 (1.7777777)
+	static	float				s_screen_angle;					 // angle subtended by the width of the screen usually 72 degrees
+	static	float				s_default_angle;				 // the default value for s_screen_angle
+	static	float				s_screen_angle_factor;			 // factor to multiply all the tan(cam_angle/2) camera values by
+
+	static	float				s_2D_in_3D_space_noscale_distance;	// z distance at which 3D scale would be 1.0
+	static	float				s_2D_in_3D_space_max_scale;			// maximum 3D scale
+	static	float				s_2D_in_3D_space_min_scale;			// minimum 3D scale
+};
+
+
+
+bool ScriptSetScreenMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetScreenMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSet2DIn3DSpaceParams(Script::CStruct *pParams, Script::CScript *pScript);
+
+///////////
+// Inlines
+///////////
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float		CViewportManager::sGet2DIn3DSpaceNoscaleDistance()
+{
+	return s_2D_in_3D_space_noscale_distance;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float		CViewportManager::sGet2DIn3DSpaceMaxScale()
+{
+	return s_2D_in_3D_space_max_scale;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float		CViewportManager::sGet2DIn3DSpaceMinScale()
+{
+	return s_2D_in_3D_space_min_scale;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int			CViewportManager::sGetNumActiveViewports()
+{
+//	return s_num_active_viewports[s_screen_mode];
+	return s_num_active_viewports_cached;
+}
+
+
+}
+
+#endif // VIEWMAN
diff --git a/Code/Gfx/NxViewport.cpp b/Code/Gfx/NxViewport.cpp
new file mode 100644
index 0000000..b562404
--- /dev/null
+++ b/Code/Gfx/NxViewport.cpp
@@ -0,0 +1,190 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		viewport.cpp											**
+**																			**
+**	Created:		01/29/00	-	mjb										**
+**																			**
+**	Description:	Graphics viewports										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewport::CViewport( ) 
+: m_Rect(0,0,1,1), m_flags(0), mp_camera(NULL)
+{
+
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+CViewport::CViewport(const Mth::Rect* rect, Gfx::Camera* cam)
+: m_Rect(0,0,1,1), m_flags(0)
+{
+	
+
+	if (rect)
+	{	
+		m_Rect.SetOriginX(rect->GetOriginX());
+		m_Rect.SetOriginY(rect->GetOriginY());
+		m_Rect.SetWidth(rect->GetWidth());
+		m_Rect.SetHeight(rect->GetHeight());
+	}
+	else
+	{
+		m_Rect.SetOriginX(0);
+		m_Rect.SetOriginY(0);
+		m_Rect.SetWidth(1);
+		m_Rect.SetHeight(1);
+	}
+
+	//m_pixel_ratio = parent->GetPixelRatio();
+
+	mp_camera = cam;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewport::~CViewport ( void )
+{	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const float		CViewport::GetAspectRatio() const
+{
+	return CViewportManager::sGetScreenAspect() * m_Rect.GetWidth() / m_Rect.GetHeight();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CViewport::MarkCameraForDeletion(Gfx::Camera *pCamera)
+{
+	if (mp_camera == pCamera)
+	{
+		m_flags |= mCAMERA_MARKED_FOR_DELETION;
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CViewport::DeleteMarkedCamera()
+{
+	if (m_flags & mCAMERA_MARKED_FOR_DELETION)
+	{
+		Dbg_Assert(mp_camera);
+		delete mp_camera;
+		mp_camera = NULL;
+
+		m_flags &= ~mCAMERA_MARKED_FOR_DELETION;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float			CViewport::TransformToScreenCoord(const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z)
+{
+	return plat_transform_to_screen_coord(world_pos, screen_pos_x, screen_pos_y, screen_pos_z);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Stub versions of all platform specific functions:
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float			CViewport::plat_transform_to_screen_coord(const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z)
+{
+	printf ("STUB: PlatTransformToScreenCoord\n");
+	return -1.0f;
+}
+
+} // namespace Gfx
+
+
diff --git a/Code/Gfx/NxViewport.h b/Code/Gfx/NxViewport.h
new file mode 100644
index 0000000..7d510ae
--- /dev/null
+++ b/Code/Gfx/NxViewport.h
@@ -0,0 +1,217 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics  (GFX)											**
+**																			**
+**	File name:		gfx/viewport.h											**
+**																			**
+**	Created: 		01/29/00	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_VIEWPORT_H
+#define __GFX_VIEWPORT_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+#include 
+			  
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Nx
+{
+
+// Since a ZBuffer value is a platform-specific type, we need "void" type for it.
+// Since we do have to give it a size, the type has been assigned to a uint32.
+// It should not be treated as a uint32 above the p-line, though.
+typedef uint32 ZBufferValue;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  CViewport
+{
+	
+	public :
+						CViewport();
+						CViewport(const Mth::Rect* Rect, Gfx::Camera* cam = NULL);
+	virtual				~CViewport();
+
+	Gfx::Camera *		GetCamera();
+	void				SetCamera(Gfx::Camera* cam);
+
+	const Mth::Rect &	GetRect() const;
+
+	const		float	GetOriginX() const;
+	const		float	GetOriginY() const;
+	const		float	GetWidth() const;
+	const		float	GetHeight() const;
+
+	const		float	GetAspectRatio() const;
+
+	// These functions are used to prevent the camera from being deleted prematurely
+				bool	MarkCameraForDeletion(Gfx::Camera *pCamera);
+				void	DeleteMarkedCamera();
+
+				float	TransformToScreenCoord(const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z);
+
+protected:	
+	// Internal flags
+	enum
+	{
+		mCAMERA_MARKED_FOR_DELETION		= 0x00000001,
+	};
+
+	
+	Mth::Rect			m_Rect;
+	uint32				m_flags;
+
+	Obj::CSmtPtr mp_camera;			// Smart pointer to camera   
+//	Gfx::Camera *		mp_camera;
+
+private :	
+	virtual float		plat_transform_to_screen_coord(const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z);
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Mth::Rect & CViewport::GetRect() const
+{
+	return m_Rect;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const float	CViewport::GetOriginX ( void ) const
+{
+   	
+
+	return m_Rect.GetOriginX();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const float	CViewport::GetOriginY ( void ) const
+{
+   	
+
+	return m_Rect.GetOriginY();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const float	CViewport::GetWidth ( void ) const
+{
+   	
+
+	return m_Rect.GetWidth();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const float	CViewport::GetHeight ( void ) const
+{
+   	
+
+	return m_Rect.GetHeight();
+}
+	
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+			
+inline const float		CViewport::GetPixelRatio ( void ) const
+{
+   	
+
+	return 	m_pixel_ratio;
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Returns a pointer to the viewport's camera
+// since this uses a smart pointer, it will be NULL
+// if the camera has been deleted
+			
+inline Gfx::Camera *	CViewport::GetCamera ( void )
+{
+	return 	mp_camera.Convert();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+			
+inline void 			CViewport::SetCamera ( Gfx::Camera* cam )
+{
+	DeleteMarkedCamera();			// In case there is an old camera
+	mp_camera = cam;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+			
+} // namespace Nx
+
+#endif	// __GFX_VIEWPORT_H
diff --git a/Code/Gfx/NxWin2D.cpp b/Code/Gfx/NxWin2D.cpp
new file mode 100644
index 0000000..a912bc4
--- /dev/null
+++ b/Code/Gfx/NxWin2D.cpp
@@ -0,0 +1,126 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxWin2D.cpp
+
+#include "gfx/NxWin2D.h"
+
+namespace	Nx
+{
+// These functions are the platform independent part of the interface to 
+// the platform specific code
+// parameter checking can go here....
+// although we might just want to have these functions inline, or not have them at all?
+					 
+///////////////////////////////////////////////////////////////////////////////
+// CWindow2D
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWindow2D::CWindow2D(int x, int y, int width, int height) : 
+	m_xpos(x),
+	m_ypos(y),
+	m_width(width),
+	m_height(height),
+	mp_next(NULL)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWindow2D::~CWindow2D() 
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CWindow2D::SetPos(int x, int y)
+{
+	m_xpos = x;
+	m_ypos = y;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CWindow2D::SetSize(int width, int height)
+{
+	m_width = width;
+	m_height = height;
+
+	plat_update_engine();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CWindow2D::plat_update_engine()
+{
+	printf ("STUB: PlatUpdateEngine\n");
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// CWindow2DManager
+
+CWindow2D *			CWindow2DManager::sp_window_list = NULL;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CWindow2DManager::sAllocWindow2DPool()
+{
+	s_plat_alloc_window2d_pool();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWindow2D *		CWindow2DManager::sGetWindowInstance(int x, int y, int width, int height)
+{
+	CWindow2D *p_window = sp_window_list;
+
+	if (p_window)
+	{
+		sp_window_list = p_window->mp_next;
+	} else {
+		Dbg_MsgAssert(0, ("Out of CWindow2D Instances"));
+	}
+
+	// Initialize
+	p_window->SetPos(x, y);
+	p_window->SetSize(width, height);
+
+	return p_window;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CWindow2DManager::sFreeWindowInstance(CWindow2D *p_window)
+{
+	p_window->mp_next = sp_window_list;
+	sp_window_list = p_window;
+}
+
+}
+
+
diff --git a/Code/Gfx/NxWin2D.h b/Code/Gfx/NxWin2D.h
new file mode 100644
index 0000000..cc53c7e
--- /dev/null
+++ b/Code/Gfx/NxWin2D.h
@@ -0,0 +1,107 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxWin2D.h
+
+
+
+#ifndef	__GFX_NXWIN2D_H__
+#define	__GFX_NXWIN2D_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __CORE_MATH_RECT_H
+#include 
+#endif
+
+namespace Nx
+{
+
+// Forward declarations
+class CWindow2DManager;
+
+//////////////////////////////////////////////////////////////////////////////
+// Holds a string of text that needs to be displayed this frame
+class CWindow2D
+{
+public:
+							CWindow2D(int x = 0, int y = 0, int width = 640, int height = 448);
+							CWindow2D(const Mth::Rect & win_rect);
+	virtual					~CWindow2D();
+
+	// Position of screen.
+	int						GetXPos() const;
+	int						GetYPos() const;
+	void					SetPos(int x, int y);
+
+	// Size of screen.
+	int						GetWidth() const;
+	int						GetHeight() const;
+	void					SetSize(int width, int height);
+
+protected:
+	int						m_xpos;
+	int						m_ypos;
+	int						m_width;
+	int						m_height;
+
+	// For free list in CWindow2DManager
+	CWindow2D *				mp_next;
+
+private:
+	//
+	virtual void			plat_update_engine();
+
+	friend CWindow2DManager;
+
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Static class for memory management of CWindow2Ds
+class CWindow2DManager
+{
+public:
+	//
+	static void				sAllocWindow2DPool();
+	static CWindow2D *		sGetWindowInstance(int x = 0, int y = 0, int width = 640, int height = 448);
+	static void				sFreeWindowInstance(CWindow2D *p_window);
+
+private:
+	// Constants
+	enum {
+		vMAX_WINDOW_INSTANCES = 10
+	};
+
+	// Because it is static, it is declared here, but defined in p_NxWin2D.cpp
+	static void				s_plat_alloc_window2d_pool();
+
+	// Array of Text requests
+	static CWindow2D *	   	sp_window_list;
+};
+
+/////////////////////////////////////////////////////////
+// CText inline function
+inline int					CWindow2D::GetXPos() const
+{
+	return m_xpos;
+}
+
+inline int					CWindow2D::GetYPos() const
+{
+	return m_ypos;
+}
+
+inline int					CWindow2D::GetWidth() const
+{
+	return m_width;
+}
+
+inline int					CWindow2D::GetHeight() const
+{
+	return m_height;
+}
+
+}
+
+#endif // 
+
diff --git a/Code/Gfx/Pose.h b/Code/Gfx/Pose.h
new file mode 100644
index 0000000..ebf93a3
--- /dev/null
+++ b/Code/Gfx/Pose.h
@@ -0,0 +1,71 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       Pose.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/13/2002
+//****************************************************************************
+
+#ifndef __GFX_POSE_H
+#define __GFX_POSE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+    
+	const int vMAX_BONES = 64;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class CPose
+{
+public:
+	Mth::Quat		m_rotations[vMAX_BONES];
+	Mth::Vector		m_translations[vMAX_BONES];
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Gfx
+
+#endif	// __GFX_POSE_H
+
diff --git a/Code/Gfx/Skeleton.cpp b/Code/Gfx/Skeleton.cpp
new file mode 100644
index 0000000..ff38363
--- /dev/null
+++ b/Code/Gfx/Skeleton.cpp
@@ -0,0 +1,1781 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       skeleton.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/15/2001
+//****************************************************************************
+
+// TODO:
+// Assert that the anim sequence contains the same number of bones as the skeleton?
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#ifdef __PLAT_NGC__
+#include "dolphin.h"
+extern bool g_in_cutscene;
+#endif
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+#define nxBONEFLAGS_ROTATE			(1<<31)
+#define nxBONEFLAGS_NOANIM			(1<<30)
+#define nxBONEFLAGS_SCALELOCAL		(1<<29)
+#define nxBONEFLAGS_SCALENONLOCAL	(1<<28)
+
+#define MAX_LOD_DISTANCE			3.4e+38f;
+
+class CBone
+{
+public:
+	CBone();
+
+public:
+	uint32					m_name;
+	Mth::Matrix*			mp_parentMatrix;
+	Mth::Matrix*			mp_flippedMatrix;
+
+// GJ:  The neutral pose has been moved to the skeleton data,
+// since it can be shared by all the instances of this skeleton
+//	Mth::Matrix				m_invertedNeutralMatrix;
+//	Mth::Matrix*			mp_invertedNeutralParentMatrix;
+	
+	uint32					m_flags;
+// GJ:  The parent name doesn't seem to be needed, and
+// can be accessed from the skeleton data if necessary...
+//	uint32					m_parentName;
+	int						m_flipIndex;
+	Mth::Vector				m_scale;
+};
+
+// GJ:  Removed reference to skateboard, because
+// there shouldn't be anything skate-specific in here!
+//#define nxSKELETONFLAGS_ROTATESKATEBOARD	(1<<30)
+
+// This flag has been moved to CAnimationComponent,
+// and will eventually be removed...
+#define nxSKELETONFLAGS_FLIPPED				(1<<29)
+
+// This flag was used as a kludge on THPS4 to get
+// female models to scale properly (because supposedly-
+// equivalent vertices in the female model were weighted
+// to different bones in the male model)
+#define nxSKELETONFLAGS_FEMALESKELETON		(1<<28)
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBone* CSkeleton::get_bone_by_id( uint32 boneId )
+{
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		if ( boneId == mp_bones[i].m_name )
+			return &mp_bones[i];
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBone::CBone()
+{
+	m_name = 0;
+	mp_parentMatrix = NULL;
+// GJ:  Parent name has been moved to skeleton data
+//	m_parentName = 0;
+	mp_flippedMatrix = NULL;
+// GJ:  Neutral pose has been moved to skeleton data
+//	m_invertedNeutralMatrix.Ident();
+//	mp_invertedNeutralParentMatrix = NULL;
+	m_flipIndex = -1;
+	m_flags = 0;
+	m_scale = Mth::Vector( 1.0f, 1.0f, 1.0f );
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkeleton::CSkeleton( CSkeletonData* pSkeletonData )
+{
+    Dbg_Assert( pSkeletonData );
+
+	// clear out flags completely
+	m_flags = 0;
+	
+	if ( pSkeletonData->m_flags & 0x1 )
+	{
+		m_flags |= nxSKELETONFLAGS_FEMALESKELETON;
+	}
+
+#ifdef __PLAT_NGC__
+	int size = sizeof( CBone ) * pSkeletonData->GetNumBones();
+	int mem_available;
+	bool need_to_pop = false;
+	if ( g_in_cutscene )
+	{
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+		mem_available = Mem::Manager::sHandle().Available();
+		if ( size < ( mem_available - ( 40 * 1024 ) ) )
+		{
+			need_to_pop = true;
+		}
+		else
+		{
+			Mem::Manager::sHandle().PopContext();
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( size < ( mem_available - ( 5 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( size < ( mem_available - ( 40 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+				}
+			}
+		}
+	}
+#endif	// __PLAT_NGC__
+
+	mp_bones = new CBone[pSkeletonData->GetNumBones()];
+
+#ifdef __PLAT_NGC__
+	if ( need_to_pop )
+	{
+		Mem::Manager::sHandle().PopContext();
+	}
+#endif	// __PLAT_NGC__
+
+#ifdef __PLAT_NGC__
+	size = sizeof( Mth::Matrix ) * pSkeletonData->GetNumBones();
+	need_to_pop = false;
+	if ( g_in_cutscene )
+	{
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+		mem_available = Mem::Manager::sHandle().Available();
+		if ( size < ( mem_available - ( 40 * 1024 ) ) )
+		{
+			need_to_pop = true;
+		}
+		else
+		{
+			Mem::Manager::sHandle().PopContext();
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( size < ( mem_available - ( 5 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( size < ( mem_available - ( 40 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+				}
+			}
+		}
+	}
+#endif	// __PLAT_NGC__
+
+	mp_matrices = new Mth::Matrix[pSkeletonData->GetNumBones()];
+	
+#ifdef __PLAT_NGC__
+	if ( need_to_pop )
+	{
+		Mem::Manager::sHandle().PopContext();
+	}
+#endif	// __PLAT_NGC__
+
+	// clear out all the matrices to the identity
+	Mth::Matrix* p_currentMatrix = mp_matrices;
+	for ( int i = 0; i < pSkeletonData->GetNumBones(); i++ )
+	{
+		p_currentMatrix->Ident();
+		p_currentMatrix++;
+	}
+
+	m_numBones = pSkeletonData->GetNumBones();
+	
+    initialize_bone_names( pSkeletonData );
+    initialize_hierarchy( pSkeletonData );
+    initialize_flip_matrices( pSkeletonData );
+
+	mp_skeletonData		= pSkeletonData;
+
+	// Set the maxmimum possible bone skip LOD level.
+	SetMaxBoneSkipLOD( CSkeletonData::BONE_SKIP_LOD_BITS - 1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkeleton::~CSkeleton()
+{
+	delete[] mp_bones;
+	delete[] mp_matrices;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CSkeleton::GetBoneIndexById( uint32 boneId )
+{
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		if ( boneId == mp_bones[i].m_name )
+			return i;
+	}
+
+	return -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CSkeleton::GetFlipIndex( int i )
+{
+	CBone* pBone = &mp_bones[i];
+	return pBone->m_flipIndex;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkeleton::initialize_flip_matrices( CSkeletonData* pSkeletonData )
+{
+    Dbg_Assert( pSkeletonData );
+    Dbg_Assert( pSkeletonData->GetNumBones() == m_numBones );
+	
+	for ( int i = 0; i < pSkeletonData->GetNumBones(); i++ )
+	{
+        // if it's got a flip
+        if ( pSkeletonData->GetFlipName(i) )
+        {
+            int index = pSkeletonData->GetIndex( pSkeletonData->GetFlipName(i) );
+            mp_bones[i].mp_flippedMatrix = mp_matrices + index;
+			mp_bones[i].m_flipIndex = index;
+
+			// and vice versa...
+			mp_bones[index].m_flipIndex = i;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkeleton::initialize_hierarchy( CSkeletonData* pSkeletonData )
+{
+	// the parent of the root is itself.
+    
+    Dbg_Assert( pSkeletonData );
+    Dbg_Assert( pSkeletonData->GetNumBones() == m_numBones );
+
+    for ( int i = 0; i < pSkeletonData->GetNumBones(); i++ )
+    {
+        int parentIndex;
+         
+        if ( pSkeletonData->GetParentName(i) == 0 )
+        {
+            parentIndex = 0;
+        }
+        else
+        {
+            parentIndex = pSkeletonData->GetParentIndex( i );
+        }
+
+		CBone* p_currentBone = &mp_bones[i];
+
+//		Dbg_Message( "Parent of %d is %d\n", i, parentIndex );
+
+// GJ:  Parent name has been moved to skeleton data
+//		p_currentBone->m_parentName = pSkeletonData->GetParentName(i);
+
+		// Assign parentage here...
+		p_currentBone->mp_parentMatrix = mp_matrices + parentIndex;
+
+// GJ:  Neutral pose has been moved to skeleton data
+//		CBone* p_parentBone = &mp_bones[parentIndex];
+//		p_currentBone->mp_invertedNeutralParentMatrix = &p_parentBone->m_invertedNeutralMatrix;
+
+		p_currentBone->m_flags = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkeleton::initialize_bone_names( CSkeletonData* pSkeletonData )
+{
+    Dbg_Assert( pSkeletonData );
+    Dbg_Assert( pSkeletonData->GetNumBones() == m_numBones );
+
+    for ( int i = 0; i < pSkeletonData->GetNumBones(); i++ )
+    {
+		mp_bones[i].m_name = pSkeletonData->GetBoneName( i );
+   	}
+}
+
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Mth::Matrix CSkeleton::GetNeutralMatrix( int boneIndex )
+{
+	Dbg_MsgAssert( 0, ( "This function has been deprecated." ) );
+
+	Dbg_Assert( boneIndex >= 0 && boneIndex < m_numBones );
+
+	Mth::Matrix theReturnMatrix;
+
+	theReturnMatrix = mp_bones[boneIndex].m_invertedNeutralMatrix;
+	theReturnMatrix.InvertUniform();
+
+	return theReturnMatrix;
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Mth::Matrix* CSkeleton::GetMatrices()
+{
+	return mp_matrices;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CSkeleton::GetNumBones() const
+{
+	return m_numBones;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeleton::Display( Mth::Matrix* pRootMatrix, float r, float g, float b )
+{
+	Mth::Matrix* p_inverseNeutralPoseMatrices = mp_skeletonData->GetInverseNeutralPoseMatrices();
+
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		CBone* p_currentBone = &mp_bones[i];
+    
+		Mth::Matrix tempMatrix0, tempMatrix1;
+					
+		Mth::Matrix boneMatrix;
+		boneMatrix = *( mp_matrices + i );
+		tempMatrix0 = *( p_inverseNeutralPoseMatrices + i );
+		tempMatrix0.InvertUniform();
+		tempMatrix0 = tempMatrix0 * boneMatrix;
+		tempMatrix0 *= (*pRootMatrix);
+		
+		boneMatrix = *p_currentBone->mp_parentMatrix;
+		if ( i == 0 )
+		{
+			// the root has no parent
+			tempMatrix1.Ident();
+		}
+		else
+		{
+			tempMatrix1 = *( p_inverseNeutralPoseMatrices + mp_skeletonData->GetParentIndex(i) );
+		}
+		tempMatrix1.InvertUniform();
+		tempMatrix1 = tempMatrix1 * boneMatrix; 
+		tempMatrix1 *= (*pRootMatrix);
+		
+		AddDebugBone( tempMatrix0.GetPos(), tempMatrix1.GetPos(), r, g, b );
+   }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeleton::SetBoneActive( uint32 boneId, bool active )
+{
+	CBone* pBone = get_bone_by_id( boneId );
+
+	if ( pBone )
+	{
+		pBone->m_flags &= ~nxBONEFLAGS_NOANIM;
+		pBone->m_flags |= ( active ? 0 : nxBONEFLAGS_NOANIM );
+	}
+
+	return pBone;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeleton::ApplyBoneScale( Script::CStruct* pBodyShapeStructure )
+{
+	// TODO:  CSkeleton shouldn't know anything about
+	// Script::CStruct*...  this should be moved
+	// out to a higher level...
+
+	if ( !pBodyShapeStructure ) 
+	{
+		return false;
+	}
+
+	int scaleFromOrigin = 0;
+	pBodyShapeStructure->GetInteger( "scaleFromOrigin", &scaleFromOrigin, Script::NO_ASSERT );
+	bool localScale = !scaleFromOrigin;
+
+	Mth::Vector theBoneScaleVector;
+	
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		if ( pBodyShapeStructure->GetVector(mp_bones[i].m_name,&theBoneScaleVector,Script::NO_ASSERT) )
+		{
+			uint32 name = mp_bones[i].m_name;
+
+			if ( m_flags & nxSKELETONFLAGS_FEMALESKELETON )
+			{
+				if ( name == CRCD(0x580c0963, "head") )
+				{
+					name = CRCD(0xdfee29ac,"neck");
+				}		   
+			}
+
+			// GJ PATCH:  test for Nolan
+			Script::CArray* pNolanScalingTest = Script::GetArray( "nonlocal_bones", Script::NO_ASSERT );
+			if ( pNolanScalingTest )
+			{
+				for ( uint32 i = 0; i < pNolanScalingTest->GetSize(); i++ )
+				{
+					if ( pNolanScalingTest->GetChecksum(i) == name )
+					{
+						localScale = false;
+					}
+				}
+			}
+
+			Mth::Vector vec = Mth::Vector( 1.0f, 1.0f, 1.0f, 1.0f );
+			if ( GetBoneScale( name, &vec ) )
+			{
+				vec[X] *= theBoneScaleVector[X];
+				vec[Y] *= theBoneScaleVector[Y];
+				vec[Z] *= theBoneScaleVector[Z];
+				vec[W] = 1.0f;
+
+				bool success = SetBoneScale(name, vec, localScale);
+				if ( !success )
+				{
+					Dbg_MsgAssert( 0, ( "Couldn't apply bone scale %s", Script::FindChecksumName(mp_bones[i].m_name) ) );
+				}
+			}
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeleton::ResetScale( void )
+{
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		CBone* pBone = &mp_bones[i];
+		pBone->m_flags &= ~(nxBONEFLAGS_SCALELOCAL | nxBONEFLAGS_SCALENONLOCAL);
+		pBone->m_scale = Mth::Vector( 1.0f, 1.0f, 1.0f, 1.0f );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeleton::GetBoneScale( uint32 boneId, Mth::Vector* pBoneScaleVector )
+{
+	CBone* pBone = get_bone_by_id( boneId );
+
+	if ( pBone )
+	{
+		Dbg_Assert( pBoneScaleVector );
+		*pBoneScaleVector = pBone->m_scale;
+	}
+	else
+	{
+		Dbg_Message( "Couldn't find bone to scale %s", Script::FindChecksumName(boneId) );
+	}
+
+	return pBone;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeleton::SetBoneScale( uint32 boneId, const Mth::Vector& theBoneScale, bool isLocalScale )
+{
+	CBone* pBone = get_bone_by_id( boneId );
+
+	if ( pBone )
+	{
+		pBone->m_scale = theBoneScale;
+
+		bool isIdentityScale = ( theBoneScale[X] == 1.0f && theBoneScale[Y] == 1.0f && theBoneScale[Z] == 1.0f );
+		pBone->m_flags &= ~(nxBONEFLAGS_SCALELOCAL | nxBONEFLAGS_SCALENONLOCAL);
+		if ( !isIdentityScale )
+		{
+			pBone->m_flags |= ( isLocalScale ? nxBONEFLAGS_SCALELOCAL : nxBONEFLAGS_SCALENONLOCAL );
+		}
+	}
+	else
+	{
+		Dbg_Message( "Couldn't find bone to scale %s", Script::FindChecksumName(boneId) );
+	}
+
+	return pBone;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeleton::CopyBoneScale( Gfx::CSkeleton* pSourceSkeleton )
+{
+	CSkeletonData* pSkeletonData = pSourceSkeleton->GetSkeletonData();
+	for ( int i = 0; i < pSkeletonData->GetNumBones(); i++ )
+	{
+		uint32 boneName = pSkeletonData->GetBoneName( i );
+
+		Mth::Vector sourceScale;
+
+		// if the bone exists in the destination skeleton,
+		// and the source skeleton, then copy the scale
+		if ( get_bone_by_id( boneName )
+			 && pSourceSkeleton->GetBoneScale( boneName, &sourceScale ) )
+		{
+			bool isLocalScale = true;
+
+			#if 0
+			// GJ:  This code handles the shoulder bones differently
+			// so that the female skaters aren't so broad-shouldered.
+			// However, it ends up screwing stuff up during cutscenes,
+			// because the female's hands will not be aligned to the
+			// items that she might be holding.  The only way I can
+			// think of around this (right now) is to create female-
+			// specific versions of the cutscenes, but it's too late 
+			// for that now.
+			Script::CArray* pShoulderScalingArray = Script::GetArray( CRCD(0x20d9ac2f,"nonlocal_bones"), Script::NO_ASSERT );
+			if ( pShoulderScalingArray )
+			{
+				for ( uint32 i = 0; i < pShoulderScalingArray->GetSize(); i++ )
+				{
+					if ( pShoulderScalingArray->GetChecksum(i) == boneName )
+					{
+						isLocalScale = false;
+					}
+				}
+			}
+			#endif
+
+			SetBoneScale( boneName, sourceScale, isLocalScale );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void sQuatVecToMatrix( Mth::Quat* pQ, Mth::Vector* pT, Mth::Matrix* pMatrix, bool rotate, bool flip )
+{
+	Dbg_Assert( pQ );
+   	Dbg_Assert( pT );
+	Dbg_Assert( pMatrix );
+	
+#ifdef __PLAT_NGPS__
+	Dbg_MsgAssert( ( (uint32)&(pQ->GetVector()) - (uint32)pQ )==0, ("Quat::quat is no longer offset 0 bytes from the start of the Quat, need to modify the VU code in sQuatVecToMatrix"));
+	
+	asm __volatile__("
+	
+	lw	$8,(%3)	# Put 'rotate' flag in t0
+	lw	$9,(%4)	# Put 'flip' flag in t1
+	
+	lqc2    vf4,0x00(%0)			# Load pQ into vf4
+	lqc2    vf7,0x00(%1)			# Load pT into vf7
+	
+	#	square[X] = (*pQ)[X] * (*pQ)[X];
+	#	square[Y] = (*pQ)[Y] * (*pQ)[Y];
+	#	square[Z] = (*pQ)[Z] * (*pQ)[Z];
+	vmul.xyz vf5,vf4,vf4			# vf5=square
+									
+	#	cross[X] = (*pQ)[Y] * (*pQ)[Z];
+	#	cross[Y] = (*pQ)[Z] * (*pQ)[X];
+	#	cross[Z] = (*pQ)[X] * (*pQ)[Y];
+	vopmula.xyz     ACC,vf4,vf4		# ACC=cross
+
+	#	wimag[X] = -(*pQ)[W] * (*pQ)[X];
+	#	wimag[Y] = -(*pQ)[W] * (*pQ)[Y];
+	#	wimag[Z] = -(*pQ)[W] * (*pQ)[Z];
+	vmsubw.xyzw vf8,vf4,vf4w	# vf8=cross+wimag
+	vmaddw.xyzw vf9,vf4,vf4w	# vf9=cross-wimag
+
+	
+	vmulax.w	ACC,vf0,vf5x	# ACCw=pQx squared
+	vmadday.w	ACC,vf0,vf5y	# ACCw=pQx squared + pQy squared
+	vmaddz.w	vf1,vf0,vf5z	# vf1w=pQx squared + pQy squared + pQz squared
+	
+	# 3 vnops here ... use 2 of them with the following.
+	vadd.xyz	vf8,vf8,vf8		# vf8=2*(cross+wimag)
+	vadd.xyz	vf9,vf9,vf9		# vf9=2*(cross-wimag)
+	
+	vsubw.xyz	vf1,vf5,vf1w	# vf1=(-y2-z2,-x2-z2,-x2-y2)
+	# 3 vnops here that could be used for something
+	vaddaw.xyz	ACC,vf1,vf0w	# ACC=(1-y2-z2,1-x2-z2,1-x2-y2)
+	vmaddw.xyz	vf1,vf1,vf0w	# vf1=(1-2(y2+z2),1-2(x2+z2),1-2(x2+y2))
+
+
+	beqz	$8,norotate		# if (rotate)
+	nop
+
+	beqz	$9,noflip		# if (flip)
+	nop
+
+	# (*pMatrix)[Mth::RIGHT][X] = -(1 - 2 * (square[Y] + square[Z]));
+	# (*pMatrix)[Mth::RIGHT][Y] = 2 * (cross[Z] + wimag[Z]);
+	# (*pMatrix)[Mth::RIGHT][Z] = 2 * (cross[Y] - wimag[Y]);
+	# (*pMatrix)[Mth::RIGHT][W] = 0.0f;
+	vsubax.x	ACC,vf0,vf1x
+	vaddaz.y	ACC,vf0,vf8z
+	vadday.z 	ACC,vf0,vf9y
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::RIGHT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x00(%2)
+
+	# (*pMatrix)[Mth::UP][X] = 2 * (cross[Z] - wimag[Z]);
+	# (*pMatrix)[Mth::UP][Y] = -(1 - 2 * (square[X] + square[Z]));
+	# (*pMatrix)[Mth::UP][Z] = -(2 * (cross[X] + wimag[X]));
+	# (*pMatrix)[Mth::UP][W] = 0.0f;
+	vaddaz.x	ACC,vf0,vf9z
+	vsubay.y	ACC,vf0,vf1y
+	vsubax.z	ACC,vf0,vf8x
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::UP]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x10(%2)
+
+	# (*pMatrix)[Mth::AT][X] = -(2 * (cross[Y] + wimag[Y]));
+	# (*pMatrix)[Mth::AT][Y] = 2 * (cross[X] - wimag[X]);
+	# (*pMatrix)[Mth::AT][Z] = 1 - 2 * (square[X] + square[Y]);
+	# (*pMatrix)[Mth::AT][W] = 0.0f;
+	vsubay.x	ACC,vf0,vf8y
+	vaddax.y	ACC,vf0,vf9x
+	vaddaz.z	ACC,vf0,vf1z
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::AT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x20(%2)
+
+	# (*pMatrix)[Mth::POS][X] = -pT->GetX();
+	# (*pMatrix)[Mth::POS][Y] = pT->GetY();
+	# (*pMatrix)[Mth::POS][Z] = pT->GetZ();
+	# (*pMatrix)[Mth::POS][W] = 1.0f;
+	vsubax.x	ACC,vf0,vf7x
+	vadday.y	ACC,vf0,vf7y
+	vaddaz.z	ACC,vf0,vf7z
+	vaddax.w	ACC,vf0,vf0x
+	# Save out (*pMatrix)[Mth::POS]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x30(%2)
+	
+	j	overnorotate		
+	nop
+noflip:							# else  (if (flip))
+
+	# (*pMatrix)[Mth::RIGHT][X] = -(1 - 2 * (square[Y] + square[Z]));
+	# (*pMatrix)[Mth::RIGHT][Y] = -(2 * (cross[Z] + wimag[Z]));
+	# (*pMatrix)[Mth::RIGHT][Z] = -(2 * (cross[Y] - wimag[Y]));
+	# (*pMatrix)[Mth::RIGHT][W] = 0.0f;
+	vsubax.x	ACC,vf0,vf1x
+	vsubaz.y	ACC,vf0,vf8z
+	vsubay.z 	ACC,vf0,vf9y
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::RIGHT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x00(%2)
+
+	# (*pMatrix)[Mth::UP][X] = -(2 * (cross[Z] - wimag[Z]));
+	# (*pMatrix)[Mth::UP][Y] = -(1 - 2 * (square[X] + square[Z]));
+	# (*pMatrix)[Mth::UP][Z] = -(2 * (cross[X] + wimag[X]));
+	# (*pMatrix)[Mth::UP][W] = 0.0f;
+	vsubaz.x	ACC,vf0,vf9z
+	vsubay.y	ACC,vf0,vf1y
+	vsubax.z	ACC,vf0,vf8x
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::UP]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x10(%2)
+
+	# (*pMatrix)[Mth::AT][X] = 2 * (cross[Y] + wimag[Y]);
+	# (*pMatrix)[Mth::AT][Y] = 2 * (cross[X] - wimag[X]);
+	# (*pMatrix)[Mth::AT][Z] = 1 - 2 * (square[X] + square[Y]);
+	# (*pMatrix)[Mth::AT][W] = 0.0f;
+	vadday.x	ACC,vf0,vf8y
+	vaddax.y	ACC,vf0,vf9x
+	vaddaz.z	ACC,vf0,vf1z
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::AT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x20(%2)
+
+	# (*pMatrix)[Mth::POS][X] = pT->GetX();
+	# (*pMatrix)[Mth::POS][Y] = pT->GetY();
+	# (*pMatrix)[Mth::POS][Z] = pT->GetZ();
+	# (*pMatrix)[Mth::POS][W] = 1.0f;
+	vaddax.x	ACC,vf0,vf7x
+	vadday.y	ACC,vf0,vf7y
+	vaddaz.z	ACC,vf0,vf7z
+	vaddax.w	ACC,vf0,vf0x
+	# Save out (*pMatrix)[Mth::POS]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x30(%2)
+
+	j	overnorotate		
+	nop						# else (if (rotate))
+norotate:
+
+	beqz	$9,noflip2		# if (flip)
+	nop
+
+	# (*pMatrix)[Mth::RIGHT][X] = 1 - 2 * (square[Y] + square[Z]);
+	# (*pMatrix)[Mth::RIGHT][Y] = -(2 * (cross[Z] + wimag[Z]));
+	# (*pMatrix)[Mth::RIGHT][Z] = -(2 * (cross[Y] - wimag[Y]));
+	# (*pMatrix)[Mth::RIGHT][W] = 0.0f;
+	vaddax.x	ACC,vf0,vf1x
+	vsubaz.y	ACC,vf0,vf8z
+	vsubay.z 	ACC,vf0,vf9y
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::RIGHT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x00(%2)
+
+	# (*pMatrix)[Mth::UP][X] = -(2 * (cross[Z] - wimag[Z]));
+	# (*pMatrix)[Mth::UP][Y] = 1 - 2 * (square[X] + square[Z]);
+	# (*pMatrix)[Mth::UP][Z] = 2 * (cross[X] + wimag[X]);
+	# (*pMatrix)[Mth::UP][W] = 0.0f;
+	vsubaz.x	ACC,vf0,vf9z
+	vadday.y	ACC,vf0,vf1y
+	vaddax.z	ACC,vf0,vf8x
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::UP]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x10(%2)
+
+	# (*pMatrix)[Mth::AT][X] = -(2 * (cross[Y] + wimag[Y]));
+	# (*pMatrix)[Mth::AT][Y] = 2 * (cross[X] - wimag[X]);
+	# (*pMatrix)[Mth::AT][Z] = 1 - 2 * (square[X] + square[Y]);
+	# (*pMatrix)[Mth::AT][W] = 0.0f;
+	vsubay.x	ACC,vf0,vf8y
+	vaddax.y	ACC,vf0,vf9x
+	vaddaz.z	ACC,vf0,vf1z
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::AT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x20(%2)
+
+	# (*pMatrix)[Mth::POS][X] = -pT->GetX();
+	# (*pMatrix)[Mth::POS][Y] = pT->GetY();
+	# (*pMatrix)[Mth::POS][Z] = pT->GetZ();
+	# (*pMatrix)[Mth::POS][W] = 1.0f;
+	vsubax.x	ACC,vf0,vf7x
+	vadday.y	ACC,vf0,vf7y
+	vaddaz.z	ACC,vf0,vf7z
+	vaddax.w	ACC,vf0,vf0x
+	# Save out (*pMatrix)[Mth::POS]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x30(%2)
+
+	j	overnoflip2			# else  (if (flip))
+	nop
+noflip2:
+
+	# (*pMatrix)[Mth::RIGHT][X] = 1 - 2 * (square[Y] + square[Z]);
+	# (*pMatrix)[Mth::RIGHT][Y] = 2 * (cross[Z] + wimag[Z]);
+	# (*pMatrix)[Mth::RIGHT][Z] = 2 * (cross[Y] - wimag[Y]);
+	# (*pMatrix)[Mth::RIGHT][W] = 0.0f;
+	vaddax.x	ACC,vf0,vf1x
+	vaddaz.y	ACC,vf0,vf8z
+	vadday.z 	ACC,vf0,vf9y
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::RIGHT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x00(%2)
+
+	# (*pMatrix)[Mth::UP][X] = 2 * (cross[Z] - wimag[Z]);
+	# (*pMatrix)[Mth::UP][Y] = 1 - 2 * (square[X] + square[Z]);
+	# (*pMatrix)[Mth::UP][Z] = 2 * (cross[X] + wimag[X]);
+	# (*pMatrix)[Mth::UP][W] = 0.0f;
+	vaddaz.x	ACC,vf0,vf9z
+	vadday.y	ACC,vf0,vf1y
+	vaddax.z	ACC,vf0,vf8x
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::UP]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x10(%2)
+
+	# (*pMatrix)[Mth::AT][X] = 2 * (cross[Y] + wimag[Y]);
+	# (*pMatrix)[Mth::AT][Y] = 2 * (cross[X] - wimag[X]);
+	# (*pMatrix)[Mth::AT][Z] = 1 - 2 * (square[X] + square[Y]);
+	# (*pMatrix)[Mth::AT][W] = 0.0f;
+	vadday.x	ACC,vf0,vf8y
+	vaddax.y	ACC,vf0,vf9x
+	vaddaz.z	ACC,vf0,vf1z
+	vsubaw.w	ACC,vf0,vf0w
+	# Save out (*pMatrix)[Mth::AT]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x20(%2)
+
+	# (*pMatrix)[Mth::POS][X] = pT->GetX();
+	# (*pMatrix)[Mth::POS][Y] = pT->GetY();
+	# (*pMatrix)[Mth::POS][Z] = pT->GetZ();
+	# (*pMatrix)[Mth::POS][W] = 1.0f;
+	vaddax.x	ACC,vf0,vf7x
+	vadday.y	ACC,vf0,vf7y
+	vaddaz.z	ACC,vf0,vf7z
+	vaddax.w	ACC,vf0,vf0x
+	# Save out (*pMatrix)[Mth::POS]
+	vmaddx.xyzw	vf11,vf0,vf0x
+	sqc2		vf11,0x30(%2)
+
+overnoflip2:
+	
+overnorotate:
+
+	": : "r" (pQ), "r" (pT), "r" (pMatrix), "r" (&rotate), "r" (&flip) : "$8","$9");
+
+#else
+#ifdef __PLAT_NGC__
+	MTXQuat( (Mtx)pMatrix, (QuaternionPtr)pQ );
+
+	(*pMatrix)[Mth::POS][X] = pT->GetX();
+	(*pMatrix)[Mth::POS][Y] = pT->GetY();
+	(*pMatrix)[Mth::POS][Z] = pT->GetZ();
+	(*pMatrix)[Mth::POS][W] = 1.0f;
+#else
+	// Ye olde slowe code
+	Mth::Vector		square;
+	Mth::Vector		cross;
+	Mth::Vector		wimag;
+
+	square[X] = (*pQ)[X] * (*pQ)[X];
+	square[Y] = (*pQ)[Y] * (*pQ)[Y];
+	square[Z] = (*pQ)[Z] * (*pQ)[Z];
+
+	cross[X] = (*pQ)[Y] * (*pQ)[Z];
+	cross[Y] = (*pQ)[Z] * (*pQ)[X];
+	cross[Z] = (*pQ)[X] * (*pQ)[Y];
+
+	wimag[X] = -(*pQ)[W] * (*pQ)[X];
+	wimag[Y] = -(*pQ)[W] * (*pQ)[Y];
+	wimag[Z] = -(*pQ)[W] * (*pQ)[Z];
+
+	(*pMatrix)[Mth::RIGHT][X] = 1 - 2 * (square[Y] + square[Z]);
+	(*pMatrix)[Mth::RIGHT][Y] = 2 * (cross[Z] + wimag[Z]);
+	(*pMatrix)[Mth::RIGHT][Z] = 2 * (cross[Y] - wimag[Y]);
+	(*pMatrix)[Mth::RIGHT][W] = 0.0f;
+	
+	(*pMatrix)[Mth::UP][X] = 2 * (cross[Z] - wimag[Z]);
+	(*pMatrix)[Mth::UP][Y] = 1 - 2 * (square[X] + square[Z]);
+	(*pMatrix)[Mth::UP][Z] = 2 * (cross[X] + wimag[X]);
+	(*pMatrix)[Mth::UP][W] = 0.0f;
+
+	(*pMatrix)[Mth::AT][X] = 2 * (cross[Y] + wimag[Y]);
+	(*pMatrix)[Mth::AT][Y] = 2 * (cross[X] - wimag[X]);
+	(*pMatrix)[Mth::AT][Z] = 1 - 2 * (square[X] + square[Y]);
+	(*pMatrix)[Mth::AT][W] = 0.0f;
+
+	(*pMatrix)[Mth::POS][X] = pT->GetX();
+	(*pMatrix)[Mth::POS][Y] = pT->GetY();
+	(*pMatrix)[Mth::POS][Z] = pT->GetZ();
+	(*pMatrix)[Mth::POS][W] = 1.0f;
+#endif		// __PLAT_NGC__
+
+	if (rotate)
+	{
+		// compensate for bone rotation (like for the skateboards)
+
+		if (flip)
+		{
+			// Right: -x,y,z
+			(*pMatrix)[Mth::RIGHT][X] = -(*pMatrix)[Mth::RIGHT][X];
+			// Up: x,-y,-z
+			(*pMatrix)[Mth::UP][Y]=-(*pMatrix)[Mth::UP][Y];
+			(*pMatrix)[Mth::UP][Z]=-(*pMatrix)[Mth::UP][Z];
+			// At: -x,y,z
+			(*pMatrix)[Mth::AT][X]=-(*pMatrix)[Mth::AT][X];
+			// Pos: -x,y,z
+			(*pMatrix)[Mth::POS][X]=-(*pMatrix)[Mth::POS][X];
+		}
+		else
+		{
+			// Right: -x,-y,-z
+			(*pMatrix)[Mth::RIGHT][X] = -(*pMatrix)[Mth::RIGHT][X];
+			(*pMatrix)[Mth::RIGHT][Y] = -(*pMatrix)[Mth::RIGHT][Y];
+			(*pMatrix)[Mth::RIGHT][Z] = -(*pMatrix)[Mth::RIGHT][Z];
+			// Up: -x,-y,-z
+			(*pMatrix)[Mth::UP][X]=-(*pMatrix)[Mth::UP][X];
+			(*pMatrix)[Mth::UP][Y]=-(*pMatrix)[Mth::UP][Y];
+			(*pMatrix)[Mth::UP][Z]=-(*pMatrix)[Mth::UP][Z];
+			// At: x,y,z
+			// Pos: x,y,z
+		}	
+	}
+	else
+	{
+		if (flip)
+		{
+			// Right: x,-y,-z
+			(*pMatrix)[Mth::RIGHT][Y] = -(*pMatrix)[Mth::RIGHT][Y];
+			(*pMatrix)[Mth::RIGHT][Z] = -(*pMatrix)[Mth::RIGHT][Z];
+			// Up: -x,y,z
+			(*pMatrix)[Mth::UP][X]=-(*pMatrix)[Mth::UP][X];
+			// At: -x,y,z
+			(*pMatrix)[Mth::AT][X]=-(*pMatrix)[Mth::AT][X];
+			// Pos: -x,y,z
+			(*pMatrix)[Mth::POS][X]=-(*pMatrix)[Mth::POS][X];
+		}
+		else
+		{
+			// Right: x,y,z
+			// Up: x,y,z
+			// At: x,y,z
+			// Pos: x,y,z
+		}	
+	}	
+#endif	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkeleton::Update( CPose* pPose )
+{
+	this->Update( &pPose->m_rotations[0], &pPose->m_translations[0] );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeleton::Update( Mth::Quat* pQuat, Mth::Vector* pTrans )
+{
+	CBone* p_currentBone = mp_bones;
+	Mth::Matrix* p_currentMatrix=mp_matrices;
+	Mth::Quat* p_current_quat=pQuat;
+	Mth::Vector* p_current_trans=pTrans;
+
+#ifdef __NOPT_ASSERT__
+	bool flipped = m_flags & nxSKELETONFLAGS_FLIPPED;
+	if ( flipped )
+	{
+		Dbg_MsgAssert( 0, ( "Flipping has been moved to CAnimationComponent" ) );
+	}
+#endif
+
+	// Calculate the mask for testing the bone skip data.
+	uint32 skip_index_mask	= 1 << GetBoneSkipIndex();
+	uint32*	p_skip_list		= mp_skeletonData->GetBoneSkipList();
+
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		if( !( p_currentBone->m_flags & nxBONEFLAGS_NOANIM ))
+		{
+			// Decide whether we can skip this bone. Skipping bones where a child bone is not skipped will cause
+			// problems.
+			bool skip_this_bone = (( p_skip_list[i] & skip_index_mask ) != 0 );
+			if( !skip_this_bone )
+			{
+				// Skateboard rotation has been moved to CAnimationComponent.
+				Dbg_MsgAssert( !( p_currentBone->m_flags & nxBONEFLAGS_ROTATE ), ( "Skateboard rotation has been moved to CAnimationComponent" ) );
+
+				sQuatVecToMatrix( p_current_quat, p_current_trans, p_currentMatrix, false, false );
+		
+				if ( p_currentBone->m_flags & nxBONEFLAGS_SCALENONLOCAL )
+				{
+					p_currentMatrix->Scale( p_currentBone->m_scale );
+				}
+
+				if ( i != 0 )
+				{
+					// if it's not the root, then apply the parent's xform
+					(*p_currentMatrix) *= *(p_currentBone->mp_parentMatrix);
+				}
+			}
+			else
+			{
+				// Use the parent bone's matrix for now.
+				(*p_currentMatrix) = *(p_currentBone->mp_parentMatrix);
+			}
+		}
+		
+		p_currentMatrix++;
+		p_currentBone++;
+		p_current_quat++;
+		p_current_trans++;
+	}
+
+	Mth::Matrix* p_currentInverseNeutralPoseMatrix = mp_skeletonData->GetInverseNeutralPoseMatrices();
+	Dbg_MsgAssert( p_currentInverseNeutralPoseMatrix, ( "Was expecting neutral pose matrices to be initialized.  SKE files not in version 2 format?" ) );
+
+	// Loop through each bone and update its matrices
+	p_currentBone=mp_bones;
+	p_currentMatrix=mp_matrices;
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+		if ( !(p_currentBone->m_flags & nxBONEFLAGS_NOANIM) )
+		{
+			if ( p_currentBone->m_flags & nxBONEFLAGS_SCALELOCAL )
+			{
+				p_currentMatrix->ScaleLocal( p_currentBone->m_scale );
+			}
+
+			// switching over to the new system of having the neutral pose
+//			Dbg_MsgAssert( p_currentInverseNeutralPoseMatrix, ( "default anims have been deprecated" ) );
+//			(*p_currentMatrix) = p_currentBone->m_invertedNeutralMatrix * (*p_currentMatrix);		
+
+			// data inside the SKE file, rather than the skeleton instance
+			(*p_currentMatrix) = (*p_currentInverseNeutralPoseMatrix) * (*p_currentMatrix);		
+				
+			// clear out final component, just in case...
+			// it's probably be worth investigating whether we need to do this...
+			(*p_currentMatrix)[Mth::RIGHT][W] = 0.0f;
+			(*p_currentMatrix)[Mth::UP][W] = 0.0f;
+			(*p_currentMatrix)[Mth::AT][W] = 0.0f;
+//			(*p_currentMatrix)[Mth::POS][W] = 1.0f;
+		}
+
+		p_currentInverseNeutralPoseMatrix++;
+		p_currentMatrix++;
+		p_currentBone++;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeleton::GetBoneMatrix( uint32 boneId, Mth::Matrix* pBoneMatrix )
+{
+	Dbg_Assert( pBoneMatrix );
+
+	CBone* pBone = get_bone_by_id( boneId );
+	int i = GetBoneIndexById( boneId );
+
+	if ( !pBone )
+	{
+		// for now, since we're changing our bone names around,
+		// don't assert if we can't find the bone...
+		return false;
+	}
+
+	Dbg_MsgAssert( pBone, ( "Couldn't find bone with id %s", Script::FindChecksumName(boneId) ) );
+
+	// m_matrix represents the transform needed to go from the default (neutral)
+	// pose to the actual pose with animation applied to it.  So, we need to
+	// transform this value by the root-to-neutral matrix (m_neutralMatrix)
+	// in order to get the actual animated-bone's position for this frame (relative
+	// to the root of the object).
+
+	Mth::Matrix boneMatrix;
+	boneMatrix = *(mp_matrices + i);
+	
+	// GJ 2/21/03:  i'm not sure why we needed to deal with the
+	// neutral matrix at all...   should look into this at some
+	// point...
+	Mth::Matrix theMat = *( mp_skeletonData->GetInverseNeutralPoseMatrices() + i );
+//	Mth::Matrix theMat = pBone->m_invertedNeutralMatrix;
+	theMat.InvertUniform();
+	theMat = theMat * boneMatrix;
+    
+	// GJ kludge:  the root bone contains a 90 degree rotation
+	// to compensate for the MAX->Game coordinate change
+	// but we don't want that to affect the bone matrix, 
+	// so transform it by the inverse of the root matrix
+//	CBone* pDummyBone = get_bone_by_id( CRCD(0x9f173eeb,"dummy_scale_zz") );
+//	if ( pDummyBone )
+	
+	int dummyIndex = GetBoneIndexById( CRCD(0x9f173eeb,"dummy_scale_zz") );
+	if ( dummyIndex != -1 )
+	{
+		Mth::Vector temp = theMat[Mth::POS];
+		theMat[Mth::POS] = Mth::Vector(0.0f,0.0f,0.0f,1.0f);
+		Mth::Matrix dummyMatrix = *( mp_skeletonData->GetInverseNeutralPoseMatrices() + dummyIndex );
+//		Mth::Matrix dummyMatrix = pDummyBone->m_invertedNeutralMatrix;
+		theMat = dummyMatrix * theMat;
+		theMat[Mth::POS] = temp;
+	}
+
+	(*pBoneMatrix) = theMat;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32* CSkeleton::GetBoneSkipList( void )
+{
+	if( mp_skeletonData )
+	{
+		return mp_skeletonData->GetBoneSkipList();
+	}
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeleton::SetBoneSkipDistance( float dist )
+{
+	if( mp_skeletonData )
+	{
+		for( uint32 lod = 0; lod <= m_maxBoneSkipLOD; ++lod )
+		{
+			if( dist < mp_skeletonData->GetBoneSkipDistance( lod ))
+			{
+				m_skipIndex = lod;
+				return;
+			}
+		}
+	}
+	m_skipIndex = m_maxBoneSkipLOD;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeleton::GetBonePosition( uint32 boneId, Mth::Vector* pBonePos )
+{
+	Dbg_Assert( pBonePos );
+
+	CBone* pBone = get_bone_by_id( boneId );
+	int i = GetBoneIndexById( boneId );
+
+	if ( !pBone )
+	{
+		// for now, since we're changing our bone names around,
+		// don't assert if we can't find the bone...
+		return false;
+	}
+
+	Dbg_MsgAssert( pBone, ( "Couldn't find bone with id %s", Script::FindChecksumName(boneId) ) );
+
+	// m_matrix represents the transform needed to go from the default (neutral)
+	// pose to the actual pose with animation applied to it.  So, we need to
+	// transform this value by the root-to-neutral matrix (m_neutralMatrix)
+	// in order to get the actual animated-bone's position for this frame (relative
+	// to the root of the object).
+
+	// GJ 2/21/03:  i'm not sure why we needed to deal with the
+	// neutral matrix at all...   should look into this at some
+	// point...
+
+	Mth::Matrix boneMatrix;
+	boneMatrix = *(mp_matrices + i);
+	Mth::Matrix theMat = *( mp_skeletonData->GetInverseNeutralPoseMatrices() + i );
+//	Mth::Matrix theMat = pBone->m_invertedNeutralMatrix;
+	theMat.InvertUniform();
+	theMat = theMat * boneMatrix;
+    
+    (*pBonePos) = theMat[Mth::POS];
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkeletonData::CSkeletonData()
+{
+    m_numBones = 0;
+	m_flags = 0;
+    m_animScriptName = 0;
+	mp_inverseNeutralPoseMatrices = NULL;
+
+	// Default the Level0 LOD distance to a large number.
+	m_skipLODDistances[0] = MAX_LOD_DISTANCE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkeletonData::~CSkeletonData()
+{
+	if ( mp_inverseNeutralPoseMatrices )
+	{
+		delete[] mp_inverseNeutralPoseMatrices;
+		mp_inverseNeutralPoseMatrices = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeletonData::Load( uint32* p_data32, int data_size, bool assertOnFail )
+{
+	uint8* p_data = (uint8*)p_data32;
+
+	// new load of platform-specific SKE files...
+	bool success = false;
+
+#ifdef __NOPT_ASSERT__
+	int versionNumber = *((int*)p_data);
+	Dbg_MsgAssert( versionNumber >= 2, ( "No longer supporting old SKE format" ) );
+#endif
+	p_data += sizeof(int);
+		
+	m_flags = *((uint32*)p_data);
+	p_data += sizeof(uint32);
+
+	m_numBones = *((int*)p_data);
+	p_data += sizeof(int);
+    
+    Dbg_MsgAssert( m_numBones < vMAX_BONES, ( "Too many bones in skeleton (raise vMAX_BONES from %d to %d)", vMAX_BONES, m_numBones ) );
+
+	memcpy( m_boneNameTable, p_data, m_numBones * sizeof(uint32) );
+	p_data += ( m_numBones * sizeof(uint32) );
+
+	memcpy( m_parentNameTable, p_data, m_numBones * sizeof(uint32) );
+	p_data += ( m_numBones * sizeof(uint32) );
+
+	memcpy( m_flipNameTable, p_data, m_numBones * sizeof(uint32) );
+	p_data += ( m_numBones * sizeof(uint32) );
+	
+	Dbg_MsgAssert( mp_inverseNeutralPoseMatrices == NULL, ( "Inverse neutral pose matrices not NULL?!?" ) );
+	mp_inverseNeutralPoseMatrices = new Mth::Matrix[m_numBones];
+
+	for ( int i = 0; i < m_numBones; i++ )
+	{
+// Mick: read a word at a time, as it's not aligned and I'm trying to ensure
+// that all vector operations work on aligned boundries
+// so we can optimize with that assumption	
+//		Mth::Quat theQuat = *((Mth::Quat*)p_data);
+		Mth::Quat theQuat;
+		theQuat[X] = *((float*)p_data);
+		theQuat[Y] = *((float*)(p_data+4));
+		theQuat[Z] = *((float*)(p_data+8));
+		theQuat[W] = *((float*)(p_data+12));
+		p_data += sizeof(Mth::Quat);
+		
+//		Mth::Vector theVector = *((Mth::Vector*)p_data);
+		Mth::Vector theVector;
+		theVector[X] = *((float*)(p_data));
+		theVector[Y] = *((float*)(p_data+4));
+		theVector[Z] = *((float*)(p_data+8));
+		theVector[W] = *((float*)(p_data+12));
+		p_data += sizeof(Mth::Vector);
+
+		Mth::Matrix theNeutralPoseMatrix;
+
+		// gets the skeleton into the model's space
+		Mth::QuatVecToMatrix( &theQuat, &theVector, &theNeutralPoseMatrix );
+
+		if ( i != 0 )
+		{
+			Mth::Matrix neutral_parent_matrix = *(mp_inverseNeutralPoseMatrices + GetIndex(GetParentName(i)));
+
+			// GJ:  shouldn't this already be inverted from
+			// previous iterations of the for-loop?
+			neutral_parent_matrix.InvertUniform();
+
+			theNeutralPoseMatrix *= neutral_parent_matrix;
+
+			theNeutralPoseMatrix[X][W] = 0.0f;
+			theNeutralPoseMatrix[Y][W] = 0.0f;
+			theNeutralPoseMatrix[Z][W] = 0.0f;
+			theNeutralPoseMatrix[W][W] = 1.0f;
+		}
+
+		theNeutralPoseMatrix.InvertUniform();
+		*(mp_inverseNeutralPoseMatrices + i) = theNeutralPoseMatrix;
+	}
+
+	// if we get here, then it's successful
+	success = true;
+
+//ERROR:
+    return success;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeletonData::InitialiseBoneSkipList( const char* p_fileName )
+{
+	// Stip leading path information and trailing extensions to obtain the name of the skeleton.
+	char skeleton_name[128];
+
+	char *p_copy = strrchr( p_fileName, '/' );
+	if( p_copy == NULL )
+		p_copy = strrchr( p_fileName, '\\' );
+
+	if( p_copy )
+	{
+		++p_copy;
+		strcpy( skeleton_name, p_copy );
+	}
+	else
+	{
+		strcpy( skeleton_name, p_fileName );
+	}
+
+	p_copy = strchr( skeleton_name, '.' );
+	if( p_copy )
+	{
+		*p_copy = '\0';
+	}
+
+	// First, get a pointer to the script global SkipBoneLODInfo.
+	Script::CStruct *p_all_lod_info = Script::GetStructure( CRCD( 0x7da53781, "BoneSkipLODInfo" ));
+	if( p_all_lod_info )
+	{
+		Script::CArray *p_skeleton_lod_info = NULL;
+		p_all_lod_info->GetArray( skeleton_name, &p_skeleton_lod_info );
+
+		// No requirement that there *must* be SkipBoneLODInfo for a given skeleton.
+		if( p_skeleton_lod_info )
+		{
+			// Now run through each element of the array. Each element is a structure.
+			uint32 array_size = p_skeleton_lod_info->GetSize();
+			for( uint32 lod_level = 0; lod_level < array_size; ++lod_level )
+			{
+				Script::CStruct *p_element = p_skeleton_lod_info->GetStructure( lod_level );
+	
+				// Obtain the distance, required, below which this LOD is active.
+				float lod_distance = 0.0f;
+				if( p_element->GetFloat( "LODDistance", &lod_distance ))
+				{
+					// Convert to inches.
+					lod_distance = FEET_TO_INCHES( lod_distance );
+
+					// Store the square of the distance (in inches).
+					// Special case - if this is the last lod level, set the distance such that it will always be active.
+					if( lod_level == ( array_size - 1 ))
+					{
+						m_skipLODDistances[lod_level] = MAX_LOD_DISTANCE;
+					}
+					else
+					{
+						m_skipLODDistances[lod_level] = lod_distance * lod_distance;
+					}
+				}
+				else
+				{
+					Dbg_MsgAssert( 0, ( "Missing LODDistance field for LOD level %d in skeleton %s\n", lod_level, skeleton_name ));
+				}
+	
+				Script::CArray *p_skip_bones_array = NULL;
+				if( p_element->GetArray( "SkipBones", &p_skip_bones_array ))
+				{
+					// There are bones to be skipped for this entry.
+					uint32 size = p_skip_bones_array->GetSize();
+					for( uint32 bone = 0; bone < size; ++bone )
+					{
+						// Get the checksum of the bone name.
+						uint32 bone_checksum = p_skip_bones_array->GetChecksum( bone );
+
+						// Obtain the bone index from this bone checksum.
+						uint32 bone_index			= GetIndex( bone_checksum );
+
+						// Set the bitfield flag for this bone.
+						m_skipLODTable[bone_index]	= 0xFFFFFFFF & ~(( 1 << lod_level ) - 1 );
+					}
+				}
+			}
+		}
+	}	
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkeletonData::Load(const char* p_fileName, bool assertOnFail)
+{
+	// new load of platform-specific SKE files...
+	bool success = false;
+	int file_size;
+	uint32* p_fileBuffer = NULL;
+
+	// open the file as a stream
+	void* pStream = File::Open( p_fileName, "rb" );
+	
+    // make sure the file is valid
+	if ( !pStream )
+	{
+		Dbg_MsgAssert( assertOnFail, ("Load of %s failed - file not found?", p_fileName) );
+		goto ERROR;
+	}
+
+	file_size = File::GetFileSize(pStream);
+	Dbg_MsgAssert( file_size, ("Skeleton file %s size is 0", p_fileName) );
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	p_fileBuffer = (uint32*)Mem::Malloc( file_size );
+	Mem::Manager::sHandle().PopContext();
+	
+	if ( !File::Read( p_fileBuffer, file_size, 1, pStream ) )
+	{
+		Dbg_MsgAssert( assertOnFail, ("Load of %s failed - read failed?", p_fileName) );
+		goto ERROR;
+	}
+
+	if ( !Load( p_fileBuffer, file_size, assertOnFail ) )
+	{
+		Dbg_MsgAssert( assertOnFail, ("Load of %s failed - parse failed?", p_fileName) );
+		goto ERROR;
+	}
+
+	// GJ:  THPS4 patch for weird head scaling on female peds...
+	if ( strstr( p_fileName, "ped_f" ) )
+	{
+		m_flags |= 0x1;
+	}
+
+	// if we get here, then it's successful
+	success = true;
+
+ERROR:
+	if ( p_fileBuffer )
+	{
+		Mem::Free( p_fileBuffer );
+	}
+	File::Close(pStream);    
+
+	InitialiseBoneSkipList( p_fileName );
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CSkeletonData::GetNumBones() const
+{
+    return m_numBones;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkeletonData::GetBoneName( int index )
+{
+    Dbg_Assert( index >= 0 && index < m_numBones );
+    
+    return m_boneNameTable[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkeletonData::GetParentName( int index )
+{
+    Dbg_Assert( index >= 0 && index < m_numBones );
+    
+    return m_parentNameTable[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkeletonData::GetParentIndex( int index )
+{
+    Dbg_Assert( index >= 0 && index < m_numBones );
+    
+    return GetIndex( m_parentNameTable[index] );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkeletonData::GetFlipName( int index )
+{
+    Dbg_Assert( index >= 0 && index < m_numBones );
+    
+    return m_flipNameTable[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkeletonData::GetIndex( uint32 boneName )
+{
+    for ( int i = 0; i < m_numBones; i++ )
+    {
+        if ( boneName == m_boneNameTable[i] )
+            return i;
+    }
+
+    // not found
+    Dbg_Assert( 0 );
+    return 0;
+}
+
+
+
+#if 0
+// The following functions have been deprecated...
+// they were formerly used to find the correct
+// default SKA file in order to build the neutral
+// pose matrices.  Now, however, the neutral pose
+// is stored inside the SKE file
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkeletonData::SetAnimScriptName( uint32 anim_script_name )
+{
+	m_animScriptName = anim_script_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkeletonData::GetAnimScriptName() const
+{
+	return m_animScriptName;
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Matrix* CSkeletonData::GetInverseNeutralPoseMatrices()
+{
+	return mp_inverseNeutralPoseMatrices;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
+
+// DEBUG FUNCTIONS
+
+#if 0
+
+	#if 0
+		// draw the bone between the origin of that bone, and its parent
+		tempMatrix0 = (*p_currentBone->mp_parentMatrix) * (*pRootMatrix);
+		tempMatrix1 = ( ( mp_matrices + i ) ) * (*pRootMatrix); 
+		AddDebugBone( tempMatrix0.GetPos(), tempMatrix1.GetPos(), r, g, b );	
+	#endif
+
+	#if 0
+		// * test neutral-in-skater-space pose *
+		tempMatrix0 = *p_currentBone->mp_invertedNeutralParentMatrix;
+		tempMatrix0.InvertUniform();
+		tempMatrix0	= tempMatrix0 * (*pRootMatrix); 
+			
+		tempMatrix1 = p_currentBone->m_invertedNeutralMatrix;
+		tempMatrix1.InvertUniform();
+		tempMatrix1 = tempMatrix1 * (*pRootMatrix);
+			
+		AddDebugBone( tempMatrix0.GetPos(), tempMatrix1.GetPos(), r, g, b );	
+	#endif
+	
+	#if 0
+		// * test inverted-to-neutral *
+		Mth::Matrix boneMatrix;
+		boneMatrix = *( ( mp_matrices + i ) );
+		tempMatrix0 = tempMatrix1;
+		tempMatrix1 = p_currentBone->m_neutralMatrix * boneMatrix;
+		tempMatrix1 *= (*pRootMatrix);
+		AddDebugBone( tempMatrix0.GetPos(), tempMatrix1.GetPos(), r, g, b );
+	#endif
+
+#endif
+
+
+#if 0
+	// Programmatic jaw test
+	if ( p_currentBone->m_name == Script::GenerateCRC("jaw") )
+	{
+		static int jaw_rotation = 0;
+		jaw_rotation++;
+		p_currentMatrix->RotateLocal(Mth::Vector( Mth::DegToRad((float)(jaw_rotation)/10.0f), 0.0f, 0.0f));
+		if (jaw_rotation>60)
+		{
+			jaw_rotation=0;
+		}
+	}
+#endif
+
+#if 0
+	// This scales the item, with respect to the model's
+	// origin.  There are some neat looking effects
+	// that can be done with this, such as a gorilla man
+	// with a over-sized upper body, but makes the lip
+	// tricks look incorrect.
+	if ( Script::GetInt( "ScalingTest", false ) )
+	{
+		// override the scale if the GorillaMode is set...
+		Script::CArray* pArray = Script::GetArray( "master_scaling_table" );
+		if ( pArray && i < (int)pArray->GetSize() )
+		{
+			Script::CVector* pVector = pArray->GetVector(i);
+			Mth::Vector theVector;
+			const float weight_scale=Script::GetFloat("weight_scale");
+			theVector[X] = 1.0f + (pVector->mX-1.0f) * weight_scale;
+			theVector[Y] = 1.0f + (pVector->mY-1.0f) * weight_scale;
+			theVector[Z] = 1.0f + (pVector->mZ-1.0f) * weight_scale;
+			p_currentMatrix->Scale(theVector);
+		}
+	}
+#endif
diff --git a/Code/Gfx/Skeleton.h b/Code/Gfx/Skeleton.h
new file mode 100644
index 0000000..5473f1e
--- /dev/null
+++ b/Code/Gfx/Skeleton.h
@@ -0,0 +1,179 @@
+//****************************************************************************
+//* MODULE:			Gfx
+//* FILENAME:		Skeleton.h
+//* OWNER:			Gary Jesdanun
+//* CREATION DATE:	11/15/2001
+//****************************************************************************
+
+#ifndef __GFX_SKELETON_H
+#define __GFX_SKELETON_H
+
+/*****************************************************************************
+**								Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								Defines									**
+*****************************************************************************/
+
+namespace Mth
+{
+	class Matrix;
+	class Quat;
+	class Vector;
+}
+									
+namespace Script
+{
+	class CStruct;
+};
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+	class CBone;
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+    
+class CSkeletonData	: public Spt::Class
+{
+public:
+
+	static const int	BONE_SKIP_LOD_BITS = 32;
+
+					CSkeletonData();
+	virtual			~CSkeletonData();
+	bool			Load( const char* p_fileName, bool assertOnFail );
+	bool			Load( uint32* p_data, int data_size, bool assertOnFail );
+	void			InitialiseBoneSkipList( const char* p_fileName );
+//	void			SetAnimScriptName( uint32 animScriptName );
+
+public:
+	int				GetNumBones() const;
+	uint32			GetBoneName( int index );
+	uint32			GetParentName( int index );
+	uint32			GetParentIndex( int index );
+	uint32			GetFlipName( int index );
+	uint32			GetIndex( uint32 boneName );
+//	uint32			GetAnimScriptName() const;
+	uint32*			GetBoneSkipList( void )				{ return m_skipLODTable; }
+	float			GetBoneSkipDistance( uint32 lod )	{ return m_skipLODDistances[lod]; }
+
+	Mth::Matrix*	GetInverseNeutralPoseMatrices();
+
+protected:
+	int				m_numBones;
+	uint32			m_boneNameTable[vMAX_BONES];
+	uint32			m_parentNameTable[vMAX_BONES];
+	uint32			m_flipNameTable[vMAX_BONES];
+	uint32			m_skipLODTable[vMAX_BONES];				// Each uint32 for a specific bone provides 32 levels of skip information.
+	float			m_skipLODDistances[BONE_SKIP_LOD_BITS];
+	uint32			m_animScriptName;
+	Mth::Matrix* 	mp_inverseNeutralPoseMatrices;
+	
+public:
+	int				m_flags;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class CSkeleton
+{
+public:
+	CSkeleton( CSkeletonData* pSkeletonData );
+	~CSkeleton();
+
+public:
+	int						GetNumBones( void ) const;
+	bool					ApplyBoneScale( Script::CStruct* pBodyShapeStructure );
+	void					ResetScale( void );
+	
+public:
+	void 					Update( CPose* pPose );
+	void					Update( Mth::Quat* pQuat, Mth::Vector* pTrans );
+	void					Display( Mth::Matrix* pMatrix, float r, float g, float b );
+	
+public:
+	Mth::Matrix*			GetMatrices();
+	Mth::Matrix				GetNeutralMatrix( int boneIndex );
+	bool					GetBoneMatrix( uint32 boneId, Mth::Matrix* pMatrix );
+	bool					GetBonePosition( uint32 boneId, Mth::Vector* pVector );
+	int						GetBoneIndexById( uint32 boneId );
+	int						GetFlipIndex( int boneIndex );
+//	void					GetSkipList( int* pSkipList );
+	uint32*					GetBoneSkipList( void );
+	uint32					GetBoneSkipIndex( void )		{ return m_skipIndex; }
+	void					SetBoneSkipDistance( float dist );
+	void					SetMaxBoneSkipLOD( uint32 max )	{ m_maxBoneSkipLOD = max; }
+
+public:
+	// The following should be moved to the CAnimationComponent class	
+//	bool					GetBoneRotationByIndex( int boneIndex );
+
+public:
+	bool					SetBoneActive( uint32 boneId, bool active );
+	bool					SetBoneScale( uint32 boneId, const Mth::Vector& theBoneScale, bool isLocalScale );
+	bool					GetBoneScale( uint32 boneId, Mth::Vector* pBoneScaleVector );
+	void					CopyBoneScale( Gfx::CSkeleton* pSourceSkeleton );
+	void					SetNeutralPose( Mth::Quat* pQuat, Mth::Vector* pTrans );
+	CSkeletonData*			GetSkeletonData() { return mp_skeletonData; }
+
+protected:
+	CBone*					get_bone_by_id( uint32 boneId );
+	void					update_matrices();
+
+protected:
+	uint32					m_flags;
+	Mth::Matrix*			mp_matrices;
+    CSkeletonData*			mp_skeletonData;
+	uint32					m_skipIndex;
+	uint32					m_maxBoneSkipLOD;
+
+	// for non-procedural bone anims
+	int						m_numBones;
+	CBone*					mp_bones;
+	
+protected:
+	void					initialize_hierarchy( CSkeletonData* pSkeletonData );
+	void					initialize_flip_matrices( CSkeletonData* pSkeletonData );
+	void					initialize_bone_names( CSkeletonData* pSkeletonData );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Gfx
+
+#endif	// __GFX_SKELETON_H
+
diff --git a/Code/Gfx/XBox/NX/BillboardScreenAlignedVS.vsh b/Code/Gfx/XBox/NX/BillboardScreenAlignedVS.vsh
new file mode 100644
index 0000000..20faeb6
--- /dev/null
+++ b/Code/Gfx/XBox/NX/BillboardScreenAlignedVS.vsh
@@ -0,0 +1,53 @@
+xvs.1.1
+
+; Constants:
+;  c0 - c3		WVP Matrix (WORLD*VIEW*PROJECTION)
+;  c4			Screen right vector
+;  c5			Screen up vector
+;  c6			Screen at vector
+
+; In:
+;   v0 - Position		(actually the position of the pivot point)
+;	v1 - Normal			(actually the position of the point relative to the pivot)
+;   v2 - Vertex color
+;   v3 - TexCoord0
+;   v4 - TexCoord1
+;   v5 - TexCoord2
+;   v6 - TexCoord3
+
+; Out:
+;   oPos - Position
+;   oTn - TextureCoords
+
+;------------------------------------------------------------------------------
+; Pivot relative position -> world relative position, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+mul	r0.xyz,	v1.x, c4
+mov oT0,	v3
+mad r0.xyz, v1.y, c5, r0
+mov oT1,	v4
+mad r0.xyz,	v1.z, c6, r0
+mov oT2,	v5
+add r0.xyz,	v0, r0
+mov oT3,	v6
+sge r0.w,	c0.x, c0.x		; Set r0.w = 1.0
+
+;------------------------------------------------------------------------------
+; Vertex color
+;------------------------------------------------------------------------------
+mov oD0, v2
+
+;------------------------------------------------------------------------------
+; Vertex->screen transform
+;------------------------------------------------------------------------------
+dp4 oPos.x, r0, c0
+dp4 oPos.y, r0, c1
+dp4 oPos.z, r0, c2
+dp4 oPos.w, r0, c3
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+mov	oFog.x, -r12.w
+
diff --git a/Code/Gfx/XBox/NX/ParticleFlatVS.vsh b/Code/Gfx/XBox/NX/ParticleFlatVS.vsh
new file mode 100644
index 0000000..b0c4786
--- /dev/null
+++ b/Code/Gfx/XBox/NX/ParticleFlatVS.vsh
@@ -0,0 +1,66 @@
+xvs.1.1
+
+; Constants:
+;  c0 - c3		WVP Matrix (WORLD*VIEW*PROJECTION)
+;  c4			Screen right vector
+;  c5			Screen up vector
+;  c8-c11		Vertex texture coordinates (for index0 through index3)
+;  c12-c15		Vertex width and height multipliers (for index0 through index3)
+
+;  c16			Particle position (xyz) (set in the data stream per particle)
+;  c17			Particle start width and height (xy) and end width and height (zw) (set in the data stream per particle)
+;  c18			Size interpolator (x) and color interpolator (y) (set in the data stream per particle)
+
+
+; In:
+;   v0 - Vertex start color
+;   v1 - Vertex end color
+;   v2 - Index
+
+; Out:
+;   oPos	- Position
+;   oD0		- Vertex color
+;   oT0		- TextureCoords
+
+; Get the index of this vert
+mov	a0.x, v2.x
+
+; Move the width and height multipliers into a general register.
+mov r1, c[12 + a0.x]
+
+; Calculate the interpolated width and height of the particle.
+mov r2, c17.zw		// r2 = ( c17.z, c17.w, c17.w, c17.w )
+sub r2, r2, c17.xy	// r2 = ( c17.z - c17.x, c17.w - c17.y, ...)
+mul r2, r2, c18.x
+add r2, r2, c17.xy
+
+; Multiply by the width and height of this particle.
+mul r1, r1, r2
+
+; Add width and height multiples of the screen right and up vectors
+mul r0.xyz, r1.x, c4
+mad r0.xyz, r1.y, c5, r0
+
+; Add particle position
+add r0.xyz, c16, r0
+sge r0.w,	r1.x, r1.x		; r0.w = 1.0
+
+; Vertex->screen transform
+dp4 oPos.x, r0, c0
+dp4 oPos.y, r0, c1
+dp4 oPos.z, r0, c2
+dp4 oPos.w, r0, c3
+
+; Calculate the interpolated vertex color
+mov r2, v1
+sub r2, r2, v0
+mad r2, r2, c18.y, v0
+
+; Vertex color
+mov oD0, r2
+
+; Deal with fog value (r12 shadows oPos)...
+mov	oFog.x, -r12.w
+
+; Move texture coordinate in based on index
+mov oT0, c[8 + a0.x]
diff --git a/Code/Gfx/XBox/NX/ParticleNewFlatPointSpriteVS.vsh b/Code/Gfx/XBox/NX/ParticleNewFlatPointSpriteVS.vsh
new file mode 100644
index 0000000..ebff279
--- /dev/null
+++ b/Code/Gfx/XBox/NX/ParticleNewFlatPointSpriteVS.vsh
@@ -0,0 +1,59 @@
+xvs.1.1
+
+; Constants:
+;  c0 - c3		WVP Matrix (WORLD*VIEW*PROJECTION)
+;  c18-c20		Particle s0, s1 and s2 vectors
+;  c21-c23		Particle p0, p1 and p2 vectors
+;  c24			Eye position (x,y,z) and viewport height (w)
+
+; In:
+;   v0 - 4-element random vector
+;   v1 - Time (x) and color interpolator (y)
+;   v2 - Vertex start color
+;   v3 - Vertex end color
+
+; Out:
+;   oPos	- Position
+;	oPts	- Point sprite size
+;   oD0		- Vertex color
+
+
+; Calculate the square of the time interpolator.
+mul r2.x, v1.x, v1.x
+
+; Calculate the position of the particle, from pos = ( m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )) + ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 )).Scale( r );
+mov r0, c21					; pos = m_p0
+mad r0, c22, v1.x, r0		; pos = m_p0 + ( t * m_p1 )
+mad r0, c23, r2.x, r0		; pos = m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )
+
+mov r1, c18					; tmp = ( m_s0 )
+mad r1, c19, v1.x, r1		; tmp = ( m_s0 + ( t * m_s1 ))
+mad r1, c20, r2.x, r1		; tmp = ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 ))
+
+mad r0, r1, v0, r0			; pos = ( m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )) + ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 )).Scale( r );
+
+; Calculate the distance from the camera to the particle.
+sub r4, c24, r0
+dp3	r4.x, r4, r4
+rsq r4.x, r4.x
+mul r4.x, r4.x, r0.w
+
+; Now set position w to 1.0
+sge r0.w, r0.x, r0.x
+
+; Vertex->screen transform
+dp4 oPos.x, r0, c0
+dp4 oPos.y, r0, c1
+dp4 oPos.z, r0, c2
+dp4 oPos.w, r0, c3
+
+; Save off the size of the particle
+mul oPts.x, r4.x, c24.w
+
+; Calculate the interpolated vertex color
+mov r2, v2					; r2 = start_col
+sub r3, v3, r2				; r3 = end_col - start_col
+mad oD0, r3, v1.y, r2		; r3 = start_col + (( end col - start_col ) * color_interpolator )
+
+; Deal with fog value (r12 shadows oPos)...
+mov	oFog.x, -r12.w
diff --git a/Code/Gfx/XBox/NX/ParticleNewFlatVS.vsh b/Code/Gfx/XBox/NX/ParticleNewFlatVS.vsh
new file mode 100644
index 0000000..f190688
--- /dev/null
+++ b/Code/Gfx/XBox/NX/ParticleNewFlatVS.vsh
@@ -0,0 +1,75 @@
+xvs.1.1
+
+; Constants:
+;  c0 - c3		WVP Matrix (WORLD*VIEW*PROJECTION)
+;  c4			Screen right vector
+;  c5			Screen up vector
+;  c8-c11		Vertex texture coordinates (for index0 through index3)
+;  c12-c15		Vertex width and height multipliers (for index0 through index3)
+
+;  c16			Particle random seed vector
+;  c17			Particle time interpolator (c17.x), color interpolator (c17.y)
+
+;  c18-c20		Particle s0, s1 and s2 vectors
+;  c21-c23		Particle p0, p1 and p2 vectors
+
+; In:
+;   v0 - Vertex start color
+;   v1 - Vertex end color
+;   v2 - Index
+
+; Out:
+;   oPos	- Position
+;   oD0		- Vertex color
+;   oT0		- TextureCoords
+
+; Get the index of this vert
+mov	a0.x, v2.x
+
+; Move the time interpolator into a general register, and calculate the square.
+mov r4, c17
+mul r4.y, r4.x, r4.x
+
+; Move the width and height multipliers into a general register.
+mov r3, c[12 + a0.x]
+
+; Calculate the position of the particle, from pos = ( m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )) + ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 )).Scale( r );
+mov r0, c21					; pos = m_p0
+mad r0, c22, r4.x, r0		; pos = m_p0 + ( t * m_p1 )
+mad r0, c23, r4.y, r0		; pos = m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )
+
+mov r1, c18					; tmp = ( m_s0 )
+mad r1, c19, r4.x, r1		; tmp = ( m_s0 + ( t * m_s1 ))
+mad r1, c20, r4.y, r1		; tmp = ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 ))
+
+mad r0, r1, c16, r0			; pos = ( m_p0 + ( t * m_p1 ) + (( t * t ) * m_p2 )) + ( m_s0 + ( t * m_s1 ) + (( t * t ) * m_s2 )).Scale( r );
+
+; Calculate the interpolated width and height of the particle.
+mov r2, r0.w
+
+; Now set position w to 1.0
+sge r0.w, r0.x, r0.x
+
+; Multiply width and height by the width and height multipliers of this vertex index
+mul r2.xy, r2.xy, r3.xy
+
+; Add width and height multiples of the screen right and up vectors to the position.
+mad r0.xyz, r2.x, c4, r0
+mad r0.xyz, r2.y, c5, r0
+
+; Vertex->screen transform
+dp4 oPos.x, r0, c0
+dp4 oPos.y, r0, c1
+dp4 oPos.z, r0, c2
+dp4 oPos.w, r0, c3
+
+; Calculate the interpolated vertex color
+mov r2, v1
+sub r2, r2, v0
+mad oD0, r2, c17.y, v0
+
+; Deal with fog value (r12 shadows oPos)...
+mov	oFog.x, -r12.w
+
+; Move texture coordinate in based on index
+mov oT0, c[8 + a0.x]
diff --git a/Code/Gfx/XBox/NX/PixelShader0.psh b/Code/Gfx/XBox/NX/PixelShader0.psh
new file mode 100644
index 0000000..376e00c
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader0.psh
@@ -0,0 +1,8 @@
+xps.1.1
+
+tex t0
+mul		t0.rgb,t0.rgb,c0.rgb	; Modulate texture color0 with material color0
+mul_x4	r0.rgb,v0.rgb,t0.rgb	; Modulate (x4) resultant color with vertex color
++mul_x2	r0.a,v0.a,t0.a			; Modulate (x2) result alpha with vertex alpha
+
+xfc prod, fog.rgb, sum, zero, 1 - fog.a, c4, r0.a
diff --git a/Code/Gfx/XBox/NX/PixelShader0IVA.psh b/Code/Gfx/XBox/NX/PixelShader0IVA.psh
new file mode 100644
index 0000000..4155e6b
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader0IVA.psh
@@ -0,0 +1,8 @@
+xps.1.1
+
+tex t0
+mul		t0.rgb,t0.rgb,c0.rgb	; Modulate texture color0 with material color0
+mul_x4	r0.rgb,v0.rgb,t0.rgb	; Modulate (x4) resultant color with vertex color
++mul_x2	r0.a,c4.a,t0.a			; Modulate (x2) result alpha with constant alpha
+
+xfc prod, fog.rgb, sum, zero, 1 - fog.a, c4, r0.a
diff --git a/Code/Gfx/XBox/NX/PixelShader1.psh b/Code/Gfx/XBox/NX/PixelShader1.psh
new file mode 100644
index 0000000..095ee93
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader1.psh
@@ -0,0 +1,3 @@
+xps.1.1
+mul_x2	v0.rgb,v0.rgb,c0.rgb						; Modulate vertex color0 with material color0
+mov		r0,v0
diff --git a/Code/Gfx/XBox/NX/PixelShader2.psh b/Code/Gfx/XBox/NX/PixelShader2.psh
new file mode 100644
index 0000000..e7414e7
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader2.psh
@@ -0,0 +1,3 @@
+xps.1.1
+tex t0
+mov r0,t0
diff --git a/Code/Gfx/XBox/NX/PixelShader3.psh b/Code/Gfx/XBox/NX/PixelShader3.psh
new file mode 100644
index 0000000..5cce747
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader3.psh
@@ -0,0 +1,2 @@
+xps.1.1
+mov r0,c0
diff --git a/Code/Gfx/XBox/NX/PixelShader4.psh b/Code/Gfx/XBox/NX/PixelShader4.psh
new file mode 100644
index 0000000..92cef16
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader4.psh
@@ -0,0 +1,3 @@
+xps.1.1
+tex t0
+mul_x2	r0,v0,t0				; Modulate (x2) texture color with vertex color.
diff --git a/Code/Gfx/XBox/NX/PixelShader5.psh b/Code/Gfx/XBox/NX/PixelShader5.psh
new file mode 100644
index 0000000..6528c1b
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader5.psh
@@ -0,0 +1,2 @@
+xps.1.1
+mov	r0,v0		; Just use vertex color.
diff --git a/Code/Gfx/XBox/NX/PixelShaderBrighten.psh b/Code/Gfx/XBox/NX/PixelShaderBrighten.psh
new file mode 100644
index 0000000..0b8f718
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderBrighten.psh
@@ -0,0 +1,10 @@
+xps.1.1
+
+tex t0
+;mul	t0.rgb,t0.rgb,c0.rgb	; Modulate texture color0 with material color0
+;mul_x4	r0.rgb,v0.rgb,t0.rgb	; Modulate (x4) resultant color with vertex color
+;mul_x2	r0.a,v0.a,t0.a			; Modulate (x2) result alpha with vertex alpha
+
+mul_x2	r0,v0.a,t0.a			; Modulate (x2) result alpha with vertex alpha and copy into all 4 channels
+
+xfc prod, fog.rgb, sum, zero, 1 - fog.a, c4, r0.a
diff --git a/Code/Gfx/XBox/NX/PixelShaderBrightenIVA.psh b/Code/Gfx/XBox/NX/PixelShaderBrightenIVA.psh
new file mode 100644
index 0000000..eba6c21
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderBrightenIVA.psh
@@ -0,0 +1,10 @@
+xps.1.1
+
+tex t0
+;mul		t0.rgb,t0.rgb,c0.rgb	; Modulate texture color0 with material color0
+;mul_x4		r0.rgb,v0.rgb,t0.rgb	; Modulate (x4) resultant color with vertex color
+;+mul_x2	r0.a,c4.a,t0.a			; Modulate (x2) result alpha with constant alpha
+
+mul_x2		r0,c4.a,t0.a			; Modulate (x2) result alpha with constant alpha and copy into all 4 channels
+
+xfc prod, fog.rgb, sum, zero, 1 - fog.a, c4, r0.a
diff --git a/Code/Gfx/XBox/NX/PixelShaderBumpWater.psh b/Code/Gfx/XBox/NX/PixelShaderBumpWater.psh
new file mode 100644
index 0000000..105715d
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderBumpWater.psh
@@ -0,0 +1,9 @@
+xps.1.1
+
+tex		t0
+texbem	t1, t0
+mul		t1.rgb,t1.rgb,c0.rgb	; Modulate texture1 color with material color0
+mul_x4	r0.rgb,v0.rgb,t1.rgb	; Modulate (x4) resultant color with vertex color
++mul_x2	r0.a,v0.a,t1.a			; Modulate (x2) result alpha with vertex alpha
+
+xfc prod, fog.rgb, sum, zero, 1 - fog.a, c4, r0.a
diff --git a/Code/Gfx/XBox/NX/PixelShaderFocusBlur.psh b/Code/Gfx/XBox/NX/PixelShaderFocusBlur.psh
new file mode 100644
index 0000000..9c31d11
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderFocusBlur.psh
@@ -0,0 +1,33 @@
+;------------------------------------------------------------------------------
+; 4-tap filtering
+; Copyright (C) 2001 Microsoft Corporation
+; All rights reserved.
+;------------------------------------------------------------------------------
+xps.1.1
+
+; default filter is box filter, but this is easily overwritten using SetPixelShaderConstant
+
+; When setting a pixel shader constant, we must check to see if the
+; filter coefficient is negative, in which case we set the constant as a positive
+; number and negate the constant in the expression below.
+def c0, 0.25f, 0.25f, 0.25f, 0.25f
+def c1, 0.25f, 0.25f, 0.25f, 0.25f 
+def c2, 0.25f, 0.25f, 0.25f, 0.25f 
+def c3, 0.25f, 0.25f, 0.25f, 0.25f 
+
+; source textures
+tex t0
+tex t1
+tex t2
+tex t3
+
+; simple way
+; mul r0, c0, t0
+; mad r0, c1, t1, r0
+; mad r0, c2, t2, r0
+; mad r0, c3, t3, r0
+
+; This has better precision (assuming the xmma_x2 intermediates don't overflow):
+xmma_x2 discard, discard, r0, c0, t0, c1, t1	; 2 * (c0 * t0 + c1 * t1)
+xmma_x2 discard, discard, r1, c2, t2, c3, t3	; 2 * (c2 * t2 + c3 * t3)
+add_d2 r0, r0, r1
diff --git a/Code/Gfx/XBox/NX/PixelShaderFocusIntegrate.psh b/Code/Gfx/XBox/NX/PixelShaderFocusIntegrate.psh
new file mode 100644
index 0000000..1e311c3
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderFocusIntegrate.psh
@@ -0,0 +1,32 @@
+;------------------------------------------------------------------------------
+; Depth of field by choosing a range of z values and using that to select
+; a blurred version of the original image.
+; 
+; Copyright (C) 2001 Microsoft Corporation
+; All rights reserved.
+;------------------------------------------------------------------------------
+xps.1.1
+
+; override with SetPixelShaderConstant
+def c0, 0.f, 0.f, 0.f, 0.f      ; offset
+def c1, 0.f, 0.f, 1.f, 0.f      ; slope x1
+def c2, 0.f, 0.f, 0.f, 0.f      ; slope x4
+def c3, 0.f, 0.f, 0.f, 1.f      ; slope x16
+def c4, 1.f, 1.f, 1.f, 0.498039215      ; 0x7f
+
+; source textures
+tex t0                  ; z-buffer texture
+tex t1                  ; pre-blurred texture
+
+; get the range of active z values
+sub    r0, t0, c0               ; offset
+mul_x4 r1, c3, r0               ; scale x16
+mad_x4 r1, c2, r0, r1           ; scale x4
+mad    r1, c1, r0, r1           ; scale x1
+sub r0.a, r1.a, c4.a            ; subtract 0x7f from r1.a
+cnd r0.a, r0.a, zero.a, r1.b    ; keep blue only if r1.a is not 0xff
+add r1.a, r1.a, r0.a            ; add alpha + blue, the desired range is now mapped to [0,1]
+mul r1.a, r1_bx2.a, r1_bx2.a    ; convert to -1,1 range and square to get parabola
+
+; modulate pre-blurred texture by depth range
+xfc r1.a, t1, zero, zero, zero, zero, r1.a
diff --git a/Code/Gfx/XBox/NX/PixelShaderFocusLookupIntegrate.psh b/Code/Gfx/XBox/NX/PixelShaderFocusLookupIntegrate.psh
new file mode 100644
index 0000000..bfdd72e
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderFocusLookupIntegrate.psh
@@ -0,0 +1,22 @@
+;------------------------------------------------------------------------------
+; FocusBlur by choosing a range of z values and using that to select
+; a blurred version of the original image.
+; 
+; Copyright (C) 2001 Microsoft Corporation
+; All rights reserved.
+;------------------------------------------------------------------------------
+xps.1.1
+
+def c0, 0.5f, 0.5f, 0.5f, 0.5f		; blur value
+
+; source textures
+tex t0				; z-buffer texture
+texreg2ar t1, t0	; lookup-table texture to choose what range of depth to use
+tex t2				; pre-blurred texture, first offset
+tex t3				; pre-blurred texture, second offset
+
+; blur pre-blurred texture even more
+lrp r0, c0, t2, t3
+
+; modulate by depth range
+xfc r0_sat, 1-t1.a, zero, zero, zero, zero, 1-t1.a
diff --git a/Code/Gfx/XBox/NX/PixelShaderNULL.psh b/Code/Gfx/XBox/NX/PixelShaderNULL.psh
new file mode 100644
index 0000000..34e2ef6
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderNULL.psh
@@ -0,0 +1,3 @@
+xps.1.1
+def c7, 1.0f, 1.0f, 1.0f, 1.0f
+mov r0, c7
diff --git a/Code/Gfx/XBox/NX/PixelShaderPointSprite.psh b/Code/Gfx/XBox/NX/PixelShaderPointSprite.psh
new file mode 100644
index 0000000..ed234d7
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShaderPointSprite.psh
@@ -0,0 +1,3 @@
+xps.1.1
+tex t3
+mul_x2	r0,v0,t3				; Modulate (x2) texture color with vertex color.
diff --git a/Code/Gfx/XBox/NX/PixelShader_ShadowBuffer.psh b/Code/Gfx/XBox/NX/PixelShader_ShadowBuffer.psh
new file mode 100644
index 0000000..1d6df0e
--- /dev/null
+++ b/Code/Gfx/XBox/NX/PixelShader_ShadowBuffer.psh
@@ -0,0 +1,11 @@
+; Shadow buffer pixel shader.
+; On entry:
+; c0 contains the base percentage of color (even in shadow),
+; c1 contains the percentage of color that shadow affects.
+
+xps.1.1
+
+tex t3			; get shadow from light
+
+mul r1, c1, t3	; Use the shadow buffer to affect the shadow portion of the color change
+add r0, c0, r1	; Add to the base color to get final color
diff --git a/Code/Gfx/XBox/NX/ShadowBufferStaticGeomPS.psh b/Code/Gfx/XBox/NX/ShadowBufferStaticGeomPS.psh
new file mode 100644
index 0000000..d57897f
--- /dev/null
+++ b/Code/Gfx/XBox/NX/ShadowBufferStaticGeomPS.psh
@@ -0,0 +1,15 @@
+;Shadowbuffer pixel shader.
+
+xps.1.1
+
+def c7, 0.6f, 0.6f, 0.6f, 0.0f
+
+tex t0							; Base geometry texture (for alpha modulation).
+tex t1							; Shadow depth buffer texture.
+
+; White texture (outside of skater) we don't want to draw - so negate blue component into alpha.
+; This way the white part of the texture will have zero alpha, the black part will have full alpha.
+
+mul		r0.a, 1-t1.b, t0.a		; Modulate shadow alpha with base texture alpha (to neatly deal with holes in texture).
++add	r0.rgb, t1.rgb, c7.rgb	; Add in the ambient component to brighten up the shadow a bit.
+
diff --git a/Code/Gfx/XBox/NX/ShadowBufferStaticGeomVS.vsh b/Code/Gfx/XBox/NX/ShadowBufferStaticGeomVS.vsh
new file mode 100644
index 0000000..a7d12c9
--- /dev/null
+++ b/Code/Gfx/XBox/NX/ShadowBufferStaticGeomVS.vsh
@@ -0,0 +1,42 @@
+xvs.1.1
+
+; Constants:
+;  c0  - c3  - WVP Matrix (WORLD*VIEW*PROJECTION)
+;  c4  - c7  - WT Matrix (WORLD*TEXTURETRANSFORM)
+;  c8  - local space light position.
+
+; In:
+;   v0 - Position
+;   v1 - Vertex color
+;   v2 - TexCoord0
+
+; Out:
+;   oPos - Position
+;   oTn - TextureCoords
+
+;vertex->screen
+dp4 oPos.x, v0, c0
+dp4 oPos.y, v0, c1
+dp4 oPos.z, v0, c2
+dp4 oPos.w, v0, c3
+
+;diffuse lighting (not necessary for our purposes)
+;add	r0,c8,-v0
+;dp3 r0.w,r0,r0
+;rsq r1.x,r0.w
+;mul r0,r0,r1.x
+;dp3 oD0,v1,r0
+
+;decal texture
+mov oT0, v2
+
+;vertex->shadowbuffer texcoords
+dp4 oT1.x, v0, c4
+dp4 oT1.y, v0, c5
+dp4 oT1.z, v0, c6
+dp4 r0.w, v0, c7
+
+;clamp w (q) to 0
+slt r1, c0, c0
+max r0.w, r0.w, r1.w
+mov oT1.w, r0.w
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_1Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_1Weight.vsh
new file mode 100644
index 0000000..49d9014
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_1Weight.vsh
@@ -0,0 +1,102 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight
+    mul     VSTMP_REG_POS_ACCUM.xyz, VSTMP_REG_POS_TMP.xyz, VSIN_REG_WEIGHTS.x
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed normal by weight
+    mul     VSTMP_REG_NORMAL_ACCUM.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSIN_REG_WEIGHTS.x
+
+    ; Copy w
+    mov		VSTMP_REG_POS_ACCUM.w, VSIN_REG_POS.w
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp4		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dp4		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dp4		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dp4     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, r12.w
+
+;------------------------------------------------------------------------------
+; Transform normal by combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_X
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Y
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Z
+
+;------------------------------------------------------------------------------
+; Single directional light + Ambient
+;------------------------------------------------------------------------------
+
+    ; Normalize
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSTMP_REG_NORMAL_TMP
+    rsq     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP.w
+    mul     VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.w
+
+    ; DP normal & light0 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_TMP.w
+
+	; This is where the ambient gets added in.
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR
+
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light1 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_TMP.w
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light2 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_TMP.w
+;    mad     oD0, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+    mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+
+	; Material color attenuation
+	mul		oD0, r11, VSCONST_REG_MATERIAL_COLOR
+
+;------------------------------------------------------------------------------
+; Copy texture coordinates
+;------------------------------------------------------------------------------
+
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mov     oT2, VSIN_REG_TEXCOORDS2
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_2Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_2Weight.vsh
new file mode 100644
index 0000000..2d88965
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_2Weight.vsh
@@ -0,0 +1,108 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+    dp3     VSTMP_REG_NORMAL_ACCUM.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_ACCUM.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_ACCUM.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, r12.w
+
+;------------------------------------------------------------------------------
+; Transform normal by combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_X
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Y
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Z
+
+;------------------------------------------------------------------------------
+; Single directional light + Ambient
+;------------------------------------------------------------------------------
+
+    ; Normalize
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSTMP_REG_NORMAL_TMP
+    rsq     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP.w
+    mul     VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.w
+
+    ; DP normal & light0 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_TMP.w
+
+	; This is where the ambient gets added in.
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR
+
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light1 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_TMP.w
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light2 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_TMP.w
+;    mad     oD0, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+    mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+
+	; Material color attenuation
+	mul		oD0, r11, VSCONST_REG_MATERIAL_COLOR
+
+;------------------------------------------------------------------------------
+; Copy texture coordinates
+;------------------------------------------------------------------------------
+
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mov     oT2, VSIN_REG_TEXCOORDS2
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_3Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_3Weight.vsh
new file mode 100644
index 0000000..de9e8bb
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_3Weight.vsh
@@ -0,0 +1,118 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+    dp3     VSTMP_REG_NORMAL_ACCUM.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_ACCUM.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_ACCUM.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, r12.w
+
+;------------------------------------------------------------------------------
+; Transform normal by combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_X
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Y
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Z
+
+;------------------------------------------------------------------------------
+; Single directional light + Ambient
+;------------------------------------------------------------------------------
+
+    ; Normalize
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSTMP_REG_NORMAL_TMP
+    rsq     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP.w
+    mul     VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.w
+
+    ; DP normal & light0 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_TMP.w
+
+	; This is where the ambient gets added in.
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR
+
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light1 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_TMP.w
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light2 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_TMP.w
+;    mad     oD0, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+    mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+
+	; Material color attenuation
+	mul		oD0, r11, VSCONST_REG_MATERIAL_COLOR
+
+;------------------------------------------------------------------------------
+; Copy texture coordinates
+;------------------------------------------------------------------------------
+
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mov     oT2, VSIN_REG_TEXCOORDS2
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight.vsh
new file mode 100644
index 0000000..68feb65
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight.vsh
@@ -0,0 +1,74 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    mov		VSTMP_REG_POS_TMP.w, VSIN_REG_POS.w
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+	; No need to scale by results by vertex weights, since this is a one-weight shader so the weight
+	; is assumed to be 1.0
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp4		oPos.x, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_X
+    dp4		oPos.y, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Y
+    dp4		oPos.z, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Z
+    dp4     oPos.w, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+
+	mov     oT0, VSIN_REG_TEXCOORDS0
+	mov     oT1, VSIN_REG_TEXCOORDS1
+	mov     oT2, VSIN_REG_TEXCOORDS2
+	mov     oT3, VSIN_REG_TEXCOORDS3
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_SBPassThru.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_SBPassThru.vsh
new file mode 100644
index 0000000..0f953da
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_SBPassThru.vsh
@@ -0,0 +1,53 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    mov		VSTMP_REG_POS_TMP.w, VSIN_REG_POS.w
+
+	; No need to scale by results by vertex weights, since this is a one-weight shader so the weight
+	; is assumed to be 1.0
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp4		oPos.x, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_X
+    dp4		oPos.y, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Y
+    dp4		oPos.z, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Z
+    dp4     oPos.w, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Calculate second set of texture coordinates (into 4th slot) by transforming the world space
+; position into the shadowbuffer space
+;------------------------------------------------------------------------------
+
+	dp4		oT3.x, VSTMP_REG_POS_TMP, VSCONST_REG_SHADOWBUFFER_TRANSFORM_X
+	dp4		oT3.y, VSTMP_REG_POS_TMP, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Y
+	dp4		oT3.z, VSTMP_REG_POS_TMP, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Z
+	dp4		r0.w,  VSTMP_REG_POS_TMP, VSCONST_REG_SHADOWBUFFER_TRANSFORM_W
+
+	;clamp w (q) to 0
+	slt		r1, c0, c0
+	max		r0.w, r0.w, r1.w
+	mov		oT3.w, r0.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_UVTransform.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_UVTransform.vsh
new file mode 100644
index 0000000..407ef5a
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_1Weight_UVTransform.vsh
@@ -0,0 +1,78 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    mov		VSTMP_REG_POS_TMP.w, VSIN_REG_POS.w
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+	; No need to scale by results by vertex weights, since this is a one-weight shader so the weight
+	; is assumed to be 1.0
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp4		oPos.x, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_X
+    dp4		oPos.y, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Y
+    dp4		oPos.z, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Z
+    dp4     oPos.w, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus transform texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+
+	dp4     oT0.x, VSIN_REG_TEXCOORDS0, VSCONST_REG_UV_MAT00								; u' = ( s * mat00.x ) + ( t * mat00.y ) + ( 1.0 * mat00.w )
+	dp4     oT0.y, VSIN_REG_TEXCOORDS0, VSCONST_REG_UV_MAT01								; v' = ( s * mat01.x ) + ( t * mat01.y ) + ( 1.0 * mat01.w )
+	dp4     oT1.x, VSIN_REG_TEXCOORDS1, VSCONST_REG_UV_MAT10								; u' = ( s * mat10.x ) + ( t * mat10.y ) + ( 1.0 * mat10.w )
+	dp4     oT1.y, VSIN_REG_TEXCOORDS1, VSCONST_REG_UV_MAT11								; v' = ( s * mat11.x ) + ( t * mat11.y ) + ( 1.0 * mat11.w )
+	dp4     oT2.x, VSIN_REG_TEXCOORDS2, VSCONST_REG_UV_MAT20								; u' = ( s * mat20.x ) + ( t * mat20.y ) + ( 1.0 * mat20.w )
+	dp4     oT2.y, VSIN_REG_TEXCOORDS2, VSCONST_REG_UV_MAT21								; v' = ( s * mat21.x ) + ( t * mat21.y ) + ( 1.0 * mat21.w )
+	dp4     oT3.x, VSIN_REG_TEXCOORDS3, VSCONST_REG_UV_MAT30								; u' = ( s * mat30.x ) + ( t * mat30.y ) + ( 1.0 * mat30.w )
+	dp4     oT3.y, VSIN_REG_TEXCOORDS3, VSCONST_REG_UV_MAT31								; v' = ( s * mat31.x ) + ( t * mat31.y ) + ( 1.0 * mat31.w )
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight.vsh
new file mode 100644
index 0000000..b9f5417
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight.vsh
@@ -0,0 +1,86 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+    mov     oT2, VSIN_REG_TEXCOORDS2
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+    mov     oT3, VSIN_REG_TEXCOORDS3
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_SBPassThru.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_SBPassThru.vsh
new file mode 100644
index 0000000..aafcaa5
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_SBPassThru.vsh
@@ -0,0 +1,69 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+    ; Copy w
+    mov		VSTMP_REG_POS_ACCUM.w, VSIN_REG_POS.w
+    
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Calculate second set of texture coordinates (into 4th slot) by transforming the world space
+; position into the shadowbuffer space
+;------------------------------------------------------------------------------
+
+	dp4		oT3.x, VSTMP_REG_POS_ACCUM,	VSCONST_REG_SHADOWBUFFER_TRANSFORM_X
+	dp4		oT3.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Y
+	dp4		oT3.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Z
+	dp4		r0.w,  VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_W
+
+	;clamp w (q) to 0
+	slt		r1, c0, c0
+	max		r0.w, r0.w, r1.w
+	mov		oT3.w, r0.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_UVTransform.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_UVTransform.vsh
new file mode 100644
index 0000000..94721ae
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_2Weight_UVTransform.vsh
@@ -0,0 +1,91 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus transform texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+
+	dp4     oT0.x, VSIN_REG_TEXCOORDS0, VSCONST_REG_UV_MAT00								; u' = ( s * mat00.x ) + ( t * mat00.y ) + ( 1.0 * mat00.w )
+	dp4     oT0.y, VSIN_REG_TEXCOORDS0, VSCONST_REG_UV_MAT01								; v' = ( s * mat01.x ) + ( t * mat01.y ) + ( 1.0 * mat01.w )
+	dp4     oT1.x, VSIN_REG_TEXCOORDS1, VSCONST_REG_UV_MAT10								; u' = ( s * mat10.x ) + ( t * mat10.y ) + ( 1.0 * mat10.w )
+	dp4     oT1.y, VSIN_REG_TEXCOORDS1, VSCONST_REG_UV_MAT11								; v' = ( s * mat11.x ) + ( t * mat11.y ) + ( 1.0 * mat11.w )
+	dp4     oT2.x, VSIN_REG_TEXCOORDS2, VSCONST_REG_UV_MAT20								; u' = ( s * mat20.x ) + ( t * mat20.y ) + ( 1.0 * mat20.w )
+	dp4     oT2.y, VSIN_REG_TEXCOORDS2, VSCONST_REG_UV_MAT21								; v' = ( s * mat21.x ) + ( t * mat21.y ) + ( 1.0 * mat21.w )
+	dp4     oT3.x, VSIN_REG_TEXCOORDS3, VSCONST_REG_UV_MAT30								; u' = ( s * mat30.x ) + ( t * mat30.y ) + ( 1.0 * mat30.w )
+	dp4     oT3.y, VSIN_REG_TEXCOORDS3, VSCONST_REG_UV_MAT31								; v' = ( s * mat31.x ) + ( t * mat31.y ) + ( 1.0 * mat31.w )
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight.vsh
new file mode 100644
index 0000000..8ebe97b
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight.vsh
@@ -0,0 +1,96 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+    mov     oT2, VSIN_REG_TEXCOORDS2
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+    mov     oT3, VSIN_REG_TEXCOORDS3
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_SBPassThru.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_SBPassThru.vsh
new file mode 100644
index 0000000..07738aa
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_SBPassThru.vsh
@@ -0,0 +1,77 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+    ; Copy w
+    mov		VSTMP_REG_POS_ACCUM.w, VSIN_REG_POS.w
+    
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Calculate second set of texture coordinates (into 4th slot) by transforming the world space
+; position into the shadowbuffer space
+;------------------------------------------------------------------------------
+
+	dp4		oT3.x, VSTMP_REG_POS_ACCUM,	VSCONST_REG_SHADOWBUFFER_TRANSFORM_X
+	dp4		oT3.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Y
+	dp4		oT3.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Z
+	dp4		r0.w,  VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_W
+
+	;clamp w (q) to 0
+	slt		r1, c0, c0
+	max		r0.w, r0.w, r1.w
+	mov		oT3.w, r0.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_UVTransform.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_UVTransform.vsh
new file mode 100644
index 0000000..4c32c03
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_3Weight_UVTransform.vsh
@@ -0,0 +1,101 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+
+	dp4     oT0.x, VSIN_REG_TEXCOORDS0, VSCONST_REG_UV_MAT00								; u' = ( s * mat00.x ) + ( t * mat00.y ) + ( 1.0 * mat00.w )
+	dp4     oT0.y, VSIN_REG_TEXCOORDS0, VSCONST_REG_UV_MAT01								; v' = ( s * mat01.x ) + ( t * mat01.y ) + ( 1.0 * mat01.w )
+	dp4     oT1.x, VSIN_REG_TEXCOORDS1, VSCONST_REG_UV_MAT10								; u' = ( s * mat10.x ) + ( t * mat10.y ) + ( 1.0 * mat10.w )
+	dp4     oT1.y, VSIN_REG_TEXCOORDS1, VSCONST_REG_UV_MAT11								; v' = ( s * mat11.x ) + ( t * mat11.y ) + ( 1.0 * mat11.w )
+	dp4     oT2.x, VSIN_REG_TEXCOORDS2, VSCONST_REG_UV_MAT20								; u' = ( s * mat20.x ) + ( t * mat20.y ) + ( 1.0 * mat20.w )
+	dp4     oT2.y, VSIN_REG_TEXCOORDS2, VSCONST_REG_UV_MAT21								; v' = ( s * mat21.x ) + ( t * mat21.y ) + ( 1.0 * mat21.w )
+	dp4     oT3.x, VSIN_REG_TEXCOORDS3, VSCONST_REG_UV_MAT30								; u' = ( s * mat30.x ) + ( t * mat30.y ) + ( 1.0 * mat30.w )
+	dp4     oT3.y, VSIN_REG_TEXCOORDS3, VSCONST_REG_UV_MAT31								; v' = ( s * mat31.x ) + ( t * mat31.y ) + ( 1.0 * mat31.w )
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight.vsh
new file mode 100644
index 0000000..98908a0
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight.vsh
@@ -0,0 +1,117 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 4
+    mov     a0.x, VSIN_REG_INDICES.w
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.w, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.w, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.w, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient
+;------------------------------------------------------------------------------
+
+    ; DP normal & light0 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_TMP.w
+
+	; This is where the ambient gets added in.
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR
+
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light1 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_TMP.w
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light2 dir clamp then scale by light color
+	dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2
+	max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_TMP.w
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+
+	; Vertex color attenuation
+	mul		oD0, r11, VSIN_REG_COLOR
+
+;------------------------------------------------------------------------------
+; Copy texture coordinates
+;------------------------------------------------------------------------------
+
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mov     oT2, VSIN_REG_TEXCOORDS2
+    mov     oT3, VSIN_REG_TEXCOORDS3
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight_SBPassThru.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight_SBPassThru.vsh
new file mode 100644
index 0000000..2a8f46d
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_4Weight_SBPassThru.vsh
@@ -0,0 +1,89 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 4
+    mov     a0.x, VSIN_REG_INDICES.w
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.w, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.w, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.w, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+    ; Copy w
+    mov		VSTMP_REG_POS_ACCUM.w, VSIN_REG_POS.w
+    
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Calculate second set of texture coordinates (into 4th slot) by transforming the world space
+; position into the shadowbuffer space
+;------------------------------------------------------------------------------
+
+	dp4		oT3.x, VSTMP_REG_POS_ACCUM,	VSCONST_REG_SHADOWBUFFER_TRANSFORM_X
+	dp4		oT3.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Y
+	dp4		oT3.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_Z
+	dp4		r0.w,  VSTMP_REG_POS_ACCUM, VSCONST_REG_SHADOWBUFFER_TRANSFORM_W
+
+	;clamp w (q) to 0
+	slt		r1, c0, c0
+	max		r0.w, r0.w, r1.w
+	mov		oT3.w, r0.w
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_1Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_1Weight.vsh
new file mode 100644
index 0000000..dc6fa20
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_1Weight.vsh
@@ -0,0 +1,108 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    mov		VSTMP_REG_POS_TMP.w, VSIN_REG_POS.w
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+	; No need to scale by results by vertex weights, since this is a one-weight shader so the weight
+	; is assumed to be 1.0
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp4		oPos.x, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_X
+    dp4		oPos.y, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Y
+    dp4		oPos.z, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_Z
+    dp4     oPos.w, VSTMP_REG_POS_TMP, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+    mov     oT2, VSIN_REG_TEXCOORDS2
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+    mov     oT3, VSIN_REG_TEXCOORDS3
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; Specular calculation
+;------------------------------------------------------------------------------
+
+	; Calculate vector to eye (V)
+	sub		r7.xyz, VSCONST_REG_CAM_POS, VSTMP_REG_POS_TMP
+	
+	; Store Light0 dir.normal off into r0
+	mov		r0.x, VSTMP_REG_NORMAL_ACCUM.x
+
+	; Normalize V
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate H = L + V
+	add		r7, -VSCONST_REG_LIGHT_DIR0, r7
+
+	; Normalize H
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate N.H (don't worry about clamping to zero, since the lit instruction below does this)
+	dp3		r0.y, VSTMP_REG_NORMAL_TMP, r7
+
+	; Move the power term over into r0
+	mov		r0.w, VSCONST_REG_SPECULAR_COLOR.w
+	
+	; Specular lighting calc - (N.H)^pow
+	lit		r1.z, r0
+
+	; Modulate specular color by specular result.
+	mul		oD1.xyz, r1.z, VSCONST_REG_SPECULAR_COLOR.xyz
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_2Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_2Weight.vsh
new file mode 100644
index 0000000..589e314
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_2Weight.vsh
@@ -0,0 +1,121 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+    mov     oT2, VSIN_REG_TEXCOORDS2
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+    mov     oT3, VSIN_REG_TEXCOORDS3
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; Specular calculation
+;------------------------------------------------------------------------------
+
+	; Calculate vector to eye (V)
+	sub		r7.xyz, VSCONST_REG_CAM_POS, VSTMP_REG_POS_ACCUM
+	
+	; Store Light0 dir.normal off into r0
+	mov		r0.x, VSTMP_REG_NORMAL_ACCUM.x
+
+	; Normalize V
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate H = L + V
+	add		r7, -VSCONST_REG_LIGHT_DIR0, r7
+
+	; Normalize H
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate N.H (don't worry about clamping to zero, since the lit instruction below does this)
+	dp3		r0.y, VSTMP_REG_NORMAL_TMP, r7
+
+	; Move the power term over into r0
+	mov		r0.w, VSCONST_REG_SPECULAR_COLOR.w
+	
+	; Specular lighting calc - (N.H)^pow
+	lit		r1.z, r0
+
+	; Modulate specular color by specular result.
+	mul		oD1.xyz, r1.z, VSCONST_REG_SPECULAR_COLOR.xyz
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_3Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_3Weight.vsh
new file mode 100644
index 0000000..8250efe
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_3Weight.vsh
@@ -0,0 +1,131 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient, plus copy texture coordinates
+; (interleaving these helps prevent stalls)
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_ACCUM.x, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0			; Light0 dir.normal
+    dp3		VSTMP_REG_NORMAL_ACCUM.y, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1			; Light1 dir.normal
+	dp3     VSTMP_REG_NORMAL_ACCUM.z, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2			; Light2 dir.normal
+
+	max     VSTMP_REG_NORMAL_ACCUM.x, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_ACCUM.x	; Light0 result clamp
+    max     VSTMP_REG_NORMAL_ACCUM.y, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_ACCUM.y	; Light1 result clamp
+	max     VSTMP_REG_NORMAL_ACCUM.z, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_ACCUM.z	; Light2 result clamp
+
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR												; Accumulate start with ambient
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_ACCUM.x, r11					; Accumulate Light0 result modulated with Light0 color
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_ACCUM.y, r11					; Accumulate Light1 result modulated with Light1 color
+    mov     oT2, VSIN_REG_TEXCOORDS2
+	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_ACCUM.z, r11					; Accumulate Light2 result modulated with Light2 color
+    mov     oT3, VSIN_REG_TEXCOORDS3
+
+	mul		oD0, r11, VSIN_REG_COLOR														; Vertex color attenuation
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; Specular calculation
+;------------------------------------------------------------------------------
+
+	; Calculate vector to eye (V)
+	sub		r7.xyz, VSCONST_REG_CAM_POS, VSTMP_REG_POS_ACCUM
+	
+	; Store Light0 dir.normal off into r0
+	mov		r0.x, VSTMP_REG_NORMAL_ACCUM.x
+
+	; Normalize V
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate H = L + V
+	add		r7, -VSCONST_REG_LIGHT_DIR0, r7
+
+	; Normalize H
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate N.H (don't worry about clamping to zero, since the lit instruction below does this)
+	dp3		r0.y, VSTMP_REG_NORMAL_TMP, r7
+
+	; Move the power term over into r0
+	mov		r0.w, VSCONST_REG_SPECULAR_COLOR.w
+	
+	; Specular lighting calc - (N.H)^pow
+	lit		r1.z, r0
+
+	; Modulate specular color by specular result.
+	mul		oD1.xyz, r1.z, VSCONST_REG_SPECULAR_COLOR.xyz
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_4Weight.vsh b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_4Weight.vsh
new file mode 100644
index 0000000..d9942bb
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVS_VXC_Specular_4Weight.vsh
@@ -0,0 +1,151 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 4
+    mov     a0.x, VSIN_REG_INDICES.w
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.w, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.w, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.w, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+	; Transform normal by weighted matrix
+	dp3     VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, VSTMP_REG_MAT0
+	dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, VSTMP_REG_MAT1
+	dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, -r12.w
+
+;------------------------------------------------------------------------------
+; Multiple directional lights plus ambient
+;------------------------------------------------------------------------------
+
+    ; DP normal & light0 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_TMP.w
+
+	; For specular calculations further down, save this result off into r0.x
+	mov		r0.x, VSTMP_REG_NORMAL_TMP.w
+
+	; This is where the ambient gets added in.
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR
+
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light1 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_TMP.w
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light2 dir clamp then scale by light color (third light currently deactivated).
+;	dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2
+;	max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_TMP.w
+;	mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+
+	; Vertex color attenuation
+	mul		oD0, r11, VSIN_REG_COLOR
+
+;------------------------------------------------------------------------------
+; Specular calculation
+;------------------------------------------------------------------------------
+
+	; Calculate vector to eye (V)
+	sub		r7.xyz, VSCONST_REG_CAM_POS, VSTMP_REG_POS_ACCUM
+	
+	; Normalize V
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate H = L + V
+	add		r7, -VSCONST_REG_LIGHT_DIR0, r7
+
+	; Normalize H
+	dp3		r7.w, r7.xyz, r7.xyz
+	rsq		r1.x, r7.w
+	mul		r7, r7, r1.x
+
+	; Calculate N.H (don't worry about clamping to zero, since the lit instruction below does this)
+	dp3		r0.y, VSTMP_REG_NORMAL_TMP, r7
+
+	; Move the power term over into r0
+	mov		r0.w, VSCONST_REG_SPECULAR_COLOR.w
+	
+	; Specular lighting calc - (N.H)^pow
+	lit		r1.z, r0
+
+	; Modulate specular color by specular result.
+;	mov		oD1.xyz, r1.z
+	mul		oD1.xyz, r1.z, VSCONST_REG_SPECULAR_COLOR.xyz
+
+;------------------------------------------------------------------------------
+; Copy texture coordinates
+;------------------------------------------------------------------------------
+
+    mov     oT0, VSIN_REG_TEXCOORDS0
+    mov     oT1, VSIN_REG_TEXCOORDS1
+    mov     oT2, VSIN_REG_TEXCOORDS2
+    mov     oT3, VSIN_REG_TEXCOORDS3
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVertexShader0.vsh b/Code/Gfx/XBox/NX/WeightedMeshVertexShader0.vsh
new file mode 100644
index 0000000..d68a566
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVertexShader0.vsh
@@ -0,0 +1,142 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight
+    mul     VSTMP_REG_POS_ACCUM.xyz, VSTMP_REG_POS_TMP.xyz, VSIN_REG_WEIGHTS.x
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed normal by weight
+    mul     VSTMP_REG_NORMAL_ACCUM.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSIN_REG_WEIGHTS.x
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight and add to previous
+    mad     VSTMP_REG_POS_ACCUM.xyz, VSTMP_REG_POS_TMP.xyz, VSIN_REG_WEIGHTS.y, VSTMP_REG_POS_ACCUM.xyz
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight
+    mad     VSTMP_REG_NORMAL_ACCUM.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSIN_REG_WEIGHTS.y, VSTMP_REG_NORMAL_ACCUM.xyz
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight and add to previous
+    mad     VSTMP_REG_POS_ACCUM.xyz, VSTMP_REG_POS_TMP.xyz, VSIN_REG_WEIGHTS.z, VSTMP_REG_POS_ACCUM.xyz
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight
+    mad     VSTMP_REG_NORMAL_ACCUM.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSIN_REG_WEIGHTS.z, VSTMP_REG_NORMAL_ACCUM.xyz
+
+    ; Copy w
+    mov		VSTMP_REG_POS_ACCUM.w, VSIN_REG_POS.w
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp4		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dp4		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dp4		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dp4     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, r12.w
+
+;------------------------------------------------------------------------------
+; Transform normal by combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_X
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Y
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Z
+
+;------------------------------------------------------------------------------
+; Single directional light + Ambient
+;------------------------------------------------------------------------------
+
+    ; Normalize
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSTMP_REG_NORMAL_TMP
+    rsq     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP.w
+    mul     VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.w
+
+    ; DP normal & light0 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_TMP.w
+
+	; This is where the ambient gets added in.
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR
+
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light1 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_TMP.w
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light2 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_TMP.w
+;    mad     oD0, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+    mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+
+	; Material color attenuation
+	mul		oD0, r11, VSCONST_REG_MATERIAL_COLOR
+
+;------------------------------------------------------------------------------
+; Copy texture coordinates
+;------------------------------------------------------------------------------
+
+    mov     oT0, VSIN_REG_TEXCOORDS0
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVertexShader1.vsh b/Code/Gfx/XBox/NX/WeightedMeshVertexShader1.vsh
new file mode 100644
index 0000000..82e577a
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVertexShader1.vsh
@@ -0,0 +1,142 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight
+    mul     VSTMP_REG_POS_ACCUM.xyz, VSTMP_REG_POS_TMP.xyz, VSIN_REG_WEIGHTS.x
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed normal by weight
+    mul     VSTMP_REG_NORMAL_ACCUM.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSIN_REG_WEIGHTS.x
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight and add to previous
+    mad     VSTMP_REG_POS_ACCUM.xyz, VSTMP_REG_POS_TMP.xyz, VSIN_REG_WEIGHTS.y, VSTMP_REG_POS_ACCUM.xyz
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight
+    mad     VSTMP_REG_NORMAL_ACCUM.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSIN_REG_WEIGHTS.y, VSTMP_REG_NORMAL_ACCUM.xyz
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+    ; Transform position
+    dp4		VSTMP_REG_POS_TMP.x, VSIN_REG_POS, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.y, VSIN_REG_POS, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp4		VSTMP_REG_POS_TMP.z, VSIN_REG_POS, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight and add to previous
+    mad     VSTMP_REG_POS_ACCUM.xyz, VSTMP_REG_POS_TMP.xyz, VSIN_REG_WEIGHTS.z, VSTMP_REG_POS_ACCUM.xyz
+
+    ; Transform normal
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSIN_REG_NORMAL, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSIN_REG_NORMAL, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSIN_REG_NORMAL, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+    ; Scale transformed point by weight
+    mad     VSTMP_REG_NORMAL_ACCUM.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSIN_REG_WEIGHTS.z, VSTMP_REG_NORMAL_ACCUM.xyz
+
+    ; Copy w
+    mov		VSTMP_REG_POS_ACCUM.w, VSIN_REG_POS.w
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp4		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dp4		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dp4		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dp4     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+;------------------------------------------------------------------------------
+; Deal with fog value (r12 shadows oPos)...
+;------------------------------------------------------------------------------
+
+	mov		oFog.x, r12.w
+
+;------------------------------------------------------------------------------
+; Transform normal by combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dp3		VSTMP_REG_NORMAL_TMP.x, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_X
+    dp3		VSTMP_REG_NORMAL_TMP.y, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Y
+    dp3		VSTMP_REG_NORMAL_TMP.z, VSTMP_REG_NORMAL_ACCUM, VSCONST_REG_WORLD_TRANSFORM_Z
+
+;------------------------------------------------------------------------------
+; Single directional light + Ambient
+;------------------------------------------------------------------------------
+
+    ; Normalize
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSTMP_REG_NORMAL_TMP
+    rsq     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP.w
+    mul     VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.xyz, VSTMP_REG_NORMAL_TMP.w
+
+    ; DP normal & light0 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR0
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR0.w, -VSTMP_REG_NORMAL_TMP.w
+
+	; This is where the ambient gets added in.
+    mov     r11, VSCONST_REG_AMB_LIGHT_COLOR
+
+    mad     r11, VSCONST_REG_LIGHT_COLOR0, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light1 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR1
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR1.w, -VSTMP_REG_NORMAL_TMP.w
+    mad     r11, VSCONST_REG_LIGHT_COLOR1, VSTMP_REG_NORMAL_TMP.w, r11
+
+    ; DP normal & light2 dir clamp then scale by light color
+    dp3     VSTMP_REG_NORMAL_TMP.w, VSTMP_REG_NORMAL_TMP, VSCONST_REG_LIGHT_DIR2
+    max     VSTMP_REG_NORMAL_TMP.w, VSCONST_REG_LIGHT_DIR2.w, -VSTMP_REG_NORMAL_TMP.w
+;    mad     oD0, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+    mad     r11, VSCONST_REG_LIGHT_COLOR2, VSTMP_REG_NORMAL_TMP.w, r11
+
+	; Vertex color attenuation
+	mul		oD0, r11, VSIN_REG_COLOR
+
+;------------------------------------------------------------------------------
+; Copy texture coordinates
+;------------------------------------------------------------------------------
+
+    mov     oT0, VSIN_REG_TEXCOORDS0
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/WeightedMeshVertexShader_SBWrite.vsh b/Code/Gfx/XBox/NX/WeightedMeshVertexShader_SBWrite.vsh
new file mode 100644
index 0000000..c52cfb6
--- /dev/null
+++ b/Code/Gfx/XBox/NX/WeightedMeshVertexShader_SBWrite.vsh
@@ -0,0 +1,60 @@
+xvs.1.1
+
+#include "anim_vertdefs.h"
+
+#pragma screenspace
+
+;------------------------------------------------------------------------------
+; Bone space transforms
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 1
+    mov     a0.x, VSIN_REG_INDICES.x
+
+	; Weight matrix
+	mul		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.x, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.x, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET]
+	mul		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.x, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET]
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 2
+    mov     a0.x, VSIN_REG_INDICES.y
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.y, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.y, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.y, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+
+    ; Get matrix index 3
+    mov     a0.x, VSIN_REG_INDICES.z
+
+	; Weight matrix
+	mad		VSTMP_REG_MAT0, VSIN_REG_WEIGHTS.z, c[0 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT0
+	mad		VSTMP_REG_MAT1, VSIN_REG_WEIGHTS.z, c[1 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT1
+	mad		VSTMP_REG_MAT2, VSIN_REG_WEIGHTS.z, c[2 + a0.x VSCONST_REG_MATRIX_OFFSET], VSTMP_REG_MAT2
+
+	; Transform position by weighted matrix
+    dp4     VSTMP_REG_POS_ACCUM.x, VSIN_REG_POS, VSTMP_REG_MAT0
+	dp4		VSTMP_REG_POS_ACCUM.y, VSIN_REG_POS, VSTMP_REG_MAT1
+	dp4		VSTMP_REG_POS_ACCUM.z, VSIN_REG_POS, VSTMP_REG_MAT2
+
+;------------------------------------------------------------------------------
+; Combined camera & projection matrix
+;------------------------------------------------------------------------------
+
+    dph		oPos.x, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_X
+    dph		oPos.y, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Y
+    dph		oPos.z, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_Z
+    dph     oPos.w, VSTMP_REG_POS_ACCUM, VSCONST_REG_TRANSFORM_W
+
+
+;------------------------------------------------------------------------------
+; oPos to screenspace transformation
+;------------------------------------------------------------------------------
+
+	mul		oPos.xyz, r12, c94			; scale
+	+ rcc	r1.x, r12.w					; compute 1/w
+	mad		oPos.xyz, r12, r1.x, c95	; scale by 1/w, add offset
diff --git a/Code/Gfx/XBox/NX/anim.cpp b/Code/Gfx/XBox/NX/anim.cpp
new file mode 100644
index 0000000..0025752
--- /dev/null
+++ b/Code/Gfx/XBox/NX/anim.cpp
@@ -0,0 +1,498 @@
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "nx_init.h"
+#include "mesh.h"
+#include "scene.h"
+#include "anim.h"
+#include "anim_vertdefs.h"
+
+#include "WeightedMeshVS_VXC_1Weight.h"
+#include "WeightedMeshVS_VXC_2Weight.h"
+#include "WeightedMeshVS_VXC_3Weight.h"
+
+#include "WeightedMeshVS_VXC_Specular_1Weight.h"
+#include "WeightedMeshVS_VXC_Specular_2Weight.h"
+#include "WeightedMeshVS_VXC_Specular_3Weight.h"
+
+#include "WeightedMeshVS_VXC_1Weight_UVTransform.h"
+#include "WeightedMeshVS_VXC_2Weight_UVTransform.h"
+#include "WeightedMeshVS_VXC_3Weight_UVTransform.h"
+
+#include "WeightedMeshVS_VXC_1Weight_SBPassThru.h"
+#include "WeightedMeshVS_VXC_2Weight_SBPassThru.h"
+#include "WeightedMeshVS_VXC_3Weight_SBPassThru.h"
+
+#include "WeightedMeshVertexShader_SBWrite.h"
+#include "ShadowBufferStaticGeomVS.h"
+#include "BillboardScreenAlignedVS.h"
+#include "ParticleFlatVS.h"
+#include "ParticleNewFlatVS.h"
+#include "ParticleNewFlatPointSpriteVS.h"
+
+DWORD WeightedMeshVS_VXC_1Weight;
+DWORD WeightedMeshVS_VXC_2Weight;
+DWORD WeightedMeshVS_VXC_3Weight;
+DWORD WeightedMeshVS_VXC_Specular_1Weight;
+DWORD WeightedMeshVS_VXC_Specular_2Weight;
+DWORD WeightedMeshVS_VXC_Specular_3Weight;
+DWORD WeightedMeshVS_VXC_1Weight_UVTransform;
+DWORD WeightedMeshVS_VXC_2Weight_UVTransform;
+DWORD WeightedMeshVS_VXC_3Weight_UVTransform;
+DWORD WeightedMeshVertexShader_SBWrite;
+DWORD WeightedMeshVS_VXC_1Weight_SBPassThru;
+DWORD WeightedMeshVS_VXC_2Weight_SBPassThru;
+DWORD WeightedMeshVS_VXC_3Weight_SBPassThru;
+DWORD WeightedMeshVertexShader_VXC_SBPassThru;
+DWORD BillboardScreenAlignedVS;
+DWORD ParticleFlatVS;
+DWORD ParticleNewFlatVS;
+DWORD ParticleNewFlatPointSpriteVS;
+DWORD ShadowBufferStaticGeomVS;
+
+namespace NxXbox
+{
+	// Vertex color attenuation, 4 sets of tex coords.
+	static DWORD WeightedMeshVertexShaderVertColUV4Decl[] = {
+	D3DVSD_STREAM( 0 ),
+	D3DVSD_REG( VSD_REG_POS,		D3DVSDT_FLOAT3 ),		// Position.
+	D3DVSD_REG( VSD_REG_WEIGHTS,	D3DVSDT_NORMPACKED3 ),	// Weights.
+	D3DVSD_REG( VSD_REG_INDICES,	D3DVSDT_SHORT4 ),		// Indices.
+	D3DVSD_REG( VSD_REG_NORMAL,		D3DVSDT_NORMPACKED3 ),	// Normals.
+	D3DVSD_REG( VSD_REG_COLOR,		D3DVSDT_D3DCOLOR ),		// Diffuse color.
+	D3DVSD_REG( VSD_REG_TEXCOORDS0,	D3DVSDT_FLOAT2 ),		// Texture coordinates 0.
+	D3DVSD_REG( VSD_REG_TEXCOORDS1,	D3DVSDT_FLOAT2 ),		// Texture coordinates 1.
+	D3DVSD_REG( VSD_REG_TEXCOORDS2,	D3DVSDT_FLOAT2 ),		// Texture coordinates 2.
+	D3DVSD_REG( VSD_REG_TEXCOORDS3,	D3DVSDT_FLOAT2 ),		// Texture coordinates 3.
+	D3DVSD_END() };
+	
+	// Billboards.
+	static DWORD BillboardVSDecl[] = {
+	D3DVSD_STREAM( 0 ),
+	D3DVSD_REG( 0,	D3DVSDT_FLOAT3 ),		// Position (actually pivot position).
+	D3DVSD_REG( 1,	D3DVSDT_FLOAT3 ),		// Normal (actually position of point relative to pivot).
+	D3DVSD_REG( 2,	D3DVSDT_D3DCOLOR ),		// Diffuse color.
+	D3DVSD_REG( 3,	D3DVSDT_FLOAT2 ),		// Texture coordinates 0.
+	D3DVSD_REG( 4,	D3DVSDT_FLOAT2 ),		// Texture coordinates 1.
+	D3DVSD_REG( 5,	D3DVSDT_FLOAT2 ),		// Texture coordinates 2.
+	D3DVSD_REG( 6,	D3DVSDT_FLOAT2 ),		// Texture coordinates 3.
+	D3DVSD_END() };
+
+	// Particles.
+	static DWORD ParticleFlatVSDecl[] = {
+	D3DVSD_STREAM( 0 ),
+	D3DVSD_REG( 0,	D3DVSDT_D3DCOLOR ),		// Diffuse color (start)
+	D3DVSD_REG( 1,	D3DVSDT_D3DCOLOR ),		// Diffuse color (end)
+	D3DVSD_REG( 2,	D3DVSDT_SHORT2 ),		// Indices.
+	D3DVSD_END() };
+
+	// New, Ps2 style particles using PointSprites.
+	static DWORD NewParticleFlatVSDecl[] = {
+	D3DVSD_STREAM( 0 ),
+	D3DVSD_REG( 0,	D3DVSDT_FLOAT4 ),		// Random 4-element 'R' vector.
+	D3DVSD_REG( 1,	D3DVSDT_FLOAT2 ),		// Time and color interpolator.
+	D3DVSD_REG( 2,	D3DVSDT_D3DCOLOR ),		// Diffuse color (start)
+	D3DVSD_REG( 3,	D3DVSDT_D3DCOLOR ),		// Diffuse color (end)
+	D3DVSD_END() };
+
+	// Shadow buffer, static geom.
+	static DWORD ShadowBufferStaticGeomVSDecl[] = {
+	D3DVSD_STREAM( 0 ),
+	D3DVSD_REG( 0,	D3DVSDT_FLOAT3 ),		// Position.
+	D3DVSD_REG( 1,	D3DVSDT_D3DCOLOR ),		// Diffuse color.
+	D3DVSD_REG( 2,	D3DVSDT_FLOAT2 ),		// Texture coordinates 0.
+	D3DVSD_REG( 3,	D3DVSDT_FLOAT2 ),		// Texture coordinates 1.
+	D3DVSD_REG( 4,	D3DVSDT_FLOAT2 ),		// Texture coordinates 2.
+	D3DVSD_END() };
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+DWORD GetVertexShader( bool vertex_colors, bool specular, uint32 max_weights_used )
+{
+	Dbg_Assert( max_weights_used > 0 );
+	
+	if( vertex_colors )
+	{
+		if( max_weights_used == 1 )
+		{
+			return ( specular ) ? WeightedMeshVS_VXC_Specular_1Weight : WeightedMeshVS_VXC_1Weight;
+		}
+		else if( max_weights_used == 2 )
+		{
+			return ( specular ) ? WeightedMeshVS_VXC_Specular_2Weight : WeightedMeshVS_VXC_2Weight;
+		}
+		else if( max_weights_used == 3 )
+		{
+			return ( specular ) ? WeightedMeshVS_VXC_Specular_3Weight : WeightedMeshVS_VXC_3Weight;
+		}
+	}
+
+	Dbg_Assert( 0 );
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CreateWeightedMeshVertexShaders( void )
+{
+	static bool created_shaders = false;
+
+	if( !created_shaders )
+	{
+		created_shaders = true;
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_1WeightVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_1Weight,
+													0 ))
+		{
+			exit( 0 );
+		}
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_2WeightVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_2Weight,
+													0 ))
+		{
+			exit( 0 );
+		}
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_3WeightVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_3Weight,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_Specular_1WeightVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_Specular_1Weight,
+													0 ))
+		{
+			exit( 0 );
+		}
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_Specular_2WeightVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_Specular_2Weight,
+													0 ))
+		{
+			exit( 0 );
+		}
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_Specular_3WeightVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_Specular_3Weight,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_1Weight_UVTransformVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_1Weight_UVTransform,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_2Weight_UVTransformVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_2Weight_UVTransform,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_3Weight_UVTransformVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_3Weight_UVTransform,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVertexShader_SBWriteVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVertexShader_SBWrite,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_1Weight_SBPassThruVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_1Weight_SBPassThru,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_2Weight_SBPassThruVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_2Weight_SBPassThru,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	WeightedMeshVertexShaderVertColUV4Decl,
+													dwWeightedMeshVS_VXC_3Weight_SBPassThruVertexShader,	// Defined in the header file from xsasm.
+													&WeightedMeshVS_VXC_3Weight_SBPassThru,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	BillboardVSDecl,
+													dwBillboardScreenAlignedVSVertexShader,					// Defined in the header file from xsasm.
+													&BillboardScreenAlignedVS,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	ParticleFlatVSDecl,
+													dwParticleFlatVSVertexShader,							// Defined in the header file from xsasm.
+													&ParticleFlatVS,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	ParticleFlatVSDecl,
+													dwParticleNewFlatVSVertexShader,						// Defined in the header file from xsasm.
+													&ParticleNewFlatVS,
+													0 ))
+		{
+			exit( 0 );
+		}
+
+		if( D3D_OK != D3DDevice_CreateVertexShader(	NewParticleFlatVSDecl,
+													dwParticleNewFlatPointSpriteVSVertexShader,				// Defined in the header file from xsasm.
+													&ParticleNewFlatPointSpriteVS,
+													0 ))
+		{
+			exit( 0 );
+		}
+		if( D3D_OK != D3DDevice_CreateVertexShader(	ShadowBufferStaticGeomVSDecl,
+													dwShadowBufferStaticGeomVSVertexShader,					// Defined in the header file from xsasm.
+													&ShadowBufferStaticGeomVS,
+													0 ))
+		{
+			exit( 0 );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void setup_weighted_mesh_vertex_shader( void *p_root_matrix, void *p_bone_matrices, int num_bone_matrices )
+{
+	XGMATRIX	dest_matrix;
+	XGMATRIX	inverse_view_matrix;
+	XGMATRIX	temp_matrix;
+	XGMATRIX	projMatrix;
+	XGMATRIX	viewMatrix;
+	XGMATRIX	worldMatrix;
+
+	// Projection matrix.
+	XGMatrixTranspose( &projMatrix, &EngineGlobals.projection_matrix );
+	
+	// View matrix.
+	XGMatrixTranspose( &viewMatrix, &EngineGlobals.view_matrix );
+    viewMatrix.m[3][0] = 0.0f;
+    viewMatrix.m[3][1] = 0.0f;
+    viewMatrix.m[3][2] = 0.0f;
+    viewMatrix.m[3][3] = 1.0f;
+	
+	// World space transformation matrix. (3x3 rotation plus 3 element translation component).
+	worldMatrix.m[0][0] = ((float*)p_root_matrix )[0];
+	worldMatrix.m[0][1] = ((float*)p_root_matrix )[1];
+	worldMatrix.m[0][2] = ((float*)p_root_matrix )[2];
+	worldMatrix.m[0][3] = ((float*)p_root_matrix )[3];
+	worldMatrix.m[1][0] = ((float*)p_root_matrix )[4];
+	worldMatrix.m[1][1] = ((float*)p_root_matrix )[5];
+	worldMatrix.m[1][2] = ((float*)p_root_matrix )[6];
+	worldMatrix.m[1][3] = ((float*)p_root_matrix )[7];
+	worldMatrix.m[2][0] = ((float*)p_root_matrix )[8];
+	worldMatrix.m[2][1] = ((float*)p_root_matrix )[9];
+	worldMatrix.m[2][2] = ((float*)p_root_matrix )[10];
+	worldMatrix.m[2][3] = ((float*)p_root_matrix )[11];
+	worldMatrix.m[3][0] = 0.0f;
+	worldMatrix.m[3][1] = 0.0f;
+	worldMatrix.m[3][2] = 0.0f;
+	worldMatrix.m[3][3] = 1.0f;
+
+	// Calculate composite world->view->projection matrix.
+	XGMatrixMultiply( &temp_matrix, &viewMatrix, &worldMatrix );
+	XGMatrixMultiply( &dest_matrix, &projMatrix, &temp_matrix );
+
+	// Switch to 192 constant mode, removing the lock on the reserved constants c-38 and c-37.
+//	D3DDevice_SetShaderConstantMode( D3DSCM_192CONSTANTS | D3DSCM_NORESERVEDCONSTANTS );
+
+	// Load up the combined world, camera & projection matrix.
+	D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_TRANSFORM_OFFSET, (void*)&dest_matrix, VSCONST_REG_TRANSFORM_SIZE );
+	D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_WORLD_TRANSFORM_OFFSET,	(void*)&worldMatrix, VSCONST_REG_WORLD_TRANSFORM_SIZE );
+
+	// We want to transform the light directions by the inverse of the world transform - this means we don't have to transform
+	// the normal by the world transform for every vertex in the vertex shader. However, the function D3DXVec3TransformNormal
+	// (used below) does the inverse transform for us, so need to actually figure the inverse...
+//	XGMATRIX inverse_world_transform = worldMatrix;
+//	D3DXMatrixInverse( &inverse_world_transform, NULL, &worldMatrix );
+
+	float directional_light_color[24];
+	CopyMemory( directional_light_color, EngineGlobals.directional_light_color, sizeof( float ) * 24 );
+	
+	XGVec3TransformNormal((XGVECTOR3*)&directional_light_color[0],	(XGVECTOR3*)&EngineGlobals.directional_light_color[0], &worldMatrix );
+	XGVec3TransformNormal((XGVECTOR3*)&directional_light_color[8],	(XGVECTOR3*)&EngineGlobals.directional_light_color[8], &worldMatrix );
+	XGVec3TransformNormal((XGVECTOR3*)&directional_light_color[16],	(XGVECTOR3*)&EngineGlobals.directional_light_color[16], &worldMatrix );
+	
+	XGVec3Normalize((XGVECTOR3*)&directional_light_color[0], (XGVECTOR3*)&directional_light_color[0] ); 
+	XGVec3Normalize((XGVECTOR3*)&directional_light_color[8], (XGVECTOR3*)&directional_light_color[8] ); 
+	XGVec3Normalize((XGVECTOR3*)&directional_light_color[16], (XGVECTOR3*)&directional_light_color[16] ); 
+
+	// Load up the directional light data.
+	D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_DIR_LIGHT_OFFSET, (void*)directional_light_color, 6 );
+
+	// Load up the ambient light color.
+	D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_AMB_LIGHT_OFFSET, (void*)EngineGlobals.ambient_light_color, 1 );
+	
+	// Calculate and load up the model-relative camera position.
+	EngineGlobals.model_relative_cam_position = XGVECTOR3( EngineGlobals.cam_position.x - worldMatrix.m[0][3],
+														   EngineGlobals.cam_position.y - worldMatrix.m[1][3],
+														   EngineGlobals.cam_position.z - worldMatrix.m[2][3] );
+	XGVec3TransformNormal( &EngineGlobals.model_relative_cam_position, &EngineGlobals.model_relative_cam_position, &worldMatrix );
+
+	float specular_attribs[4] = { EngineGlobals.model_relative_cam_position.x,
+								  EngineGlobals.model_relative_cam_position.y,
+								  EngineGlobals.model_relative_cam_position.z,
+								  0.0f };
+	D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_CAM_POS_OFFSET, (void*)&specular_attribs, 1 );
+
+	// Safety check here to limit number of bones to that available.
+	if( num_bone_matrices > 55 )
+	{
+		num_bone_matrices = 55;
+	}
+
+	DWORD*	p_bone_element	= (DWORD*)p_bone_matrices;
+
+	// Begin state block to set vertex shader constants for bone transforms.
+	DWORD *p_push;
+	EngineGlobals.p_Device->BeginState(( num_bone_matrices * ( 12 + 1 )) + 3, &p_push );
+
+	// 1 here isn't the parameter for SET_TRANSFORM_CONSTANT_LOAD; rather, it's the number of dwords written to that register.
+	p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT_LOAD, 1 ); 
+
+	// Here is the actual parameter for SET_TRANSFORM_CONSTANT_LOAD. Always add 96 to the constant register.
+	p_push[1] = VSCONST_REG_MATRIX_OFFSET + 96;
+
+	p_push += 2;
+
+	while( num_bone_matrices > 0 )
+	{
+		// A 3x4 matrix is 12 dwords. You can encode a maximum of 32 dwords to D3DPUSH_SET_TRANSFORM_CONSTANT before needing another D3DPUSH_ENCODE.
+		p_push[0]		= D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT, 12 );
+
+		p_push[1]		= p_bone_element[0];
+		p_push[2]		= p_bone_element[4];
+		p_push[3]		= p_bone_element[8];
+		p_push[4]		= p_bone_element[12];
+
+		p_push[5]		= p_bone_element[1];
+		p_push[6]		= p_bone_element[5];
+		p_push[7]		= p_bone_element[9];
+		p_push[8]		= p_bone_element[13];
+
+		p_push[9]		= p_bone_element[2];
+		p_push[10]		= p_bone_element[6];
+		p_push[11]		= p_bone_element[10];
+		p_push[12]		= p_bone_element[14];
+
+		--num_bone_matrices;
+
+		p_bone_element	+= 16;
+		p_push			+= 13;
+	}
+
+	EngineGlobals.p_Device->EndState( p_push );
+
+	// Load up the replacement registers for c-38 and c-37
+	// The z value is 2^24 - to take 1.0f to the max z buffer value for a 24bit z buffer.
+	static float homogenous_to_screen_reg[8] = { 320.0f, -240.0f, 1.6777215e7f, 0.0f, 320.53125f, 240.53125f, 0.0f, 0.0f };
+	
+	if( EngineGlobals.is_orthographic )
+	{
+		homogenous_to_screen_reg[0] = 128.0f;
+		homogenous_to_screen_reg[1] = -128.0f;
+
+		homogenous_to_screen_reg[2] = 1.6777215e7f;
+		
+		homogenous_to_screen_reg[4] = 128.53125f;
+		homogenous_to_screen_reg[5] = 128.53125f;
+	}
+	else
+	{
+		homogenous_to_screen_reg[0] = (float)EngineGlobals.viewport.Width * 0.5f;
+		homogenous_to_screen_reg[1] = (float)EngineGlobals.viewport.Height * -0.5f;
+
+		homogenous_to_screen_reg[2] = ( EngineGlobals.zstencil_depth == 16 ) ? 65535.0f : 1.6777215e7f;
+
+		homogenous_to_screen_reg[4] = (float)NxXbox::EngineGlobals.viewport.X + ((float)NxXbox::EngineGlobals.viewport.Width * 0.5f ) + 0.53125f;
+		homogenous_to_screen_reg[5] = (float)NxXbox::EngineGlobals.viewport.Y + ((float)NxXbox::EngineGlobals.viewport.Height * 0.5f ) + 0.53125f;
+	}
+
+	D3DDevice_SetVertexShaderConstantFast( 94, (void*)homogenous_to_screen_reg, 2 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void startup_weighted_mesh_vertex_shader( void )
+{
+	// Switch to 192 constant mode, removing the lock on the reserved constants c-38 and c-37.
+	D3DDevice_SetShaderConstantMode( D3DSCM_192CONSTANTS | D3DSCM_NORESERVEDCONSTANTS );
+
+	// Flag the custom pipeline is in operation.
+	EngineGlobals.custom_pipeline_enabled = true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void shutdown_weighted_mesh_vertex_shader( void )
+{
+	// Switch back to 96 constant mode.
+	D3DDevice_SetShaderConstantMode( D3DSCM_96CONSTANTS );
+
+	// Flag the custom pipeline is no longer in operation.
+	EngineGlobals.custom_pipeline_enabled = false;
+}
+
+
+} // namespace NxXbox
\ No newline at end of file
diff --git a/Code/Gfx/XBox/NX/anim.h b/Code/Gfx/XBox/NX/anim.h
new file mode 100644
index 0000000..e5f6628
--- /dev/null
+++ b/Code/Gfx/XBox/NX/anim.h
@@ -0,0 +1,36 @@
+#ifndef __ANIM_H
+#define __ANIM_H
+
+extern DWORD WeightedMeshVS_VXC_1Weight;
+extern DWORD WeightedMeshVS_VXC_2Weight;
+extern DWORD WeightedMeshVS_VXC_3Weight;
+extern DWORD WeightedMeshVS_VXC_Specular_1Weight;
+extern DWORD WeightedMeshVS_VXC_Specular_2Weight;
+extern DWORD WeightedMeshVS_VXC_Specular_3Weight;
+extern DWORD WeightedMeshVS_VXC_1Weight_UVTransform;
+extern DWORD WeightedMeshVS_VXC_2Weight_UVTransform;
+extern DWORD WeightedMeshVS_VXC_3Weight_UVTransform;
+extern DWORD WeightedMeshVS_VXC_1Weight_SBPassThru;
+extern DWORD WeightedMeshVS_VXC_2Weight_SBPassThru;
+extern DWORD WeightedMeshVS_VXC_3Weight_SBPassThru;
+extern DWORD WeightedMeshVertexShader_SBWrite;
+extern DWORD BillboardScreenAlignedVS;
+extern DWORD ParticleFlatVS;
+extern DWORD ParticleNewFlatVS;
+extern DWORD ParticleNewFlatPointSpriteVS;
+extern DWORD ShadowBufferStaticGeomVS;
+
+namespace NxXbox
+{
+
+DWORD	GetVertexShader( bool vertex_colors, bool specular, uint32 max_weights_used );
+void	CreateWeightedMeshVertexShaders( void );
+void	startup_weighted_mesh_vertex_shader( void );
+void	setup_weighted_mesh_vertex_shader( void *p_root_matrix, void *p_bone_matrices, int num_bone_matrices );
+void	shutdown_weighted_mesh_vertex_shader( void );
+
+
+} // namespace NxXbox
+
+#endif // __ANIM_H
+
diff --git a/Code/Gfx/XBox/NX/anim_vertdefs.h b/Code/Gfx/XBox/NX/anim_vertdefs.h
new file mode 100644
index 0000000..4bae48f
--- /dev/null
+++ b/Code/Gfx/XBox/NX/anim_vertdefs.h
@@ -0,0 +1,97 @@
+#ifndef ANIM_VERTDEFS_H
+#define ANIM_VERTDEFS_H
+
+// Used in the vertex shader descriptor.
+#define VSD_REG_POS                     0
+#define VSD_REG_WEIGHTS                 1
+#define VSD_REG_INDICES                 2
+#define VSD_REG_NORMAL                  3
+#define VSD_REG_COLOR                   4
+#define VSD_REG_TEXCOORDS0              5
+#define VSD_REG_TEXCOORDS1              6
+#define VSD_REG_TEXCOORDS2              7
+#define VSD_REG_TEXCOORDS3              8
+
+// Input register - used in the vertex shader code.
+#define VSIN_REG_POS                    v0
+#define VSIN_REG_WEIGHTS                v1
+#define VSIN_REG_INDICES                v2
+#define VSIN_REG_NORMAL                 v3
+#define VSIN_REG_COLOR                  v4
+#define VSIN_REG_TEXCOORDS0             v5
+#define VSIN_REG_TEXCOORDS1             v6
+#define VSIN_REG_TEXCOORDS2             v7
+#define VSIN_REG_TEXCOORDS3             v8
+
+// Temporary register - used in the vertex shader code.
+#define VSTMP_REG_POS_TMP               r0
+#define VSTMP_REG_POS_ACCUM             r1
+#define VSTMP_REG_NORMAL_TMP            r2
+#define VSTMP_REG_NORMAL_ACCUM          r3
+#define VSTMP_REG_MAT0					r8
+#define VSTMP_REG_MAT1					r9
+#define VSTMP_REG_MAT2					r10
+
+// Vertex shader defines.
+#define VSCONST_REG_BASE					-96	// Don't have to worry about the constant -38 and -37, we are not using them.
+
+#define VSCONST_REG_TRANSFORM_OFFSET		VSCONST_REG_BASE
+#define VSCONST_REG_TRANSFORM_SIZE			4
+
+#define VSCONST_REG_WORLD_TRANSFORM_OFFSET	VSCONST_REG_TRANSFORM_OFFSET + VSCONST_REG_TRANSFORM_SIZE
+#define VSCONST_REG_WORLD_TRANSFORM_SIZE	3
+
+#define VSCONST_REG_DIR_LIGHT_OFFSET		VSCONST_REG_WORLD_TRANSFORM_OFFSET + VSCONST_REG_WORLD_TRANSFORM_SIZE
+#define VSCONST_REG_DIR_LIGHT_SIZE			6	// Support for 3 directional lights.
+
+#define VSCONST_REG_AMB_LIGHT_OFFSET		VSCONST_REG_DIR_LIGHT_OFFSET + VSCONST_REG_DIR_LIGHT_SIZE
+#define VSCONST_REG_AMB_LIGHT_SIZE			1
+
+#define VSCONST_REG_CAM_POS_OFFSET			VSCONST_REG_AMB_LIGHT_OFFSET + VSCONST_REG_AMB_LIGHT_SIZE
+#define VSCONST_REG_CAM_POS_SIZE			1
+
+#define VSCONST_REG_SPECULAR_COLOR_OFFSET	VSCONST_REG_CAM_POS_OFFSET + VSCONST_REG_CAM_POS_SIZE
+#define VSCONST_REG_SPECULAR_COLOR_SIZE		1
+
+#define VSCONST_REG_UV_MAT_OFFSET			VSCONST_REG_SPECULAR_COLOR_OFFSET + VSCONST_REG_SPECULAR_COLOR_SIZE
+#define VSCONST_REG_UV_MAT_SIZE				8
+
+
+//#define VSCONST_REG_MATRIX_OFFSET			( VSCONST_REG_UV_MAT_OFFSET + VSCONST_REG_UV_MAT_SIZE )
+#define VSCONST_REG_MATRIX_OFFSET			-72
+
+// Constant registers - used in the vertex shader code.
+#define VSCONST_REG_TRANSFORM_X					c[0 + VSCONST_REG_BASE]
+#define VSCONST_REG_TRANSFORM_Y					c[1 + VSCONST_REG_BASE]
+#define VSCONST_REG_TRANSFORM_Z					c[2 + VSCONST_REG_BASE]
+#define VSCONST_REG_TRANSFORM_W					c[3 + VSCONST_REG_BASE]
+#define VSCONST_REG_WORLD_TRANSFORM_X			c[4 + VSCONST_REG_BASE]
+#define VSCONST_REG_WORLD_TRANSFORM_Y			c[5 + VSCONST_REG_BASE]
+#define VSCONST_REG_WORLD_TRANSFORM_Z			c[6 + VSCONST_REG_BASE]
+
+#define VSCONST_REG_LIGHT_DIR0					c[7 + VSCONST_REG_BASE]
+#define VSCONST_REG_LIGHT_COLOR0				c[8 + VSCONST_REG_BASE]
+#define VSCONST_REG_LIGHT_DIR1					c[9 + VSCONST_REG_BASE]
+#define VSCONST_REG_LIGHT_COLOR1				c[10 + VSCONST_REG_BASE]
+#define VSCONST_REG_LIGHT_DIR2					c[11 + VSCONST_REG_BASE]
+#define VSCONST_REG_LIGHT_COLOR2				c[12 + VSCONST_REG_BASE]
+
+// Shadowbuffer transform shares space with lighting params, since both not required at the same time.
+#define VSCONST_REG_SHADOWBUFFER_TRANSFORM_X	c[7 + VSCONST_REG_BASE]
+#define VSCONST_REG_SHADOWBUFFER_TRANSFORM_Y	c[8 + VSCONST_REG_BASE]
+#define VSCONST_REG_SHADOWBUFFER_TRANSFORM_Z	c[9 + VSCONST_REG_BASE]
+#define VSCONST_REG_SHADOWBUFFER_TRANSFORM_W	c[10 + VSCONST_REG_BASE]
+
+#define VSCONST_REG_AMB_LIGHT_COLOR				c[13 + VSCONST_REG_BASE]
+#define VSCONST_REG_CAM_POS						c[14 + VSCONST_REG_BASE]
+#define VSCONST_REG_SPECULAR_COLOR				c[15 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT00					c[16 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT01					c[17 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT10					c[18 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT11					c[19 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT20					c[20 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT21					c[21 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT30					c[22 + VSCONST_REG_BASE]
+#define VSCONST_REG_UV_MAT31					c[23 + VSCONST_REG_BASE]
+
+#endif // ANIM_VERTDEFS_H
\ No newline at end of file
diff --git a/Code/Gfx/XBox/NX/billboard.cpp b/Code/Gfx/XBox/NX/billboard.cpp
new file mode 100644
index 0000000..ee8cd2e
--- /dev/null
+++ b/Code/Gfx/XBox/NX/billboard.cpp
@@ -0,0 +1,687 @@
+#include "billboard.h"
+#include "nx_init.h"
+
+namespace NxXbox
+{
+
+sBillboardManager BillboardManager;
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sBillboardManager::sBillboardManager( void )
+{
+	m_num_batches	= 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sBillboardManager::~sBillboardManager( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int sort_batches_by_draw_order( const void *p1, const void *p2 )
+{
+	sBillboardMaterialBatch *p_batch0 = *((sBillboardMaterialBatch**)p1 );
+	sBillboardMaterialBatch *p_batch1 = *((sBillboardMaterialBatch**)p2 );
+
+	// Deal with NULL pointers first (caused by removing batches from the list).
+	if( p_batch0 == NULL )
+	{
+		if( p_batch1 == NULL )
+		{
+			return 0;
+		}
+		else
+		{
+			return 1;
+		}
+	}
+	else if( p_batch1 == NULL )
+	{
+		return -1;
+	}
+
+	return p_batch0->GetDrawOrder() < p_batch1->GetDrawOrder() ? -1 : p_batch0->GetDrawOrder() > p_batch1->GetDrawOrder() ? 1 : 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sBillboardMaterialBatch *sBillboardManager::GetBatch( sMaterial *p_material )
+{
+	for( int b = 0; b < m_num_batches; ++b )
+	{
+		if( mp_batches[b]->GetMaterial() == p_material )
+		{
+			return mp_batches[b];
+		}
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardManager::AddEntry( sMesh *p_mesh )
+{
+	if( p_mesh->mp_material )
+	{
+		// Make sure it is textured.
+		if( p_mesh->mp_material->mp_tex[0] != NULL )
+		{
+			sBillboardMaterialBatch *p_batch = GetBatch( p_mesh->mp_material );
+			if( p_batch == NULL )
+			{
+				Dbg_Assert( m_num_batches < ( MAX_BILLBOARD_BATCHES - 1 ));
+
+				// Need to create a new batch for this material.
+				p_batch = new sBillboardMaterialBatch( p_mesh->mp_material );
+				mp_batches[m_num_batches++]	= p_batch;
+
+				// Resort the list of batches.
+				SortBatches();
+			}
+
+			// Now add the mesh to this batch.
+			p_batch->AddEntry( p_mesh );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardManager::RemoveEntry( sMesh *p_mesh )
+{
+	if( p_mesh->mp_material )
+	{
+		sBillboardMaterialBatch *p_batch = GetBatch( p_mesh->mp_material );
+		if( p_batch )
+		{
+			// Removes the entry if it exists.
+			p_batch->RemoveEntry( p_mesh );
+
+			// If the batch is now empty, remove it.
+			if( p_batch->IsEmpty())
+			{
+				for( int i = 0; i < m_num_batches; ++i )
+				{
+					if( mp_batches[i] == p_batch )
+					{
+						delete p_batch;
+						mp_batches[i] = NULL;
+						break;
+					}
+				}
+
+				// Resort the batches (will move the NULL pointer to the end).
+				SortBatches();
+
+				// Important not to decrement this until after the resort has been performed.
+				--m_num_batches;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardManager::SortBatches( void )
+{
+	qsort( mp_batches, m_num_batches, sizeof( sBillboardMaterialBatch* ), sort_batches_by_draw_order );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardManager::SetCameraMatrix( void )
+{
+	Mth::Vector up( 0.0f, 1.0f, 0.0f );
+
+	m_at.Set( NxXbox::EngineGlobals.view_matrix.m[0][2], NxXbox::EngineGlobals.view_matrix.m[1][2], NxXbox::EngineGlobals.view_matrix.m[2][2] );
+	m_screen_right		= Mth::CrossProduct( m_at, up ).Normalize();
+	m_screen_up			= Mth::CrossProduct( m_screen_right, m_at ).Normalize();
+	m_at_xz				= Mth::Vector( m_at[X], 0.0f, m_at[Z] ).Normalize();
+	m_screen_right_xz	= Mth::Vector( m_screen_right[X], 0.0f, m_screen_right[Z] ).Normalize();
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardManager::Render( uint32 flags )
+{
+	// Render the opaque billboards if requested.
+	if( flags & vRENDER_OPAQUE )
+	{
+		for( int b = 0; b < m_num_batches; ++b )
+		{
+			if( !( mp_batches[b]->mp_material->m_flags[0] & 0x40 ))
+			{
+				mp_batches[b]->Render();
+			}
+		}
+	}
+
+	// Render the semitransparent billboards if requested.
+	if( flags & vRENDER_SEMITRANSPARENT )
+	{
+		for( int b = 0; b < m_num_batches; ++b )
+		{
+			if( mp_batches[b]->mp_material->m_flags[0] & 0x40 )
+			{
+				mp_batches[b]->Render();
+			}
+		}
+	}
+
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sBillboardMaterialBatch::sBillboardMaterialBatch( sMaterial *p_material )
+{
+	mp_material		= p_material;
+	mp_entries[0]	= new Lst::Head ;
+	mp_entries[1]	= new Lst::Head ;
+	mp_entries[2]	= new Lst::Head ;
+
+	// We can calculate what the per-entry size will be based on the material properties.
+	m_entry_size	 = 4 * sizeof( float ) * 3;							// Four vertex positions.
+	m_entry_size	+= 4 * sizeof( D3DCOLOR );							// Four vertex colors.
+	m_entry_size	+= 4 * sizeof( float ) * 2 * p_material->m_passes;	// Four uv pairs for each pass.
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sBillboardMaterialBatch::~sBillboardMaterialBatch( void )
+{
+	delete mp_entries[0];
+	delete mp_entries[1];
+	delete mp_entries[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sBillboardMaterialBatch::IsEmpty( void )
+{
+	return ( mp_entries[0]->IsEmpty() && mp_entries[1]->IsEmpty() && mp_entries[2]->IsEmpty());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float sBillboardMaterialBatch::GetDrawOrder( void )
+{
+	return mp_material ? mp_material->m_draw_order : 0.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMaterial *sBillboardMaterialBatch::GetMaterial( void )
+{
+	return mp_material;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardMaterialBatch::AddEntry( sMesh *p_mesh )
+{
+	// Create a new billboard entry.
+	sBillboardEntry *p_entry = new sBillboardEntry( p_mesh );
+
+	// And a new node.
+	Lst::Node *node = new Lst::Node( p_entry );
+
+	mp_entries[p_entry->m_type]->AddToTail( node );
+
+	// Now process the mesh.
+	ProcessMesh( p_mesh );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardMaterialBatch::ProcessMesh( sMesh *p_mesh )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardMaterialBatch::RemoveEntry( sMesh *p_mesh )
+{
+	for( int e = 0; e < 3; ++e )
+	{
+		Lst::Node *node, *next;
+		node = mp_entries[e]->GetNext();
+		while( node )
+		{
+			next = node->GetNext();
+			sBillboardEntry *p_entry = node->GetData();
+			if( p_entry->mp_mesh == p_mesh )
+			{
+				// This is the entry. Delete the node and the entry.
+				delete node;
+				delete p_entry;
+				return;
+			}
+			node = next;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardMaterialBatch::Render( void )
+{
+	static float vector_upload[12] = {	0.0f, 0.0f, 0.0f, 1.0f,
+										0.0f, 0.0f, 0.0f, 1.0f,
+										0.0f, 0.0f, 0.0f, 1.0f };
+
+	if( mp_material )
+	{
+		mp_material->Submit();
+
+		// Set up correct vertex shader.
+		NxXbox::set_vertex_shader( BillboardScreenAlignedVS );
+
+		// Lock out vertex shader changes.
+		EngineGlobals.vertex_shader_override = BillboardScreenAlignedVS;
+
+		// Set up correct pixel shader - this assumes that all meshes with the same material will have the same pixel shader.
+		for( int e = 0; e < 3; ++e )
+		{
+			if( mp_entries[e]->GetNext())
+			{
+				sBillboardEntry *p_entry = mp_entries[e]->GetNext()->GetData();
+				set_pixel_shader( p_entry->mp_mesh->m_pixel_shader );
+				break;
+			}
+		}
+
+		// Load up the combined world->view_projection matrix.
+		XGMATRIX	dest_matrix;
+		XGMATRIX	temp_matrix;
+		XGMATRIX	projMatrix;
+		XGMATRIX	viewMatrix;
+		XGMATRIX	worldMatrix;
+		
+		// Projection matrix.
+		XGMatrixTranspose( &projMatrix, &EngineGlobals.projection_matrix );
+	
+		// View matrix.
+		XGMatrixTranspose( &viewMatrix, &EngineGlobals.view_matrix );
+		viewMatrix.m[3][0] = 0.0f;
+		viewMatrix.m[3][1] = 0.0f;
+		viewMatrix.m[3][2] = 0.0f;
+		viewMatrix.m[3][3] = 1.0f;
+	
+		// World space transformation matrix.
+		XGMatrixIdentity( &worldMatrix );
+
+		// Calculate composite world->view->projection matrix.
+		XGMatrixMultiply( &temp_matrix, &viewMatrix, &worldMatrix );
+		XGMatrixMultiply( &dest_matrix, &projMatrix, &temp_matrix );
+
+		// Load up the combined world, camera & projection matrix.
+		D3DDevice_SetVertexShaderConstantFast( 0, (void*)&dest_matrix, 4 );
+
+		Lst::Node *node, *next;
+
+		// First do the screen aligned billboards.
+		vector_upload[0]	= BillboardManager.m_screen_right[X];
+		vector_upload[1]	= BillboardManager.m_screen_right[Y];
+		vector_upload[2]	= BillboardManager.m_screen_right[Z];
+		vector_upload[4]	= BillboardManager.m_screen_up[X];
+		vector_upload[5]	= BillboardManager.m_screen_up[Y];
+		vector_upload[6]	= BillboardManager.m_screen_up[Z];
+		vector_upload[8]	= BillboardManager.m_at[X];
+		vector_upload[9]	= BillboardManager.m_at[Y];
+		vector_upload[10]	= BillboardManager.m_at[Z];
+		D3DDevice_SetVertexShaderConstantFast( 4, (void*)( &vector_upload[0] ), 3 );
+
+		for( node = mp_entries[0]->GetNext(); node; node = next )
+		{
+			next = node->GetNext();
+			sBillboardEntry *p_entry = node->GetData();
+
+			// Only render if the mesh is active.
+			if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE )
+			{
+				// Deal with material color override.
+				if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE )
+					p_entry->mp_mesh->HandleColorOverride();
+				else
+					set_pixel_shader( p_entry->mp_mesh->m_pixel_shader );
+
+				// Deal with vertex color wibble if present.
+				if( p_entry->mp_mesh->mp_vc_wibble_data )
+				{
+					p_entry->mp_mesh->wibble_vc();
+					IDirect3DVertexBuffer8*	p_submit_buffer = p_entry->mp_mesh->mp_vertex_buffer[p_entry->mp_mesh->m_current_write_vertex_buffer];
+					p_entry->mp_mesh->SwapVertexBuffers();
+					D3DDevice_SetStreamSource( 0, p_submit_buffer, p_entry->mp_mesh->m_vertex_stride );
+					D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] );
+				}
+				else
+				{
+					D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride );
+					D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] );
+				}
+			}
+		}
+
+		// Next do the the y axis aligned billboards.
+		vector_upload[0]	= BillboardManager.m_screen_right_xz[X];
+		vector_upload[1]	= BillboardManager.m_screen_right_xz[Y];
+		vector_upload[2]	= BillboardManager.m_screen_right_xz[Z];
+		vector_upload[4]	= 0.0f;
+		vector_upload[5]	= 1.0f;
+		vector_upload[6]	= 0.0f;
+		vector_upload[8]	= BillboardManager.m_at_xz[X];
+		vector_upload[9]	= BillboardManager.m_at_xz[Y];
+		vector_upload[10]	= BillboardManager.m_at_xz[Z];
+		D3DDevice_SetVertexShaderConstantFast( 4, (void*)vector_upload, 3 );
+
+		for( node = mp_entries[1]->GetNext(); node; node = next )
+		{
+			next = node->GetNext();
+			sBillboardEntry *p_entry = node->GetData();
+
+			// Only render if the mesh is active.
+			if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE )
+			{
+				// Deal with material color override.
+				if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE )
+					p_entry->mp_mesh->HandleColorOverride();
+				else
+					set_pixel_shader( p_entry->mp_mesh->m_pixel_shader );
+
+				// Deal with vertex color wibble if present.
+				if( p_entry->mp_mesh->mp_vc_wibble_data )
+				{
+					p_entry->mp_mesh->wibble_vc();
+					IDirect3DVertexBuffer8*	p_submit_buffer = p_entry->mp_mesh->mp_vertex_buffer[p_entry->mp_mesh->m_current_write_vertex_buffer];
+					p_entry->mp_mesh->SwapVertexBuffers();
+					D3DDevice_SetStreamSource( 0, p_submit_buffer, p_entry->mp_mesh->m_vertex_stride );
+					D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] );
+				}
+				else
+				{
+					D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride );
+					D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] );
+				}
+			}
+		}
+
+		// Now do the arbitrary axis aligned billboards.
+		for( node = mp_entries[2]->GetNext(); node; node = next )
+		{
+			next = node->GetNext();
+			sBillboardEntry *p_entry		= node->GetData();
+			sBillboardData *p_entry_data	= p_entry->mp_mesh->mp_billboard_data;
+		
+			// Only render if the mesh is active.
+			if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE )
+			{
+				// For this type we need to calculate the vector perpendicular to both the view vector and the axis of rotation.
+				Mth::Vector axis_right = Mth::CrossProduct( BillboardManager.m_at, p_entry_data->m_pivot_axis ).Normalize();
+				Mth::Vector axis_at = Mth::CrossProduct( p_entry_data->m_pivot_axis, axis_right ).Normalize();
+
+				// Begin state block to set vertex shader constants for bone transforms.
+				DWORD *p_push;
+				EngineGlobals.p_Device->BeginState( 15, &p_push );
+
+				// 1 here isn't the parameter for SET_TRANSFORM_CONSTANT_LOAD; rather, it's the number of dwords written to that register.
+				p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT_LOAD, 1 ); 
+
+				// Here is the actual parameter for SET_TRANSFORM_CONSTANT_LOAD. Always add 96 to the constant register.
+				p_push[1]	= 4 + 96;
+				p_push[2]	= D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT, 12 );
+
+				p_push[3]	= *((DWORD*)&axis_right[X] );
+				p_push[4]	= *((DWORD*)&axis_right[Y] );
+				p_push[5]	= *((DWORD*)&axis_right[Z] );
+
+				p_push[7]	= *((DWORD*)&p_entry_data->m_pivot_axis[X] );
+				p_push[8]	= *((DWORD*)&p_entry_data->m_pivot_axis[Y] );
+				p_push[9]	= *((DWORD*)&p_entry_data->m_pivot_axis[Z] );
+
+				p_push[11]	= *((DWORD*)&axis_at[X] );
+				p_push[12]	= *((DWORD*)&axis_at[Y] );
+				p_push[13]	= *((DWORD*)&axis_at[Z] );
+
+				p_push		+= 15;
+				EngineGlobals.p_Device->EndState( p_push );
+
+				// Deal with material color override.
+				if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE )
+					p_entry->mp_mesh->HandleColorOverride();
+				else
+					set_pixel_shader( p_entry->mp_mesh->m_pixel_shader );
+
+				// Deal with vertex color wibble if present.
+				if( p_entry->mp_mesh->mp_vc_wibble_data )
+				{
+					p_entry->mp_mesh->wibble_vc();
+					IDirect3DVertexBuffer8*	p_submit_buffer = p_entry->mp_mesh->mp_vertex_buffer[p_entry->mp_mesh->m_current_write_vertex_buffer];
+					p_entry->mp_mesh->SwapVertexBuffers();
+					D3DDevice_SetStreamSource( 0, p_submit_buffer, p_entry->mp_mesh->m_vertex_stride );
+					D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] );
+				}
+				else
+				{
+					D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride );
+					D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] );
+				}
+			}
+		}
+
+		// Finally do the world aligned billboards.
+//		for( node = mp_entries[3]->GetNext(); node; node = next )
+//		{
+//			next = node->GetNext();
+//			sBillboardEntry *p_entry = node->GetData();
+		
+			// Only render if the mesh is active.
+//			if( p_entry->mp_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE )
+//			{
+				// For this type we need to calculate the vector perpendicular to both the view vector and the axis of rotation.
+//				Mth::Vector at( p_entry->m_pivot_axis[X] - EngineGlobals.cam_position.x,
+//								p_entry->m_pivot_axis[Y] - EngineGlobals.cam_position.y,
+//								p_entry->m_pivot_axis[Z] - EngineGlobals.cam_position.z );
+//				at.Normalize();
+
+//				Mth::Vector axis_right = Mth::CrossProduct( at, BillboardManager.m_screen_up ).Normalize();
+//				Mth::Vector axis_at = Mth::CrossProduct( BillboardManager.m_screen_up, axis_right ).Normalize();
+
+//				vector_upload[0]	= axis_right[X];
+//				vector_upload[1]	= axis_right[Y];
+//				vector_upload[2]	= axis_right[Z];
+//				vector_upload[4]	= BillboardManager.m_screen_up[X];
+//				vector_upload[5]	= BillboardManager.m_screen_up[Y];
+//				vector_upload[6]	= BillboardManager.m_screen_up[Z];
+//				vector_upload[8]	= axis_at[X];
+//				vector_upload[9]	= axis_at[Y];
+//				vector_upload[10]	= axis_at[Z];
+//				D3DDevice_SetVertexShaderConstant( 4, (void*)vector_upload, 3 );
+
+//				D3DDevice_SetStreamSource( 0, p_entry->mp_mesh->mp_vertex_buffer[0], p_entry->mp_mesh->m_vertex_stride );
+//				D3DDevice_DrawIndexedVertices( p_entry->mp_mesh->m_primitive_type, p_entry->mp_mesh->m_num_indices[0], p_entry->mp_mesh->mp_index_buffer[0] );
+//			}
+//		}
+
+		// Undo vertex shader lock.
+		EngineGlobals.vertex_shader_override = 0;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sBillboardMaterialBatch::Reset( void )
+{
+	Lst::Node *node, *next;
+
+	for( int e = 0; e < 3; ++e )
+	{
+		if( mp_entries[e] )
+		{
+			for( node = mp_entries[e]->GetNext(); node; node = next )
+			{
+				next = node->GetNext();
+				delete node;
+			}
+		}
+	}
+
+	mp_material		= NULL;
+	m_entry_size	= 0;
+}
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sBillboardEntry::sBillboardEntry( sMesh *p_mesh )
+{
+	Dbg_Assert( p_mesh->mp_billboard_data != NULL );
+
+	mp_mesh			= p_mesh;
+	m_type			= p_mesh->mp_billboard_data->m_type;
+
+	// For now set the pivot point as the center of the bounding sphere.
+//	m_pivot_pos.Set( p_mesh->m_sphere_center.x, p_mesh->m_sphere_center.y, p_mesh->m_sphere_center.z );
+
+	// Frig the verts for now.
+//	m_verts[0][0]	= -12.0f;
+//	m_verts[0][1]	= -24.0f;
+//	m_verts[0][2]	= 0.0f;
+
+//	m_verts[1][0]	= -12.0f;
+//	m_verts[1][1]	= 24.0f;
+//	m_verts[1][2]	= 0.0f;
+
+//	m_verts[2][0]	= 12.0f;
+//	m_verts[2][1]	= 24.0f;
+//	m_verts[2][2]	= 0.0f;
+
+//	m_verts[3][0]	= 12.0f;
+//	m_verts[3][1]	= -24.0f;
+//	m_verts[3][2]	= 0.0f;
+
+
+//	switch( rand() & 0x03 )
+//	{
+//		case 0:
+//		{
+//			m_type = vBILLBOARD_TYPE_SCREEN_ALIGNED;
+//			break;
+//		}
+//		case 1:
+//		{
+//			m_type = vBILLBOARD_TYPE_Y_AXIS_ALIGNED;
+//			break;
+//		}
+//		case 2:
+//		case 3:
+//		{
+//			m_type = vBILLBOARD_TYPE_ARBITRARY_AXIS_ALIGNED;
+//			m_pivot_axis = Mth::Vector((float)( rand() - rand()), (float)( rand() - rand()), (float)( rand() - rand())).Normalize();
+//			break;
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sBillboardEntry::~sBillboardEntry( void )
+{
+}
+
+
+
+
+
+
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/billboard.h b/Code/Gfx/XBox/NX/billboard.h
new file mode 100644
index 0000000..a1a3e48
--- /dev/null
+++ b/Code/Gfx/XBox/NX/billboard.h
@@ -0,0 +1,98 @@
+#ifndef __BILLBOARD_H
+#define __BILLBOARD_H
+
+
+#include 
+#include "render.h"
+
+namespace NxXbox
+{
+
+/******************************************************************/
+/*                                                                */
+/* A single billboard entry.									  */
+/*                                                                */
+/******************************************************************/
+struct sBillboardEntry
+{
+	sBillboardData::EBillboardType	m_type;
+	sMesh							*mp_mesh;
+//	Mth::Vector						m_pivot_pos;
+//	Mth::Vector						m_pivot_axis;	// Normalised axis of rotation, valid only for type ARBITRARY_AXIS_ALIGNED.
+//	float							m_verts[4][3];	// Verts defined as an offset from the pivot point.
+
+									sBillboardEntry( sMesh *p_mesh );
+									~sBillboardEntry( void );
+};
+	
+
+	
+/******************************************************************/
+/*                                                                */
+/* Stores information about a billboard batch, effectively a set  */
+/* of billboards with the same material.                          */
+/*                                                                */
+/******************************************************************/
+struct sBillboardMaterialBatch
+{
+	int							m_entry_size;		// Push buffer size of each billboard entry (bytes).
+	sMaterial					*mp_material;
+	Lst::Head 	*mp_entries[3];		// One entry each for screen aligned, y axis aligned, and arbitrary aligned.
+
+								sBillboardMaterialBatch( sMaterial *p_material );
+								~sBillboardMaterialBatch( void );
+
+	bool						IsEmpty( void );
+	float						GetDrawOrder( void );
+	sMaterial					*GetMaterial( void );
+	void						AddEntry( sMesh *p_mesh );
+	void						RemoveEntry( sMesh *p_mesh );
+	void						ProcessMesh( sMesh *p_mesh );
+	void						Render( void );
+	void						Reset( void );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sBillboardManager
+{
+	enum
+	{
+				MAX_BILLBOARD_BATCHES	= 256
+	};
+
+	int							m_num_batches;
+	sBillboardMaterialBatch		*mp_batches[MAX_BILLBOARD_BATCHES];
+
+	Mth::Vector					m_at;
+	Mth::Vector					m_screen_right;
+	Mth::Vector					m_screen_up;
+	Mth::Vector					m_at_xz;
+	Mth::Vector					m_screen_right_xz;
+
+								sBillboardManager( void );
+								~sBillboardManager( void );
+
+	void						Render( uint32 flags );
+	void						SetCameraMatrix( void );
+	sBillboardMaterialBatch		*GetBatch( sMaterial *p_material );
+	void						AddEntry( sMesh *p_mesh );
+	void						RemoveEntry( sMesh *p_mesh );
+	void						SortBatches( void );
+};
+
+
+extern sBillboardManager BillboardManager;
+
+
+	
+
+} // namespace NxXbox
+
+
+#endif // __BILLBOARD_H
+
diff --git a/Code/Gfx/XBox/NX/chars.cpp b/Code/Gfx/XBox/NX/chars.cpp
new file mode 100644
index 0000000..99f31bb
--- /dev/null
+++ b/Code/Gfx/XBox/NX/chars.cpp
@@ -0,0 +1,919 @@
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "nx_init.h"
+#include "render.h"
+#include "chars.h"
+#include "xbmemfnt.h"
+
+
+/*
+
+
+	
+.fnt file format (by Ryan)
+--------------------------
+
+	4	File size in bytes
+	4	Number of characters
+	4	Default height
+	4	Default base
+	
+	?	Character table (see below)
+	?	Texture (see below)
+
+	Character
+	2	Baseline (how many pixels down relative to top of image)
+	2	Ascii value
+
+	Texture
+	4	Size of texture
+	2	Width
+	2	Height
+	2	Bit depth
+	6	Padding
+	W*H	Raw data
+	0-3	Padding for uint32 alignment
+	1K	Palette data
+	4	Number of subtextures
+	?	Subtexture table (see below)
+
+	Subtexture
+	2	X
+	2	Y
+	2	W
+	2	H
+	
+*/
+
+
+namespace NxXbox
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+typedef struct
+{
+	float		x, y, z;
+	float		rhw;
+	D3DCOLOR	col;
+	float		u, v;
+}
+sFontVert;
+
+
+SFont			*pFontList;
+SFont			*pButtonsFont				= NULL;
+SFont			*SText::spOverrideFont		= NULL;
+
+const uint32	CHARS_PER_BUFFER			= 256;
+static int		font_vertex_offset			= 0;
+static BYTE*	p_locked_font_vertex_buffer	= NULL;
+static BYTE		font_vertex_buffer[CHARS_PER_BUFFER * 4 * sizeof( sFontVert )];
+
+static uint32	swizzle_table[4096];
+static bool		swizzle_table_generated		= false;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void generateSwizzleTable( void )
+{
+	if( !swizzle_table_generated )
+	{
+		for( uint32 i = 0, value = 0; i < 4096; i++ )
+		{
+			swizzle_table[i] = value;
+			value += 0x2AAAAAAB;
+			value &= 0x55555555;
+		}
+		swizzle_table_generated = true;
+	}
+}
+
+
+
+#define TWIDDLE(_u, _v) ((swizzle_table[(_v)] << 1) | (swizzle_table[(_u)]))
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SwizzleTexture( void *dstBuffer, void *srcBuffer, int width, int height, int32 depth, int32 stride )
+{
+    int32 tilesX, tilesY;
+    int32 tilesSizeX, tilesSizeY;
+    int32 tileSize;
+
+	generateSwizzleTable();
+
+	if( width > height )
+    {
+        tilesX = width / height;
+        tilesY = 1;
+
+        tilesSizeX = width / tilesX;
+        tilesSizeY = height;
+    }
+    else
+    {
+        tilesX = 1;
+        tilesY = height / width;
+
+        tilesSizeX = width;
+        tilesSizeY = height / tilesY;
+    }
+
+    tileSize = tilesSizeX * tilesSizeY;
+
+	switch (depth)
+	{
+		case 4:
+	    case 8:
+        {
+            int32 j;
+
+            for (j = 0; j < tilesY; j++)
+            {
+                int32 i;
+
+                for (i = 0; i < tilesX; i++)
+                {
+            int32 y;
+                    uint8 *base;
+
+                    base = (uint8 *)(((uint8 *)dstBuffer) +
+                                       ((tileSize * tilesX) * j) +
+                                       (tileSize * i));
+
+                    for (y = 0; y < tilesSizeY; y++)
+            {
+                uint8    *srcPixel;
+                int32     x;
+
+                        srcPixel = (uint8 *)(((uint8 *)srcBuffer) +
+                                               (stride * (tilesSizeY * j)) +
+                                               (tilesSizeX * i) +
+                                               (stride * y));
+
+                        for (x = 0; x < tilesSizeX; x++)
+                {
+                    uint8    *dstPixel;
+                        dstPixel = (uint8 *)(base + TWIDDLE(x, y));
+		                *dstPixel = *srcPixel;
+
+                    srcPixel++;
+                }
+            }
+        }
+            }
+        }
+        break;
+
+    case 16:
+        {
+            int32 j;
+
+            for (j = 0; j < tilesY; j++)
+            {
+                int32 i;
+
+                for (i = 0; i < tilesX; i++)
+                {
+            int32 y;
+                    uint8 *base;
+
+                    base = (uint8 *)(((uint16 *)dstBuffer) +
+                                       ((tileSize * tilesX) * j) +
+                                       (tileSize * i));
+
+                    for (y = 0; y < tilesSizeY; y++)
+            {
+                uint16    *srcPixel;
+                int32     x;
+
+                        srcPixel = (uint16 *)(((uint8 *)srcBuffer) +
+                                                (stride * (tilesSizeY * j)) +
+                                                (2 * tilesSizeX * i) +
+                                                (stride * y));
+
+                        for (x = 0; x < tilesSizeX; x++)
+                {
+                    uint16    *dstPixel;
+                    dstPixel = (uint16 *)(base + (TWIDDLE(x, y) << 1));
+                    *dstPixel = *srcPixel;
+
+                    srcPixel++;
+                }
+            }
+        }
+            }
+        }
+        break;
+
+    case 24:
+    case 32:
+        {
+            int32 j;
+
+            for (j = 0; j < tilesY; j++)
+            {
+                int32 i;
+
+                for (i = 0; i < tilesX; i++)
+                {
+            int32 y;
+                    uint8 *base;
+
+                    base = (uint8 *)(((uint32 *)dstBuffer) +
+                                       ((tileSize * tilesX) * j) +
+                                       (tileSize * i));
+
+                    for (y = 0; y < tilesSizeY; y++)
+            {
+                uint32    *srcPixel;
+                int32     x;
+
+                        srcPixel = (uint32 *)(((uint8 *)srcBuffer) +
+                                                (stride * (tilesSizeY * j)) +
+                                                (4 * tilesSizeX * i) +
+                                                (stride * y));
+
+                        for (x = 0; x < tilesSizeX; x++)
+                {
+                    uint32    *dstPixel;
+                    dstPixel = (uint32 *)(base + (TWIDDLE(x, y) << 2));
+                    *dstPixel = *srcPixel;
+
+                    srcPixel++;
+                }
+            }
+        }
+            }
+        }
+        break;
+
+    default:
+		exit( 0 );
+        break;
+    }
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SFont* InitialiseMemoryResidentFont( void )
+{
+	return LoadFont((const char*)xbmemfnt, true );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SFont* LoadFont( const char *Filename, bool memory_resident )
+{
+	SFont*	pFont;
+	SChar*	pChar;
+	uint8*	pData;
+	void*	p_FH;
+	int		i,Len,NumChars,Width,Height,Depth,NumBytes;
+
+	// Build the full filename.
+	char filename[128];
+
+	if( !memory_resident )
+	{
+		strcpy( filename, "fonts/" );
+		strcat( filename, Filename );
+		strcat( filename, ".fnt.xbx" );
+	}
+	
+	// Open the font file.
+	if( !memory_resident )
+		p_FH = File::Open( filename, "rb" );
+
+	// Allocate memory for the font structure.
+	pFont = new SFont();
+
+	// Allocate a temporary buffer.
+	uint8 FontBuf[2048];
+
+	// Load file header.
+	if( !memory_resident )
+	{
+		Len = File::Read( FontBuf, 16, 1, p_FH );
+		Dbg_MsgAssert( Len == 16, ( "couldn't read file header from font file %s", Filename ));
+	}
+	else
+	{
+		CopyMemory( FontBuf, Filename, 16 );
+		Filename += 16;
+	}
+
+	NumChars			 = ((uint32 *)FontBuf)[1];
+	pFont->DefaultHeight = ((uint32 *)FontBuf)[2];
+	pFont->DefaultBase	 = ((uint32 *)FontBuf)[3];
+
+	// Clear character map to zero.
+	memset( pFont->Map, 0, 256 );
+	memset( pFont->SpecialMap, 0, 32 );
+
+	// Allocate memory for character table.
+	pFont->pChars = new SChar[NumChars];
+
+	// Load character map and character table.
+	if( !memory_resident )
+	{
+		Len = File::Read( FontBuf, NumChars << 2, 1, p_FH );
+		Dbg_MsgAssert( Len == ( NumChars << 2 ), ( "couldn't read character table in font file %s", Filename ));
+	}
+	else
+	{
+		CopyMemory( FontBuf, Filename, NumChars << 2 );
+		Filename += NumChars << 2;
+	}
+
+	for( i = 0, pChar = pFont->pChars, pData = FontBuf; i < NumChars; i++,pChar++,pData += 4 )
+	{
+		pChar->Baseline							= ((uint16 *)pData)[0];
+		sint16 ascii_val = ((sint16 *)pData)[1];
+		if (ascii_val >= 0)
+			pFont->Map[(uint8) ascii_val] = i;
+		else
+		{
+			Dbg_Assert(ascii_val >= -32)
+			pFont->SpecialMap[(uint8) (-ascii_val - 1)] = i;
+		}
+	}
+
+	// If there is a null character in the font, make characters that could not be found
+	// in the font display that instead of 'A'
+	if( pFont->SpecialMap[31] != 0 )
+	{
+		for( i = 0; i < 256; ++i ) 
+		{
+			if( pFont->Map[i] == 0 && i != 'A' && i != 'a')
+				pFont->Map[i] = pFont->SpecialMap[31];
+
+			if( i < 31 && pFont->SpecialMap[i] == 0 )
+				pFont->SpecialMap[i] = pFont->SpecialMap[31];
+		}
+	}	
+	
+	// Load texture header.
+	if( !memory_resident )
+	{
+		Len = File::Read( FontBuf, 16, 1, p_FH );
+		Dbg_MsgAssert( Len == 16, ( "couldn't read texture header from font file %s", Filename ));
+	}
+	else
+	{
+		CopyMemory( FontBuf, Filename, 16 );
+		Filename += 16;
+	}
+
+	Width	= ((uint16 *)FontBuf)[2];
+	Height	= ((uint16 *)FontBuf)[3];
+	Depth	= ((uint16 *)FontBuf)[4];
+
+	// Create texture.
+	Dbg_Assert( Depth == 8 );
+	if( D3D_OK != D3DDevice_CreateTexture(	Width, Height, 1, 0, D3DFMT_P8, 0, &pFont->pD3DTexture ))
+	{
+		Dbg_Assert( 0 );
+		return NULL;
+	}
+	
+	// Read texture bitmap data (into temp buffer so we can then swizzle it).
+	NumBytes = ( Width * Height + 3 ) & 0xFFFFFFFC;
+
+	uint8* p_temp_texel_data = new uint8[NumBytes];
+	if( !memory_resident )
+	{
+		Len = File::Read( p_temp_texel_data, NumBytes, 1, p_FH );
+		Dbg_MsgAssert( Len == NumBytes, ( "Couldn't read texture bitmap from font file %s", Filename ));
+	}
+	else
+	{
+		CopyMemory( p_temp_texel_data, Filename, NumBytes );
+		Filename += NumBytes;
+	}
+
+	// Lock the texture so we can swizzle into it directly.
+	D3DLOCKED_RECT locked_rect;
+	if( D3D_OK != pFont->pD3DTexture->LockRect( 0, &locked_rect, NULL, 0 ))
+	{
+		Dbg_Assert( 0 );
+		return NULL;
+	}
+	
+	// Swizzle the texture data.
+	SwizzleTexture( locked_rect.pBits, p_temp_texel_data, Width, Height, 8, Width );
+
+	// No longer need this data.
+	delete[] p_temp_texel_data;	
+	
+	// Create palette.
+	if( D3D_OK != D3DDevice_CreatePalette(	D3DPALETTE_256,	&pFont->pD3DPalette ))
+	{
+		Dbg_Assert( 0 );
+		return NULL;
+	}
+	
+	// Read clut bitmap data.
+	D3DCOLOR *p_clut;
+	pFont->pD3DPalette->Lock( &p_clut, 0 );
+
+	if( !memory_resident )
+	{
+		Len	= File::Read( p_clut, 1024, 1, p_FH );
+		Dbg_MsgAssert( Len == 1024, ( "couldn't read clut bitmap from font file %s", Filename ));
+	}
+	else
+	{
+		CopyMemory( p_clut, Filename, 1024 );
+		Filename += 1024;
+	}
+
+	// Switch from RGBA to BGRA format palette.
+	for( i = 0; i < 256; ++i )
+	{
+		uint32	red = p_clut[i] & 0xFF;
+		uint32	blu = ( p_clut[i] >> 16 ) & 0xFF;
+
+		// Double the alpha in the clut (currently limited to 0x80).
+		uint32 alpha = p_clut[i] >> 24;
+		alpha = ( alpha >= 0x80 ) ? 0xFF : ( alpha * 2 );
+		p_clut[i] = ( alpha << 24 ) | ( p_clut[i] & 0x0000FF00 ) | ( red << 16 ) | ( blu );
+	}
+
+	// Skip numsubtextures, and load subtextures.
+	if( !memory_resident )
+	{
+		Len = File::Read( FontBuf, ( NumChars << 3 ) + 4, 1, p_FH );
+		Dbg_MsgAssert( Len == ( NumChars << 3 ) + 4, ( "couldn't read subtexture table from font file %s", Filename ));
+	}
+	else
+	{
+		CopyMemory( FontBuf, Filename, ( NumChars << 3 ) + 4 );
+		Filename += ( NumChars << 3 ) + 4;
+	}
+
+	for( i = 0, pChar = pFont->pChars, pData = FontBuf + 4; i < NumChars; i++, pChar++, pData += 8 )
+	{
+		uint16 x	= ((uint16 *)pData )[0];
+		uint16 y	= ((uint16 *)pData )[1];
+		uint16 w	= ((uint16 *)pData )[2];
+		uint16 h	= ((uint16 *)pData )[3];
+		
+		pChar->w	= w;
+		pChar->h	= h;
+		pChar->u0	= (float)x / (float)Width;
+		pChar->v0	= (float)y / (float)Height;
+		pChar->u1	= pChar->u0 + ((float)w / (float)Width );
+		pChar->v1	= pChar->v0 + ((float)h / (float)Height );
+	}
+
+	// Add font to font list.
+	pFont->pNext	= pFontList;
+	pFontList		= pFont;
+
+	// We're done with the font file now.
+	if( !memory_resident )
+		File::Close( p_FH );
+	
+	// this will serve as the default spacing
+	pFont->mSpaceSpacing = pFont->pChars[pFont->Map['I']].w;
+	
+	return pFont;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void UnloadFont( SFont *pFont )
+{
+	SFont*	pPrevFont;
+	int		found = 0;
+
+	// Find font and unchain from list.
+	if( pFontList == pFont )
+	{
+		found=1;
+		pFontList = pFontList->pNext;
+	}
+	else
+	{
+		for( pPrevFont=pFontList; pPrevFont->pNext; pPrevFont=pPrevFont->pNext )
+		{
+			if( pPrevFont->pNext == pFont )
+			{
+				found = 1;
+				pPrevFont->pNext = pFont->pNext;
+				break;
+			}
+		}
+	}
+
+	Dbg_MsgAssert( found, ( "Attempt to unload font which has not been loaded" ));
+
+	// Free memory.
+	delete [] pFont->pChars;
+	delete pFont;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 SFont::GetDefaultHeight() const
+{
+	return DefaultHeight;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 SFont::GetDefaultBase() const
+{
+	return DefaultBase;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SFont::QueryString( char *String, float &width, float &height )
+{
+	SChar	*pChar;
+	char	*pLetter;
+	int		x0,x1;
+
+	x0 = 0;
+
+	for (pLetter=String;; pLetter++)
+	{
+		pChar = NULL;
+		// may be overridden by the '\b' tag
+		SFont *p_font = this;
+		
+		// acount for tags (might be multiple ones in a row)
+		bool got_char_tag = false; // tag resulting in output of character
+		while (*pLetter == '\\' && !got_char_tag)
+		{
+			pLetter++;
+			if (*pLetter == '\\')
+				break;
+
+			switch(*pLetter)
+			{
+				case '\\':
+					got_char_tag = true;
+					break;
+				case 'c':
+				case 'C':
+					pLetter += 2; // skip over "c#"
+					break;
+				case 's':
+				case 'S':
+				{
+					pLetter++; // skip "s"
+					uint digit = Str::DehexifyDigit(pLetter);
+					pChar = pChars + SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				case 'b':
+				case 'B':
+				{
+					pLetter++; // skip "b"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					// switch over to buttons font, the regular font will be used again on the next character
+
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				case 'm':
+				case 'M':
+				{
+					pLetter++; // skip "m"
+					char button_char = Nx::CFontManager::sMapMetaCharacterToButton(pLetter);
+					uint digit = Str::DehexifyDigit(&button_char);
+					
+					p_font = pButtonsFont;
+					Dbg_Assert(p_font);
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					break;
+				}
+				default:
+					Dbg_MsgAssert(0, ("unknown tag"));
+					break;
+			}
+		} // end while
+		
+		if (*pLetter == '\0') break;
+		
+		if (*pLetter!=' ' || pChar)
+		{
+			if (!pChar)
+				pChar = p_font->pChars + p_font->Map[(uint8)*pLetter];
+			x1 = x0 + pChar->w;
+		}
+		else
+		{
+			x1 = x0 + mSpaceSpacing;
+		}
+
+		//x0 = x1 + mCharSpacing + 1;
+		x0 = x1 + mCharSpacing;
+	}
+
+	width  = (float)x0;
+	height = (float)DefaultHeight;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SText::SText( float pri ) : SDraw2D( pri, true )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SText::~SText( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SText::BeginDraw( void )
+{
+	p_locked_font_vertex_buffer = &( font_vertex_buffer[0] );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SText::Draw( void )
+{
+	SChar	*pChar;
+	char	*pLetter;
+	float	u0,v0,x0,y0,u1,v1,x1,y1,yt;
+
+	x0 = SCREEN_CONV_X( m_xpos );
+	y0 = SCREEN_CONV_Y( m_ypos );
+
+	float char_spacing	= (float)mp_font->mCharSpacing * m_xscale;
+	float space_spacing = (float)mp_font->mSpaceSpacing * m_xscale;
+	
+	DWORD current_color	= ( m_rgba & 0xFF00FF00 ) | (( m_rgba & 0xFF ) << 16 ) | (( m_rgba & 0xFF0000 ) >> 16 );
+	
+	float text_z = GetZValue();
+	
+	for( pLetter = mp_string;; pLetter++ )
+	{
+		pChar = NULL;
+		SFont *p_font = mp_font;
+
+		sFontVert* p_vert = ((sFontVert*)p_locked_font_vertex_buffer ) + font_vertex_offset;
+
+		// acount for tags (might be multiple ones in a row)
+		bool got_char_tag = false; // tag resulting in output of character
+		while (*pLetter == '\\' && !got_char_tag)
+		{
+			pLetter++;
+
+			switch(*pLetter)
+			{
+				case '\\':
+					got_char_tag = true;
+					break;
+				case 'c':
+				case 'C':
+				{
+					pLetter++;	// skip "c"					
+					uint digit = Str::DehexifyDigit(pLetter);
+					pLetter++; // skip "#"
+					
+					// Set active color from font.
+					if( digit == 0 || m_color_override)
+					{
+						// Switch from RGBA to BGRA format.
+						current_color	= ( m_rgba & 0xFF00FF00 ) | (( m_rgba & 0xFF ) << 16 ) | (( m_rgba & 0xFF0000 ) >> 16 );
+					}
+					else
+					{
+						// Switch from RGBA to BGRA format.
+						uint32 color	= mp_font->mRGBATab[digit-1];
+						current_color	= ( color & 0xFF00FF00 ) | (( color & 0xFF ) << 16 ) | (( color & 0xFF0000 ) >> 16 );
+					}
+					break;
+				}
+				case 's':
+				case 'S':
+				{
+					pLetter++;	// skip "s"
+					uint digit = Str::DehexifyDigit(pLetter);
+					
+					pChar = mp_font->pChars + mp_font->SpecialMap[digit];
+					got_char_tag = true;
+					
+					break;
+				}
+				case 'b':
+				case 'B':
+				{
+					// 'B' stands for button, accesses the button font
+
+					pLetter++; // skip "b"
+					uint digit = Str::DehexifyDigit( pLetter );
+					
+					// switch to the buttons font!
+					p_font = pButtonsFont;
+					Dbg_Assert( p_font );
+					
+					pChar = p_font->pChars + p_font->SpecialMap[digit];
+					got_char_tag = true;
+					
+					EndDraw();
+					BeginDraw();
+
+					// Reset the vertex data pointer.
+					p_vert = ((sFontVert*)p_locked_font_vertex_buffer ) + font_vertex_offset;
+					
+					spOverrideFont = p_font;
+					break;
+				}
+				default:
+				{
+					Dbg_MsgAssert( 0, ( "unknown tag" ));
+					break;
+				}
+			}
+		} // end while
+		
+		if (*pLetter == '\0') break;
+		
+		if( *pLetter != ' ' || pChar)
+		{
+			if (!pChar)
+				pChar = p_font->pChars + p_font->Map[(uint8) *pLetter];
+			yt = y0 + ((float)( p_font->DefaultBase - pChar->Baseline ) * m_yscale ) * EngineGlobals.screen_conv_y_multiplier;
+			u0 = pChar->u0;
+			v0 = pChar->v0;
+			u1 = pChar->u1;
+			v1 = pChar->v1;
+			x1 = x0 + ( pChar->w * m_xscale * EngineGlobals.screen_conv_x_multiplier );
+			y1 = yt + ( pChar->h * m_yscale * EngineGlobals.screen_conv_y_multiplier );
+		}
+		else
+		{
+			x0 += ( space_spacing + char_spacing ) * EngineGlobals.screen_conv_x_multiplier;
+			continue;
+		}
+
+		p_vert->x	= x0;
+		p_vert->y	= yt;
+		p_vert->z	= text_z;
+		p_vert->rhw	= 0.0f;
+		p_vert->col	= current_color;
+		p_vert->u	= u0;
+		p_vert->v	= v0;
+		++p_vert;
+
+		p_vert->x	= x0;
+		p_vert->y	= y1;
+		p_vert->z	= text_z;
+		p_vert->rhw	= 0.0f;
+		p_vert->col	= current_color;
+		p_vert->u	= u0;
+		p_vert->v	= v1;
+		++p_vert;
+
+		p_vert->x	= x1;
+		p_vert->y	= y1;
+		p_vert->z	= text_z;
+		p_vert->rhw	= 0.0f;
+		p_vert->col	= current_color;
+		p_vert->u	= u1;
+		p_vert->v	= v1;
+		++p_vert;
+
+		p_vert->x	= x1;
+		p_vert->y	= yt;
+		p_vert->z	= text_z;
+		p_vert->rhw	= 0.0f;
+		p_vert->col	= current_color;
+		p_vert->u	= u1;
+		p_vert->v	= v0;
+
+		font_vertex_offset += 4;
+
+		if( font_vertex_offset >= ( CHARS_PER_BUFFER * 4 ))
+		{
+			// Draw this buffer and cycle through to the next.
+			EndDraw();
+			BeginDraw();
+		}
+
+		x0 = x1 + ( char_spacing * EngineGlobals.screen_conv_x_multiplier );
+
+		if( p_font != mp_font )
+		{
+			// We just used the button font, so return to the regular one.
+			EndDraw();
+			BeginDraw();
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SText::EndDraw( void )
+{
+	if( font_vertex_offset > 0 )
+	{
+		// Subsequent processing within Draw() will use this font
+		// Draw() may call this function to temporarily switch fonts
+		SFont *p_font = ( spOverrideFont ) ? spOverrideFont : mp_font;
+		
+		// Set up the render state and submit.
+		set_pixel_shader( PixelShader4 );
+		set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+
+		set_texture( 0, p_font->pD3DTexture, p_font->pD3DPalette );
+
+		EngineGlobals.p_Device->DrawVerticesUP( D3DPT_QUADLIST, font_vertex_offset, &( font_vertex_buffer[0] ), sizeof( sFontVert ));
+
+		// Reset offset.
+		font_vertex_offset = 0;
+
+		// We can now return to using the regular font (no override).
+		spOverrideFont = NULL;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetTextWindow( uint16 x0, uint16 x1, uint16 y0, uint16 y1 )
+{
+}
+
+} // namespace Xbox
+
diff --git a/Code/Gfx/XBox/NX/chars.h b/Code/Gfx/XBox/NX/chars.h
new file mode 100644
index 0000000..5b7ce50
--- /dev/null
+++ b/Code/Gfx/XBox/NX/chars.h
@@ -0,0 +1,93 @@
+#ifndef __CHARS_H
+#define __CHARS_H
+
+#include 
+
+namespace NxXbox
+{
+
+
+typedef struct
+{
+	float	u0, v0, u1, v1;
+	uint16	w, h;
+	uint16	Baseline;
+}
+SChar;
+
+
+struct SFont
+{
+public:
+	uint32		GetDefaultHeight() const;
+	uint32		GetDefaultBase() const;
+
+//	void		BeginText(uint32 rgba, float Scale);
+//	void		DrawString(char *String, float x0, float y0);
+//	void		EndText(void);
+	void		QueryString(char *String, float &width, float &height);
+
+	//char Name[16];
+	uint32		DefaultHeight, DefaultBase;
+	SChar		*pChars;
+	uint8		Map[256];
+	uint8		SpecialMap[32];
+//	uint8		*pVifData;
+//	uint32		VifSize;
+//	uint64		RegTEX0, RegTEX1;
+	SFont		*pNext;
+
+	sint16		mCharSpacing;
+	sint16		mSpaceSpacing;
+	uint32		mRGBATab[16];
+	
+	IDirect3DTexture8*	pD3DTexture;		// To do - these should probably be replaced with an sTexture.
+	IDirect3DPalette8*	pD3DPalette;
+};
+
+
+
+SFont*		InitialiseMemoryResidentFont( void );
+SFont*		LoadFont( const char* Filename, bool memory_resident = false );
+void		UnloadFont( SFont * );
+void		SetTextWindow( uint16 x0, uint16 x1, uint16 y0, uint16 y1 );
+
+
+struct SText : public SDraw2D
+{
+	public:
+					SText( float pri = 0.0f );
+	virtual			~SText();
+
+	SFont			*mp_font;
+
+	char			*mp_string;
+	float			m_xpos;
+	float			m_ypos;
+	float			m_xscale;
+	float			m_yscale;
+	uint32			m_rgba;
+	bool			m_color_override;
+	
+	// used in conjunction with BeginDraw()
+	// if set, use specified font instead of mp_font
+	// if not, use mp_font
+	static SFont *	spOverrideFont;
+
+	void			BeginDraw( void );
+	void			Draw( void );
+	void			EndDraw( void );
+};
+
+void SwizzleTexture( void *dstBuffer, void *srcBuffer, int width, int height, int32 depth, int32 stride );
+
+
+extern uint32 FontVramBase;
+extern SFont *pFontList;
+extern SFont *pButtonsFont;
+
+
+} // namespace NxXbox
+
+
+#endif // __CHARS_H
diff --git a/Code/Gfx/XBox/NX/gamma.cpp b/Code/Gfx/XBox/NX/gamma.cpp
new file mode 100644
index 0000000..c4f59cf
--- /dev/null
+++ b/Code/Gfx/XBox/NX/gamma.cpp
@@ -0,0 +1,170 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		gamma.cpp												**
+**																			**
+**	Created:		02/27/02	-	dc										**
+**																			**
+**	Description:	Gamma setting code										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+#include 
+#include "gamma.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace NxXbox
+{
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// Some defines for gamma settings.
+#define gammaDefault	1.0f
+#define gammaDomain		256
+#define gammaRange		256.0f
+#define MIN_GAMMA		0.0f
+#define MAX_GAMMA		2.0f
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static D3DGAMMARAMP	gammaTable;
+static float		gammaValueR = gammaDefault;	// Current red gamma value.
+static float		gammaValueG = gammaDefault;	// Current green gamma value.
+static float		gammaValueB = gammaDefault;	// Current blue gamma value.
+
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void gammaInitTable( void )
+{
+	// Create the gamma intensity lookup table.
+	// It works like this: given a colour value r [0,255] do r' = gammaTable.red[r] etc.
+	for( int i = 0; i < gammaDomain; ++i )
+	{
+		gammaTable.red[i]	= (BYTE)((float)gammaRange * pow(((float)i / (float)gammaDomain ), 1.0f / gammaValueR ));
+		gammaTable.green[i]	= (BYTE)((float)gammaRange * pow(((float)i / (float)gammaDomain ), 1.0f / gammaValueG ));
+		gammaTable.blue[i]	= (BYTE)((float)gammaRange * pow(((float)i / (float)gammaDomain ), 1.0f / gammaValueB ));
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void gammaSetValue( float gr, float gg, float gb )
+{
+	gammaValueR = gr;
+	gammaValueG = gg;
+	gammaValueB = gb;
+
+	// Create the table.
+	gammaInitTable();
+
+	// Pass the table along to the hardware.
+	D3DDevice_SetGammaRamp( D3DSGR_NO_CALIBRATION, &gammaTable );
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetGammaNormalized( float fr, float fg, float fb )
+{
+	// Cap it....
+	fr = ( fr < 0.0f ) ? 0.0f : (( fr > 1.0f ) ? 1.0f : fr );
+	fg = ( fg < 0.0f ) ? 0.0f : (( fg > 1.0f ) ? 1.0f : fg );
+	fb = ( fb < 0.0f ) ? 0.0f : (( fb > 1.0f ) ? 1.0f : fb );
+
+	// Scale it....
+	fr *= MAX_GAMMA;
+	fg *= MAX_GAMMA;
+	fb *= MAX_GAMMA;
+
+	// Offset it.
+	fr += 0.5f;
+	fg += 0.5f;
+	fb += 0.5f;
+
+	// f now goes from 0.5 to 2.5.
+	gammaSetValue( fr, fg, fb );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void GetGammaNormalized( float *fr, float *fg, float *fb )
+{
+	// Peform the opposite operation to that done in SetGammaNormalized().
+	*fr = ( gammaValueR - 0.5f ) / MAX_GAMMA;
+	*fg = ( gammaValueG - 0.5f ) / MAX_GAMMA;
+	*fb = ( gammaValueB - 0.5f ) / MAX_GAMMA;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace Gfx
diff --git a/Code/Gfx/XBox/NX/gamma.h b/Code/Gfx/XBox/NX/gamma.h
new file mode 100644
index 0000000..f27fcfe
--- /dev/null
+++ b/Code/Gfx/XBox/NX/gamma.h
@@ -0,0 +1,11 @@
+#ifndef	__GAMMA_H__
+#define	__GAMMA_H__
+
+namespace NxXbox
+{
+	void SetGammaNormalized( float fr, float fg, float fb );
+	void GetGammaNormalized( float *fr, float *fg, float *fb );
+}
+
+#endif
+
diff --git a/Code/Gfx/XBox/NX/grass.cpp b/Code/Gfx/XBox/NX/grass.cpp
new file mode 100644
index 0000000..1be6225
--- /dev/null
+++ b/Code/Gfx/XBox/NX/grass.cpp
@@ -0,0 +1,405 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "render.h"
+#include "grass.h"
+
+static bool				bumpsLoaded = false;
+NxXbox::sMaterial		waterMaterial;
+NxXbox::sTexture		waterTextures[2];
+
+IDirect3DTexture8		*pBumpTextures[17];
+NxXbox::sTexture		bumpTextures[17];
+
+
+namespace NxXbox
+{
+
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Unswizzles a 2D texture before it gets unlocked.				  */
+/* Note: this operation can be very slow.						  */
+/*                                                                */
+/******************************************************************/
+VOID XBUtil_UnswizzleTexture2D( D3DLOCKED_RECT* pLock, const D3DSURFACE_DESC* pDesc )
+{
+    DWORD dwPixelSize   = XGBytesPerPixelFromFormat( pDesc->Format );
+    DWORD dwTextureSize = pDesc->Width * pDesc->Height * dwPixelSize;
+
+    BYTE* pSrcBits = new BYTE[ dwTextureSize ];
+    memcpy( pSrcBits, pLock->pBits, dwTextureSize );
+    
+    XGUnswizzleRect( pSrcBits, pDesc->Width, pDesc->Height, NULL, pLock->pBits, 
+                     0, NULL, dwPixelSize );
+
+    delete [] pSrcBits;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Swizzles a 2D texture before it gets unlocked.				  */
+/* Note: this operation can be very slow.						  */
+/*                                                                */
+/******************************************************************/
+VOID XBUtil_SwizzleTexture2D( D3DLOCKED_RECT* pLock, const D3DSURFACE_DESC* pDesc )
+{
+    DWORD dwPixelSize   = XGBytesPerPixelFromFormat( pDesc->Format );
+    DWORD dwTextureSize = pDesc->Width * pDesc->Height * dwPixelSize;
+
+    BYTE* pSrcBits = new BYTE[ dwTextureSize ];
+    memcpy( pSrcBits, pLock->pBits, dwTextureSize );
+    
+    XGSwizzleRect( pSrcBits, 0, NULL, pLock->pBits,
+                  pDesc->Width, pDesc->Height, 
+                  NULL, dwPixelSize );
+
+    delete [] pSrcBits;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* There are 17 frames. Each has 3 level mipmaps				  */
+/*                                                                */
+/******************************************************************/
+HRESULT LoadBumpTextures( void )
+{
+	HRESULT hr = S_OK;
+	for( int i = 0; i < 17; ++i )
+    {
+		int height = 128, width = 128;
+
+        // Find file name
+		char strBumpFile[128];
+		sprintf( strBumpFile, "c:\\skate5\\data\\images\\miscellaneous\\%d.bum", i );
+        
+        // Open and read file.
+		void *fp = File::Open( strBumpFile, "rb" );
+        if( !fp )
+            return E_FAIL;
+      
+		// Create texture.
+		if( FAILED( hr = D3DDevice_CreateTexture( width,
+            height,
+            3,
+            NULL,
+            D3DFMT_V8U8,
+            D3DPOOL_MANAGED,
+			&pBumpTextures[i] )))
+            return hr;
+       
+        // Lock first level.
+        D3DLOCKED_RECT lr; 
+        D3DSURFACE_DESC desc;
+        pBumpTextures[i]->GetLevelDesc( 0, &desc );
+        pBumpTextures[i]->LockRect( NULL, &lr, NULL, NULL );
+        XBUtil_UnswizzleTexture2D( &lr, &desc );
+
+		File::Read( lr.pBits, sizeof( WORD ), height * width, fp );
+
+		// Modulate the size of the offset.
+		uint8 *p_mod = (uint8*)lr.pBits;
+		for( int p = 0; p < height * width * 2; ++p )
+		{
+			// Noticed occasionally this maps to outside of the map tetxure with texbem.
+			// Culprit appears to be the maximum negative value (0, given 0x80 offset of values
+			// in V8U8 format).
+			if( *p_mod == 0 )
+			{
+				// Max negative offset.
+				*p_mod = 1;
+			}
+			++p_mod;
+		}
+		
+		XBUtil_SwizzleTexture2D( &lr, &desc );
+        pBumpTextures[i]->UnlockRect( 0 );
+
+        // Load mipmaps
+		int level = 0;
+        while( ++level < 3 )
+        {
+            width >>= 1;
+            height >>= 1;
+                        
+            pBumpTextures[i]->GetLevelDesc( level, &desc );
+            pBumpTextures[i]->LockRect( level, &lr, NULL,NULL );
+            XBUtil_UnswizzleTexture2D( &lr, &desc );
+
+			File::Read( lr.pBits, sizeof( WORD ), height * width, fp );
+
+			// Modulate the size of the offset.
+			p_mod = (uint8*)lr.pBits;
+			for( int p = 0; p < height * width * 2; ++p )
+			{
+				// Noticed occasionally this maps to outside of the map tetxure with texbem.
+				// Culprit appears to be the maximum negative value (0, given 0x80 offset of values
+				// in V8U8 format).
+				if( *p_mod == 0 )
+				{
+					// Max negative offset.
+					*p_mod = 1;
+				}
+				++p_mod;
+			}
+			
+			XBUtil_SwizzleTexture2D( &lr, &desc );
+            pBumpTextures[i]->UnlockRect( level );
+        }
+		File::Close( fp );
+
+		bumpTextures[i].pD3DTexture = pBumpTextures[i];
+		bumpTextures[i].pD3DPalette	= NULL;
+	}
+
+	// Set up the texture container for the bump textures.
+	ZeroMemory( &waterTextures[0],	sizeof( NxXbox::sTexture ));
+	waterTextures[0].pD3DTexture = pBumpTextures[0];
+
+	return hr;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CreateWaterMaterial( NxXbox::sMaterial *p_material )
+{
+	// Set texture 0 to be the first bump texture.
+	p_material->mp_tex[0]			= &waterTextures[0];
+	p_material->mp_tex[1]			= &waterTextures[1];
+	p_material->m_reg_alpha[0]		= NxXbox::vBLEND_MODE_BLEND_FIXED | ( 0x40UL << 24 );
+	p_material->m_color[0][3]		= 0.5f;
+	p_material->m_passes			= 2;
+
+	// Add transparent flag.
+	p_material->m_flags[0]			|= 0x40;
+	p_material->m_flags[1]			= 0x00;
+
+	// Add bump setup flag.
+	p_material->m_flags[0]			|= MATFLAG_BUMP_SIGNED_TEXTURE;
+	p_material->m_flags[1]			|= MATFLAG_BUMP_LOAD_MATRIX;
+
+	p_material->m_uv_wibble			= false;
+	p_material->m_flags[0]			&= ~MATFLAG_ENVIRONMENT;
+	p_material->m_flags[0]			&= ~MATFLAG_UV_WIBBLE;
+
+	// Adjust K.
+	p_material->m_k[0]				= 0.0f;
+	p_material->m_k[1]				= 0.0f;
+
+	// Set texture address mode.
+	p_material->m_uv_addressing[0]	= 0x00000000UL;
+	p_material->m_uv_addressing[1]	= 0x00000000UL;
+
+	// Set the draw order to ensure meshes are drawn from the bottom up. Default draw order for transparent
+	// meshes is 1000.0, so add a little onto that.
+	p_material->m_sorted			= false;
+	p_material->m_draw_order		= 1501.0f;
+
+	// Create the animating texture details.
+	p_material->m_texture_wibble	= true;
+	p_material->m_flags[0]			|= MATFLAG_PASS_TEXTURE_ANIMATES;
+
+	p_material->mp_wibble_texture_params						= new NxXbox::sTextureWibbleParams;
+	p_material->mp_wibble_texture_params->m_num_keyframes[0]	= 18;
+	p_material->mp_wibble_texture_params->m_phase[0]			= 0;
+	p_material->mp_wibble_texture_params->m_num_iterations[0]	= 0;
+	p_material->mp_wibble_texture_params->mp_keyframes[0]		= new NxXbox::sTextureWibbleKeyframe[18];
+	p_material->mp_wibble_texture_params->mp_keyframes[1]		= NULL;
+	p_material->mp_wibble_texture_params->mp_keyframes[2]		= NULL;
+	p_material->mp_wibble_texture_params->mp_keyframes[3]		= NULL;
+
+	for( int f = 0; f < 18; ++f )
+	{
+		p_material->mp_wibble_texture_params->mp_keyframes[0][f].m_time						= (( f * 1000 ) / 30 );
+		p_material->mp_wibble_texture_params->mp_keyframes[0][f].mp_texture					= &bumpTextures[f % 17];
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool AddWater( Nx::CXboxGeom *p_geom, NxXbox::sMesh *p_mesh )
+{
+	// Ensure there is at least two sets of uv's available.
+	Dbg_Assert( p_mesh->m_uv0_offset > 0 );
+
+	// Check to see whether the texture we want is present.
+	Nx::CScene		*p_sky_scene		= Nx::CEngine::sGetSkyScene();
+	Nx::CTexDict	*p_tex_dict			= p_sky_scene->GetTexDict();
+	Nx::CTexture	*p_texture			= p_tex_dict->GetTexture( 0x96d0bf08UL );
+
+	if( p_texture == NULL )
+		return false;
+
+	Nx::CXboxTexture	*p_xbox_texture = static_cast( p_texture );
+	NxXbox::sTexture	*p_engine_texture	= p_xbox_texture->GetEngineTexture();
+	CopyMemory( &waterTextures[1], p_engine_texture, sizeof( NxXbox::sTexture ));
+
+	int num_tc_sets = ( p_mesh->m_vertex_stride - p_mesh->m_uv0_offset ) / 8;
+	if( num_tc_sets == 1 )
+	{
+		// We need to alter this mesh to create two sets of uv's.
+		IDirect3DVertexBuffer8* p_new_buffer = p_mesh->AllocateVertexBuffer(( p_mesh->m_vertex_stride + 8 ) * p_mesh->m_num_vertices );
+		BYTE* p_read_byte;
+		BYTE* p_write_byte;
+		p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_read_byte, D3DLOCK_READONLY | D3DLOCK_NOFLUSH );
+		p_new_buffer->Lock( 0, 0, &p_write_byte, D3DLOCK_NOFLUSH );
+
+		for( int v = 0; v < p_mesh->m_num_vertices; ++v )
+		{
+			// Copy all existing vertex information.
+			CopyMemory( p_write_byte, p_read_byte, p_mesh->m_vertex_stride );
+			p_read_byte		+= p_mesh->m_vertex_stride;
+			p_write_byte	+= p_mesh->m_vertex_stride + 8;
+		}
+
+		delete p_mesh->mp_vertex_buffer[0];
+		p_mesh->mp_vertex_buffer[0] = p_new_buffer;
+
+		p_mesh->m_vertex_stride += 8;
+
+		// Switch the vertex shader.
+		p_mesh->m_vertex_shader[0]	&= ~( D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+		p_mesh->m_vertex_shader[0]	|= D3DFVF_TEX2 | D3DFVF_TEXCOORDSIZE2( 0 ) | D3DFVF_TEXCOORDSIZE2( 1 );
+	}
+
+	NxXbox::sMaterial *p_water_mat = p_mesh->mp_material;
+
+	CreateWaterMaterial( p_water_mat );
+
+	// Disable skater shadow on the mesh.
+	p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+	p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_BUMPED_WATER;
+
+	// Set the water pixel shader.
+	p_mesh->m_pixel_shader	= PixelShaderBumpWater;
+		
+	// Go through and calculate new texture coordinates based on the x-z space of the vertex positions.
+	BYTE *p_byte_dest;
+	p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_byte_dest, 0 );
+	for( uint32 vert = 0; vert < p_mesh->m_num_vertices; ++vert )
+	{
+		D3DXVECTOR3 *p_pos	= (D3DXVECTOR3*)( p_byte_dest + ( vert * p_mesh->m_vertex_stride ));
+
+		float u0				= p_pos->x / 192.0f;
+		float v0				= p_pos->z / 192.0f;
+
+		float u1				= p_pos->x / 96.0f;
+		float v1				= p_pos->z / 96.0f;
+
+
+		float *p_tex		= (float*)( p_byte_dest + p_mesh->m_uv0_offset + ( vert * p_mesh->m_vertex_stride ));
+		p_tex[0]			= u0;
+		p_tex[1]			= v0;
+		p_tex[2]			= u1;
+		p_tex[3]			= v1;
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool AddGrass( Nx::CXboxGeom *p_geom, NxXbox::sMesh *p_mesh )
+{
+	// Need a material to proceed.
+	if( p_mesh->mp_material == NULL )
+		return false;
+
+	if( p_mesh->mp_material->m_flags[0] & MATFLAG_WATER_EFFECT )
+	{
+		return AddWater( p_geom, p_mesh );
+	}
+
+	if( p_mesh->mp_material->m_grass_layers == 0 )
+	{
+		return false;
+	}
+
+	// At this stage we know we want to add grass.
+	Dbg_Assert( p_mesh->mp_material->m_grass_layers > 0 );
+
+	int		grass_layers		= ( p_mesh->mp_material->m_grass_layers > 5 ) ? 5 : p_mesh->mp_material->m_grass_layers;
+	float	height_per_layer	= p_mesh->mp_material->m_grass_height / grass_layers;
+	
+	for( int layer = 0; layer < grass_layers; ++layer )
+	{
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().BottomUpHeap());
+		NxXbox::sMesh *p_grass_mesh = p_mesh->Clone( false );
+		Mem::Manager::sHandle().PopContext();
+		
+		// Disable skater shadow on the mesh.
+		p_grass_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		
+		// Turn off anisotropic filtering and z-write on the mesh.
+		p_grass_mesh->m_flags |= ( NxXbox::sMesh::MESH_FLAG_NO_ANISOTROPIC | NxXbox::sMesh::MESH_FLAG_NO_ZWRITE );
+		
+		// Now point the mesh to a different material, first build the material checksum from the name of the parent
+		// material (Grass) and the name of the sub material (Grass_LayerX)...
+		char material_name[64];
+		sprintf( material_name, "Grass-Grass_Layer%d", layer );
+		uint32 material_checksum	= Crc::GenerateCRCCaseSensitive( material_name, strlen( material_name ));
+
+		// ...then use the checksum to lookup the material.
+		p_grass_mesh->mp_material	= p_geom->mp_scene->GetEngineScene()->GetMaterial( material_checksum );
+		Dbg_MsgAssert( p_grass_mesh->mp_material, ( "Grass maaterial for layer %d appears to be named badly", layer ));
+		
+		// We need also to override the mesh pixel shader, which may have been setup for a multipass material.
+		NxXbox::GetPixelShader( p_grass_mesh->mp_material, &p_grass_mesh->m_pixel_shader );
+		
+		// Add transparent flag to the material.
+		p_grass_mesh->mp_material->m_flags[0]		|= 0x40;
+
+		// Adjust K for the material.
+		p_grass_mesh->mp_material->m_k[0]			= -2.0f;
+
+		// Set the draw order to ensure meshes are drawn from the bottom up. Default draw order for transparent
+		// meshes is 1000.0, so add a little onto that.
+		p_grass_mesh->mp_material->m_sorted		 = false;
+		p_grass_mesh->mp_material->m_draw_order	 = 1100.0f + ( layer * 0.1f );
+		
+		// Go through and move all the vertices up a little, and calculate new texture coordinates based on the x-z space of the vertex positions.
+		BYTE *p_byte_dest;
+		p_grass_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_byte_dest, 0 );
+
+		for( uint32 vert = 0; vert < p_grass_mesh->m_num_vertices; ++vert )
+		{
+			D3DXVECTOR3 *p_pos = (D3DXVECTOR3*)( p_byte_dest + ( vert * p_grass_mesh->m_vertex_stride ));
+			p_pos->y += ( height_per_layer * ( layer + 1 ));
+
+			float u			= p_pos->x / 48.0f;
+			float v			= p_pos->z / 48.0f;
+			float *p_tex	= (float*)( p_byte_dest + p_grass_mesh->m_uv0_offset + ( vert * p_grass_mesh->m_vertex_stride ));
+			p_tex[0]		= u;		
+			p_tex[1]		= v;
+		}
+
+		p_geom->AddMesh( p_grass_mesh );
+	}
+
+	// Turn off anisotropic filtering on the base mesh also.
+	p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_NO_ANISOTROPIC;
+	
+	return true;
+}
diff --git a/Code/Gfx/XBox/NX/grass.h b/Code/Gfx/XBox/NX/grass.h
new file mode 100644
index 0000000..2be44d9
--- /dev/null
+++ b/Code/Gfx/XBox/NX/grass.h
@@ -0,0 +1,154 @@
+#pragma once
+//-----------------------------------------------------------------------------
+// File: XBFur.h
+//
+// Copyright (c) 2000-2001 Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#include 
+//#include "xfvf.h"
+#include 
+
+#define XBFUR_MAXSLICE_LOG2 5
+#define XBFUR_MAXSLICE (1 << XBFUR_MAXSLICE_LOG2)
+
+extern float g_fOneInch;
+
+#define FVF_XYZDIFF (D3DFVF_XYZ|D3DFVF_DIFFUSE)
+
+typedef struct sFVFT_XYZDIFF
+{
+    D3DVECTOR v;
+    DWORD diff;
+} FVFT_XYZDIFF;
+
+
+// patch generation
+
+// a fuzz is a single hair follicle, blade of grass, etc.
+struct Fuzz
+{
+    D3DVECTOR dp;            // velocity
+    D3DVECTOR ddp;            // acceleration
+    D3DXCOLOR colorBase;
+    D3DXCOLOR colorTip;
+};
+
+// a fuzz instance is a single instance of a fuzz
+// located at x, z on the patch
+// we create only a limited number of unique fuzzes
+// and index the library with lidx.
+struct FuzzInst
+{
+    float x, z;                // fuzz location
+    int lidx;                // library index
+};
+
+// a fur patch is a volume that holds fuzzes.
+// xsize and zsize are chosen by the user
+// ysize is calculated using the height of the 
+// tallest fuzz
+class CXBFur
+{
+    friend class CXBFurMesh;
+public:
+    DWORD m_dwSeed;            // patch seed
+    
+    float m_fXSize;            // patch size in world coords
+    float m_fYSize;
+    float m_fZSize;
+
+    // fuzz library
+    DWORD m_dwNumSegments;    // # of segments in highest LOD
+    Fuzz m_fuzzCenter;        // fuzz constant
+    Fuzz m_fuzzRandom;        // random offset around center
+    DWORD m_dwNumFuzzLib;    // # of fuzz in the library
+    Fuzz *m_pFuzzLib;        // fuzz library
+
+    // fuzz instances
+    DWORD m_dwNumFuzz;        // # of fuzz in this patch
+    FuzzInst *m_pFuzz;
+
+    // patch volume
+    DWORD m_dwNumSlices;    // # of layers in the volume
+    DWORD m_dwSliceSize;        // width*height
+    DWORD m_dwSliceXSize;        // width of volume texture slice
+    DWORD m_dwSliceZSize;        // height of volume texture slice
+    LPDIRECT3DTEXTURE8 m_apSliceTexture[XBFUR_MAXSLICE * 2 - 1];    // slices of volume texture
+                    // ... followed by level-of-detail textures  N/2, N/4, N/8, ... 1
+
+    // LOD textures
+    DWORD m_dwNumSlicesLOD; // number of slices in current level of detail
+    float m_fLevelOfDetail;    // current LOD value
+    DWORD m_iLOD;            // current integer LOD value
+    float m_fLODFraction;    // fraction towards next coarser level-of-detail
+    DWORD m_dwLODMax;        // maximum LOD index
+    LPDIRECT3DTEXTURE8 *m_pSliceTextureLOD; // current level of detail pointer into m_apSliceTexture array
+
+    // hair lighting texture
+    D3DMATERIAL8 m_HairLightingMaterial;
+    LPDIRECT3DTEXTURE8 m_pHairLightingTexture;
+
+    // fin texture
+    DWORD m_finWidth, m_finHeight;        // size of fin texture
+    float m_fFinXFraction, m_fFinZFraction;    // portion of hair texture to put into fin
+    LPDIRECT3DTEXTURE8 m_pFinTexture;    // texture projected from the side
+
+    CXBFur();
+    ~CXBFur();
+    void InitFuzz(DWORD nfuzz, DWORD nfuzzlib);
+    void GenSlices(DWORD nslices, DWORD slicexsize, DWORD slicezsize);
+    void GenFin(DWORD finWidth, DWORD finHeight, float fFinXFraction, float fFinZFraction);
+    void GetLinesVertexBuffer(IDirect3DVertexBuffer8 **ppVB);
+    void RenderLines();
+    void Save(char *fname, int flags);
+    void Load(char *fname);
+    HRESULT SetHairLightingMaterial(D3DMATERIAL8 *pMaterial);
+    void SetPatchSize(float x, float z)
+    {
+        m_fXSize = x;
+        m_fZSize = z;
+        InitFuzz(m_dwNumFuzz, m_dwNumFuzzLib);    // re-init the fuzz. automatically sets ysize
+    };
+    void SetFVel(float cx, float cy, float cz, float rx, float ry, float rz)
+    {
+        m_fuzzCenter.dp.x = cx; m_fuzzCenter.dp.y = cy; m_fuzzCenter.dp.z = cz;
+        m_fuzzRandom.dp.x = rx; m_fuzzRandom.dp.y = ry; m_fuzzRandom.dp.z = rz;
+    };
+    void SetFAcc(float cx, float cy, float cz, float rx, float ry, float rz)
+    {
+        m_fuzzCenter.ddp.x = cx; m_fuzzCenter.ddp.y = cy; m_fuzzCenter.ddp.z = cz;
+        m_fuzzRandom.ddp.x = rx; m_fuzzRandom.ddp.y = ry; m_fuzzRandom.ddp.z = rz;
+    };
+
+    // fLevelOfDetail can range from 0 to log2(NumSlices)
+    HRESULT SetLevelOfDetail(float fLevelOfDetail);
+    HRESULT ComputeLevelOfDetailTextures();
+    inline UINT LevelOfDetailCount(UINT iLOD)
+    {
+        return m_dwNumSlices >> iLOD;
+    }
+    inline UINT LevelOfDetailIndex(UINT iLOD)
+    {
+        UINT offset = 0;
+        for (UINT i = 1; i <= iLOD; i++)
+            offset += LevelOfDetailCount(i-1);
+        return offset;
+    }
+    inline UINT TotalTextureCount()
+    {
+        UINT TextureCount = 0;
+        for (UINT iLOD = 0; m_dwNumSlices >> iLOD; iLOD++)
+            TextureCount += LevelOfDetailCount(iLOD);
+        return TextureCount;
+    }
+
+    // Compress textures one at a time until all are done.
+    // Returns S_OK when all the textures are in fmtNew format.
+    // Returns S_FALSE if there are textures still to be done.
+    HRESULT CompressNextTexture(D3DFORMAT fmtNew, UINT *pTextureIndex);
+};
+
+HRESULT	FillHairLightingTexture(D3DMATERIAL8 *pMaterial, LPDIRECT3DTEXTURE8 pTexture);
+bool	AddGrass( Nx::CXboxGeom *p_geom, NxXbox::sMesh *p_mesh );
+HRESULT	LoadBumpTextures( void );
+
diff --git a/Code/Gfx/XBox/NX/instance.cpp b/Code/Gfx/XBox/NX/instance.cpp
new file mode 100644
index 0000000..f5afc1c
--- /dev/null
+++ b/Code/Gfx/XBox/NX/instance.cpp
@@ -0,0 +1,753 @@
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "instance.h"
+#include "anim.h"
+#include "render.h"
+#include "occlude.h"
+#include "anim_vertdefs.h"
+
+namespace NxXbox
+{
+
+static Mth::Matrix	*pLastBoneTransforms	= NULL;
+CInstance			*pFirstInstance			= NULL;
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int sort_by_bone_transform( const void *p1, const void *p2 )
+{
+	CInstance	*p_mesh1		= *((CInstance**)p1 );
+	CInstance	*p_mesh2		= *((CInstance**)p2 );
+
+	Mth::Matrix *p_mat1			= p_mesh1->GetBoneTransforms();
+	Mth::Matrix *p_mat2			= p_mesh2->GetBoneTransforms();
+	
+	if(( p_mat1 == NULL ) || ( p_mesh1->GetScene()->m_numHierarchyObjects > 0 ))
+	{
+		if(( p_mat2 == NULL ) || ( p_mesh2->GetScene()->m_numHierarchyObjects > 0 ))
+		{
+			int num_st1 = p_mesh1->GetScene()->m_num_semitransparent_mesh_entries;
+			int num_st2 = p_mesh2->GetScene()->m_num_semitransparent_mesh_entries;
+
+			if( num_st1 == num_st2 )
+			{
+				// Try sorting on the material draw order of the first semitransparent mesh.
+				if( num_st1 > 0 )
+				{
+					NxXbox::sMaterial	*p_material1 = p_mesh1->GetScene()->m_meshes[p_mesh1->GetScene()->m_first_semitransparent_entry]->mp_material;
+					NxXbox::sMaterial	*p_material2 = p_mesh2->GetScene()->m_meshes[p_mesh2->GetScene()->m_first_semitransparent_entry]->mp_material;
+
+					if( p_material1 && p_material2 )
+					{
+						return ( p_material1->m_draw_order > p_material2->m_draw_order ) ? 1 : (( p_material1->m_draw_order < p_material2->m_draw_order ) ? -1 : 0 );
+					}
+				}
+				return 0;
+			}
+	
+			return ( num_st1 > num_st2 ) ? 1 : -1;
+		}
+		return 1;
+	}
+	else if(( p_mat2 == NULL ) || ( p_mesh2->GetScene()->m_numHierarchyObjects > 0 ))
+	{
+		return -1;
+	}
+
+	// At this stage we know both instances have bone transforms.
+	if( p_mat1 == p_mat2 )
+	{
+		int num_st1 = p_mesh1->GetScene()->m_num_semitransparent_mesh_entries;
+		int num_st2 = p_mesh2->GetScene()->m_num_semitransparent_mesh_entries;
+	
+		if( num_st1 == num_st2 )
+			return 0;
+	
+		return ( num_st1 > num_st2 ) ? 1 : -1;
+	}
+
+	return((uint32)p_mat1 > (uint32)p_mat2 ) ? 1 : -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_instance( CInstance* p_instance, uint32 flags )
+{
+	// Seed the static pointer off to NULL, otherwise if there is only one object with bone transforms, it will never update.
+	pLastBoneTransforms = NULL;
+
+	if( p_instance->GetActive())
+	{
+		set_frustum_bbox_transform( p_instance->GetTransform());
+
+		bool render = true;
+		if( !( flags & vRENDER_NO_CULLING ))
+		{
+			// Check whether this instance is visible.
+			render = false;
+			if( frustum_check_sphere( &p_instance->GetScene()->m_sphere_center, p_instance->GetScene()->m_sphere_radius ))
+			{
+				if( !TestSphereAgainstOccluders( &p_instance->GetScene()->m_sphere_center, p_instance->GetScene()->m_sphere_radius ))
+				{
+					render = true;
+				}
+			}
+		}
+
+		if( render )
+		{
+			if( p_instance->GetBoneTransforms() != NULL )
+			{
+				startup_weighted_mesh_vertex_shader();
+				p_instance->Render( vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT );
+				shutdown_weighted_mesh_vertex_shader();
+			}
+			else
+			{
+				p_instance->Render( vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT );
+			}
+		}
+
+		// Restore world transform to identity.
+		D3DDevice_SetTransform( D3DTS_WORLD, &EngineGlobals.world_matrix );
+		set_frustum_bbox_transform( NULL );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_instances( uint32 flags )
+{
+	#define INSTANCE_ARRAY_SIZE	4096
+	static CInstance *p_instances[INSTANCE_ARRAY_SIZE];
+	int current_index = 0;
+
+	Mth::Matrix t;
+	t.Identity();
+	
+	// Seed the static pointer off to NULL, otherwise if there is only one object with bone transforms, it will never update.
+	pLastBoneTransforms = NULL;
+	
+	// First go through and build a list of the visible instances.
+	CInstance *p_instance = pFirstInstance;
+	while( p_instance )
+	{
+		if( p_instance->GetActive())
+		{
+			// Check to see whether this instance is of the type we want to render - opaque or semitransparent.
+			if((( flags & vRENDER_OPAQUE ) && ( p_instance->GetScene()->m_num_mesh_entries > p_instance->GetScene()->m_num_semitransparent_mesh_entries )) ||
+			   (( flags & vRENDER_SEMITRANSPARENT ) && ( p_instance->GetScene()->m_num_semitransparent_mesh_entries > 0 )))
+			{
+				// Check whether this instance is visible - if so, place it in the visible array.
+				t.SetPos( p_instance->GetTransform()->GetPos());
+				set_frustum_bbox_transform( &t );
+				
+				// For skinned objects, we have no idea how the skeleton transforms will be affecting the final position of each object.
+				// This can sometimes result in small objects getting culled incorrectly. For these objects, increase the size of
+				// the bounding sphere slightly.
+				float radius = p_instance->GetScene()->m_sphere_radius;
+				if( p_instance->GetBoneTransforms() && ( p_instance->GetScene()->m_numHierarchyObjects == 0 ))
+				{
+					radius = ( radius < 72.0f ) ? 72.0f : radius;
+				}
+				
+				// The logic code already sets the active flag based on visibility, so there is no need to perform a second visibility check
+				// at this point.
+				// We do, however, want to test against occluders.
+				if( !TestSphereAgainstOccluders( &p_instance->GetScene()->m_sphere_center, radius ))
+				{
+					Dbg_Assert( current_index < INSTANCE_ARRAY_SIZE );
+					p_instances[current_index++] = p_instance;
+				}
+			}
+		}
+		p_instance = p_instance->GetNextInstance();
+	}
+
+	// Now sort the list based on bone transform and number of semitransparent objects in scene.
+	if( current_index > 0 )
+	{
+		qsort( p_instances, current_index, sizeof( CInstance* ), sort_by_bone_transform );
+
+		int i = 0;
+
+		// First render the instances with bone transforms.
+		startup_weighted_mesh_vertex_shader();
+		for( ; i < current_index; ++i )
+		{
+			if(( p_instances[i]->GetBoneTransforms() == NULL ) || ( p_instances[i]->GetScene()->m_numHierarchyObjects > 0 ))
+				break;
+
+			if(( flags & vRENDER_OPAQUE ) || (( flags & vRENDER_SEMITRANSPARENT ) && ( flags & vRENDER_INSTANCE_PRE_WORLD_SEMITRANSPARENT )))
+				p_instances[i]->Render( flags );
+		}
+		shutdown_weighted_mesh_vertex_shader();
+		
+		// Then the instances with no bone transforms. These will require fixed function lighting.
+		D3DDevice_SetRenderState( D3DRS_LIGHTING,				TRUE );
+		D3DDevice_SetRenderState( D3DRS_COLORVERTEX,			TRUE );
+		D3DDevice_LightEnable( 0, TRUE );
+		D3DDevice_LightEnable( 1, TRUE );
+
+		Mth::Vector current_cam_pos( EngineGlobals.cam_position.x, EngineGlobals.cam_position.y, EngineGlobals.cam_position.z );
+
+		// Get the (square of) the distance at which env mapping will be disabled, in inches.
+		float env_map_disable_distance	= Script::GetFloat( CRCD( 0x4e7dc608, "EnvMapDisableDistance")) * 12.0f;
+		env_map_disable_distance		= env_map_disable_distance * env_map_disable_distance;
+
+		for( ; i < current_index; ++i )
+		{
+			// Calculate the distance from the current camera to the instance.
+			float dist_squared = Mth::DistanceSqr( p_instances[i]->GetTransform()->GetPos(), current_cam_pos );
+
+			// At forty feet, environment mapping is turned off.
+			EngineGlobals.allow_envmapping = ( dist_squared > env_map_disable_distance ) ? false : true;
+
+			if(( flags & vRENDER_OPAQUE ) || (( flags & vRENDER_SEMITRANSPARENT ) && ( flags & vRENDER_INSTANCE_POST_WORLD_SEMITRANSPARENT )))
+				p_instances[i]->Render( flags );
+		}
+
+		// Restore environment mapping..
+		EngineGlobals.allow_envmapping = true;
+
+		// Shut down fixed function lighting.
+		D3DDevice_SetRenderState( D3DRS_LIGHTING,				FALSE );
+		D3DDevice_SetRenderState( D3DRS_COLORVERTEX,			FALSE );
+		D3DDevice_LightEnable( 0, FALSE );
+		D3DDevice_LightEnable( 1, FALSE );
+	}
+
+	// Restore world transform to identity.
+	D3DDevice_SetTransform( D3DTS_WORLD, &EngineGlobals.world_matrix );
+	set_frustum_bbox_transform( NULL );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CInstance::CInstance( sScene *pScene, Mth::Matrix &transform, int numBones, Mth::Matrix *pBoneTransforms )
+{
+	SetTransform( transform );
+	mp_bone_transforms	= pBoneTransforms;
+	m_num_bones			= numBones;
+	mp_scene			= pScene;
+	mp_model			= NULL;
+	m_active			= true;
+	m_flags				= 0;
+
+	mp_next_instance	= pFirstInstance;
+	pFirstInstance		= this;
+
+	// Check to see whether this instance is allowed to be lit or not (for non-skinned instances).
+	// Currently, we assume that instances with a model containing valid normals requires lighting, except in the
+	// situation where that model contains a mesh specifically flagged not to be lit.
+	if(( pBoneTransforms == NULL ) || ( GetScene()->m_numHierarchyObjects > 0 ))
+	{
+		for( int m = 0; m < pScene->m_num_mesh_entries; ++m )
+		{
+			NxXbox::sMesh *p_mesh = pScene->m_meshes[m];
+			if(( p_mesh->m_vertex_shader[0] & D3DFVF_NORMAL ) == 0 )
+				return;
+			if( p_mesh->m_flags & sMesh::MESH_FLAG_UNLIT )
+				return;
+		}
+		m_flags |= CInstance::INSTANCE_FLAG_LIGHTING_ALLOWED;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CInstance::~CInstance()
+{
+	if( m_flags & INSTANCE_FLAG_DELETE_ATTACHED_SCENE )
+	{
+		if( mp_scene )
+		{
+			delete mp_scene;
+			mp_scene = NULL;
+		}
+	}
+	
+	// Remove this instance from the list.
+	CInstance **pp_instance = &pFirstInstance;
+	while( *pp_instance )
+	{
+		if( *pp_instance == this )
+		{
+			*pp_instance = mp_next_instance;
+			break;
+		}
+		else
+		{
+			pp_instance = &(( *pp_instance )->mp_next_instance );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CInstance::Render( uint32 flags )
+{
+	const int			MAX_SUPPORTED_BONES	= 58;
+	static float		root_matrix[12];
+
+	static D3DLIGHT8 l0 =
+	{
+		D3DLIGHT_DIRECTIONAL,
+		{ 0.0f, 0.0f, 0.0f, 1.0f },	// Diffuse color
+		{ 1.0f, 1.0f, 1.0f, 0.0f },	// Specular color
+		{ 0.0f, 0.0f, 0.0f, 0.0f },	// Ambient color
+		{ 0.0f, 0.0f, 0.0f },		// Position
+		{ 0.0f, 0.0f, 0.0f },		// Direction
+		0.0f, 0.0f,					// Range, falloff
+		0.0f, 0.0f, 0.0f,			// Attenuation0, 1, 2
+		0.0f, 0.0f					// Theta, Phi
+	};
+	static D3DLIGHT8 l1 =
+	{
+		D3DLIGHT_DIRECTIONAL,
+		{ 0.0f, 0.0f, 0.0f, 1.0f },	// Diffuse color
+		{ 0.0f, 0.0f, 0.0f, 0.0f },	// Specular color
+		{ 0.0f, 0.0f, 0.0f, 0.0f },	// Ambient color
+		{ 0.0f, 0.0f, 0.0f },		// Position
+		{ 0.0f, 0.0f, 0.0f },		// Direction
+		0.0f, 0.0f,					// Range, falloff
+		0.0f, 0.0f, 0.0f,			// Attenuation0, 1, 2
+		0.0f, 0.0f					// Theta, Phi
+	};
+
+	if(( GetBoneTransforms() == NULL ) || ( GetScene()->m_numHierarchyObjects > 0 ))
+	{
+		// Is a non-skinned object.
+		pLastBoneTransforms = NULL;
+		
+		// Do the lighting setup here.
+		if((( m_flags & CInstance::INSTANCE_FLAG_LIGHTING_ALLOWED ) == 0 ) || ( GetScene()->m_flags & SCENE_FLAG_RENDERING_SHADOW ))
+		{
+			D3DDevice_SetRenderState( D3DRS_LIGHTING, FALSE );
+		}
+		else
+		{
+			Nx::CModelLights *p_lights = GetModel() ? GetModel()->GetModelLights() : NULL;
+			if( p_lights )
+			{
+				// Obtain position from the transform, for calculating Scene Lighting.
+				p_lights->UpdateEngine( GetTransform()->GetPos(), true );
+			}
+			else
+			{
+				Nx::CLightManager::sUpdateEngine();
+			}
+
+			D3DDevice_SetRenderState( D3DRS_LIGHTING, TRUE );
+			
+			l0.Diffuse.r	= EngineGlobals.directional_light_color[4];
+			l0.Diffuse.g	= EngineGlobals.directional_light_color[5];
+			l0.Diffuse.b	= EngineGlobals.directional_light_color[6];
+			l0.Direction.x	= EngineGlobals.directional_light_color[0];
+			l0.Direction.y	= EngineGlobals.directional_light_color[1];
+			l0.Direction.z	= EngineGlobals.directional_light_color[2];
+			D3DDevice_SetLight( 0, &l0 );
+
+			l1.Diffuse.r	= EngineGlobals.directional_light_color[12];
+			l1.Diffuse.g	= EngineGlobals.directional_light_color[13];
+			l1.Diffuse.b	= EngineGlobals.directional_light_color[14];
+			l1.Direction.x	= EngineGlobals.directional_light_color[8];
+			l1.Direction.y	= EngineGlobals.directional_light_color[9];
+			l1.Direction.z	= EngineGlobals.directional_light_color[10];
+			D3DDevice_SetLight( 1, &l1 );
+
+			D3DDevice_SetRenderState( D3DRS_AMBIENT, D3DCOLOR_RGBA(	Ftoi_ASM( EngineGlobals.ambient_light_color[0] * 255.0f ),
+																	Ftoi_ASM( EngineGlobals.ambient_light_color[1] * 255.0f ),
+																	Ftoi_ASM( EngineGlobals.ambient_light_color[2] * 255.0f ),
+																	0xFF ));
+		}
+
+		// If the object has a 'fake skeleton', like the cars with rotating wheels, then set up accordingly.
+		if( GetScene()->m_numHierarchyObjects )
+		{
+			int num_bones = ( GetNumBones() < MAX_SUPPORTED_BONES ) ? GetNumBones() : MAX_SUPPORTED_BONES;
+			for( int lp = 0; lp < num_bones; ++lp )
+			{
+				Mth::Matrix temp = *GetTransform();
+				temp = GetBoneTransforms()[lp] * temp;
+				set_frustum_bbox_transform( &temp );
+				D3DDevice_SetTransform( D3DTS_WORLD, (D3DXMATRIX*)&temp );
+
+				// Scan through the meshes, setting only those with the current bone active.
+				for( int m = 0; m < GetScene()->m_num_mesh_entries; ++m )
+				{
+					NxXbox::sMesh *p_mesh = GetScene()->m_meshes[m];
+					if( p_mesh->GetBoneIndex() == lp )
+						p_mesh->SetActive( true );
+					else
+						p_mesh->SetActive( false );
+				}
+				render_scene( GetScene(), flags | vRENDER_NO_CULLING );
+			}
+		}
+		else
+		{
+			// Has no skeleton.
+			set_frustum_bbox_transform( GetTransform());
+			D3DDevice_SetTransform( D3DTS_WORLD, (D3DXMATRIX*)GetTransform());
+
+			render_scene( GetScene(), flags | vRENDER_NO_CULLING );
+		}
+	}
+	else
+	{
+		// Has bone transforms, is therefore an animated object such as a skater, pedestrian etc.
+		set_frustum_bbox_transform( GetTransform());
+
+		// We only want to upload all the bone transforms if they have changed from the last call.
+		bool upload_bone_transforms = false;
+		
+		if( pLastBoneTransforms != GetBoneTransforms())
+		{
+			// Okay, the bone transforms have changed from the last call.
+			upload_bone_transforms = true;
+			
+			// Do the lighting setup here (if not rendering shadow, which requires no lighting).
+			if(( GetScene()->m_flags & SCENE_FLAG_RENDERING_SHADOW ) == 0 )
+			{
+				Nx::CModelLights *p_lights = GetModel() ? GetModel()->GetModelLights() : NULL;
+				if( p_lights )
+				{
+					p_lights->UpdateEngine( GetTransform()->GetPos(), true );
+				}
+				else
+				{
+					Nx::CLightManager::sUpdateEngine();
+				}
+			}
+			
+			pLastBoneTransforms = GetBoneTransforms();
+
+			int num_bones = ( GetNumBones() < MAX_SUPPORTED_BONES ) ? GetNumBones() : MAX_SUPPORTED_BONES;
+		}
+				
+		root_matrix[0]	= GetTransform()->GetRight()[X];
+		root_matrix[1]	= GetTransform()->GetUp()[X];
+		root_matrix[2]	= GetTransform()->GetAt()[X];
+		root_matrix[3]	= GetTransform()->GetPos()[X];
+		root_matrix[4]	= GetTransform()->GetRight()[Y];
+		root_matrix[5]	= GetTransform()->GetUp()[Y];
+		root_matrix[6]	= GetTransform()->GetAt()[Y];
+		root_matrix[7]	= GetTransform()->GetPos()[Y];
+		root_matrix[8]	= GetTransform()->GetRight()[Z];
+		root_matrix[9]	= GetTransform()->GetUp()[Z];
+		root_matrix[10]	= GetTransform()->GetAt()[Z];
+		root_matrix[11]	= GetTransform()->GetPos()[Z];
+		
+		setup_weighted_mesh_vertex_shader( &root_matrix, &GetBoneTransforms()[0][Mth::RIGHT][X], upload_bone_transforms ? GetNumBones() : 0 );
+
+		if( GetScene()->m_flags & SCENE_FLAG_RENDERING_SHADOW )
+		{
+			// Set the simple vertex shader that does no normal transform or lighting.
+			set_vertex_shader( WeightedMeshVertexShader_SBWrite );
+			EngineGlobals.vertex_shader_override = 1;
+
+			// Set the simple pixel shader that just writes constant (1,1,1,1) out.
+			set_pixel_shader( PixelShaderNULL );
+			EngineGlobals.pixel_shader_override = 1;
+
+			// No backface culling.
+			set_render_state( RS_CULLMODE, D3DCULL_NONE );
+
+			// Lock out material changes.
+			EngineGlobals.material_override = 1;
+
+			render_scene( GetScene(), flags | vRENDER_NO_CULLING );
+
+//			RenderShadowVolume();
+		}
+		else
+		{
+			render_scene( GetScene(), flags | vRENDER_NO_CULLING );
+
+			// Render the self-shadowing pass here, if so flagged.
+			if( GetScene()->m_flags & SCENE_FLAG_SELF_SHADOWS )
+			{
+				static float texture_projection_matrix[16];
+
+				// Calculate distance to the camera - the self shadowing fades out beyond a certain distance, (and also fades out
+				// close up, to avoid the visible streaks). Also need to take into account the view angle since that directly affects
+				// the relative screen space size of the object.
+				const float SHADOW_FADE_CLOSE_START			= 150.0f;
+				const float SHADOW_FADE_CLOSE_COMPLETE		= 120.0f;
+				const float SHADOW_FADE_FAR_START			= 500.0f;
+				const float SHADOW_FADE_FAR_COMPLETE		= 600.0f;
+				const float DEFAULT_SCREEN_ANGLE			= 0.72654f;	// tan( 72 / 2 )
+
+				Mth::Vector pos_to_cam	= GetTransform()->GetPos() - Mth::Vector( NxXbox::EngineGlobals.cam_position.x, NxXbox::EngineGlobals.cam_position.y, NxXbox::EngineGlobals.cam_position.z );
+				float		dist		= pos_to_cam.Length() * ( tanf( Mth::DegToRad( EngineGlobals.screen_angle * 0.5f )) / DEFAULT_SCREEN_ANGLE );
+
+				if(( dist > SHADOW_FADE_CLOSE_COMPLETE ) && ( dist < SHADOW_FADE_FAR_COMPLETE ))
+				{
+					// Find which set of details relates to this instance.
+					pTextureProjectionDetailsTable->IterateStart();
+					sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->IterateNext();
+					while( p_details )
+					{
+						if( p_details->p_model == mp_model )
+							break;
+
+						p_details = pTextureProjectionDetailsTable->IterateNext();
+					}
+
+					if( p_details )
+					{
+						// Need to incorporate the world matrix into the texture projection matrix, since we will be using the bone-transformed
+						// vertex positions, which are in object space.
+						XGMATRIX world_matrix;
+						world_matrix.m[0][0] = GetTransform()->GetRight()[X];
+						world_matrix.m[0][1] = GetTransform()->GetRight()[Y];
+						world_matrix.m[0][2] = GetTransform()->GetRight()[Z];
+						world_matrix.m[0][3] = 0.0f;
+						world_matrix.m[1][0] = GetTransform()->GetUp()[X];
+						world_matrix.m[1][1] = GetTransform()->GetUp()[Y];
+						world_matrix.m[1][2] = GetTransform()->GetUp()[Z];
+						world_matrix.m[1][3] = 0.0f;
+						world_matrix.m[2][0] = GetTransform()->GetAt()[X];
+						world_matrix.m[2][1] = GetTransform()->GetAt()[Y];
+						world_matrix.m[2][2] = GetTransform()->GetAt()[Z];
+						world_matrix.m[2][3] = 0.0f;
+						world_matrix.m[3][0] = GetTransform()->GetPos()[X];
+						world_matrix.m[3][1] = GetTransform()->GetPos()[Y];
+						world_matrix.m[3][2] = GetTransform()->GetPos()[Z];
+						world_matrix.m[3][3] = 1.0f;
+						calculate_tex_proj_matrix( &p_details->view_matrix, &p_details->projection_matrix, &p_details->texture_projection_matrix, &world_matrix );
+
+						// Upload that matrix to the GPU also.
+						texture_projection_matrix[0]	= p_details->texture_projection_matrix.m[0][0];
+						texture_projection_matrix[1]	= p_details->texture_projection_matrix.m[1][0];
+						texture_projection_matrix[2]	= p_details->texture_projection_matrix.m[2][0];
+						texture_projection_matrix[3]	= p_details->texture_projection_matrix.m[3][0];
+						texture_projection_matrix[4]	= p_details->texture_projection_matrix.m[0][1];
+						texture_projection_matrix[5]	= p_details->texture_projection_matrix.m[1][1];
+						texture_projection_matrix[6]	= p_details->texture_projection_matrix.m[2][1];
+						texture_projection_matrix[7]	= p_details->texture_projection_matrix.m[3][1];
+						texture_projection_matrix[8]	= p_details->texture_projection_matrix.m[0][2];
+						texture_projection_matrix[9]	= p_details->texture_projection_matrix.m[1][2];
+						texture_projection_matrix[10]	= p_details->texture_projection_matrix.m[2][2];
+						texture_projection_matrix[11]	= p_details->texture_projection_matrix.m[3][2];
+						texture_projection_matrix[12]	= p_details->texture_projection_matrix.m[0][3];
+						texture_projection_matrix[13]	= p_details->texture_projection_matrix.m[1][3];
+						texture_projection_matrix[14]	= p_details->texture_projection_matrix.m[2][3];
+						texture_projection_matrix[15]	= p_details->texture_projection_matrix.m[3][3];
+
+						// We can upload this matrix to the space taken up by the directional lighting details, since this render pass requires no lighting.
+						D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_DIR_LIGHT_OFFSET, (void*)texture_projection_matrix, 4 );
+
+						// Scan through each mesh in this scene, setting the vertex shader to be the equivalent vertex shader
+						// for shadow buffering.
+						sScene *p_scene = GetScene();
+						for( int m = 0; m < p_scene->m_num_mesh_entries; ++m )
+						{
+							sMesh *p_mesh = p_scene->m_meshes[m];
+							if(( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_1Weight ) ||
+							   ( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_Specular_1Weight ) ||
+							   ( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_1Weight_UVTransform ))
+							{
+								p_mesh->PushVertexShader( WeightedMeshVS_VXC_1Weight_SBPassThru );
+							}
+							else if(( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_2Weight ) ||
+									( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_Specular_2Weight ) ||
+								    ( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_2Weight_UVTransform ))
+							{
+								p_mesh->PushVertexShader( WeightedMeshVS_VXC_2Weight_SBPassThru );
+							}
+							else
+							{
+								p_mesh->PushVertexShader( WeightedMeshVS_VXC_3Weight_SBPassThru );
+							}
+						}
+
+						float max = 0.25f;
+						if( dist < SHADOW_FADE_CLOSE_START )
+						{
+							max = (( dist - SHADOW_FADE_CLOSE_COMPLETE ) / ( SHADOW_FADE_CLOSE_START - SHADOW_FADE_CLOSE_COMPLETE )) * max;
+						}
+						else if( dist > SHADOW_FADE_FAR_START )
+						{
+							max = (( SHADOW_FADE_FAR_COMPLETE - dist ) / ( SHADOW_FADE_FAR_COMPLETE - SHADOW_FADE_FAR_START )) * max;
+						}
+
+						// Set ambient color multipliers in pixel shader C0 and C1.
+						NxXbox::EngineGlobals.pixel_shader_constants[0]		= 1.0f - max;		// Always present
+						NxXbox::EngineGlobals.pixel_shader_constants[1]		= 1.0f - max;
+						NxXbox::EngineGlobals.pixel_shader_constants[2]		= 1.0f - max;
+						NxXbox::EngineGlobals.pixel_shader_constants[3]		= 1.0f;
+						NxXbox::EngineGlobals.pixel_shader_constants[4]		= max;		// The part the shadow affects.
+						NxXbox::EngineGlobals.pixel_shader_constants[5]		= max;
+						NxXbox::EngineGlobals.pixel_shader_constants[6]		= max;
+						NxXbox::EngineGlobals.pixel_shader_constants[7]		= 1.0f;
+						NxXbox::EngineGlobals.upload_pixel_shader_constants	= true;
+
+						// Set the pixel shader that will do the shadow attenuation.			
+						extern DWORD PixelShader_ShadowBuffer;
+						set_pixel_shader( PixelShader_ShadowBuffer );
+						EngineGlobals.pixel_shader_override = 1;
+
+						// Set the material properties used for shadow modulation.
+						set_blend_mode( vBLEND_MODE_MODULATE_COLOR );
+						set_texture( 0, NULL );
+						set_texture( 1, NULL );
+						set_texture( 2, NULL );
+						EngineGlobals.material_override = 1;
+
+						// Set texture stage 3 to use the shadow buffer.
+						EngineGlobals.texture_stage_override |= ( 1 << 3 );
+						set_texture( 3, NULL );
+						set_texture( 3, p_details->p_texture->pD3DTexture );
+						set_render_state( RS_UVADDRESSMODE0 + 3, 0x00020002UL );
+						D3DDevice_SetTextureStageState( 3, D3DTSS_BORDERCOLOR, 0x00000000UL );
+						D3DDevice_SetTextureStageState( 3, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );
+						D3DDevice_SetTextureStageState( 3, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+
+						D3DDevice_SetTextureStageState( 3, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );
+						D3DDevice_SetTextureStageState( 3, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU | 3 );
+
+						// Set shadowbuffer state.
+						D3DDevice_SetRenderState( D3DRS_SHADOWFUNC, D3DCMP_GREATER );
+
+						// Render the shadow pass with fogging disabled.
+						if( EngineGlobals.fog_enabled )
+						{
+							D3DDevice_SetRenderState( D3DRS_FOGENABLE, FALSE );
+							render_scene( GetScene(), flags | vRENDER_NO_CULLING );
+							D3DDevice_SetRenderState( D3DRS_FOGENABLE, TRUE );
+						}
+						else
+						{
+							render_scene( GetScene(), flags | vRENDER_NO_CULLING );
+						}
+
+						// Restore shadowbuffer state.
+						D3DDevice_SetRenderState( D3DRS_SHADOWFUNC, D3DCMP_NEVER );
+
+						// Scan through each mesh in this scene, restoring the vertex shader to the original.
+						for( int m = 0; m < p_scene->m_num_mesh_entries; ++m )
+						{
+							sMesh *p_mesh = p_scene->m_meshes[m];
+							p_mesh->PopVertexShader();
+						}
+					}
+				}
+			}
+		}
+
+		// Restore state.
+		EngineGlobals.vertex_shader_override	= 0;
+		EngineGlobals.pixel_shader_override		= 0;
+		EngineGlobals.material_override			= 0;
+		EngineGlobals.texture_stage_override	&= ~( 1 << 3 );
+		set_texture( 3, NULL );
+
+//		shutdown_weighted_mesh_vertex_shader();
+	}
+}
+
+
+
+// Test code...
+void CInstance::RenderShadowVolume( void )
+{
+	if( GetBoneTransforms())
+	{
+		XGMATRIX root_matrix = *((XGMATRIX*)GetTransform());
+
+		sScene*	p_scene = GetScene();
+
+		// Process each mesh in turn.
+		for( int m = 0; m < p_scene->m_num_mesh_entries; ++m )
+		{
+			sMesh* p_mesh	= p_scene->m_meshes[m];
+
+			// Lock the vertex buffer so we can read the data.
+			BYTE* p_byte;
+			p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_byte, D3DLOCK_READONLY );
+
+			// Grab a buffer for the transformed points.
+			XGVECTOR4*	p_t_buffer	= new XGVECTOR4[p_mesh->m_num_vertices];
+
+			for( int v = 0; v < p_mesh->m_num_vertices; ++v )
+			{
+				// Get untransformed point.
+				XGVECTOR3*	p_source_vertex	= (XGVECTOR3*)( p_byte + ( v * p_mesh->m_vertex_stride ));
+
+				// Get and unpack weights.
+				uint32		packed_weights	= *((uint32*)( p_byte + ( v * p_mesh->m_vertex_stride ) + 12 ));
+				float		w0				= (( packed_weights >> 0 ) & 0x7ff ) * ( 1.0f / 1023.0f );
+				float		w1				= (( packed_weights >> 11 ) & 0x7ff ) * ( 1.0f / 1023.0f );
+				float		w2				= (( packed_weights >> 22 ) & 0x3ff ) * ( 1.0f / 511.0f );
+
+				// Get and unpack matrix indices.
+				uint32		packed_indices	= *((uint32*)( p_byte + ( v * p_mesh->m_vertex_stride ) + 16 ));
+				uint32		i0				= ( packed_indices >> 0 ) & 0xff;
+				uint32		i1				= ( packed_indices >> 8 ) & 0xff;
+				uint32		i2				= ( packed_indices >> 16 ) & 0xff;
+
+				// Get bone matrix pointers.
+				XGMATRIX*	p_bone_mat0		= (XGMATRIX*)( GetBoneTransforms() + i0 );
+				XGMATRIX*	p_bone_mat1		= (XGMATRIX*)( GetBoneTransforms() + i1 );
+				XGMATRIX*	p_bone_mat2		= (XGMATRIX*)( GetBoneTransforms() + i2 );
+
+				XGVECTOR4	tp0;
+				XGVECTOR4	tp1;
+				XGVECTOR4	tp2;
+
+				// Transform point by bone matrix 0, 1 and 2 and scale with weight 0, 1, 2.
+				XGVec3Transform( &tp0, p_source_vertex, p_bone_mat0 );
+				XGVec3Scale((XGVECTOR3*)&tp0, (XGVECTOR3*)&tp0, w0 );
+
+				XGVec3Transform( &tp1, p_source_vertex, p_bone_mat1 );
+				XGVec3Scale((XGVECTOR3*)&tp1, (XGVECTOR3*)&tp1, w1 );
+
+				XGVec3Transform( &tp2, p_source_vertex, p_bone_mat2 );
+				XGVec3Scale((XGVECTOR3*)&tp2, (XGVECTOR3*)&tp2, w2 );
+
+				// Obtain cumulative result.
+				tp0 = tp0 + tp1 + tp2;
+
+				// Tranform point by object transform.
+				XGVec3Transform( p_t_buffer + v, (XGVECTOR3*)&tp0, &root_matrix );
+			}
+			delete [] p_t_buffer;
+		}
+	}
+}
+
+
+
+
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/instance.h b/Code/Gfx/XBox/NX/instance.h
new file mode 100644
index 0000000..ac6939a
--- /dev/null
+++ b/Code/Gfx/XBox/NX/instance.h
@@ -0,0 +1,62 @@
+#ifndef __INSTANCE_H
+#define __INSTANCE_H
+
+
+#include 
+#include 
+#include "scene.h"
+
+namespace NxXbox
+{
+
+void	render_instance( CInstance *p_instance, uint32 flags );
+void	render_instances( uint32 flags );
+
+class CInstance
+{
+public:
+
+	enum EInstanceFlag
+	{
+		INSTANCE_FLAG_DELETE_ATTACHED_SCENE	= 0x01,
+		INSTANCE_FLAG_LIGHTING_ALLOWED		= 0x02
+	};
+	
+	void			SetTransform( Mth::Matrix &transform )	{ m_transform = transform; }
+	Mth::Matrix*	GetTransform(void)						{ return &m_transform; }
+	int				GetNumBones( void )						{ return m_num_bones; }
+	Mth::Matrix*	GetBoneTransforms( void )				{ return mp_bone_transforms; }
+	void			SetBoneTransforms( Mth::Matrix* p_t )	{ mp_bone_transforms = p_t; }
+	sScene*			GetScene( void )						{ return mp_scene; }
+	void			SetActive( bool active )				{ m_active = active; }
+	bool			GetActive( void )						{ return m_active; }
+	void			SetFlag( EInstanceFlag flag )			{ m_flags |= flag; }
+	void			ClearFlag( EInstanceFlag flag )			{ m_flags &= ~flag; }
+	void			SetModel( Nx::CModel *p_model )			{ mp_model = p_model; }
+	Nx::CModel*		GetModel( void )						{ return mp_model; }
+	CInstance*		GetNextInstance( void )				{ return mp_next_instance; }
+
+					CInstance( sScene *pScene, Mth::Matrix &transform, int numBones, Mth::Matrix *pBoneTransforms );
+					~CInstance();
+
+	void			Render( uint32 flags );
+	void			RenderShadowVolume( void );
+
+private:
+	uint32			m_flags;
+	Mth::Matrix		m_transform;
+	Mth::Matrix*	mp_bone_transforms;
+	int				m_num_bones;
+	bool			m_active;
+	Nx::CModel*		mp_model;		// Required in order to get pointer to CXboxLights structure at render time.
+	sScene*			mp_scene;
+	CInstance*		mp_next_instance;
+};
+
+
+
+} // namespace NxXbox
+
+
+#endif // __INSTANCE_H
+
diff --git a/Code/Gfx/XBox/NX/material.cpp b/Code/Gfx/XBox/NX/material.cpp
new file mode 100644
index 0000000..c7af17b
--- /dev/null
+++ b/Code/Gfx/XBox/NX/material.cpp
@@ -0,0 +1,1180 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "gfx/xbox/p_nxtexture.h"
+#include "anim_vertdefs.h"
+#include "nx_init.h"
+#include "material.h"
+#include "scene.h"
+#include "render.h"
+
+namespace NxXbox
+{
+
+
+uint32						NumMaterials;
+
+static const float pi_over_180 = (float)Mth::PI / 180.0f;
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sUVWibbleParams::sUVWibbleParams( void )
+{
+	// Zero out the members.
+	ZeroMemory( this, sizeof( sUVWibbleParams ));
+
+	// Set the matrix correctly.
+	m_UVMatrix[0] = 1.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sUVWibbleParams::~sUVWibbleParams( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMaterial::sMaterial( void )
+{
+	m_num_wibble_vc_anims		= 0;
+	mp_wibble_vc_params			= NULL;
+	mp_wibble_vc_colors			= NULL;
+	mp_wibble_texture_params	= NULL;
+	m_uv_wibble					= false;
+	m_texture_wibble			= false;
+	m_grass_layers				= 0;
+	m_zbias						= 0;
+	for( int p = 0; p < MAX_PASSES; ++p )
+	{
+		m_flags[p]				= 0;
+		mp_UVWibbleParams[p]	= NULL;
+
+		m_envmap_tiling[p][0]	= 3.0f;
+		m_envmap_tiling[p][1]	= 3.0f;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMaterial::~sMaterial( void )
+{
+	if( mp_wibble_vc_params	)
+	{
+		for( uint32 i = 0; i < m_num_wibble_vc_anims; ++i )
+		{
+			delete [] mp_wibble_vc_params[i].mp_keyframes;
+		}
+		delete [] mp_wibble_vc_params;
+	}
+	if( mp_wibble_vc_colors	)
+	{
+		delete [] mp_wibble_vc_colors;
+	}
+
+	for( int p = 0; p < MAX_PASSES; ++p )
+	{
+		if( mp_UVWibbleParams[p] )
+			delete mp_UVWibbleParams[p];
+	}
+
+	if( mp_wibble_texture_params )
+	{
+		for( uint32 p = 0; p < MAX_PASSES; ++p )
+		{
+			delete [] mp_wibble_texture_params->mp_keyframes[p];
+		}
+		delete mp_wibble_texture_params;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMaterial::figure_wibble_uv( void )
+{
+	if( m_uv_wibble )
+	{
+		// Get the time.
+		float t = (float)Tmr::GetTime() * 0.001f;
+
+		for( uint32 p = 0; p < m_passes; ++p )
+		{
+			// Figure out UV offsets for wibbling if required.
+			if( m_flags[p] & MATFLAG_UV_WIBBLE )
+			{
+				if( !( m_flags[p] & MATFLAG_EXPLICIT_UV_WIBBLE ))
+				{
+					float uoff, voff;
+			
+					uoff	= ( t * mp_UVWibbleParams[p]->m_UVel ) + ( mp_UVWibbleParams[p]->m_UAmplitude * sinf( mp_UVWibbleParams[p]->m_UFrequency * t + mp_UVWibbleParams[p]->m_UPhase ));
+					voff	= ( t * mp_UVWibbleParams[p]->m_VVel ) + ( mp_UVWibbleParams[p]->m_VAmplitude * sinf( mp_UVWibbleParams[p]->m_VFrequency * t + mp_UVWibbleParams[p]->m_VPhase ));
+
+					// Reduce offset mod 16 and put it in the range -8 to +8.
+					uoff	+= 8.0f;
+					uoff	-= (float)(( Ftoi_ASM( uoff ) >> 4 ) << 4 );
+					voff	+= 8.0f;
+					voff	-= (float)(( Ftoi_ASM( voff ) >> 4 ) << 4 );
+
+					mp_UVWibbleParams[p]->m_UVMatrix[2]	= ( uoff < 0.0f ) ? ( uoff + 8.0f ) : ( uoff - 8.0f );
+					mp_UVWibbleParams[p]->m_UVMatrix[3]	= ( voff < 0.0f ) ? ( voff + 8.0f ) : ( voff - 8.0f );
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMaterial::figure_wibble_vc( void )
+{
+	// The vertex color wibble flag is placed in pass 0.
+	if( m_flags[0] & MATFLAG_VC_WIBBLE )
+	{
+		for( uint32 i = 0; i < m_num_wibble_vc_anims; ++i )
+		{
+			struct sVCWibbleParams	*p_sequence		= mp_wibble_vc_params + i;
+			
+			// Get phase-shift.
+			int						phase_shift		= p_sequence->m_phase;
+
+			// Time parameters.
+			int						num_keys		= p_sequence->m_num_keyframes;
+			int						start_time		= p_sequence->mp_keyframes[0].m_time;
+			int						end_time		= p_sequence->mp_keyframes[num_keys - 1].m_time;
+			int						period			= end_time - start_time;
+			int						time			= start_time + ( NxXbox::EngineGlobals.render_start_time + phase_shift ) % period;
+
+			// Locate the keyframe.
+			int key;
+			for( key = num_keys - 1; key >= 0; --key )
+			{
+				if( time >= p_sequence->mp_keyframes[key].m_time )
+				{
+					break;
+				}
+			}
+			
+			Dbg_Assert( key < ( num_keys - 1 ));
+
+			// Parameter expressing how far we are between between this keyframe and the next.
+			float					t				= (float)( time - p_sequence->mp_keyframes[key].m_time ) * ReciprocalEstimateNR_ASM((float)( p_sequence->mp_keyframes[key + 1].m_time - p_sequence->mp_keyframes[key].m_time ));
+
+			// Interpolate the color.
+			uint32 red = (uint32)Ftoi_ASM((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.r ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.r ));
+			uint32 grn = (uint32)Ftoi_ASM((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.g ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.g ));
+			uint32 blu = (uint32)Ftoi_ASM((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.b ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.b ));
+			uint32 alp = (uint32)Ftoi_ASM((( 1.0f - t ) * p_sequence->mp_keyframes[key].m_color.a ) + ( t * p_sequence->mp_keyframes[key + 1].m_color.a ));
+
+			// Switch red and blue, and store.
+			mp_wibble_vc_colors[i] = ( alp << 24 ) | ( red << 16 ) | ( grn << 8 ) | blu;
+		}
+	}
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMaterial::figure_wibble_texture( void )
+{
+	// The vertex color wibble flag is placed in pass 0.
+	if( m_texture_wibble )
+	{
+		int current_time = (int)Tmr::GetTime();
+
+		struct sTextureWibbleParams	*p_sequence		= mp_wibble_texture_params;
+			
+		for( int pass = 0; pass < MAX_PASSES; ++pass )
+		{
+			if( m_flags[pass] & MATFLAG_PASS_TEXTURE_ANIMATES )
+			{
+				// Get phase-shift.
+				int						phase_shift	= p_sequence->m_phase[pass];
+
+				// Time parameters.
+				int						num_keys	= p_sequence->m_num_keyframes[pass];
+				int						start_time	= p_sequence->mp_keyframes[pass][0].m_time;
+				int						end_time	= p_sequence->mp_keyframes[pass][num_keys - 1].m_time;
+				int						period		= end_time - start_time;
+				int						time		= start_time + (( current_time + phase_shift ) % period );
+
+				// Keep track of the iterations, if iterations on this animation are required.
+				if( p_sequence->m_num_iterations[pass] > 0 )
+				{
+					int iteration = ( current_time - start_time ) / period;
+					if( iteration >= p_sequence->m_num_iterations[pass] )
+					{
+						// Set the time such that the animation no longer continues.
+						time = start_time + ((( period * p_sequence->m_num_iterations[pass] ) + phase_shift ) % period );
+					}
+				}
+
+				// Locate the keyframe.
+				int key;
+				for( key = num_keys - 1; key >= 0; --key )
+				{
+					if( time >= p_sequence->mp_keyframes[pass][key].m_time )
+					{
+						break;
+					}
+				}
+			
+				// Set the texture.
+//				Dbg_Assert( p_sequence->mp_keyframes[pass][key].mp_texture );
+				mp_tex[pass] = p_sequence->mp_keyframes[pass][key].mp_texture;
+			}
+		}
+	}
+}
+
+
+
+inline DWORD F2DW( FLOAT f )
+{
+	return *( ( DWORD *) & f );
+}
+
+					
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMaterial::Submit( void )
+{
+	// Dummy 8 element uv 'matrix' for custom pipeline.
+	static float custom_uv_mat[8] = {	1.0f, 0.0f, 0.0f, 0.0f,
+										0.0f, 1.0f, 0.0f, 0.0f };
+
+	// Matrix for UV wibbling texture transform.
+	static D3DMATRIX uv_mat = { 1.0f, 0.0f, 0.0f, 0.0f,
+								0.0f, 1.0f, 0.0f, 0.0f,
+								0.0f, 0.0f, 0.0f, 0.0f,
+								0.0f, 0.0f, 0.0f, 0.0f };
+
+	// Matrix for env mapping texture transform. Note that the [0][0] and [1][1] elements of the matrix are set up to scale, based on the
+	// material pass properties.
+	static D3DMATRIX env_mat = { 0.5f,  0.0f, 0.0f, 0.0f,
+								 0.0f, -0.5f, 0.0f, 0.0f,
+								 0.0f,  0.0f, 0.0f,	0.0f,
+								 0.5f,  0.5f, 0.0f, 0.0f };
+	
+	// Check for material change lockout.
+	if( EngineGlobals.material_override )
+	{
+		return;
+	}
+
+	// Set the alpha blend mode.
+	set_blend_mode( m_reg_alpha[0] );
+	
+	// Set the alpha cutoff value.
+	set_render_state( RS_ALPHACUTOFF, (uint32)m_alpha_cutoff );
+	
+	// Set the backface cull mode.
+	set_render_state( RS_CULLMODE, m_no_bfc ? D3DCULL_NONE : D3DCULL_CW );
+
+	// Set the z-bias.
+	set_render_state( RS_ZBIAS, m_zbias );
+	
+	// Figure uv, vc and texture wibble updates if required.
+	figure_wibble_uv();
+	figure_wibble_vc();
+	figure_wibble_texture();
+	
+	// Set specular properties of this material.
+	if( m_flags[0] & MATFLAG_SPECULAR )
+	{
+		if( EngineGlobals.specular_enabled == 0 )
+		{
+			set_render_state( RS_SPECULARENABLE, 1 );
+
+			// Set the specular material.
+			D3DMATERIAL8 test_mat;
+			ZeroMemory( &test_mat, sizeof( D3DMATERIAL8 ));
+			test_mat.Specular.r	= m_specular_color[0];
+			test_mat.Specular.g	= m_specular_color[1];
+			test_mat.Specular.b	= m_specular_color[2];
+			test_mat.Specular.a	= 1.0f;
+			test_mat.Power		= m_specular_color[3];
+			D3DDevice_SetMaterial( &test_mat );
+
+			// If using a custom vertex shader, also need to load the specular color and power to vert shader registers here.
+			if( NxXbox::EngineGlobals.custom_pipeline_enabled )
+			{
+				D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_SPECULAR_COLOR_OFFSET, (void*)&m_specular_color[0], 1 );
+			}
+		}
+	}
+	else
+	{
+		if( EngineGlobals.specular_enabled > 0 )
+		{
+			set_render_state( RS_SPECULARENABLE, 0 );
+
+			// Set the specular material (basically just to set the power to zero).
+			D3DMATERIAL8 test_mat;
+			ZeroMemory( &test_mat, sizeof( D3DMATERIAL8 ));
+			D3DDevice_SetMaterial( &test_mat );
+		}
+	}
+
+	// Set up the textures if present. This involves setting the texture and palette pointers and addressing mode.
+	// Also, for multipass textures, it may require loading of fixed alpha values into specific constant registers.
+	uint32 p;
+	for( p = 0; p < m_passes; ++p )
+	{
+		if( !( EngineGlobals.texture_stage_override & ( 1 << p )))
+		{
+			// Load pass color and fixed alpha values to the pixel shader, if required.
+			if( !EngineGlobals.upload_pixel_shader_constants )
+			{
+				// Do the comparison as 32bit unsigned ints.
+				uint32 *p_test0 = (uint32*)&m_color[p][0];
+				uint32 *p_test1 = (uint32*)&EngineGlobals.pixel_shader_constants[p * 4];
+
+				if(( p_test0[0] != p_test1[0] ) || ( p_test0[1] != p_test1[1] ) || ( p_test0[2] != p_test1[2] ) || ( p_test0[3] != p_test1[3] ))
+				{
+					EngineGlobals.upload_pixel_shader_constants = true;
+				}
+			}
+
+			if( mp_tex[p] )
+			{
+				set_texture( p, mp_tex[p]->pD3DTexture, mp_tex[p]->pD3DPalette );
+
+				// Set UV adressing mode.
+				set_render_state( RS_UVADDRESSMODE0 + p,	m_uv_addressing[p] );
+
+				// Set filtering mode.
+//				set_render_state( RS_MINMAGFILTER0 + p,		m_filtering_mode[p] );
+						
+				// Set MIP lod bias.
+				set_render_state( RS_MIPLODBIASPASS0 + p,	*((uint32*)( &m_k[p] )));
+
+				// Deal with bump mapping setup.
+				if( m_flags[p] & MATFLAG_BUMP_SIGNED_TEXTURE )
+				{
+					// Channel of bump texture value (v,u) MUST be signed.
+					if( EngineGlobals.color_sign[p] != ( D3DTSIGN_RSIGNED | D3DTSIGN_GSIGNED | D3DTSIGN_BSIGNED ))
+					{
+						EngineGlobals.color_sign[p] = ( D3DTSIGN_RSIGNED | D3DTSIGN_GSIGNED | D3DTSIGN_BSIGNED );
+						D3DDevice_SetTextureStageState( p, D3DTSS_COLORSIGN, D3DTSIGN_RSIGNED | D3DTSIGN_GSIGNED | D3DTSIGN_BSIGNED );
+					}
+				}
+				else
+				{
+					if( EngineGlobals.color_sign[p] != ( D3DTSIGN_RUNSIGNED | D3DTSIGN_GUNSIGNED | D3DTSIGN_BUNSIGNED ))
+					{
+						EngineGlobals.color_sign[p] = ( D3DTSIGN_RUNSIGNED | D3DTSIGN_GUNSIGNED | D3DTSIGN_BUNSIGNED );
+						D3DDevice_SetTextureStageState( p, D3DTSS_COLORSIGN, D3DTSIGN_RUNSIGNED | D3DTSIGN_GUNSIGNED | D3DTSIGN_BUNSIGNED );
+					}
+				}
+
+				if( m_flags[p] & MATFLAG_BUMP_LOAD_MATRIX )
+				{
+					D3DDevice_SetTextureStageState( p, D3DTSS_BUMPENVMAT00, F2DW( EngineGlobals.bump_env_matrix._11 ));
+					D3DDevice_SetTextureStageState( p, D3DTSS_BUMPENVMAT01, F2DW( EngineGlobals.bump_env_matrix._13 ));
+					D3DDevice_SetTextureStageState( p, D3DTSS_BUMPENVMAT10, F2DW( EngineGlobals.bump_env_matrix._31 ));
+					D3DDevice_SetTextureStageState( p, D3DTSS_BUMPENVMAT11, F2DW( EngineGlobals.bump_env_matrix._33 ));
+				}
+
+				if(( m_flags[p] & MATFLAG_ENVIRONMENT ) && ( EngineGlobals.allow_envmapping ))
+				{
+					// Handle environment mapping.
+					env_mat.m[0][0] = 0.5f * m_envmap_tiling[p][0];
+					env_mat.m[1][1] = 0.5f * m_envmap_tiling[p][1];
+
+					D3DDevice_SetTransform((D3DTRANSFORMSTATETYPE)( D3DTS_TEXTURE0 + p ), &env_mat );
+					D3DDevice_SetTextureStageState( p, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 );
+					D3DDevice_SetTextureStageState( p, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR | p );
+				}
+				else if( m_flags[p] & MATFLAG_UV_WIBBLE )
+				{
+					D3DDevice_SetTextureStageState( p, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2 );
+					D3DDevice_SetTextureStageState( p, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU | p );
+
+					if( NxXbox::EngineGlobals.custom_pipeline_enabled )
+					{
+						// If using a custom vertex shader, need to load the uv matrix here.
+						// If using a custom vertex shader, need to load the custom uv matrix here.
+						custom_uv_mat[0] =  mp_UVWibbleParams[p]->m_UVMatrix[0];
+						custom_uv_mat[1] = -mp_UVWibbleParams[p]->m_UVMatrix[1];
+						custom_uv_mat[3] =  mp_UVWibbleParams[p]->m_UVMatrix[2];
+
+						custom_uv_mat[4] =  mp_UVWibbleParams[p]->m_UVMatrix[1];
+						custom_uv_mat[5] =	mp_UVWibbleParams[p]->m_UVMatrix[0];
+						custom_uv_mat[7] =  mp_UVWibbleParams[p]->m_UVMatrix[3];
+
+						D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_UV_MAT_OFFSET + ( p * 2 ), (void*)&custom_uv_mat[0], 2 );
+					}
+					else
+					{
+						// Handle fixed function UV matrix.
+						uv_mat._31 = mp_UVWibbleParams[p]->m_UVMatrix[2];
+						uv_mat._32 = mp_UVWibbleParams[p]->m_UVMatrix[3];
+						
+						D3DDevice_SetTransform((D3DTRANSFORMSTATETYPE)( D3DTS_TEXTURE0 + p ), &uv_mat );
+					}
+				}
+				else
+				{
+					if( NxXbox::EngineGlobals.custom_pipeline_enabled )
+					{
+						// If using a custom vertex shader, need to load the custom uv matrix here.
+						custom_uv_mat[0] = 1.0f; custom_uv_mat[1] = 0.0f; custom_uv_mat[3] = 0.0f;
+						custom_uv_mat[4] = 0.0f; custom_uv_mat[5] = 1.0f; custom_uv_mat[7] = 0.0f;
+						D3DDevice_SetVertexShaderConstantFast( VSCONST_REG_UV_MAT_OFFSET + ( p * 2 ), (void*)&custom_uv_mat[0], 2 );
+					}
+
+					// Regular case. Check states here since it is quicker for this common case than setting blindly.
+					uint32 tex_coord_index, tex_trans_flags;
+
+					D3DDevice_GetTextureStageState( p, D3DTSS_TEXTURETRANSFORMFLAGS, &tex_trans_flags );
+					D3DDevice_GetTextureStageState( p, D3DTSS_TEXCOORDINDEX, &tex_coord_index );
+
+					if( tex_trans_flags != D3DTTFF_DISABLE )
+					{
+						D3DDevice_SetTextureStageState( p, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );
+					}
+					if( tex_coord_index != ( D3DTSS_TCI_PASSTHRU | p ))
+					{
+						D3DDevice_SetTextureStageState( p, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU | p );
+					}
+				}
+			}
+			else
+			{
+				set_texture( p, NULL, NULL );
+			}
+		}
+	}
+
+	if( EngineGlobals.upload_pixel_shader_constants )
+	{
+		CopyMemory( EngineGlobals.pixel_shader_constants, &m_color[0][0], sizeof( float ) * 4 * m_passes );
+	}
+
+	// Make sure to set the textures for unused stages to NULL, to reduce texture overhead.
+//	for( ; p < 4; ++p )
+//	{
+//		if( !( EngineGlobals.texture_stage_override & ( 1 << p )))
+//		{
+//			set_texture( p, NULL, NULL );
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 sMaterial::GetIgnoreVertexAlphaPasses( void )
+{
+	// Return a bitfield with a bit set for any pass that is flagged to ignore vertex alpha.
+	uint32 bf = 0;
+
+	for( uint32 p = 0; p < m_passes; ++p )
+	{
+		if( m_flags[p] & MATFLAG_PASS_IGNORE_VERTEX_ALPHA )
+		{
+			bf |= ( 1 << p );
+		}
+	}
+	
+	return bf;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMaterial* GetMaterial( uint32 checksum, sScene *p_scene )
+{
+	if( p_scene->pMaterialTable )
+	{
+		p_scene->pMaterialTable->IterateStart();
+		sMaterial *p_mat = p_scene->pMaterialTable->IterateNext();
+		while( p_mat )
+		{
+			if( p_mat->m_checksum == checksum )
+			{
+				return p_mat;
+			}
+			p_mat = p_scene->pMaterialTable->IterateNext();
+		}
+	}
+	return NULL;
+}
+
+
+
+
+#define MemoryRead( dst, size, num, src )	CopyMemory(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Lst::HashTable< sMaterial >	*LoadMaterialsFromMemory( void **pp_mem, Lst::HashTable< Nx::CTexture > *p_texture_table )
+{
+	uint8	*p_data = (uint8*)( *pp_mem );
+	uint32	MMAG, MMIN, K, L, NumSeqs, seq, NumKeys;
+	
+	// Get number of materials.
+	uint32 new_materials;
+	MemoryRead( &new_materials, sizeof( uint32 ), 1, p_data );
+	
+	Lst::HashTable< sMaterial >* pMaterialTable;
+
+	// Create table, dynamically sizing it based on the number of new materials.
+	uint32 optimal_table_size	= new_materials * 2;
+	uint32 test					= 4;
+	uint32 size					= 2;
+
+	for( ;; test <<= 1, ++size )
+	{
+		// Check if this iteration of table size is sufficient, or if we have hit the maximum size.
+		if(( optimal_table_size < test ) || ( size >= 12 ))
+		{
+			pMaterialTable = new Lst::HashTable< sMaterial >( size );
+			break;
+		}
+	}
+	
+	// Loop over materials.
+	for( uint32 i = 0; i < new_materials; ++i )
+	{
+		// Create new material.
+		sMaterial *pMat = new sMaterial;
+
+		// Get material checksum.
+		MemoryRead( &pMat->m_checksum, sizeof( uint32 ), 1, p_data );
+
+		// Get material name checksum.
+		MemoryRead( &pMat->m_name_checksum, sizeof( uint32 ), 1, p_data );
+
+		// Get number of passes.
+		MemoryRead( &pMat->m_passes, sizeof( uint32 ), 1, p_data );
+
+		// Get alpha cutoff value.
+		uint32 AlphaCutoff;
+		MemoryRead( &AlphaCutoff, sizeof( uint32 ), 1, p_data );
+		Dbg_Assert( AlphaCutoff <= 0xFF );
+		pMat->m_alpha_cutoff = (uint8)AlphaCutoff;
+
+		// Get sorted flag.
+		MemoryRead( &pMat->m_sorted, sizeof( bool ), 1, p_data );
+		
+		// Get draw order.
+		MemoryRead( &pMat->m_draw_order, sizeof( float ), 1, p_data );
+		
+		// Get single sided flag.
+		bool single_sided;
+		MemoryRead( &single_sided, sizeof( bool ), 1, p_data );
+
+		// Get backface cull flag.
+		MemoryRead( &pMat->m_no_bfc, sizeof( bool ), 1, p_data );
+
+		// Get z-bias value.
+		int zbias;
+		MemoryRead( &zbias, sizeof( int ), 1, p_data );
+		pMat->m_zbias = (uint8)(( zbias > 16 ) ? 16 : zbias );
+
+		// Get grassify flag and (optionally) grassify data.
+		bool grassify;
+		MemoryRead( &grassify, sizeof( bool ), 1, p_data );
+		if( grassify )
+		{
+			MemoryRead( &pMat->m_grass_height, sizeof( float ), 1, p_data );
+			MemoryRead( &pMat->m_grass_layers, sizeof( int ), 1, p_data );
+		}
+
+		// Get specular power and (optionally) specular color.
+		MemoryRead( &pMat->m_specular_color[3], sizeof( float ), 1, p_data );
+		if( pMat->m_specular_color[3] > 0.0f )
+		{
+			MemoryRead( &pMat->m_specular_color[0], sizeof( float ) * 3, 1, p_data );
+		}
+
+		// Neutral under proven otherwise.
+		bool neutral_material_color = true;
+		for( uint32 pass = 0; pass < pMat->m_passes; ++pass )
+		{
+			// Get texture checksum.
+			uint32 TextureChecksum;
+			MemoryRead( &TextureChecksum, sizeof( uint32 ), 1, p_data );
+
+			// Get material flags.
+			MemoryRead( &pMat->m_flags[pass], sizeof( uint32 ), 1, p_data );
+
+			// Get pass color flag.
+			bool has_color;
+			MemoryRead( &has_color, sizeof( bool ), 1, p_data );
+
+			// Get pass color.
+			MemoryRead( &pMat->m_color[pass][0], sizeof( float ) * 3, 1, p_data );
+
+			// Check for color being neutral.
+			if( neutral_material_color )
+			{
+				if(( pMat->m_color[pass][0] != 0.5f ) || ( pMat->m_color[pass][1] != 0.5f ) || ( pMat->m_color[pass][2] != 0.5f ))
+				{
+					neutral_material_color = false;
+				}
+			}
+			
+			// Get ALPHA register value.
+			uint64 reg_alpha;
+			MemoryRead( ®_alpha, sizeof( uint64 ), 1, p_data );
+
+			uint32	blend_mode		= (uint32)( reg_alpha & 0xFFFFFFUL );
+			uint32	fixed_alpha		= (uint32)( reg_alpha >> 32 );
+			pMat->m_reg_alpha[pass]	= blend_mode | ( fixed_alpha << 24 );
+
+			// Also calculate the floating point version of the fixed alpha.
+			pMat->m_color[pass][3]	= fixed_alpha / 128.0f;
+
+			// Backface cull test - if this is an alpha blended material, turn off backface culling, except
+			// where the material has been explicitly flagged as single sided.
+			if(( pass == 0 ) && !single_sided && (( pMat->m_reg_alpha[pass] & sMaterial::BLEND_MODE_MASK ) != 0x00 ))
+			{
+				pMat->m_no_bfc = true;
+			}
+
+			// Get UV addressing types.
+			uint32 u_addressing, v_addressing;
+			MemoryRead( &u_addressing, sizeof( uint32 ), 1, p_data );
+			MemoryRead( &v_addressing, sizeof( uint32 ), 1, p_data );
+			pMat->m_uv_addressing[pass] = (( v_addressing << 16 ) | u_addressing );
+			
+			// Get environment map u and v tiling multiples.
+			MemoryRead( &pMat->m_envmap_tiling[pass][0], sizeof( float ) * 2, 1, p_data );
+
+			// Get minification and magnification filtering mode.
+			MemoryRead( &pMat->m_filtering_mode[pass], sizeof( uint32 ), 1, p_data );
+
+			// Read uv wibble data if present.
+			if( pMat->m_flags[pass] & MATFLAG_UV_WIBBLE )
+			{
+				// Flag that this material wibbles.
+				pMat->m_uv_wibble = true;
+				
+				// Create uv wibble params structure.
+				pMat->mp_UVWibbleParams[pass] = new sUVWibbleParams;
+				MemoryRead( pMat->mp_UVWibbleParams[pass], sizeof( float ) * 8, 1, p_data );
+			}
+
+			// Read vertex color wibble data.
+			if(( pass == 0 ) && ( pMat->m_flags[0] & MATFLAG_VC_WIBBLE ))
+			{
+				MemoryRead( &NumSeqs, sizeof( uint32 ), 1, p_data );
+				pMat->m_num_wibble_vc_anims = NumSeqs;
+
+				// Create sequence data array.
+				pMat->mp_wibble_vc_params = new sVCWibbleParams[NumSeqs];
+				
+				// Create resultant color array.
+				pMat->mp_wibble_vc_colors = new D3DCOLOR[NumSeqs];
+
+				for( seq = 0; seq < NumSeqs; ++seq )
+				{ 
+					MemoryRead( &NumKeys, sizeof( uint32 ), 1, p_data );
+
+					int phase;
+					MemoryRead( &phase, sizeof( int ), 1, p_data );
+
+					// Create array for keyframes.
+					pMat->mp_wibble_vc_params[seq].m_num_keyframes	= NumKeys;
+					pMat->mp_wibble_vc_params[seq].m_phase			= phase;
+					pMat->mp_wibble_vc_params[seq].mp_keyframes		= new sVCWibbleKeyframe[NumKeys];
+
+					// Read keyframes into array.
+					MemoryRead( pMat->mp_wibble_vc_params[seq].mp_keyframes, sizeof( sVCWibbleKeyframe ), NumKeys, p_data );
+				}
+			}
+
+			// Read texture wibble data.
+			if( pMat->m_flags[pass] & MATFLAG_PASS_TEXTURE_ANIMATES )
+			{
+				// Create the texture wibble structure if not created yet.
+				if( pMat->mp_wibble_texture_params == NULL )
+				{
+					pMat->mp_wibble_texture_params = new NxXbox::sTextureWibbleParams;
+					ZeroMemory( pMat->mp_wibble_texture_params, sizeof( NxXbox::sTextureWibbleParams ));
+
+					// Flag the material as having texture wibble.
+					pMat->m_texture_wibble = true;
+				}
+
+				int num_keyframes, period, iterations, phase;
+				MemoryRead( &num_keyframes, sizeof( int ), 1, p_data );
+				MemoryRead( &period, sizeof( int ), 1, p_data );			// This value is currently discarded.
+				MemoryRead( &iterations, sizeof( int ), 1, p_data );
+				MemoryRead( &phase, sizeof( int ), 1, p_data );
+
+				Dbg_Assert( num_keyframes > 0 );
+
+				pMat->mp_wibble_texture_params->m_num_keyframes[pass]	= num_keyframes;
+				pMat->mp_wibble_texture_params->m_phase[pass]			= phase;
+				pMat->mp_wibble_texture_params->m_num_iterations[pass]	= iterations;
+				pMat->mp_wibble_texture_params->mp_keyframes[pass]		= new NxXbox::sTextureWibbleKeyframe[num_keyframes];
+
+				for( int ati = 0; ati < num_keyframes; ++ati )
+				{
+					MemoryRead( &pMat->mp_wibble_texture_params->mp_keyframes[pass][ati].m_time,		sizeof( uint32 ), 1, p_data );
+
+					// Read the texture checksum.
+					uint32 cs;
+					MemoryRead( &cs, sizeof( uint32 ), 1, p_data );
+
+					// Set the TextureChecksum variable so the mp_tex member will get populated.
+					if( ati == 0 )
+					{
+						TextureChecksum = cs;
+					}
+
+					// Resolve the checksum to a texture pointer.
+					Nx::CXboxTexture *p_xbox_texture = static_cast( p_texture_table->GetItem( cs ));
+					pMat->mp_wibble_texture_params->mp_keyframes[pass][ati].mp_texture = ( p_xbox_texture ) ? p_xbox_texture->GetEngineTexture() : NULL;
+				}
+			}
+
+			if( TextureChecksum )
+			{
+				// If textured, resolve texture checksum...
+				Nx::CXboxTexture	*p_xbox_texture	= static_cast( p_texture_table->GetItem( TextureChecksum ));
+				sTexture			*mp_tex			= ( p_xbox_texture ) ? p_xbox_texture->GetEngineTexture() : NULL;
+
+				// Bail if checksum not found.
+				if( mp_tex == NULL )
+				{
+					Dbg_Message( "error: couldn't find texture checksum %08X\n", TextureChecksum );
+					pMat->mp_tex[pass] = NULL;
+				}
+				else
+				{
+					// Set texture pointer.
+					pMat->mp_tex[pass] = mp_tex;
+				}
+
+				// Get mipmap info.
+				MemoryRead( &MMAG, sizeof( uint32 ), 1, p_data );
+				MemoryRead( &MMIN, sizeof( uint32 ), 1, p_data );
+				MemoryRead( &K, sizeof( uint32 ), 1, p_data );
+				MemoryRead( &L, sizeof( uint32 ), 1, p_data );
+				
+				// Default PS2 value for K appears to be -8.0f - we are interested in deviations from this value.
+				pMat->m_k[pass]	= ( *(float*)&K ) + 8.0f;
+				
+				// Dave note 09/03/02 - having MIPs selected earlier than normal seems to cause some problems, since Xbox
+				// MIP selection is so different to Ps2. Limit the k value such that Xbox can never select smaller MIPs
+				// earlier than it would do by default.
+				if( pMat->m_k[pass] > 0.0f )
+				{
+					pMat->m_k[pass] = 0.0f;
+				}
+			}
+			else
+			{
+				// ...otherwise just step past mipmap info.
+				pMat->mp_tex[pass] = NULL;
+				p_data += 16;
+			}
+		}
+
+		// Set the no material color flag if appropriate.
+		if( neutral_material_color )
+		{
+			pMat->m_flags[0] |= MATFLAG_NO_MAT_COL_MOD;
+		}
+		
+		// Set the specular flag if appropriate.
+		if( pMat->m_specular_color[3] > 0.0f )
+		{
+			pMat->m_flags[0] |= MATFLAG_SPECULAR;
+		}
+
+		// There is a problem adding materials with the same checksum into the table.
+		// This could happen when materials in different scenes share the same name.
+		// It also happens for the dummy material (checksum == 0), so for now just special-case
+		// that one.
+		if( pMat->m_checksum == 0 )
+		{
+			if( !pMaterialTable->GetItem( 0 ))
+			{
+				pMaterialTable->PutItem( pMat->m_checksum, pMat );
+			}
+		}
+		else		
+		{
+			if( pMaterialTable->GetItem( pMat->m_checksum ))
+			{
+				Dbg_MsgAssert( 0, ( "NXXBOX ERROR: duplicate material: %x\n", pMat->m_checksum ));
+			}
+			else
+			{
+				pMaterialTable->PutItem( pMat->m_checksum, pMat );
+			}
+		}
+	}
+
+	// Set the data pointer to the new position on return.
+	*pp_mem = p_data;
+
+	return pMaterialTable;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Lst::HashTable< sMaterial >	*LoadMaterials( void *p_FH, Lst::HashTable< Nx::CTexture > *p_texture_table )
+{
+	uint32 MMAG, MMIN, K, L, NumSeqs, seq, NumKeys;
+	
+	// Get number of materials.
+	uint32 new_materials;
+	File::Read( &new_materials, sizeof( uint32 ), 1, p_FH );
+	
+	Lst::HashTable< sMaterial >* pMaterialTable;
+
+	// Create table, dynamically sizing it based on the number of new materials.
+	uint32 optimal_table_size	= new_materials * 2;
+	uint32 test					= 4;
+	uint32 size					= 2;
+
+	for( ;; test <<= 1, ++size )
+	{
+		// Check if this iteration of table size is sufficient, or if we have hit the maximum size.
+		if(( optimal_table_size < test ) || ( size >= 12 ))
+		{
+			pMaterialTable = new Lst::HashTable< sMaterial >( size );
+			break;
+		}
+	}
+	
+	// Loop over materials.
+	for( uint32 i = 0; i < new_materials; ++i )
+	{
+		// Create new material.
+		sMaterial *pMat = new sMaterial;
+
+		// Get material checksum.
+		File::Read( &pMat->m_checksum, sizeof( uint32 ), 1, p_FH );
+
+		// Get material name checksum.
+		File::Read( &pMat->m_name_checksum, sizeof( uint32 ), 1, p_FH );
+
+		// Get number of passes.
+		File::Read( &pMat->m_passes, sizeof( uint32 ), 1, p_FH );
+
+		// Get alpha cutoff value.
+		uint32 AlphaCutoff;
+		File::Read( &AlphaCutoff, sizeof( uint32 ), 1, p_FH );
+		pMat->m_alpha_cutoff = (uint8)AlphaCutoff;
+
+		// Get sorted flag.
+		File::Read( &pMat->m_sorted, sizeof( bool ), 1, p_FH );
+		
+		// Get draw order.
+		File::Read( &pMat->m_draw_order, sizeof( float ), 1, p_FH );
+		
+		// Get single sided flag.
+		bool single_sided;
+		File::Read( &single_sided, sizeof( bool ), 1, p_FH );
+
+		// Get backface cull flag.
+		File::Read( &pMat->m_no_bfc, sizeof( bool ), 1, p_FH );
+
+		// Get z-bias value.
+		int zbias;
+		File::Read( &zbias, sizeof( int ), 1, p_FH );
+		pMat->m_zbias = (uint8)(( zbias > 16 ) ? 16 : zbias );
+
+		// Get grassify flag and (optionally) grassify data.
+		bool grassify;
+		File::Read( &grassify, sizeof( bool ), 1, p_FH );
+		if( grassify )
+		{
+			File::Read( &pMat->m_grass_height, sizeof( float ), 1, p_FH );
+			File::Read( &pMat->m_grass_layers, sizeof( int ), 1, p_FH );
+		}
+
+		// Get specular power and (optionally) specular color.
+		File::Read( &pMat->m_specular_color[3], sizeof( float ), 1, p_FH );
+		if( pMat->m_specular_color[3] > 0.0f )
+		{
+			File::Read( &pMat->m_specular_color[0], sizeof( float ) * 3, 1, p_FH );
+		}
+
+		// Neutral under proven otherwise.
+		bool neutral_material_color = true;
+		for( uint32 pass = 0; pass < pMat->m_passes; ++pass )
+		{
+			// Get texture checksum.
+			uint32 TextureChecksum;
+			File::Read( &TextureChecksum, sizeof( uint32 ), 1, p_FH );
+
+			// Get material flags.
+			File::Read( &pMat->m_flags[pass], sizeof( uint32 ), 1, p_FH );
+
+			// Get pass color flag.
+			bool has_color;
+			File::Read( &has_color, sizeof( bool ), 1, p_FH );
+
+			// Get pass color.
+			File::Read( &pMat->m_color[pass][0], sizeof( float ) * 3, 1, p_FH );
+
+			// Check for color being neutral.
+			if( neutral_material_color )
+			{
+				if(( pMat->m_color[pass][0] != 0.5f ) || ( pMat->m_color[pass][1] != 0.5f ) || ( pMat->m_color[pass][2] != 0.5f ))
+				{
+					neutral_material_color = false;
+				}
+			}
+			
+			// Get ALPHA register value.
+			uint64 reg_alpha;
+			File::Read( ®_alpha, sizeof( uint64 ), 1, p_FH );
+
+			uint32	blend_mode		= (uint32)( reg_alpha & 0xFFFFFFUL );
+			uint32	fixed_alpha		= (uint32)( reg_alpha >> 32 );
+			pMat->m_reg_alpha[pass]	= blend_mode | ( fixed_alpha << 24 );
+
+			// Also calculate the floating point version of the fixed alpha.
+			pMat->m_color[pass][3]	= fixed_alpha / 128.0f;
+
+			// Backface cull test - if this is an alpha blended material, turn off backface culling, except
+			// where the material has been explicitly flagged as single sided.
+			if(( pass == 0 ) && !single_sided && (( pMat->m_reg_alpha[pass] & sMaterial::BLEND_MODE_MASK ) != 0x00 ))
+			{
+				pMat->m_no_bfc = true;
+			}
+
+			// Get UV addressing types.
+			uint32 u_addressing, v_addressing;
+			File::Read( &u_addressing, sizeof( uint32 ), 1, p_FH );
+			File::Read( &v_addressing, sizeof( uint32 ), 1, p_FH );
+			pMat->m_uv_addressing[pass] = (( v_addressing << 16 ) | u_addressing );
+			
+			// Get environment map u and v tiling multiples.
+			File::Read( &pMat->m_envmap_tiling[pass][0], sizeof( float ) * 2, 1, p_FH );
+
+			// Get minification and magnification filtering mode.
+			File::Read( &pMat->m_filtering_mode[pass], sizeof( uint32 ), 1, p_FH );
+
+			// Read uv wibble data if present.
+			if( pMat->m_flags[pass] & MATFLAG_UV_WIBBLE )
+			{
+				// Flag that this material wibbles.
+				pMat->m_uv_wibble = true;
+				
+				// Create uv wibble params structure.
+				pMat->mp_UVWibbleParams[pass] = new sUVWibbleParams;
+				File::Read( pMat->mp_UVWibbleParams[pass], sizeof( float ) * 8, 1, p_FH );
+			}
+
+			// Read vertex color wibble data.
+			if(( pass == 0 ) && ( pMat->m_flags[0] & MATFLAG_VC_WIBBLE ))
+			{
+				File::Read( &NumSeqs, sizeof( uint32 ), 1, p_FH );
+				pMat->m_num_wibble_vc_anims = NumSeqs;
+
+				// Create sequence data array.
+				pMat->mp_wibble_vc_params = new sVCWibbleParams[NumSeqs];
+				
+				// Create resultant color array.
+				pMat->mp_wibble_vc_colors = new D3DCOLOR[NumSeqs];
+
+				for( seq = 0; seq < NumSeqs; ++seq )
+				{ 
+					File::Read( &NumKeys, sizeof( uint32 ), 1, p_FH );
+
+					int phase;
+					File::Read( &phase, sizeof( int ), 1, p_FH );
+
+					// Create array for keyframes.
+					pMat->mp_wibble_vc_params[seq].m_num_keyframes	= NumKeys;
+					pMat->mp_wibble_vc_params[seq].m_phase			= phase;
+					pMat->mp_wibble_vc_params[seq].mp_keyframes		= new sVCWibbleKeyframe[NumKeys];
+
+					// Read keyframes into array.
+					File::Read( pMat->mp_wibble_vc_params[seq].mp_keyframes, sizeof( sVCWibbleKeyframe ), NumKeys, p_FH );
+				}
+			}
+
+			// Read texture wibble data.
+			if( pMat->m_flags[pass] & MATFLAG_PASS_TEXTURE_ANIMATES )
+			{
+				// Create the texture wibble structure if not created yet.
+				if( pMat->mp_wibble_texture_params == NULL )
+				{
+					pMat->mp_wibble_texture_params = new NxXbox::sTextureWibbleParams;
+					ZeroMemory( pMat->mp_wibble_texture_params, sizeof( NxXbox::sTextureWibbleParams ));
+
+					// Flag the material as having texture wibble.
+					pMat->m_texture_wibble = true;
+				}
+
+				int num_keyframes, period, iterations, phase;
+				File::Read( &num_keyframes, sizeof( int ), 1, p_FH );
+				File::Read( &period, sizeof( int ), 1, p_FH );			// This value is currently discarded.
+				File::Read( &iterations, sizeof( int ), 1, p_FH );
+				File::Read( &phase, sizeof( int ), 1, p_FH );
+
+				Dbg_Assert( num_keyframes > 0 );
+
+				pMat->mp_wibble_texture_params->m_num_keyframes[pass]	= num_keyframes;
+				pMat->mp_wibble_texture_params->m_phase[pass]			= phase;
+				pMat->mp_wibble_texture_params->m_num_iterations[pass]	= iterations;
+				pMat->mp_wibble_texture_params->mp_keyframes[pass]		= new NxXbox::sTextureWibbleKeyframe[num_keyframes];
+
+				for( int ati = 0; ati < num_keyframes; ++ati )
+				{
+					File::Read( &pMat->mp_wibble_texture_params->mp_keyframes[pass][ati].m_time,		sizeof( uint32 ), 1, p_FH );
+
+					// Read the texture checksum.
+					uint32 cs;
+					File::Read( &cs, sizeof( uint32 ), 1, p_FH );
+
+					// Set the TextureChecksum variable so the mp_tex member will get populated.
+					if( ati == 0 )
+					{
+						TextureChecksum = cs;
+					}
+
+					// Resolve the checksum to a texture pointer.
+					Nx::CXboxTexture *p_xbox_texture = static_cast( p_texture_table->GetItem( cs ));
+					pMat->mp_wibble_texture_params->mp_keyframes[pass][ati].mp_texture = ( p_xbox_texture ) ? p_xbox_texture->GetEngineTexture() : NULL;
+				}
+			}
+
+			if( TextureChecksum )
+			{
+				// If textured, resolve texture checksum...
+				Nx::CXboxTexture	*p_xbox_texture	= static_cast( p_texture_table->GetItem( TextureChecksum ));
+				sTexture			*mp_tex			= ( p_xbox_texture ) ? p_xbox_texture->GetEngineTexture() : NULL;
+
+				// Bail if checksum not found.
+				if( mp_tex == NULL )
+				{
+					Dbg_Message( "error: couldn't find texture checksum %08X\n", TextureChecksum );
+					pMat->mp_tex[pass] = NULL;
+				}
+				else
+				{
+					// Set texture pointer.
+					pMat->mp_tex[pass] = mp_tex;
+				}
+
+				// Get mipmap info.
+				File::Read( &MMAG, sizeof( uint32 ), 1, p_FH );
+				File::Read( &MMIN, sizeof( uint32 ), 1, p_FH );
+				File::Read( &K, sizeof( uint32 ), 1, p_FH );
+				File::Read( &L, sizeof( uint32 ), 1, p_FH );
+				
+				// Default PS2 value for K appears to be -8.0f - we are interested in deviations from this value.
+				pMat->m_k[pass]	= ( *(float*)&K ) + 8.0f;
+				
+				// Dave note 09/03/02 - having MIPs selected earlier than normal seems to cause some problems, since Xbox
+				// MIP selection is so different to Ps2. Limit the k value such that Xbox can never select smaller MIPs
+				// earlier than it would do by default.
+				if( pMat->m_k[pass] > 0.0f )
+				{
+					pMat->m_k[pass] = 0.0f;
+				}
+			}
+			else
+			{
+				// ...otherwise just step past mipmap info.
+				pMat->mp_tex[pass] = NULL;
+				File::Seek( p_FH, 16, SEEK_CUR );
+			}
+		}
+
+		// Set the no material color flag if appropriate.
+		if( neutral_material_color )
+		{
+			pMat->m_flags[0] |= MATFLAG_NO_MAT_COL_MOD;
+		}
+		
+		// Set the specular flag if appropriate.
+		if( pMat->m_specular_color[3] > 0.0f )
+		{
+			pMat->m_flags[0] |= MATFLAG_SPECULAR;
+		}
+
+		// There is a problem adding materials with the same checksum into the table.
+		// This could happen when materials in different scenes share the same name.
+		// It also happens for the dummy material (checksum == 0), so for now just special-case
+		// that one.
+		if( pMat->m_checksum == 0 )
+		{
+			if( !pMaterialTable->GetItem( 0 ))
+			{
+				pMaterialTable->PutItem( pMat->m_checksum, pMat );
+			}
+		}
+		else		
+		{
+			if( pMaterialTable->GetItem( pMat->m_checksum ))
+			{
+				Dbg_MsgAssert( 0, ( "NXXBOX ERROR: duplicate material: %x\n", pMat->m_checksum ));
+			}
+			else
+			{
+				pMaterialTable->PutItem( pMat->m_checksum, pMat );
+			}
+		}
+	}
+	return pMaterialTable;
+}
+
+
+
+
+
+
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/material.h b/Code/Gfx/XBox/NX/material.h
new file mode 100644
index 0000000..9bee5d7
--- /dev/null
+++ b/Code/Gfx/XBox/NX/material.h
@@ -0,0 +1,136 @@
+#ifndef __MATERIAL_H
+#define __MATERIAL_H
+
+#include 
+#include 
+#include 
+#include "texture.h"
+
+namespace NxXbox
+{
+
+// Material Flags
+#define MATFLAG_UV_WIBBLE					(1<<0)
+#define MATFLAG_VC_WIBBLE					(1<<1)
+#define MATFLAG_TEXTURED					(1<<2)
+#define MATFLAG_ENVIRONMENT					(1<<3)
+#define MATFLAG_DECAL						(1<<4)
+#define MATFLAG_SMOOTH						(1<<5)
+#define MATFLAG_TRANSPARENT					(1<<6)
+#define MATFLAG_PASS_COLOR_LOCKED			(1<<7)
+#define MATFLAG_SPECULAR					(1<<8)		// Specular lighting is enabled on this material (Pass0).
+#define MATFLAG_BUMP_SIGNED_TEXTURE			(1<<9)		// This pass uses an offset texture which needs to be treated as signed data.
+#define MATFLAG_BUMP_LOAD_MATRIX			(1<<10)		// This pass requires the bump mapping matrix elements to be set up.
+#define MATFLAG_PASS_TEXTURE_ANIMATES		(1<<11)		// This pass has a texture which animates.
+#define MATFLAG_PASS_IGNORE_VERTEX_ALPHA	(1<<12)		// This pass should not have the texel alpha modulated by the vertex alpha.
+#define MATFLAG_EXPLICIT_UV_WIBBLE			(1<<14)		// Uses explicit uv wibble (set via script) rather than calculated.
+#define MATFLAG_WATER_EFFECT				(1<<27)		// This material should be processed to provide the water effect.
+#define MATFLAG_NO_MAT_COL_MOD				(1<<28)		// No material color modulation required (all passes have m.rgb = 0.5).
+
+
+const uint32 MAX_PASSES = 4;
+
+
+	
+struct sUVWibbleParams
+{
+			sUVWibbleParams( void );
+			~sUVWibbleParams( void );
+
+	float	m_UVel;
+	float	m_VVel;
+	float	m_UFrequency;
+	float	m_VFrequency;
+	float	m_UAmplitude;
+	float	m_VAmplitude;
+	float	m_UPhase;
+	float	m_VPhase;
+	float	m_UVMatrix[4];		// This value is written to dynamically. The first two values are rotation, the second two are translation.
+};
+
+struct sVCWibbleKeyframe
+{
+	int			m_time;
+	Image::RGBA	m_color;
+};
+
+struct sVCWibbleParams
+{
+	uint32				m_num_keyframes;
+	int					m_phase;
+	sVCWibbleKeyframe	*mp_keyframes;
+};
+
+
+struct sTextureWibbleKeyframe
+{
+	int			m_time;
+	sTexture	*mp_texture;
+};
+
+struct sTextureWibbleParams
+{
+	uint32					m_num_keyframes[MAX_PASSES];
+	int						m_phase[MAX_PASSES];
+	int						m_num_iterations[MAX_PASSES];
+	sTextureWibbleKeyframe	*mp_keyframes[MAX_PASSES];
+};
+
+
+
+struct sMaterial
+{
+	public:
+
+	static const uint32		BLEND_MODE_MASK	= 0x00FFFFFFUL;
+
+							sMaterial( void );
+							~sMaterial( void );
+	
+	void					Submit( void );
+	uint32					GetIgnoreVertexAlphaPasses( void );
+	void					figure_wibble_uv( void );
+	void					figure_wibble_vc( void );
+	void					figure_wibble_texture( void );
+
+	uint32					m_checksum;
+	uint32					m_name_checksum;
+	uint32					m_passes;
+
+	bool					m_sorted;
+	bool					m_no_bfc;
+	bool					m_uv_wibble;
+	bool					m_texture_wibble;
+	uint8					m_alpha_cutoff;
+	uint8					m_zbias;
+
+	float					m_grass_height;
+	int						m_grass_layers;
+	float					m_draw_order;
+	uint32					m_flags[MAX_PASSES];
+	sTexture*				mp_tex[MAX_PASSES];
+	float					m_color[MAX_PASSES][4];				// Element [pass][3] holds the fixed alpha value where appropriate.
+	uint32					m_reg_alpha[MAX_PASSES];			// Low 24 bits are blend mode, high 8 bits are fixed alpha value.
+	uint32					m_uv_addressing[MAX_PASSES];
+	float					m_envmap_tiling[MAX_PASSES][2];		// Tile multiples for env mapping (NOTE: could maybe be changed to byte array?)
+	uint32					m_filtering_mode[MAX_PASSES];
+	sUVWibbleParams			*mp_UVWibbleParams[MAX_PASSES];
+	float					m_k[MAX_PASSES];
+	uint32					m_num_wibble_vc_anims;
+	sVCWibbleParams			*mp_wibble_vc_params;
+	D3DCOLOR				*mp_wibble_vc_colors;				// Max of eight banks of vertex color wibble information.
+	sTextureWibbleParams	*mp_wibble_texture_params;
+	float					m_specular_color[4];				// Specular color (0-2) plus power term (3).
+};
+
+
+Lst::HashTable< sMaterial >	*LoadMaterials( void *p_FH, Lst::HashTable< Nx::CTexture > *p_texture_table );
+Lst::HashTable< sMaterial >	*LoadMaterialsFromMemory( void **pp_mem, Lst::HashTable< Nx::CTexture > *p_texture_table );
+
+//extern Lst::HashTable< sMaterial > *pMaterialTable;
+extern uint32 NumMaterials;
+
+} // namespace NxXbox
+
+#endif // __MATERIAL_H
+
diff --git a/Code/Gfx/XBox/NX/mesh.cpp b/Code/Gfx/XBox/NX/mesh.cpp
new file mode 100644
index 0000000..46b61ff
--- /dev/null
+++ b/Code/Gfx/XBox/NX/mesh.cpp
@@ -0,0 +1,1605 @@
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include "nx_init.h"
+#include "texture.h"
+#include "scene.h"
+#include "mesh.h"
+#include "anim.h"
+#include "render.h"
+#include "billboard.h"
+
+namespace NxXbox
+{
+
+bool			s_meshScalingEnabled = false;
+char*			s_pWeightIndices = NULL;
+float*			s_pWeights = NULL;
+Mth::Vector*	s_pBonePositions = NULL;
+Mth::Vector*	s_pBoneScales = NULL;
+int				s_currentVertIndex = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetMeshScalingParameters( Nx::SMeshScalingParameters* pParams )
+{
+	Dbg_Assert( pParams );
+
+	s_meshScalingEnabled	= true;
+	s_pWeights				= pParams->pWeights;
+	s_pWeightIndices		= pParams->pWeightIndices;
+	s_pBoneScales			= pParams->pBoneScales;
+	s_pBonePositions		= pParams->pBonePositions;
+	s_currentVertIndex		= 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void DisableMeshScaling( void )
+{
+	s_meshScalingEnabled	= false;
+	s_pWeights				= NULL;
+	s_pWeightIndices		= NULL;
+	s_pBoneScales			= NULL;
+	s_pBonePositions		= NULL;
+	s_currentVertIndex		= 0;
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline Mth::Vector get_bone_scale( int bone_index )
+{
+	Mth::Vector returnVec( 1.0f, 1.0f, 1.0f, 1.0f );
+
+	if( bone_index >= 29 && bone_index <= 33 )
+	{
+		// this only works with the thps5 skeleton, whose
+		// head bones are between 29 and 33...
+		// (eventually, we can remove the subtract 29
+		// once the exporter is massaging the data correctly)
+		returnVec = s_pBoneScales[ bone_index - 29 ];
+		
+		// Y & Z are reversed...  odd!
+		Mth::Vector tempVec = returnVec;
+		returnVec[Y] = tempVec[Z];
+		returnVec[Z] = tempVec[Y];
+	}
+	else if( bone_index == -1 )
+	{
+		// implies that it's not weighted to a bone
+		return returnVec;
+	}
+	else
+	{
+		// implies that it's weighted to the wrong bone
+		return returnVec;
+	}
+	return returnVec;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline Mth::Vector get_bone_pos( int bone_index )
+{
+	Mth::Vector returnVec( 0.0f, 0.0f, 0.0f, 1.0f );
+	
+	if( bone_index >= 29 && bone_index <= 33 )
+	{
+		// this only works with the thps5 skeleton, whose
+		// head bones are between 29 and 33...
+		// (eventually, we can remove the subtract 29
+		// once the exporter is massaging the data correctly)
+		returnVec = s_pBonePositions[ bone_index - 29 ];
+	}
+	else if( bone_index == -1 )
+	{
+		// implies that it's not weighted to a bone
+		return returnVec;
+	}
+	else
+	{
+		// implies that it's weighted to the wrong bone
+		return returnVec;
+	}
+	returnVec[W] = 1.0f;
+
+	return returnVec;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void ApplyMeshScaling( float* p_vertices, int num_verts )
+{
+	if( s_meshScalingEnabled )
+	{
+		for( int v = 0; v < num_verts; ++v, p_vertices += 3 )
+		{
+			float x = p_vertices[0];
+			float y = p_vertices[1];
+			float z = p_vertices[2];
+
+    		Mth::Vector origPos( x, y, z, 1.0f );
+
+			Mth::Vector bonePos0 = get_bone_pos( s_pWeightIndices[v * 3] );
+			Mth::Vector bonePos1 = get_bone_pos( s_pWeightIndices[v * 3 + 1] );
+			Mth::Vector bonePos2 = get_bone_pos( s_pWeightIndices[v * 3 + 2] );
+
+			// Need to scale each vert relative to its parent bone.
+			Mth::Vector localPos0 = origPos - bonePos0;
+			Mth::Vector localPos1 = origPos - bonePos1;
+			Mth::Vector localPos2 = origPos - bonePos2;
+
+			localPos0.Scale( get_bone_scale( s_pWeightIndices[v * 3] ) );
+			localPos1.Scale( get_bone_scale( s_pWeightIndices[v * 3 + 1] ) );
+			localPos2.Scale( get_bone_scale( s_pWeightIndices[v * 3 + 2] ) );
+
+			localPos0 += bonePos0;
+			localPos1 += bonePos1;
+			localPos2 += bonePos2;
+			
+			Mth::Vector scaledPos = ( localPos0 * s_pWeights[v * 3] ) +
+									( localPos1 * s_pWeights[v * 3 + 1] ) +
+									( localPos2 * s_pWeights[v * 3 + 2] );
+
+			p_vertices[0] = scaledPos[X];
+			p_vertices[1] = scaledPos[Y];
+			p_vertices[2] = scaledPos[Z];
+		}
+	}
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMesh::sMesh( void )
+{
+	m_flags							= 0;
+	m_num_vertex_buffers			= 1;
+	m_current_write_vertex_buffer	= 0;
+	for( int vb = 0; vb < MAX_VERTEX_BUFFERS; ++vb )
+	{
+		mp_vertex_buffer[vb]		= NULL;
+	}
+	for( int ib = 0; ib < MAX_INDEX_BUFFERS; ++ib )
+	{
+		mp_index_buffer[ib]			= NULL;
+		m_num_indices[ib]			= 0;
+	}
+	mp_vc_wibble_data				= NULL;
+	mp_index_lod_data				= NULL;
+	mp_billboard_data				= NULL;
+	m_bone_index					= -1;
+	mp_transform					= NULL;
+	m_diffuse_offset				= 0;
+	m_uv0_offset					= 0;
+	m_normal_offset					= 0;
+	m_vertex_stride					= 0;
+	m_vertex_shader[0]				= 0;
+	m_pixel_shader					= 0;
+
+	SetActive( true );
+	SetVisibility( 0xFF );
+
+	// Set default primitive type.
+	m_primitive_type				= D3DPT_TRIANGLESTRIP;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMesh::~sMesh( void )
+{
+	// Remove this mesh from the billboard manager if appropriate.
+	if( m_flags & sMesh::MESH_FLAG_BILLBOARD )
+	{
+		BillboardManager.RemoveEntry( this );
+	}
+
+	EngineGlobals.p_Device->BlockUntilIdle();
+
+	if( mp_transform )
+	{
+		delete mp_transform;
+	}
+	
+	if( !( m_flags & MESH_FLAG_IS_INSTANCE ))
+	{
+		for( int ib = 0; ib < MAX_INDEX_BUFFERS; ++ib )
+		{
+			delete [] mp_index_buffer[ib];
+			mp_index_buffer[ib] = NULL;
+		}
+
+		if( mp_vc_wibble_data )
+		{
+			delete mp_vc_wibble_data;
+			mp_vc_wibble_data = NULL;
+		}
+
+		if( mp_index_lod_data )
+		{
+			delete mp_index_lod_data;
+			mp_index_lod_data = NULL;
+		}
+
+		if( mp_billboard_data )
+		{
+			delete mp_billboard_data;
+			mp_billboard_data = NULL;
+		}
+
+		UINT					stride;
+		IDirect3DVertexBuffer8	*p_vb;
+		D3DDevice_GetStreamSource( 0, &p_vb, &stride );
+		if( p_vb )
+		{
+			// GetStreamSource() increments the reference count, so call Release() here.
+			p_vb->Release();
+		}
+
+		for( uint32 i = 0; i < m_num_vertex_buffers; ++i )
+		{
+			if( mp_vertex_buffer[i] )
+			{
+				if( p_vb == mp_vertex_buffer[i] )
+				{
+					// We are deleting a vertex buffer that is set as the current stream source. This can result in
+					// problems with the internal D3D reference counter, so clear this up first.
+					D3DDevice_SetStreamSource( 0, NULL, 0 );
+				}
+			
+				uint8 *p_del = (uint8*)mp_vertex_buffer[i];
+				delete p_del;
+				mp_vertex_buffer[i]	= NULL;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::PushVertexShader( uint32 shader_id )
+{
+	for( uint32 i = sMesh::VERTEX_SHADER_STACK_SIZE - 1; i > 0; --i )
+	{
+		m_vertex_shader[i] = m_vertex_shader[i - 1];
+	}
+	m_vertex_shader[0] = shader_id;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::PopVertexShader( void )
+{
+	for( uint32 i = 0; i < sMesh::VERTEX_SHADER_STACK_SIZE - 1; ++i )
+	{
+		m_vertex_shader[i] = m_vertex_shader[i + 1];
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::wibble_normals( void )
+{
+	if( m_flags & 0 )
+	{
+		// Angle in the range [-PI/16, PI/16], period is 1 second.
+		float time = (float)Tmr::GetTime() * 0.0005f;
+
+		BYTE		*p_byte;
+		float		*p_normal;
+		float		*p_pos;
+		mp_vertex_buffer[m_current_write_vertex_buffer]->Lock( 0, 0, &p_byte, 0 );
+		p_pos		= (float*)( p_byte + 0 );
+		p_normal	= (float*)( p_byte + m_normal_offset );
+
+		for( uint32 i = 0; i < m_num_vertices; ++i )
+		{
+			float x				= p_pos[0] - m_sphere_center.x;
+			float z				= p_pos[2] - m_sphere_center.z;
+			
+			float time_offset_x	= time + (( x / m_sphere_radius ) * 0.5f );
+			float time_offset_z	= time + (( z / m_sphere_radius ) * 0.5f );
+
+			float angle_x		= ( Mth::PI * ( 1.0f / 64.0f ) * (float)fabs( sinf( time_offset_x * Mth::PI ))) - ( Mth::PI * ( 1.0f / 128.0f ));
+			float angle_z		= ( Mth::PI * ( 1.0f / 64.0f ) * (float)fabs( sinf( time_offset_z * Mth::PI ))) - ( Mth::PI * ( 1.0f / 129.0f ));
+			
+			Mth::Vector	n( sinf( angle_x ), cosf(( angle_x + angle_z ) * 0.5f ), sinf( angle_z ));
+			n.Normalize();
+			
+			p_normal[0]			= n[X];
+			p_normal[1]			= n[Y];
+			p_normal[2]			= n[Z];
+			
+			p_pos				= (float*)((BYTE*)p_pos + m_vertex_stride );
+			p_normal			= (float*)((BYTE*)p_normal + m_vertex_stride );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::wibble_vc( void )
+{
+	if( mp_vc_wibble_data )
+	{
+		// Grab byte pointer to current 'write' vertex buffer.
+		BYTE		*p_byte;
+		D3DCOLOR	*p_color;
+		mp_vertex_buffer[m_current_write_vertex_buffer]->Lock( 0, 0, &p_byte, 0 );
+		p_color = (D3DCOLOR*)( p_byte + m_diffuse_offset );
+
+		D3DCOLOR *p_color_array	= mp_material->mp_wibble_vc_colors;
+
+		// Scan through each vertex, setting the new color.
+		for( uint32 i = 0; i < m_num_vertices; ++i )
+		{
+			// An index of zero means no update for this vert.
+			uint32 index	= mp_vc_wibble_data[i];
+			if( index > 0 )
+			{
+				*p_color	= p_color_array[index - 1];
+			}
+			p_color		= (D3DCOLOR*)((BYTE*)p_color + m_vertex_stride );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::CreateDuplicateVertexBuffers( int n )
+{
+	// Ensure this hasn't already been called.
+	Dbg_Assert( mp_vertex_buffer[0] != NULL );
+	Dbg_Assert( mp_vertex_buffer[1] == NULL );
+	Dbg_Assert(( n > 0 ) && ( n < MAX_VERTEX_BUFFERS ));
+
+	// Lock the source buffer.
+	BYTE *p_byte;
+	if( D3D_OK != mp_vertex_buffer[0]->Lock( 0, 0, &p_byte, D3DLOCK_READONLY ))
+	{
+		exit( 0 );
+	}
+	
+	for( int i = 0; i < n; ++i )
+	{
+		mp_vertex_buffer[i + 1] = AllocateVertexBuffer( m_vertex_stride * m_num_vertices );
+
+		// Lock the destination buffer and copy the contents of the original buffer into the new buffer.
+		BYTE *p_byte2;
+		if( D3D_OK != mp_vertex_buffer[i + 1]->Lock( 0, 0, &p_byte2, 0 ))
+		{
+			exit( 0 );
+		}
+		CopyMemory( p_byte2, p_byte, m_vertex_stride * m_num_vertices );
+	}
+
+	m_num_vertex_buffers = 1 + n;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::SetPosition( Mth::Vector &pos )
+{
+	// Create a transform if one doesn't exist yet.
+	if( mp_transform == NULL )
+	{
+		mp_transform = new Mth::Matrix();
+		mp_transform->Ident();
+	}
+	
+	// Figure what we need to add to each vertex, based on current position.
+	Mth::Vector offset(	pos[X] - mp_transform->GetPos()[X],
+						pos[Y] - mp_transform->GetPos()[Y],
+						pos[Z] - mp_transform->GetPos()[Z] );
+
+	mp_transform->SetPos( pos );
+	
+	for( uint32 vb = 0; vb < m_num_vertex_buffers; ++vb )
+	{
+		BYTE *p_byte;
+		mp_vertex_buffer[vb]->Lock( 0, 0, &p_byte, 0 );
+
+		for( uint32 v = 0; v < m_num_vertices; ++v )
+		{
+			((D3DVECTOR*)p_byte )->x += offset[X];
+			((D3DVECTOR*)p_byte )->y += offset[Y];
+			((D3DVECTOR*)p_byte )->z += offset[Z];
+			p_byte += m_vertex_stride;
+		}
+	}
+
+	// We also need to adjust the bounding box and sphere information for this mesh.
+	m_sphere_center.x += offset[X];
+	m_sphere_center.y += offset[Y];
+	m_sphere_center.z += offset[Z];
+}
+	
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::GetPosition( Mth::Vector *p_pos )
+{
+	if( mp_transform == NULL )
+	{
+		p_pos->Set( 0.0f, 0.0f, 0.0f );
+	}
+	else
+	{
+		*p_pos = mp_transform->GetPos();
+	}
+}
+	
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::SetYRotation( Mth::ERot90 rot )
+{
+	if( rot > Mth::ROT_0 )
+	{
+		// Create a transform if one doesn't exist yet.
+		if( mp_transform == NULL )
+		{
+			mp_transform = new Mth::Matrix();
+			mp_transform->Ident();
+		}
+
+		for( uint32 vb = 0; vb < m_num_vertex_buffers; ++vb )
+		{
+			BYTE *p_byte;
+			mp_vertex_buffer[vb]->Lock( 0, 0, &p_byte, 0 );
+
+			switch( rot )
+			{
+				case Mth::ROT_90:
+				{
+					for( uint32 v = 0; v < m_num_vertices; ++v )
+					{
+						float x = ((D3DVECTOR*)p_byte )->x - mp_transform->GetPos()[X];
+						float z = ((D3DVECTOR*)p_byte )->z - mp_transform->GetPos()[Z];
+						((D3DVECTOR*)p_byte )->x = z + mp_transform->GetPos()[X];
+						((D3DVECTOR*)p_byte )->z = -x + mp_transform->GetPos()[Z];
+						p_byte += m_vertex_stride;
+					}
+
+					// Adjust the bounding sphere information for this mesh.
+					m_sphere_center.x	-= mp_transform->GetPos()[X];
+					m_sphere_center.z	-= mp_transform->GetPos()[Z];
+					float t				= m_sphere_center.x;
+					m_sphere_center.x	= m_sphere_center.z + mp_transform->GetPos()[X];
+					m_sphere_center.z	= -t + mp_transform->GetPos()[Z];
+					break;
+				}
+				case Mth::ROT_180:
+				{
+					for( uint32 v = 0; v < m_num_vertices; ++v )
+					{
+						float x = ((D3DVECTOR*)p_byte )->x - mp_transform->GetPos()[X];
+						float z = ((D3DVECTOR*)p_byte )->z - mp_transform->GetPos()[Z];
+						((D3DVECTOR*)p_byte )->x = -x + mp_transform->GetPos()[X];
+						((D3DVECTOR*)p_byte )->z = -z + mp_transform->GetPos()[Z];
+						p_byte += m_vertex_stride;
+					}
+
+					// Adjust the bounding sphere information for this mesh.
+					m_sphere_center.x	-= mp_transform->GetPos()[X];
+					m_sphere_center.z	-= mp_transform->GetPos()[Z];
+					m_sphere_center.x	= -m_sphere_center.x + mp_transform->GetPos()[X];
+					m_sphere_center.z	= -m_sphere_center.z + mp_transform->GetPos()[Z];
+					break;
+				}
+				case Mth::ROT_270:
+				{
+					for( uint32 v = 0; v < m_num_vertices; ++v )
+					{
+						float x = ((D3DVECTOR*)p_byte )->x - mp_transform->GetPos()[X];
+						float z = ((D3DVECTOR*)p_byte )->z - mp_transform->GetPos()[Z];
+						((D3DVECTOR*)p_byte )->x = -z + mp_transform->GetPos()[X];
+						((D3DVECTOR*)p_byte )->z = x + mp_transform->GetPos()[Z];
+						p_byte += m_vertex_stride;
+					}
+
+					// Adjust the bounding sphere information for this mesh.
+					m_sphere_center.x	-= mp_transform->GetPos()[X];
+					m_sphere_center.z	-= mp_transform->GetPos()[Z];
+					float t				= m_sphere_center.x;
+					m_sphere_center.x	= -m_sphere_center.z + mp_transform->GetPos()[X];
+					m_sphere_center.z	= t + mp_transform->GetPos()[Z];
+					break;
+				}
+			}
+		}
+	}
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::SwapVertexBuffers( void )
+{
+	if( m_num_vertex_buffers > 1 )
+	{
+		m_current_write_vertex_buffer = ( m_current_write_vertex_buffer + 1 ) % m_num_vertex_buffers;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::HandleColorOverride( void )
+{
+	static float constants[16];
+
+	Dbg_Assert( m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE );
+
+	// Re-jig the pixel shader constants for the material color override.
+	CopyMemory( constants, EngineGlobals.pixel_shader_constants, sizeof( float ) * 16 );
+			
+	for( uint32 p = 0; p < mp_material->m_passes; ++p )
+	{
+		if( !( mp_material->m_flags[p] & MATFLAG_PASS_COLOR_LOCKED ))
+		{
+//			EngineGlobals.pixel_shader_constants[( p * 4 ) + 0]	*= m_material_color_override[0];
+//			EngineGlobals.pixel_shader_constants[( p * 4 ) + 1]	*= m_material_color_override[1];
+//			EngineGlobals.pixel_shader_constants[( p * 4 ) + 2]	*= m_material_color_override[2];
+			EngineGlobals.pixel_shader_constants[( p * 4 ) + 0]	= m_material_color_override[0];
+			EngineGlobals.pixel_shader_constants[( p * 4 ) + 1]	= m_material_color_override[1];
+			EngineGlobals.pixel_shader_constants[( p * 4 ) + 2]	= m_material_color_override[2];
+		}
+	}
+	EngineGlobals.upload_pixel_shader_constants = true;
+
+	// Set the pixel shader (this will upload the new constants).
+	set_pixel_shader( m_pixel_shader, mp_material->m_passes );
+
+	// Restore the pixel shader constants and flag as needing a reload.
+	CopyMemory( EngineGlobals.pixel_shader_constants, constants, sizeof( float ) * 16 );
+	EngineGlobals.upload_pixel_shader_constants = true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::Submit( void )
+{
+	DWORD	stage_zero_minfilter;
+	DWORD	zwrite;
+	
+	// Pointless submitting a mesh with zero indices.
+	if( m_num_indices == 0 )
+		return;
+
+	// Deal with vertex color wibbling.
+	wibble_vc();
+	
+	// Set vertex shader.
+	set_vertex_shader( m_vertex_shader[0] );
+
+	if( m_flags & sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE )
+	{
+		HandleColorOverride();
+	}
+	else
+	{
+		// Just set the pixel shader.
+		set_pixel_shader( m_pixel_shader, mp_material->m_passes );
+	}
+
+	// Deal with meshes that set no anisotropic filtering.
+	if( m_flags & MESH_FLAG_NO_ANISOTROPIC )
+	{
+		D3DDevice_GetTextureStageState( 0, D3DTSS_MINFILTER, &stage_zero_minfilter );
+		if( stage_zero_minfilter != D3DTEXF_LINEAR )
+		{
+			D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+		}
+	}
+	
+	// Deal with meshes that set no z-write.
+	if( m_flags & MESH_FLAG_NO_ZWRITE )
+	{
+		D3DDevice_GetRenderState( D3DRS_ZWRITEENABLE, &zwrite );
+		if( zwrite == TRUE )
+		{
+			D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
+		}
+	}
+	
+	// Get the vertex buffer to submit (which is the one we have potentially just been writing to).
+	IDirect3DVertexBuffer8*	p_submit_buffer = mp_vertex_buffer[m_current_write_vertex_buffer];
+	
+	// Swap multiple vertex buffers if present.
+	SwapVertexBuffers();
+
+	// Set the stream source.
+	D3DDevice_SetStreamSource( 0, p_submit_buffer, m_vertex_stride );
+
+	// See if we have index LOD data, in which case we need to figure distance and select the correct LOD.
+	if( mp_index_lod_data )
+	{
+		// Figure distance from this mesh to the camera. This is *not* an efficient way to do it.
+		frustum_check_sphere( &m_sphere_center, m_sphere_radius );
+		float dist = get_bounding_sphere_nearest_z();
+		for( int idx = 0; idx < MAX_INDEX_BUFFERS; ++idx )
+		{
+			if( mp_index_buffer[idx] == NULL )
+			{
+				// We have got to the end of the set without drawing anything. Just use the last valid index set.
+				if( idx > 0 )
+				{
+					--idx;
+					D3DDevice_DrawIndexedVertices( m_primitive_type, m_num_indices[idx], mp_index_buffer[idx] );
+				}
+				else
+				{
+					Dbg_Assert( 0 );
+				}
+				break;
+			}
+
+			if( dist < mp_index_lod_data[idx] )
+			{
+				// This is the index set we want.
+				D3DDevice_DrawIndexedVertices( m_primitive_type, m_num_indices[idx], mp_index_buffer[idx] );
+				break;
+			}
+		}
+	}
+	else
+	{
+		// Submit.
+		D3DDevice_DrawIndexedVertices( m_primitive_type, m_num_indices[0], mp_index_buffer[0] );
+	}
+
+	// Deal with meshes that set no anisotropic filtering.
+	if( m_flags & MESH_FLAG_NO_ANISOTROPIC )
+	{
+		if( stage_zero_minfilter != D3DTEXF_LINEAR )
+		{
+			D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, stage_zero_minfilter );
+		}
+	}
+
+	// Deal with meshes that set no z-write.
+	if( m_flags & MESH_FLAG_NO_ZWRITE )
+	{
+		if( zwrite == TRUE )
+		{
+			D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE, zwrite );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMesh *sMesh::Clone( bool instance )
+{
+	sMesh *p_clone = new sMesh();
+
+	// Copy over basic details.
+	CopyMemory( p_clone, this, sizeof( sMesh ));
+	
+	if( instance )
+	{
+		p_clone->m_flags |= MESH_FLAG_IS_INSTANCE;
+	}
+	else
+	{
+		// Build new vertex and index lists.
+		p_clone->mp_vertex_buffer[0] = AllocateVertexBuffer( p_clone->m_vertex_stride * p_clone->m_num_vertices );
+
+		BYTE *p_byte_src, *p_byte_dest;
+		if( D3D_OK != mp_vertex_buffer[0]->Lock( 0, 0, &p_byte_src, D3DLOCK_READONLY ))
+		{
+			return NULL;
+		}
+		if( D3D_OK != p_clone->mp_vertex_buffer[0]->Lock( 0, 0, &p_byte_dest, 0 ))
+		{
+			return NULL;
+		}
+
+		// Copy over vertex information.
+		CopyMemory( p_byte_dest, p_byte_src, p_clone->m_vertex_stride * p_clone->m_num_vertices );
+
+		// Create index buffer(s) and copy over index information.
+		for( int ib = 0; ib < MAX_INDEX_BUFFERS; ++ib )
+		{
+			if( p_clone->m_num_indices[ib] > 0 )
+			{
+				p_clone->mp_index_buffer[ib] = new uint16[p_clone->m_num_indices[ib]];
+				CopyMemory( p_clone->mp_index_buffer[ib], mp_index_buffer[ib], sizeof( uint16 ) * p_clone->m_num_indices[ib] );
+			}
+		}
+
+		// Handle duplicate vertex buffers if they exist.
+		if( m_num_vertex_buffers > 1 )
+		{
+			p_clone->mp_vertex_buffer[1] = NULL;
+			p_clone->CreateDuplicateVertexBuffers( m_num_vertex_buffers - 1 );
+		}
+	}
+	return p_clone;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+IDirect3DVertexBuffer8 *sMesh::AllocateVertexBuffer( uint32 size )
+{
+	uint8					*p_vb		= new uint8[sizeof( IDirect3DVertexBuffer8 ) + size];
+	IDirect3DVertexBuffer8	*p_vb_ret	= (IDirect3DVertexBuffer8*)p_vb;
+	
+	XGSetVertexBufferHeader( 0, 0, 0, 0, p_vb_ret, 0 );
+	p_vb_ret->Register( p_vb + sizeof( IDirect3DVertexBuffer8 ));
+
+	return p_vb_ret;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::Crunch( void )
+{
+	uint16 *p_indices = mp_index_buffer[0];
+
+	uint32 i0 = p_indices[0];
+	uint32 i1 = p_indices[1];
+
+	uint32	invalid			= 0;
+	uint32	total_invalid	= 0;
+	bool	crunch			= false;
+
+	for( uint32 i = 2; i < m_num_indices[0]; ++i )
+	{
+		uint32 i2 = p_indices[i];
+
+		if(( i0 == i1 ) || ( i0 == i2 ) || ( i1 == i2 ))
+		{
+			++invalid;
+		}
+		else
+		{
+			if( invalid > 5 )
+			{
+				if(( invalid & 1 ) == 0 )
+				{
+//					printf( "Crunching %d indices (even)\n", invalid - 4 );
+
+					// Ensure the leading and trailing degenerate indices are correct.
+					p_indices[i - 3]		= p_indices[i - 2];
+					p_indices[i - invalid]	= p_indices[i - invalid - 1];
+
+					// With an even number of invalid entries, the wind order won't change during crunch.
+					MoveMemory( p_indices + i - invalid + 1, p_indices + i - 3, sizeof( uint16 ) * ( m_num_indices[0] - i + 3 ));
+
+					m_num_indices[0]	-= (uint16)( invalid - 4 );
+					i					-= invalid - 4;
+				}
+				else
+				{
+//					printf( "Crunching %d indices (odd)\n", invalid - 5 );
+
+					// Ensure the leading and trailing degenerate indices are correct.
+					p_indices[i - 3]			= p_indices[i - 2];
+					p_indices[i - invalid]		= p_indices[i - invalid - 1];
+					p_indices[i - invalid + 1]	= p_indices[i - invalid];
+
+					// With an odd number of invalid entries, the wind order will change during crunch, so use one extra index.
+					MoveMemory( p_indices + i - invalid + 2, p_indices + i - 3, sizeof( uint16 ) * ( m_num_indices[0] - i + 3 ));
+					m_num_indices[0]	-= (uint16)( invalid - 5 );
+					i					-= invalid - 5;
+				}
+			}
+			invalid = 0;
+		}
+		
+		i0 = i1;
+		i1 = i2;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::SetBillboardData( uint32 type, Mth::Vector & pivot_pos, Mth::Vector & pivot_axis )
+{
+	Dbg_Assert( mp_billboard_data == NULL );
+
+	// Create the billboard data.
+	mp_billboard_data					= new sBillboardData;
+
+	// Determine the billboard type.
+	if( type == 1 )
+	{
+		mp_billboard_data->m_type = sBillboardData::vBILLBOARD_TYPE_SCREEN_ALIGNED;
+	}
+	else if( type == 2 )
+	{
+		// Axial aligned. See if this is y axis.
+		if( fabsf( Mth::DotProduct( pivot_axis, Mth::Vector( 0.0f, 1.0f, 0.0f ))) > 0.99f )
+		{
+			mp_billboard_data->m_type = sBillboardData::vBILLBOARD_TYPE_Y_AXIS_ALIGNED;
+		}
+		else
+		{
+			mp_billboard_data->m_type = sBillboardData::vBILLBOARD_TYPE_ARBITRARY_AXIS_ALIGNED;
+		}
+	}
+	else
+	{
+		Dbg_Assert( 0 );
+	}
+	
+	mp_billboard_data->m_pivot_pos		= pivot_pos;
+	if( mp_billboard_data->m_type == sBillboardData::vBILLBOARD_TYPE_ARBITRARY_AXIS_ALIGNED )
+	{
+		mp_billboard_data->m_pivot_axis	= pivot_axis;
+	}
+
+	// We need to go through at a low level and rebuild the vertex buffer. In all cases the
+	// mesh won't have been exported with normals, which we use for billboards to store the
+	// pivot-relative position, so we need to recalculate the vertex stride.
+	Dbg_Assert( m_normal_offset == 0 );
+	Dbg_Assert( m_diffuse_offset > 0 );
+	Dbg_Assert( m_uv0_offset > 0 );
+	Dbg_Assert( m_num_vertices == 4 );
+	Dbg_Assert( m_num_indices[0] == 4 );
+
+	// Add size of normal to vertex size.
+	int old_vertex_stride = m_vertex_stride;
+	int new_vertex_stride = old_vertex_stride + ( sizeof( float ) * 3 );
+
+	IDirect3DVertexBuffer8*	p_old_buffer = mp_vertex_buffer[0];
+	IDirect3DVertexBuffer8*	p_new_buffer = AllocateVertexBuffer( new_vertex_stride * 4 );
+
+	// Lock old buffer (read) and new buffer (write).
+	BYTE *p_old_vb_data;
+	BYTE *p_new_vb_data;
+	p_old_buffer->Lock( 0, 0, &p_old_vb_data, D3DLOCK_READONLY | D3DLOCK_NOFLUSH );
+	p_new_buffer->Lock( 0, 0, &p_new_vb_data, 0 );
+
+	// Calculate the normal of the billboard, using the first tri.
+	float		*p_vert;
+	uint16		indices[4];
+	indices[0]			= mp_index_buffer[0][0];
+	indices[1]			= mp_index_buffer[0][1];
+	indices[2]			= mp_index_buffer[0][2];
+	indices[3]			= mp_index_buffer[0][3];
+
+	p_vert				= (float*)( p_old_vb_data + ( indices[0] * old_vertex_stride ));
+	Mth::Vector v0( p_vert[0], p_vert[1], p_vert[2] );
+	p_vert				= (float*)( p_old_vb_data + ( indices[1] * old_vertex_stride ));
+	Mth::Vector v1( p_vert[0], p_vert[1], p_vert[2] );
+	p_vert				= (float*)( p_old_vb_data + ( indices[2] * old_vertex_stride ));
+	Mth::Vector v2( p_vert[0], p_vert[1], p_vert[2] );
+	Mth::Vector	normal	= Mth::CrossProduct(( v1 - v0 ), ( v2 - v0 )).Normalize();
+
+	// Given the normal, calculate the local right and up (u and v) vectors, based on the billboard type.
+	Mth::Vector u, v;
+
+	switch( mp_billboard_data->m_type )
+	{
+		case sBillboardData::vBILLBOARD_TYPE_SCREEN_ALIGNED:
+		case sBillboardData::vBILLBOARD_TYPE_Y_AXIS_ALIGNED:
+		case sBillboardData::vBILLBOARD_TYPE_WORLD_ORIENTED:
+		{
+			// Use the world 'up' vector to generate the 'u' vector.
+			u = Mth::CrossProduct( normal, Mth::Vector( 0.0f, 1.0f, 0.0f )).Normalize();
+
+			// Use the 'u' vector and the normal vector to generate the 'v' vector.
+			v = Mth::CrossProduct( u, normal ).Normalize();
+			break;
+		}
+		case sBillboardData::vBILLBOARD_TYPE_ARBITRARY_AXIS_ALIGNED:
+		{
+			// Use the pivot axis and the normal vector to generate the 'u' vector.
+			u = Mth::CrossProduct( normal, pivot_axis ).Normalize();
+
+			// Use the 'u' vector and the normal vector to generate the 'v' vector.
+			v = Mth::CrossProduct( u, normal ).Normalize();
+			break;
+		}
+	}
+
+	for( int i = 0; i < 4; ++i )
+	{
+		// The new position is actually the position of the pivot point for the billboard.
+		float *p_pos_old	= (float*)( p_old_vb_data + ( i * old_vertex_stride ));
+		float *p_pos_new	= (float*)( p_new_vb_data + ( i * new_vertex_stride ));
+		p_pos_new[0]		= pivot_pos[X];
+		p_pos_new[1]		= pivot_pos[Y];
+		p_pos_new[2]		= pivot_pos[Z];
+
+		// Introduce normal (which is actually the position of the vertex relative to the pivot).
+		Mth::Vector pos_relative_to_pivot( p_pos_old[0] - pivot_pos[X], p_pos_old[1] - pivot_pos[Y], p_pos_old[2] - pivot_pos[Z] );
+
+		p_pos_new[3]		= Mth::DotProduct( pos_relative_to_pivot, u );
+		p_pos_new[4]		= Mth::DotProduct( pos_relative_to_pivot, v );
+		p_pos_new[5]		= Mth::DotProduct( pos_relative_to_pivot, normal );
+
+		// Copy color.
+		D3DCOLOR *p_col_old	= (D3DCOLOR*)( p_old_vb_data + ( i * old_vertex_stride ) + m_diffuse_offset );
+		D3DCOLOR *p_col_new	= (D3DCOLOR*)( p_new_vb_data + ( i * new_vertex_stride ) + m_diffuse_offset + ( sizeof( float ) * 3 ));
+		p_col_new[0]		= p_col_old[0];
+
+		// Copy uv0...
+		float *p_uv0_old	= (float*)( p_old_vb_data + ( i * old_vertex_stride ) + m_uv0_offset );
+		float *p_uv0_new	= (float*)( p_new_vb_data + ( i * new_vertex_stride ) + m_uv0_offset + ( sizeof( float ) * 3 ));
+		p_uv0_new[0]		= p_uv0_old[0];
+		p_uv0_new[1]		= p_uv0_old[1];
+
+		// ...and additional uv's if present.
+		if(( m_vertex_shader[0] & D3DFVF_TEXCOUNT_MASK ) > D3DFVF_TEX1 )
+		{
+			p_uv0_new[2]		= p_uv0_old[2];
+			p_uv0_new[3]		= p_uv0_old[3];
+		}
+		if(( m_vertex_shader[0] & D3DFVF_TEXCOUNT_MASK ) > D3DFVF_TEX2 )
+		{
+			p_uv0_new[4]		= p_uv0_old[4];
+			p_uv0_new[5]		= p_uv0_old[5];
+		}
+		if(( m_vertex_shader[0] & D3DFVF_TEXCOUNT_MASK ) > D3DFVF_TEX3 )
+		{
+			p_uv0_new[6]		= p_uv0_old[6];
+			p_uv0_new[7]		= p_uv0_old[7];
+		}
+	}
+
+	// Now fix up the mesh. Flag the mesh as being a billboard (stop the mesh being rendered by the regular pathway).
+	m_flags |= sMesh::MESH_FLAG_BILLBOARD;
+
+	// Switch vertex buffers, deleting the old one.
+	mp_vertex_buffer[0]	= p_new_buffer;
+	delete p_old_buffer;
+
+	// Set the new vertex stride, diffuse and uv0 offset (and normal offset, just to be complete).
+	m_vertex_stride		= new_vertex_stride;
+	m_diffuse_offset	+= sizeof( float ) * 3;
+	m_uv0_offset		+= sizeof( float ) * 3;
+	m_normal_offset		= sizeof( float ) * 3;
+
+	// Copy the new vertex buffer into existing buffered buffers if m_num_vertex_buffers > 1.
+	if( m_num_vertex_buffers > 1 )
+	{
+		BYTE *p_buffer0;
+		BYTE *p_bufferN;
+		mp_vertex_buffer[0]->Lock( 0, 0, &p_buffer0, D3DLOCK_READONLY | D3DLOCK_NOFLUSH );
+		for( int vb = 1; vb < m_num_vertex_buffers; ++vb )
+		{
+			delete mp_vertex_buffer[vb];
+			mp_vertex_buffer[vb] = AllocateVertexBuffer( new_vertex_stride * 4 );
+			mp_vertex_buffer[vb]->Lock( 0, 0, &p_bufferN, 0 );
+			CopyMemory( p_bufferN, p_buffer0, new_vertex_stride * 4 );
+		}
+	}
+
+	// Set the new vertex shader.
+	m_vertex_shader[0]	= 999;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::SetBoundingData( Mth::Vector & sphere_center, float radius, Mth::Vector & bb_min, Mth::Vector & bb_max )
+{
+//	m_bbox.Set( bb_min, bb_max );
+
+	m_sphere_center = D3DXVECTOR3( sphere_center[X], sphere_center[Y], sphere_center[Z] );
+	m_sphere_radius = radius;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::Initialize( int				num_vertices,
+						float			*p_positions,
+						float			*p_normals,
+						float			*p_tex_coords,
+						int				num_tc_sets,
+						DWORD			*p_colors,
+						int				num_index_sets,			// How many sets of indices there are (usually 1 set)
+						int				*p_num_indices,			// Pointer to an array of ints containing number of indices per set
+						uint16			**pp_indices,			// Pointer to an array of pointers to the actual indices
+						unsigned long	material_checksum,
+						void			*p_scene,
+						uint16			*p_matrix_indices,
+						uint32			*p_weights,
+						char			*p_vc_wibble_anims )
+{
+	// First thing to do is grab the material pointer for this mesh.
+	mp_material	= ((sScene*)p_scene )->GetMaterial( material_checksum );
+	if( mp_material == NULL )
+	{
+		Dbg_Assert( 0 );
+		return;
+	}
+	
+	if(( num_index_sets == 0 ) || ( p_num_indices[0] == 0 ))
+	{
+		return;
+	}
+
+	uint16 min_index	= ( pp_indices[0] )[0];
+	uint16 max_index	= ( pp_indices[0] )[0];
+	for( int i = 0; i < p_num_indices[0]; ++i )
+	{
+		if(( pp_indices[0] )[i] > max_index )
+		{
+			max_index = ( pp_indices[0] )[i];
+		}
+		else if(( pp_indices[0] )[i] < min_index )
+		{
+			min_index = ( pp_indices[0] )[i];
+		}
+	}
+
+	if( max_index >= num_vertices )
+	{
+		// Error!
+		Dbg_Assert( 0 );
+		return;
+	}
+
+	// Grab top-down heap memory for the mesh workspace buffer. This will need to be as big as the maximum vertex indexed.
+	int16 *p_mesh_workspace_array = new (Mem::Manager::sHandle().TopDownHeap()) int16[max_index + 1];
+
+	// Setup workspace buffer.
+	memset( p_mesh_workspace_array, 1, sizeof( int16 ) * ( max_index + 1 ));
+	for( int i = 0; i < p_num_indices[0]; ++i )
+	{
+		p_mesh_workspace_array[( pp_indices[0] )[i]] = 0;
+	}
+	
+	// Now figure the wasted space.
+	int wasted_verts = 0;
+	for( int i = min_index; i <= max_index; ++i )
+	{
+		if( p_mesh_workspace_array[i] != 0 )
+			++wasted_verts;
+	}
+
+	// Now figure the total number of vertices required for this mesh, to span the min->max indices.
+	uint16 vertices_for_this_mesh	= ( max_index - min_index + 1 ) - wasted_verts;
+	m_num_vertices					= vertices_for_this_mesh;
+
+	// Create the index buffer(s). (Should be 16byte aligned for best performance).
+	for( int ib = 0; ib < num_index_sets; ++ib )
+	{
+		mp_index_buffer[ib]	= new uint16[p_num_indices[ib]];
+		m_num_indices[ib]	= p_num_indices[ib];
+	}
+	
+	// Use the material flags to figure the vertex format.
+	int vertex_size			= 3 * sizeof( float );
+
+	// Include weights (for weighted animation) if present.
+	uint32 biggest_index_used = 0;
+	if( p_weights )
+	{
+		Dbg_Assert( p_matrix_indices );
+
+		// Calculate the biggest weight used.
+		uint32*	p_weight_read	= p_weights + min_index;
+		for( int v = min_index; v <= max_index; ++v )
+		{
+			if( p_mesh_workspace_array[v] == 0 )
+			{
+				// This vertex is used.
+				uint32 w2 = (( p_weight_read[0] >> 22 ) & 0x3FF );
+				if( w2 > 0 )
+				{
+					biggest_index_used = 2;
+					break;
+				}
+				else
+				{
+					uint32 w1 = (( p_weight_read[0] >> 11 ) & 0x7FF );
+					if( w1 > 0 )
+					{
+						biggest_index_used = 1;
+					}
+				}
+			}
+			++p_weight_read;
+		}
+		vertex_size	+= sizeof( uint32 );
+	}
+
+	// Include indices (for weighted animation) if present.
+	if( p_matrix_indices )
+	{
+		Dbg_Assert( p_weights );
+		vertex_size	+= sizeof( uint16 ) * 4;
+	}
+	
+	// Texture coordinates.
+	uint32	tex_coord_pass	= 0;
+	bool	env_mapped		= false;
+	if( p_tex_coords )
+	{
+		for( uint32 pass = 0; pass < mp_material->m_passes; ++pass )
+		{
+			if( mp_material->m_flags[pass] & MATFLAG_ENVIRONMENT )
+			{
+				env_mapped		= true;
+			}
+
+			// Only need UV's for this stage if it is *not* environment mapped.
+			if(( mp_material->mp_tex[pass] ) && ( !( mp_material->m_flags[pass] & MATFLAG_ENVIRONMENT )))
+			{
+				// Will need uv for this pass and all before it.
+				tex_coord_pass	= pass + 1; 
+			}
+		}
+	}
+	else
+	{
+		for( uint32 pass = 0; pass < mp_material->m_passes; ++pass )
+		{
+			if( mp_material->m_flags[pass] & MATFLAG_ENVIRONMENT )
+			{
+				env_mapped		= true;
+			}
+		}
+	}
+
+	if( tex_coord_pass > 0 )
+	{
+		vertex_size			+= 2 * sizeof( float ) * tex_coord_pass;
+	}
+
+	// Assume no normals for now, unless weight information indicates an animating mesh.
+	bool use_normals		= false;
+	bool use_packed_normals	= false;
+
+	if( p_normals || p_weights || env_mapped )
+	{
+		// Need to include normals. They will be packed differently for weighted meshes.
+		use_normals	= true;
+		if( p_weights )
+		{
+			use_packed_normals = true;
+			vertex_size	+= sizeof( uint32 );
+		}
+		else
+		{
+			vertex_size	+= sizeof( float ) * 3;
+		}
+	}
+
+	bool use_colors = false;
+	if( p_colors )
+	{
+		// The raw vertex data does contain vertex colors.
+		vertex_size	+= sizeof( D3DCOLOR );
+		use_colors	= true;
+	}
+
+
+	// Create the vertex buffer.
+	m_vertex_stride	= vertex_size;
+
+	// One allocation for the header and the data buffer.	
+	mp_vertex_buffer[0] = AllocateVertexBuffer( vertex_size * vertices_for_this_mesh );
+
+	// Lock the vertex buffer.
+	BYTE* p_byte;
+	if( D3D_OK != mp_vertex_buffer[0]->Lock(	0,					// Offset to lock.
+												0,					// Size to lock ( 0 means all).
+												&p_byte,			// Pointer to data.
+												D3DLOCK_NOFLUSH ))	// Flags.
+	{
+		Dbg_Assert( 0 );
+		return;
+	}
+	
+	// Copy in vertex position data (for vertices that are used).
+	uint32		byte_write_offset	= 0;
+	float*		p_read				= p_positions + ( min_index * 3 );
+	float*		p_write				= (float*)p_byte;
+
+	for( int v = min_index; v <= max_index; ++v )
+	{
+		if( p_mesh_workspace_array[v] == 0 )
+		{
+			// This vertex is used.
+			p_write[0]	= p_read[0];
+			p_write[1]	= p_read[1];
+			p_write[2]	= p_read[2];
+			p_write	= (float*)((char*)p_write + vertex_size );
+		}
+		p_read += 3;
+	}
+
+	byte_write_offset	+= sizeof( float ) * 3;
+	m_vertex_shader[0]	|= D3DFVF_XYZ;
+
+	// Copy in vertex weight data.
+	if( p_weights )
+	{
+		uint32*	p_weight_read	= p_weights + min_index;
+		uint32*	p_weight_write	= (uint32*)((char*)p_byte + byte_write_offset );
+
+		for( int v = min_index; v <= max_index; ++v )
+		{
+			if( p_mesh_workspace_array[v] == 0 )
+			{
+				// This vertex is used.
+				p_weight_write[0]	= p_weight_read[0];
+				p_weight_write		= (uint32*)((char*)p_weight_write + vertex_size );
+			}
+			++p_weight_read;
+		}
+		byte_write_offset += sizeof( uint32 );
+
+		// No fvf flag setting, since it will be determined at the end.
+	}
+
+	// Copy in vertex matrix index data.
+	if( p_matrix_indices )
+	{
+		uint16*	p_index_read	= p_matrix_indices + ( min_index * 4 );
+		uint16*	p_index_write	= (uint16*)((char*)p_byte + byte_write_offset );
+		for( int v = min_index; v <= max_index; ++v )
+		{
+			if( p_mesh_workspace_array[v] == 0 )
+			{
+				// Have to multiply the indices by three to get the correct register offset, since there are 3 registers
+				// per matrix.
+				p_index_write[0]	= p_index_read[0] * 3;
+				p_index_write[1]	= p_index_read[1] * 3;
+				p_index_write[2]	= p_index_read[2] * 3;
+				p_index_write[3]	= p_index_read[3] * 3;
+				p_index_write		= (uint16*)((char*)p_index_write + vertex_size );
+			}
+			p_index_read += 4;
+		}
+		byte_write_offset += sizeof( uint16 ) * 4;
+
+		// No fvf flag setting, since it will be determined at the end.
+	}
+
+	// Copy in normals data.
+	if( use_normals )
+	{
+		m_normal_offset		= (uint8)byte_write_offset;
+		if( use_packed_normals )
+		{
+			float *p_read	= p_normals + ( min_index * 3 );
+			uint32 *p_write	= (uint32*)((char*)p_byte + byte_write_offset );
+			for( int v = min_index; v <= max_index; ++v )
+			{
+				if( p_mesh_workspace_array[v] == 0 )
+				{
+					// The packed normal format is as follows:
+					// 31                                             0
+					// |----- 10 -----|----- 11 ------|----- 11 ------|
+					// |       z      |       y       |       x       |
+					uint32 snx	= Ftoi_ASM( p_read[0] * 1023.0f );
+					uint32 sny	= Ftoi_ASM( p_read[1] * 1023.0f );
+					uint32 snz	= Ftoi_ASM( p_read[2] * 511.0f );
+					p_write[0]	= ( snx & 0x7FF ) | (( sny & 0x7FF ) << 11 ) | (( snz & 0x3FF ) << 22 );
+					p_write		= (uint32*)((char*)p_write + vertex_size );
+				}
+				p_read += 3;
+			}
+			byte_write_offset += sizeof( uint32 );
+		}
+		else
+		{
+			float*	p_read	= p_normals + ( min_index * 3 );
+			float*	p_write	= (float*)((char*)p_byte + byte_write_offset );
+			for( int v = min_index; v <= max_index; ++v )
+			{
+				if( p_mesh_workspace_array[v] == 0 )
+				{
+					p_write[0]	= p_read[0];
+					p_write[1]	= p_read[1];
+					p_write[2]	= p_read[2];
+					p_write		= (float*)((char*)p_write + vertex_size );
+				}
+				p_read += 3;
+			}
+			byte_write_offset += sizeof( float ) * 3;
+		}
+		m_vertex_shader[0]	|= D3DFVF_NORMAL;
+	}
+
+	// Copy in vertex color data.
+	if( use_colors )
+	{
+		m_diffuse_offset	= (uint8)byte_write_offset;
+		DWORD*	p_col_read	= p_colors + min_index;
+		DWORD*	p_col_write	= (DWORD*)((char*)p_byte + byte_write_offset );
+		for( int v = min_index; v <= max_index; ++v )
+		{
+			if( p_mesh_workspace_array[v] == 0 )
+			{
+				p_col_write[0]	= p_col_read[0];
+				p_col_write		= (DWORD*)((char*)p_col_write + vertex_size );
+			}
+			p_col_read++;
+		}
+		byte_write_offset += sizeof( DWORD );
+		m_vertex_shader[0] |= D3DFVF_DIFFUSE;
+	}
+
+	// Copy in vertex texture coordinate data.
+	if(( tex_coord_pass > 0 ) && ( p_tex_coords != NULL ))
+	{
+		m_uv0_offset						= (uint8)byte_write_offset;
+		p_read								= p_tex_coords + ( min_index * 2 * num_tc_sets );
+		p_write								= (float*)((char*)p_byte + byte_write_offset );
+		for( int v = min_index; v <= max_index; ++v )
+		{
+			if( p_mesh_workspace_array[v] == 0 )
+			{
+				for( uint32 pass = 0; pass < tex_coord_pass; ++pass )
+				{
+					p_write[( pass * 2 ) + 0]	= p_read[( pass * 2 ) + 0];
+					p_write[( pass * 2 ) + 1]	= p_read[( pass * 2 ) + 1];
+				}
+				p_write	= (float*)((char*)p_write + vertex_size );
+			}
+			p_read = p_read + ( num_tc_sets * 2 );
+		}
+		byte_write_offset	+= sizeof( float ) * 2 * tex_coord_pass;
+
+		switch( tex_coord_pass )
+		{
+			case 1:
+			{
+				m_vertex_shader[0]	|= D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 );
+				break;
+			}
+			case 2:
+			{
+				m_vertex_shader[0]	|= D3DFVF_TEX2 | D3DFVF_TEXCOORDSIZE2( 0 ) | D3DFVF_TEXCOORDSIZE2( 1 );
+				break;
+			}
+			case 3:
+			{
+				m_vertex_shader[0]	|= D3DFVF_TEX3 | D3DFVF_TEXCOORDSIZE2( 0 ) | D3DFVF_TEXCOORDSIZE2( 1 ) | D3DFVF_TEXCOORDSIZE2( 2 );
+				break;
+			}
+			case 4:
+			{
+				m_vertex_shader[0]	|= D3DFVF_TEX4 | D3DFVF_TEXCOORDSIZE2( 0 ) | D3DFVF_TEXCOORDSIZE2( 1 ) | D3DFVF_TEXCOORDSIZE2( 2 ) | D3DFVF_TEXCOORDSIZE2( 3 );
+				break;
+			}
+			default:
+			{
+				Dbg_MsgAssert( 0, ( "Bad number of passes" ));
+				break;
+			}
+		}
+	}
+
+	// Create the vertex color wibble array if data is present.
+	if( p_vc_wibble_anims )
+	{
+		mp_vc_wibble_data			= new char[m_num_vertices];
+		int vc_wibble_data_offset	= 0;
+
+		for( int v = min_index; v <= max_index; ++v )
+		{
+			if( p_mesh_workspace_array[v] == 0 )
+			{
+				mp_vc_wibble_data[vc_wibble_data_offset++] = p_vc_wibble_anims[v];
+			}
+		}
+		// Double buffer the vertex buffer.
+		CreateDuplicateVertexBuffers( 1 );
+	}
+
+	// Process the workspace array.
+	int offset = 0;
+	for( int v = 0; v <= max_index; ++v )
+	{
+		if( p_mesh_workspace_array[v] == 0 )
+		{
+			// This vertex is used.
+			p_mesh_workspace_array[v] = offset;
+		}
+		else
+		{
+			// This vertex is not used. Update the offset for the next used vertex.
+			--offset;
+		}
+	}
+	
+	// Copy in index data, normalising the indices for this vertex buffer (i.e. so the lowest index will reference
+	// vertex 0 in the buffer built specifically for this mesh).
+	for( int ib = 0; ib < num_index_sets; ++ib )
+	{
+		for( int i = 0; i < p_num_indices[ib]; ++i )
+		{
+			uint16 idx				= ( pp_indices[ib] )[i];
+			mp_index_buffer[ib][i]	= idx + p_mesh_workspace_array[idx];
+		}
+	}
+	
+	// Can now remove the mesh workspace array.
+	delete [] p_mesh_workspace_array;
+	
+	// Set the correct vertex shader if a weighted mesh.
+	// The number of indices used will be one more than the biggest index used (given 0 base).
+	if( p_weights )
+	{
+		m_vertex_shader[0] = GetVertexShader( use_colors, ( mp_material->m_flags[0] & MATFLAG_SPECULAR ) ? true : false, biggest_index_used + 1 );
+	}
+
+	// Set the pixel shader regardless.
+	GetPixelShader( mp_material, &m_pixel_shader );
+
+	if( num_index_sets > 1 )
+	{
+		mp_index_lod_data = new float[num_index_sets];
+		for( int d = 0; d < num_index_sets; ++d )
+		{
+			float dist				= ( 15.0f + ( d * 10.0f )) * 12.0f;
+			mp_index_lod_data[d]	= dist;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sMesh::DrawBoundingSphere( void )
+{
+#	ifdef __NOPT_ASSERT__
+	const uint32 NUM_SPHERE_POINTS	= 64;
+
+	static uint32 sphere_buffer[NUM_SPHERE_POINTS * 4];
+
+	D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+	Mth::Vector up( 0.0f, 1.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+	Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+
+	set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+	set_pixel_shader( PixelShader5 );
+
+	int		index		= 0;
+	float	angle		= 0.0f;
+	float	angle_step	= ( Mth::PI * 2.0f ) / (float)NUM_SPHERE_POINTS;
+
+	// Draw the screen aligned sphere.
+	for( uint32 i = 0; i < NUM_SPHERE_POINTS; ++i, angle += angle_step )
+	{
+		float x = m_sphere_center.x + ( m_sphere_radius * cosf( angle ) * screen_right[X] ) + ( m_sphere_radius * sinf( angle ) * screen_up[X] );
+		float y = m_sphere_center.y + ( m_sphere_radius * cosf( angle ) * screen_right[Y] ) + ( m_sphere_radius * sinf( angle ) * screen_up[Y] );
+		float z = m_sphere_center.z + ( m_sphere_radius * cosf( angle ) * screen_right[Z] ) + ( m_sphere_radius * sinf( angle ) * screen_up[Z] );
+
+		sphere_buffer[index++]	= *((uint32*)&x );
+		sphere_buffer[index++]	= *((uint32*)&y );
+		sphere_buffer[index++]	= *((uint32*)&z );
+		sphere_buffer[index++]	= 0x80800000;
+	}
+	D3DDevice_DrawVerticesUP( D3DPT_LINELOOP, NUM_SPHERE_POINTS, sphere_buffer, sizeof( uint32 ) * 4 );
+
+	// Draw the xz plane sphere.
+	index		= 0;
+	angle		= 0.0f;
+	for( uint32 i = 0; i < NUM_SPHERE_POINTS; ++i, angle += angle_step )
+	{
+		float x = m_sphere_center.x + ( m_sphere_radius * cosf( angle ));
+		float y = m_sphere_center.y;
+		float z = m_sphere_center.z + ( m_sphere_radius * sinf( angle ));
+
+		sphere_buffer[index++]	= *((uint32*)&x );
+		sphere_buffer[index++]	= *((uint32*)&y );
+		sphere_buffer[index++]	= *((uint32*)&z );
+		sphere_buffer[index++]	= 0x80800000;
+	}
+	D3DDevice_DrawVerticesUP( D3DPT_LINELOOP, NUM_SPHERE_POINTS, sphere_buffer, sizeof( uint32 ) * 4 );
+#	endif
+}
+
+
+
+
+
+} // namespace NxXbox
+
+
diff --git a/Code/Gfx/XBox/NX/mesh.h b/Code/Gfx/XBox/NX/mesh.h
new file mode 100644
index 0000000..06e7db7
--- /dev/null
+++ b/Code/Gfx/XBox/NX/mesh.h
@@ -0,0 +1,159 @@
+#ifndef __MESH_H
+#define __MESH_H
+
+#include 
+#include 
+#include 
+#include "material.h"
+
+namespace NxXbox
+{
+
+struct sCASData
+{
+	uint32	mask;
+	uint32	data0;
+	uint32	data1;
+	uint32	start_index;
+};
+	
+
+
+struct sBillboardData
+{
+	enum EBillboardType
+	{
+		vBILLBOARD_TYPE_SCREEN_ALIGNED			= 0,
+		vBILLBOARD_TYPE_Y_AXIS_ALIGNED			= 1,		// Specialised case of arbitrary axial alignment.
+		vBILLBOARD_TYPE_ARBITRARY_AXIS_ALIGNED	= 2,
+		vBILLBOARD_TYPE_WORLD_ORIENTED			= 3
+	};
+	
+	EBillboardType	m_type;
+	Mth::Vector		m_pivot_pos;
+	Mth::Vector		m_pivot_axis;							// Normalised axis of rotation, valid only for type ARBITRARY_AXIS_ALIGNED.
+};
+
+
+
+struct sMesh
+{
+public:
+
+	static const uint32	VERTEX_SHADER_STACK_SIZE	= 2;
+	static const uint32	MAX_VERTEX_BUFFERS			= 3;
+	static const uint32	MAX_INDEX_BUFFERS			= 8;		// Multiple index buffers are used for triangle-decimated LOD's.
+	
+	enum EMeshFlags
+	{
+		MESH_FLAG_IS_INSTANCE				= 0x01,
+		MESH_FLAG_NO_SKATER_SHADOW			= 0x02,
+		MESH_FLAG_MATERIAL_COLOR_OVERRIDE	= 0x04,
+		MESH_FLAG_VERTEX_COLOR_WIBBLE		= 0x08,
+		MESH_FLAG_BILLBOARD					= 0x10,		// This mesh is a billboard.
+		MESH_FLAG_HAS_TRANSFORM				= 0x20,
+		MESH_FLAG_ACTIVE					= 0x40,
+		MESH_FLAG_NO_ANISOTROPIC			= 0x80,		// No texture 0 anisotropic filtering for this mesh.
+		MESH_FLAG_NO_ZWRITE					= 0x100,	// No zwrite for this mesh.
+		MESH_FLAG_SHADOW_VOLUME				= 0x200,	// This mesh represents a single shadow volume.
+		MESH_FLAG_BUMPED_WATER				= 0x400,
+		MESH_FLAG_UNLIT						= 0x20000	// This corresponds to the material unlit flag during the scene conversion process.
+	};
+
+					sMesh( void );
+					~sMesh( void );
+
+	// Functions
+	void			wibble_vc( void );
+	void			wibble_normals( void );
+	uint32			GetChecksum()	const			{ return Checksum; }
+	uint32			GetFlags()		const			{ return m_flags; }
+	void			SetActive( bool active )		{ if( active ) m_flags |= MESH_FLAG_ACTIVE; else m_flags &= ~MESH_FLAG_ACTIVE; }
+	void			SetVisibility( uint8 mask )		{ m_visibility_mask	= mask; }
+	void			SetPosition( Mth::Vector &pos );
+	void			GetPosition( Mth::Vector *p_pos );
+	void			SetYRotation( Mth::ERot90 rot );
+	sMesh			*Clone( bool instance = false );
+	void			SetBoneIndex( int8 idx )		{ m_bone_index = idx; }
+	int8			GetBoneIndex( void )			{ return m_bone_index; }
+	void			SetBillboardData( uint32 type, Mth::Vector & pivot_pos, Mth::Vector & pivot_axis );
+	void			SetBoundingData( Mth::Vector & sphere_center, float radius, Mth::Vector & bb_min, Mth::Vector & bb_max );
+
+	// Grabs memory chunk and builds vertex buffer from heap memory, rather than getting DX to do it.
+	static IDirect3DVertexBuffer8	*AllocateVertexBuffer( uint32 size );
+
+	// All-purpose mesh instancing code, used for static geometry and animating weighted meshes.
+	void			Initialize( int				num_vertices,
+								float			*p_positions,
+								float			*p_normals,
+								float			*p_tex_coords,
+								int				num_tc_sets,
+								DWORD			*p_colors,
+								int				num_index_sets,			// How many sets of indices there are (usually 1 set)
+								int				*p_num_indices,			// Pointer to an array of ints containing number of indices per set
+								uint16			**pp_indices,			// Pointer to an array of pointers to the actual indices
+								unsigned long	material_checksum,
+								void			*p_scene,
+								uint16			*p_matrix_indices	= NULL,
+								uint32			*p_weights			= NULL,
+								char			*p_vc_wibble_anims	= NULL );
+
+	void			Submit( void );
+	void			HandleColorOverride( void );
+	void			CreateDuplicateVertexBuffers( int n );
+	void			SwapVertexBuffers( void );
+	void			PushVertexShader( uint32 shader_id );
+	void			PopVertexShader( void );
+	void			Crunch( void );
+
+	// Debug functions, will be empty stub for Final build.
+	void			DrawBoundingSphere( void );
+
+	// Members. Order is important here since details required for fast mesh rejection need to be in top 32 bytes of structure.
+	uint32					m_flags;
+	D3DXVECTOR3				m_sphere_center;
+	float					m_sphere_radius;
+	sMaterial				*mp_material;
+
+	uint8					m_vertex_stride;
+	uint8					m_current_write_vertex_buffer;
+	uint8					m_num_vertex_buffers;
+	uint8					m_visibility_mask;
+	uint8					m_diffuse_offset;		// Offset into vertex format for diffuse color component.
+	uint8					m_normal_offset;		// Offset into vertex format for normal component.
+	uint8					m_uv0_offset;			// Offset into vertex format for uv0 component.
+	int8					m_bone_index;
+
+	uint16					m_load_order;
+	uint16					m_num_vertices;
+	uint16					m_num_indices[MAX_INDEX_BUFFERS];
+
+	uint32					Checksum;
+	uint32					m_vertex_shader[VERTEX_SHADER_STACK_SIZE];
+	uint32					m_pixel_shader;
+
+	float					*mp_index_lod_data;				// List of distances (squared) for which a particular index list should be used.
+															// NULL for meshes that only have one set of index data.
+
+	sBillboardData			*mp_billboard_data;				// Data defining billboard properties. NULL for non-billboard meshes.
+
+	D3DPRIMITIVETYPE		m_primitive_type;
+	uint16					*mp_index_buffer[MAX_INDEX_BUFFERS];
+	IDirect3DVertexBuffer8*	mp_vertex_buffer[MAX_VERTEX_BUFFERS];
+
+	float					m_bounding_sphere_nearest_z;	// Used for dynamic sorting during front-back block sorting.
+	float					m_material_color_override[3];
+	char					*mp_vc_wibble_data;
+	Mth::Matrix				*mp_transform;
+};
+
+
+void SetMeshScalingParameters( Nx::SMeshScalingParameters* pParams );
+void DisableMeshScaling( void );
+void ApplyMeshScaling( float* p_vertices, int num_verts );
+
+
+} // namespace NxXbox
+
+#endif // __MESH_H
+
diff --git a/Code/Gfx/XBox/NX/mipmap.inl b/Code/Gfx/XBox/NX/mipmap.inl
new file mode 100644
index 0000000..d886007
--- /dev/null
+++ b/Code/Gfx/XBox/NX/mipmap.inl
@@ -0,0 +1,218 @@
+// Xbox Shader Assembler 1.00.4705.1
+D3DPIXELSHADERDEF psd;
+ZeroMemory(&psd, sizeof(psd));
+psd.PSCombinerCount=PS_COMBINERCOUNT(
+    8,
+    PS_COMBINERCOUNT_MUX_MSB | PS_COMBINERCOUNT_UNIQUE_C0 | PS_COMBINERCOUNT_UNIQUE_C1);
+psd.PSTextureModes=PS_TEXTUREMODES(
+    PS_TEXTUREMODES_PROJECT2D,
+    PS_TEXTUREMODES_PROJECT2D,
+    PS_TEXTUREMODES_PROJECT2D,
+    PS_TEXTUREMODES_PROJECT2D);
+psd.PSInputTexture=PS_INPUTTEXTURE(0,0,0,0);
+psd.PSDotMapping=PS_DOTMAPPING(
+    0,
+    PS_DOTMAPPING_ZERO_TO_ONE,
+    PS_DOTMAPPING_ZERO_TO_ONE,
+    PS_DOTMAPPING_ZERO_TO_ONE);
+psd.PSCompareMode=PS_COMPAREMODE(
+    PS_COMPAREMODE_S_LT | PS_COMPAREMODE_T_LT | PS_COMPAREMODE_R_LT | PS_COMPAREMODE_Q_LT,
+    PS_COMPAREMODE_S_LT | PS_COMPAREMODE_T_LT | PS_COMPAREMODE_R_LT | PS_COMPAREMODE_Q_LT,
+    PS_COMPAREMODE_S_LT | PS_COMPAREMODE_T_LT | PS_COMPAREMODE_R_LT | PS_COMPAREMODE_Q_LT,
+    PS_COMPAREMODE_S_LT | PS_COMPAREMODE_T_LT | PS_COMPAREMODE_R_LT | PS_COMPAREMODE_Q_LT);
+
+//------------- Stage 0 -------------
+psd.PSRGBInputs[0]=PS_COMBINERINPUTS(
+    PS_REGISTER_T0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_IDENTITY);
+psd.PSAlphaInputs[0]=PS_COMBINERINPUTS(
+    PS_REGISTER_T0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_IDENTITY);
+psd.PSRGBOutputs[0]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_R0,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[0]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_R0,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[0] = 0x40404040;
+psd.PSConstant1[0] = 0x00000000;
+
+//------------- Stage 1 -------------
+psd.PSRGBInputs[1]=PS_COMBINERINPUTS(
+    PS_REGISTER_T1 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSAlphaInputs[1]=PS_COMBINERINPUTS(
+    PS_REGISTER_T1 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSRGBOutputs[1]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[1]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[1] = 0x40404040;
+psd.PSConstant1[1] = 0x00000000;
+
+//------------- Stage 2 -------------
+psd.PSRGBInputs[2]=PS_COMBINERINPUTS(
+    PS_REGISTER_T2 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSAlphaInputs[2]=PS_COMBINERINPUTS(
+    PS_REGISTER_T2 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSRGBOutputs[2]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[2]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[2] = 0x40404040;
+psd.PSConstant1[2] = 0x00000000;
+
+//------------- Stage 3 -------------
+psd.PSRGBInputs[3]=PS_COMBINERINPUTS(
+    PS_REGISTER_T3 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSAlphaInputs[3]=PS_COMBINERINPUTS(
+    PS_REGISTER_T3 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSRGBOutputs[3]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[3]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[3] = 0x40404040;
+psd.PSConstant1[3] = 0x00000000;
+
+//------------- Stage 4 -------------
+psd.PSRGBInputs[4]=PS_COMBINERINPUTS(
+    PS_REGISTER_T0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSAlphaInputs[4]=PS_COMBINERINPUTS(
+    PS_REGISTER_T0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSRGBOutputs[4]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[4]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[4] = 0x00000000;
+psd.PSConstant1[4] = 0x00000000;
+
+//------------- Stage 5 -------------
+psd.PSRGBInputs[5]=PS_COMBINERINPUTS(
+    PS_REGISTER_T1 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSAlphaInputs[5]=PS_COMBINERINPUTS(
+    PS_REGISTER_T1 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSRGBOutputs[5]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[5]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[5] = 0x00000000;
+psd.PSConstant1[5] = 0x00000000;
+
+//------------- Stage 6 -------------
+psd.PSRGBInputs[6]=PS_COMBINERINPUTS(
+    PS_REGISTER_T2 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSAlphaInputs[6]=PS_COMBINERINPUTS(
+    PS_REGISTER_T2 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSRGBOutputs[6]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[6]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[6] = 0x00000000;
+psd.PSConstant1[6] = 0x00000000;
+
+//------------- Stage 7 -------------
+psd.PSRGBInputs[7]=PS_COMBINERINPUTS(
+    PS_REGISTER_T3 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_RGB | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_RGB | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSAlphaInputs[7]=PS_COMBINERINPUTS(
+    PS_REGISTER_T3 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+    PS_REGISTER_C0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_NEGATE,
+    PS_REGISTER_ZERO | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_UNSIGNED_INVERT,
+    PS_REGISTER_R0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY);
+psd.PSRGBOutputs[7]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSAlphaOutputs[7]=PS_COMBINEROUTPUTS(
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_DISCARD,
+    PS_REGISTER_R0,
+    PS_COMBINEROUTPUT_IDENTITY | PS_COMBINEROUTPUT_AB_MULTIPLY | PS_COMBINEROUTPUT_CD_MULTIPLY | PS_COMBINEROUTPUT_AB_CD_SUM);
+psd.PSConstant0[7] = 0x00000000;
+psd.PSConstant1[7] = 0x00000000;
+
+psd.PSC0Mapping = 0x76543210;
+psd.PSC1Mapping = 0xffffffff;
+psd.PSFinalCombinerConstants = 0x000001ff;
diff --git a/Code/Gfx/XBox/NX/nx_init.cpp b/Code/Gfx/XBox/NX/nx_init.cpp
new file mode 100644
index 0000000..e1b81a1
--- /dev/null
+++ b/Code/Gfx/XBox/NX/nx_init.cpp
@@ -0,0 +1,472 @@
+#include 
+
+// Include the following two files for detailed timing data collection.
+//#include 
+//#include 
+
+#include "sys/config/config.h"
+#include "nx_init.h"
+#include "anim.h"
+#include "chars.h"
+#include "scene.h"
+#include "render.h"
+#include "instance.h"
+#include "gamma.h"
+#include "grass.h"
+
+namespace NxXbox
+{
+
+sEngineGlobals	EngineGlobals;
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void InitialiseRenderstates( void )
+{
+	D3DDevice_SetRenderState( D3DRS_LOCALVIEWER,		FALSE );
+
+	D3DDevice_SetRenderState( D3DRS_COLORVERTEX,		FALSE );
+
+	EngineGlobals.lighting_enabled		= false;
+	D3DDevice_SetRenderState( D3DRS_LIGHTING,			FALSE );
+
+	EngineGlobals.specular_enabled		= 0;
+	D3DDevice_SetRenderState( D3DRS_SPECULARENABLE,		FALSE );
+	
+	EngineGlobals.cull_mode				= D3DCULL_CW;
+	D3DDevice_SetRenderState( D3DRS_CULLMODE,			D3DCULL_CW );
+
+	EngineGlobals.allow_envmapping		= true;
+
+	EngineGlobals.dither_enable			= TRUE;
+	D3DDevice_SetRenderState( D3DRS_DITHERENABLE,		TRUE );
+
+	EngineGlobals.z_test_enabled		= TRUE;
+	D3DDevice_SetRenderState( D3DRS_ZFUNC,				D3DCMP_LESSEQUAL );
+
+	EngineGlobals.z_write_enabled		= TRUE;
+	D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE,		TRUE );
+	
+	EngineGlobals.alpha_blend_enable	= 1;
+	D3DDevice_SetRenderState( D3DRS_ALPHABLENDENABLE,	TRUE );
+
+	EngineGlobals.alpha_test_enable		= 1;
+	D3DDevice_SetRenderState( D3DRS_ALPHATESTENABLE,	TRUE );
+
+	D3DDevice_SetRenderState( D3DRS_ALPHAFUNC,			D3DCMP_GREATEREQUAL );
+
+	EngineGlobals.alpha_ref				= 0;
+	D3DDevice_SetRenderState( D3DRS_ALPHAREF,			0x00 );
+
+	for( int stage = 0; stage < 4; ++stage )
+	{
+		EngineGlobals.uv_addressing[stage]		= 0x00000000UL;
+		EngineGlobals.mip_map_lod_bias[stage]	= 0x00000000UL;
+		EngineGlobals.p_texture[stage]			= NULL;
+		EngineGlobals.min_mag_filter[stage]		= 0x00010000UL;
+		EngineGlobals.color_sign[stage]			= 0x00000000UL;
+
+		D3DDevice_SetTextureStageState( stage, D3DTSS_MAGFILTER,		D3DTEXF_LINEAR );
+		D3DDevice_SetTextureStageState( stage, D3DTSS_MIPFILTER,		D3DTEXF_LINEAR );
+
+		if( stage == 0 )
+		{
+			// If we are running in 720p or 1080i mode, need max pixel pushing power - avoid anisotropic filtering.
+			// MSM PERFCHANGE
+			if( EngineGlobals.backbuffer_height > 480 )
+			{
+				D3DDevice_SetTextureStageState( stage, D3DTSS_MINFILTER,		D3DTEXF_LINEAR );
+			}
+			else
+			{
+				D3DDevice_SetTextureStageState( stage, D3DTSS_MINFILTER,		D3DTEXF_ANISOTROPIC );
+				D3DDevice_SetTextureStageState( stage, D3DTSS_MAXANISOTROPY, 3 );
+			}
+		}
+		else
+		{
+			D3DDevice_SetTextureStageState( stage, D3DTSS_MINFILTER,		D3DTEXF_LINEAR );
+		}
+		D3DDevice_SetTextureStageState( stage, D3DTSS_ADDRESSU,			D3DTADDRESS_WRAP );
+		D3DDevice_SetTextureStageState( stage, D3DTSS_ADDRESSV,			D3DTADDRESS_WRAP );
+	}
+
+	// Set up material for specular properties for fixed function pipeline.
+	D3DMATERIAL8	test_mat;
+	ZeroMemory( &test_mat, sizeof( D3DMATERIAL8 ));
+	D3DDevice_SetMaterial( &test_mat );
+
+	D3DDevice_SetRenderState( D3DRS_DIFFUSEMATERIALSOURCE,	D3DMCS_COLOR1 );
+    D3DDevice_SetRenderState( D3DRS_SPECULARMATERIALSOURCE, D3DMCS_MATERIAL );
+    D3DDevice_SetRenderState( D3DRS_AMBIENTMATERIALSOURCE,	D3DMCS_COLOR1 );
+    D3DDevice_SetRenderState( D3DRS_EMISSIVEMATERIALSOURCE,	D3DMCS_MATERIAL );
+
+	// Set these up so they will get reset first time through.
+	EngineGlobals.blend_mode_value			= 0xDEADBABE;
+	EngineGlobals.blend_op					= 0xDEADBABE;
+	EngineGlobals.src_blend					= 0xDEADBABE;
+	EngineGlobals.dest_blend				= 0xDEADBABE;
+
+	EngineGlobals.screenshot_name[0]		= 0;
+	
+	// Build the required vertex shaders.
+	CreateWeightedMeshVertexShaders();
+
+	// Build required pixel shaders.
+	create_pixel_shaders();
+
+	// Set pixel shader constants.
+	EngineGlobals.pixel_shader_constants[16]	= 0.0f;
+	EngineGlobals.pixel_shader_constants[17]	= 0.5f;
+	EngineGlobals.pixel_shader_constants[18]	= 0.0f;	// Fog denisty.
+	EngineGlobals.pixel_shader_constants[19]	= 0.5f;	// Constant 0.5
+
+	EngineGlobals.pixel_shader_override			= 0;
+	EngineGlobals.pixel_shader_id				= 0;
+	EngineGlobals.upload_pixel_shader_constants = false;
+	EngineGlobals.is_orthographic				= false;
+	EngineGlobals.custom_pipeline_enabled		= false;
+	EngineGlobals.vertex_shader_override		= 0;
+	EngineGlobals.texture_stage_override		= 0;
+	EngineGlobals.material_override				= 0;
+	EngineGlobals.blend_mode_override			= 0;
+
+	EngineGlobals.clear_color_buffer			= true;
+	EngineGlobals.clear_color					= 0x00506070;
+	EngineGlobals.letterbox_active				= false;
+
+	// Set default directional lights
+	EngineGlobals.directional_light_color[0]	= -0.5f;		// Dir0
+	EngineGlobals.directional_light_color[1]	= -0.8660254f;
+	EngineGlobals.directional_light_color[2]	= 0.0f;
+	EngineGlobals.directional_light_color[3]	= 0.0f;
+	EngineGlobals.directional_light_color[4]	= 0.586f;		// Col0
+	EngineGlobals.directional_light_color[5]	= 0.586f;
+	EngineGlobals.directional_light_color[6]	= 0.586f;
+	EngineGlobals.directional_light_color[7]	= 1.0f;
+	
+	EngineGlobals.directional_light_color[8]	= 1.0f;			// Dir1
+	EngineGlobals.directional_light_color[9]	= 0.0f;
+	EngineGlobals.directional_light_color[10]	= 0.0f;
+	EngineGlobals.directional_light_color[11]	= 0.0f;
+	EngineGlobals.directional_light_color[12]	= 0.0f;			// Col1
+	EngineGlobals.directional_light_color[13]	= 0.0f;
+	EngineGlobals.directional_light_color[14]	= 0.0f;
+	EngineGlobals.directional_light_color[15]	= 1.0f;
+	
+	EngineGlobals.directional_light_color[16]	= 1.0f;			// Dir2
+	EngineGlobals.directional_light_color[17]	= 0.0f;
+	EngineGlobals.directional_light_color[18]	= 0.0f;
+	EngineGlobals.directional_light_color[19]	= 0.0f;
+	EngineGlobals.directional_light_color[20]	= 0.0f;			// Col2
+	EngineGlobals.directional_light_color[21]	= 0.0f;
+	EngineGlobals.directional_light_color[22]	= 0.0f;
+	EngineGlobals.directional_light_color[23]	= 1.0f;
+
+	// Set default ambient light.
+	EngineGlobals.ambient_light_color[0]		= 0.5865f;
+	EngineGlobals.ambient_light_color[1]		= 0.5865f;
+	EngineGlobals.ambient_light_color[2]		= 0.5865f;
+	EngineGlobals.ambient_light_color[3]		= 1.0f;
+ 
+	EngineGlobals.fog_enabled					= false;
+	EngineGlobals.fog_color						= 0x00000000UL;
+	EngineGlobals.fog_start						= FEET_TO_INCHES( -20.0f );
+	EngineGlobals.fog_end						= FEET_TO_INCHES( -2050.0f );
+	
+	D3DDevice_SetRenderState( D3DRS_FOGENABLE,		EngineGlobals.fog_enabled );
+	D3DDevice_SetRenderState( D3DRS_FOGTABLEMODE,	D3DFOG_LINEAR );
+//	D3DDevice_SetRenderState( D3DRS_FOGTABLEMODE,	D3DFOG_EXP );
+//	D3DDevice_SetRenderState( D3DRS_FOGSTART,		*((DWORD*)( &EngineGlobals.fog_start )));
+//	D3DDevice_SetRenderState( D3DRS_FOGEND,			*((DWORD*)( &EngineGlobals.fog_end )));
+	D3DDevice_SetRenderState( D3DRS_FOGCOLOR,		EngineGlobals.fog_color );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void InitialiseEngine( void )
+{
+	D3DPRESENT_PARAMETERS   params;
+	DWORD					video_flags = XGetVideoFlags();
+
+	EngineGlobals.loadingbar_timer_event	= 0;
+	
+	// Setup default values for the screen conversion macro.
+	EngineGlobals.screen_conv_x_multiplier	= 640.0f / 640.0f;
+	EngineGlobals.screen_conv_y_multiplier	= 480.0f / 480.0f;
+	EngineGlobals.screen_conv_x_offset		= 0;
+	EngineGlobals.screen_conv_y_offset		= 16;
+	
+	ZeroMemory( ¶ms, sizeof( D3DPRESENT_PARAMETERS ));
+
+	// This setting required for any multisample presentation.
+	params.SwapEffect						= D3DSWAPEFFECT_DISCARD;
+
+	// Let D3D create the depth-stencil buffer for us.
+	params.EnableAutoDepthStencil			= TRUE;
+
+	// Select default refresh rate and presentation interval. Note: When we switch to the December SDK
+	// we can use the ONE_OR_IMMEDIATE value (if the tearing looks okay).
+	if(( Config::GetDisplayType() == Config::DISPLAY_TYPE_PAL ) && ( Config::FPS() == 60 ))
+	{
+		// PAL 60Hz has been selected - need to set this refresh rate explicitly.
+		params.FullScreen_RefreshRateInHz	= 60;
+	}
+	else
+	{
+		params.FullScreen_RefreshRateInHz	= D3DPRESENT_RATE_DEFAULT;
+	}
+//	params.FullScreen_PresentationInterval	= D3DPRESENT_INTERVAL_ONE;
+	params.FullScreen_PresentationInterval	= D3DPRESENT_INTERVAL_ONE_OR_IMMEDIATE;
+//	params.FullScreen_PresentationInterval	= D3DPRESENT_INTERVAL_IMMEDIATE;
+
+	// Set up the back buffer format.
+	params.BackBufferCount					= 1;
+	params.BackBufferWidth					= 640;
+	params.BackBufferHeight					= 480;
+	params.BackBufferFormat					= D3DFMT_LIN_X8R8G8B8;
+
+	// Set up the Z-stencil buffer format and multisample format.
+	params.AutoDepthStencilFormat			= D3DFMT_D24S8;
+//	params.MultiSampleType					= D3DMULTISAMPLE_NONE;
+	params.MultiSampleType					= D3DMULTISAMPLE_2_SAMPLES_MULTISAMPLE_LINEAR;
+//	params.MultiSampleType					= D3DMULTISAMPLE_2_SAMPLES_MULTISAMPLE_QUINCUNX;
+//	params.MultiSampleType					= D3DMULTISAMPLE_4_SAMPLES_SUPERSAMPLE_LINEAR;
+
+	// Set flag for widescreen where appropriate.
+	if( video_flags & XC_VIDEO_FLAGS_WIDESCREEN )
+	{
+		params.Flags			|= D3DPRESENTFLAG_WIDESCREEN;
+
+		// Optionally set up 720×480 back buffer.
+		// Set up 16:9 projection transform.
+	}
+
+	
+	// Set flag for progrssive scan where appropriate.
+	if( video_flags & XC_VIDEO_FLAGS_HDTV_720p )
+	{
+		params.Flags			|= D3DPRESENTFLAG_PROGRESSIVE | D3DPRESENTFLAG_WIDESCREEN;
+		params.BackBufferWidth	= 1280;
+		params.BackBufferHeight	= 720;
+
+		// Turn off FSAA.
+		params.MultiSampleType	= D3DMULTISAMPLE_NONE;
+
+		EngineGlobals.screen_conv_x_multiplier	= 1280.0f / 704.0f;
+		EngineGlobals.screen_conv_y_multiplier	= 720.0f / 480.0f;
+		EngineGlobals.screen_conv_x_offset		= 32;
+		EngineGlobals.screen_conv_y_offset		= 32;
+	}
+	else if( video_flags & XC_VIDEO_FLAGS_HDTV_480p )
+	{
+		params.Flags			|= D3DPRESENTFLAG_PROGRESSIVE;
+	}
+//	else if( video_flags & XC_VIDEO_FLAGS_HDTV_1080i )
+//	{
+//		params.Flags							|= D3DPRESENTFLAG_INTERLACED | D3DPRESENTFLAG_WIDESCREEN | D3DPRESENTFLAG_FIELD;
+//		params.BackBufferWidth					= 1920;
+//		params.BackBufferHeight					= 540;
+//		params.BackBufferFormat					= D3DFMT_LIN_R5G6B5;
+//		params.AutoDepthStencilFormat			= D3DFMT_D16;
+//		params.FullScreen_PresentationInterval	= D3DPRESENT_INTERVAL_TWO;
+//
+//		// Turn off FSAA.
+//		params.MultiSampleType	= D3DMULTISAMPLE_NONE;
+//
+//		EngineGlobals.screen_conv_x_multiplier	= 1920.0f / 704.0f;
+//		EngineGlobals.screen_conv_y_multiplier	= 1080.0f / 480.0f;
+//		EngineGlobals.screen_conv_x_offset		= 32;
+//		EngineGlobals.screen_conv_y_offset		= 16;
+//	}
+	else
+	{
+		params.Flags			|= D3DPRESENTFLAG_INTERLACED;
+	}
+	
+	if( params.BackBufferWidth == 640 )
+	{
+		params.Flags			|= D3DPRESENTFLAG_10X11PIXELASPECTRATIO;
+	}
+	
+	// The default push buffer size is 512k. Double this to reduce stalls from filling the push buffer.
+	Direct3D_SetPushBufferSize( 2 * 512 * 1024, 32 * 1024 );
+	
+	if( D3D_OK != Direct3D_CreateDevice(	D3DADAPTER_DEFAULT,
+											D3DDEVTYPE_HAL,
+											NULL,
+											D3DCREATE_HARDWARE_VERTEXPROCESSING,	// Note: may want to consider adding the PUREDEVICE flag here also.
+											¶ms,
+											&EngineGlobals.p_Device ))
+	{
+		// Failed to start up engine. Bad!
+		exit( 0 );
+	}
+
+	// Also create the render surface we will use when doing screen blur. (Creating this at 32bit depth also, since still
+	// takes up less memory than 720p buffers).
+	if( params.BackBufferWidth <= 640 )
+	{
+		D3DDevice_CreateRenderTarget( 640, 480, D3DFMT_LIN_X8R8G8B8, 0, 0, &EngineGlobals.p_BlurSurface[0] );
+		D3DDevice_CreateRenderTarget( 320, 240, D3DFMT_LIN_X8R8G8B8, 0, 0, &EngineGlobals.p_BlurSurface[1] );
+		D3DDevice_CreateRenderTarget( 160, 120, D3DFMT_LIN_X8R8G8B8, 0, 0, &EngineGlobals.p_BlurSurface[2] );
+		D3DDevice_CreateRenderTarget(  80,  60, D3DFMT_LIN_X8R8G8B8, 0, 0, &EngineGlobals.p_BlurSurface[3] );
+	}
+	
+	// Obtain pointers to the render and Z-stencil surfaces. Doing this increases their reference counts, so release
+	// them following the operation.
+	D3DDevice_GetRenderTarget( &EngineGlobals.p_RenderSurface );
+	D3DDevice_GetDepthStencilSurface( &EngineGlobals.p_ZStencilSurface );
+
+	LPDIRECT3DSURFACE8 pBackBuffer;
+    D3DDevice_GetBackBuffer( 0, 0, &pBackBuffer );
+	
+	// Get back buffer information.
+	EngineGlobals.backbuffer_width	= params.BackBufferWidth;
+	EngineGlobals.backbuffer_height	= params.BackBufferHeight;
+	EngineGlobals.backbuffer_format	= params.BackBufferFormat;
+	EngineGlobals.zstencil_depth	= ( params.AutoDepthStencilFormat == D3DFMT_D16 ) ? 16 : 32;
+	
+	// Get blur buffer information.
+	if( EngineGlobals.p_BlurSurface[0] )
+	{
+		D3DSURFACE_DESC blur_surface_desc;
+		EngineGlobals.p_BlurSurface[0]->GetDesc( &blur_surface_desc );
+		EngineGlobals.blurbuffer_format	= blur_surface_desc.Format;
+	}
+	
+	// Set our renderstate to a known state.
+	InitialiseRenderstates();
+
+	// Set default gamma values.
+	SetGammaNormalized( 0.14f, 0.13f, 0.12f );
+
+	// Initialise the memory resident font, used for fatal i/o error messages etc.
+	EngineGlobals.p_memory_resident_font = InitialiseMemoryResidentFont();
+
+	// Code to enable detailed timing. Need to link with d3d8i.lib.
+//	DmEnableGPUCounter( TRUE );
+//	D3DPERF_SetShowFrameRateInterval( 1000 );
+//	D3DPERF_GetStatistics()->m_dwDumpFPSInfoMask |= D3DPERF_DUMP_FPS_PERFPROFILE;
+
+	// Now that the D3DDevice is created, it's safe to install vsync handlers.
+	Tmr::InstallVSyncHandlers();
+
+	// Load up the bump textures.
+//	LoadBumpTextures();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void FatalFileError( uint32 error )
+{
+	static char*	p_error_message_english[2]	= {	"There's a problem with the disc you're using.",
+													"It may be dirty or damaged." };
+	static char*	p_error_message_french[2]	= {	"Le disque utilisé présente une anomalie.",
+													"Il est peut-être sale ou endommagé." };
+	static char*	p_error_message_german[2]	= {	"Bei der benutzten CD ist ein Problem aufgetreten.",
+													"Möglicherweise ist sie verschmutzt oder beschädigt." };
+
+	// Turn off the loading bar if it is active.
+	if( EngineGlobals.loadingbar_timer_event != 0 )
+	{
+		timeKillEvent( EngineGlobals.loadingbar_timer_event );
+		EngineGlobals.loadingbar_timer_event = 0;
+	}
+
+	// Ensure the graphics device has been initialised at this point.
+	if( EngineGlobals.p_Device == NULL )
+	{
+		InitialiseEngine();
+	}
+
+	// Wait for any rendering to complete.
+	EngineGlobals.p_Device->BlockUntilIdle();
+
+	char*	p_error_message[2];
+	switch( Config::GetLanguage())
+	{
+		case Config::LANGUAGE_FRENCH:
+		{
+			p_error_message[0] = p_error_message_french[0];
+			p_error_message[1] = p_error_message_french[1];
+			break;
+		}
+		case Config::LANGUAGE_GERMAN:
+		{
+			p_error_message[0] = p_error_message_german[0];
+			p_error_message[1] = p_error_message_german[1];
+			break;
+		}
+		default:
+		{
+			p_error_message[0] = p_error_message_english[0];
+			p_error_message[1] = p_error_message_english[1];
+			break;
+		}
+
+	}
+
+	// Set up the text string used for the error message.
+	SText error_text;
+	error_text.mp_font		= (SFont*)EngineGlobals.p_memory_resident_font;
+	error_text.m_xpos		= 48.0f;
+	error_text.m_ypos		= 128.0f;
+	error_text.m_xscale		= 0.8f;
+	error_text.m_yscale		= 1.0f;
+	error_text.m_rgba		= 0x80808080;
+	error_text.mp_next		= NULL;
+
+	set_texture( 1, NULL );
+	set_texture( 2, NULL );
+	set_texture( 3, NULL );
+
+	// Want an infinite loop here.
+	while( true )
+	{
+		D3DDevice_Swap( D3DSWAP_DEFAULT );
+
+		// Now that the swap instruction has been pushed, clear the buffer for next frame.
+		D3DDevice_Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0x00000000, 1.0f, 0 );
+
+		set_blend_mode( vBLEND_MODE_BLEND );
+		set_texture( 0, NULL );
+
+		set_render_state( RS_UVADDRESSMODE0,	0x00010001UL );
+		set_render_state( RS_ZBIAS,				0 );
+		set_render_state( RS_ALPHACUTOFF,		1 );
+		set_render_state( RS_ZWRITEENABLE,		0 );
+
+		D3DDevice_SetTextureStageState( 0, D3DTSS_COLORSIGN, D3DTSIGN_RUNSIGNED | D3DTSIGN_GUNSIGNED | D3DTSIGN_BUNSIGNED );
+		D3DDevice_SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );
+		D3DDevice_SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU | 0 );
+
+		error_text.mp_string	= p_error_message[0];
+		error_text.m_ypos		= error_text.m_ypos - 16.0f;
+		error_text.BeginDraw();
+		error_text.Draw();
+		error_text.EndDraw();
+
+		error_text.mp_string	= p_error_message[1];
+		error_text.m_ypos		= error_text.m_ypos + 16.0f;
+		error_text.BeginDraw();
+		error_text.Draw();
+		error_text.EndDraw();
+	}
+}
+
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/nx_init.h b/Code/Gfx/XBox/NX/nx_init.h
new file mode 100644
index 0000000..fefe78a
--- /dev/null
+++ b/Code/Gfx/XBox/NX/nx_init.h
@@ -0,0 +1,122 @@
+#ifndef __NX_INIT_H
+#define __NX_INIT_H
+
+#include 
+#include 
+
+namespace NxXbox
+{
+
+void InitialiseEngine( void );
+void FatalFileError( uint32 error );
+
+typedef struct
+{
+	XGMATRIX			world_matrix;
+	XGMATRIX			view_matrix;
+	XGMATRIX			projection_matrix;
+
+	XGMATRIX			bump_env_matrix;					// Used to set the D3DTSS_BUMPENVMATnn texture states where applicable.
+
+	D3DVIEWPORT8		viewport;
+	float				near_plane;
+	float				far_plane;
+	float				screen_angle;
+	float				near_plane_width;
+	float				near_plane_height;
+	bool				is_orthographic;
+	bool				clear_color_buffer;					// Whether the color buffer is cleared during buffer swap and clear process.
+	bool				letterbox_active;					// Whether running in 4:3 letterbox mode.
+	D3DCOLOR			clear_color;						// The color to which the color buffer is cleared.
+	XGVECTOR3			cam_position;
+	XGVECTOR3			model_relative_cam_position;		// Used in specular lighting calculations.
+	XGVECTOR3			cam_at;
+	XGVECTOR3			cam_up;
+	XGVECTOR3			cam_right;
+
+	int					render_start_time;					// Time (milliseconds) at which the current frame render started.
+
+	bool				loadingscreen_visible;
+	MMRESULT			loadingbar_timer_event;
+		
+	IDirect3DDevice8*	p_Device;
+	IDirect3DSurface8*	p_RenderSurface;
+	IDirect3DSurface8*	p_ZStencilSurface;
+	IDirect3DSurface8*	p_BlurSurface[4];
+
+	int					backbuffer_width;
+	int					backbuffer_height;
+	D3DFORMAT			backbuffer_format;
+	D3DFORMAT			blurbuffer_format;
+	int					zstencil_depth;
+	float				screen_conv_x_multiplier;
+	float				screen_conv_y_multiplier;
+	int					screen_conv_x_offset;
+	int					screen_conv_y_offset;
+	
+	void*				p_memory_resident_font;
+
+	char				screenshot_name[128];
+
+	// For bounding sphere culling calculations.
+	float				ViewFrustumTX;
+	float				ViewFrustumTY;
+	float				ViewFrustumSX;
+	float				ViewFrustumSY;
+	float				ViewFrustumCX;
+	float				ViewFrustumCY;
+
+	uint32				blend_mode_value;
+	uint32				blend_op;
+	uint32				src_blend;
+	uint32				dest_blend;
+
+	uint32				alpha_blend_enable;
+	uint32				alpha_test_enable;
+	uint32				alpha_ref;
+	uint32				specular_enabled;
+	bool				lighting_enabled;
+	bool				dither_enable;
+	bool				z_write_enabled;
+	bool				z_test_enabled;
+	uint32				z_bias;
+	int					cull_mode;
+	bool				allow_envmapping;				// Set to true (default) to allow costly environment mapping
+	uint32				uv_addressing[4];
+	uint32				mip_map_lod_bias[4];
+	uint32				min_mag_filter[4];
+	void				*p_texture[4];
+	DWORD				color_sign[4];
+	bool				custom_pipeline_enabled;		// A true value indicates that the fixed function pipeline is not being used.
+	DWORD				vertex_shader_id;
+	DWORD				pixel_shader_override;
+	DWORD				pixel_shader_id;
+	float				pixel_shader_constants[20];		// 4 floats per constant.
+														// c0 - c3	: material pass color (rgb) and fixed alpha (a) for relevant blend modes
+														// c4		: fog denisty (b), 0.5 (a)
+	bool				upload_pixel_shader_constants;
+	DWORD				vertex_shader_override;
+	DWORD				texture_stage_override;
+	DWORD				material_override;
+	DWORD				blend_mode_override;
+	float				ambient_light_color[4];			// In format ready to load to GPU.
+	float				directional_light_color[24];	// In format ready to load to GPU (dir0, col0, dir 1, col1, etc).
+	uint32				screen_blur;					// [0, 255] - [no blur, max blur]
+	uint32				screen_blur_duration;			// How many frames the screen blur has been active for.
+	uint32				focus_blur;						// [0, 255] - [no blur, max blur]
+	uint32				focus_blur_duration;			// How many frames the focus blur has been active for.
+
+	uint32				fog_enabled;
+	D3DCOLOR			fog_color;
+	float				fog_start;
+	float				fog_end;
+}
+sEngineGlobals;
+
+extern sEngineGlobals EngineGlobals;
+
+
+
+} // namespace NxXbox
+
+#endif // __NX_INIT_H
diff --git a/Code/Gfx/XBox/NX/occlude.cpp b/Code/Gfx/XBox/NX/occlude.cpp
new file mode 100644
index 0000000..5973139
--- /dev/null
+++ b/Code/Gfx/XBox/NX/occlude.cpp
@@ -0,0 +1,504 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		occlude.cpp												**
+**																			**
+**	Created:		03/18/02	-	dc										**
+**																			**
+**	Description:	Occlusion testing code									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+#include 
+#include 
+#include 
+#include 
+#include "occlude.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+//extern D3DXMATRIX *p_bbox_transform;
+extern XGMATRIX *p_bbox_transform;
+
+namespace NxXbox
+{
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+const uint32 MAX_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME = 4;
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// Structure used to store details of a single poly. A list of these will be built at geometry load time.
+struct sOcclusionPoly
+{
+	bool		in_use;		// Whether the poly is currently being used for occlusion.
+	bool		available;	// Whether the poly is available for selection for occlusion.
+	uint32		checksum;	// Name checksum of the occlusion poly.
+	Mth::Vector	verts[4];
+	Mth::Vector	normal;
+};
+	
+const uint32	MAX_OCCLUDERS				= 8;
+const uint32	MAX_VIEWS_PER_OCCLUDER		= 2;
+
+struct sOccluder
+{
+	static uint32		NumOccluders;
+	static sOccluder	Occluders[MAX_OCCLUDERS];
+
+	static void			add_to_stack( sOcclusionPoly *p_poly );
+	static void			sort_stack( void );
+	static void			tidy_stack( void );
+
+	sOcclusionPoly	*p_poly;
+	Mth::Vector		planes[5];
+	int				score[MAX_VIEWS_PER_OCCLUDER];	// Current rating on quality of occlusion - based on number of meshes occluded last frame.
+};
+
+const uint32	MAX_OCCLUSION_POLYS			= 512;
+uint32			NumOcclusionPolys			= 0;
+uint32			NextOcclusionPolyToCheck	= 0;
+int				CurrentView					= 0;
+sOcclusionPoly	OcclusionPolys[MAX_OCCLUSION_POLYS];
+
+uint32		sOccluder::NumOccluders				= 0;
+sOccluder	sOccluder::Occluders[MAX_OCCLUDERS];
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::add_to_stack( sOcclusionPoly *p_poly )
+{
+	if( NumOccluders < MAX_OCCLUDERS )
+	{
+		Dbg_Assert( p_poly->available );
+
+		Occluders[NumOccluders].p_poly	= p_poly;
+		p_poly->in_use					= true;
+
+		// Reset scores for all views.
+		memset( Occluders[NumOccluders].score, 0, sizeof( int ) * MAX_VIEWS_PER_OCCLUDER );
+
+		++NumOccluders;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int cmp( const void *p1, const void *p2 )
+{
+	// Sort based on the sum total of scores in all views.
+	int score1 = 0;
+	int score2 = 0;
+	for( int v = 0; v < MAX_VIEWS_PER_OCCLUDER; ++v )
+	{
+		// Zero the score for any occlusion poly that is no longer available. This will force it out of the stack.
+		if(((sOccluder*)p1)->p_poly->available == false )
+			((sOccluder*)p1)->score[v] = 0;
+
+		if(((sOccluder*)p2)->p_poly->available == false )
+			((sOccluder*)p2)->score[v] = 0;
+
+		score1 += ((sOccluder*)p1)->score[v];
+		score2 += ((sOccluder*)p2)->score[v];
+	}
+
+	return score1 < score2 ? 1 : score1 > score2 ? -1 : 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::sort_stack( void )
+{
+	qsort( Occluders, NumOccluders, sizeof( sOccluder ), cmp );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sOccluder::tidy_stack( void )
+{
+	if( NumOccluders > 0 )
+	{
+		// Sort in descending score order.
+		sort_stack();
+
+		// Count backwards so we know we get all the bad occluders.
+		for( int i = NumOccluders - 1; i >= 0; --i )
+		{
+			// If we have hit an occluder with zero meshes culled, cut off the stack at this point.
+			int total_score = 0;
+			for( int v = 0; v < MAX_VIEWS_PER_OCCLUDER; ++v )
+			{
+				total_score += Occluders[i].score[v];
+			}
+			
+			if( total_score == 0 )
+			{
+				// No longer using this poly.
+				Occluders[i].p_poly->in_use = false;
+
+				// One less occluder to worry about.
+				--NumOccluders;
+			}
+			else
+			{
+				// Reset the good occluders.
+				Occluders[i].score[CurrentView] = 0;
+			}
+		}
+	}
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to add an occlusion poly to the list. Likely to be called */
+/* as geometry is spooled in.                                     */
+/*                                                                */
+/******************************************************************/
+void AddOcclusionPoly( Mth::Vector &v0, Mth::Vector &v1, Mth::Vector &v2, Mth::Vector &v3, uint32 checksum )
+{
+	Dbg_Assert( NumOcclusionPolys < MAX_OCCLUSION_POLYS );	
+	
+	OcclusionPolys[NumOcclusionPolys].in_use	= false;
+	OcclusionPolys[NumOcclusionPolys].available	= true;
+	OcclusionPolys[NumOcclusionPolys].checksum	= checksum;
+	OcclusionPolys[NumOcclusionPolys].verts[0]	= v0;
+	OcclusionPolys[NumOcclusionPolys].verts[1]	= v1;
+	OcclusionPolys[NumOcclusionPolys].verts[2]	= v2;
+	OcclusionPolys[NumOcclusionPolys].verts[3]	= v3;
+	OcclusionPolys[NumOcclusionPolys].normal	= Mth::CrossProduct( v1 - v0, v3 - v0 );
+	OcclusionPolys[NumOcclusionPolys].normal.Normalize();
+	
+	++NumOcclusionPolys;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to toggle whether an occlusion poly can be used or not	  */
+/*                                                                */
+/******************************************************************/
+void EnableOcclusionPoly( uint32 checksum, bool available )
+{
+	for( uint32 i = 0; i < NumOcclusionPolys; ++i )
+	{
+		if( OcclusionPolys[i].checksum == checksum )
+		{
+			OcclusionPolys[i].available	= available;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Used to clear all occlusion polys (when a level is unloaded)   */
+/*                                                                */
+/******************************************************************/
+void RemoveAllOcclusionPolys( void )
+{
+	Dbg_Assert( NumOcclusionPolys < MAX_OCCLUSION_POLYS );	
+	
+ 	sOccluder::NumOccluders		= 0;
+	NumOcclusionPolys			= 0;
+	NextOcclusionPolyToCheck	= 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CheckForOptimalOccluders( Mth::Vector &cam_pos, Mth::Vector &view_direction )
+{
+	if( NumOcclusionPolys > 0 )
+	{
+		uint32 added	= 0;
+		uint32 checked	= 0;
+
+		while( added < MAX_NEW_OCCLUSION_POLYS_TO_CHECK_PER_FRAME )
+		{
+			// Given the current position of the camera, check through the unused, available occlusion polys to see if one scores higher
+			// than the lowest scoring occlusion poly in use.
+			sOcclusionPoly *poly_to_check = &OcclusionPolys[NextOcclusionPolyToCheck++];
+			if(( !poly_to_check->in_use ) && ( poly_to_check->available ))
+			{
+				sOccluder::add_to_stack( poly_to_check );
+				++added;
+			}
+			++checked;
+
+			// Ensure we are always checking within bounds.
+			if( NextOcclusionPolyToCheck >= NumOcclusionPolys )
+			{
+				NextOcclusionPolyToCheck = 0;
+			}
+
+			// Quit out if we have less available occluders than spaces to fill.
+			if( checked >= NumOcclusionPolys )
+			{
+				break;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void BuildOccluders( Mth::Vector *p_cam_pos, int view )
+{
+//	for( uint32 i = 0; i < NumOcclusionPolys; ++i )
+//	{
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[0], OcclusionPolys[i].verts[1], 0, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[1], OcclusionPolys[i].verts[2], 0, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[2], OcclusionPolys[i].verts[3], 0, 0, 1 );
+//		Gfx::AddDebugLine( OcclusionPolys[i].verts[3], OcclusionPolys[i].verts[0], 0, 0, 1 );
+//	}
+	
+	// Set the current view - this will remain active until another call to this function.
+	Dbg_Assert( view < MAX_VIEWS_PER_OCCLUDER );
+	CurrentView	= view;
+	
+	// Tidy up from last frame.
+	sOccluder::tidy_stack();
+	
+	// Cyclically add more occluders for checking.
+	CheckForOptimalOccluders( *p_cam_pos, *p_cam_pos );
+	
+	// Build all 5 planes for each occluder.
+	Mth::Vector u0, u1, p;
+
+	// The order in which the verts are used to build tha planes depends upon where the camera is in relation to the occlusion
+	// poly. We use the default order when the viewpoint is on the side of the poly on which the default poly normal faces.
+	for( uint32 i = 0; i < sOccluder::NumOccluders; ++i )
+	{
+		sOcclusionPoly *p_poly = sOccluder::Occluders[i].p_poly;
+
+//		Gfx::AddDebugLine( p_poly->verts[0], p_poly->verts[1], 0, 0, 1 );
+//		Gfx::AddDebugLine( p_poly->verts[1], p_poly->verts[2], 0, 0, 1 );
+//		Gfx::AddDebugLine( p_poly->verts[2], p_poly->verts[3], 0, 0, 1 );
+//		Gfx::AddDebugLine( p_poly->verts[3], p_poly->verts[0], 0, 0, 1 );
+		
+		if( Mth::DotProduct( *p_cam_pos - p_poly->verts[0], p_poly->normal ) >= 0.0f )
+		{
+			// Start with the front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
+			// are considered occluded. (1->3->4)...
+			u0						= p_poly->verts[2] - p_poly->verts[0];
+			u1						= p_poly->verts[3] - p_poly->verts[0];
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, p_poly->verts[0] );
+			sOccluder::Occluders[i].planes[0]	= p;
+
+			// ...then left (0->1->2)...
+			u0						= p_poly->verts[0] - *p_cam_pos;
+			u1						= p_poly->verts[1] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[1]	= p;
+
+			// ...then right (0->3->4)...
+			u0						= p_poly->verts[2] - *p_cam_pos;
+			u1						= p_poly->verts[3] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[2]	= p;
+
+			// ...then top (0->2->3)...
+			u0						= p_poly->verts[1] - *p_cam_pos;
+			u1						= p_poly->verts[2] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[3]	= p;
+
+			// ...then bottom (0->4->1)...
+			u0						= p_poly->verts[3] - *p_cam_pos;
+			u1						= p_poly->verts[0] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[4]	= p;
+		}
+		else
+		{
+			// Start with the front. We want to reverse the order of the front plane to ensure that objects *behind* the plane
+			// are considered occluded. (1->4->3)...
+			u0						= p_poly->verts[3] - p_poly->verts[0];
+			u1						= p_poly->verts[2] - p_poly->verts[0];
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, p_poly->verts[0] );
+			sOccluder::Occluders[i].planes[0]	= p;
+
+			// ...then left (0->2->1)...
+			u0						= p_poly->verts[1] - *p_cam_pos;
+			u1						= p_poly->verts[0] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[1]	= p;
+
+			// ...then right (0->4->3)...
+			u0						= p_poly->verts[3] - *p_cam_pos;
+			u1						= p_poly->verts[2] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[2]	= p;
+
+			// ...then top (0->3->2)...
+			u0						= p_poly->verts[2] - *p_cam_pos;
+			u1						= p_poly->verts[1] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[3]	= p;
+
+			// ...then bottom (0->1->4)...
+			u0						= p_poly->verts[0] - *p_cam_pos;
+			u1						= p_poly->verts[3] - *p_cam_pos;
+			p						= Mth::CrossProduct( u0, u1 );
+			p.Normalize();
+			p[W]					= Mth::DotProduct( p, *p_cam_pos );
+			sOccluder::Occluders[i].planes[4]	= p;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool TestSphereAgainstOccluders( D3DXVECTOR3 *p_center, float radius, uint32 meshes )
+{
+	XGVECTOR3 center;
+
+	// Build the composite transform if required.
+	if( p_bbox_transform )
+	{
+		// Object to world, so transform the sphere center.
+		center.x = p_center->x + p_bbox_transform->_41;
+		center.y = p_center->y + p_bbox_transform->_42;
+		center.z = p_center->z + p_bbox_transform->_43;
+	}
+	else
+	{
+		center.x = p_center->x;
+		center.y = p_center->y;
+		center.z = p_center->z;
+	}
+
+	// Test against each occluder.
+	for( uint32 o = 0; o < sOccluder::NumOccluders; ++o )
+	{
+		bool occluded = true;
+
+		// Test against each plane in the occluder.
+		for( uint32 p = 0; p < 5; ++p )
+		{
+			float result =	( sOccluder::Occluders[o].planes[p][X] * center.x ) +
+							( sOccluder::Occluders[o].planes[p][Y] * center.y ) +
+							( sOccluder::Occluders[o].planes[p][Z] * center.z ) -
+							( sOccluder::Occluders[o].planes[p][W] );
+			if( result >= -radius )
+			{
+				// Outside of this plane, therefore not occluded by this occluder.
+				occluded = false;
+				break;
+			}
+		}
+
+		if( occluded )
+		{
+			// Inside all planes, therefore occluded. Increase score for this occluder.
+			sOccluder::Occluders[o].score[CurrentView] += meshes;
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace NxXbox
diff --git a/Code/Gfx/XBox/NX/occlude.h b/Code/Gfx/XBox/NX/occlude.h
new file mode 100644
index 0000000..864e8a4
--- /dev/null
+++ b/Code/Gfx/XBox/NX/occlude.h
@@ -0,0 +1,14 @@
+#ifndef	__OCCLUDE_H__
+#define	__OCCLUDE_H__
+
+namespace NxXbox
+{
+	void AddOcclusionPoly( Mth::Vector &v0, Mth::Vector &v1, Mth::Vector &v2, Mth::Vector &v3, uint32 checksum = 0 );
+	void EnableOcclusionPoly( uint32 checksum, bool available );
+	void RemoveAllOcclusionPolys( void );
+	void BuildOccluders( Mth::Vector *p_cam_pos, int view );
+	bool TestSphereAgainstOccluders( D3DXVECTOR3 *p_center, float radius, uint32 meshes = 1 );
+}
+
+#endif
+
diff --git a/Code/Gfx/XBox/NX/particles.cpp b/Code/Gfx/XBox/NX/particles.cpp
new file mode 100644
index 0000000..a99d724
--- /dev/null
+++ b/Code/Gfx/XBox/NX/particles.cpp
@@ -0,0 +1,122 @@
+
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		particle.cpp											**
+**																			**
+**	Created:		03/27/02	-	dc										**
+**																			**
+**	Description:	Low level particle rendering code						**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+#include 
+#include 
+#include 
+#include 
+#include "render.h"
+#include "particles.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace NxXbox
+{
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sParticleSystem::sParticleSystem( uint32 max_particles, eParticleType type, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, int history )
+{
+	// Obtain the texture.
+	Nx::CTexture		* p_texture			= Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( texture_checksum );
+	Nx::CXboxTexture	*p_xbox_texture		= static_cast( p_texture );
+	NxXbox::sTexture	*p_engine_texture	= NULL;
+	if( p_xbox_texture )
+	{
+		p_engine_texture = p_xbox_texture->GetEngineTexture(); 
+	}
+	
+	// Create a (semi-transparent) material used to render the mesh.
+	sMaterial *p_material			= new sMaterial();
+	p_material->m_flags[0]			= 0x40 | (( p_engine_texture ) ? MATFLAG_TEXTURED : 0 );
+	p_material->m_checksum			= (uint32)rand() * (uint32)rand();
+	p_material->m_passes			= 1;
+	p_material->mp_tex[0]			= p_engine_texture;
+	p_material->m_reg_alpha[0]		= GetBlendMode( blendmode_checksum ) | ((uint32)fix << 24 );
+	p_material->m_color[0][0]		= 0.5f;
+	p_material->m_color[0][1]		= 0.5f;
+	p_material->m_color[0][2]		= 0.5f;
+	p_material->m_color[0][3]		= fix * ( 1.0f /  128.0f );
+
+	p_material->m_uv_addressing[0]	= 0;
+	p_material->m_k[0]				= 0.0f;
+	p_material->m_alpha_cutoff		= 1;
+	p_material->m_no_bfc			= true;
+	p_material->m_uv_wibble			= false;
+
+	mp_material						= p_material;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sParticleSystem::~sParticleSystem( void )
+{
+	delete mp_material;
+}
+
+} // namespace NxXbox
diff --git a/Code/Gfx/XBox/NX/particles.h b/Code/Gfx/XBox/NX/particles.h
new file mode 100644
index 0000000..b7c8bdd
--- /dev/null
+++ b/Code/Gfx/XBox/NX/particles.h
@@ -0,0 +1,38 @@
+
+#ifndef	__PARTICLE_H__
+#define	__PARTICLE_H__
+
+#include "gfx\xbox\nx\scene.h"
+
+namespace NxXbox
+{
+
+enum eParticleType
+{
+	PARTICLE_TYPE_LINE,
+	PARTICLE_TYPE_FLAT,
+	PARTICLE_TYPE_SHADED,
+	PARTICLE_TYPE_SMOOTH,
+	PARTICLE_TYPE_GLOW,
+	PARTICLE_TYPE_STAR,
+	PARTICLE_TYPE_SMOOTHSTAR,
+	PARTICLE_TYPE_RIBBON,
+	PARTICLE_TYPE_SMOOTHRIBBON,
+	PARTICLE_TYPE_RIBBONTRAIL,
+	PARTICLE_TYPE_GLOWRIBBONTRAIL,
+};
+
+
+struct sParticleSystem
+{
+public:
+				sParticleSystem( uint32 max_particles, eParticleType type, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments = 0, int history = 0 );
+				~sParticleSystem( void );
+
+	sMaterial	*mp_material;
+};
+
+}
+
+#endif
+
diff --git a/Code/Gfx/XBox/NX/render.cpp b/Code/Gfx/XBox/NX/render.cpp
new file mode 100644
index 0000000..84da9f7
--- /dev/null
+++ b/Code/Gfx/XBox/NX/render.cpp
@@ -0,0 +1,2955 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "scene.h"
+#include "render.h"
+#include "instance.h"
+#include "occlude.h"
+#include "billboard.h"
+
+#include "PixelShader0.h"
+#include "PixelShader0IVA.h"
+#include "PixelShader1.h"
+#include "PixelShader2.h"
+#include "PixelShader3.h"
+#include "PixelShader4.h"
+#include "PixelShader5.h"
+#include "PixelShaderBrighten.h"
+#include "PixelShaderBrightenIVA.h"
+#include "PixelShaderFocusBlur.h"
+#include "PixelShaderFocusIntegrate.h"
+#include "PixelShaderFocusLookupIntegrate.h"
+#include "PixelShaderNULL.h"
+#include "PixelShaderPointSprite.h"
+#include "PixelShader_ShadowBuffer.h"
+#include "PixelShaderBumpWater.h"
+#include "ShadowBufferStaticGeomPS.h"
+
+DWORD PixelShader0;
+DWORD PixelShader0IVA;
+DWORD PixelShader1;
+DWORD PixelShader2;
+DWORD PixelShader3;
+DWORD PixelShader4;
+DWORD PixelShader5;
+DWORD PixelShaderBrighten;
+DWORD PixelShaderBrightenIVA;
+DWORD PixelShaderFocusBlur;
+DWORD PixelShaderFocusIntegrate;
+DWORD PixelShaderFocusLookupIntegrate;
+DWORD PixelShaderNULL;
+DWORD PixelShaderPointSprite;
+DWORD PixelShader_ShadowBuffer;
+DWORD PixelShaderBumpWater;
+DWORD ShadowBufferStaticGeomPS;
+
+//D3DXMATRIX *p_bbox_transform = NULL;
+//D3DXMATRIX bbox_transform;
+XGMATRIX	*p_bbox_transform = NULL;
+XGMATRIX	bbox_transform;
+
+extern DWORD ShadowBufferStaticGeomVS;
+
+namespace NxXbox
+{
+
+const float FRONT_TO_BACK_SORT_CUTOFF	= ( 50.0f * 12.0f );
+
+Lst::HashTable< sTextureProjectionDetails >	*pTextureProjectionDetailsTable = NULL;
+
+// For converting a FLOAT to a DWORD (useful for SetRenderState() calls)
+inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }
+
+static Lst::HashTable sPixelShaderTable( 8 );
+
+
+static const int MAX_FREE_TESTS					= 1024;
+static bool		visibilityTestValuesInitialised = false;
+static uint8	visibilityTestValues[MAX_FREE_TESTS];
+
+struct sVisibilityTestFIFO
+{
+	static const int	MAX_FIFO_SIZE	= 4;
+
+	uint32				m_status_fifo[MAX_FIFO_SIZE];
+	uint32				m_fifo_index;
+
+						sVisibilityTestFIFO();
+						~sVisibilityTestFIFO();
+
+	static uint32		GetFreeTestIndex( void );
+	uint32				AddStatus( void );
+	uint32				GetStatus( void );
+
+
+	private:
+
+	void				SlideQueue( void );
+	uint32				m_last_valid_status;
+};
+
+
+struct sLightGlowDetails
+{
+	Mth::Vector			m_pos;
+	float				m_glow_radius;
+	float				m_current_radius;
+	float				m_test_radius;
+	float				m_radius_growth;
+	sVisibilityTestFIFO	m_visibility_test_fifo;
+};
+
+
+static Lst::HashTable sLightGlowDetailsTable( 8 );
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+BlendModes GetBlendMode( uint32 blend_checksum )
+{
+	BlendModes rv = NxXbox::vBLEND_MODE_DIFFUSE;
+
+	switch ( blend_checksum )
+	{
+		case 0x54628ed7:		// Blend
+			rv = NxXbox::vBLEND_MODE_BLEND;
+			break;
+		case 0x02e58c18:		// Add
+			rv = NxXbox::vBLEND_MODE_ADD;
+			break;
+		case 0xa7fd7d23:		// Sub
+		case 0xdea7e576:		// Subtract
+			rv = NxXbox::vBLEND_MODE_SUBTRACT;
+			break;
+		case 0x40f44b8a:		// Modulate
+			rv = NxXbox::vBLEND_MODE_MODULATE;
+			break;
+		case 0x68e77f40:		// Brighten
+			rv = NxXbox::vBLEND_MODE_BRIGHTEN;
+			break;
+		case 0x18b98905:		// FixBlend
+			rv = NxXbox::vBLEND_MODE_BLEND_FIXED;
+			break;
+		case 0xa86285a1:		// FixAdd
+			rv = NxXbox::vBLEND_MODE_ADD_FIXED;
+			break;
+		case 0x0d7a749a:		// FixSub
+		case 0x0eea99ff:		// FixSubtract
+			rv = NxXbox::vBLEND_MODE_SUB_FIXED;
+			break;
+		case 0x90b93703:		// FixModulate
+			rv = NxXbox::vBLEND_MODE_MODULATE_FIXED;
+			break;
+		case 0xb8aa03c9:		// FixBrighten
+			rv = NxXbox::vBLEND_MODE_BRIGHTEN_FIXED;
+			break;
+		case 0x515e298e:		// Diffuse
+		case 0x806fff30:		// None
+			rv = NxXbox::vBLEND_MODE_DIFFUSE;
+			break;
+		default:
+			Dbg_MsgAssert(0,("Illegal blend mode specified. Please use (fix)blend/add/sub/modulate/brighten or diffuse/none."));
+			break;
+	}
+	return rv;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVisibilityTestFIFO::sVisibilityTestFIFO()
+{
+	m_fifo_index		= 0;
+	m_last_valid_status	= 0;
+
+	if( !visibilityTestValuesInitialised )
+	{
+		ZeroMemory( visibilityTestValues, sizeof( uint8 ) * MAX_FREE_TESTS );
+
+		// The first index is always reserved.
+		visibilityTestValues[0] = 1;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVisibilityTestFIFO::~sVisibilityTestFIFO()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVisibilityTestFIFO::SlideQueue( void )
+{
+	Dbg_Assert( m_fifo_index > 0 );
+
+	m_status_fifo[0] = m_status_fifo[1];
+	m_status_fifo[1] = m_status_fifo[2];
+	m_status_fifo[2] = m_status_fifo[3];
+	m_status_fifo[3] = 0;
+
+	--m_fifo_index;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 sVisibilityTestFIFO::GetFreeTestIndex( void )
+{
+	for( uint32 i = 0; i < MAX_FREE_TESTS; ++i )
+	{
+		if( visibilityTestValues[i] == 0 )
+		{
+			visibilityTestValues[i] = 1;
+			return i;
+		}
+	}
+	Dbg_Assert( 0 );
+	return MAX_FREE_TESTS - 1;
+}
+	
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 sVisibilityTestFIFO::AddStatus( void )
+{
+	// If the queue is already full, do nothing.
+	if( m_fifo_index >= ( MAX_FIFO_SIZE - 1 ))
+	{
+		return 0;
+	}
+
+	// Get a free index.
+	uint32 index = GetFreeTestIndex();
+	m_status_fifo[m_fifo_index++] = index;
+
+	return index;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 sVisibilityTestFIFO::GetStatus( void )
+{
+	Dbg_Assert( m_fifo_index > 0 );
+
+	UINT result;
+
+	// Get the result from the least recently issued test.
+	HRESULT hr = D3DDevice_GetVisibilityTestResult( m_status_fifo[0], &result, NULL );
+
+	if( hr == D3D_OK )
+	{
+		// Finished with this test. First mark this test index as no longer in use.
+		visibilityTestValues[m_status_fifo[0]] = 0;
+
+		// Remove entry from the queue.
+		SlideQueue();
+
+		m_last_valid_status = (uint32)result;
+		return m_last_valid_status;
+	}
+	else if( hr == D3DERR_TESTINCOMPLETE )
+	{
+		// Use the last valid status.
+		return m_last_valid_status;
+	}
+
+	return 0;
+}
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+DWORD get_pixel_shader( sMaterial *p_material )
+{
+	const  int	PIXEL_SHADER_BUFFER_SIZE	= 64 * 1024;
+	static char pixel_shader_buffer[PIXEL_SHADER_BUFFER_SIZE];
+	static int	pixel_shader_buffer_offset = 0;
+	uint32		blend_modes[4];
+
+	bool		mcm = true;
+//	bool		mcm = false;
+	for( uint32 p = 0; p < p_material->m_passes; ++p )
+	{
+		if( !mcm )
+		{
+			if(( p_material->m_color[p][0] != 0.5f ) || ( p_material->m_color[p][1] != 0.5f ) || ( p_material->m_color[p][2] != 0.5f ))
+				mcm = true;
+		}
+		blend_modes[p] = p_material->m_reg_alpha[p] & sMaterial::BLEND_MODE_MASK;
+	}
+
+	if( p_material->m_passes > 1 )
+	{
+		// First we build the unique key from the properties of the required shader, to see if it already exists.
+		uint32 code = p_material->m_passes;
+		for( uint32 p = 0; p < p_material->m_passes; ++p )
+		{
+			code |= ( blend_modes[p] << ( 5 * ( p + 1 )));
+		}
+
+		// Integrate the ignore vertex alpha flags.
+		uint32 ignore_bf = p_material->GetIgnoreVertexAlphaPasses();
+		code |= ( ignore_bf << 25 );
+
+		// Check also to see if material color modulation is required.
+		if( mcm )
+		{
+			code |= ( 1 << 30 );
+		}
+		
+		// Check to see if the shader exists, if so just return the shader handle.
+		DWORD *p_shader = sPixelShaderTable.GetItem( code );
+		if( p_shader )
+		{
+			return (DWORD)p_shader;
+		}
+
+		// We need to build the shader.
+		char shader_buffer[1024];
+
+		sprintf( shader_buffer, "xps.1.1\n" );
+
+		strcat( shader_buffer, "tex t0\n" );
+		strcat( shader_buffer, "tex t1\n" );
+
+		switch( p_material->m_passes )
+		{
+			case 2:
+			{
+				Dbg_Assert( ignore_bf <= 0x03 );
+
+				if( mcm )
+				{
+					// Modulate texture0 and texture 1 color with pass 0 and pass 1 material color, and place into t0.rgb and t1.rgb.
+					strcat( shader_buffer, "xmma		t0.rgb,t1.rgb,discard.rgb,t0.rgb,c0.rgb,t1.rgb,c1.rgb\n" );
+					
+					// Modulate result color with vertex color.
+					strcat( shader_buffer, "xmma_x4		r0.rgb,r1.rgb,discard.rgb,t0.rgb,v0.rgb,t1.rgb,v0.rgb\n" );
+
+					// Modulate result alpha with vertex alpha (or constant alpha = 0.5 for those passes that ignore alpha).
+					if( ignore_bf == 0x00 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,v0.a\n" );
+					}
+					else if( ignore_bf == 0x01 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,v0.a\n" );
+					}
+					else if( ignore_bf == 0x02 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,c4.a\n" );
+					}
+					else if( ignore_bf == 0x03 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,c4.a\n" );
+					}
+				}
+				else
+				{
+					// Modulate texture0 and texture1 color with vertex color.
+					strcat( shader_buffer, "xmma_x2		r0.rgb,r1.rgb,discard.rgb,t0.rgb,v0.rgb,t1.rgb,v0.rgb\n" );
+
+					// Modulate texture0 and tetxure1 alpha with vertex alpha (or constant alpha = 0.5 for those passes that ignore alpha).
+					if( ignore_bf == 0x00 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,v0.a\n" );
+					}
+					else if( ignore_bf == 0x01 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,v0.a\n" );
+					}
+					else if( ignore_bf == 0x02 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,c4.a\n" );
+					}
+					else if( ignore_bf == 0x03 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,c4.a\n" );
+					}
+				}
+
+				// Then deal with the second pass blend.
+				switch( blend_modes[1] )
+				{
+					case vBLEND_MODE_ADD:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_ADD_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUBTRACT:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,-r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUB_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,-c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r1.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_FIXED:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,c1.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,r1.a\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE_FIXED:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,c1.a\n" );
+					}
+					case vBLEND_MODE_BRIGHTEN:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BRIGHTEN_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r0.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r0.a,r0.rgb,r1.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_GLOSS_MAP:
+					{
+						strcat( shader_buffer, "mul v1.rgb,v1.rgb,t1.a\n" );
+						break;
+					}
+				}
+				break;
+			}
+
+			case 3:
+			{
+				Dbg_Assert( ignore_bf <= 0x07 );
+
+				strcat( shader_buffer, "tex t2\n" );
+
+				if( mcm )
+				{
+					// Modulate texture0 and texture 1 color with pass 0 and pass 1 material color, and place into t0.rgb and t1.rgb.
+					strcat( shader_buffer, "xmma		t0.rgb,t1.rgb,discard.rgb,t0.rgb,c0.rgb,t1.rgb,c1.rgb\n" );
+					
+					// Modulate result color with vertex color.
+					strcat( shader_buffer, "xmma_x4		r0.rgb,r1.rgb,discard.rgb,t0.rgb,v0.rgb,t1.rgb,v0.rgb\n" );
+
+					// Modulate result alpha with vertex alpha (or constant alpha = 0.5 for those passes that ignore alpha).
+					if(( ignore_bf & 0x03 ) == 0x00 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,v0.a\n" );
+					}
+					else if(( ignore_bf & 0x03 ) == 0x01 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,v0.a\n" );
+					}
+					else if(( ignore_bf & 0x03 ) == 0x02 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,c4.a\n" );
+					}
+					else if(( ignore_bf & 0x03 ) == 0x03 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,c4.a\n" );
+					}
+
+					// Modulate texture2 with pass 2 material color.
+					strcat( shader_buffer, "mul			t2.rgb,t2.rgb,c2.rgb\n" );
+
+					// Modulate result color with vertex color.
+					strcat( shader_buffer, "mul_x4		t2.rgb,t2.rgb,v0.rgb\n" );
+
+					// Modulate result alpha with vertex alpha (or constant alpha = 0.5 for those passes that ignore alpha).
+					if(( ignore_bf & 0x04 ) == 0x00 )
+					{
+						// Pass2 modulates with vertex alpha.
+						strcat( shader_buffer, "+mul_x2		t2.a,t2.a,v0.a\n" );
+					}
+					else if(( ignore_bf & 0x04 ) == 0x04 )
+					{
+						// Pass2 modulates with constant alpha.
+						strcat( shader_buffer, "+mul_x2		t2.a,t2.a,c4.a\n" );
+					}
+				}
+				else
+				{
+					// Modulate texture0 and texture1 with vertex color.
+					strcat( shader_buffer, "xmma_x2	r0,r1,discard,t0,v0,t1,v0\n" );
+
+					// Modulate texture2 with vertex color.
+					strcat( shader_buffer, "mul_x2	t2,t2,v0\n" );
+				}
+
+				// Then deal with the second pass blend.
+				switch( blend_modes[1] )
+				{
+					case vBLEND_MODE_ADD:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_ADD_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUBTRACT:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,-r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUB_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,-c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r1.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_FIXED:
+					{
+						strcat( shader_buffer, "lrp	r0.rgb,c1.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,r1.a\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE_FIXED:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,c1.a\n" );
+					}
+					case vBLEND_MODE_BRIGHTEN:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BRIGHTEN_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r0.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r0.a,r0.rgb,r1.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_GLOSS_MAP:
+					{
+						strcat( shader_buffer, "mul v1.rgb,v1.rgb,t1.a\n" );
+						break;
+					}
+				}
+				
+				// Then deal with the third pass blend.
+				switch( blend_modes[2] )
+				{
+					case vBLEND_MODE_ADD:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,t2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_ADD_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,c2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUBTRACT:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,-t2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUB_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,-c2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,t2.a,t2.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_FIXED:
+					{
+						strcat( shader_buffer, "lrp	r0.rgb,c2.a,t2.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,t2.a\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE_FIXED:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,c2.a\n" );
+					}
+					case vBLEND_MODE_BRIGHTEN:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,t2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BRIGHTEN_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,c2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r1.a,t2.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r1.a,r0.rgb,t2.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_GLOSS_MAP:
+					{
+						strcat( shader_buffer, "mul v1.rgb,v1.rgb,t2.a\n" );
+						break;
+					}
+				}
+
+				break;
+			}
+		
+			case 4:
+			{
+				Dbg_Assert( ignore_bf <= 0x0F );
+
+				strcat( shader_buffer, "tex t2\n" );
+				strcat( shader_buffer, "tex t3\n" );
+
+				if( mcm )
+				{
+					// Modulate texture0 and texture 1 color with pass 0 and pass 1 material color, and place into t0.rgb and t1.rgb.
+					strcat( shader_buffer, "xmma		t0.rgb,t1.rgb,discard.rgb,t0.rgb,c0.rgb,t1.rgb,c1.rgb\n" );
+					
+					// Modulate result color with vertex color.
+					strcat( shader_buffer, "xmma_x4		r0.rgb,r1.rgb,discard.rgb,t0.rgb,v0.rgb,t1.rgb,v0.rgb\n" );
+
+					// Modulate result alpha with vertex alpha (or constant alpha = 0.5 for those passes that ignore alpha).
+					if(( ignore_bf & 0x03 ) == 0x00 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,v0.a\n" );
+					}
+					else if(( ignore_bf & 0x03 ) == 0x01 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,v0.a\n" );
+					}
+					else if(( ignore_bf & 0x03 ) == 0x02 )
+					{
+						// Pass0 modulates with vertex alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,v0.a,t1.a,c4.a\n" );
+					}
+					else if(( ignore_bf & 0x03 ) == 0x03 )
+					{
+						// Pass0 modulates with constant alpha. Pass1 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	r0.a,r1.a,discard.a,t0.a,c4.a,t1.a,c4.a\n" );
+					}
+
+					// Modulate texture2 and texture 3 color with pass 2 and pass 3 material color, and place into t0.rgb and t1.rgb.
+					strcat( shader_buffer, "xmma		t2.rgb,t3.rgb,discard.rgb,t2.rgb,c2.rgb,t3.rgb,c3.rgb\n" );
+					
+					// Modulate result color with vertex color.
+					strcat( shader_buffer, "xmma_x4		t2.rgb,t3.rgb,discard.rgb,t2.rgb,v0.rgb,t3.rgb,v0.rgb\n" );
+
+					// Modulate result alpha with vertex alpha (or constant alpha = 0.5 for those passes that ignore alpha).
+					if(( ignore_bf & 0x0C ) == 0x00 )
+					{
+						// Pass2 modulates with vertex alpha. Pass3 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	t2.a,t3.a,discard.a,t2.a,v0.a,t3.a,v0.a\n" );
+					}
+					else if(( ignore_bf & 0x0C ) == 0x04 )
+					{
+						// Pass2 modulates with constant alpha. Pass3 modulates with vertex alpha.
+						strcat( shader_buffer, "+xmma_x2	t2.a,t3.a,discard.a,t2.a,c4.a,t3.a,v0.a\n" );
+					}
+					else if(( ignore_bf & 0x0C ) == 0x08 )
+					{
+						// Pass2 modulates with vertex alpha. Pass3 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	t2.a,t3.a,discard.a,t2.a,v0.a,t3.a,c4.a\n" );
+					}
+					else if(( ignore_bf & 0x0C ) == 0x0C )
+					{
+						// Pass2 modulates with constant alpha. Pass3 modulates with constant alpha.
+						strcat( shader_buffer, "+xmma_x2	t2.a,t3.a,discard.a,t2.a,c4.a,t3.a,c4.a\n" );
+					}
+				}
+				else
+				{
+					// Modulate texture0 and texture1 with vertex color.
+					strcat( shader_buffer, "xmma_x2		r0,r1,discard,t0,v0,t1,v0\n" );
+
+					// Modulate texture2 and texture3 with vertex color.
+					strcat( shader_buffer, "xmma_x2		t2,t3,discard,t2,v0,t3,v0\n" );
+				}
+				
+				// Then deal with the second pass blend.
+				switch( blend_modes[1] )
+				{
+					case vBLEND_MODE_ADD:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_ADD_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUBTRACT:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,-r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUB_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r1.rgb,-c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r1.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_FIXED:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,c1.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,r1.a\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE_FIXED:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,c1.a\n" );
+					}
+					case vBLEND_MODE_BRIGHTEN:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,r1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BRIGHTEN_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,c1.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r0.a,r1.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r0.a,r0.rgb,r1.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_GLOSS_MAP:
+					{
+						strcat( shader_buffer, "mul v1.rgb,v1.rgb,t1.a\n" );
+						break;
+					}
+				}
+
+				// Then deal with the third pass blend.
+				switch( blend_modes[2] )
+				{
+					case vBLEND_MODE_ADD:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,t2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_ADD_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,c2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUBTRACT:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,-t2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUB_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t2.rgb,-c2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,t2.a,t2.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_FIXED:
+					{
+						strcat( shader_buffer, "lrp	r0.rgb,c2.a,t2.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,t2.a\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE_FIXED:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,c2.a\n" );
+					}
+					case vBLEND_MODE_BRIGHTEN:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,t2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BRIGHTEN_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,c2.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r1.a,t2.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,r1.a,r0.rgb,t2.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_GLOSS_MAP:
+					{
+						strcat( shader_buffer, "mul v1.rgb,v1.rgb,t2.a\n" );
+						break;
+					}
+				}
+				
+				// Then deal with the fourth pass blend.
+				switch( blend_modes[3] )
+				{
+					case vBLEND_MODE_ADD:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t3.rgb,t3.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_ADD_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t3.rgb,c3.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUBTRACT:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t3.rgb,-t3.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_SUB_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,t3.rgb,-c3.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,t3.a,t3.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_FIXED:
+					{
+						strcat( shader_buffer, "lrp	r0.rgb,c3.a,t3.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,t3.a\n" );
+						break;
+					}
+					case vBLEND_MODE_MODULATE_FIXED:
+					{
+						strcat( shader_buffer, "mul r0.rgb,r0.rgb,c3.a\n" );
+					}
+					case vBLEND_MODE_BRIGHTEN:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,t3.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BRIGHTEN_FIXED:
+					{
+						strcat( shader_buffer, "mad r0.rgb,r0.rgb,c3.a,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,t2.a,t3.rgb,r0.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+					{
+						strcat( shader_buffer, "lrp r0.rgb,t2.a,r0.rgb,t3.rgb\n" );
+						break;
+					}
+					case vBLEND_MODE_GLOSS_MAP:
+					{
+						strcat( shader_buffer, "mul v1.rgb,v1.rgb,t3.a\n" );
+						break;
+					}
+				}
+				break;
+			}
+		}
+
+		// Final combiner.
+		strcat( shader_buffer, "xfc prod, fog.rgb, sum, zero, 1 - fog.a, c4, r0.a\n" );
+
+		LPXGBUFFER pCompiledShader;
+		HRESULT hr = XGAssembleShader(	"autogen.ps",									// Source file name, used in error messages.
+										shader_buffer,									// A pointer to the source data.
+										strlen( shader_buffer ),						// The source data length.
+										SASM_SKIPPREPROCESSOR | SASM_SKIPVALIDATION,	// SASM_xxx flags. See xgraphics.h for a complete list.
+										NULL,											// If constants are declared in the shader, they are written here. Pass NULL if you don't care.
+										&pCompiledShader,								// The shader microcode is written here. Pass NULL if you don't care.
+										NULL,											// Errors are written here. Pass NULL if you don't care.
+										NULL,											// A human-readable listing is written here. Pass NULL if you don't want it.
+										NULL,											// Used by the preprocessor. Can be NULL if you don't use #include in your source file.
+										NULL,											// Passed unmodified to the pResolver function.
+										NULL );											// Returns the type of shader that was assembled. Pass NULL if you don't care.
+		Dbg_Assert( hr == S_OK );
+		
+		// Copy the microcode into our buffer.
+		Dbg_Assert( pixel_shader_buffer_offset + pCompiledShader->size < PIXEL_SHADER_BUFFER_SIZE );
+		CopyMemory( pixel_shader_buffer + pixel_shader_buffer_offset, pCompiledShader->pData, pCompiledShader->size );
+		
+		// Generate a handle to this shader.
+		DWORD shader_handle;
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( pixel_shader_buffer + pixel_shader_buffer_offset ), &shader_handle );
+
+		// Update the buffer offset now we have copied a new shader into the buffer.
+		pixel_shader_buffer_offset += pCompiledShader->size;
+
+		// This was allocated during XGAssembleShader().
+		XGBuffer_Release( pCompiledShader );
+
+		// Place the shader handle in the table.
+		sPixelShaderTable.PutItem( code, (DWORD*)shader_handle );
+		
+		// Return the handle.
+		return shader_handle;
+	}
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void create_pixel_shaders( void )
+{
+	static bool created_shaders = false;
+	if( !created_shaders )
+	{
+		created_shaders = true;
+
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader0PixelShader[0] ),						&PixelShader0 );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader0IVAPixelShader[0] ),					&PixelShader0IVA );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader1PixelShader[0] ),						&PixelShader1 );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader2PixelShader[0] ),						&PixelShader2 );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader3PixelShader[0] ),						&PixelShader3 );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader4PixelShader[0] ),						&PixelShader4 );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader5PixelShader[0] ),						&PixelShader5 );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderBrightenPixelShader[0] ),				&PixelShaderBrighten );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderBrightenIVAPixelShader[0] ),			&PixelShaderBrightenIVA );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderFocusBlurPixelShader[0] ),				&PixelShaderFocusBlur );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderFocusIntegratePixelShader[0] ),			&PixelShaderFocusIntegrate );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderFocusLookupIntegratePixelShader[0] ),	&PixelShaderFocusLookupIntegrate );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderNULLPixelShader[0] ),					&PixelShaderNULL );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderPointSpritePixelShader[0] ),			&PixelShaderPointSprite );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShaderBumpWaterPixelShader[0] ),				&PixelShaderBumpWater );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwPixelShader_ShadowBufferPixelShader[0] ),			&PixelShader_ShadowBuffer );
+		D3DDevice_CreatePixelShader((D3DPIXELSHADERDEF*)( &dwShadowBufferStaticGeomPSPixelShader[0] ),			&ShadowBufferStaticGeomPS );
+	
+		// Shouldn't be doing this here!
+		pTextureProjectionDetailsTable = new Lst::HashTable< sTextureProjectionDetails >( 8 );
+
+#		if 0
+		// Light glow test code.
+		sLightGlowDetails *p_details = new sLightGlowDetails;
+		p_details->m_pos.Set( 0.0f, 48.0f, 0.0f );
+		p_details->m_glow_radius	= 24.0f;
+		p_details->m_current_radius	= 0.0f;
+		p_details->m_test_radius	= 2.0f;
+		p_details->m_radius_growth	= 0.2f;
+		sLightGlowDetailsTable.PutItem( (uint32)p_details, p_details );
+
+		p_details = new sLightGlowDetails;
+		p_details->m_pos.Set( 60.0f, 48.0f, 0.0f );
+		p_details->m_glow_radius	= 24.0f;
+		p_details->m_current_radius	= 0.0f;
+		p_details->m_test_radius	= 2.0f;
+		p_details->m_radius_growth	= 0.2f;
+		sLightGlowDetailsTable.PutItem( (uint32)p_details, p_details );
+
+		p_details = new sLightGlowDetails;
+		p_details->m_pos.Set( -60.0f, 48.0f, 0.0f );
+		p_details->m_glow_radius	= 24.0f;
+		p_details->m_current_radius	= 0.0f;
+		p_details->m_test_radius	= 2.0f;
+		p_details->m_radius_growth	= 0.2f;
+		sLightGlowDetailsTable.PutItem( (uint32)p_details, p_details );
+#		endif
+	}
+}
+	
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void GetPixelShader( sMaterial *p_material, uint32 *p_pixel_shader_id )
+{
+	if( p_material->m_passes == 1 )
+	{
+		// There are only 2 shader for single pass materials, depending on whether a texture is required.
+		if( p_material->mp_tex[0] == NULL )
+		{
+			*p_pixel_shader_id = PixelShader1;
+		}
+		else
+		{
+			uint32 ignore_bf = p_material->GetIgnoreVertexAlphaPasses();
+
+			if(( p_material->m_reg_alpha[0] & sMaterial::BLEND_MODE_MASK ) == vBLEND_MODE_BRIGHTEN )
+			{
+				// The single pass mode is brighten, which requires special handling.
+				if( ignore_bf == 0 )
+					*p_pixel_shader_id = PixelShaderBrighten;
+				else
+					*p_pixel_shader_id = PixelShaderBrightenIVA;
+			}
+			else
+			{
+				if( ignore_bf == 0 )
+					*p_pixel_shader_id = PixelShader0;
+				else
+					*p_pixel_shader_id = PixelShader0IVA;
+			}
+		}
+	}
+	else
+	{
+		// Get the pixel shader.
+		*p_pixel_shader_id = get_pixel_shader( p_material );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_texture( uint32 pass, IDirect3DTexture8 *p_texture, IDirect3DPalette8 *p_palette )
+{
+	if((IDirect3DTexture8*)( EngineGlobals.p_texture[pass] ) != p_texture )
+	{
+		// Use SwitchTexture() whenever possible. Cannot switch from or to a NULL texture. Also cannot
+		// switch to a liner texture (in this case the calling code should perform a set_texture( NULL )
+		// call first, to force D3DDevice_SetTexture() to be called for the linear texture.
+		if(( p_texture != NULL ) && ( EngineGlobals.p_texture[pass] != NULL ))
+		{
+			EngineGlobals.p_Device->SwitchTexture( pass, (LPDIRECT3DBASETEXTURE8)( p_texture ));
+		}
+		else
+		{
+			D3DDevice_SetTexture( pass, (LPDIRECT3DBASETEXTURE8)( p_texture ));
+		}
+
+		if( p_palette )
+		{
+			D3DDevice_SetPalette( pass, p_palette );
+		}
+
+		EngineGlobals.p_texture[pass] = p_texture;
+	}
+}
+
+				
+				
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_blend_mode( uint32 mode )
+{
+	if( NxXbox::EngineGlobals.blend_mode_override )
+	{
+		mode = NxXbox::EngineGlobals.blend_mode_override;
+	}
+
+	// Only do something if the blend mode is changing.
+	if( mode != EngineGlobals.blend_mode_value )
+	{
+		// Low 24 bits contain the mode, high 8 bits contain the fixed alpha value.
+		if(( mode & 0x00FFFFFFUL ) != ( EngineGlobals.blend_mode_value & 0x00FFFFFFUL ))
+		{
+			// For additive and subtractive, we set the fog color to black.
+			if( EngineGlobals.fog_enabled )
+			{
+				if((( mode & 0x00FFFFFFUL ) >= vBLEND_MODE_ADD ) && (( mode & 0x00FFFFFFUL ) <= vBLEND_MODE_SUB_FIXED ))
+				{
+					D3DDevice_SetRenderState( D3DRS_FOGCOLOR, 0x00000000UL );
+				}
+				else
+				{
+					D3DDevice_SetRenderState( D3DRS_FOGCOLOR, NxXbox::EngineGlobals.fog_color );
+				}
+			}
+
+			int blend_op, src_blend, dest_blend;
+
+			switch( mode & 0x00FFFFFFUL )
+			{
+				case vBLEND_MODE_DIFFUSE:			// ( 0 - 0 ) * 0 + Src
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_ONE;
+					dest_blend	= D3DBLEND_ZERO;
+					break;				
+				}
+				case vBLEND_MODE_ADD:				// ( Src - 0 ) * Src + Dst
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_SRCALPHA;
+					dest_blend	= D3DBLEND_ONE;
+					break;				
+				}
+				case vBLEND_MODE_ADD_FIXED:			// ( Src - 0 ) * Fixed + Dst
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_CONSTANTALPHA;
+					dest_blend	= D3DBLEND_ONE;
+					break;				
+				}
+				case vBLEND_MODE_SUBTRACT:			// ( 0 - Src ) * Src + Dst
+				{
+					blend_op	= D3DBLENDOP_REVSUBTRACT;
+					src_blend	= D3DBLEND_SRCALPHA;
+					dest_blend	= D3DBLEND_ONE;
+					break;				
+				}
+				case vBLEND_MODE_SUB_FIXED:			// ( 0 - Src ) * Fixed + Dst
+				{
+					blend_op	= D3DBLENDOP_REVSUBTRACT;
+					src_blend	= D3DBLEND_CONSTANTALPHA;
+					dest_blend	= D3DBLEND_ONE;
+					break;				
+				}
+				case vBLEND_MODE_BLEND:				// ( Src - Dst ) * Src + Dst	
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_SRCALPHA;
+					dest_blend	= D3DBLEND_INVSRCALPHA;
+					break;				
+				}
+				case vBLEND_MODE_BLEND_FIXED:		// ( Src - Dst ) * Fixed + Dst	
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_CONSTANTALPHA;
+					dest_blend	= D3DBLEND_INVCONSTANTALPHA;
+					break;				
+				}
+				case vBLEND_MODE_MODULATE:			// ( Dst - 0 ) * Src + 0
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_ZERO;
+					dest_blend	= D3DBLEND_SRCALPHA;
+					break;				
+				}
+				case vBLEND_MODE_MODULATE_FIXED:	// ( Dst - 0 ) * Fixed + 0	
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_ZERO;
+					dest_blend	= D3DBLEND_CONSTANTALPHA;
+					break;				
+				}
+				case vBLEND_MODE_BRIGHTEN:			// ( Dst - 0 ) * Src + Dst
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_DESTCOLOR;
+					dest_blend	= D3DBLEND_ONE;
+					break;				
+				}
+				case vBLEND_MODE_BRIGHTEN_FIXED:	// ( Dst - 0 ) * Fixed + Dst	
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_DESTCOLOR;
+					dest_blend	= D3DBLEND_CONSTANTALPHA;
+					break;				
+				}
+				case vBLEND_MODE_GLOSS_MAP:
+				{
+					// Treat as diffuse for now.
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_ONE;
+					dest_blend	= D3DBLEND_ZERO;
+					break;				
+				}
+				case vBLEND_MODE_MODULATE_COLOR:	// ( Dst - 0 ) * Src(col) + 0 - specially for the shadow.
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_ZERO;
+					dest_blend	= D3DBLEND_SRCCOLOR;
+					break;				
+				}
+				case vBLEND_MODE_BLEND_PREVIOUS_MASK:
+				{
+					// Meaningless unless destination alpha is enabled.
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_DESTALPHA;
+					dest_blend	= D3DBLEND_INVDESTALPHA;
+					break;
+				}
+				case vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK:
+				{
+					// Meaningless unless destination alpha is enabled.
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_INVDESTALPHA;
+					dest_blend	= D3DBLEND_DESTALPHA;
+					break;
+				}
+				case vBLEND_MODE_ONE_INV_SRC_ALPHA:
+				{
+					blend_op	= D3DBLENDOP_ADD;
+					src_blend	= D3DBLEND_ONE;
+					dest_blend	= D3DBLEND_INVSRCALPHA;
+					break;
+				}
+				default:
+				{
+					Dbg_Assert( 0 );
+					break;
+				}
+			}
+
+			// Now set the values if they have changed.
+			if( blend_op != EngineGlobals.blend_op )
+			{
+				D3DDevice_SetRenderState( D3DRS_BLENDOP, blend_op );
+				EngineGlobals.blend_op	= blend_op;
+			}
+			if( src_blend != EngineGlobals.src_blend )
+			{
+				D3DDevice_SetRenderState( D3DRS_SRCBLEND, src_blend );
+				EngineGlobals.src_blend	= src_blend;
+			}
+			if( dest_blend != EngineGlobals.dest_blend )
+			{
+				D3DDevice_SetRenderState( D3DRS_DESTBLEND, dest_blend );
+				EngineGlobals.dest_blend = dest_blend;
+			}
+		}
+
+		// Change the fixed alpha value if different.
+		if(( mode & 0xFF000000UL ) != ( EngineGlobals.blend_mode_value & 0xFF000000UL ))
+		{
+			uint32 fixed_alpha	= mode & 0xFF000000UL;
+			fixed_alpha			= fixed_alpha >= 0x80000000UL ? 0xFF000000UL : ( fixed_alpha << 1 );
+			D3DDevice_SetRenderState( D3DRS_BLENDCOLOR,	fixed_alpha );
+		}
+
+		// Set the new blend mode value.
+		EngineGlobals.blend_mode_value	= mode;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_vertex_shader( DWORD shader_id )
+{
+	if( EngineGlobals.vertex_shader_override == 0 )
+	{
+		if( EngineGlobals.vertex_shader_id != shader_id )
+		{
+			// Set vertex shader.
+			D3DDevice_SetVertexShader( shader_id );
+			EngineGlobals.vertex_shader_id = shader_id;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_pixel_shader( uint32 shader_id )
+{
+	if( EngineGlobals.pixel_shader_override == 0 )
+	{
+		if( EngineGlobals.pixel_shader_id != shader_id )
+		{
+			// Set pixel shader.
+			D3DDevice_SetPixelShader( shader_id );
+			EngineGlobals.pixel_shader_id = shader_id;
+
+			// Changing pixel shader invalidates the constants, so we need to upload.
+			EngineGlobals.upload_pixel_shader_constants = true;
+		}
+			
+		// Upload any pixel shader constants if required.
+		if( EngineGlobals.upload_pixel_shader_constants && ( shader_id > 0 ))
+		{
+			D3DDevice_SetPixelShaderConstant( 0, EngineGlobals.pixel_shader_constants, 5 );
+		}
+	}
+
+	// Want to clear this field even if the override is set, since otherwise it will persist and cause problems later.
+	EngineGlobals.upload_pixel_shader_constants = false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_pixel_shader( uint32 shader_id, uint32 num_passes )
+{
+	if( EngineGlobals.pixel_shader_override == 0 )
+	{
+		if( EngineGlobals.pixel_shader_id != shader_id )
+		{
+			// Set pixel shader.
+			D3DDevice_SetPixelShader( shader_id );
+			EngineGlobals.pixel_shader_id = shader_id;
+
+			// Changing pixel shader invalidates the constants, so we need to upload.
+			EngineGlobals.upload_pixel_shader_constants = true;
+		}
+			
+		// Upload any pixel shader constants if required.
+		if( EngineGlobals.upload_pixel_shader_constants && ( shader_id > 0 ))
+		{
+			D3DDevice_SetPixelShaderConstant( 0, &EngineGlobals.pixel_shader_constants[0], num_passes );
+			D3DDevice_SetPixelShaderConstant( 4, &EngineGlobals.pixel_shader_constants[16], 1 );
+		}
+	}
+
+	// Want to clear this field even if the override is set, since otherwise it will persist and cause problems later.
+	EngineGlobals.upload_pixel_shader_constants = false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_render_state( uint32 type, uint32 state )
+{
+	switch( type )
+	{
+		case RS_ZBIAS:
+		{
+			if( state != EngineGlobals.z_bias )
+			{
+				EngineGlobals.z_bias = state;
+				D3DDevice_SetRenderState( D3DRS_ZBIAS, state );
+			}
+			break;
+		}
+
+		case RS_CULLMODE:
+		{
+			if( state != EngineGlobals.cull_mode )
+			{
+				EngineGlobals.cull_mode = state;
+				D3DDevice_SetRenderState( D3DRS_CULLMODE, state );
+			}
+			break;
+		}
+
+		case RS_ALPHABLENDENABLE:
+		{
+			if( state != EngineGlobals.alpha_blend_enable )
+			{
+				EngineGlobals.alpha_blend_enable = state;
+				D3DDevice_SetRenderState( D3DRS_ALPHABLENDENABLE, ( state > 0 ) ? TRUE : FALSE );
+			}
+			break;
+		}
+
+		case RS_ALPHATESTENABLE:
+		{
+			if( state != EngineGlobals.alpha_test_enable )
+			{
+				EngineGlobals.alpha_test_enable = state;
+				D3DDevice_SetRenderState( D3DRS_ALPHATESTENABLE, ( state > 0 ) ? TRUE : FALSE );
+			}
+			break;
+		}
+
+		case RS_ZWRITEENABLE:
+		{
+			if( state > 0 )
+			{
+				if( EngineGlobals.z_write_enabled == FALSE )
+				{
+					D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
+					EngineGlobals.z_write_enabled = TRUE;
+				}
+			}
+			else
+			{
+				if( EngineGlobals.z_write_enabled == TRUE )
+				{
+					D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE, FALSE );
+					EngineGlobals.z_write_enabled = FALSE;
+				}
+			}
+			break;
+		}
+
+		case RS_ZTESTENABLE:
+		{
+			if( state > 0 )
+			{
+				if( EngineGlobals.z_test_enabled == FALSE )
+				{
+					D3DDevice_SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );
+					EngineGlobals.z_test_enabled = TRUE;
+				}
+			}
+			else
+			{
+				if( EngineGlobals.z_test_enabled == TRUE )
+				{
+					D3DDevice_SetRenderState( D3DRS_ZFUNC, D3DCMP_ALWAYS );
+					EngineGlobals.z_test_enabled = FALSE;
+				}
+			}
+			break;
+		}
+
+		case RS_ALPHACUTOFF:
+		{
+			// Convert from state (where 1 means "render all pixels with alpha 1 or higher") to the D3D.
+			// Also, if alpha cutoff is 1 or greater, enable alphakill, which can in some cases provide an earlier
+			// rejection of the pixel.
+			if( state != EngineGlobals.alpha_ref )
+			{
+				EngineGlobals.alpha_ref = state;
+				if( state > 0 )
+				{
+					D3DDevice_SetRenderState( D3DRS_ALPHAREF,				state );
+
+					// Enable alpha testing.
+					if( EngineGlobals.alpha_test_enable == 0 )
+					{
+						D3DDevice_SetRenderState( D3DRS_ALPHATESTENABLE,	TRUE );
+						EngineGlobals.alpha_test_enable = 1;
+					}
+
+					D3DDevice_SetTextureStageState( 0, D3DTSS_ALPHAKILL,	D3DTALPHAKILL_ENABLE );
+				}
+				else
+				{
+					// Disable alpha testing.
+					if( EngineGlobals.alpha_test_enable > 0 )
+					{
+						D3DDevice_SetRenderState( D3DRS_ALPHATESTENABLE,	FALSE );
+						EngineGlobals.alpha_test_enable = 0;
+					}
+					D3DDevice_SetTextureStageState( 0, D3DTSS_ALPHAKILL,	D3DTALPHAKILL_DISABLE );
+				}
+			}
+			break;
+		}
+		
+		case RS_SPECULARENABLE:
+		{
+			if( state != EngineGlobals.specular_enabled )
+			{
+				EngineGlobals.specular_enabled = state;
+
+				if( state > 0 )
+				{
+					D3DDevice_SetRenderState( D3DRS_SPECULARENABLE, TRUE );
+					D3DDevice_SetRenderState( D3DRS_LOCALVIEWER, TRUE );
+				}
+				else
+				{
+					D3DDevice_SetRenderState( D3DRS_SPECULARENABLE, FALSE );
+					D3DDevice_SetRenderState( D3DRS_LOCALVIEWER, FALSE );
+				}
+			}
+			break;
+		}
+
+		case RS_FOGENABLE:
+		{
+			if( state != EngineGlobals.fog_enabled )
+			{
+				EngineGlobals.fog_enabled = state;
+				D3DDevice_SetRenderState( D3DRS_FOGENABLE, ( state > 0 ) ? TRUE : FALSE );
+			}
+			break;
+		}
+
+		case RS_UVADDRESSMODE0:
+		case RS_UVADDRESSMODE1:
+		case RS_UVADDRESSMODE2:
+		case RS_UVADDRESSMODE3:
+		{
+			int pass = type - RS_UVADDRESSMODE0;
+			if(( state & 0xFFFFUL ) != ( EngineGlobals.uv_addressing[pass] & 0xFFFFUL ))
+			{
+				switch( state & 0xFFFFUL )
+				{
+					case 0x0000U:
+					{
+						D3DDevice_SetTextureStageState( pass, D3DTSS_ADDRESSU, D3DTADDRESS_WRAP );
+						break;
+					}
+					case 0x0001U:
+					{
+						D3DDevice_SetTextureStageState( pass, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP );
+						break;
+					}
+					case 0x0002U:
+					{
+						D3DDevice_SetTextureStageState( pass, D3DTSS_ADDRESSU, D3DTADDRESS_BORDER );
+						break;
+					}
+					default:
+					{
+						Dbg_Assert( 0 );
+						break;
+					}
+				}
+				EngineGlobals.uv_addressing[pass] = ( EngineGlobals.uv_addressing[pass] & 0xFFFF0000UL ) | ( state & 0xFFFFUL );
+			}
+
+			if(( state & 0xFFFF0000UL ) != ( EngineGlobals.uv_addressing[pass] & 0xFFFF0000UL ))
+			{
+				switch( state & 0xFFFF0000UL )
+				{
+					case 0x00000000UL:
+					{
+						D3DDevice_SetTextureStageState( pass, D3DTSS_ADDRESSV, D3DTADDRESS_WRAP );
+						break;
+					}
+					case 0x00010000UL:
+					{
+						D3DDevice_SetTextureStageState( pass, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP );
+						break;
+					}
+					case 0x00020000UL:
+					{
+						D3DDevice_SetTextureStageState( pass, D3DTSS_ADDRESSV, D3DTADDRESS_BORDER );
+						break;
+					}
+					default:
+					{
+						Dbg_Assert( 0 );
+						break;
+					}
+				}
+				EngineGlobals.uv_addressing[pass] = ( EngineGlobals.uv_addressing[pass] & 0x0000FFFFUL ) | ( state & 0xFFFF0000UL );
+			}
+			break;
+		}
+
+		case RS_MIPLODBIASPASS0:
+		case RS_MIPLODBIASPASS1:
+		case RS_MIPLODBIASPASS2:
+		case RS_MIPLODBIASPASS3:
+		{
+			int pass = type - RS_MIPLODBIASPASS0;
+			if( state != EngineGlobals.mip_map_lod_bias[pass] )
+			{
+				D3DDevice_SetTextureStageState( pass, D3DTSS_MIPMAPLODBIAS, state );
+				EngineGlobals.mip_map_lod_bias[pass] = state;
+			}
+			break;
+		}
+
+		case RS_MINMAGFILTER0:
+		case RS_MINMAGFILTER1:
+		case RS_MINMAGFILTER2:
+		case RS_MINMAGFILTER3:
+		{
+			int pass = type - RS_MINMAGFILTER0;
+
+			// Magnification filter.
+			state = state & 0xFFFF0000UL;
+			if( state != EngineGlobals.min_mag_filter[pass] )
+			{
+				if( state == 0x00000000UL )
+				{
+					// Point.
+					D3DDevice_SetTextureStageState( pass, D3DTSS_MAGFILTER, D3DTEXF_POINT );
+				}
+				else
+				{
+					// Linear.
+					D3DDevice_SetTextureStageState( pass, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );
+				}
+				EngineGlobals.min_mag_filter[pass] = state;
+			}
+			break;
+		}
+	}
+}
+
+
+
+
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void create_texture_projection_details( sTexture *p_texture, Nx::CXboxModel *p_model, sScene *p_scene )
+{
+	sTextureProjectionDetails *p_details = new sTextureProjectionDetails;
+
+	p_details->p_model		= p_model;
+	p_details->p_scene		= p_scene;
+	p_details->p_texture	= p_texture;
+	
+	XGMatrixIdentity( &p_details->view_matrix );
+	XGMatrixIdentity( &p_details->projection_matrix );
+	
+	pTextureProjectionDetailsTable->PutItem((uint32)p_texture, p_details );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void destroy_texture_projection_details( sTexture *p_texture )
+{
+	sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->GetItem((uint32)p_texture );
+	if( p_details )
+	{
+		pTextureProjectionDetailsTable->FlushItem((uint32)p_texture );
+		delete p_details;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_texture_projection_camera( sTexture *p_texture, XGVECTOR3 *p_pos, XGVECTOR3 *p_at )
+{
+	sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->GetItem((uint32)p_texture );
+	if( p_details )
+	{
+		// Check for 'straight down' vector.
+		if(( p_pos->x == p_at->x ) && ( p_pos->z == p_at->z ))
+		{
+			XGMatrixLookAtRH( &p_details->view_matrix, p_pos, p_at, &XGVECTOR3( 0.0f, 0.0f, 1.0f ));
+		}
+		else
+		{
+			XGMatrixLookAtRH( &p_details->view_matrix, p_pos, p_at, &XGVECTOR3( 0.0f, 1.0f, 0.0f ));
+		}
+		XGMatrixOrthoRH( &p_details->projection_matrix, 96.0f, 96.0f, 1.0f, 128.0f );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// MSM PERFCHANGE - added scale.
+void set_camera( Mth::Matrix *p_matrix, Mth::Vector *p_position, float screen_angle, float aspect_ratio, bool render_at_infinity )
+{
+	EngineGlobals.cam_position.x	= p_position->GetX();
+	EngineGlobals.cam_position.y	= p_position->GetY();
+	EngineGlobals.cam_position.z	= p_position->GetZ();
+	
+	EngineGlobals.cam_at.x			= p_matrix->GetAt().GetX();
+	EngineGlobals.cam_at.y			= p_matrix->GetAt().GetY();
+	EngineGlobals.cam_at.z			= p_matrix->GetAt().GetZ();
+	
+	EngineGlobals.cam_up.x			= p_matrix->GetUp().GetX();
+	EngineGlobals.cam_up.y			= p_matrix->GetUp().GetY();
+	EngineGlobals.cam_up.z			= p_matrix->GetUp().GetZ();
+	
+	XGMatrixIdentity( &EngineGlobals.world_matrix );
+
+	// XGMatrixLookAtRH() takes an 'at' position rather than a direction, so we need it relative to the camera position.
+	XGVECTOR3	at;
+	at.x	= EngineGlobals.cam_position.x - EngineGlobals.cam_at.x;
+	at.y	= EngineGlobals.cam_position.y - EngineGlobals.cam_at.y;
+	at.z	= EngineGlobals.cam_position.z - EngineGlobals.cam_at.z;
+	XGMatrixLookAtRH( &EngineGlobals.view_matrix, &EngineGlobals.cam_position, &at, &EngineGlobals.cam_up );
+
+	EngineGlobals.near_plane	= 2.0f;
+	EngineGlobals.far_plane		= 32000.0f;
+	EngineGlobals.screen_angle	= screen_angle;
+
+	// Figure width and height of viewport at near clip plane.
+	float half_screen_angle_in_radians	= Mth::DegToRad( screen_angle * 0.5f );
+	float width							= EngineGlobals.near_plane * 2.0f * tanf( half_screen_angle_in_radians );
+
+	if( EngineGlobals.backbuffer_width == 640.0f )
+	{
+		// We need to adjust the aspect ratio for the Xbox, since it is now rendering with D3DPRESENTFLAG_10X11PIXELASPECTRATIO
+		// set. This changes the regular aspect ratio from 4:3 to 4:3.3, so adjust the value here.
+		aspect_ratio = aspect_ratio * (( 4.0f / 3.3f ) / ( 4.0f / 3.0f ));
+	}
+	
+	float height	= width / aspect_ratio;
+	XGMatrixPerspectiveRH( &EngineGlobals.projection_matrix, width, height, EngineGlobals.near_plane, EngineGlobals.far_plane );
+	
+	NxXbox::EngineGlobals.near_plane_width	= width;
+	NxXbox::EngineGlobals.near_plane_height	= height;
+
+	if( render_at_infinity )
+	{
+		// Rendering the sky, so set the projection transform up to calculate a constant z value of 1.0.
+		// W value must remain correct however.
+		EngineGlobals.projection_matrix.m[2][2] = -0.999999f;	// Setting this to -1.0f causes D3D to complain about WNear calculation.
+		EngineGlobals.projection_matrix.m[3][2] =  0.0f;
+	}
+	
+	D3DDevice_SetTransform( D3DTS_WORLD, &EngineGlobals.world_matrix );
+	D3DDevice_SetTransform( D3DTS_VIEW, &EngineGlobals.view_matrix );
+	D3DDevice_SetTransform( D3DTS_PROJECTION, &EngineGlobals.projection_matrix );
+
+	// Set up view frustum values for bounding sphere culling.
+	EngineGlobals.ViewFrustumTX	= tanf( Mth::DegToRad( screen_angle * 0.5f ));
+	EngineGlobals.ViewFrustumTY	= -( EngineGlobals.ViewFrustumTX / aspect_ratio );
+	EngineGlobals.ViewFrustumSX	= 1.0f / sqrtf( 1.0f + 1.0f / ( EngineGlobals.ViewFrustumTX * EngineGlobals.ViewFrustumTX ));
+	EngineGlobals.ViewFrustumSY	= 1.0f / sqrtf( 1.0f + 1.0f / ( EngineGlobals.ViewFrustumTY * EngineGlobals.ViewFrustumTY ));
+	EngineGlobals.ViewFrustumCX	= 1.0f / sqrtf( 1.0f + EngineGlobals.ViewFrustumTX * EngineGlobals.ViewFrustumTX );
+	EngineGlobals.ViewFrustumCY	= 1.0f / sqrtf( 1.0f + EngineGlobals.ViewFrustumTY * EngineGlobals.ViewFrustumTY );
+
+	// Set up matrix for offset bump mapping (the matrix that will be used to set the D3DTSS_BUMPENVMATnn texture states).
+    float rotate_angle = atan2f( -EngineGlobals.cam_at.z, -EngineGlobals.cam_at.x );
+	XGMatrixRotationY( &EngineGlobals.bump_env_matrix, rotate_angle - D3DX_PI / 2 );
+
+	// Calculate vectors for billboard rendering.
+	BillboardManager.SetCameraMatrix();
+}
+
+
+
+/******************************************************************/
+/* Quick determination of if something is visible or not, uses	  */
+/* the previously calculated s and c vectors and the			  */
+/* WorldToCamera transform (note, no attempt is made to ensure	  */
+/* this is the same camera that the object will eventually be	  */
+/* rendered with.												  */
+/******************************************************************/
+bool IsVisible( Mth::Vector ¢er, float radius )
+{
+	XGVECTOR4 test_out;
+
+	XGVec3Transform( &test_out, (XGVECTOR3*)¢er[X], (XGMATRIX*)&EngineGlobals.view_matrix );
+
+	if( -test_out.z + radius < EngineGlobals.near_plane )
+		return false;
+
+	float sx_z	= EngineGlobals.ViewFrustumSX * test_out.z;
+	float cx_x	= EngineGlobals.ViewFrustumCX * test_out.x;
+	if(( radius < sx_z - cx_x ) || ( radius < sx_z + cx_x ))
+		return false;
+
+	float sy_z	= EngineGlobals.ViewFrustumSY * test_out.z;
+	float cy_y	= EngineGlobals.ViewFrustumCY * test_out.y;
+	if(( radius < sy_z + cy_y ) || ( radius < sy_z - cy_y ))
+		return false;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_frustum_bbox_transform( Mth::Matrix *p_transform )
+{
+	if( p_transform == NULL )
+	{
+		p_bbox_transform = NULL;
+	}
+	else
+	{
+		p_bbox_transform		= &bbox_transform;
+
+		bbox_transform.m[0][0]	= ( *p_transform ).GetRight().GetX();
+		bbox_transform.m[0][1]	= ( *p_transform ).GetRight().GetY();
+		bbox_transform.m[0][2]	= ( *p_transform ).GetRight().GetZ();
+		bbox_transform.m[0][3]	= 0.0f;
+
+		bbox_transform.m[1][0]	= ( *p_transform ).GetUp().GetX();
+		bbox_transform.m[1][1]	= ( *p_transform ).GetUp().GetY();
+		bbox_transform.m[1][2]	= ( *p_transform ).GetUp().GetZ();
+		bbox_transform.m[1][3]	= 0.0f;
+
+		bbox_transform.m[2][0]	= ( *p_transform ).GetAt().GetX();
+		bbox_transform.m[2][1]	= ( *p_transform ).GetAt().GetY();
+		bbox_transform.m[2][2]	= ( *p_transform ).GetAt().GetZ();
+		bbox_transform.m[2][3]	= 0.0f;
+
+		bbox_transform.m[3][0]	= p_transform->GetPos().GetX();
+		bbox_transform.m[3][1]	= p_transform->GetPos().GetY();
+		bbox_transform.m[3][2]	= p_transform->GetPos().GetZ();
+		bbox_transform.m[3][3]	= 1.0f;
+	}
+}
+
+
+
+float boundingSphereNearestZ = 0.0f;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float get_bounding_sphere_nearest_z( void )
+{
+	return boundingSphereNearestZ;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Checks a bounding sphere against the current view frustum	  */
+/* (ignoring far clipping). Returns true if any part is visible.  */
+/* Timings suggest this method runs on average around ~0.25us,    */
+/* faster than test code doing world space culling against a set  */
+/* of plane equations representing the view frustum in world	  */
+/* space.														  */
+/*                                                                */
+/******************************************************************/
+bool frustum_check_sphere( D3DXVECTOR3 *p_center, float radius )
+{
+	XGVECTOR4	test_out;
+
+	// Build the composite transform if required.
+	if( p_bbox_transform )
+	{
+		// Object to world.
+		test_out.x = p_center->x + p_bbox_transform->_41;
+		test_out.y = p_center->y + p_bbox_transform->_42;
+		test_out.z = p_center->z + p_bbox_transform->_43;
+//		XGVec3Transform( &test_out, (XGVECTOR3*)p_center, p_bbox_transform );
+
+		// World to view.
+		XGVec3Transform( &test_out, (XGVECTOR3*)&test_out, (XGMATRIX*)&EngineGlobals.view_matrix );
+	}
+	else
+	{
+		// World to view.
+		XGVec3Transform( &test_out, (XGVECTOR3*)p_center, (XGMATRIX*)&EngineGlobals.view_matrix );
+	}
+		
+	boundingSphereNearestZ = -test_out.z - radius;
+
+	if( -test_out.z + radius < EngineGlobals.near_plane )
+		return false;
+
+	float sx_z	= EngineGlobals.ViewFrustumSX * test_out.z;
+	float cx_x	= EngineGlobals.ViewFrustumCX * test_out.x;
+	if(( radius < sx_z - cx_x ) || ( radius < sx_z + cx_x ))
+		return false;
+
+	float sy_z	= EngineGlobals.ViewFrustumSY * test_out.z;
+	float cy_y	= EngineGlobals.ViewFrustumCY * test_out.y;
+	if(( radius < sy_z + cy_y ) || ( radius < sy_z - cy_y ))
+		return false;
+
+	return true;
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Checks a bounding box against the current view frustum		  */
+/* (ignoring far clipping). Returns true if any part is visible.  */
+/*                                                                */
+/******************************************************************/
+bool frustum_check_box( Mth::CBBox *p_bbox )
+{
+	XGVECTOR3	test_in, test_out;
+	XGVECTOR4	test_mid;
+	
+	uint32	cumulative_projection_space_outcode	= 0xFF;
+	float	min_x = p_bbox->GetMin().GetX();
+	float	min_y = p_bbox->GetMin().GetY();
+	float	min_z = p_bbox->GetMin().GetZ();
+	float	max_x = p_bbox->GetMax().GetX();
+	float	max_y = p_bbox->GetMax().GetY();
+	float	max_z = p_bbox->GetMax().GetZ();
+
+	for( uint32 v = 0; v < 8; ++v )
+	{
+		uint32 projection_space_outcode = 0;
+
+		test_in.x = ( v & 0x04 ) ? max_x : min_x;
+		test_in.y = ( v & 0x02 ) ? max_y : min_y;
+		test_in.z = ( v & 0x01 ) ? max_z : min_z;
+
+		if( p_bbox_transform )
+		{
+			XGVec3Transform( &test_mid, &test_in, p_bbox_transform );
+			test_in.x = test_mid.x;
+			test_in.y = test_mid.y;
+			test_in.z = test_mid.z;
+		}
+		
+		XGVec3Transform( &test_mid, &test_in, &EngineGlobals.view_matrix );
+		test_in.x = test_mid.x;
+		test_in.y = test_mid.y;
+		test_in.z = test_mid.z;
+		
+		// Do z-checking here.
+		if( -test_mid.z < EngineGlobals.near_plane )
+		{
+			// Behind the camera near plane.
+			projection_space_outcode |= 0x10;
+		}
+		else if( -test_mid.z > EngineGlobals.far_plane )
+		{
+			// Beyond the camera far plane.
+			projection_space_outcode |= 0x20;
+		}
+		
+		// At this point it's important to check to see whether the point is in postive or negative z-space, since
+		// after the projection transform, both very large camera space z values and camera space z values where z < 0
+		// will give results with z > 1. (Camera space values in the range [0,near] give negative projection space z values).
+		XGVec3TransformCoord( &test_out, &test_in, &EngineGlobals.projection_matrix );
+
+		if(( -test_mid.z < 0.0f ) && ( !EngineGlobals.is_orthographic ))
+		{
+			test_out.x = -test_out.x;
+			test_out.y = -test_out.y;
+		}
+
+		if( test_out.x > 1.0f )
+			projection_space_outcode |= 0x01;
+		else if( test_out.x < -1.0f )
+			projection_space_outcode |= 0x02;
+
+		if( test_out.y > 1.0f )
+			projection_space_outcode |= 0x04;
+		else if( test_out.y < -1.0f )
+			projection_space_outcode |= 0x08;
+
+		cumulative_projection_space_outcode	&= projection_space_outcode;
+
+		if( cumulative_projection_space_outcode == 0 )
+		{
+			// Early out.
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+struct sSortedMeshEntry
+{
+	sMesh				*p_mesh;
+	float				sort;
+	sSortedMeshEntry	*pNext;
+};
+
+
+static sSortedMeshEntry	sortedMeshArray[1000];
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void calculate_tex_proj_matrix( XGMATRIX *p_tex_view_matrix, XGMATRIX *p_tex_proj_matrix, XGMATRIX *p_tex_transform_matrix, XGMATRIX *p_world_matrix )
+{
+	// Get the current view matrix.
+	XGMATRIX matView, matInvView;
+	D3DDevice_GetTransform( D3DTS_VIEW, (XGMATRIX*)&matView );
+	XGMatrixInverse( &matInvView,  NULL, &matView );
+
+	XGMATRIX matBiasScale;
+    XGMatrixIdentity( &matBiasScale );
+
+	static float x0 = 256.0f;
+	static float y0 = 256.0f;
+	
+	matBiasScale._11 = x0 * 0.5f;
+	matBiasScale._22 = y0 * -0.5f;
+	matBiasScale._33 = D3DZ_MAX_D24S8;
+
+	static float x1 = 256.0f;
+	static float y1 = 256.0f;
+	
+	matBiasScale._41 = x1 * 0.5f + 0.5f;
+	matBiasScale._42 = y1 * 0.5f + 0.5f;
+
+	XGMATRIX m_matTexProj;
+
+	// Don't bother with inverse view transform for Shadow Buffer, since we are picking up world-space coordinates directly.
+	if( p_world_matrix )
+	{
+		m_matTexProj = *p_world_matrix;												// Transform to world space.
+		XGMatrixMultiply( &m_matTexProj, &m_matTexProj, p_tex_view_matrix );		// Transform to projection camera space.
+	}
+	else
+	{
+		m_matTexProj = *p_tex_view_matrix;											// Transform to projection camera space.
+	}
+	XGMatrixMultiply( &m_matTexProj, &m_matTexProj, p_tex_proj_matrix );			// Situate verts relative to projector's view
+    XGMatrixMultiply( p_tex_transform_matrix, &m_matTexProj, &matBiasScale );		// Scale and bias to map the near clipping plane to texcoords
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_shadow_targets( void )
+{
+	XGMATRIX	stored_view_matrix			= EngineGlobals.view_matrix;
+	XGMATRIX	stored_projection_matrix	= EngineGlobals.projection_matrix;
+	uint32		stored_fog_state			= EngineGlobals.fog_enabled;
+	DWORD		multisample_mode;
+
+	// Get multisample mode.
+	D3DDevice_GetRenderState( D3DRS_MULTISAMPLEMODE, &multisample_mode );
+
+	// Disable fogging.
+	set_render_state( RS_FOGENABLE, 0 );
+
+	// Goes through the list of render target textures, rendering to each one in turn.
+	pTextureProjectionDetailsTable->IterateStart();
+	sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->IterateNext();
+	
+	D3DSurface fake_target;
+
+	while( p_details )
+	{
+		if( p_details->p_model )
+		{
+			// Setup dummy color buffer (bad things will happen if you write to it).
+			// The XGSetSurfaceHeader() function is slow, these values are now set explicitly from the observed values
+			// set by the function.
+//			ZeroMemory( &fake_target, sizeof( fake_target ));
+//			XGSetSurfaceHeader( 256, 256, D3DFMT_LIN_R5G6B5, &fake_target, 0, 0 );
+			fake_target.Common	= 0x00050001UL;
+			fake_target.Data	= 0x00000000UL;
+			fake_target.Lock	= 0x00000000UL;
+			fake_target.Format	= 0x00011129UL;
+			fake_target.Size	= 0x070FF0FFUL;
+			fake_target.Parent	= 0x00000000UL;
+			
+			// Set the new render target.
+			LPDIRECT3DSURFACE8 pSurface;
+
+			// This call will increase the reference count of the IDirect3DTexture8 object.
+			p_details->p_texture->pD3DTexture->GetSurfaceLevel( 0, &pSurface );
+
+			// This call will increase the reference count of the IDirect3DSurface8 object.
+			D3DDevice_SetRenderTarget( &fake_target, pSurface );
+			
+			// Clear the target.
+			D3DDevice_Clear( 0, NULL, D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0, 1.0f, 0 ); 
+			
+			// Disable color writes.
+			D3DDevice_SetRenderState( D3DRS_COLORWRITEENABLE, 0 );
+
+			// Turn on z-offset.
+			D3DDevice_SetRenderState( D3DRS_SOLIDOFFSETENABLE,			TRUE );
+			D3DDevice_SetRenderState( D3DRS_POLYGONOFFSETZOFFSET,		FtoDW( 4.0f ));
+			D3DDevice_SetRenderState( D3DRS_POLYGONOFFSETZSLOPESCALE,	FtoDW( 2.0f ));
+
+			// Performance optimiser suggested change.
+			// Priority 2: Set D3DRS_MULTISAMPLEMODE to D3DMULTISAMPLEMODE_4X for faster fill rate with no quality loss when depth only rendering.
+			D3DDevice_SetRenderState( D3DRS_MULTISAMPLEMODE,			D3DMULTISAMPLEMODE_4X );
+
+			// Set the view and projection transforms.
+			EngineGlobals.view_matrix		= p_details->view_matrix;
+			EngineGlobals.projection_matrix	= p_details->projection_matrix;
+			EngineGlobals.is_orthographic	= true;
+
+			// Render all instances for the CGeom's contained in this model.
+			int num_geoms = p_details->p_model->GetNumGeoms();
+			for( int i = 0; i < num_geoms; ++i )
+			{
+				Nx::CXboxGeom *p_xbox_geom	= static_cast( p_details->p_model->GetGeomByIndex( i ));
+				CInstance *p_instance		= p_xbox_geom->GetInstance();
+				
+				if( p_instance->GetActive())
+				{
+					// Flag the scene as having the shadow version rendered.
+					p_instance->GetScene()->m_flags |= SCENE_FLAG_RENDERING_SHADOW;
+
+					// Render the model.
+					render_instance( p_instance, vRENDER_NO_CULLING );
+
+					// Clear the flag the scene as having the shadow version rendered.
+					p_instance->GetScene()->m_flags &= ~SCENE_FLAG_RENDERING_SHADOW;
+			
+					// Flag the scene as self shadowing.
+					p_instance->GetScene()->m_flags |= SCENE_FLAG_SELF_SHADOWS;
+				}
+			}
+			pSurface->Release();
+		}
+		p_details = pTextureProjectionDetailsTable->IterateNext();
+	}
+
+	// Restore important states.
+	D3DDevice_SetRenderState( D3DRS_COLORWRITEENABLE,	D3DCOLORWRITEENABLE_ALL );
+	D3DDevice_SetRenderState( D3DRS_SOLIDOFFSETENABLE,	FALSE );
+	D3DDevice_SetRenderState( D3DRS_MULTISAMPLEMODE,	multisample_mode );
+	set_render_state( RS_FOGENABLE, stored_fog_state );
+
+	// Pixel shader override no longer required.
+	EngineGlobals.pixel_shader_override = 0;
+	
+	// Restore the view and projection transforms.
+	EngineGlobals.view_matrix		= stored_view_matrix;
+	EngineGlobals.projection_matrix	= stored_projection_matrix;
+	EngineGlobals.is_orthographic	= false;
+
+	// It's important to set the internal reference count of the dummy color surface here, otherwise
+	// the debug version of D3D will complain when it attempts to reduce the internal ref count during
+	// the subsequent SetRenderTarget() call.
+	fake_target.Common	= 0x000D0001UL;
+
+	// Restore the default render target.
+	D3DDevice_SetRenderTarget( EngineGlobals.p_RenderSurface, EngineGlobals.p_ZStencilSurface );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_shadow_meshes( sScene *p_scene, sMesh **p_mesh_indices, int num_meshes )
+{
+	// No anisotropic filtering for the base texture.
+	DWORD stage_zero_minfilter;
+	D3DDevice_GetTextureStageState( 0, D3DTSS_MINFILTER, &stage_zero_minfilter );
+	D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+	
+	// Disable fogging.
+	uint32 stored_fog_state = EngineGlobals.fog_enabled;
+	set_render_state( RS_FOGENABLE, 0 );
+
+	// Scan through each entry in the TextureProjectionDetails table, and see whether it relates to this scene.
+	pTextureProjectionDetailsTable->IterateStart();
+	sTextureProjectionDetails *p_details = pTextureProjectionDetailsTable->IterateNext();
+	while( p_details )
+	{
+		XGMATRIX	stored_view_matrix			= EngineGlobals.view_matrix;
+		XGMATRIX	stored_projection_matrix	= EngineGlobals.projection_matrix;
+			
+		// Calculate the projection matrix that will project world coordinates into our shadow buffer.
+		calculate_tex_proj_matrix( &p_details->view_matrix, &p_details->projection_matrix, &p_details->texture_projection_matrix );
+
+		// Set the vertex shader.
+		set_vertex_shader( ShadowBufferStaticGeomVS );
+
+		// Set the pixel shader that just does straight texturing.
+		set_pixel_shader( ShadowBufferStaticGeomPS );
+
+		set_render_state( RS_ALPHACUTOFF, 1 );
+		
+		// Set the other textures to NULL.
+		set_texture( 0, NULL );
+		set_texture( 2, NULL );
+		set_texture( 3, NULL );
+
+		// Need to set this texture NULL first, to flush the texture state, since the incoming texture is linear.
+		set_texture( 1, NULL );
+
+		// Set the projected texture (as the second-pass texture).
+		if( p_details->p_texture->pD3DSurface )
+			set_texture( 1, (IDirect3DTexture8*)( p_details->p_texture->pD3DSurface ));
+		else
+			set_texture( 1, p_details->p_texture->pD3DTexture );
+
+		// Set shadowbuffer texture details.
+		set_render_state( RS_UVADDRESSMODE1, 0x00020002UL );					// Set (border,border) addressing.
+		D3DDevice_SetTextureStageState( 1, D3DTSS_BORDERCOLOR, 0xffffffff );
+		D3DDevice_SetTextureStageState( 1, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );
+		D3DDevice_SetTextureStageState( 1, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+		
+		// Set shadowbuffer state.
+		D3DDevice_SetRenderState( D3DRS_SHADOWFUNC, D3DCMP_GREATEREQUAL );
+			
+		// Upload constants to the vertex shader for composite world->view->projection transform (c0 - c3) and
+		// world->texture transform (c4 - c7).
+		XGMATRIX	dest_matrix;
+		XGMATRIX	temp_matrix;
+		XGMATRIX	projMatrix;
+		XGMATRIX	viewMatrix;
+		XGMATRIX	worldMatrix;
+		XGMATRIX	texProjMatrix;
+
+		// Texture projection matrix.
+		XGMatrixTranspose( &texProjMatrix, &p_details->texture_projection_matrix );
+		
+		// Projection matrix.
+		XGMatrixTranspose( &projMatrix, &EngineGlobals.projection_matrix );
+	
+		// View matrix.
+		XGMatrixTranspose( &viewMatrix, &EngineGlobals.view_matrix );
+		viewMatrix.m[3][0] = 0.0f;
+		viewMatrix.m[3][1] = 0.0f;
+		viewMatrix.m[3][2] = 0.0f;
+		viewMatrix.m[3][3] = 1.0f;
+	
+		// World space transformation matrix.
+		XGMatrixIdentity( &worldMatrix );
+
+		// Calculate composite world->view->projection matrix.
+		XGMatrixMultiply( &temp_matrix, &viewMatrix, &worldMatrix );
+		XGMatrixMultiply( &dest_matrix, &projMatrix, &temp_matrix );
+
+		// Load up the combined world, camera & projection matrix, and the tetxure transform matrix.
+		D3DDevice_SetVertexShaderConstantFast( 0, (void*)&dest_matrix, 4 );
+		D3DDevice_SetVertexShaderConstantFast( 4, (void*)&texProjMatrix, 4 );
+
+		// Turn on z-offset.
+		D3DDevice_SetRenderState( D3DRS_SOLIDOFFSETENABLE,			TRUE );
+		D3DDevice_SetRenderState( D3DRS_POLYGONOFFSETZOFFSET,		FtoDW( -4.0f ));
+		D3DDevice_SetRenderState( D3DRS_POLYGONOFFSETZSLOPESCALE,	FtoDW( -2.0f ));
+
+		// Set the blend mode to modulate, using the texture color.
+		set_blend_mode( vBLEND_MODE_MODULATE_COLOR );
+
+		// Set up the correct view and projection matrix for frustum culling.
+		EngineGlobals.view_matrix		= p_details->view_matrix;
+		EngineGlobals.projection_matrix	= p_details->projection_matrix;
+		EngineGlobals.is_orthographic	= true;
+
+		// Draw the meshes.
+		for( int i = 0; i < num_meshes; ++i )
+		{
+			sMesh *p_mesh = p_mesh_indices[i];
+				
+			// Check this mesh is okay for shadow rendering.
+			if( !( p_mesh->m_flags & sMesh::MESH_FLAG_NO_SKATER_SHADOW ))
+			{
+				// Cull this mesh against the second view frustum.
+				if( frustum_check_sphere( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius ))
+				{
+//					if( frustum_check_box( &p_mesh->m_bbox ))
+					{
+						// Here we want to set up texture 0 as per the material on the mesh. This way we can use it as an alpha
+						// mask to avoid drawing the shadow on transparent pixels.
+						if( p_mesh->mp_material->mp_tex[0] )
+						{
+							set_texture( 0, p_mesh->mp_material->mp_tex[0]->pD3DTexture );
+							set_render_state( RS_UVADDRESSMODE0, p_mesh->mp_material->m_uv_addressing[0] );
+						}
+
+						// Draw the mesh.
+						D3DDevice_SetStreamSource( 0, p_mesh->mp_vertex_buffer[0], p_mesh->m_vertex_stride );
+						D3DDevice_DrawIndexedVertices( p_mesh->m_primitive_type, p_mesh->m_num_indices[0], p_mesh->mp_index_buffer[0] );
+					}
+				}
+			}
+		}
+
+		// Restore the view and projection transforms.
+		EngineGlobals.view_matrix		= stored_view_matrix;
+		EngineGlobals.projection_matrix	= stored_projection_matrix;
+		EngineGlobals.is_orthographic	= false;
+
+		p_details = pTextureProjectionDetailsTable->IterateNext();
+	}
+
+	// Turn off z-offset.
+	D3DDevice_SetRenderState( D3DRS_SOLIDOFFSETENABLE, FALSE );
+	D3DDevice_SetRenderState( D3DRS_SHADOWFUNC, D3DCMP_NEVER );
+	set_pixel_shader( 0 );
+
+	// Restore anisotropic filtering if present.
+	D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, stage_zero_minfilter );
+
+	// Restore fogging if present.
+	set_render_state( RS_FOGENABLE, stored_fog_state );
+
+	// Reflush linear texture.
+	set_texture( 1, NULL );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int cmp( const void *p1, const void *p2 )
+{
+	return((sSortedMeshEntry*)p1)->sort < ((sSortedMeshEntry*)p2)->sort ? -1 : ((sSortedMeshEntry*)p1)->sort > ((sSortedMeshEntry*)p2)->sort ? 1 : 0;
+}
+
+
+
+static bool debug_shadow_volumes = false;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_shadow_volumes( sScene *p_scene, uint32 viewport )
+{
+	// Switch viewport from value to bitfield value.
+	viewport = ( 1 << viewport );
+	
+	NxXbox::set_pixel_shader( PixelShader5 );
+	EngineGlobals.pixel_shader_override	= PixelShader5;
+	NxXbox::set_texture( 0, NULL );
+	NxXbox::set_render_state( RS_ZWRITEENABLE,	0 );
+	NxXbox::set_render_state( RS_ZTESTENABLE,	1 );
+	NxXbox::set_render_state( RS_ALPHACUTOFF,	0 );
+
+	if( debug_shadow_volumes == false )
+		D3DDevice_SetRenderState( D3DRS_ZFUNC,		D3DCMP_GREATEREQUAL );
+
+	// Render all meshes.
+	for( int e = 0; e < p_scene->m_num_mesh_entries; ++e )
+	{
+		sMesh *p_mesh = p_scene->m_meshes[e];
+		if(( p_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) && ( p_mesh->m_flags & sMesh::MESH_FLAG_SHADOW_VOLUME ))
+		{
+			// Frustum cull this mesh, using the associated bounding sphere.
+			if( frustum_check_sphere( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius ))
+			{
+				if( debug_shadow_volumes == false )
+				{
+					NxXbox::set_render_state( RS_CULLMODE, D3DCULL_CW );
+					NxXbox::set_blend_mode( 0x10000000UL | NxXbox::vBLEND_MODE_ADD_FIXED );
+					p_mesh->Submit();
+
+					NxXbox::set_render_state( RS_CULLMODE, D3DCULL_CCW );
+					NxXbox::set_blend_mode( 0x10000000UL | NxXbox::vBLEND_MODE_SUB_FIXED );
+					p_mesh->Submit();
+				}
+				else
+				{
+					NxXbox::set_render_state( RS_CULLMODE, D3DCULL_NONE );
+					NxXbox::set_blend_mode( 0x30000000UL | NxXbox::vBLEND_MODE_BLEND_FIXED );
+					p_mesh->Submit();
+				}
+			}
+		}
+	}
+
+	EngineGlobals.pixel_shader_override	= 0;
+	NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+	NxXbox::set_render_state( RS_CULLMODE,		D3DCULL_CW );
+	D3DDevice_SetRenderState( D3DRS_ZFUNC,		D3DCMP_LESSEQUAL );
+}
+
+
+#define			VISIBLE_MESH_ARRAY_SIZE		4096
+static sMesh	*visible_mesh_array[VISIBLE_MESH_ARRAY_SIZE];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_scene( sScene *p_scene, uint32 flags, uint32 viewport )
+{
+	sMaterial		*p_material					= NULL;
+	bool			no_culling					= ( flags & vRENDER_NO_CULLING ) > 0 ;
+	bool			render;
+	int				visible_mesh_array_index	= 0;
+
+	// Don't render dictionary scenes.
+	if( p_scene->m_is_dictionary )
+	{
+		return;
+	}
+	
+	if( flags & vRENDER_SHADOW_VOLUMES )
+	{
+		render_shadow_volumes( p_scene, viewport );
+		return;
+	}
+
+	// Disallow front to back sorting if the number of opaque meshes is larger than the visible mesh array.
+	if( p_scene->m_first_semitransparent_entry >= VISIBLE_MESH_ARRAY_SIZE )
+	{
+		flags &= ~vRENDER_SORT_FRONT_TO_BACK;
+	}
+
+	// Switch viewport from value to bitfield value.
+	viewport = ( 1 << viewport );
+	
+	// Render opaque meshes.
+	if( flags & vRENDER_OPAQUE )
+	{
+		for( int e = 0; e < p_scene->m_first_semitransparent_entry; ++e )
+		{
+			sMesh *p_mesh = p_scene->m_meshes[e];
+
+			__asm mov eax, p_mesh			// Store mesh pointer.
+			__asm prefetcht0 [eax]			// Get first 32 bytes of sMesh structure.
+			
+			if(( p_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) && (( p_mesh->m_flags & ( sMesh::MESH_FLAG_SHADOW_VOLUME | sMesh::MESH_FLAG_BILLBOARD )) == 0 ))
+			{
+				if( no_culling )
+				{
+					render = true;
+				}
+				else
+				{
+					render = false;
+
+					// Check the visibility mask.
+					if( p_mesh->m_visibility_mask & viewport )
+					{
+						// Frustum cull this set of meshes, using the associated bounding sphere and bounding box.
+						if( frustum_check_sphere( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius ))
+						{
+							// Check against any occluders.
+							if(( !( flags & vRENDER_OCCLUDED )) || ( !TestSphereAgainstOccluders( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius )))
+							{
+								render = true;
+							}
+						}
+					}
+				}
+
+				// Draw this mesh if we decided it is visible.
+				if( render )
+				{
+					if(( flags & vRENDER_SORT_FRONT_TO_BACK ) == 0 )
+					{
+						// If the material has changed, submit the new material.
+						if( p_mesh->mp_material != p_material )
+						{
+							p_material = p_mesh->mp_material;
+							p_material->Submit();
+						}
+						p_mesh->Submit();
+					}
+					else
+					{
+						// If this mesh is within the 'near' section, render it now. Meshes in the far section will be
+						// deferred for rendering later on, which should gain some benefit from the fast pixel occlusion.
+						if( boundingSphereNearestZ <= FRONT_TO_BACK_SORT_CUTOFF )
+						{
+							if( p_mesh->mp_material != p_material )
+							{
+								p_material = p_mesh->mp_material;
+								p_material->Submit();
+							}
+							p_mesh->Submit();
+						}
+
+						// Set the nearest point of the bounding sphere.
+						p_mesh->m_bounding_sphere_nearest_z = boundingSphereNearestZ;
+					}
+
+					// Add this mesh to the visible list, providing it is within bounds.
+					visible_mesh_array[visible_mesh_array_index] = p_mesh;
+					if( visible_mesh_array_index < ( VISIBLE_MESH_ARRAY_SIZE - 1 ))
+						++visible_mesh_array_index;
+				}
+			}
+		}			
+		
+		if(( flags & vRENDER_SORT_FRONT_TO_BACK ) && ( visible_mesh_array_index > 0 ))
+		{
+			// At this stage we have an array of meshes, some of which may not yet have been rendered.
+			// At this point simply scan through the list twice, drawing all 'far' meshes.
+			for( int vm = 0; vm < visible_mesh_array_index; ++vm )
+			{
+				sMesh *p_sorted_mesh = visible_mesh_array[vm];
+
+				if( p_sorted_mesh->m_bounding_sphere_nearest_z > FRONT_TO_BACK_SORT_CUTOFF )
+				{				
+					// If the material has changed, submit the new material.
+					if( p_sorted_mesh->mp_material != p_material )
+					{
+						p_material = p_sorted_mesh->mp_material;
+						p_material->Submit();
+					}
+					p_sorted_mesh->Submit();
+				}
+			}
+		}
+
+		// Now draw the opaque meshes with shadow mapped on them.
+		if( p_scene->m_flags & SCENE_FLAG_RECEIVE_SHADOWS )
+		{
+			set_render_state( RS_ZWRITEENABLE,	0 );
+			DWORD min_filter;
+			D3DDevice_GetTextureStageState( 0, D3DTSS_MINFILTER, &min_filter );
+			if( min_filter == D3DTEXF_ANISOTROPIC )
+			{
+				D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+			}
+		
+			render_shadow_meshes( p_scene, visible_mesh_array, visible_mesh_array_index );
+
+			if( min_filter == D3DTEXF_ANISOTROPIC )
+			{
+				D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_ANISOTROPIC );
+			}
+			set_render_state( RS_ZWRITEENABLE,	1 );
+		}
+
+		// Reset mesh array
+		visible_mesh_array_index = 0;
+
+		if( flags & vRENDER_BILLBOARDS )
+		{
+			BillboardManager.Render( vRENDER_OPAQUE );
+		}
+	}
+	
+	if( flags & vRENDER_SEMITRANSPARENT )
+	{
+		int e						= p_scene->m_first_semitransparent_entry;
+		int next_sorted_mesh_entry	= 0;
+		
+		// Semitransparent rendering is done in three stages.
+		// The first stage is meshes in the list up to the point where dynamic sorting starts.
+		// The second stage is meshes in the list which use dynamic sorting.
+		// The third stage is meshes in the list beyond the point where dynamic sorting ends.
+		for( ; e < p_scene->m_first_dynamic_sort_entry; ++e )
+		{
+			sMesh *p_mesh = p_scene->m_meshes[e];
+
+			__asm mov eax, p_mesh			// Store mesh pointer.
+			__asm prefetcht0 [eax]			// Get first 32 bytes of sMesh structure.
+
+			if(( p_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) && (( p_mesh->m_flags & ( sMesh::MESH_FLAG_SHADOW_VOLUME | sMesh::MESH_FLAG_BILLBOARD )) == 0 ))
+			{
+				if( no_culling )
+				{
+					render = true;
+				}
+				else
+				{
+					render = false;
+
+					// Check the visibility mask.
+					if( p_mesh->m_visibility_mask & viewport )
+					{
+						// Frustum cull this set of meshes, using the associated bounding box.
+						if( frustum_check_sphere( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius ))
+						{
+							// Check against any occluders.
+							if(( !( flags & vRENDER_OCCLUDED )) || ( !TestSphereAgainstOccluders( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius )))
+							{
+								render = true;
+							}
+						}
+					}
+				}
+				if( render )
+				{
+					// If the material has changed, submit the new material.
+					if( p_mesh->mp_material != p_material )
+					{
+						p_material = p_mesh->mp_material;
+						p_material->Submit();
+					}
+					p_mesh->Submit();
+
+					// Add this mesh to the visible list, providing it is within bounds.
+					visible_mesh_array[visible_mesh_array_index] = p_mesh;
+					if( visible_mesh_array_index < ( VISIBLE_MESH_ARRAY_SIZE - 1 ))
+						++visible_mesh_array_index;
+				}
+			}
+		}
+
+		if( p_scene->m_num_dynamic_sort_entries > 0 )
+		{
+			// Second stage - dynamically sorted meshes.
+			int last_dynamic_sort_entry = p_scene->m_first_dynamic_sort_entry + p_scene->m_num_dynamic_sort_entries;
+			for( ; e < last_dynamic_sort_entry; ++e )
+			{
+				sMesh *p_mesh = p_scene->m_meshes[e];
+
+				__asm mov eax, p_mesh			// Store mesh pointer.
+				__asm prefetcht0 [eax]			// Get first 32 bytes of sMesh structure.
+
+				if(( p_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) && (( p_mesh->m_flags & ( sMesh::MESH_FLAG_SHADOW_VOLUME | sMesh::MESH_FLAG_BILLBOARD )) == 0 ))
+				{
+					if( no_culling )
+					{
+						render = true;
+					}
+					else
+					{
+						render = false;
+
+						// Check the visibility mask.
+						if( p_mesh->m_visibility_mask & viewport )
+						{
+							// Frustum cull this set of meshes, using the associated bounding box.
+							if( frustum_check_sphere( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius ))
+							{
+								// Check against any occluders.
+								if(( !( flags & vRENDER_OCCLUDED )) || ( !TestSphereAgainstOccluders( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius )))
+								{
+									render = true;
+								}
+							}
+						}
+					}
+					if( render )
+					{
+						// Add this mesh to the visible list, providing it is within bounds.
+						visible_mesh_array[visible_mesh_array_index] = p_mesh;
+						if( visible_mesh_array_index < ( VISIBLE_MESH_ARRAY_SIZE - 1 ))
+							++visible_mesh_array_index;
+
+						sortedMeshArray[next_sorted_mesh_entry].p_mesh	= p_mesh;
+						sortedMeshArray[next_sorted_mesh_entry].sort	= boundingSphereNearestZ;
+
+						++next_sorted_mesh_entry;
+					}
+				}
+			}
+			if( next_sorted_mesh_entry > 0 )
+			{
+				// Sort the array into ascending sort order.
+				qsort( sortedMeshArray, next_sorted_mesh_entry, sizeof( sSortedMeshEntry ), cmp );
+		
+				for( int m = 0; m < next_sorted_mesh_entry; ++m )
+				{
+					if( sortedMeshArray[m].p_mesh->mp_material != p_material )
+					{
+						sortedMeshArray[m].p_mesh->mp_material->Submit();
+						p_material = sortedMeshArray[m].p_mesh->mp_material;
+					}
+					sortedMeshArray[m].p_mesh->Submit();
+				}
+			}
+		
+			// Third stage - meshes after the dynamically sorted set.
+			for( ; e < p_scene->m_num_mesh_entries; ++e )
+			{
+				sMesh *p_mesh = p_scene->m_meshes[e];
+
+				__asm mov eax, p_mesh			// Store mesh pointer.
+				__asm prefetcht0 [eax]			// Get first 32 bytes of sMesh structure.
+
+				if(( p_mesh->m_flags & sMesh::MESH_FLAG_ACTIVE ) && (( p_mesh->m_flags & ( sMesh::MESH_FLAG_SHADOW_VOLUME | sMesh::MESH_FLAG_BILLBOARD )) == 0 ))
+				{
+					if( no_culling )
+					{
+						render = true;
+					}
+					else
+					{
+						render = false;
+
+						// Check the visibility mask.
+						if( p_mesh->m_visibility_mask & viewport )
+						{
+							// Frustum cull this set of meshes, using the associated bounding box.
+							if( frustum_check_sphere( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius ))
+							{
+								// Check against any occluders.
+								if(( !( flags & vRENDER_OCCLUDED )) || ( !TestSphereAgainstOccluders( &p_mesh->m_sphere_center, p_mesh->m_sphere_radius )))
+								{
+									render = true;
+								}
+							}
+						}
+					}
+					if( render )
+					{
+						// If the material has changed, submit the new material.
+						if( p_mesh->mp_material != p_material )
+						{
+							p_material = p_mesh->mp_material;
+							p_material->Submit();
+						}
+						p_mesh->Submit();
+
+						// Add this mesh to the visible list, providing it is within bounds.
+						visible_mesh_array[visible_mesh_array_index] = p_mesh;
+						if( visible_mesh_array_index < ( VISIBLE_MESH_ARRAY_SIZE - 1 ))
+							++visible_mesh_array_index;
+					}
+				}
+			}
+		}
+
+		// Now draw the semitransparent meshes with shadow mapped on them.
+		if( p_scene->m_flags & SCENE_FLAG_RECEIVE_SHADOWS )
+		{
+			render_shadow_meshes( p_scene, visible_mesh_array, visible_mesh_array_index );
+		}
+
+		if( flags & vRENDER_BILLBOARDS )
+		{
+			BillboardManager.Render( vRENDER_SEMITRANSPARENT );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void render_light_glows( bool test )
+{
+	struct sLightGlowVert
+	{
+		D3DVECTOR	m_pos;
+		D3DCOLOR	m_col;
+	};
+
+	static sLightGlowVert verts[4];
+
+	// This function will be called twice per render, once to test the amount of pixels drawn,
+	// the other to actually draw the pixels.
+
+	// Used to figure the right and up vectors for creating screen-aligned particle quads.
+	D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+	// Concatenate p_matrix with the emmission angle to create the direction.
+	Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+	// Get the 'right' vector as the cross product of camera 'at' and world 'up'.
+	Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+	Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+	Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+	screen_right.Normalize();
+	screen_up.Normalize();
+
+	sLightGlowDetailsTable.IterateStart();
+	sLightGlowDetails *p_details = sLightGlowDetailsTable.IterateNext();
+
+	if( test )
+	{
+		// Turn of z and color writes.
+//		D3DDevice_SetRenderState( D3DRS_COLORWRITEENABLE,	0 );
+		D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE,		FALSE );
+
+		set_blend_mode( vBLEND_MODE_DIFFUSE );
+		set_texture( 0, NULL );
+		set_pixel_shader( PixelShader5 );
+		set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+
+		while( p_details )
+		{
+			// Get a new test index.
+			uint32 index = p_details->m_visibility_test_fifo.AddStatus();
+
+			// Only do the check if there is room on the queue.
+			if( index > 0 )
+			{
+				// For each light glow instance, add a render check.
+				D3DDevice_BeginVisibilityTest();
+
+				// Draw the glow.
+				verts[0].m_pos.x	= p_details->m_pos[X] - ( screen_right[X] * p_details->m_test_radius ) - ( screen_up[X] * p_details->m_test_radius );
+				verts[0].m_pos.y	= p_details->m_pos[Y] - ( screen_right[Y] * p_details->m_test_radius ) - ( screen_up[Y] * p_details->m_test_radius );
+				verts[0].m_pos.z	= p_details->m_pos[Z] - ( screen_right[Z] * p_details->m_test_radius ) - ( screen_up[Z] * p_details->m_test_radius );
+				verts[0].m_col		= 0xFFFFFFFF;
+
+				verts[1].m_pos.x	= p_details->m_pos[X] - ( screen_right[X] * p_details->m_test_radius ) + ( screen_up[X] * p_details->m_test_radius );
+				verts[1].m_pos.y	= p_details->m_pos[Y] - ( screen_right[Y] * p_details->m_test_radius ) + ( screen_up[Y] * p_details->m_test_radius );
+				verts[1].m_pos.z	= p_details->m_pos[Z] - ( screen_right[Z] * p_details->m_test_radius ) + ( screen_up[Z] * p_details->m_test_radius );
+				verts[1].m_col		= 0xFFFFFFFF;
+
+				verts[2].m_pos.x	= p_details->m_pos[X] + ( screen_right[X] * p_details->m_test_radius ) + ( screen_up[X] * p_details->m_test_radius );
+				verts[2].m_pos.y	= p_details->m_pos[Y] + ( screen_right[Y] * p_details->m_test_radius ) + ( screen_up[Y] * p_details->m_test_radius );
+				verts[2].m_pos.z	= p_details->m_pos[Z] + ( screen_right[Z] * p_details->m_test_radius ) + ( screen_up[Z] * p_details->m_test_radius );
+				verts[2].m_col		= 0xFFFFFFFF;
+
+				verts[3].m_pos.x	= p_details->m_pos[X] + ( screen_right[X] * p_details->m_test_radius ) - ( screen_up[X] * p_details->m_test_radius );
+				verts[3].m_pos.y	= p_details->m_pos[Y] + ( screen_right[Y] * p_details->m_test_radius ) - ( screen_up[Y] * p_details->m_test_radius );
+				verts[3].m_pos.z	= p_details->m_pos[Z] + ( screen_right[Z] * p_details->m_test_radius ) - ( screen_up[Z] * p_details->m_test_radius );
+				verts[3].m_col		= 0xFFFFFFFF;
+
+				D3DDevice_DrawVerticesUP( D3DPT_QUADLIST, 4, verts, sizeof( sLightGlowVert ));
+
+				// Push a visibility check onto the queue.
+				D3DDevice_EndVisibilityTest( index );
+			}
+
+			p_details = sLightGlowDetailsTable.IterateNext();
+		}
+
+		// Restore z and color writes.
+		D3DDevice_SetRenderState( D3DRS_COLORWRITEENABLE,	D3DCOLORWRITEENABLE_ALL );
+		D3DDevice_SetRenderState( D3DRS_ZWRITEENABLE,		TRUE );
+	}
+	else
+	{
+		set_blend_mode( vBLEND_MODE_BLEND );
+		set_pixel_shader( PixelShader5 );
+		set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+
+		while( p_details )
+		{
+			// Get the visibility check result.
+			uint32 result = p_details->m_visibility_test_fifo.GetStatus();
+
+			if( result > 0 )
+			{
+				// Tend the radius towards the target.
+				p_details->m_current_radius += ( p_details->m_glow_radius - p_details->m_current_radius ) * p_details->m_radius_growth;
+			}
+			else
+			{
+				// Tend the radius towards zero.
+				p_details->m_current_radius -= p_details->m_current_radius * p_details->m_radius_growth;
+			}
+
+			if( p_details->
+				m_current_radius > 0.0f )
+			{
+				// Draw the glow.
+				verts[0].m_pos.x	= p_details->m_pos[X] - ( screen_right[X] * p_details->m_current_radius ) - ( screen_up[X] * p_details->m_current_radius );
+				verts[0].m_pos.y	= p_details->m_pos[Y] - ( screen_right[Y] * p_details->m_current_radius ) - ( screen_up[Y] * p_details->m_current_radius );
+				verts[0].m_pos.z	= p_details->m_pos[Z] - ( screen_right[Z] * p_details->m_current_radius ) - ( screen_up[Z] * p_details->m_current_radius );
+				verts[0].m_col		= 0x40FFFFFF;
+
+				verts[1].m_pos.x	= p_details->m_pos[X] - ( screen_right[X] * p_details->m_current_radius ) + ( screen_up[X] * p_details->m_current_radius );
+				verts[1].m_pos.y	= p_details->m_pos[Y] - ( screen_right[Y] * p_details->m_current_radius ) + ( screen_up[Y] * p_details->m_current_radius );
+				verts[1].m_pos.z	= p_details->m_pos[Z] - ( screen_right[Z] * p_details->m_current_radius ) + ( screen_up[Z] * p_details->m_current_radius );
+				verts[1].m_col		= 0x40FFFFFF;
+
+				verts[2].m_pos.x	= p_details->m_pos[X] + ( screen_right[X] * p_details->m_current_radius ) + ( screen_up[X] * p_details->m_current_radius );
+				verts[2].m_pos.y	= p_details->m_pos[Y] + ( screen_right[Y] * p_details->m_current_radius ) + ( screen_up[Y] * p_details->m_current_radius );
+				verts[2].m_pos.z	= p_details->m_pos[Z] + ( screen_right[Z] * p_details->m_current_radius ) + ( screen_up[Z] * p_details->m_current_radius );
+				verts[2].m_col		= 0x40FFFFFF;
+
+				verts[3].m_pos.x	= p_details->m_pos[X] + ( screen_right[X] * p_details->m_current_radius ) - ( screen_up[X] * p_details->m_current_radius );
+				verts[3].m_pos.y	= p_details->m_pos[Y] + ( screen_right[Y] * p_details->m_current_radius ) - ( screen_up[Y] * p_details->m_current_radius );
+				verts[3].m_pos.z	= p_details->m_pos[Z] + ( screen_right[Z] * p_details->m_current_radius ) - ( screen_up[Z] * p_details->m_current_radius );
+				verts[3].m_col		= 0x40FFFFFF;
+
+				D3DDevice_DrawVerticesUP( D3DPT_QUADLIST, 4, verts, sizeof( sLightGlowVert ));
+			}
+
+			p_details = sLightGlowDetailsTable.IterateNext();
+		}
+	}
+}
+
+
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/render.h b/Code/Gfx/XBox/NX/render.h
new file mode 100644
index 0000000..c7da74c
--- /dev/null
+++ b/Code/Gfx/XBox/NX/render.h
@@ -0,0 +1,129 @@
+#ifndef __RENDER_H
+#define __RENDER_H
+
+#include 
+#include 
+#include 
+#include 
+#include "mesh.h"
+
+#define		RS_ZWRITEENABLE			1
+#define		RS_ZTESTENABLE			2
+#define		RS_ALPHACUTOFF			3
+#define		RS_UVADDRESSMODE0		4
+#define		RS_UVADDRESSMODE1		5
+#define		RS_UVADDRESSMODE2		6
+#define		RS_UVADDRESSMODE3		7
+#define		RS_MIPLODBIASPASS0		8
+#define		RS_MIPLODBIASPASS1		9
+#define		RS_MIPLODBIASPASS2		10
+#define		RS_MIPLODBIASPASS3		11
+#define		RS_CULLMODE				16
+#define		RS_ALPHABLENDENABLE		17
+#define		RS_ALPHATESTENABLE		18
+#define		RS_SPECULARENABLE		19
+#define		RS_FOGENABLE			20
+#define		RS_ZBIAS				21
+#define		RS_MINMAGFILTER0		32
+#define		RS_MINMAGFILTER1		33
+#define		RS_MINMAGFILTER2		34
+#define		RS_MINMAGFILTER3		35
+
+extern		DWORD PixelShader0;
+extern		DWORD PixelShader0IVA;
+extern		DWORD PixelShader1;
+extern		DWORD PixelShader2;
+extern		DWORD PixelShader3;
+extern		DWORD PixelShader4;
+extern		DWORD PixelShader5;
+extern		DWORD PixelShaderBrighten;
+extern		DWORD PixelShaderBrightenIVA;
+extern		DWORD PixelShaderFocusBlur;
+extern		DWORD PixelShaderFocusIntegrate;
+extern		DWORD PixelShaderFocusLookupIntegrate;
+extern		DWORD PixelShaderNULL;
+extern		DWORD PixelShaderPointSprite;
+extern		DWORD PixelShaderBumpWater;
+
+namespace NxXbox
+{
+	struct sTextureProjectionDetails
+	{
+		sTexture		*p_texture;
+		Nx::CXboxModel	*p_model;
+		sScene			*p_scene;
+		XGMATRIX		view_matrix;
+		XGMATRIX		projection_matrix;
+		XGMATRIX		texture_projection_matrix;
+	};
+
+
+	extern Lst::HashTable< sTextureProjectionDetails > *pTextureProjectionDetailsTable;
+
+	
+	typedef enum
+	{
+		vBLEND_MODE_DIFFUSE,								// ( 0 - 0 ) * 0 + Src
+		vBLEND_MODE_ADD,									// ( Src - 0 ) * Src + Dst
+		vBLEND_MODE_ADD_FIXED,								// ( Src - 0 ) * Fixed + Dst
+		vBLEND_MODE_SUBTRACT,								// ( 0 - Src ) * Src + Dst
+		vBLEND_MODE_SUB_FIXED,								// ( 0 - Src ) * Fixed + Dst
+		vBLEND_MODE_BLEND,									// ( Src * Dst ) * Src + Dst	
+		vBLEND_MODE_BLEND_FIXED,							// ( Src * Dst ) * Fixed + Dst	
+		vBLEND_MODE_MODULATE,								// ( Dst - 0 ) * Src + 0
+		vBLEND_MODE_MODULATE_FIXED,							// ( Dst - 0 ) * Fixed + 0	
+		vBLEND_MODE_BRIGHTEN,								// ( Dst - 0 ) * Src + Dst
+		vBLEND_MODE_BRIGHTEN_FIXED,							// ( Dst - 0 ) * Fixed + Dst	
+		vBLEND_MODE_GLOSS_MAP,								// Specular = Specular * Src	- special mode for gloss mapping
+		vBLEND_MODE_BLEND_PREVIOUS_MASK,					// ( Src - Dst ) * Dst + Dst
+		vBLEND_MODE_BLEND_INVERSE_PREVIOUS_MASK,			// ( Dst - Src ) * Dst + Src
+
+		vBLEND_MODE_MODULATE_COLOR					= 15,	// ( Dst - 0 ) * Src(col) + 0	- special mode for the shadow.
+		vBLEND_MODE_ONE_INV_SRC_ALPHA				= 17,	//								- special mode for imposter rendering.
+
+		vNUM_BLEND_MODES
+	} BlendModes; 
+
+	typedef enum
+	{
+		vRENDER_OPAQUE								= 1,						
+		vRENDER_SEMITRANSPARENT						= 2,
+		vRENDER_OCCLUDED							= 4,
+		vRENDER_NO_CULLING							= 8,		// Used for instances which have already been culled at a higher level
+		vRENDER_SORT_FRONT_TO_BACK					= 16,		// Used to improve pixel rejection tests for opaque rendering only
+		vRENDER_SHADOW_VOLUMES						= 32,		// Used to indicate that only shadow volumes should be (special-case) rendered
+		vRENDER_BILLBOARDS							= 64,		// Used to indicate that billboards should be rendered
+		vRENDER_INSTANCE_PRE_WORLD_SEMITRANSPARENT	= 128,		// Used to indicate that this instance rendering is happening prior to semitransparent world rendering
+		vRENDER_INSTANCE_POST_WORLD_SEMITRANSPARENT	= 256,		// Used to indicate that this instance rendering is happening after semitransparent world rendering
+	} SceneRenderFlags; 
+
+	BlendModes	GetBlendMode( uint32 blend_checksum );
+
+	void		create_pixel_shaders();
+	void		GetPixelShader( sMaterial *p_material, uint32 *p_pixel_shader_id );
+	void		set_pixel_shader( uint32 shader_id );
+	void		set_pixel_shader( uint32 shader_id, uint32 num_passes );
+	void		set_vertex_shader( DWORD shader_id );
+
+	void		set_render_state( uint32 type, uint32 state );
+	void		set_blend_mode( uint32 mode );
+	void		set_texture( uint32 pass, IDirect3DTexture8 *p_texture, IDirect3DPalette8 *p_palette = NULL );
+	
+	void		create_texture_projection_details( sTexture *p_texture, Nx::CXboxModel *p_model, sScene *p_scene );
+	void		destroy_texture_projection_details( sTexture *p_texture );
+	void		set_texture_projection_camera( sTexture *p_texture, XGVECTOR3 *p_pos, XGVECTOR3 *p_at );
+	void		calculate_tex_proj_matrix( XGMATRIX *p_tex_view_matrix, XGMATRIX *p_tex_proj_matrix, XGMATRIX *p_tex_transform_matrix, XGMATRIX *p_world_matrix = NULL );
+	
+	// MSM PERFCHANGE - added scale.
+	void	set_camera( Mth::Matrix *p_matrix, Mth::Vector *p_position, float screen_angle, float aspect_ratio, bool render_at_infinity = false );
+	void	set_frustum_bbox_transform( Mth::Matrix *p_transform );
+	bool	frustum_check_sphere( D3DXVECTOR3 *p_center, float radius );
+	float	get_bounding_sphere_nearest_z( void );
+	bool	IsVisible( Mth::Vector ¢er, float radius );
+	void	render_shadow_targets();
+	void	render_light_glows( bool test );
+	void	render_scene( sScene *p_scene, uint32 flags = ( vRENDER_OPAQUE | vRENDER_SEMITRANSPARENT ), uint32 viewport = 0 );
+
+} // namespace NxXbox
+
+#endif // __RENDER_H
diff --git a/Code/Gfx/XBox/NX/scene.cpp b/Code/Gfx/XBox/NX/scene.cpp
new file mode 100644
index 0000000..8c6459c
--- /dev/null
+++ b/Code/Gfx/XBox/NX/scene.cpp
@@ -0,0 +1,492 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "mesh.h"
+#include "scene.h"
+
+namespace NxXbox
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int sort_by_material_draw_order( const void *p1, const void *p2 )
+{
+	sMesh		*p_mesh1		= *((sMesh**)p1 );
+	sMesh		*p_mesh2		= *((sMesh**)p2 );
+	sMaterial	*p_material1	= p_mesh1->mp_material;
+	sMaterial	*p_material2	= p_mesh2->mp_material;
+	
+	Dbg_Assert( p_material1 != NULL );
+	Dbg_Assert( p_material2 != NULL );
+
+	if( p_material1->m_draw_order == p_material2->m_draw_order )
+	{
+		// Have to do some special case processing for the situation where two or more meshes have the same draw order, but only some are
+		// marked as being dynamically sorted. In such a situation the non dynamically sorted ones should come first.
+		if( p_material1->m_sorted == p_material2->m_sorted )
+		{
+			if( p_material1 == p_material2 )
+			{
+				// Same material, no further sorting required.
+				return 0;
+			}
+			else
+			{
+				// If the pixel shaders are the same, sort by material address, otherwise sort by pixel shader value.
+				if( p_mesh1->m_pixel_shader == p_mesh2->m_pixel_shader )
+				{
+					return ((uint32)p_material1 > (uint32)p_material2 ) ? 1 : -1;
+				}
+				else
+				{
+					return ((uint32)p_mesh1->m_pixel_shader > (uint32)p_mesh2->m_pixel_shader ) ? 1 : -1;
+				}
+			}
+		}
+		else if( p_material1->m_sorted )
+		{
+			return 1;
+		}
+		return -1;
+	}
+	return ( p_material1->m_draw_order > p_material2->m_draw_order ) ? 1 : -1;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sScene::sScene( void )
+{
+	m_flags								= 0;
+
+	// No meshes as yet.
+	m_num_mesh_entries					= 0;
+	m_num_semitransparent_mesh_entries	= 0;
+	m_num_filled_mesh_entries			= 0;
+	m_first_dynamic_sort_entry			= 0xFFFFFFFFUL;
+	m_first_semitransparent_entry		= 0xFFFFFFFFUL;
+	m_num_dynamic_sort_entries			= 0;
+	m_is_dictionary						= false;
+
+	m_meshes							= NULL;
+	pMaterialTable						= NULL;
+
+	mp_hierarchyObjects					= NULL;
+	m_numHierarchyObjects				= 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sScene::~sScene( void )
+{
+	// Remove the material table.
+	if( pMaterialTable )
+	{
+		delete pMaterialTable;
+	}
+
+	if( m_meshes != NULL )
+	{
+		delete [] m_meshes;
+	}
+
+	if( mp_hierarchyObjects )
+	{
+		delete [] mp_hierarchyObjects;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::FigureBoundingVolumes( void )
+{
+	// Figure bounding sphere assuming bounding box has already been set up (during individual mesh initialisation).
+	Mth::Vector radial	= ( m_bbox.GetMax() - m_bbox.GetMin() ) * 0.5f;
+	Mth::Vector center	= m_bbox.GetMin() + radial;
+	m_sphere_center		= D3DXVECTOR3( center[X], center[Y], center[Z] );
+	m_sphere_radius		= sqrtf(( radial[X] * radial[X] ) +	( radial[Y] * radial[Y] ) + ( radial[Z] * radial[Z] ));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::CountMeshes( int num_meshes, sMesh **pp_meshes )
+{
+	// Count each mesh.
+	for( int m = 0; m < num_meshes; ++m )
+	{
+		++m_num_mesh_entries;
+		bool transparent = (( pp_meshes[m]->mp_material ) && ( pp_meshes[m]->mp_material->m_flags[0] & 0x40 ));
+		if( transparent )
+		{
+			++m_num_semitransparent_mesh_entries;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::CreateMeshArrays( void )
+{
+	if( m_num_mesh_entries > 0 )
+	{
+		m_meshes = new sMesh*[m_num_mesh_entries];
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::AddMeshes( int num_meshes, sMesh **pp_meshes )
+{
+	// Add each mesh.
+	for( int m = 0; m < num_meshes; ++m )
+	{
+		Dbg_Assert( m_num_filled_mesh_entries < m_num_mesh_entries );
+		m_meshes[m_num_filled_mesh_entries] = pp_meshes[m];
+		++m_num_filled_mesh_entries;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::RemoveMeshes( int num_meshes, sMesh **pp_meshes )
+{
+	int num_mesh_entries_removed				= 0;
+	
+	for( int m = 0; m < num_meshes; ++m )
+	{
+		sMesh *p_mesh = pp_meshes[m];
+
+		bool found = false;
+		for( int i = 0; i < m_num_mesh_entries; ++i )
+		{
+			if( m_meshes[i] == p_mesh )
+			{
+				found = true;
+				m_meshes[i] = NULL;
+				++num_mesh_entries_removed;
+				break;
+			}
+		}
+
+		Dbg_Assert( found );	
+	}
+	
+	// Now go through and compact the arrays.
+	while( num_mesh_entries_removed > 0 )
+	{
+		for( int i = 0; i < m_num_mesh_entries; ++i )
+		{
+			if( m_meshes[i] == NULL )
+			{
+				// Only worth copying if there is anything beyond this mesh.
+				if( i < ( m_num_mesh_entries - 1 ))
+				{
+					CopyMemory( &m_meshes[i], &m_meshes[i + 1], sizeof( sMesh* ) * ( m_num_mesh_entries - ( i + 1 )));
+				}
+				--num_mesh_entries_removed;
+				--m_num_mesh_entries;
+				--m_num_filled_mesh_entries;
+				break;
+			}
+		}
+	}
+	
+	// Finally, scan through and mark the start and end of the dynamically sorted set of meshes.
+	m_num_semitransparent_mesh_entries	= 0;
+	m_num_dynamic_sort_entries			= 0;
+	m_first_semitransparent_entry		= m_num_mesh_entries;
+	m_first_dynamic_sort_entry			= m_num_mesh_entries;
+	
+	for( int i = 0; i < m_num_mesh_entries; ++i )
+	{
+		bool transparent = (( m_meshes[i]->mp_material ) && ( m_meshes[i]->mp_material->m_flags[0] & 0x40 ));
+		if( transparent )
+		{
+			++m_num_semitransparent_mesh_entries;
+
+			if( i < m_first_semitransparent_entry )
+			{
+				m_first_semitransparent_entry = i;
+			}
+
+			if( m_meshes[i]->mp_material->m_sorted )
+			{
+				++m_num_dynamic_sort_entries;
+				if( i < m_first_dynamic_sort_entry )
+				{
+					m_first_dynamic_sort_entry = i;
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::SortMeshes( void )
+{
+	// Sort the list of meshes.
+	qsort( m_meshes, m_num_mesh_entries, sizeof( sMesh* ), sort_by_material_draw_order );
+
+	// Now scan through and mark the start and end of the dynamically sorted set of meshes.
+	m_num_semitransparent_mesh_entries	= 0;
+	m_first_semitransparent_entry		= m_num_mesh_entries;
+	m_first_dynamic_sort_entry			= m_num_mesh_entries;
+	m_num_dynamic_sort_entries			= 0;
+	for( int i = 0; i < m_num_mesh_entries; ++i )
+	{
+		bool transparent = (( m_meshes[i]->mp_material ) && ( m_meshes[i]->mp_material->m_flags[0] & 0x40 ));
+		if( transparent )
+		{
+			++m_num_semitransparent_mesh_entries;
+
+			if( i < m_first_semitransparent_entry )
+			{
+				m_first_semitransparent_entry = i;
+			}
+
+			if( m_meshes[i]->mp_material->m_sorted )
+			{
+				++m_num_dynamic_sort_entries;
+				if( i < m_first_dynamic_sort_entry )
+				{
+					m_first_dynamic_sort_entry = i;
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMesh *sScene::GetMeshByLoadOrder( int load_order )
+{
+	for( int i = 0; i < m_num_mesh_entries; ++i )
+	{
+		if( m_meshes[i]->m_load_order == load_order )
+		{
+			return m_meshes[i];
+		}
+	}
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sMaterial *sScene::GetMaterial( uint32 checksum )
+{
+	if( pMaterialTable )
+	{
+		pMaterialTable->IterateStart();
+		sMaterial *p_mat = pMaterialTable->IterateNext();
+		while( p_mat )
+		{
+			if( p_mat->m_checksum == checksum )
+			{
+				return p_mat;
+			}
+			p_mat = pMaterialTable->IterateNext();
+		}
+	}
+	return NULL;
+}
+
+
+
+const uint32	INDEX_WORKBUFFER_SIZE	= 3072;
+static uint16	index_workbuffer[INDEX_WORKBUFFER_SIZE];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sScene::HidePolys( uint32 mask, sCASData *p_cas_data, uint32 num_entries )
+{
+	if(( num_entries == 0 ) || ( mask == 0 ))
+	{
+		return;
+	}
+
+//	Tmr::Time start = Tmr::GetTimeInUSeconds();
+	
+	int indices_added[64];
+	ZeroMemory( indices_added, sizeof( int ) * 64 );
+	
+	// Check against every CAS entry.
+	sCASData *p_cas_entry = p_cas_data;
+	for( uint32 entry = 0; entry < num_entries; ++entry, ++p_cas_entry )
+	{
+		// Check this CAS entry has the correct mask...
+		if( p_cas_entry->mask & mask )
+		{
+			// Get the mesh this entry references.
+			uint32	load_order	= p_cas_entry->data0 >> 16;
+			sMesh	*p_mesh		= GetMeshByLoadOrder( load_order );
+
+			Dbg_Assert( load_order < 32 );
+			
+			if( p_mesh == NULL )
+			{
+				continue;
+			}
+			
+			// Get the indices of the poly referenced.
+//			uint32 i0 = ( p_cas_entry->data0 & 0xFFFF ) - p_mesh->m_index_base;
+			uint32 i0 = p_cas_entry->data0 & 0xFFFF;
+//			uint32 i1 = ( p_cas_entry->data1 >> 16 ) - p_mesh->m_index_base;
+			uint32 i1 = p_cas_entry->data1 >> 16;
+//			uint32 i2 = ( p_cas_entry->data1 & 0xFFFF ) - p_mesh->m_index_base;
+			uint32 i2 = p_cas_entry->data1 & 0xFFFF;
+
+			// For every vertex index in this mesh...
+			uint16 *p_indices	= p_mesh->mp_index_buffer[0];
+			uint32 index0		= p_indices[p_cas_entry->start_index] & 0x7FFF;
+			uint32 index1		= p_indices[p_cas_entry->start_index + 1] & 0x7FFF;
+			for( uint32 i = p_cas_entry->start_index + 2; i < p_mesh->m_num_indices[0]; ++i )
+			{
+				uint32 index2 = p_indices[i] & 0x7FFF;
+				if(( index0 == i0 ) && ( index1 == i1 ) && ( index2 == i2 ))
+				{
+					// Indicate this mesh will need rebuilding.
+					indices_added[load_order] = indices_added[load_order] + 1;
+
+					// Flag the second index of the poly.
+					p_indices[i - 1] |= 0x8000;
+					break;
+				}
+				index0	= index1;
+				index1	= index2;
+			}
+		}
+	}
+
+	// Now rebuild those meshes that need it.
+	for( int m = 0; m < m_num_mesh_entries; ++m )
+	{
+		sMesh *p_mesh = m_meshes[m];
+		if( indices_added[p_mesh->m_load_order] > 0 )
+		{
+			uint16 *p_indices						= p_mesh->mp_index_buffer[0];
+			uint32 new_indices_index				= 0;
+			for( uint32 i = 0; i < p_mesh->m_num_indices[0]; ++i )
+			{
+				uint16 index = p_indices[i];
+				
+				Dbg_Assert( new_indices_index < INDEX_WORKBUFFER_SIZE );
+				index_workbuffer[new_indices_index++]	= index & 0x7FFF;
+
+				if( index & 0x8000 )
+				{
+					Dbg_Assert( new_indices_index < INDEX_WORKBUFFER_SIZE );
+					index_workbuffer[new_indices_index++]	= index & 0x7FFF;
+					Dbg_Assert( new_indices_index < INDEX_WORKBUFFER_SIZE );
+					index_workbuffer[new_indices_index++]	= index & 0x7FFF;
+				}
+			}
+
+			// Create new index buffer, on the same heap as the existing index buffer.
+			Mem::Allocator::BlockHeader*	p_bheader	= Mem::Allocator::BlockHeader::sRead( p_mesh->mp_index_buffer[0] );
+			Mem::Allocator*					p_allocater	= p_bheader->mpAlloc;
+			Mem::Manager::sHandle().PushContext( p_allocater );
+
+			delete [] p_mesh->mp_index_buffer[0];
+			p_mesh->m_num_indices[0]	= (uint16)new_indices_index;
+			p_mesh->mp_index_buffer[0]	= new uint16[p_mesh->m_num_indices[0]];
+
+			Mem::Manager::sHandle().PopContext();
+
+			// And copy in the new indices from the workbuffer.
+			CopyMemory( p_mesh->mp_index_buffer[0], index_workbuffer, sizeof( uint16 ) * p_mesh->m_num_indices[0] );
+		}
+	}
+
+//	Tmr::Time end = Tmr::GetTimeInUSeconds() - start;
+//	printf( "HidePolys() took %ld u seconds\n", end );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sScene *LoadScene( const char *Filename, sScene *pScene )
+{
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void DeleteScene( sScene *pScene )
+{
+	// Iterate through the table of materials, deleting them.
+	if( pScene->pMaterialTable )
+	{
+		pScene->pMaterialTable->IterateStart();
+		sMaterial* p_mat = pScene->pMaterialTable->IterateNext();
+		while( p_mat )
+		{
+			delete p_mat;
+			p_mat = pScene->pMaterialTable->IterateNext();
+		}
+	}
+	
+	// Delete the scene itself.
+	delete pScene;
+}
+
+
+
+sScene *sScene::pHead;
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/scene.h b/Code/Gfx/XBox/NX/scene.h
new file mode 100644
index 0000000..0899fa9
--- /dev/null
+++ b/Code/Gfx/XBox/NX/scene.h
@@ -0,0 +1,87 @@
+#ifndef __SCENE_H
+#define __SCENE_H
+
+
+#include 
+#include 
+#include 
+#include 
+#include "texture.h"
+#include "mesh.h"
+#include "material.h"
+#include "anim.h"
+
+namespace NxXbox
+{
+
+
+struct sMeshEntry
+{
+	sMesh*					mp_mesh;			// Pointer to mesh.
+	int						m_bbox;				// Bounding box index.
+};
+
+#define SCENE_FLAG_RENDERING_SHADOW		( 1 << 7 )
+#define SCENE_FLAG_RECEIVE_SHADOWS		( 1 << 8 )
+#define SCENE_FLAG_SELF_SHADOWS			( 1 << 9 )
+
+struct sScene
+{
+								sScene( void );
+								~sScene( void );
+
+	sMaterial *					sScene::GetMaterial( uint32 checksum );
+	void						CountMeshes( int num_meshes, sMesh **pp_meshes );
+	void						CreateMeshArrays( void );
+	void						AddMeshes( int num_meshes, sMesh **pp_meshes );
+	void						RemoveMeshes( int num_meshes, sMesh **pp_meshes );
+	void						SortMeshes( void );
+	sMesh						*GetMeshByLoadOrder( int load_order );
+	void						FigureBoundingVolumes( void );
+	void						HidePolys( uint32 mask, sCASData *p_cas_data, uint32 num_entries );
+	
+	uint32						m_flags;
+	int							NumTextures;
+	uint8						*pTexBuffer;
+	uint8						*pTexDma;
+	sTexture					*pTextures;
+
+	int							NumMaterials;
+	Lst::HashTable< sMaterial >	*pMaterialTable;
+	
+	sMesh						**m_meshes;
+	int							m_num_mesh_entries;
+	int							m_num_semitransparent_mesh_entries;		// Used for making scene level draw order decisions.
+	int							m_num_filled_mesh_entries;
+	int							m_first_semitransparent_entry;
+	int							m_first_dynamic_sort_entry;
+	int							m_num_dynamic_sort_entries;
+	
+	class CInstance				*pInstances;
+
+	sScene						*pNext;
+
+	static sScene				*pHead;
+
+	bool						m_is_dictionary;
+
+	Mth::CBBox					m_bbox;	
+	D3DXVECTOR3					m_sphere_center;
+	float						m_sphere_radius;
+
+	// For mesh heirarchies.
+	Nx::CHierarchyObject*		mp_hierarchyObjects;						// Array of hierarchy objects.
+	int							m_numHierarchyObjects;						// Number of hierarchy objects.
+};
+
+
+sScene	*LoadScene( const char *Filename, sScene *pScene );
+void	DeleteScene( sScene *pScene );
+int		sort_by_material_draw_order( const void *p1, const void *p2 );
+
+
+} // namespace NxXbox
+
+
+#endif // __SCENE_H
+
diff --git a/Code/Gfx/XBox/NX/screenfx.cpp b/Code/Gfx/XBox/NX/screenfx.cpp
new file mode 100644
index 0000000..afd9d86
--- /dev/null
+++ b/Code/Gfx/XBox/NX/screenfx.cpp
@@ -0,0 +1,1410 @@
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "render.h"
+#include "screenfx.h"
+
+//-----------------------------------------------------------------------------
+// A filter sample holds a subpixel offset and a filter value
+// to be multiplied by a source texture to compute an arbitrary
+// filter.  See filter_copy for more details.
+//-----------------------------------------------------------------------------
+struct FilterSample 
+{
+    FLOAT fValue;               // Coefficient
+    FLOAT fOffsetX, fOffsetY;   // Subpixel offsets of supersamples in destination coordinates
+};
+
+//-----------------------------------------------------------------------------
+// The depth-mapping pixel shader attempts to do higher precision
+// math with eight-bit color registers.  The _x4 instruction modifier
+// is used twice to get a 16x range of values.
+//-----------------------------------------------------------------------------
+FLOAT g_fPixelShaderScale = 16.0f;   // to get into the right range, we scale up the value in the pixel shader
+FLOAT m_fDepth0	= 0.900f;
+FLOAT m_fDepth1	= 0.997f;
+D3DTexture* m_pTextureFocusRange = NULL;   // Lookup table for range of z values to use
+
+// Enumeration of blur filters available in this sample to compare the speed and quality of different types of blur filters for
+// the out-of-focus parts of the scene.
+enum FILTERMODE 
+{
+        FM_BOX,
+        FM_VERT,
+        FM_HORIZ, 
+        FM_BOX2,
+        FM_VERT2,
+        FM_HORIZ2,
+        FM_BOX2_BOX2,
+		FM_VERT2_HORIZ2,
+        FM_HORIZ2_VERT2,
+        FM_VERT2_HORIZ,
+        FM_HORIZ2_VERT,
+        FM_VERT2_HORIZ2_BOX2,
+        FM_BOX2_BOX2_BOX2,
+        FM_VERT2_HORIZ2_VERT2,
+        FM_HORIZ2_VERT2_HORIZ2,
+        FM_VERT2_HORIZ2_VERT2_HORIZ2,
+        FM_HORIZ2_VERT2_HORIZ2_VERT2,
+        FM_BOX2_BOX2_BOX2_BOX2,
+        _FM_MAX
+    };
+
+
+
+
+
+namespace NxXbox
+{
+
+
+void draw_rain( void )
+{
+	static float table_x[512];
+	static float table_y[512];
+	static bool table_set = false;
+	static int table_index = 0;
+	if( !table_set )
+	{
+		table_set = true;
+		for( int i = 0; i < 512; ++i )
+		{
+			table_x[i] = ((float)( rand() - ( RAND_MAX / 2 )) * 32.0f ) / (float)RAND_MAX;
+			table_y[i] = ((float)rand() * 12.0f ) / (float)RAND_MAX;
+		}
+	}
+	
+	static float radius = 24.0f;
+
+	// Distance of the drip from the camera.
+	static float distance = 10.0f * 12.0f;
+
+	// Drip vector.
+	static Mth::Vector offset( 0.0f, -1.0f * radius, 0.0f );
+	static Mth::Vector offset_target( 0.0f, -1.0f * radius, 0.0f );
+
+	static int num_drops = 1000;
+
+	if(( rand() & 63 ) == 1 )
+	{
+		offset_target[X] = ( radius * 0.25f * ( rand() - ( RAND_MAX / 2 ))) / RAND_MAX;
+		offset_target[Y] = ( radius * -1.0f * rand() ) / RAND_MAX;
+		offset_target[Z] = ( radius * 0.25f * ( rand() - ( RAND_MAX / 2 ))) / RAND_MAX;
+		
+		offset_target.Normalize( radius );
+	}
+	
+	offset += ( offset_target - offset ) * 0.2f;
+	offset.Normalize( radius );	
+	
+	D3DXVECTOR4 or( EngineGlobals.cam_position.x + ( distance * EngineGlobals.cam_at.x ),
+					EngineGlobals.cam_position.y + ( distance * EngineGlobals.cam_at.y ),
+					EngineGlobals.cam_position.z + ( distance * EngineGlobals.cam_at.z ),
+					1.0f );
+	D3DXVECTOR4 of( or.x + offset[X], or.y + offset[Y], or.z + offset[Z], 1.0f );
+
+	D3DXVec4Transform( &or, &or, (D3DXMATRIX*)&EngineGlobals.view_matrix );
+	D3DXVec4Transform( &of, &of, (D3DXMATRIX*)&EngineGlobals.view_matrix );
+
+	D3DXVec4Transform( &or, &or, (D3DXMATRIX*)&EngineGlobals.projection_matrix );
+	D3DXVec4Transform( &of, &of, (D3DXMATRIX*)&EngineGlobals.projection_matrix );
+
+	or.x /= or.w;
+	or.y /= or.w;
+
+	of.x /= of.w;
+	of.y /= of.w;
+
+	of.y = -of.y;
+	
+//	printf( "(%.2f %.2f) (%.2f %.2f)\n", or.x, or.y, of.x, of.y );
+
+	// Obtain push buffer lock.
+	DWORD *p_push; 
+	DWORD dwords_per_particle	= 10;
+	DWORD dword_count			= dwords_per_particle * num_drops;
+
+	// Submit particle material.
+//	mp_engine_particle->mp_material->Submit();
+	NxXbox::set_blend_mode( vBLEND_MODE_BLEND );
+		
+	// Set up correct vertex and pixel shader.
+	NxXbox::set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_DIFFUSE );
+	NxXbox::set_pixel_shader( PixelShader5 );
+		
+	// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+	p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+	// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+	// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+	// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+	p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+	p_push[1]	= D3DPT_LINELIST;
+	p_push		+= 2; 
+
+	// Set up loop variables here, since we be potentially entering the loop more than once.
+	int lp = 0;
+
+	while( dword_count > 0 )
+	{
+		int dwords_written = 0;
+
+		// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+		// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+		++p_push;
+		
+		for( ; lp < num_drops; lp++ )
+		{
+			// Check to see if writing another particle will take us over the edge.
+			if(( dwords_written + dwords_per_particle ) > 2047 )
+			{
+				break;
+			}
+
+			float	screen_x0		= (float)(( NxXbox::EngineGlobals.backbuffer_width * rand() ) / RAND_MAX );
+			float	screen_y0		= (float)(( NxXbox::EngineGlobals.backbuffer_height * rand() ) / RAND_MAX );
+
+			int		random_length	= rand();
+			
+			float	screen_x1		= (float)( screen_x0 + (( of.x * ( EngineGlobals.backbuffer_width / 2 ) * random_length ) / RAND_MAX ));
+			float	screen_y1		= (float)( screen_y0 + (( of.y * ( EngineGlobals.backbuffer_height / 2 ) * random_length ) / RAND_MAX ));
+
+			screen_x1		+= table_x[table_index];
+			screen_y1		+= table_y[table_index];
+			table_index		= ( table_index >= 512 ) ? 0 : ( table_index + 1 );
+
+			p_push[0]	= *((DWORD*)&screen_x0 );
+			p_push[1]	= *((DWORD*)&screen_y0 );
+			p_push[2]	= 0x00000000UL;
+			p_push[3]	= 0x00000000UL;
+			p_push[4]	= 0xA0808080UL;
+			p_push		+= 5;
+
+			p_push[0]	= *((DWORD*)&screen_x1 );
+			p_push[1]	= *((DWORD*)&screen_y1 );
+			p_push[2]	= 0x00000000UL;
+			p_push[3]	= 0x00000000UL;
+			p_push[4]	= 0x20808080UL;
+			p_push		+= 5;
+
+			dwords_written	+= dwords_per_particle;
+			dword_count		-= dwords_per_particle;
+		}
+	}
+	p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+	p_push[1] = 0;
+	p_push += 2;
+	D3DDevice_EndPush( p_push );
+}
+
+	
+	
+	
+//////////////////////////////////////////////////////////////////////
+// For mapping from the depth buffer to blend values using
+// a texture map lookup. See media\shaders\depthlookup.psh
+//
+// This is more general than computing the range as in
+// media\shaders\depth.psh, since the ramp can be filled in
+// arbitrarily, but may be more expensive due to the extra texture
+// lookup.
+//
+float FUnitMap(float fAlpha, float fBlue, float fAlphaOffset, float fAlphaSlope, float fBlueOffset, float fBlueSlope)
+{
+    //return g_fPixelShaderScale * fAlphaSlope * (fAlpha - fAlphaOffset) + fBlueSlope * fBlue + fBlueOffset - 0.5f;
+    return g_fPixelShaderScale * fAlphaSlope * (fAlpha - fAlphaOffset) + fBlueSlope * (fBlue - fBlueOffset);
+}
+
+float FQuantizedDepth(float fDepth, float *pfAlpha, float *pfBlue)
+{
+    float fDepth16 = fDepth * (float)(1 << 16);
+    DWORD dwDepth16 = (DWORD)(fDepth16 /*+ 0.5f*/);
+    *pfAlpha = (dwDepth16 >> 8) * (1.0f / 255.0f);
+    *pfBlue = (dwDepth16 & 0xff) * (1.0f / 255.0f);
+    return (float)dwDepth16 / (float)(1 << 16);
+}
+
+	
+	
+	
+//-----------------------------------------------------------------------------
+// Name: CalculateDepthMapping()
+// Desc: Calculate offsets and slope to map given z range to 0,1 in
+//       the depth and focus pixel shaders.
+//-----------------------------------------------------------------------------
+static HRESULT calculate_depth_mapping( float fDepth0, float fDepth1, float* pfAlphaOffset, float* pfAlphaSlope, float* pfBlueOffset, float* pfBlueSlope )
+{
+    // Check range of args
+    if( fDepth0 < 0.0f ) fDepth0 = 0.0f;
+    if( fDepth0 > 1.0f ) fDepth0 = 1.0f;
+    if( fDepth1 < 0.0f ) fDepth1 = 0.0f;
+    if( fDepth1 > 1.0f ) fDepth1 = 1.0f;
+
+    if( fDepth1 < fDepth0 )
+    {
+        // Swap depth to make fDepth0 <= fDepth1
+        float t = fDepth1;
+        fDepth1 = fDepth0;
+        fDepth0 = t;
+    }
+    
+    // Calculate quantized values
+    float fAlpha0, fBlue0;
+    float fQuantizedDepth0 = FQuantizedDepth(fDepth0, &fAlpha0, &fBlue0);
+    float fAlpha1, fBlue1;
+    float fQuantizedDepth1 = FQuantizedDepth(fDepth1, &fAlpha1, &fBlue1);
+
+    // Calculate offset and slopes
+    float fScale = 1.0f / (fQuantizedDepth1 - fQuantizedDepth0);
+    if( fScale > g_fPixelShaderScale )
+    {
+        fScale = g_fPixelShaderScale; // This is the steepest slope we can handle
+        fDepth0 = 0.5f * (fDepth0 + fDepth1) - 0.5f / fScale; // Move start so that peak is in middle of fDepth0 and fDepth1
+        fDepth1 = fDepth0 + 1.0f / fScale;
+        fQuantizedDepth0 = FQuantizedDepth(fDepth0, &fAlpha0, &fBlue0);
+        fQuantizedDepth1 = FQuantizedDepth(fDepth1, &fAlpha1, &fBlue1);
+    }
+    
+    (*pfAlphaOffset) = fAlpha0;
+    (*pfAlphaSlope)  = fScale / g_fPixelShaderScale;
+    (*pfBlueSlope)   = fScale * (1.0f/255.0f); // blue ramp adds more levels to the ramp
+
+    // Align peak of map to center by calculating the quantized alpha value
+//    *pfBlueOffset = 0.5f;   // zero biased up by 0.5f
+//    float fZeroDesired = (fQuantizedDepth0 - fDepth0) / (fDepth1 - fDepth0);
+//    float fZero = FUnitMap(fAlpha0, fBlue0, *pfAlphaOffset, *pfAlphaSlope, *pfBlueOffset, *pfBlueSlope);
+//    float fOneDesired = (fQuantizedDepth1 - fDepth0) / (fDepth1 - fDepth0);
+//    float fOne = FUnitMap(fAlpha1, fBlue1, *pfAlphaOffset, *pfAlphaSlope, *pfBlueOffset, *pfBlueSlope);
+//    *pfBlueOffset = 0.5f * (fZeroDesired-fZero + fOneDesired-fOne) + 0.5f;  // biased up by 0.5f
+    (*pfBlueOffset) = fBlue0;
+    
+    return S_OK;
+}
+
+	
+
+	
+	
+static HRESULT fill_focus_range_texture( bool bRamp )
+{
+    HRESULT hr;
+
+    static const DWORD Width	= 256;
+    static const DWORD Height	= 256;
+    
+    // Create the focus range texture
+    if( m_pTextureFocusRange )
+        m_pTextureFocusRange->Release();
+	EngineGlobals.p_Device->CreateTexture( Width, Height, 1, 0, D3DFMT_A8, 0, &m_pTextureFocusRange );
+    
+    // Fill the focus range texture
+    D3DLOCKED_RECT lockedRect;
+    hr = m_pTextureFocusRange->LockRect( 0, &lockedRect, NULL, 0L );
+    if( FAILED(hr) )
+        return hr;
+    
+    DWORD dwPixelStride = 1;
+    Swizzler s(Width, Height, 0);
+    s.SetV(s.SwizzleV(0));
+    s.SetU(s.SwizzleU(0));
+    if( bRamp )
+    {
+        for( DWORD j = 0; j < Height; j++ )
+        {
+            for( DWORD i = 0; i < Width; i++ )
+            {
+                BYTE *p = (BYTE *)lockedRect.pBits + dwPixelStride * s.Get2D();
+                *p = (BYTE)i;
+                s.IncU();
+            }
+            s.IncV();
+        }
+    }
+    else
+    {
+//        float fAlphaOffset, fAlphaSlope, fBlueOffset, fBlueSlope;
+//        calculate_depth_mapping( m_fDepth0, m_fDepth1, &fAlphaOffset, &fAlphaSlope, &fBlueOffset, &fBlueSlope );
+        for( DWORD i = 0; i < Width; i++ )
+        {
+			float z_high = (float)i / ( Width - 1 );
+
+            for( DWORD j = 0; j < Height; j++ )
+            {
+                BYTE *p = (BYTE *)lockedRect.pBits + dwPixelStride * s.Get2D();
+//                float fAlpha = (float)i / (Width - 1);
+//                float fBlue  = (float)j / (Height - 1);
+//                float fUnit  = 2.0f * (FUnitMap(fAlpha, fBlue, fAlphaOffset, fAlphaSlope, fBlueOffset, fBlueSlope) - 0.5f);
+//                float fMap   = 1.0f - fUnit * fUnit;
+//                if( fMap < 0.0f ) fMap = 0.0f;
+//                if( fMap > 1.0f ) fMap = 1.0f;
+//                *p = (BYTE)(255 * fMap + 0.5f);
+                
+				float z_low			= (float)j / ( Height - 1 );
+				float quantized_z	= ((( z_high * 256.0f ) + z_low ) * 256.0f ) / 65536.0f;
+
+				if( quantized_z < m_fDepth0 )
+				{
+					*p = 0;
+				}
+				else if( quantized_z > m_fDepth1 )
+				{
+					*p = 0;
+				}
+				else
+				{
+					*p = 255;
+				}
+				s.IncV();
+            }
+            s.IncU();
+        }
+    }
+    
+	m_pTextureFocusRange->UnlockRect( 0 );
+
+    return S_OK;
+}
+	
+	
+	
+	
+	
+//-----------------------------------------------------------------------------
+// Name: filter_copy()
+// Desc: Filter the source texture by rendering into the destination texture
+//       with subpixel offsets. Does 4 filter coefficients at a time, using all
+//       the stages of the pixel shader.
+//-----------------------------------------------------------------------------
+
+D3DTexture* m_pBlur;
+	
+static HRESULT filter_copy( LPDIRECT3DTEXTURE8 pTextureDst,
+                                 LPDIRECT3DTEXTURE8 pTextureSrc,
+                                 DWORD dwNumSamples,
+                                 FilterSample rSample[],
+                                 DWORD dwSuperSampleX,
+                                 DWORD dwSuperSampleY )
+{
+    // Set destination as render target, with no-depth buffer
+    LPDIRECT3DSURFACE8 pSurface;
+    pTextureDst->GetSurfaceLevel( 0, &pSurface );
+    EngineGlobals.p_Device->SetRenderTarget( pSurface, NULL );
+    pSurface->Release();
+
+    // Get descriptions of source and destination
+    D3DSURFACE_DESC descSrc;
+    pTextureSrc->GetLevelDesc( 0, &descSrc );
+
+	if( descSrc.MultiSampleType == D3DMULTISAMPLE_2_SAMPLES_MULTISAMPLE_LINEAR )
+	{
+		descSrc.Width *= 2;
+	}
+	
+	// Set render state for filtering
+    EngineGlobals.p_Device->SetRenderState( D3DRS_LIGHTING,         FALSE );
+	set_render_state( RS_ZWRITEENABLE,		0 );
+	set_render_state( RS_ZTESTENABLE,		0 );
+	set_render_state( RS_ALPHATESTENABLE,	0 );
+	set_render_state( RS_ALPHABLENDENABLE,	0 );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_BLENDOP,          D3DBLENDOP_ADD ); // Setup subsequent renderings to add to previous value
+    EngineGlobals.p_Device->SetRenderState( D3DRS_SRCBLEND,         D3DBLEND_ONE );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_DESTBLEND,        D3DBLEND_ONE );
+
+    // Set texture state
+    DWORD xx;
+    for( xx = 0; xx < 4; xx++ )
+    {
+        set_texture( xx, pTextureSrc );  // use our source texture for all four stages
+
+        EngineGlobals.p_Device->SetTextureStageState( xx, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );  // pass texture coords without transformation
+        EngineGlobals.p_Device->SetTextureStageState( xx, D3DTSS_TEXCOORDINDEX, xx ); // each texture has different tex coords
+		set_render_state( RS_UVADDRESSMODE0 + xx, 0x00010001UL );
+        EngineGlobals.p_Device->SetTextureStageState( xx, D3DTSS_ALPHAKILL, D3DTALPHAKILL_DISABLE );
+    }
+    
+	// Use blur pixel shader.
+	set_pixel_shader( PixelShaderFocusBlur );
+	set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_TEX4 );
+
+    // Prepare quadrilateral vertices
+    float x0 = -0.5f;
+    float y0 = -0.5f;
+    float x1 = (float)( descSrc.Width  / dwSuperSampleX ) - 0.5f;
+    float y1 = (float)( descSrc.Height / dwSuperSampleY ) - 0.5f;
+    struct QUAD
+    {
+        float x, y, z, w1;
+        struct uv 
+        {
+            float u, v;
+        } tex[4];   // each texture has different offset
+    };
+    
+    QUAD aQuad[4] = 
+    {
+        { x0, y0, 1.0f, 1.0f, }, // texture coords are set below
+        { x1, y0, 1.0f, 1.0f, },
+        { x0, y1, 1.0f, 1.0f, },
+        { x1, y1, 1.0f, 1.0f, }
+    };
+
+    // Draw a quad for each block of 4 filter coefficients
+    xx = 0; // current texture stage
+    FLOAT fOffsetScaleX, fOffsetScaleY; // convert destination coords to source texture coords
+    FLOAT u0, v0, u1, v1;   // base source rectangle.
+	if( XGIsSwizzledFormat( descSrc.Format ))
+    {
+        FLOAT fWidthScale  = 1.0f / (FLOAT)descSrc.Width;
+        FLOAT fHeightScale = 1.0f / (FLOAT)descSrc.Height;
+        fOffsetScaleX = (FLOAT)dwSuperSampleX * fWidthScale;
+        fOffsetScaleY = (FLOAT)dwSuperSampleY * fHeightScale;
+        u0 = 0.0f;
+        v0 = 0.0f;
+        u1 = (FLOAT)descSrc.Width * fWidthScale;
+        v1 = (FLOAT)descSrc.Height * fHeightScale;
+    }
+    else
+    {
+        fOffsetScaleX = (FLOAT)dwSuperSampleX;
+        fOffsetScaleY = (FLOAT)dwSuperSampleY;
+        u0 = 0.0f;
+        v0 = 0.0f;
+        u1 = (FLOAT)descSrc.Width;
+        v1 = (FLOAT)descSrc.Height;
+	}
+    D3DCOLOR rColor[4];
+    DWORD rPSInput[4];
+    for( DWORD dwSample = 0; dwSample < dwNumSamples; dwSample++ )
+    {
+        // Set filter coefficients
+        FLOAT fValue = rSample[dwSample].fValue;
+//      float rf[4] = {fValue, fValue, fValue, fValue};
+//      EngineGlobals.p_Device->SetPixelShaderConstant(xx, rf, 1);            // positive coeff
+  
+        if( fValue < 0.0f )
+        {
+            rColor[xx] = D3DXCOLOR(-fValue, -fValue, -fValue, -fValue);
+            rPSInput[xx] = PS_INPUTMAPPING_SIGNED_NEGATE | ((xx % 2) ? PS_REGISTER_C1 : PS_REGISTER_C0);
+        }
+        else
+        {
+            rColor[xx] = D3DXCOLOR(fValue, fValue, fValue, fValue);
+            rPSInput[xx] = PS_INPUTMAPPING_SIGNED_IDENTITY | ((xx % 2) ? PS_REGISTER_C1 : PS_REGISTER_C0);
+        }
+
+        // Align supersamples with center of destination pixels
+        FLOAT fOffsetX = rSample[dwSample].fOffsetX * fOffsetScaleX;
+        FLOAT fOffsetY = rSample[dwSample].fOffsetY * fOffsetScaleY;
+        aQuad[0].tex[xx].u = u0 + fOffsetX;
+        aQuad[0].tex[xx].v = v0 + fOffsetY;
+        aQuad[1].tex[xx].u = u1 + fOffsetX;
+        aQuad[1].tex[xx].v = v0 + fOffsetY;
+        aQuad[2].tex[xx].u = u0 + fOffsetX;
+        aQuad[2].tex[xx].v = v1 + fOffsetY;
+        aQuad[3].tex[xx].u = u1 + fOffsetX;
+        aQuad[3].tex[xx].v = v1 + fOffsetY;
+        
+        xx++; // Go to next stage
+        if( xx == 4 || dwSample == dwNumSamples - 1 )  // max texture stages or last sample
+        {
+            // zero out unused texture stage coefficients 
+            // (only for last filter sample, when number of samples is not divisible by 4)
+            for( ; xx < 4; xx++ )
+            {
+				set_texture( xx, NULL );
+                rColor[xx] = 0;
+                rPSInput[xx] = PS_INPUTMAPPING_UNSIGNED_IDENTITY | PS_REGISTER_ZERO;
+            }
+        
+            // Set coefficients
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSCONSTANT0_0, rColor[0] );
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSCONSTANT1_0, rColor[1] );
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSCONSTANT0_1, rColor[2] );
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSCONSTANT1_1, rColor[3] );
+
+            // Remap coefficients to proper sign
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSRGBINPUTS0,
+                                          PS_COMBINERINPUTS( rPSInput[0] | PS_CHANNEL_RGB,   PS_REGISTER_T0 | PS_CHANNEL_RGB   | PS_INPUTMAPPING_SIGNED_IDENTITY,
+                                                             rPSInput[1] | PS_CHANNEL_RGB,   PS_REGISTER_T1 | PS_CHANNEL_RGB   | PS_INPUTMAPPING_SIGNED_IDENTITY ) );
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSALPHAINPUTS0,
+                                          PS_COMBINERINPUTS( rPSInput[0] | PS_CHANNEL_ALPHA, PS_REGISTER_T0 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+                                                             rPSInput[1] | PS_CHANNEL_ALPHA, PS_REGISTER_T1 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY ) );
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSRGBINPUTS1,
+                                          PS_COMBINERINPUTS( rPSInput[2] | PS_CHANNEL_RGB,   PS_REGISTER_T2 | PS_CHANNEL_RGB   | PS_INPUTMAPPING_SIGNED_IDENTITY,
+                                                             rPSInput[3] | PS_CHANNEL_RGB,   PS_REGISTER_T3 | PS_CHANNEL_RGB   | PS_INPUTMAPPING_SIGNED_IDENTITY ) );
+            EngineGlobals.p_Device->SetRenderState( D3DRS_PSALPHAINPUTS1,
+                                          PS_COMBINERINPUTS( rPSInput[2] | PS_CHANNEL_ALPHA, PS_REGISTER_T2 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY,
+                                                             rPSInput[3] | PS_CHANNEL_ALPHA, PS_REGISTER_T3 | PS_CHANNEL_ALPHA | PS_INPUTMAPPING_SIGNED_IDENTITY ) );
+            
+            // Draw the quad to filter the coefficients so far
+			EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, aQuad, sizeof( QUAD )); // one quad blends 4 textures
+
+			// On subsequent renderings, add to what's in the render target.
+			set_render_state( RS_ALPHABLENDENABLE,	1 );
+			xx = 0;
+        }
+    }
+
+    // Clear texture stages
+    for( xx=0; xx<4; xx++ )
+    {
+		set_texture( xx, NULL );
+    }
+
+	// Restore render target, zbuffer, and state.
+    set_pixel_shader( NULL );
+	EngineGlobals.p_Device->SetRenderTarget( EngineGlobals.p_RenderSurface, EngineGlobals.p_ZStencilSurface );
+
+	return S_OK;
+}
+	
+	
+	
+//-----------------------------------------------------------------------------
+// Name: Blur()
+// Desc: Blur backbuffer and set m_pBlur to the current blur texture.  Calls
+//       filter_copy() with different filter coefficients and offsets, based on
+//       the current FILTERMODE setting.
+//-----------------------------------------------------------------------------
+static HRESULT focus_blur( void )
+{
+	D3DTexture	m_BackBufferTexture;
+	D3DTexture	m_BlurTexture[5];
+	
+	FILTERMODE filter_mode = FM_BOX2_BOX2;
+	
+	int	multisample_adjusted_width = EngineGlobals.backbuffer_width * 2;
+	
+	XGSetTextureHeader( multisample_adjusted_width, EngineGlobals.backbuffer_height, 1, 0,
+                        EngineGlobals.backbuffer_format, 0, &m_BackBufferTexture, 
+						EngineGlobals.p_RenderSurface->Data,
+                        multisample_adjusted_width * XGBytesPerPixelFromFormat( EngineGlobals.backbuffer_format ));
+
+	XGSetTextureHeader( EngineGlobals.backbuffer_width, EngineGlobals.backbuffer_height, 1, 0,
+	                    EngineGlobals.blurbuffer_format, 0, &m_BlurTexture[0], 
+						EngineGlobals.p_BlurSurface[0]->Data,
+				        EngineGlobals.backbuffer_width * XGBytesPerPixelFromFormat( EngineGlobals.blurbuffer_format ));
+
+	XGSetTextureHeader( EngineGlobals.backbuffer_width / 2, EngineGlobals.backbuffer_height / 2, 1, 0,
+	                    EngineGlobals.blurbuffer_format, 0, &m_BlurTexture[1], 
+						EngineGlobals.p_BlurSurface[1]->Data,
+				        EngineGlobals.backbuffer_width / 2 * XGBytesPerPixelFromFormat( EngineGlobals.blurbuffer_format ));
+
+	XGSetTextureHeader( EngineGlobals.backbuffer_width / 4, EngineGlobals.backbuffer_height / 4, 1, 0,
+	                    EngineGlobals.blurbuffer_format, 0, &m_BlurTexture[2], 
+						EngineGlobals.p_BlurSurface[2]->Data,
+				        EngineGlobals.backbuffer_width / 4 * XGBytesPerPixelFromFormat( EngineGlobals.blurbuffer_format ));
+
+	XGSetTextureHeader( EngineGlobals.backbuffer_width / 8, EngineGlobals.backbuffer_height / 8, 1, 0,
+	                    EngineGlobals.blurbuffer_format, 0, &m_BlurTexture[3], 
+						EngineGlobals.p_BlurSurface[3]->Data,
+				        EngineGlobals.backbuffer_width / 8 * XGBytesPerPixelFromFormat( EngineGlobals.blurbuffer_format ));
+	
+	// Filters align to blurriest point in supersamples, on the 0.5 boundaries.
+    // This takes advantage of the bilinear filtering in the texture map lookup.
+    static FilterSample BoxFilter[] =     // for 2x2 downsampling
+    {
+        { 0.25f, -0.5f, -0.5f },
+        { 0.25f,  0.5f, -0.5f },
+        { 0.25f, -0.5f,  0.5f },
+        { 0.25f,  0.5f,  0.5f },
+    };
+    static FilterSample YFilter[] =       // 1221 4-tap filter in Y
+    {
+        { 1.0f/6.0f, 0.0f, -1.5f },
+        { 2.0f/6.0f, 0.0f, -0.5f },
+        { 2.0f/6.0f, 0.0f,  0.5f },
+        { 1.0f/6.0f, 0.0f,  1.5f },
+    };
+    static FilterSample XFilter[] =       // 1221 4-tap filter in X
+    {
+        { 1.0f/6.0f, -1.5f, 0.0f },
+        { 2.0f/6.0f, -0.5f, 0.0f },
+        { 2.0f/6.0f,  0.5f, 0.0f },
+        { 1.0f/6.0f,  1.5f, 0.0f },
+    };
+    static FilterSample Y141Filter[] =    // 141 3-tap filter in Y
+    {
+        { 1.0f/6.0f, 0.0f, -1.0f },
+        { 4.0f/6.0f, 0.0f,  0.0f },
+        { 1.0f/6.0f, 0.0f,  1.0f },
+    };
+    static FilterSample X141Filter[] =        // 141 3-tap filter in X
+    {
+        { 1.0f/6.0f, -1.0f, 0.0f },
+        { 4.0f/6.0f,  0.0f, 0.0f },
+        { 1.0f/6.0f,  1.0f, 0.0f },
+    };
+    static FilterSample IdentityFilter[] = // No filtering
+    {
+        { 1.0f, 0.0f, 0.0f },
+    };
+
+    switch( filter_mode )
+    {
+        case FM_BOX:
+        {
+            // Blur from the backbuffer to the blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[0];
+			filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 1, 1 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[0];
+            break;
+        }
+
+        case FM_VERT:
+        {
+            // Blur from the backbuffer to the blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[0];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 1, 1 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[0];
+            break;
+        }
+
+        case FM_HORIZ:
+        {
+            // Blur from the backbuffer to the blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[0];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 1, 1 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[0];
+            break;
+        }
+
+        case FM_BOX2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture *pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[1];
+            break;
+        }
+
+        case FM_VERT2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[1];
+            break;
+        }
+
+        case FM_HORIZ2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture *pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[1];
+            break;
+        }
+        
+        case FM_VERT2_HORIZ2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+            
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[2];
+            break;
+        }
+
+        case FM_HORIZ2_VERT2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+            
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[2];
+            break;
+        }
+
+        case FM_VERT2_HORIZ:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+            
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];
+            filter_copy( pTextureDst, pTextureSrc, 3, X141Filter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[2];
+            break;
+        }
+
+        case FM_HORIZ2_VERT:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];  // destination is next blur texture
+            filter_copy( pTextureDst, pTextureSrc, 3, Y141Filter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[2];
+            break;
+        }
+
+        case FM_BOX2_BOX2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[0];
+            filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 2, 1 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[0];
+            pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[1];
+            break;
+        }
+
+
+        case FM_VERT2_HORIZ2_BOX2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[2];
+            pTextureDst = &m_BlurTexture[3];
+			filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[3];
+            break;
+        }
+
+        case FM_BOX2_BOX2_BOX2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];
+            filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[2];
+            pTextureDst = &m_BlurTexture[3];
+            filter_copy( pTextureDst, pTextureSrc, 4, BoxFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[3];
+            break;
+        }
+
+        case FM_VERT2_HORIZ2_VERT2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[2];
+            pTextureDst = &m_BlurTexture[3];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[3];
+            break;
+        }
+
+        case FM_HORIZ2_VERT2_HORIZ2:
+        {
+            // Blur from the backbuffer to the 1/2 sized blur texture
+            D3DTexture* pTextureSrc = &m_BackBufferTexture;
+            D3DTexture* pTextureDst = &m_BlurTexture[1];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[1];
+            pTextureDst = &m_BlurTexture[2];
+            filter_copy( pTextureDst, pTextureSrc, 4, YFilter, 2, 2 );
+
+            // Blur from the previous blur texture to the next blur texture
+            pTextureSrc = &m_BlurTexture[2];
+            pTextureDst = &m_BlurTexture[3];
+            filter_copy( pTextureDst, pTextureSrc, 4, XFilter, 2, 2 );
+            
+            m_pBlur = (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[3];
+            break;
+        }
+
+        default:
+		{
+            m_pBlur = NULL;
+            break;
+		}
+    }
+    return S_OK;
+}
+
+
+
+
+static HRESULT draw_focus_effect_using_planes( void )
+{
+    // Make a D3DTexture wrapper around the depth buffer surface
+//    D3DTexture ZBufferTexture;
+//    XGSetTextureHeader( EngineGlobals.backbuffer_width, EngineGlobals.backbuffer_height, 1, 0, 
+//                        D3DFMT_LIN_A8B8G8R8, 0, &ZBufferTexture,
+//                        EngineGlobals.p_ZStencilSurface->Data, EngineGlobals.backbuffer_width * 4 );
+
+    // Get size of blur texture for setting texture coords of final blur
+    D3DSURFACE_DESC descBlur;
+    m_pBlur->GetLevelDesc( 0, &descBlur );
+    float fOffsetX = 0.0f;
+    float fOffsetY = 0.5f / (float)descBlur.Height; // vertical blur
+    struct VERTEX 
+    {
+        D3DXVECTOR4 p;
+        FLOAT tu0, tv0;
+
+    } v[4];
+    v[0].p = D3DXVECTOR4( -0.5f,									-0.5f,									1.0f, 1.0f );
+    v[1].p = D3DXVECTOR4( EngineGlobals.backbuffer_width - 0.5f,	-0.5f,									1.0f, 1.0f );
+    v[2].p = D3DXVECTOR4( -0.5f,									EngineGlobals.backbuffer_height - 0.5f,	1.0f, 1.0f );
+    v[3].p = D3DXVECTOR4( EngineGlobals.backbuffer_width - 0.5f,	EngineGlobals.backbuffer_height - 0.5f, 1.0f, 1.0f );
+    v[0].tu0 = fOffsetX;							v[0].tv0 = fOffsetY;
+    v[1].tu0 = fOffsetX + (float)descBlur.Width;	v[1].tv0 = fOffsetY;
+    v[2].tu0 = fOffsetX;							v[2].tv0 = fOffsetY + (float)descBlur.Height;
+    v[3].tu0 = fOffsetX + (float)descBlur.Width;	v[3].tv0 = fOffsetY + (float)descBlur.Height;
+	
+	// Set pixel shader state
+//    float fAlphaOffset, fAlphaSlope, fBlueOffset, fBlueSlope;
+//   calculate_depth_mapping( m_fDepth0, m_fDepth1, &fAlphaOffset, &fAlphaSlope, &fBlueOffset, &fBlueSlope );
+//    float Constants[] = 
+//    {
+//        0.0f, 0.0f, fBlueOffset, fAlphaOffset,      // offset
+//        0.0f, 0.0f, fBlueSlope, 0.0f,               // 1x
+//        0.0f, 0.0f, 0.0f, 0.0f,                     // 4x
+//        0.0f, 0.0f, 0.0f, fAlphaSlope,              // 16x
+//    };
+
+	set_pixel_shader( 0 );
+//    EngineGlobals.p_Device->SetPixelShaderConstant( 0, Constants, 4 );
+    
+    // Set render state
+	set_render_state( RS_ZWRITEENABLE,		0 );
+	set_render_state( RS_ZTESTENABLE,		1 );
+    set_render_state( RS_ALPHATESTENABLE,	0 );
+	set_render_state( RS_ALPHABLENDENABLE,	0 );
+    set_render_state( RS_ALPHACUTOFF,		0 );
+//    EngineGlobals.p_Device->SetRenderState( D3DRS_SRCBLEND,         D3DBLEND_ONE );
+//    EngineGlobals.p_Device->SetRenderState( D3DRS_DESTBLEND,        D3DBLEND_INVSRCALPHA );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_SRCBLEND,         D3DBLEND_ONE );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_DESTBLEND,        D3DBLEND_ZERO );
+
+	// Set texture state.
+	set_texture( 0, m_pBlur );
+	set_texture( 1, NULL );
+	set_texture( 2, NULL );
+    set_texture( 3, NULL );
+
+	set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+    
+	set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_TEX1 );
+
+	// Render the screen-aligned quadrilateral
+	for( int vx = 0; vx < 4; ++vx )
+	{
+		v[vx].p.z = m_fDepth1;
+	}
+	EngineGlobals.p_Device->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );
+    EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_QUADSTRIP, 1, v, sizeof( VERTEX ));
+
+	// Render the screen-aligned quadrilateral
+	for( int vx = 0; vx < 4; ++vx )
+	{
+		v[vx].p.z = m_fDepth0;
+	}
+	EngineGlobals.p_Device->SetRenderState( D3DRS_ZFUNC, D3DCMP_GREATEREQUAL );
+    EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_QUADSTRIP, 1, v, sizeof( VERTEX ));
+
+    // Reset render states
+	EngineGlobals.p_Device->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );
+
+    set_render_state( RS_ALPHATESTENABLE,	1 );
+	set_render_state( RS_ALPHABLENDENABLE,	1 );
+	set_render_state( RS_ZWRITEENABLE,		1 );
+	set_render_state( RS_ZTESTENABLE,		1 );
+
+	set_pixel_shader( 0 );
+
+	set_texture( 0, NULL );
+	set_texture( 1, NULL );
+	set_texture( 2, NULL );
+	set_texture( 3, NULL );
+
+    return S_OK;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: DrawFocusRange()
+// Desc: Choose the focus range by mapping z to a focus value using pixel
+//       shader arithmetic.  See media/shaders/focus.psh for more details.
+//
+//       High focus values leave the back-buffer unchanged.
+//       Low focus values blend in the blurred texture computed by Blur().
+//-----------------------------------------------------------------------------
+static HRESULT draw_focus_effect_using_range( void )
+{
+    // Make a D3DTexture wrapper around the depth buffer surface
+    D3DTexture ZBufferTexture;
+    XGSetTextureHeader( EngineGlobals.backbuffer_width, EngineGlobals.backbuffer_height, 1, 0, 
+                        D3DFMT_LIN_A8B8G8R8, 0, &ZBufferTexture,
+                        EngineGlobals.p_ZStencilSurface->Data, EngineGlobals.backbuffer_width * 4 );
+
+    // Get size of blur texture for setting texture coords of final blur
+    D3DSURFACE_DESC descBlur;
+    m_pBlur->GetLevelDesc( 0, &descBlur );
+    float fOffsetX = 0.0f;
+    float fOffsetY = 0.5f / (float)descBlur.Height; // vertical blur
+    struct VERTEX 
+    {
+        D3DXVECTOR4 p;
+        FLOAT tu0, tv0;
+        FLOAT tu1, tv1;
+    } v[4];
+    v[0].p = D3DXVECTOR4( -0.5f,                      -0.5f,                       1.0f, 1.0f );
+    v[1].p = D3DXVECTOR4( EngineGlobals.backbuffer_width - 0.5f, -0.5f,                       1.0f, 1.0f );
+    v[2].p = D3DXVECTOR4( -0.5f,                      EngineGlobals.backbuffer_height - 0.5f, 1.0f, 1.0f );
+    v[3].p = D3DXVECTOR4( EngineGlobals.backbuffer_width - 0.5f, EngineGlobals.backbuffer_height - 0.5f, 1.0f, 1.0f );
+    v[0].tu0 = 0.0f;                       v[0].tv0 = 0.0f;
+    v[1].tu0 = (float)EngineGlobals.backbuffer_width; v[1].tv0 = 0.0f;
+    v[2].tu0 = 0.0f;                       v[2].tv0 = (float)EngineGlobals.backbuffer_height;
+    v[3].tu0 = (float)EngineGlobals.backbuffer_width; v[3].tv0 = (float)EngineGlobals.backbuffer_height;
+    v[0].tu1 = fOffsetX;                         v[0].tv1 = fOffsetY;
+    v[1].tu1 = fOffsetX + (float)descBlur.Width; v[1].tv1 = fOffsetY;
+    v[2].tu1 = fOffsetX;                         v[2].tv1 = fOffsetY + (float)descBlur.Height;
+    v[3].tu1 = fOffsetX + (float)descBlur.Width; v[3].tv1 = fOffsetY + (float)descBlur.Height;
+    
+    // Set pixel shader state
+    float fAlphaOffset, fAlphaSlope, fBlueOffset, fBlueSlope;
+    calculate_depth_mapping( m_fDepth0, m_fDepth1, &fAlphaOffset, &fAlphaSlope, &fBlueOffset, &fBlueSlope );
+    float Constants[] = 
+    {
+        0.0f, 0.0f, fBlueOffset, fAlphaOffset,      // offset
+        0.0f, 0.0f, fBlueSlope, 0.0f,               // 1x
+        0.0f, 0.0f, 0.0f, 0.0f,                     // 4x
+        0.0f, 0.0f, 0.0f, fAlphaSlope,              // 16x
+    };
+
+	set_pixel_shader( PixelShaderFocusIntegrate );
+    EngineGlobals.p_Device->SetPixelShaderConstant( 0, Constants, 4 );
+    
+    // Set render state
+	set_render_state( RS_ZWRITEENABLE,		0 );
+	set_render_state( RS_ZTESTENABLE,		0 );
+    set_render_state( RS_ALPHATESTENABLE,	1 );
+	set_render_state( RS_ALPHABLENDENABLE,	1 );
+    set_render_state( RS_ALPHACUTOFF,		1 );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_SRCBLEND,         D3DBLEND_ONE );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_DESTBLEND,        D3DBLEND_INVSRCALPHA );
+
+	// Set texture state.
+    set_texture( 0, &ZBufferTexture );
+	set_texture( 1, m_pBlur );
+	set_texture( 2, NULL );
+    set_texture( 3, NULL );
+
+	set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+	set_render_state( RS_UVADDRESSMODE1, 0x00010001UL );
+	set_render_state( RS_UVADDRESSMODE2, 0x00010001UL );
+	set_render_state( RS_UVADDRESSMODE3, 0x00010001UL );
+    
+    // Render the screen-aligned quadrilateral
+	set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_TEX4 );
+    EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_QUADSTRIP, 1, v, sizeof( VERTEX ));
+
+    // Reset render states
+    set_render_state( RS_ALPHATESTENABLE,	1 );
+	set_render_state( RS_ALPHABLENDENABLE,	1 );
+	set_render_state( RS_ZWRITEENABLE,		1 );
+	set_render_state( RS_ZTESTENABLE,		1 );
+
+	set_pixel_shader( 0 );
+
+	set_texture( 0, NULL );
+	set_texture( 1, NULL );
+	set_texture( 2, NULL );
+	set_texture( 3, NULL );
+
+    return S_OK;
+}
+	
+
+	
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: DrawFocusLookup()
+// Desc: Choose the focus range by mapping z through a lookup texture.
+//
+//       See media/shaders/focuslookup.psh for more detail.
+//
+//       This technique has lower performance than using DrawFocus(),
+//       but the focus values can be arbitrary, rather than the
+//       limited types of z-to-focus value mappings available with
+//       pixel shader arithmetic.
+//
+//       High focus values leave the back-buffer unchanged.
+//       Low focus values blend in the blurred texture computed by Blur().
+//-----------------------------------------------------------------------------
+static HRESULT draw_focus_effect_using_lookup( void )
+{
+    // Make a D3DTexture wrapper around the depth buffer surface
+    D3DTexture ZBufferTexture;
+    XGSetTextureHeader( EngineGlobals.backbuffer_width, EngineGlobals.backbuffer_height, 1, 0, 
+                        D3DFMT_LIN_A8B8G8R8, 0, &ZBufferTexture,
+                        EngineGlobals.p_ZStencilSurface->Data, EngineGlobals.backbuffer_width * 4 );
+
+    // Get size of blur texture for setting texture coords of final blur
+    D3DSURFACE_DESC descBlur;
+    m_pBlur->GetLevelDesc( 0, &descBlur );
+    FLOAT fOffsetX = 0.0f;
+    FLOAT fOffsetY = 0.5f / (FLOAT)descBlur.Height; // vertical blur
+
+    // Define a set of vertices to draw a quad in screenspace
+    struct VERTEX 
+    {
+        D3DXVECTOR4 p;
+        FLOAT tu0, tv0;
+        FLOAT tu1, tv1;
+        FLOAT tu2, tv2;
+        FLOAT tu3, tv3;
+    } v[4];
+    v[0].p = D3DXVECTOR4( -0.5f,                      -0.5f,                       1.0f, 1.0f );
+    v[1].p = D3DXVECTOR4( EngineGlobals.backbuffer_width - 0.5f, -0.5f,                       1.0f, 1.0f );
+    v[2].p = D3DXVECTOR4( -0.5f,                      EngineGlobals.backbuffer_height - 0.5f, 1.0f, 1.0f );
+    v[3].p = D3DXVECTOR4( EngineGlobals.backbuffer_width - 0.5f, EngineGlobals.backbuffer_height - 0.5f, 1.0f, 1.0f );
+    v[0].tu0 = 0.0f;                       v[0].tv0 = 0.0f;
+    v[1].tu0 = (float)EngineGlobals.backbuffer_width; v[1].tv0 = 0.0f;
+    v[2].tu0 = 0.0f;                       v[2].tv0 = (float)EngineGlobals.backbuffer_height;
+    v[3].tu0 = (float)EngineGlobals.backbuffer_width; v[3].tv0 = (float)EngineGlobals.backbuffer_height;
+
+    // tu1 and tv1 are ignored
+    // offset final set of texture coords to apply an additional blur
+    v[0].tu2 = -fOffsetX;                         v[0].tv2 = -fOffsetY;
+    v[1].tu2 = -fOffsetX + (FLOAT)descBlur.Width; v[1].tv2 = -fOffsetY;
+    v[2].tu2 = -fOffsetX;                         v[2].tv2 = -fOffsetY + (FLOAT)descBlur.Height;
+    v[3].tu2 = -fOffsetX + (FLOAT)descBlur.Width; v[3].tv2 = -fOffsetY + (FLOAT)descBlur.Height;
+    v[0].tu3 =  fOffsetX;                         v[0].tv3 =  fOffsetY;
+    v[1].tu3 =  fOffsetX + (FLOAT)descBlur.Width; v[1].tv3 =  fOffsetY;
+    v[2].tu3 =  fOffsetX;                         v[2].tv3 =  fOffsetY + (FLOAT)descBlur.Height;
+    v[3].tu3 =  fOffsetX + (FLOAT)descBlur.Width; v[3].tv3 =  fOffsetY + (FLOAT)descBlur.Height;
+
+    // Set pixel shader
+	set_pixel_shader( PixelShaderFocusLookupIntegrate );
+
+    // Set texture state
+	set_texture( 0, &ZBufferTexture );
+	set_texture( 1, m_pTextureFocusRange );
+	set_texture( 2, m_pBlur );
+	set_texture( 3, m_pBlur );
+
+	set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+	set_render_state( RS_UVADDRESSMODE1, 0x00010001UL );
+	set_render_state( RS_UVADDRESSMODE2, 0x00010001UL );
+	set_render_state( RS_UVADDRESSMODE3, 0x00010001UL );
+    
+    // Set render state
+	set_render_state( RS_ZWRITEENABLE,		0 );
+	set_render_state( RS_ZTESTENABLE,		0 );
+    set_render_state( RS_ALPHATESTENABLE,	1 );
+	set_render_state( RS_ALPHABLENDENABLE,	1 );
+    set_render_state( RS_ALPHACUTOFF,		1 );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_SRCBLEND,         D3DBLEND_ONE );
+    EngineGlobals.p_Device->SetRenderState( D3DRS_DESTBLEND,        D3DBLEND_INVSRCALPHA );
+
+    // Render the screen-aligned quadrilateral
+	set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_TEX4 );
+	EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_QUADSTRIP, 1, v, sizeof( VERTEX ));
+
+    // Reset render states
+    set_render_state( RS_ALPHATESTENABLE,	1 );
+	set_render_state( RS_ALPHABLENDENABLE,	1 );
+	set_render_state( RS_ZWRITEENABLE,		1 );
+	set_render_state( RS_ZTESTENABLE,		1 );
+
+	set_pixel_shader( 0 );
+
+	set_texture( 0, NULL );
+	set_texture( 1, NULL );
+	set_texture( 2, NULL );
+	set_texture( 3, NULL );
+
+    return S_OK;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void set_focus_blur_focus( Mth::Vector & focal_point, float offset, float near_depth, float far_depth )
+{
+	Mth::Vector diff		= focal_point - Mth::Vector( EngineGlobals.cam_position[0], EngineGlobals.cam_position[1], EngineGlobals.cam_position[2] );
+	Mth::Vector unit_diff	= diff.Normalize();
+
+	Mth::Vector p0			= focal_point + (( offset - near_depth ) * unit_diff );
+	Mth::Vector p1			= focal_point + (( offset + far_depth ) * unit_diff );
+
+	D3DXVECTOR4 v0( p0[X], p0[Y], p0[Z], 1.0f );
+	D3DXVECTOR4 v1( p1[X], p1[Y], p1[Z], 1.0f );
+
+    D3DXVec4Transform( &v0, &v0, (D3DXMATRIX*)&EngineGlobals.view_matrix );
+    D3DXVec4Transform( &v0, &v0, (D3DXMATRIX*)&EngineGlobals.projection_matrix );
+
+    D3DXVec4Transform( &v1, &v1, (D3DXMATRIX*)&EngineGlobals.view_matrix );
+    D3DXVec4Transform( &v1, &v1, (D3DXMATRIX*)&EngineGlobals.projection_matrix );
+    
+	m_fDepth0				= v0.z / v0.w;
+	m_fDepth1				= v1.z / v1.w;
+
+	// If a z value ends up > 1.0, it is likely from intersecting the near plane, in which case just set it to 0.
+	if( m_fDepth0 > 1.0f )
+		m_fDepth0 = 0.0f;
+
+//	printf( "%.4f %.4f\n", m_fDepth0, m_fDepth1 );
+
+	// If the two values are sufficiently close, it will cause problems since they have
+	// to get quantized down. We have to ensure that ( 1 / ( d1 - d0 )) < 16, that is ( d1 - d0 ) > ( 1 / 16 ).
+//	m_fDepth0 = m_fDepth1  - 0.0625f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void start_focus_blur( void )
+{
+	D3DDevice_SetRenderTarget( NxXbox::EngineGlobals.p_RenderSurface, NxXbox::EngineGlobals.p_ZStencilSurface );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void finish_focus_blur( void )
+{
+	// If the focus blur is active, we want to render into a the blur buffer, otherwise into the regular frame buffer.
+	if(( EngineGlobals.focus_blur > 0 ) && ( EngineGlobals.focus_blur_duration > 1 ))
+	{
+		EngineGlobals.p_Device->BlockUntilIdle();
+
+		// Store and reset the min filter for each stage.
+		DWORD min_filter[4];
+		for( int s = 0; s < 4; ++s )
+		{
+			D3DDevice_GetTextureStageState( s, D3DTSS_MINFILTER, &min_filter[s] );
+			D3DDevice_SetTextureStageState( s, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+		}
+		
+		// First step is to render the back buffer into the blur texture.
+//		fill_focus_range_texture( false );
+
+		focus_blur();
+
+//		draw_focus_effect_using_range();
+//		draw_focus_effect_using_lookup();
+		draw_focus_effect_using_planes();
+
+		// Restore the min filter for each stage.
+		for( int s = 0; s < 4; ++s )
+		{
+			D3DDevice_SetTextureStageState( s, D3DTSS_MINFILTER, min_filter[s] );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void start_screen_blur( void )
+{
+	// If the screen blur is active, we want to render into a the blur buffer, otherwise into the regular frame buffer.
+	if(( EngineGlobals.screen_blur > 0 ) && ( EngineGlobals.screen_blur_duration > 1 ))
+	{
+		D3DDevice_SetRenderTarget( NxXbox::EngineGlobals.p_BlurSurface[0], NxXbox::EngineGlobals.p_ZStencilSurface );
+	}
+	else
+	{
+		D3DDevice_SetRenderTarget( NxXbox::EngineGlobals.p_RenderSurface, NxXbox::EngineGlobals.p_ZStencilSurface );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void finish_screen_blur( void )
+{
+	// If the screen blur is active, we want to render into a the blur buffer, otherwise into the regular frame buffer.
+	if(( EngineGlobals.screen_blur > 0 ) && ( EngineGlobals.screen_blur_duration > 1 ))
+	{
+		EngineGlobals.p_Device->BlockUntilIdle();
+
+		// Now that everything has been drawn, set the backbuffer as the rendertarget, and draw the poly on top of it.
+		D3DDevice_SetRenderTarget( NxXbox::EngineGlobals.p_RenderSurface, NxXbox::EngineGlobals.p_ZStencilSurface );
+
+		NxXbox::set_blend_mode( NxXbox::vBLEND_MODE_BLEND );
+	
+		// Turn on clamping so that the linear textures work
+		NxXbox::set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+
+		// Use a default vertex and pixel shader
+		NxXbox::set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 );
+		NxXbox::set_pixel_shader( PixelShader4 );
+
+		// Select the texture (flush first, since the blur texture is linear).
+		NxXbox::set_texture( 0, NULL );
+		NxXbox::set_texture( 0, (IDirect3DTexture8*)NxXbox::EngineGlobals.p_BlurSurface[0] );
+
+		// Setup up the vertices.
+		struct sBlurVert
+		{
+			float	sx,sy,sz;
+			float	rhw;
+			uint32	color;
+			float	tu,tv;
+		};
+	
+		sBlurVert vertices[4];
+
+		uint32 alpha		= ( 0xFF - EngineGlobals.screen_blur ) / 2;
+		alpha				= ( alpha < 0x20 ) ? 0x20 : alpha;
+		
+		vertices[0].sx		= 0;
+		vertices[0].sy		= 0;
+		vertices[0].sz		= 0.0f;
+		vertices[0].rhw		= 1.0f;
+		vertices[0].color	= ( alpha << 24 ) | 0x808080;
+		vertices[0].tu		= 0.0f;
+		vertices[0].tv		= 0.0f;
+
+		vertices[1]			= vertices[0];
+		vertices[1].sx		= 640;
+		vertices[1].tu		= 640.0f;
+
+		vertices[2]			= vertices[0];
+		vertices[2].sy		= 480;
+		vertices[2].tv		= 480.0f;
+
+		vertices[3]			= vertices[1];
+		vertices[3].sy		= vertices[2].sy;
+		vertices[3].tv		= vertices[2].tv;
+
+		// Adjust if we are in letterbox mode.
+		if( NxXbox::EngineGlobals.letterbox_active )
+		{
+			vertices[0].sy	+= NxXbox::EngineGlobals.backbuffer_height / 8;
+			vertices[1].sy	+= NxXbox::EngineGlobals.backbuffer_height / 8;
+			vertices[0].tv	+= (float)( NxXbox::EngineGlobals.backbuffer_height / 8 );
+			vertices[1].tv	+= (float)( NxXbox::EngineGlobals.backbuffer_height / 8 );
+
+			vertices[2].sy	-= NxXbox::EngineGlobals.backbuffer_height / 8;
+			vertices[3].sy	-= NxXbox::EngineGlobals.backbuffer_height / 8;
+			vertices[2].tv	-= (float)( NxXbox::EngineGlobals.backbuffer_height / 8 );
+			vertices[3].tv	-= (float)( NxXbox::EngineGlobals.backbuffer_height / 8 );
+		}
+
+		// Draw the vertices.
+		set_render_state( RS_CULLMODE,		D3DCULL_NONE );
+		set_render_state( RS_ZWRITEENABLE,	0 );
+		set_render_state( RS_ZTESTENABLE,	0 );
+
+		D3DDevice_DrawVerticesUP( D3DPT_TRIANGLESTRIP, 4, vertices, sizeof( sBlurVert ));
+
+		// Reflush linear texture.
+		NxXbox::set_texture( 0, NULL );
+	}
+}
+
+
+} // namespace NxXbox
diff --git a/Code/Gfx/XBox/NX/screenfx.h b/Code/Gfx/XBox/NX/screenfx.h
new file mode 100644
index 0000000..e4f30bc
--- /dev/null
+++ b/Code/Gfx/XBox/NX/screenfx.h
@@ -0,0 +1,16 @@
+#ifndef __SCREENFX_H
+#define __SCREENFX_H
+
+namespace NxXbox
+{
+	void	start_screen_blur( void );
+	void	finish_screen_blur( void );
+
+	void	start_focus_blur( void );
+	void	finish_focus_blur( void );
+	void	set_focus_blur_focus( Mth::Vector & focal_point, float offset, float near_depth, float far_depth );
+
+	void	draw_rain( void );
+} // namespace NxXbox
+
+#endif // __SCREENFX_H
diff --git a/Code/Gfx/XBox/NX/sprite.cpp b/Code/Gfx/XBox/NX/sprite.cpp
new file mode 100644
index 0000000..72c237c
--- /dev/null
+++ b/Code/Gfx/XBox/NX/sprite.cpp
@@ -0,0 +1,497 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "scene.h"
+#include "render.h"
+#include "sprite.h"
+
+extern DWORD PixelShader4;
+extern DWORD PixelShader5;
+
+namespace NxXbox
+{
+
+
+/******************************************************************/
+/*                                                                */
+/* SDraw2D														  */
+/*                                                                */
+/******************************************************************/
+
+SDraw2D *SDraw2D::sp_2D_draw_list = NULL;
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SDraw2D::SDraw2D( float pri, bool hide )
+{
+	m_hidden	= hide;
+	m_pri		= pri;
+	m_zvalue	= 0.0f;
+
+	mp_next = NULL;
+
+	// add to draw list
+	if( !m_hidden )
+	{
+		InsertDrawList();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+SDraw2D::~SDraw2D()
+{
+	// Try removing from draw list
+	RemoveDrawList();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SDraw2D::SetPriority( float pri )
+{
+	if( m_pri != pri )
+	{
+		m_pri = pri;
+
+		// By removing and re-inserting, we re-sort the list
+		if( !m_hidden )
+		{
+			RemoveDrawList();
+			InsertDrawList();
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SDraw2D::SetZValue( float z )
+{
+	m_zvalue = z;
+
+	if( z > 0.0f )
+	{
+		// Set the priority to zero so it will always draw before everything else.
+		SetPriority( 0.0f );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SDraw2D::SetHidden( bool hide )
+{
+	if (m_hidden != hide)
+	{
+		m_hidden = hide;
+		if (hide)
+		{
+			RemoveDrawList();
+		} else {
+			InsertDrawList();
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SDraw2D::DrawAll( void )
+{
+	static uint32 z_test_required = 0;
+	
+	set_blend_mode( vBLEND_MODE_BLEND );
+	set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+	set_render_state( RS_ZBIAS, 0 );
+
+	// Set the alpha cutoff value.
+	set_render_state( RS_ALPHACUTOFF, 1 );
+
+	set_render_state( RS_ZWRITEENABLE,	0 );
+	set_texture( 1, NULL );
+	set_texture( 2, NULL );
+	set_texture( 3, NULL );
+
+	if( EngineGlobals.color_sign[0] != ( D3DTSIGN_RUNSIGNED | D3DTSIGN_GUNSIGNED | D3DTSIGN_BUNSIGNED ))
+	{
+		EngineGlobals.color_sign[0] = ( D3DTSIGN_RUNSIGNED | D3DTSIGN_GUNSIGNED | D3DTSIGN_BUNSIGNED );
+		D3DDevice_SetTextureStageState( 0, D3DTSS_COLORSIGN, D3DTSIGN_RUNSIGNED | D3DTSIGN_GUNSIGNED | D3DTSIGN_BUNSIGNED );
+	}
+
+	// Unfortunately, now that we have 3D text, we may need to enable the z test for some strings.
+	set_render_state( RS_ZTESTENABLE,	z_test_required );
+
+	SDraw2D *pDraw	= sp_2D_draw_list;
+	uint32	z_test	= 0;
+
+	while( pDraw )
+	{
+		if (!pDraw->m_hidden)
+		{
+			pDraw->BeginDraw();
+			pDraw->Draw();
+			pDraw->EndDraw();
+
+			if(( z_test == 0 ) && ( pDraw->GetZValue() > 0.0f ))
+			{
+				// There is at least one peice of text with nonzero z, so we need to z test.
+				z_test = 1;
+			}
+		}
+		pDraw = pDraw->mp_next;
+	}
+
+	z_test_required = z_test;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SDraw2D::InsertDrawList( void )
+{
+	if( !sp_2D_draw_list || ( m_pri <= sp_2D_draw_list->m_pri ))
+	{
+		// Empty or start of list.
+		mp_next			= sp_2D_draw_list;
+		sp_2D_draw_list	= this;
+	}
+	else
+	{
+		SDraw2D *p_cur	= sp_2D_draw_list;
+	
+		// Find where to insert.
+		while( p_cur->mp_next )
+		{
+			if( m_pri <= p_cur->mp_next->m_pri )
+				break;
+
+			p_cur		= p_cur->mp_next;
+		}
+
+		// Insert at this point.
+		mp_next			= p_cur->mp_next;
+		p_cur->mp_next	= this;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SDraw2D::RemoveDrawList( void )
+{
+	// Take out from draw list
+	if (sp_2D_draw_list == this)
+	{
+		sp_2D_draw_list = mp_next;
+	} 
+	else if (sp_2D_draw_list)
+	{
+		SDraw2D *p_cur = sp_2D_draw_list;
+
+		while(p_cur->mp_next)
+		{
+			if (p_cur->mp_next == this)
+			{
+				p_cur->mp_next = mp_next;
+				break;
+			}
+
+			p_cur = p_cur->mp_next;
+		}
+	}
+}
+
+	
+
+typedef struct
+{
+	float		x, y, z;
+	float		rhw;
+	D3DCOLOR	col;
+	float		u, v;
+}
+sSpriteVert;
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sSprite::sSprite( float pri ) : SDraw2D( pri, true )
+{
+	mp_texture = NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sSprite::~sSprite()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSprite::BeginDraw( void )
+{
+	set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+
+	if( mp_texture )
+	{
+		set_pixel_shader( PixelShader4 );
+		set_texture( 0, mp_texture->pD3DTexture, mp_texture->pD3DPalette );
+	}
+	else
+	{
+		set_pixel_shader( PixelShader5 );
+		set_texture( 0, NULL );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSprite::Draw( void )
+{
+	// Sprites are based on .img files, which in turn are converted from .png files, which are upside down,
+	// so reverse the v components of the texture coordinates.
+	float u0 = 0.0f;
+	float v0 = 1.0f;
+	float u1, v1;
+
+	if( mp_texture )
+	{
+		u1	= (float)mp_texture->ActualWidth / (float)mp_texture->BaseWidth;
+		v1	= 1.0f - ((float)mp_texture->ActualHeight / (float)mp_texture->BaseHeight );
+	}
+	else
+	{
+		u1	= 1.0f;
+		v1	= 0.0f;
+	}
+
+	// Check for flip.
+	float abs_scale_x = m_scale_x;
+	float abs_scale_y = m_scale_y;
+	if( abs_scale_x < 0.0f )
+	{
+		float temp = u0;
+		u0 = u1;
+		u1 = temp;
+		abs_scale_x = -abs_scale_x;
+	}
+	if( abs_scale_y < 0.0f )
+	{
+		float temp = v0;
+		v0 = v1;
+		v1 = temp;
+		abs_scale_y = -abs_scale_y;
+	}
+
+	float x0 = -( m_xhot * abs_scale_x );
+	float y0 = -( m_yhot * abs_scale_y );
+	float x1 = x0 + ( m_width * abs_scale_x );
+	float y1 = y0 + ( m_height * abs_scale_y );
+
+	DWORD	current_color	= ( m_rgba & 0xFF00FF00 ) | (( m_rgba & 0xFF ) << 16 ) | (( m_rgba & 0xFF0000 ) >> 16 );
+	DWORD*	p_push;
+
+	if( m_rot != 0.0f )
+	{
+		Mth::Vector p0( x0, y0, 0.0f, 0.0f );
+		Mth::Vector p1( x1, y0, 0.0f, 0.0f );
+		Mth::Vector p2( x0, y1, 0.0f, 0.0f );
+		Mth::Vector p3( x1, y1, 0.0f, 0.0f );
+
+		p0.RotateZ( m_rot );
+		p1.RotateZ( m_rot );
+		p2.RotateZ( m_rot );
+		p3.RotateZ( m_rot );
+
+		p0[X]	= SCREEN_CONV_X( p0[X] + m_xpos );
+		p0[Y]	= SCREEN_CONV_Y( p0[Y] + m_ypos );
+		p1[X]	= SCREEN_CONV_X( p1[X] + m_xpos );
+		p1[Y]	= SCREEN_CONV_Y( p1[Y] + m_ypos );
+		p2[X]	= SCREEN_CONV_X( p2[X] + m_xpos );
+		p2[Y]	= SCREEN_CONV_Y( p2[Y] + m_ypos );
+		p3[X]	= SCREEN_CONV_X( p3[X] + m_xpos );
+		p3[Y]	= SCREEN_CONV_Y( p3[Y] + m_ypos );
+
+		// Now grab the push buffer space required.
+		p_push			= D3DDevice_BeginPush( 34 );
+		p_push[0]		= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]		= D3DPT_QUADLIST;
+		p_push[2]		= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, 28 );
+
+		// Vertex0.
+		p_push[3]		= *((uint32*)&p0[X] ); 
+		p_push[4]		= *((uint32*)&p0[Y] );
+		p_push[5]		= 0;
+		p_push[6]		= 0;
+		p_push[7]		= current_color;
+		p_push[8]		= *((uint32*)&u0 );
+		p_push[9]		= *((uint32*)&v0 );
+
+		// Vertex1.
+		p_push[10]		= *((uint32*)&p2[X] ); 
+		p_push[11]		= *((uint32*)&p2[Y] );
+		p_push[12]		= 0;
+		p_push[13]		= 0;
+		p_push[14]		= current_color;
+		p_push[15]		= *((uint32*)&u0 );
+		p_push[16]		= *((uint32*)&v1 );
+
+		// Vertex2.
+		p_push[17]		= *((uint32*)&p3[X] ); 
+		p_push[18]		= *((uint32*)&p3[Y] );
+		p_push[19]		= 0;
+		p_push[20]		= 0;
+		p_push[21]		= current_color;
+		p_push[22]		= *((uint32*)&u1 );
+		p_push[23]		= *((uint32*)&v1 );
+
+		// Vertex3.
+		p_push[24]		= *((uint32*)&p1[X] ); 
+		p_push[25]		= *((uint32*)&p1[Y] );
+		p_push[26]		= 0;
+		p_push[27]		= 0;
+		p_push[28]		= current_color;
+		p_push[29]		= *((uint32*)&u1 );
+		p_push[30]		= *((uint32*)&v0 );
+	}
+	else
+	{
+		x0 += m_xpos;
+		y0 += m_ypos;
+		x1 += m_xpos;
+		y1 += m_ypos;
+
+		// Nasty hack - if the sprite is intended to cover the screen from top to bottom or left to right,
+		// bypass the addtional offset added by SCREEN_CONV.
+		if(( x0 <= 0.0f ) && ( x1 >= 640.0f ))
+		{
+			x0 = 0.0f;
+			x1 = (float)NxXbox::EngineGlobals.backbuffer_width;
+		}
+		else
+		{
+			x0 = SCREEN_CONV_X( x0 );
+			x1 = SCREEN_CONV_X( x1 );
+		}
+
+		if(( y0 <= 0.0f ) && ( y1 >= 480.0f ))
+		{
+			y0 = 0.0f;
+			y1 = (float)NxXbox::EngineGlobals.backbuffer_height;
+		}
+		else
+		{
+			y0 = SCREEN_CONV_Y( y0 );
+			y1 = SCREEN_CONV_Y( y1 );
+		}
+
+
+		// Now grab the push buffer space required.
+		p_push			= D3DDevice_BeginPush( 34 );
+		p_push[0]		= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]		= D3DPT_QUADLIST;
+		p_push[2]		= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, 28 );
+
+		// Vertex0.
+		p_push[3]		= *((uint32*)&x0 ); 
+		p_push[4]		= *((uint32*)&y0 );
+		p_push[5]		= 0;
+		p_push[6]		= 0;
+		p_push[7]		= current_color;
+		p_push[8]		= *((uint32*)&u0 );
+		p_push[9]		= *((uint32*)&v0 );
+
+		// Vertex1.
+		p_push[10]		= *((uint32*)&x0 ); 
+		p_push[11]		= *((uint32*)&y1 );
+		p_push[12]		= 0;
+		p_push[13]		= 0;
+		p_push[14]		= current_color;
+		p_push[15]		= *((uint32*)&u0 );
+		p_push[16]		= *((uint32*)&v1 );
+
+		// Vertex2.
+		p_push[17]		= *((uint32*)&x1 ); 
+		p_push[18]		= *((uint32*)&y1 );
+		p_push[19]		= 0;
+		p_push[20]		= 0;
+		p_push[21]		= current_color;
+		p_push[22]		= *((uint32*)&u1 );
+		p_push[23]		= *((uint32*)&v1 );
+
+		// Vertex3.
+		p_push[24]		= *((uint32*)&x1 ); 
+		p_push[25]		= *((uint32*)&y0 );
+		p_push[26]		= 0;
+		p_push[27]		= 0;
+		p_push[28]		= current_color;
+		p_push[29]		= *((uint32*)&u1 );
+		p_push[30]		= *((uint32*)&v0 );
+	}
+
+	// End of vertex data for this sprite.
+	p_push[31] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+	p_push[32] = 0;
+	p_push += 33;
+	D3DDevice_EndPush( p_push );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sSprite::EndDraw( void )
+{
+	// Vertices have been submitted - nothing more to do.
+}
+
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/sprite.h b/Code/Gfx/XBox/NX/sprite.h
new file mode 100644
index 0000000..e22dd99
--- /dev/null
+++ b/Code/Gfx/XBox/NX/sprite.h
@@ -0,0 +1,76 @@
+#ifndef __SPRITE_H
+#define __SPRITE_H
+
+#include "texture.h"
+
+namespace NxXbox
+{
+
+struct SDraw2D
+{
+					SDraw2D( float pri = 0.0f, bool hide = true );
+	virtual			~SDraw2D( void );
+
+	void			SetPriority( float pri );
+	float			GetPriority( void ) const;
+	void			SetZValue( float z );
+	float			GetZValue( void )			{ return m_zvalue; }
+
+	void			SetHidden( bool hide );
+	bool			IsHidden( void ) const;
+
+	// members
+	SDraw2D			*mp_next;
+
+	// Statics
+	static void		DrawAll( void );
+
+private:
+	void			InsertDrawList( void );
+	void			RemoveDrawList( void );
+
+	virtual void	BeginDraw( void ) = 0;
+	virtual void	Draw( void ) = 0;
+	virtual void	EndDraw( void ) = 0;
+
+	// Not even the derived classes should have direct access
+	bool			m_hidden;
+	float			m_pri;
+	float			m_zvalue;
+
+	// 2D draw list (sorted by priority);
+	static SDraw2D	*sp_2D_draw_list;
+};
+
+
+struct sSprite : public SDraw2D
+{
+	public:
+					sSprite( float pri = 0.0f );
+					~sSprite();
+
+	sTexture		*mp_texture;
+
+	float			m_xpos;
+	float			m_ypos;
+	uint16			m_width;
+	uint16			m_height;
+	float			m_scale_x;
+	float			m_scale_y;
+	float			m_xhot;
+	float			m_yhot;
+	float			m_rot;
+	uint32			m_rgba;
+
+private:
+	
+	void					BeginDraw();
+	void					Draw();
+	void					EndDraw(void);
+};
+
+
+} // namespace NxXbox
+
+
+#endif // __SPRITE_H
diff --git a/Code/Gfx/XBox/NX/swizzleformat.h b/Code/Gfx/XBox/NX/swizzleformat.h
new file mode 100644
index 0000000..c511053
--- /dev/null
+++ b/Code/Gfx/XBox/NX/swizzleformat.h
@@ -0,0 +1,71 @@
+//////////////////////////////////////////////////////////////////////
+// Swizzled to linear and back format mapping.
+//
+//  Copyright (C) 2001 Microsoft Corporation
+//  All rights reserved.
+//////////////////////////////////////////////////////////////////////
+inline D3DFORMAT MapLinearToSwizzledFormat(D3DFORMAT fmt)
+{
+    switch (fmt)
+    {
+    case D3DFMT_LIN_A1R5G5B5:     return D3DFMT_A1R5G5B5;
+    case D3DFMT_LIN_A4R4G4B4:     return D3DFMT_A4R4G4B4;
+    case D3DFMT_LIN_A8:         return D3DFMT_A8;
+    case D3DFMT_LIN_A8B8G8R8:     return D3DFMT_A8B8G8R8;
+    case D3DFMT_LIN_A8R8G8B8:     return D3DFMT_A8R8G8B8;
+    case D3DFMT_LIN_B8G8R8A8:     return D3DFMT_B8G8R8A8;
+    case D3DFMT_LIN_G8B8:         return D3DFMT_G8B8;
+    case D3DFMT_LIN_R4G4B4A4:     return D3DFMT_R4G4B4A4;
+    case D3DFMT_LIN_R5G5B5A1:     return D3DFMT_R5G5B5A1;
+    case D3DFMT_LIN_R5G6B5:     return D3DFMT_R5G6B5;
+    case D3DFMT_LIN_R6G5B5:     return D3DFMT_R6G5B5;
+    case D3DFMT_LIN_R8B8:         return D3DFMT_R8B8;
+    case D3DFMT_LIN_R8G8B8A8:     return D3DFMT_R8G8B8A8;
+    case D3DFMT_LIN_X1R5G5B5:     return D3DFMT_X1R5G5B5;
+    case D3DFMT_LIN_X8R8G8B8:     return D3DFMT_X8R8G8B8;
+    case D3DFMT_LIN_A8L8:         return D3DFMT_A8L8;
+    case D3DFMT_LIN_AL8:         return D3DFMT_AL8;
+    case D3DFMT_LIN_L16:         return D3DFMT_L16;
+    case D3DFMT_LIN_L8:         return D3DFMT_L8;
+    case D3DFMT_LIN_V16U16:     return D3DFMT_V16U16;
+    case D3DFMT_LIN_D24S8:         return D3DFMT_D24S8;
+    case D3DFMT_LIN_F24S8:         return D3DFMT_F24S8;
+    case D3DFMT_LIN_D16:         return D3DFMT_D16;
+    case D3DFMT_LIN_F16:         return D3DFMT_F16;
+    default:
+        return fmt;
+    }
+}
+
+inline D3DFORMAT MapSwizzledToLinearFormat(D3DFORMAT fmt)
+{
+    switch (fmt)
+    {
+    case D3DFMT_A1R5G5B5:     return D3DFMT_LIN_A1R5G5B5;
+    case D3DFMT_A4R4G4B4:     return D3DFMT_LIN_A4R4G4B4;
+    case D3DFMT_A8:         return D3DFMT_LIN_A8;
+    case D3DFMT_A8B8G8R8:     return D3DFMT_LIN_A8B8G8R8;
+    case D3DFMT_A8R8G8B8:     return D3DFMT_LIN_A8R8G8B8;
+    case D3DFMT_B8G8R8A8:     return D3DFMT_LIN_B8G8R8A8;
+    case D3DFMT_G8B8:         return D3DFMT_LIN_G8B8;
+    case D3DFMT_R4G4B4A4:     return D3DFMT_LIN_R4G4B4A4;
+    case D3DFMT_R5G5B5A1:     return D3DFMT_LIN_R5G5B5A1;
+    case D3DFMT_R5G6B5:     return D3DFMT_LIN_R5G6B5;
+    case D3DFMT_R6G5B5:     return D3DFMT_LIN_R6G5B5;
+    case D3DFMT_R8B8:         return D3DFMT_LIN_R8B8;
+    case D3DFMT_R8G8B8A8:     return D3DFMT_LIN_R8G8B8A8;
+    case D3DFMT_X1R5G5B5:     return D3DFMT_LIN_X1R5G5B5;
+    case D3DFMT_X8R8G8B8:     return D3DFMT_LIN_X8R8G8B8;
+    case D3DFMT_A8L8:         return D3DFMT_LIN_A8L8;
+    case D3DFMT_AL8:         return D3DFMT_LIN_AL8;
+    case D3DFMT_L16:         return D3DFMT_LIN_L16;
+    case D3DFMT_L8:         return D3DFMT_LIN_L8;
+    case D3DFMT_V16U16:     return D3DFMT_LIN_V16U16;
+    case D3DFMT_D24S8:         return D3DFMT_LIN_D24S8;
+    case D3DFMT_F24S8:         return D3DFMT_LIN_F24S8;
+    case D3DFMT_D16:         return D3DFMT_LIN_D16;
+    case D3DFMT_F16:         return D3DFMT_LIN_F16;
+    default:
+        return fmt;
+    }
+}
diff --git a/Code/Gfx/XBox/NX/texture.cpp b/Code/Gfx/XBox/NX/texture.cpp
new file mode 100644
index 0000000..e85ed6a
--- /dev/null
+++ b/Code/Gfx/XBox/NX/texture.cpp
@@ -0,0 +1,305 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "nx_init.h"
+#include "chars.h"
+#include "texture.h"
+#include "render.h"
+
+namespace NxXbox
+{
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sTexture::sTexture()
+{
+	pD3DTexture = NULL;
+	pD3DPalette	= NULL;
+	pD3DSurface	= NULL;		// If used as a render target.
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sTexture::~sTexture()
+{
+	ULONG rr;
+
+	if( pD3DTexture )
+	{
+		rr = pD3DTexture->Release();
+		Dbg_Assert( rr == 0 );
+
+		// Ensure that this texture is no longer referenced in the EngineGlobals.
+		for( int p = 0; p < 4; ++p )
+		{
+			if( EngineGlobals.p_texture[p] == pD3DTexture )
+			{
+				set_texture( p, NULL );
+			}
+		}
+	}
+	if( pD3DPalette )
+	{
+		rr = pD3DPalette->Release();
+		Dbg_Assert( rr == 0 );
+	}
+	if( pD3DSurface )
+	{
+		rr = pD3DSurface->Release();
+		Dbg_Assert( rr == 0 );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sTexture::Set( int pass )
+{
+	// Set this texture as the active texture for a specific pass.
+	set_texture( pass, pD3DTexture, pD3DPalette );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool sTexture::SetRenderTarget( int width, int height, int depth, int z_depth )
+{
+	HRESULT		hr;
+	
+	if( pD3DTexture )
+	{
+		pD3DTexture->Release();
+	}
+	if( pD3DPalette )
+	{
+		pD3DPalette->Release();
+	}
+
+	// Create the shadow buffer (essentially just a depth buffer).
+	hr = D3DDevice_CreateTexture( width, height, 1, 0, D3DFMT_LIN_D24S8, 0, &pD3DTexture );
+	Dbg_Assert( hr == D3D_OK );
+	if( hr == D3D_OK )
+	{
+		// Set fields to reflect surface characteristics.
+		Checksum		= 0;
+		BaseWidth		= ActualWidth	= width;
+		BaseHeight		= ActualHeight	= height;
+		Levels			= 1;
+		TexelDepth		= depth;
+		PaletteDepth	= 0;
+		DXT				= 0;
+		return true;
+	}
+	return false;
+}
+
+
+
+// Eeeek - the .img contains PS2 specific register values for bit depth.
+// Use these values to convert them.
+#define PSMCT32		0x00
+#define PSMCT24		0x01
+#define PSMCT16		0x02
+#define PSMCT16S	0x0A
+#define PS_GPU24	0x12
+#define PSMT8		0x13
+#define PSMT4		0x14
+#define PSMT8H		0x1B
+#define PSMT4HL		0x24
+#define PSMT4HH		0x2C
+#define PSMZ32		0x30
+#define PSMZ24		0x31
+#define PSMZ16		0x32
+#define PSMZ16S		0x3A
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool is_power_of_two( uint32 a )
+{
+	if( a == 0 )
+	{
+		return false;
+	}
+	return (( a & ( a - 1 )) == 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sTexture *LoadTexture( const char *p_filename )
+{
+	struct sIMGHeader
+	{
+		uint32	version;
+		uint32	checksum;
+		uint32	width;
+		uint32	height;
+		uint32	bit_depth;
+		uint32	clut_bit_depth;
+		uint16	original_width;
+		uint16	original_height;
+		uint32	palette_data_size;
+	};
+
+	void *p_FH = File::Open( p_filename, "rb" );
+	
+	if( p_FH )
+	{
+		// Read header.
+		sIMGHeader header;
+		File::Read( &header, sizeof( sIMGHeader ), 1, p_FH );
+		
+		// Bits per texel and palette size.
+		switch( header.bit_depth )
+		{
+			case PSMCT32:
+				header.bit_depth = 32;
+				break;
+			case PSMCT16:
+				header.bit_depth = 16;
+				break;
+			case PSMT8:
+				header.bit_depth = 8;
+				break;
+			default:
+				Dbg_Assert( 0 );
+		}
+
+		// Bits per clut entry.
+		if(	header.bit_depth < 16 )
+		{
+			switch( header.clut_bit_depth )
+			{
+				case PSMCT32:
+					header.clut_bit_depth = 32;
+					break;
+				default:
+					Dbg_Assert( 0 );
+			}
+		}
+		else
+		{
+			header.clut_bit_depth = 0;
+		}
+		
+		{
+			// Create the texture object.
+			sTexture *p_texture = new sTexture();
+
+			// Create palette if required.
+			if( header.clut_bit_depth == 0 )
+			{
+				p_texture->pD3DPalette = NULL;
+			}
+			else
+			{
+				if( D3D_OK != D3DDevice_CreatePalette( D3DPALETTE_256, &p_texture->pD3DPalette ))
+				{
+					Dbg_Assert( 0 );
+				}
+		
+				// Read clut bitmap data.
+				D3DCOLOR *p_clut;
+				p_texture->pD3DPalette->Lock( &p_clut, 0 );
+
+				int len	= File::Read( p_clut, header.palette_data_size, 1, p_FH );
+				Dbg_MsgAssert( len == header.palette_data_size, ( "Couldn't read clut from texture file %s", p_filename ));
+			}
+
+			// Textures of width 512 and above will not have been resized. This means they cannot be in a swizzled format.
+//			bool arbitrary_texture_size = ( header.original_width >= 512 );
+			bool arbitrary_texture_size = false;
+			
+			if( !is_power_of_two( header.width ) || !is_power_of_two( header.height ))
+				arbitrary_texture_size = true;
+
+			// Create texture resource. Linear for arbitrary sized textures, swizzled for standard sizes.
+			uint32 num_bytes;
+			if( arbitrary_texture_size )
+			{
+				if( D3D_OK != D3DDevice_CreateTexture(	header.original_width,
+														header.original_height,
+														1,
+														0,
+														( header.bit_depth <= 8 ) ? D3DFMT_P8 : (( header.bit_depth == 16 ) ? D3DFMT_LIN_A1R5G5B5 : D3DFMT_LIN_A8R8G8B8 ),
+														0,
+														&p_texture->pD3DTexture ))
+				{
+					Dbg_Assert( 0 );
+				}
+				p_texture->BaseWidth	= header.original_width;
+				p_texture->BaseHeight	= header.original_height;
+				p_texture->ActualWidth	= header.original_width;
+				p_texture->ActualHeight	= header.original_height;
+				num_bytes				= ((( header.bit_depth / 8 ) * ( header.original_width ) * ( header.original_height )) + 3 ) & 0xFFFFFFFC;
+			}
+			else
+			{
+				if( D3D_OK != D3DDevice_CreateTexture(	header.width,
+														header.height,
+														1,
+														0,
+														( header.bit_depth <= 8 ) ? D3DFMT_P8 : (( header.bit_depth == 16 ) ? D3DFMT_A1R5G5B5 : D3DFMT_A8R8G8B8 ),
+														0,
+														&p_texture->pD3DTexture ))
+				{
+					Dbg_Assert( 0 );
+				}
+				p_texture->BaseWidth	= (uint16)header.width;
+				p_texture->BaseHeight	= (uint16)header.height;
+				p_texture->ActualWidth	= header.original_width;
+				p_texture->ActualHeight	= header.original_height;
+				num_bytes				= ((( header.bit_depth / 8 ) * header.width * header.height ) + 3 ) & 0xFFFFFFFC;
+			}
+	
+			// Lock the texture so we can read data into it directly.
+			D3DLOCKED_RECT locked_rect;
+			if( D3D_OK != p_texture->pD3DTexture->LockRect( 0, &locked_rect, NULL, 0 ))
+			{
+				Dbg_Assert( 0 );
+			}
+
+			// Read texture bitmap data directly into texture. 
+			int len = File::Read( locked_rect.pBits, num_bytes, 1, p_FH );
+			Dbg_MsgAssert( len == num_bytes, ( "couldn't read texture data from texture file %s", p_filename ));
+		
+			File::Close( p_FH );
+
+			// Set up some member values.
+			p_texture->PaletteDepth	= (uint8)header.clut_bit_depth;
+			p_texture->TexelDepth	= (uint8)header.bit_depth;
+			p_texture->DXT			= 0;
+			p_texture->Levels		= 1;
+			
+			return p_texture;
+		}
+	}
+	return NULL;
+}
+
+
+} // namespace NxXbox
+
diff --git a/Code/Gfx/XBox/NX/texture.h b/Code/Gfx/XBox/NX/texture.h
new file mode 100644
index 0000000..0a847f3
--- /dev/null
+++ b/Code/Gfx/XBox/NX/texture.h
@@ -0,0 +1,36 @@
+#ifndef __TEXTURE_H
+#define __TEXTURE_H
+
+#include 
+
+namespace NxXbox
+{
+
+struct sTexture
+{
+						sTexture();
+						~sTexture();
+						
+	bool				SetRenderTarget( int width, int height, int depth, int z_depth );
+	void				Set( int pass );
+
+	uint32				Checksum;
+	uint16				BaseWidth, BaseHeight;		// The size of the D3D texture (will be power of 2).
+	uint16				ActualWidth, ActualHeight;	// The size of the texture itself (may not be power of 2).
+
+	uint8				Levels;
+	uint8				TexelDepth;
+	uint8				PaletteDepth;
+	uint8				DXT;
+
+	IDirect3DTexture8*	pD3DTexture;
+	IDirect3DPalette8*	pD3DPalette;
+	IDirect3DSurface8*	pD3DSurface;
+};
+
+sTexture	*LoadTexture( const char *p_filename );
+
+} // namespace NxXbox
+
+#endif // __TEXTURE_H
+
diff --git a/Code/Gfx/XBox/NX/verlet.cpp b/Code/Gfx/XBox/NX/verlet.cpp
new file mode 100644
index 0000000..65e5641
--- /dev/null
+++ b/Code/Gfx/XBox/NX/verlet.cpp
@@ -0,0 +1,273 @@
+#include 
+#include 
+
+
+#include "verlet.h"
+#include "render.h"
+
+
+const int	NUM_PARTICLES	= 16;
+const float	PARTICLES_DIST	= 5.0f;
+
+
+sVerletSystem*				pSystem				= NULL;
+Obj::CSkeletonComponent*	pSkeletonComponent	= NULL;
+float						ground				= -110.0f;
+
+
+void CreateSystem( void )
+{
+	Obj::CCompositeObject *p_obj = (Obj::CCompositeObject *)Obj::CCompositeObjectManager::Instance()->GetObjectByID( 0 );
+	if( p_obj )
+	{
+		pSkeletonComponent = GetSkeletonComponentFromObject( p_obj );
+	}
+
+	if( pSkeletonComponent )
+	{
+		pSystem = new sVerletSystem( NUM_PARTICLES );
+
+		pSystem->m_timestep	= ( 1.0f / 60.0f );
+		pSystem->m_gravity	= Mth::Vector( 0.0f, -1200.0f, 0.0f );
+
+		// Get the skater's head position.
+		Mth::Vector bone_pos;
+		pSkeletonComponent->GetBoneWorldPosition( CRCD(0xe638eebc,"Bone_Forefinger_Tip_R"), &bone_pos );
+
+		for( int i = 0; i < NUM_PARTICLES; ++i )
+		{
+			pSystem->mp_particles[i].m_pos		= bone_pos - ( Mth::Vector( 0.0f, PARTICLES_DIST * i, 0.0f ));
+			pSystem->mp_particles[i].m_old_pos	= pSystem->mp_particles[i].m_pos;
+		}
+
+		for( int i = 0; i < ( NUM_PARTICLES - 1 ); ++i )
+		{
+			sVerletConstraint*	p_c = pSystem->AddConstraint();
+			p_c->m_particle0		= i;
+			p_c->m_particle1		= i + 1;
+			p_c->m_constraint_dist	= PARTICLES_DIST;
+		}
+	}
+}
+
+
+
+
+
+
+
+
+void UpdateVerletSystem( void )
+{
+	if( pSystem == NULL )
+	{
+		CreateSystem();
+	}
+
+	if( pSystem )
+	{
+		// Set particle 0 to always be at the skater's head position.
+		Mth::Vector bone_pos;
+		pSkeletonComponent->GetBoneWorldPosition( CRCD(0xe638eebc,"Bone_Forefinger_Tip_R"), &bone_pos );
+		pSystem->mp_particles[0].m_pos = bone_pos;
+
+		pSystem->TimeStep();
+	}
+}
+
+void DrawVerletSystem( void )
+{
+	struct sVerletDrawVert
+	{
+		float		x, y, z;
+		D3DCOLOR	col;
+	};
+
+	if( pSystem )
+	{
+		sVerletDrawVert	dv[NUM_PARTICLES];
+		for( int i = 0; i < NUM_PARTICLES; ++i )
+		{
+			dv[i].x		= pSystem->mp_particles[i].m_pos[X];
+			dv[i].y		= pSystem->mp_particles[i].m_pos[Y];
+			dv[i].z		= pSystem->mp_particles[i].m_pos[Z];
+			dv[i].col	= 0xFFFFFFFF;
+		}
+
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader5 );
+
+		NxXbox::EngineGlobals.p_Device->DrawVerticesUP( D3DPT_LINESTRIP, NUM_PARTICLES, dv, sizeof( sVerletDrawVert ));
+	}
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVerletParticle::sVerletParticle( void )
+{
+	m_pos		= Mth::Vector( 0.0f, 0.0f, 0.0f );
+	m_old_pos	= m_pos;
+	m_acc		= m_pos;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVerletParticle::~sVerletParticle( void )
+{
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVerletConstraint::Apply( sVerletParticle *p_particles )
+{
+	sVerletParticle* p_p0			= p_particles + m_particle0;
+	sVerletParticle* p_p1			= p_particles + m_particle1;
+
+	Mth::Vector		delta			= p_p1->m_pos - p_p0->m_pos;
+	float			delta_length	= delta.Length();
+	float			diff			= ( delta_length - m_constraint_dist ) / delta_length;
+
+	if( m_particle0 == 0 )
+	{
+		// Move the points.
+		p_p0->m_pos += delta * 0.0f * diff;
+		p_p1->m_pos -= delta * 1.0f * diff;
+	}
+	else
+	{
+		// Move the points.
+		p_p0->m_pos += delta * 0.5f * diff;
+		p_p1->m_pos -= delta * 0.5f * diff;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVerletSystem::sVerletSystem( int num_particles )
+{
+	m_num_particles = num_particles;
+	mp_particles	= new sVerletParticle[num_particles];
+	mp_constraints	= new Lst::Head ;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVerletSystem::~sVerletSystem( void )
+{
+	delete [] mp_particles;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sVerletConstraint* sVerletSystem::AddConstraint( void )
+{
+	sVerletConstraint*				p_constraint	= new sVerletConstraint;
+	Lst::Node*	p_node			= new Lst::Node( p_constraint );
+
+	mp_constraints->AddToTail( p_node );
+
+	return p_constraint;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVerletSystem::Verlet( void )
+{
+	for( int i = 0; i < m_num_particles; ++i )
+	{
+		Mth::Vector &x		= mp_particles[i].m_pos;
+		Mth::Vector t		= x;
+		Mth::Vector &o		= mp_particles[i].m_old_pos;
+		Mth::Vector a		= mp_particles[i].m_acc;
+		x					= ( x * 2.0f ) - o + ( a * m_timestep * m_timestep );
+		o					= t;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVerletSystem::AccumulateForces( void )
+{
+	// All particles are influenced by gravity.
+	for( int i = 0; i < m_num_particles; ++i )
+	{
+		mp_particles[i].m_acc = m_gravity;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVerletSystem::SatisfyConstraints( void )
+{
+	Lst::Node* p_node, *p_next;
+
+	for( int i = 0; i < 2; ++i )
+	{
+		// Satisfy distance constraints.
+		for( p_node = mp_constraints->GetNext(); p_node; p_node = p_next )
+		{
+			p_next = p_node->GetNext();
+			sVerletConstraint *p_constraint = p_node->GetData();
+			p_constraint->Apply( mp_particles );
+		}
+
+		// Satisfy ground constraints.
+		for( int i = 0; i < m_num_particles; ++i )
+		{
+			if( mp_particles[i].m_pos[Y] < ground )
+			{
+				mp_particles[i].m_pos[Y] = ground;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sVerletSystem::TimeStep( void )
+{
+	AccumulateForces();
+	Verlet();
+	SatisfyConstraints();
+}
diff --git a/Code/Gfx/XBox/NX/verlet.h b/Code/Gfx/XBox/NX/verlet.h
new file mode 100644
index 0000000..c24a693
--- /dev/null
+++ b/Code/Gfx/XBox/NX/verlet.h
@@ -0,0 +1,70 @@
+#ifndef __VERLET_H
+#define __VERLET_H
+
+#include 
+
+void			UpdateVerletSystem( void );
+void			DrawVerletSystem( void );
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sVerletParticle
+{
+						sVerletParticle( void );
+						~sVerletParticle( void );
+
+	Mth::Vector			m_pos;
+	Mth::Vector			m_old_pos;
+	Mth::Vector			m_acc;
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sVerletConstraint
+{
+	int					m_particle0;
+	int					m_particle1;
+	float				m_elasticity;
+	float				m_constraint_dist;
+	void				Apply( sVerletParticle *p_particles );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sVerletSystem
+{
+									sVerletSystem( int num_particles );
+									~sVerletSystem();
+
+	void							TimeStep( void );
+	void							Verlet( void );
+	void							SatisfyConstraints( void );
+	void							AccumulateForces( void );
+
+	sVerletConstraint*				AddConstraint( void );	
+
+	int								m_num_particles;
+	sVerletParticle*				mp_particles;
+	Mth::Vector						m_gravity;
+	float							m_timestep;
+
+	Lst::Head *	mp_constraints;
+};
+
+
+
+
+
+#endif // __NX_INIT_H
diff --git a/Code/Gfx/XBox/NX/xbmemfnt.h b/Code/Gfx/XBox/NX/xbmemfnt.h
new file mode 100644
index 0000000..0ab1671
--- /dev/null
+++ b/Code/Gfx/XBox/NX/xbmemfnt.h
@@ -0,0 +1,5251 @@
+
+#define XBMEMFNT_LEN 68168
+
+ static unsigned char xbmemfnt[]=
+ {
+  0x48,0x0a,0x01,0x00,0x83,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x13,
+  0x00,0x00,0x00,0x10,0x00,0x41,0x00,0x10,0x00,0x42,0x00,0x10,0x00,
+  0x43,0x00,0x10,0x00,0x44,0x00,0x10,0x00,0x45,0x00,0x10,0x00,0x46,
+  0x00,0x10,0x00,0x47,0x00,0x10,0x00,0x48,0x00,0x10,0x00,0x49,0x00,
+  0x10,0x00,0x4a,0x00,0x10,0x00,0x4b,0x00,0x10,0x00,0x4c,0x00,0x10,
+  0x00,0x4d,0x00,0x10,0x00,0x4e,0x00,0x10,0x00,0x4f,0x00,0x10,0x00,
+  0x50,0x00,0x10,0x00,0x51,0x00,0x10,0x00,0x52,0x00,0x10,0x00,0x53,
+  0x00,0x10,0x00,0x54,0x00,0x10,0x00,0x55,0x00,0x10,0x00,0x56,0x00,
+  0x10,0x00,0x57,0x00,0x10,0x00,0x58,0x00,0x10,0x00,0x59,0x00,0x10,
+  0x00,0x5a,0x00,0x0d,0x00,0x61,0x00,0x10,0x00,0x62,0x00,0x0d,0x00,
+  0x63,0x00,0x10,0x00,0x64,0x00,0x0d,0x00,0x65,0x00,0x10,0x00,0x66,
+  0x00,0x0d,0x00,0x67,0x00,0x10,0x00,0x68,0x00,0x10,0x00,0x69,0x00,
+  0x10,0x00,0x6a,0x00,0x10,0x00,0x6b,0x00,0x10,0x00,0x6c,0x00,0x0d,
+  0x00,0x6d,0x00,0x0d,0x00,0x6e,0x00,0x0d,0x00,0x6f,0x00,0x0d,0x00,
+  0x70,0x00,0x0d,0x00,0x71,0x00,0x0d,0x00,0x72,0x00,0x0d,0x00,0x73,
+  0x00,0x0f,0x00,0x74,0x00,0x0d,0x00,0x75,0x00,0x0d,0x00,0x76,0x00,
+  0x0d,0x00,0x77,0x00,0x0d,0x00,0x78,0x00,0x0d,0x00,0x79,0x00,0x0d,
+  0x00,0x7a,0x00,0x0f,0x00,0x30,0x00,0x0f,0x00,0x31,0x00,0x0f,0x00,
+  0x32,0x00,0x0f,0x00,0x33,0x00,0x0f,0x00,0x34,0x00,0x0f,0x00,0x35,
+  0x00,0x10,0x00,0x36,0x00,0x0f,0x00,0x37,0x00,0x0f,0x00,0x38,0x00,
+  0x10,0x00,0x39,0x00,0x08,0x00,0x2e,0x00,0x08,0x00,0x2c,0x00,0x0a,
+  0x00,0x2d,0x00,0x10,0x00,0x21,0x00,0x10,0x00,0x3f,0x00,0x0d,0x00,
+  0x3a,0x00,0x10,0x00,0x27,0x00,0x0e,0x00,0x2b,0x00,0x10,0x00,0x2f,
+  0x00,0x10,0x00,0x5e,0x00,0x10,0x00,0xae,0x00,0x10,0x00,0x28,0x00,
+  0x10,0x00,0x29,0x00,0x10,0x00,0x2a,0x00,0x10,0x00,0x40,0x00,0x12,
+  0x00,0x60,0x00,0x10,0x00,0xa1,0x00,0x0f,0x00,0xa2,0x00,0x0f,0x00,
+  0xa3,0x00,0x0f,0x00,0xa4,0x00,0x0f,0x00,0xa5,0x00,0x10,0x00,0xa6,
+  0x00,0x10,0x00,0xa7,0x00,0x11,0x00,0xa8,0x00,0x10,0x00,0xa9,0x00,
+  0x10,0x00,0xaa,0x00,0x0b,0x00,0xab,0x00,0x0b,0x00,0xac,0x00,0x10,
+  0x00,0x7b,0x00,0x02,0x00,0x5f,0x00,0x10,0x00,0x23,0x00,0x10,0x00,
+  0x24,0x00,0x10,0x00,0x25,0x00,0x10,0x00,0x26,0x00,0x11,0x00,0x5c,
+  0x00,0x0c,0x00,0x3d,0x00,0x0e,0x00,0x3c,0x00,0x0e,0x00,0x3e,0x00,
+  0x10,0x00,0xdf,0x00,0x13,0x00,0xc4,0x00,0x13,0x00,0xdc,0x00,0x13,
+  0x00,0xd6,0x00,0x11,0x00,0xe0,0x00,0x11,0x00,0xe2,0x00,0x13,0x00,
+  0xc4,0x00,0x11,0x00,0xea,0x00,0x12,0x00,0xe8,0x00,0x12,0x00,0xe9,
+  0x00,0x11,0x00,0xeb,0x00,0x11,0x00,0xec,0x00,0x11,0x00,0xee,0x00,
+  0x11,0x00,0xef,0x00,0x11,0x00,0xf4,0x00,0x11,0x00,0xf2,0x00,0x13,
+  0x00,0xd6,0x00,0x12,0x00,0xf9,0x00,0x11,0x00,0xfb,0x00,0x13,0x00,
+  0xdc,0x00,0x0d,0x00,0xe7,0x00,0x0d,0x00,0x9c,0x00,0x11,0x00,0xfc,
+  0x00,0x11,0x00,0xe4,0x00,0x11,0x00,0xf6,0x00,0x10,0x00,0xbc,0x00,
+  0x10,0x00,0xbd,0x00,0x10,0x00,0xbe,0x00,0x10,0x00,0xbf,0x00,0x10,
+  0x00,0xba,0x00,0x10,0x00,0xe0,0xff,0x2c,0x08,0x01,0x00,0x00,0x01,
+  0x00,0x01,0x08,0x00,0xfc,0xfe,0x12,0x00,0xdf,0xf2,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,
+  0x35,0x35,0x35,0x35,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x20,0x35,0x35,0x35,0x35,0x35,0x35,0x20,0x00,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x08,0x30,0x39,0x0f,
+  0x2f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x0e,0x35,0x35,
+  0x35,0x35,0x35,0x20,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,
+  0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x34,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x0e,0x35,0x35,0x35,0x35,0x35,0x35,0x0e,0x34,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x2e,0x3a,0x1b,0x3a,0x2e,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x35,0x35,0x35,0x35,
+  0x01,0x00,0x2b,0x20,0x35,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x20,
+  0x35,0x20,0x2b,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x34,0x35,0x35,0x35,0x35,0x01,0x01,0x01,0x01,0x01,0x35,
+  0x35,0x35,0x35,0x00,0x01,0x01,0x35,0x35,0x20,0x0e,0x20,0x01,0x01,
+  0x01,0x01,0x01,0x34,0x35,0x35,0x35,0x20,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x0e,0x35,0x35,0x35,0x35,0x35,0x34,0x01,0x20,
+  0x0e,0x35,0x35,0x35,0x0e,0x00,0x01,0x01,0x01,0x01,0x01,0x00,0x35,
+  0x35,0x35,0x35,0x35,0x00,0x01,0x00,0x35,0x35,0x0e,0x00,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x34,0x3c,0x0f,0x11,0x37,0x3c,0x34,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x08,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x01,0x01,0x01,0x01,
+  0x17,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x17,0x01,0x01,
+  0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x2b,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x2c,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x3d,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x2c,0x01,0x01,0x01,0x3d,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x3d,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x2b,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x20,0x07,0x1e,0x2d,0x2d,0x09,0x09,0x2d,0x2d,0x1e,0x32,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x09,0x2d,0x09,0x09,0x09,0x09,
+  0x2d,0x09,0x1e,0x1e,0x07,0x2b,0x01,0x01,0x01,0x01,0x08,0x1e,0x1e,
+  0x09,0x2a,0x29,0x0d,0x26,0x31,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x0f,
+  0x1e,0x18,0x10,0x09,0x09,0x09,0x09,0x2d,0x09,0x1e,0x1e,0x40,0x2b,
+  0x01,0x01,0x17,0x1e,0x1e,0x2d,0x2d,0x09,0x09,0x09,0x09,0x2d,0x2d,
+  0x18,0x1e,0x30,0x01,0x20,0x07,0x1e,0x10,0x09,0x09,0x09,0x09,0x09,
+  0x09,0x10,0x18,0x1e,0x1c,0x01,0x01,0x01,0x17,0x1e,0x1e,0x09,0x28,
+  0x15,0x15,0x15,0x28,0x1e,0x1e,0x1e,0x17,0x01,0x01,0x01,0x30,0x1e,
+  0x18,0x2d,0x2d,0x2d,0x2d,0x1e,0x1e,0x21,0x09,0x2d,0x2d,0x1e,0x38,
+  0x01,0x01,0x38,0x1e,0x09,0x2d,0x09,0x21,0x1e,0x1e,0x2f,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,0x18,0x2d,0x09,0x2d,0x2d,0x1e,
+  0x38,0x01,0x38,0x1e,0x2d,0x2d,0x2d,0x2d,0x1e,0x1e,0x1e,0x2d,0x2d,
+  0x09,0x10,0x09,0x1e,0x3b,0x01,0x1c,0x1e,0x18,0x2d,0x09,0x2d,0x09,
+  0x1e,0x3e,0x34,0x01,0x01,0x01,0x01,0x38,0x1e,0x10,0x2d,0x09,0x09,
+  0x2d,0x2d,0x1e,0x1e,0x09,0x10,0x09,0x09,0x09,0x10,0x1e,0x1e,0x3a,
+  0x01,0x08,0x1e,0x1e,0x2d,0x2d,0x09,0x2d,0x2d,0x1e,0x1e,0x1e,0x2d,
+  0x2d,0x10,0x1e,0x1e,0x2e,0x01,0x01,0x2b,0x3e,0x1e,0x1e,0x2a,0x06,
+  0x19,0x24,0x16,0x1e,0x1e,0x1e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3c,0x1e,0x16,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x03,0x1e,0x2e,0x01,0x01,0x01,0x01,
+  0x35,0x1e,0x09,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x16,
+  0x1e,0x38,0x01,0x01,0x01,0x3c,0x1e,0x1e,0x12,0x0c,0x13,0x1f,0x1f,
+  0x13,0x05,0x14,0x2d,0x1e,0x38,0x01,0x01,0x39,0x1e,0x02,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x25,0x1e,0x38,0x01,0x01,0x0f,0x1e,
+  0x06,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x22,0x1e,0x32,0x01,
+  0x08,0x1e,0x25,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x0b,0x1e,
+  0x1d,0x01,0x01,0x35,0x40,0x1e,0x06,0x13,0x0c,0x22,0x1f,0x1f,0x1f,
+  0x13,0x26,0x1e,0x33,0x0e,0x01,0x01,0x32,0x1e,0x22,0x0c,0x13,0x05,
+  0x14,0x1e,0x15,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x36,0x1e,0x31,
+  0x13,0x13,0x13,0x13,0x24,0x1e,0x37,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x07,0x1e,0x05,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x1e,0x1e,
+  0x13,0x0c,0x22,0x1f,0x12,0x1e,0x02,0x0c,0x13,0x1f,0x1f,0x29,0x1e,
+  0x30,0x01,0x1d,0x1e,0x23,0x05,0x14,0x13,0x13,0x16,0x1e,0x3c,0x01,
+  0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,0x13,0x13,0x02,0x1e,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x27,0x1e,0x2c,0x01,0x1c,0x1e,0x12,
+  0x1f,0x1f,0x13,0x05,0x14,0x23,0x1e,0x06,0x13,0x0c,0x13,0x29,0x1e,
+  0x04,0x01,0x01,0x38,0x1e,0x2a,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x15,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3a,0x1e,0x26,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x0d,0x1e,0x2c,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,0x1f,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,
+  0x01,0x04,0x1e,0x24,0x13,0x0b,0x1f,0x0c,0x05,0x0c,0x0c,0x13,0x1f,
+  0x1e,0x1e,0x2f,0x01,0x37,0x1e,0x06,0x23,0x14,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x10,0x1e,0x1b,0x01,0x3c,0x1e,0x16,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x27,0x1e,0x2c,0x01,0x36,0x1e,0x31,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x27,0x1e,0x2c,0x01,0x01,0x3a,
+  0x1e,0x25,0x0c,0x0c,0x0c,0x13,0x1f,0x14,0x0c,0x13,0x0b,0x0a,0x1e,
+  0x11,0x01,0x01,0x30,0x1e,0x24,0x14,0x13,0x05,0x02,0x1e,0x25,0x0c,
+  0x23,0x14,0x13,0x1e,0x1e,0x01,0x35,0x1e,0x2d,0x05,0x13,0x13,0x13,
+  0x0a,0x1e,0x3b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,0x0d,
+  0x13,0x13,0x13,0x0c,0x1e,0x33,0x01,0x1e,0x1e,0x0c,0x0c,0x13,0x13,
+  0x2a,0x1e,0x13,0x0b,0x1f,0x0c,0x05,0x1e,0x1e,0x3b,0x01,0x1c,0x1e,
+  0x24,0x05,0x02,0x14,0x13,0x21,0x1e,0x2b,0x01,0x01,0x01,0x01,0x1e,
+  0x1e,0x13,0x13,0x13,0x13,0x13,0x13,0x29,0x1e,0x22,0x13,0x13,0x13,
+  0x13,0x13,0x26,0x1e,0x0f,0x01,0x3b,0x1e,0x0a,0x0c,0x05,0x0c,0x0c,
+  0x13,0x19,0x1e,0x2a,0x14,0x14,0x13,0x28,0x1e,0x1b,0x01,0x36,0x1e,
+  0x21,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x31,0x1e,0x0f,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2c,0x1e,0x27,0x13,0x13,0x14,0x13,0x13,0x13,0x05,0x1e,
+  0x40,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,0x0c,0x13,0x13,0x13,0x06,
+  0x12,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x3e,0x1e,0x14,
+  0x13,0x0c,0x13,0x0d,0x31,0x13,0x02,0x14,0x0c,0x06,0x1e,0x37,0x01,
+  0x37,0x1e,0x06,0x19,0x12,0x14,0x14,0x27,0x0b,0x14,0x13,0x13,0x27,
+  0x1e,0x2c,0x01,0x08,0x1e,0x25,0x13,0x13,0x13,0x13,0x14,0x23,0x14,
+  0x05,0x06,0x1e,0x0f,0x01,0x36,0x1e,0x31,0x13,0x13,0x13,0x13,0x14,
+  0x19,0x14,0x0c,0x06,0x1e,0x0f,0x01,0x01,0x39,0x1e,0x1a,0x19,0x13,
+  0x0c,0x19,0x1e,0x0c,0x0c,0x13,0x0c,0x0c,0x1e,0x33,0x01,0x01,0x3d,
+  0x1e,0x29,0x0c,0x1f,0x23,0x0c,0x1e,0x2a,0x1f,0x19,0x12,0x14,0x1e,
+  0x1e,0x01,0x35,0x1e,0x2d,0x1f,0x13,0x13,0x13,0x28,0x1e,0x2e,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x1a,0x13,0x13,0x13,0x0c,
+  0x1e,0x33,0x01,0x1e,0x1e,0x13,0x0c,0x14,0x13,0x2a,0x25,0x13,0x0c,
+  0x13,0x05,0x16,0x1e,0x38,0x01,0x01,0x1c,0x1e,0x24,0x23,0x13,0x0b,
+  0x02,0x21,0x1e,0x36,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x27,0x10,0x14,0x13,0x13,0x13,0x13,0x1f,0x15,0x1e,
+  0x0f,0x01,0x2e,0x1e,0x0a,0x05,0x1f,0x13,0x02,0x14,0x0c,0x1e,0x10,
+  0x0c,0x0c,0x1f,0x28,0x1e,0x1b,0x01,0x3a,0x1e,0x26,0x0b,0x14,0x13,
+  0x22,0x10,0x05,0x13,0x13,0x13,0x19,0x1e,0x11,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,
+  0x1f,0x13,0x13,0x16,0x05,0x13,0x13,0x13,0x1e,0x1e,0x34,0x01,0x01,
+  0x01,0x34,0x1e,0x18,0x0c,0x13,0x13,0x0c,0x1e,0x1e,0x13,0x13,0x13,
+  0x13,0x1e,0x1e,0x01,0x01,0x01,0x07,0x1e,0x22,0x0c,0x22,0x13,0x0a,
+  0x1e,0x0c,0x1f,0x13,0x0c,0x1a,0x1e,0x39,0x01,0x37,0x1e,0x06,0x0b,
+  0x0c,0x1f,0x12,0x1e,0x24,0x24,0x14,0x13,0x05,0x1e,0x07,0x01,0x08,
+  0x1e,0x25,0x13,0x13,0x13,0x13,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x17,
+  0x01,0x36,0x1e,0x31,0x13,0x13,0x13,0x1f,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x17,0x01,0x01,0x38,0x1e,0x23,0x0c,0x13,0x0c,0x24,0x1e,0x31,
+  0x1a,0x29,0x12,0x06,0x1e,0x1e,0x00,0x01,0x3d,0x1e,0x06,0x0b,0x0c,
+  0x19,0x14,0x1e,0x0a,0x14,0x0b,0x0c,0x1f,0x1e,0x1e,0x01,0x35,0x1e,
+  0x2d,0x1f,0x13,0x13,0x13,0x28,0x1e,0x2e,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x04,0x1e,0x1a,0x13,0x13,0x13,0x0c,0x1e,0x33,0x01,0x1e,
+  0x1e,0x13,0x0c,0x13,0x1f,0x25,0x27,0x0c,0x22,0x13,0x22,0x1e,0x07,
+  0x2b,0x01,0x01,0x1c,0x1e,0x12,0x19,0x05,0x05,0x14,0x21,0x1e,0x36,
+  0x01,0x01,0x01,0x01,0x1e,0x1e,0x05,0x13,0x13,0x13,0x1f,0x13,0x19,
+  0x2a,0x05,0x13,0x05,0x13,0x13,0x13,0x06,0x1e,0x0f,0x01,0x2e,0x1e,
+  0x0a,0x0c,0x0c,0x0c,0x05,0x13,0x0c,0x16,0x2d,0x27,0x0b,0x0c,0x28,
+  0x1e,0x1b,0x01,0x2c,0x1e,0x0d,0x02,0x24,0x14,0x1a,0x1e,0x12,0x05,
+  0x13,0x13,0x02,0x1e,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x17,0x1e,0x31,0x22,0x13,0x13,0x2a,
+  0x1a,0x13,0x13,0x13,0x16,0x1e,0x3b,0x01,0x01,0x01,0x34,0x1e,0x18,
+  0x0c,0x13,0x13,0x13,0x12,0x0d,0x13,0x13,0x13,0x1f,0x1e,0x40,0x01,
+  0x01,0x0e,0x1e,0x10,0x05,0x0c,0x13,0x05,0x26,0x1e,0x2a,0x16,0x16,
+  0x25,0x21,0x1e,0x3a,0x01,0x37,0x1e,0x24,0x02,0x19,0x1f,0x23,0x1e,
+  0x28,0x0c,0x14,0x27,0x0b,0x1e,0x1d,0x01,0x08,0x1e,0x25,0x13,0x13,
+  0x13,0x13,0x12,0x15,0x06,0x06,0x1e,0x1e,0x00,0x01,0x36,0x1e,0x31,
+  0x13,0x13,0x13,0x13,0x0d,0x28,0x06,0x06,0x10,0x1e,0x36,0x01,0x00,
+  0x1e,0x1e,0x0b,0x0c,0x13,0x1f,0x1a,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x3d,0x01,0x01,0x3d,0x1e,0x29,0x0c,0x05,0x22,0x0c,0x06,0x29,
+  0x0c,0x02,0x19,0x1f,0x1e,0x1e,0x01,0x35,0x1e,0x2d,0x23,0x13,0x13,
+  0x1f,0x0a,0x1e,0x2e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,
+  0x1a,0x13,0x13,0x13,0x0c,0x1e,0x33,0x01,0x1e,0x1e,0x13,0x1f,0x0c,
+  0x13,0x23,0x1f,0x0c,0x13,0x05,0x31,0x1e,0x3d,0x01,0x01,0x01,0x1c,
+  0x1e,0x24,0x22,0x0c,0x24,0x0d,0x31,0x1e,0x36,0x01,0x01,0x01,0x01,
+  0x1e,0x1e,0x26,0x22,0x13,0x0c,0x29,0x1f,0x22,0x15,0x1f,0x0c,0x02,
+  0x0c,0x13,0x0c,0x06,0x1e,0x0f,0x01,0x2e,0x1e,0x0a,0x23,0x0c,0x14,
+  0x29,0x0c,0x13,0x29,0x2a,0x1f,0x0c,0x05,0x15,0x1e,0x1b,0x01,0x3d,
+  0x1e,0x12,0x0b,0x0c,0x14,0x12,0x1e,0x29,0x26,0x22,0x13,0x14,0x1e,
+  0x3e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x3a,0x1e,0x15,0x0c,0x19,0x0b,0x2d,0x25,0x1f,0x13,0x13,
+  0x29,0x1e,0x04,0x01,0x01,0x01,0x34,0x1e,0x18,0x0c,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x0c,0x1e,0x1e,0x39,0x01,0x01,0x0e,0x1e,0x10,
+  0x1f,0x05,0x0c,0x13,0x06,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x11,0x01,
+  0x01,0x37,0x1e,0x06,0x14,0x1f,0x0d,0x29,0x1e,0x28,0x0b,0x13,0x05,
+  0x23,0x1e,0x32,0x01,0x08,0x1e,0x25,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x21,0x1e,0x2b,0x01,0x36,0x1e,0x31,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x26,0x1e,0x3a,0x01,0x20,0x1e,0x09,0x1f,0x0c,
+  0x05,0x05,0x12,0x10,0x29,0x12,0x24,0x12,0x26,0x1e,0x38,0x01,0x01,
+  0x3d,0x1e,0x06,0x0c,0x0c,0x22,0x0b,0x14,0x23,0x22,0x14,0x1f,0x0d,
+  0x1e,0x1e,0x01,0x35,0x1e,0x2d,0x14,0x19,0x0b,0x14,0x16,0x1e,0x2e,
+  0x01,0x01,0x01,0x3c,0x37,0x1b,0x1c,0x04,0x1e,0x1a,0x13,0x13,0x13,
+  0x0c,0x1e,0x33,0x01,0x1e,0x1e,0x05,0x05,0x13,0x05,0x13,0x13,0x05,
+  0x0c,0x19,0x1e,0x32,0x34,0x01,0x01,0x01,0x1c,0x1e,0x12,0x22,0x0b,
+  0x14,0x23,0x21,0x1e,0x36,0x01,0x01,0x01,0x01,0x1e,0x1e,0x1f,0x0c,
+  0x19,0x23,0x15,0x06,0x14,0x1a,0x0c,0x05,0x12,0x14,0x13,0x1f,0x06,
+  0x1e,0x0f,0x01,0x2e,0x1e,0x0a,0x05,0x0c,0x13,0x29,0x22,0x23,0x22,
+  0x0a,0x1a,0x0c,0x0c,0x28,0x1e,0x1b,0x01,0x3d,0x1e,0x29,0x27,0x0b,
+  0x13,0x27,0x1e,0x1a,0x1f,0x0c,0x19,0x0b,0x1e,0x1e,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x1e,
+  0x12,0x13,0x05,0x0b,0x1e,0x18,0x0c,0x02,0x22,0x22,0x1e,0x3e,0x01,
+  0x01,0x01,0x34,0x1e,0x18,0x0c,0x13,0x13,0x13,0x19,0x1f,0x13,0x13,
+  0x1f,0x15,0x1e,0x38,0x01,0x01,0x0e,0x1e,0x10,0x14,0x05,0x13,0x0c,
+  0x24,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x2b,0x01,0x37,0x1e,0x06,
+  0x22,0x05,0x0c,0x02,0x1e,0x15,0x0c,0x02,0x0d,0x19,0x1e,0x32,0x01,
+  0x08,0x1e,0x16,0x22,0x0c,0x13,0x13,0x13,0x13,0x13,0x13,0x31,0x1e,
+  0x36,0x01,0x36,0x1e,0x31,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x06,0x1e,0x0f,0x01,0x35,0x1e,0x2d,0x02,0x0c,0x1f,0x22,0x26,0x12,
+  0x0c,0x05,0x05,0x13,0x0c,0x1e,0x1e,0x01,0x01,0x3d,0x1e,0x29,0x05,
+  0x27,0x05,0x0b,0x06,0x27,0x14,0x22,0x05,0x0c,0x1e,0x1e,0x01,0x35,
+  0x1e,0x2d,0x1f,0x05,0x0b,0x14,0x0a,0x1e,0x2e,0x01,0x01,0x32,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1a,0x13,0x13,0x13,0x0c,0x1e,0x33,0x01,
+  0x1e,0x1e,0x1f,0x22,0x13,0x1f,0x05,0x05,0x05,0x13,0x0c,0x09,0x1e,
+  0x08,0x01,0x01,0x01,0x1c,0x1e,0x26,0x05,0x0b,0x06,0x27,0x31,0x1e,
+  0x36,0x01,0x01,0x01,0x01,0x1e,0x1e,0x02,0x13,0x05,0x19,0x21,0x0c,
+  0x0c,0x0d,0x0b,0x0b,0x15,0x0c,0x0c,0x02,0x06,0x1e,0x0f,0x01,0x2e,
+  0x1e,0x0a,0x0c,0x13,0x1f,0x06,0x29,0x05,0x1f,0x0d,0x14,0x05,0x27,
+  0x15,0x1e,0x1b,0x01,0x3d,0x1e,0x12,0x14,0x0c,0x02,0x24,0x1e,0x29,
+  0x02,0x13,0x05,0x0b,0x1e,0x1e,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x1e,0x18,0x05,0x19,0x12,0x14,
+  0x28,0x28,0x13,0x14,0x22,0x1f,0x1e,0x33,0x00,0x01,0x01,0x34,0x1e,
+  0x18,0x0c,0x13,0x13,0x13,0x09,0x18,0x0c,0x13,0x13,0x1f,0x10,0x1e,
+  0x0e,0x01,0x0e,0x1e,0x10,0x14,0x23,0x13,0x0c,0x06,0x1e,0x0d,0x23,
+  0x27,0x19,0x15,0x1e,0x37,0x01,0x37,0x1e,0x28,0x14,0x22,0x29,0x02,
+  0x1e,0x26,0x1f,0x0c,0x14,0x19,0x1e,0x1d,0x01,0x08,0x1e,0x16,0x22,
+  0x0c,0x13,0x13,0x0a,0x31,0x2a,0x2a,0x1e,0x38,0x01,0x01,0x36,0x1e,
+  0x31,0x13,0x13,0x13,0x13,0x26,0x21,0x25,0x16,0x09,0x1e,0x17,0x01,
+  0x34,0x1e,0x18,0x05,0x0c,0x0c,0x13,0x24,0x2a,0x0c,0x1f,0x23,0x13,
+  0x23,0x1e,0x1d,0x01,0x01,0x3d,0x1e,0x12,0x13,0x23,0x22,0x1f,0x10,
+  0x24,0x29,0x14,0x22,0x29,0x1e,0x1e,0x01,0x35,0x1e,0x2d,0x02,0x12,
+  0x14,0x23,0x25,0x1e,0x2e,0x01,0x3b,0x1e,0x03,0x24,0x15,0x24,0x28,
+  0x1e,0x1a,0x13,0x13,0x13,0x0c,0x1e,0x33,0x01,0x1e,0x1e,0x0c,0x13,
+  0x0c,0x19,0x06,0x23,0x23,0x13,0x0c,0x12,0x1e,0x38,0x01,0x01,0x01,
+  0x1c,0x1e,0x06,0x22,0x1f,0x0c,0x14,0x10,0x1e,0x1e,0x38,0x1b,0x01,
+  0x01,0x1e,0x1e,0x0c,0x19,0x12,0x0b,0x18,0x27,0x13,0x0b,0x22,0x19,
+  0x03,0x05,0x13,0x23,0x06,0x1e,0x0f,0x01,0x2e,0x1e,0x0a,0x1f,0x13,
+  0x05,0x25,0x21,0x1f,0x13,0x19,0x0b,0x13,0x23,0x28,0x1e,0x1b,0x01,
+  0x3d,0x1e,0x29,0x22,0x1f,0x0c,0x1a,0x1e,0x0d,0x0c,0x19,0x12,0x02,
+  0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x36,0x1e,0x21,0x1f,0x0c,0x0b,0x22,0x1f,0x13,0x23,0x0d,
+  0x1f,0x22,0x2a,0x1e,0x08,0x01,0x01,0x34,0x1e,0x18,0x0c,0x13,0x13,
+  0x13,0x1e,0x1e,0x1f,0x13,0x0c,0x1f,0x0a,0x1e,0x2e,0x01,0x01,0x3e,
+  0x1e,0x14,0x13,0x0c,0x23,0x16,0x1e,0x23,0x05,0x0c,0x1f,0x02,0x1e,
+  0x11,0x01,0x37,0x1e,0x15,0x02,0x1f,0x1f,0x0d,0x1e,0x1a,0x05,0x29,
+  0x22,0x14,0x1e,0x40,0x01,0x08,0x1e,0x2a,0x1f,0x22,0x13,0x13,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x08,0x01,0x36,0x1e,0x31,0x13,0x13,0x13,
+  0x0c,0x2d,0x1e,0x1e,0x1e,0x1e,0x2c,0x01,0x01,0x00,0x1e,0x1e,0x02,
+  0x0c,0x1f,0x1f,0x06,0x1e,0x15,0x13,0x13,0x0c,0x27,0x1e,0x32,0x01,
+  0x01,0x3d,0x1e,0x29,0x0c,0x05,0x13,0x19,0x1e,0x03,0x02,0x02,0x1f,
+  0x1f,0x1e,0x1e,0x01,0x35,0x1e,0x2d,0x14,0x0b,0x22,0x1f,0x0a,0x1e,
+  0x2e,0x01,0x30,0x1e,0x24,0x0c,0x13,0x0c,0x05,0x1e,0x06,0x13,0x13,
+  0x13,0x0c,0x1e,0x33,0x01,0x1e,0x1e,0x1f,0x1f,0x13,0x1f,0x16,0x24,
+  0x13,0x0c,0x23,0x05,0x18,0x1e,0x08,0x01,0x01,0x1c,0x1e,0x24,0x13,
+  0x19,0x02,0x05,0x10,0x1e,0x1e,0x1e,0x1e,0x1b,0x01,0x1e,0x1e,0x1f,
+  0x0c,0x0b,0x0b,0x1e,0x13,0x23,0x0d,0x1f,0x12,0x10,0x1f,0x05,0x27,
+  0x26,0x1e,0x0f,0x01,0x2e,0x1e,0x25,0x05,0x13,0x23,0x16,0x1e,0x1f,
+  0x0c,0x05,0x0c,0x0c,0x05,0x15,0x1e,0x1b,0x01,0x2c,0x1e,0x24,0x05,
+  0x05,0x29,0x0d,0x1e,0x12,0x1f,0x0c,0x0b,0x27,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x1e,
+  0x25,0x13,0x1a,0x19,0x13,0x0b,0x05,0x0c,0x0b,0x05,0x1f,0x29,0x1e,
+  0x30,0x01,0x01,0x34,0x1e,0x18,0x0c,0x13,0x1f,0x0c,0x1a,0x14,0x22,
+  0x13,0x0c,0x1f,0x0a,0x1e,0x3b,0x01,0x01,0x07,0x1e,0x22,0x1f,0x13,
+  0x1f,0x19,0x26,0x13,0x0c,0x0b,0x05,0x25,0x1e,0x37,0x01,0x37,0x1e,
+  0x06,0x0c,0x0b,0x02,0x05,0x0d,0x02,0x0c,0x05,0x1f,0x24,0x1e,0x2c,
+  0x01,0x08,0x1e,0x16,0x05,0x1f,0x13,0x13,0x0c,0x14,0x22,0x13,0x24,
+  0x1e,0x1c,0x01,0x36,0x1e,0x31,0x13,0x13,0x13,0x13,0x03,0x1e,0x08,
+  0x3c,0x20,0x01,0x01,0x01,0x01,0x39,0x1e,0x27,0x0c,0x0c,0x13,0x23,
+  0x21,0x14,0x1f,0x1f,0x13,0x23,0x1e,0x32,0x01,0x01,0x3d,0x1e,0x12,
+  0x1f,0x22,0x0c,0x23,0x1e,0x25,0x14,0x0c,0x0b,0x02,0x1e,0x1e,0x01,
+  0x35,0x1e,0x2d,0x0d,0x19,0x13,0x0b,0x0a,0x1e,0x2e,0x01,0x1b,0x1e,
+  0x15,0x14,0x13,0x1f,0x13,0x06,0x14,0x13,0x1f,0x0c,0x14,0x1e,0x1e,
+  0x01,0x1e,0x1e,0x0c,0x13,0x0b,0x0b,0x2a,0x1e,0x1f,0x13,0x1f,0x0c,
+  0x29,0x1e,0x38,0x34,0x01,0x1c,0x1e,0x06,0x0c,0x23,0x22,0x05,0x22,
+  0x05,0x0b,0x12,0x1e,0x2c,0x01,0x1e,0x1e,0x13,0x1a,0x19,0x1f,0x1e,
+  0x14,0x0c,0x0b,0x14,0x12,0x2d,0x1f,0x13,0x05,0x26,0x1e,0x0f,0x01,
+  0x2e,0x1e,0x0a,0x1f,0x0b,0x13,0x28,0x1e,0x14,0x1f,0x02,0x05,0x1f,
+  0x22,0x15,0x1e,0x1b,0x01,0x3a,0x1e,0x0a,0x02,0x0c,0x05,0x05,0x28,
+  0x0b,0x13,0x1a,0x19,0x12,0x1e,0x1c,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,0x28,0x13,0x05,0x22,
+  0x02,0x18,0x1e,0x29,0x19,0x13,0x0b,0x23,0x1e,0x1d,0x01,0x01,0x34,
+  0x1e,0x18,0x1f,0x13,0x1f,0x0c,0x23,0x13,0x13,0x13,0x05,0x14,0x1e,
+  0x1e,0x35,0x01,0x01,0x04,0x1e,0x2a,0x13,0x0b,0x23,0x13,0x05,0x05,
+  0x0c,0x1f,0x1f,0x1e,0x1e,0x35,0x01,0x37,0x1e,0x26,0x22,0x0b,0x22,
+  0x1f,0x14,0x0c,0x14,0x27,0x0c,0x2d,0x1e,0x3b,0x01,0x3c,0x1e,0x16,
+  0x13,0x0b,0x13,0x13,0x13,0x14,0x1f,0x13,0x0d,0x1e,0x04,0x01,0x36,
+  0x1e,0x31,0x13,0x13,0x13,0x0c,0x31,0x1e,0x36,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x3a,0x1e,0x0a,0x13,0x1f,0x13,0x13,0x0c,0x13,0x1f,0x14,
+  0x0b,0x1a,0x1e,0x1d,0x01,0x01,0x30,0x1e,0x12,0x23,0x0b,0x05,0x29,
+  0x1e,0x25,0x0b,0x22,0x0b,0x22,0x1e,0x1e,0x01,0x35,0x1e,0x2d,0x22,
+  0x22,0x14,0x14,0x0a,0x1e,0x2e,0x01,0x2b,0x1e,0x1e,0x1f,0x13,0x0c,
+  0x13,0x0c,0x13,0x13,0x1f,0x0c,0x28,0x1e,0x38,0x01,0x1e,0x1e,0x1f,
+  0x13,0x13,0x0c,0x25,0x1e,0x14,0x0b,0x23,0x13,0x05,0x1e,0x1e,0x04,
+  0x01,0x1c,0x1e,0x06,0x05,0x29,0x02,0x1f,0x0b,0x22,0x0b,0x27,0x1e,
+  0x38,0x01,0x1e,0x1e,0x13,0x05,0x22,0x22,0x1e,0x0d,0x1a,0x19,0x13,
+  0x2a,0x2d,0x13,0x05,0x22,0x26,0x1e,0x0f,0x01,0x3b,0x1e,0x16,0x0c,
+  0x05,0x05,0x26,0x1e,0x0d,0x0c,0x13,0x0c,0x23,0x0b,0x15,0x1e,0x1b,
+  0x01,0x2b,0x1e,0x2d,0x0c,0x14,0x27,0x0c,0x19,0x12,0x13,0x05,0x22,
+  0x18,0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2c,0x1e,0x1a,0x1f,0x0b,0x1f,0x06,0x1e,0x1e,0x29,
+  0x23,0x22,0x22,0x13,0x31,0x1e,0x36,0x01,0x35,0x1e,0x2d,0x13,0x13,
+  0x13,0x05,0x14,0x13,0x23,0x13,0x14,0x09,0x1e,0x38,0x01,0x01,0x01,
+  0x34,0x3e,0x1e,0x2a,0x13,0x1f,0x13,0x14,0x13,0x14,0x19,0x1e,0x1e,
+  0x38,0x01,0x01,0x39,0x1e,0x29,0x14,0x0d,0x19,0x1f,0x0b,0x05,0x14,
+  0x23,0x10,0x1e,0x38,0x01,0x01,0x0f,0x1e,0x15,0x22,0x22,0x13,0x13,
+  0x13,0x13,0x22,0x13,0x22,0x1e,0x32,0x01,0x08,0x1e,0x16,0x13,0x13,
+  0x1f,0x13,0x25,0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x33,
+  0x1e,0x26,0x05,0x13,0x0c,0x0c,0x13,0x2d,0x1f,0x13,0x1f,0x1e,0x1e,
+  0x00,0x01,0x32,0x1e,0x0b,0x05,0x05,0x0c,0x0c,0x1e,0x0a,0x23,0x14,
+  0x0d,0x19,0x1e,0x1e,0x01,0x36,0x1e,0x21,0x0b,0x1f,0x0b,0x24,0x24,
+  0x1e,0x37,0x01,0x01,0x11,0x1e,0x1e,0x0c,0x13,0x13,0x05,0x13,0x13,
+  0x13,0x25,0x1e,0x1e,0x36,0x01,0x1e,0x1e,0x14,0x13,0x0c,0x0c,0x29,
+  0x1e,0x06,0x13,0x1f,0x13,0x14,0x14,0x1e,0x07,0x01,0x38,0x1e,0x02,
+  0x0c,0x0c,0x22,0x02,0x23,0x14,0x0d,0x19,0x1e,0x1e,0x01,0x1e,0x1e,
+  0x1f,0x0b,0x1f,0x0b,0x1e,0x0d,0x05,0x19,0x22,0x03,0x31,0x13,0x05,
+  0x0c,0x29,0x1e,0x2c,0x01,0x1c,0x1e,0x06,0x0c,0x27,0x13,0x0d,0x1e,
+  0x24,0x05,0x05,0x0c,0x14,0x05,0x0d,0x1e,0x04,0x01,0x01,0x38,0x1e,
+  0x10,0x14,0x22,0x1f,0x05,0x0c,0x1f,0x0b,0x2d,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3a,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1d,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x2b,0x3e,0x1e,
+  0x1e,0x10,0x0a,0x16,0x28,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x0f,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x17,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x30,0x01,0x20,0x07,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x40,
+  0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x17,0x1e,0x1e,0x1e,0x2a,
+  0x25,0x0a,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x30,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x2f,0x01,0x01,
+  0x0e,0x11,0x1e,0x1e,0x18,0x0a,0x16,0x0a,0x03,0x1e,0x1e,0x33,0x36,
+  0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x1c,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3a,
+  0x01,0x3c,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x2e,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x2d,0x25,
+  0x16,0x25,0x2d,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3a,0x38,0x1e,0x1e,
+  0x1e,0x38,0x3c,0x3b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x33,0x39,
+  0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x33,0x39,0x35,0x01,0x01,0x01,0x01,0x0f,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x40,0x2c,0x20,0x01,0x01,0x01,0x01,0x17,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x32,0x30,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x17,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x33,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x30,0x32,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2f,0x01,0x01,0x01,0x01,0x0e,0x39,0x33,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x3b,0x38,0x1e,0x1e,0x1e,0x1e,0x07,0x04,
+  0x34,0x01,0x01,0x1c,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,
+  0x1e,0x38,0x38,0x1e,0x1e,0x1e,0x38,0x3a,0x01,0x01,0x01,0x3c,0x38,
+  0x1e,0x1e,0x1e,0x38,0x1b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2e,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x3b,0x3c,0x2e,0x0e,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x34,0x2f,0x08,0x3b,0x35,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x3b,0x3c,0x3b,
+  0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x35,0x08,0x08,0x08,0x35,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x00,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x20,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x2e,0x3a,0x3a,0x1b,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x35,0x35,0x35,0x35,
+  0x35,0x35,0x35,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x34,0x08,0x30,0x1d,0x30,0x1b,0x36,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x00,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x35,0x0e,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x0e,0x20,0x0e,0x34,0x01,0x20,
+  0x35,0x35,0x35,0x20,0x01,0x01,0x01,0x01,0x01,0x34,0x35,0x35,0x35,
+  0x0e,0x01,0x01,0x00,0x0e,0x35,0x35,0x35,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x35,0x35,0x35,0x34,0x01,0x34,0x35,0x35,0x35,0x35,0x35,
+  0x01,0x01,0x0e,0x35,0x35,0x00,0x01,0x01,0x01,0x01,0x01,0x34,0x0e,
+  0x20,0x35,0x35,0x01,0x01,0x34,0x35,0x35,0x20,0x36,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x35,0x0e,0x20,0x0e,0x34,0x01,0x01,0x20,0x35,0x35,
+  0x35,0x20,0x01,0x01,0x01,0x01,0x01,0x20,0x35,0x35,0x35,0x35,0x35,
+  0x35,0x35,0x0e,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,
+  0x2e,0x3d,0x3d,0x04,0x3d,0x2e,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x35,0x35,0x35,0x35,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x20,0x3b,0x3d,0x04,0x3d,0x04,0x3a,0x17,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,0x30,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x34,0x04,0x3e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,0x1c,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x3d,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x39,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x3d,0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x39,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x00,0x2c,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x2c,0x01,
+  0x01,0x0e,0x11,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x11,0x0e,0x01,0x01,0x01,0x01,0x01,0x1c,0x33,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x33,0x0f,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x04,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x17,0x07,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x3b,0x1e,0x1e,0x2d,0x2d,0x09,0x09,0x09,0x09,0x2d,
+  0x09,0x1e,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x2d,
+  0x28,0x26,0x15,0x28,0x2d,0x1e,0x1e,0x04,0x34,0x01,0x01,0x38,0x1e,
+  0x2d,0x2d,0x09,0x09,0x09,0x09,0x2d,0x2d,0x1e,0x1e,0x1e,0x17,0x01,
+  0x01,0x01,0x01,0x2b,0x32,0x1e,0x1e,0x16,0x12,0x0b,0x28,0x15,0x1e,
+  0x1e,0x1e,0x2b,0x01,0x01,0x01,0x3c,0x1e,0x1e,0x2d,0x2d,0x09,0x09,
+  0x09,0x09,0x09,0x2d,0x10,0x1e,0x1e,0x2b,0x01,0x37,0x1e,0x18,0x10,
+  0x09,0x10,0x18,0x1e,0x18,0x2d,0x09,0x2d,0x09,0x1e,0x38,0x01,0x17,
+  0x1e,0x18,0x2d,0x2d,0x09,0x10,0x1e,0x1e,0x1e,0x10,0x09,0x2d,0x2d,
+  0x1e,0x38,0x01,0x01,0x01,0x38,0x1e,0x2d,0x2d,0x2d,0x18,0x1e,0x18,
+  0x2d,0x09,0x09,0x2d,0x2d,0x1e,0x1e,0x10,0x2d,0x2d,0x1e,0x1e,0x2f,
+  0x01,0x2f,0x1e,0x18,0x10,0x09,0x2d,0x2d,0x1e,0x1e,0x1e,0x2d,0x09,
+  0x09,0x31,0x1e,0x38,0x01,0x01,0x3d,0x1e,0x09,0x10,0x09,0x10,0x18,
+  0x1e,0x1e,0x09,0x2d,0x2d,0x2d,0x09,0x1e,0x0f,0x01,0x11,0x1e,0x18,
+  0x2d,0x09,0x09,0x09,0x09,0x09,0x09,0x10,0x09,0x1e,0x11,0x01,0x01,
+  0x01,0x01,0x1c,0x1e,0x1e,0x28,0x0d,0x0d,0x1a,0x29,0x28,0x1e,0x1e,
+  0x0f,0x01,0x01,0x01,0x38,0x1e,0x2d,0x2d,0x2d,0x2d,0x18,0x1e,0x1c,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x17,0x1e,0x1e,0x16,0x29,
+  0x24,0x26,0x29,0x26,0x09,0x1e,0x38,0x35,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x30,
+  0x1e,0x29,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x0b,0x09,0x1e,
+  0x30,0x01,0x01,0x01,0x0f,0x1e,0x1e,0x14,0x0c,0x22,0x1f,0x1f,0x1f,
+  0x13,0x23,0x1e,0x1e,0x04,0x01,0x01,0x1e,0x1e,0x0c,0x13,0x05,0x14,
+  0x13,0x13,0x13,0x13,0x13,0x26,0x1e,0x07,0x20,0x01,0x01,0x01,0x38,
+  0x1e,0x03,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x15,0x1e,0x32,0x00,
+  0x01,0x01,0x1c,0x1e,0x12,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x13,0x28,0x1e,0x2e,0x01,0x38,0x1e,0x23,0x13,0x13,0x13,0x02,0x1e,
+  0x13,0x13,0x13,0x0c,0x22,0x1e,0x1e,0x01,0x0f,0x1e,0x26,0x13,0x05,
+  0x14,0x13,0x21,0x1e,0x19,0x0c,0x13,0x05,0x14,0x2d,0x1e,0x35,0x01,
+  0x2b,0x1e,0x21,0x13,0x13,0x13,0x19,0x1e,0x0c,0x13,0x13,0x13,0x13,
+  0x13,0x1e,0x06,0x13,0x13,0x13,0x06,0x1e,0x0f,0x01,0x0f,0x1e,0x06,
+  0x13,0x13,0x0c,0x22,0x26,0x1e,0x14,0x13,0x0c,0x13,0x1f,0x1e,0x1e,
+  0x01,0x01,0x3e,0x1e,0x19,0x05,0x1f,0x0b,0x14,0x1e,0x09,0x1f,0x1f,
+  0x13,0x05,0x1a,0x1e,0x11,0x01,0x1e,0x1e,0x22,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x0c,0x09,0x1e,0x20,0x01,0x01,0x2f,0x1e,0x1e,
+  0x0b,0x13,0x13,0x13,0x13,0x13,0x13,0x02,0x1e,0x33,0x0e,0x01,0x01,
+  0x1e,0x1e,0x1f,0x1f,0x1f,0x13,0x23,0x1e,0x1d,0x2c,0x1b,0x0e,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x26,0x14,0x14,0x0b,0x0c,0x1f,0x19,
+  0x05,0x09,0x1e,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x0a,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x0b,0x1e,0x1d,0x01,0x01,0x01,
+  0x38,0x1e,0x02,0x0c,0x0c,0x13,0x13,0x14,0x0c,0x13,0x0b,0x0b,0x1e,
+  0x3e,0x01,0x01,0x1e,0x1e,0x14,0x13,0x05,0x02,0x14,0x13,0x0c,0x23,
+  0x14,0x13,0x16,0x1e,0x2e,0x01,0x01,0x0e,0x1e,0x10,0x13,0x13,0x13,
+  0x13,0x0c,0x13,0x13,0x13,0x13,0x2a,0x1e,0x1b,0x01,0x01,0x1b,0x1e,
+  0x15,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x16,0x1e,0x3c,
+  0x01,0x1c,0x1e,0x12,0x13,0x13,0x13,0x29,0x1e,0x23,0x0c,0x0c,0x0c,
+  0x23,0x1e,0x1d,0x01,0x17,0x1e,0x18,0x0c,0x0c,0x13,0x1f,0x31,0x1e,
+  0x19,0x14,0x13,0x05,0x06,0x1e,0x38,0x01,0x01,0x01,0x1d,0x1e,0x0b,
+  0x13,0x13,0x12,0x1e,0x23,0x13,0x13,0x13,0x13,0x13,0x1e,0x06,0x13,
+  0x13,0x13,0x21,0x1e,0x2f,0x01,0x2f,0x1e,0x1e,0x27,0x0c,0x0c,0x13,
+  0x0d,0x1e,0x0c,0x13,0x0b,0x1f,0x2d,0x1e,0x38,0x01,0x01,0x3d,0x1e,
+  0x18,0x0b,0x22,0x14,0x02,0x1e,0x25,0x05,0x27,0x22,0x1f,0x1e,0x1e,
+  0x0f,0x01,0x1d,0x1e,0x0d,0x14,0x13,0x0c,0x23,0x14,0x13,0x13,0x13,
+  0x0b,0x1e,0x1e,0x00,0x01,0x01,0x37,0x1e,0x24,0x1f,0x1f,0x13,0x14,
+  0x22,0x13,0x13,0x13,0x0a,0x1e,0x3b,0x01,0x01,0x33,0x1e,0x0c,0x14,
+  0x0c,0x13,0x26,0x1e,0x1e,0x1e,0x1e,0x33,0x2b,0x01,0x01,0x01,0x17,
+  0x1e,0x18,0x24,0x0d,0x0c,0x02,0x19,0x1f,0x05,0x22,0x19,0x1e,0x33,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x2e,0x1e,0x28,0x13,0x13,0x13,0x0c,0x0d,0x14,
+  0x13,0x13,0x13,0x0c,0x2d,0x1e,0x35,0x01,0x35,0x1e,0x2d,0x19,0x13,
+  0x0c,0x14,0x15,0x26,0x0c,0x13,0x0c,0x13,0x10,0x1e,0x0e,0x01,0x1e,
+  0x1e,0x0c,0x1f,0x23,0x13,0x0d,0x31,0x23,0x19,0x12,0x14,0x0d,0x1e,
+  0x04,0x01,0x01,0x3b,0x1e,0x0a,0x13,0x13,0x13,0x13,0x1e,0x05,0x13,
+  0x13,0x13,0x1a,0x1e,0x04,0x01,0x01,0x08,0x1e,0x16,0x22,0x02,0x13,
+  0x13,0x13,0x13,0x0c,0x19,0x0c,0x31,0x1e,0x36,0x01,0x30,0x1e,0x29,
+  0x13,0x13,0x13,0x29,0x1e,0x19,0x19,0x13,0x0c,0x02,0x1e,0x1d,0x01,
+  0x01,0x32,0x1e,0x22,0x02,0x14,0x0c,0x28,0x1e,0x1f,0x0c,0x1f,0x23,
+  0x06,0x1e,0x0f,0x01,0x01,0x01,0x38,0x1e,0x23,0x0b,0x14,0x19,0x1e,
+  0x14,0x13,0x13,0x13,0x13,0x13,0x1e,0x0d,0x13,0x13,0x1f,0x1e,0x40,
+  0x01,0x01,0x01,0x3a,0x1e,0x09,0x13,0x0c,0x14,0x05,0x0d,0x0c,0x13,
+  0x0c,0x0b,0x1e,0x32,0x2b,0x01,0x01,0x00,0x39,0x1e,0x27,0x05,0x23,
+  0x0c,0x18,0x26,0x22,0x27,0x12,0x26,0x1e,0x38,0x01,0x01,0x1c,0x1e,
+  0x12,0x19,0x0d,0x02,0x27,0x12,0x14,0x13,0x13,0x0d,0x1e,0x11,0x01,
+  0x01,0x01,0x08,0x1e,0x16,0x27,0x0a,0x0a,0x1e,0x26,0x13,0x13,0x13,
+  0x29,0x1e,0x30,0x01,0x01,0x33,0x1e,0x0c,0x0c,0x0c,0x13,0x06,0x1e,
+  0x27,0x1a,0x28,0x1e,0x1e,0x2b,0x01,0x01,0x3a,0x1e,0x15,0x14,0x23,
+  0x22,0x1a,0x21,0x0d,0x1a,0x0c,0x27,0x1e,0x1e,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2e,0x1e,0x28,0x13,0x13,0x13,0x1f,0x1e,0x09,0x13,0x13,0x13,0x13,
+  0x2a,0x1e,0x2f,0x01,0x36,0x1e,0x31,0x0c,0x13,0x0c,0x13,0x1e,0x1e,
+  0x0c,0x0c,0x22,0x13,0x03,0x1e,0x2f,0x01,0x1e,0x1e,0x0b,0x0c,0x19,
+  0x05,0x2a,0x1e,0x25,0x0b,0x0c,0x1f,0x12,0x1e,0x39,0x01,0x01,0x3a,
+  0x1e,0x26,0x13,0x13,0x13,0x13,0x1e,0x2a,0x31,0x31,0x15,0x03,0x1e,
+  0x1b,0x01,0x01,0x20,0x07,0x1e,0x1e,0x1e,0x13,0x13,0x13,0x13,0x02,
+  0x1e,0x1e,0x1e,0x1d,0x01,0x01,0x30,0x1e,0x29,0x13,0x13,0x13,0x29,
+  0x1e,0x23,0x0c,0x13,0x0c,0x0b,0x1e,0x1d,0x01,0x01,0x3d,0x1e,0x12,
+  0x1f,0x13,0x0c,0x12,0x1e,0x27,0x0b,0x0c,0x19,0x16,0x1e,0x3c,0x01,
+  0x01,0x01,0x37,0x1e,0x06,0x02,0x24,0x1a,0x1e,0x05,0x05,0x05,0x13,
+  0x13,0x13,0x1e,0x14,0x13,0x13,0x22,0x1e,0x32,0x01,0x01,0x01,0x01,
+  0x38,0x1e,0x1a,0x0c,0x13,0x1f,0x0b,0x0c,0x0c,0x22,0x18,0x1e,0x30,
+  0x01,0x01,0x01,0x01,0x3a,0x1e,0x2a,0x23,0x02,0x14,0x06,0x27,0x22,
+  0x19,0x05,0x1e,0x1e,0x08,0x01,0x01,0x08,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x27,0x0c,0x1f,0x19,0x05,0x10,0x1e,0x0f,0x01,0x01,0x01,0x2b,0x1e,
+  0x1e,0x18,0x28,0x06,0x06,0x0b,0x0c,0x13,0x13,0x0d,0x1e,0x3d,0x01,
+  0x01,0x33,0x1e,0x05,0x0b,0x0c,0x0c,0x02,0x13,0x0c,0x0c,0x0c,0x28,
+  0x1e,0x04,0x01,0x01,0x3d,0x1e,0x12,0x06,0x27,0x14,0x24,0x1e,0x2a,
+  0x28,0x0a,0x25,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x28,0x13,
+  0x13,0x13,0x1f,0x1e,0x18,0x13,0x13,0x13,0x13,0x16,0x1e,0x3c,0x01,
+  0x36,0x1e,0x31,0x0c,0x13,0x1f,0x0c,0x1e,0x21,0x0c,0x0c,0x13,0x05,
+  0x25,0x1e,0x3b,0x01,0x1e,0x1e,0x0c,0x05,0x22,0x0c,0x06,0x25,0x05,
+  0x02,0x19,0x1f,0x2a,0x1e,0x3a,0x01,0x01,0x3b,0x1e,0x0a,0x13,0x13,
+  0x1f,0x1f,0x13,0x02,0x28,0x09,0x1e,0x1e,0x32,0x00,0x01,0x01,0x01,
+  0x20,0x3d,0x1e,0x1e,0x13,0x13,0x13,0x13,0x19,0x1e,0x1e,0x11,0x0e,
+  0x01,0x01,0x30,0x1e,0x29,0x13,0x13,0x13,0x29,0x1e,0x19,0x0c,0x13,
+  0x1f,0x23,0x1e,0x1d,0x01,0x01,0x3b,0x1e,0x16,0x02,0x0c,0x13,0x02,
+  0x1e,0x1f,0x0c,0x05,0x0b,0x09,0x1e,0x20,0x01,0x01,0x01,0x08,0x1e,
+  0x16,0x0b,0x0c,0x23,0x03,0x14,0x0b,0x26,0x0b,0x13,0x13,0x1e,0x22,
+  0x13,0x13,0x0b,0x1e,0x1d,0x01,0x01,0x01,0x01,0x1b,0x1e,0x1e,0x1f,
+  0x0c,0x13,0x1f,0x0c,0x0c,0x1a,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,
+  0x34,0x3e,0x1e,0x27,0x0b,0x05,0x24,0x12,0x19,0x1a,0x0d,0x1e,0x32,
+  0x01,0x01,0x01,0x01,0x08,0x2c,0x07,0x1e,0x24,0x1a,0x27,0x1f,0x05,
+  0x25,0x1e,0x38,0x01,0x01,0x01,0x01,0x2c,0x1e,0x0a,0x23,0x27,0x13,
+  0x14,0x22,0x0c,0x13,0x13,0x29,0x1e,0x30,0x01,0x01,0x33,0x1e,0x0c,
+  0x1f,0x0c,0x0c,0x13,0x05,0x23,0x0c,0x05,0x1a,0x18,0x1e,0x34,0x01,
+  0x1c,0x1e,0x24,0x0c,0x14,0x29,0x0d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x04,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x28,0x13,0x13,0x13,0x13,0x29,
+  0x0b,0x13,0x13,0x13,0x0c,0x09,0x1e,0x35,0x01,0x36,0x1e,0x31,0x0c,
+  0x05,0x05,0x13,0x1e,0x21,0x13,0x05,0x0c,0x13,0x16,0x1e,0x08,0x01,
+  0x1e,0x1e,0x0c,0x0c,0x22,0x0b,0x14,0x23,0x22,0x14,0x1f,0x25,0x1e,
+  0x3e,0x00,0x01,0x01,0x0e,0x1e,0x18,0x19,0x0b,0x14,0x06,0x1f,0x13,
+  0x13,0x13,0x1a,0x1e,0x1e,0x3c,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,
+  0x13,0x13,0x13,0x13,0x19,0x1e,0x11,0x01,0x01,0x01,0x01,0x30,0x1e,
+  0x29,0x13,0x13,0x13,0x12,0x1e,0x19,0x0c,0x05,0x05,0x0b,0x1e,0x1d,
+  0x01,0x01,0x2b,0x1e,0x21,0x13,0x05,0x23,0x23,0x21,0x02,0x0c,0x0c,
+  0x1a,0x1e,0x38,0x01,0x01,0x01,0x01,0x3c,0x1e,0x16,0x27,0x0b,0x22,
+  0x03,0x0b,0x05,0x12,0x19,0x19,0x0b,0x1e,0x06,0x1f,0x13,0x27,0x1e,
+  0x39,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x26,0x13,0x05,0x13,0x13,
+  0x05,0x18,0x1e,0x2e,0x01,0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,0x16,
+  0x22,0x19,0x0b,0x19,0x23,0x0b,0x1e,0x1e,0x30,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x21,0x27,0x19,0x26,0x12,0x24,0x1e,0x33,0x2b,0x01,
+  0x01,0x01,0x34,0x1e,0x18,0x23,0x1f,0x13,0x19,0x1e,0x03,0x22,0x13,
+  0x13,0x29,0x1e,0x30,0x01,0x01,0x33,0x1e,0x14,0x13,0x13,0x05,0x22,
+  0x06,0x05,0x0c,0x13,0x1f,0x09,0x1e,0x20,0x01,0x2c,0x1e,0x12,0x02,
+  0x05,0x02,0x15,0x1e,0x02,0x14,0x27,0x0b,0x1e,0x1e,0x00,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x2e,0x1e,0x28,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x23,0x1e,0x38,0x01,0x01,0x36,0x1e,0x21,0x0c,0x1f,0x22,0x13,0x1e,
+  0x21,0x05,0x05,0x13,0x0c,0x16,0x1e,0x08,0x01,0x1e,0x1e,0x05,0x27,
+  0x05,0x0b,0x06,0x27,0x14,0x22,0x05,0x29,0x1e,0x33,0x0e,0x01,0x01,
+  0x01,0x38,0x1e,0x09,0x0d,0x14,0x0c,0x0c,0x02,0x22,0x0c,0x13,0x12,
+  0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,
+  0x19,0x1e,0x11,0x01,0x01,0x01,0x01,0x30,0x1e,0x29,0x13,0x13,0x13,
+  0x29,0x1e,0x0d,0x0c,0x1f,0x22,0x0b,0x1e,0x1d,0x01,0x01,0x34,0x1e,
+  0x18,0x05,0x13,0x05,0x1f,0x28,0x13,0x05,0x27,0x12,0x1e,0x3d,0x01,
+  0x01,0x01,0x01,0x2b,0x1e,0x21,0x14,0x0c,0x27,0x26,0x22,0x0b,0x26,
+  0x19,0x05,0x0b,0x21,0x0c,0x0c,0x02,0x24,0x1e,0x30,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x29,0x13,0x1f,0x0c,0x05,0x05,0x2d,0x1e,0x11,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x40,0x1e,0x23,0x02,0x06,0x1a,
+  0x0b,0x23,0x1e,0x40,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,0x1e,0x0d,
+  0x27,0x19,0x22,0x05,0x09,0x1e,0x3a,0x01,0x01,0x01,0x01,0x20,0x1e,
+  0x09,0x0b,0x0b,0x05,0x14,0x2d,0x12,0x1f,0x22,0x13,0x29,0x1e,0x30,
+  0x01,0x01,0x33,0x1e,0x05,0x0c,0x05,0x05,0x06,0x1e,0x0c,0x13,0x1f,
+  0x05,0x21,0x1e,0x36,0x01,0x1b,0x1e,0x03,0x22,0x05,0x14,0x0b,0x06,
+  0x02,0x1f,0x27,0x02,0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x28,
+  0x22,0x13,0x13,0x22,0x13,0x13,0x13,0x13,0x19,0x18,0x1e,0x37,0x01,
+  0x01,0x36,0x1e,0x31,0x0c,0x0c,0x13,0x0c,0x1e,0x31,0x1f,0x23,0x13,
+  0x0c,0x0a,0x1e,0x3b,0x01,0x1e,0x1e,0x13,0x23,0x22,0x1f,0x24,0x18,
+  0x29,0x14,0x22,0x29,0x0a,0x1e,0x3b,0x01,0x01,0x2f,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x2d,0x0b,0x14,0x22,0x0c,0x13,0x1f,0x1e,0x40,0x01,0x01,
+  0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,0x19,0x1e,0x11,0x01,
+  0x01,0x01,0x01,0x30,0x1e,0x12,0x13,0x13,0x1f,0x12,0x1e,0x23,0x0c,
+  0x0c,0x13,0x23,0x1e,0x1d,0x01,0x01,0x01,0x39,0x1e,0x02,0x1f,0x1f,
+  0x13,0x24,0x14,0x13,0x23,0x28,0x1e,0x1b,0x01,0x01,0x01,0x01,0x0e,
+  0x1e,0x10,0x22,0x1f,0x1f,0x02,0x22,0x05,0x21,0x24,0x12,0x14,0x06,
+  0x27,0x13,0x14,0x03,0x1e,0x08,0x01,0x01,0x01,0x01,0x30,0x1e,0x31,
+  0x13,0x0c,0x19,0x0c,0x1f,0x23,0x1f,0x1e,0x33,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2c,0x1e,0x28,0x02,0x1f,0x0b,0x12,0x09,0x1e,0x2c,
+  0x01,0x01,0x01,0x01,0x2b,0x07,0x1e,0x29,0x06,0x24,0x1a,0x29,0x03,
+  0x1e,0x1e,0x38,0x0f,0x01,0x01,0x01,0x34,0x1e,0x18,0x0b,0x14,0x13,
+  0x0d,0x02,0x23,0x0b,0x13,0x13,0x06,0x1e,0x38,0x01,0x01,0x33,0x1e,
+  0x19,0x0c,0x1f,0x23,0x28,0x1e,0x1f,0x13,0x05,0x13,0x03,0x1e,0x17,
+  0x01,0x00,0x1e,0x1e,0x02,0x1f,0x0b,0x22,0x0b,0x22,0x1f,0x14,0x29,
+  0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x28,0x22,0x0c,0x13,0x1f,
+  0x25,0x21,0x2a,0x21,0x1e,0x1e,0x04,0x01,0x01,0x01,0x2b,0x1e,0x21,
+  0x0c,0x1f,0x1f,0x13,0x1e,0x1e,0x13,0x13,0x0c,0x23,0x31,0x1e,0x36,
+  0x01,0x1e,0x1e,0x0c,0x05,0x13,0x19,0x25,0x1e,0x27,0x02,0x1f,0x1f,
+  0x15,0x1e,0x3a,0x01,0x01,0x37,0x1e,0x06,0x02,0x1a,0x19,0x16,0x1e,
+  0x24,0x1f,0x22,0x13,0x1f,0x09,0x1e,0x20,0x01,0x01,0x01,0x01,0x1e,
+  0x1e,0x13,0x13,0x13,0x13,0x19,0x1e,0x11,0x01,0x01,0x01,0x01,0x30,
+  0x1e,0x29,0x13,0x0c,0x1f,0x29,0x1e,0x29,0x0c,0x1f,0x1f,0x14,0x1e,
+  0x3e,0x01,0x01,0x01,0x0f,0x1e,0x06,0x0c,0x1f,0x0c,0x0b,0x0c,0x0c,
+  0x05,0x31,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,0x1d,0x1e,0x19,0x05,
+  0x29,0x19,0x1f,0x0b,0x10,0x15,0x0b,0x22,0x0b,0x13,0x23,0x0d,0x10,
+  0x1e,0x0e,0x01,0x01,0x01,0x0e,0x40,0x1e,0x0b,0x1f,0x13,0x1f,0x13,
+  0x13,0x13,0x0c,0x03,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x17,
+  0x1e,0x03,0x19,0x27,0x02,0x0d,0x1e,0x1e,0x20,0x01,0x01,0x01,0x01,
+  0x38,0x1e,0x2a,0x27,0x02,0x29,0x1a,0x24,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x3d,0x01,0x01,0x01,0x39,0x1e,0x16,0x24,0x13,0x05,0x02,0x16,0x22,
+  0x23,0x13,0x1f,0x09,0x1e,0x20,0x01,0x33,0x1e,0x05,0x13,0x13,0x13,
+  0x1a,0x1e,0x05,0x13,0x23,0x14,0x2d,0x1e,0x35,0x01,0x01,0x38,0x1e,
+  0x2d,0x02,0x23,0x14,0x0d,0x19,0x1f,0x12,0x1e,0x1e,0x3b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2e,0x1e,0x28,0x1f,0x14,0x13,0x05,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x37,0x01,0x01,0x01,0x01,0x35,0x1e,0x2d,0x0c,0x0c,0x13,0x0b,
+  0x0d,0x02,0x1f,0x1f,0x13,0x1f,0x09,0x1e,0x20,0x01,0x1e,0x1e,0x1f,
+  0x22,0x0c,0x23,0x15,0x1e,0x22,0x0c,0x0b,0x02,0x28,0x1e,0x2e,0x01,
+  0x01,0x38,0x1e,0x12,0x19,0x13,0x0b,0x05,0x1e,0x19,0x05,0x1f,0x13,
+  0x22,0x1e,0x32,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x0c,0x14,0x13,
+  0x22,0x19,0x1e,0x11,0x01,0x01,0x01,0x01,0x3d,0x1e,0x0d,0x13,0x0c,
+  0x1f,0x1f,0x0d,0x05,0x0c,0x0c,0x13,0x0d,0x1e,0x11,0x01,0x01,0x01,
+  0x2f,0x1e,0x2a,0x0b,0x05,0x1f,0x02,0x05,0x1f,0x23,0x1e,0x07,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x11,0x1e,0x29,0x0c,0x05,0x1f,0x19,0x22,
+  0x03,0x03,0x19,0x13,0x0b,0x05,0x0c,0x23,0x1e,0x1e,0x01,0x01,0x01,
+  0x01,0x11,0x1e,0x25,0x0b,0x13,0x23,0x0d,0x16,0x0b,0x1f,0x22,0x1f,
+  0x1e,0x1e,0x3c,0x01,0x01,0x01,0x01,0x01,0x2f,0x1e,0x03,0x02,0x0b,
+  0x22,0x0b,0x18,0x1e,0x34,0x01,0x01,0x01,0x01,0x1e,0x1e,0x23,0x24,
+  0x27,0x22,0x23,0x02,0x0d,0x02,0x29,0x12,0x1e,0x1e,0x34,0x01,0x01,
+  0x35,0x40,0x1e,0x18,0x25,0x2a,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x01,0x01,0x33,0x1e,0x02,0x1f,0x0b,0x1f,0x22,0x14,0x0c,0x0c,
+  0x13,0x05,0x09,0x1e,0x20,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x2d,0x28,
+  0x25,0x21,0x1e,0x1e,0x1e,0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,
+  0x28,0x14,0x1f,0x13,0x1f,0x09,0x1e,0x2f,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x1a,0x1f,0x13,0x13,0x0c,0x13,0x1f,0x13,
+  0x0b,0x0d,0x1e,0x38,0x01,0x01,0x1e,0x1e,0x23,0x0b,0x05,0x29,0x0a,
+  0x1e,0x0b,0x22,0x0b,0x22,0x25,0x1e,0x38,0x01,0x01,0x37,0x1e,0x16,
+  0x22,0x14,0x14,0x13,0x1a,0x19,0x13,0x0b,0x13,0x16,0x1e,0x30,0x01,
+  0x01,0x01,0x01,0x01,0x1e,0x1e,0x0c,0x23,0x13,0x13,0x19,0x1e,0x11,
+  0x01,0x01,0x01,0x01,0x3b,0x1e,0x18,0x13,0x05,0x05,0x13,0x05,0x0c,
+  0x13,0x1f,0x13,0x1e,0x1e,0x37,0x01,0x01,0x01,0x20,0x1e,0x09,0x05,
+  0x1f,0x0c,0x13,0x0c,0x23,0x02,0x1e,0x32,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x1c,0x1e,0x12,0x14,0x27,0x0c,0x19,0x12,0x2d,0x25,0x22,0x14,
+  0x14,0x13,0x1a,0x02,0x1e,0x33,0x01,0x01,0x01,0x1c,0x1e,0x1e,0x0c,
+  0x14,0x13,0x13,0x24,0x1e,0x27,0x13,0x0b,0x19,0x06,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x2f,0x1e,0x03,0x29,0x29,0x02,0x23,0x1e,0x1e,
+  0x00,0x01,0x01,0x01,0x01,0x1e,0x1e,0x29,0x1a,0x23,0x27,0x19,0x0b,
+  0x12,0x27,0x14,0x1a,0x1e,0x1e,0x00,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x33,
+  0x1e,0x14,0x0c,0x02,0x13,0x0b,0x23,0x13,0x14,0x05,0x0b,0x1e,0x1e,
+  0x00,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,0x29,0x13,0x22,0x13,
+  0x13,0x10,0x1e,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,
+  0x1e,0x1e,0x12,0x13,0x0c,0x0c,0x13,0x05,0x13,0x06,0x1e,0x1e,0x37,
+  0x01,0x01,0x1e,0x1e,0x05,0x05,0x0c,0x0c,0x0d,0x1e,0x19,0x14,0x0d,
+  0x19,0x1f,0x1e,0x1e,0x00,0x01,0x20,0x07,0x1e,0x2a,0x0b,0x24,0x13,
+  0x05,0x23,0x22,0x22,0x10,0x1e,0x3e,0x34,0x01,0x01,0x01,0x01,0x01,
+  0x1e,0x1e,0x05,0x14,0x13,0x0b,0x1f,0x1e,0x40,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x10,0x1f,0x14,0x13,0x05,0x0c,0x1f,0x05,0x2a,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x23,0x13,0x05,0x05,0x0c,
+  0x05,0x1a,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,0x12,
+  0x14,0x22,0x1f,0x05,0x0c,0x16,0x09,0x1f,0x0b,0x24,0x13,0x05,0x23,
+  0x1e,0x1e,0x01,0x01,0x01,0x1d,0x1e,0x23,0x1f,0x0b,0x13,0x0c,0x21,
+  0x1e,0x0d,0x13,0x19,0x1f,0x05,0x09,0x1e,0x20,0x01,0x01,0x01,0x01,
+  0x1b,0x1e,0x28,0x0c,0x22,0x27,0x19,0x2d,0x1e,0x35,0x01,0x01,0x01,
+  0x01,0x1e,0x1e,0x05,0x27,0x1a,0x0d,0x26,0x0d,0x27,0x19,0x1a,0x02,
+  0x18,0x1e,0x20,0x01,0x01,0x01,0x01,0x01,0x35,0x3b,0x3c,0x34,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x1f,0x14,0x23,
+  0x13,0x0a,0x1f,0x13,0x0b,0x13,0x2a,0x1e,0x3d,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x0e,0x2e,0x08,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x08,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x1e,0x1e,
+  0x22,0x0c,0x1f,0x29,0x1e,0x1e,0x1e,0x37,0x01,0x01,0x01,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x01,0x01,0x01,0x2b,0x3e,0x1e,0x1e,0x31,0x25,0x2a,0x25,0x09,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x2d,0x25,0x16,0x28,0x10,0x1e,0x1e,0x32,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3a,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,
+  0x01,0x1c,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x11,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x16,
+  0x16,0x1e,0x1e,0x3e,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x08,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x1e,0x1e,0x02,0x0c,0x1f,0x22,
+  0x1f,0x1e,0x33,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x39,0x33,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x40,0x2c,
+  0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x3a,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x3b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x1c,0x1d,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x0f,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x2b,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x11,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x40,0x1e,0x05,0x13,0x13,0x13,0x1f,0x1e,0x1e,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x36,
+  0x3b,0x3c,0x08,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x08,0x3c,0x2e,0x0e,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x20,0x08,0x08,0x20,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,
+  0x1e,0x2d,0x29,0x12,0x0d,0x12,0x1e,0x1e,0x00,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x39,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x30,0x1c,0x3d,0x1c,0x3c,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x35,
+  0x35,0x35,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3c,0x37,0x04,
+  0x3d,0x2c,0x37,0x3c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x35,0x35,0x35,0x0e,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x2f,
+  0x37,0x2c,0x3d,0x3b,0x2b,0x2e,0x3b,0x3b,0x0e,0x01,0x01,0x01,0x01,
+  0x01,0x35,0x35,0x35,0x35,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x00,0x35,0x35,0x35,0x0e,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x20,0x35,0x20,0x0e,0x00,0x01,0x01,0x01,0x01,0x01,0x34,
+  0x35,0x35,0x0e,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x34,0x0e,0x35,0x35,0x34,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x1b,0x3b,0x1b,0x36,0x2e,0x04,0x2c,0x3a,0x17,0x3b,0x3d,0x2c,
+  0x37,0x2f,0x01,0x01,0x01,0x01,0x01,0x36,0x3a,0x08,0x2e,0x17,0x2f,
+  0x37,0x39,0x2c,0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,
+  0x2e,0x3d,0x3d,0x3d,0x04,0x2e,0x35,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x17,0x2e,0x3b,0x3b,0x35,0x1b,0x04,0x2c,0x30,0x08,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2f,0x37,0x39,0x04,0x1b,0x17,0x3b,0x3b,
+  0x2e,0x17,0x01,0x01,0x01,0x01,0x01,0x17,0x2e,0x3b,0x2e,0x0e,0x08,
+  0x30,0x3d,0x3b,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x3c,0x37,0x2c,
+  0x30,0x04,0x30,0x3b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,
+  0x3b,0x30,0x0f,0x1c,0x08,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x3d,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x35,0x39,0x40,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x2b,0x01,0x01,0x2b,0x11,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x11,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x3d,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x04,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x2c,0x20,0x01,0x01,0x11,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1c,0x01,
+  0x01,0x01,0x36,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1d,0x2b,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x40,0x30,0x01,0x01,0x01,0x01,0x35,0x32,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1b,0x01,0x01,0x01,0x01,0x01,
+  0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,0x35,
+  0x01,0x2b,0x32,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3d,
+  0x01,0x01,0x01,0x01,0x2b,0x32,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1d,0x2b,0x01,0x01,0x01,0x01,0x01,0x0e,0x33,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x40,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x21,0x09,0x2d,0x09,0x1e,0x1e,0x2b,0x01,0x01,0x2b,
+  0x3e,0x1e,0x21,0x24,0x1a,0x0d,0x27,0x24,0x21,0x1e,0x1d,0x2b,0x01,
+  0x01,0x01,0x01,0x39,0x1e,0x1e,0x2d,0x2d,0x09,0x2d,0x18,0x1e,0x38,
+  0x01,0x01,0x11,0x1e,0x2d,0x24,0x24,0x12,0x10,0x21,0x28,0x0a,0x0a,
+  0x10,0x1e,0x2e,0x01,0x38,0x1e,0x2d,0x2d,0x2d,0x2d,0x1e,0x1e,0x3c,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2f,0x1e,0x1e,0x2d,0x2d,0x2d,0x2d,
+  0x1e,0x38,0x01,0x01,0x01,0x38,0x1e,0x09,0x2d,0x09,0x10,0x1e,0x1e,
+  0x3c,0x01,0x38,0x1e,0x18,0x2d,0x09,0x10,0x18,0x1e,0x30,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x18,0x10,0x09,0x2d,0x18,
+  0x1e,0x2c,0x01,0x2e,0x1e,0x10,0x15,0x0a,0x15,0x09,0x10,0x1a,0x27,
+  0x26,0x1e,0x31,0x29,0x27,0x06,0x1e,0x1e,0x1c,0x01,0x01,0x38,0x1e,
+  0x15,0x25,0x0a,0x03,0x1e,0x06,0x0d,0x1a,0x31,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x30,0x1e,0x1e,0x28,0x12,0x12,0x0d,0x1a,0x0a,0x1e,0x1e,
+  0x30,0x01,0x01,0x01,0x39,0x1e,0x03,0x28,0x0a,0x0a,0x1e,0x21,0x1a,
+  0x27,0x29,0x1e,0x1e,0x1b,0x01,0x01,0x01,0x0f,0x1e,0x1e,0x24,0x02,
+  0x0d,0x1e,0x03,0x0a,0x25,0x28,0x03,0x1e,0x39,0x01,0x38,0x1e,0x03,
+  0x0a,0x25,0x0a,0x09,0x2d,0x12,0x24,0x1e,0x1e,0x00,0x01,0x01,0x2b,
+  0x3e,0x1e,0x03,0x06,0x27,0x12,0x24,0x29,0x31,0x1e,0x3e,0x2b,0x01,
+  0x01,0x01,0x01,0x1c,0x1e,0x0a,0x12,0x06,0x24,0x25,0x1e,0x30,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x30,0x39,0x1e,0x1e,0x13,
+  0x13,0x13,0x13,0x28,0x1e,0x1b,0x01,0x01,0x38,0x1e,0x16,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x31,0x1e,0x38,0x01,0x01,0x01,0x00,0x1e,
+  0x1e,0x1f,0x13,0x13,0x13,0x13,0x0c,0x1e,0x1e,0x01,0x01,0x1e,0x1e,
+  0x1f,0x0c,0x1f,0x13,0x0c,0x1f,0x13,0x27,0x0b,0x0d,0x1e,0x04,0x01,
+  0x1e,0x1e,0x13,0x13,0x13,0x13,0x12,0x1e,0x39,0x2c,0x1b,0x35,0x01,
+  0x01,0x01,0x37,0x1e,0x24,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,
+  0x36,0x1e,0x21,0x13,0x13,0x13,0x13,0x12,0x1e,0x1c,0x01,0x1e,0x1e,
+  0x1f,0x1f,0x13,0x0c,0x0b,0x1e,0x1d,0x3a,0x08,0x2e,0x2e,0x0e,0x01,
+  0x01,0x01,0x04,0x1e,0x1a,0x13,0x13,0x13,0x05,0x1e,0x07,0x01,0x04,
+  0x1e,0x29,0x13,0x13,0x13,0x05,0x0c,0x13,0x13,0x13,0x15,0x13,0x13,
+  0x13,0x13,0x19,0x1e,0x38,0x01,0x01,0x1e,0x1e,0x0c,0x0c,0x0c,0x1f,
+  0x1f,0x0c,0x1f,0x13,0x27,0x21,0x1e,0x3b,0x01,0x01,0x08,0x1e,0x1e,
+  0x1a,0x05,0x02,0x24,0x14,0x13,0x13,0x19,0x1e,0x1e,0x1b,0x01,0x01,
+  0x33,0x1e,0x0c,0x13,0x13,0x13,0x22,0x13,0x13,0x13,0x13,0x1a,0x1e,
+  0x38,0x01,0x01,0x01,0x38,0x1e,0x19,0x13,0x13,0x13,0x02,0x13,0x0c,
+  0x13,0x1f,0x23,0x1e,0x33,0x01,0x1e,0x1e,0x13,0x0c,0x1f,0x13,0x27,
+  0x0b,0x0c,0x19,0x2d,0x1e,0x35,0x01,0x01,0x38,0x1e,0x2a,0x02,0x24,
+  0x22,0x13,0x0c,0x14,0x14,0x2a,0x1e,0x38,0x01,0x01,0x01,0x2b,0x3e,
+  0x1e,0x19,0x13,0x1f,0x14,0x0b,0x1e,0x1e,0x04,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x13,0x13,0x13,0x13,0x2a,
+  0x1e,0x2f,0x01,0x36,0x1e,0x1e,0x13,0x13,0x13,0x0c,0x02,0x13,0x13,
+  0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x04,0x1e,0x03,0x13,0x13,0x13,
+  0x0c,0x22,0x15,0x1e,0x38,0x01,0x17,0x1e,0x31,0x0c,0x05,0x02,0x0c,
+  0x13,0x1f,0x0c,0x1f,0x0c,0x15,0x1e,0x3a,0x01,0x1e,0x1e,0x13,0x13,
+  0x13,0x13,0x16,0x1e,0x1e,0x1e,0x1e,0x33,0x2b,0x01,0x01,0x2f,0x1e,
+  0x03,0x14,0x22,0x22,0x1f,0x1e,0x40,0x01,0x01,0x20,0x1e,0x18,0x02,
+  0x14,0x0b,0x13,0x2a,0x1e,0x3c,0x01,0x38,0x1e,0x0d,0x0c,0x13,0x0b,
+  0x12,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x39,0x01,0x01,0x3a,0x1e,
+  0x26,0x0c,0x23,0x14,0x0d,0x1e,0x2c,0x01,0x3a,0x1e,0x25,0x22,0x13,
+  0x13,0x1f,0x1f,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x0c,0x0c,0x21,
+  0x1e,0x2b,0x01,0x1e,0x1e,0x23,0x0c,0x05,0x02,0x0c,0x13,0x1f,0x0c,
+  0x1f,0x29,0x1e,0x3d,0x01,0x01,0x1c,0x1e,0x24,0x05,0x22,0x0b,0x05,
+  0x14,0x27,0x0c,0x0b,0x15,0x1e,0x2c,0x01,0x01,0x39,0x1e,0x27,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x2d,0x1e,0x0e,0x01,0x0e,
+  0x1e,0x10,0x13,0x13,0x13,0x0c,0x0c,0x13,0x1f,0x0c,0x13,0x1a,0x1e,
+  0x39,0x01,0x38,0x1e,0x02,0x13,0x1f,0x0c,0x1f,0x0c,0x05,0x0b,0x18,
+  0x1e,0x34,0x01,0x01,0x1e,0x1e,0x22,0x0b,0x1f,0x22,0x26,0x14,0x0b,
+  0x15,0x0b,0x1e,0x1e,0x34,0x01,0x01,0x38,0x1e,0x2a,0x1a,0x14,0x0c,
+  0x14,0x0b,0x2a,0x18,0x1e,0x20,0x01,0x01,0x01,0x01,0x38,0x1e,0x10,
+  0x29,0x02,0x06,0x1e,0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x1b,
+  0x1e,0x15,0x13,0x13,0x13,0x02,0x1e,0x24,0x13,0x13,0x13,0x10,0x1e,
+  0x08,0x01,0x1b,0x1e,0x18,0x0d,0x13,0x13,0x1f,0x19,0x26,0x10,0x1e,
+  0x11,0x01,0x2e,0x1e,0x28,0x0c,0x13,0x13,0x05,0x1e,0x1e,0x14,0x02,
+  0x0c,0x26,0x1e,0x3a,0x01,0x1e,0x1e,0x13,0x13,0x0b,0x14,0x25,0x10,
+  0x02,0x27,0x0a,0x1e,0x07,0x34,0x01,0x0e,0x1e,0x0a,0x0a,0x0a,0x0a,
+  0x0a,0x1e,0x38,0x01,0x01,0x00,0x1e,0x1e,0x0a,0x0a,0x0a,0x0a,0x0a,
+  0x1e,0x2b,0x01,0x39,0x1e,0x02,0x0c,0x13,0x0c,0x29,0x1e,0x10,0x26,
+  0x16,0x0a,0x0a,0x2d,0x1e,0x35,0x01,0x0f,0x1e,0x28,0x1f,0x19,0x12,
+  0x29,0x1e,0x04,0x01,0x0f,0x1e,0x26,0x0c,0x19,0x0b,0x23,0x26,0x1f,
+  0x13,0x13,0x13,0x02,0x14,0x13,0x1f,0x0c,0x16,0x1e,0x3c,0x01,0x1e,
+  0x1e,0x05,0x0c,0x13,0x13,0x19,0x27,0x0c,0x14,0x02,0x19,0x1e,0x38,
+  0x01,0x01,0x11,0x1e,0x24,0x1a,0x0c,0x27,0x16,0x0a,0x05,0x0c,0x05,
+  0x22,0x1e,0x07,0x01,0x01,0x39,0x1e,0x27,0x13,0x13,0x13,0x0c,0x06,
+  0x13,0x13,0x13,0x13,0x10,0x1e,0x0e,0x01,0x3b,0x1e,0x0a,0x13,0x14,
+  0x13,0x05,0x1a,0x14,0x05,0x13,0x05,0x27,0x1e,0x39,0x01,0x11,0x1e,
+  0x27,0x23,0x0c,0x14,0x02,0x0c,0x0c,0x22,0x09,0x1e,0x20,0x01,0x01,
+  0x1e,0x1e,0x0c,0x27,0x27,0x1f,0x16,0x16,0x0a,0x03,0x16,0x1e,0x1e,
+  0x34,0x01,0x35,0x1e,0x2d,0x0c,0x1f,0x13,0x0c,0x1f,0x13,0x27,0x16,
+  0x1e,0x08,0x01,0x01,0x01,0x3b,0x1e,0x2d,0x13,0x13,0x13,0x13,0x29,
+  0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x3a,0x1e,0x26,0x13,0x13,
+  0x13,0x0c,0x02,0x1f,0x13,0x13,0x13,0x29,0x1e,0x30,0x01,0x04,0x1e,
+  0x1a,0x13,0x13,0x13,0x13,0x0c,0x13,0x1f,0x1e,0x33,0x01,0x2e,0x1e,
+  0x0a,0x13,0x1f,0x1f,0x14,0x1e,0x1e,0x0c,0x13,0x05,0x28,0x1e,0x3a,
+  0x01,0x1e,0x1e,0x19,0x05,0x02,0x24,0x23,0x13,0x13,0x05,0x05,0x16,
+  0x1e,0x1c,0x01,0x2e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x01,
+  0x01,0x35,0x1e,0x2d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1b,0x01,0x39,
+  0x1e,0x0d,0x0c,0x0c,0x22,0x12,0x1e,0x1f,0x0c,0x1f,0x13,0x0c,0x10,
+  0x1e,0x0e,0x01,0x0f,0x1e,0x26,0x14,0x0b,0x0c,0x0d,0x1e,0x04,0x01,
+  0x0f,0x1e,0x28,0x13,0x05,0x0b,0x24,0x1e,0x1f,0x02,0x22,0x1f,0x1e,
+  0x0b,0x0c,0x02,0x0c,0x28,0x1e,0x1b,0x01,0x1e,0x1e,0x0c,0x13,0x1f,
+  0x1f,0x1e,0x2a,0x1f,0x0c,0x13,0x0b,0x1e,0x07,0x01,0x20,0x1e,0x09,
+  0x14,0x14,0x14,0x14,0x10,0x1e,0x0d,0x05,0x0b,0x27,0x2d,0x1e,0x35,
+  0x01,0x39,0x1e,0x27,0x13,0x13,0x13,0x27,0x1e,0x13,0x13,0x13,0x13,
+  0x03,0x1e,0x17,0x01,0x1b,0x1e,0x15,0x13,0x13,0x0c,0x02,0x1e,0x02,
+  0x22,0x13,0x1f,0x27,0x1e,0x39,0x01,0x11,0x1e,0x19,0x05,0x1f,0x0c,
+  0x13,0x05,0x24,0x23,0x18,0x1e,0x34,0x01,0x01,0x1e,0x1e,0x14,0x14,
+  0x1f,0x27,0x15,0x1a,0x06,0x16,0x1e,0x1e,0x3d,0x01,0x01,0x00,0x1e,
+  0x1e,0x02,0x02,0x0c,0x13,0x1f,0x0c,0x0b,0x21,0x1e,0x2b,0x01,0x01,
+  0x01,0x30,0x1e,0x29,0x1f,0x1f,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x2a,0x1e,0x2f,0x01,0x1b,0x1e,0x15,0x13,0x13,0x13,0x13,0x13,0x0b,
+  0x1a,0x27,0x0b,0x26,0x1e,0x3a,0x01,0x1b,0x1e,0x25,0x13,0x13,0x13,
+  0x0c,0x0c,0x13,0x12,0x1e,0x11,0x01,0x3b,0x1e,0x0a,0x13,0x05,0x13,
+  0x23,0x1e,0x1e,0x22,0x14,0x13,0x15,0x1e,0x3a,0x01,0x1e,0x1e,0x05,
+  0x22,0x0b,0x0c,0x14,0x27,0x0c,0x0b,0x26,0x27,0x1e,0x38,0x01,0x3c,
+  0x1e,0x16,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x20,0x1e,0x09,
+  0x05,0x13,0x13,0x13,0x25,0x1e,0x08,0x01,0x39,0x1e,0x27,0x0c,0x0c,
+  0x13,0x0a,0x09,0x0c,0x05,0x02,0x0c,0x03,0x1e,0x38,0x01,0x01,0x0f,
+  0x1e,0x28,0x0c,0x02,0x19,0x0d,0x1e,0x04,0x01,0x0f,0x1e,0x26,0x19,
+  0x12,0x14,0x06,0x1e,0x0c,0x14,0x22,0x0c,0x1e,0x02,0x13,0x23,0x1f,
+  0x0a,0x1e,0x3b,0x01,0x1e,0x1e,0x1f,0x13,0x05,0x13,0x1e,0x21,0x13,
+  0x22,0x14,0x22,0x1e,0x3e,0x01,0x0e,0x1e,0x10,0x29,0x14,0x1f,0x22,
+  0x2a,0x1e,0x14,0x14,0x05,0x14,0x18,0x1e,0x34,0x01,0x39,0x1e,0x27,
+  0x13,0x13,0x13,0x24,0x1e,0x13,0x22,0x13,0x13,0x28,0x1e,0x2e,0x01,
+  0x2e,0x1e,0x28,0x1f,0x1f,0x13,0x1f,0x1e,0x02,0x13,0x0c,0x19,0x27,
+  0x1e,0x39,0x01,0x11,0x1e,0x02,0x1f,0x13,0x22,0x14,0x2d,0x1e,0x09,
+  0x1e,0x04,0x01,0x01,0x01,0x38,0x1e,0x21,0x23,0x14,0x1f,0x0b,0x22,
+  0x22,0x0c,0x29,0x1e,0x1e,0x00,0x01,0x01,0x3d,0x1e,0x18,0x0b,0x05,
+  0x23,0x0c,0x27,0x1e,0x1e,0x1e,0x00,0x01,0x01,0x01,0x2c,0x1e,0x29,
+  0x14,0x06,0x1f,0x0c,0x24,0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,
+  0x37,0x1e,0x24,0x13,0x13,0x13,0x29,0x1e,0x18,0x16,0x03,0x16,0x21,
+  0x1e,0x36,0x01,0x35,0x40,0x1e,0x15,0x14,0x13,0x1f,0x14,0x21,0x1e,
+  0x1e,0x08,0x01,0x1b,0x1e,0x28,0x13,0x23,0x05,0x1f,0x25,0x1e,0x05,
+  0x0c,0x0c,0x26,0x1e,0x3a,0x01,0x1e,0x1e,0x1a,0x0c,0x27,0x0b,0x22,
+  0x19,0x0c,0x05,0x1f,0x22,0x1e,0x3e,0x01,0x3c,0x1e,0x16,0x13,0x13,
+  0x13,0x13,0x1e,0x1e,0x01,0x01,0x20,0x1e,0x09,0x1f,0x13,0x13,0x13,
+  0x25,0x1e,0x08,0x01,0x39,0x1e,0x02,0x13,0x05,0x0c,0x1a,0x27,0x0c,
+  0x13,0x13,0x1a,0x1e,0x32,0x2b,0x01,0x01,0x0f,0x1e,0x15,0x22,0x14,
+  0x1f,0x06,0x1e,0x04,0x01,0x0f,0x1e,0x26,0x0c,0x0b,0x22,0x12,0x1e,
+  0x23,0x0d,0x1f,0x23,0x1e,0x23,0x05,0x27,0x1f,0x0a,0x1e,0x3b,0x01,
+  0x1e,0x1e,0x05,0x13,0x23,0x05,0x1e,0x31,0x0c,0x05,0x0c,0x0b,0x1e,
+  0x3e,0x01,0x34,0x1e,0x18,0x0b,0x05,0x27,0x05,0x18,0x1e,0x22,0x0c,
+  0x0b,0x14,0x10,0x1e,0x0e,0x01,0x39,0x1e,0x0d,0x13,0x13,0x13,0x27,
+  0x1e,0x13,0x0c,0x13,0x13,0x21,0x1e,0x36,0x01,0x0f,0x1e,0x26,0x1f,
+  0x13,0x05,0x0b,0x1e,0x23,0x1f,0x13,0x1f,0x27,0x1e,0x39,0x01,0x11,
+  0x1e,0x02,0x1f,0x0c,0x05,0x02,0x1e,0x38,0x1e,0x04,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x1e,0x09,0x16,0x28,0x1a,0x1f,0x19,0x22,0x1f,0x21,
+  0x1e,0x2b,0x01,0x01,0x01,0x39,0x1e,0x27,0x13,0x05,0x1f,0x1a,0x1e,
+  0x1e,0x3d,0x01,0x01,0x01,0x01,0x38,0x1e,0x1a,0x14,0x0c,0x0c,0x12,
+  0x1e,0x0c,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x2f,0x1e,0x03,0x0c,
+  0x14,0x13,0x19,0x09,0x27,0x13,0x1f,0x13,0x03,0x1e,0x17,0x01,0x01,
+  0x17,0x1e,0x03,0x13,0x0c,0x02,0x22,0x1e,0x1e,0x3d,0x01,0x01,0x3c,
+  0x1e,0x16,0x0c,0x13,0x0c,0x0b,0x05,0x1f,0x02,0x05,0x1f,0x15,0x1e,
+  0x3a,0x01,0x1e,0x1e,0x14,0x14,0x14,0x0c,0x1e,0x21,0x05,0x0b,0x02,
+  0x0b,0x1e,0x1d,0x01,0x3c,0x1e,0x16,0x13,0x13,0x13,0x13,0x1e,0x1e,
+  0x01,0x01,0x20,0x1e,0x09,0x1f,0x13,0x13,0x13,0x25,0x1e,0x08,0x01,
+  0x39,0x1e,0x02,0x05,0x05,0x13,0x0c,0x0c,0x13,0x1f,0x22,0x1e,0x1e,
+  0x3a,0x01,0x01,0x01,0x0f,0x1e,0x28,0x14,0x22,0x05,0x0d,0x1e,0x04,
+  0x01,0x0f,0x1e,0x26,0x1a,0x19,0x13,0x24,0x1e,0x1f,0x0b,0x14,0x05,
+  0x1e,0x19,0x13,0x05,0x22,0x0a,0x1e,0x3b,0x01,0x1e,0x1e,0x1f,0x0b,
+  0x13,0x0c,0x1e,0x31,0x1f,0x02,0x05,0x0b,0x1e,0x3e,0x01,0x01,0x11,
+  0x1e,0x29,0x1f,0x27,0x02,0x1a,0x12,0x1f,0x19,0x22,0x22,0x1e,0x3e,
+  0x01,0x01,0x39,0x1e,0x27,0x13,0x13,0x13,0x1f,0x23,0x13,0x1f,0x13,
+  0x14,0x10,0x1e,0x0e,0x01,0x17,0x1e,0x03,0x1f,0x0c,0x13,0x13,0x1f,
+  0x0c,0x13,0x0b,0x0b,0x02,0x1e,0x39,0x01,0x11,0x1e,0x0d,0x05,0x1f,
+  0x02,0x0d,0x1e,0x04,0x20,0x01,0x01,0x01,0x01,0x00,0x1e,0x1e,0x1a,
+  0x29,0x0b,0x24,0x31,0x27,0x23,0x13,0x06,0x31,0x1e,0x17,0x01,0x01,
+  0x01,0x38,0x1e,0x19,0x1f,0x23,0x13,0x1a,0x1e,0x11,0x01,0x01,0x01,
+  0x01,0x00,0x1e,0x1e,0x02,0x23,0x27,0x13,0x24,0x1e,0x0c,0x13,0x13,
+  0x13,0x2a,0x1e,0x2f,0x01,0x01,0x1e,0x1e,0x0c,0x23,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x08,0x1e,0x16,0x1f,
+  0x13,0x0c,0x14,0x18,0x1e,0x34,0x01,0x01,0x20,0x1e,0x1e,0x05,0x05,
+  0x0c,0x1f,0x1f,0x0c,0x13,0x0c,0x23,0x15,0x1e,0x3a,0x01,0x1e,0x1e,
+  0x14,0x1f,0x22,0x1f,0x1e,0x2d,0x14,0x05,0x0c,0x1a,0x1e,0x1d,0x01,
+  0x3c,0x1e,0x16,0x13,0x13,0x22,0x13,0x1e,0x1e,0x01,0x01,0x20,0x1e,
+  0x09,0x1f,0x13,0x13,0x13,0x25,0x1e,0x08,0x01,0x39,0x1e,0x02,0x1f,
+  0x23,0x13,0x0c,0x1f,0x13,0x05,0x14,0x1e,0x1e,0x08,0x01,0x01,0x01,
+  0x0f,0x1e,0x26,0x29,0x14,0x22,0x26,0x1e,0x04,0x01,0x3a,0x1e,0x26,
+  0x05,0x22,0x14,0x24,0x1e,0x1a,0x19,0x13,0x02,0x1e,0x19,0x05,0x22,
+  0x1f,0x0a,0x1e,0x3b,0x01,0x1e,0x1e,0x0c,0x05,0x05,0x0c,0x1e,0x31,
+  0x0c,0x13,0x0c,0x27,0x1e,0x32,0x01,0x01,0x37,0x1e,0x28,0x1f,0x14,
+  0x0c,0x14,0x27,0x0c,0x19,0x12,0x29,0x1e,0x3d,0x01,0x01,0x39,0x1e,
+  0x0d,0x13,0x13,0x13,0x14,0x1f,0x13,0x0c,0x13,0x0c,0x09,0x1e,0x20,
+  0x01,0x20,0x1e,0x09,0x14,0x13,0x05,0x0c,0x13,0x1f,0x13,0x13,0x0c,
+  0x27,0x1e,0x39,0x01,0x38,0x1e,0x27,0x1f,0x0c,0x13,0x1a,0x1e,0x39,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x23,0x14,0x22,0x29,0x14,
+  0x02,0x06,0x13,0x0b,0x1e,0x3e,0x00,0x01,0x01,0x01,0x11,0x1e,0x02,
+  0x0c,0x23,0x05,0x29,0x1e,0x1e,0x2c,0x01,0x01,0x01,0x01,0x1d,0x1e,
+  0x02,0x1f,0x13,0x23,0x06,0x1e,0x22,0x13,0x13,0x13,0x2a,0x1e,0x2f,
+  0x01,0x01,0x38,0x1e,0x18,0x0b,0x13,0x0b,0x13,0x13,0x1f,0x13,0x1e,
+  0x1e,0x38,0x01,0x01,0x01,0x08,0x1e,0x16,0x13,0x05,0x22,0x14,0x1e,
+  0x1e,0x00,0x01,0x01,0x01,0x38,0x1e,0x1e,0x19,0x14,0x23,0x0a,0x12,
+  0x05,0x0c,0x05,0x15,0x1e,0x3a,0x01,0x1e,0x1e,0x05,0x27,0x05,0x05,
+  0x1e,0x10,0x0c,0x0b,0x1f,0x23,0x1e,0x1d,0x01,0x3c,0x1e,0x16,0x13,
+  0x13,0x22,0x0c,0x1e,0x1e,0x01,0x01,0x20,0x1e,0x09,0x1f,0x13,0x13,
+  0x0c,0x25,0x1e,0x08,0x01,0x39,0x1e,0x02,0x13,0x13,0x0c,0x0d,0x02,
+  0x13,0x23,0x05,0x29,0x1e,0x38,0x01,0x01,0x01,0x0f,0x1e,0x26,0x02,
+  0x02,0x1f,0x0d,0x1e,0x04,0x01,0x04,0x1e,0x0d,0x0b,0x1f,0x0b,0x26,
+  0x1e,0x05,0x19,0x22,0x23,0x1e,0x13,0x05,0x0c,0x23,0x24,0x1e,0x1c,
+  0x01,0x1e,0x1e,0x0c,0x27,0x13,0x14,0x1e,0x0a,0x05,0x05,0x0c,0x14,
+  0x1e,0x1e,0x01,0x01,0x36,0x1e,0x1e,0x16,0x0b,0x05,0x14,0x22,0x1f,
+  0x05,0x06,0x1e,0x1e,0x3b,0x01,0x01,0x39,0x1e,0x0d,0x13,0x13,0x13,
+  0x27,0x27,0x13,0x13,0x13,0x03,0x1e,0x38,0x01,0x01,0x01,0x38,0x1e,
+  0x15,0x13,0x05,0x0c,0x26,0x19,0x13,0x0c,0x0c,0x02,0x1e,0x39,0x01,
+  0x1e,0x1e,0x23,0x13,0x05,0x05,0x05,0x1e,0x33,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x31,0x23,0x14,0x19,0x05,0x22,0x22,0x05,0x18,
+  0x1e,0x04,0x01,0x01,0x01,0x01,0x1d,0x1e,0x23,0x0b,0x0b,0x0b,0x02,
+  0x06,0x09,0x1e,0x35,0x01,0x01,0x01,0x2c,0x1e,0x1a,0x0b,0x05,0x0c,
+  0x0b,0x22,0x1f,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x25,0x0a,0x2a,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x08,0x1e,0x16,0x0c,0x13,0x13,0x14,0x1e,0x1e,0x00,0x01,0x01,
+  0x20,0x1e,0x09,0x1a,0x27,0x23,0x06,0x1e,0x0d,0x13,0x14,0x19,0x12,
+  0x1e,0x1c,0x01,0x1e,0x1e,0x1f,0x27,0x02,0x0c,0x1e,0x10,0x19,0x22,
+  0x13,0x29,0x1e,0x1d,0x01,0x3c,0x1e,0x16,0x13,0x13,0x1f,0x14,0x1e,
+  0x1e,0x01,0x01,0x20,0x1e,0x09,0x1f,0x22,0x13,0x0c,0x25,0x1e,0x08,
+  0x01,0x39,0x1e,0x02,0x1f,0x1f,0x13,0x25,0x16,0x0c,0x13,0x0c,0x0b,
+  0x2d,0x1e,0x2e,0x01,0x01,0x0f,0x1e,0x26,0x14,0x0c,0x0b,0x24,0x1e,
+  0x04,0x01,0x1b,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3c,0x01,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,
+  0x01,0x36,0x07,0x1e,0x1e,0x03,0x16,0x16,0x03,0x1e,0x1e,0x1e,0x3b,
+  0x01,0x01,0x01,0x39,0x1e,0x15,0x13,0x13,0x13,0x27,0x1e,0x21,0x28,
+  0x18,0x1e,0x32,0x2b,0x01,0x01,0x01,0x36,0x1e,0x1e,0x18,0x28,0x31,
+  0x1e,0x27,0x0c,0x1f,0x0c,0x27,0x1e,0x39,0x01,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1d,
+  0x1e,0x1e,0x09,0x16,0x25,0x16,0x18,0x1e,0x1e,0x11,0x34,0x01,0x01,
+  0x01,0x01,0x04,0x1e,0x0d,0x1f,0x0b,0x1f,0x13,0x05,0x31,0x1e,0x36,
+  0x01,0x01,0x01,0x1c,0x1e,0x24,0x14,0x13,0x1a,0x19,0x13,0x0b,0x13,
+  0x13,0x13,0x03,0x1e,0x2f,0x01,0x01,0x01,0x20,0x2c,0x40,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x3c,0x1e,0x16,
+  0x13,0x05,0x0c,0x14,0x1e,0x1e,0x00,0x01,0x01,0x35,0x1e,0x2d,0x05,
+  0x0c,0x1f,0x0c,0x14,0x23,0x0c,0x13,0x14,0x31,0x1e,0x3c,0x01,0x1e,
+  0x1e,0x1f,0x14,0x0c,0x14,0x1e,0x10,0x19,0x12,0x13,0x19,0x1e,0x1d,
+  0x01,0x3c,0x1e,0x16,0x13,0x13,0x14,0x1f,0x1e,0x1e,0x01,0x01,0x20,
+  0x1e,0x09,0x05,0x13,0x13,0x05,0x25,0x1e,0x08,0x01,0x38,0x1e,0x27,
+  0x1f,0x13,0x0b,0x28,0x1e,0x05,0x05,0x0c,0x1f,0x0d,0x1e,0x1e,0x3c,
+  0x01,0x3a,0x1e,0x26,0x0b,0x22,0x0b,0x12,0x1e,0x04,0x01,0x01,0x1b,
+  0x38,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x3c,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2f,0x01,0x01,0x01,0x01,0x39,
+  0x1e,0x27,0x1f,0x23,0x13,0x02,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,
+  0x01,0x01,0x01,0x01,0x36,0x38,0x1e,0x1e,0x1e,0x1e,0x19,0x1f,0x19,
+  0x0c,0x27,0x1e,0x39,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x33,0x39,
+  0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x11,0x0e,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,
+  0x2a,0x23,0x13,0x19,0x05,0x0c,0x16,0x1e,0x08,0x01,0x01,0x01,0x08,
+  0x1e,0x1e,0x24,0x13,0x05,0x23,0x2a,0x22,0x13,0x13,0x13,0x28,0x1e,
+  0x2e,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x08,0x3b,0x3c,0x34,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3a,0x1e,0x15,0x13,0x05,0x0c,0x1f,
+  0x09,0x1e,0x20,0x01,0x01,0x01,0x33,0x1e,0x23,0x0c,0x13,0x0c,0x05,
+  0x19,0x14,0x1f,0x0b,0x1e,0x32,0x01,0x01,0x1e,0x1e,0x1f,0x0b,0x05,
+  0x14,0x1e,0x2a,0x05,0x0c,0x1f,0x0b,0x1e,0x1e,0x01,0x3a,0x1e,0x26,
+  0x13,0x13,0x13,0x22,0x1e,0x1e,0x01,0x01,0x35,0x1e,0x09,0x05,0x23,
+  0x13,0x1f,0x25,0x1e,0x08,0x01,0x1e,0x1e,0x13,0x05,0x13,0x13,0x23,
+  0x1e,0x02,0x13,0x14,0x23,0x13,0x24,0x1e,0x37,0x01,0x04,0x1e,0x24,
+  0x23,0x14,0x0d,0x27,0x1e,0x07,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x17,0x08,0x08,
+  0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1d,0x1e,0x19,0x05,0x14,
+  0x13,0x22,0x1e,0x3e,0x2e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x1b,0x33,0x1e,0x1a,0x0c,0x0c,0x13,0x0b,0x1e,0x1d,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x3b,0x3b,0x08,0x20,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x3e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x07,0x20,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x03,
+  0x0a,0x09,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x17,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x11,0x1e,0x1e,0x12,0x05,0x0c,0x14,0x23,0x05,0x03,0x1e,
+  0x1e,0x30,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x17,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x01,0x20,0x40,0x1e,0x1e,0x05,0x1f,0x13,0x13,0x25,0x1e,
+  0x08,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x3c,0x01,0x2e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x04,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x30,0x1e,0x0a,0x29,0x12,0x29,0x28,0x1e,0x3d,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x39,0x1e,0x15,0x12,0x24,0x29,0x0a,0x1e,0x30,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x17,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x0e,0x11,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x32,0x30,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x2b,0x01,0x01,0x17,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x2e,
+  0x1e,0x25,0x05,0x13,0x0b,0x0c,0x0b,0x25,0x1e,0x1b,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x32,0x30,0x1d,0x1e,0x1e,0x1e,0x1e,0x38,0x3c,
+  0x01,0x01,0x01,0x2e,0x38,0x1e,0x1e,0x1e,0x07,0x04,0x20,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x0e,0x33,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x0e,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x33,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x00,0x17,0x1b,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x08,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x33,0x11,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,0x0d,0x05,0x13,
+  0x0c,0x1f,0x22,0x09,0x1e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x2e,0x04,
+  0x1c,0x3d,0x2e,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x1b,0x30,0x1c,0x30,0x3b,0x0e,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x0f,0x1e,0x06,0x13,0x14,0x27,0x0c,0x16,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x17,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x40,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x17,0x38,0x1e,
+  0x33,0x07,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x2e,0x3b,0x2e,0x08,
+  0x35,0x08,0x2e,0x3b,0x2e,0x0e,0x01,0x01,0x01,0x01,0x20,0x08,0x2e,
+  0x3b,0x3b,0x0e,0x01,0x36,0x3a,0x08,0x3b,0x1b,0x2b,0x01,0x01,0x01,
+  0x01,0x35,0x08,0x2e,0x1b,0x2f,0x00,0x17,0x3b,0x3b,0x3b,0x3b,0x08,
+  0x35,0x2f,0x1b,0x3b,0x1b,0x2b,0x01,0x01,0x01,0x01,0x01,0x2b,0x2e,
+  0x3b,0x1b,0x17,0x00,0x17,0x2e,0x08,0x2e,0x3c,0x34,0x01,0x01,0x01,
+  0x01,0x0e,0x3b,0x3b,0x08,0x3a,0x36,0x0e,0x3b,0x3b,0x08,0x3a,0x17,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x1b,0x08,0x3b,0x3b,0x3b,0x08,
+  0x3a,0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x37,0x11,
+  0x1d,0x32,0x1d,0x30,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x35,0x3b,0x30,0x0f,0x37,0x37,0x2f,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x00,0x0f,0x11,0x32,0x1d,0x32,0x30,0x3c,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x20,0x3a,0x2c,0x32,0x1d,0x1d,0x3d,0x3b,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x3a,0x37,0x0f,
+  0x0f,0x1c,0x3b,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1b,0x37,
+  0x0f,0x0f,0x0f,0x0f,0x0f,0x1c,0x1b,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x20,0x3c,0x1c,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,
+  0x1c,0x0f,0x2f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x08,0x30,0x32,
+  0x1d,0x32,0x1d,0x1c,0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x11,0x1e,0x1e,0x1e,0x1e,0x1e,0x40,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x11,0x01,0x01,0x2b,0x07,0x1e,0x1e,0x1e,0x1e,0x1e,0x39,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x2b,0x01,0x01,0x04,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x32,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x40,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x04,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x32,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x3e,0x34,0x01,0x01,0x39,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x33,0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,0x01,0x01,0x01,
+  0x00,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x11,0x01,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x39,0x01,0x01,0x01,0x01,0x37,0x32,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x08,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x33,
+  0x0e,0x01,0x01,0x01,0x01,0x30,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x17,
+  0x1c,0x1d,0x1e,0x1e,0x3e,0x38,0x1c,0x0e,0x01,0x01,0x01,0x01,0x35,
+  0x07,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x04,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,
+  0x10,0x0a,0x0a,0x28,0x16,0x1e,0x25,0x28,0x0a,0x28,0x10,0x1e,0x2e,
+  0x01,0x38,0x1e,0x16,0x25,0x0a,0x0a,0x10,0x1e,0x10,0x15,0x25,0x0a,
+  0x28,0x1e,0x38,0x01,0x20,0x1e,0x09,0x25,0x0a,0x28,0x2a,0x1e,0x03,
+  0x16,0x16,0x25,0x25,0x16,0x1e,0x2a,0x28,0x25,0x28,0x09,0x1e,0x20,
+  0x01,0x2b,0x1e,0x21,0x28,0x25,0x15,0x03,0x1e,0x03,0x25,0x25,0x0a,
+  0x03,0x1e,0x3c,0x01,0x35,0x1e,0x2d,0x0a,0x0a,0x25,0x26,0x1e,0x1e,
+  0x0a,0x0a,0x25,0x26,0x31,0x1e,0x0f,0x01,0x01,0x3d,0x1e,0x21,0x15,
+  0x25,0x25,0x25,0x0a,0x16,0x15,0x10,0x1e,0x3b,0x01,0x01,0x01,0x01,
+  0x38,0x1e,0x1e,0x0a,0x19,0x0b,0x22,0x0b,0x28,0x1e,0x1e,0x38,0x20,
+  0x01,0x01,0x20,0x07,0x1e,0x18,0x25,0x12,0x06,0x06,0x26,0x2d,0x1e,
+  0x35,0x01,0x01,0x37,0x1e,0x1e,0x03,0x02,0x22,0x23,0x22,0x24,0x21,
+  0x1e,0x1e,0x2b,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x25,0x27,0x22,
+  0x23,0x0b,0x0d,0x21,0x1e,0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x17,
+  0x1e,0x18,0x26,0x24,0x06,0x06,0x24,0x25,0x1e,0x11,0x01,0x01,0x01,
+  0x00,0x1e,0x1e,0x28,0x24,0x26,0x26,0x06,0x06,0x06,0x24,0x28,0x1e,
+  0x1e,0x00,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x33,0x30,0x01,0x01,0x01,0x39,0x1e,0x16,0x24,0x06,
+  0x26,0x06,0x06,0x06,0x06,0x24,0x06,0x09,0x1e,0x20,0x01,0x01,0x34,
+  0x3e,0x1e,0x10,0x24,0x19,0x0b,0x22,0x23,0x06,0x1e,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x0d,0x1f,0x19,0x05,
+  0x02,0x1e,0x14,0x13,0x13,0x05,0x0d,0x1e,0x04,0x01,0x1e,0x1e,0x13,
+  0x0c,0x13,0x0c,0x23,0x1e,0x24,0x0c,0x0c,0x22,0x13,0x1e,0x1e,0x01,
+  0x36,0x1e,0x31,0x13,0x27,0x0b,0x0c,0x18,0x05,0x22,0x22,0x22,0x23,
+  0x14,0x1e,0x27,0x22,0x29,0x24,0x03,0x1e,0x2f,0x01,0x3b,0x1e,0x0a,
+  0x27,0x0b,0x0c,0x19,0x1e,0x05,0x14,0x14,0x0b,0x0a,0x1e,0x3b,0x01,
+  0x3c,0x1e,0x16,0x13,0x05,0x05,0x13,0x2a,0x1e,0x13,0x13,0x13,0x13,
+  0x19,0x1e,0x11,0x01,0x01,0x32,0x1e,0x0b,0x22,0x13,0x0c,0x0c,0x0c,
+  0x1f,0x13,0x29,0x1e,0x3d,0x01,0x01,0x01,0x0f,0x1e,0x2d,0x0c,0x23,
+  0x14,0x13,0x13,0x13,0x13,0x13,0x18,0x1e,0x2c,0x01,0x01,0x3b,0x1e,
+  0x16,0x1f,0x0c,0x05,0x0c,0x0c,0x13,0x25,0x1e,0x08,0x01,0x0e,0x33,
+  0x1e,0x27,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x28,0x1e,0x32,0x00,
+  0x01,0x01,0x38,0x1e,0x1e,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x29,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x06,0x13,0x13,
+  0x0c,0x0c,0x0c,0x1f,0x1e,0x33,0x01,0x01,0x01,0x34,0x1e,0x18,0x13,
+  0x1f,0x14,0x13,0x14,0x14,0x13,0x05,0x02,0x2d,0x1e,0x0e,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x1e,0x06,0x0b,0x13,0x13,0x14,0x23,0x0a,0x1e,
+  0x1e,0x30,0x01,0x01,0x40,0x1e,0x05,0x0c,0x0c,0x13,0x13,0x14,0x0c,
+  0x13,0x0b,0x1f,0x16,0x1e,0x3c,0x01,0x01,0x04,0x1e,0x2a,0x13,0x05,
+  0x02,0x14,0x13,0x0c,0x23,0x14,0x1e,0x1e,0x3c,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x3a,0x1e,0x28,0x1f,0x05,0x22,0x23,0x1e,0x14,0x27,
+  0x0c,0x0b,0x25,0x1e,0x3a,0x01,0x38,0x1e,0x29,0x0c,0x13,0x1f,0x19,
+  0x1e,0x27,0x0c,0x0c,0x13,0x26,0x1e,0x38,0x01,0x01,0x38,0x1e,0x19,
+  0x1f,0x0c,0x19,0x03,0x23,0x06,0x0d,0x1f,0x1a,0x02,0x2d,0x19,0x0b,
+  0x0b,0x22,0x18,0x1e,0x34,0x01,0x0e,0x33,0x1e,0x24,0x05,0x05,0x22,
+  0x1a,0x24,0x0d,0x0c,0x0d,0x1e,0x33,0x0e,0x01,0x34,0x3e,0x1e,0x19,
+  0x0b,0x26,0x22,0x2a,0x1e,0x1f,0x1f,0x13,0x13,0x2d,0x1e,0x0f,0x01,
+  0x01,0x3d,0x1e,0x06,0x13,0x02,0x02,0x0c,0x05,0x02,0x0c,0x26,0x1e,
+  0x0f,0x01,0x01,0x01,0x11,0x1e,0x29,0x1f,0x19,0x12,0x22,0x14,0x13,
+  0x0b,0x14,0x05,0x1e,0x40,0x01,0x01,0x30,0x1e,0x29,0x13,0x22,0x1f,
+  0x13,0x02,0x14,0x03,0x1e,0x17,0x01,0x3b,0x1e,0x25,0x12,0x14,0x13,
+  0x13,0x27,0x14,0x13,0x13,0x13,0x2a,0x1e,0x3b,0x01,0x01,0x1e,0x1e,
+  0x13,0x13,0x13,0x13,0x13,0x0b,0x13,0x13,0x13,0x13,0x31,0x1e,0x36,
+  0x01,0x01,0x01,0x00,0x1e,0x1e,0x14,0x13,0x1f,0x19,0x13,0x0c,0x27,
+  0x1e,0x38,0x01,0x01,0x01,0x00,0x1e,0x1e,0x23,0x0c,0x14,0x0c,0x14,
+  0x22,0x0b,0x23,0x0b,0x18,0x1e,0x20,0x01,0x01,0x01,0x1b,0x1e,0x18,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x0b,0x1e,0x1d,0x01,0x01,
+  0x39,0x1e,0x24,0x13,0x1f,0x0b,0x1f,0x0c,0x0c,0x13,0x0c,0x1f,0x09,
+  0x1e,0x20,0x01,0x01,0x07,0x1e,0x14,0x1f,0x23,0x13,0x02,0x27,0x1f,
+  0x19,0x12,0x24,0x1e,0x11,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3a,
+  0x1e,0x15,0x0d,0x1a,0x0c,0x27,0x1e,0x13,0x05,0x0c,0x05,0x15,0x1e,
+  0x3a,0x01,0x3b,0x1e,0x31,0x1f,0x05,0x05,0x23,0x1e,0x22,0x13,0x05,
+  0x0c,0x18,0x1e,0x2f,0x01,0x01,0x1c,0x1e,0x06,0x02,0x1f,0x19,0x16,
+  0x27,0x22,0x1a,0x23,0x14,0x0b,0x16,0x0d,0x14,0x1a,0x29,0x1e,0x11,
+  0x01,0x01,0x01,0x08,0x1e,0x09,0x26,0x14,0x22,0x0b,0x22,0x23,0x22,
+  0x1e,0x1e,0x3d,0x01,0x01,0x01,0x37,0x1e,0x25,0x05,0x1f,0x0c,0x28,
+  0x25,0x14,0x06,0x0b,0x0b,0x1e,0x32,0x01,0x01,0x01,0x2f,0x1e,0x18,
+  0x09,0x10,0x05,0x0c,0x13,0x13,0x22,0x09,0x1e,0x17,0x01,0x01,0x34,
+  0x1e,0x18,0x23,0x14,0x0b,0x0c,0x21,0x09,0x05,0x02,0x24,0x22,0x09,
+  0x1e,0x20,0x01,0x3b,0x1e,0x18,0x10,0x31,0x0c,0x0c,0x1f,0x13,0x03,
+  0x1e,0x17,0x01,0x37,0x1e,0x06,0x0c,0x1f,0x19,0x24,0x1e,0x24,0x14,
+  0x13,0x13,0x12,0x1e,0x30,0x01,0x01,0x38,0x1e,0x23,0x22,0x0b,0x23,
+  0x2d,0x1e,0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x01,0x01,0x2e,
+  0x1e,0x31,0x13,0x13,0x0b,0x05,0x13,0x0c,0x23,0x1e,0x38,0x01,0x01,
+  0x01,0x00,0x1e,0x1e,0x14,0x0c,0x1f,0x24,0x1e,0x03,0x2a,0x21,0x1e,
+  0x1e,0x04,0x01,0x01,0x01,0x01,0x2c,0x1e,0x0d,0x13,0x13,0x13,0x05,
+  0x05,0x13,0x13,0x13,0x13,0x31,0x1e,0x08,0x01,0x3b,0x1e,0x09,0x21,
+  0x21,0x1e,0x0a,0x0b,0x0c,0x0c,0x22,0x1a,0x1e,0x39,0x01,0x01,0x0e,
+  0x1e,0x10,0x0b,0x0c,0x19,0x14,0x1e,0x2a,0x14,0x0b,0x0c,0x05,0x1e,
+  0x1e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3a,0x1e,0x15,0x0c,0x14,
+  0x14,0x14,0x1e,0x02,0x0d,0x05,0x0b,0x28,0x1e,0x3a,0x01,0x01,0x33,
+  0x1e,0x14,0x1f,0x22,0x0b,0x21,0x05,0x05,0x05,0x13,0x1e,0x1e,0x01,
+  0x01,0x01,0x3b,0x1e,0x0a,0x13,0x05,0x1a,0x12,0x12,0x26,0x24,0x02,
+  0x22,0x0b,0x0a,0x19,0x23,0x14,0x0a,0x1e,0x3a,0x01,0x01,0x01,0x01,
+  0x38,0x1e,0x16,0x1a,0x1a,0x27,0x26,0x27,0x28,0x1e,0x38,0x00,0x01,
+  0x01,0x01,0x35,0x1e,0x18,0x23,0x02,0x13,0x24,0x06,0x14,0x1f,0x0c,
+  0x15,0x1e,0x30,0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x0d,0x0c,0x13,
+  0x1f,0x1f,0x2a,0x1e,0x39,0x01,0x01,0x01,0x0e,0x1e,0x10,0x0d,0x0c,
+  0x02,0x19,0x1e,0x1e,0x22,0x0b,0x1f,0x14,0x10,0x1e,0x0e,0x01,0x01,
+  0x04,0x1e,0x1e,0x1e,0x0c,0x05,0x02,0x0c,0x03,0x1e,0x17,0x01,0x3c,
+  0x1e,0x2d,0x25,0x03,0x2d,0x1e,0x31,0x1f,0x14,0x27,0x0c,0x12,0x1e,
+  0x3d,0x01,0x00,0x1e,0x1e,0x1e,0x1e,0x1e,0x18,0x1e,0x2a,0x0c,0x13,
+  0x13,0x13,0x31,0x1e,0x36,0x01,0x01,0x01,0x38,0x1e,0x1a,0x13,0x13,
+  0x16,0x14,0x13,0x1f,0x19,0x1e,0x38,0x01,0x01,0x01,0x00,0x1e,0x1e,
+  0x14,0x13,0x1f,0x27,0x29,0x23,0x23,0x02,0x10,0x1e,0x32,0x01,0x01,
+  0x01,0x01,0x1d,0x1e,0x06,0x14,0x13,0x13,0x06,0x1e,0x24,0x19,0x23,
+  0x13,0x29,0x1e,0x30,0x01,0x01,0x2c,0x1e,0x1e,0x1e,0x1e,0x0d,0x1f,
+  0x0c,0x0c,0x13,0x2d,0x1e,0x1b,0x01,0x01,0x01,0x3e,0x1e,0x14,0x05,
+  0x22,0x1f,0x21,0x28,0x0c,0x02,0x19,0x29,0x1e,0x11,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x3a,0x1e,0x15,0x29,0x14,0x1f,0x0b,0x1e,0x14,
+  0x14,0x14,0x05,0x26,0x1e,0x3a,0x01,0x01,0x1d,0x1e,0x19,0x0c,0x13,
+  0x23,0x0a,0x1f,0x1f,0x1a,0x02,0x1e,0x38,0x01,0x01,0x01,0x2b,0x1e,
+  0x21,0x14,0x1f,0x27,0x06,0x22,0x1f,0x02,0x06,0x0b,0x0d,0x26,0x0b,
+  0x02,0x0b,0x31,0x1e,0x36,0x01,0x01,0x01,0x01,0x2c,0x1e,0x03,0x23,
+  0x02,0x23,0x05,0x14,0x31,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x11,
+  0x1e,0x1a,0x0c,0x19,0x26,0x23,0x23,0x1a,0x05,0x1e,0x1e,0x2f,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x2a,0x0c,0x1f,0x13,0x05,0x29,0x1e,0x32,
+  0x35,0x01,0x01,0x01,0x36,0x1e,0x31,0x23,0x22,0x14,0x1f,0x1e,0x18,
+  0x0c,0x27,0x27,0x0c,0x2d,0x1e,0x35,0x01,0x01,0x01,0x20,0x1e,0x09,
+  0x1f,0x13,0x13,0x05,0x03,0x1e,0x17,0x01,0x01,0x39,0x1e,0x1e,0x1e,
+  0x10,0x02,0x27,0x0b,0x13,0x05,0x0c,0x0a,0x1e,0x2e,0x01,0x01,0x30,
+  0x1e,0x38,0x32,0x1e,0x19,0x13,0x13,0x13,0x13,0x05,0x2a,0x1e,0x1d,
+  0x01,0x01,0x01,0x17,0x1e,0x10,0x13,0x14,0x29,0x2d,0x0c,0x05,0x05,
+  0x23,0x1e,0x38,0x35,0x01,0x01,0x34,0x1e,0x18,0x14,0x23,0x0c,0x14,
+  0x02,0x0c,0x0c,0x22,0x0b,0x03,0x1e,0x1b,0x01,0x01,0x01,0x3e,0x1e,
+  0x0b,0x14,0x27,0x0c,0x12,0x18,0x06,0x27,0x06,0x1e,0x18,0x1e,0x08,
+  0x01,0x01,0x01,0x35,0x2e,0x1e,0x09,0x05,0x13,0x13,0x05,0x27,0x1e,
+  0x11,0x01,0x01,0x01,0x01,0x04,0x1e,0x09,0x14,0x22,0x0b,0x14,0x23,
+  0x22,0x14,0x27,0x1e,0x1e,0x3b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x3a,0x1e,0x15,0x1f,0x05,0x27,0x05,0x1e,0x29,0x22,0x0c,0x0b,0x15,
+  0x1e,0x3a,0x01,0x01,0x30,0x1e,0x25,0x1f,0x1f,0x0c,0x19,0x1f,0x13,
+  0x0c,0x03,0x1e,0x0f,0x01,0x01,0x01,0x34,0x1e,0x18,0x14,0x05,0x14,
+  0x22,0x0d,0x27,0x26,0x12,0x27,0x22,0x0d,0x14,0x27,0x22,0x10,0x1e,
+  0x0e,0x01,0x01,0x01,0x17,0x1e,0x1e,0x14,0x02,0x0d,0x27,0x29,0x05,
+  0x02,0x1e,0x1e,0x1b,0x01,0x01,0x01,0x01,0x3a,0x1e,0x16,0x1f,0x0c,
+  0x19,0x22,0x05,0x1f,0x0d,0x1e,0x38,0x01,0x01,0x01,0x01,0x2e,0x1e,
+  0x1e,0x0c,0x23,0x05,0x13,0x27,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,
+  0x36,0x1e,0x21,0x27,0x14,0x22,0x05,0x1e,0x1e,0x14,0x14,0x1f,0x27,
+  0x2d,0x1e,0x35,0x01,0x01,0x01,0x20,0x1e,0x09,0x0c,0x1f,0x1f,0x13,
+  0x03,0x1e,0x17,0x01,0x01,0x30,0x1e,0x1e,0x0b,0x14,0x14,0x14,0x0c,
+  0x02,0x0d,0x24,0x1e,0x33,0x0e,0x01,0x01,0x01,0x17,0x0f,0x1e,0x1e,
+  0x13,0x13,0x13,0x13,0x13,0x0c,0x03,0x1e,0x38,0x01,0x01,0x01,0x38,
+  0x1e,0x06,0x13,0x13,0x1e,0x1e,0x1f,0x1f,0x22,0x1a,0x1e,0x1e,0x40,
+  0x35,0x01,0x34,0x1e,0x18,0x0c,0x05,0x1f,0x02,0x1e,0x22,0x27,0x05,
+  0x0b,0x15,0x1e,0x2c,0x01,0x01,0x00,0x1e,0x1e,0x19,0x13,0x05,0x0c,
+  0x05,0x1f,0x0c,0x19,0x0b,0x15,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,
+  0x38,0x1e,0x0d,0x1f,0x0c,0x05,0x05,0x31,0x1e,0x0f,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x21,0x27,0x05,0x0b,0x06,0x27,0x14,0x22,0x23,0x1e,
+  0x1e,0x11,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,0x15,0x02,
+  0x1f,0x27,0x02,0x05,0x05,0x1f,0x19,0x22,0x26,0x1e,0x3a,0x01,0x01,
+  0x35,0x1e,0x1e,0x0c,0x13,0x0b,0x19,0x22,0x1f,0x14,0x18,0x1e,0x34,
+  0x01,0x01,0x01,0x01,0x39,0x1e,0x1a,0x14,0x22,0x1f,0x19,0x0b,0x26,
+  0x0a,0x23,0x1a,0x27,0x14,0x0d,0x0d,0x1e,0x1e,0x00,0x01,0x01,0x2b,
+  0x38,0x1e,0x28,0x14,0x0b,0x19,0x27,0x23,0x05,0x14,0x1a,0x1e,0x38,
+  0x2b,0x01,0x01,0x01,0x35,0x1e,0x18,0x0b,0x1a,0x1a,0x0c,0x23,0x22,
+  0x0a,0x1e,0x37,0x01,0x01,0x01,0x01,0x04,0x1e,0x29,0x13,0x0b,0x1f,
+  0x1f,0x06,0x2d,0x16,0x31,0x1e,0x0f,0x01,0x01,0x36,0x1e,0x21,0x14,
+  0x29,0x14,0x22,0x1e,0x18,0x1f,0x23,0x14,0x1f,0x2d,0x1e,0x35,0x01,
+  0x01,0x01,0x20,0x1e,0x09,0x1f,0x05,0x13,0x1f,0x03,0x1e,0x17,0x01,
+  0x01,0x32,0x1e,0x02,0x29,0x14,0x1f,0x22,0x14,0x12,0x31,0x1e,0x1e,
+  0x3c,0x01,0x01,0x01,0x2c,0x1e,0x1e,0x1e,0x1e,0x1e,0x16,0x1a,0x13,
+  0x13,0x22,0x13,0x21,0x1e,0x3c,0x01,0x00,0x1e,0x1e,0x13,0x1f,0x1f,
+  0x27,0x02,0x0c,0x0c,0x13,0x1f,0x29,0x25,0x1e,0x2e,0x01,0x17,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x12,0x23,0x22,0x1f,0x0b,0x1e,0x1e,
+  0x00,0x01,0x01,0x07,0x1e,0x14,0x02,0x0d,0x05,0x19,0x29,0x13,0x05,
+  0x0b,0x14,0x18,0x1e,0x17,0x01,0x01,0x01,0x36,0x1e,0x18,0x0c,0x19,
+  0x0c,0x1f,0x23,0x1e,0x33,0x01,0x01,0x01,0x01,0x20,0x1e,0x09,0x13,
+  0x23,0x22,0x1f,0x03,0x15,0x29,0x14,0x22,0x29,0x1e,0x1e,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x3b,0x1e,0x25,0x22,0x1f,0x22,0x0c,0x14,
+  0x27,0x0c,0x19,0x12,0x26,0x1e,0x3a,0x01,0x01,0x01,0x1e,0x1e,0x05,
+  0x13,0x13,0x22,0x1f,0x1f,0x0b,0x1e,0x32,0x01,0x01,0x01,0x01,0x01,
+  0x3a,0x1e,0x28,0x1a,0x0b,0x0b,0x12,0x02,0x0a,0x0a,0x22,0x23,0x22,
+  0x14,0x14,0x06,0x1e,0x30,0x01,0x01,0x01,0x38,0x1e,0x1e,0x22,0x19,
+  0x24,0x0d,0x03,0x12,0x1f,0x0b,0x22,0x1e,0x1e,0x38,0x01,0x01,0x01,
+  0x01,0x39,0x1e,0x12,0x05,0x22,0x14,0x23,0x14,0x1e,0x1e,0x0e,0x01,
+  0x01,0x01,0x01,0x07,0x1e,0x14,0x0b,0x02,0x13,0x05,0x0b,0x1f,0x19,
+  0x27,0x1e,0x39,0x01,0x01,0x35,0x1e,0x2d,0x05,0x02,0x02,0x1f,0x1e,
+  0x1e,0x1a,0x22,0x05,0x24,0x2d,0x1e,0x35,0x01,0x01,0x01,0x20,0x1e,
+  0x09,0x0b,0x23,0x05,0x0c,0x03,0x1e,0x17,0x01,0x36,0x1e,0x21,0x1f,
+  0x1f,0x05,0x15,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x32,0x00,0x01,0x20,
+  0x1e,0x09,0x26,0x26,0x06,0x26,0x1e,0x1e,0x0b,0x13,0x0c,0x13,0x12,
+  0x1e,0x1c,0x01,0x34,0x1e,0x18,0x0c,0x1f,0x13,0x05,0x22,0x0c,0x1f,
+  0x1f,0x13,0x1f,0x0d,0x1e,0x04,0x01,0x0f,0x1e,0x26,0x02,0x0d,0x19,
+  0x1e,0x1e,0x06,0x05,0x13,0x19,0x0d,0x1e,0x1e,0x00,0x01,0x00,0x1e,
+  0x1e,0x14,0x0c,0x14,0x14,0x06,0x1e,0x27,0x12,0x14,0x23,0x15,0x1e,
+  0x0f,0x01,0x01,0x01,0x04,0x1e,0x28,0x13,0x1f,0x13,0x13,0x28,0x1e,
+  0x11,0x01,0x01,0x01,0x01,0x08,0x1e,0x25,0x0c,0x05,0x13,0x27,0x1e,
+  0x1e,0x02,0x02,0x1f,0x1f,0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x35,0x40,0x1e,0x0d,0x1f,0x19,0x05,0x27,0x22,0x1f,0x14,0x1f,
+  0x0d,0x1e,0x2c,0x01,0x01,0x01,0x11,0x1e,0x1a,0x13,0x0c,0x0c,0x22,
+  0x05,0x26,0x1e,0x30,0x01,0x01,0x01,0x01,0x01,0x3b,0x1e,0x25,0x19,
+  0x05,0x14,0x1f,0x0b,0x16,0x21,0x22,0x29,0x19,0x22,0x23,0x06,0x1e,
+  0x37,0x01,0x01,0x01,0x1e,0x1e,0x02,0x06,0x14,0x0b,0x1a,0x1e,0x02,
+  0x02,0x23,0x14,0x0d,0x1e,0x1e,0x01,0x01,0x01,0x01,0x2e,0x1e,0x2a,
+  0x23,0x02,0x0b,0x26,0x27,0x1e,0x32,0x01,0x01,0x01,0x01,0x00,0x1e,
+  0x1e,0x13,0x13,0x22,0x13,0x14,0x0c,0x23,0x27,0x1f,0x1e,0x1e,0x00,
+  0x01,0x34,0x1e,0x18,0x0b,0x14,0x0c,0x0b,0x24,0x06,0x1a,0x1a,0x1f,
+  0x05,0x1e,0x1e,0x34,0x01,0x01,0x01,0x20,0x1e,0x09,0x05,0x13,0x0c,
+  0x0b,0x03,0x1e,0x17,0x01,0x1b,0x1e,0x15,0x27,0x02,0x1f,0x0d,0x29,
+  0x0b,0x23,0x22,0x02,0x03,0x1e,0x17,0x01,0x20,0x1e,0x09,0x05,0x1f,
+  0x13,0x13,0x12,0x10,0x14,0x13,0x1f,0x13,0x06,0x1e,0x37,0x01,0x34,
+  0x1e,0x1e,0x0c,0x1f,0x0c,0x13,0x13,0x0c,0x0c,0x13,0x0b,0x0b,0x24,
+  0x1e,0x37,0x01,0x11,0x1e,0x02,0x0b,0x05,0x1f,0x02,0x10,0x1f,0x22,
+  0x0c,0x23,0x29,0x1e,0x2c,0x01,0x01,0x01,0x3e,0x1e,0x23,0x29,0x22,
+  0x0c,0x0a,0x1e,0x23,0x0b,0x22,0x1f,0x24,0x1e,0x37,0x01,0x01,0x2b,
+  0x3e,0x1e,0x0b,0x0b,0x27,0x13,0x1f,0x1e,0x1e,0x2b,0x01,0x01,0x01,
+  0x01,0x08,0x1e,0x25,0x1f,0x22,0x0c,0x23,0x25,0x24,0x14,0x0c,0x0b,
+  0x02,0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,
+  0x1e,0x2d,0x25,0x21,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1b,0x01,
+  0x01,0x01,0x3a,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x17,
+  0x01,0x01,0x01,0x01,0x01,0x35,0x33,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3c,0x01,0x01,0x01,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x27,0x0b,0x1f,0x0c,
+  0x21,0x1e,0x30,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x39,0x01,0x01,0x01,0x04,0x1e,
+  0x12,0x0b,0x22,0x0b,0x22,0x05,0x23,0x14,0x22,0x06,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x20,0x1e,0x18,0x02,0x05,0x0c,0x1f,0x31,0x1e,0x17,
+  0x01,0x3a,0x1e,0x15,0x0b,0x22,0x1f,0x22,0x0c,0x14,0x27,0x0c,0x19,
+  0x2a,0x1e,0x08,0x01,0x01,0x1e,0x1e,0x13,0x0b,0x13,0x13,0x13,0x14,
+  0x1f,0x13,0x0c,0x13,0x03,0x1e,0x3c,0x01,0x01,0x38,0x1e,0x18,0x1e,
+  0x1e,0x1e,0x21,0x13,0x1f,0x13,0x19,0x1e,0x1e,0x1e,0x2f,0x01,0x0f,
+  0x1e,0x16,0x1f,0x1f,0x0c,0x13,0x0c,0x23,0x0b,0x05,0x29,0x2a,0x1e,
+  0x1b,0x01,0x01,0x01,0x11,0x1e,0x19,0x05,0x1f,0x19,0x29,0x25,0x0d,
+  0x19,0x13,0x0b,0x15,0x1e,0x1b,0x01,0x01,0x38,0x1e,0x2a,0x1f,0x13,
+  0x1f,0x13,0x28,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x0e,0x1e,0x10,
+  0x23,0x0b,0x05,0x29,0x02,0x1f,0x0b,0x22,0x0b,0x22,0x1e,0x1e,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x38,0x1e,0x1e,0x1e,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x1b,0x01,0x01,0x01,0x01,0x01,0x3a,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x17,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x3c,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x32,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x34,0x1e,0x18,0x12,0x0d,0x29,0x02,0x23,0x02,0x1e,0x1e,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x33,0x39,0x35,0x01,0x01,0x01,0x08,0x1e,0x1e,0x15,0x14,0x0d,
+  0x02,0x1f,0x02,0x23,0x26,0x1e,0x1e,0x1b,0x01,0x01,0x01,0x01,0x0e,
+  0x1e,0x10,0x02,0x13,0x14,0x23,0x0a,0x1e,0x3b,0x01,0x30,0x1e,0x12,
+  0x06,0x19,0x1f,0x19,0x05,0x14,0x22,0x1f,0x14,0x28,0x1e,0x1b,0x01,
+  0x01,0x38,0x1e,0x1e,0x02,0x13,0x13,0x13,0x13,0x22,0x13,0x13,0x25,
+  0x1e,0x32,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x03,0x1f,
+  0x14,0x13,0x0c,0x1e,0x1e,0x3d,0x01,0x01,0x20,0x07,0x1e,0x03,0x13,
+  0x05,0x05,0x0c,0x05,0x05,0x0c,0x31,0x1e,0x3e,0x34,0x01,0x01,0x01,
+  0x37,0x1e,0x0a,0x27,0x0c,0x19,0x12,0x13,0x05,0x22,0x14,0x14,0x1e,
+  0x1e,0x36,0x01,0x01,0x1e,0x1e,0x05,0x13,0x0c,0x14,0x13,0x1e,0x1e,
+  0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x2d,0x05,0x0c,0x0c,
+  0x22,0x02,0x23,0x14,0x12,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x2e,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,0x0c,
+  0x14,0x05,0x22,0x02,0x21,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x08,0x1e,0x1e,0x1e,0x2d,0x25,0x0a,0x21,0x1e,
+  0x1e,0x1e,0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x33,0x0e,0x01,0x3b,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x2b,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x1e,0x2a,0x25,0x16,0x16,0x09,0x1e,0x1e,0x40,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x34,0x34,0x34,0x32,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x2b,0x32,0x1e,0x1e,0x18,0x25,0x16,0x16,
+  0x09,0x1e,0x1e,0x1d,0x2b,0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x0a,
+  0x1f,0x05,0x0c,0x1f,0x0b,0x1f,0x0b,0x2d,0x1e,0x38,0x01,0x01,0x01,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x03,0x25,0x0a,0x10,0x1e,
+  0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,0x0b,0x19,0x19,0x1a,0x31,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x17,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2f,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x3b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x30,0x1d,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x1e,0x2d,0x25,0x16,
+  0x28,0x18,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x1c,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x0e,0x08,0x2e,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x2f,0x08,0x08,0x3c,0x20,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x20,0x08,0x08,0x08,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,
+  0x2f,0x3b,0x2e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x30,
+  0x32,0x1e,0x33,0x33,0x39,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x35,0x08,0x3c,0x1b,0x36,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x20,0x3c,0x2b,0x17,0x20,0x01,0x01,0x01,0x01,0x01,0x0e,
+  0x36,0x36,0x17,0x20,0x01,0x01,0x01,0x01,0x01,0x3c,0x37,0x3d,0x1c,
+  0x1c,0x2c,0x1b,0x0e,0x01,0x01,0x01,0x01,0x35,0x0e,0x20,0x35,0x20,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x17,0x30,0x1d,0x30,0x3a,
+  0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x17,0x2b,0x2f,0x20,
+  0x01,0x01,0x01,0x01,0x01,0x00,0x34,0x00,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x0e,0x3c,0x3c,0x34,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x38,0x1e,0x38,
+  0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x35,0x35,0x34,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x3b,
+  0x37,0x3d,0x3b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x00,0x35,0x35,0x20,0x0e,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,
+  0x20,0x35,0x35,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,
+  0x3d,0x32,0x2c,0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x34,0x3c,0x1c,0x38,0x30,0x3b,0x35,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x34,0x35,0x35,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x08,0x30,0x08,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x37,0x38,0x07,
+  0x1e,0x07,0x38,0x1c,0x2f,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x2c,0x01,0x01,0x01,0x11,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x04,0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x33,
+  0x0e,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x39,0x0e,0x01,0x01,
+  0x01,0x0e,0x11,0x33,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x01,0x01,
+  0x3d,0x1e,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x11,
+  0x1e,0x1e,0x1e,0x07,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x36,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x08,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x04,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x17,0x01,0x01,0x01,0x01,0x01,0x00,0x11,0x1e,0x1e,0x1e,0x1e,
+  0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2f,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x32,0x30,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x30,0x1e,0x1d,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x3d,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x3d,0x1e,0x1e,0x1e,0x30,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x35,0x39,0x1e,0x1e,0x1e,0x1e,0x18,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x2b,0x1e,0x09,0x2a,0x21,0x03,0x09,0x1e,
+  0x36,0x01,0x0f,0x1e,0x10,0x31,0x31,0x03,0x18,0x1e,0x35,0x01,0x34,
+  0x1e,0x18,0x24,0x29,0x06,0x24,0x0d,0x25,0x1e,0x30,0x01,0x38,0x1e,
+  0x09,0x10,0x09,0x2d,0x09,0x1e,0x39,0x01,0x01,0x01,0x11,0x1e,0x1e,
+  0x03,0x26,0x0b,0x24,0x26,0x03,0x1e,0x1e,0x38,0x20,0x01,0x20,0x1e,
+  0x18,0x03,0x21,0x2a,0x18,0x1e,0x2b,0x01,0x30,0x1e,0x1e,0x18,0x1e,
+  0x1e,0x1b,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,0x10,0x2a,0x16,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x04,0x1e,0x26,0x13,0x10,0x1e,0x17,0x01,0x01,0x01,0x0e,0x38,0x1e,
+  0x18,0x2d,0x2d,0x18,0x1e,0x38,0x34,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x1c,0x1e,0x1e,0x1e,0x25,0x06,0x29,0x10,0x1e,0x1e,0x38,0x2b,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x1e,0x2d,0x09,0x09,0x10,0x18,0x1e,0x0f,
+  0x01,0x35,0x40,0x1e,0x21,0x09,0x09,0x2d,0x18,0x1e,0x1e,0x17,0x01,
+  0x01,0x01,0x3d,0x1e,0x1e,0x10,0x22,0x27,0x1e,0x1e,0x38,0x2b,0x01,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x16,0x15,0x02,0x29,
+  0x2a,0x2d,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x00,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x3a,0x1e,0x1e,0x2d,0x09,0x21,
+  0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x36,0x04,0x1e,0x1e,0x29,0x1e,
+  0x1e,0x04,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,
+  0x1e,0x1e,0x28,0x23,0x05,0x13,0x05,0x23,0x24,0x18,0x1e,0x38,0x01,
+  0x01,0x1b,0x1e,0x15,0x13,0x13,0x13,0x26,0x1e,0x3a,0x01,0x39,0x1e,
+  0x02,0x1f,0x1f,0x13,0x25,0x1e,0x3b,0x01,0x0e,0x1e,0x2d,0x1f,0x13,
+  0x05,0x1f,0x0c,0x0b,0x1e,0x32,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,
+  0x0c,0x1e,0x1e,0x00,0x01,0x3a,0x1e,0x18,0x0c,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x1e,0x1e,0x2c,0x01,0x08,0x1e,0x25,0x13,0x13,0x13,
+  0x15,0x1e,0x1b,0x01,0x1d,0x1e,0x0b,0x13,0x27,0x1e,0x2c,0x01,0x01,
+  0x01,0x01,0x01,0x07,0x1e,0x14,0x0c,0x13,0x1e,0x1e,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x05,0x13,
+  0x06,0x1e,0x0f,0x01,0x01,0x01,0x11,0x1e,0x1e,0x13,0x13,0x13,0x13,
+  0x1e,0x1e,0x04,0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,0x1e,0x29,0x1f,
+  0x1f,0x13,0x05,0x14,0x13,0x2d,0x1e,0x38,0x2b,0x01,0x01,0x30,0x1e,
+  0x10,0x14,0x13,0x05,0x14,0x13,0x02,0x1e,0x39,0x01,0x3b,0x1e,0x0a,
+  0x13,0x13,0x13,0x13,0x13,0x06,0x1e,0x11,0x01,0x01,0x0e,0x1e,0x1e,
+  0x1e,0x31,0x13,0x13,0x1e,0x18,0x1e,0x38,0x01,0x01,0x01,0x01,0x2b,
+  0x38,0x1e,0x1e,0x06,0x13,0x1f,0x1f,0x13,0x05,0x14,0x13,0x19,0x18,
+  0x1e,0x38,0x00,0x01,0x01,0x2b,0x1e,0x21,0x0b,0x13,0x2d,0x1e,0x1c,
+  0x01,0x01,0x01,0x39,0x1e,0x02,0x13,0x13,0x13,0x10,0x1e,0x0e,0x01,
+  0x01,0x01,0x08,0x1e,0x1e,0x1e,0x15,0x13,0x28,0x1e,0x1e,0x1e,0x08,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x33,0x1e,0x0c,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x09,0x1e,0x1b,0x01,0x0f,0x1e,0x26,
+  0x13,0x1f,0x0c,0x26,0x1e,0x0f,0x01,0x1d,0x1e,0x23,0x0c,0x13,0x0b,
+  0x0a,0x1e,0x2e,0x01,0x35,0x1e,0x2d,0x13,0x1f,0x1f,0x13,0x22,0x27,
+  0x1e,0x38,0x01,0x38,0x1e,0x26,0x13,0x13,0x13,0x12,0x1e,0x39,0x01,
+  0x01,0x39,0x1e,0x27,0x13,0x13,0x13,0x05,0x13,0x13,0x13,0x13,0x05,
+  0x1e,0x07,0x01,0x08,0x1e,0x25,0x13,0x13,0x13,0x15,0x1e,0x1b,0x01,
+  0x32,0x1e,0x22,0x13,0x02,0x1e,0x39,0x01,0x01,0x01,0x20,0x08,0x2c,
+  0x1e,0x12,0x0c,0x0c,0x1e,0x1e,0x2e,0x0e,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x2c,0x1e,0x0a,0x14,0x13,0x1e,0x1e,0x17,0x01,
+  0x01,0x1b,0x1e,0x1e,0x0c,0x13,0x13,0x13,0x13,0x14,0x1e,0x1e,0x2b,
+  0x01,0x01,0x01,0x2b,0x1e,0x1e,0x1a,0x1f,0x19,0x31,0x1e,0x1e,0x2a,
+  0x05,0x22,0x10,0x1e,0x38,0x01,0x01,0x32,0x1e,0x23,0x13,0x1f,0x14,
+  0x13,0x14,0x0d,0x1e,0x2c,0x01,0x3c,0x1e,0x2a,0x13,0x13,0x13,0x13,
+  0x13,0x0c,0x09,0x1e,0x20,0x01,0x3a,0x1e,0x28,0x13,0x12,0x13,0x14,
+  0x12,0x13,0x2d,0x1e,0x17,0x01,0x01,0x01,0x38,0x1e,0x1e,0x13,0x0b,
+  0x1f,0x0b,0x29,0x0a,0x24,0x0b,0x1f,0x14,0x13,0x2d,0x1e,0x3d,0x01,
+  0x01,0x01,0x38,0x1e,0x19,0x13,0x0b,0x1e,0x3e,0x34,0x01,0x01,0x3a,
+  0x1e,0x26,0x13,0x13,0x13,0x09,0x1e,0x20,0x01,0x01,0x08,0x1e,0x1e,
+  0x15,0x05,0x13,0x13,0x13,0x14,0x28,0x1e,0x1e,0x08,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x3a,0x1e,0x25,0x13,0x13,0x13,0x13,0x02,0x1f,0x13,
+  0x13,0x13,0x27,0x1e,0x2c,0x01,0x1b,0x1e,0x15,0x13,0x1f,0x0c,0x28,
+  0x1e,0x3a,0x01,0x11,0x1e,0x19,0x1f,0x13,0x13,0x0a,0x1e,0x3b,0x01,
+  0x20,0x1e,0x09,0x02,0x05,0x19,0x19,0x05,0x29,0x1e,0x30,0x01,0x37,
+  0x1e,0x15,0x14,0x13,0x13,0x12,0x1e,0x3d,0x01,0x01,0x33,0x1e,0x0c,
+  0x13,0x13,0x13,0x1e,0x1a,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x08,
+  0x1e,0x25,0x13,0x13,0x13,0x15,0x1e,0x1b,0x01,0x38,0x1e,0x23,0x13,
+  0x1a,0x1e,0x04,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x12,0x0c,0x0c,
+  0x1e,0x1e,0x1e,0x1e,0x11,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,
+  0x1e,0x1e,0x02,0x12,0x2d,0x1e,0x38,0x01,0x01,0x08,0x1e,0x1e,0x27,
+  0x13,0x13,0x24,0x23,0x13,0x13,0x15,0x1e,0x33,0x35,0x01,0x01,0x38,
+  0x1e,0x28,0x0c,0x24,0x16,0x1a,0x0c,0x29,0x03,0x1e,0x22,0x1f,0x1e,
+  0x1e,0x2f,0x01,0x32,0x1e,0x12,0x14,0x0c,0x27,0x22,0x19,0x0a,0x1e,
+  0x1b,0x01,0x0e,0x1e,0x10,0x19,0x14,0x13,0x0b,0x14,0x0c,0x10,0x1e,
+  0x0e,0x01,0x39,0x1e,0x27,0x13,0x13,0x13,0x13,0x13,0x13,0x26,0x1e,
+  0x0f,0x01,0x01,0x1b,0x1e,0x1e,0x0c,0x13,0x1f,0x16,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x0b,0x0c,0x0b,0x1e,0x32,0x01,0x01,0x01,0x37,0x1e,
+  0x1e,0x27,0x13,0x2a,0x1e,0x3c,0x01,0x01,0x1c,0x1e,0x12,0x13,0x13,
+  0x13,0x2d,0x1e,0x35,0x01,0x01,0x38,0x1e,0x12,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x29,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x39,
+  0x1e,0x1a,0x13,0x13,0x13,0x14,0x1e,0x12,0x13,0x13,0x13,0x22,0x1e,
+  0x32,0x01,0x3d,0x1e,0x0d,0x13,0x13,0x05,0x0d,0x1e,0x2c,0x01,0x1e,
+  0x1e,0x1f,0x14,0x13,0x0c,0x15,0x1e,0x1b,0x01,0x01,0x04,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3b,0x01,0x0f,0x1e,0x06,0x1f,0x19,
+  0x05,0x28,0x1e,0x1b,0x01,0x01,0x39,0x1e,0x15,0x27,0x27,0x28,0x1e,
+  0x22,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x3c,0x1e,0x16,0x13,0x0c,
+  0x13,0x0a,0x1e,0x3b,0x01,0x40,0x1e,0x1f,0x13,0x23,0x1e,0x38,0x01,
+  0x20,0x1e,0x18,0x16,0x0a,0x10,0x23,0x13,0x05,0x18,0x25,0x0a,0x10,
+  0x1e,0x3c,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x1e,0x28,0x0b,0x14,
+  0x1e,0x07,0x2b,0x01,0x01,0x30,0x1e,0x29,0x13,0x13,0x05,0x1e,0x1e,
+  0x13,0x13,0x13,0x0a,0x1e,0x3b,0x01,0x01,0x1e,0x1e,0x0c,0x19,0x1e,
+  0x19,0x0c,0x23,0x05,0x13,0x06,0x1e,0x0c,0x15,0x1e,0x39,0x01,0x1d,
+  0x1e,0x27,0x02,0x22,0x27,0x26,0x1e,0x1e,0x33,0x35,0x01,0x01,0x11,
+  0x1e,0x1e,0x2a,0x05,0x02,0x24,0x0b,0x2d,0x1e,0x35,0x01,0x3a,0x1e,
+  0x21,0x29,0x13,0x13,0x13,0x05,0x0a,0x10,0x1e,0x17,0x01,0x01,0x38,
+  0x1e,0x12,0x0c,0x0c,0x1e,0x1e,0x25,0x27,0x1a,0x31,0x25,0x31,0x09,
+  0x13,0x27,0x31,0x1e,0x3c,0x01,0x01,0x01,0x37,0x1e,0x1e,0x05,0x24,
+  0x1e,0x37,0x01,0x01,0x2e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x34,
+  0x01,0x0e,0x1e,0x1e,0x13,0x13,0x13,0x13,0x0b,0x13,0x13,0x13,0x13,
+  0x31,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,0x11,0x1e,0x15,0x22,0x13,
+  0x13,0x19,0x1e,0x06,0x13,0x13,0x13,0x0b,0x1e,0x1d,0x01,0x3b,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1b,0x01,0x11,0x1e,0x1e,0x0d,0x0c,
+  0x1f,0x10,0x1e,0x36,0x01,0x01,0x01,0x37,0x11,0x1e,0x1d,0x1d,0x1e,
+  0x38,0x3b,0x01,0x01,0x2f,0x1e,0x03,0x1f,0x05,0x22,0x2a,0x1e,0x2f,
+  0x01,0x01,0x36,0x1e,0x1e,0x1e,0x1e,0x1e,0x13,0x13,0x13,0x13,0x13,
+  0x15,0x1e,0x38,0x01,0x20,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x35,
+  0x01,0x39,0x1e,0x24,0x14,0x15,0x1e,0x1c,0x01,0x17,0x1e,0x03,0x22,
+  0x13,0x1f,0x0c,0x05,0x05,0x13,0x0c,0x0c,0x12,0x1e,0x1c,0x01,0x01,
+  0x01,0x01,0x01,0x36,0x1e,0x1e,0x1f,0x02,0x21,0x1e,0x04,0x01,0x01,
+  0x01,0x3a,0x1e,0x26,0x12,0x29,0x1e,0x1e,0x1e,0x1e,0x0d,0x1a,0x15,
+  0x1e,0x1b,0x01,0x35,0x1e,0x2d,0x0c,0x15,0x1e,0x0d,0x0c,0x18,0x29,
+  0x1f,0x0c,0x1e,0x05,0x22,0x1e,0x33,0x01,0x1d,0x1e,0x29,0x14,0x0b,
+  0x19,0x0d,0x1e,0x2c,0x35,0x01,0x01,0x01,0x01,0x30,0x1e,0x0a,0x22,
+  0x0b,0x0c,0x0b,0x2d,0x1e,0x35,0x01,0x01,0x38,0x1e,0x2a,0x13,0x13,
+  0x13,0x1f,0x1e,0x1e,0x11,0x01,0x01,0x2b,0x1e,0x21,0x1f,0x0c,0x10,
+  0x1e,0x27,0x23,0x0c,0x05,0x02,0x0c,0x13,0x1e,0x26,0x1f,0x24,0x1e,
+  0x1c,0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x1e,0x3c,0x01,0x01,
+  0x0e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x3b,0x1e,0x0a,
+  0x13,0x13,0x13,0x0c,0x1e,0x1a,0x13,0x13,0x13,0x15,0x1e,0x1b,0x01,
+  0x01,0x01,0x01,0x01,0x37,0x1e,0x06,0x0c,0x19,0x0b,0x22,0x10,0x27,
+  0x13,0x13,0x13,0x0b,0x1e,0x1d,0x01,0x01,0x3b,0x38,0x1e,0x1e,0x1e,
+  0x38,0x1b,0x01,0x01,0x3c,0x1e,0x1e,0x14,0x1f,0x0d,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x00,0x01,0x01,0x01,0x01,
+  0x36,0x1e,0x31,0x0d,0x1a,0x0c,0x0a,0x1e,0x1b,0x01,0x01,0x01,0x36,
+  0x3a,0x33,0x1e,0x05,0x13,0x13,0x13,0x13,0x16,0x1e,0x1e,0x2b,0x01,
+  0x2f,0x1e,0x2a,0x1f,0x14,0x13,0x25,0x1e,0x08,0x01,0x3c,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x36,0x01,0x17,0x1e,0x03,0x13,0x0c,0x19,0x0c,0x1f,
+  0x23,0x13,0x0c,0x1f,0x24,0x1e,0x37,0x01,0x01,0x01,0x01,0x01,0x38,
+  0x1e,0x28,0x22,0x27,0x1e,0x1d,0x20,0x01,0x01,0x01,0x17,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x04,0x11,0x1e,0x1e,0x1e,0x1e,0x1e,0x36,0x01,0x36,
+  0x1e,0x31,0x05,0x31,0x1e,0x27,0x1f,0x19,0x13,0x22,0x2a,0x1e,0x0d,
+  0x02,0x1e,0x1e,0x01,0x1d,0x1e,0x12,0x29,0x23,0x1a,0x12,0x1e,0x39,
+  0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x25,0x0c,0x1a,0x0b,0x05,0x2d,
+  0x1e,0x35,0x01,0x20,0x1e,0x09,0x13,0x13,0x28,0x14,0x13,0x05,0x1e,
+  0x07,0x01,0x01,0x2e,0x1e,0x0a,0x13,0x1f,0x1e,0x26,0x13,0x05,0x25,
+  0x03,0x13,0x05,0x06,0x1e,0x25,0x02,0x0d,0x1e,0x04,0x01,0x01,0x01,
+  0x01,0x34,0x04,0x07,0x04,0x3c,0x01,0x01,0x01,0x2e,0x1e,0x0a,0x14,
+  0x0b,0x13,0x18,0x1e,0x34,0x01,0x0f,0x1e,0x06,0x13,0x13,0x13,0x13,
+  0x1e,0x1e,0x21,0x09,0x10,0x18,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,
+  0x3c,0x1e,0x1e,0x13,0x05,0x0b,0x14,0x0c,0x0c,0x02,0x22,0x0c,0x0b,
+  0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x1c,0x1e,0x12,0x27,0x0c,0x21,0x1e,0x37,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x1e,0x10,0x1f,
+  0x1a,0x19,0x21,0x1e,0x36,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,0x29,
+  0x0d,0x1a,0x22,0x2a,0x1e,0x07,0x2b,0x01,0x01,0x3b,0x1e,0x25,0x0c,
+  0x14,0x13,0x15,0x1e,0x1b,0x01,0x01,0x3c,0x04,0x3e,0x04,0x36,0x01,
+  0x01,0x00,0x1e,0x1e,0x10,0x10,0x1e,0x02,0x13,0x13,0x1e,0x10,0x10,
+  0x18,0x1e,0x3c,0x01,0x01,0x01,0x01,0x17,0x1e,0x18,0x27,0x14,0x03,
+  0x1e,0x37,0x01,0x01,0x01,0x01,0x01,0x17,0x3a,0x30,0x3d,0x2e,0x01,
+  0x01,0x3b,0x3d,0x04,0x1b,0x36,0x01,0x01,0x2b,0x1e,0x21,0x05,0x16,
+  0x1e,0x02,0x13,0x05,0x05,0x27,0x1e,0x1e,0x27,0x0c,0x1e,0x1e,0x01,
+  0x1d,0x1e,0x02,0x22,0x23,0x22,0x1a,0x1e,0x39,0x01,0x01,0x01,0x01,
+  0x01,0x2e,0x1e,0x25,0x19,0x22,0x0c,0x27,0x2d,0x1e,0x35,0x01,0x01,
+  0x38,0x1e,0x0a,0x02,0x1e,0x10,0x14,0x31,0x1e,0x2c,0x01,0x01,0x37,
+  0x1e,0x06,0x0c,0x15,0x1e,0x13,0x0c,0x14,0x1e,0x1e,0x1f,0x13,0x15,
+  0x1e,0x12,0x13,0x12,0x1e,0x30,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,0x12,0x02,0x22,0x0c,0x18,0x1e,
+  0x20,0x01,0x0f,0x1e,0x06,0x13,0x13,0x13,0x13,0x18,0x1e,0x10,0x18,
+  0x2d,0x18,0x1e,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,
+  0x0a,0x12,0x29,0x06,0x23,0x14,0x22,0x0c,0x0b,0x1e,0x1d,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3c,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x34,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x20,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x3e,0x2b,0x01,0x01,0x01,0x3c,0x1e,0x16,0x0c,0x23,0x13,0x28,0x1e,
+  0x2e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,
+  0x1e,0x1e,0x1e,0x0d,0x1f,0x1f,0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x26,0x14,0x06,0x1e,0x32,0x00,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x20,0x1e,0x18,0x23,0x0d,0x1e,0x19,0x1f,0x0d,
+  0x0c,0x05,0x1e,0x1e,0x0b,0x02,0x1e,0x3e,0x01,0x1d,0x1e,0x24,0x0b,
+  0x14,0x22,0x29,0x1e,0x39,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x0a,
+  0x23,0x0b,0x1f,0x05,0x2d,0x1e,0x35,0x01,0x01,0x2b,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1d,0x20,0x01,0x01,0x1c,0x1e,0x26,0x0c,0x21,
+  0x1e,0x13,0x0c,0x28,0x1e,0x1e,0x13,0x1f,0x03,0x1e,0x02,0x14,0x16,
+  0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x1b,0x1e,0x15,0x14,0x22,0x0c,0x10,0x1e,0x2b,0x01,0x3b,0x1e,
+  0x0a,0x13,0x13,0x13,0x0c,0x1e,0x26,0x13,0x13,0x13,0x0a,0x1e,0x3b,
+  0x01,0x01,0x01,0x01,0x01,0x2f,0x1e,0x31,0x15,0x15,0x29,0x28,0x1e,
+  0x28,0x0d,0x1f,0x22,0x0b,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3c,0x38,0x1e,0x1e,0x38,0x2b,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3c,0x01,0x01,0x01,
+  0x01,0x00,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x01,0x01,0x01,
+  0x01,0x0f,0x1e,0x06,0x05,0x14,0x13,0x06,0x1e,0x30,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x0e,0x2c,0x1e,0x1a,
+  0x1f,0x13,0x1e,0x1e,0x0e,0x34,0x01,0x01,0x01,0x01,0x01,0x08,0x1e,
+  0x1e,0x02,0x05,0x21,0x1e,0x3a,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x40,0x1e,0x1f,0x0c,0x1e,0x06,0x1f,0x09,0x19,0x0b,0x16,0x25,
+  0x22,0x03,0x1e,0x3d,0x01,0x1d,0x1e,0x02,0x14,0x0d,0x22,0x0d,0x1e,
+  0x39,0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x25,0x0d,0x19,0x05,0x12,
+  0x2d,0x1e,0x35,0x01,0x01,0x01,0x2b,0x1c,0x1d,0x1c,0x3d,0x3e,0x3d,
+  0x00,0x01,0x01,0x01,0x1c,0x1e,0x12,0x13,0x16,0x1e,0x0c,0x23,0x2a,
+  0x1e,0x0d,0x05,0x19,0x1e,0x16,0x05,0x0c,0x1e,0x1e,0x20,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x12,
+  0x0d,0x1f,0x22,0x28,0x1e,0x2e,0x01,0x35,0x1e,0x18,0x13,0x1f,0x0c,
+  0x14,0x14,0x22,0x13,0x13,0x13,0x21,0x1e,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x0f,0x1e,0x06,0x1a,0x19,0x13,0x23,0x15,0x23,0x0b,0x05,0x1f,
+  0x22,0x1e,0x32,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x17,0x1e,0x31,
+  0x02,0x1f,0x27,0x25,0x1e,0x2e,0x01,0x01,0x01,0x01,0x00,0x1e,0x1e,
+  0x0b,0x0b,0x05,0x1f,0x1e,0x1e,0x01,0x01,0x01,0x01,0x17,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x40,0x1e,0x05,0x05,0x13,0x1e,0x1e,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x06,0x22,0x02,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x1e,0x10,
+  0x22,0x1f,0x09,0x1e,0x1e,0x1e,0x1e,0x29,0x05,0x0d,0x1e,0x32,0x00,
+  0x01,0x1d,0x1e,0x29,0x02,0x29,0x27,0x06,0x1e,0x38,0x3b,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x03,0x29,0x27,0x0c,0x22,0x2d,0x1e,0x35,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x0f,0x1e,0x15,0x1f,0x0d,0x1e,0x22,0x1f,0x05,0x23,0x13,0x0c,0x19,
+  0x02,0x1f,0x02,0x2a,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x1e,0x27,0x0b,0x05,0x1f,0x15,
+  0x1e,0x1b,0x01,0x01,0x38,0x1e,0x0d,0x1f,0x0c,0x23,0x13,0x13,0x13,
+  0x13,0x29,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x2f,0x1e,0x1e,
+  0x05,0x22,0x14,0x14,0x13,0x1a,0x19,0x13,0x0b,0x15,0x1e,0x3d,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x17,0x1e,0x31,0x22,0x1f,0x14,0x25,
+  0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x12,0x19,0x13,0x0b,
+  0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,0x17,0x38,0x1e,0x1e,0x1e,0x38,
+  0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2c,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x1b,0x1e,0x1e,0x29,0x02,0x2d,0x1e,0x37,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x38,0x1e,0x03,0x19,0x14,0x0d,
+  0x16,0x25,0x0b,0x1f,0x14,0x1e,0x1e,0x30,0x01,0x01,0x1d,0x1e,0x27,
+  0x14,0x0b,0x1f,0x0b,0x2d,0x1e,0x1e,0x3b,0x01,0x00,0x32,0x1e,0x1e,
+  0x12,0x14,0x23,0x14,0x1a,0x2d,0x1e,0x0e,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x17,0x1e,0x31,0x0c,
+  0x02,0x1e,0x21,0x23,0x13,0x14,0x1a,0x23,0x1f,0x1f,0x14,0x09,0x1e,
+  0x3e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x06,0x19,0x13,0x0b,0x16,0x1e,0x38,0x01,0x01,
+  0x2e,0x1e,0x1e,0x28,0x14,0x14,0x13,0x0b,0x0c,0x0a,0x1e,0x1e,0x3b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x2d,0x1f,0x0b,0x24,
+  0x13,0x05,0x23,0x22,0x16,0x1e,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x3c,0x1e,0x2a,0x19,0x1f,0x0b,0x26,0x1e,0x0f,0x01,0x01,
+  0x01,0x01,0x34,0x1e,0x18,0x05,0x23,0x22,0x22,0x1e,0x1e,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x2c,
+  0x40,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x1d,0x1e,
+  0x1a,0x0c,0x0d,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x32,0x1e,0x1e,0x1a,0x02,0x13,0x23,0x19,0x0a,
+  0x1e,0x1e,0x39,0x01,0x01,0x01,0x38,0x1e,0x12,0x19,0x23,0x0b,0x19,
+  0x0b,0x24,0x1e,0x30,0x01,0x17,0x1e,0x31,0x27,0x19,0x1a,0x19,0x14,
+  0x0b,0x09,0x1e,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1d,0x1e,0x1a,0x23,0x13,0x2d,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x02,0x09,0x1e,0x1c,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x1e,0x2d,
+  0x05,0x23,0x22,0x22,0x13,0x1e,0x1e,0x01,0x01,0x01,0x2e,0x1e,0x1e,
+  0x1e,0x03,0x13,0x31,0x1e,0x1e,0x33,0x3b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x16,0x25,0x25,0x18,0x1e,
+  0x1e,0x07,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x07,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x2f,0x01,0x01,0x01,0x01,0x01,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x1e,0x31,0x22,0x19,0x18,0x1e,
+  0x3a,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x33,0x39,0x0e,0x01,
+  0x01,0x01,0x30,0x1e,0x29,0x19,0x23,0x22,0x29,0x12,0x24,0x1e,0x2c,
+  0x01,0x3c,0x1e,0x16,0x19,0x02,0x29,0x0a,0x0d,0x0d,0x1e,0x32,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x3a,0x1e,0x1e,0x1a,0x13,0x14,0x0b,0x03,0x1e,0x1e,0x0a,
+  0x02,0x13,0x14,0x19,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x2b,0x11,0x1e,0x2d,0x19,0x09,
+  0x1e,0x39,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,
+  0x38,0x2f,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x1e,0x1e,0x22,0x27,0x1e,0x1d,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3a,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x3b,0x1e,
+  0x1e,0x25,0x29,0x12,0x29,0x19,0x0a,0x1e,0x1b,0x01,0x0e,0x1e,0x10,
+  0x27,0x06,0x12,0x27,0x24,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,
+  0x1e,0x1e,0x19,0x19,0x05,0x23,0x24,0x05,0x22,0x0c,0x13,0x29,0x1e,
+  0x1e,0x1c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x39,0x1e,0x1e,0x1e,0x2c,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,
+  0x3c,0x08,0x3b,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x30,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x33,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x2b,0x01,0x01,0x11,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x3d,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x39,0x1e,0x1e,0x1e,
+  0x28,0x02,0x02,0x05,0x0b,0x15,0x1e,0x1e,0x1e,0x1c,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x0f,0x11,0x0f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1d,0x30,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x35,0x3a,0x2c,0x04,0x3d,0x1d,0x30,0x2b,
+  0x01,0x01,0x01,0x20,0x2c,0x40,0x2c,0x04,0x39,0x1c,0x08,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x0f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x36,0x39,0x33,0x1e,0x1e,0x07,0x04,0x36,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x0e,0x37,0x38,0x3e,0x1d,0x32,0x04,0x2e,0x00,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x04,0x32,
+  0x1d,0x1d,0x1c,0x3c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x3d,
+  0x0f,0x37,0x37,0x2f,0x34,0x3b,0x30,0x0f,0x30,0x0f,0x2f,0x01,0x01,
+  0x01,0x01,0x01,0x34,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x00,0x2f,0x1c,0x38,0x1c,0x1b,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x34,0x34,0x01,0x34,0x34,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2f,0x37,0x30,0x3b,0x35,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x08,0x1c,0x3a,0x17,0x00,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x1b,0x2c,0x3d,0x2e,0x2b,0x1b,0x30,0x2c,
+  0x1b,0x01,0x01,0x01,0x01,0x01,0x00,0x17,0x3c,0x3c,0x3c,0x3c,0x3c,
+  0x3c,0x3c,0x08,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2f,0x0f,0x08,0x35,0x01,0x01,0x01,0x01,0x01,0x2b,0x2e,0x3a,0x3b,
+  0x2e,0x2e,0x2e,0x1b,0x1b,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x0e,0x0e,0x00,0x01,0x35,0x0e,0x34,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x3d,0x3b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x00,0x34,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x3d,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x32,0x17,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x36,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x39,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x3e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x2f,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x00,0x3d,0x3e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x3e,0x1e,0x1e,0x04,0x34,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x38,0x17,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x34,
+  0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1b,0x01,0x01,
+  0x01,0x35,0x32,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x40,
+  0x2b,0x01,0x01,0x01,0x2c,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,0x04,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x1d,0x1e,0x1e,0x1e,0x3d,0x20,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x3a,0x39,0x07,0x3e,0x3d,0x35,0x01,0x01,0x17,
+  0x38,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x3a,0x39,0x32,0x1e,0x33,0x38,0x1c,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x28,0x23,0x22,
+  0x23,0x22,0x0d,0x03,0x1e,0x1e,0x17,0x01,0x01,0x01,0x01,0x01,0x36,
+  0x1e,0x1e,0x10,0x29,0x19,0x23,0x23,0x12,0x18,0x1e,0x40,0x2b,0x01,
+  0x01,0x35,0x1e,0x2d,0x0d,0x06,0x24,0x24,0x21,0x1e,0x2a,0x29,0x06,
+  0x29,0x06,0x1e,0x38,0x01,0x01,0x38,0x1e,0x1e,0x18,0x1e,0x32,0x00,
+  0x01,0x01,0x01,0x3d,0x1e,0x1e,0x2a,0x25,0x23,0x12,0x15,0x1e,0x1e,
+  0x33,0x2b,0x01,0x01,0x00,0x32,0x1e,0x18,0x18,0x1e,0x18,0x18,0x1e,
+  0x04,0x01,0x01,0x01,0x2b,0x1d,0x1e,0x1e,0x31,0x06,0x29,0x25,0x18,
+  0x1e,0x1e,0x0f,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x10,0x12,0x26,
+  0x03,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x1a,0x29,
+  0x28,0x1e,0x28,0x24,0x1a,0x1e,0x38,0x01,0x01,0x01,0x39,0x1e,0x03,
+  0x2a,0x16,0x2a,0x03,0x2a,0x16,0x16,0x25,0x21,0x1e,0x3a,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x1e,0x31,0x06,0x25,0x1e,0x38,0x01,0x01,0x20,
+  0x1e,0x09,0x28,0x26,0x0a,0x0a,0x0a,0x25,0x28,0x15,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1d,0x1e,0x10,0x10,0x1e,0x1e,
+  0x2d,0x10,0x18,0x1e,0x3c,0x01,0x01,0x01,0x20,0x2c,0x40,0x1e,0x1e,
+  0x03,0x0d,0x18,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x20,0x39,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x33,0x2b,0x17,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x18,
+  0x1e,0x1e,0x1e,0x1e,0x3a,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x00,0x3e,0x1e,0x14,0x13,0x13,0x13,0x13,0x0c,0x0c,0x0c,
+  0x26,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x26,0x1f,0x14,
+  0x13,0x14,0x14,0x13,0x05,0x16,0x1e,0x38,0x01,0x01,0x01,0x1e,0x1e,
+  0x13,0x13,0x13,0x13,0x27,0x1e,0x0b,0x13,0x13,0x13,0x13,0x1e,0x1e,
+  0x01,0x0e,0x1e,0x10,0x13,0x13,0x03,0x1e,0x17,0x01,0x01,0x08,0x1e,
+  0x1e,0x14,0x13,0x13,0x13,0x13,0x0c,0x22,0x0a,0x1e,0x38,0x01,0x01,
+  0x2f,0x1e,0x2a,0x0c,0x22,0x0b,0x13,0x05,0x1e,0x07,0x01,0x01,0x2b,
+  0x3e,0x1e,0x31,0x13,0x13,0x13,0x13,0x13,0x13,0x26,0x1e,0x1e,0x0f,
+  0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x0c,0x22,0x1f,0x09,0x1e,
+  0x2e,0x01,0x01,0x2f,0x1e,0x1e,0x02,0x0c,0x14,0x0a,0x16,0x0c,0x22,
+  0x0b,0x1e,0x1e,0x01,0x01,0x34,0x1e,0x18,0x05,0x05,0x0c,0x05,0x1f,
+  0x0c,0x19,0x0b,0x14,0x26,0x1e,0x39,0x01,0x01,0x01,0x20,0x1e,0x18,
+  0x13,0x13,0x13,0x13,0x1e,0x1e,0x00,0x01,0x2f,0x1e,0x03,0x05,0x13,
+  0x13,0x27,0x0c,0x0c,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x34,0x3a,0x1e,0x31,0x1f,0x14,0x0a,0x18,0x14,0x1f,0x24,0x1e,
+  0x1c,0x01,0x01,0x01,0x2c,0x1e,0x1e,0x21,0x16,0x22,0x13,0x24,0x21,
+  0x09,0x1e,0x1d,0x2b,0x01,0x01,0x01,0x2c,0x1e,0x2d,0x02,0x05,0x14,
+  0x0a,0x1e,0x38,0x38,0x1e,0x26,0x13,0x25,0x1e,0x08,0x01,0x01,0x01,
+  0x01,0x01,0x00,0x32,0x1e,0x21,0x27,0x22,0x13,0x0c,0x23,0x28,0x1e,
+  0x1e,0x3a,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x08,0x1e,
+  0x03,0x13,0x13,0x13,0x1f,0x0d,0x19,0x13,0x0c,0x14,0x21,0x1e,0x2b,
+  0x01,0x01,0x01,0x17,0x1e,0x10,0x14,0x0c,0x14,0x0c,0x14,0x0c,0x1f,
+  0x23,0x13,0x10,0x1e,0x0e,0x01,0x01,0x38,0x1e,0x0a,0x14,0x13,0x13,
+  0x1a,0x1e,0x14,0x13,0x13,0x13,0x1e,0x1e,0x38,0x01,0x0e,0x1e,0x10,
+  0x13,0x13,0x2a,0x1e,0x2f,0x01,0x01,0x30,0x1e,0x29,0x13,0x13,0x13,
+  0x23,0x0c,0x0c,0x13,0x13,0x21,0x1e,0x36,0x01,0x2b,0x1e,0x10,0x13,
+  0x05,0x26,0x13,0x02,0x1e,0x11,0x01,0x01,0x38,0x1e,0x2a,0x13,0x13,
+  0x03,0x1e,0x1e,0x21,0x02,0x13,0x19,0x1e,0x3e,0x2b,0x01,0x0e,0x1e,
+  0x10,0x13,0x23,0x02,0x29,0x13,0x13,0x29,0x1e,0x04,0x01,0x01,0x38,
+  0x1e,0x06,0x05,0x1f,0x24,0x18,0x05,0x27,0x14,0x1e,0x1e,0x38,0x01,
+  0x01,0x34,0x1e,0x18,0x27,0x0d,0x05,0x0b,0x02,0x13,0x05,0x0b,0x14,
+  0x1a,0x1e,0x2c,0x01,0x01,0x01,0x08,0x1e,0x25,0x13,0x13,0x13,0x0b,
+  0x1e,0x1d,0x01,0x01,0x08,0x1e,0x16,0x13,0x13,0x13,0x0c,0x1f,0x1f,
+  0x13,0x13,0x1e,0x1e,0x00,0x01,0x01,0x01,0x01,0x2b,0x3e,0x1e,0x1e,
+  0x27,0x19,0x27,0x1e,0x06,0x1a,0x02,0x1e,0x1e,0x1d,0x01,0x01,0x1c,
+  0x1e,0x1e,0x22,0x02,0x14,0x13,0x0c,0x23,0x14,0x13,0x03,0x1e,0x38,
+  0x01,0x01,0x01,0x40,0x1e,0x1f,0x13,0x13,0x13,0x13,0x21,0x1e,0x1e,
+  0x09,0x13,0x13,0x1e,0x1e,0x35,0x01,0x01,0x01,0x01,0x01,0x1b,0x1e,
+  0x03,0x13,0x13,0x13,0x13,0x13,0x0c,0x1f,0x1a,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,0x29,0x13,0x13,0x13,
+  0x26,0x1e,0x29,0x13,0x0c,0x13,0x2a,0x1e,0x2f,0x01,0x01,0x35,0x33,
+  0x1e,0x26,0x13,0x0c,0x1f,0x14,0x1e,0x29,0x19,0x1a,0x14,0x03,0x1e,
+  0x2f,0x01,0x01,0x38,0x1e,0x10,0x24,0x14,0x13,0x23,0x25,0x14,0x13,
+  0x13,0x1f,0x18,0x1e,0x38,0x01,0x35,0x1e,0x2d,0x0c,0x13,0x03,0x1e,
+  0x17,0x01,0x01,0x04,0x1e,0x1a,0x13,0x13,0x1f,0x1e,0x10,0x1f,0x0b,
+  0x13,0x15,0x1e,0x1b,0x01,0x00,0x1e,0x1e,0x03,0x2a,0x09,0x2a,0x31,
+  0x1e,0x0f,0x01,0x36,0x1e,0x18,0x13,0x0b,0x1e,0x03,0x1f,0x13,0x12,
+  0x1e,0x1a,0x13,0x16,0x1e,0x38,0x01,0x2b,0x07,0x1e,0x24,0x06,0x1a,
+  0x19,0x14,0x13,0x29,0x1e,0x3d,0x01,0x01,0x1e,0x1e,0x1f,0x1f,0x13,
+  0x2d,0x16,0x13,0x23,0x1a,0x1e,0x3e,0x2b,0x01,0x01,0x01,0x39,0x1e,
+  0x10,0x21,0x10,0x10,0x10,0x2d,0x2d,0x23,0x23,0x12,0x1e,0x39,0x01,
+  0x01,0x01,0x3c,0x1e,0x16,0x13,0x13,0x27,0x1e,0x1e,0x1c,0x01,0x01,
+  0x20,0x07,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x2a,0x16,0x0c,0x1f,0x02,0x2d,
+  0x02,0x13,0x0d,0x16,0x31,0x1e,0x08,0x01,0x1d,0x1e,0x19,0x23,0x13,
+  0x0b,0x27,0x05,0x19,0x12,0x14,0x13,0x09,0x1e,0x35,0x01,0x0e,0x1e,
+  0x10,0x13,0x13,0x1e,0x13,0x13,0x1a,0x1e,0x1e,0x05,0x13,0x21,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,0x27,0x13,0x13,0x13,
+  0x25,0x2a,0x27,0x02,0x1f,0x21,0x1e,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x3b,0x1e,0x2d,0x13,0x13,0x13,0x0a,0x1e,0x1e,0x25,
+  0x25,0x25,0x18,0x1e,0x34,0x01,0x01,0x04,0x1e,0x25,0x27,0x0c,0x13,
+  0x1f,0x1f,0x0a,0x02,0x23,0x12,0x21,0x1e,0x32,0x00,0x01,0x34,0x1e,
+  0x18,0x0b,0x0c,0x14,0x27,0x1f,0x02,0x26,0x22,0x13,0x13,0x1f,0x1e,
+  0x1e,0x01,0x35,0x1e,0x2d,0x13,0x13,0x03,0x1e,0x17,0x01,0x01,0x1b,
+  0x1e,0x15,0x13,0x13,0x13,0x06,0x2a,0x16,0x2d,0x1e,0x18,0x1e,0x2b,
+  0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,0x01,0x01,0x0f,
+  0x1e,0x28,0x05,0x1e,0x03,0x14,0x13,0x05,0x05,0x05,0x1e,0x0c,0x13,
+  0x1e,0x1e,0x01,0x38,0x1e,0x16,0x13,0x0c,0x14,0x27,0x13,0x1f,0x24,
+  0x1e,0x30,0x01,0x01,0x38,0x1e,0x21,0x1f,0x0c,0x14,0x1e,0x02,0x05,
+  0x13,0x03,0x1e,0x38,0x01,0x01,0x01,0x35,0x39,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x02,0x1f,0x1a,0x1e,0x2c,0x01,0x01,0x01,0x3c,0x1e,
+  0x16,0x13,0x13,0x2a,0x1e,0x3d,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x3d,0x1e,0x28,0x1f,0x1f,0x1f,0x12,0x23,0x1f,0x0d,0x05,0x14,0x0b,
+  0x06,0x1e,0x1c,0x01,0x1e,0x1e,0x22,0x19,0x05,0x23,0x26,0x24,0x02,
+  0x0c,0x1f,0x19,0x25,0x1e,0x08,0x01,0x0e,0x1e,0x10,0x13,0x13,0x1e,
+  0x13,0x13,0x0d,0x1e,0x15,0x13,0x1a,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x11,0x1e,0x19,0x13,0x13,0x13,0x10,0x09,0x0c,0x1f,
+  0x0c,0x31,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,
+  0x1e,0x0d,0x13,0x13,0x14,0x23,0x15,0x2d,0x1e,0x1e,0x1e,0x1e,0x04,
+  0x01,0x01,0x34,0x1e,0x18,0x19,0x13,0x05,0x23,0x0c,0x14,0x0d,0x0b,
+  0x22,0x24,0x1e,0x38,0x0e,0x01,0x01,0x01,0x38,0x1e,0x28,0x16,0x13,
+  0x05,0x0c,0x05,0x1f,0x0c,0x0d,0x25,0x25,0x1e,0x38,0x01,0x35,0x1e,
+  0x2d,0x0c,0x13,0x03,0x1e,0x17,0x01,0x01,0x17,0x1e,0x1e,0x05,0x13,
+  0x0c,0x0c,0x13,0x23,0x27,0x10,0x1e,0x1d,0x01,0x01,0x01,0x01,0x00,
+  0x17,0x2f,0x20,0x3c,0x36,0x01,0x01,0x01,0x11,0x1e,0x27,0x0b,0x1e,
+  0x0b,0x14,0x2d,0x1e,0x0d,0x26,0x1e,0x26,0x13,0x1e,0x1e,0x01,0x1e,
+  0x1e,0x13,0x0c,0x0c,0x29,0x2a,0x0c,0x13,0x06,0x1e,0x11,0x01,0x01,
+  0x2b,0x38,0x1e,0x28,0x1f,0x02,0x12,0x21,0x22,0x0c,0x23,0x18,0x1e,
+  0x34,0x01,0x01,0x01,0x01,0x0e,0x2b,0x0e,0x0e,0x0e,0x30,0x1e,0x18,
+  0x2d,0x1e,0x1e,0x1b,0x01,0x01,0x00,0x3d,0x1e,0x31,0x13,0x13,0x03,
+  0x1e,0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x32,0x1e,0x0b,0x1f,
+  0x05,0x1f,0x14,0x1f,0x22,0x22,0x22,0x28,0x12,0x1e,0x1e,0x08,0x01,
+  0x1e,0x1e,0x23,0x12,0x1f,0x15,0x25,0x1a,0x0a,0x21,0x09,0x2a,0x09,
+  0x1e,0x35,0x01,0x0e,0x1e,0x10,0x0c,0x13,0x2d,0x1f,0x13,0x12,0x1e,
+  0x13,0x13,0x1e,0x1e,0x1e,0x38,0x0f,0x01,0x01,0x01,0x01,0x01,0x3d,
+  0x1e,0x0d,0x13,0x13,0x13,0x13,0x1f,0x1f,0x0c,0x19,0x1e,0x1e,0x1e,
+  0x2c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,
+  0x13,0x0c,0x02,0x02,0x1e,0x40,0x1e,0x38,0x08,0x01,0x01,0x01,0x04,
+  0x1e,0x2a,0x05,0x13,0x05,0x1f,0x1f,0x25,0x12,0x06,0x09,0x1e,0x3b,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x29,0x0d,0x05,0x0b,0x02,
+  0x13,0x2a,0x1e,0x1e,0x1e,0x38,0x01,0x36,0x1e,0x31,0x13,0x13,0x25,
+  0x1e,0x08,0x01,0x01,0x38,0x1e,0x26,0x14,0x13,0x1f,0x0c,0x05,0x05,
+  0x13,0x05,0x03,0x1e,0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x12,0x19,0x09,0x0b,0x13,0x09,0x1e,
+  0x1e,0x1e,0x1e,0x16,0x0b,0x1e,0x1e,0x01,0x38,0x1e,0x24,0x1f,0x0c,
+  0x05,0x05,0x13,0x05,0x0c,0x1e,0x1e,0x00,0x01,0x01,0x36,0x1e,0x1e,
+  0x18,0x18,0x09,0x1e,0x1e,0x18,0x09,0x1e,0x38,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x3d,0x01,
+  0x01,0x00,0x3e,0x1e,0x1e,0x1a,0x13,0x13,0x25,0x1e,0x08,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x39,0x1e,0x1e,0x10,0x1f,0x23,0x28,0x1e,
+  0x22,0x23,0x06,0x18,0x10,0x1e,0x04,0x01,0x01,0x32,0x1e,0x23,0x19,
+  0x23,0x23,0x02,0x0b,0x29,0x24,0x25,0x1e,0x1e,0x2c,0x01,0x01,0x01,
+  0x07,0x1e,0x27,0x0b,0x14,0x06,0x1f,0x1e,0x19,0x13,0x10,0x1e,0x03,
+  0x10,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x2e,0x1e,0x1e,0x05,0x13,
+  0x14,0x22,0x05,0x05,0x0a,0x1e,0x1e,0x1e,0x09,0x1e,0x35,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x25,0x22,0x1f,0x1f,0x23,0x31,0x18,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x08,0x01,0x20,0x1e,0x18,0x0b,0x13,0x1f,
+  0x1f,0x13,0x22,0x14,0x0c,0x26,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x34,
+  0x1e,0x18,0x22,0x1f,0x0c,0x14,0x14,0x05,0x0c,0x19,0x12,0x14,0x23,
+  0x1e,0x1e,0x01,0x34,0x1e,0x18,0x0a,0x29,0x09,0x1e,0x35,0x01,0x2b,
+  0x1e,0x21,0x13,0x13,0x0c,0x27,0x2d,0x28,0x22,0x13,0x1f,0x1a,0x1e,
+  0x2c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x1d,0x1e,0x02,0x02,0x1e,0x0c,0x02,0x1e,0x1e,0x03,0x28,0x1e,0x16,
+  0x0b,0x1e,0x1e,0x01,0x3c,0x1e,0x1e,0x24,0x27,0x24,0x21,0x25,0x26,
+  0x0a,0x1e,0x1e,0x34,0x01,0x01,0x01,0x36,0x38,0x1e,0x1e,0x1e,0x38,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x34,0x35,0x00,0x01,0x01,0x01,0x2f,0x1e,0x2a,
+  0x22,0x13,0x13,0x13,0x03,0x1e,0x17,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x3a,0x1e,0x2d,0x31,0x2a,0x1f,0x05,0x28,0x31,0x29,0x05,0x06,0x10,
+  0x1e,0x38,0x34,0x01,0x01,0x30,0x1e,0x1e,0x12,0x02,0x28,0x06,0x14,
+  0x23,0x02,0x0b,0x19,0x1e,0x1e,0x36,0x01,0x01,0x04,0x1e,0x18,0x12,
+  0x27,0x27,0x31,0x10,0x22,0x29,0x1e,0x13,0x13,0x13,0x02,0x18,0x1e,
+  0x17,0x01,0x01,0x2b,0x3e,0x1e,0x09,0x1f,0x13,0x0c,0x0c,0x27,0x27,
+  0x18,0x0a,0x23,0x1f,0x25,0x1e,0x3b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x40,0x1e,0x24,0x1f,0x13,0x16,0x1e,0x09,0x22,0x14,0x13,0x12,
+  0x1e,0x30,0x01,0x34,0x1e,0x18,0x1e,0x27,0x0c,0x1f,0x0c,0x22,0x1e,
+  0x29,0x1a,0x26,0x06,0x21,0x1e,0x2b,0x01,0x01,0x38,0x1e,0x0a,0x2a,
+  0x31,0x19,0x0c,0x0b,0x1f,0x1a,0x2a,0x16,0x2a,0x1e,0x38,0x01,0x01,
+  0x04,0x1e,0x1e,0x1e,0x1e,0x39,0x01,0x01,0x08,0x1e,0x25,0x1f,0x1f,
+  0x13,0x22,0x1e,0x2d,0x13,0x0c,0x19,0x0d,0x1e,0x04,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x29,0x1f,
+  0x1e,0x02,0x0c,0x24,0x31,0x05,0x0c,0x1e,0x28,0x14,0x1e,0x1e,0x01,
+  0x01,0x1b,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3d,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x34,0x34,0x20,0x01,0x00,0x34,0x35,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,0x24,0x22,0x13,0x13,0x1a,
+  0x1e,0x32,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1a,0x05,
+  0x1f,0x0c,0x0d,0x0b,0x05,0x0d,0x0b,0x14,0x05,0x1e,0x1e,0x34,0x01,
+  0x01,0x3d,0x1e,0x1e,0x1e,0x09,0x28,0x19,0x26,0x06,0x02,0x26,0x29,
+  0x15,0x1e,0x1b,0x01,0x01,0x34,0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x14,
+  0x22,0x1e,0x19,0x13,0x19,0x0b,0x13,0x26,0x1e,0x0f,0x01,0x01,0x38,
+  0x1e,0x2a,0x13,0x13,0x1f,0x14,0x1f,0x0c,0x1f,0x1f,0x0c,0x1f,0x12,
+  0x1e,0x40,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x0c,
+  0x1f,0x0c,0x06,0x18,0x19,0x0c,0x13,0x0b,0x12,0x1e,0x04,0x01,0x01,
+  0x04,0x1e,0x1e,0x15,0x0b,0x05,0x1f,0x27,0x03,0x1f,0x22,0x0c,0x23,
+  0x21,0x1e,0x36,0x01,0x01,0x2b,0x33,0x1e,0x1e,0x1e,0x19,0x19,0x22,
+  0x13,0x15,0x1e,0x1e,0x1e,0x3e,0x2b,0x01,0x20,0x1e,0x18,0x26,0x1a,
+  0x2d,0x1e,0x35,0x01,0x2b,0x1e,0x21,0x1f,0x13,0x05,0x22,0x27,0x05,
+  0x1f,0x13,0x1f,0x26,0x1e,0x3a,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2e,0x1e,0x25,0x27,0x16,0x1e,0x29,0x22,
+  0x0c,0x0b,0x15,0x1e,0x0b,0x02,0x1e,0x32,0x01,0x01,0x01,0x1b,0x04,
+  0x39,0x1c,0x3c,0x08,0x0f,0x3b,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2f,0x1e,0x03,0x05,0x13,0x13,0x13,0x21,0x1e,0x2b,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x20,0x1e,0x09,0x14,0x05,0x22,0x1f,0x22,0x0c,
+  0x1f,0x05,0x0c,0x29,0x24,0x1e,0x38,0x01,0x01,0x01,0x32,0x1e,0x06,
+  0x1a,0x24,0x24,0x26,0x26,0x24,0x23,0x14,0x14,0x28,0x1e,0x37,0x01,
+  0x01,0x01,0x01,0x2e,0x04,0x07,0x1e,0x02,0x0d,0x03,0x1e,0x13,0x13,
+  0x16,0x27,0x0c,0x12,0x1e,0x1c,0x01,0x01,0x1e,0x1e,0x13,0x13,0x0c,
+  0x22,0x18,0x23,0x19,0x0b,0x05,0x14,0x15,0x1e,0x1e,0x3a,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x05,0x05,0x13,0x05,0x0c,
+  0x13,0x1f,0x13,0x13,0x28,0x1e,0x1b,0x01,0x01,0x01,0x36,0x1e,0x1e,
+  0x1f,0x1f,0x0c,0x13,0x0c,0x23,0x0b,0x05,0x29,0x1e,0x40,0x01,0x01,
+  0x01,0x01,0x0e,0x3b,0x32,0x1e,0x0b,0x19,0x12,0x13,0x12,0x1e,0x30,
+  0x3c,0x34,0x01,0x01,0x0e,0x1e,0x10,0x13,0x1f,0x2a,0x1e,0x2f,0x01,
+  0x01,0x38,0x1e,0x15,0x0c,0x13,0x13,0x0c,0x0c,0x13,0x23,0x31,0x1e,
+  0x1e,0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x35,0x40,0x1e,0x12,0x02,0x03,0x1e,0x03,0x16,0x1e,0x1e,0x1a,
+  0x19,0x1e,0x1e,0x30,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x32,
+  0x1e,0x1e,0x12,0x14,0x13,0x16,0x1e,0x08,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x00,0x1e,0x1e,0x1e,0x16,0x14,0x19,0x2d,0x10,0x14,0x23,0x0a,0x1e,
+  0x1e,0x1e,0x3a,0x01,0x01,0x01,0x1e,0x1e,0x23,0x14,0x29,0x27,0x19,
+  0x26,0x29,0x23,0x12,0x22,0x28,0x1e,0x0f,0x01,0x01,0x01,0x01,0x2b,
+  0x38,0x1e,0x16,0x1f,0x02,0x1e,0x2d,0x14,0x13,0x03,0x02,0x14,0x24,
+  0x1e,0x37,0x01,0x34,0x1e,0x18,0x13,0x22,0x0c,0x14,0x18,0x24,0x0c,
+  0x0c,0x1f,0x0c,0x19,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x28,0x1f,0x23,0x23,0x05,0x0c,0x1f,0x05,0x13,0x14,
+  0x1e,0x1e,0x2b,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x05,0x05,0x05,
+  0x0c,0x05,0x05,0x02,0x1e,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x1e,0x1e,0x1f,0x05,0x0c,0x1f,0x27,0x1e,0x32,0x01,0x01,0x01,0x01,
+  0x35,0x1e,0x2d,0x13,0x0c,0x03,0x1e,0x17,0x01,0x01,0x2f,0x1e,0x1e,
+  0x1e,0x1e,0x26,0x13,0x1f,0x13,0x13,0x12,0x1e,0x38,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,
+  0x1e,0x05,0x14,0x0d,0x15,0x31,0x26,0x13,0x05,0x2d,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x39,0x1e,0x03,0x0c,
+  0x13,0x2a,0x1e,0x2f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,
+  0x24,0x05,0x0c,0x1e,0x0a,0x02,0x23,0x1e,0x1e,0x38,0x3a,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x03,0x14,0x15,0x28,0x1f,0x02,0x02,0x02,0x19,
+  0x05,0x2d,0x1e,0x17,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1f,0x1a,
+  0x1e,0x1e,0x2d,0x23,0x13,0x24,0x23,0x1f,0x06,0x1e,0x37,0x01,0x01,
+  0x3e,0x1e,0x22,0x0c,0x14,0x0b,0x14,0x14,0x14,0x0c,0x05,0x05,0x13,
+  0x0c,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,
+  0x2d,0x1e,0x1e,0x1e,0x1e,0x16,0x28,0x31,0x1e,0x1e,0x2c,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x28,0x16,0x03,0x1e,0x1e,
+  0x1e,0x2c,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x30,0x01,0x01,0x01,0x01,0x35,0x1e,0x2d,0x0c,
+  0x13,0x03,0x1e,0x17,0x01,0x01,0x3d,0x1e,0x26,0x1a,0x27,0x26,0x1e,
+  0x19,0x13,0x0c,0x0c,0x21,0x1e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x1e,0x1e,0x28,0x22,
+  0x1f,0x05,0x0c,0x27,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x3c,0x1e,0x2a,0x05,0x13,0x03,0x1e,0x04,
+  0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,0x21,0x02,0x13,0x1a,0x1e,
+  0x0c,0x23,0x02,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x3e,
+  0x1e,0x0a,0x14,0x0b,0x27,0x29,0x0b,0x06,0x28,0x2d,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x1e,0x1e,0x24,0x05,0x03,0x1e,0x38,0x1e,0x29,
+  0x13,0x0b,0x13,0x22,0x2d,0x1e,0x2f,0x01,0x01,0x3d,0x1e,0x18,0x19,
+  0x05,0x0b,0x0b,0x05,0x27,0x28,0x22,0x14,0x1f,0x05,0x0c,0x2d,0x1e,
+  0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x3e,0x1d,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x40,0x2c,0x20,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x20,0x2c,0x40,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x37,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x32,0x30,
+  0x01,0x01,0x01,0x01,0x01,0x35,0x1e,0x2d,0x13,0x1f,0x03,0x1e,0x17,
+  0x01,0x01,0x32,0x1e,0x0b,0x13,0x14,0x0c,0x10,0x0b,0x0c,0x1f,0x0c,
+  0x31,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x35,0x39,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x3c,0x1e,0x2a,0x13,0x13,0x12,0x1e,0x1e,0x04,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x37,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x33,0x1e,0x1e,0x1e,
+  0x0a,0x0d,0x10,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x38,0x1e,0x28,0x0a,0x1e,0x32,0x2e,0x1e,0x1e,0x21,0x28,0x16,0x1e,
+  0x1e,0x38,0x01,0x01,0x01,0x00,0x38,0x1e,0x1e,0x31,0x28,0x25,0x10,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x35,0x01,0x01,0x01,0x34,0x3c,0x1b,0x36,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,
+  0x1b,0x08,0x2f,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x35,0x1e,0x2d,0x0c,0x0c,0x03,0x1e,0x17,0x01,0x01,0x3d,0x1e,
+  0x28,0x0c,0x05,0x13,0x13,0x14,0x1f,0x19,0x05,0x1e,0x40,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x3a,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x08,0x1e,0x16,
+  0x1f,0x13,0x13,0x22,0x1e,0x07,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,
+  0x38,0x1e,0x1e,0x38,0x38,0x1e,0x1e,0x38,0x37,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x21,0x28,0x18,0x1e,
+  0x1d,0x30,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x33,0x1e,0x1e,
+  0x33,0x2b,0x01,0x2e,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x1e,0x31,
+  0x13,0x22,0x25,0x1e,0x08,0x01,0x01,0x2f,0x1e,0x1e,0x2a,0x05,0x13,
+  0x13,0x27,0x0c,0x0d,0x1e,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x1e,0x10,0x05,0x13,0x13,0x0c,
+  0x1e,0x1e,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x04,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x2e,0x2e,0x0e,0x01,0x01,0x01,
+  0x01,0x2b,0x2e,0x08,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x36,0x2e,0x08,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x40,
+  0x35,0x01,0x01,0x01,0x2f,0x3e,0x1e,0x1e,0x1e,0x03,0x1e,0x1e,0x1e,
+  0x1e,0x2c,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x1e,0x06,0x27,0x15,0x1e,0x39,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x3c,0x37,0x3c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2e,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x00,0x17,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2f,0x0f,0x2c,
+  0x1b,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1b,0x1b,0x2e,
+  0x2e,0x2e,0x2e,0x2e,0x2e,0x1b,0x17,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x17,0x00,0x01,0x01,0x01,
+  0x01,0x01,0x34,0x17,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x08,0x1c,0x1d,0x3d,0x3a,
+  0x17,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x38,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x38,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x1c,0x38,0x1e,0x38,0x1e,0x1e,0x1d,0x30,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x38,
+  0x3a,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x34,0x04,0x3e,0x38,0x37,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x08,0x38,0x1e,0x40,0x38,0x1e,0x1d,0x1c,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3a,0x2c,0x3e,0x3d,0x2e,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x00,0x35,0x01,0x34,0x20,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x08,0x11,0x33,0x11,0x17,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x0f,0x38,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x32,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x39,0x1e,0x1e,0x1e,0x30,0x01,0x01,0x01,0x04,0x1e,0x1e,
+  0x1e,0x11,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x1c,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x30,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x35,0x33,0x1e,0x1e,0x1e,0x1e,0x30,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x32,0x1e,0x1e,0x1e,
+  0x1e,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x08,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1c,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x3a,0x1e,0x1e,0x1e,0x1e,0x1e,0x2e,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,0x3e,0x3d,0x3b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x3d,
+  0x32,0x1e,0x2c,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x38,0x1e,0x1e,0x38,0x08,0x01,0x01,0x01,0x01,0x01,0x3d,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x17,0x01,0x01,0x01,0x01,0x01,0x0f,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,
+  0x1e,0x1e,0x28,0x15,0x28,0x28,0x28,0x28,0x28,0x28,0x15,0x03,0x1e,
+  0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3c,0x38,0x1e,0x1e,0x1e,
+  0x03,0x1e,0x1e,0x00,0x01,0x34,0x1e,0x18,0x31,0x18,0x1e,0x1e,0x11,
+  0x3c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x09,
+  0x25,0x26,0x0b,0x29,0x26,0x21,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,
+  0x01,0x20,0x1e,0x09,0x13,0x13,0x10,0x13,0x13,0x21,0x1e,0x2b,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x27,0x13,0x13,
+  0x28,0x1e,0x2e,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x23,0x13,
+  0x26,0x13,0x13,0x0b,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x3b,0x1e,0x0a,0x13,0x02,0x1e,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x03,0x14,0x23,0x2d,0x1e,0x3a,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,0x29,0x13,0x05,0x12,0x13,
+  0x0b,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,
+  0x1e,0x27,0x14,0x0d,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x1b,0x1e,0x1e,0x1e,0x1e,0x1e,0x3b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x40,0x1e,0x1e,0x1e,0x1e,0x2c,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x2d,0x1e,0x18,
+  0x09,0x1e,0x1e,0x08,0x01,0x01,0x01,0x00,0x1e,0x1e,0x29,0x0c,0x26,
+  0x1e,0x38,0x00,0x01,0x01,0x01,0x01,0x38,0x1e,0x19,0x13,0x1e,0x1e,
+  0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x1e,0x2d,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x14,0x1e,0x3e,0x01,0x01,0x01,
+  0x01,0x20,0x1b,0x38,0x1e,0x1e,0x09,0x15,0x22,0x13,0x2d,0x1e,0x35,
+  0x01,0x17,0x1e,0x03,0x19,0x1f,0x28,0x2d,0x1e,0x1e,0x38,0x3a,0x00,
+  0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x28,0x13,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x1e,0x1e,0x3c,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,
+  0x05,0x13,0x18,0x13,0x13,0x2d,0x1e,0x35,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x34,0x1e,0x1e,0x13,0x13,0x26,0x05,0x13,0x2a,0x1e,0x2f,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,0x12,0x13,0x2a,0x19,0x13,0x29,
+  0x1e,0x30,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x1e,0x18,0x13,
+  0x13,0x15,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3b,
+  0x1e,0x1e,0x13,0x13,0x13,0x27,0x1e,0x07,0x20,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x1b,0x1e,0x15,0x13,0x1a,0x26,0x13,0x12,0x1e,0x1c,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x28,0x13,0x13,0x13,
+  0x09,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,0x1a,
+  0x14,0x29,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2c,0x1e,0x25,0x0b,0x22,0x1e,0x40,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x21,0x13,0x13,0x29,0x1e,0x30,
+  0x01,0x01,0x01,0x01,0x3d,0x1e,0x03,0x13,0x13,0x09,0x1e,0x3d,0x01,
+  0x01,0x01,0x34,0x1e,0x18,0x13,0x13,0x21,0x1e,0x38,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x20,0x1e,0x18,0x05,0x13,0x0c,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x27,0x1e,0x2c,0x01,0x01,0x01,0x37,0x40,0x1e,0x1e,
+  0x21,0x24,0x13,0x0c,0x13,0x1f,0x18,0x1e,0x34,0x01,0x0e,0x1e,0x10,
+  0x1f,0x1f,0x12,0x23,0x24,0x21,0x1e,0x1e,0x3e,0x0f,0x01,0x01,0x01,
+  0x2f,0x1e,0x09,0x14,0x13,0x0c,0x23,0x23,0x0c,0x13,0x13,0x13,0x24,
+  0x1e,0x37,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x21,0x28,0x1e,0x31,
+  0x25,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,
+  0x31,0x25,0x2d,0x03,0x16,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x1c,0x1e,0x18,0x26,0x18,0x21,0x28,0x18,0x1e,0x04,0x34,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x13,0x13,0x09,0x1e,
+  0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x30,0x1e,0x29,0x13,0x12,
+  0x27,0x22,0x16,0x1e,0x3c,0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x1e,
+  0x1e,0x28,0x18,0x18,0x28,0x09,0x1e,0x08,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x03,0x13,0x13,0x1e,0x1e,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1b,0x1e,0x10,0x13,0x13,0x1e,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2f,0x1e,0x1e,
+  0x14,0x1f,0x26,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,
+  0x1e,0x27,0x14,0x1e,0x1f,0x13,0x0a,0x1e,0x3b,0x01,0x01,0x01,0x01,
+  0x01,0x1d,0x1e,0x03,0x13,0x22,0x1e,0x32,0x01,0x01,0x01,0x01,0x38,
+  0x1e,0x25,0x1a,0x22,0x1e,0x1e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x18,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1b,0x01,0x01,0x1d,0x1e,0x1e,0x25,0x27,0x0c,0x0c,0x13,0x1f,
+  0x05,0x15,0x1e,0x38,0x01,0x01,0x01,0x38,0x1e,0x28,0x14,0x14,0x1f,
+  0x14,0x22,0x0d,0x03,0x1e,0x1e,0x38,0x01,0x01,0x37,0x1e,0x24,0x0b,
+  0x02,0x1f,0x19,0x1e,0x31,0x13,0x13,0x0b,0x0d,0x1e,0x2c,0x01,0x01,
+  0x01,0x01,0x37,0x1e,0x1e,0x1e,0x1e,0x18,0x1e,0x1e,0x09,0x1e,0x38,
+  0x01,0x01,0x01,0x01,0x1b,0x1e,0x1e,0x10,0x09,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x18,0x2d,0x2d,0x1e,0x38,0x01,0x01,0x34,0x04,0x1e,0x1e,0x09,
+  0x2d,0x0d,0x26,0x03,0x1e,0x1e,0x1e,0x39,0x35,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x21,0x14,0x2d,0x1e,0x1d,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x38,0x1e,0x02,0x19,0x1e,0x1e,0x05,0x25,0x1e,
+  0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x2d,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x32,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,
+  0x10,0x1f,0x10,0x1e,0x15,0x14,0x1e,0x07,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x1e,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,0x06,0x1f,0x26,0x1e,0x1e,
+  0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x2a,0x1e,
+  0x10,0x31,0x1e,0x33,0x1b,0x01,0x01,0x01,0x01,0x01,0x11,0x1e,0x1e,
+  0x2a,0x23,0x1e,0x1d,0x20,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x15,0x06,
+  0x25,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x25,
+  0x25,0x16,0x16,0x16,0x16,0x16,0x16,0x25,0x21,0x1e,0x0f,0x01,0x3b,
+  0x1e,0x03,0x23,0x14,0x13,0x1f,0x0c,0x02,0x0a,0x1e,0x1e,0x1e,0x36,
+  0x01,0x01,0x01,0x2b,0x1e,0x1e,0x1e,0x25,0x02,0x14,0x23,0x0b,0x23,
+  0x12,0x21,0x1e,0x08,0x01,0x37,0x1e,0x06,0x0b,0x23,0x14,0x27,0x1e,
+  0x29,0x19,0x05,0x02,0x03,0x1e,0x3a,0x01,0x01,0x01,0x01,0x38,0x1e,
+  0x23,0x13,0x13,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x01,
+  0x2c,0x1e,0x1a,0x13,0x13,0x13,0x13,0x1e,0x0d,0x13,0x13,0x13,0x13,
+  0x1e,0x1e,0x01,0x01,0x04,0x1e,0x1e,0x0b,0x05,0x14,0x13,0x05,0x14,
+  0x13,0x22,0x1e,0x1e,0x39,0x01,0x01,0x01,0x0e,0x38,0x1e,0x10,0x24,
+  0x1a,0x26,0x16,0x26,0x31,0x1e,0x3e,0x2b,0x01,0x01,0x01,0x35,0x38,
+  0x1e,0x2d,0x03,0x16,0x0d,0x12,0x21,0x18,0x1e,0x3e,0x2b,0x01,0x01,
+  0x01,0x01,0x36,0x1e,0x1e,0x14,0x13,0x0c,0x13,0x05,0x14,0x2a,0x1e,
+  0x2f,0x01,0x01,0x01,0x01,0x01,0x2b,0x1e,0x1e,0x09,0x03,0x06,0x1a,
+  0x28,0x03,0x21,0x1e,0x3e,0x2b,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x1e,0x06,0x22,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x0d,0x27,0x1e,0x1e,0x1e,0x3e,0x3c,0x01,0x01,
+  0x01,0x01,0x01,0x38,0x1e,0x09,0x25,0x15,0x24,0x28,0x26,0x31,0x1e,
+  0x1e,0x1b,0x01,0x01,0x01,0x2e,0x1e,0x10,0x26,0x2d,0x2d,0x31,0x1e,
+  0x2c,0x01,0x01,0x01,0x01,0x38,0x1e,0x31,0x14,0x1a,0x1e,0x1e,0x2f,
+  0x01,0x01,0x01,0x01,0x01,0x20,0x1e,0x09,0x06,0x12,0x19,0x22,0x13,
+  0x13,0x22,0x23,0x0c,0x02,0x1e,0x39,0x01,0x3d,0x1e,0x0d,0x13,0x13,
+  0x0c,0x06,0x1e,0x1e,0x1e,0x33,0x39,0x36,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x2c,0x33,0x1e,0x1e,0x1e,0x29,0x23,0x26,0x27,0x06,0x1e,0x1c,
+  0x01,0x0f,0x1e,0x06,0x15,0x2a,0x0b,0x24,0x26,0x14,0x22,0x22,0x1a,
+  0x1e,0x07,0x20,0x01,0x01,0x01,0x01,0x38,0x1e,0x23,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x09,0x1e,0x36,0x01,0x01,0x01,0x3a,0x1e,0x26,0x13,
+  0x13,0x13,0x14,0x1e,0x28,0x13,0x13,0x0c,0x0c,0x1e,0x1e,0x01,0x01,
+  0x07,0x1e,0x0b,0x0c,0x0c,0x13,0x1f,0x14,0x13,0x14,0x14,0x1f,0x1e,
+  0x40,0x01,0x01,0x01,0x11,0x1e,0x2d,0x02,0x24,0x14,0x13,0x13,0x05,
+  0x05,0x2a,0x1e,0x38,0x01,0x01,0x01,0x39,0x1e,0x10,0x13,0x13,0x0c,
+  0x13,0x0c,0x13,0x1f,0x03,0x1e,0x38,0x01,0x01,0x01,0x01,0x04,0x1e,
+  0x15,0x13,0x14,0x14,0x13,0x05,0x02,0x15,0x1e,0x0f,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x28,0x13,0x05,0x05,0x13,0x13,0x13,0x13,0x2a,
+  0x1e,0x38,0x01,0x01,0x01,0x2b,0x32,0x1e,0x10,0x06,0x1a,0x25,0x16,
+  0x24,0x21,0x1e,0x07,0x2b,0x01,0x01,0x01,0x0e,0x38,0x1e,0x09,0x26,
+  0x15,0x2a,0x29,0x29,0x2a,0x1e,0x1e,0x3c,0x01,0x01,0x01,0x1c,0x1e,
+  0x09,0x0c,0x19,0x05,0x05,0x14,0x14,0x0b,0x1a,0x1e,0x1e,0x36,0x01,
+  0x01,0x04,0x1e,0x29,0x13,0x13,0x13,0x05,0x1e,0x07,0x01,0x01,0x01,
+  0x01,0x2b,0x1e,0x1e,0x02,0x22,0x26,0x1e,0x11,0x01,0x01,0x01,0x01,
+  0x01,0x20,0x1e,0x09,0x19,0x02,0x22,0x1f,0x13,0x13,0x1f,0x0b,0x14,
+  0x1a,0x1e,0x2c,0x01,0x2c,0x1e,0x27,0x1f,0x1f,0x13,0x23,0x16,0x1e,
+  0x1e,0x1e,0x38,0x0f,0x01,0x01,0x01,0x01,0x01,0x0f,0x38,0x1e,0x1e,
+  0x1e,0x2a,0x02,0x14,0x05,0x14,0x15,0x1e,0x3d,0x01,0x0f,0x1e,0x15,
+  0x23,0x02,0x0b,0x12,0x10,0x24,0x25,0x05,0x27,0x16,0x1e,0x04,0x01,
+  0x01,0x01,0x36,0x1e,0x21,0x13,0x13,0x13,0x22,0x13,0x13,0x13,0x26,
+  0x1e,0x30,0x01,0x01,0x01,0x0f,0x1e,0x26,0x13,0x13,0x13,0x05,0x1e,
+  0x28,0x13,0x1f,0x19,0x13,0x1e,0x1e,0x01,0x0e,0x1e,0x10,0x1f,0x13,
+  0x02,0x14,0x16,0x15,0x0c,0x0c,0x0c,0x1f,0x21,0x1e,0x2b,0x01,0x01,
+  0x33,0x1e,0x0b,0x0b,0x0c,0x14,0x12,0x05,0x0b,0x26,0x22,0x09,0x1e,
+  0x20,0x01,0x01,0x33,0x1e,0x1f,0x13,0x0c,0x0c,0x1a,0x05,0x0c,0x13,
+  0x1f,0x18,0x1e,0x34,0x01,0x01,0x01,0x07,0x1e,0x23,0x0c,0x0c,0x05,
+  0x22,0x23,0x13,0x0d,0x1e,0x11,0x01,0x01,0x01,0x01,0x0e,0x1e,0x10,
+  0x27,0x0c,0x0b,0x26,0x12,0x13,0x13,0x1f,0x1f,0x1e,0x1e,0x0e,0x01,
+  0x01,0x38,0x1e,0x2a,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x16,0x1e,
+  0x38,0x01,0x01,0x01,0x39,0x1e,0x10,0x0c,0x13,0x1f,0x0b,0x0c,0x0c,
+  0x22,0x24,0x1e,0x38,0x01,0x01,0x01,0x1d,0x1e,0x23,0x05,0x22,0x0c,
+  0x26,0x0d,0x0c,0x02,0x19,0x15,0x1e,0x0f,0x01,0x01,0x0f,0x1e,0x0a,
+  0x22,0x13,0x13,0x1a,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,
+  0x2d,0x28,0x0d,0x1e,0x1e,0x3c,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,
+  0x18,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1b,0x01,
+  0x1b,0x1e,0x18,0x06,0x13,0x05,0x22,0x0c,0x1f,0x0d,0x18,0x1e,0x1e,
+  0x0f,0x01,0x01,0x01,0x04,0x1e,0x1e,0x09,0x1a,0x14,0x05,0x0c,0x29,
+  0x0d,0x15,0x18,0x1e,0x3b,0x01,0x0f,0x1e,0x15,0x28,0x06,0x14,0x19,
+  0x1e,0x28,0x0b,0x22,0x0d,0x02,0x1e,0x07,0x01,0x01,0x01,0x08,0x1e,
+  0x25,0x13,0x13,0x13,0x25,0x13,0x13,0x13,0x22,0x1e,0x32,0x01,0x01,
+  0x01,0x0f,0x1e,0x26,0x13,0x13,0x13,0x05,0x1e,0x28,0x13,0x13,0x0c,
+  0x13,0x1e,0x1e,0x01,0x17,0x1e,0x31,0x0c,0x0c,0x1f,0x13,0x1e,0x1e,
+  0x13,0x27,0x0b,0x0c,0x03,0x1e,0x2f,0x01,0x20,0x1e,0x18,0x0d,0x26,
+  0x28,0x09,0x1e,0x0b,0x05,0x1f,0x0c,0x21,0x1e,0x36,0x01,0x00,0x1e,
+  0x1e,0x24,0x0d,0x26,0x10,0x1e,0x23,0x13,0x05,0x13,0x21,0x1e,0x2b,
+  0x01,0x01,0x01,0x1e,0x1e,0x1f,0x13,0x27,0x1a,0x29,0x19,0x05,0x14,
+  0x1e,0x1e,0x00,0x01,0x01,0x01,0x3b,0x1e,0x0a,0x05,0x0c,0x05,0x12,
+  0x1e,0x0d,0x0b,0x14,0x06,0x0a,0x1e,0x3b,0x01,0x36,0x1e,0x1e,0x13,
+  0x13,0x13,0x13,0x1a,0x13,0x13,0x13,0x13,0x10,0x1e,0x0e,0x01,0x01,
+  0x33,0x1e,0x1f,0x1f,0x0c,0x13,0x0d,0x14,0x0c,0x13,0x05,0x2d,0x1e,
+  0x0f,0x01,0x35,0x1e,0x2d,0x1f,0x0c,0x22,0x0b,0x1e,0x10,0x22,0x14,
+  0x1f,0x24,0x1e,0x39,0x01,0x01,0x0f,0x1e,0x26,0x0c,0x19,0x0b,0x0d,
+  0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x2b,0x33,0x1e,0x23,0x19,0x06,
+  0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x3d,0x1e,0x1e,
+  0x18,0x25,0x13,0x0c,0x0c,0x13,0x0b,0x0d,0x1e,0x38,0x01,0x01,0x34,
+  0x1e,0x18,0x29,0x12,0x05,0x14,0x23,0x1f,0x2a,0x1e,0x1e,0x1e,0x3d,
+  0x01,0x01,0x0f,0x1e,0x26,0x22,0x22,0x26,0x06,0x1e,0x25,0x29,0x1f,
+  0x02,0x23,0x1e,0x1e,0x01,0x01,0x01,0x1c,0x1e,0x06,0x13,0x13,0x13,
+  0x1e,0x13,0x13,0x13,0x1f,0x1e,0x33,0x01,0x01,0x01,0x0f,0x1e,0x26,
+  0x13,0x13,0x13,0x05,0x1e,0x28,0x13,0x0c,0x0c,0x13,0x1e,0x1e,0x01,
+  0x3c,0x1e,0x2a,0x0c,0x05,0x02,0x0c,0x18,0x2d,0x0c,0x1f,0x0c,0x05,
+  0x16,0x1e,0x08,0x01,0x01,0x04,0x1e,0x1e,0x2a,0x26,0x26,0x24,0x05,
+  0x0b,0x02,0x13,0x10,0x1e,0x2b,0x01,0x01,0x3d,0x1e,0x1e,0x31,0x15,
+  0x06,0x02,0x22,0x13,0x1f,0x0c,0x10,0x1e,0x0e,0x01,0x01,0x2f,0x1e,
+  0x1e,0x1f,0x0c,0x1f,0x12,0x2a,0x22,0x0c,0x24,0x10,0x1e,0x17,0x01,
+  0x01,0x01,0x0f,0x1e,0x15,0x0d,0x05,0x0b,0x1a,0x23,0x05,0x0b,0x14,
+  0x0c,0x29,0x1e,0x30,0x01,0x3a,0x1e,0x26,0x13,0x13,0x13,0x14,0x1e,
+  0x26,0x13,0x13,0x13,0x16,0x1e,0x1b,0x01,0x34,0x1e,0x1e,0x05,0x05,
+  0x13,0x23,0x1e,0x15,0x05,0x0c,0x13,0x27,0x1e,0x11,0x01,0x17,0x1e,
+  0x03,0x05,0x27,0x05,0x23,0x26,0x0d,0x14,0x22,0x05,0x22,0x1e,0x3e,
+  0x01,0x01,0x0f,0x1e,0x28,0x13,0x05,0x0b,0x0d,0x1e,0x2c,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x39,0x1e,0x1e,0x0b,0x23,0x1e,0x1e,0x17,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x34,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x34,0x00,0x01,0x01,0x01,0x01,0x01,0x2f,0x38,0x1e,0x1e,0x1e,0x21,
+  0x22,0x13,0x13,0x0c,0x1e,0x1e,0x00,0x01,0x35,0x1e,0x2d,0x0c,0x0c,
+  0x1f,0x1a,0x21,0x1e,0x1e,0x1e,0x38,0x36,0x01,0x01,0x01,0x0f,0x1e,
+  0x15,0x12,0x22,0x27,0x12,0x1e,0x0a,0x14,0x24,0x12,0x22,0x1e,0x1e,
+  0x01,0x01,0x01,0x1d,0x1e,0x26,0x1f,0x13,0x13,0x1e,0x13,0x13,0x13,
+  0x13,0x21,0x1e,0x3c,0x01,0x01,0x0f,0x1e,0x26,0x13,0x13,0x13,0x05,
+  0x1e,0x28,0x13,0x1f,0x0c,0x05,0x1e,0x1e,0x01,0x37,0x1e,0x06,0x0c,
+  0x13,0x13,0x05,0x18,0x09,0x14,0x02,0x0c,0x0c,0x26,0x1e,0x37,0x01,
+  0x01,0x1d,0x1e,0x1a,0x22,0x1f,0x0c,0x14,0x14,0x05,0x0c,0x19,0x10,
+  0x1e,0x2b,0x01,0x34,0x07,0x1e,0x29,0x13,0x23,0x1f,0x0c,0x1a,0x0c,
+  0x19,0x0c,0x10,0x1e,0x0e,0x01,0x01,0x38,0x1e,0x26,0x0c,0x14,0x02,
+  0x26,0x2d,0x22,0x0b,0x14,0x28,0x1e,0x0f,0x01,0x01,0x01,0x37,0x1e,
+  0x06,0x14,0x14,0x05,0x1f,0x19,0x24,0x29,0x29,0x29,0x25,0x1e,0x3b,
+  0x01,0x3a,0x1e,0x26,0x13,0x13,0x13,0x1f,0x19,0x05,0x13,0x13,0x13,
+  0x1a,0x1e,0x04,0x01,0x2f,0x1e,0x2a,0x1f,0x22,0x13,0x05,0x02,0x05,
+  0x05,0x13,0x0c,0x22,0x1e,0x07,0x01,0x2f,0x1e,0x2a,0x13,0x23,0x22,
+  0x05,0x05,0x0b,0x06,0x29,0x27,0x15,0x1e,0x3d,0x01,0x01,0x0f,0x1e,
+  0x26,0x19,0x12,0x14,0x29,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x35,0x38,0x1e,0x12,0x0d,0x15,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x09,0x19,0x0c,
+  0x2d,0x1e,0x35,0x01,0x3c,0x1e,0x2a,0x22,0x27,0x18,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x0f,0x1e,0x28,0x27,0x05,0x12,
+  0x0d,0x1e,0x24,0x22,0x0d,0x12,0x14,0x1e,0x1e,0x01,0x01,0x0e,0x1e,
+  0x10,0x0c,0x0c,0x02,0x22,0x1e,0x1a,0x13,0x13,0x13,0x24,0x1e,0x37,
+  0x01,0x01,0x0f,0x1e,0x26,0x13,0x13,0x13,0x05,0x1e,0x28,0x0c,0x02,
+  0x0c,0x1f,0x1e,0x1e,0x01,0x08,0x1e,0x25,0x13,0x1f,0x1f,0x13,0x18,
+  0x09,0x0c,0x13,0x05,0x27,0x15,0x1e,0x3a,0x01,0x36,0x1e,0x31,0x27,
+  0x05,0x05,0x26,0x1e,0x0d,0x0b,0x1f,0x0c,0x10,0x1e,0x2b,0x01,0x3c,
+  0x1e,0x16,0x13,0x05,0x27,0x02,0x1e,0x12,0x13,0x1f,0x13,0x10,0x1e,
+  0x0e,0x01,0x01,0x1e,0x1e,0x05,0x1f,0x0c,0x13,0x10,0x1e,0x05,0x0b,
+  0x06,0x12,0x1e,0x39,0x01,0x01,0x01,0x0f,0x1e,0x0a,0x22,0x0c,0x0b,
+  0x2a,0x1e,0x09,0x2a,0x03,0x25,0x10,0x1e,0x17,0x01,0x1b,0x1e,0x15,
+  0x13,0x22,0x13,0x13,0x0c,0x14,0x0d,0x27,0x19,0x26,0x1e,0x3a,0x01,
+  0x0f,0x1e,0x26,0x0c,0x13,0x0c,0x19,0x0c,0x23,0x29,0x1a,0x02,0x26,
+  0x1e,0x04,0x01,0x36,0x1e,0x31,0x0c,0x05,0x13,0x27,0x1e,0x1e,0x16,
+  0x03,0x2a,0x03,0x1e,0x0f,0x01,0x01,0x0f,0x1e,0x26,0x0c,0x0b,0x22,
+  0x1a,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,0x09,
+  0x26,0x27,0x2d,0x1e,0x2f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x20,0x2c,0x40,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,
+  0x34,0x3e,0x1e,0x1e,0x1e,0x1e,0x40,0x2c,0x20,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x0f,0x1e,0x0a,0x28,0x1f,0x02,0x12,0x16,0x19,0x05,
+  0x12,0x02,0x0b,0x1e,0x40,0x01,0x01,0x08,0x1e,0x16,0x27,0x13,0x14,
+  0x0b,0x2a,0x23,0x13,0x13,0x22,0x19,0x1e,0x38,0x01,0x01,0x0f,0x1e,
+  0x06,0x13,0x13,0x13,0x14,0x1e,0x28,0x13,0x0c,0x0c,0x0c,0x1e,0x1e,
+  0x01,0x2b,0x1e,0x21,0x13,0x05,0x13,0x1f,0x18,0x09,0x22,0x14,0x13,
+  0x23,0x03,0x1e,0x2f,0x01,0x17,0x1e,0x03,0x27,0x02,0x0c,0x0d,0x21,
+  0x27,0x22,0x13,0x1a,0x21,0x1e,0x2b,0x01,0x3a,0x1e,0x26,0x0c,0x13,
+  0x05,0x0d,0x09,0x1f,0x0b,0x27,0x13,0x09,0x1e,0x0e,0x01,0x01,0x1e,
+  0x1e,0x1f,0x13,0x22,0x14,0x12,0x15,0x22,0x1f,0x0c,0x0b,0x1e,0x1e,
+  0x00,0x01,0x01,0x08,0x1e,0x16,0x1f,0x19,0x22,0x19,0x1e,0x02,0x0c,
+  0x23,0x05,0x26,0x1e,0x0f,0x01,0x37,0x1e,0x24,0x13,0x0c,0x13,0x27,
+  0x1e,0x18,0x0a,0x03,0x16,0x21,0x1e,0x36,0x01,0x17,0x1e,0x31,0x1f,
+  0x1f,0x13,0x1a,0x1e,0x1e,0x2a,0x2a,0x03,0x03,0x1e,0x0f,0x01,0x35,
+  0x1e,0x2d,0x1f,0x22,0x0c,0x23,0x09,0x06,0x14,0x05,0x0b,0x29,0x1e,
+  0x11,0x01,0x01,0x0f,0x1e,0x06,0x1a,0x19,0x13,0x29,0x1e,0x2c,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x24,0x22,0x28,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x37,0x38,0x1e,0x38,0x2b,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x38,0x37,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,
+  0x1e,0x0d,0x0b,0x27,0x29,0x27,0x15,0x28,0x1f,0x02,0x27,0x1e,0x1e,
+  0x39,0x01,0x01,0x30,0x1e,0x24,0x13,0x23,0x0d,0x1f,0x22,0x13,0x13,
+  0x13,0x22,0x14,0x1e,0x40,0x01,0x01,0x1b,0x1e,0x15,0x13,0x13,0x13,
+  0x22,0x1e,0x03,0x05,0x22,0x0c,0x1f,0x1e,0x1e,0x01,0x2f,0x1e,0x2a,
+  0x13,0x23,0x05,0x0c,0x1e,0x1e,0x05,0x0c,0x0c,0x05,0x16,0x1e,0x3c,
+  0x01,0x17,0x1e,0x03,0x14,0x0c,0x14,0x27,0x0c,0x19,0x12,0x13,0x05,
+  0x18,0x1e,0x3d,0x01,0x08,0x1e,0x25,0x13,0x05,0x22,0x1f,0x1f,0x1f,
+  0x13,0x1f,0x13,0x21,0x1e,0x11,0x01,0x08,0x1e,0x10,0x1f,0x0c,0x05,
+  0x0c,0x0c,0x05,0x13,0x19,0x02,0x05,0x03,0x1e,0x17,0x01,0x01,0x20,
+  0x1e,0x18,0x0c,0x19,0x12,0x13,0x05,0x22,0x14,0x14,0x13,0x1e,0x1e,
+  0x17,0x01,0x3c,0x1e,0x03,0x13,0x1f,0x13,0x14,0x1e,0x1a,0x1f,0x1f,
+  0x14,0x0a,0x1e,0x3b,0x01,0x01,0x1e,0x1e,0x0c,0x13,0x0b,0x23,0x10,
+  0x1a,0x1f,0x1f,0x1f,0x02,0x1e,0x11,0x01,0x01,0x11,0x1e,0x06,0x0b,
+  0x05,0x29,0x02,0x1f,0x0b,0x22,0x0b,0x15,0x1e,0x0f,0x01,0x01,0x0f,
+  0x1e,0x26,0x05,0x22,0x14,0x0d,0x1e,0x39,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x08,0x1e,0x09,0x19,0x02,0x09,0x1e,0x37,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3a,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x39,0x35,0x01,0x01,0x1d,
+  0x1e,0x27,0x23,0x1f,0x23,0x0b,0x05,0x14,0x13,0x22,0x1f,0x14,0x31,
+  0x1e,0x17,0x01,0x08,0x1e,0x25,0x13,0x22,0x13,0x0c,0x0d,0x23,0x13,
+  0x13,0x0c,0x0c,0x1e,0x1e,0x01,0x20,0x1e,0x09,0x0c,0x13,0x0c,0x0b,
+  0x24,0x1a,0x02,0x05,0x1f,0x22,0x18,0x1e,0x20,0x01,0x00,0x32,0x1e,
+  0x0d,0x05,0x14,0x22,0x27,0x12,0x0c,0x1f,0x0b,0x0b,0x1e,0x3e,0x01,
+  0x35,0x40,0x1e,0x22,0x05,0x0c,0x23,0x02,0x1a,0x0c,0x14,0x13,0x14,
+  0x1e,0x1e,0x01,0x38,0x1e,0x06,0x05,0x1f,0x02,0x05,0x1f,0x22,0x0c,
+  0x23,0x22,0x05,0x26,0x1e,0x0f,0x01,0x01,0x01,0x38,0x1e,0x2d,0x05,
+  0x0c,0x1f,0x0b,0x1f,0x0b,0x06,0x1e,0x1e,0x38,0x01,0x01,0x01,0x1e,
+  0x1e,0x13,0x0c,0x13,0x0c,0x13,0x13,0x1f,0x0c,0x23,0x10,0x1e,0x0e,
+  0x01,0x01,0x1d,0x1e,0x23,0x13,0x13,0x0c,0x13,0x1f,0x13,0x0b,0x23,
+  0x1e,0x1e,0x0f,0x01,0x01,0x3b,0x1e,0x1e,0x12,0x0c,0x0c,0x22,0x02,
+  0x23,0x14,0x31,0x1e,0x1e,0x36,0x01,0x01,0x04,0x1e,0x0d,0x0b,0x1f,
+  0x0b,0x24,0x1e,0x40,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x38,0x1e,0x15,0x28,0x24,0x1e,0x11,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x3a,0x38,0x1e,0x1e,0x1e,0x32,0x38,0x1e,
+  0x1e,0x1e,0x33,0x39,0x35,0x01,0x01,0x08,0x1e,0x09,0x22,0x1f,0x1a,
+  0x19,0x25,0x1e,0x1e,0x13,0x13,0x14,0x1f,0x26,0x1e,0x04,0x01,0x35,
+  0x1e,0x1e,0x13,0x13,0x13,0x05,0x14,0x13,0x05,0x0c,0x13,0x03,0x1e,
+  0x38,0x01,0x01,0x38,0x1e,0x1a,0x05,0x0c,0x1f,0x1f,0x0c,0x13,0x0c,
+  0x23,0x1a,0x1e,0x38,0x01,0x01,0x01,0x37,0x1e,0x1e,0x21,0x0a,0x10,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x3d,0x01,0x01,0x30,0x1e,0x1e,
+  0x21,0x0a,0x31,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x11,0x01,0x1e,
+  0x1e,0x05,0x1f,0x0c,0x13,0x23,0x1e,0x09,0x1a,0x29,0x02,0x1f,0x29,
+  0x1e,0x38,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x18,0x16,0x25,0x16,
+  0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x38,0x1e,0x1e,0x14,0x13,
+  0x05,0x13,0x13,0x13,0x14,0x09,0x1e,0x38,0x01,0x01,0x01,0x30,0x1e,
+  0x1e,0x0b,0x0c,0x0c,0x13,0x05,0x13,0x13,0x10,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x3b,0x1e,0x1e,0x1e,0x03,0x16,0x25,0x2d,0x1e,0x1e,0x32,
+  0x36,0x01,0x01,0x01,0x1b,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x39,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x1e,0x21,0x05,
+  0x15,0x1e,0x37,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x1c,0x1e,0x06,0x24,0x05,0x05,0x23,0x31,0x1e,0x1e,
+  0x13,0x0b,0x13,0x22,0x22,0x1e,0x07,0x01,0x01,0x38,0x1e,0x1e,0x02,
+  0x13,0x1f,0x22,0x13,0x05,0x0c,0x15,0x1e,0x32,0x2b,0x01,0x01,0x3a,
+  0x1e,0x1e,0x29,0x14,0x23,0x13,0x05,0x05,0x0c,0x0d,0x1e,0x1e,0x37,
+  0x01,0x01,0x01,0x01,0x37,0x38,0x1e,0x1e,0x1e,0x38,0x38,0x1e,0x1e,
+  0x1e,0x3e,0x3d,0x00,0x01,0x01,0x01,0x30,0x32,0x1e,0x1e,0x1e,0x1d,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x11,0x0e,0x01,0x1e,0x1e,0x19,0x13,0x05,
+  0x05,0x23,0x1e,0x1e,0x27,0x0c,0x22,0x02,0x23,0x10,0x1e,0x0e,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x32,0x30,0x01,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x28,0x25,0x16,0x1e,
+  0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x30,0x1e,0x1e,0x1e,0x31,
+  0x16,0x28,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x3b,
+  0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x1b,0x38,0x1e,0x1e,0x1e,0x40,0x39,0x35,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x17,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x08,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x04,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x18,0x25,0x25,0x28,
+  0x10,0x1e,0x1e,0x1e,0x36,0x01,0x01,0x01,0x01,0x3a,0x1e,0x1e,0x1e,
+  0x03,0x25,0x25,0x2a,0x1e,0x1e,0x1e,0x37,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x36,0x1b,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x36,0x2e,0x36,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x20,0x08,0x08,0x08,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x34,0x04,0x3e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x11,0x2b,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x30,0x1d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x2f,0x08,
+  0x08,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x38,0x17,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x08,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x07,0x04,0x34,0x01,
+  0x01,0x01,0x20,0x2c,0x07,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x36,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1d,0x30,0x0f,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x0e,0x2e,0x08,0x3c,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x17,0x08,0x1b,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x20,0x08,0x08,0x2e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x17,0x08,0x08,0x2f,0x34,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x11,
+  0x33,0x11,0x0f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x34,0x01,
+  0x35,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x30,0x32,
+  0x3e,0x3d,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x39,
+  0x33,0x39,0x0f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x35,0x39,0x33,0x1e,0x38,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x39,0x33,
+  0x38,0x37,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,
+  0x1e,0x1e,0x38,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x34,0x2e,0x3d,0x04,0x3d,0x2c,0x0f,0x17,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x35,0x2e,0x3d,0x3d,0x2c,0x1c,0x3c,0x2f,
+  0x0f,0x2c,0x3d,0x3d,0x3b,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x0e,0x01,0x00,0x35,0x00,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x20,0x01,0x20,0x20,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x34,0x01,
+  0x20,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x34,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x32,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x01,0x01,0x01,0x01,0x08,0x38,0x1e,0x1e,0x38,0x1e,0x1e,0x3d,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x35,0x3e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x11,0x1e,0x1e,0x1e,0x1e,0x39,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x39,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x30,0x1e,0x1d,0x30,0x36,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x3e,0x1e,0x1e,0x1e,0x1e,0x39,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x33,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x3e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x2e,0x33,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x07,0x3b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x38,
+  0x1e,0x38,0x1e,0x1e,0x1e,0x3d,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x38,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x33,0x1e,0x1e,0x04,0x34,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x3c,0x1c,0x1c,0x0f,0x17,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x2b,0x1b,0x37,0x2c,0x3a,0x01,0x01,0x01,0x01,0x1c,0x1d,0x1e,
+  0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x03,0x0c,0x19,0x21,0x1e,0x3a,0x01,0x01,0x08,
+  0x1e,0x1e,0x09,0x18,0x1e,0x2d,0x18,0x1e,0x30,0x01,0x01,0x01,0x01,
+  0x01,0x2c,0x1e,0x2a,0x22,0x22,0x21,0x1e,0x3a,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x0e,0x1e,0x10,0x0c,0x02,0x2d,0x1e,0x3a,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x00,0x1e,0x1e,0x0c,0x13,0x12,0x13,0x13,
+  0x21,0x1e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x2a,0x1f,0x19,0x2d,0x1e,0x3b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x1e,0x13,0x13,0x0a,0x1e,0x3b,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x40,0x1e,0x2a,0x0d,0x0d,0x29,0x27,
+  0x06,0x2d,0x1e,0x38,0x2b,0x01,0x01,0x01,0x2e,0x1e,0x1e,0x0a,0x0d,
+  0x29,0x1a,0x24,0x1e,0x18,0x06,0x1a,0x06,0x12,0x16,0x1e,0x1e,0x3b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x37,0x1e,0x1e,0x10,0x1e,0x1e,0x2d,
+  0x1e,0x1e,0x3d,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x18,
+  0x09,0x1e,0x09,0x09,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x00,
+  0x3e,0x1e,0x18,0x18,0x1e,0x09,0x1e,0x1e,0x04,0x01,0x01,0x01,0x01,
+  0x2b,0x32,0x1e,0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x34,0x38,0x1e,0x1e,
+  0x1e,0x1e,0x17,0x01,0x01,0x01,0x01,0x01,0x34,0x11,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x11,0x01,0x01,0x1c,0x1e,0x1e,0x1e,0x1e,0x1e,0x00,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x1e,0x1e,
+  0x13,0x13,0x13,0x27,0x1e,0x40,0x20,0x01,0x1c,0x1e,0x12,0x13,0x05,
+  0x29,0x13,0x22,0x1e,0x32,0x01,0x01,0x01,0x01,0x2f,0x1e,0x1e,0x1f,
+  0x13,0x13,0x27,0x1e,0x40,0x35,0x01,0x01,0x01,0x01,0x01,0x01,0x07,
+  0x1e,0x22,0x13,0x27,0x1e,0x3e,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x23,0x13,0x16,0x22,0x13,0x2d,0x1e,0x35,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1d,0x1e,0x0b,0x0b,0x15,0x1e,0x38,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2f,0x1e,0x1e,0x13,
+  0x1f,0x1f,0x29,0x1e,0x32,0x00,0x01,0x01,0x01,0x01,0x01,0x34,0x33,
+  0x1e,0x0c,0x13,0x1e,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x25,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x10,0x1e,
+  0x38,0x01,0x01,0x34,0x3e,0x1e,0x0d,0x1f,0x0b,0x0c,0x0c,0x22,0x22,
+  0x05,0x0c,0x0c,0x1f,0x13,0x0c,0x29,0x1e,0x40,0x20,0x01,0x01,0x01,
+  0x01,0x01,0x38,0x1e,0x23,0x13,0x25,0x13,0x13,0x22,0x1e,0x3e,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x31,0x13,0x13,
+  0x18,0x1e,0x34,0x01,0x01,0x01,0x01,0x01,0x2f,0x1e,0x2a,0x13,0x13,
+  0x0c,0x13,0x05,0x1e,0x07,0x01,0x01,0x01,0x01,0x38,0x1e,0x03,0x24,
+  0x12,0x15,0x18,0x1e,0x34,0x04,0x1e,0x10,0x1f,0x26,0x1e,0x0f,0x01,
+  0x01,0x01,0x01,0x01,0x04,0x1e,0x10,0x15,0x24,0x27,0x10,0x1e,0x36,
+  0x2b,0x32,0x1e,0x0b,0x0b,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,0x28,0x13,0x27,0x02,0x13,
+  0x25,0x1e,0x08,0x01,0x3b,0x1e,0x0a,0x13,0x0b,0x16,0x13,0x1a,0x1e,
+  0x04,0x01,0x01,0x01,0x01,0x0f,0x1e,0x06,0x13,0x1a,0x23,0x0c,0x25,
+  0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,0x1e,0x14,0x0c,
+  0x2a,0x1e,0x2f,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x21,0x28,0x1e,0x16,0x16,0x1e,0x38,0x2f,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x1c,0x1e,0x2a,0x13,0x13,0x1e,0x1e,0x0f,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x36,0x37,0x1e,0x26,0x05,0x12,0x27,0x14,0x03,
+  0x1e,0x17,0x01,0x01,0x01,0x01,0x3d,0x1e,0x1e,0x1e,0x21,0x0a,0x1e,
+  0x0a,0x03,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x2f,0x1e,0x1e,0x13,
+  0x13,0x1f,0x1f,0x13,0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x1b,
+  0x1e,0x2a,0x0c,0x13,0x1f,0x0c,0x1f,0x13,0x05,0x23,0x0c,0x23,0x29,
+  0x0c,0x13,0x1f,0x25,0x1e,0x08,0x01,0x01,0x01,0x01,0x35,0x37,0x1e,
+  0x06,0x13,0x03,0x19,0x13,0x29,0x1e,0x3d,0x35,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x3e,0x1e,0x14,0x13,0x1e,0x13,0x0c,0x1e,0x33,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x0e,0x1e,0x10,0x13,0x0b,0x24,0x13,0x1a,0x1e,
+  0x04,0x01,0x01,0x01,0x2b,0x1e,0x21,0x0c,0x0c,0x0c,0x13,0x10,0x1e,
+  0x2b,0x07,0x1e,0x22,0x0c,0x31,0x1e,0x17,0x01,0x01,0x01,0x01,0x01,
+  0x07,0x1e,0x14,0x0c,0x23,0x14,0x15,0x1e,0x1b,0x38,0x1e,0x03,0x13,
+  0x27,0x1e,0x2c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x1d,0x1e,0x0b,0x24,0x1e,0x1e,0x0b,0x26,0x1e,0x3a,0x01,
+  0x35,0x1e,0x18,0x2d,0x18,0x1e,0x10,0x2d,0x1e,0x1b,0x01,0x01,0x01,
+  0x35,0x39,0x1e,0x29,0x23,0x1e,0x1e,0x14,0x25,0x1e,0x04,0x34,0x01,
+  0x01,0x01,0x01,0x1c,0x40,0x1e,0x1e,0x1e,0x22,0x0a,0x1e,0x1e,0x04,
+  0x34,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x10,0x16,0x0d,0x26,
+  0x21,0x09,0x1e,0x1e,0x2f,0x01,0x01,0x01,0x01,0x00,0x17,0x1b,0x3e,
+  0x1e,0x06,0x0c,0x1a,0x1e,0x11,0x3b,0x35,0x01,0x01,0x01,0x01,0x36,
+  0x1e,0x1e,0x1e,0x1a,0x06,0x1e,0x1e,0x0d,0x10,0x1e,0x1e,0x1d,0x00,
+  0x01,0x37,0x1e,0x18,0x2d,0x2d,0x1e,0x1e,0x1e,0x1e,0x1e,0x18,0x2d,
+  0x09,0x1e,0x38,0x01,0x01,0x37,0x1e,0x06,0x19,0x0b,0x14,0x26,0x09,
+  0x13,0x13,0x13,0x13,0x18,0x1e,0x34,0x01,0x04,0x1e,0x0d,0x13,0x05,
+  0x13,0x26,0x2a,0x0c,0x13,0x05,0x0c,0x1e,0x2d,0x05,0x23,0x0c,0x24,
+  0x1e,0x39,0x01,0x01,0x01,0x2b,0x40,0x1e,0x1e,0x1e,0x21,0x1e,0x09,
+  0x2d,0x1e,0x1e,0x1e,0x40,0x2b,0x01,0x01,0x01,0x01,0x36,0x3e,0x1e,
+  0x2d,0x16,0x1e,0x31,0x2d,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x2f,0x3e,0x1e,0x10,0x21,0x1e,0x2a,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x38,0x1e,0x10,0x05,0x0c,0x14,0x2d,0x1e,0x38,0x1e,0x03,0x13,
+  0x24,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x12,
+  0x19,0x12,0x16,0x1e,0x08,0x1e,0x1e,0x13,0x13,0x1e,0x1e,0x1b,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1c,0x1e,
+  0x1e,0x31,0x25,0x16,0x1e,0x1e,0x1e,0x36,0x01,0x01,0x38,0x1e,0x21,
+  0x2a,0x16,0x03,0x1e,0x1e,0x2b,0x01,0x01,0x01,0x39,0x1e,0x1e,0x31,
+  0x16,0x24,0x12,0x2a,0x03,0x1e,0x1e,0x04,0x01,0x01,0x01,0x1c,0x1e,
+  0x1e,0x25,0x0d,0x24,0x03,0x15,0x28,0x1e,0x1e,0x04,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x21,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x24,0x1e,
+  0x1e,0x2b,0x01,0x01,0x00,0x32,0x1e,0x1e,0x1e,0x1e,0x1e,0x02,0x1a,
+  0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x01,0x01,0x38,0x1e,0x28,0x25,0x2d,
+  0x18,0x1e,0x18,0x2d,0x31,0x0a,0x31,0x1e,0x3d,0x01,0x11,0x1e,0x19,
+  0x13,0x05,0x14,0x13,0x1e,0x22,0x13,0x13,0x13,0x13,0x18,0x1e,0x34,
+  0x01,0x30,0x1e,0x29,0x05,0x0b,0x14,0x02,0x1e,0x03,0x28,0x0a,0x26,
+  0x1e,0x38,0x01,0x01,0x38,0x1e,0x27,0x13,0x1f,0x0c,0x10,0x1e,0x13,
+  0x0c,0x0c,0x13,0x0d,0x14,0x13,0x05,0x1f,0x05,0x1e,0x1e,0x00,0x01,
+  0x01,0x38,0x1e,0x25,0x25,0x16,0x03,0x1e,0x1e,0x31,0x2a,0x0a,0x0a,
+  0x1e,0x38,0x01,0x01,0x01,0x36,0x1e,0x1e,0x2a,0x26,0x26,0x29,0x26,
+  0x25,0x18,0x1e,0x38,0x01,0x01,0x01,0x01,0x2f,0x1e,0x1e,0x2a,0x15,
+  0x06,0x24,0x26,0x15,0x09,0x1e,0x38,0x0e,0x01,0x01,0x2b,0x3e,0x1e,
+  0x22,0x0c,0x13,0x2d,0x1e,0x1e,0x1e,0x22,0x13,0x1e,0x1e,0x3d,0x3d,
+  0x1c,0x08,0x01,0x01,0x01,0x34,0x04,0x1e,0x16,0x0b,0x0c,0x25,0x1e,
+  0x38,0x1e,0x16,0x14,0x24,0x1e,0x3e,0x1d,0x1c,0x3c,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x1e,0x1e,0x13,0x13,0x13,
+  0x13,0x21,0x1e,0x36,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,0x15,
+  0x1e,0x1b,0x01,0x01,0x2e,0x1e,0x18,0x0c,0x13,0x13,0x0c,0x13,0x0c,
+  0x13,0x22,0x1e,0x40,0x20,0x01,0x2e,0x1e,0x1e,0x19,0x1f,0x13,0x27,
+  0x0b,0x0c,0x19,0x0b,0x1e,0x1e,0x2e,0x01,0x01,0x00,0x1e,0x1e,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x15,0x1e,0x3d,0x01,0x01,
+  0x30,0x1e,0x03,0x15,0x25,0x0a,0x31,0x1e,0x2d,0x28,0x0a,0x0a,0x09,
+  0x1e,0x35,0x01,0x20,0x1e,0x09,0x1f,0x0c,0x27,0x19,0x1e,0x26,0x14,
+  0x19,0x22,0x19,0x1e,0x32,0x01,0x37,0x1e,0x26,0x13,0x05,0x02,0x14,
+  0x1e,0x12,0x23,0x14,0x13,0x22,0x1e,0x1e,0x00,0x01,0x37,0x1e,0x15,
+  0x12,0x14,0x23,0x0d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,
+  0x11,0x1e,0x02,0x0c,0x19,0x0c,0x21,0x18,0x13,0x0c,0x1f,0x13,0x14,
+  0x05,0x0d,0x1a,0x19,0x24,0x1e,0x39,0x01,0x01,0x34,0x1e,0x18,0x0b,
+  0x0c,0x1f,0x19,0x1e,0x06,0x24,0x14,0x13,0x13,0x1e,0x1e,0x01,0x01,
+  0x34,0x3e,0x1e,0x15,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x2d,0x1e,
+  0x3a,0x01,0x01,0x00,0x32,0x1e,0x06,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x13,0x09,0x1e,0x11,0x01,0x01,0x01,0x07,0x1e,0x14,0x1f,0x1f,0x2d,
+  0x1e,0x1e,0x0a,0x13,0x26,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x08,0x01,
+  0x01,0x01,0x1b,0x1e,0x15,0x02,0x19,0x25,0x1e,0x1e,0x1e,0x0c,0x22,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x39,0x35,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x1f,0x2d,0x1e,0x35,
+  0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x01,
+  0x04,0x1e,0x0d,0x13,0x13,0x0c,0x0c,0x0c,0x1f,0x0c,0x13,0x25,0x1e,
+  0x1c,0x01,0x38,0x1e,0x0d,0x13,0x1f,0x0c,0x1f,0x05,0x05,0x22,0x0c,
+  0x26,0x1e,0x04,0x01,0x01,0x3b,0x1e,0x2a,0x13,0x13,0x13,0x0c,0x03,
+  0x27,0x13,0x13,0x13,0x14,0x1e,0x3e,0x01,0x01,0x1d,0x1e,0x0b,0x13,
+  0x13,0x0c,0x05,0x1e,0x13,0x1f,0x0b,0x0c,0x25,0x1e,0x3b,0x01,0x00,
+  0x1e,0x1e,0x1f,0x05,0x05,0x1f,0x1e,0x15,0x05,0x24,0x12,0x24,0x1e,
+  0x04,0x01,0x37,0x1e,0x24,0x1f,0x23,0x13,0x0b,0x1e,0x12,0x19,0x12,
+  0x14,0x22,0x1e,0x1e,0x00,0x01,0x3d,0x1e,0x29,0x0b,0x22,0x1f,0x27,
+  0x1e,0x24,0x1f,0x23,0x13,0x1e,0x1e,0x01,0x01,0x11,0x1e,0x02,0x13,
+  0x1f,0x13,0x21,0x1e,0x0c,0x23,0x05,0x13,0x1e,0x1e,0x25,0x03,0x2a,
+  0x16,0x1e,0x2e,0x01,0x01,0x01,0x1e,0x1e,0x02,0x19,0x1f,0x05,0x1e,
+  0x0a,0x0c,0x14,0x27,0x0c,0x1e,0x1e,0x01,0x01,0x2f,0x1e,0x2a,0x13,
+  0x13,0x13,0x13,0x27,0x13,0x13,0x13,0x27,0x1e,0x39,0x01,0x01,0x08,
+  0x1e,0x03,0x13,0x13,0x13,0x0c,0x0c,0x13,0x13,0x13,0x13,0x1e,0x1e,
+  0x01,0x01,0x01,0x33,0x1e,0x22,0x05,0x13,0x2d,0x1e,0x1e,0x05,0x0c,
+  0x1e,0x1e,0x15,0x0d,0x0d,0x24,0x1e,0x38,0x01,0x01,0x01,0x3a,0x1e,
+  0x15,0x14,0x1f,0x16,0x1e,0x1e,0x26,0x0b,0x03,0x1e,0x02,0x23,0x19,
+  0x24,0x1e,0x1e,0x39,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x1e,0x1e,0x13,0x13,0x13,0x1f,0x2d,0x1e,0x35,0x01,0x01,0x1e,0x1e,
+  0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x34,0x1e,0x18,0x14,0x14,
+  0x13,0x1f,0x25,0x06,0x05,0x13,0x05,0x23,0x1e,0x1d,0x01,0x1e,0x1e,
+  0x05,0x23,0x0c,0x14,0x0a,0x31,0x0c,0x22,0x0b,0x23,0x1e,0x3e,0x01,
+  0x01,0x3d,0x1e,0x15,0x14,0x13,0x13,0x0b,0x1e,0x16,0x13,0x13,0x13,
+  0x0c,0x18,0x1e,0x34,0x01,0x39,0x1e,0x02,0x13,0x0c,0x0c,0x1a,0x1e,
+  0x0c,0x13,0x1f,0x0c,0x31,0x1e,0x36,0x01,0x00,0x1e,0x1e,0x22,0x23,
+  0x02,0x22,0x1e,0x15,0x19,0x0b,0x19,0x12,0x1e,0x04,0x01,0x37,0x1e,
+  0x26,0x0c,0x27,0x0b,0x05,0x1e,0x24,0x0b,0x0c,0x1f,0x1a,0x1e,0x1e,
+  0x00,0x01,0x1b,0x1e,0x0a,0x19,0x22,0x0b,0x23,0x15,0x19,0x23,0x1f,
+  0x22,0x1e,0x1e,0x01,0x01,0x1c,0x1e,0x12,0x23,0x19,0x1f,0x0d,0x29,
+  0x22,0x1f,0x0c,0x0c,0x2d,0x28,0x0b,0x22,0x1f,0x24,0x1e,0x04,0x01,
+  0x01,0x00,0x1e,0x1e,0x14,0x1f,0x0d,0x1a,0x1e,0x25,0x0b,0x13,0x05,
+  0x0c,0x1e,0x1e,0x01,0x01,0x3c,0x1e,0x16,0x27,0x06,0x06,0x1e,0x09,
+  0x13,0x13,0x13,0x05,0x18,0x1e,0x34,0x01,0x1c,0x1e,0x12,0x13,0x13,
+  0x13,0x12,0x31,0x0c,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x40,
+  0x1e,0x14,0x22,0x13,0x2d,0x1e,0x24,0x05,0x0a,0x1e,0x03,0x13,0x1f,
+  0x1f,0x13,0x1e,0x1e,0x01,0x01,0x01,0x3a,0x1e,0x15,0x22,0x05,0x0a,
+  0x1e,0x1e,0x14,0x14,0x1e,0x0d,0x05,0x0b,0x02,0x13,0x14,0x1e,0x40,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,
+  0x13,0x1f,0x2d,0x1e,0x35,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,
+  0x2a,0x1e,0x2f,0x01,0x0e,0x1e,0x10,0x13,0x13,0x0c,0x02,0x1e,0x25,
+  0x22,0x13,0x1f,0x0c,0x1e,0x1e,0x01,0x33,0x1e,0x1f,0x05,0x1f,0x0c,
+  0x2d,0x1e,0x27,0x05,0x0b,0x06,0x09,0x1e,0x20,0x01,0x38,0x1e,0x19,
+  0x14,0x27,0x0c,0x23,0x1e,0x16,0x13,0x13,0x1f,0x05,0x18,0x1e,0x34,
+  0x01,0x39,0x1e,0x1a,0x13,0x1f,0x0c,0x0d,0x1e,0x13,0x05,0x13,0x13,
+  0x03,0x1e,0x17,0x01,0x00,0x1e,0x1e,0x14,0x0b,0x0c,0x22,0x1e,0x15,
+  0x02,0x06,0x1a,0x29,0x1e,0x04,0x01,0x37,0x1e,0x15,0x14,0x27,0x15,
+  0x15,0x1e,0x12,0x1a,0x27,0x1f,0x23,0x1e,0x1e,0x00,0x01,0x36,0x1e,
+  0x1e,0x22,0x22,0x14,0x13,0x0d,0x02,0x23,0x0b,0x29,0x1e,0x38,0x01,
+  0x01,0x08,0x1e,0x21,0x13,0x05,0x0c,0x02,0x13,0x0b,0x23,0x13,0x14,
+  0x05,0x0c,0x1f,0x1f,0x0c,0x2a,0x1e,0x2e,0x01,0x01,0x00,0x1e,0x1e,
+  0x22,0x05,0x0c,0x14,0x1e,0x0a,0x0c,0x02,0x0d,0x05,0x1e,0x1e,0x01,
+  0x01,0x34,0x07,0x1e,0x1e,0x16,0x12,0x24,0x1f,0x13,0x13,0x13,0x0c,
+  0x2d,0x1e,0x35,0x01,0x1c,0x1e,0x12,0x13,0x13,0x13,0x15,0x1e,0x05,
+  0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x39,0x1e,0x10,0x31,0x21,
+  0x1e,0x1e,0x1f,0x23,0x1e,0x1e,0x27,0x13,0x14,0x13,0x1f,0x1e,0x1e,
+  0x2b,0x01,0x01,0x36,0x1e,0x18,0x21,0x2a,0x18,0x1e,0x02,0x22,0x1e,
+  0x1e,0x14,0x12,0x16,0x02,0x19,0x12,0x10,0x1e,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x13,0x13,0x22,0x1f,0x2d,0x1e,
+  0x35,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,
+  0x0e,0x1e,0x10,0x1f,0x1f,0x13,0x1f,0x1e,0x15,0x13,0x0c,0x19,0x0c,
+  0x1e,0x1e,0x01,0x33,0x1e,0x05,0x1f,0x13,0x22,0x03,0x2d,0x23,0x22,
+  0x1f,0x0c,0x31,0x1e,0x17,0x01,0x39,0x1e,0x29,0x13,0x05,0x0c,0x22,
+  0x1e,0x25,0x19,0x0b,0x14,0x06,0x18,0x1e,0x34,0x01,0x39,0x1e,0x02,
+  0x0c,0x02,0x0c,0x0d,0x1e,0x13,0x1f,0x0c,0x05,0x31,0x1e,0x17,0x01,
+  0x00,0x1e,0x1e,0x0c,0x0b,0x0b,0x02,0x1e,0x0a,0x02,0x1f,0x0b,0x26,
+  0x1e,0x04,0x01,0x37,0x1e,0x06,0x1f,0x19,0x19,0x22,0x1e,0x26,0x19,
+  0x26,0x12,0x29,0x1e,0x1e,0x00,0x01,0x01,0x38,0x1e,0x18,0x19,0x24,
+  0x13,0x05,0x02,0x19,0x0d,0x1e,0x1e,0x08,0x01,0x01,0x01,0x38,0x1e,
+  0x03,0x1f,0x14,0x23,0x13,0x23,0x15,0x27,0x0b,0x13,0x14,0x23,0x13,
+  0x10,0x1e,0x3e,0x00,0x01,0x01,0x00,0x1e,0x1e,0x14,0x22,0x29,0x14,
+  0x1e,0x16,0x1f,0x0c,0x14,0x14,0x1e,0x1e,0x01,0x01,0x1b,0x1e,0x10,
+  0x23,0x0c,0x13,0x13,0x14,0x22,0x13,0x13,0x23,0x09,0x1e,0x20,0x01,
+  0x1c,0x1e,0x12,0x13,0x13,0x13,0x06,0x1e,0x1f,0x13,0x13,0x13,0x1e,
+  0x1e,0x01,0x01,0x01,0x35,0x39,0x1e,0x1e,0x1e,0x1e,0x12,0x13,0x16,
+  0x1e,0x2d,0x05,0x21,0x28,0x05,0x1f,0x1e,0x1e,0x38,0x01,0x01,0x01,
+  0x04,0x1e,0x1e,0x1e,0x1e,0x1e,0x27,0x22,0x1e,0x1e,0x1e,0x0b,0x23,
+  0x1f,0x0c,0x0b,0x1e,0x1e,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x1e,0x1e,0x0c,0x13,0x0c,0x1f,0x2d,0x1e,0x35,0x01,0x01,0x1e,
+  0x1e,0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,0x01,0x0e,0x1e,0x10,0x1f,
+  0x13,0x05,0x22,0x1e,0x16,0x1f,0x13,0x1f,0x13,0x1e,0x1e,0x01,0x33,
+  0x1e,0x1f,0x1f,0x0c,0x05,0x2d,0x1e,0x05,0x13,0x19,0x1a,0x1e,0x1e,
+  0x00,0x01,0x39,0x1e,0x27,0x02,0x0d,0x05,0x19,0x1e,0x25,0x05,0x0b,
+  0x14,0x05,0x18,0x1e,0x34,0x01,0x39,0x1e,0x27,0x13,0x0b,0x0c,0x1a,
+  0x1e,0x0c,0x19,0x0c,0x1f,0x31,0x1e,0x17,0x01,0x20,0x1e,0x09,0x05,
+  0x22,0x0c,0x05,0x21,0x15,0x19,0x27,0x02,0x06,0x1e,0x04,0x01,0x37,
+  0x1e,0x28,0x0d,0x02,0x12,0x06,0x1e,0x26,0x19,0x22,0x05,0x12,0x1e,
+  0x1e,0x00,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x27,0x19,0x0a,0x1e,
+  0x1e,0x1e,0x3a,0x01,0x01,0x01,0x01,0x2b,0x32,0x1e,0x1e,0x21,0x16,
+  0x15,0x1e,0x1e,0x1e,0x1e,0x28,0x25,0x21,0x1e,0x1e,0x38,0x2b,0x01,
+  0x01,0x01,0x00,0x1e,0x1e,0x02,0x1f,0x1f,0x05,0x2d,0x26,0x05,0x29,
+  0x22,0x0c,0x1e,0x1e,0x01,0x01,0x04,0x1e,0x06,0x1f,0x22,0x13,0x0a,
+  0x1e,0x22,0x0c,0x13,0x05,0x09,0x1e,0x20,0x01,0x30,0x1e,0x29,0x13,
+  0x13,0x13,0x0a,0x1e,0x14,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,
+  0x01,0x01,0x0e,0x2e,0x1e,0x09,0x13,0x0b,0x1e,0x1e,0x29,0x0c,0x05,
+  0x1f,0x0c,0x0b,0x05,0x1e,0x1e,0x01,0x01,0x01,0x01,0x34,0x2b,0x32,
+  0x1e,0x23,0x27,0x1e,0x1e,0x1e,0x19,0x19,0x22,0x29,0x09,0x1e,0x1e,
+  0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x14,
+  0x13,0x1f,0x1f,0x2d,0x1e,0x35,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,
+  0x13,0x2a,0x1e,0x2f,0x01,0x00,0x1e,0x1e,0x23,0x0c,0x13,0x13,0x0d,
+  0x27,0x13,0x0b,0x0b,0x02,0x1e,0x38,0x01,0x3e,0x1e,0x02,0x05,0x1f,
+  0x02,0x0d,0x06,0x22,0x0c,0x23,0x23,0x1e,0x40,0x01,0x01,0x38,0x1e,
+  0x02,0x0c,0x14,0x14,0x22,0x1e,0x2a,0x12,0x14,0x23,0x27,0x18,0x1e,
+  0x20,0x01,0x11,0x1e,0x02,0x05,0x0b,0x0c,0x05,0x1e,0x13,0x1f,0x13,
+  0x13,0x03,0x1e,0x17,0x01,0x01,0x1e,0x1e,0x05,0x02,0x02,0x19,0x22,
+  0x14,0x02,0x0b,0x22,0x29,0x1e,0x04,0x01,0x37,0x1e,0x06,0x27,0x19,
+  0x05,0x02,0x1e,0x28,0x1a,0x29,0x29,0x27,0x1e,0x1e,0x00,0x01,0x01,
+  0x01,0x2b,0x38,0x1e,0x1e,0x10,0x23,0x22,0x28,0x1e,0x3a,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x38,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x33,
+  0x1e,0x1f,0x0b,0x02,0x1f,0x1a,0x02,0x0c,0x05,0x1f,0x19,0x1e,0x1e,
+  0x01,0x00,0x1e,0x1e,0x27,0x05,0x1f,0x13,0x31,0x26,0x1f,0x14,0x13,
+  0x14,0x09,0x1e,0x20,0x01,0x1c,0x1e,0x06,0x13,0x13,0x13,0x02,0x24,
+  0x13,0x13,0x13,0x13,0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,0x20,0x07,
+  0x1e,0x0d,0x13,0x10,0x1e,0x1e,0x12,0x13,0x05,0x05,0x0c,0x1f,0x1f,
+  0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x31,0x1f,0x12,0x1e,
+  0x1e,0x10,0x0c,0x19,0x24,0x06,0x24,0x24,0x1e,0x38,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x1f,0x13,0x0c,0x1f,0x2d,
+  0x1e,0x35,0x01,0x01,0x1e,0x1e,0x13,0x13,0x13,0x13,0x2a,0x1e,0x2f,
+  0x01,0x01,0x3d,0x1e,0x06,0x13,0x05,0x0c,0x13,0x1f,0x13,0x13,0x0c,
+  0x21,0x1e,0x0f,0x01,0x3d,0x1e,0x25,0x1f,0x0c,0x13,0x0c,0x23,0x0b,
+  0x05,0x29,0x26,0x1e,0x2c,0x01,0x01,0x3d,0x1e,0x12,0x29,0x22,0x0c,
+  0x02,0x1e,0x25,0x0b,0x22,0x1f,0x05,0x1e,0x1e,0x00,0x01,0x1c,0x1e,
+  0x06,0x13,0x05,0x0c,0x0b,0x1f,0x23,0x19,0x1f,0x0b,0x03,0x1e,0x17,
+  0x01,0x01,0x1e,0x1e,0x05,0x13,0x0c,0x19,0x0b,0x22,0x29,0x29,0x02,
+  0x12,0x1e,0x04,0x01,0x37,0x1e,0x26,0x14,0x0b,0x27,0x27,0x1e,0x28,
+  0x1a,0x05,0x02,0x24,0x09,0x1e,0x20,0x01,0x01,0x01,0x01,0x11,0x1e,
+  0x1a,0x03,0x31,0x1f,0x24,0x1e,0x04,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x2b,0x08,0x1b,0x36,0x01,0x01,0x2b,0x1b,0x08,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x27,0x0b,0x22,
+  0x1f,0x14,0x0c,0x14,0x27,0x0c,0x19,0x1e,0x1e,0x01,0x01,0x2c,0x1e,
+  0x12,0x13,0x0b,0x13,0x13,0x13,0x14,0x1f,0x13,0x0b,0x1e,0x1e,0x2f,
+  0x01,0x3c,0x1e,0x09,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x13,0x23,
+  0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x3c,0x1e,0x16,0x0c,0x22,0x1e,
+  0x32,0x1e,0x1e,0x1e,0x1e,0x22,0x14,0x23,0x1e,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x01,0x1e,0x1e,0x19,0x1f,0x1e,0x1e,0x1e,0x31,0x1f,0x05,
+  0x0c,0x1f,0x0b,0x1f,0x1e,0x1e,0x00,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x34,0x1e,0x1e,0x22,0x13,0x13,0x13,0x21,0x1e,0x2b,0x01,0x01,
+  0x1e,0x1e,0x13,0x1f,0x13,0x13,0x15,0x1e,0x1b,0x01,0x01,0x2f,0x1e,
+  0x1e,0x29,0x05,0x0c,0x1f,0x05,0x13,0x0c,0x16,0x1e,0x38,0x01,0x01,
+  0x20,0x40,0x1e,0x15,0x05,0x05,0x0c,0x05,0x05,0x0c,0x06,0x1e,0x1e,
+  0x2f,0x01,0x01,0x3b,0x1e,0x10,0x05,0x1f,0x19,0x22,0x26,0x29,0x19,
+  0x13,0x0b,0x02,0x1e,0x38,0x01,0x01,0x08,0x1e,0x16,0x05,0x1f,0x13,
+  0x14,0x13,0x13,0x05,0x0c,0x02,0x31,0x1e,0x17,0x01,0x01,0x38,0x1e,
+  0x21,0x14,0x1f,0x14,0x14,0x27,0x0c,0x22,0x27,0x27,0x1e,0x3e,0x01,
+  0x3a,0x1e,0x15,0x0d,0x23,0x23,0x24,0x25,0x0b,0x23,0x19,0x27,0x0d,
+  0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x33,0x1e,0x1f,0x0c,0x22,0x05,
+  0x21,0x1e,0x2e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x37,0x1e,0x2d,0x0d,0x19,0x1f,0x0b,0x29,0x14,
+  0x22,0x1f,0x05,0x1e,0x1e,0x01,0x01,0x1b,0x1e,0x09,0x22,0x22,0x13,
+  0x13,0x15,0x13,0x22,0x13,0x13,0x24,0x1e,0x37,0x01,0x01,0x38,0x1e,
+  0x21,0x13,0x1f,0x13,0x13,0x13,0x13,0x23,0x1e,0x1e,0x1c,0x01,0x01,
+  0x01,0x01,0x01,0x36,0x1e,0x31,0x15,0x1e,0x1e,0x30,0x1b,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x20,0x1e,
+  0x09,0x15,0x21,0x1e,0x38,0x1d,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,0x01,0x38,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x2b,0x01,0x01,0x01,0x3b,0x1e,0x1e,0x1e,0x31,
+  0x25,0x28,0x10,0x1e,0x1e,0x07,0x2b,0x01,0x01,0x01,0x36,0x1e,0x1e,
+  0x1e,0x2d,0x0a,0x16,0x10,0x1e,0x1e,0x1e,0x2f,0x01,0x01,0x01,0x01,
+  0x1e,0x1e,0x27,0x0c,0x19,0x12,0x13,0x05,0x22,0x14,0x14,0x16,0x1e,
+  0x37,0x01,0x01,0x20,0x07,0x1e,0x19,0x0c,0x1f,0x0b,0x14,0x22,0x1f,
+  0x14,0x23,0x0a,0x1e,0x3b,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x28,0x2a,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x04,0x01,0x36,0x1e,0x18,0x29,
+  0x06,0x29,0x1a,0x23,0x27,0x19,0x0b,0x12,0x15,0x1e,0x1c,0x01,0x01,
+  0x01,0x01,0x01,0x11,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x38,0x1e,0x1e,0x16,0x25,0x18,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x01,0x01,0x01,0x38,0x1e,0x1e,0x31,0x15,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x2f,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x18,0x25,
+  0x28,0x03,0x1e,0x1e,0x1e,0x1c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x1d,0x1e,0x1e,0x1e,0x30,0x01,0x01,0x01,0x00,0x3d,0x32,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x38,
+  0x2b,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x3b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,
+  0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x36,0x38,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x2f,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x0b,
+  0x05,0x0c,0x1f,0x0b,0x1f,0x0b,0x03,0x1e,0x07,0x34,0x01,0x01,0x01,
+  0x30,0x1e,0x1e,0x31,0x0a,0x31,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x33,
+  0x35,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x33,0x38,0x1e,0x1e,
+  0x1e,0x3e,0x04,0x34,0x01,0x01,0x38,0x1e,0x1e,0x05,0x05,0x27,0x1a,
+  0x0d,0x26,0x0d,0x25,0x1e,0x1e,0x3c,0x01,0x01,0x01,0x01,0x01,0x0e,
+  0x11,0x33,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x1e,0x1e,0x1e,0x1e,0x38,0x2f,
+  0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1d,
+  0x1c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x1b,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x1b,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x17,0x08,0x1b,0x2b,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x0e,0x2e,0x08,0x0e,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x18,0x16,0x25,0x25,
+  0x21,0x1e,0x1e,0x3e,0x2b,0x01,0x01,0x01,0x01,0x01,0x30,0x32,0x1e,
+  0x1e,0x1e,0x3e,0x3e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x1b,0x3c,0x34,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x2b,0x38,0x1e,0x1e,0x18,0x2a,0x2a,0x16,0x21,0x1e,0x1e,
+  0x33,0x3c,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x3c,0x2e,0x0e,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x2f,
+  0x1b,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x20,0x08,0x2e,0x17,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x00,0x3d,0x3e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x36,0x3b,0x36,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,
+  0x11,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x20,0x3c,0x08,0x08,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x08,
+  0x08,0x08,0x36,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x34,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x20,0x0e,0x20,0x0e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x36,0x1b,0x0f,0x17,0x01,0x01,0x01,0x01,0x01,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,
+  0x0f,0x11,0x1d,0x04,0x2e,0x34,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x3d,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x2c,0x1e,0x1e,
+  0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x09,0x13,0x13,0x13,
+  0x13,0x13,0x13,0x13,0x13,0x13,0x3f,0x18,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1b,0x32,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x3e,0x2b,0x2b,0x32,0x1e,0x18,0x1e,0x1e,0x20,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x04,0x1e,0x09,0x10,0x09,0x10,0x1e,0x32,
+  0x00,0x01,0x01,0x01,0x01,0x01,0x38,0x1e,0x1e,0x21,0x15,0x06,0x10,
+  0x1e,0x1e,0x07,0x2b,0x01,0x09,0x13,0x13,0x13,0x13,0x13,0x13,0x13,
+  0x13,0x13,0x3f,0x18,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x1b,0x1e,0x1e,0x2a,0x19,0x0b,0x1a,0x2a,0x1e,0x1e,0x38,
+  0x1e,0x03,0x13,0x25,0x1e,0x08,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x07,0x1e,0x22,0x13,0x0c,0x13,0x03,0x1e,0x17,0x01,0x01,0x01,
+  0x01,0x1b,0x1e,0x1e,0x14,0x13,0x13,0x13,0x13,0x13,0x25,0x1e,0x38,
+  0x01,0x09,0x13,0x16,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x24,0x0c,0x1e,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,
+  0x27,0x13,0x13,0x13,0x13,0x13,0x28,0x1e,0x1e,0x1e,0x13,0x13,0x1e,
+  0x1e,0x20,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x11,0x1e,0x02,
+  0x13,0x0b,0x1f,0x10,0x1e,0x0e,0x01,0x01,0x01,0x01,0x2c,0x1e,0x0d,
+  0x02,0x14,0x19,0x05,0x23,0x14,0x13,0x1e,0x1e,0x01,0x09,0x13,0x16,
+  0x2c,0x00,0x00,0x00,0x00,0x11,0x24,0x0c,0x1e,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x34,0x1e,0x18,0x28,0x12,0x31,0x06,
+  0x13,0x13,0x02,0x1e,0x1e,0x0a,0x13,0x0a,0x1e,0x38,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x32,0x1e,0x0b,0x13,0x0c,0x13,0x31,
+  0x1e,0x36,0x01,0x01,0x01,0x01,0x2c,0x1e,0x29,0x13,0x0b,0x18,0x2a,
+  0x19,0x12,0x14,0x1e,0x1e,0x01,0x09,0x13,0x16,0x2c,0x00,0x00,0x00,
+  0x00,0x11,0x24,0x0c,0x1e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x04,0x1e,0x1e,0x1e,0x27,0x13,0x13,0x13,0x18,0x1e,
+  0x1e,0x13,0x13,0x1e,0x1e,0x3d,0x3d,0x2e,0x2b,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x30,0x1e,0x15,0x27,0x12,0x19,0x09,0x1e,0x20,0x01,0x01,
+  0x01,0x01,0x2c,0x1e,0x12,0x05,0x05,0x09,0x31,0x0b,0x0c,0x1f,0x1e,
+  0x1e,0x01,0x09,0x13,0x16,0x2c,0x00,0x00,0x00,0x00,0x11,0x24,0x0c,
+  0x1e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x1e,
+  0x1e,0x18,0x25,0x06,0x0d,0x13,0x13,0x15,0x1e,0x19,0x13,0x03,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x2b,0x01,0x01,0x01,0x01,0x0e,0x11,0x1e,
+  0x09,0x16,0x03,0x2a,0x1e,0x1d,0x01,0x01,0x01,0x01,0x01,0x2c,0x1e,
+  0x0d,0x0c,0x24,0x06,0x19,0x02,0x19,0x1f,0x1e,0x1e,0x01,0x09,0x13,
+  0x16,0x39,0x00,0x00,0x00,0x00,0x11,0x24,0x1f,0x1e,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x35,0x1e,0x2d,0x13,0x13,0x0a,
+  0x06,0x13,0x13,0x0b,0x1e,0x13,0x1f,0x1e,0x1e,0x12,0x0d,0x0d,0x28,
+  0x1e,0x2c,0x01,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x05,0x05,0x0c,0x13,
+  0x03,0x1e,0x17,0x01,0x01,0x01,0x01,0x3a,0x1e,0x2d,0x0b,0x14,0x23,
+  0x22,0x14,0x1f,0x28,0x1e,0x38,0x01,0x09,0x19,0x2a,0x39,0x00,0x00,
+  0x00,0x00,0x11,0x24,0x22,0x1e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x38,0x1e,0x0d,0x13,0x13,0x13,0x13,0x13,0x1e,
+  0x22,0x13,0x1e,0x1e,0x16,0x13,0x13,0x13,0x1f,0x1e,0x40,0x01,0x01,
+  0x01,0x38,0x1e,0x2d,0x1f,0x05,0x05,0x13,0x0c,0x1e,0x1e,0x40,0x39,
+  0x17,0x01,0x01,0x01,0x38,0x1e,0x21,0x28,0x24,0x0d,0x06,0x31,0x1e,
+  0x1e,0x17,0x01,0x09,0x05,0x03,0x39,0x00,0x00,0x00,0x00,0x38,0x24,
+  0x1f,0x1e,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x2e,0x1e,0x1e,0x09,0x03,0x16,0x31,0x1e,0x1e,0x13,0x23,0x1e,0x1e,
+  0x14,0x13,0x1f,0x13,0x23,0x1e,0x38,0x01,0x01,0x35,0x1e,0x09,0x19,
+  0x0c,0x1f,0x23,0x13,0x24,0x1e,0x1e,0x1e,0x1e,0x1e,0x17,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x17,0x01,0x01,0x18,
+  0x12,0x2a,0x11,0x00,0x00,0x00,0x00,0x38,0x06,0x05,0x1e,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2e,0x38,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x0c,0x13,0x1e,0x1e,0x2a,0x13,0x21,0x2a,0x13,
+  0x29,0x1e,0x1e,0x1c,0x01,0x08,0x1e,0x25,0x1f,0x13,0x13,0x13,0x1e,
+  0x0a,0x22,0x05,0x19,0x26,0x1e,0x3a,0x01,0x01,0x01,0x01,0x3c,0x1c,
+  0x2c,0x2c,0x1c,0x3c,0x01,0x01,0x01,0x01,0x09,0x0b,0x2a,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x06,0x1f,0x1e,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x17,0x38,0x1e,0x2a,
+  0x13,0x26,0x1e,0x1e,0x23,0x0c,0x0b,0x05,0x22,0x13,0x23,0x1e,0x38,
+  0x01,0x08,0x1e,0x16,0x0b,0x13,0x1f,0x1f,0x1e,0x14,0x0c,0x0c,0x13,
+  0x15,0x1e,0x1b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x18,0x19,0x13,0x0b,0x05,0x0c,0x0b,0x14,0x05,
+  0x1f,0x05,0x18,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x0f,0x1e,0x1e,0x0c,0x13,0x1e,0x1e,0x1e,
+  0x0b,0x0c,0x23,0x13,0x13,0x13,0x22,0x1e,0x33,0x01,0x2b,0x1e,0x21,
+  0x0c,0x13,0x1f,0x13,0x0b,0x23,0x13,0x05,0x05,0x1e,0x1e,0x36,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x09,0x22,0x14,0x14,0x13,0x1a,0x19,0x13,0x02,0x05,0x05,0x18,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x11,0x1e,0x19,0x13,0x25,0x1e,0x38,0x1e,0x1e,0x1e,0x1e,0x0b,
+  0x23,0x13,0x1e,0x1e,0x11,0x01,0x01,0x38,0x1e,0x21,0x13,0x05,0x13,
+  0x13,0x1f,0x13,0x14,0x21,0x1e,0x38,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x1e,0x1e,0x1e,0x1e,
+  0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0f,0x1e,0x16,
+  0x28,0x1e,0x40,0x2b,0x04,0x3e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,
+  0x0e,0x01,0x01,0x2b,0x38,0x1e,0x1e,0x21,0x0a,0x16,0x25,0x03,0x1e,
+  0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x20,0x07,0x1e,0x1e,0x1e,0x2b,0x01,
+  0x01,0x01,0x01,0x1c,0x1d,0x1e,0x1e,0x38,0x2b,0x01,0x01,0x01,0x01,
+  0x2b,0x38,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x38,0x2b,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x20,0x3c,0x2e,0x2b,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x2b,
+  0x3b,0x3c,0x08,0x17,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,
+  0x00,0x00,0x00,0x02,0xff,0xff,0xff,0x00,0xb3,0xb3,0xb3,0x80,0x3b,
+  0x3b,0x3b,0x80,0x00,0x00,0x00,0x52,0xe4,0xe4,0xe4,0x80,0x7b,0x7b,
+  0x7b,0x80,0x00,0x00,0x00,0x72,0x00,0x00,0x00,0x2a,0x14,0x14,0x14,
+  0x80,0x5b,0x5b,0x5b,0x80,0xcc,0xcc,0xcc,0x80,0xf4,0xf4,0xf4,0x80,
+  0x9b,0x9b,0x9b,0x80,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x3e,0x23,
+  0x23,0x23,0x80,0x00,0x00,0x00,0x5e,0x8c,0x8c,0x8c,0x80,0xfe,0xfe,
+  0xfe,0x80,0xdc,0xdc,0xdc,0x80,0x6b,0x6b,0x6b,0x80,0x4b,0x4b,0x4b,
+  0x80,0x00,0x00,0x00,0x1e,0x0b,0x0b,0x0b,0x80,0xbc,0xbc,0xbc,0x80,
+  0xa4,0xa4,0xa4,0x80,0x00,0x00,0x00,0x36,0x00,0x00,0x00,0x46,0x00,
+  0x00,0x00,0x66,0x00,0x00,0x00,0x80,0xec,0xec,0xec,0x80,0x00,0x00,
+  0x00,0x0a,0x2b,0x2b,0x2b,0x80,0xd4,0xd4,0xd4,0x80,0xc4,0xc4,0xc4,
+  0x80,0x84,0x84,0x84,0x80,0x54,0x54,0x54,0x80,0x74,0x74,0x74,0x80,
+  0xac,0xac,0xac,0x80,0x64,0x64,0x64,0x80,0x94,0x94,0x94,0x80,0x44,
+  0x44,0x44,0x80,0x00,0x00,0x00,0x16,0x00,0x00,0x00,0x56,0x1b,0x1b,
+  0x1b,0x80,0x00,0x00,0x00,0x32,0x00,0x00,0x00,0x22,0x00,0x00,0x00,
+  0x4a,0x33,0x33,0x33,0x80,0x00,0x00,0x00,0x6a,0x00,0x00,0x00,0x7a,
+  0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0d,0x00,0x00,0x00,0x1a,0x00,
+  0x00,0x00,0x42,0x00,0x00,0x00,0x62,0x00,0x00,0x00,0x5a,0x00,0x00,
+  0x00,0x3a,0x00,0x00,0x00,0x2e,0x00,0x00,0x00,0x26,0x00,0x00,0x00,
+  0x4e,0x00,0x00,0x00,0x6e,0xe7,0xe8,0xe8,0x80,0x00,0x00,0x00,0x76,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x83,0x00,0x00,
+  0x00,0x01,0x00,0x01,0x00,0x11,0x00,0x11,0x00,0x13,0x00,0x01,0x00,
+  0x10,0x00,0x11,0x00,0x24,0x00,0x01,0x00,0x10,0x00,0x12,0x00,0x35,
+  0x00,0x01,0x00,0x0f,0x00,0x11,0x00,0x45,0x00,0x01,0x00,0x0e,0x00,
+  0x11,0x00,0x54,0x00,0x01,0x00,0x0e,0x00,0x11,0x00,0x63,0x00,0x01,
+  0x00,0x11,0x00,0x12,0x00,0x75,0x00,0x01,0x00,0x0f,0x00,0x11,0x00,
+  0x85,0x00,0x01,0x00,0x0a,0x00,0x11,0x00,0x90,0x00,0x01,0x00,0x0f,
+  0x00,0x12,0x00,0xa0,0x00,0x01,0x00,0x10,0x00,0x11,0x00,0xb1,0x00,
+  0x01,0x00,0x0d,0x00,0x11,0x00,0xbf,0x00,0x01,0x00,0x13,0x00,0x11,
+  0x00,0xd3,0x00,0x01,0x00,0x11,0x00,0x11,0x00,0xe5,0x00,0x01,0x00,
+  0x10,0x00,0x12,0x00,0x01,0x00,0x1a,0x00,0x10,0x00,0x11,0x00,0x12,
+  0x00,0x1a,0x00,0x10,0x00,0x15,0x00,0x23,0x00,0x1a,0x00,0x10,0x00,
+  0x11,0x00,0x34,0x00,0x1a,0x00,0x10,0x00,0x12,0x00,0x45,0x00,0x1a,
+  0x00,0x0f,0x00,0x11,0x00,0x55,0x00,0x1a,0x00,0x0f,0x00,0x12,0x00,
+  0x65,0x00,0x1a,0x00,0x11,0x00,0x11,0x00,0x77,0x00,0x1a,0x00,0x16,
+  0x00,0x11,0x00,0x8e,0x00,0x1a,0x00,0x11,0x00,0x12,0x00,0xa0,0x00,
+  0x1a,0x00,0x10,0x00,0x11,0x00,0xb1,0x00,0x1a,0x00,0x0f,0x00,0x11,
+  0x00,0xc1,0x00,0x1a,0x00,0x10,0x00,0x0f,0x00,0xd2,0x00,0x1a,0x00,
+  0x0f,0x00,0x12,0x00,0xe2,0x00,0x1a,0x00,0x0f,0x00,0x0f,0x00,0x01,
+  0x00,0x33,0x00,0x10,0x00,0x12,0x00,0x12,0x00,0x33,0x00,0x0f,0x00,
+  0x0f,0x00,0x22,0x00,0x33,0x00,0x0c,0x00,0x11,0x00,0x2f,0x00,0x33,
+  0x00,0x0f,0x00,0x12,0x00,0x3f,0x00,0x33,0x00,0x0e,0x00,0x11,0x00,
+  0x4e,0x00,0x33,0x00,0x09,0x00,0x11,0x00,0x58,0x00,0x33,0x00,0x0b,
+  0x00,0x15,0x00,0x64,0x00,0x33,0x00,0x10,0x00,0x11,0x00,0x75,0x00,
+  0x33,0x00,0x09,0x00,0x11,0x00,0x7f,0x00,0x33,0x00,0x14,0x00,0x0e,
+  0x00,0x94,0x00,0x33,0x00,0x0e,0x00,0x0e,0x00,0xa3,0x00,0x33,0x00,
+  0x10,0x00,0x0f,0x00,0xb4,0x00,0x33,0x00,0x0f,0x00,0x12,0x00,0xc4,
+  0x00,0x33,0x00,0x0f,0x00,0x12,0x00,0xd4,0x00,0x33,0x00,0x0d,0x00,
+  0x0e,0x00,0xe2,0x00,0x33,0x00,0x0f,0x00,0x0f,0x00,0xf2,0x00,0x33,
+  0x00,0x0d,0x00,0x10,0x00,0x01,0x00,0x4c,0x00,0x0f,0x00,0x0f,0x00,
+  0x11,0x00,0x4c,0x00,0x0f,0x00,0x0e,0x00,0x21,0x00,0x4c,0x00,0x16,
+  0x00,0x0e,0x00,0x38,0x00,0x4c,0x00,0x0f,0x00,0x0e,0x00,0x48,0x00,
+  0x4c,0x00,0x10,0x00,0x12,0x00,0x59,0x00,0x4c,0x00,0x0f,0x00,0x0e,
+  0x00,0x69,0x00,0x4c,0x00,0x10,0x00,0x11,0x00,0x7a,0x00,0x4c,0x00,
+  0x0c,0x00,0x10,0x00,0x87,0x00,0x4c,0x00,0x0f,0x00,0x10,0x00,0x97,
+  0x00,0x4c,0x00,0x10,0x00,0x11,0x00,0xa8,0x00,0x4c,0x00,0x10,0x00,
+  0x10,0x00,0xb9,0x00,0x4c,0x00,0x10,0x00,0x11,0x00,0xca,0x00,0x4c,
+  0x00,0x10,0x00,0x12,0x00,0xdb,0x00,0x4c,0x00,0x0f,0x00,0x10,0x00,
+  0xeb,0x00,0x4c,0x00,0x0f,0x00,0x11,0x00,0x01,0x00,0x65,0x00,0x0f,
+  0x00,0x12,0x00,0x11,0x00,0x65,0x00,0x09,0x00,0x09,0x00,0x1b,0x00,
+  0x65,0x00,0x09,0x00,0x0c,0x00,0x25,0x00,0x65,0x00,0x0b,0x00,0x09,
+  0x00,0x31,0x00,0x65,0x00,0x0a,0x00,0x12,0x00,0x3c,0x00,0x65,0x00,
+  0x0e,0x00,0x12,0x00,0x4b,0x00,0x65,0x00,0x09,0x00,0x0e,0x00,0x55,
+  0x00,0x65,0x00,0x07,0x00,0x0a,0x00,0x5d,0x00,0x65,0x00,0x0f,0x00,
+  0x0f,0x00,0x6d,0x00,0x65,0x00,0x0e,0x00,0x13,0x00,0x7c,0x00,0x65,
+  0x00,0x0e,0x00,0x0a,0x00,0x8b,0x00,0x65,0x00,0x11,0x00,0x11,0x00,
+  0x9d,0x00,0x65,0x00,0x0b,0x00,0x13,0x00,0xa9,0x00,0x65,0x00,0x0c,
+  0x00,0x13,0x00,0xb6,0x00,0x65,0x00,0x0c,0x00,0x0c,0x00,0xc3,0x00,
+  0x65,0x00,0x14,0x00,0x14,0x00,0xd8,0x00,0x65,0x00,0x09,0x00,0x09,
+  0x00,0xe2,0x00,0x65,0x00,0x0a,0x00,0x11,0x00,0xed,0x00,0x65,0x00,
+  0x0f,0x00,0x12,0x00,0x01,0x00,0x7e,0x00,0x0f,0x00,0x11,0x00,0x11,
+  0x00,0x7e,0x00,0x11,0x00,0x11,0x00,0x23,0x00,0x7e,0x00,0x10,0x00,
+  0x10,0x00,0x34,0x00,0x7e,0x00,0x08,0x00,0x14,0x00,0x3d,0x00,0x7e,
+  0x00,0x0f,0x00,0x15,0x00,0x4d,0x00,0x7e,0x00,0x0a,0x00,0x08,0x00,
+  0x58,0x00,0x7e,0x00,0x10,0x00,0x11,0x00,0x69,0x00,0x7e,0x00,0x0d,
+  0x00,0x0c,0x00,0x77,0x00,0x7e,0x00,0x0e,0x00,0x0b,0x00,0x86,0x00,
+  0x7e,0x00,0x0f,0x00,0x0a,0x00,0x96,0x00,0x7e,0x00,0x0c,0x00,0x15,
+  0x00,0xa3,0x00,0x7e,0x00,0x0e,0x00,0x07,0x00,0xb2,0x00,0x7e,0x00,
+  0x12,0x00,0x11,0x00,0xc5,0x00,0x7e,0x00,0x0f,0x00,0x13,0x00,0xd5,
+  0x00,0x7e,0x00,0x13,0x00,0x12,0x00,0xe9,0x00,0x7e,0x00,0x13,0x00,
+  0x12,0x00,0x01,0x00,0x97,0x00,0x0e,0x00,0x14,0x00,0x10,0x00,0x97,
+  0x00,0x0f,0x00,0x0c,0x00,0x20,0x00,0x97,0x00,0x0f,0x00,0x0f,0x00,
+  0x30,0x00,0x97,0x00,0x0f,0x00,0x0f,0x00,0x40,0x00,0x97,0x00,0x0f,
+  0x00,0x11,0x00,0x50,0x00,0x97,0x00,0x11,0x00,0x14,0x00,0x62,0x00,
+  0x97,0x00,0x0f,0x00,0x15,0x00,0x72,0x00,0x97,0x00,0x10,0x00,0x15,
+  0x00,0x83,0x00,0x97,0x00,0x0f,0x00,0x13,0x00,0x93,0x00,0x97,0x00,
+  0x0f,0x00,0x13,0x00,0xa3,0x00,0x97,0x00,0x11,0x00,0x14,0x00,0xb5,
+  0x00,0x97,0x00,0x0f,0x00,0x13,0x00,0xc5,0x00,0x97,0x00,0x0f,0x00,
+  0x14,0x00,0xd5,0x00,0x97,0x00,0x0f,0x00,0x14,0x00,0xe5,0x00,0x97,
+  0x00,0x0f,0x00,0x13,0x00,0xf5,0x00,0x97,0x00,0x0a,0x00,0x12,0x00,
+  0x01,0x00,0xb0,0x00,0x0a,0x00,0x13,0x00,0x0c,0x00,0xb0,0x00,0x0a,
+  0x00,0x12,0x00,0x17,0x00,0xb0,0x00,0x0f,0x00,0x13,0x00,0x27,0x00,
+  0xb0,0x00,0x0f,0x00,0x13,0x00,0x37,0x00,0xb0,0x00,0x10,0x00,0x15,
+  0x00,0x48,0x00,0xb0,0x00,0x0f,0x00,0x14,0x00,0x58,0x00,0xb0,0x00,
+  0x0f,0x00,0x13,0x00,0x68,0x00,0xb0,0x00,0x10,0x00,0x15,0x00,0x79,
+  0x00,0xb0,0x00,0x0f,0x00,0x12,0x00,0x89,0x00,0xb0,0x00,0x15,0x00,
+  0x0f,0x00,0x9f,0x00,0xb0,0x00,0x0f,0x00,0x13,0x00,0xaf,0x00,0xb0,
+  0x00,0x10,0x00,0x13,0x00,0xc0,0x00,0xb0,0x00,0x0e,0x00,0x13,0x00,
+  0xcf,0x00,0xb0,0x00,0x15,0x00,0x12,0x00,0xe5,0x00,0xb0,0x00,0x15,
+  0x00,0x12,0x00,0x01,0x00,0xc9,0x00,0x16,0x00,0x12,0x00,0x18,0x00,
+  0xc9,0x00,0x0f,0x00,0x12,0x00,0x28,0x00,0xc9,0x00,0x0c,0x00,0x0c,
+  0x00,0x35,0x00,0xc9,0x00,0x0c,0x00,0x0f,0x00,0x00
+  };
+
diff --git a/Code/Gfx/XBox/p_NxGeom.cpp b/Code/Gfx/XBox/p_NxGeom.cpp
new file mode 100644
index 0000000..0b27dd7
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxGeom.cpp
@@ -0,0 +1,1496 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxGeom.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  3/4/2002
+//****************************************************************************
+
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxGeom::CXboxGeom() : mp_init_mesh_list( NULL ), m_mesh_array( NULL ), m_visible( 0xDEADBEEF )
+{
+	mp_instance = NULL;
+//	mp_sector	= NULL;
+	m_scale.Set( 1.0f, 1.0f, 1.0f );
+	m_active	= true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxGeom::~CXboxGeom( void )
+{
+	DestroyMeshArray();
+
+	if( mp_instance != NULL )
+	{
+		delete mp_instance;
+		mp_instance = NULL;
+	}
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Mesh List Functions
+//
+// The mesh list is only for initialization of the CGeom.  As we find all the
+// meshes for the CGeoms, we add them to the temporary lists.  When we are
+// done, the list is copied into a permanent array.
+//
+// All the list functions use the TopDownHeap for memory allocation.
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::InitMeshList( void )
+{
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+	mp_init_mesh_list = new Lst::Head< NxXbox::sMesh >;
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::AddMesh( NxXbox::sMesh *mesh )
+{
+	Dbg_Assert( mp_init_mesh_list );
+
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+	Lst::Node< NxXbox::sMesh > *node = new Lst::Node< NxXbox::sMesh > (mesh);
+	mp_init_mesh_list->AddToTail( node );
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::CreateMeshArray( void )
+{
+	if( !m_mesh_array )
+	{
+		Dbg_Assert( mp_init_mesh_list );
+
+		m_num_mesh = mp_init_mesh_list->CountItems();
+		if (m_num_mesh)
+		{
+			Lst::Node< NxXbox::sMesh > *mesh, *next;
+
+			m_mesh_array = new NxXbox::sMesh*[m_num_mesh];
+			int k = 0;
+			for( mesh = mp_init_mesh_list->GetNext(); mesh; mesh = next )
+			{
+				next			= mesh->GetNext();
+				m_mesh_array[k]	= mesh->GetData();
+				delete mesh;
+				k++;
+			}
+		}
+
+		// Delete temporary list.
+		delete mp_init_mesh_list;
+		mp_init_mesh_list = NULL;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::RegisterMeshArray( bool just_count )
+{
+	// Tells the engine to count or add the meshes for this sector to the rendering list for supplied scene id.
+	if( just_count )
+	{
+		mp_scene->GetEngineScene()->CountMeshes( m_num_mesh, m_mesh_array );
+	}
+	else
+	{
+		mp_scene->GetEngineScene()->AddMeshes( m_num_mesh, m_mesh_array );
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::DestroyMeshArray( void )
+{
+	if( m_mesh_array )
+	{
+		// Tells the engine to remove the meshes for this sector from the rendering list for supplied scene id.
+		if( mp_scene && mp_scene->GetEngineScene())
+		{
+			mp_scene->GetEngineScene()->RemoveMeshes( m_num_mesh, m_mesh_array );
+		}
+
+		// Now actually go through and delete each mesh.
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+			delete p_mesh;
+		}
+
+		delete [] m_mesh_array;
+		m_mesh_array = NULL;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Generates an entirely new engine scene.                        */
+/* Used when instance-cloning CGeoms.                             */
+/*                                                                */
+/******************************************************************/
+NxXbox::sScene* CXboxGeom::GenerateScene( void )
+{
+	NxXbox::sScene *p_scene = new NxXbox::sScene();
+
+	p_scene->CountMeshes( m_num_mesh, m_mesh_array );
+	p_scene->CreateMeshArrays();
+	p_scene->AddMeshes( m_num_mesh, m_mesh_array );
+	p_scene->SortMeshes();
+
+	// Set the bounding box and bounding sphere.
+	p_scene->m_bbox = m_bbox;
+	p_scene->FigureBoundingVolumes();
+	
+	return p_scene;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_load_geom_data( CMesh *pMesh, CModel *pModel, bool color_per_material )
+{
+	// The skeleton must exist by this point (unless it's a hacked up car).
+	int			numBones = pModel->GetNumBones();
+	Mth::Matrix	temp;
+	
+	temp.Identity();
+
+	CXboxMesh *p_xbox_mesh = static_cast( pMesh );
+	
+	// Store a pointer to the CXboxMesh, used for obtaining CAS poly removal data.
+	mp_mesh = p_xbox_mesh;
+
+	CXboxModel *p_xbox_model = static_cast( pModel );
+
+	NxXbox::CInstance *p_instance = new NxXbox::CInstance( p_xbox_mesh->GetScene()->GetEngineScene(), temp, numBones, pModel->GetBoneTransforms());
+	
+	SetInstance( p_instance );
+	p_instance->SetModel( pModel );
+
+    return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_finalize( void )
+{
+	// Scan through and remove degenerate indices...
+	if( mp_instance )
+	{
+		NxXbox::sScene *p_scene = mp_instance->GetScene();
+		if( p_scene )
+		{
+			for( int m = 0; m < p_scene->m_num_mesh_entries; ++m )
+			{
+				NxXbox::sMesh *p_mesh = p_scene->m_meshes[m];
+				p_mesh->Crunch();
+			}
+		}
+	}	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_active( bool active )
+{
+	if( mp_instance )
+	{
+		mp_instance->SetActive( active );
+	}	
+	else
+	{
+		m_active = active;
+		if( m_mesh_array )
+		{
+			for( uint m = 0; m < m_num_mesh; ++m )
+			{
+				NxXbox::sMesh *p_mesh = m_mesh_array[m];
+				p_mesh->SetActive( active );
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_is_active( void ) const
+{
+	if( mp_instance )
+	{
+		return mp_instance->GetActive();
+	}	
+	return m_active;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector &CXboxGeom::plat_get_world_position( void ) const
+{
+	static Mth::Vector pos;
+	
+	if( mp_instance )
+	{
+		pos = mp_instance->GetTransform()->GetPos();
+	}
+	else
+	{
+		pos.Set( 0.0f, 0.0f, 0.0f, 1.0f );
+
+		NxXbox::sMesh *p_mesh = m_mesh_array[0];
+		if( p_mesh )
+		{
+			p_mesh->GetPosition( &pos );
+		}
+	}
+	Dbg_Assert( pos[W] == 1.0f );
+	return pos;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_world_position( const Mth::Vector& pos )
+{
+	// Ensure w component is correct.
+	Mth::Vector proper_pos( pos[X], pos[Y], pos[Z], 1.0f );
+
+	if( mp_instance )
+	{
+		mp_instance->GetTransform()->SetPos( proper_pos );
+	}
+	else
+	{
+		// Go through and adjust the individual meshes.
+		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[i];
+			p_mesh->SetPosition( proper_pos );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::CBBox & CXboxGeom::plat_get_bounding_box( void ) const
+{
+	return m_bbox;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	if( mp_instance )
+	{
+		NxXbox::sScene *p_scene = mp_instance->GetScene();
+		if( p_scene )
+		{
+			p_scene->m_sphere_center.x	= boundingSphere[X];
+			p_scene->m_sphere_center.y	= boundingSphere[Y];
+			p_scene->m_sphere_center.z	= boundingSphere[Z];
+			p_scene->m_sphere_radius	= boundingSphere[W];
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector CXboxGeom::plat_get_bounding_sphere( void ) const
+{
+	if( mp_instance )
+	{
+		NxXbox::sScene *p_scene = mp_instance->GetScene();
+		if( p_scene )
+		{
+			return Mth::Vector( p_scene->m_sphere_center.x, p_scene->m_sphere_center.y,  p_scene->m_sphere_center.z, p_scene->m_sphere_radius );
+		}
+	}
+	
+	return Mth::Vector( 0.0f, 0.0f, 0.0f, 10000.0f );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_orientation( const Mth::Matrix& orient )
+{
+	if( mp_instance )
+	{
+		Mth::Matrix *p_matrix = mp_instance->GetTransform();
+		Mth::Matrix new_orientation = *p_matrix;
+		new_orientation[X] = orient[X];
+		new_orientation[Y] = orient[Y];
+		new_orientation[Z] = orient[Z];
+		mp_instance->SetTransform( new_orientation );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Matrix &CXboxGeom::plat_get_orientation( void ) const
+{
+	static Mth::Matrix orientation;
+	
+	if( mp_instance )
+	{
+		orientation = *( mp_instance->GetTransform());
+		orientation[W] = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
+	}
+	else
+	{
+		orientation.Identity();
+	}
+
+	return orientation;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_rotate_y( Mth::ERot90 rot )
+{
+	if( rot != Mth::ROT_0 )
+	{
+		if( mp_instance )
+		{
+			Mth::Matrix *p_matrix = mp_instance->GetTransform();
+
+			// Zero out translation component.
+			Mth::Matrix	instance_transform = *p_matrix;
+			instance_transform[W] = Mth::Vector(0.0f, 0.0f, 0.0f, 1.0f);
+
+			// Build rotation matrix.
+			Mth::Matrix	rot_mat;
+			float		rad = (float)((int)rot) * ( Mth::PI * 0.5f );
+			Mth::CreateRotateYMatrix( rot_mat, rad );
+
+			// Rotate matrix.
+			rot_mat	= instance_transform * rot_mat;
+
+			// Replace translation component.
+			rot_mat[W] = p_matrix->GetPos();
+
+			mp_instance->SetTransform( rot_mat );
+		}
+		else
+		{
+			// Go through and adjust the individual meshes.
+			for( uint32 i = 0; i < m_num_mesh; ++i )
+			{
+				NxXbox::sMesh *p_mesh = m_mesh_array[i];
+				p_mesh->SetYRotation( rot );
+			}
+		}
+	}
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_render(Mth::Matrix* pRootMatrix, Mth::Matrix* pBoneMatrices, int numBones)
+{
+	if( mp_instance )
+	{
+		mp_instance->SetTransform( *pRootMatrix );
+	}
+    return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_bone_matrix_data( Mth::Matrix* pBoneMatrices, int numBones )
+{
+	if( mp_instance )
+	{
+		Mth::Matrix* p_bone_matrices	= mp_instance->GetBoneTransforms();
+		CopyMemory( p_bone_matrices, pBoneMatrices, numBones * sizeof( Mth::Matrix ));
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_hide_polys( uint32 mask )
+{
+	if( mp_mesh )
+	{
+		// Obtain a pointer to the Xbox scene.
+		NxXbox::sScene *p_engine_scene = GetInstance()->GetScene();
+
+		// Request the scene to hide the relevant polys.
+		p_engine_scene->HidePolys( mask, mp_mesh->GetCASData(), mp_mesh->GetNumCASData());
+	}
+	
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_visibility( uint32 mask )
+{
+	// Set values
+	m_visible = mask;
+
+	if( m_mesh_array )
+	{
+		for( uint m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+			p_mesh->SetVisibility((uint8)mask );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 CXboxGeom::plat_get_visibility( void ) const
+{
+	return m_visible | 0xFFFFFF00;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_color( Image::RGBA rgba )
+{
+	// If we are setting the color (which is used as a multiplier) to (0x80,0x80,0x80), it is effectively
+	// just turning the coloring off...
+	// Note that some indoor stuff is set to 0x808081, since on Ps2 it needs to be set to something
+	// other than 0x808080 in order to ensure that it doesn't inherit the color from the parent node.
+	// This is not an issue for Xbox, so it treats the two values the same.
+	if((( rgba.r == 0x80 ) || ( rgba.r == 0x81 )) && ( rgba.g == 0x80 ) && ( rgba.b == 0x80 ))
+	{
+		plat_clear_color();
+		return;
+	}
+
+	// Oofa, nasty hack.
+	if( mp_instance )
+	{
+		// Grab the engine scene from the geom, and set all meshes to the color.
+		NxXbox::sScene *p_scene = mp_instance->GetScene();
+		for( int i = 0; i < p_scene->m_num_mesh_entries; ++i )
+		{
+			NxXbox::sMesh *p_mesh = p_scene->m_meshes[i];
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+			p_mesh->m_material_color_override[0] = (float)rgba.r / 255.0f;
+			p_mesh->m_material_color_override[1] = (float)rgba.g / 255.0f;
+			p_mesh->m_material_color_override[2] = (float)rgba.b / 255.0f;
+		}
+	}
+	else if( m_mesh_array != NULL )
+	{
+		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[i];
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+			p_mesh->m_material_color_override[0] = (float)rgba.r / 255.0f;
+			p_mesh->m_material_color_override[1] = (float)rgba.g / 255.0f;
+			p_mesh->m_material_color_override[2] = (float)rgba.b / 255.0f;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_clear_color( void )
+{
+	// Oofa, nasty hack.
+	if( mp_instance )
+	{
+		// Grab the engine scene from the geom, and clear all meshes of the flag.
+		NxXbox::sScene *p_scene = mp_instance->GetScene();
+		for( int i = 0; i < p_scene->m_num_mesh_entries; ++i )
+		{
+			NxXbox::sMesh *p_mesh = p_scene->m_meshes[i];
+			p_mesh->m_flags &= ~NxXbox::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+		}
+	}
+	else if( m_mesh_array != NULL )
+	{
+		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[i];
+			p_mesh->m_flags &= ~NxXbox::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_set_material_color( uint32 mat_name_checksum, int pass, Image::RGBA rgba )
+{
+	bool something_changed = false;
+
+	if( mp_instance && mp_instance->GetScene())
+	{
+		for( int m = 0; m < mp_instance->GetScene()->m_num_mesh_entries; ++m )
+		{
+			NxXbox::sMesh		*p_mesh	= mp_instance->GetScene()->m_meshes[m];
+			NxXbox::sMaterial	*p_mat	= p_mesh->mp_material;
+			if( p_mat )
+			{
+				bool	want_this_material	= false;
+				int		adjusted_pass		= pass;
+
+				// We are searching for materials with a matching name. However there is a caveat in that the
+				// conversion process sometimes creates new materials for geometry flagged as 'render as separate', or for
+				// geometry which is mapped with only certain passes of a multipass material (or both cases).
+				// In such a case, the new material name checksum will differ from the original material name checksum,
+				// with the following bits having significance (i.e. consider bitflags = new_namechecksum - old_namechecksum ):
+				//
+				// Bits 3->6	Pass flags indicating which passes of the original material this material uses
+				// Bits 0->2	Absolute number ('render as separate' flagged geometry) indicating which single pass of the material this material represents.
+				if( p_mat->m_name_checksum == mat_name_checksum )
+					want_this_material = true;
+				else if(( p_mat->m_name_checksum > mat_name_checksum ) && (( p_mat->m_name_checksum - mat_name_checksum ) <= 0x7F ))
+				{
+					uint32 checksum_diff		= p_mat->m_name_checksum - mat_name_checksum;
+					uint32 render_separate_pass	= checksum_diff & 0x07;
+					uint32 pass_flags			= checksum_diff >> 3;
+
+					if( render_separate_pass )
+					{
+						if( render_separate_pass == ( pass + 1 ))
+						{
+							want_this_material = true;
+
+							// Readjust the pass to zero, since this material was formed as a single pass of a multipass material.
+							adjusted_pass = 0;
+						}
+					}
+					else if( pass_flags )
+					{
+						// This material was created during scene conversion from another material with more passes.
+						if( pass_flags & ( 1 << pass ))
+						{
+							want_this_material = true;
+							for( int p = 0; p < pass; ++p )
+							{
+								if(( pass_flags & ( 1 << p )) == 0 )
+								{
+									// Readjust the pass down by 1, since this material was created as a subset of another material.
+									--adjusted_pass;
+								}
+							}
+						}
+					}
+				}
+
+				if( want_this_material )
+				{
+					if((uint32)adjusted_pass < p_mat->m_passes )
+					{
+						if( !( p_mat->m_flags[adjusted_pass] & MATFLAG_PASS_COLOR_LOCKED ))
+						{
+							p_mat->m_color[adjusted_pass][0] = (float)rgba.r / 255.0f;
+							p_mat->m_color[adjusted_pass][1] = (float)rgba.g / 255.0f;
+							p_mat->m_color[adjusted_pass][2] = (float)rgba.b / 255.0f;
+
+							something_changed = true;
+						}
+					}
+				}
+			}
+		}
+	}
+	return something_changed;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CXboxGeom::plat_get_color( void ) const
+{
+	Image::RGBA rgba( 0, 0, 0, 0 );
+
+	NxXbox::sMesh *p_mesh = NULL;
+
+	if( mp_instance )
+	{
+		// Grab the engine scene from the geom, and get a mesh color.
+		NxXbox::sScene *p_scene = mp_instance->GetScene();
+
+		if( p_scene->m_num_mesh_entries > 0 )
+			p_mesh = p_scene->m_meshes[0];
+	}
+	else if( m_mesh_array != NULL )
+	{
+		p_mesh = m_mesh_array[0];
+	}
+
+	if( p_mesh && ( p_mesh->m_flags & NxXbox::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE ))
+	{
+		rgba.r = (uint8)( p_mesh->m_material_color_override[0] * 255.0f );
+		rgba.g = (uint8)( p_mesh->m_material_color_override[1] * 255.0f );
+		rgba.b = (uint8)( p_mesh->m_material_color_override[2] * 255.0f );
+	}
+	
+	return rgba;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxGeom::plat_get_num_render_verts( void )
+{
+	Dbg_MsgAssert( m_mesh_array, ( "Invalid for instanced sectors" ));
+	
+	int total_verts = 0;
+
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+			total_verts += p_mesh->m_num_vertices;
+		}
+	}
+	return total_verts;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_get_render_verts( Mth::Vector *p_verts )
+{
+	Dbg_MsgAssert( m_mesh_array, ( "Invalid for instanced sectors" ));
+
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+
+			// Obtain a read-only lock on the mesh data.
+			D3DVECTOR *p_pos;
+			p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, (BYTE**)&p_pos, D3DLOCK_READONLY | D3DLOCK_NOFLUSH );
+
+			// Copy over every vertex position in this mesh.
+			for( uint32 v = 0; v < p_mesh->m_num_vertices; ++v )
+			{
+				p_verts->Set( p_pos->x, p_pos->y, p_pos->z );
+
+				++p_verts;
+				p_pos = (D3DVECTOR*)((BYTE*)p_pos + p_mesh->m_vertex_stride );
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_get_render_colors( Image::RGBA *p_colors )
+{
+	Dbg_MsgAssert( m_mesh_array, ( "Invalid for instanced sectors" ));
+
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+
+			// Obtain a read-only lock on the mesh data.
+			Image::RGBA *p_col;
+			p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, (BYTE**)&p_col, D3DLOCK_READONLY | D3DLOCK_NOFLUSH );
+			p_col = (Image::RGBA*)((BYTE*)p_col + p_mesh->m_diffuse_offset );
+
+			// Copy over every vertex color in this mesh, swapping red and blue.
+			for( uint32 v = 0; v < p_mesh->m_num_vertices; ++v )
+			{
+				p_colors->r = p_col->b;
+				p_colors->g = p_col->g;
+				p_colors->b = p_col->r;
+				p_colors->a = p_col->a;
+
+				++p_colors;
+				p_col = (Image::RGBA*)((BYTE*)p_col + p_mesh->m_vertex_stride );
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_render_verts( Mth::Vector *p_verts )
+{
+	Dbg_MsgAssert( m_mesh_array, ( "Invalid for instanced sectors" ));
+
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+
+			// Obtain a writeable lock on the mesh data.
+			D3DVECTOR *p_pos;
+			p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, (BYTE**)&p_pos, D3DLOCK_NOFLUSH );
+
+			// Will need to store the min and max points in order to calculate the new bounding sphere for the mesh.
+			Mth::CBBox bbox;
+
+			// Copy over every vertex position in this mesh.
+			for( uint32 v = 0; v < p_mesh->m_num_vertices; ++v )
+			{
+				p_pos->x = p_verts->GetX();
+				p_pos->y = p_verts->GetY();
+				p_pos->z = p_verts->GetZ();
+
+				// Add this point to the bounding box.
+				bbox.AddPoint( *p_verts );
+
+				++p_verts;
+				p_pos = (D3DVECTOR*)((BYTE*)p_pos + p_mesh->m_vertex_stride );
+			}
+
+			// Now refigure the bounding sphere.
+			Mth::Vector sphere_center	= bbox.GetMin() + ( 0.5f * ( bbox.GetMax() - bbox.GetMin()));
+			p_mesh->m_sphere_center.x	= sphere_center[X];
+			p_mesh->m_sphere_center.y	= sphere_center[Y];
+			p_mesh->m_sphere_center.z	= sphere_center[Z];
+			p_mesh->m_sphere_radius		= ( bbox.GetMax() - sphere_center ).Length();
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_render_colors( Image::RGBA *p_colors )
+{
+	Dbg_MsgAssert( m_mesh_array, ( "Invalid for instanced sectors" ));
+
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh*	p_mesh			= m_mesh_array[m];
+			Image::RGBA*	p_colors_save	= p_colors;
+
+			// The mesh may contain more than one vertex set, usually in the case of vertex wibbling.
+			for( uint32 v = 0; v < p_mesh->m_num_vertex_buffers; ++v )
+			{
+				p_colors = p_colors_save;
+
+				// Obtain a writeable lock on the mesh data.
+				Image::RGBA *p_col;
+				p_mesh->mp_vertex_buffer[v]->Lock( 0, 0, (BYTE**)&p_col, D3DLOCK_NOFLUSH );
+				p_col = (Image::RGBA*)((BYTE*)p_col + p_mesh->m_diffuse_offset );
+
+				// Copy over every vertex color in this mesh, swapping red and blue.
+				for( uint32 v = 0; v < p_mesh->m_num_vertices; ++v )
+				{
+					p_col->b = p_colors->r;
+					p_col->g = p_colors->g;
+					p_col->r = p_colors->b;
+					p_col->a = p_colors->a;
+
+					++p_colors;
+					p_col = (Image::RGBA*)((BYTE*)p_col + p_mesh->m_vertex_stride );
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_scale( const Mth::Vector & scale )
+{
+	Dbg_MsgAssert( m_mesh_array, ( "Invalid for instanced sectors" ));
+
+	m_scale = scale;
+
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+
+			Mth::Vector current_pos( 0.0f, 0.0f, 0.0f, 1.0f );
+			p_mesh->GetPosition( ¤t_pos );
+			
+			// Obtain a writeable lock on the mesh data.
+			D3DVECTOR *p_pos;
+			p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, (BYTE**)&p_pos, D3DLOCK_NOFLUSH );
+
+			// Scale over every vertex position in this mesh.
+			for( uint32 v = 0; v < p_mesh->m_num_vertices; ++v )
+			{
+				p_pos->x	= (( p_pos->x - current_pos[X] ) * scale[X] ) + current_pos[X];
+				p_pos->y	= (( p_pos->y - current_pos[Y] ) * scale[Y] ) + current_pos[Y];
+				p_pos->z	= (( p_pos->z - current_pos[Z] ) * scale[Z] ) + current_pos[Z];
+
+				p_pos		= (D3DVECTOR*)((BYTE*)p_pos + p_mesh->m_vertex_stride );
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Mth::Vector CXboxGeom::plat_get_scale() const
+{
+	return m_scale;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_uv_wibble_params( float u_vel, float u_amp, float u_freq, float u_phase,
+										   float v_vel, float v_amp, float v_freq, float v_phase )
+{
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh		*p_mesh	= m_mesh_array[m];
+			NxXbox::sMaterial	*p_mat	= p_mesh->mp_material;
+			if( p_mat )
+			{
+				// Find the first pass that wibbles.
+				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+				{
+					if( p_mat->mp_UVWibbleParams[p] )
+					{
+						p_mat->mp_UVWibbleParams[p]->m_UVel			= u_vel;
+						p_mat->mp_UVWibbleParams[p]->m_VVel			= v_vel;
+						p_mat->mp_UVWibbleParams[p]->m_UAmplitude	= u_amp;
+						p_mat->mp_UVWibbleParams[p]->m_VAmplitude	= v_amp;
+						p_mat->mp_UVWibbleParams[p]->m_UFrequency	= u_freq;
+						p_mat->mp_UVWibbleParams[p]->m_VFrequency	= v_freq;
+						p_mat->mp_UVWibbleParams[p]->m_UPhase		= u_phase;
+						p_mat->mp_UVWibbleParams[p]->m_VPhase		= v_phase;
+						break;
+					}
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_use_explicit_uv_wibble( bool yes )
+{
+	if( mp_instance && mp_instance->GetScene())
+	{
+		for( int m = 0; m < mp_instance->GetScene()->m_num_mesh_entries; ++m )
+		{
+			NxXbox::sMesh		*p_mesh	= mp_instance->GetScene()->m_meshes[m];
+			NxXbox::sMaterial	*p_mat	= p_mesh->mp_material;
+			if( p_mat )
+			{
+				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+				{
+					if( p_mat->mp_UVWibbleParams[p] )
+					{
+						p_mat->m_flags[p] |= MATFLAG_EXPLICIT_UV_WIBBLE;
+					}
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_uv_wibble_offsets( float u_offset, float v_offset )
+{
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh		*p_mesh	= m_mesh_array[m];
+			NxXbox::sMaterial	*p_mat	= p_mesh->mp_material;
+			if( p_mat )
+			{
+				// Find the first pass that wibbles.
+				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+				{
+					if( p_mat->mp_UVWibbleParams[p] )
+					{
+						p_mat->mp_UVWibbleParams[p]->m_UVMatrix[2] = u_offset;
+						p_mat->mp_UVWibbleParams[p]->m_UVMatrix[3] = v_offset;
+						plat_use_explicit_uv_wibble( true );
+						break;
+					}
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_set_uv_wibble_offsets( uint32 mat_name_checksum, int pass, float u_offset, float v_offset )
+{
+	if( m_mesh_array )
+	{
+		for( uint32 m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh		*p_mesh	= m_mesh_array[m];
+			NxXbox::sMaterial	*p_mat	= p_mesh->mp_material;
+			if( p_mat && ( p_mat->m_name_checksum == mat_name_checksum ))
+			{
+				// Find the first pass that wibbles.
+//				for( uint32 p = 0; p < p_mat->m_passes; ++p )
+//				{
+//					if( p_mat->mp_UVWibbleParams[p] )
+//					{
+//						p_mat->mp_UVWibbleParams[p]->m_UWibbleOffset = u_offset;
+//						p_mat->mp_UVWibbleParams[p]->m_VWibbleOffset = v_offset;
+//						plat_use_explicit_uv_wibble( true );
+//						break;
+//					}
+//				}
+			}
+		}
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_set_uv_matrix( uint32 mat_name_checksum, int pass, const Mth::Matrix& mat )
+{
+	if( mp_instance && mp_instance->GetScene())
+	{
+		for( int m = 0; m < mp_instance->GetScene()->m_num_mesh_entries; ++m )
+		{
+			NxXbox::sMesh		*p_mesh	= mp_instance->GetScene()->m_meshes[m];
+			NxXbox::sMaterial	*p_mat	= p_mesh->mp_material;
+			if( p_mat )
+			{
+				bool	want_this_material	= false;
+				int		adjusted_pass		= pass;
+
+				// We are searching for materials with a matching name. However there is a caveat in that the
+				// conversion process sometimes creates new materials for geometry flagged as 'render as separate', or for
+				// geometry which is mapped with only certain passes of a multipass material (or both cases).
+				// In such a case, the new material name checksum will differ from the original material name checksum,
+				// with the following bits having significance (i.e. consider bitflags = new_namechecksum - old_namechecksum ):
+				//
+				// Bits 3->6	Pass flags indicating which passes of the original material this material uses
+				// Bits 0->2	Absolute number ('render as separate' flagged geometry) indicating which single pass of the material this material represents.
+				if( p_mat->m_name_checksum == mat_name_checksum )
+				{
+					want_this_material = true;
+				}
+				else if(( p_mat->m_name_checksum > mat_name_checksum ) && (( p_mat->m_name_checksum - mat_name_checksum ) <= 0x7F ))
+				{
+					uint32 checksum_diff		= p_mat->m_name_checksum - mat_name_checksum;
+					uint32 render_separate_pass	= checksum_diff & 0x07;
+					uint32 pass_flags			= checksum_diff >> 3;
+
+					if( render_separate_pass )
+					{
+						if( render_separate_pass == ( pass + 1 ))
+						{
+							want_this_material = true;
+
+							// Readjust the pass to zero, since this material was formed as a single pass of a multipass material.
+							adjusted_pass = 0;
+						}
+					}
+					else if( pass_flags )
+					{
+						// This material was created during scene conversion from another material with more passes.
+						if( pass_flags & ( 1 << pass ))
+						{
+							want_this_material = true;
+							for( int p = 0; p < pass; ++p )
+							{
+								if(( pass_flags & ( 1 << p )) == 0 )
+								{
+									// Readjust the pass down by 1, since this material was created as a subset of another material.
+									--adjusted_pass;
+								}
+							}
+						}
+					}
+				}
+
+				if( want_this_material )
+				{
+					if((uint32)adjusted_pass < p_mat->m_passes )
+					{
+						// Create the wibble params if they don't exist already.
+						if( p_mat->mp_UVWibbleParams[adjusted_pass] == NULL )
+						{
+							p_mat->mp_UVWibbleParams[adjusted_pass]	= new NxXbox::sUVWibbleParams;
+
+							// Need to set flags to indicate that uv wibble is now in effect.
+							p_mat->m_uv_wibble				= true;
+							p_mat->m_flags[adjusted_pass]  |= MATFLAG_UV_WIBBLE | MATFLAG_EXPLICIT_UV_WIBBLE;
+						}
+
+						// Also need to switch vertex shaders if the current vertex shader does not support UV Transforms.
+						if( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_1Weight )
+						{
+							p_mesh->m_vertex_shader[0] = WeightedMeshVS_VXC_1Weight_UVTransform;
+						}
+						else if( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_2Weight )
+						{
+							p_mesh->m_vertex_shader[0] = WeightedMeshVS_VXC_2Weight_UVTransform;
+						}
+						else if( p_mesh->m_vertex_shader[0] == WeightedMeshVS_VXC_3Weight )
+						{
+							p_mesh->m_vertex_shader[0] = WeightedMeshVS_VXC_3Weight_UVTransform;
+						}
+
+						// Set the matrix values.
+						p_mat->mp_UVWibbleParams[adjusted_pass]->m_UVMatrix[0]	= mat[0][0];
+						p_mat->mp_UVWibbleParams[adjusted_pass]->m_UVMatrix[1]	= mat[0][1];
+						p_mat->mp_UVWibbleParams[adjusted_pass]->m_UVMatrix[2]	= mat[3][0];
+						p_mat->mp_UVWibbleParams[adjusted_pass]->m_UVMatrix[3]	= mat[3][1];
+					}
+				}
+			}
+		}
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxGeom::plat_allocate_uv_matrix_params( uint32 mat_name_checksum, int pass )
+{
+	if( mp_instance && mp_instance->GetScene())
+	{
+		bool changed_something = false;
+
+		for( int m = 0; m < mp_instance->GetScene()->m_num_mesh_entries; ++m )
+		{
+			NxXbox::sMesh		*p_mesh	= mp_instance->GetScene()->m_meshes[m];
+			NxXbox::sMaterial	*p_mat	= p_mesh->mp_material;
+			if( p_mat )
+			{
+				bool	want_this_material	= false;
+				int		adjusted_pass		= pass;
+
+				// We are searching for materials with a matching name. However there is a caveat in that the
+				// conversion process sometimes creates new materials for geometry flagged as 'render as separate', or for
+				// geometry which is mapped with only certain passes of a multipass material (or both cases).
+				// In such a case, the new material name checksum will differ from the original material name checksum,
+				// with the following bits having significance (i.e. consider bitflags = new_namechecksum - old_namechecksum ):
+				//
+				// Bits 3->6	Pass flags indicating which passes of the original material this material uses
+				// Bits 0->2	Absolute number ('render as separate' flagged geometry) indicating which single pass of the material this material represents.
+				if( p_mat->m_name_checksum == mat_name_checksum )
+				{
+					want_this_material = true;
+				}
+				else if(( p_mat->m_name_checksum > mat_name_checksum ) && (( p_mat->m_name_checksum - mat_name_checksum ) <= 0x7F ))
+				{
+					uint32 checksum_diff		= p_mat->m_name_checksum - mat_name_checksum;
+					uint32 render_separate_pass	= checksum_diff & 0x07;
+					uint32 pass_flags			= checksum_diff >> 3;
+
+					if( render_separate_pass )
+					{
+						if( render_separate_pass == ( pass + 1 ))
+						{
+							want_this_material = true;
+
+							// Readjust the pass to zero, since this material was formed as a single pass of a multipass material.
+							adjusted_pass = 0;
+						}
+					}
+					else if( pass_flags )
+					{
+						// This material was created during scene conversion from another material with more passes.
+						if( pass_flags & ( 1 << pass ))
+						{
+							want_this_material = true;
+							for( int p = 0; p < pass; ++p )
+							{
+								if(( pass_flags & ( 1 << p )) == 0 )
+								{
+									// Readjust the pass down by 1, since this material was created as a subset of another material.
+									--adjusted_pass;
+								}
+							}
+						}
+					}
+				}
+
+				if( want_this_material )
+				{
+					if((uint32)adjusted_pass < p_mat->m_passes )
+					{
+						// Create the wibble params if they don't exist already.
+						if( p_mat->mp_UVWibbleParams[adjusted_pass] == NULL )
+						{
+							p_mat->mp_UVWibbleParams[adjusted_pass]	= new NxXbox::sUVWibbleParams;
+
+							// Need to set flags to indicate that uv wibble is now in effect.
+							p_mat->m_uv_wibble						= true;
+							p_mat->m_flags[adjusted_pass]		   |= MATFLAG_UV_WIBBLE | MATFLAG_EXPLICIT_UV_WIBBLE;
+
+							changed_something = true;
+						}
+					}
+				}
+			}
+		}
+		return changed_something;
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxGeom::plat_set_model_lights( CModelLights* p_model_lights )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxGeom *CXboxGeom::plat_clone( bool instance, CScene *p_dest_scene )
+{
+	CXboxGeom *p_clone = NULL;
+	
+	// This is a CXboxGeom 'hanging' from a sector. Create a new CXboxGeom which will store the CInstance.
+	p_clone = new CXboxGeom();
+	
+	// Create new meshes for the clone.
+	p_clone->m_mesh_array	= new NxXbox::sMesh*[m_num_mesh];
+	p_clone->m_num_mesh		= m_num_mesh;
+	for( uint32 m = 0; m < p_clone->m_num_mesh; ++m )
+	{
+		p_clone->m_mesh_array[m] = m_mesh_array[m]->Clone( instance );
+	}
+	
+	if( instance == false )
+	{
+		p_clone->SetActive( true );
+
+		// In this situation, we need to add the individual meshes to the scene.
+		// Grab a temporary workspace buffer.
+		Nx::CXboxScene *p_xbox_scene						= static_cast( p_dest_scene );
+		NxXbox::sScene *p_scene								= p_xbox_scene->GetEngineScene();
+		NxXbox::sMesh **p_temp_mesh_buffer					= ( p_scene->m_num_mesh_entries > 0 ) ? new NxXbox::sMesh*[p_scene->m_num_mesh_entries] : NULL;
+
+		// Set the scene pointer for the clone.
+		p_clone->SetScene( p_xbox_scene );
+		
+		// Copy meshes over into the temporary workspace buffer.
+		for( int i = 0; i < p_scene->m_num_mesh_entries; ++i )
+		{
+			p_temp_mesh_buffer[i]	= p_scene->m_meshes[i];
+		}
+
+		// Store how many meshes were present.
+		int old_num_mesh_entries	= p_scene->m_num_mesh_entries;
+		
+		// Delete current mesh array.
+		delete [] p_scene->m_meshes;
+
+		// Important to set this to NULL.
+		p_scene->m_meshes			= NULL;
+		
+		// Include new meshes in count.
+		p_scene->CountMeshes( m_num_mesh, m_mesh_array );
+
+		// Allocate new mesh arrays.
+		p_scene->CreateMeshArrays();
+
+		// Copy old mesh data back in.
+		for( int i = 0; i < old_num_mesh_entries; ++i )
+		{
+			p_scene->m_meshes[i] = p_temp_mesh_buffer[i];
+		}
+
+		// Remove temporary arrays.
+		delete [] p_temp_mesh_buffer;
+		
+		// Add new meshes.
+		p_scene->AddMeshes( p_clone->m_num_mesh, p_clone->m_mesh_array );
+
+		// Sort the meshes.
+		p_scene->SortMeshes();
+	}
+	else
+	{
+		// Create a new scene which will be attached via the instance.
+		p_clone->m_bbox			= m_bbox;
+		NxXbox::sScene *p_scene = p_clone->GenerateScene();
+
+		p_clone->SetActive( true );
+	
+		// Create the instance.
+		Mth::Matrix temp;
+		temp.Identity();
+		NxXbox::CInstance *p_instance = new NxXbox::CInstance( p_scene, temp, 1, NULL );
+		
+		// This instance will be the only object maintaining a reference to the attached scene, so we want to delete
+		// the scene when the instance gets removed.
+		p_instance->SetFlag( NxXbox::CInstance::INSTANCE_FLAG_DELETE_ATTACHED_SCENE );
+
+		// Hook the clone up to the instance.		
+		p_clone->SetInstance( p_instance );
+	}
+	return p_clone;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxGeom* CXboxGeom::plat_clone( bool instance, CModel* p_dest_model )
+{
+	CXboxGeom* p_clone = NULL;
+
+	Nx::CXboxScene* p_xbox_scene = new CXboxScene;
+	p_xbox_scene->SetEngineScene( mp_instance->GetScene() );
+
+	p_clone				= new CXboxGeom();
+	p_clone->mp_scene	= p_xbox_scene;
+
+	int num_mesh		= mp_instance->GetScene()->m_num_mesh_entries;
+
+	// Create new meshes for the clone.
+	p_clone->m_mesh_array	= num_mesh ? new NxXbox::sMesh*[num_mesh] : NULL;
+	p_clone->m_num_mesh		= num_mesh;
+	for( uint32 m = 0; m < p_clone->m_num_mesh; ++m )
+	{
+		p_clone->m_mesh_array[m] = mp_instance->GetScene()->m_meshes[m]->Clone( instance );
+	}
+
+	// Create a new scene which will be attached via the instance.
+	p_clone->m_bbox			= m_bbox;
+	NxXbox::sScene* p_scene = p_clone->GenerateScene();
+
+	// Kill the temp scene.
+	p_xbox_scene->SetEngineScene( NULL );
+	delete p_xbox_scene;
+	p_clone->mp_scene		= NULL;
+
+	p_clone->SetActive( true );
+
+	// Create the new bone array.
+	int				num_bones	= p_dest_model->GetNumBones();
+	Mth::Matrix*	p_bones		= new Mth::Matrix[num_bones];
+	for( int b = 0; b < num_bones; ++b )
+	{
+		p_bones[b].Identity();
+	}
+
+	// Create the instance.
+	Mth::Matrix temp;
+	temp.Identity();
+	NxXbox::CInstance* p_instance	= new NxXbox::CInstance( p_scene, temp, num_bones, p_bones );
+	
+	Nx::CXboxModel* p_xbox_model	= static_cast( p_dest_model );
+	((CXboxModel*)p_dest_model )->SetInstance( p_instance );
+
+	// This instance will be the only object maintaining a reference to the attached scene, so we want to delete
+	// the scene when the instance gets removed.
+	p_instance->SetFlag( NxXbox::CInstance::INSTANCE_FLAG_DELETE_ATTACHED_SCENE );
+
+	// Hook the clone up to the instance.		
+	p_clone->SetInstance( p_instance );
+
+	return p_clone;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+				
diff --git a/Code/Gfx/XBox/p_NxGeom.h b/Code/Gfx/XBox/p_NxGeom.h
new file mode 100644
index 0000000..6032f3b
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxGeom.h
@@ -0,0 +1,122 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxGeom.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  3/5/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_GEOM_H__
+#define	__GFX_P_NX_GEOM_H__
+    
+#include "gfx/nxgeom.h"
+#include "gfx/xbox/p_nxsector.h"
+#include "gfx/xbox/p_nxmesh.h"
+
+namespace Mth
+{
+	class Matrix;
+}
+
+namespace NxXbox
+{
+	class CInstance;
+}
+						   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CGeom
+class CXboxGeom : public CGeom
+{
+                                      
+public:
+								CXboxGeom();
+	virtual 					~CXboxGeom();
+	void						SetInstance( NxXbox::CInstance *p_instance )		{ mp_instance = p_instance; }
+	NxXbox::CInstance			*GetInstance( void )								{ return mp_instance; }
+	void						InitMeshList();
+	void						ClearMeshList();
+	void						AddMesh( NxXbox::sMesh * );
+	Lst::Head< NxXbox::sMesh >	*GetMeshList();
+
+	void						CreateMeshArray();
+	bool						RegisterMeshArray( bool just_count );
+	void						DestroyMeshArray( void );
+
+	const Mth::CBBox &			GetBoundingBox( void )			{ return m_bbox; }
+	void						SetScene( CXboxScene *p_scene )	{ mp_scene = p_scene; }
+	NxXbox::sScene				*GenerateScene( void );
+
+
+private:						// It's all private, as it is machine specific
+	virtual	bool					plat_load_geom_data( CMesh *pMesh, CModel *pModel, bool color_per_material );
+	virtual void					plat_finalize( void );
+	virtual	void					plat_set_active( bool active );
+	virtual bool					plat_is_active( void ) const;
+	virtual const Mth::CBBox &		plat_get_bounding_box( void ) const;
+	virtual const Mth::Vector		plat_get_bounding_sphere( void ) const;
+	virtual void					plat_set_bounding_sphere( const Mth::Vector& boundingSphere );
+
+	virtual const Mth::Vector &		plat_get_world_position( void ) const;
+	virtual void					plat_set_world_position( const Mth::Vector& pos );
+	virtual const Mth::Matrix &		plat_get_orientation( void ) const;
+	virtual void					plat_set_orientation( const Mth::Matrix& orient );
+	virtual void					plat_set_scale( const Mth::Vector & scale );
+	virtual Mth::Vector				plat_get_scale( void ) const;
+	virtual void					plat_rotate_y( Mth::ERot90 rot );
+	virtual bool					plat_render( Mth::Matrix* pRootMatrix, Mth::Matrix* ppBoneMatrices, int numBones );
+	virtual void					plat_set_bone_matrix_data( Mth::Matrix* pBoneMatrices, int numBones );
+	virtual bool					plat_hide_polys( uint32 mask );
+	virtual uint32					plat_get_visibility( void ) const;
+	virtual	void					plat_set_visibility( uint32 mask );
+	virtual void					plat_set_color( Image::RGBA rgba );
+	virtual void					plat_clear_color( void );
+	virtual Image::RGBA				plat_get_color( void ) const;
+	virtual	int						plat_get_num_render_verts( void );
+	virtual	void					plat_get_render_verts( Mth::Vector *p_verts );
+	virtual	void					plat_get_render_colors( Image::RGBA *p_colors );
+	virtual void					plat_set_render_verts( Mth::Vector *p_verts );
+	virtual	void					plat_set_render_colors( Image::RGBA *p_colors );
+	virtual void					plat_set_model_lights( CModelLights* p_model_lights );
+	virtual void					plat_set_uv_wibble_params( float u_vel, float u_amp, float u_freq, float u_phase, float v_vel, float v_amp, float v_freq, float v_phase );
+	virtual void					plat_use_explicit_uv_wibble( bool yes );
+	virtual void					plat_set_uv_wibble_offsets( float u_offset, float v_offset );
+	virtual bool					plat_set_uv_wibble_offsets( uint32 mat_name_checksum, int pass, float u_offset, float v_offset );
+	virtual bool					plat_set_uv_matrix( uint32 mat_name_checksum, int pass, const Mth::Matrix& mat );
+	virtual bool					plat_set_material_color( uint32 mat_name_checksum, int pass, Image::RGBA rgba );
+	virtual bool					plat_allocate_uv_matrix_params( uint32 mat_checksum, int pass );
+
+	virtual CXboxGeom*				plat_clone( bool instance, CScene *p_dest_scene = NULL );
+	virtual CXboxGeom*				plat_clone( bool instance, CModel *p_dest_model );
+
+public:
+	Mth::CBBox						m_bbox;	
+	
+	CXboxScene *					mp_scene;
+	Lst::Head< NxXbox::sMesh >		*mp_init_mesh_list;   
+	NxXbox::sMesh **				m_mesh_array;
+	uint							m_num_mesh;
+	uint32							m_visible;
+	bool							m_active;
+
+private:
+	NxXbox::CInstance				*mp_instance;
+	CXboxMesh						*mp_mesh;		// Used for obtaining CAS poly removal data.
+	Mth::Vector						m_scale;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
diff --git a/Code/Gfx/XBox/p_NxImposter.cpp b/Code/Gfx/XBox/p_NxImposter.cpp
new file mode 100644
index 0000000..8cb16ab
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxImposter.cpp
@@ -0,0 +1,565 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxImposter.cpp
+
+#include 	"sys/timer.h"
+#include 	"gfx/xbox/p_NxGeom.h"
+#include 	"gfx/xbox/p_NxImposter.h"
+#include 	"gfx/xbox/nx/nx_init.h"
+#include 	"gfx/xbox/nx/render.h"
+
+namespace Nx
+{
+
+	
+const int XBOX_IMPOSTER_UPDATE_LIMIT		= 30;
+const int XBOX_IMPOSTER_MAX_U_TEXTURE_SIZE	= 128;
+const int XBOX_IMPOSTER_MAX_V_TEXTURE_SIZE	= 128;
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void frustum_project_box( Mth::CBBox &bbox, XGMATRIX *p_view_matrix, Mth::Vector *p_max_x, Mth::Vector *p_max_y, Mth::Vector *p_mid )
+{
+	float	max_projected_xx, max_projected_xz;		// The camera space position of the point with the greatest x axis value when projected to z = mid_z.
+	float	max_projected_x_mid_z;					// The projected x axis value when this point is projected to z = mid_z;
+
+	float	max_projected_yy, max_projected_yz;		// The camera space position of the point furthest along the y axis when projected to z = mid_z.
+	float	max_projected_y_mid_z;					// The projected y axis value when this point is projected to z = mid_z;
+
+	float	min_x = bbox.GetMin().GetX();
+	float	min_y = bbox.GetMin().GetY();
+	float	min_z = bbox.GetMin().GetZ();
+	float	max_x = bbox.GetMax().GetX();
+	float	max_y = bbox.GetMax().GetY();
+	float	max_z = bbox.GetMax().GetZ();
+
+	// Project the midpoint of the box, since this is the point through which the imposter polygon will pass.
+	XGVECTOR3 mid_in( min_x + ( 0.5f * ( max_x - min_x )), min_y + ( 0.5f * ( max_y - min_y )), min_z + ( 0.5f * ( max_z - min_z )));
+	XGVECTOR4 mid_out;
+	XGVec3Transform( &mid_out, &mid_in, p_view_matrix );
+
+	for( uint32 v = 0; v < 8; ++v )
+	{
+		XGVECTOR3 in;
+		XGVECTOR4 out;
+
+		in.x = ( v & 0x04 ) ? max_x : min_x;
+		in.y = ( v & 0x02 ) ? max_y : min_y;
+		in.z = ( v & 0x01 ) ? max_z : min_z;
+		
+		XGVec3Transform( &out, &in, p_view_matrix );
+
+		out.x = fabsf( out.x );
+		out.y = fabsf( out.y );
+
+		float projected_x_mid_z	= out.x * ( mid_out.z / out.z );
+		float projected_y_mid_z	= out.y * ( mid_out.z / out.z );
+
+		if( v == 0 )
+		{
+			max_projected_x_mid_z	= projected_x_mid_z;
+			max_projected_xx		= out.x;
+			max_projected_xz		= out.z;
+
+			max_projected_y_mid_z	= projected_y_mid_z;
+			max_projected_yy		= out.y;
+			max_projected_yz		= out.z;
+		}
+		else
+		{
+			if( projected_x_mid_z > max_projected_x_mid_z )
+			{
+				max_projected_xx		= out.x;
+				max_projected_xz		= out.z;
+				max_projected_x_mid_z	= projected_x_mid_z;
+			}
+
+			if( projected_y_mid_z > max_projected_y_mid_z )
+			{
+				max_projected_yy		= out.y;
+				max_projected_yz		= out.z;
+				max_projected_y_mid_z	= projected_y_mid_z;
+			}
+		}
+	}
+
+	p_max_x->Set( max_projected_xx, 0.0f, max_projected_xz, 0.0f );
+	p_max_y->Set( 0.0f, max_projected_yy, max_projected_yz, 0.0f );
+	p_mid->Set( mid_out.x, mid_out.y, mid_out.z );
+}
+	
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CImposterGroup* CImposterManager::plat_create_imposter_group( void )
+{
+	return new CXboxImposterGroup;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::plat_pre_render_imposters( void )
+{
+	// Set up the common material attributes for the imposters.
+//	NxXbox::set_blend_mode( NxXbox::vBLEND_MODE_BLEND );
+	NxXbox::set_blend_mode( NxXbox::vBLEND_MODE_ONE_INV_SRC_ALPHA );
+
+	NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+	NxXbox::set_pixel_shader( PixelShader2 );
+
+	NxXbox::set_render_state( RS_UVADDRESSMODE0,	0x00010001UL );
+	NxXbox::set_render_state( RS_ALPHACUTOFF,		1 );
+	NxXbox::set_render_state( RS_ZWRITEENABLE,		1 );
+	NxXbox::set_render_state( RS_ZTESTENABLE,		1 );
+	NxXbox::set_render_state( RS_CULLMODE,			D3DCULL_NONE );
+
+	D3DDevice_SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE );
+	D3DDevice_SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_PASSTHRU | 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CImposterManager::plat_post_render_imposters( void )
+{
+	// Clean up the common material attributes for the imposters.
+	NxXbox::set_texture( 0, NULL );
+	NxXbox::set_render_state( RS_CULLMODE, D3DCULL_CW );
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CImposterGroup
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxImposterGroup::CXboxImposterGroup()
+{
+	mp_texture					= NULL;
+	m_update_count				= Mth::Rnd( XBOX_IMPOSTER_UPDATE_LIMIT );
+	mp_removed_textures_list	= new Lst::Head ;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxImposterGroup::~CXboxImposterGroup()
+{
+	if( mp_texture )
+	{
+		mp_texture->Release();
+		mp_texture = NULL;
+	}
+
+	// Remove all nodes from the removed textures table.
+	Lst::Node *p_node, *p_next;
+	for( p_node = mp_removed_textures_list->GetNext(); p_node; p_node = p_next )
+	{
+		p_next = p_node->GetNext();
+
+		sRemovedTextureDetails	*p_details	= p_node->GetData();
+		IDirect3DTexture8		*p_texture	= p_details->mp_texture;
+		p_texture->Release();
+
+		delete p_details;
+		delete p_node;
+	}
+
+	// Remove the table itself.
+	delete mp_removed_textures_list;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxImposterGroup::process_removed_textures( void )
+{
+	Lst::Node *p_node, *p_next;
+	for( p_node = mp_removed_textures_list->GetNext(); p_node; p_node = p_next )
+	{
+		p_next = p_node->GetNext();
+
+		sRemovedTextureDetails	*p_details	= p_node->GetData();
+		int						time		= p_details->m_time_removed;
+		if((( NxXbox::EngineGlobals.render_start_time - time ) > 250 ) || ( time > NxXbox::EngineGlobals.render_start_time ))
+		{
+			IDirect3DTexture8*	p_texture	= p_details->mp_texture;
+			p_texture->Release();
+
+			delete p_details;
+			delete p_node;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxImposterGroup::plat_process( void )
+{
+	process_removed_textures();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxImposterGroup::plat_create_imposter_polygon( void )
+{
+	Dbg_Assert( !m_imposter_polygon_exists );
+
+	// Generate a camera matrix that will point the camera directly at the midpoint of the bounding box.
+	XGMATRIX	cam_mat;
+	XGVECTOR3	look_at( m_composite_bbox_mid[X], m_composite_bbox_mid[Y], m_composite_bbox_mid[Z] );
+	XGMatrixLookAtRH( &cam_mat, &NxXbox::EngineGlobals.cam_position, &look_at, &NxXbox::EngineGlobals.cam_up );
+
+	// Using this camera matrix, project all eight corners of the bounding box, in order to determine the best size for the texture.
+	Mth::Vector proj_max_x, proj_max_y, proj_mid;
+	frustum_project_box( m_composite_bbox, &cam_mat, &proj_max_x, &proj_max_y, &proj_mid );
+
+	// Now project the minimum and maximum x and y values onto the projection plane - the plane through the midpoint of the box.
+	float max_projected_x	= proj_max_x[X] * ( proj_mid[Z] / proj_max_x[Z] );
+	float max_projected_y	= proj_max_y[Y] * ( proj_mid[Z] / proj_max_y[Z] );
+
+	// Calculate the maximum width and height at the near plane.
+	float wnp				= 2.0f * proj_max_x[X] * ( NxXbox::EngineGlobals.near_plane / fabsf( proj_max_x[Z] ));
+	float hnp				= 2.0f * proj_max_y[Y] * ( NxXbox::EngineGlobals.near_plane / fabsf( proj_max_y[Z] ));
+
+	m_tex_width				= 16 + Ftoi_ASM(( 640.0f * wnp ) / NxXbox::EngineGlobals.near_plane_width );
+	m_tex_height			= 16 + Ftoi_ASM(( 480.0f * hnp ) / NxXbox::EngineGlobals.near_plane_height );
+
+	// Round texture to the nearest 16 pixel limit.
+	m_tex_width				= (( m_tex_width + 0x0F ) & ~0x0F );
+	m_tex_height			= (( m_tex_height + 0x0F ) & ~0x0F );
+
+	// Clamp texture to maximum allowed size.
+	m_tex_width				= ( m_tex_width > XBOX_IMPOSTER_MAX_U_TEXTURE_SIZE ) ? XBOX_IMPOSTER_MAX_U_TEXTURE_SIZE : m_tex_width;
+	m_tex_height			= ( m_tex_height > XBOX_IMPOSTER_MAX_V_TEXTURE_SIZE ) ? XBOX_IMPOSTER_MAX_V_TEXTURE_SIZE : m_tex_height;
+
+	// Calculate the corresponding projection matrix.
+	XGMATRIX proj_mat;
+	XGMatrixPerspectiveRH( &proj_mat, wnp, hnp, NxXbox::EngineGlobals.near_plane, NxXbox::EngineGlobals.far_plane );
+
+	// Set the calculated view and projection matrices.
+	D3DDevice_SetTransform( D3DTS_VIEW, &cam_mat );
+	D3DDevice_SetTransform( D3DTS_PROJECTION, &proj_mat );
+
+	// Create a render target texture into which the object will be drawn.
+	HRESULT hr;
+	if( mp_texture == NULL )
+	{
+		hr = D3DDevice_CreateTexture( m_tex_width, m_tex_height, 1, 0, D3DFMT_LIN_A8R8G8B8, 0, &mp_texture );
+		Dbg_Assert( hr == D3D_OK );
+	}
+
+	// Create a corresponding depth texture (we need this only for the render - then it can be removed).
+	IDirect3DTexture8* p_depth_buffer;
+	hr = D3DDevice_CreateTexture( m_tex_width, m_tex_height, 1, 0, D3DFMT_LIN_D24S8, 0, &p_depth_buffer );
+	Dbg_Assert( hr == D3D_OK );
+
+	// This call will increase the reference count of the IDirect3DTexture8 object.
+	LPDIRECT3DSURFACE8 p_surface, p_depth_surface;
+	mp_texture->GetSurfaceLevel( 0, &p_surface );
+	p_depth_buffer->GetSurfaceLevel( 0, &p_depth_surface );
+
+	// This call will increase the reference count of the IDirect3DSurface8 object.
+	D3DDevice_SetRenderTarget( p_surface, p_depth_surface );
+			
+	// Clear the render target.
+	D3DDevice_Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0x00000000, 1.0f, 0 );
+//	D3DDevice_Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0x80800000, 1.0f, 0 );
+
+	// The imposter polygon has now been created.
+	m_imposter_polygon_exists = true;
+
+	// Set the camera position at the time of creation.
+	m_cam_pos.Set( NxXbox::EngineGlobals.cam_position.x, NxXbox::EngineGlobals.cam_position.y, NxXbox::EngineGlobals.cam_position.z );
+
+	// Build a list of meshes, so we can sort them dynamically into draw order.
+	NxXbox::sMesh*	mesh_list[256];
+	uint32			num_meshes = 0;
+
+	Lst::Node *node, *next;
+	for( node = mp_geom_list->GetNext(); node; node = next )
+	{
+		next = node->GetNext();
+		Nx::CXboxGeom *p_xbox_geom = static_cast( node->GetData());
+
+		for( uint32 m = 0; m < p_xbox_geom->m_num_mesh; ++m )
+		{
+			Dbg_Assert( num_meshes < 256 );
+			NxXbox::sMesh* p_mesh	= p_xbox_geom->m_mesh_array[m];
+			mesh_list[num_meshes++]	= p_mesh;
+		}
+	}
+
+	if( num_meshes > 0 )
+	{
+		// Sort the array of pointers into draw order.
+		qsort( mesh_list, num_meshes, sizeof( NxXbox::sMesh* ), NxXbox::sort_by_material_draw_order );
+	}
+
+	// Render each mesh into the render target.
+	NxXbox::EngineGlobals.blend_mode_override = NxXbox::vBLEND_MODE_ONE_INV_SRC_ALPHA;
+	NxXbox::sMaterial*	p_material = NULL;
+	for( uint32 m = 0; m < num_meshes; ++m )
+	{
+		NxXbox::sMesh* p_mesh = mesh_list[m];
+		if( p_mesh->mp_material != p_material )
+		{
+			p_material	= p_mesh->mp_material;
+			p_material->Submit();
+		}
+		p_mesh->Submit();
+	}
+	NxXbox::EngineGlobals.blend_mode_override = 0;
+
+	// Can now set the meshes in the geom inactive.
+	for( node = mp_geom_list->GetNext(); node; node = next )
+	{
+		next = node->GetNext();
+		Nx::CXboxGeom *p_xbox_geom = static_cast( node->GetData());
+		p_xbox_geom->SetActive( false );
+	}
+
+	// Remove references to surfaces.
+	p_surface->Release();
+	p_depth_surface->Release();
+	p_depth_buffer->Release();
+
+	// Restore the default render target.
+	D3DDevice_SetRenderTarget( NxXbox::EngineGlobals.p_RenderSurface, NxXbox::EngineGlobals.p_ZStencilSurface );
+
+	// Restore the view and projection transforms.
+	D3DDevice_SetTransform( D3DTS_VIEW, &NxXbox::EngineGlobals.view_matrix );
+	D3DDevice_SetTransform( D3DTS_PROJECTION, &NxXbox::EngineGlobals.projection_matrix );
+
+	// Now figure the vertex positions for the polygon.
+	Mth::Vector at = m_composite_bbox_mid - m_cam_pos;
+	at.Normalize();
+	Mth::Vector right	= Mth::CrossProduct( Mth::Vector( NxXbox::EngineGlobals.cam_up.x, NxXbox::EngineGlobals.cam_up.y, NxXbox::EngineGlobals.cam_up.z ), at );
+	right.Normalize();
+	Mth::Vector up		= Mth::CrossProduct( at, right );
+
+	Mth::Vector	verts[4];
+	verts[0]			= m_composite_bbox_mid - ( max_projected_x * right ) + ( max_projected_y * up );
+	verts[1]			= m_composite_bbox_mid + ( max_projected_x * right ) + ( max_projected_y * up );
+	verts[2]			= m_composite_bbox_mid + ( max_projected_x * right ) - ( max_projected_y * up );
+	verts[3]			= m_composite_bbox_mid - ( max_projected_x * right ) - ( max_projected_y * up );
+
+	for( int v = 0; v < 4; ++v )
+	{
+		m_vertex_buffer[v].x	= verts[v][X];
+		m_vertex_buffer[v].y	= verts[v][Y];
+		m_vertex_buffer[v].z	= verts[v][Z];
+	}
+
+	// The texture is a linear format, so the uv's are in texel space.
+	m_vertex_buffer[0].u		= (float)m_tex_width;
+	m_vertex_buffer[0].v		= 0.0f;
+	m_vertex_buffer[1].u		= 0.0f;
+	m_vertex_buffer[1].v		= 0.0f;
+	m_vertex_buffer[2].u		= 0.0f;
+	m_vertex_buffer[2].v		= (float)m_tex_height;
+	m_vertex_buffer[3].u		= (float)m_tex_width;
+	m_vertex_buffer[3].v		= (float)m_tex_height;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxImposterGroup::plat_remove_imposter_polygon( void )
+{
+	if( m_imposter_polygon_exists )
+	{
+		m_imposter_polygon_exists = false;
+
+		if( mp_texture )
+		{
+			// At this point move the texture resource into a list of removed textures. Here it will remain
+			// for sufficient time to ensure that the GPU will no longer try to access it during push buffer processing.
+			sRemovedTextureDetails	*p_new_details			= new sRemovedTextureDetails;
+			p_new_details->mp_texture						= mp_texture;
+			p_new_details->m_time_removed					= NxXbox::EngineGlobals.render_start_time;
+
+			Lst::Node *p_new_node	= new Lst::Node( p_new_details );
+			mp_removed_textures_list->AddToTail( p_new_node );
+
+			mp_texture = NULL;
+		}
+
+		Lst::Node *node, *next;
+		for( node = mp_geom_list->GetNext(); node; node = next )
+		{
+			next = node->GetNext();
+			Nx::CXboxGeom *p_xbox_geom = static_cast( node->GetData());
+
+			// Can now set the meshes in the geom active.
+			p_xbox_geom->SetActive( true );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxImposterGroup::plat_update_imposter_polygon( void )
+{
+	// Calculate the new vector from bounding box midpoint to camera.
+	Mth::Vector new_vec( NxXbox::EngineGlobals.cam_position.x - m_composite_bbox_mid[X], 
+						 NxXbox::EngineGlobals.cam_position.y - m_composite_bbox_mid[Y],
+						 NxXbox::EngineGlobals.cam_position.z - m_composite_bbox_mid[Z] );
+	new_vec.Normalize();
+
+	// Calculate the old vector from bounding box midpoint to camera when the imposter was created.
+	Mth::Vector old_vec = m_cam_pos - m_composite_bbox_mid;
+	old_vec.Normalize();
+
+	float angle_change = acosf( Mth::DotProduct( new_vec, old_vec ));
+
+	// Rebuild the imposter polygon if the angle change is beyond some limit.
+	if( angle_change > Mth::DegToRad( 5.0f ))
+	{
+		RemoveImposterPolygon();
+		CreateImposterPolygon();
+		return true;
+	}
+
+	// Check intermittently to see if the projected screen area of the imposter has changed sufficienty
+	// to warrant regenerating a new texture.
+	if( m_update_count >= XBOX_IMPOSTER_UPDATE_LIMIT )
+	{
+		m_update_count = 0;
+
+		// Generate a camera matrix that will point the camera directly at the midpoint of the bounding box.
+		XGMATRIX	cam_mat;
+		XGVECTOR3	look_at( m_composite_bbox_mid[X], m_composite_bbox_mid[Y], m_composite_bbox_mid[Z] );
+		XGMatrixLookAtRH( &cam_mat, &NxXbox::EngineGlobals.cam_position, &look_at, &NxXbox::EngineGlobals.cam_up );
+
+		// Using this camera matrix, project all eight corners of the bounding box, in order to determine the best size for the texture.
+		Mth::Vector proj_max_x, proj_max_y, proj_mid;
+		frustum_project_box( m_composite_bbox, &cam_mat, &proj_max_x, &proj_max_y, &proj_mid );
+
+		// Calculate the maximum width and height at the near plane.
+		float wnp		= 2.0f * proj_max_x[X] * ( NxXbox::EngineGlobals.near_plane / fabsf( proj_max_x[Z] ));
+		float hnp		= 2.0f * proj_max_y[Y] * ( NxXbox::EngineGlobals.near_plane / fabsf( proj_max_y[Z] ));
+
+		// Round texture to the nearest 16 pixel limit.
+		int tex_width	= 16 + Ftoi_ASM(( 640.0f * wnp ) / NxXbox::EngineGlobals.near_plane_width );
+		int tex_height	= 16 + Ftoi_ASM(( 480.0f * hnp ) / NxXbox::EngineGlobals.near_plane_height );
+		tex_width		= ( tex_width + 0x0F ) & ~0x0F;
+		tex_height		= ( tex_height + 0x0F ) & ~0x0F;
+
+		// Clamp texture to maximum allowed size.
+		tex_width		= ( tex_width > XBOX_IMPOSTER_MAX_U_TEXTURE_SIZE ) ? XBOX_IMPOSTER_MAX_U_TEXTURE_SIZE : tex_width;
+		tex_height		= ( tex_height > XBOX_IMPOSTER_MAX_V_TEXTURE_SIZE ) ? XBOX_IMPOSTER_MAX_V_TEXTURE_SIZE : tex_height;
+
+		if(( tex_width != m_tex_width ) || ( tex_height != m_tex_height ))
+		{
+			RemoveImposterPolygon();
+			CreateImposterPolygon();
+			return true;
+		}
+	}
+
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxImposterGroup::plat_draw_imposter_polygon( void )
+{
+	// Have to clear texture 0 before switching to the imposter texture, because it is a linear format.
+	NxXbox::set_texture( 0, NULL );
+	NxXbox::set_texture( 0, mp_texture );
+
+	NxXbox::EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_QUADLIST, 1, m_vertex_buffer, sizeof( sImposterPolyVert ));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float CXboxImposterGroup::plat_check_distance( void )
+{
+	// First check the visibility, using the bounding sphere.
+	bool visible = NxXbox::frustum_check_sphere((D3DXVECTOR3*)&( m_composite_bbox_mid[X] ), m_composite_bsphere_radius );
+
+	if( !visible )
+		return 0.0f;
+
+	float	min_x = m_composite_bbox.GetMin().GetX();
+	float	min_y = m_composite_bbox.GetMin().GetY();
+	float	min_z = m_composite_bbox.GetMin().GetZ();
+	float	max_x = m_composite_bbox.GetMax().GetX();
+	float	max_y = m_composite_bbox.GetMax().GetY();
+	float	max_z = m_composite_bbox.GetMax().GetZ();
+
+	// The camera-space distance to the nearest point on the composite bounding box of the imposter.
+	float	nearest = NxXbox::EngineGlobals.far_plane;
+
+	for( uint32 v = 0; v < 8; ++v )
+	{
+		XGVECTOR3 test_in( ( v & 0x04 ) ? max_x : min_x, ( v & 0x02 ) ? max_y : min_y, ( v & 0x01 ) ? max_z : min_z );
+		XGVECTOR4 test_mid;
+		
+		XGVec3Transform( &test_mid, &test_in, &NxXbox::EngineGlobals.view_matrix );
+
+		// Do z-checking here.
+		if( -test_mid.z < m_switch_distance )
+		{
+			return -test_mid.z;
+		}
+		else if( -test_mid.z < nearest )
+		{
+			nearest = -test_mid.z;
+		}
+	}
+	return nearest;
+}
+
+
+
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_NxImposter.h b/Code/Gfx/XBox/p_NxImposter.h
new file mode 100644
index 0000000..ec72b1e
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxImposter.h
@@ -0,0 +1,62 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.h
+
+#ifndef	__GFX_P_NX_IMPOSTER_H__
+#define	__GFX_P_NX_IMPOSTER_H__
+
+#include 	"gfx/NxImposter.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sImposterPolyVert
+{
+	float		x, y, z;
+	float		u, v;
+};
+	
+	
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+struct sRemovedTextureDetails
+{
+	IDirect3DTexture8*	mp_texture;
+	int					m_time_removed;
+};
+
+
+
+// Here's a machine specific implementation of the CImposterGroup
+class CXboxImposterGroup : public CImposterGroup
+{
+	public:
+												CXboxImposterGroup();
+	virtual										~CXboxImposterGroup();
+
+
+	private:									// It's all private, as it is machine specific
+
+	virtual void								plat_create_imposter_polygon( void );
+	virtual void								plat_remove_imposter_polygon( void );
+	virtual bool								plat_update_imposter_polygon( void );
+	virtual void								plat_draw_imposter_polygon( void );
+	virtual float								plat_check_distance( void );
+	virtual void								plat_process( void );
+
+	void										process_removed_textures( void );
+
+	// Machine specific members
+	sImposterPolyVert							m_vertex_buffer[4];
+	IDirect3DTexture8*							mp_texture;
+	Lst::Head  *		mp_removed_textures_list;
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/XBox/p_NxLight.cpp b/Code/Gfx/XBox/p_NxLight.cpp
new file mode 100644
index 0000000..e45a9e6
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxLight.cpp
@@ -0,0 +1,269 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLight.cpp - Xbox platform specific interface to CModelLights
+
+#include 
+
+#include 
+#include 
+
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxModelLights::CXboxModelLights()
+{
+	m_flags = 0;
+
+	EnableAmbientLight( false );
+	for( int i = 0; i < CLightManager::MAX_LIGHTS; ++i )
+	{
+		EnableDiffuseLight( i, false );
+	}
+	
+	// Set valid default direction.
+	m_diffuse_direction[0].Set( 0.0f, 1.0f, 0.0f );
+	m_diffuse_direction[1].Set( 0.0f, 1.0f, 0.0f );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxModelLights::~CXboxModelLights()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxModelLights::plat_update_brightness()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxModelLights::plat_update_engine( Mth::Vector & pos, bool add_scene_light )
+{
+	if( m_flags & mUSE_MODEL_AMBIENT )
+	{
+		// Use the local ambient color, modulate it with the local ambient brightness.
+		NxXbox::EngineGlobals.ambient_light_color[0] = m_ambient_color.r * ( 1.0f / 128.0f ) * m_ambient_brightness;
+		NxXbox::EngineGlobals.ambient_light_color[1] = m_ambient_color.g * ( 1.0f / 128.0f ) * m_ambient_brightness;
+		NxXbox::EngineGlobals.ambient_light_color[2] = m_ambient_color.b * ( 1.0f / 128.0f ) * m_ambient_brightness;
+	}
+	else
+	{
+		// Use the default ambient color, but modulate it with the local ambient brightness.
+		Image::RGBA	amb = CLightManager::sGetLightAmbientColor();
+		NxXbox::EngineGlobals.ambient_light_color[0] = amb.r * ( 1.0f / 128.0f ) * m_ambient_brightness;
+		NxXbox::EngineGlobals.ambient_light_color[1] = amb.g * ( 1.0f / 128.0f ) * m_ambient_brightness;
+		NxXbox::EngineGlobals.ambient_light_color[2] = amb.b * ( 1.0f / 128.0f ) * m_ambient_brightness;
+	}
+
+	for( int i = 0; i < 2; ++i )
+	{
+		if( m_flags & (( i == 0 ) ? mUSE_MODEL_DIFFUSE_0 : mUSE_MODEL_DIFFUSE_1 ))
+		{
+			// Use the local directional color, modulate it with the local directional brightness.
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 4] = m_diffuse_color[i].r * ( 1.0f / 128.0f ) * m_diffuse_brightness[i];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 5] = m_diffuse_color[i].g * ( 1.0f / 128.0f ) * m_diffuse_brightness[i];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 6] = m_diffuse_color[i].b * ( 1.0f / 128.0f ) * m_diffuse_brightness[i];
+
+			// Use the local direction.
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 0] = -m_diffuse_direction[i][X];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 1] = -m_diffuse_direction[i][Y];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 2] = -m_diffuse_direction[i][Z];
+		}
+		else
+		{
+			// Use the default directional color, but modulate it with the local directional brightness.
+			Image::RGBA	dif = CLightManager::sGetLightDiffuseColor( i );
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 4] = dif.r * ( 1.0f / 128.0f ) * m_diffuse_brightness[i];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 5] = dif.g * ( 1.0f / 128.0f ) * m_diffuse_brightness[i];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 6] = dif.b * ( 1.0f / 128.0f ) * m_diffuse_brightness[i];
+
+			// Use the default direction.
+			Mth::Vector dir = CLightManager::sGetLightDirection( i );
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 0] = -dir[X];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 1] = -dir[Y];
+			NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 2] = -dir[Z];
+		}
+	}
+
+	// Figure Scene Lighting if required.
+	if( add_scene_light )
+	{
+		Nx::CSceneLight *p_scene_light = CLightManager::sGetOptimumSceneLight( pos );
+		if( p_scene_light )
+		{
+			Mth::Vector light_pos = p_scene_light->GetLightPosition();
+
+			float dist	= Mth::Distance( pos, light_pos );
+			float ratio	= dist * p_scene_light->GetLightReciprocalRadius();
+
+			light_pos = ( pos - light_pos ).Normalize();
+
+			// Figure the direction...
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 0] = light_pos[X];
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 1] = light_pos[Y];
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 2] = light_pos[Z];
+
+			// ...and the color.
+			ratio															= sqrtf( 1.0f - ratio ) * ( 1.0f / 255.0f ) * p_scene_light->GetLightIntensity();
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 4]	= ratio * p_scene_light->GetLightColor().r;
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 5]	= ratio * p_scene_light->GetLightColor().g;
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 6]	= ratio * p_scene_light->GetLightColor().b;
+		}
+		else
+		{
+			// Disbale this light by setting zero color.
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 4] = 0.0f;
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 5] = 0.0f;
+			NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 6] = 0.0f;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxModelLights::plat_set_light_ambient_color( const Image::RGBA &rgba )
+{
+	m_ambient_color = rgba;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CXboxModelLights::plat_get_light_ambient_color() const
+{
+	return m_ambient_color;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxModelLights::plat_set_light_direction( int light_index, const Mth::Vector &direction )
+{
+	m_diffuse_direction[light_index] = direction;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector &	CXboxModelLights::plat_get_light_direction( int light_index ) const
+{
+	if( plat_is_diffuse_light_enabled( light_index ))
+		return m_diffuse_direction[light_index];
+	else
+		return Nx::CLightManager::sGetLightDirection( light_index );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxModelLights::plat_set_light_diffuse_color( int light_index, const Image::RGBA &rgba )
+{
+	m_diffuse_color[light_index] = rgba;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CXboxModelLights::plat_get_light_diffuse_color( int light_index ) const
+{
+	return m_diffuse_color[light_index];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxModelLights::plat_enable_ambient_light( bool enable )
+{
+	if( enable )
+		m_flags |= mUSE_MODEL_AMBIENT;
+	else
+		m_flags &= ~mUSE_MODEL_AMBIENT;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxModelLights::plat_enable_diffuse_light( int light_index, bool enable )
+{
+	if( enable )
+		m_flags |= ( light_index == 0 ) ? mUSE_MODEL_DIFFUSE_0 : mUSE_MODEL_DIFFUSE_1;
+	else
+		m_flags &= ~(( light_index == 0 ) ? mUSE_MODEL_DIFFUSE_0 : mUSE_MODEL_DIFFUSE_1 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxModelLights::plat_is_ambient_light_enabled() const
+{
+	return ( m_flags & mUSE_MODEL_AMBIENT ) > 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxModelLights::plat_is_diffuse_light_enabled( int light_index ) const
+{
+	return (( light_index == 0 ) ? (( m_flags & mUSE_MODEL_DIFFUSE_0 ) > 0 ) : (( m_flags & mUSE_MODEL_DIFFUSE_1 ) > 0 ));
+}
+
+} 
+ 
diff --git a/Code/Gfx/XBox/p_NxLight.h b/Code/Gfx/XBox/p_NxLight.h
new file mode 100644
index 0000000..64ffb38
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxLight.h
@@ -0,0 +1,55 @@
+///////////////////////////////////////////////////////////////////////////////////
+// p_NxLight.H - Neversoft Engine, Rendering portion, Platform dependent interface
+
+#ifndef	__GFX_P_NX_LIGHT_H__
+#define	__GFX_P_NX_LIGHT_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+///////////////////////////////////////////////////////////////////////////////////
+// Nx::CXboxModelLights
+class	CXboxModelLights : public CModelLights
+{
+public:
+								CXboxModelLights();
+	virtual						~CXboxModelLights();
+
+private:	
+
+	// Platform-specific calls
+	virtual void				plat_update_engine( Mth::Vector & pos, bool add_scene_light );
+	virtual bool				plat_set_light_ambient_color(const Image::RGBA &rgba);
+	virtual bool				plat_set_light_direction(int light_index, const Mth::Vector &direction);
+	virtual bool				plat_set_light_diffuse_color(int light_index, const Image::RGBA &rgba);
+	virtual Image::RGBA			plat_get_light_ambient_color() const;
+	virtual const Mth::Vector &	plat_get_light_direction(int light_index) const;
+	virtual Image::RGBA			plat_get_light_diffuse_color(int light_index) const;
+
+	virtual void				plat_update_brightness();
+
+	virtual void				plat_enable_ambient_light(bool enable);
+	virtual void				plat_enable_diffuse_light(int light_index, bool enable);
+	virtual bool				plat_is_ambient_light_enabled() const;
+	virtual bool				plat_is_diffuse_light_enabled(int light_index) const;
+
+	uint32						m_flags;
+	Image::RGBA					m_ambient_color;
+	Image::RGBA					m_diffuse_color[CLightManager::MAX_LIGHTS];
+	Mth::Vector					m_diffuse_direction[CLightManager::MAX_LIGHTS];
+};
+
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/XBox/p_NxLightMan.cpp b/Code/Gfx/XBox/p_NxLightMan.cpp
new file mode 100644
index 0000000..8de8565
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxLightMan.cpp
@@ -0,0 +1,186 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLightMan.cpp - Xbox platform specific interface to CLightManager
+
+#include 
+#include "gfx/NxLightMan.h"
+#include "gfx/xbox/p_NxLight.h"
+#include "gfx/xbox/nx/nx_init.h"
+
+namespace Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_plat_update_engine( void )
+{
+	NxXbox::EngineGlobals.ambient_light_color[0] = s_world_lights.m_light_ambient_rgba.r * ( 1.0f / 128.0f ) * s_ambient_brightness;
+	NxXbox::EngineGlobals.ambient_light_color[1] = s_world_lights.m_light_ambient_rgba.g * ( 1.0f / 128.0f ) * s_ambient_brightness;
+	NxXbox::EngineGlobals.ambient_light_color[2] = s_world_lights.m_light_ambient_rgba.b * ( 1.0f / 128.0f ) * s_ambient_brightness;
+
+	for( int i = 0; i < 2; ++i )
+	{
+		Image::RGBA	dif = CLightManager::sGetLightDiffuseColor( i );
+		NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 4] = dif.r * ( 1.0f / 128.0f ) * s_diffuse_brightness[i];
+		NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 5] = dif.g * ( 1.0f / 128.0f ) * s_diffuse_brightness[i];
+		NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 6] = dif.b * ( 1.0f / 128.0f ) * s_diffuse_brightness[i];
+
+		Mth::Vector dir = CLightManager::sGetLightDirection( i );
+		NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 0] = -dir[X];
+		NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 1] = -dir[Y];
+		NxXbox::EngineGlobals.directional_light_color[( i * 8 ) + 2] = -dir[Z];
+	}
+
+	// Set third color to black.
+	NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 4] = 0.0f;
+	NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 5] = 0.0f;
+	NxXbox::EngineGlobals.directional_light_color[( 2 * 8 ) + 6] = 0.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_plat_update_lights( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_set_light_ambient_color( void )
+{
+	NxXbox::EngineGlobals.ambient_light_color[0] = s_world_lights.m_light_ambient_rgba.r * ( 1.0f / 128.0f ) * s_ambient_brightness;
+	NxXbox::EngineGlobals.ambient_light_color[1] = s_world_lights.m_light_ambient_rgba.g * ( 1.0f / 128.0f ) * s_ambient_brightness;
+	NxXbox::EngineGlobals.ambient_light_color[2] = s_world_lights.m_light_ambient_rgba.b * ( 1.0f / 128.0f ) * s_ambient_brightness;
+	
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CLightManager::s_plat_get_light_ambient_color()
+{
+	return s_world_lights.m_light_ambient_rgba;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_set_light_direction( int light_index )
+{
+	int array_index = ( light_index * 8 );
+	
+	NxXbox::EngineGlobals.directional_light_color[array_index]		= -s_world_lights.m_light_direction[light_index][X];
+	NxXbox::EngineGlobals.directional_light_color[array_index + 1]	= -s_world_lights.m_light_direction[light_index][Y];
+	NxXbox::EngineGlobals.directional_light_color[array_index + 2]	= -s_world_lights.m_light_direction[light_index][Z];
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector & CLightManager::s_plat_get_light_direction( int light_index )
+{
+	static Mth::Vector dir;
+	dir.Set( s_world_lights.m_light_direction[light_index][X], s_world_lights.m_light_direction[light_index][Y], s_world_lights.m_light_direction[light_index][Z] );
+	return dir;
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_set_light_diffuse_color( int light_index )
+{
+	int array_index = ( light_index * 8 ) + 4;
+	
+	NxXbox::EngineGlobals.directional_light_color[array_index]		= s_world_lights.m_light_diffuse_rgba[light_index].r * ( 1.0f / 128.0f ) * s_diffuse_brightness[light_index];
+	NxXbox::EngineGlobals.directional_light_color[array_index + 1]	= s_world_lights.m_light_diffuse_rgba[light_index].g * ( 1.0f / 128.0f ) * s_diffuse_brightness[light_index];
+	NxXbox::EngineGlobals.directional_light_color[array_index + 2]	= s_world_lights.m_light_diffuse_rgba[light_index].b * ( 1.0f / 128.0f ) * s_diffuse_brightness[light_index];
+	
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Image::RGBA	CLightManager::s_plat_get_light_diffuse_color( int light_index )
+{
+	return s_world_lights.m_light_diffuse_rgba[light_index];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLightManager::s_plat_update_colors( void )
+{
+	s_plat_set_light_ambient_color();
+	for( int i = 0; i < MAX_LIGHTS; ++i )
+	{
+		s_plat_set_light_diffuse_color( i );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CModelLights *CLightManager::s_plat_create_model_lights()
+{
+	return new CXboxModelLights;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CLightManager::s_plat_free_model_lights( CModelLights *p_model_lights )
+{
+	Dbg_Assert( p_model_lights );
+
+	delete p_model_lights;
+
+	return true;
+}
+
+
+
+} 
+ 
diff --git a/Code/Gfx/XBox/p_NxLoadScreen.cpp b/Code/Gfx/XBox/p_NxLoadScreen.cpp
new file mode 100644
index 0000000..8923f0c
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxLoadScreen.cpp
@@ -0,0 +1,416 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxLoadScreen.cpp - Xbox platform specific interface for the load screen
+//
+//
+
+#include	"gfx\Nx.h"
+#include	"gfx\NxLoadScreen.h"
+#include	"gfx\xbox\p_NxTexture.h"
+#include	"gfx\xbox\p_NxSprite.h"
+#include	"gfx\xbox\NX\sprite.h"
+#include	"gfx\xbox\NX\scene.h"
+#include	"gfx\xbox\NX\render.h"
+#include	"sys\config\config.h"
+
+#include	"core\macros.h"
+
+namespace	Nx
+{
+
+
+Nx::CXboxTexture	*sp_load_screen_texture;
+Nx::CXboxSprite	*sp_load_screen_sprite;
+
+static float		loadingBarTotalSeconds;
+static float		loadingBarCurrentSeconds;
+static float		loadingBarDeltaSeconds;
+static int			loadingBarStartColor[3];		// r,g,b
+static int			loadingBarEndColor[3];			// r,g,b
+static uint32		loadingBarColors[1280][3];		// r,g,b
+static bool			loadingBarColorsSet = false;
+static int			loadingBarWidth;
+static uint32		loadingBarBorderColor;
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+#define USE_SPRITES 0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CALLBACK loadingBarTimerCallback( UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2 )
+{
+	if( NxXbox::EngineGlobals.loadingbar_timer_event != 0 )
+	{
+		loadingBarCurrentSeconds += loadingBarDeltaSeconds;
+		float mult			= loadingBarCurrentSeconds / loadingBarTotalSeconds;
+		mult				= ( mult > 1.0f ) ? 1.0f : mult;
+		int bar_width		= (int)( loadingBarWidth * mult );
+		
+		// Get pointer to front buffer memory.
+		IDirect3DSurface8	*p_buffer;
+		D3DLOCKED_RECT		locked_rect;
+		NxXbox::EngineGlobals.p_Device->GetBackBuffer( -1, D3DBACKBUFFER_TYPE_MONO, &p_buffer );
+		p_buffer->LockRect( &locked_rect, NULL, D3DLOCK_TILED );
+		uint32 *p_screen = (uint32*)locked_rect.pBits;
+
+		// ----------------------------------
+		// | ||||||||||||||||||||||          |
+		// ----------------------------------
+		// ^ ^                    ^          ^
+		// a b                    c			 d
+		// 
+		// a = surround start
+		// b = bar start
+		// c = bar end
+		// d = surround end
+		int bar_start			= ( 640 - loadingBarWidth ) / 2;
+		int surround_start		= bar_start - 5;
+		int bar_end				= bar_start + bar_width;
+		int surround_end		= surround_start + loadingBarWidth + 10;
+
+		const int HDTV_OFFSET	= 48;	
+		
+		bar_start				= (int)SCREEN_CONV_X( bar_start );
+		surround_start			= (int)SCREEN_CONV_X( surround_start );
+		bar_end					= (int)SCREEN_CONV_X( bar_end );
+		surround_end			= (int)SCREEN_CONV_X( surround_end );
+		
+		if( NxXbox::EngineGlobals.backbuffer_width > 640 )
+		{
+			bar_start			+= HDTV_OFFSET;
+			surround_start		+= HDTV_OFFSET;
+			bar_end				+= HDTV_OFFSET;
+			surround_end		+= HDTV_OFFSET;
+		}
+		
+		int base_y				= (int)SCREEN_CONV_Y( 410 );
+		
+		for( int i = 0; i < 20; ++i )
+		{
+			uint32 *p_loop = p_screen + (( base_y + i ) * NxXbox::EngineGlobals.backbuffer_width );
+
+			if(( i < 5 ) || ( i >= 15 ))
+			{
+				for( int j = surround_start; j < surround_end; ++j )
+					p_loop[j] = loadingBarBorderColor;
+			}
+			else
+			{
+				for( int j = surround_start; j < ( surround_start + 5 ); ++j )
+					p_loop[j] = loadingBarBorderColor;
+
+				for( int j = bar_end; j < surround_end; ++j )
+					p_loop[j] = loadingBarBorderColor;
+
+				for( int j = bar_start; j < bar_end; ++j )			
+				{
+					uint32 idx			= ( j - bar_start >= 1279 ) ? 1279 : ( j - bar_start );
+					uint32 write_value	= 0x80000000UL | ( loadingBarColors[idx][0] << 16 ) | ( loadingBarColors[idx][1] << 8 ) | ( loadingBarColors[idx][2] << 0 );
+					p_loop[j]			= write_value;
+				}
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool is_power_of_two( uint32 a )
+{
+	if( a == 0 )
+	{
+		return false;
+	}
+	return (( a & ( a - 1 )) == 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLoadScreen::s_plat_display(const char* filename, bool just_freeze, bool blank)
+{
+	// Wait for asyncronous rendering to finish.
+	NxXbox::EngineGlobals.p_Device->BlockUntilIdle();
+
+	if( !just_freeze )
+	{
+		if( blank )
+		{
+			D3DDevice_Clear( 0, NULL, D3DCLEAR_TARGET, 0x00000000UL, 1.0f, 0 );
+			D3DDevice_Swap( D3DSWAP_DEFAULT );
+		}
+		else
+		{
+			// Engine stuff
+			Dbg_Assert(!sp_load_screen_texture);
+			Dbg_Assert(!sp_load_screen_sprite);
+
+			sp_load_screen_texture = new CXboxTexture();
+
+#			ifdef __PAL_BUILD__
+			switch( Config::GetLanguage())
+			{
+				case Config::LANGUAGE_FRENCH:
+				{
+					char t_filename[200];
+					sprintf( t_filename, "PALImages/FRImages/%s", filename );
+					if( !sp_load_screen_texture->LoadTexture( t_filename, true ))
+					{
+						Dbg_Error( "Can't load texture %s", t_filename );
+						return;
+					}
+					break;
+				}
+				case Config::LANGUAGE_GERMAN:
+				{
+					char t_filename[200];
+					sprintf( t_filename, "PALImages/GRImages/%s", filename );
+					if( !sp_load_screen_texture->LoadTexture( t_filename, true ))
+					{
+						Dbg_Error( "Can't load texture %s", t_filename );
+						return;
+					}
+					break;
+				}
+				default:
+				{
+					if( !sp_load_screen_texture->LoadTexture( filename, true ))
+					{
+						Dbg_Error( "Can't load texture %s", filename );
+						return;
+					}
+					break;
+				}
+			}
+#			else
+			if( !sp_load_screen_texture->LoadTexture( filename, true ))
+			{
+				Dbg_Error( "Can't load texture %s", filename );
+				return;
+			}
+#			endif // __PAL_BUILD__
+
+			// Copy into frame buffer.
+			float x_offset		= 0.0f;
+			float y_offset		= 0.0f;
+			float x_scale		= 1.0f;
+			float y_scale		= 1.0f;
+			float alpha_level	= 1.0f;
+	
+			D3DDevice_SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
+			D3DDevice_SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );
+			D3DDevice_SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
+			D3DDevice_SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
+
+			// Store the stage zero minfilter, since it may be anisotropic.
+			DWORD	stage_zero_minfilter;
+			D3DDevice_GetTextureStageState( 0, D3DTSS_MINFILTER, &stage_zero_minfilter );
+
+			// Turn on texture filtering when scaling...
+			D3DDevice_SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );
+			D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+
+			// Turn on clamping so that the linear textures work
+			NxXbox::set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+
+			D3DDevice_SetRenderState( D3DRS_LIGHTING,				FALSE );
+	
+			// Use a default vertex shader
+			NxXbox::set_pixel_shader( 0 );
+			NxXbox::set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_TEX1 );
+			NxXbox::set_blend_mode( NxXbox::vBLEND_MODE_DIFFUSE );
+
+			// Select the texture (flush first, since the screen texture is linear).
+			NxXbox::set_texture( 0, NULL );
+			NxXbox::set_texture( 0, sp_load_screen_texture->GetEngineTexture()->pD3DTexture, sp_load_screen_texture->GetEngineTexture()->pD3DPalette );
+
+			// Setup up the vertices.
+			typedef struct
+			{
+				float	sx,sy,sz;
+				float	rhw;
+				float	tu,tv;
+			}
+			LOADSCREEN_VERT;
+	
+			// Get the width height from the texture itself, since we may be using a texture not designed especially
+			// for this screen dimension.
+			float tex_w = (float)sp_load_screen_texture->GetEngineTexture()->ActualWidth;
+			float tex_h = (float)sp_load_screen_texture->GetEngineTexture()->ActualHeight;
+			float scr_w = (float)NxXbox::EngineGlobals.backbuffer_width;
+			float scr_h = (float)NxXbox::EngineGlobals.backbuffer_height;
+
+			LOADSCREEN_VERT	vertices[4];
+
+			// The texture coordinate addressing will differ depending on whether this is a linear texture or not.
+			if( is_power_of_two( sp_load_screen_texture->GetEngineTexture()->ActualWidth ) &&
+				is_power_of_two( sp_load_screen_texture->GetEngineTexture()->ActualHeight ))
+			{
+				// Not a linear texture, will be swizzled, max uv is (1.0, 1.0).
+				vertices[0].sx		= x_offset;
+				vertices[0].sy		= y_offset;
+				vertices[0].sz		= 0.0f;
+				vertices[0].rhw		= 0.0f;
+				vertices[0].tu		= 0.0f;
+				vertices[0].tv		= 1.0f;
+				vertices[1]			= vertices[0];
+				vertices[1].sy		= y_offset + ( scr_h * y_scale );
+				vertices[1].tv		= 0.0f;
+				vertices[2]			= vertices[0];
+				vertices[2].sx		= x_offset + ( scr_w * x_scale );
+				vertices[2].tu		= 1.0f;
+				vertices[3]			= vertices[2];
+				vertices[3].sy		= vertices[1].sy;
+				vertices[3].tv		= 0.0f;
+			}
+			else
+			{
+				// Linear texture, won't be swizzled, max uv is (tex_w, tex_h).
+				vertices[0].sx		= x_offset;
+				vertices[0].sy		= y_offset;
+				vertices[0].sz		= 0.0f;
+				vertices[0].rhw		= 0.0f;
+				vertices[0].tu		= -0.5f;
+				vertices[0].tv		= tex_h - 0.5f;
+				vertices[1]			= vertices[0];
+				vertices[1].sy		= y_offset + ( scr_h * y_scale );
+				vertices[1].tv		= -0.5f;
+				vertices[2]			= vertices[0];
+				vertices[2].sx		= x_offset + ( scr_w * x_scale );
+				vertices[2].tu		= tex_w - 0.5f;
+				vertices[3]			= vertices[2];
+				vertices[3].sy		= vertices[1].sy;
+				vertices[3].tv		= -0.5f;
+			}
+
+			// Draw the vertices, and make sure they're displayed.
+			D3DDevice_DrawVerticesUP( D3DPT_TRIANGLESTRIP, 4, vertices, sizeof( LOADSCREEN_VERT ));
+			D3DDevice_Swap( D3DSWAP_DEFAULT );
+
+			// Done with texture
+			delete sp_load_screen_texture;
+			sp_load_screen_texture = NULL;
+
+			// Reflush linear texture out.
+			NxXbox::set_texture( 0, NULL );
+
+			// Restore the stage zero minfilter.
+			D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, stage_zero_minfilter );
+		}
+	}
+
+	// Indicate that the loading screen is visible, to stop any more rendering until it is hidden.
+	NxXbox::EngineGlobals.loadingscreen_visible = true;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_hide()
+{
+	// Remove the loading bar.
+	if( NxXbox::EngineGlobals.loadingbar_timer_event != 0 )
+	{
+		timeKillEvent( NxXbox::EngineGlobals.loadingbar_timer_event );
+		NxXbox::EngineGlobals.loadingbar_timer_event = 0;
+	}
+
+	// Indicate that the loading screen is no longer visible.
+	NxXbox::EngineGlobals.loadingscreen_visible = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLoadScreen::s_plat_clear()
+{
+	if( sp_load_screen_sprite )
+	{
+		CEngine::sDestroySprite(sp_load_screen_sprite);
+		sp_load_screen_sprite = NULL;
+	}
+
+	if( sp_load_screen_texture )
+	{
+		delete sp_load_screen_texture;
+		sp_load_screen_texture = NULL;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLoadScreen::s_plat_start_loading_bar( float seconds )
+{
+	loadingBarTotalSeconds		= seconds * 0.6f;
+	loadingBarCurrentSeconds	= 0.0f;
+	loadingBarDeltaSeconds		= 0.03f;	// 30 milliseconds.
+	
+	s_plat_update_bar_properties();
+
+	// Set up the timer event.
+	if( NxXbox::EngineGlobals.loadingbar_timer_event == 0 )
+	{
+		NxXbox::EngineGlobals.loadingbar_timer_event = timeSetEvent( (uint32)( loadingBarDeltaSeconds * 1000.0f ),	// Delay (ms).
+																	 0,												// Ignored resolution (ms).
+																	 loadingBarTimerCallback,						// Callback function.
+																	 0,												// Callback data.
+																	 TIME_PERIODIC | TIME_CALLBACK_FUNCTION );
+		Dbg_Assert( NxXbox::EngineGlobals.loadingbar_timer_event != 0 );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CLoadScreen::s_plat_update_bar_properties( void )
+{
+
+	loadingBarStartColor[0] = s_bar_start_color.r;
+	loadingBarStartColor[1] = s_bar_start_color.g;
+	loadingBarStartColor[2] = s_bar_start_color.b;
+
+	loadingBarEndColor[0]	= s_bar_end_color.r;
+	loadingBarEndColor[1]	= s_bar_end_color.g;
+	loadingBarEndColor[2]	= s_bar_end_color.b;
+
+	loadingBarWidth			= s_bar_width;
+	loadingBarBorderColor	= 0x80000000 | ((uint32)s_bar_border_color.r << 16 ) | ((uint32)s_bar_border_color.g << 8 )  | (uint32)s_bar_border_color.b;
+
+	// Build the interpolated color array.
+	int last_color = (int)( loadingBarWidth * NxXbox::EngineGlobals.screen_conv_x_multiplier );
+	for( int i = 0; i < last_color; ++i )
+	{
+		for( int c = 0; c < 3; ++c )
+		{
+			loadingBarColors[i][c] = loadingBarStartColor[c] + ((( loadingBarEndColor[c] - loadingBarStartColor[c] ) * i ) / last_color );
+		}
+	}
+}
+
+
+} 
+ 
diff --git a/Code/Gfx/XBox/p_NxMesh.cpp b/Code/Gfx/XBox/p_NxMesh.cpp
new file mode 100644
index 0000000..9e09157
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxMesh.cpp
@@ -0,0 +1,274 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxMesh.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+//extern Nx::CXboxScene *p_skater;
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int cmp( const void *p1, const void *p2 )
+{
+	NxXbox::sCASData *p_casdata1 = (NxXbox::sCASData*)p1;
+	NxXbox::sCASData *p_casdata2 = (NxXbox::sCASData*)p2;
+	
+	uint32 mesh1 = p_casdata1->data0 >> 16;
+	uint32 mesh2 = p_casdata2->data0 >> 16;
+	
+	if( mesh1 > mesh2 )
+	{
+		return 1;
+	}
+	else if( mesh1 < mesh2 )
+	{
+		return -1;
+	}
+	else
+	{
+		uint32 indexzero1 = p_casdata1->data0 & 0xFFFF;
+		uint32 indexzero2 = p_casdata2->data0 & 0xFFFF;
+		if( indexzero1 > indexzero2 )
+		{
+			return 1;
+		}
+		else if( indexzero1 < indexzero2 )
+		{
+			return -1;
+		}
+	}
+	return 0;
+}
+
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxMesh::build_casdata_table( const char* pFileName )
+{
+	void *pFile = File::Open( pFileName, "rb" );
+	Dbg_MsgAssert( pFile, ( "Couldn't open CAS data file %s\n", pFileName ));
+
+	uint32 version;
+	File::Read( &version, sizeof( uint32 ), 1, pFile );
+
+	if( version >= 2 )
+	{
+		File::Read( &m_CASRemovalMask, sizeof( uint32 ), 1, pFile );
+	}
+	
+	File::Read( &m_numCASData, sizeof( int ), 1, pFile );
+
+	if( m_numCASData > 0 )
+	{
+		// CAS flags.
+		mp_CASData = new NxXbox::sCASData[m_numCASData];
+
+		for( uint32 i = 0; i < m_numCASData; ++i )
+		{
+			File::Read( &mp_CASData[i].mask, sizeof( uint32 ) * 3, 1, pFile );
+		}
+
+		// Sort the CAS data based first on mesh, then on the first tri index, lowest to highest. This allows some efficient early-out checking
+		// during the poly removal.
+		qsort( mp_CASData, m_numCASData, sizeof( NxXbox::sCASData ), cmp );
+	}
+
+	File::Close( pFile );
+	
+	return ( m_numCASData > 0 );
+}
+
+
+
+#define MemoryRead( dst, size, num, src )	CopyMemory(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxMesh::build_casdata_table_from_memory( void **pp_mem )
+{
+	uint8 *p_data = (uint8*)( *pp_mem );
+
+	uint32 version;
+	MemoryRead( &version, sizeof( uint32 ), 1, p_data );
+
+	if( version >= 2 )
+	{
+		MemoryRead( &m_CASRemovalMask, sizeof( uint32 ), 1, p_data );
+	}
+	
+	MemoryRead( &m_numCASData, sizeof( int ), 1, p_data );
+
+	if( m_numCASData > 0 )
+	{
+		// CAS flags.
+		mp_CASData = new NxXbox::sCASData[m_numCASData];
+
+		for( uint32 i = 0; i < m_numCASData; ++i )
+		{
+			MemoryRead( &mp_CASData[i].mask, sizeof( uint32 ) * 3, 1, p_data );
+		}
+
+		// Sort the CAS data based first on mesh, then on the first tri index, lowest to highest. This allows some efficient early-out checking
+		// during the poly removal.
+		qsort( mp_CASData, m_numCASData, sizeof( NxXbox::sCASData ), cmp );
+	}
+	
+	// Set the data pointer to the new position on return.
+	*pp_mem = p_data;
+
+	return ( m_numCASData > 0 );
+}
+
+
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxMesh::CXboxMesh( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxMesh::CXboxMesh( const char *pMeshFileName )
+{
+	// Only do the CAS flag building for skinned objects.
+	if( strstr( pMeshFileName, "skin" ))
+	{
+		char CASFileName[256];
+		strcpy( CASFileName, pMeshFileName );
+		Str::LowerCase( CASFileName );
+		char *pExt = strstr( CASFileName, "skin.xbx" );
+		if( pExt )
+		{
+			Dbg_MsgAssert( pExt, ( "Couldn't find skin.xbx extension in %s", CASFileName ));
+			strcpy( pExt, "cas.xbx" );
+			build_casdata_table( CASFileName );
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxMesh::~CXboxMesh( void )
+{
+	if( mp_CASData )
+	{
+		delete [] mp_CASData;
+	}
+	if( mp_scene )
+	{
+		CEngine::sUnloadScene( mp_scene );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxMesh::SetCASData( uint8 *p_cas_data )
+{
+	if( p_cas_data )
+	{
+		build_casdata_table_from_memory((void**)&p_cas_data );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxMesh::SetScene( CXboxScene *p_scene )
+{
+	mp_scene = p_scene;
+
+	// Copy the hierarchy info from the scene so that above the p-line stuff can access it.
+	mp_hierarchyObjects		= mp_scene->GetEngineScene()->mp_hierarchyObjects;
+	m_numHierarchyObjects	= mp_scene->GetEngineScene()->m_numHierarchyObjects;
+
+	// Now that we have a scene attached, resolve any cas flag data into specific indexes in the mesh data.
+	if( mp_CASData )
+	{
+		NxXbox::sCASData *p_cas_entry = mp_CASData;
+		for( uint32 entry = 0; entry < m_numCASData; ++entry, ++p_cas_entry )
+		{
+			// Get the mesh this entry references.
+			uint32			load_order	= p_cas_entry->data0 >> 16;
+			NxXbox::sMesh	*p_mesh		= p_scene->GetEngineScene()->GetMeshByLoadOrder( load_order );
+			if( p_mesh )
+			{
+				// Get the indices of the poly referenced.
+				uint32 i0 = p_cas_entry->data0 & 0xFFFF;
+				uint32 i1 = p_cas_entry->data1 >> 16;
+				uint32 i2 = p_cas_entry->data1 & 0xFFFF;
+
+				// For every vertex index in this mesh...
+				uint16 *p_indices	= p_mesh->mp_index_buffer[0];
+				uint32 index0		= p_indices[0] & 0x7FFF;
+				uint32 index1		= p_indices[1] & 0x7FFF;
+				for( uint32 i = 2; i < p_mesh->m_num_indices[0]; ++i )
+				{
+					uint32 index2 = p_indices[i] & 0x7FFF;
+					if(( index0 == i0 ) && ( index1 == i1 ) && ( index2 == i2 ))
+					{
+						p_cas_entry->start_index = i - 2;
+						break;
+					}
+					index0	= index1;
+					index1	= index2;
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+				
diff --git a/Code/Gfx/XBox/p_NxMesh.h b/Code/Gfx/XBox/p_NxMesh.h
new file mode 100644
index 0000000..b17f5aa
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxMesh.h
@@ -0,0 +1,64 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxMesh.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/15/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_MESH_H__
+#define	__GFX_P_NX_MESH_H__
+    
+#include "gfx/nxmesh.h"
+#include "p_nxscene.h"
+
+namespace NxXbox
+{
+	struct sScene;
+}
+			 
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CMesh
+    
+class CXboxMesh : public CMesh
+{
+                                      
+public:
+						CXboxMesh( void );
+						CXboxMesh( const char *pMeshFileName );
+	virtual 			~CXboxMesh();
+	void				SetScene( CXboxScene *p_scene );
+	void				SetTexDict( Nx::CTexDict *p_tex_dict )	{ mp_texDict = p_tex_dict; }
+	void				SetCASData( uint8 *p_cas_data );
+	CXboxScene			*GetScene( void )						{ return mp_scene; }
+
+	NxXbox::sCASData	*GetCASData( void )						{ return mp_CASData; }
+	uint32				GetNumCASData( void )					{ return m_numCASData; }
+
+protected:
+	bool				build_casdata_table(const char* pFileName);
+	bool				build_casdata_table_from_memory( void **pp_mem );
+
+	NxXbox::sCASData	*mp_CASData;
+	uint32				m_numCASData;
+
+private:
+	CXboxScene			*mp_scene;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
diff --git a/Code/Gfx/XBox/p_NxModel.cpp b/Code/Gfx/XBox/p_NxModel.cpp
new file mode 100644
index 0000000..870481e
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxModel.cpp
@@ -0,0 +1,317 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxModel.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/21/2002
+//****************************************************************************
+
+#include 
+
+#include "gfx/nxmodel.h"
+#include "gfx/skeleton.h"
+#include 
+#include "gfx/xbox/p_nxmodel.h"
+#include "gfx/xbox/p_nxscene.h"
+#include "gfx/xbox/p_nxgeom.h"
+#include "gfx/xbox/nx/texture.h"
+#include "gfx/xbox/nx/render.h"
+			   
+#include 
+#include 
+
+int			test_num_bones			= 0;
+Mth::Matrix	*p_test_bone_matrices	= NULL;
+Mth::Matrix	test_root_matrix;
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxModel::plat_init_skeleton( int num_bones )
+{
+//	if ( !mp_instance ) return false;
+//	Mth::Matrix * p_bone = new Mth::Matrix[numBones];
+//
+//	mp_instance->SetBoneTransforms( p_bone );
+//	for ( int i = 0; i < numBones; i++ )
+//	{
+//		p_bone[i].Identity();
+//	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/*
+	bool CXboxModel::plat_load_file(const char* p_fileName)
+{
+	// Machine specific code here ............
+	
+	// TODO:  Make this more generalized
+
+	// Load in the texture dictionary for the model.
+	Lst::HashTable< NxXbox::sTexture > *p_texture_table = NxXbox::LoadTextureFile( "models/testskin/testskin.tex.xbx" );
+
+    return true;
+}
+*/
+
+
+/*
+bool CXboxModel::plat_load_mesh( CMesh* pMesh )
+{
+	// The skeleton must exist by this point (unless it's a hacked up car).
+	int numBones;
+	numBones = mp_skeleton ? mp_skeleton->GetNumBones() : 1;
+	
+	Mth::Matrix temp;
+	CXboxMesh *p_xbox_mesh = static_cast( pMesh );
+	mp_instance = new NxXbox::CInstance( p_xbox_mesh->GetScene()->GetEngineScene(), temp, numBones, mp_boneTransforms );
+
+    return true;
+}
+*/	
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_unload_mesh( void )
+{
+	if ( mp_instance != NULL )
+	{
+		delete mp_instance;
+		mp_instance = NULL;
+	}
+
+	return true;
+}
+*/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_set_render_mode(ERenderMode mode)
+{
+	// Machine specific code here ............
+    return true;
+}
+*/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_set_color(uint8 r, uint8 g, uint8 b, uint8 a)
+{
+	// Machine specific code here ............
+    return true;
+}
+*/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_set_visibility(uint32 mask)
+{
+    return true;
+}
+*/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_set_active( bool active )
+{
+	if( mp_instance )
+	{
+		mp_instance->SetActive( active );
+	}
+	return true;
+}
+*/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_set_scale(float scaleFactor)
+{
+	// Machine specific code here ............
+    return true;
+}
+*/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_replace_texture(char* p_srcFileName, char* p_dstFileName)
+{
+	// Machine specific code here ............
+    return true;
+}
+*/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CXboxModel::plat_render( Mth::Matrix *pRootMatrix, Mth::Matrix *pBoneMatrices, int numBones )
+{
+	if( mp_instance )
+	{
+		mp_instance->SetTransform( *pRootMatrix );
+	}
+    return true;
+}
+*/
+
+
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxModel::CXboxModel()
+{
+	mp_instance = NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxModel::~CXboxModel()
+{
+	// make sure it's deleted
+//	plat_unload_mesh();
+
+	if( mp_instance && mp_instance->GetBoneTransforms())
+	{
+		delete mp_instance->GetBoneTransforms();
+		mp_instance->SetBoneTransforms( NULL );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxModel::plat_set_bounding_sphere( const Mth::Vector& boundingSphere )
+{
+	// Loop over all spheres.
+	for( int i = 0; i < m_numGeoms; i++ )
+	{
+		mp_geom[i]->SetBoundingSphere( boundingSphere );
+	}
+}
+
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Mth::Vector CXboxModel::plat_get_bounding_sphere( void )
+{
+	Mth::Vector sphere, sphere1, sum, diff;
+	float dist;
+
+	// This should probably never happen.
+	if( m_numGeoms == 0 )
+		return Mth::Vector( 0.0f, 0.0f, 0.0f, 0.0f );
+
+	// Combine the spheres of all geoms (this should really be done once at load time).
+
+	// Start with first sphere.
+	sphere = mp_geom[0]->GetBoundingSphere();
+
+	// Loop over remaining spheres, expanding as necessary.
+	for( int i = 1; i < m_numGeoms; ++i )
+	{
+		// Get next sphere.
+		sphere1 = mp_geom[i]->GetBoundingSphere();
+
+		// Centre-to-centre vector, and distance.
+		diff	= sphere1-sphere;
+		dist	= diff.Length();
+
+		// Test for sphere1 inside sphere.
+		if( dist + sphere1[3] <= sphere[3] )
+			continue;			// keep sphere
+
+		// Test for sphere inside sphere1.
+		if( dist + sphere[3] <= sphere1[3] )
+		{
+			sphere = sphere1;	// replace sphere
+			continue;
+		}
+
+		// Otherwise make a larger sphere that contains both.
+		sum			= sphere+sphere1;
+		sphere		= 0.5f * ( sum + ( diff[3] / dist ) * diff );
+		sphere[3]	= 0.5f * ( dist + sum[3] );
+	}
+
+	sphere[3] *= 2.0f;
+	
+	return sphere;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // Nx
+
+				
diff --git a/Code/Gfx/XBox/p_NxModel.h b/Code/Gfx/XBox/p_NxModel.h
new file mode 100644
index 0000000..deda280
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxModel.h
@@ -0,0 +1,50 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxModel.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  1/8/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_MODEL_H__
+#define	__GFX_P_NX_MODEL_H__
+    
+#include "gfx/nxmodel.h"
+#include "gfx/xbox/nx/instance.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CModel
+    
+class CXboxModel : public CModel
+{
+public:
+						CXboxModel();
+	virtual 			~CXboxModel();
+	NxXbox::CInstance	*GetInstance( void )								{ return mp_instance; }
+	void				SetInstance( NxXbox::CInstance *p_instance )		{ mp_instance = p_instance; }
+
+private:				// It's all private, as it is machine specific
+	virtual Mth::Vector	plat_get_bounding_sphere( void );
+	virtual void		plat_set_bounding_sphere( const Mth::Vector& boundingSphere );
+
+	bool				plat_init_skeleton( int num_bones );
+
+	NxXbox::CInstance	*mp_instance;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
diff --git a/Code/Gfx/XBox/p_NxParticleRibbonTrail.cpp b/Code/Gfx/XBox/p_NxParticleRibbonTrail.cpp
new file mode 100644
index 0000000..0b88089
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxParticleRibbonTrail.cpp
@@ -0,0 +1,361 @@
+#include 
+#include 
+
+#include "gfx/xbox/p_nxparticleRibbonTrail.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleRibbonTrail::CXboxParticleRibbonTrail()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleRibbonTrail::CXboxParticleRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum		= checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+	m_mid_time		= -1.0f;
+	m_history		= history;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_RIBBONTRAIL, texture_checksum, blendmode_checksum, fix, num_segments, history );
+
+	// Default color.
+	m_start_color = new Image::RGBA[m_num_vertex_buffers];
+	m_mid_color = new Image::RGBA[m_num_vertex_buffers];
+	m_end_color = new Image::RGBA[m_num_vertex_buffers];
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 255;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleRibbonTrail::~CXboxParticleRibbonTrail()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+	delete [] m_start_color;
+	delete [] m_mid_color;
+	delete [] m_end_color;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbonTrail::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbonTrail::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbonTrail::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleRibbonTrail::plat_get_num_particle_colors( void ) { return m_num_vertex_buffers; }
+int CXboxParticleRibbonTrail::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CXboxParticleRibbonTrail::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleRibbonTrail::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleRibbonTrail::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleRibbonTrail::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleRibbonTrail::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleRibbonTrail::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleRibbonTrail::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleRibbonTrail::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleRibbonTrail::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleRibbonTrail::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleRibbonTrail::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleRibbonTrail::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbonTrail::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+
+		Image::RGBA color[2];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 16 * ( m_num_vertex_buffers - 1 );
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_QUADLIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices[0];
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp = p_particle->m_time / p_particle->m_life;
+
+				Mth::Vector	pos[2];
+				p_v = &mp_vertices[0][lp*3];
+				pos[0].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				p_v = &mp_vertices[1][lp*3];
+				pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+
+				Mth::Vector	part_vec = pos[1] - pos[0];
+				Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos[0];
+//					max = pos[0];
+//				}
+//				else
+//				{
+//					if( pos[0][X] < min[X] ) min[X] = pos[0][X]; else if( pos[0][X] > max[X] ) max[X] = pos[0][X];
+//					if( pos[0][Y] < min[Y] ) min[Y] = pos[0][Y]; else if( pos[0][Y] > max[Y] ) max[Y] = pos[0][Y];
+//					if( pos[0][Z] < min[Z] ) min[Z] = pos[0][Z]; else if( pos[0][Z] > max[Z] ) max[Z] = pos[0][Z];
+//				}
+
+				float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		
+				Mth::Vector tmp[4];
+
+				if( m_mid_time >= 0.0f )
+				{
+					if ( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+
+				Image::RGBA start = *p_col0++;
+				Image::RGBA end = *p_col1++;
+
+				color[0].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+				color[0].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+				color[0].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+				color[0].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+				tmp[0]		= pos[0] + ( perp_vec * w );
+				tmp[1]		= pos[0] - ( perp_vec * w );
+
+				for( int c = 1; c < m_num_vertex_buffers; c++ )
+				{
+					start = *p_col0++;
+					end = *p_col1++;
+
+					color[1].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[1].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[1].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[1].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+
+					if( c > 1 )
+					{
+						p_v = &mp_vertices[c][lp*3];
+						pos[1].Set( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+						part_vec = pos[1] - pos[0];
+						perp_vec = Mth::CrossProduct( part_vec, at );
+						perp_vec.Normalize();
+					}
+
+					tmp[2]		= pos[1] + ( perp_vec * w );
+					tmp[3]		= pos[1] - ( perp_vec * w );
+
+					p_push[0]	= *((DWORD*)&tmp[0][X] );
+					p_push[1]	= *((DWORD*)&tmp[0][Y] );
+					p_push[2]	= *((DWORD*)&tmp[0][Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[3][X] );
+					p_push[1]	= *((DWORD*)&tmp[3][Y] );
+					p_push[2]	= *((DWORD*)&tmp[3][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+					
+					color[0] = color[1];
+					pos[0] = pos[1];
+					tmp[0] = tmp[2];
+					tmp[1] = tmp[3];
+				}
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/XBox/p_NxParticleRibbonTrail.h b/Code/Gfx/XBox/p_NxParticleRibbonTrail.h
new file mode 100644
index 0000000..9771961
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxParticleRibbonTrail.h
@@ -0,0 +1,76 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLERibbonTrail_H__
+#define	__GFX_P_NX_PARTICLERibbonTrail_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxParticleRibbonTrail : public CParticle
+{
+	public:
+								CXboxParticleRibbonTrail();
+								CXboxParticleRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 					~CXboxParticleRibbonTrail();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+
+	private:					// It's all private, as it is machine specific
+	void						plat_render( void );
+	void						plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void						plat_set_position( int entry, int list, float x, float y, float z );
+	void						plat_add_position( int entry, int list, float x, float y, float z );
+	int							plat_get_num_vertex_lists( void );
+	int							plat_get_num_particle_colors( void );
+
+	void						plat_set_sr( int entry, uint8 value );
+	void						plat_set_sg( int entry, uint8 value );
+	void						plat_set_sb( int entry, uint8 value );
+	void						plat_set_sa( int entry, uint8 value );
+	void						plat_set_mr( int entry, uint8 value );
+	void						plat_set_mg( int entry, uint8 value );
+	void						plat_set_mb( int entry, uint8 value );
+	void						plat_set_ma( int entry, uint8 value );
+	void						plat_set_er( int entry, uint8 value );
+	void						plat_set_eg( int entry, uint8 value );
+	void						plat_set_eb( int entry, uint8 value );
+	void						plat_set_ea( int entry, uint8 value );
+		
+	int							m_num_vertex_buffers;
+	int							m_history;
+	float**						mp_vertices;
+	uint32*						mp_colors;
+	uint64						m_blend;
+	NxXbox::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA*				m_start_color;				// Start color for each corner.
+	Image::RGBA*				m_mid_color;				// Mid color for each corner.
+	Image::RGBA*				m_end_color;				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_NxParticleShaded.cpp b/Code/Gfx/XBox/p_NxParticleShaded.cpp
new file mode 100644
index 0000000..da8d29a
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxParticleShaded.cpp
@@ -0,0 +1,348 @@
+#include 
+#include 
+#include "gfx/xbox/p_NxParticleShaded.h"
+
+extern DWORD PixelShader0;
+
+namespace Nx
+{
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleShaded::CXboxParticleShaded()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleShaded::CXboxParticleShaded( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix )
+{
+	m_checksum		= checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+	m_mid_time		= -1.0f;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices		= new float[max_particles * 3];
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_SHADED, texture_checksum, blendmode_checksum, fix );
+
+	// Default color.
+	for( int c = 0; c < 4; ++c )
+	{
+		m_start_color[c].r = 128;
+		m_start_color[c].g = 128;
+		m_start_color[c].b = 128;
+		m_start_color[c].a = 255;
+
+		m_mid_color[c].r = 128;
+		m_mid_color[c].g = 128;
+		m_mid_color[c].b = 128;
+		m_mid_color[c].a = 255;
+
+		m_end_color[c].r = 128;
+		m_end_color[c].g = 128;
+		m_end_color[c].b = 128;
+		m_end_color[c].a = 255;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleShaded::~CXboxParticleShaded()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleShaded::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleShaded::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleShaded::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleShaded::plat_get_num_particle_colors( void )
+{
+	return 4;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleShaded::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleShaded::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleShaded::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleShaded::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleShaded::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleShaded::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleShaded::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleShaded::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleShaded::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleShaded::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleShaded::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleShaded::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleShaded::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+	
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 24;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+		NxXbox::set_pixel_shader( PixelShader0 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 16 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_QUADLIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices;
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp	= p_particle->m_time / p_particle->m_life;
+				float w		= p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+				float h		= p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+				// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+				Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				Mth::Vector	ss_right, ss_up, ss_pos;
+				Mth::Vector tmp;
+	
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos;
+//					max = pos;
+//				}
+//				else
+//				{
+//					if( pos[X] < min[X] ) min[X] = pos[X]; else if( pos[X] > max[X] ) max[X] = pos[X];
+//					if( pos[Y] < min[Y] ) min[Y] = pos[Y]; else if( pos[Y] > max[Y] ) max[Y] = pos[Y];
+//					if( pos[Z] < min[Z] ) min[Z] = pos[Z]; else if( pos[Z] > max[Z] ) max[Z] = pos[Z];
+//				}
+		
+				ss_right	= screen_right * w;
+				ss_up		= screen_up * h;
+
+				Image::RGBA color[4];
+				Image::RGBA *p_col0;
+				Image::RGBA *p_col1;
+		
+				if( m_mid_time >= 0.0f )
+				{
+					if( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+
+				for ( int c = 0; c < 4; c++ )
+				{
+					Image::RGBA start = *p_col0++;
+					Image::RGBA end = *p_col1++;
+
+					// Swap red and blue here.
+					color[c].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[c].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+				}
+		
+				tmp			= pos - ss_right + ss_up;
+				p_push[0]	= *((DWORD*)&tmp[X] );
+				p_push[1]	= *((DWORD*)&tmp[Y] );
+				p_push[2]	= *((DWORD*)&tmp[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+	
+				tmp			= pos + ss_right + ss_up;		
+				p_push[0]	= *((DWORD*)&tmp[X] );
+				p_push[1]	= *((DWORD*)&tmp[Y] );
+				p_push[2]	= *((DWORD*)&tmp[Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x3F800000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				tmp			= pos + ss_right - ss_up;		
+				p_push[0]	= *((DWORD*)&tmp[X] );
+				p_push[1]	= *((DWORD*)&tmp[Y] );
+				p_push[2]	= *((DWORD*)&tmp[Z] );
+				p_push[3]	= *((DWORD*)&color[2] );
+				p_push[4]	= 0x3F800000UL;
+				p_push[5]	= 0x3F800000UL;
+				p_push		+= 6;
+		
+				tmp			= pos - ss_right - ss_up;		
+				p_push[0]	= *((DWORD*)&tmp[X] );
+				p_push[1]	= *((DWORD*)&tmp[Y] );
+				p_push[2]	= *((DWORD*)&tmp[Z] );
+				p_push[3]	= *((DWORD*)&color[3] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x3F800000UL;
+				p_push		+= 6;
+
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+				
+
+
diff --git a/Code/Gfx/XBox/p_NxParticleShaded.h b/Code/Gfx/XBox/p_NxParticleShaded.h
new file mode 100644
index 0000000..0859cad
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxParticleShaded.h
@@ -0,0 +1,64 @@
+#ifndef	__GFX_P_NX_PARTICLESHADED_H__
+#define	__GFX_P_NX_PARTICLESHADED_H__
+    
+#include "gfx/nxparticle.h"
+#include "gfx/xbox/nx/particles.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxParticleShaded : public CParticle
+{
+	public:
+							CXboxParticleShaded();
+							CXboxParticleShaded( uint32 checksum, int max_particles = 256, uint32 texture_checksum = 0, uint32 blendmode_checksum = 0, int fix = 128 );
+	virtual 				~CXboxParticleShaded();
+
+	NxXbox::sParticleSystem	*GetEngineParticle( void )	{ return mp_engine_particle; }
+
+
+	private:				// It's all private, as it is machine specific
+	virtual void			plat_render( void );
+	virtual void			plat_get_position( int entry, int list, float *x, float *y, float *z );
+	virtual void			plat_set_position( int entry, int list, float x, float y, float z );
+	virtual void			plat_add_position( int entry, int list, float x, float y, float z );
+	virtual int				plat_get_num_particle_colors( void );
+	virtual int				plat_get_num_vertex_lists( void )		{ return 1; }
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+
+	Image::RGBA				m_start_color[4];			// Start color for each corner.
+	Image::RGBA				m_mid_color[4];				// Mid color for each corner.
+	Image::RGBA				m_end_color[4];				// End color for each corner.
+	
+	float*					mp_vertices;
+	NxXbox::sParticleSystem	*mp_engine_particle;
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
+
+
+
diff --git a/Code/Gfx/XBox/p_NxParticleStar.cpp b/Code/Gfx/XBox/p_NxParticleStar.cpp
new file mode 100644
index 0000000..e79119a
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxParticleStar.cpp
@@ -0,0 +1,342 @@
+#include 
+#include "gfx/xbox/nx/render.h"
+
+#include "gfx/xbox/p_nxparticleStar.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleStar::CXboxParticleStar()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleStar::CXboxParticleStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split )
+{
+	m_checksum		= checksum;
+	m_max_particles	= max_particles;
+	m_num_particles	= 0;
+	m_mid_time		= -1.0f;
+	m_num_segments	= num_segments;
+	m_split			= split;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_STAR, texture_checksum, blendmode_checksum, fix, num_segments );
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 255;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleStar::~CXboxParticleStar()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleStar::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleStar::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleStar::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleStar::plat_get_num_particle_colors( void ) { return 3; }
+int CXboxParticleStar::plat_get_num_vertex_lists( void ) { return 1; }
+void CXboxParticleStar::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleStar::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleStar::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleStar::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleStar::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleStar::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleStar::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleStar::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleStar::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleStar::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleStar::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleStar::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleStar::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+	
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 16 * m_num_segments;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_QUADLIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices;
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp	= p_particle->m_time / p_particle->m_life;
+				float w		= p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+				float h		= p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+				// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+				Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				Mth::Vector	ss_right, ss_up;
+				Mth::Vector tmp[3];
+	
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos;
+//					max = pos;
+//				}
+//				else
+//				{
+//					if( pos[X] < min[X] ) min[X] = pos[X]; else if( pos[X] > max[X] ) max[X] = pos[X];
+//					if( pos[Y] < min[Y] ) min[Y] = pos[Y]; else if( pos[Y] > max[Y] ) max[Y] = pos[Y];
+//					if( pos[Z] < min[Z] ) min[Z] = pos[Z]; else if( pos[Z] > max[Z] ) max[Z] = pos[Z];
+//				}
+
+				ss_right	= screen_right * w;
+				ss_up		= screen_up * h;
+	
+				Image::RGBA color[3];
+				Image::RGBA *p_col0;
+				Image::RGBA *p_col1;
+
+				if( m_mid_time >= 0.0f )
+				{
+					if( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+
+				for( int c = 0; c < 3; c++ )
+				{
+					Image::RGBA start = *p_col0++;
+					Image::RGBA end = *p_col1++;
+
+					// Swap red and blue here.
+					color[c].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[c].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+				}
+
+				tmp[0]  = pos;
+				tmp[0] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+				tmp[0] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+				for( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+				{
+					tmp[1]  = pos;
+					tmp[1] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+					tmp[1] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments) ) * ( lp2 + 1 ) ) ) ) * m_split;
+
+					tmp[2]  = pos;
+					tmp[2] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+					tmp[2] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+
+					p_push[0]	= *((DWORD*)&pos[X] );
+					p_push[1]	= *((DWORD*)&pos[Y] );
+					p_push[2]	= *((DWORD*)&pos[Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[0][X] );
+					p_push[1]	= *((DWORD*)&tmp[0][Y] );
+					p_push[2]	= *((DWORD*)&tmp[0][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					tmp[0] = tmp[1];
+				}
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/XBox/p_NxParticleStar.h b/Code/Gfx/XBox/p_NxParticleStar.h
new file mode 100644
index 0000000..18e80b5
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxParticleStar.h
@@ -0,0 +1,70 @@
+#ifndef	__GFX_P_NX_PARTICLEStar_H__
+#define	__GFX_P_NX_PARTICLEStar_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxParticleStar : public CParticle
+{
+	public:
+							CXboxParticleStar();
+							CXboxParticleStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split );
+	virtual 				~CXboxParticleStar();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+
+	private:				// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+
+	NxXbox::sParticleSystem*	mp_engine_particle;
+	int						m_num_segments;
+	float					m_split;
+	Image::RGBA				m_start_color[3];			// Start color for each corner.
+	Image::RGBA				m_mid_color[3];				// Mid color for each corner.
+	Image::RGBA				m_end_color[3];				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_NxSprite.cpp b/Code/Gfx/XBox/p_NxSprite.cpp
new file mode 100644
index 0000000..8510d60
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxSprite.cpp
@@ -0,0 +1,137 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxSprite.cpp
+
+#include 	"Gfx/xbox/p_NxSprite.h"
+#include 	"Gfx/xbox/p_NxTexture.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CSprite
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CXboxSprite::CXboxSprite()
+{
+	mp_plat_sprite = new NxXbox::sSprite();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CXboxSprite::~CXboxSprite()
+{
+	delete mp_plat_sprite;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CXboxSprite::plat_initialize()
+{
+	plat_update_engine();
+	plat_update_priority();
+	plat_update_hidden();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSprite::plat_update_hidden()
+{
+	// Take sprite on or off draw list
+	mp_plat_sprite->SetHidden( m_hidden );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSprite::plat_update_engine()
+{
+	CXboxTexture *p_xbox_texture = static_cast( mp_texture );
+
+	if( p_xbox_texture )
+	{
+		// Rebuild sprite primitives
+		mp_plat_sprite->mp_texture	= p_xbox_texture->GetEngineTexture();
+	}
+	
+	mp_plat_sprite->m_xpos		= m_pos_x;
+	mp_plat_sprite->m_ypos		= m_pos_y;
+	mp_plat_sprite->m_width		= m_width;
+	mp_plat_sprite->m_height	= m_height;
+	mp_plat_sprite->m_scale_x	= m_scale_x;
+	mp_plat_sprite->m_scale_y	= m_scale_y;
+
+	mp_plat_sprite->m_xhot		= (( m_anchor_x + 1.0f ) * 0.5f) * ( m_width );
+	mp_plat_sprite->m_yhot		= (( m_anchor_y + 1.0f ) * 0.5f) * ( m_height );
+
+	mp_plat_sprite->m_rot		= m_rotation;
+	mp_plat_sprite->m_rgba		= *((uint32 *) &m_rgba);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSprite::plat_update_priority()
+{
+	// Update draw list.
+	if( mp_plat_sprite )
+	{
+		mp_plat_sprite->SetPriority( m_priority );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::plat_enable_constant_z_value(bool enable)
+{
+	//NxPs2::SSprite::EnableConstantZValue(enable);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CSprite::plat_set_constant_z_value(Nx::ZBufferValue z)
+{
+	//NxPs2::SSprite::SetConstantZValue(z);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::ZBufferValue	CSprite::plat_get_constant_z_value()
+{
+	//return NxPs2::SSprite::GetConstantZValue();
+	return 0;
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_NxSprite.h b/Code/Gfx/XBox/p_NxSprite.h
new file mode 100644
index 0000000..aa062b4
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxSprite.h
@@ -0,0 +1,36 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_SPRITE_H__
+#define	__GFX_P_NX_SPRITE_H__
+
+#include 	"Gfx/NxSprite.h"
+#include 	"Gfx/xbox/NX/texture.h"
+#include 	"Gfx/xbox/NX/sprite.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CSprite
+class	CXboxSprite : public CSprite
+{
+public:
+								CXboxSprite();
+	virtual						~CXboxSprite();
+
+private:		// It's all private, as it is machine specific
+	virtual void				plat_initialize();
+
+	virtual void				plat_update_hidden();		// Tell engine of update
+	virtual void				plat_update_engine();		// Update engine primitives
+	virtual void				plat_update_priority();
+
+	NxXbox::sSprite *			mp_plat_sprite;
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/XBox/p_NxTextured3dPoly.cpp b/Code/Gfx/XBox/p_NxTextured3dPoly.cpp
new file mode 100644
index 0000000..67a1768
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxTextured3dPoly.cpp
@@ -0,0 +1,23 @@
+#include 	"Gfx/Xbox/p_NxTextured3dPoly.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTextured3dPoly
+
+CXboxTextured3dPoly::CXboxTextured3dPoly( void )
+{
+}
+
+CXboxTextured3dPoly::~CXboxTextured3dPoly( void )
+{
+}
+
+void CXboxTextured3dPoly::plat_render( void )
+{
+}
+
+} // Namespace NxXbox  			
+				
+				
diff --git a/Code/Gfx/XBox/p_NxTextured3dPoly.h b/Code/Gfx/XBox/p_NxTextured3dPoly.h
new file mode 100644
index 0000000..98695ca
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxTextured3dPoly.h
@@ -0,0 +1,22 @@
+#ifndef	__GFX_P_NX_TEXTURED_3D_POLY_H__
+#define	__GFX_P_NX_TEXTURED_3D_POLY_H__
+
+#include 
+
+namespace Nx
+{
+
+// Machine specific implementation of CTextured3dPoly
+class	CXboxTextured3dPoly : public Nx::CTextured3dPoly
+{
+public:
+		   					CXboxTextured3dPoly();
+	virtual					~CXboxTextured3dPoly();
+private:
+	virtual void			plat_render();
+};
+
+}	// namespace NxXbox
+
+#endif
+				   
diff --git a/Code/Gfx/XBox/p_NxViewMan.cpp b/Code/Gfx/XBox/p_NxViewMan.cpp
new file mode 100644
index 0000000..f241e69
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxViewMan.cpp
@@ -0,0 +1,28 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxViewMan.cpp - Xbox platform specific interface to CViewportManager
+
+#include 
+
+#include "gfx/NxViewMan.h"
+#include "gfx/Xbox/p_NxViewport.h"
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CViewport *CViewportManager::s_plat_create_viewport( const Mth::Rect *rect, Gfx::Camera *cam )
+{
+	return new CXboxViewport( rect, cam );
+}
+
+} 
+ 
diff --git a/Code/Gfx/XBox/p_NxViewport.cpp b/Code/Gfx/XBox/p_NxViewport.cpp
new file mode 100644
index 0000000..1216bde
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxViewport.cpp
@@ -0,0 +1,102 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxViewport.cpp
+
+#include 	"Gfx/NxViewMan.h"
+#include 	"Gfx/Xbox/p_NxViewport.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CViewport
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxViewport::CXboxViewport()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxViewport::CXboxViewport( const Mth::Rect *rect, Gfx::Camera *cam) : CViewport( rect, cam )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxViewport::~CXboxViewport()
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float CXboxViewport::plat_transform_to_screen_coord( const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z )
+{
+	D3DXVECTOR3	in( world_pos[X], world_pos[Y], world_pos[Z] );
+	D3DXVECTOR4	mid;
+	D3DXVECTOR3	out;
+	D3DXVec3Transform( &mid, &in, (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix );
+
+	in.x = mid.x;
+	in.y = mid.y;
+	in.z = mid.z;
+	D3DXVec3TransformCoord( &out, &in, (D3DXMATRIX*)&NxXbox::EngineGlobals.projection_matrix );
+
+	// Convert from homogenous cube (-1.0, 1.0) to pixel coordinates. Have to be careful here, the initial
+	// feeling is to use the backbuffer width and height as multipliers, but since this value is being used
+	// for text which has it's position re-converted at draw time, we actually want to use the fixed Ps2 screen
+	// values here.
+	screen_pos_x = (( out.x + 1.0f ) * 0.5f ) * 640.0f;
+	screen_pos_y = (( -out.y + 1.0f ) * 0.5f ) * 448.0f;
+	
+	float scale = -1.0f;
+	
+	if( out.z < 1.0f )
+	{
+		// Text is not clipped. Calculate scale factor.
+		float d_noscale = Nx::CViewportManager::sGet2DIn3DSpaceNoscaleDistance();
+		float s_min		= Nx::CViewportManager::sGet2DIn3DSpaceMinScale();
+		float s_max		= Nx::CViewportManager::sGet2DIn3DSpaceMaxScale();
+		
+		scale = d_noscale / -mid.z;
+
+		// Clamp scale.
+		if( scale > s_max )
+		{
+			scale = s_max;
+		}
+		else if( scale < s_min )
+		{
+			scale = s_min;
+		}
+		
+		// The z value is passed in as a uint32, so set the value here based on fixed point 16:16.
+		screen_pos_z = (uint32)( out.z * 65536.0f );
+	}
+		
+	// This is the scale factor. Returning a value < 0 will cause the text to be hidden.
+	return scale;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_NxViewport.h b/Code/Gfx/XBox/p_NxViewport.h
new file mode 100644
index 0000000..a9d70b9
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxViewport.h
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxViewport.h
+
+#ifndef	__GFX_P_NX_VIEWPORT_H__
+#define	__GFX_P_NX_VIEWPORT_H__
+
+#include "Gfx/NxViewport.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CViewport
+class	CXboxViewport : public CViewport
+{
+public:
+								CXboxViewport();
+								CXboxViewport( const Mth::Rect *rect, Gfx::Camera *cam = NULL );
+	virtual						~CXboxViewport();
+
+private:		// It's all private, as it is machine specific
+//	virtual void				plat_initialize();
+	virtual float				plat_transform_to_screen_coord( const Mth::Vector & world_pos, float & screen_pos_x, float & screen_pos_y, ZBufferValue & screen_pos_z );
+
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/XBox/p_NxWin2D.cpp b/Code/Gfx/XBox/p_NxWin2D.cpp
new file mode 100644
index 0000000..0f853dd
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxWin2D.cpp
@@ -0,0 +1,60 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxWin2D.cpp
+
+#include 	"Gfx/Xbox/p_NxWin2D.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxWindow2D::CXboxWindow2D( int x, int y, int width, int height) : CWindow2D( x, y, width, height )
+{
+	plat_update_engine();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxWindow2D::~CXboxWindow2D()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWindow2D::plat_update_engine()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CWindow2DManager::s_plat_alloc_window2d_pool()
+{
+	for( int i = 0; i < vMAX_WINDOW_INSTANCES; i++ )
+	{
+	   	CXboxWindow2D *p_window = new CXboxWindow2D;
+		p_window->mp_next	= sp_window_list;
+		sp_window_list		= p_window;
+	}
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_NxWin2D.h b/Code/Gfx/XBox/p_NxWin2D.h
new file mode 100644
index 0000000..fe825d8
--- /dev/null
+++ b/Code/Gfx/XBox/p_NxWin2D.h
@@ -0,0 +1,33 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxWin2D.h
+
+#ifndef	__GFX_P_NX_WIN2D_H__
+#define	__GFX_P_NX_WIN2D_H__
+
+#include 	"Gfx/NxWin2D.h"
+#include 	"Gfx/Xbox/NX/sprite.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CWindow2D
+class	CXboxWindow2D : public CWindow2D
+{
+public:
+								CXboxWindow2D( int x = 0, int y = 0, int width = 640, int height = 480 );
+								CXboxWindow2D( const Mth::Rect & win_rect);
+	virtual						~CXboxWindow2D();
+
+private:
+	//
+	virtual void				plat_update_engine();		// Update engine primitives
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/XBox/p_gfxman.cpp b/Code/Gfx/XBox/p_gfxman.cpp
new file mode 100644
index 0000000..76aa95a
--- /dev/null
+++ b/Code/Gfx/XBox/p_gfxman.cpp
@@ -0,0 +1,160 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		p_gfxman.cpp											**
+**																			**
+**	Created:		07/26/99	-	mjb										**
+**																			**
+**	Description:	Graphics device manager									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+		
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Manager::SetGammaNormalized( float fr, float fg, float fb )
+{
+	NxXbox::SetGammaNormalized( fr, fg, fb );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Manager::GetGammaNormalized( float *fr, float *fg, float *fb )
+{
+	NxXbox::GetGammaNormalized( fr, fg, fb );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::DumpVRAMUsage( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::ScreenShot( const char *fileroot )
+{
+	// Called twice per frame - once to request the screenshot, and once (post Swap()), to actually perform it.
+	if( NxXbox::EngineGlobals.screenshot_name[0] == 0 )
+	{
+		strcpy( NxXbox::EngineGlobals.screenshot_name, fileroot );
+		return;
+	}
+	
+	char fileName[32];
+	char fullFileName[64];
+	
+	// Try to find a good filename of the format filebasexxx.bmp. "Good" is defined here as one that isn't already used.
+	for( int i = 0;; ++i )
+	{
+		sprintf( fileName, "screens\\%s%03d.bmp", fileroot, i );
+
+		// Found an unused one! Yay!
+		if( !File::Exist( fileName ))
+		{
+			sprintf( fullFileName, "d:\\data\\screens\\%s%03d.bmp", fileroot, i );
+			break;
+		}
+	}
+	
+	// Obtain the render surface.
+	IDirect3DSurface8 *p_render_target = NxXbox::EngineGlobals.p_RenderSurface;
+
+	// Get the surface description, just for s and g.
+	D3DSURFACE_DESC surface_desc;
+	p_render_target->GetDesc( &surface_desc );
+
+	// This is great - this function spits surfaces straight out into a file.
+	HRESULT hr = XGWriteSurfaceToFile( p_render_target, fullFileName );
+	Dbg_Assert( hr == S_OK );
+}
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace Gfx
diff --git a/Code/Gfx/XBox/p_loadscreen.cpp b/Code/Gfx/XBox/p_loadscreen.cpp
new file mode 100644
index 0000000..fe56262
--- /dev/null
+++ b/Code/Gfx/XBox/p_loadscreen.cpp
@@ -0,0 +1,392 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Gfx														**
+**																			**
+**	Module:			LoadScreen			 									**
+**																			**
+**	File name:		p_loadscreen.cpp										**
+**																			**
+**	Created by:		05/08/02	-	SPG										**
+**																			**
+**	Description:	XBox-specific loading screen calls						**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace LoadScreen
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+// Skate3 code, disabled for now.
+#if 0
+extern "C" RwBool _rwXbSetRenderState(RwRenderState nState, void *pParam);
+extern "C" RwBool _rwXbGetRenderState(RwRenderState nState, void *pParam);
+extern "C" RwBool _rwXbRenderStateTextureRaster(RwRaster *raster);
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+class LoadingIconData
+{
+public:
+	int	m_X;
+	int	m_Y;
+	int m_FrameDelay;
+//	Skate3 code, disabled for now.
+#	if 0
+	RwRaster** m_Rasters;
+#	endif
+	int m_NumFrames;
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static bool				s_loading_screen_on		= false;
+static MMRESULT			s_timer_event			= 0;
+static LoadingIconData	s_load_icon_data		= { 0 };
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+void CALLBACK loadIconTimerCallback( UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2 )
+{
+	static int current_image = 0;
+
+//	Skate3 code, disabled for now.
+#	if 0
+	
+	// Here is where the load icon will be written to the screen.
+	RwRect rect;
+	rect.x = s_load_icon_data.m_X;
+	rect.y = s_load_icon_data.m_Y;
+
+	rect.w = SCREEN_CONV_X( 32.0f ) - SCREEN_CONV_X( 0.0f );
+	rect.h = SCREEN_CONV_Y( 32.0f ) - SCREEN_CONV_Y( 0.0f );
+
+	Spt::SingletonPtr< Gfx::Manager > gfx_manager;
+
+	_rwXbSetRenderState( rwRENDERSTATETEXTURERASTER, (void *)NULL );
+
+	RwRaster* p_context_raster;
+	p_context_raster = gfx_manager->DefaultViewport().GetFrameBuffer();
+
+	RwRasterPushContext( p_context_raster );
+
+	RwRasterRenderScaled( s_load_icon_data.m_Rasters[current_image], &rect );
+	RwRasterShowRaster( p_context_raster, NULL, 0 );
+
+	RwRasterPopContext();
+
+#	endif
+	
+	current_image = ( current_image + 1 ) % s_load_icon_data.m_NumFrames ;
+}
+
+
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void SetLoadingIconProperties( int x, int y, int frame_delay, int num_frames, char* basename, char* ext )
+{
+	int i;
+
+//	Skate3 code, disabled for now.
+#	if 0
+
+	if( s_load_icon_data.m_NumFrames > 0  )
+	{   
+		for( i = 0; i < s_load_icon_data.m_NumFrames; i++ )
+		{
+			RwRasterDestroy( s_load_icon_data.m_Rasters[i] );
+		}
+		delete [] s_load_icon_data.m_Rasters;
+	}
+
+	s_load_icon_data.m_X			= SCREEN_CONV_X( x );
+	s_load_icon_data.m_Y			= SCREEN_CONV_Y( y );
+	s_load_icon_data.m_FrameDelay = frame_delay;
+	s_load_icon_data.m_NumFrames = num_frames;
+	s_load_icon_data.m_Rasters = new RwRaster*[s_load_icon_data.m_NumFrames];
+
+	RwImageSetPath("");
+	for( i = 0; i < s_load_icon_data.m_NumFrames; i++ )
+	{
+		char image_name[64];
+		//sprintf( image_name, "Images/PanelSprites/wheel_0%d.png", i+1 );
+		sprintf( image_name, "%s%d.%s", basename, i+1, ext );
+		s_load_icon_data.m_Rasters[i] = RwRasterRead( image_name );
+	}
+
+#	endif
+}
+
+void Display( char* filename, bool just_freeze, bool blank )
+{
+	// Redundancy Check
+	if( s_loading_screen_on )
+	{
+		return;
+	}
+    
+	// Set filename up for foreign languages.
+	char name[256];
+
+	// Seems that filename may sometimes be NULL.
+	if( filename )
+	{
+		char * p8 = &filename[strlen(filename)];
+
+		if( IsEnglish())
+		{
+			strcpy ( name, filename );
+		}
+		else
+		{
+			switch ( XGetLanguage() ) {
+				case XC_LANGUAGE_FRENCH:
+					while ( ( *p8 != '\\' ) && ( *p8 != '/' ) ) p8--;
+					sprintf( name, "images\\frimages\\%s", &p8[1] );
+					break;
+				case XC_LANGUAGE_GERMAN:
+					while ( ( *p8 != '\\' ) && ( *p8 != '/' ) ) p8--;
+					sprintf( name, "images\\grimages\\%s", &p8[1] );
+					break;
+				default:
+					strcpy ( name, filename );
+					break;
+			}
+		}
+	}
+
+//	Tmr::Time start_time = Tmr::GetTime();
+
+//	Skate3 code, disabled for now.
+#	if 0
+    if (!just_freeze && ! blank)
+	{
+		RwImageSetPath( "" );
+		RwRaster* raster = RwRasterRead( name );
+	
+		if( raster == NULL )
+		{
+			// Try the english version.
+			raster = RwRasterRead( filename );
+			if( raster == NULL )
+			{
+				Dbg_Printf( "Could not load %s\n", filename );
+				return;
+			}
+		}
+
+		// Wait a few VBlanks for RW stuff to disappear.
+//		while( Tmr::GetTime() - start_time < Tmr::VBlanks( 4 ));
+
+		RwRect rect;
+		rect.x = SCREEN_CONV_X( 0 );
+		rect.y = SCREEN_CONV_Y( 0 );
+		rect.w = SCREEN_CONV_X( 640 ) - SCREEN_CONV_X( 0 );
+		rect.h = SCREEN_CONV_Y( 448 ) - SCREEN_CONV_Y( 0 );
+
+		Spt::SingletonPtr< Gfx::Manager > gfx_manager;
+
+		_rwXbSetRenderState( rwRENDERSTATETEXTURERASTER, (void *)NULL );
+
+		RwRaster* p_context_raster;
+		p_context_raster = gfx_manager->DefaultViewport().GetFrameBuffer();
+
+		RwInt32 format = p_context_raster->cFormat & 0x0000FF00L;
+		RwRGBA black = { 0, 0, 0, 0 }; 
+
+		RwRasterPushContext( p_context_raster );
+		
+		RwRasterClear( RwRGBAToPixel( &black, format ));		// 0: Clear to black
+		RwRasterShowRaster( p_context_raster, NULL, 0 );
+
+		RwRasterClear( RwRGBAToPixel( &black, format ));        // 1: Clear to black 
+		RwRasterShowRaster( p_context_raster, NULL, 0 );
+
+		RwRasterRenderScaled( raster, &rect );			        // 2: Draw image
+		RwRasterShowRaster( p_context_raster, NULL, 0 );
+
+		RwRasterRenderScaled( raster, &rect );                  // 3: Draw image
+		RwRasterShowRaster( p_context_raster, NULL, 0 );
+		RwRasterPopContext();
+
+		RwRasterDestroy( raster );
+
+		// Set up the timer event last so that the icon will only ever appear once the loadscreen is visible.
+		if( s_timer_event == 0 )
+		{
+			s_timer_event = timeSetEvent(	( 1000 * s_load_icon_data.m_FrameDelay ) / 60,	// Delay (ms).
+											0,												// Ignored resolution (ms).
+											loadIconTimerCallback,							// Callback function.
+											0,												// Callback data.
+											TIME_PERIODIC | TIME_CALLBACK_FUNCTION );
+
+
+			Dbg_Assert( s_timer_event != 0 );
+		}
+	}
+	else
+	{
+		if (just_freeze)
+		{
+			Script::RunScript( "PreFreezeScreen" );
+//			if( skyFrameBit & 0x1 )
+//			{
+//				copy_buffer( 1 );
+//			}
+//			else
+//			{
+//				copy_buffer( 0 );
+//			}
+
+			Script::RunScript( "PostFreezeScreen" );
+		}
+		else if( blank )
+		{
+			RwRect rect;
+			rect.x = SCREEN_CONV_X( 0 );
+			rect.y = SCREEN_CONV_Y( 0 );
+			rect.w = SCREEN_CONV_X( 640 ) - SCREEN_CONV_X( 0 );
+			rect.h = SCREEN_CONV_Y( 448 ) - SCREEN_CONV_Y( 0 );
+
+			Spt::SingletonPtr< Gfx::Manager > gfx_manager;
+
+			_rwXbSetRenderState( rwRENDERSTATETEXTURERASTER, (void *)NULL );
+
+			RwRaster* p_context_raster;
+			p_context_raster = gfx_manager->DefaultViewport().GetFrameBuffer();
+
+			RwInt32 format = p_context_raster->cFormat & 0x0000FF00L;
+			RwRGBA black = { 0, 0, 0, 0 }; 
+
+			RwRasterPushContext( p_context_raster );
+
+			RwRasterClear( RwRGBAToPixel( &black, format ));		// 0: Clear to black
+			RwRasterShowRaster( p_context_raster, NULL, 0 );
+
+			RwRasterClear( RwRGBAToPixel( &black, format ));        // 1: Clear to black 
+			RwRasterShowRaster( p_context_raster, NULL, 0 );
+
+			RwRasterPopContext();
+		}
+	}
+
+#	endif
+	
+	Spt::SingletonPtr< Mlp::Manager > mlp_man;
+	s_loading_screen_on = true;
+	mlp_man->PauseDisplayTasks( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Hide( void )
+{
+	if( s_timer_event != 0 )
+	{
+		timeKillEvent( s_timer_event );
+		s_timer_event = 0;
+	}
+
+	Spt::SingletonPtr< Mlp::Manager > mlp_man;
+	mlp_man->PauseDisplayTasks( false ); 
+
+	if( !s_loading_screen_on )
+	{
+		return;
+	}
+
+	s_loading_screen_on = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StartProgressBar( char* tick_image, int num_ticks, int x, int y )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ProgressTick( int num_ticks )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	EndProgressBar( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace LoadScreen
+
+
+
+
diff --git a/Code/Gfx/XBox/p_memview.cpp b/Code/Gfx/XBox/p_memview.cpp
new file mode 100644
index 0000000..b8781c2
--- /dev/null
+++ b/Code/Gfx/XBox/p_memview.cpp
@@ -0,0 +1,1130 @@
+//////////////////////////////////////////////////////
+// p_memview.cpp
+//
+// code for tracking memory usage, and displaying it in a graphical manner
+// keeps extra info about allocated blocks
+// including the call stack, so we can print out information
+// about specific allocated blocks
+// which we will select using the graphical memory browser
+//
+//
+// tried to use a little of the task system as possible
+// so we can run the inspector
+// without it messing with the heap it inspects
+//
+//
+
+
+//extern char _mem_dump_start[];
+//extern char _map_file_start[];
+//extern char _symbols_start[];
+//extern char _callstack_start[];
+//extern char _code_end[];
+//extern char	_std_mem_end[];
+//extern char	_stack_size[];
+
+//extern char __text_org[];
+//extern char __data_org[];
+//extern char __rodata_org[];
+//extern char __bss_org[];
+
+//extern char __rodata_orgend[];
+//extern char __bss_objend[];
+//extern char __text_objend[];
+
+//extern char _rwheapdebug_start[];
+
+			 
+#define	STACKDEPTH  30			 
+
+
+			 
+extern "C"				 
+{
+extern void* ENTRYPOINT;		
+}
+
+#include 
+#include 
+#include 					// needed for buttons
+#include 
+#include  			// needed for loading map file
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+// needed for some VerticalMenu specific debugging							
+#include 
+#include 
+#include 
+
+
+extern volatile int	test_vblanks;
+
+
+class CCallStack
+{
+public:
+	void Append(CCallStack *p);
+	void Remove();
+	void InitHead();
+	int	used;
+	int	size;
+	CCallStack *pNext;
+	CCallStack *pPrev;
+	int	addr[STACKDEPTH];
+	uint32	flags;
+	Mem::Allocator::BlockHeader * pBlock;	// pointer to block that has this callstack
+
+};
+
+CCallStack		free_list;	// list of created objects
+CCallStack		used_list;	// list of created objects
+
+// init a node, so it can act as the head			 
+inline void CCallStack::InitHead()
+{
+	pPrev = this;
+	pNext = this;
+}
+	
+// append node p to this node (after it)									 
+inline void CCallStack::Append(CCallStack *p)
+{
+
+	p->pNext = this->pNext;
+	p->pPrev = this;
+	this->pNext = p;
+	p->pNext->pPrev = p;
+}
+
+// simply unlink it from the list			   
+inline void CCallStack::Remove()
+{
+	pPrev->pNext = pNext;
+	pNext->pPrev = pPrev;
+}
+
+
+						
+//CCallStack * CallStack_FirstFree;
+//CCallStack * CallStack_FirstUsed; 
+
+static int MemView_Active = 0;
+
+
+
+#define	MAX_CALLSTACK (8192 * 8)		// we got 8 mb, woo woo.
+
+
+static float step = 128.0f;
+
+
+static char HexByte(char a)
+{
+	if (a >= '0' && a <='9')
+	{
+		return a-'0';
+	}
+	if (a >= 'A' && a <='F')
+	{
+		return 10 + a-'A';
+	}
+	if (a >= 'a' && a <='f')
+	{
+		return 10 + a-'a';
+	}
+
+	// should really be an error, but just ignore it and return 0
+	// as this is only used for parsing the map file	
+	return 0;
+	
+	
+}
+
+
+static int doneonce = 0;
+
+
+char *MemView_GetFunctionName(int pc, int *p_size)
+{
+	return "UNKNOWN";
+	
+}
+
+// Modifed version of Jamie's unwind stack function
+// ignores the fp, and just goes directly off the sp
+// seems to work much better (and faster too)
+int DumpUnwindStack( int iMaxDepth, int *pDest )
+{
+	return 0;
+}
+				   
+				   
+//        mD_L2           = nBit( vD_L2 ),
+//        mD_R2           = nBit( vD_R2 ),
+//        mD_L1           = nBit( vD_L1 ),
+//        mD_R1           = nBit( vD_R1 ),
+//        mD_TRIANGLE     = nBit( vD_TRIANGLE ),
+//	      mD_CIRCLE       = nBit( vD_CIRCLE ),
+//        mD_X            = nBit( vD_X ),
+//        mD_SQUARE       = nBit( vD_SQUARE ),
+//        mD_SELECT       = nBit( vD_SELECT ),
+//        mD_L3           = nBit( vD_L3 ),
+//        mD_R3           = nBit( vD_R3 ),
+//        mD_START        = nBit( vD_START ),
+//        mD_UP           = nBit( vD_UP ),
+//        mD_RIGHT        = nBit( vD_RIGHT ),
+//        mD_DOWN         = nBit( vD_DOWN ),
+//        mD_LEFT         = nBit( vD_LEFT ),
+
+
+void MemViewToggle()
+{
+	MemView_Active ^=1;
+}
+
+
+	 
+
+void MemView_Alloc( void *v)
+{
+#ifdef	__LINKED_LIST_HEAP__
+
+
+#ifdef	__NOPT_CDROM__OLD
+   return;
+#endif 	
+
+#endif
+}
+
+void MemView_Free( void *v)
+{
+#ifdef	__LINKED_LIST_HEAP__
+#endif	
+}
+
+
+Mem::Allocator::BlockHeader *MemView_FindBlock( int addr)
+{
+#ifdef	__LINKED_LIST_HEAP__
+
+	
+	Mem::Allocator::BlockHeader *pSmallestBlock	= NULL;
+	uint32 smallest_block_size = 100000000;
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	for (Mem::Heap* heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+	{
+		Mem::Allocator::BlockHeader *pBlock = (Mem::Allocator::BlockHeader *) heap->find_block((void*)addr);	
+		if (pBlock)
+		{
+			if (pBlock->mSize < smallest_block_size)
+			{
+				smallest_block_size = pBlock->mSize;
+				pSmallestBlock = pBlock;
+			}
+		}
+	}
+	return pSmallestBlock;
+#else
+	return NULL;
+#endif
+}
+
+char * MemView_GetClassName(CCallStack *c)
+{
+#ifdef	__LINKED_LIST_HEAP__
+	int *ra = (int*)(c->addr[4]);
+	int count = STACKDEPTH-4;
+	while (count--)
+	{
+		int instruction = *ra++;
+		if (instruction >> 24 == 0x0c)
+		{
+			int code = (instruction & 0xffffff)<<2;
+			int size;
+			char *p = MemView_GetFunctionName(code,&size); 
+			// to tell if this is class or not
+			// we see if the text is of the form  
+			//    classname::classname (teminated by a 0)
+			// as that indicates that it is a constructor
+			// dude... this is where we need a regular expression....
+			char *end = p;
+			while (*end) end++;	   						// scan to end
+			while (end[-1] != ':' && end > p)	end--;	// skip to char after the last :
+			char *other = strstr(p,end);				// find fist occurance of end of string
+			if (other != end)							// if different, then this is it!!
+			{
+				return MemView_GetFunctionName(code,&size);
+				break;
+			}
+		}
+	}
+#endif
+	return NULL;
+}
+
+
+void MemView_DumpBlockInfo(int cursor)
+{
+#	ifdef	__LINKED_LIST_HEAP__
+	
+	char os[256];
+	
+	Mem::Allocator::BlockHeader *pBlock = MemView_FindBlock( cursor );
+	if( !pBlock )
+	{
+		// should search free blocks here???
+	}
+
+	// find this in the allocators used list
+	// and say if it is free, or not	
+	if( pBlock == NULL )
+	{
+/*		if (cursor > (int)__text_org && cursor < (int)__bss_objend)		// check to see if in code/data
+		{
+			
+			if (cursor < (int)__data_org)
+				printf("Code: ");
+			else if (cursor < (int)__rodata_org)
+				printf("Data: ");
+			else if (cursor < (int)__bss_org)
+				printf("RO-Data: ");
+			else 
+				printf("BSS: ");
+			
+		
+			int size;
+			char *p_name = MemView_GetFunctionName(cursor,&size);
+			printf ( "%s, size %d\n",p_name,size);
+		}
+		else
+*/
+		{
+			OutputDebugString( "Block Not Found\n" );
+		}
+	}
+	else
+	{
+		void * p_start = (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+		sprintf( os, "Block found, addr = %p, size = %d (Header = %d)\n",p_start,pBlock->mSize,Mem::Allocator::BlockHeader::sSize);
+		OutputDebugString( os );
+
+		CCallStack *c = (CCallStack*)pBlock->mp_debug_data;
+		if (!c)
+		{
+//			OutputDebugString( "Block with No Debug Info!!\n" );
+		}
+		else
+		{
+			// assume this is a "new", then the fourth callstack ra will point to the 
+			// jal xxxxxx  instruction, where xxxxx is the constructor for the 
+			// or it might be sortly thereafter, so check 16 instructions
+			char *classname = MemView_GetClassName( c );
+			if( classname )
+			{
+				sprintf( os, "CLASS: %s\n", classname );
+				OutputDebugString( os );
+			}
+
+			// then list out the call stack (skipping the MemView_Alloc, as that's a given, and irrelevant);			
+		
+			int *p = c->addr + 1;
+			while (p[1])			// also skip the ENTRYPOINT, just go back to main()
+			{
+				int size;
+				sprintf( os, "%p: %s\n", (void*)*p,MemView_GetFunctionName(*p,&size));
+				OutputDebugString( os );
+				p++;
+			}
+		}
+	}
+#endif
+}
+
+static	int	blockstart;
+
+static float cursor;
+
+void 		MemView_Display()
+{
+#	ifdef	__NOPT_CDROM__OLD
+	return;
+#	endif 	
+}
+
+
+#ifdef	__LINKED_LIST_HEAP__
+
+static int num_used;
+
+static void ScanRegion(uint32 *p_start, uint32 *p_end)
+{
+#if 0
+	printf ("scanning from %p to %p\n",p_start,p_end);
+	// scan the whole range of memeory
+	while (p_start (uint32)_code_end /*&& x < (uint32)_std_mem_end*/) // don't check for end now, as we have some debug heaps up there we want to include
+		{
+			// check to see if it points to one of the heap members
+			
+			uint32 *p_refs = (uint32*)_mem_dump_start;
+			
+		#if 0
+			for (int i=0;i x)
+					{
+						high = mid;
+					}
+					else
+					{
+						// if the low point is already the same as the mid point
+						// then the only way to go is up!
+						// as this will only occur when low + 1 == high
+						if (low == mid)
+						{
+							low = high;
+						}
+						else
+						{
+							low = mid;
+						}
+					}				
+				}
+				x -= 16;
+			}
+			x = oldx;
+		#endif						  		
+		}
+	}
+#endif
+}
+#endif
+
+#ifdef	__LINKED_LIST_HEAP__
+static uint32 *p_used;		
+#endif
+
+int MemView_CountBlocks(Mem::Allocator::BlockHeader *p_header)
+{
+#ifdef	__LINKED_LIST_HEAP__
+	int num_used = 0;
+	while ( p_header )
+	{
+		void * p_start = (void*)((uint)p_header + Mem::Allocator::BlockHeader::sSize);
+		
+		*p_used++ = (uint32)p_start;	 		// store the start of the block
+		*p_used++ = 0;							// store a count
+		p_header = p_header->mp_next_used;
+		num_used++;
+	}
+	return num_used;
+#else
+	return 0;
+#endif
+}
+
+// Find memory leaks
+// the algorithm is quite simple:
+// 1) Make a list of all "used" memory blocks, and set their usage count to 0
+// 2) Scan all of the heap, and the stack, for each word that looks like a pointer, 
+//    check to see if it is in the list of "used", and increment the usage count if so
+// 3) Scan the list of used pointers, and check for any with usage == 0
+
+// NEED OT EXTEND FOR TOP_DOWN heap.....		
+		
+void MemView_FindLeaks()
+{
+#ifdef	__LINKED_LIST_HEAP__
+#if 0
+		p_used  =  (uint32*)_mem_dump_start;		
+		num_used = 0;
+		printf ("Counting blocks....");		
+		num_used += MemView_CountBlocks(Mem::Manager::sHandle().BottomUpHeap()->first_block()); 
+		num_used += MemView_CountBlocks(Mem::Manager::sHandle().TopDownHeap()->first_block()); 
+		num_used += MemView_CountBlocks(Mem::Manager::sHandle().FrontEndHeap()->first_block()); 
+		num_used += MemView_CountBlocks(Mem::Manager::sHandle().NetworkHeap()->first_block()); 
+		num_used += MemView_CountBlocks(Mem::Manager::sHandle().ScriptHeap()->first_block()); 
+		num_used += MemView_CountBlocks(Mem::Manager::sHandle().SkaterHeap(0)->first_block()); 
+//		num_used += MemView_CountBlocks(Mem::Manager::sHandle().DebugHeap()->first_block()); 
+		printf (" %d\n",num_used);
+		printf ("Sorting .....\n");			
+		// Now we've done that, let's sort the list, so we can use a binary search later
+		uint32 *p_top  =  (uint32*)_mem_dump_start;		
+		for	(int i = 0;i= 10)
+		{
+			printf ("Stopping after %d refs\n",count);
+			return;
+		}
+		if (p_start >= p_end)
+		{
+			printf ("No more References Found in heap \n");
+			return;
+		}
+	}
+#endif
+#endif
+}
+
+// Find the first block in the free list
+// if no free blocks, then return
+// scan all used blocks, and print out the info for all the blocks
+// that have an address above the first free block
+void MemView_DumpFragments(Mem::Heap *pHeap)
+{
+#	ifdef __LINKED_LIST_HEAP__ 
+
+	char os[256];
+
+	if (!pHeap->mFreeBlocks.m_count)
+	{
+		OutputDebugString( "NO Fragmentation\n" );
+		return;
+	}
+	
+	if( !pHeap->mp_context->mp_free_list )
+	{
+		sprintf( os, "!!!!!! No free list, but there are %d free blocks???\n",pHeap->mFreeBlocks.m_count);
+		OutputDebugString( os );
+		return;
+	}
+
+	Mem::Allocator::BlockHeader *p_free = pHeap->mp_context->mp_free_list;
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+	
+	sprintf( os, "!!!!!! Free list starts at %p\n",p_free );
+	OutputDebugString( os );
+
+	// The first p_free will be the start of fragmentations
+	while (p_full)
+	{
+		if (p_full > p_free)
+		{
+			OutputDebugString( "Framgented Block: " );
+			void * p_start = (void*)((uint)p_full + Mem::Allocator::BlockHeader::sSize);
+			MemView_DumpBlockInfo((int)p_start);
+		}
+		p_full = p_full->mp_next_used;
+	}
+	#endif
+}
+
+void MemView_DumpHeap( Mem::Heap *pHeap )
+{
+	#ifdef	__LINKED_LIST_HEAP__    
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+	
+	// The first p_free will be the start of fragmentations
+	while( p_full )
+	{
+		printf ("\n");
+		void * p_start = (void*)((uint)p_full + Mem::Allocator::BlockHeader::sSize);
+		MemView_DumpBlockInfo((int)p_start);
+		p_full = p_full->mp_next_used;
+	}
+	#endif
+}
+
+
+
+void MemView_DumpBottomFragments()
+{
+
+	MemView_DumpFragments(Mem::Manager::sHandle().BottomUpHeap());
+}
+
+void MemView_DumpTopFragments()
+{
+
+	MemView_DumpFragments(Mem::Manager::sHandle().TopDownHeap());
+}
+
+
+
+/*
+class CCallStack
+{
+public:
+	void Append(CCallStack *p);
+	void Remove();
+	void InitHead();
+	int	used;
+	int	size;
+	CCallStack *pNext;
+	CCallStack *pPrev;
+	int	addr[STACKDEPTH];
+
+};
+*/
+
+struct SBlockType
+{
+	int	return_addr; 			// first meaningful return addr 
+	
+	int	size;					// size of block (if we want to sort by it
+	int	total;					// total size of this type
+	int	actual;					// actual total size, including headers
+	char *p_class;				// points to class node 
+	
+	int	count;
+};
+
+// scan throught the list of "used" blocks
+// and sort them into a list, organized by "type"
+// the "type" is determined by the first return address after
+// a callstack entry that is either "Malloc" or "Spt::Class::operator new"
+// the "type" is furthur sorted by either "size" or "Class"
+// where "size" is the size of the block (for a Malloc)
+// and "Class" is the type of class that constructed this block 
+
+#define	MAX_TYPES 10000
+
+
+void MemView_DumpAnalysis( SBlockType* blocks, int numBlocksToPrint )
+{
+#ifdef	__LINKED_LIST_HEAP__
+	// Sorts the types, and print out totals
+
+	int temp;
+
+	for (int i = 0; i < numBlocksToPrint; i++)
+	{
+		for (int j = i+1;jsize;	  	// size is the only thing we know for sure
+	int	return_addr = 0;				// default unknown return address
+	char *p_class = "not a class";
+	int latest = 1;
+	int i = 0;
+	
+	for ( i = 1; i < 8; i++ )
+	{
+		int xsize;
+
+		/*
+		// the types of call stack we may encounter:
+		// need to 
+			0x10be48: Mem::Heap::allocate                                                   
+			0x109914: Mem::Manager::New                                                     
+			0x1035b0: Spt::Class::operator new                                              
+			0x161094: Front::KeyboardControl::sCreateInstance   
+
+			0x10be48: Mem::Heap::allocate                                                   
+			0x109914: Mem::Manager::New                                                     
+			0x10a150: Malloc                                                                
+			0x222df8: _SkyBuildPktForUpLoadAlignedContiguousRectangle  
+
+			0x10be48: Mem::Heap::allocate                                                   
+			0x109914: Mem::Manager::New                                                     
+			0x10a210: Malloc_FreeList                                                       
+			0x257034: _rwFreeListAllocReal
+		*/
+
+		char *p_name = MemView_GetFunctionName(pCallStack->addr[i],&xsize);
+		if (!strcmp("Malloc",p_name) 
+			|| !strcmp("Spt::Class::operator new",p_name)
+			|| !strcmp("Mem::Manager::New",p_name)
+			|| !strcmp("_rwFreeListAllocReal",p_name))
+		{
+			latest = i;
+		}
+	}
+
+	if (latest != 1)
+	{
+		return_addr = pCallStack->addr[latest+1];
+	}
+
+	p_class = MemView_GetClassName(pCallStack);				// get class
+	// right, now we have all the info on this block
+	// let's see if we've got one just like it
+
+	//		if (!p_class && !MemView_GetFunctionName(return_addr,&temp))
+	/*		
+			if (!return_addr)
+			{
+				for (int i = 0;i>%s<<\n",i,MemView_GetFunctionName(p->addr[i],&temp));
+
+				}
+				return;
+
+			}
+	*/
+	
+									  
+	// check if it is a string, and print it out, if so					
+/*	
+	int temp;				  
+	if (!strcmp("Str::String::copy",MemView_GetFunctionName(return_addr,&temp)))
+	{
+		printf ("String <%s>\n",(char*)((char*)(pCallStack->pBlock)+32)); 
+	}
+	
+	
+	if (!strcmp("Front::VerticalMenu::sCreateInstance",MemView_GetFunctionName(return_addr,&temp)))
+	{
+		void *p_start  =  (void*)((char*)(pCallStack->pBlock)+32);
+		printf ("\nVertical Menu "); 
+		
+		Front::ScreenElement *pV =  (Front::ScreenElement *)p_start;
+		printf (" id = %s\n", Script::FindChecksumName(pV->GetID()));
+//		MemView_DumpBlockInfo((int)p_start);
+		
+	}	
+*/	
+
+	// check to see if this block is already included
+	for ( i = 0; i < num; i++ )
+	{
+		if ( pBlocks[i].p_class == p_class
+			/*&& pBlocks[i].size == size */
+			 && pBlocks[i].return_addr == return_addr )
+		{
+			pBlocks[i].count++;
+			pBlocks[i].total += size;
+			pBlocks[i].actual += size + Mem::Allocator::BlockHeader::sSize;
+			break;				
+		}
+	}
+
+	// if not, then add the block
+	if ( i == num )
+	{
+		pBlocks[i].p_class = p_class;
+		pBlocks[i].size = size;
+		pBlocks[i].total = size;
+		pBlocks[i].actual = size + Mem::Allocator::BlockHeader::sSize;
+		pBlocks[i].return_addr = return_addr;
+		pBlocks[i].count = 1;
+		num++;
+	}
+}
+
+#ifdef	__LINKED_LIST_HEAP__
+static int bbb = 0;	   	// compiler patch var, see below
+#endif
+
+void MemView_AnalyzeBlocks(uint32 mask)
+{
+#ifdef	__LINKED_LIST_HEAP__
+#if 0
+	SBlockType  *pBlocks = (SBlockType  *)_mem_dump_start;	// temp memory
+	int	num_blocks = 0;
+	int num = 0;
+
+	printf ("\nAnalyzing blocks....\n");
+	
+	CCallStack *p = used_list.pNext;  
+	while (p != &used_list)
+	{
+		// Get the actualy block we referred to
+//		Mem::Allocator::BlockHeader * pBlock = p->pBlock;
+//		void * p_start = (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize);
+		// Otionally check to see if it on the front end heap
+//		if (Mem::SameContext(p_start,Mem::Manager::sHandle().FrontEndHeap()))
+		{
+			if (!mask || !(p->flags & mask))
+			{
+				MemView_AnalyzeCallStack( p, pBlocks, num );			
+				num_blocks++;
+			}
+		}
+		p = p->pNext; 
+	}
+	
+	printf ("%d types, in %d total blocks\n", num, num_blocks); 
+
+	MemView_DumpAnalysis( pBlocks, num );
+	if (bbb)
+	{
+		MemView_DumpBottomFragments();			// just to get it compiling 
+		MemView_DumpTopFragments();			// just to get it compiling 
+	}		
+#endif
+#endif
+}
+
+
+void MemView_MarkBlocks(uint32 mask)
+{
+#ifdef	__LINKED_LIST_HEAP__
+
+	CCallStack *p = used_list.pNext;  
+	while (p != &used_list)
+	{
+		p->flags |= mask;
+		
+		p = p->pNext;
+	}
+#endif
+}
+
+
+
+void MemView_Input(uint buttons, uint makes, uint breaks)
+{
+
+	if (Config::CD())
+	{
+		return;
+	}	
+
+
+//	if (makes & Inp::Data::mD_TRIANGLE)
+//	{
+//		MemView_Active = !MemView_Active;
+//	}
+
+
+	if (!MemView_Active)
+	{
+		return;
+	}
+
+					  
+	float step1 = step;
+	
+	float zoom = 1.1f;
+	
+	float scroll = 4.0f;
+
+	
+
+
+
+	if (buttons & Inp::Data::mD_LEFT)
+	{
+		step1 = step * zoom;
+	}
+	if (buttons & Inp::Data::mD_RIGHT)
+	{
+		step1 = step / zoom;
+	}
+	
+	if (buttons & Inp::Data::mD_UP)
+	{
+//		start = start - scroll * 512.0f * 2.0f * step;
+		cursor = cursor - scroll * 512.0f * 2.0f * step;
+	}
+	if (buttons & Inp::Data::mD_DOWN)
+	{
+//		start = start + scroll * 512.0f * 2.0f * step;
+		cursor = cursor + scroll * 512.0f * 2.0f * step;
+	}
+
+	if (buttons & Inp::Data::mD_L1)
+	{
+//		start = start - scroll * 512.0f * 2.0f * step / 256.0f;
+		cursor = cursor - scroll * 2.0f * 2.0f * step;
+	}
+	if (buttons & Inp::Data::mD_L2)
+	{
+//		start = start + scroll * 512.0f * 2.0f * step / 256.0f;
+		cursor = cursor + scroll * 2.0f * 2.0f * step;
+	}
+
+#define 	MMMIN 	(0.0078125f)
+	 				
+	if (step1 1024.0f)
+	{
+		step1 = 1024.0f;
+	}
+
+//	start = start + (512.0f * 2.0f * 128.0f * (step - step1));
+
+	step = step1;
+
+	if (makes & Inp::Data::mD_CIRCLE)
+	{
+		if (blockstart)
+		{
+			MemView_DumpRefs(blockstart);
+		}
+//		MemView_MarkBlocks(1);
+	}
+
+	// We don't look for leaks automatically now, so I'v put it on "SQUARE"	
+	if (makes & Inp::Data::mD_SQUARE)
+	{
+		MemView_FindLeaks();
+//	Mem::Manager& mem_man = Mem::Manager::sHandle();		MemView_DumpHeap(1);
+//	heap	= mem_man.TopDownHeap();
+//	MemView_DumpFragments(heap);
+//	MemView_DumpHeap(heap,1);
+
+	}
+
+	if (makes & Inp::Data::mD_X)
+	{
+		MemView_AnalyzeBlocks();
+	}
+
+	// Triangle = Dump Fragmentation
+/*	if (makes & Inp::Data::mD_TRIANGLE)
+	{
+		Mem::Manager& mem_man = Mem::Manager::sHandle();
+		Mem::Heap* heap = mem_man.BottomUpHeap();
+		Mem::Region* region = heap->ParentRegion();
+		printf ("BottomUp Frag %dK, %d Blocks\n",heap->mFreeMem.m_count / 1024, heap->mFreeBlocks.m_count);
+		printf ("Region %d/%d K", region->MemAvailable() / 1024, region->TotalSize() / 1024 );
+		MemView_DumpFragments(heap);
+	}
+*/	
+
+}
+
+void MemView_AnalyzeHeap(Mem::Heap *pHeap)
+{
+	if ( !pHeap )
+		return;
+	
+#ifdef	__LINKED_LIST_HEAP__
+#if 0
+	SBlockType  *pBlocks = (SBlockType  *)_mem_dump_start;	// temp memory
+	int	num_blocks = 0;
+	int num = 0;
+
+	Mem::Allocator::BlockHeader *p_full = pHeap->mp_context->mp_used_list;
+
+	while (p_full)
+	{
+		CCallStack* pCallStack = (CCallStack*)p_full->mp_debug_data;
+
+		if ( pCallStack )
+		{
+			MemView_AnalyzeCallStack( pCallStack, pBlocks, num );
+		}
+		else
+		{
+			printf ("Block with No Debug Info!!\n");
+		}
+
+		p_full = p_full->mp_next_used;
+	}
+
+	printf ("%d types, in %d total blocks\n", num, num_blocks); 
+
+	MemView_DumpAnalysis( pBlocks, num );
+#endif
+#endif
+}
+  
+  
+
diff --git a/Code/Gfx/XBox/p_memview.h b/Code/Gfx/XBox/p_memview.h
new file mode 100644
index 0000000..1a63ec5
--- /dev/null
+++ b/Code/Gfx/XBox/p_memview.h
@@ -0,0 +1,20 @@
+////
+
+#ifndef	__P_MEMVIEW_H__
+#define	__P_MEMVIEW_H__
+
+void MemView_Display();
+void MemView_Input(uint buttons, uint makes, uint breaks);
+void MemView_Alloc( void *v);
+void MemView_Free( void *v);
+void MemViewToggle();
+void MemView_FindLeaks();
+int DumpUnwindStack( int iMaxDepth, int *pDest );
+char *MemView_GetFunctionName(int pc, int *p_size);
+void MemView_DumpFragments(Mem::Heap *pHeap);
+void MemView_AnalyzeBlocks(uint32 mask = 0);
+void MemView_MarkBlocks(uint32 flags = 1 );
+void MemView_DumpHeap(Mem::Heap *pHeap);
+void MemView_AnalyzeHeap(Mem::Heap *pHeap);
+#endif
+
diff --git a/Code/Gfx/XBox/p_nx.cpp b/Code/Gfx/XBox/p_nx.cpp
new file mode 100644
index 0000000..19da655
--- /dev/null
+++ b/Code/Gfx/XBox/p_nx.cpp
@@ -0,0 +1,1245 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:						 		 									**
+**																			**
+**	File name:		gfx/xbox/p_nx.cpp										**
+**																			**
+**	Created:		01/16/2002	-	dc										**
+**																			**
+**	Description:	Xbox platform specific interface to the engine			**
+**					This is Xbox SPECIFIC!!!!!! If there is anything in		**
+**					here that is not Xbox specific, then it needs to be		**
+**					in nx.cpp												**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include	
+#include	
+#include	
+
+#include	"gfx\camera.h"
+#include	"gfx\gfxman.h"
+#include	"gfx\nx.h"
+#include	"gfx\nxtexman.h"
+#include	"gfx\nxviewman.h"
+#include	"gfx\NxQuickAnim.h"
+#include	"gfx\NxParticleMgr.h"
+#include	"gfx\NxMiscFX.h"
+#include	"gfx\debuggfx.h"
+#include	"gfx\xbox\p_NxSector.h"
+#include	"gfx\xbox\p_NxScene.h"
+#include	"gfx\xbox\p_NxModel.h"
+#include	"gfx\xbox\p_NxGeom.h"
+#include	"gfx\xbox\p_NxMesh.h"
+#include	"gfx\xbox\p_NxSprite.h"
+#include	"gfx\xbox\p_NxTexture.h"
+#include	"gfx\xbox\p_NxParticle.h"
+#include	"gfx\xbox\p_NxTextured3dPoly.h"
+#include	"gfx\xbox\p_NxNewParticleMgr.h"
+#include	"gfx\xbox\p_NxWeather.h"
+#include	"core\math.h"
+#include 	"sk\engine\SuperSector.h"					
+#include 	"gel\scripting\script.h"
+
+#include 	"gfx\xbox\nx\nx_init.h"
+#include 	"gfx\xbox\nx\texture.h"
+#include 	"gfx\xbox\nx\material.h"
+#include 	"gfx\xbox\nx\render.h"
+#include 	"gfx\xbox\nx\screenfx.h"
+#include 	"gfx\xbox\nx\occlude.h"
+#include 	"gfx\xbox\nx\scene.h"
+#include 	"gfx\xbox\nx\chars.h"
+
+#include	"gel\music\xbox\p_soundtrack.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_start_engine( void )
+{
+	NxXbox::InitialiseEngine();
+
+	mp_particle_manager	= new CXboxNewParticleManager;
+	mp_weather			= new CXboxWeather;
+
+	// If the user selected widescreen from the dashboard, reset the default screen angle and aspect ratio.
+	if( XGetVideoFlags() & ( XC_VIDEO_FLAGS_WIDESCREEN | XC_VIDEO_FLAGS_HDTV_720p ))
+	{
+		Script::RunScript( "screen_setup_widescreen" );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_pre_render( void )
+{
+	// The screen clear is now added to the push buffer directly after the swap, in s_plat_post_render().
+
+	// No rendering should take place whilst the loading screen is visible.
+//	if( !NxXbox::EngineGlobals.loadingscreen_visible )
+//	{
+//		D3DCOLOR col = 0x00506070;
+//
+//		if( NxXbox::EngineGlobals.screen_blur > 0 )
+//		{
+//			D3DDevice_Clear( 0, NULL, D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, col, 1.0f, 0 );
+//			++NxXbox::EngineGlobals.screen_blur_duration;
+//		}
+//		else
+//		{
+//			D3DDevice_Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, col, 1.0f, 0 );
+//			NxXbox::EngineGlobals.screen_blur_duration = 0;
+//		}
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_post_render( void )
+{
+	// No rendering should take place whilst the loading screen is visible.
+	if( !NxXbox::EngineGlobals.loadingscreen_visible )
+	{
+		D3DDevice_Swap( D3DSWAP_DEFAULT );
+
+		if( NxXbox::EngineGlobals.screenshot_name[0] != 0 )
+		{
+			Spt::SingletonPtr< Gfx::Manager > gfx_manager;
+			gfx_manager->ScreenShot( NxXbox::EngineGlobals.screenshot_name );
+			NxXbox::EngineGlobals.screenshot_name[0] = 0;
+		}
+
+		// Now that the swap instruction has been pushed, clear the buffer for next frame.
+		if( NxXbox::EngineGlobals.screen_blur > 0 )
+		{
+			D3DDevice_Clear( 0, NULL, D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, NxXbox::EngineGlobals.clear_color, 1.0f, 0 );
+			++NxXbox::EngineGlobals.screen_blur_duration;
+		}
+		else
+		{
+			if( NxXbox::EngineGlobals.letterbox_active )
+			{
+				D3DDevice_Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0x00000000UL, 1.0f, 0 );
+			}
+			else if( NxXbox::EngineGlobals.clear_color_buffer )
+			{
+				D3DDevice_Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, NxXbox::EngineGlobals.clear_color, 1.0f, 0 );
+			}
+			else
+			{
+				D3DDevice_Clear( 0, NULL, D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, NxXbox::EngineGlobals.clear_color, 1.0f, 0 );
+			}
+			NxXbox::EngineGlobals.screen_blur_duration = 0;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_render_world( void )
+{
+	// Remove the loading bar.
+	if( NxXbox::EngineGlobals.loadingbar_timer_event != 0 )
+	{
+		timeKillEvent( NxXbox::EngineGlobals.loadingbar_timer_event );
+		NxXbox::EngineGlobals.loadingbar_timer_event = 0;
+	}
+
+	// No rendering should take place whilst the loading screen is visible.
+	if( !NxXbox::EngineGlobals.loadingscreen_visible )
+	{
+		// Store time at start of render, used as reference throughout.
+		NxXbox::EngineGlobals.render_start_time = (int)Tmr::GetTime();
+
+		// Render objects of interest for the render target (shadow objects).
+		NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+		NxXbox::set_render_state( RS_ZTESTENABLE,	1 );
+
+		NxXbox::render_shadow_targets();
+
+		CEngine::sGetImposterManager()->ProcessImposters();
+
+		// Start up the screen blur if we're using it.
+		NxXbox::start_screen_blur();
+
+		int num_viewports = CViewportManager::sGetNumActiveViewports();
+		for( int v = 0; v < num_viewports; ++v )
+		{
+			CViewport	*p_cur_viewport	= CViewportManager::sGetActiveViewport( v );
+			Gfx::Camera	*p_cur_camera	= p_cur_viewport->GetCamera();
+			
+			// Check for a valid camera.
+			if( p_cur_camera == NULL )
+			{
+				continue;
+			}
+		
+			NxXbox::EngineGlobals.viewport.X		= (DWORD)( p_cur_viewport->GetOriginX() * NxXbox::EngineGlobals.backbuffer_width );
+			NxXbox::EngineGlobals.viewport.Y		= (DWORD)( p_cur_viewport->GetOriginY() * NxXbox::EngineGlobals.backbuffer_height );
+			NxXbox::EngineGlobals.viewport.Width	= (DWORD)( p_cur_viewport->GetWidth() * NxXbox::EngineGlobals.backbuffer_width );
+			NxXbox::EngineGlobals.viewport.Height	= (DWORD)( p_cur_viewport->GetHeight() * NxXbox::EngineGlobals.backbuffer_height );
+			NxXbox::EngineGlobals.viewport.MinZ		= 0.0f;
+			NxXbox::EngineGlobals.viewport.MaxZ		= 1.0f;
+
+			if( NxXbox::EngineGlobals.letterbox_active )
+			{	
+				NxXbox::EngineGlobals.viewport.Y		+= (DWORD)( NxXbox::EngineGlobals.backbuffer_height / 8 );
+				NxXbox::EngineGlobals.viewport.Height	-= (DWORD)( NxXbox::EngineGlobals.backbuffer_height / 4 );
+			}
+
+			D3DDevice_SetViewport( &NxXbox::EngineGlobals.viewport );
+
+			// There is no bounding box transform for rendering the world.
+			NxXbox::set_frustum_bbox_transform( NULL );
+		
+			// Set up the camera..
+			float aspect_ratio = p_cur_viewport->GetAspectRatio();
+
+			NxXbox::set_camera( &( p_cur_camera->GetMatrix()), &( p_cur_camera->GetPos()), p_cur_camera->GetAdjustedHFOV(), aspect_ratio );
+
+			// Render the non-sky world scenes.
+			for( int i = 0; i < MAX_LOADED_SCENES; i++ )
+			{
+				if( sp_loaded_scenes[i] )
+				{
+					CXboxScene *pXboxScene = static_cast( sp_loaded_scenes[i] );
+
+					if( !pXboxScene->IsSky())
+					{
+						// Build relevant occlusion poly list, now that the camera is set.
+						NxXbox::BuildOccluders( &( p_cur_camera->GetPos()), v );
+
+						NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+						NxXbox::set_render_state( RS_ZTESTENABLE,	1 );
+
+						// Flag this scene as receiving shadows.
+						pXboxScene->GetEngineScene()->m_flags |= SCENE_FLAG_RECEIVE_SHADOWS;
+
+						NxXbox::render_scene( pXboxScene->GetEngineScene(), NxXbox::vRENDER_OPAQUE |
+																			NxXbox::vRENDER_OCCLUDED |
+																			NxXbox::vRENDER_SORT_FRONT_TO_BACK |
+																			NxXbox::vRENDER_BILLBOARDS, v );
+					}
+				}
+			}
+
+			NxXbox::set_render_state( RS_ZWRITEENABLE,	0 );
+			Nx::TextureSplatRender();
+			NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+
+			CEngine::sGetImposterManager()->DrawImposters();
+
+			// Render all opaque instances.
+			NxXbox::render_instances( NxXbox::vRENDER_OPAQUE );
+		
+			// Now that opaque geometry is drawn, do the render tests for the light glows.
+			NxXbox::render_light_glows( true );
+
+			// Render the sky, followed by all the non-sky semitransparent scene geometry. There is no bounding box transform for rendering the world.
+			NxXbox::set_frustum_bbox_transform( NULL );
+
+			// Set up the sky camera.
+			Mth::Vector	centre_pos( 0.0f, 0.0f, 0.0f );
+			NxXbox::set_camera( &( p_cur_camera->GetMatrix()), ¢re_pos, p_cur_camera->GetAdjustedHFOV(), aspect_ratio, true );
+
+			// Render the sky. We have to fudge the fog here to ensure the sky is fully fogged, since it is rendered with a non-standard projection
+			// matrix to ensure a constant z=1, but which breaks the fog interpolation value.
+			float fog_start = NxXbox::EngineGlobals.fog_start;
+			float fog_end	= NxXbox::EngineGlobals.fog_end;
+			NxXbox::EngineGlobals.fog_start	= -20.0f;
+			NxXbox::EngineGlobals.fog_end	= -21.0f;
+			D3DDevice_SetRenderState( D3DRS_FOGSTART,	*((DWORD*)( &NxXbox::EngineGlobals.fog_start )));
+			D3DDevice_SetRenderState( D3DRS_FOGEND,		*((DWORD*)( &NxXbox::EngineGlobals.fog_end )));
+
+			NxXbox::set_render_state( RS_ZWRITEENABLE,	0 );
+			NxXbox::set_render_state( RS_ZTESTENABLE,	1 );
+			for( int i = 0; i < MAX_LOADED_SCENES; i++ )
+			{
+				if( sp_loaded_scenes[i] )
+				{
+					CXboxScene *pXboxScene = static_cast( sp_loaded_scenes[i] );
+					if( pXboxScene->IsSky())
+					{
+						// No anisotropic filtering for the sky.
+						DWORD stage_zero_minfilter, stage_zero_mipfilter;
+						D3DDevice_GetTextureStageState( 0, D3DTSS_MINFILTER, &stage_zero_minfilter );
+						D3DDevice_GetTextureStageState( 0, D3DTSS_MIPFILTER, &stage_zero_mipfilter );
+						D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+						D3DDevice_SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_NONE );
+
+						NxXbox::render_scene( pXboxScene->GetEngineScene(), NxXbox::vRENDER_OPAQUE | NxXbox::vRENDER_SEMITRANSPARENT, v );
+			
+						D3DDevice_SetTextureStageState( 0, D3DTSS_MIPFILTER, stage_zero_mipfilter );
+						D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, stage_zero_minfilter );
+					}
+				}
+			}
+			NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+
+			// Restore fog values.
+			NxXbox::EngineGlobals.fog_start	= fog_start;
+			NxXbox::EngineGlobals.fog_end	= fog_end;
+			D3DDevice_SetRenderState( D3DRS_FOGSTART,	*((DWORD*)( &NxXbox::EngineGlobals.fog_start )));
+			D3DDevice_SetRenderState( D3DRS_FOGEND,		*((DWORD*)( &NxXbox::EngineGlobals.fog_end )));
+
+			// Revert to the regular camera.
+			NxXbox::set_camera( &( p_cur_camera->GetMatrix()), &( p_cur_camera->GetPos()), p_cur_camera->GetAdjustedHFOV(), aspect_ratio );
+
+			// Render all semitransparent instances.
+			NxXbox::render_instances( NxXbox::vRENDER_SEMITRANSPARENT | NxXbox::vRENDER_INSTANCE_PRE_WORLD_SEMITRANSPARENT );
+
+			// Render the non-sky semitransparent scene geometry.
+			// Setting the depth clip control to clamp here means that semitransparent periphary objects that would usually cull out
+			// are now drawn correctly (since they will clamp at 1.0, and the z test is <=).
+			D3DDevice_SetRenderState( D3DRS_DEPTHCLIPCONTROL, D3DDCC_CLAMP );
+			NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+			NxXbox::set_render_state( RS_ZTESTENABLE,	1 );
+			for( int i = 0; i < MAX_LOADED_SCENES; i++ )
+			{
+				if( sp_loaded_scenes[i] )
+				{
+					CXboxScene *pXboxScene = static_cast( sp_loaded_scenes[i] );
+					if( !pXboxScene->IsSky())
+					{
+						// Build relevant occlusion poly list, now that the camera is set.
+						NxXbox::render_scene( pXboxScene->GetEngineScene(), NxXbox::vRENDER_SEMITRANSPARENT |
+																			NxXbox::vRENDER_OCCLUDED |
+																			NxXbox::vRENDER_BILLBOARDS, v );
+					}
+				}
+			}
+			D3DDevice_SetRenderState( D3DRS_DEPTHCLIPCONTROL, D3DDCC_CULLPRIMITIVE );
+
+			// Render the particles.
+			NxXbox::set_render_state( RS_ZWRITEENABLE,	0 );
+			render_particles();
+
+			// New style particles. Update should probably be somewhere else.
+			mp_particle_manager->UpdateParticles();
+			mp_particle_manager->RenderParticles();
+
+			// Render weather effects.
+			mp_weather->Process( Tmr::FrameLength());
+			mp_weather->Render();
+
+			NxXbox::set_render_state( RS_ZWRITEENABLE,	1 );
+
+			// We want shatter objects to be rendered after the sky since they will typically be semitransparent.
+			Nx::ShatterRender();
+
+			// Render all semitransparent instances.
+			NxXbox::render_instances( NxXbox::vRENDER_SEMITRANSPARENT | NxXbox::vRENDER_INSTANCE_POST_WORLD_SEMITRANSPARENT );
+
+			// Render the light glows (the visibility tests were performed earlier).
+			NxXbox::render_light_glows( false );
+
+			// Render the shadow volumes.
+//			for( int i = 0; i < MAX_LOADED_SCENES; i++ )
+//			{
+//				if( sp_loaded_scenes[i] )
+//				{
+//					CXboxScene *pXboxScene = static_cast( sp_loaded_scenes[i] );
+//					if( !pXboxScene->IsSky())
+//					{
+//						NxXbox::render_scene( pXboxScene->GetEngineScene(), NxXbox::vRENDER_SHADOW_VOLUMES, v );
+//					}
+//				}
+//			}
+			
+			Nx::ScreenFlashRender( v, 0 );
+		}
+
+		// This x86 instruction writes back and invalidates the cache. This should hopefully mean that all updated vertex buffer
+		// data (vc wibble) are flushed out by the time the GPU hits them.
+		_asm
+		{
+			wbinvd;
+		}
+
+		// Draw debug lines.
+#		ifdef __NOPT_ASSERT__
+		Gfx::DebugGfx_Draw();
+#		endif
+
+		// Reset viewport so text appears on full screen.
+		NxXbox::EngineGlobals.viewport.X		= 0;
+		NxXbox::EngineGlobals.viewport.Y		= 0;
+		NxXbox::EngineGlobals.viewport.Width	= NxXbox::EngineGlobals.backbuffer_width;
+		NxXbox::EngineGlobals.viewport.Height	= NxXbox::EngineGlobals.backbuffer_height;
+		NxXbox::EngineGlobals.viewport.MinZ		= 0.0f;
+		NxXbox::EngineGlobals.viewport.MaxZ		= 1.0f;
+
+		if( NxXbox::EngineGlobals.letterbox_active )
+		{	
+			NxXbox::EngineGlobals.viewport.Y		+= (DWORD)( NxXbox::EngineGlobals.backbuffer_height / 8 );
+			NxXbox::EngineGlobals.viewport.Height	-= (DWORD)( NxXbox::EngineGlobals.backbuffer_height / 4 );
+		}
+
+		D3DDevice_SetViewport( &NxXbox::EngineGlobals.viewport );
+
+//		Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+//		Mth::Vector pos = pSkater->GetPos();
+//		NxXbox::set_focus_blur_focus( pos, 0.0f, 10.0f * 12.0f, 100.0f * 12.0f );
+//		NxXbox::finish_focus_blur();
+
+		// Horrible hack - this should be somewhere else ASAP.
+		NxXbox::SDraw2D::DrawAll();
+
+		// Finish the screen blur if we're using it.
+		NxXbox::finish_screen_blur();
+	}
+
+	// Reset viewport so screen clear will work.
+	NxXbox::EngineGlobals.viewport.X		= 0;
+	NxXbox::EngineGlobals.viewport.Y		= 0;
+	NxXbox::EngineGlobals.viewport.Width	= NxXbox::EngineGlobals.backbuffer_width;
+	NxXbox::EngineGlobals.viewport.Height	= NxXbox::EngineGlobals.backbuffer_height;
+	NxXbox::EngineGlobals.viewport.MinZ		= 0.0f;
+	NxXbox::EngineGlobals.viewport.MaxZ		= 1.0f;
+	D3DDevice_SetViewport( &NxXbox::EngineGlobals.viewport );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CScene	*CEngine::s_plat_create_scene( const char *p_name, CTexDict *p_tex_dict, bool add_super_sectors )
+{
+	// Create scene class instance
+	CXboxScene	*p_xbox_scene	= new CXboxScene;
+	CScene		*p_new_scene	= p_xbox_scene;
+	p_new_scene->SetInSuperSectors( add_super_sectors );
+	p_new_scene->SetIsSky( false );
+
+	// Create a new sScene so the engine can track assets for this scene.
+	NxXbox::sScene *p_engine_scene = new NxXbox::sScene();
+	p_xbox_scene->SetEngineScene( p_engine_scene );
+
+	return p_new_scene;
+}
+
+
+
+#define MemoryRead( dst, size, num, src )	CopyMemory(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CScene *CEngine::s_plat_load_scene_from_memory( void *p_mem, CTexDict *p_tex_dict, bool add_super_sectors, bool is_sky, bool is_dictionary )
+{
+	uint8			*p_data = (uint8*)p_mem;
+	CSector			*pSector;
+	CXboxSector		*pXboxSector;
+
+	// Create a new sScene so the engine can track assets for this scene.
+	NxXbox::sScene *p_engine_scene = new NxXbox::sScene();
+
+	// Set the dictionary flag.
+	p_engine_scene->m_is_dictionary	= is_dictionary;
+
+	// Version numbers.
+	uint32 mat_version, mesh_version, vert_version;
+	MemoryRead( &mat_version, sizeof( uint32 ), 1, p_data );
+	MemoryRead( &mesh_version, sizeof( uint32 ), 1, p_data );
+	MemoryRead( &vert_version, sizeof( uint32 ), 1, p_data );
+
+	// Import materials (they will now be associated at the engine-level with this scene).
+	p_engine_scene->pMaterialTable = NxXbox::LoadMaterialsFromMemory( (void**)&p_data, p_tex_dict->GetTexLookup());
+
+	// Read number of sectors.
+	int num_sectors;
+	MemoryRead( &num_sectors, sizeof( int ), 1, p_data );
+
+	// Figure optimum hash table lookup size.
+	uint32 optimal_table_size	= num_sectors * 2;
+	uint32 test					= 2;
+	uint32 size					= 1;
+	for( ;; test <<= 1, ++size )
+	{
+		// Check if this iteration of table size is sufficient, or if we have hit the maximum size.
+		if(( optimal_table_size <= test ) || ( size >= 12 ))
+		{
+			break;
+		}
+	}
+
+	// Create scene class instance, using optimum size sector table.
+	CScene* new_scene = new CXboxScene( size );
+	new_scene->SetInSuperSectors( add_super_sectors );
+	new_scene->SetIsSky( is_sky );
+
+	// Get a scene id from the engine.
+	CXboxScene *p_new_xbox_scene = static_cast( new_scene );
+	p_new_xbox_scene->SetEngineScene( p_engine_scene );
+
+	for( int s = 0; s < num_sectors; ++s )
+	{
+		// Create a new sector to hold the incoming details.
+		pSector						= p_new_xbox_scene->CreateSector();
+		pXboxSector					= static_cast( pSector );
+		
+		// Generate a hanging geom for the sector, used for creating level objects etc.
+		CXboxGeom	*p_xbox_geom	= new CXboxGeom();
+		p_xbox_geom->SetScene( p_new_xbox_scene );
+		pXboxSector->SetGeom( p_xbox_geom );
+		
+		// Prepare CXboxGeom for receiving data.
+		p_xbox_geom->InitMeshList();
+		
+		// Load sector data.
+		if( pXboxSector->LoadFromMemory( (void**)&p_data ))
+		{
+			new_scene->AddSector( pSector );
+		}
+	}
+
+	// At this point get the engine scene to figure it's bounding volumes.
+	p_engine_scene->FigureBoundingVolumes();
+	
+	// Read hierarchy information.
+	int num_hierarchy_objects;
+	MemoryRead( &num_hierarchy_objects, sizeof( int ), 1, p_data );
+
+	if( num_hierarchy_objects > 0 )
+	{
+		p_engine_scene->mp_hierarchyObjects = new CHierarchyObject[num_hierarchy_objects];
+		MemoryRead( p_engine_scene->mp_hierarchyObjects, sizeof( CHierarchyObject ), num_hierarchy_objects, p_data );
+		p_engine_scene->m_numHierarchyObjects = num_hierarchy_objects;
+	}
+
+	return new_scene;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CScene *CEngine::s_plat_load_scene( const char *p_name, CTexDict *p_tex_dict, bool add_super_sectors, bool is_sky, bool is_dictionary )
+{
+	CSector*		pSector;
+	CXboxSector*	pXboxSector;
+
+	Dbg_Message( "loading scene from file %s\n", p_name );
+
+	// Create a new NxXbox::sScene so the engine can track assets for this scene.
+	NxXbox::sScene *p_engine_scene = new NxXbox::sScene();
+
+	// Set the dictionary flag.
+	p_engine_scene->m_is_dictionary	= is_dictionary;
+
+	// Open the scene file.
+	void* p_file = File::Open( p_name, "rb" );
+	if( !p_file )
+	{
+		Dbg_MsgAssert( p_file, ( "Couldn't open scene file %s\n", p_name ));
+		return NULL;
+	}
+	
+	// Version numbers.
+	uint32 mat_version, mesh_version, vert_version;
+	File::Read( &mat_version, sizeof( uint32 ), 1, p_file );
+	File::Read( &mesh_version, sizeof( uint32 ), 1, p_file );
+	File::Read( &vert_version, sizeof( uint32 ), 1, p_file );
+
+	// Import materials (they will now be associated at the engine-level with this scene).
+	p_engine_scene->pMaterialTable = NxXbox::LoadMaterials( p_file, p_tex_dict->GetTexLookup());
+
+	// Read number of sectors.
+	int num_sectors;
+	File::Read( &num_sectors, sizeof( int ), 1, p_file );
+
+	// Figure optimum hash table lookup size.
+	uint32 optimal_table_size	= num_sectors * 2;
+	uint32 test					= 2;
+	uint32 size					= 1;
+	for( ;; test <<= 1, ++size )
+	{
+		// Check if this iteration of table size is sufficient, or if we have hit the maximum size.
+		if(( optimal_table_size <= test ) || ( size >= 12 ))
+		{
+			break;
+		}
+	}
+
+	// Create scene class instance, using optimum size sector table.
+	CScene* new_scene = new CXboxScene( size );
+	new_scene->SetInSuperSectors( add_super_sectors );
+	new_scene->SetIsSky( is_sky );
+
+	// Get a scene id from the engine.
+	CXboxScene *p_new_xbox_scene = static_cast( new_scene );
+	p_new_xbox_scene->SetEngineScene( p_engine_scene );
+
+	for( int s = 0; s < num_sectors; ++s )
+	{
+		// Create a new sector to hold the incoming details.
+		pSector						= p_new_xbox_scene->CreateSector();
+		pXboxSector					= static_cast( pSector );
+		
+		// Generate a hanging geom for the sector, used for creating level objects etc.
+		CXboxGeom	*p_xbox_geom	= new CXboxGeom();
+		p_xbox_geom->SetScene( p_new_xbox_scene );
+		pXboxSector->SetGeom( p_xbox_geom );
+		
+		// Prepare CXboxGeom for receiving data.
+		p_xbox_geom->InitMeshList();
+		
+		// Load sector data.
+		if( pXboxSector->LoadFromFile( p_file ))
+		{
+			new_scene->AddSector( pSector );
+		}
+	}
+
+	// At this point get the engine scene to figure it's bounding volumes.
+	p_engine_scene->FigureBoundingVolumes();
+	
+	// Read hierarchy information.
+	int num_hierarchy_objects;
+	File::Read( &num_hierarchy_objects, sizeof( int ), 1, p_file );
+
+	if( num_hierarchy_objects > 0 )
+	{
+		p_engine_scene->mp_hierarchyObjects = new CHierarchyObject[num_hierarchy_objects];
+		File::Read( p_engine_scene->mp_hierarchyObjects, sizeof( CHierarchyObject ), num_hierarchy_objects, p_file );
+		p_engine_scene->m_numHierarchyObjects = num_hierarchy_objects;
+	}
+	
+	File::Close( p_file );
+
+	return new_scene;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_unload_scene( CScene *p_scene )
+{
+	Dbg_MsgAssert( p_scene,( "Trying to delete a NULL scene" ));
+
+	CXboxScene *p_xbox_scene = (CXboxScene*)p_scene;
+
+	// Ask the engine to remove the associated meshes for each sector in the scene.
+	p_xbox_scene->DestroySectorMeshes();
+
+	// Get the engine specific scene data and pass it to the engine to delete.
+	NxXbox::DeleteScene( p_xbox_scene->GetEngineScene());
+	p_xbox_scene->SetEngineScene( NULL );
+	
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_add_scene( CScene *p_scene, const char *p_filename )
+{
+	// Function to incrementally add geometry to a scene - should NOT be getting called on Xbox.
+	Dbg_Assert( 0 );
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//CTexDict* CEngine::s_plat_load_textures( const char* p_name )
+//{
+//	NxXbox::LoadTextureFile( p_name );
+//	return NULL;
+//}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CModel* CEngine::s_plat_init_model( void )
+{
+	CXboxModel *pModel = new CXboxModel;
+	
+	return pModel;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_uninit_model( CModel* pModel )
+{
+	delete pModel;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CGeom* CEngine::s_plat_init_geom( void )
+{
+	CXboxGeom *pGeom = new CXboxGeom;
+	return pGeom;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_uninit_geom( CGeom *p_geom )
+{
+	delete p_geom;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CQuickAnim* CEngine::s_plat_init_quick_anim()
+{
+	CQuickAnim* pQuickAnim = new CQuickAnim;
+	return pQuickAnim;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_uninit_quick_anim(CQuickAnim* pQuickAnim)
+{
+	delete pQuickAnim;
+	return;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CMesh* CEngine::s_plat_load_mesh( const char* pMeshFileName, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume )
+{
+	// Load the scene.
+	Nx::CScene *p_scene = Nx::CEngine::s_plat_load_scene( pMeshFileName, pTexDict, false, false, false );
+
+	// Store the checksum of the scene name.
+	p_scene->SetID(Script::GenerateCRC( pMeshFileName )); 	// store the checksum of the scene name
+
+	p_scene->SetTexDict( pTexDict );
+	p_scene->PostLoad( pMeshFileName );
+	
+	// Disable any scaling.
+	NxXbox::DisableMeshScaling();
+
+	CXboxMesh *pMesh = new CXboxMesh( pMeshFileName );
+
+	Nx::CXboxScene *p_xbox_scene = static_cast( p_scene );
+	pMesh->SetScene( p_xbox_scene );
+	pMesh->SetTexDict( pTexDict );
+
+	return pMesh;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CMesh* CEngine::s_plat_load_mesh( uint32 id, uint32 *p_model_data, int model_data_size, uint8 *p_cas_data, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume )
+{
+	// Convert the id into a usable string.
+	Dbg_Assert( id > 0 );
+	char id_as_string[16];
+	sprintf( id_as_string, "%d\n", id );
+
+	// Load the scene.
+	Nx::CScene *p_scene = Nx::CEngine::s_plat_load_scene_from_memory( p_model_data, pTexDict, false, false, false );
+
+	// Store the checksum of the scene name.
+	p_scene->SetID( Script::GenerateCRC( id_as_string ));
+
+	p_scene->SetTexDict( pTexDict );
+	p_scene->PostLoad( id_as_string );
+	
+	CXboxMesh *pMesh = new CXboxMesh();
+
+	// Set CAS data for mesh.
+	pMesh->SetCASData( p_cas_data );
+
+	// Disable any scaling.
+	NxXbox::DisableMeshScaling();
+
+	Nx::CXboxScene *p_xbox_scene = static_cast( p_scene );
+	pMesh->SetScene( p_xbox_scene );
+	pMesh->SetTexDict( pTexDict );
+	return pMesh;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_unload_mesh( CMesh *pMesh )
+{
+	delete pMesh;
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_mesh_scaling_parameters( SMeshScalingParameters* pParams )
+{
+	NxXbox::SetMeshScalingParameters( pParams );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSprite *CEngine::s_plat_create_sprite( CWindow2D *p_window )
+{
+	return new CXboxSprite;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CEngine::s_plat_destroy_sprite( CSprite *p_sprite )
+{
+	delete p_sprite;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextured3dPoly *	CEngine::s_plat_create_textured_3d_poly()
+{
+	return new CXboxTextured3dPoly;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		CEngine::s_plat_destroy_textured_3d_poly(CTextured3dPoly *p_poly)
+{
+	delete p_poly;
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Nx::CTexture *CEngine::s_plat_create_render_target_texture( int width, int height, int depth, int z_depth )
+{
+	// Create the CXBoxTexture (just a container class for the NxXbox::sTexture).
+	CXboxTexture *p_texture = new CXboxTexture();
+
+	// Create the NxXbox::sTexture.
+	NxXbox::sTexture *p_engine_texture = new NxXbox::sTexture;
+	p_texture->SetEngineTexture( p_engine_texture );
+	
+	// Set the texture as a render target.
+	p_engine_texture->SetRenderTarget( width, height, depth, z_depth );
+	
+	return p_texture;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_project_texture_into_scene( Nx::CTexture *p_texture, Nx::CModel *p_model, Nx::CScene *p_scene )
+{
+	Nx::CXboxTexture	*p_xbox_texture	= static_cast( p_texture );
+	Nx::CXboxModel		*p_xbox_model	= static_cast( p_model );
+//	Nx::CXboxScene		*p_xbox_scene	= static_cast( p_scene );
+//	NxXbox::create_texture_projection_details( p_xbox_texture->GetEngineTexture(), p_xbox_model, p_xbox_scene->GetEngineScene());
+	NxXbox::create_texture_projection_details( p_xbox_texture->GetEngineTexture(), p_xbox_model, NULL );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_projection_texture_camera( Nx::CTexture *p_texture, Gfx::Camera *p_camera )
+{
+	Nx::CXboxTexture	*p_xbox_texture	= static_cast( p_texture );
+	XGVECTOR3			pos( p_camera->GetPos()[X], p_camera->GetPos()[Y], p_camera->GetPos()[Z] );
+	XGVECTOR3			at = pos + D3DXVECTOR3( p_camera->GetMatrix()[Z][X], p_camera->GetMatrix()[Z][Y], p_camera->GetMatrix()[Z][Z] );
+	
+	NxXbox::set_texture_projection_camera( p_xbox_texture->GetEngineTexture(), &pos, &at );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_stop_projection_texture( Nx::CTexture *p_texture )
+{
+	Nx::CXboxTexture *p_xbox_texture = static_cast( p_texture );
+	NxXbox::destroy_texture_projection_details( p_xbox_texture->GetEngineTexture());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_add_occlusion_poly( uint32 num_verts, Mth::Vector *p_vert_array, uint32 checksum )
+{
+	if( num_verts == 4 )
+	{
+		NxXbox::AddOcclusionPoly( p_vert_array[0], p_vert_array[1], p_vert_array[2], p_vert_array[3], checksum );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_enable_occlusion_poly( uint32 checksum, bool enable )
+{
+	NxXbox::EnableOcclusionPoly( checksum, enable );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_remove_all_occlusion_polys( void )
+{
+	NxXbox::RemoveAllOcclusionPolys();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// returns true if the sphere at "center", with the "radius"
+// is visible to the current camera
+// (note, currently this is the last frame's camera on PS2)
+bool CEngine::s_plat_is_visible( Mth::Vector& center, float radius )
+{
+	return NxXbox::IsVisible( center, radius );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_max_multipass_distance( float dist )
+{
+	// Has no meaning for Xbox.
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const char* CEngine::s_plat_get_platform_extension( void )
+{
+	// String literals are statically allocated so can be returned safely, (Bjarne, p90)
+	return "Xbx";
+}
+
+
+/******************************************************************/
+// Wait for any pending asyncronous rendering to finish, so rendering
+// data can be unloaded
+/******************************************************************/
+void CEngine::s_plat_finish_rendering()
+{
+	// Wait for asyncronous rendering to finish.
+	NxXbox::EngineGlobals.p_Device->BlockUntilIdle();
+} 
+
+/******************************************************************/
+// Set the amount that the previous frame is blended with this frame
+// 0 = none	  	(just see current frame) 	
+// 128 = 50/50
+// 255 = 100% 	(so you only see the previous frame)												  
+/******************************************************************/
+void CEngine::s_plat_set_screen_blur( uint32 amount )
+{
+	// Only set the blur if we have a blur buffer into which to render.
+	if( NxXbox::EngineGlobals.p_BlurSurface[0] )
+	{
+		NxXbox::EngineGlobals.screen_blur = amount;
+	}
+} 
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int	CEngine::s_plat_get_num_soundtracks( void )
+{
+	return Pcm::GetNumSoundtracks();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const char* CEngine::s_plat_get_soundtrack_name( int soundtrack_number )
+{
+	static char buf[128];
+
+	const WCHAR* p_soundtrack_name_wide = Pcm::GetSoundtrackName( soundtrack_number );
+
+	if( p_soundtrack_name_wide )
+	{
+		// If p_soundtrack_name_wide contains characters not recognized by the XBox then wsprintfA
+		// will write an empty string. It does this for the test bad soundtrack names provided by
+		// the certification tool.
+		// However, I'm not sure if wsprintf will write an empty string if there is just one bad
+		// character in the name, or if it will only write an empty string if they are all bad.
+		// So just to be sure, do a wsprintfA for each character in turn. That way, any good characters
+		// will definitely be printed OK, and all bad characters will appear as xbox null characters.
+		char *p_dest = buf;
+		int count=0;
+		WCHAR p_temp[2]; // A buffer for holding one WCHAR at a time for sending to wsprintfA.
+		p_temp[1]	= 0;
+		const WCHAR *p_scan = p_soundtrack_name_wide;
+
+		while( *p_scan ) // WCHAR strings are terminated by a 0 just like normal strings, except its a 2byte 0.
+		{
+			p_temp[0] = *p_scan++;
+				
+			char p_one_char[10];
+			wsprintfA( p_one_char, "%ls", p_temp);
+
+			// p_one_char now contains a one char string.
+			if( count < 99 )
+			{
+				if( *p_one_char )
+				{
+					*p_dest = *p_one_char;
+				}
+				else
+				{
+					// Bad char, so write a ~ so that it appears as a xbox null char.
+					*p_dest='~';
+				}	
+				++p_dest;
+				++count;
+			}
+		}	
+		*p_dest = 0;
+	}
+	else
+	{
+		// In theory this should never happen, but make sure p_buf contains something if it does.
+		sprintf( buf, "~" );
+	}	
+		
+	int len = strlen( buf );
+	for( int c = 0; c < len; ++c )
+	{
+		// Force any special characters (arrows or button icons) to be displayed
+		// as the xbox NULL character by changing them to an invalid character.
+		switch( buf[c] )
+		{
+			case '¡': case '¢': case '£': case '¤':
+			case '¥': case '¦': case '§': case '¨':
+			case '©': case 'ª': case '«': case '¬':
+				buf[c] = '~';
+				break;
+			default:
+				break;
+		}	
+	}
+	return buf;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_letterbox( bool letterbox )
+{
+	// Letterbox mode is designed for use on a regular 4:3 screen.
+	// It should use the same, wider viewing angle as for widescreen mode, but shrink the resultant image down
+	// vertically by 25%.
+	if( letterbox )
+	{
+		if( NxXbox::EngineGlobals.letterbox_active == false )
+		{
+			// Need to adjust the screen y offset and multiplier to ensure sprites are scaled properly for this mode.
+			NxXbox::EngineGlobals.screen_conv_y_offset		+= ( NxXbox::EngineGlobals.backbuffer_height / 4 ) / 2;
+			NxXbox::EngineGlobals.screen_conv_y_multiplier	= 0.75f;
+			NxXbox::EngineGlobals.letterbox_active			= true;
+		}
+	}
+	else
+	{
+		if( NxXbox::EngineGlobals.letterbox_active == true )
+		{
+			// Restore the screen y offset and multiplier.
+			NxXbox::EngineGlobals.screen_conv_y_offset		-= ( NxXbox::EngineGlobals.backbuffer_height / 4 ) / 2;
+			NxXbox::EngineGlobals.screen_conv_y_multiplier	= 1.0f;
+			NxXbox::EngineGlobals.letterbox_active			= false;
+		}
+	}
+} 
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::s_plat_set_color_buffer_clear( bool clear )
+{
+//	NxXbox::EngineGlobals.clear_color_buffer = clear;
+}
+
+} // namespace Nx
diff --git a/Code/Gfx/XBox/p_nxfont.cpp b/Code/Gfx/XBox/p_nxfont.cpp
new file mode 100644
index 0000000..e91d853
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxfont.cpp
@@ -0,0 +1,299 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.cpp
+
+#include 	"gfx/xbox/p_nxfont.h"
+#include 	"sys/config/config.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CFont
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CXboxFont::CXboxFont() : mp_plat_font(NULL)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CXboxFont::~CXboxFont()
+{
+	if (mp_plat_font)
+	{
+		plat_unload();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxFont::plat_load(const char *filename)
+{
+#	ifdef __PAL_BUILD__
+	// . Have to trap the small font, which needs to be loaded from a different
+	// location for the French build.
+	if( Config::GetLanguage() == Config::LANGUAGE_FRENCH )
+	{
+		if( strstr( filename, "small" ))
+		{
+			mp_plat_font = NxXbox::LoadFont( "small_fr" );
+		}
+		else
+		{
+			mp_plat_font = NxXbox::LoadFont( filename );
+		}
+	}
+	else
+	{
+		mp_plat_font = NxXbox::LoadFont(filename);
+	}
+#	else
+	mp_plat_font = NxXbox::LoadFont(filename);
+#	endif
+
+	return (mp_plat_font != NULL);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CXboxFont::plat_set_spacings(int charSpacing, int spaceSpacing)
+{
+	mp_plat_font->mCharSpacing = charSpacing;
+	if (spaceSpacing > 0)
+		mp_plat_font->mSpaceSpacing = spaceSpacing;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CXboxFont::plat_set_rgba_table(Image::RGBA *pTab)
+{
+	for (int i = 0; i < 16; i++)
+		mp_plat_font->mRGBATab[i] = *((uint32 *) &pTab[i]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CXboxFont::plat_mark_as_button_font(bool isButton)
+{
+	NxXbox::pButtonsFont = (isButton) ? mp_plat_font : NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CXboxFont::plat_unload()
+{
+	NxXbox::UnloadFont(mp_plat_font);
+	mp_plat_font = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 CXboxFont::plat_get_default_height() const
+{
+	Dbg_Assert(mp_plat_font);
+
+	return mp_plat_font->GetDefaultHeight();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 CXboxFont::plat_get_default_base() const
+{
+	Dbg_Assert(mp_plat_font);
+
+	return mp_plat_font->GetDefaultBase();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void CXboxFont::plat_begin_text(uint32 rgba, float Scale)
+//{
+//	Dbg_Assert(mp_plat_font);
+//
+//	mp_plat_font->BeginText(rgba, Scale);
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void CXboxFont::plat_draw_string(char *String, float x0, float y0)
+//{
+//	Dbg_Assert(mp_plat_font);
+//
+//	mp_plat_font->DrawString(String, x0, y0);
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+//void CXboxFont::plat_end_text(void)
+//{
+//	Dbg_Assert(mp_plat_font);
+//
+//	mp_plat_font->EndText();
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxFont::plat_query_string(char *String, float &width, float &height) const
+{
+	Dbg_Assert(mp_plat_font);
+
+	mp_plat_font->QueryString(String, width, height);
+}
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxText::CXboxText()
+{
+	mp_plat_text	= new NxXbox::SText();
+	m_zvalue		= 0;
+
+	plat_initialize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxText::~CXboxText()
+{
+	if( mp_plat_text )
+	{
+		delete mp_plat_text;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CXboxText::plat_initialize()
+{
+	plat_update_engine();
+	plat_update_priority();
+	plat_update_hidden();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CXboxText::plat_update_hidden()
+{
+	mp_plat_text->SetHidden(m_hidden);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxText::plat_update_engine()
+{
+	CXboxFont *p_xbox_font = static_cast( mp_font );
+
+	mp_plat_text->mp_string	= m_string;
+	if( p_xbox_font)
+	{
+		mp_plat_text->mp_font = p_xbox_font->GetEngineFont();
+	}
+
+	mp_plat_text->m_xpos	= m_xpos;
+	mp_plat_text->m_ypos	= m_ypos;
+	mp_plat_text->m_xscale	= m_xscale;
+	mp_plat_text->m_yscale	= m_yscale;
+	mp_plat_text->m_rgba	= *((uint32 *) &m_rgba);
+	mp_plat_text->m_color_override = m_color_override;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxText::plat_update_priority( void )
+{
+	// Update draw list
+	mp_plat_text->SetPriority( m_priority );
+
+	if( m_use_zbuffer )
+	{
+		// Convert the 16:16 fixed z value to a float here.
+		float z = (float)m_zvalue / 65536.0f;
+		mp_plat_text->SetZValue( z );
+	}
+	else
+	{
+		mp_plat_text->SetZValue( 0.0f );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CTextMan::s_plat_alloc_text_pool( void )
+{
+	for( int i = 0; i < vMAX_TEXT_INSTANCES; ++i )
+	{
+	   	CXboxText *p_text		= new CXboxText;
+		p_text->mp_next			= sp_dynamic_text_list;
+		sp_dynamic_text_list	= p_text;
+	}
+}
+
+
+
+
+
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_nxfont.h b/Code/Gfx/XBox/p_nxfont.h
new file mode 100644
index 0000000..cf6179b
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxfont.h
@@ -0,0 +1,76 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxFont.h
+
+#ifndef	__GFX_P_NX_FONT_H__
+#define	__GFX_P_NX_FONT_H__
+
+#include 	"gfx/nxfont.h"
+#include 	"gfx/xbox/nx/chars.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CFont
+class CXboxFont : public CFont
+{
+public:
+								CXboxFont();
+	virtual						~CXboxFont();
+	NxXbox::SFont				*GetEngineFont() const;
+
+private:		// It's all private, as it is machine specific
+	virtual	bool				plat_load(const char *filename);
+	virtual void				plat_set_spacings(int charSpacing, int spaceSpacing);
+	virtual void				plat_set_rgba_table(Image::RGBA *pTab);
+	virtual void 				plat_mark_as_button_font(bool isButton);
+	virtual void				plat_unload();
+
+	virtual	uint32				plat_get_default_height() const;
+	virtual	uint32				plat_get_default_base() const;
+//	virtual void				plat_begin_text(uint32 rgba, float Scale);
+//	virtual void				plat_draw_string(char *String, float x0, float y0);
+//	virtual void				plat_end_text(void);
+	virtual void				plat_query_string(char *String, float &width, float &height) const;
+
+	// Machine specific members
+	NxXbox::SFont *				mp_plat_font;		// Pointer to engine font
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of the CText
+class	CXboxText : public CText
+{
+public:
+								CXboxText();
+	virtual						~CXboxText();
+
+private:
+	//
+	virtual void				plat_initialize();
+
+	virtual void				plat_update_hidden();		// Tell engine of update
+	virtual void				plat_update_engine();		// Update engine primitives
+	virtual void				plat_update_priority();
+
+	// Machine specific members
+	NxXbox::SText				*mp_plat_text;		// Pointer to engine text
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline NxXbox::SFont *CXboxFont::GetEngineFont() const
+{
+	return mp_plat_font;
+}
+
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/XBox/p_nxfontman.cpp b/Code/Gfx/XBox/p_nxfontman.cpp
new file mode 100644
index 0000000..d39c093
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxfontman.cpp
@@ -0,0 +1,105 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:						 		 									**
+**																			**
+**	File name:		gfx/xbox/p_nxfontman.cpp								**
+**																			**
+**	Created:		01/16/2002	-	dc										**
+**																			**
+**	Description:	Xbox platform specific interface to the Font Manager	**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include	"gfx\nx.h"
+#include	"gfx\NxFontMan.h"
+#include	"gfx\xbox\p_NxFont.h"
+#include 	"gfx\xbox\nx\chars.h"
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+namespace Nx
+{
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CFont* CFontManager::s_plat_load_font( const char* pName )
+{
+	CXboxFont *p_new_font = new CXboxFont;
+	p_new_font->Load( pName );
+	return p_new_font;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFontManager::s_plat_unload_font( CFont* pFont )
+{
+	pFont->Unload();
+}
+
+} // namespace Nx
+ 
diff --git a/Code/Gfx/XBox/p_nxmiscfx.cpp b/Code/Gfx/XBox/p_nxmiscfx.cpp
new file mode 100644
index 0000000..b623f18
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxmiscfx.cpp
@@ -0,0 +1,1279 @@
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+namespace Nx
+{
+
+#define	DRAW_DEBUG_LINES		0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sXboxScreenFlashVert
+{
+	float		x, y, z;
+	float		rhw;
+	D3DCOLOR	col;
+	float		u, v;
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sXboxSplatVert
+{
+	D3DXVECTOR3		pos;
+	D3DCOLOR		col;
+	float			u, v;
+};
+
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sXboxSplatInstanceDetails : public sSplatInstanceDetails
+{
+	// Platform specific part.
+	NxXbox::CInstance	*mp_instance;
+	NxXbox::sMaterial	*mp_material;
+	sXboxSplatVert		m_verts[SPLAT_POLYS_PER_MESH * 3];
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sXboxShatterInstanceDetails : public sShatterInstanceDetails
+{
+	// Platform specific part.
+
+						sXboxShatterInstanceDetails( int num_tris, NxXbox::sMesh *p_mesh );
+						~sXboxShatterInstanceDetails( void );
+	
+	NxXbox::sMesh		*mp_mesh;
+	uint8				*mp_vertex_buffer;
+};
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sXboxShatterInstanceDetails::sXboxShatterInstanceDetails( int num_tris, NxXbox::sMesh *p_mesh ) : sShatterInstanceDetails( num_tris )
+{
+	mp_mesh				= p_mesh;
+	mp_vertex_buffer	= new uint8[num_tris * 3 * p_mesh->m_vertex_stride];
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sXboxShatterInstanceDetails::~sXboxShatterInstanceDetails( void )
+{
+	delete [] mp_vertex_buffer;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sXboxSplatInstanceDetails * getDetailsForTextureSplat( NxXbox::sTexture *p_texture )
+{
+	sXboxSplatInstanceDetails *p_xbox_details;
+
+	Dbg_Assert( p_splat_details_table );
+	
+	// Check to see whether we have a scene already created for this type of texture splat.
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	while( p_details )
+	{
+		p_xbox_details					= static_cast( p_details );
+		NxXbox::sMaterial *p_material	= p_xbox_details->mp_material;
+		if( p_material->mp_tex[0] == p_texture )
+		{
+			// This scene contains a material with the required texture, so use this scene.
+			return p_xbox_details;
+		}
+		p_details = p_splat_details_table->IterateNext();
+	}
+	
+	// Create an (opaque) material used to render the mesh.
+	NxXbox::sMaterial *p_material	= new NxXbox::sMaterial();
+	p_material->m_flags[0]			= (( p_texture ) ? MATFLAG_TEXTURED : 0 );
+	p_material->m_checksum			= (uint32)rand() * (uint32)rand();
+	p_material->m_passes			= 1;
+	p_material->mp_tex[0]			= p_texture;
+	p_material->m_no_bfc			= true;
+	p_material->m_zbias				= 1;				// To ensure it will sort above most geometry.
+	p_material->m_reg_alpha[0]		= 0x00000005UL;		// Blend for now.
+	p_material->m_color[0][0]		= 0x80;
+	p_material->m_color[0][1]		= 0x80;
+	p_material->m_color[0][2]		= 0x80;
+	p_material->m_uv_addressing[0]	= 0x00020002UL;		// We want the texture to border - most efficient for alphakill.
+	p_material->m_k[0]				= 0.0f;
+	p_material->m_alpha_cutoff		= 1;
+
+	p_xbox_details = new sXboxSplatInstanceDetails;
+	p_xbox_details->m_highest_active_splat	= 0;
+	p_xbox_details->mp_material				= p_material;
+	ZeroMemory( p_xbox_details->m_lifetimes, sizeof( int ) * SPLAT_POLYS_PER_MESH );
+
+	for( int v = 0; v < SPLAT_POLYS_PER_MESH * 3; ++v )
+	{
+		p_xbox_details->m_verts[v].pos = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
+		p_xbox_details->m_verts[v].col = D3DCOLOR_RGBA( 0x80, 0x80, 0x80, 0x80 );
+	}
+	
+	p_splat_details_table->PutItem((uint32)p_xbox_details, p_xbox_details );
+
+	return p_xbox_details;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool subdivide_tri_stack( uint8 **p_write, NxXbox::sMesh *p_mesh )
+{
+	// Three temporary buffers.
+	static uint8 v0[128];
+	static uint8 v1[128];
+	static uint8 v2[128];
+
+	// Three more temporary buffers.
+	static uint8 i01[128];
+	static uint8 i12[128];
+	static uint8 i20[128];
+
+	// If there are elements on the stack, pop off the top three vertices and subdivide if necessary.
+	if( triSubdivideStack.IsEmpty())
+	{
+		return false;
+	}
+	
+	D3DXVECTOR3	*p_v0 = (D3DXVECTOR3*)v0;
+	D3DXVECTOR3	*p_v1 = (D3DXVECTOR3*)v1;
+	D3DXVECTOR3	*p_v2 = (D3DXVECTOR3*)v2;
+	
+	// Stack is LIFO, so Pop() off in reverse order.
+	triSubdivideStack.Pop( p_v2 );
+	triSubdivideStack.Pop( p_v1 );
+	triSubdivideStack.Pop( p_v0 );
+	
+	// Calculate the area of this tri.
+	Mth::Vector p(	p_v1->x - p_v0->x, p_v1->y - p_v0->y, p_v1->z - p_v0->z );
+	Mth::Vector q(	p_v2->x - p_v0->x, p_v2->y - p_v0->y, p_v2->z - p_v0->z );
+	Mth::Vector r(( p[Y] * q[Z] ) - ( q[Y] * p[Z] ), ( p[Z] * q[X] ) - ( q[Z] * p[X] ), ( p[X] * q[Y] ) - ( q[X] * p[Y] ));
+	float area_squared = r.LengthSqr();
+
+	if( area_squared > shatterAreaTest )
+	{
+		// We need to subdivide this tri. Calculate the three intermediate points.
+		int block_size = triSubdivideStack.GetBlockSize();
+
+		memcpy( i01, v0, block_size );
+		memcpy( i12, v1, block_size );
+		memcpy( i20, v2, block_size );
+
+		// Deal with positions (always present).
+		((D3DXVECTOR3*)i01 )->x = p_v0->x + (( p_v1->x - p_v0->x ) * 0.5f );
+		((D3DXVECTOR3*)i01 )->y = p_v0->y + (( p_v1->y - p_v0->y ) * 0.5f );
+		((D3DXVECTOR3*)i01 )->z = p_v0->z + (( p_v1->z - p_v0->z ) * 0.5f );
+
+		((D3DXVECTOR3*)i12 )->x = p_v1->x + (( p_v2->x - p_v1->x ) * 0.5f );
+		((D3DXVECTOR3*)i12 )->y = p_v1->y + (( p_v2->y - p_v1->y ) * 0.5f );
+		((D3DXVECTOR3*)i12 )->z = p_v1->z + (( p_v2->z - p_v1->z ) * 0.5f );
+
+		((D3DXVECTOR3*)i20 )->x = p_v2->x + (( p_v0->x - p_v2->x ) * 0.5f );
+		((D3DXVECTOR3*)i20 )->y = p_v2->y + (( p_v0->y - p_v2->y ) * 0.5f );
+		((D3DXVECTOR3*)i20 )->z = p_v2->z + (( p_v0->z - p_v2->z ) * 0.5f );
+
+		// Deal with colors (not always present).
+		if( p_mesh->m_diffuse_offset > 0 )
+		{
+			uint8	*p_v0col	= (uint8*)( v0 + p_mesh->m_diffuse_offset );
+			uint8	*p_v1col	= (uint8*)( v1 + p_mesh->m_diffuse_offset );
+			uint8	*p_v2col	= (uint8*)( v2 + p_mesh->m_diffuse_offset );
+			uint8	*p_i01col	= (uint8*)( i01 + p_mesh->m_diffuse_offset );
+			uint8	*p_i12col	= (uint8*)( i12 + p_mesh->m_diffuse_offset );
+			uint8	*p_i20col	= (uint8*)( i20 + p_mesh->m_diffuse_offset );
+		
+			for( int i = 0; i < 4; ++i )
+			{
+				p_i01col[i]		= p_v0col[i] + (((int)p_v1col[i] - (int)p_v0col[i] ) / 2 );
+				p_i12col[i]		= p_v1col[i] + (((int)p_v2col[i] - (int)p_v1col[i] ) / 2 );
+				p_i20col[i]		= p_v2col[i] + (((int)p_v0col[i] - (int)p_v2col[i] ) / 2 );
+			}
+		}
+
+		// Deal with uv0 (not always present).
+		if( p_mesh->m_uv0_offset > 0 )
+		{
+			float	*p_v0uv		= (float*)( v0 + p_mesh->m_uv0_offset );
+			float	*p_v1uv		= (float*)( v1 + p_mesh->m_uv0_offset );
+			float	*p_v2uv		= (float*)( v2 + p_mesh->m_uv0_offset );
+			float	*p_i01uv	= (float*)( i01 + p_mesh->m_uv0_offset );
+			float	*p_i12uv	= (float*)( i12 + p_mesh->m_uv0_offset );
+			float	*p_i20uv	= (float*)( i20 + p_mesh->m_uv0_offset );
+		
+			for( int i = 0; i < 2; ++i )
+			{
+				p_i01uv[i]		= p_v0uv[i] + (( p_v1uv[i] - p_v0uv[i] ) * 0.5f );
+				p_i12uv[i]		= p_v1uv[i] + (( p_v2uv[i] - p_v1uv[i] ) * 0.5f );
+				p_i20uv[i]		= p_v2uv[i] + (( p_v0uv[i] - p_v2uv[i] ) * 0.5f );
+			}
+		}
+		
+		// Push the four new tris onto the stack.
+		triSubdivideStack.Push( v0 );
+		triSubdivideStack.Push( i01 );
+		triSubdivideStack.Push( i20 );
+
+		triSubdivideStack.Push( i01 );
+		triSubdivideStack.Push( v1 );
+		triSubdivideStack.Push( i12 );
+
+		triSubdivideStack.Push( i01 );
+		triSubdivideStack.Push( i12 );
+		triSubdivideStack.Push( i20 );
+
+		triSubdivideStack.Push( i20 );
+		triSubdivideStack.Push( i12 );
+		triSubdivideStack.Push( v2 );
+	}
+	else
+	{
+		// Don't need to subdivide this tri.
+		int block_size = triSubdivideStack.GetBlockSize();
+
+		// Just copy the tri into the next available slot.
+		memcpy( *p_write, v0, block_size );
+		*p_write += block_size;
+		memcpy( *p_write, v1, block_size );
+		*p_write += block_size;
+		memcpy( *p_write, v2, block_size );
+		*p_write += block_size;
+	}
+	return true;
+}
+
+
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_screen_flash_render( sScreenFlashDetails *p_details )
+{
+	// Get viewport details.
+	CViewport *p_vp = CViewportManager::sGetActiveViewport( p_details->m_viewport );
+
+	sXboxScreenFlashVert verts[4];
+
+	verts[0].x	= p_vp->GetOriginX() * NxXbox::EngineGlobals.backbuffer_width;
+	verts[0].y	= p_vp->GetOriginY() * NxXbox::EngineGlobals.backbuffer_height;
+	verts[0].z	= p_details->m_z;
+	
+	verts[1].x	= verts[0].x + ( p_vp->GetWidth() * NxXbox::EngineGlobals.backbuffer_width );
+	verts[1].y	= verts[0].y;
+	verts[1].z	= verts[0].z;
+
+	verts[2].x	= verts[0].x + ( p_vp->GetWidth() * NxXbox::EngineGlobals.backbuffer_width );
+	verts[2].y	= verts[0].y + ( p_vp->GetHeight() * NxXbox::EngineGlobals.backbuffer_height );
+	verts[2].z	= verts[0].z;
+
+	verts[3].x	= verts[0].x;
+	verts[3].y	= verts[0].y + ( p_vp->GetHeight() * NxXbox::EngineGlobals.backbuffer_height );
+	verts[3].z	= verts[0].z;
+
+	for( int v = 0; v < 4; ++v )
+	{
+		verts[v].col = D3DCOLOR_ARGB( p_details->m_current.a, p_details->m_current.r, p_details->m_current.g, p_details->m_current.b );
+		verts[v].rhw = 1.0f;
+	}
+
+	if( p_details->mp_texture )
+	{
+		verts[0].u	= 0.0f;
+		verts[0].v	= 0.0f;
+		verts[1].u	= 1.0f;
+		verts[1].v	= 0.0f;
+		verts[2].u	= 1.0f;
+		verts[2].v	= 1.0f;
+		verts[3].u	= 0.0f;
+		verts[3].v	= 1.0f;
+
+		Nx::CXboxTexture *p_xbox_texture = static_cast( p_details->mp_texture );
+
+		NxXbox::set_texture( 0, p_xbox_texture->GetEngineTexture()->pD3DTexture, p_xbox_texture->GetEngineTexture()->pD3DPalette );
+		NxXbox::set_render_state( RS_UVADDRESSMODE0, 0x00010001UL );
+	}
+	else	
+	{
+		NxXbox::set_texture( 0, NULL );
+	}
+	
+	NxXbox::set_blend_mode( NxXbox::vBLEND_MODE_BLEND );
+
+	NxXbox::set_render_state( RS_ZWRITEENABLE,	0 );
+	NxXbox::set_render_state( RS_ZTESTENABLE,	0 );
+
+	NxXbox::set_pixel_shader( 0 );
+	NxXbox::set_vertex_shader( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+	
+	NxXbox::EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_QUADLIST, 1, verts, sizeof( sXboxScreenFlashVert ));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_initialize( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_cleanup( void )
+{
+	sXboxSplatInstanceDetails *p_xbox_details;
+
+	Dbg_Assert( p_splat_details_table );
+	
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	while( p_details )
+	{
+		p_xbox_details = static_cast( p_details );
+
+		delete p_xbox_details->mp_material;
+		
+		p_details = p_splat_details_table->IterateNext();
+
+		p_splat_details_table->FlushItem((uint32)p_xbox_details );
+		delete p_xbox_details;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_reset_poly( sSplatInstanceDetails *p_details, int index )
+{
+	// Cast the details to Xbox details.
+	sXboxSplatInstanceDetails *p_xbox_details = static_cast( p_details );
+	
+	// Force this poly to be degenerate.
+	p_xbox_details->m_verts[index * 3 + 1]	= p_xbox_details->m_verts[index * 3];
+	p_xbox_details->m_verts[index * 3 + 2]	= p_xbox_details->m_verts[index * 3];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline float CrossProduct2D( const Mth::Vector& v1, const Mth::Vector& v2 )
+{	
+	// Assumes for both v1 and v2 that the [z] and [w] components are 0.
+	return ( v1[X] * v2[Y] ) - ( v1[Y] * v2[X] );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool same_side( Mth::Vector &p1, Mth::Vector &p2, Mth::Vector &a, Mth::Vector &b )
+{
+	float cp1 = CrossProduct2D( b - a, p1 - a );
+	float cp2 = CrossProduct2D( b - a, p2 - a );
+	if(( cp1 * cp2 ) >= 0.0f )
+		return true;
+    else
+		return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static bool point_in_triangle( Mth::Vector &p, Mth::Vector &a, Mth::Vector &b, Mth::Vector &c )
+{
+	if( same_side( p, a, b, c ) && same_side( p, b, c, a ) && same_side( p, c, a, b ))
+		return true;
+    else
+		return false;
+}
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline bool line_segment_intersection( float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4 )
+{
+	float ax = x2 - x1;
+	float bx = x3 - x4;
+	float ay = y2 - y1;
+	float by = y3 - y4;
+	float cx = x1 - x3;
+	float cy = y1 - y3;
+	float d = by * cx - bx * cy;	// Alpha numerator.
+	float f = ay * bx - ax * by;	// Both denominator.
+
+	// Alpha tests.
+	if( f > 0.0f )
+	{
+		if( d < 0.0f || d > f )
+			return false;
+	}
+	else
+	{
+		if( d > 0 || d < f )
+			return false;
+	}
+
+	float e = ax * cy - ay * cx;	// Beta numerator.
+	
+	// Beta tests.
+	if( f > 0.0f )
+	{
+		if( e < 0.0f || e > f )
+			return false;
+	}
+	else
+	{
+		if( e > 0 || e < f )
+			return false;
+	}
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline bool tri_texture_intersect( float u0, float v0, float u1, float v1, float u2, float v2 )
+{
+	// Trivial check to see if all three points are outside range of texture.
+	if(( u0 < -1.0f ) && ( u1 < -1.0f ) && ( u2 < -1.0f ))
+		return false;
+	if(( u0 > 1.0f ) && ( u1 > 1.0f ) && ( u2 > 1.0f ))
+		return false;
+	if(( v0 < -1.0f ) && ( v1 < -1.0f ) && ( v2 < -1.0f ))
+		return false;
+	if(( v0 > 1.0f ) && ( v1 > 1.0f ) && ( v2 > 1.0f ))
+		return false;
+	
+	// Perform the check to see if any line segment forming the tri intersects any line segment forming the texture.
+	Mth::Vector texture_square[4] = {	Mth::Vector( -1.0f, -1.0f, 0.0f, 0.0f ),
+										Mth::Vector(  1.0f, -1.0f, 0.0f, 0.0f ),
+										Mth::Vector(  1.0f,  1.0f, 0.0f, 0.0f ),
+										Mth::Vector( -1.0f,  1.0f, 0.0f, 0.0f )};
+	for( int p = 0; p < 4; ++p )
+	{
+		int q = ( p + 1 ) % 4;
+		if( line_segment_intersection( u0, v0, u1, v1, texture_square[p][X], texture_square[p][Y], texture_square[q][X], texture_square[q][Y] ))
+			return true;
+		if( line_segment_intersection( u1, v1, u2, v2, texture_square[p][X], texture_square[p][Y], texture_square[q][X], texture_square[q][Y] ))
+			return true;
+		if( line_segment_intersection( u2, v2, u0, v0, texture_square[p][X], texture_square[p][Y], texture_square[q][X], texture_square[q][Y] ))
+			return true;
+	}
+
+	// If we reach this point there are three remaining possibilities:
+	// 1) That the tri lies entirely within the texture
+	// 2) That the texture lies entirely within the tri
+	// 3) That there is no space shared by tri and texture
+
+	// 1) Perform a trivial check to see whether a corner of the tri lies within the texture.
+	if(( u0 >= -1.0f ) && ( u0 <= 1.0f ) && ( v0 >= -1.0f ) && ( v0 <= 1.0f ))
+		return true;
+	if(( u1 >= -1.0f ) && ( u1 <= 1.0f ) && ( v1 >= -1.0f ) && ( v1 <= 1.0f ))
+		return true;
+	if(( u2 >= -1.0f ) && ( u2 <= 1.0f ) && ( v2 >= -1.0f ) && ( v2 <= 1.0f ))
+		return true;
+
+	// 2) Check that at least one corner of the texture falls within the tri.
+	Mth::Vector a( u0, v0, 0.0f );
+	Mth::Vector b( u1, v1, 0.0f );
+	Mth::Vector c( u2, v2, 0.0f );
+	for( int p = 0; p < 4; ++p )
+	{
+		if( point_in_triangle( texture_square[p], a, b, c ))
+		{
+			return true;
+		}
+	}
+
+	// 3) No space shared.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool plat_texture_splat( Nx::CSector **pp_sectors, Nx::CCollStatic **pp_collision, Mth::Vector& start, Mth::Vector& end, float size, float lifetime, Nx::CTexture *p_texture, Nx::sSplatTrailInstanceDetails *p_trail_details )
+{
+	XGMATRIX view_matrix, ortho_matrix, projection_matrix;
+
+#	if DRAW_DEBUG_LINES
+	Gfx::AddDebugLine( start, end, MAKE_RGB( 200, 200, 0 ), MAKE_RGB( 200, 200, 0 ), 1 );
+#	endif // DRAW_DEBUG_LINES
+	
+	// The length of the start->end line defines the view depth of the frustum.
+	Mth::Vector	splat_vector	= end - start;
+	float		view_depth		= splat_vector.Length();
+	splat_vector.Normalize();
+	
+	// Calculate the parallel projection matrix. Generally the projection vector will tend to point downwards, so we use a
+	// random vector in the x-z plane to define the up vector for the projection. However if this splat is part of a trail,
+	// use the previous point to generate the up vector.
+	Mth::Vector up;
+	if( p_trail_details )
+	{
+		up.Set( start[X] - p_trail_details->m_last_pos[X], start[Y] - p_trail_details->m_last_pos[Y], start[Z] - p_trail_details->m_last_pos[Z] );
+
+		// The height of the viewport is defined by the distance between the two points.
+		float height = up.Length() * 0.5f;
+
+		// Now we move start and end halfway back along the up vector.
+		start	-= up * 0.5f;
+		end		-= up * 0.5f;
+		
+		up.Normalize();
+		
+		XGMatrixLookAtRH( &view_matrix, (XGVECTOR3*)( &start[X] ), (XGVECTOR3*)( &end[X] ), (XGVECTOR3*)( &up[X] ));
+		XGMatrixOrthoRH( &ortho_matrix, size, height, 0.1f, view_depth );
+	}
+	else if( fabsf( splat_vector[Y] ) > 0.5f )
+	{
+		float angle = ((float)rand() * 2.0f * Mth::PI ) / (float)RAND_MAX;
+		up.Set( sinf( angle ), 0.0f, cosf( angle ));
+		XGMatrixLookAtRH( &view_matrix, (XGVECTOR3*)( &start[X] ), (XGVECTOR3*)( &end[X] ), (XGVECTOR3*)( &up[X] ));
+		XGMatrixOrthoRH( &ortho_matrix, size, size, 0.1f, view_depth );
+	}
+	else
+	{
+		up.Set( 0.0f, 1.0f, 0.0f );
+		XGMatrixLookAtRH( &view_matrix, (XGVECTOR3*)( &start[X] ), (XGVECTOR3*)( &end[X] ), (XGVECTOR3*)( &up[X] ));
+		XGMatrixOrthoRH( &ortho_matrix, size, size, 0.1f, view_depth );
+	}
+
+	XGMatrixMultiply( &projection_matrix, &view_matrix, &ortho_matrix );
+	
+	// Pointer to the mesh we will be modifying. (Don't want to set the pointer up until we know for
+	// sure that we will be adding some polys).
+	sXboxSplatInstanceDetails	*p_details		= NULL;
+	sXboxSplatVert				*p_target_verts	= NULL;
+
+	Nx::CSector *p_sector;
+
+	while( p_sector = *pp_sectors )
+	{
+		Nx::CXboxGeom *p_xbox_geom = static_cast( p_sector->GetGeom());
+
+		if( p_xbox_geom )
+		{
+#			if DRAW_DEBUG_LINES
+			Mth::Vector min = p_xbox_geom->GetBoundingBox().GetMin();
+			Mth::Vector max = p_xbox_geom->GetBoundingBox().GetMax();
+
+			Mth::Vector box[8];
+			box[0] = box[1] = box[2] = box[3] = max;
+			box[1][X] = min[X];
+			box[2][Y] = min[Y];
+			box[3][Z] = min[Z];
+			box[5] = box[6] = box[7] = box[4] = min;;
+			box[5][X] = max[X];
+			box[6][Y] = max[Y];
+			box[7][Z] = max[Z];
+
+			for ( int i = 1; i < 4; i++ )
+			{
+				Gfx::AddDebugLine( box[0], box[i], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			}
+			for ( int i = 5; i < 8; i++ )
+			{
+				Gfx::AddDebugLine( box[4], box[i], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			}
+			Gfx::AddDebugLine( box[1], box[6], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[1], box[7], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[2], box[5], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[2], box[7], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[3], box[5], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+			Gfx::AddDebugLine( box[3], box[6], MAKE_RGB( 200, 0, 0 ), MAKE_RGB( 200, 0, 0 ), 1 );
+#			endif // DRAW_DEBUG_LINES
+			
+			// For each mesh in the geom...
+			for( uint32 m = 0; m < p_xbox_geom->m_num_mesh; ++m )
+			{
+				NxXbox::sMesh *p_mesh = p_xbox_geom->m_mesh_array[m];
+
+				// Not allowed on meshes which are flagged not to shadow.
+				if( p_mesh->m_flags & NxXbox::sMesh::MESH_FLAG_NO_SKATER_SHADOW )
+					continue;
+
+				// Check the bounding box of this mesh falls within the scope of the line.
+				
+				// Transform the mesh bounding box to see whether it falls within the projection frustum.
+				
+				// If it falls within the projection frustum, we need to explicitly transform all the vertices.
+				BYTE *p_vert_data;
+				p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_vert_data, D3DLOCK_READONLY );
+				
+				// Now scan through each non-degenerate tri, checking the verts to see if they are within scope.
+				uint32 index0;
+				uint32 index1 = p_mesh->mp_index_buffer[0][0];
+				uint32 index2 = p_mesh->mp_index_buffer[0][1];
+				for( uint32 i = 2; i < p_mesh->m_num_indices[0]; ++i )
+				{
+					// Wrap the indices round.
+					index0 = index1;
+					index1 = index2;
+					index2 = p_mesh->mp_index_buffer[0][i];
+
+					if(( index0 != index1 ) && ( index0 != index2 ) && ( index1 != index2 ))
+					{
+						XGVECTOR3 uvprojections[3];
+						XGVec3TransformCoord( &uvprojections[0], (XGVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index0 )), &projection_matrix );
+						XGVec3TransformCoord( &uvprojections[1], (XGVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index1 )), &projection_matrix );
+						XGVec3TransformCoord( &uvprojections[2], (XGVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index2 )), &projection_matrix );
+
+						// Check the z-values here, everything else is checked in tri_texture_intersect().
+						if(( uvprojections[0].z < 0.0f ) && ( uvprojections[1].z < 0.0f ) && ( uvprojections[2].z < 0.0f ))
+							continue;
+						if(( uvprojections[0].z > 1.0f ) && ( uvprojections[1].z > 1.0f ) && ( uvprojections[2].z > 1.0f ))
+							continue;
+						
+						// Okay, this tri lies within the projection frustum. Now check that it intersects the texture
+						if( !tri_texture_intersect( uvprojections[0].x, uvprojections[0].y,
+													uvprojections[1].x, uvprojections[1].y,
+													uvprojections[2].x, uvprojections[2].y ))
+						{
+							continue;
+						}
+						
+						// Get a pointer to the mesh used for rendering texture splats with the given texture.
+						// (Note this will create a new instance to handle texture splats of this texture if one does not already exist).
+						if( p_target_verts == NULL )
+						{
+							CXboxTexture *p_xbox_texture	= static_cast( p_texture );
+							p_details						= getDetailsForTextureSplat( p_xbox_texture->GetEngineTexture());
+							p_target_verts					= p_details->m_verts;
+							Dbg_Assert( p_target_verts );
+						}
+						
+						// Scan through the lifetimes, finding a 'dead' poly (lifetime == 0), or the oldest.
+						uint32 idx						= p_details->GetOldestSplat();
+
+						// Convert lifetime from seconds to milliseconds.
+						p_details->m_lifetimes[idx]		= (int)( lifetime * 1000.0f );
+
+						// Set up the corresponding vertices. First write the positions...
+						uint32 index					= idx * 3;
+						p_target_verts[index + 0].pos	= *(D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index0 ));
+						p_target_verts[index + 1].pos	= *(D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index1 ));
+						p_target_verts[index + 2].pos	= *(D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index2 ));
+
+						// ...then the uv's...
+						p_target_verts[index + 0].u		= ( uvprojections[0].x + 1.0f ) * 0.5f;
+						p_target_verts[index + 0].v		= ( uvprojections[0].y + 1.0f ) * 0.5f;
+						p_target_verts[index + 1].u		= ( uvprojections[1].x + 1.0f ) * 0.5f;
+						p_target_verts[index + 1].v		= ( uvprojections[1].y + 1.0f ) * 0.5f;
+						p_target_verts[index + 2].u		= ( uvprojections[2].x + 1.0f ) * 0.5f;
+						p_target_verts[index + 2].v		= ( uvprojections[2].y + 1.0f ) * 0.5f;
+
+						// ...then the vertex colors.
+						p_target_verts[index + 0].col	= ( *(D3DCOLOR*)( p_vert_data + ( p_mesh->m_vertex_stride * index0 ) + p_mesh->m_diffuse_offset ) & 0xFFFFFFUL ) | 0x80000000UL;
+						p_target_verts[index + 1].col	= ( *(D3DCOLOR*)( p_vert_data + ( p_mesh->m_vertex_stride * index1 ) + p_mesh->m_diffuse_offset ) & 0xFFFFFFUL ) | 0x80000000UL;
+						p_target_verts[index + 2].col	= ( *(D3DCOLOR*)( p_vert_data + ( p_mesh->m_vertex_stride * index2 ) + p_mesh->m_diffuse_offset ) & 0xFFFFFFUL ) | 0x80000000UL;
+						
+						D3DXVECTOR3	*p_v0 = &( p_target_verts[index + 0].pos );
+						D3DXVECTOR3	*p_v1 = &( p_target_verts[index + 1].pos );
+						D3DXVECTOR3	*p_v2 = &( p_target_verts[index + 2].pos );
+						Mth::Vector pv(	p_v1->x - p_v0->x, p_v1->y - p_v0->y, p_v1->z - p_v0->z );
+						Mth::Vector qv(	p_v2->x - p_v0->x, p_v2->y - p_v0->y, p_v2->z - p_v0->z );
+						Mth::Vector r(( pv[Y] * qv[Z] ) - ( qv[Y] * pv[Z] ), ( pv[Z] * qv[X] ) - ( qv[Z] * pv[X] ), ( pv[X] * qv[Y] ) - ( qv[X] * pv[Y] ));
+						float area_squared = r.LengthSqr();
+
+						// Set the shatter test to ensure that we don't subdivide too far. Note that each successive subdivision will quarter
+						// the area of each triangle, which means the area *squared* of each triangle will become 1/16th of the previous value.
+						shatterAreaTest = area_squared / 128.0f;
+						
+						triSubdivideStack.Reset();
+						triSubdivideStack.SetBlockSize( sizeof( sXboxSplatVert ));
+						triSubdivideStack.Push( &p_target_verts[index + 0] );
+						triSubdivideStack.Push( &p_target_verts[index + 1] );
+						triSubdivideStack.Push( &p_target_verts[index + 2] );
+
+						// Allocate a block of memory into which the subdivision stack will write the results.
+						uint8			*p_array		= new uint8[8 * 1024];
+						uint8			*p_array_start	= p_array;
+						uint8			*p_array_loop	= p_array;
+						memset( p_array, 0, 8 * 1024 );
+
+						NxXbox::sMesh	*p_dummy_mesh	= new NxXbox::sMesh();
+						p_dummy_mesh->m_diffuse_offset	= 12;
+						p_dummy_mesh->m_uv0_offset		= 16;
+
+						while( subdivide_tri_stack( &p_array, p_dummy_mesh ));
+
+						// Ensure we haven't overrun the buffer.
+						Dbg_Assert((uint32)p_array - (uint32)p_array_loop < ( 8 * 1024 ));
+
+						bool subdivided_tri_added = false;
+
+						while( p_array_loop != p_array )
+						{
+							// Add this triangle, *if* it is valid.
+							if( tri_texture_intersect((((sXboxSplatVert*)p_array_loop )[0].u * 2.0f ) - 1.0f,
+													  (((sXboxSplatVert*)p_array_loop )[0].v * 2.0f ) - 1.0f,
+													  (((sXboxSplatVert*)p_array_loop )[1].u * 2.0f ) - 1.0f,
+													  (((sXboxSplatVert*)p_array_loop )[1].v * 2.0f ) - 1.0f,
+													  (((sXboxSplatVert*)p_array_loop )[2].u * 2.0f ) - 1.0f,
+													  (((sXboxSplatVert*)p_array_loop )[2].v * 2.0f ) - 1.0f ))
+							{
+								// We have added at least one subdivided tri.
+								subdivided_tri_added = true;
+
+								// Convert lifetime from seconds to milliseconds.
+								p_details->m_lifetimes[idx]		= (int)( lifetime * 1000.0f );
+							
+								p_target_verts[index + 0].pos	= ((sXboxSplatVert*)p_array_loop )[0].pos;
+								p_target_verts[index + 1].pos	= ((sXboxSplatVert*)p_array_loop )[1].pos;
+								p_target_verts[index + 2].pos	= ((sXboxSplatVert*)p_array_loop )[2].pos;
+							
+								p_target_verts[index + 0].u		= ((sXboxSplatVert*)p_array_loop )[0].u;
+								p_target_verts[index + 0].v		= ((sXboxSplatVert*)p_array_loop )[0].v;
+								p_target_verts[index + 1].u		= ((sXboxSplatVert*)p_array_loop )[1].u;
+								p_target_verts[index + 1].v		= ((sXboxSplatVert*)p_array_loop )[1].v;
+								p_target_verts[index + 2].u		= ((sXboxSplatVert*)p_array_loop )[2].u;
+								p_target_verts[index + 2].v		= ((sXboxSplatVert*)p_array_loop )[2].v;
+
+								p_target_verts[index + 0].col	= (((sXboxSplatVert*)p_array_loop )[0].col & 0xFFFFFFUL ) | 0x80000000UL;
+								p_target_verts[index + 1].col	= (((sXboxSplatVert*)p_array_loop )[1].col & 0xFFFFFFUL ) | 0x80000000UL;
+								p_target_verts[index + 2].col	= (((sXboxSplatVert*)p_array_loop )[2].col & 0xFFFFFFUL ) | 0x80000000UL;
+							
+								idx								= p_details->GetOldestSplat();
+								index							= idx * 3;
+							}
+							
+							p_array_loop						+= ( sizeof( sXboxSplatVert ) * 3 );
+						}
+
+						if( !subdivided_tri_added )
+						{
+							// No subdivided tris were added. This means we still have the large tri sitting in the list, which we don't want.
+							p_details->m_lifetimes[idx]			= 0;
+							plat_texture_splat_reset_poly( p_details, idx );
+						}
+						
+						delete p_dummy_mesh;
+						delete [] p_array_start;
+
+#						if DRAW_DEBUG_LINES
+						D3DXVECTOR3* p_d3dvert;
+						p_d3dvert = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index0 ));
+						Mth::Vector v0( p_d3dvert->x, p_d3dvert->y, p_d3dvert->z );
+						p_d3dvert = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index1 ));
+						Mth::Vector v1( p_d3dvert->x, p_d3dvert->y, p_d3dvert->z );
+						p_d3dvert = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index2 ));
+						Mth::Vector v2( p_d3dvert->x, p_d3dvert->y, p_d3dvert->z );
+						Gfx::AddDebugLine( v0, v1, MAKE_RGB( 0, 200, 200 ), MAKE_RGB( 0, 200, 200 ), 1 );
+						Gfx::AddDebugLine( v1, v2, MAKE_RGB( 0, 200, 200 ), MAKE_RGB( 0, 200, 200 ), 1 );
+						Gfx::AddDebugLine( v2, v0, MAKE_RGB( 0, 200, 200 ), MAKE_RGB( 0, 200, 200 ), 1 );
+#						endif // DRAW_DEBUG_LINES
+					}
+				}
+			}
+		}
+		++pp_sectors;
+	}
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_texture_splat_render( void )
+{
+	sXboxSplatInstanceDetails *p_xbox_details;
+
+	Dbg_Assert( p_splat_details_table );
+
+	NxXbox::set_pixel_shader( 0 );	
+	NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+	
+	D3DDevice_SetTextureStageState( 0, D3DTSS_BORDERCOLOR, 0x00000000UL );
+
+	// Store the stage zero minfilter, since it may be anisotropic.
+	DWORD stage_zero_minfilter;
+	D3DDevice_GetTextureStageState( 0, D3DTSS_MINFILTER, &stage_zero_minfilter );
+	D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
+
+	p_splat_details_table->IterateStart();
+	sSplatInstanceDetails *p_details = p_splat_details_table->IterateNext();
+	
+	while( p_details )
+	{
+		p_xbox_details = static_cast( p_details );
+
+		if( p_xbox_details->m_highest_active_splat >= 0 )
+		{
+			p_xbox_details->mp_material->Submit();
+			NxXbox::EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_TRIANGLELIST, p_xbox_details->m_highest_active_splat + 1, p_xbox_details->m_verts, sizeof( sXboxSplatVert ));
+		}
+		
+		p_details = p_splat_details_table->IterateNext();
+	}
+
+	// Restore the stage zero minfilter.
+	D3DDevice_SetTextureStageState( 0, D3DTSS_MINFILTER, stage_zero_minfilter );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter_initialize( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter_cleanup( void )
+{
+	sXboxShatterInstanceDetails *p_xbox_details;
+
+	Dbg_Assert( p_shatter_details_table );
+	
+	p_shatter_details_table->IterateStart();
+	sShatterInstanceDetails *p_details = p_shatter_details_table->IterateNext();
+	while( p_details )
+	{
+		p_xbox_details = static_cast( p_details );
+		
+		p_details = p_shatter_details_table->IterateNext();
+
+		p_shatter_details_table->FlushItem((uint32)p_xbox_details );
+		delete p_xbox_details;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void plat_shatter( CGeom *p_geom )
+{
+	CXboxGeom *p_xbox_geom = static_cast( p_geom );
+
+	// For each mesh in the geom...
+	for( uint32 m = 0; m < p_xbox_geom->m_num_mesh; ++m )
+	{
+		NxXbox::sMesh *p_mesh = p_xbox_geom->m_mesh_array[m];
+
+		if( p_mesh->m_num_indices[0] >= 3 )
+		{
+			// Set the block size for this mesh.
+			triSubdivideStack.SetBlockSize( p_mesh->m_vertex_stride );
+			
+			// Get a pointer to the renderable data.
+			BYTE *p_vert_data;
+			p_mesh->mp_vertex_buffer[0]->Lock( 0, 0, &p_vert_data, D3DLOCK_READONLY );
+				
+			// First scan through each non-degenerate tri, counting them to see how many verts we'll need.
+			// We also have to figure the area of the tris here, since we need to calculate the worst case given the requirements for subdivision.
+			uint32 valid_tris	= 0;
+			uint32 index0;
+			uint32 index1		= p_mesh->mp_index_buffer[0][0];
+			uint32 index2		= p_mesh->mp_index_buffer[0][1];
+			for( uint32 i = 2; i < p_mesh->m_num_indices[0]; ++i )
+			{
+				// Wrap the indices round.
+				index0 = index1;
+				index1 = index2;
+				index2 = p_mesh->mp_index_buffer[0][i];
+
+				if(( index0 != index1 ) && ( index0 != index2 ) && ( index1 != index2 ))
+				{
+					++valid_tris;
+
+					D3DXVECTOR3 *p_vert0 = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index0 ));
+					D3DXVECTOR3 *p_vert1 = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index1 ));
+					D3DXVECTOR3 *p_vert2 = (D3DXVECTOR3*)( p_vert_data + ( p_mesh->m_vertex_stride * index2 ));
+					
+					// Push this tri onto the stack.
+					triSubdivideStack.Push( p_vert0 );
+					triSubdivideStack.Push( p_vert1 );
+					triSubdivideStack.Push( p_vert2 );
+
+					// Figure the area of this tri.
+					Mth::Vector p(	p_vert1->x - p_vert0->x, p_vert1->y - p_vert0->y, p_vert1->z - p_vert0->z );
+					Mth::Vector q(	p_vert2->x - p_vert0->x, p_vert2->y - p_vert0->y, p_vert2->z - p_vert0->z );
+					Mth::Vector r(( p[Y] * q[Z] ) - ( q[Y] * p[Z] ), ( p[Z] * q[X] ) - ( q[Z] * p[X] ), ( p[X] * q[Y] ) - ( q[X] * p[Y] ));
+					float area_squared = r.LengthSqr();
+					if( area_squared > shatterAreaTest )
+					{
+						// We will need to subdivide - each subdivision will result in an area one quarter the previous area
+						// (and thusly the square of the area will be one sixteenth the previous area).
+						int num_extra_tris = 1;
+						while( area_squared > shatterAreaTest )
+						{
+							num_extra_tris *= 4;
+							area_squared *= ( 1.0f / 16.0f );
+						}
+					
+						// This original tri will not be added...
+						--valid_tris;
+
+						// ...however, the subdivided versions will.
+						valid_tris += num_extra_tris;
+					}
+				}
+			}
+
+			if( valid_tris == 0 )
+			{
+				continue;
+			}
+			
+			// Create a tracking structure for this mesh.
+			sXboxShatterInstanceDetails *p_details		= new sXboxShatterInstanceDetails( valid_tris, p_mesh );
+			uint8						*p_write_vertex	= p_details->mp_vertex_buffer;
+			uint32						details_index	= 0;
+
+			Mth::Vector					spread_center	= shatterVelocity * -shatterSpreadFactor;
+			float						base_speed		= shatterVelocity.Length();
+
+			spread_center += Mth::Vector( p_mesh->m_sphere_center.x, p_mesh->m_sphere_center.y, p_mesh->m_sphere_center.z );
+			
+			// Add the tracking structure to the table.
+			p_shatter_details_table->PutItem((uint32)p_details, p_details );
+			
+			// Process-subdivide the entire stack.
+			uint8 *p_copy_vertex = p_write_vertex;
+			while( subdivide_tri_stack( &p_write_vertex, p_mesh ));
+					
+			// Copy the (possibly subdivided) vertex data over.
+			Dbg_Assert(((uint32)p_write_vertex - (uint32)p_copy_vertex ) <= ( valid_tris * 3 * p_mesh->m_vertex_stride ));
+			while( p_copy_vertex < p_write_vertex )
+			{
+				Dbg_Assert( details_index < valid_tris );
+				
+				D3DXVECTOR3 *p_vert0 = (D3DXVECTOR3*)( p_copy_vertex + ( p_mesh->m_vertex_stride * 0 ));
+				D3DXVECTOR3 *p_vert1 = (D3DXVECTOR3*)( p_copy_vertex + ( p_mesh->m_vertex_stride * 1 ));
+				D3DXVECTOR3 *p_vert2 = (D3DXVECTOR3*)( p_copy_vertex + ( p_mesh->m_vertex_stride * 2 ));
+
+				// Calculate position as the midpoint of the three vertices per poly.
+				p_details->mp_positions[details_index][X] = ( p_vert0->x + p_vert1->x + p_vert2->x ) * ( 1.0f / 3.0f );
+				p_details->mp_positions[details_index][Y] = ( p_vert0->y + p_vert1->y + p_vert2->y ) * ( 1.0f / 3.0f );
+				p_details->mp_positions[details_index][Z] = ( p_vert0->z + p_vert1->z + p_vert2->z ) * ( 1.0f / 3.0f );
+
+				// Calculate the vector  back from the bounding box of the object. Then use this to figure the 'spread' of the
+				// shards by calculating the vector from this position to the center of each shard.
+				float speed = base_speed + ( base_speed * (( shatterVelocityVariance * rand() ) / RAND_MAX ));
+				p_details->mp_velocities[details_index] = ( p_details->mp_positions[details_index] - spread_center ).Normalize( speed );
+
+				Mth::Vector axis( -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ), -1.0f + ( 2.0f * (float)rand() / RAND_MAX ));
+				axis.Normalize();
+				p_details->mp_matrices[details_index].Ident();
+				p_details->mp_matrices[details_index].Rotate( axis, 0.1f * ((float)rand() / RAND_MAX ));
+
+				p_copy_vertex += ( p_mesh->m_vertex_stride * 3 );
+						
+				++details_index;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************************
+ *
+ * 
+ *****************************************************************************/
+void plat_shatter_update( sShatterInstanceDetails *p_details, float framelength )
+{
+	sXboxShatterInstanceDetails *p_xbox_details = static_cast( p_details );
+	
+	BYTE *p_vert_data = p_xbox_details->mp_vertex_buffer;
+	
+	// Load up initial three vertex pointers.
+	D3DXVECTOR3 *p_v0	= (D3DXVECTOR3*)( p_vert_data );
+	D3DXVECTOR3 *p_v1	= (D3DXVECTOR3*)( p_vert_data + p_xbox_details->mp_mesh->m_vertex_stride );
+	D3DXVECTOR3 *p_v2	= (D3DXVECTOR3*)( p_vert_data + ( 2 * p_xbox_details->mp_mesh->m_vertex_stride ));
+	
+	for( int i = 0; i < p_details->m_num_triangles; ++i )
+	{
+		// To move the shatter pieces:
+		// 1) subtract position from each vertex
+		// 2) rotate
+		// 3) update position with velocity
+		// 4) add new position to each vertex
+
+		// The matrix holds 3 vectors at once.
+		Mth::Matrix m;
+		m[X].Set( p_v0->x - p_details->mp_positions[i][X], p_v0->y - p_details->mp_positions[i][Y], p_v0->z - p_details->mp_positions[i][Z] );
+		m[Y].Set( p_v1->x - p_details->mp_positions[i][X], p_v1->y - p_details->mp_positions[i][Y], p_v1->z - p_details->mp_positions[i][Z] );
+		m[Z].Set( p_v2->x - p_details->mp_positions[i][X], p_v2->y - p_details->mp_positions[i][Y], p_v2->z - p_details->mp_positions[i][Z] );
+         
+		m[X].Rotate( p_details->mp_matrices[i] );
+		m[Y].Rotate( p_details->mp_matrices[i] );
+		m[Z].Rotate( p_details->mp_matrices[i] );
+
+		// Update the position and velocity of the shatter piece, dealing with bouncing if necessary.
+		p_details->UpdateParameters( i, framelength );
+      
+		m[X] += p_details->mp_positions[i]; 
+		m[Y] += p_details->mp_positions[i]; 
+		m[Z] += p_details->mp_positions[i];
+
+		p_v0->x = m[X][X]; p_v0->y = m[X][Y]; p_v0->z = m[X][Z];
+		p_v1->x = m[Y][X]; p_v1->y = m[Y][Y]; p_v1->z = m[Y][Z];
+		p_v2->x = m[Z][X]; p_v2->y = m[Z][Y]; p_v2->z = m[Z][Z];
+
+		p_v0 = (D3DXVECTOR3*)(((BYTE*)p_v0 ) + ( p_xbox_details->mp_mesh->m_vertex_stride * 3 ));
+		p_v1 = (D3DXVECTOR3*)(((BYTE*)p_v1 ) + ( p_xbox_details->mp_mesh->m_vertex_stride * 3 ));
+		p_v2 = (D3DXVECTOR3*)(((BYTE*)p_v2 ) + ( p_xbox_details->mp_mesh->m_vertex_stride * 3 ));
+	}
+
+	// Also process normals if they exist.
+	if( p_xbox_details->mp_mesh->m_normal_offset > 0 )
+	{
+		p_v0	= (D3DXVECTOR3*)( p_vert_data + p_xbox_details->mp_mesh->m_normal_offset );
+		p_v1	= (D3DXVECTOR3*)( p_vert_data + p_xbox_details->mp_mesh->m_normal_offset + p_xbox_details->mp_mesh->m_vertex_stride );
+		p_v2	= (D3DXVECTOR3*)( p_vert_data + p_xbox_details->mp_mesh->m_normal_offset + ( 2 * p_xbox_details->mp_mesh->m_vertex_stride ));
+	
+		for( int i = 0; i < p_details->m_num_triangles; ++i )
+		{
+			// The matrix holds 3 vectors at once.
+			Mth::Matrix m;
+			m[X].Set( p_v0->x, p_v0->y, p_v0->z );
+			m[Y].Set( p_v1->x, p_v1->y, p_v1->z );
+			m[Z].Set( p_v2->x, p_v2->y, p_v2->z );
+         
+			m[X].Rotate( p_details->mp_matrices[i] );
+			m[Y].Rotate( p_details->mp_matrices[i] );
+			m[Z].Rotate( p_details->mp_matrices[i] );
+
+			p_v0->x = m[X][X]; p_v0->y = m[X][Y]; p_v0->z = m[X][Z];
+			p_v1->x = m[Y][X]; p_v1->y = m[Y][Y]; p_v1->z = m[Y][Z];
+			p_v2->x = m[Z][X]; p_v2->y = m[Z][Y]; p_v2->z = m[Z][Z];
+
+			p_v0 = (D3DXVECTOR3*)(((BYTE*)p_v0 ) + ( p_xbox_details->mp_mesh->m_vertex_stride * 3 ));
+			p_v1 = (D3DXVECTOR3*)(((BYTE*)p_v1 ) + ( p_xbox_details->mp_mesh->m_vertex_stride * 3 ));
+			p_v2 = (D3DXVECTOR3*)(((BYTE*)p_v2 ) + ( p_xbox_details->mp_mesh->m_vertex_stride * 3 ));
+		}
+	}
+}
+
+
+
+/******************************************************************************
+ *
+ * 
+ *****************************************************************************/
+void plat_shatter_render( sShatterInstanceDetails *p_details )
+{
+	sXboxShatterInstanceDetails *p_xbox_details = static_cast( p_details );
+
+	p_xbox_details->mp_mesh->mp_material->Submit();
+
+	NxXbox::set_pixel_shader( p_xbox_details->mp_mesh->m_pixel_shader );
+	NxXbox::set_vertex_shader( p_xbox_details->mp_mesh->m_vertex_shader[0] );
+
+	NxXbox::EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_TRIANGLELIST, p_xbox_details->m_num_triangles, p_xbox_details->mp_vertex_buffer, p_xbox_details->mp_mesh->m_vertex_stride );
+}
+
+	
+///////////////////////////////////////////////////////////////////
+//
+// FOG
+//
+///////////////////////////////////////////////////////////////////
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_enable_fog( bool enable )
+{
+	if( enable != (bool)NxXbox::EngineGlobals.fog_enabled )
+	{
+		NxXbox::EngineGlobals.fog_enabled = enable;
+		D3DDevice_SetRenderState( D3DRS_FOGENABLE, NxXbox::EngineGlobals.fog_enabled );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_set_fog_near_distance( float distance )
+{
+	NxXbox::EngineGlobals.fog_start	= -distance;
+
+	// Test code for now.
+	NxXbox::EngineGlobals.fog_end	= NxXbox::EngineGlobals.fog_start - FEET_TO_INCHES( 600.0f );
+
+	D3DDevice_SetRenderState( D3DRS_FOGSTART,	*((DWORD*)( &NxXbox::EngineGlobals.fog_start )));
+	D3DDevice_SetRenderState( D3DRS_FOGEND,		*((DWORD*)( &NxXbox::EngineGlobals.fog_end )));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_set_fog_exponent( float exponent )
+{
+	// This is no longer a valid call.
+//	if( exponent > 0.0f )
+//	{
+//		s_plat_enable_fog( true );
+//
+//		NxXbox::EngineGlobals.fog_start				= FEET_TO_INCHES( -20.0f );
+//		NxXbox::EngineGlobals.fog_end				= FEET_TO_INCHES( -60.0f );
+//		D3DDevice_SetRenderState( D3DRS_FOGSTART,	*((DWORD*)( &NxXbox::EngineGlobals.fog_start )));
+//		D3DDevice_SetRenderState( D3DRS_FOGEND,		*((DWORD*)( &NxXbox::EngineGlobals.fog_end )));
+//	}
+//	else
+//	{
+//		s_plat_enable_fog( false );
+//	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_set_fog_color( void )
+{
+}
+	
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_fog_update( void )
+{
+}
+	
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFog::s_plat_set_fog_rgba( Image::RGBA rgba )
+{
+	// Alpha effectively determines the fog density, with zero alpha meaning no fog.
+	if( rgba.a == 0 )
+	{
+		s_plat_enable_fog( false );
+	}
+	else
+	{
+		s_plat_enable_fog( true );
+	}
+
+	// Calculate alpha (and clamp between 0.0 and 1.0).
+	float f_alpha = (float)rgba.a / 128.0f;
+	f_alpha = Mth::Min( f_alpha, 1.0f );
+	f_alpha = Mth::Max( f_alpha, 0.0f );
+
+	// Set the density register in the pixel shader constants (uses c4.r/g/b).
+	NxXbox::EngineGlobals.pixel_shader_constants[16] = f_alpha;
+	NxXbox::EngineGlobals.pixel_shader_constants[17] = f_alpha;
+	NxXbox::EngineGlobals.pixel_shader_constants[18] = f_alpha;
+
+	NxXbox::EngineGlobals.fog_color	= ((uint32)rgba.r << 16 ) | ((uint32)rgba.g << 8 ) | ((uint32)rgba.b );
+	D3DDevice_SetRenderState( D3DRS_FOGCOLOR, NxXbox::EngineGlobals.fog_color );
+}
+
+} // Nx
diff --git a/Code/Gfx/XBox/p_nxnewparticle.cpp b/Code/Gfx/XBox/p_nxnewparticle.cpp
new file mode 100644
index 0000000..4021a39
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxnewparticle.cpp
@@ -0,0 +1,864 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticle.cpp										**
+**																			**
+**	Created by:		3/25/03	-	SPG											**
+**																			**
+**	Description:	Xbox new parametric particle system						**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static int rand_seed;
+static int rand_a	= 314159265;
+static int rand_b	= 178453311;
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void seed_particle_rnd( int s, int a, int b )
+{
+	rand_seed		= s;
+	rand_a			= a;
+	rand_b			= b;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static inline int particle_rnd( int n )
+{
+	rand_seed	= rand_seed * rand_a + rand_b;
+	rand_a		= ( rand_a ^ rand_seed ) + ( rand_seed >> 4 );
+	rand_b		+= ( rand_seed >> 3 ) - 0x10101010L;
+	return (int)(( rand_seed & 0xffff ) * n ) >> 16;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CParticleStream::AdvanceSeed( int num_places )
+{
+	// Seed the random number generator back to the current seed.
+	seed_particle_rnd( m_rand_seed, m_rand_a, m_rand_b );
+
+	// Each particle will call the random function four times.
+	for( int i = 0; i < ( num_places * 4 ); i++ )
+	{
+		rand_seed	= rand_seed * rand_a + rand_b;
+		rand_a		= ( rand_a ^ rand_seed ) + ( rand_seed >> 4 );
+		rand_b		+= ( rand_seed >> 3 ) - 0x10101010L;
+	}
+
+	m_rand_seed = rand_seed;
+	m_rand_a	= rand_a;
+	m_rand_b	= rand_b;
+}
+	
+	
+	
+inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxNewParticle::plat_render( void )
+{
+	CParticleStream* p_stream;
+	int i;
+
+	// Process the streams.
+	if( m_params.m_EmitRate && ( !m_emitting || ( m_params.m_EmitRate != mp_newest_stream->m_rate )))
+	{	
+		if( m_num_streams < m_max_streams )
+		{
+			// Add new stream to cyclic buffer
+			m_num_streams++;
+			mp_newest_stream++;
+			if( mp_newest_stream == mp_stream + m_max_streams )
+			{
+				mp_newest_stream = mp_stream;
+			}
+
+			// Initialise new stream.
+			mp_newest_stream->m_rate			= m_params.m_EmitRate;
+			mp_newest_stream->m_interval		= 1.0f / m_params.m_EmitRate;
+			mp_newest_stream->m_oldest_age		= 0.0f;
+			mp_newest_stream->m_num_particles	= 0;
+			mp_newest_stream->m_rand_seed		= rand();
+			mp_newest_stream->m_rand_a			= 314159265;
+			mp_newest_stream->m_rand_b			= 178453311;
+			m_emitting = true;
+		}
+		else
+		{
+			m_emitting = false;
+		}
+	}
+	else
+	{
+		m_emitting = m_params.m_EmitRate;
+	}
+
+	if( !m_num_streams )
+		return;
+
+	// Age all streams.
+	for( i = 0, p_stream = mp_oldest_stream; i < m_num_streams; ++i )
+	{
+		// Increase age of oldest particle.
+		p_stream->m_oldest_age += 1.0f / 60.0f;
+
+		// Step pointer within cyclic buffer.
+		p_stream++;
+		if( p_stream == mp_stream + m_max_streams )
+		{
+			p_stream = mp_stream;
+		}
+	}
+
+	// Births into newest stream.
+	if( m_emitting )
+	{
+		// How many particles so far emitted?
+		mp_newest_stream->m_num_particles = (int)( mp_newest_stream->m_oldest_age * mp_newest_stream->m_rate + 1.0f );
+	}
+
+	// Deaths from oldest stream.
+	if( mp_oldest_stream->m_oldest_age > m_params.m_Lifetime )
+	{
+		// Work out number dead.
+		int particles_dead = (int)(( mp_oldest_stream->m_oldest_age - m_params.m_Lifetime ) * mp_oldest_stream->m_rate + 1.0f );
+
+		// Remove dead particles.
+		mp_oldest_stream->m_num_particles -= particles_dead;
+
+		// Should we keep processing the oldest stream?
+		if( mp_oldest_stream->m_num_particles > 0 || ( m_num_streams == 1 && m_emitting ))
+		{
+			// Adjust age of oldest particle.
+			mp_oldest_stream->m_oldest_age -= (float)particles_dead * mp_oldest_stream->m_interval;
+
+			// Advance seed.
+			mp_oldest_stream->AdvanceSeed( particles_dead );
+		}
+		else
+		{
+			// Remove oldest stream and wrap in cyclic buffer if necessary.
+			m_num_streams--;
+			mp_oldest_stream++;
+			if( mp_oldest_stream == mp_stream + m_max_streams )
+			{
+				mp_oldest_stream = mp_stream;
+			}
+			if( !m_num_streams )
+				return;
+		}
+	}
+
+	// Now render the streams. after checking the bounding sphere is visible (for no dynamic systems).
+	if( !m_params.m_LocalCoord )
+	{
+		D3DXVECTOR3	center( m_bsphere[X], m_bsphere[Y], m_bsphere[Z] );
+		if( !NxXbox::frustum_check_sphere( ¢er, m_bsphere[W] ))
+		{
+			return;
+		}
+	}
+
+	// Swap the r and b color components in the params.
+	for( i = 0; i < vNUM_BOXES; ++i )
+	{
+		uint8 swap = m_params.m_Color[i].r;
+		m_params.m_Color[i].r	= m_params.m_Color[i].b;
+		m_params.m_Color[i].b	= swap;
+	}
+
+	// Figure the distance from the camera to the system, and use a different technique accordingly.
+	// Need to do this because point sprites are limited to 64 pixels in each dimension, and sometimes
+	// you can get close enough to a system that a single particle will occupy more than this.
+	// A more sophisticated approach would be to figure what the max particle size could be at a given distance,
+	// rather than using the current hardcoded distance value of 20 feet.
+	float dist_squared = Mth::DistanceSqr( Mth::Vector( m_bsphere[X], m_bsphere[Y], m_bsphere[Z], 0.0f ),
+										   Mth::Vector( NxXbox::EngineGlobals.cam_position.x, NxXbox::EngineGlobals.cam_position.y, NxXbox::EngineGlobals.cam_position.z, 0.0f ));
+	if( dist_squared < 57600.0f )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+
+		// Submit particle material.
+		mp_material->Submit();
+
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( ParticleNewFlatVS );
+		NxXbox::set_pixel_shader( PixelShader0 );
+
+		// Load up the combined world->view_projection matrix.
+		XGMATRIX	dest_matrix;
+		XGMATRIX	projMatrix;
+		XGMATRIX	viewMatrix;
+			
+		// Projection matrix.
+		XGMatrixTranspose( &projMatrix, &NxXbox::EngineGlobals.projection_matrix );
+		
+		// View matrix.
+		XGMatrixTranspose( &viewMatrix, &NxXbox::EngineGlobals.view_matrix );
+		viewMatrix.m[3][0] = 0.0f;
+		viewMatrix.m[3][1] = 0.0f;
+		viewMatrix.m[3][2] = 0.0f;
+		viewMatrix.m[3][3] = 1.0f;
+
+		// Calculate composite world->view->projection matrix (simplified since world transform will be indentity).
+		XGMatrixMultiply( &dest_matrix, &projMatrix, &viewMatrix );
+
+		// Load up the combined world, camera & projection matrix.
+		D3DDevice_SetVertexShaderConstantFast( 0, (void*)&dest_matrix, 4 );
+
+		float vector_upload[8];
+		vector_upload[0]	= screen_right[X];
+		vector_upload[1]	= screen_right[Y];
+		vector_upload[2]	= screen_right[Z];
+		vector_upload[4]	= screen_up[X];
+		vector_upload[5]	= screen_up[Y];
+		vector_upload[6]	= screen_up[Z];
+		D3DDevice_SetVertexShaderConstantFast( 4, (void*)( &vector_upload[0] ), 2 );
+
+		static float vconstants[32]	= {	0.0f,  0.0f, 1.0f, 1.0f,		// Vert tex coords in C8 through C11
+										1.0f,  0.0f, 1.0f, 1.0f,
+										1.0f,  1.0f, 1.0f, 1.0f,
+										0.0f,  1.0f, 1.0f, 1.0f,
+									-1.0f,  1.0f, 1.0f, 1.0f,		// Vert w/h multipliers in C12 through C15
+										1.0f,  1.0f, 1.0f, 1.0f,
+										1.0f, -1.0f, 1.0f, 1.0f,
+									-1.0f, -1.0f, 1.0f, 1.0f };
+		D3DDevice_SetVertexShaderConstantFast( 8, (void*)( &vconstants[0] ), 8 );
+
+		float stream_params[24];
+		stream_params[0]	= m_s0[X];
+		stream_params[1]	= m_s0[Y];
+		stream_params[2]	= m_s0[Z];
+		stream_params[3]	= m_s0[W];
+
+		stream_params[4]	= m_s1[X];
+		stream_params[5]	= m_s1[Y];
+		stream_params[6]	= m_s1[Z];
+		stream_params[7]	= m_s1[W];
+
+		stream_params[8]	= m_s2[X];
+		stream_params[9]	= m_s2[Y];
+		stream_params[10]	= m_s2[Z];
+		stream_params[11]	= m_s2[W];
+
+		stream_params[12]	= m_p0[X];
+		stream_params[13]	= m_p0[Y];
+		stream_params[14]	= m_p0[Z];
+		stream_params[15]	= m_p0[W];
+
+		stream_params[16]	= m_p1[X];
+		stream_params[17]	= m_p1[Y];
+		stream_params[18]	= m_p1[Z];
+		stream_params[19]	= m_p1[W];
+
+		stream_params[20]	= m_p2[X];
+		stream_params[21]	= m_p2[Y];
+		stream_params[22]	= m_p2[Z];
+		stream_params[23]	= m_p2[W];
+		D3DDevice_SetVertexShaderConstantFast( 18, (void*)( &stream_params[0] ), 6 );
+
+		// Construct a packet with data for each stream.
+		for( i = 0, p_stream = mp_oldest_stream; i < m_num_streams; i++, p_stream++ )
+		{
+			Dbg_MsgAssert( p_stream->m_num_particles < 65536, ( "particle limit reached" ));
+
+			// Wrap at end of cyclic buffer.
+			if( p_stream == mp_stream + m_max_streams )
+			{
+				p_stream = mp_stream;
+			}
+
+			// Calculate space needed.
+			DWORD dwords_per_particle	= 28;
+			DWORD dword_count			= dwords_per_particle * p_stream->m_num_particles;
+
+			// Obtain push buffer lock.
+			// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+			// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+			// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+			DWORD *p_push; 
+			p_push = D3DDevice_BeginPush( dword_count );
+
+			float t				= p_stream->m_oldest_age;
+			float midpoint_time = m_params.m_Lifetime * ( m_params.m_ColorMidpointPct * 0.01f );
+
+			// Seed the random number generators for this stream.
+			seed_particle_rnd( p_stream->m_rand_seed, p_stream->m_rand_a, p_stream->m_rand_b );
+
+			for( int p = 0; p < p_stream->m_num_particles; ++p )
+			{
+				// Generate random vector. Each component in the range [1.0, 2.0].
+				float r[4];
+				r[0] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+				r[1] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+				r[2] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+				r[3] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+
+				float color_interpolator;
+				Image::RGBA	col0, col1;
+
+				if( m_params.m_UseMidcolor )
+				{
+					if( t > midpoint_time )
+					{
+						color_interpolator	= ( t - midpoint_time ) * ReciprocalEstimate_ASM( m_params.m_Lifetime - midpoint_time );
+						col0				= m_params.m_Color[1];
+						col1				= m_params.m_Color[2];
+					}
+					else
+					{
+						color_interpolator	= t * ReciprocalEstimate_ASM( midpoint_time );
+						col0				= m_params.m_Color[0];
+						col1				= m_params.m_Color[1];
+					}
+				}
+				else 
+				{
+					color_interpolator		= t * ReciprocalEstimate_ASM( m_params.m_Lifetime );
+					col0					= m_params.m_Color[0];
+					col1					= m_params.m_Color[2];
+				}
+
+				// We're going to be loading constants.
+				p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT_LOAD, 1 );
+
+				// Specify the starting register (physical registers are offset by 96 from the D3D logical register).
+				p_push[1]	= 96 + 16;
+
+				// Specify the number of DWORDS to load. 8 DWORDS for 2 constants.
+				p_push[2]	= D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT, 8 );
+
+				// Load r vector.
+				p_push[3]	= *((DWORD*)&r[0] );
+				p_push[4]	= *((DWORD*)&r[1] );
+				p_push[5]	= *((DWORD*)&r[2] );
+				p_push[6]	= *((DWORD*)&r[3] );
+
+				// Load interpolator values.
+				p_push[7]	= *((DWORD*)&t );
+				p_push[8]	= *((DWORD*)&color_interpolator );
+				p_push		+= 11;
+
+				p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+				p_push[1]	= D3DPT_QUADLIST;
+				p_push		+= 2;
+
+				// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+				// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+				p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, 12 );
+				++p_push;
+
+				// Now we can start the actual vertex data.
+				p_push[0]	= *((DWORD*)&col0 );
+				p_push[1]	= *((DWORD*)&col1 );
+				p_push[2]	= 0x00000000UL;
+
+				p_push[3]	= *((DWORD*)&col0 );
+				p_push[4]	= *((DWORD*)&col1 );
+				p_push[5]	= 0x00010001UL;
+
+				p_push[6]	= *((DWORD*)&col0 );
+				p_push[7]	= *((DWORD*)&col1 );
+				p_push[8]	= 0x00020002UL;
+				
+				p_push[9]	= *((DWORD*)&col0 );
+				p_push[10]	= *((DWORD*)&col1 );
+				p_push[11]	= 0x00030003UL;
+
+				p_push		+= 12;
+
+				// End of vertex data for this particle.
+				p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+				p_push[1] = 0;
+				p_push += 2;
+
+				// Reduce t by particle interval.
+				t -= p_stream->m_interval;
+			}
+			D3DDevice_EndPush( p_push );
+		}
+	}
+	else
+	{
+		// Submit particle material.
+		mp_material->Submit();
+
+		// Point sprites actually use texture stage 3...
+		NxXbox::set_render_state( RS_UVADDRESSMODE3, 0x00000000UL );
+		mp_material->mp_tex[0]->Set( 3 );
+
+		// Set up point sprite rendering.
+		D3DDevice_SetRenderState( D3DRS_POINTSPRITEENABLE,	TRUE );
+		D3DDevice_SetRenderState( D3DRS_POINTSCALEENABLE,	TRUE );
+//		D3DDevice_SetRenderState( D3DRS_POINTSCALE_A,		FtoDW( 0.00f ));
+//		D3DDevice_SetRenderState( D3DRS_POINTSCALE_B,		FtoDW( 0.00f ));
+//		D3DDevice_SetRenderState( D3DRS_POINTSCALE_C,		FtoDW( 1.00f ));
+
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( ParticleNewFlatPointSpriteVS );
+		NxXbox::set_pixel_shader( PixelShaderPointSprite );
+
+		// Load up the combined world->view_projection matrix.
+		XGMATRIX	dest_matrix;
+		XGMATRIX	projMatrix;
+		XGMATRIX	viewMatrix;
+			
+		// Projection matrix.
+		XGMatrixTranspose( &projMatrix, &NxXbox::EngineGlobals.projection_matrix );
+		
+		// View matrix.
+		XGMatrixTranspose( &viewMatrix, &NxXbox::EngineGlobals.view_matrix );
+		viewMatrix.m[3][0] = 0.0f;
+		viewMatrix.m[3][1] = 0.0f;
+		viewMatrix.m[3][2] = 0.0f;
+		viewMatrix.m[3][3] = 1.0f;
+
+		// Calculate composite world->view->projection matrix (simplified since world transform will be indentity).
+		XGMatrixMultiply( &dest_matrix, &projMatrix, &viewMatrix );
+
+		// Load up the combined world, camera & projection matrix.
+		D3DDevice_SetVertexShaderConstantFast( 0, (void*)&dest_matrix, 4 );
+
+		// Load up the stream parameters.
+		D3DDevice_SetVertexShaderConstantFast( 18, (void*)( &m_s0[X] ), 1 );
+		D3DDevice_SetVertexShaderConstantFast( 19, (void*)( &m_s1[X] ), 1 );
+		D3DDevice_SetVertexShaderConstantFast( 20, (void*)( &m_s2[X] ), 1 );
+
+		D3DDevice_SetVertexShaderConstantFast( 21, (void*)( &m_p0[X] ), 1 );
+		D3DDevice_SetVertexShaderConstantFast( 22, (void*)( &m_p1[X] ), 1 );
+		D3DDevice_SetVertexShaderConstantFast( 23, (void*)( &m_p2[X] ), 1 );
+
+		// Load up the camera position and viewport height.
+		static float cam_pos_viewport_height[4];
+		cam_pos_viewport_height[0]	= NxXbox::EngineGlobals.cam_position.x;
+		cam_pos_viewport_height[1]	= NxXbox::EngineGlobals.cam_position.y;
+		cam_pos_viewport_height[2]	= NxXbox::EngineGlobals.cam_position.z;
+		cam_pos_viewport_height[3]	= NxXbox::EngineGlobals.viewport.Height * 2.0f;
+
+		D3DDevice_SetVertexShaderConstantFast( 24, (void*)( &cam_pos_viewport_height[0] ), 1 );
+
+		// Construct a packet with data for each stream.
+		for( i = 0, p_stream = mp_oldest_stream; i < m_num_streams; i++, p_stream++ )
+		{
+			Dbg_MsgAssert( p_stream->m_num_particles < 65536, ( "particle limit reached" ));
+
+			// Wrap at end of cyclic buffer.
+			if( p_stream == mp_stream + m_max_streams )
+			{
+				p_stream = mp_stream;
+			}
+
+			// Calculate space needed.
+			DWORD dwords_per_particle	= 13;
+			DWORD dword_count			= dwords_per_particle * p_stream->m_num_particles;
+
+			// Obtain push buffer lock.
+			// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+			// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+			// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+			DWORD *p_push; 
+			p_push = D3DDevice_BeginPush( dword_count );
+
+			float t				= p_stream->m_oldest_age;
+			float midpoint_time = m_params.m_Lifetime * ( m_params.m_ColorMidpointPct * 0.01f );
+
+			// Seed the random number generators for this stream.
+			seed_particle_rnd( p_stream->m_rand_seed, p_stream->m_rand_a, p_stream->m_rand_b );
+
+			for( int p = 0; p < p_stream->m_num_particles; ++p )
+			{
+				// Generate random vector. Each component in the range [1.0, 2.0].
+				float r[4];
+				r[0] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+				r[1] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+				r[2] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+				r[3] = 1.0f + ((float)particle_rnd( 16384 ) / 16384 );
+
+				float color_interpolator;
+				Image::RGBA	col0, col1;
+
+				if( m_params.m_UseMidcolor )
+				{
+					if( t > midpoint_time )
+					{
+						color_interpolator	= ( t - midpoint_time ) * ReciprocalEstimate_ASM( m_params.m_Lifetime - midpoint_time );
+						col0				= m_params.m_Color[1];
+						col1				= m_params.m_Color[2];
+					}
+					else
+					{
+						color_interpolator	= t * ReciprocalEstimate_ASM( midpoint_time );
+						col0				= m_params.m_Color[0];
+						col1				= m_params.m_Color[1];
+					}
+				}
+				else 
+				{
+					color_interpolator		= t * ReciprocalEstimate_ASM( m_params.m_Lifetime );
+					col0					= m_params.m_Color[0];
+					col1					= m_params.m_Color[2];
+				}
+
+				// Signal the primitive type to follow.
+				p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+				p_push[1]	= D3DPT_POINTLIST;
+				p_push		+= 2;
+
+				// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+				// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+				p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, 8 );
+				++p_push;
+
+				// Now we can start the actual vertex data.
+
+				// Load r vector.
+				p_push[0]	= *((DWORD*)&r[0] );
+				p_push[1]	= *((DWORD*)&r[1] );
+				p_push[2]	= *((DWORD*)&r[2] );
+				p_push[3]	= *((DWORD*)&r[3] );
+
+				// Load time and color interpolator values.
+				p_push[4]	= *((DWORD*)&t );
+				p_push[5]	= *((DWORD*)&color_interpolator );
+
+				// Load colors.
+				p_push[6]	= *((DWORD*)&col0 );
+				p_push[7]	= *((DWORD*)&col1 );
+
+				p_push		+= 8;
+
+				// End of vertex data for this particle.
+				p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+				p_push[1] = 0;
+				p_push += 2;
+
+				// Reduce t by particle interval.
+				t -= p_stream->m_interval;
+			}
+			D3DDevice_EndPush( p_push );
+		}
+
+		// Restore render states.
+		D3DDevice_SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
+	}
+
+	// Swap the r and b color components back in the params.
+	for( i = 0; i < vNUM_BOXES; ++i )
+	{
+		uint8 swap = m_params.m_Color[i].r;
+		m_params.m_Color[i].r	= m_params.m_Color[i].b;
+		m_params.m_Color[i].b	= swap;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxNewParticle::update_position( void )
+{
+	// Convert 3-point -> PVA format
+	float t1 = m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f;
+	float t2 = m_params.m_Lifetime;
+	Mth::Vector u, a_;
+
+	Mth::Vector x0	= m_params.m_BoxPos[0];
+	x0[3]			= m_params.m_Radius[0];
+	Mth::Vector x1	= m_params.m_BoxPos[1];
+	x1[3]			= m_params.m_Radius[1];
+	Mth::Vector x2	= m_params.m_BoxPos[2];
+	x2[3]			= m_params.m_Radius[2];
+
+	if( m_params.m_UseMidpoint )
+	{
+		u  = ( t2 * t2 * ( x1 - x0 ) - t1 * t1 * ( x2 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+		a_ = ( t1 * ( x2 - x0 ) - t2 * ( x1 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set( 0, 0, 0, 0 );
+	}
+
+	m_p0 = x0 - 1.5f * m_s0;
+	m_p1 = u  - 1.5f * m_s1;
+	m_p2 = a_ - 1.5f * m_s2;
+	m_p0[3] = x0[3] - 1.5f * m_s0[3];
+	m_p1[3] = u[3]  - 1.5f * m_s1[3];
+	m_p2[3] = a_[3] - 1.5f * m_s2[3];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxNewParticle::plat_update( void )
+{
+	if( m_params.m_LocalCoord )
+	{
+		update_position();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxNewParticle::plat_build( void )
+{
+	// Reduce emit rate selectively to improve performance.
+	m_params.m_EmitRate	= m_params.m_EmitRate * 0.5f;
+
+	// Initialise streams.
+	m_max_streams		= 5;
+	m_num_streams		= 0;
+
+	mp_stream			= new CParticleStream[m_max_streams]; 
+	mp_newest_stream	= mp_stream + m_max_streams - 1;
+	mp_oldest_stream	= mp_stream;
+	m_emitting			= false;
+
+	// Create a (semi-transparent) material used to render the mesh.
+	mp_material			= new NxXbox::sMaterial;
+	ZeroMemory( mp_material, sizeof( NxXbox::sMaterial ));
+
+	mp_material->m_flags[0]		= MATFLAG_TRANSPARENT | MATFLAG_TEXTURED;
+	mp_material->m_passes		= 1;
+	mp_material->m_alpha_cutoff	= 1;
+	mp_material->m_no_bfc		= true;
+	mp_material->m_color[0][0]	= 0.5f;
+	mp_material->m_color[0][1]	= 0.5f;
+	mp_material->m_color[0][2]	= 0.5f;
+	mp_material->m_color[0][3]	= m_params.m_FixedAlpha * ( 1.0f /  128.0f );
+	mp_material->m_reg_alpha[0]	= NxXbox::GetBlendMode( m_params.m_BlendMode );
+
+	// Get texture.
+	Nx::CTexture*		p_texture;
+	Nx::CXboxTexture*	p_xbox_texture;
+	mp_material->mp_tex[0]	= NULL;
+	p_texture				= Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( m_params.m_Texture );
+	p_xbox_texture			= static_cast( p_texture );
+	if( p_xbox_texture )
+	{
+		mp_material->mp_tex[0] = p_xbox_texture->GetEngineTexture();
+	}
+
+	// Convert 3-point -> PVA format.
+	float t1 = m_params.m_Lifetime * m_params.m_MidpointPct * 0.01f;
+	float t2 = m_params.m_Lifetime;
+	Mth::Vector x0,x1,x2,u,a_;
+
+	x0    = m_params.m_BoxDims[0];
+	x0[3] = m_params.m_RadiusSpread[0];
+	x1    = m_params.m_BoxDims[1];
+	x1[3] = m_params.m_RadiusSpread[1];
+	x2    = m_params.m_BoxDims[2];
+	x2[3] = m_params.m_RadiusSpread[2];
+
+	if( m_params.m_UseMidpoint )
+	{
+		u  = ( t2 * t2 * ( x1 - x0 ) - t1 * t1 * ( x2 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+		a_ = ( t1 * ( x2 - x0 ) - t2 * ( x1 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+	}
+
+	m_s0 = x0;
+	m_s1 = u;
+	m_s2 = a_;
+
+	x0    = m_params.m_BoxPos[0];
+	x0[3] = m_params.m_Radius[0];
+	x1    = m_params.m_BoxPos[1];
+	x1[3] = m_params.m_Radius[1];
+	x2    = m_params.m_BoxPos[2];
+	x2[3] = m_params.m_Radius[2];
+
+	if( m_params.m_UseMidpoint )
+	{
+		u  =  ( t2 * t2 * ( x1 - x0 ) - t1 * t1 * ( x2 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+		a_ =  ( t1 * ( x2 - x0 ) - t2 * ( x1 - x0 )) / ( t1 * t2 * ( t2 - t1 ));
+	}
+	else
+	{
+		u  = ( x2 - x0 ) / t2;
+		a_.Set( 0.0f, 0.0f, 0.0f, 0.0f );
+	}
+
+	m_p0	= x0 - 1.5f * m_s0;
+	m_p1	= u  - 1.5f * m_s1;
+	m_p2	= a_ - 1.5f * m_s2;
+	m_p0[3]	= x0[3] - 1.5f * m_s0[3];
+	m_p1[3]	= u[3]  - 1.5f * m_s1[3];
+	m_p2[3]	= a_[3] - 1.5f * m_s2[3];
+
+	update_position();
+
+	// Color.
+	if( m_params.m_UseMidcolor )
+	{
+		float q0 = 100.0f / ( m_params.m_Lifetime * m_params.m_ColorMidpointPct );
+		float q1 = 100.0f / ( m_params.m_Lifetime * ( 100.0f - m_params.m_ColorMidpointPct ));
+
+//		m_systemDmaData.m_c0[0] = ((float)m_params.m_Color[1].r - (float)m_params.m_Color[0].r) * q0;
+//		m_systemDmaData.m_c0[1] = ((float)m_params.m_Color[1].g - (float)m_params.m_Color[0].g) * q0;
+//		m_systemDmaData.m_c0[2] = ((float)m_params.m_Color[1].b - (float)m_params.m_Color[0].b) * q0;
+//		m_systemDmaData.m_c0[3] = ((float)m_params.m_Color[1].a - (float)m_params.m_Color[0].a) * q0;
+
+//		m_systemDmaData.m_c1[0] = (float)m_params.m_Color[1].r;
+//		m_systemDmaData.m_c1[1] = (float)m_params.m_Color[1].g;
+//		m_systemDmaData.m_c1[2] = (float)m_params.m_Color[1].b;
+//		m_systemDmaData.m_c1[3] = (float)m_params.m_Color[1].a;
+
+//		m_systemDmaData.m_c2[0] = ((float)m_params.m_Color[2].r - (float)m_params.m_Color[1].r) * q1;
+//		m_systemDmaData.m_c2[1] = ((float)m_params.m_Color[2].g - (float)m_params.m_Color[1].g) * q1;
+//		m_systemDmaData.m_c2[2] = ((float)m_params.m_Color[2].b - (float)m_params.m_Color[1].b) * q1;
+//		m_systemDmaData.m_c2[3] = ((float)m_params.m_Color[2].a - (float)m_params.m_Color[1].a) * q1;
+	}
+	else // else suppress mid-colour
+	{
+		float q = 1.0f / m_params.m_Lifetime;
+
+//		m_systemDmaData.m_c1[0] = (float)m_params.m_Color[0].r;
+//		m_systemDmaData.m_c1[1] = (float)m_params.m_Color[0].g;
+//		m_systemDmaData.m_c1[2] = (float)m_params.m_Color[0].b;
+//		m_systemDmaData.m_c1[3] = (float)m_params.m_Color[0].a;
+
+//		m_systemDmaData.m_c2[0] = ((float)m_params.m_Color[2].r - (float)m_params.m_Color[0].r) * q;
+//		m_systemDmaData.m_c2[1] = ((float)m_params.m_Color[2].g - (float)m_params.m_Color[0].g) * q;
+//		m_systemDmaData.m_c2[2] = ((float)m_params.m_Color[2].b - (float)m_params.m_Color[0].b) * q;
+//		m_systemDmaData.m_c2[3] = ((float)m_params.m_Color[2].a - (float)m_params.m_Color[0].a) * q;
+	}
+
+	// Rotation matrix.
+	m_rotation.Identity();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxNewParticle::plat_destroy( void )
+{
+	if( mp_stream )
+	{
+		delete [] mp_stream;
+	}
+
+	if( mp_material )
+	{
+		delete mp_material;
+	}
+}
+
+
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxnewparticle.h b/Code/Gfx/XBox/p_nxnewparticle.h
new file mode 100644
index 0000000..96a51c5
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxnewparticle.h
@@ -0,0 +1,125 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticle.h										**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	Xbox implementation of new parametric particle system	**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_XBOX_P_NXNEWPARTICLE_H__
+#define __GFX_XBOX_P_NXNEWPARTICLE_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Nx
+{
+
+                        
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CParticleStream
+{
+public:
+	int						m_num_particles;
+	float					m_rate;
+	float					m_interval;
+	float					m_oldest_age;
+	uint32					m_rand_seed;
+	uint32					m_rand_a;
+	uint32					m_rand_b;
+	void					AdvanceSeed( int num_places );
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxNewParticle : public CNewParticle
+{
+	bool				m_emitting;
+	int					m_max_streams;
+	int					m_num_streams;
+	CParticleStream*	mp_stream;
+	CParticleStream*	mp_newest_stream;
+	CParticleStream*	mp_oldest_stream;
+	Mth::Matrix 		m_rotation;
+	Mth::Matrix			m_new_matrix;
+	NxXbox::sMaterial*	mp_material;
+
+	Mth::Vector			m_s0;
+	Mth::Vector			m_s1;
+	Mth::Vector			m_s2;
+	Mth::Vector			m_p0;
+	Mth::Vector			m_p1;
+	Mth::Vector			m_p2;
+
+protected:
+	void	plat_build( void );
+	void	plat_destroy( void );
+	void	plat_render( void );
+	void	plat_update( void );
+	void	update_position( void );
+};
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_XBOX_P_NXNEWPARTICLE_H__
+
+
diff --git a/Code/Gfx/XBox/p_nxnewparticlemgr.cpp b/Code/Gfx/XBox/p_nxnewparticlemgr.cpp
new file mode 100644
index 0000000..84f236a
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxnewparticlemgr.cpp
@@ -0,0 +1,85 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_NxNewParticleMgr.cpp									**
+**																			**
+**	Created by:		3/25/03	-	SPG											**
+**																			**
+**	Description:	Xbox-specific parametric particle system manager		**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Nx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+CNewParticle*	CXboxNewParticleManager::plat_create_particle( void )
+{
+	return new CXboxNewParticle;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxnewparticlemgr.h b/Code/Gfx/XBox/p_nxnewparticlemgr.h
new file mode 100644
index 0000000..e4f860d
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxnewparticlemgr.h
@@ -0,0 +1,77 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5 													**
+**																			**
+**	Module:			Gfx			 											**
+**																			**
+**	File name:		p_nxnewparticlemgr.h									**
+**																			**
+**	Created by:		3/24/03	-	SPG											**
+**																			**
+**	Description:	Xbox-specific new parametric particle system manager	**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_XBOX_P_NXNEWPARTICLEMGR_H__
+#define __GFX_XBOX_P_NXNEWPARTICLEMGR_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Nx
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CXboxNewParticleManager : public CNewParticleManager
+{
+protected:
+	virtual	CNewParticle*	plat_create_particle( void );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Nx
+
+#endif	// __GFX_XBOX_P_NXNEWPARTICLEMGR_H__
+
+
diff --git a/Code/Gfx/XBox/p_nxparticle.cpp b/Code/Gfx/XBox/p_nxparticle.cpp
new file mode 100644
index 0000000..646eec2
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticle.cpp
@@ -0,0 +1,120 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/xbox/nx/nx_init.h"
+#include "gfx/xbox/p_nxparticle.h"
+#include "gfx/xbox/p_nxparticleline.h"
+#include "gfx/xbox/p_nxparticleflat.h"
+#include "gfx/xbox/p_nxparticleshaded.h"
+#include "gfx/xbox/p_nxparticlesmooth.h"
+#include "gfx/xbox/p_nxparticleglow.h"
+#include "gfx/xbox/p_nxparticlestar.h"
+#include "gfx/xbox/p_nxparticlesmoothstar.h"
+#include "gfx/xbox/p_nxparticleribbon.h"
+#include "gfx/xbox/p_nxparticlesmoothribbon.h"
+#include "gfx/xbox/p_nxparticleribbontrail.h"
+#include "gfx/xbox/p_nxparticleglowribbontrail.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CParticle *plat_create_particle( uint32 checksum, uint32 type_checksum, int max_particles, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	switch( type_checksum )
+	{
+		case 0x2eeb4b09:	// Line
+		{
+			CXboxParticleLine *p_particle = new CXboxParticleLine( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split );
+			return static_cast( p_particle );
+		}
+
+		case 0xaab555bb:	// Flat
+		{
+			CXboxParticleFlat *p_particle = new CXboxParticleFlat( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split );
+			return static_cast( p_particle );
+		}
+
+		case 0xf4d8d486:	// Shaded
+		{
+			CXboxParticleShaded *p_particle = new CXboxParticleShaded( checksum, max_particles, texture_checksum, blendmode_checksum, fix );
+			return static_cast( p_particle );
+		}
+		
+		case 0x8addac1f:	// Smooth
+		{
+			CXboxParticleSmooth *p_particle = new CXboxParticleSmooth( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split );
+			return static_cast( p_particle );
+		}
+		
+		case 0x15834eea:	// Glow
+		{
+			CXboxParticleGlow *p_particle = new CXboxParticleGlow( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split );
+			return static_cast( p_particle );
+		}
+
+		case 0x3624a5eb:	// Star
+		{
+			CXboxParticleStar *p_particle = new CXboxParticleStar( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split );
+			return static_cast( p_particle );
+		}
+		
+		case 0x97cb7a9:		// SmoothStar
+		{
+			CXboxParticleSmoothStar *p_particle = new CXboxParticleSmoothStar( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split );
+			return static_cast( p_particle );
+		}
+
+		case 0xee6fc5b:		// Ribbon
+		{
+			CXboxParticleRibbon *p_particle = new CXboxParticleRibbon( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+			return static_cast( p_particle );
+		}
+
+		case 0x3f109fcc:	// SmoothRibbon
+		{
+			CXboxParticleSmoothRibbon *p_particle = new CXboxParticleSmoothRibbon( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+			return static_cast( p_particle );
+		}
+
+		case 0xc4d5a4cb:	// RibbonTrail
+		{
+			CXboxParticleRibbonTrail *p_particle = new CXboxParticleRibbonTrail( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+			return static_cast( p_particle );
+		}
+
+		case 0x7ec7252d:	// GlowRibbonTrail
+		{
+			CXboxParticleGlowRibbonTrail *p_particle = new CXboxParticleGlowRibbonTrail( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+			return static_cast( p_particle );
+		}
+
+		case 0xdedfc057:	// NewFlat
+		{
+			// Just default to old flat for now.
+			CXboxParticleFlat *p_particle = new CXboxParticleFlat( checksum, max_particles, texture_checksum, blendmode_checksum, fix, num_segments, split );
+			return static_cast( p_particle );
+		}
+
+		default:
+		{
+			Dbg_MsgAssert( 0, ( "Unsupported particle type" ));
+			break;
+		}
+	}
+	return NULL;
+}
+
+} // Nx
+
+				
+
+
diff --git a/Code/Gfx/XBox/p_nxparticle.h b/Code/Gfx/XBox/p_nxparticle.h
new file mode 100644
index 0000000..a68b7ce
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticle.h
@@ -0,0 +1,27 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticle.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLE_H__
+#define	__GFX_P_NX_PARTICLE_H__
+    
+#include "gfx/nxparticle.h"
+#include "gfx/xbox/nx/particles.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+#endif 
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleflat.cpp b/Code/Gfx/XBox/p_nxparticleflat.cpp
new file mode 100644
index 0000000..d0aad2a
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleflat.cpp
@@ -0,0 +1,522 @@
+#include 
+#include 
+#include "gfx/xbox/p_nxparticleflat.h"
+
+extern DWORD PixelShader0;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleFlat::CXboxParticleFlat()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleFlat::CXboxParticleFlat( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split )
+{
+	m_checksum		= checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+	
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_FLAT, texture_checksum, blendmode_checksum, fix );
+
+	// Default color.
+	m_start_color.r = m_start_color.g = m_start_color.b = 128;
+	m_start_color.a = 255;
+	m_mid_color.r = m_mid_color.g = m_mid_color.b = 128;
+	m_mid_color.a = 255;
+	m_end_color.r = m_end_color.g = m_end_color.b = 128;
+	m_end_color.a = 255;
+
+	m_mid_time = -1.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleFlat::~CXboxParticleFlat()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleFlat::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleFlat::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleFlat::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleFlat::plat_get_num_particle_colors( void ) { return 1; }
+int CXboxParticleFlat::plat_get_num_vertex_lists( void ) { return 1; }
+
+// Note these are r/b reversed for direct uploading to Xbox GPU.
+void CXboxParticleFlat::plat_set_sr( int entry, uint8 value ) { m_start_color.b = value; }
+void CXboxParticleFlat::plat_set_sg( int entry, uint8 value ) { m_start_color.g = value; }
+void CXboxParticleFlat::plat_set_sb( int entry, uint8 value ) { m_start_color.r = value; }
+void CXboxParticleFlat::plat_set_sa( int entry, uint8 value ) { m_start_color.a = value; }
+void CXboxParticleFlat::plat_set_mr( int entry, uint8 value ) { m_mid_color.b = value; }
+void CXboxParticleFlat::plat_set_mg( int entry, uint8 value ) { m_mid_color.g = value; }
+void CXboxParticleFlat::plat_set_mb( int entry, uint8 value ) { m_mid_color.r = value; }
+void CXboxParticleFlat::plat_set_ma( int entry, uint8 value ) { m_mid_color.a = value; }
+void CXboxParticleFlat::plat_set_er( int entry, uint8 value ) { m_end_color.b = value; }
+void CXboxParticleFlat::plat_set_eg( int entry, uint8 value ) { m_end_color.g = value; }
+void CXboxParticleFlat::plat_set_eb( int entry, uint8 value ) { m_end_color.r = value; }
+void CXboxParticleFlat::plat_set_ea( int entry, uint8 value ) { m_end_color.a = value; }
+
+
+		
+
+
+
+#if 1
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleFlat::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+
+		// Calculate space needed.
+		DWORD dwords_per_particle	= 32;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( ParticleFlatVS );
+		NxXbox::set_pixel_shader( PixelShader0 );
+		
+		// Load up the combined world->view_projection matrix.
+		XGMATRIX	temp_matrix;
+		XGMATRIX	dest_matrix;
+		XGMATRIX	projMatrix;
+		XGMATRIX	viewMatrix;
+		XGMATRIX	worldMatrix;
+		
+		// Projection matrix.
+		XGMatrixTranspose( &projMatrix, &NxXbox::EngineGlobals.projection_matrix );
+	
+		// View matrix.
+		XGMatrixTranspose( &viewMatrix, &NxXbox::EngineGlobals.view_matrix );
+		viewMatrix.m[3][0] = 0.0f;
+		viewMatrix.m[3][1] = 0.0f;
+		viewMatrix.m[3][2] = 0.0f;
+		viewMatrix.m[3][3] = 1.0f;
+
+		// World space transformation matrix, set to be a translation matrix corresponding to the emitter position.
+		XGMatrixTranslation( &worldMatrix, m_pos[0], m_pos[1], m_pos[2] );
+		XGMatrixTranspose( &worldMatrix, &worldMatrix );
+
+		// Calculate composite world->view->projection matrix.
+		XGMatrixMultiply( &temp_matrix, &viewMatrix, &worldMatrix );
+		XGMatrixMultiply( &dest_matrix, &projMatrix, &temp_matrix );
+
+		// Load up the combined world, camera & projection matrix.
+		D3DDevice_SetVertexShaderConstantFast( 0, (void*)&dest_matrix, 4 );
+
+		float vector_upload[8];
+		vector_upload[0]	= screen_right[X];
+		vector_upload[1]	= screen_right[Y];
+		vector_upload[2]	= screen_right[Z];
+		vector_upload[4]	= screen_up[X];
+		vector_upload[5]	= screen_up[Y];
+		vector_upload[6]	= screen_up[Z];
+		D3DDevice_SetVertexShaderConstantFast( 4, (void*)( &vector_upload[0] ), 2 );
+
+		static float vconstants[32]	= {	0.0f,  0.0f, 1.0f, 1.0f,		// Vert tex coords in C8 through C11
+										1.0f,  0.0f, 1.0f, 1.0f,
+										1.0f,  1.0f, 1.0f, 1.0f,
+										0.0f,  1.0f, 1.0f, 1.0f,
+									   -1.0f,  1.0f, 1.0f, 1.0f,		// Vert w/h multipliers in C12 through C15
+										1.0f,  1.0f, 1.0f, 1.0f,
+										1.0f, -1.0f, 1.0f, 1.0f,
+									   -1.0f, -1.0f, 1.0f, 1.0f };
+		D3DDevice_SetVertexShaderConstantFast( 8, (void*)( &vconstants[0] ), 8 );
+
+		// Obtain push buffer lock.
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		DWORD *p_push; 
+		p_push = D3DDevice_BeginPush( dword_count );
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices;
+
+		for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+		{
+			// Calculate the interpolator ( 1.0f / particle_life ).
+			float terp	= p_particle->m_time * ReciprocalEstimateNR_ASM( p_particle->m_life );
+
+			// Separate interpolator for color.
+			float col_terp;
+
+			Mth::Vector	pos( p_v[0], p_v[1], p_v[2] );
+			Image::RGBA *p_col0;
+			Image::RGBA *p_col1;
+		
+			if( m_mid_time >= 0.0f )
+			{
+				if( terp < m_mid_time )
+				{
+					p_col0		= &m_start_color;
+					p_col1		= &m_mid_color;
+
+					// Adjust interpolation for this half of the color blend.
+					col_terp	= terp / m_mid_time;
+				}
+				else
+				{
+					p_col0		= &m_mid_color;
+					p_col1		= &m_end_color;
+
+					// Adjust interpolation for this half of the color blend.
+					col_terp	= ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+				}
+			}
+			else
+			{
+				// No mid color specified.
+				p_col0		= &m_start_color;
+				p_col1		= &m_end_color;
+
+				// Color interpoltor value is the same as the regular interpolator.
+				col_terp	= terp;
+			}
+
+			// We're going to be loading constants.
+			p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT_LOAD, 1 );
+
+			// Specify the starting register (physical registers are offset by 96 from the D3D logical register).
+			p_push[1]	= 96 + 16;
+
+			// Specify the number of DWORDS to load. 12 DWORDS for 3 constants.
+			p_push[2]	= D3DPUSH_ENCODE( D3DPUSH_SET_TRANSFORM_CONSTANT, 12 );
+
+			// Load position.
+			p_push[3]	= *((DWORD*)&pos[X] );
+			p_push[4]	= *((DWORD*)&pos[Y] );
+			p_push[5]	= *((DWORD*)&pos[Z] );
+
+			// Load start and end width and height.
+			p_push[7]	= *((DWORD*)&p_particle->m_sw );
+			p_push[8]	= *((DWORD*)&p_particle->m_sh );
+			p_push[9]	= *((DWORD*)&p_particle->m_ew );
+			p_push[10]	= *((DWORD*)&p_particle->m_eh );
+
+			// Load size and color interpolators.
+			p_push[11]	= *((DWORD*)&terp );
+			p_push[12]	= *((DWORD*)&col_terp );
+
+			p_push		+= 15;
+
+			p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+			p_push[1]	= D3DPT_QUADLIST;
+			p_push		+= 2;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, 12 );
+			++p_push;
+
+			// Now we can start the actual vertex data.
+			p_push[0]	= *((DWORD*)p_col0 );
+			p_push[1]	= *((DWORD*)p_col1 );
+			p_push[2]	= 0x00000000UL;
+
+			p_push[3]	= *((DWORD*)p_col0 );
+			p_push[4]	= *((DWORD*)p_col1 );
+			p_push[5]	= 0x00010001UL;
+
+			p_push[6]	= *((DWORD*)p_col0 );
+			p_push[7]	= *((DWORD*)p_col1 );
+			p_push[8]	= 0x00020002UL;
+			
+			p_push[9]	= *((DWORD*)p_col0 );
+			p_push[10]	= *((DWORD*)p_col1 );
+			p_push[11]	= 0x00030003UL;
+
+			p_push		+= 12;
+
+			// End of vertex data for this particle.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+			p_push[1] = 0;
+			p_push += 2;
+		}
+		D3DDevice_EndPush( p_push );
+	}
+
+	// Deal with the Ps2 specific extensions.
+	if( m_emit_rate > 0.0f )
+	{
+		m_emit_rate_fractional += ( m_emit_rate * ( 1.0f / 60.0f ));
+
+		if( m_emit_rate_fractional >= 1.0f )
+		{
+			// This should actually deal with fractional values by accumulating them.
+			emit( Ftoi_ASM( m_emit_rate_fractional ));
+			m_emit_rate_fractional -= (float)Ftoi_ASM( m_emit_rate_fractional );
+		}
+	}
+}
+
+#else
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleFlat::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+		NxXbox::set_pixel_shader( PixelShader0 );
+		
+		DWORD dwords_per_particle	= 24;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Obtain push buffer lock.
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		DWORD *p_push; 
+		p_push = D3DDevice_BeginPush( dword_count + ( dword_count / 2047 ) + 16 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_QUADLIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices;
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+				
+				// Calculate the interpolator ( 1.0f / particle_life ).
+				float terp	= p_particle->m_time * ReciprocalEstimateNR_ASM( p_particle->m_life );
+				float w		= p_particle->m_sw + (( p_particle->m_ew - p_particle->m_sw ) * terp );
+				float h		= p_particle->m_sh + (( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+				// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+				Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				Mth::Vector	ss_right, ss_up;
+				Mth::Vector tmp;
+		
+				ss_right	= screen_right * w;
+				ss_up		= screen_up * h;
+
+				Image::RGBA color;
+				Image::RGBA *p_col0;
+				Image::RGBA *p_col1;
+		
+				if( m_mid_time >= 0.0f )
+				{
+					if( terp < m_mid_time )
+					{
+						p_col0 = &m_start_color;
+						p_col1 = &m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = &m_mid_color;
+						p_col1 = &m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = &m_start_color;
+					p_col1 = &m_end_color;
+				}
+
+				Image::RGBA start	= *p_col0++;
+				Image::RGBA end		= *p_col1++;
+
+				// Use fixed point math to avoid _ftol2 calls.
+				int f_terp	= Ftoi_ASM( terp * 4096.0f );
+				color.r		= ((((int)start.r ) * 4096 ) + (((int)end.r - (int)start.r ) * f_terp )) / 4096;
+				color.g		= ((((int)start.g ) * 4096 ) + (((int)end.g - (int)start.g ) * f_terp )) / 4096;
+				color.b		= ((((int)start.b ) * 4096 ) + (((int)end.b - (int)start.b ) * f_terp )) / 4096;
+				color.a		= ((((int)start.a ) * 4096 ) + (((int)end.a - (int)start.a ) * f_terp )) / 4096;
+		
+				tmp			= pos - ss_right + ss_up;
+				p_push[0]	= *((DWORD*)&tmp[X] );
+				p_push[1]	= *((DWORD*)&tmp[Y] );
+				p_push[2]	= *((DWORD*)&tmp[Z] );
+				p_push[3]	= *((DWORD*)&color );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+	
+				tmp			= pos + ss_right + ss_up;		
+				p_push[6]	= *((DWORD*)&tmp[X] );
+				p_push[7]	= *((DWORD*)&tmp[Y] );
+				p_push[8]	= *((DWORD*)&tmp[Z] );
+				p_push[9]	= *((DWORD*)&color );
+				p_push[10]	= 0x3F800000UL;
+				p_push[11]	= 0x00000000UL;
+
+				tmp			= pos + ss_right - ss_up;		
+				p_push[12]	= *((DWORD*)&tmp[X] );
+				p_push[13]	= *((DWORD*)&tmp[Y] );
+				p_push[14]	= *((DWORD*)&tmp[Z] );
+				p_push[15]	= *((DWORD*)&color );
+				p_push[16]	= 0x3F800000UL;
+				p_push[17]	= 0x3F800000UL;
+			
+				tmp			= pos - ss_right - ss_up;		
+				p_push[18]	= *((DWORD*)&tmp[X] );
+				p_push[19]	= *((DWORD*)&tmp[Y] );
+				p_push[20]	= *((DWORD*)&tmp[Z] );
+				p_push[21]	= *((DWORD*)&color );
+				p_push[22]	= 0x00000000UL;
+				p_push[23]	= 0x3F800000UL;
+
+				p_push		+= 24;
+
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+	}
+}
+#endif
+
+
+
+} // Nx
+
diff --git a/Code/Gfx/XBox/p_nxparticleflat.h b/Code/Gfx/XBox/p_nxparticleflat.h
new file mode 100644
index 0000000..d518855
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleflat.h
@@ -0,0 +1,66 @@
+#ifndef	__GFX_P_NX_PARTICLEFLAT_H__
+#define	__GFX_P_NX_PARTICLEFLAT_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxParticleFlat : public CParticle
+{
+	public:
+							CXboxParticleFlat();
+							CXboxParticleFlat( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split );
+	virtual 				~CXboxParticleFlat();
+
+//	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	float*						mp_vertices;
+	uint32*						mp_colors;
+	uint64						m_blend;
+	NxXbox::sParticleSystem*	mp_engine_particle;
+	float						m_emit_rate_fractional;		// Deals with the fractional part of m_emit_rate for the pseudo parametric systems.
+
+	Image::RGBA					m_start_color;				// Start color for each corner.
+	Image::RGBA					m_mid_color;				// Mid color for each corner.
+	Image::RGBA					m_end_color;				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleglow.cpp b/Code/Gfx/XBox/p_nxparticleglow.cpp
new file mode 100644
index 0000000..4324643
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleglow.cpp
@@ -0,0 +1,375 @@
+#include 
+#include "gfx/xbox/nx/render.h"
+
+#include "gfx/xbox/p_nxparticleGlow.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleGlow::CXboxParticleGlow()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleGlow::CXboxParticleGlow( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split )
+{
+	m_checksum		= checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+	m_num_segments	= num_segments;
+	m_split			= split;
+	m_mid_time		= -1.0f;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_GLOW, texture_checksum, blendmode_checksum, fix, num_segments );
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 255;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleGlow::~CXboxParticleGlow()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlow::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlow::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlow::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleGlow::plat_get_num_particle_colors( void ) { return 3; }
+int CXboxParticleGlow::plat_get_num_vertex_lists( void ) { return 1; }
+void CXboxParticleGlow::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleGlow::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleGlow::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleGlow::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleGlow::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleGlow::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleGlow::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleGlow::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleGlow::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleGlow::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleGlow::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleGlow::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlow::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+	
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 36 * m_num_segments;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + ( dword_count / 2047 ) + 16 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_TRIANGLELIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices;
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp	= p_particle->m_time / p_particle->m_life;
+				float w		= p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+				float h		= p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+				Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				Mth::Vector	ss_right, ss_up;
+				Mth::Vector tmp0, tmp1, tmp2, tmp3;
+	
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos;
+//					max = pos;
+//				}
+//				else
+//				{
+//					if( pos[X] < min[X] ) min[X] = pos[X]; else if( pos[X] > max[X] ) max[X] = pos[X];
+//					if( pos[Y] < min[Y] ) min[Y] = pos[Y]; else if( pos[Y] > max[Y] ) max[Y] = pos[Y];
+//					if( pos[Z] < min[Z] ) min[Z] = pos[Z]; else if( pos[Z] > max[Z] ) max[Z] = pos[Z];
+//				}
+
+				ss_right	= screen_right * w;
+				ss_up		= screen_up * h;
+	
+				Image::RGBA color[3];
+				Image::RGBA *p_col0;
+				Image::RGBA *p_col1;
+
+				if ( m_mid_time >= 0.0f )
+				{
+					if ( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+
+				// Swap red and blue here.
+				for( int c = 0; c < 3; c++ )
+				{
+					Image::RGBA start	= *p_col0++;
+					Image::RGBA end		= *p_col1++;
+
+					color[c].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[c].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+				}
+
+				tmp0  = pos;
+				tmp0 += ss_right * sinf( 0.0f ) * m_split;
+				tmp0 += ss_up    * cosf( 0.0f ) * m_split;
+
+				tmp2  = pos;
+				tmp2 += ss_right * sinf( 0.0f );
+				tmp2 += ss_up    * cosf( 0.0f );
+
+				float radians_per_segment	= ( 2.0f * Mth::PI ) / (float)m_num_segments;
+
+				for( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+				{
+					tmp1  = pos;
+					tmp1 += ss_right * sinf( radians_per_segment * ( lp2 + 1 )) * m_split;
+					tmp1 += ss_up    * cosf( radians_per_segment * ( lp2 + 1 )) * m_split;
+
+					tmp3  = pos;
+					tmp3 += ss_right * sinf( radians_per_segment * ( lp2 + 1 ));
+					tmp3 += ss_up    * cosf( radians_per_segment * ( lp2 + 1 ));
+
+					// Triangle zero.
+					p_push[0]	= *((DWORD*)&pos[X] );
+					p_push[1]	= *((DWORD*)&pos[Y] );
+					p_push[2]	= *((DWORD*)&pos[Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+
+					p_push[4]	= *((DWORD*)&tmp0[X] );
+					p_push[5]	= *((DWORD*)&tmp0[Y] );
+					p_push[6]	= *((DWORD*)&tmp0[Z] );
+					p_push[7]	= *((DWORD*)&color[1] );
+					
+					p_push[8]	= *((DWORD*)&tmp1[X] );
+					p_push[9]	= *((DWORD*)&tmp1[Y] );
+					p_push[10]	= *((DWORD*)&tmp1[Z] );
+					p_push[11]	= *((DWORD*)&color[1] );
+				
+					// Triangle one.
+					p_push[12]	= *((DWORD*)&tmp0[X] );
+					p_push[13]	= *((DWORD*)&tmp0[Y] );
+					p_push[14]	= *((DWORD*)&tmp0[Z] );
+					p_push[15]	= *((DWORD*)&color[1] );
+					
+					p_push[16]	= *((DWORD*)&tmp1[X] );
+					p_push[17]	= *((DWORD*)&tmp1[Y] );
+					p_push[18]	= *((DWORD*)&tmp1[Z] );
+					p_push[19]	= *((DWORD*)&color[1] );
+
+					p_push[20]	= *((DWORD*)&tmp2[X] );
+					p_push[21]	= *((DWORD*)&tmp2[Y] );
+					p_push[22]	= *((DWORD*)&tmp2[Z] );
+					p_push[23]	= *((DWORD*)&color[2] );
+					
+					// Triangle two.
+					p_push[24]	= *((DWORD*)&tmp1[X] );
+					p_push[25]	= *((DWORD*)&tmp1[Y] );
+					p_push[26]	= *((DWORD*)&tmp1[Z] );
+					p_push[27]	= *((DWORD*)&color[1] );
+					
+					p_push[28]	= *((DWORD*)&tmp2[X] );
+					p_push[29]	= *((DWORD*)&tmp2[Y] );
+					p_push[30]	= *((DWORD*)&tmp2[Z] );
+					p_push[31]	= *((DWORD*)&color[2] );
+
+					p_push[32]	= *((DWORD*)&tmp3[X] );
+					p_push[33]	= *((DWORD*)&tmp3[Y] );
+					p_push[34]	= *((DWORD*)&tmp3[Z] );
+					p_push[35]	= *((DWORD*)&color[2] );
+					p_push		+= 36;
+					
+					tmp0 = tmp1;
+					tmp2 = tmp3;
+				}
+
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleglow.h b/Code/Gfx/XBox/p_nxparticleglow.h
new file mode 100644
index 0000000..c87491b
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleglow.h
@@ -0,0 +1,74 @@
+#ifndef	__GFX_P_NX_PARTICLEGlow_H__
+#define	__GFX_P_NX_PARTICLEGlow_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CXboxParticleGlow : public CParticle
+{
+public:
+							CXboxParticleGlow();
+							CXboxParticleGlow( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split );
+	virtual 				~CXboxParticleGlow();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+
+	NxXbox::sParticleSystem*	mp_engine_particle;
+	int						m_num_segments;
+	float					m_split;
+	Image::RGBA				m_start_color[3];			// Start color for each corner.
+	Image::RGBA				m_mid_color[3];				// Mid color for each corner.
+	Image::RGBA				m_end_color[3];				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleglowribbontrail.cpp b/Code/Gfx/XBox/p_nxparticleglowribbontrail.cpp
new file mode 100644
index 0000000..d338fef
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleglowribbontrail.cpp
@@ -0,0 +1,492 @@
+#include 
+#include "gfx/xbox/nx/render.h"
+
+#include "gfx/xbox/p_nxparticleGlowRibbonTrail.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CXboxParticleGlowRibbonTrail::CXboxParticleGlowRibbonTrail()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleGlowRibbonTrail::CXboxParticleGlowRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum		= checksum;
+	m_max_particles	= max_particles;
+	m_num_particles	= 0;
+	m_mid_time		= -1.0f;
+	m_num_segments	= num_segments;
+	m_segment_step	= 360.0f / m_num_segments;
+	m_history		= history;
+	m_split			= split;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_GLOWRIBBONTRAIL, texture_checksum, blendmode_checksum, fix, num_segments, history );
+
+	// Default color.
+	m_start_color = new Image::RGBA[m_num_vertex_buffers + 3];
+	m_mid_color = new Image::RGBA[m_num_vertex_buffers + 3];
+	m_end_color = new Image::RGBA[m_num_vertex_buffers + 3];
+	for ( int lp = 0; lp < ( m_num_vertex_buffers + 3 ); lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 128;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 128;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 128;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleGlowRibbonTrail::~CXboxParticleGlowRibbonTrail()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+	delete [] m_start_color;
+	delete [] m_mid_color;
+	delete [] m_end_color;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlowRibbonTrail::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlowRibbonTrail::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlowRibbonTrail::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleGlowRibbonTrail::plat_get_num_particle_colors( void ) { return m_num_vertex_buffers + 3; }
+int CXboxParticleGlowRibbonTrail::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CXboxParticleGlowRibbonTrail::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value >> 1; }
+void CXboxParticleGlowRibbonTrail::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value >> 1; }
+void CXboxParticleGlowRibbonTrail::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleGlowRibbonTrail::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value >> 1; }
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleGlowRibbonTrail::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v0, *p_v1;
+
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		Image::RGBA color[3];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+		
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= ( 36 * m_num_segments ) + ( 24 * ( m_num_vertex_buffers - 1 ));
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_TRIANGLELIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v0		= mp_vertices[0];
+		p_v1		= mp_vertices[(m_num_vertex_buffers - 1)];
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+				
+				// Calculate the interpolator ( 1.0f / particle_life ).
+				float terp = p_particle->m_time * ReciprocalEstimateNR_ASM( p_particle->m_life );
+
+				Mth::Vector	pos[2];
+				p_v0 = &mp_vertices[0][lp*3];
+				pos[0].Set( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+				p_v0 = &mp_vertices[1][lp*3];
+				pos[1].Set( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+			
+				Mth::Vector	part_vec = pos[1] - pos[0];
+				Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+
+				float w = p_particle->m_sw + (( p_particle->m_ew - p_particle->m_sw ) * terp );
+				float h = p_particle->m_sh + (( p_particle->m_eh - p_particle->m_sh ) * terp );
+		
+				Mth::Vector tmp[4];
+
+				if( m_mid_time >= 0.0f )
+				{
+					if( terp < m_mid_time )
+					{
+						p_col0 = &m_start_color[3];
+						p_col1 = &m_mid_color[3];
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = &m_mid_color[3];
+						p_col1 = &m_end_color[3];
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = &m_start_color[3];
+					p_col1 = &m_end_color[3];
+				}
+
+				Image::RGBA start = *p_col0++;
+				Image::RGBA end = *p_col1++;
+
+				// Interpolate and swap red and blue here. Use fixed point math to avoid _ftol2 calls.
+				int f_terp	= Ftoi_ASM( terp * 4096.0f );
+				color[0].b	= ((((int)start.r ) * 4096 ) + (((int)end.r - (int)start.r ) * f_terp )) / 4096;
+				color[0].g	= ((((int)start.g ) * 4096 ) + (((int)end.g - (int)start.g ) * f_terp )) / 4096;
+				color[0].r	= ((((int)start.b ) * 4096 ) + (((int)end.b - (int)start.b ) * f_terp )) / 4096;
+				color[0].a	= ((((int)start.a ) * 4096 ) + (((int)end.a - (int)start.a ) * f_terp )) / 4096;
+
+				tmp[0]		= pos[0] + ( perp_vec * w * m_split );
+				tmp[1]		= pos[0] - ( perp_vec * w * m_split );
+
+				for( int c = 1; c < m_num_vertex_buffers; c++ )
+				{
+					start = *p_col0++;
+					end = *p_col1++;
+
+					// Interpolate and swap red and blue here. Use fixed point math to avoid _ftol2 calls.
+					color[1].b	= ((((int)start.r ) * 4096 ) + (((int)end.r - (int)start.r ) * f_terp )) / 4096;
+					color[1].g	= ((((int)start.g ) * 4096 ) + (((int)end.g - (int)start.g ) * f_terp )) / 4096;
+					color[1].r	= ((((int)start.b ) * 4096 ) + (((int)end.b - (int)start.b ) * f_terp )) / 4096;
+					color[1].a	= ((((int)start.a ) * 4096 ) + (((int)end.a - (int)start.a ) * f_terp )) / 4096;
+
+					if ( c > 1 )
+					{
+						p_v0 = &mp_vertices[c][lp*3];
+						pos[1].Set( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+						part_vec = pos[1] - pos[0];
+						perp_vec = Mth::CrossProduct( part_vec, at );
+						perp_vec.Normalize();
+					}
+
+					tmp[2]		= pos[1] + ( perp_vec * w * m_split );
+					tmp[3]		= pos[1] - ( perp_vec * w * m_split );
+
+					// First tri.
+					p_push[0]	= *((DWORD*)&tmp[0][X] );
+					p_push[1]	= *((DWORD*)&tmp[0][Y] );
+					p_push[2]	= *((DWORD*)&tmp[0][Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+						
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+					
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					// Second tri.
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+						
+					p_push[0]	= *((DWORD*)&tmp[3][X] );
+					p_push[1]	= *((DWORD*)&tmp[3][Y] );
+					p_push[2]	= *((DWORD*)&tmp[3][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+						
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					color[0] = color[1];
+					pos[0] = pos[1];
+					tmp[0] = tmp[2];
+					tmp[1] = tmp[3];
+				}
+
+				// Now draw the glow.
+				p_v0 = &mp_vertices[0][lp*3];
+				pos[0].Set( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+				Mth::Vector	ss_right, ss_up;
+	
+				ss_right	= screen_right * w;
+				ss_up		= screen_up * h;
+
+				if( m_mid_time >= 0.0f )
+				{
+					if( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+	
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+		
+				// Swap red and blue here.
+				for( int c = 0; c < 3; c++ )
+				{
+					Image::RGBA start = *p_col0++;
+					Image::RGBA end = *p_col1++;
+
+					// Interpolate and swap red and blue here. Use fixed point math to avoid _ftol2 calls.
+					color[c].b	= ((((int)start.r ) * 4096 ) + (((int)end.r - (int)start.r ) * f_terp )) / 4096;
+					color[c].g	= ((((int)start.g ) * 4096 ) + (((int)end.g - (int)start.g ) * f_terp )) / 4096;
+					color[c].r	= ((((int)start.b ) * 4096 ) + (((int)end.b - (int)start.b ) * f_terp )) / 4096;
+					color[c].a	= ((((int)start.a ) * 4096 ) + (((int)end.a - (int)start.a ) * f_terp )) / 4096;
+				}
+
+				// We know that sin( 0 ) = 0, and cos( 0 ) = 1, so we can optimise the first iteration.
+				tmp[0]  = pos[0];
+				tmp[0] += ss_right * 0.0f * m_split;
+				tmp[0] += ss_up    * 1.0f * m_split;
+
+				tmp[2]  = pos[0];
+				tmp[2] += ss_right * 0.0f;
+				tmp[2] += ss_up    * 1.0f;
+
+				for ( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+				{
+					float rt	= sinf( Mth::DegToRad( m_segment_step * ( lp2 + 1 )));
+					float up	= cosf( Mth::DegToRad( m_segment_step * ( lp2 + 1 )));
+
+					tmp[1]  = pos[0];
+					tmp[1] += ss_right * rt * m_split;
+					tmp[1] += ss_up    * up * m_split;
+
+					tmp[3]  = pos[0];
+					tmp[3] += ss_right * rt;
+					tmp[3] += ss_up    * up;
+
+					// First tri.
+					p_push[0]	= *((DWORD*)&pos[0][X] );
+					p_push[1]	= *((DWORD*)&pos[0][Y] );
+					p_push[2]	= *((DWORD*)&pos[0][Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+					
+					p_push[0]	= *((DWORD*)&tmp[0][X] );
+					p_push[1]	= *((DWORD*)&tmp[0][Y] );
+					p_push[2]	= *((DWORD*)&tmp[0][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+					
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					// Second tri.					
+					p_push[0]	= *((DWORD*)&tmp[0][X] );
+					p_push[1]	= *((DWORD*)&tmp[0][Y] );
+					p_push[2]	= *((DWORD*)&tmp[0][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					// Third tri.
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[3][X] );
+					p_push[1]	= *((DWORD*)&tmp[3][Y] );
+					p_push[2]	= *((DWORD*)&tmp[3][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					tmp[0] = tmp[1];
+					tmp[2] = tmp[3];
+				}
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleglowribbontrail.h b/Code/Gfx/XBox/p_nxparticleglowribbontrail.h
new file mode 100644
index 0000000..5379b07
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleglowribbontrail.h
@@ -0,0 +1,71 @@
+#ifndef	__GFX_P_NX_PARTICLEGlowRibbonTrail_H__
+#define	__GFX_P_NX_PARTICLEGlowRibbonTrail_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxParticleGlowRibbonTrail : public CParticle
+{
+public:
+							CXboxParticleGlowRibbonTrail();
+							CXboxParticleGlowRibbonTrail( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CXboxParticleGlowRibbonTrail();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+private:					// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+	NxXbox::sParticleSystem*	mp_engine_particle;
+
+	int						m_num_segments;
+	float					m_segment_step;				// 360.0f / m_num_segments;
+	int						m_history;
+	float					m_split;
+	Image::RGBA*			m_start_color;				// Start color for each corner.
+	Image::RGBA*			m_mid_color;				// Mid color for each corner.
+	Image::RGBA*			m_end_color;				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleline.cpp b/Code/Gfx/XBox/p_nxparticleline.cpp
new file mode 100644
index 0000000..ccab799
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleline.cpp
@@ -0,0 +1,294 @@
+#include 
+#include "gfx/xbox/nx/render.h"
+
+#include "gfx/xbox/p_nxparticleLine.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleLine::CXboxParticleLine()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleLine::CXboxParticleLine( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split )
+{
+	m_checksum		= checksum;
+	m_max_particles	= max_particles;
+	m_num_particles	= 0;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices[0] = new float[max_particles * 3];
+	mp_vertices[1] = new float[max_particles * 3];		// 2nd buffer to keep history.
+
+	// Create the engine representation.
+	mp_engine_particle	= new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_LINE, texture_checksum, blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 2; lp++ )
+	{
+		m_start_color[lp].r = m_start_color[lp].g = m_start_color[lp].b = 128;
+		m_start_color[lp].a = 255;
+		m_mid_color[lp].r = m_mid_color[lp].g = m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = m_end_color[lp].g = m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleLine::~CXboxParticleLine()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices[0];
+	delete [] mp_vertices[1];
+
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleLine::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleLine::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleLine::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleLine::plat_get_num_particle_colors( void ) { return 2; }
+int CXboxParticleLine::plat_get_num_vertex_lists( void ) { return 2; }
+void CXboxParticleLine::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleLine::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleLine::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleLine::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleLine::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleLine::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleLine::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleLine::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleLine::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleLine::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleLine::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleLine::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleLine::plat_render( void )
+{
+	if( m_num_particles > 0 )
+	{
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v0, *p_v1;
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+	
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 8;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_LINELIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v0		= mp_vertices[0];
+		p_v1		= mp_vertices[1];
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp = p_particle->m_time / p_particle->m_life;
+
+				// Todo: Move hook to matrix/emitter code to cut down on per particle calculation.
+				Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+				Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+	
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos0;
+//					max = pos0;
+//				}
+//				else
+//				{
+//					if( pos0[X] < min[X] ) min[X] = pos0[X]; else if( pos0[X] > max[X] ) max[X] = pos0[X];
+//					if( pos0[Y] < min[Y] ) min[Y] = pos0[Y]; else if( pos0[Y] > max[Y] ) max[Y] = pos0[Y];
+//					if( pos0[Z] < min[Z] ) min[Z] = pos0[Z]; else if( pos0[Z] > max[Z] ) max[Z] = pos0[Z];
+//				}
+
+				Image::RGBA color[2];
+				Image::RGBA *p_col0[2];
+				Image::RGBA *p_col1[2];
+		
+				if( m_mid_time >= 0.0f )
+				{
+					if( terp < m_mid_time )
+					{
+						p_col0[0] = &m_start_color[0];
+						p_col1[0] = &m_mid_color[0];
+						p_col0[1] = &m_start_color[1];
+						p_col1[1] = &m_mid_color[1];
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0[0] = &m_mid_color[0];
+						p_col1[0] = &m_end_color[0];
+						p_col0[1] = &m_mid_color[1];
+						p_col1[1] = &m_end_color[1];
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0[0] = &m_start_color[0];
+					p_col1[0] = &m_end_color[0];
+					p_col0[1] = &m_start_color[1];
+					p_col1[1] = &m_end_color[1];
+				}
+
+				// Swap red and blue here.
+				for( int c = 0; c < 2; ++c )
+				{
+					Image::RGBA start	= *( p_col0[c] );
+					Image::RGBA end		= *( p_col1[c] );
+					color[c].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[c].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+				}
+		
+				p_push[0]	= *((DWORD*)&pos0[X] );
+				p_push[1]	= *((DWORD*)&pos0[Y] );
+				p_push[2]	= *((DWORD*)&pos0[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push		+= 4;
+
+				p_push[0]	= *((DWORD*)&pos1[X] );
+				p_push[1]	= *((DWORD*)&pos1[Y] );
+				p_push[2]	= *((DWORD*)&pos1[Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push		+= 4;
+
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleline.h b/Code/Gfx/XBox/p_nxparticleline.h
new file mode 100644
index 0000000..75ca1c9
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleline.h
@@ -0,0 +1,71 @@
+#ifndef	__GFX_P_NX_PARTICLELine_H__
+#define	__GFX_P_NX_PARTICLELine_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CXboxParticleLine : public CParticle
+{
+public:
+								CXboxParticleLine();
+								CXboxParticleLine( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split );
+	virtual						~CXboxParticleLine();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+	private:					// It's all private, as it is machine specific
+	void						plat_render( void );
+	void						plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void						plat_set_position( int entry, int list, float x, float y, float z );
+	void						plat_add_position( int entry, int list, float x, float y, float z );
+	int							plat_get_num_vertex_lists( void );
+	int							plat_get_num_particle_colors( void );
+
+	void						plat_set_sr( int entry, uint8 value );
+	void						plat_set_sg( int entry, uint8 value );
+	void						plat_set_sb( int entry, uint8 value );
+	void						plat_set_sa( int entry, uint8 value );
+	void						plat_set_mr( int entry, uint8 value );
+	void						plat_set_mg( int entry, uint8 value );
+	void						plat_set_mb( int entry, uint8 value );
+	void						plat_set_ma( int entry, uint8 value );
+	void						plat_set_er( int entry, uint8 value );
+	void						plat_set_eg( int entry, uint8 value );
+	void						plat_set_eb( int entry, uint8 value );
+	void						plat_set_ea( int entry, uint8 value );
+		
+	float*						mp_vertices[2];
+	uint32*						mp_colors;
+	uint64						m_blend;
+	NxXbox::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA					m_start_color[2];			// Start color for each corner.
+	Image::RGBA					m_mid_color[2];				// Mid color for each corner.
+	Image::RGBA					m_end_color[2];				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleribbon.cpp b/Code/Gfx/XBox/p_nxparticleribbon.cpp
new file mode 100644
index 0000000..df559e9
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleribbon.cpp
@@ -0,0 +1,315 @@
+#include 
+#include "gfx/xbox/nx/render.h"
+
+#include "gfx/xbox/p_nxparticleRibbon.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleRibbon::CXboxParticleRibbon()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleRibbon::CXboxParticleRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum		= checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+	m_mid_time		= -1.0f;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float*[history + 1];
+	for( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_RIBBON, texture_checksum, blendmode_checksum, fix, num_segments );
+
+	// Default color.
+	for ( int lp = 0; lp < 2; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 255;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleRibbon::~CXboxParticleRibbon()
+{
+	delete [] mp_particle_array;
+	for ( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbon::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbon::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbon::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleRibbon::plat_get_num_particle_colors( void ) { return 2; }
+int CXboxParticleRibbon::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CXboxParticleRibbon::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleRibbon::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleRibbon::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleRibbon::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleRibbon::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleRibbon::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleRibbon::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleRibbon::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleRibbon::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleRibbon::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleRibbon::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleRibbon::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleRibbon::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v0;
+		float			*p_v1;
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+
+		Image::RGBA color[2];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+		
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 16;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_QUADLIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v0		= mp_vertices[0];
+		p_v1		= mp_vertices[(m_num_vertex_buffers - 1)];
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp = p_particle->m_time / p_particle->m_life;
+
+				Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+				Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos0;
+//					max = pos0;
+//				}
+//				else
+//				{
+//					if( pos0[X] < min[X] ) min[X] = pos0[X]; else if( pos0[X] > max[X] ) max[X] = pos0[X];
+//					if( pos0[Y] < min[Y] ) min[Y] = pos0[Y]; else if( pos0[Y] > max[Y] ) max[Y] = pos0[Y];
+//					if( pos0[Z] < min[Z] ) min[Z] = pos0[Z]; else if( pos0[Z] > max[Z] ) max[Z] = pos0[Z];
+//				}
+
+				Mth::Vector	part_vec = pos1 - pos0;
+				Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+
+				float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		
+				Mth::Vector tmp[4];
+				tmp[0]		= pos0 + ( perp_vec * w );
+				tmp[1]		= pos0 - ( perp_vec * w );
+				tmp[2]		= pos1 - ( perp_vec * w );
+				tmp[3]		= pos1 + ( perp_vec * w );
+	
+				if ( m_mid_time >= 0.0f )
+				{
+					if ( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+
+				for ( int c = 0; c < 2; c++ )
+				{
+					Image::RGBA start = *p_col0++;
+					Image::RGBA end = *p_col1++;
+
+					// Swap red and blue here.
+					color[c].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[c].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+				}
+
+				for( int v = 0; v < 4; ++v )
+				{
+					p_push[0]	= *((DWORD*)&tmp[v][X] );
+					p_push[1]	= *((DWORD*)&tmp[v][Y] );
+					p_push[2]	= *((DWORD*)&tmp[v][Z] );
+					p_push[3]	= *((DWORD*)&color[v / 2] );
+					p_push		+= 4;
+				}
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/XBox/p_nxparticleribbon.h b/Code/Gfx/XBox/p_nxparticleribbon.h
new file mode 100644
index 0000000..d332aac
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticleribbon.h
@@ -0,0 +1,68 @@
+#ifndef	__GFX_P_NX_PARTICLERibbon_H__
+#define	__GFX_P_NX_PARTICLERibbon_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxParticleRibbon : public CParticle
+{
+	public:
+							CXboxParticleRibbon();
+							CXboxParticleRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CXboxParticleRibbon();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+
+	private:				// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+	NxXbox::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color[2];			// Start color for each corner.
+	Image::RGBA				m_mid_color[2];				// Mid color for each corner.
+	Image::RGBA				m_end_color[2];				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticlesmooth.cpp b/Code/Gfx/XBox/p_nxparticlesmooth.cpp
new file mode 100644
index 0000000..824e90f
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticlesmooth.cpp
@@ -0,0 +1,405 @@
+#include 
+#include "gfx/xbox/nx/render.h"
+#include 
+#include 
+#include "gfx/xbox/nx/mesh.h"
+#include "gfx/xbox/p_nxparticleSmooth.h"
+
+extern DWORD PixelShader0;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmooth::CXboxParticleSmooth()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmooth::CXboxParticleSmooth( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split )
+{
+	m_checksum		= checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+	m_mid_time		= -1.0f;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_SMOOTH, texture_checksum, blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 2; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 255;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmooth::~CXboxParticleSmooth()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmooth::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmooth::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmooth::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleSmooth::plat_get_num_particle_colors( void ) { return 2; }
+int CXboxParticleSmooth::plat_get_num_vertex_lists( void ) { return 1; }
+void CXboxParticleSmooth::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleSmooth::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleSmooth::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleSmooth::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleSmooth::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleSmooth::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleSmooth::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleSmooth::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleSmooth::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleSmooth::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleSmooth::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleSmooth::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmooth::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+	
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 72;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+		NxXbox::set_pixel_shader( PixelShader0 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_TRIANGLELIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices;
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp	= p_particle->m_time / p_particle->m_life;
+				float w		= p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+				float h		= p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+				Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				Mth::Vector	ss_right, ss_up;	//, ss_pos;
+				Mth::Vector tmp[4];
+	
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos;
+//					max = pos;
+//				}
+//				else
+//				{
+//					if( pos[X] < min[X] ) min[X] = pos[X]; else if( pos[X] > max[X] ) max[X] = pos[X];
+//					if( pos[Y] < min[Y] ) min[Y] = pos[Y]; else if( pos[Y] > max[Y] ) max[Y] = pos[Y];
+//					if( pos[Z] < min[Z] ) min[Z] = pos[Z]; else if( pos[Z] > max[Z] ) max[Z] = pos[Z];
+//				}
+
+				ss_right	= screen_right * w;
+				ss_up		= screen_up * h;
+		
+				tmp[0]		= pos - ss_right + ss_up;
+				tmp[1]		= pos + ss_right + ss_up;		
+				tmp[2]		= pos + ss_right - ss_up;		
+				tmp[3]		= pos - ss_right - ss_up;		
+	
+				Image::RGBA color[2];
+				Image::RGBA *p_col0;
+				Image::RGBA *p_col1;
+		
+				if ( m_mid_time >= 0.0f )
+				{
+					if ( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+
+				for ( int c = 0; c < 2; c++ )
+				{
+					Image::RGBA start = *p_col0++;
+					Image::RGBA end = *p_col1++;
+
+					color[c].r = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[c].b = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+				}
+
+				// First triangle.
+				p_push[0]	= *((DWORD*)&pos[X] );
+				p_push[1]	= *((DWORD*)&pos[Y] );
+				p_push[2]	= *((DWORD*)&pos[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[0][X] );
+				p_push[1]	= *((DWORD*)&tmp[0][Y] );
+				p_push[2]	= *((DWORD*)&tmp[0][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[1][X] );
+				p_push[1]	= *((DWORD*)&tmp[1][Y] );
+				p_push[2]	= *((DWORD*)&tmp[1][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x3F800000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+					
+				// Second triangle.
+				p_push[0]	= *((DWORD*)&pos[X] );
+				p_push[1]	= *((DWORD*)&pos[Y] );
+				p_push[2]	= *((DWORD*)&pos[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[1][X] );
+				p_push[1]	= *((DWORD*)&tmp[1][Y] );
+				p_push[2]	= *((DWORD*)&tmp[1][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x3F800000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[2][X] );
+				p_push[1]	= *((DWORD*)&tmp[2][Y] );
+				p_push[2]	= *((DWORD*)&tmp[2][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x3F800000UL;
+				p_push[5]	= 0x3F800000UL;
+				p_push		+= 6;
+					
+				// Third triangle.
+				p_push[0]	= *((DWORD*)&pos[X] );
+				p_push[1]	= *((DWORD*)&pos[Y] );
+				p_push[2]	= *((DWORD*)&pos[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[2][X] );
+				p_push[1]	= *((DWORD*)&tmp[2][Y] );
+				p_push[2]	= *((DWORD*)&tmp[2][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x3F800000UL;
+				p_push[5]	= 0x3F800000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[3][X] );
+				p_push[1]	= *((DWORD*)&tmp[3][Y] );
+				p_push[2]	= *((DWORD*)&tmp[3][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x3F800000UL;
+				p_push		+= 6;
+					
+				// Fourth triangle.
+				p_push[0]	= *((DWORD*)&pos[X] );
+				p_push[1]	= *((DWORD*)&pos[Y] );
+				p_push[2]	= *((DWORD*)&pos[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[3][X] );
+				p_push[1]	= *((DWORD*)&tmp[3][Y] );
+				p_push[2]	= *((DWORD*)&tmp[3][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x3F800000UL;
+				p_push		+= 6;
+
+				p_push[0]	= *((DWORD*)&tmp[0][X] );
+				p_push[1]	= *((DWORD*)&tmp[0][Y] );
+				p_push[2]	= *((DWORD*)&tmp[0][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push[4]	= 0x00000000UL;
+				p_push[5]	= 0x00000000UL;
+				p_push		+= 6;
+
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticlesmooth.h b/Code/Gfx/XBox/p_nxparticlesmooth.h
new file mode 100644
index 0000000..e434f19
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticlesmooth.h
@@ -0,0 +1,73 @@
+#ifndef	__GFX_P_NX_PARTICLESmooth_H__
+#define	__GFX_P_NX_PARTICLESmooth_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CXboxParticleSmooth : public CParticle
+{
+	public:
+							CXboxParticleSmooth();
+							CXboxParticleSmooth( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split );
+	virtual 				~CXboxParticleSmooth();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+
+	private:				// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	float*					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+	NxXbox::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color[2];			// Start color for each corner.
+	Image::RGBA				m_mid_color[2];				// Mid color for each corner.
+	Image::RGBA				m_end_color[2];				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticlesmoothribbon.cpp b/Code/Gfx/XBox/p_nxparticlesmoothribbon.cpp
new file mode 100644
index 0000000..bc84f97
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticlesmoothribbon.cpp
@@ -0,0 +1,357 @@
+#include 
+#include "gfx/xbox/nx/render.h"
+
+#include "gfx/xbox/p_nxparticleSmoothRibbon.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmoothRibbon::CXboxParticleSmoothRibbon()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmoothRibbon::CXboxParticleSmoothRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history )
+{
+	m_checksum		= checksum;
+	m_max_particles = max_particles;
+	m_num_particles = 0;
+	m_mid_time		= -1.0f;
+
+	mp_particle_array = new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float*[( history + 1)];
+	for ( int lp = 0; lp < ( history + 1 ); lp++ )
+	{
+		mp_vertices[lp] = new float[max_particles * 3];
+	}
+	m_num_vertex_buffers = history + 1;
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_SMOOTHRIBBON, texture_checksum, blendmode_checksum, fix );
+
+	// Default color.
+	for ( int lp = 0; lp < 4; lp++ )
+	{
+		m_start_color[lp].r = 128;
+		m_start_color[lp].g = 128;
+		m_start_color[lp].b = 128;
+		m_start_color[lp].a = 255;
+		m_mid_color[lp].r = 128;
+		m_mid_color[lp].g = 128;
+		m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = 128;
+		m_end_color[lp].g = 128;
+		m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmoothRibbon::~CXboxParticleSmoothRibbon()
+{
+	delete [] mp_particle_array;
+	for( int lp = 0; lp < m_num_vertex_buffers; lp++ )
+	{
+		delete [] mp_vertices[lp];
+	}
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothRibbon::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothRibbon::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothRibbon::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[list][entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxParticleSmoothRibbon::plat_get_num_particle_colors( void ) { return 4; }
+int CXboxParticleSmoothRibbon::plat_get_num_vertex_lists( void ) { return m_num_vertex_buffers; }
+void CXboxParticleSmoothRibbon::plat_set_sr( int entry, uint8 value ) { m_start_color[entry].r = value; }
+void CXboxParticleSmoothRibbon::plat_set_sg( int entry, uint8 value ) { m_start_color[entry].g = value; }
+void CXboxParticleSmoothRibbon::plat_set_sb( int entry, uint8 value ) { m_start_color[entry].b = value; }
+void CXboxParticleSmoothRibbon::plat_set_sa( int entry, uint8 value ) { m_start_color[entry].a = value; }
+void CXboxParticleSmoothRibbon::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleSmoothRibbon::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleSmoothRibbon::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleSmoothRibbon::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleSmoothRibbon::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleSmoothRibbon::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleSmoothRibbon::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleSmoothRibbon::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothRibbon::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v0;
+		float			*p_v1;
+		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+
+		Image::RGBA color[4];
+		Image::RGBA *p_col0;
+		Image::RGBA *p_col1;
+		
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 32;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_QUADLIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v0		= mp_vertices[0];
+		p_v1		= mp_vertices[(m_num_vertex_buffers - 1)];
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v0 += 3, p_v1 += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp = p_particle->m_time / p_particle->m_life;
+
+				Mth::Vector	pos0( p_v0[0] + m_pos[X], p_v0[1] + m_pos[Y], p_v0[2] + m_pos[Z] );
+				Mth::Vector	pos1( p_v1[0] + m_pos[X], p_v1[1] + m_pos[Y], p_v1[2] + m_pos[Z] );
+
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos0;
+//					max = pos0;
+//				}
+//				else
+//				{
+//					if( pos0[X] < min[X] ) min[X] = pos0[X]; else if( pos0[X] > max[X] ) max[X] = pos0[X];
+//					if( pos0[Y] < min[Y] ) min[Y] = pos0[Y]; else if( pos0[Y] > max[Y] ) max[Y] = pos0[Y];
+//					if( pos0[Z] < min[Z] ) min[Z] = pos0[Z]; else if( pos0[Z] > max[Z] ) max[Z] = pos0[Z];
+//				}
+
+				Mth::Vector	part_vec = pos1 - pos0;
+				Mth::Vector perp_vec = Mth::CrossProduct( part_vec, at );
+				perp_vec.Normalize();
+
+				float w = p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+		
+				Mth::Vector tmp[4];
+				tmp[0]		= pos0 + ( perp_vec * w );
+				tmp[1]		= pos0 - ( perp_vec * w );
+				tmp[2]		= pos1 - ( perp_vec * w );
+				tmp[3]		= pos1 + ( perp_vec * w );
+	
+				if( m_mid_time >= 0.0f )
+				{
+					if( terp < m_mid_time )
+					{
+						p_col0 = m_start_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_start_color;
+					p_col1 = m_end_color;
+				}
+
+				for( int c = 0; c < 4; c++ )
+				{
+					Image::RGBA start = *p_col0++;
+					Image::RGBA end = *p_col1++;
+
+					// Swap red and blue here.
+					color[c].b = start.r + (uint8)(( ((float)( end.r - start.r )) * terp ));
+					color[c].g = start.g + (uint8)(( ((float)( end.g - start.g )) * terp ));
+					color[c].r = start.b + (uint8)(( ((float)( end.b - start.b )) * terp ));
+					color[c].a = start.a + (uint8)(( ((float)( end.a - start.a )) * terp ));
+				}
+
+				// First quad.
+				p_push[0]	= *((DWORD*)&pos0[X] );
+				p_push[1]	= *((DWORD*)&pos0[Y] );
+				p_push[2]	= *((DWORD*)&pos0[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push		+= 4;
+
+				p_push[0]	= *((DWORD*)&pos1[X] );
+				p_push[1]	= *((DWORD*)&pos1[Y] );
+				p_push[2]	= *((DWORD*)&pos1[Z] );
+				p_push[3]	= *((DWORD*)&color[3] );
+				p_push		+= 4;
+				
+				p_push[0]	= *((DWORD*)&tmp[3][X] );
+				p_push[1]	= *((DWORD*)&tmp[3][Y] );
+				p_push[2]	= *((DWORD*)&tmp[3][Z] );
+				p_push[3]	= *((DWORD*)&color[2] );
+				p_push		+= 4;
+				
+				p_push[0]	= *((DWORD*)&tmp[0][X] );
+				p_push[1]	= *((DWORD*)&tmp[0][Y] );
+				p_push[2]	= *((DWORD*)&tmp[0][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push		+= 4;
+				
+				// Second quad.
+				p_push[0]	= *((DWORD*)&pos0[X] );
+				p_push[1]	= *((DWORD*)&pos0[Y] );
+				p_push[2]	= *((DWORD*)&pos0[Z] );
+				p_push[3]	= *((DWORD*)&color[0] );
+				p_push		+= 4;
+
+				p_push[0]	= *((DWORD*)&pos1[X] );
+				p_push[1]	= *((DWORD*)&pos1[Y] );
+				p_push[2]	= *((DWORD*)&pos1[Z] );
+				p_push[3]	= *((DWORD*)&color[3] );
+				p_push		+= 4;
+				
+				p_push[0]	= *((DWORD*)&tmp[2][X] );
+				p_push[1]	= *((DWORD*)&tmp[2][Y] );
+				p_push[2]	= *((DWORD*)&tmp[2][Z] );
+				p_push[3]	= *((DWORD*)&color[2] );
+				p_push		+= 4;
+				
+				p_push[0]	= *((DWORD*)&tmp[1][X] );
+				p_push[1]	= *((DWORD*)&tmp[1][Y] );
+				p_push[2]	= *((DWORD*)&tmp[1][Z] );
+				p_push[3]	= *((DWORD*)&color[1] );
+				p_push		+= 4;
+
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
diff --git a/Code/Gfx/XBox/p_nxparticlesmoothribbon.h b/Code/Gfx/XBox/p_nxparticlesmoothribbon.h
new file mode 100644
index 0000000..b6ae160
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticlesmoothribbon.h
@@ -0,0 +1,73 @@
+#ifndef	__GFX_P_NX_PARTICLESmoothRibbon_H__
+#define	__GFX_P_NX_PARTICLESmoothRibbon_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/////////////////////////////////////////////////////////////////////////////////////
+//
+// Here's a machine specific implementation of the CParticle
+    
+class CXboxParticleSmoothRibbon : public CParticle
+{
+public:
+							CXboxParticleSmoothRibbon();
+							CXboxParticleSmoothRibbon( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+	virtual 				~CXboxParticleSmoothRibbon();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+
+	private:				// It's all private, as it is machine specific
+	void					plat_render( void );
+	void					plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void					plat_set_position( int entry, int list, float x, float y, float z );
+	void					plat_add_position( int entry, int list, float x, float y, float z );
+	int						plat_get_num_vertex_lists( void );
+	int						plat_get_num_particle_colors( void );
+
+	void					plat_set_sr( int entry, uint8 value );
+	void					plat_set_sg( int entry, uint8 value );
+	void					plat_set_sb( int entry, uint8 value );
+	void					plat_set_sa( int entry, uint8 value );
+	void					plat_set_mr( int entry, uint8 value );
+	void					plat_set_mg( int entry, uint8 value );
+	void					plat_set_mb( int entry, uint8 value );
+	void					plat_set_ma( int entry, uint8 value );
+	void					plat_set_er( int entry, uint8 value );
+	void					plat_set_eg( int entry, uint8 value );
+	void					plat_set_eb( int entry, uint8 value );
+	void					plat_set_ea( int entry, uint8 value );
+		
+	int						m_num_vertex_buffers;
+	float**					mp_vertices;
+	uint32*					mp_colors;
+	uint64					m_blend;
+	NxXbox::sParticleSystem*	mp_engine_particle;
+
+	Image::RGBA				m_start_color[4];			// Start color for each corner.
+	Image::RGBA				m_mid_color[4];				// Mid color for each corner.
+	Image::RGBA				m_end_color[4];				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticlesmoothstar.cpp b/Code/Gfx/XBox/p_nxparticlesmoothstar.cpp
new file mode 100644
index 0000000..a8dd52c
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticlesmoothstar.cpp
@@ -0,0 +1,367 @@
+#include 
+#include 
+
+#include "gfx/xbox/p_nxparticleSmoothStar.h"
+
+extern DWORD PixelShader1;
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmoothStar::CXboxParticleSmoothStar()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmoothStar::CXboxParticleSmoothStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split )
+{
+	m_checksum			= checksum;
+	m_max_particles		= max_particles;
+	m_num_particles		= 0;
+	m_num_segments		= num_segments;
+	m_split				= split;
+
+	mp_particle_array	= new CParticleEntry[max_particles];
+
+	// Allocate vertex buffer.
+	mp_vertices = new float[max_particles * 3];
+
+	// Create the engine representation.
+	mp_engine_particle = new NxXbox::sParticleSystem( max_particles, NxXbox::PARTICLE_TYPE_SMOOTHSTAR, texture_checksum, blendmode_checksum, fix, num_segments );
+
+	// Default color.
+	for ( int lp = 0; lp < 3; lp++ )
+	{
+		m_SmoothStart_color[lp].r = m_SmoothStart_color[lp].g = m_SmoothStart_color[lp].b = 128;
+		m_SmoothStart_color[lp].a = 255;
+		m_mid_color[lp].r = m_mid_color[lp].g = m_mid_color[lp].b = 128;
+		m_mid_color[lp].a = 255;
+		m_end_color[lp].r = m_end_color[lp].g = m_end_color[lp].b = 128;
+		m_end_color[lp].a = 255;
+	}
+
+	m_mid_time = -1.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxParticleSmoothStar::~CXboxParticleSmoothStar()
+{
+	delete [] mp_particle_array;
+	delete [] mp_vertices;
+	delete mp_engine_particle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothStar::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	*x = p_v[0];
+	*y = p_v[1];
+	*z = p_v[2];
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothStar::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] = x;
+	p_v[1] = y;
+	p_v[2] = z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothStar::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	float* p_v = &mp_vertices[entry*3];
+	p_v[0] += x;
+	p_v[1] += y;
+	p_v[2] += z;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CXboxParticleSmoothStar::plat_get_num_particle_colors( void ) { return 3; }
+int CXboxParticleSmoothStar::plat_get_num_vertex_lists( void ) { return 1; }
+void CXboxParticleSmoothStar::plat_set_sr( int entry, uint8 value ) { m_SmoothStart_color[entry].r = value; }
+void CXboxParticleSmoothStar::plat_set_sg( int entry, uint8 value ) { m_SmoothStart_color[entry].g = value; }
+void CXboxParticleSmoothStar::plat_set_sb( int entry, uint8 value ) { m_SmoothStart_color[entry].b = value; }
+void CXboxParticleSmoothStar::plat_set_sa( int entry, uint8 value ) { m_SmoothStart_color[entry].a = value; }
+void CXboxParticleSmoothStar::plat_set_mr( int entry, uint8 value ) { m_mid_color[entry].r = value; }
+void CXboxParticleSmoothStar::plat_set_mg( int entry, uint8 value ) { m_mid_color[entry].g = value; }
+void CXboxParticleSmoothStar::plat_set_mb( int entry, uint8 value ) { m_mid_color[entry].b = value; }
+void CXboxParticleSmoothStar::plat_set_ma( int entry, uint8 value ) { m_mid_color[entry].a = value; }
+void CXboxParticleSmoothStar::plat_set_er( int entry, uint8 value ) { m_end_color[entry].r = value; }
+void CXboxParticleSmoothStar::plat_set_eg( int entry, uint8 value ) { m_end_color[entry].g = value; }
+void CXboxParticleSmoothStar::plat_set_eb( int entry, uint8 value ) { m_end_color[entry].b = value; }
+void CXboxParticleSmoothStar::plat_set_ea( int entry, uint8 value ) { m_end_color[entry].a = value; }
+
+
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxParticleSmoothStar::plat_render( void )
+{
+	// Draw the particles.
+	if( m_num_particles > 0 )
+	{
+		// Used to figure the right and up vectors for creating screen-aligned particle quads.
+		D3DXMATRIX *p_matrix = (D3DXMATRIX*)&NxXbox::EngineGlobals.view_matrix;
+
+		// Concatenate p_matrix with the emmission angle to create the direction.
+		Mth::Vector up( 0.0f, 1.0f, 0.0f, 0.0f );
+
+		// Get the 'right' vector as the cross product of camera 'at and world 'up'.
+		Mth::Vector at( p_matrix->m[0][2], p_matrix->m[1][2], p_matrix->m[2][2] );
+		Mth::Vector screen_right	= Mth::CrossProduct( at, up );
+		Mth::Vector screen_up		= Mth::CrossProduct( screen_right, at );
+
+		screen_right.Normalize();
+		screen_up.Normalize();
+	
+		int				lp;
+		CParticleEntry	*p_particle;
+		float			*p_v;
+//		Mth::Vector		min, max;	// For dynamic bounding box calculation.
+
+		// Obtain push buffer lock.
+		DWORD *p_push; 
+		DWORD dwords_per_particle	= 32 * m_num_segments;
+		DWORD dword_count			= dwords_per_particle * m_num_particles;
+
+		// Submit particle material.
+		mp_engine_particle->mp_material->Submit();
+		
+		// Set up correct vertex and pixel shader.
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( PixelShader1 );
+		
+		// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+		p_push = D3DDevice_BeginPush( dword_count + 32 );
+
+		// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+		// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+		// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+		p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1]	= D3DPT_QUADLIST;
+		p_push		+= 2;
+
+		// Set up loop variables here, since we be potentially enetering the loop more than once.
+		lp			= 0;
+		p_particle	= mp_particle_array;
+		p_v			= mp_vertices;
+
+		while( dword_count > 0 )
+		{
+			int dwords_written = 0;
+
+			// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+			// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+			p_push[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, ( dword_count > 2047 ) ? ((int)( 2047 / dwords_per_particle )) * dwords_per_particle: dword_count );
+			++p_push;
+		
+			for( ; lp < m_num_particles; lp++, p_particle++, p_v += 3 )
+			{
+				// Check to see if writing another particle will take us over the edge.
+				if(( dwords_written + dwords_per_particle ) > 2047 )
+				{
+					break;
+				}
+
+				float terp	= p_particle->m_time / p_particle->m_life;
+				float w		= p_particle->m_sw + ( ( p_particle->m_ew - p_particle->m_sw ) * terp );
+				float h		= p_particle->m_sh + ( ( p_particle->m_eh - p_particle->m_sh ) * terp );
+
+				Mth::Vector	pos( p_v[0] + m_pos[X], p_v[1] + m_pos[Y], p_v[2] + m_pos[Z] );
+				Mth::Vector	ss_right, ss_up;	//, ss_pos;
+				Mth::Vector tmp[4];
+	
+				// Dynamic bounding box calculation.
+//				if( lp == 0 )
+//				{
+//					min = pos;
+//					max = pos;
+//				}
+//				else
+//				{
+//					if( pos[X] < min[X] ) min[X] = pos[X]; else if( pos[X] > max[X] ) max[X] = pos[X];
+//					if( pos[Y] < min[Y] ) min[Y] = pos[Y]; else if( pos[Y] > max[Y] ) max[Y] = pos[Y];
+//					if( pos[Z] < min[Z] ) min[Z] = pos[Z]; else if( pos[Z] > max[Z] ) max[Z] = pos[Z];
+//				}
+
+				ss_right	= screen_right * w;
+				ss_up		= screen_up * h;
+	
+				Image::RGBA color[3];
+				Image::RGBA *p_col0;
+				Image::RGBA *p_col1;
+
+				if( m_mid_time >= 0.0f )
+				{
+					if ( terp < m_mid_time )
+					{
+						p_col0 = m_SmoothStart_color;
+						p_col1 = m_mid_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = terp / m_mid_time;
+					}
+					else
+					{
+						p_col0 = m_mid_color;
+						p_col1 = m_end_color;
+
+						// Adjust interpolation for this half of the color blend.
+						terp = ( terp - m_mid_time ) / ( 1.0f - m_mid_time );
+					}
+				}
+				else
+				{
+					// No mid color specified.
+					p_col0 = m_SmoothStart_color;
+					p_col1 = m_end_color;
+				}
+
+				// Swap red and blue here during interpolation.
+				for ( int c = 0; c < 3; c++ )
+				{
+					Image::RGBA SmoothStart = *p_col0++;
+					Image::RGBA end = *p_col1++;
+
+					color[c].b = SmoothStart.r + (uint8)(( ((float)( end.r - SmoothStart.r )) * terp ));
+					color[c].g = SmoothStart.g + (uint8)(( ((float)( end.g - SmoothStart.g )) * terp ));
+					color[c].r = SmoothStart.b + (uint8)(( ((float)( end.b - SmoothStart.b )) * terp ));
+					color[c].a = SmoothStart.a + (uint8)(( ((float)( end.a - SmoothStart.a )) * terp ));
+				}
+
+				tmp[1]  = pos;
+				tmp[1] += ss_right * sinf( Mth::DegToRad( 0.0f ) ) * m_split;
+				tmp[1] += ss_up    * cosf( Mth::DegToRad( 0.0f ) ) * m_split;
+
+				for( int lp2 = 0; lp2 < m_num_segments; lp2++ )
+				{
+					tmp[0]  = pos;
+					tmp[0] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) ) * m_split;
+					tmp[0] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) ) * m_split;
+			
+					tmp[3]  = pos;
+					tmp[3] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 2 ) ) ) ) * m_split;
+					tmp[3] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 2 ) ) ) ) * m_split;
+			
+					tmp[2]  = pos;
+					tmp[2] += ss_right * sinf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+					tmp[2] += ss_up    * cosf( Mth::DegToRad( ( ( 360.0f / ((float)m_num_segments*2) ) * ( ( lp2 * 2 ) + 1 ) ) ) );
+
+					// First quad.
+					p_push[0]	= *((DWORD*)&tmp[0][X] );
+					p_push[1]	= *((DWORD*)&tmp[0][Y] );
+					p_push[2]	= *((DWORD*)&tmp[0][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&pos[X] );
+					p_push[1]	= *((DWORD*)&pos[Y] );
+					p_push[2]	= *((DWORD*)&pos[Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[3][X] );
+					p_push[1]	= *((DWORD*)&tmp[3][Y] );
+					p_push[2]	= *((DWORD*)&tmp[3][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					// Second quad.
+					p_push[0]	= *((DWORD*)&tmp[0][X] );
+					p_push[1]	= *((DWORD*)&tmp[0][Y] );
+					p_push[2]	= *((DWORD*)&tmp[0][Z] );
+					p_push[3]	= *((DWORD*)&color[1] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&pos[X] );
+					p_push[1]	= *((DWORD*)&pos[Y] );
+					p_push[2]	= *((DWORD*)&pos[Z] );
+					p_push[3]	= *((DWORD*)&color[0] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[1][X] );
+					p_push[1]	= *((DWORD*)&tmp[1][Y] );
+					p_push[2]	= *((DWORD*)&tmp[1][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					p_push[0]	= *((DWORD*)&tmp[2][X] );
+					p_push[1]	= *((DWORD*)&tmp[2][Y] );
+					p_push[2]	= *((DWORD*)&tmp[2][Z] );
+					p_push[3]	= *((DWORD*)&color[2] );
+					p_push		+= 4;
+
+					tmp[1] = tmp[3];
+				}
+				dwords_written	+= dwords_per_particle;
+				dword_count		-= dwords_per_particle;
+			}
+		}
+
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+
+		// Set the mesh bounding box and sphere.
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMin( min );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_bbox.SetMax( max );
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center = D3DXVECTOR3( min[X] + (( max[X] - min[X] ) * 0.5f ), min[Y] + (( max[Y] - min[Y] ) * 0.5f ), min[Z] + (( max[Z] - min[Z] ) * 0.5f ));
+//		mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius = 360.0f;
+
+		// And the scene bounding sphere.
+//		mp_engine_particle->mp_scene->m_sphere_center = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_center;
+//		mp_engine_particle->mp_scene->m_sphere_radius = mp_engine_particle->mp_scene->m_semitransparent_meshes[0]->m_sphere_radius;
+	}
+}
+
+} // Nx
+
+
+
diff --git a/Code/Gfx/XBox/p_nxparticlesmoothstar.h b/Code/Gfx/XBox/p_nxparticlesmoothstar.h
new file mode 100644
index 0000000..26c75f1
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxparticlesmoothstar.h
@@ -0,0 +1,70 @@
+#ifndef	__GFX_P_NX_PARTICLESmoothStar_H__
+#define	__GFX_P_NX_PARTICLESmoothStar_H__
+    
+#include "gfx/xbox/p_nxparticle.h"
+                   
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CXboxParticleSmoothStar : public CParticle
+{
+	public:
+								CXboxParticleSmoothStar();
+								CXboxParticleSmoothStar( uint32 checksum, int max_particles, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split );
+	virtual 					~CXboxParticleSmoothStar();
+
+	NxXbox::sParticleSystem*	GetEngineParticle( void )	{ return mp_engine_particle; }
+
+	private: // It's all private, as it is machine specific
+	void						plat_render( void );
+	void						plat_get_position( int entry, int list, float * x, float * y, float * z );
+	void						plat_set_position( int entry, int list, float x, float y, float z );
+	void						plat_add_position( int entry, int list, float x, float y, float z );
+	int							plat_get_num_vertex_lists( void );
+	int							plat_get_num_particle_colors( void );
+
+	void						plat_set_sr( int entry, uint8 value );
+	void						plat_set_sg( int entry, uint8 value );
+	void						plat_set_sb( int entry, uint8 value );
+	void						plat_set_sa( int entry, uint8 value );
+	void						plat_set_mr( int entry, uint8 value );
+	void						plat_set_mg( int entry, uint8 value );
+	void						plat_set_mb( int entry, uint8 value );
+	void						plat_set_ma( int entry, uint8 value );
+	void						plat_set_er( int entry, uint8 value );
+	void						plat_set_eg( int entry, uint8 value );
+	void						plat_set_eb( int entry, uint8 value );
+	void						plat_set_ea( int entry, uint8 value );
+		
+	float*						mp_vertices;
+	uint32*						mp_colors;
+	uint64						m_blend;
+
+	NxXbox::sParticleSystem*	mp_engine_particle;
+	int							m_num_segments;
+	float						m_split;
+	Image::RGBA					m_SmoothStart_color[3];			// SmoothStart color for each corner.
+	Image::RGBA					m_mid_color[3];				// Mid color for each corner.
+	Image::RGBA					m_end_color[3];				// End color for each corner.
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif 
+
+
+
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxscene.cpp b/Code/Gfx/XBox/p_nxscene.cpp
new file mode 100644
index 0000000..a5ad180
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxscene.cpp
@@ -0,0 +1,138 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.cpp
+
+#include 	"gfx/xbox/p_NxScene.h"
+#include 	"gfx/xbox/p_NxSector.h"
+#include 	"gfx/xbox/p_NxGeom.h"
+
+namespace Nx
+{
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxScene::CXboxScene( int sector_table_size ) : CScene( sector_table_size )
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxScene::DestroySectorMeshes( void )
+{
+	// Iterate through the list of sectors, removing each one in turn, and requesting the engine to
+	// remove the meshes.
+	mp_sector_table->IterateStart();
+	CSector* pSector = mp_sector_table->IterateNext();
+	while( pSector )
+	{
+		// Access platform dependent data.
+		CXboxSector* pXboxSector = static_cast(pSector);
+
+		// Remove this mesh array from the engine.
+//		pXboxSector->DestroyMeshArray();
+
+		pSector = mp_sector_table->IterateNext();
+	}
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CScene
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxScene::plat_post_load()
+{
+	// Now turn the temporary mesh lists into mesh arrays.
+	mp_sector_table->IterateStart();
+	CSector* pSector = mp_sector_table->IterateNext();
+	while( pSector )
+	{
+		CXboxGeom *p_xbox_geom = static_cast(pSector->GetGeom());
+
+		p_xbox_geom->CreateMeshArray();
+
+		// First time through we just want to count the meshes,
+		p_xbox_geom->RegisterMeshArray( true );
+
+		pSector = mp_sector_table->IterateNext();
+	}
+
+	// Now we have counted all the meshes, tell the engine to create the arrays to hold them.
+	GetEngineScene()->CreateMeshArrays();
+	
+	// Now go through and actually add the meshes.
+	mp_sector_table->IterateStart();
+	pSector = mp_sector_table->IterateNext();
+	while( pSector )
+	{
+		// Access platform dependent data.
+		CXboxGeom *p_xbox_geom = static_cast(pSector->GetGeom());
+
+		p_xbox_geom->RegisterMeshArray( false );
+
+		pSector = mp_sector_table->IterateNext();
+	}
+
+	// Now all meshes are registered, tell the engine to sort them.
+	GetEngineScene()->SortMeshes();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxScene::plat_load_textures(const char* p_name)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxScene::plat_load_collision(const char* p_name)
+{
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxScene::plat_unload_add_scene( void )
+{
+	// Not sure what this is supposed to do, but added for now to remove annoying stub output.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Create an empty sector
+CSector	*CXboxScene::plat_create_sector()
+{
+	CXboxSector *p_xbox_sector	= new CXboxSector();
+	return p_xbox_sector;
+}
+
+
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_nxscene.h b/Code/Gfx/XBox/p_nxscene.h
new file mode 100644
index 0000000..0c6e75c
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxscene.h
@@ -0,0 +1,40 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_SCENE_H__
+#define	__GFX_P_NX_SCENE_H__
+
+#include "Gfx/Nx.h"
+#include "Gfx/xbox/nx/scene.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CScene
+class	CXboxScene : public CScene
+{
+public:
+
+								CXboxScene( int sector_table_size = 10 );
+	NxXbox::sScene *			GetEngineScene() const						{ return mp_engine_scene; }
+	void						SetEngineScene( NxXbox::sScene *p_scene )	{ mp_engine_scene = p_scene; }
+	void						DestroySectorMeshes( void );
+
+private:		// It's all private, as it is machine specific
+	virtual void				plat_post_load();	
+	virtual bool				plat_load_textures( const char *p_name );	// load textures 
+	virtual bool				plat_load_collision( const char *p_name );	// load collision data
+	virtual bool				plat_unload_add_scene( void );
+	virtual	CSector	*			plat_create_sector();	 					// empty sector
+
+
+	NxXbox::sScene				*mp_engine_scene;
+
+};
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/XBox/p_nxsector.cpp b/Code/Gfx/XBox/p_nxsector.cpp
new file mode 100644
index 0000000..b52880d
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxsector.cpp
@@ -0,0 +1,821 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxSector.cpp
+
+#include	
+
+#include 	"gfx/xbox/p_NxSector.h"
+#include 	"gfx/xbox/p_NxGeom.h"
+#include 	"gfx/NxMiscFX.h"
+#include 	"gfx/xbox/nx/grass.h"
+#include 	"gfx/xbox/nx/billboard.h"
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//CXboxSector::CXboxSector() : mp_init_mesh_list( NULL ), m_mesh_array( NULL ), m_visible( 0xDEADBEEF )
+CXboxSector::CXboxSector()
+{
+	m_pos_offset.Set( 0.0f, 0.0f, 0.0f );
+}
+
+
+
+
+#define MemoryRead( dst, size, num, src )	CopyMemory(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxSector::LoadFromMemory( void **pp_mem )
+{
+	Dbg_Assert( mp_geom );
+
+	uint8		*p_data	= (uint8*)( *pp_mem );
+	CXboxGeom	*p_geom = static_cast( mp_geom );
+	
+	// Read sector checksum.
+	uint32 sector_checksum;
+	MemoryRead( §or_checksum, sizeof( uint32 ), 1, p_data );
+	SetChecksum( sector_checksum );
+
+	// Read bone index.
+	int bone_idx;
+	MemoryRead( &bone_idx, sizeof( int ), 1, p_data );
+
+	// Read sector flags.
+	MemoryRead( &m_flags, sizeof( int ), 1, p_data );
+
+	// Read number of meshes.
+	MemoryRead( &p_geom->m_num_mesh, sizeof( uint ), 1, p_data );
+
+	// Read bounding box.
+	float bbox[6];
+	MemoryRead( &bbox[0], sizeof( float ), 6, p_data );
+	Mth::Vector	inf( bbox[0], bbox[1], bbox[2] );
+	Mth::Vector	sup( bbox[3], bbox[4], bbox[5] );
+	p_geom->m_bbox.Set( inf, sup );
+		
+	// Read bounding sphere.
+	float bsphere[4];
+	MemoryRead( &bsphere[0], sizeof( float ), 4, p_data );
+		
+	// Read billboard data if present.
+	uint32		billboard_type;
+	Mth::Vector	billboard_origin;
+	Mth::Vector billboard_pivot_pos;
+	Mth::Vector billboard_pivot_axis;
+	if( m_flags & 0x00800000UL )
+	{
+		MemoryRead( &billboard_type,			sizeof( uint32 ), 1, p_data );
+		MemoryRead( &billboard_origin[X],		sizeof( float ) * 3, 1, p_data );
+		MemoryRead( &billboard_pivot_pos[X],	sizeof( float ) * 3, 1, p_data );
+		MemoryRead( &billboard_pivot_axis[X],	sizeof( float ) * 3, 1, p_data );
+
+		billboard_origin[Z]		= -billboard_origin[Z];
+		billboard_pivot_pos[Z]	= -billboard_pivot_pos[Z];
+	}
+
+	// Read num vertices.
+	int num_vertices;
+	MemoryRead( &num_vertices, sizeof( int ), 1, p_data );
+	
+	// Read vertex data stride.
+	int vertex_data_stride;
+	MemoryRead( &vertex_data_stride, sizeof( int ), 1, p_data );
+
+	// We want all the temporary buffer allocations to come off of the top down heap.
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+	
+	// Grab a buffer for the raw vertex data position stream, and read it.
+	float* p_vertex_positions = new float[num_vertices * 3];
+	MemoryRead( p_vertex_positions, sizeof( float ) * 3, num_vertices, p_data );
+	
+	// Grab a buffer for the raw vertex data normal stream (if present), and read it.
+	float* p_vertex_normals = ( m_flags & 0x04 ) ? new float[num_vertices * 3] : NULL;
+	if( p_vertex_normals )
+	{
+		MemoryRead( p_vertex_normals, sizeof( float ) * 3, num_vertices, p_data );
+	}
+
+	// Grab a buffer for the raw vertex data weights stream (if present), and read it.
+	uint32* p_vertex_weights = ( m_flags & 0x10 ) ? new uint32[num_vertices] : NULL;
+	if( p_vertex_weights )
+	{
+		MemoryRead( p_vertex_weights, sizeof( uint32 ), num_vertices, p_data );
+	}
+	
+	// Grab a buffer for the raw vertex data bone indices stream (if present), and read it.
+	uint16* p_vertex_bone_indices = ( m_flags & 0x10 ) ? new uint16[num_vertices * 4] : NULL;
+	if( p_vertex_bone_indices )
+	{
+		MemoryRead( p_vertex_bone_indices, sizeof( uint16 ) * 4, num_vertices, p_data );
+	}
+
+	// Grab a buffer for the raw vertex texture coordinate stream (if present), and read it.
+	int		num_tc_sets			= 0;
+	float*	p_vertex_tex_coords	= NULL;
+	if( m_flags & 0x01 )
+	{
+		MemoryRead( &num_tc_sets, sizeof( int ), 1, p_data );
+		
+		if( num_tc_sets > 0 )
+		{
+			p_vertex_tex_coords = new float[num_vertices * 2 * num_tc_sets];
+			MemoryRead( p_vertex_tex_coords, sizeof( float ) * 2 * num_tc_sets, num_vertices, p_data );
+		}
+	}
+	
+	// Grab a buffer for the raw vertex colors stream (if present), and read it.
+	DWORD* p_vertex_colors = ( m_flags & 0x02 ) ? new DWORD[num_vertices] : NULL;
+	if( p_vertex_colors )
+	{
+		MemoryRead( p_vertex_colors, sizeof( DWORD ), num_vertices, p_data );
+	}
+
+	// Grab a buffer for the vertex color wibble stream (if present), and read it.
+	char* p_vc_wibble_indices = ( m_flags & 0x800 ) ? new char[num_vertices] : NULL;
+	if( p_vc_wibble_indices )
+	{
+		MemoryRead( p_vc_wibble_indices, sizeof( char ), num_vertices, p_data );
+	}
+	
+	// Remove TopDownHeap context.
+	Mem::Manager::sHandle().PopContext();
+
+	// Preprocess verts that require cutscene scaling.
+	NxXbox::ApplyMeshScaling( p_vertex_positions, num_vertices );
+
+	for( uint m = 0; m < p_geom->m_num_mesh; ++m )
+	{
+		unsigned long	material_checksum;
+		unsigned int	flags, num_lod_index_levels;
+
+		NxXbox::sMesh*	p_mesh = new NxXbox::sMesh;
+
+		// Read bounding sphere and box data.
+		float		rad;
+		Mth::Vector inf, sup, cen;
+		MemoryRead( &cen[X], sizeof( float ), 3, p_data );
+		MemoryRead( &rad, sizeof( float ), 1, p_data );
+		MemoryRead( &inf[X], sizeof( float ), 3, p_data );
+		MemoryRead( &sup[X], sizeof( float ), 3, p_data );
+
+		// Read and deal with flags, including skater shadow flag.
+		MemoryRead( &flags, sizeof( uint32 ), 1, p_data );
+		if( flags & 0x400 )
+		{
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		}
+		if( flags & NxXbox::sMesh::MESH_FLAG_UNLIT )
+		{
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_UNLIT;
+		}
+
+		// The material checksum for this mesh.
+		MemoryRead( &material_checksum,	sizeof( unsigned long ), 1, p_data );
+
+		// How many levels of LOD indices? Should be at least 1!
+		MemoryRead( &num_lod_index_levels, sizeof( unsigned int ), 1, p_data );
+
+		// Can have up to 8 levels of LOD indices.
+		uint16*	p_indices[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+		int		num_indices[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+		for( unsigned int lod_level = 0; lod_level < num_lod_index_levels; ++lod_level )
+		{
+			MemoryRead( &num_indices[lod_level], sizeof( int ), 1, p_data );
+		
+			// Again, we want all the temporary buffer allocations to come off of the top down heap.
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+			p_indices[lod_level] = new uint16[num_indices[lod_level]];
+			Mem::Manager::sHandle().PopContext();
+
+			MemoryRead( p_indices[lod_level], sizeof( uint16 ), num_indices[lod_level], p_data );
+		}
+
+		// Set the load order of this mesh.
+		p_mesh->m_load_order = m;
+
+		// Set up the mesh.
+		p_mesh->Initialize(	num_vertices,
+							p_vertex_positions,
+							p_vertex_normals,
+							p_vertex_tex_coords,
+							num_tc_sets,
+							p_vertex_colors,
+							num_lod_index_levels,
+							num_indices,
+							p_indices,
+							material_checksum,
+							p_geom->mp_scene->GetEngineScene(),
+							p_vertex_bone_indices,
+							p_vertex_weights,
+							( flags & 0x800 ) ? p_vc_wibble_indices : NULL );
+		
+		// Set the bounding data (sphere and box) for the mesh.
+		p_mesh->SetBoundingData( cen, rad, inf, sup );
+
+		// Add the bounding data to the scene.
+		p_geom->mp_scene->GetEngineScene()->m_bbox.AddPoint( inf );
+		p_geom->mp_scene->GetEngineScene()->m_bbox.AddPoint( sup );
+
+		// Set up as a billboard if required.
+		if( m_flags & 0x00800000UL )
+		{
+			p_mesh->SetBillboardData( billboard_type, billboard_pivot_pos, billboard_pivot_axis );
+			NxXbox::BillboardManager.AddEntry( p_mesh );
+		}
+
+		// Flag the mesh as being a shadow volume if applicable.
+		if( m_flags & 0x200000 )
+		{
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_SHADOW_VOLUME;
+		}
+
+		// Add the mesh to the attached CXboxGeom.
+		p_geom->AddMesh( p_mesh );
+
+		// Set the mesh bone index (mostly not applicable).
+		p_mesh->SetBoneIndex( bone_idx );
+	
+		// Test code - if the mesh is mapped with a grass texture, add grass meshes.
+		AddGrass( p_geom, p_mesh );
+		
+		// Done with the raw index data.
+		for( int lod_level = 0; lod_level < 8; ++lod_level )
+		{
+			delete[] p_indices[lod_level];
+		}
+	}
+
+	// Recount the number of meshes in case any have been added.
+	p_geom->m_num_mesh = p_geom->mp_init_mesh_list->CountItems();
+	
+	// Done with the raw vertex data.
+	delete[] p_vc_wibble_indices;
+	delete[] p_vertex_colors;
+	delete[] p_vertex_tex_coords;
+	delete[] p_vertex_bone_indices;
+	delete[] p_vertex_weights;
+	delete[] p_vertex_normals;
+	delete[] p_vertex_positions;
+	
+	// Set the data pointer to the new position on return.
+	*pp_mem = p_data;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxSector::LoadFromFile( void* p_file )
+{
+	Dbg_Assert( mp_geom );
+
+	CXboxGeom *p_geom = static_cast( mp_geom );
+	
+	// Read sector checksum.
+	uint32 sector_checksum;
+	File::Read( §or_checksum, sizeof( uint32 ), 1, p_file );
+	SetChecksum( sector_checksum );
+
+	// Read bone index.
+	int bone_idx;
+	File::Read( &bone_idx, sizeof( int ), 1, p_file );
+
+	// Read sector flags.
+	File::Read( &m_flags, sizeof( int ), 1, p_file );
+
+	// Read number of meshes.
+	File::Read( &p_geom->m_num_mesh, sizeof( uint ), 1, p_file );
+
+	// Read bounding box.
+	float bbox[6];
+	File::Read( &bbox[0], sizeof( float ), 6, p_file );
+	Mth::Vector	inf( bbox[0], bbox[1], bbox[2] );
+	Mth::Vector	sup( bbox[3], bbox[4], bbox[5] );
+	p_geom->m_bbox.Set( inf, sup );
+		
+	// Read bounding sphere.
+	float bsphere[4];
+	File::Read( &bsphere[0], sizeof( float ), 4, p_file );
+		
+	// Read billboard data if present.
+	uint32		billboard_type;
+	Mth::Vector	billboard_origin;
+	Mth::Vector billboard_pivot_pos;
+	Mth::Vector billboard_pivot_axis;
+	if( m_flags & 0x00800000UL )
+	{
+		File::Read( &billboard_type,			sizeof( uint32 ), 1, p_file );
+		File::Read( &billboard_origin[X],		sizeof( float ) * 3, 1, p_file );
+		File::Read( &billboard_pivot_pos[X],	sizeof( float ) * 3, 1, p_file );
+		File::Read( &billboard_pivot_axis[X],	sizeof( float ) * 3, 1, p_file );
+
+		billboard_origin[Z]		= -billboard_origin[Z];
+		billboard_origin[W]		= 0.0f;
+
+		billboard_pivot_pos[Z]	= -billboard_pivot_pos[Z];
+		billboard_pivot_pos[W]	= 0.0f;
+
+		billboard_pivot_axis[W]	= 0.0f;
+	}
+
+	// Read num vertices.
+	int num_vertices;
+	File::Read( &num_vertices, sizeof( int ), 1, p_file );
+	
+	// Read vertex data stride.
+	int vertex_data_stride;
+	File::Read( &vertex_data_stride, sizeof( int ), 1, p_file );
+
+	// We want all the temporary buffer allocations to come off of the top down heap.
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+	
+	// Grab a buffer for the raw vertex data position stream, and read it.
+	float* p_vertex_positions = new float[num_vertices * 3];
+	File::Read( p_vertex_positions, sizeof( float ) * 3, num_vertices, p_file );
+	
+	// Grab a buffer for the raw vertex data normal stream (if present), and read it.
+	float* p_vertex_normals = ( m_flags & 0x04 ) ? new float[num_vertices * 3] : NULL;
+	if( p_vertex_normals )
+	{
+		File::Read( p_vertex_normals, sizeof( float ) * 3, num_vertices, p_file );
+	}
+
+	// Grab a buffer for the raw vertex data weights stream (if present), and read it.
+	uint32* p_vertex_weights = ( m_flags & 0x10 ) ? new uint32[num_vertices] : NULL;
+	if( p_vertex_weights )
+	{
+		File::Read( p_vertex_weights, sizeof( uint32 ), num_vertices, p_file );
+	}
+	
+	// Grab a buffer for the raw vertex data bone indices stream (if present), and read it.
+	uint16* p_vertex_bone_indices = ( m_flags & 0x10 ) ? new uint16[num_vertices * 4] : NULL;
+	if( p_vertex_bone_indices )
+	{
+		File::Read( p_vertex_bone_indices, sizeof( uint16 ) * 4, num_vertices, p_file );
+	}
+
+	// Grab a buffer for the raw vertex texture coordinate stream (if present), and read it.
+	int		num_tc_sets			= 0;
+	float*	p_vertex_tex_coords	= NULL;
+	if( m_flags & 0x01 )
+	{
+		File::Read( &num_tc_sets, sizeof( int ), 1, p_file );
+		
+		if( num_tc_sets > 0 )
+		{
+			p_vertex_tex_coords = new float[num_vertices * 2 * num_tc_sets];
+			File::Read( p_vertex_tex_coords, sizeof( float ) * 2 * num_tc_sets, num_vertices, p_file );
+		}
+	}
+	
+	// Grab a buffer for the raw vertex colors stream (if present), and read it.
+	DWORD* p_vertex_colors = ( m_flags & 0x02 ) ? new DWORD[num_vertices] : NULL;
+	if( p_vertex_colors )
+	{
+		File::Read( p_vertex_colors, sizeof( DWORD ), num_vertices, p_file );
+	}
+
+	// Grab a buffer for the vertex color wibble stream (if present), and read it.
+	char* p_vc_wibble_indices = ( m_flags & 0x800 ) ? new char[num_vertices] : NULL;
+	if( p_vc_wibble_indices )
+	{
+		File::Read( p_vc_wibble_indices, sizeof( char ), num_vertices, p_file );
+	}
+	
+	// Remove TopDownHeap context.
+	Mem::Manager::sHandle().PopContext();
+
+	for( uint m = 0; m < p_geom->m_num_mesh; ++m )
+	{
+		unsigned long	material_checksum;
+		unsigned int	flags, num_lod_index_levels;
+
+		NxXbox::sMesh*	p_mesh = new NxXbox::sMesh;
+
+		// Read bounding sphere and box data.
+		float		rad;
+		Mth::Vector cen;
+		File::Read( &cen[X], sizeof( float ), 3, p_file );
+		File::Read( &rad, sizeof( float ), 1, p_file );
+		File::Read( &inf[X], sizeof( float ), 3, p_file );
+		File::Read( &sup[X], sizeof( float ), 3, p_file );
+
+		// Read and deal with flags, including skater shadow flag.
+		File::Read( &flags, sizeof( uint32 ), 1, p_file );
+		if( flags & 0x400 )
+		{
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_NO_SKATER_SHADOW;
+		}
+		if( flags & NxXbox::sMesh::MESH_FLAG_UNLIT )
+		{
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_UNLIT;
+		}
+
+		// The material checksum for this mesh.
+		File::Read( &material_checksum,	sizeof( unsigned long ), 1, p_file );
+
+		// How many levels of LOD indices? Should be at least 1!
+		File::Read( &num_lod_index_levels, sizeof( unsigned int ), 1, p_file );
+
+		// Can have up to 8 levels of LOD indices.
+		uint16*	p_indices[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+		int		num_indices[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+		for( unsigned int lod_level = 0; lod_level < num_lod_index_levels; ++lod_level )
+		{
+			File::Read( &num_indices[lod_level], sizeof( int ), 1, p_file );
+		
+			// Again, we want all the temporary buffer allocations to come off of the top down heap.
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap());
+			p_indices[lod_level] = new uint16[num_indices[lod_level]];
+			Mem::Manager::sHandle().PopContext();
+
+			File::Read( p_indices[lod_level], sizeof( uint16 ), num_indices[lod_level], p_file );
+		}
+
+		// Set the load order of this mesh.
+		p_mesh->m_load_order = m;
+
+		// Set up the mesh.
+		p_mesh->Initialize(	num_vertices,
+							p_vertex_positions,
+							( m_flags & 0x00800000UL ) ? NULL : p_vertex_normals,		// No normals allowed for billboards.
+							p_vertex_tex_coords,
+							num_tc_sets,
+							p_vertex_colors,
+							num_lod_index_levels,
+							num_indices,
+							p_indices,
+							material_checksum,
+							p_geom->mp_scene->GetEngineScene(),
+							p_vertex_bone_indices,
+							p_vertex_weights,
+							( flags & 0x800 ) ? p_vc_wibble_indices : NULL );
+		
+		// Set the bounding data (sphere and box) for the mesh.
+		p_mesh->SetBoundingData( cen, rad, inf, sup );
+
+		// Add the bounding data to the scene.
+		p_geom->mp_scene->GetEngineScene()->m_bbox.AddPoint( inf );
+		p_geom->mp_scene->GetEngineScene()->m_bbox.AddPoint( sup );
+
+		// Set up as a billboard if required.
+		if( m_flags & 0x00800000UL )
+		{
+			p_mesh->SetBillboardData( billboard_type, billboard_pivot_pos, billboard_pivot_axis );
+			NxXbox::BillboardManager.AddEntry( p_mesh );
+		}
+
+		// Flag the mesh as being a shadow volume if applicable.
+		if( m_flags & 0x200000 )
+		{
+			p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_SHADOW_VOLUME;
+		}
+
+		// Add the mesh to the attached CXboxGeom.
+		p_geom->AddMesh( p_mesh );
+
+		// Set the mesh bone index (mostly not applicable).
+		p_mesh->SetBoneIndex( bone_idx );
+	
+		// Test code - if the mesh is mapped with a grass texture, add grass meshes.
+		AddGrass( p_geom, p_mesh );
+		
+		// Done with the raw index data.
+		for( int lod_level = 0; lod_level < 8; ++lod_level )
+		{
+			delete[] p_indices[lod_level];
+		}
+	}
+
+	// Recount the number of meshes in case any have been added.
+	p_geom->m_num_mesh = p_geom->mp_init_mesh_list->CountItems();
+	
+
+	// Test code for creating imposters.
+#	if 0
+	if( p_vertex_weights == NULL )
+	{
+		char *p_ok_sectors[] = {	"NJ_Houses_North_09",
+									"NJ_Houses_North_10" };
+/*
+char *p_ok_sectors[] = { "CP_telephonepole43",
+								"CP_telephonepole51",
+								"CP_telephonepole52",
+								"NJ_TelePole_Node03",
+								"NJ_TelePole_Node02",
+								"NJ_TelePole_Node04",
+								"NJ_Telepole_00",
+								"NJ_Telepole_01",
+								"NJ_Telepole_02",
+								"NJ_Telepole_03",
+								"NJ_Telepole_04",
+								"NJ_Telepole_05",
+								"NJ_Telepole_06",
+								"NJ_Telepole_07",
+								"NJ_Telepole_08",
+								"NJ_Telepole_09",
+								"NJ_Telepole_12",
+								"NJ_Telepole_13",
+								"NJ_Telepole_14",
+								"NJ_Telepole_15",
+								"NJ_Telepole_16",
+								"NJ_Telepole_17",
+								"NJ_Telepole_18",
+								"NJ_Telepole_19",
+								"NJ_Telepole_20",
+								"NJ_Telepole_21",
+								"NJ_Telepole_22",
+								"NJ_Telepole_23",
+								"NJ_Telepole_24",
+								"NJ_Telepole_25",
+								"NJ_Telepole_26",
+								"CP_telephonepole11",
+								"NJ_Telepole_27",
+								"NJ_Telepole_28",
+								"NJ_Telepole_29",
+								"NJ_Telepole_30",
+								"NJ_Telepole_31",
+								"NJ_Telepole_32" };
+*/
+
+		for( int s = 0; s < ( sizeof( p_ok_sectors ) / sizeof( char* )); ++s )
+		{
+			uint32 checksum = Crc::GenerateCRCFromString( p_ok_sectors[s] );
+			if( checksum == m_checksum )
+			{
+//				Nx::CEngine::sGetImposterManager()->AddGeomToImposter( checksum, p_geom );
+				Nx::CEngine::sGetImposterManager()->AddGeomToImposter( 0xb88905d6UL, p_geom );
+			}
+		}
+	}
+#	endif
+
+	// Done with the raw vertex data.
+	delete[] p_vc_wibble_indices;
+	delete[] p_vertex_colors;
+	delete[] p_vertex_tex_coords;
+	delete[] p_vertex_bone_indices;
+	delete[] p_vertex_weights;
+	delete[] p_vertex_normals;
+	delete[] p_vertex_positions;
+	
+	return true;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CSector
+// and we will also have a CXboxSector, CNgcSector, even a CPcSector
+// maybe in the future we will have a CPS3Sector?
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSector::plat_set_visibility(uint32 mask)
+{
+/*
+	// Set values
+	m_visible = mask;
+
+	if( m_mesh_array )
+	{
+		for( uint m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+			p_mesh->SetVisibility( mask );
+		}
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSector::plat_set_active( bool on )
+{
+/*
+	// Set values
+	m_active = on;
+
+	if( m_mesh_array )
+	{
+		for( uint m = 0; m < m_num_mesh; ++m )
+		{
+			NxXbox::sMesh *p_mesh = m_mesh_array[m];
+			p_mesh->SetActive( on );
+		}
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSector::plat_set_color( Image::RGBA rgba )
+{
+/*
+	if( m_mesh_array == NULL )
+	{
+		return;
+	}
+
+	for( uint32 i = 0; i < m_num_mesh; ++i )
+	{
+		NxXbox::sMesh *p_mesh = m_mesh_array[i];
+		p_mesh->m_flags |= NxXbox::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+		p_mesh->m_material_color_override[0] = (float)rgba.r / 255.0f;
+		p_mesh->m_material_color_override[1] = (float)rgba.g / 255.0f;
+		p_mesh->m_material_color_override[2] = (float)rgba.b / 255.0f;
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSector::plat_clear_color( void )
+{
+/*
+	if( m_mesh_array == NULL )
+	{
+		return;
+	}
+
+	for( uint32 i = 0; i < m_num_mesh; ++i )
+	{
+		NxXbox::sMesh *p_mesh = m_mesh_array[i];
+		p_mesh->m_flags &= ~NxXbox::sMesh::MESH_FLAG_MATERIAL_COLOR_OVERRIDE;
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSector::plat_set_world_position( const Mth::Vector& pos )
+{
+/*
+	Mth::Vector new_offset = pos - m_pos_offset;
+
+	// Go through and adjust the individual meshes.
+	for( uint32 i = 0; i < m_num_mesh; ++i )
+	{
+		NxXbox::sMesh *p_mesh = m_mesh_array[i];
+		p_mesh->Move( new_offset );
+	}
+*/
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::CBBox &CXboxSector::plat_get_bounding_box( void ) const
+{
+	static Mth::CBBox dummy;
+	
+	//	return m_bbox;
+	return dummy;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const Mth::Vector &CXboxSector::plat_get_world_position( void ) const
+{
+	return m_pos_offset;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxSector::plat_set_shatter( bool on )
+{
+	if( on && mp_geom )
+	{
+		Shatter( mp_geom );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CSector *CXboxSector::plat_clone( bool instance, CScene *p_dest_scene )
+{
+	CXboxSector *p_xbox_sector = new CXboxSector();
+
+	/*
+
+
+	// Copies over much of the standard stuff, individual stuff will be overwritten later.
+	CopyMemory( p_xbox_sector, this, sizeof( CXboxSector ));
+
+	if( instance )
+	{
+		Dbg_Assert( 0 );
+	}
+	else
+	{
+		// Need to create a new set of meshes. First create the mesh pointer array...
+		p_xbox_sector->m_mesh_array = new NxXbox::sMesh*[m_num_mesh];
+
+		// ...then clone the meshes themselves.
+		for( uint32 i = 0; i < m_num_mesh; ++i )
+		{
+			p_xbox_sector->m_mesh_array[i] = m_mesh_array[i]->Clone();
+		}
+
+		// Grab a temporary workspace buffer.
+		NxXbox::sScene *p_scene								= ( static_cast( p_dest_scene ))->GetEngineScene();
+		NxXbox::sMesh **p_temp_opaque_mesh_buffer			= new NxXbox::sMesh*[p_scene->m_num_opaque_entries];
+		NxXbox::sMesh **p_temp_semitransparent_mesh_buffer	= new NxXbox::sMesh*[p_scene->m_num_semitransparent_entries];
+
+		// Copy meshes over into the temporary workspace buffer.
+		for( int i = 0; i < p_scene->m_num_opaque_entries; ++i )
+		{
+			p_temp_opaque_mesh_buffer[i] = p_scene->m_opaque_meshes[i];
+		}
+		for( int i = 0; i < p_scene->m_num_semitransparent_entries; ++i )
+		{
+			p_temp_semitransparent_mesh_buffer[i] = p_scene->m_semitransparent_meshes[i];
+		}
+
+		// Delete current mesh arrays.
+		delete [] p_scene->m_opaque_meshes;
+		delete [] p_scene->m_semitransparent_meshes;
+
+		// Include new meshes in count.
+		p_scene->CountMeshes( p_xbox_sector->m_num_mesh, p_xbox_sector->m_mesh_array );
+
+		// Allocate new mesh arrays.
+		p_scene->CreateMeshArrays();
+
+		// Copy old mesh data back in.
+		for( int i = 0; i < p_scene->m_num_opaque_entries; ++i )
+		{
+			p_scene->m_opaque_meshes[i] = p_temp_opaque_mesh_buffer[i];
+		}
+		for( int i = 0; i < p_scene->m_num_semitransparent_entries; ++i )
+		{
+			p_scene->m_semitransparent_meshes[i] = p_temp_semitransparent_mesh_buffer[i];
+		}
+
+		// Add new meshes.
+		p_scene->AddMeshes( p_xbox_sector->m_num_mesh, p_xbox_sector->m_mesh_array );
+
+		// Sort the meshes.
+		p_scene->SortMeshes();
+	}
+*/
+	return p_xbox_sector;
+
+}
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_nxsector.h b/Code/Gfx/XBox/p_nxsector.h
new file mode 100644
index 0000000..d16b74b
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxsector.h
@@ -0,0 +1,49 @@
+#ifndef	__GFX_P_NX_SECTOR_H__
+#define	__GFX_P_NX_SECTOR_H__
+
+#include 	
+#include 	
+#include 	"gfx\NxSector.h"
+#include 	"gfx\Image\ImageBasic.h"
+
+#include 	"gfx\xbox\p_nxscene.h"
+#include 	"gfx\xbox\nx\mesh.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Here's a machine specific implementation of the CSector
+class CXboxSector : public CSector
+{
+	public:
+									CXboxSector();
+
+	bool							LoadFromFile( void* p_file );
+	bool							LoadFromMemory( void **p_mem );
+
+	NxXbox::sScene					*GenerateScene( void );
+	
+	private:						// It's all private, as it is machine specific
+	virtual void					plat_set_color( Image::RGBA rgba );
+	virtual void					plat_clear_color( void );
+	virtual void					plat_set_visibility( uint32 mask );
+	virtual void					plat_set_active( bool on );
+	virtual void					plat_set_world_position( const Mth::Vector& pos );
+	virtual const Mth::CBBox		&plat_get_bounding_box( void ) const;
+	virtual const Mth::Vector		&plat_get_world_position( void ) const;
+	virtual void					plat_set_shatter( bool on );
+	virtual CSector					*plat_clone( bool instance, CScene *p_scene = NULL );
+
+	int								m_flags;
+
+	Mth::Vector						m_pos_offset;
+
+	Image::RGBA						m_rgba;
+};
+
+} // Namespace Nx  			
+
+#endif
\ No newline at end of file
diff --git a/Code/Gfx/XBox/p_nxtexman.cpp b/Code/Gfx/XBox/p_nxtexman.cpp
new file mode 100644
index 0000000..674050e
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxtexman.cpp
@@ -0,0 +1,67 @@
+/////////////////////////////////////////////////////////////////////////////
+// p_NxTexMan.cpp - Xbox platform specific interface to CTexMan
+//
+
+#include 
+
+#include "gfx/NxTexMan.h"
+#include "gfx/xbox/p_NxTexture.h"
+#include "gfx/xbox/nx/texture.h"
+
+namespace	Nx
+{
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+// Functions
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexDict* CTexDictManager::s_plat_load_texture_dictionary( const char *p_tex_dict_name, bool is_level_data, uint32 texDictOffset, bool is_skin, bool forceTexDictLookup )
+{
+	return new CXboxTexDict( p_tex_dict_name );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexDict* CTexDictManager::s_plat_load_texture_dictionary( uint32 checksum, uint32 *p_data, int data_size, bool is_level_data, uint32 texDictOffset, bool is_skin, bool forceTexDictLookup )
+{
+	CXboxTexDict *p_dict = new CXboxTexDict( checksum );
+	p_dict->LoadTextureDictionaryFromMemory( p_data );
+	return p_dict;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexDict* CTexDictManager::s_plat_create_texture_dictionary( uint32 checksum )
+{
+	return new CXboxTexDict( checksum );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CTexDictManager::s_plat_unload_texture_dictionary( CTexDict* p_tex_dict )
+{
+	delete p_tex_dict;
+	return true;
+}
+
+
+} 
+ 
diff --git a/Code/Gfx/XBox/p_nxtexture.cpp b/Code/Gfx/XBox/p_nxtexture.cpp
new file mode 100644
index 0000000..0904cdd
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxtexture.cpp
@@ -0,0 +1,698 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxTexture.cpp
+
+#include 	"Gfx/Nx.h"
+#include 	"Gfx/xbox/p_NxTexture.h"
+#include	"sys/file/filesys.h"
+
+namespace Nx
+{
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTexture
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxTexture::CXboxTexture() :  m_transparent( false ), mp_texture( NULL )
+{
+	m_num_mipmaps = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxTexture::~CXboxTexture()
+{
+	if( mp_texture )
+	{
+		delete mp_texture;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxTexture::SetEngineTexture( NxXbox::sTexture *p_texture )
+{
+	mp_texture	= p_texture;
+	m_checksum	= p_texture->Checksum;
+}
+
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexture::plat_load_texture( const char *p_texture_name, bool sprite, bool alloc_vram )
+{
+	char filename[256];
+
+	strcpy( filename, p_texture_name );
+	
+	// Append '.img.xbx' to the end.
+	strcat( filename, ".img.xbx" );
+
+	mp_texture = NxXbox::LoadTexture( filename );
+	
+	return mp_texture;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexture::plat_replace_texture( CTexture *p_texture )
+{								 	
+	CXboxTexture *p_xbox_texture = static_cast( p_texture );
+
+	// Go through and copy the texture.
+	NxXbox::sTexture *p_src	= p_xbox_texture->GetEngineTexture();
+	NxXbox::sTexture *p_dst	= GetEngineTexture();
+
+	if( p_dst->pD3DTexture )
+	{
+		p_dst->pD3DTexture->Release();
+	}
+	if( p_dst->pD3DPalette )
+	{
+		p_dst->pD3DPalette->Release();
+	}
+
+	D3DSURFACE_DESC	desc;
+	uint32			num_levels = p_src->pD3DTexture->GetLevelCount();
+	p_src->pD3DTexture->GetLevelDesc( 0, &desc );
+	if( D3D_OK != D3DDevice_CreateTexture(	desc.Width,
+											desc.Height,
+											num_levels,
+											0,
+											desc.Format,
+											0,
+											&p_dst->pD3DTexture ))
+	{
+		exit( 0 );
+	}
+
+	// Create and copy the palette if present.
+	if( p_src->pD3DPalette )
+	{
+		if( D3D_OK != D3DDevice_CreatePalette( D3DPALETTE_256, &p_dst->pD3DPalette ))
+		{
+			exit( 0 );
+		}
+		
+		D3DCOLOR *p_src_palette;
+		D3DCOLOR *p_dst_palette;
+		p_src->pD3DPalette->Lock( &p_src_palette, D3DLOCK_READONLY );
+		p_dst->pD3DPalette->Lock( &p_dst_palette, 0 );
+		CopyMemory( p_dst_palette, p_src_palette, sizeof( D3DCOLOR ) * 256 );
+	}
+	
+	for( uint32 l = 0; l < num_levels; ++l )
+	{
+		p_src->pD3DTexture->GetLevelDesc( l, &desc );
+
+		D3DLOCKED_RECT src_rect, dst_rect;
+		p_src->pD3DTexture->LockRect( l, &src_rect, NULL, D3DLOCK_READONLY );
+		p_dst->pD3DTexture->LockRect( l, &dst_rect, NULL, 0 );
+
+		CopyMemory( dst_rect.pBits, src_rect.pBits, desc.Size );
+	}
+	
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexture::plat_add_to_vram( void )
+{								 	
+	// Meaningless on Xbox, added to remove annoying debug stub output.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexture::plat_remove_from_vram( void )
+{								 	
+	// Meaningless on Xbox, added to remove annoying debug stub output.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint16	CXboxTexture::plat_get_width() const
+{
+	if( mp_texture )
+		return mp_texture->ActualWidth;
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint16	CXboxTexture::plat_get_height() const
+{
+	if( mp_texture )
+		return mp_texture->ActualHeight;
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CXboxTexture::plat_get_bitdepth() const
+{
+	if( mp_texture )
+		return mp_texture->TexelDepth;
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8	CXboxTexture::plat_get_num_mipmaps() const
+{
+	return m_num_mipmaps;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CXboxTexture::plat_is_transparent() const
+{
+	return m_transparent;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// Here's a machine specific implementation of CTexDict
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CXboxTexDict::CXboxTexDict( uint32 checksum ) : CTexDict( checksum )
+{
+	// Load nothing
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CXboxTexDict::CXboxTexDict( const char *p_tex_dict_name ) : CTexDict( p_tex_dict_name, true )
+{
+	 LoadTextureDictionary( p_tex_dict_name );	// the derived class will does this
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxTexDict::~CXboxTexDict()
+{
+	UnloadTextureDictionary();				// the derived class does this
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexDict::LoadTextureDictionary( const char *p_tex_dict_name )
+{
+	// Count the number of entries in the lookup table. If it is empty, it is safe
+	// to delete it and create a new, optimum sized one during the load process itself.
+	if( mp_texture_lookup )
+	{
+		int num_items = 0;
+		mp_texture_lookup->IterateStart();
+		while( mp_texture_lookup->IterateNext())
+			++num_items;
+
+		if( num_items == 0 )
+			mp_texture_lookup = Nx::LoadTextureFile( p_tex_dict_name, mp_texture_lookup, true );
+		else
+			Nx::LoadTextureFile( p_tex_dict_name, mp_texture_lookup );
+	}
+	else
+	{
+		Nx::LoadTextureFile( p_tex_dict_name, mp_texture_lookup );
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexDict::LoadTextureDictionaryFromMemory( void *p_mem )
+{
+	// Count the number of entries in the lookup table. If it is empty, it is safe
+	// to delete it and create a new, optimum sized one during the load process itself.
+	if( mp_texture_lookup )
+	{
+		int num_items = 0;
+		mp_texture_lookup->IterateStart();
+		while( mp_texture_lookup->IterateNext())
+			++num_items;
+
+		if( num_items == 0 )
+			mp_texture_lookup =	Nx::LoadTextureFileFromMemory( &p_mem, mp_texture_lookup, true );
+		else
+			Nx::LoadTextureFileFromMemory( &p_mem, mp_texture_lookup );
+	}
+	else
+	{
+		Nx::LoadTextureFileFromMemory( &p_mem, mp_texture_lookup );
+	}
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexDict::UnloadTextureDictionary( void )
+{
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexture *CXboxTexDict::plat_load_texture( const char *p_texture_name, bool sprite, bool alloc_vram )
+{
+	CXboxTexture *p_texture = new CXboxTexture;
+	if( !p_texture->LoadTexture( p_texture_name, sprite ))
+	{
+		Dbg_Error("Can't load texture %s", p_texture_name);
+	}
+	return p_texture;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CTexture *CXboxTexDict::plat_reload_texture( const char *p_texture_name )
+{
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexDict::plat_unload_texture( CTexture *p_texture )
+{
+	delete p_texture;
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxTexDict::plat_add_texture( CTexture *p_texture )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxTexDict::plat_remove_texture( CTexture *p_texture )
+{
+	return false;
+}
+
+
+
+#define MemoryRead( dst, size, num, src )	CopyMemory(( dst ), ( src ), (( num ) * ( size )));	\
+											( src ) += (( num ) * ( size ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Lst::HashTable* LoadTextureFileFromMemory( void **pp_mem, Lst::HashTable *p_texture_table, bool okay_to_rebuild_texture_table )
+{
+	uint8 *p_data = (uint8*)( *pp_mem );
+
+	// Read the texture file version and number of textures.
+	int version, num_textures;
+	MemoryRead( &version, sizeof( int ), 1, p_data );
+	MemoryRead( &num_textures, sizeof( int ), 1, p_data );
+
+	// If allowed, rebuild the texture table to the optimum size, using the same heap as the original table.
+	if( okay_to_rebuild_texture_table )
+	{
+		uint32 optimal_table_size	= num_textures * 2;
+		uint32 test					= 2;
+		uint32 size					= 1;
+		for( ;; test <<= 1, ++size )
+		{
+			// Check if this iteration of table size is sufficient, or if we have hit the maximum size.
+			if(( optimal_table_size <= test ) || ( size >= 12 ))
+			{
+				Mem::Allocator::BlockHeader*	p_bheader	= Mem::Allocator::BlockHeader::sRead( p_texture_table );
+				Mem::Allocator*					p_allocater	= p_bheader->mpAlloc;
+
+				delete p_texture_table;
+
+				Mem::Manager::sHandle().PushContext( p_allocater );
+				p_texture_table = new Lst::HashTable( size );
+				Mem::Manager::sHandle().PopContext();
+				break;
+			}
+		}
+	}
+
+	for( int t = 0; t < num_textures; ++t )
+	{
+		// Create the engine level texture.
+		NxXbox::sTexture *p_texture = new NxXbox::sTexture;
+
+		uint32 base_width, base_height, levels, texel_depth, palette_depth, dxt, palette_size;
+		MemoryRead( &p_texture->Checksum,	sizeof( uint32 ), 1, p_data );
+		MemoryRead( &base_width,			sizeof( uint32 ), 1, p_data );
+		MemoryRead( &base_height,			sizeof( uint32 ), 1, p_data );
+		MemoryRead( &levels,				sizeof( uint32 ), 1, p_data );
+		MemoryRead( &texel_depth,			sizeof( uint32 ), 1, p_data );
+		MemoryRead( &palette_depth,			sizeof( uint32 ), 1, p_data );
+		MemoryRead( &dxt,					sizeof( uint32 ), 1, p_data );
+		MemoryRead( &palette_size,			sizeof( uint32 ), 1, p_data );
+
+		p_texture->BaseWidth	= (uint16)base_width;
+		p_texture->BaseHeight	= (uint16)base_height;
+		p_texture->Levels		= (uint8)levels;
+		p_texture->TexelDepth	= (uint8)texel_depth;
+		p_texture->PaletteDepth	= (uint8)palette_depth;
+		p_texture->DXT			= (uint8)dxt;
+		
+		D3DFORMAT	texture_format;
+		if( p_texture->DXT > 0 )
+		{
+			if(( p_texture->DXT == 1 ) || ( p_texture->DXT == 2 ))
+			{
+				texture_format = D3DFMT_DXT1;
+			}
+			else if( p_texture->DXT == 5 )
+			{
+				texture_format = D3DFMT_DXT5;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+		}
+		else if( p_texture->TexelDepth == 8 )
+		{
+			texture_format = D3DFMT_P8;
+		}
+		else if( p_texture->TexelDepth == 16 )
+		{
+			texture_format = D3DFMT_A1R5G5B5;	// Could also be X1R5G5B5;
+		}
+		else if( p_texture->TexelDepth == 32 )
+		{
+			texture_format = D3DFMT_A8R8G8B8;
+		}
+		else
+		{
+			Dbg_Assert( 0 );
+		}
+		
+		if( D3D_OK != D3DDevice_CreateTexture(	p_texture->BaseWidth, p_texture->BaseHeight, p_texture->Levels,	0, texture_format, 0, &p_texture->pD3DTexture ))
+		{
+			Dbg_Assert( 0 );
+		}
+
+		if( palette_size > 0 )
+		{
+			// Create and lock the palette.
+			if( D3D_OK != D3DDevice_CreatePalette(	palette_size == ( 256 * sizeof( D3DCOLOR )) ? D3DPALETTE_256 : D3DPALETTE_32, &p_texture->pD3DPalette ))
+			{
+				Dbg_Assert( 0 );
+			}
+			else
+			{
+				D3DCOLOR* p_colors;
+				if( D3D_OK != p_texture->pD3DPalette->Lock( &p_colors, 0 ))
+				{
+					Dbg_Assert( 0 );
+				}
+				else
+				{
+					// Read in palette data.
+					MemoryRead( p_colors, palette_size, 1, p_data );
+				}
+			}
+		}
+		else
+		{
+			p_texture->pD3DPalette = NULL;
+		}
+
+		for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+		{
+			uint32 texture_level_data_size;
+			MemoryRead( &texture_level_data_size, sizeof( uint32 ), 1, p_data );
+
+			D3DLOCKED_RECT locked_rect;
+			if( D3D_OK != p_texture->pD3DTexture->LockRect( mip_level, &locked_rect, NULL, 0 ))
+			{
+				Dbg_Assert( 0 );
+			}
+			else
+			{
+				MemoryRead( locked_rect.pBits, texture_level_data_size, 1, p_data );
+			}
+		}
+
+		// Add this texture to the table.
+		Nx::CXboxTexture *p_xbox_texture = new Nx::CXboxTexture();
+		p_xbox_texture->SetEngineTexture( p_texture );
+		p_texture_table->PutItem( p_texture->Checksum, p_xbox_texture );
+	}
+	return p_texture_table;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Lst::HashTable* LoadTextureFile( const char *Filename, Lst::HashTable *p_texture_table, bool okay_to_rebuild_texture_table )
+{
+	// Open the texture file.
+	void *p_FH = File::Open( Filename, "rb" );
+	if( !p_FH )
+	{
+		Dbg_Message( "Couldn't open texture file %s\n", Filename );
+		return p_texture_table;
+	}
+
+	// Read the texture file version and number of textures.
+	int version, num_textures;
+	File::Read( &version, sizeof( int ), 1, p_FH );
+	File::Read( &num_textures, sizeof( int ), 1, p_FH );
+
+	// If allowed, rebuild the texture table to the optimum size, using the same heap as the original table.
+	if( okay_to_rebuild_texture_table )
+	{
+		uint32 optimal_table_size	= num_textures * 2;
+		uint32 test					= 2;
+		uint32 size					= 1;
+		for( ;; test <<= 1, ++size )
+		{
+			// Check if this iteration of table size is sufficient, or if we have hit the maximum size.
+			if(( optimal_table_size <= test ) || ( size >= 12 ))
+			{
+				Mem::Allocator::BlockHeader*	p_bheader	= Mem::Allocator::BlockHeader::sRead( p_texture_table );
+				Mem::Allocator*					p_allocater	= p_bheader->mpAlloc;
+
+				delete p_texture_table;
+
+				Mem::Manager::sHandle().PushContext( p_allocater );
+				p_texture_table = new Lst::HashTable( size );
+				Mem::Manager::sHandle().PopContext();
+				break;
+			}
+		}
+	}
+
+	for( int t = 0; t < num_textures; ++t )
+	{
+		// Create the engine level texture.
+		NxXbox::sTexture *p_texture = new NxXbox::sTexture;
+
+		uint32 base_width, base_height, levels, texel_depth, palette_depth, dxt, palette_size;
+		File::Read( &p_texture->Checksum,	sizeof( uint32 ), 1, p_FH );
+		File::Read( &base_width,			sizeof( uint32 ), 1, p_FH );
+		File::Read( &base_height,			sizeof( uint32 ), 1, p_FH );
+		File::Read( &levels,				sizeof( uint32 ), 1, p_FH );
+		File::Read( &texel_depth,			sizeof( uint32 ), 1, p_FH );
+		File::Read( &palette_depth,			sizeof( uint32 ), 1, p_FH );
+		File::Read( &dxt,					sizeof( uint32 ), 1, p_FH );
+		File::Read( &palette_size,			sizeof( uint32 ), 1, p_FH );
+
+		p_texture->BaseWidth	= (uint16)base_width;
+		p_texture->BaseHeight	= (uint16)base_height;
+		p_texture->Levels		= (uint8)levels;
+		p_texture->TexelDepth	= (uint8)texel_depth;
+		p_texture->PaletteDepth	= (uint8)palette_depth;
+		p_texture->DXT			= (uint8)dxt;
+		
+		D3DFORMAT	texture_format;
+		if( p_texture->DXT > 0 )
+		{
+			if(( p_texture->DXT == 1 ) || ( p_texture->DXT == 2 ))
+			{
+				texture_format = D3DFMT_DXT1;
+			}
+			else if( p_texture->DXT == 5 )
+			{
+				texture_format = D3DFMT_DXT5;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+		}
+		else if( p_texture->TexelDepth == 8 )
+		{
+			texture_format = D3DFMT_P8;
+		}
+		else if( p_texture->TexelDepth == 16 )
+		{
+			texture_format = D3DFMT_A1R5G5B5;	// Could also be X1R5G5B5;
+		}
+		else if( p_texture->TexelDepth == 32 )
+		{
+			texture_format = D3DFMT_A8R8G8B8;
+		}
+		else
+		{
+			Dbg_Assert( 0 );
+		}
+		
+		if( D3D_OK != D3DDevice_CreateTexture(	p_texture->BaseWidth, p_texture->BaseHeight, p_texture->Levels,	0, texture_format, 0, &p_texture->pD3DTexture ))
+		{
+			Dbg_Assert( 0 );
+		}
+
+		if( palette_size > 0 )
+		{
+			// Create and lock the palette.
+			if( D3D_OK != D3DDevice_CreatePalette(	palette_size == ( 256 * sizeof( D3DCOLOR )) ? D3DPALETTE_256 : D3DPALETTE_32, &p_texture->pD3DPalette ))
+			{
+				Dbg_Assert( 0 );
+			}
+			else
+			{
+				D3DCOLOR* p_colors;
+				if( D3D_OK != p_texture->pD3DPalette->Lock( &p_colors, 0 ))
+				{
+					Dbg_Assert( 0 );
+				}
+				else
+				{
+					// Read in palette data.
+					File::Read( p_colors, palette_size, 1, p_FH );
+				}
+			}
+		}
+		else
+		{
+			p_texture->pD3DPalette = NULL;
+		}
+
+		for( uint32 mip_level = 0; mip_level < p_texture->Levels; ++mip_level )
+		{
+			uint32 texture_level_data_size;
+			File::Read( &texture_level_data_size, sizeof( uint32 ), 1, p_FH );
+
+			D3DLOCKED_RECT locked_rect;
+			if( D3D_OK != p_texture->pD3DTexture->LockRect( mip_level, &locked_rect, NULL, 0 ))
+			{
+				Dbg_Assert( 0 );
+			}
+			else
+			{
+				File::Read( locked_rect.pBits, texture_level_data_size, 1, p_FH );
+			}
+		}
+
+		// Add this texture to the table.
+		Nx::CXboxTexture *p_xbox_texture = new Nx::CXboxTexture();
+		p_xbox_texture->SetEngineTexture( p_texture );
+		p_texture_table->PutItem( p_texture->Checksum, p_xbox_texture );
+	}
+	File::Close( p_FH );
+
+	return p_texture_table;
+}
+
+
+} // Namespace Nx  			
+				
+				
diff --git a/Code/Gfx/XBox/p_nxtexture.h b/Code/Gfx/XBox/p_nxtexture.h
new file mode 100644
index 0000000..452a2ff
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxtexture.h
@@ -0,0 +1,107 @@
+///////////////////////////////////////////////////////////////////////////////
+// p_NxScene.h
+
+#ifndef	__GFX_P_NX_TEXTURE_H__
+#define	__GFX_P_NX_TEXTURE_H__
+
+#include 	"Gfx/NxTexture.h"
+#include 	"Gfx/xbox/nx/texture.h"
+#include 	"Gfx/xbox/nx/material.h"
+
+namespace Nx
+{
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Private classes
+//
+// Machine specific implementation of the CTexture
+class	CXboxTexture : public CTexture
+{
+public:
+								CXboxTexture();
+	virtual						~CXboxTexture();
+
+	NxXbox::sTexture			*GetEngineTexture() const;
+	void						SetEngineTexture( NxXbox::sTexture *p_texture );
+
+
+private:		// It's all private, as it is machine specific
+	virtual bool				plat_load_texture( const char *p_texture_name, bool sprite, bool alloc_vram );
+	virtual bool				plat_replace_texture( CTexture *p_texture );
+	virtual bool				plat_add_to_vram( void );
+	virtual bool				plat_remove_from_vram( void );
+
+	virtual uint16				plat_get_width() const;
+	virtual uint16				plat_get_height() const;
+	virtual uint8				plat_get_bitdepth() const;
+	virtual uint8				plat_get_num_mipmaps() const;
+	virtual bool				plat_is_transparent() const;
+
+	uint8						m_num_mipmaps;
+	bool						m_transparent;
+
+	// The actual data in the engine
+	NxXbox::sTexture *			mp_texture;
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline NxXbox::sTexture	*CXboxTexture::GetEngineTexture() const
+{
+	return mp_texture;
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////////
+// Machine specific implementation of the CMaterial
+class	CXboxMaterial : public CMaterial
+{
+public:
+								CXboxMaterial();
+	virtual						~CXboxMaterial();
+
+private:
+	Image::RGBA					plat_get_rgba() const;
+	void						plat_set_rgba(Image::RGBA rgba);
+	void						plat_set_texture();
+
+	Image::RGBA					m_rgba;
+
+	// The actual data in the engine
+	NxXbox::sMaterial *			mp_material;
+};
+
+//////////////////////////////////////////////////////////////////////////////////
+// Machine specific implementation of the CTexDict
+class	CXboxTexDict : public CTexDict
+{
+public:
+								CXboxTexDict( uint32 checksum );		// loads nothing
+								CXboxTexDict(const char *p_tex_dict_name);
+	virtual						~CXboxTexDict();
+
+	bool						LoadTextureDictionary( const char *p_tex_dict_name );
+	bool						LoadTextureDictionaryFromMemory( void *p_mem );
+	bool						UnloadTextureDictionary();
+
+private:
+
+	// Platform-specific calls
+	virtual CTexture *			plat_load_texture( const char *p_texture_name, bool sprite, bool alloc_vram );
+	virtual CTexture *			plat_reload_texture( const char *p_texture_name );
+	virtual bool				plat_unload_texture( CTexture *p_texture );
+	virtual void				plat_add_texture( CTexture *p_texture );
+	virtual bool				plat_remove_texture( CTexture *p_texture );
+};
+
+Lst::HashTable*	LoadTextureFile( const char *Filename, Lst::HashTable *p_texture_table, bool okay_to_rebuild_texture_table = false );
+Lst::HashTable*	LoadTextureFileFromMemory( void **pp_mem, Lst::HashTable *p_texture_table, bool okay_to_rebuild_texture_table = false );
+
+
+} // Namespace Nx  			
+
+#endif
diff --git a/Code/Gfx/XBox/p_nxweather.cpp b/Code/Gfx/XBox/p_nxweather.cpp
new file mode 100644
index 0000000..2f4ad04
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxweather.cpp
@@ -0,0 +1,1086 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_NxWeather.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  6/19/2003
+//****************************************************************************
+
+#include 
+#include "gfx/xbox/nx/render.h"
+
+#include 
+#include 
+#include "gfx/xbox/nx/mesh.h"
+#include "gfx/xbox/p_nxweather.h"
+
+#include 
+#include 
+
+#include 
+#include 
+#include "gfx/nx.h"
+
+#include "gfx/xbox/nx/sprite.h"
+
+#define FEELER_START			50000.0f
+#define NUM_LINES_PER_BATCH		80
+#define NUM_SPRITES_PER_BATCH	80
+
+#define PRECISION_SHIFT			4
+
+#define RENDER_DIST				16
+#define SEQ_START				411		// Between 1 and 4095.
+#define SEQ_MASK				0x0240
+
+unsigned char grid_bytes[70*1024];
+
+//1-3: 0x03
+//1-7: 0x06
+//1-15: 0x0C
+//1-31: 0x14
+//1-63: 0x30
+//1-127: 0x60
+//1-255: 0xB8
+//1-511: 0x0110
+//1-1023: 0x0240
+//1-2047: 0x0500
+//1-4095: 0x0CA0
+//1-8191: 0x1B00
+//1-16383: 0x3500
+//1-32767: 0x6000
+//1-65535: 0xB400
+//0x00012000
+//0x00020400
+//0x00072000
+//0x00090000
+//0x00140000
+//0x00300000
+//0x00400000
+//0x00D80000
+//0x01200000
+//0x03880000
+//0x07200000
+//0x09000000
+//0x14000000
+//0x32800000
+//0x48000000
+//0xA3000000
+
+
+namespace Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxWeather::CXboxWeather()
+{
+	mp_roof_height_index	= NULL;
+	mp_rain_texture			= NULL;
+
+	m_rain_blend			= NxXbox::GetBlendMode( CRCD( 0xa86285a1, "fixadd" )) | 0x80000000UL;
+	m_splash_blend			= NxXbox::GetBlendMode( CRCD( 0xa86285a1, "fixadd" )) | 0x80000000UL;
+	m_snow_blend			= NxXbox::GetBlendMode( CRCD( 0xa86285a1, "fixadd" )) | 0x80000000UL;
+
+	m_rain_rate				= 0.0f;
+	m_splash_rate			= 0.0f;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxWeather::~CXboxWeather()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_update_grid( void )
+{
+	m_system_active = false;
+
+	// Get super sector manager.
+	SSec::Manager *ss_man;
+	Mth::Line line;
+	ss_man = Nx::CEngine::sGetNearestSuperSectorManager( line );		// Line is ignored, 1st manager is returned.
+	if ( !ss_man ) return;
+
+	// Calculate the size of the world in cels.
+	int min_x = ( ((int)ss_man->GetWorldBBox()->GetMin()[X]) / WEATHER_CEL_SIZE ) - 1;
+	int max_x = ( ((int)ss_man->GetWorldBBox()->GetMax()[X]) / WEATHER_CEL_SIZE ) + 1;
+	int min_z = ( ((int)ss_man->GetWorldBBox()->GetMin()[Z]) / WEATHER_CEL_SIZE ) - 1;
+	int max_z = ( ((int)ss_man->GetWorldBBox()->GetMax()[Z]) / WEATHER_CEL_SIZE ) + 1;
+
+	// Define a maximum...
+	if(( max_x - min_x ) > 350 )
+	{
+		int wid = ( max_x - min_x );
+		int excess = ( wid - 350 );
+		min_x += ( excess / 2 );
+		max_x -= ( excess / 2 );
+	}
+
+	if(( max_z - min_z ) > 300 )
+	{
+		int wid = ( max_z - min_z );
+		int excess = ( wid - 300 );
+		min_z += ( excess / 2 );
+		max_z -= ( excess / 2 );
+	}
+
+	// This is the actual width.
+	m_width = ( max_x - min_x ) + 1;
+	m_height = ( max_z - min_z ) + 1;
+
+	m_min_x = (float)( min_x * WEATHER_CEL_SIZE );
+	m_min_z = (float)( min_z * WEATHER_CEL_SIZE );
+
+	// Temporary buffer for the raw array.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	unsigned char * p8 = new unsigned char[m_width*m_height];
+	Mem::Manager::sHandle().PopContext();
+
+	// Go through and get the height for each cel corner.
+	CFeeler feeler;
+	int num_heights = 0;
+	int xx, zz;
+	for ( zz = min_z; zz <= max_z; zz++ )
+	{
+		for ( xx = min_x; xx <= max_x; xx++ )
+		{
+			// The cel to fill.
+			int cel = ( ( zz - min_z ) * m_width ) + ( xx - min_x );
+
+			// The position to check.
+			Mth::Vector pos;
+			pos[X] = (float)( xx * WEATHER_CEL_SIZE );
+			pos[Y] = FEELER_START;
+			pos[Z] = (float)( zz * WEATHER_CEL_SIZE );
+			pos[W] = 1.0f;
+
+			feeler.SetStart( pos );
+			pos[Y] = -FEELER_START;
+			feeler.SetEnd( pos );
+
+			// Get the y position.
+			sint32 y;
+			if ( feeler.GetCollision( false, false ) )		// No movables, nearest collision.
+			{
+				y = (sint32)( feeler.GetPoint()[Y] * SUB_INCH_PRECISION );
+			}
+			else
+			{
+				y = (sint32)( FEELER_START * SUB_INCH_PRECISION );
+			}
+
+			// See if a close enough y pos already exists.
+			int found = -1;
+			for ( int lp = 0; lp < num_heights; lp++ )
+			{
+				if ( abs( ( m_roof_height[lp] - y ) ) < HEIGHT_TOLERANCE )
+				{
+					found = lp;
+					break;
+				}
+			}
+
+			// Fill in the cel.
+			if ( found != -1 )
+			{
+				// Existing height.
+				p8[cel] = found;
+			}
+			else
+			{
+				// New height.
+				p8[cel] = num_heights;
+				m_roof_height[num_heights] = y;
+				num_heights++;
+			}
+		}
+	}
+
+	// Work out highest height for each cel.
+	for ( zz = min_z; zz <= max_z; zz++ )
+	{
+		for ( xx = min_x; xx <= max_x; xx++ )
+		{
+			// The cel to fill.
+			int cel = ( ( zz - min_z ) * m_width ) + ( xx - min_x );
+			int celx = ( ( zz - min_z ) * m_width ) + ( ( xx + 1 ) - min_x );
+			int celz = ( ( ( zz + 1 ) - min_z ) * m_width ) + ( xx - min_x );
+			int celxz = ( ( ( zz + 1 ) - min_z ) * m_width ) + ( ( xx + 1 ) - min_x );
+
+			if ( m_roof_height[p8[celx]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celx];
+			if ( m_roof_height[p8[celz]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celz];
+			if ( m_roof_height[p8[celxz]] > m_roof_height[p8[cel]] ) p8[cel] = p8[celxz];
+		}
+	}
+
+	// Create a sparse array.
+	mp_roof_row = (sRowEntry *)grid_bytes;
+	mp_roof_height_index = (unsigned char *)&mp_roof_row[m_height];
+
+	// 0 = offset
+	// 1 = width
+	// 2 = index
+
+	unsigned short index = 0;
+	for ( zz = 0; zz <= m_height; zz++ )
+	{
+		unsigned short start = 0;
+		unsigned short end = m_width - 1;
+
+		// Scan to find the start.
+		bool start_set = false;
+		for ( xx = 0; xx < m_width; xx++ )
+		{
+			int cel = ( zz * m_width ) + xx;
+
+			if ( m_roof_height[p8[cel]] != (sint32)( FEELER_START * SUB_INCH_PRECISION ) )
+			{
+				if ( !start_set )
+				{
+					// Set start value.
+					start = xx;
+					start_set = true;
+				}
+				else
+				{
+					// Set end value.
+					end = xx;
+				}
+
+			}
+		}
+
+		// Copy data & set row entry.
+		if ( start < end )
+		{
+			mp_roof_row[zz].start = start;
+			mp_roof_row[zz].end = end;
+			mp_roof_row[zz].index = index;
+			for ( xx = start; xx <= end ; xx++ )
+			{
+				int cel = ( zz * m_width ) + xx;
+
+				mp_roof_height_index[index] = p8[cel];
+				index++;
+			}
+		}
+		else
+		{
+			// Row doesn't exist.
+			mp_roof_row[zz].start = 16384;
+			mp_roof_row[zz].end = 0;
+			mp_roof_row[zz].index = 0;
+		}
+	}
+
+	delete p8;
+
+	int new_size = ( m_height * 6 ) + index;
+
+	Dbg_Message( "Grid Size: Old: %d New: %d Num Heights: %d\n", ( m_width * m_height ), new_size, num_heights );
+
+	// Set all drip time counters to 255.
+	// 255 means the drop won't be rendered.
+	// Setting to anything other than 255 will mean that it will increment to 255 and stop.
+	for ( int lp = 0; lp < ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ); lp++ )
+	{
+		m_drop_time[lp] = 255;
+		m_x_offset[lp] = Mth::Rnd( 255 );
+		m_z_offset[lp] = Mth::Rnd( 255 );
+	}
+	m_active_drops = 0;
+
+	m_seq = SEQ_START;
+
+	// Get the texture.
+	Nx::CTexture*		p_texture;
+	Nx::CXboxTexture*	p_xbox_texture;
+	mp_rain_texture = NULL;
+
+	// Set Rain Texture.
+	p_texture		= Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( CRCD( 0x45c7eb0f,"splash" ));
+	p_xbox_texture	= static_cast( p_texture );
+	if( p_xbox_texture )
+	{
+		mp_rain_texture = p_xbox_texture->GetEngineTexture();
+	}
+
+	// Set Snow Texture.
+	p_texture = Nx::CTexDictManager::sp_particle_tex_dict->GetTexture( CRCD( 0xc97c5a4c,"snow" ));
+	p_xbox_texture = static_cast( p_texture );
+	if( p_xbox_texture )
+	{
+		mp_snow_texture = p_xbox_texture->GetEngineTexture();
+	}
+
+	m_system_active = true;
+
+	// Zero out the splashes.
+	for( int sp = 0; sp < NUM_SPLASH_ACTIVE; sp++ )
+	{
+		m_splash_current_life[sp] = 0;
+	}
+	m_current_splash = 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_process( float delta_time )
+{
+	if( !m_system_active ) return;
+
+	if( m_raining )
+	{
+		// It's raining.
+		float rate = m_rain_drops_per_frame;
+		if( rate > (float)(( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_rain_frames ))
+		{
+			rate = (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_rain_frames );
+		}
+
+		float last = m_rain_rate;
+		m_rain_rate += rate;
+		int new_drops = (int)m_rain_rate - (int)last;
+
+		for( int lp = 0; lp < new_drops; lp++ )
+		{
+			// If this cel is not inactive, we caught up with ourselves.
+			if ( m_drop_time[m_seq] != 255 ) break;
+
+			// Setup the drop.
+			m_drop_time[m_seq] = ( 254 - m_rain_frames );
+			m_x_offset[m_seq] = Mth::Rnd( 255 );
+			m_z_offset[m_seq] = Mth::Rnd( 255 );
+			m_active_drops++;
+
+			// Calculate next sequence value. Notice hack to get entry 0 into the sequence.
+			switch ( m_seq )
+			{
+				case SEQ_START:
+					m_seq = 0;
+					break;
+				case 0:
+					m_seq = SEQ_START;
+					// Fall through to default case...
+				default:
+					if ( m_seq & 1 )
+					{
+						m_seq = ( m_seq >> 1 ) ^ SEQ_MASK;
+					}
+					else
+					{
+						m_seq = ( m_seq >> 1 );
+					}
+					break;
+			}
+		}
+	}
+	else
+	{
+		// It's snowing.
+		float rate = m_snow_flakes_per_frame;
+		if ( rate > (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_snow_frames ) ) rate = (float)( ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ) / m_snow_frames );
+
+		float last = m_snow_rate;
+		m_snow_rate += rate;
+		int new_drops = (int)m_snow_rate - (int)last;
+
+		for ( int lp = 0; lp < new_drops; lp++ )
+		{
+			// If this cel is not inactive, we caught up with ourselves.
+			if ( m_drop_time[m_seq] != 255 ) break;
+
+			// Setup the drop.
+			m_drop_time[m_seq] = ( 254 - m_snow_frames );
+			m_x_offset[m_seq] = Mth::Rnd( 255 );
+			m_z_offset[m_seq] = Mth::Rnd( 255 );
+			m_active_drops++;
+
+			// Calculate next sequence value. Notice hack to get entry 0 into the sequence.
+			switch ( m_seq )
+			{
+				case SEQ_START:
+				{
+					m_seq = 0;
+					break;
+				}
+				case 0:
+				{
+					m_seq = SEQ_START;
+					// Fall through to default case...
+				}
+				default:
+				{
+					if ( m_seq & 1 )
+					{
+						m_seq = ( m_seq >> 1 ) ^ SEQ_MASK;
+					}
+					else
+					{
+						m_seq = ( m_seq >> 1 );
+					}
+					break;
+				}
+			}
+		}
+	}
+}
+
+
+
+inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_render_snow( float skx, float skz )
+{
+	// Early out if no drops to draw.
+	if( !m_active_drops ) return;
+
+	// Get skater position.
+	int x = (int)(( skx - m_min_x ) / WEATHER_CEL_SIZE );
+	int z = (int)(( skz - m_min_z ) / WEATHER_CEL_SIZE );
+
+	// Calculate area to render.
+	int sx = x - RENDER_DIST;
+	int ex = x + DROP_SIZE;	//RENDER_DIST;
+	int sz = z - RENDER_DIST;
+	int ez = z + DROP_SIZE;	//RENDER_DIST;
+
+	// Clip z values.
+	if( ez < 0 ) return;
+	if( sz > ( m_height - 1 )) return;
+
+	// Pointsprites use texture stage 3 for some reason...
+	NxXbox::set_blend_mode( m_snow_blend );
+	NxXbox::set_render_state( RS_ALPHACUTOFF, 0 );
+	NxXbox::set_render_state( RS_UVADDRESSMODE3, 0x00000000UL );
+	mp_snow_texture->Set( 3 );
+
+	// Set up point sprite rendering.
+	D3DDevice_SetRenderState( D3DRS_POINTSPRITEENABLE,	TRUE );
+	D3DDevice_SetRenderState( D3DRS_POINTSCALEENABLE,	TRUE );
+	D3DDevice_SetRenderState( D3DRS_POINTSIZE,			FtoDW( m_snow_size ));
+	D3DDevice_SetRenderState( D3DRS_POINTSCALE_A,		FtoDW( 0.00f ));
+	D3DDevice_SetRenderState( D3DRS_POINTSCALE_B,		FtoDW( 0.00f ));
+	D3DDevice_SetRenderState( D3DRS_POINTSCALE_C,		FtoDW( 1.00f ));
+
+	NxXbox::set_pixel_shader( PixelShaderPointSprite );
+	NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+
+	float minx = m_min_x;
+	float minz = m_min_z;
+	
+	// Calculate drop height list.
+	int lp;
+	float y_off[256];
+	for( lp = ( 254 - m_snow_frames ); lp < 256; lp++ )
+	{
+		y_off[lp] = m_snow_height - (((float)(( lp - 254 ) + m_snow_frames ) / (float)m_snow_frames ) * m_snow_height );
+	}
+
+	// Calculate xz offset list.
+	float xz_off[256];
+	for( lp = 0; lp < 256; lp++ )
+	{
+		xz_off[lp] = ((float)lp * (float)( WEATHER_CEL_SIZE / 2 )) / 255.0f;
+	}
+
+	// Calculate sine wave.
+	float sin_off[256];
+
+	for( lp = 0; lp < 256; lp++ )
+	{
+		sin_off[lp] = sinf( Mth::PI * 2.0f * ((float)lp / 256.0f )) * ( WEATHER_CEL_SIZE / 2 );
+	}
+
+	// Get Xbox format color.
+	uint32 snow_color = ((uint32)m_snow_color.a << 24 ) | ((uint32)m_snow_color.r << 16 ) | ((uint32)m_snow_color.g << 8 ) | ((uint32)m_snow_color.b << 0 );
+
+	int	flakes = 0;
+
+	DWORD* p_push				= NULL;
+	DWORD* p_push_encode_fixup	= NULL;
+	uint32 dwords_written		= 0;
+	uint32 total_dwords_written	= 2;
+
+	for( int lzz = sz; lzz <= ez; lzz++ )
+	{
+		int zz = ( lzz < 0 ) ? 0 : ( lzz > ( m_height - 1 ) ) ? ( m_height - 1 ) : lzz;
+
+		if( mp_roof_row[zz].start == 16384 ) continue;
+
+		float vx = ((float)sx * (float)WEATHER_CEL_SIZE ) + minx;
+		float vz = ((float)zz * (float)WEATHER_CEL_SIZE ) + minz;
+
+		int cel = mp_roof_row[zz].index + ( sx - mp_roof_row[zz].start );
+
+		int drop_cel_z = (( zz & ( DROP_SIZE - 1 )) << DROP_SIZE_SHIFT );
+
+		for( int lxx = sx; lxx <= ex; lxx++, cel++ )
+		{
+			int xx = ( lxx > mp_roof_row[zz].start ) ? ( ( lxx < mp_roof_row[zz].end ) ? lxx : mp_roof_row[zz].end ) : mp_roof_row[zz].start;
+
+			// Get the current drop value. Skip this one if it's inactive.
+			int drop_cel = drop_cel_z + ( xx & ( DROP_SIZE - 1 ));
+
+			vx += (float)WEATHER_CEL_SIZE;
+			float vy = (float)( m_roof_height[mp_roof_height_index[cel]] >> PRECISION_SHIFT );
+
+			for( int d = 0; d < DROP_LAYERS; d++, drop_cel += ( DROP_SIZE * DROP_SIZE ))
+			{
+				if( m_drop_time[drop_cel] == 255 )
+					continue;
+
+				// Create the position for rendering.
+				float v0x = vx + xz_off[m_x_offset[drop_cel]] + sin_off[(m_z_offset[drop_cel]+m_drop_time[drop_cel])&255];
+				float v0y = vy + y_off[m_drop_time[drop_cel]] + m_snow_size;
+				float v0z = vz + xz_off[m_z_offset[drop_cel]] + sin_off[(m_x_offset[drop_cel]+m_drop_time[drop_cel])&255];
+
+				if( flakes == 0 )
+				{
+					flakes = NUM_SPRITES_PER_BATCH;
+				}
+
+				// Grab the push buffer if we don't have it yet.
+				if( p_push == NULL )
+				{
+					// Grab 32k of push buffer.	
+					p_push = D3DDevice_BeginPush( 32 * 1024 / 4 );
+
+					// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+					// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+					// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+					p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+					p_push[1]	= D3DPT_POINTLIST;
+					p_push		+= 2;
+
+					// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+					// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+					// As yet, we don't know how many dwords we will write, so save a pointer to the current push buffer location
+					// so we can fix up this value at the end.
+					p_push_encode_fixup = p_push;
+					++p_push;
+				}
+
+				p_push[0]				= *((uint32*)&v0x );
+				p_push[1]				= *((uint32*)&v0y );
+				p_push[2]				= *((uint32*)&v0z );
+				p_push[3]				= snow_color;
+
+				p_push					+= 4;
+				dwords_written			+= 4;
+				total_dwords_written	+= 4;
+
+				// Check for hitting the max dwords for this encode block, in which case we need to end this block
+				// and start another one.
+				if( dwords_written >= 2044 )
+				{
+					// Now we know exactly how many dwords written, fix-up our earlier entry.
+					p_push_encode_fixup[0]	= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, dwords_written );
+
+					// Are we approaching the end of this push buffer? If so, close it out and start a new one.
+					if( total_dwords_written > 6000 )
+					{
+						// End the push buffer.
+						total_dwords_written	= 0;
+						p_push[0]				= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+						p_push[1]				= 0;
+						p_push					+= 2;
+						D3DDevice_EndPush( p_push );
+
+						p_push					= NULL;
+					}
+					else
+					{
+						// As yet, we don't know how many dwords we will write, so save a pointer to the current push buffer location
+						// so we can fix up this value at the end.
+						p_push_encode_fixup		= p_push;
+						++p_push;
+					}
+
+					dwords_written = 0;
+				}
+
+				flakes--;
+			}
+		}
+	}
+
+	if( p_push )
+	{
+		// Now we know exactly how many dwords written, fix-up our earlier entry.
+		p_push_encode_fixup[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, dwords_written );
+
+		// End the push buffer.
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+	}
+
+	// Restore render states.
+	D3DDevice_SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_render_rain( float skx, float skz )
+{
+	// Early out if no drops to draw.
+	if ( !m_active_drops ) return;
+
+	// Get skater position.
+	int x = (int)(( skx - m_min_x ) / WEATHER_CEL_SIZE );
+	int z = (int)(( skz - m_min_z ) / WEATHER_CEL_SIZE );
+
+	// Calculate area to render.
+	int sx = x - RENDER_DIST;
+	int ex = x + DROP_SIZE;	//RENDER_DIST;
+	int sz = z - RENDER_DIST;
+	int ez = z + DROP_SIZE;	//RENDER_DIST;
+
+	// Clip z values.
+	if ( ez < 0 ) return;
+	if ( sz > ( m_height - 1 ) ) return;
+
+	// Get Xbox format top and bottom color.
+	uint32 top_color = ((uint32)m_rain_top_color.a << 24 ) | ((uint32)m_rain_top_color.r << 16 ) | ((uint32)m_rain_top_color.g << 8 ) | ((uint32)m_rain_top_color.b << 0 );
+	uint32 bot_color = ((uint32)m_rain_bottom_color.a << 24 ) | ((uint32)m_rain_bottom_color.r << 16 ) | ((uint32)m_rain_bottom_color.g << 8 ) | ((uint32)m_rain_bottom_color.b << 0 );
+
+	// Deal with FIXED_BRIGHTEN mode, which doesn't work.
+	if(( m_rain_blend & NxXbox::sMaterial::BLEND_MODE_MASK ) == NxXbox::vBLEND_MODE_BRIGHTEN_FIXED )
+	{
+		set_blend_mode( NxXbox::vBLEND_MODE_BRIGHTEN );
+
+		top_color = ( top_color & 0x00FFFFFFUL ) | ( m_rain_blend & 0xFF000000UL );
+		bot_color = ( bot_color & 0x00FFFFFFUL ) | ( m_rain_blend & 0xFF000000UL );
+	}
+	else
+	{
+		NxXbox::set_blend_mode( m_rain_blend );
+	}
+
+	NxXbox::set_render_state( RS_ALPHACUTOFF, 0 );
+
+	NxXbox::set_pixel_shader( PixelShader5 );
+	NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+
+	sint32 minx		= (sint32)( m_min_x * SUB_INCH_PRECISION );
+	sint32 minz		= (sint32)( m_min_z * SUB_INCH_PRECISION );
+	sint32 rlength	= (sint32)( m_rain_length * SUB_INCH_PRECISION );
+	
+	// Calculate drop height list.
+	int lp;
+	sint32 y_off[256];
+
+	for( lp = ( 254 - m_rain_frames ); lp < 256; lp++ )
+	{
+		y_off[lp] = (sint32)(( m_rain_height - (((float)(( lp - 254 ) + m_rain_frames ) / (float)m_rain_frames ) * m_rain_height )) * SUB_INCH_PRECISION );
+	}
+
+	// Calculate xz offset list.
+	sint32 xz_off[256];
+	for( lp = 0; lp < 256; lp++ )
+	{
+		xz_off[lp] = (sint32)((((float)lp * (float)WEATHER_CEL_SIZE * SUB_INCH_PRECISION ) / 255.0f ));
+	}
+
+	int lines = 0;
+
+	DWORD* p_push				= NULL;
+	DWORD* p_push_encode_fixup	= NULL;
+	uint32 dwords_written		= 0;
+	uint32 total_dwords_written	= 2;
+
+	for ( int lzz = sz; lzz <= ez; lzz++ )
+	{
+		int zz = ( lzz < 0 ) ? 0 : ( lzz > ( m_height - 1 ) ) ? ( m_height - 1 ) : lzz;
+
+		if ( mp_roof_row[zz].start == 16384 ) continue;
+
+		sint32 vx = ( (sint32)sx << ( WEATHER_CEL_SIZE_SHIFT + PRECISION_SHIFT ) ) + minx;
+		sint32 vz = ( (sint32)zz << ( WEATHER_CEL_SIZE_SHIFT + PRECISION_SHIFT ) ) + minz;
+
+		int cel = mp_roof_row[zz].index + ( sx - mp_roof_row[zz].start );
+
+		int drop_cel_z = ( ( zz & ( DROP_SIZE - 1 ) ) << DROP_SIZE_SHIFT );
+
+		for ( int lxx = sx; lxx <= ex; lxx++, cel++ )
+		{
+			int xx = ( lxx > mp_roof_row[zz].start ) ? ( ( lxx < mp_roof_row[zz].end ) ? lxx : mp_roof_row[zz].end ) : mp_roof_row[zz].start;
+
+			// Get the current drop value. Skip this one if it's inactive.
+			int drop_cel = drop_cel_z + ( xx & ( DROP_SIZE - 1 ) );
+
+			vx += ( WEATHER_CEL_SIZE << PRECISION_SHIFT );
+			sint32 vy = m_roof_height[mp_roof_height_index[cel]];
+
+			for ( int d = 0; d < DROP_LAYERS; d++, drop_cel += ( DROP_SIZE * DROP_SIZE ) )
+			{
+				if ( m_drop_time[drop_cel] == 255 )
+					continue;
+
+				// Create the position for rendering.
+				float v0x = ( vx + xz_off[m_x_offset[drop_cel]] ) * ( 1.0f / (float)( 1 << PRECISION_SHIFT ));
+				float v0y = ( vy + y_off[m_drop_time[drop_cel]] ) * ( 1.0f / (float)( 1 << PRECISION_SHIFT ));
+				float v0z = ( vz + xz_off[m_z_offset[drop_cel]] ) * ( 1.0f / (float)( 1 << PRECISION_SHIFT ));
+				float v1y = v0y + ( rlength * ( 1.0f / (float)( 1 << PRECISION_SHIFT )));
+
+				if ( lines == 0 )
+				{
+					lines = NUM_LINES_PER_BATCH;
+				}
+
+				// Grab the push buffer if we don't have it yet.
+				if( p_push == NULL )
+				{
+					// Grab 32k of push buffer.	
+					p_push = D3DDevice_BeginPush( 32 * 1024 / 4 );
+
+					// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+					// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+					// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+					p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+					p_push[1]	= D3DPT_LINELIST;
+					p_push		+= 2;
+
+					// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+					// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+					// As yet, we don't know how many dwords we will write, so save a pointer to the current push buffer location
+					// so we can fix up this value at the end.
+					p_push_encode_fixup = p_push;
+					++p_push;
+				}
+
+				p_push[0] = *((DWORD*)&v0x);
+				p_push[1] = *((DWORD*)&v0y);
+				p_push[2] = *((DWORD*)&v0z);
+				p_push[3] = bot_color;
+
+				p_push[4] = *((DWORD*)&v0x);
+				p_push[5] = *((DWORD*)&v1y);
+				p_push[6] = *((DWORD*)&v0z);
+				p_push[7] = top_color;
+
+				p_push					+= 8;
+				dwords_written			+= 8;
+				total_dwords_written	+= 8;
+
+				// Check for hitting the max dwords for this encode block, in which case we need to end this block
+				// and start another one.
+				if( dwords_written >= 2040 )
+				{
+					// Now we know exactly how many dwords written, fix-up our earlier entry.
+					p_push_encode_fixup[0]		= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, dwords_written );
+
+					// Are we approaching the end of this push buffer? If so, close it out and start a new one.
+					if( total_dwords_written > 6000 )
+					{
+						// End the push buffer.
+						total_dwords_written	= 0;
+						p_push[0]				= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+						p_push[1]				= 0;
+						p_push					+= 2;
+						D3DDevice_EndPush( p_push );
+
+						p_push					= NULL;
+					}
+					else
+					{
+						// As yet, we don't know how many dwords we will write, so save a pointer to the current push buffer location
+						// so we can fix up this value at the end.
+						p_push_encode_fixup		= p_push;
+						++p_push;
+					}
+					dwords_written = 0;
+				}
+				lines--;
+			}
+		}
+	}
+
+	if( p_push )
+	{
+		// Now we know exactly how many dwords written, fix-up our earlier entry.
+		p_push_encode_fixup[0] = D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, dwords_written );
+
+		// End the push buffer.
+		p_push[0] = D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+		p_push[1] = 0;
+		p_push += 2;
+		D3DDevice_EndPush( p_push );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_render_splashes( float skx, float skz )
+{
+	// Create a new splash if required.
+	float last = m_splash_rate;
+	m_splash_rate += m_splash_per_frame;
+	int new_splashes = (int)m_splash_rate - (int)last;
+
+	if ( new_splashes )
+	{
+		CFeeler feeler;
+		Mth::Vector pos;
+
+		pos[X] = NxXbox::EngineGlobals.cam_position[X];
+		pos[Y] = FEELER_START;
+		pos[Z] = NxXbox::EngineGlobals.cam_position[Z];
+		pos[W] = 1.0f;
+
+		Mth::Vector dir;
+		dir[X] = skx - NxXbox::EngineGlobals.cam_position[X];
+		dir[Y] = 0.0f;
+		dir[Z] = skz - NxXbox::EngineGlobals.cam_position[Z];
+		dir[W] = 1.0f;
+		dir.Normalize();
+
+		// Add distance.
+		float r1 = (float)Mth::Rnd( 32768 ) / 32768.0f;
+		pos		+= ( dir * ((float)WEATHER_CEL_SIZE * RENDER_DIST * r1 ));
+
+		// Add lateral.
+		Mth::Vector lat( dir[Z], 0.0f, -dir[X], 1.0f );
+
+		float r2 = 1.0f - ( (float)Mth::Rnd( 32768 ) / 32768.0f );
+		if ( m_current_splash & 1 )
+		{
+			pos += ( lat * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 * r2 ) );
+		}
+		else
+		{
+			pos += ( lat * ( (float)WEATHER_CEL_SIZE * RENDER_DIST * r1 * -r2 ) );
+		}
+
+		feeler.SetStart( pos );
+		pos[Y] = -FEELER_START;
+		feeler.SetEnd( pos );
+
+		// Get the y position.
+		float y;
+		if ( feeler.GetCollision( false, false ) )		// No movables, nearest collision.
+		{
+			y = feeler.GetPoint()[Y];
+		}
+		else
+		{
+			y = FEELER_START;
+		}
+
+		m_splash_x[m_current_splash]			= pos[X];
+		m_splash_y[m_current_splash]			= y + 4.0f;	// Move up slightly.
+		m_splash_z[m_current_splash]			= pos[Z];
+		m_splash_current_life[m_current_splash]	= m_splash_life;
+
+		m_current_splash++;
+		m_current_splash %= NUM_SPLASH_ACTIVE;
+	}
+	
+	// Render the splashes.
+	NxXbox::set_blend_mode( m_splash_blend );
+	NxXbox::set_render_state( RS_ALPHACUTOFF, 0 );
+	NxXbox::set_render_state( RS_UVADDRESSMODE0, 0x00000000UL );
+	mp_rain_texture->Set( 0 );
+
+	NxXbox::set_pixel_shader( PixelShader4 );
+	NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 | D3DFVF_TEXCOORDSIZE2( 0 ));
+
+	uint32 dword_count		= 24 * NUM_SPLASH_ACTIVE;
+	uint32 dwords_written	= 0;
+	Dbg_Assert( dword_count < 2048 );
+
+	// Obtain push buffer lock.
+	// The additional number (+5 is minimum) is to reserve enough overhead for the encoding parameters. It can safely be more, but no less.
+	DWORD* p_push				= D3DDevice_BeginPush( dword_count + 16 );
+	DWORD* p_push_encode_fixup	= NULL;
+
+	// Note that p_push is returned as a pointer to write-combined memory. Writes to write-combined memory should be
+	// consecutive and in increasing order. Reads should be avoided. Additionally, any CPU reads from memory or the
+	// L2 cache can force expensive partial flushes of the 32-byte write-combine cache.
+	p_push[0]	= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+	p_push[1]	= D3DPT_QUADLIST;
+	p_push		+= 2;
+
+	// NOTE: A maximum of 2047 DWORDs can be specified to D3DPUSH_ENCODE. If there is more than 2047 DWORDs of vertex
+	// data, simply split the data into multiple D3DPUSH_ENCODE( D3DPUSH_INLINE_ARRAY ) sections.
+	// As yet, we don't know how many dwords we will write, so save a pointer to the current push buffer location
+	// so we can fix up this value at the end.
+	p_push_encode_fixup = p_push;
+	++p_push;
+
+	for ( int spl = 0; spl < NUM_SPLASH_ACTIVE; spl++ )
+	{
+		if( m_splash_current_life[spl] == 0 )
+			continue;
+
+		// Interpolate color (and switch to Xbox format at the same time).
+		Image::RGBA	icol;
+		icol.b		= (uint8)(((int)m_splash_color.r * m_splash_current_life[spl] ) / m_splash_life );
+		icol.g		= (uint8)(((int)m_splash_color.g * m_splash_current_life[spl] ) / m_splash_life );
+		icol.r		= (uint8)(((int)m_splash_color.b * m_splash_current_life[spl] ) / m_splash_life );
+		icol.a		= (uint8)(((int)m_splash_color.a * m_splash_current_life[spl] ) / m_splash_life );
+
+		// Draw splashes...
+		float vx0	= m_splash_x[spl] - ( m_splash_size * 0.5f );
+		float vx1	= m_splash_x[spl] + ( m_splash_size * 0.5f );
+		float vy	= m_splash_y[spl];
+		float vz0	= m_splash_z[spl] - ( m_splash_size * 0.5f );
+		float vz1	= m_splash_z[spl] + ( m_splash_size * 0.5f );
+
+		p_push[0]	= *((DWORD*)&vx0 );
+		p_push[1]	= *((DWORD*)&vy );
+		p_push[2]	= *((DWORD*)&vz0 );
+		p_push[3]	= *((DWORD*)&icol );
+		p_push[4]	= 0x00000000UL;
+		p_push[5]	= 0x00000000UL;
+
+		p_push[6]	= *((DWORD*)&vx1 );
+		p_push[7]	= *((DWORD*)&vy );
+		p_push[8]	= *((DWORD*)&vz0 );
+		p_push[9]	= *((DWORD*)&icol );
+		p_push[10]	= 0x3F800000UL;
+		p_push[11]	= 0x00000000UL;
+
+		p_push[12]	= *((DWORD*)&vx1 );
+		p_push[13]	= *((DWORD*)&vy );
+		p_push[14]	= *((DWORD*)&vz1 );
+		p_push[15]	= *((DWORD*)&icol );
+		p_push[16]	= 0x3F800000UL;
+		p_push[17]	= 0x3F800000UL;
+
+		p_push[18]	= *((DWORD*)&vx0 );
+		p_push[19]	= *((DWORD*)&vy );
+		p_push[20]	= *((DWORD*)&vz1 );
+		p_push[21]	= *((DWORD*)&icol );
+		p_push[22]	= 0x00000000UL;
+		p_push[23]	= 0x3F800000UL;
+
+		p_push			+= 24;
+		dwords_written	+= 24;
+		m_splash_current_life[spl]--;
+	}
+
+	p_push_encode_fixup[0]	= D3DPUSH_ENCODE( D3DPUSH_NOINCREMENT_FLAG | D3DPUSH_INLINE_ARRAY, dwords_written );
+	p_push[0]				= D3DPUSH_ENCODE( D3DPUSH_SET_BEGIN_END, 1 );
+	p_push[1]				= 0;
+	D3DDevice_EndPush( p_push + 2 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_render( void )
+{
+	if ( !m_system_active ) return;
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = pSkate->GetSkater(0);
+	if (!pSkater) return;
+	
+	float skx = pSkater->m_pos[X];
+	float skz = pSkater->m_pos[Z];
+
+	if ( m_raining )
+	{
+		plat_render_splashes( skx, skz );
+		plat_render_rain( skx, skz );
+	}
+	else
+	{
+		plat_render_snow( skx, skz );
+	}
+
+	// Increment rain drop/snow flake positions.
+	if ( m_active_drops )
+	{
+		for ( int lp = 0; lp < ( DROP_SIZE * DROP_SIZE * DROP_LAYERS ); lp++ )
+		{
+			switch ( m_drop_time[lp] )
+			{
+				case 255:
+					// Inactive.
+					break;
+				case 254:
+					// About to become inactive, so decrement active drops.
+					m_active_drops--;
+					// Deliberately falls through...
+				default:
+					// Increment time.
+					m_drop_time[lp]++;
+					break;
+			}
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_rain_blend = NxXbox::GetBlendMode( blendmode_checksum ) | ((uint32)fix << 24 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_splash_blend = NxXbox::GetBlendMode( blendmode_checksum ) | ((uint32)fix << 24 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxWeather::plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	m_snow_blend = NxXbox::GetBlendMode( blendmode_checksum ) | ((uint32)fix << 24 );
+}
+
+} // Nx
+
+
+
+
diff --git a/Code/Gfx/XBox/p_nxweather.h b/Code/Gfx/XBox/p_nxweather.h
new file mode 100644
index 0000000..ad41b22
--- /dev/null
+++ b/Code/Gfx/XBox/p_nxweather.h
@@ -0,0 +1,95 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_NxWeather.h
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  6/19/2003
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_WEATHER_H__
+#define	__GFX_P_NX_WEATHER_H__
+    
+#include "gfx/nxweather.h"
+
+namespace Nx
+{
+
+#define SUB_INCH_PRECISION		16.0f
+#define WEATHER_CEL_SIZE		128
+#define WEATHER_CEL_SIZE_SHIFT	7
+#define HEIGHT_TOLERANCE		(sint32)( 30.0f * SUB_INCH_PRECISION )
+#define DROP_SIZE				16
+#define DROP_SIZE_SHIFT			4
+#define DROP_LAYERS				4
+#define NUM_SPLASH_ACTIVE		32
+
+typedef struct
+{
+	unsigned short start;
+	unsigned short end;
+	unsigned short index;
+}
+sRowEntry;
+
+class CXboxWeather : public CWeather
+{
+public:
+							CXboxWeather();
+	virtual 				~CXboxWeather();
+
+private:					// It's all private, as it is machine specific
+	void					plat_update_grid( void );
+	virtual void			plat_process( float delta_time );
+	virtual void			plat_render( void );
+	void					plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix );
+	void					plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix );
+	void					plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix );
+
+	void					plat_render_splashes( float skx, float skz );
+	void					plat_render_rain( float skx, float skz );
+	void					plat_render_snow( float skx, float skz );
+
+	NxXbox::sTexture*		mp_rain_texture;
+	NxXbox::sTexture*		mp_snow_texture;
+
+	unsigned char *			mp_roof_height_index;
+	sRowEntry *				mp_roof_row;
+	sint32					m_roof_height[256];
+	int						m_width;
+	int						m_height;
+
+	float					m_min_x;
+	float					m_min_z;
+
+	float					m_rain_rate;
+	float					m_splash_rate;
+	float					m_snow_rate;
+
+	Mth::Vector				m_grid_base;
+
+	unsigned char			m_drop_time[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+	unsigned char			m_x_offset[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+	unsigned char			m_z_offset[DROP_SIZE*DROP_SIZE*DROP_LAYERS];
+
+	uint32					m_seq;
+
+	uint32					m_rain_blend;
+	uint32					m_splash_blend;
+	uint32					m_snow_blend;
+
+	float					m_splash_x[NUM_SPLASH_ACTIVE];
+	float					m_splash_y[NUM_SPLASH_ACTIVE];
+	float					m_splash_z[NUM_SPLASH_ACTIVE];
+	int						m_splash_current_life[NUM_SPLASH_ACTIVE]; 
+	int						m_current_splash;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // Nx
+
+
+#endif
+
diff --git a/Code/Gfx/baseanimcontroller.cpp b/Code/Gfx/baseanimcontroller.cpp
new file mode 100644
index 0000000..7adca88
--- /dev/null
+++ b/Code/Gfx/baseanimcontroller.cpp
@@ -0,0 +1,122 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       BaseAnimController.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/06/2003
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+							 
+#include 
+#include 
+						  
+namespace Gfx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseAnimController::CBaseAnimController( CBlendChannel* pBlendChannel ) : mp_blendChannel( pBlendChannel )
+{
+	m_name = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseAnimController::~CBaseAnimController()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBaseAnimController::InitFromStructure( Script::CStruct* pParams )
+{
+	pParams->GetChecksum( CRCD(0x40c698af,"id"), &m_name, Script::NO_ASSERT );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBaseAnimController::Update()
+{
+	Dbg_Assert( 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBaseAnimController::GetPose( Gfx::CPose* pResultPose )
+{
+	// not handled
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBaseAnimController::GetDebugInfo( Script::CStruct* p_info )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAnimFunctionResult CBaseAnimController::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	return AF_NOT_EXECUTED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject* CBaseAnimController::GetObject()
+{
+	return mp_blendChannel->GetObject();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::CSkeleton* CBaseAnimController::GetSkeleton()
+{
+	Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( GetObject() );
+	if ( pSkeletonComponent )
+	{
+		return pSkeletonComponent->GetSkeleton();
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Gfx/baseanimcontroller.h b/Code/Gfx/baseanimcontroller.h
new file mode 100644
index 0000000..f674f9b
--- /dev/null
+++ b/Code/Gfx/baseanimcontroller.h
@@ -0,0 +1,157 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       BaseAnimController.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/6/2003
+//****************************************************************************
+
+#ifndef __GFX_BASEANIMCONTROLLER_H__
+#define __GFX_BASEANIMCONTROLLER_H__
+
+#include 
+#include 
+
+// for procedural bones, which will be moved to another file...
+#include 
+
+namespace Obj
+{
+	class CCompositeObject;
+}
+				
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+
+namespace Gfx
+{
+	class CBlendChannel;
+	class CPose;
+	class CSkeleton;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// (If the order of these change, then please also
+// change Obj::CBaseComponent::EMemberFunctionResult)
+enum EAnimFunctionResult
+{
+	AF_FALSE 		= 0,
+	AF_TRUE  		= 1,
+	AF_NOT_EXECUTED = 2
+};
+
+// maybe anim controllers should subclass from Obj::CBaseComponent
+// so that they can access to the same CallMemberFunction interface?
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// TODO:  Move this to some generic animcontrollertypes.h
+
+struct SWobbleDetails
+{
+    float				wobbleAmpA;
+    float				wobbleAmpB;
+    float				wobbleK1;
+    float				wobbleK2;
+    float				spazFactor;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// TODO:  Move this to some generic procanim.h
+
+class CProceduralBone
+{
+public:
+	CProceduralBone()
+	{
+		m_name = 0;
+	}
+
+public:
+	uint32					m_name;
+	
+	bool					transEnabled;
+	Mth::Vector				trans0;
+	Mth::Vector				trans1;
+	Mth::Vector				deltaTrans;
+	Mth::Vector				currentTrans;
+	
+	bool					rotEnabled;
+	Mth::Vector				rot0;
+	Mth::Vector				rot1;
+	Mth::Vector				deltaRot;
+	Mth::Vector				currentRot;
+	
+	bool					scaleEnabled;
+	Mth::Vector				scale0;
+	Mth::Vector				scale1;
+	Mth::Vector				deltaScale;
+	Mth::Vector				currentScale;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class CBaseAnimController : public Spt::Class
+{
+public:
+								CBaseAnimController( CBlendChannel* pBlendChannel );
+	virtual 					~CBaseAnimController();
+
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+	virtual void				GetDebugInfo( Script::CStruct* p_info );
+    virtual EAnimFunctionResult CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+
+	int							GetPriority() const
+	{
+		return m_priority;
+	}
+
+	void						SetPriority( int priority )
+	{
+		m_priority = priority;
+	}
+
+	uint32						GetID() const
+	{
+		return m_name;
+	}
+
+protected:
+	Obj::CCompositeObject*		GetObject();
+	Gfx::CSkeleton*				GetSkeleton();
+
+protected:
+	CBlendChannel*				mp_blendChannel;
+	Obj::CCompositeObject*		mp_object;
+	int							m_priority;
+	uint32						m_name;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
+
+#endif
+
diff --git a/Code/Gfx/bbox.cpp b/Code/Gfx/bbox.cpp
new file mode 100644
index 0000000..2f87fca
--- /dev/null
+++ b/Code/Gfx/bbox.cpp
@@ -0,0 +1,202 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		bbox.cpp												**
+**																			**
+**	Created:		02/01/00	-	mjb	(actualy Matt's now)				**
+**																			**
+**	Description:	some functions for line/box intersection
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+namespace Gfx
+{
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+bool PointInsideBox( const Mth::Vector &point, const Mth::Vector &boxMax, const Mth::Vector &boxMin )
+{
+	
+	if ( point[ X ] > boxMax[ X ] )
+		return ( false );
+	if ( point[ Y ] > boxMax[ Y ] )
+		return ( false );
+	if ( point[ Z ] > boxMax[ Z ] )
+		return ( false );
+	if ( point[ X ] < boxMin[ X ] )
+		return ( false );
+	if ( point[ Y ] < boxMin[ Y ] )
+		return ( false );
+	if ( point[ Z ] < boxMin[ Z ] )
+		return ( false );
+	
+	return ( true );
+}// end of PointInsideBox( )
+
+
+// Checks whether pStart-pEnd collides with the axis-aligned bounding box defined by
+// pMax and pMin.
+bool LineCollidesWithBox( const Mth::Vector &pStart, const Mth::Vector &pEnd, const Mth::Vector &pMax, const Mth::Vector &pMin )
+{
+	
+
+	// if either point is in the box... that counts!
+	if ( PointInsideBox( pStart, pMax, pMin ) ||
+		PointInsideBox( pEnd, pMax, pMin ) )
+	{
+		return ( true );
+	}
+
+	// Trivial rejection.
+	if((pStart[ Y ]>pMax[ Y ])&&( pEnd[ Y ]>pMax[ Y ])) return false;
+	if((pStart[ Y ]pMax[ X ])&&( pEnd[ X ]>pMax[ X ])) return false;
+	if((pStart[ X ]pMax[ Z ])&&( pEnd[ Z ]>pMax[ Z ])) return false;
+	if((pStart[ Z ]pMax[ X ] && pEnd[ X ]pMin[ Y ] && zpMin[ Z ])
+		{
+			// It does collide!
+			return true;
+		}
+	}
+
+	// Check the min-x face.
+	if (pStart[ X ]pMin[ X ])
+	{
+		// It crosses the plane of the face, so calculate the y & z coords
+		// of the intersection and see if they are in the face,
+		float d=pMin[ X ]-pStart[ X ];
+		float y=d*dy/dx+pStart[ Y ];
+		float z=d*dz/dx+pStart[ Z ];
+		if (ypMin[ Y ] && zpMin[ Z ])
+		{
+			// It does collide!
+			return true;
+		}
+	}
+
+	// Check the max-y face.
+	if (pStart[ Y ]>pMax[ Y ] && pEnd[ Y ]pMin[ X ] && zpMin[ Z ])
+		{
+			// It does collide!
+			return true;
+		}
+	}
+
+	// Check the min-y face.
+	if (pStart[ Y ]pMin[ Y ])
+	{
+		// It crosses the plane of the face, so calculate the x & z coords
+		// of the intersection and see if they are in the face,
+		float d=pMin[ Y ]-pStart[ Y ];
+		float x=d*dx/dy+pStart[ X ];
+		float z=d*dz/dy+pStart[ Z ];
+		if (xpMin[ X ] && zpMin[ Z ])
+		{
+			// It does collide!
+			return true;
+		}
+	}
+
+	// Check the max-z face.
+	if (pStart[ Z ]>pMax[ Z ] && pEnd[ Z ]pMin[ X ] && ypMin[ Y ])
+		{
+			// It does collide!
+			return true;
+		}
+	}
+
+
+	// Check the min-z face.
+	if (pStart[ Z ]pMin[ Z ])
+	{
+		// It crosses the plane of the face, so calculate the x & y coords
+		// of the intersection and see if they are in the face,
+		float d=pMin[ Z ]-pStart[ Z ];
+		float x=d*dx/dz+pStart[ X ];
+		float y=d*dy/dz+pStart[ Y ];
+		if (xpMin[ X ] && ypMin[ Y ])
+		{
+			// It does collide!
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+} // namespace Gfx
diff --git a/Code/Gfx/bbox.h b/Code/Gfx/bbox.h
new file mode 100644
index 0000000..56a5b0a
--- /dev/null
+++ b/Code/Gfx/bbox.h
@@ -0,0 +1,75 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics  (GFX)											**
+**																			**
+**	File name:		gfx/bbox.h												**
+**																			**
+**	Created: 		06/22/00	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_BBOX_H
+#define __GFX_BBOX_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#ifdef __PLAT_NGC__
+#include "sys/ngc/p_camera.h"
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+			
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+bool PointInsideBox( const Mth::Vector &point, const Mth::Vector &boxMax, const Mth::Vector &boxMin );
+bool LineCollidesWithBox( const Mth::Vector &pStart, const Mth::Vector &pEnd, const Mth::Vector &pMax, const Mth::Vector &pMin );
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Gfx
+
+#endif	// __GFX_BBOX_H
diff --git a/Code/Gfx/blendchannel.cpp b/Code/Gfx/blendchannel.cpp
new file mode 100644
index 0000000..9f511a1
--- /dev/null
+++ b/Code/Gfx/blendchannel.cpp
@@ -0,0 +1,727 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       BlendChannel.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/12/2002
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+							 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+				  
+namespace Gfx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBlendChannel::CBlendChannel( Obj::CCompositeObject* pCompositeObject )
+{
+	m_numControllers = 0;
+
+	// immediately starts active when it's created
+	m_status = ANIM_STATUS_ACTIVE;
+
+	mp_object = pCompositeObject;
+	Dbg_MsgAssert( mp_object, ( "Blend channel has no object" ) );
+}
+
+// how to handle conflicts between controllers?
+// you should really only have either the boned anim or the wobble controller
+// maybe each item should have its own local time...
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBlendChannel::~CBlendChannel()
+{
+	remove_controllers();
+
+	// wipe out any old keys
+	delete_custom_keys();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseAnimController* CBlendChannel::AddController( Script::CStruct* pParams )
+{
+	uint32 type;
+	pParams->GetChecksum( CRCD(0x7321a8d6,"type"), &type, Script::ASSERT );
+
+	CBaseAnimController* pController = NULL;
+
+	int priority = 500;
+
+	switch ( type )
+	{
+		case 0xcb4533ff:	// bonedanim
+			{
+				pController = new CBonedAnimController( this );
+			}
+			break;
+
+		case 0xd079853e:	// ik
+			{
+				pController = new CIKController( this );
+			}
+			break;
+
+		case 0x6d941203:	// wobble
+			{
+				pController = new CWobbleController( this );
+			}
+			break;
+										 
+		case 0xcfc5b380:	// pose
+			{
+				pController = new CPoseController( this );
+			}
+			break;
+
+		case 0xfdf0436c:	// proceduralanim
+			{
+				pController = new CProceduralAnimController( this );
+			}
+			break;
+		
+		case 0xdf5c091a:	// fliprotate
+			{
+				pController = new CFlipRotateController( this );
+				
+				// make it be processed later
+				// (so that partial anims can take effect of flipping)
+				priority = 100;
+			}
+			break;
+	
+		case 0x26205db6:	// lookat
+			{
+				pController = new CLookAtController( this );
+			}
+			break;
+		
+		case 0x659bf355:	// partialanim
+			{
+				pController = new CPartialAnimController( this );
+			}
+			break;
+
+		default:
+			{
+				Dbg_MsgAssert( 0, ( "Unrecognized controller %s", Script::FindChecksumName(type) ) );
+			}
+	}
+
+	// override priority, if necessary
+	pParams->GetInteger( CRCD(0x9d5923d8,"priority"), &priority, Script::NO_ASSERT );
+
+	Dbg_MsgAssert( pController, ( "No controller" ) );
+	pController->InitFromStructure( pParams );
+	pController->SetPriority( priority );
+	add_controller( pController, priority );
+
+	return pController;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::add_controller( CBaseAnimController* pAnimController, int priority )
+{
+	Dbg_MsgAssert( m_numControllers < vMAX_CONTROLLERS, ( "Too many controllers for this channel on %s", Script::FindChecksumName( GetObject()->GetID() ) ) );	
+	Dbg_MsgAssert( pAnimController, ( "No controller" ) );
+		
+	int i = 0;
+	for ( i = 0; i < m_numControllers; i++ )
+	{
+		if ( priority > mp_controllers[i]->GetPriority() )
+		{
+			// shift all of them
+			for ( int j = m_numControllers; j > i; j-- )
+			{
+				mp_controllers[j] = mp_controllers[j - 1];
+			}
+			
+			break;
+		}
+	}
+
+	mp_controllers[i] = pAnimController;
+	m_numControllers++;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::remove_controllers()
+{
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		delete mp_controllers[i];
+		mp_controllers[i] = NULL;
+	}
+
+	m_numControllers = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::remove_controller( CBaseAnimController* pAnimController )
+{
+	// TODO:  Should make sure that high-level
+	// function is not currently traversing the list...
+	
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		if ( pAnimController == mp_controllers[i] )
+		{
+			delete mp_controllers[i];
+			mp_controllers[i] = NULL;
+
+			// shift the rest of them
+			for ( int j = i; j < m_numControllers - 1; j++ )
+			{
+				mp_controllers[j] = mp_controllers[j + 1];
+			}
+
+			m_numControllers--;
+			return;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseAnimController* CBlendChannel::get_controller_by_id( uint32 id )
+{
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		if ( mp_controllers[i]->GetID() == id )
+		{
+			return mp_controllers[i];
+		}
+	}
+
+	// not found
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::Update()
+{
+	// reset the anim complete field for this frame
+	m_animComplete = false;
+
+	if ( GetStatus() == ANIM_STATUS_DEGENERATING )
+	{
+		m_degenerationTime -= ( m_blendSpeed * Tmr::FrameRatio() );
+		if ( m_degenerationTime <= 0.0f )
+		{								    
+			// This animation is no longer active.
+			SetStatus( ANIM_STATUS_INACTIVE );
+		}
+		else if ( ( m_degenerationTime * m_degenerationTimeToBlendMultiplier ) < 0.0f )
+		{
+			// Check for being too small to care about.
+			SetStatus( ANIM_STATUS_INACTIVE ); 
+		}
+	}
+	else
+	{
+		// don't really need to do this any more, because
+		// the update happens in the CBonedAnimController...
+		//		CAnimChannel::Update();
+	}
+	
+	if ( GetStatus() == ANIM_STATUS_ACTIVE )
+	{
+		for ( int i = 0; i < m_numControllers; i++ )
+		{
+			// one of these update functions should set the correct m_time for this frame
+			mp_controllers[i]->Update();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBlendChannel::GetPose( Gfx::CPose* pResultPose )
+{
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		mp_controllers[i]->GetPose( pResultPose );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBlendChannel::Degenerate( float blend_period )
+{
+	if ( blend_period == 0.0f )
+	{
+		// no point in degenerating it
+		// so consider it "done"
+		SetStatus( ANIM_STATUS_INACTIVE );
+	}
+
+	// only degenerate if it's active
+	if( GetStatus() == ANIM_STATUS_ACTIVE )
+	{
+		SetStatus( ANIM_STATUS_DEGENERATING );
+		m_degenerationTime = blend_period;
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CBlendChannel::GetBlendValue()
+{
+	EAnimStatus theStatus = GetStatus();
+
+	switch ( theStatus )
+	{
+		case ANIM_STATUS_ACTIVE:
+			return 1.0f;
+		case ANIM_STATUS_INACTIVE:
+			return 0.0f;
+		case ANIM_STATUS_DEGENERATING:
+			return m_degenerationTime * m_degenerationTimeToBlendMultiplier;
+		default:
+			Dbg_Assert( 0 );
+			return 0.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	CAnimChannel::GetDebugInfo( p_info );
+	
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		mp_controllers[i]->GetDebugInfo( p_info );
+	}
+
+	p_info->AddFloat( CRCD(0x98146e11,"m_degenerationTime"), m_degenerationTime );
+	p_info->AddFloat( CRCD(0x0897053d,"m_degenerationTimeToBlendMultiplier"), m_degenerationTimeToBlendMultiplier );
+	
+	uint32 status_checksum = 0;
+	switch ( m_status )
+	{
+		case ANIM_STATUS_INACTIVE:
+			status_checksum = CRCD(0xbc4202a3,"ANIM_STATUS_INACTIVE");
+			break;
+		case ANIM_STATUS_DEGENERATING:
+			status_checksum = CRCD(0x5071e935,"ANIM_STATUS_DEGENERATING");
+			break;
+		case ANIM_STATUS_ACTIVE:
+			status_checksum = CRCD(0x7009388b,"ANIM_STATUS_ACTIVE");
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Unknown status found in CBlendChannel::GetDebugInfo.  Was the enum changed?" ) );
+			break;
+	}
+	p_info->AddChecksum( CRCD(0xce6cc81d,"m_status"), status_checksum );
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::Reset()
+{
+	CAnimChannel::Reset();
+
+    m_status									= ANIM_STATUS_INACTIVE;
+
+	m_degenerationTime						    = 0.0f;
+	m_degenerationTimeToBlendMultiplier	        = 0.0f;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::InvalidateCache()
+{
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		mp_controllers[i]->CallMemberFunction( CRCD(0x5f495ae0,"InvalidateCache"), NULL, NULL );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAnimStatus CBlendChannel::GetStatus() const
+{
+	return m_status;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::SetStatus( EAnimStatus status )
+{
+	m_status = status;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBlendChannel::IsActive()
+{
+	return ( GetStatus() != Gfx::ANIM_STATUS_INACTIVE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBlendChannel::IsDegenerating()
+{
+	return ( GetStatus() == Gfx::ANIM_STATUS_DEGENERATING );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::PlaySequence( uint32 anim_name, float start_time, float end_time, EAnimLoopingType loop_type, float blend_period, float speed, bool flipped )
+{
+	// clear out any old custom keys, and add the new ones
+	delete_custom_keys();
+	add_custom_keys( anim_name );
+	
+	m_status = ANIM_STATUS_ACTIVE;
+	
+	m_blendSpeed = speed / 60.0f;
+
+	Script::CStruct* pTempParams = NULL;
+	 
+	if ( loop_type == Gfx::LOOPING_WOBBLE )
+	{
+		pTempParams = new Script::CStruct;
+		pTempParams->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0x6d941203,"wobble") );
+		this->AddController( pTempParams );
+		delete pTempParams;
+	}
+	else
+	{
+		pTempParams= new Script::CStruct;
+		pTempParams->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0xcb4533ff,"bonedanim") );
+		this->AddController( pTempParams );
+		delete pTempParams;
+	}
+
+	pTempParams= new Script::CStruct;
+	pTempParams->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0xdf5c091a,"fliprotate") );
+    pTempParams->AddInteger( "flipped", flipped );
+	this->AddController( pTempParams );
+	delete pTempParams;
+
+#if 0
+	// if it's the skater, then add a procedural anim controller
+	if ( GetObject()->GetID() == 0 )
+	{
+		pTempParams = new Script::CStruct;
+		pTempParams->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0xfdf0436c,"proceduralanim") );
+		this->AddController( pTempParams );
+		delete pTempParams;
+	}
+#endif
+	
+#if 0
+	// if it's the skater, then add a partial anim controller
+	if ( GetObject()->GetID() == 0 )
+	{
+		pTempParams = new Script::CStruct;
+		pTempParams->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0x659bf355,"partialanim") );
+		pTempParams->AddChecksum( CRCD(0x6c2bfb7f,"animname"), Script::GenerateCRC("Taunt1") );
+		pTempParams->AddFloat( CRCD(0xd16b61e6,"starttime"), 0.0f );
+		pTempParams->AddFloat( CRCD(0xab81bb3d,"endtime"), 1.0f );
+		pTempParams->AddInteger( CRCD(0xcd8d7c1b,"looptype"), Gfx::LOOPING_CYCLE );
+		pTempParams->AddFloat( CRCD(0x8f0d24ed,"blendperiod"), 0.0f );
+		pTempParams->AddFloat( CRCD(0xf0d90109,"speed"), 1.0f );
+		this->AddController( pTempParams );
+		delete pTempParams;
+	}
+#endif
+
+	pTempParams = new Script::CStruct;
+	pTempParams->AddFloat( CRCD(0x4d747fa0,"wobbleTargetAlpha"), 0.0f );
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		mp_controllers[i]->CallMemberFunction( CRCD(0xd0209498,"setWobbleTarget"), pTempParams, NULL );
+	}
+	delete pTempParams;
+
+	pTempParams = new Script::CStruct;
+	pTempParams->AddChecksum( CRCD(0x6c2bfb7f,"animname"), anim_name );
+	pTempParams->AddFloat( CRCD(0xd16b61e6,"starttime"), start_time );
+	pTempParams->AddFloat( CRCD(0xab81bb3d,"endtime"), end_time );
+	pTempParams->AddInteger( CRCD(0xcd8d7c1b,"looptype"), loop_type );
+	pTempParams->AddFloat( CRCD(0x8f0d24ed,"blendperiod"), blend_period );
+	pTempParams->AddFloat( CRCD(0xf0d90109,"speed"), speed );
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		mp_controllers[i]->CallMemberFunction( CRCD(0xaf2fae19,"PlaySequence"), pTempParams, NULL );
+	}
+	delete pTempParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::delete_custom_keys()
+{
+	m_customAnimKeyList.DestroyAllNodes();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::add_custom_keys( uint32 animName )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	if ( !pAnimationComponent )
+	{
+		return;
+	}
+
+	uint32 animEventTableName = pAnimationComponent->GetAnimEventTableName();
+	if ( !animEventTableName )
+	{
+		return;
+	}
+
+	Script::CStruct* pAnimEventTable = Script::GetStructure( animEventTableName, Script::ASSERT );
+	if ( pAnimEventTable )
+	{
+		Script::CArray* pSubArray;
+		if ( pAnimEventTable->GetArray( animName, &pSubArray, Script::NO_ASSERT ) )
+		{
+			for ( uint32 i = 0; i < pSubArray->GetSize(); i++ )
+			{
+				Script::CStruct* pSubStruct;
+				pSubStruct = pSubArray->GetStructure( i );
+    
+				int frame = 0;
+				// if the frame is not specified, then look for a time value
+				if ( !pSubStruct->GetInteger( CRCD(0x4a07c332,"frame"), &frame, Script::NO_ASSERT ) )
+				{
+					float keyTime = 0.0f;
+						pSubStruct->GetFloat( CRCD(0x906b67ba,"time"), &keyTime, Script::ASSERT );
+						frame = (int)(keyTime * 60.0f);
+				}
+
+                uint32 eventType;
+				pSubStruct->GetChecksum( CRCD(0xc451f558,"event"), &eventType, Script::ASSERT );
+
+				Script::CStruct* pEventParams = NULL;
+				pSubStruct->GetStructure( CRCD(0x7031f10c,"params"), &pEventParams, Script::NO_ASSERT );
+                
+				Gfx::CCustomAnimKey* pKey = new Gfx::CEventKey( frame, eventType, pEventParams );
+				pKey->SetActive( true );
+				m_customAnimKeyList.AddToTail( pKey );
+			}
+		}													  
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::ResetCustomKeys()
+{
+	int customKeyCount = m_customAnimKeyList.CountItems();
+	for ( int i = 0; i < customKeyCount; i++ )
+	{
+		Gfx::CCustomAnimKey* pKey = (Gfx::CCustomAnimKey*)m_customAnimKeyList.GetItem(i);
+		pKey->SetActive( true );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBlendChannel::ProcessCustomKeys( float startTime, float endTime )
+{
+	// Only fire off events on the primary anim
+	if ( GetStatus() == ANIM_STATUS_ACTIVE )
+	{
+		startTime *= 60.0f;
+		endTime *= 60.0f;
+
+		// we've just looped, so reset the keys
+		if ( m_direction == ANIM_DIR_FORWARDS )
+		{
+			if ( startTime > endTime )
+			{
+				ResetCustomKeys();
+				startTime = m_startTime;
+			}
+		}
+		else
+		{
+			if ( endTime > startTime )
+			{
+				ResetCustomKeys();
+				startTime = m_startTime;
+			}
+		}
+
+		int customKeyCount = m_customAnimKeyList.CountItems();
+		for ( int i = 0; i < customKeyCount; i++ )
+		{
+			Gfx::CCustomAnimKey* pKey = (Gfx::CCustomAnimKey*)m_customAnimKeyList.GetItem(i);
+			if ( pKey->WithinRange( startTime, endTime ) )
+			{
+//				printf( "Processing key at %f (%f %f)\n", 0.0f, startTime, endTime );
+				pKey->ProcessKey( GetObject() );
+			}
+			else
+			{
+//				printf( "Not processing key at %f (%f %f)\n", custom_key_time, startTime, endTime );
+			}
+		}
+	}
+	
+	// TODO:  reset the custom keys when it loops?
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAnimFunctionResult CBlendChannel::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case 0x83654874:	// AddAnimController
+		{
+			AddController( pParams );
+			return AF_TRUE;
+		}
+		break;
+
+		case 0x986d274e:	// RemoveAnimController
+		{
+			uint32 id;
+			pParams->GetChecksum( CRCD(0x40c698af,"id"), &id, Script::ASSERT );
+			CBaseAnimController* pAnimController = get_controller_by_id( id );
+			if ( pAnimController )
+			{
+				remove_controller( pAnimController );
+			}
+		}
+		break;
+	}
+
+	for ( int i = 0; i < m_numControllers; i++ )
+	{
+		EAnimFunctionResult theResult = mp_controllers[i]->CallMemberFunction( Checksum, pParams, pScript );
+		
+		if ( theResult != AF_NOT_EXECUTED )
+		{
+			return theResult;
+		}
+	}
+	
+	return AF_NOT_EXECUTED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject* CBlendChannel::GetObject()
+{
+	return mp_object;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Gfx/blendchannel.h b/Code/Gfx/blendchannel.h
new file mode 100644
index 0000000..61224a8
--- /dev/null
+++ b/Code/Gfx/blendchannel.h
@@ -0,0 +1,129 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       BlendChannel.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  12/12/2002
+//****************************************************************************
+
+#ifndef __GFX_BLENDCHANNEL_H__
+#define __GFX_BLENDCHANNEL_H__
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+	
+namespace Object
+{
+	class CObject;
+}
+			 
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+
+namespace Gfx
+{
+	class CBlendChannel;
+	class CPose;
+	class CBaseAnimController;
+
+	// NOTE: if you change this enum, update the GetDebugInfo switch statement!	
+	enum EAnimStatus
+	{								 
+		ANIM_STATUS_INACTIVE		= 0,	// No animation playing.
+		ANIM_STATUS_DEGENERATING,			// Animation playing, but being faded out.
+		ANIM_STATUS_ACTIVE					// Animation playing.
+	};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class CBlendChannel : public CAnimChannel
+{
+protected:
+	// TODO:  Replace with a more efficient list structure?
+	enum
+	{
+		vMAX_CONTROLLERS = 4
+	};
+
+public:
+	CBlendChannel( Obj::CCompositeObject* pCompositeObject );
+	virtual	~CBlendChannel();
+
+public:
+	CBaseAnimController*		AddController( Script::CStruct* pParams );
+	bool						GetPose( Gfx::CPose* pPose );
+	float						GetBlendValue();
+	bool						IsActive();
+	bool						IsDegenerating();
+	void						InvalidateCache();
+    virtual EAnimFunctionResult	CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+
+public:
+	virtual void				Update();
+	virtual void				Reset();
+	virtual void				GetDebugInfo( Script::CStruct* p_info );
+	virtual void        		PlaySequence( uint32 animName, float start_time, float end_time, EAnimLoopingType loop_type, float blend_period, float speed, bool flipped );
+
+public:
+	float						GetDegenerationTime() { return m_degenerationTime; }
+	float						GetDegenerationTimeToBlendMultiplier() { return m_degenerationTimeToBlendMultiplier; }
+	void						SetDegenerationTime( float time ) { m_degenerationTime = time; }
+	void						SetDegenerationTimeToBlendMultiplier( float mult ) { m_degenerationTimeToBlendMultiplier = mult; }
+	bool						Degenerate( float blendPeriod );
+
+protected:		
+	void						add_controller( CBaseAnimController* pAnimController, int priority = 0 );
+	void						remove_controllers();
+	void						remove_controller( CBaseAnimController* pAnimController );
+	CBaseAnimController* 		get_controller_by_id( uint32 id );
+
+protected:
+	EAnimStatus					GetStatus() const;
+
+public:
+	void						SetStatus(EAnimStatus status);
+	Obj::CCompositeObject*		GetObject();
+
+protected:
+	EAnimStatus					m_status;
+                       
+protected:
+	CBaseAnimController*		mp_controllers[vMAX_CONTROLLERS];
+	int							m_numControllers;
+    
+	float						m_blendSpeed;
+	float						m_degenerationTime;
+    float						m_degenerationTimeToBlendMultiplier;
+
+	uint32						m_animScriptName;
+	Obj::CCompositeObject*		mp_object;
+
+public:
+	// for doing anim events...
+	void							ProcessCustomKeys(float startTime, float endTime);
+	void							ResetCustomKeys();
+
+protected:
+	void 							add_custom_keys( uint32 animName );
+	void							delete_custom_keys();
+	Lst::Head	m_customAnimKeyList;	
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
+
+#endif
+
diff --git a/Code/Gfx/camera.cpp b/Code/Gfx/camera.cpp
new file mode 100644
index 0000000..18b349f
--- /dev/null
+++ b/Code/Gfx/camera.cpp
@@ -0,0 +1,325 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       camera.cpp
+//* OWNER:          Mark Burton
+//* CREATION DATE:  1/31/1999
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+const float	Camera::vDEFAULT_NEARZ		= 12.0f;
+const float	Camera::vDEFAULT_FARZ		= 12000.0f;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+const float Camera::vUSE_PIXEL_RATIO	= 0.0f;
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Camera::Camera ( )
+{	
+	//SetFOV ( Mth::DegToRad ( 60.0f ));
+	SetHFOV(Nx::CViewportManager::sGetDefaultAngle());
+
+	m_matrix.Ident();	// Since the default constructor doesn't do this
+
+	m_near_clip = vDEFAULT_NEARZ;
+	m_far_clip = vDEFAULT_FARZ;
+	m_pos.Set();
+	m_old_pos.Set();
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Camera::~Camera ( void )
+{   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Camera::SetHFOV(float h_fov)
+{
+	m_h_fov = h_fov;
+	//Dbg_Message("Changed camera %x to angle %f", this, m_h_fov);
+
+	UpdateAdjustedHFOV();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Camera::UpdateAdjustedHFOV()
+{
+	m_adj_h_fov = Mth::RadToDeg( 2.0f * atanf( tanf( Mth::DegToRad( m_h_fov / 2.0f ) ) * Nx::CViewportManager::sGetScreenAngleFactor() ) );
+	//Dbg_Message("%x: Orig angle %f; Adjusted angle %f", this, m_h_fov, m_adj_h_fov);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float Camera::GetAdjustedHFOV()
+{
+	// Garrett: m_adj_h_fov may not be right, so we calculate it every time we need it now.  Will try to keep it
+	// up-to-date at a later date.
+	UpdateAdjustedHFOV();
+
+	return m_adj_h_fov;
+#if 0
+	return Mth::RadToDeg( 2.0f * atanf( tanf( Mth::DegToRad( m_h_fov / 2.0f ) ) * Nx::CViewportManager::sGetScreenAngleFactor() ) );
+#endif
+}
+
+#if 0
+/******************************************************************************
+ *
+ * Function:		Camera::SetFocalLength ( float focal_length, float h_aspect )
+ *
+ * Description:		Sets the camera viewplane according to focal length
+ *
+ * Parameters:		focal_length = focal length of lens in meters 
+ *					h_aspect = horizontal aspect scale factor
+ *
+ * Notes:			focal_length of 0.035 is equal to a 35mm lens.
+ *
+ *					You cannot set horizontally or vertically flipped
+ *					viewplanes diRectly with this function
+ *
+ *					focal_length is clamped to a minimum value of N_EPSILON
+ *
+ ******************************************************************************/
+
+void Camera::SetFocalLength ( float focal_length, float h_aspect )
+{
+	float fov = atanf (( 18.0f / 1000.0f) / Mth::Max ( Mth::EPSILON, focal_length )) * 2.0f;
+	
+	SetFOV ( fov, h_aspect );
+}
+
+/******************************************************************************
+ *
+ * Function:		Camera::SetViewPlane ( float h_fov, float v_fov )
+ *
+ * Description:		Sets the camera view plane with two FOV angles 
+ *
+ * Parameters:		h_fov	= horizontal FOV angle in radians [-N_PI,+N_PI]
+ *					v_fov	= vertical FOV angle in radians	  [_N_PI,+N_PI]
+ *
+ * Notes:			The FOV angles are clamped separately, i.e. if clamping
+ *					occurs due to too large an angle, the aspect ratio is
+ *					affected.
+ *					
+ ******************************************************************************/
+
+void Camera::SetViewPlane ( float h_fov, float v_fov )
+{
+	
+
+}
+
+/******************************************************************************
+ *
+ * Function:		Camera::SetFOV ( float fov, float h_aspect )
+ *
+ * Description:		Sets camera view plane with a field-of-view angle
+ *					and an aspect ratio
+ *
+ * Parameters:		fov		 = FOV angle in radians
+ *					h_aspect = horizontal aspect ratio ( default: vp pixel ratio )
+ *					
+ ******************************************************************************/
+
+void Camera::SetFOV ( float v_fov, float h_aspect )
+{
+	
+/*
+	float x,y 
+	
+	v_fov = Mth::Abs ( v_fov );
+	v_fov = Mth::Clamp ( v_fov, Mth::EPSILON, Mth::PI - Mth::EPSILON );
+	
+	v_fov = tan ( v_fov / 2.0f );
+	
+	h_aspect = ( h_aspect == vUSE_PIXEL_RATIO ) ? m_viewport->GetPixelRatio() : h_aspect; 
+
+	x = v_fov * h_aspect;
+	y = v_fov;
+  
+// Mick: x and y contain the view window x and y   
+*/
+    
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float Camera::GetFOV ( void )
+{
+	// Mick: not meaningful......
+	return 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float Camera::GetFocalLength ( void )
+{	
+	// Mick: not meaningful......
+	return 0.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float Camera::GetAspectRatio ( void )
+{
+	// Mick: not meaningful......
+	return 0.0f;
+
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Camera::StoreOldPos( void )
+{
+	m_old_pos = GetPos();		   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Note these are returned by value, until
+// we can store them explicity in the camera, and return them by reference
+void Camera::SetPos(const Mth::Vector& pos)
+{
+	m_pos = pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector & Camera::GetPos()
+{
+	return  m_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Camera::SetMatrix(const Mth::Matrix& mat)
+{
+	m_matrix = mat;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Matrix & Camera::GetMatrix()
+{
+	return m_matrix;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Camera::CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( checksum )
+	{
+		case 0x44ea2b6d:		// ChangeCameraFOV
+			{
+				float fov;
+				pParams->GetFloat( NONAME, &fov, Script::ASSERT );
+				//Nx::CViewportManager::sSetScreenAngle( Mth::RadToDeg( fov ) );
+				SetHFOV(Mth::RadToDeg(fov));
+				return true;
+			}
+			break;
+	}
+
+	return Obj::CObject::CallMemberFunction( checksum, pParams, pScript );
+}
+
+} // namespace Gfx
diff --git a/Code/Gfx/camera.h b/Code/Gfx/camera.h
new file mode 100644
index 0000000..6adf929
--- /dev/null
+++ b/Code/Gfx/camera.h
@@ -0,0 +1,177 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       camera.cpp
+//* OWNER:          Mark Burton
+//* CREATION DATE:  11/11/1999
+//****************************************************************************
+
+#ifndef __GFX_CAMERA_H
+#define __GFX_CAMERA_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#ifdef __PLAT_NGC__
+#include "sys/ngc/p_camera.h"
+#endif		// __PLAT_NGC__
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+	class CStruct;
+}
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class Camera : public Obj::CObject
+{
+	
+public:
+	static const float vUSE_PIXEL_RATIO;
+
+	Camera( void );
+	virtual ~Camera( void );
+
+public:	
+	void			SetPos(const Mth::Vector& pos);		// Set the position of the camera
+	Mth::Vector	&	GetPos();							// Return the position of the camera
+	void			SetMatrix(const Mth::Matrix& mat);	// Set the orientation matrix
+	Mth::Matrix	&	GetMatrix();						// Return the orientation matrix
+
+
+	void			SetHFOV(float h_fov);
+	float			GetHFOV() const;
+	void			UpdateAdjustedHFOV();
+	float			GetAdjustedHFOV();
+
+#if 0
+	void			SetFOV ( float v_fov, float h_aspect = vUSE_PIXEL_RATIO );
+	void			SetViewPlane ( float h_fov, float v_fov );
+	void			SetFocalLength ( float focal_length, float h_aspect = vUSE_PIXEL_RATIO );
+
+	float			GetFOV ( void );
+	float			GetFocalLength ( void );
+	float			GetAspectRatio ( void );
+#endif
+
+	void			SetNearFarClipPlanes ( float nearPlane, float farPlane );
+	void			SetFogNearPlane ( float nearPlane );
+	
+	float			GetNearClipPlane() const;
+	float			GetFarClipPlane() const;
+
+	static const float	vDEFAULT_NEARZ;
+	static const float	vDEFAULT_FARZ;
+
+	void			StoreOldPos( void );
+	Mth::Vector		m_old_pos;
+
+public:
+	bool			CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript ); 
+
+protected:  	
+	Mth::Vector		m_pos;								// camera position
+	Mth::Matrix		m_matrix;							// orientation matrix
+
+	float			m_h_fov;							// horizontal field of view angle (degrees)
+	float			m_adj_h_fov;						// screen adjusted horizontal field of view angle (degrees)
+
+	float			m_near_clip;						// near clip plane
+	float			m_far_clip;							// far clip plane
+};
+
+/*****************************s************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float Camera::GetHFOV() const
+{
+	return m_h_fov;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void Camera::SetNearFarClipPlanes ( float nearPlane, float farPlane ) 
+{
+	Dbg_MsgAssert( farPlane > 0.0f,( "Far Plane <= 0" ));
+	Dbg_MsgAssert( nearPlane > 0.0f,( "Near Plane <= 0" ));
+	Dbg_MsgAssert( farPlane > nearPlane,( "Far Plane <= Near Plane" ));
+
+	m_far_clip = farPlane;
+	m_near_clip = nearPlane;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void Camera::SetFogNearPlane ( float nearPlane ) 
+{
+//	printf ("STUBBED:  SetFogNearPlane\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float Camera::GetNearClipPlane() const
+{
+	return m_near_clip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline float Camera::GetFarClipPlane() const
+{
+	return m_far_clip;
+}
+
+} // namespace Gfx
+
+#endif	// __GFX_CAMERA_H
diff --git a/Code/Gfx/debuggfx.cpp b/Code/Gfx/debuggfx.cpp
new file mode 100644
index 0000000..9d91590
--- /dev/null
+++ b/Code/Gfx/debuggfx.cpp
@@ -0,0 +1,675 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			GFX														**
+**																			**
+**	File name:		debuggfx.cpp											**
+**																			**
+**	Created by:		11/01/00	-	mjd										**
+**																			**
+**	Description:	Draws debug lines, other debug graphics					**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+
+#include 
+#include 
+#include 				// Including for debugging
+#include 
+#include 
+#include 
+
+#include 			// for camera
+
+#ifdef	__PLAT_NGPS__	
+#include 
+#include 
+namespace Sys
+{
+	extern void			box(int x0,int y0, int x1, int y1, uint32 color);
+}
+#endif
+
+#ifdef	__PLAT_XBOX__
+#include 
+#include 
+#endif
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#ifdef	__PLAT_NGPS__
+#define DEBUGGERY_LINE_ARRAY_SIZE		10000			// * 48 bytes = 4.8MB off the debug heap
+#else
+#define DEBUGGERY_LINE_ARRAY_SIZE		2048
+#endif
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+struct SDebuggeryLine{
+	Mth::Vector			v0, v1;
+	unsigned int	rgb0, rgb1;
+	int				in_use;
+	int				num_draw_frames;
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+#ifndef __PLAT_NGC__
+SDebuggeryLine *DebuggeryLineArray = NULL;		//[ DEBUGGERY_LINE_ARRAY_SIZE ];
+#endif		// __PLAT_NGC__
+
+#ifdef	__DEBUG_CODE__
+static bool	sActive = false;
+#endif
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+void DebuggeryLines_Draw( void );
+void DebuggeryLines_CleanUp( void );
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+void DebuggeryLines_CleanUp( void )
+{
+#ifdef	__DEBUG_CODE__
+#ifndef __PLAT_NGC__
+	if (!DebuggeryLineArray)
+	{
+		// DOn't bother if not allocated yet
+		return;
+	}
+	
+	int i;
+	
+	for ( i = 0; i < DEBUGGERY_LINE_ARRAY_SIZE; i++ )
+	{
+		DebuggeryLineArray[ i ].in_use = 0;
+	}
+#endif		// __PLAT_NGC__
+#endif
+}
+
+#ifndef __PLAT_NGC__
+#ifdef	__DEBUG_CODE__
+static bool DebuggeryLinesInitialized = 0;
+#endif
+#endif		// __PLAT_NGC__
+
+
+void DebuggeryLines_Draw( void )
+{
+#ifdef	__DEBUG_CODE__
+
+
+	if (!sActive)
+	{
+		return;
+	}
+	
+	sActive = false;
+#	if defined( __PLAT_NGPS__ )
+
+	Mth::Vector	cam_fwd;
+	
+	if (Nx::CViewportManager::sGetActiveCamera())
+	{
+		cam_fwd = Nx::CViewportManager::sGetActiveCamera()->GetMatrix()[Z];
+	}
+	else
+	{
+		printf("WARNING: called Gfx::DebuggeryLines_Draw without a camera\n");
+		return;
+	}
+
+	// can remove this when this module is properly initialized:
+	int i;
+	uint32	last_color = 0x33333333;	 	// the dodginess of my code astounds me!
+
+	if ( !DebuggeryLinesInitialized )
+	{
+		DebuggeryLines_CleanUp( );
+		DebuggeryLinesInitialized = 1;
+	}
+
+	SDebuggeryLine* p_debugline = DebuggeryLineArray;	
+	for ( i = DEBUGGERY_LINE_ARRAY_SIZE; i ; i-- )
+	{
+		if ( p_debugline->in_use )
+		{
+		
+			NxPs2::DMAOverflowOK = 2;
+		
+			if ( last_color != p_debugline->rgb0)
+			{
+				if (last_color != 0x33333333)
+				{
+					NxPs2::ChangeLineColor(0x80000000 + (0x00ffffff & p_debugline->rgb0));
+				}			
+				else
+				{
+					NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & p_debugline->rgb0));
+				}
+				last_color = p_debugline->rgb0;
+			}
+		
+			sActive = true;
+			NxPs2::DrawLine3D(p_debugline->v0[X],
+					   p_debugline->v0[Y],
+					   p_debugline->v0[Z],
+					   p_debugline->v1[X],
+					   p_debugline->v1[Y],
+					   p_debugline->v1[Z]);
+			NxPs2::DrawLine3D(p_debugline->v0[X],
+					   p_debugline->v0[Y],
+					   p_debugline->v0[Z],
+					   p_debugline->v1[X],
+					   p_debugline->v1[Y],
+					   p_debugline->v1[Z]);
+
+
+			if ( p_debugline->in_use == 2 )
+			{
+					
+				// Calculate and draw an arrowhead 
+				// 1/4th the length, at ~30 degrees					   
+				Mth::Vector	a = p_debugline->v0;
+				Mth::Vector b = p_debugline->v1;
+				Mth::Vector	ab = (b-a);
+				ab /= 4.0f;
+				Mth::Vector out;
+				out = ab;
+				out.Normalize();
+				out = Mth::CrossProduct(out, cam_fwd);
+				out *= ab.Length()/3.0f;			
+				
+				Mth::Vector left =  b - ab + out;
+				Mth::Vector right = b - ab - out;
+	
+				NxPs2::DrawLine3D(left[X],left[Y],left[Z],b[X],b[Y],b[Z]);
+				NxPs2::DrawLine3D(right[X],right[Y],right[Z],b[X],b[Y],b[Z]);
+				NxPs2::DrawLine3D(right[X],right[Y],right[Z],left[X],left[Y],left[Z]);	 // crossbar
+				// have to draw it twice for some stupid reason	(final segment of a particular color is not rendered)
+				NxPs2::DrawLine3D(left[X],left[Y],left[Z],b[X],b[Y],b[Z]);
+				NxPs2::DrawLine3D(right[X],right[Y],right[Z],b[X],b[Y],b[Z]);
+				NxPs2::DrawLine3D(right[X],right[Y],right[Z],left[X],left[Y],left[Z]);
+				
+			}			   
+					   
+					   
+			if ( p_debugline->num_draw_frames )
+			{
+				p_debugline->num_draw_frames--;
+				if ( !p_debugline->num_draw_frames )
+					p_debugline->in_use = false;
+			}
+		}
+		p_debugline++;
+	}
+
+	if (last_color != 0x33333333)
+	{
+		NxPs2::EndLines3D();
+	}			
+#	elif defined( __PLAT_XBOX__ )
+	struct sLineVert
+	{
+		D3DVECTOR	pos;
+		D3DCOLOR	col;
+	};
+
+	static sLineVert	line_verts[DEBUGGERY_LINE_ARRAY_SIZE * 2];
+	uint32				last_color = 0x33333333;
+	uint32				index = 0;
+	if( !DebuggeryLinesInitialized )
+	{
+		DebuggeryLines_CleanUp();
+		DebuggeryLinesInitialized = 1;
+	}
+	
+	for( int i = 0; i < DEBUGGERY_LINE_ARRAY_SIZE; ++i )
+	{
+		Dbg_Assert( index < DEBUGGERY_LINE_ARRAY_SIZE * 2 );
+		if( DebuggeryLineArray[i].in_use )
+		{
+			if( DebuggeryLineArray[ i ].num_draw_frames )
+			{
+				DebuggeryLineArray[ i ].num_draw_frames--;
+				if( !DebuggeryLineArray[i].num_draw_frames )
+					DebuggeryLineArray[i].in_use = false;
+			}
+			sActive = true;
+			
+			// Need to switch rgba to bgra.
+			uint32 rgb0 = ( DebuggeryLineArray[i].rgb0 & 0xFF00FF00UL ) | (( DebuggeryLineArray[i].rgb0 & 0x00FF0000UL ) >> 16 ) |(( DebuggeryLineArray[i].rgb0 & 0x000000FFUL ) << 16 );
+			uint32 rgb1 = ( DebuggeryLineArray[i].rgb1 & 0xFF00FF00UL ) | (( DebuggeryLineArray[i].rgb1 & 0x00FF0000UL ) >> 16 ) |(( DebuggeryLineArray[i].rgb1 & 0x000000FFUL ) << 16 );
+			
+			line_verts[index].col	= rgb0;
+			line_verts[index].pos.x	= DebuggeryLineArray[i].v0[X];
+			line_verts[index].pos.y	= DebuggeryLineArray[i].v0[Y];
+			line_verts[index].pos.z	= DebuggeryLineArray[i].v0[Z];
+			++index;
+			line_verts[index].col	= rgb1;
+			line_verts[index].pos.x	= DebuggeryLineArray[i].v1[X];
+			line_verts[index].pos.y	= DebuggeryLineArray[i].v1[Y];
+			line_verts[index].pos.z	= DebuggeryLineArray[i].v1[Z];
+			++index;
+		}
+	}
+	
+	if( index > 0 )
+	{
+		NxXbox::set_texture( 0, NULL );
+		NxXbox::set_blend_mode( NxXbox::vBLEND_MODE_DIFFUSE );
+		NxXbox::set_vertex_shader( D3DFVF_XYZ | D3DFVF_DIFFUSE );
+		NxXbox::set_pixel_shader( 0 );
+		HRESULT hr = NxXbox::EngineGlobals.p_Device->DrawPrimitiveUP( D3DPT_LINELIST, index / 2, line_verts, sizeof( sLineVert ));
+	}
+#	endif
+#endif
+}  // end of DebuggeryLines_Draw( )
+
+ 
+// Draw a bunch of 2D rectangles that we added to the list this frame. 
+// currently used for the framerate indicator.
+
+
+void DebuggeryRects_Draw( float time)
+{
+
+#ifdef	__DEBUG_CODE__
+
+	#ifdef	__PLAT_NGPS__
+	#ifdef		__USE_PROFILER__
+	
+						  
+		if (Script::GetInteger(CRCD(0xd9859988,"Display_framerate_box")) == 0)
+		{
+			return;
+		}
+						  
+		if (time < 0.016667f)
+		{
+			return;
+		}
+		
+		if (Config::CD())
+		{
+			return;
+		}
+
+		int size = (int) (time * 400); 
+
+		
+		int x = 70-size;
+		int y = 200-size;
+		int w = size*2;
+		int h = size*2;
+
+		
+		Sys::box( x,y,x+w,y+h, 0x0000ff);
+		Sys::box( x+w/4,y+h/4,x+w*3/4,y+h*3/4, 0xffffff);
+	
+	#endif
+	#endif
+#endif
+
+}
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+// given a movement vector v0, then move all the debug lines by this amount
+// (this allows us to see the lines relative to a moving object)
+void AdjustDebugLines( const Mth::Vector &v0)
+{
+#ifdef	__DEBUG_CODE__
+
+#ifndef __PLAT_NGC__
+	if (!DebuggeryLineArray)
+	{
+		// DOn't bother if not allocated yet
+		return;
+	}
+
+	for (int i= 0; i< DEBUGGERY_LINE_ARRAY_SIZE; i++ )
+	{
+		if ( DebuggeryLineArray[ i ].in_use )
+		{
+			DebuggeryLineArray[ i ].v0 += v0;
+			DebuggeryLineArray[ i ].v1 += v0;
+		}
+	}
+
+#endif		// __PLAT_NGC__
+#endif
+}
+
+
+/*	Adds a line to our debug line buffer, to be drawn every frame
+	until the buffer wraps around again...
+*/						   
+void AddDebugLine( const Mth::Vector &v0, const Mth::Vector &v1, int rgb0, int rgb1, int numDrawFrames )
+{
+#ifdef	__DEBUG_CODE__
+#ifndef __PLAT_NGC__
+	
+    static int DebuggeryLineIndex = 0;
+	
+	if (!DebuggeryLineArray)
+	{
+		if (!Config::GotExtraMemory())
+		{
+			return;
+		}
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+		DebuggeryLineArray  = (SDebuggeryLine *) Mem::Malloc(DEBUGGERY_LINE_ARRAY_SIZE * sizeof (SDebuggeryLine));
+		Mem::Manager::sHandle().PopContext();
+		DebuggeryLines_CleanUp( );	  //initialize it
+		
+	}
+	
+//	Dbg_MsgAssert((v0-v1).Length() < 1000,("Suspiciously long line...."));
+
+	if ( !DebuggeryLinesInitialized )
+	{
+		DebuggeryLines_CleanUp( );
+		DebuggeryLinesInitialized = 1;
+	}
+
+	if ( rgb1 == SAME_COLOR_AS_RGB0_PLEASE )
+	{
+		rgb1 = rgb0;
+	}
+	
+	DebuggeryLineArray[ DebuggeryLineIndex ].v0 = v0;
+	
+	DebuggeryLineArray[ DebuggeryLineIndex ].v1 = v1;
+													 
+	DebuggeryLineArray[ DebuggeryLineIndex ].rgb0 = rgb0;
+	DebuggeryLineArray[ DebuggeryLineIndex ].rgb1 = rgb1;
+
+	DebuggeryLineArray[ DebuggeryLineIndex ].in_use = 1;
+	
+	DebuggeryLineArray[ DebuggeryLineIndex ].num_draw_frames = numDrawFrames;
+															  
+	if ( ++DebuggeryLineIndex >= DEBUGGERY_LINE_ARRAY_SIZE )
+	{
+		DebuggeryLineIndex = 0;
+	}
+	
+	sActive = true;
+	
+#endif		// __PLAT_NGC__
+#endif
+} // end of AddDebugLine( )
+
+
+void AddDebugArrow( const Mth::Vector &v0, const Mth::Vector &v1, int rgb0, int rgb1, int numDrawFrames )
+{
+#ifdef	__DEBUG_CODE__
+#ifndef __PLAT_NGC__
+	
+    static int DebuggeryLineIndex = 0;
+	
+	if (!DebuggeryLineArray)
+	{
+		if (!Config::GotExtraMemory())
+		{
+			return;
+		}
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+		DebuggeryLineArray  = (SDebuggeryLine *) Mem::Malloc(DEBUGGERY_LINE_ARRAY_SIZE * sizeof (SDebuggeryLine));
+		Mem::Manager::sHandle().PopContext();
+		DebuggeryLines_CleanUp( );	  //initialize it
+		
+	}
+	
+//	Dbg_MsgAssert((v0-v1).Length() < 1000,("Suspiciously long line...."));
+
+	if ( !DebuggeryLinesInitialized )
+	{
+		DebuggeryLines_CleanUp( );
+		DebuggeryLinesInitialized = 1;
+	}
+
+	if ( rgb1 == SAME_COLOR_AS_RGB0_PLEASE )
+	{
+		rgb1 = rgb0;
+	}
+	
+	DebuggeryLineArray[ DebuggeryLineIndex ].v0 = v0;
+	
+	DebuggeryLineArray[ DebuggeryLineIndex ].v1 = v1;
+													 
+	DebuggeryLineArray[ DebuggeryLineIndex ].rgb0 = rgb0;
+	DebuggeryLineArray[ DebuggeryLineIndex ].rgb1 = rgb1;
+
+	DebuggeryLineArray[ DebuggeryLineIndex ].in_use = 2;
+	
+	DebuggeryLineArray[ DebuggeryLineIndex ].num_draw_frames = numDrawFrames;
+															  
+	if ( ++DebuggeryLineIndex >= DEBUGGERY_LINE_ARRAY_SIZE )
+	{
+		DebuggeryLineIndex = 0;
+	}
+	
+	sActive = true;
+	
+#endif		// __PLAT_NGC__
+#endif
+} // end of AddDebugLine( )
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void AddDebugStar(Mth::Vector v0, float r, int rgb0, int numDrawFrames)
+{
+#ifdef	__DEBUG_CODE__
+	Mth::Vector v1,v2;
+	
+	v1 = v0; v2 = v0; v1[X]+=r; v2[X]-=r; AddDebugLine(v1,v2,rgb0,rgb0,numDrawFrames);
+	v1 = v0; v2 = v0; v1[Y]+=r; v2[Y]-=r; AddDebugLine(v1,v2,rgb0,rgb0,numDrawFrames);
+	v1 = v0; v2 = v0; v1[Z]+=r; v2[Z]-=r; AddDebugLine(v1,v2,rgb0,rgb0,numDrawFrames);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void AddDebugCircle(Mth::Vector v0, int numPoints, float r, int rgb0, int numDrawFrames)
+{
+#ifdef	__DEBUG_CODE__
+	Mth::Vector v1, v2;
+
+	Mth::Vector fwd( 0.0f, 0.0f, r );
+
+	v1 = v0 + fwd;
+
+	for ( int i = 0; i < numPoints; i++ )
+	{
+		fwd.RotateY( 2.0f * Mth::PI / (float)numPoints );
+		v2 = v0 + fwd;
+
+		AddDebugLine(v1,v2,rgb0,rgb0,numDrawFrames);
+		
+		v1 = v2;
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void AddDebugBone( const Mth::Vector& p1, const Mth::Vector& p2, float red, float green, float blue )
+{
+#ifdef	__DEBUG_CODE__
+	// GJ:  Pretty much the same as AddDebugLine, but with 
+	// a more convenient interface for the skeleton code
+
+	Gfx::AddDebugLine( p1, p2,
+					   MAKE_RGB( (int)(red * 255), (int)(green * 255), (int)(blue * 255) ),
+					   MAKE_RGB( (int)(red * 255), (int)(green * 255), (int)(blue * 255) ),
+					   1 );
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void AddDebugBox( const Mth::Matrix& root, const Mth::Vector& pos, SBBox *pBox, Mth::Vector *pOffset, int numFrames, Mth::Vector *pRot, int rgb )
+{
+#ifdef	__DEBUG_CODE__
+	
+	if (Config::CD())
+	{
+		return;
+	}
+		
+	Mth::Vector box[ 8 ];
+	Mth::Matrix m;
+	int i;
+
+	m = root;
+	if ( pRot )
+	{
+		m.RotateLocal( *pRot );
+	}
+
+	box[ 0 ] = box[ 1 ] = box[ 2 ] = box[ 3 ] = pBox->m_max;
+
+	box[ 1 ][ X ] = pBox->m_min[ X ];
+	box[ 2 ][ Y ] = pBox->m_min[ Y ];
+	box[ 3 ][ Z ] = pBox->m_min[ Z ];
+	
+	box[ 5 ] = box[ 6 ] = box[ 7 ] = box[ 4 ] = pBox->m_min;
+	
+	box[ 5 ][ X ] = pBox->m_max[ X ];
+	box[ 6 ][ Y ] = pBox->m_max[ Y ];
+	box[ 7 ][ Z ] = pBox->m_max[ Z ];
+	
+	
+	for ( i = 0; i < 8; i++ )
+	{
+//		float tempY = box[ i ][ Y ];
+//		box[ i ][ Y ] = box[ i ][ Z ];
+//		box[ i ][ Z ] = -tempY;
+		if ( pOffset )
+		{
+			box[ i ] += *pOffset;
+		}
+		box[ i ] = m.Transform( box[ i ] );
+		box[ i ] += pos;
+	}
+
+	for ( i = 1; i < 4; i++ )
+	{
+		Gfx::AddDebugLine( box[ 0 ], box[ i ], rgb, rgb, numFrames );
+	}
+	for ( i = 5; i < 8; i++ )
+	{
+		Gfx::AddDebugLine( box[ 4 ], box[ i ], rgb, rgb, numFrames );
+	}
+	// fill in the cracks:
+	Gfx::AddDebugLine( box[ 1 ], box[ 6 ], rgb, rgb, numFrames );
+	Gfx::AddDebugLine( box[ 1 ], box[ 7 ], rgb, rgb, numFrames );
+
+	Gfx::AddDebugLine( box[ 2 ], box[ 5 ], rgb, rgb, numFrames );
+	Gfx::AddDebugLine( box[ 2 ], box[ 7 ], rgb, rgb, numFrames );
+
+	Gfx::AddDebugLine( box[ 3 ], box[ 5 ], rgb, rgb, numFrames );
+	Gfx::AddDebugLine( box[ 3 ], box[ 6 ], rgb, rgb, numFrames );
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void DebugGfx_Draw( void )
+{	
+#ifdef	__DEBUG_CODE__
+	DebuggeryLines_Draw( );
+   
+//	Mdl::FrontEnd* p_frontend = Mdl::FrontEnd::Instance();
+//	if( !p_frontend->GamePaused())
+	{
+		DebuggeryRects_Draw(Tmr::FrameLength());	 
+	}	
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void DebugGfx_CleanUp( void )
+{
+#ifdef	__DEBUG_CODE__
+	
+	
+	DebuggeryLines_CleanUp( );
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
+
+
+
diff --git a/Code/Gfx/debuggfx.h b/Code/Gfx/debuggfx.h
new file mode 100644
index 0000000..b52fef3
--- /dev/null
+++ b/Code/Gfx/debuggfx.h
@@ -0,0 +1,104 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			GFX														**
+**																			**
+**	File name:		debuggfx.h												**
+**																			**
+**	Created: 		11/01/00	-	mjd										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__DEBUG_GFX_H
+#define	__DEBUG_GFX_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#include 
+#include 
+#include 
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Gfx
+{
+
+// use this macro to create the last two params to AddDebugLine( )
+#define MAKE_RGBA( r, g, b, a )	( ( ( a ) << 24 ) | ( ( b ) << 16 ) | ( ( g ) << 8 ) | ( r ) )
+#define MAKE_RGB( r, g, b )		( ( ( 255 ) << 24 ) | ( ( b ) << 16 ) | ( ( g ) << 8 ) | ( r ) )
+#define GET_R( rgba ) 	( ( ( rgba ) ) & 255 )
+#define GET_G( rgba ) 	( ( ( rgba ) >> 8 ) & 255 )
+#define GET_B( rgba ) 	( ( ( rgba ) >> 16 ) & 255 )
+#define GET_A( rgba ) 	( ( ( rgba ) >> 24 ) & 255 )
+										
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+// if i put them here, would they be private any more?
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+#define SAME_COLOR_AS_RGB0_PLEASE ( 1 << 31 )
+
+/*	Adds a line to our debug line buffer, to be drawn every frame
+	until the buffer wraps around again.
+*/						   
+void AddDebugStar(Mth::Vector v0, float r = 12.0f, int rgb0 = MAKE_RGB( 127, 0, 127 ), int numDrawFrames = 0);
+
+void AddDebugCircle(Mth::Vector v0, int numPoints, float r, int rgb0, int numDrawFrames);
+
+void AdjustDebugLines( const Mth::Vector &v0);
+void AddDebugLine( const Mth::Vector &v0, const Mth::Vector &v1, int rgb0 = MAKE_RGB( 127, 127, 127 ), int rgb1 = SAME_COLOR_AS_RGB0_PLEASE, int numDrawFrames = 0 );
+void AddDebugArrow( const Mth::Vector &v0, const Mth::Vector &v1, int rgb0 = MAKE_RGB( 127, 127, 127 ), int rgb1 = SAME_COLOR_AS_RGB0_PLEASE, int numDrawFrames = 0 );
+
+// For drawing skeleton
+void AddDebugBone( const Mth::Vector& p1, const Mth::Vector& p2, float red = 1.0f, float green = 0.5f, float blue = 0.5f );
+
+// For drawing bounding box
+void AddDebugBox( const Mth::Matrix& root, const Mth::Vector& pos, SBBox *pBox, Mth::Vector *pOffset, int numFrames, Mth::Vector *pRot, int rgb = MAKE_RGB( 200, 0, 0 ) );
+
+// Call every frame from the renderer.
+void DebugGfx_Draw( void );
+
+// Cleanup code for this module:
+void DebugGfx_CleanUp( void );
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Gfx
+
+#endif	// __DEBUG_GFX_H
+
diff --git a/Code/Gfx/gfxman.cpp b/Code/Gfx/gfxman.cpp
new file mode 100644
index 0000000..619bcb8
--- /dev/null
+++ b/Code/Gfx/gfxman.cpp
@@ -0,0 +1,264 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics (GFX)		 									**
+**																			**
+**	File name:		gfxman.cpp												**
+**																			**
+**	Created:		07/26/99	-	mjb										**
+**																			**
+**	Description:	Graphics device manager									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+Dbg_DefineProject ( GfxLib, "Graphics Library" )
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+uint32 Gfx_LastVBlank = 0;
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Gfx Manager" )
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+void		Manager::s_timer_code( const Tsk::Task< Manager >& task )
+{
+	
+	
+	
+	Dbg_AssertType ( &task, Tsk::Task< Manager > );
+
+	Manager& gfx_manager = task.GetData();
+
+	gfx_manager.m_time += (Tmr::Time) (int) (( Tmr::FrameLength() * 60.0f ) * 
+										( Tmr::vRESOLUTION / Config::FPS() ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGC__
+int gDumpMem = 0;
+#endif		// __PLAT_NGC__
+
+void		Manager::s_start_render_code ( const Tsk::Hook< Manager >& hook )
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::s_end_render_code ( const Tsk::Hook< Manager >& hook )
+{
+
+// Note: not currently called.... just left in to show the timing stuff....
+	
+	Dbg_AssertType ( &hook, Tsk::Hook< Manager > );
+	Manager&	gfx_manager = hook.GetData();
+    
+	uint64 this_vblank;
+	
+	do
+	{
+		this_vblank = Tmr::GetVblanks();
+	}
+	while(( this_vblank - Gfx_LastVBlank ) < gfx_manager.m_min_vblank_wait );
+	Gfx_LastVBlank = this_vblank;
+
+	Tmr::OncePerRender();   		// update the frame counter
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::start_engine( void )
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::stop_engine( void )
+{
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+Manager::Manager ( void )
+{
+	
+
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+
+	m_render_start_hook = new Tsk::Hook< Manager > ( s_start_render_code, *this );
+	mlp_man->RegisterRenderStartHook ( m_render_start_hook );
+
+	m_render_end_hook = new Tsk::Hook< Manager > ( s_end_render_code, *this );
+	mlp_man->RegisterRenderEndHook ( m_render_end_hook );
+
+	m_timer_task = new Tsk::Task< Manager > ( s_timer_code, *this );
+	m_timer_task->SetMask(1<<3);
+	mlp_man->AddLogicTask( *m_timer_task );
+		
+	m_min_vblank_wait = 0;
+
+	m_metrics_active = false;
+	m_vram_viewer_active = false;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager ( void )
+{
+	
+	
+	Dbg_AssertType ( m_render_start_hook, Tsk::Hook< Manager > );
+	delete m_render_start_hook;
+
+	Dbg_AssertType ( m_render_end_hook, Tsk::Hook< Manager > );
+	delete m_render_end_hook;
+
+	Dbg_AssertType( m_timer_task, Tsk::Task< Manager > );
+	delete m_timer_task;
+
+#ifdef __NOPT_ASSERT__
+	Dbg_SetScreenAssert( false );
+#endif
+
+	stop_engine();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void	Manager::ToggleMetrics( void )
+{
+	
+	
+	m_metrics_active = !m_metrics_active;
+
+#ifdef		__USE_PROFILER__
+	if( m_metrics_active )
+	{
+		Sys::Profiler::sEnable();
+	}
+	else
+	{
+		Sys::Profiler::sDisable();
+	}
+#endif
+}
+
+#ifdef __NOPT_ASSERT__
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::AssertText ( int line, const char* text )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::AssertFlush( void )
+{
+// not needed...	
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#endif /* __NOPT_ASSERT__ */
+
+} // namespace Gfx
diff --git a/Code/Gfx/gfxman.h b/Code/Gfx/gfxman.h
new file mode 100644
index 0000000..b023bcf
--- /dev/null
+++ b/Code/Gfx/gfxman.h
@@ -0,0 +1,181 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics  (GFX)											**
+**																			**
+**	File name:		gfx/gfxman.h											**
+**																			**
+**	Created: 		07/26/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_GFXMAN_H
+#define __GFX_GFXMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+			  
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define USE_SKIN
+
+namespace Gfx
+{
+
+#define OBJECT_RENDER_FLAG_BASIC_ATOMIC		( 1 << 0 )
+#define OBJECT_RENDER_FLAG_SKIN_ATOMIC		( 1 << 1 )
+#define OBJECT_RENDER_FLAG_PARTICLE_ATOMIC	( 1 << 2 )
+#define OBJECT_RENDER_FLAG_HUD_ATOMIC		( 1 << 3 )
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+     
+class  Manager  : public Spt::Class
+{
+	
+	
+public :
+		
+
+//	static const int vDEFAULT_VIDEO_HEIGHT = SCREEN_CONV_Y( 448 );
+// Mick, this only applies to the virtual viewport, so don't convert for PAL	
+	static const int vDEFAULT_VIDEO_HEIGHT = ( 448 );
+
+//	static 	Image::RGBA				sBackgroundColor; 
+
+
+	void						ToggleMetrics( void );
+	void						ToggleVRAMViewer( void );
+	void						DumpVRAMUsage( void );
+	void						SetTrivialFarZClip( bool on )	{ m_trivial_far_z_clip = on; }
+
+//	Metrics*					GetMetrics( void );
+#ifdef __NOPT_ASSERT__
+	void						AssertText( int line, const char* text );
+	void						AssertFlush( void );
+#endif
+
+#	ifdef __PLAT_XBOX__
+	void						SetGammaNormalized( float fr, float fg, float fb );
+	void						GetGammaNormalized( float *fr, float *fg, float *fb );
+#	endif
+
+	void						ScreenShot( const char *fileroot );
+	void						DumpMemcardScreeenshots();
+	
+
+	void						SetMinVblankWait( int num_vblanks );
+
+	// used by other graphics system to update displays
+	// This is used instead of GetTime() because this will
+	// stop incrementing when the game is paused
+	Tmr::Time					GetGfxTime( void ) {return m_time; }
+													
+private :
+
+								~Manager( void );
+								Manager( void );
+	
+	static	Tsk::Hook< Manager >::Code	s_start_render_code;
+	static	Tsk::Hook< Manager >::Code	s_end_render_code;
+	static	Tsk::Task< Manager >::Code	s_timer_code;
+
+	Tsk::Hook< Manager >*		m_render_start_hook;
+	Tsk::Hook< Manager >*		m_render_end_hook;
+	Tsk::Task< Manager >*		m_timer_task;
+
+	void						start_engine( void );
+	void						stop_engine( void );
+	
+//	Metrics*					m_metrics;
+	bool						m_metrics_active;
+	bool						m_vram_viewer_active;
+	bool						m_trivial_far_z_clip;
+
+
+	uint32						m_min_vblank_wait;
+	Tmr::Time					m_time;	// used by other graphics system to update displays
+										// This is used instead of GetTime() because this will
+										// stop incrementing when the game is paused
+	
+	DeclareSingletonClass(Manager)
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void	Manager::ToggleVRAMViewer( void )
+{
+	
+	
+	m_vram_viewer_active = !m_vram_viewer_active;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline	void	Manager::SetMinVblankWait( int num_vblanks )
+{
+	m_min_vblank_wait = num_vblanks;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
+#endif	// __GFX_GFXMAN_H
diff --git a/Code/Gfx/gfxutils.cpp b/Code/Gfx/gfxutils.cpp
new file mode 100644
index 0000000..cac21a9
--- /dev/null
+++ b/Code/Gfx/gfxutils.cpp
@@ -0,0 +1,439 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       GfxUtils.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  02/01/2001
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   PrivateFunctions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void RGBtoHSV( float r, float g, float b, float *h, float *s, float *v )
+{
+	float min, max, delta;
+	min = Mth::Min3( r, g, b );
+	max = Mth::Max3( r, g, b );
+	*v = max;				// v
+	delta = max - min;
+	if( max != 0.0f )
+		*s = delta / max;		// s
+	else {
+		// r = g = b = 0		// s = 0, v is undefined
+		*s = 0.0f;
+		*h = -1.0f;
+		return;
+	}
+
+	// GJ:
+	if (delta == 0.0f)
+		return;
+
+	if( r == max )
+		*h = ( g - b ) / delta;		// between yellow & magenta
+	else if( g == max )
+		*h = 2.0f + ( b - r ) / delta;	// between cyan & yellow
+	else
+		*h = 4.0f + ( r - g ) / delta;	// between magenta & cyan
+	*h *= 60.0f;				// degrees
+	if( *h < 0.0f )
+		*h += 360.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void HSVtoRGB( float *r, float *g, float *b, float h, float s, float v )
+{
+	int i;
+	float f, p, q, t;
+	if( s == 0.0f ) {
+		// achromatic (grey)
+		*r = *g = *b = v;
+		return;
+	}
+	h /= 60.0f;			// sector 0 to 5
+	i = (int)h;			// basically, the floor
+	f = h - i;			// factorial part of h
+	p = v * ( 1.0f - s );
+	q = v * ( 1.0f - s * f );
+	t = v * ( 1.0f - s * ( 1.0f - f ) );
+	switch( i ) {
+		case 0:
+			*r = v;
+			*g = t;
+			*b = p;
+			break;
+		case 1:
+			*r = q;
+			*g = v;
+			*b = p;
+			break;
+		case 2:
+			*r = p;
+			*g = v;
+			*b = t;
+			break;
+		case 3:
+			*r = p;
+			*g = q;
+			*b = v;
+			break;
+		case 4:
+			*r = t;
+			*g = p;
+			*b = v;
+			break;
+		default:		// case 5:
+			*r = v;
+			*g = p;
+			*b = q;
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float FRAMES_TO_TIME(int frames)
+{
+	return (frames / 60.0f);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int TIME_TO_FRAMES(float time)
+{
+	return (int)(time * 60.0f + 0.5f);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void GetModelFromFileName ( char* filename, char* pModelNameBuf )
+{
+	char* pModelName = NULL;
+	while (*filename)
+	{
+		if (*filename == '\\' || *filename == '/')
+		{
+			pModelName = filename + 1;
+		}
+		filename++;
+	}
+
+	Dbg_MsgAssert ( pModelName,( "Not full path name" ));
+
+	strcpy(pModelNameBuf, pModelName);
+
+	// strip out extension
+	for (unsigned int i = 0; i < strlen(pModelNameBuf); i++)
+	{
+		if (pModelNameBuf[i] == '.')
+		{
+			pModelNameBuf[i] = 0;
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool GetScaleFromParams( Mth::Vector* pScaleVector, Script::CStruct* pParams )
+{
+	if ( pParams->ContainsComponentNamed( CRCD(0x13b9da7b,"scale") ) )
+	{
+		float scaleValue;
+		if ( pParams->GetFloat( CRCD(0x13b9da7b,"scale"), &scaleValue, Script::NO_ASSERT ) )
+		{
+			*pScaleVector = Mth::Vector( scaleValue, scaleValue, scaleValue );
+			return true;
+		}
+		else if ( pParams->GetVector( CRCD(0x13b9da7b,"scale"), pScaleVector, Script::NO_ASSERT ) )
+		{
+			return true;
+		}
+		else
+		{
+			Dbg_MsgAssert( 0, ( "Scale should be either a float or a vector" ) );
+		}
+	}
+		
+	bool xFound = pParams->GetFloat( CRCD(0x7323e97c,"x"), &(*pScaleVector)[X], false );
+	bool yFound = pParams->GetFloat( CRCD(0x424d9ea,"y"), &(*pScaleVector)[Y], false );
+	bool zFound = pParams->GetFloat( CRCD(0x9d2d8850,"z"), &(*pScaleVector)[Z], false );
+
+	return ( xFound || yFound || zFound );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool GetLoopingTypeFromParams( Gfx::EAnimLoopingType* pLoopingType, Script::CStruct* pParams )
+{
+	*pLoopingType = Gfx::LOOPING_HOLD;
+	
+	if ( pParams->ContainsFlag( CRCD(0x4f792e6c,"Cycle") ) )
+	{
+		*pLoopingType=Gfx::LOOPING_CYCLE;
+	}
+	else if ( pParams->ContainsFlag( CRCD(0x3153e314,"PingPong") ) )
+	{
+		*pLoopingType=Gfx::LOOPING_PINGPONG;
+	}
+	else if ( pParams->ContainsFlag( CRCD(0x6d941203,"Wobble") ) )
+	{
+		*pLoopingType=Gfx::LOOPING_WOBBLE;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool GetTimeFromParams( float* pStart, float* pEnd, float Current, float Duration, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	float From = 0.0f;
+	float To = Duration;
+
+	uint32 FromChecksum=0;
+	if ( pParams->GetChecksum( CRCD(0x46e55e8f,"From"), &FromChecksum ) )
+	{
+		switch (FromChecksum)
+		{
+		case 0x6086aa70: // Start
+			From=0;
+			break;
+		case 0xff03cc4e: // End
+			From=Duration;
+			break;
+		case 0x230ccbf4: // Current
+			From=Current;
+			break;
+		case 0x617fe530: // Middle
+			From=Duration / 2.0f;
+			break;	
+		default:
+			Dbg_MsgAssert(0,("\n%s\nUnrecognized value '%s' for From in PlayAnim",pScript?pScript->GetScriptInfo():"???",Script::FindChecksumName(FromChecksum)));
+			break;
+		}
+	}		
+	
+	uint32 ToChecksum=0;
+	if (pParams->GetChecksum( CRCD(0x28782d3b,"To"), &ToChecksum) )
+	{
+		switch (ToChecksum)
+		{
+		case 0x6086aa70: // Start
+			To=0;
+			break;
+		case 0xff03cc4e: // End
+			To=Duration;
+			break;
+		case 0x230ccbf4: // Current
+			To=Current;
+			break;	
+		case 0x617fe530: // Middle
+			To=Duration / 2.0f;
+			break;	
+		default:
+			Dbg_MsgAssert(0,("\n%s\nUnrecognized value '%s' for To in PlayAnim",pScript?pScript->GetScriptInfo():"???",Script::FindChecksumName(ToChecksum)));
+			break;
+		}
+	}		
+	
+	// also check if From and To were specified as integers, in which case use units of 60ths.
+	float FromFrames=0;
+	if (pParams->GetFloat( CRCD(0x46e55e8f,"From"), &FromFrames ) )
+	{
+		if ( pParams->ContainsFlag( CRCD(0xd029f619,"seconds") ) )
+		{
+			From = FromFrames;
+		}
+		else
+		{
+			From = Gfx::FRAMES_TO_TIME((int)FromFrames);
+		}
+	}
+		
+	float ToFrames=0;
+	if ( pParams->GetFloat( CRCD(0x28782d3b,"To"), &ToFrames ) )
+	{
+		if ( pParams->ContainsFlag( CRCD(0xd029f619,"seconds") ) )
+		{
+			To = ToFrames;
+		}
+		else
+		{
+			To = Gfx::FRAMES_TO_TIME((int)ToFrames);
+		}
+	}
+	
+	if ( pParams->ContainsFlag( CRCD(0xf8cfd515,"Backwards") ) )
+	{
+		float Temp=From;
+		From=To;
+		To=Temp;
+	}	
+
+	*pStart = From;
+	*pEnd = To;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String GetModelFileName(const char* pName, const char* pExtension)
+{
+	char fullName[512];
+
+	if ( strstr( pName, "/" ) || strstr( pName, "\\" ) )
+	{
+		Dbg_MsgAssert( strstr( pName, "." ), ( "Filename %s is missing extension", pName ) ); 
+		
+		sprintf( fullName, "models\\%s", pName ); 
+	}
+	else
+	{
+		sprintf( fullName, "models\\%s\\%s%s", pName, pName, pExtension );
+	}
+
+#ifdef __NOPT_ASSERT__
+	// replace all forward slashes with backslashes
+	char temp[512];
+	strcpy( temp, fullName );
+	char* pString = temp;
+	while ( *pString )
+	{
+		if ( *pString == '/' )
+		{
+			*pString = '\\';
+		}
+		pString++;
+	}
+	// look for double backslashes, which are bad
+	Dbg_MsgAssert( !strstr( temp, "\\\\" ), ( "Filename %s has double backslash", temp ) ); 
+#endif
+
+	return Str::String( fullName );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void GetModelFileName(const char* pName, const char* pExtension, char* pTarget)
+{
+	if ( strstr( pName, "/" ) || strstr( pName, "\\" ) )
+	{
+		Dbg_MsgAssert( strstr( pName, "." ), ( "Filename %s is missing extension", pName ) ); 
+		
+		Dbg_MsgAssert( pTarget, ( "No target buffer" ) );
+		sprintf( pTarget, "models\\%s", pName ); 
+	}
+	else
+	{
+		Dbg_MsgAssert( pTarget, ( "No target buffer" ) );
+		sprintf( pTarget, "models\\%s\\%s%s", pName, pName, pExtension );
+	}
+	
+#ifdef __NOPT_ASSERT__
+	// replace all forward slashes with backslashes
+	char temp[512];
+	strcpy( temp, pTarget );
+	char* pString = temp;
+	while ( *pString )
+	{
+		if ( *pString == '/' )
+		{
+			*pString = '\\';
+		}
+		pString++;
+	}
+	// look for double backslashes, which are bad
+	Dbg_MsgAssert( !strstr( temp, "\\\\" ), ( "Filename %s has double backslash", temp ) ); 
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
diff --git a/Code/Gfx/gfxutils.h b/Code/Gfx/gfxutils.h
new file mode 100644
index 0000000..1c29bf2
--- /dev/null
+++ b/Code/Gfx/gfxutils.h
@@ -0,0 +1,177 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       GfxUtils.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  02/01/2001
+//****************************************************************************
+
+#ifndef __GFX_UTILS_H
+#define __GFX_UTILS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+#include 
+
+#include 
+	
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mth
+{
+	class Vector;
+};
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+};
+
+namespace Gfx
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+void RGBtoHSV( float r, float g, float b, float *h, float *s, float *v );
+void HSVtoRGB( float *r, float *g, float *b, float h, float s, float v );
+
+void inlineRGBtoHSV( float r, float g, float b, float *h, float *s, float *v );
+void inlineHSVtoRGB( float *r, float *g, float *b, float h, float s, float v );
+
+// some time conversion functions
+float FRAMES_TO_TIME(int frames);
+int TIME_TO_FRAMES(float time);
+
+void 		GetModelFromFileName( char* filename, char* pModelNameBuf );
+bool 		GetScaleFromParams( Mth::Vector* pScaleVector, Script::CStruct* pParams );
+bool 		GetLoopingTypeFromParams( Gfx::EAnimLoopingType* pLoopingType, Script::CStruct* pParams );
+bool 		GetTimeFromParams( float* pStart, float* pEnd, float Current, float Duration, Script::CStruct* pParams, Script::CScript* pScript );
+Str::String	GetModelFileName(const char* pName, const char* pExt);
+void    	GetModelFileName(const char* pName, const char* pExt, char* pTargetBuffer);
+
+/*****************************************************************************
+**						Inline Functions									**
+*****************************************************************************/
+					
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void inlineRGBtoHSV( float r, float g, float b, float &h, float &s, float &v )
+{
+	float min, max, delta;
+	min = Mth::Min3( r, g, b );
+	max = Mth::Max3( r, g, b );
+	v = max;				// v
+	delta = max - min;
+	if( max != 0.0f )
+		s = delta / max;		// s
+	else {
+		// r = g = b = 0		// s = 0, v is undefined
+		s = 0.0f;
+		h = -1.0f;
+		return;
+	}
+
+	// GJ:
+	if (delta == 0.0f)
+		return;
+
+	if( r == max )
+		h = ( g - b ) / delta;		// between yellow & magenta
+	else if( g == max )
+		h = 2.0f + ( b - r ) / delta;	// between cyan & yellow
+	else
+		h = 4.0f + ( r - g ) / delta;	// between magenta & cyan
+	h *= 60.0f;				// degrees
+	if( h < 0.0f )
+		h += 360.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void inlineHSVtoRGB( float &r, float &g, float &b, float h, float s, float v )
+{
+	if( s == 0.0f ) {
+		// achromatic (grey)
+		r = g = b = v;
+		return;
+	}
+
+	int i;
+	float f, p, q, t;
+
+	h *= (1.0f/60.0f);	// sector 0 to 5
+	i = (int)h;			// basically, the floor
+	f = h - i;			// factorial part of h
+	p = v * ( 1.0f - s );
+	q = v * ( 1.0f - s * f );
+	t = v * ( 1.0f - s * ( 1.0f - f ) );
+	switch( i ) {
+		case 0:
+			r = v;
+			g = t;
+			b = p;
+			break;
+		case 1:
+			r = q;
+			g = v;
+			b = p;
+			break;
+		case 2:
+			r = p;
+			g = v;
+			b = t;
+			break;
+		case 3:
+			r = p;
+			g = q;
+			b = v;
+			break;
+		case 4:
+			r = t;
+			g = p;
+			b = v;
+			break;
+		default:		// case 5:
+			r = v;
+			g = p;
+			b = q;
+			break;
+	}
+}
+
+} // namespace Gfx
+
+#endif // __GFX_UTILS_H
\ No newline at end of file
diff --git a/Code/Gfx/nx.cpp b/Code/Gfx/nx.cpp
new file mode 100644
index 0000000..bd794ba
--- /dev/null
+++ b/Code/Gfx/nx.cpp
@@ -0,0 +1,1324 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NX.CPP - Platform independent interface to the platfrom specific engine code 
+
+#include 
+
+#include 
+#include 
+
+#include 
+                               
+#include 	   
+#include 	   
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#ifdef __PLAT_NGC__
+#include 
+#endif		// __PLAT_NGC__
+
+//#include 		// For getting list of movable objects
+
+#include 
+
+#ifdef	__PLAT_NGPS__
+namespace NxPs2
+{
+	extern int geom_stats_total;
+	extern int geom_stats_inactive ;
+	extern int geom_stats_sky;
+	extern int geom_stats_transformed;
+	extern int geom_stats_skeletal;
+	extern int geom_stats_camera_sphere;
+	extern int geom_stats_culled;
+	extern int geom_stats_leaf_culled;
+	extern int geom_stats_clipcull;
+	extern int geom_stats_boxcheck;
+	extern int geom_stats_occludecheck;
+	extern int geom_stats_occluded;
+	extern int geom_stats_colored;
+	extern int geom_stats_leaf;
+	extern int geom_stats_wibbleUV;
+	extern int geom_stats_wibbleVC;
+	extern int geom_stats_sendcontext;
+	extern int geom_stats_sorted;
+	extern int geom_stats_shadow;
+
+	extern bool DoLetterbox;
+}
+#endif
+
+namespace	Nx
+{
+
+///////////////////////////////////////////////////////////////////
+// Static definitions
+
+CScene *						CEngine::sp_loaded_scenes[CEngine::MAX_LOADED_SCENES];
+Lst::HashTable< CParticle >*	CEngine::p_particle_table = NULL;
+uint32							CEngine::s_next_avail_sector_checksum = 1;	// 0 is invalid
+uint32							CEngine::s_render_mode;
+uint32							CEngine::s_wireframe_mode;
+uint32							CEngine::s_screen_blur;
+uint32							CEngine::s_debug_ignore_1;
+uint32							CEngine::s_debug_ignore_0;
+Obj::CGeneralManager*			CEngine::sp_moving_object_manager;
+CImposterManager*				CEngine::mp_imposter_manager	= NULL;
+CNewParticleManager*			CEngine::mp_particle_manager	= NULL;
+CWeather*						CEngine::mp_weather = NULL;
+
+/******************************************************************/
+/*                                                                */
+/* get a sector pointer, based on the checksum of the name		  */
+/*                                                                */
+/******************************************************************/
+
+CSector	*		CEngine::sGetSector(uint32 sector_checksum)
+{
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (sp_loaded_scenes[i])
+		{
+			CSector *ret_sector = sp_loaded_scenes[i]->GetSector(sector_checksum);
+			if (ret_sector) return ret_sector;
+		}
+	}
+
+	// If we got here, we didn't find anything
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sStartEngine()
+{		
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		Dbg_Assert(!sp_loaded_scenes[i]);	// Make sure it was deallocated
+	}
+
+	// Allocate CTexts and CWindow2Ds
+	Nx::CTextMan::sAllocTextPool();
+	Nx::CWindow2DManager::sAllocWindow2DPool();
+
+	CViewportManager::sSetScreenAspect(4.0f/3.0f);
+	CViewportManager::sSetDefaultAngle( Script::GetFloat("camera_fov") );
+	CViewportManager::sSetScreenAngle( 0.0f);		// set to default
+
+	CViewportManager::sInit();
+	CViewportManager::sSetScreenMode(vONE_CAM);
+
+	//sSetMaxMultipassDistance(3000.0f);
+
+	// Allocate particle system table
+	p_particle_table = new Lst::HashTable( 6 ); 
+						 
+	// Create the imposter manager.
+	mp_imposter_manager = new CImposterManager;
+
+	// Startup effects allocations.
+	Nx::MiscFXInitialize();
+
+	s_plat_start_engine();				
+
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sSuspendEngine()
+{
+	printf ("STUB: SuspendEngine\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sResumeEngine()
+{
+	printf ("STUB: ResumeEngine\n");
+}
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sPreRender()
+{
+	s_plat_pre_render();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sPostRender()
+{
+	s_plat_post_render();
+
+	// Clear away any old cameras
+	CViewportManager::sDeleteMarkedCameras();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sRenderWorld()
+{
+	process_particles( Tmr::FrameLength() ); 
+
+	ScreenFlashUpdate();
+	TextureSplatUpdate();
+	ShatterUpdate();
+	
+	CLightManager::sUpdateVCLights();
+	
+	s_plat_render_world();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene	*		CEngine::sCreateScene(const char *p_name, CTexDict *p_tex_dict,
+									  bool add_super_sectors)
+{
+	CScene *p_created_scene = s_plat_create_scene(p_name, p_tex_dict, add_super_sectors);
+
+	p_created_scene->SetID(Script::GenerateCRC(p_name)); 	// store the checksum of the scene name
+
+	p_created_scene->SetTexDict(p_tex_dict);					  
+
+	// Put in scene array
+	int i;
+	for (i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (!sp_loaded_scenes[i])
+		{
+			sp_loaded_scenes[i] = p_created_scene;
+			break;
+		}
+	}
+
+	Dbg_MsgAssert(i < MAX_LOADED_SCENES, ("Have more than MAX_LOADED SCENES"));
+
+	return p_created_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene	*		CEngine::sLoadScene(const char *p_name, CTexDict *p_tex_dict,
+									bool add_super_sectors, bool is_sky, bool is_dictionary, bool is_net)
+{
+	// first expand the scene file name out to include the path
+	// the level directory, and the platform specific extension
+	// e.g.,  "can" become levels\can\can.scn.ps2
+
+	
+
+	char	full_name[128];
+#ifdef __PLAT_NGPS__
+	sprintf(full_name,"levels\\%s\\%s%s.geom.%s",p_name,p_name,is_net?"_net":"",sGetPlatformExtension());
+#else
+	sprintf(full_name,"levels\\%s\\%s%s.scn.%s",  p_name,p_name,is_net?"_net":"",sGetPlatformExtension());
+#endif
+
+	Mem::PushMemProfile((char*)full_name);
+
+	CScene *loaded_scene = s_plat_load_scene(full_name, p_tex_dict, add_super_sectors, is_sky, is_dictionary);
+
+	loaded_scene->SetID(Script::GenerateCRC(p_name)); 	// store the checksum of the scene name
+
+	loaded_scene->SetTexDict(p_tex_dict);					  
+					  
+	// Do post-load processing
+	loaded_scene->PostLoad(full_name);
+
+	// Put in scene array
+	int i;
+	for (i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (!sp_loaded_scenes[i])
+		{
+			sp_loaded_scenes[i] = loaded_scene;
+			break;
+		}
+	}
+
+	Dbg_MsgAssert(i < MAX_LOADED_SCENES, ("Have more than MAX_LOADED SCENES"));
+	
+	Mem::PopMemProfile(/*p_name*/);
+
+	return loaded_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScene	*		CEngine::sAddScene(const char *p_scene_name, const char *p_filename)
+{
+	// first expand the scene file name out to include the path
+	// the level directory, and the platform specific extension
+	// e.g.,  "can" become levels\can\can.scn.ps2
+
+	char	full_name[128];
+#ifdef __PLAT_NGPS__
+	sprintf(full_name,"levels\\%s\\%s.geom.%s",p_filename,p_filename,sGetPlatformExtension());
+#else
+	sprintf(full_name,"levels\\%s\\%s.scn.%s",p_filename,p_filename,sGetPlatformExtension());
+#endif
+
+	char	texture_dict_name[128];
+	sprintf(texture_dict_name,"levels\\%s\\%s.tex",p_filename,p_filename);
+
+	CScene *p_scene = sGetScene(Script::GenerateCRC(p_scene_name));
+
+	Dbg_MsgAssert(p_scene, ("sAddScene(): Can't find existing scene %s", p_scene_name));
+
+	Dbg_Message("Adding to scene %s with file %s", p_scene_name, full_name);
+
+	// Check if we need to unload old one
+	if (strcmp(p_scene->GetAddSceneFilename(), full_name) == 0)
+	{
+		Dbg_Message("Unloading old added scene %s", full_name);
+		p_scene->UnloadAddScene();
+
+		Nx::CTexDict * p_old_tex_dict = Nx::CTexDictManager::sGetTextureDictionary(Crc::GenerateCRCFromString(texture_dict_name));
+		if (p_old_tex_dict)		// See if we previously loaded this
+		{
+			Dbg_Message("Unloading old texture dictionary %s", texture_dict_name);
+			Nx::CTexDictManager::sUnloadTextureDictionary(p_old_tex_dict);
+		} else {
+			Dbg_Assert(0);
+		}
+	}
+
+	Nx::CTexDict * p_tex_dict = Nx::CTexDictManager::sLoadTextureDictionary(texture_dict_name,true);
+	Dbg_MsgAssert(p_tex_dict, ("ERROR loading tex dict for %s\n",texture_dict_name));
+
+	Dbg_Message("Adding to scene %s with file %s", p_scene_name, full_name);
+
+	s_plat_add_scene(p_scene, full_name);
+
+	// Do post-add processing
+	p_scene->PostAdd(full_name, p_tex_dict);
+
+	return p_scene;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sToggleAddScenes()
+{
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (sp_loaded_scenes[i])
+		{
+			sp_loaded_scenes[i]->ToggleAddScene();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CEngine::sUnloadScene(CScene *p_scene)
+{
+
+	// Wait for the frame to finish rendering before we unload the scene
+	// otherwise we might be trying to render something that is no longer there	
+	// (Without this line, the engine can hang when doing a quickview, and seems
+	// to be more crashy when using particle systems)
+	Nx::CEngine::sFinishRendering();	
+	
+	// Remove Incremental Data first
+	// (Doesn't work yet because the replace CSectors are lost and the texture dictionary is still around)
+	p_scene->UnloadAddScene();
+
+	// remove platform specific assets
+	if (s_plat_unload_scene(p_scene))
+	{
+//#ifdef __PLAT_NGC__
+//		CNgcScene *p_ngc_scene = (CNgcScene*)p_scene;
+//		NxNgc::sScene * p_engine_scene = p_ngc_scene->GetEngineScene();
+//#endif		// __PLAT_NGC__
+
+		// delete the scene object itself. 
+		delete	p_scene;
+
+//#ifdef __PLAT_NGC__
+//		// Get the engine specific scene data and pass it to the engine to delete.
+//		NxNgc::DeleteScene( p_engine_scene );
+//		p_ngc_scene->SetEngineScene( NULL );
+//#endif		// __PLAT_NGC__
+
+		// and remove it from the list of scenes
+		for (int i = 0; i < MAX_LOADED_SCENES; i++)
+		{
+			if (sp_loaded_scenes[i] == p_scene)
+			{
+				sp_loaded_scenes[i] = NULL;
+				return true;
+			}
+		}
+	}
+
+	Dbg_Error("Could not find CScene to unload");
+	return false;
+}
+
+
+// (Not working yet!!!)							  
+// This is useful function to be only used during development
+// it attempts to reload just the .scn part of a level
+// without reloading the texture dictionary (assumes it stays the same)
+// and without reloading the collision (assumes you are in viewer mode, and don't need it)  				  
+bool			CEngine::sQuickReloadGeometry(const char *p_scene_name)
+{	
+	// find the scene
+	Nx::CScene	*p_scene = sGetScene(p_scene_name);
+	
+	if (p_scene)
+	{
+		CTexDict *p_tex_dict = p_scene->GetTexDict();
+		bool	is_sky = p_scene->IsSky();
+		
+		sUnloadScene(p_scene);
+		/*Nx::CScene	*p_new_scene = */ sLoadScene(p_scene_name,p_tex_dict,!is_sky,is_sky);
+//		sUnloadScene(p_scene);
+//		p_new_scene = sLoadScene(p_scene_name,p_tex_dict,!is_sky,is_sky);
+		
+		return true;
+	}
+	return false;
+}
+
+
+// Given the id of a scene, then return the CScene that has this id
+// returns NULL if scene not found
+CScene *		CEngine::sGetScene(uint32 id)
+{
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (sp_loaded_scenes[i] && (sp_loaded_scenes[i]->GetID() == id))
+		{
+			return sp_loaded_scenes[i];
+		}
+	}
+	return NULL;
+}
+
+
+CScene *		CEngine::sGetScene(const char *p_name)
+{
+	return		sGetScene(Script::GenerateCRC(p_name));
+}
+
+
+// Get the last non-sky scene.  Kind of application dependent, as it assumes 
+// that the "Main" scene is the last non sky scene
+// but that's a pretty safe bet for what we are doing now.
+// (Mick:  I'm only using this for the "Disco" cheat in THPS4)
+// (Mick: now this function is used in ParseNodeArray, so I had to change this from
+// using the first non-sky to the last non-sky, so now it's even dodgier....
+// we need a better way of saying what is the "main" scene
+CScene *  		CEngine::sGetMainScene()
+{
+	for (int i = MAX_LOADED_SCENES-1; i >= 0; i--)
+	{
+		if (sp_loaded_scenes[i] && !sp_loaded_scenes[i]->IsSky())
+		{
+			return sp_loaded_scenes[i];
+		}
+	}
+	return NULL;	
+}
+
+// Get the first sky scene.
+CScene *  		CEngine::sGetSkyScene()
+{
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (sp_loaded_scenes[i] && sp_loaded_scenes[i]->IsSky())
+		{
+			return sp_loaded_scenes[i];
+		}
+	}
+	return NULL;	
+}
+
+  
+// Mick:::::   ........  
+// a temporary utility function
+// unloads all loaded scenes, (level and sky)
+// and also unloads the texture dictionaries
+// (or at least decrements the reference counts)
+bool		CEngine::sUnloadAllScenesAndTexDicts()
+{
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		CScene * p_scene = sp_loaded_scenes[i];
+		if (p_scene)
+		{
+			CTexDict *p_tex_dict = p_scene->GetTexDict();
+
+    		sUnloadScene(p_scene);
+			// must unload dictionary after scene
+			if (p_tex_dict)
+			{
+				CTexDictManager::sUnloadTextureDictionary(p_tex_dict);
+			}
+		}
+	}
+
+	// Remove occlusion polys.
+	s_plat_remove_all_occlusion_polys();
+	
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSprite *		CEngine::sCreateSprite(CWindow2D *p_window)
+{
+	return s_plat_create_sprite(p_window);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CEngine::sDestroySprite(CSprite *p_sprite)
+{
+	return s_plat_destroy_sprite(p_sprite);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTextured3dPoly *CEngine::sCreateTextured3dPoly()
+{
+	return s_plat_create_textured_3d_poly();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CEngine::sDestroyTextured3dPoly(CTextured3dPoly *p_poly)
+{
+	return s_plat_destroy_textured_3d_poly(p_poly);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTexture *CEngine::sCreateRenderTargetTexture( int width, int height, int depth, int z_depth )
+{
+	return s_plat_create_render_target_texture( width, height, depth, z_depth );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::sProjectTextureIntoScene( Nx::CTexture *p_texture, Nx::CModel *p_model, Nx::CScene *p_scene )
+{
+	// usually we don't want to know the scene, so we pass in NULL
+	// and let the engine figure it out
+	// if we have multiple scenes, then we would need to figure this out externally
+
+	if (!p_scene)
+	{
+		// find the first non-sky scene
+		for (int i = 0; i < MAX_LOADED_SCENES; i++)
+		{
+			if (sp_loaded_scenes[i] && !sp_loaded_scenes[i]->IsSky())
+			{
+				p_scene = sp_loaded_scenes[i];
+				break;
+			}
+		}
+	}
+
+	// It no longer matters if the scene is NULL.
+	s_plat_project_texture_into_scene( p_texture, p_model, p_scene );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEngine::sSetProjectionTextureCamera( Nx::CTexture *p_texture, Gfx::Camera *p_camera )
+{
+	s_plat_set_projection_texture_camera( p_texture, p_camera );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::sStopProjectionTexture( Nx::CTexture *p_texture )
+{
+	s_plat_stop_projection_texture( p_texture );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::sAddOcclusionPoly( uint32 num_verts, Mth::Vector *p_vert_array, uint32 checksum )
+{
+	s_plat_add_occlusion_poly( num_verts, p_vert_array, checksum );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::sEnableOcclusionPoly( uint32 checksum, bool enable )
+{
+	s_plat_enable_occlusion_poly( checksum, enable );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEngine::sDebugCheckForHoles()
+{
+
+	Tmr::Time start_time, end_time;
+	start_time = Tmr::GetTime();
+
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (sp_loaded_scenes[i] && !sp_loaded_scenes[i]->IsSky())
+		{
+			sp_loaded_scenes[i]->DebugCheckForHoles();
+		}
+	}
+
+	end_time = Tmr::GetTime();
+
+	Dbg_Message("CheckForHoles() took %d msecs", end_time - start_time);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CModel*			CEngine::sInitModel(void)
+{
+	return s_plat_init_model();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CEngine::sUninitModel(CModel* pModel)
+{
+	// Wait for any async rendering to finish before we proceed	
+#ifndef __PLAT_NGC__
+	sFinishRendering();
+#endif		// __PLAT_NGC__
+	return s_plat_uninit_model(pModel);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGeom*			CEngine::sInitGeom()
+{
+	return s_plat_init_geom();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CEngine::sUninitGeom(CGeom* pGeom)
+{
+	// Wait for any async rendering to finish before we proceed	
+#ifndef __PLAT_NGC__
+	sFinishRendering();
+#endif		// __PLAT_NGC__
+	return s_plat_uninit_geom(pGeom);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CQuickAnim*		CEngine::sInitQuickAnim(void)
+{
+	return s_plat_init_quick_anim();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+			
+void			CEngine::sUninitQuickAnim(CQuickAnim* pQuickAnim)
+{
+	s_plat_uninit_quick_anim(pQuickAnim);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMesh*			CEngine::sLoadMesh(const char* pFileName, uint32 texDictOffset, bool forceTexDictLookup, bool doShadowVolume)
+{
+	char baseName[512];
+	strcpy( baseName, pFileName );
+
+	char* pEnd;
+
+	bool foundSkin = false;
+	pEnd = strstr( baseName, ".skin" );
+	foundSkin = ( pEnd != NULL );
+	if ( foundSkin )
+	{
+		*pEnd = NULL;
+	}
+
+	bool foundModel = false;
+	pEnd = strstr( baseName, ".mdl" );
+	foundModel = ( pEnd != NULL );
+	if ( foundModel )
+	{
+		*pEnd = NULL;
+	}
+
+	Dbg_Assert( foundSkin || foundModel );
+
+	char meshName[512];
+	Str::LowerCase( meshName );
+	sprintf( meshName, "%s.%s.%s", baseName, foundSkin ? "skin" : "mdl", sGetPlatformExtension() );
+
+	char textureName[512];
+	sprintf( textureName, "%s.tex", baseName );
+
+	// brad - special case for boardshop boards.
+	// (ideally, this would be coming in from a
+	// higher level, such as with the
+	// "forceTexDictLookup" flag
+	if ( strstr( meshName, "thps4board_01" ) )
+	{
+		forceTexDictLookup = true;
+	}
+	
+	Nx::CTexDict* pTexDict = Nx::CTexDictManager::sLoadTextureDictionary(textureName, false, texDictOffset, foundSkin, forceTexDictLookup);
+	Dbg_MsgAssert(pTexDict,("ERROR loading tex dict for %s\n",textureName));
+
+	Mem::PushMemProfile((char*)meshName);	
+	CMesh* p_mesh = s_plat_load_mesh(meshName, pTexDict, texDictOffset, foundSkin, doShadowVolume);
+	Mem::PopMemProfile(/*(char*)meshname*/);	
+	return p_mesh;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMesh*			CEngine::sLoadMesh(uint32 id, uint32* pModelData, int modelDataSize, uint8* pCASData, uint32 textureDictChecksum, uint32* pTextureData, int textureDataSize, uint32 textureDictOffset, bool isSkin, bool doShadowVolume )
+{
+	Dbg_MsgAssert( id != 0, ( "invalid id checksum" ) );
+
+	bool forceTexDictLookup = false;
+
+	Nx::CTexDict* pTexDict = Nx::CTexDictManager::sLoadTextureDictionary(textureDictChecksum, pTextureData, textureDataSize, false, textureDictOffset, isSkin, forceTexDictLookup);
+	Dbg_MsgAssert(pTexDict,("ERROR loading tex dict from buffer"));
+
+	Mem::PushMemProfile((char*)"model_data_buffer");	
+	CMesh*	p_mesh = s_plat_load_mesh(id, pModelData, modelDataSize, pCASData, pTexDict, textureDictOffset, isSkin, doShadowVolume);
+	Mem::PopMemProfile(/*(char*)meshname*/);	
+	return p_mesh;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			CEngine::sUnloadMesh(CMesh* pMesh)
+{
+	CTexDict *p_tex_dict = pMesh->GetTextureDictionary();
+	
+	bool result = s_plat_unload_mesh(pMesh);
+
+	CTexDictManager::sUnloadTextureDictionary( p_tex_dict );
+
+	return result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CEngine::sSetMeshScalingParameters( SMeshScalingParameters* pParams )
+{
+	s_plat_set_mesh_scaling_parameters( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Set the moving object manager.  Perviously we asked the skater module for 
+// this, but now we are independent of Skate, so we have to be told
+
+void	CEngine::sSetMovableObjectManager(Obj::CGeneralManager* p_object_manager)
+{
+	Dbg_MsgAssert(p_object_manager,("NULL Movable Object Manager"));
+	sp_moving_object_manager = p_object_manager;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Lst::Head & CEngine::sGetMovableObjects()
+{
+
+	Dbg_MsgAssert( sp_moving_object_manager, ("NULL sp_moving_object_manager"));
+	return sp_moving_object_manager->GetRefObjectList();
+}
+
+Lst::HashTable< CParticle > * CEngine::sGetParticleTable()
+{
+	return p_particle_table;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SSec::Manager *	CEngine::sGetNearestSuperSectorManager(Mth::Line &collision_line)
+{
+	// For now, just return the first manager
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (sp_loaded_scenes[i])
+		{ 
+			SSec::Manager *p_man = sp_loaded_scenes[i]->GetSuperSectorManager();
+			if (p_man) return p_man;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SSec::Manager *	CEngine::sGetNearestSuperSectorManager(Mth::Vector &collision_point)
+{
+	// For now, just return the first manager
+	for (int i = 0; i < MAX_LOADED_SCENES; i++)
+	{
+		if (sp_loaded_scenes[i])
+		{ 
+			SSec::Manager *p_man = sp_loaded_scenes[i]->GetSuperSectorManager();
+			if (p_man) return p_man;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32			CEngine::sGetUnusedSectorChecksum()
+{
+	// Make sure this checksum isn't being used
+	while (sGetSector(s_next_avail_sector_checksum))
+		s_next_avail_sector_checksum++;
+
+	return s_next_avail_sector_checksum++;
+}
+
+/******************************************************************/
+/*                                                                */
+/* return platform specifc extension, like ".PS2"				  */
+/*                                                                */
+/******************************************************************/
+
+const char *	CEngine::sGetPlatformExtension()
+{
+	return s_plat_get_platform_extension();
+}
+
+/******************************************************************/
+// Wait for any pending asyncronous rendering to finish, so rendering
+// data can be unloaded
+// (Might be a wait, or a flush, depending on platform)
+// This function should generally return immediatly if there is no
+// pending rendering.  However, it might stall for up to a frame if
+// there are things waiting to render.
+// so if it is to be used in a real-time situation, then you want to
+// call it as late as possible in the frame (preferably the last thing)
+// so it incurs as little wait as possible
+// this might be accomplished by making the "task" that uses it
+// be as low priority as possible
+/******************************************************************/					  
+
+void	CEngine::sFinishRendering()
+{
+	s_plat_finish_rendering();
+}
+
+
+void				CEngine::sToggleRenderMode()
+{
+	s_render_mode++;
+	if (s_render_mode == 4)
+	{
+		s_render_mode = 0;	
+	}
+}
+
+
+void				CEngine::sSetDebugIgnore(uint32 ignore_1, uint32 ignore_0)
+{
+	s_debug_ignore_0 = ignore_0;
+	s_debug_ignore_1 = ignore_1;
+}
+
+bool				CEngine::sIsVisible(Mth::Vector ¢er, float radius)
+{
+	// PATCH for multiple viewports
+	// always return true, until we sort out the per-viewport visibility (which might not be needed)				   
+	if (Nx::CViewportManager::sGetNumActiveViewports() > 1)
+	{
+		return true;
+	}
+
+	return s_plat_is_visible(center,radius);
+}
+
+void				CEngine::sSetMaxMultipassDistance(float dist)
+{
+	s_plat_set_max_multipass_distance(dist);
+}
+
+void				CEngine::sSetScreenBlur(uint32 amount)
+{
+	Dbg_MsgAssert(amount >=0 && amount <=255,("Screen blur amount out of range of 0..255 (it's %d)",amount));
+	s_screen_blur = amount;
+	s_plat_set_screen_blur(amount);
+}
+
+uint32 CEngine::sGetScreenBlur()
+{
+	uint32	amount = s_screen_blur; 
+	Dbg_MsgAssert(amount >=0 && amount <=255,("s_screen_blur out of range of 0..255 (it's %d), maybe corrupted???",amount));
+	return amount;	
+}
+
+int CEngine::sGetNumSoundtracks()
+{
+	return s_plat_get_num_soundtracks();
+}
+
+const char* CEngine::sGetSoundtrackName( int soundtrack_number )
+{
+	const char* p_soundtrack_name_wide = s_plat_get_soundtrack_name( soundtrack_number );
+	return p_soundtrack_name_wide;
+}
+
+// Added by Ken for use by replay code.
+// 
+int CEngine::sWriteSectorStatusBitfield(uint32 *p_bitfield, int numUint32s)
+{
+	return 0;
+	
+	// Removed since we no longer have saving of replays to mem card.
+	// Leaving this code in was causing a problem in that the passed numUint32s
+	// was too small for the Skillzilla Park. Rather than increasing numUint32's
+	// and hence using up more memory, it was safer just to remove the contents of this function.
+	#if 0
+	Dbg_MsgAssert(p_bitfield,("NULL p_bitfield"));
+
+	int i;
+	for (i=0; i *p_table=sp_loaded_scenes[i]->GetSectorList();
+			Dbg_MsgAssert(p_table,("NULL p_table"));
+			
+			p_table->IterateStart();
+			CSector *p_sector=p_table->IterateNext();
+			while (p_sector)
+			{
+				if (p_sector->GetActiveAtReplayStart())
+				{
+					Dbg_MsgAssert(long_index=32)
+				{
+					bit_index=0;
+					++long_index;
+				}	
+
+				if (p_sector->GetVisibleAtReplayStart())
+				{
+					Dbg_MsgAssert(long_index=32)
+				{
+					bit_index=0;
+					++long_index;
+				}	
+				
+				p_sector=p_table->IterateNext();
+			}
+		}
+	}	
+	
+	return long_index*32+bit_index;
+	#endif
+}
+
+void CEngine::sReadSectorStatusBitfield(uint32 *p_bitfield, int numUint32s)
+{
+	return;
+	
+	// Removed since we no longer have saving of replays to mem card.
+	// Leaving this code in was causing a problem in that the passed numUint32s
+	// was too small for the Skillzilla Park. Rather than increasing numUint32's
+	// and hence using up more memory, it was safer just to remove the contents of this function.
+	#if 0
+	Dbg_MsgAssert(p_bitfield,("NULL p_bitfield"));
+
+	int long_index=0;
+	int bit_index=0;
+	
+	for (int i = 0; i *p_table=sp_loaded_scenes[i]->GetSectorList();
+			Dbg_MsgAssert(p_table,("NULL p_table"));
+			
+			p_table->IterateStart();
+			CSector *p_sector=p_table->IterateNext();
+			while (p_sector)
+			{
+				Dbg_MsgAssert(long_indexSetActiveAtReplayStart(true);
+				}	
+				else
+				{
+					p_sector->SetActiveAtReplayStart(false);
+				}	
+				
+				++bit_index;
+				if (bit_index>=32)
+				{
+					bit_index=0;
+					++long_index;
+				}	
+
+				Dbg_MsgAssert(long_indexSetVisibleAtReplayStart(true);
+				}	
+				else
+				{
+					p_sector->SetVisibleAtReplayStart(false);
+				}	
+				
+				++bit_index;
+				if (bit_index>=32)
+				{
+					bit_index=0;
+					++long_index;
+				}	
+				
+				p_sector=p_table->IterateNext();
+			}
+		}
+	}	
+	#endif
+}
+
+void CEngine::sInitReplayStartState()
+{
+	//printf("Calling sInitReplayStartState()\n");
+	for (int i = 0; i *p_table=sp_loaded_scenes[i]->GetSectorList();
+			Dbg_MsgAssert(p_table,("NULL p_table"));
+			
+			p_table->IterateStart();
+			CSector *p_sector=p_table->IterateNext();
+			while (p_sector)
+			{
+				p_sector->SetActiveAtReplayStart(p_sector->IsActive());
+				p_sector->SetVisibleAtReplayStart(p_sector->GetVisibility()&0x01);
+				// Just to be safe
+				p_sector->SetStoredActive(p_sector->IsActive());
+				p_sector->SetStoredVisible(p_sector->GetVisibility()&0x01);
+				
+				p_sector=p_table->IterateNext();
+			}
+		}
+	}	
+}
+
+void CEngine::sPrepareSectorsForReplayPlayback(bool store_initial_state)
+{
+	//printf("Calling sPrepareSectorsForReplayPlayback()\n");
+	for (int i = 0; i *p_table=sp_loaded_scenes[i]->GetSectorList();
+			Dbg_MsgAssert(p_table,("NULL p_table"));
+			
+			p_table->IterateStart();
+			CSector *p_sector=p_table->IterateNext();
+			while (p_sector)
+			{
+				if (store_initial_state)
+				{
+					p_sector->SetStoredActive(p_sector->IsActive());
+					p_sector->SetStoredVisible(p_sector->GetVisibility()&0x01);
+				}	  
+				  		
+				p_sector->SetActive(p_sector->GetActiveAtReplayStart());
+				p_sector->SetVisibility(p_sector->GetVisibleAtReplayStart()?0xff:0x00);
+				
+				p_sector=p_table->IterateNext();
+			}
+		}
+	}	
+}
+
+void CEngine::sRestoreSectorsAfterReplayPlayback()
+{
+	//printf("Calling sRestoreSectorsAfterReplayPlayback()\n");
+	for (int i = 0; i *p_table=sp_loaded_scenes[i]->GetSectorList();
+			Dbg_MsgAssert(p_table,("NULL p_table"));
+			
+			p_table->IterateStart();
+			CSector *p_sector=p_table->IterateNext();
+			while (p_sector)
+			{
+				p_sector->SetActive(p_sector->GetStoredActive());
+				p_sector->SetVisibility(p_sector->GetStoredVisible()?0xff:0x00);
+				
+				p_sector=p_table->IterateNext();
+			}
+		}
+	}	
+}
+
+bool ScriptToggleAddScenes(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Just call engine function
+	CEngine::sToggleAddScenes();
+
+	return true;
+}
+
+
+bool ScriptSetScreenBlur(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int amount = 0;
+	pParams->GetInteger(NONAME,&amount,false);
+	CEngine::sSetScreenBlur(amount);
+	return true;
+}
+
+
+// Given a script structure, add some metrics to it
+// like number of objects, polygons, collision polygons, etc
+void	CEngine::sGetMetrics(Script::CStruct * p_info)
+{
+
+	// Do main scene first, as it's more interesting
+	if (sGetMainScene())
+	{
+		Script::CStruct *p_main = new Script::CStruct();
+		sGetMainScene()->GetMetrics(p_main);
+		p_info->AddStructurePointer(CRCD(0x9b10525c,"MainScene"),p_main);
+	}
+		
+	// But also do the sky, just in case												  
+	if (sGetSkyScene())
+	{
+		Script::CStruct *p_sky = new Script::CStruct();
+		sGetSkyScene()->GetMetrics(p_sky);
+		p_info->AddStructurePointer(CRCD(0x2c5ea34,"SkyScene"),p_sky);
+	}
+
+	#ifdef	__PLAT_NGPS__
+	
+
+		Script::CStruct *p_ps2 = new Script::CStruct();
+		p_ps2->AddInteger(CRCD(0x3dd01ea1,"total"), NxPs2::geom_stats_total);        
+		p_ps2->AddInteger(CRCD(0x742cf11f,"inactive"), NxPs2::geom_stats_inactive );    
+		p_ps2->AddInteger(CRCD(0xf9d98b10,"sky"), NxPs2::geom_stats_sky);          
+		p_ps2->AddInteger(CRCD(0x6d65d103,"transformed"), NxPs2::geom_stats_transformed);  
+		p_ps2->AddInteger(CRCD(0x52aa1a77,"skeletal"), NxPs2::geom_stats_skeletal);     
+		p_ps2->AddInteger(CRCD(0xaf792e8d,"camera_sphere"), NxPs2::geom_stats_camera_sphere);
+		p_ps2->AddInteger(CRCD(0xa863e48c,"clipcull"), NxPs2::geom_stats_clipcull);     
+		p_ps2->AddInteger(CRCD(0x93070c4b,"culled"), NxPs2::geom_stats_culled);     
+		p_ps2->AddInteger(CRCD(0xac43724a,"leaf_culled"), NxPs2::geom_stats_leaf_culled);     
+		p_ps2->AddInteger(CRCD(0x68306ffe,"boxcheck"), NxPs2::geom_stats_boxcheck);     
+		p_ps2->AddInteger(CRCD(0xae81c5f6,"occludecheck"), NxPs2::geom_stats_occludecheck); 
+		p_ps2->AddInteger(CRCD(0xba802ab0,"occluded"), NxPs2::geom_stats_occluded);     
+		p_ps2->AddInteger(CRCD(0x1bc830f2,"colored"), NxPs2::geom_stats_colored);      
+		p_ps2->AddInteger(CRCD(0x3960ff18,"leaf"), NxPs2::geom_stats_leaf);         
+		p_ps2->AddInteger(CRCD(0x506a239f,"wibbleUV"), NxPs2::geom_stats_wibbleUV);     
+		p_ps2->AddInteger(CRCD(0x169a94b7,"wibbleVC"), NxPs2::geom_stats_wibbleVC);     
+		p_ps2->AddInteger(CRCD(0x7b471ba2,"sendcontext"), NxPs2::geom_stats_sendcontext);  
+		p_ps2->AddInteger(CRCD(0x18725397,"sorted"), NxPs2::geom_stats_sorted);       
+		p_ps2->AddInteger(CRCD(0x8a897dd2,"shadow"), NxPs2::geom_stats_shadow);       
+		
+		p_info->AddStructurePointer(CRCD(0xf2316b47,"PS2_Info"),p_ps2);
+
+		s_plat_get_metrics(p_info);
+
+#endif
+
+
+}
+					 					  
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void	CEngine::sSetLetterbox(bool letterbox)
+{
+	s_plat_set_letterbox( letterbox );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void	CEngine::sSetColorBufferClear(bool clear)
+{
+	s_plat_set_color_buffer_clear( clear );
+}
+
+
+
+} // namespace Nx
+
+
+
+
diff --git a/Code/Gfx/nx.h b/Code/Gfx/nx.h
new file mode 100644
index 0000000..4c84693
--- /dev/null
+++ b/Code/Gfx/nx.h
@@ -0,0 +1,310 @@
+///////////////////////////////////////////////////////////////////////////////////
+// NX.H - Neversoft Engine, Rendering portion, Platform independent interface
+
+#ifndef	__GFX_NX_H__
+#define	__GFX_NX_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+	class CObject;
+}
+
+namespace Gfx
+{
+	class Camera;
+}
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+
+namespace Nx
+{
+	// forward declarations
+	class CGeom;
+	class CMesh;
+	class CModel;
+	class CQuickAnim;
+	class CWindow2D;
+	class CParticle;
+
+// GJ:  The following is for doing cutscene head scaling...
+// These parameters will only be active during the loading
+// of the next CMesh.
+struct SMeshScalingParameters
+{
+	char* 			pWeightIndices;
+	float* 			pWeights;
+	Mth::Vector* 	pBonePositions;
+	Mth::Vector* 	pBoneScales;
+};
+
+////////////////////////////////////////////////////////////////////////
+// Globally accessible Engine functions ar public static functions, 
+// just use them like:
+//
+//  Nx::CSector * p_sector = Nx::CEngine::sGetSector(checksum);
+//
+// // NOT // I might change this later so the engine is a class, so it would become:
+// // NOT // Nx::CSector * p_sector = Nx::CEngine::Instance()->GetSector(checksum);
+
+
+// The engine class is all static
+class CEngine
+{
+public:
+	static CSector	*			sGetSector(uint32 sector_checksum);			// get a sector pointer, based on the checksum of the name
+	static void					sStartEngine();
+	static void					sSuspendEngine();
+	static void					sResumeEngine();
+	static void					sPreRender();
+	static void					sPostRender();
+	static void					sRenderWorld();
+	
+	static void					sFinishRendering();
+
+	static void					sSetScreenBlur(uint32 amount);	
+	static uint32				sGetScreenBlur();	
+	
+	static CScene	*			sCreateScene(const char *p_name, CTexDict *p_tex_dict,
+											 bool add_super_sectors = true);	// creates an empty scene
+	static CScene	*			sLoadScene(const char *p_name, CTexDict *p_tex_dict, bool add_super_sectors = true,
+										   bool is_sky = false, bool is_dictionary = false, bool is_net=false);	// load a platform specific scene file
+	static CScene	*			sAddScene(const char *p_scene_name, const char *p_filename);	// add a scene file to an existing scene
+	static void					sToggleAddScenes();
+	static bool					sUnloadScene(CScene *p_scene);
+	static bool					sQuickReloadGeometry(const char *p_scene_name);	
+
+	static SSec::Manager * 		sGetNearestSuperSectorManager(Mth::Line &);		// Returns the closest SuperSector Manager
+	static SSec::Manager * 		sGetNearestSuperSectorManager(Mth::Vector &);	// Returns the closest SuperSector Manager
+
+	static	const char *		sGetPlatformExtension();   					// return platform specifc extension, like ".PS2"
+	
+	static bool					sUnloadAllScenesAndTexDicts();				// unloads all loaded scenes and their associated texture dicts
+	
+	static	CScene *			sGetScene(uint32 id);		  				// get scene, given the checksum id of the name
+	static	CScene *			sGetScene(const char *p_name);				// get scene, given name
+	static 	CScene *  			sGetMainScene();							// get first non-sky scene
+	static 	CScene *  			sGetSkyScene();								// get first sky scene
+
+	static CSprite *			sCreateSprite(CWindow2D *p_window = NULL);
+	static bool					sDestroySprite(CSprite *p_sprite);
+								
+	static CTextured3dPoly *	sCreateTextured3dPoly();
+	static bool					sDestroyTextured3dPoly(CTextured3dPoly *p_poly);
+	
+											  
+	static CTexture *			sCreateRenderTargetTexture( int width, int height, int depth, int z_depth );
+	static void					sProjectTextureIntoScene( Nx::CTexture *p_texture, Nx::CModel *p_model, Nx::CScene *p_scene = NULL);
+	static void					sSetProjectionTextureCamera( Nx::CTexture *p_texture, Gfx::Camera *p_camera );
+	static void					sStopProjectionTexture( Nx::CTexture *p_texture );
+
+	static void					sAddOcclusionPoly( uint32 num_verts, Mth::Vector *p_vert_array, uint32 checksum = 0 );
+	static void					sEnableOcclusionPoly( uint32 checksum, bool enable );
+	
+	static CModel*				sInitModel();
+	static bool					sUninitModel(CModel* pModel);
+
+	static CGeom*				sInitGeom();
+	static bool					sUninitGeom(CGeom* pGeom);
+
+	static CQuickAnim*			sInitQuickAnim();
+	static void					sUninitQuickAnim(CQuickAnim* pQuickAnim);
+
+	static CMesh*				sLoadMesh(const char* pFileName, uint32 texDictOffset, bool forceTexDictLookup, bool doShadowVolume);
+	static CMesh*				sLoadMesh(uint32 id, uint32* pModelData, int modelDataSize, uint8* pCASData, uint32 textureDictChecksum, uint32* pTextureData, int textureDataSize, uint32 textureDictOffset, bool isSkin, bool doShadowVolume );
+	static bool					sUnloadMesh(CMesh* pMesh);
+
+	static void					sSetMeshScalingParameters( SMeshScalingParameters* pParams );
+
+	static	void				sSetMovableObjectManager(Obj::CGeneralManager* p_object_manager);
+	static Lst::Head & sGetMovableObjects();					// Returns the list of all movable objects
+	static 	Lst::HashTable< CParticle > * sGetParticleTable();
+
+	static CImposterManager*	sGetImposterManager( void )	{ return mp_imposter_manager; }
+	static CNewParticleManager*	sGetParticleManager( void )	{ return mp_particle_manager; }
+
+	static uint32				sGetUnusedSectorChecksum();					// Gets a unique sector checksum for cloning
+
+	static bool					sIsVisible(Mth::Vector ¢er, float radius);										
+										
+	static void					sSetMaxMultipassDistance(float dist);		// Sets distance at which multipass will stop drawing
+
+	static void					sSetLetterbox(bool lettterbox);
+
+	static void					sSetColorBufferClear(bool clear);				// Indicates whether a per-frame color buffer clear is required (in addition to z-buffer clear).
+	static CWeather*			sGetWeather( void ) { return mp_weather; }
+
+	// K: Added so that park editor can update the grid after geometry has changed.
+	static bool					ScriptWeatherUpdateGrid( Script::CStruct* pParams, Script::CScript* pScript ) { mp_weather->UpdateGrid(); return true; }
+	
+	static bool					ScriptWeatherSetRainHeight( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetRainHeight( pParams, pScript ); }
+	static bool					ScriptWeatherSetRainFrames( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetRainFrames( pParams, pScript ); } 
+	static bool					ScriptWeatherSetRainLength( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetRainLength( pParams, pScript ); } 
+	static bool					ScriptWeatherSetRainBlendMode( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetRainBlendMode( pParams, pScript ); } 
+	static bool					ScriptWeatherSetRainRate( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetRainRate( pParams, pScript ); } 
+	static bool					ScriptWeatherSetRainColor( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetRainColor( pParams, pScript ); }  
+
+	static bool					ScriptWeatherSetSplashRate( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSplashRate( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSplashLife( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSplashLife( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSplashSize( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSplashSize( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSplashColor( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSplashColor( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSplashBlendMode( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSplashBlendMode( pParams, pScript ); } 
+
+	static bool					ScriptWeatherSetSnowHeight( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSnowHeight( pParams, pScript ); }
+	static bool					ScriptWeatherSetSnowFrames( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSnowFrames( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSnowSize( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSnowSize( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSnowBlendMode( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSnowBlendMode( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSnowRate( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSnowRate( pParams, pScript ); } 
+	static bool					ScriptWeatherSetSnowColor( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSnowColor( pParams, pScript ); }  
+	
+	static bool					ScriptWeatherSetSnowActive( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetSnowActive( pParams, pScript ); }  
+	static bool					ScriptWeatherSetRainActive( Script::CStruct* pParams, Script::CScript* pScript ) { return mp_weather->ScriptWeatherSetRainActive( pParams, pScript ); }  
+
+	//Some Debugging functions
+	static	void				sToggleRenderMode();
+	static	void				sSetRenderMode(int mode) {s_render_mode = mode;}
+	static	void				sSetWireframeMode(int mode) {s_wireframe_mode = mode;}
+	static	void				sSetDebugIgnore(uint32 ignore_1, uint32 ignore_0);
+	static	void				sDebugCheckForHoles();
+	static  uint32				GetRenderMode() {return s_render_mode;}
+	static  uint32				GetWireframeMode() {return s_wireframe_mode;}
+	
+	// Debug inspection stuff								
+	static	void				sGetMetrics(Script::CStruct* p_info);
+
+	// soundtrack stuff (hook to xbox specific funcs)
+	static	int					sGetNumSoundtracks();
+	static	const char*			sGetSoundtrackName( int soundtrack_number );
+
+	// Added by Ken, for use by replay code, which needs to store the status of the sectors
+	// so that they can be restored when the replay finishes.
+	static	int					sWriteSectorStatusBitfield(uint32 *p_bitfield, int numUint32s);
+	static	void				sReadSectorStatusBitfield(uint32 *p_bitfield, int numUint32s);
+	
+	static	void				sInitReplayStartState();
+	static	void				sPrepareSectorsForReplayPlayback(bool store_initial_state);
+	static	void				sRestoreSectorsAfterReplayPlayback();
+	
+	
+	
+	// Constants
+	enum
+	{
+		MAX_LOADED_SCENES = 4
+	};
+
+private:
+	// Platform specific function calls	
+	static void					s_plat_start_engine();
+	static void					s_plat_suspend_engine();
+	static void					s_plat_resumeEngine();
+	static void					s_plat_pre_render();
+	static void					s_plat_post_render();
+	static void					s_plat_render_world();
+	
+	static void					s_plat_set_screen_blur(uint32 amount);
+
+	static void					s_plat_set_letterbox(bool letterbox);
+	static void					s_plat_set_color_buffer_clear(bool clear);
+			  
+	static CScene	*			s_plat_create_scene(const char *p_name, CTexDict *p_tex_dict,
+													bool add_super_sectors = true);	// creates an empty scene
+	static CScene	*			s_plat_load_scene(const char *p_name, CTexDict *p_tex_dict, bool add_super_sectors,
+												  bool is_sky, bool is_dictionary);		// load a platform specific scene file
+	static CScene	*			s_plat_load_scene_from_memory(void *p_data, CTexDict *p_tex_dict, bool add_super_sectors,
+												  bool is_sky, bool is_dictionary);		// load a platform specific scene file
+	static bool					s_plat_add_scene(CScene *p_scene, const char *p_filename);
+	static bool					s_plat_unload_scene(CScene *p_scene);
+
+	static CSprite *			s_plat_create_sprite(CWindow2D *p_window);
+	static bool					s_plat_destroy_sprite(CSprite *p_sprite);
+
+	static CTextured3dPoly *	s_plat_create_textured_3d_poly();
+	static bool					s_plat_destroy_textured_3d_poly(CTextured3dPoly *p_poly);
+
+	static CTexture *			s_plat_create_render_target_texture( int width, int height, int depth, int z_depth );
+	static void					s_plat_project_texture_into_scene( Nx::CTexture *p_texture, Nx::CModel *p_model, Nx::CScene *p_scene );
+	static void					s_plat_set_projection_texture_camera( Nx::CTexture *p_texture, Gfx::Camera *p_camera );
+	static void					s_plat_stop_projection_texture( Nx::CTexture *p_texture );
+
+	static void					s_plat_add_occlusion_poly( uint32 num_verts, Mth::Vector *p_vert_array, uint32 checksum = 0 );
+	static void					s_plat_enable_occlusion_poly( uint32 checksum, bool enable );
+	static void					s_plat_remove_all_occlusion_polys( void );
+
+	static	const char *		s_plat_get_platform_extension();
+
+	static CModel*				s_plat_init_model();
+	static bool					s_plat_uninit_model(CModel* pModel);
+
+	static CGeom*				s_plat_init_geom();
+	static bool					s_plat_uninit_geom(CGeom* pGeom);
+
+	static CQuickAnim*			s_plat_init_quick_anim();
+	static void					s_plat_uninit_quick_anim(CQuickAnim* pQuickAnim);
+
+	static CMesh*				s_plat_load_mesh(const char* pMeshFileName, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume);
+	static CMesh*				s_plat_load_mesh(uint32 id, uint32* pModelData, int modelDataSize, uint8* pCASData, Nx::CTexDict* pTexDict, uint32 texDictOffset, bool isSkin, bool doShadowVolume);
+	static bool					s_plat_unload_mesh(CMesh* pMesh);
+	
+	static void					s_plat_set_mesh_scaling_parameters( SMeshScalingParameters* pParams );
+
+	static bool					s_plat_is_visible(Mth::Vector& center, float radius);										
+
+	static void					s_plat_set_max_multipass_distance(float dist);
+
+	static	void				s_plat_finish_rendering();
+		
+	static 	int					s_plat_get_num_soundtracks();
+	static	const char*			s_plat_get_soundtrack_name( int soundtrack_number );
+	
+	#ifdef	__PLAT_NGPS__
+	// a debug function just for the PS2 - although we could well have it on all platforms
+	static 	void 				s_plat_get_metrics(Script::CStruct * p_info);
+	#endif
+
+	// Member variables
+	static CScene *				sp_loaded_scenes[MAX_LOADED_SCENES];
+
+	static uint32				s_next_avail_sector_checksum;				// next checksum available for clones
+	
+	static	uint32				s_screen_blur;
+	static	uint32				s_render_mode;
+	static	uint32				s_wireframe_mode;
+	static	uint32				s_debug_ignore_0;
+	static	uint32				s_debug_ignore_1;
+	
+	static 	Lst::HashTable< CParticle > *p_particle_table;
+	static  Obj::CGeneralManager* 		sp_moving_object_manager;
+
+	static CImposterManager*	mp_imposter_manager;	
+	static CNewParticleManager*	mp_particle_manager;
+	static CWeather*			mp_weather;
+};
+
+
+bool ScriptToggleAddScenes(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetScreenBlur(Script::CStruct *pParams, Script::CScript *pScript);
+
+
+}
+
+
+#endif
+
diff --git a/Code/Gfx/nxflags.h b/Code/Gfx/nxflags.h
new file mode 100644
index 0000000..62528c8
--- /dev/null
+++ b/Code/Gfx/nxflags.h
@@ -0,0 +1,125 @@
+///////////////////////////////////////////////////////////////////////////////////////
+// nxflags.h - flag definitions that used to be in nxplugin
+//
+
+#ifndef	__GFX_NXFLAGS_H
+#define	__GFX_NXFLAGS_H
+
+
+// Face flags
+enum
+{
+	mFD_SKATABLE			= 0x00000001,
+	mFD_NOT_SKATABLE		= 0x00000002,
+	mFD_WALL_RIDABLE		= 0x00000004,
+	mFD_VERT				= 0x00000008,
+	mFD_NON_COLLIDABLE		= 0x00000010,
+	mFD_DECAL				= 0x00000020,
+	mFD_TRIGGER				= 0x00000040,
+	mFD_CAMERA_COLLIDABLE	= 0x00000080,
+	mFD_NO_SKATER_SHADOW	= 0x00000100,
+	mFD_SKATER_SHADOW		= 0x00000200,
+	mFD_NO_SKATER_SHADOW_WALL=0x00000400,
+	mFD_UNDER_OK			= 0x00000800,
+    mFD_INVISIBLE			= 0x00001000,
+	mFD_CASFACEFLAGSEXIST   = 0x00002000,
+	mFD_PASS_1_DISABLED     = 0x00004000,
+	mFD_PASS_2_ENABLED      = 0x00008000,
+	mFD_PASS_3_ENABLED      = 0x00010000,
+	mFD_PASS_4_ENABLED      = 0x00020000,
+	mFD_RENDER_SEPARATE		= 0x00040000,
+	mFD_LIGHTMAPPED			= 0x00080000,
+   	mFD_NON_WALL_RIDABLE	= 0x00100000,
+	mFD_NON_CAMERA_COLLIDABLE = 0x00200000,
+	mFD_EXPORT_COLLISION	= 0x00400000,
+};
+
+// Object flags
+enum
+{
+	mSD_INVISIBLE			= 0x0001,	// Invisible in primary viewport
+	mSD_NON_COLLIDABLE		= 0x0002,
+	mSD_KILLED				= 0x0004,
+	mSD_DONT_FOG			= 0x0008,
+	mSD_ALWAYS_FACE			= 0x0010,
+	mSD_NO_SKATER_SHADOW	= 0x0020,	// This is set at runtime for sectors with every face flagged mFD_SKATER_SHADOW.
+	mSD_INVISIBLE2			= 0x0040,	// Invisible in secondary viewport (Mick)
+	mSD_OCCLUDER			= 0x0080,	// Occluder (it's a single plane that hides stuff)
+	mSD_CLONE				= 0x8000,	// Cloned collision object (Garrett)
+};
+
+
+// Material flags
+enum ETerrainType
+{
+	// If you change any of these,
+	// don't forget to change terrain.q
+	// to match!
+	vTERRAIN_DEFAULT, 	
+	vTERRAIN_CONCSMOOTH,	
+	vTERRAIN_CONCROUGH,	
+	vTERRAIN_METALSMOOTH,
+	vTERRAIN_METALROUGH,	
+	vTERRAIN_METALCORRUGATED,	
+	vTERRAIN_METALGRATING,
+	vTERRAIN_METALTIN,	
+	vTERRAIN_WOOD,		
+	vTERRAIN_WOODMASONITE,
+	vTERRAIN_WOODPLYWOOD,
+	vTERRAIN_WOODFLIMSY,	
+	vTERRAIN_WOODSHINGLE,
+	vTERRAIN_WOODPIER,	
+	vTERRAIN_BRICK,		
+	vTERRAIN_TILE,		
+	vTERRAIN_ASPHALT,	
+	vTERRAIN_ROCK,		
+	vTERRAIN_GRAVEL,		
+	vTERRAIN_SIDEWALK,	
+	vTERRAIN_GRASS,		
+	vTERRAIN_GRASSDRIED,	
+	vTERRAIN_DIRT,		
+	vTERRAIN_DIRTPACKED,	
+	vTERRAIN_WATER,		
+	vTERRAIN_ICE,		
+	vTERRAIN_SNOW,		
+	vTERRAIN_SAND,		
+	vTERRAIN_PLEXIGLASS,	
+	vTERRAIN_FIBERGLASS,	
+	vTERRAIN_CARPET,		
+	vTERRAIN_CONVEYOR,	
+	vTERRAIN_CHAINLINK,
+	vTERRAIN_METALFUTURE,
+	vTERRAIN_GENERIC1,	
+	vTERRAIN_GENERIC2,
+	vTERRAIN_WHEELS, // K: Used only by the skateboard wheels, as a means of identifying them so their color can be changed.	
+	vTERRAIN_WETCONC,
+	vTERRAIN_METALFENCE,
+	vTERRAIN_GRINDTRAIN,
+	vTERRAIN_GRINDROPE,
+	vTERRAIN_GRINDWIRE,
+	vTERRAIN_GRINDCONC,			// New as of 7/29/03
+	vTERRAIN_GRINDROUNDMETALPOLE,
+	vTERRAIN_GRINDCHAINLINK,
+	vTERRAIN_GRINDMETAL,
+	vTERRAIN_GRINDWOODRAILING,
+	vTERRAIN_GRINDWOODLOG,
+	vTERRAIN_GRINDWOOD,
+	vTERRAIN_GRINDPLASTIC,
+	vTERRAIN_GRINDELECTRICWIRE,
+	vTERRAIN_GRINDCABLE,
+	vTERRAIN_GRINDCHAIN,
+	vTERRAIN_GRINDPLASTICBARRIER,
+	vTERRAIN_GRINDNEONLIGHT,
+	vTERRAIN_GRINDGLASSMONSTER,
+	vTERRAIN_GRINDBANYONTREE,
+	vTERRAIN_GRINDBRASSRAIL,
+	vTERRAIN_GRINDCATWALK,
+	vTERRAIN_GRINDTANKTURRET,
+
+	// If anyone adds a new terrain so that vNUM_TERRAIN_TYPES > 64, let Steve G know
+	vNUM_TERRAIN_TYPES
+};
+
+
+#endif
+
diff --git a/Code/Gfx/nxparticle.cpp b/Code/Gfx/nxparticle.cpp
new file mode 100644
index 0000000..874d66e
--- /dev/null
+++ b/Code/Gfx/nxparticle.cpp
@@ -0,0 +1,946 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxParticle.cpp
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define next_random() ((((float)rand() / RAND_MAX ) * 2.0f ) - 1.0f)
+
+//NsVector pright;
+//NsVector pup;
+//NsVector pat;
+
+namespace	Nx
+{
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CParticle::plat_render( void )
+{
+	printf ("STUB: plat_render\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CParticle::plat_get_position( int entry, int list, float * x, float * y, float * z )
+{
+	printf ("STUB: plat_get_position\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CParticle::plat_set_position( int entry, int list, float x, float y, float z )
+{
+	printf ("STUB: plat_set_position\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CParticle::plat_add_position( int entry, int list, float x, float y, float z )
+{
+	printf ("STUB: plat_add_position\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CParticle::plat_set_active( bool active )
+{
+	// PS2 & Gamecube do nothing here.
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CParticle::plat_build_path( void )
+{
+	printf ("STUB: plat_build_path\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CParticle::plat_get_num_particle_colors( void ) { printf ("STUB: plat_get_num_particle_colors\n"); return 0; };
+int CParticle::plat_get_num_vertex_lists( void ) { printf ("STUB: plat_get_num_vertex_lists\n"); return 0; };
+void CParticle::plat_set_sr( int entry, uint8 value ) { printf ("STUB: plat_set_sr\n"); }
+void CParticle::plat_set_sg( int entry, uint8 value ) { printf ("STUB: plat_set_sg\n"); }
+void CParticle::plat_set_sb( int entry, uint8 value ) { printf ("STUB: plat_set_sb\n"); }
+void CParticle::plat_set_sa( int entry, uint8 value ) { printf ("STUB: plat_set_sa\n"); }
+void CParticle::plat_set_mr( int entry, uint8 value ) { printf ("STUB: plat_set_mr\n"); }
+void CParticle::plat_set_mg( int entry, uint8 value ) { printf ("STUB: plat_set_mg\n"); }
+void CParticle::plat_set_mb( int entry, uint8 value ) { printf ("STUB: plat_set_mb\n"); }
+void CParticle::plat_set_ma( int entry, uint8 value ) { printf ("STUB: plat_set_ma\n"); }
+void CParticle::plat_set_er( int entry, uint8 value ) { printf ("STUB: plat_set_er\n"); }
+void CParticle::plat_set_eg( int entry, uint8 value ) { printf ("STUB: plat_set_eg\n"); }
+void CParticle::plat_set_eb( int entry, uint8 value ) { printf ("STUB: plat_set_eb\n"); }
+void CParticle::plat_set_ea( int entry, uint8 value ) { printf ("STUB: plat_set_ea\n"); }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CParticleEntry::CParticleEntry()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CParticleEntry::~CParticleEntry()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CParticle::set_defaults( void )
+{
+#define px 0x800		//0x4a1;
+#define py 0x90			//-0x117;		//0xfffffee9;
+#define pz -0x3a0		//0xb96;
+
+	mp_emit_script = NULL;
+	mp_update_script = NULL;
+	mp_params = NULL;
+	m_life_set = false;
+	m_end_set = false;
+
+
+	// Temp: Set up some debug values.
+	m_pos[X] = px;
+	m_pos[Y] = py;
+	m_pos[Z] = pz;
+
+	m_sw = 2.0f;
+	m_sh = 2.0f;
+	m_ew = 2.0f;
+	m_eh = 2.0f;
+	
+	m_life_min = 2.0f;
+	m_life_max = 2.0f;
+
+	m_emit_w = 1.0f;	//16.0f;
+	m_emit_h = 1.0f;    //16.0f;
+	m_emit_angle_spread = 0.5f;		//0.5f;
+
+	m_fx = 0.0f;
+	m_fy = -0.02f;
+	m_fz = 0.0f;
+
+	m_tx = 0.0f;		// Default target: straight up.
+	m_ty = 1.0f;
+	m_tz = 0.0f;
+
+	m_ax = 0.0f;		// Default angles: No rotation.
+	m_ay = 0.0f;
+	m_az = 0.0f;
+
+	m_speed_min = 1.0f;
+	m_speed_max = 2.0f;
+
+	m_random_angle = false;
+	m_circular_emit = true;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CParticle::CParticle()
+{
+	set_defaults();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CParticle::CParticle( uint32 checksum )
+{
+	set_defaults();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CParticle::CParticle( uint32 checksum, int maxParticles )
+{
+	set_defaults();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CParticle::~CParticle()
+{
+	if ( mp_emit_script )
+	{
+		delete mp_emit_script;
+	}
+	if ( mp_update_script )
+	{
+		delete mp_update_script;
+	}
+	if ( mp_params )
+	{
+		delete mp_params;
+	}
+	CEngine::sGetParticleTable()->FlushItem( m_checksum );		// remove reference from hash table
+//	printf ("\nJust Deleted 0x%x, contenets are now:\n",m_checksum);
+//	CEngine::sGetParticleTable()->PrintContents();
+}
+
+void CParticle::process( float delta_time )
+{
+	int				lp;
+	CParticleEntry*	p_particle;
+
+	// If flagged for deletion, check if the particle system is empty and delete if it is.
+	if ( m_delete_when_empty )
+	{
+		if ( !m_num_particles )
+		{
+			// removing reference is sone in the destructor
+			//CEngine::sGetParticleTable()->FlushItem( m_checksum );		// remove reference from hash table
+			delete this;
+			return;
+		}
+	}
+
+	// Don't process if paused.
+	if (Mdl::FrontEnd::Instance()->GamePaused() && !Replay::RunningReplay())
+	{
+		return;
+	}	
+	if (Replay::Paused())
+	{
+		return;
+	}	
+	
+	if ( GetSuspendComponent()->SkipLogic( ) )
+	{
+		return;
+	}
+	
+	
+
+	// Script updates.
+	if ( mp_update_script )
+	{
+		mp_update_script->Update();
+	}
+
+// We can't check for off screen or not until we've run the update script
+// as that might change the position (like for sparks)
+
+	// Don't process if off screen
+//	if (!Nx::CEngine::sIsVisible(m_pos,m_bounding_radius))
+//	{
+//		//printf ("Off - process\n");
+//		Gfx::AddDebugLine(m_pos,Mth::Vector(0,0,0),0x0000ff,0x0000ff,100);	 	// where is it?
+//		return;
+//	}
+
+
+	if ( mp_emit_script )
+	{
+		mp_emit_script->Update();
+//		if ( mp_emit_script->Update() == Script::ESCRIPTRETURNVAL_FINISHED )
+//		{
+//			m_delete_when_empty = true;
+//		}
+	}
+
+
+
+	// If life was not set, assume we're a screen-facing system.
+	if ( !m_life_set ) return;
+
+	// Physics update.
+	for ( lp = 0, p_particle = mp_particle_array; lp < m_num_particles; lp++, p_particle++ )
+	{
+		// Update time/life.
+		p_particle->m_time += delta_time;
+
+		// Apply force to velocity.
+		p_particle->m_vx += p_particle->m_fx;
+		p_particle->m_vy += p_particle->m_fy;
+		p_particle->m_vz += p_particle->m_fz;
+
+		// Copy history.
+		float x, y, z;
+		for ( int lp2 = ( plat_get_num_vertex_lists() - 1 ); lp2 > 0; lp2-- )
+		{
+			plat_get_position( lp, lp2 - 1, &x, &y, &z );
+			plat_set_position( lp, lp2, x, y, z );
+		}
+
+		// Apply velocity to position.
+		plat_add_position( lp, 0, p_particle->m_vx, p_particle->m_vy, p_particle->m_vz );
+	}
+
+	// Delete old particles.
+	for ( lp = 0, p_particle = mp_particle_array; lp < m_num_particles; lp++, p_particle++ )
+	{
+		if ( p_particle->m_time > p_particle->m_life )
+		{
+			// Delete old particles by copying last entry to entry that is being deleted.
+			m_num_particles--;
+			memcpy( p_particle, &mp_particle_array[m_num_particles], sizeof( CParticleEntry ) );
+
+			float x, y, z;
+			for ( int lp2 = 0; lp2 < plat_get_num_vertex_lists(); lp2++ )
+			{
+				plat_get_position( m_num_particles, lp2, &x, &y, &z );
+				plat_set_position( lp, lp2, x, y, z );
+				plat_set_position( m_num_particles, lp2, 1024.0f, 1024.0f, 1024.0f );
+			}
+
+			// Since we copied the last one to the current slot, decrement lp counter & particle
+			// pointer so that the copied entry gets checked for deletion as well.
+			p_particle--;
+			lp--;
+		}
+	}
+}
+
+void CParticle::render( void )
+{
+
+	if ( GetSuspendComponent()->SkipRender( ) )
+	{
+		return;
+	}
+
+	plat_render();
+}
+
+void CParticle::emit( int count )
+{
+	int first, last;
+
+	// Do nothing here if flagged for deletion.
+	if ( m_delete_when_empty )
+	{
+		return;
+	}
+
+	// Calculate the range of array entries to emit to.
+	first = m_num_particles;
+	if ( ( m_num_particles + count ) <= m_max_particles )
+	{
+		m_num_particles += count;
+	}
+	else
+	{
+		m_num_particles = m_max_particles;
+	}
+	last = m_num_particles;
+
+	// Build matrix to rotate the particle to the target.
+	Mth::Matrix mt;
+
+	mt.GetPos().Set();
+	mt.GetAt().Set( m_tx, m_ty, m_tz );
+	mt.GetAt().Normalize();
+
+	Mth::Vector temp;
+	temp.Set( 0.0f, 1.0f, 0.0f );
+	if ( ( fabs( mt.GetAt()[Y] ) > fabs( mt.GetAt()[X] ) ) && ( fabs( mt.GetAt()[Y] ) > fabs( mt.GetAt()[Z] ) ) )
+	{
+		// Y Major - use Z as up.
+		temp.Set( 0.0f, 0.0f, 1.0f );
+	}
+
+	mt.GetRight() = Mth::CrossProduct( temp, mt.GetAt() ); 
+	mt.GetRight().Normalize();
+	mt.GetUp() = Mth::CrossProduct( mt.GetRight(), mt.GetAt() ); 
+	mt.GetUp().Normalize();
+
+	// Add on rotation.
+	mt.RotateX( Mth::DegToRad(m_ax) );
+	mt.RotateY( Mth::DegToRad(m_ay) );
+	mt.RotateZ( Mth::DegToRad(m_az) );
+
+//	pright.set( mt.GetRight()[X], mt.GetRight()[Y], mt.GetRight()[Z] );
+//	pup.set( mt.GetUp()[X], mt.GetUp()[Y], mt.GetUp()[Z] );
+//	pat.set( mt.GetAt()[X], mt.GetAt()[Y], mt.GetAt()[Z] );
+
+	// Emit particles.
+	for ( int lp = first; lp < last; lp++ )
+	{
+		if ( m_life_set )
+		{
+			// Random values for this emission.
+			float rx = next_random();
+			float ry = next_random();
+
+			// Calculate emission initial velocity.		
+			Mth::Vector velocity;
+			velocity[X] = rx * ( Mth::PI * ( m_emit_angle_spread / 4.0f ) );
+			velocity[Y] = ry * ( Mth::PI * ( m_emit_angle_spread / 4.0f ) );  
+			velocity[Z] = 1.0f - (( velocity[X] * velocity[X] ) + ( velocity[Y] * velocity[Y] ));
+			velocity[W] = 1.0f;
+
+			float speed_mul = ( ( m_speed_max - m_speed_min ) * ((float)rand() / RAND_MAX ) ) + m_speed_min;
+			velocity[X] *= speed_mul;
+			velocity[Y] *= speed_mul;
+			velocity[Z] *= speed_mul;
+
+			// Get new rx, ry to make the particles cross over one another.
+			if ( m_random_angle )
+			{
+				rx = next_random();
+				ry = next_random();
+			}
+
+			// Normalize rx, ry to make a circular emission.
+			if ( m_circular_emit )
+			{
+				float l = 1.0f / sqrtf( ( rx * rx ) + ( ry * ry ) );
+				rx = rx * ( l * (float)fabs( rx ) );		// Note: length is scaled by random factor.
+				ry = ry * ( l * (float)fabs( ry ) );
+			}
+
+			// Set position around emitter width/height.
+			Mth::Vector pos;
+			pos[X] = m_emit_w * rx;
+			pos[Y] = m_emit_h * ry;
+			pos[Z] = 0.0f;
+			pos[W] = 1.0f;
+
+			// Set force.
+			mp_particle_array[lp].m_fx = m_fx;
+			mp_particle_array[lp].m_fy = m_fy;
+			mp_particle_array[lp].m_fz = m_fz;
+
+			// Set particle w/h
+			mp_particle_array[lp].m_sw = m_sw;
+			mp_particle_array[lp].m_sh = m_sh;
+			mp_particle_array[lp].m_ew = m_ew;
+			mp_particle_array[lp].m_eh = m_eh;
+
+			// Set time & life.
+			mp_particle_array[lp].m_time = 0.0f;
+			mp_particle_array[lp].m_life = ( ( m_life_max - m_life_min ) * ((float)rand() / RAND_MAX ) ) + m_life_min;
+
+			// Orient the particles to the target.
+			Mth::Vector v;
+			v = mt.Transform( velocity );
+			mp_particle_array[lp].m_vx = v[X];
+			mp_particle_array[lp].m_vy = v[Y];
+			mp_particle_array[lp].m_vz = v[Z];
+			
+			v = mt.Transform( pos );
+
+			for ( int lp2 = 0; lp2 < plat_get_num_vertex_lists(); lp2++ )
+			{
+				plat_set_position( lp, lp2, v[X], v[Y], v[Z] );
+			}
+		}
+		else
+		{
+			// Set particle w/h
+			mp_particle_array[lp].m_sw = m_sw;
+			mp_particle_array[lp].m_sh = m_sh;
+			mp_particle_array[lp].m_ew = m_ew;
+			mp_particle_array[lp].m_eh = m_eh;
+
+			// Set time & life.
+			mp_particle_array[lp].m_time = 0.0f;
+			mp_particle_array[lp].m_life = ( ( m_life_max - m_life_min ) * ((float)rand() / RAND_MAX ) ) + m_life_min;
+
+			// Screen-facing poly. Just set to 0,0,0.
+			for ( int lp2 = 0; lp2 < plat_get_num_vertex_lists(); lp2++ )
+			{
+				plat_set_position( lp, lp2, 0, 0, 0 );
+			}
+		}
+
+	}
+}
+
+void CParticle::set_emit_script( uint32 checksum, Script::CStruct* pParams )
+{
+	if ( checksum )
+	{
+		if ( !mp_emit_script )
+		{
+			mp_emit_script = new Script::CScript();
+			#ifdef __NOPT_ASSERT__
+			mp_emit_script->SetCommentString("Created in CParticle::set_emit_script(...)");
+			#endif
+		}
+		if ( !mp_params )
+		{
+			mp_params = new Script::CStruct;
+		}
+		mp_params->AppendStructure( pParams );
+		mp_emit_script->SetScript( checksum, mp_params, this );
+//		mp_emit_script->Update();
+	}
+}
+
+void CParticle::set_update_script( uint32 checksum, Script::CStruct* pParams )
+{
+	if ( checksum )
+	{
+		if ( !mp_update_script )
+		{
+			mp_update_script = new Script::CScript();
+			#ifdef __NOPT_ASSERT__
+			mp_update_script->SetCommentString("Created in CParticle::set_update_script(...)");
+			#endif
+		}
+		if ( !mp_params )
+		{
+			mp_params = new Script::CStruct;
+		}
+		mp_params->AppendStructure( pParams );
+		mp_update_script->SetScript( checksum, mp_params, this );
+	}
+}
+
+
+// Refresh a particle system after things like position have changed 
+
+void CParticle::Refresh()
+{
+	plat_build_path();
+}
+
+
+
+void CParticle::SetActive( bool active )
+{
+	m_active = active;
+	plat_set_active( active );
+}
+
+/******************************************************************/
+/*   CParticle::CallMemberFunction                               */
+/*   Call a member function, based on a checksum                  */
+/*   This is usually the checksum of the name of the function     */
+/*   but can actually be any number, as it just uses a switch     */
+/*   note this is a virtual function, so the same checksum        */
+/*   can do differnet things for different objects                */
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CParticle::CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript )
+{
+    
+	Dbg_MsgAssert(pScript,("NULL pScript"));
+
+	switch(Checksum)
+	{
+		// @script | SetPos | Sets the position of the particle system.
+		// @parmopt vector | pos | world position.
+		// @parmopt float | x	| x world position.
+		// @parmopt float | y	| y world position.
+		// @parmopt float | z	| z world position.
+		case 0x99cd17b:		// SetPos
+			{
+				Mth::Vector pos;
+				float x = 0.0f;
+				float y = 0.0f;
+				float z = 0.0f;
+				if ( pParams->GetVector(CRCD(0x7f261953,"pos"),&pos) )
+				{
+					x = pos[X];
+					y = pos[Y];
+					z = pos[Z];
+				}
+				else
+				{
+					pParams->GetFloat(CRCD(0x7323e97c,"x"),&x);
+					pParams->GetFloat(CRCD(0x424d9ea,"y"),&y);
+					pParams->GetFloat(CRCD(0x9d2d8850,"z"),&z);
+				}
+
+				set_pos( x, y, z );
+			}
+			break;
+		// @script | SetSpeedRange | Sets the min & max speed of the particles upon emission.
+		// @parm float | min | minimum speed. If only min is provided, all speeds will be set to min.
+		// @parmopt float | max | maximum speed.
+		case 0x63274395:		// SetSpeedRange
+			{
+				float min = 0.0f;
+				pParams->GetFloat(CRCD(0x5e84e22f,"min"),&min);
+				float max = min;
+				pParams->GetFloat(CRCD(0x6289dd76,"max"),&max);
+
+				set_speed_range( min, max );
+			}
+			break;
+		// @script | SetEmitRange | Sets the emission range (width and height).
+		// @parm float | width | Width of emission area. If only width is provided, height will be set to width.
+		// @parmopt float | height | Height of emission area.
+		case 0x8ffdef38:		// SetEmitRange
+			{
+				float width = 0.0f;
+				pParams->GetFloat(CRCD(0x73e5bad0,"width"),&width);
+				float height = width;
+				pParams->GetFloat(CRCD(0xab21af0,"height"),&height);
+			
+				set_emit_range( width, height );
+			}
+			break;
+		// @script | SetAngleSpread | Sets the angle spread for particle emissions.
+		// @parm float | spread | Angle spread. 0=No spread. 1=Full spread (180 degrees).
+		case 0x7911855b:		// SetAngleSpread
+			{
+				float spread = 0.0f;
+				pParams->GetFloat(CRCD(0x834a5f87,"spread"),&spread);
+			
+				set_emit_angle_spread( spread );
+			}
+		break;
+	
+
+		// @script | SetRandomAngle | Sets whether the emission angle is random or not.
+		// @parm int | random | 1 means choose a random angle regardless of the emission position. 0 means use the emission position to define the emission angle.
+		case 0xdb8621f7:		// SetRandomAngle
+			{
+				int random = 0;
+				pParams->GetInteger("random",(int*)&random);
+			
+				set_random_angle( random > 0 );
+			}
+			break;
+		
+		// @script | SetCircularEmit | Sets whether the emission area is circular or square.
+		// @parm int | circular | 1 means use a circular emission area. 0 means use a square emission area.
+		case 0xe985a9a6:		// SetCircularEmit
+			{
+				int circular = 0;
+				pParams->GetInteger("circular",(int*)&circular);
+			
+				set_circular_emit( circular > 0 );
+			}
+			break;
+		
+		// @script | SetForce | Sets the force acting on the particles.
+		// @parmopt vector | force	| Force.
+		// @parmopt float | x	| x force.
+		// @parmopt float | y	| y force.
+		// @parmopt float | z	| z force.
+		case 0xa075ee45:		// SetForce
+			{
+				Mth::Vector force;
+				float x = 0.0f;
+				float y = 0.0f;
+				float z = 0.0f;
+				if ( pParams->GetVector("force",&force) )
+				{
+					x = force[X];
+					y = force[Y];
+					z = force[Z];
+				}
+				else
+				{
+					pParams->GetFloat("x",&x);
+					pParams->GetFloat("y",&y);
+					pParams->GetFloat("z",&z);
+				}
+
+				set_force( x, y, z );
+			}
+			break;
+			
+		// @script | SetParticleSize | Sets the size of the particles.
+		// @parm float | sw | Start width of the particle. If only width is provided, height is set to width.
+		// @parmopt float | sh | Start height of the particle.
+		// @parm float | ew | End width of the particle. If only width is provided, height is set to width.
+		// @parmopt float | eh | End height of the particle.
+		case 0x2a479569:		// SetParticleSize
+			{
+				float sw = 0.0f;
+				pParams->GetFloat("sw",&sw);
+				float sh = sw;
+				pParams->GetFloat("sh",&sh);
+			
+				set_particle_start_size( sw, sh );
+
+				float ew = sw;
+				float eh;
+				if ( pParams->GetFloat("ew",&ew) )
+				{
+					eh = ew;
+				}
+				else
+				{
+					eh = sh;
+				}
+				pParams->GetFloat("eh",&eh);
+			
+				set_particle_end_size( ew, eh );
+			}
+			break;
+		
+		// @script | SetLife | Sets the life of the particles in seconds.
+		// @parm float | min | Minimum life of the particle. 1.0 = 1 second.
+		// @parmopt float | max | Maximum life of the particle. 1.0 = 1 second. If not specified, will be set to same as min.
+		case 0xd3f1d333:		// SetLife
+			{
+				float min = 0.0f;
+				pParams->GetFloat("min",&min);
+				float max = min;
+				pParams->GetFloat("max",&max);
+			
+				set_particle_life( min, max );
+			}
+			break;
+
+		// @script | SetEmitTarget | Sets the target position that the particle system will emit particles towards.
+		// @parmopt vector | target	| Target.
+		// @parmopt float | x	| x target.
+		// @parmopt float | y	| y target.
+		// @parmopt float | z	| z target.
+		case 0xb373fb6e:		// SetEmitTarget
+			{
+				Mth::Vector target;
+				float x = 0.0f;
+				float y = 0.0f;
+				float z = 0.0f;
+				if ( pParams->GetVector("target",&target) )
+				{
+					x = target[X];
+					y = target[Y];
+					z = target[Z];
+				}
+				else
+				{
+					pParams->GetFloat("x",&x);
+					pParams->GetFloat("y",&y);
+					pParams->GetFloat("z",&z);
+				}
+				
+				// If it's just 0,1,0, then rotate it by the angles of the node
+				if (x == 0.0f && y == 1.0f && z == 0.0f)
+				{
+					// not done here though....
+					
+				}
+				else
+				{
+					set_emit_target( x, y, z );
+				}
+				
+
+			}
+			break;
+		// @script | SetEmitAngle | Sets the emission angle. This is done after the target, so you can set a target as well as angles to rotate in addition to the target angle. Target defaults to 0,1,0 (straight up), so all angles rotate from there.
+		// @parmopt vector | angle	| Angle (in degrees).
+		// @parmopt float | x	| x angle.
+		// @parmopt float | y	| y angle.
+		// @parmopt float | z	| z angle.
+		case 0x1cfbf078:		// SetEmitAngle
+			{
+				Mth::Vector angle;
+				float x = 0.0f;
+				float y = 0.0f;
+				float z = 0.0f;
+				if ( pParams->GetVector("angle",&angle) )
+				{
+					x = angle[X];
+					y = angle[Y];
+					z = angle[Z];
+				}
+				else
+				{
+					pParams->GetFloat("x",&x);
+					pParams->GetFloat("y",&y);
+					pParams->GetFloat("z",&z);
+				}
+
+				set_emit_angle( x, y, z );
+			}
+			break;
+		// CreateParticleSystem colorentries=n	// default 2.
+		// SetColorEntry slot=n r=n g=n b=n
+
+
+		// @script | SetColor | Sets the color for the next particle emission.
+		// @parmopt int | corner | | The corner to set. Omitting this paramter means set all corners.
+		// @parmopt int | sr | | Start red color element to set (0-255). 128 is normal.
+		// @parmopt int | sg | | Start green color element to set (0-255). 128 is normal.
+		// @parmopt int | sb | | Start blue color element to set (0-255). 128 is normal.
+		// @parmopt int | sa | | Start alpha color element to set (0-255). 255 is opaque, 0 is invisible.
+		// @parmopt int | mr | | Mid red color element to set (0-255). 128 is normal.
+		// @parmopt int | mg | | Mid green color element to set (0-255). 128 is normal.
+		// @parmopt int | mb | | Mid blue color element to set (0-255). 128 is normal.
+		// @parmopt int | ma | | Mid alpha color element to set (0-255). 255 is opaque, 0 is invisible.
+		// @parmopt int | er | | End red color element to set (0-255). 128 is normal.
+		// @parmopt int | eg | | End green color element to set (0-255). 128 is normal.
+		// @parmopt int | eb | | End blue color element to set (0-255). 128 is normal.
+		// @parmopt int | ea | | End alpha color element to set (0-255). 255 is opaque, 0 is invisible.
+		// @parmopt int | midtime | | Sets the % through the particle's life that the mid color is reached.
+		// 0.5 means halfway through. Use values from 0-1. By default, the mid color is not used. By setting
+		// the mid time, the middle color will be used.
+		case 0x514b2584:		// SetColor
+			{
+				int corner;
+				int first = 0;
+				int last = 0;
+				if ( pParams->GetInteger("corner",&corner) )
+				{
+					first = last = corner;
+				}
+				else
+				{
+					first = 0;
+					last = plat_get_num_particle_colors() - 1;
+				}
+				if ( ( first < plat_get_num_particle_colors() ) && ( last < plat_get_num_particle_colors() ) )
+				{
+					for ( int entry = first; entry < ( last + 1 ); entry++ )
+					{
+						if ( entry < plat_get_num_particle_colors() )
+						{
+							int elem;
+							if ( pParams->GetInteger("er",&elem) )
+							{
+								plat_set_er( entry, elem );
+								m_end_set = true;
+							}
+							if ( pParams->GetInteger("eg",&elem) )
+							{
+								plat_set_eg( entry, elem );
+								m_end_set = true;
+							}
+							if ( pParams->GetInteger("eb",&elem) )
+							{
+								plat_set_eb( entry, elem );
+								m_end_set = true;
+							}
+							if ( pParams->GetInteger("ea",&elem) )
+							{
+								plat_set_ea( entry, elem );
+								m_end_set = true;
+							}
+							if ( pParams->GetInteger("mr",&elem) )
+							{
+								plat_set_mr( entry, elem );
+							}
+							if ( pParams->GetInteger("mg",&elem) )
+							{
+								plat_set_mg( entry, elem );
+							}
+							if ( pParams->GetInteger("mb",&elem) )
+							{
+								plat_set_mb( entry, elem );
+							}
+							if ( pParams->GetInteger("ma",&elem) )
+							{
+								plat_set_ma( entry, elem );
+							}
+							if ( pParams->GetInteger("sr",&elem) )
+							{
+								plat_set_sr( entry, elem );
+								if ( !m_end_set ) plat_set_er( entry, elem ); 
+							}
+							if ( pParams->GetInteger("sg",&elem) )
+							{
+								plat_set_sg( entry, elem );
+								if ( !m_end_set ) plat_set_eg( entry, elem ); 
+							}
+							if ( pParams->GetInteger("sb",&elem) )
+							{
+								plat_set_sb( entry, elem );
+								if ( !m_end_set ) plat_set_eb( entry, elem ); 
+							}
+							if ( pParams->GetInteger("sa",&elem) )
+							{
+								plat_set_sa( entry, elem );
+								if ( !m_end_set ) plat_set_ea( entry, elem ); 
+							}
+						}
+					}
+				}
+				pParams->GetFloat("midtime",&m_mid_time);
+			}
+			break;
+		// @script | Emit | Emits the specified number of particles.
+		// @parm int | num | Number of particles to emit.
+		case 0x711b1a6a:		// Emit
+			{
+				int num = 1;		// Defaults to 1 particle emitted.
+				pParams->GetInteger("num",&num);
+
+				emit( num );
+			}
+			break;
+		// @script | EmitRate | Emits at the specified rate - 1=1 particle per frame.
+		// @parm float | rate | The rate to emit at.
+		case 0x35b65bd4:		// EmitRate
+			{
+				float rate = 1.0f;		// Defaults to 1 particle emitted per frame.
+				pParams->GetFloat("rate",&rate);
+
+				set_emit_rate( rate );
+			}
+			break;
+		// @script | BuildPath | Builds a path based on the current physics data.
+		case 0xa8699e0e:		// BuildPath
+			{
+				plat_build_path();
+			}
+			break;
+
+		// @script | EmptySystem | Empties the particle system immediately (sets number of particles to 0.
+		case 0x482482c6:		// EmptySystem
+			{
+				m_num_particles = 0;
+			}
+			break;
+
+		default:
+			return Obj::CMovingObject::CallMemberFunction( Checksum, pParams, pScript );
+	}
+
+	return true;
+}
+
+}
+
diff --git a/Code/Gfx/nxparticle.h b/Code/Gfx/nxparticle.h
new file mode 100644
index 0000000..8e7562d
--- /dev/null
+++ b/Code/Gfx/nxparticle.h
@@ -0,0 +1,187 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxParticle.h
+
+
+
+#ifndef	__GFX_NXPARTICLE_H__
+#define	__GFX_NXPARTICLE_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+#include 
+
+
+
+namespace Script
+{
+
+// Forward declarations
+class CScript;
+
+}
+
+namespace Nx
+{
+
+// Forward declarations
+class CParticleEntry;
+
+//////////////////////////////////////////////////////////////////////////////
+// The CParticle class is the platform independent abstract base class
+// of the platform specific CParticle classes
+class	CParticle : public Obj::CMovingObject
+{
+public:
+							CParticle();
+							CParticle( uint32 checksum );
+							CParticle( uint32 checksum, int maxParticles );
+	virtual					~CParticle();
+
+	void					process( float delta_time );
+	void					render( void );
+
+	void					emit( int count );
+
+	void					set_pos( float x, float y, float z )			{ m_pos[X] = x; m_pos[Y] = y; m_pos[Z] = z; }
+	void					set_speed_range( float min, float max )			{ m_speed_min = min; m_speed_max = max; }
+	void					set_emit_range( float width, float height )		{ m_emit_w = width; m_emit_h = height; }
+	void					set_emit_angle_spread( float spread )			{ m_emit_angle_spread = spread; }
+	void					set_random_angle( bool random_angle )			{ m_random_angle = random_angle; }
+	void					set_circular_emit( bool circular_emit )			{ m_circular_emit = circular_emit; }
+	void					set_force( float x, float y, float z )			{ m_fx = x; m_fy = y; m_fz = z; }
+	void					set_particle_start_size( float w, float h )		{ m_sw = w; m_sh = h; }
+	void					set_particle_end_size( float w, float h )		{ m_ew = w; m_eh = h; }
+	void					set_particle_life( float min, float max )		{ m_life_min = min; m_life_max = max; m_life_set = true; }
+	void					set_emit_target( float x, float y, float z )	{ m_tx = x; m_ty = y; m_tz = z; }
+	void					set_emit_angle( float x, float y, float z )		{ m_ax = x; m_ay = y; m_az = z; }
+	void					set_emit_rate( float rate )						{ m_emit_rate = rate; }
+
+	void					set_checkum(uint32 checksum) {m_checksum = checksum;}
+
+	void					delete_when_empty( void )						{ m_delete_when_empty = true; }
+
+	bool					CallMemberFunction (uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript);	// Call a member function based on the checksum of the function name
+
+	void					set_emit_script( uint32 checksum, Script::CStruct* pParams );
+	void					set_update_script( uint32 checksum, Script::CStruct* pParams );
+
+	void					SetActive( bool active );
+	bool					IsActive()										{ return m_active; }
+	
+	void					SetEmitting( bool emitting )					{ m_emitting = emitting; }
+	bool					IsEmitting()									{ return m_emitting; }
+
+
+	void					SetPerm( bool perm )							{ m_perm = perm; }
+	bool					IsPerm()										{ return m_perm; }
+
+	void					SetNumParticles( int num )						{ m_num_particles = num; }
+	int						GetNumParticles( void )							{ return m_num_particles; }
+
+	void 					Refresh();		
+
+
+protected:
+	// Extensions for Mike's system.
+	float					m_emit_rate;			// The rate of emission - 1.0 = 1 per frame.
+
+	// Member variables (protected so the p-classes can access them)
+	int						m_max_particles;		// Maximum size of particle array.
+	int						m_num_particles;		// Current number of active particles.
+		
+	float					m_speed_min;			// Initial Speed setting.
+	float					m_speed_max;
+	float					m_emit_w, m_emit_h;		// Emitter dimensions.
+	float					m_emit_angle_spread;	// Angle spread from 0 to 1.
+	
+	// Should be changed to a flag value...
+	bool					m_random_angle;			// true means use a random angle, regardless of the emit pos.
+	bool					m_circular_emit;		// true means emit in a circle.
+
+	float					m_fx, m_fy, m_fz;		// Initial Force values. fy is gravity, will not dec to 0.
+	float					m_sw, m_sh;				// Initial width & height.
+	float					m_ew, m_eh;				// Initial width & height.
+	float					m_life_min, m_life_max;	// Life min & max
+
+	float					m_tx, m_ty, m_tz;		// Target position.
+	float					m_ax, m_ay, m_az;		// Angle rotations.
+	
+	uint32					m_checksum;
+
+	bool					m_delete_when_empty;	// When m_num_particles == 0, this system will self-delete.
+
+	CParticleEntry*			mp_particle_array;		// Pointer to array of particles.
+
+	Script::CScript*		mp_emit_script;
+	Script::CScript*		mp_update_script;
+
+	// parameters that will be passed to the emit/update scripts
+	// generally used for passing the ID of a moving object
+	Script::CStruct*		mp_params;
+	bool					m_active;
+	bool					m_emitting;
+
+	float					m_mid_time;					// Time of the mid color.
+
+	bool					m_life_set;
+
+	bool					m_end_set;
+
+	bool					m_perm;						// true if not deleted between levels
+
+	// The virtual functions have a stub implementation.
+private:
+	virtual void			plat_render( void );
+	virtual void			plat_get_position( int entry, int list, float * x, float * y, float * z );
+	virtual void			plat_set_position( int entry, int list, float x, float y, float z );
+	virtual void			plat_add_position( int entry, int list, float x, float y, float z );
+	virtual int				plat_get_num_vertex_lists( void );
+	virtual int				plat_get_num_particle_colors( void );
+	virtual void			plat_set_sr( int entry, uint8 value );
+	virtual void			plat_set_sg( int entry, uint8 value );
+	virtual void			plat_set_sb( int entry, uint8 value );
+	virtual void			plat_set_sa( int entry, uint8 value );
+	virtual void			plat_set_mr( int entry, uint8 value );
+	virtual void			plat_set_mg( int entry, uint8 value );
+	virtual void			plat_set_mb( int entry, uint8 value );
+	virtual void			plat_set_ma( int entry, uint8 value );
+	virtual void			plat_set_er( int entry, uint8 value );
+	virtual void			plat_set_eg( int entry, uint8 value );
+	virtual void			plat_set_eb( int entry, uint8 value );
+	virtual void			plat_set_ea( int entry, uint8 value );
+	virtual void			plat_set_active( bool active );
+	virtual void			plat_build_path( void );
+
+	void					set_defaults( void );
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Particle system entry. Does very little other than hold information. The controller does all the work.
+class CParticleEntry
+{
+public:
+							CParticleEntry();
+							~CParticleEntry();
+
+//							float					m_x, m_y, m_z;				// Current x, y, z positions.
+							float					m_vx, m_vy, m_vz;			// Current velocity.
+							float					m_fx, m_fy, m_fz;			// Current force.
+							float					m_sw, m_sh;					// Start Width & Height.
+							float					m_ew, m_eh;					// EndWidth & Height.
+							float					m_time, m_life;				// Current time & life of particle.
+protected:
+private:
+	friend CParticle;
+};
+
+}
+
+#endif // 
+
+
diff --git a/Code/Gfx/nxparticlemgr.cpp b/Code/Gfx/nxparticlemgr.cpp
new file mode 100644
index 0000000..1b6ebab
--- /dev/null
+++ b/Code/Gfx/nxparticlemgr.cpp
@@ -0,0 +1,119 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticleMgr.cpp
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#include 
+#include "gfx/nx.h"
+#include "gfx/nxparticlemgr.h"
+
+namespace Nx
+{
+void process_particles( float delta_time )
+{
+//	plat_process_particles( delta_time );
+	if( CEngine::sGetParticleTable() )
+	{
+		CParticle *p_particle;
+		CEngine::sGetParticleTable()->IterateStart();
+		while(( p_particle = CEngine::sGetParticleTable()->IterateNext()))
+		{
+			if ( p_particle->IsActive() ) p_particle->process( delta_time );
+		}
+	}
+}
+
+void render_particles( void )
+{
+//	plat_process_particles( delta_time );
+	if( CEngine::sGetParticleTable() )
+	{
+		CParticle *p_particle;
+		CEngine::sGetParticleTable()->IterateStart();
+		while(( p_particle = CEngine::sGetParticleTable()->IterateNext()))
+		{
+			if ( p_particle->IsActive() ) p_particle->render();
+		}
+	}
+}
+
+CParticle* get_particle( uint32 checksum )
+{
+	CParticle *p_particle = NULL;
+	if( CEngine::sGetParticleTable() )
+	{
+		p_particle = CEngine::sGetParticleTable()->GetItem( checksum );
+	}
+	return p_particle;
+}
+
+CParticle* create_particle( uint32 checksum, uint32 type_checksum, int max_particles, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history, int perm )
+{
+	// Types to add:
+	// 1. Flat color quad
+	// 2. Gouraud quad
+	// 3. n sided glow (center color + outer color)
+	// 4. n sided 2 layer glow ( center color + mid color + outer color)
+	// 5. n spiked star (center color + mid color + spike color)
+	// 6. Lines (current color + previous color)
+	// 7. Ribbons - volumetric lines made from quads (current color + previous color)
+
+	CParticle *p_particle =  plat_create_particle( checksum, type_checksum, max_particles, max_streams, texture_checksum, blendmode_checksum, fix, num_segments, split, history );
+	
+	p_particle->SetPerm(perm);
+	
+	p_particle->SetActive( true );
+	p_particle->SetEmitting( false ); 	// This is already set to false, this si just a reminder
+
+	// Add to our list of particles.
+	CEngine::sGetParticleTable()->PutItem( checksum, p_particle );
+	return p_particle;
+}
+
+void destroy_particle( uint32 checksum )
+{
+	CParticle * p = get_particle( checksum );	 	// get item
+	CEngine::sGetParticleTable()->FlushItem( checksum );		// remove reference from hash table
+	delete p;										// deleted particle, will remove itself from the table
+}
+
+void destroy_particle_when_empty( uint32 checksum )
+{
+	CParticle * p = get_particle( checksum );	 	// get item
+	p->delete_when_empty();
+}
+
+void destroy_all_particles(  )
+{
+	if( CEngine::sGetParticleTable() )
+	{
+		CParticle *p_particle;
+		CEngine::sGetParticleTable()->IterateStart();
+		while(( p_particle = CEngine::sGetParticleTable()->IterateNext()))
+		{
+			delete p_particle;
+		}
+	}
+}
+
+void destroy_all_temp_particles(  )
+{
+	if( CEngine::sGetParticleTable() )
+	{
+		CParticle *p_particle;
+		CEngine::sGetParticleTable()->IterateStart();
+		while(( p_particle = CEngine::sGetParticleTable()->IterateNext()))
+		{
+			if (!p_particle->IsPerm())
+			{
+				delete p_particle;
+			}
+		}
+	}
+}
+
+
+
+} // Nx
diff --git a/Code/Gfx/nxparticlemgr.h b/Code/Gfx/nxparticlemgr.h
new file mode 100644
index 0000000..79904ba
--- /dev/null
+++ b/Code/Gfx/nxparticlemgr.h
@@ -0,0 +1,34 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       p_nxParticleMgr.h
+//* OWNER:          Paul Robinson
+//* CREATION DATE:  3/27/2002
+//****************************************************************************
+
+#ifndef	__GFX_P_NX_PARTICLE_MGR_H__
+#define	__GFX_P_NX_PARTICLE_MGR_H__
+    
+#include "gfx/nxparticle.h"
+                   
+namespace Nx
+{
+
+void process_particles( float delta_time );
+void render_particles( void );
+CParticle* get_particle( uint32 checksum );
+CParticle* create_particle( uint32 checksum, uint32 type_checksum, int max_particles, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history, int perm );
+void	   destroy_particle( uint32 checksum );
+void	   destroy_particle_when_empty( uint32 checksum );
+void destroy_all_particles(  );
+void destroy_all_temp_particles(  );
+
+void plat_process_particles( float delta_time );
+CParticle* plat_get_particle( uint32 checksum );
+CParticle* plat_create_particle( uint32 checksum, uint32 type_checksum, int max_particles, int max_streams, uint32 texture_checksum, uint32 blendmode_checksum, int fix, int num_segments, float split, int history );
+} // Nx
+
+#endif 
+
+
+
+
diff --git a/Code/Gfx/nxweather.cpp b/Code/Gfx/nxweather.cpp
new file mode 100644
index 0000000..2a5b2d5
--- /dev/null
+++ b/Code/Gfx/nxweather.cpp
@@ -0,0 +1,509 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxWeather.cpp
+
+#include 
+#include 
+#include 
+#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+
+#define next_random() ((((float)rand() / RAND_MAX ) * 2.0f ) - 1.0f)
+
+//NsVector pright;
+//NsVector pup;
+//NsVector pat;
+
+namespace	Nx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWeather::plat_update_grid( void )
+{
+	printf ("STUB: plat_update_grid\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWeather::plat_process( float delta_time )
+{
+	printf ("STUB: plat_process\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CWeather::plat_render( void )
+{
+	printf ("STUB: plat_render\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWeather::plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	printf ("STUB: plat_set_rain_blend_mode\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWeather::plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	printf ("STUB: plat_set_splash_blend_mode\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWeather::plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix )
+{
+	printf ("STUB: plat_set_snow_blend_mode\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CWeather::CWeather()
+{
+	m_rain_height = 2000.0f;
+	m_rain_frames = 40;
+	m_rain_length = 100.0f;
+	m_rain_drops_per_frame = 0.0f;
+	m_rain_top_color.r = 0x00;
+	m_rain_top_color.g = 0x00;
+	m_rain_top_color.b = 0x00;
+	m_rain_top_color.a = 0x00;
+	m_rain_bottom_color.r = 0xff;
+	m_rain_bottom_color.g = 0xff;
+	m_rain_bottom_color.b = 0xff;
+	m_rain_bottom_color.a = 0xff;
+
+	m_splash_per_frame = 0.0f;
+	m_splash_life = 8;
+	m_splash_size = 16.0f;
+	m_splash_color.r = 0x80;
+	m_splash_color.g = 0x80;
+	m_splash_color.b = 0x80;
+	m_splash_color.a = 0x80;
+
+	m_snow_height = 500.0f;
+	m_snow_frames = 254;
+	m_snow_size = 4.0f;
+	m_snow_flakes_per_frame = 0.0f;
+	m_snow_color.r = 0x80;
+	m_snow_color.g = 0x80;
+	m_snow_color.b = 0x80;
+	m_snow_color.a = 0x80;
+
+	m_raining = true;
+
+	m_system_active = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWeather::~CWeather()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWeather::UpdateGrid( void )
+{
+	plat_update_grid();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWeather::Process( float delta_time )
+{
+	plat_process( delta_time );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWeather::Render( void )
+{
+	plat_render();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetRainHeight | Sets the height of the rain above the ground.
+// @parm height | The height of the rain in inches - defaults to 2000 inches.
+bool CWeather::ScriptWeatherSetRainHeight( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_rain_height = 2000.0f;		// Default
+	pParams->GetFloat( NONAME, &m_rain_height );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetRainFrames | Sets the number of frames it takes a rain drop to fall.
+// @parm frames | Number of frames for the rain to fall from RainHeight to the ground. Defaults to 40 frames.
+bool CWeather::ScriptWeatherSetRainFrames( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_rain_frames = 40;		// Default
+	pParams->GetInteger( NONAME, &m_rain_frames );
+	if ( m_rain_frames < 0 ) m_rain_frames = 0;
+	if ( m_rain_frames > 254 ) m_rain_frames = 254;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetRainLength | Sets the length of the rain drops (the rain drop is a line).
+// @parm length | Length of the rain drops in inches. Defaults to 100 inches.
+bool CWeather::ScriptWeatherSetRainLength( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_rain_length = 100.0f;		// Default
+	pParams->GetFloat( NONAME, &m_rain_length );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetRainBlendMode | Sets the blendmode of the rain particles.
+// @parm blendmode | The name of the blend mode. Type are: blend/add/sub/modulate/brighten &
+// fixblend/fixadd/fixsub/fixmodulate/fixbrighten & diffuse (no blend at all). Defaults to fixadd.
+// @parmopt int | Fixed alpha value. Defaults to 64. Range is 0-255. Only required if using fix blend modes.
+bool CWeather::ScriptWeatherSetRainBlendMode( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 blendmode_checksum = 0;
+	if ( !pParams->GetChecksum( NONAME, &blendmode_checksum ) )
+	{
+		return false;
+	}
+	int fix = 32;		// Default
+	pParams->GetInteger( NONAME, &fix );
+
+	plat_set_rain_blend_mode( blendmode_checksum, fix );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetRainRate | Sets the rate that the rain drops fall in drops per frame.
+// @parm rate | The number of new drops of rain per frame. Defaults to 0 (off).
+bool CWeather::ScriptWeatherSetRainRate( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_rain_drops_per_frame = 0.0f;		// Default
+	pParams->GetFloat( NONAME, &m_rain_drops_per_frame );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetRainColor | Sets the color of the rain.
+// @parm top | The color of the rain at the top of the droplet. Defaults to 0x00000000
+// @parm bottom | The color of the rain at the bottom of the droplet. Defaults to 0xffffffff
+bool CWeather::ScriptWeatherSetRainColor( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 color;
+
+	color = 0x00000000;
+	pParams->GetInteger( "top", (int*)&color ); 
+
+	m_rain_top_color.r = (uint8)((color)&0xff);
+	m_rain_top_color.g = (uint8)((color>>8)&0xff);
+	m_rain_top_color.b = (uint8)((color>>16)&0xff);
+	m_rain_top_color.a = (uint8)((color>>24)&0xff); 
+
+	color = 0xffffffff;
+	pParams->GetInteger( "bottom", (int*)&color ); 
+
+	m_rain_bottom_color.r = (uint8)((color)&0xff);
+	m_rain_bottom_color.g = (uint8)((color>>8)&0xff);
+	m_rain_bottom_color.b = (uint8)((color>>16)&0xff);
+	m_rain_bottom_color.a = (uint8)((color>>24)&0xff); 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSplashRate | Sets the rate that the splashes appear.
+// @parm rate | The number of splashes per frame. 1 is the maximum. Defaults to 0 (off).
+bool CWeather::ScriptWeatherSetSplashRate( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_splash_per_frame = 0.0f;		// Default
+	pParams->GetFloat( NONAME, &m_splash_per_frame );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSplashLife | Sets the number of frames a splash will stay on screen.
+// @parm life | Number of frames the splash is on screen. Defaults to 8. Maximum is 32.
+bool CWeather::ScriptWeatherSetSplashLife( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_splash_life = 8;		// Default
+	pParams->GetInteger( NONAME, &m_splash_life );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSplashSize | Sets the size of the splashes on-screen in inches.
+// @parm size | Sets the size of a splash on screen - defaults to 16 inches.
+bool CWeather::ScriptWeatherSetSplashSize( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_splash_size = 16.0f;		// Default
+	pParams->GetFloat( NONAME, &m_splash_size );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSplashColor | Sets the color of the splash.
+// @parm color | The color of the splash. Defaults to 0x80808080.
+bool CWeather::ScriptWeatherSetSplashColor( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 color;
+
+	color = 0x80808080;
+	pParams->GetInteger( NONAME, (int*)&color ); 
+
+	m_splash_color.r = (uint8)((color)&0xff);
+	m_splash_color.g = (uint8)((color>>8)&0xff);
+	m_splash_color.b = (uint8)((color>>16)&0xff);
+	m_splash_color.a = (uint8)((color>>24)&0xff); 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSplashBlendMode | Sets the blendmode of the splashes.
+// @parm blendmode | The name of the blend mode. Type are: blend/add/sub/modulate/brighten &
+// fixblend/fixadd/fixsub/fixmodulate/fixbrighten & diffuse (no blend at all). Defaults to fixadd.
+// @parmopt int | Fixed alpha value. Defaults to 64. Range is 0-255. Only required if using fix blend modes.
+bool CWeather::ScriptWeatherSetSplashBlendMode( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 blendmode_checksum = 0;
+	if ( !pParams->GetChecksum( NONAME, &blendmode_checksum ) )
+	{
+		return false;
+	}
+	int fix = 32;		// Default
+	pParams->GetInteger( NONAME, &fix );
+
+	plat_set_splash_blend_mode( blendmode_checksum, fix );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSnowHeight | Sets the height of the snow above the ground.
+// @parm height | The height of the snow in inches - defaults to 500 inches.
+bool CWeather::ScriptWeatherSetSnowHeight( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_snow_height = 500.0f;		// Default
+	pParams->GetFloat( NONAME, &m_snow_height );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSnowFrames | Sets the number of frames it takes a snow flake to fall.
+// @parm frames | Number of frames for the snow to fall from SnowHeight to the ground. Defaults to 254 frames.
+bool CWeather::ScriptWeatherSetSnowFrames( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_snow_frames = 254;		// Default
+	pParams->GetInteger( NONAME, &m_snow_frames );
+
+	if ( m_snow_frames < 0 ) m_snow_frames = 0;
+	if ( m_snow_frames > 254 ) m_snow_frames = 254;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSnowSize | Sets the size of the snow flake image.
+// @parm size | Size of the snow flakes in inches. Defaults to 4 inches.
+bool CWeather::ScriptWeatherSetSnowSize( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_snow_size = 4.0f;		// Default
+	pParams->GetFloat( NONAME, &m_snow_size );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSnowBlendMode | Sets the blendmode of the snow flakes.
+// @parm blendmode | The name of the blend mode. Type are: blend/add/sub/modulate/brighten &
+// fixblend/fixadd/fixsub/fixmodulate/fixbrighten & diffuse (no blend at all). Defaults to fixadd.
+// @parmopt int | Fixed alpha value. Defaults to 64. Range is 0-255. Only required if using fix blend modes.
+bool CWeather::ScriptWeatherSetSnowBlendMode( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 blendmode_checksum = 0;
+	if ( !pParams->GetChecksum( NONAME, &blendmode_checksum ) )
+	{
+		return false;
+	}
+	int fix = 32;		// Default
+	pParams->GetInteger( NONAME, &fix );
+
+	plat_set_snow_blend_mode( blendmode_checksum, fix );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSnowRate | Sets the rate that the snow flakes fall in flakes per frame.
+// @parm rate | The number of new flakes of snow per frame. Defaults to 0 (off).
+bool CWeather::ScriptWeatherSetSnowRate( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_snow_flakes_per_frame = 0.0f;		// Default
+	pParams->GetFloat( NONAME, &m_snow_flakes_per_frame );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSnowColor | Sets the color of the snow.
+// @parm col | The color of the snow. Defaults to 0x80808080
+bool CWeather::ScriptWeatherSetSnowColor( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 color;
+
+	color = 0x80808080;
+	pParams->GetInteger( NONAME, (int*)&color ); 
+
+	m_snow_color.r = (uint8)((color)&0xff);
+	m_snow_color.g = (uint8)((color>>8)&0xff);
+	m_snow_color.b = (uint8)((color>>16)&0xff);
+	m_snow_color.a = (uint8)((color>>24)&0xff);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetSnowActive | Sets weather system to snow mode.
+bool CWeather::ScriptWeatherSetSnowActive( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_raining = false;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | WeatherSetRainActive | Sets weather system to rain mode.
+bool CWeather::ScriptWeatherSetRainActive( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	m_raining = true;
+
+	return true;
+}
+
+}
+
diff --git a/Code/Gfx/nxweather.h b/Code/Gfx/nxweather.h
new file mode 100644
index 0000000..c9bd905
--- /dev/null
+++ b/Code/Gfx/nxweather.h
@@ -0,0 +1,93 @@
+///////////////////////////////////////////////////////////////////////////////
+// NxParticle.h
+
+
+
+#ifndef	__GFX_WEATHER_H__
+#define	__GFX_WEATHER_H__
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Nx
+{
+
+class CWeather
+{
+public:
+							CWeather();
+	virtual					~CWeather();
+
+	void					UpdateGrid( void );
+
+	void					Process( float delta_time );
+	void					Render( void );
+
+	// Script functions.
+	bool					ScriptWeatherSetRainHeight( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetRainFrames( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetRainLength( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetRainBlendMode( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetRainRate( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetRainColor( Script::CStruct* pParams, Script::CScript* pScript );
+
+	bool					ScriptWeatherSetSplashRate( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSplashLife( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSplashSize( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSplashColor( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSplashBlendMode( Script::CStruct* pParams, Script::CScript* pScript );
+
+	bool					ScriptWeatherSetSnowHeight( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSnowFrames( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSnowSize( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSnowBlendMode( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSnowRate( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetSnowColor( Script::CStruct* pParams, Script::CScript* pScript );
+
+	bool					ScriptWeatherSetSnowActive( Script::CStruct* pParams, Script::CScript* pScript );
+	bool					ScriptWeatherSetRainActive( Script::CStruct* pParams, Script::CScript* pScript );
+
+	// The virtual functions have a stub implementation.
+private:
+	virtual void			plat_update_grid( void );
+	virtual void			plat_process( float delta_time );
+	virtual void			plat_render( void );
+	virtual void			plat_set_rain_blend_mode( uint32 blendmode_checksum, int fix );
+	virtual void			plat_set_splash_blend_mode( uint32 blendmode_checksum, int fix );
+	virtual void			plat_set_snow_blend_mode( uint32 blendmode_checksum, int fix );
+
+protected:
+	float					m_rain_height;
+	int						m_rain_frames;
+	float					m_rain_length;
+	float					m_rain_drops_per_frame;
+	Image::RGBA				m_rain_top_color;
+	Image::RGBA				m_rain_bottom_color;
+
+	float					m_splash_per_frame;
+	int						m_splash_life;
+	float					m_splash_size;
+	Image::RGBA				m_splash_color;
+
+	int						m_active_drops;
+
+	bool					m_system_active;
+
+	float					m_snow_height;
+	int						m_snow_frames;
+	float					m_snow_size;
+	float					m_snow_flakes_per_frame;
+	Image::RGBA				m_snow_color;
+
+	bool					m_raining;
+};
+
+}
+
+#endif // 
+
+
+
diff --git a/Code/Gfx/shadow.cpp b/Code/Gfx/shadow.cpp
new file mode 100644
index 0000000..1385b71
--- /dev/null
+++ b/Code/Gfx/shadow.cpp
@@ -0,0 +1,223 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	File name:		shadow.cpp												**
+**																			**
+**	Description:	Shadow interface code									**
+**																			**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Gfx
+{
+
+CShadow::CShadow()
+{
+	m_type=vUNDEFINED_SHADOW;
+}
+
+CShadow::~CShadow()
+{
+}
+
+CDetailedShadow::CDetailedShadow(Nx::CModel *p_model, Mth::Vector camera_direction, float camera_distance)
+{
+	m_type=vDETAILED_SHADOW;
+	mp_texture = Nx::CEngine::sCreateRenderTargetTexture( 256, 256, 16, 16 );		// maybe platform dependent?
+	
+	// Note, engine will figure out scene for itself
+	Nx::CEngine::sProjectTextureIntoScene( mp_texture, p_model );		
+
+	m_distance = camera_distance;
+	m_direction = camera_direction;
+	m_direction.Normalize();
+	mp_camera = new Camera();
+	mp_camera->SetPos(Mth::Vector(0,0,0));
+	// construct an orientation matrix for the camera
+	// so it is looking in the required direction
+	Mth::Matrix orient;
+	orient.Ident();		 		// initialize (since it aint)
+	orient[Z] = m_direction;
+	orient[Z].Normalize();
+	
+	// X and Y are at right angles to Z  
+	orient[X][X] =  orient[Z][Z];
+	orient[X][Y] = -orient[Z][X];
+	orient[X][Z] = -orient[Z][Y];
+	
+	orient[Y][X] =  orient[Z][Y];
+	orient[Y][Y] = -orient[Z][Z];
+	orient[Y][Z] = -orient[Z][X];
+	mp_camera->SetMatrix(orient);
+
+	// this loops through all the existing shadows,
+	// and sets their shadow flags...  the model
+	// also remembers its shadow state, for any
+	// subsequent geoms that are added
+	p_model->EnableShadow( true ); 
+}
+
+	
+CDetailedShadow::~CDetailedShadow()
+{	
+	Nx::CEngine::sStopProjectionTexture( mp_texture );
+	
+	if ( mp_texture )
+	{
+		delete mp_texture;
+	}
+
+	delete mp_camera;
+}
+
+
+// set the position for the projection camera to point at
+void		CDetailedShadow::UpdatePosition(Mth::Vector pos)
+{
+	mp_camera->SetPos(pos - m_direction * m_distance);
+	Nx::CEngine::sSetProjectionTextureCamera( mp_texture, mp_camera );
+}
+
+
+
+// set the direction in which the projection camera points
+void CDetailedShadow::UpdateDirection( const Mth::Vector& dir )
+{
+	// Store camera position.
+	Mth::Vector cam_pos = mp_camera->GetPos();
+
+	// Set new direction and normalize.	
+	m_direction = dir;
+	m_direction.Normalize();
+	
+	// Zero out camera position.
+	mp_camera->SetPos( Mth::Vector( 0, 0, 0 ));
+
+	// Construct an orientation matrix for the camera so it is looking in the required direction.
+	Mth::Matrix orient;
+	orient.Identity();
+	orient[Z] = m_direction;
+	
+	// X and Y are at right angles to Z.
+	orient[X][X] =  orient[Z][Z];
+	orient[X][Y] = -orient[Z][X];
+	orient[X][Z] = -orient[Z][Y];
+	orient[Y][X] =  orient[Z][Y];
+	orient[Y][Y] = -orient[Z][Z];
+	orient[Y][Z] = -orient[Z][X];
+	mp_camera->SetMatrix( orient );
+
+	// Restore camera position.
+	mp_camera->SetPos( cam_pos );
+
+	// Pass camera details on to engine.
+	Nx::CEngine::sSetProjectionTextureCamera( mp_texture, mp_camera );
+}
+
+
+
+CSimpleShadow::CSimpleShadow()
+{
+	m_type=vSIMPLE_SHADOW;
+	m_scale=1.0f;
+	m_offset=1.0f;	
+	mp_model=NULL;
+	/*
+	mp_poly=Nx::CEngine::sCreateTextured3dPoly();
+	Dbg_MsgAssert(mp_poly,("Could not create textured 3d poly for shadow"));
+	mp_poly->SetTexture("pedshadow");
+	*/
+}
+
+CSimpleShadow::~CSimpleShadow()
+{
+	/*
+	Dbg_MsgAssert(mp_poly,("NULL mp_poly ?"));
+	Nx::CEngine::sDestroyTextured3dPoly(mp_poly);
+	*/
+	
+	if (mp_model)
+	{
+		Nx::CEngine::sUninitModel(mp_model);
+		mp_model=NULL;
+	}	
+}
+
+void CSimpleShadow::SetModel(const char *p_model_name)
+{
+	if (mp_model)
+	{
+		Nx::CEngine::sUninitModel(mp_model);
+		mp_model=NULL;
+	}	
+
+	mp_model=Nx::CEngine::sInitModel();
+	
+	Dbg_MsgAssert(p_model_name,("NULL p_model_name"));
+	
+	// TODO: Change to use a geom file instead for PS2, more efficient than mdl ...
+	mp_model->AddGeom(Gfx::GetModelFileName(p_model_name, ".mdl").getString(), 0, true);
+}
+
+void CSimpleShadow::UpdatePosition(Mth::Vector& parentPos, Mth::Matrix& parentMatrix, Mth::Vector normal)
+{
+	/*
+	Dbg_MsgAssert(mp_poly,("NULL mp_poly ?"));
+	float w=Script::GetFloat("shadowwidth");
+	mp_poly->SetPos(pos,w,w,normal);
+	*/
+		
+	Mth::Matrix display_matrix;
+	display_matrix[Y]=normal;
+	display_matrix[Z]=parentMatrix[Z];
+	display_matrix[X]=Mth::CrossProduct(display_matrix[Y],display_matrix[Z]);
+	
+	Mth::Vector scale(m_scale,m_scale,m_scale,1.0f);
+	display_matrix.Scale(scale);
+	
+	// Change requested by LF/Andre:  use the passed normal
+	display_matrix.SetPos(parentPos+normal*m_offset);  //WAS: parentPos+parentMatrix[Y]*m_offset
+ 	
+	//Dbg_MsgAssert(mp_model,("NULL mp_model"));
+	if (mp_model)
+	{
+		mp_model->Render(&display_matrix,true,NULL);
+	}	
+}
+
+void CSimpleShadow::Hide()
+{
+	//Dbg_MsgAssert(mp_model,("NULL mp_model"));
+	if (mp_model)
+	{
+		mp_model->SetActive(false);
+	}	
+}
+
+void CSimpleShadow::UnHide()
+{
+	//Dbg_MsgAssert(mp_model,("NULL mp_model"));
+	if (mp_model)
+	{
+		mp_model->SetActive(true);
+	}	
+}
+
+}
+
diff --git a/Code/Gfx/shadow.h b/Code/Gfx/shadow.h
new file mode 100644
index 0000000..a4544ef
--- /dev/null
+++ b/Code/Gfx/shadow.h
@@ -0,0 +1,130 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2001 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Shadow  (GFX)											**
+**																			**
+**	File name:		gfx/shadow.h											**
+**																			**
+**	Created: 		01/25/01	-	dc										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_SHADOW_H
+#define __GFX_SHADOW_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+namespace Nx
+{
+	class CModel;
+	class CTexture;
+	class CTextured3dPoly;
+}
+							 
+							 
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+enum EShadowType
+{
+	vUNDEFINED_SHADOW,
+	vDETAILED_SHADOW,
+	vSIMPLE_SHADOW
+};
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+
+class	CShadow : public Spt::Class
+{
+protected:
+	EShadowType	m_type;
+	
+public:
+								CShadow();
+	virtual						~CShadow();
+	virtual void				UpdatePosition(Mth::Vector pos) {}
+	virtual void				UpdatePosition(Mth::Vector& parentPos, Mth::Matrix& parentMatrix, Mth::Vector normal) {}
+	virtual void				UpdateDirection( const Mth::Vector& dir ) {}
+	virtual void				Hide() {}
+	virtual void				UnHide() {}
+	EShadowType					GetShadowType() {return m_type;}
+};
+
+class	CSimpleShadow : public CShadow
+{
+	//Nx::CTextured3dPoly *mp_poly;
+	Nx::CModel	*mp_model;
+	float m_scale;
+	float m_offset;
+	
+public:
+								CSimpleShadow();
+								~CSimpleShadow();
+	void						SetScale(float scale) {m_scale=scale;}
+	void						SetOffset(float offset) {m_offset=offset;}
+	void						SetModel(const char *p_model_name);
+	void						UpdatePosition(Mth::Vector& parentPos, Mth::Matrix& parentMatrix, Mth::Vector normal);
+	void						Hide();
+	void						UnHide();
+};
+
+
+class	Camera;				 
+class	CDetailedShadow : public CShadow
+{
+public:
+								CDetailedShadow(Nx::CModel *p_model, Mth::Vector camera_direction = Mth::Vector(0.0f,-1.0f,0.0f), float camera_distance = 72.0f);
+								~CDetailedShadow();
+	void    					UpdatePosition(Mth::Vector	pos);		// the position of the target
+	void    					UpdateDirection( const Mth::Vector& dir );		// the direction in which the camera points
+
+private:
+	Camera*						mp_camera;
+	Nx::CTexture*				mp_texture;
+	Mth::Vector					m_direction;
+	float						m_distance;
+};
+	   
+
+} // namespace Gfx
+
+#endif	// __GFX_SHADOW_H
diff --git a/Code/Gfx/stdafx.h b/Code/Gfx/stdafx.h
new file mode 100644
index 0000000..b670b06
--- /dev/null
+++ b/Code/Gfx/stdafx.h
@@ -0,0 +1,3 @@
+// You cannot conditionally compile-out stdafx.h in the PC project
+// so the only recourse is to always include "stdafx.h" in both
+// projects and to include this dummy version on the PS2 project
diff --git a/Code/Gfx/subanimcontroller.cpp b/Code/Gfx/subanimcontroller.cpp
new file mode 100644
index 0000000..8207065
--- /dev/null
+++ b/Code/Gfx/subanimcontroller.cpp
@@ -0,0 +1,1652 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       SubAnimController.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/06/2003
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+										   
+#include 
+#include 
+#include 
+#include 
+#include 
+						 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+						  
+namespace Gfx
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBonedAnimController::CBonedAnimController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+	mp_quickAnim = Nx::CEngine::sInitQuickAnim();
+	mp_quickAnim->Enable( false );
+	
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	m_animScriptName = pAnimationComponent->GetAnimScriptName();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBonedAnimController::~CBonedAnimController()
+{
+	if ( mp_quickAnim )
+	{
+		Nx::CEngine::sUninitQuickAnim( mp_quickAnim );
+		mp_quickAnim = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBonedAnimController::InitFromStructure( Script::CStruct* pParams )
+{
+	CBaseAnimController::InitFromStructure( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBonedAnimController::GetPose( Gfx::CPose* pResultPose )
+{
+	Dbg_MsgAssert( pResultPose, ( "No return data" ) );
+
+//	int skipList[vMAX_BONES];
+//	GetSkeleton()->GetSkipList( &skipList[0] );
+	uint32*	p_skip_list = GetSkeleton()->GetBoneSkipList();
+	uint32	skip_index	= GetSkeleton()->GetBoneSkipIndex();
+
+	Dbg_Assert( mp_quickAnim );
+//	mp_quickAnim->GetInterpolatedFrames( pResultPose->m_rotations, pResultPose->m_translations, &skipList[0], mp_blendChannel->GetCurrentAnimTime() );
+	mp_quickAnim->GetInterpolatedFrames( pResultPose->m_rotations, pResultPose->m_translations, p_skip_list, skip_index, mp_blendChannel->GetCurrentAnimTime() );
+	mp_quickAnim->Enable( true );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBonedAnimController::Update()
+{
+	Dbg_MsgAssert( mp_blendChannel->m_loopingType != Gfx::LOOPING_WOBBLE, ( "Not supposed to be wobble type" ) );
+			
+	float oldTime = mp_blendChannel->m_currentTime;
+
+	mp_blendChannel->m_currentTime = mp_blendChannel->get_new_anim_time();
+
+	mp_blendChannel->ProcessCustomKeys( oldTime, mp_blendChannel->m_currentTime );
+
+	if ( mp_blendChannel->m_currentTime < oldTime )
+	{
+		mp_quickAnim->Enable( false );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAnimFunctionResult CBonedAnimController::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case 0x5f495ae0:	// invalidateCache
+		{
+			mp_quickAnim->Enable( false );
+			
+			// want the other controllers to be invalidated too
+			return AF_NOT_EXECUTED;
+		}
+		break;
+
+		case 0xaf2fae19:	// playsequence
+		{
+			uint32 anim_name;
+			float start_time;
+			float end_time;
+			int loop_type;
+			float blend_period;
+			float speed;
+
+			pParams->GetChecksum( CRCD(0x6c2bfb7f,"animname"), &anim_name, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xd16b61e6,"starttime"), &start_time, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xab81bb3d,"endtime"), &end_time, Script::ASSERT );
+			pParams->GetInteger( CRCD(0xcd8d7c1b,"looptype"), &loop_type, Script::ASSERT );
+			pParams->GetFloat( CRCD(0x8f0d24ed,"blendperiod"), &blend_period, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xf0d90109,"speed"), &speed, Script::ASSERT );
+			
+			mp_blendChannel->CAnimChannel::PlaySequence( anim_name, start_time, end_time, (Gfx::EAnimLoopingType)loop_type, blend_period, speed);
+		
+			Dbg_Assert( mp_quickAnim );
+			mp_quickAnim->SetAnimAssetName( m_animScriptName + anim_name );
+		}
+		break;
+			
+		default:
+			return AF_NOT_EXECUTED;
+	}
+
+	return AF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWobbleController::CWobbleController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+	m_wobbleTargetTime			 = 0.0f;
+
+	m_wobbleDetails.wobbleAmpA    = 0.0f;
+	m_wobbleDetails.wobbleAmpB    = 0.0f;
+	m_wobbleDetails.wobbleK1      = 0.0f;
+	m_wobbleDetails.wobbleK2      = 0.0f;
+	m_wobbleDetails.spazFactor    = 0.0f;	
+	
+	mp_quickAnim = Nx::CEngine::sInitQuickAnim();
+	mp_quickAnim->Enable( false );
+	
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	m_animScriptName = pAnimationComponent->GetAnimScriptName();
+	
+	/*
+	float startTime;
+	float endTime;
+
+	if ( pParams->GetFloat( "startTime", &startTime, Script::NO_ASSERT );
+		&& pParams->GetFloat( "endTime", &endTime, Script::NO_ASSERT ) )
+	{
+		m_wobbleTargetTime = ( startTime + endTime ) / 2;
+	}
+	*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CWobbleController::~CWobbleController()
+{
+	if ( mp_quickAnim )
+	{
+		Nx::CEngine::sUninitQuickAnim( mp_quickAnim );
+		mp_quickAnim = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWobbleController::InitFromStructure( Script::CStruct* pParams )
+{
+	CBaseAnimController::InitFromStructure( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CWobbleController::GetPose( Gfx::CPose* pResultPose )
+{
+	Dbg_MsgAssert( pResultPose, ( "No return data" ) );
+
+//	int skipList[vMAX_BONES];
+//	GetSkeleton()->GetSkipList( &skipList[0] );
+	uint32*	p_skip_list = GetSkeleton()->GetBoneSkipList();
+	uint32	skip_index	= GetSkeleton()->GetBoneSkipIndex();
+	
+	Dbg_Assert( mp_quickAnim );
+//	mp_quickAnim->GetInterpolatedFrames( pResultPose->m_rotations, pResultPose->m_translations, &skipList[0], mp_blendChannel->GetCurrentAnimTime() );
+	mp_quickAnim->GetInterpolatedFrames( pResultPose->m_rotations, pResultPose->m_translations, p_skip_list, skip_index, mp_blendChannel->GetCurrentAnimTime() );
+	mp_quickAnim->Enable( true );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CWobbleController::get_new_wobble_time()
+{
+	float AmpA = m_wobbleDetails.wobbleAmpA;
+	float AmpB = m_wobbleDetails.wobbleAmpB;
+	float k1 = m_wobbleDetails.wobbleK1;
+	float k2 = m_wobbleDetails.wobbleK2;
+	float SpazFactor = m_wobbleDetails.spazFactor;
+
+	float new_time = m_wobbleTargetTime;
+
+	Dbg_MsgAssert( mp_blendChannel->m_startTime < mp_blendChannel->m_endTime, ("start time must be smaller than end time for LOOPING_WOBBLE"));
+	float l = ( mp_blendChannel->m_endTime - mp_blendChannel->m_startTime ) / 2;
+	float mid = ( mp_blendChannel->m_endTime + mp_blendChannel->m_startTime ) / 2;
+	float d = Mth::Abs( new_time - mid );
+	SpazFactor = 1 + ( SpazFactor - 1 ) * d / l;
+	AmpA *= SpazFactor;
+	AmpB *= SpazFactor;
+
+	if ( new_time - AmpA - AmpB < mp_blendChannel->m_startTime )
+	{
+		new_time = mp_blendChannel->m_startTime + AmpA + AmpB;
+	}	
+	if ( new_time + AmpA + AmpB > mp_blendChannel->m_endTime )
+	{
+		new_time = mp_blendChannel->m_endTime - AmpA - AmpB;
+	}	
+
+	float t = (int)Tmr::ElapsedTime( 0 ) * k1;
+	t -= 16.0f * ( ((int)t) / 16.0f );
+	float amp = AmpA + AmpB * sinf( t * 2.0f * Mth::PI );
+
+	t = (int)Tmr::ElapsedTime( 0 ) * k2;
+	t -= 16.0f * ( ((int)t) / 16.0f );
+
+	new_time += amp * sinf( t * 2.0f * Mth::PI );
+
+	// These should never be true, but just in case.
+	if (new_time < mp_blendChannel->m_startTime)
+	{
+		new_time = mp_blendChannel->m_startTime;
+	}
+	if (new_time > mp_blendChannel->m_endTime)
+	{
+		new_time = mp_blendChannel->m_endTime;
+	}
+
+	return new_time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWobbleController::Update()
+{
+	// the looping check below is only needed until
+	// we stop automatically adding the wobble
+	// controller to all channels
+	Dbg_MsgAssert ( mp_blendChannel->m_loopingType == Gfx::LOOPING_WOBBLE, ( "Was supposed to be wobble type %d", mp_blendChannel->m_loopingType ) );
+
+	// remember old time, so that we know whether to use the quick anim
+	float oldTime = mp_blendChannel->m_currentTime;
+
+	mp_blendChannel->m_currentTime = get_new_wobble_time();
+
+	// remember to use the quick anim
+	if ( mp_blendChannel->m_currentTime < oldTime )
+	{
+		mp_quickAnim->Enable( false );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CWobbleController::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	p_info->AddFloat( CRCD(0x359ac119,"m_wobbleTargetTime"), m_wobbleTargetTime );
+
+	p_info->AddFloat( CRCD(0xfd266a26,"wobbleAmpA"), m_wobbleDetails.wobbleAmpA );
+	p_info->AddFloat( CRCD(0x642f3b9c,"wobbleAmpB"), m_wobbleDetails.wobbleAmpB );
+	p_info->AddFloat( CRCD(0x0f43fd49,"wobbleK1"), m_wobbleDetails.wobbleK1 );
+	p_info->AddFloat( CRCD(0x964aacf3,"wobbleK2"), m_wobbleDetails.wobbleK2 );
+	p_info->AddFloat( CRCD(0xf90b0824,"spazFactor"), m_wobbleDetails.spazFactor );
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAnimFunctionResult CWobbleController::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case 0x5f495ae0:	// invalidateCache
+		{
+			mp_quickAnim->Enable( false );
+			
+			// want the other controllers to be invalidated too
+			return AF_NOT_EXECUTED;
+		}
+		break;
+
+		case 0xaf2fae19:	// playsequence
+		{
+			uint32 anim_name;
+			float start_time;
+			float end_time;
+			int loop_type;
+			float blend_period;
+			float speed;
+
+			pParams->GetChecksum( CRCD(0x6c2bfb7f,"animname"), &anim_name, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xd16b61e6,"starttime"), &start_time, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xab81bb3d,"endtime"), &end_time, Script::ASSERT );
+			pParams->GetInteger( CRCD(0xcd8d7c1b,"looptype"), &loop_type, Script::ASSERT );
+			pParams->GetFloat( CRCD(0x8f0d24ed,"blendperiod"), &blend_period, Script::ASSERT );
+			pParams->GetFloat( CRCD(0xf0d90109,"speed"), &speed, Script::ASSERT );
+			
+			mp_blendChannel->CAnimChannel::PlaySequence( anim_name, start_time, end_time, (Gfx::EAnimLoopingType)loop_type, blend_period, speed);
+		
+			Dbg_Assert( mp_quickAnim );
+			mp_quickAnim->SetAnimAssetName( m_animScriptName + anim_name );
+		}
+		break;
+			
+		case 0xd0209498:	// setwobbletarget
+		{
+			float wobbleTargetAlpha;
+
+			if ( pParams->GetFloat( CRCD(0x4d747fa0,"wobbletargetalpha"), &wobbleTargetAlpha, Script::NO_ASSERT ) )
+			{
+				// check bounds
+				if ( wobbleTargetAlpha < 0.0f )
+				{
+					wobbleTargetAlpha = 0.0f;
+				}
+
+				if ( wobbleTargetAlpha > 1.0f )
+				{
+					wobbleTargetAlpha = 1.0f;
+				}
+
+				m_wobbleTargetTime = mp_blendChannel->m_startTime + ( mp_blendChannel->m_endTime - mp_blendChannel->m_startTime ) * wobbleTargetAlpha;
+			}
+		}
+		break;
+			
+		case 0xea6d0efd:	// setwobbledetails
+		{
+			float wobbleAmpA;
+			float wobbleAmpB;
+			float wobbleK1;
+			float wobbleK2;
+			float spazFactor;
+
+			if ( pParams->GetFloat( CRCD(0xfd266a26,"wobbleAmpA"), &wobbleAmpA, Script::NO_ASSERT )
+				 && pParams->GetFloat( CRCD(0x642f3b9c,"wobbleAmpB"), &wobbleAmpB, Script::NO_ASSERT )
+				 && pParams->GetFloat( CRCD(0x0f43fd49,"wobbleK1"), &wobbleK1, Script::NO_ASSERT )
+				 && pParams->GetFloat( CRCD(0x964aacf3,"wobbleK2"), &wobbleK2, Script::NO_ASSERT )
+				 && pParams->GetFloat( CRCD(0xf90b0824,"spazFactor"), &spazFactor, Script::NO_ASSERT ) )
+			{
+				m_wobbleDetails.wobbleAmpA = wobbleAmpA;
+				m_wobbleDetails.wobbleAmpB = wobbleAmpB;
+				m_wobbleDetails.wobbleK1 = wobbleK1;
+				m_wobbleDetails.wobbleK2 = wobbleK2;
+				m_wobbleDetails.spazFactor = spazFactor;
+			}
+		}
+		break;
+
+		default:
+			return AF_NOT_EXECUTED;
+	}
+
+	return AF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFlipRotateController::CFlipRotateController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+	m_flipped = false;
+	m_rotated = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFlipRotateController::~CFlipRotateController()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFlipRotateController::InitFromStructure( Script::CStruct* pParams )
+{
+	CBaseAnimController::InitFromStructure( pParams );
+
+	int flipped;
+	if ( pParams->GetInteger( CRCD(0x0c7a712c,"flipped"), &flipped, Script::NO_ASSERT ) )
+	{
+		m_flipped = flipped;
+	}
+
+	int rotated;
+	if ( pParams->GetInteger( CRCD(0x6ea3704a,"rotated"), &rotated, Script::NO_ASSERT ) )
+	{
+		m_rotated = rotated;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CFlipRotateController::Update()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CFlipRotateController::GetPose( Gfx::CPose* pResultPose )
+{
+	// rather than using the m_flipped and m_rotated flags,
+	// we should use the one stored in the CAnimationComponent
+	// temporarily...  this is because right now we don't want 
+	// to blend across the flip (but eventually we will want to)
+
+	CSkeleton* pSkeleton = GetSkeleton();
+	Dbg_MsgAssert( pSkeleton, ( "No skeleton?" ) );
+
+	#ifdef	__NOPT_ASSERT__
+	{	// remove this scope if you actually want to use pAnimationComponent
+		// in something other than the assertion
+		Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+		Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	}
+	#endif
+	
+	Obj::CSkaterFlipAndRotateComponent* pSkaterFlipAndRotateComponent = GetSkaterFlipAndRotateComponentFromObject( GetObject() );
+
+	if ( pSkaterFlipAndRotateComponent && pSkaterFlipAndRotateComponent->IsBoardRotated() )
+//	if ( m_rotated )
+	{
+		int idx;
+		
+		// name of skateboard bone in new skeleton
+		idx = pSkeleton->GetBoneIndexById( CRCD(0x98971faf, "bone_board_root") );
+		if ( idx != -1 )
+		{	   
+			Mth::Quat* pQuat = (pResultPose->m_rotations + idx);
+			*pQuat = Mth::Quat(0.0f,0.0f,1.0f,0.0f) * *pQuat;
+			pQuat->Normalize();
+		}		
+	}
+
+	// GJ:  this remembers the flip state when the animation was
+	// first launched, so that we can blend across the flip...  
+	// it produces some weird effects currently in the THPS5, 
+	// (such as the pelvis rotating 180 degrees), but this may 
+	// be due to the way the scripts were originally written...
+	// i'll take a look at it later, but hopefully this will 
+	// give Left Field some of the functionality they've been
+	// looking for...
+	if ( m_flipped )
+//	if ( pAnimationComponent->IsFlipped() )
+	{
+		int i;
+		Gfx::CPose oldPose;
+		int numBones = pSkeleton->GetNumBones();
+
+//		oldPose = *pResultPose;
+		for ( i = 0; i < numBones; i++ )
+		{
+			oldPose.m_rotations[i] = pResultPose->m_rotations[i];
+		}	
+		for ( i = 0; i < numBones; i++ )
+		{
+			oldPose.m_translations[i] = pResultPose->m_translations[i];
+		}	
+
+		Mth::Quat* pQuat = (pResultPose->m_rotations);
+		Mth::Vector* pTrans = (pResultPose->m_translations);
+
+		for ( i = 0; i < numBones; i++ )
+		{
+			int flipIndex = pSkeleton->GetFlipIndex(i);	
+			if ( flipIndex != -1 )
+			{
+				*pQuat = *(oldPose.m_rotations+flipIndex);
+				*pTrans = *(oldPose.m_translations+flipIndex);				
+	   		}
+			else
+			{
+				*pQuat = *(oldPose.m_rotations+i);
+				*pTrans = *(oldPose.m_translations+i);
+			}
+			
+			// flip all rotations and translations on Y.
+			(*pQuat)[X] = -(*pQuat)[X];
+			(*pQuat)[W] = -(*pQuat)[W];
+			(*pTrans)[X] = -(*pTrans)[X];
+
+			pQuat++;
+			pTrans++;
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CIKController::CIKController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CIKController::~CIKController()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CIKController::InitFromStructure( Script::CStruct* pParams )
+{
+	CBaseAnimController::InitFromStructure( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CIKController::Update()
+{
+	// here we'd do things like grab the position of the bike pedals
+	// from the CBikeComponent, and override the leg bones, or whatever
+
+	// if data needs be shared among different
+	// blend channels, then we might want to consider
+	// storing it in a CIKComponent...  then, the
+	// Update() function can grab it like so:
+	// Obj::CIKComponent* pIKComponent = GetIKComponentFromObject( GetObject() );
+	// if ( pIKComponent )
+	// {
+	//		Mth::Vector steering_column_pos = pIKComponent->GetBonePosition( "steering_column" );
+	//		Mth::Vector left_handle_bar_pos	= pIKComponent->GetBonePosition( "left_handle_bar" );
+	// }
+
+	// if each blend channel needs its own set of IK data
+	// then we can just do it like so:
+	// Mth::Vector steering_column_pos = m_steering_column_pos;
+	// Mth::Vector left_handle_bar_pos = m_left_handle_bar_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CIKController::GetDebugInfo( Script::CStruct* p_info )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAnimFunctionResult CIKController::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return AF_NOT_EXECUTED;
+	}
+
+	return AF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CIKController::GetPose( Gfx::CPose* pResultPose )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CProceduralAnimController::CProceduralAnimController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CProceduralAnimController::~CProceduralAnimController()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProceduralAnimController::InitFromStructure( Script::CStruct* pParams )
+{
+	CBaseAnimController::InitFromStructure( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProceduralAnimController::Update()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CProceduralAnimController::GetPose( Gfx::CPose* pResultPose )
+{
+	// To be consistent with THPS4, we should actually do this 
+	// AFTER building the object-space transform of each bone
+	// (but i'll wait until someone actually asks for this feature)
+	
+	// I didn't fully re-implement this functionality from THPS4,
+	// since we're going to do our procedural cloth animations
+	// in a different way for THPS5.  I've left this partial
+	// implementation in as an example for other developers
+	// to write their own controllers.
+
+	// THPS4 had ping-ponging animations (so that we can animate
+	// the shirt rising and falling)...  we can re-implement
+	// this later if we end up deciding to use this controller...
+
+	CSkeleton* pSkeleton = GetSkeleton();
+	Dbg_MsgAssert( pSkeleton, ( "No skeleton?" ) );
+
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	int numProceduralBones = pAnimationComponent->GetNumProceduralBones(); 
+	CProceduralBone* p_current_procedural_bone = pAnimationComponent->GetProceduralBones();
+
+	for ( int i = 0; i < numProceduralBones; i++ )
+	{
+		int idx = pSkeleton->GetBoneIndexById( p_current_procedural_bone->m_name );
+		if ( idx != -1 )
+		{
+			Mth::Quat* pQuat = pResultPose->m_rotations + idx;
+
+			if ( p_current_procedural_bone->rotEnabled )
+			{
+				Mth::Vector theRotVector = p_current_procedural_bone->currentRot;
+				theRotVector.Scale( Mth::PI * 2.0f / 4096.0f );
+				Mth::Quat theRotQuat = Mth::EulerToQuat( theRotVector );
+				
+				// TODO:  should I be post-rotating this?
+				*pQuat = theRotQuat * *pQuat;
+				
+				pQuat->Normalize();
+			}
+		}
+											  
+		p_current_procedural_bone++;
+    }
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneTransMin( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->trans0 = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneTransMax( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->trans1 = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneTransSpeed( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->deltaTrans = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneTransActive( uint32 boneName, bool enabled )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->transEnabled = enabled;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneTransCurrent( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->currentTrans = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneRotMin( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->rot0[X] = vec[X] * 2.0f * Mth::PI / 4096.0f;
+		pProceduralBone->rot0[Y] = vec[Y] * 2.0f * Mth::PI / 4096.0f;
+		pProceduralBone->rot0[Z] = vec[Z] * 2.0f * Mth::PI / 4096.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneRotMax( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->rot1[X] = vec[X] * 2.0f * Mth::PI / 4096.0f;
+		pProceduralBone->rot1[Y] = vec[Y] * 2.0f * Mth::PI / 4096.0f;
+		pProceduralBone->rot1[Z] = vec[Z] * 2.0f * Mth::PI / 4096.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneRotSpeed( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->deltaRot = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneRotCurrent( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->currentRot = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneRotActive( uint32 boneName, bool enabled )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->rotEnabled = enabled;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneScaleMin( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->scale0 = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneScaleMax( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->scale1 = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneScaleSpeed( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->deltaScale = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneScaleCurrent( uint32 boneName, const Mth::Vector& vec )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->currentScale = vec;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CProceduralAnimController::SetProceduralBoneScaleActive( uint32 boneName, bool enabled )
+{
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	Dbg_MsgAssert( pAnimationComponent, ( "No animation component?" ) );
+	
+	CProceduralBone* pProceduralBone = pAnimationComponent->GetProceduralBoneByName( boneName );
+
+	if ( pProceduralBone )
+	{
+		pProceduralBone->scaleEnabled = enabled;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// TODO:  It's kind of backwards for the CAnimationComponent to be calling
+// member functions in the CProceduralAnimController that set values
+// back inside the CAnimationComponent...  This is because I haven't
+// really decided whether each blend channel should have its own
+// proc anim data, or whether the blend channels should share a single
+// set of proc anim data.  Right now, the blend channels all share
+// a single set of proc anim data (stored in the CAnimationComponent).
+
+EAnimFunctionResult CProceduralAnimController::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+	    // @script | SetBoneTransMin | Sets the minimum translation extent for procedurally animated bone (in world units)
+        // @parmopt float | x | 0 | minimum extent x
+        // @parmopt float | y | 0 | minimum extent y
+        // @parmopt float | z | 0 | minimum extent z
+        // @parm name | bone | name of procedural bone
+		case 0x02565bd1: //	SetBoneTransMin
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneTransMin( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneTransMax | Sets the maximum translation extent for procedurally animated bone (in world units)
+		// @parmopt float | x | 0 | maximum extent x
+		// @parmopt float | y | 0 | maximum extent y
+		// @parmopt float | z | 0 | maximum extent z
+		// @parm name | bone | name of procedural bone
+		case 0x3e5b6488: //	SetBoneTransMax
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneTransMax( boneName, vec );
+			}
+			break;
+		
+		// @script | SetBoneTransSpeed | Sets the speed at which the bone's translation will procedurally animate (in 4096 notation)
+		// @parmopt int | x | 0 | speed x (2048 immediately goes from T0 to T1)
+		// @parmopt int | y | 0 | speed y (2048 immediately goes from T0 to T1)
+		// @parmopt int | z | 0 | speed z (2048 immediately goes from T0 to T1)
+		// @parm name | bone | name of procedural bone
+		case 0xc85b9ac0: //	SetBoneTransSpeed
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				Dbg_Assert( mp_blendChannel );
+				this->SetProceduralBoneTransSpeed( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneTransCurrent | Immediately sets the procedurally animated bone to a specific translation (in 4096 notation)
+		// @parmopt int | x | 0 | current x (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parmopt int | y | 0 | current y (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parmopt int | z | 0 | current z (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parm name | bone | name of procedural bone
+		case 0x5ee9d5bd: //	SetBoneTransCurrent
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneTransCurrent( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneTransActive | Sets whether the bone should procedurally animate its translation
+		// @uparm int | active (either 0 or 1)
+		// @parm name | bone | name of procedural bone
+		case 0x5661fb72: //	SetBoneTransActive
+			{
+				uint32 boneName;
+				int enabled;
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				pParams->GetInteger( NONAME, &enabled, true );
+				
+                Dbg_Assert( mp_blendChannel );
+                this->SetProceduralBoneTransActive( boneName, enabled );
+			}
+			break;
+		
+		// @script | SetBoneRotMin | Sets the minimum rotation extent for procedurally animated bone (in 4096 notation)
+		// @parmopt int | x | 0 | minimum extent x (in 4096 notation)
+		// @parmopt int | y | 0 | minimum extent y (in 4096 notation)
+		// @parmopt int | z | 0 | minimum extent z (in 4096 notation)
+		// @parm name | bone | name of procedural bone
+		case 0xa47767f2: //	SetBoneRotMin
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneRotMin( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneRotMax | Sets the maximum rotation extent for procedurally animated bone (in 4096 notation)
+		// @parmopt int | x | 0 | maximum extent x (in 4096 notation)
+		// @parmopt int | y | 0 | maximum extent y (in 4096 notation)
+		// @parmopt int | z | 0 | maximum extent z (in 4096 notation)
+		case 0x987a58ab: //	SetBoneRotMax
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneRotMax( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneRotSpeed | Sets the speed at which the bone's rotation will procedurally animate (in 4096 notation)
+		// @parmopt int | x | 0 | speed x (2048 immediately goes from T0 to T1)
+		// @parmopt int | y | 0 | speed y (2048 immediately goes from T0 to T1)
+		// @parmopt int | z | 0 | speed z (2048 immediately goes from T0 to T1)
+		// @parm name | bone | name of procedural bone
+		case 0x599d3707: //	SetBoneRotSpeed
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+
+				this->SetProceduralBoneRotSpeed( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneRotCurrent | Immediately sets the procedurally animated bone to a specific rotation (in 4096 notation)
+		// @parmopt int | x | 0 | current x (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parmopt int | y | 0 | current y (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parmopt int | z | 0 | current z (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parm name | bone | name of procedural bone
+		case 0x7235daa7: //	SetBoneRotCurrent
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneRotCurrent( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneRotActive | Sets whether the bone should procedurally animate its rotation
+		// @uparm int | active (either 0 or 1)
+		// @parm name | bone | name of procedural bone
+		case 0x53f06acc: //	SetBoneRotActive
+			{		
+				uint32 boneName;
+				int enabled;
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				pParams->GetInteger( NONAME, &enabled, true );
+
+                this->SetProceduralBoneRotActive( boneName, enabled );
+			}
+			break;
+			
+		// @script | SetBoneScaleMin | Sets the minimum translation extent for procedurally animated bone (in world units)
+        // @parmopt float | x | 0 | minimum extent x
+        // @parmopt float | y | 0 | minimum extent y
+        // @parmopt float | z | 0 | minimum extent z
+        // @parm name | bone | name of procedural bone
+		case 0xc6889e91: //	SetBoneScaleMin
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneScaleMin( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneScaleMax | Sets the maximum translation extent for procedurally animated bone (in world units)
+		// @parmopt float | x | 0 | maximum extent x
+		// @parmopt float | y | 0 | maximum extent y
+		// @parmopt float | z | 0 | maximum extent z
+		// @parm name | bone | name of procedural bone
+		case 0xfa85a1c8: //	SetBoneScaleMax
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneScaleMax( boneName, vec );
+			}
+			break;
+		
+		// @script | SetBoneScaleSpeed | Sets the speed at which the bone's translation will procedurally animate (in 4096 notation)
+		// @parmopt int | x | 0 | speed x (2048 immediately goes from T0 to T1)
+		// @parmopt int | y | 0 | speed y (2048 immediately goes from T0 to T1)
+		// @parmopt int | z | 0 | speed z (2048 immediately goes from T0 to T1)
+		// @parm name | bone | name of procedural bone
+		case 0xd32c2724: //	SetBoneScaleSpeed
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneScaleSpeed( boneName, vec );
+			}
+			break;
+			
+		// @script | SetBoneScaleCurrent | Immediately sets the procedurally animated bone to a specific translation (in 4096 notation)
+		// @parmopt int | x | 0 | current x (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parmopt int | y | 0 | current y (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parmopt int | z | 0 | current z (0 = T0, 1024 = halfway between T0 and T1, 2048 = T1, 3072 = halfway between T1 and T0, 4096 = T0)
+		// @parm name | bone | name of procedural bone
+		case 0xd12b3713: //	SetBoneScaleCurrent
+			{
+				Mth::Vector vec(0.0f,0.0f,0.0f);
+				uint32 boneName;
+				pParams->GetFloat( CRCD(0x7323e97c,"x"), &vec[X], false );
+				pParams->GetFloat( CRCD(0x0424d9ea,"y"), &vec[Y], false );
+				pParams->GetFloat( CRCD(0x9d2d8850,"z"), &vec[Z], false );
+				pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, true );
+				
+				this->SetProceduralBoneScaleCurrent( boneName, vec );
+			}
+			break;
+		
+		// @script | SetBoneScaleActive | Sets whether the bone should procedurally animate its translation
+		// @uparm int | active (either 0 or 1)
+		// @parm name | bone | name of procedural bone
+		case 0xf11daaae: //	SetBoneScaleActive
+			{
+				uint32 boneName;
+				int enabled;
+				pParams->GetChecksum( "bone", &boneName, true );
+				pParams->GetInteger( NONAME, &enabled, true );
+				
+                this->SetProceduralBoneScaleActive( boneName, enabled );
+			}
+			break;
+		
+		default:
+			return AF_NOT_EXECUTED;
+	}
+
+	return AF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPoseController::CPoseController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPoseController::~CPoseController()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPoseController::InitFromStructure( Script::CStruct* pParams )
+{
+	CBaseAnimController::InitFromStructure( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPoseController::Update()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPoseController::SetPose( Gfx::CPose* pPose )
+{
+	memcpy( &m_pose, pPose, sizeof(Gfx::CPose) );
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPoseController::GetPose( Gfx::CPose* pResultPose )
+{
+	// this nukes the existing pose, meaning that
+	// this should be the only controller in the list...
+	memcpy( pResultPose, &m_pose, sizeof(Gfx::CPose) );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CLookAtController::CLookAtController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CLookAtController::~CLookAtController()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLookAtController::InitFromStructure( Script::CStruct* pParams )
+{
+	CBaseAnimController::InitFromStructure( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CLookAtController::Update()
+{
+	Dbg_MsgAssert( 0, ( "Unimplemented function" ) );
+
+	// GJ:  This is just a sample controller...  I was thinking
+	// that this could be used for orienting the head towards
+	// an object in the scene, given some constraints
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPartialAnimController::CPartialAnimController( CBlendChannel* pBlendChannel ) : CBaseAnimController( pBlendChannel )
+{
+	mp_quickAnim = Nx::CEngine::sInitQuickAnim();
+	mp_quickAnim->Enable( false );
+	
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	m_animScriptName = pAnimationComponent->GetAnimScriptName();
+
+	mp_animChannel = new Gfx::CAnimChannel;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPartialAnimController::~CPartialAnimController()
+{
+	if ( mp_quickAnim )
+	{
+		Nx::CEngine::sUninitQuickAnim( mp_quickAnim );
+		mp_quickAnim = NULL;
+	}
+
+	if ( mp_animChannel )
+	{
+		delete mp_animChannel;
+		mp_animChannel = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPartialAnimController::InitFromStructure( Script::CStruct* pParams )
+{   
+	CBaseAnimController::InitFromStructure( pParams );
+	
+	uint32 anim_name;
+	pParams->GetChecksum( CRCD(0x6c2bfb7f,"animName"), &anim_name, Script::ASSERT );
+	
+	float From = 0.0f;
+	float To = 0.0f;
+	float Current = 0.0f;
+	Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( GetObject() );
+	float Duration = pAnimationComponent->AnimDuration( anim_name );
+	Gfx::GetTimeFromParams( &From, &To, Current, Duration, pParams, NULL );
+	  
+	Gfx::EAnimLoopingType loopingType;
+	Gfx::GetLoopingTypeFromParams( &loopingType, pParams );
+
+	float speed = 1.0f;
+	pParams->GetFloat( CRCD(0xf0d90109,"speed"), &speed, Script::NO_ASSERT );
+	
+	pParams->GetFloat(CRCD(0x230ccbf4, "Current"), &Current);
+
+	float blend_period = 0.0f;
+
+	mp_animChannel->PlaySequence( anim_name, From, To, loopingType, blend_period, speed);
+	
+	if (Current != 0.0f)
+	{
+		mp_animChannel->AddTime(Current);
+	}
+
+	Dbg_Assert( mp_quickAnim );
+	mp_quickAnim->SetAnimAssetName( m_animScriptName + anim_name );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPartialAnimController::GetPose( Gfx::CPose* pResultPose )
+{
+	
+	#ifdef	__NOPT_ASSERT__
+	{
+		CSkeleton* pSkeleton = GetSkeleton();
+		Dbg_MsgAssert( pSkeleton, ( "No skeleton?" ) );	
+		Dbg_MsgAssert( pResultPose, ( "No return data" ) );
+	}
+	#endif
+	
+//	int skipList[vMAX_BONES];
+//	GetSkeleton()->GetSkipList( &skipList[0] );
+	uint32*	p_skip_list = GetSkeleton()->GetBoneSkipList();
+	uint32	skip_index	= GetSkeleton()->GetBoneSkipIndex();
+
+#if 1
+	Dbg_Assert( mp_quickAnim );
+	mp_quickAnim->GetInterpolatedFrames( pResultPose->m_rotations, pResultPose->m_translations, p_skip_list, skip_index, mp_animChannel->GetCurrentAnimTime() );
+	mp_quickAnim->Enable( true );
+#else
+	Mth::Quat theRot[128];
+	Mth::Vector theTrans[128];
+
+	Dbg_Assert( mp_quickAnim );
+	mp_quickAnim->GetInterpolatedFrames( theRot, theTrans, p_skip_list, skip_index, mp_animChannel->GetCurrentAnimTime() );
+	mp_quickAnim->Enable( true );
+	
+	Script::CArray* pArray = Script::GetArray( m_boneListName, Script::ASSERT );
+	for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+	{
+		int idx = pSkeleton->GetBoneIndexById( pArray->GetChecksum(i) );
+		if ( idx != -1 )
+		{
+			*(pResultPose->m_rotations+idx)=theRot[idx];
+			*(pResultPose->m_translations+idx)=theTrans[idx];
+		}
+	}
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPartialAnimController::Update()
+{
+	// This class is used for either appending,
+	// or overwriting a subset of the bones in the RT array,
+	// given a CBonedAnimFrameData...
+	Dbg_MsgAssert( mp_animChannel->m_loopingType != Gfx::LOOPING_WOBBLE, ( "Not supposed to be wobble type" ) );
+			
+	float oldTime = mp_animChannel->m_currentTime;
+
+	mp_animChannel->m_currentTime = mp_animChannel->get_new_anim_time();
+
+//	mp_animChannel->ProcessCustomKeys( oldTime, mp_animChannel->m_currentTime );
+
+	if ( mp_animChannel->m_currentTime < oldTime )
+	{
+		mp_quickAnim->Enable( false );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAnimFunctionResult CPartialAnimController::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case 0x5f495ae0:	// InvalidateCache
+		{
+			mp_quickAnim->Enable( false );
+			
+			// want the other controllers to be invalidated too
+			return AF_NOT_EXECUTED;
+		}
+		break;
+
+		case 0xd63a1b81:	// GetPartialAnimParams
+		{
+			Dbg_Assert( pScript );
+			pScript->GetParams()->AddFloat( CRCD(0xaaecb346,"partialAnimTime"), mp_animChannel->GetCurrentAnimTime() );
+			pScript->GetParams()->AddChecksum( CRCD(0x659bf355,"partialAnim"), mp_animChannel->GetCurrentAnim() );
+			pScript->GetParams()->AddInteger( CRCD(0x4966cc11,"partialAnimComplete"), mp_animChannel->IsAnimComplete() );
+
+			return AF_TRUE;
+		}
+		break;
+
+		case 0xbd4edd44: // SetPartialAnimSpeed
+		{
+			float speed;
+			pParams->GetFloat( CRCD(0xf0d90109,"speed"), &speed, Script::ASSERT );			
+			mp_animChannel->SetAnimSpeed( speed );
+
+			return AF_TRUE;
+		}
+		break;
+
+		case 0x6aaeb76f: // IncrementPartialAnimTime
+		{
+			float incVal;
+			pParams->GetFloat( CRCD(0x06c9f278,"incVal"), &incVal, Script::ASSERT );			
+			
+			// GJ:  a way to fool the animation controller
+			// into incrementing its time (frame rate
+			// independent) for the viewer object
+
+			float oldAnimSpeed = mp_animChannel->GetAnimSpeed();
+
+			mp_animChannel->SetAnimSpeed( 0.0f );
+
+			mp_animChannel->m_currentTime += incVal;
+
+			mp_animChannel->Update();
+
+			mp_animChannel->SetAnimSpeed( oldAnimSpeed );
+
+			return AF_TRUE;
+		}
+		break;
+			
+		case 0xf5e2b871: // ReversePartialAnimDirection
+		{
+			mp_animChannel->ReverseDirection();
+
+			return AF_TRUE;
+		}
+		break;
+
+		default:
+			return AF_NOT_EXECUTED;
+	}
+
+	return AF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
+
diff --git a/Code/Gfx/subanimcontroller.h b/Code/Gfx/subanimcontroller.h
new file mode 100644
index 0000000..625db25
--- /dev/null
+++ b/Code/Gfx/subanimcontroller.h
@@ -0,0 +1,307 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       SubAnimController.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  2/06/2003
+//****************************************************************************
+
+#ifndef __GFX_SUBANIMCONTROLLER_H__
+#define __GFX_SUBANIMCONTROLLER_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+// This file contains all the custom animation controllers
+// which can be chained together by an animation channel
+// to produce its final array of RT data for each frame.
+// For example, an animated skeleton might need to:
+//   (1) grab interpolated keyframe data
+//   (2) flip the skeleton
+//   (3) rotate the skateboard by 180 degrees
+//   (4) perform IK on the leg bones to lock them to the skateboard
+//   (5) apply an additional keyframed anim to only the cloth bones
+//   (6) orient the head towards the closest pedestrian.
+// In this case, there would be one animation channel with
+// six animation controllers.
+
+// Eventually, we'll probably want to break up this
+// code into a separate file for each animation
+// controller, and perhaps create a factory class so that
+// BlendChannel.cpp doesn't need to be updated every time
+// a new animation controller is added.
+						   
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+
+namespace Nx
+{
+	class CQuickAnim;
+}
+
+namespace Gfx
+{
+	class CBlendChannel;
+	class CProceduralBone;
+	class CAnimChannel;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller is used for getting interpolated
+// keyframe data from a CBonedAnimFrameData.  It
+// accesses it through a Nx::CQuickAnim, which allows
+// for future optimizations.
+
+class CBonedAnimController : public CBaseAnimController
+{
+public:
+	CBonedAnimController( CBlendChannel* pBlendChannel );
+	virtual	~CBonedAnimController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+    virtual EAnimFunctionResult CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+	
+protected:
+	Nx::CQuickAnim*				mp_quickAnim;
+	uint32						m_animScriptName;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller is used for wobble effects (such
+// as grinding and manualling).  It's very similar to
+// the boned anim controller, and should probably be
+// refactored to share some code at some point.
+
+class CWobbleController : public CBaseAnimController
+{
+public:
+	CWobbleController( CBlendChannel* pBlendChannel );
+	virtual	~CWobbleController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+	virtual void				GetDebugInfo( Script::CStruct* p_info );
+    virtual EAnimFunctionResult CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+
+protected:
+    SWobbleDetails  			m_wobbleDetails;
+    float						m_wobbleTargetTime;
+	Nx::CQuickAnim*				mp_quickAnim;
+	uint32						m_animScriptName;
+
+protected:
+	float						get_new_wobble_time();
+
+public:
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller is used for flipping the skeleton
+// and rotating the skateboard.  Truthfully, the two
+// actions are completely unrelated, but they are
+// so simple that it seemed like a waste to separate
+// them into two controllers.  Later on, I may decide
+// to add separate controllers for each.
+
+class CFlipRotateController : public CBaseAnimController
+{
+public:
+	CFlipRotateController( CBlendChannel* pBlendChannel );
+	virtual ~CFlipRotateController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+	
+protected:
+	bool						m_flipped;
+	bool						m_rotated;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller is a placeholder for Left Field's
+// IK code.  Although THPS4 did not use IK, it would
+// be easy to add once LF and Neversoft both reorganized
+// their code to use this system of animation channels
+// and animation controllers.
+
+class CIKController : public CBaseAnimController
+{
+public:
+	CIKController( CBlendChannel* pBlendChannel );
+	virtual ~CIKController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+	virtual void				GetDebugInfo( Script::CStruct* p_info );
+    virtual EAnimFunctionResult CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller is also a placeholder.  On THPS4,
+// we implemented something similar in order to
+// programmatically animate our cloth bones.  This
+// system was inefficient and unwieldy, and we've
+// decided to scrap it in order to implement 
+// "partial animations", which means the animators 
+// can create SKA files containing animation
+// data for only the cloth bones.  Left Field can
+// potentially use the CProceduralAnimController
+// to do programmatic animation on the bike's tires.
+
+class CProceduralAnimController : public CBaseAnimController
+{
+public:
+	CProceduralAnimController( CBlendChannel* pBlendChannel );
+	virtual ~CProceduralAnimController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();    
+	virtual EAnimFunctionResult CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+	
+protected:
+	CProceduralBone*			get_procedural_bone_by_id( uint32 boneName );
+	
+protected:
+	void		   				SetProceduralBoneTransMin( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneTransMax( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneTransSpeed( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneTransCurrent( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneTransActive( uint32 boneName, bool enabled );
+	void						SetProceduralBoneRotMin( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneRotMax( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneRotSpeed( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneRotCurrent( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneRotActive( uint32 boneName, bool enabled );
+	void						SetProceduralBoneScaleMin( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneScaleMax( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneScaleSpeed( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneScaleCurrent( uint32 boneName, const Mth::Vector& vec );
+	void						SetProceduralBoneScaleActive( uint32 boneName, bool enabled );
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller just stores a single Gfx::CPose.
+// Perhaps degenerating blend channels can
+// collapse their list of animation controllers
+// and replace them with the CPoseController
+// as a slight optimization.
+
+class CPoseController : public CBaseAnimController
+{
+public:
+	CPoseController( CBlendChannel* pBlendChannel );
+	virtual	~CPoseController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+
+public:
+	void						SetPose( Gfx::CPose* pPose );
+
+protected:
+	Gfx::CPose					m_pose;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller hasn't been implemented yet.
+// I was thinking that it could be used to
+// reorient the head towards a particular lookup
+// target.
+
+class CLookAtController : public CBaseAnimController
+{
+public:
+	CLookAtController( CBlendChannel* pBlendChannel );
+	virtual	~CLookAtController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This controller hasn't been implemented yet.
+// It will be used to read a partial SKA anim 
+// from a file, for animating cloth bones and
+// for doing secondary anims (for example, we
+// may want to apply an additional wobble animation
+// to the upper body when the skater lands from
+// a particular high trick)
+class CPartialAnimController : public CBaseAnimController
+{
+
+public:
+	CPartialAnimController( CBlendChannel* pBlendChannel );
+	virtual ~CPartialAnimController();
+
+public:
+	virtual void 				InitFromStructure( Script::CStruct* pParams );
+	virtual void 				Update();
+	virtual bool				GetPose( Gfx::CPose* pResultPose );
+	EAnimFunctionResult			CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+
+public:
+	Gfx::CAnimChannel*			mp_animChannel;
+	Nx::CQuickAnim*				mp_quickAnim;
+	uint32						m_animScriptName;
+};
+																			   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
+
+#endif
diff --git a/Code/Gfx/vecfont.cpp b/Code/Gfx/vecfont.cpp
new file mode 100644
index 0000000..9cdcfcb
--- /dev/null
+++ b/Code/Gfx/vecfont.cpp
@@ -0,0 +1,449 @@
+#if 0
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics Lib (gfx)		 								**
+**																			**
+**	File name:		vecfont.cpp												**
+**																			**
+**	Created:		01/31/99	-	mjb										**
+**																			**
+**	Description:	Vector font class										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+namespace Gfx
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+uint32				VecFont::s_char_length[256];
+VecFont::CharLine*		VecFont::s_char_maps[256];
+VecFont::CharLine		VecFont::s_symbol_maps1[15][16] =
+{
+	 {{64,0,64,16},
+      {64,32,64,128}},          /* ! */
+     {{32,96,32,128},
+      {96,96,96,128}},          /* " */
+     {{32,0,32,128},
+      {96,0,96,128},
+      {0,32,128,32},
+      {0,96,128,96}},           /* # */
+     {{0,0,128,0},
+      {128,0,128,64},
+      {128,64,0,64},
+      {0,64,0,128},
+      {0,128,128,128},
+      {64,0,64,128}},           /* $ */
+     {{0,0,128,128},
+      {0,112,16,128},
+      {112,0,128,16}},          /* % */
+     {{0,0,0,0}},               /* & - yeah,   right */
+     {{64,96,64,128}},          /* ' */
+     {{96,128,64,96},
+      {64,96,64,32},
+      {64,32,96,0}},            /* ( */
+     {{32,128,64,96},
+      {64,96,64,32},
+      {64,32,32,0}},            /* ) */
+     {{64,0,64,128},
+      {0,64,128,64},
+      {32,32,96,96},
+      {32,96,96,32}},           /* * */
+     {{64,32,64,96},
+      {32,64,96,32}},           /* + */
+     {{64,0,96,32}},            /* , */
+     {{32,64,96,64}},           /* - */
+     {{64,0,64,32}},            /* . */     
+     {{0,0,128,128}}
+};          /* / */
+
+VecFont::CharLine VecFont::s_symbol_maps2[7][16] =
+{
+	 {{64,0,64,16},
+	  {64,128,64,112}},         /* : */
+     {{32,0,64,32},
+      {64,128,64,112}},         /* ; */
+     {{128,128,0,64},
+      {0,64,128,0}},            /* < */
+     {{0,32,128,32},
+      {0,96,128,96}},           /* =
+                                 */
+     {{0,128,128,64},
+      {128,64,0,0}},            /* > */
+     {{0,0,0,0}},               /* ? - yeah, right */
+     {{0,0,0,0}}                /* @ - yeah, right */
+};              
+
+VecFont::CharLine VecFont::s_digit_maps[10][16] =
+{
+	 {{0,0,128,0},
+      {128,0,128,128},
+      {128,128,0,128},
+      {0,128,0,0}},             /* 0 */
+     {{128,0,128,128}},         /* 1 */
+     {{0,128,128,128},
+      {128,128,128,64},
+      {128,64,0,64},
+      {0,64,0,0},
+      {0,0,128,0}},             /* 2 */
+     {{0,128,128,128},
+      {128,128,128,0},
+      {128,0,0,0},
+      {128,64,0,64}},           /* 3 */
+     {{0,128,0,64},
+      {0,64,128,64},
+      {128,128,128,0}},         /* 4 */
+     {{128,128,0,128},
+      {0,128,0,64},
+      {0,64,128,64},
+      {128,64,128,0},
+      {128,0,0,0}},             /* 5 */
+     {{128,128,0,128},
+      {0,128,0,0},
+      {0,0,128,0},
+      {128,0,128,64},
+      {128,64,0,64}},           /* 6 */
+     {{0,128,128,128},
+      {128,128,128,0}},         /* 7 */
+     {{0,0,128,0},
+      {128,0,128,128},
+      {128,128,0,128},
+      {0,128,0,0},
+      {0,64,128,64}},           /* 8 */
+     {{0,0,128,0},
+      {128,0,128,128},
+      {128,128,0,128},
+      {0,128,0,64},      {0,64,128,64}}
+}; /* 9 */
+
+VecFont::CharLine VecFont::s_alpha_maps[26][16] =
+{
+	 {{0,0,64,128},
+      {64,128,128,0},
+      {32,64,96,64}},           /* a */
+     {{0,128,0,0},
+      {0,0,128,0},
+      {128,0,128,64},
+      {128,64,0,64}},           /* b */
+     {{128,128,0,128},
+      {0,128,0,0},
+      {0,0,128,0}},             /* c */
+     {{0,0,0,128},
+      {0,128,128,64},
+      {128,64,128,0},
+      {128,0,0,0}},             /* d */
+     {{128,128,0,128},
+      {0,128,0,0},
+      {0,0,128,0},
+      {0,64,64,64}},            /* e */
+     {{128,128,0,128},
+      {0,128,0,0},
+      {0,64,64,64}},            /* f */
+     {{128,128,0,128},
+      {0,128,0,0},
+      {0,0,128,0},
+      {128,0,128,64}},          /* g */
+     {{0,128,0,0},
+      {0,64,128,64},
+      {128,128,128,0}},         /* h */
+     {{64,0,64,128}},           /* i */
+     {{0,0,128,0},
+      {128,0,128,128}},         /* j */
+     {{0,0,0,128},
+      {128,0,0,64},
+      {0,64,128,128}},          /* k */
+     {{0,0,0,128},
+      {0,0,128,0}},             /* l */
+     {{0,0,0,128},
+      {0,128,64,0},
+      {64,0,128,128},
+      {128,128,128,0}},         /* m */
+     {{0,0,0,128},
+      {0,128,128,128},
+      {128,128,128,0}},         /* n */
+     {{0,0,128,0},
+      {128,0,128,128},
+      {128,128,0,128},
+      {0,128,0,0}},             /* o */
+     {{0,0,0,128},
+      {0,128,128,128},
+      {128,128,128,64},
+      {128,64,0,64}},           /* p */
+     {{128,0,128,128},
+      {128,128,0,128},
+      {0,128,0,64},
+      {0,64,128,64}},           /* q */
+     {{0,0,0,128},
+      {0,128,128,128},
+      {128,128,128,64},
+      {128,64,0,64},
+      {0,64,128,0}},            /* r */
+     {{0,0,128,0},
+      {128,0,128,64},
+      {128,64,0,64},
+      {0,64,0,128},
+      {0,128,128,128}},         /* s */
+     {{0,128,128,128},
+      {64,128,64,0}},           /* t */
+     {{0,128,0,0},
+      {0,0,128,0},
+      {128,0,128,128}},         /* u */
+     {{0,128,64,0},
+      {64,0,128,128}},          /* v */
+     {{0,128,0,0},
+      {0,0,64,128},
+      {64,128,128,0},
+      {128,0,128,128}},         /* w */
+     {{0,128,128,0},
+      {0,0,128,128}},           /* x */
+     {{0,0,128,0},
+      {128,0,128,128},
+      {0,128,0,64},
+      {0,64,128,64}},           /* y */
+     {{128,0,0,0},
+      {0,0,128,128},      
+      {128,128,0,128}}			/* z */
+};
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+void VecFont::assign_line_list( uint32 target, CharLine* lineList )
+{
+    s_char_length[target] = 0;
+    s_char_maps[target] = lineList;
+
+    while ( lineList->startX | lineList->startY | lineList->endX | lineList->endY )
+    {
+        s_char_length[target]++;
+        lineList++;
+    }
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+VecFont::VecFont( const Image::RGBA color, float w, float h )
+{
+/*
+        
+//	RwV2dScale( &m_size, size, (RwReal)(1.0f/192.0f) );
+	
+	m_color = color;
+	m_lineVertBuffer = NULL;
+	m_lineVertBufferSize = 0;
+
+    uint32 i;
+
+    for (i = 0; i < 256; i++)
+    {
+        s_char_length[i] = 0;
+    }
+
+    for (i = 0; i < 15; i++)
+    {
+        assign_line_list('!'+i, s_symbol_maps1[i]);
+    }
+
+    for (i = 0; i < 10; i++)
+    {
+        assign_line_list('0'+i, s_digit_maps[i]);
+    }
+
+    for (i = 0; i < 7; i++)
+    {
+        assign_line_list(':'+i, s_symbol_maps2[i]);
+    }
+
+    for (i = 0; i < 26; i++)
+    {
+        assign_line_list('a'+i, s_alpha_maps[i]);
+        assign_line_list('A'+i, s_alpha_maps[i]);
+    }
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define	VECFONTSIZE  50000
+
+void	VecFont::Print( float x, float y, const char* string )
+{
+	
+#if 0
+
+#ifndef __PLAT_NGC__
+    uint32 i, numLines;
+    RwV2d    localPos = pos;
+
+    /* Make sure that the line vertex buffer is big enough */
+    i = numLines = 0;
+    while (string[i])
+    {
+        numLines += s_char_length[(uint32)string[i]];
+        i++;
+    }
+
+
+
+    /* Will it fit? */
+    if (numLines > m_lineVertBufferSize)
+    {
+        uint32		newSize = 2 * sizeof(RWIM2DVERTEX) * numLines;
+
+		if (newSize < VECFONTSIZE)
+		{
+			numLines = VECFONTSIZE / (2 * sizeof(RWIM2DVERTEX))  + 1;
+			newSize = 2 * sizeof(RWIM2DVERTEX) * numLines;
+		}
+		else
+		{
+			Dbg_MsgAssert(0,("metric vector font overflow, numLines = %d, m_lineVertBufferSize %d ", numLines, m_lineVertBufferSize));
+		}
+		
+		printf(">>>>>>>>>>>>>>>>>>>>>>>>> newSize = %d\n",newSize);
+		
+        RWIM2DVERTEX*	newLineVertBuffer;
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().RwHeap());
+        if ( m_lineVertBuffer )
+        {
+            newLineVertBuffer = (RWIM2DVERTEX *)RwRealloc( m_lineVertBuffer, newSize );
+        }
+        else
+        {
+            newLineVertBuffer = (RWIM2DVERTEX *)RwMalloc(newSize);
+        }
+		Mem::Manager::sHandle().PopContext();
+
+        if (newLineVertBuffer)
+        {
+            /* Got through and set all uv, recip z, screen z, etc */
+            for (i = m_lineVertBufferSize*2; i < numLines*2; i++ )
+            {
+				RwReal	near_z = RwIm2DGetNearScreenZ();
+				RwReal	recip_z = 1.0f/near_z;
+                RWIM2DVERTEXSetScreenZ( &newLineVertBuffer[i], near_z );
+                RWIM2DVERTEXSetRecipCameraZ( &newLineVertBuffer[i], recip_z );
+                RWIM2DVERTEXSetU(&newLineVertBuffer[i], (RwReal)(0.0f), (RwReal)(1.0f));
+                RWIM2DVERTEXSetV(&newLineVertBuffer[i], (RwReal)(0.0f), (RwReal)(1.0f));
+                RWIM2DVERTEXSetIntRGBA(&newLineVertBuffer[i], m_color.red, m_color.green,
+                                      m_color.blue, m_color.alpha);
+            }
+			m_lineVertBuffer = newLineVertBuffer;
+            m_lineVertBufferSize = numLines;
+        }
+    }
+
+    /* Will it fit now */
+    if ( numLines <= m_lineVertBufferSize )
+    {
+        RWIM2DVERTEX*	curLineVerts = m_lineVertBuffer;
+
+        i = 0;
+        while (string[i])
+        {
+            uint32 	numCharLines = s_char_length[(uint32)string[i]];
+            CharLine*	curLine 	 = s_char_maps[(uint32)string[i]];
+
+            while (numCharLines--)
+            {
+                RwLine line;
+
+                line.start.x = localPos.x + ((RwReal)curLine->startX * m_size.x);
+                line.start.y = localPos.y + ((RwReal)(128-curLine->startY) * m_size.y);
+                line.end.x = localPos.x + ((RwReal)curLine->endX * m_size.x);
+                line.end.y = localPos.y + ((RwReal)(128-curLine->endY) * m_size.y);
+
+                RWIM2DVERTEXSetScreenX(&curLineVerts[0], line.start.x);
+                RWIM2DVERTEXSetScreenY(&curLineVerts[0], line.start.y);
+                RWIM2DVERTEXSetScreenX(&curLineVerts[1], line.end.x);
+                RWIM2DVERTEXSetScreenY(&curLineVerts[1], line.end.y);
+
+                curLineVerts += 2;
+                curLine++;
+            }
+
+            localPos.x += m_size.x * (RwReal)(192.0f);
+            i++;
+        }
+
+        /* We don't need to corrupt too much render state here */
+        RwRenderStateSet(rwRENDERSTATETEXTURERASTER, NULL);
+
+        RwIm2DRenderPrimitive(rwPRIMTYPELINELIST, m_lineVertBuffer, numLines*2);
+    }
+#endif		// __PLAT_NGC__
+#endif
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+VecFont::~VecFont( void )
+{
+	
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Gfx
+
+
+#endif
diff --git a/Code/Gfx/vecfont.h b/Code/Gfx/vecfont.h
new file mode 100644
index 0000000..e77b6d0
--- /dev/null
+++ b/Code/Gfx/vecfont.h
@@ -0,0 +1,111 @@
+#if 0
+
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		GFX (Graphics Library)									**
+**																			**
+**	Module:			Graphics  (GFX)											**
+**																			**
+**	File name:		gfx/vecfont.h											**
+**																			**
+**	Created: 		11/11/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __GFX_VECFONT_H
+#define __GFX_VECFONT_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Gfx
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  VecFont  : public Spt::Class
+{
+	
+
+public:
+						VecFont( const Image::RGBA color, float w, float h);
+						~VecFont( void );		 
+
+	void				Print( float x, float y, const char* string );
+
+private:
+
+
+	struct CharLine
+	{
+		uint8			startX;
+		uint8			startY;
+		uint8			endX;
+		uint8			endY;
+	};
+
+	void				assign_line_list( uint32 target, CharLine* lineList );
+
+	float				m_size_x,m_size_y;
+	Image::RGBA			m_color;
+	
+//	RWIM2DVERTEX*		m_lineVertBuffer;
+//	RwUInt32			m_lineVertBufferSize;
+
+	static uint32		s_char_length[256];
+	static CharLine*	s_char_maps[256];
+	static CharLine		s_symbol_maps1[15][16];
+	static CharLine		s_symbol_maps2[7][16];
+	static CharLine		s_digit_maps[10][16];
+	static CharLine		s_alpha_maps[26][16];
+};
+
+
+/*****************************s************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Gfx
+
+#endif // __GFX_VECFONT_H
+
+#endif
+
diff --git a/Code/Sk/.DS_Store b/Code/Sk/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..ee0e10b8f7882abb046e34c56a409149567cdd45
GIT binary patch
literal 6148
zcmeHKF=_)r43uIQ4ryGb+%Mz@i?Lte4;VXO3?U97>96v;{ItwSBE(!$88=}B((GzA
zx7-w`lbQMA^ZChaZe}w$(M}9w<2ij~ca?D<9M5>O4?Eba?GL+A_3sIC?`>^6dk6Ce
ze}CM*9Je^-)zfl-ObSQ=DIf);fE2h&0rp>*K63P=Gd@T~y94-HQ2g+pR|IuK$6
z0P+Vt4C|OBfQ<=YFB}q)fq7DaN%a~rJn6`{s_TVAV$#jyW;~~E_L@*UZb!aFxp_}i
zlmb%VQh`Zs=j{J0_#gBCB}qFeAO-%F0zO@?mkYd7_14kLvDY^E2F{u@oQ8E!5TYFe
jqa9
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+//#define DEBUG_COLLISION
+
+namespace Obj
+{
+
+
+static bool sHitKillPoly=false;
+// If a poly's node's trigger script contains any of these then it is considered a Kill poly
+static uint32 spKillScripts[]=
+{
+	CRCC(0xdea3057b,"SK3_KillSkater"),
+	CRCC(0xb1058546,"SK3_KillSkater_Water"),
+	CRCC(0xb38ed6b,"SK3_KillSkater_Finish"),
+	CRCC(0x1310920e,"SK3_KillSkater_Pungee"),
+	CRCC(0x979a99ab,"NY_KillWater"),
+	CRCD(0x7aae1282,"RU_KillSkater"),
+	CRCD(0xcb1b1d9e,"RU_KillSkater2"),
+	CRCD(0x84e5c703,"NY_LeavingMessage"),
+	CRCD(0xf66fdf6e,"SJ_Televator"),
+	CRCD(0xb21175da,"HI_KillSkater_LittleDogs"),
+	CRCD(0xd9d60165,"VC_CAG_boundary"),
+};
+
+static void s_check_if_kill_poly ( CFeeler* p_feeler )
+{
+	if (!p_feeler->GetTrigger() || !p_feeler->GetNodeChecksum())
+	{
+		#ifdef DEBUG_COLLISION
+		//printf("Not got trigger or not got node checksum\n");
+		#endif
+		return;
+	}	
+	
+	int node = SkateScript::FindNamedNode(p_feeler->GetNodeChecksum(), false);// false means don't assert
+	if (node < 0)
+	{
+		// Got a trigger, but no node, so assume it is a kill poly. This case arises in the park editor
+		// where a kill piece such as water or lava has been placed but the node array has not been generated yet.
+		//sHitKillPoly=true;
+		// Note: Commented out, to fix TT8832, where the cursor could get stuck on cars.
+		// The PE now allows the cursor to go anywhere, even onto kill polys.
+		return;
+	}
+		
+	Script::CArray *p_node_array = Script::GetArray(CRCD(0xc472ecc5, "NodeArray"));
+	Script::CStruct *p_node = p_node_array->GetStructure(node);
+	
+	uint32 trigger_script=0;
+	p_node->GetChecksum(CRCD(0x2ca8a299,"TriggerScript"),&trigger_script);
+	
+	if (trigger_script)
+	{
+		//printf("trigger_script=%s\n",Script::FindChecksumName(trigger_script));
+		if (Script::ScriptContainsAnyOfTheNames(trigger_script,spKillScripts,sizeof(spKillScripts)/sizeof(uint32)))
+		{
+			sHitKillPoly=true;
+		}	
+	}	
+}
+
+// Checks whether the vector posA -> posB goes through any Kill polys.
+// Does not check collidable polys.
+static bool s_goes_through_kill_poly(const Mth::Vector &posA, const Mth::Vector &posB)
+{
+	sHitKillPoly=false;
+	
+	CFeeler feeler;
+	
+	feeler.m_start = posA;
+	feeler.m_end = posB;
+
+	feeler.SetIgnore(0, mFD_NON_COLLIDABLE | mFD_TRIGGER);
+	feeler.SetCallback(s_check_if_kill_poly);
+	//feeler.DebugLine(0,255,0);
+	feeler.GetCollision();
+	
+	return sHitKillPoly;
+}
+
+bool CEditorCameraComponent::find_y(Mth::Vector start, Mth::Vector end, float *p_y)
+{
+	#ifdef DEBUG_COLLISION
+	//printf("s_find_y: ");
+	#endif
+	
+	CFeeler feeler;
+	feeler.SetStart(start);
+	feeler.SetEnd(end);
+	// Ignore invisible polys, otherwise one can get stuck on top of the hotel in Hawaii,
+	// because it is surrounded by horizontal invisible polys.
+	//feeler.SetIgnore(mFD_INVISIBLE, 0);	
+	
+	if (feeler.GetCollision())
+	{
+		if (m_simple_collision)
+		{
+			#ifdef DEBUG_COLLISION
+			printf("Simple drop down\n");
+			#endif
+			
+			sHitKillPoly=false;
+			Mth::Vector point=feeler.GetPoint();
+			*p_y=point[Y];
+			return true;
+		}	
+	
+		sHitKillPoly=false;
+		s_check_if_kill_poly(&feeler);
+		if (sHitKillPoly)
+		{
+			// Can't go onto collidable Kill polys
+			#ifdef DEBUG_COLLISION
+			printf("Kill poly!\n");
+			#endif
+			return false;
+		}
+				
+		// Check the steepness
+		Mth::Vector normal=feeler.GetNormal();
+		// .087=max steepness of 85 degrees
+		if (normal[Y] < 0.087f/*sinf(Mth::DegToRad(90.0f-Script::GetFloat(CRCD(0x87b926b4,"MaxSteepness"))))*/)
+		{
+			// The ground is too steep!
+			// Need this check otherwise the skater can be placed halfway down the almost vertical
+			// riverbank in NJ
+			#ifdef DEBUG_COLLISION
+			printf("Too steep!\n");
+			#endif
+			return false;
+		}
+		
+		#ifdef DEBUG_COLLISION
+		//printf("Found y=%f normal[Y]=%f\n",feeler.GetPoint()[Y],normal[Y]);
+		#endif
+		
+		Mth::Vector point=feeler.GetPoint();
+		*p_y=point[Y];
+
+		// But, before returning true, check whether there are any kill polys between the start and the
+		// collision point.
+		if (s_goes_through_kill_poly(start, point))
+		{
+			#ifdef DEBUG_COLLISION
+			printf("Kill poly above found collision!\n");
+			#endif
+			return false;
+		}
+				
+		return true;
+	}
+	
+	#ifdef DEBUG_COLLISION
+	printf("No collision\n");
+	#endif
+	
+	return false;
+}
+
+bool CEditorCameraComponent::find_collision_at_offset(Mth::Vector oldCursorPos, float upOffset)
+{
+	Mth::Vector start=m_cursor_pos;
+	start[Y]+=upOffset;
+	Mth::Vector end=m_cursor_pos;
+	end[Y]+=Script::GetFloat(CRCD(0xf230d521,"EditorCam_CursorCollisionDownOffset"));
+	
+	bool new_position_is_ok=false;
+	
+	float y;
+	if (find_y(start,end,&y))
+	{
+		new_position_is_ok=true;
+	}
+	
+	if (new_position_is_ok)
+	{
+		Mth::Vector from = oldCursorPos;
+		Mth::Vector to = m_cursor_pos;
+		to[Y]=y;
+		to[Y]+=72.0f; // 6 feet above the ground
+		// Make it really be horizontal to avoid steep collision check weirdness
+		from[Y]=to[Y]; 
+		
+		if (!m_simple_collision)
+		{
+			// Do a horizontal collision check to see if the cursor 
+			// is going to move through a kill poly
+			if (s_goes_through_kill_poly(from, to))
+			{
+				#ifdef DEBUG_COLLISION
+				printf("Goes through kill poly\n");
+				#endif
+				new_position_is_ok=false;
+			}
+		}
+		
+		if (!m_allow_movement_through_walls)
+		{
+			// Also don't allow moving through walls
+			CFeeler feeler;
+			feeler.SetIgnore(mFD_NON_COLLIDABLE, 0);
+			feeler.SetStart(from);
+			feeler.SetEnd(to);
+			if (feeler.GetCollision())
+			{
+				#ifdef DEBUG_COLLISION
+				printf("oof\n");
+				#endif
+				new_position_is_ok=false;
+			}
+		}	
+	}
+
+	if (new_position_is_ok)
+	{
+		m_cursor_pos[Y]=y;
+		return true;
+	}
+	return false;	
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CEditorCameraComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CEditorCameraComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CEditorCameraComponent::CEditorCameraComponent() : CBaseComponent()
+{
+	SetType( CRC_EDITORCAMERA );
+	
+	m_camera_focus_pos.Set();
+	m_cam_pos.Set();
+	m_cursor_pos.Set();
+	m_cursor_u.Set();
+	m_cursor_v.Set();
+	
+	m_radius=0.0f;
+	m_radius_min=0.0f;
+	m_radius_max=0.0f;
+	
+	m_angle=0.0f;
+	m_tilt_angle=0.0f;
+	m_tilt_angle_min=0.0f;
+	m_tilt_angle_max=0.0f;
+	
+	m_cursor_height=0.0f;
+	m_min_height=0.0f;
+	
+	mp_shadow=NULL;
+	mp_input_component=NULL;
+	
+	m_simple_collision=false;
+	m_allow_movement_through_walls=false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CEditorCameraComponent::~CEditorCameraComponent()
+{
+	delete_shadow();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CEditorCameraComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+
+	m_min_height=0.0f;
+	pParams->GetFloat(CRCD(0x9cdf40cd,"min_height"),&m_min_height);
+	
+	m_radius_min=100.0f;
+	pParams->GetFloat(CRCD(0x52eecb98,"min_radius"),&m_radius_min);
+	
+	m_radius_max=2000.0f;
+	pParams->GetFloat(CRCD(0x53e2512c,"max_radius"),&m_radius_max);
+	
+	m_simple_collision=pParams->ContainsFlag(CRCD(0xa0d7fbab,"SimpleCollision"));
+	m_allow_movement_through_walls=pParams->ContainsFlag(CRCD(0xf5680def,"AllowMovementThroughWalls"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CEditorCameraComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	InitFromStructure(pParams);
+}
+
+void CEditorCameraComponent::Hide( bool shouldHide )
+{
+	if (shouldHide)
+	{
+		delete_shadow();
+	}	
+	else
+	{
+		create_shadow();
+	}
+}
+
+void CEditorCameraComponent::GetCursorOrientation(Mth::Vector *p_u, Mth::Vector *p_v)
+{
+	*p_u=m_cursor_u;
+	*p_v=m_cursor_v;
+}
+
+void CEditorCameraComponent::create_shadow()
+{
+	if (!mp_shadow)
+	{
+		mp_shadow = new Gfx::CSimpleShadow;
+		mp_shadow->SetScale( SHADOW_SCALE );
+		mp_shadow->SetModel( "Ped_Shadow" );
+	}	
+}
+
+void CEditorCameraComponent::delete_shadow()
+{
+	if (mp_shadow)
+	{
+		delete mp_shadow;
+		mp_shadow=NULL;
+	}	
+}
+
+void CEditorCameraComponent::update_shadow()
+{
+	Mth::Vector cursor_pos=m_cursor_pos;		
+	cursor_pos[Y]+=m_cursor_height;
+
+	CFeeler feeler;
+	feeler.SetStart(cursor_pos);
+	feeler.SetEnd(cursor_pos + Mth::Vector(0.0f,SHADOW_COLLISION_CHECK_DISTANCE,0.0f,0.0f));
+	if (feeler.GetCollision())
+	{
+		create_shadow();
+		
+		Mth::Matrix mat;
+		mat.Ident();
+
+		Mth::Vector shadow_pos=feeler.GetPoint();
+		Mth::Vector shadow_normal=feeler.GetNormal();
+		mp_shadow->UpdatePosition(shadow_pos,
+										 mat,
+										 shadow_normal);
+	}
+	else
+	{
+		delete_shadow();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CEditorCameraComponent::Finalize()
+{
+	// Get the pointers to the other required components.
+	Dbg_MsgAssert(mp_input_component==NULL,("mp_input_component not NULL ?"));
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	Dbg_MsgAssert(mp_input_component,("CEditorCameraComponent requires parent object to have an input component!"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CEditorCameraComponent::Update()
+{
+	Dbg_MsgAssert(mp_input_component,("NULL mp_input_component"));
+	
+	// TODO: When in park editor, use different values here, cos they'll need to be
+	// tweaked according to the shell geometry.
+	m_tilt_angle_min=Script::GetFloat(CRCD(0x7d16d113,"EditorCam_TiltMin"));
+	m_tilt_angle_max=Script::GetFloat(CRCD(0x411bee4a,"EditorCam_TiltMax"));
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+
+	
+	// Update m_angle
+	float angle_vel=Script::GetFloat(CRCD(0x8242d878,"EditorCam_TurnSpeed"))*control_pad.m_scaled_rightX;
+	m_angle+=angle_vel;
+	while (m_angle > 2*3.141592654f)
+	{
+		m_angle -= 2*3.141592654f;
+	}	
+	while (m_angle < 0.0f)
+	{
+		m_angle += 2*3.141592654f;
+	}	
+	
+	// Update m_tilt_angle
+	angle_vel=-Script::GetFloat(CRCD(0x3cfa3a72,"EditorCam_TiltSpeed"))*control_pad.m_scaled_rightY;
+	m_tilt_angle+=angle_vel;
+	if (m_tilt_angle > m_tilt_angle_max)
+	{
+		m_tilt_angle = m_tilt_angle_max;
+	}	
+	if (m_tilt_angle < m_tilt_angle_min)
+	{
+		m_tilt_angle = m_tilt_angle_min;
+	}	
+	
+	// Update m_radius
+	bool zoom_in_button_pressed=false;
+	bool zoom_out_button_pressed=false;
+	bool raise_pressed=false;
+	bool lower_pressed=false;
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_XBOX:
+		{
+			uint32 input_mask = mp_input_component->GetInputMask();
+			if (input_mask & Inp::Data::mA_BLACK)
+			{
+				zoom_out_button_pressed=true;
+			}	
+			if (input_mask & Inp::Data::mA_WHITE)
+			{
+				zoom_in_button_pressed=true;
+			}	
+			// Note: See p_siodev.cpp for the low level code that does the weird XBox buttons mappings
+			if (Ed::CParkEditor::Instance()->EditingCustomPark())
+			{
+				// When editing a park, XBox L gives L2, XBox R gives L1
+				// We want XBox L to lower, XBox R to raise
+				if (input_mask & Inp::Data::mA_L1)
+				{
+					raise_pressed=true;
+				}	
+				if (input_mask & Inp::Data::mA_L2)
+				{
+					lower_pressed=true;
+				}	
+			}
+			else
+			{
+				// When not editing a park, XBox L gives L1, XBox R gives R1
+				// We want XBox L to lower, XBox R to raise
+				if (input_mask & Inp::Data::mA_R1)
+				{
+					raise_pressed=true;
+				}	
+				if (input_mask & Inp::Data::mA_L1)
+				{
+					lower_pressed=true;
+				}	
+			}
+			break;
+		}
+		case Config::HARDWARE_NGC:
+		{
+			uint32 input_mask = mp_input_component->GetInputMask();
+			if (input_mask & Inp::Data::mA_Z)
+			{
+				if (input_mask & Inp::Data::mA_R1)
+				{
+					zoom_out_button_pressed=true;
+				}
+				if (input_mask & Inp::Data::mA_L1)
+				{
+					zoom_in_button_pressed=true;
+				}
+			}
+			else
+			{
+				if (input_mask & Inp::Data::mA_R1)
+				{
+					lower_pressed=true;
+				}
+				if (input_mask & Inp::Data::mA_L1)
+				{
+					raise_pressed=true;
+				}
+			}
+			break;
+		}
+		default:	
+		{
+			zoom_out_button_pressed=control_pad.m_R1.GetPressed();
+			zoom_in_button_pressed=control_pad.m_R2.GetPressed();
+			raise_pressed=control_pad.m_L1.GetPressed();
+			lower_pressed=control_pad.m_L2.GetPressed();
+			break;		
+		}	
+	}
+	
+	float radius_vel=Script::GetFloat(CRCD(0x4e58f829,"EditorCam_InOutSpeed"));
+	if (zoom_out_button_pressed)
+	{
+	}	
+	else if (zoom_in_button_pressed)
+	{
+		radius_vel=-radius_vel;
+	}
+	else
+	{
+		radius_vel=0.0f;
+	}
+		
+	// Make the speed proportional to the current radius so that the camera can be zoomed
+	// out to the max nice and quick.
+	radius_vel*=m_radius;
+	
+	m_radius+=radius_vel;
+	if (m_radius < m_radius_min)
+	{
+		m_radius = m_radius_min;
+	}	
+	if (m_radius > m_radius_max)
+	{
+		m_radius = m_radius_max;
+	}	
+	float zoom_factor=(m_radius-m_radius_min)/(m_radius_max-m_radius_min);
+	// zoom_factor is between 0 and 1 and indicates how far out the camera is zoomed.
+	// 0 = closest to cursor, 1 = furthest away.
+
+
+	// Update the height
+	float height_vel_min=Script::GetFloat(CRCD(0x604961f4,"EditorCam_UpDownSpeedMin"));
+	float height_vel_max=Script::GetFloat(CRCD(0x5c445ead,"EditorCam_UpDownSpeedMax"));
+	float height_vel=height_vel_min + zoom_factor * (height_vel_max-height_vel_min);
+	
+	// If very close in allow very fine movements, needed for rail placement in rail editor.
+	// TODO: Fix this so that the change in speed is not so sudden, make it linear like above.
+	if (m_radius < 100)
+	{
+		height_vel=0.2f;
+	}
+	
+	if (raise_pressed)
+	{
+	}	
+	else if (lower_pressed)
+	{
+		height_vel=-height_vel;
+	}
+	else
+	{
+		height_vel=0.0f;
+	}	
+	
+	m_cursor_height+=height_vel;
+	float max_height=Script::GetFloat(CRCD(0x61d56fa0,"EditorCam_MaxHeight"));
+	if (m_cursor_height > max_height)
+	{
+		m_cursor_height=max_height;
+	}
+
+	// Switch off collision on any created-rail sectors whilst doing collision checks.
+	// This is because the rail sectors have a vertical collidable poly, which occasionally does cause
+	// collisions, making the cursor ping up into the air. Must be some sort of floating point innaccuracy
+	// (or too much accuracy), probably because the collision check vector is straight down the vertical poly.
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	if (p_skate_mod->m_cur_level == CRCD(0xe8b4b836,"load_sk5ed"))
+	{
+		Obj::GetRailEditor()->SetSectorActiveStatus(false);
+	}	
+
+	// Make sure that the cursor cannot be raised through a collidable poly, otherwise it
+	// can be raised through the stadium roof in slam city jam (TT3142)
+	CFeeler feeler;
+	feeler.SetStart(m_cursor_pos);
+	feeler.SetEnd(m_cursor_pos + Mth::Vector(0.0f, m_cursor_height+10.0f, 0.0f));
+	if (feeler.GetCollision())
+	{
+		m_cursor_height=feeler.GetPoint()[Y]-m_cursor_pos[Y]-10.0f;
+	}
+	
+	// This allows the height to be less than m_min_height, but as soon as it goes
+	// over m_min_height it won't be able to go below it again.
+	// This is for the rail editor, where the min height needs to be limited whilst
+	// still allowing snapping to a vertex.
+	if (height_vel < 0.0f && m_cursor_height-height_vel+0.01 >= m_min_height)
+	{
+		if (m_cursor_height < m_min_height)
+		{
+			m_cursor_height=m_min_height;
+		}
+	}	
+	if (m_cursor_height < 0.0f)
+	{
+		m_cursor_height=0.0f;
+	}
+		
+	// Update m_cursor_pos ...
+
+	// First move it horizontally according to the left analogue stick
+	m_cursor_u.Set(cosf(m_angle),0.0f,-sinf(m_angle));
+	m_cursor_v.Set(sinf(m_angle),0.0f,cosf(m_angle));
+	
+	float speed_min=Script::GetFloat(CRCD(0x83bde786,"EditorCam_MoveSpeedMin"));
+	float speed_max=Script::GetFloat(CRCD(0xbfb0d8df,"EditorCam_MoveSpeedMax"));
+	// Make the speed proportional to the zoom factor so that fine movements can be done
+	// when zoomed in, whilst fast movements can be made when zoomed out.
+	float speed = speed_min + zoom_factor * (speed_max-speed_min);
+	
+	// If very close in allow very fine movements, needed for rail placement in rail editor.
+	// TODO: Fix this so that the change in speed is not so sudden, make it linear like above.
+	if (m_radius < 100)
+	{
+		speed=1.0f;
+	}
+		
+	float horiz=control_pad.m_leftX / 128.0f;
+	float vert=control_pad.m_leftY / 128.0f;
+	if (fabs(horiz) < 0.01f)
+	{
+		if (control_pad.m_left.GetPressed())
+		{
+			horiz=-0.5f;
+		}	
+		if (control_pad.m_right.GetPressed())
+		{
+			horiz=0.5f;
+		}	
+	}
+		
+	if (fabs(vert) < 0.01f)
+	{
+		if (control_pad.m_up.GetPressed())
+		{
+			vert=-0.5f;
+		}	
+		if (control_pad.m_down.GetPressed())
+		{
+			vert=0.5f;
+		}	
+	}	
+		
+	Mth::Vector vel = m_cursor_u * horiz*speed+
+					  m_cursor_v * vert*speed;
+	Mth::Vector old_pos=m_cursor_pos;					  
+	m_cursor_pos+=vel;
+
+	// Now do a collision check to find the y.	
+	if (!find_collision_at_offset(old_pos, Script::GetFloat(CRCD(0xc7210318,"EditorCam_CursorCollisionFirstUpOffset"))))
+	{
+		// Do an upwards collision check to check for a ceiling above the skater, and do not
+		// do the check for higher collision if there is a ceiling. This is to fix a bug where in Slam City
+		// the goal cursor would pop up into the stands if hitting the teleport poly in the escalator D corridor.
+		CFeeler feeler;
+		feeler.SetStart(old_pos);
+		Mth::Vector high=old_pos;
+		high[Y]+=120.0f;
+		feeler.SetEnd(high);
+		if (feeler.GetCollision())
+		{
+			// Undo the movement so that the cursor does not move.
+			m_cursor_pos=old_pos;
+		}
+		else if (!find_collision_at_offset(old_pos, Script::GetFloat(CRCD(0x56c2bff2,"EditorCam_CursorCollisionSecondUpOffset"))))
+		{
+			// Undo the movement so that the cursor does not move.
+			m_cursor_pos=old_pos;
+		}
+		else
+		{
+			// Did find higher collision, which is going to cause the cursor to pop to a high place, such
+			// as the roof of a building. In this case, zero the cursor height, because it seems weird to
+			// have it maintain it's hover height in that case. (TT10272)
+			m_cursor_height=m_min_height;
+		}	
+	}
+		
+	if (p_skate_mod->m_cur_level == CRCD(0xe8b4b836,"load_sk5ed"))
+	{
+		Obj::GetRailEditor()->SetSectorActiveStatus(true);
+	}	
+
+	
+	// Calculate the camera position, which equals the cursor position but has a lag
+	// applied so that as the cursor follows the contours of the ground it does not make
+	// the camera glitch.
+	m_camera_focus_pos[X]=m_cursor_pos[X];
+	m_camera_focus_pos[Z]=m_cursor_pos[Z];
+	m_camera_focus_pos[Y]+=(m_cursor_pos[Y]+m_cursor_height-m_camera_focus_pos[Y])*Script::GetFloat(CRCD(0x725ad02c,"EditorCam_YCatchUpFactor"));
+	
+	Mth::Vector tilted_v;
+	tilted_v[X]=m_cursor_v[X]*cosf(m_tilt_angle);
+	tilted_v[Z]=m_cursor_v[Z]*cosf(m_tilt_angle);
+	tilted_v[Y]=sinf(m_tilt_angle);
+
+	Mth::Matrix mat;
+	mat[Z]=tilted_v;
+	mat[X]=m_cursor_u;
+	mat[Y]=Mth::CrossProduct(tilted_v,m_cursor_u);
+	mat[X][W]=0.0f;
+	mat[Y][W]=0.0f;
+	mat[Z][W]=0.0f;
+	mat[W].Set();
+	GetObject()->SetMatrix(mat);
+	
+	Mth::Vector start=m_camera_focus_pos;
+	/*
+	Mth::Vector end;
+	float radius=m_radius;
+	while (true)
+	{
+		end=start+tilted_v*radius;
+
+		if (radius > Script::GetFloat(CRCD(0xf612c474,"EditorCam_CursorCollisionEnableDistMax")))
+		{
+			printf("Not checksing for collision\n");
+			break;
+		}
+		
+		Mth::Vector p_rays[4];
+		p_rays[0]=start;
+		p_rays[1]=start-120*u+Mth::Vector(0.0f,70.0f,0.0f,0.0f);
+		p_rays[2]=start+Mth::Vector(0.0f,70.0f,0.0f,0.0f);
+		p_rays[3]=start+120*u+Mth::Vector(0.0f,70.0f,0.0f,0.0f);
+		if (cursor_occluded(p_rays,4,end))
+		{
+			radius=radius*0.9f; // TODO: Hmmm, should find out the exact distance ...
+		}	
+		else
+		{
+			break;
+		}	
+	}
+
+	m_cam_pos+=(end-m_cam_pos)*Script::GetFloat(CRCD(0xf151a64,"EditorCam_CameraCatchUpFactor"));
+	*/
+	// TODO: Fix the camera collision above, then comment out this line for it to have effect.
+	m_cam_pos=start + tilted_v*m_radius;
+
+	m_cam_pos[W]=1.0f;
+	GetObject()->SetPos(m_cam_pos);
+	
+	
+	update_shadow();
+}
+
+bool CEditorCameraComponent::cursor_occluded(Mth::Vector *p_ray_starts, int numRays, Mth::Vector end)
+{
+	CFeeler feeler;
+	for (int i=0; iGetVector(CRCD(0xb9d31b0a,"position"),&m_cursor_pos);
+			m_camera_focus_pos=m_cursor_pos;
+			m_radius=700.0f;
+			pParams->GetFloat(CRCD(0xc48391a5,"radius"),&m_radius);
+			m_tilt_angle=0.6f;
+			pParams->GetFloat(CRCD(0xe3c07609,"tilt"),&m_tilt_angle);
+			m_cursor_height=0.0f;
+			pParams->GetFloat(CRCD(0xb8d629ca,"cursor_height"),&m_cursor_height);
+			break;
+
+		case 0x69cb3ae1: // EditorCam_SetCursorPos
+		{
+			Mth::Vector pos;
+			pParams->GetVector(CRCD(0xb9d31b0a,"position"),&pos,Script::ASSERT);
+			m_cursor_pos=pos;
+			m_cursor_height=0.0f;
+			
+			// Call update to do the ground collision check.
+			Update();
+			// That might have changed m_cursor_pos[Y], so adjust the height so that the cursor does 
+			// appear at the required position.
+			m_cursor_height=pos[Y]-m_cursor_pos[Y];
+			// Note: If the above call to Update() followed by adjusting the height were not done, then it would not
+			// be possible to snap to rail vertices, because rails are uncollidable, so on the next frame the cursor
+			// would fall through the rail back to the ground.
+			break;
+		}
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CEditorCameraComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CEditorCameraComponent::GetDebugInfo"));
+	
+	p_info->AddInteger("m_active",m_active);
+	p_info->AddVector("m_camera_focus_pos",m_camera_focus_pos.GetX(),m_camera_focus_pos.GetY(),m_camera_focus_pos.GetZ());
+	p_info->AddVector("m_cursor_pos",m_cursor_pos.GetX(),m_cursor_pos.GetY(),m_cursor_pos.GetZ());
+	p_info->AddFloat("m_cursor_height",m_cursor_height);
+	p_info->AddFloat("m_radius",m_radius);
+	p_info->AddFloat("m_radius_min",m_radius_min);
+	p_info->AddFloat("m_radius_max",m_radius_max);
+	p_info->AddFloat("m_angle",m_angle);
+	p_info->AddFloat("m_tilt_angle",m_tilt_angle);
+	p_info->AddFloat("m_tilt_angle_min",m_tilt_angle_min);
+	p_info->AddFloat("m_tilt_angle_max",m_tilt_angle_max);
+
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Sk/Components/EditorCameraComponent.h b/Code/Sk/Components/EditorCameraComponent.h
new file mode 100644
index 0000000..3e73759
--- /dev/null
+++ b/Code/Sk/Components/EditorCameraComponent.h
@@ -0,0 +1,121 @@
+//****************************************************************************
+//* MODULE:         Sk/Components/
+//* FILENAME:       EditorCameraComponent.h
+//* OWNER:          KSH
+//* CREATION DATE:  26 Mar 2003
+//****************************************************************************
+
+#ifndef __COMPONENTS_EDITORCAMERACOMPONENT_H__
+#define __COMPONENTS_EDITORCAMERACOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_EDITORCAMERA CRCD(0xac8946e2,"EditorCamera")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetEditorCameraComponent() ((Obj::CEditorCameraComponent*)GetComponent(CRC_EDITORCAMERA))
+#define		GetEditorCameraComponentFromObject(pObj) ((Obj::CEditorCameraComponent*)(pObj)->GetComponent(CRC_EDITORCAMERA))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Nx
+{
+	class CModel;
+}
+
+namespace Gfx
+{
+	class CSimpleShadow;
+}
+	
+namespace Obj
+{
+class CInputComponent;
+
+class CEditorCameraComponent : public CBaseComponent
+{
+	bool m_active;
+	
+	// The point at which the camera looks. This basically equals m_cursor_pos
+	// except it smoothly catches up with m_cursor_pos so as to smooth out glitches.
+	Mth::Vector m_camera_focus_pos;
+	Mth::Vector m_cam_pos;
+	
+	// The position of the Goal model that get moves around by the controls.
+	Mth::Vector m_cursor_pos;
+	
+	// The orthogonal horizontal vectors that define the cursor's orientation					 
+	Mth::Vector m_cursor_u;
+	Mth::Vector m_cursor_v;
+	
+	float m_radius;
+	float m_radius_min;
+	float m_radius_max;
+	
+	float m_angle;
+	float m_tilt_angle;
+	float m_tilt_angle_min;
+	float m_tilt_angle_max;
+
+	float m_cursor_height;
+	float m_min_height;
+
+	bool m_simple_collision;
+	bool m_allow_movement_through_walls;
+		
+	CInputComponent *mp_input_component;
+	
+	Gfx::CSimpleShadow *mp_shadow;
+	void create_shadow();
+	void delete_shadow();
+	void update_shadow();
+	#define SHADOW_COLLISION_CHECK_DISTANCE -10000.0f
+	#define SHADOW_SCALE 1.2f
+	
+	bool cursor_occluded(Mth::Vector *p_ray_starts, int numRays, Mth::Vector end);
+
+	bool find_y(Mth::Vector start, Mth::Vector end, float *p_y);
+	bool find_collision_at_offset(Mth::Vector oldCursorPos, float upOffset);
+	
+
+public:
+    CEditorCameraComponent();
+    virtual ~CEditorCameraComponent();
+
+public:
+	Mth::Vector&					GetCursorPos() {return m_cursor_pos;}
+	void							SetCursorPos(const Mth::Vector& pos) {m_cursor_pos=pos;}
+	void							GetCursorOrientation(Mth::Vector *p_u, Mth::Vector *p_v);
+	float							GetCursorOrientation() {return m_angle;}
+	void							SetCursorOrientation(float angle) {m_angle=angle;}
+	
+	void							SetCursorHeight(float height) {m_cursor_height=height;}
+	float							GetCursorHeight() {return m_cursor_height;}
+	void							SetCursorMinHeight(float minHeight) {m_min_height=minHeight;}
+	
+	void							SetSimpleCollision(bool whatever) {m_simple_collision=whatever;}
+	
+	virtual	void 					Finalize();
+	virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Hide( bool shouldHide );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+};
+
+}
+
+#endif // __COMPONENTS_EDITORCAMERACOMPONENT_H__
diff --git a/Code/Sk/Components/GoalEditorComponent.cpp b/Code/Sk/Components/GoalEditorComponent.cpp
new file mode 100644
index 0000000..7dcef95
--- /dev/null
+++ b/Code/Sk/Components/GoalEditorComponent.cpp
@@ -0,0 +1,3040 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       GoalEditorComponent.cpp
+//* OWNER:          Kendall Harrison
+//* CREATION DATE:  3/21/2003
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+//#define SIZE_TEST
+
+namespace Obj
+{
+
+static uint32 s_convert_level(uint32 level)
+{
+	if (level==CRCD(0xb664035d,"Load_Sk5Ed_gameplay"))
+	{
+		// Map Load_Sk5Ed_gameplay back to Load_Sk5Ed 
+		level=CRCD(0xe8b4b836,"Load_Sk5Ed");
+	}
+	
+	return level;
+}
+
+// Often in the scripts the value returned by GetCurrentLevel is passed on to commands like GetEditedGoalsInfo
+// However sometimes the level might be returned as Load_Sk5Ed_gameplay rather than Load_Sk5Ed, so
+// this will map it back to Load_Sk5Ed
+static uint32 s_get_level_checksum(Script::CStruct *p_params)
+{
+	uint32 level=0;
+	p_params->GetChecksum(CRCD(0x651533ec,"level"),&level);
+	
+	level=s_convert_level(level);
+			
+	return level;
+}
+
+static uint32 s_get_current_level()
+{
+	Mdl::Skate *p_skate_module = Mdl::Skate::Instance();
+	uint32 current_level=p_skate_module->m_cur_level;
+	
+	current_level=s_convert_level(current_level);						 
+	
+	return current_level;
+}	
+	
+static const char *s_generate_letter_model_filename(uint32 goalType, int letterIndex)
+{
+	const char *p_model_file_name=NULL;
+	switch (goalType)
+	{
+		case 0x54166acd: // Skate
+			switch (letterIndex)
+			{
+				case 0:
+					p_model_file_name="gameobjects\\skate\\letter_s\\letter_s.mdl";
+					break;
+				case 1:
+					p_model_file_name="gameobjects\\skate\\letter_k\\letter_k.mdl";
+					break;
+				case 2:
+					p_model_file_name="gameobjects\\skate\\letter_a\\letter_a.mdl";
+					break;
+				case 3:
+					p_model_file_name="gameobjects\\skate\\letter_t\\letter_t.mdl";
+					break;
+				case 4:
+					p_model_file_name="gameobjects\\skate\\letter_e\\letter_e.mdl";
+					break;
+				default:
+					break;
+			}		
+			break;
+		case 0x4ec3cfb5: // Combo
+			switch (letterIndex)
+			{
+				case 0:
+					p_model_file_name="gameobjects\\combo\\goal_combo_c\\goal_combo_c.mdl";
+					break;
+				case 1:
+					p_model_file_name="gameobjects\\combo\\goal_combo_o\\goal_combo_o.mdl";
+					break;
+				case 2:
+					p_model_file_name="gameobjects\\combo\\goal_combo_m\\goal_combo_m.mdl";
+					break;
+				case 3:
+					p_model_file_name="gameobjects\\combo\\goal_combo_b\\goal_combo_b.mdl";
+					break;
+				case 4:
+					p_model_file_name="gameobjects\\combo\\goal_combo_o\\goal_combo_o.mdl";
+					break;
+				default:
+					break;
+			}		
+			break;
+		default:
+			break;
+	}				
+	return p_model_file_name;
+}
+
+static uint32 s_generate_letter_node_type_checksum(uint32 goalType, int letterIndex)
+{
+	uint32 checksum=0;
+	switch (goalType)
+	{
+		case 0x54166acd: // Skate
+			switch (letterIndex)
+			{
+				case 0:
+					checksum=CRCD(0xf75425ea,"Letter_S");
+					break;
+				case 1:
+					checksum=CRCD(0xe438bdbc,"Letter_K");
+					break;
+				case 2:
+					checksum=CRCD(0x4ed54a2,"Letter_A");
+					break;
+				case 3:
+					checksum=CRCD(0x6930b049,"Letter_T");
+					break;
+				case 4:
+					checksum=CRCD(0x38090bb,"Letter_E");
+					break;
+				default:
+					break;
+			}		
+			break;
+		case 0x4ec3cfb5: // Combo
+			switch (letterIndex)
+			{
+				case 0:
+					checksum=CRCD(0x7bf1ba66,"Combo_C");
+					break;
+				case 1:
+					checksum=CRCD(0x7247f64d,"Combo_O");
+					break;
+				case 2:
+					checksum=CRCD(0x9c499761,"Combo_M");
+					break;
+				case 3:
+					checksum=CRCD(0xcf68af0,"Combo_B");
+					break;
+				case 4:
+					checksum=CRCD(0x7247f64d,"Combo_O");
+					break;
+				default:
+					break;
+			}		
+			break;
+		default:
+			break;
+	}				
+	return checksum;
+}
+
+static uint32 s_generate_node_name_checksum(int goalIndex, int positionIndex)
+{
+	char p_name[100];
+	p_name[0]=0;
+	
+	switch (positionIndex)
+	{
+		case 0:
+			sprintf(p_name,"TRG_EditedGoal%d_Pro",goalIndex);
+			break;
+		case 1:
+			sprintf(p_name,"TRG_EditedGoal%d_Restart",goalIndex);
+			break;
+		default:
+			sprintf(p_name,"TRG_EditedGoal%d_Letter_%d",goalIndex,positionIndex-2);
+			break;	
+	}
+	
+	return Script::GenerateCRC(p_name);
+}
+
+static const Script::CStruct *s_get_default_goal_params(uint32 goalType)
+{
+	Script::CStruct *p_default_params=NULL;
+	
+	switch (goalType)
+	{
+		case 0x54166acd: // Skate
+			p_default_params=Script::GetStructure(CRCD(0x3328976a,"Goal_SkateLetters_genericParams"));
+			break;
+		case 0x4ec3cfb5: // Combo
+			p_default_params=Script::GetStructure(CRCD(0xb4f5801d,"Goal_amateurCOMBOline_genericParams"));
+			break;
+		case 0x6fe44c6d: // HighScore
+			p_default_params=Script::GetStructure(CRCD(0xef8f345f,"Goal_HighScore_genericParams"));
+			break;
+		case 0xec414b76: // HighCombo
+			p_default_params=Script::GetStructure(CRCD(0xb520a510,"goal_highcombo_genericParams"));
+			break;
+		case 0x7fe2238a: // SkateTris
+		case 0x0fc6f698: // ComboSkateTris
+		case 0xea7ba666: // TrickTris
+			p_default_params=Script::GetStructure(CRCD(0x675816b5,"goal_tetris_genericParams"));
+			break;
+		case 0x61c5d092: // Gap
+			p_default_params=Script::GetStructure(CRCD(0x5756a675,"Goal_Gaps_genericParams"));	
+			break;
+		default:
+			break;
+	}			
+	return p_default_params;
+}
+
+static const Script::CStruct *s_get_extra_goal_params(uint32 goalType)
+{
+	Script::CStruct *p_extra_params=NULL;
+	
+	switch (goalType)
+	{
+		case 0x54166acd: // Skate
+			p_extra_params=Script::GetStructure(CRCD(0xe41e0e04,"EditedGoal_ExtraParams_Skate"));
+			break;
+		case 0x4ec3cfb5: // Combo
+			p_extra_params=Script::GetStructure(CRCD(0xfecbab7c,"EditedGoal_ExtraParams_Combo"));
+			break;
+		case 0x6fe44c6d: // HighScore
+			p_extra_params=Script::GetStructure(CRCD(0x28c7b799,"EditedGoal_ExtraParams_HighScore"));
+			break;
+		case 0xec414b76: // HighCombo
+			p_extra_params=Script::GetStructure(CRCD(0xab62b082,"EditedGoal_ExtraParams_HighCombo"));
+			break;
+		case 0x7fe2238a: // SkateTris
+			p_extra_params=Script::GetStructure(CRCD(0x38c1d87e,"EditedGoal_ExtraParams_SkateTris"));
+			break;
+		case 0x0fc6f698: // ComboSkateTris
+			p_extra_params=Script::GetStructure(CRCD(0x6626b0d8,"EditedGoal_ExtraParams_ComboSkateTris"));
+			break;
+		case 0xea7ba666: // TrickTris	
+			p_extra_params=Script::GetStructure(CRCD(0xad585d92,"EditedGoal_ExtraParams_TrickTris"));
+			break;
+		case 0x61c5d092: // Gap
+			p_extra_params=Script::GetStructure(CRCD(0x9005703a,"EditedGoal_ExtraParams_Gap"));
+			break;
+		default:
+			break;
+	}
+	return p_extra_params;
+}
+			
+void InsertGoalEditorNodes()
+{
+	Script::CArray *p_node_array=GetArray(CRCD(0xc472ecc5,"NodeArray"),Script::ASSERT);
+	
+	uint32 new_node_index=0;
+	if (p_node_array->GetSize()==0)
+	{
+		// If it's empty, it'll need its type setting too.
+		p_node_array->SetSizeAndType(MAX_POSITIONS_PER_GOAL*MAX_GOALS_PER_LEVEL,ESYMBOLTYPE_STRUCTURE);
+	}
+	else
+	{
+		new_node_index=p_node_array->GetSize();
+		p_node_array->Resize(p_node_array->GetSize() + MAX_POSITIONS_PER_GOAL*MAX_GOALS_PER_LEVEL);
+	}	
+	
+	Script::CStruct *p_new_node=NULL;
+	for (int g=0; gAppendStructure(Script::GetStructure(CRCD(0x8f0f67d7,"EditedGoal_Pro_Node"),Script::ASSERT));
+		p_new_node->AddChecksum(CRCD(0xa1dc81f9,"Name"),s_generate_node_name_checksum(g,0));
+		p_new_node->AddInteger(CRCD(0xe50d6573,"NodeIndex"),new_node_index);
+		p_node_array->SetStructure(new_node_index++,p_new_node);
+		
+		p_new_node=new Script::CStruct;
+		p_new_node->AppendStructure(Script::GetStructure(CRCD(0xc9119673,"EditedGoal_Restart_Node"),Script::ASSERT));
+		p_new_node->AddChecksum(CRCD(0xa1dc81f9,"Name"),s_generate_node_name_checksum(g,1));
+		p_new_node->AddInteger(CRCD(0xe50d6573,"NodeIndex"),new_node_index);
+		p_node_array->SetStructure(new_node_index++,p_new_node);
+		
+		for (int i=0; iAppendStructure(Script::GetStructure(CRCD(0x1aaf51ba,"EditedGoal_Letter_Node"),Script::ASSERT));
+			
+			p_new_node->AddChecksum(CRCD(0xa1dc81f9,"Name"),s_generate_node_name_checksum(g,i+2));
+			p_new_node->AddInteger(CRCD(0xe50d6573,"NodeIndex"),new_node_index);
+			
+			p_node_array->SetStructure(new_node_index++,p_new_node);
+		}	
+	}	
+}
+
+CEditGoal::CEditGoal()
+{
+	Clear();
+}
+
+CEditGoal::~CEditGoal()
+{
+}
+
+void CEditGoal::Clear()
+{
+	m_used=false;
+	m_level_script=0;
+	mp_goal_name[0]=0;	
+	mp_goal_description[0]=0;
+	mp_win_message[0]=0;
+	mp_ped_name[0]=0;
+	m_goal_type=0;
+	
+	m_num_positions_set=0;
+	m_num_positions_required=0;
+	m_current_position_index=0;
+	m_placed_last_position=false;
+	m_has_won_goal=false;
+		
+	m_score=100;
+	m_time_limit=120;
+	
+	m_acceleration_interval=5;
+	m_acceleration_percent=0.1f;
+	m_trick_time=3000;
+	m_time_to_stop_adding_tricks=5;
+	m_max_tricks=11;
+	m_spin=360;
+	
+	m_single_combo=false;
+	m_combo_size=2;
+	
+	m_tricktris_block_size=3;
+	m_tricktris_total_to_win=8;
+
+	m_num_combo_sets=0;
+
+	m_control_type=CRCD(0x54166acd,"skate");
+	
+	m_num_gaps=0;
+	mp_trick_name[0]=0;
+				
+	for (int i=0; iAppendStructure(s_get_default_goal_params(m_goal_type));
+	p_params->AppendStructure(s_get_extra_goal_params(m_goal_type));
+	
+	// Now change the node names to be those of the special nodes created just for edited goals.
+    p_params->AddChecksum(CRCD(0x2d7e03d,"trigger_obj_id"),s_generate_node_name_checksum(m_level_goal_index,0));
+    p_params->AddChecksum(CRCD(0x3494f170,"restart_node"),s_generate_node_name_checksum(m_level_goal_index,1));
+	
+	// Then write in other misc params.
+	
+	switch (m_goal_type)
+	{
+		case 0x54166acd: // Skate
+		{
+			p_params->AddChecksum(CRCD(0xba54bd5c,"S_obj_id"),s_generate_node_name_checksum(m_level_goal_index,2));
+			p_params->AddChecksum(CRCD(0x150a97c2,"K_obj_id"),s_generate_node_name_checksum(m_level_goal_index,3));
+			p_params->AddChecksum(CRCD(0x84ca8b0a,"A_obj_id"),s_generate_node_name_checksum(m_level_goal_index,4));
+			p_params->AddChecksum(CRCD(0xb091b445,"T_obj_id"),s_generate_node_name_checksum(m_level_goal_index,5));
+			p_params->AddChecksum(CRCD(0x008085f0,"E_obj_id"),s_generate_node_name_checksum(m_level_goal_index,6));
+			break;
+		}
+			
+		case 0x4ec3cfb5: // Combo
+		{
+			Script::CArray *p_array=new Script::CArray;
+			p_array->SetSizeAndType(5,ESYMBOLTYPE_STRUCTURE);
+			
+			Script::CStruct *p_struct=new Script::CStruct;
+			p_struct->AddChecksum(CRCD(0x99f6ccbb,"obj_id"),s_generate_node_name_checksum(m_level_goal_index,2));
+			p_struct->AddString(CRCD(0xc4745838,"text"),"C");
+			p_array->SetStructure(0,p_struct);
+
+			p_struct=new Script::CStruct;
+			p_struct->AddChecksum(CRCD(0x99f6ccbb,"obj_id"),s_generate_node_name_checksum(m_level_goal_index,3));
+			p_struct->AddString(CRCD(0xc4745838,"text"),"O");
+			p_array->SetStructure(1,p_struct);
+
+			p_struct=new Script::CStruct;
+			p_struct->AddChecksum(CRCD(0x99f6ccbb,"obj_id"),s_generate_node_name_checksum(m_level_goal_index,4));
+			p_struct->AddString(CRCD(0xc4745838,"text"),"M");
+			p_array->SetStructure(2,p_struct);
+
+			p_struct=new Script::CStruct;
+			p_struct->AddChecksum(CRCD(0x99f6ccbb,"obj_id"),s_generate_node_name_checksum(m_level_goal_index,5));
+			p_struct->AddString(CRCD(0xc4745838,"text"),"B");
+			p_array->SetStructure(3,p_struct);
+
+			p_struct=new Script::CStruct;
+			p_struct->AddChecksum(CRCD(0x99f6ccbb,"obj_id"),s_generate_node_name_checksum(m_level_goal_index,6));
+			p_struct->AddString(CRCD(0xc4745838,"text"),"O");
+			p_array->SetStructure(4,p_struct);
+			
+			p_params->AddArrayPointer(CRCD(0xb3c7f1b2,"letter_info"),p_array);
+			break;
+		}	
+		
+		default:
+			break;
+	}
+
+	WriteGoalSpecificParams(p_params,WRITING_TO_GOAL_MANAGER);
+	
+	if (mp_goal_description[0])
+	{
+		p_params->AddString(CRCD(0xc5d7e6b,"goal_description"),mp_goal_description);
+	}
+
+	if (mp_win_message[0])
+	{
+		Script::CArray *p_default_success_cam_anims=Script::GetArray(CRCD(0x8070c110,"EditedGoal_success_cam_anims"),Script::ASSERT);
+		Dbg_MsgAssert(p_default_success_cam_anims->GetSize()==1,("Expected EditedGoal_success_cam_anims to contain just one structure"));
+		
+		Script::CArray *p_success_cam_anims=new Script::CArray;
+		Script::CopyArray(p_success_cam_anims, p_default_success_cam_anims);
+		p_success_cam_anims->GetStructure(0)->AddString(CRCD(0xa7ab3b6d,"cam_anim_text"), mp_win_message);
+		p_success_cam_anims->GetStructure(0)->AddChecksum(CRCD(0x531e4d28,"targetid"),s_generate_node_name_checksum(m_level_goal_index,0));
+			
+		p_params->AddArrayPointer(CRCD(0x27cd32e1,"success_cam_anims"), p_success_cam_anims);
+	}
+
+	p_params->AddString(CRCD(0xbfecc45b,"goal_name"),GetIDString());
+	
+	if (mp_goal_name[0])
+	{
+		p_params->AddString(CRCD(0x4bc5229d,"view_goals_text"),mp_goal_name);
+	}	
+		
+	if (mp_ped_name[0])
+	{
+		p_params->AddString(CRCD(0x243b9c3b,"full_name"),mp_ped_name);
+	}	
+	
+	p_params->AddInteger(CRCD(0x906b67ba,"time"),m_time_limit);
+	p_params->AddChecksum(CRCD(0x81cff663,"control_type"),m_control_type);		
+	
+	
+	// This is so that the goal manager can tell that this is an edited goal, and hence not save its
+	// details to the mem card.
+	p_params->AddChecksum(NONAME,CRCD(0x981d3ad0,"edited_goal"));
+}
+
+uint32 CEditGoal::generate_marker_object_name(int index)
+{
+	static char p_name[100];
+	sprintf(p_name,"GoalEditorMarkerObject%d",index);
+	return Script::GenerateCRC(p_name);
+}	
+
+uint32 CEditGoal::generate_goal_object_name(int index)
+{
+	static char p_name[100];
+	
+	if (index == 0)
+	{
+		sprintf(p_name,"TRG_EditedGoal%d_Pro",m_level_goal_index);
+	}
+	else if (index > 1)
+	{
+		sprintf(p_name,"TRG_EditedGoal%d_Letter_%d",m_level_goal_index,index-2);
+	}
+	else
+	{
+		// Position 1 is the restart, so no object exists.
+		return 0;
+	}	
+	
+	return Script::GenerateCRC(p_name);
+}	
+
+void CEditGoal::create_marker(int index)
+{
+	Dbg_MsgAssert(index=2,("Bad index"));
+			const char *p_letter_model_filename=s_generate_letter_model_filename(m_goal_type,index-2);
+			params.AddString("Model",p_letter_model_filename);
+			params.AddChecksum(NONAME,CRCD(0x3bd176d9,"RotateAndHover"));
+			break;
+		}
+		default:
+			break;
+	}
+					
+	params.AddChecksum("Name",generate_marker_object_name(index));
+
+	params.AddVector("Pos",mp_item_positions[index].mPos[X],
+						   mp_item_positions[index].mPos[Y]+mp_item_positions[index].mHeight,
+						   mp_item_positions[index].mPos[Z]);
+	params.AddFloat("Angle",mp_item_positions[index].mAngle * 180.0f/3.141592654f);
+	
+	Script::RunScript("goal_editor_create_marker_object",¶ms);
+}
+
+void CEditGoal::delete_marker(int index)
+{
+	Dbg_MsgAssert(indexm_cur_level == CRCD(0xe8b4b836,"load_sk5ed") )
+	{
+		// Don't allow the pos to be too close to the park boundary, otherwise the ped can be made to
+		// be embedded in the wall.
+		if (!Ed::IsWithinParkBoundaries(pos, 30.0f))
+		{
+			return false;
+		}
+	}
+			
+	Mth::Vector pos_with_height=pos;
+	pos_with_height[Y]+=height;
+	if (position_too_close_to_existing_positions(pos_with_height))
+	{
+		return false;
+	}
+	return true;
+}
+
+// Used by the GetEditedGoalsInfo script command for use when creating the menu of existing goals.
+// Also used by GetCurrentEditedGoalInfo command.
+// Also used for writing the goal info to the mem card.
+// And used by GetDebugInfo for sending stuff to the script debugger.
+void CEditGoal::WriteIntoStructure(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	
+	p_info->AddChecksum(NONAME,CRCD(0x981d3ad0,"edited_goal"));
+	
+	p_info->AddChecksum(CRCD(0x651533ec,"level"),m_level_script);
+	p_info->AddInteger(CRCD(0x60ce75ae,"level_goal_index"),m_level_goal_index);
+	p_info->AddChecksum(CRCD(0x9982e501,"goal_id"),GetId());
+	p_info->AddInteger(CRCD(0xc51bf6e3,"time_limit"),m_time_limit);
+	p_info->AddInteger(CRCD(0x14938611,"won_goal"),m_has_won_goal);
+	p_info->AddChecksum(CRCD(0x81cff663,"control_type"),m_control_type);		
+	
+	p_info->AddString(CRCD(0x4bc5229d,"view_goals_text"),mp_goal_name);
+	p_info->AddString(CRCD(0xc5d7e6b,"goal_description"),mp_goal_description);
+	p_info->AddString(CRCD(0x5de57284,"win_message"),mp_win_message);
+	p_info->AddString(CRCD(0xb9426608,"ped_name"),mp_ped_name);
+	p_info->AddChecksum(CRCD(0x6d11ed74,"goal_type"),m_goal_type);
+	p_info->AddChecksum(CRCD(0x71fc348b,"pro_name"),s_generate_node_name_checksum(m_level_goal_index,0));
+	
+	WriteGoalSpecificParams(p_info);
+
+	if (m_num_positions_set)
+	{
+		Script::CArray *p_position_array=new Script::CArray;
+		p_position_array->SetSizeAndType(m_num_positions_set,ESYMBOLTYPE_STRUCTURE);
+		for (uint i=0; iAddVector(CRCD(0x7f261953,"Pos"),mp_item_positions[i].mPos[X],
+														 mp_item_positions[i].mPos[Y],
+														 mp_item_positions[i].mPos[Z]);
+			p_pos_info->AddFloat(CRCD(0xab21af0,"Height"),mp_item_positions[i].mHeight);
+			p_pos_info->AddFloat(CRCD(0xff7ebaf6,"Angle"),mp_item_positions[i].mAngle);
+			p_position_array->SetStructure(i,p_pos_info);
+		}
+		p_info->AddArrayPointer(CRCD(0x32df12e0,"node_positions"),p_position_array);
+	}	
+}
+
+void CEditGoal::ReadFromStructure(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	
+	Clear();
+	m_used=true;
+
+	int won_goal=0;
+    //p_info->GetInteger(CRCD(0x14938611,"won_goal"),&won_goal);
+	m_has_won_goal=won_goal;
+	
+	m_level_script=s_get_level_checksum(p_info);
+	Dbg_MsgAssert(m_level_script,("Zero m_level_script"));
+	
+	p_info->GetInteger(CRCD(0x60ce75ae,"level_goal_index"),&m_level_goal_index);
+	
+	p_info->GetChecksum(CRCD(0x6d11ed74,"goal_type"),&m_goal_type);
+	Dbg_MsgAssert(m_goal_type,("Zero m_goal_type"));
+	// Calling SetType will set up m_num_positions_required
+	SetType(m_goal_type);
+
+	p_info->GetInteger(CRCD(0xcd66c8ae,"score"),&m_score);
+
+	p_info->GetInteger(CRCD(0xc51bf6e3,"time_limit"),&m_time_limit);
+	p_info->GetChecksum(CRCD(0x81cff663,"control_type"),&m_control_type);
+	
+	const char *p_string="";
+	p_info->GetString(CRCD(0x4bc5229d,"view_goals_text"),&p_string);
+	Dbg_MsgAssert(strlen(p_string)GetString(CRCD(0xc5d7e6b,"goal_description"),&p_string);
+	Dbg_MsgAssert(strlen(p_string)GetString(CRCD(0x5de57284,"win_message"),&p_string);
+	Dbg_MsgAssert(strlen(p_string)GetString(CRCD(0xb9426608,"ped_name"),&p_string);
+	Dbg_MsgAssert(strlen(p_string)GetArray(CRCD(0x32df12e0,"node_positions"),&p_position_array))
+	{
+		Dbg_MsgAssert(p_position_array->GetSize()==m_num_positions_required,("Bad node_positions size for goal '%s'",mp_goal_name));
+		
+		m_num_positions_set=p_position_array->GetSize();
+		for (uint i=0; iGetStructure(i);
+			p_struct->GetVector(CRCD(0x7f261953,"Pos"),&mp_item_positions[i].mPos);
+			p_struct->GetFloat(CRCD(0xab21af0,"Height"),&mp_item_positions[i].mHeight);
+			p_struct->GetFloat(CRCD(0xff7ebaf6,"Angle"),&mp_item_positions[i].mAngle);
+		}	
+	}
+		
+	ReadGoalSpecificParams(p_info);
+}
+
+void CEditGoal::AddGap(uint8 gap_number)
+{
+	if (GotGap(gap_number))
+	{
+		return;
+	}
+		
+	if (m_num_gaps>=MAX_GAPS)
+	{
+		printf("Warning! MAX_GAPS limit reached!\n");
+		return;
+	}	
+	Dbg_MsgAssert(m_num_gaps gap_number)
+		{
+			--mp_gap_numbers[i];
+		}
+	}
+}
+	
+bool CEditGoal::GotGap(uint8 gap_number)
+{
+	for (int i=0; iAddChecksum(CRCD(0x7be1e689,"goal_tetris_key_combos"),CRCD(0x6d641bcb,"emergency_key_combos"));
+		return;
+	}
+		
+	Script::CArray *p_predefined_key_combos=Script::GetArray(CRCD(0xba9358a5,"cag_skatetris_key_combos"),Script::ASSERT);
+
+	int size=0;
+	
+	// Calculate the total size of the goal_tetris_key_combos array.
+	for (int i=0; iGetStructure(mp_combo_sets[i].mSetIndex);
+		Script::CArray *p_key_combos=NULL;
+		p_struct->GetArray(CRCD(0x79704516,"key_combos"),&p_key_combos);
+		Dbg_MsgAssert(p_key_combos,("Missing key_combos array in element %d of array cag_skatetris_key_combos",i));
+		size+=p_key_combos->GetSize();
+	}
+
+	Dbg_MsgAssert(size,("Zero size for goal_tetris_key_combos array!"));
+
+	Script::CArray *p_goal_tetris_key_combos=new Script::CArray;
+	p_goal_tetris_key_combos->SetSizeAndType(size,ESYMBOLTYPE_STRUCTURE);
+
+	int dest_index=0;
+	for (int i=0; iGetStructure(mp_combo_sets[i].mSetIndex);
+		Script::CArray *p_key_combos=NULL;
+		p_struct->GetArray(CRCD(0x79704516,"key_combos"),&p_key_combos);
+		Dbg_MsgAssert(p_key_combos,("Missing key_combos array in element %d of array cag_skatetris_key_combos",i));
+		
+		for (uint32 j=0; jGetSize(); ++j)
+		{
+			Script::CStruct *p_entry=new Script::CStruct;
+
+			if (p_key_combos->GetType()==ESYMBOLTYPE_NAME)
+			{
+				p_entry->AddChecksum(CRCD(0xacfdb27a,"key_combo"),p_key_combos->GetChecksum(j));
+			}
+			else
+			{
+				p_entry->AppendStructure(p_key_combos->GetStructure(j));
+			}
+			
+			// Overlay any extra params the user specified for this set.
+			if (mp_combo_sets[i].mNumTaps)
+			{
+				p_entry->AddInteger(CRCD(0xa4bee6a1,"num_taps"),mp_combo_sets[i].mNumTaps);
+			}
+			if (mp_combo_sets[i].mRequirePerfect)
+			{
+				p_entry->AddChecksum(NONAME,CRCD(0x687e2c25,"require_perfect"));
+			}
+			//if (mp_combo_sets[i].mSpin)
+			//{
+			//	p_entry->AddInteger(CRCD(0x8c8abd19,"random_spin"),mp_combo_sets[i].mSpin*180);
+			//}
+			
+			// Originally each set could have it's own spin, but it was decided that that was not
+			// necessary and that instead a single spin value would be specified for the whole goal.
+			if (m_spin)
+			{
+				// random_spin means the goal manager will actually choose a random spin up to the m_spin value.
+				p_entry->AddInteger(CRCD(0x8c8abd19,"random_spin"),m_spin);
+			}
+				
+			p_goal_tetris_key_combos->SetStructure(dest_index++,p_entry);
+		}
+	}
+		
+	p_info->AddArrayPointer(CRCD(0x7be1e689,"goal_tetris_key_combos"),p_goal_tetris_key_combos);
+}
+
+// Used for writing the mp_combo_sets array to mem card.
+// Also used in GetDebugInfo
+void CEditGoal::write_combo_sets(Script::CStruct *p_info)
+{
+	if (!m_num_combo_sets)
+	{
+		return;
+	}
+		
+	Script::CArray *p_array=new Script::CArray;
+	p_array->SetSizeAndType(m_num_combo_sets,ESYMBOLTYPE_STRUCTURE);
+	for (int i=0; iAddInteger(CRCD(0xeb7d6498,"set_index"),mp_combo_sets[i].mSetIndex);
+		if (mp_combo_sets[i].mRequirePerfect)
+		{
+			p_struct->AddChecksum(NONAME,CRCD(0x687e2c25,"require_perfect"));
+		}
+		p_struct->AddInteger(CRCD(0xedf5db70,"spin"),mp_combo_sets[i].mSpin*180);
+		p_struct->AddInteger(CRCD(0xa4bee6a1,"num_taps"),mp_combo_sets[i].mNumTaps);
+		
+		p_array->SetStructure(i,p_struct);
+	}	
+	p_info->AddArrayPointer(CRCD(0x490f171,"combo_sets"),p_array);
+}
+
+void CEditGoal::read_combo_sets(Script::CStruct *p_info)
+{
+	Script::CArray *p_array=NULL;
+	if (!p_info->GetArray(CRCD(0x490f171,"combo_sets"),&p_array))
+	{
+		return;
+	}
+		
+	m_num_combo_sets=p_array->GetSize();
+	Dbg_MsgAssert(m_num_combo_sets<=MAX_COMBO_SETS,("m_num_combo_sets of %d too big",m_num_combo_sets));
+	
+	for (int i=0; iGetStructure(i);
+		
+		int int_val=0;
+		p_struct->GetInteger(CRCD(0xeb7d6498,"set_index"),&int_val);
+		mp_combo_sets[i].mSetIndex=int_val;
+		
+		int_val=0;
+		p_struct->GetInteger(CRCD(0xedf5db70,"spin"),&int_val);
+		Dbg_MsgAssert((int_val%180)==0,("Bad spin of %d",int_val));
+		mp_combo_sets[i].mSpin=int_val/180;
+		
+		int_val=0;
+		p_struct->GetInteger(CRCD(0xa4bee6a1,"num_taps"),&int_val);
+		mp_combo_sets[i].mNumTaps=int_val;
+		
+		mp_combo_sets[i].mRequirePerfect=p_struct->ContainsFlag(CRCD(0x687e2c25,"require_perfect"));
+	}	
+}
+
+// The dest parameter determines the format in which the combo sets are written out.
+// When generating params for the goal manager, the full goal_tetris_key_combos array is
+// generated. When writing to mem card the chosen pre-defined set numbers are written instead.
+void CEditGoal::write_skatetris_params(Script::CStruct *p_info, EWriteDest dest)
+{
+	p_info->AddInteger(CRCD(0x1181b29,"acceleration_interval"),m_acceleration_interval);
+	p_info->AddFloat(CRCD(0x76f65c8,"acceleration_percent"),m_acceleration_percent);
+	p_info->AddInteger(CRCD(0xded8540a,"trick_time"),m_trick_time);
+	p_info->AddInteger(CRCD(0xa6d96603,"time_to_stop_adding_tricks"),m_time_to_stop_adding_tricks);
+	p_info->AddInteger(CRCD(0x89473db7,"max_tricks"),m_max_tricks);
+	
+	if (dest==WRITING_TO_GOAL_MANAGER)
+	{
+		// When writing params out for the goal manager, the m_spin value is given to all of
+		// the key combos.
+		generate_key_combos_array(p_info);
+	}
+	else
+	{
+		p_info->AddInteger(CRCD(0xedf5db70,"spin"),m_spin);
+		write_combo_sets(p_info);
+	}
+}
+
+void CEditGoal::read_skatetris_params(Script::CStruct *p_info)
+{
+	p_info->GetInteger(CRCD(0x1181b29,"acceleration_interval"),&m_acceleration_interval);
+	p_info->GetFloat(CRCD(0x76f65c8,"acceleration_percent"),&m_acceleration_percent);
+	p_info->GetInteger(CRCD(0xded8540a,"trick_time"),&m_trick_time);
+	p_info->GetInteger(CRCD(0xa6d96603,"time_to_stop_adding_tricks"),&m_time_to_stop_adding_tricks);
+	p_info->GetInteger(CRCD(0x89473db7,"max_tricks"),&m_max_tricks);
+	p_info->GetInteger(CRCD(0xedf5db70,"spin"),&m_spin);
+	
+	read_combo_sets(p_info);
+}
+
+// The dest parameter determines the format in which the combo sets are written out.
+// When generating params for the goal manager, the full goal_tetris_key_combos array is
+// generated. When writing to mem card the chosen pre-defined set numbers are written instead.
+void CEditGoal::WriteGoalSpecificParams(Script::CStruct *p_info, EWriteDest dest)
+{
+	switch (m_goal_type)
+	{
+		case 0xea7ba666: // TrickTris
+			p_info->AddChecksum(NONAME,CRCD(0xea7ba666,"tricktris"));
+			p_info->AddInteger(CRCD(0x45a375c9,"tricktris_block_size"),m_tricktris_block_size);
+			p_info->AddInteger(CRCD(0xbfd3a831,"tricktris_total_to_win"),m_tricktris_total_to_win);
+			// Needed otherwise the goal will fail straight away for block size 13, total to win 12 (TT5933)
+			m_max_tricks=m_tricktris_block_size;
+			write_skatetris_params(p_info,dest);
+			break;
+			
+		case 0x0fc6f698: // ComboSkateTris
+			p_info->AddChecksum(NONAME,CRCD(0x4ec3cfb5,"combo"));
+			p_info->AddInteger(CRCD(0x67dd90ca,"combo_size"),m_combo_size);
+			if (m_single_combo)
+			{
+				p_info->AddChecksum(NONAME,CRCD(0xdf47b144,"single_combo"));
+				
+				// Needed otherwise the goal will fail straight away for a single combo size of 12 (TT7652)
+				m_max_tricks=m_combo_size;
+			}
+			write_skatetris_params(p_info,dest);
+			break;	
+			
+		case 0x7fe2238a: // SkateTris
+			write_skatetris_params(p_info,dest);
+			break;
+			
+		case 0x6fe44c6d: // HighScore
+		case 0xec414b76: // HighCombo
+		{
+			p_info->AddInteger(CRCD(0xcd66c8ae,"score"),m_score);
+			if (dest==WRITING_TO_GOAL_MANAGER)
+			{
+				const char *p_text=NULL;
+				if (m_goal_type==CRCD(0xec414b76,"HighCombo"))
+				{
+					p_text=Script::GetString(CRCD(0x3693bfe4,"edited_high_combo_goal_text"));
+				}
+				else
+				{
+					p_text=Script::GetString(CRCD(0xd6efb1b5,"edited_high_score_goal_text"));
+				}	
+				char p_temp[100];
+				sprintf(p_temp,p_text,m_score);
+				p_info->AddString(CRCD(0xda441d9a,"goal_text"),p_temp);
+			}
+			break;
+		}
+
+		case 0x61c5d092: // Gap
+		{
+			p_info->AddString(CRCD(0x730844a3,"required_trick_name"),mp_trick_name);
+			
+			if (dest==WRITING_TO_GOAL_MANAGER)
+			{
+				Script::CArray *p_goal_flags=new Script::CArray;
+				p_goal_flags->SetSizeAndType(m_num_gaps,ESYMBOLTYPE_NAME);
+				char p_foo[20];
+				for (int i=0; iSetChecksum(i,Script::GenerateCRC(p_foo));
+				}	
+				p_info->AddArrayPointer(CRCD(0xcc3c4cc4,"goal_flags"),p_goal_flags);
+
+				
+				Script::CArray *p_required_gaps=new Script::CArray;
+				p_required_gaps->SetSizeAndType(m_num_gaps,ESYMBOLTYPE_INTEGER);
+				
+				for (int i=0; iSetInteger(i,mp_gap_numbers[i]);
+				}	
+				p_info->AddArrayPointer(CRCD(0x52d4489e,"required_gaps"),p_required_gaps);
+			}
+			else
+			{
+				if (m_num_gaps)
+				{
+					Script::CArray *p_gaps=new Script::CArray;
+					p_gaps->SetSizeAndType(m_num_gaps,ESYMBOLTYPE_INTEGER);
+					for (int i=0; iSetInteger(i,mp_gap_numbers[i]);
+					}	
+					p_info->AddArrayPointer(CRCD(0xd76c173e,"Gaps"),p_gaps);
+				}	
+			}
+			break;
+		}
+			
+		default:
+			break;
+	}			
+}
+
+void CEditGoal::ReadGoalSpecificParams(Script::CStruct *p_params)
+{
+	switch (m_goal_type)
+	{
+		case 0xea7ba666: // TrickTris
+			p_params->GetInteger(CRCD(0x45a375c9,"tricktris_block_size"),&m_tricktris_block_size);
+			p_params->GetInteger(CRCD(0xbfd3a831,"tricktris_total_to_win"),&m_tricktris_total_to_win);
+			read_skatetris_params(p_params);
+			break;
+			
+		case 0x0fc6f698: // ComboSkateTris
+			p_params->GetInteger(CRCD(0x67dd90ca,"combo_size"),&m_combo_size);
+			
+			// Note: This only sets the m_single_combo if the flag single_combo is specified,
+			// if single_combo is not specified it will leave m_single_combo the way it was.
+			// Need this otherwise m_single_combo will get switched off if ReadGoalSpecificParams
+			// is used to just set combo_size.
+			if (p_params->ContainsFlag(CRCD(0xdf47b144,"single_combo")))
+			{
+				m_single_combo=true;
+			}	
+			
+			read_skatetris_params(p_params);
+			break;
+			
+		case 0x7fe2238a: // SkateTris
+			read_skatetris_params(p_params);
+			break;
+			
+		case 0x6fe44c6d: // HighScore
+		case 0xec414b76: // HighCombo
+		{
+			p_params->GetInteger(CRCD(0xcd66c8ae,"score"),&m_score);
+			break;
+		}
+			
+		case 0x61c5d092: // Gap
+		{
+			const char *p_trick_name=NULL;
+			if (p_params->GetString(CRCD(0x730844a3,"required_trick_name"),&p_trick_name))
+			{
+				Dbg_MsgAssert(strlen(p_trick_name)<=MAX_GAP_TRICK_NAME_CHARS,("trick_name '%s' too long",p_trick_name));
+				strcpy(mp_trick_name,p_trick_name);
+			}
+			
+			Script::CArray *p_gaps=NULL;
+			p_params->GetArray(CRCD(0xd76c173e,"Gaps"),&p_gaps);
+			if (p_gaps)
+			{
+				Dbg_MsgAssert(p_gaps->GetSize()<=MAX_GAPS,("Gaps array too big, max is %d",MAX_GAPS));
+				m_num_gaps=p_gaps->GetSize();
+				for (int i=0; iGetInteger(i);
+				}
+			}		
+			break;
+		}
+			
+		default:
+			break;
+	}			
+}
+
+void CEditGoal::RemoveGoalSpecificFlag(Script::CStruct *p_params)
+{
+	uint32 flag=0;
+	p_params->GetChecksum(NONAME,&flag);
+	
+	switch (m_goal_type)
+	{
+		case 0x0fc6f698: // ComboSkateTris
+			if (flag==CRCD(0xdf47b144,"single_combo"))
+			{
+				m_single_combo=false;
+			}	
+			break;
+			
+		default:
+			break;
+	}			
+}
+	
+void CEditGoal::EditGoal()
+{
+//	Dbg_MsgAssert(m_num_positions_set == m_num_positions_required,("Tried to edit a goal that was not fully defined originally"));
+	// Note: The index starts at 1, because the cursor will initially be at index 0, and the cursor object is
+	// not a marker object.
+	for (int index=1; index= m_num_positions_set)
+	{
+		m_num_positions_set=m_current_position_index;
+		if (m_num_positions_set == m_num_positions_required)
+		{
+			m_current_position_index=m_num_positions_required-1;
+			return true;
+		}	
+		
+		// Make the new position default to the last position
+		mp_item_positions[m_current_position_index].mPos=pos;
+		mp_item_positions[m_current_position_index].mHeight=height;
+		mp_item_positions[m_current_position_index].mAngle=angle;
+		
+		// When moving on to the first letter of the skate or combo line, add in a tweak to 
+		// the height so that the letter does not appear initially half way through the ground.
+		if ((m_goal_type==CRCD(0x4ec3cfb5,"Combo") || m_goal_type==CRCD(0x54166acd,"Skate")) &&  
+			 m_current_position_index == 2 )
+		{
+			mp_item_positions[m_current_position_index].mHeight+=Script::GetFloat(CRCD(0x82a694e,"GoalEditor_LetterHeight"));
+		}
+	}	
+		
+	return true;
+}
+
+bool CEditGoal::GetPosition(Mth::Vector *p_pos, float *p_height, float *p_angle)
+{
+	*p_pos=mp_item_positions[m_current_position_index].mPos;
+	*p_height=mp_item_positions[m_current_position_index].mHeight;
+	*p_angle=mp_item_positions[m_current_position_index].mAngle;
+	return true;
+}
+
+void CEditGoal::GetPosition(Mth::Vector *p_pos, int index)
+{
+	Dbg_MsgAssert(m_num_positions_set > index,("Called GetPedPosition with index=%d but m_num_positions_set=%d",index,m_num_positions_set));
+	*p_pos=mp_item_positions[index].mPos;
+	(*p_pos)[Y]+=mp_item_positions[index].mHeight;
+}
+
+CGoalPos::EType CEditGoal::GetPositionType()
+{
+	if (m_num_positions_set)
+	{
+		return mp_item_positions[m_current_position_index].mType;
+	}
+	return CGoalPos::PED;
+}
+
+bool CEditGoal::BackUp(const Mth::Vector& pos, float height, float angle)
+{
+	// Can't back up if at the first position
+	if (m_current_position_index == 0)
+	{
+		return false;
+	}
+
+	if (!position_ok_to_place(pos,height))
+	{
+		return false;
+	}
+		
+	// First, set the position as though X had been pressed, so that a marker is dropped at the current
+	// position which can be got back to by pressing X again.
+	Dbg_MsgAssert(m_current_position_indexAddChecksum(NONAME,CRCD(0x7f1538e0,"PedPosition"));
+		Script::RunScript(CRCD(0xd066a889,"create_cag_helper_text"), p_params);
+		delete p_params;
+	}	
+
+	return true;	
+}
+
+uint CEditGoal::GetCurrentLetterIndex()
+{
+	uint index=0;
+	
+	switch (m_goal_type)
+	{
+		case 0x4ec3cfb5: // Combo
+		case 0x54166acd: // Skate
+		{
+			Dbg_MsgAssert(m_current_position_index>=2,("Current position is not that of a letter"));
+			index=m_current_position_index-2;
+			break;
+		}
+		default:
+			Dbg_MsgAssert(0,("Called GetCurrentLetterIndex() when goal type is %s\n",Script::FindChecksumName(m_goal_type)));
+			break;
+	}
+	
+	return index;
+}
+
+void CEditGoal::SetType(uint32 type)
+{
+	m_goal_type=type;
+	
+	mp_item_positions[0].mType=CGoalPos::PED;
+	mp_item_positions[0].mHeight=0.0f;
+	mp_item_positions[1].mType=CGoalPos::RESTART;
+	mp_item_positions[1].mHeight=0.0f;
+	
+	const Script::CStruct *p_default_params=s_get_default_goal_params(m_goal_type);
+	
+	switch (m_goal_type)
+	{
+		case 0x54166acd: // Skate
+		{
+			m_num_positions_required=7;
+			
+			for (int i=2; iGetInteger(CRCD(0xc51bf6e3,"time_limit"),&m_time_limit);
+		p_default_params->GetInteger(CRCD(0xcd66c8ae,"score"),&m_score);
+		
+		const char *p_string="";
+		p_default_params->GetString(CRCD(0x4bc5229d,"view_goals_text"),&p_string);
+		Dbg_MsgAssert(strlen(p_string) m_time_limit)
+			{
+				m_time_to_stop_adding_tricks=m_time_limit;
+			}
+			break;
+			
+		default:
+			break;
+	}		
+}
+
+void CEditGoal::SetControlType(uint32 controlType)
+{
+	m_control_type=controlType;
+}
+
+void CEditGoal::AddGoalToGoalManager(bool markUnbeaten)
+{
+	Script::CStruct *p_params=new Script::CStruct;
+	add_goal_params(p_params);
+	
+	Game::CGoalManager* p_goal_manager = Game::GetGoalManager();
+	Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager ?"));
+	p_goal_manager->AddGoal(GetId(),p_params);
+
+	Game::CGoal *p_goal=p_goal_manager->GetGoal(GetId());
+	// Mark it as seen so that it is selectable in the view-goals menu
+	p_goal->SetHasSeen();		
+
+	if (markUnbeaten)
+	{
+		m_has_won_goal=false;
+	}
+		
+	if (m_has_won_goal)
+	{
+		p_goal->MarkBeaten();
+	}	
+	
+	delete p_params;
+}
+
+void CEditGoal::write_position_into_node(uint index, uint32 nodeName)
+{
+	Dbg_MsgAssert(indexAddVector(CRCD(0x7f261953,"Pos"),	mp_item_positions[index].mPos.GetX(),
+												mp_item_positions[index].mPos.GetY()+mp_item_positions[index].mHeight,
+												mp_item_positions[index].mPos.GetZ());
+	p_node->AddVector(CRCD(0x9d2d0915,"Angles"),0,
+												mp_item_positions[index].mAngle-3.141592654f,
+												0);
+												
+	// For letter nodes, the type and model filename need to be added.
+	if (index >=2 )
+	{
+		p_node->AddChecksum(CRCD(0x7321a8d6,"Type"),s_generate_letter_node_type_checksum(m_goal_type,index-2));
+		p_node->AddString(CRCD(0x286a8d26,"Model"),s_generate_letter_model_filename(m_goal_type,index-2));
+	}	
+}
+
+void CEditGoal::WriteNodePositions()
+{
+	for (int i=0; i 1)
+	{
+		Dbg_MsgAssert(mp_item_positions[1].mType==CGoalPos::RESTART,("Strange type for goal position 1"));
+		float x=mp_item_positions[1].mPos.GetX();
+		float z=mp_item_positions[1].mPos.GetZ();
+		
+		if (x < x0 || x > x1 || z < z0 || z > z1)
+		{
+			return true;
+		}
+	}		
+	return false;
+}
+
+void CEditGoal::GetDebugInfo(Script::CStruct* p_info)
+{
+#ifdef	__DEBUG_CODE__
+	WriteIntoStructure(p_info);
+#endif				 
+}
+
+#ifdef SIZE_TEST	
+void CEditGoal::FillNameBuffers()
+{
+	int i;
+	for (i=0; i( new CGoalEditorComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CGoalEditorComponent::CGoalEditorComponent() : CBaseComponent()
+{
+	SetType( CRC_GOALEDITOR );
+	
+	ClearAllExceptParkGoals();
+	ClearOnlyParkGoals();
+	mp_input_component=NULL;
+	mp_editor_camera_component=NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CGoalEditorComponent::~CGoalEditorComponent()
+{
+	Script::RunScript("goal_editor_destroy_cursor");
+}
+
+// For writing to memcard
+void CGoalEditorComponent::WriteIntoStructure(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+		
+	int goal_count=0;
+	for (int i=0; iSetSizeAndType(goal_count,ESYMBOLTYPE_STRUCTURE);
+	int goal_index=0;
+	for (int i=0; iSetStructure(goal_index++,p_goal_info);
+		}	
+	}
+	Dbg_MsgAssert(goal_index==goal_count,("Bad goal_index ?"));
+	p_info->AddArrayPointer(CRCD(0x38dbe1d0,"Goals"),p_goals_array);
+}
+
+// This just writes in the goals for a particular level. Used when sending goal info
+// across the network for network goal attack games.
+void CGoalEditorComponent::WriteIntoStructure(uint32 levelScript, Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	
+	int num_goals=CountGoalsForLevel(levelScript);
+			
+	Script::CArray *p_goals_array=new Script::CArray;
+	p_goals_array->SetSizeAndType(num_goals,ESYMBOLTYPE_STRUCTURE);
+	int goal_index=0;
+	for (int i=0; iSetStructure(goal_index++,p_goal_info);
+		}	
+	}
+	Dbg_MsgAssert(goal_index==num_goals,("Bad num_goals ?"));
+	
+	p_info->AddArrayPointer(CRCD(0x38dbe1d0,"Goals"),p_goals_array);
+}
+
+// Writes out the goals for a particular level into a uint8* buffer.
+// Used when sending goal info across the network for network goal attack games.
+uint32 CGoalEditorComponent::WriteToBuffer(uint32 levelScript, uint8 *p_buffer, uint32 bufferSize)
+{
+	Dbg_MsgAssert(p_buffer,("NULL p_buffer"));
+	
+	// Warning! It's feasible that we could run out of memory here ... 
+	// The structure could contain around 6K of stuff.	
+	Script::CStruct *p_struct=new Script::CStruct;
+	WriteIntoStructure( s_convert_level( levelScript ), p_struct);
+	uint32 bytes_written=Script::WriteToBuffer(p_struct, p_buffer, bufferSize);
+	delete p_struct;
+	
+	return bytes_written;
+}
+
+uint8 *CGoalEditorComponent::ReadFromBuffer(uint32 levelScript, uint8 *p_buffer)
+{
+	Dbg_MsgAssert(p_buffer,("NULL p_buffer"));
+
+	levelScript=s_convert_level(levelScript);
+	
+	Script::CStruct *p_struct=new Script::CStruct;
+	uint8 *p_end=Script::ReadFromBuffer(p_struct, p_buffer);
+	
+	// Run through all the goals clearing any that exist for this level.
+	for (int i=0; iGetArray(CRCD(0x38dbe1d0,"Goals"),&p_goals_array,Script::ASSERT);
+
+	uint32 array_index=0;
+	int num_goals_left_to_load=p_goals_array->GetSize();
+	for (int i=0; iGetStructure(array_index++));
+			mp_goals[i].SetLevel(levelScript);
+			--num_goals_left_to_load;
+		}
+	}
+	Dbg_MsgAssert(num_goals_left_to_load == 0,("Could not load all the goals"));
+	
+	delete p_struct;
+	return p_end;
+}
+
+// For reading from memcard
+void CGoalEditorComponent::ReadFromStructure(Script::CStruct *p_info, EBoolLoadingParkGoals loadingParkGoals)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+
+	if (loadingParkGoals)
+	{
+		ClearOnlyParkGoals();
+	}
+	else
+	{
+		ClearAllExceptParkGoals();
+	}
+	
+	Script::CArray *p_goals_array=NULL;
+	p_info->GetArray(CRCD(0x38dbe1d0,"Goals"),&p_goals_array);
+	if (!p_goals_array)
+	{
+		// No Goals array means no goals.
+		return;
+	}
+	
+	uint32 array_size=p_goals_array->GetSize();
+
+	for (uint32 array_index=0; array_indexGetStructure(array_index);
+		if ( (loadingParkGoals && s_get_level_checksum(p_struct) == CRCD(0xe8b4b836,"Load_Sk5Ed")) ||
+			 (!loadingParkGoals && s_get_level_checksum(p_struct) != CRCD(0xe8b4b836,"Load_Sk5Ed")))
+		{
+			for (int i=0; iAddInteger(CRCD(0x67e6859a,"player"),0);
+		mp_input_component->InitFromStructure(p_params);
+		delete p_params;
+		
+		refresh_cursor_object();
+	}
+}
+
+Obj::CCompositeObject *CGoalEditorComponent::get_cursor()
+{
+	return (Obj::CCompositeObject*)Obj::CTracker::Instance()->GetObject(CRCD(0x9211b125,"GoalEditorCursor"));
+}
+
+void CGoalEditorComponent::refresh_cursor_object()
+{
+	const char *p_model_filename="";
+
+	Dbg_MsgAssert(mp_current_goal,("NULL mp_current_goal"));
+	switch (mp_current_goal->GetPositionType())
+	{
+		case CGoalPos::RESTART:
+			Dbg_MsgAssert(mp_current_goal,("NULL mp_current_goal?"));
+			p_model_filename="gameobjects\\p1_cursor\\p1_cursor.mdl";
+			break;
+		case CGoalPos::LETTER:
+			Dbg_MsgAssert(mp_current_goal,("NULL mp_current_goal?"));
+			p_model_filename=s_generate_letter_model_filename(mp_current_goal->GetType(),
+															  mp_current_goal->GetCurrentLetterIndex());
+			break;
+		default:
+			p_model_filename="gameobjects\\goal_cursor\\goal_cursor.mdl";
+			break;
+	}
+	
+	Script::CStruct params;
+	if (mp_current_goal->GetPositionType()==CGoalPos::PED)
+	{
+		params.AddChecksum(CRCD(0x7321a8d6,"Type"),CRCD(0x61a741e,"Ped"));
+	}
+	else
+	{
+		params.AddChecksum(CRCD(0x7321a8d6,"Type"),0);
+		params.AddString(CRCD(0x286a8d26,"Model"),p_model_filename);
+	}
+	
+	Script::RunScript(CRCD(0x36791e3c,"goal_editor_create_cursor"),¶ms,GetObject());
+	
+	// Choose whether the cursor should be allowed to be moved over kill polys.
+	// Only the restart position is not allowed to, to allow letters to be placed over kill polys.
+	update_cursor_collision_type();
+}
+
+void CGoalEditorComponent::get_pos_from_camera_component(Mth::Vector *p_pos, float *p_height, float *p_angle)
+{
+	Dbg_MsgAssert(mp_editor_camera_component,("NULL mp_editor_camera_component ?"));
+	*p_pos=mp_editor_camera_component->GetCursorPos();
+	*p_height=mp_editor_camera_component->GetCursorHeight();
+	*p_angle=mp_editor_camera_component->GetCursorOrientation();
+}
+
+void CGoalEditorComponent::update_cursor_collision_type()
+{
+	Dbg_MsgAssert(mp_editor_camera_component,("NULL mp_editor_camera_component ?"));
+	
+	// Restart positions are not allowed to go over kill polys, otherwise the skater will
+	// die straight away.
+	// Note that the ped position also has to not be allowed to go over kill polys, otherwise
+	// as soon as it is placed the restart cursor will be over the kill poly, and hence be stuck.
+	if (mp_current_goal->GetPositionType()==CGoalPos::PED ||
+		mp_current_goal->GetPositionType()==CGoalPos::RESTART)
+	{
+		Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+		if (p_skate_mod->m_cur_level == CRCD(0xe8b4b836,"load_sk5ed") )
+		{
+			// In the park editor, allow the cursor to move anywhere, otherwise the cursor
+			// will get stuck if initialised on a kill poly.
+			mp_editor_camera_component->SetSimpleCollision(true);
+		}
+		else
+		{
+			mp_editor_camera_component->SetSimpleCollision(false);
+		}	
+	}
+	else
+	{
+		mp_editor_camera_component->SetSimpleCollision(true);
+	}
+}
+
+CEditGoal *CGoalEditorComponent::find_goal(const char *p_name)
+{
+	uint32 current_level=s_get_current_level();
+
+	Dbg_MsgAssert(p_name,("NULL p_name ?"));
+	for (int i=0; iGetChecksum(CRCD(0x9982e501,"goal_id"),&id))
+	{
+		return find_goal(id);
+	}
+	
+	const char *p_name=NULL;
+	if (p_params->GetString(CRCD(0xbfecc45b,"goal_name"),&p_name))
+	{
+		return find_goal(p_name);
+	}
+	
+	int index=0;
+	if (p_params->GetInteger(CRCD(0x474a6a7f,"goal_index"),&index))
+	{
+		Dbg_MsgAssert(0,("find_goal called for an index"));
+		
+		uint32 current_level=s_get_current_level();
+		
+		for (int i=0; iGetPosition(&pos,&height,&angle);
+	
+	mp_editor_camera_component->SetCursorHeight(height);
+	mp_editor_camera_component->SetCursorOrientation(angle);
+	mp_editor_camera_component->SetCursorPos(pos);
+}
+
+void CGoalEditorComponent::update_cursor_position()
+{
+	Script::CStruct params;
+	
+	Mth::Vector cursor_pos(0.0f,0.0f,0.0f);		
+	float cursor_height=0.0f;
+	float cursor_angle=0.0f;
+	get_pos_from_camera_component(&cursor_pos,&cursor_height,&cursor_angle);
+	cursor_pos[Y]+=cursor_height;
+	
+	params.AddVector(CRCD(0x7f261953,"Pos"),cursor_pos[X],cursor_pos[Y],cursor_pos[Z]);
+	params.AddFloat(CRCD(0xff7ebaf6,"Angle"),mp_editor_camera_component->GetCursorOrientation());
+	
+	Script::RunScript(CRCD(0xdea7fe56,"goal_editor_update_cursor_position"),¶ms);
+}
+
+void CGoalEditorComponent::remove_goal(CEditGoal *p_goal)
+{
+	Dbg_MsgAssert(p_goal,("NULL p_goal"));
+	
+	if (p_goal->Used())
+	{
+		Game::CGoalManager* p_goal_manager = Game::GetGoalManager();
+		Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager ?"));
+		
+		if (p_goal_manager->GoalExists(p_goal->GetId()))
+		{
+			p_goal_manager->RemoveGoal(p_goal->GetId());
+		}	
+	}	
+	p_goal->Clear();
+	
+	if (mp_current_goal==p_goal)
+	{
+		mp_current_goal=NULL;
+	}
+	if (GetNumGoals()==0)
+	{
+		Dbg_MsgAssert(mp_current_goal==NULL,("mp_current_goal not NULL ?"));
+	}	
+}
+
+bool CGoalEditorComponent::too_close_to_another_goal_position(const Mth::Vector &pos, float height, uint32 level, int positionIndex)
+{
+	Mth::Vector pos_with_height=pos;
+	pos_with_height[Y]+=height;
+	
+	float default_min_dist_allowable=Script::GetFloat(CRCD(0x71b20276,"GoalEditor_DefaultMinDistBetweenPositions"));
+	
+	for (int i=0; i positionIndex)
+		{
+			Mth::Vector p;
+			mp_goals[i].GetPosition(&p, positionIndex);
+			p-=pos_with_height;
+			if (p.Length() < default_min_dist_allowable)
+			{
+				return true;
+			}
+		}
+	}
+			
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CGoalEditorComponent::Finalize()
+{
+	// Get the pointers to the other required components.
+	
+	Dbg_MsgAssert(mp_input_component==NULL,("mp_input_component not NULL ?"));
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	Dbg_MsgAssert(mp_input_component,("CGoalEditorComponent requires parent object to have an input component!"));
+
+	Dbg_MsgAssert(mp_editor_camera_component==NULL,("mp_editor_camera_component not NULL ?"));
+	mp_editor_camera_component = GetEditorCameraComponentFromObject(GetObject());
+	Dbg_MsgAssert(mp_editor_camera_component,("CGoalEditorComponent requires parent object to have an EditorCamera component!"));
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CGoalEditorComponent::Update()
+{
+	if (mp_current_goal)
+	{
+		CControlPad& control_pad = mp_input_component->GetControlPad();
+		if (control_pad.m_x.GetTriggered())
+		{
+			control_pad.m_x.ClearTrigger();
+	
+			Mth::Vector cursor_pos(0.0f,0.0f,0.0f);
+			float cursor_height=0.0f;
+			float cursor_angle=0.0f;
+			get_pos_from_camera_component(&cursor_pos,&cursor_height,&cursor_angle);
+			
+			if ((mp_current_goal->GetPositionType() == CGoalPos::RESTART && 
+				too_close_to_another_goal_position(cursor_pos,cursor_height,mp_current_goal->GetLevel(),0)) || // 0 = ped
+				(mp_current_goal->GetPositionType() == CGoalPos::PED && 
+				too_close_to_another_goal_position(cursor_pos,cursor_height,mp_current_goal->GetLevel(),1))) // 1 = restart
+			{
+				// Can't place restart positions too close to another goal's ped position, or
+				// ped positions too close to another goal's restart position.
+				Script::RunScript(CRCD(0xaebc7020,"goal_editor_play_placement_fail_sound"),NULL,GetObject());
+			}
+			else
+			{
+				if (mp_current_goal->SetPosition(cursor_pos,cursor_height,cursor_angle))
+				{
+					Script::RunScript(CRCD(0x718b071b,"goal_editor_play_placement_success_sound"),NULL,GetObject());
+					// Once a position is set, the cursor model will need to change for the next position.
+					refresh_cursor_object();
+					update_camera_pos();
+				}
+				else
+				{
+					Script::RunScript(CRCD(0xaebc7020,"goal_editor_play_placement_fail_sound"),NULL,GetObject());
+				}
+			}	
+			
+			if (mp_current_goal->PlacedLastPosition())
+			{
+				Script::RunScript(CRCD(0x89119628,"goal_editor_finished_placing_letters"),NULL,GetObject());
+			}
+		}	
+	
+		if (control_pad.m_triangle.GetTriggered())
+		{
+			control_pad.m_triangle.ClearTrigger();
+	
+			Mth::Vector cursor_pos(0.0f,0.0f,0.0f);
+			float cursor_height=0.0f;
+			float cursor_angle=0.0f;
+			get_pos_from_camera_component(&cursor_pos,&cursor_height,&cursor_angle);
+	
+			Dbg_MsgAssert(mp_current_goal,("NULL mp_current_goal"));
+			if ((mp_current_goal->GetPositionType() == CGoalPos::RESTART && 
+				too_close_to_another_goal_position(cursor_pos,cursor_height,mp_current_goal->GetLevel(),0)) || // 0 = ped
+				(mp_current_goal->GetPositionType() == CGoalPos::PED && 
+				too_close_to_another_goal_position(cursor_pos,cursor_height,mp_current_goal->GetLevel(),1))) // 1 = restart
+			{
+				// Can't place restart positions too close to another goal's ped position, or
+				// ped positions too close to another goal's restart position.
+				Script::RunScript(CRCD(0xade100d6,"goal_editor_play_backup_fail_sound"),NULL,GetObject());
+			}
+			else
+			{
+				if (mp_current_goal->BackUp(cursor_pos,cursor_height,cursor_angle))
+				{
+					Script::RunScript(CRCD(0xfa275cc5,"goal_editor_play_backup_success_sound"),NULL,GetObject());
+					refresh_cursor_object();
+					update_camera_pos();
+				}	
+				else
+				{
+					Script::RunScript(CRCD(0xade100d6,"goal_editor_play_backup_fail_sound"),NULL,GetObject());
+				}			
+			}	
+		}	
+	}
+	
+	update_cursor_position();
+}
+
+void CGoalEditorComponent::ClearAllExceptParkGoals()
+{
+	for (uint i=0; iClear();
+			mp_current_goal->SetUsedFlag(true);
+		
+			mp_current_goal->SetLevel(current_level);
+			mp_current_goal->SetLevelGoalIndex(level_goal_index);
+			
+			refresh_cursor_object();
+			return;
+		}
+	}
+								
+	Dbg_MsgAssert(0,("Exceeded CGoalEditorComponent::MAX_GOALS_TOTAL of %d",MAX_GOALS_TOTAL));
+}
+
+void CGoalEditorComponent::SetGoalType(uint32 type)
+{
+	Dbg_MsgAssert(mp_current_goal,("NULL mp_current_goal"));
+	mp_current_goal->SetType(type);
+}
+	
+bool CGoalEditorComponent::ThereAreGoalsOutsideArea(float x0, float z0, float x1, float z1)
+{
+	for (int i=0; iGetInteger(CRCD(0x3ac9ab5e,"gap_number"),&gap_number);
+				p_goal->AddGap(gap_number);
+			}
+			break;
+		}
+
+		// @script | EditedGoalRemoveGap
+		case 0x5c155a29: // EditedGoalRemoveGap
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				int gap_number=0;
+				pParams->GetInteger(CRCD(0x3ac9ab5e,"gap_number"),&gap_number);
+				p_goal->RemoveGap(gap_number);
+			}
+			break;
+		}
+
+		// @script | EditedGoalGotGap
+		case 0xfb279560: // EditedGoalGotGap
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				int gap_number=0;
+				pParams->GetInteger(CRCD(0x3ac9ab5e,"gap_number"),&gap_number);
+				if (p_goal->GotGap(gap_number))
+				{
+					return CBaseComponent::MF_TRUE;
+				}	
+			}
+			return CBaseComponent::MF_FALSE;
+			break;
+		}
+		
+		// @script | AddKeyComboSet
+		case 0xcca11c78: // AddKeyComboSet
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				int set_index=0;
+				pParams->GetInteger(CRCD(0xeb7d6498,"set_index"),&set_index);
+				int spin=0;
+				pParams->GetInteger(CRCD(0xedf5db70,"spin"),&spin);
+				int num_taps=0;
+				pParams->GetInteger(CRCD(0xa4bee6a1,"num_taps"),&num_taps);
+				p_goal->AddKeyComboSet(set_index,
+									   pParams->ContainsFlag(CRCD(0x687e2c25,"require_perfect")),
+									   spin,
+									   num_taps);
+			}	
+			break;
+		}
+		
+		// @script | RemoveKeyComboSet
+		case 0x1d30a7b6: // RemoveKeyComboSet
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				int set_index=0;
+				pParams->GetInteger(CRCD(0xeb7d6498,"set_index"),&set_index);
+				p_goal->RemoveKeyComboSet(set_index);
+			}
+			break;
+		}
+
+		// @script | GetKeyComboSet
+		case 0xe5ed63b7: // GetKeyComboSet
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				int set_index=0;
+				pParams->GetInteger(CRCD(0xeb7d6498,"set_index"),&set_index);
+				
+				SPreDefinedKeyComboSet *p_keycombo_set=p_goal->GetKeyComboSet(set_index);
+				if (p_keycombo_set)
+				{
+					pScript->GetParams()->AddInteger(CRCD(0xedf5db70,"Spin"),p_keycombo_set->mSpin*180);
+					pScript->GetParams()->AddInteger(CRCD(0xa4bee6a1,"num_taps"),p_keycombo_set->mNumTaps);
+					if (p_keycombo_set->mRequirePerfect)
+					{
+						pScript->GetParams()->AddChecksum(NONAME,CRCD(0x687e2c25,"require_perfect"));
+					}	
+				}
+				else
+				{
+					return CBaseComponent::MF_FALSE;
+				}	
+			}
+			break;
+		}
+		
+		// @script | RemoveGoalSpecificFlag
+		case 0x3ebd2c9: // RemoveGoalSpecificFlag
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->RemoveGoalSpecificFlag(pParams);
+			}	
+			break;
+		}
+		
+		// @script | SetGoalSpecificParams
+		case 0x47208c6d: // SetGoalSpecificParams
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->ReadGoalSpecificParams(pParams);
+			}	
+			break;
+		}
+
+		// @script | FlagGoalAsWon
+		case 0x769309e1: // FlagGoalAsWon
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->FlagAsWon();
+			}	
+			break;
+		}
+		
+		
+		// @script | FindUnfinishedGoal
+		case 0x88d54a10: // FindUnfinishedGoal
+		{
+			uint32 type=0;
+			pParams->GetChecksum("type",&type);
+			
+			mp_current_goal=NULL;
+			
+			for (int i=0; iGetSkateGoalMaxSize());
+			
+			pScript->GetParams()->AddChecksum("goal_id",mp_current_goal->GetId());
+			break;
+		}	
+		
+		// @script | RemovedCreatedGoal |
+		case 0x1f80b5ae: // RemovedCreatedGoal
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				remove_goal(p_goal);
+			}	
+			break;
+		}
+
+		// @script | GoalHasAllPositionsSet |
+		case 0x26848221: // GoalHasAllPositionsSet
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				if (!p_goal->GotAllPositions())
+				{
+					return CBaseComponent::MF_FALSE;
+				}	
+			}	
+			else
+			{
+				return CBaseComponent::MF_FALSE;
+			}
+			break;
+		}
+			
+		// @script | SetCurrentEditorGoal |
+		case 0xfdb2c514: // SetCurrentEditorGoal
+		{
+			mp_current_goal=find_goal(pParams);
+			break;
+		}
+			
+        // @script | SetEditorGoalType | 
+		case 0x98abbd09: // SetEditorGoalType
+		{
+			uint32 type=0;
+			pParams->GetChecksum("type",&type);
+			
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->SetType(type);
+			}	
+			break;
+		}
+
+        // @script | SetEditorGoalDescription | 
+		case 0x66a33157: // SetEditorGoalDescription
+		{
+			const char *p_text="";
+			pParams->GetString("text",&p_text);
+			
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->SetGoalDescription(p_text);
+			}	
+			break;
+		}
+
+        // @script | SetEditorGoalWinMessage | 
+		case 0x7aabbc09: // SetEditorGoalWinMessage
+		{
+			const char *p_text="";
+			pParams->GetString("text",&p_text);
+			
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->SetWinMessage(p_text);
+			}	
+			break;
+		}
+
+		// @script | GetCurrentEditedGoalInfo
+		case 0x69b872e3: // GetCurrentEditedGoalInfo
+		{
+			if (mp_current_goal)
+			{
+				mp_current_goal->WriteIntoStructure(pScript->GetParams());
+			}	
+			break;
+		}
+			
+		// @script | GetEditedGoalsInfo
+		// @parmopt name | Level | If specified, it will only include goals for this level.
+		// The level is specified using the name of its load script.
+		case 0x96f11bd3: // GetEditedGoalsInfo
+		{
+			uint32 level=s_get_level_checksum(pParams);
+			
+			int n=CountGoalsForLevel(level);
+			if (n==0)
+			{
+				break;
+			}
+				
+			Script::CArray *p_info_array=new Script::CArray;
+			p_info_array->SetSizeAndType(n, ESYMBOLTYPE_STRUCTURE);
+			int index=0;
+			
+			for (int i=0; iSetStructure(index++,p_info);
+				}	
+			}
+			Dbg_MsgAssert(index==n,("Eh?"));
+			
+			pScript->GetParams()->AddArrayPointer("EditedGoalsInfo",p_info_array);
+			break;
+		}
+			
+		// @script | GetNumEditedGoals	
+		// @parmopt name | Level | If specified, it will only count goals for this level.
+		// The level is specified using the name of its load script.
+		case 0x96874358: // GetNumEditedGoals
+		{
+			uint32 level=s_get_level_checksum(pParams);
+			
+			int n=CountGoalsForLevel(level);
+			if (level==0 && pParams->ContainsFlag(CRCD(0xb99f6330,"ExcludeParkEditorGoals")))
+			{
+				n-=CountGoalsForLevel(CRCD(0xe8b4b836,"Load_Sk5Ed"));
+				Dbg_MsgAssert(n>=0,("More editor goals (%d) than total goals? (%d)",CountGoalsForLevel(CRCD(0xe8b4b836,"Load_Sk5Ed")),CountGoalsForLevel(0)));
+			}
+			pScript->GetParams()->AddInteger("NumGoals",n);
+			break;
+		}
+
+		// @script | GetCurrentEditedGoalId
+		case 0x5bd8050a: // GetCurrentEditedGoalId
+		{
+			pScript->GetParams()->RemoveComponent("goal_id");
+			if (mp_current_goal)
+			{
+				pScript->GetParams()->AddChecksum("goal_id",mp_current_goal->GetId());
+			}
+			break;
+		}
+
+		// @script | WriteEditedGoalNodePositions
+		case 0x5408ac04: // WriteEditedGoalNodePositions
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->WriteNodePositions();
+			}	
+			break;
+		}
+		
+		// @script | AddEditedGoalToGoalManager
+		case 0xb525b113: // AddEditedGoalToGoalManager
+		{
+			CEditGoal *p_goal=find_goal(pParams);
+			if (p_goal)
+			{
+				p_goal->AddGoalToGoalManager(pParams->ContainsFlag(CRCD(0xc6322a25,"MarkUnbeaten")));
+			}	
+			break;
+		}
+
+		// @script | SetEditorGoalName |
+		case 0x4a569426: // SetEditorGoalName
+		{
+			const char *p_name="";
+			if (mp_current_goal && pParams->GetString("Name",&p_name))
+			{
+				mp_current_goal=find_goal(pParams);
+				mp_current_goal->SetGoalName(p_name);
+			}	
+			break;
+		}
+
+		// @script | SetEditorPedName |
+		case 0x410821df: // SetEditorPedName
+		{
+			const char *p_name="";
+			if (mp_current_goal && pParams->GetString("Name",&p_name))
+			{
+				mp_current_goal=find_goal(pParams);
+				mp_current_goal->SetPedName(p_name);
+			}	
+			break;
+		}
+
+		// @script | MaxEditedGoalsReached | Returns true if there is no space for any more
+		// goals, or if the max goals per level has been reached for this level.
+		// @parmopt name | Level | The level, specified using its load script name.
+		case 0x60288abe: // MaxEditedGoalsReached
+		{
+			if (GetNumGoals()==MAX_GOALS_TOTAL)
+			{
+				// Definitely reached the max if got MAX_GOALS_TOTAL
+				return CBaseComponent::MF_TRUE;
+			}
+			
+			// Otherwise, count up how many goals exist for this level, 
+			// and compare with MAX_GOALS_PER_LEVEL
+			uint32 level=s_get_level_checksum(pParams);
+			Dbg_MsgAssert(level,("\n%s\nNo Level specified",pScript->GetScriptInfo()));
+			
+			if (CountGoalsForLevel(level) >= MAX_GOALS_PER_LEVEL)
+			{
+				return CBaseComponent::MF_TRUE;
+			}
+				
+			return CBaseComponent::MF_FALSE;
+			break;
+		}
+
+		// @script | EditGoal |
+		case 0x831eca10: // EditGoal
+			mp_current_goal=find_goal(pParams);
+			if (mp_current_goal)
+			{
+				mp_current_goal->EditGoal();
+				update_camera_pos();
+				refresh_cursor_object();
+			}
+			break;
+
+		// @script | NukeAllGoals	
+		case 0xdba8e3fc: // NukeAllGoals
+		{
+			if (pParams->ContainsFlag(CRCD(0x98439808,"OnlyParkEditorGoals")))
+			{
+				ClearOnlyParkGoals();
+			}
+			else
+			{
+				ClearAllExceptParkGoals();
+			}	
+			break;
+		}
+			
+		// @script | GetMaxGoalsPerLevel		
+		case 0xd7258335: // GetMaxGoalsPerLevel
+		{
+			pScript->GetParams()->AddInteger(CRCD(0x8bc2e0db,"max_goals"),MAX_GOALS_PER_LEVEL);
+			break;
+		}
+
+		case 0x9e5a634a: // SetGoalScore
+		{
+			mp_current_goal=find_goal(pParams);
+			if (mp_current_goal)
+			{
+				int score=0;
+				if (pParams->GetInteger(NONAME,&score))
+				{
+					mp_current_goal->SetScore(score);
+				}	
+			}
+			break;
+		}
+
+		// @script | SetGoalTimeLimit
+		case 0x7d5073f6: // SetGoalTimeLimit
+		{
+			mp_current_goal=find_goal(pParams);
+			if (mp_current_goal)
+			{
+				int time=120;
+				if (pParams->GetInteger(NONAME,&time))
+				{
+					mp_current_goal->SetTimeLimit(time);
+				}	
+			}
+			break;
+		}
+
+		// @script | SetGoalControlType
+		case 0xec316a9c: // SetGoalControlType
+		{
+			mp_current_goal=find_goal(pParams);
+			if (mp_current_goal)
+			{
+				uint32 control_type=CRCD(0x54166acd,"skate");
+				if (pParams->GetChecksum(NONAME,&control_type))
+				{
+					mp_current_goal->SetControlType(control_type);
+				}	
+			}
+			break;
+		}
+
+		// @script | RefreshGoalCursorPosition
+		case 0x20b566b1: // RefreshGoalCursorPosition
+		{
+			if (mp_current_goal)
+			{
+				Mth::Vector cursor_pos(0.0f,0.0f,0.0f);		
+				float angle=0.0f;
+				float height=0.0f;
+				mp_current_goal->GetPosition(&cursor_pos,&height,&angle);
+				
+				Script::CStruct params;
+				params.AddVector(CRCD(0x7f261953,"Pos"),cursor_pos[X],cursor_pos[Y]+height,cursor_pos[Z]);
+				params.AddFloat(CRCD(0xff7ebaf6,"Angle"),angle);
+				Script::RunScript(CRCD(0xdea7fe56,"goal_editor_update_cursor_position"),¶ms);
+			}	
+			break;
+		}
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalEditorComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CGoalEditorComponent::GetDebugInfo"));
+
+	int num_goals=GetNumGoals();
+	p_info->AddInteger("NumGoals",num_goals);
+	if (mp_current_goal)
+	{
+		Script::CStruct *p_struct=new Script::CStruct;
+		mp_current_goal->GetDebugInfo(p_struct);
+		p_info->AddStructurePointer("CurrentGoal",p_struct);
+	}
+	
+	if (num_goals)
+	{
+		Script::CArray *p_array=new Script::CArray;
+		p_array->SetSizeAndType(num_goals,ESYMBOLTYPE_STRUCTURE);
+		int index=0;
+		for (int i=0; iSetStructure(index++,p_struct);
+			}	
+		}
+		Dbg_MsgAssert(index==num_goals,("Bad num_goals ?"));
+		p_info->AddArrayPointer("Goals",p_array);
+	}
+		
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+Obj::CGoalEditorComponent *GetGoalEditor()
+{
+	Obj::CCompositeObject *p_obj=(Obj::CCompositeObject*)Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x81f01058,"GoalEditor"));
+	Dbg_MsgAssert(p_obj,("No GoalEditor object"));
+	Obj::CGoalEditorComponent *p_goal_editor=GetGoalEditorComponentFromObject(p_obj);
+	Dbg_MsgAssert(p_goal_editor,("No goal editor component ???"));
+	
+	return p_goal_editor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+}
diff --git a/Code/Sk/Components/GoalEditorComponent.h b/Code/Sk/Components/GoalEditorComponent.h
new file mode 100644
index 0000000..c44c88a
--- /dev/null
+++ b/Code/Sk/Components/GoalEditorComponent.h
@@ -0,0 +1,362 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       GoalEditorComponent.h
+//* OWNER:          Kendall Harrison
+//* CREATION DATE:  3/21/2003
+//****************************************************************************
+
+#ifndef __COMPONENTS_GOALEDITORCOMPONENT_H__
+#define __COMPONENTS_GOALEDITORCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_GOALEDITOR CRCD(0x81f01058,"GoalEditor")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetGoalEditorComponent() ((Obj::CGoalEditorComponent*)GetComponent(CRC_GOALEDITOR))
+#define		GetGoalEditorComponentFromObject(pObj) ((Obj::CGoalEditorComponent*)(pObj)->GetComponent(CRC_GOALEDITOR))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+
+namespace Nx
+{
+	class CModel;
+}
+
+namespace Obj
+{
+enum
+{
+	// The number of nodes used by a goal
+	MAX_POSITIONS_PER_GOAL=8,
+	// Limited to 20 because a ped will need to be created for each goal
+	// Note: If this value is changed, it would be a good idea to update VERSION_CREATEDGOALS
+	// in memcard.q too, to prevent loading old mem card saves that may have more goals than
+	// the new value allows.
+	MAX_GOALS_PER_LEVEL=10,
+	
+	// Note: MAX_POSITIONS_PER_GOAL*MAX_GOALS_PER_LEVEL nodes will get added to the NodeArray
+	
+	// The total number of goals stored in the GoalEditor composite object.
+	MAX_GOALS_TOTAL=50,
+};
+
+class CInputComponent;
+class CEditorCameraComponent;
+
+class CGoalPos
+{
+public:
+	enum EType
+	{
+		NONE,
+		PED,
+		RESTART,
+		LETTER,
+	};
+	
+	EType mType;
+	Mth::Vector mPos;
+	float mHeight;
+	float mAngle;
+};	
+
+struct SPreDefinedKeyComboSet
+{
+	uint8 mSetIndex; // Index into the cag_skatetris_key_combos array defined in goal_editor.q
+	
+	// Note: These three are not really used at the moment. They used to be settable from menus,
+	// but are now all left as zero for simplicity, with only a global spin value settable for the
+	// whole goal.
+	// So if it stays this way, there is no need for this structure, could just have a simple array
+	// of set indices instead.
+	uint8 mRequirePerfect;
+	uint8 mSpin; // Actually the spin/180
+	uint8 mNumTaps;
+};
+	
+class CEditGoal
+{
+	// Identifies which level this goal is for via the level load script name.
+	uint32 m_level_script;
+	
+	enum
+	{
+		// These needs to be at least 1 bigger than the max_length value passed to
+		// the create_onscreen_keyboard script.
+		GOAL_NAME_BUFFER_SIZE=50,
+		PED_NAME_BUFFER_SIZE=21,
+		GOAL_DESCRIPTION_BUFFER_SIZE=100,
+		GOAL_WIN_MESSAGE_BUFFER_SIZE=100,
+	};
+	char mp_goal_name[GOAL_NAME_BUFFER_SIZE];
+	char mp_ped_name[PED_NAME_BUFFER_SIZE];
+	char mp_goal_description[GOAL_DESCRIPTION_BUFFER_SIZE];
+	char mp_win_message[GOAL_WIN_MESSAGE_BUFFER_SIZE];
+
+
+	uint32 m_goal_type;
+	
+	CGoalPos mp_item_positions[MAX_POSITIONS_PER_GOAL];
+	uint8 m_num_positions_required;
+	uint8 m_num_positions_set;
+	uint8 m_current_position_index;
+	uint8 m_placed_last_position;
+	
+	uint8 m_has_won_goal;
+	
+	uint8 m_used; // Indicates whether this slot in the array of CEditGoals is used or not.
+	
+	// m_level_goal_index has a value from 0 to MAX_GOALS_PER_LEVEL-1
+	// From the level goal index is also derived the goal's id string
+	// which the goal manager needs when creating the goal ped arrows
+	// and the goal id, which is just the checksum of the id string. 
+	int m_level_goal_index;
+	
+	int m_time_limit;
+	
+	// One of skate, walk, junkercar or rallycar
+	uint32 m_control_type;
+	
+	// The following are goal specific params.
+	// Could have stored these in a CStruct instead, but that would have caused varying
+	// memory requirements depending on how many goals the user had added, making it harder to test.
+	
+	// The score value for high-score, high-combo etc.
+	int m_score;
+
+	// Skatetris specific
+	int m_acceleration_interval;
+	float m_acceleration_percent;
+	int m_trick_time;
+	int m_time_to_stop_adding_tricks;
+	int m_max_tricks;
+	
+	int m_spin;
+
+	// Combo skatetris specific
+	bool m_single_combo;
+	int m_combo_size;
+
+	// Tricktris specific
+	int m_tricktris_block_size;
+	int m_tricktris_total_to_win;
+	
+	enum
+	{
+		MAX_COMBO_SETS=10
+	};	
+	SPreDefinedKeyComboSet mp_combo_sets[MAX_COMBO_SETS];
+	int m_num_combo_sets;
+	// Used for writing/reading the above array to mem card.
+	void write_combo_sets(Script::CStruct *p_info);
+	void read_combo_sets(Script::CStruct *p_info);
+	
+	// Generates the goal_tetris_key_combos array and inserts it into p_info, for sending to
+	// the goal manager.
+	void generate_key_combos_array(Script::CStruct *p_info);
+	
+	
+	enum
+	{
+		MAX_GAPS=10,
+		MAX_GAP_TRICK_NAME_CHARS=50,
+	};
+	uint8 mp_gap_numbers[MAX_GAPS];
+	uint8 m_num_gaps;
+	char mp_trick_name[MAX_GAP_TRICK_NAME_CHARS+1];
+	
+	
+	void write_position_into_node(uint index, uint32 nodeName);
+	void add_goal_params(Script::CStruct *p_params);
+	uint32 generate_marker_object_name(int index);
+	uint32 generate_goal_object_name(int index);
+	void create_marker(int index);
+	void delete_marker(int index);
+	bool position_too_close_to_existing_positions(const Mth::Vector& pos);
+	bool position_ok_to_place(const Mth::Vector& pos, float height);
+
+	void refresh_position_using_collision_check(uint positionIndex);
+
+public:
+	enum EWriteDest
+	{
+		NOT_WRITING_TO_GOAL_MANAGER=0,
+		WRITING_TO_GOAL_MANAGER=1,
+	};	
+private:
+	
+	// The dest parameter determines the format in which the combo sets are written out.
+	// When generating params for the goal manager, the full goal_tetris_key_combos array is
+	// generated. When writing to mem card the chosen pre-defined set numbers are written instead,
+	// so that after loading they can still be edited.
+	void write_skatetris_params(Script::CStruct *p_info, EWriteDest dest);
+	void read_skatetris_params(Script::CStruct *p_info);
+	
+public:
+	CEditGoal();
+	~CEditGoal();
+	
+	void Clear();
+
+	#ifdef SIZE_TEST	
+	// For testing, this fills the name buffers, eg mp_goal_name, mp_trick_name etc, to their
+	// max size.
+	void FillNameBuffers();
+	uint32 GetSkateGoalMaxSize();
+	#endif
+	
+	bool Used() {return m_used;}
+	void SetUsedFlag(bool used) {m_used=used;}
+	
+	void SetLevelGoalIndex(uint index) {m_level_goal_index=index;}
+	int GetLevelGoalIndex() {return m_level_goal_index;}
+	
+	uint32 GetId();
+	const char *GetIDString();
+	
+	bool SetPosition(const Mth::Vector& pos, float height, float angle);
+	bool GetPosition(Mth::Vector *p_pos, float *p_height, float *p_angle);
+	CGoalPos::EType GetPositionType();
+	void GetPosition(Mth::Vector *p_pos, int index);
+	int GetNumPositionsSet() {return m_num_positions_set;}
+	
+	bool BackUp(const Mth::Vector& pos, float height, float angle);
+	bool GotAllPositions();
+	uint GetCurrentLetterIndex();
+	bool PlacedLastPosition() {return m_placed_last_position;}
+	
+	void SetGoalDescription(const char *p_text);
+	void SetWinMessage(const char *p_text);
+	void SetGoalName(const char *p_name);
+	void SetPedName(const char *p_name);
+	bool PositionClashesWithExistingPositions(Mth::Vector& pos);
+	
+	int  GetScore() {return m_score;}
+	void SetScore(int score) {m_score=score;}
+	void SetTimeLimit(int timeLimit);
+	void SetControlType(uint32 controlType);
+	
+	void EditGoal();
+
+	void WriteIntoStructure(Script::CStruct *p_info);
+	void ReadFromStructure(Script::CStruct *p_info);
+
+	void WriteGoalSpecificParams(Script::CStruct *p_info, EWriteDest dest=NOT_WRITING_TO_GOAL_MANAGER);
+	void ReadGoalSpecificParams(Script::CStruct *p_params);
+	void RemoveGoalSpecificFlag(Script::CStruct *p_params);
+	
+	uint32 GetType() {return m_goal_type;}
+	void SetType(uint32 type);
+	void SetLevel(uint32 levelScript) {m_level_script=levelScript;}
+	uint32 GetLevel() {return m_level_script;}
+	const char *GetGoalName() {return mp_goal_name;}
+	const char *GetPedName() {return mp_ped_name;}
+	void FlagAsWon() {m_has_won_goal=true;}
+	
+	void AddKeyComboSet(int setIndex, bool requirePerfect, int spin, int numTaps);
+	void RemoveKeyComboSet(int setIndex);
+	SPreDefinedKeyComboSet *GetKeyComboSet(int setIndex);
+	
+	void AddGap(uint8 gap_number);
+	void RemoveGap(uint8 gap_number);
+	void RemoveGapAndReorder(uint8 gap_number);
+	bool GotGap(uint8 gap_number);
+	
+	void AddGoalToGoalManager(bool markUnbeaten=false);
+	void WriteNodePositions();
+	
+	bool GoalIsOutsideArea(float x0, float z0, float x1, float z1);
+	void RefreshPositionsUsingCollisionCheck();
+	
+	void GetDebugInfo(Script::CStruct* p_info);
+};
+
+class CGoalEditorComponent : public CBaseComponent
+{
+	CEditGoal 	mp_goals[MAX_GOALS_TOTAL];
+	CEditGoal 	*mp_current_goal;
+
+	CInputComponent 		*mp_input_component;
+	CEditorCameraComponent 	*mp_editor_camera_component;
+	void get_pointers_to_required_components();
+	Obj::CCompositeObject *get_cursor();
+	void refresh_cursor_object();
+
+	void get_pos_from_camera_component(Mth::Vector *p_pos, float *p_height, float *p_angle);
+	void update_cursor_collision_type();
+	
+	CEditGoal *find_goal(const char *p_name);
+	CEditGoal *find_goal(uint32 id);
+	CEditGoal *find_goal(Script::CStruct *p_params);
+	
+	void update_camera_pos();
+	void update_cursor_position();
+	void remove_goal(CEditGoal *p_goal);
+	bool too_close_to_another_goal_position(const Mth::Vector &pos, float height, uint32 level, int positionIndex);
+	
+public:
+    CGoalEditorComponent();
+    virtual ~CGoalEditorComponent();
+
+public:
+	void							ClearAllExceptParkGoals();
+	void							ClearOnlyParkGoals();
+	
+	void							CleanOutUnfinishedGoals();
+	void							NewGoal();
+	void							SetGoalType(uint32 type);
+	void							AddGoalsToGoalManager();
+	void							WriteNodePositions();
+	int								GetNumGoals();
+	bool							ThereAreGoalsOutsideArea(float x0, float z0, float x1, float z1);
+	void							DeleteGoalsOutsideArea(float x0, float z0, float x1, float z1);
+	int 							CountGoalsForLevel(uint32 level);
+	
+	void							RefreshGoalPositionsUsingCollisionCheck();
+	void							RefreshGapGoalsAfterGapRemovedFromPark(int gapNumber);
+	
+	
+	// For reading/writing to memcard
+	void							WriteIntoStructure(Script::CStruct *p_info);
+	enum EBoolLoadingParkGoals
+	{
+		NOT_LOADING_PARK_GOALS=0,
+		LOADING_PARK_GOALS=1,
+	};	
+	void							ReadFromStructure(Script::CStruct *p_info, EBoolLoadingParkGoals loadingParkGoals=NOT_LOADING_PARK_GOALS);
+	
+	void							WriteIntoStructure(uint32 levelScript, Script::CStruct *p_info);
+	uint32							WriteToBuffer(uint32 levelScript, uint8 *p_buffer, uint32 bufferSize);
+	uint8 *							ReadFromBuffer(uint32 levelScript, uint8 *p_buffer);
+	
+
+	virtual	void 					Finalize();
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+	virtual void					Hide( bool shouldHide );
+
+	static CBaseComponent*			s_create();
+};
+
+//char p[sizeof(CGoalEditorComponent)/0];
+
+void InsertGoalEditorNodes();
+Obj::CGoalEditorComponent *GetGoalEditor();
+
+} // namespace Obj
+
+#endif
diff --git a/Code/Sk/Components/ProjectileCollisionComponent.cpp b/Code/Sk/Components/ProjectileCollisionComponent.cpp
new file mode 100644
index 0000000..576fc00
--- /dev/null
+++ b/Code/Sk/Components/ProjectileCollisionComponent.cpp
@@ -0,0 +1,380 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       ProjectileCollisionComponent.cpp
+//* OWNER:          SPG
+//* CREATION DATE:  7/10/03
+//****************************************************************************
+
+// The CEmptyComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy emptycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "Empty" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define vSKATER_RADIUS	FEET( 4 )
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CProjectileCollisionComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CProjectileCollisionComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CProjectileCollisionComponent::CProjectileCollisionComponent() : CBaseComponent()
+{
+    SetType( CRC_PROJECTILECOLLISION );
+	m_radius = 0.0f;
+	m_owner_id = 0;
+	m_death_script = 0;
+	m_scale = 0;
+	m_dying = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CProjectileCollisionComponent::~CProjectileCollisionComponent()
+{   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProjectileCollisionComponent::SetCollisionRadius( float radius )
+{
+	m_radius = radius;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CProjectileCollisionComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	pParams->GetFloat(CRCD(0xc48391a5,"radius"), &m_radius, Script::ASSERT);
+	pParams->GetFloat(CRCD(0x13b9da7b,"scale"), &m_scale, Script::ASSERT);
+	pParams->GetChecksum(CRCD(0x81c39e06,"owner_id"), &m_owner_id, Script::ASSERT);
+	pParams->GetChecksum(CRCD(0x6647adc3,"death_script"), &m_death_script, Script::ASSERT);
+
+	m_vel.Set( 0, 0, 1 );
+	pParams->GetVector(CRCD(0xc4c809e, "vel"), &m_vel, Script::ASSERT);
+	m_vel.Normalize();
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CProjectileCollisionComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	//InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProjectileCollisionComponent::Finalize()
+{
+	mp_suspend_component =  GetSuspendComponentFromObject( GetObject() );
+}
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CProjectileCollisionComponent::Hide( bool should_hide )
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CProjectileCollisionComponent::Update()
+{
+	float radii;
+
+	radii = m_radius + vSKATER_RADIUS;
+	if (!mp_suspend_component->SkipLogic() && !m_dying )
+	{
+		float dist;
+		bool team_game;
+		Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+		GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+		uint32 NumSkaters = skate_mod->GetNumSkaters();
+		GameNet::PlayerInfo* target_player, *src_player;
+
+		team_game = skate_mod->GetGameMode()->IsTeamGame();
+		for( uint32 i = 0; i < NumSkaters; i++ )
+		{
+			Obj::CSkater *pSkater = skate_mod->GetSkater(i);
+			Obj::CSkaterScoreComponent* p_score_comp;
+			Mdl::Score* score;
+
+			// Can't hit yourself with your own projectile
+			if( pSkater->GetID() == m_owner_id )
+			{
+				continue;
+			}
+
+			src_player = gamenet_man->GetPlayerByObjectID( m_owner_id );
+			target_player = gamenet_man->GetPlayerByObjectID( pSkater->GetID());
+			
+			// Allow players to get up after being knocked down
+			if( target_player->IsVulnerable() == false )
+			{
+				continue;
+			}
+
+			p_score_comp = GetSkaterScoreComponentFromObject( pSkater );
+			score = p_score_comp->GetScore();
+
+			// Don't target dead players
+			if( score->GetTotalScore() <= 0 )
+			{
+				continue;
+			}
+
+			if( team_game )
+			{
+				if( src_player->m_Team == target_player->m_Team )
+				{
+					uint32 enabled;
+
+					enabled = gamenet_man->GetNetworkPreferences()->GetPreferenceChecksum( CRCD(0xe959c43a,"friendly_fire"), CRCD(0x21902065,"checksum"));
+					if( enabled != CRCD(0xf81bc89b,"boolean_true"))
+					{
+						continue;
+					}
+				}
+			}
+
+			dist = (pSkater->GetPos() - GetObject()->GetPos()).Length();
+			if( dist < radii )
+			{
+				GameNet::MsgProjectileHit hit_msg;
+				Net::MsgDesc msg_desc;
+				Net::Server* server;
+				GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+				server = gamenet_man->GetServer();
+
+				//Dbg_Printf( "COLLISION: Skater %d: owner: %d\n", pSkater->GetID(), m_owner_id);
+				GetObject()->MarkAsDead();
+				if( m_death_script != 0 )
+				{
+					Script::CStruct* params;
+					Mth::Vector pos;
+
+					pos = GetObject()->GetPos();
+					params = new Script::CStruct;
+					
+					params->AddVector( CRCD(0x7f261953,"pos"), pos );
+					params->AddVector( CRCD(0xc4c809e,"vel"), m_vel );
+					params->AddFloat( CRCD(0x13b9da7b,"scale"), m_scale );
+
+					Script::RunScript( m_death_script, params );
+					delete params;
+				}
+	
+				// a message explaining "You've been hit down by ID for N damage"
+				hit_msg.m_Id = m_owner_id;
+				hit_msg.m_Damage = get_damage_amount();
+	
+				/*if (CInputComponent* p_input_component = GetInputComponentFromObject(GetObject()))
+				{
+					p_input_component->DisableInput();
+				}
+				p_player->MarkAsNonCollidable();*/
+				
+				msg_desc.m_Id = GameNet::MSG_ID_SKATER_HIT_BY_PROJECTILE;
+				msg_desc.m_Data = &hit_msg;
+				msg_desc.m_Length = sizeof(GameNet::MsgProjectileHit);
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+				target_player->SetHitTime( Tmr::GetTime());
+				server->EnqueueMessage( target_player->GetConnHandle(), &msg_desc );
+	
+				// basically a message explaining "You hit someone"
+				hit_msg.m_Id = pSkater->GetID();
+				msg_desc.m_Id = GameNet::MSG_ID_SKATER_PROJECTILE_HIT_TARGET;
+				server->EnqueueMessage( src_player->GetConnHandle(), &msg_desc );	
+				
+				
+				if(( score->GetTotalScore() - get_damage_amount() ) <= 0 )
+				{
+					// If this shot is killing them...
+					if( score->GetTotalScore() > 0 )
+					{
+						if( target_player->IsLocalPlayer() == false )
+						{
+							Script::CStruct* params;
+	
+							Dbg_Printf( "*** Elimination\n" );
+	
+							params = new Script::CStruct;
+							params->AddString( CRCD(0xa1dc81f9,"name"), pSkater->GetDisplayName());
+							Script::RunScript( CRCD(0x9b043179,"announce_elimination"), params );
+							delete params;
+							skate_mod->HideSkater( pSkater, true );
+						}
+					}
+					score->SetTotalScore( 0 );
+				}
+				else
+				{
+					score->SetTotalScore( score->GetTotalScore() - get_damage_amount());
+				}
+				
+				break;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CProjectileCollisionComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProjectileCollisionComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CProjectileCollisionComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+	
+
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int	CProjectileCollisionComponent::get_damage_amount( void )
+{
+	return (int) m_scale * 10;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CProjectileCollisionComponent::MarkAsDying( void )
+{
+	m_dying = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Sk/Components/ProjectileCollisionComponent.h b/Code/Sk/Components/ProjectileCollisionComponent.h
new file mode 100644
index 0000000..29b43ad
--- /dev/null
+++ b/Code/Sk/Components/ProjectileCollisionComponent.h
@@ -0,0 +1,70 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       ProjectileCollisionComponent.h
+//* OWNER:          SPG
+//* CREATION DATE:  07/14/03
+//****************************************************************************
+
+#ifndef __COMPONENTS_PROJECTILECOLLISIONCOMPONENT_H__
+#define __COMPONENTS_PROJECTILECOLLISIONCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_PROJECTILECOLLISION CRCD(0x7767d6d7,"ProjectileCollision")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetProjectileCollisionComponent() ((Obj::CProjectileCollisionComponent*)GetComponent(CRC_PROJECTILECOLLISION))
+#define		GetProjectileCollisionComponentFromObject(pObj) ((Obj::CProjectileCollisionComponent*)(pObj)->GetComponent(CRC_PROJECTILECOLLISION))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CProjectileCollisionComponent : public CBaseComponent
+{
+public:
+    CProjectileCollisionComponent();
+    virtual ~CProjectileCollisionComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void					Hide( bool should_hide );
+	virtual void					Finalize();
+	
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+
+	void							SetCollisionRadius( float radius );
+	void							MarkAsDying( void );
+
+private:
+	int								get_damage_amount( void );
+
+	CSuspendComponent*	mp_suspend_component;
+
+	float				m_radius;	// Radius for spherical collision detection
+	float				m_scale;
+	uint32				m_death_script;
+	uint32				m_owner_id;
+	Mth::Vector			m_vel;
+	bool				m_dying;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/RailEditorComponent.cpp b/Code/Sk/Components/RailEditorComponent.cpp
new file mode 100644
index 0000000..3a94d0b
--- /dev/null
+++ b/Code/Sk/Components/RailEditorComponent.cpp
@@ -0,0 +1,2898 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       RailEditorComponent.cpp
+//* OWNER:          Kendall Harrison
+//* CREATION DATE:  3/21/2003
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+DefinePoolableClass(Obj::CEditedRailPoint);
+DefinePoolableClass(Obj::CEditedRail);
+
+// TODO: The m_mode member could in theory be removed, just use the tags to store it instead.
+// Hence could remove the SetEditingMode and GetEditingMode functions.
+// (Nice to have as much logic moved to script as possible)
+
+namespace Obj
+{
+Mth::Vector ZeroVector;
+
+#define RAIL_SECTOR_HEIGHT 2.625f
+
+static uint32 s_rail_unique_id=1;
+static int s_highlight_flash_counter=0;
+
+// This will rotate pos-rotateCentre by degrees about Y, add the result to newCentre, and return the result.
+// Needed by the clipboard when displaying the copied rails as they rotate with the cursor.
+static Mth::Vector s_rotate_and_translate(Mth::Vector& pos, Mth::Vector& rotateCentre, Mth::Vector& newCentre, float degrees)
+{
+	Mth::Vector d=pos-rotateCentre;
+	
+	float rad=degrees*3.141592654f/180.0f;
+	float s=sinf(rad);
+	float c=cosf(rad);
+
+	Mth::Vector rotated;
+	rotated[X]=c*d[X]+s*d[Z];
+	rotated[Y]=d[Y];
+	rotated[Z]=c*d[Z]-s*d[X];
+	
+	return newCentre+rotated;
+}
+
+// SPEEDOPT: Pass false to updateSuperSectors when creating a batch of sectors, and call
+// UpdateSuperSectors afterwards.
+static Nx::CSector *s_clone_sector(uint32 sector_name)
+{
+	Nx::CScene *p_source_scene=Nx::CEngine::sGetScene("sk5ed");
+	if (!p_source_scene)
+	{
+		return NULL;
+	}
+		
+	Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+	if (!p_cloned_scene)
+	{
+		return NULL;
+	}
+		
+	uint32 new_sector_checksum = p_source_scene->CloneSector(sector_name, p_cloned_scene, false, true);
+	
+	// SPEEDOPT: If a batch of sectors are cloned, only do this once at the end.
+	p_cloned_scene->UpdateSuperSectors();
+	
+	Nx::CSector *p_new_sector = Nx::CEngine::sGetSector(new_sector_checksum);
+
+	return p_new_sector;
+}
+
+
+static void s_generate_end_vert_positions(Mth::Vector &pos, Mth::Vector &dir, 
+										  Mth::Vector *p_geom_verts,
+										  int *p_vert_indices,
+										  int numIndices)
+{
+	// Need to copy the origin into a local var cos the source is about to be modified.
+	Mth::Vector origin=p_geom_verts[p_vert_indices[0]];
+	
+	Mth::Vector up(0.0f,1.0f,0.0f);
+	Mth::Vector u=dir;
+	u.Normalize();
+	
+	Mth::Vector w;
+	if (fabs(u[Y]) > 0.99999f)
+	{
+		w.Set(0.0f,0.0f,1.0f);
+	}
+	else
+	{
+		w=Mth::CrossProduct(up,u);
+		w.Normalize();
+	}	
+	
+	Mth::Vector v=Mth::CrossProduct(u,w);
+	v.Normalize();
+	Mth::Matrix rot;
+	rot[Mth::RIGHT]=u;
+	rot[Mth::UP]=v;
+	rot[Mth::AT]=w;
+	rot[3][3]=1.0f;
+	
+	for (int i=0; iGetGeom();
+	Dbg_MsgAssert(p_source_geom,("NULL p_source_geom ?"));
+	
+	int num_render_verts=p_source_geom->GetNumRenderVerts();
+	Dbg_MsgAssert(num_render_verts==num_indices*2,("Unexpected extra vertices in rail sector, expected %d, got %d",2*num_indices,num_render_verts));
+	
+	// SPEEDOPT: If necessary, could use a static buffer for the verts
+	Mth::Vector *p_modified_render_verts=(Mth::Vector*)Mem::Malloc(num_render_verts * sizeof(Mth::Vector));
+	p_source_geom->GetRenderVerts(p_modified_render_verts);
+
+	#ifdef __NOPT_ASSERT__
+	for (int i=0; iGetGeom();
+		Dbg_MsgAssert(p_last_geom,("NULL p_last_geom ?"));
+		
+		int last_num_render_verts=p_last_geom->GetNumRenderVerts();
+		// SPEEDOPT: If necessary, could use a static buffer for the verts
+		Mth::Vector *p_last_verts=(Mth::Vector*)Mem::Malloc(last_num_render_verts * sizeof(Mth::Vector));
+		p_last_geom->GetRenderVerts(p_last_verts);
+
+		// Tie the end verts to the new start verts.
+		for (int i=0; iSetRenderVerts(p_last_verts);
+
+		Mem::Free(p_last_verts);
+	}
+	else
+	{
+		dir=endPos-startPos;
+		s_generate_end_vert_positions(startPos, dir, p_modified_render_verts, p_end_verts_a, num_indices);
+	}	
+	
+	dir=endPos-startPos;
+	s_generate_end_vert_positions(endPos, dir, p_modified_render_verts, p_end_verts_b, num_indices);
+	
+
+	// Write the vertex coords just calculated into the cloned sector.
+	Dbg_MsgAssert(p_clonedSector,("NULL p_clonedSector"));
+	Nx::CGeom *p_geom=p_clonedSector->GetGeom();
+	Dbg_MsgAssert(p_geom,("NULL p_geom ?"));
+	Dbg_MsgAssert(p_geom->GetNumRenderVerts()==p_source_geom->GetNumRenderVerts(),("Source geom num verts mismatch"));
+	p_geom->SetRenderVerts(p_modified_render_verts);
+
+	/////////////////////////////////////////////////////////////////
+	// Now update the collision verts
+	/////////////////////////////////////////////////////////////////
+	
+	if (update_collision) // Uncomment this once the rail pieces have been reexported with correct collision
+	{
+		// Get the source render verts again so that we can match up the source collision verts with them, and hence get
+		// the index of the modified render vert to use for each collision vert.
+		Mth::Vector *p_source_render_verts=(Mth::Vector*)Mem::Malloc(num_render_verts * sizeof(Mth::Vector));
+		p_source_geom->GetRenderVerts(p_source_render_verts);
+		// The source render verts are in relative coords.
+	
+		// Get the source collision verts, which will also be in relative coords.
+		Nx::CCollObjTriData *p_source_col_data=p_source_geom->GetCollTriData();
+		int num_source_col_verts=p_source_col_data->GetNumVerts();
+
+		Mth::Vector *p_collision_verts=(Mth::Vector*)Mem::Malloc(num_source_col_verts * sizeof(Mth::Vector));
+		p_source_col_data->GetRawVertices(p_collision_verts);
+	
+		// For each of the collision verts, look for a matching coord in the source render verts,
+		// and if found, write in the world coords calculated for it earlier.
+		for (int i=0; iGetCollTriData();
+		Dbg_MsgAssert(p_dest_col_data->GetNumVerts()==num_source_col_verts,("Bad p_dest_col_data->GetNumVerts() ?"));
+		#endif
+
+		p_clonedSector->SetRawCollVertices(p_collision_verts);
+		Mem::Free(p_collision_verts);
+		Mem::Free(p_source_render_verts);
+	}
+	
+	Mem::Free(p_modified_render_verts);	
+} 
+
+// Returns true if the skater will be able to grind from a to b to c without being forced off
+// due to the angle being too big.
+static bool s_angle_is_ok_to_grind(Mth::Vector &a, Mth::Vector &b, Mth::Vector &c)
+{
+	Mth::Vector ab=b-a;
+	ab[Y]=0.0f;
+	ab.Normalize();
+	
+	Mth::Vector bc=c-b;
+	bc[Y]=0.0f;
+	bc.Normalize();
+	
+	float cosine=Mth::DotProduct(ab,bc);
+	if (cosine < cosf(Mth::DegToRad(Script::GetFloat(CRCD(0x76c1da15,"Rail_Corner_Leave_Angle")))))
+	{
+		return false;
+	}
+	
+	return true;	
+}
+
+static bool s_distance_is_too_long(Mth::Vector &a, Mth::Vector &b)
+{
+	Mth::Vector d=a-b;
+	// The distance at which the dotted line will flash red to indicate the distance is too long
+	return d.Length() > 4000.0f;
+}
+
+static bool s_distance_is_way_too_long(Mth::Vector &a, Mth::Vector &b)
+{
+	Mth::Vector d=a-b;
+	// The distance at which the dotted line will disappear completely to avoid rendering problems.
+	return d.Length() > 4500.0f;
+}
+
+static bool s_positions_OK(Mth::Vector &a, Mth::Vector &b)
+{
+	if (s_distance_is_too_long(a, b))
+	{
+		return false;
+	}
+	
+	Mth::Vector diff=a-b;
+	
+	// Don't allow consecutive points to be placed too close, otherwise the user may accidentally
+	// place points on top of each other, & it could cause weirdness in the calculations due
+	// to zero vectors trying to get normalized etc.
+	if (diff.Length() < Script::GetFloat(CRCD(0x8bea1c03,"RailEditorMinimumPointSeparation")))
+	{
+		return false;
+	}
+	
+	// Also don't allow the rail segment to get too steep.
+	diff.Normalize();
+	if (fabs(diff[Y]) > sinf(Mth::DegToRad(Script::GetFloat(CRCD(0x9fd761af,"RailEditorMaxSlope")))))
+	{
+		return false;
+	}
+	
+	return true;
+}
+										  
+CEditedRailPoint::CEditedRailPoint()
+{
+	mPos.Set();
+	mHasPost=false;
+	mHighlighted=false;
+	mHeightAboveGround=0.0f;
+	mpNext=NULL;
+	mpPrevious=NULL;
+	mpClonedRailSector=NULL;
+	mpPostSector=NULL;
+}
+
+CEditedRailPoint::~CEditedRailPoint()
+{
+	DestroyRailGeometry();
+	DestroyPostGeometry();
+}
+
+// Creates the rail sector if it does not exist already, then writes the world
+// positions into the vertices.
+// rotateCentre, newCentre, and degrees allow the rail to be displayed rotated and translated from its original
+// position. This is used when displaying a rail section on the clipboard cursor in the park editor.
+void CEditedRailPoint::UpdateRailGeometry(Mth::Vector& rotateCentre, Mth::Vector& newCentre, float degrees)
+{
+	// Note: The last point that was placed is actually the next in the list, since they get added to the front.
+	if (!mpNext)
+	{
+		// There is no last point, so a rail can't be created because there is nothing to join to.
+		
+		// TODO: Could maybe still make a small section of rail, so that the first post of a rail has
+		// a bit of rail overhanging it. Would look better than having the rail stop right above the post.
+		
+		// Make sure any existing rail is destroyed.
+		DestroyRailGeometry();
+		return;
+	}
+
+	// Clone the sector if necessary
+	if (!mpClonedRailSector)
+	{
+		mpClonedRailSector=s_clone_sector(CRCD(0x66fe99bf,"Sk5Ed_RA_Dynamic"));
+	}
+	
+	Mth::Vector a, b, c;
+	
+	// Don't tie the end vertices of the two rail sections together if they meet at too great
+	// an angle, cos otherwise one of the sections gets too thin.
+	bool join_to_last_sector=true;
+	if (mpNext->mpNext)
+	{
+		a=mpNext->mPos-mpNext->mpNext->mPos;
+		b=mPos-mpNext->mPos;
+		a.Normalize();
+		b.Normalize();
+		if (Mth::DotProduct(a,b) < cosf(Mth::DegToRad(Script::GetFloat(CRCD(0xb1459a78,"RailEditorMaxJoinAngle")))))
+		{
+			join_to_last_sector=false;
+		}	
+
+		a=s_rotate_and_translate(mpNext->mpNext->mPos, rotateCentre, newCentre, degrees);
+		b=s_rotate_and_translate(mpNext->mPos, rotateCentre, newCentre, degrees);
+		c=s_rotate_and_translate(mPos, rotateCentre, newCentre, degrees);
+		
+		s_calculate_rail_sector_vertex_coords(a, b, c,
+											  mpClonedRailSector, CRCD(0x66fe99bf,"Sk5Ed_RA_Dynamic"),
+											  join_to_last_sector ? mpNext->mpClonedRailSector:NULL, true);
+	}
+	else
+	{
+		Dbg_MsgAssert(mpNext->mpClonedRailSector==NULL,("Expected mpNext->mpClonedRailSector to be NULL ?"));
+		
+		Mth::Vector dummy;
+		a=s_rotate_and_translate(mpNext->mPos, rotateCentre, newCentre, degrees);
+		b=s_rotate_and_translate(mPos, rotateCentre, newCentre, degrees);
+		s_calculate_rail_sector_vertex_coords(dummy, a, b, 
+											  mpClonedRailSector, CRCD(0x66fe99bf,"Sk5Ed_RA_Dynamic"),
+											  NULL, true);
+	}											  
+}
+
+// Creates the post sector if it does not exist already, then writes the correct world
+// positions into the vertices.
+// This is called when the post is first created, and also gets called for all posts whenever a cell in
+// the park is raised or lowered, so that the posts maintain contact with the ground.
+
+// rotateCentre, newCentre, and degrees allow the post to be displayed rotated and translated from its original
+// position. This is used when displaying a rail section on the clipboard cursor in the park editor.
+void CEditedRailPoint::UpdatePostGeometry(Mth::Vector& rotateCentre, Mth::Vector& newCentre, float degrees)
+{
+	if (!mHasPost)
+	{
+		DestroyPostGeometry();
+		return;
+	}
+	
+	// Don't place a post if the rail is so close to the ground that there is not enough room for a post.
+	// This often happens when snapping a rail point to the edge of some level geometry for example.	
+	if (mHeightAboveGround < RAIL_SECTOR_HEIGHT)
+	{
+		DestroyPostGeometry();
+		return;
+	}
+	
+	// Get the coords of the source sectors vertices. These will be relative to the sectors origin,
+	// which is in the middle of the post.
+	Nx::CSector *p_source_sector=Nx::CEngine::sGetSector(CRCD(0x2756c52d,"Sk5Ed_RAp_Dynamic"));
+	Nx::CGeom *p_source_geom=p_source_sector->GetGeom();
+	Dbg_MsgAssert(p_source_geom,("NULL p_source_geom ?"));
+	
+	int num_verts=p_source_geom->GetNumRenderVerts();
+	// SPEEDOPT: If necessary, could use a static buffer for the verts
+	Mth::Vector *p_verts=(Mth::Vector*)Mem::Malloc(num_verts * sizeof(Mth::Vector));
+	p_source_geom->GetRenderVerts(p_verts);
+
+	// Find the y coords of the top and bottom
+	float min_y=1000000.0f;
+	float max_y=-1000000.0f;
+	for (int i=0; i < num_verts; ++i)
+	{
+		float y=p_verts[i].GetY();
+		if (y < min_y)
+		{
+			min_y=y;
+		}
+		if (y > max_y)
+		{
+			max_y=y;
+		}	
+	}
+	
+	// Check that max_y and min_y are the same, just opposite in sign.
+	Dbg_MsgAssert(fabs(max_y-(-min_y)) < 0.01f,("Expected post geometry to have origin in centre, but min_y=%f, max_y=%f",min_y,max_y));
+	Dbg_MsgAssert(max_y > 0.0f,("max_y < 0 ??  (max_y=%f)",max_y));
+	
+	float half_post_height=max_y;
+	
+	float rail_sector_height=RAIL_SECTOR_HEIGHT;
+	// If the next post is at a different height, the height of the rail be a bit smaller than normal
+	// due to the end face being tilted to point towards the next post.
+	if (mpNext && mpPrevious)
+	{
+		Mth::Vector a=mpPrevious->mPos-mPos;
+		Mth::Vector b=mPos-mpNext->mPos;
+		// The end-face's normal will be the average of the two rails
+		Mth::Vector av=(a+b)/2.0f;
+		av.Normalize();
+		// av[Y] is the sine of the angle, but we want the cosine
+		rail_sector_height *= sqrtf(1.0f-av[Y]*av[Y]);
+	}	
+
+	Mth::Vector translated_rail_point=s_rotate_and_translate(mPos, rotateCentre, newCentre, degrees);
+	
+	// Translate the post so that it is centred on the rail point, then shift the y's down so that the
+	// top of the post is at the bottom of the rail piece.
+	// The height of the rail section needs to be added too because the rail point (mPos) is at the apex of the
+	// rail section.
+	float shift_down_amount=half_post_height+rail_sector_height;
+	for (int i=0; i < num_verts; ++i)
+	{
+		p_verts[i]+=translated_rail_point;
+		p_verts[i][Y]-=shift_down_amount;
+	}
+
+	// The top of the post is now in the correct position.
+	// Now we need to move the bottom vertices down to ground level.
+	
+	// This array figured out manually, by printing out the vertex coords.
+	// TODO: These indices will need to be different on Xbox and GameCube
+#	ifdef __PLAT_NGC__
+	int p_bottom_vert_indices[]=
+	{
+		0,2,4,6,		// Post bottom
+		8,9,10,11		// Base plate
+	};
+#else
+#	ifdef __PLAT_XBOX__
+	int p_bottom_vert_indices[]=
+	{
+		8,9,10,11,		// Post bottom
+		1,2,5,6			// Base plate
+	};
+#	else
+	int p_bottom_vert_indices[]=
+	{
+		1,3,5,7,9,		// Post bottom
+		10,11,12,13		// Base plate
+	};
+#	endif
+#	endif
+	int num_bottom_vertices=sizeof(p_bottom_vert_indices)/sizeof(int);
+	Dbg_MsgAssert(num_bottom_vertices <= num_verts,("Too many bottom vertices!"));
+
+	// The amount that the bottom vertices need to be moved down is the height, minus the
+	// previous shift_down_amount cos we've already moved them by that much, minus
+	// half the height of the post so that the bottom is on the ground.
+	shift_down_amount=mHeightAboveGround-shift_down_amount-half_post_height;
+	
+	for (int i=0; imPos;
+		u[Y]=0.0f;
+		u.Normalize();
+		float ux=u[X];
+		float uz=u[Z];
+		float vx=-uz;
+		float vz=ux;
+
+		for (int i=0; i < num_verts; ++i)
+		{
+			float dx=p_verts[i][X]-translated_rail_point[X];
+			float dz=p_verts[i][Z]-translated_rail_point[Z];
+			p_verts[i][X]=ux*dx+vx*dz+translated_rail_point[X];
+			p_verts[i][Z]=uz*dx+vz*dz+translated_rail_point[Z];
+		}			
+	}
+
+	// Clone the sector if necessary and write in the vertex coords just calculated.
+	if (!mpPostSector)
+	{
+		mpPostSector=s_clone_sector(CRCD(0x2756c52d,"Sk5Ed_RAp_Dynamic"));
+	}
+	
+	Nx::CGeom *p_geom=mpPostSector->GetGeom();
+	Dbg_MsgAssert(p_geom,("NULL p_geom ?"));
+	Dbg_MsgAssert(p_geom->GetNumRenderVerts()==p_source_geom->GetNumRenderVerts(),("Source geom num verts mismatch"));
+	p_geom->SetRenderVerts(p_verts);
+	Mem::Free(p_verts);	
+}
+
+void CEditedRailPoint::DestroyRailGeometry()
+{
+	if (mpClonedRailSector)
+	{
+		Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+		Dbg_MsgAssert(p_cloned_scene,("Missing cloned scene!"));
+		p_cloned_scene->DeleteSector(mpClonedRailSector);
+		mpClonedRailSector=NULL;
+
+		#ifdef __PLAT_NGC__
+		Nx::CEngine::sFinishRendering();
+		#endif
+
+		// SPEEDOPT: If a batch of things are deleted, only do this once at the end.
+		if (CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors)
+		{
+			p_cloned_scene->UpdateSuperSectors();
+		}	
+	}
+}
+
+void CEditedRailPoint::DestroyPostGeometry()
+{
+	if (mpPostSector)
+	{
+		Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+		Dbg_MsgAssert(p_cloned_scene,("Missing cloned scene!"));
+		p_cloned_scene->DeleteSector(mpPostSector);
+		mpPostSector=NULL;
+		
+		#ifdef __PLAT_NGC__
+		Nx::CEngine::sFinishRendering();
+		#endif
+		
+		// SPEEDOPT: If a batch of things are deleted, only do this once at the end.
+		if (CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors)
+		{
+			p_cloned_scene->UpdateSuperSectors();
+		}	
+	}
+}
+
+// Does a drop down collision check to find the height of mPos above the ground.
+float CEditedRailPoint::FindGroundY()
+{
+	Mth::Vector off(0.0f,10000.0f,0.0f,0.0f);
+	
+	CFeeler feeler;
+	feeler.SetStart(mPos + off );
+	feeler.SetEnd(mPos - off );
+	if (feeler.GetCollision())
+	{
+		return feeler.GetPoint().GetY();
+	}
+	else
+	{
+		return 0.0f;
+	}	
+}
+
+// This will adjust mPos[Y] to be mHeightAboveGround above the ground.
+// This is used to update the rails whenever the ground is raised or lowered.
+// The rails will maintain the post heights if a post was on the ground
+// that moved.
+void CEditedRailPoint::AdjustY()
+{
+	if (mHasPost)
+	{
+		mPos[Y]=FindGroundY()+mHeightAboveGround;
+	}	
+}
+
+void CEditedRailPoint::InitialiseHeight()
+{
+	mHeightAboveGround=mPos[Y]-FindGroundY();
+}
+
+void CEditedRailPoint::Highlight(EFlash flash, EEndPosts includeEndPosts)
+{
+	if (mHighlighted)
+	{
+		return;
+	}
+		
+	uint32 vis=0xffffffff;
+	if (flash)
+	{
+		int flash_rate=Script::GetInteger(CRCD(0x4551e901,"RailEditorHighlightFlashRate"));
+		if (s_highlight_flash_counter < flash_rate/2)
+		{
+			vis=0xffffffff;
+		}
+		else
+		{
+			vis=0;
+		}
+			
+		++s_highlight_flash_counter;
+		if (s_highlight_flash_counter >= flash_rate)
+		{
+			s_highlight_flash_counter=0;
+		}	
+	}
+
+	uint8 good_r=Script::GetInteger(CRCD(0xad28b18b,"RailEditorHighlightColourR"));
+	uint8 good_g=Script::GetInteger(CRCD(0xc0f55560,"RailEditorHighlightColourG"));
+	uint8 good_b=Script::GetInteger(CRCD(0xb09fa1ef,"RailEditorHighlightColourB"));
+
+	uint8 bad_r=Script::GetInteger(CRCD(0x89b66fb9,"RailEditorBadAngleHighlightColourR"));
+	uint8 bad_g=Script::GetInteger(CRCD(0xe46b8b52,"RailEditorBadAngleHighlightColourG"));
+	uint8 bad_b=Script::GetInteger(CRCD(0x94017fdd,"RailEditorBadAngleHighlightColourB"));
+	
+	uint8 r=good_r;
+	uint8 g=good_g;
+	uint8 b=good_b;
+		
+	bool section_stretched_too_long=false;
+	if (mpNext)
+	{
+		if (s_distance_is_too_long(mPos, mpNext->mPos))
+		{
+			section_stretched_too_long=true;
+		}
+	}		
+	if (mpPrevious)
+	{
+		if (s_distance_is_too_long(mPos, mpPrevious->mPos))
+		{
+			section_stretched_too_long=true;
+		}
+	}		
+	
+	if (mpPostSector)
+	{
+		if (AngleIsOKToGrind() && !section_stretched_too_long)
+		{
+			r=good_r;
+			g=good_g;
+			b=good_b;
+		}
+		else
+		{
+			r=bad_r;
+			g=bad_g;
+			b=bad_b;
+		}	
+		mpPostSector->SetColor(Image::RGBA(r,g,b,0));
+		mpPostSector->SetVisibility(vis);
+	}	
+	
+	if (mpClonedRailSector)
+	{
+		if (mpNext)
+		{
+			if (mpNext->AngleIsOKToGrind() && AngleIsOKToGrind() && !section_stretched_too_long)
+			{
+				// The angle at the point at the other end of mpClonedRailSector is OK,
+				// and so is the angle at this point, so all OK.
+				r=good_r;
+				g=good_g;
+				b=good_b;
+			}
+			else
+			{
+				r=bad_r;
+				g=bad_g;
+				b=bad_b;
+			}	
+		}
+		
+		mpClonedRailSector->SetColor(Image::RGBA(r,g,b,0));
+		mpClonedRailSector->SetVisibility(vis);
+	}
+	if (mpPrevious && mpPrevious->mpClonedRailSector)
+	{
+		if (mpPrevious->AngleIsOKToGrind() && AngleIsOKToGrind() && !section_stretched_too_long)
+		{
+			// The angle at the point at the other end of the next segment is OK,
+			// and so is the angle at this point, so all OK.
+			r=good_r;
+			g=good_g;
+			b=good_b;
+		}
+		else
+		{
+			r=bad_r;
+			g=bad_g;
+			b=bad_b;
+		}	
+		
+		mpPrevious->mpClonedRailSector->SetColor(Image::RGBA(r,g,b,0));
+		mpPrevious->mpClonedRailSector->SetVisibility(vis);
+	}	
+
+	if (includeEndPosts)
+	{
+		if (mpNext && !mpNext->mpNext && mpNext->mpPostSector)
+		{
+			mpNext->mpPostSector->SetColor(Image::RGBA(good_r,good_g,good_b,0));
+			mpNext->mpPostSector->SetVisibility(vis);
+		}
+		if (mpPrevious && !mpPrevious->mpPrevious && mpPrevious->mpPostSector)
+		{
+			mpPrevious->mpPostSector->SetColor(Image::RGBA(good_r,good_g,good_b,0));
+			mpPrevious->mpPostSector->SetVisibility(vis);
+		}
+	}
+		
+	mHighlighted=true;
+}
+
+void CEditedRailPoint::UnHighlight()
+{
+	if (mHighlighted)
+	{
+		if (mpPostSector)
+		{
+			mpPostSector->ClearColor();
+			mpPostSector->SetVisibility(0xffffffff);
+		}	
+		if (mpClonedRailSector)
+		{
+			mpClonedRailSector->ClearColor();
+			mpClonedRailSector->SetVisibility(0xffffffff);
+		}
+		if (mpPrevious && mpPrevious->mpClonedRailSector)
+		{
+			mpPrevious->mpClonedRailSector->ClearColor();
+			mpPrevious->mpClonedRailSector->SetVisibility(0xffffffff);
+		}	
+
+		if (mpNext && !mpNext->mpNext && mpNext->mpPostSector)
+		{
+			mpNext->mpPostSector->ClearColor();
+			mpNext->mpPostSector->SetVisibility(0xffffffff);
+		}	
+		if (mpPrevious && !mpPrevious->mpPrevious && mpPrevious->mpPostSector)
+		{
+			mpPrevious->mpPostSector->ClearColor();
+			mpPrevious->mpPostSector->SetVisibility(0xffffffff);
+		}	
+		
+		mHighlighted=false;
+	}	
+}
+
+// Used by graffiti games
+void CEditedRailPoint::SetColor(Image::RGBA rgba)
+{
+	if (mpPostSector)
+	{
+		mpPostSector->SetColor(rgba);
+	}	
+	if (mpClonedRailSector)
+	{
+		mpClonedRailSector->SetColor(rgba);
+	}	
+}
+
+// Used by graffiti games
+void CEditedRailPoint::ClearColor()
+{
+	if (mpPostSector)
+	{
+		mpPostSector->ClearColor();
+	}	
+	if (mpClonedRailSector)
+	{
+		mpClonedRailSector->ClearColor();
+	}	
+}
+
+void CEditedRailPoint::SetSectorActiveStatus(bool active)
+{
+	if (mpPostSector)
+	{
+		mpPostSector->SetActive(active);
+	}	
+	if (mpClonedRailSector)
+	{
+		mpClonedRailSector->SetActive(active);
+	}	
+}
+
+bool CEditedRailPoint::AngleIsOKToGrind()
+{
+	if (mpNext && mpPrevious)
+	{
+		return s_angle_is_ok_to_grind(mpNext->mPos, mPos, mpPrevious->mPos);
+	}
+	return true;	
+}
+
+void CEditedRailPoint::WriteCompressedRailPoint(SCompressedRailPoint *p_dest)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+	
+	p_dest->mHasPost=mHasPost;
+	
+	// Sometimes the point may have a height that is fractionally below zero, so instead of asserting
+	// clamp it to zero.
+	//Dbg_MsgAssert(mHeightAboveGround >= 0.0f,("Expected mHeightAboveGround to be >= 0 ? (is %f)",mHeightAboveGround));
+	if (mHeightAboveGround < 0.0f)
+	{
+		p_dest->mHeight=0;
+	}
+	else
+	{
+		p_dest->mHeight=(uint16)(mHeightAboveGround*16.0f);
+	}	
+	
+	Dbg_MsgAssert(fabs(mPos[X]*8.0f)<=32767.0f,("Rail point position x=%f out of range",mPos[X]));
+	Dbg_MsgAssert(fabs(mPos[Y]*8.0f)<=32767.0f,("Rail point position y=%f out of range",mPos[Y]));
+	Dbg_MsgAssert(fabs(mPos[Z]*8.0f)<=32767.0f,("Rail point position z=%f out of range",mPos[Z]));
+	
+	p_dest->mX=(sint16)(mPos[X]*8.0f);
+	p_dest->mY=(sint16)(mPos[Y]*8.0f);
+	p_dest->mZ=(sint16)(mPos[Z]*8.0f);
+}
+
+CEditedRail::CEditedRail()
+{
+	mId=s_rail_unique_id++;
+	
+	mpRailPoints=NULL;
+	mpNext=NULL;
+	mpPrevious=NULL;
+	Clear();
+}
+
+CEditedRail::~CEditedRail()
+{
+	Clear();
+}
+
+void CEditedRail::Clear()
+{
+	CEditedRailPoint *p_rail_point=mpRailPoints;
+	while (p_rail_point)
+	{
+		CEditedRailPoint *p_next=p_rail_point->mpNext;
+		delete p_rail_point;
+		p_rail_point=p_next;
+	}	
+	mpRailPoints=NULL;
+}
+
+// Runs through all the points and adjusts the y coords so that the stored heights match the actual
+// heights above the ground. This is used to keep the post heights constant when raising or lowering the
+// ground in the park editor.
+void CEditedRail::AdjustYs()
+{
+	CEditedRailPoint *p_rail_point=mpRailPoints;
+	while (p_rail_point)
+	{
+		p_rail_point->AdjustY();
+		p_rail_point=p_rail_point->mpNext;
+	}	
+}
+
+void CEditedRail::InitialiseHeights()
+{
+	CEditedRailPoint *p_rail_point=mpRailPoints;
+	while (p_rail_point)
+	{
+		p_rail_point->InitialiseHeight();
+		p_rail_point=p_rail_point->mpNext;
+	}	
+}
+
+void CEditedRail::UpdateRailGeometry(Mth::Vector& rotateCentre, Mth::Vector& newCentre, float degrees)
+{
+	// Find the last rail point.
+	CEditedRailPoint *p_rail_point=mpRailPoints;
+	CEditedRailPoint *p_last=NULL;
+	while (p_rail_point)
+	{
+		p_last=p_rail_point;
+		p_rail_point=p_rail_point->mpNext;
+	}	
+
+	// It is necessary to traverse the list backwards because UpdateRailGeometry() uses some of the vertices of the next
+	// rail in the list for the end points of its rail, so the next rail needs to have been updated first.
+	p_rail_point=p_last;
+	while (p_rail_point)
+	{
+		p_rail_point->UpdateRailGeometry(rotateCentre, newCentre, degrees);
+		p_rail_point=p_rail_point->mpPrevious;
+	}	
+}
+
+void CEditedRail::UpdatePostGeometry(Mth::Vector& rotateCentre, Mth::Vector& newCentre, float degrees)
+{
+	CEditedRailPoint *p_rail_point=mpRailPoints;
+	while (p_rail_point)
+	{
+		p_rail_point->UpdatePostGeometry(rotateCentre, newCentre, degrees);
+		p_rail_point=p_rail_point->mpNext;
+	}	
+}
+
+void CEditedRail::DestroyRailGeometry()
+{
+	CEditedRailPoint *p_rail_point=mpRailPoints;
+	while (p_rail_point)
+	{
+		p_rail_point->DestroyRailGeometry();
+		p_rail_point=p_rail_point->mpNext;
+	}	
+}
+
+void CEditedRail::DestroyPostGeometry()
+{
+	CEditedRailPoint *p_rail_point=mpRailPoints;
+	while (p_rail_point)
+	{
+		p_rail_point->DestroyPostGeometry();
+		p_rail_point=p_rail_point->mpNext;
+	}	
+}
+
+CEditedRailPoint *CEditedRail::AddPoint()
+{
+	if (CEditedRailPoint::SGetNumUsedItems()==MAX_EDITED_RAIL_POINTS)
+	{
+		// No space left on the pool to store a new point.
+		return NULL;
+	}
+	
+	CEditedRailPoint *p_new=new CEditedRailPoint;
+	p_new->mpNext=mpRailPoints;
+	if (mpRailPoints)
+	{
+		mpRailPoints->mpPrevious=p_new;
+	}
+	p_new->mpPrevious=NULL;
+	mpRailPoints=p_new;
+	
+	return p_new;	
+}
+
+int CEditedRail::CountPoints()
+{
+	int num_points=0;
+	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		++num_points;
+		p_point=p_point->mpNext;
+	}
+	return num_points;
+}
+
+bool CEditedRail::FindNearestRailPoint(Mth::Vector &pos,
+									   Mth::Vector *p_nearest_pos, float *p_dist_squared, int *p_rail_point_index, 
+									   int ignore_index)
+{
+	if (!mpRailPoints)
+	{
+		return false;
+	}
+		
+	CEditedRailPoint *p_point=mpRailPoints;
+	int rail_point_index=0;
+	float min_dist_squared=100000000.0f;
+	while (p_point)
+	{
+		if (rail_point_index != ignore_index)
+		{
+			Mth::Vector diff=p_point->mPos-pos;
+			// Zero the y so that one does not have to raise the cursor to make high rails highlight.
+			// (Make's a big difference!)
+			diff[Y]=0.0f;
+			float dd=diff.LengthSqr();
+			
+			if (dd < min_dist_squared)
+			{
+				min_dist_squared=dd;
+				
+				*p_nearest_pos=p_point->mPos;
+				*p_dist_squared=min_dist_squared;
+				*p_rail_point_index=rail_point_index;
+			}
+		}
+			
+		p_point=p_point->mpNext;
+		++rail_point_index;
+	}
+	
+	return true;
+}
+
+int CEditedRail::CountPointsInArea(float x0, float z0, float x1, float z1)
+{
+	int num_points=0;
+
+   	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		if (p_point->mPos[X] > x0 && p_point->mPos[X] < x1 &&
+			p_point->mPos[Z] > z0 && p_point->mPos[Z] < z1)
+		{
+			// The point is inside the area
+			++num_points;
+		}
+			
+		p_point=p_point->mpNext;
+	}	
+	
+	return num_points;
+}
+
+void CEditedRail::DuplicateAndAddPoint(CEditedRailPoint *p_point)
+{
+	Dbg_MsgAssert(p_point,("NULL p_point"));
+	
+	CEditedRailPoint *p_new_point=AddPoint();
+	Dbg_MsgAssert(p_new_point,("NULL p_new_point"));
+	
+	p_new_point->mPos=p_point->mPos;
+	p_new_point->mHasPost=p_point->mHasPost;
+	
+	p_new_point->mHeightAboveGround=p_point->mHeightAboveGround;
+}
+
+CEditedRail *CEditedRail::GenerateDuplicateRails(float x0, float z0, float x1, float z1, CEditedRail *p_head)
+{
+	bool inside=false;
+	CEditedRailPoint *p_first_point_of_new_rail=NULL;
+	
+   	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		if (p_point->mPos[X] > x0 && p_point->mPos[X] < x1 &&
+			p_point->mPos[Z] > z0 && p_point->mPos[Z] < z1)
+		{
+			// The point is inside the area
+			
+			if (inside)
+			{
+				if (p_first_point_of_new_rail)
+				{
+					CEditedRail *p_new_rail=new CEditedRail;
+					p_new_rail->mpNext=p_head;
+					p_head=p_new_rail;
+					
+					p_head->DuplicateAndAddPoint(p_first_point_of_new_rail);
+					p_first_point_of_new_rail=NULL;
+				}	
+				p_head->DuplicateAndAddPoint(p_point);
+			}
+			else	
+			{
+				// Gone from outside to inside, so it's time to make a new rail.
+				// Don't actually create the rail yet though, since it may only end up containing
+				// one point, & don't want to create those.
+				p_first_point_of_new_rail=p_point;
+			}
+			
+			inside=true;
+		}
+		else
+		{
+			p_first_point_of_new_rail=NULL;
+			inside=false;	
+		}
+			
+		p_point=p_point->mpNext;
+	}	
+
+	return p_head;
+}
+
+void CEditedRail::UnHighlight()
+{
+   	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		p_point->UnHighlight();
+		p_point=p_point->mpNext;
+	}	
+}
+
+CEditedRailPoint *CEditedRail::GetRailPointFromIndex(int index)
+{
+   	CEditedRailPoint *p_point=mpRailPoints;
+	while (index)
+	{
+		if (!p_point)
+		{
+			break;
+		}	
+		p_point=p_point->mpNext;
+		--index;
+	}	
+	Dbg_MsgAssert(p_point,("Could not find rail point with index %d",index));
+	
+	return p_point;
+}
+
+// Deletes p_point from the rail, and returns a pointer to any fragment that gets created.
+// If the max rails has been reached and deleting the point will result in a fragment,
+// then it won't delete the point after all, because a new rail will not be able to be created for
+// the fragment.
+CEditedRailPoint *CEditedRail::DeleteRailPoint(CEditedRailPoint *p_point)
+{
+	if (!p_point)
+	{
+		return NULL;
+	}
+	
+	// Check that p_point is in the rail ...
+	#ifdef __NOPT_ASSERT__
+	bool found=false;
+	CEditedRailPoint *p_check=mpRailPoints;
+	while (p_check)
+	{
+		if (p_check == p_point)
+		{
+			found=true;
+			break;
+		}
+		p_check=p_check->mpNext;
+	}
+	Dbg_MsgAssert(found,("p_point is not in the rail !"));		
+	#endif
+	
+	if (p_point->mpPrevious && p_point->mpNext)
+	{
+		// A new rail will need to be created so bail out if there is not enough
+		// space to create one.
+		if (CEditedRail::SGetNumUsedItems()==MAX_EDITED_RAILS)
+		{
+			return NULL;
+		}
+	}
+	
+	// Update this rail if removing its first point.
+	if (p_point == mpRailPoints)
+	{
+		Dbg_MsgAssert(p_point->mpPrevious==NULL,("Expected p_point->mpPrevious==NULL"));
+		mpRailPoints=p_point->mpNext;
+		if (p_point->mpNext)
+		{
+			p_point->mpNext->mpPrevious=NULL;
+		}
+		delete p_point;
+		return NULL;
+	}
+
+	Dbg_MsgAssert(p_point->mpPrevious,("Expected p_point->mpPrevious not to be NULL"));
+	// Remove any rail geometry connecting the adjacent point to this one.
+	p_point->mpPrevious->DestroyRailGeometry();
+	// Terminate this rail.
+	p_point->mpPrevious->mpNext=NULL;
+	
+	// Return the new rail fragment.
+	CEditedRailPoint *p_fragment_start=p_point->mpNext;
+	delete p_point;
+	
+	if (p_fragment_start)
+	{
+		p_fragment_start->mpPrevious=NULL;
+	}
+	return p_fragment_start;
+}
+
+bool CEditedRail::UpdateRailPointPosition(int rail_point_index, Mth::Vector &pos, EUpdateSuperSectors updateSuperSectors)
+{
+   	CEditedRailPoint *p_point=GetRailPointFromIndex(rail_point_index);
+	
+	// UpdateRailPointPosition returns false if the position is bad in that it is
+	// too close to an adjacent point or causes the rail to be too steep.
+	// It still writes in the position so that the rail always stays attached to
+	// the cursor as the user moves it around when in grab mode.
+	// However, it won't write in the position if one of the rails will be stretched too long,
+	// because that causes rendering problems.
+	
+	bool position_is_ok=true;
+	if (p_point->mpNext)
+	{
+		if (!s_positions_OK(pos,p_point->mpNext->mPos))
+		{
+			position_is_ok=false;
+		}
+	}
+	if (p_point->mpPrevious)
+	{
+		if (!s_positions_OK(pos,p_point->mpPrevious->mPos))
+		{
+			position_is_ok=false;
+		}
+	}
+
+	// Don't allow points to be placed too close to the boundary of the park (TT5464)
+	if (!Ed::IsWithinParkBoundaries(pos,PARK_BOUNDARY_MARGIN))
+	{
+		position_is_ok=false;
+	}
+
+	bool ok_to_update=true;
+	if (p_point->mpNext)
+	{
+		if (s_distance_is_way_too_long(pos,p_point->mpNext->mPos))
+		{
+			ok_to_update=false;
+		}	
+	}
+	if (p_point->mpPrevious)
+	{
+		if (s_distance_is_way_too_long(pos,p_point->mpPrevious->mPos))
+		{
+			ok_to_update=false;
+		}	
+	}
+
+
+	if (ok_to_update)
+	{
+		p_point->mPos=pos;
+		p_point->InitialiseHeight();
+		p_point->UpdatePostGeometry();
+		p_point->UpdateRailGeometry();
+		if (p_point->mpPrevious)
+		{
+			p_point->mpPrevious->UpdateRailGeometry();
+		}	
+	
+		// Updating the super sectors is technically required every time the collision verts on a sector
+		// are modified, as they will be by this function.
+		// If UpdateSuperSectors is not done, then when attempting to delete the sector later the low-level code
+		// (in nxscene) will assert.
+		// However, it can be slow to call UpdateSuperSectors every frame, so when a rail point is being dragged
+		// around in grab mode it is not called. It is only called when placing the new point or when backing
+		// out and restoring the old position.
+		if (updateSuperSectors)
+		{
+			Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+			Dbg_MsgAssert(p_cloned_scene,("NULL p_cloned_scene ?"));
+			p_cloned_scene->UpdateSuperSectors();
+		}
+	}
+		
+	return position_is_ok;
+}
+
+// Used when pasting a rail
+void CEditedRail::CopyRail(CEditedRail *p_source_rail)
+{
+	Clear();
+	
+	Dbg_MsgAssert(p_source_rail,("NULL p_source_rail"));
+	
+	CEditedRailPoint *p_source_point=p_source_rail->mpRailPoints;
+	while (p_source_point)
+	{
+		CEditedRailPoint *p_new_point=AddPoint();
+		
+		p_new_point->mPos=p_source_point->mPos;
+		p_new_point->mHasPost=p_source_point->mHasPost;
+		p_new_point->mHeightAboveGround=p_source_point->mHeightAboveGround;
+		
+		p_source_point=p_source_point->mpNext;
+	}	
+}
+
+// Used when pasting a rail
+void CEditedRail::RotateAndTranslate(Mth::Vector& rotateCentre, Mth::Vector& newCentre, float degrees)
+{
+	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		p_point->mPos=s_rotate_and_translate(p_point->mPos, rotateCentre, newCentre, degrees);
+		p_point=p_point->mpNext;
+	}
+}
+
+SCompressedRailPoint *CEditedRail::WriteCompressedRailPoints(SCompressedRailPoint *p_dest)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+
+	CEditedRailPoint *p_point=mpRailPoints;
+	// Skip to the end of the list of points so that it can be traversed backwards.
+	// This is so that when the rails are regenerated using InitUsingCompressedRailsBuffer()
+	// they get recreated with the points in the original order, and hence the rail node indices 
+	// will match on the server and client. 
+	while (p_point && p_point->mpNext)
+	{
+		p_point=p_point->mpNext;
+	}
+	
+	while (p_point)
+	{
+		p_point->WriteCompressedRailPoint(p_dest++);
+		p_point=p_point->mpPrevious;
+	}
+	return p_dest;
+}
+
+// Used by graffiti games
+void CEditedRail::ModulateRailColor(int seqIndex)
+{
+	Script::CArray* p_graffiti_col_tab = Script::GetArray( CRCD(0x7f1ba1aa,"graffitiColors") );
+	Dbg_MsgAssert( (uint) seqIndex < p_graffiti_col_tab->GetSize(), ( "graffitiColors array too small" ) );
+
+	Script::CArray *p_entry = p_graffiti_col_tab->GetArray(seqIndex);
+	
+	#ifdef	__NOPT_ASSERT__
+	int size = p_entry->GetSize();
+	Dbg_MsgAssert(size >= 3 && size <= 4, ("wrong size %d for color array", size));
+	#endif
+	
+	Image::RGBA color;
+
+	color.r = p_entry->GetInteger( 0 );
+	color.g = p_entry->GetInteger( 1 );
+	color.b = p_entry->GetInteger( 2 );
+	color.a = 128;
+	
+	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		p_point->SetColor(color);
+		p_point=p_point->mpNext;
+	}
+}
+
+// Used by graffiti games
+void CEditedRail::ClearRailColor()
+{
+	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		p_point->ClearColor();
+		p_point=p_point->mpNext;
+	}
+}
+
+void CEditedRail::SetSectorActiveStatus(bool active)
+{
+	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		p_point->SetSectorActiveStatus(active);
+		p_point=p_point->mpNext;
+	}
+}
+
+void CEditedRail::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	
+	p_info->AddChecksum(CRCD(0x40c698af,"id"),mId);
+	
+	int num_points=CountPoints();
+		
+	if (num_points)
+	{
+		Script::CArray *p_array=new Script::CArray;
+		p_array->SetSizeAndType(num_points,ESYMBOLTYPE_STRUCTURE);
+
+		int i=0;
+		CEditedRailPoint *p_point=mpRailPoints;
+		while (p_point)
+		{
+			Script::CStruct *p_struct=new Script::CStruct;
+			p_struct->AddVector(CRCD(0x7f261953,"pos"),p_point->mPos[X],p_point->mPos[Y],p_point->mPos[Z]);
+			p_struct->AddFloat(CRCD(0xab21af0,"Height"),p_point->mHeightAboveGround);
+			p_struct->AddInteger(CRCD(0x41a51a93,"mpClonedRailSector"),(int)p_point->mpClonedRailSector);
+			p_struct->AddInteger(CRCD(0x79ffd768,"mpPostSector"),(int)p_point->mpPostSector);
+			
+			p_array->SetStructure(i,p_struct);
+			++i;
+			p_point=p_point->mpNext;
+		}
+		
+		p_info->AddArrayPointer(CRCD(0xd84571d6,"Points"),p_array);
+	}
+#endif				 
+}
+
+bool CEditedRail::ThereAreRailPointsOutsideArea(float x0, float z0, float x1, float z1)
+{
+	Dbg_MsgAssert(x0 <= x1 && z0 <= z1,("Bad area"));
+	
+   	CEditedRailPoint *p_point=mpRailPoints;
+	while (p_point)
+	{
+		if (p_point->mPos[X] < x0 || p_point->mPos[X] > x1 ||
+			p_point->mPos[Z] < z0 || p_point->mPos[Z] > z1)
+		{
+			return true;
+		}	
+		p_point=p_point->mpNext;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=true;
+
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CRailEditorComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CRailEditorComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CRailEditorComponent::CRailEditorComponent() : CBaseComponent()
+{
+	SetType( CRC_RAILEDITOR );
+
+	Dbg_MsgAssert(MAX_EDITED_RAILS == MAX_EDITED_RAIL_POINTS/2,("Bad MAX_EDITED_RAILS"));
+	
+	CEditedRailPoint::SCreatePool(MAX_EDITED_RAIL_POINTS, "CEditedRailPoint");
+	CEditedRail::SCreatePool(MAX_EDITED_RAILS, "CEditedRail");
+
+	mp_input_component=NULL;
+	mp_editor_camera_component=NULL;
+	mp_dotted_line_sector=NULL;
+	m_dotted_line_sector_name=0;
+	mp_edited_rails=NULL;
+
+	clear_compressed_rails_buffer();
+	Clear();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CRailEditorComponent::~CRailEditorComponent()
+{
+	Clear();
+	CEditedRailPoint::SRemovePool();
+	CEditedRail::SRemovePool();
+}
+
+void CRailEditorComponent::clear_compressed_rails_buffer()
+{
+	for (uint i=0; impNext)
+	{
+		p_rail=p_rail->mpNext;
+	}
+		
+	while (p_rail)
+	{
+		*p_num_points_in_rail++=p_rail->CountPoints();
+		p_rail_point=p_rail->WriteCompressedRailPoints(p_rail_point);	
+		
+		p_rail=p_rail->mpPrevious;
+	}
+	
+	Dbg_MsgAssert((uint8*)p_rail_point <= mp_compressed_rails_buffer+sizeof(mp_compressed_rails_buffer),("p_rail_point overwrote end of buffer"));
+	Dbg_MsgAssert(p_num_points_in_rail-(uint16*)(mp_compressed_rails_buffer+2)==*(uint16*)mp_compressed_rails_buffer,("Num rails mismatch"));
+}
+
+// Used when sending rail info over the net.
+uint8 *CRailEditorComponent::GetCompressedRailsBuffer()
+{
+	generate_compressed_rails_buffer();
+	return mp_compressed_rails_buffer;
+}
+
+void CRailEditorComponent::SetCompressedRailsBuffer(uint8 *p_buffer)
+{
+	Dbg_MsgAssert(p_buffer,("NULL p_buffer"));
+	memcpy(mp_compressed_rails_buffer, p_buffer, sizeof(mp_compressed_rails_buffer));
+}
+
+void CRailEditorComponent::InitUsingCompressedRailsBuffer()
+{
+	Clear();
+	
+	uint16 num_rails=*(uint16*)mp_compressed_rails_buffer;
+	uint16 *p_num_points_in_rail=(uint16*)(mp_compressed_rails_buffer+2);
+	SCompressedRailPoint *p_rail_point=(SCompressedRailPoint*)(p_num_points_in_rail+MAX_EDITED_RAILS);
+	
+	for (uint i=0; iAddPoint();
+			if (p_point)
+			{
+				p_point->mHasPost=p_rail_point->mHasPost;
+				p_point->mHeightAboveGround=p_rail_point->mHeight / 16.0f;
+				p_point->mPos[X]=p_rail_point->mX / 8.0f;
+				p_point->mPos[Y]=p_rail_point->mY / 8.0f;
+				p_point->mPos[Z]=p_rail_point->mZ / 8.0f;
+			}
+			++p_rail_point;
+		}	
+	}
+}
+
+void CRailEditorComponent::AdjustYs()
+{
+	SetSectorActiveStatus(false);
+
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->AdjustYs();
+		p_rail=p_rail->mpNext;
+	}
+
+	SetSectorActiveStatus(true);
+}
+
+void CRailEditorComponent::InitialiseHeights()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->InitialiseHeights();
+		p_rail=p_rail->mpNext;
+	}
+}
+
+void CRailEditorComponent::UpdateRailGeometry()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->UpdateRailGeometry();
+		p_rail=p_rail->mpNext;
+	}
+	
+	Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+	Dbg_MsgAssert(p_cloned_scene,("NULL p_cloned_scene ?"));
+	p_cloned_scene->UpdateSuperSectors();
+}
+
+void CRailEditorComponent::UpdatePostGeometry()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->UpdatePostGeometry();
+		p_rail=p_rail->mpNext;
+	}
+	
+	Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+	Dbg_MsgAssert(p_cloned_scene,("NULL p_cloned_scene ?"));
+	p_cloned_scene->UpdateSuperSectors();
+}
+
+void CRailEditorComponent::DestroyRailGeometry()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->DestroyRailGeometry();
+		p_rail=p_rail->mpNext;
+	}
+}
+
+void CRailEditorComponent::DestroyPostGeometry()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->DestroyPostGeometry();
+		p_rail=p_rail->mpNext;
+	}
+}
+
+void CRailEditorComponent::RefreshGeometry()
+{
+	InitialiseHeights();
+	UpdateRailGeometry();
+	UpdatePostGeometry();
+}
+
+void CRailEditorComponent::UnHighlightAllRails()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->UnHighlight();
+		p_rail=p_rail->mpNext;
+	}
+}
+
+// Removes the point p_point from the rail p_rail, so p_rail may contain no points afterwards.
+// May result in the creation of a new rail, if the point was not one of the end points of p_rail.
+// Returns true if it did create a new rail.
+bool CRailEditorComponent::DeleteRailPoint(CEditedRail *p_rail, CEditedRailPoint *p_point)
+{
+	Dbg_MsgAssert(p_rail,("NULL p_rail"));
+	Dbg_MsgAssert(p_point,("NULL p_point"));
+
+	CEditedRailPoint *p_fragment=p_rail->DeleteRailPoint(p_point);
+	if (p_fragment)
+	{
+		CEditedRail *p_new_rail=NewRail();
+		p_new_rail->mpRailPoints=p_fragment;
+		return true;
+	}	
+	
+	return false;
+}
+
+// Used when resizing the park, or when deleting rail points within an area using the area-select tool.
+// This will remove all rail points outside of, or inside of (x0,z0) (x1,z1)
+// It needs to be a member function of CRailEditorComponent rather than CEditedRail, because
+// it could result in the creation of new rails.
+// Returns true if it did create new rails.
+bool CRailEditorComponent::ClipRail(CEditedRail *p_rail, float x0, float z0, float x1, float z1, EClipType clipType)
+{
+	Dbg_MsgAssert(x0 <= x1,("Need x0 <= x1"));
+	Dbg_MsgAssert(z0 <= z1,("Need z0 <= z1"));
+
+	CEditedRailPoint *p_point=p_rail->mpRailPoints;
+	while (p_point)
+	{
+		CEditedRailPoint *p_next=p_point->mpNext;
+		
+		bool delete_point=false;
+		
+		if (p_point->mPos[X] > x0 && p_point->mPos[X] < x1 &&
+			p_point->mPos[Z] > z0 && p_point->mPos[Z] < z1)
+		{
+			// The point is inside the area
+			if (clipType==DELETE_POINTS_INSIDE)
+			{
+				delete_point=true;
+			}	
+		}
+		else	
+		{
+			// The point is outside the area
+			if (clipType==DELETE_POINTS_OUTSIDE)
+			{
+				delete_point=true;
+			}	
+		}
+		
+		if (delete_point)
+		{
+			if (DeleteRailPoint(p_rail, p_point))
+			{
+				// Deleting the point resulted in a new rail being created, which means that
+				// p_next no longer belongs to this rail, so bail out.
+				return true;
+			}
+		}
+		
+		p_point=p_next;
+	}		
+	
+	return false;
+}
+
+void CRailEditorComponent::ClipRails(float x0, float z0, float x1, float z1, EClipType clipType)
+{
+	if (x0 > x1)
+	{
+		float t=x0;
+		x0=x1;
+		x1=t;
+	}
+	if (z0 > z1)
+	{
+		float t=z0;
+		z0=z1;
+		z1=t;
+	}
+		
+	while (true)
+	{
+		bool created_new_rails=false;
+		
+		CEditedRail *p_rail=mp_edited_rails;
+		while (p_rail)
+		{
+			if (ClipRail(p_rail, x0, z0, x1, z1, clipType))
+			{
+				created_new_rails=true;
+			}	
+			p_rail=p_rail->mpNext;
+		}		
+		
+		// Repeat until no new rails were created.
+		// Any new rails created by the above ClipRail calls will have been stuck on the front of
+		// the list, so mp_edited_rails will be different next time around.
+		if (!created_new_rails)
+		{
+			break;
+		}
+	}
+	
+	RemoveEmptyAndSinglePointRails();
+}
+
+bool CRailEditorComponent::ThereAreRailPointsOutsideArea(float x0, float z0, float x1, float z1)
+{
+	if (x0 > x1)
+	{
+		float t=x0;
+		x0=x1;
+		x1=t;
+	}
+	if (z0 > z1)
+	{
+		float t=z0;
+		z0=z1;
+		z1=t;
+	}
+		
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		if (p_rail->ThereAreRailPointsOutsideArea(x0, z0, x1, z1))
+		{
+			return true;
+		}	
+		p_rail=p_rail->mpNext;
+	}		
+		
+	return false;	
+}
+
+// Used when copying a set of rails to the clipboard.
+// Counts how many rail points will need to be created when copying the given area to the clipboard.
+// This allows it to bail out before attempting to copy if there are not enough points left in the pool.
+int CRailEditorComponent::CountPointsInArea(float x0, float z0, float x1, float z1)
+{
+	if (x0 > x1)
+	{
+		float t=x0;
+		x0=x1;
+		x1=t;
+	}
+	if (z0 > z1)
+	{
+		float t=z0;
+		z0=z1;
+		z1=t;
+	}
+		
+	int num_points=0;
+		
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		num_points += p_rail->CountPointsInArea(x0, z0, x1, z1);
+		p_rail=p_rail->mpNext;
+	}		
+	
+	return num_points;
+}
+
+bool CRailEditorComponent::AbleToCopyRails(float x0, float z0, float x1, float z1)
+{
+	return CountPointsInArea(x0,z0,x1,z1) <= GetNumFreePoints();
+}
+
+CEditedRail *CRailEditorComponent::GenerateDuplicateRails(float x0, float z0, float x1, float z1)
+{
+	CEditedRail *p_duplicated_rails=NULL;
+	
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_duplicated_rails=p_rail->GenerateDuplicateRails(x0, z0, x1, z1, p_duplicated_rails);
+		p_rail=p_rail->mpNext;
+	}
+		
+	return p_duplicated_rails;
+}
+
+bool CRailEditorComponent::FindNearestRailPoint(Mth::Vector &pos, Mth::Vector *p_nearest_pos, 
+												float *p_dist, 
+												uint32 *p_rail_id, int *p_rail_point_index,
+												uint32 ignore_rail_id, int ignore_rail_point_index)
+{
+	float min_dist_squared=100000000.0f;
+	bool found_nearest_point=false;
+	
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		Mth::Vector nearest;
+		float dist;
+		int rail_point_index=0;
+		
+		int ignore_index=-1;
+		if (p_rail->mId==ignore_rail_id)
+		{
+			ignore_index=ignore_rail_point_index;
+		}	
+		
+		if (p_rail->FindNearestRailPoint(pos, &nearest, &dist, &rail_point_index, ignore_index))
+		{
+			if (dist < min_dist_squared)
+			{
+				min_dist_squared=dist;
+				*p_nearest_pos=nearest;
+				*p_dist=dist;
+				*p_rail_id=p_rail->mId;
+				*p_rail_point_index=rail_point_index;
+				found_nearest_point=true;
+			}	
+		}
+		
+		p_rail=p_rail->mpNext;
+	}
+	
+	return found_nearest_point;
+}
+
+// For writing to memcard
+void CRailEditorComponent::WriteIntoStructure(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	
+	int num_rails=count_rails();
+	
+	Script::CArray *p_rails=new Script::CArray;
+	p_rails->SetSizeAndType(num_rails,ESYMBOLTYPE_STRUCTURE);
+	
+	int index=0;
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		Script::CStruct *p_rail_info=new Script::CStruct;
+		
+		// Note: Don't need to write out the mId member because ReadFromBuffer will create new rails,
+		// which will each get a new id on creation.
+		
+		int num_points=p_rail->CountPoints();
+		Script::CArray *p_points=new Script::CArray;
+		p_points->SetSizeAndType(num_points,ESYMBOLTYPE_STRUCTURE);
+		
+		int point_index=0;
+		CEditedRailPoint *p_point=p_rail->mpRailPoints;
+		while (p_point)
+		{
+			Script::CStruct *p_point_info=new Script::CStruct;
+			p_point_info->AddVector(CRCD(0x7f261953,"Pos"),p_point->mPos[X],p_point->mPos[Y],p_point->mPos[Z]);
+			if (p_point->mpPostSector)
+			{
+				p_point_info->AddChecksum(NONAME,CRCD(0xdcded772,"HasPost"));
+			}	
+			p_points->SetStructure(point_index,p_point_info);
+			
+			p_point=p_point->mpNext;
+			++point_index;
+		}	
+
+		p_rail_info->AddArrayPointer(CRCD(0xd84571d6,"Points"),p_points);
+		p_rails->SetStructure(index,p_rail_info);
+		++index;
+		
+		p_rail=p_rail->mpNext;
+	}
+	
+	p_info->AddArrayPointer(CRCD(0x244550a6,"CreatedRails"),p_rails);
+}
+
+// For reading from memcard
+void CRailEditorComponent::ReadFromStructure(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	
+	Clear();
+	
+	Script::CArray *p_rails=NULL;
+	p_info->GetArray(CRCD(0x244550a6,"CreatedRails"),&p_rails);
+	if (!p_rails)
+	{
+		return;
+	}
+
+	Dbg_MsgAssert((int)p_rails->GetSize() <= GetNumFreeRails(),("Too many rails, got %d, max is %d",p_rails->GetSize(),GetNumFreeRails()));
+	
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *p_rail_info=p_rails->GetStructure(i);
+		Dbg_MsgAssert(p_rail_info,("Eh? NULL p_rail_info ?"));
+		
+		NewRail();
+		
+		Script::CArray *p_points=NULL;
+		p_rail_info->GetArray(CRCD(0xd84571d6,"Points"),&p_points,Script::ASSERT);
+		for (uint32 p=0; pGetSize(); ++p)
+		{
+			Dbg_MsgAssert(mp_current_rail,("NULL mp_current_rail ?"));
+			CEditedRailPoint *p_new_point=mp_current_rail->AddPoint();
+			
+			Script::CStruct *p_point_info=p_points->GetStructure(p);
+			p_point_info->GetVector(CRCD(0x7f261953,"pos"),&p_new_point->mPos);
+			if (p_point_info->ContainsFlag(CRCD(0xdcded772,"HasPost")))
+			{
+				p_new_point->mHasPost=true;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CRailEditorComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CRailEditorComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	InitFromStructure(pParams);
+}
+
+void CRailEditorComponent::Hide( bool shouldHide )
+{
+	Ed::CParkManager *p_park_manager=Ed::CParkManager::sInstance();
+
+	if (shouldHide)
+	{
+		Script::RunScript(CRCD(0xec0a515,"rail_editor_destroy_cursor"));
+		p_park_manager->GetGenerator()->CleanUpOutRailSet();
+		UnHighlightAllRails();
+		DeleteDottedLine();
+	}	
+	else
+	{
+		Script::RunScript(CRCD(0x52d1ce6f,"rail_editor_create_cursor"));
+
+		// Create the 'out rail' set within the park generator, required for snapping to the
+		// nearest rail point.
+		p_park_manager->GetGenerator()->GenerateOutRailSet(p_park_manager->GetConcreteMetaList());
+		
+		// Make sure we're using the skater's pad
+		Script::CStruct *p_params=new Script::CStruct;
+		p_params->AddInteger(CRCD(0x67e6859a,"player"),0);
+		mp_input_component->InitFromStructure(p_params);
+		delete p_params;
+
+		
+		// This is a quick fix to a bug where button triggers are stored up whilst the
+		// rail editor component is suspended. I think it's a bug in the input component, cos
+		// surely it should ignore the pad whist suspended?
+		CControlPad& control_pad = mp_input_component->GetControlPad();
+		control_pad.Reset();
+	}
+}
+
+void CRailEditorComponent::get_pos_from_camera_component(Mth::Vector *p_pos, float *p_height, float *p_angle)
+{
+	Dbg_MsgAssert(mp_editor_camera_component,("NULL mp_editor_camera_component ?"));
+	*p_pos=mp_editor_camera_component->GetCursorPos();
+	*p_height=mp_editor_camera_component->GetCursorHeight();
+	*p_angle=mp_editor_camera_component->GetCursorOrientation();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CRailEditorComponent::Finalize()
+{
+	// Get the pointers to the other required components.
+	
+	Dbg_MsgAssert(mp_input_component==NULL,("mp_input_component not NULL ?"));
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	Dbg_MsgAssert(mp_input_component,("CRailEditorComponent requires parent object to have an input component!"));
+
+	Dbg_MsgAssert(mp_editor_camera_component==NULL,("mp_editor_camera_component not NULL ?"));
+	mp_editor_camera_component = GetEditorCameraComponentFromObject(GetObject());
+	Dbg_MsgAssert(mp_editor_camera_component,("CRailEditorComponent requires parent object to have an EditorCamera component!"));
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CRailEditorComponent::Update()
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	if (control_pad.m_x.GetTriggered())
+	{
+		control_pad.m_x.ClearTrigger();
+		Script::RunScript(CRCD(0x415f2afa,"RailEditorX"),NULL,GetObject());
+	}	
+
+	#ifdef __PLAT_NGC__
+	if (control_pad.m_square.GetTriggered())
+	{
+		control_pad.m_square.ClearTrigger();
+		Script::RunScript(CRCD(0x3ad21c4e,"RailEditorTriangle"),NULL,GetObject());
+	}	
+
+	if (control_pad.m_triangle.GetTriggered())
+	{
+		control_pad.m_triangle.ClearTrigger();
+		Script::RunScript(CRCD(0xd8d956c9,"RailEditorSquare"),NULL,GetObject());
+	}	
+	#else
+	if (control_pad.m_triangle.GetTriggered())
+	{
+		control_pad.m_triangle.ClearTrigger();
+		Script::RunScript(CRCD(0x3ad21c4e,"RailEditorTriangle"),NULL,GetObject());
+	}	
+
+	if (control_pad.m_square.GetTriggered())
+	{
+		control_pad.m_square.ClearTrigger();
+		Script::RunScript(CRCD(0xd8d956c9,"RailEditorSquare"),NULL,GetObject());
+	}	
+	#endif
+	
+	if (control_pad.m_circle.GetTriggered())
+	{
+		control_pad.m_circle.ClearTrigger();
+		Script::RunScript(CRCD(0xc18d5b19,"RailEditorCircle"),NULL,GetObject());
+	}	
+
+	Script::RunScript(CRCD(0x97153f0e,"RailEditorEveryFrame"),NULL,GetObject());
+}
+
+void CRailEditorComponent::Clear()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		CEditedRail *p_next=p_rail->mpNext;
+		delete p_rail;
+		p_rail=p_next;
+	}
+	mp_edited_rails=NULL;
+	mp_current_rail=NULL;
+	m_mode=0;
+	DeleteDottedLine();
+}	
+
+void CRailEditorComponent::DeleteRail(CEditedRail *p_rail)
+{
+	if (mp_edited_rails==p_rail)
+	{
+		mp_edited_rails=p_rail->mpNext;
+	}	
+	if (mp_current_rail==p_rail)
+	{
+		mp_current_rail=p_rail->mpNext;
+	}	
+
+	if (p_rail->mpNext)
+	{
+		p_rail->mpNext->mpPrevious=p_rail->mpPrevious;
+	}
+	if (p_rail->mpPrevious)
+	{
+		p_rail->mpPrevious->mpNext=p_rail->mpNext;
+	}
+	delete p_rail;
+}
+
+CEditedRail *CRailEditorComponent::NewRail()
+{
+	CEditedRail *p_new=new CEditedRail;
+	
+	p_new->mpNext=mp_edited_rails;
+	p_new->mpPrevious=NULL;
+	if (p_new->mpNext)
+	{
+		p_new->mpNext->mpPrevious=p_new;
+	}
+	mp_edited_rails=p_new;
+	mp_current_rail=p_new;	
+	
+	return p_new;
+}
+
+CEditedRail *CRailEditorComponent::GetRail(uint32 id)
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		if (p_rail->mId==id)
+		{
+			return p_rail;
+		}
+		p_rail=p_rail->mpNext;
+	}		
+	return NULL;
+}
+
+void CRailEditorComponent::RemoveEmptyAndSinglePointRails()
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		CEditedRail *p_next=p_rail->mpNext;
+		
+		if (p_rail->mpRailPoints == NULL || (p_rail->mpRailPoints && p_rail->mpRailPoints->mpNext==NULL))
+		{
+			DeleteRail(p_rail);
+		}	
+		p_rail=p_next;
+	}		
+}
+
+int CRailEditorComponent::count_rails()
+{
+	int num_rails=0;
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		++num_rails;
+		p_rail=p_rail->mpNext;
+	}
+	return num_rails;
+}
+
+int CRailEditorComponent::GetTotalRailNodesRequired()
+{
+	int total_nodes=0;
+	
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		CEditedRailPoint *p_point=p_rail->mpRailPoints;
+		while (p_point)
+		{
+			++total_nodes;
+			p_point=p_point->mpNext;
+		}	
+		p_rail=p_rail->mpNext;
+	}
+	
+	return total_nodes;
+}
+
+void CRailEditorComponent::SetUpRailNodes(Script::CArray *p_nodeArray, int *p_nodeNum, uint32 firstID)
+{
+	Dbg_MsgAssert(p_nodeArray,("NULL p_nodeArray"));
+	Dbg_MsgAssert(p_nodeNum,("NULL p_nodeNum"));
+	
+	int node_index=*p_nodeNum;
+	uint32 rail_point_id=firstID;
+
+	int rail_index=0;	
+	char p_rail_name[50];
+	
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		CEditedRailPoint *p_point=p_rail->mpRailPoints;
+		while (p_point)
+		{
+			Script::CStruct *p_node=p_nodeArray->GetStructure(node_index++);
+	
+			p_node->AddVector(CRCD(0x7f261953,"Pos"),p_point->mPos[X],p_point->mPos[Y],p_point->mPos[Z]);
+			p_node->AddVector(CRCD(0x9d2d0915,"Angles"),0.0f,0.0f,0.0f);
+			
+			p_node->AddChecksum(CRCD(0xa1dc81f9,"Name"),rail_point_id++);
+			
+			p_node->AddChecksum(CRCD(0x12b4e660,"Class"),CRCD(0x8e6b02ad,"RailNode"));
+			p_node->AddChecksum(NONAME,CRCD(0x7c2552b9,"CreatedAtStart"));
+			
+			p_node->AddChecksum(NONAME,CRCD(0x1645b830,"TrickObject"));
+			sprintf(p_rail_name,"CreatedRailCluster%d",rail_index);
+			p_node->AddChecksum(CRCD(0x1a3a966b,"Cluster"),Script::GenerateCRC(p_rail_name));
+			
+			// hardwire to metal for the moment
+			p_node->AddChecksum(CRCD(0x54cf8532,"TerrainType"),CRCD(0xa9ecf4e9,"TERRAIN_METALSMOOTH"));
+			
+			if (p_point->mpNext)
+			{
+				Script::CArray *p_links = new Script::CArray();
+				p_links->SetSizeAndType(1,ESYMBOLTYPE_INTEGER);
+				p_links->SetInteger(0, node_index);
+				p_node->AddArrayPointer(CRCD(0x2e7d5ee7,"Links"), p_links);
+			}			
+
+			// An attempt to make graffiti mode able to tag created rails, didn't work though
+			// (TT11801)
+			//p_node->AddChecksum(NONAME,CRCD(0x1645b830,"TrickObject"));
+			//char p_cluster_name[20];
+			//sprintf(p_cluster_name,"RailCluster%d",node_index);
+			//p_node->AddChecksum(CRCD(0x1a3a966b,"Cluster"),Script::GenerateCRC(p_cluster_name));
+			
+			p_point=p_point->mpNext;
+		}	
+		p_rail=p_rail->mpNext;
+		++rail_index;
+	}
+
+	*p_nodeNum=node_index;
+}
+
+void CRailEditorComponent::DeleteDottedLine()
+{
+	if (mp_dotted_line_sector)
+	{
+		Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+		Dbg_MsgAssert(p_cloned_scene,("Missing cloned scene!"));
+		p_cloned_scene->DeleteSector(mp_dotted_line_sector);
+		
+		mp_dotted_line_sector=NULL;
+		m_dotted_line_sector_name=0;
+
+		#ifdef __PLAT_NGC__
+		Nx::CEngine::sFinishRendering();
+		#endif
+		
+		p_cloned_scene->UpdateSuperSectors();
+	}	
+}
+
+void CRailEditorComponent::DrawDottedLine(Mth::Vector& pos)
+{
+	// Do nothing if there is no last position to draw from.
+	if (!mp_current_rail || !mp_current_rail->mpRailPoints)
+	{
+		return;
+	}
+	CEditedRailPoint *p_last_point=mp_current_rail->mpRailPoints;
+	
+	
+	uint32 dotted_line_sector_name=CRCD(0x5dc3c690,"Sk5Ed_RAdot_Dynamic_Green");
+	if (s_distance_is_too_long(p_last_point->mPos, pos))
+	{
+		if (s_distance_is_way_too_long(p_last_point->mPos, pos))
+		{
+			// If the rail sector is stretched too far it renders all weird, so don't draw it at all.
+			DeleteDottedLine();
+			return;
+		}	
+
+		dotted_line_sector_name=CRCD(0x8dda17d6,"Sk5Ed_RADot_Dynamic");
+	}
+	else if (p_last_point->mpNext)
+	{
+		if (!s_angle_is_ok_to_grind(p_last_point->mpNext->mPos, p_last_point->mPos, pos))
+		{
+			dotted_line_sector_name=CRCD(0x8dda17d6,"Sk5Ed_RADot_Dynamic");
+		}
+	}		
+	
+	
+	// Clone the sector if the dotted line does not exist or needs changing colour
+	if (!mp_dotted_line_sector || dotted_line_sector_name != m_dotted_line_sector_name)
+	{
+		DeleteDottedLine();
+		mp_dotted_line_sector=s_clone_sector(dotted_line_sector_name);
+		m_dotted_line_sector_name=dotted_line_sector_name;
+	}
+	
+	Mth::Vector dummy;
+	s_calculate_rail_sector_vertex_coords(dummy, p_last_point->mPos, pos, 
+										  mp_dotted_line_sector, m_dotted_line_sector_name,
+										  NULL, false);
+
+	if (dotted_line_sector_name==CRCD(0x8dda17d6,"Sk5Ed_RADot_Dynamic"))
+	{
+		int flash_rate=Script::GetInteger(CRCD(0xeebe4394,"RailEditorRedLineFlashRate"));
+		uint32 vis=0xffffffff;
+		if (s_highlight_flash_counter >= flash_rate/2)
+		{
+			vis=0;
+		}
+			
+		++s_highlight_flash_counter;
+		if (s_highlight_flash_counter >= flash_rate)
+		{
+			s_highlight_flash_counter=0;
+		}	
+		mp_dotted_line_sector->SetVisibility(vis);
+	}
+										  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CRailEditorComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case 0x8f3e0b97: // GetCursorPosition
+		{
+			Mth::Vector cursor_pos(0.0f,0.0f,0.0f);		
+			float cursor_height=0.0f;
+			float cursor_angle=0.0f;
+			get_pos_from_camera_component(&cursor_pos,&cursor_height,&cursor_angle);
+			cursor_pos[Y]+=cursor_height;
+			
+			pScript->GetParams()->AddVector(CRCD(0x7f261953,"Pos"),cursor_pos[X],cursor_pos[Y],cursor_pos[Z]);
+			pScript->GetParams()->AddFloat(CRCD(0xff7ebaf6,"Angle"),mp_editor_camera_component->GetCursorOrientation()*180.0f/3.141592654f);
+			break;
+		}
+		
+		// Returns false if a new rail could not be created, due to the limit on the number of rails 
+		// being reached.
+		case 0xd2c98192: // NewRail
+		{
+			if (GetNumFreePoints()==0)
+			{
+				return CBaseComponent::MF_FALSE;
+			}
+				
+			NewRail();
+			break;
+		}
+
+		case 0x31f66e6b: // MaxRailsReached
+		{
+			if (GetNumFreePoints()==0)
+			{
+				return CBaseComponent::MF_TRUE;
+			}
+			else
+			{
+				return CBaseComponent::MF_FALSE;
+			}	
+			break;
+		}
+			
+		case 0x5828e852: // AddNewPosition
+		{
+			Mth::Vector pos;
+			pos.Set();
+			pParams->GetVector(CRCD(0x7f261953,"pos"),&pos);
+			if (mp_current_rail)
+			{
+				CEditedRailPoint *p_last_point=mp_current_rail->mpRailPoints;
+				if (p_last_point)
+				{
+					// Don't allow placement of points that are too close, or segments that are too steep
+					if (!s_positions_OK(p_last_point->mPos, pos))
+					{
+						// But return true anyway, otherwise the script will make the cursor switch out
+						// of rail-layout mode.
+						return CBaseComponent::MF_TRUE;
+					}
+				}	
+
+				// Don't allow points to be placed too close to the boundary of the park (TT5464)
+				if (!Ed::IsWithinParkBoundaries(pos,PARK_BOUNDARY_MARGIN))
+				{
+					if (p_last_point)
+					{
+						// We must be in rail-layout mode, so do not add the point but return true
+						// so that the cursor stays in rail layout mode.
+						return CBaseComponent::MF_TRUE;
+					}
+					else	
+					{
+						return CBaseComponent::MF_FALSE;
+					}	
+				}
+						
+				Spt::SingletonPtr p_editor;
+				if (p_editor->IsParkFull())
+				{
+					return CBaseComponent::MF_FALSE;
+				}
+		
+				CEditedRailPoint *p_point=mp_current_rail->AddPoint();
+				if (p_point)
+				{
+					p_point->mPos=pos;
+					p_point->mHeightAboveGround=pos[Y]-p_point->FindGroundY();
+					p_point->mHasPost=pParams->ContainsFlag(CRCD(0xcb4b4880,"AddPost"));
+					p_point->UpdateRailGeometry();
+					p_point->UpdatePostGeometry();
+					return CBaseComponent::MF_TRUE;
+				}
+			}
+			
+			return CBaseComponent::MF_FALSE;
+			break;
+		}
+			
+		case 0x9c0762c4: // ClearRailEditor
+		{
+			Clear();
+			break;
+		}
+		
+		case 0x1cc3a173: // SetEditingMode
+		{
+			pParams->GetChecksum(CRCD(0x6835b854,"mode"),&m_mode);
+			
+			Script::CStruct *p_params=new Script::CStruct;
+			p_params->AddChecksum(CRCD(0x6835b854,"mode"),CRCD(0xffd81c08,"rail_placement"));
+			Script::RunScript(CRCD(0x9db065ad,"parked_set_helper_text_mode"),p_params);
+			delete p_params;
+			break;
+		}
+		
+		case 0x63dd1204: // GetEditingMode
+		{
+			pScript->GetParams()->AddChecksum(CRCD(0x6835b854,"mode"),m_mode);
+			break;
+		}	
+		
+		case 0xaa438d71: // DrawDottedLine
+		{
+			Mth::Vector pos;
+			pParams->GetVector(CRCD(0x7f261953,"pos"),&pos);
+			DrawDottedLine(pos);
+			break;
+		}
+
+		case 0x9386dcd9: // DeleteDottedLine
+		{
+			DeleteDottedLine();
+			break;
+		}
+		
+		case 0x6d2eee1b: // UpdateRailPointPosition
+		{
+			uint32 rail_id=0;
+			pParams->GetChecksum(CRCD(0xa61e7cd9,"rail_id"),&rail_id);
+			
+			int rail_point_index=0;
+			pParams->GetInteger(CRCD(0xab3c14,"rail_point_index"),&rail_point_index);
+			
+			Mth::Vector pos;
+			pParams->GetVector(CRCD(0x7f261953,"Pos"),&pos);
+
+			CEditedRail *p_rail=GetRail(rail_id);
+			if (p_rail)
+			{
+				// UpdateRailPointPosition will always write in the new position, but returns
+				// false if the position is bad in that it is too close to an adjacent point
+				// or causes the rail to be too steep.
+				// It still writes in the position so that the rail always stays attached to
+				// the cursor as the user moves it around.
+				if (!p_rail->UpdateRailPointPosition(rail_point_index, pos, 
+													 (CEditedRail::EUpdateSuperSectors)pParams->ContainsFlag(CRCD(0xddf9a2bb,"UpdateSuperSectors"))))
+				{
+					return CBaseComponent::MF_FALSE;
+				}	
+			}	
+			break;
+		}
+
+		case 0x8798e959: // HighlightRailPoint
+		{
+			uint32 rail_id=0;
+			pParams->GetChecksum(CRCD(0xa61e7cd9,"rail_id"),&rail_id);
+			
+			int rail_point_index=0;
+			pParams->GetInteger(CRCD(0xab3c14,"rail_point_index"),&rail_point_index);
+
+			CEditedRail *p_rail=GetRail(rail_id);
+			if (p_rail)
+			{
+				CEditedRailPoint *p_point=p_rail->GetRailPointFromIndex(rail_point_index);
+				p_point->Highlight((CEditedRailPoint::EFlash)pParams->ContainsFlag(CRCD(0x5031a0fc,"Flash")), 
+								   (CEditedRailPoint::EEndPosts)pParams->ContainsFlag(CRCD(0x1a52e4b8,"IncludeEndPosts")));
+			}					   
+			break;
+		}
+
+		case 0x6716227c: // DeleteRailPoint
+		{
+			uint32 rail_id=0;
+			pParams->GetChecksum(CRCD(0xa61e7cd9,"rail_id"),&rail_id);
+			CEditedRail *p_rail=GetRail(rail_id);
+			if (!p_rail)
+			{
+				return CBaseComponent::MF_FALSE;
+			}
+			
+			int rail_point_index=0;
+			pParams->GetInteger(CRCD(0xab3c14,"rail_point_index"),&rail_point_index);
+			CEditedRailPoint *p_point=p_rail->GetRailPointFromIndex(rail_point_index);
+			if (!p_point)
+			{
+				return CBaseComponent::MF_FALSE;
+			}
+			
+			DeleteRailPoint(p_rail, p_point);
+			RemoveEmptyAndSinglePointRails();
+			break;
+		}
+			
+		case 0xd5a87eee: // UnHighlightAllRails
+		{
+			UnHighlightAllRails();
+			break;
+		}	
+
+		case 0xfb6a0888: // GetEditedRailInfo
+		{
+			uint32 rail_id=0;
+			pParams->GetChecksum(CRCD(0xa61e7cd9,"rail_id"),&rail_id);
+			CEditedRail *p_rail=GetRail(rail_id);
+			
+			if (pParams->ContainsFlag(CRCD(0xe234f2b2,"CurrentRail")))
+			{
+				p_rail=mp_current_rail;
+			}
+			
+			if (p_rail)
+			{
+				pScript->GetParams()->AddChecksum(CRCD(0xa61e7cd9,"rail_id"),p_rail->mId);
+				pScript->GetParams()->AddInteger(CRCD(0x6164266e,"num_points"),p_rail->CountPoints());
+			}	
+			else
+			{
+				return CBaseComponent::MF_FALSE;
+			}	
+			break;
+		}
+			
+		case 0x353ea2a9: // DeleteRail
+		{
+			uint32 rail_id=0;
+			pParams->GetChecksum(CRCD(0xa61e7cd9,"rail_id"),&rail_id);
+			CEditedRail *p_rail=GetRail(rail_id);
+			if (p_rail)
+			{
+				DeleteRail(p_rail);
+			}
+			break;
+		}
+
+		case 0xd70781c3: // DestroyEditedRailSectors
+		{
+			CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=true;
+			if (pParams->ContainsFlag(CRCD(0x23a11cbc,"DoNotUpdateSuperSectors")))
+			{
+				CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=false;
+			}	
+			DestroyRailGeometry();
+			DestroyPostGeometry();
+			CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=true;
+			break;
+		}
+		
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+int CRailEditorComponent::GetNumFreePoints()
+{
+	return MAX_EDITED_RAIL_POINTS-CEditedRailPoint::SGetNumUsedItems();
+}
+
+int CRailEditorComponent::GetNumFreeRails()
+{
+	return MAX_EDITED_RAILS-CEditedRail::SGetNumUsedItems();
+}
+
+CEditedRail *CRailEditorComponent::get_rail_from_cluster_name(uint32 clusterChecksum)
+{
+	char p_rail_cluster_name[50];
+	int rail_index=0;
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		sprintf(p_rail_cluster_name,"CreatedRailCluster%d",rail_index);
+		if (clusterChecksum == Script::GenerateCRC(p_rail_cluster_name))
+		{
+			return p_rail;
+		}	
+			
+		p_rail=p_rail->mpNext;
+		++rail_index;
+	}	
+	return NULL;
+}
+
+// Used by graffiti games
+void CRailEditorComponent::ModulateRailColor(uint32 clusterChecksum, int seqIndex)
+{
+	CEditedRail *p_rail=get_rail_from_cluster_name(clusterChecksum);
+	if (p_rail)
+	{
+		p_rail->ModulateRailColor(seqIndex);
+	}	
+}
+
+// Used by graffiti games
+void CRailEditorComponent::ClearRailColor(uint32 clusterChecksum)
+{
+	CEditedRail *p_rail=get_rail_from_cluster_name(clusterChecksum);
+	if (p_rail)
+	{
+		p_rail->ClearRailColor();
+	}	
+}
+
+// Used for switching off collision on any created-rail sectors whilst the rail editor
+// cursor is doing collision checks.
+// This is because the rail sectors have a vertical collidable poly, which occasionally does cause
+// collisions, making the cursor ping up into the air. Must be some sort of floating point innaccuracy
+// (or too much accuracy), probably because the collision check vector is straight down the vertical poly.
+void CRailEditorComponent::SetSectorActiveStatus(bool active)
+{
+	CEditedRail *p_rail=mp_edited_rails;
+	while (p_rail)
+	{
+		p_rail->SetSectorActiveStatus(active);
+		p_rail=p_rail->mpNext;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CRailEditorComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CRailEditorComponent::GetDebugInfo"));
+
+	p_info->AddInteger(CRCD(0x8c75a6c1,"NumFreeRailPoints"),GetNumFreePoints());
+	p_info->AddInteger(CRCD(0xe954b3d4,"NumFreeRails"),GetNumFreeRails());
+	
+	if (mp_current_rail)
+	{
+		Script::CStruct *p_struct=new Script::CStruct;
+		mp_current_rail->GetDebugInfo(p_struct);
+		p_info->AddStructurePointer(CRCD(0xe234f2b2,"CurrentRail"),p_struct);
+	}
+	else
+	{
+		p_info->AddChecksum(CRCD(0xe234f2b2,"CurrentRail"),CRCD(0xda3403b0,"NULL"));
+	}
+		
+	int num_rails=count_rails();
+	if (num_rails)
+	{
+		Script::CArray *p_array=new Script::CArray;
+		p_array->SetSizeAndType(num_rails,ESYMBOLTYPE_STRUCTURE);
+		
+		int i=0;
+		CEditedRail *p_rail=mp_edited_rails;
+		while (p_rail)
+		{
+			Script::CStruct *p_struct=new Script::CStruct;
+			p_rail->GetDebugInfo(p_struct);
+			p_array->SetStructure(i,p_struct);
+			++i;
+			p_rail=p_rail->mpNext;
+		}
+		Dbg_MsgAssert(i==num_rails,("Eh ?"));
+		
+		p_info->AddArrayPointer(CRCD(0x7e17dfa9,"Rails"),p_array);
+	}
+		
+	
+	// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CRailEditorComponent *GetRailEditor()
+{
+	Obj::CCompositeObject *p_obj=(Obj::CCompositeObject*)Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x5b509ad3,"RailEditor"));
+	Dbg_MsgAssert(p_obj,("No RailEditor object"));
+	Obj::CRailEditorComponent *p_rail_editor=GetRailEditorComponentFromObject(p_obj);
+	Dbg_MsgAssert(p_rail_editor,("No rail editor component ???"));
+	
+	return p_rail_editor;
+}
+
+}
+
+
diff --git a/Code/Sk/Components/RailEditorComponent.h b/Code/Sk/Components/RailEditorComponent.h
new file mode 100644
index 0000000..a09910b
--- /dev/null
+++ b/Code/Sk/Components/RailEditorComponent.h
@@ -0,0 +1,326 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       RailEditorComponent.h
+//* OWNER:          Kendall Harrison
+//* CREATION DATE:  6/19/2003
+//****************************************************************************
+
+#ifndef __COMPONENTS_RAILEDITORCOMPONENT_H__
+#define __COMPONENTS_RAILEDITORCOMPONENT_H__
+
+#include 
+#include 
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef __OBJECT_BASECOMPONENT_H__
+#include 
+#endif
+
+#ifndef __GFX_IMAGE_IMAGEBASIC_H
+#include 
+#endif
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_RAILEDITOR CRCD(0x5b509ad3,"RailEditor")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetRailEditorComponent() ((Obj::CRailEditorComponent*)GetComponent(CRC_RAILEDITOR))
+#define		GetRailEditorComponentFromObject(pObj) ((Obj::CRailEditorComponent*)(pObj)->GetComponent(CRC_RAILEDITOR))
+
+namespace Nx
+{
+	class CSector;
+}
+	
+namespace Obj
+{
+extern Mth::Vector ZeroVector;
+
+enum
+{
+	MAX_EDITED_RAIL_POINTS=400,
+	
+	// Single point rails are not allowed, so the maximum possible number of rails will be half the
+	// number of points, since the shortest rail will have 2 points.
+	MAX_EDITED_RAILS=MAX_EDITED_RAIL_POINTS/2,
+};
+
+struct SCompressedRailPoint
+{
+	// These are the original float values * 8
+	sint16 mX,mY,mZ;
+	// This is the original float height * 16
+	uint16 mHeight;
+	uint16 mHasPost;
+};
+	
+class CEditedRailPoint : public Mem::CPoolable
+{
+public:
+	CEditedRailPoint();
+	~CEditedRailPoint();
+
+	void WriteCompressedRailPoint(SCompressedRailPoint *p_dest);
+	
+	void UpdateRailGeometry(Mth::Vector& rotateCentre=ZeroVector, Mth::Vector& newCentre=ZeroVector, float degrees=0.0f);
+	void UpdatePostGeometry(Mth::Vector& rotateCentre=ZeroVector, Mth::Vector& newCentre=ZeroVector, float degrees=0.0f);
+	void DestroyRailGeometry();
+	void DestroyPostGeometry();
+	
+	float FindGroundY();
+	void AdjustY();
+	void InitialiseHeight();
+
+	// These enums are just to avoid passing multiple bools to Highlight()
+	enum EFlash
+	{
+		DONT_FLASH=0,
+		FLASH=1,
+	};
+	enum EEndPosts
+	{
+		DONT_INCLUDE_END_POSTS=0,
+		INCLUDE_END_POSTS=1,
+	};	
+	void Highlight(EFlash flash, EEndPosts includeEndPosts);
+	void UnHighlight();
+
+	void SetColor(Image::RGBA rgba);
+	void ClearColor();
+
+	void SetSectorActiveStatus(bool active);
+	
+	bool AngleIsOKToGrind();
+		
+	// This class needs to be kept as small as possible since a pool of them exists.	
+	
+	// MEMOPT: Change pos to be 3 sint16's, would save 10K for 400 points.
+	// Used x(fixed)=int(9.0f*x(float)+0.5f) if x>0, x(fixed)=int(9.0f*x(float)-0.5f) if x<0
+	Mth::Vector mPos;
+	//sint16 m_x,m_y,m_z;
+	
+	// MEMOPT: Change to be bitfield of flags.
+	bool mHasPost;
+	bool mHighlighted;
+	
+	// The height is stored so that if the ground moves up or down, mPos[Y] can be recalculated
+	// so as to maintain the post height.
+	float mHeightAboveGround;
+
+	
+	CEditedRailPoint *mpNext;	
+	// TODO: MEMOPT
+	// Don't really need the list to be doubly linked
+	CEditedRailPoint *mpPrevious;	
+
+
+	// The rail geometry joining the last point to this point.
+	// If this is the first point of the rail, mpClonedRailSector will be NULL.
+	Nx::CSector *mpClonedRailSector;	
+
+	// The post geometry joining this point to the ground. May be NULL, since the user chooses
+	// whether they want a post.
+	// Also, mpPostSector may be NULL even when mHasPost is true, because we don't always want 
+	// the geometry for the rail to exist.
+	// For example, when loading a park off the memory card in the skateshop it will set the mPos
+	// and mHasPost members, but the sector pointers will remain NULL until it is time to build the park.
+	Nx::CSector *mpPostSector;
+};
+
+class CEditedRail : public Mem::CPoolable
+{
+public:
+	CEditedRail();
+	~CEditedRail();
+
+	void Clear();
+
+	CEditedRail *mpNext;	
+	CEditedRail *mpPrevious;
+	
+	CEditedRailPoint *mpRailPoints;
+	
+	uint32 mId;
+
+	CEditedRailPoint *AddPoint();
+
+	void AdjustYs();
+	void InitialiseHeights();
+	void UpdateRailGeometry(Mth::Vector& rotateCentre=ZeroVector, Mth::Vector& newCentre=ZeroVector, float degrees=0.0f);
+	void UpdatePostGeometry(Mth::Vector& rotateCentre=ZeroVector, Mth::Vector& newCentre=ZeroVector, float degrees=0.0f);
+	void DestroyRailGeometry();
+	void DestroyPostGeometry();
+	
+	int CountPoints();
+	bool FindNearestRailPoint(Mth::Vector &pos, 
+							  Mth::Vector *p_nearest_pos, float *p_dist_squared, int *p_rail_point_index, 
+							  int ignore_index=-1);
+							  
+	int CountPointsInArea(float x0, float z0, float x1, float z1);
+	void DuplicateAndAddPoint(CEditedRailPoint *p_point);
+	CEditedRail *GenerateDuplicateRails(float x0, float z0, float x1, float z1, CEditedRail *p_head);	
+							  
+	enum EUpdateSuperSectors
+	{
+		DONT_UPDATE_SUPER_SECTORS=0,
+		UPDATE_SUPER_SECTORS=1
+	};	
+	bool UpdateRailPointPosition(int rail_point_index, Mth::Vector &pos, EUpdateSuperSectors updateSuperSectors);
+	CEditedRailPoint *GetRailPointFromIndex(int index);
+	void UnHighlight();
+
+	CEditedRailPoint *DeleteRailPoint(CEditedRailPoint *p_point);
+	
+	void CopyRail(CEditedRail *p_source_rail);
+	void RotateAndTranslate(Mth::Vector& rotateCentre, Mth::Vector& newCentre, float degrees);
+
+	SCompressedRailPoint *WriteCompressedRailPoints(SCompressedRailPoint *p_dest);
+
+	bool ThereAreRailPointsOutsideArea(float x0, float z0, float x1, float z1);
+
+	// Used by graffiti games
+	void ModulateRailColor(int seqIndex);
+	void ClearRailColor();
+
+	void SetSectorActiveStatus(bool active);
+	
+	void GetDebugInfo( Script::CStruct* p_info );
+};
+
+class CInputComponent;
+class CEditorCameraComponent;
+
+class CRailEditorComponent : public CBaseComponent
+{
+	CInputComponent 		*mp_input_component;
+	CEditorCameraComponent 	*mp_editor_camera_component;
+	void get_pointers_to_required_components();
+
+	void get_pos_from_camera_component(Mth::Vector *p_pos, float *p_height, float *p_angle);
+		
+	int count_rails();
+	
+	CEditedRail *mp_edited_rails;
+	CEditedRail *mp_current_rail;
+	
+	// The mode that the cursor is in, ie FreeRoaming, RailLayout or Grab
+	// This is stored as a checksum rather than an enum because it will mostly
+	// be scripts that are doing logic based on the mode, and this way I don't
+	// have to define a bunch of script global integers to match the enum.
+	uint32 m_mode;
+	
+	uint32 m_dotted_line_sector_name;	
+	Nx::CSector *mp_dotted_line_sector;
+
+	enum
+	{
+		COMPRESSED_RAILS_BUFFER_SIZE = sizeof(uint16) + // Num rails
+									   MAX_EDITED_RAILS * sizeof(uint16) + // Num points in each rail
+									   MAX_EDITED_RAIL_POINTS * sizeof(SCompressedRailPoint), // The rail points
+	};									   
+		
+	uint8 mp_compressed_rails_buffer[COMPRESSED_RAILS_BUFFER_SIZE];
+	void generate_compressed_rails_buffer();
+	void clear_compressed_rails_buffer();
+	
+	// Used by graffiti games
+	CEditedRail *get_rail_from_cluster_name(uint32 clusterChecksum);
+	
+public:
+    CRailEditorComponent();
+    virtual ~CRailEditorComponent();
+
+public:
+	uint8 *							GetCompressedRailsBuffer();
+	int								GetCompressedRailsBufferSize() {return COMPRESSED_RAILS_BUFFER_SIZE;}
+	void							SetCompressedRailsBuffer(uint8 *p_buffer);
+	void							InitUsingCompressedRailsBuffer();
+	
+	void							Clear();
+	CEditedRail *					NewRail();
+	bool							NewRail(const char *p_railName);
+	void							DeleteRail(CEditedRail *p_rail);
+	CEditedRail *					GetRail(uint32 id);
+	void							RemoveEmptyAndSinglePointRails();
+	
+	int								GetTotalRailNodesRequired();
+	void							SetUpRailNodes(Script::CArray *p_nodeArray, int *p_nodeNum, uint32 firstID);
+
+	void							AdjustYs();
+	void							InitialiseHeights();
+	void							UpdateRailGeometry();
+	void							UpdatePostGeometry();
+	void							DestroyRailGeometry();
+	void							DestroyPostGeometry();
+
+	void							RefreshGeometry();
+	void							UnHighlightAllRails();
+	
+	bool							DeleteRailPoint(CEditedRail *p_rail, CEditedRailPoint *p_point);
+
+	enum EClipType
+	{
+		DELETE_POINTS_OUTSIDE=0,
+		DELETE_POINTS_INSIDE=1
+	};
+	bool							ClipRail(CEditedRail *p_rail, float x0, float z0, float x1, float z1, EClipType clipType);
+	void							ClipRails(float x0, float z0, float x1, float z1, EClipType clipType);
+	bool							ThereAreRailPointsOutsideArea(float x0, float z0, float x1, float z1);
+	
+
+	int								CountPointsInArea(float x0, float z0, float x1, float z1);
+	bool							AbleToCopyRails(float x0, float z0, float x1, float z1);
+	CEditedRail *					GenerateDuplicateRails(float x0, float z0, float x1, float z1);
+									
+	bool							FindNearestRailPoint(Mth::Vector &pos, Mth::Vector *p_nearest_pos, 
+														 float *p_dist_squared, 
+														 uint32 *p_rail_id, int *p_rail_point_index,
+														 uint32 ignore_rail_index=0, int ignore_rail_point_index=-1);
+
+	
+	void							DeleteDottedLine();
+	void							DrawDottedLine(Mth::Vector& pos);
+
+	int								GetNumFreePoints();
+	int								GetNumFreeRails();
+	
+	// Used by graffiti games
+	void							ModulateRailColor(uint32 clusterChecksum, int seqIndex);
+	void							ClearRailColor(uint32 clusterChecksum);
+
+	void							SetSectorActiveStatus(bool active);
+	
+	// A quick hack to allow UpdateSuperSectors not to be called when loading a new park in a net game.
+	// If it is called, the whole park disappears due to all the sectors having been flagged for deletion.
+	static bool						sUpdateSuperSectorsAfterDeletingRailSectors;
+	
+	// For reading/writing to memcard
+	void							WriteIntoStructure(Script::CStruct *p_info);
+	void							ReadFromStructure(Script::CStruct *p_info);
+
+	virtual	void 					Finalize();
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+	virtual void					Hide( bool shouldHide );
+
+	static CBaseComponent*			s_create();
+};
+
+Obj::CRailEditorComponent *GetRailEditor();
+
+
+// This will print the memory used by the rail editor component, via a compiler error.
+// Need to keep it within 30K, since it exists in memory permanently.
+//char p[(sizeof(CRailEditorComponent)+MAX_EDITED_RAIL_POINTS*sizeof(CEditedRailPoint)+MAX_EDITED_RAILS*sizeof(CEditedRail))/0];
+
+} // namespace Obj
+
+#endif // #ifndef __COMPONENTS_RAILEDITORCOMPONENT_H__
diff --git a/Code/Sk/Components/SkaterAdjustPhysicsComponent.cpp b/Code/Sk/Components/SkaterAdjustPhysicsComponent.cpp
new file mode 100644
index 0000000..d3183a9
--- /dev/null
+++ b/Code/Sk/Components/SkaterAdjustPhysicsComponent.cpp
@@ -0,0 +1,365 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterAdjustPhysicsComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/26/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+void	TrackingLine2(int type, Mth::Vector &start, Mth::Vector &end)
+{
+	if	(Script::GetInt(CRCD(0x19fb78fa,"output_tracking_lines")))
+	{
+//		Gfx::AddDebugLine(start, end, 0xffffff);
+		printf ("Tracking%d %.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n",type, start[X], start[Y], start[Z], end[X], end[Y], end[Z]);
+	}
+}
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterAdjustPhysicsComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterAdjustPhysicsComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterAdjustPhysicsComponent::CSkaterAdjustPhysicsComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERADJUSTPHYSICS );
+	
+	mp_core_physics_component = NULL;
+	mp_model_component = NULL;
+	mp_shadow_component = NULL;
+	mp_movable_contact_component = NULL;
+	
+	m_uber_frigged_this_frame = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterAdjustPhysicsComponent::~CSkaterAdjustPhysicsComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterAdjustPhysicsComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterAdjustPhysicsComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterAdjustPhysicsComponent::Finalize (   )
+{
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	mp_model_component = GetModelComponentFromObject(GetObject());
+	mp_shadow_component = GetShadowComponentFromObject(GetObject());
+	mp_movable_contact_component = GetMovableContactComponentFromObject(GetObject());
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+
+	Dbg_Assert(mp_core_physics_component);
+	Dbg_Assert(mp_model_component);
+	Dbg_Assert(mp_shadow_component);
+	Dbg_Assert(mp_movable_contact_component);
+	Dbg_Assert(mp_state_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterAdjustPhysicsComponent::Update()
+{
+	// If on the ground, or moving downwards, then allow us to hit a car again		
+	if (mp_core_physics_component->GetState() == GROUND || GetObject()->m_vel[Y] < 0.0f)
+	{
+		mp_core_physics_component->SetFlagTrue(CAN_HIT_CAR);
+	}
+
+	if (!mp_core_physics_component->GetFlag(SKITCHING))
+	{
+		check_inside_objects();
+	}
+
+	uber_frig();
+
+	GetObject()->m_old_pos = GetObject()->m_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterAdjustPhysicsComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterAdjustPhysicsComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterAdjustPhysicsComponent::GetDebugInfo"));
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+					
+void CSkaterAdjustPhysicsComponent::uber_frig (   )
+{
+	// Uberfrig() will attempt to see if we are in a region with valid collision
+	// if were are not, then it will attempt to move us back to a position that is valid; generally this is the old position
+	
+	m_uber_frigged_this_frame = false;
+
+	if (GetObject()->m_pos != mp_core_physics_component->m_safe_pos)
+	{		
+		#ifdef	__NOPT_ASSERT__
+		if (! (Tmr::GetRenderFrame() & 15) )
+		{
+			TrackingLine2(4, GetObject()->m_old_pos, GetObject()->m_pos);	  // 4 = line
+		}
+		#endif
+		
+		mp_core_physics_component->m_col_start = GetObject()->m_pos;
+		mp_core_physics_component->m_col_end = GetObject()->m_pos;
+
+		// Very minor adjustment to move origin away from vert walls
+		mp_core_physics_component->m_col_start += GetObject()->m_matrix[Y] * 0.001f;
+		
+		mp_core_physics_component->m_col_start[Y] += 8.0f;
+		mp_core_physics_component->m_col_end[Y] -= FEET(400);
+			   
+		if (mp_core_physics_component->get_member_feeler_collision())
+		{
+			// if we are in "contact" with something, and on the ground or in air, then that should be what we collide with
+			// so if not, we need to leave contact, and impart the final velocity
+			// ("on ground" just means skating on something, which could be the moving object
+			if (!mp_core_physics_component->GetFlag(SKITCHING) && !mp_core_physics_component->GetFlag(SPINE_PHYSICS))
+			{
+				// but we leave it if skitching, or attempting to spin between them
+				if (mp_movable_contact_component->HaveContact()
+					&& (mp_core_physics_component->GetState() == GROUND || mp_core_physics_component->GetState() == AIR))
+				{
+					if (!mp_core_physics_component->m_feeler.IsMovableCollision() 
+						|| mp_core_physics_component->m_feeler.GetMovingObject() != mp_movable_contact_component->GetContact()->GetObject())
+					{
+						GetObject()->m_vel += mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+						DUMP_VELOCITY;
+						mp_movable_contact_component->LoseAnyContact();
+					}
+				}
+			}
+			
+			static Mth::Vector xa, xb, xc;
+
+			float height = GetObject()->m_pos[Y] - mp_core_physics_component->m_feeler.GetPoint()[Y];
+
+			mp_state_component->m_height = height;
+			
+			// if we are below the ground, then move him up
+			if (height < 0.001f)
+			{
+				GetObject()->m_pos[Y] = mp_core_physics_component->m_feeler.GetPoint()[Y] + 0.001f; 	// above ground by a fraction of an inch
+				DUMP_POSITION;
+				height = 0.0f;
+			}
+
+			if (mp_core_physics_component->GetState() != RAIL)
+			{
+				mp_model_component->ApplyLightingFromCollision(mp_core_physics_component->m_feeler);
+			}
+			
+			// Store these values off for the simple shadow calculation.
+			mp_shadow_component->SetShadowPos(mp_core_physics_component->m_feeler.GetPoint());
+			mp_shadow_component->SetShadowNormal(mp_core_physics_component->m_feeler.GetNormal()); 
+		
+			if (Ed::CParkEditor::Instance()->UsingCustomPark())
+			{
+				// when we are in the park editor, there are other checks we might want to do
+				CFeeler feeler;
+				
+				// ignore non-collidable, under-ok
+				feeler.SetIgnore(mFD_NON_COLLIDABLE | mFD_UNDER_OK, 0);
+				feeler.SetStart(GetObject()->m_pos + Mth::Vector(0.0f, 3000.0f, 0.0f, 0.0f));
+				feeler.SetEnd(GetObject()->m_pos + GetObject()->m_matrix[Y]);
+				if (feeler.GetCollision())
+				{
+					// Something above me that I'm not supposed to be under; just move the skater back to the old pos, and flip him
+					
+					// if only just under it, then pop me up
+					if ((GetObject()->m_pos - feeler.GetPoint()).LengthSqr() < 6.0f * 6.0f)
+					{
+						GetObject()->m_pos = feeler.GetPoint();
+						DUMP_POSITION;
+					}
+					else
+					{
+						// if we are not moving, then pop us up above the face we detected a collision with
+						if ((GetObject()->m_pos - mp_core_physics_component->m_safe_pos).LengthSqr() < 1.0f * 1.0f)
+						{
+							GetObject()->m_pos = feeler.GetPoint();
+							DUMP_POSITION;
+						}
+						else
+						{
+							GetObject()->m_pos = mp_core_physics_component->m_safe_pos;
+							DUMP_POSITION;
+						}
+						GetObject()->m_vel = GetObject()->m_vel * (-1.0f / 2.0f);
+						DUMP_VELOCITY;
+						if (mp_core_physics_component->GetState() == RAIL)
+						{
+							mp_core_physics_component->SetState(AIR);			// Stop grinding
+							GetObject()->SelfEvent(CRCD(0xafaa46ba, "OffRail"));					// regular exception
+							GetObject()->m_pos[Y] += 2.0f;								// make sure we are out of the rail...
+							DUMP_POSITION;
+						}
+					}
+				}
+			}
+			
+			m_nudge = 0;	
+			
+			 // Mick:  Set a safe position.  Here there was a collision, so safe
+			mp_core_physics_component->m_safe_pos = GetObject()->m_pos;
+			
+		}
+		else
+		{
+			// I'm going to assume that if we are on a rail, then the uberfrig is just a problem with the collision getting parallel to the wall		
+			// so I can safely ignore it, as no rail should be laid out into nothingness
+			// except in the park editor, dammit...
+			if ((mp_core_physics_component->GetState() != RAIL && mp_core_physics_component->GetState() != LIP)
+				|| Ed::CParkEditor::Instance()->UsingCustomPark())
+			{
+				m_uber_frigged_this_frame = true;
+				
+				// first nudge is zero
+				static Mth::Vector uberfrig_nudges[] =
+				{
+					Mth::Vector(0.0f, 0.0f, 0.0f),
+					Mth::Vector(0.1f, 0.1f, 0.1f),
+					Mth::Vector(-0.2f, 0.0f, 0.0f),
+					Mth::Vector(0.0f, 0.0f, -0.2f),
+					Mth::Vector(0.2f, 0.0f, 0.0f)
+				};
+				
+				if (m_nudge < 5)
+				{
+					GetObject()->m_pos = mp_core_physics_component->m_safe_pos + uberfrig_nudges[m_nudge++];
+					DUMP_POSITION;
+				}
+				else
+				{
+					// just reset it, and hope for the best.  Probably we are just over a seam, and will get over it.
+					m_nudge = 0;
+				}
+				
+				GetObject()->m_vel = -GetObject()->m_vel + Mth::Vector(0.101f, 0.101f, 0.1001f);
+				DUMP_VELOCITY;
+				
+				// special case for wallrides, lip tricks and suchlike
+				// just change them to air state; bit of a patch, but prevent you getting stuck in a state													
+				if (mp_core_physics_component->GetState() != GROUND && mp_core_physics_component->GetState() != AIR)
+				{
+					mp_core_physics_component->SetState(AIR);
+				}
+			}
+			
+			mp_state_component->m_height = 0.0f;
+		}
+	} 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterAdjustPhysicsComponent::check_inside_objects (   )
+{
+	if (mp_movable_contact_component->CheckInsideObjects(GetObject()->m_pos, mp_core_physics_component->m_safe_pos))
+	{
+		MESSAGE("found to be inside a moving object");
+		
+		// Whichever way we rcover from being inside a moving object, this abrupt change in velocity and position means any rail grinding might
+		// be messed up, either with zero velocity, or being moved away from the rail; so, the best thing to do is skate off the rail
+
+		if (mp_core_physics_component->GetState() == RAIL)
+		{
+			mp_core_physics_component->SetState(AIR);
+			GetObject()->SelfEvent(CRCD(0xafaa46ba, "OffRail"));
+		}
+		else if (GetObject()->m_pos == mp_core_physics_component->m_safe_pos)
+		{
+			// Set velocity to zero, otherwise we can get stuck inside things as the velocity from this frame will keep pushing you back inside things
+			// especially if m_safe_pos is just inside the object, when there will be no collision detected
+			// If we are on a rail, let them keep the velocity as maybe we can handle it next frame
+			GetObject()->GetVel().Set();
+		}
+	}
+}
+
+}
diff --git a/Code/Sk/Components/SkaterAdjustPhysicsComponent.h b/Code/Sk/Components/SkaterAdjustPhysicsComponent.h
new file mode 100644
index 0000000..2ce6ff0
--- /dev/null
+++ b/Code/Sk/Components/SkaterAdjustPhysicsComponent.h
@@ -0,0 +1,79 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterAdjustPhysicsComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/26/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERADJUSTPHYSICSCOMPONENT_H__
+#define __COMPONENTS_SKATERADJUSTPHYSICSCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERADJUSTPHYSICS CRCD(0x9f9cc949, "SkaterAdjustPhysics")
+
+#define		GetSkaterAdjustPhysicsComponent() ((Obj::CSkaterAdjustPhysicsComponent*)GetComponent(CRC_SKATERADJUSTPHYSICS))
+#define		GetSkaterAdjustPhysicsComponentFromObject(pObj) ((Obj::CSkaterAdjustPhysicsComponent*)(pObj)->GetComponent(CRC_SKATERADJUSTPHYSICS))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterCorePhysicsComponent;
+	class CModelComponent;
+	class CShadowComponent;
+	class CMovableContactComponent;
+	class CSkaterStateComponent;
+
+class CSkaterAdjustPhysicsComponent : public CBaseComponent
+{
+	friend CSkaterNonLocalNetLogicComponent;
+	
+public:
+    CSkaterAdjustPhysicsComponent();
+    virtual ~CSkaterAdjustPhysicsComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+	bool							UberFriggedThisFrame (  ) { return m_uber_frigged_this_frame; }
+	
+private:
+	void							uber_frig (   );
+	void							check_inside_objects (   );
+	
+private:
+	int								m_nudge;						// counter for which nudges to attempt if we get stuck in an uberfrig
+
+	bool							m_uber_frigged_this_frame;
+	
+	// peer components
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+	CModelComponent*				mp_model_component;
+	CShadowComponent*				mp_shadow_component;
+	CMovableContactComponent*		mp_movable_contact_component;
+	CSkaterStateComponent*			mp_state_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterBalanceTrickComponent.cpp b/Code/Sk/Components/SkaterBalanceTrickComponent.cpp
new file mode 100644
index 0000000..6d2beec
--- /dev/null
+++ b/Code/Sk/Components/SkaterBalanceTrickComponent.cpp
@@ -0,0 +1,572 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterBalanceTrickComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  328/3
+//****************************************************************************
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterBalanceTrickComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterBalanceTrickComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterBalanceTrickComponent::CSkaterBalanceTrickComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERBALANCETRICK );
+	
+	mp_animation_component = NULL;
+	
+	mpBalanceParams = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterBalanceTrickComponent::~CSkaterBalanceTrickComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	mManual.Init(GetSkater());
+	mGrind.Init(GetSkater());
+	mLip.Init(GetSkater());
+	mSkitch.Init(GetSkater());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::Finalize (   )
+{
+	
+	mp_animation_component = GetAnimationComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_animation_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::Update()
+{
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterBalanceTrickComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | SetWobbleDetails | gets called from the animation
+		// component's PlayAnim handler (needs to come after the
+		// animation has launched...)
+		case CRCC(0xea6d0efd,"SetWobbleDetails"):
+		{
+			// Intercepts the animation component's PlayAnim member function and handle's skater specific logic
+			
+			// If this is a wobbling balance anim, then allow the manual or grind to programmatically wobble it.
+			if (pParams->ContainsFlag(CRCD(0x6d941203, "Wobble")))
+			{
+				mManual.EnableWobble();
+				mGrind.EnableWobble();
+				mLip.EnableWobble();
+				mSkitch.EnableWobble();
+			}	
+			
+			Script::CStruct* p_wobble_params = Script::GetStructure(CRCD(0x5adb96d4, "DefaultWobbleParams"), Script::ASSERT);
+			pParams->GetStructure(CRCD(0xd095379c, "WobbleParams"), &p_wobble_params);
+			set_wobble_params(p_wobble_params);
+			
+			return CBaseComponent::MF_TRUE;
+		}
+		
+        // @script | DoBalanceTrick | 
+        // @parmopt int | Tweak | 0 | 
+        // @parm name | ButtonA | 
+        // @parm name | ButtonB |                       
+        // @parmopt name | Type | 0 | balance trick type (NoseManual, 
+        // Manual, Grind, Slide, Lip)
+        // @flag DoFlipCheck | If set, the range anim will be played backwards if the skater is flipped.
+        // @flag PlayRangeAnimBackwards | If set, the range anim will be played backwards. Can be
+		// used in conjunction with DoFlipCheck. Ie, if both flags are set, then the anim will be
+		// played forwards if the skater is flipped.
+		// @flag ClearCheese | If set this will reset any previous cheese.
+        // @parmopt structure | BalanceParams | | If not specified then the balance trick will use
+		// the params defined by the script globals LipParams, or ManualParams etc depending on the
+		// Type value. 
+		case CRCC(0x7e92670c, "DoBalanceTrick"):	  
+		{
+			if (mpBalanceParams)
+			{
+				delete mpBalanceParams;
+				mpBalanceParams = NULL;
+			}
+			Script::CStruct* p_balance_params = NULL;
+			if (pParams->GetStructure(CRCD(0x647facc2, "BalanceParams"), &p_balance_params))
+			{
+				set_balance_trick_params(p_balance_params);
+			}
+			
+			int Tweak = 0;
+			pParams->GetInteger(CRCD(0x2bfd3d35, "Tweak"), &Tweak);
+			
+			uint32 ButtonA = 0;
+			uint32 ButtonB = 0;
+			pParams->GetChecksum(CRCD(0x4f1a0b01, "ButtonA"), &ButtonA, Script::ASSERT);
+			pParams->GetChecksum(CRCD(0xd6135abb, "ButtonB"), &ButtonB, Script::ASSERT);
+			
+			uint32 NewBalanceTrickType = 0;
+			pParams->GetChecksum(CRCD(0x7321a8d6, "Type"), &NewBalanceTrickType);
+
+			// Do nothing if already doing the same type of balance trick.  This is so that the meter does not glitch when switching to
+			// a balance trick from another balance trick where there is no StopBalanceTrick script command in between.
+			if (mBalanceTrickType == NewBalanceTrickType) break;
+			mBalanceTrickType = NewBalanceTrickType;
+			
+			bool DoFlipCheck = pParams->ContainsFlag(CRCD(0x1304e677, "DoFlipCheck"));
+			bool PlayRangeAnimBackwards = pParams->ContainsFlag(CRCD(0x8fe31ed, "PlayRangeAnimBackwards"));
+			
+			// This needed because for the extra grind tricks (eg left triangle triangle) the
+			// DoBalanceTrick command gets called twice in a row, once for the first time the grind is detected
+			// by the first triangle, then again once the extra trick is detected.
+			// In that case the cheese needs to be reset otherwise it thinks you've jumped and re-railed & you
+			// fall off straight away.
+			bool clear_cheese=pParams->ContainsFlag(CRCD(0xbbdb0f78,"ClearCheese"));
+				
+			switch (mBalanceTrickType)
+			{
+				case 0:
+					Dbg_MsgAssert(false, ("\n%s\nMust specify a type in DoBalanceTrick, eg Type=NoseManual", pScript->GetScriptInfo()));
+					break;
+					
+				case CRCC(0xac90769, "NoseManual"):
+				case CRCC(0xef24413b, "Manual"):
+					if (clear_cheese)
+					{
+						mManual.Reset();
+					}	
+					mManual.UpdateRecord();
+					mManual.SetUp(ButtonA, ButtonB, Tweak, DoFlipCheck, PlayRangeAnimBackwards);
+					break;
+					
+				case CRCC(0x255ed86f, "Grind"):
+				case CRCC(0x8d10119d, "Slide"):
+					if (clear_cheese)
+					{
+						mGrind.Reset();
+					}	
+					mGrind.UpdateRecord();
+					mGrind.SetUp(ButtonA, ButtonB, Tweak, DoFlipCheck, PlayRangeAnimBackwards);
+					break;
+					
+				case CRCC(0xa549b57b, "Lip"):
+					if (clear_cheese)
+					{
+						mLip.Reset();
+					}	
+					mLip.UpdateRecord();
+					mLip.SetUp(ButtonA, ButtonB, Tweak, DoFlipCheck, PlayRangeAnimBackwards);
+					break;
+					
+				case CRCC(0x3506ce64, "Skitch"):
+					if (clear_cheese)
+					{
+						mSkitch.Reset();
+					}	
+					mSkitch.UpdateRecord();
+					mSkitch.SetUp(ButtonA, ButtonB, Tweak, DoFlipCheck, PlayRangeAnimBackwards);
+					break;
+					
+				default:	
+					Dbg_Assert(false);
+					break;
+			}		
+			break;
+		}
+		
+        // @script | AdjustBalance | Make minor adjustments, nudges, to the current balance trick, if any
+        // @parmopt float | TimeAdd | 0.0 | 
+        // @parmopt float | LeanAdd | 0.0 | 
+        // @parmopt float | SpeedAdd | 0.0 | 
+        // @parmopt float | TimeMult | 1.0 | 
+        // @parmopt float | LeanMult | 1.0 | 
+        // @parmopt float | SpeedMult | 1.0 | 
+		case CRCC(0x7a0dcd4b, "AdjustBalance"):
+		{
+			if (mBalanceTrickType)
+			{
+				float TimeMult = 1.0f;
+				float LeanMult = 1.0f;
+				float SpeedMult = 1.0f;
+				float TimeAdd = 0.0f;
+				float LeanAdd = 0.0f;
+				float SpeedAdd = 0.0f;
+				pParams->GetFloat(CRCD(0x3a4e9413, "TimeAdd"), &TimeAdd);
+				pParams->GetFloat(CRCD(0xb4cecc1, "LeanAdd"), &LeanAdd);
+				pParams->GetFloat(CRCD(0xeb9ecabb, "SpeedAdd"), &SpeedAdd);
+				pParams->GetFloat(CRCD(0x4c074698, "TimeMult"), &TimeMult);
+				pParams->GetFloat(CRCD(0x24ebf718, "LeanMult"), &LeanMult);
+				pParams->GetFloat(CRCD(0x94dbbd1c, "SpeedMult"), &SpeedMult);
+
+				// Get whichever balance trick we are doing	
+				CManual* pBalance;
+				switch (mBalanceTrickType)
+				{
+					case CRCC(0x255ed86f, "Grind"):
+					case CRCC(0x8d10119d, "Slide"):
+						pBalance = &mGrind;
+						break;
+						
+					case CRCC(0xa549b57b, "Lip"):
+						pBalance = &mLip;
+						break;
+					
+					case CRCC(0x3506ce64, "Skitch"):
+						pBalance = &mSkitch;
+						break;
+						
+					case CRCC(0xac90769, "NoseManual"):
+					case CRCC(0xef24413b, "Manual"):
+						pBalance = &mManual;
+						break;
+						
+					default:
+						pBalance = NULL;
+						Dbg_Assert(false);
+				}
+	
+				pBalance->mManualTime *= TimeMult;
+				pBalance->mManualTime += TimeAdd;
+				if (pBalance->mManualTime < 0.0f)
+				{
+					pBalance->mManualTime = 0.0f;
+				}
+				pBalance->mManualLean *= LeanMult;
+				pBalance->mManualLean += LeanAdd * Mth::Sgn(pBalance->mManualLean);
+				pBalance->mManualLeanDir *= SpeedMult;
+				pBalance->mManualLeanDir += SpeedAdd  * Mth::Sgn(pBalance->mManualLeanDir);
+			}
+			break;
+		}
+
+        // @script | StopBalanceTrick | 
+		case CRCC(0xe553a5b8, "StopBalanceTrick"):
+			stop_balance_trick();
+			break;
+		
+        // @script | StartBalanceTrick | 
+		case CRCC(0x6cc475b7, "StartBalanceTrick"):
+			mDoingBalanceTrick = true;
+			break;
+			
+		case CRCC(0x78e669ab, "SwitchOffBalanceMeter"):
+		{
+			CSkaterScoreComponent* p_score_component = GetSkaterScoreComponentFromObject(GetObject());
+			Dbg_Assert(p_score_component);
+			
+			p_score_component->GetScore()->SetBalanceMeter(false);
+			p_score_component->GetScore()->SetManualMeter(false);
+			
+			mManual.SwitchOffMeters();
+			mGrind.SwitchOffMeters();
+			mLip.SwitchOffMeters();
+			mSkitch.SwitchOffMeters();
+			break;
+		}
+			
+        // @script | SwitchOnBalanceMeter |
+		case CRCC(0x73727701, "SwitchOnBalanceMeter"):
+			mManual.SwitchOnMeters();
+			mGrind.SwitchOnMeters();
+			mLip.SwitchOnMeters();
+			mSkitch.SwitchOnMeters();
+			break;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterBalanceTrickComponent::GetDebugInfo"));
+	
+	p_info->AddChecksum("mDoingBalanceTrick", mDoingBalanceTrick ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum("mBalanceTrickType", mBalanceTrickType);
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::ClearMaxTimes (   )
+{
+   	mManual.ClearMaxTime();
+	mGrind.ClearMaxTime();
+	mLip.ClearMaxTime();
+	mSkitch.ClearMaxTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::UpdateRecord (   )
+{
+	mManual.UpdateRecord();		   
+	mGrind.UpdateRecord();		   
+	mLip.UpdateRecord();		   
+	mSkitch.UpdateRecord();		   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::Reset (   )
+{
+	mManual.Reset();
+	mGrind.Reset();
+	mSkitch.Reset();
+	mLip.Reset();
+	
+	mBalanceTrickType = 0;
+	mDoingBalanceTrick = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkaterBalanceTrickComponent::ExcludeBalanceButtons ( int& numButtonsToIgnore, uint32 pButtonsToIgnore[] )
+{
+	// If a balance trick is being done, then this will stop the buttons being used to control the balance from
+	// generating the events used to trigger tricks.
+	// Otherwise, ChrisR can't do kickflips by very quickly jumping out of a grind & kickflipping by rolling
+	// from X to Square.
+	
+	// Note, in case of problems later:
+	// Now, potentially a button could be pressed just before the balance trick,
+	// then released during the balance trick, and the release would not get recorded as an event.
+	// So if the event buffer were analysed, it would appear that the button was still pressed.
+	// However, this does not seem to cause any problems, because generally if we want to know if a button
+	// is pressed right now we just read the pad. Just noting this if any problems occur later.
+	switch (mBalanceTrickType)
+	{
+		case CRCC(0xac90769, "NoseManual"):
+		case CRCC(0xef24413b, "Manual"):
+			numButtonsToIgnore = 2;
+			pButtonsToIgnore[0] = mManual.mButtonAChecksum;
+			pButtonsToIgnore[1] = mManual.mButtonBChecksum;
+			break;
+			
+		case CRCC(0xa549b57b, "Lip"):
+			numButtonsToIgnore = 2;
+			pButtonsToIgnore[0] = mLip.mButtonAChecksum;
+			pButtonsToIgnore[1] = mLip.mButtonBChecksum;
+			break;
+			
+		case CRCC(0x255ed86f, "Grind"):
+		case CRCC(0x8d10119d, "Slide"):
+			numButtonsToIgnore = 2;
+			pButtonsToIgnore[0] = mGrind.mButtonAChecksum;
+			pButtonsToIgnore[1] = mGrind.mButtonBChecksum;
+			break;			
+			
+		case CRCC(0x3506ce64, "Skitch"):
+			numButtonsToIgnore = 2;
+			pButtonsToIgnore[0] = mSkitch.mButtonAChecksum;
+			pButtonsToIgnore[1] = mSkitch.mButtonBChecksum;
+			break;
+			
+		default:	
+			numButtonsToIgnore = 0;
+			break;
+	}		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSkaterBalanceTrickComponent::GetBalanceStat ( uint32 Checksum )
+{
+	// If some custom params have been set, use them.
+	if (mpBalanceParams)
+	{
+		return GetSkater()->GetScriptedStat(Checksum, mpBalanceParams);
+	}
+		
+	switch (mBalanceTrickType)
+	{
+		case CRCC(0xac90769, "NoseManual"):
+		case CRCC(0xef24413b, "Manual"):
+			return GetSkater()->GetScriptedStat(Checksum, Script::GetStructure(CRCD(0xb8cc8633, "ManualParams")));
+			
+		case CRCC(0x255ed86f, "Grind"):
+		case CRCC(0x8d10119d, "Slide"):
+			return GetSkater()->GetScriptedStat(Checksum, Script::GetStructure(CRCD(0x6b7fdadd, "GrindParams")));
+			
+		case CRCC(0xa549b57b, "Lip"):
+			return GetSkater()->GetScriptedStat(Checksum, Script::GetStructure(CRCD(0xbe615d0a, "LipParams")));
+		
+		case CRCC(0x3506ce64, "Skitch"):
+			return GetSkater()->GetScriptedStat(Checksum, Script::GetStructure(CRCD(0xfc87cc01, "SkitchParams")));
+			
+		default:
+			Dbg_Assert(false);
+			return 0.0f;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkaterBalanceTrickComponent::ClearBalanceParameters (   )
+{
+	if (mpBalanceParams)
+	{
+		delete mpBalanceParams;
+		mpBalanceParams = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::stop_balance_trick (   )
+{
+	// Stops doing any balance trick.
+	// Called when going into the lip state, and also called by the StopBalanceTrick script command.
+	mManual.Stop();	
+	mGrind.Stop();	
+	mLip.Stop();	
+	mSkitch.Stop();
+		
+	mBalanceTrickType = 0;
+	mDoingBalanceTrick = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::set_wobble_params ( Script::CStruct *pParams )
+{
+	if (!GetObject()->GetScript())
+	{
+		GetObject()->SetScript(new Script::CScript);
+	}
+
+	Dbg_Assert(pParams);
+
+	// Extract all the wobble details and stick them in WobbleDetails, then send that to SetWobbleDetails
+	
+	Script::CStruct* pStat = NULL;
+	Gfx::SWobbleDetails theWobbleDetails;
+
+	pParams->GetStructure(CRCD(0xfd266a26, "WobbleAmpA"), &pStat, Script::ASSERT);
+	theWobbleDetails.wobbleAmpA = GetSkater()->GetScriptedStat(pStat);
+
+	pParams->GetStructure(CRCD(0x642f3b9c, "WobbleAmpB"), &pStat, Script::ASSERT);
+	theWobbleDetails.wobbleAmpB = GetSkater()->GetScriptedStat(pStat);
+
+	pParams->GetStructure(CRCD(0xf43fd49, "WobbleK1"), &pStat, Script::ASSERT);
+	theWobbleDetails.wobbleK1 = GetSkater()->GetScriptedStat(pStat);
+
+	pParams->GetStructure(CRCD(0x964aacf3, "WobbleK2"), &pStat, Script::ASSERT);
+	theWobbleDetails.wobbleK2 = GetSkater()->GetScriptedStat(pStat);
+
+	pParams->GetStructure(CRCD(0xf90b0824, "SpazFactor"), &pStat, Script::ASSERT);
+	theWobbleDetails.spazFactor = GetSkater()->GetScriptedStat(pStat);
+
+	mp_animation_component->SetWobbleDetails(theWobbleDetails, true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterBalanceTrickComponent::set_balance_trick_params ( Script::CStruct *pParams )
+{
+	Dbg_Assert(pParams);
+
+	if (!mpBalanceParams)
+	{
+		mpBalanceParams = new Script::CStruct;
+	}
+	mpBalanceParams->Clear();
+	mpBalanceParams->AppendStructure(pParams);	
+}
+
+}
diff --git a/Code/Sk/Components/SkaterBalanceTrickComponent.h b/Code/Sk/Components/SkaterBalanceTrickComponent.h
new file mode 100644
index 0000000..a805658
--- /dev/null
+++ b/Code/Sk/Components/SkaterBalanceTrickComponent.h
@@ -0,0 +1,97 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterBalanceTrickComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  328/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERBALANCETRICKCOMPONENT_H__
+#define __COMPONENTS_SKATERBALANCETRICKCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#define		CRC_SKATERBALANCETRICK CRCD(0x589e5fb3, "SkaterBalanceTrick")
+
+#define		GetSkaterBalanceTrickComponent() ((Obj::CSkaterBalanceTrickComponent*)GetComponent(CRC_SKATERBALANCETRICK))
+#define		GetSkaterBalanceTrickComponentFromObject(pObj) ((Obj::CSkaterBalanceTrickComponent*)(pObj)->GetComponent(CRC_SKATERBALANCETRICK))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+	
+namespace Mdl
+{
+	class Skate;
+}
+              
+namespace Obj
+{
+	class CAnimationComponent;
+
+class CSkaterBalanceTrickComponent : public CBaseComponent
+{
+	friend Mdl::Skate;
+	friend class CSkater;
+	friend class CManual;
+	friend class CSkaterCam;
+	friend class CSkaterCameraComponent;
+	friend class CSkaterCorePhysicsComponent;
+	
+public:
+    CSkaterBalanceTrickComponent();
+    virtual ~CSkaterBalanceTrickComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+	uint32							GetBalanceTrickType (   ) { return mBalanceTrickType; }
+	bool							DoingBalanceTrick (   ) { return mDoingBalanceTrick; }
+	
+	void							ClearMaxTimes (   );
+	void							UpdateRecord (   );
+	void							Reset (   );
+	void							ExcludeBalanceButtons ( int& numButtonsToIgnore, uint32 pButtonsToIgnore[] );
+	float							GetBalanceStat ( uint32 Checksum );
+	void							ClearBalanceParameters (   );
+	
+private:
+	void							stop_balance_trick (   );
+	void							set_balance_trick_params ( Script::CStruct *pParams );
+	void							set_wobble_params ( Script::CStruct *pParams );
+	
+private:
+	uint32 							mBalanceTrickType;				// Nosemanual,manual,grind or slide (checksum of)
+	bool   							mDoingBalanceTrick;				// set in script when we are doing a balance trick, but not actually into it yet
+	
+	Script::CStruct*				mpBalanceParams;
+	
+	CManual 						mManual;
+	CManual 						mGrind;
+	CManual 						mLip;
+	CManual 						mSkitch;
+	
+	// peer components
+	CAnimationComponent*			mp_animation_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterCleanupStateComponent.cpp b/Code/Sk/Components/SkaterCleanupStateComponent.cpp
new file mode 100644
index 0000000..6a827a8
--- /dev/null
+++ b/Code/Sk/Components/SkaterCleanupStateComponent.cpp
@@ -0,0 +1,137 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterCleanupStateComponent.cpp
+//* OWNER:        	Dan
+//* CREATION DATE:  3/26/3
+//****************************************************************************
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterCleanupStateComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterCleanupStateComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterCleanupStateComponent::CSkaterCleanupStateComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERCLEANUPSTATE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterCleanupStateComponent::~CSkaterCleanupStateComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCleanupStateComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	mp_state_component = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCleanupStateComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCleanupStateComponent::Finalize(   )
+{
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_state_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCleanupStateComponent::Update()
+{
+	// Make sure the sparks are off if not on a rail
+	if (GetSkater()->mSparksRequireRail && mp_state_component->GetState() != RAIL)
+	{
+		GetSkater()->SparksOff();
+	}
+		
+	// Dan: shouldn't need to do this; the suspend components update themselves
+	// Perform LOD/culling/occluding
+	// NOTE: Move to CModeldComponent::Update?  Ask Gary about this.
+	// GetSuspendComponentFromObject(GetObject())->CheckModelActive();
+	
+	// Don't update the shadow if running a replay. The replay code will update it, using the replay dummy skater's position and matrix.
+	// if (!Replay::RunningReplay())
+	// {
+	GetSkater()->UpdateShadow(GetObject()->m_pos, GetObject()->m_matrix);
+	// }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterCleanupStateComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCleanupStateComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterCleanupStateComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+	
+}
diff --git a/Code/Sk/Components/SkaterCleanupStateComponent.h b/Code/Sk/Components/SkaterCleanupStateComponent.h
new file mode 100644
index 0000000..69d489b
--- /dev/null
+++ b/Code/Sk/Components/SkaterCleanupStateComponent.h
@@ -0,0 +1,58 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterCleanupStateComponent.h
+//* OWNER:        	Dan
+//* CREATION DATE:  3/26/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERCLEANUPSTATECOMPONENT_H__
+#define __COMPONENTS_SKATERCLEANUPSTATECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERCLEANUPSTATE CRCD(0xd8cc3428, "SkaterCleanupState")
+
+#define		GetSkaterCleanupStateComponent() ((Obj::CSkaterCleanupStateComponent*)GetComponent(CRC_SKATERCLEANUPSTATE))
+#define		GetSkaterCleanupStateComponentFromObject(pObj) ((Obj::CSkaterCleanupStateComponent*)(pObj)->GetComponent(CRC_SKATERCLEANUPSTATE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterStateComponent;
+
+class CSkaterCleanupStateComponent : public CBaseComponent
+{
+public:
+    CSkaterCleanupStateComponent();
+    virtual ~CSkaterCleanupStateComponent();
+
+public:
+    virtual void            		Update();
+	virtual void					Finalize(   );
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+private:
+	CSkaterStateComponent*			mp_state_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterCorePhysicsComponent.cpp b/Code/Sk/Components/SkaterCorePhysicsComponent.cpp
new file mode 100644
index 0000000..8ff6572
--- /dev/null
+++ b/Code/Sk/Components/SkaterCorePhysicsComponent.cpp
@@ -0,0 +1,8396 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterCorePhysicsComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/21/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#define	FLAGEXCEPTION(X) GetObject()->SelfEvent(X)
+							
+void	TrackingLine(int type, Mth::Vector &start, Mth::Vector &end)
+{
+	if	(Script::GetInt(CRCD(0x19fb78fa,"output_tracking_lines")))
+	{
+//		Gfx::AddDebugLine(start, end, 0xffffff);
+		printf ("Tracking%d %.2f,%.2f,%.2f,%.2f,%.2f,%.2f\n",type, start[X], start[Y], start[Z], end[X], end[Y], end[Z]);
+	}
+}
+ 
+extern bool g_CheatsEnabled;
+
+namespace Obj
+{
+	Mth::Vector acid_hold;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterCorePhysicsComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterCorePhysicsComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterCorePhysicsComponent::CSkaterCorePhysicsComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERCOREPHYSICS );
+	
+	mp_input_component = NULL;
+	mp_trigger_component = NULL;
+	mp_sound_component = NULL;
+	mp_trick_component = NULL;
+	mp_rotate_component = NULL;
+	mp_score_component = NULL;
+	mp_balance_trick_component = NULL;
+	mp_state_component = NULL;
+	mp_movable_contact_component = NULL;
+	mp_physics_control_component = NULL;
+	mp_walk_component = NULL;
+	
+	mp_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterCorePhysicsComponent::~CSkaterCorePhysicsComponent()
+{
+	Nx::CCollCacheManager::sDestroyCollCache(mp_coll_cache);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterCorePhysicsComponent added to non-skater composite object"));
+	
+	m_rolling_friction = GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction"));
+	
+    GetPos().Set(0.0f, 0.0f, 0.0f);
+	DUMP_POSITION;
+
+	GetMatrix().Identity();
+    ResetLerpingMatrix();
+
+	m_display_normal.Set(0.0f, 1.0f, 0.0f);
+	m_current_normal = m_display_normal;
+	m_last_display_normal = m_display_normal;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::Finalize (   )
+{
+	mp_input_component = GetInputComponentFromObject(GetObject());
+	mp_sound_component = GetSkaterSoundComponentFromObject(GetObject());
+	mp_trigger_component = GetTriggerComponentFromObject(GetObject());
+	mp_trick_component = GetTrickComponentFromObject(GetObject());
+	mp_rotate_component = GetSkaterRotateComponentFromObject(GetObject());
+	mp_score_component = GetSkaterScoreComponentFromObject(GetObject());
+	mp_balance_trick_component = GetSkaterBalanceTrickComponentFromObject(GetObject());
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+	mp_movable_contact_component = GetMovableContactComponentFromObject(GetObject());
+	mp_physics_control_component = GetSkaterPhysicsControlComponentFromObject(GetObject());
+	mp_walk_component = GetWalkComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_input_component);
+	Dbg_Assert(mp_sound_component);
+	Dbg_Assert(mp_trigger_component);
+	Dbg_Assert(mp_trick_component);
+	Dbg_Assert(mp_rotate_component);
+	Dbg_Assert(mp_score_component);
+	Dbg_Assert(mp_balance_trick_component);
+	Dbg_Assert(mp_movable_contact_component);
+	Dbg_Assert(mp_physics_control_component);
+	Dbg_Assert(mp_walk_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::Update()
+{
+	DUMP_POSITION;
+	
+	m_frame_length = Tmr::FrameLength();
+	
+	// bool up = GetVel()[Y] > 0.0f;
+
+	m_landed_this_frame = false;
+
+	m_began_frame_in_lip_state = GetState() == LIP;
+	m_began_frame_in_transfer = GetFlag(SPINE_PHYSICS);
+	
+	handle_tensing();
+
+	limit_speed();
+
+	SetFlagFalse(SNAPPED_OVER_CURB);  // this flag only gets set for one frame, to fix camera snaps
+	SetFlagFalse(SNAPPED);  // this flag only gets set for one frame, to fix camera snaps
+	
+	setup_default_collision_cache();
+	
+	switch (GetState())
+	{
+		case GROUND:
+			// Mick: Remember the last ground position for calculating which side of the rail we're on later.
+			// Note, we do this BEFORE we move as the movement might take us off the ground
+			m_last_ground_pos = GetPos();
+			do_on_ground_physics();
+			maybe_skitch();
+			break;
+			
+		case AIR:
+			do_in_air_physics();
+			if (GetState() == GROUND)
+			{
+				m_landed_this_frame = true;
+			}
+			break;
+			
+		case WALL:
+			do_wallride_physics();
+			break;
+			
+		case LIP:
+			do_lip_physics();
+			break;
+			
+		case RAIL:
+			do_rail_physics();
+			break;
+			
+		case WALLPLANT:
+			do_wallplant_physics();
+			break;
+	}
+	
+	handle_post_transfer_limit_overrides();
+	
+	// NOTE: moved this call from after CSkaterRotateComponent::Update to before it
+	maybe_stick_to_rail();
+	
+	update_special_friction_index();
+	
+	// if (up && GetVel()[Y] < 0.0f)
+	// {
+		// DUMPF(GetPos()[Y]);
+	// }
+	
+	if (m_vert_air_last_frame != (GetState() == AIR && GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS)))
+	{
+		m_vert_air_last_frame = !m_vert_air_last_frame;
+        GetObject()->BroadcastEvent(m_vert_air_last_frame ? CRCD(0xf225fe69, "SkaterEnterVertAir") : CRCD(0x5e27200a, "SkaterExitVertAir"));
+	}
+	
+	#ifdef __USER_DAN__
+	// Gfx::AddDebugArrow(GetPos(), GetPos() + 60.0f * GetMatrix()[Z], RED, 0, 1);
+	// Gfx::AddDebugArrow(GetPos(), GetPos() + 60.0f * GetMatrix()[X], BLUE, 0, 1);
+	// Gfx::AddDebugArrow(GetPos(), GetPos() + 60.0f * GetMatrix()[Y], GREEN, 0, 1);
+	#endif
+	
+	CFeeler::sClearDefaultCache();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterCorePhysicsComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Jump | 
+        // @flag BonelessHeight | 
+		case CRCC(0x584cf9e9, "Jump"):
+			do_jump(pParams);
+			break;
+			
+        // @script | Flipped | true if flipped
+		case CRCC(0xc7a712c, "Flipped"):
+			return GetFlag(FLIPPED) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | Switched | true if switched
+		case CRCC(0x8f66b80b, "Switched"):
+			return IsSwitched() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | Crouched | true if skater crouched
+		case CRCC(0x4adc6c2a, "Crouched"):
+			return GetFlag(TENSE) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | OnGround | true if current state is on ground
+		case CRCC(0x5ea287f2, "OnGround"):
+			return GetState() == GROUND ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | InAir | true if current state is in air
+		case CRCC(0x3527fc07, "InAir"):
+			return GetState() == AIR ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | OnWall | true if current state is on wall
+		case CRCC(0xa32c1a15, "OnWall"):
+			return GetState() == WALL ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | OnLip | true if current state is on lip
+		case CRCC(0x5cb1fbd8, "OnLip"):
+			return GetState() == LIP ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | OnRail | true if current state is on rail
+		case CRCC(0xe9851e62, "OnRail"):
+			return GetState() == RAIL ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | InWallplant | true if current state is in wallplant
+		case CRCC(0xa64dcf8b, "InWallplant"):
+			return GetState() == WALLPLANT ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+
+		// @script | FirstTimeOnThisRail | true if this is the first time we grinded this rail wihout doing something else
+		// like skating or wallriding
+		case CRCC(0x262d42d5,"FirstTimeOnThisRail"):
+			return GetFlag(NEW_RAIL) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+
+		// @script | StartSkitch | Start skitch physics
+		// This will send a SkitchOn exception to the object
+		case CRCC(0xca6b3809, "StartSkitch"):
+			start_skitch();
+			break;
+
+        // @script | Skitching | Returns True if we are skitching
+		case CRCC(0x9c6a7e41, "Skitching"):
+			return GetFlag(SKITCHING) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+
+        // @script | StopSkitch | Stop Skitch physics
+		// this wills send a SkitchOff exeception to the object
+		case CRCC(0x32dcc9cf, "StopSkitch"):
+			StopSkitch();
+			break;
+			
+		// @script | CancelWallpush | Cancels the current wallpush event
+		case CRCC(0x5e8f9e77, "CancelWallpush"):
+			SetFlagTrue(CANCEL_WALL_PUSH);
+			break;
+			 
+        // @script | AirTimeLessThan | true if the air time is
+        // less than the specified time
+        // @uparm 1.0 | time (default is milliseconds)
+        // @flag seconds | time in seconds
+        // @flag frames | time in frames
+        case CRCC(0xc890a84, "AirTimeLessThan"):
+        // @script | AirTimeGreaterThan | true if the air time is 
+        // greater than the specified time
+        // @uparm 1.0 | time (default is milliseconds)
+        // @flag seconds | time in seconds
+        // @flag frames | time in frames
+		case CRCC(0xbbf2b570, "AirTimeGreaterThan"):
+		{
+			float t = 0;
+			pParams->GetFloat(NO_NAME, &t);
+	
+			Tmr::Time TestTime;
+			if (pParams->ContainsFlag(CRCD(0xd029f619, "seconds")) || pParams->ContainsFlag(CRCD(0x49e0ee96, "second")))
+			{
+				TestTime = static_cast< Tmr::Time >(t * 1000);
+			}	
+			else if (pParams->ContainsFlag(CRCD(0x19176c5, "frames")) || pParams->ContainsFlag(CRCD(0x4a07c332, "frame")))
+			{
+				TestTime = static_cast< Tmr::Time >(t * (1000 / 60));
+			}
+			else
+			{
+				TestTime = static_cast< Tmr::Time >(t);
+			}
+
+			Tmr::Time AirTime;
+			if (GetState() == AIR || GetState() == WALL)
+			{
+				AirTime = Tmr::ElapsedTime(m_went_airborne_time);
+			}
+			else
+			{
+				AirTime = m_landed_time - m_went_airborne_time;
+			}	
+			
+			if (Checksum == CRCD(0xc890a84, "AirTimeLessThan"))
+			{
+				return AirTime < TestTime ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			}
+			else
+			{
+				return AirTime > TestTime ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			}	
+		}
+
+		// @script | GetAirTime | Puts the air time, in seconds, into a param called airtime
+		case CRCC(0xde583e34, "GetAirTime"):
+		{
+			Tmr::Time AirTime;
+			if (GetState() == AIR || GetState() == WALL)
+			{
+				AirTime = Tmr::ElapsedTime(m_went_airborne_time);
+			}
+			else
+			{
+				AirTime = m_landed_time - m_went_airborne_time;
+			}	
+			pScript->GetParams()->AddFloat(CRCD(0xad6bcdb4, "AirTime"), AirTime * (1.0f / 1000.0f));	
+			break;
+		}
+		
+		// @script | GetAirTimeLeft | Puts the amount of air time left before landing, in seconds, into a param called AirTimeLeft
+		case CRCC(0x1996b797, "GetAirTimeLeft"):
+		{
+			pScript->GetParams()->AddFloat(CRCD(0x7e2a8993, "AirTimeLeft"),
+				Mth::ClampMin(calculate_time_to_reach_height(GetPos()[Y] - mp_state_component->m_height, GetPos()[Y], GetVel()[Y]), 0.0f));
+			break;
+		}
+						
+        // @script | Braking | true if skater is braking
+		case CRCC(0x1f8bbd05, "Braking"):
+			return is_braking() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+
+		case CRCC(0xebbcf455, "CanBrakeOff"):
+			m_pressing_down_brakes = false;
+			break;
+
+		case CRCC(0xbc19e291, "CanBrakeOn"):
+			m_pressing_down_brakes = true;
+			break;
+			
+        // @script | CanKick | true if skater can kick
+		case CRCC(0x2f66333, "CanKick"):
+			return can_kick() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+
+        // @script | CanKickOn | sets can kick to true
+		case CRCC(0x68bf6c13, "CanKickOn"):
+			m_force_cankick_off = false;
+			break;
+			
+        // @script | CanKickOff | sets can kick to false
+		case CRCC(0xe8deb0d7, "CanKickOff"):
+			m_force_cankick_off = true;
+			break;
+		
+        // @script | ForceAutokickOn | turns on auto kick
+		case CRCC(0x34dcfc97, "ForceAutokickOn"):
+			m_auto_kick = true;
+			break;
+
+        // @script | ForceAutoKickOff | turns off auto kick
+		case CRCC(0x257947e, "ForceAutokickOff"):
+			m_auto_kick = false;
+			break;
+
+	    // @script | AutoKickIsOff | true if auto kick is off
+		case CRCC(0x1baa1d9, "AutoKickIsOff"):
+			return m_auto_kick == false ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | RestoreAutokick | restores auto kick to 
+        // original preferences
+		case CRCC(0x9fcfdfeb, "RestoreAutokick"):
+			m_auto_kick = Mdl::Skate::Instance()->mp_controller_preferences[GetSkater()->m_skater_number].AutoKickOn;
+			break;
+			
+        // @script | DoCarPlantBoost | boost after doing car plant
+		case CRCC(0x17846595, "DoCarPlantBoost"):
+		{
+			GetVel()[Y] = Mth::ClampMin(GetVel()[Y], 0.0f);
+			GetVel()[Y] += GetPhysicsFloat(CRCD(0xcb49b3f2, "Carplant_upward_boost"));	
+			
+			DUMP_VELOCITY;
+			
+			Mth::Vector front = GetVel();
+			front[Y] = 0.0f;
+			if (front.LengthSqr() < 10.0f * 10.0f)
+			{
+				front = GetMatrix()[Z];				
+				front[Y] = 0.0f;
+				if (front.LengthSqr() < 0.01f * 0.01f)
+				{
+					front.Set(1.0f, 0.0f, 1.0f);
+				}
+			}
+			front.Normalize();
+			GetVel() += front * GetPhysicsFloat(CRCD(0xb7422173, "Carplant_forward_boost"));
+			
+			DUMP_VELOCITY;
+			break;
+		}
+		
+		// @script | HandleLipOllieDirection |
+		case CRCC(0x259cb90d, "HandleLipOllieDirection"):
+		{
+			CControlPad& control_pad = mp_input_component->GetControlPad();
+			
+			Mth::Vector out = GetMatrix()[Z];
+			out[Y] = 0.0f;
+			out.Normalize();	 
+			Mth::Vector along(out[Z], 0.0f, -out[X], 0.0f);
+											  
+			// We've jumped off a lip, so we want to give us some velocity to the left
+			if (control_pad.m_right.GetPressed())
+			{
+				// don't allow right jumping
+				return CBaseComponent::MF_FALSE;
+			}
+			else if (BREAK_SPINE_BUTTONS)
+			{
+				GetVel() += out * GetPhysicsFloat(CRCD(0x1f96c224, "Lip_side_hop_speed"));
+				DUMP_VELOCITY;
+				return CBaseComponent::MF_TRUE;
+			} 
+			else if (control_pad.m_left.GetPressed()
+				&& static_cast< int >(control_pad.m_left.GetPressedTime()) > GetPhysicsInt(CRCD(0x3080e9e7, "Lip_held_jump_out_time")))
+			{
+				GetVel() += out * GetPhysicsFloat(CRCD(0xdc818b7e, "Lip_side_jump_speed")); 
+				DUMP_VELOCITY;
+				return CBaseComponent::MF_TRUE;
+			} 
+			if (control_pad.m_up.GetPressed()
+				&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x9b25142a, "Lip_held_jump_along_time")))
+			{
+				GetVel() -= along * GetPhysicsFloat(CRCD(0x1ab3809f, "Lip_along_jump_speed")); 
+				DUMP_VELOCITY;
+				return CBaseComponent::MF_TRUE;
+			}
+			else if (control_pad.m_down.GetPressed()
+				&& static_cast< int >(control_pad.m_down.GetPressedTime()) > GetPhysicsInt(CRCD(0x9b25142a,"Lip_held_jump_along_time")))
+			{
+				GetVel() += along * GetPhysicsFloat(CRCD(0x1ab3809f, "Lip_along_jump_speed"));
+				DUMP_VELOCITY;
+				return CBaseComponent::MF_TRUE;
+			} 
+			else
+			{
+				return CBaseComponent::MF_FALSE;
+			}
+		}  	
+			
+        // @script | OrientToNormal | 
+		case CRCC(0x1d1fd4f0, "OrientToNormal"):
+			m_display_normal = GetMatrix()[Y];
+			m_current_normal = GetMatrix()[Y];
+			new_normal(m_feeler.GetNormal());
+			break;
+			
+        // @script | SetSpeed | 
+        // @uparm 0.0 | speed in inches per second, so 3000 is very very fast
+		// The skater's max speed is about 1100 inches per second (depends on stats)
+		// this is usually used in conjunction with Overridelimits, to provide a temporary speed boost
+		case CRCC(0x383b939b, "SetSpeed"):
+		{
+			float Speed = 0.0f;
+			pParams->GetFloat(NO_NAME, &Speed);
+			float length_sqr = GetVel().LengthSqr();
+			if (length_sqr < 0.001f)
+			{
+				GetVel() = GetMatrix()[Z];
+			}
+			else
+			{
+				GetVel() *= 1.0f / sqrtf(length_sqr);
+			}
+			GetVel() *= Speed;
+			DUMP_VELOCITY;
+			break;
+		}
+		
+        // @script | NoSpin | sets flag, disabling spin
+		case CRCC(0x54ef2b79, "NoSpin"):
+			mNoSpin = true;
+			SetFlagFalse(AUTOTURN);
+			break;
+
+        // @script | CanSpin | sets flag enabling spin
+		case CRCC(0xe2998b9, "CanSpin"):
+			mNoSpin = false;
+			break;
+		
+        // @script | InBail | sets is_bailing flag 
+		case CRCC(0x6fc5aae0, "InBail"):
+		{
+			SetFlagTrue(IS_BAILING);
+			break;
+		}
+		
+        // @script | NotInBail | clears is_bailing flag
+		case CRCC(0xbd4303f4, "NotInBail"):
+		{
+			SetFlagFalse(IS_BAILING);
+			break;
+		}
+			
+        // @script | IsInBail | true if bailing (as defined by is_bailing flag)
+		case CRCC(0xa901a50c, "IsInBail"):
+			return GetFlag(IS_BAILING) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+
+	    // @script | BailOn | turn bails on
+		case CRCC(0xe0df1f0a, "BailOn"):
+			m_bail = true;
+			break;
+			
+        // @script | BailOff | turn bails off
+		case CRCC(0x8c3d7864, "BailOff"):
+			m_bail = false;
+			break;
+
+        // @script | BailIsOn | true if bails on
+		case CRCC(0xe1d5168, "BailIsOn"):
+			return m_bail ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+
+        // @script | FrontTruckSparks | true if sparks should be coming from the front trucks, false if from rear
+        case CRCC(0xe0760055, "FrontTruckSparks"):
+			return m_front_truck_sparks ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+
+        // @script | SetFrontTruckSparks | sets sparks to come from front trucks
+		case CRCC(0x19d0e5fb, "SetFrontTruckSparks"):
+			m_front_truck_sparks = true;
+			break;
+
+        // @script | SetRearTruckSparks | sets sparks to come from rear trucks
+		case CRCC(0xb48ec756, "SetRearTruckSparks"):
+			m_front_truck_sparks = false;
+			break;
+			
+        // @script | SetState | sets the state to the specified state
+        // @uparm name | state name (lip, air, ground)
+		case CRCC(0x948ebf96, "SetState"):
+		{
+			uint32 StateChecksum = 0;
+			pParams->GetChecksum(NO_NAME, &StateChecksum);
+			
+			if (GetState() == LIP && StateChecksum != CRCD(0xa549b57b, "Lip"))
+			{
+				// Trigger the lip off event.
+				maybe_trip_rail_trigger(TRIGGER_LIP_OFF);
+			}
+
+			// If we are going to "LIP" via script then clear the rail node, as it's a patch to get the skater to freeze
+			if (GetState() != LIP && StateChecksum == CRCD(0xa549b57b, "Lip"))
+			{
+				mp_rail_node = NULL;
+			}
+			
+			// if script alters state from wallride (like for a bail), then snap upright   
+			// note, this command does not support setting TO wall, if it does, we should check here
+			if (GetState() == WALL)
+			{
+				new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
+				ResetLerpingMatrix();
+			}
+			
+			switch (StateChecksum)
+			{
+				case 0:
+					break;
+				case CRCC(0x439f4704, "Air"):
+					SetState(AIR);
+					break;	
+				case CRCC(0x58007c97, "Ground"):
+					m_last_ground_feeler.SetTrigger(0); // prevent spurious gaps
+					SetState(GROUND);
+					break;	
+				case CRCC(0xa549b57b, "Lip"):
+					SetState(LIP);
+					break;
+				default:
+					Dbg_Assert(false);
+					break;
+			}		
+			break;
+		}
+
+        // @script | SetRailSound | sets the rail sound to either grind or slide
+        // @uparm Grind | pass either Grind or Slide
+		case CRCC(0x947fccf4, "SetRailSound"):
+		{
+			uint32 rail_sound_checksum = 0;
+			pParams->GetChecksum(NO_NAME, &rail_sound_checksum, Script::ASSERT);
+			Dbg_MsgAssert(rail_sound_checksum == CRCD(0x255ed86f,"Grind") || rail_sound_checksum == CRCD(0x8d10119d, "Slide"),
+				("\n%s\nBad rail sound type '%s' sent to SetRailSound", pScript->GetScriptInfo(), Script::FindChecksumName(rail_sound_checksum)));
+			if (rail_sound_checksum == CRCD(0x8d10119d, "Slide"))
+			{
+				SetFlagTrue(RAIL_SLIDING);
+			}
+			else
+			{
+				SetFlagFalse(RAIL_SLIDING);
+			}
+			break;
+		}
+
+		// @script | LockVelocityDirection | When passed the flag On this will cause the skater's
+		// velocity to be locked in its current direction and be unaffected by the skater's rotation.
+		// Only works when on the ground however.
+		// @flag On | Enable
+		// @flag Off | Disable (Ie, back to normal)
+		case CRCC(0xacb82c02, "LockVelocityDirection"):
+			m_lock_velocity_direction = pParams->ContainsFlag(CRCD(0xf649d637, "On"));
+			break;
+			
+        // @script | SetRollingFriction | change the rolling friction value
+        // @flag Default | use the default value
+        // @uparm 0.0 | friction coeff
+		case CRCC(0x510f983b, "SetRollingFriction"):
+			pParams->GetFloat(NO_NAME, &m_rolling_friction);
+			if (pParams->ContainsFlag(CRCD(0x1ca1ff20, "Default")))
+			{
+				if (m_special_friction_duration == 0.0f)
+				{
+					m_rolling_friction = GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction"));
+				}
+			}	
+			break;
+        
+        // @script | SetGrindTweak | 
+        // @uparm int | grind tweak
+		case CRCC(0x71b993b7, "SetGrindTweak"):
+			pParams->GetInteger(NO_NAME, &mGrindTweak);
+			break;
+   
+        // @script | Ledge | true if ledge
+		case CRCC(0x315d9ed4, "Ledge"):
+			return mLedge ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | BadLedge | true if bad ledge
+		case CRCC(0xbe371b7b, "BadLedge"):
+			return mBadLedge ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | SkateInAble | Do a collision check using a horizontal line a bit below the skater, and return true if it hits something skatable.
+        // @flag Left | go left to right
+		// Parameters in physics.q: SkateInAble_HorizOffset and SkateInAble_DownOffset
+        // @flag Lip | This will do a check to see if the other side of the spine that the
+		// skater might be doing a lip trick on is skateinable.
+		// The collision check is down relative to the skater, but horizontal relative to 
+		// the world because of the skater's wacky orientation when doing a lip trick.
+		// The direction of the collision check is towards the skater, so that it detects the
+		// other side of the spine.
+		// Parameters in physics.q: SkateInAble_LipHorizOffset and SkateInAble_LipDownOffset
+		case CRCC(0x55934543, "SkateInAble"):
+			if (pParams->ContainsFlag(CRCD(0xa549b57b, "Lip")))
+			{
+				float HorizOff = GetPhysicsFloat(CRCD(0xb92cbe3b, "SkateInAble_LipHorizOffset"));
+				float DownOff = GetPhysicsFloat(CRCD(0xefdfe781, "SkateInAble_LipDownOffset"));
+				m_col_end = GetPos();
+				m_col_end[Y] -= DownOff;
+				m_col_start = m_col_end - HorizOff * GetMatrix()[Y];
+				if (get_member_feeler_collision())
+				{
+					return m_col_flag_vert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}
+				else
+				{
+					// If the first check fails, do another check straight down. This is for when the skater is doing a lip trick on a high up rail that
+					// still has vert beneath it.
+					HorizOff = GetPhysicsFloat(CRCD(0x108613cb, "SkateInAble_LipExtraCheckHorizOffset"));
+					DownOff = GetPhysicsFloat(CRCD(0xf9dc446d, "SkateInAble_LipExtraCheckDownOffset"));
+					m_col_start = GetPos() - HorizOff * GetMatrix()[Y];
+					m_col_end = m_col_start;
+					m_col_end[Y] -= DownOff;
+					if (get_member_feeler_collision())
+					{
+						return m_col_flag_vert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+					}
+				}
+			}
+			else
+			{
+				float HorizOff=GetPhysicsFloat(CRCD(0xc78ebe56,"SkateInAble_HorizOffset"));
+				Mth::Vector a = GetPos() + HorizOff * GetMatrix()[X];
+				a[Y] -= GetPhysicsFloat(CRCD(0xfca3378c,"SkateInAble_DownOffset"));
+				Mth::Vector b = a - 2.0f * HorizOff * GetMatrix()[X];
+				
+				if (pParams->ContainsFlag(CRCD(0x85981897,"Left")))
+
+				{
+					m_col_start = a;
+					m_col_end = b;
+				}
+				else
+				{
+					m_col_start = b;
+					m_col_end = a;
+				}
+			
+				if (get_member_feeler_collision())
+				{
+					return m_col_flag_vert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}	
+			}	
+			return CBaseComponent::MF_FALSE;
+			
+        // @script | LastSpinWas | check if last spin was frontside or backside (requires one flag)
+        // @flag Backside |
+        // @flag Frontside |
+		case CRCC(0x6c8a1316, "LastSpinWas"):
+			if (pParams->ContainsFlag(CRCD(0x47953f00, "Backside")))
+			{
+				if (GetFlag(FLIPPED))
+				{
+					return !mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}
+				else
+				{
+					return mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}				
+			}	
+			else if (pParams->ContainsFlag(CRCD(0x996d5512, "Frontside")))
+			{
+				if (GetFlag(FLIPPED))
+				{
+					return mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}
+				else
+				{
+					return !mYAngleIncreased ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}	
+			}	
+			else
+			{
+				Dbg_MsgAssert(false, ("LastSpinWas requires a FrontSide or BackSide flag"));
+			}
+			break;
+			
+        // @script | LandedFromSpine | 
+		case CRCC(0x448e3630, "LandedFromSpine"):
+			return mLandedFromSpine ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | LandedFromVert | 
+		case CRCC(0xf37ce040, "LandedFromVert"):
+			return mLandedFromVert ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+   
+        // @script | SetLandedFromVert | sets the mlandedfromvert flag 
+        // (check with LandedFromVert)
+		case CRCC(0x133e1293, "SetLandedFromVert"):
+			mLandedFromVert = true;
+			break;
+		
+        // @script | ResetLandedFromVert | clears mlandedfromvert flag
+        // (check with LandedFromVert)
+		case CRCC(0xf922431, "ResetLandedFromVert"):
+			mLandedFromVert = false;
+			break;
+			
+		// @script | IsInVertAir | check if VERT_AIR flag is set
+		case CRCC(0xdfb8f052, "InVertAir"):
+			return GetFlag(VERT_AIR) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | AllowLipNoGrind | 
+		case CRCC(0xc5b4b390, "AllowLipNoGrind"):
+			mAllowLipNoGrind = true;
+			break;
+
+        // @script | ClearAllLipNoGrind |
+		case CRCC(0xb9eebff6, "ClearAllowLipNoGrind"):
+			mAllowLipNoGrind = false;
+			break;
+			
+        // @script | NoRailTricks | don't allow rail tricks
+        // (turn on with AllowRailTricks)
+		case CRCC(0xec681a59, "NoRailTricks"):
+			mNoRailTricks = true;
+			break;
+
+        // @script | AllowRailTricks | turn off with NoRailTricks
+		case CRCC(0x65a559a, "AllowRailTricks"):
+			mNoRailTricks = false;
+			break;
+			
+        // @script | TurnToFaceVelocity | turn to face the proper direction
+		case CRCC(0x461e5b92, "TurnToFaceVelocity"):
+			// Mick: Don't do it if vel is too short as skater will collapse
+			if (GetVel().LengthSqr() < 0.01f * 0.01f) break;
+			
+			GetMatrix()[Z] = GetVel();
+			GetMatrix()[Z].Normalize();						
+			GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
+			GetMatrix()[X].Normalize();
+			GetMatrix()[Y] = Mth::CrossProduct(GetMatrix()[Z], GetMatrix()[X]);
+			GetMatrix()[Y].Normalize();
+			
+			ResetLerpingMatrix();	
+			break;
+		
+        // @script | OverrideLimits | Overrides the limits to the maximum speed that the skater
+		// can achieve.  This allows us to do temporary speed boosts whihc are much faster 
+		// than regular gameplay.  
+		// You can specify a time that this speed limit will last for 
+        // @parm float | max | Maximum speed in inches per second (normal gameplay is around 1000, so 5000 is nice)
+		// @parmopt float | max_max | max | usualy the same as above.  It's a hard cap, making it bigger that max will give
+		// you a smoother limit to the speed.
+		// @parmopt float | friction | 0.0000020 | air friction.  Slows you down at a rate proportional to speed. So reduce this to stay fast longer
+		// @parmopt float | gravity | physics default | down_gravity. gravity that slows you down when going up a slope
+		// @parmopt float | time | 10000000000000,0f | time in seconds that this effect should last for
+		// @flag end | end the effect (other parameters are unneeded and ignored if end is flagged)
+		case CRCC(0x90f1d8d5, "OverrideLimits"):
+		{
+			if (pParams->ContainsFlag(CRCD(0xff03cc4e, "end")))
+			{
+				m_override_limits_time = 0.0f;
+			}
+			else
+			{
+				pParams->GetFloat(CRCD(0x6289dd76, "max"), &m_override_max, Script::ASSERT);
+				m_override_max_max = m_override_max;
+				pParams->GetFloat(CRCD(0x73a19e3a, "max_max"), &m_override_max_max);
+				m_override_limits_time = 10000000000000.0f;
+				pParams->GetFloat(CRCD(0x906b67ba, "time"), &m_override_limits_time);
+				m_override_air_friction = 0.0000020f;
+				pParams->GetFloat(CRCD(0xedf3e7f4, "friction"), &m_override_air_friction);
+				m_override_down_gravity = GetPhysicsFloat(CRCD(0x6618a713, "Physics_Ground_Gravity"));
+				pParams->GetFloat(CRCD(0xa5e2da58, "gravity"), &m_override_down_gravity);
+				
+				if (pParams->ContainsFlag(CRCD(0xdf312928, "NoTimeLimit")))
+				{
+					// magic number meaning no time limit
+					m_override_limits_time = -1.0f;
+				}
+			}
+			break;
+		}
+
+        // @script | SetSpecialFriction | This command is used in the Revert script to specify a set of friction values that should be used for
+		// each Revert in the combo. m_special_friction_index gets reset as soon as the combo ends, ie by the ClearPanel_Landed & ClearPanel_Bailed
+	    // functions.
+        // @uparm [] | array of friction values
+		case CRCC(0x5e8d9b80,"SetSpecialFriction"):
+		{
+			Script::CArray* pArray = NULL;
+			pParams->GetArray(NO_NAME, &pArray);
+			Dbg_MsgAssert(pArray, ("\n%s\nSetSpecialFriction requires an array of friction values", pScript->GetScriptInfo()));
+			if (m_special_friction_index >= static_cast< int >(pArray->GetSize()))
+			{
+				m_special_friction_index = static_cast< int >(pArray->GetSize()) - 1;
+			}	
+			m_rolling_friction = pArray->GetFloat(m_special_friction_index);
+			if (m_special_friction_index == 0)
+			{
+				m_special_friction_decrement_time_stamp = Tmr::GetTime();
+			}
+			++m_special_friction_index;
+			
+			pParams->GetFloat(CRCD(0x79a07f3f, "Duration"), &m_special_friction_duration);
+			break;
+		}
+			
+		// @script | OverrideCancelGround | Overrided the Cancel_Ground flag in gaps
+		// for use with reverts
+        // @flag Off | turn this flag off
+		case CRCC(0xb94bc0c6, "OverrideCancelGround"):
+			SetFlag(OVERRIDE_CANCEL_GROUND, !pParams->ContainsFlag(CRCD(0xd443a2bc, "Off")));
+			break;	
+		     			
+        // @script | SetExtraPush | 
+        // @parmopt float | radius | 48.0 | wall push radius
+        // @parmopt float | speed | 100.0 | wall push speed
+        // @parmopt float | turn | 6.0 | wall rotate speed
+		case CRCC(0xe47ff4b5, "SetExtraPush"):
+		{
+			m_wall_push_radius = 48.0f;
+			m_wall_push_speed = 100.0f;
+			m_wall_rotate_speed = 6.0f;
+			pParams->GetFloat(CRCD(0xc48391a5, "radius"), &m_wall_push_radius);
+			pParams->GetFloat(CRCD(0xf0d90109, "speed"), &m_wall_push_speed);
+			pParams->GetFloat(CRCD(0xdfdfeab8, "turn"), &m_wall_rotate_speed);
+			break;
+		}
+		
+		// @script | GetMatrixNormal | places the skater's matrix normal in x, y, and z
+		case CRCC(0x5144783, "GetMatrixNormal"):
+		{
+			pScript->GetParams()->AddFloat(CRCD(0x7323e97c, "x"), GetMatrix()[Y][X]);
+			pScript->GetParams()->AddFloat(CRCD(0x0424d9ea, "y"), GetMatrix()[Y][Y]);
+			pScript->GetParams()->AddFloat(CRCD(0x9d2d8850, "z"), GetMatrix()[Y][Z]);
+			break;
+		}
+		
+		// Overloading the CMotionComponent member function for skater specific logic
+		case CRCC(0x8819dd8b, "Obj_MoveToNode"):
+		{
+			CMotionComponent* p_motion_component = GetMotionComponentFromObject(GetObject());
+			Dbg_Assert(p_motion_component);
+			
+			// call the member function, which will set m_matrix and m_pos
+			bool result = p_motion_component->CallMemberFunction( Checksum, pParams, pScript );
+			
+			// and copy this into m_display_matrix
+			ResetLerpingMatrix();
+
+			// set the shadow to stick to his feet, assuming we are on the ground
+			// Note: These are used by the simple shadow, not the detailed one.
+			CShadowComponent* p_shadow_component = GetShadowComponentFromObject(GetObject());
+			Dbg_Assert(p_shadow_component);
+			
+			p_shadow_component->SetShadowPos( GetSkater()->m_pos );
+			p_shadow_component->SetShadowNormal( GetSkater()->m_matrix[Y] );
+
+			m_safe_pos = GetPos();			// needed for uberfrig
+			GetOldPos() = GetPos();			// needed for camera, so it thinks we've stopped
+			
+			GetObject()->SetTeleported();
+
+			// Force an update of the skater's camera if this is a local skater			
+			if( GetSkater()->IsLocalClient())
+			{
+				Obj::CCompositeObject *p_obj = GetSkater()->GetCamera();
+				if (p_obj)
+				{
+					p_obj->Update();	 // Not the best way of doing it...
+				}
+			}
+
+			if (!pParams->ContainsFlag( CRCD(0xd607e2e6,"NoReset") ))
+			{
+				// assume we want the skater to stop...						
+				m_last_ground_feeler.SetTrigger(0);  // patch to stop spurious gaps
+				GetVel().Set();
+				DUMP_VELOCITY;
+				SetState(GROUND);
+				
+				// Make sure the flippedness of the skater is in a stable state
+				// that reflects if he is goofy or not
+                SetFlag(FLIPPED, !GetSkater()->m_isGoofy);
+				
+				// reset any flippedness of animation
+				CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
+				Dbg_Assert(p_flip_and_rotate_component);
+				p_flip_and_rotate_component->ApplyFlipState();
+			}
+			else
+			{
+				// the "NoReset" flag is set
+				// so velocity is maintained
+				// but we need to check if the orient flag is set
+				// as that indicates that we want the velocity
+				// to be oriented as well.
+				// (if NoReset was set, then velocity will be zero)
+				if ( pParams->ContainsFlag( CRCD(0x90a91232, "orient") ))
+				{
+					float y_vel = GetVel()[Y];	   			// y velocity is preserved.....
+					GetVel()[Y] = 0.0f;
+					float speed = GetVel().Length();  		// how fast are we going?
+					GetVel() = GetMatrix()[Z] * speed;		// face forward, at that speed
+					GetVel()[Y] = y_vel;
+					DUMP_VELOCITY;
+				}
+			}
+			return result ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+
+		// Used by the create-a-goal cursor to initialise the cursor position.
+		// Cannot use the current skater's position because he might have just bailed by
+		// jumping into water, which would then cause the cursor to get stuck.
+		case 0xf7e21884: // GetLastGroundPos
+		{
+			pScript->GetParams()->AddVector(CRCD(0x7f261953,"Pos"),m_last_ground_pos[X],m_last_ground_pos[Y],m_last_ground_pos[Z]);
+			break;
+		}
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterCorePhysicsComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::Reset (   )
+{
+	// In Network games, don't set the skater to 0,0,0 because he is about to be teleported to a starting position and 0,0,0 means nothing.
+	if (!GameNet::Manager::Instance()->InNetGame())
+	{
+		/*
+		GetPos().Set(0.0f, 0.0f, 0.0f);
+		GetOldPos().Set(0.0f, 0.0f, 0.0f);
+		m_safe_pos.Set(0.0f, 0.0f, 0.0f);
+		*/
+		GetOldPos() = GetPos();
+		m_safe_pos = GetPos();
+		DUMP_POSITION;
+	}
+	
+	m_pre_lip_pos.Set();
+	
+	/*
+	GetMatrix().Identity();
+	ResetLerpingMatrix();
+	*/
+	
+	m_state_change_timestamp = Tmr::GetTime();
+	
+	GetVel().Set(0.0f, 0.0f, 0.0f);
+	DUMP_VELOCITY;
+	
+	SetState(GROUND);
+	m_previous_state = GROUND;
+	
+	for (int flag = NUM_ESKATERFLAGS; flag--; )
+	{
+		SetFlagTrue(static_cast< ESkaterFlag >(flag));
+		SetFlagFalse(static_cast< ESkaterFlag >(flag));
+	}
+	SetFlag(FLIPPED, !GetSkater()->m_isGoofy);
+	
+	mp_movable_contact_component->LoseAnyContact();
+	
+	// bit nasty, prevents us getting "SKATE_OFF_EDGE" triggers
+	m_last_ground_feeler.SetTrigger(0);
+
+	m_wall_push_radius = 0.0f;
+	
+	m_tense_time = 0;
+
+	mWallrideTime = 0;
+	
+	mNoSpin = false;
+	
+	m_bail = false;
+	
+	m_force_cankick_off = false;
+	m_pressing_down_brakes = true;
+	
+	mp_state_component->mJumpedOutOfLipTrick = false;
+		
+	// mp_rail_node points to previous rail grinded, which might no longer be valid
+	// best to set it to NULL  (fixed crash in park editor with multiple test-plays TT#678)	
+	mp_rail_node = NULL;
+	
+	m_override_limits_time = 0.0f;
+	m_transfer_overrides_factor = 1.0f;
+	
+	m_last_wallplant_time_stamp = 0;
+	m_last_wallpush_time_stamp = 0;
+	
+	m_last_jump_time_stamp = 0;
+	
+	m_special_friction_duration = 0.0f;
+}
+          
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::ReadySkateState ( bool to_ground_state, const SRailData* p_rail_data, const SAcidDropData* p_acid_drop_data )
+{
+	// setup the state in preparation for being in skating mode next object update
+	
+	// reset all flags except FLIPPED
+	ResetFlags();
+	
+	if (to_ground_state)
+	{
+		SetState(GROUND);
+		m_previous_state = GROUND;
+	}
+	else
+	{
+		SetState(AIR);
+		m_previous_state = AIR;
+		
+		// no in-air orientation control after walking
+		SetFlagTrue(NO_ORIENTATION_CONTROL);
+		
+		if (mp_walk_component->m_disallow_acid_drops)
+		{
+			SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
+		}
+	}
+	
+	m_state_change_timestamp = Tmr::GetTime();
+	
+	ResetLerpingMatrix();
+	
+	m_display_normal = m_last_display_normal = m_current_normal = GetMatrix()[Y];
+	
+	GetOldPos() = m_safe_pos = GetPos();
+	
+	// bit nasty, prevents us getting "SKATE_OFF_EDGE" triggers
+	m_last_ground_feeler.SetTrigger(0);
+
+	m_wall_push_radius = 0.0f;
+	
+	m_tense_time = 0;
+
+	mWallrideTime = 0;
+	
+	mNoSpin = false;
+	
+	// m_bail = false;
+	
+	m_force_cankick_off = false;
+	m_pressing_down_brakes = true;
+	
+	mp_state_component->mJumpedOutOfLipTrick = false;
+		
+	m_override_limits_time = 0.0f;
+	m_transfer_overrides_factor = 1.0f;
+	
+	m_last_wallplant_time_stamp = 0;
+	m_last_wallpush_time_stamp = 0;
+	
+	m_last_jump_time_stamp = 0;
+	
+	// handle special transitions
+	if (p_rail_data)
+	{
+		// TT8717.  If walker had a movable contact, but that contact was not the same as the object containing the relavent rail manager component,
+		// the skater was asserting in do_rail_physics.  We need to lose any old contact and acquire the appropriate contact.
+		mp_movable_contact_component->LoseAnyContact();
+		if (p_rail_data->p_movable_contact)
+		{
+			mp_movable_contact_component->ObtainContact(p_rail_data->p_movable_contact);
+		}
+		got_rail(p_rail_data->rail_pos, p_rail_data->p_node, p_rail_data->p_rail_man, true, true);
+		GetOldPos() = m_safe_pos = GetPos();
+	}
+	else if (p_acid_drop_data)
+	{
+		enter_acid_drop(*p_acid_drop_data);
+	}
+	
+	#if 0
+	const char* p_state_names [   ] =
+	{
+		"GROUND",
+		"AIR",
+		"WALL",
+		"LIP",
+		"RAIL",
+		"WALLPLANT"
+	};
+	DUMPS(p_state_names[GetState()]);
+	#endif
+}
+          
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::CleanUpSkateState (   )
+{
+	if (m_vert_air_last_frame)
+	{
+		m_vert_air_last_frame = false;
+        GetObject()->BroadcastEvent(CRCD(0x5e27200a, "SkaterExitVertAir"));
+	}
+	
+	// longer rerail delay when walking
+	m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0xe4412eb7, "Rail_walk_rerail_time")));
+	
+	// lip time counts towards rerail delay when walking
+	if (m_lip_time > m_rail_time)
+	{
+		m_rail_time = m_lip_time;
+	}
+	
+	// no grinding from walking after jumping out of a lip
+	if (GetState() == AIR && m_previous_state == LIP)
+	{
+		SetFlagFalse(CAN_RERAIL);
+	}
+	
+	// Extra clean up so that an observing camera will act OK while walking.  Note that observing cameras always use skater camera logic, even when the
+	// observed player is walking.
+	mp_state_component->m_state = GROUND;
+	mp_state_component->m_camera_display_normal.Set(0.0f, 1.0f, 0.0f);
+	mp_state_component->m_camera_current_normal.Set(0.0f, 1.0f, 0.0f);
+	mp_state_component->m_spine_vel.Set();
+	mp_state_component->mJumpedOutOfLipTrick = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkaterCorePhysicsComponent::ResetFlags (   )
+{
+	// reset all flags except FLIPPED
+	for (int flag = NUM_ESKATERFLAGS; flag--; )
+	{
+		ESkaterFlag skater_flag = static_cast< ESkaterFlag >(flag);
+		if (skater_flag == FLIPPED) continue;
+		SetFlagTrue(skater_flag);
+		SetFlagFalse(skater_flag);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::SetState ( EStateType state )
+{
+	if (mp_state_component->m_state != state)
+	{
+		m_state_change_timestamp = Tmr::GetTime();
+		m_previous_state = mp_state_component->m_state;
+	}
+	
+	// if going out of lip state, and previous lip pos is valid (not zero), then move back to the previous lip position (so we drop down nicely)
+	if (mp_state_component->m_state == LIP
+		&& state != LIP 
+		&& (m_pre_lip_pos[X] != 0.0f || m_pre_lip_pos[Y] != 0.0f || m_pre_lip_pos[Z] != 0.0f))
+	{
+		// *** (SetState(LIP))  kind of nasty, but we want to allow them to move through the lip
+		// we leave m_old_trigger_pos alone
+		GetPos() = m_pre_lip_pos;
+		GetOldPos() = GetPos();
+		DUMP_POSITION;
+	}
+
+	// If setting the state to anything other than lip then kill the pre_lip_pos	
+	if (state != LIP)
+	{
+		m_pre_lip_pos.Set(0.0f, 0.0f, 0.0f);
+	}
+
+	if (state != GROUND)
+	{
+		// start recording graffiti tricks
+		mp_trick_component->SetGraffitiTrickStarted( true );
+	}
+
+	if (mp_state_component->m_state == RAIL && state != RAIL)
+	{
+		SetFlagFalse(RAIL_SLIDING);
+	}
+
+	if (mp_state_component->m_state == AIR && state != AIR)
+	{
+		// If going from AIR to any other state, record the landing time. Required for the AirTimeLessThan and AirTimeGreaterThan functions.
+		m_landed_time = Tmr::GetTime();
+		
+		SetFlagFalse(NO_ORIENTATION_CONTROL);
+		SetFlagFalse(OLLIED_FROM_RAIL);
+	}	
+	else if (mp_state_component->m_state != AIR && state == AIR)
+	{
+		// If going to AIR, record the takeoff time, unless going from WALL to AIR.
+		// (The 'unless' bit fixes TT493, where hitting the ground from doing a wall-ride was not setting a big enough airtime)
+		if (mp_state_component->m_state != WALL)
+		{
+			m_went_airborne_time = Tmr::GetTime();
+		}	
+
+		// Reset the spin tracking stuff.
+		mp_trick_component->mTallyAngles = 0.0f;
+		
+		// Clear the L1 and R1 triggers, since they're used for spin taps.
+		mp_input_component->GetControlPad().m_L1.ClearTrigger();
+		mp_input_component->GetControlPad().m_R1.ClearTrigger();
+		// Just to be sure
+		m_tap_turns = 0.0f;
+	}   
+	
+	mp_state_component->m_state = state;
+																			   
+	mp_sound_component->SetState(state);
+}
+          
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::ReverseFacing (   )
+{
+	GetMatrix()[Z] = -GetMatrix()[Z];
+	GetMatrix()[X] = -GetMatrix()[X];
+	ResetLerpingMatrix();
+	mRail_Backwards = !mRail_Backwards;
+	
+	#ifdef __NOPT_ASSERT__
+	if (DebugSkaterScripts && GetObject()->GetType() == SKATE_TYPE_SKATER)
+	{
+		printf("%d: Rotating skater\n", (int) Tmr::GetRenderFrame());
+	}
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::CollideWithOtherSkaterLost ( CCompositeObject* p_other_skater )
+{
+	// reset all flags except FLIPPED
+	ResetFlags();
+
+	SetState(AIR);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::limit_speed (   )
+{
+	float save_y = GetVel()[Y];
+	GetVel()[Y] = 0.0f;
+	
+	float speed = GetVel().Length();
+	float max_max_speed = GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")); 
+	float max_speed = GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat"));
+
+	if (m_override_limits_time != 0.0f)
+	{
+		// -1.0f is a magic number causing no time limit
+		if (m_override_limits_time != -1.0f)
+		{
+			m_override_limits_time -= m_frame_length;
+			if (m_override_limits_time < 0.0f)
+			{
+				m_override_limits_time = 0.0f;
+			}
+		}
+		max_max_speed = m_override_max_max;
+		max_speed = m_override_max;
+	}
+	
+	if (speed > max_max_speed)
+	{
+		speed = max_max_speed;
+		GetVel().Normalize(speed);
+		DUMP_VELOCITY;
+	}
+	
+	if (speed > max_speed)
+	{
+		apply_wind_resistance(GetPhysicsFloat(CRCD(0x850eb87a, "Physics_Heavy_Air_Friction")));
+	}
+	
+	GetVel()[Y] = save_y;
+	
+	// decrement the special friction counter
+	if (m_special_friction_duration != 0.0f)
+	{
+		m_special_friction_duration -= m_frame_length;
+		if (m_special_friction_duration <= 0.0f)
+		{
+			// reset the special friction to default
+			m_special_friction_duration = 0.0f;
+			m_rolling_friction = GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction"));
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::apply_wind_resistance ( float friction )
+{
+	// air friction is proportional to the square of the velocity; this is a limiting friction, and has much more effect at high speeds
+	float speed_squared = GetVel().LengthSqr();
+	if (speed_squared < 0.00001f) return;
+	
+	Mth::Vector air_friction = GetVel();
+	air_friction.Normalize(friction * 60.0f * m_frame_length * speed_squared);
+	
+	if (air_friction.LengthSqr() > speed_squared)
+	{
+		GetVel().Set();
+		DUMP_VELOCITY;
+	}
+	else
+	{
+		GetVel() -= air_friction;
+		DUMP_VELOCITY;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_on_ground_physics (  )
+{		
+	// clear any flags that have no meaning when on the ground
+	SetFlagFalse(CAN_BREAK_VERT);
+	SetFlagFalse(VERT_AIR);
+	SetFlagFalse(TRACKING_VERT);
+	SetFlagFalse(SPINE_PHYSICS);
+	SetFlagFalse(IN_ACID_DROP);
+	SetFlagFalse(IN_RECOVERY);
+	SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
+
+	// ground skateing will cancel any memory of what rail we were on, so the next one seems fresh	
+	mp_rail_node = NULL;
+	m_last_rail_trigger_node_name = 0;
+	
+	// rotate velocity, so it lays in the plane we are on: (as skaters orientation can lag behind)	
+	// Dave note: calling RotateToPlane() with too small |vector| causes Nan problems - fix at some point.
+	if( GetVel().Length() > 0.001f )
+	{
+		GetVel().RotateToPlane(m_current_normal);
+	}
+		
+	// get gravitational force 	
+	Mth::Vector gravity(0.0f, GetPhysicsFloat(CRCD(0x6618a713, "Physics_Ground_Gravity")), 0.0f);
+
+	// allow for override if we are going up the slope (we might want to set gravity to zero when rolling up a slope)
+	if (m_override_limits_time != 0.0f && GetVel()[Y] > 0.0f)
+	{
+		gravity.Set(0.0f, m_override_down_gravity, 0.0f);
+	}
+
+	// project gravity onto the plane we are on
+	gravity.ProjectToPlane(m_current_normal);
+	
+	// add gravity into our velocity, adjusting for time
+	GetVel() += gravity * m_frame_length;
+	DUMP_VELOCITY;
+
+	// based on if we are doing a balance trick, then either handle braking/kicking, or do the balance physics
+	switch (mp_balance_trick_component->mBalanceTrickType)
+	{
+		case 0:
+			// Only do braking & speeding up when not doing a balance trick like a manual			
+			if (is_trying_to_brake())
+			{
+				do_brake();
+			}
+			else
+			{
+				m_braking = false;
+				if (can_kick())
+				{
+					do_kick();
+				}
+			}
+			break;
+			
+		case CRCC(0xac90769, "NoseManual"):
+		case CRCC(0xef24413b, "Manual"):
+			m_braking = false;
+			
+			mp_balance_trick_component->mManual.DoManualPhysics();
+
+			// start recording graffiti tricks
+			mp_trick_component->SetGraffitiTrickStarted(true);
+			
+			if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL")))
+			{
+				// Here, set the flag. It may seem redundant, but the above line is very likely
+				// to be hacked by gameshark. They probably won't notice this one, which will
+				// set the flags as if they had actually enabled the cheat -- which enables us
+				// to detect that it has been turned on more easily.
+				Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL")));
+				mp_balance_trick_component->mManual.mManualLean = 0.0f;
+				mp_balance_trick_component->mManual.mManualLeanDir = 0.0f;
+				g_CheatsEnabled = true;
+			}
+			break;
+			
+		case CRCC(0x3506ce64, "Skitch"):
+			mp_balance_trick_component->mSkitch.DoManualPhysics();
+            if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH")))
+			{
+				// Here, set the flag. It may seem redundant, but the above line is very likely
+				// to be hacked by gameshark. They probably won't notice this one, which will
+				// set the flags as if they had actually enabled the cheat -- which enables us
+				// to detect that it has been turned on more easily.
+				Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH")));
+				mp_balance_trick_component->mSkitch.mManualLean = 0.0f;
+				mp_balance_trick_component->mSkitch.mManualLeanDir = 0.0f;
+				g_CheatsEnabled = true;
+			}
+			break;			
+			
+		case CRCC(0x255ed86f, "Grind"):
+		case CRCC(0x8d10119d, "Slide"):
+		{
+			#ifdef __NOPT_ASSERT__
+			CSkaterEndRunComponent* p_endrun_component = GetSkaterEndRunComponentFromObject(GetObject());
+			Dbg_Assert(p_endrun_component);
+            Dbg_MsgAssert(!p_endrun_component->IsEndingRun() || !p_endrun_component->RunHasEnded(), ("Grind balance trick done on ground?"));
+			#endif
+			break;			
+		}
+			
+		case CRCC(0xa549b57b, "Lip"):	
+		{
+			#ifdef __NOPT_ASSERT__
+			CSkaterEndRunComponent* p_endrun_component = GetSkaterEndRunComponentFromObject(GetObject());
+			Dbg_Assert(p_endrun_component);
+			Dbg_MsgAssert(!p_endrun_component->IsEndingRun() || !p_endrun_component->RunHasEnded(), ("Lip balance trick done on ground?"));
+			#endif
+			break;
+		}
+		
+		default:	
+		{
+			Dbg_Assert(false);
+			break;
+		}
+	}		
+
+	// Ground friction is rolling friction + wind resistance
+	// Update velocity for friction before using velocity
+	handle_ground_friction();
+
+	// if too close to a wall, then adjust velocity to move the skater away from it
+	// the distance that is checked can be changed by script, and probably gets big in bails, to stop the skater clipping through walls
+	push_away_from_walls();
+
+	// Calculate how much we want to move, based on the velocity
+	Mth::Vector m_movement = GetVel() * m_frame_length;
+
+	// when skitching, we get sucked into the skitch point
+	if (GetFlag(SKITCHING))
+	{
+		if (mp_movable_contact_component->GetContact() && mp_movable_contact_component->GetContact()->GetObject())
+		{
+			GetVel() = mp_movable_contact_component->GetContact()->GetObject()->GetVel() * GetPhysicsFloat(CRCD(0xdef25b34, "skitch_speed_match"));
+			DUMP_VELOCITY;
+		}
+		m_movement.Set();
+		move_to_skitch_point();
+	}
+
+	// if we are in contact with something, then factor in that "movement," but not if (we are skitching, and in not in initial movement) 
+	if (mp_movable_contact_component->UpdateContact(GetPos()))  // still need to update it every frame, otherwise it gets confused
+	{
+		if (!GetFlag(SKITCHING))
+		{
+			// handle movement due to contact seperately from normal physic movement 
+			
+			GetPos() += mp_movable_contact_component->GetContact()->GetMovement();		 
+			GetOldPos() = GetPos();	// (WITH CONTACT) frig, as movement is due to something carying us, no need to incorporate that into collision   
+			m_safe_pos = GetPos();	// (WITH CONTACT) frig, as movement is due to something carying us, no need to incorporate that into collision
+			DUMP_POSITION;
+			
+			if (mp_movable_contact_component->GetContact()->IsRotated())
+			{
+				GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
+				m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
+			}
+		}
+			
+	}
+	
+	// set "move_again" flag, for while loop
+	// generally this will be cleared after the first time through the loop
+	// but it can be set again, indicating that out movmeent was brought short
+	// by hitting a curver surface that we can track around (like a Qp)			  
+	bool move_again = true;
+	
+	// we only let them move again once
+	// so we have a flag that says if we have already done it this frame
+	bool already_moved_again = false;	
+	
+	while (move_again)
+	{
+		move_again = false;
+		
+		Mth::Vector	start_pos = GetPos();	  		// remember where we started on this iteration
+		GetPos() += m_movement;	  				// Move him
+		DUMP_POSITION;
+			
+		// Handle collisions //////////////////////////////////////////////						
+		if (!GetFlag(SKITCHING))   	// if skitching, then dont bother with forward collision
+		{
+ 			handle_forward_collision();							
+		}
+		
+
+		// Mick - removed the test for m_moving_to_skitch for now
+		// as we want the cars to be able to drag the player over gaps and jumps
+		// and it does not seem to look bad this way
+		// the only problem might be during very abrupt cahnges in ground angle, 
+		// or maybe very uneven ground		
+		if (GetFlag(SKITCHING) /* && m_moving_to_skitch*/)
+		{
+			// moving to skitch point, so don't snap to ground, as
+			// we might snap onto the vehicle
+			// instead, just project the skater onto the plane of the car
+			
+			// Dan: We still need to update terrain, however
+			m_feeler.m_start = GetPos() + 36.0f * GetMatrix()[Y];
+			m_feeler.m_end = GetPos() - 200.0f * GetMatrix()[Y];
+			if (get_member_feeler_collision())
+			{
+				set_terrain(m_feeler.GetTerrain());
+			}
+		}
+		else
+		{
+			snap_to_ground();
+		}
+													 
+		// now see how far we have moved
+		// interestingly we often move more than we are supposed to
+		// probably due to the "snap to ground" stuff.		
+		Mth::Vector actual_movement_vector;
+		actual_movement_vector = GetPos() - start_pos;
+		float actual_distance_moved = actual_movement_vector.Length();
+		float attempted_distance = m_movement.Length();
+
+		if (!already_moved_again && GetState() == GROUND)
+		{
+			if (actual_distance_moved < (attempted_distance - 0.1f))
+			{
+				m_movement = GetVel();					 	
+				m_movement.Normalize();			  								// get new direction of travel
+				m_movement *= attempted_distance;									// at old speed
+				m_movement *= 1.0f - actual_distance_moved / attempted_distance;	// scale to account for movement							
+				move_again = true;
+				already_moved_again = true;				
+			}
+		}
+	}
+
+	// The remaining "ground physics" should only be done if we are still on the ground (might have skated off it) 
+	if (GetState() == GROUND)
+	{
+		handle_ground_rotation();			
+		
+		// don't let the board slide sideways	
+		// K: Avoid anything that might change the velocity direction if this flag is set.
+		if (!m_lock_velocity_direction)
+		{
+			remove_sideways_velocity(GetVel());
+		}	
+	
+		if (!GetFlag(SKITCHING))
+		{
+			// check if we are too close to a wall, and pop us out and away		
+			check_side_collisions();		
+			
+			// check if we are moving slowly and leaning, and mush us more away from a wall if so
+			check_leaning_into_wall();
+		}
+		
+		// K: This flag is to allow the skater to be rotated on the ground without affecting his
+		// velocity direction, so also disable the flipping otherwise 360 rotations won't be possible.
+		if (!m_lock_velocity_direction)
+		{
+			// might have gone upa  slope, and gravity pulled us down, so we are not skating backwards
+			flip_if_skating_backwards();	
+		}	
+	
+		// Check for jumping
+		if (maybe_flag_ollie_exception())	
+		{
+			maybe_straight_up();
+			SetFlagTrue(CAN_BREAK_VERT);
+		}
+	
+		mp_trick_component->TrickOffObject(m_last_ground_feeler.GetNodeChecksum());
+			
+		m_tap_turns = 0.0f;	
+	}
+		
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
+	{
+		Gfx::AddDebugLine(GetPos() + m_current_normal, GetOldPos() + m_current_normal, GREEN, 0, 0);
+	}
+	#endif
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::is_trying_to_brake (   )
+{								   
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	// if not autokick, and not accelerating (square pressed), and going slow, then brake	
+	if (!m_auto_kick && !control_pad.m_square.GetPressed() && GetVel().Length() < 50.0f)
+	{
+		return true;
+	}
+	
+	return control_pad.m_down.GetPressed() 				// Down must be pressed
+		&& m_pressing_down_brakes						// and must be enabled
+		&&
+		(
+			GetVel().Length() < 50.0f		   	// and either going really slow
+			||
+			(
+				!control_pad.m_right.GetPressed()		// or not pressed right or left
+				 &&
+				!control_pad.m_left.GetPressed()
+			)
+			||
+			(                                           // or going backwards
+				Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f
+			)
+		);				
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_brake (   )
+{		
+	// Handle braking
+	
+	m_braking = true;
+	
+	if (on_steep_slow_slope())
+	{
+		m_braking = false;
+		return;
+	}
+
+	float speed = GetVel().Length();
+
+	// if already really slow (or stopped), then just stop
+	if (speed < GetPhysicsFloat(CRCD(0xe5d73f6f, "Physics_Brake_Acceleration")) * 2.0f * m_frame_length)
+	{
+			GetVel().Set();
+			DUMP_VELOCITY;
+	}
+	else
+	{
+		#if 0 // old code, this check doesn't seem necessary given the above if branch; Dan
+		// Mth::Vector forward = GetVel();
+		// forward.Normalize();
+		// forward *= -PHYSICS_BRAKE_ACCELERATION;
+		// Mth::Vector old_vel = GetVel();					// remember old velocity		
+		// m_vel += forward * m_frame_length;						 	// apply braking force
+		// if (Mth::DotProduct(GetVel(), old_vel) < 0.0f)	// if the velocty now in other direction
+		// {
+			// GetVel().Set();								// then clear it to zero velocity
+		// }
+		#else
+		Mth::Vector brake = GetVel();
+		brake.Normalize(-GetPhysicsFloat(CRCD(0xe5d73f6f, "Physics_Brake_Acceleration")) * m_frame_length);
+		GetVel() += brake;
+		DUMP_VELOCITY;
+		#endif
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::on_steep_slow_slope (   )
+{
+	float speed = GetVel().Length();
+	if (speed < GetPhysicsFloat(CRCD(0x62a1fa03,"Skater_max_sloped_turn_speed"))
+		&& m_current_normal[Y] < GetPhysicsFloat(CRCD(0xc3527ef2, "Skater_max_sloped_turn_cosine")))
+	{
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::can_kick (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();					 
+	
+	if (m_force_cankick_off)
+	{
+		return false;
+	}
+
+	// don't allow kicking if autokick is off and square not pressed	
+	if (!m_auto_kick && !control_pad.m_square.GetPressed())
+	{
+		return false;
+	}
+	
+	// don't allow kicking (accelerating) if "down" is pressed
+	// as we would either be braking or sharp turning  
+	if (control_pad.m_down.GetPressed())
+	{
+		return false;
+	}
+
+	float speed = GetVel().Length();
+	
+	if (!GetFlag(TENSE))
+	{
+		if (speed > GetSkater()->GetScriptedStat(CRCD(0x4610c2e3, "Skater_Max_Standing_Kick_Speed_Stat")))
+		{
+			return false;
+		}
+	}
+	else
+	{
+		if (speed > GetSkater()->GetScriptedStat(CRCD(0x92e0247c, "Skater_Max_Crouched_Kick_Speed_Stat")))
+		{
+			return false;
+		}
+	}
+	
+	Dbg_Assert(m_auto_kick || control_pad.m_square.GetPressed());
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_kick (   )
+{
+	// Get skater's forward direction
+	Mth::Vector forward;
+	
+	// K: Avoid anything that might change the velocity direction if this flag is set.
+	if (m_lock_velocity_direction)
+	{
+		forward = GetVel();
+		
+		// Make sure the skater doesn't get stuck unable to move.
+		if (forward.Length() < 0.5f)
+		{
+			forward = GetMatrix()[Z];
+		}	
+		else
+		{
+			forward.Normalize();
+		}
+	}
+	else
+	{
+		forward = GetMatrix()[Z];
+	}
+		
+	if (GetFlag(TENSE))
+	{
+		forward *= GetSkater()->GetScriptedStat(CRCD(0x3d24128e, "Physics_crouching_Acceleration_stat"));
+	}
+	else
+	{
+		forward *= GetSkater()->GetScriptedStat(CRCD(0x5f9b864d, "Physics_Standing_Acceleration_stat"));
+	}
+		
+	// apply to velocity
+	GetVel() += forward * m_frame_length;
+	DUMP_VELOCITY;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_ground_friction (   )
+{
+	// Apply the various type of friction that are acting when on the ground
+	
+	// if no autokick, and we are not bailing, and we have default friction then don't apply any friction
+	// as it sucks to slow down when you do not have autokick
+	if (!m_auto_kick && !GetFlag(IS_BAILING) && m_rolling_friction == GetPhysicsFloat(CRCD(0x78f80ec4, "Physics_Rolling_Friction")))
+	{
+		// autokick friction
+		// currently none
+	}
+	else
+	{	
+		// non-autokick friction
+		handle_wind_resistance();
+		handle_rolling_resistance();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_wind_resistance (   )
+{	
+	// Wind resistance differs, based on if we are crouched or not (as when we are crouched, we have a lower profile, so less wind resistance)
+	
+	float crouched_friction = GetPhysicsFloat(CRCD(0xbed96eda, "Physics_Crouched_Air_Friction"));
+	float standing_friction = GetPhysicsFloat(CRCD(0x1a78b6fc, "Physics_Standing_Air_Friction"));
+	
+	if (m_override_limits_time != 0.0f)
+	{
+		crouched_friction = m_override_air_friction;
+		standing_friction = m_override_air_friction;
+	}
+		  
+	if (GetFlag(TENSE))
+	{
+		apply_wind_resistance(crouched_friction);
+	}
+	else
+	{
+		apply_wind_resistance(standing_friction);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		
+void CSkaterCorePhysicsComponent::handle_rolling_resistance (   )
+{	
+	// Only have rolling resistance if we are not going slow on a steep slope.	
+	if (!slide_off_slow_steep_slope())
+	{
+		apply_rolling_friction();
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::slide_off_slow_steep_slope (   )
+{
+	// If we are on very steep ground and moving slowly then do not allow the skater to brake
+	if (on_steep_slow_slope())
+	{
+		float speed = GetVel().Length();
+		
+		Mth::Vector forward;
+		if (speed < 0.001f)
+		{
+			forward = GetMatrix()[Z];
+		}
+		else
+		{
+			forward = GetVel();
+			
+		}
+		
+		Mth::Vector fall(0.0f, -1.0f, 0.0f);
+		fall.ProjectToPlane(m_current_normal);	  
+		
+		float angle = Mth::GetAngleAbout(forward, fall, GetMatrix()[Y]);
+		float rot = GetPhysicsFloat(CRCD(0x7dd5678b, "Skater_Slow_Turn_on_slopes")) * Mth::Sgn(angle) * m_frame_length;
+		if (Mth::Abs(rot) > Mth::Abs(angle))
+		{
+			// just about done, so just turn the last bit of angle left
+			rot = angle;
+		}
+		GetMatrix().RotateYLocal(rot);				
+		m_lerping_display_matrix.RotateYLocal(rot);				
+		
+		return true;
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::apply_rolling_friction (   )
+{
+	// apply a constant braking friction; if the velocity reaches 0, then we will stop (and not change direction)  		
+	// This funtion will typically be used for rolling resistance
+	
+	// rolling friction is a constant, and is mostly noticable at slow speeds
+	// if your speed is less than that produced by the force of rolling friction, then you will abruptly stop
+	Mth::Vector rolling_friction = GetVel();
+	float length = rolling_friction.Length();
+	
+	if (length < 0.0001f)
+	{
+		GetVel().Set();
+		DUMP_VELOCITY;
+		return;
+	}
+	
+	rolling_friction *= 60.0f * m_rolling_friction * m_frame_length / length;
+	
+	if (rolling_friction.LengthSqr() > length * length)
+	{
+		GetVel().Set();
+		DUMP_VELOCITY;
+	}
+	else
+	{
+		GetVel() -= rolling_friction;
+		DUMP_VELOCITY;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		
+void CSkaterCorePhysicsComponent::push_away_from_walls (   )
+{
+	if (!m_wall_push_radius || !m_wall_push_speed) return;
+	
+	m_wall_push.Set();
+	m_wall_dist = 1.0f;
+	
+	Mth::Vector	start, end;
+	
+	start = GetPos() + GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xb45b39e2, "Skater_side_collide_height"));
+	
+	end = start + GetMatrix()[X] * m_wall_push_radius;
+	check_for_wall_push(start, end, 0);
+	
+	end = start - GetMatrix()[X] * m_wall_push_radius;
+	check_for_wall_push(start, end, 1);
+	
+	end = start + GetMatrix()[Z] * m_wall_push_radius;
+	check_for_wall_push(start, end, 2);
+	
+	end = start - GetMatrix()[Z] * m_wall_push_radius;
+	check_for_wall_push(start, end, 3);
+
+	if (m_wall_dist == 1.0f) return;
+	
+	float push_speed = m_wall_push_speed * (1.0f - m_wall_dist);
+	m_wall_push.Normalize(push_speed);
+	GetVel() += m_wall_push;
+	GetVel().RotateToPlane(m_current_normal);
+	DUMP_VELOCITY;
+	
+	// if facing into the wall, then rotate away from it
+	if (Mth::DotProduct(GetMatrix()[Z], m_feeler.GetNormal()) < 0.0f)
+	{
+		Mth::Vector target = GetMatrix()[Z];
+		target.RotateToPlane(m_feeler.GetNormal());
+		float angle = Mth::GetAngleAbout(GetMatrix()[Z], target, GetMatrix()[Y]);
+		
+		float rot = m_wall_rotate_speed * Mth::Sgn(angle) * m_frame_length;
+		if (Mth::Abs(rot) > Mth::Abs(angle))
+		{
+			// just about done, so just turn the last bit of angle left
+			rot = angle;
+		}
+		GetMatrix().RotateYLocal(rot);				
+		m_lerping_display_matrix.RotateYLocal(rot);	   
+	} 		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::check_for_wall_push ( const Mth::Vector& start, const Mth::Vector& end, int index )
+{
+	// do not allow a push in this direction until a push in any other direction has NOT happenend for half a second
+	// this should prevent the nasty flickering
+	for (int i = 0; i < 4; i++)
+	{
+		if (i == index) continue;
+		
+		// get time for opposite direction
+		Tmr::Time t = Tmr::ElapsedTime(m_push_time[index ^ 1]);
+		
+		if (t > 500)
+		{
+			return;
+		}
+	}
+		
+	m_col_start = start;
+	m_col_end = end;
+	
+	if (get_member_feeler_collision())
+	{
+		m_wall_push += m_feeler.GetNormal();
+		if (m_feeler.GetDist() < m_wall_dist)
+		{
+			m_wall_dist = m_feeler.GetDist();
+		}
+		m_push_time[index] = Tmr::GetTime();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::move_to_skitch_point (   )						
+{
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	if (!mp_movable_contact_component->GetContact() || !mp_movable_contact_component->GetContact()->GetObject())
+	{
+		FLAGEXCEPTION(CRCD(0x47d44b84, "OffMeterBottom"));
+		return;
+	}
+
+	CCompositeObject* p_skitch_object = mp_movable_contact_component->GetContact()->GetObject();
+	
+	CSkitchComponent* p_skitch_comp = GetSkitchComponentFromObject(p_skitch_object);
+	Dbg_Assert(p_skitch_comp);
+	
+	////////////////////////////////////////////////////////
+	// Do moving between the skitch nodes
+	// if L1 or R1 pressed
+	// Basically we just look for nodes to the left or right of us at a
+	// one foot interval
+	// up a a maximum of 10 feet
+	// if one is found, then switch the m_node_index to that node 
+
+	float dir = 0.0f;	   
+	   
+	if (control_pad.m_L1.GetTriggered())
+	{		
+		control_pad.m_L1.ClearTrigger();
+		dir = 1.0f;
+	}	
+
+	if (control_pad.m_R1.GetTriggered())
+	{		
+		control_pad.m_R1.ClearTrigger();
+		dir = -1.0f;
+	}	
+
+	if (control_pad.m_L2.GetTriggered())
+	{		
+		control_pad.m_L2.ClearTrigger();
+		dir = 1.0f;
+	}	
+
+	if (control_pad.m_R2.GetTriggered())
+	{		
+		control_pad.m_R2.ClearTrigger();
+		dir = -1.0f;
+	}	
+
+	// if moving, and either was not moving or moving in opposite direction, then we can actually try looking for a node
+	if (dir != 0.0f && (!m_moving_to_skitch || dir != m_skitch_dir))
+	{
+		float len = 6.0f;
+		float step = 6.0f;
+		float max = 12.0f * 10.0f;
+		while (len < max)
+		{
+			// get an offset to the right or left
+			Mth::Vector offset = GetMatrix()[X];  // X points left, from the camera's POV			
+			offset *= len * dir;
+			Mth::Vector	test_pos = GetPos() + offset;
+	
+			// see if there is a skitch point there
+			Mth::Vector dummy(0.0f, 0.0f, 0.0f);
+			int index = p_skitch_comp->GetNearestSkitchPoint(&dummy, test_pos);
+	
+			// if there is, then switch to that point
+			if (index != m_skitch_index)
+			{
+				m_skitch_index = index;
+				m_moving_to_skitch = true;
+				m_skitch_dir = dir;
+				
+				if (dir == 1.0f)
+				{
+					FLAGEXCEPTION(CRCD(0x74bf80cf, "SkitchLeft"));
+				}
+				else
+				{
+					FLAGEXCEPTION(CRCD(0x2e7474b5, "SkitchRight"));
+				}				
+				break;
+			}	
+		
+			len += step;
+		}
+	}
+	
+	// 
+	// end of moving between skitch nodes
+	///////////////////////////////////////////////////
+	
+	if (!mp_movable_contact_component->GetContact() || !GetFlag(SKITCHING)) return;
+
+	///////////////////////////////////////////////////////
+	// Do the actual movement
+
+	Mth::Vector skitch_point;
+	if (!p_skitch_comp->GetIndexedSkitchPoint( &skitch_point, m_skitch_index)) return;
+
+	// zero out W component to prevent overflows (in theory this should not be necessary)
+	GetPos()[W] = 0.0f;
+
+	Mth::Vector target = skitch_point + GetPhysicsFloat(CRCD(0x21fb182c, "skitch_offset")) * -p_skitch_object->GetMatrix()[Z];
+
+	if (m_moving_to_skitch)
+	{
+		Mth::Vector con_move = mp_movable_contact_component->GetContact()->GetMovement();
+
+		GetPos() += con_move;
+		DUMP_POSITION;
+
+		Mth::Vector dir = target - GetPos();
+		float dir_length_sqr = dir.LengthSqr();
+		float suck_speed = GetPhysicsFloat(CRCD(0x97496256, "skitch_suck_speed")) * m_frame_length;
+
+		// if skater is stuck in a wall, then end the skitch when car is fifteen feet away 														 
+		if (dir_length_sqr > FEET(15.0f) * FEET(15.0f))
+		{
+			FLAGEXCEPTION(CRCD(0x47d44b84, "OffMeterBottom"));
+			return;
+		}
+
+		if (dir_length_sqr <= suck_speed * suck_speed)
+		{
+			// we have arrived, so no need for sucking later
+			GetPos() = target;
+			m_moving_to_skitch = false;
+			DUMP_POSITION;
+		}							   
+		else
+		{
+			dir *= suck_speed / sqrtf(dir_length_sqr);
+			GetPos() += dir;
+			DUMP_POSITION;
+		}
+	}
+	else
+	{
+			GetPos() = target;
+			DUMP_POSITION;
+	}
+	
+	// Copy the objects Display matrix over the skater's
+	// will orient the skater the same way as the thing that is dragging it, so if the car looks solid, then so should the skater...
+	GetMatrix() = p_skitch_object->GetDisplayMatrix();
+	
+	// we also set the display matrix, to avoid little glitches
+	ResetLerpingMatrix();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+     
+void CSkaterCorePhysicsComponent::handle_forward_collision (   )
+{
+	if (GetPos() == GetOldPos()) return;
+	
+	Mth::Vector	forward = GetPos() - GetOldPos();
+	forward.Normalize();	
+	
+	Mth::Vector up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b, "Skater_First_Forward_Collision_Height"));
+	
+	m_col_start = GetOldPos() + up_offset;
+	
+	m_col_end = GetPos() + up_offset + forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
+
+	if (!get_member_feeler_collision()) return;
+	
+	// we have hit something going forward
+	// either it is a wall, and we need to bounce off it or it is a steep QP, and we need to stick to it.
+	// (note, with the slow normal changing, then it is possible that we might even collide with the poly that we are on
+	
+	Mth::Vector	normal = m_feeler.GetNormal();								
+							
+	float dot = Mth::DotProduct(normal, m_current_normal);			
+
+	// For fairly shallow curves, the dot between two normals will be pretty large
+	// it's very important here to distinguish between a tight curve (like in a narrow QP) and a direct hit with a wall.
+			   
+	if (!m_col_flag_skatable || Mth::Abs(dot) < 0.01f)
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_BONK, m_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return;
+		
+		bounce_off_wall(normal);
+	}
+	else
+	{
+		// it's a qp, stick to it
+		
+		// Just move our contact point to the point of collision.  This is not right, as it could kill a lot of the movement for this frame
+		// but should do for now (and stops you falling through)											
+		GetPos() = m_feeler.GetPoint();
+		
+		// move it off the surface a little, so we are not IN it (which would be indeterminates as to which side)
+		GetPos() += normal * 0.1f;
+		DUMP_POSITION;
+	}
+}			
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::bounce_off_wall ( const Mth::Vector& normal )
+{
+	if (check_for_wallpush()) return;
+	
+	// Given the normal of the wall, then bounce off it, turning the skater away from the wall
+	
+	Mth::Vector	forward = GetPos() - GetOldPos();
+	Mth::Vector movement = forward;				   		// remember how far we moved
+	forward.Normalize();
+	
+	Mth::Vector up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b,"Skater_First_Forward_Collision_Height"));
+
+	float turn_angle;
+	float angle = rotate_away_from_wall(normal, turn_angle);
+
+	float min = Mth::DegToRad(GetPhysicsFloat(CRCD(0x1483fd01, "Wall_Bounce_Dont_Slow_Angle"))); 
+					 							
+	if (Mth::Abs(angle) > min)
+	{
+		float old_speed = GetVel().Length();
+		
+		// The maximum value of Abs(angle) would be PI/2 (90 degrees), so scale the velocity 
+		float x = Mth::Abs(angle) - min;
+		x /= (Mth::PI / 2.0f) - min;		// get in the range 0 .. 1
+		x = 1.0f - x; 						// invert, as we want to stop when angle is 90
+		
+		GetVel() *= x;
+		DUMP_VELOCITY;
+
+		// if (negative ^ flipped) then backwards flail, otherwise forward flail	
+
+		if (old_speed > GetPhysicsFloat(CRCD(0xbe0a58a0, "Wall_Bounce_Dont_Flail_Speed")))
+		{
+			#ifdef	__NOPT_ASSERT__
+			{
+				Mth::Vector	up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b,"Skater_First_Forward_Collision_Height"));
+				Mth::Vector start = up_offset+GetOldPos();
+				Mth::Vector end = up_offset+GetPos();
+				TrackingLine(1, start, end);	  // 1 = wall bounce flail
+			}
+			#endif
+			
+			if ((angle < 0.0f) ^ (GetFlag(FLIPPED)))														   
+			{
+				FLAGEXCEPTION(CRCD(0xb4101d70,"FlailLeft"));
+			}
+			else
+			{
+				FLAGEXCEPTION(CRCD(0x756a7535,"FlailRight"));
+			}
+			
+			// Player's terrain isn't set to the terrain in m_feeler, as this is a wall we're bouncing off of (or chain link fence or something)...
+			// Steve: we gots ta figure out how to do this...
+			// Perhaps have another terrain value sent to client skaters that tell them to play a bonk sound?
+			mp_sound_component->PlayBonkSound( old_speed / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_feeler.GetTerrain());
+		}
+	}
+	
+	// Bit of a patch here to move the skater away from the wall
+	// Not needed so much with new sideways collision checks but we keep it in for low ledges.
+	// Should perhaps standardize the height so collision checks for side and front but we'd probably still have problems.
+	
+	GetPos() = m_feeler.GetPoint() - up_offset;
+	GetPos() += normal * 6.0f;
+	DUMP_POSITION;
+	
+	// Now the majority of cases have been taken care of; we need to see if the skater is going to get stuck in a corner.
+
+	float old_speed = movement.Length();		 					// get how much we moved last time
+	Mth::Vector next_movement = GetVel();					// get new direction of velocity
+	forward = GetVel();
+	forward.Normalize();
+	next_movement = forward * old_speed;							// extend by same movment as last time
+	
+	m_col_start = GetPos() + up_offset;
+	m_col_start += up_offset;
+	
+	m_col_end = GetPos() + up_offset + next_movement
+		+ forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
+	
+	if (get_member_feeler_collision() && GetSkater()->IsLocalClient())
+	{
+		// Just rotating the skater will lead to another collision, so try just inverting the skater's velocity from it's original and halving it....
+		
+		// First reverse the rotation, and rotate 180 degrees
+		GetVel().RotateY(Mth::DegToRad(180.0f) - turn_angle);
+		GetMatrix().RotateYLocal(Mth::DegToRad(180.0f) - turn_angle);
+		ResetLerpingMatrix();
+		
+		GetVel() *= 0.5f; 
+		DUMP_VELOCITY;
+		
+		if (old_speed > GetPhysicsFloat(CRCD(0xbe0a58a0, "Wall_Bounce_Dont_Flail_Speed")))
+		{
+			FLAGEXCEPTION(CRCD(0xb4101d70, "FlailLeft"));
+		}
+	}
+}			
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::snap_to_ground (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	float up_dot = 0.0f;
+	
+	// Since we really don't want to loose contact with the ground while skitching, we use a much bigger snap up dist
+	// The problem will come when we get dragged down a slope.  The car will flatten out well ahead of us, so pushing us down through the slop
+	// (as we are a few feet behind it) and we will be so far under the ground that our normal snap up will not be able to dig us out of it,
+	// so we go in air, uberfrig, and get dragged to a random spot under the level.
+	// (This would not happen if we just skitch on flat ground)																								
+	
+	// Dan: snap_to_ground is never called while skitching
+	// if (GetFlag(SKITCHING))
+	// {
+		// m_col_start = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0x5c0d9610,"Physics_Ground_Snap_Up_SKITCHING"));	// much above feet
+	// }
+	// else
+	// {
+		m_col_start = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up"));	  		// bit above the feet
+	// }
+	
+	m_col_end = GetMatrix()[Y] * -200.0f;		    // WAY! below the feet, we check distance later
+
+	m_col_start += GetPos();
+	m_col_end += GetPos();
+		 
+	bool sticking = false;			
+	// get disatnce to ground and snap the skater to it, but only if the ground is skatable, otherwise we just go to "AIR"
+	if (get_member_feeler_collision())
+	{
+		Mth::Vector movement = GetPos() - m_feeler.GetPoint(); 
+		float drop_dist = movement.Length();
+		float drop_sign = Mth::DotProduct(GetMatrix()[Y], movement); // might be approx +/- 0.00001f
+		if (!m_col_flag_skatable)	
+		{
+			// if below the face (or very close to it), then push us away from it				
+			if (drop_sign < 0.001f)
+			{
+				// at point of contact, and move away from surface
+				GetPos() = m_feeler.GetPoint() + m_feeler.GetNormal();	
+				DUMP_POSITION;
+			}
+		}
+		else
+		{
+			sticking = true;
+			
+			// Note the two ways of calculating the angle between two faces
+			// the more "accurate" method simply takes angle between the two normals
+			// however, this fails to account for the direction the skater is travelling
+			// when in conjunction with the "snap" up.
+			// If the skater approaches a slope from the side, then he can snap up to the slope
+			// however, if the angle between the ground and the face to which we are snapping up to 
+			// is too great, then we transition to in-air
+			// and will drop through the slope
+			// the solution is to take the angle between the front vector rotated onto each face.
+			
+			// Firstly we check the angle between the two faces	
+			Mth::Vector normal = m_feeler.GetNormal();
+			
+			Mth::Vector	forward = GetMatrix()[Z];
+			float front_dot = Mth::DotProduct(forward,normal);
+
+			Mth::Vector	old_forward = forward;
+			forward.RotateToPlane(normal);
+			old_forward.RotateToPlane(m_current_normal);
+
+			// angle between front vectors, projected onto faces
+			up_dot = Mth::DotProduct(forward, old_forward);
+			
+			float stick_angle_cosine;
+			if (!control_pad.m_up.GetPressed())
+			{
+				stick_angle_cosine = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xef161c2a, "Ground_stick_angle"))));
+			}
+			else
+			{
+				stick_angle_cosine = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x4138283e, "Ground_stick_angle_forward"))));
+			}
+			
+			if (front_dot > 0.0f  && up_dot > 0.0f && up_dot < stick_angle_cosine)
+			{
+				sticking = false;
+			}
+			else
+			{
+				// only need to test if snap is downwards
+				if (drop_sign > 0.0f)
+				{
+					// angle between the old plane and the new plane is either the same, or withing the set limits
+					// (like, < 60 degrees, or so, see physics.q) now calculate the drop distance, based on the angle
+					
+#ifdef __PLAT_NGC__
+					float angle = acosf(Mth::Clamp(up_dot, -1.0f, 1.0f));
+#else
+					float angle = acosf(up_dot);
+#endif // __PLAT_NGC__
+
+					Mth::Vector	last_move = GetPos() - GetOldPos();
+					
+					float max_drop = last_move.Length() * tanf(angle);
+					
+					float min_drop = GetPhysicsFloat(CRCD(0x899ba3d0, "Physics_Ground_Snap_Down"));				
+                    // if (GetFlag(SKITCHING))
+					// {
+						// min_drop = GetPhysicsFloat(CRCD(0x20df7e33, "Physics_Ground_Snap_Down_Skitching"));				
+					// }
+
+					if (max_drop < min_drop)
+					{
+						max_drop = min_drop;
+					}
+					
+					if (drop_dist > max_drop)
+					{
+						sticking = false;
+					}
+				}
+				
+				if (sticking)
+				{		  
+					SetFlag(LAST_POLY_WAS_VERT, m_col_flag_vert);
+					new_normal(normal);
+				}		 
+			}																		  
+		}
+					
+		if (sticking)
+		{	
+			// if there is a collision, then snap to it
+			GetPos() = m_feeler.GetPoint();
+			DUMP_POSITION;
+			
+			if (GetState() == GROUND && movement.Length() > 2.0f)
+			{
+				// curbs are assumed to be between parallel surfaces, so check this...
+				if (up_dot > 0.99f)
+				{
+					SetFlag(SNAPPED_OVER_CURB, true);
+				}				
+			}
+
+			// will return trivially if terrain is already set to this type...
+			set_terrain(m_feeler.GetTerrain());
+			
+			adjust_normal();
+
+			// check to see if we have skated onto a movable object
+			check_movable_contact();
+
+			// still on ground, so store the latest ground collision data
+			// check first to see if we are about to change
+			if (m_last_ground_feeler.GetSector() != m_feeler.GetSector())
+			{
+				// changin sectors, so check the sector we came from and the one we are going to
+				mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF, m_last_ground_feeler);
+				if (mp_physics_control_component->HaveBeenReset()) return;
+				
+				mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, m_feeler);
+				if (mp_physics_control_component->HaveBeenReset()) return;
+			}
+
+			if (!mp_trick_component->GraffitiTrickStarted())
+			{
+				// clear the graffiti trick buffer if we're moving to a new world sector, but haven't started our trick yet...
+				mp_trick_component->SetGraffitiTrickStarted(false);
+			}
+			
+			set_last_ground_feeler(m_feeler);
+		}
+	}
+	
+	// skated off surface into the air
+	if (!sticking)
+	{
+		SetState(AIR);
+		GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
+		FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
+		
+		maybe_straight_up();
+		
+		if (GetFlag(VERT_AIR))
+		{
+			SetFlagTrue(CAN_BREAK_VERT);
+
+			// we want to break vert, but don't want to go into spine physics for a frame
+			// so don't do this check if we are trying to break the spine
+			// (we can still break vert or spine on the next frame, when in the air)
+ 			if (!BREAK_SPINE_BUTTONS)
+			{
+				maybe_break_vert();	
+			}
+			
+			// if we did not break vert now
+			// then only allow us to break vert later if we've been tapping the "up" button
+			// this is indicated by us having RELEASED or Pressed in teh last few ticks
+			if (static_cast< int >(control_pad.m_up.GetReleasedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time"))
+				&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time")))			
+			{
+				// "UP" was not pressed any time recently, so don't let us break late
+				SetFlagFalse(CAN_BREAK_VERT);	
+			}
+		}
+		else if (BREAK_SPINE_BUTTONS)
+		{
+			SAcidDropData acid_drop_data;
+			if (maybe_acid_drop(true, GetPos(), GetOldPos(), GetVel(), acid_drop_data))
+			{
+				enter_acid_drop(acid_drop_data);
+			}
+		}
+		
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return;
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::new_normal ( Mth::Vector normal )
+{
+	if (Ed::CParkEditor::Instance()->UsingCustomPark())
+	{
+		// check if this new normal will make us lean into a wall
+		if (normal[Y] > 0.0f)
+		{
+			CFeeler	feeler(GetPos(), GetPos() + 72.0f * normal);
+			if (feeler.GetCollision())
+			{
+				normal.Set(0.0f,1.0f,0.0f);
+			}
+		}
+	}
+	
+	if (normal != m_current_normal)
+	{
+		m_current_normal = normal;	  										// remember this, for detecting if it changes
+		m_last_display_normal = m_display_normal;				// remember start position for lerping	
+		m_normal_lerp = 1.0f;												// set lerp counter
+		
+		GetMatrix()[Y] = normal;
+		GetMatrix().OrthoNormalizeAbout(Y);									// set regular normal immediately
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::adjust_normal (   )
+{
+	///////////////////////////////////////////////////////////////////////////////
+	///////////////////////////////////////////////////////////////////////////////
+	//
+	// As the skater moves over the ground, he will go from being in contact
+	// with one polygon, to another.
+	// The "normal" of a polygon is the vector perpendicular to the surface of
+	// the polygon, and the skater is normally aligned so that the "up" vector
+	// of the skater (the Y component of the orientation matrix) is the same
+	// as the normal or the polgon he is in contact with.
+	//
+	// However, as we go from one polygon to another (in a quarterpipe, for example)
+	// the normal will change abruptly, and this looks very jerky
+	// So, we try to smooth this out by remembering the old normal, and interpolating 
+	// towards the current display normal
+	// this is done at a fixed speed, controled by the script value "Normal_Lerp_Speed" 
+	// whihc is defined in phsyics.q
+	//
+	// m_normal_lerp represents how far off the current face normal we are, it
+	// will vary from 1.0 (still at the last display normal) to 0.0 (at current normal)
+	// the intermediate normal is stored in m_display_normal
+	// and is also copied directly into m_lerping_display_matrix, to rotate the skater 
+
+	// if m_normal_lerp is 0.0, then we don't need to do anything, as we should already be there
+	if (m_normal_lerp != 0.0f)	   		// if lerping
+	{			
+		// If the last display normal is the same as the current normal, then we can't interpolate between them
+		if (m_last_display_normal == m_current_normal)
+		{
+			m_normal_lerp = 0.0f;
+		}
+		else
+		{
+			// adjust lerp at constant speed from 1.0 to 0.0, accounting for framerate
+			m_normal_lerp -= GetPhysicsFloat(CRCD(0xd8120182, "Normal_Lerp_Speed")) * m_frame_length * 60.0f;
+	
+			// if gone all the way, then clear lerping values and set m_display_normal to be the current face normal
+			if (m_normal_lerp <= 0.0f)
+			{
+				m_normal_lerp = 0.0f;
+				m_display_normal = m_current_normal;
+			}
+			else
+			{
+				// Still between old and current normal, so calculate intermediate normal
+				m_display_normal = Mth::Lerp(m_current_normal, m_last_display_normal, m_normal_lerp);											
+				m_display_normal.Normalize();
+			}
+		}
+	}
+	
+	// Now update the orientation matrix.
+	// We need our up (Y) vector to be this vector
+	// if it changes, rotate the X and Z vectors to match
+	if (m_lerping_display_matrix[Y] != m_display_normal)
+	{
+		// lerp the y axis
+		m_lerping_display_matrix[Y] = m_display_normal;
+		m_lerping_display_matrix.OrthoNormalizeAbout(Y);	
+		m_lerping_display_matrix[X] = GetMatrix()[X];
+		m_lerping_display_matrix[Z] = GetMatrix()[Z];		
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::check_movable_contact (   )
+{
+	// given m_feeler is our current contact point with the ground, then check if our mp_movable_contact_component->GetContact() information needs updating				
+	
+	if (GetFlag(SKITCHING)) return;
+	
+	if (mp_movable_contact_component->CheckForMovableContact(m_feeler))
+	{
+		GetVel() -= mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+		DUMP_VELOCITY;
+	}
+}				
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::maybe_straight_up (   )
+{			
+	// Skater has just left gound, either jumped, or skated off it
+	// so we need to check if the ground we left was flagged as VERT and if it is,
+	// then we need to set our skater's velocity and orientation so they go straight up
+	
+	if (GetFlag(LAST_POLY_WAS_VERT))	
+	{
+		TrackingLine(3, GetOldPos(), GetPos());	  // 3 = going vert
+		
+		// Get the normal to the plane we jumped from
+		Mth::Vector	up_plane_normal = m_current_normal;
+
+		m_vert_normal = m_current_normal;
+		m_vert_pos = GetOldPos();
+		
+		// move vert pos down one inch to better track subtle changes in edge height
+		m_vert_pos[Y] -= 1.0f;
+											
+		// clear any Y component to this plane, makes it vertical
+		up_plane_normal[Y] = 0.0f;
+		
+		if (up_plane_normal.Length() > 0.001f)
+		{
+			// and re-normalize to get a unit normal to the vertical plane.
+			up_plane_normal.Normalize();
+			
+			GetVel().RotateToPlane(up_plane_normal);
+			DUMP_VELOCITY;
+	
+			new_normal(up_plane_normal);
+			
+			// Fall line is used for auto turn
+			m_fall_line = GetMatrix()[Z];
+			m_fall_line[Y] = -m_fall_line[Y];
+				
+			// offset the jumper away from the plane by an inch
+			GetPos() += up_plane_normal * GetPhysicsFloat(CRCD(0x78384871, "Physics_Vert_Push_Out"));
+			DUMP_POSITION;
+			
+			SetFlagTrue(VERT_AIR);
+			SetFlagTrue(TRACKING_VERT);
+			SetFlagTrue(AUTOTURN);
+			m_vert_upstep = 6.0f;			
+		}
+		else
+		{
+			SetFlagFalse(VERT_AIR);
+		}
+	}
+	else
+	{
+		SetFlagFalse(VERT_AIR);
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::maybe_break_vert (   )
+{
+	// We "Break Vert" when we are pressing forward at the start of transitioning from the ground into a "vert" state
+	// however, we need to defer the test until there is no ground in that "forward" direction (which will be directly beneath the skater's feet)
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	if (
+		(	
+			control_pad.m_up.GetPressed() 
+			&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x9d2f8cc8, "Skater_vert_push_time"))
+			&& !control_pad.m_left.GetPressed()		// must be JUST up, to avoid accidents when turning
+			&& !control_pad.m_right.GetPressed()
+			&& !control_pad.m_square.GetPressed()	// and don't break if trying to do a trick involving up
+			&& !control_pad.m_circle.GetPressed()
+		)
+		||
+		(
+			BREAK_SPINE_BUTTONS
+		)
+	)
+	{
+		// aha... up is pressed, so we want to break the air poly
+
+		if (!BREAK_SPINE_BUTTONS)
+		{
+			// normal breaking the air polygon, we just fly forward
+			
+			float speed = GetVel().Length() * GetPhysicsFloat(CRCD(0x13f33b41, "physics_break_air_speed_scale"));
+			
+			GetVel()[X] += -m_display_normal[X] * speed;
+			GetVel()[Z] += -m_display_normal[Z] * speed;
+			GetVel()[Y] *= GetPhysicsFloat(CRCD(0x848c5cd6, "physics_break_air_up_scale"));
+			DUMP_VELOCITY;
+			
+			// Now, since we broke the vert poly, the way it was set up, the skater will have alrready been snapped to vertical position
+			// so we need to rotate him forwads 45 degrees to componsate
+			GetMatrix().RotateXLocal(Mth::DegToRad(GetPhysicsFloat(CRCD(0xd757f4bb, "Skater_Break_Vert_forward_tilt"))));				
+
+			SetFlagFalse(VERT_AIR);	   	// just regular air, if we broke the air poly
+			SetFlagFalse(TRACKING_VERT);	// and certainly not tracking the vert
+			SetFlagFalse(CAN_BREAK_VERT);	// and as we broke vert, we don't want to do it again
+			SetFlagFalse(AIR_ACID_DROP_DISALLOWED);	// allow acid drops once once again
+
+			// and we want to be going in the direction of our velocity, so set front x and Z, but leave Y
+			Mth::Vector vel_normal = GetVel();
+			vel_normal.Normalize();
+			
+			GetMatrix()[Z][X] = vel_normal[X];
+			GetMatrix()[Z][Z] = vel_normal[Z];
+			GetMatrix()[Z].Normalize();		
+			GetMatrix().OrthoNormalizeAbout(Z); 
+		}
+		else
+		{
+			if (!maybe_spine_transfer())
+			{
+				// cannot find a transfer target, so just break the air polygon
+				GetVel()[X] += -m_display_normal[X] * 24.0f;
+				GetVel()[Z] += -m_display_normal[Z] * 24.0f;
+				DUMP_VELOCITY;
+				GetMatrix().RotateXLocal(Mth::DegToRad(GetPhysicsFloat(CRCD(0xd757f4bb, "Skater_Break_Vert_forward_tilt"))));				
+	
+				SetFlagFalse(VERT_AIR);	   	// just regular air, if we broke the air poly
+				SetFlagFalse(TRACKING_VERT);	// and certainly not tracking the vert
+				SetFlagFalse(CAN_BREAK_VERT);	// and as we broke vert, we don't want to do it again
+				SetFlagTrue(IN_RECOVERY);		// tell him to just upright himself
+				
+				// and we want to be going in the direction of our velocity, so set front X and Z, but leave Y
+				Mth::Vector vel_normal = GetVel();
+				vel_normal.Normalize();
+				
+				GetMatrix()[Z][X] = vel_normal[X];
+				GetMatrix()[Z][Z] = vel_normal[Z];
+				GetMatrix()[Z].Normalize();		
+				GetMatrix().OrthoNormalizeAbout(Z); 
+			
+				return;
+			}
+		}
+		
+		ResetLerpingMatrix();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+                    
+bool CSkaterCorePhysicsComponent::maybe_spine_transfer (   )
+{
+	// Break spin button is pressed, so try to break the spine
+			
+	// The line to check along is the skater's forward directinal vector, rotated onto the XZ plane
+	// if you go straight up the wall, then this will be the same as the normal of the wall (in XY) as we previously calculated
+	// however we also want to handle the cases where you approach the QP at an angle
+	
+	// Need to take the forward vector (Z) and rotate it "forward" 90 degrees
+	// Rotate about an axis perpendicular to both the horizontal part of m_matrix[Y] and also the world up (0,1,0)
+	
+	Mth::Vector skater_up = GetMatrix()[Y];	// skater_up is expected to be horizontal here, as we are "vert"
+	skater_up[Y] = 0.0f;
+	skater_up.Normalize();
+	
+	// get a vector perpendicular to the plane containing m_matrix[Z] and the world up 	
+	#if 0 // old code - crossing by axis alined	vector bugs me
+	// Mth::Vector world_up(0.0f, 1.0f, 0.0f);
+	// Mth::Vector side = Mth::CrossProduct(skater_up, world_up);
+	#else
+	Mth::Vector side(-skater_up[Z], 0.0f, skater_up[X], 0.0f);
+	#endif
+	
+	// assuming we have not slowed down much, then our velocity should roughly be in the direction we took off from 
+	Mth::Vector forward = -GetVel();
+	forward.Normalize();
+				
+	Mth::Vector wall_out = forward; 							// forward facing vector
+	wall_out.Rotate(side, Mth::PI / 2.0f);					// rotate fowrad 90 degrees
+	
+	float speed;
+	float dist = 12.0f;
+	float time = 1.0f;
+	bool hip_transfer = false;							  
+			  
+	CFeeler feeler;
+	
+	// Here the "wall" is what we are currently skating on, anything with "wall" in the name refers to that
+
+	Mth::Vector target;
+	Mth::Vector target_normal;
+	bool target_found = false;			
+
+	// First find a point beneath our current position
+	// Nice long line, higher than we can posibly jump
+	feeler.m_start = GetPos() + wall_out * 0.5f;
+	feeler.m_end = GetPos() + wall_out * 0.5f;
+	feeler.m_end[Y] -= 4000.0f;
+	
+	// ignore everything that is NOT vert
+	// feeler.SetIgnore(0, mFD_VERT);
+	
+	Mth::Vector	wall_pos;
+	if (feeler.GetCollision())
+	{
+		wall_pos = feeler.GetPoint();
+
+		Mth::Vector start_normal = feeler.GetNormal();
+		start_normal[Y] = 0.0f;
+		start_normal.Normalize();
+		
+		target_found = look_for_transfer_target(-wall_out, start_normal, hip_transfer, target, target_normal);
+			
+		if (!target_found)
+		{
+			Mth::Vector left_along_vert(-start_normal[Z], 0.0f, start_normal[X]);
+			
+			// no target was found in the forward direction, perhaps we should look slightly left or right; look in the horizontal direction which is
+			// halfway between the previous search direction and the plane of the vert
+			if (mp_input_component->GetControlPad().m_left.GetPressed() && !mp_input_component->GetControlPad().m_right.GetPressed())
+			{
+				Mth::Vector search_dir = left_along_vert + -wall_out;
+				search_dir.Normalize();
+				target_found = look_for_transfer_target(search_dir, start_normal, hip_transfer, target, target_normal);
+			}
+			else if (mp_input_component->GetControlPad().m_right.GetPressed() && !mp_input_component->GetControlPad().m_left.GetPressed())
+			{
+				Mth::Vector search_dir = -left_along_vert + -wall_out;
+				search_dir.Normalize();
+				target_found = look_for_transfer_target(search_dir, start_normal, hip_transfer, target, target_normal);
+			}
+		}
+	}
+	
+	if (!target_found) return false;
+	
+	Mth::Vector XZ_to_target = target - wall_pos;
+	XZ_to_target[Y] = 0.0f;
+	dist = XZ_to_target.Length();
+	
+	// We are only going to allow this later if the target point is the same level
+	// as the takeoff point, and we have a clear line
+	// so set it to this now, so we calculate the time correctly
+	target[Y] = GetPos()[Y];
+
+	// if the two faces are not really perpendicular or if the spine is wider than
+	// then we determine that we are on a "hip" and we just want to go across it without drifting left or right
+	// so we want to project all our velocity straight up
+
+	Mth::Vector	horizontal_target_normal = target_normal;
+	horizontal_target_normal[Y] = 0.0f;
+	horizontal_target_normal.Normalize();
+	
+	Mth::Vector cache_vel = GetVel();
+	
+	float face_dot = Mth::Abs(Mth::DotProduct(skater_up, horizontal_target_normal));															
+	if (face_dot < 0.9f)
+	{
+		GetVel()[Y] = GetVel().Length();
+		GetVel()[X] = 0.0f;
+		GetVel()[Z] = 0.0f;
+		DUMP_VELOCITY;
+	}
+	else
+	{
+		// if spine more than two feet wide, then also don't allow drift
+		if (dist > FEET(2.0f))
+		{
+			GetVel()[Y] = GetVel().Length();
+			GetVel()[X] = 0.0f;
+			GetVel()[Z] = 0.0f;
+			DUMP_VELOCITY;
+		}
+	}
+	
+	// one inch out, to ensure miss the lip
+	dist += 1.0f;
+
+	#if 0 // old transfer code
+	// get angle to rotate about, being the vector perpendicular to the world up vector and the difference between the two face normals
+	// (generally for a spine these normals will be opposite, however they might be up to 90 degrees or more off when doing a hip)
+	
+	Mth::Vector normal_diff = target_normal - skater_up;
+	normal_diff[Y] = 0.0f;
+	normal_diff.Normalize();
+	
+	m_spine_rotate_axis[X] = -normal_diff[Z];
+	m_spine_rotate_axis[Y] = 0.0f;
+	m_spine_rotate_axis[Z] = normal_diff[X];
+	m_spine_rotate_axis[W] = 0.0f;;
+	#endif
+	
+	// for gravity calculations, temporarily pretend we are doing spine physics, so g is constant
+	SetFlagTrue(SPINE_PHYSICS);
+	time = calculate_time_to_reach_height(target[Y], GetPos()[Y], GetVel()[Y]);
+	SetFlagFalse(SPINE_PHYSICS);
+
+	// subtract some frames of time, to ensure we make it
+	// time -= m_frame_length * 2.0f;
+	
+	if (time < 0.1f)
+	{
+		time = 0.1f;
+	}
+	
+	speed = dist / time;
+	
+	// if spine more than two foot wide, then make sure that we have enough speed to get over it
+	// otherwise, just do a little pop over, and allow them to recover						  
+	if (dist > 24.0f && speed * speed > GetVel().LengthSqr())
+	{
+		return false;
+	}
+
+	// we have found a target point, either by looking directly in front or by doing the drop-down method
+	// but we don't want to go for it until there is a clear line from our current position to the target
+	
+	Mth::Vector	target_XZ = target;
+	target_XZ[Y] = GetPos()[Y];
+	
+	feeler.m_start = GetPos();
+	feeler.m_end = target_XZ;
+	if (feeler.GetCollision())
+	{
+		// don't do anything.  We have a valid transfer but we can wait until we get high enough before we try for it
+		return true;
+	}
+		
+	// setup the transfer's matrix slerp
+	
+	Mth::Vector land_facing;
+	if (!hip_transfer)
+	{
+		land_facing = target - GetPos();
+		land_facing[Y] = -(land_facing[X] * target_normal[X] + land_facing[Z] * target_normal[Z]) / target_normal[Y];
+		land_facing.Normalize();
+	}
+	else
+	{
+		Mth::Vector offset = target - GetPos();
+		offset.Normalize();
+		float dot = Mth::DotProduct(offset, horizontal_target_normal);
+		if (dot < 0.0f)
+		{
+			land_facing.Set(0.0f, 1.0f, 0.0f);
+		}
+		else
+		{
+			land_facing.Set(0.0f, -1.0f, 0.0f);
+		}
+	}
+
+	Mth::Matrix transfer_slerp_start = GetMatrix();
+
+	// calculate the facing we want when we land; retain our horizontal direction and choose a vertical component which puts us parallel so the target
+	// poly's plane
+
+	// calculate goal matrix
+	Mth::Matrix transfer_slerp_goal;
+	transfer_slerp_goal[Z] = land_facing;
+	transfer_slerp_goal[Z].ProjectToPlane(target_normal);
+	transfer_slerp_goal[Z].Normalize();
+	transfer_slerp_goal[Y] = target_normal;
+	transfer_slerp_goal[X] = Mth::CrossProduct(transfer_slerp_goal[Y], transfer_slerp_goal[Z]);
+	transfer_slerp_goal[W].Set();
+	
+	// store the goal facing for use in adjusting the velocity at land time
+	m_transfer_goal_facing = transfer_slerp_goal[Z];
+
+	// if the skater is entering the spine transfer with an odd facing due to rotation, we want to preserve that angle in the slerp's goal matrix
+
+	// calculate the deviation between the skater's velocity and facing
+	float angle = Mth::GetAngleAbout(GetMatrix()[Z], cache_vel, GetMatrix()[Y]);
+	
+	// be a bit forgiving for hip transfers, as you often have to hit left/right to trigger them, which causes rotation
+	if (Mth::Abs(angle) < Mth::DegToRad(30.0f))
+	{
+		angle = 0.0f;
+	}
+
+	// rotate goal facing to reflect the deviation in the initial facing
+	transfer_slerp_goal.RotateYLocal(-angle);
+
+	// setup the slerp state
+	m_transfer_slerper.setMatrices(&transfer_slerp_start, &transfer_slerp_goal);
+	m_transfer_slerp_timer = 0.0f;
+	m_transfer_slerp_duration = Mth::ClampMin(time, 0.9f); // clamp the time to stop super fast rotations
+	m_transfer_slerp_previous_matrix = transfer_slerp_start;
+	
+	// insure that the slerp takes us over the top, and doesn't invert us
+	Mth::Matrix slerp_test;
+	m_transfer_slerper.getMatrix(&slerp_test, 0.5f);
+	if (slerp_test[Y][Y] < 0.0f)
+	{
+		m_transfer_slerper.invertDirection();
+	}
+	
+	// remember the height we are aiming for, so when we come down through this height
+	// then we remove the non vert velocity (or make it very small....)
+	m_transfer_target_height = target[Y];
+	
+	// set velocity over the wall fast enough to land on the target point																	 
+	mp_state_component->m_spine_vel = (target - GetPos()) / time;		// velocity from start to target
+	mp_state_component->m_spine_vel[Y] = 0.0f;															// but ignore Y, as gravity handles that...
+	
+	// tell the code we are doing spine physics, so we lean quicker 
+	if (!hip_transfer)
+	{
+		GetObject()->SpawnAndRunScript(CRCD(0xa5179e9e, "SkaterAwardTransfer"));	// award a trick (might want to do it as an exception later)
+	}
+	else
+	{
+		GetObject()->SpawnAndRunScript(CRCD(0x283bb5d6, "SkaterAwardHipTransfer"));	// award a trick (might want to do it as an exception later)
+	}
+	
+	// no late jumps during a transfer
+	GetObject()->RemoveEventHandler(CRCD(0x8ffefb28, "Ollied"));
+	
+	SetFlagTrue(SPINE_PHYSICS);	// flag in spin physics, to do the lean forward, and also allow downcoming lip tricks
+	SetFlagFalse(IN_ACID_DROP);
+	SetFlagFalse(TRACKING_VERT);	// we are still vert, but not tracking the vert
+	SetFlagFalse(CAN_BREAK_VERT);	// and as we "broke" vert, we don't want to do it again
+	
+	return true;
+ }
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+                    
+bool CSkaterCorePhysicsComponent::look_for_transfer_target ( const Mth::Vector& search_dir, const Mth::Vector& start_normal, bool& hip_transfer, Mth::Vector& target, Mth::Vector& target_normal )
+{
+	// take a bunch of steps forward until we find one		
+	// This is not very good, as we have to do 80 collision checks....
+	// we really need to optimize our collision detection to be able to select a set of "nearby" object
+	// or to select a set that intersects a sphere, or a plane
+	// (here, we could just get the set that intersects the plane)
+	// this could be statically cached by the colision code, and have one set
+	// or perhaps more flexibly, each "feeler" could have a set of objects
+	// that it deals with (defaulting to the set of all objects)
+	
+	CFeeler feeler;
+	
+	// setup collision cache
+	Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
+	Mth::CBBox bbox;
+	Mth::Vector p;
+	p = GetPos() + search_dir * 10.0f;
+	bbox.AddPoint(p);
+	p[Y] -= 4000.0f;
+	bbox.AddPoint(p);
+	p = GetPos() + search_dir * 500.0f;
+	bbox.AddPoint(p);
+	p[Y] -= 4000.0f;
+	bbox.AddPoint(p);
+	p_coll_cache->Update(bbox);
+	feeler.SetCache(p_coll_cache);
+	
+	for (float step = 10.0f; step < 500.0f; step += 6.0f)		
+	{
+		// First find a VERT point a bit in front of us
+		// can be some distance below us 
+		// allowing us to transfer from high to low pools
+		// (and low to high, proving you can jump up from the low point to the high point first)
+		feeler.m_start = GetPos() + search_dir * step;		// start at current height
+		feeler.m_end = feeler.m_start;
+		feeler.m_end[Y] -= 4000.0f;									// long way below
+		
+		if (feeler.GetCollision() && (feeler.GetFlags() & mFD_VERT) && is_vert_for_transfers(feeler.GetNormal()))
+		{
+			Mth::Vector horizontal_normal = feeler.GetNormal();
+			horizontal_normal[Y] = 0.0f;
+			horizontal_normal.Normalize();
+			float dot = Mth::DotProduct(start_normal, horizontal_normal);
+			if (dot <= 0.95f)
+			{
+				target = feeler.GetPoint();
+				target_normal = feeler.GetNormal();
+				
+				hip_transfer = dot > -0.866f;
+				
+				// feeler.m_end[Y] += 3960.0f;
+				// feeler.DebugLine(255, 100, 100, 0);
+				
+				Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+				
+				return true;
+			}
+			else
+			{
+				// feeler.m_end[Y] += 3960.0f;
+				// feeler.DebugLine(100, 255, 100, 0);
+			}
+		}
+		else
+		{
+			// feeler.m_end[Y] += 3960.0f;
+			// feeler.DebugLine(100, 100, 255, 0);
+		}
+	}
+	
+	Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+                    
+bool CSkaterCorePhysicsComponent::maybe_acid_drop ( bool skated_off_edge, const Mth::Vector &pos, const Mth::Vector& old_pos, Mth::Vector& vel, SAcidDropData& acid_drop_data )
+{
+	// horizontal direction in which a drop would occur
+	Mth::Vector drop_direction;
+	if (mp_physics_control_component->IsSkating())
+	{
+		drop_direction = vel;
+		drop_direction[Y] = 0.0f;
+		float length = drop_direction.Length();
+		if (length < 0.01f) return false;
+		drop_direction *= 1.0f / length;
+	}
+	else
+	{
+		drop_direction = mp_walk_component->m_facing;
+	}
+	
+	bool target_found = false;
+	Mth::Vector target;
+	
+	// in order not to miss vert polys with a thin horizontal projection, we check for them starting at this frame's initial position
+	Mth::Vector search_pos = old_pos;
+	search_pos[Y] = Mth::Max(pos[Y], old_pos[Y]);
+	
+	float scan_distance = 500.0f;
+	float scan_height = 0.0f;
+	if (mp_physics_control_component->IsWalking())
+	{
+		if (mp_walk_component->m_state == CWalkComponent::WALKING_GROUND)
+		{
+			// and use a reduced scan distance
+			scan_distance = Script::GetFloat(CRCD(0xe50a9d56, "Physics_Acid_Drop_Walking_On_Ground_Search_Distance"));
+
+			// and look for vert polys above us
+			scan_height = 200.0f;
+		}
+		else
+		{
+			// look slightly behind us for acid drops (we may be facing down a vert we're standing on)
+			search_pos -= 12.0f * drop_direction;
+		}
+	}
+	
+	CFeeler feeler;
+	
+	// setup collision cache
+	Nx::CCollCache* p_coll_cache = Nx::CCollCacheManager::sCreateCollCache();
+	Mth::CBBox bbox;
+	Mth::Vector p;
+	p = search_pos;
+	p[Y] += scan_height;
+	bbox.AddPoint(p);
+	p[Y] -= 4200.0f;
+	bbox.AddPoint(p);
+	p = search_pos + drop_direction * scan_distance;
+	p[Y] += scan_height;
+	bbox.AddPoint(p);
+	p[Y] -= 4200.0f;
+	bbox.AddPoint(p);
+	p_coll_cache->Update(bbox);
+	feeler.SetCache(p_coll_cache);
+	
+	Mth::Vector target_normal;
+	Mth::Vector horizontal_target_normal;
+	float distance;
+	for (distance = 0.01f; distance < scan_distance; distance += 3.0f)
+	{
+		// look for a vert poly below us
+		feeler.m_start = search_pos + distance * drop_direction;
+		feeler.m_end = feeler.m_start;
+		feeler.m_start[Y] += scan_height;
+		feeler.m_end[Y] -= 4000.0f;
+		if (feeler.GetCollision() && (feeler.GetFlags() & mFD_VERT) && is_vert_for_transfers(feeler.GetNormal()))
+		{
+			// the horizontal projection of the vert's normal just correspond somewhat to our direction			 
+			target_normal = horizontal_target_normal = feeler.GetNormal();
+			horizontal_target_normal[Y] = 0.0f;
+			horizontal_target_normal.Normalize();
+			
+			if (mp_physics_control_component->IsWalking() && mp_walk_component->m_state == CWalkComponent::WALKING_AIR)
+			{
+				// special acceptance rules for walking in-air acid drops
+				target_found = Mth::DotProduct(drop_direction, horizontal_target_normal) <= -0.25f
+					|| Mth::DotProduct(drop_direction, horizontal_target_normal) >= 0.05f;
+			}
+			else
+			{
+				target_found = Mth::DotProduct(drop_direction, horizontal_target_normal) >= 0.05f;
+			}
+			
+			if (target_found)
+			{
+				target = feeler.GetPoint();
+				// feeler.m_end[Y] += 3960.0f;
+				// feeler.DebugLine(255, 100, 100, 0);
+				break;
+			}
+			else
+			{
+				// feeler.m_end[Y] += 3960.0f;
+				// feeler.DebugLine(100, 255, 100, 0);
+			}
+		}
+		else
+		{
+			// feeler.m_end[Y] += 3960.0f;
+			// feeler.DebugLine(100, 100, 255, 0);
+		}
+		
+		// use a larger incrememt at larger distances, as we have several frames yet to find these polys
+		if (distance > 100.0f)
+		{
+			distance += 24.0f;
+		}
+	}
+	
+	if (!target_found)
+	{
+		// no valid acid drop target found
+		Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+		return false;
+	}
+	
+	float original_target_height = target[Y];
+	
+	// because our search began behind us, the horizontal offset to the target may not be forward
+	Mth::Vector horizontal_offset = target - pos;
+	horizontal_offset[Y] = 0.0f;
+	distance = horizontal_offset.Length();
+	if (Mth::DotProduct(horizontal_offset, drop_direction) < 0.0f)
+	{
+		distance = -distance;
+	}
+	drop_direction = horizontal_offset / distance;
+	
+	// stash a copy of velocity so we can pretend it has an adjusted value
+	Mth::Vector hold_vel = vel;
+	
+	if (mp_physics_control_component->IsWalking())
+	{
+		// because when walking they are necessarily in the same direction, project our horizontal velocity in the drop direction
+		vel.ProjectToNormal(drop_direction);
+		vel[Y] = hold_vel[Y];
+	}
+	
+	// calculate our effective horizontal velocity
+	float initial_horiz_speed = sqrtf(vel[X] * vel[X] + vel[Z] * vel[Z]);
+	
+	if (mp_physics_control_component->IsWalking())
+	{
+		// boost our effective horizontal speed up to maximum run speed
+		float horizontal_boost = mp_walk_component->get_run_speed();
+		if (initial_horiz_speed < horizontal_boost)
+		{
+			vel[X] = horizontal_boost * GetWalkComponentFromObject(GetObject())->m_facing[X];
+			vel[Z] = horizontal_boost * GetWalkComponentFromObject(GetObject())->m_facing[Z];
+			initial_horiz_speed = horizontal_boost;
+		}
+	}
+	
+	// give a slight upward pop
+	if (skated_off_edge)
+	{
+		vel[Y] = Mth::Max(vel[Y], GetPhysicsFloat(CRCD(0x95a79c32, "Physics_Acid_Drop_Pop_Speed")));
+	}
+	
+	// but limit upward speed to something reasonable
+	if (!mp_physics_control_component->IsWalking() || mp_walk_component->m_state != CWalkComponent::WALKING_GROUND)
+	{
+		vel[Y] = Mth::Min(vel[Y], 2.0f * GetPhysicsFloat(CRCD(0x95a79c32, "Physics_Acid_Drop_Pop_Speed")));
+	}
+	
+	// grab the acceleration we will have during our acid drop
+	SetFlagTrue(SPINE_PHYSICS);
+	float acceleration = get_air_gravity();
+	SetFlagFalse(SPINE_PHYSICS);
+	
+	// calculate what height we would have if we used our current horizontal velocity to reach the target position
+	float final_height;
+	if (distance > 0.0f && initial_horiz_speed > 0.0001f)
+	{
+		float time_to_target = distance / initial_horiz_speed;
+		final_height = pos[Y] + vel[Y] * time_to_target + 0.5f * acceleration * time_to_target * time_to_target;
+	}
+	else
+	{
+		// for backwards acid drops, just act as through we are directly over the target point
+		final_height = pos[Y];
+	}
+	
+	// if we need to jump up to the target
+	if (mp_physics_control_component->IsWalking() && vel[Y] > 0.0f && pos[Y] < target[Y])
+	{
+		// check to see if we'll ever reach that height with our effective upward velocity
+		float max_height = pos[Y] + vel[Y] * vel[Y] / (-2.0f * acceleration);
+		float time_to_target = Mth::Abs(distance) / initial_horiz_speed;
+		float time_to_max_height = vel[Y] / -acceleration;
+		if (time_to_target < time_to_max_height)
+		{
+			// effectively, this means that we're willing to reduce our horizontal boost in order allow more time to reach the required height
+			final_height = max_height;
+		}
+	}
+	
+	// if we can't reach the target with our current velocity, ditch the acid drop
+	if (final_height < target[Y])
+	{
+		Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+		vel = hold_vel;
+		return false;
+	}
+	
+	// calculate the air time before the acid drop would hit its true target; prevent acid drops from occuring moments before landing
+	SetFlagTrue(SPINE_PHYSICS);
+	float time_to_reach_target_height = calculate_time_to_reach_height(original_target_height, pos[Y], vel[Y]);
+	SetFlagFalse(SPINE_PHYSICS);
+	if (time_to_reach_target_height < Script::GetFloat(CRCD(0x32c20f7e, "Physics_Acid_Drop_Min_Air_Time")))
+	{
+		Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+		vel = hold_vel;
+		return false;
+	}
+	
+	// ensure that we have a clear shot to the target
+	
+	bool clear_path = false;
+    
+	// keep shifting our target point up until we can get a clear shot to it, or we get to an unreachable height
+	while (target[Y] < final_height)
+	{
+		feeler.m_start = pos;
+		
+		// check a path constructed from two concatenated lines, with the midpoint halfway along the acid drop trajectory; this is an attempt
+		// to allow most acid drop which might be disallowed by a ledge which would block a straight line
+		
+		// calculate the time span required to fall to the target height
+		SetFlagTrue(SPINE_PHYSICS);
+		float half_time_to_reach_target_height = 0.5f * calculate_time_to_reach_height(target[Y], pos[Y], vel[Y]);
+		SetFlagFalse(SPINE_PHYSICS);
+		
+		// calculate the spine velocity which would be used for this target
+		float required_speed = 0.5f * distance / half_time_to_reach_target_height;
+		
+		// calculate the height we will be at halfway through the acid drop
+		float height_halfway = pos[Y] + vel[Y] * half_time_to_reach_target_height
+			+ 0.5f * acceleration * Mth::Sqr(half_time_to_reach_target_height);
+		
+		// calculate the point halfway through the acid drop
+		Mth::Vector halfway_point = pos;
+		halfway_point[Y] = height_halfway;
+		halfway_point += required_speed * half_time_to_reach_target_height * drop_direction;
+		
+		// check for collisions alone the two-line path
+		feeler.m_end = halfway_point;
+		if (!feeler.GetCollision())
+		{
+			// feeler.DebugLine(255, 255, 0);
+			feeler.m_start = feeler.m_end;
+			feeler.m_end = target;
+			feeler.m_end[Y] += 1.0f;
+			if (!feeler.GetCollision())
+			{
+				// feeler.DebugLine(255, 255, 0);
+				clear_path = true;
+				break;
+			}
+			else
+			{
+				// feeler.DebugLine(0, 0, 0, 0);
+			}
+		}
+		
+		// feeler.DebugLine(0, 0, 0, 0);
+		
+		// try a higher target point
+		target[Y] += 24.0f;
+	}
+	
+	// no clear path was found along the acid drop
+	if (!clear_path)
+	{
+		Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+		vel = hold_vel;
+		return false;
+	}
+	DUMP_VELOCITY;
+	
+	Nx::CCollCacheManager::sDestroyCollCache(p_coll_cache);
+	
+	acid_drop_data.target_pos = target;
+	acid_drop_data.target_normal = target_normal;
+	acid_drop_data.true_target_height = original_target_height;
+	return true;
+}
+
+void CSkaterCorePhysicsComponent::enter_acid_drop ( const SAcidDropData& acid_drop_data )
+{
+	const Mth::Vector& target_pos = acid_drop_data.target_pos;
+	const Mth::Vector& target_normal = acid_drop_data.target_normal;
+	const float& true_target_height = acid_drop_data.true_target_height;
+	
+	// setup the skater state for the acid drop
+	
+	Mth::Vector horizontal_offset = target_pos - GetPos();
+	horizontal_offset[Y] = 0.0f;
+	float distance = horizontal_offset.Length();
+	if (Mth::DotProduct(horizontal_offset, GetVel()) < 0.0f)
+	{
+		distance = -distance;
+	}
+	Mth::Vector drop_direction = horizontal_offset / distance;
+	
+	// calculate the spine speed required to reach the target given our current vertical velocity
+	SetFlagTrue(SPINE_PHYSICS);
+	float time_to_reach_target_height = calculate_time_to_reach_height(target_pos[Y], GetPos()[Y], GetVel()[Y]);
+	float required_speed = distance / time_to_reach_target_height;
+	SetFlagFalse(SPINE_PHYSICS);
+	mp_state_component->m_spine_vel.Set(required_speed * drop_direction[X], 0.0f, required_speed * drop_direction[Z]);
+	acid_hold = mp_state_component->m_spine_vel;
+	
+	// once we reach this height, the skater's horizontal velocity will be zeroed out
+	m_transfer_target_height = target_pos[Y];
+	
+	// Gfx::AddDebugStar(target, 24.0f, RED, 0);
+	
+	// enter the acid drop state
+	SetFlagTrue(SPINE_PHYSICS);
+	SetFlagTrue(VERT_AIR);
+	SetFlagTrue(IN_ACID_DROP);
+	SetFlagFalse(TRACKING_VERT);
+	SetFlagFalse(AUTOTURN);
+	
+	// zero our horizontal velocity
+	GetVel()[X] = 0.0f;
+	GetVel()[Z] = 0.0f;
+	DUMP_VELOCITY;
+	
+	// setup the acid drop's matrix slerp
+	
+	Mth::Matrix acid_drop_slerp_start = GetMatrix();
+	
+	// calculate the facing we want when we land; retain our horizontal direction and choose a vertical component which puts us parallel so the target
+	// poly's plane
+	Mth::Vector land_facing = drop_direction;
+	land_facing[Y] = -(land_facing[X] * target_normal[X] + land_facing[Z] * target_normal[Z]) / target_normal[Y];
+	land_facing.Normalize();
+	
+	// calculate goal matrix
+	Mth::Matrix acid_drop_slerp_goal;
+	acid_drop_slerp_goal[Z] = land_facing;
+	acid_drop_slerp_goal[Z].ProjectToPlane(target_normal);
+	acid_drop_slerp_goal[Z].Normalize();
+	acid_drop_slerp_goal[Y] = target_normal;
+	acid_drop_slerp_goal[X] = Mth::CrossProduct(acid_drop_slerp_goal[Y], acid_drop_slerp_goal[Z]);
+	acid_drop_slerp_goal[W].Set();
+	
+	// store the goal facing for use in adjusting the velocity at land time
+	m_transfer_goal_facing = acid_drop_slerp_goal[Z];
+	
+	// setup a good camera matrix for the acid drop (before applying any deviation preserving adjustments)
+	m_acid_drop_camera_matrix = acid_drop_slerp_goal;
+	if (m_acid_drop_camera_matrix[Z][Y] > 0.0f)
+	{
+		m_acid_drop_camera_matrix[Z] *= -1.0f;
+		m_acid_drop_camera_matrix[X] *= -1.0f;
+	}
+	
+	// if the skater is entering the acid drop with an odd facing due to rotation, we want to preserve that angle in the slerp's goal matrix
+	
+	// calculate the deviation between the skater's velocity and facing
+	Mth::Vector horizontal_facing = GetMatrix()[Z];
+	horizontal_facing[Y] = 0.0f;
+	float angle = Mth::GetAngleAbout(horizontal_facing, drop_direction, GetMatrix()[Y]);
+	
+	// rotate goal facing to reflect the deviation in the initial facing
+	acid_drop_slerp_goal.RotateYLocal(-angle);
+
+	// setup the slerp state
+	m_transfer_slerper.setMatrices(&acid_drop_slerp_start, &acid_drop_slerp_goal);
+	m_transfer_slerp_timer = 0.0f;
+	m_transfer_slerp_duration = time_to_reach_target_height;
+	m_transfer_slerp_previous_matrix = acid_drop_slerp_start;
+	
+	// trigger the appropriate script
+	Script::CStruct* p_params = new Script::CStruct;
+	p_params->AddFloat(CRCD(0xbb00fe40, "DropHeight"), GetPos()[Y] - true_target_height);
+	GetObject()->SpawnAndRunScript(CRCD(0xc7ed5fef, "SkaterAcidDropTriggered"), -1, false, false, p_params);
+	
+	// no late jumps during an acid drop
+	GetObject()->RemoveEventHandler(CRCD(0x8ffefb28, "Ollied"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+                    
+void CSkaterCorePhysicsComponent::handle_post_transfer_limit_overrides (   )
+{
+	// After a transfer, if we are above our standard speed limits, we ignore those limits for a short duration.  Over the duration the speed limits
+	// lerp from our starting speed down to the standard speed limits.  Only non-air time is counted towards the duration.
+	
+	// detect leaving an acid drop
+	if (m_began_frame_in_transfer && !GetFlag(SPINE_PHYSICS) && !GetFlag(IS_BAILING))
+	{
+		// only override limits if we start above the limits
+		float standard_max = GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat"));
+		float speed = GetVel().Length();
+		if (speed < 1.25f * standard_max) return;
+		
+		// setup state to ignore speed limits
+		m_transfer_overrides_factor = Mth::Min(speed / standard_max, GetPhysicsFloat(CRCD(0xc6b38be0, "Physics_Transfer_Speed_Limit_Override_Max")));
+		
+		// large values
+		m_override_max = 1e20f;
+		m_override_max_max = 1e20f;
+	}
+	else if (m_transfer_overrides_factor == 1.0f) return;
+	
+	// turn on speed limit overrides
+	m_override_limits_time = -1.0f;
+	
+	// count our timer down
+	m_transfer_overrides_factor -= m_frame_length * GetPhysicsFloat(CRCD(0xf9b006aa, "Physics_Transfer_Speed_Limit_Override_Drop_Rate"));
+	
+	// end the ignoring of speed limits if the duration is up
+	if (m_transfer_overrides_factor < 1.0f)
+	{
+		m_transfer_overrides_factor = 1.0f;
+		m_override_limits_time = 0.0f;
+		return;
+	}
+	
+	// grab the standard speed limit
+	float standard_max = GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat")); 
+	
+	// calculate the appropriate speed limit based on the time since the acid drop and the speed at the end of the acid drop
+	float time_based_appropriate_max = m_transfer_overrides_factor * standard_max;
+	
+	// calculate a speed limit based on the current speed; thus, if we break during the ignoring of speed limits, our speed limits will turn back on
+	float speed_based_appropriate_max = 1.1f * GetVel().Length();
+	
+	// take the lowest speed limit; never increase the speed limit
+	float appropriate_max;
+	if (GetState() != AIR)
+	{
+		appropriate_max = Mth::Min3(time_based_appropriate_max, speed_based_appropriate_max, m_override_max);
+	}
+	else
+	{
+		// in air, don't drop the limits when your current speed drops; otherwise you lose your overrides as the top of vert air
+		appropriate_max = Mth::Min(time_based_appropriate_max, m_override_max);
+	}
+	
+	// end the ignoring of speed limits if the duration is up
+	if (appropriate_max < standard_max)
+	{
+		m_transfer_overrides_factor = 1.0f;
+		m_override_limits_time = 0.0f;
+		return;
+	}
+	
+	// set the artificially high speed limit override
+	m_override_max = appropriate_max;
+	
+	// the max max speed limit will never fall below the standard max max speed limit
+	m_override_max_max = m_override_max / standard_max * GetSkater()->GetScriptedStat(CRCD(0x2eacddb3, "Skater_Max_Speed_Stat"));
+	
+	PERIODIC(10)
+	{
+		printf("Post-Transfer Speed Limit Overrides:  current / standard = %.2f\n", m_override_max / standard_max);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+                    
+float CSkaterCorePhysicsComponent::get_air_gravity (   )
+{
+	// given that we are in the air, figure out what gravity to use	
+	// based on if we are VERT or regular air and the cheat modes
+	// make sure that if you use this in calculations, then your flags do not change while you expect it to be the same
+	float gravity;
+
+	if (GetFlag(VERT_AIR) || GetFlag(SPINE_PHYSICS))   // Note, spine is treated same as vert
+	{							  
+		// gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetSkater()->GetScriptedStat(CRCD(0x441c38a0, "Physics_vert_hang_Stat"));
+		gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetPhysicsFloat(CRCD(0x441c38a0, "Physics_vert_hang_Stat"));
+	}
+	else
+	{
+		// gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetSkater()->GetScriptedStat(CRCD(0xc31ca696, "Physics_Air_hang_Stat"));
+		gravity = GetPhysicsFloat(CRCD(0xfaa40754, "Physics_Air_Gravity")) / GetPhysicsFloat(CRCD(0xc31ca696, "Physics_Air_hang_Stat"));
+	}
+	
+	if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0x9c8c6df1, "CHEAT_MOON")))
+	{
+		// Here, set the flag. It may seem redundant, but the above line is very likely
+		// to be hacked by gameshark. They probably won't notice this one, which will
+		// set the flags as if they had actually enabled the cheat -- which enables us
+		// to detect that it has been turned on more easily.
+		Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0x9c8c6df1, "CHEAT_MOON")));
+		gravity *= GetPhysicsFloat(CRCD(0xec128f0, "moon_gravity"));
+		g_CheatsEnabled = true;
+	}
+	 
+	return gravity;
+} 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				
+float CSkaterCorePhysicsComponent::calculate_time_to_reach_height ( float target_height, float pos_Y, float vel_Y )
+{
+	// s = ut - 1/2 * g * t^2			 (note, -g = a in the more traditional formula)
+	// solve this using the quadratic equation, gives us the formula below
+	// Note the sign of s is important.....
+	float distance = pos_Y - target_height; 
+	float velocity = vel_Y;
+	float acceleration = -get_air_gravity();
+	return (velocity + sqrtf(velocity * velocity + 2.0f * acceleration * distance)) / acceleration; 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_ground_rotation (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();	
+
+	float rot = 0.0f; 
+	float speed = GetVel().Length();						
+	
+	if (control_pad.m_left.GetPressed())
+	{
+		if (!control_pad.m_down.GetPressed())
+		{
+			rot = GetPhysicsFloat(CRCD(0x374e056b, "Physics_Ground_Rotation"));	
+		}
+		else
+		{
+			rot = GetPhysicsFloat(CRCD(0x7933a8ef, "Physics_Ground_Sharp_Rotation")); 
+			if (speed < 10.0f)
+			{
+				// If not moving, then we want more control over turning, so ramp up the turing speed over a second
+				int pressed_time = control_pad.m_left.GetPressedTime();
+				if (pressed_time < STOPPED_TURN_RAMP_TIME)
+				{
+					rot = rot * pressed_time / STOPPED_TURN_RAMP_TIME;
+				}
+			}
+		}
+	}
+	else if (control_pad.m_right.GetPressed())
+	{
+		if (!control_pad.m_down.GetPressed())
+		{
+			rot = -GetPhysicsFloat(CRCD(0x374e056b, "Physics_Ground_Rotation"));
+		}
+		else
+		{
+			rot = -GetPhysicsFloat(CRCD(0x7933a8ef, "Physics_Ground_Sharp_Rotation")); 
+			if (speed < 10.0f)
+			{
+				// If not moving, then we want more control over turning, so ramp up the turing speed over a second
+				int pressed_time = control_pad.m_right.GetPressedTime();
+				if (pressed_time < STOPPED_TURN_RAMP_TIME)
+				{
+					rot = rot * pressed_time / STOPPED_TURN_RAMP_TIME;
+				}
+			}
+		}
+	}
+
+	/*
+	bool do_cess = false;							
+	if (CESS_SLIDE_BUTTONS)
+	{
+		float cess_turn_min_speed = GetPhysicsFloat(CRCD(0xae84e34a, "cess_turn_min_speed"));
+		if (speed > cess_turn_min_speed)
+		{
+			do_cess = true;
+			
+			float cess_turn_cap_speed = GetPhysicsFloat(CRCD(0x8242c4fe, "cess_turn_cap_speed"));
+			
+			float scale = speed;
+			if (scale > cess_turn_cap_speed)
+			{
+				scale = cess_turn_cap_speed;
+			}
+			scale -= cess_turn_min_speed;
+			scale /= cess_turn_cap_speed - cess_turn_min_speed;
+		
+			rot = rot * scale * GetPhysicsFloat(CRCD(0x22834151, "cess_turn_multiplier"));
+		}
+	}
+	*/
+
+	if (rot == 0.0f) return;
+	
+	rot *= m_frame_length;
+	
+	mYAngleIncreased = rot > 0.0f;
+	
+	// K: Avoid anything that might change the velocity direction if this flag is set.
+	if (!m_lock_velocity_direction /* && !do_cess*/)
+	{
+		GetVel().RotateY(rot);					// Note:  Need to rotate this about UP vector		
+		DUMP_VELOCITY;
+	}	
+	
+	GetMatrix().RotateYLocal(rot);				
+	m_lerping_display_matrix.RotateYLocal(rot);				
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::remove_sideways_velocity ( Mth::Vector& vel )
+{				
+	// Remove any non-forward component of the velocity
+	
+	float speed = vel.Length(); 		// get size of velocity
+	if (speed > 0.00001f)
+	{
+		// if (!USE_BIKE_PHYSICS)
+		// {		
+			vel *= 1.0f / speed;																// get unit vector in direction of velocity
+			float direction = Mth::Sgn(Mth::DotProduct(vel, GetMatrix()[Z]));	// get fwds or backwards
+			
+			vel = GetMatrix()[Z];												// get forward direction
+			vel *= speed * direction;													// apply all speed in this direction
+			DUMP_VELOCITY;
+		// }
+		// else
+		// {
+		  // Mth::Vector old_vel = vel;
+		  
+		  // vel.ProjectToNormal(GetMatrix()[Z]); 								// leave forward velocity alone
+		  
+		  // old_vel -= GetVel();		  														// find remaining sideways velocity
+		  
+		  // old_vel *= 1.0f - GetPhysicsFloat(CRCD(0x53385759, "cess_Friction"));
+		  // vel += old_vel;
+		  // DUMP_VELOCITY;
+		// }
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::check_side_collisions (   )
+{
+	// check for collisins left and right of the skater; if we get collisions on both sides then restore him to the original position
+	
+	Mth::Vector debounce = GetPos();
+
+	float side_col = GetPhysicsFloat(CRCD(0x406b425f, "Skater_side_collide_length"));
+
+	if (check_side(-1.0f, side_col))
+	{
+		if (check_side(1.0f, side_col))
+		{
+			GetPos() = debounce;			// two collisions, back to safety
+			DUMP_POSITION;
+		}
+	}
+	else
+	{
+		if (check_side(1.0f, side_col))
+		{
+			if (check_side(-1.0f, side_col))
+			{
+				GetPos() = debounce;		// two collisions, back to safety
+				DUMP_POSITION;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::check_side ( float side, float side_col )
+{
+	#ifdef STICKY_WALLRIDES
+	// Prevous checks might have put us into a wall ride, so just ignore future checks					 
+	if (GetState() == WALL)
+	{
+		return false;
+	}
+	#endif
+			  
+	// - - - side collision detection  - - - - - - - - - - - - - - - - -
+
+	m_col_start = GetPos() + GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xb45b39e2, "Skater_side_collide_height"));
+	
+	float col_len = side_col;
+	#ifdef STICKY_WALLRIDES
+	if (GetState() == AIR)
+	{
+		col_len += GetPhysicsFloat(CRCD(0x1c58c8b4, "Skater_air_extra_side_col"));
+	}
+	#endif
+	m_col_end = m_col_start + side * GetMatrix()[X] * col_len;  
+
+	if (!get_member_feeler_collision()) return false;
+	
+	Mth::Vector WallFloorNormal = m_feeler.GetNormal();
+
+	#ifdef STICKY_WALLRIDES
+	if (GetState() == AIR)
+	{
+		if (check_for_wallride())
+		{
+			return true;
+		}
+		else
+		{
+			// push a bit away from the wall if in the air
+			Mth::Vector to_wall = m_feeler.GetPoint();
+			to_wall -= m_col_start;				// vector towards the wall
+			float push_dist = to_wall.Length();					// distance to wall
+			push_dist -= col_len - side_col;	   				// adjust by the extra push we gave
+			if (push_dist > 0.0f)								// if closer to wall than side_col
+			{
+				to_wall.Normalize(push_dist);				    // then get direction to wall, scaled by dist we want to move				
+				GetPos() -= to_wall / 10.0f;		    // move 1/10th of the way, for nice lerp
+				DUMP_POSITION;
+				
+				float turn_angle;					
+				// Rotate away from wall only if not rotating myself
+				// otherwise velocity can be continually rotated one way while 
+				// the orientation is rated the other way via the d-pad 
+				
+				CControlPad& control_pad = mp_input_component->GetControlPad();
+
+				// don't rotate at all if in the air, as it changes our direction, usually not what we want
+				if (GetState() != AIR
+					&& !control_pad.m_R1.GetPressed()
+					&& !control_pad.m_L1.GetPressed() 
+					&& !control_pad.m_left.GetPressed() 
+					&& !control_pad.m_right.GetPressed()) 
+				{
+					rotate_away_from_wall(WallFloorNormal, turn_angle, 0.2f); 	// and rotate away from the wall
+				}
+			}
+		}
+	}
+	else
+	#endif
+	{
+		float angle = Mth::DotProduct(WallFloorNormal, GetMatrix()[Y]);
+		// Consider 90+-30 degrees as wall (_0_5)
+		// Consider 90+-15 degrees as wall (_0_25)
+		if (angle < 0.25f && angle > -0.25f)
+		{
+			// Undo movement
+			GetPos() = m_safe_pos;
+			DUMP_POSITION;
+
+			float turn_angle;
+			rotate_away_from_wall(WallFloorNormal, turn_angle, 0.2f);
+
+			// Try moving him off the wall:
+			CFeeler	feeler(m_feeler.GetPoint() + WallFloorNormal, m_feeler.GetPoint() + WallFloorNormal * side_col);	
+			if (!feeler.GetCollision())
+			{
+				mp_trigger_component->CheckFeelerForTrigger(TRIGGER_BONK, m_feeler);
+				if (mp_physics_control_component->HaveBeenReset()) return false;
+				
+				// Lower skater back down to the ground
+				GetPos() = feeler.m_end - GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xb45b39e2,"Skater_side_collide_height"));
+				DUMP_POSITION;
+				return true;
+			}
+		}
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::check_for_wallpush (   )
+{
+	// requires that m_feeler is a wall
+	
+	if (!mp_input_component->GetControlPad().m_triangle.GetPressed()) return false;
+	
+	// last wallpush must not have been too recently
+	if (Tmr::ElapsedTime(m_last_wallpush_time_stamp) < Script::GetFloat(CRCD(0x17d543, "Physics_Disallow_Rewallpush_Duration"))) return false;
+	
+	// wall normal must be opposite our forward direction; just under the maximum flail angle
+	if (Mth::DotProduct(GetMatrix()[Z], m_feeler.GetNormal()) >= -sinf(Mth::DegToRad(Script::GetFloat(CRCD(0x1483fd01, "Wall_Bounce_Dont_Slow_Angle")) - 1.0f))) return false;
+	
+	// last wallplant must not have been too recently
+	if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) < Script::GetFloat(CRCD(0x17d543, "Physics_Disallow_Rewallpush_Duration"))) return false;
+	
+	// throw a wallpush event for the scripts
+	GetObject()->SelfEvent(CRCD(0x4c03635b, "WallPush"));
+	
+	// check to see if the wallpush has been canceled during the event
+	if (GetFlag(CANCEL_WALL_PUSH))
+	{
+		SetFlagFalse(CANCEL_WALL_PUSH);
+		return false;
+	}
+	
+	// reverse direction of velocity perpendicular to the wall
+	Mth::Vector perp_vel = Mth::DotProduct(GetVel(), m_feeler.GetNormal()) * m_feeler.GetNormal();
+	GetVel() -= 2.0f * perp_vel;
+	
+	// damp horizontal velocity
+	float speed = GetVel().Length();
+	if (speed > 0.001f)
+	{
+		GetVel() *= Mth::Max(
+			Script::GetFloat(CRCD(0xb78542c2, "Physics_Wallpush_Min_Exit_Speed")),
+			speed - Script::GetFloat(CRCD(0x1112fb1c, "Physics_Wallpush_Speed_Loss"))
+		) / speed;
+	}
+	else
+	{
+		GetVel() = -Script::GetFloat(CRCD(0xb78542c2, "Physics_Wallpush_Min_Exit_Speed")) * GetMatrix()[Z];
+	}
+	
+	// project the resulting velocity into the ground's plane
+	GetVel().RotateToPlane(m_current_normal);
+    
+	DUMP_VELOCITY;
+	
+	// set orientation along new velocity
+	GetMatrix()[Z] = GetVel();
+	GetMatrix()[Z].Normalize();
+	GetMatrix()[Y] = m_current_normal;
+	GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
+	ResetLerpingMatrix();
+	
+	// time stamp the wallplant
+	m_last_wallpush_time_stamp = Tmr::GetTime();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::check_for_wallplant (   )
+{
+	// requires that m_feeler is a wall
+	
+	// we must be somewhat vertical
+	if (GetMatrix()[Y][Y] < 0.1f) return false;
+	
+	// last wallplant must not have been too recently
+	if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) < Script::GetFloat(CRCD(0x82135dd7, "Physics_Disallow_Rewallplant_Duration"))) return false;
+	
+	// not when you're too near the ground
+	if (mp_state_component->m_height < Script::GetFloat(CRCD(0xd5349cc6, "Physics_Min_Wallplant_Height"))) return false;
+	
+	// wall must be substantially vertical
+	if (!(m_feeler.GetFlags() & mFD_VERT) && Mth::Abs(m_feeler.GetNormal()[Y]) > 0.1f) return false;
+	
+	float speed = GetVel().Length();
+	if (speed < 0.01f) return false;
+	Mth::Vector forward = GetVel() / speed;
+		
+	// horizontal wall normal must be opposite our horizontal velocity
+	Mth::Vector horizontal_forward = forward;
+	horizontal_forward[Y] = 0.0f;
+	horizontal_forward.Normalize();
+	Mth::Vector horizontal_normal = m_feeler.GetNormal();
+	horizontal_normal[Y] = 0.0f;
+	horizontal_normal.Normalize();
+	if (Mth::DotProduct(horizontal_forward, horizontal_normal) > -sinf(Mth::DegToRad(Script::GetFloat(CRCD(0x8f79cc1c, "Physics_Wallplant_Min_Approach_Angle"))))) return false;
+	
+	// here we attempt to stop wallplant when in is more likely that the player is going for a grind
+	if (GetVel()[Y] > 0.0f && mp_input_component->GetControlPad().m_triangle.GetPressed())
+	{
+		Mth::Vector wall_point = m_feeler.GetPoint();
+		Mth::Vector	wall_normal = m_feeler.GetNormal();
+
+		Mth::Vector wall_up_vel(0.0f, GetVel()[Y] * 0.15f, 0.0f);		// check 0.15 seconds ahead
+		wall_up_vel.RotateToPlane(wall_normal);  
+
+		// check at what height will be in two frames
+		wall_point += wall_up_vel;		
+
+		CFeeler feeler(wall_point + wall_normal * 6.0f, wall_point - wall_normal * 6.0f);
+		if (!feeler.GetCollision())
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
+			{
+				feeler.DebugLine(255, 255, 0, 0);
+			}
+			#endif
+			return false;
+		}
+		else
+		{
+			#ifdef __USER_DAN__
+			if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
+			{
+				feeler.DebugLine(255, 0, 255, 0);
+			}
+			#endif
+		}
+	}
+		
+	// check for wallplant trick
+	// K: Modified this to take an array of triggers, so that Kurt could check out using
+	// Down,X DownLeft,X or DownRight,X as a trigger.
+	bool triggered=false;
+	Script::CArray *p_trick_query_array=Script::GetArray(CRCD(0x5d1f84a7, "Wallplant_Trick"));
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *p_trick_query_struct=p_trick_query_array->GetStructure(i);
+		if (mp_trick_component->QueryEvents(p_trick_query_struct))
+		{
+			triggered=true;
+			break;
+		}
+	}
+	if (!triggered)
+	{
+		return false;
+	}
+	
+	// trip wallplant triggers
+	mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_feeler);
+	if (mp_physics_control_component->HaveBeenReset()) return true;
+	
+	// zero vertical velocity
+	GetVel()[Y] = 0.0f;
+	
+	// reverse velocity in the direction of the wall's normal
+	Mth::Vector perp_vel = Mth::DotProduct(GetVel(), horizontal_normal) * horizontal_normal;
+	GetVel() -= 2.0f * perp_vel;
+	
+	// damp horizontal velocity
+	float horizontal_speed = sqrtf(GetVel()[X] * GetVel()[X] + GetVel()[Z] * GetVel()[Z]);
+	if (horizontal_speed > 0.0001f)
+	{
+		GetVel()[Y] = 0.0f;
+		GetVel().Normalize(Mth::Max(
+			Script::GetFloat(CRCD(0x7cee396c, "Physics_Wallplant_Min_Exit_Speed")),
+			horizontal_speed - Script::GetFloat(CRCD(0x9b2d4a3, "Physics_Wallplant_Speed_Loss"))
+		));
+	}
+	else
+	{
+		GetVel() = -Script::GetFloat(CRCD(0x7cee396c, "Physics_Wallplant_Min_Exit_Speed")) * horizontal_forward;
+	}
+	
+	// replace vertical velocity with a wallplant boost
+	GetVel()[Y] = Script::GetFloat(CRCD(0x74957fa, "Physics_Wallplant_Vertical_Exit_Speed"));
+	
+	if (m_feeler.IsMovableCollision())
+	{
+		// if the wall is moving, we are now in contact with it
+		if (!mp_movable_contact_component->HaveContact() || m_feeler.GetMovingObject() != mp_movable_contact_component->GetContact()->GetObject())
+		{
+			mp_movable_contact_component->LoseAnyContact();
+			mp_movable_contact_component->ObtainContact(m_feeler.GetMovingObject());
+		}
+	}
+	
+	DUMP_VELOCITY;
+	
+	// move to just outside the wall, insuring that there is no additional collision along the line to that point
+	m_feeler.m_start = m_feeler.GetPoint();
+	m_feeler.m_end = m_feeler.GetPoint() + Script::GetFloat(CRCD(0x24be8f0, "Physics_Wallplant_Distance_From_Wall")) * m_feeler.GetNormal();
+	if (m_feeler.GetCollision())
+	{
+		GetPos() = m_feeler.GetPoint() + 0.1f * m_feeler.GetNormal();
+	}
+	else
+	{
+		GetPos() = m_feeler.m_end;
+	}
+	DUMP_POSITION;
+	
+	// set orientation along new velocity
+	GetMatrix()[Z] = GetVel();
+	GetMatrix()[Z][Y] = 0.0f;
+	GetMatrix()[Z].Normalize();
+	GetMatrix()[Y].Set(0.0f, 1.0f, 0.0f);
+	GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
+	ResetLerpingMatrix();
+	
+	// time stamp the wallplant
+	m_last_wallplant_time_stamp = Tmr::GetTime();
+	
+	// throw a wallplant event for the scripts
+	GetObject()->SelfEvent(CRCD(0xcf74f6b7, "WallPlant"));
+	
+	// trick off the object
+	mp_trick_component->TrickOffObject(m_feeler.GetNodeChecksum());
+	
+	// turn back on orientation control in case we just came out of walking
+	SetFlagFalse(NO_ORIENTATION_CONTROL);
+	
+	// acid drops are always allowed after a wallplant
+	SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
+	
+	// let the camera know we're snapping our position slightly
+	SetFlagTrue(SNAPPED);
+	
+	// enter wallplant state
+	SetState(WALLPLANT);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::check_for_wallride (   )
+{
+	// Requires that m_feeler is a valid wall
+	
+	if (!m_col_flag_wallable) return false;
+		
+	// Allow a wall-ride attempt if triangle being pressed & long enough after the last wall-ride.	
+	if ((mp_trick_component->GetButtonState(CRCD(0x20689278, "Triangle")) || mp_trick_component->TriggeredInLastNMilliseconds(
+			CRCD(0x20689278, "Triangle"),
+			1000 * GetPhysicsFloat(CRCD(0x5d241b00, "Wall_Ride_Triangle_Window"))
+		)) && static_cast< int >(Tmr::ElapsedTime(mWallrideTime)) > 1000 * GetPhysicsFloat(CRCD(0xf0c74ec2, "Wall_Ride_Delay")))
+	{
+		////////////////////////////////////////////////
+		// check to see if we are going upwards, and within 0.15 seconds from the top of the wall (based on current up speed)
+		// and we are going upwards, then don't wallride, as we will probably snap into a rail soon after
+		//
+
+		if (GetVel()[Y] > 0.0f)
+		{
+			Mth::Vector wall_point = m_feeler.GetPoint();
+			Mth::Vector	wall_normal = m_feeler.GetNormal();
+	
+			Mth::Vector wall_up_vel(0.0f, GetVel()[Y] * 0.15f, 0.0f);		// check 0.15 seconds ahead
+			wall_up_vel.RotateToPlane(wall_normal);  
+			
+			// check at what height will be in two frames
+			wall_point += wall_up_vel;		
+			
+			CFeeler check_feeler(wall_point + wall_normal * 6.0f, wall_point - wall_normal * 6.0f);
+			if (!check_feeler.GetCollision()) return false;
+		}	
+		
+		////////////////////////////////////////////////
+		mWallNormal = m_feeler.GetNormal();
+		
+		Mth::Vector SquashedWallNormal = mWallNormal;			 // wall normal in the XZ plane
+		SquashedWallNormal[Y] = 0;
+		SquashedWallNormal.Normalize();
+		
+		Mth::Vector SquashedVel = GetVel();			 			// velocity in the XZ plane
+		SquashedVel[Y] = 0;
+		SquashedVel.Normalize();
+		
+		// Calculate the speed "along" the wall (i.e., in the XZ plane) needed to have enough speed along the wall to make the wallride viable 
+		// (otherwise we just go up and down, and end up at odd angles)
+		Mth::Vector vel_along_wall = GetVel();
+		vel_along_wall[Y] = 0;
+		vel_along_wall.ProjectToPlane(SquashedWallNormal);
+		float speed_along_wall = vel_along_wall.Length();
+		
+		if (speed_along_wall < GetPhysicsFloat(CRCD(0xf0636a67, "Wall_Ride_Min_Speed"))) return false;
+		
+		float dot = fabsf(Mth::DotProduct(SquashedVel, SquashedWallNormal));
+		
+		// If all angles OK then trigger a wall-ride.
+		if (mWallNormal[Y] > -sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xae23e850, "Wall_Ride_Upside_Down_Angle")))) && 
+			dot < sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x6ac9f64d, "Wall_Ride_Max_Incident_Angle")))) && 
+			GetMatrix()[Y][Y] > cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xbfcbb7e8, "Wall_Ride_Max_Tilt")))))
+		{
+			if (!mp_trick_component->GraffitiTrickStarted())
+			{
+				// clear the graffiti trick buffer if we're moving to a new
+				// world sector, but haven't started our trick yet...
+				mp_trick_component->SetGraffitiTrickStarted(false);
+			}
+
+			// landed in a wall ride, check for triggers associated with this object
+			// note we pass "TRIGGER_LAND_ON", perhaps not semanticaly correct but we use it for landing on rails,
+			// so the meaning is the same across ground-rail-wallride
+			set_last_ground_feeler(m_feeler);
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_LAND_ON, m_last_ground_feeler);
+			if (mp_physics_control_component->HaveBeenReset()) return false;
+
+			Mth::Vector	point = m_feeler.GetPoint();				   
+
+			// check to see if we are going into a corner
+			// by checking the vector from out current pos along a line parallel to our movment rotated onto the wall.
+			Mth::Vector	forward_vel = GetPos() - GetOldPos();
+			forward_vel.RotateToPlane(mWallNormal);
+			m_col_start = GetPos();
+			m_col_end = GetPos() + forward_vel * 3.0f;
+			if (get_member_feeler_collision()) return false;
+			
+			// Record start time.
+			mWallrideTime = static_cast< int >(Tmr::GetTime());
+			
+			// Snap position to wall.
+			// we don't want to snap too close
+			// previously we moved him an inch away from the wall after snapping him to the collision point.
+			// However, this seemed to cause problems in corner so now I just move him back one inch along the collision line
+			// which is hence guarenteed not to push him through walls.
+			
+			GetPos() = point;	   					// move skater
+			GetPos() += mWallNormal;	// move away from surface
+			DUMP_POSITION;
+			
+			// Rotate velocity to plane.
+			GetVel().RotateToPlane(mWallNormal);
+			DUMP_VELOCITY;
+			
+			// Mick: if we set the velocity as direction, skater will keep going up more
+			GetMatrix()[Z] = GetVel();
+			
+			GetMatrix()[Z].Normalize();
+			GetMatrix()[Y] = mWallNormal;
+			GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
+			GetMatrix()[X].Normalize();
+			ResetLerpingMatrix();
+
+			// Decide whether left or right wall-ride.
+			#if 0 // old code
+			// Mth::Vector Up(0.0f, 1.0f, 0.0f);
+			// Mth::Vector Cross = Mth::CrossProduct(mWallNormal, Up);
+			#else
+			Mth::Vector Cross(-mWallNormal[Z], 0.0f, mWallNormal[X], 0.0f);
+			#endif
+			
+			if (Mth::DotProduct(GetVel(), Cross) < 0.0f)
+			{
+				// Let the script do any extra logic, like playing anims & stuff.
+				FLAGEXCEPTION(CRCD(0x5de19c83, "WallRideLeft"));
+			}
+			else
+			{
+				FLAGEXCEPTION(CRCD(0x51372712, "WallRideRight"));
+			}
+			
+			SetState(WALL);
+
+			// Handle contact			
+			check_movable_contact();
+			
+			return true;
+		}	
+		else
+		{
+			// Otherwise trigger the bail script.
+			// FLAGEXCEPTION(CRCD(0x2ec3c7f5, "WallRideBail"));
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSkaterCorePhysicsComponent::rotate_away_from_wall ( const Mth::Vector& normal, float &turn_angle, float lerp )
+{
+	// given a wall normal, then calculate the "turn_angle" to rotate the skater by and rotate the matrix and display matrix by this
+	// return the angle between the skater and the wall
+	// note turn_angle is passed by reference, and is altered !!!!
+	
+	// given m_right(dot)normal, we should be able to get a nice angle
+	float dot_right_normal = Mth::DotProduct(GetMatrix()[X], normal);
+
+	float angle = acosf(Mth::Clamp(dot_right_normal, -1.0f, 1.0f)); 	
+
+	if (angle > Mth::PI / 2.0f)
+	{
+		angle -= Mth::PI;
+	}
+	
+	// angle away from the wall
+	turn_angle = angle * GetPhysicsFloat(CRCD(0xe07ee1a9, "Wall_Bounce_Angle_Multiplier")) * lerp;
+	
+	// Rotate the skater so he is at a slight angle to the wall, especially if we are in a right angled corner, where the skater will bounce out
+
+	GetVel().RotateY(turn_angle);
+	DUMP_VELOCITY;
+	GetMatrix().RotateYLocal(turn_angle);
+	m_lerping_display_matrix.RotateYLocal(turn_angle);	   
+	
+	return angle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::check_leaning_into_wall (   )
+{
+	// If we are leaning left or right, then do a collision check in that direction, and proportional to the amount of the lean.
+	// If there is a collision, then push the skater away from the wall.
+	
+	// First determine if we actually need to be doing this
+	
+	// Need to be moving at a reasonable rate
+	if (GetVel().Length() > GetPhysicsFloat(CRCD(0xbc33d268, "Skate_min_wall_lean_push_speed"))) return;
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+
+	float time;
+	if (control_pad.m_left.GetPressed())
+	{
+		time = control_pad.m_left.GetPressedTime();
+	}
+	else if (control_pad.m_right.GetPressed())
+	{
+		time = control_pad.m_right.GetPressedTime();
+	}
+	else
+	{
+		return;
+	}
+	time *= 1.0f / 1000.0f;
+
+	// Calculate the length of the vector based on how long you have held down the left or right button.  
+	// (Ideally it would be tied to the animation, but this is simple, and it works)	
+	float min_time = GetPhysicsFloat(CRCD(0x435f0653, "Skate_wall_lean_push_time"));
+	if (time > min_time)
+	{
+		time = min_time;
+	}
+	
+	if (control_pad.m_right.GetPressed())
+	{
+		time = -time;
+	}
+	float length = time / min_time * GetPhysicsFloat(CRCD(0x4b8bab12, "Skate_wall_lean_push_length"));	
+
+	
+	// Now we've got the length, and in the right direction, check for a collision
+	
+	m_col_start = GetPos() + GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xbfbbd0af, "Skate_wall_lean_push_height"));
+	m_col_end = m_col_start + GetMatrix()[X] * length;
+	if (get_member_feeler_collision())
+	{
+	
+		// see how much we are into the wall
+		Mth::Vector push = m_feeler.GetPoint() - m_col_end;
+		
+		// Only push directly out from the wall (like the upwards collision, otherwise we get pushed back)
+		Mth::Vector normal = m_feeler.GetNormal();
+		push.ProjectToNormal(normal);
+		
+		m_col_start = GetPos() + GetMatrix()[Y];
+		m_col_end = m_col_start + push;
+		if (!get_member_feeler_collision())
+		{
+			// just move him, might put him 1 inch above the ground, but regular physics should snap him down
+			GetPos() = m_col_end - GetMatrix()[Y];
+			DUMP_POSITION;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::flip_if_skating_backwards (   )
+{	
+	// if sufficient speed, and facing backwards
+	if (!GetFlag(IS_BAILING)
+		&& !GetFlag(SKITCHING)		// if skitching, probably just car turning a slow sharp corner
+		&& !is_braking()
+		&& Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f
+		&& GetVel().LengthSqr() > Mth::Sqr(GetPhysicsFloat(CRCD(0x2bf71eeb, "Skater_Flip_Speed")))
+		)
+	{
+		// flip Z and X, to rotate 180 degrees about y
+		GetMatrix()[Z] = -GetMatrix()[Z];
+		GetMatrix()[X] = -GetMatrix()[X];
+		m_lerping_display_matrix[Z] = -m_lerping_display_matrix[Z];
+		m_lerping_display_matrix[X] = -m_lerping_display_matrix[X];
+
+		// Dan: we can no longer flip mid animation
+		/*
+		CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
+		Dbg_Assert(p_flip_and_rotate_component);
+		p_flip_and_rotate_component->ToggleFlipState();
+		*/
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::maybe_flag_ollie_exception (   )
+{
+	#ifdef __NOPT_ASSERT__
+	if (GetFlag(TENSE) && Script::GetInteger("TurboOllie"))
+	{
+		m_tense_time = GetFlagElapsedTime(TENSE);
+		SetFlagFalse(TENSE);
+		FLAGEXCEPTION(CRCD(0x8ffefb28, "Ollied"));
+		return true;
+	}
+	#endif
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	if (GetFlag(TENSE) && !control_pad.m_x.GetPressed())
+	{
+		// Remember the tense time, cause it will be needed when the Jump script command executes.
+		m_tense_time = GetFlagElapsedTime(TENSE);
+		
+		SetFlagFalse(TENSE);
+		
+		control_pad.m_x.ClearRelease();
+		
+		FLAGEXCEPTION(CRCD(0x8ffefb28, "Ollied"));
+		
+		return true;
+	}	
+
+	return false;
+}  
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 
+void CSkaterCorePhysicsComponent::maybe_skitch (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+
+	if (GetFlag(SKITCHING) || !SKITCH_BUTTON || GetState() != GROUND) return;
+	
+	// Find the nearest skitch point.
+	Mth::Vector closest_pos;
+	float closest_dist = GetPhysicsFloat(CRCD(0x2928f080, "Skitch_Max_Distance"));
+	CCompositeObject* p_closest = NULL;
+	
+	for (CSkitchComponent *p_skitch_comp = static_cast< CSkitchComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_SKITCH));
+		  p_skitch_comp;
+		  p_skitch_comp = static_cast< CSkitchComponent* >(p_skitch_comp->GetNextSameType()))
+	{
+		if (!p_skitch_comp->CanSkitch()) continue;
+		
+		CCompositeObject* p_composite_object = p_skitch_comp->GetObject();
+		if (p_composite_object == GetObject()) continue;
+		
+		Mth::Vector	skitch_point(0.0f, 0.0f, 0.0f);
+		int skitch_index = p_skitch_comp->GetNearestSkitchPoint(&skitch_point, GetPos());
+		if (skitch_index == -1) continue;
+		
+		Mth::Vector line_to = skitch_point - GetPos();
+		float dist_to = line_to.Length();
+		if ( dist_to < closest_dist)
+		{
+			closest_dist = dist_to;
+			p_closest = p_composite_object;
+			closest_pos	= skitch_point;
+			m_skitch_index = skitch_index;
+		}
+	}
+
+	if (p_closest)
+	{
+		// Clear any triggers, so old L1/R1 presses don't affect us.
+		control_pad.m_L1.ClearTrigger();
+		control_pad.m_R1.ClearTrigger();
+		mp_skitch_object = p_closest;
+
+		FLAGEXCEPTION(CRCD(0x2f184eb1, "Skitched"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_in_air_physics (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+
+	// Acceleration is re-calculated every frame
+	// here it is just set to the air gravity 
+	// there are generally no other forces that we would use in the air
+
+	Mth::Vector acc(0.0f, get_air_gravity(), 0.0f);
+
+	SetFlagFalse(LAST_POLY_WAS_VERT);
+	
+	// disallow acid drops before this frame's breaking of vert air
+	if (GetFlag(VERT_AIR) || GetFlag(SPINE_PHYSICS) || GetFlag(IN_ACID_DROP) || GetFlag(IS_BAILING))
+	{
+		SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
+	}
+	
+	///////////////////////////////////////////////////////////////////////////////
+	// Adjust orientation before we do any movement
+	// in the air the orientation is independent of the velocity but it will affect the collision checks we do (left/right/forwards/up)
+
+	handle_air_rotation(); 		// rotate left/right (spinning by holding left/right or L1/R1)
+	
+	handle_air_lean();			// lean forwards backwards (by hold up/down) also handles spine leans
+	
+	handle_transfer_slerping();	// during acid drops, we slerp to our required orientation
+	
+	if (GetFlag(IN_RECOVERY) || VERT_RECOVERY_BUTTONS)
+	{
+		// no control here, except for the VERT_RECOVERY_BUTTONS, above 
+		handle_air_vert_recovery();	// recovering from going off the end of a vert polygon
+	}
+	
+	// Upright if not vert, and not doing a spine transfer
+	if (!GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS) && !mp_rotate_component->IsApplyingRotation(X) && !mp_rotate_component->IsApplyingRotation(Z))
+	{
+		rotate_upright();
+	}		
+
+	// end of adjusting orientation in air
+	////////////////////////////////////////////////////////////////////
+	
+	//////////////////////////////////////////////////////////////////////////////
+	// Apply special friction even in the air
+	
+	if (m_special_friction_duration != 0.0f)
+	{
+		apply_rolling_friction();
+	}
+	
+	// end of friction
+	////////////////////////////////////////////////////////////////////////////
+
+	///////////////////////////////////////////////////////////////////////////////
+	// Calculate what velocity to use
+	// generally this is just the stored m_vel, but if we are doing
+	// a spine transfer	we a	dd in m_spine_vel
+	// (note, we also update the state of the spine transfer here, 
+	// perhaps that would be better done elsewhere)
+	
+	Mth::Vector vel = GetVel();
+	
+	///////////////////////////////////////////////////////////////////////////
+
+	if (GetFlag(SPINE_PHYSICS))
+	{
+		// this check is only valid if we are not in contact with a moving object		
+		if (vel[Y] < 0.0f && GetPos()[Y] < m_transfer_target_height)	// just check if we have dropped below the target height		
+		{
+			// if (Mth::Abs(mp_state_component->m_spine_vel.Length() - 0.1f) > 1.0f)
+			// {
+				// Gfx::AddDebugStar(GetPos(), 24, GREEN, 0);
+			// }
+			mp_state_component->m_spine_vel.Normalize(0.1f); // make spine velocity very small, but still there, so camera works
+		}		
+		else
+		{
+			vel += mp_state_component->m_spine_vel;
+		}
+	}
+
+	///////////////////////////////////////////////////
+	// calculate contact movement
+	// If the skater is in contact with an object, then his velocity (m_vel) is considered to be local to that object, so here we have to add in the
+	// movement of the object from the last frame; we also account for rotation here, in the call to mp_movable_contact_component->UpdateContact
+
+	Mth::Vector	contact_movement;
+	contact_movement.Set(0.0f, 0.0f, 0.0f);
+
+	// if we are in contact with something, then factor in that "movement"
+	if (mp_movable_contact_component->UpdateContact(GetPos()))
+	{
+		contact_movement = mp_movable_contact_component->GetContact()->GetMovement();
+	}
+
+	// end of calculating contact movment
+	/////////////////////////////////////////////////////////////////////////
+
+	///////////////////////////////////////////////////////////////
+	// Update position
+	
+	// Update position using correct equations of motion (S = ut + 0.5at^2)
+	GetPos() += vel * m_frame_length + acc * (0.5f * m_frame_length * m_frame_length);
+	DUMP_POSITION;
+	
+	// add in movement due to contact
+	GetPos() += contact_movement;
+	DUMP_POSITION;
+	
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
+	{
+		if (GetFlag(SPINE_PHYSICS))
+		{
+			Gfx::AddDebugLine(GetPos(), GetOldPos(), RED, 0, 0);
+		}
+		else if (GetFlag(VERT_AIR))
+		{
+			Gfx::AddDebugLine(GetPos(), GetOldPos(), YELLOW, 0, 0);
+		}
+		else
+		{
+			Gfx::AddDebugLine(GetPos(), GetOldPos(), BLUE, 0, 0);
+		}
+	}
+	#endif
+	
+	//
+	// end updating position
+	//////////////////////////////////////////////////////////////
+	
+	
+	/////////////////////////////////////////////////////////////////
+	// update velocity
+	//
+	
+	// Update Velocity in air	
+	GetVel() += acc * m_frame_length;
+	DUMP_VELOCITY;
+	
+	//
+	// end updating velocity
+	/////////////////////////////////////////////////////////////////
+
+	/////////////////////////////////////////////////////////////////
+	// Handle trying to break VERT	
+	//	
+	
+	if (GetFlag(CAN_BREAK_VERT))
+	{
+		if (GetFlag(TRACKING_VERT) && GetFlag(VERT_AIR))
+		{
+			// might want to check how long we've been in the air before allowing this
+			// also don't want to do it if we've been doing tricks and stuff 
+			
+			// first of all, check if there is a collision beneath my feet
+			m_col_start = GetPos() + GetMatrix()[Y] * 1.0f;		// bit above the feet
+			m_col_end = GetPos() + GetMatrix()[Y] * -23.0f;
+			if (!get_member_feeler_collision())
+			{
+				maybe_break_vert();
+				
+				// if we did not break vert now, then only allow us to break vert later if we've been tapping the "up" button
+				// this is indicated by us having RELEASED or Pressed in the last few ticks
+				if (static_cast< int >(control_pad.m_up.GetReleasedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time"))
+					&& static_cast< int >(control_pad.m_up.GetPressedTime()) > GetPhysicsInt(CRCD(0x6bb5b751, "Skater_vert_active_up_time")))
+				{
+					// "UP" was not pressed any time recently, so don't let us break late
+					SetFlagFalse(CAN_BREAK_VERT);	
+				}
+			}
+		}	 
+		   
+		// Allow us to break vert polys for a while after we ge onto them
+		// to make it more robust
+		// Essentially lets CAN_BREAK_VERT flag expire....
+		if (static_cast< int >(Tmr::ElapsedTime(GetFlagTime(CAN_BREAK_VERT))) > GetPhysicsInt(CRCD(0x26495947, "Skater_Vert_Allow_break_Time")))
+		{
+			SetFlagFalse(CAN_BREAK_VERT);
+		}
+	}
+	else
+	{
+		// We are flagged as not being able to break vert
+		// however, we still want to be able to do this at any time on the up journey
+		// if we try break the spine
+		if (GetFlag(VERT_AIR) && BREAK_SPINE_BUTTONS && !GetFlag(SPINE_PHYSICS) && GetVel()[Y] > 0.0f)
+		{
+			maybe_break_vert();
+		}
+	
+	}
+	
+	//
+	// End of Handling breaking Vert
+	////////////////////////////////////////
+	
+	if (!mp_movable_contact_component->HaveContact())
+	{
+		
+		//////////////////////////////////////////////////////////////////////
+		// Now update the tracking position before we do a collision check	   
+		// "Tracking" is used when we are "vert" (moving in a vertical plane through the air above a quarterpipe), to track the ground directly
+		// benath us, and if the QP bends, to move the skater appropiately so he can catch vert air along bent QPs, and round corners. 		 
+		
+		if (GetFlag(TRACKING_VERT) && GetFlag(VERT_AIR))
+		{
+			m_col_start[X] = GetPos()[X];
+			m_col_start[Y] = m_vert_pos[Y];
+			m_col_start[Z] = GetPos()[Z];
+	
+			m_col_end = m_col_start;
+			
+			m_col_start += m_vert_normal * 30;			// Away from face
+			m_col_end -= m_vert_normal * 30;			// into face
+	
+			// Now see if there is a collision, and track it if so
+	
+			// First we try an above this position
+			// raising us up by m_vert_upstep
+			// which starts at 6 inches, and is halved down until
+			// less than half an inch, at which point we stop trying to move up
+	
+			bool collision;
+	
+			if (m_vert_upstep > 0.5f)
+			{
+				m_col_start[Y] += m_vert_upstep;
+				m_col_end[Y] += m_vert_upstep;
+				// Only check for vert polys, ignore collision info
+				collision = get_member_feeler_collision(0, mFD_VERT);  
+		
+				// If we did not find a collision six inches above, then check back at the old height
+				if (!collision)
+				{
+					m_col_start[Y] -= m_vert_upstep;
+					m_col_end[Y] -= m_vert_upstep;	
+					m_vert_upstep *= 1.0f / 2.0f;			// binary search will zoom in on the edge		
+					collision = get_member_feeler_collision(0, mFD_VERT);  
+				}
+			}
+			else
+			{
+				collision = get_member_feeler_collision(0, mFD_VERT);  	
+			}
+	
+	
+			if (!collision)
+			{
+				// there is no collision, which usually mean we have gone off the end of a qp but might mean the tracking point has drifted off the top
+				// of the very polygon (it might not be exactly horizontal), so try tracking down up to 30 inches to see if we can find it again
+				for (int i = 0; i < 10; i++)
+				{
+					m_col_start[Y] -= 3.0f;
+					m_col_end[Y] -= 3.0f;
+					collision = get_member_feeler_collision(0, mFD_VERT);  
+					if (collision)
+					{
+						// need to store new m_vert_pos
+						// as code below does not generally allow us to go below it
+						m_vert_pos[Y] = m_feeler.GetPoint()[Y];
+						break;
+					}
+				}
+				
+			}
+	
+	
+			// Dot product in the XZ plane is the angle between them			
+			float change_dot = sqrtf(
+				m_vert_normal[X] * m_feeler.GetNormal()[X] + m_vert_normal[Z] * m_feeler.GetNormal()[Z]
+			);
+			
+			// the dot check is a fix for sharp corners, nearly are right angles, TT#438
+			if (collision && change_dot > 0.02f)
+			{
+				// let's just track it simple for now
+				Mth::Vector track_point = m_feeler.GetPoint();
+	
+				// This is a bit of a patch
+				// basically clamp the tracking point at the hihgest level it has reached, that way we can never "slip" down a slope
+				// which the rest of the physics makes you do, for reasons best explained with a diagram
+				if (m_vert_pos[Y] > track_point[Y])  
+				{
+					track_point[Y] = m_vert_pos[Y];
+				}
+				
+				// keep vert pos updated (only used for height)
+				m_vert_pos = track_point;
+				
+				GetPos()[X] = track_point[X];	
+				GetPos()[Z] = track_point[Z];	
+				m_vert_normal = m_feeler.GetNormal();
+	
+				// offset the jumper away from the plane
+				GetPos() += m_vert_normal * (GetPhysicsFloat(CRCD(0x78384871, "Physics_Vert_Push_Out")));
+				DUMP_POSITION;
+				
+				// The normal we might be tracking might not be vert, so need to adjust it so it is fully horizontal
+				// it should never be fully horizontal (which would cause it to implode									
+				Mth::Vector	flat_normal = m_vert_normal;
+				flat_normal[Y] = 0.0f;
+				flat_normal.Normalize();
+				
+				// adjust the orientation to match the plane
+				new_normal(flat_normal);
+				
+				// now velocity
+				GetVel().RotateToPlane(flat_normal);
+			} 
+			else
+			{
+				SetFlagFalse(TRACKING_VERT);
+			}
+		}
+	}
+	
+	//
+	// End of updating tracking
+	////////////////////////////////////////////////////////////////////////////////////////	
+    
+	///////////////////////////////////////////////////////
+	// Adjust the normal while in the air	
+	//
+	
+	// When on an air poly, we have the usual drifting of the UP vector to smooth out changes in the normal
+	// (note, we can be in VERT_AIR, but also SPINE_PHYSICS, in whcih case the orientation is controled by the "lean" routine
+	// which is leaning the skater forward, so he comes down on the opposing face)
+	if (GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS)
+		&& !mp_rotate_component->IsApplyingRotation(X) && !mp_rotate_component->IsApplyingRotation(Z))
+	{
+		adjust_normal();
+	}
+	else
+	{
+		// otherwise, the matrix is same for display and physics
+		ResetLerpingMatrix();
+	}
+	
+	//
+	// End of adjusting normal
+	//////////////////////////////////////////////////////////
+
+
+	///////////////////////////////////////////////////////////
+	// Handle any collisions resultant from our movement
+	// remember at this point m_pos is the new position
+	// and we might have gone through a wall
+	
+	// here again, we are factoring in the contact_movement to the start position											 
+	// We check if there is anything in font of us
+	bool snapped_up = handle_forward_collision_in_air(GetOldPos() + contact_movement);					
+
+	// If we are now in the WALL (Wallride) state, then just return	
+	if (GetState() == WALL || GetState() == WALLPLANT)
+	{
+		// Maybe call DoWallPhysics() to avoid a glitch? Maybe not, cos GetOldPos() will be wrong ?
+		return;
+	}	
+	
+	// check for actual movement collision (along the full line of travel)
+	// note we factor in the contact_movement here which effectivly ignores it
+	// so contact movement could possibly drag us through a solid object
+	// if this is a problem, then we need to add another collision check, just for the contact movement
+	m_col_start = GetOldPos() + contact_movement;
+	m_col_end = GetPos();
+								
+	if (!snapped_up && get_member_feeler_collision())
+	{
+		bool hit_vertical = m_feeler.GetNormal()[Y] < 0.1f;
+		
+		if (check_for_air_snap_up(GetOldPos() + contact_movement) && (GetVel()[Y] > 10.0f || hit_vertical) && mp_movable_contact_component->GetTimeSinceLastLostContact() > 500)
+		{
+		}
+		else
+		{
+			Mth::Vector normal = m_feeler.GetNormal();
+				
+			// set our position (the point of contact) to be that point we just found
+			GetPos() = m_feeler.GetPoint();
+			
+			// Move point up one inch to avoid dropping through geometry with something below it (like canada blades, and park editor stuff)
+			GetPos() += normal;
+			DUMP_POSITION;
+			
+			if (m_col_flag_skatable)
+			{
+				//////////////////////////////////////////////////////////////////////////
+				// handle landing on the ground
+				// Collided with a skatable face!! yay, let's stick to it!
+				
+				SetState(GROUND);
+				check_movable_contact();
+				
+				// Used by the LandedFromVert script command.
+				// landing from a spine transfer is considered to be the same as landing from vert
+				if (GetFlag(VERT_AIR) || GetFlag(SPINE_PHYSICS))
+				{
+					// Note: Not setting it to false if the VERT_AIR flag is not set.
+					// This is because sometimes Scott wants to force the mLandedFromVert to be on from in script. Don't want landing to override that.
+					// mLandedFromVert never gets reset by the C-code, only by the ResetLandedFromVert script command.
+					mLandedFromVert = true;
+					
+	 				// This flag however cannot be cleared by script
+					// Added this flag for use by ClearPanel_Landed, because mLandedFromVert is false at that point even if landing from vert,
+					// due to being cleared by script.
+					m_true_landed_from_vert = true;
+				}
+				else
+				{
+					m_true_landed_from_vert = false;
+				}
+					
+				mLandedFromSpine = GetFlag(SPINE_PHYSICS);
+
+				set_terrain(m_feeler.GetTerrain());
+				
+				mp_sound_component->PlayLandSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
+
+				if (!mp_trick_component->GraffitiTrickStarted())
+				{
+					// clear the graffiti trick buffer if we're moving to a new world sector, but haven't started our trick yet...
+					mp_trick_component->SetGraffitiTrickStarted(false);
+				}
+
+				set_last_ground_feeler(m_feeler);
+				mp_trigger_component->CheckFeelerForTrigger(TRIGGER_LAND_ON, m_last_ground_feeler);
+
+				// Now, we landed, and triggered an event, which might have reset us, so we should possibly abort here if we were restarted
+				if (mp_physics_control_component->HaveBeenReset())
+				{
+					// K: I added this here to be consistent with the above change on 7 Mar (see above comment)
+					// Need it though?
+					FLAGEXCEPTION(CRCD(0x532b16ef, "Landed"));
+					return;
+				}
+				
+				
+				if (!m_true_landed_from_vert 			// not if coming down off vert
+					&& !mLandedFromVert				// and not pretending to (like jumping out of a lip trick)
+					&& GetVel()[X] == 0.0f
+					&& GetVel()[Z] == 0.0f 
+					&& control_pad.m_down.GetPressed())
+				{
+					// we had just jumped straight up, and are braking, and not on vert
+					GetVel()[Y] = 0.0f;	
+					DUMP_VELOCITY;
+				}
+				else
+				{
+					if (!GetFlag(SPINE_PHYSICS))
+					{
+						GetVel().ProjectToPlane(m_feeler.GetNormal());	   	// kill vel that is perpendicular to normal
+						DUMP_VELOCITY;
+					}
+					else
+					{
+						// special landing from transfers to prevent speed loss
+						
+						Mth::Vector landing_vel = GetVel();
+						
+						// rotate all velocity to the facing direction
+						GetVel().RotateToNormal(m_transfer_goal_facing);
+						DUMP_VELOCITY;
+						
+						// now, rotate into the plane instead of projecting (actually, m_transfer_goal_facing should already be in the plane)
+						GetVel().RotateToPlane(m_feeler.GetNormal());
+						DUMP_VELOCITY;
+						
+						// test what velocity we will have once the ground physics removes sideways velocity
+						Mth::Vector test_vel = GetVel();
+						remove_sideways_velocity(test_vel);
+						
+						// if we'd be moving upwards (this can occur because sometimes an acid drop's goal facing is along a vert, not down it; and thus,
+						// a slight upwards rotation means that the skater will land pointing up and immediately hop off the vert; this looks very
+						// bizzare, so we prevent it here)
+						if (test_vel[Y] > 0.0f)
+						{
+							// use a standard landing instead
+							GetVel() = landing_vel;
+							GetVel().ProjectToPlane(m_feeler.GetNormal());
+							DUMP_VELOCITY;
+						}
+						
+						if (GetVel().LengthSqr() < Mth::Sqr(Script::GetFloat(CRCD(0x59484878, "Physics_Acid_Drop_Min_Land_Speed"))))
+						{
+							GetVel().Normalize(Script::GetFloat(CRCD(0x59484878, "Physics_Acid_Drop_Min_Land_Speed")));
+						}
+					}
+					GetVel().ZeroIfShorterThan(10.0f);
+				}
+				
+				SetFlagFalse(VERT_AIR);
+				
+				m_display_normal = m_feeler.GetNormal();
+				m_current_normal = m_display_normal;
+				m_last_display_normal = m_display_normal;
+				
+				GetMatrix()[Y] = m_display_normal;
+				GetMatrix().OrthoNormalizeAbout(Y);   
+				ResetLerpingMatrix();
+
+				// Flagging the exceptions needs to be the last thing done, as the script for the
+				// excpetion is executed immediately, and might need some of the above values
+				// Like, PitchGreaterThan requires m_last_display_matrix to be correct (as calculated above)				
+				
+				FLAGEXCEPTION(CRCD(0x532b16ef, "Landed"));
+				
+				return;
+				 	
+				//
+				// end of handling landing
+				//////////////////////////////////////////////////////////////
+			}
+			else
+			{
+				// it's a wall, bounce off it
+				GetVel().RotateToPlane(normal);			
+				DUMP_VELOCITY;
+				GetMatrix()[Z].RotateToPlane(normal);
+				GetMatrix().OrthoNormalizeAbout(Z);
+				ResetLerpingMatrix();
+							
+				// Bit of a patach here to move the skater away from the wall
+				GetPos() += normal * GetPhysicsFloat(CRCD(0x23410c14, "Skater_Min_Distance_To_Wall"));
+				DUMP_POSITION;
+				
+				// no acid drops after an in-air collision
+				SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
+				
+			}
+		}
+	}
+	else
+	{
+		// No forward collision was found, so nothing to do   
+		
+		// so do another check to push me away from walls, will also need a wallride check here 
+		#ifdef		STICKY_WALLRIDES
+		if (!GetFlag(VERT_AIR))
+		{
+			// check if we are too close to a wall, and pop us out and away		
+			check_side_collisions();
+		}
+		#endif
+	}
+	
+	// Push skater down from any roof he might now be under			
+	if (handle_upward_collision_in_air())
+	{
+		// no acid drops after an in-air collision
+		SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
+	}
+	
+	//
+	// End of handling collisions
+	/////////////////////////////////////////////////////////////////////////////////////////////////
+	
+	// If we've triggered a jump, then do the jump (note, same as jump from ground for now, might want to make it different) 	
+	maybe_flag_ollie_exception();
+	
+	// if in standard vanilla air, check for acid drop
+    if (!GetFlag(AIR_ACID_DROP_DISALLOWED) && BREAK_SPINE_BUTTONS)
+	{
+		bool count_as_skate_off_edge = m_went_airborne_time > m_last_jump_time_stamp
+			&& Tmr::ElapsedTime(m_went_airborne_time) < 250;
+		
+		SAcidDropData acid_drop_data;
+		if (maybe_acid_drop(count_as_skate_off_edge, GetPos(), GetOldPos(), GetVel(), acid_drop_data))
+		{
+			enter_acid_drop(acid_drop_data);
+		}
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_air_rotation (   )
+{
+	CControlPad& control_pad = mp_input_component->GetControlPad();		 
+
+	//////////////////////////////////////////////////////////////////////////////////
+	// Part 1 - Decide on what spin to do, based on the controls
+
+	if (mNoSpin) return;
+	
+	// IF doing a spine transfer, then no auto turning....	
+	if (GetFlag(SPINE_PHYSICS))
+	{
+		SetFlagFalse(AUTOTURN);
+	}
+	
+	int time = 0; 			
+	float rot = 0.0f;
+	float no_time = 0.0f;
+	float ramp_time = 0.0f;
+
+	// if (!USE_BIKE_PHYSICS)
+	// {
+		if (m_spin_taps)
+		{
+			if (control_pad.m_L1.GetTriggered())
+			{
+				control_pad.m_L1.ClearTrigger();
+				if (!control_pad.m_R1.GetPressed())	 	// ignore it if R1 is pressed
+				{
+					m_tap_turns += Mth::PI;
+				}
+			}
+			if (control_pad.m_R1.GetTriggered())
+			{
+				control_pad.m_R1.ClearTrigger();
+				if (!control_pad.m_L1.GetPressed())	 	// ignore it if L1 is pressed
+				{
+					m_tap_turns -= Mth::PI;
+				}
+			}
+		
+			if (m_tap_turns != 0.0f)
+			{
+				time = 1;
+				float turn_amount = Mth::Sgn(m_tap_turns) * GetSkater()->GetScriptedStat(CRCD(0xf7a2acc1, "Physics_air_tap_turn_speed_stat"));
+				if (Mth::Abs(turn_amount * m_frame_length) > Mth::Abs(m_tap_turns))
+				{
+					m_tap_turns = 0.0f;
+				}
+				else
+				{
+					m_tap_turns -= turn_amount * m_frame_length;
+				}	
+				rot = turn_amount;
+			}		
+		}
+		else
+		{
+			if (control_pad.m_L1.GetPressed() && !control_pad.m_R1.GetPressed())
+			{
+				rot = GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));	
+				time = 1;
+				if (control_pad.m_L1.GetPressedTime() > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
+				{
+					SetFlagFalse(AUTOTURN);
+				}
+			}								
+			
+			if (control_pad.m_R1.GetPressed() && !control_pad.m_L1.GetPressed())
+			{
+				rot = -GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));
+				time = 1;
+				if (control_pad.m_R1.GetPressedTime() > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
+				{
+					SetFlagFalse(AUTOTURN);
+				}
+			}								
+		}
+	// }
+	
+	// if just transitioned from walking, ignore Left/Right for rotation purposes
+	if (!GetFlag(NO_ORIENTATION_CONTROL))
+	{
+		// if time is set, then we just ignore the Left/Right button and take no account of the spinning ramp
+		if (!time)
+		{
+			if (control_pad.m_left.GetPressed())
+			{
+				rot = GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));	
+				time = control_pad.m_left.GetPressedTime();
+				if (time > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
+				{
+					SetFlagFalse(AUTOTURN);
+				}
+			}
+		
+			if (control_pad.m_right.GetPressed())
+			{
+				rot = -GetSkater()->GetScriptedStat(CRCD(0x4957db43, "Physics_air_rotation_stat"));	
+				time = control_pad.m_right.GetPressedTime();
+				if (time > GetPhysicsFloat(CRCD(0xabdd3395, "skater_autoturn_cancel_time")))
+				{
+					SetFlagFalse(AUTOTURN);
+				}
+			}
+			
+			no_time = GetPhysicsFloat(CRCD(0xa092b2be, "Physics_Air_No_Rotate_Time"));
+			ramp_time = GetPhysicsFloat(CRCD(0x5d756349, "Physics_Air_Ramp_Rotate_Time"));
+		
+			// if just tapped, then no leaning for a while
+			if (time <= no_time)
+			{
+				rot = 0;
+			}
+		
+			// if tapped enough, then ramp up to full speed over a small amount of time		
+			if (time < ramp_time)
+			{
+				rot = rot * (time - no_time) / (ramp_time - no_time);
+			}
+		}
+	}
+	
+	// if we are not rotating by hand, then might want to auto-turn	
+	if (!rot && GetFlag(VERT_AIR) && GetFlag(AUTOTURN))
+	{
+		float angle_from_vert = acosf(Mth::Clamp(GetMatrix()[Z][Y], -1.0f, 1.0f));
+		
+		if (angle_from_vert < Mth::DegToRad(GetPhysicsFloat(CRCD(0x61224f2e, "skater_autoturn_vert_angle"))))
+		{
+			// If pointing more or less straight up, then don't auto-turn
+			SetFlagFalse(AUTOTURN);				
+		}
+		else
+		{
+			float angle = Mth::GetAngleAbout(GetMatrix()[Z], m_fall_line, GetMatrix()[Y]);
+							
+			rot = Mth::Sgn(angle) * GetPhysicsFloat(CRCD(0x9db33213, "Skater_autoturn_speed"));
+			if (Mth::Abs(rot * m_frame_length) > Mth::Abs(angle))
+			{
+				// just about done, so just turn the last bit of angle left
+				rot = angle / m_frame_length;
+				SetFlagFalse(AUTOTURN);
+			}
+			#if 0 // Dan: removed as this code has no effect
+			time = 1;	  		// frig, we really don't want to start auto turn immediately
+			ramp_time = 0;
+			#endif
+		}
+	}
+	
+	// End of Part 1
+	//////////////////////////////////////////////////////////////////////////////////
+	
+	if (rot == 0.0f) return;
+	
+	//////////////////////////////////////////////////////////////////////////////////
+	// Part 2 - Do the actual rotation
+
+	mYAngleIncreased = rot > 0.0f;
+			
+	rot *= m_frame_length;
+	GetMatrix().RotateYLocal(rot);				
+	m_lerping_display_matrix.RotateYLocal(rot);				
+	
+	// End of Part 2
+	//////////////////////////////////////////////////////////////////////////////////
+		
+							   
+	//////////////////////////////////////////////////////////////////////////////////
+	// Part 3 - tracking rotation for the purposes of score (Spin multipliers)								   
+	// Keep track of the total angle spun through.
+	
+	mp_trick_component->mTallyAngles += Mth::RadToDeg(rot);
+	
+	// Set the spin.
+	if (GetFlag(VERT_AIR) && !GetFlag(SPINE_PHYSICS))
+	{
+		// If in vert air, only count the spin if it is at least 360, because getting 180 is too easy.
+		if (Mth::Abs(mp_trick_component->mTallyAngles) >= 360.0f - GetPhysicsFloat(CRCD(0x50c5cc2f, "spin_count_slop")) + 0.1f)
+		{
+			mp_score_component->GetScore()->UpdateSpin(mp_trick_component->mTallyAngles);
+		}	
+	}
+	else
+	{
+		mp_score_component->GetScore()->UpdateSpin(mp_trick_component->mTallyAngles);
+	}	
+	
+	// End of Part 3
+	//////////////////////////////////////////////////////////////////////////////////
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_air_lean (   )
+{						
+	// Handle leaning formward and backwards in the air
+												 
+	//////////////////////////////////////////////////////////////////////////////////
+	// Spine specific lean, should be seperated out for spine physics
+	// we automatically rotate forward but don't let skaters's up vector to go past horizontal 
+	
+	// no leaning during transfers; we slerp instead
+	if (GetFlag(SPINE_PHYSICS) || GetFlag(NO_ORIENTATION_CONTROL)) return;
+	
+	#if 0 // old transfer code
+	if (GetFlag(SPINE_PHYSICS))
+	{
+		if (GetMatrix()[Y][Y] > -0.05f)	   // if y component of up vector (the Y vector) is +ve, then we are heads up
+		{
+			float rot = GetSkater()->GetScriptedStat(CRCD(0x110a1742, "Physics_spine_lean_stat"));
+				
+			GetMatrix().Rotate(m_spine_rotate_axis, -rot*m_frame_length);
+		}
+		return;
+	}
+	#endif
+	
+	//
+	//////////////////////////////////////////////////////////////////////////////////
+	
+	///////////////////////////////////////////////////////////////////////////////////
+	// 1 - Control Logic	
+	//
+	
+	// don't even try to lean if in VERT_AIR		  
+	if (GetFlag(VERT_AIR)) return;
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	// Don't want to lean if "grab" or "kick" pressed, as we could be doing some kind of grab trick
+	if (control_pad.m_circle.GetPressed() || control_pad.m_square.GetPressed()) return;
+	
+	float rot = 0.0f;
+	int time = 0;
+	
+	if (control_pad.m_up.GetPressed())
+	{
+		rot = GetSkater()->GetScriptedStat(CRCD(0xcbfdd841, "Physics_air_lean_stat"));
+		time = control_pad.m_up.GetPressedTime();
+	}
+
+	if (control_pad.m_down.GetPressed())
+	{
+		rot = -GetSkater()->GetScriptedStat(CRCD(0xcbfdd841, "Physics_air_lean_stat"));	
+		time = control_pad.m_down.GetPressedTime();
+	}
+
+	float no_time = GetPhysicsFloat(CRCD(0x5f70ef46, "Physics_Air_No_Lean_Time"));
+	float ramp_time = GetPhysicsFloat(CRCD(0x12cd2d14, "Physics_Air_Ramp_Lean_Time"));
+
+	// if just tapped, then no rotation for a while
+	if (time <= no_time) return;
+	
+	// if tapped enough, then ramp up to full speed over a small amount of time		
+	if (time < ramp_time)
+	{
+		rot = rot * (time - no_time) / (ramp_time - no_time);
+	}
+
+	// end of control logic
+	//////////////////////////////////////////////////////////////////////////////////
+	
+
+	//////////////////////////////////////////////////////////////////////////////////
+	// air lean physics
+	
+	if (rot == 0.0f) return;
+	
+	GetMatrix().RotateXLocal(rot*m_frame_length);				
+	m_lerping_display_matrix.RotateXLocal(rot*m_frame_length);				
+	
+	// end of air lean physics
+	//////////////////////////////////////////////////////////////////////////////////
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_transfer_slerping (   )
+{
+	// during transfers, we slerp to our required orientation
+	if (!GetFlag(SPINE_PHYSICS)) return;
+	
+	// we can't simply use the slerp's result vector at the skater's vector, because we need to allow the skater to rotate; instead, we apply
+	// the change in the result vector each frame
+	
+	// increment the timer
+	m_transfer_slerp_timer += m_frame_length;
+	
+	// calculate this frame's slerp result
+	Mth::Matrix transfer_slerp_current_matrix;
+	m_transfer_slerper.getMatrix(
+		&transfer_slerp_current_matrix,
+		Mth::SmoothStep(Mth::ClampMax(m_transfer_slerp_timer / m_transfer_slerp_duration, 1.0f))
+	);
+	
+	// invert the slerp result vector from last frame
+	Mth::Matrix inverse_transfer_slerp_previous_matrix = m_transfer_slerp_previous_matrix;
+	inverse_transfer_slerp_previous_matrix.InvertUniform();
+	
+	// calculate the change between the frames
+	Mth::Matrix transfer_slerp_delta_matrix = inverse_transfer_slerp_previous_matrix * transfer_slerp_current_matrix;
+	
+	// apply the change to the skater's matrix
+	GetMatrix() *= transfer_slerp_delta_matrix;
+	ResetLerpingMatrix();
+	
+	// store this frame's matrix
+	m_transfer_slerp_previous_matrix = transfer_slerp_current_matrix;
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_air_vert_recovery (   )
+{						   
+	// recovering from going off the end of a vert polygon	
+
+	// don't want to recover during a transfer
+	if (GetFlag(SPINE_PHYSICS)) return;
+
+	if (GetVel()[Y] > 0.0f && mp_movable_contact_component->GetContact()) return;
+	
+	// right, we are in vert, but no longer tracking, so perhaps we are over flat ground?
+	CFeeler	feeler;
+	feeler.m_start = GetPos();
+	feeler.m_end = GetPos();
+	feeler.m_end[Y] -= 500.0f;
+	
+	// can't find ground
+	if (!feeler.GetCollision()) return;
+
+	// still over vert poly
+	if (feeler.GetFlags() & mFD_VERT) return;
+	
+	// still over steep ground
+	if (feeler.GetNormal()[Y] < 0.2f) return;
+
+	if (GetFlag(VERT_AIR))
+	{
+		SetFlagFalse(VERT_AIR);
+	}
+
+	SetFlagTrue(IN_RECOVERY);
+
+	// we want to smoothly rotate the skater in the plane formed by the up vector, and the skater's up vector
+	
+	// got sufficently far up 
+	if (GetMatrix()[Y][Y] > 0.9f) return;
+	
+	float rot = GetSkater()->GetScriptedStat(CRCD(0xe7116a96,"Physics_recover_rate_stat"));	
+	
+	// need to rotate the Y vector in the plane formed by the XZ velocity and the (0,1,0) up vector
+	// (or rather, rotate it about the vector prependicular to this
+	
+	// get a vector perpendicular to the plane containing m_matrix[Z] and the world up 	
+	Mth::Vector forward = GetMatrix()[Y];
+	forward[Y] = 0.0f;
+	forward.Normalize();
+	#if 0 // old code
+	// Mth::Vector world_up(0.0f,1.0f,0.0f);
+	// Mth::Vector side = Mth::CrossProduct(forward,world_up);
+	#else
+	Mth::Vector side(-forward[Z], 0.0f, forward[X], 0.0f);
+	#endif
+	
+	GetMatrix().Rotate(side, rot * m_frame_length);
+	
+	ResetLerpingMatrix();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::rotate_upright (   )
+{
+	// Attempt to upright player (sideways) while going through air
+	//
+	// Cannot just check angle between mUp and world_up as that would include
+	// tilting forward/backward when character is already uprighted sideways
+	// (and tilting forward/backward should solely be under player control).
+	// Thus, should rotate about mFront only.
+	//
+	// Then, basically what we want to do is:
+	// 1) Rotate about mFront so that mUp is in the plane of world_up and mUp.
+	// 2) Pick one of CW/CCW to rotate by, such that angle between mUp and
+	//    world_up is <= 90 degrees.
+	//
+	// As we can be off by at most 180 degrees, rotating by 5 degrees would
+	// upright us in 180/5=36 frames. For 45 degrees off (more likely), we
+	// would be uprighted in 45/5=9 frames.
+	//
+	// Your average jump is probably around 30 frames long, so try 1 degree?
+	
+	// If the skater's Z vector is nearly straight up or down, then 
+	// uprighting sideways will not look good 
+	// (On XBox, some innacuracy in Z causes the skater to rotate oddly during "drop-in" situations)
+	// so, here we test for this case, and just return if so
+	if (Mth::Abs(GetMatrix()[Z][Y]) > 0.95f) return;
+	
+	// get a vector perpendicular to the plane containing m_matrix[Z] and the world up 	
+	#if 0 // old code
+	// Mth::Vector v;
+	// Mth::Vector world_up;
+	// world_up.Set(0.0f,1.0f,0.0f);
+	// v = Mth::CrossProduct(GetMatrix()[Z],world_up);
+	#else
+	Mth::Vector v(-GetMatrix()[Z][Z], 0.0f, GetMatrix()[Z][X], 0.0f);
+	#endif
+
+	// Then get the dot product of the up vector with this 	
+	// if up vector is in the same plane, then this will be zero
+	float dot = Mth::DotProduct(GetMatrix()[Y], v);
+	if (dot > 0.02f * m_frame_length * 60.0f) // prevent wobbling
+	{
+		float rot = Mth::DegToRad(Script::GetFloat(CRCD(0xabd57877, "skater_upright_sideways_speed")));
+		GetMatrix().RotateZLocal(rot * m_frame_length);				
+		ResetLerpingMatrix();
+	}
+	else if (dot < -0.02f * m_frame_length * 60.0f)
+	{
+		float rot = Mth::DegToRad(Script::GetFloat(CRCD(0xabd57877, "skater_upright_sideways_speed")));
+		GetMatrix().RotateZLocal(-rot * m_frame_length);				
+		ResetLerpingMatrix();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::handle_forward_collision_in_air ( const Mth::Vector &start_pos )
+{
+	// return true if there was a collision, but we snapped up over it
+	
+	Mth::Vector	forward = GetPos() - start_pos;
+	forward.Normalize();	
+	
+	Mth::Vector up_offset = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xd4205c9b, "Skater_First_Forward_Collision_Height"));
+	
+	m_col_start = start_pos + up_offset;
+	m_col_end = GetPos() + up_offset + forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
+	
+	if (get_member_feeler_collision())
+	{
+		// we have hit something going forward
+		// either it is a wall, and we need to bounce off it
+		// or it is a floor, and we need to stick to it.
+							 
+		Mth::Vector normal = m_feeler.GetNormal();
+
+		float dot = Mth::DotProduct(normal, m_current_normal);
+		if (!m_col_flag_skatable || (dot < 0.8f && normal[Y] < 0.5f))
+		{
+
+		   // at this point we have hit something that is not skatable and is fairly vertical
+		   // so we are considering if we want to bounce off it
+		   // one possibility is that it is a low ledge
+		   //
+		   // so, we check for snapping up, and if we do snap up, and hit_vertical is true, then we don't bounce off the wall
+			
+			/*
+			// View from side
+									   /
+									  /	 <---------- Movement of skater
+									 /
+									/
+				---------------+   /
+							   |  /
+							   | /
+							   |/
+							   /  <-------------  point of collision 
+							  /|
+							 / |
+							/  |
+						   /   |
+						  /    +---------------	 <-------------- The ground
+						 /
+						/
+					   /
+					  /
+					 /	<------------ where we end up
+			*/
+		   
+			// Need to store m_feeler, as it is corrupted by CheckForAirSnapUp
+			CFeeler tmp_feeler = m_feeler;
+			
+			bool hit_vertical = m_feeler.GetNormal()[Y] < 0.1f;
+			
+			// Since this collision had the forward vector added into it, we need to temporarily add in the forward vector again
+			// so we can check for snapping up; note this will result in the skater snapping forward by the length of the forward vector (currently 10")
+			// whenever we snap up in this particular check		    			
+			Mth::Vector	tmp = GetPos();
+			GetPos() = m_col_end - up_offset;					
+			if (check_for_air_snap_up(start_pos))
+			{
+				// we snapped up, so if we are either moving upward or the first collision was near vertical, then we return true, allowing us to continue
+				// if we were moving upwards, then we would continue into the air
+				// if we were moving down, but the collision was near vert, then we do not want to bonk off the surface, and instead we let
+				// the landing happen in the next frame
+				if (GetVel()[Y] > 10.0f || hit_vertical) return true;
+			}
+			
+			GetPos() = tmp;
+			DUMP_POSITION;
+			m_feeler = tmp_feeler;
+
+			#ifdef		SNAP_OVER_THIN_WALLS
+			// we have hit a wall, and we can't snap up over i;  see if we can just move up and go over it....
+
+			// only do this if we are roughly vertical and going up and the wall is very verticle
+			if (GetMatrix()[Y][Y] > 0.5f		   // skater is upright
+			    && GetVel()[Y] > 0.0f			   // skater travelling upwards
+				&& normal[Y] < 0.01f)					   // thing we hit is not far off vert
+			{
+				 
+				float snap_Y = GetPhysicsFloat(CRCD(0x786b3272, "Physics_Air_Snap_Up"));
+				tmp_feeler.m_start[Y] += snap_Y;
+				tmp_feeler.m_end[Y] += snap_Y;
+						   
+				if (!tmp_feeler.GetCollision())
+				{
+					while (snap_Y > 4.0f)
+					{
+						// move down until we get a collision
+						// so as to minimize the amount we snap up
+						tmp_feeler.m_start[Y] -= 2.0f;
+						tmp_feeler.m_end[Y] -= 2.0f;
+						if (tmp_feeler.GetCollision()) break;
+						snap_Y -= 2.0f;
+					}
+					GetPos()[Y] += snap_Y;
+					DUMP_POSITION;
+					return true;
+				}
+			}
+			#endif
+			
+			// If we are on a vert poly, then just slide off the wall
+			// just kill velocity perpendicular to the wall
+			if (GetFlag(VERT_AIR))
+			{
+				GetPos() = m_feeler.GetPoint() - up_offset;
+				GetPos() += normal * GetPhysicsFloat(CRCD(0x23410c14, "Skater_Min_Distance_To_Wall"));				
+				DUMP_POSITION;
+				
+				if (!GetFlag(SPINE_PHYSICS))
+				{
+					GetVel().ProjectToPlane(normal);
+				}
+				else
+				{
+					GetVel().RotateToPlane(m_feeler.GetNormal());
+				}
+				DUMP_VELOCITY;
+				return false;			
+			}
+			
+			// maybe wall plant
+			if (check_for_wallplant()) return false;
+			
+			// maybe wall ride
+			if (check_for_wallride()) return false;
+			
+			// no acid drops after a in-air collision
+			SetFlagTrue(AIR_ACID_DROP_DISALLOWED);
+				
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_BONK, m_feeler);
+			if (mp_physics_control_component->HaveBeenReset()) return false;
+
+			// only play bonk sound for things that are near vert 												  
+			if (m_feeler.GetNormal().GetY() < 0.05f)
+			{
+				mp_sound_component->PlayBonkSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_feeler.GetTerrain());
+			}
+
+			// it's a wall, bounce off it
+			
+			float vel_y = 0.0f;
+
+			// ignore y vel if the polygon is not too much upside down 
+			if (normal[Y] > -0.1f)
+			{
+				vel_y = GetVel()[Y];					// remember old Y vel
+				GetVel()[Y] = 0.0f;					// kill Y vel
+				DUMP_VELOCITY;
+			}
+			
+			GetVel().ProjectToPlane(normal);											// project X and Z to plane of collision poly	
+			GetVel() += normal * (GetVel().Length() * (1.0f / 10.0f));		// 10% tiny push away.....
+			DUMP_VELOCITY;
+			
+			if (normal[Y] > -0.1f)
+			{
+				GetVel()[Y] = vel_y;					// restore old Y vel, so it's impossible to jump higher by jumping against a wall
+				DUMP_VELOCITY;
+			}			
+			
+			float Z_Y = GetMatrix()[Z][Y];
+			GetMatrix()[Z][Y] = 0.0f;
+			GetMatrix()[Z].RotateToPlane(normal);
+			GetMatrix()[Z][Y] = Z_Y;
+			GetMatrix()[Z] += (1.0f / 20.0f) * normal;
+			GetMatrix()[Z].Normalize();			
+			GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
+			GetMatrix()[X].Normalize();
+			GetMatrix()[Y] = Mth::CrossProduct(GetMatrix()[Z], GetMatrix()[X]);
+			GetMatrix()[Y].Normalize();
+			
+			// Bit of a patach here to move the skater away from the wall
+			GetPos() = m_feeler.GetPoint() - up_offset;
+			GetPos() += normal * GetPhysicsFloat(CRCD(0x23410c14, "Skater_Min_Distance_To_Wall"));
+			DUMP_POSITION;
+			
+			// if the thing we have collided with is a movable object, then add in the last movement to move us away 
+			if (m_feeler.IsMovableCollision())
+			{
+				Mth::Vector obj_vel = m_feeler.GetMovingObject()->GetVel();
+
+				// if object is moving in same direction as face normal
+				if (Mth::DotProduct(obj_vel, m_feeler.GetNormal()) > 0.0f)
+				{
+					// then add in the velocity, just to be on the safe side
+					GetPos() += obj_vel * m_frame_length * 2.0f;		// 2.0f is a safety factor
+					DUMP_POSITION;
+					// and move with the thing we just hit
+					GetVel() += obj_vel;					
+					DUMP_VELOCITY;
+				}
+			}
+		}
+	}
+	return false;
+}	 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::check_for_air_snap_up ( const Mth::Vector& start_pos )
+{
+	////////////////////////////////////////////////////////////////////////////////////////
+	// We moved from GetOldPos() to m_pos
+	// and some collision was detected
+	// either along this line, or along the "forward" line, which is 8 inches above
+	// regardless, we want to see if the new m_pos (considered bad)
+	// can be snapped upwards to a surface, where the normal is in the same direction as
+	// the current movement
+	// and check to see if there is a clear line to this position
+	// currently in physics.q:  Physics_Air_Snap_Up = 15       
+	//
+	// Note, we ignore if the skater is going up or down, the calling code has to 
+	// handle that (for the spcial case of hitting a wall coming down, when we can snap over it)
+
+	// don't do it if bailing, to avoid snapping through loops
+	if (GetFlag(IS_BAILING)) return false;
+	
+	CFeeler feeler;
+	
+	float up_len = GetPhysicsFloat(CRCD(0x786b3272,"Physics_Air_Snap_Up"));
+	
+	feeler.m_start = GetPos();
+	feeler.m_start[Y] += up_len;
+	feeler.m_end = GetPos();
+	
+	// Since the new pos might actually be ABOVE the ledge (i.e., we might have just clipped though the corner, and not be inside it)
+	// then we need to check all the way down to the previous height, but only if moving upwards
+	if (start_pos[Y] < GetPos()[Y])
+	{
+		feeler.m_end[Y] = start_pos[Y];		
+	}
+
+	// if the start pos, plus the snap up distance was above the top of the collision line, then extend the collision line up to that	
+	if (start_pos[Y] + up_len > feeler.m_start[Y])
+	{
+		feeler.m_start[Y] = start_pos[Y] + up_len;
+	}
+	
+	// set up the feeler, now check for collision
+	if (feeler.GetCollision())
+	{
+		// Collision, possibly something we can snap up to
+		
+		// movement vector is in same direction as face normal, so we are moving away from it
+		// usually this implies we are moving up, away from the horizontal face at the top of a wall, or a curb
+		if (feeler.GetNormal().GetY() > 0.5f)		// not if too vertical
+		{			
+			// Okay, we can snap up, so all we have to do is see if the line is clear
+			Mth::Vector	snap_point = feeler.GetPoint() + feeler.GetNormal();
+			
+			feeler.m_start = start_pos;
+			feeler.m_start[Y] += up_len;
+			feeler.m_end = snap_point;
+			feeler.m_end[Y] += up_len;
+			
+			if (!feeler.GetCollision())
+			{
+				// No collision along upper line,so we consider it safe to move
+				// note, this will generally result in passing through the corner of some geometry (generally like the top of a wall, or a curb)
+				GetPos() = snap_point;	 		// set to snap point
+				GetPos()[Y] += 0.1f;				// offset up a little, so we are outside the plane
+				DUMP_POSITION;
+				return true;								// return true, indicating we snapped up, so carry on in air
+			}
+			else
+			{
+				// was a collision when trying to move to new position, so don't move
+			}
+		}
+		else
+		{
+			// too vert, so don't allow snap (probably on a QP)
+		}
+
+		return false;
+	}
+	else
+	{
+		// No collision, so don't do anything, probably deep within a wall, or off the level.
+	}
+
+	return false;	 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::handle_upward_collision_in_air (   )
+{
+	// return true if there was a collision, but we snapped up over it
+	
+	// if we are upside down, then return false
+	if (GetMatrix()[Y][Y] < 0.0f) return false;
+	
+	float head_height = GetPhysicsFloat(CRCD(0x542cf0c7, "Skater_default_head_height"));
+	
+	// ignore head collisions for a duration after wallplants
+    if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) <= static_cast< Tmr::Time >(Script::GetInteger(CRCD(0x2757ed2c, "Physics_Ignore_Ceilings_After_Wallplant_Duration"))))
+	{
+		head_height = 6.0f;
+	}
+	
+	Mth::Vector up_offset = GetMatrix()[Y] * head_height;
+	
+	m_col_start = GetPos();
+	
+	m_col_end = GetPos();
+	m_col_end += up_offset;
+	
+	if (get_member_feeler_collision())
+	{
+		Mth::Vector ceiling_normal = m_feeler.GetNormal();
+		
+		// if it's not at least tilted a bit downwards, then ignore it and return false	   
+		if (ceiling_normal[Y] > -0.1f) return false;
+					  
+		// get the vector we need to push down
+		Mth::Vector push_down = m_feeler.GetPoint() - m_col_end;
+		
+		// only push down away from the ceiling	(otherwise we would push back along the line of travel, jerky)
+		push_down.ProjectToNormal(ceiling_normal);
+
+		// push down as far as we can go, so check for collisions		
+		m_col_start = GetPos();
+		m_col_end = GetPos() + push_down;
+		if (get_member_feeler_collision())
+		{
+			GetPos() = m_feeler.GetPoint() + 0.001f * m_feeler.GetNormal();
+			DUMP_POSITION;
+		}
+		else
+		{
+			GetPos() = m_col_end;
+			DUMP_POSITION;
+		}
+		
+		GetVel().ProjectToPlane(ceiling_normal);
+		DUMP_VELOCITY;
+		
+		return true; 	// we have had a collision, so return true
+	}
+	
+	return false;  // no collision found, return false
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::handle_upward_collision_in_wallride (   )
+{
+	m_col_start = GetPos() + 18.0f * mWallNormal;
+	
+	m_col_end = m_col_start;
+	m_col_end[Y] += GetPhysicsFloat(CRCD(0x542cf0c7, "Skater_default_head_height"));
+	
+	if (get_member_feeler_collision())
+	{
+		Mth::Vector ceiling_normal = m_feeler.GetNormal();
+		
+		// if it's not at least tilted a bit downwards, then ignore it and return false	   
+		if (ceiling_normal[Y] > -0.1f) return false;
+					  
+		// get the vector we need to push down
+		Mth::Vector push_down = m_feeler.GetPoint() - m_col_end;
+		
+		// only push down away from the ceiling	(otherwise we would push back along the line of travel, jerky)
+		push_down.ProjectToNormal(ceiling_normal);
+
+		// push down as far as we can go, so check for collisions		
+		m_col_start = GetPos();
+		m_col_end = GetPos() + push_down;
+		if (get_member_feeler_collision())
+		{
+			GetPos() = m_feeler.GetPoint() + m_feeler.GetNormal();
+			DUMP_POSITION;
+		}
+		else
+		{
+			GetPos() = m_col_end;
+			DUMP_POSITION;
+		}
+		
+		GetVel().ProjectToPlane(ceiling_normal);
+		DUMP_VELOCITY;
+		
+		return true; 	// we have had a collision, so return true
+	}
+	
+	return false;  // no collision found, return false
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_wallride_physics (   )
+{
+	SetFlagFalse(SPINE_PHYSICS);
+	SetFlagFalse(IN_ACID_DROP);
+	SetFlagFalse(IN_RECOVERY);
+	SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
+
+	// Wallriding will cancel any memory of what rail we were on, so the next one seems fresh	
+	mp_rail_node = NULL;
+	m_last_rail_trigger_node_name = 0;
+	
+
+
+	// Keep updating the time, cos it needs to be the time that the last wall ride finished.
+	mWallrideTime = Tmr::GetTime();
+
+	// set wallride movement to nothing
+	Mth::Vector m_movement(0.0f, 0.0f, 0.0f);
+	
+	// if we are in contact with something, then factor in that "movement"
+	if (mp_movable_contact_component->UpdateContact(GetPos()))
+	{
+		m_movement = mp_movable_contact_component->GetContact()->GetMovement();
+		if (mp_movable_contact_component->GetContact()->IsRotated())
+		{
+			GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
+			m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
+		}
+	}
+
+	// Mick: changed to include gravity
+	// note this is the way THPS2 worked
+	// the physics might not be intuitive, but it works	
+	// also note we are intentionally not removing sideways components of velocity, so we get to drift up a bit
+	// after we hit the wall, allowing us to to the "claw" trick (Jump-WallRide-Jump-Grind)
+	
+	Mth::Vector acc(0.0f, GetPhysicsFloat(CRCD(0xc617caf, "Wall_Ride_Gravity")), 0.0f);
+	GetPos() += GetVel() * m_frame_length + acc * (0.5f * m_frame_length * m_frame_length);
+	GetPos() += m_movement;
+	DUMP_POSITION;
+	GetVel() += acc * m_frame_length;
+	DUMP_VELOCITY;
+			  
+	#if 0 // old code
+	// Mth::Vector Down(0.0f,-1.0f,0.0f);
+	// Mth::Vector Horiz=Mth::CrossProduct(mWallNormal,Down);
+	#else
+	Mth::Vector Horiz(mWallNormal[Z], 0.0f, -mWallNormal[X], 0.0f);
+	#endif
+	Horiz.Normalize();
+	
+	// Down is a unit vector pointing down along the plane of the wall.
+	Mth::Vector Down = Mth::CrossProduct(Horiz, mWallNormal);
+	
+	float Theta = GetPhysicsFloat(CRCD(0x64a48a64, "Wall_Ride_Turn_Speed"));
+	
+	// Check if Theta is bigger than the angle the skater's z is already making with the Down vector.
+	// If Theta is bigger, don't rotate by it, otherwise the skater will turn beyond the vertical.
+	float zdot = Mth::DotProduct(GetMatrix()[Z], Down);
+	if (zdot > 0.68f || (zdot > 0.0f && zdot > cosf(Theta)))
+	{
+		// Pointing pretty much straight down, so don't turn.
+	}
+	else
+	{
+		// Choose which way to turn.
+		// Need to turn one way or the other depending on whether the skater's z axis is on the left or right side of the down vector.
+		// This is the same as whether the skater's x axis is pointing up or down, so just check the sign of the dot product of x with down.
+		float xdot = Mth::DotProduct(GetMatrix()[X], Down);
+		if (xdot <= 0.0f)
+		{
+			Theta = -Theta;
+		}	
+
+		// Not strictly required for NGPS, fixes a problem on NGC. However, given m_vel is used only
+		// as a three-element vector, not a bad idea to set this for all platforms.
+		GetVel()[W] = 1.0f;
+	
+		// Rotate both the skater and the skater's velocity by Theta about the wall normal axis.
+		GetVel().Rotate(mWallNormal, Theta);
+		DUMP_VELOCITY;
+
+		// set skater to face in the same direction as velocity
+		GetMatrix()[Z] = GetVel();
+		
+		GetMatrix()[Z].Normalize();
+		GetMatrix()[Y] = mWallNormal;
+		GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y], GetMatrix()[Z]);
+		GetMatrix()[X].Normalize();
+		ResetLerpingMatrix();
+	}	
+	
+	// Forward collision check	
+	Mth::Vector forward = GetPos() - GetOldPos();
+	forward.Normalize();	
+	
+	m_col_start = GetOldPos();
+	
+	m_col_end = GetPos();
+	m_col_end += forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
+
+	if (get_member_feeler_collision())
+	{
+		// we have hit something going forward
+		// either it is a wall, and we need to bounce off it or it is a steep QP, and we need to stick to it.
+		// (note, with the slow normal changing, then it is possible that we might even collide with the poly that we are on)
+		
+		Mth::Vector	normal = m_feeler.GetNormal();								
+								
+		float dot = Mth::DotProduct(normal, mWallNormal);			
+
+		GetPos() = m_feeler.GetPoint() + normal;
+		DUMP_POSITION;
+
+		// For fairly shallow curves, the dot between two normals will be pretty large
+		// it's very important here to distinguish between a tight curve (like in a narrow QP) and a direct hit with a wall.
+		
+		if (!m_col_flag_wallable || Mth::Abs(dot) < 0.01f)
+		{
+			if (normal[Y] > 0.9f)
+			{
+				// If the new poly is the ground, pop upright and set the landed exception.
+				GetPos() = m_feeler.GetPoint();
+				DUMP_POSITION;
+
+				// Set every orientation type thing I can think of so as to
+				// pop his orientation to that of the ground.				
+				GetMatrix()[Z] = GetVel();
+				GetMatrix()[Z].ProjectToPlane(normal);	 // Mick: project forward vel to ground
+				GetMatrix()[Z].Normalize();
+				GetMatrix()[Y] = normal;				  		
+				GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y],GetMatrix()[Z]);
+				GetMatrix()[X].Normalize();
+				
+				ResetLerpingMatrix();
+				
+				GetSkaterMatrixQueriesComponentFromObject(GetObject())->ResetLatestMatrix();
+				
+				m_last_display_normal = GetMatrix()[Y];
+				m_display_normal = GetMatrix()[Y];
+				m_current_normal = GetMatrix()[Y];
+				
+				// check for sticking to rail first; use the "override_air" to stick to rail)
+				maybe_stick_to_rail(true);
+				if (GetState() != RAIL)
+				{
+					SetState(GROUND);
+					FLAGEXCEPTION(CRCD(0x532b16ef, "Landed"));
+				}				
+				return;
+			}
+			else
+			{
+				new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
+				
+				GetPos() += 18.0f * mWallNormal;
+				DUMP_POSITION;
+			
+				SetState(AIR);
+				GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
+				FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
+
+
+			}	
+		}
+		else
+		{
+			mWallNormal = normal;			// update the normal to the new one...			
+			new_normal(mWallNormal);
+            ResetLerpingMatrix();
+			GetVel().RotateToPlane(mWallNormal);		// make velocity go along it
+			DUMP_VELOCITY;
+		}
+	}
+	
+	if (handle_upward_collision_in_wallride())
+	{
+		new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
+		
+		GetPos() += 18.0f * mWallNormal;
+		DUMP_POSITION;
+
+		SetState(AIR);
+		GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
+		FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
+	}
+
+	// Downwards collision check (down in direction of skater's y)
+	
+	m_col_start = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up"));
+	m_col_start += GetPos();
+	
+	m_col_end = GetMatrix()[Y] * GetPhysicsFloat(CRCD(0x438ba168, "Wall_Ride_Down_Collision_Check_Length"));
+	m_col_end += GetPos();
+	
+	if (get_member_feeler_collision())
+	{
+		mWallNormal=m_feeler.GetNormal();
+
+ 		// Snap position to wall.
+		GetPos() = m_feeler.GetPoint();
+		// but one inch out from it
+		GetPos() += mWallNormal;
+		DUMP_POSITION;
+			
+		// Rotate velocity to plane.
+		GetVel().RotateToPlane(mWallNormal);
+		DUMP_VELOCITY;
+			
+		// Snap skater's orientation to the plane.
+		new_normal(mWallNormal);
+		ResetLerpingMatrix();
+		
+		if (!m_col_flag_wallable)
+		{
+			GetVel() += mWallNormal * 100.0f;		// boost away from the wall
+			DUMP_VELOCITY;
+			
+			new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
+            ResetLerpingMatrix();
+			SetState(AIR);
+			
+			GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
+			FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
+			
+			if (mp_physics_control_component->HaveBeenReset()) return;
+		}
+		else
+		{
+			// handle wallride transition from one object to the next
+			if (m_feeler.GetSector() != m_last_ground_feeler.GetSector())
+			{
+				mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
+				if (mp_physics_control_component->HaveBeenReset()) return;
+				mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_ONTO, m_last_ground_feeler);
+				if (mp_physics_control_component->HaveBeenReset()) return;
+			}
+			if (!mp_trick_component->GraffitiTrickStarted())
+			{
+				// clear the graffiti trick buffer if we're moving to a new world sector, but haven't started our trick yet...
+				mp_trick_component->SetGraffitiTrickStarted(false);
+			}
+
+			set_last_ground_feeler(m_feeler);
+		}
+	}
+	else
+	{
+		new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
+		ResetLerpingMatrix();
+		GetPos() += 18.0f * mWallNormal;
+		DUMP_POSITION;
+		SetState(AIR);
+		GetObject()->BroadcastEvent(CRCD(0xd96f01f1, "SkaterOffEdge"));
+		FLAGEXCEPTION(CRCD(0x3b1001b6, "GroundGone"));
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_SKATE_OFF_EDGE, m_last_ground_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return;
+	}
+	
+	// look for head collisions
+	
+	
+	// This was cut-and-past'd from DoOnGroundPhysics
+	handle_tensing();
+	
+	if (maybe_flag_ollie_exception())
+	{
+		mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+		if (mp_physics_control_component->HaveBeenReset()) return;
+		
+		GetVel() += GetPhysicsFloat(CRCD(0x349d02d4, "Wall_Ride_Jump_Out_Speed")) * mWallNormal;
+		GetVel()[Y] += GetPhysicsFloat(CRCD(0x5a9de35a, "Wall_Ride_Jump_Up_Speed"));
+		DUMP_VELOCITY;
+		
+		// Pop the skater upright again immediately.
+		GetMatrix()[Y].Set(0.0f, 1.0f, 0.0);
+		GetMatrix().OrthoNormalizeAbout(Y);
+		ResetLerpingMatrix();
+
+		// and immediately adjust the normal, to prevent single frame snaps when walliing and the 90 degree swoop when wallride to grind
+		m_normal_lerp = 0.0f;
+		m_display_normal = GetMatrix()[Y];
+		m_current_normal  = m_display_normal;
+		m_last_display_normal  = m_display_normal;
+	}
+	
+	// if we've already started doing a trick, then start remembering our trick chain
+	// GJ:  It's possible to do a wallride while mNumTricksInCombo == 0, for some reason
+	mp_trick_component->TrickOffObject(m_last_ground_feeler.GetNodeChecksum());
+			
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0x3ae85eef, "skater_trails")))
+	{
+		Gfx::AddDebugLine(GetPos() + m_current_normal, GetOldPos() + m_current_normal, PURPLE, 0, 0);
+	}
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_wallplant_physics (   )
+{
+	// check if the wallplant duration is up
+	if (Tmr::ElapsedTime(m_state_change_timestamp) > Script::GetFloat(CRCD(0xa06e446b, "Physics_Wallplant_Duration")))
+	{
+		SetState(AIR);
+		return;
+	}
+
+	// Wallplanting will cancel any memory of what rail we were on, so the next one seems fresh	
+	mp_rail_node = NULL;
+	m_last_rail_trigger_node_name = 0;
+
+	
+	// during a wallplant, our velocity is set to the exit velocity, but we simply ignore it
+	
+	// the only movement comes from our moving contact; we assume that this movement does not lead to collisions
+	if (mp_movable_contact_component->UpdateContact(GetPos()))
+	{
+		GetPos() += mp_movable_contact_component->GetContact()->GetMovement();
+		DUMP_POSITION;
+		if (mp_movable_contact_component->GetContact()->IsRotated())
+		{
+			GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
+			m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::maybe_stick_to_rail ( bool override_air )
+{
+	// Only alow them to grind if we are in the air, that way we will avoid nasty problem with snapping through geometry onto rails 
+	if (GetState() != AIR && !override_air) return;
+
+	// K: Don't allow a grind trick if bailing, otherwise you can late-trick into a liptrick, bail, get snapped into a grind, & hence bail again.
+	if (GetFlag(IS_BAILING)) return;
+		
+	// K: No grinds or lip tricks are allowed if this flag is set. This is switched on and off by the NoGrind and AllowGrind script commands.
+	// These are used when jumping out of a lip trick to disallow going straight into a grind.
+	if (mNoRailTricks) return;
+
+	if (!mp_input_component->GetControlPad().m_triangle.GetPressed())
+	{
+		// Mick: not sure why this was removed
+		// SetFlagTrue(CAN_RERAIL);
+		return;
+	}
+	
+	// don't grind for a short duration after a wallplant
+	if (Tmr::ElapsedTime(m_last_wallplant_time_stamp) < static_cast< Tmr::Time >(Script::GetInteger(CRCD(0x96dca7dc,"Physics_Wallplant_Disallow_Grind_Duration")))) return;
+
+	Mth::Vector a = GetOldPos();
+	Mth::Vector b = GetPos();
+	
+	// if we were on a rail recently, and in the park editor, then don't let us snap to rails that are very perpendicular to us for a while
+	float min_dot;
+	if (Ed::CParkEditor::Instance()->UsingCustomPark() && Tmr::ElapsedTime(m_rail_time) < m_rerail_time)
+	{
+		// Should only do this if we've recently been on a rail
+		min_dot = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle"))));
+	}
+	else
+	{
+		min_dot = 1.0f;
+	}
+
+	// eventually we should get the rail manager base on whatever rail we are on
+	CRailManager* p_rail_man = Mdl::Skate::Instance()->GetRailManager();
+		
+	// first check the world rail manager (rails that do not move)
+	CRailNode* pNode;		
+	Mth::Vector rail_pos;
+	if (p_rail_man->StickToRail(a, b, &rail_pos, &pNode, NULL, min_dot))
+	{
+		TrackingLine(2,GetOldPos(), GetPos());	  // 2 = stick to rail
+		
+		mp_movable_contact_component->LoseAnyContact();
+		if (will_take_rail(pNode, p_rail_man))
+		{
+			got_rail(rail_pos, pNode, p_rail_man);
+		}
+		return;
+	}
+	
+	// iterate through all rail manager components, starting with the first one
+	for (CRailManagerComponent* p_railmanager_component = static_cast< CRailManagerComponent* >(CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_RAILMANAGER));
+		p_railmanager_component;
+		p_railmanager_component = static_cast< CRailManagerComponent* >(p_railmanager_component->GetNextSameType()))
+	{
+		CRailManager* p_rail_man = p_railmanager_component->GetRailManager();
+		
+		Mth::Matrix	total_mat = p_railmanager_component->UpdateRailManager();
+		Mth::Matrix	inv = total_mat;
+		inv.Invert();
+
+		a[W] = 1.0f;
+		b[W] = 1.0f;
+
+		// Transform a,b into the space of the object			
+		Mth::Vector local_a = inv.Transform(a);
+		Mth::Vector local_b = inv.Transform(b);
+
+		if (p_rail_man->StickToRail(local_a, local_b, &rail_pos, &pNode, NULL, min_dot))
+		{
+			// transform from object space to world space
+			rail_pos[W] = 1.0f;
+			rail_pos = total_mat.Transform(rail_pos);
+			if (will_take_rail(pNode, p_rail_man))
+			{
+				got_rail(rail_pos, pNode, p_rail_man); 
+				mp_movable_contact_component->ObtainContact(p_railmanager_component->GetObject());
+				
+				GetVel() -= mp_movable_contact_component->GetContact()->GetObject()->GetVel();
+			}
+			return;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::will_take_rail ( const CRailNode* pNode, CRailManager* p_rail_man, bool from_walk )
+{
+	// no grinding if we're in an acid drop which was generated from an ollie out of a grind; prevents acid-grind-acid-grind repetition
+	if (GetState() == AIR && GetFlag(IN_ACID_DROP) && GetFlag(OLLIED_FROM_RAIL)
+		&& (mp_rail_node == pNode || mp_rail_node == pNode->GetNextLink() || mp_rail_node == pNode->GetPrevLink()))
+	{
+		return false;
+	}
+	
+	// if it's a different rail, then allow rerailing
+	if (!mp_rail_node																			// if there was no last rail
+		
+		// Dan: removed this line to prevent occasional rerailing when you grind off the end of a car; why was it here in the first place?
+		// || (mp_rail_man && mp_rail_man->IsMoving() && !mp_movable_contact_component->HaveContact())	// or last rail was movable, and we've lost contact
+		
+		|| mp_rail_node != pNode																// or not same segment
+		&& mp_rail_node != pNode->GetNextLink()  												// or the one before
+		&& mp_rail_node != pNode->GetPrevLink()) 												// of the one after
+	{
+		if (mp_rail_node && Ed::CParkEditor::Instance()->UsingCustomPark())
+		{
+			Dbg_Assert(mp_rail_man);
+			
+			// in park editor rail must also not share end points to be considered different
+			Mth::Vector a = mp_rail_man->GetPos(mp_rail_node) - mp_rail_man->GetPos(pNode);
+			Mth::Vector b = mp_rail_man->GetPos(mp_rail_node) - mp_rail_man->GetPos(pNode->GetNextLink());
+			Mth::Vector c = mp_rail_man->GetPos(mp_rail_node->GetNextLink()) - mp_rail_man->GetPos(pNode);
+			Mth::Vector d = mp_rail_man->GetPos(mp_rail_node->GetNextLink()) - mp_rail_man->GetPos(pNode->GetNextLink());
+
+			if (Mth::Abs(a.Length()) > 6.0f			
+				&& Mth::Abs(b.Length()) > 6.0f			
+				&& Mth::Abs(c.Length()) > 6.0f			
+				&& Mth::Abs(d.Length()) > 6.0f)
+			{
+				SetFlagTrue(CAN_RERAIL);
+			}
+		}
+		else
+		{
+			SetFlagTrue(CAN_RERAIL);
+		}
+	}
+	
+    return (GetFlag(CAN_RERAIL) || Tmr::ElapsedTime(m_rail_time) > m_rerail_time)
+		&& (from_walk
+			|| (GetState() != RAIL 									// not already on a rail
+			&& (!GetFlag(TRACKING_VERT) || GetVel()[Y] > 0.0f)));		// must be not vert, or going up 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::got_rail ( const Mth::Vector& rail_pos, const CRailNode* pNode, CRailManager* p_rail_man, bool no_lip_tricks, bool from_walk )
+{
+	// got a point on rail, with start node number from a particular rail manager
+	// use this info to start grinding or lipping on the rail
+	
+	CControlPad& control_pad = mp_input_component->GetControlPad();
+	
+	CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
+	Dbg_Assert(p_flip_and_rotate_component);
+	p_flip_and_rotate_component->DoAnyFlipRotateOrBoardRotateAfters();
+
+
+	// Mick - landed on a rail
+	// if it's a "new" rail, then tell the robot detector about it
+	if (mp_rail_node != pNode)
+	{
+		int rail_index = p_rail_man->GetNodeIndex(pNode);		
+		mp_score_component->GetScore()->UpdateRobotDetection(rail_index);
+	}
+
+	mp_rail_node = pNode;					  
+	mp_rail_man = p_rail_man;
+	
+	// handle single node rails
+	if (!pNode->GetPrevLink() && !pNode->GetNextLink())
+	{
+		// for a single node rail, we apply in a single frame all the effects of entering and exiting the rail state;
+
+		/////////////////////////////////////////////////////
+		// Emulate entering the rail state (with horizontal dir direction)
+
+		// check for collision in moving from m_pos to rail_pos
+		
+		m_col_start = GetPos();
+		m_col_end = rail_pos;
+		if (get_member_feeler_collision())
+		{
+			// check distance from the rail to the collision point
+			if ((rail_pos - m_feeler.GetPoint()).LengthSqr() > 6.0f * 6.0f) 
+			{
+				return;
+			}
+		}
+		
+		// check first if we are not moving much in the XY and if not, then se the XZ velocity to the matrix[Z], so we always go forward
+		if (Mth::Abs(GetVel()[X]) < 0.01f && Mth::Abs(GetVel()[Z]) < 0.01f)
+		{
+			GetVel()[X] = GetMatrix()[Z][X];
+			GetVel()[Z] = GetMatrix()[Z][Z];
+			DUMP_VELOCITY;
+		}
+		
+		if (!from_walk && GetVel()[X] * GetVel()[X] + GetVel()[Z] * GetVel()[Z] > 10.0f * 10.0f)
+		{
+			GetVel().RotateToPlane(Mth::Vector(0.0f, 1.0f, 0.0f));
+			DUMP_VELOCITY;
+		}
+
+		// rail direction is taken to always simply be along our horizontal velocity, rotated up
+		Mth::Vector dir = GetVel();
+		dir[Y] = 0.0f;
+		dir.Normalize();
+		float angle = Mth::DegToRad(Script::GetFloat(CRCD(0xbb357ecb,"Physics_Point_Rail_Kick_Upward_Angle")));
+		float c = cosf(angle);
+		float s = sinf(angle);
+		Mth::Vector boost_dir(c * dir[X], s, c * dir[Z]);
+		
+		// get the rail node name
+		Script::CArray* pNodeArray = mp_rail_man->GetNodeArray();
+		Script::CStruct* pNode = pNodeArray->GetStructure(mp_rail_node->GetNode());
+		pNode->GetChecksum(CRCD(0xa1dc81f9, "Name"), &m_last_rail_node_name, Script::ASSERT);
+		
+		mp_trick_component->TrickOffObject(m_last_rail_node_name);
+
+		// Now we want to see if the rail has a trigger, and if it does, trigger it....
+		
+		uint32 trigger_script = 0;
+		
+		// no need to call maybe_trip_rail_trigger for a single node rail
+		if (pNode->GetChecksum(CRCD(0x2ca8a299, "TriggerScript"), &trigger_script))
+		{
+			mp_trigger_component->TripTrigger(
+				TRIGGER_LAND_ON,
+				m_last_rail_node_name,
+				pNodeArray == Script::GetArray(CRCD(0xc472ecc5, "NodeArray")) ? NULL : pNodeArray,
+				mp_movable_contact_component->HaveContact() ? mp_movable_contact_component->GetContact()->GetObject() : NULL
+			);
+		}
+
+		GetPos() = rail_pos;
+		DUMP_POSITION;
+
+		// Now we'v got onto the rail, we need to:
+		// 1) kill velocity perpendicular to the rail
+		// 2) add a speed boost in the direction we are going.
+
+		SetFlagFalse(VERT_AIR);
+		SetFlagFalse(TRACKING_VERT);
+
+		// if we are transitioning from wall to rail, then snap him upright		
+		if (GetState() == WALL)
+		{
+			new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
+			ResetLerpingMatrix();
+		}
+
+		SetState(RAIL);
+
+		set_terrain(mp_rail_node->GetTerrain());
+		mp_sound_component->PlayLandSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
+		
+		float old_y = GetVel()[Y];
+		GetVel().ProjectToNormal(dir);	   							// kill perp velocity
+		if (from_walk && Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f)
+		{
+			if (GetVel().LengthSqr() < Mth::Sqr(1.1f * mp_walk_component->s_get_param(CRCD(0x896c8888, "jump_adjust_speed"))))
+			{
+				GetVel().Set();
+				dir = GetMatrix()[Z];
+				dir[Y] = 0.0f;
+				dir.Normalize();
+			}
+
+		}
+		GetVel()[Y] = old_y;											// except for Y
+
+		GetVel() += dir * GetPhysicsFloat(CRCD(0xa3ef4833, "Point_Rail_Speed_Boost"));	// add speed boost			
+		DUMP_VELOCITY;
+
+
+		// (Mick) Set m_rail_time, otherwise there is a single frame where it is invalid
+		// and this allows us to immediately re-rail and hence do the "insta-bail", since the triangle button will be held down   
+		m_rail_time  = Tmr::GetTime();
+
+		/////////////////////////////////////////////////////
+		// Emulate effects of rail state (with boost_dir direction)
+
+		SetFlagFalse(SPINE_PHYSICS);
+		SetFlagFalse(IN_ACID_DROP);
+		SetFlagFalse(IN_RECOVERY);
+		SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
+
+		// set default rerail time	
+		m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0x2b4645ad, "Rail_Minimum_Rerail_Time")));
+		// and dissalow any overriding of this
+		SetFlagFalse(CAN_RERAIL);		// dont allow rerailing after coming off a segment
+		
+		GetVel().RotateToNormal(boost_dir);
+		DUMP_VELOCITY;
+
+		/////////////////////////////////////////////////////
+		// Emulate exiting the rail state
+
+		// no need to call maybe_trip_rail_trigger for a single node rail
+		if (trigger_script)
+		{
+			mp_trigger_component->TripTrigger(
+				TRIGGER_SKATE_OFF,
+				m_last_rail_node_name,
+				pNodeArray == Script::GetArray(CRCD(0xc472ecc5, "NodeArray")) ? NULL : pNodeArray,
+				mp_movable_contact_component->HaveContact() ? mp_movable_contact_component->GetContact()->GetObject() : NULL
+			);
+		}
+			
+		SetState(AIR);
+		GetPos()[Y] += 1.0f;
+		DUMP_POSITION;
+
+		/////////////////////////////////////////////////////
+		// Do extra point rail logic
+
+		// trigger the appropriate script
+		GetObject()->SelfEvent(CRCD(0xb8048f1d, "PointRail"));
+		
+		return;
+	}
+	
+	///////////////////////////////////////////////////////////////////////////
+	// 						Check for lip trick
+	///////////////////////////////////////////////////////////////////////////
+	
+	float LipAllowSine = sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x8157c5d9, "LipAllowAngle"))));
+	if (pNode->GetFlag(LIP_OVERRIDE))
+	{
+		LipAllowSine = sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xf3b6f95e, "LipAllowAngle_Override"))));
+	}
+	float HorizSine = sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0xe2a85fbb, "LipPlayerHorizontalAngle"))));
+	float VertCos = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x61079462, "LipRampVertAngle"))));
+
+	if ((mAllowLipNoGrind											// This flag makes lip tricks always happen, hooray!
+		|| (
+			GetVel()[Y] > 0.0f											// going upwards
+			&& Mth::Abs(GetMatrix()[Y][Y]) < HorizSine		   			// fairly horizontal skater
+			&& GetMatrix()[Z][Y] > 0.0f									// Facing fairly up in the air
+			&& Mth::Abs(m_current_normal[Y]) < VertCos					// last poly we were on was near vert
+			&& Mth::Abs(GetMatrix()[X][Y]) < LipAllowSine		 			// skater pointing fairly straight up
+		)) && !no_lip_tricks)
+	{
+		// Reset a bunch of stuff.
+		GetVel().Set(0.0f, 0.0f, 0.0f);
+		DUMP_VELOCITY;
+		SetFlagFalse(VERT_AIR);
+		SetFlagFalse(TRACKING_VERT);
+		SetFlagFalse(LAST_POLY_WAS_VERT);
+		SetFlagFalse(CAN_BREAK_VERT);
+		// SetFlagFalse(LEAN);
+		
+		// Make sure any other balance trick is stopped.
+		mp_balance_trick_component->stop_balance_trick();
+		
+		m_pre_lip_pos = GetPos();
+		
+		// Into the lip state
+		SetState(LIP);
+		
+		// Snap the position
+		GetPos() = rail_pos;
+		DUMP_POSITION;
+	
+		// Pop the skater horizontal
+		Mth::Vector u = m_current_normal;
+		u[Y] = 0.0f;
+		u.Normalize();
+		GetMatrix()[Y] = u;		
+		
+		GetMatrix()[Z].Set(0.0f, 1.0f, 0.0f);
+		
+		#if 0 // old code
+		// GetMatrix()[X] = Mth::CrossProduct(m_matrix[Y],m_matrix[Z]);
+		// GetMatrix()[X].Normalize();
+		#else
+		GetMatrix()[X].Set(
+			-GetMatrix()[Y][Z],
+			0.0f,
+			GetMatrix()[Y][X],
+			0.0f
+		);
+		#endif
+
+		ResetLerpingMatrix();
+		
+		// Update the "Require_Lip" flag so lip only gaps don't need to wait on frame
+		GetSkaterGapComponentFromObject(GetObject())->UpdateCancelRequire(0,REQUIRE_LIP);
+		
+		
+		// Trigger the lip on event.
+		maybe_trip_rail_trigger(TRIGGER_LIP_ON);
+
+		mp_trick_component->mUseSpecialTrickText = false;
+		
+		if (!GetObject()->GetScript())
+		{
+			GetObject()->SetScript(new Script::CScript);
+		}
+
+		// Run the lip script.
+		GetObject()->GetScript()->SetScript(CRCD(0x1647cf96, "LipTrick"), NULL, GetObject());
+		GetObject()->GetScript()->Update();
+	
+		// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
+		mp_state_component->SetDoingTrick(true);
+
+		set_terrain(mp_rail_node->GetTerrain());
+	
+		return;
+	}
+	
+	// no lip trick, so we rail
+	
+	set_terrain(mp_rail_node->GetTerrain());
+			  
+	const CRailNode* pStart = mp_rail_node;
+	const CRailNode* pEnd = pStart->GetNextLink();
+
+	Mth::Vector	dir = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
+	dir.Normalize();
+	
+	// Now, we've get a rail
+	
+	// check for collision in moving from m_pos to rail_pos
+	
+	m_col_start = GetPos();
+	m_col_end = rail_pos;
+	if (get_member_feeler_collision())
+	{
+		// check distance from the rail to the collision point
+		if ((rail_pos - m_feeler.GetPoint()).LengthSqr() > 6.0f * 6.0f) 
+		{
+			return;
+		}
+	}
+	
+	if (GetFlag(IN_ACID_DROP))
+	{
+		MESSAGE("GRINDING FROM ACID DROP");
+		DUMPV(acid_hold);
+		DUMP_VELOCITY;
+		GetVel() += acid_hold;
+		DUMP_VELOCITY;
+	}
+
+	// we need to check if this is the end of the rail and we are going to come off it next frame, then we don't want to get on it....
+
+	// check first if we are not moving much in the XY and if not, then se the XZ velocity to the matrix[Z], so we always go forward
+	if (GetVel()[X] == 0.0f && GetVel()[Z] == 0.0f)
+	{
+		GetVel()[X] = GetMatrix()[Z][X];
+		GetVel()[Z] = GetMatrix()[Z][Z];
+	}
+	
+	//////////////////////////////////////////////////////////////////			
+	// Mick: Start of patch
+	// (For Oil Rig type problem with steep rails)
+	// if we are moving forward, then rotate velocity onto a horizontal plane
+	// so we don't seem to be going backwards up steep rails when we jump onto them
+	
+	#if 0 // old code
+	// Mth::Vector flat_vel = GetVel();
+	// flat_vel[Y] = 0;
+	// if (flat_vel.Length() > 10.0f)
+	// {
+		// GetVel().RotateToPlane(Mth::Vector(0.0f,1.0f,0.0f));
+	//}
+	#else
+	if (!from_walk && GetVel()[X] * GetVel()[X] + GetVel()[Z] * GetVel()[Z] > 10.0f * 10.0f)
+	{
+		GetVel().RotateToPlane(Mth::Vector(0.0f, 1.0f, 0.0f));
+		DUMP_VELOCITY;
+	}
+	#endif
+	
+	//
+	// Mick: end of patch
+	/////////////////////////////////////////////////////////////////////
+
+	float dot = Mth::DotProduct(dir, GetVel());
+	
+	// sign is which way we are going along the rail
+	float sign;
+	if (dot == 0.0f)
+	{
+		// if the dot product can not determine the direction (pull up from hangs), choose randomly
+		sign = Mth::Rnd(2) ? 1.0f : -1.0f;
+	}
+	else
+	{
+	   sign = Mth::Sgn(dot);
+	}
+	
+	if (sign < 0.0f)
+	{
+		// will be going backwards along the rail
+		
+		// if the rail stick point is this last point, then we don't want to snap to it as we will immediatly fly off,
+		// and the point might be coincident with a wall or the ground and that will cause problems
+		if (!pStart->GetPrevLink() && mp_rail_man->GetPos(pStart) == rail_pos) return;
+	}
+	else
+	{
+		// same for going forward along the rail
+		if (!pEnd->GetNextLink() && mp_rail_man->GetPos(pEnd) == rail_pos) return;
+	}
+	
+	// Now we want to see if the rail has a trigger, and if it does, trigger it....
+	maybe_trip_rail_trigger(TRIGGER_LAND_ON);
+	
+	GetPos() = rail_pos;
+	DUMP_POSITION;
+	
+	// Now we'v got onto the rail, we need to:
+	// 1) kill velocity perpendicular to the rail
+	// 2) add a speed boost in the direction we are going.
+	
+	SetFlagFalse(VERT_AIR);
+	SetFlagFalse(TRACKING_VERT);
+
+	// if we are transitioning from wall to rail, then snap him upright		
+	if (GetState() == WALL)
+	{
+		new_normal(Mth::Vector(0.0f, 1.0f, 0.0f));
+		ResetLerpingMatrix();
+	}
+	
+	SetState(RAIL);
+	
+	// don't play the rail sound when coming onto a rail from walking
+	if (Tmr::ElapsedTime(mp_physics_control_component->GetStateSwitchTime()) != 0)
+	{
+		// play sound based on pre-rail velocity
+		mp_sound_component->PlayLandSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
+	}
+
+	float old_y = GetVel()[Y];
+	GetVel().ProjectToNormal(dir);	   							// kill perp velocity
+	if (from_walk && Mth::DotProduct(GetVel(), GetMatrix()[Z]) < 0.0f)
+	{
+		if (GetVel().LengthSqr() < Mth::Sqr(1.1f * mp_walk_component->s_get_param(CRCD(0x896c8888, "jump_adjust_speed"))))
+		{
+			GetVel().Set();
+			sign = Mth::Sgn(Mth::DotProduct(dir, GetMatrix()[Z]));
+		}
+		
+	}
+	GetVel()[Y] = old_y;											// except for Y
+	
+	GetVel() += dir * sign * GetPhysicsFloat(CRCD(0x457bb395, "Rail_Speed_Boost"));	// add speed boost			
+	DUMP_VELOCITY;
+	
+	// Ken stuff ...
+	bool Rail_RightOfRail = false;
+	bool Rail_Parallel = false;
+	mRail_Backwards = false;
+
+	if (sign < 0.0f)
+	{
+		dir = -dir;
+	}
+	// dir is now the unit vector pointing along the rail in the direction we will be moving on the rail
+		
+	// Calculate which side of the rail we're on
+	#if 0 // old code
+	// Mth::Vector d = rail_pos - m_last_ground_pos;
+	// Mth::Vector side_vel(d.GetZ(), 0.0f, -d.GetX());
+	// if (Mth::DotProduct(dir, side_vel) < 0.0f)
+	// {
+		// Rail_RightOfRail = true;
+	// }	
+	#else
+	float side_vel_X = rail_pos[Z] - m_last_ground_pos[Z];
+	float side_vel_Z = -(rail_pos[X] - m_last_ground_pos[X]);
+	if (dir[X] * side_vel_X + dir[Z] * side_vel_Z < 0.0f)
+	{
+		Rail_RightOfRail = true;
+	}
+	#endif
+	
+	///////////////////////////////////////////////////////////////////////////////////
+	// 							   Bad ledge detection
+	///////////////////////////////////////////////////////////////////////////////////
+	
+	// Calculate the up and down offsets for collision test.
+	m_col_start.Set(0.0f, GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up")), 0.0f, 0.0f);
+	m_col_end.Set(0.0f, -GetPhysicsFloat(CRCD(0x9cd7ed5c, "Rail_Bad_Ledge_Drop_Down_Dist")), 0.0f, 0.0f);
+	
+	// Add the rail pos, so start and end are now above and below the rail.
+	m_col_start += rail_pos;
+	m_col_end += rail_pos;
+
+	// Calculate a side offset, using the rail direction rotated 90 degrees.
+	Mth::Vector Off(dir[Z], 0.0f, -dir[X], 0.0f);
+	Off *= GetPhysicsFloat(CRCD(0x31669752, "Rail_Bad_Ledge_Side_Dist"));
+
+	// Add the offset and do the left collision.
+	m_col_start += Off;
+	m_col_end += Off;
+	bool LeftCollision = get_member_feeler_collision();
+	
+	// Move across to the other side and do the right collision.
+	m_col_start -= 2.0f * Off;
+	m_col_end -= 2.0f * Off;
+	bool RightCollision = get_member_feeler_collision();
+	
+	// Bit of logic to get whether it's a bad ledge or not.
+	mBadLedge = (LeftCollision && !Rail_RightOfRail) || (RightCollision && Rail_RightOfRail);
+	mLedge = LeftCollision || RightCollision;
+	
+	///////////////////////////////////////////////////////////////////////////////////
+	
+	float RightDot = Mth::DotProduct(dir, GetMatrix()[X]);
+	float FrontDot = Mth::DotProduct(dir, GetMatrix()[Z]);
+	
+	if (Mth::Abs(RightDot) < GetPhysicsFloat(CRCD(0xd0688d4e, "Rail_Tolerance")))
+	{
+		// The skater's right vector is close to perpendicular to the rail, so the skater must be pointing fairly parallel to it.
+		Rail_Parallel = true;
+		
+		// Use the front vector to determine forwards/backwards, since the front vector is fairly parallel to the rail.
+		if (FrontDot < 0.0f)
+		{
+			mRail_Backwards = true;
+		}
+	}
+	else
+	{
+		if (GetFlag(FLIPPED))
+		{
+			RightDot = -RightDot;
+		}
+			
+		// The skater's right vector is fairly parallel to the rail, so use it to determine forwards/backwards.
+		if (RightDot < 0.0f)
+		{
+			mRail_Backwards = true;
+		}
+	}
+
+	mp_trick_component->mUseSpecialTrickText = false;
+
+	if (!mp_trick_component->TriggerAnyExtraGrindTrick(
+			Rail_RightOfRail,
+			Rail_Parallel,
+			mRail_Backwards,
+			GetFlag(FLIPPED))
+		)
+	{
+		do_grind_trick(CSkaterPad::sGetDirection(
+			control_pad.m_up.GetPressed(),
+			control_pad.m_down.GetPressed(),
+			control_pad.m_left.GetPressed(),
+			control_pad.m_right.GetPressed()
+		), Rail_RightOfRail, Rail_Parallel, mRail_Backwards, GetFlag(FLIPPED));
+	}	
+	
+	// (Mick) Set m_rail_time, otherwise there is a single frame where it is invalid
+	// and this allows us to immediately re-rail and hence do the "insta-bail", since the triangle button will be held down   
+	m_rail_time  = Tmr::GetTime();
+}		
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_lip_physics (   )
+{
+	SetFlagFalse(SPINE_PHYSICS);
+	SetFlagFalse(IN_ACID_DROP);
+	SetFlagFalse(IN_RECOVERY);
+	
+	if (mp_movable_contact_component->UpdateContact(GetPos()))
+	{
+		GetPos() += mp_movable_contact_component->GetContact()->GetMovement();
+		DUMP_POSITION;
+		
+		// this is close to correct, 'cept that but because of rotation, it's not quite right
+		m_pre_lip_pos += mp_movable_contact_component->GetContact()->GetMovement();
+		
+		if (mp_movable_contact_component->GetContact()->IsRotated())
+		{
+
+			GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
+			m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
+		}
+	}
+	
+	#ifdef __USER_DAN__
+	if (Script::GetInteger(CRCD(0x1a5eab7, "rail_highlights")))
+	{
+		Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node), mp_rail_man->GetPos(mp_rail_node->GetNextLink()), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+		Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node) + Mth::Vector(1.0f, 0.0f, 0.0f), mp_rail_man->GetPos(mp_rail_node->GetNextLink()) + Mth::Vector(1.0f, 0.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+		Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node) + Mth::Vector(0.0f, 1.0f, 0.0f), mp_rail_man->GetPos(mp_rail_node->GetNextLink()) + Mth::Vector(0.0f, 1.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+		Gfx::AddDebugLine(mp_rail_man->GetPos(mp_rail_node) + Mth::Vector(0.0f, 0.0f, 1.0f), mp_rail_man->GetPos(mp_rail_node->GetNextLink()) + Mth::Vector(0.0f, 0.0f, 1.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+	}
+	#endif
+	
+	// Update any balance control required.
+	if (mp_balance_trick_component->mBalanceTrickType == CRCD(0xa549b57b, "Lip"))
+	{
+		if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")))
+		{
+			// Here, set the flag. It may seem redundant, but the above line is very likely
+			// to be hacked by gameshark. They probably won't notice this one, which will
+			// set the flags as if they had actually enabled the cheat -- which enables us
+			// to detect that it has been turned on more easily.
+			Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")));
+			mp_balance_trick_component->mLip.mManualLean = 0.0f;
+			mp_balance_trick_component->mLip.mManualLeanDir = 0.0f;
+			g_CheatsEnabled = true;
+		}
+		mp_balance_trick_component->mLip.DoManualPhysics();
+	}
+	
+	handle_tensing();
+	if (maybe_flag_ollie_exception())	
+	{
+		// Trigger the lip jump event.
+		maybe_trip_rail_trigger(TRIGGER_LIP_JUMP);
+	}
+
+	// lip tricks are very rail-like when determining which world sector you've just tricked off of
+	if (mp_rail_node)
+	{
+		if (mp_rail_man->GetNodeArray())
+		{
+			const CRailNode* pRail = mp_rail_node;
+			Script::CArray* pNodeArray = Script::GetArray(CRCD(0xc472ecc5, "NodeArray"));
+			Script::CStruct* pNode = pNodeArray->GetStructure(pRail->GetNode());
+			pNode->GetChecksum(CRCD(0xa1dc81f9, "name"), &m_last_rail_node_name, Script::ASSERT);
+			mp_trick_component->TrickOffObject(m_last_rail_node_name);
+		}
+	}
+	
+	m_lip_time = Tmr::GetTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_rail_physics (   )
+{
+	SetFlagFalse(SPINE_PHYSICS);
+	SetFlagFalse(IN_ACID_DROP);
+	SetFlagFalse(IN_RECOVERY);
+	SetFlagFalse(AIR_ACID_DROP_DISALLOWED);
+
+	// First of all we apply the movement due to contact; best to do it first, as then we will be on the rail we are moving along
+
+	if (mp_movable_contact_component->HaveContact())
+	{
+		// need to update the transform of the rail manager
+		// no need to do this														
+		CRailManagerComponent* p_rail_manager_component = GetRailManagerComponentFromObject(mp_movable_contact_component->GetContact()->GetObject());
+		Dbg_Assert(p_rail_manager_component);
+		
+		p_rail_manager_component->UpdateRailManager();
+	
+		if (mp_movable_contact_component->UpdateContact(GetPos()))
+		{
+			
+			GetPos() += mp_movable_contact_component->GetContact()->GetMovement();
+			DUMP_POSITION;
+			if (mp_movable_contact_component->GetContact()->IsRotated())
+			{
+				GetMatrix() *= mp_movable_contact_component->GetContact()->GetRotation();
+				m_lerping_display_matrix *= mp_movable_contact_component->GetContact()->GetRotation();
+			}
+				
+		}
+	}
+
+	// set default rerail time	
+	m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0x2b4645ad, "Rail_minimum_rerail_time")));
+	// and dissalow any overriding of this
+	SetFlagFalse(CAN_RERAIL);		// dont allow rerailing after coming off a segment
+
+	
+	if (maybe_flag_ollie_exception())
+	{
+		maybe_trip_rail_trigger(TRIGGER_JUMP_OFF);
+		
+		m_rerail_time = static_cast< Tmr::Time >(GetPhysicsFloat(CRCD(0xbf35053, "Rail_jump_rerail_time")));
+		
+		SetFlagTrue(OLLIED_FROM_RAIL);
+		
+		// when we jump off a rail, then raise him up one inch, so we don't collide with the top of a fence or something
+		GetPos()[Y] += 1.0f;			// up one inch......
+		DUMP_POSITION;
+	}
+	else
+	{
+		switch (mp_balance_trick_component->mBalanceTrickType)
+		{
+			case 0:
+			case 0x0ac90769: // NoseManual	
+			case 0xef24413b: // Manual
+			case 0xa549b57b: // Lip	
+				break;
+				
+			case 0x255ed86f: // Grind
+			case 0x8d10119d: // Slide
+				mp_balance_trick_component->mGrind.DoManualPhysics();
+				if (Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")))
+				{
+					// Here, set the flag. It may seem redundant, but the above line is very likely
+					// to be hacked by gameshark. They probably won't notice this one, which will
+					// set the flags as if they had actually enabled the cheat -- which enables us
+					// to detect that it has been turned on more easily.
+					Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")));
+					mp_balance_trick_component->mGrind.mManualLean = 0.0f;
+					mp_balance_trick_component->mGrind.mManualLeanDir = 0.0f;
+					g_CheatsEnabled = true;
+				}
+				break;	
+						
+			default:	
+				Dbg_Assert(false);
+				break;
+		}		
+	
+		// Get the rail segments
+
+		const CRailNode* pStart = mp_rail_node;
+		const CRailNode* pEnd = pStart->GetNextLink();
+		
+		const CRailNode* pFrom = pStart;
+		const CRailNode* pOnto = NULL;
+		
+		#ifdef __USER_DAN__
+		if (Script::GetInteger(CRCD(0x1a5eab7, "rail_highlights")))
+		{
+			Gfx::AddDebugLine(mp_rail_man->GetPos(pStart), mp_rail_man->GetPos(pEnd), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+			Gfx::AddDebugLine(mp_rail_man->GetPos(pStart) + Mth::Vector(1.0f, 0.0f, 0.0f), mp_rail_man->GetPos(pEnd) + Mth::Vector(1.0f, 0.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+			Gfx::AddDebugLine(mp_rail_man->GetPos(pStart) + Mth::Vector(0.0f, 1.0f, 0.0f), mp_rail_man->GetPos(pEnd) + Mth::Vector(0.0f, 1.0f, 0.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+			Gfx::AddDebugLine(mp_rail_man->GetPos(pStart) + Mth::Vector(0.0f, 0.0f, 1.0f), mp_rail_man->GetPos(pEnd) + Mth::Vector(0.0f, 0.0f, 1.0f), MAKE_RGB(Mth::Rnd(256), Mth::Rnd(256), Mth::Rnd(256)), 0, 1);
+		}
+		#endif
+		
+		Mth::Vector	dir = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
+		float segment_length = dir.Length();
+		dir *= 1.0f / segment_length;
+
+		// sign is which way we are going along the rail
+		float old_sign = Mth::Sgn(Mth::DotProduct(dir, GetVel()));
+
+		// Get gravity force 	
+		Mth::Vector gravity(0.0f, GetPhysicsFloat(CRCD(0xd1f46992, "Physics_Rail_Gravity")), 0.0f);
+
+		// Project gravity onto the line we are on
+		gravity.ProjectToNormal(dir);
+		GetVel() += gravity * m_frame_length;
+		DUMP_VELOCITY;
+		
+		// sign is which way we are going along the rail
+		float sign = Mth::Sgn(Mth::DotProduct(dir, GetVel()));
+		
+		// sign might have changed here
+		// so could do the "flipping on a rail" thing
+		// .................
+		
+		if (sign != old_sign)
+		{
+			// Note, we JUST flip the "Backwards" flag, as we want to stay in essentailly the same
+			// pose as before, and as the sign of our velocity dotted with the rail has
+			// changed, then all we need to to is tell the code we are
+			// going in the opposite direction to what we were going before, and the
+			// target vector will be calculated correctly.
+			mRail_Backwards = !mRail_Backwards;			 		
+		}
+		
+		// check to see if we are on the last segment of the rail
+		// this logic could be folded into the logic below but it's perhps clearer to have it here
+		bool last_segment = false;
+		if (sign < 0.0f)
+		{
+			if (pStart->GetPrevLink() && pStart->GetPrevLink()->IsActive())
+			{
+				Mth::Vector v1, v2;
+				v1 = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
+				v2 = mp_rail_man->GetPos(pStart) - mp_rail_man->GetPos(pStart->GetPrevLink());
+				v1[Y] = 0.0f;
+				v2[Y] = 0.0f;
+				v1.Normalize();
+				v2.Normalize();
+				float dot = Mth::DotProduct(v1, v2);
+				if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle")))))
+				{
+					// there is more, but angle is too sharp
+					last_segment = true;
+				}
+				else
+				{
+					pOnto = pStart->GetPrevLink();
+				}
+			}
+			else
+			{
+				last_segment = true;			
+			}
+		}
+		else
+		{
+			if (pEnd && pEnd->GetNextLink() && pEnd->GetNextLink()->IsActive())
+			{
+				Mth::Vector  v1,v2;
+				v1 = mp_rail_man->GetPos(pStart) - mp_rail_man->GetPos(pEnd);
+				v2 = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pEnd->GetNextLink());
+				v1[Y] = 0.0f;
+				v2[Y] = 0.0f;
+				v1.Normalize();
+				v2.Normalize();
+				float dot = Mth::DotProduct(v1,v2);
+				if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15,"Rail_Corner_Leave_Angle")))))
+				{
+					// there is more, but angle is too sharp
+					last_segment = true;
+				}
+				else
+				{
+					pOnto = pEnd;
+				}
+			}
+			else
+			{
+				last_segment = true;
+			}
+		}
+
+		// Now we need to see if we have gone beyond the end of the rail segment
+		
+		float extra_dist = 0.0f;
+			
+		float length_along;
+		if (sign < 0.0f)
+		{
+			// going backwards, so it's the distance from the end of the segment
+			length_along = (GetPos() - mp_rail_man->GetPos(pEnd)).Length();
+		}
+		else
+		{
+			// going forwards, so it's the distance from the start
+			length_along = (GetPos() - mp_rail_man->GetPos(pStart)).Length();
+		}
+		
+		if (length_along > segment_length + 0.1f)	// 0.1 inch, so we don't get stuck
+		{
+			// remember this, so we can move along next segment
+			extra_dist = length_along - segment_length;
+			
+			// gone off the end of the segment
+			if (sign < 0.0f)
+			{
+				if (pStart->GetPrevLink()
+					&& pStart->GetPrevLink()->IsActive()
+					&& Rail_ValidInEditor(mp_rail_man->GetPos(pStart), mp_rail_man->GetPos(pStart->GetPrevLink())))
+				{
+					if (!last_segment)
+					{
+						// go onto previous segment
+						GetPos() = mp_rail_man->GetPos(pStart);
+						DUMP_POSITION;
+						mp_rail_node = pStart->GetPrevLink();
+						set_terrain(mp_rail_node->GetTerrain());
+						maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);
+					}		   
+					else
+					{
+						skate_off_rail(mp_rail_man->GetPos(pStart));
+					} 		
+				}
+				else
+				{
+					skate_off_rail(mp_rail_man->GetPos(pStart));
+				}
+			}
+			else
+			{
+				if (pEnd->GetNextLink()
+					&& pEnd->IsActive()
+					&& Rail_ValidInEditor(mp_rail_man->GetPos(pEnd), mp_rail_man->GetPos(pEnd->GetNextLink())))
+				{
+					if (!last_segment)
+					{
+						GetPos() = mp_rail_man->GetPos(pEnd);
+						DUMP_POSITION;
+						mp_rail_node = pEnd;					
+						set_terrain(mp_rail_node->GetTerrain());
+						maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);						
+					}		   
+					else
+					{
+						skate_off_rail(mp_rail_man->GetPos(pEnd));
+					} 						
+				}
+				else
+				{
+					skate_off_rail(mp_rail_man->GetPos(pEnd));
+				}
+			}		
+		}
+		
+		if (GetState() == RAIL)
+		{
+			// recalculate start, end, dir, as we might be on a new segment
+			const CRailNode* pStart = mp_rail_node;
+			const CRailNode* pEnd = pStart->GetNextLink();
+			
+			Mth::Vector	dir = mp_rail_man->GetPos(pEnd) - mp_rail_man->GetPos(pStart);
+			dir.Normalize();
+
+		    // sign also may have changed, now that we are auto-linking rail segments
+			
+			// sign is which way we are going along the rail
+			float sign = Mth::Sgn(Mth::DotProduct(dir,GetVel()));
+
+			m_rail_time = Tmr::GetTime();
+								
+			GetVel().RotateToNormal(dir);
+			GetVel() *= sign;						   						// sign won't be on a new segment
+			DUMP_VELOCITY;
+
+			float facing_sign = mRail_Backwards ? -sign : sign;
+			
+			// z is forward
+			Mth::Vector target_forward = dir * facing_sign;
+
+			m_lerping_display_matrix[Z] = Mth::Lerp(m_lerping_display_matrix[Z], target_forward, 0.3f);
+			m_lerping_display_matrix[Z].Normalize(); 
+			
+			#if 0 // old code
+			// m_lerping_display_matrix[Y].Set(0.0f, 1.0f, 0.0f);
+			// m_lerping_display_matrix[X] = Mth::CrossProduct(
+				// m_lerping_display_matrix[Y],
+				// m_lerping_display_matrix[Z]
+			// );
+			#else
+			m_lerping_display_matrix[X].Set(
+				m_lerping_display_matrix[Z][Z],
+				0.0f,
+				-m_lerping_display_matrix[Z][X],
+				0.0f
+			);
+			#endif
+			m_lerping_display_matrix[X].Normalize();
+			
+			m_lerping_display_matrix[Y] = Mth::CrossProduct(
+				m_lerping_display_matrix[Z],
+				m_lerping_display_matrix[X]
+			);
+			m_lerping_display_matrix[Y].Normalize();
+
+			// adjust our Z value towards the new value												 
+			GetMatrix()[Z] = target_forward;
+			GetMatrix()[Z].Normalize(); 
+			
+			#if 0 // old code
+			// GetMatrix()[Y].Set(0.0f, 1.0f, 0.0f);
+			// GetMatrix()[X] = Mth::CrossProduct(GetMatrix()[Y],GetMatrix()[Z]);
+			#else
+			GetMatrix()[X].Set(
+				GetMatrix()[Z][Z],
+				0.0f,
+				-GetMatrix()[Z][X],
+				0.0f
+			);
+			#endif
+			GetMatrix()[X].Normalize();
+			
+			GetMatrix()[Y] = Mth::CrossProduct(GetMatrix()[Z], GetMatrix()[X]);
+			GetMatrix()[Y].Normalize();
+
+			Mth::Vector m_movement(0.0f, 0.0f, 0.0f);
+			
+			// This is where we do the actual movement
+			
+			// if this makes us bump into the wall or the ground, then we should leave the rail
+			
+			Mth::Vector old_pos = GetPos();  	
+			GetPos() += GetVel() * m_frame_length;			// current movement
+			GetPos() += extra_dist * target_forward;						// any extra dist from previous segment
+			GetPos() += m_movement;							// movement due to contact with moving object
+			DUMP_POSITION;
+			
+			// Mick:  use "old_pos" to generate the direction
+			// so it is guarenteed to be parallel to the rail
+			// and m_pos might have been adjusted if we continued from
+			// one rail to another (in-line) rail, like in the park editor
+			Mth::Vector movement = GetPos() - old_pos;
+			Mth::Vector direction = movement;
+			direction.Normalize();
+			
+			bool always_check = false;
+			if (!last_segment && Ed::CParkEditor::Instance()->UsingCustomPark())
+			{
+				// in the park editor we can have tight curves that end in walls, so we want to always do the forward check
+				// if we are on a segment that is horizontal, and leads to another segment that is also horizontal
+				Mth::Vector from = mp_rail_man->GetPos(pFrom->GetNextLink()) - mp_rail_man->GetPos(pFrom);				
+				Mth::Vector onto = mp_rail_man->GetPos(pOnto->GetNextLink()) - mp_rail_man->GetPos(pOnto);		
+				from.Normalize();
+				onto.Normalize();
+				float delta = Mth::Abs(from[Y] - onto[Y]);		
+				if (delta < 0.01f)
+				{
+					// lines have a sufficently close Y angle
+					always_check = true;
+				}
+			}
+			
+			// Only check for hitting a wall if we are on a segment of rail that has no more rail
+			if (last_segment || always_check)
+			{
+				m_col_start = old_pos;
+				m_col_end = GetPos();
+				m_col_end += movement + (direction * 6.0f);
+				
+				// raise them up one inch, so we don't collide with the rail
+				m_col_start += GetMatrix()[Y];			 
+				m_col_end += GetMatrix()[Y];  
+				if (get_member_feeler_collision())
+				{
+					// if in the park editor, then ignore collision with invisible surfaces 
+					if (!Ed::CParkEditor::Instance()->UsingCustomPark() || !(m_feeler.GetFlags() & mFD_INVISIBLE))
+					{
+						maybe_trip_rail_trigger(TRIGGER_SKATE_OFF);
+						// don't let him make this movement!!
+						GetPos() = GetOldPos();
+						GetPos()[Y] += 1.0f;
+						DUMP_POSITION;
+						
+						// project velocity along the plane if we run into a wall
+						GetVel().ProjectToPlane(m_feeler.GetNormal());
+						DUMP_VELOCITY;
+						
+						SetState(AIR);			// knocked off rail, as something in front
+						FLAGEXCEPTION(CRCD(0xafaa46ba, "OffRail"));
+					}
+				}
+			}
+		}	
+	}			
+	
+	// set the normal value, so the camera is not too confused
+	m_current_normal = GetMatrix()[Y];
+	  
+	// Add mGrindTweak to the score.
+	// adjusted by the robot rail mult (1.0 to 0.1, depending on how much you've ground the rail)
+	mp_score_component->GetScore()->TweakTrick(mGrindTweak * mp_score_component->GetScore()->GetRobotRailMult());
+
+	// if we've already started doing a trick, then start remembering our trick chain
+	{
+		const CRailNode* pRail = mp_rail_node;
+		Dbg_Assert(pRail);
+
+		if (mp_rail_man->GetNodeArray())
+		{
+			Script::CArray* pNodeArray=Script::GetArray(CRCD(0xc472ecc5, "NodeArray"));
+			Script::CStruct* pNode=pNodeArray->GetStructure(pRail->GetNode());
+			pNode->GetChecksum(CRCD(0xa1dc81f9, "name"), &m_last_rail_node_name, true);
+			mp_trick_component->TrickOffObject(m_last_rail_node_name);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::ollie_off_rail_rotate (   )
+{
+	float rot;
+	if (mp_input_component->GetControlPad().m_left.GetPressed())
+	{
+		rot = Mth::DegToRad(GetPhysicsFloat(CRCD(0x38505ef3, "Rail_Jump_Angle")));
+	}
+	else if (mp_input_component->GetControlPad().m_right.GetPressed())
+	{
+		rot = -Mth::DegToRad(GetPhysicsFloat(CRCD(0x38505ef3, "Rail_Jump_Angle")));
+	}
+	else return;
+
+	GetVel().RotateY(rot);		 
+	GetMatrix().RotateYLocal(rot);				
+	m_lerping_display_matrix.RotateYLocal(rot);				
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::skate_off_rail ( const Mth::Vector& off_point )
+{
+	// we have skated off a rail; either it was the end of a rail or we hit a sharp corner
+	// we need to see if there is another rail in front of us that we can continue skating on
+
+	bool really_off = true;
+	
+	Mth::Vector	a;
+	if (Ed::CParkEditor::Instance()->UsingCustomPark())
+	{
+		// in the park editor, we use the last point on the rail we just took off from as the start of the line to use for looking for new rails
+		// so we don't get rail segments earlier in the list
+		a = off_point;
+	}
+	else
+	{
+		// go back a step
+		a = GetPos() - GetVel() * m_frame_length;
+		DUMP_POSITION;
+	}
+	
+	// use current pos, as we know that is off the end
+	Mth::Vector b = GetPos();
+	
+	Mth::Vector rail_pos;	
+	CRailNode* pNode;	
+	
+
+	// we need to tilt the line down, as the rail code currently fails to find a rail if the line we check is exactly parallel with it	
+	a[Y] += 6.0f;							   
+	b[Y] += 6.1f;							   
+
+	float min_dot = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle"))));
+
+	// Note: we are now passing in Rail_Side() of the current rail and velocity
+	// so we can see if we switch from a rail on a left facing ledge to a rail on a right facing ledge, and try to inhibit that type of transition
+	// in favour of one that retains the same side
+	if (mp_rail_man->StickToRail(
+			a,
+			b, 
+			&rail_pos,
+			&pNode,
+			mp_rail_node,
+			min_dot,
+			mp_rail_node->Side(GetVel())
+		))
+	{		
+		// Mick, in park editor, we also disallow this if the rail is the next or the prev rail node from our current node
+		if (mp_rail_node != pNode && (
+				!Ed::CParkEditor::Instance()->UsingCustomPark()
+				|| mp_rail_node->GetNextLink() != pNode
+				&& mp_rail_node->GetPrevLink() != pNode
+			))
+		{
+			const CRailNode* pNewStart = pNode;
+			const CRailNode* pNewEnd = pNewStart->GetNextLink();	
+			
+			// check to see if our new position is within the two points
+			Mth::Vector	to_start = mp_rail_man->GetPos(pNewStart) - GetPos();
+			Mth::Vector	to_end = mp_rail_man->GetPos(pNewEnd) - GetPos();
+			
+			float mid_dot = Mth::DotProduct(to_start, to_end);
+
+			// In game, the point must actualy be in the line, so mid dot will be negative
+			bool ok = mid_dot < 0.0f;					
+			
+			// Park Editor specific rail joining			
+			if (!ok && Ed::CParkEditor::Instance()->UsingCustomPark())
+			{
+				// in the park editor, we let the user overshoot, so he sticks to tight curves that are between two pieces
+				ok = true;
+
+				// we need to ensure that the rail segment is a good continuation of the segment that we were just on
+				// Namely that one of the new start/end points is close to the end of the rail that we just came off
+				// and that the direction we will be going along is within 45 degrees of the direction we were going along before. 
+				// (NOT DONE HERE, as the following test is sufficent)
+											 
+				// first a simple elimination, if the rail is longer than our velocity projected onto it then we can not posibly have overshot it!!
+				Mth::Vector	new_rail_segment = mp_rail_man->GetPos(pNewEnd) - mp_rail_man->GetPos(pNewStart);
+				Mth::Vector skater_movement = b - a;
+				float new_rail_length = new_rail_segment.Length();
+				if (new_rail_length > skater_movement.Length())
+				{
+					ok = false;
+				}
+				
+				// now we could find the shortest distance between two line segments; should be within 2 inches
+				// (ALSO NOT DONE)
+				
+				// bit of a patch for the park editor, it's gone past the end of the rail, so set him in the middle of the rail, so we can continue nicely
+				// possible glitch here, but better than falling of the rail
+				// It's going to be a small rail anyway, so won't look too bad.
+				if (ok && mid_dot >= 0.0f)
+				{
+					rail_pos = (mp_rail_man->GetPos(pNewEnd) + mp_rail_man->GetPos(pNewStart)) / 2.0f;
+				}
+
+			}
+
+			if (ok)
+			{
+				Mth::Vector	newdir = mp_rail_man->GetPos(pNewEnd) - mp_rail_man->GetPos(pNewStart);
+				newdir.Normalize();
+				if (GetVel()[X] == 0.0f && GetVel()[Z] == 0.0f)
+				{
+					GetVel()[X] = GetMatrix()[Z][X];
+					GetVel()[Z] = GetMatrix()[Z][Z];
+					DUMP_VELOCITY;
+				}
+
+				// sign is which way we are going along the rail
+				float sign = Mth::Sgn(Mth::DotProduct(newdir, GetVel()));
+				GetVel().RotateToNormal(newdir);
+				GetVel() *= sign;
+				
+				mp_rail_node = pNode;		// oh yes, this is the node
+				GetPos() = rail_pos;			// move to closest position on the line
+				DUMP_POSITION;
+				maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);
+				set_terrain(mp_rail_node->GetTerrain());
+		
+				really_off = false;			
+			}				
+			else
+			{
+				// new position is outside this rail
+			}	
+		}
+		else
+		{
+			// its the same as this one
+		}	
+	}
+	else
+	{
+		// did not find any
+	}
+	
+	if (!really_off) return;
+
+	maybe_trip_rail_trigger(TRIGGER_SKATE_OFF);
+	SetState(AIR);
+	GetPos()[Y] += 1.0f;
+	DUMP_POSITION;
+	FLAGEXCEPTION(CRCD(0xafaa46ba,"OffRail"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::maybe_trip_rail_trigger ( uint32 type )
+{
+	// given that m_rail_node is valid, then trigger any script associated with this rail node
+	// will search backwards for the first rail that has a trigger script, and then execute that.
+	
+	if (!mp_rail_node) return;
+	
+	Dbg_Assert(mp_rail_man);
+		 
+	
+	const CRailNode* pStartOfRail = mp_rail_node;																	 
+	const CRailNode* pRail = mp_rail_node;																	 
+	
+	// no node array in rail manager indicates auto generated rails, so just return
+	Script::CArray* pNodeArray = mp_rail_man->GetNodeArray();
+	if (!pNodeArray) return;
+	
+	Script::CStruct* pNode = pNodeArray->GetStructure(mp_rail_node->GetNode());
+
+	// find a rail node that has a "TriggerScript" in it
+	uint32 value = 0;
+	if (pNode->GetChecksum(CRCD(0x2ca8a299, "Triggerscript"), &value))
+	{
+		// we got it 
+	}
+	else
+	{
+		// did not get it, so scoot backwards until we detect a loop or we find one
+		const CRailNode* p_loop_detect = pRail;	 	// start loop detect at the start
+		pRail = pRail->GetPrevLink(); 				// and the first node we check is the next one
+		int loop_advance = 0;
+		while (pRail && pRail != pStartOfRail && pRail != p_loop_detect)
+		{
+			pNode = pNodeArray->GetStructure(pRail->GetNode());
+			if (pNode->GetChecksum(CRCD(0x2ca8a299, "Triggerscript"), &value)) break;
+			
+			pRail = pRail->GetPrevLink(); 
+			// The p_loop_detect pointer goes backwards at half speed, so if there is a loop
+			// then pRail is guarenteed to eventually catch up with p_loop_detect
+			if (loop_advance)
+			{
+				p_loop_detect = p_loop_detect->GetPrevLink();				
+			}
+			loop_advance ^= 1;
+		}
+	}
+
+	if (value)
+	{
+		// If this is different to last time, then set flag accordingly
+		uint32 new_last_rail_node_name;
+		pNode->GetChecksum(CRCD(0xa1dc81f9, "name"), &new_last_rail_node_name, Script::ASSERT);
+//		printf ("%s,%s\n",Script::FindChecksumName(new_last_rail_node_name), Script::FindChecksumName(m_last_rail_node_name));
+		if (new_last_rail_node_name != m_last_rail_trigger_node_name)
+		{
+			SetFlagTrue(NEW_RAIL);
+		}
+		else
+		{
+			SetFlagFalse(NEW_RAIL);
+		}
+		m_last_rail_node_name = new_last_rail_node_name;
+		m_last_rail_trigger_node_name = new_last_rail_node_name;
+		
+		
+		// if we are using the default node array, then set it to NULL, so TriggerEventFromNode can use this default, which is a lot faster
+		if (pNodeArray == Script::GetArray(CRCD(0xc472ecc5, "NodeArray")))
+		{
+			pNodeArray = NULL;
+		}
+		
+		mp_trigger_component->TripTrigger(
+			type,
+			m_last_rail_node_name,
+			pNodeArray,
+			mp_movable_contact_component->HaveContact() ? mp_movable_contact_component->GetContact()->GetObject() : NULL
+		);
+	}
+}		
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::handle_tensing (   )
+{
+	if (!GetFlag(TENSE) && mp_input_component->GetControlPad().m_x.GetPressed())
+	{
+		// just starting to tense, so set the flag 
+		SetFlagTrue(TENSE);		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCorePhysicsComponent::get_member_feeler_collision ( uint16 ignore_1, uint16 ignore_0 )
+{
+	// use m_col_start and m_col_end to set up m_feeler, then check for collision and if there is a collision, then get the various skater collision flags
+
+	m_feeler.SetLine(m_col_start, m_col_end);
+	m_feeler.SetIgnore(ignore_1, ignore_0);
+
+	bool collision = m_feeler.GetCollision();
+	
+	if (!collision) return false;
+	
+	if (m_feeler.GetNodeChecksum() && m_feeler.GetTrigger())
+	{
+		// Given a node name, then find the checksum of the triggerscript in that node
+		// note that this is VERY SLOW, as it has to scan the whole node array
+		// it's usually only called once per frame, per skater, but might still be a good candidate for optimization
+		
+		// Now just clear the script, indicating it needed doing later
+		m_feeler.SetScript(0);
+	}
+
+	// get any skating specific flags here
+	uint16 flags = m_feeler.GetFlags();
+
+	// get normal, as it's the kind of things we use in face flag determination				   
+	Mth::Vector	normal = m_feeler.GetNormal();								
+					
+	// Make wallable default to off.				
+	m_col_flag_wallable = false;
+	
+	///////////////////////////////////////////////////////////////////
+	// Vert
+	
+	m_col_flag_vert = flags & mFD_VERT;
+	
+	////////////////////////////////////////////////////////////////
+	// Skatable					   
+	   
+	if (flags & mFD_SKATABLE)
+	{
+		// if flaged as skatable, than that overrides all other flags
+		m_col_flag_skatable = true;
+	}
+	else if (flags & mFD_NOT_SKATABLE)
+	{
+		// if flagged as non_skatable, then that overrides all other flags
+		m_col_flag_skatable = false;
+	}
+	else if (flags & mFD_VERT)
+	{			
+		// if flagged as VERT, then it's the top of a QP, so it's skatable and this overrides the angle flag
+		m_col_flag_skatable = true;
+	}
+	else if (flags & mFD_WALL_RIDABLE)
+	{
+		m_col_flag_skatable = false;
+		m_col_flag_wallable = true;
+	}
+	else
+	{
+		// determine the skatablitlity based on the angle
+		// if angle is > 5 degrees from vert, then it is skatable
+		// here the y component of the normal is the cosine of the angle from vertical
+								  
+		if (normal[Y] < sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x3eede4d3,"Wall_Non_Skatable_Angle")))))
+		{
+			// get could possibly do another test here to see if there is a polygon beneath this meaning this is a QP
+			// however, we want to be flagging the tops of all QPs
+			m_col_flag_skatable = false;
+		}
+		else
+		{
+			m_col_flag_skatable = true;
+		}
+		
+	}
+
+	return collision;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_jump ( Script::CStruct *pParams )
+{
+	// Called by the Jump script command
+	
+	// play sounds before modifying the skater speed, as the sound volume and pitch are based on the pre-jump velocity
+	bool play_sound = !pParams->ContainsFlag(CRCD(0xe13620a8, "no_sound"));
+	
+	switch (GetState())
+	{
+		case GROUND:
+			mp_trigger_component->CheckFeelerForTrigger(TRIGGER_JUMP_OFF, m_last_ground_feeler);
+			if (mp_physics_control_component->HaveBeenReset()) return;
+			
+			// K: Remember the last ground position for calculating which side of the rail we're on later.
+			m_last_ground_pos = GetPos();
+			
+			if (play_sound)
+			{
+				mp_sound_component->PlayJumpSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_last_ground_feeler.GetTerrain());
+			}	
+			
+			maybe_straight_up();
+			SetFlagTrue(CAN_BREAK_VERT);
+			break;
+
+		case AIR:
+			if (play_sound)
+			{
+				mp_sound_component->PlayJumpSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), m_last_ground_feeler.GetTerrain());
+			}	
+			break;
+
+		case RAIL:
+			if (play_sound)
+			{
+				Dbg_Assert(mp_rail_node);
+				mp_sound_component->PlayJumpSound(GetObject()->GetVel().Length() / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat")), mp_state_component->m_terrain);
+			}	
+			break;
+			
+		default:
+			break;
+	}
+			  
+	Dbg_Assert(pParams);
+	
+	int max_tense_time = GetPhysicsInt(CRCD(0xf733c097, "skater_max_tense_time"));
+	m_tense_time = Mth::Min(m_tense_time, max_tense_time);
+	
+	float max_jump_speed;
+	float min_jump_speed;
+	
+	bool from_vert = GetFlag(VERT_AIR) || (GetState() == GROUND && GetFlag(LAST_POLY_WAS_VERT));
+
+	if (pParams->ContainsFlag(CRCD(0xec6b7fc7, "BonelessHeight")))
+	{
+		if (from_vert)
+		{
+			max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x39766891, "Physics_Boneless_air_Jump_Speed_stat"));
+			min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x4cdb18cb, "Physics_Boneless_air_Jump_Speed_min_stat"));
+		}
+		else
+		{
+			max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x8851a76e, "Physics_Boneless_Jump_Speed_stat"));
+			min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x6e8efe38, "Physics_Boneless_Jump_Speed_min_stat"));
+		}
+	}	
+	else
+	{
+		if (from_vert)
+		{
+			max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x9f79c8ea, "Physics_air_Jump_Speed_stat"));
+			min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x91b07824, "Physics_air_Jump_Speed_min_stat"));
+		}
+		else
+		{
+			max_jump_speed = GetSkater()->GetScriptedStat(CRCD(0x2ade3ad, "Physics_Jump_Speed_stat"));
+			min_jump_speed = GetSkater()->GetScriptedStat(CRCD(0xc8815e43, "Physics_Jump_Speed_min_stat"));
+		}
+	}	
+	
+	float jump_speed = Mth::LinearMap(min_jump_speed, max_jump_speed, m_tense_time, 0.0f, max_tense_time);
+	
+	// If any speed param is specified, then use that instead. Need by Zac for when small skater is doing scripted jumps in the front end.
+	pParams->GetFloat(CRCD(0xf0d90109, "speed"), &jump_speed);
+
+	SetFlagFalse(TENSE);
+
+	// if we have a very high downward velocity and we are landing on vert, then do not jump
+
+	// when jumping, don't add in the current velocity if it is less than zero
+	// this will give us nicer feeling jumps when 
+	//  - comping down wall rides
+	//  - skating down slopes
+	//  - skating down the back of bumps, liks the speed bumps in CA
+	//  - landing on a QP
+
+	
+	if (GetFlag(VERT_AIR) && GetVel()[Y] < 0.0f)
+	{
+		 	// push outward	(and upward) away from vert poly
+			GetVel() += jump_speed * m_display_normal;
+			DUMP_VELOCITY;
+			jump_speed = 0;
+			SetFlagFalse(VERT_AIR);
+	}
+	else
+	{
+		if (GetVel()[Y] < 0.0f)
+		{
+			GetVel()[Y] = 0.0f;
+			DUMP_VELOCITY;
+		}
+	}
+	
+	GetVel()[Y] += jump_speed;
+	DUMP_VELOCITY;
+
+	// if pointing down, then jump down, as moving up will make us pass through whatever we are on	
+	if (GetMatrix()[Y][Y] < -0.1f)
+	{
+		GetVel()[Y] -= jump_speed * 1.5f;
+		DUMP_VELOCITY;
+		GetPos() += GetMatrix()[Y] * 12.0f;
+		DUMP_POSITION;
+	}
+	
+	// allow side jumps when jumping off rails or when late jumping in air after skating off a rail
+	if (GetState() == RAIL || (GetState() == AIR && m_previous_state == RAIL))
+	{
+		ollie_off_rail_rotate();
+	}
+	
+	// don't change the state until after sound is played, as the above switch relyies on the GROUND/RAIL state...
+	SetState(AIR);
+	
+	m_last_jump_time_stamp = Tmr::GetTime();
+	
+	GetObject()->BroadcastEvent(CRCD(0x8687163a, "SkaterJump"));
+}			
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::start_skitch (   )
+{
+	// Start Skitching, given that we've queue one up
+	// Note, mp_skitch_object is a smart pointer so there is a slight possibility that it might have died but we check that
+	
+	// Mick:  I'm using .Convert here.   I'm not sure if makes any difference,
+	// but it can't hurt and should compile the same.
+	if (!mp_skitch_object.Convert()) return;
+	
+	mp_movable_contact_component->ObtainContact(mp_skitch_object);
+	SetFlagTrue(SKITCHING);
+	m_moving_to_skitch = true;
+	move_to_skitch_point();
+	
+	// Mick:  For some reason we sometimes get a NULL smart pointer here
+	// so I'm checking again here, in addition to changing the code
+	// to use .Convert 	
+	if (!mp_skitch_object.Convert()) return;
+	
+	mp_skitch_object->SelfEvent(CRCD(0x35224f25, "SkitchOn"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::do_grind_trick ( uint Direction, bool Right, bool Parallel, bool Backwards, bool Regular )
+{
+	int SubArrayIndex = 0;
+	
+	// Choose which array index to use.
+	switch (Direction)
+	{
+		case 0: // Nowt
+			SubArrayIndex = 0;
+			break;
+		case PAD_U: // Up
+			SubArrayIndex = 1;
+			break;
+		case PAD_D: // Down
+			SubArrayIndex = 2;
+			break;
+		case PAD_L: // Left
+			SubArrayIndex = 3;
+			break;
+		case PAD_R: // Right
+			SubArrayIndex = 4;
+			break;
+		case PAD_UL: // UpLeft
+			SubArrayIndex = 5;
+			break;
+		case PAD_UR: // UpRight
+			SubArrayIndex = 6;
+			break;
+		case PAD_DL: // DownLeft
+			SubArrayIndex = 7;
+			break;
+		case PAD_DR: // DownRight
+			SubArrayIndex = 8;
+			break;
+		default:
+			Dbg_MsgAssert(false, ("Bad button checksum"));
+			break;
+	}	
+		
+	// GetArray will assert if GrindTrickList not found.
+	Script::CArray* pMainArray = Script::GetArray(CRCD(0x2ab3341d, "GrindTrickList"));
+	Script::CArray* pSubArray = pMainArray->GetArray(SubArrayIndex);
+
+	uint Index = 0;
+	if (Right) Index |= 1;
+	if (Parallel) Index |= 2;
+	if (Backwards) Index |= 4;
+	if (Regular) Index |= 8;
+
+	// Initialise mGrindTweak, which should get set by a SetGrindTweak command in the script that is about to be run.
+	ResetGrindTweak();
+		
+	CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
+	Dbg_Assert(p_flip_and_rotate_component);
+	p_flip_and_rotate_component->DoAnyFlipRotateOrBoardRotateAfters();
+	
+	if (!GetObject()->GetScript())
+	{
+		GetObject()->SetScript(new Script::CScript);
+	}
+	
+	GetObject()->GetScript()->SetScript(pSubArray->GetNameChecksum(Index), NULL, GetObject());
+	GetObject()->GetScript()->Update();
+	
+	// Set the mDoingTrick flag so that the camera can detect that a trick is being done.
+	mp_state_component->SetDoingTrick(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::StopSkitch (   )
+{
+	// If we were skitching, then stop it
+	
+	if (!GetFlag(SKITCHING)) return;
+	
+	SetFlagFalse(SKITCHING);
+	if (mp_skitch_object)
+	{
+		mp_skitch_object->SelfEvent(CRCD(0x2739b86d, "SkitchOff"));
+		mp_skitch_object = NULL;
+	}
+	mp_movable_contact_component->LoseAnyContact();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::setup_default_collision_cache (   )
+{
+	// reduced Y extent as this no longer subsumes the uberfrig
+	Mth::CBBox bbox(
+		GetPos() - Mth::Vector(FEET(15.0f), FEET(17.0f), FEET(15.0f), 0.0f),
+		GetPos() + Mth::Vector(FEET(15.0f), FEET(8.0f), FEET(15.0f), 0.0f)
+	);
+	
+	mp_coll_cache->Update(bbox);
+	CFeeler::sSetDefaultCache(mp_coll_cache);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCorePhysicsComponent::update_special_friction_index (   )
+{
+	if (m_special_friction_index == 0) return;
+	
+	if (Tmr::ElapsedTime(m_special_friction_decrement_time_stamp) > static_cast< Tmr::Time >(1000.0f * Script::GetFloat(CRCD(0xf4813ad5, "Physics_Time_Before_Free_Revert"))))
+	{
+		m_special_friction_index--;
+		MESSAGE("You earned a free revert!!!!");
+		if (m_special_friction_index != 0)
+		{
+			m_special_friction_decrement_time_stamp = Tmr::GetTime();
+		}
+	}
+}
+
+}
diff --git a/Code/Sk/Components/SkaterCorePhysicsComponent.h b/Code/Sk/Components/SkaterCorePhysicsComponent.h
new file mode 100644
index 0000000..166e01c
--- /dev/null
+++ b/Code/Sk/Components/SkaterCorePhysicsComponent.h
@@ -0,0 +1,621 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterCorePhysicsComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/21/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERCOREPHYSICSCOMPONENT_H__
+#define __COMPONENTS_SKATERCOREPHYSICSCOMPONENT_H__
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define		CRC_SKATERCOREPHYSICS CRCD(0x5bd63b29, "SkaterCorePhysics")
+
+#define		GetSkaterCorePhysicsComponent() ((Obj::CSkaterCorePhysicsComponent*)GetComponent(CRC_SKATERCOREPHYSICS))
+#define		GetSkaterCorePhysicsComponentFromObject(pObj) ((Obj::CSkaterCorePhysicsComponent*)(pObj)->GetComponent(CRC_SKATERCOREPHYSICS))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+
+namespace Nx
+{
+	class CCollCache;
+}
+              
+namespace Obj
+{
+	class CInputComponent;
+	class CTriggerComponent;
+	class CSkaterLoopingSoundComponent;
+	class CTrickComponent;
+	class CSkaterRotateComponent;
+	class CSkaterScoreComponent;
+	class CMovableContactComponent;
+	
+	struct SRailData;
+
+class CSkaterCorePhysicsComponent : public CBaseComponent
+{
+	friend Mdl::Skate;
+	friend CSkater;
+	friend CSkaterCam;
+	friend CManual;
+	friend CSkaterCameraComponent;
+	friend CSkaterAdjustPhysicsComponent;
+	friend CSkaterFinalizePhysicsComponent;
+	friend CSkaterNonLocalNetLogicComponent;
+	friend CSkaterRotateComponent;
+	friend CSkaterPhysicsControlComponent;
+	friend CSkaterMatrixQueriesComponent;
+	friend CSkaterStateHistoryComponent;
+	friend CWalkComponent;
+	
+public:
+    CSkaterCorePhysicsComponent();
+    virtual ~CSkaterCorePhysicsComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+public:
+	void							Reset (   );
+	void							ReadySkateState ( bool to_ground_state, const SRailData* p_rail_data, const SAcidDropData* p_acid_drop_data );
+	void							CleanUpSkateState (   );
+	
+	EStateType						GetState (   );
+	void							SetState ( EStateType state );
+	bool							GetFlag ( ESkaterFlag flag );
+	void							SetFlag ( ESkaterFlag flag, bool state = true );
+	void							SetFlagTrue ( ESkaterFlag flag );
+	void							SetFlagFalse ( ESkaterFlag flag );
+	void							ToggleFlag ( ESkaterFlag flag );
+	Tmr::Time						GetFlagTime ( ESkaterFlag flag );
+	Tmr::Time						GetFlagElapsedTime ( ESkaterFlag flag );
+	void							ResetFlags (   );
+	void							ResetSpecialFrictionIndex (   );
+	void							ReverseFacing (   );
+	bool							HaveLandedThisFrame (   );
+	ETerrainType					GetTerrain (   );
+	ETerrainType					GetLastGroundTerrain (   );
+	uint32							GetLastNodeChecksum (   );
+	bool							GetTrueLandedFromVert (   );
+	void							ResetLerpingMatrix (   );
+	void							StopSkitch (   );
+	bool							IsSwitched (   );
+	void							ResetGrindTweak (   );
+	sint16							GetRailNode( void );
+	
+	void							CollideWithOtherSkaterLost ( CCompositeObject* p_other_skater );
+	
+	Mth::Vector&					GetPos (   ) { return GetObject()->m_pos; }
+	Mth::Vector&					GetOldPos (   ) { return GetObject()->m_old_pos; }
+	Mth::Matrix&					GetMatrix (   ) { return GetObject()->m_matrix; }
+	Mth::Vector&					GetVel (   ) { return GetObject()->m_vel; }
+	
+private:
+	void							do_on_ground_physics (   );
+	void							do_in_air_physics (   );
+	void							do_wallride_physics (   );
+	void							do_wallplant_physics (   );
+	void							do_lip_physics (   );
+	void							do_rail_physics (   );
+	
+	void							limit_speed (   );
+	bool							is_trying_to_brake (   );
+	bool							is_braking (   );
+	void							do_brake (   );
+	bool							on_steep_slow_slope (   );
+	bool							can_kick (   );
+	void							do_kick (   );
+	void							handle_ground_friction (   );
+	void							handle_wind_resistance (   );
+	void							handle_rolling_resistance (   );
+	bool							slide_off_slow_steep_slope (   );
+	void							apply_wind_resistance ( float friction );
+	void							apply_rolling_friction (   );
+	void							push_away_from_walls (   );
+	void							bounce_off_wall ( const Mth::Vector& normal );
+	void							check_for_wall_push ( const Mth::Vector& start, const Mth::Vector& end, int index );
+	void							snap_to_ground (   );
+	void							maybe_straight_up (   );
+	void							maybe_break_vert (   );
+	bool							maybe_spine_transfer (   );
+	bool							look_for_transfer_target ( const Mth::Vector& search_dir, const Mth::Vector& start_normal, bool& hip_transfer, Mth::Vector& target, Mth::Vector& target_normal );
+	bool							maybe_acid_drop ( bool skated_off_edge, const Mth::Vector &pos, const Mth::Vector& old_pos, Mth::Vector& vel, SAcidDropData& acid_drop_data );
+	void							enter_acid_drop ( const SAcidDropData& acid_drop_data );
+	void							handle_post_transfer_limit_overrides (   );
+	void							handle_ground_rotation (   );
+	void							handle_air_rotation (   );
+	void							remove_sideways_velocity ( Mth::Vector& vel );
+	void							check_side_collisions (   );
+	bool							check_side ( float Side, float SideCol );
+	void							handle_forward_collision (   );
+	float							rotate_away_from_wall ( const Mth::Vector& normal, float& turn_angle, float lerp = 1.0f );
+	float							get_air_gravity (   );
+	void							check_leaning_into_wall (   );
+	float							calculate_time_to_reach_height ( float target_height, float pos_Y, float vel_Y );
+	void							flip_if_skating_backwards (   );
+	bool							maybe_flag_ollie_exception (   );
+	void							handle_air_lean (   );
+	void							handle_transfer_slerping (   );
+	void							handle_air_vert_recovery (   );
+	void							rotate_upright (   );
+	bool							handle_forward_collision_in_air ( const Mth::Vector& start_pos );
+	bool							check_for_air_snap_up ( const Mth::Vector& start_pos );
+	bool							handle_upward_collision_in_air (   );
+	bool							handle_upward_collision_in_wallride (   );
+	void							ollie_off_rail_rotate (   );
+	void							skate_off_rail ( const Mth::Vector& off_point );
+	bool							check_for_wallpush (   );
+	bool							check_for_wallplant (   );
+	bool							check_for_wallride (   );
+	void							maybe_skitch (   );
+	void							move_to_skitch_point (   );
+	void							check_movable_contact (   );
+	void							new_normal ( Mth::Vector normal );
+	void							adjust_normal (   );
+	void							maybe_trip_rail_trigger ( uint32 type );
+	void							handle_tensing (   );
+	void							maybe_stick_to_rail ( bool override_air = false );
+	bool							will_take_rail ( const CRailNode* pNode, CRailManager* p_rail_man, bool from_walk = false );
+	void							got_rail ( const Mth::Vector& rail_pos, const CRailNode* pNode, CRailManager* p_rail_man, bool no_lip_tricks = false, bool from_walk = false );
+	bool							get_member_feeler_collision ( uint16 ignore_1 = mFD_NON_COLLIDABLE, uint16 ignore_0 = 0 );
+	void							start_skitch (   );
+	void							do_grind_trick ( uint Direction, bool Right, bool Parallel, bool Backwards, bool Regular );
+	void							set_terrain ( ETerrainType terrain );
+	void							set_last_ground_feeler ( CFeeler& feeler );
+	void							setup_default_collision_cache (   );
+	bool							is_vert_for_transfers ( const Mth::Vector& normal );
+	void							update_special_friction_index (   );
+	
+public:
+	void							do_jump ( Script::CStruct *pParams );
+	
+private:
+	Mth::Matrix						m_lerping_display_matrix;
+	Mth::Vector						m_display_normal;				// current normal the skater is oriented to
+	Mth::Vector						m_current_normal;   			// normal of current polygon 
+	Mth::Vector						m_last_display_normal;   		// last value of m_display_normal
+	float							m_normal_lerp;					// counter 1 to 0 for amount of normal lerp
+	
+	float							m_frame_length;
+	
+	Tmr::Time						m_state_change_timestamp;		// timestamp of the latest state change
+	EStateType						m_previous_state;				// the skater's state previous to the current state
+	
+	Mth::Vector						m_safe_pos;						// Safe world position, to which we can be moved in an emergency
+	
+	CFeeler							m_feeler;	   					// collision data from last collision check 
+	CFeeler							m_last_ground_feeler;			// and from last time we were in valid contact with ground
+	Mth::Vector						m_col_start, m_col_end;			// start and end of collision line segment
+	bool							m_col_flag_skatable;			// true is last face was skatable
+	bool							m_col_flag_vert;				// true if last face was flagged vert
+	bool							m_col_flag_wallable;			// true if last face was flagged wall-ridable
+	
+	// Mth::Vector						m_movement;						// distance we want to move this frame
+																	// used when moving over ground so we don't travel short distances
+																	// when we hit a concave surface which causes collisions				  
+	
+	const CRailNode*				mp_rail_node;					// pointer to current rail node
+	CRailManager*					mp_rail_man;					// pointer to manager of this rail
+	Tmr::Time						m_rail_time;					// last time we were on a rail
+	Tmr::Time						m_lip_time;						// last time we were on a lip
+	Tmr::Time						m_rerail_time;					// time that must elapse before we can get back on the same rail
+	uint32							m_last_rail_node_name;
+	uint32							m_last_rail_trigger_node_name;
+	bool							mLedge;                         // Whether it's a ledge. Calculated in MaybeStickToRail.
+	bool							mBadLedge;                      // Whether this is a bad ledge. Calculated in MaybeStickToRail, and 
+																	// used by the BadLedge script command.
+	
+	
+	bool							m_moving_to_skitch; 			// true if in initial movment to skitch
+	int								m_skitch_index;					// index of the node number on the object we are skitching
+	float							m_skitch_dir;					// direction (-1.0f or 1.0f) we are moving in
+	
+	bool							m_landed_this_frame;			// true if we just landed this frame
+	
+	bool							m_began_frame_in_transfer;
+	bool							m_began_frame_in_lip_state;
+
+	Mth::Vector						m_pre_lip_pos;					// position we were at before we snapped to a lip
+
+	Mth::Vector						m_vert_pos;						// point below us on a vert face
+	Mth::Vector						m_vert_normal;					// normal of that face
+
+	Mth::Vector						m_fall_line;					// unit vector that points "down" the plane
+
+	float							m_vert_upstep;					// amount we try to move up each frame when tracking vert
+
+	Mth::Vector						m_extra_ground_push;			// extra vector to apply to next "ground" movement
+
+	Mth::Vector						m_wall_push; 					// direction to push away from wall
+	float							m_wall_dist;					// shortest distance from wall
+	float							m_wall_push_radius;				// radius to check
+	float							m_wall_push_speed;				// speed at which to push out
+	float 							m_wall_rotate_speed;			// speed at which we rotate away
+
+	float							m_last_time;					// time at last frame
+	
+public:
+	bool							m_auto_kick;   					// true if auto kick on
+	bool							m_spin_taps;					// true when 180 spin taps are enabled.
+	
+protected:
+	float							m_tap_turns;					// How much tap-turn rotation remains.
+
+	bool							m_force_cankick_off;			// Set by the CanKickOff script command, so that Scott can
+																	// force CanKick to return false;
+
+	bool							m_pressing_down_brakes;			// Set/Reset by the CanBrakeOn and CanBrakeOff
+																	// commands.
+
+
+	float							m_rolling_friction;				// Friction, set by SetRollingFriction script command.
+	int								m_special_friction_index;		// Index into an array of special friction values, set by the
+																	// SetSpecialFriction command (used by reverts)
+	Tmr::Time						m_special_friction_decrement_time_stamp;
+	float							m_special_friction_duration;	// When m_rolling_friction is set via special friction, it must be given a duration;
+																	// after which it reverts to default.  Calls to set m_rolling_friction to default
+																	// are ignored during this duration.
+
+	bool							m_braking;						// true if currently braking
+	Tmr::Time						m_push_time[4];					// time of last side push
+
+	Mth::Vector						m_last_ground_pos; 				// Set in DoJump(), & used when calculating which side of rail skater is on.
+	bool							mRail_Backwards;
+
+																	// Flags and value to override the speed limits set on the skater
+	float							m_override_limits_time;			// 0.0f if not overriding, else time in seconds to override for
+	float							m_override_max;					// override value for max_speed
+	float							m_override_max_max;				// override value for max_max_speed
+	float							m_override_air_friction;    	// override value for air friction
+	float							m_override_down_gravity;		// override value for ground gravity (only applied when not going down)
+
+	Obj::CSmtPtr	mp_skitch_object;
+
+	bool							mNoSpin;						// Gets set and reset by NoSpin and CanSpin.
+																	// Disables left-right pad control of the spin.
+	bool							m_bail;							// Set by BailOn, reset by BailOff and tested using BailIsOn
+	
+	bool							m_front_truck_sparks;			// True if sparks come from the front trucks; false if from the rear
+
+	bool							mLandedFromSpine;				// True if landed from a transfer.
+	bool							mLandedFromVert;				// True if landed from vert. Can be set & reset by script commands.
+	
+	bool							m_true_landed_from_vert;		// This is the 'true' landed from vert flag, which only gets set by the C-code on landing. Scripts
+																	// cannot change its value.
+																	// Used by the Clearpanel_Landed C-code to determine whether or not to count an ollie as a trick.
+																	// 180 Ollies are not counted as tricks if you landed from vert, only spins of at least 360 count.
+																	// mLandedFromVert can't be used because the scripts have already cleared it by the time the 
+																	// Clearpanel_Landed command is executed.
+	
+	bool							mAllowLipNoGrind;               // When this is set, then lip tricks will always happen when sticking to a rail,
+																	// and grinds will not be allowed.
+																	// Only gets set or reset by script commands, AllowLipNoGrind and ClearAllowLipNoGrind
+	
+	int								m_tense_time;                   // The time that the player was tensed for. Gets recorded by MaybeFlagOllieException,
+																	// and used by the DoJump function.
+	
+	float							m_transfer_target_height;		// target point for transfer
+	
+																	// These next two needed by the AirTimeLessThan and AirTimeGreaterThan script functions.
+	Tmr::Time						m_went_airborne_time;			// The time when the state change to AIR
+	Tmr::Time						m_landed_time; 					// The time when the state changed from AIR to something else.
+	
+	bool							m_lock_velocity_direction;		// Ken: Switched on/off by the LockVelocityDirection command.
+																	// This is a way to allow the skater to be able to cross a gap in a loop whilst
+																	// inverted and technically being on the ground (on an invisible poly) but such that he 
+																	// looks he's in the air. He will be able to be rotated, but his velocity will not turn
+																	// with his rotation.
+	
+	Mth::Vector						mWallNormal;
+	uint32							mWallrideTime; 					// The time the wall-ride was triggered. Used to stop the player triggering
+																	// another one too soon after the last.
+
+	bool							mNoRailTricks;                  // K: No grinds or lip tricks are allowed if this flag is set. This is switched on and off by
+																	// the NoGrind and AllowGrind script commands. These are used when jumping out of
+																	// a lip trick to disallow going straight into a grind.
+	
+	bool							mYAngleIncreased;				// Keeps track of whether the y angle last increased or decreased.
+																	// Used by the LastSpinWas command.
+	
+	int								mGrindTweak;
+	
+	float							m_transfer_overrides_factor;	// Control the overriding of speed limits after an acid drop.
+	
+	Mth::Matrix						m_acid_drop_camera_matrix;
+	
+	Mth::SlerpInterpolator			m_transfer_slerper;				// Variables controlling the slerping of the skater's matrix during an acid drop
+	float							m_transfer_slerp_timer;
+	float							m_transfer_slerp_duration;
+	Mth::Vector						m_transfer_goal_facing;
+	Mth::Matrix						m_transfer_slerp_previous_matrix;
+
+	Tmr::Time						m_last_wallplant_time_stamp;	// Timestamps wallplants in order to not allow a second one for a short duration.
+	Tmr::Time						m_last_wallpush_time_stamp;		// Timestamps wallpushes in order to not allow a second one for a short duration.
+	
+	Tmr::Time						m_last_jump_time_stamp;			// Timestamps jumps
+	
+	bool							m_vert_air_last_frame;			// Used to broadcast vert air enter and exit events
+	
+	Nx::CCollCache*					mp_coll_cache;
+	
+private:
+	// peer components
+	CInputComponent*				mp_input_component;
+	CTriggerComponent*				mp_trigger_component;
+	CSkaterSoundComponent*			mp_sound_component;
+	CTrickComponent*				mp_trick_component;
+	CSkaterRotateComponent*			mp_rotate_component;
+	CSkaterScoreComponent*			mp_score_component;
+	CSkaterBalanceTrickComponent*	mp_balance_trick_component;
+	CSkaterStateComponent*			mp_state_component;
+	CMovableContactComponent*		mp_movable_contact_component;
+	CSkaterPhysicsControlComponent*	mp_physics_control_component;
+	CWalkComponent*					mp_walk_component;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline bool CSkaterCorePhysicsComponent::is_braking (   )
+{
+	 return m_braking;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline bool CSkaterCorePhysicsComponent::is_vert_for_transfers ( const Mth::Vector& normal )
+{
+	// cull out non-vert vert polys when looking for spine transfer and acid drop triggers; allows designers to be a little sloppier
+	return Mth::Abs(normal[Y]) < 0.707f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline EStateType CSkaterCorePhysicsComponent::GetState (   )
+{
+	return mp_state_component->m_state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Tmr::Time CSkaterCorePhysicsComponent::GetFlagTime ( ESkaterFlag flag )
+{
+	return mp_state_component->m_skater_flags[flag].GetTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Tmr::Time CSkaterCorePhysicsComponent::GetFlagElapsedTime ( ESkaterFlag flag )
+{
+	return mp_state_component->m_skater_flags[flag].GetElapsedTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CSkaterCorePhysicsComponent::GetFlag ( ESkaterFlag flag )
+{
+	return mp_state_component->m_skater_flags[flag].Get(); 
+} 								 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+								 
+inline void CSkaterCorePhysicsComponent::SetFlag ( ESkaterFlag flag, bool state )
+{
+	mp_state_component->m_skater_flags[flag].Set(state);
+	if (flag == RAIL_SLIDING)
+	{
+		mp_sound_component->SetIsRailSliding(state);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterCorePhysicsComponent::SetFlagTrue ( ESkaterFlag flag )
+{
+	SetFlag(flag, true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterCorePhysicsComponent::SetFlagFalse ( ESkaterFlag flag )
+{
+	SetFlag(flag, false);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterCorePhysicsComponent::ToggleFlag ( ESkaterFlag flag )
+{
+	SetFlag(flag, !GetFlag(flag));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 								 
+inline bool CSkaterCorePhysicsComponent::IsSwitched (   )
+{
+	return GetSkater()->m_isGoofy ? GetFlag(FLIPPED) : !GetFlag(FLIPPED);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 								 
+inline bool CSkaterCorePhysicsComponent::HaveLandedThisFrame (   )
+{
+	return m_landed_this_frame;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline ETerrainType CSkaterCorePhysicsComponent::GetLastGroundTerrain (   )
+{
+	return m_last_ground_feeler.GetTerrain();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32 CSkaterCorePhysicsComponent::GetLastNodeChecksum (   )
+{
+	return m_last_ground_feeler.GetNodeChecksum();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CSkaterCorePhysicsComponent::GetTrueLandedFromVert (   )
+{
+	return m_true_landed_from_vert;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterCorePhysicsComponent::ResetSpecialFrictionIndex (   )
+{
+ 	m_special_friction_index = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 								 
+inline ETerrainType CSkaterCorePhysicsComponent::GetTerrain (   )
+{
+	return mp_state_component->m_terrain;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline sint16	CSkaterCorePhysicsComponent::GetRailNode( void )
+{
+	if( mp_rail_node )
+	{
+		return mp_rail_node->GetNode();
+	}
+	else
+	{
+		return -1;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 								 
+inline void CSkaterCorePhysicsComponent::ResetGrindTweak (   )
+{
+	mGrindTweak = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+inline void CSkaterCorePhysicsComponent::ResetLerpingMatrix (   )
+{
+	m_lerping_display_matrix = GetObject()->m_matrix;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 								 
+inline void CSkaterCorePhysicsComponent::set_terrain ( ETerrainType terrain )
+{
+	mp_state_component->m_terrain = terrain;
+	mp_sound_component->SetTerrain(terrain);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 								 
+inline void CSkaterCorePhysicsComponent::set_last_ground_feeler ( CFeeler& feeler )
+{
+	m_last_ground_feeler = feeler;
+	mp_sound_component->SetLastTerrain(m_last_ground_feeler.GetTerrain());
+}
+
+}
+
+
+#endif
diff --git a/Code/Sk/Components/SkaterEndRunComponent.cpp b/Code/Sk/Components/SkaterEndRunComponent.cpp
new file mode 100644
index 0000000..c9e8ea7
--- /dev/null
+++ b/Code/Sk/Components/SkaterEndRunComponent.cpp
@@ -0,0 +1,307 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterEndRunComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/28/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define	FLAGEXCEPTION(X) GetObject()->SelfEvent(X)
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterEndRunComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterEndRunComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterEndRunComponent::CSkaterEndRunComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERENDRUN );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterEndRunComponent::~CSkaterEndRunComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::Update()
+{
+	if (!Mdl::Skate::Instance()->IsMultiplayerGame())
+	{
+		Suspend(true);
+		return;
+	}
+	
+	if( static_cast< CSkater* >(GetObject())->IsLocalClient())
+	{
+		if (m_flags.Test(FINISHED_END_OF_RUN) && !GameNet::Manager::Instance()->HaveSentEndOfRunMessage())
+		{
+			GameNet::Manager::Instance()->SendEndOfRunMessage();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterEndRunComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | EndOfRunDone | 
+		case CRCC(0x4c58771e, "EndOfRunDone"):
+			m_flags.Set(FINISHED_END_OF_RUN);
+			m_flags.Clear(IS_ENDING_RUN);
+			break;
+		
+		case CRCC(0xb866bf9b, "RunStarted"):
+			m_flags.Clear(STARTED_END_OF_RUN);
+			m_flags.Clear( STARTED_GOAL_END_OF_RUN );
+			m_flags.Clear(FINISHED_END_OF_RUN);
+			m_flags.Clear( FINISHED_GOAL_END_OF_RUN );
+			m_flags.Clear(IS_ENDING_RUN);
+			m_flags.Clear(IS_ENDING_GOAL);
+			break;
+
+		case CRCC(0x962dea01, "EndOfRunStarted"):
+			m_flags.Set(STARTED_END_OF_RUN);
+			break;
+
+		case CRCC(0xafc6a7,"Goal_EndOfRunStarted"):
+			m_flags.Set(STARTED_GOAL_END_OF_RUN);
+			break;
+			
+		// @script | Goal_EndOfRunDone | 
+		case CRCC(0x69a9e37f, "Goal_EndOfRunDone"):
+			m_flags.Set(FINISHED_GOAL_END_OF_RUN);
+			m_flags.Clear(IS_ENDING_GOAL);
+			break;
+			
+		case CRCC(0x95bdcfcd, "IsInEndOfRun"):
+			return (m_flags.Test(STARTED_END_OF_RUN) && !m_flags.Test(FINISHED_END_OF_RUN)) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterEndRunComponent::GetDebugInfo"));
+	
+	p_info->AddChecksum(CRCD(0x449233d0, "STARTED_END_OF_RUN"), m_flags.Test(STARTED_END_OF_RUN) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum(CRCD(0x90259288, "FINISHED_END_OF_RUN"), m_flags.Test(FINISHED_END_OF_RUN) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum(CRCD(0x0d2a8609, "IS_ENDING_RUN"), m_flags.Test(IS_ENDING_RUN) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum(CRCD(0x80818017, "STARTED_GOAL_END_OF_RUN"), m_flags.Test(STARTED_GOAL_END_OF_RUN) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum(CRCD(0x587092f3, "FINISHED_GOAL_END_OF_RUN"), m_flags.Test(FINISHED_GOAL_END_OF_RUN) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum(CRCD(0xcc3b2295, "IS_ENDING_GOAL"), m_flags.Test(IS_ENDING_GOAL) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::EndRun ( bool force_end )
+{
+	if (force_end || Mdl::Skate::Instance()->GetGameMode()->ShouldStopAtZero())
+	{
+		if (!m_flags.Test(IS_ENDING_RUN) && !m_flags.Test(FINISHED_END_OF_RUN))
+		{
+			//DumpUnwindStack( 20, 0 );
+			m_flags.Set(IS_ENDING_RUN);
+			if (static_cast< CSkater* >(GetObject())->IsLocalClient())
+			{
+				Script::RunScript(CRCD(0xf4ce3e97, "ForceEndOfRun"), NULL, GetObject());
+			}
+		}
+	}
+	else
+	{
+		//DumpUnwindStack( 20, 0 );
+		FLAGEXCEPTION(CRCD(0x822e13a9, "RunHasEnded"));
+		m_flags.Clear(FINISHED_END_OF_RUN);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::EndGoalRun ( bool force_end )
+{
+	m_flags.Clear(FINISHED_GOAL_END_OF_RUN);
+	FLAGEXCEPTION( CRCD(0xab676175, "GoalHasEnded") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterEndRunComponent::RunHasEnded (   )
+{
+	return m_flags.Test(FINISHED_END_OF_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterEndRunComponent::GoalRunHasEnded (   )
+{
+	return m_flags.Test(FINISHED_GOAL_END_OF_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterEndRunComponent::StartedEndOfRun (   )
+{
+	return m_flags.Test(STARTED_END_OF_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::ClearStartedEndOfRunFlag (   )
+{
+	m_flags.Clear(STARTED_END_OF_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterEndRunComponent::StartedGoalEndOfRun (   )
+{
+	return m_flags.Test(STARTED_GOAL_END_OF_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::ClearStartedGoalEndOfRunFlag (   )
+{
+	m_flags.Clear(STARTED_GOAL_END_OF_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterEndRunComponent::IsEndingRun (   )
+{
+	return m_flags.Test(IS_ENDING_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterEndRunComponent::ClearIsEndingRun (   )
+{
+	m_flags.Clear(IS_ENDING_RUN);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Flags	CSkaterEndRunComponent::GetFlags( void )
+{
+	return m_flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterEndRunComponent::SetFlags( Flags flags )
+{
+	m_flags = flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Sk/Components/SkaterEndRunComponent.h b/Code/Sk/Components/SkaterEndRunComponent.h
new file mode 100644
index 0000000..0b8faa5
--- /dev/null
+++ b/Code/Sk/Components/SkaterEndRunComponent.h
@@ -0,0 +1,78 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterEndRunComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/28/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERENDRUNCOMPONENT_H__
+#define __COMPONENTS_SKATERENDRUNCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#define		CRC_SKATERENDRUN CRCD(0xb1d5a3e1, "SkaterEndRun")
+
+#define		GetSkaterEndRunComponent() ((Obj::CSkaterEndRunComponent*)GetComponent(CRC_SKATERENDRUN))
+#define		GetSkaterEndRunComponentFromObject(pObj) ((Obj::CSkaterEndRunComponent*)(pObj)->GetComponent(CRC_SKATERENDRUN))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkaterEndRunComponent : public CBaseComponent
+{
+public:
+    CSkaterEndRunComponent();
+    virtual ~CSkaterEndRunComponent();
+	
+	enum EFlagType
+	{
+		STARTED_END_OF_RUN,
+		FINISHED_END_OF_RUN,
+		IS_ENDING_RUN,
+		STARTED_GOAL_END_OF_RUN,
+		FINISHED_GOAL_END_OF_RUN,
+		IS_ENDING_GOAL
+		// If adding more than 8 flags, tell Steve
+	};
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							EndRun ( bool force_end = false );
+	void							EndGoalRun ( bool force_end = false );
+	bool							RunHasEnded (   );
+	bool							GoalRunHasEnded (   );
+	bool							StartedEndOfRun (   );
+	void							ClearStartedEndOfRunFlag (   );
+	bool							StartedGoalEndOfRun (   );
+	void							ClearStartedGoalEndOfRunFlag (   );
+	bool							IsEndingRun (   );
+	void							ClearIsEndingRun (   );
+	Flags						GetFlags( void );
+	void							SetFlags( Flags flags );
+
+	
+private:
+	Flags						m_flags;
+	
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterFinalizePhysicsComponent.cpp b/Code/Sk/Components/SkaterFinalizePhysicsComponent.cpp
new file mode 100644
index 0000000..fb9d0c2
--- /dev/null
+++ b/Code/Sk/Components/SkaterFinalizePhysicsComponent.cpp
@@ -0,0 +1,190 @@
+//****************************************************************************
+//* MODULE:         sk/Components
+//* FILENAME:       SkaterFinalizePhysicsComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/26/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+				  
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterFinalizePhysicsComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterFinalizePhysicsComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterFinalizePhysicsComponent::CSkaterFinalizePhysicsComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERFINALIZEPHYSICS );
+
+	mp_core_physics_component = NULL;
+	mp_state_component = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterFinalizePhysicsComponent::~CSkaterFinalizePhysicsComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFinalizePhysicsComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFinalizePhysicsComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFinalizePhysicsComponent::Finalize (   )
+{
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+		
+	Dbg_Assert(mp_core_physics_component);
+	Dbg_Assert(mp_state_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFinalizePhysicsComponent::Update()
+{
+	// setup the state component
+	
+	// Logic for setting/not setting the flag for telling the camera whether to look down on the skater or not.
+	if (mp_core_physics_component->m_began_frame_in_lip_state && mp_state_component->GetState() != LIP)
+	{
+		// This flag is sort of badly named now, it really means we changed from the lip state to something other than GROUND or LIP.
+		mp_state_component->mJumpedOutOfLipTrick = false;
+		if (mp_state_component->GetState() == AIR)
+		{
+			// we only want to set this flag if we jumped straight up; meaning the x and z velocities are close to zero
+			if (Mth::Abs(GetObject()->m_vel[X]) < 1.0f && Mth::Abs(GetObject()->m_vel[Z]) < 1.0f)
+			{
+				mp_state_component->mJumpedOutOfLipTrick = true;
+			}				
+		}
+	}
+	
+	// this flag needs clearing whenever we get out of the air
+	if (mp_state_component->GetState() != AIR)
+	{
+		mp_state_component->mJumpedOutOfLipTrick = false;
+	}
+	
+	mp_state_component->m_camera_display_normal = mp_core_physics_component->m_display_normal;
+	mp_state_component->m_camera_current_normal = mp_core_physics_component->m_current_normal;
+	
+	// make sure the matrices don't get distorted
+	GetObject()->m_matrix.OrthoNormalizeAbout(Y);
+	mp_core_physics_component->m_lerping_display_matrix.OrthoNormalizeAbout(Y);
+	
+	// if any part of the matrix has collapsed, then we will neet to patch it up	
+	// Note, this is a very rare occurence; probably only occurs when you hit things perfectly at right angles, so
+	// you attempt to orthonormalize about an axis that that is now coincident with another axis
+	// would not happen if we rotated the matrix, or used quaternions
+	if (GetObject()->m_matrix.PatchOrthogonality())
+	{
+		mp_core_physics_component->ResetLerpingMatrix();
+	}
+	
+	// Extract the informations from the physics object that we need for rendering
+	GetObject()->SetDisplayMatrix(mp_core_physics_component->m_lerping_display_matrix);
+	
+	#ifdef __USER_DAN__
+	// Gfx::AddDebugArrow(GetObject()->GetPos(), GetObject()->GetPos() + 60.0f * GetObject()->GetDisplayMatrix()[Z], RED, 0, 1);
+	// Gfx::AddDebugArrow(GetObject()->GetPos(), GetObject()->GetPos() + 60.0f * GetObject()->GetDisplayMatrix()[X], BLUE, 0, 1);
+	// Gfx::AddDebugArrow(GetObject()->GetPos(), GetObject()->GetPos() + 60.0f * GetObject()->GetDisplayMatrix()[Y], GREEN, 0, 1);
+	#endif
+	
+	// update the sound components' state
+	
+	Obj::CSkaterSoundComponent *pSoundComponent = GetSkaterSoundComponentFromObject( GetObject() );
+	Dbg_Assert( pSoundComponent );
+	
+	pSoundComponent->SetIsRailSliding( mp_state_component->GetFlag(RAIL_SLIDING) );
+	pSoundComponent->SetTerrain( mp_state_component->GetTerrain() );
+	
+	Obj::CSkaterLoopingSoundComponent *pLoopingSoundComponent = GetSkaterLoopingSoundComponentFromObject( GetObject() );
+	Dbg_Assert( pLoopingSoundComponent );
+	
+	float speed_fraction = sqrtf( GetObject()->GetVel()[X] * GetObject()->GetVel()[X] + GetObject()->GetVel()[Z] * GetObject()->GetVel()[Z] ) / GetSkater()->GetScriptedStat(CRCD(0xcc5f87aa, "Skater_Max_Max_Speed_Stat") );
+	pLoopingSoundComponent->SetSpeedFraction( speed_fraction );
+	pLoopingSoundComponent->SetIsBailing(mp_state_component->GetFlag(IS_BAILING));
+	pLoopingSoundComponent->SetIsRailSliding( mp_state_component->GetFlag(RAIL_SLIDING) );
+	pLoopingSoundComponent->SetTerrain( mp_state_component->GetTerrain() );
+	pLoopingSoundComponent->SetState( mp_state_component->GetState() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterFinalizePhysicsComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFinalizePhysicsComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterFinalizePhysicsComponent::GetDebugInfo"));
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+	
+}
diff --git a/Code/Sk/Components/SkaterFinalizePhysicsComponent.h b/Code/Sk/Components/SkaterFinalizePhysicsComponent.h
new file mode 100644
index 0000000..2d2bd2d
--- /dev/null
+++ b/Code/Sk/Components/SkaterFinalizePhysicsComponent.h
@@ -0,0 +1,59 @@
+//****************************************************************************
+//* MODULE:         sk/Components
+//* FILENAME:       SkaterFinalizePhysicsComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/26/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERFINALIZEPHYSICSCOMPONENT_H__
+#define __COMPONENTS_SKATERFINALIZEPHYSICSCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERFINALIZEPHYSICS CRCD(0x9b373e58, "SkaterFinalizePhysics")
+
+#define		GetSkaterFinalizePhysicsComponent() ((Obj::CSkaterFinalizePhysicsComponent*)GetComponent(CRC_SKATERFINALIZEPHYSICS))
+#define		GetSkaterFinalizePhysicsComponentFromObject(pObj) ((Obj::CSkaterFinalizePhysicsComponent*)(pObj)->GetComponent(CRC_SKATERFINALIZEPHYSICS))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterStateComponent;
+
+class CSkaterFinalizePhysicsComponent : public CBaseComponent
+{
+public:
+    CSkaterFinalizePhysicsComponent();
+    virtual ~CSkaterFinalizePhysicsComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+private:
+	CSkaterCorePhysicsComponent* 	mp_core_physics_component;
+	CSkaterStateComponent* 			mp_state_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterFlipAndRotateComponent.cpp b/Code/Sk/Components/SkaterFlipAndRotateComponent.cpp
new file mode 100644
index 0000000..b41ff8d
--- /dev/null
+++ b/Code/Sk/Components/SkaterFlipAndRotateComponent.cpp
@@ -0,0 +1,387 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterFlipAndRotateComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/31/3
+//****************************************************************************
+
+/*
+ * Encapsulates the dirty, dirty skater flip, skater rotate, and board rotate animation logic.
+ *
+ * "Please!" says CSkaterFlipAndRotateComponent, "Deprecate me, and end my torturous existence!"
+ */
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+	extern bool DebugSkaterScripts;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterFlipAndRotateComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterFlipAndRotateComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterFlipAndRotateComponent::CSkaterFlipAndRotateComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERFLIPANDROTATE );
+	
+	mp_animation_component = NULL;
+	mp_model_component = NULL;
+	
+	m_rotate_board = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterFlipAndRotateComponent::~CSkaterFlipAndRotateComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::Finalize (   )
+{
+	// Note: Non-local clients have a CSkaterFlipAndRotateComponent, but not a CSkaterCorePhysicsComponent.
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	
+	mp_model_component = GetModelComponentFromObject(GetObject());
+   	mp_animation_component = GetAnimationComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_model_component);
+	Dbg_Assert(mp_animation_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::Update()
+{
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterFlipAndRotateComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | Flip | flip animation
+		case CRCC(0x65011baa, "Flip"):	
+			ToggleFlipState();
+			break;
+			
+        // @script | ResetSwitched | flip if we're switched
+		case CRCC(0x72289a28, "ResetSwitched"):
+			Dbg_Assert(mp_core_physics_component);
+			if (mp_core_physics_component->IsSwitched())
+			{
+				ToggleFlipState();
+			}
+			break;
+			
+		// These three just set flags, which will cause the appropriate flip/rotate
+		// when the next PlayAnim occurs. 
+        // @script | FlipAfter | sets flag, which will cause the appropriate flip/rotate when the next PlayAnim occurs
+		case CRCC(0xeba11b20, "FlipAfter"):
+			mFlipAfter = true;
+			break;
+			
+        // @script | RotateAfter | sets flag, which will cause the appropriate flip/rotate when the next PlayAnim occurs
+		case CRCC(0xacac1bad, "RotateAfter"):
+			mRotateAfter = true;
+			break;
+			
+        // @script | BoardRotateAfter | sets flag, which will cause the appropriate flip/rotate when the next PlayAnim occurs
+		case CRCC(0x63e93f69, "BoardRotateAfter"):
+			mBoardRotateAfter = true;
+			break;
+			
+		// @script | IsFlipAfterSet | returns mFlipAfter
+		case CRCC(0xa6243daa, "IsFlipAfterSet"):
+			return mFlipAfter ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+        // @script | FlipAndRotate | flip Z and X, to rotate 180 degrees about y
+		case CRCC(0xd9de0c65, "FlipAndRotate"):
+		{
+			Dbg_Assert(mp_core_physics_component);
+			mp_core_physics_component->ReverseFacing();
+			ToggleFlipState();
+			break;
+		}
+			
+		// @script | BoardRotate | 
+        // @flag normal | put the board back to normal (otherwise just flip)
+		case CRCC(0xe0f3a644, "BoardRotate"):
+		{
+			GameNet::PlayerInfo* player = GameNet::Manager::Instance()->GetPlayerByObjectID(GetObject()->GetID());
+			Dbg_Assert(player);
+			
+			Net::Client* client = GameNet::Manager::Instance()->GetClient(player->GetSkaterNumber());
+			Dbg_Assert(client);
+
+			if (pParams->ContainsFlag(CRCD(0xde7a971b, "Normal")))
+			{
+				// Put the board back to normal
+				RotateSkateboard(GetObject()->GetID(), false, client->m_Timestamp, true);
+			}
+			else
+			{
+				// Otherwise flip it, flip it good.
+				RotateSkateboard(GetObject()->GetID(), !m_rotate_board, client->m_Timestamp, true);
+			}	
+			break;
+		}
+
+		// @script | BoardIsRotated | 
+		case CRCC(0x79ee5ccf, "BoardIsRotated"):
+			return m_rotate_board ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		case CRCC(0xb1e7291, "PlayAnim"):
+			// do any required flips and rotates, and then cascade the member function call
+			DoAnyFlipRotateOrBoardRotateAfters();
+			return CBaseComponent::MF_NOT_EXECUTED;
+		
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterFlipAndRotateComponent::GetDebugInfo"));
+	
+	if (mp_core_physics_component)
+	{
+		p_info->AddChecksum(CRCD(0x8f66b80b, "switched"), mp_core_physics_component->IsSwitched() ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	}
+	p_info->AddChecksum(CRCD(0xc7a712c, "flipped"), GetSkaterStateComponentFromObject(GetObject())->GetFlag(FLIPPED) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	
+	p_info->AddChecksum(CRCD(0xeba11b20, "FlipAfter"), mFlipAfter ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum(CRCD(0xacac1bad, "RotateAfter"), mRotateAfter ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddChecksum(CRCD(0x63e93f69, "BoardRotateAfter"), mBoardRotateAfter ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterFlipAndRotateComponent::RotateSkateboard ( uint32 objId, bool rotate, uint32 time, bool propagate )
+{
+	if ( propagate )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+
+		player = gamenet_man->GetPlayerByObjectID( objId );
+		if( player && player->IsLocalPlayer())
+		{
+			Net::Client* client;
+			GameNet::MsgRotateSkateboard msg;
+			Net::MsgDesc msg_desc;
+
+			client = gamenet_man->GetClient( player->GetSkaterNumber());
+			Dbg_Assert( client );
+
+			msg.m_Rotate = rotate;
+			msg.m_ObjId = objId;
+
+			msg_desc.m_Data = &msg;
+			msg_desc.m_Id = GameNet::MSG_ID_ROTATE_SKATEBOARD;
+			msg_desc.m_Length = sizeof( GameNet::MsgRotateSkateboard );
+			client->EnqueueMessageToServer(	&msg_desc );
+		}
+	}
+
+	bool oldRotation = m_rotate_board;
+
+	m_rotate_board = rotate;
+	
+	#ifdef __NOPT_ASSERT__
+	if (DebugSkaterScripts && GetObject()->GetType() == SKATE_TYPE_SKATER)
+	{
+		printf("%d: Rotating board [rotated = %s]\n", (int) Tmr::GetRenderFrame(), m_rotate_board ? "true" : "false");
+	}
+	#endif
+
+	return oldRotation;
+}
+
+/******************************************************************/
+
+/*
+
+If any of mFlipAfter, mRotateAfter or mBoardRotateAfter are set
+this will flip or rotate the skater, or rotate the board, and then
+reset those flags.
+
+Quite often the skater needs to be flipped and/or rotated in order to
+look correctly oriented, due to the way the animations are done.
+For example, the nollie heelflip animation is actually animated backwards,
+so to look correct the skater has to be flipped and rotated just for the
+duration of that animation.
+As soon as any sort of 'event' happens that could result in an animation
+change, or requires that the orientation be correct for the physics, then
+the skater needs to be flipped and rotated back.
+So in the script, when running the nollie heelflip anim, Scott will flip and
+rotate the skater first so that he looks correct, then set FlipAfter and RotateAfter.
+
+It used to be that I'd check the flags only in the next call to the PlayAnim script 
+member function.
+However, this is often not early enough. For example, when the player lands whilst
+doing a nollie heelflip, the land script will do some checks to see if the player 
+is facing backwards before doing a playanim, so it will wrongly think the player
+is backwards, when he isn't 'really'.
+
+So instead I sprinkled calls to the following function whenever anything happens
+that could cause a new script to be executed. In particular, before every SetScript,
+and also as soon as a grind gets detected, so that the grind maths won't think the
+skater is backwards when landing from a nollie heelflip.
+
+*/
+
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::DoAnyFlipRotateOrBoardRotateAfters (   )
+{
+	if (mFlipAfter)
+	{
+		ToggleFlipState();
+		mFlipAfter = false;
+	}
+		
+	if (mRotateAfter)
+	{
+		Dbg_Assert(mp_core_physics_component);
+		mp_core_physics_component->ReverseFacing();
+		mRotateAfter = false;
+	}
+		
+	if (mBoardRotateAfter)
+	{
+		GameNet::PlayerInfo* player = GameNet::Manager::Instance()->GetPlayerByObjectID(GetObject()->GetID());
+		Dbg_Assert(player);
+
+		Net::Client* client = GameNet::Manager::Instance()->GetClient(player->GetSkaterNumber());
+		Dbg_Assert(client);
+
+		// Rotate the board.
+		RotateSkateboard(GetObject()->GetID(), !m_rotate_board, client->m_Timestamp, true);
+		mBoardRotateAfter = false;
+	}	
+	
+	// This makes the skater display facing the correct way again.
+	// The mFlipDisplayMatrix is only used when falling back in from a lip trick.
+	// Since it would be bad if this flag got left on, make sure it is off whenever
+	// a new anim runs. This function is a good place to put it, since it gets
+	// run whenever the anim changes.
+	mp_model_component->mFlipDisplayMatrix = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::Reset (   )
+{
+	mFlipAfter = false;
+	mRotateAfter = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::ToggleFlipState (   )
+{
+	GetSkaterStateComponentFromObject(GetObject())->m_skater_flags[FLIPPED].Toggle();	
+	
+	#ifdef __NOPT_ASSERT__
+	if (DebugSkaterScripts && GetObject()->GetType() == SKATE_TYPE_SKATER)
+	{
+		printf("%d: Flipping skater [flipped = %s]\n", (int) Tmr::GetRenderFrame(), mp_core_physics_component->GetFlag(FLIPPED) ? "true" : "false");
+	}
+	#endif
+	
+	// Flip the animation to the correct orientation Just setting a flag for Dave's code to handle when rendering
+	ApplyFlipState();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFlipAndRotateComponent::ApplyFlipState (   )
+{
+	Net::Client* client = GameNet::Manager::Instance()->GetClient(GetSkater()->GetSkaterNumber());
+	Dbg_Assert(client);
+	
+	mp_animation_component->FlipAnimation(GetObject()->GetID(), GetSkaterStateComponentFromObject(GetObject())->GetFlag(FLIPPED), client->m_Timestamp, true);
+}
+
+}
diff --git a/Code/Sk/Components/SkaterFlipAndRotateComponent.h b/Code/Sk/Components/SkaterFlipAndRotateComponent.h
new file mode 100644
index 0000000..44cdc03
--- /dev/null
+++ b/Code/Sk/Components/SkaterFlipAndRotateComponent.h
@@ -0,0 +1,80 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterFlipAndRotateComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/31/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERFLIPANDROTATECOMPONENT_H__
+#define __COMPONENTS_SKATERFLIPANDROTATECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERFLIPANDROTATE CRCD(0x488af07a, "SkaterFlipAndRotate")
+
+#define		GetSkaterFlipAndRotateComponent() ((Obj::CSkaterFlipAndRotateComponent*)GetComponent(CRC_SKATERFLIPANDROTATE))
+#define		GetSkaterFlipAndRotateComponentFromObject(pObj) ((Obj::CSkaterFlipAndRotateComponent*)(pObj)->GetComponent(CRC_SKATERFLIPANDROTATE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CAnimationComponent;
+	class CSkaterCorePhysicsComponent;
+	class CModelComponent;
+
+class CSkaterFlipAndRotateComponent : public CBaseComponent
+{
+public:
+    CSkaterFlipAndRotateComponent();
+    virtual ~CSkaterFlipAndRotateComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	   	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+
+public:
+	void							Reset (   );
+	
+	bool							RotateSkateboard ( uint32 objId, bool rotate, uint32 time, bool propagate );
+	bool							IsBoardRotated (   ) { return m_rotate_board; }
+	
+	void							DoAnyFlipRotateOrBoardRotateAfters (   );
+									  	
+	void							ToggleFlipState (   );
+	void							ApplyFlipState (   );
+
+private:
+	bool							m_rotate_board;
+	
+	bool							mFlipAfter;
+	bool							mRotateAfter;
+	bool							mBoardRotateAfter;
+	
+	// peer components
+	CAnimationComponent*			mp_animation_component;
+	CModelComponent*				mp_model_component;
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterFloatingNameComponent.cpp b/Code/Sk/Components/SkaterFloatingNameComponent.cpp
new file mode 100644
index 0000000..e3adf39
--- /dev/null
+++ b/Code/Sk/Components/SkaterFloatingNameComponent.cpp
@@ -0,0 +1,196 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterFloatingNameComponent.h
+//* OWNER:			Dan
+//* CREATION DATE:  3/13/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterFloatingNameComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterFloatingNameComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterFloatingNameComponent::CSkaterFloatingNameComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERFLOATINGNAME );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterFloatingNameComponent::~CSkaterFloatingNameComponent()
+{
+	Script::CStruct* pParams = pParams = new Script::CStruct;
+	pParams->AddChecksum(CRCD(0x40c698af, "id"), m_screen_element_id);
+	
+	Script::RunScript(CRCD(0x2575b406, "destroy_object_label"), pParams);
+	
+	delete pParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFloatingNameComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterFloatingNameComponent added to non-skater composite object"));
+	
+	switch (GetObject()->GetID())
+	{
+		case 0:
+			m_screen_element_id = CRCD(0xe797a186, "skater_name_0");
+			break;
+		case 1:
+			m_screen_element_id = CRCD(0x90909110, "skater_name_1");
+			break;
+		case 2:
+			m_screen_element_id = CRCD(0x999c0aa, "skater_name_2");
+			break;
+		case 3:
+			m_screen_element_id = CRCD(0x7e9ef03c, "skater_name_3");
+			break;
+		case 4:
+			m_screen_element_id = CRCD(0xe0fa659f, "skater_name_4");
+			break;
+		case 5:
+			m_screen_element_id = CRCD(0x97fd5509, "skater_name_5");
+			break;
+		case 6:
+			m_screen_element_id = CRCD(0xef404b3, "skater_name_6");
+			break;
+		case 7:
+			m_screen_element_id = CRCD(0x79f33425, "skater_name_7");
+			break;
+		default:
+			Dbg_MsgAssert(false, ("CSkaterFloatingNameComponent in CCompositeObject with ID of 8 or greater"));
+			return;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFloatingNameComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFloatingNameComponent::Update()
+{
+	if (!GameNet::Manager::Instance()->InNetGame() || !GameNet::Manager::Instance()->ShouldDrawPlayerNames())
+	{
+		Suspend(true);
+		return;
+	}
+	
+	if (!GetSkater()->IsInWorld()) return;
+	
+	GameNet::PlayerInfo* player = GameNet::Manager::Instance()->GetPlayerByObjectID(GetObject()->GetID());
+	
+	float offset;
+	if (GameNet::Manager::Instance()->GetCurrentlyObservedPlayer() == player)
+	{
+		offset = FEET(8.0f);
+	}
+	else
+	{
+		offset = FEET(10.0f);
+	}
+
+	Script::CStruct* pParams = new Script::CStruct;
+	pParams->AddChecksum(CRCD(0x40c698af, "id"), m_screen_element_id);
+
+	int color_index;
+	if (Mdl::Skate::Instance()->GetGameMode()->IsTeamGame())
+	{
+		color_index = player->m_Team + 2;
+	}
+	else
+	{
+		color_index = GetObject()->GetID() + 2;
+	}
+
+	char text[64];
+	sprintf(text, "\\c%d%s", color_index, GetSkater()->GetDisplayName());
+	pParams->AddString(CRCD(0xc4745838, "text"), text);
+	pParams->AddVector(CRCD(0x4b491900, "pos3D"), GetObject()->m_pos[X], GetObject()->m_pos[Y] + offset, GetObject()->m_pos[Z]);
+
+	Front::CScreenElement *p_name_elem = Front::CScreenElementManager::Instance()->GetElement(m_screen_element_id);
+	if (p_name_elem)
+	{
+		p_name_elem->SetProperties(pParams);
+	}
+	else
+	{
+		Script::RunScript(CRCD(0x6a060cf0, "create_object_label"), pParams);
+	}
+	
+	delete pParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterFloatingNameComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterFloatingNameComponent::GetDebugInfo ( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterFloatingNameComponent::GetDebugInfo"));
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+	
+}
diff --git a/Code/Sk/Components/SkaterFloatingNameComponent.h b/Code/Sk/Components/SkaterFloatingNameComponent.h
new file mode 100644
index 0000000..24bcb61
--- /dev/null
+++ b/Code/Sk/Components/SkaterFloatingNameComponent.h
@@ -0,0 +1,56 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterFloatingNameComponent.h
+//* OWNER:			Dan
+//* CREATION DATE:  3/13/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERFLOATINGNAMECOMPONENT_H__
+#define __COMPONENTS_SKATERFLOATINGNAMECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERFLOATINGNAME CRCD(0x125044e2, "SkaterFloatingName")
+
+#define		GetSkaterFloatingNameComponent() ((Obj::CSkaterFloatingNameComponent*)GetComponent(CRC_SKATERFLOATINGNAME))
+#define		GetSkaterFloatingNameComponentFromObject(pObj) ((Obj::CSkaterFloatingNameComponent*)(pObj)->GetComponent(CRC_SKATERFLOATINGNAME))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkaterFloatingNameComponent : public CBaseComponent
+{
+public:
+    CSkaterFloatingNameComponent();
+    virtual ~CSkaterFloatingNameComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+private:
+	uint32							m_screen_element_id;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterGapComponent.cpp b/Code/Sk/Components/SkaterGapComponent.cpp
new file mode 100644
index 0000000..3291f47
--- /dev/null
+++ b/Code/Sk/Components/SkaterGapComponent.cpp
@@ -0,0 +1,974 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterGapComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/5/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 							// for event broadcasting on sucess of a gap
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+// #define DEBUG_GAPS
+
+namespace Obj
+{
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterGapComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterGapComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterGapComponent::CSkaterGapComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERGAP );
+	
+	mp_core_physics_component = NULL;
+	mp_balance_trick_component = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterGapComponent::~CSkaterGapComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterGapComponent added to non-skater composite object"));
+	
+	m_frame_count = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::Finalize (   )
+{
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	mp_balance_trick_component = GetSkaterBalanceTrickComponentFromObject(GetObject());
+	mp_physics_control_component = GetSkaterPhysicsControlComponentFromObject(GetObject());
+	mp_walk_component = GetWalkComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_core_physics_component);
+	Dbg_Assert(mp_balance_trick_component);
+	Dbg_Assert(mp_physics_control_component);
+	Dbg_Assert(mp_walk_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::Update()
+{
+	m_frame_count++;
+	
+	// setup flags based on skater state
+	int cancel, require;
+	get_state_flags(cancel, require);
+	
+	UpdateCancelRequire(cancel, require);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Split out the actual updating from Update(), so we co do specific updates
+// of the require/cancel flags at arbitart times
+// specifically we want to update the "Lip" requirements as soon as we land on them
+// and do it in isolation, so nothing else is messed up
+
+void CSkaterGapComponent::UpdateCancelRequire(int cancel, int require)
+{
+	CGap* pGap = static_cast< CGap* >(m_gap_list.FirstItem());
+	
+	// don't bother determining the state flags if there are no active gaps
+	if (!pGap) return;
+	
+	// update the active gaps based on the flags
+	while (pGap)
+	{
+		CGap* pNext = static_cast< CGap* >(pGap->GetNext());
+		
+		// Clear any require flags that match the require mask, so if we do all that is required, then the require part of the flags field will be cleared
+		pGap->m_flags &= ~require;
+	
+		// if any cancel flags match the cancel mask, cancel the gap
+		if (pGap->m_flags & cancel)
+		{
+//			printf ("Cancelled by %x\n",pGap->m_flags & cancel); 
+			if (pGap->m_trickscript)
+			{
+				// delete the gap trick
+				CGapTrick *pGapTrick = static_cast< CGapTrick* >(m_gaptrick_list.FirstItem());
+				while (pGapTrick)
+				{
+					CGapTrick * pNext = static_cast< CGapTrick* >(pGapTrick->GetNext());
+	
+					// only delete gaptricks if you've not been awarded them, and they exactly match the gap we're cancelling
+					if (!pGapTrick->m_got
+						&& pGapTrick->m_id == pGap->m_id
+						&& pGapTrick->m_script == pGap->m_trickscript
+						&& pGapTrick->m_node == pGap->m_node)
+					{
+						delete pGapTrick;
+						break;
+					}
+	
+					pGapTrick = pNext;
+				}
+			}
+			
+			#ifdef DEBUG_GAPS
+			MESSAGE("DELETING GAP");
+			DUMPC(pGap->m_id);
+			#endif
+	
+			// delete the gap
+			delete pGap;
+		}
+		pGap = pNext;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterGapComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | StartGap | Start a gap...used in conjunction with EndGap. 
+        // See online doc for detailed description and examples
+        // @parmopt name | GapID | Links | the name of the gap
+        // @parmopt array | flags | | array of flags to use
+		// @parmopt name | trickscript | | Script that is run when you land a combo that includes this gap
+		// subject to the two optional conditions below
+		// @parmopt checksum | KeyCombo | | Name of a key combo, like Air_SquareD, you have to do trick with this button combo
+		// @parmopt string | TrickText | | This text substring, like 'kickflip' has to be in the combo
+		// @nextparm | flags | flags | file | gapflags.lst | done
+		// @nextparm | trickscript | script
+		// @nextparm | KeyCombo | list | file | keycombo.lst | done
+		// @parmopt checksum | combined_flags | | An optional way of specifying the cancel flags as a uint32 consisting
+		// of the flag bit values or'd together. Currently only used when the Park Editor calls StartGap from within
+		// the c-code.
+		case CRCC(0x65b9ec39, "StartGap"):
+			start_gap(pParams, pScript);
+			break;
+		
+        // @script | StartGapTrick | This adds a gap trick to the skaters
+        // current list
+        // @parm string | TrickText | text to look for in the trick text 
+        // @parm name | gapscript | script to run when you land this gap trick
+		// @parmopt checksum | KeyCombo | | Name of a key combo, like Air_SquareD, you have to do trick with this button combo
+		case CRCC(0xfc4f2009, "StartGapTrick"):
+			start_gap_trick(pParams, pScript);
+			break;
+			
+        // @script | EndGap | end a gap - used with StartGap 
+        // See online doc for detailed description and examples
+        // @parm name | GapID | gap gap_id
+        // @parmopt string | text | "Unnamed Gap" | text to display
+        // @parmopt int | score | | score to award for this gap
+        // @flag NetEnabled | 
+        // @flag Permanent | 
+        // @parmopt structure | continue | | for linking gaps 
+        // e.g.  continue = { GapID = SecondHalf Cancel = CANCEL_GROUND }
+		// @nextparm | score | slidernum | 0 | 999999
+		case CRCC(0xe5399fb2, "EndGap"):
+			end_gap(pParams, pScript);
+			break;
+		
+		// @script | CheckGapTricks | Check to see if there are any gap tricks,
+		// then check to see if we did the trick yet
+		// if so, then fire them off
+		// note, this does not delete gaps that you did not do
+		// so you would ahve to call ClearGapTricks
+		// in that case
+        case CRCC(0x42f80e5e, "CheckGapTricks"):
+			check_gap_tricks(pParams);
+			break;
+		
+        // @script | ClearGapTricks | clears all the gap tricks.
+        // Currently, I call CheckGapTricks then ClearGapTricks
+        // at any point in the skater's logic where he can land
+        // a trick combo. So if you just stick a "StartGapTrick"
+        // in your gap script, then it will automatically work
+		case CRCC(0x196772b, "ClearGapTricks"):
+			clear_gap_tricks(pParams);
+			break;
+		
+		// @script | DuplicateTrigger | quick way of doing two way gaps 
+        // see online doc for detailed description and examples
+		case CRCC(0x4562e4a6, "DuplicateTrigger"):
+		{
+			// used in a trigger script
+			// find the first node linked to this one that has
+			// a trigger script, and execute that instead
+			{
+				int node = pScript->mNode;
+				Dbg_MsgAssert(node != -1,("DuplicateTrigger in script with no node,\n%s\n",pScript->GetScriptInfo()));
+				if (node != -1)	 		// Failsafe
+				{
+					int links = SkateScript::GetNumLinks(node);
+					//dodgy_test(); printf("node %d has %d links\n",node,links);
+					
+					Dbg_MsgAssert(links>0,("DuplicateTrigger in node (%d) with no links,\n%s\n",node,pScript->GetScriptInfo()));
+					int i;
+					for (i=0;iGetChecksum(CRCD(0x2ca8a299, "TriggerScript"),&script))
+						{
+							// found another trigger script in the node we are linked to
+							GetObject()->SpawnAndRunScript(script,pScript->mNode);
+							break;		
+						}						
+					}
+					Dbg_MsgAssert(iGetScriptInfo()));					
+				}
+			}
+			break;
+		}
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterGapComponent::GetDebugInfo"));
+	
+	static const uint32 p_gap_flag_checksums [   ]
+	= {
+		CRCD(0x9a27e74d, "CANCEL_GROUND"),				// 0x00000001
+		CRCD(0xc6362354, "CANCEL_AIR"),              	// 0x00000002
+		CRCD(0xcd4decee, "CANCEL_RAIL"),             	// 0x00000004
+		CRCD(0x87e4e899, "CANCEL_WALL"),             	// 0x00000008
+		CRCD(0x20e0d12b, "CANCEL_LIP"),              	// 0x00000010
+		CRCD(0x78d5a029, "CANCEL_WALLPLANT"),			// 0x00000020
+		CRCD(0x2d03dae1, "CANCEL_MANUAL"),				// 0x00000040
+		CRCD(0x2a7a145a, "CANCEL_HANG"),     	    	// 0x00000080
+		CRCD(0x0a65d800, "CANCEL_LADDER"),	         	// 0x00000100
+		CRCD(0xa4721771, "CANCEL_SKATE"),				// 0x00000200
+		CRCD(0x19807d3a, "CANCEL_WALK"),   				// 0x00000400
+		CRCD(0x678677cc, "CANCEL_DRIVE"),				// 0x00000800
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG"),			// 0x00001000
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG"),			// 0x00002000
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG"),			// 0x00004000
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG"),			// 0x00008000
+		CRCD(0xae0f7a14, "REQUIRE_GROUND"),				// 0x00010000
+		CRCD(0xdfc901ab, "REQUIRE_AIR"),             	// 0x00020000
+		CRCD(0xe056fc41, "REQUIRE_RAIL"),            	// 0x00040000
+		CRCD(0xaafff836, "REQUIRE_WALL"),            	// 0x00080000
+		CRCD(0x391ff3d4, "REQUIRE_LIP"),             	// 0x00100000
+		CRCD(0xeae2e98f, "REQUIRE_WALLPLANT"),			// 0x00200000
+		CRCD(0x192b47b8, "REQUIRE_MANUAL"),				// 0x00400000
+		CRCD(0x076104f5, "REQUIRE_HANG"),	        	// 0x00800000
+		CRCD(0x3e4d4559, "REQUIRE_LADDER"),	        	// 0x01000000
+		CRCD(0xe236b218, "REQUIRE_SKATE"),				// 0x02000000
+		CRCD(0x349b6d95, "REQUIRE_WALK"),				// 0x04000000
+		CRCD(0x21c2d2a5, "REQUIRE_DRIVE"),				// 0x08000000
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG"),			// 0x10000000
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG"),			// 0x20000000
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG"),			// 0x40000000
+		CRCD(0xc6aa6589, "NO_SUCH_GAP_FLAG")			// 0x80000000
+	};
+	
+	int gap_count = 0;
+	CGap* pGap = static_cast< CGap* >(m_gap_list.FirstItem());
+	while (pGap)
+	{
+		gap_count++;
+		pGap = static_cast< CGap* >(pGap->GetNext());
+	}
+	
+	Script::CArray* p_gaps_array = new Script::CArray;
+	p_gaps_array->SetSizeAndType(gap_count, ESYMBOLTYPE_STRUCTURE);
+	
+	int i = 0;
+	pGap = static_cast< CGap* >(m_gap_list.FirstItem());
+	while (pGap)
+	{
+		Script::CStruct* p_gap_struct = new Script::CStruct;
+		p_gap_struct->AddChecksum(CRCD(0x40c698af, "Id"), pGap->m_id);
+		
+		Script::CStruct* p_gap_flags = new Script::CStruct;
+		for (int n = 0; n < 32; n++)
+		{
+			uint32 mask = 1 << n;
+			if (mask & pGap->m_flags)
+			{
+				p_gap_flags->AddChecksum(NO_NAME, p_gap_flag_checksums[n]);
+			}
+		}
+		p_gap_struct->AddStructurePointer(CRCD(0xf4fabe45, "Flags"), p_gap_flags);
+		
+		p_gaps_array->SetStructure(i, p_gap_struct);
+		i++;
+		pGap = static_cast< CGap* >(pGap->GetNext());
+	}
+	p_info->AddArrayPointer(CRCD(0xd76c173e, "Gaps"), p_gaps_array);
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkaterGapComponent::ClearActiveGaps (   )
+{
+	m_gap_list.DestroyAllNodes();
+	m_gaptrick_list.DestroyAllNodes();
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::ClearPendingGaps (   )
+{
+	Mdl::Skate::Instance()->GetGapChecklist()->ClearPendingGaps();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkaterGapComponent::AwardPendingGaps (   )
+{
+	Mdl::Skate::Instance()->GetGapChecklist()->AwardPendingGaps();
+	
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+	if (!pCareer->GetGlobalFlag(405/*GOT_ALL_GAPS*/) && pCareer->GotAllGaps())
+	{
+		Script::SpawnScript(CRCD(0xcc74cc2e, "got_all_gaps_screen_create"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CSkaterGapComponent::get_state_flags ( int& cancel, int& require )
+{
+	cancel = require = 0;
+	
+	if (!mp_physics_control_component->IsDriving())
+	{
+		if (mp_physics_control_component->IsSkating())
+		{
+			// skating
+			
+			cancel |= CANCEL_SKATE;
+			require |= REQUIRE_SKATE;
+			
+			switch (mp_core_physics_component->GetState())
+			{
+				case AIR:
+					cancel |= CANCEL_AIR;
+					require |= REQUIRE_AIR;
+					break;
+					
+				case GROUND:
+					if (!mp_core_physics_component->GetFlag(OVERRIDE_CANCEL_GROUND)
+						&& !mp_balance_trick_component->GetBalanceTrickType()
+						&& !mp_balance_trick_component->DoingBalanceTrick())
+						// && !mp_core_physics_component->HaveLandedThisFrame())
+					{
+						cancel |= CANCEL_GROUND;
+						require |= REQUIRE_GROUND; 		
+					}
+					if (mp_balance_trick_component->GetBalanceTrickType() || mp_balance_trick_component->DoingBalanceTrick())
+					{
+						cancel |= CANCEL_MANUAL;
+						require |= REQUIRE_MANUAL;
+					}
+					break;
+		
+				case RAIL:
+					cancel |= CANCEL_RAIL;
+					require |= REQUIRE_RAIL;
+					break;
+			
+				case WALL:
+					cancel |= CANCEL_WALL;
+					require |= REQUIRE_WALL; 		
+					break;
+					
+				case LIP:
+					cancel |= CANCEL_LIP;
+					require |= REQUIRE_LIP; 		
+					break;
+					
+				case WALLPLANT:
+					cancel |= CANCEL_WALLPLANT;
+					require |= REQUIRE_WALLPLANT; 		
+					break;
+			}
+		}
+		else
+		{
+			// walking
+			
+			cancel |= CANCEL_WALK;
+			require |= REQUIRE_WALK;
+			
+			switch (mp_walk_component->GetState())
+			{
+				case CWalkComponent::WALKING_AIR:
+					cancel |= CANCEL_AIR;
+					require |= REQUIRE_AIR;
+					break;
+					
+				case CWalkComponent::WALKING_GROUND:
+					cancel |= CANCEL_GROUND;
+					require |= REQUIRE_GROUND;
+					break;
+					
+				case CWalkComponent::WALKING_HANG:
+					cancel |= CANCEL_HANG;
+					require |= REQUIRE_HANG;
+					break;
+					
+				case CWalkComponent::WALKING_LADDER:
+					cancel |= CANCEL_LADDER;
+					require |= REQUIRE_LADDER;
+					break;
+					
+				default:
+					break;
+			}
+		}
+	}
+	else
+	{
+		// driving
+		
+		cancel |= CANCEL_DRIVE;
+		require |= REQUIRE_DRIVE;
+		
+		// driving gaps are a bit of a kludge right now
+		
+		CCompositeObject* p_vehicle = static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x824c6a24, "PlayerVehicle")));
+		Dbg_Assert(p_vehicle);
+		CVehicleComponent* p_vehicle_component = GetVehicleComponentFromObject(p_vehicle);
+		Dbg_Assert(p_vehicle_component);
+		
+		if (p_vehicle_component->IsOnGround())
+		{
+			cancel |= CANCEL_GROUND;
+			require |= REQUIRE_GROUND;
+		}
+		else
+		{
+			cancel |= CANCEL_AIR;
+			require |= REQUIRE_AIR;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::start_gap ( Script::CStruct *pParams, Script::CScript* pScript )
+{
+	if (mp_core_physics_component->GetFlag(IS_BAILING)) return;
+						  
+	uint32 gap_id = 0;
+	pParams->GetChecksum(CRCD(0x3b442e26, "GapID"), &gap_id);
+	if (!gap_id)
+	{
+		// no gap_id is assumed to indicate a "Links" gap
+		Dbg_MsgAssert(pScript->mNode != -1, ("\n%s\nLink gap with no node", pScript->GetScriptInfo()));
+		Dbg_MsgAssert(SkateScript::GetNumLinks(pScript->mNode), ("\n%s\nLink gap with no links", pScript->GetScriptInfo()));
+	}
+	
+	#ifdef DEBUG_GAPS
+	MESSAGE("STARTING GAP");
+	DUMPC(gap_id);
+	#endif
+	
+	// check to see if there are any other gaps from this node with this ID and delete them
+	// (note that this means you can only have one "Links" gap from a single node although there can be many targets)
+	CGap* pGap = static_cast< CGap* >(m_gap_list.FirstItem());
+	while (pGap)
+	{
+		CGap* pNext = static_cast< CGap* >(pGap->GetNext());
+
+		if (pGap->m_id == gap_id && static_cast< int >(pGap->m_node) == pScript->mNode)
+		{
+			delete pGap;
+		}
+		pGap = pNext;
+	}
+
+	// create the gap and add the info to it
+	pGap = new CGap;
+	m_gap_list.AddToHead(pGap);
+	pGap->m_id = gap_id;
+	pGap->m_node = pScript->mNode;
+	pGap->m_trickTextChecksum = 0;
+	
+	// Take a snapshot of the skater's position & orientation so that if the gap is successfully
+	// got this info can be recorded into the CGapCheck instance and used to determine a good
+	// camera position for viewing the gap in the view-gaps menu.
+	pGap->m_skater_start_pos=((CSkater*)GetObject())->GetCamera()->GetPos();
+	pGap->m_skater_start_dir=-((CSkater*)GetObject())->GetCamera()->GetMatrix()[Mth::AT];
+
+	// if it has a trickscript, it's a gaptrick
+	uint32 trickscript;
+	if (pParams->GetChecksum(CRCD(0xa26994e6, "trickscript"), &trickscript))
+	{
+		uint32 key_combo;
+		const char* tricktext;
+		Script::CArray* p_gapTricks;
+		
+		pGap->m_trickscript = trickscript;
+		
+		Dbg_Assert(mp_score);
+
+		if (pParams->GetChecksum(CRCD(0x95e16467,"KeyCombo"), &key_combo))
+		{
+			pGap->m_trickChecksum = key_combo;
+
+			int spin;
+			if ( pParams->GetInteger(CRCD(0xedf5db70, "spin"), &spin))
+			{
+				pGap->m_requirePerfect = pParams->ContainsFlag(CRCD(0x1c39f1b9, "perfect"));
+				Dbg_MsgAssert(spin % 180 == 0, ("StartGap called with a spin value of %i which is not a multiple of 180", spin));
+				pGap->m_spinMult = spin / 180;
+			}
+			pParams->GetInteger( CRCD(0xa4bee6a1,"num_taps"), &pGap->m_numTaps, Script::NO_ASSERT );
+
+			pGap->m_numTrickOccurrences = mp_score->GetPreviousNumberOfOccurrences( key_combo, pGap->m_spinMult, pGap->m_numTaps );
+		}
+		else if (pParams->GetString(CRCD(0x3eafa520, "TrickText"), &tricktext))
+		{
+			pGap->m_trickTextChecksum = Script::GenerateCRC(tricktext);
+
+			int spin;
+			if (pParams->GetInteger(CRCD(0xedf5db70, "spin"), &spin))
+			{
+				pGap->m_requirePerfect = pParams->ContainsFlag(CRCD(0x1c39f1b9, "perfect"));
+				Dbg_MsgAssert(spin % 180 == 0, ("StartGap called with a spin value of %i which is not a multiple of 180", spin));
+				pGap->m_spinMult = spin / 180;
+			}
+			
+			pParams->GetInteger( CRCD(0xa4bee6a1,"num_taps"), &pGap->m_numTaps, Script::NO_ASSERT );
+
+			pGap->m_numTrickOccurrences = mp_score->GetPreviousNumberOfOccurrencesByName( pGap->m_trickTextChecksum, pGap->m_spinMult, pGap->m_numTaps );
+		}
+		else if (pParams->GetArray(CRCD(0x1e26fd3e, "tricks"), &p_gapTricks))
+		{
+			Script::CopyArray(pGap->mp_tricks, p_gapTricks);
+			pGap->m_startGapTrickCount = mp_score->GetCurrentTrickCount();
+		}
+		
+		// create the gap trick object
+		start_gap_trick(pParams, pScript);
+	}
+
+	
+	pGap->m_flags = 0;
+	Script::CArray* pArray = NULL;			
+	pParams->GetArray(CRCD(0xf4fabe45, "flags"), &pArray);
+	if (pArray)
+	{
+		for (uint32 i = 0; i < pArray->GetSize(); i++)
+		{
+			int checksum = pArray->GetChecksum(i);
+			pGap->m_flags |= Script::GetInteger(checksum);	
+		}
+	}
+	else
+	{
+		uint32 checksum;
+		if (pParams->GetChecksum(CRCD(0xf4fabe45, "flags"), &checksum, false))
+		{
+			pGap->m_flags |= Script::GetInteger(checksum);
+		}
+	}
+
+	// K: Added this as part of implementing ability to set cancel type for gaps in the Park Editor. (TT1969)
+	// When the park editor runs the StartGap command, it generates the passed parameters in the C-code.
+	// At that point, the PE already has the or'd together cancel flags. It would be tricky to try to construct
+	// the array of flag checksums at that point, since it would have to do lots of compares with script globals
+	// and if any new ones were added we'd have to remember to update that bit of code.
+	// Easier to just support passing of the combined flags here.
+	uint32 combined_flags=0;
+	if (pParams->GetChecksum(CRCD(0x2760de9e,"combined_flags"),&combined_flags))
+	{
+		pGap->m_flags |= combined_flags;
+	}
+	
+	uint32 car_cancel_flags=CANCEL_SKATE | CANCEL_WALK;
+	
+	if (pParams->ContainsFlag(CRCD(0xc442e33d, "carGap")))
+	{
+		// car gaps cancel when skating or walking
+		pGap->m_flags |= car_cancel_flags;
+	}
+	else if (pParams->ContainsFlag(CRCD(0xe3ed4ade, "complexGap")))
+	{
+		// complex gaps must be setup completely by their designer
+	}
+	else
+	{
+		// default to gaps which cancel driving
+		if (pGap->m_flags & car_cancel_flags)
+		{
+			// Unless we decided we did want to drive earlier
+		}
+		else
+		{
+			pGap->m_flags |= CANCEL_DRIVE;
+		}	
+	}
+	
+	// Because the car can trigger multiple trigger scripts in the same frame, we must allow the gap to be canceled immediately.  Otherwise, if one wheel
+	// triggers a start gap and another an end gap in the same frame, the cancel flags of the gap will never be checked.
+	if (mp_physics_control_component->IsDriving() && (pGap->m_flags & CANCEL_DRIVE)
+		// Created park gaps can be so close together that we might not have a frame with which to check our cancel flags
+		|| Ed::CParkEditor::Instance()->UsingCustomPark())
+	{
+		int cancel, require;
+		get_state_flags(cancel, require);
+		UpdateCancelRequire(cancel, require);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::start_gap_trick ( Script::CStruct *pParams, Script::CScript* pScript )
+{
+	// create the gap trick and add the info to it
+	CGapTrick* pGapTrick = new CGapTrick;
+	m_gaptrick_list.AddToHead(pGapTrick);
+	pGapTrick->m_node = pScript->mNode;
+	pGapTrick->m_got = false;
+	pGapTrick->m_frame = m_frame_count;
+
+	uint32 gap_id = 0;
+	pParams->GetChecksum(CRCD(0x3b442e26, "GapId"), &gap_id);
+	pGapTrick->m_id = gap_id;
+
+	const char *p_trickString = NULL;
+	pParams->GetString(CRCD(0x3eafa520, "TrickText"), &p_trickString);
+	pGapTrick->m_trickString = p_trickString;
+	
+	pGapTrick->m_script = 0;
+	pParams->GetChecksum(CRCD(0xa26994e6, "trickscript"), &pGapTrick->m_script, Script::ASSERT);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::end_gap ( Script::CStruct *pParams, Script::CScript* pScript )
+{
+	if (mp_core_physics_component->GetFlag(IS_BAILING)) return;
+	
+	uint32 gap_id = 0;
+	pParams->GetChecksum(CRCD(0x3b442e26, "GapID"), &gap_id);
+	
+	#ifdef DEBUG_GAPS
+	MESSAGE("ENDING GAP");
+	DUMPC(gap_id);
+	#endif
+	
+	Dbg_Assert(mp_score);
+
+	// find the ending gap in our gap list
+	CGap* pNext;
+	for (CGap* pGap = static_cast< CGap* >(m_gap_list.FirstItem()); pGap; pGap = pNext)
+	{
+		pNext = static_cast< CGap* >(pGap->GetNext());
+		
+		if (pGap->m_id != gap_id) continue;
+		
+		// check if its got a gap_id, or if the gaps node is linked to this node					
+		if (gap_id == 0 && !SkateScript::IsLinkedTo(pGap->m_node, pScript->mNode)) continue;
+				
+		// found the gap
+		
+		// check requirements
+		if (pGap->m_flags & REQUIRE_MASK)
+		{							 
+			// requirements failed, so just delete the gap
+			delete pGap;
+			continue;
+		}
+		
+		// requirements met
+
+		// Get the gap name, this is what is displayed as the trick name for this gap.
+		// This is not required, and will default if not there.
+		const char *p_name;
+		if (!pParams->GetString(CRCD(0xc4745838, "text"), &p_name))
+		{
+			p_name = "Unnamed Gap";
+		}
+		char gap_name[256];
+		sprintf(gap_name, "\\c1%s\\c0", p_name);
+
+
+		// check for gap trick; this MUST occur before the gap is added to the trick string
+		if (pGap->m_trickscript != 0)
+		{
+			bool gotGapTrick = false;
+			
+			if (pGap->m_trickChecksum != 0 || pGap->m_trickTextChecksum != 0 || pGap->mp_tricks->GetSize() > 0)
+			{
+				// see if the trick has been done since the gap started
+				if ( pGap->m_trickChecksum && pGap->m_numTrickOccurrences < mp_score->GetCurrentNumberOfOccurrences( pGap->m_trickChecksum, pGap->m_spinMult, pGap->m_requirePerfect, pGap->m_numTaps ) )
+				{
+					gotGapTrick = true;
+				}
+				else if ( pGap->m_trickTextChecksum && pGap->m_numTrickOccurrences < mp_score->GetCurrentNumberOfOccurrencesByName( pGap->m_trickTextChecksum, pGap->m_spinMult, pGap->m_requirePerfect, pGap->m_numTaps ) )
+				{
+					gotGapTrick = true;
+				}
+				else if ( pGap->mp_tricks->GetSize() > 0 && mp_score->GetCurrentNumberOfOccurrences( pGap->mp_tricks, pGap->m_startGapTrickCount ) > 0 )
+				{
+					gotGapTrick = true;
+				}
+
+				// if they get the gap trick
+				if ( gotGapTrick )
+				{
+					// get the gap trick
+					CGapTrick* pGapTrick = static_cast< CGapTrick* >(m_gaptrick_list.FirstItem());
+					while ( pGapTrick )
+					{
+						CGapTrick* pNext = static_cast< CGapTrick* >(pGapTrick->GetNext());
+						
+						if (pGapTrick->m_id == pGap->m_id)
+						{
+							pGapTrick->GetGapTrick();
+							break;
+						}
+
+						pGapTrick = pNext;
+					}
+				} // END if they get the gap trick
+			}
+			else 
+			{
+				// if no trick script, look for a no requirement gap trick
+				CGapTrick* pGapTrick = static_cast< CGapTrick* >(m_gaptrick_list.FirstItem());
+				while (pGapTrick)
+				{
+					CGapTrick* pNext = static_cast< CGapTrick* >(pGapTrick->GetNext());
+
+					if (pGapTrick->m_id == pGap->m_id)
+					{
+						pGapTrick->GetGapTrick();
+						break;
+					}
+					pGapTrick = pNext;
+				}
+			}
+		} // END if trickscript != 0
+
+
+		Mdl::Skate::Instance()->GetGapChecklist()->SetInfoForCamera(p_name,pGap->m_skater_start_pos,pGap->m_skater_start_dir);
+		
+		// Get the score for this gap.
+		// If there is no score defined, then we don't award a trick.
+		int score = 0;
+		if (pParams->GetInteger(CRCD(0xcd66c8ae, "score"), &score))
+		{
+			Mdl::Score::Flags flags = Mdl::Score::vGAP;
+			if (GetSkater()->IsLocalClient())
+			{
+				mp_score->Trigger(gap_name, score, flags);
+				
+				CTrickComponent* p_trick_component = GetTrickComponentFromObject(GetObject());
+				Dbg_Assert(p_trick_component);
+				p_trick_component->SetFirstTrickStarted(true);
+
+				Mdl::Skate::Instance()->GetGapChecklist()->GetGapByText(p_name);  
+				  
+				GetObject()->SpawnAndRunScript(CRCD(0x541a2485, "DefaultGapScript"));
+			}
+		}
+		
+		//  execute the gap_script
+		uint32 gap_script;
+		if (pParams->GetChecksum(CRCD(0x7c9e51ab, "gapscript"), &gap_script))
+		{
+			GetObject()->SpawnAndRunScript(
+				gap_script,
+				pScript->mNode,
+				pParams->ContainsFlag(CRCD(0x20209c31, "NetEnabled")),
+				pParams->ContainsFlag(CRCD(0x23627fd7, "Permanent"))
+			);
+		}	  			  
+		
+		// handle for continue script parameter
+		Script::CStruct * p_continue = NULL;
+		if (pParams->GetStructure(CRCD(0xec1cd520, "continue"), &p_continue))
+		{
+			start_gap(p_continue, pScript);
+		}										  
+
+		// Broadcast an event saying we got the gap
+		// won't happen very often, and broadcasing is cheap)
+		// we also pass in the parameters, just in case we want to do 
+		uint32	id_got = Crc::ExtendCRCWithString(gap_id,"_Success");
+		Obj::CTracker::Instance()->LaunchEvent( id_got, 0xffffffff, GetObject()->GetID(), pParams, true /*, radius */  );
+						
+		// Script functions we might have called could have deleted this gap and maybe the next gap.
+		// The safest things is to check to see if this one is still there, delete it, then reset back to the start of the list.
+		CGap* pKill = static_cast< CGap* >(m_gap_list.FirstItem());
+		while (pKill)
+		{
+			if (pKill == pGap)
+			{
+				delete pGap;
+				break;
+			}
+			pKill = static_cast< CGap* >(pKill->GetNext());
+		}
+		
+		// restart our pass through the gaps
+		pNext = static_cast< CGap* >(m_gap_list.FirstItem());
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::check_gap_tricks ( Script::CStruct *pParams )
+{
+	// look for completed gaps and run their scripts
+	CGapTrick* pGapTrick = static_cast< CGapTrick* >(m_gaptrick_list.FirstItem());
+	while (pGapTrick)
+	{
+		CGapTrick* pNext = static_cast< CGapTrick* >(pGapTrick->GetNext());
+		if (pGapTrick->m_got)
+		{
+			GetObject()->SpawnAndRunScript(pGapTrick->m_script, pGapTrick->m_node);
+		}
+		pGapTrick = pNext;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterGapComponent::clear_gap_tricks ( Script::CStruct *pParams )
+{
+	if (pParams->ContainsFlag(CRCD(0x97975226, "NotInSameFrame")))
+	{
+		// delete all gap tricks leaving those created this frame
+		CGapTrick* pGapTrick = static_cast< CGapTrick* >(m_gaptrick_list.FirstItem());
+		while (pGapTrick)
+		{
+			CGapTrick* pNext = static_cast< CGapTrick* >(pGapTrick->GetNext());
+			if (pGapTrick->m_frame != m_frame_count)
+			{
+				delete pGapTrick;
+			}
+			pGapTrick = pNext;
+		}
+	}
+	else
+	{
+		// delete all gap tricks
+		CGapTrick* pGapTrick = static_cast< CGapTrick* >(m_gaptrick_list.FirstItem());
+		while (pGapTrick)
+		{
+			CGapTrick* pNext = static_cast< CGapTrick* >(pGapTrick->GetNext());
+			delete pGapTrick;
+			pGapTrick = pNext;
+		}
+	}
+}
+
+}
diff --git a/Code/Sk/Components/SkaterGapComponent.h b/Code/Sk/Components/SkaterGapComponent.h
new file mode 100644
index 0000000..5fa83af
--- /dev/null
+++ b/Code/Sk/Components/SkaterGapComponent.h
@@ -0,0 +1,99 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterGapComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/5/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERGAPCOMPONENT_H__
+#define __COMPONENTS_SKATERGAPCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERGAP CRCD(0x11b4c67f, "SkaterGap")
+
+#define		GetSkaterGapComponent() ((Obj::CSkaterGapComponent*)GetComponent(CRC_SKATERGAP))
+#define		GetSkaterGapComponentFromObject(pObj) ((Obj::CSkaterGapComponent*)(pObj)->GetComponent(CRC_SKATERGAP))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+
+namespace Mdl
+{
+	class Score;
+}
+              
+namespace Obj
+{
+	class CGap;
+	class CGapTrick;
+	class CSkaterCorePhysicsComponent;
+	class CSkaterBalanceTrickComponent;
+	class CSkaterPhysicsControlComponent;
+	class CWalkComponent;
+
+class CSkaterGapComponent : public CBaseComponent
+{
+public:
+    CSkaterGapComponent();
+    virtual ~CSkaterGapComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	void 							UpdateCancelRequire(int cancel, int require);
+	
+	void							SetAssociatedScore ( Mdl::Score* p_score ) { mp_score = p_score; }
+	void							ClearActiveGaps (   );
+	void							ClearPendingGaps (   );
+	void							AwardPendingGaps (   );
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+
+	static CBaseComponent*			s_create();
+	
+private:
+	void							get_state_flags ( int& cancel, int& require );
+	void							start_gap ( Script::CStruct *pParams, Script::CScript* pScript );
+	void							start_gap_trick ( Script::CStruct *pParams, Script::CScript* pScript );
+	void							end_gap ( Script::CStruct *pParams, Script::CScript* pScript );
+	void							check_gap_tricks ( Script::CStruct *pParams );
+	void							clear_gap_tricks ( Script::CStruct *pParams );
+	
+private:
+	// list of active gaps
+	Lst::Head< CGap >				m_gap_list;
+	
+	// list of active gapTricks
+	Lst::Head< CGapTrick >			m_gaptrick_list;
+	
+	// count frames to allow timestamping of gap tricks
+	uint32							m_frame_count;
+	
+	// associated score object
+	Mdl::Score*						mp_score;
+	
+	// peer components
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+	CSkaterBalanceTrickComponent*	mp_balance_trick_component;
+	CSkaterPhysicsControlComponent*	mp_physics_control_component;
+	CWalkComponent*					mp_walk_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterLocalNetLogicComponent.cpp b/Code/Sk/Components/SkaterLocalNetLogicComponent.cpp
new file mode 100644
index 0000000..e1681a1
--- /dev/null
+++ b/Code/Sk/Components/SkaterLocalNetLogicComponent.cpp
@@ -0,0 +1,530 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterLocalNetLogicComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/12/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterLocalNetLogicComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterLocalNetLogicComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterLocalNetLogicComponent::CSkaterLocalNetLogicComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERLOCALNETLOGIC );
+	
+	mp_state_component = NULL;
+	mp_physics_component = NULL;
+	m_last_update_time = 0;
+	m_last_sent_terrain = -1;
+	m_last_sent_flags = 0;
+	m_last_sent_state = 0;
+	m_last_sent_doing_trick = -1;
+	m_last_sent_rail = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterLocalNetLogicComponent::~CSkaterLocalNetLogicComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLocalNetLogicComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterLocalNetLogicComponent added to non-skater composite object"));
+	Dbg_MsgAssert(GetSkater()->IsLocalClient(), ("CSkaterLocalNetLogicComponent added to non-local skater"));
+	
+	m_last_update_time = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLocalNetLogicComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLocalNetLogicComponent::Finalize (   )
+{
+	
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+	mp_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_state_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLocalNetLogicComponent::Update()
+{
+	if (!Mdl::Skate::Instance()->IsMultiplayerGame())
+	{
+		//Suspend(true);
+		return;
+	}
+	if( GameNet::Manager::Instance()->GetLocalPlayer() == NULL )
+	{
+		return;
+	}
+		
+	network_update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterLocalNetLogicComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLocalNetLogicComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterLocalNetLogicComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+
+/******************************************************************/
+/* Print a graffiti steal message  								  */
+/* 						  										  */
+/******************************************************************/
+
+int	CSkaterLocalNetLogicComponent::sHandleStealMessage( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgStealMessage* p_msg = (GameNet::MsgStealMessage *) context->m_Msg;
+	
+	if (p_msg->m_GameId != GameNet::Manager::Instance()->GetNetworkGameId()) return Net::HANDLER_MSG_DONE;
+
+	Obj::CSkater* pCurrSkater = (CSkater*) context->m_Data;
+	Dbg_Assert( pCurrSkater );
+
+	// For now, just exit out in these cases to avoid a crash
+	Obj::CSkater* pNewSkater = Mdl::Skate::Instance()->GetSkaterById( p_msg->m_NewOwner );
+	if( pNewSkater == NULL )
+	{
+		return Net::HANDLER_CONTINUE;
+	}
+
+	Obj::CSkater* pOldSkater = Mdl::Skate::Instance()->GetSkaterById( p_msg->m_OldOwner );
+	if( pOldSkater == NULL )
+	{
+		return Net::HANDLER_CONTINUE;
+	}
+
+	// TODO:  Maybe send the color of this skater's graffiti tags
+	
+	if ( pCurrSkater->GetID() == (uint32)p_msg->m_NewOwner )
+	{
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		pTempStructure->Clear();
+		pTempStructure->AddComponent( CRCD(0xa4b08520, "String0"), ESYMBOLTYPE_STRING, pOldSkater->GetDisplayName() );
+		Script::RunScript( CRCD(0xb3ff911, "GraffitiStealYou"), pTempStructure, pCurrSkater );
+		delete pTempStructure;
+	}
+	else if ( pCurrSkater->GetID() == (uint32)p_msg->m_OldOwner )
+	{
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		pTempStructure->Clear();
+		pTempStructure->AddComponent( CRCD(0xa4b08520, "String0"), ESYMBOLTYPE_STRING, pNewSkater->GetDisplayName() );
+		Script::RunScript( CRCD(0x4d7f6ffa, "GraffitiStealOther"), pTempStructure, pCurrSkater );
+		delete pTempStructure;
+	}
+	else
+	{
+		// useless steal message encountered
+		Dbg_Assert( 0 );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CSkaterLocalNetLogicComponent::get_update_flags()
+{
+	Net::Client* client;
+	Mth::Vector eulers;
+	short pos[3];
+	short rot[3];
+	char doing_trick, state, terrain, walking, driving;
+	Flags< int > skater_flags;
+	Flags< int > end_run_flags;
+	int i;
+	bool on_server, force_send;   
+	int update_flags;
+	sint16 rail_node;
+	Obj::CSkaterEndRunComponent* p_skater_endrun_component;
+
+	update_flags = 0;
+
+	client = GameNet::Manager::Instance()->GetClient( GetSkater()->m_skater_number );
+	Dbg_Assert( client );
+
+	p_skater_endrun_component = GetSkaterEndRunComponentFromObject(GetSkater());
+
+	on_server = GameNet::Manager::Instance()->OnServer();
+	force_send = on_server || ( !( client->m_FrameCounter % vFORCE_SEND_INTERVAL )); 
+
+	GetObject()->GetDisplayMatrix().GetEulers( eulers );
+	
+	state = (char) mp_state_component->m_state;
+	terrain = (char) mp_state_component->m_terrain;
+	doing_trick = (char) mp_state_component->m_doing_trick;
+	walking = (char) (mp_state_component->m_physics_state == WALKING ? true : false);
+	driving = (char) mp_state_component->m_driving;
+	rail_node = mp_physics_component->GetRailNode();
+
+	for( i = 0; i < NUM_ESKATERFLAGS; i++ )
+	{     		
+		skater_flags.Set( i, mp_state_component->m_skater_flags[( ESkaterFlag ) i ].Get() );
+	}
+	end_run_flags = p_skater_endrun_component->GetFlags();
+
+	for( i = 0; i < 3; i++ )
+	{
+		if( i == Y )
+		{
+			pos[i] = (short) ( GetObject()->m_pos[i] * 4.0f );
+			//pos[i] = GetObject()->m_pos[i];
+		}
+		else
+		{
+			pos[i] = (short) ( GetObject()->m_pos[i] * 2.0f );
+			//pos[i] = GetObject()->m_pos[i];
+		}
+		if(( pos[i] != m_last_sent_pos[i] ) || force_send )
+		{
+			if( i == X )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_POS_X;
+			}
+			else if( i == Y )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_POS_Y;
+			}
+			else if( i == Z )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_POS_Z;
+			}
+		}
+	}
+    
+	for( i = 0; i < 3; i++ )
+	{
+		rot[i] = (short) ( eulers[i] * 4096.0f );
+		if(( rot[i] != m_last_sent_rot[i] ) || force_send )
+		{   
+			m_last_sent_rot[i] = rot[i];
+			if( i == X )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_ROT_X;
+			}
+			else if( i == Y )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_ROT_Y;
+			}
+			else if( i == Z )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_ROT_Z;
+			}
+		}
+	}
+    
+	if( ( state != m_last_sent_state ) ||
+		( doing_trick != m_last_sent_doing_trick ) ||
+		( terrain != m_last_sent_terrain ) ||
+		( walking != m_last_sent_walking ) ||
+		( driving != m_last_sent_driving ) ||
+		( force_send ))
+	{
+			update_flags |= GameNet::mUPDATE_FIELD_STATE;
+	}
+
+    if(( skater_flags != m_last_sent_flags ) || 
+	   ( end_run_flags != m_last_sent_end_run_flags ) || 
+	   ( force_send ))
+	{
+		update_flags |= GameNet::mUPDATE_FIELD_FLAGS;
+	}
+
+	if( rail_node != m_last_sent_rail )
+	{
+		update_flags |= GameNet::mUPDATE_FIELD_RAIL_NODE;
+	}
+
+	return update_flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLocalNetLogicComponent::network_update ( void )
+{
+	// update the server with our current state so that he can relay the data to other clients
+	
+	Net::Client* client;
+	Net::MsgMax p_msg;
+	Mth::Vector eulers;
+	short pos[3];
+	short rot[3];
+	char doing_trick, state, terrain, walking, driving;
+	Flags< int > skater_flags;
+	Flags< int > end_run_flags;
+	int i, msg_len;
+	sint16 rail_node;
+	int update_flags;
+	bool on_server, force_send;   
+	Net::MsgDesc msg_desc;
+	Net::BitStream stream;
+	Obj::CSkaterEndRunComponent* p_skater_endrun_component;
+
+	client = GameNet::Manager::Instance()->GetClient( GetSkater()->m_skater_number );
+	Dbg_MsgAssert( client, ( "Could not find client: %d\n", GetSkater()->m_skater_number ));
+
+	p_skater_endrun_component = GetSkaterEndRunComponentFromObject(GetSkater());
+
+	on_server = GameNet::Manager::Instance()->OnServer();
+	force_send = on_server || ( !( client->m_FrameCounter % vFORCE_SEND_INTERVAL )); 
+
+	// Send object updates to narrowband servers only every other frame (except for forced-send frames)
+	if( !on_server && !force_send )
+	{
+		Lst::Search< Net::Conn > sh;
+		Net::Conn* server_conn;
+		
+		server_conn = client->FirstConnection( &sh );
+		Dbg_Assert( server_conn );
+
+		// Only send object updates as often as we send packets
+		if(( client->m_Timestamp - m_last_update_time ) < (unsigned int) server_conn->GetSendInterval())
+		{
+			return;
+		}
+	}
+
+	m_last_update_time = client->m_Timestamp;
+
+	stream.SetOutputData( p_msg.m_Data, 1024 );
+	
+	GetObject()->GetDisplayMatrix().GetEulers( eulers );
+	
+	state = (char) mp_state_component->m_state;
+	terrain = (char) mp_state_component->m_terrain;
+	doing_trick = (char) mp_state_component->m_doing_trick;
+	walking = (char) (mp_state_component->m_physics_state == WALKING ? true : false);
+	driving = (char) mp_state_component->m_driving;
+	rail_node = mp_physics_component->GetRailNode();
+
+	for( i = 0; i < NUM_ESKATERFLAGS; i++ )
+	{     		
+		skater_flags.Set( i, mp_state_component->m_skater_flags[( ESkaterFlag ) i ].Get() );
+	}
+	
+	end_run_flags = p_skater_endrun_component->GetFlags();
+
+	// Write out the time for which this state info is valid
+	stream.WriteValue( client->m_Timestamp, sizeof( int ) * 8 );
+	update_flags = get_update_flags();
+	stream.WriteValue( update_flags, 9 );
+
+	if( on_server )
+	{
+		static Mth::Vector last_pos;
+		Mth::Vector diff;
+
+		diff = GetObject()->m_pos - last_pos;
+		//Dbg_Printf( "Vel: %f %f %f\n", diff[X], diff[Y], diff[Z] );
+		last_pos = GetObject()->m_pos;
+	}
+
+// Write out the object's position as three shorts (fixed-point)
+	for( i = 0; i < 3; i++ )
+	{
+		if( i == Y )
+		{
+			pos[i] = (short) ( GetObject()->m_pos[i] * 4.0f );
+			//pos[i] = GetObject()->m_pos[i];
+		}
+		else
+		{
+			pos[i] = (short) ( GetObject()->m_pos[i] * 2.0f );
+			//pos[i] = GetObject()->m_pos[i];
+		}
+		if( i == X )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_POS_X )
+			{
+				stream.WriteValue( pos[i], sizeof( short ) * 8 );
+				//stream.WriteFloatValue( pos[i] );
+			}
+		}
+		else if( i == Y )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_POS_Y )
+			{
+				stream.WriteValue( pos[i], sizeof( short ) * 8 );
+				//stream.WriteFloatValue( pos[i] );
+			}
+		}
+		else if( i == Z )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_POS_Z )
+			{
+				stream.WriteValue( pos[i], sizeof( short ) * 8 );
+				//stream.WriteFloatValue( pos[i] );
+			}
+		}
+
+		m_last_sent_pos[i] = pos[i];
+	}
+    
+	// Write out the object's orientation as three short euler angles (fixed-point)
+	for( i = 0; i < 3; i++ )
+	{
+		rot[i] = (short) ( eulers[i] * 4096.0f );
+		if( i == X )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_ROT_X )
+			{
+				stream.WriteValue( rot[i], sizeof( short ) * 8 );
+			}
+		}
+		else if( i == Y )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_ROT_Y )
+			{
+				stream.WriteValue( rot[i], sizeof( short ) * 8 );
+			}
+		}
+		else if( i == Z )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_ROT_Z )
+			{
+				stream.WriteValue( rot[i], sizeof( short ) * 8 );
+			}
+		}
+
+		m_last_sent_rot[i] = rot[i];
+	}
+    
+	// Write out the skater's 'state'
+	// Write out the skater's 'doing trick' state
+	// Write out the skater's terrain
+	if( update_flags & GameNet::mUPDATE_FIELD_STATE )
+	{
+		char mask;
+
+		m_last_sent_state = state;
+		m_last_sent_doing_trick = doing_trick;
+		m_last_sent_terrain = terrain;
+		m_last_sent_walking = walking;
+		m_last_sent_driving = driving;
+
+		mask = state;
+		if( doing_trick )
+		{
+			mask |= GameNet::mDOING_TRICK_MASK;
+		}
+		
+		stream.WriteValue( mask, 4 );
+		stream.WriteValue( terrain, 6 );
+		stream.WriteValue( walking, 1 );
+		stream.WriteValue( driving, 1 );
+	}
+
+    // Write out the skaters' flags
+	if( update_flags & GameNet::mUPDATE_FIELD_FLAGS )
+	{
+		m_last_sent_flags = skater_flags;
+		m_last_sent_end_run_flags = end_run_flags;
+		stream.WriteValue( skater_flags, 5 );
+		stream.WriteValue( end_run_flags, 3 );
+	}
+
+	if( update_flags & GameNet::mUPDATE_FIELD_RAIL_NODE )
+	{
+		m_last_sent_rail = rail_node;
+		stream.WriteValue( rail_node, sizeof( sint16 ) * 8 );
+	}
+
+	stream.Flush();
+	msg_len = stream.GetByteLength();
+
+	msg_desc.m_Id = GameNet::MSG_ID_OBJ_UPDATE_STREAM;
+	msg_desc.m_Length = msg_len;
+	msg_desc.m_Data = &p_msg;
+	msg_desc.m_Singular = true;
+	msg_desc.m_Priority = Net::NORMAL_PRIORITY + 1;
+	client->EnqueueMessageToServer( &msg_desc );
+}
+                                           
+}
\ No newline at end of file
diff --git a/Code/Sk/Components/SkaterLocalNetLogicComponent.h b/Code/Sk/Components/SkaterLocalNetLogicComponent.h
new file mode 100644
index 0000000..a846dec
--- /dev/null
+++ b/Code/Sk/Components/SkaterLocalNetLogicComponent.h
@@ -0,0 +1,79 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterLocalNetLogicComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/12/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERLOCALNETLOGICCOMPONENT_H__
+#define __COMPONENTS_SKATERLOCALNETLOGICCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERLOCALNETLOGIC CRCD(0x7cd3e6d5, "SkaterLocalNetLogic")
+
+#define		GetSkaterLocalNetLogicComponent() ((Obj::CSkaterLocalNetLogicComponent*)GetComponent(CRC_SKATERLOCALNETLOGIC))
+#define		GetSkaterLocalNetLogicComponentFromObject(pObj) ((Obj::CSkaterLocalNetLogicComponent*)(pObj)->GetComponent(CRC_SKATERLOCALNETLOGIC))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterCorePhysicsComponent;
+	class CSkaterStateComponent;
+
+class CSkaterLocalNetLogicComponent : public CBaseComponent
+{
+public:
+    CSkaterLocalNetLogicComponent();
+    virtual ~CSkaterLocalNetLogicComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+	static int						sHandleStealMessage( Net::MsgHandlerContext* context );
+	
+private:
+	void							network_update();
+	int								get_update_flags();
+	
+	Tmr::Time						m_last_update_time;
+	
+	char							m_last_sent_terrain;
+	short							m_last_sent_pos[3];
+	short							m_last_sent_rot[3];
+	Flags< int >					m_last_sent_flags;
+	Flags< int >					m_last_sent_end_run_flags;
+	char							m_last_sent_state;
+	char							m_last_sent_doing_trick;
+	char							m_last_sent_walking;
+	char							m_last_sent_driving;
+	sint16							m_last_sent_rail;
+	
+private:
+	CSkaterStateComponent*			mp_state_component;
+	CSkaterCorePhysicsComponent*	mp_physics_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterLoopingSoundComponent.cpp b/Code/Sk/Components/SkaterLoopingSoundComponent.cpp
new file mode 100644
index 0000000..01a1eae
--- /dev/null
+++ b/Code/Sk/Components/SkaterLoopingSoundComponent.cpp
@@ -0,0 +1,385 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterLoopingSoundComponent.cpp
+//* OWNERD:			Dan
+//* CREATION DATE:  2/26/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterLoopingSoundComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterLoopingSoundComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterLoopingSoundComponent::CSkaterLoopingSoundComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERLOOPINGSOUND );
+	
+	m_is_bailing = m_is_rail_sliding = false;
+	m_have_sound_info = false;
+	m_update_sound_info = true;
+	m_StateType = AIR;
+	m_vol_mult = 1.0f;
+	m_active = true;
+	m_unpause = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterLoopingSoundComponent::~CSkaterLoopingSoundComponent()
+{
+	StopLoopingSound();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_wheelspin_pitch_step = (vSS_WHEELSPIN_MIN_PITCH - (vSS_WHEELSPIN_MIN_PITCH - vSS_WHEELSPIN_MAX_PITCH / 2.0f)) / vSS_MIN_WHEELSPIN_TIME;
+	m_wheelspin_end_pitch = vSS_WHEELSPIN_MIN_PITCH - vSS_WHEELSPIN_MAX_PITCH / 2.0f;
+	pParams->GetFloat( CRCD(0xf1a99b27,"volume_mult"), &m_vol_mult, Script::NO_ASSERT );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::Finalize (   )
+{
+	mp_physics_control_component = GetSkaterPhysicsControlComponentFromObject(GetObject());
+	
+	// Dbg_Assert(mp_physics_control_component);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::Update()
+{
+	if (!m_active)
+	{
+		Suspend(true);
+		return;
+	}
+	
+	// update looping sounds
+	
+	Sfx::sVolume volume;
+	float pitch;
+
+	switch ( m_StateType )
+	{
+		case RAIL:
+		{
+			if ( m_is_bailing )
+			{
+				m_have_sound_info = false;
+				break;
+			}
+			
+			Env::ETerrainActionType table;
+			if ( m_is_rail_sliding )
+			{
+				table = Env::vTABLE_SLIDE;
+			}
+			else								
+			{
+				table = Env::vTABLE_GRIND;
+			}
+			
+			if (m_update_sound_info)
+			{
+				m_have_sound_info = Env::CTerrainManager::sGetTerrainSoundInfo(&m_sound_info, m_terrain, table);
+				m_update_sound_info = false;
+			}
+			
+			break;
+		}
+		
+		case GROUND:
+			if (m_update_sound_info)
+			{
+				m_have_sound_info = Env::CTerrainManager::sGetTerrainSoundInfo(&m_sound_info, m_terrain, Env::vTABLE_WHEELROLL);			
+				m_update_sound_info = false;
+			}
+
+			break;
+			
+		case AIR:
+			if (m_update_sound_info)
+			{
+				m_have_sound_info = true;
+				m_sound_info = AIR_LOOPING_SOUND_INFO;
+				m_update_sound_info = false;
+			}
+			
+			break;
+			
+		default:
+			break;
+	} // END switch on skater state
+
+	// if the sound has changed, turn off the old one
+	if (m_looping_sound_id && (!m_have_sound_info || m_looping_sound_checksum != m_sound_info.m_soundChecksum))
+	{
+		Sfx::CSfxManager::Instance()->StopSound(m_looping_sound_id);
+		m_looping_sound_id = 0;
+	}
+	
+	// we have no sound to play
+	if (!m_have_sound_info) return;
+	
+	// setup the sound's pitch and volume based on the skater's state
+	
+	Sfx::CSfxManager* p_sfx_manager = Sfx::CSfxManager::Instance();
+	
+	// adjust volume based of skater's offset from the nearest camera
+	p_sfx_manager->SetVolumeFromPos(&volume, GetObject()->GetPos(), p_sfx_manager->GetDropoffDist(m_sound_info.m_soundChecksum));
+	
+	// if the skater is in the air and this isn't the first frame we've been playing the air looping sound
+	if ( m_StateType == AIR && m_looping_sound_id )
+	{
+		// drop the pitch over time
+		if (m_wheelspin_pitch > m_wheelspin_end_pitch)
+		{
+			m_wheelspin_pitch -= m_wheelspin_pitch_step * Tmr::FrameLength();
+		}
+		// then kill the volume
+		else
+		{
+			volume.SetSilent();
+		}
+		
+		pitch = m_wheelspin_pitch;
+	}
+	else
+	{
+		// adjust the volume and pitch based on the speed
+		 
+		if ( m_speed_fraction > 0.0f)
+		{
+			volume.PercentageAdjustment(Env::CTerrainManager::sGetVolPercent(&m_sound_info, 100.0f * m_speed_fraction * m_vol_mult));
+			
+			pitch = m_speed_fraction * (m_sound_info.m_maxPitch - m_sound_info.m_minPitch) + m_sound_info.m_minPitch;
+			
+			// save the current pitch incase we are in the air next frame
+			m_wheelspin_pitch = pitch;
+		}
+		else
+		{
+			volume.SetSilent();
+			m_wheelspin_pitch = pitch = 0.0f;
+		}
+	}
+	
+	// if the volume is zero
+	if (volume.IsSilent())
+	{
+		// stop playing the sound
+		if (m_looping_sound_id)
+		{
+			Sfx::CSfxManager::Instance()->StopSound(m_looping_sound_id);
+			m_looping_sound_id = 0;
+		}
+		return;
+	}
+	
+	// NOTE: removed until I can figure out what to do with this
+	// adjust the sound based on doppler effects
+	// if ( Nx::CViewportManager::sGetScreenMode( ) == 0 ) // that zero should be an enum or something...
+	// {
+		// sfx_manager->AdjustPitchForDoppler( &pitch, mp_physics->m_pos, mp_physics->m_old_pos, mp_physics->m_time, Nx::CViewportManager::sGetActiveCamera( 0 ) );
+	// }
+	
+	// NOTE: removing all replay code for now
+	// save pitch information for the replay code
+	// m_pitch_min = sound_info.m_minPitch;
+	// m_pitch_max = sound_info.m_maxPitch;
+	
+	// if we're not already playing a sound
+	if (!m_looping_sound_id)
+	{
+		m_last_volume = volume;
+		m_last_pitch = pitch;
+		
+		// start the sound
+		m_looping_sound_id = p_sfx_manager->PlaySound(m_sound_info.m_soundChecksum, &volume, pitch, 0, NULL, m_sound_info.mp_soundName);
+		if (!m_looping_sound_id)
+		{
+			return;
+		}
+		
+		// save the checksum of the currently playing sound
+		m_looping_sound_checksum = m_sound_info.m_soundChecksum;
+	}
+	
+	// if we need to update the sound already playing; since we scale all channels equally, we can get away with only checking the first volume
+	else if (m_unpause || Mth::Abs(volume.m_channels[0] - m_last_volume.m_channels[0]) > vSS_MAX_PERCENTAGE_VOLUME_CHANGE_WITHOUT_UPDATE
+		|| Mth::Abs(pitch - m_last_pitch) > vSS_MAX_PITCH_CHANGE_WITHOUT_UPDATE)
+	{
+		m_last_volume = volume;
+		m_last_pitch = pitch;
+		m_unpause = false;
+		
+		p_sfx_manager->UpdateLoopingSound(m_looping_sound_id, &volume, pitch);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterLoopingSoundComponent::CallMemberFunction ( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		case CRCC(0xede3935f, "SkaterLoopingSound_TurnOn"):
+			SetActive(true);
+			break;
+			
+		case CRCC(0x9731e193, "SkaterLoopingSound_TurnOff"):
+			SetActive(false);
+			break;
+			
+		case CRCC(0xb1e7291, "PlayAnim"):
+			// if a transition anim is interrupted, we must turn the looping sounds on here
+			Dbg_MsgAssert( mp_physics_control_component, ( "Don't call PlayAnim on a non-skater" ) );
+			
+			Script::CStruct* p_anim_tags_struct;
+			if (GetObject()->GetTags()
+				&& mp_physics_control_component->IsSkating()
+				&& GetObject()->GetTags()->GetStructure(CRCD(0x5db4115f, "AnimTags"), &p_anim_tags_struct)
+				&& p_anim_tags_struct->ContainsFlag(CRCD(0x910d77c1, "WalkToSkateTransition")))
+			{
+				Suspend(false);
+				m_active = true;
+			}
+			return CBaseComponent::MF_NOT_EXECUTED;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::GetDebugInfo ( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterLoopingSoundComponent::GetDebugInfo"));
+	
+	p_info->AddInteger("m_looping_sound_id", m_looping_sound_id);
+	p_info->AddChecksum("m_looping_sound_checksum", m_looping_sound_checksum);
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::Suspend ( bool suspend )
+{
+	CBaseComponent::Suspend(suspend);
+	
+	if (suspend)
+	{
+		StopLoopingSound();
+	}
+	else
+	{
+		m_update_sound_info = true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::StopLoopingSound (   )
+{
+	if (m_looping_sound_id)
+	{
+		Sfx::CSfxManager::Instance()->StopSound(m_looping_sound_id);
+		m_looping_sound_id = 0;
+	}
+}
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::SetSpeedFraction( float speed_fraction )
+{
+	m_speed_fraction = speed_fraction;
+	if ( m_speed_fraction > 1.0f )
+	{
+		m_speed_fraction = 1.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterLoopingSoundComponent::SetVolumeMultiplier( float mult )
+{
+	Dbg_MsgAssert( mult >= 0.0f && mult <= 1.0f, ( "SetVolumeMultiplier called with bad mult value: %f", mult ) );
+	m_vol_mult = mult;
+}
+
+}
diff --git a/Code/Sk/Components/SkaterLoopingSoundComponent.h b/Code/Sk/Components/SkaterLoopingSoundComponent.h
new file mode 100644
index 0000000..f437528
--- /dev/null
+++ b/Code/Sk/Components/SkaterLoopingSoundComponent.h
@@ -0,0 +1,194 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterLoopingSoundComponent.h
+//* OWNERD:			Dan
+//* CREATION DATE:  2/26/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERLOOPINGSOUNDCOMPONENT_H__
+#define __COMPONENTS_SKATERLOOPINGSOUNDCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#define		CRC_SKATERLOOPINGSOUND CRCD(0xb5f1e9d2, "SkaterLoopingSound")
+
+#define		GetSkaterLoopingSoundComponent() ((Obj::CSkaterLoopingSoundComponent*)GetComponent(CRC_SKATERLOOPINGSOUND))
+#define		GetSkaterLoopingSoundComponentFromObject(pObj) ((Obj::CSkaterLoopingSoundComponent*)(pObj)->GetComponent(CRC_SKATERLOOPINGSOUND))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterPhysicsControlComponent;
+
+	static const float vSS_MAX_PITCH_CHANGE_WITHOUT_UPDATE = 0.5f;
+	static const float vSS_MAX_PERCENTAGE_VOLUME_CHANGE_WITHOUT_UPDATE = 0.5f;
+	
+	static const float vSS_WHEELSPIN_MIN_PITCH = 100.0f;
+	static const float vSS_WHEELSPIN_MAX_PITCH = 110.0f;
+	static const float vSS_MIN_WHEELSPIN_TIME = 3.0f;
+	
+	// looping sound used when skater is in the air
+	static const Env::STerrainSoundInfo AIR_LOOPING_SOUND_INFO =
+	{
+		"wheels01",
+		CRCD(0x24257432, "wheels01"),
+		vSS_WHEELSPIN_MIN_PITCH,
+		vSS_WHEELSPIN_MAX_PITCH
+	};
+	
+class CSkaterLoopingSoundComponent : public CBaseComponent
+{
+public:
+    CSkaterLoopingSoundComponent();
+    virtual ~CSkaterLoopingSoundComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+	virtual void					Finalize (   );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+	virtual void					Suspend ( bool suspend );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							StopLoopingSound (   );
+
+	void							UnPause();
+	
+	void							SetActive ( bool active );
+
+	void							SetIsBailing( bool is_bailing );
+	void							SetIsRailSliding( bool is_sliding );
+
+	void							SetState( EStateType new_state );
+	void							SetTerrain( ETerrainType terrain );
+
+	void							SetSpeedFraction( float speed_fraction );
+	void							SetVolumeMultiplier( float mult );
+private:
+	// the ID of the current looping sound
+	uint32							m_looping_sound_id;
+	
+	// checksum of the current looping sound
+	uint32							m_looping_sound_checksum;
+	
+	// holds the last frame's looping sound pitch so that can be used when the skater goes airborne
+	float							m_wheelspin_pitch;
+	
+	// optimization variables
+	float							m_last_pitch;
+	Sfx::sVolume					m_last_volume;
+	
+	// constant characteristics
+	
+	// the drop in pitch of the air looping sound per second
+	float m_wheelspin_end_pitch;
+	
+	// the final pitch of the air looping sound; when reached the sound is turned off
+	float m_wheelspin_pitch_step;
+
+	// peer components
+	CSkaterPhysicsControlComponent*	mp_physics_control_component;
+
+	ETerrainType					m_terrain;
+	EStateType	m_StateType;
+
+	Env::STerrainSoundInfo			m_sound_info;
+	bool							m_have_sound_info;
+	bool							m_update_sound_info;
+	bool							m_unpause;
+
+	bool							m_is_bailing;
+	bool							m_is_rail_sliding;
+
+	float							m_speed_fraction;
+	float							m_vol_mult;
+	
+	bool							m_active;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterLoopingSoundComponent::SetActive ( bool active )
+{
+	if (m_active != active)
+	{
+		Suspend(!active);
+		m_active = active;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void	CSkaterLoopingSoundComponent::UnPause()
+{
+	m_unpause = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterLoopingSoundComponent::SetState( EStateType state )
+{
+	m_update_sound_info |= (m_StateType != state);
+	m_StateType = state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterLoopingSoundComponent::SetTerrain( ETerrainType terrain )
+{
+	m_update_sound_info |= (m_terrain != terrain);
+	m_terrain = terrain;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterLoopingSoundComponent::SetIsBailing( bool is_bailing )
+{
+	m_is_bailing = is_bailing;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterLoopingSoundComponent::SetIsRailSliding( bool is_rail_sliding )
+{
+	m_update_sound_info |= (m_is_rail_sliding != is_rail_sliding);
+	m_is_rail_sliding = is_rail_sliding;
+}
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterMatrixQueriesComponent.cpp b/Code/Sk/Components/SkaterMatrixQueriesComponent.cpp
new file mode 100644
index 0000000..82f2a1f
--- /dev/null
+++ b/Code/Sk/Components/SkaterMatrixQueriesComponent.cpp
@@ -0,0 +1,260 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterMatrixQueriesComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/12/3
+//****************************************************************************
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterMatrixQueriesComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterMatrixQueriesComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterMatrixQueriesComponent::CSkaterMatrixQueriesComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERMATRIXQUERIES );
+	
+	mp_core_physics_component = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterMatrixQueriesComponent::~CSkaterMatrixQueriesComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterMatrixQueriesComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterMatrixQueriesComponent added to non-skater composite object"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterMatrixQueriesComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterMatrixQueriesComponent::Finalize (   )
+{
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+		
+	Dbg_Assert(mp_core_physics_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterMatrixQueriesComponent::Update()
+{
+	// Store the matrix from the last frame. Used by script functions for measuring angles & stuff. Has to use the last frame's matrix
+	// because the landing physics snaps the orientation to the ground, yet the land script (which executes just after) needs to measure 
+	// the angles on impact to maybe trigger bails and such.
+	m_latest_matrix = mp_core_physics_component->m_lerping_display_matrix;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterMatrixQueriesComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | YawBetween | yaw between the two specified angles
+        // @uparm (45, 135) | angle range values
+		case CRCC(0xb992f3cc, "YawBetween"):
+		{
+			// if (CHEAT_SNOWBOARD) 
+			// {
+				// return false;
+			// }
+			
+			Script::CPair Pair;
+			if (!pParams->GetPair(NO_NAME, &Pair, Script::ASSERT))
+			Dbg_MsgAssert(Pair.mX < Pair.mY,("\n%s\n1st angle must be less than the 2nd angle", pScript->GetScriptInfo()));
+			
+			Mth::Vector a = GetSkater()->m_vel;
+			a.RotateToPlane(m_latest_matrix[Y]);
+			return (Mth::AngleBetweenGreaterThan(a, m_latest_matrix[Z], Pair.mX)
+				&& !Mth::AngleBetweenGreaterThan(a, m_latest_matrix[Z], Pair.mY))
+				? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+
+        // @script | YawingLeft | true if currently yawing left
+		case CRCC(0xa745c080, "YawingLeft"):
+		{
+			Mth::Vector a = GetSkater()->m_vel;
+			a.RotateToPlane(m_latest_matrix[Y]);
+			return Mth::CrossProduct(a, m_latest_matrix[Z])[Y] > 0.0f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+			
+        // @script | YawingRight | true if currently yawing right
+		case CRCC(0xc8c4d2f4, "YawingRight"):
+		{
+			Mth::Vector a = GetSkater()->m_vel;
+			a.RotateToPlane(m_latest_matrix[Y]);
+			return Mth::CrossProduct(a, m_latest_matrix[Z])[Y] < 0.0f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+			
+        // @script | PitchGreaterThan | true if the pitch is greater
+        // than the specified value
+        // @uparm 0.0 | test angle
+		case CRCC(0xa0551543, "PitchGreaterThan"):
+		{
+			float TestAngle = 0.0f;
+			pParams->GetFloat(NO_NAME, &TestAngle, Script::ASSERT);
+			
+			return Mth::DotProduct(m_latest_matrix[Y], mp_core_physics_component->m_last_display_normal) < cosf(Mth::DegToRad(TestAngle))
+				? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+
+		case CRCC(0x5e269b2b, "AbsolutePitchGreaterThan"):
+		{
+			float TestAngle = 0.0f;
+			pParams->GetFloat(NO_NAME, &TestAngle, Script::ASSERT);
+			
+			return m_latest_matrix[Y][Y] < cosf(Mth::DegToRad(TestAngle)) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+			
+		/*
+        // @script | PitchingForward | true if pitching forward
+		case CRCC(0xdaeda59c, "PitchingForward"):
+		{
+			Mth::Vector b = Mth::CrossProduct(m_latest_matrix[Y], mp_core_physics_component->m_last_display_normal);
+			return Mth::DotProduct(b, m_latest_matrix[X]) < 0.0f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+
+        // @script | PitchingBackward | true if pitching backward
+		case CRCC(0x7dd9e92c, "PitchingBackward"):
+		{
+			Mth::Vector b = Mth::CrossProduct(m_latest_matrix[Y], mp_core_physics_component->m_last_display_normal);
+			return Mth::DotProduct(b, m_latest_matrix[X]) > 0.0f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		*/
+
+        // @script | RollGreaterThan | true if the roll is greater than
+        // the specified angle value
+        // @uparm 0.0 | angle value
+		case CRCC(0xd3313e92, "RollGreaterThan"):
+		{
+			float TestAngle = 0.0f;
+			pParams->GetFloat(NO_NAME, &TestAngle, Script::ASSERT);
+			
+			Mth::Vector a = m_latest_matrix[X];
+			a.RotateToPlane(mp_core_physics_component->m_last_display_normal);
+			return Mth::AngleBetweenGreaterThan(a, m_latest_matrix[X], TestAngle) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+
+		/*
+		// @script | RollingLeft | true if rolling left
+		case CRCC(0x7328c9ad, "RollingLeft"):
+		{
+			Mth::Vector b = Mth::CrossProduct(m_latest_matrix[Y], mp_core_physics_component->m_last_display_normal);
+			return Mth::DotProduct(b, m_latest_matrix[Z]) > 0.0f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+
+        // @script | RollingRight | true if rolling right
+		case CRCC(0x8dcfe388, "RollingRight"):
+		{
+			Mth::Vector b = Mth::CrossProduct(m_latest_matrix[Y], mp_core_physics_component->m_last_display_normal);
+			return Mth::DotProduct(b, m_latest_matrix[Z]) < 0.0f ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		*/
+			
+		// @script | GetSlope | Puts the angle of the slope into a param called Slope.
+		// Units are degrees. Zero is horizontal, positive is up, negative is down.
+		// The change in slope since the last call to GetSlope is put in a parameter
+		// called ChangeInSlope
+		case CRCC(0x97201739, "GetSlope"):
+		{
+			Mth::Vector v = GetObject()->m_matrix[Z];
+			v[Y] = 0.0f;
+			float slope = Mth::GetAngle(GetObject()->m_matrix[Z], v);
+			if (GetObject()->m_matrix[Z][Y] < 0.0f)
+			{
+				slope = -slope;
+			}	
+			pScript->GetParams()->AddFloat(CRCD(0xa733ba7a, "Slope"), slope);
+			pScript->GetParams()->AddFloat(CRCD(0x21afff16, "ChangeInSlope"), slope - m_last_slope);
+			m_last_slope = slope;
+			break;
+		}
+		
+		case CRCC(0x8e7833be, "GetHeading"):
+		{
+			float heading = Mth::RadToDeg(cosf(GetObject()->m_matrix[Z][X]));
+			if (GetObject()->m_matrix[Z][Z] < 0.0f)
+			{
+				heading = 360.0f - heading;
+			}	
+			pScript->GetParams()->AddFloat(CRCD(0xfd4bc03e, "heading"), heading);
+			pScript->GetParams()->AddFloat(CRCD(0x2315ef17, "cosine"), GetObject()->m_matrix[Z][X]);
+			pScript->GetParams()->AddFloat(CRCD(0x26910cc0, "sine"), GetObject()->m_matrix[Z][Z]);
+			break;
+		}
+		
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterMatrixQueriesComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterMatrixQueriesComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+}
diff --git a/Code/Sk/Components/SkaterMatrixQueriesComponent.h b/Code/Sk/Components/SkaterMatrixQueriesComponent.h
new file mode 100644
index 0000000..8ada2c2
--- /dev/null
+++ b/Code/Sk/Components/SkaterMatrixQueriesComponent.h
@@ -0,0 +1,63 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterMatrixQueriesComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/12/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERMATRIXQUERIESCOMPONENT_H__
+#define __COMPONENTS_SKATERMATRIXQUERIESCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERMATRIXQUERIES CRCD(0xbf518e27, "SkaterMatrixQueries")
+
+#define		GetSkaterMatrixQueriesComponent() ((Obj::CSkaterMatrixQueriesComponent*)GetComponent(CRC_SKATERMATRIXQUERIES))
+#define		GetSkaterMatrixQueriesComponentFromObject(pObj) ((Obj::CSkaterMatrixQueriesComponent*)(pObj)->GetComponent(CRC_SKATERMATRIXQUERIES))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkaterMatrixQueriesComponent : public CBaseComponent
+{
+public:
+    CSkaterMatrixQueriesComponent();
+    virtual ~CSkaterMatrixQueriesComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+	void							ResetLatestMatrix (   ) { m_latest_matrix = GetObject()->GetMatrix(); }
+	
+private:
+	Mth::Matrix						m_latest_matrix;
+	float							m_last_slope;
+	
+	// peer components
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterNonLocalNetLogicComponent.cpp b/Code/Sk/Components/SkaterNonLocalNetLogicComponent.cpp
new file mode 100644
index 0000000..6229123
--- /dev/null
+++ b/Code/Sk/Components/SkaterNonLocalNetLogicComponent.cpp
@@ -0,0 +1,1542 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterNonLocalNetLogicComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/11/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+								
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterNonLocalNetLogicComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterNonLocalNetLogicComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterNonLocalNetLogicComponent::CSkaterNonLocalNetLogicComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERNONLOCALNETLOGIC );
+	
+	mp_state_history_component = NULL;
+	mp_state_component = NULL;
+	mp_endrun_component = NULL;
+	mp_animation_component = NULL;
+	mp_model_component = NULL;
+	mp_flip_and_rotate_component = NULL;
+	mp_shadow_component = NULL;
+	m_last_pos_index = 0;
+	m_interp_pos = Mth::Vector( 0, 0, 0 );
+	m_old_interp_pos = Mth::Vector( 0, 0, 0 );
+	m_num_mags = 0;
+	m_last_anm_update_time = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterNonLocalNetLogicComponent::~CSkaterNonLocalNetLogicComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterNonLocalNetLogicComponent added to non-skater composite object"));
+	Dbg_MsgAssert(!GetSkater()->IsLocalClient(), ("CSkaterNonLocalNetLogicComponent added to non-local skater"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::Finalize (   )
+{
+	mp_state_history_component = GetSkaterStateHistoryComponentFromObject(GetObject());
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+	mp_endrun_component = GetSkaterEndRunComponentFromObject(GetObject());
+	mp_animation_component = GetAnimationComponentFromObject(GetObject());
+	mp_flip_and_rotate_component = GetSkaterFlipAndRotateComponentFromObject(GetObject());
+	mp_model_component = GetModelComponentFromObject(GetObject());
+	mp_shadow_component = GetShadowComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_state_history_component);
+	Dbg_Assert(mp_state_component);
+	Dbg_Assert(mp_endrun_component);
+	Dbg_Assert(mp_animation_component);
+	Dbg_Assert(mp_flip_and_rotate_component);
+	Dbg_Assert(mp_model_component);
+	Dbg_Assert(mp_shadow_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::Update()
+{
+	GameNet::PlayerInfo* local_player;
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+	m_frame_length = Tmr::UncappedFrameLength();
+
+	bool BeganFrameInLipState = mp_state_component->GetState() == LIP;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	if( mp_state_component->GetState() != AIR )
+	{
+		mp_state_component->m_camera_display_normal = GetObject()->GetMatrix().GetUp();
+		mp_state_component->m_camera_current_normal = GetObject()->GetMatrix().GetUp();
+	}
+
+	setup_brightness_and_shadow();             
+	
+	m_old_extrap_pos = GetObject()->m_pos;
+	GetObject()->m_old_pos = GetObject()->m_pos;
+	interpolate_client_position();
+	// Only extrapolate if we can collide with other players. Otherwise, interpolation looks 
+	// much better.
+	local_player = gamenet_man->GetLocalPlayer();
+	if( !local_player->IsObserving() &&
+		!local_player->IsSurveying())
+	{
+		if( ( GameNet::Manager::Instance()->PlayerCollisionEnabled()) ||
+			( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0xbff33600,"netfirefight")) ||
+			( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x3d6d444f,"firefight")))
+		{
+			extrapolate_client_position();
+		}
+	}
+	do_client_animation_update();
+	
+	// NOTE: Below logic is copied from CSkaterFinalizePhysicsComponent and should instead be merged.
+	
+	// Logic for setting/not setting the flag for telling the camera whether to look down on the skater or not.
+	if (BeganFrameInLipState && mp_state_component->GetState() != LIP)
+	{
+		// This flag is sort of badly named now, it really means we changed from the lip state to something other than GROUND or LIP.
+		mp_state_component->mJumpedOutOfLipTrick = false;
+		if (mp_state_component->GetState() == AIR)
+		{
+			// we only want to set this flag if we jumped straight up; meaning the x and z velocities are close to zero
+			if (Mth::Abs(GetObject()->m_vel[X]) < 1.0f && Mth::Abs(GetObject()->m_vel[Z]) < 1.0f)
+			{
+				mp_state_component->mJumpedOutOfLipTrick = true;
+			}				
+		}
+	}
+	
+	// this flag needs clearing whenever we get out of the air
+	if (mp_state_component->GetState() != AIR)
+	{
+		mp_state_component->mJumpedOutOfLipTrick = false;
+	}
+	
+	// take the non-local client in and out of the car
+	if (mp_state_component->GetDriving() && mp_state_history_component->GetCurrentVehicleControlType() != 0)
+	{
+		Script::CStruct* pParams = new Script::CStruct;
+		pParams->AddChecksum(CRCD(0x5b24faaa, "SkaterId"), GetObject()->GetID());
+		pParams->AddChecksum(CRCD(0x81cff663, "control_type"), mp_state_history_component->GetCurrentVehicleControlType());
+		RunScript(CRCD(0x7853f8c4, "NonLocalClientInVehicle"), pParams);
+		delete pParams;
+	}
+	else if (!mp_state_component->GetDriving() && m_last_driving)
+	{
+		Script::CStruct* pParams = new Script::CStruct;
+		pParams->AddChecksum(CRCD(0x5b24faaa, "SkaterId"), GetObject()->GetID());
+		RunScript(CRCD(0xf02e44f, "NonLocalClientExitVehicle"), pParams);
+		delete pParams;
+	}
+	m_last_driving = mp_state_component->GetDriving();
+	
+	// update the looping sound component
+	CSkaterLoopingSoundComponent* p_looping_sound_component = GetSkaterLoopingSoundComponentFromObject(GetObject());
+	Dbg_Assert(p_looping_sound_component);
+	if (mp_state_component->m_physics_state == SKATING)
+	{
+		p_looping_sound_component->SetActive(true);
+		float speed_fraction = sqrtf(GetObject()->m_vel[X] * GetObject()->m_vel[X] + GetObject()->m_vel[Z] * GetObject()->m_vel[Z]) / 1000.0f;
+		p_looping_sound_component->SetSpeedFraction(speed_fraction);
+		p_looping_sound_component->SetState(mp_state_component->m_state);
+		p_looping_sound_component->SetTerrain(mp_state_component->m_terrain);
+		p_looping_sound_component->SetIsBailing(mp_state_component->GetFlag(IS_BAILING));
+		p_looping_sound_component->SetIsRailSliding(mp_state_component->GetFlag(RAIL_SLIDING));
+	}
+	else
+	{
+		p_looping_sound_component->SetActive(false);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterNonLocalNetLogicComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterNonLocalNetLogicComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::Resync (   )
+{
+	m_client_initial_update_time = 0;
+	m_last_anm_update_time = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::snap_to_ground( void  )
+{
+	float up_dot = 0.0f;
+	Mth::Vector col_start, col_end;
+	CFeeler feeler;
+	
+	// Since we really don't want to loose contact with the ground while skitching, we use a much bigger snap up dist
+	// The problem will come when we get dragged down a slope.  The car will flatten out well ahead of us, so pushing us down through the slop
+	// (as we are a few feet behind it) and we will be so far under the ground that our normal snap up will not be able to dig us out of it,
+	// so we go in air, uberfrig, and get dragged to a random spot under the level.
+	// (This would not happen if we just skitch on flat ground)																								
+	// if (mp_state_component->m_skater_flags[SKITCHING].Get())
+	// {
+		// col_start = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0x5c0d9610,"Physics_Ground_Snap_Up_SKITCHING"));	// much above feet
+	// }
+	// else
+	// {
+		col_start = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0xe4d79235, "Physics_Ground_Snap_Up"));	  		// bit above the feet
+	// }
+	
+	col_end = GetObject()->m_matrix[Y] * -200.0f;		    // WAY! below the feet, we check distance later
+
+	col_start += GetObject()->m_pos;
+	col_end += GetObject()->m_pos;
+		 
+	bool sticking = false;			
+	
+	feeler.SetLine( col_start, col_end );
+	feeler.SetIgnore( mFD_NON_COLLIDABLE, 0 );
+	// get disatnce to ground and snap the skater to it, but only if the ground is skatable, otherwise we just go to "AIR"
+	if( feeler.GetCollision())
+	{
+		Mth::Vector movement = GetObject()->m_pos - feeler.GetPoint(); 
+		uint16 flags = feeler.GetFlags();
+		float drop_dist = movement.Length();
+		float drop_sign = Mth::DotProduct(GetObject()->m_matrix[Y], movement); // might be approx +/- 0.00001f
+		if(	(!flags & mFD_SKATABLE ) || ( flags & mFD_NOT_SKATABLE ) || (!flags & mFD_VERT) || 
+			(flags & mFD_WALL_RIDABLE) ||
+			(feeler.GetNormal()[Y] < sinf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x3eede4d3,"Wall_Non_Skatable_Angle"))))))
+		{
+			// if below the face (or very close to it), then push us away from it				
+			if (drop_sign < 0.001f)
+			{
+				// at point of contact, and move away from surface
+				GetObject()->m_pos = feeler.GetPoint() + feeler.GetNormal();	
+			}
+		}
+		else
+		{
+			sticking = true;
+			
+			// Note the two ways of calculating the angle between two faces
+			// the more "accurate" method simply takes angle between the two normals
+			// however, this fails to account for the direction the skater is travelling
+			// when in conjunction with the "snap" up.
+			// If the skater approaches a slope from the side, then he can snap up to the slope
+			// however, if the angle between the ground and the face to which we are snapping up to 
+			// is too great, then we transition to in-air
+			// and will drop through the slope
+			// the solution is to take the angle between the front vector rotated onto each face.
+			
+			// Firstly we check the angle between the two faces	
+			Mth::Vector normal = feeler.GetNormal();
+			
+			Mth::Vector	forward = GetObject()->m_matrix[Z];
+			float front_dot = Mth::DotProduct(forward,normal);
+
+			Mth::Vector	old_forward = forward;
+			forward.RotateToPlane(normal);
+			old_forward.RotateToPlane(m_current_normal);
+
+			// angle between front vectors, projected onto faces
+			up_dot = Mth::DotProduct(forward, old_forward);
+			
+			float stick_angle_cosine;
+			stick_angle_cosine = cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x4138283e, "Ground_stick_angle_forward"))));
+			
+			if (front_dot > 0.0f  && up_dot > 0.0f && up_dot < stick_angle_cosine)
+			{
+				sticking = false;
+			}
+			else
+			{
+				// only need to test if snap is downwards
+				if (drop_sign > 0.0f)
+				{
+					// angle between the old plane and the new plane is either the same, or withing the set limits
+					// (like, < 60 degrees, or so, see physics.q) now calculate the drop distance, based on the angle
+					
+					//float max_drop = 60.0f;////last_move.Length() * tanf(angle);
+#ifdef __PLAT_NGC__
+					float angle = acosf(Mth::Clamp(up_dot, -1.0f, 1.0f));
+#else
+					float angle = acosf(up_dot);
+#endif // __PLAT_NGC__
+
+					Mth::Vector	last_move = GetObject()->m_pos - m_old_extrap_pos;
+					
+					float max_drop = last_move.Length() * tanf(angle);
+					
+					float min_drop = GetPhysicsFloat(CRCD(0x899ba3d0, "Physics_Ground_Snap_Down"));				
+                    // if (mp_state_component->m_skater_flags[SKITCHING].Get())
+					// {
+						// min_drop = GetPhysicsFloat(CRCD(0x20df7e33, "Physics_Ground_Snap_Down_Skitching"));				
+					// }
+
+					if (max_drop < min_drop)
+					{
+						max_drop = min_drop;
+					}
+					
+					if (drop_dist > max_drop)
+					{
+						sticking = false;
+					}
+				}
+				
+				if (sticking)
+				{    
+					//new_normal(normal);
+					m_current_normal = normal;	  										// remember this, for detecting if it changes
+		
+					GetObject()->m_matrix[Y] = normal;
+					GetObject()->m_matrix.OrthoNormalizeAbout(Y);									// set regular normal immediately
+				}		 
+			}																		  
+		}
+					
+		if (sticking)
+		{	
+			// if there is a collision, then snap to it
+			GetObject()->m_pos = feeler.GetPoint();
+		}
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CRailNode*	CSkaterNonLocalNetLogicComponent::travel_on_rail( CRailNode* start_node, float frame_length )
+{
+	bool on_rail;
+
+	Dbg_Assert( start_node );
+
+	on_rail = true;
+
+	CRailManager* p_rail_man = Mdl::Skate::Instance()->GetRailManager();
+
+	const CRailNode* pStart = start_node;
+	const CRailNode* pEnd = pStart->GetNextLink();
+	if( pEnd == NULL )
+	{
+		Dbg_MsgAssert(pEnd,("NonLocal Skater on Rail node (%d) with no next link\n",start_node->GetNode()));
+		return NULL;
+	}
+	
+	//const CRailNode* pFrom = pStart;
+	const CRailNode* pOnto = NULL;
+	
+	Mth::Vector	dir = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pStart);
+	float segment_length = dir.Length();
+	dir *= (1.0f / segment_length);
+
+	// sign is which way we are going along the rail
+	//float old_sign = Mth::Sgn(Mth::DotProduct(dir, GetObject()->m_vel));
+
+	// Get gravity force 	
+	Mth::Vector gravity(0.0f, GetPhysicsFloat(CRCD(0xd1f46992, "Physics_Rail_Gravity")), 0.0f);
+
+	// Project gravity onto the line we are on
+	gravity.ProjectToNormal(dir);
+	GetObject()->m_vel += gravity * frame_length;
+	
+	// sign is which way we are going along the rail
+	float sign = Mth::Sgn(Mth::DotProduct(dir, GetObject()->m_vel));
+	
+	// check to see if we are on the last segment of the rail
+	// this logic could be folded into the logic below but it's perhps clearer to have it here
+	bool last_segment = false;
+	if (sign < 0.0f)
+	{
+		if (pStart->GetPrevLink() && pStart->GetPrevLink()->IsActive())
+		{
+			Mth::Vector v1, v2;
+			v1 = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pStart);
+			v2 = p_rail_man->GetPos(pStart) - p_rail_man->GetPos(pStart->GetPrevLink());
+			v1[Y] = 0.0f;
+			v2[Y] = 0.0f;
+			v1.Normalize();
+			v2.Normalize();
+			float dot = Mth::DotProduct(v1, v2);
+			if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15, "Rail_Corner_Leave_Angle")))))
+			{
+				// there is more, but angle is too sharp
+				last_segment = true;
+			}
+			else
+			{
+				pOnto = pStart->GetPrevLink();
+			}
+		}
+		else
+		{
+			last_segment = true;			
+		}
+	}
+	else
+	{
+		if (pEnd && pEnd->GetNextLink() && pEnd->GetNextLink()->IsActive())
+		{
+			Mth::Vector  v1,v2;
+			v1 = p_rail_man->GetPos(pStart) - p_rail_man->GetPos(pEnd);
+			v2 = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pEnd->GetNextLink());
+			v1[Y] = 0.0f;
+			v2[Y] = 0.0f;
+			v1.Normalize();
+			v2.Normalize();
+			float dot = Mth::DotProduct(v1,v2);
+			if (dot < cosf(Mth::DegToRad(GetPhysicsFloat(CRCD(0x76c1da15,"Rail_Corner_Leave_Angle")))))
+			{
+				// there is more, but angle is too sharp
+				last_segment = true;
+			}
+			else
+			{
+				pOnto = pEnd;
+			}
+		}
+		else
+		{
+			last_segment = true;
+		}
+	}
+
+	// Now we need to see if we have gone beyond the end of the rail segment
+	
+	float extra_dist = 0.0f;
+		
+	float length_along;
+	if (sign < 0.0f)
+	{
+		// going backwards, so it's the distance from the end of the segment
+		length_along = (GetObject()->m_pos - p_rail_man->GetPos(pEnd)).Length();
+	}
+	else
+	{
+		// going forwards, so it's the distance from the start
+		length_along = (GetObject()->m_pos - p_rail_man->GetPos(pStart)).Length();
+	}
+	
+	if (length_along > segment_length + 0.1f)	// 0.1 inch, so we don't get stuck
+	{
+		// remember this, so we can move along next segment
+		extra_dist = length_along - segment_length;
+		
+		// gone off the end of the segment
+		if (sign < 0.0f)
+		{
+			if (pStart->GetPrevLink()
+				&& pStart->GetPrevLink()->IsActive()
+				&& Rail_ValidInEditor(p_rail_man->GetPos(pStart), p_rail_man->GetPos(pStart->GetPrevLink())))
+			{
+				if (!last_segment)
+				{
+					// go onto previous segment
+					GetObject()->m_pos = p_rail_man->GetPos(pStart);
+					start_node = (CRailNode*) pStart->GetPrevLink();
+					//set_terrain(start_node->GetTerrain());
+					//maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);
+				}		   
+				else
+				{
+					//skate_off_rail(p_rail_man->GetPos(pStart));
+					on_rail = false;
+				} 		
+			}
+			else
+			{
+				//skate_off_rail(p_rail_man->GetPos(pStart));
+				on_rail = false;
+			}
+		}
+		else
+		{
+			if (pEnd->GetNextLink()
+				&& pEnd->IsActive()
+				&& Rail_ValidInEditor(p_rail_man->GetPos(pEnd), p_rail_man->GetPos(pEnd->GetNextLink())))
+			{
+				if (!last_segment)
+				{
+					GetObject()->m_pos = p_rail_man->GetPos(pEnd);
+					start_node = (CRailNode*) pEnd;					
+					//set_terrain(start_node->GetTerrain());
+					//maybe_trip_rail_trigger(TRIGGER_SKATE_ONTO);						
+				}		   
+				else
+				{
+					//skate_off_rail(p_rail_man->GetPos(pEnd));
+					on_rail = false;
+				} 						
+			}
+			else
+			{
+				//skate_off_rail(p_rail_man->GetPos(pEnd));
+				on_rail = false;
+			}
+		}		
+	}
+	
+	if( on_rail ) // If still on the rail     GetState() == RAIL)
+	{
+		// recalculate start, end, dir, as we might be on a new segment
+		const CRailNode* pStart = start_node;
+		const CRailNode* pEnd = pStart->GetNextLink();
+		
+		Mth::Vector	dir = p_rail_man->GetPos(pEnd) - p_rail_man->GetPos(pStart);
+		dir.Normalize();
+
+		// sign also may have changed, now that we are auto-linking rail segments
+		
+		// sign is which way we are going along the rail
+		float sign = Mth::Sgn(Mth::DotProduct(dir,GetObject()->m_vel));
+
+		//m_rail_time = Tmr::GetTime();
+							
+		GetObject()->m_vel.RotateToNormal(dir);
+		GetObject()->m_vel *= sign;						   						// sign won't be on a new segment
+
+		//float facing_sign = mRail_Backwards ? -sign : sign;
+		float facing_sign = sign;
+		
+		// z is forward
+		Mth::Vector target_forward = dir * facing_sign;
+
+		//m_lerping_display_matrix[Z] = Mth::Lerp(m_lerping_display_matrix[Z], target_forward, 0.3f);
+		//m_lerping_display_matrix[Z].Normalize(); 
+		
+		#if 0 // old code
+		// m_lerping_display_matrix[Y].Set(0.0f, 1.0f, 0.0f);
+		// m_lerping_display_matrix[X] = Mth::CrossProduct(
+			// m_lerping_display_matrix[Y],
+			// m_lerping_display_matrix[Z]
+		// );
+		#else
+		//m_lerping_display_matrix[X].Set(
+			//m_lerping_display_matrix[Z][Z],
+			//0.0f,
+			//-m_lerping_display_matrix[Z][X],
+			//0.0f
+		//);
+		#endif
+		//m_lerping_display_matrix[X].Normalize();
+		
+		//m_lerping_display_matrix[Y] = Mth::CrossProduct(
+			//m_lerping_display_matrix[Z],
+			//m_lerping_display_matrix[X]
+		//);
+		//m_lerping_display_matrix[Y].Normalize();
+
+		// adjust our Z value towards the new value												 
+		GetObject()->m_matrix[Z] = target_forward;
+		GetObject()->m_matrix[Z].Normalize(); 
+		
+		#if 0 // old code
+		// GetObject()->m_matrix[Y].Set(0.0f, 1.0f, 0.0f);
+		// GetObject()->m_matrix[X] = Mth::CrossProduct(GetObject()->m_matrix[Y],GetObject()->m_matrix[Z]);
+		#else
+		GetObject()->m_matrix[X].Set(
+			GetObject()->m_matrix[Z][Z],
+			0.0f,
+			-GetObject()->m_matrix[Z][X],
+			0.0f
+		);
+		#endif
+		GetObject()->m_matrix[X].Normalize();
+		
+		GetObject()->m_matrix[Y] = Mth::CrossProduct(GetObject()->m_matrix[Z], GetObject()->m_matrix[X]);
+		GetObject()->m_matrix[Y].Normalize();
+
+        // This is where we do the actual movement
+		// if this makes us bump into the wall or the ground, then we should leave the rail
+		GetObject()->m_pos += GetObject()->m_vel * frame_length;			// current movement
+		GetObject()->m_pos += extra_dist * target_forward;						// any extra dist from previous segment
+	}
+	else
+	{
+		return NULL;
+	}
+
+	return start_node;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::extrapolate_rail_position( void )
+{
+	GameNet::Manager* gamenet_man =  GameNet::Manager::Instance();
+	CRailManager* p_rail_man = Mdl::Skate::Instance()->GetRailManager();
+	CRailNode* start_node;
+	int i, prev_index, most_recent_index, mag_index;
+	SPosEvent* p_pos_history;
+	unsigned int delta_t, net_lag;
+	float coeff, ratio, total_mag, total_ratio, total_coeff, vel_mag, frame_length;
+	GameNet::PlayerInfo* local_player;
+	Net::Client* client;
+	Mth::Vector vel;
+
+	client = gamenet_man->GetClient( 0 );
+
+	p_pos_history = mp_state_history_component->GetPosHistory();
+	most_recent_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+	prev_index = ( m_last_pos_index + ( CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - 1 )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+	
+	delta_t = (int) ( m_frame_length * Tmr::vRESOLUTION );
+	if( gamenet_man->OnServer())
+	{
+		GameNet::PlayerInfo* player;
+		Net::Conn* client_conn;
+
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID());
+		Dbg_Assert( player );
+		client_conn = player->m_Conn;
+		net_lag = client_conn->GetAveLatency();
+	}
+	else
+	{
+		Lst::Search< Net::Conn > sh;
+		Net::Conn* server_conn;
+
+		server_conn = client->FirstConnection( &sh );
+		net_lag = server_conn->GetAveLatency();
+	}
+
+	// protect against dbz
+	if( delta_t == 0 )
+	{
+		ratio = 0;
+	}
+	else
+	{
+		ratio = (float) ( net_lag - delta_t ) / (float) delta_t;
+		if( ratio < 0 )
+		{
+			ratio = 0;
+		}
+	}
+	
+	mag_index = m_num_mags % vMAG_HISTORY_LENGTH;
+	vel = m_interp_pos - m_old_interp_pos;
+
+	coeff = 0.0f;
+	local_player = gamenet_man->GetLocalPlayer();
+	if( local_player )
+	{
+		Mth::Vector my_vel, their_vel;
+
+		my_vel = local_player->m_Skater->GetVel();
+		their_vel = vel;
+
+		my_vel.Normalize();
+		their_vel.Normalize();
+
+		// Get the dot product of our movement vectors
+		coeff = Mth::DotProduct( my_vel, their_vel );
+
+		// This will result in a value between -1.0f and 1.0f. Adding one will make that
+		// between 0 and 2
+		coeff += 1.0f;
+
+		// We want a value between 0 and 1, so divide by 2
+		coeff /= 2.0f;
+	}
+
+	m_mag_history[mag_index] = ( vel.Length() / delta_t ) * 16;
+	m_ratio_history[mag_index] = ratio;
+	m_extrap_coeff_history[mag_index] = coeff;
+	m_num_mags++;
+
+	total_mag = 0;
+	total_ratio = 0;
+	total_coeff = 0;
+	for( i = 0; i < vMAG_HISTORY_LENGTH; i++ )
+	{
+		if( i >= m_num_mags )
+		{
+			break;
+		}
+		total_mag += m_mag_history[i];
+		total_ratio += m_ratio_history[i];
+		total_coeff += m_extrap_coeff_history[i];
+		
+	}
+	vel_mag = total_mag / i;
+	coeff = total_coeff / i;
+	ratio = total_ratio / i;
+	ratio *= coeff;
+
+	vel.Normalize();
+	vel *= vel_mag;
+	
+	frame_length = m_frame_length * ratio;
+	// Sanity check to avoid infinite loops
+	if( frame_length > 1.0f )
+	{
+		frame_length = 1.0f;
+	}
+	start_node = p_rail_man->GetRailNodeByNodeNumber( p_pos_history[m_last_pos_index].RailNode );
+	if( start_node )
+	{
+		while( frame_length > 0 )
+		{
+			if( frame_length > m_frame_length )
+			{
+				start_node = travel_on_rail( start_node, m_frame_length );
+			}
+			else
+			{
+				start_node = travel_on_rail( start_node, frame_length );
+			}
+			
+			// If we grinded off a rail, use standard extrapolation instead.
+			if( start_node == NULL )
+			{
+				GetObject()->m_pos = m_interp_pos;
+				extrapolate_position();
+				break;
+			}
+			frame_length -= m_frame_length;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSkaterNonLocalNetLogicComponent::rotate_away_from_wall ( Mth::Vector normal, float &turn_angle )
+{
+	float lerp;
+
+	lerp = 1.0f;
+	// given a wall normal, then calculate the "turn_angle" to rotate the skater by and rotate the matrix and display matrix by this
+	// return the angle between the skater and the wall
+	// note turn_angle is passed by reference, and is altered !!!!
+	
+	// given m_right(dot)normal, we should be able to get a nice angle
+	float dot_right_normal = Mth::DotProduct(GetObject()->m_matrix[X], normal);
+
+	float angle = acosf(Mth::Clamp(dot_right_normal, -1.0f, 1.0f)); 	
+
+	if (angle > Mth::PI / 2.0f)
+	{
+		angle -= Mth::PI;
+	}
+	
+	// angle away from the wall
+	turn_angle = angle * GetPhysicsFloat(CRCD(0xe07ee1a9, "Wall_Bounce_Angle_Multiplier")) * lerp;
+	
+	// Rotate the skater so he is at a slight angle to the wall, especially if we are in a right angled corner, where the skater will bounce out
+
+	GetObject()->m_vel.RotateY(turn_angle);
+	GetObject()->m_matrix.RotateYLocal(turn_angle);
+	
+	return angle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::bounce_off_wall ( const Mth::Vector& normal, const Mth::Vector& point )
+{
+	// Given the normal of the wall, then bounce off it, turning the skater away from the wall
+	Mth::Vector col_start, col_end;
+	Mth::Vector	forward = GetObject()->m_pos - m_old_extrap_pos;
+	Mth::Vector movement = forward;				   		// remember how far we moved
+	CFeeler feeler;
+
+    forward.Normalize();
+	
+	Mth::Vector up_offset = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0xd4205c9b,"Skater_First_Forward_Collision_Height"));
+
+	float turn_angle;
+	float angle = rotate_away_from_wall(normal, turn_angle);
+
+	float min = Mth::DegToRad(GetPhysicsFloat(CRCD(0x1483fd01, "Wall_Bounce_Dont_Slow_Angle"))); 
+					 							
+	if (Mth::Abs(angle) > min)
+	{
+		//float old_speed = GetObject()->m_vel.Length();
+		
+		// The maximum value of Abs(angle) would be PI/2 (90 degrees), so scale the velocity 
+		float x = Mth::Abs(angle) - min;
+		x /= (Mth::PI / 2.0f) - min;		// get in the range 0 .. 1
+		x = 1.0f - x; 						// invert, as we want to stop when angle is 90
+		
+		GetObject()->m_vel *= x;
+	}
+	
+	// Bit of a patch here to move the skater away from the wall
+	// Not needed so much with new sideways collision checks but we keep it in for low ledges.
+	// Should perhaps standardize the height so collision checks for side and front but we'd probably still have problems.
+	
+	GetObject()->m_pos = point - up_offset;
+	GetObject()->m_pos += normal * 6.0f;
+	
+	// Now the majority of cases have been taken care of; we need to see if the skater is going to get stuck in a corner.
+
+	float old_speed = movement.Length();		 					// get how much we moved last time
+	Mth::Vector next_movement = GetObject()->m_vel;					// get new direction of velocity
+	forward = GetObject()->m_vel;
+	forward.Normalize();
+	next_movement = forward * old_speed;							// extend by same movment as last time
+	
+	col_start = GetObject()->m_pos + up_offset;
+	col_start += up_offset;
+	
+	col_end = GetObject()->m_pos + up_offset + next_movement
+		+ forward * GetPhysicsFloat(CRCD(0x20102726, "Skater_First_Forward_Collision_Length"));
+	
+	// Sanity check to make sure we're not using a line that's a whole level long
+	if(( col_start - col_end ).Length() > FEET( 20 ))
+	{
+		return;
+	}
+
+	feeler.SetLine( col_start, col_end );
+	feeler.SetIgnore( mFD_NON_COLLIDABLE, 0 );
+	if( feeler.GetCollision() && GetSkater()->IsLocalClient())
+	{
+		// Just rotating the skater will lead to another collision, so try just inverting the skater's velocity from it's original and halving it....
+		
+		// First reverse the rotation, and rotate 180 degrees
+		GetObject()->m_vel.RotateY(Mth::DegToRad(180.0f) - turn_angle);
+		GetObject()->m_matrix.RotateYLocal(Mth::DegToRad(180.0f) - turn_angle);
+		//ResetLerpingMatrix();
+		
+		GetObject()->m_vel *= 0.5f; 
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::extrapolate_position( void )
+{
+	GameNet::Manager* gamenet_man =  GameNet::Manager::Instance();
+	int i, prev_index, most_recent_index, mag_index;
+	SPosEvent* p_pos_history;
+	unsigned int delta_t, net_lag;
+	float coeff, ratio, total_mag, total_ratio, total_coeff, vel_mag;
+	Mth::Vector extrap_pos;
+	GameNet::PlayerInfo* local_player;
+	Mth::Vector vel;
+	Net::Client* client;
+
+	client = gamenet_man->GetClient( 0 );
+		
+	p_pos_history = mp_state_history_component->GetPosHistory();
+	most_recent_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+	prev_index = ( m_last_pos_index + ( CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - 1 )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+	
+	delta_t = (int) ( m_frame_length * Tmr::vRESOLUTION );
+	if( gamenet_man->OnServer())
+	{
+		GameNet::PlayerInfo* player;
+		Net::Conn* client_conn;
+
+		player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID());
+		Dbg_Assert( player );
+		client_conn = player->m_Conn;
+		net_lag = client_conn->GetAveLatency();
+	}
+	else
+	{
+		Lst::Search< Net::Conn > sh;
+		Net::Conn* server_conn;
+
+		server_conn = client->FirstConnection( &sh );
+		net_lag = server_conn->GetAveLatency();
+	}
+
+	// protect against dbz
+	if( delta_t == 0 )
+	{
+		ratio = 0;
+	}
+	else
+	{
+		ratio = (float) ( net_lag - delta_t ) / (float) delta_t;
+		if( ratio < 0 )
+		{
+			ratio = 0;
+		}
+	}
+	
+	mag_index = m_num_mags % vMAG_HISTORY_LENGTH;
+	vel = m_interp_pos - m_old_interp_pos;
+
+	coeff = 0.0f;
+	local_player = gamenet_man->GetLocalPlayer();
+	if( local_player && local_player->m_Skater )
+	{
+		Mth::Vector my_vel, their_vel;
+
+		my_vel = local_player->m_Skater->GetVel();
+		their_vel = vel;
+
+		my_vel.Normalize();
+		their_vel.Normalize();
+
+		// Get the dot product of our movement vectors
+		coeff = Mth::DotProduct( my_vel, their_vel );
+
+		// This will result in a value between -1.0f and 1.0f. Adding one will make that
+		// between 0 and 2
+		coeff += 1.0f;
+
+		// We want a value between 0 and 1, so divide by 2
+		coeff /= 2.0f;
+	}
+	else
+	{
+		coeff = 0.0f;
+	}
+
+	m_mag_history[mag_index] = ( vel.Length() / delta_t ) * 16;
+	m_ratio_history[mag_index] = ratio;
+	m_extrap_coeff_history[mag_index] = coeff;
+	m_num_mags++;
+
+	total_mag = 0;
+	total_ratio = 0;
+	total_coeff = 0;
+	for( i = 0; i < vMAG_HISTORY_LENGTH; i++ )
+	{
+		if( i >= m_num_mags )
+		{
+			break;
+		}
+		total_mag += m_mag_history[i];
+		total_ratio += m_ratio_history[i];
+		total_coeff += m_extrap_coeff_history[i];
+		
+	}
+	vel_mag = total_mag / i;
+	coeff = total_coeff / i;
+	ratio = total_ratio / i;
+	ratio *= coeff;
+
+	vel.Normalize();
+	vel *= vel_mag;
+	
+	extrap_pos = GetObject()->m_pos + ( vel * ratio );
+	extrap_pos[Y] = GetObject()->m_pos[Y];
+	GetObject()->m_pos = extrap_pos;
+	
+	Mth::Vector up_offset = GetObject()->m_matrix[Y] * GetPhysicsFloat(CRCD(0xd4205c9b, "Skater_First_Forward_Collision_Height"));
+
+	Mth::Vector col_start = m_interp_pos + up_offset;
+	Mth::Vector col_end = GetObject()->m_pos + up_offset;
+
+	CFeeler feeler;
+
+	// Sanity check to make sure we're not using a line that's a whole level long
+	if(( col_start - col_end ).Length() > FEET( 10 ))
+	{
+		return;
+	}
+
+	feeler.SetLine( col_start, col_end );
+	feeler.SetIgnore( mFD_NON_COLLIDABLE, 0 );
+
+	if( feeler.GetCollision())
+	{
+		bounce_off_wall( feeler.GetNormal(), feeler.GetPoint());
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::extrapolate_client_position (   )
+{
+	if( mp_state_history_component->GetNumPosUpdates() >= CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS )
+	{
+		if( mp_state_component->GetState() == RAIL )
+		{
+			extrapolate_rail_position();
+		}
+		else
+		{
+			extrapolate_position();
+			snap_to_ground();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::interpolate_client_position (   )
+{
+	GameNet::Manager* gamenet_man =  GameNet::Manager::Instance();
+	Net::Client* client;
+	
+	SPosEvent* p_pos_history = mp_state_history_component->GetPosHistory();
+
+	if(( client = gamenet_man->GetClient( 0 )))
+	{   
+		// Wait for the object update buffer to fill up
+		if( mp_state_history_component->GetNumPosUpdates() >= CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS )
+		{
+			unsigned int cur_time;
+			int i, prev_index, start_index, total_t, delta_t;
+			float ratio;
+			Mth::Vector delta_pos;
+            
+			// This will be true only if we've never performed an interpolation
+			if( m_client_initial_update_time == 0 )
+			{
+				//Dbg_Printf( "Resync'd Skater %d\n", GetID() );
+				// start off at the latest update and offset ourselves backwards in time from there
+				start_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+				
+				m_client_initial_update_time = client->m_Timestamp;
+				// impose a lag of N frames
+				m_server_initial_update_time = p_pos_history[start_index].GetTime() - Tmr::VBlanksF( vNUM_LAG_FRAMES );
+				m_last_pos_index = start_index;
+                
+				//Dbg_Printf( "(%d) server initial time = %d\n", nonlocalnet->m_FrameCounter, (int) m_server_initial_update_time );
+				//Dbg_Printf( "(%d) nonlocalnet initial time = %d\n", nonlocalnet->m_FrameCounter, (int) m_client_initial_update_time );
+				
+                GetObject()->SetMatrix( p_pos_history[start_index].Matrix );
+				GetObject()->SetDisplayMatrix( p_pos_history[start_index].Matrix );
+				GetObject()->SetPos(p_pos_history[start_index].Position);
+				GetObject()->SetTeleported(); 
+				GetObject()->m_vel.Set(0.0f, 0.0f, 0.0f);
+
+				// We don't want to actually render the skaters until we have buffered
+				// up their position updates and are ready to start interpolating. Now we have
+				// that data, so add them to the world
+				if( !GetSkater()->IsInWorld())
+				{
+					GameNet::PlayerInfo* player;
+                    
+					GetSkater()->AddToCurrentWorld();
+					Script::RunScript( "NetIdle", NULL, GetObject() );
+					//Hide( false );
+					player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+					Dbg_Assert( player );
+					
+                    // We're just adding them to the world.  If they were standing still,
+					// UberFrig would not have been done. So let's do it here
+					setup_brightness_and_shadow();
+				}
+			}
+			else
+			{   
+				Mth::SlerpInterpolator slerp;
+
+				Mth::Matrix interp_matrix;
+				int most_recent_index, flag;
+				GameNet::PlayerInfo* player;
+				
+				// Maybe a little bit overkill, but this will definitely set skaters that we can
+				// see in the level to be "fully in", which triggers things like their names in
+				// the score list
+				player = gamenet_man->GetPlayerByObjectID( GetObject()->GetID() );
+				if( !player->IsFullyIn())
+				{
+					player->MarkAsFullyIn();
+				}
+
+				start_index = mp_state_history_component->GetNumPosUpdates() % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+				most_recent_index = ( mp_state_history_component->GetNumPosUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+				
+				cur_time = m_server_initial_update_time + ( client->m_Timestamp - m_client_initial_update_time );
+
+				// If we've gone past the most recent update, slow down a little to allow updates
+				// to catch up
+				//Dbg_Printf( "(%d) NonLocalNet Timestamp %d\n", nonlocalnet->m_FrameCounter, nonlocalnet->m_Timestamp );
+				if( cur_time > p_pos_history[most_recent_index].GetTime())
+				{
+					// If we have some major catching up to do (usually at the beginning
+					// when there are long pauses after loading) catch up in one step
+					if(( cur_time - p_pos_history[most_recent_index].GetTime()) > Tmr::Seconds( 1 ))
+					{
+						//Dbg_Printf( "************************ (%d) Major catchup\n", client->m_FrameCounter );
+						m_server_initial_update_time -= ( cur_time - p_pos_history[most_recent_index].GetTime());
+					}
+					else
+					{
+						//Dbg_Printf( "************************ (%d) (%d) Slowdown\n", GetObject()->GetID(), client->m_FrameCounter );
+						m_server_initial_update_time -= Tmr::VBlanks( 1 );
+					}
+					
+					cur_time = m_server_initial_update_time + ( client->m_Timestamp - m_client_initial_update_time );
+					//Dbg_Printf( "- Slowdown\n" );
+				}
+
+				i = start_index;
+				do							   
+				{   
+					if( cur_time <= p_pos_history[i].GetTime()) 
+					{						 
+						static int lag = 0;
+						
+						m_last_pos_index = i;
+						lag = ( start_index + (CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - i )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+						
+						// We probably also should adjust if we get too far behind
+						if( lag >= 10 )
+						{   
+							//Dbg_Printf( "************************ (%d) (%d) +7 Catchup. Lag : %d\n", client->m_FrameCounter, GetObject()->GetID(), lag );
+							m_server_initial_update_time += Tmr::VBlanks( 10 );
+						}
+						else if( lag == 0 )
+						{
+							//Dbg_Printf( "************************ (%d) Resync'ing skater %d\n", client->m_FrameCounter, GetObject()->GetID() );
+							GetSkater()->Resync();
+						}
+
+						prev_index = ( i + ( CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - 1 )) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+						
+						delta_t = cur_time - p_pos_history[prev_index].GetTime();
+						total_t = p_pos_history[i].GetTime() - p_pos_history[prev_index].GetTime();
+						
+						ratio = (float) delta_t / (float) total_t;
+						
+						delta_pos = p_pos_history[i].Position - p_pos_history[prev_index].Position;
+
+						m_old_interp_pos = m_interp_pos;
+						
+						// Slerp between the two matrices
+						Mth::Matrix t1, t2;
+						Mth::Vector diff;
+
+						diff = p_pos_history[i].Matrix[Z] - p_pos_history[prev_index].Matrix[Z];
+						// Don't slerp if the matrices are way different. Just stick with the matrix
+						// we have until the switch is made. 
+						if( diff.Length() > 0.75f )
+						{
+							GetObject()->SetMatrix( p_pos_history[prev_index].Matrix );
+							GetObject()->SetDisplayMatrix( p_pos_history[prev_index].Matrix );
+						}
+						else
+						{
+							t1 = p_pos_history[prev_index].Matrix;
+							t2 = p_pos_history[i].Matrix;
+							slerp.setMatrices( &t1, &t2 );
+							slerp.getMatrix( &interp_matrix, ratio );
+							
+							GetObject()->SetMatrix( interp_matrix );
+							GetObject()->SetDisplayMatrix( interp_matrix );
+						}
+						
+						if( delta_pos.Length() > FEET( 50 ))
+						{
+							GetObject()->m_pos = p_pos_history[i].Position;
+							GetObject()->m_vel = Mth::Vector( 0, 0, 0 );
+							GetObject()->SetTeleported();
+						}
+						else
+						{
+							int idx, mag_index;
+							Mth::Vector total_vel;
+
+							GetObject()->m_pos = p_pos_history[prev_index].Position + ( delta_pos * ratio );
+							GetObject()->m_vel = ( GetObject()->m_pos - m_old_interp_pos ) / m_frame_length; // do_nonlocalnet_position_update, calculating speed
+						
+							mag_index = m_num_mags % vMAG_HISTORY_LENGTH;
+
+							// Smooth out changes in velocity
+							m_vel_history[mag_index] = GetObject()->m_vel;
+							for( idx = 0; idx < vMAG_HISTORY_LENGTH; idx++ )
+							{
+								if( idx >= m_num_mags )
+								{
+									break;
+								}
+								
+								total_vel += m_vel_history[idx];
+							}
+
+							if( idx > 0 )
+							{
+								GetObject()->m_vel = total_vel / idx;
+							}
+						}
+
+						m_interp_pos = GetObject()->m_pos;
+
+						// Apply the state/flag data from this PosEvent
+						mp_state_component->m_state = static_cast< EStateType >( p_pos_history[i].State );
+						mp_state_component->m_doing_trick = p_pos_history[i].DoingTrick;
+						mp_state_component->m_terrain = p_pos_history[i].Terrain;
+						mp_state_component->m_physics_state = p_pos_history[i].Walking ? WALKING : SKATING;
+						mp_state_component->m_driving = p_pos_history[i].Driving;
+						mp_endrun_component->SetFlags(  p_pos_history[i].EndRunFlags );
+						if( p_pos_history[i].EndRunFlags & CSkaterEndRunComponent::FINISHED_END_OF_RUN )
+						{
+							//Dbg_Printf( "*** At END OF RUN ***\n" );
+						}
+						
+						for( flag = 0; flag < NUM_ESKATERFLAGS; flag++ )
+						{
+							mp_state_component->m_skater_flags[flag].Set(p_pos_history[i].SkaterFlags.Test( flag ));
+						}
+						if( mp_state_component->m_skater_flags[SPINE_PHYSICS].Get())
+						{
+							mp_state_component->m_spine_vel = GetObject()->m_vel;
+						}
+						else
+						{
+							mp_state_component->m_spine_vel.Set(0.0f, 0.0f, 0.0f);
+						}
+
+						break;
+					}
+					i = ( i + 1 ) % CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+				} while( i != start_index );
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterNonLocalNetLogicComponent::do_client_animation_update(   )
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client;
+
+	Dbg_Assert( mp_animation_component );
+
+	if(( client = gamenet_man->GetClient( 0 )))
+	{   
+		// Wait for the object update buffer to fill up
+		if( mp_state_history_component->GetNumPosUpdates() >= CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS )
+		{
+			unsigned int cur_time;
+			int i, start_index;
+			SAnimEvent* event;
+			int most_recent_index;
+            
+			start_index = mp_state_history_component->GetNumAnimUpdates() % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS;
+			most_recent_index = ( mp_state_history_component->GetNumAnimUpdates() - 1 ) % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS;
+			
+			cur_time = m_server_initial_update_time + ( client->m_Timestamp - m_client_initial_update_time );
+
+			i = start_index;
+			bool updated = false;
+			do							   
+			{   
+				event = &mp_state_history_component->GetAnimHistory()[i];
+				//Dbg_Printf( "(%d) CurTime: %d EventTime[%d]: %d\n", client->m_FrameCounter, cur_time, event->m_MsgId, event->GetTime());
+				if( ( event->GetTime() > m_last_anm_update_time ) &&
+					( event->GetTime() <= cur_time ))
+				{   
+					updated = true;
+					switch( event->m_MsgId )
+					{
+						case GameNet::MSG_ID_PRIM_ANIM_START:
+						{
+							//Dbg_Printf( "(%d) Playing primary anim: 0x%x Speed: %f Duration: %d Time:%d\n", client->m_Timestamp, event->m_Asset, event->m_Speed, event->m_Duration, event->GetTime());
+							mp_animation_component->PlayPrimarySequence( event->m_Asset, false, event->m_StartTime, event->m_EndTime,
+																		 (Gfx::EAnimLoopingType) event->m_LoopingType, 
+																		 event->m_BlendPeriod, event->m_Speed );
+	
+							break;
+						}
+						case GameNet::MSG_ID_SET_WOBBLE_TARGET:
+						{
+							mp_animation_component->SetWobbleTarget( event->m_Alpha, false );
+							break;
+						}
+						case GameNet::MSG_ID_ROTATE_SKATEBOARD:
+						{
+							mp_flip_and_rotate_component->RotateSkateboard( event->m_ObjId, event->m_Rotate, 0, false );
+							break;
+						}
+						case GameNet::MSG_ID_SET_WOBBLE_DETAILS:
+						{
+							Gfx::SWobbleDetails wobbleDetails;
+				
+							wobbleDetails.wobbleAmpA = event->m_WobbleAmpA;
+							wobbleDetails.wobbleAmpB = event->m_WobbleAmpB;
+							wobbleDetails.wobbleK1 = event->m_WobbleK1;
+							wobbleDetails.wobbleK2 = event->m_WobbleK2;
+							wobbleDetails.spazFactor = event->m_SpazFactor;
+
+							mp_animation_component->SetWobbleDetails( wobbleDetails, false );
+							break;
+						}
+						case GameNet::MSG_ID_SET_LOOPING_TYPE:
+						{
+							mp_animation_component->SetLoopingType( (Gfx::EAnimLoopingType) event->m_LoopingType, false );
+							break;
+						}
+						case GameNet::MSG_ID_SET_HIDE_ATOMIC:
+						{
+						   	mp_model_component->HideGeom( event->m_Asset, event->m_Hide, false );
+							break;
+						}
+						case GameNet::MSG_ID_SET_ANIM_SPEED:
+						{
+							mp_animation_component->SetAnimSpeed( event->m_Speed, false );
+							break;
+						}
+						case GameNet::MSG_ID_FLIP_ANIM:
+						{
+							mp_animation_component->FlipAnimation( event->m_ObjId, event->m_Flipped, 0, false );
+							break;
+						}
+						case GameNet::MSG_ID_ROTATE_DISPLAY:
+						{
+							mp_model_component->m_display_rotation_offset.Set(0,30,0);
+							Tmr::Time start_time=Tmr::ElapsedTime(0);
+			
+							if( event->m_Flags & 1 )
+							{
+								mp_model_component->mpDisplayRotationInfo[0].SetUp(event->m_Duration,
+															   start_time,
+															   event->m_StartAngle,
+															   event->m_DeltaAngle,
+															   event->m_SinePower,
+															   event->m_HoldOnLastAngle);
+							}	
+							if( event->m_Flags & 2 )
+							{
+								mp_model_component->mpDisplayRotationInfo[1].SetUp(event->m_Duration,
+															   start_time,
+															   event->m_StartAngle,
+															   event->m_DeltaAngle,
+															   event->m_SinePower,
+															   event->m_HoldOnLastAngle);
+							}	
+							if( event->m_Flags & 4 )
+							{
+								mp_model_component->mpDisplayRotationInfo[2].SetUp(event->m_Duration,
+															   start_time,
+															   event->m_StartAngle,
+															   event->m_DeltaAngle,
+															   event->m_SinePower,
+															   event->m_HoldOnLastAngle);
+							}	
+							break;
+						}
+						case (char) GameNet::MSG_ID_CLEAR_ROTATE_DISPLAY:
+						{
+							mp_model_component->mpDisplayRotationInfo[0].Clear();
+							mp_model_component->mpDisplayRotationInfo[1].Clear();
+							mp_model_component->mpDisplayRotationInfo[2].Clear();
+							break;
+						}
+						case GameNet::MSG_ID_CREATE_SPECIAL_ITEM:
+						{
+							CSpecialItemComponent* pSpecialItemComponent = GetSpecialItemComponentFromObject(GetObject());
+							Dbg_MsgAssert( pSpecialItemComponent, ( "No special item component?" ) );
+
+							Script::CStruct* pSpecialItemParams = Script::GetStructure( event->m_Asset, Script::ASSERT );
+							CCompositeObject* pSpecialItemObject = pSpecialItemComponent->CreateSpecialItem( event->m_Index, pSpecialItemParams );
+								
+							Script::CStruct* pLockParams = new Script::CStruct;
+							// pass along any bone or offset parameters...
+							//pLockParams->AppendStructure( pParams );
+							pLockParams->AddChecksum( CRCD(0xcab94088,"bone"), event->m_Bone );
+							pLockParams->AddChecksum( CRCD(0x40c698af,"id"), GetObject()->GetID() );
+								
+							// component-based
+							CLockObjComponent* pLockObjComponent = GetLockObjComponentFromObject( pSpecialItemObject );
+							Dbg_MsgAssert( pLockObjComponent, ( "No lockobj component" ) );
+							pLockObjComponent->InitFromStructure( pLockParams );				
+												
+							delete pLockParams;
+						}
+						break;
+
+						case GameNet::MSG_ID_DESTROY_SPECIAL_ITEM:
+						{
+							CSpecialItemComponent* pSpecialItemComponent = GetSpecialItemComponentFromObject(GetObject());
+                            pSpecialItemComponent->DestroySpecialItem( event->m_Index );
+							break;
+						}
+					}
+				}
+				i = ( i + 1 ) % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS;
+			} while( i != start_index );
+			
+			/*static Tmr::Time s_time = 0;
+
+			if( !updated )
+			{
+				if(( Tmr::GetTime() - s_time ) > 1000 )
+				{
+					i = start_index;
+					do							   
+					{   
+						event = &mp_state_history_component->GetAnimHistory()[i];
+						Dbg_Printf( "(%d) CurTime: %d EventTime[%d]: %d\n", client->m_FrameCounter, cur_time, event->m_MsgId, event->GetTime());
+						i = ( i + 1 ) % CSkaterStateHistoryComponent::vNUM_ANIM_HISTORY_ELEMENTS;
+					} while( i != start_index );
+					s_time = Tmr::GetTime();
+				}
+			}*/
+            
+			m_last_anm_update_time = cur_time;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+					
+void CSkaterNonLocalNetLogicComponent::setup_brightness_and_shadow (   )
+{
+	// logic extracted from CAdjustComponent::uber_frig which is judged to be required by nonlocal clients
+	
+	CFeeler feeler;
+	
+	feeler.m_start = GetObject()->m_pos;
+	feeler.m_end = GetObject()->m_pos;
+
+	// Very minor adjustment to move origin away from vert walls
+	feeler.m_start += GetObject()->m_matrix[Y] * 0.001f;
+	
+	feeler.m_start[Y] += 8.0f;
+	feeler.m_end[Y] -= FEET(400);
+		   
+	if (!feeler.GetCollision()) return;
+	
+	if (mp_state_component->GetState() != RAIL && feeler.IsBrightnessAvailable())
+	{
+		Nx::CModelLights *p_model_lights = mp_model_component->GetModel()->GetModelLights();
+		if (p_model_lights)
+		{
+			p_model_lights->SetBrightness(feeler.GetBrightness());
+		} 
+		else
+		{
+			// Garrett: This should move to CModel eventually
+			Nx::CLightManager::sSetBrightness(feeler.GetBrightness());
+		}
+	}
+	
+	// Store these values off for the simple shadow calculation.
+	mp_shadow_component->SetShadowPos(feeler.GetPoint());
+	mp_shadow_component->SetShadowNormal(feeler.GetNormal()); 
+}
+
+}
diff --git a/Code/Sk/Components/SkaterNonLocalNetLogicComponent.h b/Code/Sk/Components/SkaterNonLocalNetLogicComponent.h
new file mode 100644
index 0000000..968ea7c
--- /dev/null
+++ b/Code/Sk/Components/SkaterNonLocalNetLogicComponent.h
@@ -0,0 +1,110 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterNonLocalNetLogicComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/11/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERNONLOCALNETLOGICCOMPONENT_H__
+#define __COMPONENTS_SKATERNONLOCALNETLOGICCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERNONLOCALNETLOGIC CRCD(0x850eef66, "SkaterNonLocalNetLogic")
+
+#define		GetSkaterNonLocalNetLogicComponent() ((Obj::CSkaterNonLocalNetLogicComponent*)GetComponent(CRC_SKATERNONLOCALNETLOGIC))
+#define		GetSkaterNonLocalNetLogicComponentFromObject(pObj) ((Obj::CSkaterNonLocalNetLogicComponent*)(pObj)->GetComponent(CRC_SKATERNONLOCALNETLOGIC))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterStateHistoryComponent;
+	class CSkaterCorePhysicsComponent;
+	class CSkaterStateComponent;
+	class CSkaterEndRunComponent;
+	class CSkaterFlipAndRotateComponent;
+	class CShadowComponent;
+	class CRailNode;
+	
+enum
+{
+	vMAG_HISTORY_LENGTH = 20
+};
+
+class CSkaterNonLocalNetLogicComponent : public CBaseComponent
+{
+public:
+    CSkaterNonLocalNetLogicComponent();
+    virtual ~CSkaterNonLocalNetLogicComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							Resync();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+private:
+	void							setup_brightness_and_shadow (   );
+	void							interpolate_client_position (   );
+	void							extrapolate_client_position (   );
+	void							extrapolate_rail_position( void );
+	CRailNode*						travel_on_rail( CRailNode* start_node, float frame_length );
+	float 							rotate_away_from_wall( Mth::Vector normal, float &turn_angle );
+	void 							bounce_off_wall( const Mth::Vector& normal, const Mth::Vector& point );
+	void 							snap_to_ground( void  );
+	void							extrapolate_position( void );
+	void							do_client_animation_update (   );
+	
+private:
+ 	Tmr::Time						m_client_initial_update_time;
+	//Tmr::Time						m_client_initial_anm_update_time;
+	Tmr::Time						m_server_initial_update_time;
+	//Tmr::Time						m_server_initial_anm_update_time;
+	Tmr::Time						m_last_anm_update_time;
+	int								m_last_pos_index;
+	
+	Mth::Vector						m_interp_pos;
+	Mth::Vector						m_old_interp_pos;
+	Mth::Vector						m_old_extrap_pos;
+	Mth::Vector						m_current_normal;
+	float							m_mag_history[vMAG_HISTORY_LENGTH];
+	float							m_ratio_history[vMAG_HISTORY_LENGTH];
+	float							m_extrap_coeff_history[vMAG_HISTORY_LENGTH];
+	Mth::Vector						m_vel_history[vMAG_HISTORY_LENGTH];
+	int								m_num_mags;
+	
+	float							m_frame_length;
+	
+	bool							m_last_driving;
+	
+	CSkaterStateHistoryComponent*	mp_state_history_component;
+	CAnimationComponent*			mp_animation_component;
+	CModelComponent*				mp_model_component;
+	CSkaterStateComponent*			mp_state_component;
+	CSkaterEndRunComponent*			mp_endrun_component;
+	CSkaterFlipAndRotateComponent*	mp_flip_and_rotate_component;
+    CShadowComponent*				mp_shadow_component;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterPhysicsControlComponent.cpp b/Code/Sk/Components/SkaterPhysicsControlComponent.cpp
new file mode 100644
index 0000000..82cedce
--- /dev/null
+++ b/Code/Sk/Components/SkaterPhysicsControlComponent.cpp
@@ -0,0 +1,500 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterPhysicsControlComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/7/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef TESTING_GUNSLINGER
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterPhysicsControlComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterPhysicsControlComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterPhysicsControlComponent::CSkaterPhysicsControlComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERPHYSICSCONTROL );
+	
+	mp_core_physics_component = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterPhysicsControlComponent::~CSkaterPhysicsControlComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterPhysicsControlComponent added to non-skater composite object"));
+	
+	m_physics_suspended = false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::Finalize(  )
+{
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	mp_state_component = GetSkaterStateComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_core_physics_component);
+	Dbg_Assert(mp_state_component);
+	
+	mp_state_component->m_physics_state = NO_STATE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::Update()
+{
+	m_restarted_this_frame = false;
+	
+	// SkaterPhysicsControlComponent is inactive when physics is NOT suspended.  When physics is suspended SkaterPhysicsControlComponent handles
+	// setting the skater's display matrix.
+	if (!m_physics_suspended) return;
+	
+	switch (mp_state_component->m_physics_state)
+	{
+		case SKATING:
+			GetObject()->SetDisplayMatrix(mp_core_physics_component->m_lerping_display_matrix);
+			break;
+		
+		case WALKING:
+			GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
+			break;
+			
+		default:
+			Dbg_MsgAssert(false, ("No skater physics state switched on before first skater update"));
+	}
+	
+	// If the skater is unsuspended, the physics components will unsuspend as well.  We must check for this and resuspend
+	if (!mp_core_physics_component->IsSuspended())
+	{
+		suspend_physics(true);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterPhysicsControlComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | PausePhysics | PausePhysics will stop all programmatic movement and rotation of the skater
+		case CRCC(0x3522d612, "PausePhysics"):
+			if (!m_physics_suspended)
+			{
+				suspend_physics(true);
+			}
+			break;
+			
+        // @script | UnPausePhysics | 
+		case CRCC(0x595627c, "UnPausePhysics"):
+			if (m_physics_suspended)
+			{
+				suspend_physics(false);
+			}
+			break;
+			
+		case CRCC(0x57bfbae8, "Walking"):
+			return IsWalking() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		case CRCC(0xf2813ee5, "Skating"):
+			return IsSkating() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		case CRCC(0xee584cbc, "Driving"):
+			return IsDriving() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+		case CRCC(0xb32e9eae, "SetDriving"):
+			mp_state_component->m_driving = true;
+			break;
+			
+		case CRCC(0x953a7b6d, "UnsetDriving"):
+			mp_state_component->m_driving = false;
+			break;
+			
+		case CRCC(0x9569d5b1, "GetTimeSincePhysicsSwitch"):
+			pScript->GetParams()->AddFloat(CRCD(0x27f48e93, "TimeSincePhysicsSwitch"), Tmr::ElapsedTime(m_physics_state_switch_time_stamp) * (1.0f / 1000.0f));
+			break;
+			
+		case CRCC(0xa887285a, "GetPreviousPhysicsStateDuration"):
+			pScript->GetParams()->AddFloat(CRCD(0x24d061ba, "PreviousPhysicsStateDuration"), m_previous_physics_state_duration * (1.0f / 1000.0f));
+			break;
+			
+		case CRCC(0x5c038f9b, "SkaterPhysicsControl_SwitchWalkingToSkating"):
+			switch_walking_to_skating();
+			break;
+			
+		case CRCC(0x6e8e39b3, "SkaterPhysicsControl_SwitchSkatingToWalking"):
+			switch_skating_to_walking();
+			break;
+			
+		case CRCC(0x9366a509, "SetBoardMissing"):
+			m_board_missing = true;
+			break;
+			
+		case CRCC(0x4830e80, "UnsetBoardMissing"):
+			m_board_missing = false;
+			break;
+		
+		case CRCC(0xd5a9f889, "IsBoardMissing"):
+			return m_board_missing ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			
+#		ifdef TESTING_GUNSLINGER
+		case CRCC(0x14c4f16b,"SkaterPhysicsControl_SwitchWalkingToRiding"):
+			switch_walking_to_riding();
+			break;
+
+		case CRCC(0x82604c1e,"SkaterPhysicsControl_SwitchRidingToWalking"):
+			switch_riding_to_walking();
+			break;
+#		endif
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterPhysicsControlComponent::GetDebugInfo"));
+	
+	p_info->AddChecksum("m_physics_suspended", m_physics_suspended ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	p_info->AddInteger("m_physics_state", mp_state_component->m_physics_state);
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::suspend_physics ( bool suspend )
+{
+	m_physics_suspended = suspend;
+	
+	// If the skater is suspended, we don't want to unsuspend the physics components.  We only want to change the CSkaterPhysicsControlComponent state.
+	if (!suspend && GetObject()->IsSuspended()) return;
+    
+	switch (mp_state_component->m_physics_state)
+	{
+		case SKATING:
+			Dbg_Assert(mp_core_physics_component);
+			Dbg_Assert(GetSkaterRotateComponentFromObject(GetObject()));
+			Dbg_Assert(GetSkaterAdjustPhysicsComponentFromObject(GetObject()));
+			Dbg_Assert(GetSkaterFinalizePhysicsComponentFromObject(GetObject()));
+			
+			mp_core_physics_component->Suspend(suspend);
+			GetSkaterRotateComponentFromObject(GetObject())->Suspend(suspend);
+			GetSkaterAdjustPhysicsComponentFromObject(GetObject())->Suspend(suspend);
+			GetSkaterFinalizePhysicsComponentFromObject(GetObject())->Suspend(suspend);
+			break;
+
+		case WALKING:
+			Dbg_Assert(GetWalkComponentFromObject(GetObject()));
+			
+			GetWalkComponentFromObject(GetObject())->Suspend(suspend);
+			break;
+			
+		default:
+			Dbg_Assert(false);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::switch_walking_to_skating (   )
+{
+	if (mp_state_component->m_physics_state == SKATING) return;
+	
+	m_previous_physics_state_duration = Tmr::ElapsedTime(m_physics_state_switch_time_stamp);
+	m_physics_state_switch_time_stamp = Tmr::GetTime();
+	
+	CWalkComponent* p_walk_component = GetWalkComponentFromObject(GetObject());
+	CCompositeObject* p_skater_cam = get_skater_camera();
+	CSkaterCameraComponent* p_skater_camera_component = GetSkaterCameraComponentFromObject(p_skater_cam);
+	CWalkCameraComponent* p_walk_camera_component = GetWalkCameraComponentFromObject(p_skater_cam);
+	
+	Dbg_Assert(p_walk_component);
+	Dbg_Assert(p_skater_camera_component);
+	Dbg_Assert(p_walk_camera_component);
+	
+	// switch off walking
+	
+	p_walk_component->CleanUpWalkState();
+	p_walk_component->Suspend(true);
+	
+	p_walk_camera_component->Suspend(true);
+	
+	// switch on skating
+
+	mp_state_component->m_physics_state = SKATING;
+	
+	mp_core_physics_component->Suspend(false);
+	mp_core_physics_component->ReadySkateState(p_walk_component->GetState() == CWalkComponent::WALKING_GROUND, p_walk_component->GetRailData(), p_walk_component->GetAcidDropData());
+	
+	GetSkaterRotateComponentFromObject(GetObject())->Suspend(false);
+	GetSkaterAdjustPhysicsComponentFromObject(GetObject())->Suspend(false);
+	GetSkaterFinalizePhysicsComponentFromObject(GetObject())->Suspend(false);
+	
+	p_skater_camera_component->Suspend(false);
+	
+	// exchange camera states
+	SCameraState camera_state;
+	p_walk_camera_component->GetCameraState(camera_state);
+	p_skater_camera_component->ReadyForActivation(camera_state);
+	
+	// reapply the physics suspend state
+	if (m_physics_suspended)
+	{
+		suspend_physics(m_physics_suspended);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPhysicsControlComponent::switch_skating_to_walking (   )
+{
+	if (mp_state_component->m_physics_state == WALKING) return;
+	
+	m_previous_physics_state_duration = Tmr::ElapsedTime(m_physics_state_switch_time_stamp);
+	m_physics_state_switch_time_stamp = Tmr::GetTime();
+	
+	CWalkComponent* p_walk_component = GetWalkComponentFromObject(GetObject());
+	CCompositeObject* p_skater_cam = get_skater_camera();
+	CSkaterCameraComponent* p_skater_camera_component = GetSkaterCameraComponentFromObject(p_skater_cam);
+	CWalkCameraComponent* p_walk_camera_component = GetWalkCameraComponentFromObject(p_skater_cam);
+	
+	Dbg_Assert(p_walk_component);
+	Dbg_Assert(p_skater_camera_component);
+	Dbg_Assert(p_walk_camera_component);
+	
+	// switch off skating
+	
+	bool ground = mp_core_physics_component->GetState() == GROUND;
+	mp_core_physics_component->CleanUpSkateState();
+	mp_core_physics_component->Suspend(true);
+
+	GetSkaterRotateComponentFromObject(GetObject())->Suspend(true);
+	GetSkaterAdjustPhysicsComponentFromObject(GetObject())->Suspend(true);
+	GetSkaterFinalizePhysicsComponentFromObject(GetObject())->Suspend(true);
+
+	p_skater_camera_component->Suspend(true);
+	
+	// switch on walking
+		
+	mp_state_component->m_physics_state = WALKING;
+	
+	p_walk_component->Suspend(false);
+	p_walk_component->ReadyWalkState(ground);
+	
+	p_walk_camera_component->Suspend(false);
+	
+	// exchange camera states
+	SCameraState camera_state;
+	p_skater_camera_component->GetCameraState(camera_state);
+	p_walk_camera_component->ReadyForActivation(camera_state);
+	
+	// reapply the physics suspend state
+	if (m_physics_suspended)
+	{
+		suspend_physics(m_physics_suspended);
+	}
+}
+
+
+#ifdef TESTING_GUNSLINGER
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterPhysicsControlComponent::switch_walking_to_riding( void )
+{
+	if (mp_state_component->m_physics_state == SKATING) return;
+	if (mp_state_component->m_physics_state == RIDING) return;
+	
+	CRiderComponent*		p_rider_component			= GetRiderComponentFromObject(GetObject());
+
+	// Use the rider component to check that this switch is valid.
+	if(	p_rider_component->ReadyRiderState( true ))
+	{
+		CWalkComponent*			p_walk_component			= GetWalkComponentFromObject(GetObject());
+		CCompositeObject*		p_skater_cam				= get_skater_camera();
+		CSkaterCameraComponent*	p_skater_camera_component	= GetSkaterCameraComponentFromObject(p_skater_cam);
+		CWalkCameraComponent*	p_walk_camera_component		= GetWalkCameraComponentFromObject(p_skater_cam);
+	
+		Dbg_Assert(p_walk_component);
+		Dbg_Assert(p_skater_camera_component);
+		Dbg_Assert(p_walk_camera_component);
+	
+		// switch off walking
+		p_walk_component->Suspend(true);
+		p_walk_camera_component->Suspend(true);
+	
+		// switch on riding
+		mp_state_component->m_physics_state = RIDING;
+	
+//		mp_core_physics_component->Suspend(false);
+//		mp_core_physics_component->ReadySkateState(p_walk_component->GetState() == CWalkComponent::WALKING_GROUND, p_walk_component->GetRailData());
+		p_rider_component->Suspend( false );
+	
+//		GetSkaterRotateComponentFromObject(GetObject())->Suspend(false);
+//		GetSkaterAdjustPhysicsComponentFromObject(GetObject())->Suspend(false);
+//		GetSkaterFinalizePhysicsComponentFromObject(GetObject())->Suspend(false);
+//		GetSkaterCleanupStateComponentFromObject(GetObject())->Suspend(false);
+	
+//		p_skater_camera_component->Suspend(false);
+	
+		// exchange camera states
+//		SCameraState camera_state;
+//		p_walk_camera_component->GetCameraState(camera_state);
+//		p_skater_camera_component->ReadyForActivation(camera_state);
+	
+		// reapply the physics suspend state
+		if (m_physics_suspended)
+		{
+			suspend_physics(m_physics_suspended);
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterPhysicsControlComponent::switch_riding_to_walking( void )
+{
+//	if (mp_state_component->m_physics_state == WALKING) return;
+	
+	CWalkComponent*			p_walk_component = GetWalkComponentFromObject(GetObject());
+	CRiderComponent*		p_rider_component			= GetRiderComponentFromObject(GetObject());
+	CCompositeObject*		p_skater_cam = get_skater_camera();
+	CSkaterCameraComponent*	p_skater_camera_component = GetSkaterCameraComponentFromObject(p_skater_cam);
+	CWalkCameraComponent*	p_walk_camera_component = GetWalkCameraComponentFromObject(p_skater_cam);
+	
+	Dbg_Assert(p_walk_component);
+	Dbg_Assert(p_skater_camera_component);
+	Dbg_Assert(p_walk_camera_component);
+	
+	// switch off skating
+	mp_core_physics_component->Suspend(true);
+
+	GetSkaterRotateComponentFromObject(GetObject())->Suspend(true);
+	GetSkaterAdjustPhysicsComponentFromObject(GetObject())->Suspend(true);
+	GetSkaterFinalizePhysicsComponentFromObject(GetObject())->Suspend(true);
+//	GetSkaterCleanupStateComponentFromObject(GetObject())->Suspend(true);
+
+	p_skater_camera_component->Suspend(true);
+	
+	// Switch off riding.
+	p_rider_component->Suspend( true );
+
+	// switch on walking
+	mp_state_component->m_physics_state = WALKING;
+	
+	p_walk_component->Suspend(false);
+	p_walk_component->ReadyWalkState(mp_core_physics_component->GetState() == GROUND);
+	p_walk_camera_component->Suspend( false );
+	
+	// exchange camera states
+//	SCameraState camera_state;
+//	p_skater_camera_component->GetCameraState( camera_state );
+//	p_walk_camera_component->ReadyForActivation( camera_state );
+	
+	// reapply the physics suspend state
+	if (m_physics_suspended)
+	{
+		suspend_physics(m_physics_suspended);
+	}
+}
+
+
+
+#endif
+
+
+
+
+}
diff --git a/Code/Sk/Components/SkaterPhysicsControlComponent.h b/Code/Sk/Components/SkaterPhysicsControlComponent.h
new file mode 100644
index 0000000..cdc7562
--- /dev/null
+++ b/Code/Sk/Components/SkaterPhysicsControlComponent.h
@@ -0,0 +1,195 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterPhysicsControlComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/7/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERPHYSICSCONTROLCOMPONENT_H__
+#define __COMPONENTS_SKATERPHYSICSCONTROLCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#define		CRC_SKATERPHYSICSCONTROL CRCD(0xd9151d4d, "SkaterPhysicsControl")
+
+#define		GetSkaterPhysicsControlComponent() ((Obj::CSkaterPhysicsControlComponent*)GetComponent(CRC_SKATERPHYSICSCONTROL))
+#define		GetSkaterPhysicsControlComponentFromObject(pObj) ((Obj::CSkaterPhysicsControlComponent*)(pObj)->GetComponent(CRC_SKATERPHYSICSCONTROL))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterCorePhysicsComponent;
+	class CSkaterStateComponent;
+	class CRailManager;
+	class CRailNode;
+	
+	enum ESpecialTransitionType
+	{
+		NO_SPECIAL_TRANSITION,
+		RAIL_TRANSITION,
+		ACID_DROP_TRANSITION
+	};
+
+	struct SRailData
+	{
+		Mth::Vector					rail_pos;
+		CRailManager*				p_rail_man;
+		CRailNode*					p_node;
+		CCompositeObject*			p_movable_contact;
+	};
+	
+	struct SAcidDropData
+	{
+		Mth::Vector					target_pos;
+		Mth::Vector					target_normal;
+		float						true_target_height;
+	};
+
+class CSkaterPhysicsControlComponent : public CBaseComponent
+{
+public:
+    CSkaterPhysicsControlComponent();
+    virtual ~CSkaterPhysicsControlComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		Finalize();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+			 	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+
+	void							SuspendPhysics ( bool suspend );
+	bool							IsPhysicsSuspended (   ) { return m_physics_suspended; }
+	
+	bool							IsSkating (   );
+	bool							IsWalking (   );
+	bool							IsDriving (   );
+	
+	Tmr::Time						GetStateSwitchTime (   );
+	Tmr::Time						GetPreviousPhysicsStateDuration (   );
+	
+	bool							IsBoardMissing (   ) { return m_board_missing; }
+	bool							HaveBeenReset (   ) { return m_restarted_this_frame; }
+	void							NotifyReset (   ) { m_restarted_this_frame = true; }
+	
+private:
+	CCompositeObject*				get_skater_camera (   );
+	void							suspend_physics ( bool suspend );
+	
+	void							switch_skating_to_walking (   );
+	void							switch_walking_to_skating (   );
+
+#	ifdef TESTING_GUNSLINGER
+	void							switch_walking_to_riding (   );
+	void							switch_riding_to_walking (   );
+#	endif
+
+
+private:
+	bool							m_physics_suspended;
+	
+	Tmr::Time						m_physics_state_switch_time_stamp;
+	Tmr::Time						m_previous_physics_state_duration;
+	
+	bool							m_board_missing;
+	
+	bool							m_restarted_this_frame;  		// Set if a restart occured, so we can early out
+	
+	// peer components
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+	CSkaterStateComponent*			mp_state_component;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CSkaterPhysicsControlComponent::IsDriving (   )
+{
+	return mp_state_component->m_driving;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CSkaterPhysicsControlComponent::IsSkating (   )
+{
+	return !IsDriving() && mp_state_component->m_physics_state == SKATING;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CSkaterPhysicsControlComponent::IsWalking (   )
+{
+	return !IsDriving() && mp_state_component->m_physics_state == WALKING;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Tmr::Time CSkaterPhysicsControlComponent::GetStateSwitchTime (   )
+{
+	return m_physics_state_switch_time_stamp;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Tmr::Time CSkaterPhysicsControlComponent::GetPreviousPhysicsStateDuration (   )
+{
+	return m_previous_physics_state_duration;
+}
+
+/*****************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterPhysicsControlComponent::SuspendPhysics ( bool suspend )
+{
+	if (m_physics_suspended == suspend) return;
+	
+	suspend_physics(suspend);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline CCompositeObject* CSkaterPhysicsControlComponent::get_skater_camera (   )
+{
+	
+	return static_cast< CSkater* >(GetObject())->GetCamera();
+}
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterProximityComponent.cpp b/Code/Sk/Components/SkaterProximityComponent.cpp
new file mode 100644
index 0000000..444645f
--- /dev/null
+++ b/Code/Sk/Components/SkaterProximityComponent.cpp
@@ -0,0 +1,374 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkaterProximityComponent.cpp
+//* OWNER:          Mick West
+//* CREATION DATE:  3/20/03
+//****************************************************************************
+
+/*
+
+	Component for raising exceptions/events when the skater gets within a particular radius
+	this used to be part of the ExceptionComponent, but as that is slated for destruction,
+	I've had to seperate it out.  					
+
+*/
+					
+// The CSkaterProximityComponent class is an skeletal version of a component
+// It is intended that you use this as the basis for creating new
+// components.  
+// To create a new component called "Watch", (CWatchComponent):
+//  - copy SkaterProximitycomponent.cpp/.h to watchcomponent.cpp/.h
+//  - in both files, search and replace "SkaterProximity" with "Watch", preserving the case
+//  - in WatchComponent.h, update the CRCD value of CRC_WATCH
+//  - in CompositeObjectManager.cpp, in the CCompositeObjectManager constructor, add:
+//		  	RegisterComponent(CRC_WATCH,			CWatchComponent::s_create); 
+//  - and add the include of the header
+//			#include  
+//  - Add it to build\gel.mkf, like:
+//          $(NGEL)/components/WatchComponent.cpp\
+//  - Fill in the OWNER (yourself) and the CREATION DATE (today's date) in the .cpp and the .h files
+//	- Insert code as needed and remove generic comments
+//  - remove these comments
+//  - add comments specfic to the component, explaining its usage
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+#include 
+#include 
+
+
+#define	FLAGEXCEPTION(X) GetObject()->SelfEvent(X)
+
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// s_create is what is registered with the component factory 
+// object, (currently the CCompositeObjectManager) 
+// s_create	returns a CBaseComponent*, as it is to be used
+// by factor creation schemes that do not care what type of
+// component is being created
+// **  after you've finished creating this component, be sure to
+// **  add it to the list of registered functions in the
+// **  CCompositeObjectManager constructor  
+
+CBaseComponent* CSkaterProximityComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterProximityComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// All components set their type, which is a unique 32-bit number
+// (the CRC of their name), which is used to identify the component	
+CSkaterProximityComponent::CSkaterProximityComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERPROXIMITY );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterProximityComponent::~CSkaterProximityComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// InitFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CSkaterProximityComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	// ** Add code to parse the structure, and initialize the component
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RefreshFromStructure is passed a Script::CStruct that contains a
+// number of parameters to initialize this component
+// this currently is the contents of a node
+// but you can pass in anything you like.	
+void CSkaterProximityComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	// Default to just calline InitFromStructure()
+	// but if that does not handle it, then will need to write a specific 
+	// function here. 
+	// The user might only want to update a single field in the structure
+	// and we don't want to be asserting becasue everything is missing 
+	
+	InitFromStructure(pParams);
+
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The component's Update() function is called from the CCompositeObject's 
+// Update() function.  That is called every game frame by the CCompositeObjectManager
+// from the s_logic_code function that the CCompositeObjectManager registers
+// with the task manger.
+void CSkaterProximityComponent::Update()
+{
+	// GJ TODO:  maybe this should be made more generic
+	// so that it takes a CCompositeObject*	(it would
+	// make replacing the skater w/ a different player
+	// object easier)
+
+	// check skater dist exceptions...
+	float distToSkaterSqr = 0.0f;
+	
+	if ( mInnerRadiusSqr )
+	{
+		distToSkaterSqr = GetDistToLocalSkaterSquared();
+		//distToSkaterSqr = GetDistToNearestSkaterSquared( );
+		if ( distToSkaterSqr <= mInnerRadiusSqr )
+		{
+			// If the local skater is in radius, then flag "SkaterInRadius"
+			FLAGEXCEPTION( CRCD(0xdb7413fb,"SkaterInRadius") );
+			if (!GetObject()->IsDead())
+			{
+				FLAGEXCEPTION( CRCD(0x5e8eb123,"AnySkaterInRadius") );
+			}
+		}
+		else
+		{
+			distToSkaterSqr = GetDistToNearestSkaterSquared();	// MIGHT BE A NETWORK SKATER
+			if ( distToSkaterSqr <= mInnerRadiusSqr )
+			{
+				FLAGEXCEPTION( CRCD(0x5e8eb123,"AnySkaterInRadius") );
+			}
+			distToSkaterSqr = 0.0f;		// Mick:  need to clear it, otherwise this value will be used for the
+										// AvoidRadius settings, which only apply to local skaters
+		}
+	}
+	
+	if ( mOuterRadiusSqr )
+	{
+		if ( distToSkaterSqr == 0.0f )
+		{
+			distToSkaterSqr = GetDistToLocalSkaterSquared();
+		}
+		if ( distToSkaterSqr >= mOuterRadiusSqr )
+		{
+			if (!GetObject()->IsDead())
+			{
+				FLAGEXCEPTION( CRCD(0xa41f5336,"SkaterOutOfRadius") ); 
+			}
+		}
+	}
+	
+	if ( mInnerAvoidRadiusSqr )
+	{
+		if ( distToSkaterSqr == 0.0f )
+		{
+			distToSkaterSqr = GetDistToLocalSkaterSquared();
+		}
+		if ( distToSkaterSqr <= mInnerAvoidRadiusSqr )
+		{
+			if (!GetObject()->IsDead())
+			{
+				FLAGEXCEPTION( CRCD(0xfaeec40f,"SkaterInAvoidRadius") );
+			}
+		}
+	}
+	
+	if ( mOuterAvoidRadiusSqr )
+	{
+		if ( distToSkaterSqr == 0.0f )
+		{
+			distToSkaterSqr = GetDistToLocalSkaterSquared();
+		}
+		if ( distToSkaterSqr >= mOuterAvoidRadiusSqr )
+		{
+			if (!GetObject()->IsDead())
+			{
+				FLAGEXCEPTION( CRCD(0x9c5af1a0,"SkaterOutOfAvoidRadius") );
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the "Checksum" of a script command, then possibly handle it
+// if it's a command that this component will handle	
+CBaseComponent::EMemberFunctionResult CSkaterProximityComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | Obj_SetInnerRadius | 
+        // @uparm 1.0 | inner radius
+		case ( 0x82a0a873 ):	// Obj_SetInnerRadius
+			if ( pParams->GetFloat( NONAME, &mInnerRadiusSqr ) )
+			{
+				mInnerRadiusSqr *= FEET_TO_INCHES( 1.0f );
+				mInnerRadiusSqr *= mInnerRadiusSqr;
+			}
+			break;
+
+        // @script | Obj_SetOuterRadius | 
+        // @uparm 1.0 | outer radius
+		case ( 0x8cb5d0c0 ):	// Obj_SetOuterRadius
+			if ( pParams->GetFloat( NONAME, &mOuterRadiusSqr ) )
+			{
+				mOuterRadiusSqr *= FEET_TO_INCHES( 1.0f );
+				mOuterRadiusSqr *= mOuterRadiusSqr;
+			}
+			break;
+
+		// @script | Obj_SetInnerAvoidRadius | 
+		// @uparm 1.0 | inner radius
+		case 0x50f28d36: // Obj_SetInnerAvoidRadius
+			if ( pParams->GetFloat( NONAME, &mInnerAvoidRadiusSqr ) )
+			{
+				mInnerAvoidRadiusSqr *= FEET_TO_INCHES( 1.0f );
+				mInnerAvoidRadiusSqr *= mInnerAvoidRadiusSqr;
+			}
+			break;
+
+		// @script | Obj_SetOuterAvoidRadius | 
+		// @uparm 1.0 | outer radius
+		case 0x2d703b94: // Obj_SetOuterAvoidRadius
+			if ( pParams->GetFloat( NONAME, &mOuterAvoidRadiusSqr ) )
+			{
+				mOuterAvoidRadiusSqr *= FEET_TO_INCHES( 1.0f );
+				mOuterAvoidRadiusSqr *= mOuterAvoidRadiusSqr;
+			}
+			break;
+
+
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+	// the "default" case of the switch statement handles
+	// unrecognized functions;  if we make it down here,
+	// that means that the component already handled it
+	// somehow
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProximityComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterProximityComponent::GetDebugInfo"));
+
+	// Add any script components to the p_info structure,
+	// and they will be displayed in the script debugger (qdebug.exe)
+	// you will need to add the names to debugger_names.q, if they are not existing checksums
+
+	/*	Example:
+	p_info->AddInteger("m_never_suspend",m_never_suspend);
+	p_info->AddFloat("m_suspend_distance",m_suspend_distance);
+	*/
+	p_info->AddFloat("mInnerRadiusSqr",mInnerRadiusSqr);
+	p_info->AddFloat("mOuterRadiusSqr",mOuterRadiusSqr);
+	p_info->AddFloat("mInnerAvoidRadiusSqr",mInnerAvoidRadiusSqr);
+	p_info->AddFloat("mOuterAvoidRadiusSqr",mOuterAvoidRadiusSqr);
+
+	
+// we call the base component's GetDebugInfo, so we can add info from the common base component										 
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define HUGE_DISTANCE_SQUARED ( HUGE_DISTANCE * HUGE_DISTANCE )
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSkaterProximityComponent::GetDistToLocalSkaterSquared()
+{
+	CCompositeObject* pObj = Mdl::Skate::Instance()->GetLocalSkater();
+	
+	if ( pObj && ( pObj != GetObject() ))
+	{
+		return Mth::DistanceSqr( pObj->GetPos(), GetObject()->GetPos() );
+	}
+	
+	return ( HUGE_DISTANCE_SQUARED );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSkaterProximityComponent::GetDistToNearestSkaterSquared()
+{	
+	float nearest_distance = HUGE_DISTANCE_SQUARED;
+	
+	int num_skaters = Mdl::Skate::Instance()->GetNumSkaters();
+	
+	for( int i = 0; i < num_skaters; i++ )
+	{
+		CCompositeObject* pObj = Mdl::Skate::Instance()->GetSkater( i );
+		if ( pObj && ( pObj != GetObject() ))
+		{
+			float this_dist = Mth::DistanceSqr( pObj->GetPos(), GetObject()->GetPos() );
+			if( this_dist < nearest_distance )
+			{
+				nearest_distance = this_dist;
+			}
+		}
+	}
+	
+	return nearest_distance;
+}
+
+
+
+	
+}
diff --git a/Code/Sk/Components/SkaterProximityComponent.h b/Code/Sk/Components/SkaterProximityComponent.h
new file mode 100644
index 0000000..be97148
--- /dev/null
+++ b/Code/Sk/Components/SkaterProximityComponent.h
@@ -0,0 +1,75 @@
+//****************************************************************************
+//* MODULE:         Gel/Components
+//* FILENAME:       SkaterProximityComponent.h
+//* OWNER:          ???
+//* CREATION DATE:  ??/??/??
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERPROXIMITYCOMPONENT_H__
+#define __COMPONENTS_SKATERPROXIMITYCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+// Replace this with the CRCD of the component you are adding
+#define		CRC_SKATERPROXIMITY CRCD(0x27457739,"SkaterProximity")
+
+//  Standard accessor macros for getting the component either from within an object, or 
+//  given an object				 
+#define		GetSkaterProximityComponent() ((Obj::CSkaterProximityComponent*)GetComponent(CRC_SkaterProximity))
+#define		GetSkaterProximityComponentFromObject(pObj) ((Obj::CSkaterProximityComponent*)(pObj)->GetComponent(CRC_SkaterProximity))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkaterProximityComponent : public CBaseComponent
+{
+
+// Bitfield flags, so we don't trigger things twice
+enum	{
+	INNER=0x0001,
+	OUTER=0x0002,
+	INNERAVOID=0x0004,
+	OUTERAVOID=0x0008,
+};
+
+public:
+    CSkaterProximityComponent();
+    virtual ~CSkaterProximityComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	float					GetDistToLocalSkaterSquared();
+	float					GetDistToNearestSkaterSquared();
+
+protected:
+	float					mInnerRadiusSqr;
+	float					mOuterRadiusSqr;
+
+	float					mInnerAvoidRadiusSqr;
+	float					mOuterAvoidRadiusSqr;
+	
+	uint32					m_flags;
+	
+	
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterRotateComponent.cpp b/Code/Sk/Components/SkaterRotateComponent.cpp
new file mode 100644
index 0000000..4f4f7eb
--- /dev/null
+++ b/Code/Sk/Components/SkaterRotateComponent.cpp
@@ -0,0 +1,289 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterRotateComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/6/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterRotateComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterRotateComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterRotateComponent::CSkaterRotateComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERROTATE );
+	
+	mp_core_physics_component = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterRotateComponent::~CSkaterRotateComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRotateComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	for (int n = 3; n--; )
+	{
+		mp_rotations[n].active = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRotateComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRotateComponent::Finalize (   )
+{
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_core_physics_component);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRotateComponent::Update()
+{
+	Mth::Matrix rotation_matrix;
+	
+	for (int n = X; n < Z + 1; n++)
+	{
+		SRotation& rotation = mp_rotations[n];
+		
+		if (!rotation.active) continue;
+		
+		float amount = Tmr::FrameLength() * rotation.angle_step;
+		
+		rotation.angle_traversed += Mth::Abs(amount);
+		
+		if (rotation.angle_traversed >= rotation.angle)
+		{
+			rotation.active = false;
+			amount -= Mth::Sgn(amount) * (rotation.angle_traversed - rotation.angle);
+		}
+		
+		Mth::CreateRotateMatrix(rotation_matrix, n, amount);
+		
+		GetObject()->m_matrix = rotation_matrix * GetObject()->m_matrix;
+		mp_core_physics_component->m_lerping_display_matrix = rotation_matrix * mp_core_physics_component->m_lerping_display_matrix;;
+	}
+	
+	if (!mp_rotations[0].active && !mp_rotations[1].active && !mp_rotations[2].active)
+	{
+		Suspend(true);
+	}
+}		
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterRotateComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | Rotate | 
+        // @parmopt float | Duration | 0.0 | duration time (default is ms)
+        // @flag seconds | time in seconds
+        // @flag frames | time in frames
+        // @parmopt float | x | 0.0 | x rotation angle
+        // @parmopt float | y | 0.0 | y rotation angle
+        // @parmopt float | z | 0.0 | z rotation angle
+        // @parmopt name | Node | | Node to point to. Currently only supported if a duration is specified.
+		case CRCC(0xc876adf3, "Rotate"):
+		{
+			float duration;
+			if (pParams->GetFloat(CRCD(0x79a07f3f, "Duration"), &duration))
+			{
+				Dbg_MsgAssert(duration > 0.0f, ("Zero or negative Rotate Duration"));
+				
+				if (pParams->ContainsFlag(CRCD(0xd029f619, "Seconds")) || pParams->ContainsFlag(CRCD(0x49e0ee96, "Second")))
+				{
+					// Seconds is what we want, so nothing to do
+				}
+				else if (pParams->ContainsFlag(CRCD(0x19176c5, "Frames")) || pParams->ContainsFlag(CRCD(0x4a07c332, "Frame")))
+				{
+					// Convert from frames to seconds
+					duration /= 60.0f;
+				}
+				else
+				{
+					// Convert from milliseconds to seconds
+					duration /= 1000.0f;
+				}	
+
+				if (pParams->GetFloat(CRCD(0x7323e97c, "x"), &mp_rotations[X].angle))
+				{
+					mp_rotations[X].duration = duration;
+					mp_rotations[X].angle_step = Mth::DegToRad(mp_rotations[X].angle / mp_rotations[X].duration);
+					mp_rotations[X].angle = Mth::Abs(Mth::DegToRad(mp_rotations[X].angle));
+					mp_rotations[X].angle_traversed = 0.0f;
+					mp_rotations[X].active = true;
+				}	
+				if (pParams->GetFloat(CRCD(0x424d9ea, "y"), &mp_rotations[Y].angle))
+				{
+					mp_rotations[Y].duration = duration;
+					mp_rotations[Y].angle_step = Mth::DegToRad(mp_rotations[Y].angle / mp_rotations[Y].duration);
+					mp_rotations[Y].angle = Mth::Abs(Mth::DegToRad(mp_rotations[Y].angle));
+					mp_rotations[Y].angle_traversed = 0.0f;
+					mp_rotations[Y].active = true;
+				}	
+				if (pParams->GetFloat(CRCD(0x9d2d8850, "z"), &mp_rotations[Z].angle))
+				{
+					mp_rotations[Z].duration = duration;
+					mp_rotations[Z].angle_step = Mth::DegToRad(mp_rotations[Z].angle / mp_rotations[Z].duration);
+					mp_rotations[Z].angle = Mth::Abs(Mth::DegToRad(mp_rotations[Z].angle));
+					mp_rotations[Z].angle_traversed = 0.0f;
+					mp_rotations[Z].active = true;
+				}	
+				
+				// If a node is specified, turn to point to it instead.
+				// Added by Ken for use by Brad when making the skater turn around when he goes outside the level limits.
+				uint32 node_name = 0;
+				if (pParams->GetChecksum(CRCD(0x7a8017ba, "Node"), &node_name))
+				{
+					int node = SkateScript::FindNamedNode(node_name);
+					Mth::Vector node_pos;
+					SkateScript::GetPosition(node, &node_pos);
+				
+					mp_rotations[Y].angle = Mth::RadToDeg(Mth::GetAngle(GetObject()->m_matrix, node_pos - GetObject()->m_pos));
+					
+					mp_rotations[Y].duration = duration;
+					mp_rotations[Y].angle_step = Mth::DegToRad(mp_rotations[Y].angle / mp_rotations[Y].duration);
+					mp_rotations[Y].angle = Mth::Abs(Mth::DegToRad(mp_rotations[Y].angle));
+					mp_rotations[Y].angle_traversed = 0.0f;
+					mp_rotations[Y].active = true;
+				}
+				
+				Suspend(false);
+			} // END if duration specified
+			else
+			{
+				// rotate immediately
+				float angle;
+				if (pParams->GetFloat(CRCD(0x7323e97c, "x"), &angle))
+				{
+					GetObject()->m_matrix.RotateXLocal(Mth::DegToRad(angle));				
+					mp_core_physics_component->ResetLerpingMatrix();
+				}
+				else if (pParams->GetFloat(CRCD(0x424d9ea, "y"), &angle))
+				{
+					GetObject()->m_matrix.RotateYLocal(Mth::DegToRad(angle));				
+					mp_core_physics_component->ResetLerpingMatrix();
+				}
+				else if (pParams->GetFloat(CRCD(0x9d2d8850, "z"), &angle))
+				{
+					GetObject()->m_matrix.RotateZLocal(Mth::DegToRad(angle));
+					mp_core_physics_component->ResetLerpingMatrix();
+				}
+				else
+				{
+					// if no parameters are given, rotate 180 degrees about Y
+					GetObject()->m_matrix[Z].Negate();
+					GetObject()->m_matrix[X].Negate();
+					mp_core_physics_component->ResetLerpingMatrix();
+					
+					mp_core_physics_component->mRail_Backwards = !mp_core_physics_component->mRail_Backwards;
+				}
+			} // END else no Duration specified	
+			break;
+		}
+		
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRotateComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterRotateComponent::GetDebugInfo"));
+	
+	Script::CArray* p_rotations_array = new Script::CArray;
+	p_rotations_array->SetSizeAndType(3, ESYMBOLTYPE_STRUCTURE);
+	for (int n = 3; n--; )
+	{
+		SRotation& rotation = mp_rotations[n];
+		
+		Script::CStruct* p_rotation_struct = new Script::CStruct;
+		p_rotation_struct->AddInteger("active", rotation.active);
+		p_rotation_struct->AddFloat("angle", rotation.angle);
+		p_rotation_struct->AddFloat("duration", rotation.duration);
+		p_rotation_struct->AddFloat("angle_step", rotation.angle_step);
+		p_rotation_struct->AddFloat("angle_traversed", rotation.angle_traversed);
+		
+		p_rotations_array->SetStructure(n, p_rotation_struct);
+	}
+	p_info->AddArrayPointer("mp_rotations", p_rotations_array);
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRotateComponent::StopAllRotation (   )
+{
+	for (int n = 3; n--; )
+	{
+		mp_rotations[n].active = false;
+	}
+}
+  
+}
diff --git a/Code/Sk/Components/SkaterRotateComponent.h b/Code/Sk/Components/SkaterRotateComponent.h
new file mode 100644
index 0000000..36b93bb
--- /dev/null
+++ b/Code/Sk/Components/SkaterRotateComponent.h
@@ -0,0 +1,79 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterRotateComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/6/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERROTATECOMPONENT_H__
+#define __COMPONENTS_SKATERROTATECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERROTATE CRCD(0x52d61a0d, "SkaterRotate")
+
+#define		GetSkaterRotateComponent() ((Obj::CSkaterRotateComponent*)GetComponent(CRC_SKATERROTATE))
+#define		GetSkaterRotateComponentFromObject(pObj) ((Obj::CSkaterRotateComponent*)(pObj)->GetComponent(CRC_SKATERROTATE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CSkaterCorePhysicsComponent;
+
+class CSkaterRotateComponent : public CBaseComponent
+{
+	struct SRotation
+	{
+		float angle;
+		float duration;
+		float angle_step;
+		float angle_traversed;
+		bool active;
+	};
+	
+public:
+    CSkaterRotateComponent();
+    virtual ~CSkaterRotateComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	bool							IsApplyingRotation ( unsigned axis );
+	void							StopAllRotation (   );
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+private:
+	SRotation						mp_rotations[3];
+	
+	// peer components
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+};
+
+inline bool CSkaterRotateComponent::IsApplyingRotation ( unsigned axis )
+{
+	Dbg_Assert(axis < 3);
+	return mp_rotations[axis].active;
+}
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterRunTimerComponent.cpp b/Code/Sk/Components/SkaterRunTimerComponent.cpp
new file mode 100644
index 0000000..223f626
--- /dev/null
+++ b/Code/Sk/Components/SkaterRunTimerComponent.cpp
@@ -0,0 +1,215 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterRunTimerComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  7/17/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterRunTimerComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterRunTimerComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterRunTimerComponent::CSkaterRunTimerComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERRUNTIMER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterRunTimerComponent::~CSkaterRunTimerComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRunTimerComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_state = INACTIVE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRunTimerComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRunTimerComponent::Finalize()
+{
+	mp_walk_component = GetWalkComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_walk_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRunTimerComponent::Update()
+{
+	if (m_state == ACTIVE_RUNNING)
+	{
+		switch (mp_walk_component->GetState())
+		{
+			case CWalkComponent::WALKING_HANG:
+			case CWalkComponent::WALKING_LADDER:
+			case CWalkComponent::WALKING_ANIMWAIT:
+				m_timer -= Script::GetFloat(CRCD(0x92fb4d09, "Hang_Run_Timer_Speed_Adjustment")) * Tmr::FrameLength();
+				break;
+				
+			default:
+				m_timer -= Tmr::FrameLength();
+				break;
+		}
+		
+		if (m_timer < 0.0f)
+		{
+			set_state(ACTIVE_TIME_UP);
+			GetObject()->SelfEvent(CRCD(0x9e22a8e9, "RunTimerUp"));
+		}
+	}
+	else if (m_state == ACTIVE_TIME_UP)
+	{
+		GetObject()->SelfEvent(CRCD(0x9e22a8e9, "RunTimerUp"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterRunTimerComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | RunTimer_Pause | pauses the run timer
+		case CRCC(0x82445966, "RunTimer_Pause"):
+			pause();
+			break;
+		
+		// @script | RunTimer_UnPause | unpauses the run timer
+		case CRCC(0x956236b1, "RunTimer_UnPause"):
+			unpause();
+			break;
+			
+		// @script | RunTimer_GetFactorComplete | returns the time left of the run timer as a float between zero and one in FactorComplete
+		case CRCC(0xab04cbf9, "RunTimer_GetFactorComplete"):
+			pScript->GetParams()->AddFloat(CRCD(0x41f56511, "FactorComplete"), m_timer / GetSkater()->GetScriptedStat(CRCD(0xb84f532, "Physics_RunTimer_Duration")));
+			break;
+			
+		// @script | RunTimer_GetRunTimerControllerId | returns the id of the display controller script for the run timer in RunTimerControllerId
+		case CRCC(0x96d81d8e, "RunTimer_GetRunTimerControllerId"):
+			pScript->GetParams()->AddChecksum(CRCD(0x39778122, "RunTimerControllerId"), get_run_timer_controller_id());
+			break; 
+			
+		// @script | RunTimer_GetRunTimerId | returns the id of the display controller script for the run timer in RunTimerId
+		case CRCC(0x81aaa6dd, "RunTimer_GetRunTimerId"):
+			pScript->GetParams()->AddChecksum(CRCD(0x628f21c9, "RunTimerId"), CRCD(0x7d42821c, "the_run_timer") + GetSkater()->GetSkaterNumber());
+			break; 
+											  
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRunTimerComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterRunTimerComponent::GetDebugInfo"));
+	
+	static const uint32 states [   ] =
+	{
+		CRCD(0x742cf11f, "INACTIVE"),
+		CRCD(0x5392ca8e, "ACTIVE_RUNNING"),
+		CRCD(0x26776873, "ACTIVE_PAUSED"),
+		CRCD(0xdef417fa, "ACTIVE_TIME_UP")
+	};
+	p_info->AddChecksum(CRCD(0x109b9260, "m_state"), states[m_state]);
+	
+	p_info->AddFloat(CRCD(0xd9d89e81, "m_timer"), m_timer);
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRunTimerComponent::ComboEnded (   )
+{
+	if (Script::FindSpawnedScriptWithID(get_run_timer_controller_id()))
+	{
+		CTracker::Instance()->LaunchEvent(CRCD(0x813cc576, "HideRunTimer"), get_run_timer_controller_id(), GetObject()->GetID());
+	}
+	
+	set_state(INACTIVE);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterRunTimerComponent::unpause (   )
+{
+	if (m_state == INACTIVE) return;
+		
+	set_state(m_timer > 0.0f ? ACTIVE_RUNNING : ACTIVE_TIME_UP);
+	
+	if (m_unpause_count < vRT_NUM_TIME_CHUNKS)
+	{
+		float max_timer = (vRT_NUM_TIME_CHUNKS - m_unpause_count) * GetSkater()->GetScriptedStat(CRCD(0xb84f532, "Physics_RunTimer_Duration")) / vRT_NUM_TIME_CHUNKS;
+		m_timer = Mth::Min(m_timer, max_timer);
+	}
+	
+	m_unpause_count++;
+	
+	CTracker::Instance()->LaunchEvent(CRCD(0x47eec244, "ShowRunTimer"), get_run_timer_controller_id(), GetObject()->GetID());
+}
+
+}
diff --git a/Code/Sk/Components/SkaterRunTimerComponent.h b/Code/Sk/Components/SkaterRunTimerComponent.h
new file mode 100644
index 0000000..ad9bba7
--- /dev/null
+++ b/Code/Sk/Components/SkaterRunTimerComponent.h
@@ -0,0 +1,162 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterRunTimerComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  7/17/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERRUNTIMERCOMPONENT_H__
+#define __COMPONENTS_SKATERRUNTIMERCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERRUNTIMER CRCD(0x8bce7e66, "SkaterRunTimer")
+
+#define		GetSkaterRunTimerComponent() ((Obj::CSkaterRunTimerComponent*)GetComponent(CRC_SKATERRUNTIMER))
+#define		GetSkaterRunTimerComponentFromObject(pObj) ((Obj::CSkaterRunTimerComponent*)(pObj)->GetComponent(CRC_SKATERRUNTIMER))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	class CWalkComponent;
+
+class CSkaterRunTimerComponent : public CBaseComponent
+{
+	enum EState
+	{
+		INACTIVE,
+		ACTIVE_RUNNING,
+		ACTIVE_PAUSED,
+		ACTIVE_TIME_UP
+	};
+	
+	// number of divisions of the timer; you always lose at least one chunk of timer per runout
+	enum { vRT_NUM_TIME_CHUNKS = 8 };
+	
+public:
+    CSkaterRunTimerComponent();
+    virtual ~CSkaterRunTimerComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    virtual void            		Finalize();
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+	void							ComboStarted (   );
+	void							ComboEnded (   );
+
+	static CBaseComponent*			s_create();
+	
+private:
+	void							update_timer (   );
+	void							pause (   );
+	void							unpause (   );
+	bool							is_timer_up (   );
+	void							set_state ( EState state );
+	uint32							get_run_timer_controller_id (   );
+	
+private:
+	EState							m_state;
+	
+	float							m_timer;
+	
+	short							m_unpause_count;
+	
+	CWalkComponent*					mp_walk_component;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterRunTimerComponent::set_state ( EState state )
+{
+	m_state = state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32 CSkaterRunTimerComponent::get_run_timer_controller_id (   )
+{
+	return CRCD(0x83321dea, "RunTimerController") + GetSkater()->GetSkaterNumber();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterRunTimerComponent::ComboStarted (   )
+{
+	m_timer = GetSkater()->GetScriptedStat(CRCD(0xb84f532, "Physics_RunTimer_Duration"));
+	m_unpause_count = 0;
+	
+	set_state(ACTIVE_PAUSED);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterRunTimerComponent::update_timer (   )
+{
+	m_timer -= Tmr::FrameLength();
+	
+	if (m_timer < 0.0f)
+	{
+		set_state(ACTIVE_TIME_UP);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterRunTimerComponent::pause (   )
+{
+	if (m_state == INACTIVE) return;
+	
+	set_state(ACTIVE_PAUSED);
+	
+	CTracker::Instance()->LaunchEvent(CRCD(0x813cc576, "HideRunTimer"), get_run_timer_controller_id(), GetObject()->GetID());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline bool CSkaterRunTimerComponent::is_timer_up (   )
+{
+	Dbg_Assert(m_state != INACTIVE);
+	
+	return m_state == ACTIVE_TIME_UP;
+}
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterScoreComponent.cpp b/Code/Sk/Components/SkaterScoreComponent.cpp
new file mode 100644
index 0000000..c1bbe6b
--- /dev/null
+++ b/Code/Sk/Components/SkaterScoreComponent.cpp
@@ -0,0 +1,290 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterScoreComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/12/3
+//****************************************************************************
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+extern bool g_CheatsEnabled;
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterScoreComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterScoreComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterScoreComponent::CSkaterScoreComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERSCORE );
+	mp_score = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterScoreComponent::~CSkaterScoreComponent()
+{
+	if (mp_score)
+	{
+		delete mp_score;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterScoreComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	Dbg_MsgAssert(GetObject()->GetType() == SKATE_TYPE_SKATER, ("CSkaterScoreComponent added to non-skater composite object"));
+	
+	if (!mp_score)
+	{
+		mp_score = new Mdl::Score();
+		mp_score->SetSkaterId(GetObject()->GetID());
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterScoreComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterScoreComponent::Update()
+{
+	// NOTE: in fact, non-local skaters may not need a score component at all
+	if (!GetSkater()->IsLocalClient())
+	{
+		Suspend(true);
+		return;
+	}
+	
+	mp_score->Update();
+	
+	if (GetSkater()->m_always_special || Mdl::Skate::Instance()->GetCareer()->GetCheat(CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL")))
+	{
+		// Here, set the flag. It may seem redundant, but the above line is very likely
+		// to be hacked by gameshark. They probably won't notice this one, which will
+		// set the flags as if they had actually enabled the cheat -- which enables us
+		// to detect that it has been turned on more easily.
+		Mdl::Skate::Instance()->GetCareer()->SetGlobalFlag( Script::GetInteger(CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL")));
+		mp_score->ForceSpecial();
+		g_CheatsEnabled = true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterScoreComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		// @script | LastScoreLandedGreaterThan | Multiplayer-safe version 
+		// of SkaterLastScoreLandedGreaterThan
+		// Example: if LastScoreLandedGreaterThan 2000 --do cool stuff-- endif
+		// @uparm 1 | Score (int)
+		case CRCC(0xe0112aab, "LastScoreLandedGreaterThan"):
+		{
+			int score;
+			pParams->GetInteger(NO_NAME, &score, Script::ASSERT);
+			return mp_score->GetLastScoreLanded() > score ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		
+		// @script | LastScoreLandedLessThan | Multiplayer-safe version 
+		// of SkaterLastScoreLandedLessThan
+		// Example: if LastScoreLandedLessThan 2000 --do cool stuff-- endif
+		// @uparm 1 | Score (int)
+		case CRCC(0x3c445a82, "LastScoreLandedLessThan"):
+		{
+			int score;
+			pParams->GetInteger(NO_NAME, &score, Script::ASSERT);
+			return mp_score->GetLastScoreLanded() < score ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		
+		// @script | TotalScoreGreaterThan | Multiplayer-safe version 
+		// of SkaterTotalScoreGreaterThan
+		// Example: if TotalScoreGreaterThan 2000 --do cool stuff-- endif
+		// @uparm 1 | Score (int)
+		case CRCC(0x8bf43831, "TotalScoreGreaterThan"):
+		{
+			int score;
+			pParams->GetInteger(NO_NAME, &score, Script::ASSERT);
+			return mp_score->GetTotalScore() > score ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+			
+		// @script | TotalScoreLessThan | Multiplayer-safe version 
+		// of SkaterTotalScoreLessThan
+		// Example: if TotalScoreLessThan 2000 --do cool stuff-- endif
+		// @uparm 1 | Score (int)
+		case CRCC(0xdf14bd98, "TotalScoreLessThan"):
+		{
+			int score;
+			pParams->GetInteger(NO_NAME, &score, Script::ASSERT);
+			return mp_score->GetTotalScore() < score ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+			
+		// @script | CurrentScorePotGreaterThan | Multiplayer-safe version 
+		// of SkaterCurrentScorePotGreaterThan
+		// Example: if CurrentScorePotGreaterThan 2000 --do cool stuff-- endif
+		// @uparm 1 | Score (int)
+		case CRCC(0xd19bdb50, "CurrentScorePotGreaterThan"):
+		{
+			int score;
+			pParams->GetInteger(NO_NAME, &score, Script::ASSERT);
+			return mp_score->GetScorePotValue() > score ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+
+		// @script | GetTotalScore | returns the total score of the skater
+		// in TotalScore
+		case CRCC( 0x8f2fe3d3, "GetTotalScore" ):
+		{
+			pScript->GetParams()->AddInteger( CRCD( 0xee5b2b48, "TotalScore" ), mp_score->GetTotalScore() );
+			return CBaseComponent::MF_TRUE;
+		}
+			
+		// @script | CurrentScorePotLessThan | Multiplayer-safe version 
+		// of SkaterCurrentScorePotLessThan
+		// Example: if CurrentScorePotLessThan 2000 --do cool stuff-- endif
+		// @uparm 1 | Score (int)
+		case CRCC(0x56645fc6, "CurrentScorePotLessThan"):
+		{
+			int score;
+			pParams->GetInteger(NO_NAME, &score, Script::ASSERT);
+			return mp_score->GetScorePotValue() < score ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+		}
+		
+		// @script | GetNumberOfNonGapTricks | returns number of non-gap tricks currently in the combo
+		case CRCC(0xc068ea59, "GetNumberOfNonGapTricks"):
+			pScript->GetParams()->AddInteger(CRCD(0xffe7e02c, "NumberOfNonGapTricks"), mp_score->GetNumberOfNonGapTricks());
+			break;
+		
+        // @script | GotSpecial | true if special is active
+		case CRCC(0x5589b902, "GotSpecial"):	
+			return mp_score->GetSpecialState() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+        
+		// @script | TweakTrick | 
+        // @uparm 0 | tweak
+		case CRCC(0xbe3e5463, "TweakTrick"):
+		{
+			int Tweak = 0;
+			pParams->GetInteger(NO_NAME, &Tweak);
+			
+			mp_score->TweakTrick(Tweak);
+			break;
+		}
+			
+		// @script | IsLatestTrick | 
+		case CRCC(0x2877b61a, "IsLatestTrick"):
+		{
+			uint32 key_combo;
+			if (pParams->GetChecksum(CRCD(0x95e16467, "KeyCombo"), &key_combo))
+			{
+				int spin = 0;
+				if (pParams->GetInteger(CRCD(0xedf5db70, "Spin"), &spin))
+				{
+					Dbg_MsgAssert(spin % 180 == 0, ("IsLatestTrick called with a spin value of %i which is not a multiple of 180", spin));
+					spin /= 180;
+				}
+				
+				int num_taps = 1;
+				pParams->GetInteger(CRCD(0xc82cf71b, "NumTaps"), &num_taps);
+				
+				return mp_score->IsLatestTrick(key_combo, spin, false, num_taps) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			}
+			else
+			{
+				const char* trick_text;
+				if (pParams->GetString(CRCD(0x3eafa520, "TrickText"), &trick_text))
+				{
+					int spin = 0;
+					if (pParams->GetInteger(CRCD(0xedf5db70, "Spin"), &spin))
+					{
+						Dbg_MsgAssert(spin % 180 == 0, ("IsLatestTrick called with a spin value of %i which is not a multiple of 180", spin));
+						spin /= 180;
+					}
+					
+					int num_taps = 1;
+					pParams->GetInteger(CRCD(0xc82cf71b, "NumTaps"), &num_taps);
+					
+					return mp_score->IsLatestTrickByName(Script::GenerateCRC(trick_text), spin, false, num_taps) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+				}
+				else
+				{
+					Dbg_MsgAssert(false, ("IsLatestTrick must have either a KeyCombo or a TrickText parameter"));
+				}
+			}
+		}
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterScoreComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterScoreComponent::GetDebugInfo"));
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterScoreComponent::Reset (   )
+{
+	mp_score->Reset();
+
+	mp_score->SetBalanceMeter(false);
+	mp_score->SetManualMeter(false);
+}
+
+}
diff --git a/Code/Sk/Components/SkaterScoreComponent.h b/Code/Sk/Components/SkaterScoreComponent.h
new file mode 100644
index 0000000..cad9ac9
--- /dev/null
+++ b/Code/Sk/Components/SkaterScoreComponent.h
@@ -0,0 +1,59 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterScoreComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/12/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERSCORECOMPONENT_H__
+#define __COMPONENTS_SKATERSCORECOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERSCORE CRCD(0xd262802f, "SkaterScore")
+
+#define		GetSkaterScoreComponent() ((Obj::CSkaterScoreComponent*)GetComponent(CRC_SKATERSCORE))
+#define		GetSkaterScoreComponentFromObject(pObj) ((Obj::CSkaterScoreComponent*)(pObj)->GetComponent(CRC_SKATERSCORE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkaterScoreComponent : public CBaseComponent
+{
+public:
+    CSkaterScoreComponent();
+    virtual ~CSkaterScoreComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	Mdl::Score*						GetScore() { return mp_score; }
+	
+	void							Reset (   );
+	
+private:
+	Mdl::Score*						mp_score;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterSoundComponent.cpp b/Code/Sk/Components/SkaterSoundComponent.cpp
new file mode 100644
index 0000000..a5ca7dd
--- /dev/null
+++ b/Code/Sk/Components/SkaterSoundComponent.cpp
@@ -0,0 +1,350 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterSoundComponent.cpp
+//* OWNER:			Dan
+//* CREATION DATE:  2/27/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent* CSkaterSoundComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterSoundComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterSoundComponent::CSkaterSoundComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERSOUND );
+	
+	m_is_rail_sliding = false;
+	m_max_speed = 1.0f;
+	m_vol_mult = 1.0f;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterSoundComponent::~CSkaterSoundComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	float vol_mult;
+	if (pParams->GetFloat( CRCD(0xf1a99b27,"volume_mult"), &vol_mult))
+	{
+		SetVolumeMultiplier(vol_mult);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::Update()
+{
+	// As a minor optimization, CSkaterSoundComponent is always suspended.
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterSoundComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | PlayJumpSound |
+		case CRCC(0xe338369, "PlayJumpSound"):
+		{
+			switch ( m_stateType )
+			{
+				case GROUND:
+				case AIR:
+					PlayJumpSound(GetObject()->GetVel().Length() / m_max_speed);
+					break;
+					 
+				case RAIL:
+					PlayJumpSound(GetObject()->GetVel().Length() / m_max_speed);
+				
+				default:
+					break;
+			}
+			break;
+		}
+		
+        // @script | PlayLandSound | 
+		case CRCC(0x2c779f22, "PlayLandSound"):
+		{
+			switch ( m_stateType )
+			{
+				case GROUND:
+				case AIR:
+					PlayLandSound(Mth::Abs(GetObject()->GetVel()[Y]) / m_max_speed);
+					break;
+					
+				case RAIL:
+					PlayLandSound(GetObject()->GetVel().Length() / m_max_speed);
+					break;
+					
+				default:
+					break;
+			}
+			break;
+		}
+		
+		// @script | PlayBonkSound |
+		case CRCC(0x0069e457, "PlayBonkSound"):
+		{
+			PlayBonkSound(GetObject()->GetVel().Length() / m_max_speed);
+			break;
+		}
+		
+        // @script | PlayCessSound | 
+		case CRCC(0x6f5e9124, "PlayCessSound"):
+		{
+			PlayCessSound(GetObject()->GetVel().Length() / m_max_speed);
+			break;
+		}
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterSoundComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayLandSound ( float speed_fraction )
+{
+	ETerrainType terrain;
+	if ( m_stateType == GROUND || m_stateType == AIR )
+	{
+		terrain = m_lastTerrain;
+	}
+	else
+	{
+		terrain = m_terrain;
+	}
+	
+	PlayLandSound(speed_fraction, terrain);
+}
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayLandSound ( float speed_fraction, ETerrainType terrain )
+{
+	Env::ETerrainActionType table;
+	if ( m_stateType == RAIL )
+	{
+		table = m_is_rail_sliding ? Env::vTABLE_SLIDELAND : Env::vTABLE_GRINDLAND;
+	}
+	else
+	{
+		table = Env::vTABLE_LAND;
+	}
+	
+	speed_fraction *= 100.0f;
+	
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0xb9ba2d27, "debug_skater_triggered_sounds")))
+	{
+		Dbg_Message("Playing sound [land]:   %.1f", speed_fraction);
+	}
+	#endif
+	
+	Env::CTerrainManager::sPlaySound(table, terrain, GetObject()->GetPos(), speed_fraction * m_vol_mult, speed_fraction, speed_fraction);
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayJumpSound ( float speed_fraction )
+{
+	ETerrainType terrain;
+	if ( m_stateType == GROUND || m_stateType == AIR )
+	{
+		terrain = m_lastTerrain;
+	}
+	else
+	{
+		terrain = m_terrain;
+	}
+	
+	PlayJumpSound(speed_fraction, terrain);
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayJumpSound ( float speed_fraction, ETerrainType terrain )
+{
+	PlayJumpSound(speed_fraction, terrain, m_stateType);
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayJumpSound ( float speed_fraction, ETerrainType terrain, EStateType stateType )
+{
+	Env::ETerrainActionType table;
+	if ( stateType == RAIL )
+	{
+		table = m_is_rail_sliding ? Env::vTABLE_SLIDEJUMP : Env::vTABLE_GRINDJUMP; 
+	}
+	else
+	{
+		table = Env::vTABLE_JUMP;
+	}
+	
+	speed_fraction *= 100.0f;
+	
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0xb9ba2d27, "debug_skater_triggered_sounds")))
+	{
+		Dbg_Message("Playing sound [ollie]:  %.1f", speed_fraction);
+	}
+	#endif
+
+	Env::CTerrainManager::sPlaySound(table, terrain, GetObject()->GetPos(), speed_fraction * m_vol_mult, speed_fraction, speed_fraction);
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayBonkSound ( float speed_fraction )
+{
+	PlayBonkSound(speed_fraction, m_terrain);
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayBonkSound ( float speed_fraction, ETerrainType terrain )
+{
+	speed_fraction *= 100.0f;
+	
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0xb9ba2d27, "debug_skater_triggered_sounds")))
+	{
+		Dbg_Message("Playing sound [bonk]:   %.1f", speed_fraction);
+	}
+	#endif
+
+	Env::CTerrainManager::sPlaySound(Env::vTABLE_BONK, terrain, GetObject()->GetPos(), speed_fraction * m_vol_mult, speed_fraction, speed_fraction);
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayCessSound ( float speed_fraction )
+{
+	PlayCessSound(speed_fraction, m_terrain);
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::PlayCessSound ( float speed_fraction, ETerrainType terrain )
+{
+	speed_fraction *= 100.0f;
+	
+	#ifdef __NOPT_ASSERT__
+	if (Script::GetInteger(CRCD(0xb9ba2d27, "debug_skater_triggered_sounds")))
+	{
+		Dbg_Message("Playing sound [revert]: %.1f", speed_fraction);
+	}
+	#endif
+
+	Env::CTerrainManager::sPlaySound(Env::vTABLE_CESS, terrain, GetObject()->GetPos(), speed_fraction * m_vol_mult, speed_fraction, speed_fraction);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterSoundComponent::SetVolumeMultiplier( float mult )
+{
+	Dbg_MsgAssert( mult >= 0.0f && mult <= 1.0f, ( "SetVolumeMultiplier called with bad mult value: %f", mult ) );
+	m_vol_mult = mult;
+}
+
+}
diff --git a/Code/Sk/Components/SkaterSoundComponent.h b/Code/Sk/Components/SkaterSoundComponent.h
new file mode 100644
index 0000000..b67c823
--- /dev/null
+++ b/Code/Sk/Components/SkaterSoundComponent.h
@@ -0,0 +1,83 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterSoundComponent.cpp
+//* OWNER:			Dan
+//* CREATION DATE:  2/27/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERSOUNDCOMPONENT_H__
+#define __COMPONENTS_SKATERSOUNDCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERSOUND CRCD(0x187574fa, "SkaterSound")
+
+#define		GetSkaterSoundComponent() ((Obj::CSkaterSoundComponent*)GetComponent(CRC_SKATERSOUND))
+#define		GetSkaterSoundComponentFromObject(pObj) ((Obj::CSkaterSoundComponent*)(pObj)->GetComponent(CRC_SKATERSOUND))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkaterSoundComponent : public CBaseComponent
+{
+public:
+    CSkaterSoundComponent();
+    virtual ~CSkaterSoundComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	void							PlayLandSound ( float speed_fraction );
+	void							PlayJumpSound ( float speed_fraction );
+	void							PlayBonkSound ( float speed_fraction );
+	void							PlayCessSound ( float speed_fraction );
+	
+	void							PlayLandSound ( float speed_fraction, ETerrainType terrain );
+	void							PlayJumpSound ( float speed_fraction, ETerrainType terrain );
+	void							PlayBonkSound ( float speed_fraction, ETerrainType terrain );
+	void							PlayCessSound ( float speed_fraction, ETerrainType terrain );
+	
+	void							PlayJumpSound ( float speed_fraction, ETerrainType terrain, EStateType stateType );
+	
+	void							SetTerrain ( ETerrainType terrain ) { m_terrain = terrain; }
+	void							SetLastTerrain ( ETerrainType terrain ) { m_lastTerrain = terrain; }
+	void							SetState ( EStateType state ) { m_stateType = state; }
+	void							SetIsRailSliding ( bool is_sliding ) { m_is_rail_sliding = is_sliding; }
+	
+	void							SetMaxSpeed ( float max_speed ) { m_max_speed = max_speed; }
+	
+	void							SetVolumeMultiplier ( float mult );
+private:
+	EStateType						m_stateType;
+	ETerrainType					m_terrain;
+	ETerrainType					m_lastTerrain;
+
+	bool							m_is_rail_sliding;
+	
+	float							m_max_speed;
+	float							m_vol_mult;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterStancePanelComponent.cpp b/Code/Sk/Components/SkaterStancePanelComponent.cpp
new file mode 100644
index 0000000..5f3d223
--- /dev/null
+++ b/Code/Sk/Components/SkaterStancePanelComponent.cpp
@@ -0,0 +1,204 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterStancePanelComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  2/25/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Obj
+{
+	
+// Component giving script control through a composite object over the input pad vibrators of the composite object's input handler.
+	
+// Only composite objects corresponding to local clients should be given this component.
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterStancePanelComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterStancePanelComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterStancePanelComponent::CSkaterStancePanelComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERSTANCEPANEL );
+	
+	mp_core_physics_component = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterStancePanelComponent::~CSkaterStancePanelComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStancePanelComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_last_stance = -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStancePanelComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStancePanelComponent::Finalize (   )
+{
+	mp_core_physics_component = GetSkaterCorePhysicsComponentFromObject(GetObject());
+	
+	Dbg_Assert(mp_core_physics_component);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStancePanelComponent::Update()
+{
+	Front::CSpriteElement* p_stance_icon = static_cast< Front::CSpriteElement* >(Front::CScreenElementManager::Instance()->GetElement(
+		CRCD(0x968ae5dd, "the_boardstance_sprite") + static_cast< CSkater* >(GetObject())->GetHeapIndex()
+	).Convert());
+	
+	Dbg_Assert(p_stance_icon);
+	
+	int stance = determine_stance();
+	
+	if (stance == m_last_stance) return;
+	m_last_stance = stance;
+	
+	if (stance == 5)
+	{
+		p_stance_icon->SetAlpha(0.0f, Front::CScreenElement::FORCE_INSTANT);
+	}
+	else
+	{
+		const static uint32 sp_texture_checksums[] =
+		{
+            CRCD(0x33a15296, "nollie_icon"),
+            CRCD(0xbe91f3b6, "fakie_icon"),
+			CRCD(0x9f9d3907, "switch_icon"),
+            CRCD(0xb793ef40, "sw_pressure_icon"),
+            CRCD(0x4e304fa1, "pressure_icon")
+		};
+		
+		p_stance_icon->SetTexture(sp_texture_checksums[stance]);
+		p_stance_icon->SetAlpha(1.0f, Front::CScreenElement::FORCE_INSTANT);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterStancePanelComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | InNollie | true if in nollie
+		case CRCC(0x1eb61dce, "InNollie"):
+			return m_in_nollie ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+
+        // @script | NollieOn | sets nollie to on (check with InNollie)
+		case CRCC(0x41eb710b, "NollieOn"):
+			if (m_in_nollie == false)
+			{
+				m_in_nollie = true;
+				GetObject()->BroadcastEvent(CRCD(0x8157ab31, "SkaterEnterNollie"));
+			}
+			break;
+
+        // @script | NollieOff | sets nollie to off (check with InNollie)
+		case CRCC(0xfb9b7c9c, "NollieOff"):
+			if (m_in_nollie == true)
+			{
+				m_in_nollie = false;
+				GetObject()->BroadcastEvent(CRCD(0x3f70881a, "SkaterExitNollie"));
+			}
+			break;
+
+        // @script | InPressure | true if in pressure
+        case CRCC(0x9fab9d0b,"InPressure"):
+			return m_in_pressure ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+
+        // @script | PressureOn | sets pressure to on (check with InPressure)
+		case CRCC(0xa23d710,"PressureOn"):
+			if (m_in_pressure == false)
+			{
+				m_in_pressure = true;
+				GetObject()->BroadcastEvent(CRCD(0x2e8de921,"SkaterEnterPressure"));
+			}
+			break;
+
+        // @script | PressureOff | sets pressure to off (check with InPressure)
+		case CRCC(0x71b57dd6,"PressureOff"):
+			if (m_in_pressure == true)
+			{
+				m_in_pressure = false;
+				GetObject()->BroadcastEvent(CRCD(0xfa9adb1d,"SkaterExitPressure"));
+			}
+			break;
+			
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+	
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStancePanelComponent::GetDebugInfo ( Script::CStruct *p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterStancePanelComponent::GetDebugInfo"));
+	
+	p_info->AddInteger("stance", determine_stance());
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+}
diff --git a/Code/Sk/Components/SkaterStancePanelComponent.h b/Code/Sk/Components/SkaterStancePanelComponent.h
new file mode 100644
index 0000000..7b1e93d
--- /dev/null
+++ b/Code/Sk/Components/SkaterStancePanelComponent.h
@@ -0,0 +1,132 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterStancePanelComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  2/25/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_STANCEPANELCOMPONENT_H__
+#define __COMPONENTS_STANCEPANELCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERSTANCEPANEL CRCD(0x21fc3301, "SkaterStancePanel")
+
+#define		GetSkaterStancePanelComponent() ((Obj::CSkaterStancePanelComponent*)GetComponent(CRC_SKATERSTANCEPANEL))
+#define		GetSkaterStancePanelComponentFromObject(pObj) ((Obj::CSkaterStancePanelComponent*)(pObj)->GetComponent(CRC_SKATERSTANCEPANEL))
+														 
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+
+class CSkaterStancePanelComponent : public CBaseComponent
+{
+	
+public:
+    CSkaterStancePanelComponent();
+    virtual ~CSkaterStancePanelComponent();
+
+public:
+    virtual void            		Update (   );
+    virtual void            		InitFromStructure ( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure ( Script::CStruct* pParams );
+	virtual void					Finalize (   );
+    
+    virtual EMemberFunctionResult   CallMemberFunction ( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo ( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create (   );
+	
+	void							Reset (   );
+	
+private:
+	int								determine_stance (   );
+	
+private:
+	int								m_last_stance;
+	
+	bool							m_active;
+	
+	bool							m_in_nollie;					// Whether in Nollie stance or not.
+    bool							m_in_pressure;					// Whether in pressure stance or not.
+
+	// peer components
+	CSkaterCorePhysicsComponent*	mp_core_physics_component;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void CSkaterStancePanelComponent::Reset (   )
+{
+	m_last_stance = -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline int CSkaterStancePanelComponent::determine_stance (   )
+{
+	// regular
+    int stance = 5;
+	
+    // pressure
+    if (m_in_pressure)
+	{
+        // switch pressure
+		if (mp_core_physics_component->IsSwitched())
+    	{
+    		stance -= 2;
+    	}
+        else
+        {
+            stance -= 1;
+        }
+	}
+	else
+    {
+        // switch
+    	if (mp_core_physics_component->IsSwitched())
+    	{
+    		// fakie
+            if (m_in_nollie)
+        	{
+                stance -= 4;
+            }
+            else
+            {
+                stance -= 3;
+            }
+    	}
+        else
+        {
+            // nollie
+            if (m_in_nollie)
+        	{
+        		stance -= 5;
+        	}
+        }
+    }
+    
+    return stance;
+}
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterStateComponent.cpp b/Code/Sk/Components/SkaterStateComponent.cpp
new file mode 100644
index 0000000..a3e47a5
--- /dev/null
+++ b/Code/Sk/Components/SkaterStateComponent.cpp
@@ -0,0 +1,278 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterStateComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/31/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+/*
+ * Holds all skater state which is needed by both local and nonlocal clients.  This way, code external to the skater can access this information in a
+ * consistent manner, without having to know which components within the skater are controling the state.
+ *
+ * Currently, state within the core physics component has not been moved into theis component.
+ */
+ 
+namespace Obj
+{
+//											Fireball										
+static uint32 s_powerups[vNUM_POWERUPS] = { 0xd039432c };
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterStateComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterStateComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterStateComponent::CSkaterStateComponent() : CBaseComponent()
+{
+	int i;
+
+	SetType( CRC_SKATERSTATE );
+	for( i = 0; i < vNUM_POWERUPS; i++ )
+	{
+		m_powerups[i] = false;
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterStateComponent::~CSkaterStateComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateComponent::InitFromStructure( Script::CStruct* pParams )
+{
+	m_state = AIR;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateComponent::Update()
+{
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterStateComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+        // @script | DoingTrick | true if we're doing a trick
+		case 0x58ad903f: // DoingTrick
+			return DoingTrick() ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+			
+		case 0xb07ac662: // HasPowerup
+		{
+			uint32 type;
+
+			pParams->GetChecksum( NONAME, &type, true );
+			return HasPowerup( type ) ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
+			break;
+		}
+
+		case 0xe11b7ca:	// PickedUpPowerup
+		{
+			uint32 type;
+
+			pParams->GetChecksum( NONAME, &type, true );
+			PickedUpPowerup( type );
+			break;
+		}
+		
+		// @script | GetTerrain | returns the number of the terrain in 'terrain'
+		case CRCC(0x44ba5fce, "GetTerrain"):
+			pScript->GetParams()->AddInteger(CRCD(0x3789ac4e, "terrain"), m_terrain);
+			break;
+
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterStateComponent::GetDebugInfo"));
+	
+	const uint32 p_state_checksums [   ] =
+	{
+		CRCD(0x58007c97, "GROUND"),
+		CRCD(0x439f4704, "AIR"),
+		CRCD(0xec0a1009, "WALL"),
+		CRCD(0xa549b57b, "LIP"),
+		CRCD(0xa6a3147e, "RAIL"),
+		CRCD(0xcf74f6b7, "WALLPLANT")
+	};
+	
+	const uint32 p_flag_checksums [ NUM_ESKATERFLAGS ] =
+	{
+		CRCD(0x42f41014, "TENSE"),
+		CRCD(0x0c7a712c, "FLIPPED"),
+		CRCD(0xb39b4f1b, "VERT_AIR"),
+		CRCD(0xc6bdeafc, "TRACKING_VERT"),
+		CRCD(0x7747d16a, "LAST_POLY_WAS_VERT"),
+		CRCD(0x0b6c902c, "CAN_BREAK_VERT"),
+		CRCD(0x1261f6a0, "CAN_RERAIL"),
+		CRCD(0x2bdce1e1, "RAIL_SLIDING"),
+		CRCD(0xfb2e505c, "CAN_HIT_CAR"),
+		CRCD(0xb2791a2f, "AUTOTURN"),
+		CRCD(0x21523880, "IS_BAILING"),
+		CRCD(0xe8e7a9a1, "SPINE_PHYSICS"),
+		CRCD(0x4b45106a, "IN_RECOVERY"),
+		CRCD(0x9c6a7e41, "SKITCHING"),
+		CRCD(0x468c28b6, "OVERRIDE_CANCEL_GROUND"),
+		CRCD(0xa29e3a92, "SNAPPED_OVER_CURB"),
+		CRCD(0x4424288f, "SNAPPED"),
+		CRCD(0x0849fb13, "IN_ACID_DROP"),
+		CRCD(0x35f996fb, "AIR_ACID_DROP_DISALLOWED"),
+		CRCD(0xd1c9cb24, "CANCEL_WALL_PUSH"),
+		CRCD(0x260e0844, "NO_ORIENTATION_CONTROL"),
+		CRCD(0x524ea0a3, "NEW_RAIL")
+	};
+	
+	p_info->AddChecksum(CRCD(0x109b9260, "m_state"), p_state_checksums[m_state]);
+	for (int flag = 0; flag < NUM_ESKATERFLAGS; flag++)
+	{
+		p_info->AddChecksum(p_flag_checksums[flag], GetFlag(static_cast< ESkaterFlag >(flag)) ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
+	}
+	
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateComponent::Reset (   )
+{
+	SetDoingTrick(false);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static int	s_get_powerup_index( uint32 type )
+{
+	int i;
+
+	for( i = 0; i < vNUM_POWERUPS; i++ )
+	{
+		if( s_powerups[i] == type )
+		{
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterStateComponent::HasPowerup( uint32 type )
+{
+	int index;
+
+	index = s_get_powerup_index( type );
+	if( index == -1 )
+	{
+		return false;
+	}
+	
+	return m_powerups[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateComponent::ClearPowerups( void )
+{
+	int i;
+
+	for( i = 0; i < vNUM_POWERUPS; i++ )
+	{
+		m_powerups[i] = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateComponent::PickedUpPowerup( uint32 type )
+{
+	int index;
+
+	index = s_get_powerup_index( type );
+	if( index == -1 )
+	{
+		return;
+	}
+	
+	m_powerups[index] = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Sk/Components/SkaterStateComponent.h b/Code/Sk/Components/SkaterStateComponent.h
new file mode 100644
index 0000000..6539f60
--- /dev/null
+++ b/Code/Sk/Components/SkaterStateComponent.h
@@ -0,0 +1,122 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterStateComponent.h
+//* OWNER:          Dan
+//* CREATION DATE:  3/31/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERSTATECOMPONENT_H__
+#define __COMPONENTS_SKATERSTATECOMPONENT_H__
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#define		CRC_SKATERSTATE CRCD(0x43686585, "SkaterState")
+
+#define		GetSkaterStateComponent() ((Obj::CSkaterStateComponent*)GetComponent(CRC_SKATERSTATE))
+#define		GetSkaterStateComponentFromObject(pObj) ((Obj::CSkaterStateComponent*)(pObj)->GetComponent(CRC_SKATERSTATE))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+              
+namespace Obj
+{
+	enum
+	{
+		vFIREBALL,
+		vNUM_POWERUPS
+	};
+	
+	enum EPhysicsStateType
+	{
+		NO_STATE = -1,
+		SKATING,
+		WALKING,
+#		ifdef TESTING_GUNSLINGER
+		RIDING
+#		endif
+	};
+
+class CSkaterStateComponent : public CBaseComponent
+{
+	friend class CSkaterNonLocalNetLogicComponent;
+	friend class CSkaterLocalNetLogicComponent;
+	friend class CSkaterCorePhysicsComponent;
+	friend class CSkaterAdjustPhysicsComponent;
+	friend class CSkaterFinalizePhysicsComponent;
+	friend class CSkaterFlipAndRotateComponent;
+	friend class CSkaterPhysicsControlComponent;
+	friend class CWalkComponent;
+				  
+public:
+    CSkaterStateComponent();
+    virtual ~CSkaterStateComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+public:
+	void							Reset (   );
+	
+	bool							DoingTrick (   ) { return m_doing_trick; }
+	bool							HasPowerup( uint32 type );
+	void							ClearPowerups( void );
+	void							PickedUpPowerup( uint32 type );
+	void							SetDoingTrick ( bool doing_trick ) { m_doing_trick = doing_trick; }
+	EStateType						GetState (   ) { return m_state; }
+	bool							GetFlag ( ESkaterFlag flag ) { return m_skater_flags[flag].Get(); }
+	ETerrainType					GetTerrain (   ) { return m_terrain; }
+	Mth::Vector&					GetCameraDisplayNormal (   ) { return m_camera_display_normal; }
+	Mth::Vector&					GetCameraCurrentNormal (   ) { return m_camera_current_normal; }
+	Mth::Vector&					GetSpineVel (   ) { return m_spine_vel; }
+	bool							GetJumpedOutOfLipTrick (   ) { return mJumpedOutOfLipTrick; }
+	float							GetHeight (   ) { return m_height; }
+	bool							GetDriving (   ) { return m_driving; }
+	bool							GetPhysicsState (   ) { return m_physics_state; }
+	
+private:
+	EStateType						m_state;
+	
+	EPhysicsStateType				m_physics_state;
+
+	CTimestampedFlag				m_skater_flags [ NUM_ESKATERFLAGS ];
+	
+	bool							m_doing_trick;
+	
+	ETerrainType					m_terrain;						// current terrain type on which the skater is skating
+	
+	Mth::Vector						m_spine_vel;					// velocity to move over the spine
+	
+	bool							mJumpedOutOfLipTrick;			// Gets set when the player jumps out of a lip trick, and reset when they land.
+																	// Controlled by the c-code, not script.
+																	// Used by the camera.
+	
+	Mth::Vector						m_camera_display_normal;		// skater's up vector for the purposes of the camera
+	Mth::Vector						m_camera_current_normal;		// skater's up vector for the purposes of the camera
+	
+	bool							m_driving;						// true if the skater is driving a car
+	
+	float							m_height;
+	bool							m_powerups[vNUM_POWERUPS];
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Components/SkaterStateHistoryComponent.cpp b/Code/Sk/Components/SkaterStateHistoryComponent.cpp
new file mode 100644
index 0000000..866e136
--- /dev/null
+++ b/Code/Sk/Components/SkaterStateHistoryComponent.cpp
@@ -0,0 +1,837 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterStateHistoryComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/13/3
+//****************************************************************************
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CBaseComponent* CSkaterStateHistoryComponent::s_create()
+{
+	return static_cast< CBaseComponent* >( new CSkaterStateHistoryComponent );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterStateHistoryComponent::CSkaterStateHistoryComponent() : CBaseComponent()
+{
+	SetType( CRC_SKATERSTATEHISTORY );
+
+	m_last_anm_time = 0;
+	m_num_pos_updates = 0;
+	m_num_anim_updates = 0;
+	memset( mp_pos_history, 0, sizeof( SPosEvent ) * vNUM_POS_HISTORY_ELEMENTS );
+	memset( mp_anim_history, 0, sizeof( SAnimEvent ) * vNUM_ANIM_HISTORY_ELEMENTS );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CSkaterStateHistoryComponent::~CSkaterStateHistoryComponent()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateHistoryComponent::InitFromStructure( Script::CStruct* pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateHistoryComponent::RefreshFromStructure( Script::CStruct* pParams )
+{
+	InitFromStructure(pParams);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateHistoryComponent::Update()
+{
+	Suspend(true);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBaseComponent::EMemberFunctionResult CSkaterStateHistoryComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( Checksum )
+	{
+		default:
+			return CBaseComponent::MF_NOT_EXECUTED;
+	}
+    return CBaseComponent::MF_TRUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateHistoryComponent::GetDebugInfo(Script::CStruct *p_info)
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert(p_info,("NULL p_info sent to CSkaterStateHistoryComponent::GetDebugInfo"));
+
+	CBaseComponent::GetDebugInfo(p_info);	  
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterStateHistoryComponent::CheckForCrownCollision(   )
+{
+	Obj::CCrown* crown = GameNet::Manager::Instance()->GetCrown();
+	Dbg_Assert(crown && !crown->OnKing());
+	
+	return (get_latest_position() - crown->GetPosition()).Length() < CCrown::vCROWN_RADIUS;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterStateHistoryComponent::CollideWithOtherSkaters( int start_index )
+{
+    GameNet::PlayerInfo* p_player;
+
+	p_player = GameNet::Manager::Instance()->GetPlayerByObjectID(GetObject()->GetID());
+	if( p_player == NULL )
+	{
+		return;
+	}
+
+	// If we've marked you as non-collidable, you are exempt from collision
+	// In koth, if collision is turned off, only the king is collidable
+	if (p_player->IsNonCollidable()
+		|| (!GameNet::Manager::Instance()->PlayerCollisionEnabled() && !p_player->IsKing() && !p_player->HasCTFFlag())) return;
+	
+	CSkaterStateComponent* p_my_state_component = GetSkaterStateComponentFromObject(GetSkater());
+	Dbg_Assert(p_my_state_component);
+
+	// If this p_player is bailing or noncollidable (and he's NOT the king) he is exempt from being smacked down
+	// If you're in a slap game, you can always be hit; i.e. you've just been slapped but haven't teleported yet
+    bool can_fall = !p_my_state_component->GetFlag(IS_BAILING) || CFuncs::ScriptInSlapGame(NULL, NULL);
+	
+	// However, fallen kings are vulnerable
+	if (!can_fall && !p_player->IsKing() && !p_player->HasCTFFlag()) return;
+	
+	bool my_driving = p_my_state_component->GetDriving();
+	Mth::Vector my_pos = get_latest_position();
+	Mth::Vector my_vel = get_vel();
+	Mth::Vector my_dir = my_vel;
+	my_dir.Normalize();
+
+	Mth::Line my_line;
+	my_line.m_start = my_pos;
+
+	// Loop through all other skaters and check for collisions
+	for (int i = start_index; i < Mdl::Skate::vMAX_SKATERS; i++)
+	{
+		CSkater* p_other_skater = Mdl::Skate::Instance()->GetSkater(i);
+
+		if (!p_other_skater
+			|| p_other_skater == GetSkater()) continue;
+		
+		CSkaterStateComponent* p_other_state_component = GetSkaterStateComponentFromObject(p_other_skater);
+		Dbg_Assert(p_other_state_component);
+		if (p_other_state_component->GetFlag(IS_BAILING)) continue;
+		
+		GameNet::PlayerInfo* p_other_player = p_other_player = GameNet::Manager::Instance()->GetPlayerByObjectID(p_other_skater->GetID());
+		Dbg_Assert(p_other_player);
+		
+		// Non-Collidable people and kings should never win
+		if (p_other_player->IsNonCollidable() || p_other_player->IsKing()) continue;
+
+		// If both players are carrying flags, and the subject doesn't have the other guy's flag, don't let them collide
+		if (p_player->HasCTFFlag() && p_other_player->HasCTFFlag() && p_player->HasWhichFlag() != p_other_player->m_Team) continue;
+
+		// No smacking teammates
+		if (Mdl::Skate::Instance()->GetGameMode()->IsTeamGame() && p_other_player->m_Team == p_player->m_Team) continue;
+
+		bool other_driving = p_other_state_component->GetDriving();
+		Mth::Vector other_pos = p_other_skater->mp_skater_state_history_component->get_latest_position();
+		Mth::Vector other_vel = p_other_skater->mp_skater_state_history_component->get_vel();
+		Mth::Vector other_dir = other_vel;
+		other_dir.Normalize();
+		
+		if( other_vel.Length() > 100.0f )
+		{
+			continue;
+		}
+		
+		bool collided = false;
+		
+		// Collision extents are based upon the "other" skater -- whether or not he's a remote skater
+		
+		float collide_dist = get_collision_cylinder_radius(my_driving, other_driving);
+		float driving_radius_multiplier = 1.0f;
+		if (my_driving)
+		{
+			driving_radius_multiplier += Script::GetFloat("driving_radius_boost");
+		}
+		if (other_driving)
+		{
+			driving_radius_multiplier += Script::GetFloat("driving_radius_boost");
+		}
+		
+		my_line.m_end = my_line.m_start + my_dir * get_collision_cylinder_coeff(my_driving);
+		
+		Mth::Line other_line;
+		other_line.m_start = other_pos;
+		other_line.m_end = other_line.m_start + other_dir * p_other_skater->mp_skater_state_history_component->get_collision_cylinder_coeff(other_driving);
+		
+		float temp;
+		Mth::Vector my_pt, other_pt;
+		if (Mth::LineLineIntersect(my_line, other_line, &my_pt, &other_pt, &temp, &temp, true))
+		{
+			if ((my_pt - other_pt).Length() < collide_dist)
+			{
+				collided = true;
+			}   
+		}
+		else
+		{
+			// No solution exists -- lines must be parallel. Try testing endpoint lengths
+			// Note: This only really works if velocities are relatively small
+			if( (other_line.m_start - my_line.m_start).Length() < collide_dist)
+			{
+				collided = true;
+				other_pt = other_line.m_start;
+				my_pt = my_line.m_start;
+			}
+			else if ((other_line.m_start - my_line.m_end).Length() < collide_dist)
+			{
+				collided = true;
+				other_pt = other_line.m_start;
+				my_pt = my_line.m_end;
+			}
+			else if ((other_line.m_end - my_line.m_start).Length() < collide_dist)
+			{
+				collided = true;
+				other_pt = other_line.m_end;
+				my_pt = my_line.m_start;
+			}
+			else if ((other_line.m_end - my_line.m_end).Length() < collide_dist)
+			{
+				collided = true;
+				other_pt = other_line.m_end;
+				my_pt = my_line.m_end;
+			}
+		}
+		
+		if (!collided) continue;
+		
+		// If the "other" p_player is going faster or if
+		// the subject p_player is king or if
+		// the subject is skating and we're driving, the subject loses
+		if (((my_driving == other_driving && my_vel.Length() > other_vel.Length())
+			 || (my_driving && !other_driving)) && !p_player->IsKing()) continue;
+		
+		//Dbg_Printf( "**** My Vel: %.2f, Theirs: %.2f\n", my_vel.Length(), other_vel.Length() );
+		Net::Server* p_server = GameNet::Manager::Instance()->GetServer();
+		Dbg_Assert(p_server);
+			
+		if (can_fall)
+		{
+			GameNet::MsgCollideLost lost_msg;
+			GameNet::MsgByteInfo won_msg;
+			Net::MsgDesc msg_desc;
+
+			// basically a one-byte message explaining "You've been knocked down by ID"
+			lost_msg.m_Id = p_other_skater->GetID();
+			lost_msg.m_Id |= (1 << 7) * other_driving;
+			lost_msg.m_Offset = my_pt - other_pt;
+
+			if (CInputComponent* p_input_component = GetInputComponentFromObject(GetObject()))
+			{
+				p_input_component->DisableInput();
+			}
+			p_player->MarkAsNonCollidable();
+			
+			msg_desc.m_Id = GameNet::MSG_ID_SKATER_COLLIDE_LOST;
+			msg_desc.m_Data = &lost_msg;
+			msg_desc.m_Length = sizeof(GameNet::MsgCollideLost);
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+			p_server->EnqueueMessage(p_player->GetConnHandle(), &msg_desc);
+
+			// basically a one-byte message explaining "You knocked someone down"
+			won_msg.m_Data = GetObject()->GetID();
+			won_msg.m_Data |= (1 << 7) * my_driving;
+
+			msg_desc.m_Id = GameNet::MSG_ID_SKATER_COLLIDE_WON;
+			msg_desc.m_Data = &won_msg;
+			msg_desc.m_Length = sizeof(GameNet::MsgByteInfo);
+			p_server->EnqueueMessage(p_other_player->GetConnHandle(), &msg_desc);	
+			
+			if (CFuncs::ScriptInSlapGame(NULL, NULL))
+			{
+				Dbg_Assert(p_other_skater->mp_skater_score_component);
+				Mdl::Score* score = p_other_skater->mp_skater_score_component->GetScore();
+				score->SetTotalScore(score->GetTotalScore() + 1);
+			}
+		}
+		
+		if (Mdl::Skate::Instance()->GetGameMode()->GetNameChecksum() == CRCD(0x6ef8fda0, "netking")
+			|| Mdl::Skate::Instance()->GetGameMode()->GetNameChecksum() == CRCD(0x5d32129c, "king"))
+		{
+			// Don't bother switching kings if the game is over
+			if (Mdl::Skate::Instance()->GetGameMode()->EndConditionsMet()) return;
+			
+			// If the king was just slapped, the "slapper" (hehe) is the new king
+			if (!p_player->IsKing()) continue;
+			
+			// It is important that we mark the king immediately (rather than through a
+			// network message) since we do logic based on the "current" king
+			p_other_player->MarkAsKing(true);
+			
+			Lst::Search< GameNet::PlayerInfo > search;
+			for (GameNet::PlayerInfo* p_player = GameNet::Manager::Instance()->FirstPlayerInfo(search, true);
+				p_player;
+				p_player = GameNet::Manager::Instance()->NextPlayerInfo(search, true))
+			{
+				// Already marked the king for the local p_player (above)
+				if (p_player->IsLocalPlayer()) continue;
+				
+				GameNet::MsgByteInfo msg;
+				Net::MsgDesc msg_desc;
+				
+				msg.m_Data = p_other_skater->GetID();
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Id = GameNet::MSG_ID_NEW_KING;
+				msg_desc.m_Length = sizeof(GameNet::MsgByteInfo);
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+				p_server->EnqueueMessage(p_player->GetConnHandle(), &msg_desc);
+			}
+		}
+		else if (Mdl::Skate::Instance()->GetGameMode()->GetNameChecksum() == CRCD(0x6c5ff266, "netctf"))
+		{
+			GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+			// Don't bother handling this if the game is over
+			//if (Mdl::Skate::Instance()->GetGameMode()->EndConditionsMet())
+			if( gamenet_man->GameIsOver())
+			{
+				return;
+			}
+			
+			if (!p_player->HasCTFFlag())
+			{
+				continue;
+			}
+			
+			GameNet::MsgFlagMsg msg;
+			Net::MsgDesc msg_desc;
+
+			msg.m_ObjId = p_other_skater->GetID();
+			msg.m_Team = p_player->HasWhichFlag();
+
+			bool retrieve = p_player->HasWhichFlag() == p_other_player->m_Team;
+
+			Lst::Search< GameNet::PlayerInfo > search;
+			for (GameNet::PlayerInfo* send_player = GameNet::Manager::Instance()->FirstPlayerInfo(search, true);
+				send_player; 
+				send_player = GameNet::Manager::Instance()->NextPlayerInfo(search, true))
+			{
+				// If you knock down the p_player with your team's flag, you recover it. Otherwise, you steal it
+				if (retrieve)
+				{
+					if (send_player->IsLocalPlayer())
+					{
+						p_other_player->RetrievedFlag();
+						continue;
+					}
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = sizeof(GameNet::MsgFlagMsg);
+					msg_desc.m_Id = GameNet::MSG_ID_RETRIEVED_FLAG;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+					p_server->EnqueueMessage(send_player->GetConnHandle(), &msg_desc);
+				}
+				else
+				{
+					if (send_player->IsLocalPlayer())
+					{
+						p_other_player->StoleFlag(p_player->HasWhichFlag());
+						continue;
+					}
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = sizeof(GameNet::MsgFlagMsg);
+					msg_desc.m_Id = GameNet::MSG_ID_STOLE_FLAG;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+					p_server->EnqueueMessage(send_player->GetConnHandle(), &msg_desc);
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			CSkaterStateHistoryComponent::sHandleProjectileHit ( Net::MsgHandlerContext* context )
+{
+	CSkater* p_skater = (CSkater*) (context->m_Data);
+	GameNet::MsgProjectileHit* p_msg = (GameNet::MsgProjectileHit*) (context->m_Msg);
+	
+	switch (context->m_MsgId)
+	{
+		case GameNet::MSG_ID_SKATER_PROJECTILE_HIT_TARGET:
+		{
+			char name[32] = "Someone";
+			
+			if (GameNet::PlayerInfo* p_other_player = GameNet::Manager::Instance()->GetPlayerByObjectID(p_msg->m_Id))
+			{
+				strncpy(name, p_other_player->m_Skater->GetDisplayName(), 32);
+			}
+
+			Script::CStruct params;
+			params.AddString(CRCD(0xa4b08520, "String0"), name);
+			params.AddChecksum( NONAME, CRCD(0xd039432c,"fireball") );
+			p_skater->SelfEvent(CRCD(0xa1021af0, "MadeOtherSkaterBail"),¶ms);
+			
+			break;
+		}
+		
+		case GameNet::MSG_ID_SKATER_HIT_BY_PROJECTILE:
+		{
+			char name[32] = "Someone";
+
+			if (GameNet::PlayerInfo* p_other_player = GameNet::Manager::Instance()->GetPlayerByObjectID(p_msg->m_Id))
+			{
+				GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+				Mth::Vector offset = p_skater->GetPos() - p_other_player->m_Skater->GetPos();
+				// Perform a "safe" normalization
+				float offset_length = offset.Length();
+				if (offset_length < 0.0001f)
+				{
+					// if skaters are super close, or coincident then pretend they are not, to avoid zero length vectors
+					offset.Set(1.0f, 0.0f, 1.0f);
+				}
+				else
+				{
+					offset /= offset_length;
+				}
+				
+				// fudge it for now
+				// float speed = p_other_skater->GetVel().Length();
+				float speed = 300.0f;
+				p_skater->GetVel() = offset * speed * 1.5f; 
+				p_skater->GetVel()[Y] = 200.0f;
+
+				CSkaterPhysicsControlComponent* p_skater_physics_control_component = GetSkaterPhysicsControlComponentFromObject(p_skater);
+				if (p_skater_physics_control_component->IsSkating())
+				{
+					CSkaterCorePhysicsComponent* p_skater_core_physics_component = GetSkaterCorePhysicsComponentFromObject(p_skater);
+					p_skater_core_physics_component->CollideWithOtherSkaterLost(p_other_player->m_Skater);
+				}
+				else
+				{
+					CWalkComponent* p_walk_component = GetWalkComponentFromObject(p_skater);
+					p_walk_component->CollideWithOtherSkaterLost(p_other_player->m_Skater);
+				}
+
+				strncpy(name, p_other_player->m_Skater->GetDisplayName(), 32);
+				
+
+				if( gamenet_man->OnServer() == false )
+				{
+					Mdl::Score* score = p_skater->mp_skater_score_component->GetScore();
+					if(( score->GetTotalScore() - p_msg->m_Damage ) < 0 )
+					{
+						score->SetTotalScore( 0 );
+					}
+					else
+					{
+						score->SetTotalScore( score->GetTotalScore() - p_msg->m_Damage );
+					}
+				}
+			}
+
+			Script::CStruct params;
+			params.AddString(CRCD(0xa4b08520, "String0"), name);
+			params.AddChecksum( NONAME, CRCD(0xd039432c,"fireball") );
+			p_skater->SelfEvent(CRCD(0x915e5e39, "SkaterCollideBail"),¶ms);
+			break;
+		}
+		
+		default:
+			Dbg_Assert(false);
+			break;
+	}
+		
+	return Net::HANDLER_MSG_DONE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CSkaterStateHistoryComponent::sHandleCollision( Net::MsgHandlerContext* context )
+{
+	CSkater* p_skater = (CSkater*) (context->m_Data);
+	
+	switch (context->m_MsgId)
+	{
+		case GameNet::MSG_ID_SKATER_COLLIDE_WON:
+		{
+			GameNet::MsgByteInfo* p_msg = (GameNet::MsgByteInfo*) (context->m_Msg);
+			char name[32] = "Someone";
+			
+			bool loser_drivng = p_msg->m_Data & (1 << 7);
+			p_msg->m_Data &= ~(1 << 7);
+			
+			if (GameNet::PlayerInfo* p_other_player = GameNet::Manager::Instance()->GetPlayerByObjectID(p_msg->m_Data))
+			{
+				strncpy(name, p_other_player->m_Skater->GetDisplayName(), 32);
+			}
+
+			Script::CStruct params;
+			params.AddString(CRCD(0xa4b08520, "String0"), name);
+			if (loser_drivng)
+			{
+				params.AddChecksum(NO_NAME, CRCD(0x7fd0663d, "LoserIsDriving"));
+			}
+			p_skater->SelfEvent(CRCD(0xa1021af0, "MadeOtherSkaterBail"),¶ms);
+			
+			break;
+		}
+		
+		case GameNet::MSG_ID_SKATER_COLLIDE_LOST:
+		{
+			GameNet::MsgCollideLost* p_msg = (GameNet::MsgCollideLost*) (context->m_Msg);
+			char name[32] = "Someone";
+			
+			bool winner_driving = p_msg->m_Id & (1 << 7);
+			p_msg->m_Id &= ~(1 << 7);
+
+			if (GameNet::PlayerInfo* p_other_player = GameNet::Manager::Instance()->GetPlayerByObjectID(p_msg->m_Id))
+			{
+				CSkaterPhysicsControlComponent* p_skater_physics_control_component = GetSkaterPhysicsControlComponentFromObject(p_skater);
+				
+				if (!p_skater_physics_control_component->IsDriving())
+				{
+					Mth::Vector offset = p_skater->GetPos() - p_other_player->m_Skater->GetPos();
+					// Perform a "safe" normalization
+					float offset_length = offset.Length();
+					if (offset_length < 0.0001f)
+					{
+						// if skaters are super close, or coincident then pretend they are not, to avoid zero length vectors
+						offset.Set(1.0f, 0.0f, 1.0f);
+					}
+					else
+					{
+						offset /= offset_length;
+					}
+					
+					// fudge it for now
+					// float speed = p_other_skater->GetVel().Length();
+					float speed = 300.0f;
+					p_skater->GetVel() = offset * speed * 1.5f; 
+					p_skater->GetVel()[Y] = 200.0f;
+	
+					if (p_skater_physics_control_component->IsSkating())
+					{
+						CSkaterCorePhysicsComponent* p_skater_core_physics_component = GetSkaterCorePhysicsComponentFromObject(p_skater);
+						p_skater_core_physics_component->CollideWithOtherSkaterLost(p_other_player->m_Skater);
+					}
+					else
+					{
+						CWalkComponent* p_walk_component = GetWalkComponentFromObject(p_skater);
+						p_walk_component->CollideWithOtherSkaterLost(p_other_player->m_Skater);
+					}
+				}
+
+				strncpy(name, p_other_player->m_Skater->GetDisplayName(), 32);
+			}
+
+			Script::CStruct params;
+			params.AddString(CRCD(0xa4b08520, "String0"), name);
+			params.AddVector(CRCD(0xa6f5352f, "Offset"), p_msg->m_Offset);
+			if (winner_driving)
+			{
+				params.AddChecksum(NO_NAME, CRCD(0x2f679251, "WinnerIsDriving"));
+			}
+			p_skater->SelfEvent(CRCD(0x915e5e39, "SkaterCollideBail"),¶ms);
+			break;
+		}
+		
+		default:
+			Dbg_Assert(false);
+			break;
+			
+	}
+		
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector	CSkaterStateHistoryComponent::get_vel (   )
+{
+	return GetObject()->m_vel * Tmr::UncappedFrameLength();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CSkaterStateHistoryComponent::get_latest_position(   )
+{
+	return GetObject()->m_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CSkaterStateHistoryComponent::get_last_position(   )
+{
+    return GetObject()->m_old_pos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CSkaterStateHistoryComponent::get_time_between_last_update(   )
+{
+	int time_elapsed;
+	
+	time_elapsed = static_cast< int >(Tmr::FrameLength());
+	
+	if( time_elapsed == 0 )
+	{
+		time_elapsed = Tmr::VBlanks( 1 );
+	}
+	return time_elapsed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSkaterStateHistoryComponent::get_collision_cylinder_coeff( bool driving )
+{
+    // multiplied by the velocity of the skater to give the length of a line along which to check for collision
+	
+	float radius;
+	
+	if (GetSkater()->IsLocalClient())
+	{
+        radius = GetPhysicsFloat( CRCD(0xd24273b, "LanServerCollCoefficient"), Script::ASSERT);
+	}
+	else
+	{
+		radius = GetPhysicsFloat( CRCD(0x25f78481, "LanClientCollCoefficient"), Script::ASSERT);
+	}
+	
+	if (!driving)
+	{
+		return radius;
+	}
+	else
+	{
+        return radius * (1.0f + Script::GetFloat(CRCD(0xa49ff23, "DrivingCoefficientBoostFactor")));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CSkaterStateHistoryComponent::get_collision_cylinder_radius( bool first_driving, bool second_driving )
+{
+	float radius;
+	
+	if (GetSkater()->IsLocalClient())
+	{
+		if (GameNet::Manager::Instance()->InInternetMode())
+		{
+			radius = GetPhysicsFloat(CRCD(0xf2e35fa3, "InternetServerCollRadius"), Script::ASSERT);
+		}
+		else
+		{
+			radius = GetPhysicsFloat(CRCD(0xecb41860, "LanServerCollRadius"), Script::ASSERT);
+		}
+	}
+	else
+	{
+		if (GameNet::Manager::Instance()->InInternetMode())
+		{
+			radius = GetPhysicsFloat(CRCD(0xdbd6d58a, "InternetClientCollRadius"), Script::ASSERT);
+		}
+		else
+		{
+			radius = GetPhysicsFloat(CRCD(0xc5819249, "LanClientCollRadius"), Script::ASSERT);
+		}
+	}
+	
+	float driving_count = 0;
+	if (first_driving)
+	{
+		driving_count++;
+	}
+	if (second_driving)
+	{
+		driving_count++;
+	}
+	
+	return radius * (1.0f + driving_count * GetPhysicsFloat(CRCD(0xeef4afb3, "DrivingRadiusBoostFactor")));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SPosEvent::SPosEvent( void )
+{
+	memset( this, 0, sizeof( SPosEvent ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	SPosEvent::GetTime( void )
+{
+	uint32 time;
+
+	time = ( HiTime << 16 ) | ( LoTime );
+
+	return time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			SPosEvent::SetTime( uint32 time )
+{
+	LoTime = (unsigned short) ( time & 0xFFFF );
+	HiTime = (unsigned short) ( time >> 16 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SAnimEvent::SAnimEvent( void )
+{
+	memset( this, 0, sizeof( SAnimEvent ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	SAnimEvent::GetTime( void )
+{
+	uint32 time;
+
+	time = ( m_HiTime << 16 ) | ( m_LoTime );
+
+	return time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			SAnimEvent::SetTime( uint32 time )
+{
+	m_LoTime = (unsigned short) ( time & 0xFFFF );
+	m_HiTime = (unsigned short) ( time >> 16 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32			CSkaterStateHistoryComponent::GetLatestAnimTimestamp( void )
+{
+	return m_last_anm_time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			CSkaterStateHistoryComponent::SetLatestAnimTimestamp( uint32 timestamp )
+{
+	m_last_anm_time = timestamp;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
diff --git a/Code/Sk/Components/SkaterStateHistoryComponent.h b/Code/Sk/Components/SkaterStateHistoryComponent.h
new file mode 100644
index 0000000..2d8362a
--- /dev/null
+++ b/Code/Sk/Components/SkaterStateHistoryComponent.h
@@ -0,0 +1,173 @@
+//****************************************************************************
+//* MODULE:         Sk/Components
+//* FILENAME:       SkaterStateHistoryComponent.cpp
+//* OWNER:          Dan
+//* CREATION DATE:  3/13/3
+//****************************************************************************
+
+#ifndef __COMPONENTS_SKATERSTATEHISTORYCOMPONENT_H__
+#define __COMPONENTS_SKATERSTATEHISTORYCOMPONENT_H__
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#define		CRC_SKATERSTATEHISTORY CRCD(0x18223fd6, "SkaterStateHistory")
+
+#define		GetSkaterStateHistoryComponent() ((Obj::CSkaterStateHistoryComponent*)GetComponent(CRC_SKATERSTATEHISTORY))
+#define		GetSkaterStateHistoryComponentFromObject(pObj) ((Obj::CSkaterStateHistoryComponent*)(pObj)->GetComponent(CRC_SKATERSTATEHISTORY))
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+}
+
+namespace Net
+{
+	class MsgHandlerContext;
+}
+              
+namespace Obj
+{
+
+class SPosEvent
+{   
+public:
+	SPosEvent( void );
+
+	uint32			GetTime( void );
+	void			SetTime( uint32 time );
+
+	short			ShortPos[3];
+	Mth::Matrix		Matrix;
+	Mth::Vector		Position;
+	Mth::Vector		Eulers;
+	Flags< int >	SkaterFlags;
+	Flags< int >	EndRunFlags;
+	int				State;
+	char			DoingTrick;
+	char			Walking;
+	char			Driving;
+	uint16			LoTime;
+	uint16			HiTime;
+	ETerrainType	Terrain;
+	sint16			RailNode;
+};
+
+class SAnimEvent
+{   
+public:
+	SAnimEvent( void );
+
+	uint32			GetTime( void );
+	void			SetTime( uint32 time );
+
+	char			m_MsgId;
+	char			m_ObjId;
+	char			m_LoopingType;
+	char			m_Flags;
+	uint16			m_LoTime;
+	uint16			m_HiTime;
+	bool			m_Flipped;
+	bool			m_Rotate;
+	bool			m_Hide;
+	float			m_Alpha;
+	float			m_StartTime;
+	float			m_EndTime;
+	float			m_BlendPeriod;
+	float 			m_Speed;
+	uint32			m_Asset;
+	uint32			m_Bone;
+	float			m_WobbleAmpA;
+	float			m_WobbleAmpB;
+	float			m_WobbleK1;
+	float			m_WobbleK2;
+	float			m_SpazFactor;
+	int 			m_Duration;
+	int 			m_SinePower;
+	int				m_Index;
+	float 			m_StartAngle;
+	float 			m_DeltaAngle;
+	bool			m_HoldOnLastAngle;
+};
+
+class CSkaterStateHistoryComponent : public CBaseComponent
+{
+public:
+	enum
+	{
+		vNUM_POS_HISTORY_ELEMENTS = 20,
+		vNUM_ANIM_HISTORY_ELEMENTS = 20
+	};
+	
+public:
+    CSkaterStateHistoryComponent();
+    virtual ~CSkaterStateHistoryComponent();
+
+public:
+    virtual void            		Update();
+    virtual void            		InitFromStructure( Script::CStruct* pParams );
+    virtual void            		RefreshFromStructure( Script::CStruct* pParams );
+    
+    virtual EMemberFunctionResult   CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	virtual void 					GetDebugInfo( Script::CStruct* p_info );
+
+	static CBaseComponent*			s_create();
+	
+	CSkater*						GetSkater() { return static_cast< CSkater* >(GetObject()); }
+	
+	static int						sHandleCollision ( Net::MsgHandlerContext* context );
+	static int						sHandleProjectileHit ( Net::MsgHandlerContext* context );
+	
+	SPosEvent*						GetPosHistory (   ) { return mp_pos_history; }
+	SPosEvent*						GetLatestPosEvent (   ) { return &mp_pos_history[m_num_pos_updates % vNUM_POS_HISTORY_ELEMENTS]; }
+	SPosEvent*						GetLastPosEvent (   ) { return &mp_pos_history[(m_num_pos_updates + ( vNUM_POS_HISTORY_ELEMENTS - 1 )) % vNUM_POS_HISTORY_ELEMENTS]; }
+	void							IncrementNumPosUpdates (   ) { m_num_pos_updates++; }
+	void							ResetPosHistory (   ) { m_num_pos_updates = 0; }
+	int								GetNumPosUpdates (   ) { return m_num_pos_updates; }
+	
+	uint32							GetLatestAnimTimestamp( void );
+	void							SetLatestAnimTimestamp( uint32 timestamp );
+	SAnimEvent*						GetAnimHistory (   ) { return mp_anim_history; }
+	SAnimEvent*						GetLatestAnimEvent (   ) { return &mp_anim_history[m_num_anim_updates % vNUM_ANIM_HISTORY_ELEMENTS]; }
+	SAnimEvent*						GetLastAnimEvent (   ) { return &mp_anim_history[(m_num_anim_updates + ( vNUM_ANIM_HISTORY_ELEMENTS - 1 )) % vNUM_ANIM_HISTORY_ELEMENTS]; }
+	void							IncrementNumAnimUpdates (   ) { m_num_anim_updates++; }
+	void							ResetAnimHistory (   ) { m_num_anim_updates = 0; }
+	int								GetNumAnimUpdates (   ) { return m_num_anim_updates; }
+	
+	bool							CheckForCrownCollision (   );
+	void							CollideWithOtherSkaters ( int start_index );
+	bool							GetCollidingPlayerAndTeam ( Script::CStruct* pParams, Script::CScript* pScript );
+	
+	void							SetCurrentVehicleControlType ( uint32 control_type ) { m_current_vehicle_control_type = control_type; }
+	uint32							GetCurrentVehicleControlType (   ) { return m_current_vehicle_control_type; }
+	
+private:
+	Mth::Vector						get_latest_position (   );
+	Mth::Vector						get_last_position (   );
+	Mth::Vector						get_vel (   );
+	int								get_time_between_last_update (   );
+	
+	float							get_collision_cylinder_coeff ( bool driving );
+	float							get_collision_cylinder_radius ( bool first_driving, bool second_driving );
+	
+private:
+	int								m_num_pos_updates;
+	SPosEvent						mp_pos_history [ vNUM_POS_HISTORY_ELEMENTS ];
+	
+	int								m_num_anim_updates;
+	SAnimEvent						mp_anim_history [ vNUM_ANIM_HISTORY_ELEMENTS ];
+
+        uint32							m_last_anm_time;
+	// if the non-local client is driving, this control type's model will be used; set via a network message
+	uint32							m_current_vehicle_control_type;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Engine/RectFeeler.cpp b/Code/Sk/Engine/RectFeeler.cpp
new file mode 100644
index 0000000..78915a5
--- /dev/null
+++ b/Code/Sk/Engine/RectFeeler.cpp
@@ -0,0 +1,219 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Sk/Engine												**
+**																			**
+**	File name:		RectFeeler.cpp											**
+**																			**
+**	Created: 		02/14/2003	-	Dan										**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+		
+#include 
+
+#include 
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+Nx::CCollCache* CRectFeeler::sp_default_cache = NULL;
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+// BUG: getting false collisions when rectangle is flush with axes
+
+CRectFeeler::CRectFeeler (   )
+{
+	init();
+}
+
+CRectFeeler::CRectFeeler ( const Mth::Vector& corner, const Mth::Vector& first_edge, const Mth::Vector& second_edge )
+{
+	m_corner = corner;
+	m_first_edge = first_edge;
+	m_second_edge = second_edge;
+	
+	init();
+}
+
+void CRectFeeler::init (   )
+{
+	mp_cache = NULL;
+	m_coll_data.num_surfaces = 0;
+	m_merged_coll_data.num_surfaces = 0;
+	
+	m_coll_data.ignore_1 = mFD_NON_COLLIDABLE;
+	m_coll_data.ignore_0 = 0;
+}
+
+bool CRectFeeler::GetCollision ( bool movables )
+{
+	Dbg_MsgAssert(!movables, ("Collision test against movables not yet supported by CRectFeeler"));
+	
+	Rectangle& rect = *this;
+	
+	return Nx::CCollObj::sFindRectangleStaticCollision(rect, &m_coll_data, mp_cache ? mp_cache : sp_default_cache);
+}
+
+void CRectFeeler::MergeCollisionSurfaces (   )
+{
+	// precomputed values
+	bool p_surfaces_used[Nx::MAX_NUM_2D_COLLISIONS_REPORTED];
+	Mth::Vector p_line_directions[Nx::MAX_NUM_2D_COLLISIONS_REPORTED];
+	for (int n = m_coll_data.num_surfaces; n--; )
+	{
+		p_surfaces_used[n] = false;
+		
+		p_line_directions[n] = m_coll_data.p_surfaces[n].ends[1].point - m_coll_data.p_surfaces[n].ends[0].point;
+		p_line_directions[n].Normalize();
+	}
+	
+	// drop duplicate surfaces
+	for (int i = m_coll_data.num_surfaces; i--; )
+	{
+		Nx::S2DCollSurface& surface_i = m_coll_data.p_surfaces[i];
+		
+		if (p_surfaces_used[i]) continue;
+		
+		for (int j = i; j--; )
+		{
+			Nx::S2DCollSurface& surface_j = m_coll_data.p_surfaces[j];
+			if ((very_close(surface_i.ends[0].point, surface_j.ends[0].point) && very_close(surface_i.ends[1].point, surface_j.ends[1].point))
+				|| (very_close(surface_i.ends[0].point, surface_j.ends[1].point) && very_close(surface_i.ends[1].point, surface_j.ends[0].point)))
+			{
+				if (Mth::DotProduct(surface_i.normal, surface_j.normal) > 0.99f)
+				{
+					p_surfaces_used[j] = true;
+					continue;
+				}
+			}
+		}
+	}
+	
+	// we build a merged surface list out of the collision data surface list, attempting to reduce the number of surfaces by merging those which
+	// are equivalent for the purpose of collision lines; that is, merge chains of collision lines which are parallel and have equal surface normals
+	
+	// for each surface, first attempt to absorb as yet unvisited surfaces; then, attempt to absorb the surface into a surface previously accepted into
+	// the merged surface list; if unsuccessful, add the surface to the merged surface list
+	
+	m_merged_coll_data.num_surfaces = 0;
+	
+	// loop over the collision data surfaces
+	for (int proposed_idx = m_coll_data.num_surfaces; proposed_idx--; )
+	{
+		Nx::S2DCollSurface proposed_surface = m_coll_data.p_surfaces[proposed_idx];
+		
+		if (p_surfaces_used[proposed_idx]) continue;
+		
+		// check to absorb unused surfaces into the proposed surface
+		
+		bool clean_pass;
+		do // loop over the unused surfaces until we pass through them cleanly, finding no absorption opportunities
+		{
+			clean_pass = true;
+			
+			for (int check_idx = proposed_idx; check_idx--; )
+			{
+				Nx::S2DCollSurface& check_surface = m_coll_data.p_surfaces[check_idx];
+				
+				// check that this line is not already used
+				if (p_surfaces_used[check_idx]) continue;
+				
+				// check that surfaces are parallel
+				if (Mth::DotProduct(proposed_surface.normal, check_surface.normal) < 0.99f) continue;
+				
+				// check that collision lines are parallel
+				if (Mth::Abs(Mth::DotProduct(p_line_directions[proposed_idx], p_line_directions[check_idx])) < 0.99f) continue;
+				
+				// check that collision lines connect
+				if (very_close(proposed_surface.ends[0].point, check_surface.ends[0].point))
+				{
+	   				proposed_surface.ends[0].point = check_surface.ends[1].point;
+	   				proposed_surface.ends[0].tangent_exists = check_surface.ends[1].tangent_exists;
+	   				proposed_surface.ends[0].tangent = check_surface.ends[1].tangent;
+					
+				}
+				else if (very_close(proposed_surface.ends[0].point, check_surface.ends[1].point))
+				{
+	   				proposed_surface.ends[0].point = check_surface.ends[0].point;
+	   				proposed_surface.ends[0].tangent_exists = check_surface.ends[0].tangent_exists;
+	   				proposed_surface.ends[0].tangent = check_surface.ends[0].tangent;
+				}
+				else if (very_close(proposed_surface.ends[1].point, check_surface.ends[0].point))
+				{
+					proposed_surface.ends[1].point = check_surface.ends[1].point;
+	   				proposed_surface.ends[1].tangent_exists = check_surface.ends[1].tangent_exists;
+	   				proposed_surface.ends[1].tangent = check_surface.ends[1].tangent;
+				}
+				else if (very_close(proposed_surface.ends[1].point, check_surface.ends[1].point))
+				{
+					proposed_surface.ends[1].point = check_surface.ends[0].point;
+	   				proposed_surface.ends[1].tangent_exists = check_surface.ends[0].tangent_exists;
+	   				proposed_surface.ends[1].tangent = check_surface.ends[0].tangent;
+				}
+				else
+				{
+					continue;
+				}
+				
+				// we've successfully absorbed check_surface into proposed_surface
+				p_surfaces_used[check_idx] = true;
+				
+				clean_pass = false;
+			} // END loop over unvisited surfaces
+		} while (clean_pass = false);
+		
+		// accept the poropsed surface into the reduced surface list
+		
+		m_merged_coll_data.p_surfaces[m_merged_coll_data.num_surfaces++] = proposed_surface;
+	} // END loop over proposing surfaces
+}
+
+void CRectFeeler::DebugLines ( int r, int g, int b, int num_frames ) const
+{
+	uint32 color = MAKE_RGB(r, g, b);
+	
+	Gfx::AddDebugLine(m_corner, m_corner + m_first_edge, color, color, num_frames);
+	Gfx::AddDebugLine(m_corner + m_first_edge, m_corner + m_first_edge + m_second_edge, color, color, num_frames);
+	Gfx::AddDebugLine(m_corner + m_first_edge + m_second_edge, m_corner + m_second_edge, color, color, num_frames);
+	Gfx::AddDebugLine(m_corner + m_second_edge, m_corner, color, color, num_frames);
+	
+	// if we've merged the surfaces
+	if (m_merged_coll_data.num_surfaces > 0)
+	{
+		// draw the merged collision lines
+		for (int n = m_merged_coll_data.num_surfaces; n--; )
+		{
+			color = MAKE_RGB(r += 201, g += 99, b += 31);
+			Gfx::AddDebugLine(m_merged_coll_data.p_surfaces[n].ends[0].point, m_merged_coll_data.p_surfaces[n].ends[1].point, color, color, 1);
+		}
+	}
+	else
+	{
+		// otherwise, draw the bare collision lines
+		for (int n = m_coll_data.num_surfaces; n--; )
+		{
+			color = MAKE_RGB(r += 201, g += 99, b += 31);
+			Gfx::AddDebugLine(m_coll_data.p_surfaces[n].ends[0].point, m_coll_data.p_surfaces[n].ends[1].point, color, color, 1);
+		}
+	}
+}
+	
+
diff --git a/Code/Sk/Engine/RectFeeler.h b/Code/Sk/Engine/RectFeeler.h
new file mode 100644
index 0000000..93735b0
--- /dev/null
+++ b/Code/Sk/Engine/RectFeeler.h
@@ -0,0 +1,116 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Sk/Engine												**
+**																			**
+**	File name:		RectFeeler.h											**
+**																			**
+**	Created: 		02/14/2003	-	Dan										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SK_ENGINE_RECTFEELER_H
+#define	__SK_ENGINE_RECTFEELER_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class	CRectFeeler	: public Mth::Rectangle
+{
+public:
+	CRectFeeler (   );
+	CRectFeeler ( const Mth::Vector& corner, const Mth::Vector& first_edge, const Mth::Vector& second_edge );
+	
+	void							SetRectangle ( const Mth::Rectangle& rect );
+	
+	bool							GetCollision ( bool movables = false );
+	
+	int								GetNumCollisionSurfaces (   ) const { return m_coll_data.num_surfaces; }
+	const Nx::S2DCollSurface&		GetCollisionSurface ( int n ) const;
+	
+	int								GetNumMergedCollisionSurfaces (   ) const { return m_merged_coll_data.num_surfaces; }
+	const Nx::S2DCollSurface&		GetMergedCollisionSurface ( int n ) const;
+	
+	void							MergeCollisionSurfaces (   );
+	
+	void							SetIgnore ( uint16 ignore_1, uint16 ignore_0 );
+	
+	void							SetCache ( Nx::CCollCache* p_cache ) { mp_cache = p_cache; }
+	void							ClearCache (   ) { mp_cache = NULL; }
+	
+	static void						sSetDefaultCache ( Nx::CCollCache* p_cache ) { sp_default_cache = p_cache; }
+	static void						sClearDefaultCache (   ) { sp_default_cache = NULL; }
+	
+	// ReduceCollisionSurfaces (   )
+	
+	void							DebugLines ( int r = 255, int g = 255, int b = 255, int num_frames = 0 ) const;
+
+private:
+	void							init (   );
+	
+	bool							very_close ( const Mth::Vector p, const Mth::Vector q ) const;
+
+private:
+	
+	Nx::S2DCollData					m_coll_data;
+	Nx::S2DCollData					m_merged_coll_data;
+	
+	Nx::CCollCache*					mp_cache;
+	
+	static Nx::CCollCache*			sp_default_cache;
+};
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline void							CRectFeeler::SetRectangle ( const Mth::Rectangle& rect )
+{
+	m_corner = rect.m_corner;
+	m_first_edge = rect.m_first_edge;
+	m_second_edge = rect.m_second_edge;
+}
+
+inline const Nx::S2DCollSurface&	CRectFeeler::GetCollisionSurface ( int n ) const
+{
+	Dbg_Assert(n < m_coll_data.num_surfaces);
+	return m_coll_data.p_surfaces[n];
+}
+
+inline const Nx::S2DCollSurface&	CRectFeeler::GetMergedCollisionSurface ( int n ) const
+{
+	Dbg_Assert(n < m_merged_coll_data.num_surfaces);
+	Dbg_Assert(!isnanf(m_merged_coll_data.p_surfaces[n].ends[0].point[W]));
+	Dbg_Assert(!isnanf(m_merged_coll_data.p_surfaces[n].ends[1].point[W]));
+	return m_merged_coll_data.p_surfaces[n];
+}
+
+inline void							CRectFeeler::SetIgnore ( uint16 ignore_1, uint16 ignore_0 )
+{
+	m_coll_data.ignore_1 = ignore_1;
+	m_coll_data.ignore_0 = ignore_0;
+}
+
+inline bool							CRectFeeler::very_close ( const Mth::Vector p, const Mth::Vector q ) const
+{
+	return Mth::Abs(p[X] - q[X]) < 0.1f && Mth::Abs(p[Y] - q[Y]) < 0.1f && Mth::Abs(p[Z] - q[Z]) < 0.1f;
+}
+
+#endif
diff --git a/Code/Sk/Engine/SuperSector.cpp b/Code/Sk/Engine/SuperSector.cpp
new file mode 100644
index 0000000..2c5908b
--- /dev/null
+++ b/Code/Sk/Engine/SuperSector.cpp
@@ -0,0 +1,876 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SKATE3													**
+**																			**
+**	Module:			SSEC					 								**
+**																			**
+**	File name:		SuperSector.cpp											**
+**																			**
+**	Created by:		01/12/01	-	spg										**
+**																			**
+**	Description:	Functions concerning SuperSectors						**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+
+#include 					
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+
+namespace SSec
+{
+
+
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define	COLL_LINE_EXTENSION	0.5f
+
+#define	vMAX_QUAL_SECTORS		1024	   	// never saw this go above 100
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+class WorldSectorNode : public Lst::Node< Nx::CSector >
+{
+public:
+	WorldSectorNode( Nx::CSector* sector ) : Lst::Node< Nx::CSector > ( sector ) {}
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static	uint8	sOperationId = 0;	// operation id for sectors
+//#ifdef	__PLAT_NGPS__
+//const	Nx::CCollStatic**	QualCollSectors = (const Nx::CCollStatic**)0x70000000;	// use ScratchPad Ram (SPR) for speed
+//#else
+static	Nx::CCollStatic*	QualCollSectors[vMAX_QUAL_SECTORS];
+//#endif
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+Sector::Sector( void )
+{
+	
+
+	m_NumSectors = 0;
+	m_NumCollSectors = 0;
+	m_SectorList = NULL;
+	m_CollSectorList = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Sector::~Sector( void )
+{
+	
+
+	if( m_SectorList )
+	{   
+		delete [] m_SectorList;
+	}
+	if( m_CollSectorList )
+	{   
+		Mem::Free(m_CollSectorList);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Sector::remove_collision(Nx::CCollStatic *p_coll)
+{
+	for (int idx = 0; idx < m_NumCollSectors; idx++)
+	{
+		if (m_CollSectorList[idx] == p_coll)
+		{
+			// Shift the rest of the pointers over
+			for (int copy_idx = idx + 1; copy_idx < m_NumCollSectors; copy_idx++)
+			{
+				m_CollSectorList[copy_idx - 1] = m_CollSectorList[copy_idx];
+			}
+
+			// And put a NULL on the last one (in case it isn't there, yet)
+			m_CollSectorList[m_NumCollSectors - 1] = NULL;
+
+			return true;
+		}
+	}
+
+	// Never found it
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Sector::replace_collision(Nx::CCollStatic *p_coll, Nx::CCollStatic *p_replace_coll)
+{
+	for (int idx = 0; idx < m_NumCollSectors; idx++)
+	{
+		if (m_CollSectorList[idx] == p_coll)
+		{
+			m_CollSectorList[idx] = p_replace_coll;
+			return true;
+		}
+	}
+	
+	// Never found it
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Sector::has_collision(Nx::CCollStatic *p_coll)
+{
+	for (int idx = 0; idx < m_NumCollSectors; idx++)
+	{
+		if (m_CollSectorList[idx] == p_coll)
+		{
+			return true;
+		}
+	}
+
+	// Never found it
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8		GetCurrentSectorOperationId( void )
+{
+	
+
+	return sOperationId;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8		GetNewSectorOperationId( void )
+{   
+	
+
+	return ++sOperationId;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void	Manager::GenerateSuperSectors(const Mth::CBBox& world_bbox )
+{
+	int i, j;
+	float world_width, world_depth;
+	float start_x, start_z;
+
+	Dbg_Printf( "Generating Blank SuperSectors\n" );
+
+	m_world_bbox = world_bbox;
+
+	world_width = world_bbox.GetMax()[X] - world_bbox.GetMin()[X];
+	world_depth = world_bbox.GetMax()[Z] - world_bbox.GetMin()[Z];
+
+	m_sector_width = world_width / NUM_PARTITIONS_X;
+	m_sector_depth = world_depth / NUM_PARTITIONS_Z;
+
+	// For each super sector, find any world sectors which intersect the ss bounding box
+	// and add that ws to ss's list of ws's
+	start_x = world_bbox.GetMin()[X];
+		
+	Mth::Vector sec_min, sec_max;
+	sec_min[W] = 0.0f;		 			// to avoid assertions
+	sec_max[W] = 0.0f;
+	for( i = 0; i < NUM_PARTITIONS_X; i++ )
+	{
+		start_z = world_bbox.GetMin()[Z];
+
+		for( j = 0; j < NUM_PARTITIONS_Z; j++ )
+		{
+			sec_min[X] = start_x;
+			sec_min[Y] = (float) -1000000000000.0f; //world_bbox.GetMin()[Y];
+			sec_min[Z] = start_z;
+            
+			sec_max[X] = start_x + m_sector_width;
+			sec_max[Y] = (float) 1000000000000.0f; //world_bbox.GetMax()[Y];
+			sec_max[Z] = start_z + m_sector_depth;
+
+			m_super_sector_list[i][j].m_Bbox.Set(sec_min, sec_max);
+			
+			start_z += m_sector_depth;
+		}
+
+		start_x += m_sector_width;
+	}
+
+	m_num_sectors_x = NUM_PARTITIONS_X;
+	m_num_sectors_z = NUM_PARTITIONS_Z;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::AddCollisionToSuperSectors( Nx::CCollStatic *coll, int num_coll_sectors )
+{
+	Lst::Head< Nx::CCollStatic > sector_list;
+	Lst::Node< Nx::CCollStatic > *sector, *next;
+
+//	for (int i=0;i<100;i++)
+//	{
+//		printf ("%f,%f -> %f,%f\n",
+//		coll[i].GetGeometry()->GetBBox().GetMin().GetX(),
+//		coll[i].GetGeometry()->GetBBox().GetMin().GetZ(),
+//		coll[i].GetGeometry()->GetBBox().GetMax().GetX(),
+//		coll[i].GetGeometry()->GetBBox().GetMax().GetZ());
+//	}
+
+
+					
+	for( int i = 0; i < NUM_PARTITIONS_X; i++ )
+	{
+		for( int j = 0; j < NUM_PARTITIONS_Z; j++ )
+		{
+			// Allocate temp list on high heap
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+			for ( int idx = 0; idx < num_coll_sectors; idx++ )
+			{
+				// Check to see if this collision sector belongs in the SuperSector
+				Dbg_Assert(coll[idx].GetGeometry());
+				if (coll[idx].GetGeometry()->GetNumFaces()>0 && coll[idx].GetGeometry()->GetBBox().Intersect(m_super_sector_list[i][j].m_Bbox))
+				{
+					Lst::Node< Nx::CCollStatic > *node = new Lst::Node< Nx::CCollStatic > ( &(coll[idx]) );
+					sector_list.AddToTail( node );
+				}
+			}
+
+			Mem::Manager::sHandle().PopContext();
+
+			if( sector_list.CountItems() > 0 )
+			{
+				m_super_sector_list[i][j].m_CollSectorList = (Nx::CCollStatic **) Mem::Malloc(sizeof(Nx::CCollStatic *) * sector_list.CountItems());
+				m_super_sector_list[i][j].m_NumCollSectors = sector_list.CountItems();
+				int k = 0;
+				for( sector = sector_list.GetNext(); sector; sector = next )
+				{
+					next = sector->GetNext();
+
+					m_super_sector_list[i][j].m_CollSectorList[k] = sector->GetData();
+					delete sector;
+					k++;
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::UpdateCollisionSuperSectors(Lst::Head &add_list,
+											 Lst::Head &remove_list,
+											 Lst::Head &update_list)
+{
+	Lst::Head< Nx::CCollStatic > sector_add_list;
+	Lst::Node< Nx::CCollStatic > *sector, *next;
+	Nx::CCollStatic *p_coll_sector;
+
+	uint num_add_items = add_list.CountItems();
+	uint num_remove_items = remove_list.CountItems();
+	uint num_update_items = update_list.CountItems();
+
+	//Dbg_Message("In UpdateCollisionSuperSectors with add size %d, remove size %d", num_add_items, num_remove_items);
+
+	uint idx;
+	int num_change;
+	for( int i = 0; i < NUM_PARTITIONS_X; i++ )
+	{
+		for( int j = 0; j < NUM_PARTITIONS_Z; j++ )
+		{
+			SSec::Sector *p_super_sector = &(m_super_sector_list[i][j]);
+			num_change = 0;
+
+			// Allocate temp list on high heap
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+			// Check removed sectors first
+			sector = remove_list.GetNext();
+			for (idx = 0; idx < num_remove_items; idx++)
+			{
+				p_coll_sector = sector->GetData();
+
+				// Check to see if this collision sector should be in the SuperSector
+				if (p_coll_sector->GetGeometry()->GetBBox().Intersect(p_super_sector->m_Bbox))
+				{
+					//Dbg_Message("Removing collision %x to SuperSector (%d, %d)", p_coll_sector->GetChecksum(), i, j);
+					num_change = remove_from_super_sector(p_coll_sector, p_super_sector, sector_add_list, num_change);
+#if 0
+				} else {
+					if (p_super_sector->remove_collision(p_coll_sector))
+					{
+						num_change--;
+						Dbg_MsgAssert(0, ("UpdateCollisionSuperSectors: Removed collision %x that shouldn't be in SuperSector (%d, %d)", 
+										  p_coll_sector->GetChecksum(), i, j));
+					}
+#endif
+				}
+
+				sector = sector->GetNext();
+			}
+
+			// Now check added sectors
+			sector = add_list.GetNext();
+			for (idx = 0; idx < num_add_items; idx++)
+			{
+				p_coll_sector = sector->GetData();
+
+				// Check to see if this collision sector belongs in the SuperSector
+				if (p_coll_sector->GetGeometry()->GetBBox().Intersect(p_super_sector->m_Bbox))
+				{
+					//Dbg_Message("Adding collision %x to SuperSector (%d, %d)", p_coll_sector->GetChecksum(), i, j);
+					num_change = add_to_super_sector(p_coll_sector, p_super_sector, sector_add_list, num_change);
+				}
+
+				sector = sector->GetNext();
+			}
+			// And check updated sectors
+			sector = update_list.GetNext();
+			for (idx = 0; idx < num_update_items; idx++)
+			{
+				p_coll_sector = sector->GetData();
+				bool on_list = p_super_sector->has_collision(p_coll_sector);
+				if (p_coll_sector->GetGeometry()->GetBBox().Intersect(p_super_sector->m_Bbox) != on_list)
+				{
+					if (on_list)
+					{
+						//Dbg_Message("Removing updated collision %x to SuperSector (%d, %d)", p_coll_sector->GetChecksum(), i, j);
+						// Remove sector
+						num_change = remove_from_super_sector(p_coll_sector, p_super_sector, sector_add_list, num_change);
+					}
+					else
+					{
+						//Dbg_Message("Adding updated collision %x to SuperSector (%d, %d)", p_coll_sector->GetChecksum(), i, j);
+						// Add sector
+						num_change = add_to_super_sector(p_coll_sector, p_super_sector, sector_add_list, num_change);
+					}
+				}
+
+				sector = sector->GetNext();
+			}
+
+			Mem::Manager::sHandle().PopContext();
+
+			int new_size = p_super_sector->m_NumCollSectors + num_change;
+
+			if (num_change < 0)				// Re-alloc array down
+			{
+				if (!p_super_sector->m_CollSectorList ||
+					!Mem::Manager::sHandle().ReallocateShrink(sizeof(Nx::CCollStatic *) * new_size, p_super_sector->m_CollSectorList))
+				{
+					// If we can't realloc down, then we have to use the old realloc
+					p_super_sector->m_CollSectorList = (Nx::CCollStatic **) Mem::Realloc(p_super_sector->m_CollSectorList,
+																						 sizeof(Nx::CCollStatic *) * new_size);
+				}
+			}
+			else if (num_change > 0)		// Re-alloc array up and copy in sector_add_list
+			{
+				if (!p_super_sector->m_CollSectorList ||
+				    !Mem::Manager::sHandle().ReallocateUp(sizeof(Nx::CCollStatic *) * new_size, p_super_sector->m_CollSectorList))
+				{
+					// If we can't realloc up, then we have to use the old realloc
+					p_super_sector->m_CollSectorList = (Nx::CCollStatic **) Mem::Realloc(p_super_sector->m_CollSectorList,
+																						 sizeof(Nx::CCollStatic *) * new_size);
+				}
+
+				// Copy add list in
+				idx = p_super_sector->m_NumCollSectors;
+				for(sector = sector_add_list.GetNext(); sector; sector = next)
+				{
+					next = sector->GetNext();
+
+					p_super_sector->m_CollSectorList[idx] = sector->GetData();
+					delete sector;
+					idx++;
+				}
+				//Dbg_Message("Adding %d sectors to [%d, %d]. New size %d", num_change, i, j, new_size);
+			}
+
+			Dbg_Assert(sector_add_list.IsEmpty());
+
+			p_super_sector->m_NumCollSectors = new_size;
+		}
+	}
+	
+	
+	
+	for( int i = 0; i < NUM_PARTITIONS_X; i++ )
+	{
+		for( int j = 0; j < NUM_PARTITIONS_Z; j++ )
+		{
+			SSec::Sector *p_super_sector = &(m_super_sector_list[i][j]);
+			Nx::CCollStatic** pp_old = p_super_sector->m_CollSectorList;
+			if (pp_old)
+			{
+				uint32 count = p_super_sector->m_NumCollSectors;
+				if (count)
+				{
+					p_super_sector->m_CollSectorList = (Nx::CCollStatic**)Mem::Malloc(4*(count));
+					memcpy(p_super_sector->m_CollSectorList, pp_old, 4*(count));
+					Mem::Free(pp_old);
+				}
+				else
+				{
+					p_super_sector->m_CollSectorList = NULL;
+					Mem::Free(pp_old);
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::add_to_super_sector(Nx::CCollStatic *p_coll, SSec::Sector *p_super_sector, Lst::Head< Nx::CCollStatic > & add_list, int num_changes)
+{
+	if (num_changes < 0)
+	{
+		// Just append to end of existing array
+		int avail_idx = p_super_sector->m_NumCollSectors + num_changes;
+		p_super_sector->m_CollSectorList[avail_idx] = p_coll;
+	} else {
+		// Append to add list
+		Lst::Node< Nx::CCollStatic > *node = new Lst::Node< Nx::CCollStatic > (p_coll);
+		add_list.AddToTail(node);
+	}
+
+	return ++num_changes;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::remove_from_super_sector(Nx::CCollStatic *p_coll, SSec::Sector *p_super_sector, Lst::Head< Nx::CCollStatic > & add_list, int num_changes)
+{
+	if (num_changes > 0)
+	{
+		// Move a sector off the add list, if there is one
+		Lst::Node< Nx::CCollStatic > *p_sector_node;
+
+		p_sector_node = add_list.GetNext();
+		if (!p_super_sector->replace_collision(p_coll, p_sector_node->GetData()))
+		{
+			Dbg_MsgAssert(0, ("UpdateCollisionSuperSectors: Can't replace collision %x that should be in SuperSector", 
+							  p_coll->GetChecksum()));
+		}
+		delete p_sector_node;
+	}
+	else 
+	{
+		// Just remove the sector
+		if (!p_super_sector->remove_collision(p_coll))
+		{
+			Dbg_MsgAssert(0, ("UpdateCollisionSuperSectors: Can't remove collision %x that should be in SuperSector", 
+							  p_coll->GetChecksum()));
+		}
+	}
+
+	return --num_changes;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ClearCollisionSuperSectors()
+{
+	// Just delete all the arrays, since whoever called this knows they are all going away
+	for( int i = 0; i < NUM_PARTITIONS_X; i++ )
+	{
+		for( int j = 0; j < NUM_PARTITIONS_Z; j++ )
+		{
+			SSec::Sector *p_super_sector = &(m_super_sector_list[i][j]);
+
+			if( p_super_sector->m_CollSectorList )
+			{   
+				Mem::Free(p_super_sector->m_CollSectorList);
+				p_super_sector->m_CollSectorList = NULL;
+			}
+
+			p_super_sector->m_NumCollSectors = 0;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CCollStatic** Manager::GetIntersectingCollSectors( Mth::CBBox& bbox )
+{
+	int op_id = GetNewSectorOperationId();
+	
+	float x_min = bbox.GetMin()[X] - m_world_bbox.GetMin()[X];
+	float x_max = bbox.GetMax()[X] - m_world_bbox.GetMin()[X];
+	float z_min = bbox.GetMin()[Z] - m_world_bbox.GetMin()[Z];
+	float z_max = bbox.GetMax()[Z] - m_world_bbox.GetMin()[Z];
+	
+	// extent the min and max in each direction to catch boundary conditions
+	x_min -= COLL_LINE_EXTENSION;
+	x_max += COLL_LINE_EXTENSION;
+	z_min -= COLL_LINE_EXTENSION;
+	z_max += COLL_LINE_EXTENSION;
+	
+	// determine the corresponding rectangle in terms of super sectors indicies
+	int start_x_box = static_cast< int >(x_min / m_sector_width);
+	int start_z_box = static_cast< int >(z_min / m_sector_depth);
+	int end_x_box = static_cast< int >(x_max / m_sector_width);
+	int end_z_box = static_cast< int >(z_max / m_sector_depth);
+	
+	// cap the super sector indicies
+	if (start_x_box < 0)
+	{
+		start_x_box = 0;
+	}
+	else if (start_x_box >= m_num_sectors_x)
+	{
+		start_x_box = m_num_sectors_x - 1;
+	}
+	if (start_z_box < 0)
+	{
+		start_z_box = 0;
+	}
+	else if (start_z_box >= m_num_sectors_z)
+	{
+		start_z_box = m_num_sectors_z - 1;
+	}
+	if (end_x_box < 0)
+	{
+		end_x_box = 0;
+	}
+	else if (end_x_box >= m_num_sectors_x)
+	{
+		end_x_box = m_num_sectors_x - 1;
+	}
+	if (end_z_box < 0)
+	{
+		end_z_box = 0;
+	}
+	else if (end_z_box >= m_num_sectors_z)
+	{
+		end_z_box = m_num_sectors_z - 1;
+	}
+	
+	// loop over the corresponding super sectors and add the sectors to the qualifying list
+	int qual_sector_idx = 0;
+	for (int i = start_x_box; i <= end_x_box; i++)
+	{
+		for (int j = start_z_box; j <= end_z_box; j++)
+		{
+			for (int k = 0; k < m_super_sector_list[i][j].m_NumCollSectors; k++)
+			{
+				Nx::CCollStatic* cs = m_super_sector_list[i][j].m_CollSectorList[k];
+				
+				if (cs->GetObjectFlags() & (mSD_NON_COLLIDABLE | mSD_KILLED)) continue;
+				
+				if (cs->GetSuperSectorID() == op_id) continue;
+				
+				QualCollSectors[qual_sector_idx++] = cs;
+				
+				cs->SetSuperSectorID(op_id);
+				
+				Dbg_MsgAssert(qual_sector_idx < (vMAX_QUAL_SECTORS*8/10),("Too many %d qualifying collision sectors",qual_sector_idx));						
+			}
+		}
+	}
+	
+	QualCollSectors[qual_sector_idx] = NULL;
+	
+	return static_cast< Nx::CCollStatic** >(QualCollSectors);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CCollStatic** Manager::GetIntersectingCollSectors( Mth::Line &line )
+{
+	
+	
+	int qual_sector_idx;
+	Mth::Line test_line;
+	Mth::Vector dir;
+	float x_offset, z_offset;
+	int start_x_box, start_z_box;
+	int end_x_box, end_z_box;
+	int temp,k;
+	int op_id;
+		
+	qual_sector_idx = 0;
+
+
+	// Extend the line a foot in each direction just to catch boundary conditions
+	dir = Mth::Vector( line.m_end[X] - line.m_start[X],
+					   line.m_end[Y] - line.m_start[Y],
+					   line.m_end[Z] - line.m_start[Z] );
+	dir.Normalize();
+	dir.Scale( COLL_LINE_EXTENSION );
+	test_line.m_start[X]	= line.m_start[X] - dir.GetX();
+	test_line.m_start[Y]	= line.m_start[Y] - dir.GetY();
+	test_line.m_start[Z]	= line.m_start[Z] - dir.GetZ();
+	test_line.m_end[X]		= line.m_end[X] + dir.GetX();
+	test_line.m_end[Y]		= line.m_end[Y] + dir.GetY();
+	test_line.m_end[Z]		= line.m_end[Z] + dir.GetZ();
+
+	// Figure out which super sector the start point is in
+	x_offset = test_line.m_start[X] - m_world_bbox.GetMin()[X];
+	z_offset = test_line.m_start[Z] - m_world_bbox.GetMin()[Z];
+
+	start_x_box = (int) ( x_offset / m_sector_width );
+	start_z_box = (int) ( z_offset / m_sector_depth );
+
+//#define DISABLE_SUPER_SECTORS
+#ifdef DISABLE_SUPER_SECTORS
+	start_x_box = start_z_box = 0;
+#endif // DISABLE_SUPER_SECTORS
+
+	// Some sanity checks
+	if( start_x_box < 0 )
+	{
+		start_x_box = 0;
+	}
+	else if( start_x_box >= m_num_sectors_x )
+	{
+		start_x_box = m_num_sectors_x - 1;
+	}
+
+	if( start_z_box < 0 )
+	{
+		start_z_box = 0;
+	}
+	else if( start_z_box >= m_num_sectors_z )
+	{
+		start_z_box = m_num_sectors_z - 1;
+	}
+
+	// Figure out which super sector the end point is in
+	x_offset = test_line.m_end[X] - m_world_bbox.GetMin()[X];
+	z_offset = test_line.m_end[Z] - m_world_bbox.GetMin()[Z];
+		
+	end_x_box = (int) ( x_offset / m_sector_width );
+	end_z_box = (int) ( z_offset / m_sector_depth );
+
+#ifdef DISABLE_SUPER_SECTORS
+	end_x_box = m_num_sectors_x - 1;
+	end_z_box = m_num_sectors_z - 1;
+#endif // DISABLE_SUPER_SECTORS
+
+	// Some sanity checks
+	if( end_x_box < 0 )
+	{
+		end_x_box = 0;
+	}
+	else if( end_x_box >= m_num_sectors_x )
+	{
+		end_x_box = m_num_sectors_x - 1;
+	}
+
+	if( end_z_box < 0 )
+	{
+		end_z_box = 0;
+	}
+	else if( end_z_box >= m_num_sectors_z )
+	{
+		end_z_box = m_num_sectors_z - 1;
+	}
+
+	// Organize vars for two-dimensional "for" loop
+	if( end_x_box < start_x_box )
+	{
+		temp = end_x_box;
+		end_x_box = start_x_box;
+		start_x_box = temp;
+	}
+
+	if( end_z_box < start_z_box )
+	{
+		temp = end_z_box;
+		end_z_box = start_z_box;
+		start_z_box = temp;
+	}
+
+	//Dbg_Message("SuperSector collision start(%d, %d) end(%d, %d)", start_x_box, start_z_box, end_x_box, end_z_box);
+	//Dbg_Message("SuperSector testline start(%f, %f) end(%f, %f)", test_line.m_start[X], test_line.m_start[Z], test_line.m_end[X], test_line.m_end[Z]);
+	//Dbg_Message("SuperSector world start(%f, %f) sector size(%f, %f)", m_world_bbox.inf.x, m_world_bbox.inf.z, m_sector_width, m_sector_depth);
+
+
+// Optimize for the case (95%) where start and end are the same
+// Just copy over the qualifying sectors pointers from one supersector
+// This makes the routine about 30% fater on average.  
+	if (start_x_box == end_x_box && start_z_box == end_z_box)
+	{
+		Sector * p_sec = &m_super_sector_list[start_x_box][start_z_box]; 
+		int num = p_sec->m_NumCollSectors;
+		Nx::CCollStatic** pp_cs = p_sec->m_CollSectorList; 
+		Nx::CCollStatic**  pp_qual = QualCollSectors;		
+//		printf ("%8d: %3d (%d,%d)\n",(int)Tmr::GetRenderFrame(),num,start_x_box, start_z_box);
+		for( k = 0; k < num; k++ )
+		{
+			Nx::CCollStatic* cs= *pp_cs++;
+			if ( !( cs->GetObjectFlags() & ( mSD_NON_COLLIDABLE | mSD_KILLED ) ) )
+			{
+				*pp_qual++ = cs;
+			}
+
+		}
+		*pp_qual = NULL;
+		return (Nx::CCollStatic**)QualCollSectors;
+	}
+	
+
+//	int checked = 0;
+//	int passed = 0;
+
+
+	op_id = GetNewSectorOperationId();
+		
+	// Add world sectors from these super sectors to the qualifying list
+	for( int i = start_x_box; i <= end_x_box; i++ )
+	{
+		for( int j = start_z_box; j <= end_z_box; j++ )
+		{
+			for( k = 0; k < m_super_sector_list[i][j].m_NumCollSectors; k++ )
+			{
+//				checked++;
+				Nx::CCollStatic* cs;
+				
+				cs = m_super_sector_list[i][j].m_CollSectorList[k];
+				
+				if ( !( cs->GetObjectFlags() & ( mSD_NON_COLLIDABLE | mSD_KILLED ) ) )
+				{
+					// If we haven't already added it, add it
+					if( cs->GetSuperSectorID() != op_id )
+					{
+						if (qual_sector_idx < vMAX_QUAL_SECTORS-1)
+						{
+//							passed++;
+							QualCollSectors[qual_sector_idx++] = cs;
+							// Mark it as having been added in this operation
+							cs->SetSuperSectorID(op_id);
+							
+						}
+						// normally we just return
+						// but in case someone does something that involves the whole
+						// world, we add this assertion...
+						Dbg_MsgAssert(qual_sector_idx < (vMAX_QUAL_SECTORS*8/10),("Too many %d qualifying collision sectors (%d,%d)-(%d,%d).\n  Is a non-playable portion of the level flagged as collidable? Maybe the clouds, or the sea?",
+						qual_sector_idx, start_x_box,start_z_box,end_x_box,end_z_box ));						
+					}
+				}
+			}
+		}
+	}        
+	
+	//printf ("(%d,%d) to (%d, %d), checked %d, returning %d\n",start_x_box,start_z_box,end_x_box,end_z_box,checked, passed);                              
+
+	QualCollSectors[qual_sector_idx] = NULL;
+	return (Nx::CCollStatic**)QualCollSectors;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CSector**	Manager::GetIntersectingWorldSectors( Mth::Line &line )
+{
+
+
+	Dbg_MsgAssert(0,("GetIntersectingWorldSectors is an old function"));	
+	return NULL;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SSec
+
+
+
+
+
diff --git a/Code/Sk/Engine/SuperSector.h b/Code/Sk/Engine/SuperSector.h
new file mode 100644
index 0000000..2397a97
--- /dev/null
+++ b/Code/Sk/Engine/SuperSector.h
@@ -0,0 +1,149 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			SSEC													**
+**																			**
+**	File name:		SuperSector.h											**
+**																			**
+**	Created: 		01/12/2001	-	spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__ENGINE_SUPERSECTOR_H
+#define	__ENGINE_SUPERSECTOR_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/////////////////////////////////////////////////////////////////////////
+// Forward declarations
+namespace Nx
+{
+class	CSector;
+class	CCollObj;
+class	CCollStatic;
+}
+
+
+namespace SSec
+{
+
+// Forward declaration
+class Manager;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class Sector
+{
+public:
+	Sector( void );
+	~Sector( void );
+
+private:
+	// Remove pointer out of sector array and shift the rest.  Doesn't update m_NumCollSectors!
+	bool				remove_collision(Nx::CCollStatic *p_coll);
+	bool				replace_collision(Nx::CCollStatic *p_coll, Nx::CCollStatic *p_replace_coll);
+	bool				has_collision(Nx::CCollStatic *p_coll);
+
+	Mth::CBBox			m_Bbox;
+	int					m_NumSectors;
+	int					m_NumCollSectors;
+	Nx::CSector**		m_SectorList;
+	Nx::CCollStatic**	m_CollSectorList;
+
+	friend Manager;
+};
+
+
+class Manager
+{
+public:
+	enum
+	{
+		NUM_PARTITIONS_X = 20,
+		NUM_PARTITIONS_Z = 20,
+	};
+
+	Nx::CSector**		GetIntersectingWorldSectors( Mth::Line &line );
+	Nx::CCollStatic**	GetIntersectingCollSectors( Mth::Line &line );
+	Nx::CCollStatic**	GetIntersectingCollSectors( Mth::CBBox &bbox );
+	void				GenerateSuperSectors( const Mth::CBBox& world_bbox );
+	void				AddCollisionToSuperSectors( Nx::CCollStatic *coll, int num_coll_sectors );
+	void				UpdateCollisionSuperSectors(Lst::Head &add_list,
+													Lst::Head &remove_list,
+													Lst::Head &update_list);
+	void				ClearCollisionSuperSectors();
+
+	Mth::CBBox *		GetWorldBBox( void ) { return &m_world_bbox; }
+
+private:
+	int					add_to_super_sector(Nx::CCollStatic *p_coll, SSec::Sector *p_super_sector, Lst::Head< Nx::CCollStatic > & add_list, int num_changes);
+	int					remove_from_super_sector(Nx::CCollStatic *p_coll, SSec::Sector *p_super_sector, Lst::Head< Nx::CCollStatic > & add_list, int num_changes);
+
+	Mth::CBBox			m_world_bbox;
+	float				m_sector_width;
+	float 				m_sector_depth;
+	int 				m_num_sectors_x;
+	int 				m_num_sectors_z;
+	Sector				m_super_sector_list[NUM_PARTITIONS_X][NUM_PARTITIONS_Z];
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+uint8		GetCurrentSectorOperationId( void );
+uint8		GetNewSectorOperationId( void );
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SSec
+
+#endif	// __ENGINE_SUPERSECTOR_H
\ No newline at end of file
diff --git a/Code/Sk/Engine/contact.cpp b/Code/Sk/Engine/contact.cpp
new file mode 100644
index 0000000..f723b2c
--- /dev/null
+++ b/Code/Sk/Engine/contact.cpp
@@ -0,0 +1,112 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Contact.cpp	  - Mick
+//
+// a contact is a point "on" a moving object
+// generally on the  surface of the object
+// and more generally at a position realative to it
+// it is responsible for maintaining changes in
+// position, due to changes on the object it is on
+
+
+#include 
+#include 
+#include 
+#include 
+
+						
+					
+CContact::CContact()
+{
+}
+
+CContact::CContact(Obj::CCompositeObject *p_moving_object )
+{
+
+	mp_moving_object = p_moving_object;
+//	m_object_id = mp_moving_object->GetID();
+	
+	m_object_last_matrix = mp_moving_object->GetMatrix();
+	m_object_last_pos = mp_moving_object->GetPos();
+	
+	m_movement.Set();
+	m_rotated = false;
+}
+
+
+// check to see if the object exists.									
+bool		CContact::ObjectExists()
+{
+	// With smart pointers, GetObject will just return NULL if the object has been deleted 
+	return (GetObject() != NULL);
+}
+
+// call this once per frame, to calculate the movement of the contact point
+bool		CContact::Update(const Mth::Vector &pos)
+{											
+	if (!mp_moving_object.Convert())
+	{
+		return false;
+	}
+	
+	m_movement = mp_moving_object->GetPos() - m_object_last_pos;
+	
+	// so far we have only accounted for the movement of the object's origin
+	// (mp_moving_object->GetPos()), and we need to see if the object has rotated
+	// which will give us an additional movement vector
+
+	if (m_object_last_matrix != mp_moving_object->GetMatrix())
+	{
+		m_rotated = true;
+//		printf ("Object has rotated\n");
+		// Get the position relative to the origin on the object
+		Mth::Vector old_offset = pos - m_object_last_pos;
+		Mth::Vector offset = old_offset;
+
+//		printf ("Offset (%.2f,%.2f,%.2f)\n",offset[X],offset[Y],offset[Z]);
+		
+		// transform by the inverse of the original matrix
+		Mth::Matrix back = m_object_last_matrix;		  
+		// just to be safe
+		back[W] = Mth::Vector(0,0,0,1);
+		back[X][W] = 0.0f;
+		back[Y][W] = 0.0f;
+		back[Z][W] = 0.0f;
+		back.InvertUniform();
+
+		// transform to the new matrix
+		m_rotation =  mp_moving_object->GetMatrix();
+		// just to be safe
+		m_rotation[W] = Mth::Vector(0,0,0,1);
+		m_rotation[X][W] = 0.0f;
+		m_rotation[Y][W] = 0.0f;
+		m_rotation[Z][W] = 0.0f;
+		
+		m_rotation *= back;		
+		
+		Mth::Vector new_offset = m_rotation.Transform(offset);
+
+//		printf ("new Offset (%.2f,%.2f,%.2f)\n",new_offset[X],new_offset[Y],new_offset[Z]);
+		
+		// Calculate the movment due to rotation
+		Mth::Vector	rotation_movement = (new_offset - old_offset);
+	
+		// add it to the movement, so it is included in the skater's movement
+		m_movement += rotation_movement;
+
+		m_object_last_matrix = mp_moving_object->GetMatrix();
+	
+	}
+	else
+	{
+		m_rotated = false;
+	}
+	
+	m_object_last_pos = mp_moving_object->GetPos();
+
+	return true;
+}
+
+												   
+
+
diff --git a/Code/Sk/Engine/contact.h b/Code/Sk/Engine/contact.h
new file mode 100644
index 0000000..08fa344
--- /dev/null
+++ b/Code/Sk/Engine/contact.h
@@ -0,0 +1,54 @@
+
+#ifndef	__ENGINE_CONTACT_H__
+#define	__ENGINE_CONTACT_H__
+
+#include 
+#include 
+#include 
+
+class	CContact
+{
+public:
+								CContact();
+								CContact(Obj::CCompositeObject *p_moving_object);
+
+	bool						ObjectExists();		// returns false if object has been deleted, or is "dead" or "inactive"
+	bool						Update(const Mth::Vector &pos);		// Update the position, return false if object no longer exists
+	const Mth::Vector		&	GetMovement() const;		// get movement vector of the contact point for this frame 	
+	const Mth::Matrix		&	GetRotation() const;		// get rotation matrix for this frame 	
+	bool						IsRotated() const;
+						   
+private:
+	Obj::CSmtPtr		mp_moving_object;
+	Mth::Matrix					m_object_last_matrix; 	// The object's last orientation
+	Mth::Vector					m_object_last_pos;		// The object's last position
+
+	Mth::Vector					m_movement;
+	Mth::Matrix					m_rotation;
+	
+	bool 						m_rotated;				// set if it was rotated
+
+public:
+	Obj::CCompositeObject*		GetObject() {return mp_moving_object.Convert();}		// return the object we are in contact with
+
+
+};
+
+inline const Mth::Vector		&	CContact::GetMovement() const
+{
+	return	m_movement;	
+}
+
+inline bool							CContact::IsRotated() const
+{
+	return	m_rotated;
+}
+
+inline const Mth::Matrix		&	CContact::GetRotation() const
+{
+	return	m_rotation;	
+
+}
+
+#endif
+
diff --git a/Code/Sk/Engine/feeler.cpp b/Code/Sk/Engine/feeler.cpp
new file mode 100644
index 0000000..82766d2
--- /dev/null
+++ b/Code/Sk/Engine/feeler.cpp
@@ -0,0 +1,414 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Feeler.cpp	  - Mick
+//
+// A feeler is a 3D line segment that can check for collision with
+// the world, and return information about the faces it collides with
+//
+// it's derived from the line type
+// so operations you can do with a line
+// can also be done with the feeler		  
+// (See geometry.cpp for line operations)
+
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+
+#define PRINT_TIMES 0
+						
+Nx::CCollCache *		CFeeler::sp_default_cache = NULL;
+
+					
+CFeeler::CFeeler()
+{
+	init();
+}
+
+CFeeler::CFeeler(const Mth::Vector &start, const Mth::Vector &end)
+{
+	m_start = start;
+	m_end = end;
+	m_col_data.surface.normal.Set(1.0, 1.0, 1.0);
+	init();
+}
+
+void CFeeler::init (   )
+{
+	// When we create a feeler
+	// we use the default "ignore" settings
+	// as they will be used the most
+	m_col_data.ignore_1 = mFD_NON_COLLIDABLE; 		// ignore NON_COLLIDABLES
+	m_col_data.ignore_0 = 0;						// don't ignore anything else
+	mp_callback = NULL;
+	mp_cache = NULL;
+
+	// Now un-initialized...
+	m_col_data.surface.normal.Set();
+	m_col_data.surface.point.Set();
+}
+
+void CFeeler::SetIgnore(uint16 ignore_1, uint16 ignore_0)
+{
+	m_col_data.ignore_1 = ignore_1;
+	m_col_data.ignore_0 = ignore_0;
+}
+
+
+void CFeeler::SetStart(const Mth::Vector &start)
+{
+	m_start = start;
+}
+
+void CFeeler::SetEnd(const Mth::Vector &end)
+{
+	m_end = end;
+}
+
+void CFeeler::SetLine(const Mth::Vector &start, const Mth::Vector &end)
+{
+	SetStart(start);
+	SetEnd(end);
+}
+
+							  
+
+// This callback gets called by the CLd:: collision code
+// (which was in turn a callback called from elsewhere)
+// and then this calls my Callback
+// we should try to simplyify this chain of callbacks
+// whilst still keeping platform and engine independence
+static void s_feeler_collide_callback(Nx::CollData* p_col_data)
+{
+	CFeeler *p_feeler = (CFeeler*) p_col_data->p_callback_data;
+	p_feeler->mp_callback(p_feeler); 
+}
+
+// This is the guts, the interface to the lower level collision code
+bool	CFeeler::GetCollision(bool movables, bool bfar)
+{
+#if PRINT_TIMES
+	static uint64 s_total_time = 0, s_num_collisions = 0;
+	uint64 start_time = Tmr::GetTimeInUSeconds();
+#endif
+	
+ #ifdef	__PLAT_NGPS__		
+	//	snProfSetRange( -1, (void*)0, (void*)-1);
+	//	snProfSetFlagValue(0x01);
+ #endif
+   
+	
+	Mth::Line is;
+
+	// just copy over the start and the end of the line directly.
+	is.m_start = m_start;
+	is.m_end = m_end;
+
+	m_col_data.p_callback_data = this;	// callback data is this current feeler
+
+	// initialize collision info
+	m_col_data.coll_found = false;
+	m_col_data.trigger = false;
+	m_col_data.script = 0;				// This is a checksum
+
+	m_col_data.mp_callback_object = NULL;
+						  
+	void *callback = 0;					
+	if (mp_callback)
+	{
+	   	callback = s_feeler_collide_callback;
+	}
+
+	Nx::CCollCache *p_cache = (mp_cache) ? mp_cache : sp_default_cache;
+
+	bool	collision;
+	mp_movable_collision_obj = NULL;
+	m_movable_collision_id = 0;
+	if (bfar)
+	{
+		collision = Nx::CCollObj::sFindFarStaticCollision( is, &m_col_data, callback, p_cache );
+	}
+	else
+	{
+		collision = Nx::CCollObj::sFindNearestStaticCollision( is, &m_col_data, callback, p_cache );
+	}
+
+	// Now see if we can find a closer movable
+	if (movables)
+	{
+		if (bfar)
+		{
+			mp_movable_collision_obj = Nx::CCollObj::sFindFarMovableCollision( is, &m_col_data, callback, p_cache );
+		}
+		else
+		{
+			mp_movable_collision_obj = Nx::CCollObj::sFindNearestMovableCollision( is, &m_col_data, callback, p_cache );
+		}
+		if (mp_movable_collision_obj)
+		{
+			m_movable_collision_id = mp_movable_collision_obj->GetID();
+		}
+	}
+
+	m_dist = m_col_data.dist;
+	m_point = m_col_data.surface.point;
+	m_normal = m_col_data.surface.normal;
+
+#if 0
+	if (collision)
+	{
+		Gfx::AddDebugLine(m_point,m_point + (m_normal * 100.0f),coll_color,10);
+	}
+
+	//static int num_print = 1;
+	if (/*collision && (num_print++ % 100) ==*/ 0)
+	{
+		Dbg_Message("\nStart point (%f, %f, %f)", m_start[X], m_start[Y], m_start[Z]);
+		Dbg_Message("End point (%f, %f, %f)", m_end[X], m_end[Y], m_end[Z]);
+
+		Dbg_Message("Collision Distance %f", m_dist);
+		Dbg_Message("Collision point (%f, %f, %f)", m_point[X], m_point[Y], m_point[Z]);
+		Dbg_Message("Collision normal (%f, %f, %f)", m_normal[X], m_normal[Y], m_normal[Z]);
+	}
+#endif
+
+
+ #ifdef	__PLAT_NGPS__		
+	//	snProfSetRange( 4, (void*)NULL, (void*)-1);
+ #endif		
+
+#if PRINT_TIMES
+	uint64 end_time = Tmr::GetTimeInUSeconds();
+	s_total_time += end_time - start_time;
+
+	if (++s_num_collisions >= 1000)
+	{
+		Dbg_Message("Feeler time %d us", s_total_time);
+		s_total_time = s_num_collisions = 0;
+	}
+#endif
+
+	
+	return collision || (mp_movable_collision_obj != NULL);
+
+
+
+}
+
+//
+// Just checks the movable collisions
+//
+bool CFeeler::GetMovableCollision(bool bfar)
+{
+	Mth::Line is;
+
+	// just copy over the start and the end of the line directly.
+	is.m_start = m_start;
+	is.m_end = m_end;
+
+////////////////////////////////////////////////////////////
+// Clear data than might have been set on a previous call
+// (change suggested by Andre at LF, 4/28/03)
+	m_col_data.coll_found = false;
+	m_col_data.trigger = false;
+	m_col_data.script = 0;				// This is a checksum
+	m_col_data.mp_callback_object = NULL;
+// End of change
+////////////////////////////////////////////////////////////
+
+
+	m_col_data.p_callback_data = this;	// callback data is this current feeler
+						  
+	void *callback = 0;					
+	if (mp_callback)
+	{
+	   	callback = s_feeler_collide_callback;
+	}
+
+	Nx::CCollCache *p_cache = (mp_cache) ? mp_cache : sp_default_cache;
+
+	if (bfar)
+	{
+		mp_movable_collision_obj = Nx::CCollObj::sFindFarMovableCollision( is, &m_col_data, callback, p_cache );
+	}
+	else
+	{
+		mp_movable_collision_obj = Nx::CCollObj::sFindNearestMovableCollision( is, &m_col_data, callback, p_cache );
+	}
+	
+	if (mp_movable_collision_obj)
+	{
+		m_movable_collision_id = mp_movable_collision_obj->GetID();
+	}
+
+	m_dist = m_col_data.dist;
+	m_point = m_col_data.surface.point;
+	m_normal = m_col_data.surface.normal;
+
+	return mp_movable_collision_obj != NULL;
+}
+
+// Check to see that if we have collision, that we have enough data to
+// calculate the brightness
+bool	CFeeler::IsBrightnessAvailable()
+{
+	if (m_col_data.coll_found)
+	{
+		Dbg_MsgAssert(m_col_data.p_coll_object, ("IsBrightnessAvailable(): collision w/o a collsion object"));
+		return m_col_data.p_coll_object->IsTriangleCollision();
+	}
+
+	return true;
+}
+
+// get the brightness 0.0 .. 1.0 of the point of the last
+// collision, based on the RGB values of the triangle, interpolatd to
+// the collision point
+float	CFeeler::GetBrightness()
+{
+	float brightness;
+	
+	struct SGPoint
+	{
+		Mth::Vector	p;
+		float		v;			// Brightness in range [0.0, 1.0].
+	};
+
+	uint32	i;
+	SGPoint	point[3];
+
+	
+	// If no collision found, set default brightness.
+	if( !m_col_data.coll_found )
+	{
+		brightness = 0.5f;
+	}
+	else
+	{
+		Nx::CCollObj*				p_object	= m_col_data.p_coll_object;
+		const Nx::SCollSurface*		p_face		= &m_col_data.surface;
+		uint32						face_index	= p_face->index;
+		//Cld::CCollSector::SFace*	p_polygon	= p_object->mp_faces + face_index;
+
+		// Extract contact point.
+		float x = p_face->point[X];
+		float y = p_face->point[Y];
+		float z = p_face->point[Z];
+
+		Dbg_MsgAssert(p_object->IsTriangleCollision(),("Not triangle collision !!!"));
+
+		Nx::CCollObjTriData *p_tri_data=p_object->GetGeometry();
+		Dbg_MsgAssert(p_tri_data,("NULL p_tri_data"));
+
+		// Get all the vertices into a nice simple array of (x,y,z,v).
+		for( i = 0; i < 3; ++i )
+		{
+			//uint16 *vert_idxs = p_object->GetFaceVertIndicies(face_index);
+
+			const Mth::Vector & vertex = p_object->GetVertexPos(p_tri_data->GetFaceVertIndex(face_index,i));
+
+			// Check that the vertices match.
+			point[i].p.Set( vertex.GetX() - x, vertex.GetY() - y, vertex.GetZ() - z );
+
+			// Here is where the conversion is made from values [0,255] to [0,1].
+#ifdef __PLAT_NGC__
+			point[i].v	= (float)( p_tri_data->GetVertexIntensity(face_index, i)) * ( 1.0f / 255.0f );
+#else
+			point[i].v	= (float)( p_tri_data->GetVertexIntensity(p_tri_data->GetFaceVertIndex(face_index, i)) ) * ( 1.0f / 255.0f );
+#endif		// __PLAT_NGC__
+		}
+
+		// Calculate the intersection of the line from Point 0 through the origin, with the line 1-2.
+		float		mua, mub;
+		Mth::Line	line1( point[0].p, Mth::Vector( 0.0f, 0.0f, 0.0f ));
+		Mth::Line	line2( point[1].p, point[2].p );
+		Mth::Vector	a, b;
+
+		Mth::LineLineIntersect( line1, line2, &a, &b, &mua, &mub, false );
+
+		float l0, l1;
+		Mth::Vector length;
+
+		// First interpolation a = i( 1, 2 ).
+		length				= a - point[1].p;
+		l0					= length.Length();
+		length				= point[2].p - point[1].p;
+		l1					= length.Length();
+		float va			= point[1].v + (( point[2].v - point[1].v ) * ( l0 / l1 ));
+
+		// Second interpolation b = i( 0, a ).
+		l0					= point[0].p.Length();
+		length				= a - point[0].p;
+		l1					= length.Length();
+		brightness	= point[0].v + (( va - point[0].v ) * ( l0 / l1 ));
+	}
+	
+#	ifdef __PLAT_XBOX__
+	// Xbox vertex colors run on a different scale to Ps2.
+	brightness *= 0.5f;
+#	endif
+	
+	return brightness;
+}
+
+
+bool CFeeler::GetCollision(const Mth::Vector &start, const Mth::Vector &end, bool movables)
+{
+	SetLine(start,end);
+	return GetCollision(movables);
+}
+
+bool CFeeler::GetMovableCollision(const Mth::Vector &start, const Mth::Vector &end)
+{
+	SetLine(start,end);
+	return GetMovableCollision();
+}
+
+bool	CFeeler::IsMovableCollision()
+{
+	if (mp_movable_collision_obj.Convert())
+	{
+		return true;
+	}
+
+// if it was movable, and now we have no movable object
+// then kill the id and the node name
+// so we don't get stung by the snap to ground assuming they are still valid....
+	if (m_movable_collision_id)
+	{
+		m_movable_collision_id = 0;
+		m_col_data.node_name = 0;		// we are SO like not colliding with anything!
+	}
+	return false;
+}
+
+Obj::CCompositeObject *		CFeeler::GetMovingObject()	// { return mp_movable_collision_obj;}
+{
+	return mp_movable_collision_obj.Convert();
+}
+
+// if we are in the middle of a callback, then
+// we need to look at the callback object													   
+Obj::CCompositeObject *		CFeeler::GetCallbackObject() const
+{
+	return m_col_data.mp_callback_object;
+}
+
+
+void CFeeler::DebugLine(int r, int g, int b, int num_frames)
+{	
+	Gfx::AddDebugLine( m_start, m_end, MAKE_RGB( r, g, b ),MAKE_RGB( r,g, b ), num_frames );
+}
+
+
diff --git a/Code/Sk/Engine/feeler.h b/Code/Sk/Engine/feeler.h
new file mode 100644
index 0000000..f2c9294
--- /dev/null
+++ b/Code/Sk/Engine/feeler.h
@@ -0,0 +1,197 @@
+
+#ifndef	__ENGINE_FEELER_H__
+#define	__ENGINE_FEELER_H__
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+#include 
+#include 
+#include 
+#include 
+//#include 		// needed for smart pointer destructor in feeler, dammit
+#include 
+#include 
+
+namespace Nx
+{
+	class CCollCache;
+}
+
+class CFeeler;
+
+typedef CFeeler CLineFeeler;
+
+typedef	void (*FEELER_CB)(CFeeler*);		// feeler callback
+
+class	CFeeler : public Mth::Line
+{
+public:
+	CFeeler();
+	CFeeler(const Mth::Vector &start, const Mth::Vector &end);
+
+	void 							SetIgnore(uint16 ignore_1, uint16 ignore_0);
+	void 							SetLine(const Mth::Vector &start, const Mth::Vector &end);
+	void 							SetStart(const Mth::Vector &start);	
+	void 							SetEnd(const Mth::Vector &end);
+
+	void							DebugLine(int r=255, int g=255, int b=255, int num_frames = 0);
+	
+	bool							GetCollision(const Mth::Vector &start, const Mth::Vector &end, bool movables = true);
+	bool							GetCollision(bool movables = true, bool far = false);
+	inline bool						GetFarCollision(bool movables = true) {return GetCollision(movables, true);};
+	bool							GetMovableCollision(const Mth::Vector &start, const Mth::Vector &end);
+	bool							GetMovableCollision(bool far = false);
+	inline bool						GetFarMovableCollision() {return GetMovableCollision(true);};
+	bool							IsBrightnessAvailable();
+	float							GetBrightness();
+	
+	float							GetDist() const;
+	const Mth::Vector			&	GetPoint() const;
+	const Mth::Vector			&	GetNormal() const;
+	int								GetFaceIndex() const;
+	
+	void 							SetCallback(FEELER_CB p_callback);
+	void 							SetCallbackData(void *p_callback_data);
+	void * 							GetCallbackData() const;
+
+	void							SetCache(Nx::CCollCache *p_cache);
+	void							ClearCache();
+
+	uint16 							GetFlags() const;
+	ETerrainType 					GetTerrain() const;
+	bool							IsMovableCollision(); 
+	Nx::CCollObj *	 				GetSector() const;		// aieee!!!!!!
+	Obj::CCompositeObject *			GetMovingObject();
+	Obj::CCompositeObject *			GetCallbackObject() const;
+	bool 							GetTrigger() const;
+	uint32 							GetNodeChecksum() const;
+	uint32 							GetScript() const;
+	void 							SetScript(uint32 script);
+	void 							SetTrigger(bool trigger);
+
+	static void						sSetDefaultCache(Nx::CCollCache *p_cache);
+	static void						sClearDefaultCache();
+
+	// feeler callback probably should not be public						   
+	FEELER_CB			mp_callback;	   // a callback called every frame
+						   
+private:
+	void							init (   );
+	
+// for now, our implmentation will just copy over the relevent things from rw		
+// as needed
+// keep m_col_data private, and eventually we can get rid of it	
+	Nx::CollData 		m_col_data;	 			// 
+	float				m_dist;
+	Mth::Vector			m_point;
+	Mth::Vector			m_normal;
+
+	Obj::CSmtPtr mp_movable_collision_obj;	//  set if last collision was with a movable
+	uint32 				m_movable_collision_id;		// id of this object, so we can check if it is dead.
+	
+	void 		*		mp_callback_data;		// 	pointer to some data the callback can use
+
+	Nx::CCollCache *	mp_cache;
+
+	static Nx::CCollCache *	sp_default_cache;
+};
+
+inline float	CFeeler::GetDist() const
+{
+	return m_dist;
+}
+
+inline const Mth::Vector &CFeeler::GetPoint() const
+{
+	return m_point;
+}
+
+inline const Mth::Vector &CFeeler::GetNormal() const
+{
+	return m_normal;
+}
+
+inline int		CFeeler::GetFaceIndex() const
+{
+	return m_col_data.surface.index;
+}
+
+inline uint16	CFeeler::GetFlags() const
+{
+	return m_col_data.flags;
+}
+
+inline ETerrainType CFeeler::GetTerrain() const
+{
+	return m_col_data.terrain;
+}
+
+inline Nx::CCollObj* CFeeler::GetSector() const // aieee!!!!!!
+{
+	return m_col_data.p_coll_object;
+}
+
+inline bool		CFeeler::GetTrigger() const
+{
+	return m_col_data.trigger;
+}
+
+inline uint32	CFeeler::GetNodeChecksum() const
+{
+	return m_col_data.node_name;
+}
+
+inline uint32	CFeeler::GetScript() const
+{
+	return m_col_data.script;	
+}
+
+inline void		CFeeler::SetScript(uint32 script)
+{
+	m_col_data.script = script; 
+}
+
+inline void		CFeeler::SetTrigger(bool trigger)
+{
+	m_col_data.trigger = trigger; 
+}
+
+inline void		CFeeler::SetCallback(FEELER_CB p_callback)
+{
+	mp_callback = p_callback;
+}
+
+inline void		CFeeler::SetCallbackData(void *p_callback_data)
+{
+	mp_callback_data = p_callback_data;
+}
+
+inline void *		CFeeler::GetCallbackData() const
+{
+	return mp_callback_data;
+}
+
+inline void		CFeeler::SetCache(Nx::CCollCache *p_cache)
+{
+	mp_cache = p_cache;
+}
+
+inline void		CFeeler::ClearCache()
+{
+	mp_cache = NULL;
+}
+
+inline void		CFeeler::sSetDefaultCache(Nx::CCollCache *p_cache)
+{
+	sp_default_cache = p_cache;
+}
+
+inline void		CFeeler::sClearDefaultCache()
+{
+	sp_default_cache = NULL;
+}
+
+#endif
diff --git a/Code/Sk/Engine/sounds.cpp b/Code/Sk/Engine/sounds.cpp
new file mode 100644
index 0000000..6763a47
--- /dev/null
+++ b/Code/Sk/Engine/sounds.cpp
@@ -0,0 +1,215 @@
+/*
+	This is just a database for arrays of skater sounds on different terrains.
+	
+	For each type of sound in sounds.h (like grind, land, slide, wheel roll, etc...)
+	there is an array of sounds to play, one for each possible terrain type.
+	
+	A terrain type of zero indicates that the sound is the default (for surfaces not
+	defined in the list).
+*/
+
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+						   
+#include 
+#include 
+#include 
+#include 
+
+
+
+namespace Sk3Sfx
+{
+
+
+
+DefineSingletonClass( CSk3SfxManager, "Sk3 Sound FX" );
+
+CSk3SfxManager::CSk3SfxManager( void )
+{
+	
+	Reset( );
+}
+
+CSk3SfxManager::~CSk3SfxManager( void )
+{
+	
+}
+
+void CSk3SfxManager::Reset( void )
+{
+	
+	int i;
+	for ( i = 0; i < vNUM_SOUND_TYPES; i++ )
+	{
+		mNumEntries[ i ] = 0;
+	}
+} // end of Reset( )
+
+// Sound FX for the level...
+/*	The surface flag indicates which surface the skater is currently on (grass, cement, wood, metal)
+	whichSound is the checksum from the name of the looping sound (should be loaded using LoadSound
+		in the script file for each level)
+	whichArray indicates whether this sound belongs in the list of wheels rolling sounds, or
+		grinding sounds, etc...
+*/
+void CSk3SfxManager::SetSkaterSoundInfo( int surfaceFlag, uint32 whichSound, int whichArray,
+	float maxPitch, float minPitch, float maxVol, float minVol )
+{
+	
+	if (Sfx::NoSoundPlease()) return;
+	
+	// must initialize PInfo!
+	int i;
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+
+	if ( NULL == sfx_manager->GetWaveTableIndex( whichSound ) )
+	{
+		Dbg_MsgAssert( 0,( "Terrain sound not loaded! surface %d sound %s checksum %d soundType %d",
+			surfaceFlag, Script::FindChecksumName( whichSound ), whichSound, whichArray ));
+		return;
+	}
+	int numEntries = mNumEntries[ whichArray ];
+	SkaterSoundInfo	*pArray = mSoundArray[ whichArray ];
+	SkaterSoundInfo *pInfo = NULL;
+	
+	for ( i = 0; i < numEntries; i++ )
+	{
+		if ( pArray[ i ].surfaceFlag == surfaceFlag )
+		{
+			Dbg_Message( "Re-defining soundtype %d for surfaceFlag %d", whichArray, surfaceFlag );
+			pInfo = &pArray[ i ];
+			break;
+		}
+	}
+	if ( !pInfo )
+	{
+		pInfo = &pArray[ mNumEntries[ whichArray ] ];
+		mNumEntries[ whichArray ] += 1;
+		Dbg_MsgAssert( mNumEntries[ whichArray ] < vMAX_NUM_ENTRIES,( "Array too small type %d.  Increase MAX_NUM_ENTRIES.", whichArray ));
+	}
+	
+	Dbg_MsgAssert( pInfo,( "Please fire Matt immediately after kicking him in the nuts." ));
+	
+	// surfaceFlag of zero will be used for the default
+	pInfo->surfaceFlag = surfaceFlag;
+	// if soundChecksum is zero, no sound will play on this surface.
+	pInfo->soundChecksum = whichSound;
+	pInfo->maxPitch = maxPitch;
+	pInfo->minPitch = minPitch;
+	pInfo->maxVol = maxVol;
+	pInfo->minVol = minVol;
+
+} // end of SetSkaterSoundInfo( )
+
+SkaterSoundInfo	*CSk3SfxManager::GetSkaterSoundInfo( int surfaceFlag, int whichArray )
+{
+	
+	if (Sfx::NoSoundPlease()) return NULL;
+	
+	
+	int numEntries = 0;
+	numEntries = mNumEntries[ whichArray ];
+	SkaterSoundInfo	*pArray = mSoundArray[ whichArray ];
+	Dbg_MsgAssert( pArray,( "Fire Matt please." ));
+	int i;
+	for ( i = 0; i < numEntries; i++ )
+	{
+		if ( pArray[ i ].surfaceFlag == surfaceFlag )
+		{
+			return ( &pArray[ i ] );
+		}
+	}
+	// couldn't find the surface flag in the table... return the default:
+	for ( i = 0; i < numEntries; i++ )
+	{
+		if ( !pArray[ i ].surfaceFlag )
+		{
+			return ( &pArray[ i ] );
+		}
+	}
+
+	return ( NULL );
+} // end of GetSkaterSoundInfo( )
+
+void CSk3SfxManager::PlaySound( int whichArray, int surfaceFlag, const Mth::Vector &pos, float volPercent,
+								bool propogate )
+{
+	if (Sfx::NoSoundPlease()) return;
+	
+
+	Replay::WriteSkaterSoundEffect(whichArray,surfaceFlag,pos,volPercent);
+	
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+	GameNet::Manager * gamenet_manager = GameNet::Manager::Instance();
+    
+	SkaterSoundInfo	*pInfo = GetSkaterSoundInfo( surfaceFlag, whichArray );
+	if ( !pInfo )
+	{
+		// no sounds are supposed to be played for this surface on this transition:
+		return;
+	}
+
+	if( propogate )
+	{
+		Net::Client* client;
+		GameNet::MsgPlaySound msg;
+		Net::MsgDesc msg_desc;
+
+		client = gamenet_manager->GetClient( 0 );
+		Dbg_Assert( client );
+
+		msg.m_WhichArray = (char) whichArray;
+		msg.m_SurfaceFlag = (char) surfaceFlag;
+		msg.m_Pos[0] = (short) pos[X];
+		msg.m_Pos[1] = (short) pos[Y];
+		msg.m_Pos[2] = (short) pos[Z];
+		msg.m_VolPercent = (char) volPercent;
+
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgPlaySound );
+		msg_desc.m_Id = GameNet::MSG_ID_PLAY_SOUND;
+		client->EnqueueMessageToServer( &msg_desc );
+	}
+
+//	float volL, volR;
+	Sfx::sVolume vol;
+//	sfx_manager->SetVolumeFromPos( &volL, &volR, pos, sfx_manager->GetDropoffDist( pInfo->soundChecksum ) );
+	sfx_manager->SetVolumeFromPos( &vol, pos, sfx_manager->GetDropoffDist( pInfo->soundChecksum ));
+
+	// Adjust volume according to speed.
+	volPercent = GetVolPercent( pInfo, volPercent );
+//	volL = PERCENT( volL, volPercent );
+//	volR = PERCENT( volR, volPercent );
+	vol.PercentageAdjustment( volPercent );
+
+//	sfx_manager->PlaySound( pInfo->soundChecksum, volL, volR );
+	sfx_manager->PlaySound( pInfo->soundChecksum, &vol );
+}
+
+// set the volume according to the range specified by the designers...
+float CSk3SfxManager::GetVolPercent( SkaterSoundInfo *pInfo, float volPercent, bool clipToMaxVol )
+{
+	
+	Dbg_MsgAssert( pInfo,(( "Fire whoever called this function with this nonsense." )));
+	if ( !( ( pInfo->minVol == 0.0f ) && ( pInfo->maxVol == 100.0f ) ) )
+	{
+		volPercent = ( pInfo->minVol + PERCENT( ( pInfo->maxVol - pInfo->minVol ), volPercent ) );
+	}
+	
+	if ( clipToMaxVol )
+	{
+		if ( volPercent > pInfo->maxVol )
+			volPercent = pInfo->maxVol;
+	}
+	return ( volPercent );
+}
+
+} // namespace sfx
diff --git a/Code/Sk/Engine/sounds.h b/Code/Sk/Engine/sounds.h
new file mode 100644
index 0000000..c52ede9
--- /dev/null
+++ b/Code/Sk/Engine/sounds.h
@@ -0,0 +1,103 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate Module (SKATE) 									**
+**																			**
+**	File name:		sk/engine/sounds.h										**
+**																			**
+**	Created by:		Matt Jan. 2001											**
+**																			**
+**	Description:	Lists of soundFX for terrains, etc...					**
+**																			**
+*****************************************************************************/
+
+#ifndef __GEL_SK_SOUNDS_H
+#define __GEL_SK_SOUNDS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+namespace Sk3Sfx
+{
+
+
+
+
+struct SkaterSoundInfo
+{
+	int		surfaceFlag;
+	uint32	soundChecksum;
+	float		maxPitch;
+	float		minPitch;
+	float		maxVol;
+	float		minVol;
+};
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class  CSk3SfxManager  : public Spt::Class
+{
+	
+	DeclareSingletonClass( CSk3SfxManager );
+
+public:
+	// these sound trigger values are also used to
+	// communicate with the sounds.cpp module and scripted
+	// soundfx for jumping/landing... if changed please
+	// change skater_sfx.q also:
+	enum
+	{
+		vTABLE_WHEELROLL,
+		vTABLE_GRIND, // on a rail...
+		vTABLE_JUMP,
+		vTABLE_LAND,
+		vTABLE_BONK,
+		vTABLE_GRINDJUMP,
+		vTABLE_GRINDLAND,
+		vTABLE_SLIDE, // on a rail
+		vTABLE_SLIDEJUMP,
+		vTABLE_SLIDELAND,
+		vTABLE_CESS,
+		vNUM_SOUND_TYPES,
+	};
+	
+	CSk3SfxManager::CSk3SfxManager( void );
+	CSk3SfxManager::~CSk3SfxManager( void );
+
+	void			Reset( void );
+	void			SetSkaterSoundInfo( int surfaceFlag, uint32 whichSound,
+									int whichArray, float maxPitch, float minPitch, float maxVol, float minVol );
+	SkaterSoundInfo	*GetSkaterSoundInfo( int surfaceFlag, int whichArray );
+
+	float			GetVolPercent( SkaterSoundInfo *pInfo, float volPercent, bool clipToMaxVol = false );
+
+	void 			PlaySound( int whichArray, int surfaceFlag, const Mth::Vector &pos, float volPercent,
+							   bool propogate = true );
+
+private:
+	
+	enum
+	{
+		vMAX_NUM_ENTRIES  = 64,
+	};
+
+	int mNumEntries[ vNUM_SOUND_TYPES ];
+	SkaterSoundInfo mSoundArray[ vNUM_SOUND_TYPES ][ vMAX_NUM_ENTRIES ];
+};
+
+}  // namespace Sfx
+
+#endif
diff --git a/Code/Sk/GameNet/ExportMsg.h b/Code/Sk/GameNet/ExportMsg.h
new file mode 100644
index 0000000..c791dbc
--- /dev/null
+++ b/Code/Sk/GameNet/ExportMsg.h
@@ -0,0 +1,105 @@
+#ifndef __ENGINE_EXPORTMSG_H__
+#define	__ENGINE_EXPORTMSG_H__
+
+#include 
+
+namespace Net
+{
+
+#define	vSERVER_IP_VARIABLE	"VIEWER_IP"
+#define vXBOX_SERVER_IP_VARIABLE "XBOX_VIEWER_IP"
+#define vNGC_SERVER_IP_VARIABLE "NGC_VIEWER_IP"
+
+enum
+{
+	vEXPORT_COMM_PORT = 10000,
+};	
+
+enum
+{	
+	vMSG_ID_QUICKVIEW		= 32,	//	32 is the first available user-defined message id
+	vMSG_ID_UPDATE_MATERIAL,		//	New material properties
+	vMSG_ID_REMOTE_Q,
+	vMSG_ID_VIEWOBJ_LOAD_MODEL,
+	vMSG_ID_VIEWOBJ_UNLOAD_MODEL,
+	vMSG_ID_VIEWOBJ_SET_ANIM,
+	vMSG_ID_VIEWOBJ_SET_ANIM_SPEED,
+	vMSG_ID_VIEWOBJ_INCREMENT_FRAME,
+	vMSG_ID_VIEWOBJ_SET_ANIM_FILE,
+	vMSG_ID_VIEWOBJ_SET_CAMANIM_FILE,
+	vMSG_ID_INCREMENTAL_UPDATE,
+	vMSG_ID_RUN_SCRIPT_COMMAND,
+	vMSG_ID_VIEWOBJ_PREVIEW_SEQUENCE,
+};
+
+class MsgViewObjLoadModel
+{
+public:
+	char	m_ModelName[128];
+	uint32	m_AnimScriptName;
+	uint32	m_SkeletonName;
+};
+
+class MsgViewObjSetAnimSpeed
+{
+public:
+	float	m_AnimSpeed;
+};
+
+class MsgViewObjSetAnim
+{
+public:
+	uint32	m_AnimName;
+};
+
+class MsgViewObjIncrementFrame
+{
+public:
+	bool	m_Forwards;
+};				   
+
+class MsgViewObjSetAnimFile
+{
+public:
+	char    m_Filename[128];
+	uint32  m_checksum;
+};
+
+class MsgViewObjSetCamAnimFile
+{
+public:
+	char    m_Filename[128];
+	uint32  m_checksum;
+};
+
+class MsgQuickview
+{
+public:
+	char	m_Filename[128];
+	char	m_UpdateFilename[128];
+};
+
+class MsgMaterialUpdate
+{
+public:
+	unsigned long	MaterialChecksum;
+	int				m_BlendMode;
+	int				m_FixedAlpha;
+	int				m_MappingMode;		// Explicit or procedural (eg. environment-mapping)	
+	int				m_MinFilteringMode;	// Point/Bi-linear
+	int				m_MagFilteringMode;	// Point/Bi-linear/Tri-linear
+	bool			m_UVWibbleEnabled;		
+	float			m_UVel;
+	float			m_VVel;	
+	float			m_UAmplitude;
+	float			m_VAmplitude;
+	float			m_UPhase;
+	float			m_VPhase;
+	float			m_UFrequency;
+	float			m_VFrequency;
+	float			m_MipMapK;
+	int				m_MipMapL;	
+};
+
+}
+#endif	// __ENGINE_EXPORTMSG_H__
\ No newline at end of file
diff --git a/Code/Sk/GameNet/GameHandler.cpp b/Code/Sk/GameNet/GameHandler.cpp
new file mode 100644
index 0000000..1e8d7ef
--- /dev/null
+++ b/Code/Sk/GameNet/GameHandler.cpp
@@ -0,0 +1,3624 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate3													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		GameHandler.cpp											**
+**																			**
+**	Created by:		08/09/01	-	spg										**
+**																			**
+**	Description:	Game-Side Network Handlers								**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 		   // 	 various high level network game things
+
+#ifdef __PLAT_NGPS__
+#include 
+#include 
+#include 
+#endif
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vTIME_BEFORE_CHANGING_LEVEL		Tmr::Seconds( 3 )
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static Tmr::Time	s_time_change_level;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Handler Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/* The server has told us to create a new skater and associate it */
+/* with a player                                                  */
+/******************************************************************/
+
+int Manager::s_handle_new_player( Net::MsgHandlerContext* context )
+{
+	MsgNewPlayer* msg;
+	Manager* gamenet_man;
+	
+	msg = (MsgNewPlayer *) context->m_Msg;
+	gamenet_man = (Manager *) context->m_Data;
+
+	NewPlayerInfo new_player;
+
+	strcpy( new_player.Name, msg->m_Name );
+	new_player.ObjID = msg->m_ObjId;
+	new_player.Conn = NULL;
+	new_player.Flags = msg->m_Flags;
+	new_player.Team = msg->m_Team;
+	new_player.Profile = msg->m_Profile;
+	new_player.Rating = msg->m_Rating;
+	new_player.Score = msg->m_Score;
+	new_player.VehicleControlType = msg->m_VehicleControlType;
+
+	if( new_player.Flags & PlayerInfo::mLOCAL_PLAYER )
+	{
+		Dbg_Message( "(%d) Got local new player with ID %d!\n", context->m_App->m_FrameCounter,
+															msg->m_ObjId );
+	}
+	else
+	{
+		Dbg_Message( "(%d) Got new player with ID %d!\n", context->m_App->m_FrameCounter,
+															msg->m_ObjId );
+	}
+
+	// Observers and local players do not receive their own appearance data
+	if( !( new_player.Flags & ( PlayerInfo::mOBSERVER | PlayerInfo::mLOCAL_PLAYER )))
+	{
+		// GJ:  the appearance is transmitted inside the message,
+		// so copy it to the "new_player" structure
+		new_player.mpSkaterProfile->ReadFromBuffer( msg->m_AppearanceData );
+	}
+	
+	// If this is all part of our join packet, just load players immediately instead of deferring it
+	if( gamenet_man->ReadyToPlay() == false )
+	{
+		Dbg_Printf( "Calling DeferredNewPlayer with no wait\n" );
+		gamenet_man->DeferredNewPlayer( &new_player, 0 );
+	}
+	else
+	{
+		gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC("net_message_new_player"), 
+											new_player.Name );
+
+		gamenet_man->DeferredNewPlayer( &new_player );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_wait_n_seconds( Net::MsgHandlerContext* context )
+{
+	char *num_seconds = (char*) context->m_Msg;
+
+	context->m_Conn->UpdateCommTime( Tmr::Seconds( *num_seconds ));
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_player_info_ack_req( Net::MsgHandlerContext* context )
+{
+	MsgIntInfo* msg;
+	Manager* gamenet_man;
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Net::MsgDesc msg_desc;
+	int i;
+
+	Dbg_Printf( "Handling PlayerInfo ack request\n" );
+	// At this point, we've received the batch of player info's representing the players
+	// that were in the game at the time of our join. Advance our state machine
+	gamenet_man = (Manager *) context->m_Data;
+
+	gamenet_man->SetJoinState( GameNet::vJOIN_STATE_GOT_PLAYERS );
+    
+	msg = (MsgIntInfo*) context->m_Msg;
+
+	msg_desc.m_Id = MSG_ID_PLAYER_INFO_ACK;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+
+	context->m_App->EnqueueMessageToServer( &msg_desc );
+
+	if( !gamenet_man->OnServer())
+	{
+		for( i = 0; i < 2; i++ )
+		{
+			Net::Client* client;
+	
+			if(( client = gamenet_man->GetClient( i )))
+			{
+				skate_mod->AddNetworkMsgHandlers( client, i );
+			}
+		}
+	}
+                                                            
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_player_info_ack( Net::MsgHandlerContext* context )
+{   
+	Net::Manager * net_man = Net::Manager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	PlayerInfo* player;
+	Manager* gamenet_man;
+
+	gamenet_man = (Manager*) context->m_Data;
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player )
+	{
+		// tell them what level to load
+		if( !( player->m_flags.TestMask( PlayerInfo::mHAS_PLAYER_INFO )))
+		{
+			Lst::Search< TriggerEvent > trigger_sh;
+			TriggerEvent* trigger;
+			MsgStartInfo start_info_msg;
+			Net::MsgDesc start_desc;
+			PlayerInfo* king;
+			MsgReady ready_msg;
+		
+			ready_msg.m_Time = Tmr::GetTime();
+	
+			player->m_flags.SetMask( PlayerInfo::mHAS_PLAYER_INFO );
+			
+			start_info_msg.m_MaxPlayers = gamenet_man->GetMaxPlayers();
+			if( player->IsLocalPlayer())
+			{
+				gamenet_man->SetReadyToPlay(true);
+			}
+			else
+			{
+				// If the game is over, just tell the client to join in net_lobby mode
+				if( gamenet_man->GameIsOver())
+				{
+					start_info_msg.m_GameMode = Script::GenerateCRC( "netlobby" );
+					start_info_msg.m_TimeLeft = 0;
+					start_info_msg.m_TimeLimit = 0;
+					start_info_msg.m_TargetScore = 0;
+				}
+				else
+				{
+					start_info_msg.m_GameMode = skate_mod->GetGameMode()->GetNameChecksum();
+					start_info_msg.m_TimeLeft = Tmr::InSeconds( skate_mod->GetGameMode()->GetTimeLeft());
+					//start_info_msg.m_TimeLimit = gamenet_man->m_network_preferences.GetPreferenceValue( Script::GenerateCRC("time_limit"), Script::GenerateCRC("time") );
+					start_info_msg.m_TimeLimit = skate_mod->GetGameMode()->GetTimeLimit();
+					start_info_msg.m_TargetScore = gamenet_man->m_network_preferences.GetPreferenceValue( Script::GenerateCRC("target_score"), Script::GenerateCRC("score") );
+				}
+				start_info_msg.m_GameId = gamenet_man->GetNetworkGameId();
+				start_info_msg.m_LevelId = gamenet_man->GetNetworkLevelId();
+				start_info_msg.m_TeamMode = skate_mod->GetGameMode()->NumTeams();
+				start_info_msg.m_CrownSpawnPoint = gamenet_man->m_crown_spawn_point;
+				start_info_msg.m_ProSetFlags = gamenet_man->m_proset_flags;
+				memcpy( start_info_msg.m_StartPoints, gamenet_man->m_skater_starting_points, 
+						Mdl::Skate::vMAX_SKATERS * sizeof( int ));
+				if( net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM )
+				{
+					start_info_msg.m_Broadband = 0;
+				}
+				else
+				{
+					start_info_msg.m_Broadband = 1;
+				}
+
+				start_desc.m_Data = &start_info_msg;
+				start_desc.m_Length = sizeof( MsgStartInfo );
+				start_desc.m_Id = MSG_ID_START_INFO;
+				start_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				start_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+				gamenet_man->m_server->EnqueueMessage( 	player->GetConnHandle(), &start_desc );
+
+				if( gamenet_man->UsingCreatedGoals())
+				{
+					gamenet_man->LoadGoals( gamenet_man->GetNetworkLevelId());
+					gamenet_man->m_server->StreamMessage( player->GetConnHandle(), MSG_ID_GOALS_DATA, gamenet_man->GetGoalsDataSize(), 
+												 gamenet_man->GetGoalsData(), "goals data", vSEQ_GROUP_PLAYER_MSGS,
+												 false, true );
+				}
+
+				// tell them to execute the queue of scripts
+				for( trigger = trigger_sh.FirstItem( gamenet_man->m_trigger_events ); trigger; 
+						trigger = trigger_sh.NextItem())
+				{
+					MsgSpawnAndRunScript script_msg;
+					Net::MsgDesc script_desc;
+	
+					script_msg.m_Node = trigger->m_Node;
+					script_msg.m_ObjID = trigger->m_ObjID;
+					script_msg.m_ScriptName = trigger->m_Script;
+					script_msg.m_Permanent = 0;
+	
+					script_desc.m_Data = &script_msg;
+					script_desc.m_Length = sizeof( MsgSpawnAndRunScript );
+					script_desc.m_Id = MSG_ID_SPAWN_AND_RUN_SCRIPT;
+					script_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					script_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					gamenet_man->m_server->EnqueueMessage( 	player->GetConnHandle(), &script_desc );
+				}
+	
+				if( player->IsLocalPlayer() == false )
+				{
+					skate_mod->SendCheatList( player );
+				}
+
+				// Update the observers with graffiti status
+				if( player->IsObserving())
+				{
+					if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgraffiti" ))
+					{
+						MsgInitGraffitiState init_graffiti_state_msg;
+						Net::MsgDesc obs_msg_desc;
+						uint32 msg_size = skate_mod->GetTrickObjectManager()->SetInitGraffitiStateMessage( &init_graffiti_state_msg );
+						Dbg_Printf( "Sending graffiti state message %d\n", msg_size );
+		
+						obs_msg_desc.m_Data = &init_graffiti_state_msg;
+						obs_msg_desc.m_Length = msg_size;
+						obs_msg_desc.m_Id = MSG_ID_OBSERVER_INIT_GRAFFITI_STATE;
+						obs_msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+						obs_msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+
+						gamenet_man->m_server->StreamMessage( player->GetConnHandle(), obs_msg_desc.m_Id, obs_msg_desc.m_Length, 
+						   obs_msg_desc.m_Data, "TrickObj Buffer", GameNet::vSEQ_GROUP_PLAYER_MSGS );
+						//gamenet_man->m_server->EnqueueMessage( 	player->GetConnHandle(), &obs_msg_desc );
+                        
+					}
+				}
+								
+				// Compile the list of selected goals to send to the client
+				{
+					int i, num_goals;
+					Game::CGoalManager* pGoalManager;
+					Game::CGoal* pGoal;
+					Net::MsgMax msg;
+					Net::MsgDesc msg_desc;
+					uint32 goal_id;
+					char* data;
+					 
+					pGoalManager = Game::GetGoalManager();
+			
+					data = msg.m_Data;
+					*data++ = 0;	// This indicates that they should not bring up the goal summary dialog
+					num_goals = pGoalManager->GetNumGoals();
+					for( i = 0; i < num_goals; i++ )
+					{
+						pGoal = pGoalManager->GetGoalByIndex( i );
+						Dbg_Assert( pGoal );
+						if( pGoal->IsSelected())
+						{
+							goal_id = pGoal->GetGoalId();
+							memcpy( data, &goal_id, sizeof( uint32 ));
+							data += sizeof( uint32 );
+						}
+					}
+			
+					// zero-terminate the list of goals
+					goal_id = 0;
+					memcpy( data, &goal_id, sizeof( uint32 ));
+					data += sizeof( uint32 );
+			
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = (int) ( data - msg.m_Data );
+					msg_desc.m_Id = MSG_ID_SELECT_GOALS;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					gamenet_man->m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+				}
+				
+				// Update the player with the king of the hill status
+				if(( king = gamenet_man->GetKingOfTheHill()))
+				{
+					GameNet::MsgByteInfo msg;
+					Net::MsgDesc msg_desc;
+	
+					Dbg_MsgAssert( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x6ef8fda0,"netking")  ||
+								   skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x5d32129c,"king") ,
+									( "King exists in non-king of the hill game" ));
+	
+					msg.m_Data = king->m_Skater->GetID();
+
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = sizeof( GameNet::MsgByteInfo );
+					msg_desc.m_Id = MSG_ID_NEW_KING;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					gamenet_man->m_server->EnqueueMessage(	player->GetConnHandle(), &msg_desc );
+				}
+			}
+			
+			Net::MsgDesc proceed_desc;
+
+			proceed_desc.m_Id = MSG_ID_PROCEED_TO_PLAY;
+			proceed_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			proceed_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			// Send this to let the client know when he has received the full set of initial data
+			gamenet_man->m_server->EnqueueMessage( 	player->GetConnHandle(), &proceed_desc );
+
+			if( gamenet_man->InNetGame())
+			{
+				gamenet_man->SendFaceDataToPlayer( player );
+			}
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_ready_query( Net::MsgHandlerContext* context )
+{
+	MsgReady* msg;
+	Manager* gamenet_man;
+
+	gamenet_man = (Manager*) context->m_Data;
+	msg = (MsgReady*) context->m_Msg;
+	
+	gamenet_man->m_latest_ready_query = msg->m_Time;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_ready_response( Net::MsgHandlerContext* context )
+{
+	PlayerInfo* player;
+	Manager* gamenet_man;
+	MsgReady* ready_msg;
+
+	gamenet_man = (Manager*) context->m_Data;
+	ready_msg = (MsgReady* ) context->m_Msg;
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player )
+	{   
+		player->MarkAsReady( ready_msg->m_Time );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Respond to a client who is looking for games to join on the LAN*/
+/* Tell them info about the game                                  */
+/******************************************************************/
+
+int Manager::s_handle_find_server( Net::MsgHandlerContext* context )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Lst::Search< NewPlayerInfo > new_sh;
+	NewPlayerInfo* np;
+    MsgServerDescription msg;
+	MsgFindServer* find_msg;
+	Manager* gamenet_man;
+	Net::Server* server;
+	int i;
+	char name[vMAX_SERVER_NAME_LEN + 1];
+	const char *server_name;
+	Script::CScriptStructure* pStructure;
+	Prefs::Preferences* pPreferences;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();   
+
+	//Dbg_Printf( "Got find server broadcast\n" );
+	if( context->m_MsgLength != sizeof( MsgFindServer ))
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	gamenet_man = (Manager*) context->m_Data;
+	if( gamenet_man->InInternetMode())
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	pPreferences = gamenet_man->GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("server_name") );
+	pStructure->GetText( "ui_string", &server_name, true );
+	
+	find_msg = (MsgFindServer *) context->m_Msg;
+    
+	strncpy( name, server_name, vMAX_SERVER_NAME_LEN );
+	name[ vMAX_SERVER_NAME_LEN ] = '\0';
+	
+	server = gamenet_man->GetServer();
+	Dbg_Assert( server != NULL );
+
+	msg.m_SkillLevel = gamenet_man->GetSkillLevel();
+	msg.m_NumPlayers = gamenet_man->GetNumPlayers();
+	msg.m_MaxPlayers = gamenet_man->GetMaxPlayers();
+	msg.m_NumObservers = gamenet_man->GetNumObservers();
+	msg.m_MaxObservers = gamenet_man->GetMaxObservers();
+    
+	// Handle the cases where you've just changed the limits and your current number of players/observers
+	// exceeds the limits
+	if( msg.m_MaxPlayers < msg.m_NumPlayers )
+	{
+		msg.m_MaxPlayers = msg.m_NumPlayers;
+	}
+	if( msg.m_MaxObservers < msg.m_NumObservers )
+	{
+		msg.m_MaxObservers = msg.m_NumObservers;
+	}
+
+	if( strcmp( gamenet_man->GetPassword(), "" ) == 0 )
+	{
+		msg.m_Password = 0;
+	}
+	else
+	{
+		msg.m_Password = 1;
+	}
+
+	if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netlobby" ))
+	{
+		msg.m_GameStarted = false;
+	}
+	else
+	{
+		msg.m_GameStarted = true;
+	}
+
+	msg.m_HostMode = gamenet_man->GetHostMode();
+#ifdef __PLAT_NGPS__	
+	msg.m_Ranked = false;
+	if( gamenet_man->InInternetMode())
+	{
+		if( gamenet_man->mpBuddyMan->IsLoggedIn())
+		{
+			msg.m_Ranked = true;
+		}
+	}
+#endif
+	
+	strcpy( msg.m_Name, name );
+	sprintf( msg.m_Level, "%s", gamenet_man->GetLevelName());
+	sprintf( msg.m_Mode, "%s", gamenet_man->GetGameModeName());
+	msg.m_Timestamp = find_msg->m_Timestamp;
+#ifdef __PLAT_XBOX__
+	memcpy( msg.m_Nonce, find_msg->m_Nonce, 8 );
+	memcpy( &msg.m_XboxKeyId, &server->m_XboxKeyId, sizeof( XNKID ));
+	memcpy( &msg.m_XboxKey, &server->m_XboxKey, sizeof( XNKEY ));
+	memcpy( &msg.m_XboxAddr, &server->m_XboxAddr, sizeof( XNADDR ));
+#endif // __PLAT_XBOX__
+
+	i = 0;
+
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+			player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( !player->IsObserving() || player->IsPendingPlayer())
+		{
+			sprintf( msg.m_PlayerNames[i], player->m_Name );
+			i++;
+		}
+	}
+    
+	for( np = gamenet_man->FirstNewPlayerInfo( new_sh ); np; np = gamenet_man->NextNewPlayerInfo( new_sh ))
+	{
+		// Pending players count, observers don't
+		if(	( np->Flags & PlayerInfo::mPENDING_PLAYER ) ||
+			( np->Flags & PlayerInfo::mJUMPING_IN ) ||
+			!( np->Flags & PlayerInfo::mOBSERVER ))
+		{
+			sprintf( msg.m_PlayerNames[i], np->Name );
+			i++;
+		}                 
+	}
+	
+#ifdef __PLAT_XBOX__	
+	server->SendMessageTo( Net::MSG_ID_SERVER_RESPONSE, sizeof( MsgServerDescription ), &msg,
+							INADDR_BROADCAST, context->m_Conn->GetPort(), 0 );
+#else
+	server->SendMessageTo( Net::MSG_ID_SERVER_RESPONSE, sizeof( MsgServerDescription ), &msg,
+							context->m_Conn->GetIP(), context->m_Conn->GetPort(), 0 );	
+#endif		
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Got a connection request: For now, just accept it, create a    */
+/* skater for the requester, and tell them to create one too      */
+/******************************************************************/
+
+int	Manager::s_handle_connection( Net::MsgHandlerContext* context )
+{   
+	Manager* gamenet_man;
+	MsgConnectInfo* connect_msg;
+	int reason;
+	
+	Dbg_Assert( context );
+
+	gamenet_man = (Manager*) context->m_Data;
+	connect_msg = (MsgConnectInfo*) context->m_Msg;
+    
+	if( context->m_Conn->IsRemote())
+	{
+		MsgProceed msg;
+		const char *server_name;
+		Script::CScriptStructure* pStructure;
+		Prefs::Preferences* pPreferences;
+		Net::Manager* net_man = Net::Manager::Instance();
+		
+		if( !gamenet_man->ok_to_join( reason, connect_msg, context->m_Conn ))
+		{
+			MsgEmbedded msg;
+	
+			msg.m_SubMsgId = reason;
+
+			context->m_App->SendMessageTo( MSG_ID_JOIN_REFUSED, sizeof( MsgEmbedded ), &msg, 
+										   context->m_Conn->GetIP(), context->m_Conn->GetPort(), 0 );
+			return Net::HANDLER_HALT;
+		}
+
+		Dbg_Printf( "Sending Join Proceed Message to IP: %x Port: %d MaxPlayers: %d\n", context->m_Conn->GetIP(), context->m_Conn->GetPort(), gamenet_man->GetNumPlayers());
+		msg.m_MaxPlayers = gamenet_man->GetMaxPlayers();
+		if(	net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM )
+		{
+			msg.m_Broadband = 0;
+		}
+		else
+		{
+			msg.m_Broadband = 1;
+		}
+		
+		if( gamenet_man->InInternetMode())
+		{
+			msg.m_Port = gamenet_man->GetJoinPort();
+			msg.m_PrivateIP = gamenet_man->GetJoinPrivateIP();
+			msg.m_PublicIP = gamenet_man->GetJoinIP();
+		}
+		
+		pPreferences = gamenet_man->GetNetworkPreferences();
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("server_name") );
+		pStructure->GetText( "ui_string", &server_name, true );
+	
+		strncpy( msg.m_ServerName, server_name, vMAX_SERVER_NAME_LEN );
+		msg.m_ServerName[ vMAX_SERVER_NAME_LEN ] = '\0';
+
+		context->m_App->SendMessageTo( 	MSG_ID_JOIN_PROCEED, sizeof( MsgProceed), &msg,
+										context->m_Conn->GetIP(), context->m_Conn->GetPort(), 0 );
+	}
+	else
+	{
+		MsgProceed msg;
+		Net::MsgDesc msg_desc;
+
+		msg.m_MaxPlayers = gamenet_man->GetMaxPlayers();
+		msg_desc.m_Id = MSG_ID_JOIN_PROCEED;
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( MsgProceed );
+		context->m_App->EnqueueMessage( context->m_Conn->GetHandle(), &msg_desc );
+		//context->m_App->SendData();
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client wants to join a connected server						  */
+/*																  */
+/******************************************************************/
+
+int	Manager::s_handle_join_request( Net::MsgHandlerContext* context )
+{   
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	NewPlayerInfo new_player;
+	Manager* gamenet_man;
+	MsgJoinInfo* join_msg;
+	int reason;
+	
+	Dbg_Assert( context );
+
+	Dbg_Printf( "Got Join Request from ip %x port %d\n", context->m_Conn->GetIP(), context->m_Conn->GetPort());
+
+	gamenet_man = (Manager*) context->m_Data;
+	join_msg = (MsgJoinInfo*) context->m_Msg;
+
+	// We'll just accept local clients
+	if( context->m_Conn->IsRemote())
+	{
+		// Rule out redundancy
+		if( context->m_PacketFlags & Net::mHANDLE_FOREIGN )
+		{
+			Net::Conn* conn;
+
+			// Are we currently accepting players?
+			if( context->m_App->AcceptsForeignConnections() == false )
+			{
+				return Net::HANDLER_MSG_DONE;
+			}
+			
+			// Create a more permanent connection
+			conn = context->m_App->NewConnection( context->m_Conn->GetIP(), context->m_Conn->GetPort());
+			if( join_msg->m_Broadband )
+			{
+				conn->SetBandwidthType( Net::Conn::vBROADBAND );
+				conn->SetSendInterval( Tmr::VBlanks( 2 ));
+			}
+			else
+			{
+				conn->SetBandwidthType( Net::Conn::vNARROWBAND );
+				conn->SetSendInterval( Tmr::VBlanks( 2 ));
+			}
+			
+			
+			context->m_Conn = conn;	// the rest of the chain will use this new, valid connection
+			
+			if( !gamenet_man->ok_to_join( reason, join_msg, conn ))
+			{
+				MsgEmbedded msg;
+				Net::MsgDesc msg_desc;
+		
+				msg.m_SubMsgId = reason;
+		
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( MsgEmbedded );
+				msg_desc.m_Id = MSG_ID_JOIN_REFUSED;
+				context->m_App->EnqueueMessage( context->m_Conn->GetHandle(), &msg_desc );
+				context->m_Conn->Invalidate();
+		
+				return Net::HANDLER_HALT;
+			}
+		}
+		else
+		{
+			return Net::HANDLER_MSG_DONE;
+		}
+	}
+	
+	strcpy( new_player.Name, join_msg->m_Name );
+	new_player.ObjID = gamenet_man->GetNextPlayerObjectId();
+	new_player.Conn = context->m_Conn;
+	new_player.Flags = 0;
+	new_player.Profile = join_msg->m_Profile;
+	new_player.Rating = join_msg->m_Rating;
+	new_player.VehicleControlType = join_msg->m_VehicleControlType;
+	if( join_msg->m_Observer != 0 )
+	{
+		new_player.Flags |= PlayerInfo::mOBSERVER;
+	}
+	else if(( skate_mod->GetGameMode()->GetNameChecksum() != CRCD(0x1c471c60, "netlobby") ) &&
+			( gamenet_man->GameIsOver() == false ))
+	{
+		new_player.Flags |= ( PlayerInfo::mPENDING_PLAYER | PlayerInfo::mOBSERVER );
+	}
+    
+	Dbg_Printf( "====================== GOT JOIN REQUEST FROM PLAYER %d\n", new_player.ObjID );
+	if( context->m_Conn->IsLocal())
+	{
+		// GJ:  copy the contents of the skater profile from one to the other
+		// it'd be nice to just use a copy constructor, but i couldn't
+		// get it to work for some reason
+		uint8* pTempBuffer = new uint8[vMAX_APPEARANCE_DATA_SIZE];
+		skate_mod->GetProfile( new_player.ObjID )->WriteToBuffer(pTempBuffer, vMAX_APPEARANCE_DATA_SIZE);
+		new_player.mpSkaterProfile->ReadFromBuffer(pTempBuffer);
+		delete pTempBuffer;
+
+		new_player.Flags = PlayerInfo::mLOCAL_PLAYER;
+		gamenet_man->DeferredNewPlayer( &new_player, 0 );
+	}
+	else
+	{
+		if( !join_msg->m_Observer )
+		{
+			// GJ:  the appearance is transmitted inside the message,
+			// so copy it to the "new_player" structure
+			new_player.mpSkaterProfile->ReadFromBuffer(join_msg->m_AppearanceData);
+		}
+		gamenet_man->DeferredNewPlayer( &new_player );
+
+		// Flag this new player's connection as busy so he doesn't get game-related messages
+		// in the ineterum
+		context->m_Conn->ClearStatus( Net::Conn::mSTATUS_READY );
+		context->m_Conn->SetStatus( Net::Conn::mSTATUS_BUSY );
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client wants to enter observer mode	  						  */
+/*																  */
+/******************************************************************/
+
+int	Manager::s_handle_observe( Net::MsgHandlerContext* context )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Manager* gamenet_man;
+	PlayerInfo* player;
+	Obj::CSkater* skater;
+		
+	gamenet_man = (Manager *) context->m_Data;
+
+	Dbg_Printf( "Got observe request\n" );
+
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( !player || player->IsObserving())
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	// Allow the server to go into observer mode without restriction
+	if( ( player->IsLocalPlayer() == false ) &&
+		( gamenet_man->GetNumObservers() >= gamenet_man->GetMaxObservers()))
+	{
+		Net::MsgDesc msg_desc;
+
+		Dbg_Printf( "Not enough observer spots\n" );
+		msg_desc.m_Id = MSG_ID_OBSERVE_REFUSED;
+		gamenet_man->m_server->EnqueueMessage( context->m_Conn->GetHandle(), &msg_desc );
+	}
+	else
+	{
+		Net::MsgDesc msg_desc;
+
+        // Only do this player->observer conversion here for remote players.
+		// For our local player, let the observe_proceed message run its course
+		if( player->IsLocalPlayer() == false )
+		{
+			skater = player->m_Skater;
+			gamenet_man->DropPlayer( player, vREASON_OBSERVING );
+			skate_mod->remove_skater( skater );
+			player->m_flags.SetMask( PlayerInfo::mOBSERVER );
+			player->m_Skater = NULL;
+			if( player->IsServerPlayer())
+			{
+				player->MarkAsNotServerPlayer();
+				gamenet_man->ChooseNewServerPlayer();
+			}
+			// If we're destroying the player we're looking at, change to the next player
+			if( player == gamenet_man->m_cam_player )
+			{
+				gamenet_man->m_cam_player = gamenet_man->GetNextPlayerToObserve();
+				if( player == gamenet_man->m_cam_player )
+				{
+					gamenet_man->m_cam_player = NULL;
+				}
+				
+				gamenet_man->ObservePlayer( gamenet_man->m_cam_player );
+			}
+			
+		}
+
+		Dbg_Printf( "Sent Proceed to Observe\n" );
+		msg_desc.m_Id = MSG_ID_OBSERVE_PROCEED;
+		gamenet_man->m_server->EnqueueMessage( context->m_Conn->GetHandle(), &msg_desc );
+	}
+    
+	return Net::HANDLER_CONTINUE;
+}   
+
+/******************************************************************/
+/* Got a disconnection request. Accept it at face value for now	  */
+/*																  */
+/******************************************************************/
+
+int	Manager::s_handle_disconn_request( Net::MsgHandlerContext* context )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Manager* gamenet_man;
+	PlayerInfo* quitter;
+		
+	
+
+    gamenet_man = (Manager *) context->m_Data;
+	quitter = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( quitter )
+	{
+		if (!quitter->IsLocalPlayer())
+		{
+			Obj::CSkater* quitting_skater;
+			bool observing;
+
+			quitting_skater = quitter->m_Skater;
+			observing = quitter->IsObserving();
+
+			gamenet_man->DropPlayer( quitter, vREASON_QUIT );
+			if( !observing )
+			{
+				skate_mod->remove_skater( quitting_skater );
+			}
+		}
+	}
+	else
+	{
+		Lst::Node< NewPlayerInfo > *node, *next;
+		NewPlayerInfo* new_player;
+			
+		
+	
+		for( node = gamenet_man->m_new_players.GetNext(); node; node = next )
+		{
+			next = node->GetNext();
+	
+			new_player = node->GetData();
+			
+			// 
+			if( new_player->Conn == context->m_Conn )
+			{
+				Dbg_Printf( "Removing %s\n", new_player->Name );
+				context->m_App->TerminateConnection( new_player->Conn );
+				
+				delete node;
+				delete new_player;
+				
+				// Return this value to signify that we've destroyed the message (done by
+				// TerminateConnection())
+				return Net::HANDLER_MSG_DESTROYED;
+			}
+		}
+		// Pay no attention to any other pending messages from ths client
+		return Net::HANDLER_HALT;
+	}
+    
+	// Return this value to signify that we've destroyed the message (done by
+	// TerminateConnection())
+	return Net::HANDLER_MSG_DESTROYED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_client_proceed( Net::MsgHandlerContext* context )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	Manager* gamenet_man;
+	
+	
+	Dbg_Printf( "(%d) Got proceed instruction from server\n", context->m_App->m_FrameCounter );
+
+	gamenet_man = (Manager *) context->m_Data;
+	gamenet_man->SetReadyToPlay(true);
+	
+	if( !context->m_App->IsLocal())
+	{
+		mlp_manager->AddLogicTask( *gamenet_man->m_client_add_new_players_task );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_observe_proceed( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+		
+	Dbg_Printf( "Entering Observer Mode\n" );	
+
+	gamenet_man = (Manager *) context->m_Data;
+	ScriptExitSurveyorMode( NULL, NULL );
+	gamenet_man->EnterObserverMode(); 
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_observe_refused( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	
+    gamenet_man = (Manager *) context->m_Data;
+	Script::CStruct* p_structure = new Script::CStruct;
+
+	p_structure->AddChecksum( "reason", Script::GenerateCRC( "net_reason_full_observers" ));
+	p_structure->AddChecksum( "just_dialog", 0 );
+	Script::RunScript( "CreateJoinRefusedDialog", p_structure );
+	delete p_structure;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_join_refused( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgEmbedded* msg;
+    
+	
+
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgEmbedded *) context->m_Msg;
+
+
+	switch( msg->m_SubMsgId )
+	{
+		case JOIN_REFUSED_ID_BANNED:
+			gamenet_man->m_conn_refused_reason = Script::GenerateCRC("net_reason_banned");
+			break;
+		case JOIN_REFUSED_ID_FULL:
+			gamenet_man->m_conn_refused_reason = Script::GenerateCRC("net_reason_full");
+			break;
+		case JOIN_REFUSED_ID_FULL_OBSERVERS:
+			gamenet_man->m_conn_refused_reason = Script::GenerateCRC("net_reason_full_observers");
+			break;
+		case JOIN_REFUSED_ID_PW:
+		{   
+			if( gamenet_man->GetJoinState() == vJOIN_STATE_TRYING_PASSWORD )
+			{
+				gamenet_man->m_conn_refused_reason = Script::GenerateCRC("net_reason_wrong_password");
+			}
+			else
+			{
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+				Script::RunScript( "CreateEnterPasswordControl" );
+				/*
+				dlg->GoBack();
+							
+				Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+				Front::MenuElement *net_menu = menu_factory->GetMenuElement(Script::GenerateCRC("net_menu"));
+				Front::MenuEvent event;
+				
+				event.SetTypeAndTarget( Front::MenuEvent::vLINK, Script::GenerateCRC("enter_password_control") );
+				net_menu->LaunchEvent( &event );
+				*/
+
+				// Temporarily disable timeout
+				gamenet_man->m_join_timeout_task->Remove();
+
+				Mem::Manager::sHandle().PopContext();
+				return Net::HANDLER_CONTINUE;
+			}
+			
+			break;
+		}
+		case JOIN_REFUSED_ID_IN_PROGRESS:
+		{
+			Dbg_Printf( "Refused: Joining a game in progress\n" );
+			
+			Script::RunScript( "CreateGameInProgressDialog" );
+            
+			gamenet_man->m_join_timeout_task->Remove();
+			return Net::HANDLER_CONTINUE;
+		}
+		case JOIN_REFUSED_ID_VERSION:
+			gamenet_man->m_conn_refused_reason = Script::GenerateCRC("net_reason_version" );
+			break;
+		default:
+			gamenet_man->m_conn_refused_reason = Script::GenerateCRC("net_reason_default" );
+			break;
+	}
+	
+	Dbg_Printf( "(%d) Host refused join request: Reason %d\n", 	context->m_App->m_FrameCounter,
+																msg->m_SubMsgId );
+	
+	gamenet_man->CancelJoinServer();
+	gamenet_man->SetJoinState( vJOIN_STATE_REFUSED );
+	
+	return Net::HANDLER_HALT;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_join_proceed( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	Net::Manager * net_man = Net::Manager::Instance();
+	MsgJoinInfo msg;
+    Net::MsgDesc msg_desc;
+	int size;
+	Script::CScriptStructure* pStructure;
+	Prefs::Preferences* pPreferences;
+	const char* network_id;
+	MsgProceed* proceed_msg;
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	bool ignore_face_data;
+
+	gamenet_man = (Manager *) context->m_Data;
+		
+	proceed_msg = (MsgProceed*) context->m_Msg;
+	if( context->m_Conn->IsRemote())
+	{
+		// Note whether or not the server is narrowband/broadband
+		if( proceed_msg->m_Broadband )
+		{
+			context->m_Conn->SetBandwidthType( Net::Conn::vBROADBAND );
+		}
+		else
+		{
+			context->m_Conn->SetBandwidthType( Net::Conn::vNARROWBAND );
+		}
+
+		gamenet_man->m_join_ip = proceed_msg->m_PublicIP;
+		gamenet_man->m_join_private_ip = proceed_msg->m_PrivateIP;
+		gamenet_man->SetJoinPort( proceed_msg->m_Port );
+
+		Dbg_Printf( "**** Setting public IP to %s\n", inet_ntoa(*(struct in_addr *) &gamenet_man->m_join_ip ));
+#ifdef __PLAT_NGPS__
+		if( gamenet_man->mpBuddyMan->IsLoggedIn())
+		{
+			char location[1024];
+
+			gamenet_man->mpLobbyMan->SetServerName( proceed_msg->m_ServerName );
+			sprintf( location, "%d:%d:%d:%s (%s)", gamenet_man->m_join_ip, gamenet_man->m_join_private_ip, gamenet_man->GetJoinPort(), 
+					 gamenet_man->mpLobbyMan->GetServerName(), gamenet_man->mpLobbyMan->GetLobbyName());
+			if( gamenet_man->GetJoinMode() == vJOIN_MODE_PLAY )
+			{
+				gamenet_man->mpBuddyMan->SetStatusAndLocation( GP_PLAYING, (char*) Script::GetString( "homie_status_playing" ), location );
+			}
+			else
+			{
+				gamenet_man->mpBuddyMan->SetStatusAndLocation( GP_PLAYING, (char*) Script::GetString( "homie_status_observing" ), location );
+			}
+		}
+#endif
+
+	}
+	skate_mod->GetGameMode()->SetMaximumNumberOfPlayers( proceed_msg->m_MaxPlayers );
+
+	/*Dbg_Printf( "******* Got Join Proceed from: %s, private: %s, port: %d\n", 
+				inet_ntoa(*(struct in_addr *) &gamenet_man->m_join_ip ), 
+				inet_ntoa(*(struct in_addr *) &gamenet_man->m_join_private_ip ),
+				gamenet_man->m_join_port );*/
+	
+	pPreferences = gamenet_man->GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("network_id") );
+	pStructure->GetText( "ui_string", &network_id, true );
+
+#ifdef __PLAT_NGPS__
+	msg.m_Profile = gamenet_man->mpBuddyMan->GetProfile();
+	msg.m_Rating = gamenet_man->mpStatsMan->GetStats()->GetRating();
+#endif
+	msg.m_Observer = ( gamenet_man->GetJoinMode() == vJOIN_MODE_OBSERVE );
+	msg.m_Version = vVERSION_NUMBER;
+	msg.m_WillingToWait = 1;
+	if(	net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM )
+	{
+		ignore_face_data = true;
+		msg.m_Broadband = 0;
+	}
+	else
+	{
+		ignore_face_data = false;
+		msg.m_Broadband = 1;
+	}
+			
+	strcpy( msg.m_Name, network_id );
+	msg.m_Password[0] = '\0';
+	size = 0;
+	if( !msg.m_Observer )
+	{
+		// GJ:  transmit the way you look (slot 0 of the
+		// skater profile manager) to the server
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		Obj::CSkaterProfile* pSkaterProfile = skate_mod->GetProfile(0);
+		Net::Conn *server_conn;
+		Lst::Search< Net::Conn > sh;
+	
+		server_conn = context->m_App->FirstConnection( &sh );
+		if( server_conn->GetBandwidthType() == Net::Conn::vNARROWBAND )
+		{
+			ignore_face_data = true;
+		}
+
+		size = pSkaterProfile->WriteToBuffer(msg.m_AppearanceData, vMAX_APPEARANCE_DATA_SIZE,
+												ignore_face_data );
+		Dbg_Assert( size < vMAX_APPEARANCE_DATA_SIZE );
+		Dbg_Printf("\n\n******************* MsgJoinInfo (%d) appearance data size = %d %d Broadband %d\n", MSG_ID_JOIN_REQ,
+				size, sizeof(MsgJoinInfo) - vMAX_APPEARANCE_DATA_SIZE + size, ignore_face_data );
+	}
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof(MsgJoinInfo) - vMAX_APPEARANCE_DATA_SIZE + size;
+	msg_desc.m_Id = MSG_ID_JOIN_REQ;
+	if( context->m_Conn->IsRemote())
+	{
+		msg_desc.m_Queue = Net::QUEUE_IMPORTANT;
+	}
+
+	context->m_App->EnqueueMessageToServer( &msg_desc );
+	
+	/*context->m_App->StreamMessageToServer( GameNet::MSG_ID_JOIN_REQ, msg_desc.m_Length, 
+					   msg_desc.m_Data, "appearance data", vSEQ_GROUP_PLAYER_MSGS, false );*/
+
+	gamenet_man->SetJoinState( vJOIN_STATE_JOINING );
+
+	return Net::HANDLER_MSG_DONE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_join_accepted( Net::MsgHandlerContext* context )
+{
+	Dbg_Message( "(%d) Got Join Accepted!\n", context->m_App->m_FrameCounter );
+
+	// Consider all of our messages up to this point to have been received
+	//context->m_Conn->AckAllMessages();
+	//context->m_Conn->DestroyMessageQueues();
+	if( context->m_Conn->IsRemote())
+	{
+		context->m_Conn->DestroyImportantMessageQueues();
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_new_king( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgByteInfo* msg;
+	PlayerInfo* player;
+
+	msg = (MsgByteInfo*) context->m_Msg;
+
+	gamenet_man = (Manager *) context->m_Data;
+	
+	player = gamenet_man->GetKingOfTheHill();
+	if( player )
+	{
+		player->MarkAsKing( false );
+	}
+	else
+	{
+		NewPlayerInfo* new_player;
+		Lst::Search< NewPlayerInfo > sh;
+
+		// Maybe we just haven't officially added the previous king. Search our list of
+		// queued up new players
+		for( new_player = gamenet_man->FirstNewPlayerInfo( sh ); new_player;
+				new_player = gamenet_man->NextNewPlayerInfo( sh ))
+		{
+			if( new_player->Flags & PlayerInfo::mKING_OF_THE_HILL )
+			{
+				new_player->Flags &= ~PlayerInfo::mKING_OF_THE_HILL;
+				break;
+			}
+		}
+	}
+
+	player = gamenet_man->GetPlayerByObjectID( msg->m_Data );
+	if( player )
+	{
+		player->MarkAsKing( true );
+	}
+	else
+	{
+		NewPlayerInfo* new_player;
+		Lst::Search< NewPlayerInfo > sh;
+
+		// Maybe we just haven't officially added this new king player. Search our list of
+		// queued up new players
+		for( new_player = gamenet_man->FirstNewPlayerInfo( sh ); new_player;
+				new_player = gamenet_man->NextNewPlayerInfo( sh ))
+		{
+			if( new_player->ObjID == msg->m_Data )
+			{
+				new_player->Flags |= PlayerInfo::mKING_OF_THE_HILL;
+				break;
+			}
+		}
+	}
+    
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_stole_flag( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgFlagMsg* msg;
+	PlayerInfo* player;
+
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgFlagMsg*) context->m_Msg;
+
+	player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+	if( player )
+	{
+		player->StoleFlag( msg->m_Team );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_took_flag( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgFlagMsg* msg;
+	PlayerInfo* player;
+
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgFlagMsg*) context->m_Msg;
+
+	player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+	if( player )
+	{
+		player->TookFlag( msg->m_Team );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_captured_flag( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgFlagMsg* msg;
+	PlayerInfo* player;
+
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgFlagMsg*) context->m_Msg;
+
+	player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+	if( player )
+	{
+		player->CapturedFlag( msg->m_Team );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_retrieved_flag( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgFlagMsg* msg;
+	PlayerInfo* player;
+
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgFlagMsg*) context->m_Msg;
+
+	player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+	if( player )
+	{
+		player->RetrievedFlag();
+	}
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* A player has restarted in koth mode. If it is the king, send	  */
+/* out a message signifying that the king has lost his crown	  */
+/******************************************************************/
+
+int	Manager::s_handle_player_restarted( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	PlayerInfo* player;
+	Mdl::Skate * skate_mod;
+	 
+	skate_mod =  Mdl::Skate::Instance();
+	gamenet_man = (Manager *) context->m_Data;
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player )
+	{
+		player->m_flags.ClearMask( PlayerInfo::mRESTARTING );
+	}
+
+	if(	skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netctf" ))
+	{
+		if( player && player->HasCTFFlag())
+		{
+			Lst::Search< PlayerInfo > sh;
+			PlayerInfo* send_player, *local_player;
+			char team_str[64];
+			int team;
+			Script::CStruct* pParams;
+			MsgFlagMsg msg;
+			Net::MsgDesc msg_desc;
+			
+			team = player->HasWhichFlag();
+			sprintf( team_str, "team_%d_name", team + 1 );
+			pParams = new Script::CStruct;
+	
+			pParams->AddInteger( "team", team );
+			pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+			Script::RunScript( "flag_returned", pParams );
+	
+			delete pParams;
+			msg.m_Team = team;
+			msg.m_ObjId = player->m_Skater->GetID();
+
+			// If it's me, remove the message that says "return the flag to your base"
+			if( player->IsLocalPlayer())
+			{
+				Script::RunScript( "destroy_ctf_panel_message" );
+			}
+			local_player = gamenet_man->GetLocalPlayer();
+			if( local_player && !local_player->IsObserving())
+			{
+				if( local_player->m_Team == team )
+				{
+					Script::RunScript( "hide_ctf_arrow" );
+				}
+			}
+
+			player->ClearCTFState();
+
+			msg_desc.m_Data = &msg;
+			msg_desc.m_Length = sizeof( MsgFlagMsg );
+			msg_desc.m_Id = MSG_ID_PLAYER_DROPPED_FLAG;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			for( send_player = gamenet_man->FirstPlayerInfo( sh, true ); send_player; 
+					send_player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				if( send_player->IsLocalPlayer())
+				{
+					continue;
+				}
+				context->m_App->EnqueueMessage( send_player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+	else if(( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netking" )) ||
+			( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "king" )))
+	{
+		if( player && player->IsKing())
+		{
+			Lst::Search< PlayerInfo > sh;
+			Net::MsgDesc msg_desc;
+			char point;
+	
+			player->MarkAsKing( false );
+			gamenet_man->m_crown_spawn_point = Mth::Rnd( vNUM_CROWN_SPAWN_POINTS );
+            point = (char) gamenet_man->m_crown_spawn_point;
+			
+			msg_desc.m_Data = &point;
+			msg_desc.m_Length = sizeof( char );
+			msg_desc.m_Id = MSG_ID_KING_DROPPED_CROWN;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+					player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				context->m_App->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The server is notifying the client that he's in an auto-serving*/
+/* server									  					  */
+/******************************************************************/
+
+int	Manager::s_handle_auto_server_notification( Net::MsgHandlerContext* context )
+{
+	Script::RunScript( "launch_auto_server_notification" );
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The server is notifying the client that he is the new		  */
+/* operating server	in this fcfs server		  					  */
+/******************************************************************/
+
+int	Manager::s_handle_fcfs_assignment( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	PlayerInfo* player;
+	uint32 *checksum;
+	Script::CStruct* pParams;
+	
+	gamenet_man = (Manager *) context->m_Data;
+
+	player = gamenet_man->GetLocalPlayer();
+	Dbg_Assert( player );
+
+	checksum = (uint32*) context->m_Msg;
+	player->MarkAsServerPlayer();
+	pParams = new Script::CStruct;
+	pParams->AddChecksum( "checksum", *checksum );
+	Script::RunScript( "launch_fcfs_notification", pParams );
+	delete pParams;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The fcfs client is requesting to perform a server operation	  */
+/* 								  								  */
+/******************************************************************/
+
+int	Manager::s_handle_fcfs_request( Net::MsgHandlerContext* context )
+{
+	PlayerInfo* player;
+	Manager* gamenet_man;
+
+	gamenet_man = (Manager *) context->m_Data;
+
+	Dbg_Printf( "GOT FCFS REQUEST\n" );
+	// First, ensure we even have an fcfs server
+	player = gamenet_man->GetServerPlayer();
+	if( player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	// Next, make sure the requesting client matches the fcfs client
+	if( player->m_Conn != context->m_Conn )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	Dbg_Printf( "GOT FCFS REQUEST 2\n" );
+	switch( context->m_MsgId )
+	{
+		case MSG_ID_FCFS_START_GAME:
+		{
+			MsgStartGameRequest* msg;
+			Prefs::Preferences* pPreferences;
+			Script::CStruct* pTempStructure;
+			const char* ui_string;
+			int score, time, fireball_level;
+	
+			msg = (MsgStartGameRequest*) context->m_Msg;
+			pPreferences = gamenet_man->GetNetworkPreferences();
+
+			// First, apply the options chosen by the fcfs
+			pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "net_game_type_info", msg->m_GameMode );
+			Dbg_Printf( "Got Game Mode UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_GameMode );
+			pPreferences->SetPreference( Script::GenerateCRC( "game_type" ), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "skill_level_info", msg->m_SkillLevel );
+			Dbg_Printf( "Got Skill Level UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_SkillLevel );
+			pPreferences->SetPreference( Script::GenerateCRC( "skill_level" ), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "fireball_level_info", msg->m_FireballLevel );
+			fireball_level = gamenet_man->GetIntFromArrayEntry( "fireball_level_info", msg->m_FireballLevel, 
+																CRCD(0xce87e4e3,"fireball_level") );
+			Dbg_Printf( "Got Fireball Level UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_FireballLevel );
+			pPreferences->SetPreference( Script::GenerateCRC( "fireball_difficulty" ), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "on_off_types", msg->m_PlayerCollision );
+			Dbg_Printf( "Got Player Collision UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_PlayerCollision );
+			pPreferences->SetPreference( Script::GenerateCRC( "player_collision" ), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "on_off_types", msg->m_FriendlyFire );
+			Dbg_Printf( "Got Friendly Fire UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_FriendlyFire );
+			pPreferences->SetPreference( Script::GenerateCRC( "friendly_fire" ), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "boolean_types", msg->m_StopAtZero );
+			Dbg_Printf( "Got Stop At Zero UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_StopAtZero );
+			pPreferences->SetPreference( Script::GenerateCRC( "stop_at_zero" ), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "ctf_type", msg->m_CTFType );
+			Dbg_Printf( "Got CTF Type UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_CTFType );
+			pPreferences->SetPreference( Script::GenerateCRC( "ctf_game_type" ), pTempStructure );
+			delete pTempStructure;
+
+            pTempStructure = new Script::CStruct;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "time_limit_options", msg->m_TimeLimit );
+			time = gamenet_man->GetIntFromArrayEntry( "time_limit_options", msg->m_TimeLimit, CRCD( 0x906b67ba, "time" ) );
+			Dbg_Printf( "Got Time Limit UI String of %s\n", ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_TimeLimit );
+			pTempStructure->AddComponent( Script::GenerateCRC("time"), ESYMBOLTYPE_INTEGER, time );
+			pPreferences->SetPreference( Script::GenerateCRC( "time_limit" ), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CStruct;
+
+			score = 0;
+			ui_string = gamenet_man->GetNameFromArrayEntry( "target_score_options", msg->m_TargetScore );
+			score = gamenet_man->GetIntFromArrayEntry( "target_score_options", msg->m_TargetScore, CRCD( 0xcd66c8ae, "score" ));
+			// Our "target score" comes from either the target score options array or the time limit options array, depending on
+			// whether the game type is score challenge or koth
+			if( ui_string == NULL )
+			{
+				ui_string = gamenet_man->GetNameFromArrayEntry( "capture_options", msg->m_TargetScore );
+				score = gamenet_man->GetIntFromArrayEntry( "capture_options", msg->m_TargetScore, CRCD( 0xcd66c8ae, "score" ));
+				if( ui_string == NULL )
+				{
+					ui_string = gamenet_man->GetNameFromArrayEntry( "time_limit_options", msg->m_TargetScore );
+					score = gamenet_man->GetIntFromArrayEntry( "time_limit_options", msg->m_TargetScore, CRCD( 0x906b67ba, "time" ));
+					score *= 1000;
+				}
+			}
+			Dbg_Printf( "Got Target Score %08x UI String of %s, score %d\n", msg->m_TargetScore, ui_string, score );
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, ui_string );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, msg->m_TargetScore );
+			if( score > 0 )
+			{
+				pTempStructure->AddComponent( Script::GenerateCRC("score"), ESYMBOLTYPE_INTEGER, score );
+			}
+			pPreferences->SetPreference( Script::GenerateCRC( "target_score" ), pTempStructure );
+
+			delete pTempStructure;
+
+			Script::RunScript( "chosen_start_game" );
+			break;
+		}
+		case MSG_ID_FCFS_BAN_PLAYER:
+		{
+			MsgRemovePlayerRequest* msg;
+			PlayerInfo* player, *target_player;
+			Lst::Search< PlayerInfo > sh;
+			int i;
+
+			msg = (MsgRemovePlayerRequest*) context->m_Msg;
+
+			i = 0;
+			target_player = NULL;
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				if( msg->m_Index == i )
+				{
+					if( stricmp( msg->m_Name, player->m_Name ) == 0 )
+					{
+						target_player = player;
+					}
+					break;
+				}
+				i++;
+			}
+
+			if( target_player == NULL )
+			{
+				return Net::HANDLER_MSG_DONE;
+			}
+
+			if( msg->m_Ban == 1 )
+			{
+				Obj::CSkater* quitting_skater;
+				bool observing;
+	
+				quitting_skater = target_player->m_Skater;
+				observing = target_player->IsObserving();
+				gamenet_man->DropPlayer( target_player, vREASON_BANNED );
+				if( !observing )
+				{
+					Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+					skate_mod->remove_skater( quitting_skater );
+				}
+			}
+			else
+			{
+				Obj::CSkater* quitting_skater;
+				bool observing;
+	
+				quitting_skater = target_player->m_Skater;
+				observing = target_player->IsObserving();
+				gamenet_man->DropPlayer( target_player, vREASON_KICKED );
+				if( !observing )
+				{
+					Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+					skate_mod->remove_skater( quitting_skater );
+				}
+			}
+			break;
+		}
+		case MSG_ID_FCFS_CHANGE_LEVEL:
+		{
+			MsgIntInfo* msg;
+			Script::CStruct* pStructure;
+
+			msg = (MsgIntInfo *) context->m_Msg;
+			pStructure = new Script::CStruct;
+	
+			pStructure->AddComponent( Script::GenerateCRC( "level" ), ESYMBOLTYPE_NAME, (uint32) msg->m_Data );
+			pStructure->AddChecksum( "from_fcfs", Script::GenerateCRC( "from_fcfs" ));
+			pStructure->AddChecksum( "show_warning", Script::GenerateCRC( "show_warning" ));
+
+			Script::RunScript( "change_level", pStructure );
+
+			delete pStructure;
+			break;
+		}
+		case MSG_ID_FCFS_TOGGLE_PROSET:
+		{
+			MsgToggleProSet* msg;
+
+			msg = (MsgToggleProSet *) context->m_Msg;
+			gamenet_man->ToggleProSet( msg->m_Bit, msg->m_ParamId );
+			Script::RunScript( "toggle_proset_flag", Script::GetStructure( msg->m_ParamId, Script::ASSERT ));
+			Script::RunScript( "toggle_geo_nomenu", Script::GetStructure( msg->m_ParamId, Script::ASSERT ));
+			break;
+		}
+		case MSG_ID_FCFS_TOGGLE_GOAL_SELECTION:
+		{
+			Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+			MsgIntInfo* msg;
+			
+			msg = (MsgIntInfo*) context->m_Msg;
+
+			pGoalManager->ToggleGoalSelection( msg->m_Data );
+			break;
+		}
+		case MSG_ID_FCFS_END_GAME:
+		{
+			Net::Server* server;
+			Lst::Search< PlayerInfo > sh;
+			Net::MsgDesc msg_desc;
+
+			server = gamenet_man->GetServer();
+
+			Script::RunScript( "fcfc_end_game_selected" );
+	
+			msg_desc.m_Id = MSG_ID_END_GAME;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				if( player->IsLocalPlayer())
+				{
+					continue;
+				}
+		
+				server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+			break;
+		}
+
+		case MSG_ID_FCFS_SET_NUM_TEAMS:
+		{
+			Prefs::Preferences* prefs;
+			Script::CStruct* pParams, *pPrefStruct;
+			MsgByteInfo* msg;
+            
+			msg = (MsgByteInfo*) context->m_Msg;
+			Dbg_Printf( "GOT FCFS REQUEST : NUM TEAMS %d\n", msg->m_Data );
+			pParams = new Script::CStruct;
+			pParams->AddInteger( NONAME, msg->m_Data );
+
+			pPrefStruct = new Script::CScriptStructure;
+			switch( msg->m_Data )
+			{
+				case 0:
+					pPrefStruct->AddString( "ui_string", "None" );
+					pPrefStruct->AddChecksum( "checksum", Script::GenerateCRC( "teams_none" ));
+					break;
+				case 2:
+					pPrefStruct->AddString( "ui_string", "2" );
+					pPrefStruct->AddChecksum( "checksum", Script::GenerateCRC( "teams_two" ));
+					break;
+				case 3:
+					pPrefStruct->AddString( "ui_string", "3" );
+					pPrefStruct->AddChecksum( "checksum", Script::GenerateCRC( "teams_three" ));
+					break;
+				case 4:
+					pPrefStruct->AddString( "ui_string", "4" );
+					pPrefStruct->AddChecksum( "checksum", Script::GenerateCRC( "teams_four" ));
+					break;
+				default:
+					Dbg_Assert( 0 );
+					break;
+			}
+			
+			prefs = gamenet_man->GetNetworkPreferences();
+			prefs->SetPreference( Script::GenerateCRC("team_mode"), pPrefStruct );
+			delete pPrefStruct;
+
+			ScriptSetNumTeams( pParams, NULL );
+
+			delete pParams;
+			break;
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The king has lost his crown. Spawn a new one in the world at   */
+/* the spawn point specified									  */
+/******************************************************************/
+
+int	Manager::s_handle_dropped_crown( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	PlayerInfo* king, *other_player;
+	char spawn_pt;
+	Obj::CCrown* crown;
+	
+	gamenet_man = (Manager *) context->m_Data;
+	king = gamenet_man->GetKingOfTheHill();
+	memcpy( &spawn_pt, context->m_Msg, sizeof( char ));
+	if( king )
+	{
+		// He is no longer king
+		king->MarkAsKing( false );
+	}
+	
+	if( king && king->IsLocalPlayer())
+	{
+		gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC("net_message_dropped_crown_you"),
+											NULL, NULL, king->m_Skater );
+	}
+	else
+	{   
+		if( gamenet_man->InNetGame())
+		{
+			gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC("net_message_dropped_crown_other"));
+		}
+		else if( king && king->m_Skater )
+		{   
+			int other_id;
+
+			// NOTE: This code only works for 2-player splitscreen. If we ever go to four,
+			// it needs to be more sophisticated
+			if( king->m_Skater->GetID() == 0 )
+			{
+				other_id = 1;
+			}
+			else
+			{ 
+				other_id = 0;
+			}
+
+			other_player = gamenet_man->GetPlayerByObjectID( other_id );
+			if( other_player )
+			{
+				gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC("net_message_dropped_crown_other"),
+													NULL, NULL, other_player->m_Skater );
+			}
+		}
+	}
+
+	crown = gamenet_man->GetCrown();
+	if( crown )
+	{
+#if 1
+		int loop_count = 0;
+		bool node_found = false;
+		
+		// KLUDGE: keep looping through incase spawn_pt is larger than the number of crowns in the node array
+		while (!node_found)
+#endif
+		{
+#if 1
+			// KLUDGE: make sure we exit this loop at some point
+			loop_count++;
+			if (loop_count == 50) break;
+#endif
+
+			int i;
+			
+			Script::CArray *pNodeArray=Script::GetArray("NodeArray");
+	
+			// Make sure there's a node array
+			Dbg_Assert( pNodeArray != NULL );
+							 
+			i = 0;
+			// scan through it for a crown spawn point
+			while( i < (int)pNodeArray->GetSize() )
+			{
+				uint32	TypeChecksum;
+				Mth::Vector pos;
+				Script::CScriptStructure *pNode=pNodeArray->GetStructure(i);
+				
+				TypeChecksum = 0;
+				pNode->GetChecksum("Type",&TypeChecksum);
+				if( TypeChecksum == 0xaf86421b )	// checksum of "Crown"
+				{
+					// We want the Nth crown spawn pt
+					if( spawn_pt == 0 )
+					{
+						SkateScript::GetPosition( pNode, &pos );
+						crown->SetPosition( pos );
+#if 1
+						node_found = true;
+#endif
+						break;
+					}
+					spawn_pt--;
+				}
+				i++;
+			}
+		}
+
+		crown->RemoveFromKing();
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The player has dropped a ctf flag.  Relocate it.				  */
+/*																  */
+/******************************************************************/
+
+int	Manager::s_handle_dropped_flag( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgFlagMsg* msg;
+	PlayerInfo* player, *local_player;
+
+	gamenet_man = (Manager*) context->m_Data;
+	msg = (MsgFlagMsg*) context->m_Msg;
+	player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+	if( player && player->HasCTFFlag())
+	{
+		char team_str[64];
+		int team;
+		Script::CStruct* pParams;
+		
+		team = player->HasWhichFlag();
+		sprintf( team_str, "team_%d_name", team + 1 );
+		pParams = new Script::CStruct;
+
+		pParams->AddInteger( "team", team );
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		Script::RunScript( "flag_returned", pParams );
+
+		delete pParams;
+
+		// If it's me, remove the message that says "return the flag to your base"
+		if( player->IsLocalPlayer())
+		{
+			Script::RunScript( "destroy_ctf_panel_message" );
+		}
+
+		local_player = gamenet_man->GetLocalPlayer();
+		if( local_player && !local_player->IsObserving())
+		{
+			if( local_player->m_Team == team )
+			{
+				Script::RunScript( "hide_ctf_arrow" );
+			}
+		}
+
+		player->ClearCTFState();
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Streams the level data to the client    						  */
+/*                                                 				  */
+/******************************************************************/
+
+int	Manager::s_handle_request_level( Net::MsgHandlerContext* context )
+{
+	Net::Server* server;
+	MsgRequestLevel* msg;
+	Manager* gamenet_man;
+	PlayerInfo* player;
+
+	gamenet_man = (Manager*) context->m_Data;
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	msg = (MsgRequestLevel*) context->m_Msg;
+		 
+	server = (Net::Server*) context->m_App;
+	Dbg_Printf( "************** STREAMING LEVEL DATA ***************\n" );
+	
+	server->StreamMessage( context->m_Conn->GetHandle(), MSG_ID_LEVEL_DATA, Ed::CParkManager::COMPRESSED_MAP_SIZE, 
+						   Ed::CParkManager::sInstance()->GetCompressedMapBuffer(), "level data", vSEQ_GROUP_PLAYER_MSGS,
+						   false, true );
+						   
+	server->StreamMessage( context->m_Conn->GetHandle(), MSG_ID_RAIL_DATA, Obj::GetRailEditor()->GetCompressedRailsBufferSize(), 
+						   Obj::GetRailEditor()->GetCompressedRailsBuffer(), "rail data", vSEQ_GROUP_PLAYER_MSGS,
+						   false, true );
+						   
+						   
+	if( gamenet_man->UsingCreatedGoals())
+	{
+		gamenet_man->LoadGoals( msg->m_LevelId );
+		server->StreamMessage( context->m_Conn->GetHandle(), MSG_ID_GOALS_DATA, gamenet_man->GetGoalsDataSize(),
+							   gamenet_man->GetGoalsData(), "goals data", vSEQ_GROUP_PLAYER_MSGS, false, true );
+	}
+
+	if( msg->m_Source == MSG_ID_CHANGE_LEVEL )
+	{
+		GameNet::MsgReady ready_msg;
+		MsgChangeLevel change_msg;
+		Net::MsgDesc msg_desc;
+
+		Dbg_Printf( "************** SENDING CHANGE LEVEL DATA ***************\n" );
+			
+		change_msg.m_Level = msg->m_LevelId;
+		change_msg.m_ShowWarning = 0;
+			
+		ready_msg.m_Time = Tmr::GetTime();
+
+		msg_desc.m_Data = &change_msg;
+		msg_desc.m_Length = sizeof(MsgChangeLevel);
+		msg_desc.m_Id = MSG_ID_CHANGE_LEVEL;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	
+		// Don't send them any non-important messages until they're finished loading
+		msg_desc.m_Data = &ready_msg;
+		msg_desc.m_Length = sizeof(MsgReady);
+		msg_desc.m_Id = MSG_ID_READY_QUERY;
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+					
+		player->MarkAsNotReady( ready_msg.m_Time );
+		
+		server->SendData();		// Mick, (true) because we want to send it immediatly
+		
+#ifdef __PLAT_NGPS__
+		//server->WaitForAsyncCallsToFinish();
+#endif
+	} 
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Handles level data from server    						  	  */
+/*                                                 				  */
+/******************************************************************/
+
+int	Manager::s_handle_level_data( Net::MsgHandlerContext* context )
+{
+    Dbg_Printf( "received streamed message! size %d\n", (int)context->m_MsgLength ); 
+	
+	Ed::CParkManager::sInstance()->SetCompressedMapBuffer((uint8*) context->m_Msg, true );
+
+	uint32 crc;
+	crc = Crc::GenerateCRCCaseSensitive((char*) Ed::CParkManager::sInstance()->GetCompressedMapBuffer(), Ed::CParkManager::COMPRESSED_MAP_SIZE );
+	Dbg_Printf( "******************** CHECKSUM OF MAP : %08x\n", crc );
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Handles rail data from server    						  	  */
+/*                                                 				  */
+/******************************************************************/
+
+int	Manager::s_handle_rail_data( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man = (Manager*) context->m_Data;
+    
+	Dbg_Printf( "received streamed message! size %d\n", (int)context->m_MsgLength ); 
+
+	if( context->m_PacketFlags & Net::mHANDLE_CRC_MISMATCH )
+	{
+		Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+		Net::MsgDesc msg_desc;
+
+		if( skate_mod->m_cur_level == CRCD(0x9f2bafb7,"Load_Skateshop"))
+		{
+			Script::CStruct* params;
+
+			params = new Script::CStruct;
+			params->AddChecksum( NONAME, CRCD(0x19eca78e,"show_timeout"));
+
+			// Set it back to the joining state so that cancel_join_server does not
+			// opt out early
+			gamenet_man->m_join_state_task->Remove();
+			gamenet_man->SetJoinState( vJOIN_STATE_JOINING );
+
+			Dbg_Printf( "************** received bad rails data!! Cancelling join.\n" );
+			Script::RunScript( CRCD(0x60b653db,"cancel_join_server" ), params );
+			delete params;
+
+			return Net::HANDLER_MSG_DESTROYED;
+		}
+		else
+		{
+			Dbg_Printf( "************** received bad rails data!! Re-requesting it.\n" );
+			Dbg_Printf( "*** Level was 0x%x : %s\n", skate_mod->m_cur_level, Script::FindChecksumName(skate_mod->m_cur_level));
+
+			msg_desc.m_Id = MSG_ID_REQUEST_RAILS_DATA;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	
+			context->m_App->EnqueueMessageToServer( &msg_desc );
+
+			return Net::HANDLER_MSG_DONE;
+		}
+	}
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	
+	Obj::GetRailEditor()->SetCompressedRailsBuffer((uint8*) context->m_Msg);
+    
+    // InitUsingCompressedRailsBuffer will do an initial cleanup of any existing rails, which may result in
+    // a call to UpdateSuperSectors (due to sectors being deleted).  
+    // Trouble is, UpdateSuperSectors will cause the park to disappear from under the skater, so disable
+    // it temporarily.
+   
+	Obj::CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=false;   
+    Obj::GetRailEditor()->InitUsingCompressedRailsBuffer();
+	Obj::CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=true;
+		
+	Mem::Manager::sHandle().PopContext();
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Handles goals data from server    						  	  */
+/*                                                 				  */
+/******************************************************************/
+
+int Manager::s_handle_goals_data( Net::MsgHandlerContext* context )
+{
+	uint32 level;
+	uint8* goals_data;
+	Manager* gamenet_man;
+	Script::CStruct* params;
+	Net::MsgDesc msg_desc;
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+
+	gamenet_man = (Manager *) context->m_Data;
+
+	Dbg_Printf( "received goals data\n" );
+	if( context->m_PacketFlags & Net::mHANDLE_CRC_MISMATCH )
+	{
+		if( skate_mod->m_cur_level == CRCD(0x9f2bafb7,"Load_Skateshop"))
+		{
+			Script::CStruct* params;
+			Dbg_Printf( "************** received bad goals data!! Cancelling join server.\n" );
+
+			// Set it back to the joining state so that cancel_join_server does not
+			// opt out early
+			gamenet_man->m_join_state_task->Remove();
+			gamenet_man->SetJoinState( vJOIN_STATE_JOINING );
+
+			params = new Script::CStruct;
+			params->AddChecksum( NONAME, CRCD(0x19eca78e,"show_timeout"));
+			Script::RunScript( CRCD(0x60b653db,"cancel_join_server" ), params );
+			delete params;
+			return Net::HANDLER_MSG_DESTROYED;
+		}
+		else
+		{
+			Dbg_Printf( "************** received bad goals data!! Re-requesting it.\n" );
+			
+			msg_desc.m_Id = MSG_ID_REQUEST_GOALS_DATA;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	
+			context->m_App->EnqueueMessageToServer( &msg_desc );
+			return Net::HANDLER_MSG_DONE;
+		}
+	}
+	
+	memcpy( &level, context->m_Msg, sizeof( uint32 ));
+
+	//Dbg_Printf( "*** Level: 0x%x  requested level: 0x%x\n", level, Mdl::Skate::Instance()->m_requested_level );
+	//skate_mod->m_requested_level = level;
+	//skate_mod->m_cur_level = level;
+
+	goals_data = (uint8*) ( context->m_Msg + sizeof( uint32 ));
+	Obj::GetGoalEditor()->ReadFromBuffer( level, goals_data );
+
+	
+
+	params = new Script::CStruct;
+	params->AddChecksum( NONAME, CRCD(0x88001327,"DoNotCreateGoalPeds"));
+	Script::RunScript( CRCD(0x6f4180d0,"InitialiseCreatedGoals"), params );
+	delete params;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Handles any sort of change of level. Makes sure we actually    */
+/* Have that level                                                */
+/******************************************************************/
+
+int	Manager::s_handle_new_level( Net::MsgHandlerContext* context )
+{
+	Dbg_Printf( "***************** Handling new level!!!\n" );
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_change_level( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	MsgChangeLevel* pMsg;
+
+	gamenet_man = (Manager *) context->m_Data;
+	
+	pMsg = (MsgChangeLevel*) context->m_Msg;
+	
+	Dbg_Printf( "********** GameNet:: Got change level\n" );
+	gamenet_man->ClearTriggerEventList();
+	gamenet_man->m_game_over = false;
+	
+	if( pMsg->m_ShowWarning )
+	{
+		Dbg_Printf( "******** GameNet:: IN NET GAME!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" );
+		Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+				
+		// Don't allow two quick change level commands
+		if( gamenet_man->m_change_level_task->InList())
+		{
+			if( !gamenet_man->OnServer())
+			{
+				gamenet_man->m_level_id = pMsg->m_Level;
+			}
+			
+			return Net::HANDLER_MSG_DONE;
+		}
+
+		gamenet_man->m_level_id = pMsg->m_Level;
+		s_time_change_level = Tmr::GetTime() + vTIME_BEFORE_CHANGING_LEVEL;
+
+		if( gamenet_man->OnServer())
+		{                                    
+			Script::CScriptStructure* pTempStructure;
+			Prefs::Preferences* pPreferences;
+			
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, 
+										  gamenet_man->GetLevelName( false ));
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, (int) gamenet_man->m_level_id );
+			pPreferences = gamenet_man->GetNetworkPreferences();
+			pPreferences->SetPreference( Script::GenerateCRC( "level"), pTempStructure );
+			delete pTempStructure;
+
+			// Allow a little extra time for the transmission of level data
+			if( gamenet_man->m_level_id == CRCD(0xb664035d,"Load_Sk5Ed_gameplay"))
+			{
+				s_time_change_level += Tmr::Seconds( 1 );
+			}
+		}
+			
+		mlp_man->AddLogicTask( *gamenet_man->m_change_level_task );
+
+		gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC("net_message_changing_levels"),
+											gamenet_man->GetLevelName(), NULL, NULL, NULL, false, Tmr::Seconds( 5 ));
+		
+		Script::RunScript("hide_console_window");
+		// Don't let this message pass through the Skate. We don't want to change levels just yet
+		return Net::HANDLER_MSG_DONE;
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_kill_flags( Net::MsgHandlerContext* context )
+{
+	Script::RunScript( "Kill_Team_Flags" );
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_game_info( Net::MsgHandlerContext* context )
+{   
+    Manager* gamenet_man;
+	MsgGameInfo* msg;
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Script::CArray* mode_array;
+	int i;
+    
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	msg = (MsgGameInfo*) context->m_Msg;
+	gamenet_man = (Manager *) context->m_Data;
+
+	Dbg_Printf( "Got game info\n" );
+
+	gamenet_man->SetSkaterStartingPoints( msg->m_StartPoints );
+	gamenet_man->m_crown_spawn_point = msg->m_CrownSpawnPoint;
+	gamenet_man->m_waiting_for_game_to_start = false;
+
+	// Invalidate old positions. Player will be re-added to the world when we get a
+	// sufficient number of object updates
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		if( player->IsLocalPlayer() == false )
+		{
+			player->m_Skater->RemoveFromCurrentWorld();
+			player->m_Skater->Resync();
+		}
+	}
+
+	// set up the game mode for the client cause we're now in the lobby
+	// anything that's lobby-dependent should go after this line
+	skate_mod->GetGameMode()->LoadGameType( msg->m_GameMode );
+	
+	// Remove all currently-running goals
+	skate_mod->GetGoalManager()->DeactivateAllGoals();	
+
+	if( gamenet_man->InNetGame())
+	{
+		mode_array = Script::GetArray( "net_game_type_info" );
+	}
+	else
+	{
+		mode_array = Script::GetArray( "mp_game_type_info" );
+	}
+	
+	Dbg_Assert( mode_array );
+
+	{
+		Script::CScriptStructure* pTempStructure = new Script::CScriptStructure;
+
+		// In king of the hill, interpret time limit as a target time
+		if(	( msg->m_TimeLimit == 0 ) &&
+			( msg->m_GameMode != Script::GenerateCRC( "netgoalattack" )))
+		{
+			pTempStructure->AddComponent( Script::GenerateCRC("default_time_limit"), ESYMBOLTYPE_INTEGER, (int) 0 );
+			if( ( msg->m_GameMode != CRCD(0x3d6d444f,"firefight")) &&
+				( msg->m_GameMode != CRCD(0xbff33600,"netfirefight")))
+			{
+				Script::CArray* pArray = new Script::CArray;
+				Script::CopyArray(pArray,Script::GetArray("targetScoreArray") );
+				Script::CScriptStructure* pSubStruct = pArray->GetStructure(0);
+				Dbg_Assert(pSubStruct);
+				pSubStruct->AddComponent(Script::GenerateCRC("score"),ESYMBOLTYPE_INTEGER, msg->m_TargetScore );
+				
+				pTempStructure->AddComponent( Script::GenerateCRC("victory_conditions"), pArray );
+			}
+		}
+		else
+		{
+			pTempStructure->AddComponent( Script::GenerateCRC("default_time_limit"), ESYMBOLTYPE_INTEGER, (int) msg->m_TimeLimit );
+			
+			if( msg->m_GameMode == Script::GenerateCRC( "netctf" ))
+			{
+				Script::CArray* pArray = new Script::CArray;
+				Script::CopyArray(pArray,Script::GetArray("highestScoreArray") );
+				pTempStructure->AddComponent( Script::GenerateCRC("victory_conditions"), pArray );
+			}
+		}
+
+		Dbg_Printf( "***** GOT FIREBALL LEVEL OF %d\n", msg->m_FireballLevel );
+		pTempStructure->AddComponent( CRCD(0xce87e4e3,"fireball_level"), ESYMBOLTYPE_INTEGER, msg->m_FireballLevel );
+		pTempStructure->AddComponent( CRCD(0x48e748b5,"stop_at_zero"), ESYMBOLTYPE_INTEGER, msg->m_StopAtZero );
+	    Dbg_Printf( "******************** Setting time limit to %d\n", (int)msg->m_TimeLimit );
+		skate_mod->SetTimeLimit( msg->m_TimeLimit );
+		skate_mod->GetGameMode()->OverrideOptions( pTempStructure );
+		skate_mod->GetGameMode()->SetNumTeams( msg->m_TeamMode );
+		delete pTempStructure;
+	}
+
+	Script::RunScript( "StartingNewNetGame" );
+	
+	// the following sets up the panel and stuff
+	skate_mod->LaunchGame();
+	gamenet_man->ResetPlayers();
+	gamenet_man->m_game_pending = false;
+	gamenet_man->m_cheating_occurred = false;
+	gamenet_man->MarkReceivedFinalScores( false );
+	gamenet_man->SetCurrentLeader( NULL );
+	gamenet_man->SetCurrentLeadingTeam( vNO_TEAM );
+	gamenet_man->SetNetworkGameId( msg->m_GameId );
+	if( msg->m_GameMode == Script::GenerateCRC( "netgoalattack" ))
+	{
+		skate_mod->GetGoalManager()->InitializeAllSelectedGoals();
+	}
+	gamenet_man->m_game_over = false;
+	
+	// Clear all scores
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		Mdl::Score* score;
+		
+		player->ClearCTFState();
+		player->ResetProjectileVulnerability();
+		if( player->m_Skater )
+		{
+			Obj::CSkaterScoreComponent* p_skater_score_component = GetSkaterScoreComponentFromObject(player->m_Skater);
+			Dbg_Assert(p_skater_score_component);
+			score = p_skater_score_component->GetScore();
+			if( score )
+			{
+				score->Reset();
+			}
+		}
+	}
+    
+	// Clear king of the hill data
+	player = gamenet_man->GetKingOfTheHill();
+	if( player )
+	{
+		player->MarkAsKing( false );
+	}
+	
+	for( i = 0; i < (int)mode_array->GetSize(); i++ )
+	{   
+		uint32 value, script;
+		Script::CStruct* mode_struct;
+		 
+		mode_struct = mode_array->GetStructure( i );
+		Dbg_Assert( mode_struct );
+		
+		mode_struct->GetChecksum( "checksum", &value, true );
+		if( value == msg->m_GameMode )
+		{
+			Script::CStruct* params;
+						
+			params = new Script::CStruct;
+			params->AddInteger( "time", msg->m_TimeLimit );
+			params->AddInteger( "score", msg->m_TargetScore );
+			if( msg->m_TimeLimit == 0 )
+			{
+				params->AddInteger( CRCD(0xf0e712d2,"unlimited_time"), 1 );
+			}
+			else
+			{
+				params->AddInteger( CRCD(0xf0e712d2,"unlimited_time"), 0 );
+			}
+			mode_struct->GetChecksum( "goal_script", &script, true );
+			Script::RunScript( script, params );
+			delete params;
+			break;
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+	
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_select_goals( Net::MsgHandlerContext* context )
+{
+	Net::MsgMax* msg;
+	Game::CGoalManager* pGoalManager;
+	Game::CGoal* pGoal;
+	char* data;
+	uint32 goal_id;
+	char show_summary;
+		
+	msg = (Net::MsgMax*) context->m_Msg;	 
+	data = msg->m_Data;
+	pGoalManager = Game::GetGoalManager();
+	pGoalManager->DeselectAllGoals();
+	show_summary = *data++;
+	memcpy( &goal_id, data, sizeof( uint32 ));
+	data+= sizeof( uint32 );
+	while( goal_id )
+	{
+		pGoal = pGoalManager->GetGoal( goal_id );
+		Dbg_Assert( pGoal );
+
+		pGoal->UnBeatGoal();
+		pGoal->Select();
+		
+		memcpy( &goal_id, data, sizeof( uint32 ));
+		data+= sizeof( uint32 );
+	}
+
+	if( show_summary == 1 )
+	{
+		Script::CStruct* pParams;
+		pParams = new Script::CStruct;
+		pParams->AddChecksum( "goal_summary", Script::GenerateCRC( "goal_summary" ));
+		#ifdef __NOPT_ASSERT__
+		Script::CScript *p_script=Script::SpawnScript( "wait_and_create_view_selected_goals_menu", pParams );
+		p_script->SetCommentString("Spawned from Manager::s_handle_select_goals");
+		#else
+		Script::SpawnScript( "wait_and_create_view_selected_goals_menu", pParams );
+		#endif
+		delete pParams;
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Manager::s_handle_panel_message( Net::MsgHandlerContext* context )
+{
+    GameNet::MsgPanelMessage *msg;
+	Manager* gamenet_man;
+    
+	msg = (GameNet::MsgPanelMessage*) context->m_Msg;
+	gamenet_man = (Manager *) context->m_Data;
+	
+	gamenet_man->CreateNetPanelMessage( false, msg->m_StringId, msg->m_Parm1, msg->m_Parm2, NULL, NULL, false, msg->m_Time );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_run_ended( Net::MsgHandlerContext* context )
+{   
+	Manager* gamenet_man;
+	PlayerInfo* player;
+	bool all_done;
+	unsigned char game_id;
+
+	gamenet_man = (Manager *) context->m_Data;
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	memcpy( &game_id, context->m_Msg, sizeof( unsigned char ));
+	//Dbg_Printf( "**** Got end of run from player\n" );
+
+	if( player )
+	{
+		Lst::Search< PlayerInfo > sh;
+		if( player->m_flags.TestMask( PlayerInfo::mRUN_ENDED ))
+		{
+			//Dbg_Printf( "**** Run had already ended\n" );
+			return Net::HANDLER_MSG_DONE;
+		}
+
+		// Make sure this message pertains to the current game
+		if( game_id != gamenet_man->GetNetworkGameId())
+		{
+			//Dbg_Printf( "**** Wrong game id\n" );
+			return Net::HANDLER_MSG_DONE;
+		}
+		
+		player->m_flags.SetMask( PlayerInfo::mRUN_ENDED );
+		
+		all_done = true;
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player;
+				player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			if( !player->m_flags.TestMask( PlayerInfo::mRUN_ENDED))
+			{
+				all_done = false;
+				break;
+			}
+		}
+
+		if( all_done )
+		{
+			Net::Server* server;
+			Net::MsgDesc msg_desc;
+
+			server = gamenet_man->GetServer();
+			Dbg_Assert( server );
+
+			msg_desc.m_Id = MSG_ID_GAME_OVER;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player;
+				player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}
+		else
+		{
+			//Dbg_Printf( "**** Not all done id\n" );
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_game_over( Net::MsgHandlerContext* context )
+{   
+	Manager* gamenet_man;
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+
+	gamenet_man = (Manager *) context->m_Data;
+	gamenet_man->MarkGameOver();
+
+	if( ( CFuncs::ScriptIsObserving( NULL, NULL )) &&
+		( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0xec200eaa,"netgoalattack" )))
+	{
+		Script::RunScript( CRCD(0xfce4f72d,"create_rankings"));
+	}
+	
+	//Script::RunScript( "set_lobby_mode" );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::s_handle_end_game( Net::MsgHandlerContext* context )
+{   
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CTrickObjectManager* manager = skate_mod->GetTrickObjectManager();
+
+	manager->DeleteAllTrickObjects();
+	Script::RunScript( "create_game_ended_dialog" );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Just calculate the length of the message and pass it back to   */
+/* the dispatcher                                                 */
+/******************************************************************/
+
+int	Manager::s_handle_object_update( Net::MsgHandlerContext* context )
+{   
+	Manager* gamenet_man;
+	int update_flags;
+	unsigned char obj_id, obj_id_mask;
+	Net::BitStream stream;
+    
+	Dbg_Assert( context );
+     
+	gamenet_man = (Manager *) context->m_Data;
+	stream.SetInputData( context->m_Msg, 1024 );
+	obj_id_mask = stream.ReadUnsignedValue( sizeof( char ) * 8 );
+		
+	for( obj_id = 0; obj_id < Mdl::Skate::vMAX_SKATERS; obj_id++ )
+	{
+		int value;
+
+		// If the bit for this player number is not set, that means there is no 
+		// object update for them. Continue
+		if(( obj_id_mask & ( 1 << obj_id )) == 0 )
+		{
+			continue;
+		}
+
+		value = stream.ReadUnsignedValue( sizeof( uint16 ) * 8 );
+		//Dbg_Printf( "Length after timestamp: %d\n", stream.GetByteLength());
+		
+		update_flags = stream.ReadUnsignedValue( 9 );
+		//Dbg_Printf( "Length after update flags: %d\n", stream.GetByteLength());
+		
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_X )
+		{   
+			value = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//Dbg_Printf( "Length after pos X: %d\n", stream.GetByteLength());
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_Y )
+		{
+			value = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//Dbg_Printf( "Length after pos Y: %d\n", stream.GetByteLength());
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_Z )
+		{
+			value = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//Dbg_Printf( "Length after pos Z: %d\n", stream.GetByteLength());
+		}   
+				
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_X )
+		{
+			value = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//Dbg_Printf( "Length after rot X: %d\n", stream.GetByteLength());
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_Y )
+		{
+			value = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//Dbg_Printf( "Length after rot Y: %d\n", stream.GetByteLength());
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_Z )
+		{
+			value = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//Dbg_Printf( "Length after rot Z: %d\n", stream.GetByteLength());
+		}
+        
+		if( update_flags & GameNet::mUPDATE_FIELD_STATE )
+		{   
+			value = stream.ReadUnsignedValue( 4 );
+			//Dbg_Printf( "Length after state1: %d\n", stream.GetByteLength());
+			value = stream.ReadUnsignedValue( 6 );
+			value = stream.ReadUnsignedValue( 1 );
+			value = stream.ReadUnsignedValue( 1 );
+			//Dbg_Printf( "Length after state2: %d\n", stream.GetByteLength());
+		}
+
+		if( update_flags & GameNet::mUPDATE_FIELD_FLAGS )
+		{
+			value = stream.ReadUnsignedValue( 8 );
+			//Dbg_Printf( "Length after flags: %d\n", stream.GetByteLength());
+		}
+
+		if( update_flags & GameNet::mUPDATE_FIELD_RAIL_NODE )
+		{
+			value = stream.ReadSignedValue( sizeof( sint16 ) * 8 );
+			//Dbg_Printf( "Length After Rail Node: %d\n", stream.GetByteLength());
+		}
+	}
+			
+	// Tell the dispatcher the size of this message because it does not know (optimization)
+	context->m_MsgLength = stream.GetByteLength();
+	//Dbg_Printf( "Message Length %d\n", context->m_MsgLength );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Someone has requested to change teams						  */
+/*                                                  			  */
+/******************************************************************/
+
+int	Manager::s_handle_team_change_request( Net::MsgHandlerContext* context )
+{
+	Manager* gamenet_man;
+	PlayerInfo* change_player, *player;
+	MsgChangeTeam* msg;
+	Net::Server* server;
+	MsgChangeTeam change_msg;
+	Net::MsgDesc msg_desc;
+	Lst::Search< PlayerInfo > sh;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		
+	gamenet_man = (Manager *) context->m_Data;
+	
+	msg = (MsgChangeTeam*) context->m_Msg;
+	change_player = gamenet_man->GetPlayerByObjectID( msg->m_ObjID );
+	if(( change_player == NULL ) ||
+	   ( change_player->m_Skater == NULL ) ||
+	   ( skate_mod->GetGameMode()->GetNameChecksum() != Script::GenerateCRC( "netlobby" )))	// ignore unless in lobby
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	change_msg.m_ObjID = msg->m_ObjID;
+	change_msg.m_Team = msg->m_Team;
+
+	server = gamenet_man->GetServer();
+	Dbg_Assert( server );
+
+	msg_desc.m_Data = &change_msg;
+	msg_desc.m_Length = sizeof( MsgChangeTeam );
+	msg_desc.m_Id = MSG_ID_TEAM_CHANGE;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+				player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Someone has changed teams  									  */
+/*                                                  			  */
+/******************************************************************/
+
+int	Manager::s_handle_team_change( Net::MsgHandlerContext* context )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Manager* gamenet_man;
+	PlayerInfo* player, *other_player;
+	Lst::Search< PlayerInfo > sh;
+	MsgChangeTeam* msg;
+	int num_other_players;
+		
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgChangeTeam*) context->m_Msg;
+	
+	player = gamenet_man->GetPlayerByObjectID( msg->m_ObjID );
+	if( player )
+	{
+		char team_str[64];
+		Script::CStruct* pParams;
+		bool all_same_team;
+		
+		player->m_Team = msg->m_Team;
+		sprintf( team_str, "team_%d_name", msg->m_Team + 1 );
+		pParams = new Script::CStruct;
+		if( player->IsLocalPlayer())
+		{
+			if( skate_mod->GetGameMode()->IsTeamGame())
+			{
+				pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+				Script::RunScript( "joined_team_you", pParams );
+			}
+		}
+		else
+		{
+			if( skate_mod->GetGameMode()->IsTeamGame())
+			{
+				pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, player->m_Name );
+				pParams->AddComponent( Script::GenerateCRC("String1"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+				Script::RunScript( "joined_team_other", pParams );
+			}
+		}
+		
+		delete pParams;
+
+		all_same_team = true;
+		num_other_players = 0;
+		for( other_player = gamenet_man->FirstPlayerInfo( sh ); other_player; 
+				other_player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			if( other_player != player )
+			{
+				num_other_players++;
+				if( msg->m_Team != other_player->m_Team )
+				{
+					all_same_team = false;
+				}
+			}
+		}
+
+		if( all_same_team && ( num_other_players > 0 ))
+		{
+			if( skate_mod->GetGameMode()->NumTeams() > 0 )
+			{
+				Script::RunScript( CRCD(0x16c9b0dc,"warn_all_same_team"));
+			}
+		}
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client beat a goalattack goal. Verify that no other teammate	  */
+/* has beaten it and relay it to all clients involved  			  */
+/******************************************************************/
+
+int Manager::s_handle_beat_goal( Net::MsgHandlerContext* context )
+{
+	MsgBeatGoal *msg;
+	Manager* gamenet_man;
+	Game::CGoalManager* pGoalManager;
+	Game::CGoal* pGoal;    
+		
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgBeatGoal*) context->m_Msg;
+    
+	Dbg_Printf( "*** Got s_handle_beat_goal: 0x%x\n", msg->m_GoalId );
+	// Make sure this message pertains to the current game
+	if( msg->m_GameId != gamenet_man->GetNetworkGameId())
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	pGoalManager = Game::GetGoalManager();
+	pGoal = pGoalManager->GetGoal( msg->m_GoalId );
+	if( pGoal )
+	{
+		PlayerInfo* player;
+		bool already_beaten;
+
+		player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+		Dbg_Assert( player );
+
+		already_beaten = false;
+		if( player->m_Skater )
+		{   
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+			// If we're in a team game, flag the goal as completed for all team members just in case someone quits later.
+			// Also, uninitialize the goal
+			if( skate_mod->GetGameMode()->IsTeamGame())
+			{
+				PlayerInfo* team_player;
+				Lst::Search< PlayerInfo > sh;
+
+				for( team_player = gamenet_man->FirstPlayerInfo( sh ); team_player; 
+						team_player = gamenet_man->NextPlayerInfo( sh ))
+				{
+					if( player->m_Team == team_player->m_Team )
+					{
+						if( pGoal->HasWonGoal( team_player->m_Skater->GetID()))
+						{
+							already_beaten = true;
+							break;
+						}
+					}
+				}
+				
+				if( !already_beaten )
+				{
+					pGoal->MarkBeatenBy( player->m_Skater->GetID());
+					// Notify all team members of the fact that the goal was beaten
+					for( team_player = gamenet_man->FirstPlayerInfo( sh ); team_player; 
+							team_player = gamenet_man->NextPlayerInfo( sh ))
+					{
+						MsgBeatGoalRelay goal_msg;
+						Net::MsgDesc msg_desc;
+
+						goal_msg.m_GoalId = msg->m_GoalId;
+						goal_msg.m_ObjId = player->m_Skater->GetID();
+						
+						msg_desc.m_Data = &goal_msg;
+						msg_desc.m_Length = sizeof( MsgBeatGoalRelay );
+						msg_desc.m_Id = MSG_ID_BEAT_GOAL;
+						msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+						msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+						
+						context->m_App->EnqueueMessage( team_player->GetConnHandle(), &msg_desc );
+					}
+				}
+			}
+			else
+			{
+				if( pGoal->HasWonGoal( player->m_Skater->GetID()) == false )
+				{
+					PlayerInfo* send_player;
+					Lst::Search< PlayerInfo > sh;
+					MsgBeatGoalRelay goal_msg;
+					Net::MsgDesc msg_desc;
+
+					pGoal->MarkBeatenBy( player->m_Skater->GetID());
+
+					goal_msg.m_GoalId = msg->m_GoalId;
+					goal_msg.m_ObjId = player->m_Skater->GetID();
+
+					msg_desc.m_Data = &goal_msg;
+					msg_desc.m_Length = sizeof( MsgBeatGoalRelay );
+					msg_desc.m_Id = MSG_ID_BEAT_GOAL;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					for( send_player = gamenet_man->FirstPlayerInfo( sh ); send_player; 
+							send_player = gamenet_man->NextPlayerInfo( sh ))
+					{
+						context->m_App->EnqueueMessage( send_player->GetConnHandle(), &msg_desc );
+					}
+				}
+			}
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client beat a goalattack goal						  	  	  */
+/*                                                  			  */
+/******************************************************************/
+
+int Manager::s_handle_beat_goal_relay( Net::MsgHandlerContext* context )
+{
+	MsgBeatGoalRelay *msg;
+	Manager* gamenet_man;
+	Game::CGoalManager* pGoalManager;
+	Game::CGoal* pGoal, *pCurrentGoal;
+	int current_goal_index;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgBeatGoalRelay*) context->m_Msg;
+    
+	pGoalManager = Game::GetGoalManager();
+	pGoal = pGoalManager->GetGoal( msg->m_GoalId );
+	pCurrentGoal = NULL;
+	current_goal_index = pGoalManager->GetActiveGoal( true );
+	if( current_goal_index >= 0 )
+	{
+		pCurrentGoal = pGoalManager->GetGoalByIndex( current_goal_index );
+	}
+
+	if( pGoal )
+	{
+		PlayerInfo* local_player, *player;
+
+		player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+		local_player = gamenet_man->GetLocalPlayer();
+		Dbg_Assert( player );
+
+		pGoal->MarkBeatenByTeam( player->m_Team );
+
+		if( player->m_Skater )
+		{
+			pGoal->MarkBeatenBy( player->m_Skater->GetID());
+
+			// If it's not us, it must be a teammate
+			if( local_player == player )
+			{
+				int num_goals, num_beaten;
+				Script::CStruct* pParams;
+					
+				num_goals = pGoalManager->GetNumSelectedGoals();
+				num_beaten = pGoalManager->NumGoalsBeaten();
+		
+				if( num_goals > num_beaten )
+				{
+					pParams = new Script::CStruct;
+					pParams->AddInteger( "NumGoalsLeft", num_goals - num_beaten );
+					Script::RunScript( "goal_attack_completed_goal", pParams );
+					delete pParams;
+				}
+
+				pGoal->mp_goalPed->DestroyGoalPed();
+			}
+			else
+			{
+				int num_goals, num_beaten;
+				Script::CStruct* pParams;
+				bool in_same_goal;
+					
+				in_same_goal = 	( pCurrentGoal != NULL ) && 
+								( pGoal->GetRootGoalId() == pCurrentGoal->GetRootGoalId());
+				if( ( skate_mod->GetGameMode()->IsTeamGame()) && 
+					( local_player->m_Team == player->m_Team ))
+				{
+					if( in_same_goal )
+					{
+						if( pGoal->GetChildren()->m_relative != 0 )
+						{
+							Game::CGoal* pChildGoal = pGoal;	
+							
+							// get the root node
+							Game::CGoalLink* p_child = pGoal->GetChildren();
+							while ( p_child && p_child->m_relative != 0 )
+							{
+								pChildGoal = pGoalManager->GetGoal( p_child->m_relative );
+								p_child = pChildGoal->GetChildren();
+							}
+						
+							pChildGoal->Win();
+							if( pChildGoal != pCurrentGoal )
+							{
+								pCurrentGoal->Deactivate();
+							}
+						}
+						else
+						{
+							pGoal->Win();
+						}
+
+						pCurrentGoal->mp_goalPed->DestroyGoalPed();
+					}
+					else
+					{
+						pGoal->mp_goalPed->DestroyGoalPed();
+					}
+					
+					pGoal->MarkBeaten();
+	
+					num_goals = pGoalManager->GetNumSelectedGoals();
+					num_beaten = pGoalManager->NumGoalsBeaten();
+			
+					if( num_goals > num_beaten )
+					{
+						const char* p_view_goals_text = NULL;
+	
+						pGoal->GetViewGoalsText( &p_view_goals_text );
+						pParams = new Script::CStruct;
+						pParams->AddInteger( CRCD(0x684a8396, "NumGoalsLeft"), num_goals - num_beaten );
+						pParams->AddString( CRCD(0x9196d920, "PlayerName"), player->m_Name );
+						pParams->AddString( CRCD(0xb8a88b50, "GoalText"), p_view_goals_text );
+						Script::RunScript( CRCD(0xf4748e47, "goal_attack_completed_goal_other_same_team"), pParams );
+						delete pParams;
+					}
+
+					
+				}
+			}
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client started a goalattack goal. 							  */
+/* Relay it to all clients			  			  				  */
+/******************************************************************/
+
+int Manager::s_handle_started_goal( Net::MsgHandlerContext* context )
+{
+	MsgStartedGoal *msg;
+	Manager* gamenet_man;
+	Game::CGoalManager* pGoalManager;
+	Game::CGoal* pGoal;    
+		
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgStartedGoal*) context->m_Msg;
+    
+	// Make sure this message pertains to the current game
+	if( msg->m_GameId != gamenet_man->GetNetworkGameId())
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	pGoalManager = Game::GetGoalManager();
+	pGoal = pGoalManager->GetGoal( msg->m_GoalId );
+	if( pGoal )
+	{
+		PlayerInfo* player;
+
+		player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+		Dbg_Assert( player );
+
+		if( player->m_Skater )
+		{   
+			PlayerInfo* other_player;
+			Lst::Search< PlayerInfo > sh;
+			MsgStartedGoalRelay goal_msg;
+			Net::MsgDesc msg_desc;
+
+			goal_msg.m_GoalId = msg->m_GoalId;
+			goal_msg.m_ObjId = player->m_Skater->GetID();
+
+			msg_desc.m_Data = &goal_msg;
+			msg_desc.m_Length = sizeof( MsgStartedGoalRelay );
+			msg_desc.m_Id = MSG_ID_STARTED_GOAL;
+			/* Dan: used to only sent to teammates
+			for( team_player = gamenet_man->FirstPlayerInfo( sh ); team_player; 
+					team_player = gamenet_man->NextPlayerInfo( sh ))
+			{
+				if( player == team_player )
+				{
+					continue;
+				}
+				if( player->m_Team == team_player->m_Team )
+				{
+					context->m_App->EnqueueMessage( team_player->GetConnHandle(), &msg_desc );
+				}
+			}
+			*/
+			for( other_player = gamenet_man->FirstPlayerInfo( sh ); other_player; 
+					other_player = gamenet_man->NextPlayerInfo( sh ))
+			{
+				if( player == other_player )
+				{
+					continue;
+				}
+				context->m_App->EnqueueMessage( other_player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client started a goalattack goal						  	  	  */
+/*                                                  			  */
+/******************************************************************/
+
+int Manager::s_handle_started_goal_relay( Net::MsgHandlerContext* context )
+{
+	MsgStartedGoalRelay *msg;
+	Manager* gamenet_man;
+	Game::CGoalManager* pGoalManager;
+	Game::CGoal* pGoal;
+    
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgStartedGoalRelay*) context->m_Msg;
+    
+	pGoalManager = Game::GetGoalManager();
+	pGoal = pGoalManager->GetGoal( msg->m_GoalId );
+	if( pGoal )
+	{
+		Script::CStruct* pParams;
+		const char* p_view_goals_text = NULL;
+		PlayerInfo* player;
+
+		player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+		Dbg_Assert( player );
+
+		if (gamenet_man->GetLocalPlayer()->m_Team == player->m_Team)
+		{
+			pGoal->GetViewGoalsText( &p_view_goals_text );
+			pParams = new Script::CStruct;
+			pParams->AddString( CRCD(0x9196d920, "PlayerName"), player->m_Name );
+			pParams->AddString( CRCD(0xb8a88b50, "GoalText"), p_view_goals_text );
+			Script::RunScript( CRCD(0xa77758de, "goal_attack_started_goal_other_same_team"), pParams );
+			delete pParams;
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Toggle this proset						  	  				  */
+/*                                                  			  */
+/******************************************************************/
+
+int Manager::s_handle_toggle_proset( Net::MsgHandlerContext* context )
+{
+	MsgToggleProSet* msg;
+	Script::CStruct* pParams;
+	Manager* gamenet_man;
+
+	msg = (MsgToggleProSet*) context->m_Msg;
+	gamenet_man = (Manager *) context->m_Data;
+	pParams = new Script::CStruct;
+
+	Dbg_Printf( "********************* handle toggle proset" );
+
+	Script::RunScript( "toggle_proset_flag", Script::GetStructure( msg->m_ParamId, Script::ASSERT ));
+	Script::RunScript( "toggle_geo_nomenu", Script::GetStructure( msg->m_ParamId, Script::ASSERT ));
+
+	if( gamenet_man->OnServer() == false )
+	{
+		gamenet_man->m_proset_flags.Toggle( msg->m_Bit );
+	}
+
+	delete pParams;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Someone has sent us a chat message						  	  */
+/*                                                  			  */
+/******************************************************************/
+
+int Manager::s_handle_chat( Net::MsgHandlerContext* context )
+{
+	MsgChat* chat_msg;
+	Script::CStruct* p_params;
+	char final_msg[256];
+	
+	chat_msg = (MsgChat*) context->m_Msg;
+	p_params = new Script::CStruct;	
+	sprintf( final_msg, "\\c%i%s\\c0 : %s", ( chat_msg->m_ObjId + 2 ), chat_msg->m_Name, chat_msg->m_ChatMsg );
+	p_params->AddString( "text", final_msg );
+	Script::RunScript("create_console_message", p_params );
+	delete p_params;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Server has changed the number of teams						  */
+/*                                                  			  */
+/******************************************************************/
+
+int	Manager::s_handle_set_num_teams( Net::MsgHandlerContext* context )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Manager* gamenet_man;
+	MsgByteInfo* msg;
+	int num_teams;
+    
+	gamenet_man = (Manager *) context->m_Data;
+	msg = (MsgByteInfo*) context->m_Msg;
+	num_teams = msg->m_Data;
+
+	if( ( num_teams > 0 ) &&
+		( !skate_mod->GetGameMode()->IsTeamGame()))
+	{
+		skate_mod->GetGameMode()->SetNumTeams( num_teams );
+
+		Script::RunScript( "ChooseTeamMessage" );
+		if( skate_mod->GetLocalSkater())
+		{
+			Script::RunScript( "PrepareSkaterForMove", NULL, skate_mod->GetLocalSkater());
+			skate_mod->move_to_restart_point( skate_mod->GetLocalSkater());
+		}
+	}
+	else
+	{
+		skate_mod->GetGameMode()->SetNumTeams( num_teams );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Server requests client's cheat checksum				  		  */
+/*                                                  			  */
+/******************************************************************/
+
+int	Manager::s_handle_cheat_checksum_request( Net::MsgHandlerContext* context )
+{
+	MsgCheatChecksum* msg;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Net::MsgDesc msg_desc;
+
+	msg = (MsgCheatChecksum*) context->m_Msg;
+	msg->m_ClientChecksum = skate_mod->GetCheatChecksum();
+
+	msg_desc.m_Id = MSG_ID_CHEAT_CHECKSUM_RESPONSE;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	msg_desc.m_Data = msg;
+	msg_desc.m_Length = sizeof( MsgCheatChecksum );
+
+	context->m_App->EnqueueMessageToServer( &msg_desc );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Server has sent a GameSpy stats challenge					  */
+/*                                                  			  */
+/******************************************************************/
+
+int	Manager::s_handle_challenge( Net::MsgHandlerContext* context )
+{
+#ifdef __PLAT_NGPS__
+	Net::MsgDesc msg_desc;
+	char* challenge;
+	char response[33];
+	Prefs::Preferences* pPreferences;
+	Manager* gamenet_man;
+	
+	gamenet_man = (Manager*) context->m_Data;
+
+	pPreferences = gamenet_man->GetNetworkPreferences();
+	const char* password = pPreferences->GetPreferenceString( Script::GenerateCRC( "profile_password" ), Script::GenerateCRC("ui_string") );
+
+	challenge = context->m_Msg;
+	gamenet_man->mpStatsMan->GenerateAuthResponse( challenge, (char*) password, response );
+
+	msg_desc.m_Id = MSG_ID_CHALLENGE_RESPONSE;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	msg_desc.m_Data = response;
+	msg_desc.m_Length = 33;
+
+	context->m_App->EnqueueMessageToServer( &msg_desc );
+#endif
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client has answered the GameSpy stats challenge				  */
+/*                                                  			  */
+/******************************************************************/
+
+int	Manager::s_handle_challenge_response( Net::MsgHandlerContext* context )
+{
+#ifdef __PLAT_NGPS__
+	PlayerInfo* player;
+	Manager* gamenet_man;
+	
+	gamenet_man = (Manager*) context->m_Data;
+
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	gamenet_man->mpStatsMan->AuthorizePlayer( player->m_Skater->GetID(), context->m_Msg );
+#endif
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client has requested that we re-send level data to them		  */
+/*                                                  			  */
+/******************************************************************/
+
+int Manager::s_handle_level_data_request( Net::MsgHandlerContext* context )
+{
+	Net::Server* server;
+	Manager* gamenet_man;
+
+	server = (Net::Server*) context->m_App;
+	gamenet_man = (Manager*) context->m_Data;
+
+	if( context->m_MsgId == MSG_ID_REQUEST_RAILS_DATA )
+	{
+		Dbg_Printf( "***** Client requested rails data\n" );
+		server->StreamMessage( context->m_Conn->GetHandle(), MSG_ID_RAIL_DATA, Obj::GetRailEditor()->GetCompressedRailsBufferSize(), 
+							   Obj::GetRailEditor()->GetCompressedRailsBuffer(), "rail data", vSEQ_GROUP_PLAYER_MSGS,
+							   false, true );
+							   
+	}
+	else if( context->m_MsgId == MSG_ID_REQUEST_GOALS_DATA )
+	{
+		Dbg_Printf( "***** Client requested goals data\n" );
+		if( gamenet_man->UsingCreatedGoals())
+		{
+			server->StreamMessage( context->m_Conn->GetHandle(), MSG_ID_GOALS_DATA, gamenet_man->GetGoalsDataSize(),
+								   gamenet_man->GetGoalsData(), "goals data", vSEQ_GROUP_PLAYER_MSGS, false, true );
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Client has responded to the cheat checksum request			  */
+/*                                                  			  */
+/******************************************************************/
+
+int	Manager::s_handle_cheat_checksum_response( Net::MsgHandlerContext* context )
+{
+	MsgCheatChecksum* msg;
+	PlayerInfo* player;
+	Manager* gamenet_man;
+
+	gamenet_man = (Manager*) context->m_Data;
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	msg = (MsgCheatChecksum*) context->m_Msg;
+	if(( msg->m_ServerChecksum ^ 0xDEADFACE ) != msg->m_ClientChecksum )
+	{
+		Dbg_Printf( "***** CHEATING! 0x%x  0x%x\n", msg->m_ServerChecksum ^ 0xDEADFACE, msg->m_ClientChecksum );
+		if( gamenet_man->HasCheatingOccurred() == false )
+		{
+			Script::CStruct* p_params;
+	
+			p_params = new Script::CStruct;
+			p_params->AddString( CRCD(0xa4b08520,"String0"), player->m_Name );
+			Script::RunScript( CRCD(0x62ddbe0a,"notify_client_cheating"), p_params );
+			delete p_params;
+			
+			gamenet_man->CheatingOccured();
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_change_level_code( const Tsk::Task< Manager >& task )
+{
+	Manager& man = task.GetData();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	if(( Tmr::GetTime() < s_time_change_level ))
+	{
+		return;
+	}
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	skate_mod->ChangeLevel( man.m_level_id );
+
+	// Clear the king of the hill
+	if(( player = man.GetKingOfTheHill()))
+	{
+		player->MarkAsKing( false );
+	}
+
+	for( player = man.FirstPlayerInfo( sh, true ); player; player = man.NextPlayerInfo( sh, true ))
+	{
+		player->ClearCTFState();
+	}
+
+	//man.RespondToReadyQuery();
+	man.m_change_level_task->Remove();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ok_to_join( int& reason, MsgConnectInfo* connect_msg, Net::Conn* conn )
+{
+	char* password;
+	
+	if( m_server->IsConnectionBanned( conn ))
+	{
+		reason = JOIN_REFUSED_ID_BANNED;
+		return false;
+	}
+	if( connect_msg->m_Observer )
+	{
+		if( GetNumObservers() >= GetMaxObservers())
+		{
+			reason = JOIN_REFUSED_ID_FULL_OBSERVERS;
+			return false;
+		}
+	}
+	else
+	{
+		if( GetNumPlayers() >= GetMaxPlayers())
+		{
+			reason = JOIN_REFUSED_ID_FULL;
+			return false;
+		}
+	}
+		
+	// Version check
+	if( connect_msg->m_Version != vVERSION_NUMBER )
+	{
+		reason = JOIN_REFUSED_ID_VERSION;
+		Dbg_Printf( "Client had the wrong version. Dropping connection\n" );
+		return false;
+	}
+		
+	// Game-in-progress check
+	if( !connect_msg->m_WillingToWait && !connect_msg->m_Observer )
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+		if(( skate_mod->GetGameMode()->GetNameChecksum() != Script::GenerateCRC( "netlobby" )) &&
+		   ( GameIsOver() == false ))
+		{
+			reason = JOIN_REFUSED_ID_IN_PROGRESS;
+			return false;
+		}
+	}
+
+	// Check if we're password-protected. If so, compare passwords
+	password = GetPassword();
+	if( password[0] != '\0' )
+	{
+		Dbg_Printf( "Comparing passwords %s and %s\n", password, connect_msg->m_Password );
+		if( strcmp( password, connect_msg->m_Password ))
+		{          
+			reason = JOIN_REFUSED_ID_PW;
+
+			Dbg_Printf( "Client gave the wrong password. Dropping connection\n" );
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/GameMsg.h b/Code/Sk/GameNet/GameMsg.h
new file mode 100644
index 0000000..1c243e6
--- /dev/null
+++ b/Code/Sk/GameNet/GameMsg.h
@@ -0,0 +1,813 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate3													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		GameMsg.h												**
+**																			**
+**	Created by:		02/20/01	-	spg										**
+**																			**
+**	Description:	Game-Side Network Messages								**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_GAMEMSG_H
+#define __GAMENET_GAMEMSG_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+						
+
+enum
+{
+	MSG_ID_SKATER_INPUT	= Net::MSG_ID_USER,	//	= 32 : C->S The Client's Pad Input
+	MSG_ID_YOUR_PLAYER_CREATE,				//	= 33 : S->C Create local skater
+	MSG_ID_PLAYER_CREATE,					//	= 34 : S->C Each player's info
+	MSG_ID_OBJ_UPDATE_STREAM,				//	= 35 : S->C Stream of object updates
+	MSG_ID_NEW_ALIAS,						//	= 36 : C->S Alias suggestion
+	MSG_ID_READY_QUERY,						//	= 37 : S->C Are you ready to receive more data?
+	MSG_ID_READY_RESPONSE,					//	= 38 : C->S or S->C Ready query response
+	MSG_ID_PAUSE,							//	= 39 : S->C Pause instruction
+	MSG_ID_UNPAUSE,							//	= 40 : S->C UnPause instruction
+	MSG_ID_PRIM_ANIM_START,					//	= 41 : S->C Play primary animation
+	MSG_ID_ROTATE_SKATEBOARD,				//	= 42 : S->C Rotate skateboard
+	MSG_ID_SET_WOBBLE_TARGET,				//	= 43 : S->C Set wobble target
+	MSG_ID_FLIP_ANIM,						//	= 44 : S->C Flip the animation
+	MSG_ID_PLAYER_QUIT,						//	= 45 : S->C Notification of a player quitting
+	MSG_ID_HOST_REQ,						//  = 46 : GameServer->MatchMaker Game Host Request
+	MSG_ID_HOST_PROCEED,					//  = 47 : MatchMaker->GameServer Proceed hosting
+	MSG_ID_GAMELIST_REQ,					//  = 48 : GameClient->MatchMaker Game list request
+	MSG_ID_GAMELIST_RESPONSE,				//  = 49 : MatchMaker->GameClient Game list response
+	MSG_ID_HOST_QUIT,						//	= 50 : S->C Server is shutting down
+	MSG_ID_START_INFO,						//	= 51 : S->C Startup Game Option
+	MSG_ID_GAME_INFO,						//	= 52 : S->C Game Options
+	MSG_ID_PLAY_SOUND,						//	= 53 : S->C Play Sound
+	MSG_ID_PLAY_LOOPING_SOUND,				// 	= 54 : S->C Play Looping Sound
+	MSG_ID_SPARKS_ON,						//	= 55 : S->C Sparks on
+	MSG_ID_SPARKS_OFF,						//	= 56 : S->C Sparks off
+	MSG_ID_BLOOD_ON,						//	= 57 : S->C Blood on
+	MSG_ID_BLOOD_OFF,						//	= 58 : S->C Blood off
+	MSG_ID_SCORE,							//	= 59 : S->C General Score Msg: See SCORE_MSG_ID...
+	MSG_ID_SET_WOBBLE_DETAILS,				//	= 60 : S->C Set the wobbling parameters
+	MSG_ID_SET_LOOPING_TYPE,				//	= 61 : S->C Set the desired looping type
+	MSG_ID_SET_HIDE_ATOMIC,					//	= 62 : S->C Hide an arbitrary atomic on the skin model
+	MSG_ID_PANEL_MESSAGE,					//	= 63 : S->C Display this panel message
+	MSG_ID_SCORE_UPDATE,					//	= 64 : S->C Score update for other clients
+	MSG_ID_SET_ANIM_SPEED,					//	= 65 : S->C Set animation speed
+	MSG_ID_PROCEED_TO_PLAY,					//	= 66 : S->C Init phase complete: Next state
+	MSG_ID_SKATER_COLLIDE_LOST,				//	= 67 : S->C You were involved in a skater collision and lost
+	MSG_ID_SKATER_COLLIDE_WON,				//	= 68 : S->C You were involved in a skater collision and won
+	MSG_ID_BAIL_DONE,						//	= 69 : C->S Bail is done
+	MSG_ID_CHANGE_LEVEL,					//	= 70 : S->C Change level
+	MSG_ID_JOIN_REFUSED,					//	= 71 : S->C Server refused to allow player to join
+	MSG_ID_RUN_SCRIPT,						//  = 72 : C->C Run this script
+	MSG_ID_SPAWN_AND_RUN_SCRIPT,			//  = 73 : C->C Spawn and Run this script
+	MSG_ID_OBSERVE,							//	= 74 : C->S A request to enter observer mode
+	MSG_ID_STEAL_MESSAGE,					//	= 75 : S->C A request to display a "steal message" in graffiti
+	MSG_ID_CHAT,							//	= 76 : C->C Chat message
+	MSG_ID_OBSERVE_PROCEED,					//  = 77 : S->C Proceed to observe
+	MSG_ID_OBSERVE_REFUSED,					//	= 78 : S->C Server says you can't observe
+	MSG_ID_JOIN_PROCEED,					//	= 79 : S->C Go ahead and attempt to join
+	MSG_ID_JOIN_REQ,						//  = 80 : C->S Request to join a connected server
+	MSG_ID_KICKED,							//	= 81 : S->C You've been kicked
+	MSG_ID_LANDED_TRICK,					//	= 82 : S->C Landed a trick
+	MSG_ID_PLAYER_INFO_ACK_REQ,				//	= 83 : S->C Have you received all the player info data?
+	MSG_ID_PLAYER_INFO_ACK,					//	= 84 : C->S Yes I have received all the player info data
+	MSG_ID_NEW_KING,						//	= 85 : S->C Crown the new king
+	MSG_ID_LEFT_OUT,						//  = 86 : S->C Sorry. We're playing without you
+	MSG_ID_MOVED_TO_RESTART,				//	= 87 : C->S Client has moved to a restart
+	MSG_ID_KING_DROPPED_CROWN,				//	= 88 : S->C Spawn a new crown in the world
+	MSG_ID_RUN_HAS_ENDED,					//	= 89 : C->S My run has ended
+	MSG_ID_GAME_OVER,						//	= 90 : S->C Game is over
+	MSG_ID_OBSERVER_LOG_TRICK_OBJ,			//	= 91 : S->C Special-case observer trick object report
+	MSG_ID_OBSERVER_INIT_GRAFFITI_STATE,	//	= 92 : S->C Special-case observer, to synchronize trick objects with the current graffiti state
+	MSG_ID_PRINTF,							//  = 93 : S->C Printf
+	MSG_ID_AUTO_SERVER_NOTIFICATION,		//	= 94 : S->C Notify the client that he's on an auto-serving server
+	MSG_ID_FCFS_ASSIGNMENT,					//	= 95 : S->C Notify the client that he's the acting host
+	MSG_ID_FCFS_START_GAME,					//	= 96 : C->S Request to start a game from a FCFS
+	MSG_ID_FCFS_BAN_PLAYER,					//	= 97 : C->S Request to ban a player from a FCFS
+	MSG_ID_FCFS_CHANGE_LEVEL,				//	= 98 : C->S Request to change levels from a FCFS
+	MSG_ID_FCFS_TOGGLE_PROSET,				//	= 99 : C->S Request to toggle a pro set
+	MSG_ID_FCFS_TOGGLE_GOAL_SELECTION,		//	= 100 : C->S Request to toggle a goal selection
+	MSG_ID_FCFS_END_GAME,					//	= 101 : C->S Request to end the current game
+	MSG_ID_FCFS_SET_NUM_TEAMS,				//	= 102 : C->S Request to set the number of teams
+	MSG_ID_REQUEST_CHANGE_TEAM,				//	= 103 : C->S Request to change teams
+	MSG_ID_TEAM_CHANGE,						//	= 104 : S->C Team change update
+	MSG_ID_SET_NUM_TEAMS,					//	= 105 : S->C Change in the number of teams
+	MSG_ID_END_GAME,						//	= 106 : S->C Server has chosen to end the game
+	MSG_ID_REQUEST_LEVEL,					//	= 107 : C->S Client requests level data
+	MSG_ID_LEVEL_DATA,						//	= 108 : S->C Server sending client level data
+	MSG_ID_SELECT_GOALS,					//	= 109 : S->C A list of goals to complete
+	MSG_ID_BEAT_GOAL,						//	= 110 : C->S I beat a goal
+	MSG_ID_STARTED_GOAL,					//	= 111 : C->S I started a goal
+	MSG_ID_TOGGLE_PROSET,					//	= 112 : S->C Toggle this proset
+	MSG_ID_TOOK_FLAG,						//	= 113 : S->C Player took a ctf flag
+	MSG_ID_CAPTURED_FLAG,					//	= 114 : S->C Player captured a ctf flag
+	MSG_ID_RETRIEVED_FLAG,					//	= 115 : S->C Player retrieved a ctf flag
+	MSG_ID_STOLE_FLAG,						//	= 116 : S->C Player stole a ctf flag
+	MSG_ID_PLAYER_DROPPED_FLAG,				//	= 117 : S->C Player dropped a flag
+	MSG_ID_ROTATE_DISPLAY,					//	= 118 : S->C Rotate the skater by this much over time
+	MSG_ID_CREATE_SPECIAL_ITEM,				//	= 119 : S->C Create a special item
+	MSG_ID_DESTROY_SPECIAL_ITEM,			//	= 120 : S->C Destroy a special item
+	MSG_ID_JOIN_ACCEPTED,					//	= 121 : S->C You're in - completely
+	MSG_ID_KILL_TEAM_FLAGS,					//	= 122 : S->C Kill team flag (i.e. no more switching teams)
+	MSG_ID_WAIT_N_SECONDS,					//	= 123 : S->C Prepare for a period of N seconds of no communication
+	MSG_ID_CHALLENGE,						//	= 124 : S->C GameSpy Stats Challenge
+	MSG_ID_CHALLENGE_RESPONSE,				//	= 125 : C->S GameSpy Stats Challenge Response
+	MSG_ID_FACE_DATA,						//	= 126 : C->C Custom face data
+	MSG_ID_TOGGLE_CHEAT,					//	= 127 : S->C Toggle a cheat on or off
+	MSG_ID_CHEAT_CHECKSUM_REQUEST,			//	= 128 : S->C Server requesting client's cheat checksum
+	MSG_ID_CHEAT_CHECKSUM_RESPONSE,			//	= 129 : C->S Client responding to server's request
+	MSG_ID_CHEAT_LIST,						//	= 130 : S->C A list of all active cheats
+	MSG_ID_SPAWN_PROJECTILE,				//  = 131 : C->C Spawn a projectile
+	MSG_ID_SKATER_HIT_BY_PROJECTILE,		//	= 132 : S->C You were hit by a projectile
+	MSG_ID_SKATER_PROJECTILE_HIT_TARGET,	//	= 133 : S->C You hit someone with a projectile
+	MSG_ID_COMBO_REPORT,					//	= 134 : C->S Reporting combo
+	MSG_ID_GOALS_DATA,						//	= 135 : S->C Server sending client goals data
+	MSG_ID_RAIL_DATA,						//	= 136 : S->C Server sending edited rail data
+	MSG_ID_ENTER_VEHICLE,					//  = 137 : C->S Client entering a vehicle
+	MSG_ID_REQUEST_GOALS_DATA,				//	= 138 : C->S Re-requesting goals data
+	MSG_ID_REQUEST_RAILS_DATA,				//	= 139 : C->S Re-requesting rails data
+	MSG_ID_CLEAR_ROTATE_DISPLAY,			//	= 140 : S->C Clear any model rotations
+};
+
+enum
+{
+	SCORE_MSG_ID_LAND,						// = 0 : S->C Tabulate final score
+	SCORE_MSG_ID_LOG_TRICK_OBJECT,			// = 1 : S->C Color an object
+};
+
+enum
+{
+	JOIN_REFUSED_ID_FULL,					// = 0 : S->C Server Full
+	JOIN_REFUSED_ID_PW,						// = 1 : S->C Wrong Password
+	JOIN_REFUSED_ID_VERSION,				// = 2 : S->C Wrong version
+	JOIN_REFUSED_ID_IN_PROGRESS,			// = 3 : S->C Game is in progress
+	JOIN_REFUSED_ID_BANNED,					// = 4 : S->C You've been banned
+	JOIN_REFUSED_ID_FULL_OBSERVERS,			// = 5 : S->C Server has no more room for observers
+};
+
+enum
+{
+	vSEQ_GROUP_PLAYER_MSGS = 8,				// Messages about new players & quitting players
+	vSEQ_GROUP_FACE_MSGS,					// Face download messages
+};
+
+enum
+{
+	vMAX_CONNECTIONS			= 20,
+	vMAX_PLAYERS 				= 8,
+	vMAX_SERVER_NAME_LEN 		= 15,
+	vMAX_PLAYER_NAME_LEN 		= 15,
+	vMAX_APPEARANCE_DATA_SIZE 	= 4096,
+	vMAX_PASSWORD_LEN			= 9,
+	vMAX_CHAT_MSG_LEN			= 127,
+	vMAX_TRICK_OBJECTS_IN_LEVEL = 256,	// actually custom parks can have more than 256 trick objects, so we'll need to address that in THPS4
+};
+
+enum
+{
+#ifdef __PLAT_XBOX__
+	vCONNECTION_TIMEOUT = 5000,		//  5 seconds in ms
+#else
+	vCONNECTION_TIMEOUT = 20000,	//	30 seconds in ms
+#endif
+	vNOT_READY_TIMEOUT	= 60000,	//	Clients are marked "not ready" when we tell them to load
+									//	levels/skaters/etc.  If that time exceeds this timeout
+									//	we should disconnect them
+	vJOIN_TIMEOUT		= 60000,	//  20 seconds in ms to join a server
+};
+
+enum
+{
+	vHOST_PORT 		= 5150,
+	vJOIN_PORT		= 5151,
+};
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							Messages										**
+*****************************************************************************/
+
+class MsgReady
+{
+public:
+	unsigned int	m_Time;
+};
+
+class MsgPlayPrimaryAnim
+{
+public:
+	//uint32			m_Time;
+	uint32			m_Index;
+	char			m_LoopingType;
+	char			m_ObjId;
+	unsigned short	m_StartTime;
+	unsigned short	m_EndTime;
+	unsigned short	m_BlendPeriod;
+	unsigned short	m_Speed;
+};
+
+class MsgFlagMsg
+{
+public:
+	char m_ObjId;
+	char m_Team;
+};
+
+class MsgFlipAnim
+{
+public:
+	//uint32			m_Time;
+	char			m_ObjId;
+	char			m_Flipped;
+};
+
+class MsgRotateSkateboard
+{
+public:
+	//uint32			m_Time;
+	char			m_ObjId;
+	char			m_Rotate;
+};
+
+class MsgRotateDisplay
+{
+public:
+	//uint32			m_Time;
+	int 			m_Duration;
+	int 			m_SinePower;
+	short 			m_StartAngle;
+	short 			m_DeltaAngle;
+	char 			m_ObjId;
+	bool			m_HoldOnLastAngle;
+	char			m_Flags;
+};
+
+class MsgSetWobbleTarget
+{
+public:
+	//uint32			m_Time;
+	char			m_ObjId;
+	unsigned char	m_Alpha;
+};
+
+class MsgSetWobbleDetails
+{
+public:
+	enum
+	{
+		vWOBBLE_AMP_A,
+		vWOBBLE_AMP_B,
+		vWOBBLE_K1,
+		vWOBBLE_K2,
+		vSPAZFACTOR,
+
+		mWOBBLE_AMPA_MASK	= 0x03,
+		mWOBBLE_AMPB_MASK	= 0x04,
+		mWOBBLE_K1_MASK		= 0x08,
+		mWOBBLE_K2_MASK		= 0x10,
+		mSPAZFACTOR_MASK	= 0x20,
+	};
+
+	//uint32			m_Time;
+	char			m_WobbleDetails;
+	char			m_ObjId;
+};
+
+class MsgSetLoopingType
+{
+public:
+	//uint32			m_Time;
+	char			m_ObjId;
+	char			m_LoopingType;
+};
+
+class MsgSpecialItem
+{
+public:
+	//uint32			m_Time;
+	unsigned int	m_Params;
+	uint32			m_Bone;
+	char			m_ObjId;
+	char			m_Index;
+};
+
+class MsgHideAtomic
+{
+public:
+	//uint32			m_Time;
+	uint32			m_AtomicName;
+	char			m_ObjId;
+	char			m_Hide;
+};
+
+class MsgSetAnimSpeed
+{
+	public:
+		//uint32			m_Time;
+		float			m_AnimSpeed;
+		char			m_ObjId;
+};
+
+// Game options sent to joining players
+class MsgStartInfo
+{
+public:
+	char			m_CrownSpawnPoint;
+	char			m_Broadband;	// boolean
+	unsigned char	m_GameId;
+	char			m_ProSetFlags;
+	char			m_MaxPlayers;
+	int				m_TimeLeft;
+	unsigned long	m_GameMode;
+	uint32			m_LevelId;
+	uint32			m_TeamMode;
+	
+	int				m_TimeLimit;
+	int				m_TargetScore;
+	int				m_StartPoints[vMAX_PLAYERS];
+};
+
+class MsgInitGraffitiState
+{
+public:
+	uint8			m_NumTrickObjects[vMAX_PLAYERS];
+	uint8			m_TrickObjectStream[vMAX_TRICK_OBJECTS_IN_LEVEL];
+};
+
+class MsgGameInfo
+{
+public:
+	unsigned long	m_GameMode;
+	unsigned long	m_TimeLimit;
+	unsigned long	m_TargetScore;
+	int				m_FireballLevel;
+	char			m_NumPlayers;
+	char			m_AccumulateScore;
+	char			m_DegradeScore;
+	char			m_UseClock;
+	char			m_StopAtZero;
+	unsigned char	m_GameId;
+	uint32			m_TeamMode;
+	char			m_CrownSpawnPoint;
+	int				m_StartPoints[vMAX_PLAYERS];
+};
+
+class MsgConnectInfo
+{
+public:
+	int				m_Version;
+	char			m_Observer;	// Does this player just want to observe?
+	char			m_WillingToWait;	// Willing to sit out until the next lobby
+	char			m_Password[ vMAX_PASSWORD_LEN + 1 ];
+};
+
+class MsgJoinInfo : public MsgConnectInfo
+{
+public:
+	int				m_Profile;
+	int				m_Rating;
+	char			m_Broadband;	// boolean
+	uint32			m_VehicleControlType;
+	char			m_Name[ vMAX_PLAYER_NAME_LEN + 1 ];
+	uint8			m_AppearanceData[vMAX_APPEARANCE_DATA_SIZE];	// stream information for player appearance
+};
+
+class MsgNewPlayer
+{
+public:
+	int			m_Profile;
+	int			m_Rating;
+	int			m_Flags;
+	int			m_Score;
+	char		m_ObjId;	// object id for this player
+	char		m_Team;
+	uint32		m_VehicleControlType;
+	char		m_Name[ vMAX_PLAYER_NAME_LEN + 1 ];
+	uint8		m_AppearanceData[vMAX_APPEARANCE_DATA_SIZE]; // stream information for player appearance
+};
+
+class MsgPlayerQuit
+{
+public:
+	char	m_ObjId;
+	char	m_Reason;
+};
+
+class MsgPanelMessage
+{
+public:
+	enum
+	{
+		vMAX_STRING_PARAM_LENGTH = vMAX_PLAYER_NAME_LEN,
+	};
+
+	uint32	m_PropsStructureCRC;
+	uint32	m_StringId;
+	int		m_Time;
+	char	m_Parm1[vMAX_STRING_PARAM_LENGTH + 1];
+	char	m_Parm2[vMAX_STRING_PARAM_LENGTH + 1];
+};
+
+class MsgNewAlias
+{
+public:
+	unsigned char	m_Alias;
+	char		m_ObjId;
+	int			m_Expiration;
+};
+
+#ifdef __PLAT_XBOX__
+class MsgFindServer : public Net::MsgTimestamp
+{
+public:
+	BYTE m_Nonce[8]; 
+};
+#else
+typedef Net::MsgTimestamp	MsgFindServer;
+#endif
+
+class MsgServerDescription
+{
+public:
+#ifdef __PLAT_XBOX__
+	BYTE	m_Nonce[8]; 
+	XNKID	m_XboxKeyId;
+	XNKEY	m_XboxKey;
+	XNADDR	m_XboxAddr;
+#endif
+	char	m_NumPlayers;
+	char	m_MaxPlayers;
+	char	m_NumObservers;
+	char	m_MaxObservers;
+	char	m_Password;	// boolean
+	char	m_GameStarted;
+	char	m_HostMode;
+	char	m_Ranked;
+	char	m_Name[GameNet::vMAX_SERVER_NAME_LEN + 1];
+	char	m_Level[32];
+	char	m_Mode[32];
+	char	m_SkillLevel;
+	int		m_Timestamp;
+	char	m_PlayerNames[vMAX_PLAYERS][ vMAX_PLAYER_NAME_LEN + 1 ];
+};
+
+class ObjectUpdate
+{
+public:
+	short			m_ObjId;
+	Mth::Vector		m_Pos;
+	Mth::Matrix		m_Matrix;
+};
+
+class MsgScriptedSound
+{
+public:
+	char			m_ObjId;
+	uint32 			m_SoundChecksum;
+	int 			m_Volume;
+	int 			m_Pitch;
+};
+
+class MsgPlaySound
+{
+public:
+	short 			m_Pos[3];
+	char 			m_WhichArray;
+	char			m_SurfaceFlag;
+	char 			m_VolPercent;
+	char			m_PitchPercent;
+	char			m_SoundChoice;
+};
+
+class MsgObjMessage
+{
+public:
+	char			m_ObjId;
+};
+
+class MsgPlayLoopingSound
+{
+public:
+	char			m_ObjId;
+	int 			m_WhichArray;
+	int				m_SurfaceFlag;
+	float 			m_VolPercent;
+};
+
+class MsgBlood : public MsgObjMessage
+{
+public:
+	char			m_BodyPart;
+	float			m_Size;
+	float			m_Frequency;
+	float			m_RandomRadius;
+};
+
+class MsgBloodOff : public MsgObjMessage
+{
+public:
+	char			m_BodyPart;
+};
+
+class MsgActuator
+{
+public:
+	char			m_ActuatorId;	// left/right
+	char			m_Percent;		// between 1 and 100
+	unsigned short	m_Duration;		// ms duration
+};
+
+class MsgEmbedded
+{
+public:
+	char	m_SubMsgId;		// Sub-message
+};
+
+class MsgScoreLogTrickObject : public MsgEmbedded
+{
+public:
+	enum
+	{
+		vMAX_PENDING_TRICKS = 512,	// how much data we can send across
+	};
+	
+	unsigned char	m_GameId;
+	int		m_OwnerId;
+	int		m_Score;
+	uint32	m_NumPendingTricks;
+	uint32	m_PendingTrickBuffer[vMAX_PENDING_TRICKS];
+};
+
+class MsgObsScoreLogTrickObject
+{
+public:
+	enum
+	{
+		vMAX_PENDING_TRICKS = 512,	// how much data we can send across
+	};
+	
+	unsigned char	m_GameId;
+	int		m_OwnerId;
+	uint32	m_NumPendingTricks;
+	uint32	m_PendingTrickBuffer[vMAX_PENDING_TRICKS];
+};
+
+class MsgScoreLanded
+{
+public:
+	unsigned char	m_GameId;
+	int		m_Score;
+};
+
+class MsgBeatGoal
+{
+public:
+	unsigned char	m_GameId;
+	uint32			m_GoalId;
+};
+
+class MsgBeatGoalRelay
+{
+public:
+	char			m_ObjId;
+	uint32			m_GoalId;
+};
+
+typedef MsgBeatGoal MsgStartedGoal;
+typedef MsgBeatGoalRelay MsgStartedGoalRelay;
+
+class MsgScoreUpdate
+{
+public:
+	int		m_TimeLeft;		// Time left in the game
+	Flags< int > m_Cheats;
+	char	m_Final;		// Final scores?
+	char	m_NumScores;
+	char	m_ScoreData[256];
+};
+
+class MsgSpawnAndRunScript
+{
+public:
+	uint32	m_ScriptName;
+	int		m_ObjID;
+	int		m_Node;
+	char	m_Permanent;	// is this an event that the server should queue up for new joiners?
+};
+
+class MsgToggleProSet
+{
+public:
+	uint32	m_ParamId;
+	char	m_Bit;
+};
+
+class MsgToggleCheat
+{
+public:
+	uint32	m_Cheat;
+	char	m_On;
+};
+
+class MsgEnabledCheats
+{
+public:
+	enum
+	{
+		vMAX_CHEATS = 32,
+	};
+	int		m_NumCheats;
+	uint32	m_Cheats[vMAX_CHEATS];
+};
+
+class MsgStealMessage
+{
+public:
+	unsigned char	m_GameId;
+	int		m_NewOwner;		// new owner of the trick object
+	int		m_OldOwner;		// old owner of the trick object
+};
+
+class MsgRunScript
+{
+public:
+	enum
+	{
+		vMAX_SCRIPT_PARAMS_LEN = 1024
+	};
+	uint32	m_ScriptName;
+	int		m_ObjID;
+	char	m_Data[vMAX_SCRIPT_PARAMS_LEN];
+};
+
+class MsgChangeLevel
+{
+public:
+	int		m_Level;
+	char	m_ShowWarning;
+};
+
+class MsgChangeTeam
+{
+public:
+	char	m_ObjID;
+	char	m_Team;
+};
+
+class MsgByteInfo		// Generic one-byte message
+{
+public:
+	char	m_Data;
+};
+
+class MsgIntInfo		// Generic four-byte message
+{
+	public:
+		int		m_Data;
+};
+
+class MsgChat
+{
+	public:
+		char	m_ObjId;
+		char	m_Name[vMAX_PLAYER_NAME_LEN + 1];
+		char	m_ChatMsg[vMAX_CHAT_MSG_LEN + 1];
+};
+
+class MsgRequestLevel
+{
+public:
+	uint32		m_LevelId;
+	char		m_Source;	// Was this request the result of a "StartInfo" message or a "Change Level" message
+};
+
+class MsgRemovePlayerRequest
+{
+public:
+	char	m_Ban;	// Should we ban? If not, just kick
+	char	m_Index;
+	char	m_Name[vMAX_PLAYER_NAME_LEN + 1];
+};
+
+class MsgStartGameRequest
+{
+public:
+	uint32	m_GameMode;
+	uint32	m_TimeLimit;
+	uint32	m_SkillLevel;
+	uint32	m_FireballLevel;
+	uint32	m_PlayerCollision;
+	uint32	m_FriendlyFire;
+	uint32	m_TeamMode;
+	uint32	m_TargetScore;
+	uint32	m_StopAtZero;
+	uint32	m_CTFType;
+};
+
+class MsgCheatChecksum
+{
+public:
+	uint32	m_ServerChecksum;
+	uint32	m_ClientChecksum;
+};
+
+class MsgProjectile
+{
+public:
+	uint32		m_Latency;
+	Mth::Vector	m_Pos;
+	Mth::Vector	m_Vel;
+	uint32		m_Type;
+	int			m_Radius;
+	float		m_Scale;
+	char		m_Id;
+};
+
+class MsgProjectileHit
+{
+public:
+	char	m_Id;
+	int		m_Damage;
+
+};
+
+class MsgCollideLost
+{
+public:
+	char		m_Id;
+	Mth::Vector	m_Offset;
+};
+
+class MsgEnterVehicle
+{
+public:
+	char		m_Id;
+	uint32		m_ControlType;
+};
+
+class MsgProceed
+{
+public:
+	int			m_PublicIP;
+	int			m_PrivateIP;
+	int			m_Port;
+	char		m_ServerName[GameNet::vMAX_SERVER_NAME_LEN + 1];
+	char		m_MaxPlayers;
+	char		m_Broadband;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_GAMEMSG_H
+
+
diff --git a/Code/Sk/GameNet/GameNet.cpp b/Code/Sk/GameNet/GameNet.cpp
new file mode 100644
index 0000000..1fd887e
--- /dev/null
+++ b/Code/Sk/GameNet/GameNet.cpp
@@ -0,0 +1,8283 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		GameNet.cpp												**
+**																			**
+**	Created by:		02/01/01	-	spg										**
+**																			**
+**	Description:	Game-Side Network Functionality							**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC gamenet
+// @module gamenet | None
+// @subindex Scripting Database
+// @index script | gamenet
+                          
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+//#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGPS__
+#include 
+#include 
+#include 
+#include 
+#endif
+
+#ifdef __PLAT_XBOX__
+#include 
+#include 
+#include 
+#endif
+
+#include 		   // we do various things with the skater, network play related
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+extern bool g_CheatsEnabled;
+
+namespace GameNet
+{
+
+
+
+DefineSingletonClass( Manager, "Game Network Manager" )
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+#ifndef __PLAT_NGC__
+extern	bool	 gEnteringFromMainMenu;
+#endif		// __PLAT_NGC__
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+#define USE_DNS	1
+enum
+{   
+	vMAX_TOLERABLE_RESENDS = 20	// we'll allow 20 reseonds of messages to ready coonections but that's our limit
+};
+
+#define vTIME_BEFORE_STARTING_NETGAME   Tmr::Seconds( 3 )
+#define vTIME_BEFORE_STARTING_GOALATTACK   Tmr::Seconds( 8 )
+#define vAUTO_START_INTERVAL			Tmr::Seconds( 5 )
+#define vLINK_CHECK_FREQUENCY			Tmr::Seconds( 1 )
+#define vTAUNT_INTERVAL			Tmr::Seconds( 2 )
+#define vMAX_FACE_DATA_SIZE		( Gfx::CFaceTexture::vTOTAL_CFACETEXTURE_SIZE )
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static Tmr::Time	s_time_to_start_game;
+static Tmr::Time	s_auto_serve_start_time;
+//static bool			s_waiting_for_game_to_start = false;
+//static Tmr::Time	s_usage_tracking_start_time;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+Manager::Manager( void )
+{
+	Net::Manager * net_man = Net::Manager::Instance();
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	int i;
+
+	
+
+	net_man->SetMessageName( MSG_ID_SKATER_INPUT, "Skater Input" );
+	net_man->SetMessageName( MSG_ID_PLAYER_CREATE, "Player Create" );
+	net_man->SetMessageName( MSG_ID_JOIN_ACCEPTED, "Join Accepted" );
+	net_man->SetMessageName( MSG_ID_OBJ_UPDATE_STREAM, "Object Update Stream" );
+	net_man->SetMessageName( MSG_ID_NEW_ALIAS, "New Alias" );
+	net_man->SetMessageName( MSG_ID_READY_QUERY, "Ready Query" );
+	net_man->SetMessageName( MSG_ID_READY_RESPONSE, "Ready Response" );
+	net_man->SetMessageName( MSG_ID_PAUSE, "Pause" );
+	net_man->SetMessageName( MSG_ID_UNPAUSE, "Unpause" );
+	net_man->SetMessageName( MSG_ID_PRIM_ANIM_START, "Primary Animation" );
+	net_man->SetMessageName( MSG_ID_FLIP_ANIM, "Flip Animation" );
+	net_man->SetMessageName( MSG_ID_ROTATE_SKATEBOARD, "Rotate Skateboard" );
+	net_man->SetMessageName( MSG_ID_ROTATE_DISPLAY, "Rotate Display" );
+	net_man->SetMessageName( MSG_ID_CLEAR_ROTATE_DISPLAY, "Clear Rotate Display" );
+	net_man->SetMessageName( MSG_ID_CREATE_SPECIAL_ITEM, "Create Item" );
+	net_man->SetMessageName( MSG_ID_DESTROY_SPECIAL_ITEM, "Destroy Item" );
+	net_man->SetMessageName( MSG_ID_SET_WOBBLE_TARGET, "Set Wobble Target" );
+	net_man->SetMessageName( MSG_ID_SET_WOBBLE_DETAILS, "Set Wobble Details" );
+	net_man->SetMessageName( MSG_ID_SET_LOOPING_TYPE, "Set Looping Type" );
+	net_man->SetMessageName( MSG_ID_SET_HIDE_ATOMIC, "Hide Atomic" );
+	net_man->SetMessageName( MSG_ID_SET_ANIM_SPEED, "Set anim speed" );
+	net_man->SetMessageName( MSG_ID_PLAYER_QUIT, "Player Quit" );	
+	net_man->SetMessageName( MSG_ID_HOST_PROCEED, "Proceed Hosting" );
+	net_man->SetMessageName( MSG_ID_GAMELIST_RESPONSE, "Gamelist" );
+	net_man->SetMessageName( MSG_ID_HOST_QUIT, "Host Shutdown" );
+	net_man->SetMessageName( MSG_ID_START_INFO, "Start Info" );
+	net_man->SetMessageName( MSG_ID_GAME_INFO, "Game Info" );
+	net_man->SetMessageName( MSG_ID_PLAY_SOUND, "Play Sound" );
+	net_man->SetMessageName( MSG_ID_PLAY_LOOPING_SOUND, "Play Looping Sound" );
+	net_man->SetMessageName( MSG_ID_SPARKS_ON, "Sparks On" );
+	net_man->SetMessageName( MSG_ID_SPARKS_OFF, "Sparks Off" );
+	net_man->SetMessageName( MSG_ID_BLOOD_ON, "Blood On" );
+	net_man->SetMessageName( MSG_ID_BLOOD_OFF, "Blood Off" );
+	net_man->SetMessageName( MSG_ID_SCORE, "Score Msg" );
+	net_man->SetMessageName( MSG_ID_PANEL_MESSAGE, "Panel Message" );
+	net_man->SetMessageName( MSG_ID_SCORE_UPDATE, "Score Update" );
+	net_man->SetMessageName( MSG_ID_PROCEED_TO_PLAY, "Proceed To Play" );
+	net_man->SetMessageName( MSG_ID_SKATER_COLLIDE_LOST, "Skater Col Lost" );
+	net_man->SetMessageName( MSG_ID_SKATER_COLLIDE_WON, "Skater Col Win" );
+	net_man->SetMessageName( MSG_ID_SKATER_HIT_BY_PROJECTILE, "Hit by projectile" );
+	net_man->SetMessageName( MSG_ID_SKATER_PROJECTILE_HIT_TARGET, "Projectile Hit" );
+	net_man->SetMessageName( MSG_ID_BAIL_DONE, "Bail done" );
+	net_man->SetMessageName( MSG_ID_RUN_SCRIPT, "Run Script" );
+	net_man->SetMessageName( MSG_ID_SPAWN_AND_RUN_SCRIPT, "SpawnRun Script" );
+	net_man->SetMessageName( MSG_ID_OBSERVE, "Observe" );
+	net_man->SetMessageName( MSG_ID_STEAL_MESSAGE, "Steal message" );
+	net_man->SetMessageName( MSG_ID_CHAT, "Chat Message" );
+	net_man->SetMessageName( MSG_ID_OBSERVE_PROCEED, "Proceed to Observe" );
+	net_man->SetMessageName( MSG_ID_OBSERVE_REFUSED, "Observe Refused" );
+	net_man->SetMessageName( MSG_ID_JOIN_PROCEED, "Proceed to Join" );
+	net_man->SetMessageName( MSG_ID_JOIN_REQ, "Join Request" );
+	net_man->SetMessageName( MSG_ID_KICKED, "Kick Message" );
+	net_man->SetMessageName( MSG_ID_LANDED_TRICK, "Landed Trick" );
+	net_man->SetMessageName( MSG_ID_PLAYER_INFO_ACK_REQ, "Player Ack Req" );
+	net_man->SetMessageName( MSG_ID_PLAYER_INFO_ACK, "Player Info Ack" );
+	net_man->SetMessageName( MSG_ID_NEW_KING, "New King" );
+	net_man->SetMessageName( MSG_ID_LEFT_OUT, "Left Out" );
+	net_man->SetMessageName( MSG_ID_MOVED_TO_RESTART, "Moved to Restart" );
+	net_man->SetMessageName( MSG_ID_KING_DROPPED_CROWN, "Dropped Crown" );
+	net_man->SetMessageName( MSG_ID_PLAYER_DROPPED_FLAG, "Dropped Flag" );
+	net_man->SetMessageName( MSG_ID_RUN_HAS_ENDED, "Run ended" );
+	net_man->SetMessageName( MSG_ID_GAME_OVER, "Game over" );
+	net_man->SetMessageName( MSG_ID_OBSERVER_LOG_TRICK_OBJ, "Observer Log Trick Object" );
+	net_man->SetMessageName( MSG_ID_OBSERVER_INIT_GRAFFITI_STATE, "Observer Init Graffiti State" );
+	net_man->SetMessageName( MSG_ID_AUTO_SERVER_NOTIFICATION, "Auto-Server Notification" );
+	net_man->SetMessageName( MSG_ID_FCFS_ASSIGNMENT, "FCFS Assignment" );
+	net_man->SetMessageName( MSG_ID_FCFS_END_GAME, "FCFS End Game" );
+	net_man->SetMessageName( MSG_ID_FCFS_SET_NUM_TEAMS, "FCFS Num Teams" );
+	net_man->SetMessageName( MSG_ID_END_GAME, "End Game" );
+	net_man->SetMessageName( MSG_ID_SELECT_GOALS, "Select Goals" );
+	net_man->SetMessageName( MSG_ID_BEAT_GOAL, "Beat Goal" );
+	net_man->SetMessageName( MSG_ID_STARTED_GOAL, "Started Goal" );
+	net_man->SetMessageName( MSG_ID_TOGGLE_PROSET, "Toggle proset" );
+	net_man->SetMessageName( MSG_ID_FCFS_TOGGLE_PROSET, "FCFS Toggle proset" );
+	net_man->SetMessageName( MSG_ID_FCFS_TOGGLE_GOAL_SELECTION, "FCFS Toggle Goal" );
+	net_man->SetMessageName( MSG_ID_KILL_TEAM_FLAGS, "Kill Team Flags" );
+	net_man->SetMessageName( MSG_ID_WAIT_N_SECONDS, "Wait N Seconds" );
+	net_man->SetMessageName( MSG_ID_CHEAT_CHECKSUM_REQUEST, "CC Request" );
+	net_man->SetMessageName( MSG_ID_CHEAT_CHECKSUM_RESPONSE, "CC Response" );
+    
+	net_man->SetMessageFlags( MSG_ID_OBJ_UPDATE_STREAM, Net::mMSG_SIZE_UNKNOWN );
+		
+	for( i = 0; i < vMAX_LOCAL_CLIENTS; i++ )
+	{
+		m_client[i] = NULL;
+		m_match_client = NULL;
+	}
+	
+	m_server = NULL;
+    
+	m_metrics_on = false;
+	m_scores_on = false;
+	m_cheating_occurred = false;
+	m_draw_player_names = true;
+	m_sort_key = vSORT_KEY_NAME;
+	m_host_mode = vHOST_MODE_SERVE;
+	m_server_list_state = vSERVER_LIST_STATE_SHUTDOWN;
+	m_next_server_list_state = vSERVER_LIST_STATE_SHUTDOWN;
+    
+	// initialize the network preferences
+	m_network_preferences.Load( Script::GenerateCRC("default_network_preferences" ) );
+	m_taunt_preferences.Load( Script::GenerateCRC("default_taunt_preferences" ) );
+
+	m_timeout_connections_task = new Tsk::Task< Manager > ( s_timeout_connections_code, *this,
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_TIMEOUT_CONNECTIONS );
+	m_render_metrics_task = new Tsk::Task< Manager > ( s_render_metrics_code, *this );
+    m_auto_refresh_task = new Tsk::Task< Manager > ( s_auto_refresh_code, *this );
+	m_auto_server_task = new Tsk::Task< Manager > ( s_auto_server_code, *this );
+	m_render_scores_task = new Tsk::Task< Manager > ( s_render_scores_code, *this );
+	m_client_add_new_players_task = new Tsk::Task< Manager > ( s_client_add_new_players_code, *this, 
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_CLIENT_ADD_NEW_PLAYERS );
+	m_server_add_new_players_task = new Tsk::Task< Manager > ( s_server_add_new_players_code, *this, 
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_SERVER_ADD_NEW_PLAYERS );
+    m_enter_chat_task = new Tsk::Task< Manager > ( s_enter_chat_code, *this );
+	m_modem_state_task = new Tsk::Task< Manager > ( s_modem_state_code, *this );
+	m_join_timeout_task = new Tsk::Task< Manager > ( s_join_timeout_code, *this );
+	m_server_list_state_task = new Tsk::Task< Manager > ( s_server_list_state_code, *this );
+	m_join_state_task = new Tsk::Task< Manager > ( s_join_state_code, *this );
+	m_start_network_game_task = new Tsk::Task< Manager > ( s_start_network_game_code, *this );
+	m_change_level_task = new Tsk::Task< Manager > ( s_change_level_code, *this );
+	m_ctf_logic_task = new Tsk::Task< Manager > ( s_ctf_logic_code, *this );
+
+	mlp_manager->AddLogicTask( *m_timeout_connections_task );
+
+	m_last_modem_state = Net::vMODEM_STATE_DISCONNECTED;
+	m_goals_data_size = 0;
+	m_goals_level = 0;
+
+#ifdef __PLAT_XBOX__
+	m_XboxKeyRegistered = false;
+	mpAuthMan = new AuthMan;
+	mpBuddyMan = new BuddyMan;
+	mpVoiceMan = new VoiceMan;
+#endif
+
+#ifdef __PLAT_NGPS__
+	m_got_motd = false;
+	mpLobbyMan = new LobbyMan;
+	mpContentMan = new ContentMan;
+	mpBuddyMan = new BuddyMan;
+	mpStatsMan = new StatsMan;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+#ifdef __PLAT_NGPS__
+	delete mpLobbyMan;
+	delete mpContentMan;
+	delete mpBuddyMan;
+	delete mpStatsMan;
+#endif
+
+	delete m_client_add_new_players_task;
+	delete m_server_add_new_players_task;
+	delete m_timeout_connections_task;
+	delete m_render_metrics_task;
+	delete m_enter_chat_task;
+	delete m_server_list_state_task;
+	delete m_join_state_task;
+	delete m_start_network_game_task;
+	delete m_change_level_task;
+    delete m_auto_refresh_task;
+#ifdef __PLAT_XBOX__
+	delete mpAuthMan;
+	delete mpBuddyMan;
+	delete mpVoiceMan;
+#endif // __PLAT_XBOX__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::free_all_players( void )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	PlayerInfo *player, *next;
+	Lst::Search< PlayerInfo > sh;
+
+	for( player = FirstPlayerInfo( sh, true ); player; player = next )
+	{
+		next = NextPlayerInfo( sh, true );
+		// if this player has a skater
+		if( player->m_Skater )
+		{
+			// and the skater is not on the default heap, or it's not a local player
+			if ( player->m_Skater->GetHeapIndex() != 0 || !player->IsLocalPlayer() )
+			{
+				// then remove it (deletes the CSkater, which will unload any model)
+				skate_mod->remove_skater( player->m_Skater );
+			}
+		}
+		DestroyPlayer( player );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::free_all_pending_players( void )
+{
+	Lst::Node< NewPlayerInfo > *node, *next;
+	NewPlayerInfo* new_info;
+		
+	
+
+	Dbg_Printf( "Destroying Pending Players\n" );
+	for( node = m_new_players.GetNext(); node; node = next )
+	{
+		next = node->GetNext();
+
+		new_info = node->GetData();
+
+		if( m_server )
+		{
+			Dbg_Printf( "Removing %s\n", new_info->Name );
+			m_server->TerminateConnection( new_info->Conn );
+		}
+		
+		delete node;
+		delete new_info;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::s_observer_logic_code ( const Tsk::Task< PlayerInfo >& task )
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	// Dan: Proxim_Update use is now done through CProximTriggerComponents attached to the camera CCompositeObjects.  Thus, the observer's
+	// camera should handle proxim updating	on its own.
+	
+	// PlayerInfo* current_player;
+	// current_player = gamenet_man->GetCurrentlyObservedPlayer();
+	// if( current_player )
+	// {
+		// Obj::CSkater* skater;
+		// skater = local_player.m_cam->GetSkater();
+	
+		// if( skater )
+		// {   
+			// Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			// if( current_player->IsFullyIn())
+			// {
+				// local_player.m_cam->Update( false );
+			
+				// if( skate_mod->mpProximManager )
+				// {   
+					// if( local_player.m_cam )
+					// {
+						// Obj::Proxim_Update( skater, local_player.m_cam );
+					// }
+				// }
+			// }
+		// }
+	// }
+
+	if( gamenet_man->GetObserverCommand() == vOBSERVER_COMMAND_NEXT )
+	{
+		Dbg_Printf( "Got observer next command.....\n" );
+
+		PlayerInfo*	player;
+		
+		player = gamenet_man->GetNextPlayerToObserve();
+		gamenet_man->ObservePlayer( player );
+
+		gamenet_man->SetObserverCommand( vOBSERVER_COMMAND_NONE );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_observer_input_logic_code ( const Inp::Handler < Manager >& handler )
+{
+    
+
+	Manager&	man = handler.GetData();
+	
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	Front::CScreenElement* p_root_window = p_screen_elem_man->GetElement( Script::GenerateCRC( "root_window" ) , 
+																			Front::CScreenElementManager::DONT_ASSERT );
+	// If any operable menus are up, ignore input
+	if( p_root_window )
+	{
+		Script::CStruct* pTags = new Script::CStruct();
+		p_root_window->CopyTagsToScriptStruct( pTags );
+		uint32 menu_state;
+		pTags->GetChecksum( "menu_state", &menu_state, Script::NO_ASSERT );
+		delete pTags;
+		if ( menu_state == Script::GenerateCRC( "on" ) )
+		{
+			return;
+		}
+	}
+
+	if( handler.m_Input->m_Makes & Inp::Data::mD_X )
+	{
+		Dbg_Printf( "Got X, inserting command....\n" );
+		man.SetObserverCommand( vOBSERVER_COMMAND_NEXT );
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_auto_server_code( const Tsk::Task< Manager >& task )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Manager& man = task.GetData();
+
+	// Give it a couple of seconds before activating
+	if(( Tmr::GetTime() - s_auto_serve_start_time ) < Tmr::Seconds( 2 ))
+	{
+		return;
+	}
+	if( ( skate_mod->GetGameMode()->GetNameChecksum() != Script::GenerateCRC( "netlobby" )))
+	{
+		return;
+	}
+
+	if( man.GetNumPlayers() < 2 )
+	{
+		return;
+	}
+
+	if( man.m_waiting_for_game_to_start )
+	{
+		return;
+	}
+
+	if(( Tmr::GetTime() - man.m_lobby_start_time ) > vAUTO_START_INTERVAL )
+	{
+        Dbg_Printf( "Starting Network Game....\n" );
+		Script::RunScript( "LoadPendingPlayers" );
+		Script::RunScript( "StartNetworkGame" );
+		man.m_waiting_for_game_to_start = true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_enter_chat_code( const Tsk::Task< Manager >& task )
+{
+	int num_chars;
+	char makes[256];
+	static Tmr::Time s_last_taunt_time = 0;
+		
+	num_chars = SIO::KeyboardRead( makes );
+
+	if( num_chars > 0 )
+	{
+		// Space and enter bring up the chat interface
+		if( ( makes[0] == SIO::vKB_ENTER ) ||
+			( makes[0] == 32 ))
+		{
+			Script::CStruct* pParams;
+			
+			pParams = new Script::CStruct;
+			pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "keyboard_anchor" ));
+			
+			// Enter and space act as "choose" only if you're not currently using the on-screen keyboard
+			if( Obj::ScriptObjectExists( pParams, NULL ) == false )
+			{
+				pParams->Clear();
+				pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "current_menu_anchor" ));
+				if( Obj::ScriptObjectExists( pParams, NULL ) == false )
+				{
+					pParams->Clear();
+					pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "dialog_box_anchor" ));
+					if( Obj::ScriptObjectExists( pParams, NULL ) == false )
+					{
+						Script::RunScript( "enter_kb_chat" );
+						SIO::KeyboardClear();
+					}
+				}
+			}
+		}
+		else if( makes[0] == SIO::vKB_F1 )
+		{
+			if(( Tmr::GetTime() - s_last_taunt_time ) > vTAUNT_INTERVAL )
+			{
+				Script::CStruct* pParams;
+
+				pParams = new Script::CStruct;
+				pParams->AddChecksum( CRCD(0xb53d0e0f,"string_id"), CRCD(0xe5fd359,"props_string"));
+				Script::RunScript( "SendTauntMessage", pParams );
+				delete pParams;
+
+				s_last_taunt_time = Tmr::GetTime();
+			}
+		}
+		else if( makes[0] == SIO::vKB_F2 )
+		{
+			if(( Tmr::GetTime() - s_last_taunt_time ) > vTAUNT_INTERVAL )
+			{
+				Script::CStruct* pParams;
+
+				pParams = new Script::CStruct;
+				pParams->AddChecksum( CRCD(0xb53d0e0f,"string_id"), CRCD(0xbeea3518,"your_daddy_string"));
+				Script::RunScript( "SendTauntMessage", pParams );
+				delete pParams;
+
+				s_last_taunt_time = Tmr::GetTime();
+			}
+		}
+		else if( makes[0] == SIO::vKB_F3 )
+		{
+			if(( Tmr::GetTime() - s_last_taunt_time ) > vTAUNT_INTERVAL )
+			{
+				Script::CStruct* pParams;
+
+				pParams = new Script::CStruct;
+				pParams->AddChecksum( CRCD(0xb53d0e0f,"string_id"), CRCD(0x4525adbd,"get_some_string"));
+				Script::RunScript( "SendTauntMessage", pParams );
+				delete pParams;
+
+				s_last_taunt_time = Tmr::GetTime();
+			}
+		}
+		else if( makes[0] == SIO::vKB_F4 )
+		{
+			if(( Tmr::GetTime() - s_last_taunt_time ) > vTAUNT_INTERVAL )
+			{
+				Script::CStruct* pParams;
+
+				pParams = new Script::CStruct;
+				pParams->AddChecksum( CRCD(0xb53d0e0f,"string_id"), CRCD(0xa36dbee1,"no_way_string"));
+				Script::RunScript( "SendTauntMessage", pParams );
+				delete pParams;
+
+				s_last_taunt_time = Tmr::GetTime();
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_join_state_code( const Tsk::Task< Manager >& task )
+{
+	Manager& man = task.GetData();
+		
+	switch( man.GetJoinState())
+	{   
+		case vJOIN_STATE_TRYING_PASSWORD:
+		case vJOIN_STATE_WAITING:
+		{   
+			break;
+		}
+		case vJOIN_STATE_CONNECTING:
+		{   
+			Script::RunScript( "CreateConnectingDialog" );
+			
+			man.SetJoinState( vJOIN_STATE_WAITING );
+			break;
+		}
+		case vJOIN_STATE_JOINING:
+		{
+			Net::Client* client;
+
+			client = man.GetClient( 0 );
+			if( !client->IsLocal())
+			{
+				Script::RunScript( "CreateJoiningDialog" );
+			}
+					
+			man.SetJoinState( vJOIN_STATE_WAITING );
+			break;
+		}
+		case vJOIN_STATE_JOINING_WITH_PASSWORD:
+		{
+			Script::RunScript( "CreateTryingPasswordDialog" );
+			
+			man.SetJoinState( vJOIN_STATE_TRYING_PASSWORD );
+			break;
+		}
+		case vJOIN_STATE_REFUSED:
+		{
+			Script::CStruct* p_structure = new Script::CStruct;
+			p_structure->AddChecksum( "reason", man.m_conn_refused_reason );
+
+			man.m_join_state_task->Remove();
+
+			Script::RunScript( "CreateJoinRefusedDialog", p_structure );
+			delete p_structure;
+			break;
+		}
+		case vJOIN_STATE_GOT_PLAYERS:
+		{
+			Lst::Search< NewPlayerInfo > sh;
+			NewPlayerInfo* new_player;		
+			
+			// If we're observing, we need to remove our skater
+			for( new_player = man.FirstNewPlayerInfo( sh ); new_player; new_player = man.NextNewPlayerInfo( sh ))
+			{
+				if( new_player->Flags & PlayerInfo::mLOCAL_PLAYER )
+				{
+					if( new_player->Flags & PlayerInfo::mOBSERVER )
+					{
+						Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+						Obj::CSkater* skater;
+						skater = skate_mod->GetLocalSkater();
+						Dbg_Assert( skater );
+						skate_mod->remove_skater( skater );
+						man.ObservePlayer( NULL );
+					}
+					break;
+				}
+			}
+			
+			Script::RunScript( "entered_network_game" );
+
+			man.SetJoinState( vJOIN_STATE_WAITING_FOR_START_INFO );
+			break;
+		}
+		case vJOIN_STATE_WAITING_FOR_START_INFO:
+			break;
+		case vJOIN_STATE_CONNECTED:
+		{
+			Script::RunScript( "dialog_box_exit" );
+			man.m_join_timeout_task->Remove();
+		}
+		// Fall-through intentional
+		case vJOIN_STATE_FINISHED:
+		{
+			man.m_join_state_task->Remove();
+			break;
+		}
+		default:
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_ctf_logic_code( const Tsk::Task< Manager >& task )
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if( skate_mod->GetGameMode()->GetNameChecksum() != Script::GenerateCRC( "netctf" ))
+	{
+		Dbg_MsgAssert( 0, ( "Shouldn't be running CTF logic in non-ctf game" ));
+		return;
+	}
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		if( player->HasCTFFlag())
+		{
+			int team;
+			uint32 obj_id;
+			Obj::CMovingObject* obj;
+			Obj::CMovingObject* moving_obj;
+
+			team = player->HasWhichFlag();
+			switch( team )
+			{
+				case vTEAM_RED:
+					obj_id = Script::GenerateCRC( "TRG_CTF_Red" );
+					break;
+				case vTEAM_BLUE:
+					obj_id = Script::GenerateCRC( "TRG_CTF_Blue" );
+					break;
+				case vTEAM_GREEN:
+					obj_id = Script::GenerateCRC( "TRG_CTF_Green" );
+					break;
+			case vTEAM_YELLOW:
+					obj_id = Script::GenerateCRC( "TRG_CTF_Yellow" );
+					break;
+				default:
+					Dbg_Assert( 0 );
+					obj_id = 0;
+					break;
+			}
+
+			obj = (Obj::CMovingObject *) Obj::ResolveToObject( obj_id );
+			if( obj == NULL )
+			{
+				return;
+			}
+
+			moving_obj = static_cast( obj );
+
+			Gfx::CSkeleton* pSkeleton;
+			pSkeleton = player->m_Skater->GetSkeleton();
+			Dbg_Assert( pSkeleton );
+
+			Mth::Matrix& flag_matrix = moving_obj->GetMatrix();
+			pSkeleton->GetBoneMatrix( CRCD(0xddec28af,"Bone_Head"), &flag_matrix );
+		
+			flag_matrix.RotateXLocal( 90.0f );
+
+			// raise the flag by 1 foot
+			flag_matrix.TranslateLocal( Mth::Vector( 0.0f, FEET(1.0f), 0.0f, 0.0f ) );
+		
+			Mth::Vector flag_offset = flag_matrix[Mth::POS];
+			flag_matrix[Mth::POS] = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
+		
+			// for some reason, the below doesn't work as expected.
+			// so instead, use the skater's root matrix for the orientation
+			flag_matrix = flag_matrix * player->m_Skater->GetMatrix();
+			//		crown.m_matrix = crown.mp_king->GetMatrix();
+
+			moving_obj->m_pos = player->m_Skater->GetMatrix().Transform( flag_offset );
+			moving_obj->m_pos += player->m_Skater->m_pos;
+
+// Mick:  Removed this as it no longer does anything apart from copying over the display matrix
+// if that's still needed then do it here
+// Might also possibly have some problem with shadows being left behind, but that just indicates the need
+// for a proper LOD system for shadows
+//			moving_obj->MovingObj_Update();
+// Mick:  Like this, but I don't think it's needed  		
+//			moving_obj->m_display_matrix = moving_obj->m_matrix;
+		
+	//		Gfx::AddDebugStar( crown.m_pos, 10, 0x0000ffff, 1 );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_start_network_game_code( const Tsk::Task< Manager >& task )
+{
+	//Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Manager& man = task.GetData();
+	static bool showing_wait_screen = false;
+                                                                                             
+	if( man.NumPartiallyLoadedPlayers() > 0 )
+	{
+		if( man.OnServer())
+		{
+			if( showing_wait_screen == false )
+			{
+				Front::CScreenElementManager* pManager = Front::CScreenElementManager::Instance();
+				Front::CScreenElementPtr p_elem = pManager->GetElement( CRCD(0xf53d1d83,"current_menu_anchor"), Front::CScreenElementManager::DONT_ASSERT );
+				if( !p_elem )
+				{
+					Script::RunScript( "CreateWaitForPlayersDialog" );
+					showing_wait_screen = true;
+				}
+			}
+		}
+		else
+		{
+			if( showing_wait_screen == false )
+			{
+				man.CreateNetPanelMessage( true, Script::GenerateCRC("net_message_game_will_start"),
+										   NULL, NULL, NULL, "netexceptionprops" );
+				showing_wait_screen = true;
+			}
+		}
+
+		return;
+	}
+    
+	if( ScriptAllPlayersAreReady( NULL, NULL ) == false )
+	{
+		return;
+	}
+
+	if(( Tmr::GetTime() < s_time_to_start_game ))
+	{
+		return;
+	}
+
+	showing_wait_screen = false;
+	man.StartNetworkGame();
+	man.m_start_network_game_task->Remove();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_modem_state_code( const Tsk::Task< Manager >& task )
+{
+	Net::Manager * net_man = Net::Manager::Instance();
+	int new_modem_state;
+	Manager& man = task.GetData();
+
+	new_modem_state = net_man->GetModemState();
+	
+	// Check for positive edge
+	if( new_modem_state != man.m_last_modem_state )
+	{
+		Script::CStruct* pStructure = new Script::CStruct;
+		
+		switch( new_modem_state )
+		{
+			case Net::vMODEM_STATE_DIALING:
+			{
+				char dial_msg[128];
+
+                if( net_man->GetConnectionType() == Net::vCONN_TYPE_PPPOE )
+				{
+					sprintf( dial_msg, "%s", Script::GetString("net_modem_state_conencting"));
+				}
+				else
+				{
+					sprintf( dial_msg, "%s %s", Script::GetString("net_modem_state_dialing"), 
+				   							net_man->GetISPPhoneNumber());
+				}
+
+				pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, dial_msg );
+				Script::RunScript( "create_modem_state_dialog", pStructure );
+                
+				Dbg_Printf( "In modem state code. Got state dialing\n" );
+				break;
+			}
+			case Net::vMODEM_STATE_CONNECTED:
+			{
+				pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_modem_state_connected" ));
+				Script::RunScript( "create_modem_state_dialog", pStructure );
+
+				Dbg_Printf( "In modem state code. Got state connected\n" );
+				break;
+			}
+			case Net::vMODEM_STATE_LOGGED_IN:
+			{
+				char conn_msg[128];
+
+				Dbg_Printf( "In modem state code. Got state logged in\n" );
+				if( net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM )
+				{
+					sprintf( conn_msg, "%s %d bps", Script::GetString("net_modem_state_logged_in"), 
+										net_man->GetModemBaudRate());
+				}
+				else
+				{
+					sprintf( conn_msg, "%s", Script::GetString("net_modem_state_logged_in"));
+				}
+				
+				pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, conn_msg );
+				Script::RunScript( "create_modem_final_state_dialog", pStructure );
+				break;
+			}
+			case Net::vMODEM_STATE_DISCONNECTING:
+			{
+				pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_modem_state_disconnecting" ));
+				Script::RunScript( "create_modem_status_dialog", pStructure );
+				
+
+				Dbg_Printf( "In modem state code. Got state disconnecting\n" );
+				break;
+			}
+			case Net::vMODEM_STATE_HANGING_UP:
+			{
+				pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_modem_state_hanging_up" ));
+				Script::RunScript( "create_modem_status_dialog", pStructure );
+
+				Dbg_Printf( "In modem state code. Got state hanging up\n" );
+				break;
+			}
+			case Net::vMODEM_STATE_DISCONNECTED:
+			{
+				pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_modem_state_disconnected" ));
+				Script::RunScript( "create_modem_final_state_dialog", pStructure );
+
+				Dbg_Printf( "In modem state code. Got state disconnected\n" );
+				break;
+			}
+			case Net::vMODEM_STATE_READY_TO_TRANSMIT:
+			{
+#ifdef __PLAT_NGPS__
+				Dbg_Printf( "Removing modem state task\n" );
+				man.RemoveModemStateTask();
+				
+				if( man.m_connection_success_script )
+				{
+					Script::RunScript( man.m_connection_success_script );
+				}
+#endif		// __PLAT_NGPS__
+				break;
+			}
+			case Net::vMODEM_STATE_ERROR:
+			{
+#ifdef __PLAT_NGPS__
+				uint32 modem_error;
+
+				switch( net_man->GetModemError())
+				{
+					case SNTC_ERR_NOMODEM:
+						modem_error = Script::GenerateCRC("net_modem_error_no_modem");
+						break;
+					case SNTC_ERR_TIMEOUT:
+						modem_error = Script::GenerateCRC("net_modem_error_timeout");
+						break;
+					case SNTC_ERR_CONNECT:
+					{
+						Prefs::Preferences* prefs;
+						uint32 config_type;
+
+						prefs = man.GetNetworkPreferences();
+						config_type = prefs->GetPreferenceChecksum( CRCD(0xe381f426,"config_type"), 
+																	CRCD(0x21902065,"checksum") );
+						if( config_type == CRCD(0x99452df9,"config_manual"))
+						{
+							modem_error = Script::GenerateCRC("net_modem_error_during_connect");
+						}
+						else
+						{
+							modem_error = Script::GenerateCRC("net_modem_error_during_connect_ync");
+						}
+						
+						break;
+					}
+					case SNTC_ERR_BUSY:
+						modem_error = Script::GenerateCRC("net_modem_error_busy");
+						break;
+					case SNTC_ERR_NOCARRIER:
+					case SNTC_ERR_NOANSWER:
+						modem_error = Script::GenerateCRC("net_modem_error_no_connect");
+						break;
+					case SNTC_ERR_NODIALTONE:
+						modem_error = Script::GenerateCRC("net_modem_error_no_dialtone");
+						break;
+					default:
+						modem_error = Script::GenerateCRC("net_modem_error_no_connect");
+						break;
+				}
+
+				Dbg_Printf( "In modem state error: %s\n", Script::GetString( modem_error ));
+				pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( modem_error ));
+				Script::RunScript( "create_modem_final_state_dialog", pStructure );
+#endif		// __PLAT_NGPS__
+				break;
+			}
+			default:
+				Dbg_Assert( 0 );
+				break;
+		}
+		
+		delete pStructure;
+	}
+
+	man.m_last_modem_state = new_modem_state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_client_add_new_players_code( const Tsk::Task< Manager >& task )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+	
+	Manager& man = task.GetData();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* skater;
+	Lst::Node< NewPlayerInfo > *node, *next;
+	NewPlayerInfo* new_player;
+	Net::Conn* server_conn;
+	Lst::Search< Net::Conn > sh;
+	bool added_player;
+	int i;
+                         
+	added_player = false;
+	for( node = man.m_new_players.GetNext(); node; node = next )
+	{
+		next = node->GetNext();
+
+		new_player = node->GetData();
+
+		if( new_player->AllowedJoinFrame > man.m_client[0]->m_FrameCounter )
+		{
+			break;
+		}
+
+		// GJ:  the "new_player" structure should already
+		// contain the desired appearance
+
+		man.ClientAddNewPlayer( new_player );
+
+		delete new_player;
+		delete node;
+
+		added_player = true;
+	}
+
+	if( added_player )
+	{
+        for( i = 0; i < vMAX_LOCAL_CLIENTS; i++ )
+		{
+			if( man.m_client[i] )
+			{
+				server_conn = man.m_client[i]->FirstConnection( &sh );
+				if( server_conn )
+				{
+					server_conn->UpdateCommTime();	// update the current comm time so it doesn't time out after
+													// laoding the skater
+				}
+			}
+		}
+		for( i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+		{
+			skater = skate_mod->GetSkater( i );
+			if( skater )
+			{   
+				skater->Resync();
+			}
+		}
+        
+		man.RespondToReadyQuery();
+	}
+	Mem::Manager::sHandle().PopContext();
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_server_add_new_players_code( const Tsk::Task< Manager >& task )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+	Manager& man = task.GetData();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Lst::Node< NewPlayerInfo > *node, *next;
+	NewPlayerInfo* new_info;
+	Net::MsgDesc msg_desc;
+	Net::Manager* net_man = Net::Manager::Instance();
+
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	for( node = man.m_new_players.GetNext(); node; node = next )
+	{
+		Obj::CSkater* skater;
+		MsgNewPlayer new_player_msg;
+		Lst::Search< PlayerInfo > sh;
+		PlayerInfo* player, *new_player;
+		MsgReady ready_msg;
+		bool narrowband;
+				
+		next = node->GetNext();
+
+		new_info = node->GetData();
+	
+		if( new_info->AllowedJoinFrame > man.m_server->m_FrameCounter )
+		{
+			break;
+		}
+
+		narrowband = ( new_info->Conn->GetBandwidthType() == Net::Conn::vNARROWBAND ) ||
+						( net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM );
+
+		// Mick: moved onto skater info heap, to prevent fragmentation
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+		new_player = man.NewPlayer( NULL, new_info->Conn, new_info->Flags );
+		Mem::Manager::sHandle().PopContext();
+		
+		strcpy( new_player->m_Name, new_info->Name );
+		new_player->CopyProfile( new_info->mpSkaterProfile );
+		new_player->m_jump_in_frame = new_info->JumpInFrame;
+		new_player->m_Profile = new_info->Profile;
+		new_player->m_Rating = new_info->Rating;
+		new_player->m_VehicleControlType = new_info->VehicleControlType;
+
+		man.AddPlayerToList( new_player );
+		ready_msg.m_Time = Tmr::GetTime();
+        
+		if( !new_player->IsLocalPlayer())
+		{
+			// Send the new player's create skater command in such a way that he knows it's his own skater
+			new_player_msg.m_ObjId = new_info->ObjID;
+			new_player_msg.m_Team = new_player->m_Team;
+			new_player_msg.m_Profile = new_player->m_Profile;
+			new_player_msg.m_Rating = new_player->m_Rating;
+			new_player_msg.m_VehicleControlType = new_player->m_VehicleControlType;
+			new_player_msg.m_Score = 0;
+			strcpy( new_player_msg.m_Name, new_info->Name );
+            
+			new_player_msg.m_Flags = PlayerInfo::mLOCAL_PLAYER;
+			if( new_player->m_flags.TestMask( PlayerInfo::mJUMPING_IN ))
+			{
+				new_player_msg.m_Flags |= PlayerInfo::mJUMPING_IN;
+			}
+			if( new_player->IsObserving())
+			{
+				new_player_msg.m_Flags |= PlayerInfo::mOBSERVER;
+			}
+			
+			msg_desc.m_Data = NULL;
+			msg_desc.m_Length = 0;
+			msg_desc.m_Id = MSG_ID_JOIN_ACCEPTED;
+			msg_desc.m_Priority = Net::HIGHEST_PRIORITY;
+			man.m_server->EnqueueMessage( new_player->GetConnHandle(), &msg_desc );
+		
+			msg_desc.m_Data = &new_player_msg;
+			msg_desc.m_Length = sizeof( MsgNewPlayer ) - vMAX_APPEARANCE_DATA_SIZE;
+			msg_desc.m_Id = MSG_ID_PLAYER_CREATE;
+			man.m_server->EnqueueMessage( new_player->GetConnHandle(), &msg_desc );
+		}
+		
+
+		if( !new_player->IsObserving())
+		{
+			// Send the new player's info to existing players
+			for( player = man.FirstPlayerInfo( sh, true ); player; 
+					player = man.NextPlayerInfo( sh, true ))
+			{
+				// don't send the new player's info to the new player. We sent it separately and specially
+				// Also, don't send to the server (ourselves)
+				if(( new_player != player ) &&
+				   ( !player->IsLocalPlayer()))
+				{   
+					new_player_msg.m_ObjId = new_info->ObjID;
+					new_player_msg.m_Flags = 0;
+					new_player_msg.m_Team = new_player->m_Team;
+					new_player_msg.m_Profile = new_player->m_Profile;
+					new_player_msg.m_Rating = new_player->m_Rating;
+					new_player_msg.m_VehicleControlType = new_player->m_VehicleControlType;
+					new_player_msg.m_Score = 0;
+					strcpy( new_player_msg.m_Name, new_player->m_Name );
+					Dbg_Assert( new_player->mp_SkaterProfile );
+					uint32 data_size = new_player->mp_SkaterProfile->WriteToBuffer(new_player_msg.m_AppearanceData, vMAX_APPEARANCE_DATA_SIZE );
+
+					msg_desc.m_Data = &new_player_msg;
+					msg_desc.m_Length = sizeof( MsgNewPlayer ) - vMAX_APPEARANCE_DATA_SIZE + data_size;
+					msg_desc.m_Id = MSG_ID_PLAYER_CREATE;
+					msg_desc.m_Priority = Net::NORMAL_PRIORITY;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					man.m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+				}
+			}
+		}
+
+		// Players that are "jumping in" already have current players' info
+		// Only send them other players who jumped in at the same time
+		if( new_player->m_flags.TestMask( PlayerInfo::mJUMPING_IN ))
+		{
+			for( player = man.FirstPlayerInfo( sh ); player; 
+					player = man.NextPlayerInfo( sh ))
+			{
+				if( ( new_player != player ) &&
+					( player->m_jump_in_frame == new_player->m_jump_in_frame ))
+				{   
+					new_player_msg.m_ObjId = player->m_Skater->GetID();
+					new_player_msg.m_Flags = ( player->m_flags & ( PlayerInfo::mKING_OF_THE_HILL | PlayerInfo::mHAS_ANY_FLAG ));
+					new_player_msg.m_Team = player->m_Team;
+					new_player_msg.m_Profile = player->m_Profile;
+					new_player_msg.m_Rating = player->m_Rating;
+					new_player_msg.m_VehicleControlType = player->m_VehicleControlType;
+					new_player_msg.m_Score = 0;
+					strcpy( new_player_msg.m_Name, player->m_Name );
+					Dbg_Assert( player->mp_SkaterProfile );
+					uint32 data_size = player->mp_SkaterProfile->WriteToBuffer(new_player_msg.m_AppearanceData, 
+																	vMAX_APPEARANCE_DATA_SIZE, narrowband );
+
+					msg_desc.m_Data = &new_player_msg;
+					msg_desc.m_Length = sizeof( MsgNewPlayer ) - vMAX_APPEARANCE_DATA_SIZE + data_size;
+					msg_desc.m_Id = MSG_ID_PLAYER_CREATE;
+					msg_desc.m_Priority = Net::NORMAL_PRIORITY;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					man.m_server->EnqueueMessage( new_player->GetConnHandle(), &msg_desc );
+				}
+			}
+		}
+		else
+		{
+			// Send the new player the info on all current players( except himself )
+			for( player = man.FirstPlayerInfo( sh ); player; 
+					player = man.NextPlayerInfo( sh ))
+			{
+				// don't send the new player's info to the new player. We sent it separately and specially
+				// also only send these messages to remote clients as we've already loaded the player here
+				if( ( new_player != player ) &&
+					( !new_player->IsLocalPlayer()))
+				{   
+					new_player_msg.m_ObjId = player->m_Skater->GetID();
+					new_player_msg.m_Flags = ( player->m_flags & ( PlayerInfo::mKING_OF_THE_HILL | PlayerInfo::mHAS_ANY_FLAG ));
+					new_player_msg.m_Team = player->m_Team;
+					new_player_msg.m_Profile = player->m_Profile;
+					new_player_msg.m_Rating = player->m_Rating;
+					new_player_msg.m_VehicleControlType = player->m_VehicleControlType;
+					new_player_msg.m_Score = man.GetPlayerScore( player->m_Skater->GetID() );
+					strcpy( new_player_msg.m_Name, player->m_Name );
+					Dbg_Assert( player->mp_SkaterProfile );
+					uint32 data_size = player->mp_SkaterProfile->WriteToBuffer(new_player_msg.m_AppearanceData, 
+																		vMAX_APPEARANCE_DATA_SIZE, narrowband );
+
+					msg_desc.m_Data = &new_player_msg;
+					msg_desc.m_Length = sizeof( MsgNewPlayer ) - vMAX_APPEARANCE_DATA_SIZE + data_size;
+					msg_desc.m_Id = MSG_ID_PLAYER_CREATE;
+					msg_desc.m_Priority = Net::NORMAL_PRIORITY;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					man.m_server->EnqueueMessage( new_player->GetConnHandle(), &msg_desc );
+				}
+			}
+			
+			MsgIntInfo level_msg;
+			uint32 level;
+
+			level = (int) man.GetNetworkLevelId();
+			if( !new_player->IsLocalPlayer())
+			{
+				if( level == CRCD(0xb664035d,"Load_Sk5Ed_gameplay"))
+				{
+					Ed::CParkManager::sInstance()->WriteCompressedMapBuffer();
+
+					man.m_server->StreamMessage( new_player->GetConnHandle(), MSG_ID_LEVEL_DATA, Ed::CParkManager::COMPRESSED_MAP_SIZE, 
+							   Ed::CParkManager::sInstance()->GetCompressedMapBuffer(), "level data", vSEQ_GROUP_PLAYER_MSGS,
+												 false, true );
+												 
+					man.m_server->StreamMessage( new_player->GetConnHandle(), MSG_ID_RAIL_DATA, Obj::GetRailEditor()->GetCompressedRailsBufferSize(), 
+							   Obj::GetRailEditor()->GetCompressedRailsBuffer(), "rail data", vSEQ_GROUP_PLAYER_MSGS,
+												 false, true );
+												 
+				}
+			}
+
+			level_msg.m_Data = (int) level;
+			Dbg_Printf( "Sending player info ack request\n" );
+
+			msg_desc.m_Data = &level_msg;
+			msg_desc.m_Length = sizeof( MsgIntInfo );
+			msg_desc.m_Id = MSG_ID_PLAYER_INFO_ACK_REQ;
+			msg_desc.m_Priority = Net::NORMAL_PRIORITY;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			man.m_server->EnqueueMessage( new_player->GetConnHandle(), &msg_desc );
+		}
+		
+		if( !new_player->IsObserving())
+		{   
+			int skater_num;
+
+			// GJ:  the "new_info" should contain the desired
+			// appearance by this point
+			
+			// Mick: if this is a local player in a net game
+			// then it must be skater 0
+			if( new_player->IsLocalPlayer() && man.InNetGame())
+			{
+				skater_num = 0;
+			}
+			else
+			{
+				// Otherwise, for 2P games, nonlocal net players, and P1, 
+				skater_num = skate_mod->GetNumSkaters();
+			}
+			skater = skate_mod->add_skater( new_info->mpSkaterProfile, new_player->m_Conn->IsLocal(), 
+											new_info->ObjID, skater_num );
+			new_player->SetSkater( skater );
+			if( !new_player->IsLocalPlayer())
+			{
+				skater->RemoveFromCurrentWorld();
+			}
+
+			for( player = man.FirstPlayerInfo( sh, true ); player; player = man.NextPlayerInfo( sh, true ))
+			{
+				if( new_player == player )
+				{
+					continue;
+				}
+
+				msg_desc.m_Data = &ready_msg;
+				msg_desc.m_Length = sizeof( MsgReady );
+				msg_desc.m_Id = MSG_ID_READY_QUERY;
+				msg_desc.m_Priority = Net::NORMAL_PRIORITY;
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+				man.m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+				if( !player->IsLocalPlayer() && !player->IsObserving())
+				{
+					player->m_Skater->Resync();
+					player->MarkAsNotReady( ready_msg.m_Time );
+				}
+			}
+		}
+
+		// Mark the client as "not ready" so we don't bombard him with non-important messages
+		// until he has fully loaded all that he needs to load
+		msg_desc.m_Data = &ready_msg;
+		msg_desc.m_Length = sizeof( MsgReady );
+		msg_desc.m_Id = MSG_ID_READY_QUERY;
+		msg_desc.m_Priority = Net::NORMAL_PRIORITY;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		man.m_server->EnqueueMessage( new_player->GetConnHandle(), &msg_desc );
+		new_player->MarkAsNotReady( ready_msg.m_Time );
+        
+		delete new_info;
+		delete node;
+	}
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_render_metrics_code( const Tsk::Task< Manager >& task )
+{
+	Lst::Search< Net::Conn > sh;
+	Net::Conn* conn;
+	int conn_num;
+	Net::Metrics* metrics_in, *metrics_out;
+	char buff[ 1024 ];
+	int i;
+
+    Manager& man = task.GetData();
+
+
+    for( i = 0; i < 2; i++ )
+	{
+		if( man.m_client[0] && !man.m_client[0]->IsLocal())
+		{
+			conn_num = 0;
+			
+	
+			for( conn = man.m_client[0]->FirstConnection( &sh ); conn;
+					conn = man.m_client[0]->NextConnection( &sh ))
+			{
+				if( conn->IsLocal())
+				{
+					continue;
+				}
+				metrics_in = conn->GetInboundMetrics();
+				metrics_out = conn->GetOutboundMetrics();
+				
+				sprintf( buff, "(%d) Lag: %d Resends: %d Bps: %d %d", 
+						 conn_num, 
+						 conn->GetAveLatency(),
+						 conn->GetNumResends(),
+						 metrics_in->GetBytesPerSec(),
+						 metrics_out->GetBytesPerSec());
+					
+				Script::CStruct* pParams;
+				pParams = new Script::CStruct;
+				pParams->AddString( "text", buff );
+				Script::RunScript( "update_net_metrics", pParams );
+
+				delete pParams;
+				conn_num++;
+			}
+		}
+		
+		if( man.m_server )
+		{
+			conn_num = 0;
+			for( conn = man.m_server->FirstConnection( &sh ); conn;
+					conn = man.m_server->NextConnection( &sh ))
+			{
+				if( conn->IsLocal())
+				{
+					continue;
+				}
+
+				metrics_in = conn->GetInboundMetrics();
+				metrics_out = conn->GetOutboundMetrics();
+				
+				sprintf( buff, "(%d) Lag: %d Resends: %d Bps: %d %d", 
+						 conn_num, 
+						 conn->GetAveLatency(),
+						 conn->GetNumResends(),
+						 metrics_in->GetBytesPerSec(),
+						 metrics_out->GetBytesPerSec());
+
+				Script::CStruct* pParams;
+				pParams = new Script::CStruct;
+				pParams->AddString( "text", buff );
+				Script::RunScript( "update_net_metrics", pParams );
+
+				delete pParams;
+					
+				conn_num++;
+			}
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_timeout_connections_code( const Tsk::Task< Manager >& task )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo *player, *next;
+	Manager& man = task.GetData();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	bool time_out;
+	
+#ifdef __PLAT_XBOX__
+	static Tmr::Time s_last_link_check = 0;
+	
+	if(	( man.InNetMode()) &&
+		(( Tmr::GetTime() - s_last_link_check ) > vLINK_CHECK_FREQUENCY ))
+	{
+		if( man.InNetGame())
+		{
+			s_last_link_check = Tmr::GetTime();
+			DWORD dwStatus = XNetGetEthernetLinkStatus();
+			
+        	if(( dwStatus & XNET_ETHERNET_LINK_ACTIVE ) == 0 )
+			{
+				Front::CScreenElementManager* pManager = Front::CScreenElementManager::Instance();
+				Front::CScreenElementPtr p_elem = pManager->GetElement( Script::GenerateCRC( "link_lost_dialog_anchor" ), Front::CScreenElementManager::DONT_ASSERT );
+				if( !p_elem )
+				{
+					Script::RunScript( "create_link_unplugged_dialog" );
+				}
+			}
+		}
+		else
+		{
+			s_last_link_check = Tmr::GetTime();
+			DWORD dwStatus = XNetGetEthernetLinkStatus();
+			
+        	if(( dwStatus & XNET_ETHERNET_LINK_ACTIVE ) == 0 )
+			{
+				Front::CScreenElementManager* pManager = Front::CScreenElementManager::Instance();
+				Front::CScreenElementPtr p_elem = pManager->GetElement( CRCD(0x3b56e746,"dialog_box_anchor"), Front::CScreenElementManager::DONT_ASSERT );
+				if( !p_elem )
+				{
+					Script::RunScript( "create_link_unplugged_front_end_dialog" );
+				}
+			}
+		}
+	}
+	
+#endif	
+
+	if( man.OnServer())
+	{   
+		for( player = man.FirstPlayerInfo( sh, true ); player; player = next )
+		{   
+			time_out = false;
+			next = man.NextPlayerInfo( sh, true );
+			if( player->m_Conn->IsLocal())
+			{
+				continue;
+			}
+
+			if( player->m_Conn->TestStatus( Net::Conn::mSTATUS_READY ))
+			{   
+				if( player->m_Conn->GetTimeElapsedSinceCommunication() > vCONNECTION_TIMEOUT )
+				{
+					time_out = true;
+				}
+				else
+				{
+					if( player->m_Conn->GetNumResends() > vMAX_TOLERABLE_RESENDS )
+					{
+						Obj::CSkater* skater;
+						bool observing;
+
+						skater = player->m_Skater;
+						observing = player->IsObserving();
+						man.DropPlayer( player, vREASON_BAD_CONNECTION );
+						if( !observing)
+						{
+							skate_mod->remove_skater( skater );
+						}
+						continue;
+					}
+				}
+			}
+			else
+			{
+				if( player->m_Conn->GetTimeElapsedSinceCommunication() > vNOT_READY_TIMEOUT )
+				{
+					time_out = true;
+				}
+			}
+			if( time_out )
+			{
+				Obj::CSkater* skater;
+				bool observing;
+
+#ifdef __NOPT_ASSERT__
+				unsigned int cur_time = Tmr::GetTime();
+				unsigned int last_time = player->m_Conn->GetLastCommTime();
+				Dbg_Printf( "Gamenet : Timing client out. Current time (%d) last comm time (%d)\n",
+								cur_time, last_time );
+#endif				
+				skater = player->m_Skater;
+				observing = player->IsObserving();
+				man.DropPlayer( player, vREASON_TIMEOUT );
+				if( !observing)
+				{
+					skate_mod->remove_skater( skater );
+				}
+			}
+		}
+	}
+	else
+	{
+		Net::Conn* server_conn;
+		Lst::Search< Net::Conn > sh;
+		
+		if( man.m_client[0] )
+		{   
+            server_conn = man.m_client[0]->FirstConnection( &sh );
+			if( server_conn == NULL )
+			{
+				return;
+			}
+			if(( man.GetJoinState() == vJOIN_STATE_CONNECTED ) ||
+			   ( man.GetJoinState() == vJOIN_STATE_WAITING_FOR_START_INFO ) || 
+			   ( man.GetJoinState() == vJOIN_STATE_GOT_PLAYERS ))
+			{
+				if( server_conn->GetTimeElapsedSinceCommunication() > vCONNECTION_TIMEOUT )
+				{  
+#ifdef __NOPT_ASSERT__
+					unsigned int cur_time = Tmr::GetTime();
+					unsigned int last_time = server_conn->GetLastCommTime();
+#endif					
+#ifdef __NOPT_ASSERT__
+					Dbg_Printf( "Gamenet : Timing server out. Current time (%d) last comm time (%d)\n",
+									cur_time, last_time );
+#endif              
+					
+					if( skate_mod->m_cur_level == CRCD(0x9f2bafb7,"Load_Skateshop"))
+					{						
+                        // If we got partially in, cancel the join and remove partially-loaded players
+						// Shut the client down so that it doesn't bombard the server ip with messages
+						
+						Script::RunScript( "ShowJoinTimeoutNotice" );
+
+						//man.CancelJoinServer();
+						man.m_join_state_task->Remove();
+						man.SetJoinState( vJOIN_STATE_FINISHED );
+					}
+					else
+					{
+// On the xbox, don't show the lost conn dialog if the quit dialog box is up					
+#ifdef __PLAT_XBOX__
+						Front::CScreenElementManager* pManager = Front::CScreenElementManager::Instance();
+						Front::CScreenElementPtr p_elem = pManager->GetElement( CRCD(0x4c8bf619,"quit_dialog_anchor"), Front::CScreenElementManager::DONT_ASSERT );
+						if( !p_elem )
+#endif
+						{
+							Script::RunScript( "CreateLostConnectionDialog" );
+	
+							man.CleanupPlayers();
+							man.ClientShutdown();
+						}
+					}
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_join_timeout_code( const Tsk::Task< Manager >& task )
+{
+	Manager& man = task.GetData();
+
+	if(( Tmr::GetTime() - man.m_join_start_time ) > vJOIN_TIMEOUT )
+	{
+		//Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+		
+		//man.CancelJoinServer();
+		//man.SetJoinState( vJOIN_STATE_FINISHED );
+        
+		man.m_join_state_task->Remove();
+		Script::RunScript( "ShowJoinTimeoutNotice" );
+	}
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+void				Manager::SetNetworkMode( NetworkMode mode )
+{
+	m_flags.ClearMask( mNETWORK_MODE);
+	
+	if( mode == vLAN_MODE )
+	{
+		Dbg_Printf( "************* Setting Lan Mode\n" );
+		m_flags.SetMask( mLAN );
+	}
+	else if( mode == vINTERNET_MODE )
+	{
+		Dbg_Printf( "************* Setting Internet Mode\n" );
+		m_flags.SetMask( mINTERNET );
+	}
+	else
+	{
+		Dbg_Printf( "************* Clearing Net Mode\n" );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				Manager::SetJoinMode( JoinMode mode )
+{
+	m_join_mode = mode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+JoinMode			Manager::GetJoinMode( void )
+{
+	return m_join_mode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				Manager::SetHostMode( HostMode mode )
+{
+	// Only accept if it is actually a change in modes
+	if( mode == m_host_mode )
+	{
+		return;
+	}
+	
+	m_host_mode = mode;
+	
+	switch( m_host_mode )
+	{
+		case vHOST_MODE_SERVE:
+		{
+			// Do nothing. This is the standard mode
+			Dbg_Printf( "Got mode serve\n" );
+			break;
+		}
+		case vHOST_MODE_AUTO_SERVE:
+		{
+			RequestObserverMode();
+			SpawnAutoServer();
+
+			// Should start a game and also spawn a task that starts new games
+			// after X seconds in a lobby
+			break;
+		}
+		
+		case vHOST_MODE_FCFS:
+		{
+			RequestObserverMode();
+			ChooseNewServerPlayer();
+
+			// Should start a game and also spawn a task that starts new games
+			// after X seconds in a lobby
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+	HostMode			Manager::GetHostMode( void )
+{
+	return m_host_mode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				Manager::SetServerMode( bool on )
+{
+	if( on )
+	{
+		m_flags.SetMask( mSERVER );
+	}
+	else
+	{
+		m_flags.ClearMask( mSERVER );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				Manager::InNetMode( void )
+{
+	return m_flags.TestMask( mNETWORK_MODE);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				Manager::InNetGame( void )
+{
+	#ifdef	__NOPT_ASSERT__
+	// Mick:  Pretend we are in a net game when we are actually in
+	// regular single player
+	// This allows designer to test net games without starting a server
+	if (Script::GetInt(CRCD(0xcbc1a46,"fake_net")))
+	{
+		return true;
+	}
+	#endif
+	
+	
+	if( !InNetMode())
+	{
+		return false;
+	}
+
+	if( m_server && !m_server->IsLocal())
+	{
+		return true;
+	}
+
+	if( m_client[0] && !m_client[0]->IsLocal())
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				Manager::InLanMode( void )
+{
+	return m_flags.TestMask( mLAN );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				Manager::InInternetMode( void )
+{
+    return m_flags.TestMask( mINTERNET );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				Manager::OnServer( void )
+{
+	return m_flags.TestMask( mSERVER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				Manager::OnClient( void )
+{
+	return m_flags.TestMask( mCLIENT );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					Manager::GetSkillLevel( void )
+{
+	uint32 checksum;
+	Script::CStruct* pStructure;
+		
+	pStructure = m_network_preferences.GetPreference( Script::GenerateCRC("skill_level") );
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	if( checksum == Script::GenerateCRC( "num_1" ))
+	{
+		return vSKILL_LEVEL_1;
+	}
+	else if( checksum == Script::GenerateCRC( "num_2" ))
+	{
+		return vSKILL_LEVEL_2;
+	}
+	else if( checksum == Script::GenerateCRC( "num_3" ))
+	{
+		return vSKILL_LEVEL_3;
+	}                                              
+	else if( checksum == Script::GenerateCRC( "num_4" ))
+	{
+		return vSKILL_LEVEL_4;
+	}                                              
+	else if( checksum == Script::GenerateCRC( "num_5" ))
+	{
+		return vSKILL_LEVEL_5;
+	}                                              
+
+	return vSKILL_LEVEL_DEFAULT;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					Manager::GetMaxPlayers( void )
+{
+	uint32 checksum;
+	int max_players = 2;
+	Script::CStruct* pStructure;
+		
+	pStructure = m_network_preferences.GetPreference( Script::GenerateCRC("num_players") );
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	if( checksum == Script::GenerateCRC( "num_2" ))
+	{
+		max_players = 2;
+	}
+	else if( checksum == Script::GenerateCRC( "num_3" ))
+	{
+		max_players = 3;
+	}
+	else if( checksum == Script::GenerateCRC( "num_4" ))
+	{
+		max_players = 4;
+	}                                              
+	else if( checksum == Script::GenerateCRC( "num_5" ))
+	{
+		max_players = 5;
+	}
+	else if( checksum == Script::GenerateCRC( "num_6" ))
+	{
+		max_players = 6;
+	}
+	else if( checksum == Script::GenerateCRC( "num_7" ))
+	{
+		max_players = 7;
+	}
+	else if( checksum == Script::GenerateCRC( "num_8" ))
+	{
+		max_players = 8;
+	}
+
+	return max_players;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				Manager::CreateTeamFlags( int num_teams )
+{
+	Script::CStruct* pParams;
+	
+	// First destroy all team flags
+	Script::RunScript( "Kill_Team_Flags" );
+
+	// Now only create the number of flags we need
+	if( num_teams == 0 )
+	{
+		return;
+	}
+
+	pParams = new Script::CStruct;
+	pParams->AddInteger( "num_teams", num_teams );
+	Script::RunScript( "Create_Team_Flags", pParams );
+	delete pParams;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					Manager::GetNumTeams( void )
+{
+	uint32 checksum;
+	int num_teams = vMAX_TEAMS;
+	Script::CStruct* pStructure;
+		
+	pStructure = m_network_preferences.GetPreference( Script::GenerateCRC("team_mode") );
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	if( checksum == Script::GenerateCRC( "teams_none" ))
+	{
+		num_teams = 0;
+	}
+	else if( checksum == Script::GenerateCRC( "teams_two" ))
+	{
+		num_teams = 2;
+	}
+	else if( checksum == Script::GenerateCRC( "teams_three" ))
+	{
+		num_teams = 3;
+	}                                              
+	else if( checksum == Script::GenerateCRC( "teams_four" ))
+	{
+		num_teams = 4;
+	}
+
+	return num_teams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					Manager::NumTeamMembers( int team )
+{
+	int num_players;
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+	Lst::Search< NewPlayerInfo > new_sh;
+	NewPlayerInfo* np;
+
+	num_players = 0;
+
+	for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+	{
+		if( player->m_Team == team )
+		{
+			num_players++;
+		}
+	}
+
+	for( np = new_sh.FirstItem( m_new_players ); np; np = new_sh.NextItem())
+	{
+		// Pending players count, observers don't
+		if(	( np->Flags & PlayerInfo::mPENDING_PLAYER ) ||
+			( np->Flags & PlayerInfo::mJUMPING_IN ) ||
+			!( np->Flags & PlayerInfo::mOBSERVER ))
+		{
+			if( np->Team == team )
+			{
+				num_players++;
+			}
+		}                 
+	}
+
+	
+	return num_players;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				Manager::SetMaxPlayers( int max_players )
+{
+	Script::CStruct* pTempStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 checksum;
+	char check_string[32];
+	char name_string[32];
+	
+	Dbg_Assert(( max_players >= 2 ) && ( max_players <= vMAX_PLAYERS ));
+	
+	sprintf( check_string, "num_%d", max_players );
+	checksum = Script::GenerateCRC( check_string );
+
+#if ENGLISH == 0
+	sprintf( name_string, "%d %s", max_players, Script::GetLocalString( "netmenu_str_players" ));
+#else
+	sprintf( name_string, "%d Players", max_players );
+#endif
+
+	pTempStructure = new Script::CStruct;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, 
+								  name_string );
+	pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, (int) checksum );
+	pPreferences = GetNetworkPreferences();
+	pPreferences->SetPreference( Script::GenerateCRC( "num_players"), pTempStructure );
+}
+                                        
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					Manager::GetMaxObservers( void )
+{
+	uint32 checksum;
+	int max_observers = 2;
+	Script::CStruct* pStructure;
+		
+	pStructure = m_network_preferences.GetPreference( Script::GenerateCRC("num_observers"));
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	if( checksum == Script::GenerateCRC( "num_0" ))
+	{
+		max_observers = 0;
+	}
+	else if( checksum == Script::GenerateCRC( "num_1" ))
+	{
+		max_observers = 1;
+	}
+	else if( checksum == Script::GenerateCRC( "num_2" ))
+	{
+		max_observers = 2;
+	}
+	else if( checksum == Script::GenerateCRC( "num_3" ))
+	{
+		max_observers = 3;
+	}
+	else if( checksum == Script::GenerateCRC( "num_4" ))
+	{
+		max_observers = 4;
+	}                                              
+	else if( checksum == Script::GenerateCRC( "num_5" ))
+	{
+		max_observers = 5;
+	}
+	else if( checksum == Script::GenerateCRC( "num_6" ))
+	{
+		max_observers = 6;
+	}
+	else if( checksum == Script::GenerateCRC( "num_7" ))
+	{
+		max_observers = 7;
+	}
+	else if( checksum == Script::GenerateCRC( "num_8" ))
+	{
+		max_observers = 8;
+	}                                              
+
+	return max_observers;
+}
+                                        
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				Manager::SetMaxObservers( int max_observers )
+{
+	Script::CStruct* pTempStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 checksum;
+	char check_string[32];
+	char name_string[32];
+	
+    sprintf( check_string, "num_%d", max_observers );
+	checksum = Script::GenerateCRC( check_string );
+	
+#if ENGLISH == 0
+	if( max_observers == 0 )
+	{
+		sprintf( name_string, Script::GetLocalString( "netmenu_str_no_observers" ));
+	}
+	else
+	{
+		if( max_observers == 1 )
+		{
+			sprintf( name_string, "1 %s", Script::GetLocalString( "netmenu_str_observer" ));
+		}
+		else
+		{
+			sprintf( name_string, "%d %s", max_observers, Script::GetLocalString( "netmenu_str_observers" ));
+		}
+	}
+#else
+	if( max_observers == 0 )
+	{
+		sprintf( name_string, "No Observers" );
+	}
+	else
+	{
+		if( max_observers == 1 )
+		{
+			sprintf( name_string, "1 Observer" );
+		}
+		else
+		{
+			sprintf( name_string, "%d Observers", max_observers );
+		}
+	}
+#endif
+	pTempStructure = new Script::CStruct;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, 
+								  name_string );
+	pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, (int) checksum );
+	pPreferences = GetNetworkPreferences();
+	pPreferences->SetPreference( Script::GenerateCRC( "num_observers"), pTempStructure );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*				Manager::GetPassword( void )
+{
+	Prefs::Preferences* pPreferences;
+	Script::CStruct* pStructure;
+	const char* password;
+		
+	pPreferences = GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("password") );
+	pStructure->GetText( "ui_string", &password, true );
+
+	return (char*) password;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*				Manager::GetLevelName( bool get_created_park_name )
+{
+	Script::CArray* pArray = Script::GetArray("level_select_menu_level_info");
+	Dbg_Assert( pArray );
+
+	for ( int i = 0; i < (int)pArray->GetSize(); i++ )
+	{   
+		uint32 checksum;
+		Script::CStruct* pStructure = pArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+
+        pStructure->GetChecksum( "level", &checksum );
+		if( GetNetworkLevelId() == checksum )
+		{
+			const char* level_name;
+
+			if( get_created_park_name && ( checksum == CRCD(0xb664035d,"Load_Sk5Ed_gameplay")))
+			{
+				return (char*) Ed::CParkManager::sInstance()->GetParkName();
+			}
+			pStructure->GetText( "text", &level_name, true );
+			return (char *) level_name;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*				Manager::GetGameModeName( void )
+{
+	Script::CArray* pArray = Script::GetArray("mode_info");
+	Dbg_Assert( pArray );
+
+	for ( int i = 0; i < (int)pArray->GetSize(); i++ )
+	{   
+		uint32 checksum;
+		uint32 game_mode_id;
+		Script::CStruct* pStructure = pArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+	
+		game_mode_id = GetGameTypeFromPreferences();//skate_mod->GetGameMode()->GetNameChecksum();
+		pStructure->GetChecksum( "checksum", &checksum );
+		if( game_mode_id == checksum )
+		{
+			const char* mode_name;
+
+			pStructure->GetText( "name", &mode_name, true );
+			return (char *) mode_name;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				Manager::PlayerCollisionEnabled( void )
+{
+	uint32 collision_pref;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	 
+	if(	( GameIsOver()) ||
+		( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0x34861a16, "freeskate" )) ||
+		( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0x9d65d0e7, "horse" )))
+	{
+		// Allow collision in lobbies now, if the option is on
+		if( skate_mod->GetGameMode()->GetNameChecksum() != CRCD(0x1c471c60,"netlobby"))
+		{
+			return false;
+		}
+	}                
+
+	// Option is always on in splitscreen games
+	if( CFuncs::ScriptInSplitScreenGame( NULL, NULL ))
+	{
+		return true;
+	}
+
+	// If this option is on, we definitely collide
+	collision_pref = m_network_preferences.GetPreferenceChecksum( CRCD( 0x43ee978, "player_collision"), CRCD( 0x21902065, "checksum" ));
+	if( collision_pref == CRCD( 0xf81bc89b, "boolean_true" ))
+	{
+		return true;
+	}
+
+	// Even if it's turned off, we might still enable collision because player collision is the basis
+	// of some game modes
+	if( ( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0xf9d5d933, "netslap" )) ||
+		( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0xca1f360f, "slap" )))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ShouldDisplayTeamScores( void )
+{
+	uint32 collision_pref;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	 
+	// Always just show players in lobbies
+	if( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0x1c471c60, "netlobby" ))
+	{
+		return false;
+	}
+
+	if(	skate_mod->GetGameMode()->IsTeamGame() == false )
+	{
+		return false;
+	}                
+
+	// If this option is on, we definitely collide
+	collision_pref = m_network_preferences.GetPreferenceChecksum( CRCD( 0x92a6a8c8, "score_display"), CRCD( 0x21902065, "checksum") );
+	return ( collision_pref == CRCD( 0xd071112f, "score_teams" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Net::Server*		Manager::SpawnServer( bool local, bool secure )
+{
+	
+	int flags;
+
+	Net::Manager * net_man = Net::Manager::Instance();
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+
+	Dbg_MsgAssert( m_server == NULL,( "Failed to spawn new server. Old server still running!\n" ));
+
+	flags = Net::App::mBROADCAST |
+			Net::App::mDYNAMIC_RESEND |
+			Net::App::mACCEPT_FOREIGN_CONN;
+
+	if( local )
+	{
+		flags |= Net::App::mLOCAL;
+	}
+	if( secure )
+	{
+		flags |= Net::App::mSECURE;
+	}
+
+	m_server = net_man->CreateNewAppServer( 0, "Skate4", vMAX_CONNECTIONS, 
+											vHOST_PORT, inet_addr( net_man->GetLocalIP()), 
+											flags );
+    
+	SetHostMode( vHOST_MODE_SERVE );
+
+	if( m_server )
+	{
+		mlp_manager->AddLogicTask( *m_server_add_new_players_task );
+		// These handlers will be removed after the server actually starts (i.e. after the lobby shuts down)
+		m_server->m_Dispatcher.AddHandler( Net::MSG_ID_CONNECTION_REQ, 
+											s_handle_connection, 
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN,
+											this, Net::LOWEST_PRIORITY );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_JOIN_REQ,
+										   s_handle_join_request,
+										   Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN,
+										   this );
+		m_server->m_Dispatcher.AddHandler( Net::MSG_ID_FIND_SERVER, 
+											s_handle_find_server, 
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN , this );
+		m_server->m_Dispatcher.AddHandler( Net::MSG_ID_DISCONN_REQ, s_handle_disconn_request, 
+										   Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_OBSERVE, s_handle_observe, 
+										   Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_READY_RESPONSE, s_handle_ready_response, 
+											Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_PLAYER_INFO_ACK, s_handle_player_info_ack,
+										   0, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_MOVED_TO_RESTART, s_handle_player_restarted,
+										   0, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_RUN_HAS_ENDED, s_handle_run_ended, 0, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_FCFS_START_GAME, s_handle_fcfs_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_FCFS_BAN_PLAYER, s_handle_fcfs_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_FCFS_CHANGE_LEVEL, s_handle_fcfs_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_FCFS_TOGGLE_PROSET, s_handle_fcfs_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_FCFS_TOGGLE_GOAL_SELECTION, s_handle_fcfs_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_FCFS_END_GAME, s_handle_fcfs_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_FCFS_SET_NUM_TEAMS, s_handle_fcfs_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_REQUEST_CHANGE_TEAM, s_handle_team_change_request, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_REQUEST_LEVEL, s_handle_request_level, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_BEAT_GOAL, s_handle_beat_goal, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_STARTED_GOAL, s_handle_started_goal, Net::mHANDLE_LATE, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_LEVEL_DATA, s_handle_level_data, 0, this, Net::HIGHEST_PRIORITY );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_RAIL_DATA, s_handle_rail_data, 0, this, Net::HIGHEST_PRIORITY );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_CHALLENGE_RESPONSE, s_handle_challenge_response, 0, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_CHEAT_CHECKSUM_RESPONSE, s_handle_cheat_checksum_response,
+											  0, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_REQUEST_RAILS_DATA, s_handle_level_data_request, 0, this );
+		m_server->m_Dispatcher.AddHandler( MSG_ID_REQUEST_GOALS_DATA, s_handle_level_data_request, 0, this );
+
+		m_last_score_update = 0;
+		m_waiting_for_game_to_start = false;
+        	
+		RandomizeSkaterStartingPoints();
+		m_observer_input_handler = NULL;		// record and handle button inputs
+	}
+	else
+	{
+#ifdef __NOPT_ASSERT__
+		int error = net_man->GetLastError();
+		Dbg_Printf( "Spawn Server: Error %d\n", error );
+#endif
+		// Here output appropriate error message to the screen
+	}
+
+    return m_server;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Net::Client*		Manager::SpawnClient( bool broadcast, bool local, bool secure, int index )
+{
+	
+
+	Net::Manager * net_man = Net::Manager::Instance();
+	int flags;
+
+	Dbg_MsgAssert( m_client[index] == NULL,( "Failed to spawn new client. Old client still running!\n" ));
+
+	flags = 0;
+	if( broadcast )
+	{
+		flags |= Net::App::mBROADCAST;
+	}
+	if( local )
+	{
+		flags |= Net::App::mLOCAL;
+	}
+	else
+	{
+		flags |= Net::App::mDYNAMIC_RESEND | Net::App::mACCEPT_FOREIGN_CONN;
+	}
+	if( secure )
+	{
+		flags |= Net::App::mSECURE;
+	}
+
+	m_client[index] = net_man->CreateNewAppClient( index, "Skate4", 
+											vJOIN_PORT, inet_addr( net_man->GetLocalIP()),
+											flags );
+		
+	if(( index == 0) && ( m_client[index] ))
+	{  
+		m_latest_ready_query = 0;
+		m_proset_flags = 0;
+		m_cam_player = NULL;
+		SetReadyToPlay(false);
+	}
+	else
+	{
+#ifdef __NOPT_ASSERT__
+		int error = net_man->GetLastError();
+		Dbg_Printf( "Spawn Client: Error %d\n", error );
+#endif
+		// Here output appropriate error message to the screen
+	}
+
+	m_observer_input_handler = NULL;
+	m_game_over = false;
+	m_game_pending = false;
+	return m_client[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Net::Client*		Manager::SpawnMatchClient( void )
+{
+	Net::Manager * net_man = Net::Manager::Instance();
+	int flags;
+
+	flags = Net::App::mBROADCAST | Net::App::mDYNAMIC_RESEND | Net::App::mACCEPT_FOREIGN_CONN | Net::App::mSECURE;
+
+	m_match_client = net_man->CreateNewAppClient( 0, "Match Skate4", 
+											vJOIN_PORT, inet_addr( net_man->GetLocalIP()),
+											flags );
+	m_match_client->m_Dispatcher.AddHandler( 	Net::MSG_ID_SERVER_RESPONSE, 
+												s_handle_server_response, 
+												Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN,
+												this );
+
+	return m_match_client;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SpawnAutoServer( void )
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+
+	s_auto_serve_start_time = Tmr::GetTime();
+	mlp_man->AddLogicTask( *m_auto_server_task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::JoinServer( bool observe_only, unsigned long ip, unsigned short port, int index )
+{
+    
+	Net::Conn* conn;
+	MsgConnectInfo msg;
+	Net::MsgDesc msg_desc;
+	uint32 size;
+	const char* network_id;
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	//Net::Manager * net_man = Net::Manager::Instance();
+		
+	Dbg_MsgAssert( m_client[index] != NULL,( "Can't join server : Client has not been spawned\n" ));
+
+#ifndef __PLAT_NGC__
+	//Dbg_Printf( "Joining server at %s %d\n", inet_ntoa( *(struct in_addr*) &ip ), port );
+#endif		// __PLAT_NGC__
+
+	pPreferences = GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("network_id") );
+	pStructure->GetText( "ui_string", &network_id, true );
+
+	if( m_client[index]->IsLocal())
+	{
+		Net::Conn* server_conn;
+
+		Dbg_Assert( m_server );
+
+		server_conn = m_server->NewConnection( ip, port, Net::Conn::mLOCAL );
+		conn = m_client[index]->NewConnection( ip, port, Net::Conn::mLOCAL );
+        m_client[index]->AliasConnections( server_conn, conn );
+	}
+	else
+	{   
+		m_client[index]->ConnectToServer( ip, port );
+		conn = m_client[index]->NewConnection( ip, port );
+
+		m_join_start_time = Tmr::GetTime();
+		mlp_man->AddLogicTask( *m_join_timeout_task );
+		SetJoinState( vJOIN_STATE_CONNECTING );
+		mlp_man->AddLogicTask( *m_join_state_task );
+		
+		SetJoinIP( ip );
+		SetJoinPort( port );
+	}   
+	
+	msg.m_Observer = observe_only;
+	size = 0;
+	
+	msg.m_Version = vVERSION_NUMBER;
+	msg.m_Password[0] = '\0';
+	msg.m_WillingToWait = 0;
+	
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof(MsgConnectInfo);
+	msg_desc.m_Id = Net::MSG_ID_CONNECTION_REQ;
+
+	m_client[index]->EnqueueMessageToServer( &msg_desc );
+	m_client[index]->SendData();
+
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_PROCEED_TO_PLAY, 
+												s_handle_client_proceed, Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_OBSERVE_PROCEED, 
+												s_handle_observe_proceed, Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_OBSERVE_REFUSED, 
+												s_handle_observe_refused, Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_JOIN_REFUSED, 
+												s_handle_join_refused, Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_JOIN_PROCEED, 
+												s_handle_join_proceed, Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_READY_QUERY, s_handle_ready_query, 
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_READY_RESPONSE, s_handle_ready_response, 
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_PLAYER_INFO_ACK_REQ, s_handle_player_info_ack_req, 
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler(	MSG_ID_KING_DROPPED_CROWN, s_handle_dropped_crown, 0, this );
+	m_client[index]->m_Dispatcher.AddHandler(	MSG_ID_PLAYER_DROPPED_FLAG, s_handle_dropped_flag, 0, this );
+	m_client[index]->m_Dispatcher.AddHandler(	MSG_ID_PANEL_MESSAGE, s_handle_panel_message, 
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_AUTO_SERVER_NOTIFICATION, s_handle_auto_server_notification,
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_FCFS_ASSIGNMENT, s_handle_fcfs_assignment,
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_TEAM_CHANGE, s_handle_team_change,
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_SET_NUM_TEAMS, s_handle_set_num_teams,
+												Net::mHANDLE_LATE, this );
+	m_client[index]->m_Dispatcher.AddHandler( 	MSG_ID_CHAT, s_handle_chat, Net::mHANDLE_LATE );
+	m_client[index]->m_Dispatcher.AddHandler(	MSG_ID_CHALLENGE, s_handle_challenge,
+												Net::mHANDLE_LATE, this ); 
+	
+	if( index == 0 )
+	{   
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_SELECT_GOALS, s_handle_select_goals, 0, this );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_GAME_INFO, s_handle_game_info, 0, this );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_KILL_TEAM_FLAGS, s_handle_kill_flags, 0, this );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_BEAT_GOAL, s_handle_beat_goal_relay, 0, this );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_STARTED_GOAL, s_handle_started_goal_relay, 0, this );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_TOGGLE_PROSET, s_handle_toggle_proset, 0, this );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_CHANGE_LEVEL, s_handle_change_level, 0, this,
+												Net::HIGHEST_PRIORITY );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_CHANGE_LEVEL, s_handle_new_level, 0, this,
+												Net::HIGHEST_PRIORITY );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_LEVEL_DATA, s_handle_level_data, 0, this,
+												Net::HIGHEST_PRIORITY );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_RAIL_DATA, s_handle_rail_data, Net::mHANDLE_CRC_MISMATCH, this,
+												Net::HIGHEST_PRIORITY );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_GOALS_DATA, s_handle_goals_data, Net::mHANDLE_CRC_MISMATCH, this,
+												Net::HIGHEST_PRIORITY );
+		m_client[0]->m_Dispatcher.AddHandler( MSG_ID_CHEAT_CHECKSUM_REQUEST, s_handle_cheat_checksum_request,
+											  0, this );
+		
+		
+		if( !m_client[0]->IsLocal())
+		{
+			m_client[index]->m_Dispatcher.AddHandler( MSG_ID_PLAYER_CREATE, s_handle_new_player, 0, this );
+			m_client[index]->m_Dispatcher.AddHandler( MSG_ID_JOIN_ACCEPTED, s_handle_join_accepted, 0, this );
+			
+		}
+
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_WAIT_N_SECONDS, s_handle_wait_n_seconds, 0, this );
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_NEW_KING, s_handle_new_king, 0, this );
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_CAPTURED_FLAG, s_handle_captured_flag, 0, this );
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_STOLE_FLAG, s_handle_stole_flag, 0, this );
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_TOOK_FLAG, s_handle_took_flag, 0, this );
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_RETRIEVED_FLAG, s_handle_retrieved_flag, 0, this );
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_GAME_OVER, s_handle_game_over, 0, this );
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_END_GAME, s_handle_end_game, 0, this );
+		
+		// Add this low-priority object update handler just to advance the dispatcher's stream pointer.
+		// Since we no longer send the size of the object update stream with the stream, we need
+		// some code to return to the dispatcher the length of the message. If, because of timing,
+		// we get one of these messages before our Mdl::Skate object update handler is instantiated
+		// this handler will figure out the length of the message and pass it back
+		m_client[index]->m_Dispatcher.AddHandler( MSG_ID_OBJ_UPDATE_STREAM, s_handle_object_update, 
+												  Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this,
+												  Net::HIGHEST_PRIORITY );
+	}
+
+	SetObserverCommand( vOBSERVER_COMMAND_NONE );
+}
+    
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ReattemptJoin( Net::App* client )
+{
+	MsgConnectInfo msg;
+	Net::MsgDesc msg_desc;
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+		
+	msg.m_Observer = ( GetJoinMode() == vJOIN_MODE_OBSERVE );
+	msg.m_Version = vVERSION_NUMBER;
+	msg.m_WillingToWait = 1;
+	msg.m_Password[0] = '\0';
+	
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof(MsgConnectInfo);
+	msg_desc.m_Id = Net::MSG_ID_CONNECTION_REQ;
+	client->EnqueueMessageToServer( &msg_desc );
+	client->SendData();
+	
+	// Reset join timeout
+	m_join_start_time = Tmr::GetTime();
+	mlp_man->AddLogicTask( *m_join_timeout_task );
+
+	SetJoinState( vJOIN_STATE_JOINING );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::CancelJoinServer( void )
+{
+	Net::Client* client;
+    Net::MsgDesc msg_desc;
+
+	// These are "finished" states. Don't redundantly cancel our join if we've already cancelled
+	if(	( GetJoinState() == vJOIN_STATE_FINISHED ) ||
+		( GetJoinState() == vJOIN_STATE_CONNECTED ) ||
+		( GetJoinState() == vJOIN_STATE_REFUSED ))
+	{
+		return;
+	}
+
+	client = GetClient( 0 );
+	Dbg_Assert( client );
+
+    // First, wait for any pending network tasks to finish before sending out final disconn message
+#ifdef __PLAT_NGPS__
+	//client->WaitForAsyncCallsToFinish();
+#endif  		
+	Dbg_Printf( "Leaving server\n" );
+			
+	// Send off last message, notifying server that I'm about to quit
+	msg_desc.m_Id = Net::MSG_ID_DISCONN_REQ;
+	client->EnqueueMessageToServer( &msg_desc );
+	// Wake the network thread up to send the data
+	client->SendData();
+		
+	// Wait for that final send to complete
+#ifdef __PLAT_NGPS__
+	//client->WaitForAsyncCallsToFinish();
+#endif  
+    
+	ClientShutdown();
+
+	m_join_timeout_task->Remove();
+
+	// We may have made it halfway in, so free up any pending players.
+	free_all_pending_players(); 
+
+#ifdef __PLAT_NGPS__
+	if( mpBuddyMan->IsLoggedIn())
+	{
+		mpBuddyMan->SetStatusAndLocation( GP_CHATTING, (char*) Script::GetString( "homie_status_chatting" ), 
+														mpLobbyMan->GetLobbyName());
+	}
+#endif
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ReattemptJoinWithPassword( char* password )
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	Net::Manager * net_man = Net::Manager::Instance();
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	const char* network_id;
+	MsgJoinInfo msg;
+	Net::MsgDesc msg_desc;
+	int size;
+	bool ignore_face_data;
+
+	Dbg_Printf( "************* Trying password : %s\n", password );
+		
+	pPreferences = GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("network_id") );
+	pStructure->GetText( "ui_string", &network_id, true );
+
+#ifdef __PLAT_NGPS__
+	msg.m_Profile = mpBuddyMan->GetProfile();
+	msg.m_Rating = mpStatsMan->GetStats()->GetRating();
+#endif
+	msg.m_Observer = ( GetJoinMode() == vJOIN_MODE_OBSERVE );
+	msg.m_Version = vVERSION_NUMBER;
+	msg.m_WillingToWait = 1;
+	if(	net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM )
+	{
+		msg.m_Broadband = 0;
+		ignore_face_data = true;
+	}
+	else
+	{
+		msg.m_Broadband = 1;
+		ignore_face_data = false;
+	}
+	strcpy( msg.m_Name, network_id );
+	strcpy( msg.m_Password, password );
+	size = 0;
+	if( !msg.m_Observer )
+	{
+		// GJ:  transmit the way you look (slot 0 of the
+		// skater profile manager) to the server
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		Obj::CSkaterProfile* pSkaterProfile = skate_mod->GetProfile(0);
+		Net::Conn *server_conn;
+		Lst::Search< Net::Conn > sh;
+	
+		server_conn = m_client[0]->FirstConnection( &sh );
+		if( server_conn->GetBandwidthType() == Net::Conn::vNARROWBAND )
+		{
+			ignore_face_data = true;
+		}
+	
+		size = pSkaterProfile->WriteToBuffer(msg.m_AppearanceData, vMAX_APPEARANCE_DATA_SIZE,
+												ignore_face_data );
+		Dbg_Assert( size < vMAX_APPEARANCE_DATA_SIZE );
+		Dbg_Printf("\n\n******************* MsgJoinInfo appearance data size = %d %d\n", size, sizeof(MsgJoinInfo) - vMAX_APPEARANCE_DATA_SIZE + size);
+	}
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof(MsgJoinInfo) - vMAX_APPEARANCE_DATA_SIZE + size;
+	msg_desc.m_Id = MSG_ID_JOIN_REQ;
+	msg_desc.m_Queue = Net::QUEUE_IMPORTANT;
+	m_client[0]->EnqueueMessageToServer( &msg_desc );
+    
+	//m_client[0]->SendData();
+
+	// Reset join timeout
+	m_join_start_time = Tmr::GetTime();
+	mlp_man->AddLogicTask( *m_join_timeout_task );
+
+	SetJoinState( vJOIN_STATE_JOINING_WITH_PASSWORD );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ServerShutdown( void )
+{
+	
+	Net::Manager * net_man = Net::Manager::Instance();
+
+	Dbg_Message( "Shutting Server Down\n" );
+
+	if( m_server )
+	{
+#ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+		if( !m_server->IsLocal() && InInternetMode())
+		{
+			mpLobbyMan->StopReportingGame();
+			NNFreeNegotiateList();
+		}
+#endif
+#endif    
+		net_man->DestroyApp( m_server );
+		m_server_add_new_players_task->Remove();
+		m_start_network_game_task->Remove();
+		m_server = NULL;
+	}
+
+	m_flags.ClearMask( mSERVER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	  Manager::ClientShutdown( void )
+{
+	
+	
+	Net::Manager * net_man = Net::Manager::Instance();
+	int i;
+
+#ifdef	__PLAT_XBOX__	 
+	if( m_client[0] && !m_client[0]->IsLocal())
+	{
+		if( m_XboxKeyRegistered )
+		{
+			if( XNetUnregisterKey( &m_XboxKeyId ) == 0 )
+			{
+				m_XboxKeyRegistered = false;
+			}
+		}
+	}
+#endif
+
+	for( i = 0; i < vMAX_LOCAL_CLIENTS; i++ )
+	{
+		if( m_client[i] )
+		{
+			if(( i == 0 ) && !m_client[i]->IsLocal())
+			{
+				m_client_add_new_players_task->Remove();
+			}
+
+			net_man->DestroyApp( m_client[i] );
+			m_client[i] = NULL;
+		}
+	}
+
+	if( m_observer_input_handler )
+	{
+		delete m_observer_input_handler;
+		m_observer_input_handler = NULL;
+	}
+
+	m_enter_chat_task->Remove();
+	m_render_scores_task->Remove();
+	m_change_level_task->Remove();
+
+	ClearTriggerEventList();
+	SetReadyToPlay(false);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::MatchClientShutdown( void )
+{
+	if( m_match_client )
+	{
+		Net::Manager * net_man = Net::Manager::Instance();
+
+		net_man->DestroyApp( m_match_client );
+		m_match_client = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::AutoServerShutdown( void )
+{
+	m_auto_server_task->Remove();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetJoinIP( void )
+{
+	return m_join_ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetJoinPrivateIP( void )
+{
+	return m_join_private_ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetJoinPort( void )
+{
+	return m_join_port;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetJoinIP( int ip )
+{
+	Dbg_Printf( "**** In SetJoinIP: %s\n", inet_ntoa(*(struct in_addr *) &ip ));
+	m_join_ip = ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetJoinPrivateIP( int ip )
+{
+	m_join_private_ip = ip;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetJoinPort( int port )
+{
+	m_join_port = port;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Net::Server*  Manager::GetServer( void )
+{
+	return m_server;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Net::Client*   Manager::GetClient( int index )
+{
+	
+
+	Dbg_Assert( index < vMAX_LOCAL_CLIENTS );
+   	
+	return m_client[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Net::Client*   Manager::GetMatchClient( void )
+{   
+	return m_match_client;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetNumPlayers( void )
+{
+	int num_players;
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+	Lst::Search< NewPlayerInfo > new_sh;
+	NewPlayerInfo* np;
+
+	num_players = 0;
+
+	for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+	{
+		if( !player->IsObserving() || player->m_flags.TestMask( PlayerInfo::mPENDING_PLAYER ))
+		{
+			num_players++;
+		}
+	}
+    
+	for( np = new_sh.FirstItem( m_new_players ); np; np = new_sh.NextItem())
+	{
+		// Pending players count, observers don't
+		if(	( np->Flags & PlayerInfo::mPENDING_PLAYER ) ||
+			( np->Flags & PlayerInfo::mJUMPING_IN ) ||
+			!( np->Flags & PlayerInfo::mOBSERVER ))
+		{
+			num_players++;
+		}                 
+	}
+	
+	return num_players;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetNumObservers( void )
+{
+	int num_observers;
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+	Lst::Search< NewPlayerInfo > new_sh;
+	NewPlayerInfo* np;
+
+	num_observers = 0;
+
+	for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+	{
+		// Pending players don't count as observers
+		if( ( player->IsObserving()) && 
+			( !player->IsPendingPlayer()))
+		{   
+			// Don't count a server who is sitting out as an observer
+			if(	( player->IsLocalPlayer() && 
+				( GetHostMode() != vHOST_MODE_SERVE )))
+			{
+				continue;
+			}
+			num_observers++;
+		}
+	}
+    
+	for( np = new_sh.FirstItem( m_new_players ); np; np = new_sh.NextItem())
+	{
+		// Don't count pending players as observers
+		if(( np->Flags & PlayerInfo::mOBSERVER ) &&
+		   ( !( np->Flags & PlayerInfo::mPENDING_PLAYER )))
+		{
+			num_observers++;
+		}                 
+	}
+	
+	return num_observers;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*		Manager::NewPlayer( Obj::CSkater* skater, Net::Conn* conn, int flags )
+{
+	PlayerInfo* new_player;
+		
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	new_player = new PlayerInfo( flags );
+
+	new_player->m_Skater = skater;
+	new_player->m_Conn = conn;
+		
+	// By default, count the Local player as our currently observed player
+	if( new_player->IsLocalPlayer())
+	{
+		if( new_player->IsObserving())
+		{   
+			Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+			Inp::Manager * inp_manager = Inp::Manager::Instance();
+			
+			/*new_player->m_cam = new Obj::CSkaterCam( 0 );
+			Nx::CViewportManager::sSetCamera( 0, new_player->m_cam );
+			new_player->m_cam->SetMode( Obj::CSkaterCam::SKATERCAM_MODE_NORMAL_MEDIUM, 0.0f );*/
+						
+			Script::RunScript( "hide_panel_stuff" );
+			ObservePlayer( GetNextPlayerToObserve());
+
+			new_player->m_observer_logic_task = 
+					new Tsk::Task< PlayerInfo > ( PlayerInfo::s_observer_logic_code, *new_player );
+			mlp_manager->AddLogicTask( *new_player->m_observer_logic_task );
+			m_observer_input_handler = new Inp::Handler< Manager > ( 0,  s_observer_input_logic_code, *this, 
+															 Tsk::BaseTask::Node::vHANDLER_PRIORITY_OBSERVER_INPUT_LOGIC );
+			inp_manager->AddHandler( *m_observer_input_handler );
+		}
+		else
+		{
+			m_cam_player = NULL;
+		}
+	}
+	else
+	{
+		PlayerInfo* local_player;
+
+		local_player = GetLocalPlayer();
+		Dbg_MsgAssert( local_player,( "Should have local player by now" ));
+
+		if( local_player->IsObserving())
+		{
+			if( m_cam_player == NULL )
+			{
+				ObservePlayer( new_player );
+			}
+		}
+	}
+
+	Dbg_Printf( "Player added : %p with conn %p\n", new_player, new_player->m_Conn );
+    
+	Mem::Manager::sHandle().PopContext();
+
+	return new_player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			Manager::SendFaceDataToServer( void )
+{
+	bool sent_data;
+	Net::Manager* net_man = Net::Manager::Instance();
+
+	Dbg_Assert( InNetGame());
+
+	// Don't send face data to other players if I'm on a modem
+	if(	net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM )
+	{
+		return false;
+	}
+
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+
+	sent_data = false;
+	if( pFaceTexture && pFaceTexture->IsValid())
+	{
+		if( OnServer())
+		{
+			PlayerInfo* local_player;
+
+			local_player = GetLocalPlayer();
+			
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetMiscHeap());
+			local_player->m_face_data = new uint8[vMAX_FACE_DATA_SIZE + 1];
+			Mem::Manager::sHandle().PopContext();
+
+			local_player->m_face_data[0] = 0;	// player id
+			pFaceTexture->WriteToBuffer( &local_player->m_face_data[1], vMAX_FACE_DATA_SIZE );
+			sent_data = true;
+		}
+		else
+		{
+			Net::Conn *server_conn;
+			Lst::Search< Net::Conn > sh;
+		
+			server_conn = GetClient(0)->FirstConnection( &sh );
+			if( server_conn->GetBandwidthType() == Net::Conn::vBROADBAND )
+			{
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+				uint8* buffer = new uint8[vMAX_FACE_DATA_SIZE];
+				Mem::Manager::sHandle().PopContext();
+	
+				pFaceTexture->WriteToBuffer( buffer, vMAX_FACE_DATA_SIZE );
+				GetClient(0)->StreamMessageToServer( MSG_ID_FACE_DATA, vMAX_FACE_DATA_SIZE, buffer, "Face Data",
+													 vSEQ_GROUP_FACE_MSGS, false, false );
+				delete[] buffer;
+				sent_data = true;
+			}
+		}
+	}
+
+	return sent_data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SendFaceDataToPlayer( PlayerInfo* player )
+{
+	PlayerInfo* face_player;
+	Lst::Search< PlayerInfo > sh;
+	Net::Manager* net_man = Net::Manager::Instance();
+
+	Dbg_Assert( OnServer());
+
+	// Don't send face data to other players if I'm on a modem
+	if(	net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM )
+	{
+		return;
+	}
+
+	for( face_player = FirstPlayerInfo( sh ); face_player; face_player = NextPlayerInfo( sh ))
+	{
+		bool has_face_data;
+
+		if( face_player == player )
+		{
+			continue;
+		}
+		// Don't send face data to modem players for bandwidth reasons
+		if( player->m_Conn->GetBandwidthType() == Net::Conn::vNARROWBAND )
+		{
+			continue;
+		}
+
+		has_face_data = false;
+		if( face_player->GetFaceData())
+		{
+			has_face_data = true;
+		}
+		else
+		{
+			if( face_player->IsLocalPlayer())
+			{
+				has_face_data = SendFaceDataToServer();
+			}
+		}
+
+		if( has_face_data == false )
+		{
+			continue;
+		}
+
+		
+		m_server->StreamMessageToConn( player->m_Conn, MSG_ID_FACE_DATA, vMAX_FACE_DATA_SIZE + 1, face_player->GetFaceData(), "Face Data",
+									   vSEQ_GROUP_FACE_MSGS, false, true );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ClientAddNewPlayer( NewPlayerInfo* new_player )
+{
+	Obj::CSkater* skater;
+	PlayerInfo* player_info;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    
+	Dbg_Assert( !OnServer());
+
+	skater = NULL;
+    
+	if( !( new_player->Flags & PlayerInfo::mOBSERVER ))
+	{
+		Obj::CSkaterProfile* pSkaterProfile;
+
+		if ( CFuncs::ScriptInSplitScreenGame( NULL, NULL ) )
+		{
+			// in splitscreen games, we have to figure out
+			// which player is currently being added (either 0 or 1)
+			pSkaterProfile = skate_mod->GetProfile(new_player->ObjID);
+		}
+		else
+		{
+			// in other types of games, "your player" is always
+			// in slot 0 of the local machine's skater profile manager
+			pSkaterProfile = skate_mod->GetProfile(0);
+		}
+		if( InNetGame())
+		{
+			if( new_player->Flags & PlayerInfo::mLOCAL_PLAYER )
+			{
+				skater = skate_mod->add_skater( pSkaterProfile, true, new_player->ObjID, 0);
+			}
+			else
+			{
+				int skater_num;
+
+				
+				if( skate_mod->GetLocalSkater())
+				{
+					skater_num = skate_mod->GetNumSkaters();
+				}
+				else
+				{
+					skater_num = ( skate_mod->GetNumSkaters() + 1 ) % Mdl::Skate::vMAX_SKATERS;
+				}
+
+				skater = skate_mod->add_skater( new_player->mpSkaterProfile, false, new_player->ObjID,
+													skater_num );
+				
+				if (new_player->VehicleControlType)
+				{
+					Obj::CSkaterStateHistoryComponent* p_skater_state_history_component = GetSkaterStateHistoryComponentFromObject(skater);
+					Dbg_Assert(p_skater_state_history_component);
+					
+					p_skater_state_history_component->SetCurrentVehicleControlType(new_player->VehicleControlType);
+				}
+			}
+		}
+		else
+		{
+			skater = skate_mod->add_skater( pSkaterProfile, true, new_player->ObjID, skate_mod->GetNumSkaters() );
+		}
+	}
+
+	player_info = GetPlayerByObjectID( new_player->ObjID );
+	if( new_player->Flags & PlayerInfo::mJUMPING_IN )
+	{   
+		if( player_info == NULL )
+		{
+			player_info = GetLocalPlayer();
+		}
+
+		if( player_info )
+		{
+			DestroyPlayer( player_info );
+			player_info = NULL;
+		}
+	}
+
+	if( player_info == NULL )
+	{
+		int flags;
+
+		flags = ( new_player->Flags & (	PlayerInfo::mLOCAL_PLAYER | PlayerInfo::mOBSERVER | PlayerInfo::mHAS_ANY_FLAG ));
+										
+		player_info = NewPlayer( skater, new_player->Conn, flags );
+        strcpy( player_info->m_Name, new_player->Name );
+		player_info->m_Team = new_player->Team;
+		player_info->m_Profile = new_player->Profile;
+		player_info->m_Rating = new_player->Rating;
+		player_info->m_VehicleControlType = new_player->VehicleControlType;
+		AddPlayerToList( player_info );
+		if( new_player->Flags & PlayerInfo::mKING_OF_THE_HILL )
+		{
+			player_info->MarkAsKing( true );
+		}
+	}
+	
+	// We get this message when we're added to a network lobby. So go ahead and start it up
+	// on our end
+	if( new_player->Flags & PlayerInfo::mJUMPING_IN )
+	{
+		// We're jumping into a game at the lobby so start the lobby game flow.
+		// Also send our face texture data to all other players.
+		SendFaceDataToServer();
+		skate_mod->GetTrickObjectManager()->DeleteAllTrickObjects();
+		Script::RunScript( "do_backend_retry" );
+	}
+
+	if( player_info->IsLocalPlayer() == false )
+	{
+		Mdl::Score* score = GetSkaterScoreComponentFromObject( skater )->GetScore();
+
+		skater->RemoveFromCurrentWorld();
+		score->SetTotalScore( new_player->Score );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::DeferredNewPlayer( NewPlayerInfo* new_player, int num_frames )
+{
+	
+	
+	Lst::Node< NewPlayerInfo > *player;
+	Lst::Search< NewPlayerInfo > new_sh;
+	NewPlayerInfo* np;
+    
+	// First, make sure we don't already have this new player queued up
+	for( np = new_sh.FirstItem( m_new_players ); np; np = new_sh.NextItem())
+	{
+		if( np->ObjID == new_player->ObjID )
+		{
+			return;
+		}
+	}
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	np = new NewPlayerInfo;
+
+	strcpy( np->Name, new_player->Name );
+    
+	np->Conn = new_player->Conn;
+	np->ObjID = new_player->ObjID;
+	np->Flags = new_player->Flags;
+	np->Team = new_player->Team;
+	np->Profile = new_player->Profile;
+	np->Rating = new_player->Rating;
+	np->Score = new_player->Score;
+	np->VehicleControlType = new_player->VehicleControlType;
+	if(( np->Flags & PlayerInfo::mJUMPING_IN ) && OnServer())
+	{
+		np->JumpInFrame = m_server->m_FrameCounter;
+	}
+
+	// Pure observers don't have skater profile date
+	if( (!( np->Flags & PlayerInfo::mOBSERVER ) || ( np->Flags & PlayerInfo::mPENDING_PLAYER )))
+	{
+		// GJ:  copy the contents of the skater profile from one to the other
+		// it'd be nice to just use a copy constructor, but the compiler
+		// doesn't seem to like it.
+		uint8* pTempBuffer = new uint8[vMAX_APPEARANCE_DATA_SIZE];
+		new_player->mpSkaterProfile->WriteToBuffer(pTempBuffer, vMAX_APPEARANCE_DATA_SIZE);
+		np->mpSkaterProfile->ReadFromBuffer(pTempBuffer);
+		delete[] pTempBuffer;
+		
+		// GJ 2/12/03:  the above was written sometime during THPS3,
+		// and our current compiler seems to be okay now.  However,
+		// i'm afraid of breaking anything by changing this now...
+//		*np->mpSkaterProfile = *new_player->mpSkaterProfile;
+	}
+						 
+	if( OnServer())
+	{
+        np->AllowedJoinFrame = m_server->m_FrameCounter + num_frames;
+
+		if( !( np->Flags & PlayerInfo::mLOCAL_PLAYER ))
+		{
+			if( np->Flags & PlayerInfo::mOBSERVER )
+			{
+				uint32 msg_checksum;
+
+				if( np->Flags & PlayerInfo::mPENDING_PLAYER )
+				{
+					msg_checksum = Script::GenerateCRC("net_message_join_pending");
+				}
+				else
+				{
+					msg_checksum = Script::GenerateCRC("net_message_observing");
+				}
+				CreateNetPanelMessage( true, msg_checksum, new_player->Name );
+			}
+			else
+			{
+				CreateNetPanelMessage( false, Script::GenerateCRC("net_message_joining"),
+									   new_player->Name );
+			}
+		}
+	}
+	else
+	{
+		np->AllowedJoinFrame = m_client[0]->m_FrameCounter + num_frames;
+	}
+	
+	player = new Lst::Node< NewPlayerInfo > ( np );
+
+	m_new_players.AddToTail( player );
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::AddPlayerToList( PlayerInfo* player )
+{
+	
+
+	Dbg_Assert( player );
+
+    m_players.AddToTail( player );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::DestroyPlayer( PlayerInfo* player )
+{
+	// If we're destroying the player we're looking at, change to the next player
+	if( player == m_cam_player )
+	{
+		PlayerInfo* local_player;
+
+		m_cam_player = GetNextPlayerToObserve();
+		if( player == m_cam_player )
+		{
+			m_cam_player = NULL;
+		}
+        
+		local_player = GetLocalPlayer();
+		if( local_player && local_player->m_Skater )
+		{
+			ObservePlayer( local_player );
+		}
+		else
+		{
+			ObservePlayer( m_cam_player );
+		}
+	}
+
+	if( player->IsKing())
+	{
+		if( m_crown )
+		{
+			m_crown->RemoveFromKing();
+		}
+	}
+
+	if( player->HasCTFFlag())
+	{
+		char team_str[64];
+		int team;
+		Script::CStruct* pParams;
+		PlayerInfo* local_player;
+		
+		team = player->HasWhichFlag();
+		sprintf( team_str, "team_%d_name", team + 1 );
+		pParams = new Script::CStruct;
+
+		pParams->AddInteger( "team", team );
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		Script::RunScript( "flag_returned", pParams );
+
+		delete pParams;
+
+		local_player = GetLocalPlayer();
+		if( local_player && !local_player->IsObserving())
+		{
+			if( local_player->m_Team == team )
+			{
+				Script::RunScript( "hide_ctf_arrow" );		
+			}
+		}
+		player->ClearCTFState();
+	}
+
+	delete player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*		Manager::GetCurrentlyObservedPlayer( void )
+{
+	return m_cam_player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*		Manager::GetNextPlayerToObserve( void )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* new_cam_player;
+
+	
+
+	new_cam_player = NULL;
+	for( new_cam_player = FirstPlayerInfo( sh ); new_cam_player;
+			new_cam_player = NextPlayerInfo( sh ))
+	{
+		// If we had no 'current' target, just return the first player
+		if( m_cam_player == NULL )
+		{
+			break;
+		}
+
+		// Ok, we found our current cam_player. Return the next one, if there is a next one
+		if( new_cam_player == m_cam_player )
+		{
+			new_cam_player = NextPlayerInfo( sh );
+			if( new_cam_player == NULL )
+			{
+				// We've reached the end of the list. Return the first one
+				new_cam_player = FirstPlayerInfo( sh );
+			}
+			break;
+		}
+	}
+
+	return new_cam_player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::ObservePlayer( PlayerInfo* player )
+{   
+	Obj::CSkaterCameraComponent* skater_cam;
+	Obj::CWalkCameraComponent* walk_cam;
+	Obj::CCompositeObject* cam_obj;
+	
+	cam_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x967c138c,"skatercam0"));
+	skater_cam = GetSkaterCameraComponentFromObject( cam_obj );
+	walk_cam = GetWalkCameraComponentFromObject( cam_obj );
+	
+	if (!player || !player->m_Skater)
+	{
+		Dbg_Printf( "Attempting to observe NULL player\n" );
+		
+		skater_cam->SetSkater( NULL );
+		walk_cam->SetSkater( NULL );
+		
+		m_cam_player = NULL;
+		
+		return;
+	}
+	
+	Manager* gamenet_man = Manager::Instance();
+	PlayerInfo* local_player;
+	local_player = gamenet_man->GetLocalPlayer();
+	
+	if (player != local_player)
+	{
+		// observer camera only use the skater camera logic
+		
+		Dbg_Printf( "Observing player %d\n", player->m_Skater->GetID());
+		skater_cam->SetSkater( player->m_Skater );
+		
+		walk_cam->SetSkater( NULL );
+		
+		skater_cam->Suspend( false );
+		walk_cam->Suspend( true );
+	}
+	else
+	{
+		// restore the correct camera state for a normal skate camera
+		
+		Obj::CSkaterPhysicsControlComponent* physics_control_comp;
+		
+		physics_control_comp = GetSkaterPhysicsControlComponentFromObject( player->m_Skater );
+		Dbg_MsgAssert(physics_control_comp, ("Local player has no SkaterPhysicsControlComponent"));
+		
+		Dbg_Printf( "Restoring standard camera to player %d\n", player->m_Skater->GetID());
+		
+		skater_cam->SetSkater( player->m_Skater );
+		walk_cam->SetSkater( player->m_Skater );
+		
+		if (physics_control_comp->IsSkating())
+		{
+			skater_cam->Suspend( false );
+			walk_cam->Suspend( true );
+		}
+		else
+		{
+			skater_cam->Suspend( true );
+			walk_cam->Suspend( false );
+			walk_cam->Reset();
+		}
+	}
+	
+	m_cam_player = player;
+
+	/*if( player == NULL )
+	{
+		Mth::Vector rot;
+		Mth::Matrix cam_matrix;
+		Mth::Vector node_pos;
+		int node;
+
+		node = Obj::GetRestartNode( Script::GenerateCRC( "Player1" ), 0 );
+		Script::CStruct* pNodeData = SkateScript::GetNode( node ); 
+		
+		cam_matrix.Ident( );	   		// Set identity before we decided if we need to rotate or not, in case we don't.
+		
+		SkateScript::GetPosition( node, &node_pos );
+		node_pos[Y] += FEET( 5 );
+		Dbg_Printf( "************************ Got Position %f %f %f\n", node_pos[X], node_pos[Y], node_pos[Z] );
+		
+		SkateScript::GetAngles( pNodeData, &rot );
+		cam_matrix.RotateY( rot[ Y ] );
+		cam_matrix[Z] = -cam_matrix[Z];
+		cam_matrix[X] = -cam_matrix[X];
+		Dbg_Printf( "************************ Rots X: %f Y: %f Z: %f\n", rot[X], rot[Y], rot[Z] );
+		
+		Dbg_Assert( local_player );
+		Dbg_Assert( local_player->m_cam );
+
+		local_player->m_cam->SetMatrix( cam_matrix );
+		local_player->m_cam->SetPos( node_pos );
+		local_player->m_cam->SetSkater( NULL );
+		return;
+	}
+
+	// Only works in network games
+	if( InNetGame())
+	{   
+		if( local_player && local_player->IsObserving())
+		{   
+			m_cam_player = player;
+			local_player->m_cam->SetSkater( m_cam_player->m_Skater );
+		}
+	}*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::RequestObserverMode( void )
+{
+	PlayerInfo* local_player;
+    Net::MsgDesc msg_desc;
+	
+	local_player = GetLocalPlayer();
+	Dbg_Assert( local_player );
+
+	if( local_player->IsObserving())
+	{
+		return;
+	}
+	
+	Dbg_Printf( "Sent request to enter observer mode\n" );
+	msg_desc.m_Id = MSG_ID_OBSERVE;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	m_client[0]->EnqueueMessageToServer( &msg_desc );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::ChooseNewServerPlayer( void )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Net::MsgDesc msg_desc;
+
+	Dbg_Assert(( GetServerPlayer() == NULL ));	// Shouldn't call this function if one already exists
+	Dbg_Assert( m_server );
+
+	for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+	{
+		Script::CStruct* pStructure;
+		uint32 checksum;
+
+		// For now, just select the first non-server player info
+		if( player->IsLocalPlayer())
+		{
+			continue;
+		}
+
+		// If we don't have any "fully in" players at this point, that's ok. Don't choose a server player.
+		// When they are marked as being "fully in" they will be chosen as the fcfs and notified
+		if( player->IsFullyIn() == false )
+		{
+			continue;
+		}
+
+		player->MarkAsServerPlayer();
+		
+		pStructure = m_network_preferences.GetPreference( Script::GenerateCRC("player_collision"));
+		pStructure->GetChecksum( "Checksum", &checksum, true );
+		
+		msg_desc.m_Data = &checksum;
+		msg_desc.m_Length = sizeof( uint32 );
+		msg_desc.m_Id = MSG_ID_FCFS_ASSIGNMENT;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+
+		break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::EnterObserverMode( void )
+{
+	PlayerInfo* local_player;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	Inp::Manager * inp_manager = Inp::Manager::Instance();
+	Lst::Search< PlayerInfo > first_player;
+	
+	Dbg_Printf( "Entering Observer Mode\n" );
+	
+	local_player = GetLocalPlayer();
+	Dbg_Assert( local_player );
+
+    if( local_player->IsObserving())
+	{
+		return;
+	}
+	
+	if( OnServer())
+	{
+		DropPlayer( local_player, vREASON_OBSERVING );
+	}
+
+	if( local_player->IsKing())
+	{
+		local_player->MarkAsKing( false );
+		if( m_crown )
+		{
+			m_crown->RemoveFromKing();
+		}
+	}
+	if( local_player->HasCTFFlag())
+	{
+		char team_str[64];
+		int team;
+		Script::CStruct* pParams;
+		
+		team = local_player->HasWhichFlag();
+		sprintf( team_str, "team_%d_name", team + 1 );
+		pParams = new Script::CStruct;
+
+		pParams->AddInteger( "team", team );
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		Script::RunScript( "flag_returned", pParams );
+
+		delete pParams;
+
+		Script::RunScript( "hide_ctf_arrow" );		
+		local_player->ClearCTFState();
+	}
+
+	local_player->m_flags.SetMask( PlayerInfo::mOBSERVER );
+
+
+	skate_mod->remove_skater( local_player->m_Skater );
+	local_player->m_Skater = NULL;
+	//skate_mod->HideSkater( local_player->m_Skater );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterHeap( 0 ));
+
+	//Gfx::Camera* p_camera = &(viewer_mod->GetCamera( 0 )->camera );
+	/*local_player->m_cam = new Obj::CSkaterCam( 0 );
+	Dbg_Assert( local_player->m_cam );
+	Nx::CViewportManager::sSetCamera( 0, local_player->m_cam );
+	local_player->m_cam->SetMode( Obj::CSkaterCam::SKATERCAM_MODE_NORMAL_MEDIUM, 0.0f );*/
+
+	ObservePlayer( GetNextPlayerToObserve());
+
+	local_player->m_observer_logic_task = 
+			new Tsk::Task< PlayerInfo > ( PlayerInfo::s_observer_logic_code, *local_player );
+	mlp_manager->AddLogicTask( *local_player->m_observer_logic_task );
+	m_observer_input_handler = new Inp::Handler< Manager > ( 0,  s_observer_input_logic_code, *this, 
+															 Tsk::BaseTask::Node::vNORMAL_PRIORITY-1 );
+	inp_manager->AddHandler( *m_observer_input_handler );
+	SetObserverCommand( vOBSERVER_COMMAND_NONE );
+
+	Script::RunScript( "ShowAllObjects" );
+	Script::RunScript( "hide_panel_stuff" );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetNextPlayerObjectId( void )
+{
+	Lst::Search< PlayerInfo > sh;
+	Lst::Search< NewPlayerInfo > new_sh;
+	PlayerInfo* player;
+	NewPlayerInfo* new_player;
+	int i;
+	bool taken;
+
+	
+
+	Dbg_MsgAssert( m_flags.TestMask( mSERVER ),( "Only the server should be assigning ids!\n" ));
+	
+	for( i = 0; i < 16; i++ )
+	{
+		taken = false;
+		for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+		{
+			if( player->m_Skater->GetID() == (uint32)i )
+			{
+				taken = true;
+				break;
+			}
+		}
+
+		if( taken == false )
+		{
+			for( new_player = FirstNewPlayerInfo( new_sh ); new_player;
+					new_player = NextNewPlayerInfo( new_sh ))
+			{
+				// Pure observers don't and won't have object ids
+				if(( new_player->Flags & PlayerInfo::mOBSERVER ) &&
+				   ( !( new_player->Flags & PlayerInfo::mPENDING_PLAYER )))
+				{
+					continue;
+				}
+				if( new_player->ObjID == i )
+				{
+					taken = true;
+					break;
+				}
+			}
+		}
+
+		if( taken == false )
+		{
+			return i;
+		}
+	}
+
+	Dbg_MsgAssert( 0,( "Ran out of player object id's. More than 16 players in game?\n" ));
+	return 16;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CSkater*	Manager::GetSkaterByConnection( Net::Conn* conn )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo *player;
+
+	for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+	{
+		if( player->m_Conn == conn )
+		{
+			return player->m_Skater;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*		Manager::GetPlayerByConnection( Net::Conn* conn )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo *player;
+
+	for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+	{
+		if( player->m_Conn == conn )
+		{
+			return player;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*		Manager::GetPlayerByObjectID( unsigned short obj_id )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo *player;
+
+	for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+	{
+		if( player->m_Skater )
+		{
+			if( player->m_Skater->GetID() == obj_id )
+			{
+				return player;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::FindServersOnLAN( void )
+{
+	if( m_match_client )
+	{
+		MsgFindServer msg;
+        
+		msg.m_Timestamp = Tmr::GetTime();
+        
+#if (defined(__PLAT_NGPS__)||defined(__PLAT_NGC__))
+		m_match_client->SendMessageTo( Net::MSG_ID_FIND_SERVER, sizeof( MsgFindServer ), &msg, 
+									0xFFFFFFFF, vHOST_PORT, 0 );
+#else
+		XNetRandom( m_match_client->m_Nonce, sizeof( m_match_client->m_Nonce ));
+		memcpy( msg.m_Nonce, &m_match_client->m_Nonce, sizeof( m_match_client->m_Nonce ));
+		m_match_client->SendMessageTo( Net::MSG_ID_FIND_SERVER, sizeof( MsgFindServer ), &msg,
+									INADDR_BROADCAST, vHOST_PORT, 0 );
+#endif
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*			Manager::FirstPlayerInfo( Lst::Search< PlayerInfo > &sh, bool include_observers )
+{
+	PlayerInfo* player;
+
+	player = sh.FirstItem( m_players );
+	if( !include_observers && player && player->IsObserving())
+	{
+		player = NextPlayerInfo( sh, include_observers );
+	}
+	
+	return player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*			Manager::NextPlayerInfo( Lst::Search< PlayerInfo > &sh, bool include_observers )
+{
+	PlayerInfo* player;
+
+	player = sh.NextItem();
+	if( !include_observers && player && player->IsObserving())
+	{
+		player = NextPlayerInfo( sh, include_observers );
+	}
+	
+	return player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NewPlayerInfo*		Manager::FirstNewPlayerInfo( Lst::Search< NewPlayerInfo > &sh )
+{
+	NewPlayerInfo* player;
+
+	player = sh.FirstItem( m_new_players );
+	
+	return player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NewPlayerInfo*		Manager::NextNewPlayerInfo( Lst::Search< NewPlayerInfo > &sh )
+{
+	NewPlayerInfo* player;
+
+	player = sh.NextItem();
+	
+	return player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NewPlayerInfo*		Manager::GetNewPlayerInfoByObjectID( unsigned short obj_id )
+{
+	NewPlayerInfo* player;
+	Lst::Search< NewPlayerInfo > sh;
+
+	for( player = FirstNewPlayerInfo( sh ); player; player = NextNewPlayerInfo( sh ))
+	{
+		if( player->ObjID == obj_id )
+		{
+			return player;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::DestroyNewPlayer( NewPlayerInfo* new_player )
+{
+	Lst::Node< NewPlayerInfo > *node;
+	NewPlayerInfo* new_info;
+
+	Dbg_Assert( new_player );
+	Dbg_Printf( "Destroying Pending Player %d\n", new_player->ObjID );
+	for( node = m_new_players.GetNext(); node; node = node->GetNext())
+	{
+		new_info = node->GetData();
+		if( new_info == new_player )
+		{
+			if( m_server )
+			{
+				Dbg_Printf( "Removing %s\n", new_info->Name );
+				m_server->TerminateConnection( new_info->Conn );
+			}
+			else
+			{
+				Dbg_Printf( "Removing %s\n", new_info->Name );
+				if( new_info->Conn )
+				{
+					m_client[0]->TerminateConnection( new_info->Conn );
+				}
+			}
+			
+			delete node;
+			delete new_info;
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*			Manager::GetLocalPlayer( void )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	for( player = FirstPlayerInfo( sh, true ); player;
+			player = NextPlayerInfo( sh, true ))
+	{
+        if( player->IsLocalPlayer())
+		{
+			return player;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*			Manager::GetServerPlayer( void )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	for( player = FirstPlayerInfo( sh, true ); player;
+			player = NextPlayerInfo( sh, true ))
+	{
+        if( player->IsServerPlayer())
+		{
+			return player;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*			Manager::GetKingOfTheHill( void )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	for( player = FirstPlayerInfo( sh, true ); player;
+			player = NextPlayerInfo( sh, true ))
+	{
+        if( player->IsKing())
+		{
+			return player;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32				Manager::GetNetworkLevelId( void )
+{
+	return m_level_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::DisconnectFromServer( void )
+{
+	Dbg_Message( "Disconnecting from server\n" );
+    
+	CleanupPlayers();
+	
+	ClientShutdown();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::CleanupPlayers( void )
+{
+	free_all_players();
+	free_all_pending_players();	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::NumPartiallyLoadedPlayers( void )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	int num;
+
+	num = 0;
+	for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+	{
+		if( player->IsFullyIn() == false )
+		{
+			num++;
+		}
+	}
+
+	return num;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::DropPartiallyLoadedPlayers( void )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	PlayerInfo* player, *next;
+	Lst::Search< PlayerInfo > sh;
+
+	Dbg_Printf( "****** DROPPING PARTIALLY-LOADED SKATERS\n" );
+	for( player = FirstPlayerInfo( sh ); player; player = next )
+	{
+		next = NextPlayerInfo( sh );
+		if( player->IsFullyIn() == false )
+		{
+			Obj::CSkater* quitting_skater;
+						
+			quitting_skater = player->m_Skater;
+						
+			Dbg_Printf( "****** DROPPING PLAYER %d\n", quitting_skater->GetID());
+			DropPlayer( player, vREASON_LEFT_OUT );
+            skate_mod->remove_skater( quitting_skater );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::DropPlayer( PlayerInfo* info, DropReason reason )
+{
+	
+	char player_name[vMAX_PLAYER_NAME_LEN + 1];
+
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	Dbg_Assert( info );
+	Dbg_Message( "Removing player %s from the game\n", info->m_Name );
+
+    strcpy( player_name, info->m_Name );
+	
+	if( !info->IsObserving())
+	{
+		// if we're playing a game of graffiti, then free up all of the trick objects accumulated by the dropped client
+		if ( skate_mod->GetGameMode()->IsTrue( "should_modulate_color" ) )
+		{
+			skate_mod->GetTrickObjectManager()->FreeTrickObjects( info->m_Skater->GetID() );
+		}
+	}
+
+	if( OnServer())
+	{
+		if( !info->IsObserving())
+		{
+			// Inform all others of the quitter's departure
+			for( player = FirstPlayerInfo( sh, true ); player;
+					player = NextPlayerInfo( sh, true ))
+			{
+				if( !player->IsLocalPlayer())
+				{
+					MsgPlayerQuit msg;
+					Net::MsgDesc msg_desc;
+					
+					msg.m_ObjId = info->m_Skater->GetID();
+					msg.m_Reason = (char) reason;
+			
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = sizeof( MsgPlayerQuit );
+					msg_desc.m_Id = MSG_ID_PLAYER_QUIT;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+					m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+				}
+			}
+
+#ifdef __PLAT_NGPS__
+			if( ( reason == vREASON_QUIT ) ||
+				( reason == vREASON_TIMEOUT ) ||
+				( reason == vREASON_BAD_CONNECTION ))
+			{
+				if( mpStatsMan->IsLoggedIn())
+				{
+					mpStatsMan->PlayerLeft( info->m_Skater->GetID());
+				}
+			}
+#endif
+		}
+
+		if(	( reason == vREASON_BANNED ) ||
+			( reason == vREASON_KICKED ))
+		{
+			m_server->SendMessageTo( MSG_ID_KICKED, 0, NULL,
+									   info->m_Conn->GetIP(), info->m_Conn->GetPort(), 0 );
+			if( reason == vREASON_BANNED )
+			{
+				m_server->BanConnection( info->m_Conn );
+			}
+#ifdef __PLAT_NGPS__
+			//m_server->WaitForAsyncCallsToFinish();
+#endif // __PLAT_NGPS__
+
+		}
+		else if( reason == vREASON_LEFT_OUT )
+		{
+			m_server->SendMessageTo( MSG_ID_LEFT_OUT, 0, NULL,
+									   info->m_Conn->GetIP(), info->m_Conn->GetPort(), 0 );
+#ifdef __PLAT_NGPS__
+			//m_server->WaitForAsyncCallsToFinish();
+#endif // __PLAT_NGPS__
+		}
+		if( reason != vREASON_OBSERVING )
+		{
+			if( info->m_Conn )
+			{
+				info->m_Conn->Invalidate();
+				//Dbg_Printf( "DestroyingAllMessageData 1\n" );
+				info->m_Conn->DestroyAllMessageData();
+			}
+		}
+
+		uint32 reason_checksum;
+		
+		switch( reason )
+		{
+			case vREASON_QUIT:
+			{
+				reason_checksum = Script::GenerateCRC("net_message_player_quit");
+				break;
+			}
+			case vREASON_TIMEOUT:
+			{
+				reason_checksum = Script::GenerateCRC("net_message_player_timed_out");
+				break;
+			}
+			case vREASON_OBSERVING:
+			{
+				reason_checksum = Script::GenerateCRC("net_message_player_now_observing");
+				break;
+			}
+			case vREASON_BANNED:
+			{
+				reason_checksum = Script::GenerateCRC("net_message_player_banned");
+				break;
+			}
+			case vREASON_KICKED:
+			{
+				reason_checksum = Script::GenerateCRC("net_message_player_kicked");
+				break;
+			}
+			case vREASON_LEFT_OUT:
+			{
+				reason_checksum = Script::GenerateCRC("net_message_player_left_out");
+				break;
+			}
+			case vREASON_BAD_CONNECTION:
+			{
+				reason_checksum = Script::GenerateCRC("net_message_player_dropped");
+				break;
+			}
+			default:
+				reason_checksum = Script::GenerateCRC("net_message_player_timed_out");
+				break;
+		}
+
+		// If the info is about the server player, no need to print a panel message.
+		// Currently this would only happen if the server decided to sit out
+		if( info->IsLocalPlayer() == false )
+		{	   
+			CreateNetPanelMessage( true, reason_checksum, player_name );
+		}
+		if( reason == vREASON_OBSERVING )
+		{
+			if( info->IsKing())
+			{
+				if( m_crown )
+				{
+					m_crown->RemoveFromKing();
+				}
+				info->MarkAsKing( false );
+			}
+			if( info->HasCTFFlag())
+			{
+				char team_str[64];
+				int team;
+				Script::CStruct* pParams;
+				PlayerInfo* local_player;
+				
+				team = info->HasWhichFlag();
+				sprintf( team_str, "team_%d_name", team + 1 );
+				pParams = new Script::CStruct;
+		
+				pParams->AddInteger( "team", team );
+				pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+				Script::RunScript( "flag_returned", pParams );
+		
+				delete pParams;
+		
+				local_player = GetLocalPlayer();
+				if( local_player && !local_player->IsObserving())
+				{
+					if( local_player->m_Team == team )
+					{
+						Script::RunScript( "hide_ctf_arrow" );		
+					}
+				}
+				info->ClearCTFState();
+			}
+		}
+		else
+		{
+			bool is_server_player;
+			
+			is_server_player = info->IsServerPlayer();
+			
+			Dbg_Printf( "Destroying Player %s\n", info->m_Name );
+			DestroyPlayer( info );
+			if( is_server_player )
+			{
+				ChooseNewServerPlayer();
+				if( GetServerPlayer() == NULL )
+				{
+					if( ( skate_mod->GetGameMode()->GetNameChecksum() != Script::GenerateCRC( "netlobby" )))
+					{
+						Script::RunScript( "network_end_game_selected" );
+					}
+				}
+			}
+		}
+
+		// Now see if the leaving of this player satisfies ending conditions
+		bool all_done = true;
+		for( player = FirstPlayerInfo( sh ); player;
+				player = NextPlayerInfo( sh ))
+		{
+			if( !player->m_flags.TestMask( PlayerInfo::mRUN_ENDED))
+			{
+				all_done = false;
+				break;
+			}
+		}
+	
+		if( all_done )
+		{
+			Net::MsgDesc msg_desc;
+
+			msg_desc.m_Id = MSG_ID_GAME_OVER;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			for( player = FirstPlayerInfo( sh, true ); player;
+					player = NextPlayerInfo( sh, true ))
+			{
+				m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+	else
+	{
+		DestroyPlayer( info );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Prefs::Preferences* Manager::GetNetworkPreferences( void )
+{
+	return &m_network_preferences;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Prefs::Preferences* Manager::GetTauntPreferences( void )
+{
+	return &m_taunt_preferences;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char*			Manager::GetNameFromArrayEntry( char* array_name, uint32 checksum )
+{
+	Script::CArray* pArray;
+	const char* string = NULL;
+	int i;
+
+	pArray = Script::GetArray( array_name );
+	Dbg_Assert( pArray );
+
+	for( i = 0; i < (int)pArray->GetSize(); i++ )
+	{   
+		uint32 value;
+		Script::CStruct* pStructure;
+		
+		 
+		pStructure = pArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+		
+		pStructure->GetChecksum( "checksum", &value, true );
+		if( value == checksum )
+		{
+			pStructure->GetText( "name", &string, true );
+		}
+	}
+
+	return string;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					Manager::GetIntFromArrayEntry( char* array_name, uint32 checksum, uint32 field_checksum )
+{
+	Script::CArray* pArray;
+	int int_val;
+	int i;
+
+	pArray = Script::GetArray( array_name );
+	Dbg_Assert( pArray );
+
+	int_val = 0;
+	for( i = 0; i < (int)pArray->GetSize(); i++ )
+	{   
+		uint32 value;
+		Script::CStruct* pStructure;
+		
+		 
+		pStructure = pArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+		
+		pStructure->GetChecksum( "checksum", &value, true );
+		if( value == checksum )
+		{
+			pStructure->GetInteger( field_checksum, &int_val, true );
+		}
+	}
+
+	return int_val;
+}
+
+/******************************************************************/
+/* Make use of the network preferences we currently have stored	  */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::UsePreferences( void )
+{
+	Net::Manager * net_man = Net::Manager::Instance();
+	Prefs::Preferences* pPreferences;
+	Script::CStruct* pStructure;
+	Script::CArray* pArray;
+	const char* ip, *phone, *login, *password, *host, *domain;
+	uint32 dev_type, ip_assignment, checksum;
+	bool use_dhcp, use_authentication, use_pcmcia_card;
+	int i;
+
+	// Initialize the master server list
+	for( i = 0; i < vMAX_MASTER_SERVERS; i++ )
+	{
+		m_master_servers[i][0] = '\0';
+	}
+
+	pPreferences = GetNetworkPreferences();
+
+	pArray = Script::GetArray("default_master_servers");
+	Dbg_Assert( pArray );
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("use_default_master_servers"));
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	if( checksum == Script::GenerateCRC( "boolean_true" ))
+	{
+		for( i = 0; i < (int)pArray->GetSize(); i++ )
+		{   
+			Script::CStruct* pStructure = pArray->GetStructure( i );
+			Dbg_Assert( pStructure );
+            
+			pStructure->GetText( "name", &ip, true );
+			strcpy( m_master_servers[ i ], ip );
+		}
+	}
+	else
+	{
+		int num_servers;
+
+		num_servers = 0;
+
+		// First, add the manually-entered master servers to the list, if they're not
+		// 0.0.0.0
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("master_server1") );
+		pStructure->GetText( "ui_string", &ip, true );
+		if( strcmp( ip, "0.0.0.0" ))
+		{
+			strcpy( m_master_servers[ num_servers ], ip );
+			num_servers++;
+		}
+				
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("master_server2") );
+		pStructure->GetText( "ui_string", &ip, true );
+		if( strcmp( ip, "0.0.0.0" ))
+		{
+			strcpy( m_master_servers[ num_servers ], ip );
+			num_servers++;
+		}
+
+		// Now tack on our default servers, just in case
+		for( i = 0; i < (int)pArray->GetSize(); i++ )
+		{   
+			Script::CStruct* pStructure = pArray->GetStructure( i );
+			Dbg_Assert( pStructure );
+            
+			pStructure->GetText( "name", &ip, true );
+			strcpy( m_master_servers[ num_servers + i ], ip );
+		}
+	}
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("broadband_type") );
+	pStructure->GetChecksum( "Checksum", &ip_assignment, true );
+	use_dhcp = false;
+	if( ip_assignment == Script::GenerateCRC( "ip_dhcp" ))
+	{
+		Dbg_Printf( "************* Supposed to use DHCP here !! ***************\n" );
+		use_dhcp = true;
+	}
+
+	// Override IP settings with the viewer_ip script variable, used to launch a server for real-time communication
+#ifdef __PLAT_XBOX__
+	const char* viewer_ip = Script::GetString( "xbox_viewer_ip" );
+#endif
+#ifdef __PLAT_NGPS__
+	const char* viewer_ip = Script::GetString( "viewer_ip" );
+#endif
+#ifdef __PLAT_NGC__
+	const char* viewer_ip = Script::GetString( "ngc_viewer_ip" );
+#endif
+	const char* viewer_gateway = Script::GetString( "viewer_gateway" );
+	if( viewer_ip && !*viewer_ip )
+	{
+		viewer_ip = NULL;
+	}
+	if( viewer_ip )
+	{
+		Dbg_Printf( "************* Got a viewer IP !! ***************\n" );
+		Prefs::Preferences* prefs;
+        Script::CScriptStructure* pTempStructure;
+
+		use_dhcp = false;
+
+        pTempStructure = new Script::CScriptStructure;
+		pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, 
+								  viewer_ip );
+        prefs = GetNetworkPreferences();
+		prefs->SetPreference( Script::GenerateCRC("ip_address"), pTempStructure );
+		delete pTempStructure;
+
+		if( viewer_gateway && *viewer_gateway )
+		{
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, 
+									  viewer_gateway );
+			prefs = GetNetworkPreferences();
+			prefs->SetPreference( Script::GenerateCRC("gateway"), pTempStructure );
+			delete pTempStructure;
+		}
+	}
+		
+	net_man->SetDHCP( use_dhcp );
+
+    if( use_dhcp )
+	{
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("host_name") );
+		pStructure->GetText( "ui_string", &host, true );
+		net_man->SetHostName((char*) host );
+
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("domain_name") );
+		pStructure->GetText( "ui_string", &domain, true );
+		net_man->SetDomainName((char*) domain );
+	}
+	else
+	{
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("ip_address") );
+		pStructure->GetText( "ui_string", &ip, true );
+		Dbg_Printf( "****************** Setting Local IP to %s ********** \n", ip );
+		net_man->SetLocalIP((char*) ip );
+        
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("gateway") );
+		pStructure->GetText( "ui_string", &ip, true );
+		Dbg_Printf( "****************** Setting Gateway to %s ********** \n", ip );
+		net_man->SetGateway((char*) ip );
+
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("subnet_mask") );
+		pStructure->GetText( "ui_string", &ip, true );
+		Dbg_Printf( "****************** Setting Subnet to %s ********** \n", ip );
+		net_man->SetSubnetMask((char*) ip );
+	}
+
+#ifdef USE_DNS
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("auto_dns") );
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	if( checksum == Script::GenerateCRC( "boolean_true" ) && ( viewer_ip == NULL ))
+	{
+		Dbg_Printf( "****************** Setting DNS to 0.0.0.0 ******** \n" );
+		net_man->SetDNSServer( 0, "" );
+		net_man->SetDNSServer( 1, "" );
+		net_man->SetDNSServer( 2, "" );
+	}
+	else
+	{
+		if( viewer_ip )
+		{
+			net_man->SetDNSServer( 0, "205.147.0.100" );
+			net_man->SetDNSServer( 1, "205.147.0.102" );
+		}
+		else
+		{
+			pStructure = pPreferences->GetPreference( Script::GenerateCRC("dns_server") );
+			pStructure->GetText( "ui_string", &ip, true );
+			Dbg_Printf( "****************** Setting DNS 1 to %s ******** \n", ip );
+			net_man->SetDNSServer( 0, (char*) ip );
+		
+			pStructure = pPreferences->GetPreference( Script::GenerateCRC("dns_server2") );
+			pStructure->GetText( "ui_string", &ip, true );
+			Dbg_Printf( "****************** Setting DNS 2 to %s ******** \n", ip );
+			net_man->SetDNSServer( 1, (char*) ip );
+		}
+	}
+#else
+#ifdef USE_FREE_DNS
+	net_man->SetDNSServer( 0, "204.80.125.130" );	// Free DNS server in LA
+	net_man->SetDNSServer( 1, "204.57.55.100" );	// Free DNS server in Boston
+	net_man->SetDNSServer( 2, "" );
+#else
+	net_man->SetDNSServer( 0, "" );	// Free DNS server in LA
+	net_man->SetDNSServer( 1, "" );	// Free DNS server in Boston
+	net_man->SetDNSServer( 2, "" );
+#endif // USE_FREE_DNS
+#endif // USE_DNS
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("dialup_number") );
+	pStructure->GetText( "ui_string", &phone, true );
+	net_man->SetISPPhoneNumber((char*) phone );
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("dialup_username") );
+	pStructure->GetText( "ui_string", &login, true );
+	net_man->SetISPLogin((char*) login );
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("dialup_password") );
+	pStructure->GetText( "ui_string", &password, true );
+	net_man->SetISPPassword((char*) password );
+	
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("device_type") );
+	pStructure->GetChecksum( "Checksum", &dev_type, true );
+	const char* use_pcmcia = Script::GetString( "use_pcmcia" );
+	use_pcmcia_card = use_pcmcia[0] != '\0';
+	
+	if(( dev_type == Script::GenerateCRC( "device_broadband_pc" )) || use_pcmcia_card ) 
+	{
+		if( viewer_ip )
+		{
+			Script::RunScript( "spoof_pcmcia_adaptor_setup" );
+		}
+		net_man->SetDeviceType( Net::vDEV_TYPE_PC_ETHERNET );
+		net_man->SetConnectionType( Net::vCONN_TYPE_ETHERNET );
+	}
+	else if(( dev_type == Script::GenerateCRC( "device_broadband_usb" )) || ( viewer_ip ))
+	{
+		if( viewer_ip )
+		{
+			Script::RunScript( "spoof_usb_adaptor_setup" );
+		}
+		net_man->SetDeviceType( Net::vDEV_TYPE_USB_ETHERNET );
+		net_man->SetConnectionType( Net::vCONN_TYPE_ETHERNET );
+	}
+	else if( dev_type == Script::GenerateCRC( "device_broadband_usb_pppoe" ))
+	{
+		net_man->SetDeviceType( Net::vDEV_TYPE_USB_ETHERNET );
+		net_man->SetConnectionType( Net::vCONN_TYPE_PPPOE );
+	}
+	else if( dev_type == Script::GenerateCRC( "device_usb_modem" ))
+	{
+		net_man->SetDeviceType( Net::vDEV_TYPE_USB_MODEM );
+		net_man->SetConnectionType( Net::vCONN_TYPE_MODEM );
+		if( GetMaxPlayers() > 3 )
+		{
+			SetMaxPlayers( 3 );
+		}
+		SetMaxObservers( 0 );
+	}
+	else if( dev_type == Script::GenerateCRC( "device_sony_modem" ))
+	{
+		net_man->SetDeviceType( Net::vDEV_TYPE_SONY_MODEM );
+		net_man->SetConnectionType( Net::vCONN_TYPE_MODEM );
+		if( GetMaxPlayers() > 3 )
+		{
+			SetMaxPlayers( 3 );
+		}
+		SetMaxObservers( 0 );
+	}
+	else if( dev_type == Script::GenerateCRC( "device_broadband_pc_pppoe" ))
+	{
+		net_man->SetDeviceType( Net::vDEV_TYPE_PC_ETHERNET );
+		net_man->SetConnectionType( Net::vCONN_TYPE_PPPOE );
+	}
+	else
+	{   
+		net_man->SetConnectionType( Net::vCONN_TYPE_NONE );
+		net_man->SetDeviceType( Net::vDEV_TYPE_NONE );
+	}
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("dialup_authentication") );
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	use_authentication = false;
+	if( checksum == Script::GenerateCRC( "boolean_true" ))
+	{
+		use_authentication = true;
+	}
+	net_man->SetDialupAuthentication( use_authentication );
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::RandomizeSkaterStartingPoints( void )
+{
+	int i, j, offset, max_skaters;
+
+	// choose a random offset for these starting points
+	offset = Mth::Rnd( 256 );
+
+	if( InNetGame())
+	{
+		max_skaters = Mdl::Skate::vMAX_SKATERS;
+	}
+	else
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		max_skaters = skate_mod->GetGameMode()->GetMaximumNumberOfPlayers();
+		
+		// PATCH  (Mick)
+		// If playing a splitscreen game in the park editor
+		// then set the Maximum number of players to 2
+		// so we only use the 1P and 2P start points
+		// oterwise, we will use all the auto-horse points
+		// which end up in stupid positions
+		if (Ed::CParkEditor::Instance()->UsingCustomPark() && !skate_mod->GetGameMode()->IsTrue("is_horse")) 		// is it a custom park and not horse mode?
+		{
+			max_skaters = 2;
+			offset = 0;	   // reset the offset, so they always get used the same way
+		}
+		
+		
+	}
+
+	for( i = 0; i < max_skaters; i++ )
+	{
+		m_skater_starting_points[i] = -1;
+	}
+
+	// For each skater, choose a random starting point
+	for( i = 0; i < max_skaters; i++ )
+	{
+		bool unique;
+        
+		// Choose a random starting point until it's unique
+		do
+		{
+			m_skater_starting_points[i] = Mth::Rnd( max_skaters );
+			unique = true;
+			for( j = 0; j < max_skaters; j++ )
+			{
+				// skip myself
+				if( i == j )
+				{
+					continue;
+				}
+
+				if( m_skater_starting_points[i] == m_skater_starting_points[j] )
+				{
+					unique = false;
+					break;
+				}
+			}
+		} while( !unique );
+	}
+
+	// Offset these random sequential numbers by a random number so that we pick
+	// four sequential numbers within a possibly larger range of starting points
+	for( i = 0; i < max_skaters; i++ )
+	{
+		m_skater_starting_points[i] += offset;
+	}
+
+	m_crown_spawn_point = Mth::Rnd( vNUM_CROWN_SPAWN_POINTS );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetSkaterStartingPoints( int* start_points )
+{
+	int i;
+
+	for( i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		m_skater_starting_points[i] = start_points[i];
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetSkaterStartingPoint( int skater_id )
+{
+	return m_skater_starting_points[skater_id];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::LoadPendingPlayers( void )
+{
+	PlayerInfo* player, *next;
+	Lst::Search< PlayerInfo > sh;
+	int num_new_players;
+
+	Dbg_Assert( m_server );
+
+	num_new_players = 0;
+
+#ifdef __PLAT_XBOX__
+	// Since XBOX has a quicker timeout, we need to tell clients that this process (loading pending players) may take much longer
+	// than the timeout allows. Clients will allow the server N seconds to get back to them after receiving this message.
+	// NOTE: This will only be guaranteed to work in LAN mode, because if the packet is lost, the original timeout will stand
+	bool players_pending;
+
+	players_pending = false;
+	// Add all pending players to the game
+	for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+	{
+		if( player->IsPendingPlayer())
+		{
+			players_pending = true;
+			break;
+		}
+	}
+	if( players_pending )
+	{
+		Net::MsgDesc msg_desc;
+		for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+		{
+			if( !player->IsLocalPlayer())
+			{
+				char num_seconds;
+
+				num_seconds = 15;
+
+				msg_desc.m_Data = &num_seconds;
+				msg_desc.m_Length = sizeof( char );
+				msg_desc.m_Id = MSG_ID_WAIT_N_SECONDS;
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+				m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}		
+		m_server->SendData();
+	}
+	
+#endif	
+
+	// Add all pending players to the game
+	for( player = FirstPlayerInfo( sh, true ); player; player = next )
+	{
+		next = NextPlayerInfo( sh, true );
+		if( player->m_flags.TestMask( PlayerInfo::mPENDING_PLAYER ))
+		{
+			NewPlayerInfo new_player;
+
+			Dbg_Assert( player->IsObserving());
+
+			num_new_players++;
+
+			strcpy( new_player.Name, player->m_Name );
+			new_player.ObjID = GetNextPlayerObjectId();
+			new_player.Conn = player->m_Conn;
+			new_player.Flags = PlayerInfo::mJUMPING_IN;
+			new_player.Profile = player->m_Profile;
+			new_player.Rating = player->m_Rating;
+			new_player.VehicleControlType = player->m_VehicleControlType;
+			
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+			*new_player.mpSkaterProfile = *player->mp_SkaterProfile;
+			Mem::Manager::sHandle().PopContext();
+            
+			DestroyPlayer( player );
+
+			DeferredNewPlayer( &new_player, ( num_new_players + 1 ) * vDEFERRED_JOIN_FRAMES );
+
+		}
+	}
+
+	// Invalidate old positions. Player will be re-added to the world when we get a
+	// sufficient number of object updates
+	for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			continue;
+		}
+		
+		player->m_Skater->RemoveFromCurrentWorld();
+		player->m_Skater->Resync();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::StartNetworkLobby( void )
+{
+	int num_teams;	
+
+	// This version of "StartNetworkLobby"
+	// exits the game mode, and enters the lobby
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+    
+	if( OnServer())
+	{
+		m_level_id = GetLevelFromPreferences();
+		num_teams = GetNumTeams();
+	}
+	else
+	{
+		num_teams = skate_mod->GetGameMode()->NumTeams();
+	}
+
+	// set the game type for the server
+	m_gamemode_id = Script::GenerateCRC( "netlobby" );
+	skate_mod->GetGameMode()->LoadGameType( m_gamemode_id );
+	skate_mod->GetGameMode()->SetNumTeams( num_teams );
+
+	m_game_over = false;
+
+    // launches the game for this client
+	// (should really wait until the clients are ready)
+	
+	//Mdl::ScriptRetry( NULL, NULL );
+
+	ResetPlayers();
+
+	Dbg_Printf( "Starting network lobby\n" );
+	
+	// Clear the king of the hill
+	if(( player = GetKingOfTheHill()))
+	{
+		player->MarkAsKing( false );
+	}
+
+	for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+	{
+		player->ClearCTFState();
+	}
+
+	ClearTriggerEventList();
+
+	m_lobby_start_time = Tmr::GetTime();
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ShouldStopAtZero( void )
+{
+	uint32 should_stop;
+
+	if( ( m_gamemode_id == Script::GenerateCRC( "netgoalattack" )) ||
+		( m_gamemode_id == Script::GenerateCRC( "king" )) ||
+		( m_gamemode_id == Script::GenerateCRC( "netking" )) ||
+		( m_gamemode_id == Script::GenerateCRC( "netscorechallenge" )) ||
+		( m_gamemode_id == Script::GenerateCRC( "scorechallenge" )))
+	{
+		return true;
+	}
+
+	if( InNetGame() == false )
+	{
+		return false;
+	}
+
+	if( m_gamemode_id == Script::GenerateCRC( "netctf" ))
+	{
+		uint32 mode;
+
+		mode = GetNetworkPreferences()->GetPreferenceChecksum( Script::GenerateCRC("ctf_game_type"), Script::GenerateCRC("checksum") );
+		if( mode != CRCD(0x938ab24b,"timed_ctf"))
+		{
+			return true;
+		}
+	}
+
+	should_stop = GetNetworkPreferences()->GetPreferenceChecksum( Script::GenerateCRC("stop_at_zero"), Script::GenerateCRC("checksum") );
+	if( should_stop == CRCD(0xf81bc89b,"boolean_true"))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::StartNetworkGame( void )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	int time_limit;
+	Prefs::Preferences* pPreferences;
+	Net::MsgDesc msg_desc;
+
+	Dbg_MsgAssert( OnServer(),( "Server-only function called on client" ));
+
+	pPreferences = NULL;
+	if( InNetGame())
+	{
+		pPreferences = GetNetworkPreferences();
+	}
+	else 
+	{
+		pPreferences = skate_mod->GetSplitScreenPreferences();
+	}
+
+	
+
+	Net::Server* server;
+	server = GetServer();
+	Dbg_MsgAssert( server,( "Server has not been started\n" ));
+
+	// Sorry boys, you didn't quite make it in
+	free_all_pending_players();
+
+	// set the game type for the server, from the network preferences
+	m_gamemode_id = GetGameTypeFromPreferences();
+	//skate_mod->GetGameMode()->LoadGameType( m_gamemode_id );
+
+	// Mick: Moved this to after the setting of the game type, so we can check for horse mode in it
+	// (used to explicitly set 1P and 2P start points in 2P split screen (not horse) game)
+	RandomizeSkaterStartingPoints();
+
+
+	if( m_gamemode_id == Script::GenerateCRC( "horse" ))
+	{
+		time_limit = pPreferences->GetPreferenceValue( Script::GenerateCRC("horse_time_limit"), Script::GenerateCRC("time") );
+	}
+	else
+	{
+		time_limit = pPreferences->GetPreferenceValue( Script::GenerateCRC("time_limit"), Script::GenerateCRC("time") );
+	}
+
+	Dbg_Printf("Starting network game of type %s with time limit %d\n", Script::FindChecksumName(m_gamemode_id), time_limit);
+	m_level_id = GetLevelFromPreferences();
+	
+	MsgGameInfo game_info_msg;
+	game_info_msg.m_GameMode = m_gamemode_id;
+	game_info_msg.m_FireballLevel = GetFireballLevelFromPreferences();
+	Dbg_Printf( "***** FIREBALL LEVEL: %d\n", game_info_msg.m_FireballLevel );
+	if( ( m_gamemode_id == CRCD(0xec200eaa,"netgoalattack")) ||
+		( m_gamemode_id == CRCD(0x5d32129c,"king")) ||
+		( m_gamemode_id == CRCD(0x6ef8fda0,"netking")) ||
+		( m_gamemode_id == CRCD(0x1498240a,"netscorechallenge")) ||
+		( m_gamemode_id == CRCD(0xf135ecb6,"scorechallenge")) ||
+		( m_gamemode_id == CRCD(0x3d6d444f,"firefight")) ||
+		( m_gamemode_id == CRCD(0xbff33600,"netfirefight")))
+	{
+		game_info_msg.m_TimeLimit = 0;
+	}
+	else
+	{
+
+		if( m_gamemode_id == Script::GenerateCRC( "netctf" ))
+		{
+			uint32 mode;
+
+            mode = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("ctf_game_type"), Script::GenerateCRC("checksum") );
+			if( mode == CRCD(0x938ab24b,"timed_ctf"))
+			{
+				game_info_msg.m_TimeLimit = time_limit;
+			}
+			else
+			{
+				game_info_msg.m_TimeLimit = 0;
+			}
+		}
+		else
+		{
+			game_info_msg.m_TimeLimit = time_limit;
+		}
+	}
+	
+	game_info_msg.m_StopAtZero = (char) ShouldStopAtZero();
+	game_info_msg.m_TargetScore = pPreferences->GetPreferenceValue( Script::GenerateCRC("target_score"), Script::GenerateCRC("score") );
+	Dbg_Printf( "Target score %d\n", (int)game_info_msg.m_TargetScore );
+	game_info_msg.m_TeamMode = GetNumTeams();
+
+	// Here, make sure we get a game id that does not match our last game id
+	do
+	{
+		game_info_msg.m_GameId = 1 + (unsigned char) Mth::Rnd( 255 );
+	} while( game_info_msg.m_GameId == m_last_end_of_run_game_id );
+
+	game_info_msg.m_CrownSpawnPoint = m_crown_spawn_point;
+	memcpy( game_info_msg.m_StartPoints, m_skater_starting_points, Mdl::Skate::vMAX_SKATERS * sizeof( int ));
+
+	msg_desc.m_Data = &game_info_msg;
+	msg_desc.m_Length = sizeof( MsgGameInfo );
+	msg_desc.m_Id = MSG_ID_GAME_INFO;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+	{
+		player->m_BestCombo = 0;
+		player->ClearCTFState();
+		player->m_flags.ClearMask( PlayerInfo::mRESTARTING );
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+
+		// In splitscreen games, just send it to the first client
+		if( InNetGame() == false )
+		{
+			break;
+		}
+	}
+
+	// Clear the king of the hill
+	if(( player = GetKingOfTheHill()))
+	{
+		player->MarkAsKing( false );
+	}
+	SetCurrentLeader( NULL );
+	SetCurrentLeadingTeam( vNO_TEAM );
+	ClearTriggerEventList();
+	ResetPlayers();
+	g_CheatsEnabled = false;
+
+#ifdef __PLAT_NGPS__
+	if( mpStatsMan->IsLoggedIn())
+	{
+		mpStatsMan->StartNewGame();
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::ResetPlayers( void )
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	for( player = FirstPlayerInfo( sh, true ); player;
+			player = NextPlayerInfo( sh, true ))
+	{
+		Mdl::Score* score;
+		player->m_flags.ClearMask( PlayerInfo::mRUN_ENDED );
+		if( player->m_Skater )
+		{
+			Obj::CSkaterScoreComponent* p_skater_score_component = GetSkaterScoreComponentFromObject(player->m_Skater);
+			Dbg_Assert(p_skater_score_component);
+			score = p_skater_score_component->GetScore();
+			
+			if( score )
+			{
+				score->Reset();
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::SetLevel( uint32 level_id )
+{
+	m_level_id = level_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetNetworkGameId( unsigned char id )
+{
+	m_game_id = id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+unsigned char	Manager::GetNetworkGameId( void )
+{
+	return m_game_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 Manager::GetGameTypeFromPreferences( void )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	Prefs::Preferences* pPreferences;
+	 
+	pPreferences = NULL;
+	if( InNetGame())
+	{
+		pPreferences = GetNetworkPreferences();
+	}
+	else 
+	{
+		pPreferences = skate_mod->GetSplitScreenPreferences();
+	}
+
+	Script::CStruct* pStructure = pPreferences->GetPreference( Script::GenerateCRC("game_type") );
+
+	Dbg_Assert( pStructure );
+
+	uint32 gametype_id;
+	pStructure->GetChecksum( "checksum", &gametype_id, true );
+
+	return gametype_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 Manager::GetLevelFromPreferences( void )
+{
+	uint32 level_id;
+
+	Prefs::Preferences* pPreferences;
+
+	pPreferences = GetNetworkPreferences();
+	Script::CStruct* pStructure = pPreferences->GetPreference( Script::GenerateCRC("level") );
+
+	Dbg_Assert( pStructure );
+    pStructure->GetChecksum( "checksum", &level_id, true );
+	
+	return level_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetFireballLevelFromPreferences( void )
+{
+	uint32 checksum;
+	Script::CStruct* pStructure;
+		
+	pStructure = m_network_preferences.GetPreference( CRCD(0xce238f5d,"fireball_difficulty") );
+	pStructure->GetChecksum( "Checksum", &checksum, true );
+	if( checksum == Script::GenerateCRC( "num_1" ))
+	{
+		return 1;
+	}
+	else if( checksum == Script::GenerateCRC( "num_2" ))
+	{
+		return 2;
+	}
+	else if( checksum == Script::GenerateCRC( "num_3" ))
+	{
+		return 3;
+	}                                              
+	else if( checksum == Script::GenerateCRC( "num_4" ))
+	{
+		return 4;
+	}                                              
+	else if( checksum == Script::GenerateCRC( "num_5" ))
+	{
+		return 5;
+	}                                              
+
+	return 3;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ToggleMetrics( void )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+
+	m_metrics_on ^= 1;
+
+	if( m_metrics_on )
+	{
+		mlp_manager->AddLogicTask( *m_render_metrics_task );
+	}
+	else
+	{
+		m_render_metrics_task->Remove();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ToggleScores( void )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Game::CGameMode* pGameMode;
+	bool show_score;
+
+	pGameMode = skate_mod->GetGameMode();
+	show_score = pGameMode->ShouldAccumulateScore();
+
+	m_scores_on ^= 1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::TogglePlayerNames( void )
+{
+	m_draw_player_names ^= 1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ShouldDrawPlayerNames( void )
+{
+	Prefs::Preferences* pPreferences;
+	Script::CStruct* pStructure;
+	uint32 checksum = 0;
+		
+	pPreferences = GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( CRCD(0xf7dd263,"show_names") );
+	pStructure->GetChecksum( CRCD(0x21902065,"checksum"), &checksum, false );
+	return( checksum == CRCD(0xf81bc89b,"boolean_true") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::RemoveModemStateTask( void )
+{
+	m_modem_state_task->Remove();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ConnectToInternet( uint32 success_script, uint32 failure_script )
+{
+#ifdef __PLAT_NGPS__
+	Net::Manager * net_man = Net::Manager::Instance();
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	bool was_online, is_online;
+
+	m_connection_success_script = success_script;
+	m_connection_failure_script = failure_script;
+    
+	Dbg_Printf( "Connecting to the internet\n" );
+	was_online = net_man->IsOnline();
+	if( was_online )
+	{
+		is_online = true;
+	}
+	else
+	{
+		m_last_modem_state = -1;
+		is_online = net_man->ConnectToInternet();
+	}
+	
+	if( is_online )
+	{   
+		if( m_connection_success_script > 0 )
+		{
+			Script::RunScript( m_connection_success_script );
+		}
+	}
+	else if(( net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM ) ||
+			( net_man->GetConnectionType() == Net::vCONN_TYPE_PPPOE ))
+	{   
+		mlp_man->AddLogicTask( *m_modem_state_task );
+	}
+	
+	return is_online;
+#else
+#ifdef __PLAT_XBOX__
+	mpAuthMan->UserLogon();
+#endif		// __PLAT_XBOX__
+	
+	return false;
+#endif		// __PLAT_NGPS__	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::CancelConnectToInternet( void )
+{
+	
+	Net::Manager * net_man = Net::Manager::Instance();
+	Manager * gamenet_man = Manager::Instance();
+	
+	switch( net_man->GetModemState())
+	{
+		case Net::vMODEM_STATE_DIALING:
+		case Net::vMODEM_STATE_CONNECTED:
+			gamenet_man->RemoveModemStateTask();
+			gamenet_man->DisconnectFromInternet();
+			break;
+		case Net::vMODEM_STATE_ERROR:
+#ifdef __PLAT_NGPS__
+			if( net_man->GetModemError() == SNTC_ERR_NOMODEM )
+			{
+				Script::RunScript( gamenet_man->m_connection_failure_script );
+			}
+			else
+			{
+				gamenet_man->RemoveModemStateTask();
+				gamenet_man->DisconnectFromInternet();
+				break;
+			}
+#endif		// __PLAT_NGPS__
+			gamenet_man->RemoveModemStateTask();
+			break;;
+		case Net::vMODEM_STATE_DISCONNECTED:
+			Dbg_Printf( "Removing modem state task\n" );
+			gamenet_man->RemoveModemStateTask();
+			Script::RunScript( gamenet_man->m_connection_failure_script );
+			break;
+		case Net::vMODEM_STATE_LOGGED_IN:
+		{
+			net_man->SetModemState( Net::vMODEM_STATE_READY_TO_TRANSMIT );
+			break;
+		}
+		default:
+			break;
+
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::DisconnectFromInternet( uint32 callback_script )
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	Net::Manager * net_man = Net::Manager::Instance();
+
+	Dbg_Assert(	( net_man->GetConnectionType() == Net::vCONN_TYPE_MODEM ) ||
+				( net_man->GetConnectionType() == Net::vCONN_TYPE_PPPOE ));
+	   
+	m_last_modem_state = -1;
+	if( callback_script != 0 )
+	{
+        m_connection_failure_script = callback_script;
+	}
+
+	mlp_man->AddLogicTask( *m_modem_state_task );
+	SetServerListState( vSERVER_LIST_STATE_SHUTDOWN );
+	net_man->DisconnectFromInternet();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::CreateNetPanelMessage( bool broadcast, uint32 message_checksum, char* string_parm_1,
+										char* string_parm_2, Obj::CMovingObject* object, char* properties, bool important,
+										int time )
+{
+	if( broadcast )
+	{
+		PlayerInfo* player;
+		Lst::Search< PlayerInfo > sh;
+		MsgPanelMessage msg;
+		Net::MsgDesc msg_desc;
+
+		msg.m_Parm1[0] = '\0';
+		msg.m_Parm2[0] = '\0';
+		msg.m_StringId = message_checksum;
+		msg.m_Time = time;
+		msg.m_PropsStructureCRC = (int) Script::GenerateCRC( properties );
+		if( string_parm_1 )
+		{
+			strcpy( msg.m_Parm1, string_parm_1 );
+		}
+		if( string_parm_2 )
+		{
+			strcpy( msg.m_Parm2, string_parm_2 );
+		}
+
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( MsgPanelMessage );
+		msg_desc.m_Id = MSG_ID_PANEL_MESSAGE;
+		if( important )
+		{
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		}
+
+		Dbg_Assert( m_server );
+        for( player = FirstPlayerInfo( sh, true ); player;
+				player = NextPlayerInfo( sh, true ))
+		{
+			m_server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+		}
+	}
+	else
+	{   
+		Script::CScriptStructure* pStructure = new Script::CScriptStructure;
+		const char* text;	
+		char temp_msg[256];
+		char final_msg[256];
+		char* format_start;
+
+		text = Script::GetLocalString( message_checksum );
+		strcpy( temp_msg, text );
+		if(( format_start = strstr( temp_msg, "%s0" )))
+		{
+			*format_start = '\0';
+
+			strcpy( final_msg, temp_msg );
+			Dbg_Assert( string_parm_1 );	// Need a parameter for this format string
+			strcat( final_msg, string_parm_1 );
+			format_start += 3;	// skip the format tag
+			strcat( final_msg, format_start );
+
+			pStructure->AddComponent( Script::GenerateCRC( "Text" ), ESYMBOLTYPE_STRING, final_msg );
+		}
+		else
+		{
+			pStructure->AddComponent( Script::GenerateCRC( "Text" ), ESYMBOLTYPE_STRING, text );
+		}
+		
+		pStructure->AddComponent( Script::GenerateCRC( "id" ), ESYMBOLTYPE_NAME, Script::GenerateCRC( "net_panel_msg" ));
+		pStructure->AddInteger( Script::GenerateCRC( "msg_time" ), time );
+		Script::RunScript( "create_net_panel_message", pStructure );
+		
+		delete pStructure;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ShouldSendScoreUpdates( void )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	Game::CGameMode* pGameMode = skate_mod->GetGameMode();
+	
+	// We need to send score updates in goal attack
+	if( ( pGameMode->ShouldAccumulateScore() == false ) &&
+		( pGameMode->ShouldTrackBestCombo() == false ) &&
+		( pGameMode->GetNameChecksum() != CRCD(0xbff33600,"netfirefight")) &&
+		( pGameMode->GetNameChecksum() != CRCD(0xec200eaa,"netgoalattack")) &&
+		( pGameMode->GetNameChecksum() != CRCD(0x6c5ff266,"netctf")))
+	{
+		return false;
+	}
+	
+	if( pGameMode->EndConditionsMet() && ( pGameMode->GetNameChecksum() != CRCD(0xbff33600,"netfirefight")))
+	{
+		return false;
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::SetCurrentLeader( PlayerInfo* player )
+{
+	m_leader = player;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo*		Manager::GetCurrentLeader( void )
+{
+	return m_leader;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::SetCurrentLeadingTeam( int team )
+{
+	m_leading_team = team;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int				Manager::GetCurrentLeadingTeam( void )
+{
+	return m_leading_team;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetLastScoreUpdateTime( int time )
+{
+	m_last_score_update = time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Manager::GetLastScoreUpdateTime( void )
+{
+	return m_last_score_update;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::MarkReceivedFinalScores( bool received )
+{
+	m_received_final_scores = received;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::HaveReceivedFinalScores( void )
+{
+	return m_received_final_scores;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::AddSpawnedTriggerEvent( int node, int obj_id, uint32 script )
+{
+	TriggerEvent* queued_script;
+	Lst::Search< TriggerEvent > sh;
+	
+	// Check to make sure we don't queue duplicate scripts
+	for( queued_script = sh.FirstItem( m_trigger_events ); queued_script; 
+			queued_script = sh.NextItem())
+	{
+		if( queued_script->m_Script == script )
+		{
+			return;
+		}
+	}
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	TriggerEvent* event = new TriggerEvent;
+
+	event->m_Node = node;
+	event->m_ObjID = obj_id;
+	event->m_Script = script;
+
+	m_trigger_events.AddToTail( event );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ClearTriggerEventList( void )
+{
+	Lst::Search< TriggerEvent > sh;
+	TriggerEvent* event, *next;
+
+	for( event = sh.FirstItem( m_trigger_events ); event; event = next )
+	{
+		next = sh.NextItem();
+		delete event;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetProSetFlags( char flags )
+{
+	m_proset_flags = flags;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SendChatMessage( char* message )
+{
+	
+	MsgChat msg;
+	int size;
+	PlayerInfo* player;
+	Script::CStruct* p_params;
+	char final_msg[256];
+	Net::MsgDesc msg_desc;
+
+    Dbg_Assert( message );
+	Dbg_Assert( m_client[0] );
+
+	// No point in printing/sending an empty message
+	if( message[0] == '\0' )
+	{
+		return;
+	}
+
+	player = GetLocalPlayer();
+	Dbg_Assert( player );
+    
+	strcpy( msg.m_Name, player->m_Name );
+	strcpy( msg.m_ChatMsg, message );
+	if( player->IsObserving() )
+	{
+		msg.m_ObjId = Mdl::Skate::vMAX_SKATERS; 	// Signifies "observer"
+	}
+	else
+	{
+		msg.m_ObjId = player->m_Skater->GetID();
+	}
+
+	size = sizeof( char ) + ( vMAX_PLAYER_NAME_LEN + 1 ) + strlen( message ) + 1;
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = size;
+	msg_desc.m_Id = MSG_ID_CHAT;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	m_client[0]->EnqueueMessageToServer( &msg_desc );
+	
+	p_params = new Script::CStruct;	
+	sprintf( final_msg, "%s : %s", player->m_Name, message );
+	p_params->AddString( "text", final_msg );
+	Script::RunScript("create_console_message", p_params );
+	delete p_params;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::SetJoinState( JoinState state )
+{
+	m_join_state = state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+JoinState		Manager::GetJoinState( void )
+{
+	return m_join_state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::SetObserverCommand( ObserverCommand command )
+{
+	m_observer_command = command;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ObserverCommand		Manager::GetObserverCommand( void )
+{
+	return m_observer_command;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::LaunchQueuedScripts( void )
+{
+	TriggerEvent* queued_script, *next;
+	Lst::Search< TriggerEvent > sh;
+	Sfx::CSfxManager * pSfxManager = Sfx::CSfxManager::Instance();
+	Mdl::Skate * mod = Mdl::Skate::Instance();
+	Script::CScript* pScript;
+    
+    Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	
+	mod->SetLaunchingQueuedScripts();
+	// Remember the original volume, then turn sound off so scripts don't make noise
+	float orig_volume = pSfxManager->GetMainVolume();
+	pSfxManager->SetMainVolume( 0.0f );
+
+    Dbg_Printf( "********************* SPAWNING QUEUED SCRIPTS ******************\n" );
+	
+	Script::RunScript( "PreRunQueuedScripts" );
+	for( queued_script = sh.FirstItem( m_trigger_events ); queued_script; queued_script = next )
+	{
+		Script::EScriptReturnVal ret_val;
+
+		next = sh.NextItem();
+	
+		pScript = Script::SpawnScript( queued_script->m_Script, NULL, 0, NULL, 
+									   queued_script->m_Node ); // K: The 0,NULL bit means no callback script specified
+		#ifdef __NOPT_ASSERT__
+		pScript->SetCommentString("Spawned from Manager::LaunchQueuedScripts");
+		#endif
+		pScript->mpObject = mod->GetObjectManager()->GetObjectByID( queued_script->m_ObjID );; 
+
+		// Now run script to completion
+		do 
+		{
+			ret_val = pScript->Update();
+			// Script must not get blocked, otherwise it'll hang in this loop forever.
+			Dbg_MsgAssert( ret_val!=Script::ESCRIPTRETURNVAL_BLOCKED,("\n%s\nScript got blocked when being run by RunScript.",pScript->GetScriptInfo()));
+		
+		} while( ret_val != Script::ESCRIPTRETURNVAL_FINISHED );
+		Script::KillSpawnedScript(pScript);
+		delete queued_script;
+	}
+
+	Script::RunScript( "PostRunQueuedScripts" );
+
+	Dbg_Printf( "********************* FINISHED SPAWNING QUEUED SCRIPTS ******************\n" );
+	 
+	// Back to normal volume
+	pSfxManager->SetMainVolume( orig_volume );
+	mod->ClearLaunchingQueuedScripts();
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCrown*		Manager::GetCrown( void )
+{
+	return m_crown;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::CleanupObjects( void )
+{
+	// Nullify references to now-dead objects
+	m_crown = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::SetCrownSpawnPoint( char spawn_point )
+{
+	m_crown_spawn_point = spawn_point;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*			Manager::GetNetThreadStack( void )
+{
+	return m_net_thread_stack;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::SetNetThreadId( int net_thread_id )
+{
+	m_net_thread_id = net_thread_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int				Manager::GetNetThreadId( void )
+{
+	return m_net_thread_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::SendEndOfRunMessage( void )
+{
+	Net::Client* client;
+	Net::MsgDesc msg_desc;
+
+	Dbg_Assert( HaveSentEndOfRunMessage() == false );
+
+	client = m_client[0];
+	Dbg_Assert( client );
+
+	msg_desc.m_Data = &m_game_id;
+	msg_desc.m_Length = sizeof( char );
+	msg_desc.m_Id = MSG_ID_RUN_HAS_ENDED;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	client->EnqueueMessageToServer( &msg_desc );
+
+	m_last_end_of_run_game_id = m_game_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			Manager::HaveSentEndOfRunMessage( void )
+{
+	if( m_last_end_of_run_game_id == m_game_id )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			Manager::GameIsOver( void )
+{
+	return m_game_over;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::MarkGameOver( void )
+{
+	m_game_over = true;
+}
+                                          
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::ClearGameOver( void )
+{
+	m_game_over = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool			Manager::HasCheatingOccurred( void )
+{
+	return m_cheating_occurred;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::CheatingOccured( void )
+{
+	m_cheating_occurred = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::ClearCheating( void )
+{
+	m_cheating_occurred = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::RespondToReadyQuery( void )
+{
+	MsgReady msg;
+	Net::MsgDesc msg_desc;
+	int i;
+
+	msg.m_Time = m_latest_ready_query;
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgReady );
+	msg_desc.m_Id = MSG_ID_READY_RESPONSE;
+	msg_desc.m_Queue = Net::QUEUE_IMPORTANT;
+	for( i = 0; i < 2; i++ )
+	{
+		if( m_client[i] )
+		{
+			m_client[i]->EnqueueMessageToServer( &msg_desc );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void			Manager::ToggleProSet( char bit, uint32 param_id )
+{
+	MsgToggleProSet msg;
+	Net::MsgDesc msg_desc;
+	Net::Server* server;
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	m_proset_flags.Toggle( bit );
+	
+	msg.m_Bit = bit;
+	msg.m_ParamId = param_id;
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgToggleProSet );
+	msg_desc.m_Id = MSG_ID_TOGGLE_PROSET;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+
+	server = GetServer();
+	for( player = FirstPlayerInfo( sh, true ); player; player = NextPlayerInfo( sh, true ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			continue;
+		}
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ScriptStartCTFGame(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+
+	mlp_man->AddLogicTask( *gamenet_man->m_ctf_logic_task );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ScriptEndCTFGame(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	gamenet_man->m_ctf_logic_task->Remove();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptTookFlag(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player, *capture_player;
+	Net::Server* server;
+	Lst::Search< PlayerInfo > sh;
+	MsgFlagMsg msg;
+	Net::MsgDesc msg_desc;
+	int obj_id, team;
+
+	pParams->GetInteger( "player", &obj_id );
+	pParams->GetInteger( "flag_team", &team );
+	
+	capture_player = gamenet_man->GetPlayerByObjectID( obj_id );
+	if( capture_player == NULL )
+	{
+		return true;
+	}
+
+	msg.m_ObjId = (char) obj_id;
+	msg.m_Team = (char) team;
+		
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgFlagMsg );
+	msg_desc.m_Id = MSG_ID_TOOK_FLAG;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+
+	server = gamenet_man->GetServer();
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			capture_player->TookFlag( team );
+			continue;
+		}
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptCapturedFlag(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player, *capture_player;
+	Net::Server* server;
+	Lst::Search< PlayerInfo > sh;
+	MsgFlagMsg msg;
+	Net::MsgDesc msg_desc;
+	int obj_id, team;
+
+	pParams->GetInteger( "player", &obj_id );
+	capture_player = gamenet_man->GetPlayerByObjectID( obj_id );
+	if(( capture_player == NULL ) || ( capture_player->m_flags.TestMask( PlayerInfo::mRESTARTING )))
+	{
+		return true;
+	}
+
+	team = capture_player->HasWhichFlag();
+	msg.m_ObjId = (char) obj_id;
+	msg.m_Team = (char) team;
+		
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgFlagMsg );
+	msg_desc.m_Id = MSG_ID_CAPTURED_FLAG;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+
+	server = gamenet_man->GetServer();
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			Mdl::Score* score_obj;
+			
+			Obj::CSkaterScoreComponent* p_skater_score_component = GetSkaterScoreComponentFromObject(capture_player->m_Skater);
+			Dbg_Assert(p_skater_score_component);
+			score_obj = p_skater_score_component->GetScore();
+			
+			Dbg_Assert(score_obj);
+			score_obj->SetTotalScore( score_obj->GetTotalScore() + 1 );
+
+			capture_player->CapturedFlag( team );
+			continue;
+		}
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptRetrievedFlag(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player, *retrieve_player;
+	Net::Server* server;
+	Lst::Search< PlayerInfo > sh;
+	MsgFlagMsg msg;
+	Net::MsgDesc msg_desc;
+	int obj_id, team;
+
+	pParams->GetInteger( "player", &obj_id );
+	pParams->GetInteger( "flag_team", &team );
+	
+	retrieve_player = gamenet_man->GetPlayerByObjectID( obj_id );
+	if( retrieve_player == NULL )
+	{
+		return true;
+	}
+
+	msg.m_ObjId = (char) obj_id;
+	msg.m_Team = (char) team;
+		
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgFlagMsg );
+	msg_desc.m_Id = MSG_ID_CAPTURED_FLAG;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+
+	server = gamenet_man->GetServer();
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			retrieve_player->CapturedFlag( team );
+			continue;
+		}
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptHasFlag(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	int obj_id;
+
+	pParams->GetInteger( "player", &obj_id );
+	
+	player = gamenet_man->GetPlayerByObjectID( obj_id );
+	if( player == NULL )
+	{
+		return true;
+	}
+
+	return player->HasCTFFlag();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptDisplayFlagBaseWarning(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	static Tmr::Time s_last_show = 0;
+
+	if(( Tmr::GetTime() - s_last_show ) > Tmr::Seconds( 3 ))
+	{
+		Script::RunScript( CRCD(0x55af7c02,"display_flag_base_warning"), NULL );
+		s_last_show = Tmr::GetTime();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptTeamFlagTaken(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	int team;
+
+	pParams->GetInteger( "team", &team );
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		if( player->HasWhichFlag() == team )
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SpawnCrown | spawn the king of the hill crown
+bool	Manager::ScriptSpawnCrown(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	// Spawn the king of the hill crown
+	gamenet_man->m_crown = new Obj::CCrown;
+
+	int i, spawn_pt;
+		
+	Script::CArray *pNodeArray=Script::GetArray("NodeArray");
+
+	// if there is no node array, then there are no restart points
+	if (!pNodeArray)
+	{
+		return false;
+	}
+						 
+	i = 0;
+	spawn_pt = gamenet_man->m_crown_spawn_point;
+		
+	// collect a table of all crown nodes
+	int max_crown_node = 0;
+	Script::CStruct *pCrownNodeTab[vNUM_CROWN_SPAWN_POINTS];
+	int crown_nodes[vNUM_CROWN_SPAWN_POINTS];
+	while( i < (int)pNodeArray->GetSize() )
+	{
+		uint32	TypeChecksum;
+		Script::CStruct *pNode=pNodeArray->GetStructure(i);
+		
+		TypeChecksum = 0;
+		pNode->GetChecksum("Type",&TypeChecksum);
+		if( TypeChecksum == 0xaf86421b )	// checksum of "Crown"
+		{
+			Dbg_MsgAssert( max_crown_node < vNUM_CROWN_SPAWN_POINTS, ( "Too many crown spawn points" ));
+			crown_nodes[max_crown_node] = i;
+			pCrownNodeTab[max_crown_node++] = pNode;
+		}
+		i++;
+	}
+	Dbg_MsgAssert(max_crown_node, ("no crown nodes found"));
+		
+	// there may be fewer crown points than the one we want, so
+	// truncate the request number if so
+	spawn_pt = spawn_pt % max_crown_node;
+
+	// now set up crown point in world
+	Mth::Vector pos;
+	SkateScript::GetPosition( pCrownNodeTab[spawn_pt], &pos );
+    
+	gamenet_man->m_crown->InitCrown( skate_mod->GetObjectManager(), pCrownNodeTab[spawn_pt] );
+
+    PlayerInfo* king;
+	//gamenet_man->m_crown->AddToWorld( viewer_mod->GetCurrentWorld(), &pos );
+	
+	// We may have a king "queued" up (i.e. we joined a KOTH game in progress.
+	// If so, coronate the king straight away
+	king = gamenet_man->GetKingOfTheHill();
+	if( king )
+	{
+		gamenet_man->m_crown->PlaceOnKing( king->m_Skater );
+	}
+    
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | NetworkGamePending | 
+bool Manager::ScriptNetworkGamePending(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+    
+	return gamenet_man->m_game_pending;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartNetworkGame | 
+bool Manager::ScriptStartNetworkGame(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Manager * gamenet_man = Manager::Instance();
+    
+	if( gamenet_man->m_start_network_game_task->InList())
+	{
+		return true;
+	}
+	if( gamenet_man->m_change_level_task->InList())
+	{
+		return true;
+	}
+
+	gamenet_man->m_game_pending = true;
+	if( gamenet_man->InNetGame())
+	{ 
+		if( gamenet_man->GetGameTypeFromPreferences() == Script::GenerateCRC( "netgoalattack" ))
+		{
+			s_time_to_start_game = Tmr::GetTime() + vTIME_BEFORE_STARTING_GOALATTACK;
+			
+		}
+		else
+		{
+			s_time_to_start_game = Tmr::GetTime() + vTIME_BEFORE_STARTING_NETGAME;
+		}
+	}
+	else
+	{
+		s_time_to_start_game = Tmr::GetTime();
+	}
+			
+	mlp_man->AddLogicTask( *gamenet_man->m_start_network_game_task );
+	
+	if( gamenet_man->InNetGame())
+	{
+		if( gamenet_man->GetHostMode() == vHOST_MODE_AUTO_SERVE )
+		{
+			gamenet_man->CreateNetPanelMessage( true, Script::GenerateCRC("net_message_auto_starting_game"),
+											 gamenet_man->GetGameModeName(), NULL, NULL, "netstatusprops", true,
+												Tmr::Seconds( 3 ));    
+		}
+		else
+		{
+			gamenet_man->CreateNetPanelMessage( true, Script::GenerateCRC("net_message_starting_game"),
+										 gamenet_man->GetGameModeName(), NULL, NULL, "netstatusprops", true,
+											Tmr::Seconds( 3 ));
+		}
+
+		if( skate_mod->GetGameMode()->IsTeamGame())
+		{
+			Net::Server* server;
+			PlayerInfo* player;
+			Lst::Search< PlayerInfo > sh;
+			Net::MsgDesc msg_desc;
+		 
+			server = gamenet_man->GetServer();
+			Dbg_Assert( server );
+
+			msg_desc.m_Id = MSG_ID_KILL_TEAM_FLAGS;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+	
+	// Compile the list of selected goals to send to all clients at the start of goal attack games
+	if( gamenet_man->InNetGame() && ( gamenet_man->GetGameTypeFromPreferences() == Script::GenerateCRC( "netgoalattack" )))
+	{
+		int i, num_goals;
+		Game::CGoalManager* pGoalManager;
+		Game::CGoal* pGoal;
+		Net::MsgMax msg;
+		Net::MsgDesc msg_desc;
+		uint32 goal_id;
+		char* data;
+		Net::Server* server;
+		PlayerInfo* player;
+		Lst::Search< PlayerInfo > sh;
+		 
+		pGoalManager = Game::GetGoalManager();
+		server = gamenet_man->GetServer();
+		Dbg_Assert( server );
+
+		data = msg.m_Data;
+		*data++ = 1;	// This indicates that they should bring up the goal summary dialog
+		num_goals = pGoalManager->GetNumGoals();
+		Dbg_Printf( "*** Num Goals: %d\n" );
+		for( i = 0; i < num_goals; i++ )
+		{
+			pGoal = pGoalManager->GetGoalByIndex( i );
+			Dbg_Assert( pGoal );
+			if( pGoal->IsSelected())
+			{
+				pGoal->UnBeatGoal();
+				goal_id = pGoal->GetGoalId();
+				Dbg_Printf( "*** Goals %d selected, id 0x%x\n", i, goal_id );
+				memcpy( data, &goal_id, sizeof( uint32 ));
+				data += sizeof( uint32 );
+			}
+		}
+
+		// zero-terminate the list of goals
+		goal_id = 0;
+		memcpy( data, &goal_id, sizeof( uint32 ));
+		data += sizeof( uint32 );
+
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = (int) ( data - msg.m_Data );
+		msg_desc.m_Id = MSG_ID_SELECT_GOALS;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+		{
+			// No need to send to server
+			if( player->IsLocalPlayer())
+			{
+				continue;
+			}
+			server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+		}
+		
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptSendGameOverToObservers(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Manager * gamenet_man = Manager::Instance();
+
+	Dbg_Printf( "**** In ScriptSendGameOverToObservers\n" );
+	if( gamenet_man->OnServer())
+	{
+		Net::Server* server;
+		Net::MsgDesc msg_desc;
+
+		server = gamenet_man->GetServer();
+
+		msg_desc.m_Id = MSG_ID_GAME_OVER;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+		{
+			if( player->IsObserving())
+			{
+				Dbg_Printf( "**** Sending message to observer\n" );
+                server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ScriptEndNetworkGame(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Manager * gamenet_man = Manager::Instance();
+
+	if( gamenet_man->OnServer())
+	{
+		Net::Server* server;
+		Net::MsgDesc msg_desc;
+
+		server = gamenet_man->GetServer();
+
+		Script::RunScript( "do_backend_retry" );
+	
+		msg_desc.m_Id = MSG_ID_END_GAME;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+		{
+			if( player->IsLocalPlayer())
+			{
+				continue;
+			}
+	
+			server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+		}
+	}
+	else
+	{
+		Net::Client* client;
+		Net::MsgDesc msg_desc;
+
+		client = gamenet_man->GetClient( 0 );
+
+		msg_desc.m_Id = MSG_ID_FCFS_END_GAME;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		client->EnqueueMessageToServer( &msg_desc );
+	}
+
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetJoinMode | 
+// @uparm 1 | mode (0 for play, anything else for observe)
+bool Manager::ScriptSetJoinMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	int mode = 0;
+    
+	mode = 0;
+	pParams->GetInteger( NONAME, &mode );
+	if( mode == 0 )
+	{
+		gamenet_man->SetJoinMode( vJOIN_MODE_PLAY );
+	}
+	else
+	{
+		gamenet_man->SetJoinMode( vJOIN_MODE_OBSERVE );
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetHostMode | 
+// @uparm 1 | mode (0 for server, 1 for auto_serve, 2 for fcfs)
+bool Manager::ScriptSetHostMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	int mode = 0;
+    
+	mode = 0;
+	pParams->GetInteger( NONAME, &mode );
+
+	gamenet_man->SetHostMode( (HostMode) mode );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptGetNumTeams(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Script::CStruct* pass_back_params;
+	int num_teams;
+	
+	num_teams = skate_mod->GetGameMode()->NumTeams();
+	pass_back_params = pScript->GetParams();
+	pass_back_params->AddInteger( "num_teams", num_teams );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptGetNumPlayersOnTeam(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Script::CStruct* pass_back_params;
+	int team_num = 0;
+	int num_players = 0;
+	
+	pParams->GetInteger( "team", &team_num, Script::ASSERT );
+	num_players = gamenet_man->NumTeamMembers( team_num );
+	pass_back_params = pScript->GetParams();
+	pass_back_params->AddInteger( "num_members", num_players );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptGetMyTeam(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Script::CStruct* pass_back_params;
+	PlayerInfo *player;
+	
+	player = gamenet_man->GetLocalPlayer();
+	if( player )
+	{
+		pass_back_params = pScript->GetParams();
+		pass_back_params->AddInteger( "team", player->m_Team );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptSetNumTeams(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	PlayerInfo* team_player, *player;
+	Lst::Search< PlayerInfo > team_sh, sh;
+	MsgChangeTeam msg;
+	MsgByteInfo num_teams_msg;
+	Net::MsgDesc team_msg_desc;
+	int num_teams = 0;
+	Net::Server* server;
+    
+	pParams->GetInteger( NONAME, &num_teams );
+
+	if( !gamenet_man->OnServer())
+	{
+		Net::Client* client;
+		MsgByteInfo team_msg;
+		Net::MsgDesc msg_desc;
+
+		client = gamenet_man->GetClient( 0 );
+
+		team_msg.m_Data = num_teams;
+		Dbg_Printf( "Telling server to create %d teams\n", num_teams );
+
+		msg_desc.m_Data = &team_msg;
+		msg_desc.m_Length = sizeof( MsgByteInfo );
+		msg_desc.m_Id = MSG_ID_FCFS_SET_NUM_TEAMS;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+		client->EnqueueMessageToServer( &msg_desc );
+		return true;
+	}
+
+	if( ( num_teams > 0 ) &&
+		( !skate_mod->GetGameMode()->IsTeamGame()))
+	{
+		skate_mod->GetGameMode()->SetNumTeams( num_teams );
+		if( skate_mod->GetLocalSkater())
+		{
+			Script::RunScript( "PrepareSkaterForMove", NULL, skate_mod->GetLocalSkater());
+			skate_mod->move_to_restart_point( skate_mod->GetLocalSkater());
+		}
+	}
+	else
+	{
+		skate_mod->GetGameMode()->SetNumTeams( num_teams );
+	}
+	
+	server = gamenet_man->GetServer();
+	if( server == NULL )
+	{
+		return true;
+	}
+
+	num_teams_msg.m_Data = num_teams;
+
+	team_msg_desc.m_Data = &num_teams_msg;
+	team_msg_desc.m_Length = sizeof( MsgByteInfo );
+	team_msg_desc.m_Id = MSG_ID_SET_NUM_TEAMS;
+	team_msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	team_msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			continue;
+		}
+		server->EnqueueMessage( player->GetConnHandle(), &team_msg_desc );
+	}
+
+	team_msg_desc.m_Data = &msg;
+	team_msg_desc.m_Length = sizeof( MsgChangeTeam );
+	team_msg_desc.m_Id = MSG_ID_TEAM_CHANGE;
+	team_msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	team_msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	for( team_player = gamenet_man->FirstPlayerInfo( team_sh ); team_player; team_player = gamenet_man->NextPlayerInfo( team_sh ))
+	{
+		if( team_player->m_Team >= num_teams )
+		{
+			msg.m_Team = (unsigned char) vTEAM_RED;
+			msg.m_ObjID = (unsigned char) team_player->m_Skater->GetID();
+
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				if( player->IsLocalPlayer())
+				{
+					continue;
+				}
+				server->EnqueueMessage( player->GetConnHandle(), &team_msg_desc );
+			}
+			team_player->m_Team = vTEAM_RED;
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptJoinTeam(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	MsgChangeTeam msg;
+	Net::MsgDesc msg_desc;
+	Net::Client* client;
+	uint32 team_checksum;
+	int team;
+	static Tmr::Time s_last_join_time = 0;
+
+	player = gamenet_man->GetLocalPlayer();
+	if( ( player == NULL ) || 
+		( player->m_Skater == NULL ))
+	{
+		return false;
+	}
+
+	pParams->GetChecksum( NONAME, &team_checksum );
+	if( team_checksum == Script::GenerateCRC( "blue" ))
+	{
+		team = vTEAM_BLUE;
+	}
+	else if( team_checksum == Script::GenerateCRC( "red" ))
+	{
+		team = vTEAM_RED;
+	}
+	else if( team_checksum == Script::GenerateCRC( "green" ))
+	{
+		team = vTEAM_GREEN;
+	}
+	else 
+	{
+		team = vTEAM_YELLOW;
+	}
+    
+	msg.m_Team = (unsigned char) team;
+	msg.m_ObjID = (unsigned char) player->m_Skater->GetID();
+	client = gamenet_man->GetClient( 0 );
+	if(( Tmr::GetTime() - s_last_join_time ) < 150 )
+	{
+		return true;
+	}
+	s_last_join_time = Tmr::GetTime();
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgChangeTeam );
+	msg_desc.m_Id = MSG_ID_REQUEST_CHANGE_TEAM;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	client->EnqueueMessageToServer( &msg_desc );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptConnectedToPeer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_NGPS__
+	Manager * gamenet_man = Manager::Instance();
+	PEER peer;
+
+	peer = gamenet_man->mpLobbyMan->GetPeer();
+
+	return ( peer && peerIsConnected( peer ));
+#else
+	return false;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptInGroupRoom(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifndef __PLAT_NGPS__
+	return false;
+#else
+	Manager * gamenet_man = Manager::Instance();
+	return gamenet_man->mpLobbyMan->InGroupRoom();
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptIsHost(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+
+	if( gamenet_man->InNetGame() == false )
+	{
+		return false;
+	}
+
+	if( gamenet_man->OnServer())
+	{
+		return( gamenet_man->GetHostMode() == vHOST_MODE_SERVE );
+	}
+
+	player = gamenet_man->GetLocalPlayer();
+	if( player && player->IsServerPlayer())
+	{
+		return true;
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptIsAutoServing(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+		
+	if( gamenet_man->InNetGame() == false )
+	{
+		return false;
+	}
+
+	if( gamenet_man->OnServer() == false )
+	{
+		return false;
+		
+	}
+
+	return( gamenet_man->GetHostMode() == vHOST_MODE_AUTO_SERVE );
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptChangeLevelPending(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	
+	return( gamenet_man->m_change_level_task->InList());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptDownloadMotd(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	gamenet_man->SetNextServerListState( vSERVER_LIST_STATE_STARTING_MOTD );
+	gamenet_man->SetServerListState( vSERVER_LIST_STATE_INITIALIZE );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptAlreadyGotMotd(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_NGPS__
+	Manager * gamenet_man = Manager::Instance();
+
+	return gamenet_man->m_got_motd;
+#else
+	return true;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptAlreadySignedIn(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_XBOX__
+	Manager * gamenet_man = Manager::Instance();
+
+	return gamenet_man->mpAuthMan->SignedIn();
+#else
+	return false;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptSignOut(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_XBOX__
+	Manager * gamenet_man = Manager::Instance();
+
+	gamenet_man->mpAuthMan->SignOut();
+#endif
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptAllPlayersAreReady(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+
+	if( gamenet_man->OnServer())
+	{
+		for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+		{
+			if(( player->m_Conn->GetStatus() & Net::Conn::mSTATUS_READY ) == 0 )
+			{
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptJoinServerComplete(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	return gamenet_man->ReadyToPlay();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptJustStartedNetGame(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	if(( Tmr::GetTime() - s_time_to_start_game ) < Tmr::Seconds( 5 ))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptEnteredNetworkGame(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+    
+	mlp_manager->AddLogicTask( *gamenet_man->m_enter_chat_task );
+	mlp_manager->AddLogicTask( *gamenet_man->m_render_scores_task );
+	gamenet_man->m_scores_on = true;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptChooseAccount(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_XBOX__
+	Manager * gamenet_man = Manager::Instance();
+	int index;
+
+	index = 0;
+	pParams->GetInteger( Script::GenerateCRC("index"), &index );
+	gamenet_man->mpAuthMan->SelectAccount( index );
+#endif	// __PLAT_XBOX__
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptCreatePlayerOptions(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Script::CScriptStructure* pStructure;
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	int index;
+	const char* name;
+
+	index = 0;
+	name = NULL;
+	pParams->GetInteger( Script::GenerateCRC("index"), &index );
+	pParams->GetString( Script::GenerateCRC("name"), &name );
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( index == 0 )
+		{
+			if( stricmp( player->m_Name, name ) == 0 )
+			{
+				player->m_flags.SetMask( PlayerInfo::mMARKED_PLAYER );
+
+				pStructure = new Script::CScriptStructure;
+
+#ifdef __PLAT_NGPS__	
+				if( gamenet_man->InInternetMode())
+				{
+					if( ( player->m_Profile ) && 
+						( gamenet_man->mpBuddyMan->IsLoggedIn()))
+					{         
+						pStructure->AddInteger( "profile", player->m_Profile );	
+						pStructure->AddString( "nick", player->m_Name );    
+				
+						if( gamenet_man->mpBuddyMan->IsAlreadyMyBuddy( player->m_Profile ))
+						{
+							pStructure->AddChecksum( NONAME, Script::GenerateCRC( "allow_remove_homie" ));
+						}
+						else
+						{
+							pStructure->AddChecksum( NONAME, Script::GenerateCRC( "allow_add_homie" ));
+						}
+					}
+				}
+#endif
+				pStructure->AddComponent( Script::GenerateCRC( "name" ), ESYMBOLTYPE_STRING, name );
+	
+				Script::RunScript( "create_player_options_dialog", pStructure );
+	
+				delete pStructure;
+			}
+			break;
+		}
+		
+		index--;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGPS__
+bool Manager::ScriptFillPlayerListMenu( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	Script::CStruct* p_item_params, *p_unfocus_params;
+	Script::CArray* p_colors;
+	int i;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+	i = 0;
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			i++;
+			continue;
+		}
+		
+		p_colors = new Script::CArray;
+		p_colors->SetSizeAndType( 4, ESYMBOLTYPE_INTEGER );
+
+		if( ( player->m_Profile ) && 
+			( gamenet_man->mpBuddyMan->IsLoggedIn()))
+		{         
+			if( gamenet_man->mpBuddyMan->IsAlreadyMyBuddy( player->m_Profile ))
+			{
+				p_colors->SetInteger( 0, 128 );
+				p_colors->SetInteger( 1, 128 );
+				p_colors->SetInteger( 2, 0 );
+				p_colors->SetInteger( 3, 128 );
+			}
+			else
+			{
+				p_colors->SetInteger( 0, 0 );
+				p_colors->SetInteger( 1, 128 );
+				p_colors->SetInteger( 2, 0 );
+				p_colors->SetInteger( 3, 128 );
+			}
+		}
+		else
+		{
+			p_colors->SetInteger( 0, 128 );
+			p_colors->SetInteger( 1, 128 );
+			p_colors->SetInteger( 2, 128 );
+			p_colors->SetInteger( 3, 128 );
+		}
+		
+		p_item_params = new Script::CStruct;	
+		p_item_params->AddString( "text", player->m_Name );
+		p_item_params->AddChecksum( "centered", CRCD(0x2a434d05,"centered") );
+		p_item_params->AddChecksum( "id", 123456 + i );
+		p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("choose_selected_player"));
+		
+		p_unfocus_params = new Script::CStruct;
+		p_unfocus_params->AddArray( "rgba", p_colors );
+		p_item_params->AddArray( "rgba", p_colors );
+		p_item_params->AddStructure( "unfocus_params", p_unfocus_params );
+	
+		// create the parameters that are passed to the X script
+		Script::CStruct *p_script_params= new Script::CStruct;
+		p_script_params->AddInteger( "index", i );	
+		p_script_params->AddString( "name", player->m_Name );	
+		p_item_params->AddStructure("pad_choose_params",p_script_params);			
+	
+		Script::RunScript("theme_menu_add_item",p_item_params);
+		
+		delete p_item_params;
+		delete p_script_params;
+		delete p_unfocus_params;
+		delete p_colors;
+		i++;
+	}
+
+	Mem::Manager::sHandle().PopContext();
+	
+	return true;
+}
+#endif
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptRemovePlayer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Script::CScriptStructure* pStructure;
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	int index;
+	const char* name;
+
+	index = 0;
+	name = NULL;
+	pParams->GetInteger( Script::GenerateCRC("index"), &index );
+	pParams->GetString( Script::GenerateCRC("name"), &name );
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		if( index == 0 )
+		{
+			if( stricmp( player->m_Name, name ) == 0 )
+			{
+				player->m_flags.SetMask( PlayerInfo::mMARKED_PLAYER );
+
+				pStructure = new Script::CScriptStructure;
+				pStructure->AddComponent( Script::GenerateCRC( "name" ), ESYMBOLTYPE_STRING, name );
+	
+				Script::RunScript( "create_kick_ban_menu", pStructure );
+	
+				delete pStructure;
+			}
+			break;
+		}
+		
+		index--;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptCancelRemovePlayer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		player->m_flags.ClearMask( PlayerInfo::mMARKED_PLAYER );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptKickPlayer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	int i;
+
+	i = 0;
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->m_flags.TestMask( PlayerInfo::mMARKED_PLAYER ))
+		{
+			if( gamenet_man->OnServer())
+			{
+				Obj::CSkater* quitting_skater;
+				bool observing;
+	
+				quitting_skater = player->m_Skater;
+				observing = player->IsObserving();
+				gamenet_man->DropPlayer( player, vREASON_KICKED );
+				if( !observing )
+				{
+					Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+					skate_mod->remove_skater( quitting_skater );
+				}
+			}
+			else
+			{
+				Net::Client* client;
+				MsgRemovePlayerRequest msg;
+				Net::MsgDesc msg_desc;
+
+				client = gamenet_man->GetClient( 0 );
+				Dbg_Assert( client );
+
+				msg.m_Ban = 0;
+				msg.m_Index = i;
+				strcpy( msg.m_Name, player->m_Name );
+
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( MsgRemovePlayerRequest );
+				msg_desc.m_Id = MSG_ID_FCFS_BAN_PLAYER;
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+				client->EnqueueMessageToServer( &msg_desc );
+												
+			}
+			break;
+		}
+		i++;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptBanPlayer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	int i;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+	i = 0;
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->m_flags.TestMask( PlayerInfo::mMARKED_PLAYER ))
+		{
+			if( gamenet_man->OnServer())
+			{
+				Obj::CSkater* quitting_skater;
+				bool observing;
+	
+				quitting_skater = player->m_Skater;
+				observing = player->IsObserving();
+				gamenet_man->DropPlayer( player, vREASON_BANNED );
+				if( !observing )
+				{
+					Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+					skate_mod->remove_skater( quitting_skater );
+				}
+			}
+			else
+			{
+				Net::Client* client;
+				MsgRemovePlayerRequest msg;
+				Net::MsgDesc msg_desc;
+
+				client = gamenet_man->GetClient( 0 );
+				Dbg_Assert( client );
+
+				msg.m_Ban = 1;
+				msg.m_Index = i;
+				strcpy( msg.m_Name, player->m_Name );
+
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( MsgRemovePlayerRequest );
+				msg_desc.m_Id = MSG_ID_FCFS_BAN_PLAYER;
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+				client->EnqueueMessageToServer( &msg_desc );
+			
+			}
+			break;
+		}
+		i++;
+	}
+
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptFCFSRequestStartGame(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	MsgStartGameRequest msg;
+	Net::MsgDesc msg_desc;
+	Manager * gamenet_man = Manager::Instance();
+	Net::Client* client;
+	Prefs::Preferences* pPreferences;
+	
+	pPreferences = gamenet_man->GetNetworkPreferences();
+	msg.m_SkillLevel = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("skill_level"), Script::GenerateCRC("checksum") );
+	msg.m_FireballLevel = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("fireball_difficulty"), Script::GenerateCRC("checksum") );
+	msg.m_GameMode = gamenet_man->GetGameTypeFromPreferences();
+	msg.m_TimeLimit = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("time_limit"), Script::GenerateCRC("checksum") );
+	msg.m_TargetScore = 0;
+	msg.m_TargetScore = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("target_score"), Script::GenerateCRC("checksum") );
+	Dbg_Printf( "************ SENDING TARGET SCORE OF %08x\n", msg.m_TargetScore );
+	msg.m_PlayerCollision = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("player_collision"), Script::GenerateCRC("checksum") );
+	msg.m_FriendlyFire = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("friendly_fire"), Script::GenerateCRC("checksum") );
+	msg.m_StopAtZero = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("stop_at_zero"), Script::GenerateCRC("checksum") );
+	msg.m_CTFType = pPreferences->GetPreferenceChecksum( Script::GenerateCRC("ctf_game_type"), Script::GenerateCRC("checksum") );
+	
+	// FCFS functions should only happen on clients
+	Dbg_Assert( !gamenet_man->OnServer());
+
+	client = gamenet_man->GetClient( 0 );
+	Dbg_Assert( client );
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgStartGameRequest );
+	msg_desc.m_Id = MSG_ID_FCFS_START_GAME;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	client->EnqueueMessageToServer( &msg_desc );
+
+	gamenet_man->m_game_pending = true;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptFCFSRequestChangeLevel(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 level;
+	Manager * gamenet_man = Manager::Instance();
+	MsgIntInfo msg;
+	Net::MsgDesc msg_desc;
+	Net::Client* client;
+
+	// Need to limit this option. Make sure a second request doesn't go out while waiting for the first
+	// to complete
+
+	// FCFS functions should only happen on clients
+	Dbg_Assert( !gamenet_man->OnServer());
+
+	level = 0;
+	pParams->GetChecksum( Script::GenerateCRC( "level" ), &level );
+    msg.m_Data = (int) level;
+
+	client = gamenet_man->GetClient( 0 );
+	Dbg_Assert( client );
+
+	if( level == CRCD(0xb664035d,"Load_Sk5Ed_gameplay"))
+	{
+		client->StreamMessageToServer( MSG_ID_LEVEL_DATA, Ed::CParkManager::COMPRESSED_MAP_SIZE, 
+						   Ed::CParkManager::sInstance()->GetCompressedMapBuffer(), "level data", vSEQ_GROUP_PLAYER_MSGS );
+						   
+		client->StreamMessageToServer( MSG_ID_RAIL_DATA, Obj::GetRailEditor()->GetCompressedRailsBufferSize(), 
+						   Obj::GetRailEditor()->GetCompressedRailsBuffer(), "rail data", vSEQ_GROUP_PLAYER_MSGS );
+    
+	}
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgIntInfo );
+	msg_desc.m_Id = MSG_ID_FCFS_CHANGE_LEVEL;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	client->EnqueueMessageToServer( &msg_desc );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptFoundServers(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	
+	return( gamenet_man->m_servers.CountItems() > 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptCancelJoinServer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	gamenet_man->CancelJoinServer();
+	gamenet_man->SetJoinState( vJOIN_STATE_FINISHED );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptReattemptJoinServer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	gamenet_man->ReattemptJoin( gamenet_man->GetClient( 0 ));
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptDropPendingPlayers(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	gamenet_man->DropPartiallyLoadedPlayers();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptToggleProSet(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	int bit;
+	uint32 param_id;
+	Manager * gamenet_man = Manager::Instance();
+
+	pParams->GetInteger( "bit", &bit, Script::ASSERT );
+	pParams->GetChecksum( "param_id", ¶m_id, Script::ASSERT );
+    
+	gamenet_man->ToggleProSet( bit, param_id );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptFCFSRequestToggleProSet(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Net::Client* client;
+	MsgToggleProSet msg;
+	Net::MsgDesc msg_desc;
+	uint32 param_id;
+	int bit;
+
+	pParams->GetInteger( "bit", &bit, Script::ASSERT );
+	pParams->GetChecksum( "param_id", ¶m_id, Script::ASSERT );
+
+	client = gamenet_man->GetClient( 0 );
+	Dbg_Assert( client );
+
+	msg.m_Bit = bit;
+	msg.m_ParamId = param_id;
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgToggleProSet );
+	msg_desc.m_Id = MSG_ID_FCFS_TOGGLE_PROSET;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	client->EnqueueMessageToServer( &msg_desc );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptFCFSRequestToggleGoalSelection(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    uint32 goalId;
+	Manager * gamenet_man = Manager::Instance();
+	Net::Client* client;
+	MsgIntInfo msg;
+	Net::MsgDesc msg_desc;
+
+    pParams->GetChecksum( "name", &goalId, Script::ASSERT );
+	
+	client = gamenet_man->GetClient( 0 );
+	Dbg_Assert( client );
+
+	msg.m_Data = goalId;
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( MsgIntInfo );
+	msg_desc.m_Id = MSG_ID_FCFS_TOGGLE_GOAL_SELECTION;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	client->EnqueueMessageToServer( &msg_desc );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptResetProSetFlags(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	int i;
+
+	for( i = 0; i < vMAX_NUM_PROSETS; i++ )
+	{
+		Script::CStruct* pSetParams;
+		pSetParams = new Script::CStruct;
+
+		pSetParams->AddInteger( "flag", Script::GetInt( "FLAG_PROSET1_GEO_ON" ) + i );
+		if( gamenet_man->m_proset_flags.Test( i ))
+		{
+			Script::RunScript( "SetFlag", pSetParams );
+		}
+		else
+		{
+			Script::RunScript( "UnSetFlag", pSetParams );
+		}
+
+		delete pSetParams;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptHasSignedDisclaimer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Prefs::Preferences*	prefs;
+	
+	prefs = gamenet_man->GetNetworkPreferences();
+	uint32 has_signed = prefs->GetPreferenceChecksum( Script::GenerateCRC( "signed_disclaimer" ), Script::GenerateCRC( "checksum" ));
+	
+	return ( has_signed == Script::GenerateCRC( "boolean_true" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptEnterSurveyorMode(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Inp::Manager * inp_manager = Inp::Manager::Instance();
+	Mlp::Manager* mlp_man = Mlp::Manager::Instance();
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* local_player;
+
+	local_player = gamenet_man->GetLocalPlayer();
+	if( local_player->IsObserving() || local_player->IsSurveying())
+	{
+		return false;
+	}
+
+	Dbg_Printf( "Entering surveyor mode\n" );
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	local_player->m_observer_logic_task = 
+					new Tsk::Task< PlayerInfo > ( PlayerInfo::s_observer_logic_code, *local_player );
+	mlp_man->AddLogicTask( *local_player->m_observer_logic_task );
+	gamenet_man->m_observer_input_handler = new Inp::Handler< Manager > ( 0,  s_observer_input_logic_code, *gamenet_man, 
+															 Tsk::BaseTask::Node::vHANDLER_PRIORITY_OBSERVER_INPUT_LOGIC );
+	inp_manager->AddHandler( *gamenet_man->m_observer_input_handler );
+
+	local_player->m_flags.SetMask( PlayerInfo::mSURVEYING );
+
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptExitSurveyorMode(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* local_player;
+
+	local_player = gamenet_man->GetLocalPlayer();
+	if( !local_player )
+	{
+		return false;
+	}
+
+	Dbg_Assert( !( local_player->IsObserving()));
+
+	if( local_player->IsSurveying() == false )
+	{
+		return true;
+	}
+
+	Dbg_Printf( "Exiting surveyor mode\n" );
+	gamenet_man->ObservePlayer( local_player );
+	delete local_player->m_observer_logic_task;
+	local_player->m_observer_logic_task = NULL;
+	delete gamenet_man->m_observer_input_handler;
+	gamenet_man->m_observer_input_handler = NULL;
+
+	local_player->m_flags.ClearMask( PlayerInfo::mSURVEYING );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetReadyToPlay(bool ready_to_play)
+{
+	if (ready_to_play == m_ready_to_play) return;
+	
+	m_ready_to_play = ready_to_play;
+	
+	// Dan: moved from the beginning of CSkater::DoServerLogic().  I'm sort of just moving a hack, instead of fixing it...
+	
+	// keep the skater from skating around before he it ready
+	if (!Mdl::Skate::Instance()->GetGameMode()->IsTrue( "is_frontend" ))
+	{
+		if (m_ready_to_play)
+		{
+			Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+			uint32 NumSkaters = skate_mod->GetNumSkaters();
+			for (uint32 i = 0; i < NumSkaters; ++i)
+			{
+			   Obj::CSkater *pSkater = skate_mod->GetSkater(i);
+			   if (pSkater)
+			   {
+				   pSkater->UnPause();
+			   }	
+			}	
+		}
+		else
+		{
+			Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+			uint32 NumSkaters = skate_mod->GetNumSkaters();
+			for (uint32 i = 0; i < NumSkaters; ++i)
+			{
+			   Obj::CSkater *pSkater = skate_mod->GetSkater(i);
+			   if (pSkater)
+			   {
+				   pSkater->Pause();
+			   }	
+			}	
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32		Manager::GetGoalsLevel( void )
+{
+	return m_goals_level;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::UsingCreatedGoals( void )
+{
+	/*uint32 goals_pref;
+
+	goals_pref = m_network_preferences.GetPreferenceChecksum( CRCD(0x38dbe1d0,"goals"), CRCD( 0x21902065, "checksum" ));
+	if( goals_pref == CRCD(0xe9354db8,"goals_created"))
+	{
+		return true;
+	}*/
+
+	if( Obj::GetGoalEditor()->CountGoalsForLevel( 0 ) > 0 )
+	{
+		return true;
+	}
+
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::LoadGoals( uint32 level )
+{
+	m_goals_level = level;
+
+	// Store the level as the first 4 bytes
+	memcpy( m_goals_data, &level, sizeof( uint32 ));
+	m_goals_data_size = Obj::GetGoalEditor()->WriteToBuffer( level, (uint8*) ( m_goals_data + sizeof( uint32 )), 
+															 vMAX_GOAL_SIZE );
+	m_goals_data_size += sizeof( uint32 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*		Manager::GetGoalsData( void )
+{
+	return m_goals_data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			Manager::GetGoalsDataSize( void )
+{
+	return m_goals_data_size;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::SetGoalsData( char* data, uint32 level, int size )
+{
+	Dbg_Assert( size < vMAX_GOAL_SIZE );
+
+	memcpy( m_goals_data, data, size );
+	m_goals_data_size = size;
+	m_goals_level = level;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/GameNet.h b/Code/Sk/GameNet/GameNet.h
new file mode 100644
index 0000000..c3a21dc
--- /dev/null
+++ b/Code/Sk/GameNet/GameNet.h
@@ -0,0 +1,1100 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate3													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		GameNet.h												**
+**																			**
+**	Created by:		02/01/01	-	spg										**
+**																			**
+**	Description:	Game-Side Network Code 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_GAMENET_H
+#define __GAMENET_GAMENET_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#ifdef __PLAT_NGPS__
+//#include 
+#include 
+#include 
+#include 
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+	  
+namespace	Obj
+{
+	class	CSkaterProfile;
+	class	CCrown;
+    class   CMovingObject;
+}
+
+namespace GameNet
+{
+
+						
+
+enum
+{
+	vVERSION_NUMBER = 0x30036,	// version number. High word is the integer, lo word is the decimal fraction
+};
+
+enum
+{
+	vMAX_RANK			=	10,
+	vMAX_GAME_CREDIT	=	500,
+	vNUM_RANKED_LEVELS	=	12,
+	vMAX_SCORE_CREDIT	=	100,
+	vMAX_COMBO_CREDIT	=	100,
+	vMAX_RATING			= (( vMAX_GAME_CREDIT ) + ( vNUM_RANKED_LEVELS * ( vMAX_SCORE_CREDIT + vMAX_COMBO_CREDIT ))),
+};
+
+enum NetworkMode
+{
+	vNO_NET_MODE,
+	vLAN_MODE,
+	vINTERNET_MODE
+};
+
+enum JoinMode
+{
+	vJOIN_MODE_PLAY,
+	vJOIN_MODE_OBSERVE
+};
+
+enum
+{
+	mSERVER 	= 0x0001,
+	mCLIENT 	= 0x0002,
+	mINTERNET	= 0x0004,
+	mLAN		= 0x0008,
+	
+	mNETWORK_MODE = mINTERNET | mLAN
+};
+						 
+enum
+{
+	vMAX_LOCAL_CLIENTS		= 2	// two-player split screen
+};
+
+enum
+{
+	vSKILL_LEVEL_1,
+	vSKILL_LEVEL_2,
+	vSKILL_LEVEL_3,
+	vSKILL_LEVEL_4,
+	vSKILL_LEVEL_5,
+	
+	vSKILL_LEVEL_DEFAULT = vSKILL_LEVEL_3
+};
+
+enum
+{
+	vDEFERRED_JOIN_FRAMES	=	10
+};
+
+enum
+{
+	vSCORE_UPDATE_FREQUENCY	=	2000	// update the score every 2 seconds
+};
+
+enum DropReason
+{
+	vREASON_QUIT,
+	vREASON_TIMEOUT,
+	vREASON_OBSERVING,
+	vREASON_BANNED,
+	vREASON_KICKED,
+	vREASON_BAD_CONNECTION,
+	vREASON_LEFT_OUT
+};
+
+// Valid fields of object updates
+enum
+{
+	mUPDATE_FIELD_POS_X			= 0x0001,
+	mUPDATE_FIELD_POS_Y 		= 0x0002,
+	mUPDATE_FIELD_POS_Z 		= 0x0004,
+	mUPDATE_FIELD_ROT_X			= 0x0008,
+	mUPDATE_FIELD_ROT_Y			= 0x0010,
+	mUPDATE_FIELD_ROT_Z			= 0x0020,
+	mUPDATE_FIELD_STATE			= 0x0040,
+	mUPDATE_FIELD_FLAGS 		= 0x0080,
+	mUPDATE_FIELD_RAIL_NODE		= 0x0100,
+};
+
+enum
+{
+	mSTATE_MASK = 0x07,
+	mDOING_TRICK_MASK = 0x80,
+};
+
+enum ServerListState
+{
+	vSERVER_LIST_STATE_WAIT,
+	vSERVER_LIST_STATE_INITIALIZE,
+	vSERVER_LIST_STATE_SHUTDOWN,
+	vSERVER_LIST_STATE_STARTING_MOTD,
+	vSERVER_LIST_STATE_GETTING_MOTD,
+	vSERVER_LIST_STATE_TRACK_USAGE,
+	vSERVER_LIST_STATE_SHOW_MOTD,
+	vSERVER_LIST_STATE_RETRY_MOTD,
+	vSERVER_LIST_STATE_FAILED_MOTD,
+	vSERVER_LIST_STATE_TRACKING_USAGE,
+	vSERVER_LIST_STATE_STARTING_LOBBY_LIST,
+	vSERVER_LIST_STATE_GETTING_LOBBY_LIST,
+	vSERVER_LIST_STATE_GOT_LOBBY_LIST,
+	vSERVER_LIST_STATE_FAILED_LOBBY_LIST,
+	vSERVER_LIST_STATE_FILL_LOBBY_LIST,
+};
+
+enum JoinState
+{   
+	vJOIN_STATE_WAITING,
+	vJOIN_STATE_CONNECTING,
+	vJOIN_STATE_JOINING,
+	vJOIN_STATE_JOINING_WITH_PASSWORD,
+	vJOIN_STATE_TRYING_PASSWORD,
+	vJOIN_STATE_GOT_PLAYERS,
+	vJOIN_STATE_WAITING_FOR_START_INFO,
+	vJOIN_STATE_CONNECTED,
+	vJOIN_STATE_REFUSED,
+	vJOIN_STATE_FINISHED
+};
+
+enum ObserverCommand
+{
+	vOBSERVER_COMMAND_NONE,
+	vOBSERVER_COMMAND_NEXT,
+};
+
+enum HostMode
+{
+	vHOST_MODE_SERVE,
+	vHOST_MODE_AUTO_SERVE,
+	vHOST_MODE_FCFS,      
+};
+
+enum
+{
+	vSORT_KEY_NAME,
+	vSORT_KEY_PING,
+	vSORT_KEY_NUMPLAYERS,
+	vSORT_KEY_MODE,
+	vSORT_KEY_LEVEL,
+	vSORT_KEY_SKILL,
+	vNUM_SORT_KEYS
+};
+
+enum
+{
+	vTEAM_RED,
+	vTEAM_BLUE,
+	vTEAM_GREEN,
+	vTEAM_YELLOW,
+	vMAX_TEAMS,
+
+	vNO_TEAM = -1
+};
+
+enum
+{
+	vNET_THREAD_STACK_SIZE = ( 32 * 1024 )
+};
+
+enum
+{
+	vNUM_CROWN_SPAWN_POINTS	= 10
+};
+
+enum
+{
+	vMAX_MASTER_SERVERS = 10,
+	vMAX_NUM_PROSETS = 7,
+	vMAX_GOAL_SIZE = 6000,
+};
+
+enum
+{
+	vGAMESPY_PRODUCT_ID = 10160
+};
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class LobbyMan;
+class ContentMan;
+class BuddyMan;
+class StatsMan;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  NewPlayerInfo  : public Spt::Class
+{
+	
+	
+	public:
+		NewPlayerInfo();
+		~NewPlayerInfo();
+		
+	public:
+		char					Name[ vMAX_PLAYER_NAME_LEN + 1 ];
+		char					ObjID;
+		Net::Conn*				Conn;
+		unsigned int			AllowedJoinFrame;
+		Obj::CSkaterProfile*	mpSkaterProfile;
+		int 					Flags;
+		int						JumpInFrame;
+		int						Team;
+		int						Profile;
+		int						Rating;
+		int						Score;
+		uint32					VehicleControlType;
+};
+
+class TriggerEvent : public Lst::Node< TriggerEvent >
+{   
+public:
+		TriggerEvent( void ) 
+			: Lst::Node< TriggerEvent > ( this ) {}
+
+		int						m_Node;
+		int						m_ObjID;
+		uint32					m_Script;
+};
+
+class LastSentProps
+{
+public:
+
+	unsigned int	m_LastSkaterUpdateTime[Mdl::Skate::vMAX_SKATERS];	// The last time of update
+	char			m_LastSkaterTerrain[Mdl::Skate::vMAX_SKATERS];		// The last terrain of each skater
+	char			m_LastSkaterWalking[Mdl::Skate::vMAX_SKATERS];		// The last walking of each skater
+	char			m_LastSkaterDriving[Mdl::Skate::vMAX_SKATERS];		// The last driving of each skater
+	short			m_LastSkaterPosUpdate[Mdl::Skate::vMAX_SKATERS][3];	// The last pos of each skater
+	short			m_LastSkaterRotUpdate[Mdl::Skate::vMAX_SKATERS][3];	// The last pos of each skater
+    Flags< int >	m_LastSkaterFlagsUpdate[Mdl::Skate::vMAX_SKATERS];	// The last flags 
+																	   	// we sent to each skater
+	Flags< int >	m_LastEndRunFlagsUpdate[Mdl::Skate::vMAX_SKATERS];	// The last end run flags 
+																	   	// we sent to each skater
+	char			m_LastSkaterStateUpdate[Mdl::Skate::vMAX_SKATERS];	// The last state
+																		// we sent to each skater
+	char			m_LastDoingTrickUpdate[Mdl::Skate::vMAX_SKATERS];	// The last "doing trick"
+																		// state we sent to each skater
+	char			m_LastDrivingUpdate[Mdl::Skate::vMAX_SKATERS];		// The last "driving"
+																		// state we sent to each skater
+	sint16			m_LastRailNodeUpdate[Mdl::Skate::vMAX_SKATERS];		// The last rail node sent to each skater
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class PlayerInfo : public Lst::Node< PlayerInfo >
+{
+public:
+	
+	friend class Manager;
+
+	enum SkaterFlagMasks
+	{
+		mTENSE = nBit( Obj::TENSE ),
+		// mSWITCH = nBit( Obj::SWITCH ),
+		// mLEAN = nBit( Obj::LEAN ),
+		// mTENSE_ON_GROUND = nBit( Obj::TENSE_ON_GROUND ),
+		mFLIPPED = nBit( Obj::FLIPPED ),
+		mVERT_AIR = nBit( Obj::VERT_AIR),
+		mTRACKING_VERT = nBit( Obj::TRACKING_VERT ),
+		mLAST_POLY_WAS_VERT = nBit( Obj::LAST_POLY_WAS_VERT ),
+		mCAN_BREAK_VERT = nBit( Obj::CAN_BREAK_VERT ),
+		mCAN_RERAIL = nBit( Obj::CAN_RERAIL ),
+		// mSHOULD_END_RUN = nBit( Obj::SHOULD_END_RUN ),
+		// mFINISHED_END_OF_RUN = nBit( Obj::FINISHED_END_OF_RUN ),
+		mRAIL_SLIDING = nBit( Obj::RAIL_SLIDING ),
+		mCAN_HIT_CAR = nBit( Obj::CAN_HIT_CAR ),
+		mAUTOTURN = nBit( Obj::AUTOTURN ),
+		mIS_BAILING = nBit( Obj::IS_BAILING ),
+	};
+
+	enum
+	{
+		mLOCAL_PLAYER	=	0x00000001,
+		mNON_COLLIDABLE	=	0x00000002,	// For use, for example, after skater is knocked down so you don't
+									// knock him down over and over
+		mOBSERVER		=	0x00000004,
+		mPENDING_PLAYER	=	0x00000008,	// Player is observing until the game finishes
+		mJUMPING_IN		=	0x00000010,	// Transitioning from pending to playing
+		mFULLY_IN		=	0x00000020,	// Player is fully in the game
+		mHAS_PLAYER_INFO=	0x00000040,	// Player has fully received initial player info packet(s)
+		mKING_OF_THE_HILL=	0x00000080,	// Player is king of the hill
+		mRUN_ENDED		=	0x00000100,	// Player's run has ended
+		mSERVER_PLAYER	=	0x00000200,	// The player acting as host
+		mMARKED_PLAYER	=	0x00000400,	// The player is marked for an operation
+		mHAS_RED_FLAG	=	0x00000800,	// The player has the red flag
+		mHAS_BLUE_FLAG	=	0x00001000,	// The player has the blue flag
+		mHAS_GREEN_FLAG	=	0x00002000,	// The player has the green flag
+		mHAS_YELLOW_FLAG=	0x00004000,	// The player has the yellow flag
+		mRESTARTING		=	0x00008000,	// The player is restarting
+		mSURVEYING		=	0x00010000,	// The player is surveying the playing field
+
+		mHAS_ANY_FLAG = mHAS_RED_FLAG | mHAS_BLUE_FLAG | mHAS_GREEN_FLAG | mHAS_YELLOW_FLAG,
+	};
+	
+	PlayerInfo( int flags );
+	
+	void			SetSkater( Obj::CSkater* skater );
+	void			CopyProfile( Obj::CSkaterProfile* pSkaterProfile );
+
+	void			SetFaceData( uint8* face_data, int size );
+	uint8*			GetFaceData( void );
+	
+	bool			IsLocalPlayer( void );
+	bool			IsServerPlayer( void );
+	bool			IsObserving( void );
+	bool			IsSurveying( void );
+
+	void			MarkAsServerPlayer( void );
+	void			MarkAsNotServerPlayer( void );
+	void			MarkAsReady( int time );
+	void			MarkAsNotReady( int time );
+	void			MarkAsRestarting( void );
+	void			SetReadyQueryTime( int time );
+
+	void			MarkAsNonCollidable( void );
+	void			ClearNonCollidable( void );
+	bool			IsNonCollidable( void );
+
+	void			MarkAsFullyIn( void );
+	bool			IsFullyIn( void );
+
+	void			MarkAsKing( bool mark );
+	bool			IsKing( void );
+
+	bool			HasCTFFlag( void );
+	int				HasWhichFlag( void );
+	void			TookFlag( int team );
+	void			StoleFlag( int team );
+	void			CapturedFlag( int team );
+	void			RetrievedFlag( void );
+	void			ClearCTFState( void );
+
+	bool			IsVulnerable( void );
+	void			ResetProjectileVulnerability( void );
+	void			SetHitTime( Tmr::Time hit_time );
+
+	bool			IsPendingPlayer( void );
+
+	int				GetLastObjectUpdateID( void );
+	void			SetLastObjectUpdateID( int id );
+	int				GetMaxObjectUpdates( void );
+
+	int				GetConnHandle( void );
+	int				GetSkaterNumber( void );
+
+	Obj::CSkater*	m_Skater;
+	Net::Conn*		m_Conn;
+	int				m_Score;
+	int				m_BestCombo;
+	int				m_Profile;
+	int				m_Rating;
+	int				m_Team;
+	char			m_Name[ vMAX_PLAYER_NAME_LEN + 1 ];
+	uint32			m_VehicleControlType;
+	Obj::CSkaterProfile*	mp_SkaterProfile;							// Appearance information
+    
+	LastSentProps	m_LastSentProps;
+
+protected:
+					~PlayerInfo();
+private:
+	static	Tsk::Task< PlayerInfo >::Code   	s_observer_logic_code;
+
+	Flags< int >	m_flags;
+	int				m_latest_ready_query;
+	int				m_jump_in_frame;
+	Tmr::Time		m_last_bail_time;
+	Tmr::Time		m_last_hit_time;
+	int				m_last_object_update_id;
+	uint8*			m_face_data;
+
+	Tsk::Task< PlayerInfo >*		m_observer_logic_task;
+};
+
+class ServerInfo : public Lst::Node< ServerInfo >
+{
+	public:
+		ServerInfo( void );
+		~ServerInfo( void );
+
+		void	AddPlayer( char* name, int rating = 0 );
+		char*	GetPlayerName( int index );
+		int		GetPlayerRating( int index );
+		void	ClearPlayerNames( void );
+
+		char	m_Name[vMAX_SERVER_NAME_LEN + 1];
+		int		m_Ip;
+		int 	m_Port;
+		int		m_Latency;
+		int		m_NumPlayers;
+		int		m_MaxPlayers;
+		int		m_NumObservers;
+		int		m_MaxObservers;
+		char	m_Level[32];
+		char	m_Mode[32];
+		bool	m_Password;
+		bool	m_GameStarted;
+		char	m_SkillLevel;
+		int		m_HostMode;
+		bool	m_Ranked;
+		bool	m_CanDirectConnect;
+		bool	m_Listed;
+		bool	m_InFocus;
+		bool	m_HasBasicInfo;
+		bool	m_HasFullInfo;
+#ifdef __PLAT_NGPS__
+		SBServer m_GServer;
+#endif
+#ifdef __PLAT_XBOX__
+		XNKID		m_XboxKeyId;
+		XNKEY		m_XboxKey;
+		XNADDR		m_XboxAddr;
+#endif // __PLAT_XBOX__
+
+	private:
+		Lst::Head< PlayerInfo > m_players;
+};
+
+class LobbyInfo : public Lst::Node< LobbyInfo >
+{
+	public:
+		LobbyInfo( void );
+
+		char	m_Name[64];
+		int		m_GroupId;
+		int		m_NumServers;
+		int 	m_MaxServers;
+		int		m_MinRating;
+		int		m_MaxRating;
+		bool	m_Full;
+		bool	m_OffLimits;
+		bool	m_Official;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Manager  : public Spt::Class
+{
+	
+public:
+	void				UsePreferences( void );
+
+//	Client and Server Access
+/////////////////////////////
+	Net::Server*		SpawnServer( bool local, bool secure );
+	Net::Client*		SpawnClient( bool broadcast, bool local, bool secure, int index );
+	Net::Client*		SpawnMatchClient( void );
+	void				SpawnAutoServer( void );
+
+	void				ServerShutdown( void );
+	void				ClientShutdown( void );
+	void				MatchClientShutdown( void );
+	void				AutoServerShutdown( void );
+
+	Net::Server*		GetServer( void );
+	Net::Client*		GetClient( int index );
+	Net::Client*		GetMatchClient( void );
+
+	int					GetJoinIP( void );
+	int					GetJoinPrivateIP( void );
+	int					GetJoinPort( void );
+	void				SetJoinIP( int ip );
+	void				SetJoinPrivateIP( int ip );
+	void				SetJoinPort( int port );
+
+//	Player Access
+/////////////////////////////
+	void				LoadPendingPlayers( void );
+	int					NumPartiallyLoadedPlayers( void );
+	void				DropPartiallyLoadedPlayers( void );
+	void				DropPlayer( PlayerInfo* info, DropReason reason );
+	void				ResetPlayers( void );
+	int					GetMaxPlayers( void );
+	int					GetMaxObservers( void );
+	void				SetMaxPlayers( int max_players );
+	void				SetMaxObservers( int max_observers );
+	int					GetNumPlayers( void );
+	int					GetNumObservers( void );
+	int					GetNumTeams( void );
+	int					NumTeamMembers( int team );
+	void				CreateTeamFlags( int num_teams );
+	void				ClientAddNewPlayer( NewPlayerInfo* new_player );
+	void				DeferredNewPlayer( NewPlayerInfo* new_player, 
+										   int num_frames = vDEFERRED_JOIN_FRAMES );
+	PlayerInfo*			NewPlayer( Obj::CSkater* skater, Net::Conn* conn, int flags = 0 );
+	void				AddPlayerToList( PlayerInfo* player );
+	void				DestroyPlayer( PlayerInfo* player );
+    
+	int					GetNextPlayerObjectId( void );
+	Obj::CSkater*		GetSkaterByConnection( Net::Conn* conn );          
+	PlayerInfo*			FirstPlayerInfo( Lst::Search< PlayerInfo > &sh, bool include_observers = false );
+	PlayerInfo*			NextPlayerInfo( Lst::Search< PlayerInfo > &sh, bool include_observers = false );
+	PlayerInfo*			GetPlayerByConnection( Net::Conn* conn );          
+	PlayerInfo*			GetPlayerByObjectID( unsigned short obj_id );
+    
+	PlayerInfo*			GetLocalPlayer( void );
+	PlayerInfo*			GetServerPlayer( void );
+	PlayerInfo*			GetNextPlayerToObserve( void );
+	PlayerInfo*			GetCurrentlyObservedPlayer( void );
+	PlayerInfo*			GetKingOfTheHill( void );
+	
+	NewPlayerInfo*		FirstNewPlayerInfo( Lst::Search< NewPlayerInfo > &sh );
+	NewPlayerInfo*		NextNewPlayerInfo( Lst::Search< NewPlayerInfo > &sh );
+	NewPlayerInfo*		GetNewPlayerInfoByObjectID( unsigned short obj_id );
+	void				DestroyNewPlayer( NewPlayerInfo* new_player );
+
+	bool				SendFaceDataToServer( void );
+	void				SendFaceDataToPlayer( PlayerInfo* player );
+
+//	Modes and Options
+/////////////////////////////
+
+	char*				GetLevelName( bool get_created_park_name = true );
+	char*				GetGameModeName( void );
+	char*				GetPassword( void );
+	int					GetSkillLevel( void );
+	void				SetNetworkMode( NetworkMode mode );
+    void				SetJoinMode( JoinMode mode );
+	JoinMode			GetJoinMode( void );
+	void				SetServerMode( bool on );
+	HostMode			GetHostMode( void );
+	void				SetHostMode( HostMode mode );
+	bool				PlayerCollisionEnabled( void );
+	bool				ShouldDisplayTeamScores( void );
+	Prefs::Preferences*	GetNetworkPreferences( void );
+	Prefs::Preferences*	GetTauntPreferences( void );
+	uint32				GetNetworkLevelId( void );
+	void				SetNetworkGameId( unsigned char id );
+	unsigned char		GetNetworkGameId( void );
+	const char*			GetNameFromArrayEntry( char* array_name, uint32 checksum );
+	int					GetIntFromArrayEntry( char* array_name, uint32 checksum, uint32 field_checksum );
+	bool				InNetMode( void );
+	bool				InNetGame( void );
+	bool				InLanMode( void );
+	bool				InInternetMode( void );
+	bool				OnServer( void );
+	bool				OnClient( void );
+	
+	void				JoinServer( bool observe_only, unsigned long ip, unsigned short port, int index );
+	void				CancelJoinServer( void );
+	void				ReattemptJoin( Net::App* client );
+	void				ReattemptJoinWithPassword( char* password );
+	void				FindServersOnLAN( void );
+	void				DisconnectFromServer( void );
+	void				CleanupPlayers();
+	
+	void				RequestObserverMode( void );
+	void				ObservePlayer( PlayerInfo* player );
+	void				EnterObserverMode( void );
+	
+	void				ChooseNewServerPlayer( void );
+	
+	void				StartNetworkLobby( void );
+	void				StartNetworkGame( void );
+	bool				ShouldStopAtZero( void );
+	
+	void				ToggleProSet( char bit, uint32 param_id );
+//	Script Functions
+/////////////////////////////	
+    
+	static	bool		ScriptJoinTeam(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptSetNumTeams(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptGetNumTeams(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptGetNumPlayersOnTeam(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptGetMyTeam(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptInGroupRoom(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptConnectedToPeer(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptIsHost(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptIsAutoServing(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptChangeLevelPending(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool 		ScriptSetJoinMode(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool 		ScriptSetHostMode(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptChooseServer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptRetrieveServerInfo(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptDescribeServer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptFoundServers(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static 	bool		ScriptRemovePlayer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static 	bool		ScriptAllPlayersAreReady(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptCreatePlayerOptions(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static 	bool		ScriptCancelRemovePlayer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static 	bool		ScriptKickPlayer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static 	bool		ScriptBanPlayer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptStartNetworkGame(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptNetworkGamePending(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptEndNetworkGame(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptSendGameOverToObservers(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptSpawnCrown(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptStartCTFGame(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptEndCTFGame(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptTookFlag(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptCapturedFlag(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptRetrievedFlag(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptHasFlag(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptTeamFlagTaken(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptDisplayFlagBaseWarning(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptAlreadyGotMotd(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptDownloadMotd(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptAlreadySignedIn(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptSignOut(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptJoinServerComplete(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptEnteredNetworkGame(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptJustStartedNetGame(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptChooseAccount(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptCancelJoinServer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptReattemptJoinServer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptDropPendingPlayers(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static 	bool		ScriptToggleProSet(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static 	bool		ScriptResetProSetFlags(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptFCFSRequestStartGame(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptFCFSRequestChangeLevel(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptFCFSRequestToggleProSet(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptFCFSRequestToggleGoalSelection(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool 		ScriptHasSignedDisclaimer(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+	static	bool		ScriptGetNumServersInLobby(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+	static	bool		ScriptEnterSurveyorMode(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool		ScriptExitSurveyorMode(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+	//void				RequestMatchmakerConnect( void );
+	void				PostGame( void );
+	void				SetLevel( uint32 level_id );
+	uint32				GetLevelFromPreferences( void );
+	int					GetFireballLevelFromPreferences( void );
+	uint32				GetGameTypeFromPreferences( void );
+
+	void				ToggleMetrics( void );
+	void				ToggleScores( void );
+	void				TogglePlayerNames( void );
+	bool				ShouldDrawPlayerNames( void );
+
+	void				CreateNetPanelMessage( 	bool broadcast, uint32 message_checksum, char* string_parm_1 = NULL,
+												char* string_param_2 = NULL, Obj::CMovingObject* object = NULL, char* properties = NULL,
+												bool important = false, int time = 2000 );
+	bool				ReadyToPlay( void ) { return m_ready_to_play; }
+	void				SetReadyToPlay( bool ready_to_play );
+	void				RespondToReadyQuery( void );
+		
+	bool				ShouldSendScoreUpdates( void );
+	void				SetLastScoreUpdateTime( int time );
+	int					GetLastScoreUpdateTime( void );
+	int					GetTeamScore( int team_id );
+	int					GetPlayerScore( int obj_id );
+
+	void				MarkReceivedFinalScores( bool received );
+	bool				HaveReceivedFinalScores( void );
+
+	void				SetCurrentLeader( PlayerInfo* player );
+	PlayerInfo*			GetCurrentLeader( void );
+
+	void				SetCurrentLeadingTeam( int team );
+	int					GetCurrentLeadingTeam( void );
+
+	void				AddSpawnedTriggerEvent( int node, int obj_id, uint32 script );
+	void				ClearTriggerEventList( void );
+	void				SetProSetFlags( char flags );
+	
+//	Server List Functions
+/////////////////////////////	
+	
+
+	void				SetServerListState( ServerListState state );
+	void				SetNextServerListState( ServerListState state );
+	ServerListState		GetServerListState( void );
+	ServerListState		GetNextServerListState( void );
+
+	bool				StartServerList( void );
+	void				StopServerList( void );
+	void				SortPreviousKey( void );
+	void				SortNextKey( void );
+	void				SortServerList( void );
+#ifdef __PLAT_XBOX__
+	bool				ServerAlreadyInList( char* name, XNADDR* xbox_addr );
+#else
+	bool				ServerAlreadyInList( char* name, int ip );
+#endif
+	void				RefreshServerList( bool force_refresh = false );
+	
+	void				SetServerFocus( ServerInfo* server );
+	void				ClearServerFocus( void );
+	ServerInfo*			GetServerFocus( void );
+	int					NumServersListed( void );
+	void				AddServerToMenu( ServerInfo* server, int index );
+	
+	void				ClearServerList( bool refocus = true );
+	void				FreeServerList( void );
+	ServerInfo*			GetServerInfo( uint32 id );
+
+	
+
+	void				SendChatMessage( char* message );
+
+	void				SetJoinState( JoinState state );
+	JoinState			GetJoinState( void );
+
+	void				SetObserverCommand( ObserverCommand command );
+	ObserverCommand		GetObserverCommand( void );
+
+	bool				ConnectToInternet( uint32 success_script, uint32 failure_script );
+	void				DisconnectFromInternet( uint32 callback_script = 0 );
+	void				RemoveModemStateTask( void );
+	void				CancelConnectToInternet( void );
+
+	void				RandomizeSkaterStartingPoints( void );
+	void				SetSkaterStartingPoints( int* start_points );
+	int					GetSkaterStartingPoint( int skater_id );
+
+	void				LaunchQueuedScripts( void );
+
+	Obj::CCrown*		GetCrown( void );
+	void				SetCrownSpawnPoint( char spawn_point ); 
+	void				CleanupObjects( void );
+
+	void				SendEndOfRunMessage( void );
+	bool				HaveSentEndOfRunMessage(  void );
+	bool				GameIsOver( void );
+	void				MarkGameOver( void );
+	void				ClearGameOver( void );
+
+	char*				GetNetThreadStack( void );
+	void				SetNetThreadId( int net_thread_id );
+	int					GetNetThreadId( void );
+
+	bool				HasCheatingOccurred( void );
+	void				ClearCheating( void );
+	void				CheatingOccured( void );
+
+	bool				UsingCreatedGoals( void );
+	void				LoadGoals( uint32 level );
+	uint32				GetGoalsLevel( void );
+	char*				GetGoalsData( void );
+	int					GetGoalsDataSize( void );
+	void				SetGoalsData( char* data, uint32 level, int size );
+
+	LobbyMan*			mpLobbyMan;
+	ContentMan*			mpContentMan;
+#ifdef __PLAT_XBOX__
+	bool				m_XboxKeyRegistered;
+	XNKID				m_XboxKeyId;	
+	class AuthMan		*mpAuthMan;
+	class BuddyMan		*mpBuddyMan;
+	class VoiceMan		*mpVoiceMan;
+#else
+	BuddyMan* 			mpBuddyMan;
+	StatsMan*			mpStatsMan;
+#endif
+
+private:
+	Manager( void );
+	~Manager( void );
+	
+	void				free_all_players( void );
+	void				free_all_pending_players( void );
+	bool				ok_to_join( int& reason, MsgConnectInfo* connect_msg, Net::Conn* conn );
+
+	Net::Server*	m_server;
+	Net::Client*	m_client[vMAX_LOCAL_CLIENTS];
+	Net::Client* 	m_match_client;
+    
+	uint32			m_gamemode_id;
+	uint32 			m_level_id;
+	unsigned char	m_game_id;
+	unsigned char	m_last_end_of_run_game_id;
+	bool			m_game_over;
+	bool			m_cheating_occurred;
+
+	JoinMode		m_join_mode;
+	HostMode		m_host_mode;
+	
+	bool			m_received_final_scores;
+
+	bool			m_metrics_on;
+	bool			m_scores_on;
+	bool			m_draw_player_names;
+
+	int				m_skater_starting_points[Mdl::Skate::vMAX_SKATERS];
+	int				m_crown_spawn_point;
+
+	Obj::CCrown* 	m_crown;
+
+	static	Tsk::Task< Manager >::Code	s_modem_state_code;
+	static	Tsk::Task< Manager >::Code 	s_timeout_connections_code;
+	static	Tsk::Task< Manager >::Code 	s_render_metrics_code;
+	static	Tsk::Task< Manager >::Code 	s_render_scores_code;
+	static	Tsk::Task< Manager >::Code 	s_server_add_new_players_code;
+	static	Tsk::Task< Manager >::Code 	s_client_add_new_players_code;
+	static 	Tsk::Task< Manager >::Code	s_enter_chat_code;
+	static	Tsk::Task< Manager >::Code	s_join_timeout_code;
+	static 	Tsk::Task< Manager >::Code	s_server_list_state_code;
+	static 	Tsk::Task< Manager >::Code	s_join_state_code;
+	static	Tsk::Task< Manager >::Code	s_start_network_game_code;
+	static	Tsk::Task< Manager >::Code	s_change_level_code;
+    static  Tsk::Task< Manager >::Code	s_auto_refresh_code;
+	static  Tsk::Task< Manager >::Code	s_auto_server_code;
+	static	Tsk::Task< Manager >::Code	s_ctf_logic_code;
+	static Net::MsgHandlerCode			s_handle_disconn_request;
+	static Net::MsgHandlerCode			s_handle_find_server;
+	static Net::MsgHandlerCode			s_handle_connection;
+	static Net::MsgHandlerCode			s_handle_join_request;
+	static Net::MsgHandlerCode			s_handle_join_accepted;
+	static Net::MsgHandlerCode			s_handle_wait_n_seconds;
+	static Net::MsgHandlerCode			s_handle_new_player;
+	static Net::MsgHandlerCode			s_handle_game_info;
+	static Net::MsgHandlerCode			s_handle_kill_flags;
+	static Net::MsgHandlerCode			s_handle_select_goals;
+	static Net::MsgHandlerCode			s_handle_change_level;
+	static Net::MsgHandlerCode			s_handle_request_level;
+	static Net::MsgHandlerCode			s_handle_new_level;
+	static Net::MsgHandlerCode			s_handle_level_data;
+	static Net::MsgHandlerCode			s_handle_rail_data;
+	static Net::MsgHandlerCode			s_handle_goals_data;
+	static Net::MsgHandlerCode			s_handle_client_proceed;
+	static Net::MsgHandlerCode			s_handle_join_refused;
+	static Net::MsgHandlerCode			s_handle_join_proceed;
+	static Net::MsgHandlerCode			s_handle_observe;
+	static Net::MsgHandlerCode			s_handle_server_response;
+	static Net::MsgHandlerCode			s_handle_observe_proceed;
+	static Net::MsgHandlerCode			s_handle_observe_refused;
+	static Net::MsgHandlerCode			s_handle_ready_query;
+	static Net::MsgHandlerCode			s_handle_ready_response;
+	static Net::MsgHandlerCode			s_handle_player_info_ack_req;
+	static Net::MsgHandlerCode			s_handle_player_info_ack;
+	static Net::MsgHandlerCode			s_handle_new_king;
+	static Net::MsgHandlerCode			s_handle_stole_flag;
+	static Net::MsgHandlerCode			s_handle_took_flag;
+	static Net::MsgHandlerCode			s_handle_captured_flag;
+	static Net::MsgHandlerCode			s_handle_retrieved_flag;
+	static Net::MsgHandlerCode			s_handle_player_restarted;
+	static Net::MsgHandlerCode			s_handle_dropped_crown;
+	static Net::MsgHandlerCode			s_handle_dropped_flag;
+	static Net::MsgHandlerCode			s_handle_run_ended;
+	static Net::MsgHandlerCode			s_handle_game_over;
+	static Net::MsgHandlerCode			s_handle_end_game;
+	static Net::MsgHandlerCode			s_handle_object_update;
+	static Net::MsgHandlerCode			s_handle_panel_message;
+	static Net::MsgHandlerCode			s_handle_auto_server_notification;
+	static Net::MsgHandlerCode			s_handle_fcfs_assignment;
+	static Net::MsgHandlerCode			s_handle_fcfs_request;
+	static Net::MsgHandlerCode			s_handle_team_change_request;
+	static Net::MsgHandlerCode			s_handle_team_change;
+	static Net::MsgHandlerCode			s_handle_set_num_teams;
+	static Net::MsgHandlerCode			s_handle_chat;
+	static Net::MsgHandlerCode			s_handle_beat_goal;
+	static Net::MsgHandlerCode			s_handle_beat_goal_relay;
+	static Net::MsgHandlerCode			s_handle_started_goal;
+	static Net::MsgHandlerCode			s_handle_started_goal_relay;
+	static Net::MsgHandlerCode			s_handle_toggle_proset;
+	static Net::MsgHandlerCode			s_handle_challenge;
+	
+	static Net::MsgHandlerCode			s_handle_challenge_response;
+	static Net::MsgHandlerCode			s_handle_cheat_checksum_request;
+	static Net::MsgHandlerCode			s_handle_cheat_checksum_response;
+	static Net::MsgHandlerCode			s_handle_level_data_request;
+
+	Tsk::Task< Manager >	*m_timeout_connections_task;
+	Tsk::Task< Manager >	*m_render_metrics_task;
+	Tsk::Task< Manager >	*m_render_scores_task;
+	Tsk::Task< Manager >	*m_server_add_new_players_task;
+	Tsk::Task< Manager >	*m_client_add_new_players_task;
+	Tsk::Task< Manager >	*m_enter_chat_task;
+	Tsk::Task< Manager >	*m_modem_state_task;
+	Tsk::Task< Manager >	*m_join_timeout_task;
+	Tsk::Task< Manager > 	*m_server_list_state_task;
+	Tsk::Task< Manager > 	*m_join_state_task;
+	Tsk::Task< Manager > 	*m_start_network_game_task;
+	Tsk::Task< Manager > 	*m_change_level_task;
+    Tsk::Task< Manager >	*m_auto_refresh_task;
+	Tsk::Task< Manager >	*m_auto_server_task;
+	Tsk::Task< Manager >	*m_ctf_logic_task;
+	
+	static	Inp::Handler< Manager >::Code 	s_observer_input_logic_code;      
+	Inp::Handler< Manager >*	m_observer_input_handler;
+	ObserverCommand			m_observer_command;
+
+
+	Prefs::Preferences		m_network_preferences;
+	// Ken: Taunt preferences used to be part of the network preferences, but then they were 
+	// required to be saved out with the career file when saving to mem card, rather than with
+	// the net settings (See TT6068) so I separated them out into a separate set of preferences.
+	Prefs::Preferences		m_taunt_preferences;
+    
+	Lst::Head< PlayerInfo >		m_players;
+	Lst::Head< NewPlayerInfo >	m_new_players;
+	Lst::Head< TriggerEvent >	m_trigger_events;
+	Lst::Head< ServerInfo > 	m_servers;
+    Lst::Head< ServerInfo > 	m_temp_servers;
+	int							m_sort_key;
+	uint32						m_conn_refused_reason;
+	
+	//						These are currently only for clients since servers are currently
+	//						the only ones sending out ready queries. Clients keep track of the latest
+	//						received queries in these members
+	int						m_latest_ready_query;
+	bool					m_ready_to_play;
+	int						m_last_score_update;
+	Flags< int >			m_flags;
+	Flags< char >			m_proset_flags;
+	JoinState				m_join_state;
+	int						m_join_ip;
+	int						m_join_private_ip;
+	int						m_join_port;
+	int						m_last_modem_state;
+	Tmr::Time				m_join_start_time;
+	Tmr::Time				m_lobby_start_time;
+	bool					m_game_pending;
+	bool					m_waiting_for_game_to_start;
+
+	ServerListState 		m_server_list_state;
+	ServerListState 		m_next_server_list_state;
+	Tmr::Time				m_server_list_wait_time;
+
+	uint32					m_connection_success_script;
+	uint32					m_connection_failure_script;
+
+	PlayerInfo*				m_cam_player;	// the player that I'm watching with my camera
+	PlayerInfo*				m_leader;
+	int						m_leading_team;
+
+	char					m_goals_data[vMAX_GOAL_SIZE];
+	int						m_goals_data_size;
+	uint32					m_goals_level;
+
+#	ifndef __PLAT_XBOX__
+	char					m_net_thread_stack[ vNET_THREAD_STACK_SIZE ]	__attribute__ ((aligned(16)));
+#	else
+#	pragma pack( 16 )
+	char					m_net_thread_stack[ vNET_THREAD_STACK_SIZE ];
+#	pragma pack()
+#	endif // __PLAT_XBOX__
+	int						m_net_thread_id;
+
+	char					m_master_servers[vMAX_MASTER_SERVERS][16];
+	
+#ifdef __PLAT_NGPS__
+	// Gamespy members
+public:
+	void					RemoveMessageOfTheDay( void );
+	static bool				ScriptLoadNetConfigs(Script::CStruct *pParams, Script::CScript *pScript);
+	static bool				ScriptNoNetConfigFiles(Script::CStruct *pParams, Script::CScript *pScript);
+	static bool				ScriptFillNetConfigList(Script::CStruct *pParams, Script::CScript *pScript);
+	static bool				ScriptChooseNetConfig(Script::CStruct *pParams, Script::CScript *pScript);
+	static bool 			ScriptFillPlayerListMenu( Script::CStruct* pParams, Script::CScript* pScript );
+	static bool 			ScriptPostGame( Script::CStruct* pParams, Script::CScript* pScript );
+	static bool 			ScriptAuthenticateClient( Script::CStruct* pParams, Script::CScript* pScript );
+	static bool 			ScriptWriteDNASBinary( Script::CStruct* pParams, Script::CScript* pScript );
+	
+
+private:
+	char					m_motd[1024];	// Message of the day text
+	GHTTPRequest 			m_ghttp_request;
+	Tmr::Time				m_ghttp_start_time;
+	bool					m_got_motd;
+	//ezNetCnfCombination_t*	mp_combinations[6];
+	ezNetCnfCombinationList_t m_combination_list;
+	sceNetcnfifData_t		m_env_data[6];
+    
+	
+	static void	s_create_game_callback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, 
+											void * param );
+	static void s_server_list_callback( PEER peer, PEERBool success, const char * name, SBServer server, PEERBool staging, int msg, int progress, void * param );
+	static GHTTPBool MotdComplete( GHTTPRequest request, GHTTPResult result, char* buffer, int buffer_len, 
+						  void* param );
+	static GHTTPBool TrackUsageComplete( GHTTPRequest request, GHTTPResult result, char* buffer, int buffer_len, 
+						  void* param );
+
+	static	void			s_threaded_get_message_of_the_day( Manager* gamenet_man );
+	static	void			s_threaded_track_usage( Manager* gamenet_man );
+#endif
+
+	DeclareSingletonClass( Manager );
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+class ScoreRank : public Lst::Node< ScoreRank >
+{
+public:
+	ScoreRank( void )
+		: Lst::Node< ScoreRank > ( this ) {}
+
+	char	m_Name[32];
+	bool	m_IsKing;
+	bool	m_HasFlag;
+	int		m_WhichFlags;
+	int		m_ColorIndex;
+	int		m_TotalScore;
+	int		m_TeamId;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_GAMENET_H
+
+
diff --git a/Code/Sk/GameNet/Lobby.cpp b/Code/Sk/GameNet/Lobby.cpp
new file mode 100644
index 0000000..abe6971
--- /dev/null
+++ b/Code/Sk/GameNet/Lobby.cpp
@@ -0,0 +1,2410 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		Lobby.cpp												**
+**																			**
+**	Created by:		04/4/02	-	spg											**
+**																			**
+**	Description:	Gamespy peer lobby implementation						**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include                                        
+#include 
+
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vPOST_SERVER_TIMEOUT		Tmr::Seconds( 90 )
+#define vPOST_SERVER_RETRY_TIME		Tmr::Seconds( 5 )
+#define vGAME_VERSION				5
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+enum
+{
+	vGAMESPY_NO_DATA,
+	vGAMESPY_QUERY_DATA,
+	vGAMESPY_NAT_DATA,
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static Tmr::Time	s_last_refresh_time = 0;
+static Tmr::Time	s_time_of_connection = 0;
+static	int		s_gamespy_data_type = vGAMESPY_NO_DATA;
+static	char	s_gamespy_parse_data[2048];
+static	int		s_gamespy_parse_data_len = 0;
+static	struct sockaddr s_gamespy_sender;
+static	bool	s_got_gamespy_callback;
+static	Tmr::Time	s_game_start_time;
+static	Tmr::Time	s_last_post_time;
+static	bool		s_notified_user_not_connected;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::add_lobby_to_menu( LobbyInfo* lobby, int index )
+{
+	Script::CStruct* p_item_params;
+	char lobby_title[64];
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	if( lobby->m_Full )
+	{
+		sprintf( lobby_title, "%s - %d/%d (%s)", lobby->m_Name, lobby->m_NumServers, lobby->m_MaxServers, Script::GetString( "net_lobby_full" ));
+	}
+	else
+	{
+		sprintf( lobby_title, "%s - %d/%d", lobby->m_Name, lobby->m_NumServers, lobby->m_MaxServers );
+	}
+
+	p_item_params = new Script::CStruct;	
+	p_item_params->AddString( "text", lobby_title );
+	if( lobby->m_Full || lobby->m_OffLimits )
+	{
+		p_item_params->AddChecksum( NONAME, CRCD( 0x1d944426, "not_focusable" ) );
+		p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("selected_lobby_full"));
+	}
+	else
+	{
+		p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("choose_selected_lobby"));
+		p_item_params->AddChecksum( "focus_script",Script::GenerateCRC("regions_menu_set_focus"));
+	}
+
+	// create the parameters that are passed to the X script
+	Script::CStruct *p_script_params= new Script::CStruct;
+	p_script_params->AddInteger( "index", index );	
+	
+	if( !( lobby->m_Full || lobby->m_OffLimits ))
+	{
+		p_item_params->AddStructure("pad_choose_params",p_script_params);			
+	}
+
+	Script::RunScript("regions_menu_add_item",p_item_params);
+	delete p_item_params;
+	delete p_script_params;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::add_player_to_menu( LobbyPlayerInfo* player )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Script::CStruct *p_script_params;
+	Script::CStruct *p_unfocus_params;
+	Script::CStruct* p_item_params;
+	Script::CArray* p_colors;
+	int rank;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	p_script_params = new Script::CStruct;
+	p_item_params = new Script::CStruct;	
+	p_colors = new Script::CArray;
+	p_unfocus_params = new Script::CStruct;
+
+	p_item_params->AddString( "text", PlayerName( player->m_Name ));
+	p_colors->SetSizeAndType( 4, ESYMBOLTYPE_INTEGER );
+
+	if( ( player->m_Profile ) && 
+		( gamenet_man->mpBuddyMan->IsLoggedIn()))
+	{           
+		//Dbg_Printf( "Adding Player %s of %d to list\n", player->m_Name, player->m_Profile );
+		
+		// If it's me (or a buddy), color it differently and don't let me add myself to my own buddy list
+		if( player->m_Profile == gamenet_man->mpBuddyMan->GetProfile() || gamenet_man->mpBuddyMan->IsAlreadyMyBuddy( player->m_Profile ))
+		{
+			if( gamenet_man->mpBuddyMan->GetProfile() == player->m_Profile )
+			{
+				p_colors->SetInteger( 0, 0 );
+				p_colors->SetInteger( 1, 0 );
+				p_colors->SetInteger( 2, 128 );
+				p_colors->SetInteger( 3, 128 );
+
+				p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("cant_add_self_to_buddy_prompt"));
+			}
+			else
+			{
+				p_colors->SetInteger( 0, 128 );
+				p_colors->SetInteger( 1, 128 );
+				p_colors->SetInteger( 2, 0 );
+				p_colors->SetInteger( 3, 128 );
+
+				p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("already_buddy_prompt"));
+			}
+		}
+		else
+		{
+			p_colors->SetInteger( 0, 0 );
+			p_colors->SetInteger( 1, 128 );
+			p_colors->SetInteger( 2, 0 );
+			p_colors->SetInteger( 3, 128 );
+
+			p_script_params->AddInteger( "profile", player->m_Profile );	
+			p_script_params->AddString( "nick", player->m_Name );	
+			p_script_params->AddString( "net_name", PlayerName( player->m_Name ));  
+			p_item_params->AddStructure( "pad_choose_params", p_script_params );
+
+			if( gamenet_man->mpBuddyMan->NumBuddies() < vMAX_BUDDIES )
+			{
+				p_item_params->AddChecksum( "pad_choose_script", Script::GenerateCRC( "add_buddy_prompt" ));
+			}
+			else
+			{
+				p_item_params->AddChecksum( "pad_choose_script", Script::GenerateCRC( "cant_add_buddy_prompt_3" ));
+			}
+		}
+	}
+	else
+	{
+		//Dbg_Printf( "Adding Player %s, no profile\n", player->m_Name );
+
+		p_colors->SetInteger( 0, 128 );
+		p_colors->SetInteger( 1, 128 );
+		p_colors->SetInteger( 2, 128 );
+		p_colors->SetInteger( 3, 128 );
+		
+		if( gamenet_man->mpBuddyMan->IsLoggedIn())
+		{
+			p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("cant_add_buddy_prompt_1"));
+		}
+		else
+		{
+			p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("cant_add_buddy_prompt_2"));
+		}
+		
+	}
+	
+	rank = (int) (((float) player->m_Rating / (float) vMAX_RATING ) * (float) vMAX_RANK );
+	p_item_params->AddInteger( "rank", rank );
+	p_item_params->AddChecksum( "id", Script::GenerateCRC( player->m_Name ));
+	p_item_params->AddChecksum( "parent_menu_id", Script::GenerateCRC( "lobby_player_list_menu" ));
+	p_item_params->AddFloat( "scale", 1.1f );
+
+	p_unfocus_params->AddArray( "rgba", p_colors );
+	p_item_params->AddArray( "rgba", p_colors );
+	p_item_params->AddStructure( "unfocus_params", p_unfocus_params );
+	//Script::RunScript("make_text_sub_menu_item", p_item_params );
+	
+	Script::RunScript("player_list_add_item", p_item_params );
+	
+	delete p_colors;
+	delete p_script_params;
+	delete p_item_params;
+	delete p_unfocus_params;
+	
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::refresh_player_list( void )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	clear_player_list();
+	Script::RunScript( "destroy_lobby_user_list_children" );
+	peerEnumPlayers( m_peer, GroupRoom, s_enum_players_callback, this );
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::clear_player_list( void )
+{
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player, *next;
+
+	for( player = sh.FirstItem( m_players ); player; player = next )
+	{
+		next = sh.NextItem();
+		delete player;
+	}
+
+	Script::RunScript( "update_lobby_player_list" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::fill_player_list( void )
+{
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+
+	Script::RunScript( "destroy_lobby_user_list_children" );
+
+    for( player = sh.FirstItem( m_players ); player; player = sh.NextItem())
+	{
+		if( player->m_GotInfo )
+		{
+			add_player_to_menu( player );
+		}
+	}
+	
+	Script::RunScript( "update_lobby_player_list" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::add_buddy_to_menu( LobbyPlayerInfo* player )
+{
+	Script::CStruct* p_item_params;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	//Dbg_Printf( "Adding %s of %d to the list\n", PlayerName( player->m_Name ), player->m_Profile );
+	p_item_params = new Script::CStruct;	
+	p_item_params->AddString( "text", PlayerName( player->m_Name ));
+	p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("lobby_add_buddy"));
+	p_item_params->AddChecksum( "parent_menu_id", Script::GenerateCRC( "lobby_buddy_list_menu" ));
+
+	// create the parameters that are passed to the X script
+	Script::CStruct *p_script_params= new Script::CStruct;
+	p_script_params->AddInteger( "profile", player->m_Profile );	
+	p_script_params->AddString( "nick", player->m_Name );	
+	p_script_params->AddString( "net_name", PlayerName( player->m_Name ));  
+	p_item_params->AddStructure( "pad_choose_params", p_script_params );
+
+	Script::RunScript("make_text_sub_menu_item",p_item_params);
+
+	delete p_item_params;
+	delete p_script_params;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::fill_prospective_buddy_list( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+
+	Script::RunScript( "destroy_lobby_buddy_list_children" );
+
+	for( player = sh.FirstItem( m_players ); player; player = sh.NextItem())
+	{
+		if( player->m_Profile == gamenet_man->mpBuddyMan->GetProfile())
+		{
+			continue;
+		}
+
+		if(	( player->m_Profile > 0 ) && 
+			( gamenet_man->mpBuddyMan->IsAlreadyMyBuddy( player->m_Profile ) == false ))
+		{
+			add_buddy_to_menu( player );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::s_threaded_peer_connect( LobbyMan* lobby_man )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+	Prefs::Preferences* prefs;
+	char lobby_name[128];
+	
+	prefs = gamenet_man->GetNetworkPreferences();
+    const char* network_name = prefs->GetPreferenceString( Script::GenerateCRC( "network_id" ), Script::GenerateCRC( "ui_string" ));
+    
+	// Use our unique Gamespy profile ID as our "unique number", if one is available
+	if( gamenet_man->mpBuddyMan->IsLoggedIn())
+	{
+		sprintf( lobby_name, "a%d_%s", gamenet_man->mpBuddyMan->GetProfile(), network_name );
+	}
+	else
+	{
+		sprintf( lobby_name, "a%d_%s", (int) Tmr::GetTime(), network_name );
+	}
+
+	lobby_man->m_connection_in_progress = true;
+    peerConnect( lobby_man->m_peer, lobby_name, gamenet_man->mpBuddyMan->GetProfile(), s_nick_error_callback, s_connect_callback, lobby_man, false );
+
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_disconnected_callback( PEER peer, const char * reason, void * param )
+{
+	LobbyMan* man = (LobbyMan*) param;
+	Tmr::Time elapsed_time;
+
+	elapsed_time = Tmr::GetTime() - s_time_of_connection;
+	printf( "Disconnected at %d after %d ms. Reason: %s\n", (int) Tmr::GetTime(), (int) elapsed_time, reason );
+
+	if( man->m_connection_in_progress )
+	{
+		printf( "******* Connect Callback FAILED!\n" );
+		Script::RunScript( "create_gamespy_connection_failure_dialog" );
+		man->m_connection_in_progress = false;
+	}
+	else if( man->m_expecting_disconnect == false )
+	{
+		printf( "Wasn't expecting discon.  Attempting to stop hosting game\n" );
+		Script::RunScript( "lost_connection_to_gamespy" );
+	}
+	else
+	{
+		printf( "Was expecting discon. Ignore\n" );
+	}
+
+	man->m_expecting_disconnect = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_room_message_callback( PEER peer, RoomType roomType, const char * nick, const char * message,
+										MessageType messageType, void * param )
+{
+	Script::CStruct* p_params;
+	char msg[1024];
+
+	LobbyMan* lobby_man = (LobbyMan*) param;
+
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	sprintf( msg, "%s: %s", lobby_man->PlayerName((char*) nick ), message );
+	p_params = new Script::CStruct;	
+	p_params->AddString( "text", msg );
+	Script::RunScript("create_console_message", p_params );
+	delete p_params;
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_player_message_callback( PEER peer, const char * nick, const char * message, MessageType messageType,
+												void * param )
+{
+	//printf( "Player Message. Nick: %s Message: %s\n", nick, message );
+	//LobbyMan* man = (LobbyMan*) param;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_player_proile_id_callback( PEER peer, PEERBool success,  const char * nick, int profileID, void * param )
+{
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+	LobbyMan* lobby_man = (LobbyMan*) param;
+
+	//Dbg_Printf( "Got a player profile id callback on %s : %d\n", nick, profileID );
+	for( player = sh.FirstItem( lobby_man->m_players ); player; player = sh.NextItem())
+	{
+		if( stricmp( player->m_Name, nick ) == 0 )
+		{
+			player->m_Profile = profileID;
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_player_joined_callback( PEER peer, RoomType roomType, const char * nick, void * param )
+{
+	Script::CStruct* p_params;
+	char msg[1024];
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+	
+	LobbyMan* lobby_man = (LobbyMan*) param;
+
+	//printf( "%s joined the room\n", nick );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	for( player = sh.FirstItem( lobby_man->m_players ); player; player = sh.NextItem())
+	{
+		if( stricmp( player->m_Name, nick ) == 0 )
+		{
+			peerGetPlayerInfoNoWait( peer, nick, NULL, &player->m_Profile );
+			Mem::Manager::sHandle().PopContext();
+			player->m_GotInfo = true;
+			return;	// already have this player
+		}
+	}
+
+	player = new LobbyPlayerInfo;
+	strcpy( player->m_Name, nick );
+	peerGetPlayerInfoNoWait( peer, nick, NULL, &player->m_Profile );
+	player->m_GotInfo = true;
+
+	lobby_man->m_players.AddToTail( player );
+
+	Script::RunScript( "prepare_lobby_user_list_for_new_children" );
+	lobby_man->add_player_to_menu( player );
+	Script::RunScript( "update_lobby_player_list" );
+		
+	sprintf( msg, "%s %s", lobby_man->PlayerName((char*) nick ), Script::GetString( "lobby_status_joined" ));
+
+	p_params = new Script::CStruct;	
+	p_params->AddString( "text", msg );
+    p_params->AddChecksum( "join", 0 );
+	Script::RunScript("create_console_message", p_params );
+	delete p_params;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_player_info_callback( PEER peer, RoomType roomType, const char * nick, unsigned int IP, int profileID,  
+                                                void * param )
+{
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+	LobbyMan* lobby_man = (LobbyMan*) param;
+	
+	if( nick == NULL )
+	{
+		return;
+	}
+	
+	//Dbg_Printf( "Got info on %s : %d\n", nick, profileID );
+
+	for( player = sh.FirstItem( lobby_man->m_players ); player; player = sh.NextItem())
+	{
+		if( stricmp( player->m_Name, nick ) == 0 )
+		{
+			// If we don't already have this player's profile
+			if( player->m_Profile == 0 )
+			{
+				player->m_Profile = profileID;
+			}
+			player->m_GotInfo = true;
+			lobby_man->add_player_to_menu( player );
+            Script::RunScript( "update_lobby_player_list" );
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_player_left_callback( PEER peer, RoomType roomType, const char * nick, const char * reason, 
+												void * param )
+{
+	Script::CStruct* p_params;
+	char msg[1024];
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+	LobbyMan* lobby_man = (LobbyMan*) param;
+	
+	//printf( "%s left the room\n", nick );
+
+	for( player = sh.FirstItem( lobby_man->m_players ); player; player = sh.NextItem())
+	{
+		if( stricmp( player->m_Name, nick ) == 0 )
+		{
+			p_params = new Script::CStruct;
+
+			p_params->AddChecksum( "user_id", Script::GenerateCRC( nick ));
+			Script::RunScript( "destroy_lobby_user", p_params );
+
+			delete player;
+			delete p_params;
+
+			Script::RunScript( "update_lobby_player_list" );
+			break;
+		}
+	}
+
+	sprintf( msg, "%s %s", lobby_man->PlayerName((char*) nick ), Script::GetString( "lobby_status_left" ));
+
+	p_params = new Script::CStruct;	
+	p_params->AddString( "text", msg );
+    p_params->AddChecksum( "left", 0 );
+	Script::RunScript("create_console_message", p_params );
+	delete p_params;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	LobbyMan::s_get_room_keys_callback( PEER peer, PEERBool success, RoomType roomType, 
+											const char * nick, int num, char ** keys, char ** values, 
+											void * param )
+{
+	int i;
+	LobbyMan* lobby_man = (LobbyMan*) param;
+
+	for( i = 0; i < num; i++ )
+	{
+		//Dbg_Printf( "Got Key: %s\n", keys[i] );
+	}
+	if( num > 0 )
+	{
+		int rank;
+		Script::CStruct* p_item_params;
+		Lst::Search< LobbyPlayerInfo > sh;
+		LobbyPlayerInfo* player;
+
+		if(( keys[0] != NULL )&& stricmp( keys[0], "b_rating" ) == 0 )
+		{
+			for( player = sh.FirstItem( lobby_man->m_players ); player; player = sh.NextItem())
+			{
+				if( stricmp( player->m_Name, nick ) == 0 )
+				{
+					Dbg_Printf( "Got rating %d for %s\n", player->m_Rating, nick );
+					player->m_Rating = atoi( values[0] );
+					rank = (int) (((float) player->m_Rating / (float) vMAX_RATING ) * (float) vMAX_RANK );
+			
+					p_item_params = new Script::CStruct;
+					p_item_params->AddChecksum( "id", Script::GenerateCRC( player->m_Name ));
+					p_item_params->AddInteger( "rank", rank );
+					Script::RunScript( "player_list_update_rank", p_item_params );
+					delete p_item_params;
+				}
+			}
+		}
+	}
+	
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_enum_players_callback( PEER peer, PEERBool success, RoomType roomType, int index, const char * nick,  // The nick of the player.
+												int flags, void * param )
+{
+	LobbyMan* lobby_man = (LobbyMan*) param;
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+
+	if( success == false )
+	{
+		//printf( "Failed to enum players\n" );
+		return;
+	}
+
+	//Dbg_Printf( "Enumerating players\n" );
+	if( index == -1 )
+	{
+		// List is finished. 
+		//lobby_man->fill_player_list();
+        return;
+	}
+	
+	for( player = sh.FirstItem( lobby_man->m_players ); player; player = sh.NextItem())
+	{
+		if( stricmp( player->m_Name, nick ) == 0 )
+		{
+			return;	// already have this player in our list
+		}
+	}
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	player = new LobbyPlayerInfo;
+	strcpy( player->m_Name, nick );
+	//Dbg_Printf( "Before getting player info on %s : %d\n", nick, player->m_Profile );
+	if( peerGetPlayerInfoNoWait( peer, nick, NULL, &player->m_Profile ))
+	{
+		//Dbg_Printf( "***** Got Player Info for %s : %d\n", nick, player->m_Profile );
+	}
+	else
+	{
+		//Dbg_Printf( "***** Failed to Get Player Info for %s : %d\n", nick, player->m_Profile );
+	}
+
+	lobby_man->m_players.AddToTail( player );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_new_player_list_callback( PEER peer, RoomType roomType, void * param )
+{
+	LobbyMan* lobby_man = (LobbyMan*) param;
+    
+	//printf( "New Player List\n" );
+
+	lobby_man->refresh_player_list();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_room_key_callback( PEER peer, RoomType roomType, const char * nick, 
+									const char * key, const char * value, void * param )
+{
+	LobbyMan* lobby_man = (LobbyMan*) param;
+	Lst::Search< LobbyPlayerInfo > sh;
+	LobbyPlayerInfo* player;
+
+	for( player = sh.FirstItem( lobby_man->m_players ); player; player = sh.NextItem())
+	{
+		if( stricmp( player->m_Name, nick ) == 0 )
+		{
+			int rank;
+			Script::CStruct* p_item_params;
+
+			player->m_Rating = atoi( value );
+			rank = (int) (((float) player->m_Rating / (float) vMAX_RATING ) * (float) vMAX_RANK );
+
+			Dbg_Printf( "Got room key for %s. Value: %d\n", nick, player->m_Rating );
+
+			p_item_params = new Script::CStruct;
+			p_item_params->AddChecksum( "id", Script::GenerateCRC( player->m_Name ));
+			p_item_params->AddInteger( "rank", rank );
+			Script::RunScript( "player_list_update_rank", p_item_params );
+			delete p_item_params;
+			break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_nat_negotiate_callback( PEER peer, int cookie, void * param )
+{
+    NegotiateError err;
+	Manager * gamenet_man = Manager::Instance();
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	Net::Server* server;
+
+	if( gamenet_man->mpLobbyMan->m_nat_count.InUse())
+	{
+		Dbg_Printf( "******* Ignoring new nat negotiation request\n" );
+		return;
+	}
+
+	server = gamenet_man->GetServer();
+	Dbg_Assert( server );
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	Dbg_Printf( "******* Beginning new nat negotiation\n" );
+	err = NNBeginNegotiationWithSocket( server->GetSocket(), cookie, 0, s_nat_negotiate_progress,
+										s_nat_negotiate_complete, (void*) cookie );
+	if( err != ne_noerror )
+	{
+		Dbg_Printf( "******* NAT Neg Error: %d\n", err );
+		NNCancel( cookie );
+		NNThink();
+		Mem::Manager::sHandle().PopContext();
+		return;
+	}
+	
+	if( gamenet_man->mpLobbyMan->m_nat_count.InUse() == false )
+	{
+		mlp_manager->AddLogicTask( *gamenet_man->mpLobbyMan->m_nat_negotiate_task );
+	}
+
+	gamenet_man->mpLobbyMan->m_nat_count.Acquire();
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void LobbyMan::s_server_key_callback( PEER peer, int key, qr2_buffer_t buffer, void * param )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	const char* server_name;
+	Prefs::Preferences* pPreferences;
+	Script::CScriptStructure* pStructure;
+    char* password;
+	
+	//Dbg_Printf( "***** SERVER KEY CALLBACK\n" );
+	pPreferences = gamenet_man->GetNetworkPreferences();
+
+    switch(key)
+	{
+		case HOSTNAME_KEY:
+			pStructure = pPreferences->GetPreference( Script::GenerateCRC("server_name") );
+			pStructure->GetText( "ui_string", &server_name, true );
+			//Dbg_Printf( "Server name is %s\n", server_name );
+			qr2_buffer_add(buffer, server_name );
+			break;
+		case GAMENAME_KEY:
+			qr2_buffer_add(buffer, "thps5ps2" );
+			break;
+		case GAMEMODE_KEY:
+			qr2_buffer_add(buffer, gamenet_man->GetGameModeName());
+			break;
+		case HOSTPORT_KEY:
+			qr2_buffer_add_int(buffer, vHOST_PORT );
+			break;
+		case MAPNAME_KEY:
+			qr2_buffer_add(buffer, gamenet_man->GetLevelName());
+			break;
+		case GAMETYPE_KEY:
+			qr2_buffer_add(buffer, gamenet_man->GetGameModeName());
+			break;
+		case TEAMPLAY_KEY:
+			qr2_buffer_add_int(buffer, 0);
+			break;
+		case NUMPLAYERS_KEY:
+			qr2_buffer_add_int(buffer, gamenet_man->GetNumPlayers());
+			break;
+		case MAXPLAYERS_KEY:
+			qr2_buffer_add_int(buffer, gamenet_man->GetMaxPlayers());
+			break;
+		case PASSWORD_KEY:
+			password = gamenet_man->GetPassword();
+			if( password[0] == '\0' )
+			{
+				qr2_buffer_add_int(buffer, 0 );
+			}
+			else
+			{
+				qr2_buffer_add_int(buffer, 1 );
+			}
+			break;
+			
+		case NUMOBSERVERS_KEY:
+			qr2_buffer_add_int(buffer, gamenet_man->GetNumObservers());
+			break;
+		case MAXOBSERVERS_KEY:
+			qr2_buffer_add_int(buffer, gamenet_man->GetMaxObservers());
+			break;
+		case SKILLLEVEL_KEY:
+			qr2_buffer_add_int(buffer, gamenet_man->GetSkillLevel());
+			break;
+		case STARTED_KEY:
+			if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netlobby" ))
+			{
+				qr2_buffer_add_int(buffer, 0 );
+			}
+			else
+			{
+				qr2_buffer_add_int(buffer, 1 );
+			}
+			break;
+		case HOSTED_MODE_KEY:
+			qr2_buffer_add_int(buffer, gamenet_man->GetHostMode());
+			break;
+		case RANKED_KEY:
+			qr2_buffer_add_int(buffer, gamenet_man->mpBuddyMan->IsLoggedIn());
+			break;
+		default:
+			qr2_buffer_add(buffer, "");
+			break;
+	}
+
+	s_got_gamespy_callback = true;
+}
+
+void LobbyMan::s_player_key_callback( PEER peer, int key, int index, qr2_buffer_t buffer, void * param )
+{
+	s_got_gamespy_callback = true;
+
+	//Dbg_Printf( "***** PLAYER KEY %d CALLBACK : for player %d\n", key, index );
+	switch(key)
+	{
+		case RATING__KEY:
+		{
+			GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+			Lst::Search< PlayerInfo > sh;
+			PlayerInfo* player;
+			Lst::Search< NewPlayerInfo > new_sh;
+			NewPlayerInfo* np;
+			int i;
+			
+			i = 0;
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+					player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				if( !player->IsObserving() || player->IsPendingPlayer())
+				{
+					if( i == index )
+					{
+						//printf( "(2) Added %s's rating: %d to list\n", player->m_Name, player->m_Rating );
+						qr2_buffer_add_int(buffer, player->m_Rating );
+						return;
+					}
+					i++;
+				}
+			}
+		
+			for( np = gamenet_man->FirstNewPlayerInfo( new_sh ); np; np = gamenet_man->NextNewPlayerInfo( new_sh ))
+			{
+				// Pending players count, observers don't
+				if(	( np->Flags & PlayerInfo::mPENDING_PLAYER ) ||
+					( np->Flags & PlayerInfo::mJUMPING_IN ) ||
+					!( np->Flags & PlayerInfo::mOBSERVER ))
+				{
+					if( i == index )
+					{
+						//printf( "(2) Added %s's rating: %d to list\n", np->Name, np->Rating );
+						qr2_buffer_add_int(buffer, np->Rating );
+						return;
+					}
+					i++;
+				}                 
+			}
+			
+			//printf( "Added empty string to player list\n" );
+			// If we're here, we don't have a player == index
+            qr2_buffer_add(buffer, "");
+			break;
+		}
+		case PLAYER__KEY:
+		{
+			GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+			Lst::Search< PlayerInfo > sh;
+			PlayerInfo* player;
+			Lst::Search< NewPlayerInfo > new_sh;
+			NewPlayerInfo* np;
+			int i;
+			
+			i = 0;
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+					player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				if( !player->IsObserving() || player->IsPendingPlayer())
+				{
+					if( i == index )
+					{
+						//printf( "Added %s to player list\n", player->m_Name );
+						qr2_buffer_add(buffer, player->m_Name);
+						return;
+					}
+					i++;
+				}
+			}
+		
+			for( np = gamenet_man->FirstNewPlayerInfo( new_sh ); np; np = gamenet_man->NextNewPlayerInfo( new_sh ))
+			{
+				// Pending players count, observers don't
+				if(	( np->Flags & PlayerInfo::mPENDING_PLAYER ) ||
+					( np->Flags & PlayerInfo::mJUMPING_IN ) ||
+					!( np->Flags & PlayerInfo::mOBSERVER ))
+				{
+					if( i == index )
+					{
+						//printf( "(2) Added %s to player list\n", player->m_Name );
+						qr2_buffer_add(buffer, np->Name );
+						return;
+					}
+					i++;
+				}                 
+			}
+			
+			//printf( "Added empty string to player list\n" );
+			// If we're here, we don't have a player == index
+            qr2_buffer_add(buffer, "");
+			break;
+		}
+
+		case PING__KEY:
+			qr2_buffer_add(buffer, "");
+			break;
+	}
+}
+
+void LobbyMan::s_public_address_callback( PEER peer, unsigned int ip, unsigned short port, 
+											   void * param )
+{
+	char location[1024];
+	const char *server_name;
+	Script::CScriptStructure* pStructure;
+	Prefs::Preferences* pPreferences;
+	Manager * gamenet_man = Manager::Instance();
+	BuddyMan* buddy_man;
+	LobbyMan* lobby_man;
+
+	lobby_man = (LobbyMan*) param;
+	buddy_man = gamenet_man->mpBuddyMan;
+
+	Dbg_Printf( "***** Got public address: %s : %d\n", inet_ntoa(*(struct in_addr *) &ip ), port );
+	if( gamenet_man->OnServer() && lobby_man->GetPeer())
+	{
+		gamenet_man->SetJoinPort( port );
+		gamenet_man->SetJoinIP( ip );
+		gamenet_man->SetJoinPrivateIP( peerGetPrivateIP( lobby_man->GetPeer()) );
+		pPreferences = gamenet_man->GetNetworkPreferences();
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("server_name") );
+		pStructure->GetText( "ui_string", &server_name, true );
+	
+		sprintf( location, "%d:%d:%d:%s (%s)", ip, peerGetPrivateIP( lobby_man->GetPeer()), port, server_name, lobby_man->GetLobbyName());
+		if( buddy_man->IsLoggedIn())
+		{
+			buddy_man->SetStatusAndLocation( GP_PLAYING, (char*) Script::GetString( "homie_status_hosting" ), location );
+		}
+	}	
+}
+
+void LobbyMan::s_add_error_callback( PEER peer, qr2_error_t error, char * errorString, void * param )
+{
+	Dbg_Printf( "***** Got an error (%d) adding the server: %s\n", (int) error, errorString );
+}
+
+int LobbyMan::s_key_count_callback( PEER peer, qr2_key_type type, void * param )
+{
+	//Dbg_Printf( "*** GOT KEY COUNT CALLBACK\n" );
+	switch( type )
+	{
+		case key_server:
+		{
+			//Dbg_Printf( "*** Server Key Count Request: 15\n" );
+			return 16;
+		}
+		case key_player:
+		{
+			GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+			//Dbg_Printf( "*** Player Key Count Request: %d\n", gamenet_man->GetNumPlayers() );
+			return gamenet_man->GetNumPlayers();
+		}
+		case key_team:
+		default:
+			return 0;
+	}
+
+	return 0;
+}
+
+void LobbyMan::s_key_list_callback( PEER peer, qr2_key_type type, qr2_keybuffer_t keyBuffer, 
+									void * param )
+{
+	//Dbg_Printf( "***** Got a key list callback\n" );
+	// register the keys we use
+	switch(type)
+	{
+		case key_server:
+			//Dbg_Printf( "***** Got a request for server keys\n" );
+			
+			qr2_keybuffer_add(keyBuffer, GAMEMODE_KEY);
+			qr2_keybuffer_add(keyBuffer, GAMENAME_KEY);
+			qr2_keybuffer_add(keyBuffer, HOSTNAME_KEY);
+			qr2_keybuffer_add(keyBuffer, HOSTPORT_KEY);
+			qr2_keybuffer_add(keyBuffer, MAPNAME_KEY);
+			qr2_keybuffer_add(keyBuffer, GAMETYPE_KEY);
+			qr2_keybuffer_add(keyBuffer, TEAMPLAY_KEY);
+			qr2_keybuffer_add(keyBuffer, NUMPLAYERS_KEY);
+			qr2_keybuffer_add(keyBuffer, MAXPLAYERS_KEY);
+			qr2_keybuffer_add(keyBuffer, PASSWORD_KEY);
+			
+			qr2_keybuffer_add(keyBuffer, NUMOBSERVERS_KEY);
+			qr2_keybuffer_add(keyBuffer, MAXOBSERVERS_KEY);
+			qr2_keybuffer_add(keyBuffer, RANKED_KEY);
+			
+			qr2_keybuffer_add(keyBuffer, SKILLLEVEL_KEY);
+			qr2_keybuffer_add(keyBuffer, STARTED_KEY);
+			qr2_keybuffer_add(keyBuffer, HOSTED_MODE_KEY);
+			break;
+		case key_player:
+			//Dbg_Printf( "***** Got a request for player keys\n" );
+			qr2_keybuffer_add(keyBuffer, PLAYER__KEY);
+			qr2_keybuffer_add(keyBuffer, RATING__KEY);
+			break;
+		case key_team:
+			//Dbg_Printf( "***** Got a request for team keys\n" );
+			// no custom team keys
+			break;
+		default:
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_nick_error_callback( PEER peer, int type, const char* nick, void *param )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	if( type == PEER_IN_USE )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		char new_nick[128];
+		Prefs::Preferences* prefs;
+	
+		prefs = gamenet_man->GetNetworkPreferences();
+		const char* network_name = prefs->GetPreferenceString( Script::GenerateCRC( "network_id" ), Script::GenerateCRC( "ui_string" ));
+        
+		printf( "Nick Error. Nick %s In Use\n", nick );
+		sprintf( new_nick, "a%d_%s", (int) Tmr::GetTime(), network_name );
+		peerRetryWithNick( peer, (const char*) new_nick );
+	}
+	else if( type == PEER_INVALID )
+	{
+		char new_nick[128];
+		
+		printf( "Nick Error. Nick %s is Invalid\n", nick );
+        chatFixNick( new_nick, nick );
+		peerRetryWithNick( peer, (const char*) new_nick );
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_connect_callback( PEER peer, PEERBool success, void* param )
+{
+	LobbyMan* lobby_man = (LobbyMan*) param;
+
+	lobby_man->m_connection_in_progress = false;
+	if( success )
+	{
+		Net::Manager * net_man = Net::Manager::Instance();
+
+		//Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+		//GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		//mlp_man->AddLogicTask( *lobby_man->m_lobby_logic_task );
+		
+		printf( "****** Connect Callback Successful\n" );
+
+		net_man->SetPublicIP( peerGetLocalIP( peer ));
+		CFuncs::ScriptStartLobbyList( NULL, NULL );
+		//gamenet_man->SetServerListState( vSERVER_LIST_STATE_STARTING_LOBBY_LIST );
+		lobby_man->m_expecting_disconnect = false;
+		s_time_of_connection = Tmr::GetTime();
+	}
+	else
+	{
+		printf( "******* Connect Callback FAILED!\n" );
+		Script::RunScript( "create_gamespy_connection_failure_dialog" );
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_group_rooms_callback( PEER peer, PEERBool success, int groupID, SBServer server,  
+									const char * name, int numWaiting, int maxWaiting, int numGames, int numPlaying,
+									void* param )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	LobbyMan* lobby_man = (LobbyMan*) param;
+	// Check for the terminator
+	if( success )
+	{
+		if( groupID == 0 )
+		{   
+			gamenet_man->SetServerListState( vSERVER_LIST_STATE_GOT_LOBBY_LIST );
+		}
+		else
+		{
+			int max_players, official_key;
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+			LobbyInfo* lobby_info;
+			lobby_info = new LobbyInfo;
+			strncpy( lobby_info->m_Name, name, 63 );
+			lobby_info->m_Name[63] = '\0';
+			lobby_info->m_NumServers = numGames;
+			lobby_info->m_GroupId = groupID;
+			lobby_info->m_MaxServers = maxWaiting;
+            max_players = SBServerGetIntValue( server, "maxplayers", 0 );
+			if( numWaiting >= max_players )
+			{
+				lobby_info->m_Full = true;
+			}
+
+			lobby_info->m_MaxRating = SBServerGetIntValue( server, "maxrating", 100000 );
+			lobby_info->m_MinRating = SBServerGetIntValue( server, "minrating", 0 );
+
+			Dbg_Printf( "Got lobby: %s, max: %d min: %d, rating: %d\n", name, lobby_info->m_MaxRating, lobby_info->m_MinRating, gamenet_man->mpStatsMan->GetStats()->GetRating());
+
+			if( gamenet_man->mpStatsMan->GetStats()->GetRating() > lobby_info->m_MaxRating )
+			{
+				lobby_info->m_OffLimits = true;
+			}
+
+			if( gamenet_man->mpStatsMan->GetStats()->GetRating() < lobby_info->m_MinRating )
+			{
+				lobby_info->m_OffLimits = true;
+			}
+
+			official_key = 0;
+			official_key = SBServerGetIntValue( server, "neversoft", 0 );
+			if( official_key )
+			{
+				lobby_info->m_Official = true;
+			}
+            
+			//printf( "Got lobby %d: %s\n", groupID, name );
+			//printf( "Max Servers of lobby %d: %d\n", lobby_info->m_GroupId, lobby_info->m_MaxServers );
+			if( lobby_info->m_NumServers > lobby_info->m_MaxServers )
+			{
+				lobby_info->m_MaxServers = lobby_info->m_NumServers;
+			}
+	
+			lobby_info->SetPri( -groupID );
+			lobby_man->m_lobbies.AddNode( lobby_info );
+
+			Mem::Manager::sHandle().PopContext();
+		}
+	}
+	else
+	{
+		gamenet_man->SetServerListState( vSERVER_LIST_STATE_FAILED_LOBBY_LIST );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_join_room_callback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, 
+										void* param )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	LobbyMan* lobby_man = (LobbyMan*) param;
+
+	
+	Dbg_Assert( roomType == GroupRoom );
+
+	if( success )
+	{
+		//printf( "Entered group room\n" );
+		Script::RunScript( "dialog_box_exit" );
+		Script::RunScript( "create_network_select_games_menu" );
+		lobby_man->m_in_group_room = true;
+
+		lobby_man->refresh_player_list();
+		if( gamenet_man->mpBuddyMan->IsLoggedIn())
+		{
+			char rating_str[64];
+			char key[64];
+			const char* keys;
+			const char* values;
+
+			gamenet_man->mpBuddyMan->SetStatusAndLocation( GP_CHATTING, (char*) Script::GetString( "homie_status_chatting" ), 
+																		lobby_man->GetLobbyName());
+			sprintf( key, "b_rating" );
+			sprintf( rating_str, "%d", gamenet_man->mpStatsMan->GetStats()->GetRating());
+			keys = key;
+			values = rating_str;
+			peerSetRoomKeys(peer, roomType, peerGetNick( peer ), 1, &keys, &values );
+			peerGetRoomKeys( peer, roomType, "*", 1, &keys, s_get_room_keys_callback, 
+							 lobby_man, false );
+		}
+	}
+	else
+	{
+		switch( result )
+		{
+			case PEERFullRoom:
+				//printf( "Full Room\n" );
+				break;
+			case PEERInviteOnlyRoom:
+				//printf( "Invite Only\n" );
+				break;
+			case PEERBannedFromRoom:
+				//printf( "Banned from room\n" );
+				break;
+			case PEERBadPassword:
+				//printf( "Bad Password\n" );
+				break;
+			case PEERAlreadyInRoom:
+				//printf( "Already in Room\n" );
+				break;
+			case PEERNoTitleSet:
+				//printf( "No Title Set\n" );
+				break;
+			case PEERJoinFailed:
+				//printf( "Generic: Join Failed\n" );
+				break;
+			default:
+				break;
+
+		};
+		
+		Script::RunScript( "CreateJoinLobbyFailedDialog" );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_lobby_logic_code( const Tsk::Task< LobbyMan >& task )
+{
+	LobbyMan& man = task.GetData();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	peerThink( man.m_peer );
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_nat_negotiate_think_code( const Tsk::Task< LobbyMan >& task )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+	NNThink();
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void gamespy_data_handler( char* packet_data, int len, struct sockaddr* sender )
+{
+	
+
+	//Dbg_Printf( "Got gamepsy data of length %d\n", len );
+	//Dbg_Printf( "Data[0] = %c\n", packet_data[0] );
+	//Dbg_Printf( "Data[1] = %c\n", packet_data[1] );
+
+	Dbg_Assert( len < 2047 );
+	// backslash as the first character signifies a gamespy packet
+	if( ( (unsigned char) packet_data[0] == QR_MAGIC_1 ) &&
+		( (unsigned char) packet_data[1] == QR_MAGIC_2 ))
+	{
+		//Dbg_Printf( "************ GOT GAMESPY QUERY CALL!!!!!!!!\n" );
+		memcpy( s_gamespy_parse_data, packet_data, len );
+		s_gamespy_sender = *sender;
+		s_gamespy_parse_data_len = len;
+		s_gamespy_data_type = vGAMESPY_QUERY_DATA;
+	}
+	else
+	{
+		if( len >= NATNEG_MAGIC_LEN )
+		{
+			//Dbg_Printf( "Got Nat Neg data of length %d\n", len );
+			if(	( (unsigned char) packet_data[0] == NN_MAGIC_0 ) &&
+				( (unsigned char) packet_data[1] == NN_MAGIC_1 ) &&
+				( (unsigned char) packet_data[2] == NN_MAGIC_2 ) &&
+				( (unsigned char) packet_data[3] == NN_MAGIC_3 ) &&
+				( (unsigned char) packet_data[4] == NN_MAGIC_4 ) &&
+				( (unsigned char) packet_data[5] == NN_MAGIC_5 ))
+			{
+				//Dbg_Printf( "Magic number matches\n" );
+                memcpy( s_gamespy_parse_data, packet_data, len );
+				s_gamespy_sender = *sender;
+				s_gamespy_parse_data_len = len;
+				s_gamespy_data_type = vGAMESPY_NAT_DATA;
+			}
+		}
+
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::s_process_gamespy_queries_code( const Tsk::Task< LobbyMan >& task )
+{
+	LobbyMan& man = task.GetData();
+	Tmr::Time current_time;
+
+	current_time = Tmr::GetTime();
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	//if( s_gamespy_parse_data[0] != '\0' )
+	if( s_gamespy_data_type == vGAMESPY_QUERY_DATA )
+	{
+		//Dbg_Printf( "***** Parsing Gamespy Packet Data...\n" ); 
+		//qr_parse_query( NULL, s_gamespy_parse_data, &s_gamespy_sender );
+		peerParseQuery( man.GetPeer(), s_gamespy_parse_data, s_gamespy_parse_data_len, &s_gamespy_sender );
+		s_gamespy_parse_data[0] = '\0';
+		s_gamespy_parse_data_len = 0;
+		s_gamespy_data_type = vGAMESPY_NO_DATA;
+	}
+	else if( s_gamespy_data_type == vGAMESPY_NAT_DATA )
+	{
+		//Dbg_Printf( "Processing Nat Neg data\n" );
+        NNProcessData( s_gamespy_parse_data, s_gamespy_parse_data_len, (sockaddr_in*) &s_gamespy_sender );
+		s_gamespy_parse_data[0] = '\0';
+		s_gamespy_parse_data_len = 0;
+		s_gamespy_data_type = vGAMESPY_NO_DATA;
+	}
+	else if( s_got_gamespy_callback == false )
+	{
+		Manager * gamenet_man = Manager::Instance();
+
+		if( gamenet_man->InNetGame() && gamenet_man->OnServer())
+		{
+			// After N seconds, if we have yet to be contacted by GameSpy, notify the user that his server
+			// was not properly posted to GameSpy
+			if( ( current_time - s_game_start_time ) > vPOST_SERVER_TIMEOUT )
+			{
+				
+				if( s_notified_user_not_connected == false )
+				{
+					Script::RunScript( "CreateNotPostedDialog" );
+					s_notified_user_not_connected = true;
+				}
+			}
+			else if(( current_time - s_last_post_time ) > vPOST_SERVER_RETRY_TIME )
+			{
+				// Gamespy still hasn't queried us for our options. Let's tell them again
+				// that our server is up
+				peerStateChanged( man.GetPeer());
+				s_last_post_time = current_time;
+			}
+		}
+	}
+	
+	//qr_process_queries( NULL );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/*****************************************************************************
+**							  Handler Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+LobbyMan::LobbyMan( void )
+{
+	m_in_group_room = false;
+	m_expecting_disconnect = true;
+	m_peer = NULL;
+
+	m_lobby_logic_task = new Tsk::Task< LobbyMan > ( s_lobby_logic_code, *this );
+	m_nat_negotiate_task = new Tsk::Task< LobbyMan > ( s_nat_negotiate_think_code, *this );
+	m_process_gamespy_queries_task = new Tsk::Task< LobbyMan > ( s_process_gamespy_queries_code, *this );
+	qr2_register_key( NUMOBSERVERS_KEY, "numobservers" );
+	qr2_register_key( MAXOBSERVERS_KEY, "maxobservers" );
+	qr2_register_key( SKILLLEVEL_KEY, "skilllevel" );
+	qr2_register_key( STARTED_KEY, "started" );
+	qr2_register_key( HOSTED_MODE_KEY, "hostmode" );
+	qr2_register_key( RANKED_KEY, "ranked" );
+	qr2_register_key( RATING__KEY, "rating_" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+LobbyMan::~LobbyMan( void )
+{
+	delete m_lobby_logic_task;
+	delete m_nat_negotiate_task;
+	delete m_process_gamespy_queries_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::StartReportingGame( void )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	Manager * gamenet_man = Manager::Instance();
+	Net::Server* server;
+	PEERBool result;
+
+	server = gamenet_man->GetServer();
+	Dbg_Assert( server );
+	
+	server->SetForeignPacketHandler( gamespy_data_handler );
+	s_gamespy_parse_data[0] = '\0';
+	s_gamespy_data_type = vGAMESPY_NO_DATA;
+    
+	result = peerStartReportingWithSocket( m_peer, server->GetSocket(), vHOST_PORT );
+	if( result == true )
+	{
+		peerStateChanged( m_peer );
+	}
+
+	s_game_start_time = Tmr::GetTime();
+	s_last_post_time = s_game_start_time;
+	s_notified_user_not_connected = false;
+	s_got_gamespy_callback = false;
+	mlp_manager->AddLogicTask( *m_process_gamespy_queries_task );
+	m_reported_game = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::StopReportingGame( void )
+{
+	if( m_peer )
+	{
+        peerStopGame( m_peer );
+		m_process_gamespy_queries_task->Remove();
+	}
+
+	m_reported_game = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		LobbyMan::ReportedGame( void )
+{
+	return m_reported_game;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::StartLobbyList( void )
+{
+	Manager * gamenet_man = Manager::Instance();
+	//Thread::PerThreadStruct	net_thread_data;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+    gamenet_man->SetServerListState( vSERVER_LIST_STATE_GETTING_LOBBY_LIST );
+
+	// Reset the server list refresh time so that the 1-second minimum rule for refreshing server
+	// lists won't apply when going back and forth really quickly to/from lobby/server lists
+	s_last_refresh_time = 0;
+
+	peerListGroupRooms( m_peer, "\\maxplayers\\neversoft\\maxrating\\minrating", s_group_rooms_callback, this, false );
+    
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::StopLobbyList( void )
+{
+	Lst::Search< LobbyInfo > sh;
+	LobbyInfo* lobby, *next;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	if( m_peer )
+	{
+		peerStopListingGames( m_peer );
+	}
+
+	for( lobby = sh.FirstItem( m_lobbies ); lobby; lobby = next )
+	{
+		next = sh.NextItem();
+		delete lobby;
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::FillLobbyList( void )
+{
+	Lst::Search< LobbyInfo > sh;
+	LobbyInfo* lobby;
+	int index;
+
+	Script::RunScript( "dialog_box_exit" );
+	Script::RunScript( "create_lobby_list_menu" );
+	
+	index = 0;
+	for( lobby = sh.FirstItem( m_lobbies ); lobby; lobby = sh.NextItem())
+	{
+		add_lobby_to_menu( lobby, index++ );    
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+LobbyInfo*	LobbyMan::GetLobbyInfo( int index )
+{
+	Lst::Search< LobbyInfo > sh;
+	LobbyInfo* lobby;
+
+	for( lobby = sh.FirstItem( m_lobbies ); lobby; lobby = sh.NextItem())
+	{
+		if( index == 0 )
+		{
+			return lobby;
+		}
+
+		index--;
+	}
+	
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::SetLobbyId( int id )
+{
+	m_lobby_id = id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			LobbyMan::GetLobbyId( void )
+{
+	return m_lobby_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::SetLobbyNumServers( int num_servers )
+{
+	m_lobby_num_servers = num_servers;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			LobbyMan::GetLobbyNumServers( void )
+{
+	return m_lobby_num_servers;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::SetLobbyMaxServers( int max_servers )
+{
+	m_lobby_max_servers = max_servers;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int			LobbyMan::GetLobbyMaxServers( void )
+{
+	return m_lobby_max_servers;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::SetLobbyName( char* name )
+{
+	Dbg_Assert( name );
+
+	strcpy( m_lobby_name, name );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*		LobbyMan::GetLobbyName( void )
+{
+	return m_lobby_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::SetServerName( char* name )
+{
+	strcpy( m_server_name, name );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*		LobbyMan::GetServerName( void )
+{
+	return m_server_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		LobbyMan::SetOfficialLobby( bool official )
+{
+	m_official = official;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		LobbyMan::IsLobbyOfficial( void )
+{
+	return m_official;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	LobbyMan::s_nat_negotiate_complete( NegotiateResult result, SOCKET gamesocket, 
+												struct sockaddr_in *remoteaddr, void *userdata )
+{
+	Manager* gamenet_man = Manager::Instance();
+	int cookie;
+
+	cookie = (int) userdata;
+
+	Dbg_Printf( "**** NAT Negotiation Complete: %d\n", result );
+	switch( result )
+	{
+		case nr_success:
+		{
+			Dbg_Printf( "**** NAT Negotiation Complete: Success!\n" );
+
+			if( !gamenet_man->OnServer())
+			{
+				bool observe_only;
+				Dbg_Printf( "**** IP: %s Port: %d\n", inet_ntoa( remoteaddr->sin_addr ), ntohs( remoteaddr->sin_port ));
+				observe_only = ( gamenet_man->GetJoinMode() == GameNet::vJOIN_MODE_OBSERVE );
+				gamenet_man->JoinServer( observe_only, (int) remoteaddr->sin_addr.s_addr, ntohs( remoteaddr->sin_port ), 0 );
+			}
+			break;
+		}
+		case nr_deadbeatpartner:
+			Dbg_Printf( "**** NAT Negotiation Complete: Deadbeat Partner\n" );
+			break;
+		case nr_inittimeout:
+			Dbg_Printf( "**** NAT Negotiation Complete: Timeout\n" );
+			break;
+		case nr_unknownerror:
+		default:
+			Dbg_Printf( "**** NAT Negotiation Complete: Unknown Error\n" );
+			break;
+	}
+
+	if( gamenet_man->OnServer())
+	{
+		gamenet_man->mpLobbyMan->m_nat_count.Release();
+		if( gamenet_man->mpLobbyMan->m_nat_count.InUse() == false )
+		{
+			gamenet_man->mpLobbyMan->m_nat_negotiate_task->Remove();
+		}
+	}
+	else
+	{
+		Net::Client* client;
+
+		NNCancel( cookie );
+		NNThink();
+		NNFreeNegotiateList();
+
+		client = gamenet_man->GetClient( 0 );
+		client->SetForeignPacketHandler( NULL );
+		gamenet_man->mpLobbyMan->m_nat_negotiate_task->Remove();
+		gamenet_man->mpLobbyMan->m_process_gamespy_queries_task->Remove();
+
+		if( result != nr_success )
+		{
+			Script::RunScript( "show_nat_timeout" );
+		}
+	}
+
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	LobbyMan::s_nat_negotiate_progress( NegotiateState state, void *userdata )
+{
+    switch( state )
+	{
+		case ns_initack:
+			Dbg_Printf( "**** NAT Negotiation : Init Ack State\n" );
+			break;
+		case ns_connectping:
+			Dbg_Printf( "**** NAT Negotiation : Connect Ping State\n" );
+			break;
+		default:
+			Dbg_Printf( "**** NAT Negotiation : Unknown State\n" );
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptCancelNatNegotiation(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager* gamenet_man = Manager::Instance();
+
+	NNCancel( gamenet_man->mpLobbyMan->m_cookie );
+	NNThink();
+	NNFreeNegotiateList();
+	
+	gamenet_man->mpLobbyMan->m_nat_negotiate_task->Remove();
+	gamenet_man->mpLobbyMan->m_process_gamespy_queries_task->Remove();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptStartNatNegotiation(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager* gamenet_man = Manager::Instance();
+	LobbyMan* lobby_man;
+	Mlp::Manager* mlp_manager = Mlp::Manager::Instance();
+	Net::Client* client;
+	const char *server_ip = NULL;
+	int port = 0;
+	NegotiateError err;
+    
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	lobby_man = gamenet_man->mpLobbyMan;
+	pParams->GetText( NONAME, &server_ip );
+	pParams->GetInteger( NONAME, &port );
+	pParams->GetChecksum( CRCD( 0x751f4599, "cookie" ), (uint32*) &lobby_man->m_cookie );
+	
+    //port = 9959;
+	Dbg_Printf( "******* Starting NAT Negotiation for server : %s port %p *******", server_ip, port );
+	
+	client = gamenet_man->SpawnClient( false, false, true, 0 );
+    peerSendNatNegotiateCookie( lobby_man->GetPeer(), inet_addr( server_ip ), 
+								port, lobby_man->m_cookie );
+    err = NNBeginNegotiationWithSocket( client->GetSocket(), lobby_man->m_cookie, 1, s_nat_negotiate_progress, 
+										s_nat_negotiate_complete, (void*) lobby_man->m_cookie );
+	if( err != ne_noerror )
+	{
+		Dbg_Printf( "******* NAT Neg Error: %d\n", err );
+		NNFreeNegotiateList();
+		Mem::Manager::sHandle().PopContext();
+		return false;
+	}
+	Dbg_Assert( err == ne_noerror );
+
+	Dbg_Printf( "*** Added NAT Task\n" );
+	mlp_manager->AddLogicTask( *lobby_man->m_nat_negotiate_task );
+	client->SetForeignPacketHandler( gamespy_data_handler );
+	mlp_manager->AddLogicTask( *lobby_man->m_process_gamespy_queries_task );
+	s_gamespy_parse_data[0] = '\0';
+
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptRejoinLobby(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager* gamenet_man = Manager::Instance();
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	peerJoinGroupRoom( gamenet_man->mpLobbyMan->m_peer, gamenet_man->mpLobbyMan->GetLobbyId(), s_join_room_callback, gamenet_man->mpLobbyMan, false );
+
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptChooseLobby(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	LobbyInfo* p_lobby;
+	Manager* gamenet_man = Manager::Instance();
+	int index = 0;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	pParams->GetInteger( Script::GenerateCRC("index"), &index );
+				
+	p_lobby = gamenet_man->mpLobbyMan->GetLobbyInfo( index );
+	Dbg_Assert( p_lobby );
+								
+	//Dbg_Printf( "Choosing lobby %d\n", p_lobby->m_GroupId );
+	gamenet_man->mpLobbyMan->SetLobbyId( p_lobby->m_GroupId );
+	gamenet_man->mpLobbyMan->SetLobbyNumServers( p_lobby->m_NumServers );
+	gamenet_man->mpLobbyMan->SetLobbyMaxServers( p_lobby->m_MaxServers );
+	gamenet_man->mpLobbyMan->SetLobbyName( p_lobby->m_Name );
+	gamenet_man->mpLobbyMan->SetOfficialLobby( p_lobby->m_Official );
+	
+    peerJoinGroupRoom( gamenet_man->mpLobbyMan->m_peer, gamenet_man->mpLobbyMan->GetLobbyId(), s_join_room_callback, gamenet_man->mpLobbyMan, false );
+
+	// Don't need the lobby list anymore. We have recorded all its data
+	gamenet_man->mpLobbyMan->StopLobbyList();
+
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptGetNumPlayersInLobby(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager* gamenet_man = Manager::Instance();
+	Script::CStruct* p_return_params;
+
+	p_return_params = pScript->GetParams();
+	p_return_params->AddInteger( "num_players", gamenet_man->mpLobbyMan->m_players.CountItems());
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptLeaveLobby(Script::CScriptStructure *pParams, Script::CScript* pScript )
+{
+	Manager* gamenet_man = Manager::Instance();
+	LobbyMan* lobby_man;
+	BuddyMan* buddy_man;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	lobby_man = gamenet_man->mpLobbyMan;
+	buddy_man = gamenet_man->mpBuddyMan;
+
+	peerLeaveRoom( lobby_man->m_peer, GroupRoom, NULL );
+	lobby_man->m_in_group_room = false;
+
+	if( buddy_man->IsLoggedIn())
+	{
+		if( !pParams->ContainsComponentNamed( CRCD(0xb2ab4dab,"preserve_status")))
+		{
+			buddy_man->SetStatusAndLocation( GP_ONLINE, 	(char*) Script::GetString( "homie_status_online" ), 
+														(char*) Script::GetString( "homie_status_logging_in" ));
+		}
+	}
+
+	// Maintain our association with our group room so that, if we're hosting a game, it will
+	// report it to the correct group room
+	peerSetGroupID( lobby_man->m_peer, lobby_man->GetLobbyId());
+
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptLobbyConnect(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	PEERBool ping_rooms[NumRooms] = {false, true, false};
+	PEERBool x_ping_rooms[NumRooms] = {false, false, false};
+	PEER peer;
+	Thread::PerThreadStruct	net_thread_data;
+    
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+	
+	gamenet_man->mpLobbyMan->Initialize();
+	
+	peer = gamenet_man->mpLobbyMan->m_peer;
+	peerSetTitle( peer, 
+				  "thps5ps2", 
+				  "G2k8cF", 
+				  "thps5ps2", 
+				  "G2k8cF", 
+				  vGAME_VERSION,
+				  15, 
+				  true,
+				  ping_rooms, 
+				  x_ping_rooms );
+
+	Dbg_Printf( "Connecting to lobby 1\n" );
+	net_thread_data.m_pEntry = s_threaded_peer_connect;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+	StartThread( gamenet_man->GetNetThreadId(), gamenet_man->mpLobbyMan );
+	Dbg_Printf( "Connecting to lobby 2\n" );
+	mlp_man->AddLogicTask( *gamenet_man->mpLobbyMan->m_lobby_logic_task );
+	
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptLobbyDisconnect(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	Dbg_Printf( "******************************* DISCONNECTING FROM PEER!!!! \n" );
+
+	while( gamenet_man->mpLobbyMan->m_nat_count.InUse())
+	{
+		gamenet_man->mpLobbyMan->m_nat_count.Release();
+	}
+
+	gamenet_man->mpLobbyMan->StopLobbyList();
+	gamenet_man->mpLobbyMan->clear_player_list();
+	gamenet_man->mpLobbyMan->m_lobby_logic_task->Remove();
+	gamenet_man->mpLobbyMan->m_nat_negotiate_task->Remove();
+	gamenet_man->mpLobbyMan->m_in_group_room = false;
+	gamenet_man->mpLobbyMan->m_expecting_disconnect = true;
+	if( gamenet_man->mpLobbyMan->m_peer )
+	{
+		peerStopGame( gamenet_man->mpLobbyMan->m_peer );
+		peerClearTitle( gamenet_man->mpLobbyMan->m_peer );
+		peerDisconnect( gamenet_man->mpLobbyMan->m_peer );
+	}
+	gamenet_man->mpLobbyMan->Shutdown();
+	//peerShutdown( gamenet_man->mpLobbyMan->m_peer );
+	//gamenet_man->mpLobbyMan->m_peer = NULL;
+
+	//Dbg_Printf( "******************************* DISCONNECTED FROM PEER!!!! \n" );
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptSetQuietMode( Script::CScriptStructure* pParams, Script::CScript* pScript )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	if( pParams->ContainsFlag( "off" ))
+	{
+		peerSetQuietMode( gamenet_man->mpLobbyMan->m_peer, false );
+	}
+	else
+	{
+		peerSetQuietMode( gamenet_man->mpLobbyMan->m_peer, true );
+	}
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptFillPlayerList( Script::CScriptStructure* pParams, Script::CScript* pScript )
+{
+	Spt::SingletonPtr< GameNet
+		::Manager > gamenet_man;
+
+	gamenet_man->mpLobbyMan->fill_player_list();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptFillLobbyProspectiveBuddyList( Script::CScriptStructure* pParams, Script::CScript* pScript )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	gamenet_man->mpLobbyMan->fill_prospective_buddy_list();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptCanHostGame( Script::CScriptStructure* pParams, Script::CScript* pScript )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	if( gamenet_man->mpLobbyMan->IsLobbyOfficial())
+	{
+		if( gamenet_man->mpBuddyMan->IsLoggedIn())
+		{
+			int profile;
+
+			profile = gamenet_man->mpBuddyMan->GetProfile();
+			if(	( profile == 27747931 ) ||
+				( profile == 27747977 ) ||
+				( profile == 27748011 ) ||
+				( profile == 27748097 ) ||
+				( profile == 27748044 ) ||
+				( profile == 27748142 ) ||
+				( profile == 27748187 ) ||
+				( profile == 27748211 ) ||
+				( profile == 27748244 ) ||
+				( profile == 27748273 ))
+			{
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	return( gamenet_man->mpLobbyMan->GetLobbyNumServers() < gamenet_man->mpLobbyMan->GetLobbyMaxServers());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	LobbyMan::ScriptSendMessage( Script::CScriptStructure* pParams, Script::CScript* pScript )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	const char *p_text;
+	
+	if( pParams->GetText( "text", &p_text ))
+	{
+		if( pParams->ContainsFlag( "system_message" ))
+		{
+			Script::CStruct* p_params;
+			char msg[1024];
+		
+			sprintf( msg, " %s", p_text );
+			p_params = new Script::CStruct;	
+			p_params->AddString( "text", msg );
+			Script::RunScript("create_console_message", p_params );
+			delete p_params;
+		}
+		else
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+			peerMessageRoom( gamenet_man->mpLobbyMan->m_peer, GroupRoom, p_text, NormalMessage );
+			Mem::Manager::sHandle().PopContext();
+		}
+	}
+
+	return true;
+}
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::Initialize( void )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	PEERCallbacks callbacks;
+
+	memset( &callbacks, 0, sizeof( PEERCallbacks ));
+	callbacks.disconnected = s_disconnected_callback;
+	callbacks.roomMessage = s_room_message_callback;
+	callbacks.playerMessage = s_player_message_callback;
+	callbacks.playerJoined = s_player_joined_callback;
+	callbacks.playerLeft = s_player_left_callback;
+	callbacks.playerInfo = s_player_info_callback;
+	callbacks.newPlayerList = s_new_player_list_callback;
+	callbacks.roomKeyChanged = s_room_key_callback;
+
+    callbacks.qrNatNegotiateCallback = s_nat_negotiate_callback;
+	callbacks.qrKeyList = s_key_list_callback;
+	callbacks.qrServerKey = s_server_key_callback;
+	callbacks.qrPlayerKey = s_player_key_callback;
+	callbacks.qrCount = s_key_count_callback;
+	callbacks.qrAddError = s_add_error_callback;
+	callbacks.qrPublicAddressCallback = s_public_address_callback;
+	callbacks.param = this;
+	
+	m_peer = peerInitialize( &callbacks );
+	Dbg_Assert( m_peer );
+	
+	m_in_group_room = false;
+	m_expecting_disconnect = true;
+	m_reported_game = false;
+	m_official = false;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	LobbyMan::Shutdown( void )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	m_lobby_logic_task->Remove();
+	m_nat_negotiate_task->Remove();
+	m_process_gamespy_queries_task->Remove();
+
+	if( m_peer )
+	{
+		peerShutdown( m_peer );
+	}
+	m_peer = NULL;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PEER	LobbyMan::GetPeer( void )
+{
+	return m_peer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+bool	LobbyMan::InGroupRoom( void )
+{
+	return m_in_group_room;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	LobbyMan::PlayerName( char* lobby_name )
+{
+	char* player_name;
+
+	player_name = strchr( lobby_name, '_' );
+	if( player_name == NULL )
+	{
+		return lobby_name;
+	}
+	return ( player_name + 1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+LobbyPlayerInfo::LobbyPlayerInfo( void ) : Lst::Node< LobbyPlayerInfo > ( this )
+{
+	m_Profile = 0;
+	m_Rating = 0;
+	m_GotInfo = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/Lobby.h b/Code/Sk/GameNet/Lobby.h
new file mode 100644
index 0000000..b3fb737
--- /dev/null
+++ b/Code/Sk/GameNet/Lobby.h
@@ -0,0 +1,231 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet													**
+**																			**
+**	File name:		Lobby.h													**
+**																			**
+**	Created:		04/04/02	- spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__LOBBY_H
+#define	__LOBBY_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+namespace GameNet
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+enum
+{
+	NUMOBSERVERS_KEY = NUM_RESERVED_KEYS + 1,
+	MAXOBSERVERS_KEY,
+	SKILLLEVEL_KEY,
+	STARTED_KEY,
+	HOSTED_MODE_KEY,
+	RANKED_KEY,
+	RATING__KEY,
+};
+	
+
+/*****************************************************************************
+**							     Class Definitions							**
+*****************************************************************************/
+
+class LobbyPlayerInfo : public Lst::Node< LobbyPlayerInfo >
+{
+	public:
+		LobbyPlayerInfo( void );
+
+		char	m_Name[64];
+		int		m_Profile;
+		int		m_Rating;
+		bool	m_GotInfo;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class LobbyMan
+{
+public:
+				LobbyMan( void );
+				~LobbyMan( void );
+
+	void		Initialize( void );
+	void		Shutdown( void );
+
+	void		StartReportingGame( void );
+	void		StopReportingGame( void );
+	bool		ReportedGame( void );
+
+	void		StartLobbyList( void );
+	void		StopLobbyList( void );
+	void		FillLobbyList( void );
+
+	LobbyInfo*	GetLobbyInfo( int index );
+
+	void		SetLobbyId( int id );
+	int			GetLobbyId( void );
+	void		SetLobbyNumServers( int num_servers );
+	int			GetLobbyNumServers( void );
+	void		SetLobbyMaxServers( int max_servers );
+	int			GetLobbyMaxServers( void );
+	char*		GetServerName( void );
+	void		SetServerName( char* name );
+	void		SetLobbyName( char* name );
+	char*		GetLobbyName( void );
+	void		SetOfficialLobby( bool official );
+	bool		IsLobbyOfficial( void );
+
+	PEER		GetPeer( void );
+	bool		InGroupRoom( void );
+	char*		PlayerName( char* lobby_name );
+    
+	static	bool	ScriptGetNumPlayersInLobby(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptChooseLobby(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptRejoinLobby(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptLeaveLobby(Script::CScriptStructure *pParams, Script::CScript* pScript );
+	static	bool	ScriptLobbyConnect(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptLobbyDisconnect(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptSendMessage( Script::CScriptStructure* pParams, Script::CScript* pScript );
+	static	bool	ScriptSetQuietMode( Script::CScriptStructure* pParams, Script::CScript* pScript );
+	static	bool	ScriptFillPlayerList( Script::CScriptStructure* pParams, Script::CScript* pScript );
+	static	bool	ScriptFillLobbyProspectiveBuddyList( Script::CScriptStructure* pParams, Script::CScript* pScript );
+	static	bool	ScriptCanHostGame( Script::CScriptStructure* pParams, Script::CScript* pScript );
+	static	bool	ScriptStartNatNegotiation(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptCancelNatNegotiation(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+private:
+	Lst::Head< LobbyInfo > 	m_lobbies;
+	Lst::Head< LobbyPlayerInfo > m_players;
+
+	int				m_lobby_id;
+	int				m_lobby_max_servers;
+	int				m_lobby_num_servers;
+	char			m_lobby_name[128];
+	char			m_server_name[128];
+	bool			m_in_group_room;
+	bool			m_official;
+	
+	bool			m_expecting_disconnect;
+	bool			m_connection_in_progress;
+	bool			m_reported_game;
+
+	Spt::Ref		m_nat_count;
+		
+	PEER			m_peer;
+	int				m_cookie;
+	
+	void			add_lobby_to_menu( LobbyInfo* lobby, int index );
+	void			add_player_to_menu( LobbyPlayerInfo* player );
+	void			add_buddy_to_menu( LobbyPlayerInfo* player );
+	void			refresh_player_list( void );
+	void			clear_player_list( void );
+	void			fill_player_list( void );
+	void			fill_prospective_buddy_list( void );
+
+	Tsk::Task< LobbyMan >*		m_lobby_logic_task;
+	Tsk::Task< LobbyMan >*		m_nat_negotiate_task;
+	Tsk::Task< LobbyMan >*		m_process_gamespy_queries_task;
+	static	Tsk::Task< LobbyMan >::Code   	s_lobby_logic_code;
+	static	Tsk::Task< LobbyMan >::Code   	s_nat_negotiate_think_code;
+	static	Tsk::Task< LobbyMan >::Code		s_process_gamespy_queries_code;
+
+	static	void	s_enum_players_callback( PEER peer, PEERBool success, RoomType roomType, int index, const char * nick,  // The nick of the player.
+												int flags, void * param );
+	static 	void	s_nick_error_callback( PEER peer, int type, const char* nick, void *param );
+	static	void	s_connect_callback( PEER peer, PEERBool success, void* param );
+
+	static	void 	s_disconnected_callback( PEER peer, const char* reason, void* param );
+	static	void 	s_room_message_callback( PEER peer, RoomType roomType, const char * nick, const char * message,
+										MessageType messageType, void * param );
+	static	void 	s_player_message_callback( PEER peer, const char * nick, const char * message, MessageType messageType,
+												void * param );
+	static 	void 	s_player_joined_callback( PEER peer, RoomType roomType, const char * nick, void * param );
+	static	void 	s_player_info_callback( PEER peer, RoomType roomType, const char * nick, unsigned int IP, int profileID,  
+                                                void * param );
+	static	void	s_player_proile_id_callback( PEER peer, PEERBool success,  const char * nick, int profileID, void * param );
+	static	void	s_player_left_callback( PEER peer, RoomType roomType, const char * nick, const char * reason, 
+												void * param );
+	static	void	s_new_player_list_callback( PEER peer, RoomType roomType, void * param );
+	static	void	s_room_key_callback( PEER peer, RoomType roomType, const char * nick, 
+										 const char * key, const char * value, void * param );
+	static	void	s_nat_negotiate_callback( PEER peer, int cookie, void * param );
+	static	void	s_key_list_callback( PEER peer, qr2_key_type type, qr2_keybuffer_t keyBuffer, 
+									void * param );
+	static	void	s_server_key_callback( PEER peer, int key, qr2_buffer_t buffer, void * param );
+	static	void	s_player_key_callback( PEER peer, int key, int index, qr2_buffer_t buffer, void * param );
+	static	int		s_key_count_callback( PEER peer, qr2_key_type type, void * param );
+	static	void    s_add_error_callback( PEER peer, qr2_error_t error, char * errorString, void * param );
+	static	void    s_public_address_callback( PEER peer, unsigned int ip, unsigned short port, 
+											   void * param );
+	static	void	s_public_address_callback( PEER peer, qr2_error_t error, char * errorString, void * param );
+
+	static	void	s_group_rooms_callback( PEER peer, PEERBool success, int groupID, SBServer server,  
+									const char * name, int numWaiting, int maxWaiting, int numGames, int numPlaying,
+									void* param );
+	static	void	s_join_room_callback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, 
+										void * param );
+	static	void 	s_get_room_keys_callback( PEER peer, PEERBool success, RoomType roomType, 
+											const char * nick, int num, char ** keys, char ** values, 
+											void * param );
+
+	//static 	void 	s_lobby_list_callback( GServerList serverlist, int msg, void *instance, void *param1, void *param2 );
+	//static	void	s_threaded_get_lobby_list( LobbyMan* lobby_man );
+
+	static	void	s_threaded_peer_connect( LobbyMan* lobby_man );
+	static	void	s_nat_negotiate_progress( NegotiateState state, void *userdata );
+	static	void	s_nat_negotiate_complete( NegotiateResult result, SOCKET gamesocket, 
+												struct sockaddr_in *remoteaddr, void *userdata);
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+}	// namespace GameNet
+
+#endif	// __LOBBY_H
\ No newline at end of file
diff --git a/Code/Sk/GameNet/Ngps/dnas.cpp b/Code/Sk/GameNet/Ngps/dnas.cpp
new file mode 100644
index 0000000..7bf4e87
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/dnas.cpp
@@ -0,0 +1,699 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#define WAIT_VSYNC	0x1111
+#define vAUTH_TIMEOUT	30
+
+static	int	s_extra_time = 0;
+
+//#define __DNAS_DEBUG__
+
+static int s_get_thread_priority( void )
+{
+	struct ThreadParam info;
+	// Fill info with zeros to be sure the following call is doing
+	// something to info
+	memset(&info,0,sizeof(info));
+	// return value not defined in 2.0 PDF EE lib ref
+	ReferThreadStatus( GetThreadId(), &info );
+	return ( info.currentPriority );
+}
+
+static int netstart_dnas(int proxy_state, const char *proxy_host, u_short proxy_port, int timeout, sceDNAS2Status_t& status) 
+{
+	int  ret, error = 0;
+	sceDNAS2TitleAuthInfo_t auth_info;
+	sceDNAS2Status_t prev_status;
+	sceDNAS2TimeoutInfo_t timeout_info;
+	int /*fd, */proxy_state_check = 0;
+	char* auth_data;
+	char proxy_host_check[512];
+	u_short proxy_port_check;
+	unsigned char* phrase;
+	//int vsync_handler_id;
+	int orig_priority;
+
+    unsigned char us_english_pass_phrase[] = { 0xfb, 0x31, 0xce, 0xf7, 0x2a, 0x57, 0x2f, 0xf4 };
+	unsigned char uk_english_pass_phrase[] = { 0xea, 0x67, 0x94, 0x2c, 0x37, 0xd1, 0xfc, 0x8f };
+	unsigned char spanish_pass_phrase[] = { 0xb5, 0x05, 0xcb, 0x0b, 0xe5, 0x20, 0x8e, 0x38 };
+    unsigned char german_pass_phrase[] = { 0x91, 0xe7, 0x87, 0x5c, 0xc5, 0x3f, 0x70, 0x4c };
+    unsigned char french_pass_phrase[] = { 0x15, 0xb3, 0x8d, 0x7f, 0x2d, 0x81, 0xbe, 0x05 };
+    unsigned char italian_pass_phrase[] = { 0x88, 0xe9, 0xfa, 0x1e, 0xb4, 0x3d, 0x88, 0x3c };
+
+	phrase = NULL;
+
+	auth_data = new char[1024*64];
+
+		/* Initialize ID module for DNAS */
+	void *p_file;
+	 
+	p_file = NULL;
+	if( Config::gLanguage == Config::LANGUAGE_ENGLISH )
+	{
+		if( Config::gDisplayType == Config::DISPLAY_TYPE_NTSC )
+		{
+#ifdef __DNAS_DEBUG__
+			scePrintf( "Auth File: dnas/SLUS20731_client.dat\n" );
+#endif
+			p_file = File::Open( "dnas/SLUS20731_client.dat", "rb" );
+			phrase = us_english_pass_phrase;
+		}
+		else
+		{
+#ifdef __DNAS_DEBUG__
+			scePrintf( "Auth File: dnas/SLES51848_client.dat\n" );
+#endif
+			p_file = File::Open( "dnas/SLES51848_client.dat", "rb" );
+			phrase = uk_english_pass_phrase;
+		}
+	}
+	else if( Config::gLanguage == Config::LANGUAGE_FRENCH )
+	{
+#ifdef __DNAS_DEBUG__
+		scePrintf( "Auth File: dnas/SLES51851_client.dat\n" );
+#endif
+		p_file = File::Open( "dnas/SLES51851_client.dat", "rb" );
+		phrase = french_pass_phrase;
+	}
+	else if( Config::gLanguage == Config::LANGUAGE_GERMAN )
+	{
+#ifdef __DNAS_DEBUG__
+		scePrintf( "Auth File: dnas/SLES51852_client.dat\n" );
+#endif
+		p_file = File::Open( "dnas/SLES51852_client.dat", "rb" );
+		phrase = german_pass_phrase;
+	}
+	else if( Config::gLanguage == Config::LANGUAGE_ITALIAN )
+	{
+#ifdef __DNAS_DEBUG__
+		scePrintf( "Auth File: dnas/SLES51853_client.dat\n" );
+#endif
+		p_file = File::Open( "dnas/SLES51853_client.dat", "rb" );
+		phrase = italian_pass_phrase;
+	}
+	else if( Config::gLanguage == Config::LANGUAGE_SPANISH )
+	{
+#ifdef __DNAS_DEBUG__
+		scePrintf( "Auth File: dnas/SLES51854_client.dat\n" );
+#endif
+		p_file = File::Open( "dnas/SLES51854_client.dat", "rb" );
+		phrase = spanish_pass_phrase;
+	}
+	else
+	{
+#ifdef __DNAS_DEBUG__
+		scePrintf( "Auth File: NONE!\n" );
+#endif
+	}
+
+	Dbg_Assert( p_file != NULL );
+	Dbg_Assert( phrase != NULL );
+
+#ifdef __DNAS_DEBUG__
+	scePrintf( "p_file: 0x%x  phrase: 0x%x\n", p_file, phrase );
+#endif
+
+	//fd = sceOpen("host0:../../authfile/auth.dat", SCE_RDONLY);
+	Dbg_Printf( "install_dnas(1)\n" );
+
+	File::Read( auth_data, 1, 1024 * 64, p_file );
+	//sceRead(fd, auth_data, sizeof(auth_data));
+
+	Dbg_Printf( "install_dnas(2)\n" );
+	File::Close( p_file );
+
+	orig_priority = s_get_thread_priority();
+	ChangeThreadPriority( GetThreadId(), 49 );
+
+	memset(&status, 0, sizeof(sceDNAS2Status_t));
+	memset(&prev_status, 0, sizeof(sceDNAS2Status_t));
+	memset(&auth_info, 0, sizeof(sceDNAS2TitleAuthInfo_t));
+	if( timeout > 0 )
+	{
+		memset(&timeout_info, 0, sizeof(sceDNAS2TimeoutInfo_t));
+		timeout_info.timeout = timeout;
+		timeout_info.priority = 48;
+	}
+	auth_info.line_type = sceDNAS2_T_INET;
+	auth_info.auth_data.ptr = auth_data;
+	auth_info.auth_data.size = 1024 * 64;//sizeof(auth_data);
+	auth_info.pass_phrase.ptr = phrase;
+	auth_info.pass_phrase.size = sizeof(phrase);
+#ifdef __DNAS_DEBUG__
+	scePrintf("before sceDNAS2Init()\n");
+#endif
+
+	if(timeout > 0)
+	{
+		ret = sceDNAS2InitNoHDD(&auth_info, 48, 0, 0, &timeout_info);
+	}
+	else
+	{
+		ret = sceDNAS2InitNoHDD(&auth_info, 48, 0, 0, NULL);
+	}
+	/* NOTE */
+	/* sceDNAS2InitNoHDD() with Debug flag should never be used in the final release, but for test program only */
+#ifdef __DNAS_DEBUG__
+	scePrintf("return value of sceDNAS2Init(): %d\n", ret);
+#endif
+	if(ret < 0)
+	{
+		sceDNAS2GetStatus( &status );
+		
+		error = ret;
+		goto error;
+	}
+
+	/*set proxy parameter*/
+	if(proxy_state)
+	{
+		ret = sceDNAS2SetProxy(1, proxy_host, proxy_port);
+#ifdef __DNAS_DEBUG__
+		scePrintf("return value of sceDNAS2SetProxy = %d\n", ret);
+#endif
+		if(ret < 0)
+		{
+			sceDNAS2GetStatus( &status );
+			error = ret;
+			goto error;
+		}
+	}
+
+	/*get proxy parameter*/
+	if(proxy_state)
+	{
+		ret = sceDNAS2GetProxy(&proxy_state_check, proxy_host_check, sizeof(proxy_host_check), &proxy_port_check);
+#ifdef __DNAS_DEBUG__
+		scePrintf("return value of sceDNASGetProxy = %d\n", ret);
+#endif
+		if(ret < 0)
+		{
+			sceDNAS2GetStatus( &status );
+			error = ret;
+			goto error;
+		}
+#ifdef __DNAS_DEBUG__
+		if(proxy_state_check)
+		{
+			scePrintf("Proxy Info : Server = %s Port = %d\n", proxy_host_check, proxy_port_check);
+		}
+		else
+		{
+			scePrintf("Proxy Info : no use \n");
+		}
+#endif
+	}
+
+	ret = sceDNAS2AuthNetStart();
+#ifdef __DNAS_DEBUG__
+	scePrintf("ret of AuthNetStart() = %d\n", ret);
+#endif
+	if(ret < 0)
+	{
+		sceDNAS2GetStatus( &status );
+		error = ret;
+		goto error;
+	}
+
+	while(1)
+	{
+		if((ret = sceDNAS2GetStatus(&status)) < 0)
+		{
+#ifdef __DNAS_DEBUG__
+			scePrintf("ret of sceDNAS2GetStatus() = %d\n", ret);
+#endif
+			error = ret;
+			goto error;
+		}
+		if((ret = memcmp(&prev_status, &status, sizeof(sceDNAS2Status_t))) != 0)
+		{
+			/* status changed */
+#ifdef __DNAS_DEBUG__
+			scePrintf("code: %d, sub_code: %d, progress: %d\n",
+				status.code, status.sub_code, status.progress);
+#endif
+			if(status.code == sceDNAS2_S_END)
+			{
+#ifdef __DNAS_DEBUG__
+				scePrintf("subcode:type %d, inst_result %d\n",
+					sceDNAS2Status_SubCode_TYPE(status),
+					sceDNAS2Status_SubCode_INST_RESULT(status));
+#endif
+				memcpy(&prev_status, &status, sizeof(sceDNAS2Status_t));
+				break;
+			}
+			else if(status.code == sceDNAS2_S_NG || status.code == sceDNAS2_S_COM_ERROR)
+			{
+				error = -1;
+				break;
+			}
+		}
+		memcpy(&prev_status, &status, sizeof(sceDNAS2Status_t));
+	}
+
+error:
+#ifdef __DNAS_DEBUG__
+	if( error < 0 )
+	{
+		scePrintf("*********** DNAS failed. Error code : %d**********\n", error );
+	}
+	else
+	{
+		scePrintf("*********** DNAS succeeded.!!!! *************\n");
+	}
+#endif
+	ret = sceDNAS2Shutdown();
+#ifdef __DNAS_DEBUG__
+	scePrintf("ret of sceDNAS2Shutdown() is %d\n", ret);
+#endif
+	ChangeThreadPriority( GetThreadId(), orig_priority );
+	delete [] auth_data;
+
+	return error;
+}
+
+static	uint32	s_get_error_desc( int code )
+{
+	switch( code )
+	{
+		case sceDNAS2_SS_SERVER_BUSY:
+			return CRCD(0xf9814711,"net_auth_error_server_busy");
+			break;
+		case sceDNAS2_SS_BEFORE_SERVICE:
+			return CRCD(0x87da162f,"net_auth_error_before_service");
+			break;
+		case sceDNAS2_SS_OUT_OF_SERVICE:
+			return CRCD(0xa7b3ce4c,"net_auth_error_out_of_service");
+			break;
+		case sceDNAS2_SS_END_OF_SERVICE:
+			return CRCD(0x2a52e052,"net_auth_error_end_of_service");
+			break;
+		case sceDNAS2_SS_SESSION_TIME_OUT:
+			return CRCD(0x6b58d871,"net_auth_error_time_out");
+			break;
+		case sceDNAS2_SS_INVALID_SERVER:
+			return CRCD(0x3472f158,"net_auth_error_invalid_server");
+			break;
+		case sceDNAS2_SS_INTERNAL_ERROR:
+			return CRCD(0x3c0c381,"net_auth_error_internal");
+			break;
+		case sceDNAS2_SS_EXTERNAL_ERROR:
+			return CRCD(0x50617f3a,"net_auth_error_external");
+			break;
+		case sceDNAS2_SS_ID_NOUSE:
+		case sceDNAS2_SS_ID_NOT_JOIN_TO_CAT:
+			return CRCD(0xe8177760,"net_auth_error_unique");
+			break;
+
+		case sceDNAS2_SS_DL_NODATA:
+		case sceDNAS2_SS_DL_BEFORE_SERVICE:
+		case sceDNAS2_SS_DL_OUT_OF_SERVICE:
+		case sceDNAS2_SS_DL_NOT_UPDATED:
+			return CRCD(0x70295cf2,"net_auth_error_download");
+			break;
+
+		case sceDNAS2_SS_INVALID_PS2:
+		case sceDNAS2_SS_INVALID_HDD_BINDING:
+			return CRCD(0x448c00ef,"net_auth_error_machine");
+			break;
+
+		case sceDNAS2_SS_INVALID_MEDIA:
+		case sceDNAS2_SS_INVALID_AUTHDATA:
+			return CRCD(0x736c330,"net_auth_error_disc");
+			break;
+
+		case sceDNAS2_ERR_GLUE_ABORT:
+		case sceDNAS2_ERR_NET_PROXY:
+		case sceDNAS2_ERR_NET_SSL:
+		case sceDNAS2_ERR_NET_DNS_HOST_NOT_FOUND:
+		case sceDNAS2_ERR_NET_DNS_TRY_AGAIN:
+		case sceDNAS2_ERR_NET_DNS_NO_RECOVERY:
+		case sceDNAS2_ERR_NET_DNS_NO_DATA:
+		case sceDNAS2_ERR_NET_DNS_OTHERS:
+		case sceDNAS2_ERR_NET_EISCONN:
+		case sceDNAS2_ERR_NET_ETIMEOUT:
+		case sceDNAS2_ERR_NET_TIMEOUT:
+		case sceDNAS2_ERR_NET_ECONNREFUSED:
+		case sceDNAS2_ERR_NET_ENETUNREACH:
+		case sceDNAS2_ERR_NET_ENOTCONN:
+		case sceDNAS2_ERR_NET_ENOBUFS:
+		case sceDNAS2_ERR_NET_EMFILE:
+		case sceDNAS2_ERR_NET_EBADF:
+		case sceDNAS2_ERR_NET_EINVAL:
+		case sceDNAS2_ERR_NET_OTHERS:
+			return CRCD(0x310d58d7,"net_auth_error_network");
+			break;
+		default:
+			return CRCD(0xc7c67eb8,"net_auth_error_generic");
+			break;
+	}
+}
+
+static	uint32	s_get_error_footer( int code )
+{
+	switch( code )
+	{
+		case sceDNAS2_SS_BEFORE_SERVICE:
+		case sceDNAS2_SS_OUT_OF_SERVICE:
+		case sceDNAS2_SS_END_OF_SERVICE:
+		case sceDNAS2_SS_INVALID_SERVER:
+		case sceDNAS2_SS_INTERNAL_ERROR:
+		case sceDNAS2_SS_EXTERNAL_ERROR:
+		case sceDNAS2_SS_INVALID_PS2:
+		case sceDNAS2_SS_INVALID_HDD_BINDING:
+		case sceDNAS2_SS_INVALID_MEDIA:
+		case sceDNAS2_SS_INVALID_AUTHDATA:
+			return CRCD(0x16ff6a40,"net_auth_footer_contact");
+		case sceDNAS2_SS_SERVER_BUSY:
+		case sceDNAS2_SS_SESSION_TIME_OUT:
+		case sceDNAS2_SS_ID_NOUSE:
+		case sceDNAS2_SS_ID_NOT_JOIN_TO_CAT:
+		case sceDNAS2_SS_DL_NODATA:
+		case sceDNAS2_SS_DL_BEFORE_SERVICE:
+		case sceDNAS2_SS_DL_OUT_OF_SERVICE:
+		case sceDNAS2_SS_DL_NOT_UPDATED:
+			return CRCD(0x8c671de9,"net_auth_footer_empty");
+		case sceDNAS2_ERR_GLUE_ABORT:
+		case sceDNAS2_ERR_NET_PROXY:
+		case sceDNAS2_ERR_NET_SSL:
+		case sceDNAS2_ERR_NET_DNS_HOST_NOT_FOUND:
+		case sceDNAS2_ERR_NET_DNS_TRY_AGAIN:
+		case sceDNAS2_ERR_NET_DNS_NO_RECOVERY:
+		case sceDNAS2_ERR_NET_DNS_NO_DATA:
+		case sceDNAS2_ERR_NET_DNS_OTHERS:
+		case sceDNAS2_ERR_NET_EISCONN:
+		case sceDNAS2_ERR_NET_ETIMEOUT:
+		case sceDNAS2_ERR_NET_TIMEOUT:
+		case sceDNAS2_ERR_NET_ECONNREFUSED:
+		case sceDNAS2_ERR_NET_ENETUNREACH:
+		case sceDNAS2_ERR_NET_ENOTCONN:
+		case sceDNAS2_ERR_NET_ENOBUFS:
+		case sceDNAS2_ERR_NET_EMFILE:
+		case sceDNAS2_ERR_NET_EBADF:
+		case sceDNAS2_ERR_NET_EINVAL:
+		case sceDNAS2_ERR_NET_OTHERS:
+			return CRCD(0x3a190bc4,"net_auth_footer_network");
+		default:
+			return CRCD(0x16ff6a40,"net_auth_footer_contact");
+	}
+}
+
+static	uint32	s_get_error_footer_pal( int code )
+{
+	switch( code )
+	{
+		case sceDNAS2_SS_SERVER_BUSY:
+			return CRCD(0x8c671de9,"net_auth_footer_empty");
+		case sceDNAS2_SS_BEFORE_SERVICE:
+		case sceDNAS2_SS_OUT_OF_SERVICE:
+			return CRCD(0x99b34c96,"net_auth_footer_service_pal");
+		case sceDNAS2_SS_END_OF_SERVICE:
+			return CRCD(0x1a95350b,"net_auth_footer_central_pal");
+		case sceDNAS2_SS_SESSION_TIME_OUT:
+		case sceDNAS2_SS_INVALID_SERVER:
+		case sceDNAS2_SS_INTERNAL_ERROR:
+		case sceDNAS2_SS_EXTERNAL_ERROR:
+			return CRCD(0x3e6e83d0,"net_auth_footer_cont_customer_pal");
+		case sceDNAS2_SS_ID_NOUSE:
+		case sceDNAS2_SS_ID_NOT_JOIN_TO_CAT:
+		case sceDNAS2_SS_DL_NODATA:
+		case sceDNAS2_SS_DL_BEFORE_SERVICE:
+		case sceDNAS2_SS_DL_OUT_OF_SERVICE:
+		case sceDNAS2_SS_DL_NOT_UPDATED:
+			return CRCD(0x8c671de9,"net_auth_footer_empty");
+		case sceDNAS2_SS_INVALID_PS2:
+		case sceDNAS2_SS_INVALID_MEDIA:
+		case sceDNAS2_SS_INVALID_HDD_BINDING:
+			return CRCD(0xc13153cc,"net_auth_footer_customer_pal");
+		case sceDNAS2_SS_INVALID_AUTHDATA:
+			return CRCD(0x85cb1e0c,"net_auth_footer_clean_pal");
+		case sceDNAS2_ERR_GLUE_ABORT:
+		case sceDNAS2_ERR_NET_PROXY:
+		case sceDNAS2_ERR_NET_SSL:
+		case sceDNAS2_ERR_NET_DNS_HOST_NOT_FOUND:
+		case sceDNAS2_ERR_NET_DNS_TRY_AGAIN:
+		case sceDNAS2_ERR_NET_DNS_NO_RECOVERY:
+		case sceDNAS2_ERR_NET_DNS_NO_DATA:
+		case sceDNAS2_ERR_NET_DNS_OTHERS:
+		case sceDNAS2_ERR_NET_EISCONN:
+		case sceDNAS2_ERR_NET_ETIMEOUT:
+		case sceDNAS2_ERR_NET_TIMEOUT:
+		case sceDNAS2_ERR_NET_ECONNREFUSED:
+		case sceDNAS2_ERR_NET_ENETUNREACH:
+		case sceDNAS2_ERR_NET_ENOTCONN:
+		case sceDNAS2_ERR_NET_ENOBUFS:
+		case sceDNAS2_ERR_NET_EMFILE:
+		case sceDNAS2_ERR_NET_EBADF:
+		case sceDNAS2_ERR_NET_EINVAL:
+		case sceDNAS2_ERR_NET_OTHERS:
+			return CRCD(0x570be62b,"net_auth_footer_network_pal");
+			break;
+		default:
+			return CRCD(0x1a95350b,"net_auth_footer_central_pal");
+			break;
+	}
+}
+
+
+extern char _dnas_start[];
+extern char _dnas_size[];
+
+bool 		GameNet::Manager::ScriptAuthenticateClient( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	sceDNAS2Status_t status;
+	int result;
+	void *p_file;
+	char* memory;
+	
+	// Hardware will be flagged as Config::HARDWARE_PS2_DEVSYSTEM if we run a 
+	// .elf file.  This is probably different from what we are running on
+	// the cd, so just return true, and don't actually perform the DNAS check
+	// (Note, we must be running off a CD, as the check is only performed on CD burns)										  
+	// If we actually BOOT the cd on the dev system, it will think it's
+	// on a regular PS2, and the DNAS test will be performed
+	if 	( Config::CD() && Config::GetHardware() == Config::HARDWARE_PS2_DEVSYSTEM ) 
+	{
+		Script::RunScript( CRCD(0xbd54c02b,"connected_to_internet") );
+		s_extra_time = 0;
+		return true;
+	}
+#ifdef __DNAS_DEBUG__	
+	scePrintf( "Available Memory: %d\n", Mem::Manager::sHandle().Available());
+#endif
+
+	// Worst case, if we're out of memory because of leaks or fragmentation, just act as if
+	// DNAS authentication took place and verified the authenticity of the user
+	if( Mem::Manager::sHandle().Available() < (int) ( 1024 * 1024 * 1.7 ))
+	{
+#ifdef __DNAS_DEBUG__
+		scePrintf( "Could not allocate overlay buffer. Available mem: %d. Skipping test\n", Mem::Manager::sHandle().Available());
+#endif
+		Dbg_MsgAssert( 0, ( "Could not allocate enough safe memory for DNAS. Tell Steve\n" ));
+		Script::RunScript( CRCD(0xbd54c02b,"connected_to_internet") );
+		s_extra_time = 0;
+		return true;
+	}
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	memory = new char[(int) ( 1024 * 1024 * 1.7 )];
+	Mem::Manager::sHandle().PopContext();
+
+#ifdef __DNAS_DEBUG__
+	scePrintf( "Memory is at : 0x%x\n", memory );
+	scePrintf( "DNAS Start: 0x%x  DNAS Size: %d\n", (int) _dnas_start, (int) _dnas_size );
+#endif
+	
+	if(	((uint32) _dnas_start + (uint32) _dnas_size ) > (uint32) ( memory + (int) ( 1024 * 1024 * 1.7 )) ||
+		((uint32) _dnas_start < (uint32) memory ))
+	{
+		// Worst case, if we're out of memory because of leaks or fragmentation, just act as if
+		// DNAS authentication took place and verified the authenticity of the user
+
+		delete [] memory;
+#ifdef __DNAS_DEBUG__
+		scePrintf( "DNAS didn't fit in the memory that was allocated\n" );
+#endif
+		Dbg_MsgAssert( 0, ( "Could not allocate enough safe memory for DNAS. Tell Steve\n" ));
+		Script::RunScript( CRCD(0xbd54c02b,"connected_to_internet") );
+		s_extra_time = 0;
+		return true;
+	}
+		 
+	// Clear out the destination memory
+	memset( _dnas_start, 0, (int) _dnas_size );
+
+	p_file = NULL;
+	if( Config::gLanguage == Config::LANGUAGE_ENGLISH )
+	{
+		if( Config::gDisplayType == Config::DISPLAY_TYPE_NTSC )
+		{
+			p_file = File::Open( "dnas/dnas.bin", "rb" );
+		}
+		else
+		{
+			p_file = File::Open( "dnas/dnas_e.bin", "rb" );
+		}
+	}
+	else
+	{
+		p_file = File::Open( "dnas/dnas_e.bin", "rb" );
+	}
+
+#ifdef __DNAS_DEBUG__
+	if( p_file == NULL )
+	{
+		scePrintf( "Failed to open dnas/dnas(_e).bin" );
+	}
+#endif
+
+	File::Read( _dnas_start, 1, (int) _dnas_size, p_file );
+	File::Close( p_file );
+
+#ifndef __PURE_HEAP__
+	Dbg_MsgAssert( !Config::CD(), ( "DNAS will not work with memory debugging enabled." ));
+#endif
+
+#ifdef __USE_DNAS_DLL__
+	char* rel_mem, *buff;
+	void *p_file = File::Open( "dnas/dnas.rel", "rb" );
+	File::Seek( p_file, 0, SEEK_END );
+	int32 size = File::Tell( p_file );
+	Dbg_Printf( "***************** FILE SIZE IS %d\n", size );
+	File::Seek( p_file, 0, SEEK_SET );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	rel_mem = new char[size + 127];
+	Mem::Manager::sHandle().PopContext();
+
+	buff = (char*) (((int) rel_mem + 127 ) & ~127 ); // Align to 128-byte boundary
+
+	File::Read( buff, sizeof( char ), size, p_file );
+	File::Close( p_file );
+
+	if( snDllLoaded( buff, 0 ))
+	{
+		Dbg_MsgAssert( 0, ( "Failed to load dnas DLL\n" ));
+		return false;
+	}
+#endif
+
+	if( !Config::CD())
+	{
+		sceCdInit(SCECdINIT);
+		sceCdMmode(SCECdDVD);
+		sceCdDiskReady( 0 );
+	}
+
+	memset( &status, 0, sizeof( sceDNAS2Status_t )); 
+	result = netstart_dnas( 0, NULL, 0, vAUTH_TIMEOUT + s_extra_time, status );
+	Dbg_Printf( "dnas result was : %d\n", result );
+	//install_dnas( 0, NULL, 0, 0 );
+
+	delete [] memory;
+
+	if( result != 0 )
+	{
+		Script::CStruct* pParams;
+		uint32 desc;
+		uint32 footer;
+		const char* err_desc;
+		const char* err_footer;
+
+		s_extra_time += 10;
+
+		pParams = new Script::CStruct;
+		if( status.code == 0 )
+		{
+			pParams->AddInteger( CRCD(0x88eacf67,"code"), 0 );
+			pParams->AddInteger( CRCD(0xaf7c50a6,"sub_code"), 0 );
+			desc = CRCD(0xc7c67eb8,"net_auth_error_generic");
+			if( Config::PAL())
+			{
+				footer = CRCD(0xc13153cc,"net_auth_footer_customer_pal");
+			}
+			else
+			{
+				
+				footer = CRCD(0x16ff6a40,"net_auth_footer_contact");
+			}
+		}
+		else
+		{
+			Dbg_Printf( " Code: %d  SubCode: %d\n", status.code, status.sub_code );
+			pParams->AddInteger( CRCD(0x88eacf67,"code"), status.code );
+			pParams->AddInteger( CRCD(0xaf7c50a6,"sub_code"), status.sub_code );
+			desc = s_get_error_desc( status.sub_code );
+			if( Config::PAL())
+			{
+				footer = s_get_error_footer_pal( status.sub_code );
+			}
+			else
+			{
+				footer = s_get_error_footer( status.sub_code );
+			}
+		}
+		
+		err_desc = Script::GetString( desc, Script::ASSERT );
+		err_footer = Script::GetString( footer, Script::ASSERT );
+		Dbg_Printf( "ERROR DESC: %s\n", err_desc );
+		Dbg_Printf( "ERROR FOOTER: %s\n", err_footer );
+		pParams->AddString( CRCD(0xf44a53ab,"desc"), err_desc );
+		pParams->AddString( CRCD(0x1dcefaac,"footer"), err_footer );
+		Script::RunScript( CRCD(0xc3bb2b5a,"display_auth_error"), pParams );
+
+		delete pParams;
+		return false;
+	}
+
+	Dbg_Printf( "Success: Showing disclaimer\n" );
+	Script::RunScript( CRCD(0xbd54c02b,"connected_to_internet") );
+	s_extra_time = 0;
+	return true;
+}
+
+bool GameNet::Manager::ScriptWriteDNASBinary( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	void *p_file;
+	static bool written = false;
+
+	if( Config::CD())
+	{
+		written = true;
+	}
+	
+	if( !written ) 
+	{
+		if( Config::gLanguage == Config::LANGUAGE_ENGLISH )
+		{
+			if( Config::gDisplayType == Config::DISPLAY_TYPE_NTSC )
+			{
+				p_file = File::Open( "dnas/dnas.bin", "wb" );
+			}
+			else
+			{
+				p_file = File::Open( "dnas/dnas_e.bin", "wb" );
+			}
+		}
+		else
+		{
+			p_file = File::Open( "dnas/dnas_e.bin", "wb" );
+		}
+		File::Write( _dnas_start, 1, (int) _dnas_size, p_file );
+		File::Flush( p_file );
+		File::Close( p_file );
+
+		written = true;
+	}
+
+	return true;
+}
+
+
diff --git a/Code/Sk/GameNet/Ngps/p_buddy.cpp b/Code/Sk/GameNet/Ngps/p_buddy.cpp
new file mode 100644
index 0000000..b9f8dd5
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_buddy.cpp
@@ -0,0 +1,1243 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_buddy.cpp												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	PS2 Buddy List Code 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vBUDDY_CONNECT_TIMEOUT Tmr::Seconds( 10 )
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static 	Tmr::Time	s_connect_start_time = 0;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+void BuddyMan::s_error_callback( GPConnection* connection, void* arg, void* param )
+{
+    GPErrorArg* error;
+	BuddyMan* buddy_man;
+
+	error = (GPErrorArg*) arg;
+	buddy_man = (BuddyMan*) param;
+	
+	Dbg_Printf( "******** ERROR: %d %s\n", error->errorCode, error->errorString );
+
+	if( error->fatal )
+	{
+		Dbg_Printf( "******** ERROR IS FATAL\n" );
+	}
+	
+	switch( error->errorCode )
+	{
+		case GP_NEWUSER_BAD_PASSWORD:
+			Dbg_Printf( "Bad password\n" );
+			Script::RunScript( "create_wrong_profile_password_dialog" );
+			buddy_man->Disconnect();
+			break;
+		case GP_NEWUSER_BAD_NICK:
+			Dbg_Printf( "Bad nick (already exists?) Trying to connect now\n" );
+			buddy_man->m_state = vSTATE_BEGIN_LOGIN;
+			break;
+		case GP_ADDBUDDY_ALREADY_BUDDY:
+			Script::RunScript( "failed_add_buddy_already_buddy" );
+			break;
+		case GP_LOGIN_CONNECTION_FAILED:
+			buddy_man->Disconnect();
+			Script::RunScript( "CreateBuddyLoginFailedDialog" );
+			break;
+		default:
+			Dbg_Printf( "Not handled: %d\n", error->errorCode );
+			break;
+		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BuddyMan::s_buddy_request( GPConnection* connection, void* arg, void* param )
+{
+	BuddyMan* buddy_man;
+	GPRecvBuddyRequestArg* request;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	
+	buddy_man = (BuddyMan*) param;
+	request = (GPRecvBuddyRequestArg*) arg;
+	
+	// Always accept
+	gpAuthBuddyRequest( &buddy_man->m_gp_conn, request->profile );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BuddyMan::s_buddy_status( GPConnection* connection, void* arg, void* param )
+{
+	BuddyMan* buddy_man;
+	GPRecvBuddyStatusArg* status_arg;
+	GPBuddyStatus status;
+	Lst::Search< BuddyInfo > sh;
+	BuddyInfo* buddy;
+	char* join_info;
+	char* location_str;
+	bool handled;
+	int i;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	buddy_man = (BuddyMan*) param;
+	status_arg = (GPRecvBuddyStatusArg*) arg;
+    
+	gpGetBuddyStatus( &buddy_man->m_gp_conn, status_arg->index, &status );
+
+	//Dbg_Printf( "Got buddy status %d for %d : %s, %s\n", status.status, status.profile, status.statusString, status.locationString );
+
+	handled = false;
+	for( buddy = sh.FirstItem( buddy_man->m_buddies ); buddy; buddy = sh.NextItem())
+	{
+		if( buddy->m_Profile == status.profile )
+		{
+			Script::CStruct* p_status_params;
+
+			buddy->m_Status = status.status;
+			
+			strcpy( buddy->m_StatusString, status.statusString );
+			if( buddy->m_Status == GP_PLAYING )
+			{
+				location_str = strchr( status.locationString, ':' );
+				location_str += 1;
+				location_str = strchr( location_str, ':' );
+				location_str += 1;
+				location_str = strchr( location_str, ':' );
+				location_str += 1;
+	
+				strcpy( buddy->m_Location, location_str );
+				join_info = strtok( status.locationString, ":" );
+				Dbg_Assert( join_info != NULL );
+				buddy->m_JoinIP = atoi( join_info );
+				join_info = strtok( NULL, ":" );
+				Dbg_Assert( join_info != NULL );
+				buddy->m_JoinPrivateIP = atoi( join_info );
+				join_info = strtok( NULL, ":" );
+				Dbg_Assert( join_info != NULL );
+				buddy->m_JoinPort = atoi( join_info );
+			}
+			else
+			{
+				strcpy( buddy->m_Location, status.locationString );
+			}
+
+			if( buddy->m_Pending )
+			{
+				// This was a pending addition.  Add him to our permanent list
+				GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+				Prefs::Preferences*	prefs;
+				Script::CArray* buddy_array, *new_array;
+				Script::CStruct* new_buddy;
+				Script::CStruct* params;
+                      
+				params = new Script::CStruct;	
+				params->AddString( "net_name", buddy->m_Nick );
+
+				Script::RunScript( "added_buddy", params );
+				delete params;
+
+				buddy->m_Pending = false;
+
+				prefs = gamenet_man->GetNetworkPreferences();
+				buddy_array = prefs->GetPreferenceArray( Script::GenerateCRC( "buddy_array" ));
+
+				new_array = new Script::CArray;
+				new_array->SetSizeAndType( buddy_array->GetSize() + 1, ESYMBOLTYPE_STRUCTURE );
+
+				for( i = 0; i < (int) buddy_array->GetSize(); i++ )
+				{
+					Script::CStruct* entry, *new_entry;
+
+					entry = buddy_array->GetStructure( i );
+					new_entry = new Script::CStruct;
+					*new_entry = *entry;
+					new_array->SetStructure( i, new_entry );
+				}
+
+				new_buddy = new Script::CStruct;
+				new_buddy->AddInteger( "profile", buddy->m_Profile );
+				new_buddy->AddString( "nick", buddy->m_Nick );
+				new_array->SetStructure( i, new_buddy );
+				
+				prefs->SetPreference( Script::GenerateCRC( "buddy_array" ), new_array );
+			}
+
+			p_status_params = new Script::CScriptStructure;
+			p_status_params->AddComponent( Script::GenerateCRC( "name" ), ESYMBOLTYPE_STRING, buddy->m_Nick );
+			p_status_params->AddComponent( Script::GenerateCRC( "status" ), ESYMBOLTYPE_STRING, buddy->m_StatusString );
+			p_status_params->AddComponent( Script::GenerateCRC( "location" ), ESYMBOLTYPE_STRING, buddy->m_Location );
+			p_status_params->AddComponent( Script::GenerateCRC( "id" ), ESYMBOLTYPE_NAME, Script::GenerateCRC( "first_lobby_buddy" ) + buddy->m_Profile );
+			Script::RunScript( "update_buddy_status", p_status_params );
+			delete p_status_params;
+
+			handled = true;
+			break;
+		}
+	}
+	
+	if( !handled )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		Prefs::Preferences*	prefs;
+		Script::CArray* buddy_array;
+		int profile;
+
+		prefs = gamenet_man->GetNetworkPreferences();
+
+		buddy = new BuddyInfo;
+		buddy->m_Profile = status.profile;
+		buddy->m_Status = status.status;
+		strcpy( buddy->m_StatusString, status.statusString );
+		sprintf( buddy->m_Nick, "ProSkater" );
+
+		if( buddy->m_Status == GP_PLAYING )
+		{
+			location_str = strchr( status.locationString, ':' );
+			location_str += 1;
+			location_str = strchr( location_str, ':' );
+			location_str += 1;
+			location_str = strchr( location_str, ':' );
+			location_str += 1;
+	
+			strcpy( buddy->m_Location, location_str );
+			join_info = strtok( status.locationString, ":" );
+			Dbg_Assert( join_info );
+			buddy->m_JoinIP = atoi( join_info );
+			join_info = strtok( NULL, ":" );
+			Dbg_Assert( join_info );
+			buddy->m_JoinPrivateIP = atoi( join_info );
+			join_info = strtok( NULL, ":" );
+			Dbg_Assert( join_info );
+			buddy->m_JoinPort = atoi( join_info );
+		}
+		else
+		{
+			strcpy( buddy->m_Location, status.locationString );
+		}
+
+		buddy_man->m_buddies.AddToTail( buddy );
+
+		// We now need to find the name by which we know this buddy.  When we added them to our list
+		// we added an entry into our network preferences with a matching profile ID and the player's
+		// network name
+		buddy_array = prefs->GetPreferenceArray( Script::GenerateCRC( "buddy_array" ));
+		if( buddy_array )
+		{
+			Script::CStruct* entry;
+			for( i = 0; i < (int) buddy_array->GetSize(); i++ )
+			{
+				entry = buddy_array->GetStructure( i );
+				entry->GetInteger( "profile", &profile, Script::ASSERT );
+				if( profile == status.profile )
+				{
+					const char* nick = NULL;
+					entry->GetString( "nick", &nick, Script::ASSERT );
+
+					strcpy( buddy->m_Nick, nick );
+				}
+			}
+		}
+	}
+
+    Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BuddyMan::s_buddy_message( GPConnection* connection, void* arg, void* param )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BuddyMan::s_game_invite( GPConnection* connection, void* arg, void* param )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BuddyMan::s_transfer_callback( GPConnection* connection, void* arg, void* param )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void BuddyMan::s_connect_callback( GPConnection* connection, void* arg, void* param )
+{
+	BuddyMan* buddy_man;
+    GPConnectResponseArg* response;
+
+	buddy_man = (BuddyMan*) param;
+	response = (GPConnectResponseArg*) arg;
+	switch( response->result )
+	{
+		case GP_NO_ERROR:
+			//Dbg_Printf( "*************** CONNECTED, NO ERROR\n" );
+			buddy_man->Connected( response->profile );
+			break;
+		case GP_MEMORY_ERROR:
+			//Dbg_Printf( "*************** DIDNT CONNECT: MEMORY ERROR\n" );
+			break;
+		case GP_PARAMETER_ERROR:
+			//Dbg_Printf( "*************** DIDNT CONNECT: PARAMETER ERROR\n" );
+			break;
+		case GP_NETWORK_ERROR:
+			//Dbg_Printf( "*************** DIDNT CONNECT: NETWORK ERROR\n" );
+			break;
+		case GP_SERVER_ERROR:
+			//Dbg_Printf( "*************** DIDNT CONNECT: SERVER ERROR\n" );
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::s_buddy_logic_code( const Tsk::Task< BuddyMan >& task )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	BuddyMan& man = task.GetData();
+	static Tmr::Time s_last_process_time = 0;
+
+	switch( man.m_state )
+	{
+		case vSTATE_WAIT:
+			break;
+		case vSTATE_BEGIN_LOGIN:
+			man.Connect();
+			break;
+		case vSTATE_CREATING_PROFILE:
+		case vSTATE_CONNECTING:
+			if(( Tmr::GetTime() - s_connect_start_time ) > vBUDDY_CONNECT_TIMEOUT )
+			{
+				//Dbg_Printf( "Timing buddy server connection out\n" );
+				man.Disconnect();
+				Script::RunScript( "CreateBuddyLoginFailedDialog" );
+			}
+			else
+			{
+				gpProcess( &man.m_gp_conn );
+			}
+			break;
+		case vSTATE_LOGGED_IN:
+			if(( Tmr::GetTime() - s_last_process_time ) > Tmr::Seconds( 1 ))
+			{
+				gpProcess( &man.m_gp_conn );
+				s_last_process_time = Tmr::GetTime();
+			}
+			break;
+		default:
+			Dbg_Assert( 0 );
+			break;
+
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	BuddyMan::s_threaded_buddy_connect( BuddyMan* buddy_man )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Prefs::Preferences*	prefs;
+	GPResult result;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+	prefs = gamenet_man->GetNetworkPreferences();
+	const char* email = prefs->GetPreferenceString( Script::GenerateCRC( "profile_email" ), Script::GenerateCRC("ui_string") );
+	const char* password = prefs->GetPreferenceString( Script::GenerateCRC( "profile_password" ), Script::GenerateCRC("ui_string") );
+
+	Dbg_Printf( "Connecting to gp with %s : %s\n", email, password );
+	result = gpConnect( &buddy_man->m_gp_conn, "proskater", email, password, GP_FIREWALL, GP_NON_BLOCKING, s_connect_callback, buddy_man );
+
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	BuddyMan::s_threaded_buddy_create( BuddyMan* buddy_man )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Prefs::Preferences*	prefs;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+	prefs = gamenet_man->GetNetworkPreferences();
+	const char* email = prefs->GetPreferenceString( Script::GenerateCRC( "profile_email" ), Script::GenerateCRC("ui_string") );
+	const char* password = prefs->GetPreferenceString( Script::GenerateCRC( "profile_password" ), Script::GenerateCRC("ui_string") );
+
+	//Dbg_Printf( "************ Creating profile: Proskater for account %s, password %s\n", email, password );
+	gpConnectNewUser( &buddy_man->m_gp_conn, "proskater", email, password, GP_FIREWALL, GP_NON_BLOCKING, s_connect_callback, buddy_man );
+
+	/*if( buddy_man->m_command == vCOMMAND_LOGIN )
+	{
+		//Dbg_Printf( "Connecting instead\n" );
+		Script::RunScript( "log_in_profile" );
+		buddy_man->m_command = vCOMMAND_CONNECT;
+		s_connect_start_time = Tmr::GetTime();
+		result = gpConnect( &buddy_man->m_gp_conn, "proskater", email, password, GP_FIREWALL, GP_NON_BLOCKING, s_connect_callback, buddy_man );
+	}*/
+	//Dbg_Printf( "called gpconnect\n" );
+
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		BuddyMan::add_player_to_menu( BuddyInfo* buddy, bool allow_join, bool allow_remove )
+{
+	Script::CStruct* p_item_params;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	p_item_params = new Script::CStruct;	
+	char upper_nick[1024];
+
+	//Dbg_Printf( "****** add_player_to_menu. Allow Join %d\n", (int) allow_join );
+
+	sprintf( upper_nick, buddy->m_Nick );
+
+	p_item_params->AddString( "name", buddy->m_Nick );
+	p_item_params->AddString( "status", buddy->m_StatusString );
+	p_item_params->AddString( "location", buddy->m_Location );
+	p_item_params->AddChecksum( "id", Script::GenerateCRC( "first_lobby_buddy" ) + buddy->m_Profile );
+	if( allow_join || allow_remove )
+	{
+		// create the parameters that are passed to the X script
+		Script::CStruct *p_script_params= new Script::CStruct;
+		p_script_params->AddInteger( "profile", buddy->m_Profile );	
+		p_script_params->AddString( "nick", strupr( upper_nick ));	
+		p_script_params->AddString( "status", buddy->m_StatusString );	
+		p_script_params->AddString( "location", buddy->m_Location );
+		p_script_params->AddInteger( "join_ip", buddy->m_JoinIP );	
+		p_script_params->AddInteger( "join_private_ip", buddy->m_JoinPrivateIP );	
+		p_script_params->AddInteger( "join_port", buddy->m_JoinPort );	
+
+		if( allow_remove )
+		{
+			p_script_params->AddChecksum( NONAME, Script::GenerateCRC( "allow_remove" ));	
+		}
+
+		if( allow_join && ( buddy->m_Status == GP_PLAYING ))
+		{
+			//Dbg_Printf( "****** GP_PLAYING\n" );
+			p_script_params->AddChecksum( NONAME, Script::GenerateCRC( "allow_join" ));	
+		}
+
+		if( allow_join )
+		{
+			p_script_params->AddChecksum( NONAME, Script::GenerateCRC( "in_lobby" ));	
+		}
+		else
+		{
+			p_script_params->AddChecksum( "back_script", Script::GenerateCRC( "back_from_shell_buddy_options" ));   
+			p_script_params->AddChecksum( "remove_script", Script::GenerateCRC( "shell_remove_buddy" ));   
+		}
+		p_item_params->AddStructure( "pad_choose_params", p_script_params );
+		p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("present_buddy_options"));
+	}
+	else
+	{
+		p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("do_nothing"));
+	}
+	
+	p_item_params->AddChecksum( "parent_menu_id", Script::GenerateCRC( "lobby_buddy_list_menu" ));
+	if( allow_join )
+	{
+		p_item_params->AddFloat( "scale", 1.1f );
+	}                                            
+
+	//Script::RunScript("make_text_sub_menu_item",p_item_params);
+	Script::RunScript("homie_list_add_item",p_item_params);
+	delete p_item_params;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		BuddyMan::fill_buddy_list( bool clear_list, bool allow_join, bool allow_remove )
+{
+	Lst::Search< BuddyInfo > sh;
+	BuddyInfo* buddy;
+
+	if( clear_list )
+	{
+		//Dbg_Printf( "Clearing buddy list menu\n" );
+		Script::RunScript( "destroy_lobby_buddy_list_children" );
+	}
+
+	for( buddy = sh.FirstItem( m_buddies ); buddy; buddy = sh.NextItem())
+	{
+		if( buddy->m_Pending )
+		{
+			continue;
+		}
+		add_player_to_menu( buddy, allow_join, allow_remove );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		BuddyMan::fill_prospective_buddy_list( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		if( player->IsLocalPlayer())
+		{
+			continue;
+		}
+		if( player->m_Profile && !IsAlreadyMyBuddy( player->m_Profile ))
+		{
+			Script::CStruct* p_item_params;
+	
+			//Dbg_Printf( "Adding %s of %d to the list\n", player->m_Name, player->m_Profile );
+			p_item_params = new Script::CStruct;	
+			p_item_params->AddString( "text", player->m_Name );
+			p_item_params->AddChecksum( "id", player->m_Profile );
+			p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("add_buddy"));
+			p_item_params->AddChecksum( "parent_menu_id", Script::GenerateCRC( "lobby_buddy_list_menu" ));
+		
+			// create the parameters that are passed to the X script
+			Script::CStruct *p_script_params= new Script::CStruct;
+			p_script_params->AddInteger( "profile", player->m_Profile );	
+			p_script_params->AddString( "nick", player->m_Name );	
+			p_item_params->AddStructure( "pad_choose_params", p_script_params );
+			p_item_params->AddFloat( "scale", 1.1f );
+
+			Script::RunScript("make_text_sub_menu_item",p_item_params);
+
+			delete p_item_params;
+			delete p_script_params;
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::add_buddy( char *name, int profile )
+{
+	GPResult result;
+	BuddyInfo* new_buddy;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	new_buddy = new BuddyInfo;
+	strcpy( new_buddy->m_Nick, name );
+	new_buddy->m_Profile = profile;
+	new_buddy->m_Status = 0;
+	new_buddy->m_Pending = true;
+
+	m_buddies.AddToTail( new_buddy );
+    
+	result = gpSendBuddyRequest( &m_gp_conn, profile, "" );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::cancel_add_buddy( void )
+{
+	BuddyInfo* buddy;
+	Lst::Search< BuddyInfo > sh;
+
+	for( buddy = sh.FirstItem( m_buddies ); buddy; buddy = sh.NextItem())
+	{
+		if( buddy->m_Pending )
+		{
+			delete buddy;
+			return;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::remove_buddy( int profile )
+{
+	BuddyInfo* buddy;
+	Lst::Search< BuddyInfo > sh;
+	Script::CStruct* p_struct;
+
+	for( buddy = sh.FirstItem( m_buddies ); buddy; buddy = sh.NextItem())
+	{
+		if( buddy->m_Profile == profile )
+		{
+			delete buddy;
+			break;
+		}
+	}
+
+	gpDeleteBuddy( &m_gp_conn, profile );
+                      
+	p_struct = new Script::CStruct;
+	p_struct->AddChecksum( "id", Script::GenerateCRC( "first_lobby_buddy" ) + profile );
+
+	Script::RunScript( "removed_buddy", p_struct );
+	delete p_struct;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+BuddyMan::BuddyMan( void )
+{
+	GPResult result;
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+	
+	result = gpInitialize( &m_gp_conn, vGAMESPY_PRODUCT_ID );
+	Dbg_Assert( result == GP_NO_ERROR );
+
+	result = gpSetCallback( &m_gp_conn, GP_ERROR, s_error_callback, this );
+	Dbg_Assert( result == GP_NO_ERROR );
+
+	result = gpSetCallback( &m_gp_conn, GP_RECV_BUDDY_REQUEST, s_buddy_request, this );
+	Dbg_Assert( result == GP_NO_ERROR );
+
+	result = gpSetCallback( &m_gp_conn, GP_RECV_BUDDY_STATUS, s_buddy_status, this );
+	Dbg_Assert( result == GP_NO_ERROR );
+
+	result = gpSetCallback( &m_gp_conn, GP_RECV_BUDDY_MESSAGE, s_buddy_message, this );
+	Dbg_Assert( result == GP_NO_ERROR );
+
+	result = gpSetCallback( &m_gp_conn, GP_RECV_GAME_INVITE, s_game_invite, this );
+	Dbg_Assert( result == GP_NO_ERROR );
+
+	result = gpSetCallback( &m_gp_conn, GP_TRANSFER_CALLBACK, s_transfer_callback, this );
+	Dbg_Assert( result == GP_NO_ERROR );
+
+	m_buddy_logic_task = new Tsk::Task< BuddyMan > ( s_buddy_logic_code, *this );
+
+	m_logged_in = false;
+	m_state = vSTATE_WAIT;
+	m_profile = 0;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+BuddyMan::~BuddyMan( void )
+{
+	delete m_buddy_logic_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::Connected( GPProfile profile )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	m_logged_in = true;
+	m_profile = profile;
+	m_state = vSTATE_LOGGED_IN;
+
+	//Dbg_Printf( "******************** PROFILE IS %d\n", m_profile );
+	Script::RunScript( "profile_logged_in" );
+    gpSetStatus( &m_gp_conn, GP_ONLINE, (char*) Script::GetString( "homie_status_online" ), 
+										(char*) Script::GetString( "homie_status_logging_in" ));
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::IsLoggedIn( void )
+{
+	return m_logged_in;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::Connect( void )
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	Thread::PerThreadStruct	net_thread_data;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+	
+	m_state = vSTATE_CONNECTING;
+	s_connect_start_time = Tmr::GetTime();
+
+	Script::RunScript( "log_in_profile" );
+
+    net_thread_data.m_pEntry = s_threaded_buddy_connect;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+	StartThread( gamenet_man->GetNetThreadId(), this );
+
+	if( !m_buddy_logic_task->InList())
+	{
+		mlp_man->AddLogicTask( *m_buddy_logic_task );
+	}
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::CreateProfile( void )
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	Thread::PerThreadStruct	net_thread_data;
+
+	m_state = vSTATE_CREATING_PROFILE;
+	s_connect_start_time = Tmr::GetTime();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	
+	net_thread_data.m_pEntry = s_threaded_buddy_create;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+	StartThread( gamenet_man->GetNetThreadId(), this );
+
+	mlp_man->AddLogicTask( *m_buddy_logic_task );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::Disconnect( void )
+{   
+	BuddyInfo* buddy, *next;
+	Lst::Search< BuddyInfo > sh;
+	
+	//Dbg_Printf( "***** Disconnecting from presence system\n" );
+	gpDisconnect( &m_gp_conn );
+	m_buddy_logic_task->Remove();
+	m_logged_in = false;
+
+	/*SetStatusAndLocation( GP_OFFLINE, 	(char*) Script::GetString( "homie_status_offline" ), 
+										"" );*/
+	for( buddy = sh.FirstItem( m_buddies ); buddy; buddy = next )
+	{
+		next = sh.NextItem();
+		delete buddy;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		BuddyMan::GetProfile( void )
+{
+	return m_profile;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::IsAlreadyMyBuddy( int profile )
+{
+	BuddyInfo* buddy;
+	Lst::Search< BuddyInfo > sh;
+	
+	for( buddy = sh.FirstItem( m_buddies ); buddy; buddy = sh.NextItem())
+	{
+		if( buddy->m_Profile == profile )
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		BuddyMan::NumBuddies( void )
+{
+	return m_buddies.CountItems();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::SetStatusAndLocation( int status, char* status_string, char* location )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	//Dbg_Printf( "Setting status to %d : %s , %s\n", status, status_string, location );
+	gpSetStatus( &m_gp_conn, (GPEnum) status, status_string, location );
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptSetUniqueId(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Script::CStruct* new_params;
+	Prefs::Preferences*	prefs;
+	Tmr::Time time;
+	int ms_time;
+	char str_time[32];
+
+	time = Tmr::GetTime();
+	ms_time = (int) time;
+
+	//Dbg_Printf( "*************** (Getting Time) Setting unique id to %d\n", ms_time );
+	
+	prefs = gamenet_man->GetNetworkPreferences();
+	new_params = new Script::CStruct;
+	sprintf( str_time, "%d", ms_time );
+	new_params->AddString( "ui_string", str_time );
+	prefs->SetPreference( Script::GenerateCRC( "unique_id" ), new_params );
+	delete new_params;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptProfileLoggedIn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	BuddyMan* buddy_man;
+
+	buddy_man = gamenet_man->mpBuddyMan;
+	return buddy_man->IsLoggedIn();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptProfileLogIn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Prefs::Preferences*	prefs;
+	
+	prefs = gamenet_man->GetNetworkPreferences();
+	uint32 success = prefs->GetPreferenceChecksum( Script::GenerateCRC( "profile_success" ), Script::GenerateCRC( "checksum" ));
+	if( success != Script::GenerateCRC( "boolean_true" ))
+	{
+		return false;
+	}
+
+	gamenet_man->mpBuddyMan->Connect();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptProfileLogOff(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	BuddyMan* buddy_man;
+
+	buddy_man = gamenet_man->mpBuddyMan;
+	buddy_man->Disconnect();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptCreateProfile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	
+	gamenet_man->mpBuddyMan->CreateProfile();
+    
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptFillBuddyList(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	bool clear_list, allow_join, allow_remove;
+
+	clear_list = pParams->ContainsFlag( "clear_list" );
+	allow_join = pParams->ContainsFlag( "allow_join" );
+	allow_remove = pParams->ContainsFlag( "allow_remove" );
+
+	//Dbg_Printf( "****** ScripFillBuddyList. Allow Join %d\n", (int) allow_join );
+	gamenet_man->mpBuddyMan->fill_buddy_list( clear_list, allow_join, allow_remove );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptFillProspectiveBuddyList(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	
+	gamenet_man->mpBuddyMan->fill_prospective_buddy_list();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptAddBuddy(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	int profile;
+	const char* nick;
+
+	pParams->GetInteger( "profile", &profile, Script::ASSERT );
+	pParams->GetString( "nick", &nick, Script::ASSERT );
+
+	gamenet_man->mpBuddyMan->add_buddy((char *) nick, profile );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptCancelAddBuddy(Script::CStruct *pParams, Script::CScript *pScript)
+{                                                               
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+                                                                
+	gamenet_man->mpBuddyMan->cancel_add_buddy();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptRemoveBuddy(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int profile;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+                                                                
+	pParams->GetInteger( "profile", &profile, Script::ASSERT );
+	gamenet_man->mpBuddyMan->remove_buddy( profile );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptJoinBuddy(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	char location[1024];
+	int join_ip, join_private_ip, join_port;
+	const char* location_str;
+	bool observing, local;
+	Script::CStruct* p_struct;
+
+	observing = pParams->ContainsFlag( "observe" );
+	pParams->GetString( "location", &location_str, Script::ASSERT );
+	pParams->GetInteger( "join_ip", &join_ip, Script::ASSERT );
+	pParams->GetInteger( "join_private_ip", &join_private_ip, Script::ASSERT );
+	pParams->GetInteger( "join_port", &join_port, Script::ASSERT );
+	sprintf( location, "%d:%d:%d:%s", join_ip, join_private_ip, join_port, location_str );
+
+	local = false;
+	if((unsigned int) join_ip == peerGetLocalIP( gamenet_man->mpLobbyMan->GetPeer()))
+	{
+		join_ip = join_private_ip;
+		join_port = vHOST_PORT;
+		local = true;
+	}
+	
+	if( observing )
+	{
+		gamenet_man->SetJoinMode( vJOIN_MODE_OBSERVE );
+		gamenet_man->mpBuddyMan->SetStatusAndLocation( GP_PLAYING, (char*) Script::GetString( "homie_status_observing" ), location );
+	}
+	else
+	{
+		gamenet_man->SetJoinMode( vJOIN_MODE_PLAY );
+		gamenet_man->mpBuddyMan->SetStatusAndLocation( GP_PLAYING, (char*) Script::GetString( "homie_status_playing" ), location );
+	}
+
+	char *ip_addr;
+	struct in_addr addr;
+	uint32 cookie;
+		
+	p_struct = new Script::CStruct;
+
+	addr.s_addr = join_ip;
+	ip_addr = inet_ntoa( addr );
+	p_struct->AddComponent( NONAME, ESYMBOLTYPE_INTEGER, join_port );
+	p_struct->AddComponent( NONAME, ESYMBOLTYPE_STRING, ip_addr );
+		
+	if( !local )
+	{
+		cookie = Mth::Rnd( 65535 );
+		p_struct->AddComponent( CRCD( 0x751f4599, "cookie" ), ESYMBOLTYPE_NAME, cookie );
+	}
+		
+	Script::RunScript( "net_chosen_join_server", p_struct );
+	delete p_struct;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptHasBuddies(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	return ( gamenet_man->mpBuddyMan->NumBuddies() > 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptBuddyListFull(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	return ( gamenet_man->mpBuddyMan->NumBuddies() >= vMAX_BUDDIES );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	BuddyMan::ScriptSetLobbyStatus(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	Dbg_Assert( gamenet_man->mpBuddyMan->IsLoggedIn());
+	gamenet_man->mpBuddyMan->SetStatusAndLocation( GP_CHATTING, (char*) Script::GetString( "homie_status_chatting" ), 
+																gamenet_man->mpLobbyMan->GetLobbyName());
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+BuddyInfo::BuddyInfo( void )
+: Lst::Node< BuddyInfo > ( this )
+{
+	m_Pending = false;
+	m_Status = 0;
+	m_Location[0] = '\0';
+	sprintf( m_StatusString, "Offline" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/Ngps/p_buddy.h b/Code/Sk/GameNet/Ngps/p_buddy.h
new file mode 100644
index 0000000..4666242
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_buddy.h
@@ -0,0 +1,176 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_buddy.h												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	PS2 Buddy List Code 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_NGPS_P_BUDDY_H
+#define __GAMENET_NGPS_P_BUDDY_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace GameNet
+{
+
+enum
+{
+	vCOMMAND_NONE,
+	vCOMMAND_CONNECT,
+	vCOMMAND_LOGIN
+};
+
+enum
+{
+	vSTATE_WAIT,
+	vSTATE_CREATING_PROFILE,
+	vSTATE_CONNECTING,
+	vSTATE_BEGIN_LOGIN,
+	vSTATE_LOGGED_IN
+};
+
+enum
+{
+	vMAX_BUDDIES = 30
+};
+
+class BuddyInfo : public Lst::Node< BuddyInfo >
+{
+public:
+	BuddyInfo( void );
+
+	char	m_Nick[GP_NICK_LEN];
+	int		m_Profile;
+	int		m_Status;
+	bool	m_Pending;
+	int		m_JoinIP;
+	int		m_JoinPrivateIP;
+	int		m_JoinPort;
+	char	m_Location[GP_LOCATION_STRING_LEN];
+	char	m_StatusString[GP_STATUS_STRING_LEN];
+};
+
+class BuddyMan
+{
+public:
+	BuddyMan( void );
+	~BuddyMan( void );
+
+	void	Connect( void );
+	void	CreateProfile( void );
+	void	Disconnect( void );
+	void	Connected( GPProfile profile );
+	bool	IsLoggedIn( void );
+	bool	IsAlreadyMyBuddy( int profile );
+	int		NumBuddies( void );
+
+	int		GetProfile( void );
+	void	SetStatusAndLocation( int status, char* status_string, char* location );
+
+	static	bool		ScriptSetUniqueId(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptProfileLoggedIn(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptCreateProfile(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptProfileLogOff(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptProfileLogIn(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptFillBuddyList(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptFillProspectiveBuddyList(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptAddBuddy(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptCancelAddBuddy(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptRemoveBuddy(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptJoinBuddy(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptHasBuddies(Script::CStruct *pParams, Script::CScript *pScript);
+	static 	bool		ScriptBuddyListFull(Script::CStruct *pParams, Script::CScript *pScript);
+	static 	bool		ScriptSetLobbyStatus(Script::CStruct *pParams, Script::CScript *pScript);
+    
+private:
+	GPConnection	m_gp_conn;
+	GPProfile		m_profile;
+	int				m_state;
+
+	static	void s_connect_callback( GPConnection* connection, void* arg, void* param );
+
+	static	void s_error_callback( GPConnection* connection, void* arg, void* param );
+	static 	void s_buddy_request( GPConnection* connection, void* arg, void* param );
+	static	void s_buddy_status( GPConnection* connection, void* arg, void* param );
+	static	void s_buddy_message( GPConnection* connection, void* arg, void* param );
+	static	void s_game_invite( GPConnection* connection, void* arg, void* param );
+	static	void s_transfer_callback( GPConnection* connection, void* arg, void* param );
+
+	static	void s_threaded_buddy_connect( BuddyMan* buddy_man );
+	static	void s_threaded_buddy_create( BuddyMan* buddy_man );
+
+	void		add_player_to_menu( BuddyInfo* buddy, bool allow_join, bool allow_remove );
+	void		fill_buddy_list( bool clear_list, bool allow_join, bool allow_remove );
+	void		fill_prospective_buddy_list( void );
+	void		add_buddy( char *name, int profile );
+	void		cancel_add_buddy( void );
+	void		remove_buddy( int profile );
+
+	Tsk::Task< BuddyMan >*					m_buddy_logic_task;
+	static	Tsk::Task< BuddyMan >::Code   	s_buddy_logic_code;
+
+	bool	m_logged_in;
+	Lst::Head< BuddyInfo > m_buddies;
+};
+
+
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_NGPS_P_BUDDY_H
+
+
diff --git a/Code/Sk/GameNet/Ngps/p_content.cpp b/Code/Sk/GameNet/Ngps/p_content.cpp
new file mode 100644
index 0000000..77f3f49
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_content.cpp
@@ -0,0 +1,1667 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		Lobby.cpp												**
+**																			**
+**	Created by:		04/4/02	-	spg											**
+**																			**
+**	Description:	Gamespy peer lobby implementation						**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// for downloading faces
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vHTTP_COMM_TIMEOUT	Tmr::Seconds( 25 )
+#define vMAX_FACE_SIZE		( 32 * 1024 )
+#define vMAX_FILE_SIZE	( 80 * 1024 )
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	ContentMan::s_progress_callback(	GHTTPRequest request,       // The request.
+											GHTTPState state,           // The current state of the request.
+											const char* buffer,        	// The file's bytes so far, NULL if state 0 )
+		{
+			content_man->m_percent_complete = (int) (((float) bytes_received / (float) total_size ) * 100.0f );
+		}
+	}
+
+	content_man->m_last_comm_time = Tmr::GetTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+GHTTPBool ContentMan::s_dl_completed_callback(	GHTTPRequest request,       // The request.
+										GHTTPResult result,         // The result (success or an error).
+										char* buffer,              // The file's bytes (only valid if ghttpGetFile[Ex] was used).
+										int buffer_len,              // The file's length.
+										void * param                // User-data.
+										)
+{
+	ContentMan* content_man;
+
+	content_man = (ContentMan*) param;
+
+	Dbg_Printf( "Got result %d\n", result );
+
+	content_man->m_percent_complete = 100;
+	if( result == GHTTPSuccess )
+	{
+		uint32 type;
+
+		content_man->m_result = vCONTENT_RESULT_SUCCESS;
+
+		type = 0;
+		switch( content_man->m_vault_type ) 
+		{
+			case 0x5be5569f:	// parks
+				type = CRCD(0x3bf882cc,"park");
+				break;
+			case 0x7364ea1:		// skaters
+				type = CRCD(0xb010f357,"optionsandpros");
+				break;
+			case 0x38dbe1d0:	// goals
+				type = CRCD(0x62896edf,"createdgoals");
+				break;
+			case 0x1e26fd3e:	// tricks
+				type = CRCD(0x61a1bc57,"cat");
+				break;
+			default:
+				Dbg_Assert( 0 );
+				break;
+		}
+		CFuncs::SetVaultData((uint8*) buffer, type );
+		
+		content_man->m_error = 0;
+	}
+	else
+	{
+		content_man->m_result = vCONTENT_RESULT_FAILURE;
+		content_man->m_error = result;
+	}
+	
+	return GHTTPTrue;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+GHTTPBool ContentMan::s_face_dl_completed_callback(	GHTTPRequest request,       // The request.
+													GHTTPResult result,         // The result (success or an error).
+													char* buffer,              // The file's bytes (only valid if ghttpGetFile[Ex] was used).
+													int buffer_len,              // The file's length.
+													void * param                // User-data.
+													)
+{
+	ContentMan* content_man;
+
+	content_man = (ContentMan*) param;
+
+	Dbg_Printf( "Got result %d\n", result );
+
+	content_man->m_percent_complete = 100;
+	if( result == GHTTPSuccess )
+	{
+		content_man->m_result = vCONTENT_RESULT_SUCCESS;
+		content_man->m_error = 0;
+		
+		// Copy the downloaded data to the skater profile
+		Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+		Obj::CSkaterProfile* pSkaterProfile = skate_mod->GetCurrentProfile();
+		Dbg_Assert( pSkaterProfile );
+		Dbg_MsgAssert( !pSkaterProfile->IsPro(), ( "Can only download face onto a custom skater.  UI must make the custom skater active before this point" ) );
+		Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+		Dbg_Assert( pAppearance );
+		Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+		Dbg_MsgAssert( pFaceTexture, ( "Appearance has no face texture" ) );
+		
+		// GJ:  The CFaceTexture is permanently allocated in the
+		// custom skater, so there should be no need to push 
+		// any heap contexts here...  if this assumption changes, 
+		// then we should probably push the skt_info heap
+		pFaceTexture->ReadTextureDataFromBuffer( (uint8*)buffer, 0 );
+		pFaceTexture->SetValid( true );
+
+		// rebuild the skater to display the new face
+		Script::RunScript( CRCD(0x808008ea,"refresh_skater_model") );
+	}
+	else
+	{
+		content_man->m_result = vCONTENT_RESULT_FAILURE;
+		content_man->m_error = result;
+	}
+    
+	return GHTTPTrue;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+GHTTPBool ContentMan::s_ul_completed_callback(	GHTTPRequest request,       // The request.
+										GHTTPResult result,         // The result (success or an error).
+										char* buffer,              // The file's bytes (only valid if ghttpGetFile[Ex] was used).
+										int buffer_len,              // The file's length.
+										void * param                // User-data.
+										)
+{
+	ContentMan* content_man;
+
+	content_man = (ContentMan*) param;
+
+	Dbg_Printf( "Got result %d\n", result );
+
+	content_man->m_percent_complete = 100;
+	if( result == GHTTPSuccess )
+	{
+		content_man->m_result = vCONTENT_RESULT_SUCCESS;
+	}
+	else
+	{
+		content_man->m_result = vCONTENT_RESULT_FAILURE;
+	}
+	
+	return GHTTPTrue;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::s_http_transfer_code( const Tsk::Task< ContentMan > &task )
+{
+	ContentMan& content_man = task.GetData();
+
+	if(( Tmr::GetTime() - content_man.m_last_comm_time ) > vHTTP_COMM_TIMEOUT )
+	{
+		if(	content_man.m_state != vCONTENT_STATE_IDLE )
+		{
+			delete [] content_man.m_buffer;
+		}
+
+		Dbg_Printf( "********* TIMING OUT!!!!\n" );
+		Dbg_Assert( content_man.m_transfer_failed_script );
+
+		ghttpCancelRequest( content_man.m_file_request );
+
+		Script::RunScript( content_man.m_transfer_failed_script );
+		content_man.m_state = vCONTENT_STATE_IDLE;
+		content_man.m_http_transfer_task->Remove();
+
+        //ghttpCleanup();
+		return;
+	}
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	ghttpThink();
+	Mem::Manager::sHandle().PopContext();
+
+	if( content_man.m_percent_complete < 100 )
+	{
+		if(( content_man.m_state == vCONTENT_STATE_DOWNLOADING ) ||
+		   ( content_man.m_state == vCONTENT_STATE_DOWNLOADING_FACE ))
+		{
+			Script::CStruct* p_item_params;
+			char pct_string[8];
+		
+			sprintf( pct_string, "%d%%", content_man.m_percent_complete );
+					
+			p_item_params = new Script::CStruct;
+			p_item_params->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, pct_string );
+			Dbg_Assert( content_man.m_update_progress_script );
+			Script::RunScript( content_man.m_update_progress_script, p_item_params );
+			delete p_item_params;
+		}
+	}
+	else
+	{
+		if( ( content_man.m_state == vCONTENT_STATE_DOWNLOADING ) ||
+			( content_man.m_state == vCONTENT_STATE_DOWNLOADING_FACE ))
+		{
+			delete [] content_man.m_buffer;
+		}
+
+		//WaitSema( content_man.m_thread_semaphore_id );
+		if(( content_man.m_state == vCONTENT_STATE_DOWNLOADING ) || 
+		   ( content_man.m_state == vCONTENT_STATE_DOWNLOADING_FACE ) ||
+		   ( content_man.m_state == vCONTENT_STATE_UPLOADING ))
+		{
+			int prev_state;
+			 
+			prev_state = content_man.m_state;
+			content_man.m_http_transfer_task->Remove();
+			content_man.m_state = vCONTENT_STATE_IDLE;
+
+			if( content_man.m_result == vCONTENT_RESULT_SUCCESS )
+			{
+				Script::CStruct* params;
+
+				params = new Script::CStruct;
+				params->AddChecksum( "type", content_man.m_vault_type );
+				if( prev_state == vCONTENT_STATE_UPLOADING )
+				{
+					Script::RunScript( "LaunchUploadCompleteDialog", params );
+				}
+				else
+				{
+					Dbg_Assert( content_man.m_transfer_complete_script );
+					Script::RunScript( content_man.m_transfer_complete_script, params );
+				}
+
+				delete params;
+			}
+			else
+			{
+				if( content_man.m_error == GHTTPFileNotFound )
+				{
+					Dbg_Assert( content_man.m_file_not_found_script );
+					Script::RunScript( content_man.m_file_not_found_script );
+				}
+				else
+				{
+					Dbg_Assert( content_man.m_transfer_failed_script );
+					Script::RunScript( content_man.m_transfer_failed_script );
+				}
+			}
+			if( ( prev_state == vCONTENT_STATE_DOWNLOADING ) ||
+				( prev_state == vCONTENT_STATE_DOWNLOADING_FACE ))
+			{
+				//delete [] content_man.m_buffer;
+			}
+			if( prev_state == vCONTENT_STATE_UPLOADING )
+			{
+				delete [] content_man.m_buffer;
+			}
+			
+			//content_man.m_state = vCONTENT_STATE_IDLE;
+			//content_man.m_http_transfer_task->Remove();
+			//ghttpCleanup();
+		}
+		else if( content_man.m_state == vCONTENT_STATE_GETTING_DIR_LIST )
+		{
+			if( content_man.m_result == vCONTENT_RESULT_SUCCESS )
+			{
+				char* token;
+				Directory* dir;
+
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+				Dbg_Printf( "**** Parsing directory listing\n" );
+				token = strtok( content_man.m_buffer, "\r\n" );
+				if( token )
+				{
+					do
+					{   
+						Dbg_Printf( "**** Got dir %s\n", token );
+						dir = new Directory;
+						content_man.m_directories.AddToHead( dir );
+						strcpy( dir->m_Name, token );
+					} while(( token = strtok( NULL, "\r\n" )));
+
+					content_man.m_cur_dir = content_man.m_next_dir_sh.FirstItem( content_man.m_directories );
+					if( content_man.m_cur_dir->m_Uploads )
+					{
+						content_man.m_cur_dir = content_man.m_next_dir_sh.NextItem();
+					}
+
+					Dbg_Printf( "**** Getting subdir listing for %s\n", content_man.m_cur_dir->m_Name );
+
+					content_man.m_state = vCONTENT_STATE_IDLE;
+					//ghttpCleanup();
+					delete [] content_man.m_buffer;
+
+					//SignalSema( content_man.m_thread_semaphore_id );
+					content_man.GetSubDirListing( content_man.m_cur_dir );
+					Mem::Manager::sHandle().PopContext();
+					return;
+				}
+
+				Mem::Manager::sHandle().PopContext();
+			}
+			else
+			{
+				content_man.m_state = vCONTENT_STATE_IDLE;
+				content_man.m_http_transfer_task->Remove();
+				//ghttpCleanup();
+				delete [] content_man.m_buffer;
+
+				Dbg_Assert( content_man.m_transfer_failed_script );
+				Script::RunScript( content_man.m_transfer_failed_script );
+			}
+		}
+		else if( content_man.m_state == vCONTENT_STATE_GETTING_SUB_DIR_LIST )
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+			Dbg_Printf( "Got sub_dir list\n" );
+
+			if( content_man.m_result == vCONTENT_RESULT_SUCCESS )
+			{
+				Content* content;
+				char* line, *colon, *token;
+				char fname[Content::vMAX_FILE_NAME_LEN + 1];
+				char buff[128];
+
+				line = strtok( content_man.m_buffer, "\r\n" );
+				if( line )
+				{
+					do
+					{
+						content = new Content;
+						content_man.m_cur_dir->m_content_list.AddToHead( content );
+
+						token = line;
+						colon = strstr( line, ":" );
+						Dbg_Assert( colon );
+						*colon = '\0';
+						strcpy( fname, token );
+
+						sprintf( content->m_Path, "http://www.thugonline.com/%s/%s/%s", content_man.m_vault_root, content_man.m_cur_dir->m_Name, fname );
+						Dbg_Printf( "Filename: %s\n", fname );
+
+						token = colon + 1;
+						colon = strstr( token, ":" );
+						Dbg_Assert( colon );
+						*colon = '\0';
+						strcpy( content->m_Name, token );
+						Dbg_Printf( "Display name: %s\n", content->m_Name );
+
+						token = colon + 1;
+						colon = strstr( token, ":" );
+						Dbg_Assert( colon );
+						*colon = '\0';
+						strcpy( content->m_Creator, token );
+						Dbg_Printf( "Creator: %s\n", content->m_Creator );
+
+						// Read the content size, if we're looking at parks
+						if( content_man.m_vault_type == CRCD(0x5be5569f,"parks") )
+						{
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( content->m_Size, token );
+							Dbg_Printf( "Size: %s\n", content->m_Size );
+
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( buff, token );
+							content->m_NumPieces = atoi( buff );
+							Dbg_Printf( "Pieces: %d\n", content->m_NumPieces );
+
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( buff, token );
+							content->m_NumGaps = atoi( buff );
+							Dbg_Printf( "Gaps: %d\n", content->m_NumGaps );
+
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( buff, token );
+							content->m_NumGoals = atoi( buff );
+							Dbg_Printf( "Goals: %d\n", content->m_NumGoals );
+
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( buff, token );
+							content->m_Theme = atoi( buff );
+							Dbg_Printf( "Theme: %d\n", content->m_Theme );
+
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( buff, token );
+							content->m_TODScript = atoi( buff );
+							Dbg_Printf( "Script: 0x%x\n", content->m_TODScript );
+						}
+
+						// Read the content score, if we're looking at tricks
+						if( content_man.m_vault_type == CRCD(0x1e26fd3e,"tricks") )
+						{
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( buff, token );
+							content->m_Score = atoi( buff );
+							Dbg_Printf( "Score: %d\n", content->m_Score );
+						}
+
+						// Read the content sex, if we're looking at skaters
+						if( content_man.m_vault_type == CRCD(0x7364ea1,"skaters") )
+						{
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							//strcpy( content->m_IsMale, token );
+							strcpy( buff, token );
+							content->m_IsMale = atoi( buff );
+							Dbg_Printf( "IsMale: %d\n", content->m_IsMale );
+						}
+
+						// Read the content sex, if we're looking at skaters
+						if( content_man.m_vault_type == CRCD(0x38dbe1d0,"goals") )
+						{
+							token = colon + 1;
+							colon = strstr( token, ":" );
+							Dbg_Assert( colon );
+							*colon = '\0';
+							strcpy( buff, token );
+							content->m_NumGoals = atoi( buff );
+							Dbg_Printf( "Goals: %d\n", content->m_NumGoals );
+						}
+
+						token = colon + 1;
+						colon = strstr( token, ":" );
+						Dbg_Assert( colon );
+						*colon = '\0';
+						strcpy( content->m_Checksum, token );
+						Dbg_Printf( "Checksum: %s\n", content->m_Checksum );
+
+						token = colon + 1;
+						strcpy( content->m_Description, token );
+						Dbg_Printf( "Desc: %s\n", content->m_Description );
+						
+
+						//Dbg_Printf( "////////////////////////\n" );
+						//Dbg_Printf( "Name: %s\n", content->m_Name );
+						//Dbg_Printf( "Creator: %s\n", content->m_Creator );
+						//Dbg_Printf( "Size: %s\n", content->m_Size );
+						//Dbg_Printf( "Checksum: %s\n", content->m_Checksum );
+						//Dbg_Printf( "Desc: %s\n", content->m_Description );
+					} while(( line = strtok( NULL, "\r\n" )));
+				}
+				
+				content_man.m_state = vCONTENT_STATE_IDLE;
+				//ghttpCleanup();
+				delete [] content_man.m_buffer;
+
+				content_man.m_cur_dir = content_man.m_next_dir_sh.NextItem();
+				if(( content_man.m_cur_dir == NULL ) || ( content_man.m_cur_dir->m_Uploads ))
+				{
+					Script::CStruct* pParams;
+					content_man.m_cur_dir = content_man.m_next_dir_sh.FirstItem( content_man.m_directories );
+
+					// We are done. Now display the first category of files
+					//Dbg_Printf( "**** Done downloading subdirectory listings\n" );
+					content_man.m_http_transfer_task->Remove();
+					pParams = new Script::CStruct;
+					pParams->AddString( "category", content_man.m_cur_dir->m_Name );
+					pParams->AddChecksum( "type", content_man.m_vault_type );
+					Script::RunScript( "net_vault_menu_create", pParams );
+					delete pParams;
+				}
+				else
+				{
+					content_man.GetSubDirListing( content_man.m_cur_dir );
+				}
+			}
+            else
+			{
+				content_man.m_state = vCONTENT_STATE_IDLE;
+				content_man.m_http_transfer_task->Remove();
+
+				//ghttpCleanup();
+				delete [] content_man.m_buffer;
+				Dbg_Assert( content_man.m_transfer_failed_script );
+				Script::RunScript( content_man.m_transfer_failed_script );
+			}
+
+			Mem::Manager::sHandle().PopContext();
+		}
+		else if( content_man.m_state == vCONTENT_STATE_GETTING_UPLOADED_LEVELS )
+		{
+			char* line, *colon, *token;
+			Content* content;
+
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+                                      
+			line = strtok( content_man.m_buffer, "\r\n" );
+			if( line )
+			{
+				do
+				{
+					content = new Content;
+					content_man.m_cur_dir->m_content_list.AddToHead( content );
+
+					//Dbg_Printf( "******************************************\n" );
+					//Dbg_Printf( "Line: %s\n", line );
+					sprintf( content->m_Path, "http://www.thugonline.com/%s/%s", content_man.m_vault_root, line );
+
+					token = line;
+					colon = strstr( line, ":" );
+					Dbg_Assert( colon );
+					*colon = '\0';
+					strcpy( content->m_Name, token );
+
+					token = colon + 1;
+					colon = strstr( token, ":" );
+					Dbg_Assert( colon );
+					*colon = '\0';
+					strcpy( content->m_Creator, token );
+
+					token = colon + 1;
+					colon = strstr( token, ":" );
+					Dbg_Assert( colon );
+					*colon = '\0';
+					strcpy( content->m_Size, token );
+
+					token = colon + 1;
+					colon = strstr( token, ":" );
+					Dbg_Assert( colon );
+					*colon = '\0';
+					strcpy( content->m_Checksum, token );
+
+					token = colon + 1;
+					strcpy( content->m_Description, token );
+
+					/*Dbg_Printf( "////////////////////////\n" );
+					Dbg_Printf( "Name: %s\n", content->m_Name );
+					Dbg_Printf( "Creator: %s\n", content->m_Creator );
+					Dbg_Printf( "Size: %s\n", content->m_Size );
+					Dbg_Printf( "Checksum: %s\n", content->m_Checksum );
+					Dbg_Printf( "Desc: %s\n", content->m_Description );
+					*/
+				} while(( line = strtok( NULL, "\r\n" )));
+			}
+
+			content_man.m_state = vCONTENT_STATE_IDLE;
+			content_man.m_http_transfer_task->Remove();
+
+			//ghttpCleanup();
+			delete [] content_man.m_buffer;
+
+			content_man.DownloadDirectoryListing();
+
+			Mem::Manager::sHandle().PopContext();
+		}
+		
+		//SignalSema( content_man.m_thread_semaphore_id );
+	}
+}
+
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+void		ContentMan::FreeDirectoryListing( void )
+{
+	Directory* dir, *next;
+	Lst::Search< Directory > sh;
+
+	for( dir = sh.FirstItem( m_directories ); dir; dir = next )
+	{
+		next = sh.NextItem();
+		delete dir;
+	}
+	ghttpCleanup();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		ContentMan::GetDirListing( char* path )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Thread::PerThreadStruct	net_thread_data;
+
+	sprintf( m_download_path, path );
+	//mlp_man->AddLogicTask( *m_http_transfer_task );
+	m_percent_complete = 0;
+	m_last_comm_time = Tmr::GetTime();
+
+	m_transfer_complete_script = CRCD(0x89990f,"LaunchDownloadCompleteDialog");
+	m_transfer_failed_script = CRCD(0x7335ab7f,"LaunchTransferFailedDialog");
+	m_file_not_found_script = CRCD(0xcf040665,"LaunchGeneralFileNotFoundDialog");
+	m_update_progress_script = CRCD(0x4b85ee64,"update_transfer_progress");
+
+	Dbg_Printf( "**** Downloading path %s\n", m_download_path );
+
+	net_thread_data.m_pEntry = s_threaded_download_directory_list;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+
+	//WaitSema( m_thread_semaphore_id );
+	StartThread( gamenet_man->GetNetThreadId(), this );
+	//SignalSema( m_thread_semaphore_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		ContentMan::GetSubDirListing( Directory* dir )
+{
+	char subdir_path[128];
+
+	sprintf( subdir_path, "http://www.thugonline.com/%s/%s/dir_list.txt", m_vault_root, dir->m_Name );
+	m_state = vCONTENT_STATE_GETTING_SUB_DIR_LIST;
+	m_result = vCONTENT_RESULT_UNDETERMINED;
+	GetDirListing( subdir_path );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		ContentMan::FillDirectoryListing( char* dir_text )
+{
+	char* token;
+	Script::CStruct* p_item_params;
+	Script::CStruct* p_script_params;
+
+	//Dbg_Printf( "printing buffer\n" );
+	//Dbg_Printf( "Buffer: %s\n", dir_text );
+
+	token = strtok( dir_text, "\t" );
+	if( token )
+	{
+		p_item_params = new Script::CStruct;
+		p_item_params->AddString( "text", token );
+		token = strtok( NULL, "\r\n" );
+		Dbg_Assert( token );
+
+		p_item_params->AddChecksum( "pad_choose_script", Script::GenerateCRC("download_selected_file"));
+
+		// create the parameters that are passed to the choose script
+		p_script_params= new Script::CStruct;
+		p_script_params->AddString( "file", token );	
+		p_item_params->AddStructure("pad_choose_params",p_script_params);			
+
+		Script::RunScript( "make_text_sub_menu_item", p_item_params );
+		delete p_item_params;
+		delete p_script_params;
+
+        while( token )
+		{   
+			p_item_params = new Script::CStruct;
+			
+			token = strtok( NULL, "\t" );
+			if( token == NULL )
+			{
+				break;
+			}
+
+			p_item_params->AddString( "text", token );
+	
+			p_item_params->AddChecksum( "pad_choose_script", Script::GenerateCRC("download_selected_file"));
+	
+			token = strtok( NULL, "\r\n" );
+			Dbg_Assert( token );
+
+			// create the parameters that are passed to the choose script
+			p_script_params= new Script::CStruct;
+			p_script_params->AddString( "file", token );	
+			p_item_params->AddStructure("pad_choose_params",p_script_params);			
+	
+			Script::RunScript( "make_text_sub_menu_item", p_item_params );
+			delete p_item_params;
+			delete p_script_params;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ContentMan::ContentMan( void )
+{
+	struct SemaParam params;
+
+	m_state = vCONTENT_STATE_IDLE;
+	m_percent_complete = 0;
+	m_last_comm_time = 0;
+	m_http_transfer_task = new Tsk::Task< ContentMan > ( s_http_transfer_code, *this );
+
+	params.initCount = 1;
+	params.maxCount = 10;
+
+	m_thread_semaphore_id = CreateSema( ¶ms );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ContentMan::~ContentMan( void )
+{
+	DeleteSema( m_thread_semaphore_id );
+	delete m_http_transfer_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		ContentMan::s_threaded_download_directory_list( ContentMan* content_man )
+{
+	WaitSema( content_man->m_thread_semaphore_id );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+	content_man->m_buffer = new char[100000];
+	memset( content_man->m_buffer, 0, 100000 );
+
+	content_man->m_file_request = ghttpGetEx( 	content_man->m_download_path,
+												NULL,
+												content_man->m_buffer,
+												100000,
+												NULL,
+												GHTTPFalse,
+												GHTTPFalse,
+												s_progress_callback,
+												s_ul_completed_callback,
+												content_man );
+    
+	//delete buffer;
+
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+
+	//printf( "******** EXITING DOWNLOAD DIRECTORY THREAD\n" );
+
+	Mem::Manager::sHandle().PopContext();
+
+	SignalSema( content_man->m_thread_semaphore_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::DownloadDirectoryListing( void )
+{
+	char path[128];
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+
+	m_state = vCONTENT_STATE_GETTING_DIR_LIST;
+	sprintf( path, "http://www.thugonline.com/%s/dir_list.txt", m_vault_root );
+	GetDirListing( path );
+	m_result = vCONTENT_RESULT_UNDETERMINED;;
+	mlp_man->AddLogicTask( *m_http_transfer_task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::DownloadFace( char* filename )
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Thread::PerThreadStruct	net_thread_data;
+	char path[256];
+    
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	m_state = vCONTENT_STATE_DOWNLOADING_FACE;
+	m_result = vCONTENT_RESULT_UNDETERMINED;
+	mlp_man->AddLogicTask( *m_http_transfer_task );
+	m_percent_complete = 0;
+	m_last_comm_time = Tmr::GetTime();
+
+	m_transfer_complete_script = CRCD(0x1d53d27f,"LaunchFaceDownloadCompleteDialog");
+	m_transfer_failed_script = CRCD(0x7335ab7f,"LaunchTransferFailedDialog");
+	m_file_not_found_script = CRCD(0x609f7535,"LaunchFileNotFoundDialog");
+	m_update_progress_script = CRCD(0x4b85ee64,"update_transfer_progress");
+
+	net_thread_data.m_pEntry = s_threaded_download_face;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+	
+	Dbg_Printf( "In DownloadFace. Path: %s\n", filename );
+	WaitSema( m_thread_semaphore_id );
+	
+	sprintf( path, "http://www.thugonline.com/faces/%s", filename );
+	Dbg_Printf( "Downloading %s\n", path );
+	StartThread( gamenet_man->GetNetThreadId(), (char*) path );
+	SignalSema( m_thread_semaphore_id );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptDownloadFace(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	ContentMan* content_man;
+	BuddyMan* buddy_man;
+	char filename[128];
+	const char* key;
+
+	pParams->GetString( "string", &key, Script::ASSERT );
+	sprintf( filename, "%s.img.ps2", key );
+	Dbg_Printf( "Downloading %s...\n", filename );
+
+	content_man = gamenet_man->mpContentMan;
+	buddy_man = gamenet_man->mpBuddyMan;
+	buddy_man->GetProfile();
+	content_man->DownloadFace( filename );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptDownloadDirectoryList(Script::CScriptStructure *p_item_params, Script::CScript *pScript)
+{
+    GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	ContentMan* content_man;
+	BuddyMan* buddy_man;
+
+    content_man = gamenet_man->mpContentMan;
+	buddy_man = gamenet_man->mpBuddyMan;
+	p_item_params->GetChecksum( NONAME, &content_man->m_vault_type , true );
+	switch( content_man->m_vault_type )
+	{
+		case 0x5be5569f:	// parks
+			sprintf( content_man->m_vault_root, "levels" );
+			break;
+		case 0x7364ea1:		// skaters
+			sprintf( content_man->m_vault_root, "skaters" );
+			break;
+		case 0x38dbe1d0:	// goals
+			sprintf( content_man->m_vault_root, "goals" );
+			break;
+		case 0x1e26fd3e:	// tricks
+			sprintf( content_man->m_vault_root, "tricks" );
+			break;
+		default:
+			Dbg_Assert( 0 );
+			break;
+			
+	}
+
+	if( buddy_man->GetProfile() == 18549782 )	// Gamespy profile of our level-verifying account
+	{
+		Directory* dir;
+		char path[128];
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+		dir = new Directory;
+		dir->m_Uploads = true;
+		content_man->m_directories.AddToHead( dir );
+		strcpy( dir->m_Name, "Uploads" );
+		content_man->m_cur_dir = content_man->m_next_dir_sh.FirstItem( content_man->m_directories );
+
+		content_man->m_state = vCONTENT_STATE_GETTING_UPLOADED_LEVELS;
+		content_man->m_result = vCONTENT_RESULT_UNDETERMINED;
+		sprintf( path, "http://www.thugonline.com/%s/uploads.txt", content_man->m_vault_root );
+		content_man->GetDirListing( path );
+		mlp_man->AddLogicTask( *content_man->m_http_transfer_task );
+		Mem::Manager::sHandle().PopContext();
+	}
+	else
+	{
+		content_man->DownloadDirectoryListing();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptFreeDirectoryListing(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	ContentMan* content_man;
+
+	content_man = gamenet_man->mpContentMan;
+	content_man->FreeDirectoryListing();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		ContentMan::s_threaded_download_file( const char* path )
+{
+	int max_size;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	ContentMan* content_man;
+
+	content_man = gamenet_man->mpContentMan;
+	WaitSema( content_man->m_thread_semaphore_id );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	max_size = content_man->m_buffer_size;
+
+	content_man->m_buffer = new char[max_size];
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+	//Dbg_Printf( "downloading %s, size: %d dl complete: %p\n", path, max_size, content_man->m_dl_complete );
+	gamenet_man->mpContentMan->m_file_request = ghttpGetEx( path,
+															NULL,
+															content_man->m_buffer,
+															max_size,
+															NULL,
+															GHTTPFalse,
+															GHTTPFalse,
+															s_progress_callback,
+															content_man->m_dl_complete,
+															content_man );
+    
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+    
+	Mem::Manager::sHandle().PopContext();
+	
+	SignalSema( gamenet_man->mpContentMan->m_thread_semaphore_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::s_threaded_download_face( const char* path )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	WaitSema( gamenet_man->mpContentMan->m_thread_semaphore_id );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	gamenet_man->mpContentMan->m_buffer = new char[vMAX_FACE_SIZE];
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+	gamenet_man->mpContentMan->m_file_request = ghttpGetEx( path,
+															NULL,
+															gamenet_man->mpContentMan->m_buffer,
+															vMAX_FACE_SIZE,
+															NULL,
+															GHTTPFalse,
+															GHTTPFalse,
+															s_progress_callback,
+															s_face_dl_completed_callback,
+															gamenet_man->mpContentMan );
+    
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+    
+	Mem::Manager::sHandle().PopContext();
+
+	SignalSema( gamenet_man->mpContentMan->m_thread_semaphore_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::SetPercentComplete( int percent_complete )
+{
+	m_percent_complete = percent_complete;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::SetResult( int result )
+{
+	m_result = result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::SetError( int error )
+{
+	m_error = error;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ContentMan::DownloadFile( DownloadContext* context )
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	ContentMan* content_man;
+	Thread::PerThreadStruct	net_thread_data;
+    
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	Dbg_Printf( "Downloading %s\n", context->m_path );
+	
+	content_man = gamenet_man->mpContentMan;
+	content_man->m_state = vCONTENT_STATE_DOWNLOADING;
+	content_man->m_result = vCONTENT_RESULT_UNDETERMINED;
+	mlp_man->AddLogicTask( *content_man->m_http_transfer_task );
+	content_man->m_percent_complete = 0;
+	content_man->m_last_comm_time = Tmr::GetTime();
+	
+	content_man->m_buffer_size = context->m_max_size;
+	content_man->m_transfer_failed_script = context->m_transfer_failed_script;
+	content_man->m_transfer_complete_script = context->m_transfer_complete_script;
+	content_man->m_update_progress_script = context->m_update_progress_script;
+	content_man->m_file_not_found_script = context->m_file_not_found_script;
+	content_man->m_dl_complete = context->m_dl_complete;
+
+	net_thread_data.m_pEntry = s_threaded_download_file;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+	
+	WaitSema( content_man->m_thread_semaphore_id );
+	StartThread( gamenet_man->GetNetThreadId(), context->m_path );
+	SignalSema( content_man->m_thread_semaphore_id );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptDownloadFile(Script::CScriptStructure *p_item_params, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	ContentMan* content_man;
+	const char* path;
+	DownloadContext context;
+	
+	p_item_params->GetString( "file", &path );
+
+	content_man = gamenet_man->mpContentMan;
+	
+	context.m_transfer_complete_script = CRCD(0x89990f,"LaunchDownloadCompleteDialog");
+	context.m_transfer_failed_script = CRCD(0x7335ab7f,"LaunchTransferFailedDialog");
+	context.m_file_not_found_script = CRCD(0xcf040665,"LaunchGeneralFileNotFoundDialog");
+	context.m_update_progress_script = CRCD(0x4b85ee64,"update_transfer_progress");
+	context.m_max_size = vMAX_FILE_SIZE;
+	sprintf( context.m_path, path );
+	context.m_dl_complete = s_dl_completed_callback;
+	content_man->DownloadFile( &context );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************
+
+static char* s_fix_name( char* filename )
+{
+	char* ch = filename;
+	
+	while( *ch )
+	{
+		if(( *ch >= '0' ) && ( *ch <= '9' ))
+		{
+			ch++;
+			continue;
+		}
+
+		if(( *ch >= 'A' ) && ( *ch <= 'Z' ))
+		{
+			ch++;
+			continue;
+		}
+
+		if(( *ch >= 'a' ) && ( *ch <= 'z' ))
+		{
+			ch++;
+			continue;
+		}
+
+		if(( *ch == '-' ) || ( *ch =='_' ))
+		{
+			ch++;
+			continue;
+		}
+
+		*ch = '_';
+		ch++;
+	}
+
+	return filename;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		ContentMan::s_threaded_upload_file( ContentMan* content_man )
+{
+	GHTTPPost post;
+	char *file_buffer; 
+	char filename[64], desc[1024], str[64];
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Prefs::Preferences* pPreferences;
+	Script::CStruct* pStructure;
+	const char* network_id;
+	int filesize;
+	uint32 checksum;
+
+	pPreferences = gamenet_man->GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("network_id") );
+	pStructure->GetText( "ui_string", &network_id, true );
+
+	WaitSema( content_man->m_thread_semaphore_id );
+	sockAPIregthr();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+	post = ghttpNewPost();
+	switch( content_man->m_vault_type )
+	{
+		case 0x5be5569f:	// parks
+		{
+			filesize = Script::CalculateBufferSize( content_man->m_upload_struct );
+			file_buffer = new char[filesize];
+			Script::WriteToBuffer( content_man->m_upload_struct, (uint8*) file_buffer, filesize );
+			checksum = Crc::GenerateCRC( file_buffer, filesize );
+		
+			strcpy( filename, content_man->m_upload_filename );
+			ghttpPostAddString( post, "player", network_id );
+			sprintf( str, "%d", content_man->m_Width );
+			ghttpPostAddString( post, "width", str );
+			sprintf( str, "%d", content_man->m_Height );
+			ghttpPostAddString( post, "height", str );
+			sprintf( str, "%d", checksum );
+			ghttpPostAddString( post, "checksum", str );
+			
+			sprintf( str, "%d", content_man->m_NumPieces );
+			ghttpPostAddString( post, "num_pieces", str );
+			sprintf( str, "%d", content_man->m_NumGaps );
+			ghttpPostAddString( post, "num_gaps", str );
+			sprintf( str, "%d", content_man->m_NumGoals );
+			ghttpPostAddString( post, "num_goals", str );
+			sprintf( str, "%d", content_man->m_Theme );
+			ghttpPostAddString( post, "theme", str );
+			sprintf( str, "0x%x", content_man->m_TODScript );
+			ghttpPostAddString( post, "tod_script", str );
+			break;
+		}
+
+		case 0x7364ea1:		// skaters
+		{
+			ghttpPostAddString( post, "filetype", "cas" );
+			sprintf( str, "%d", content_man->m_IsMale );
+			ghttpPostAddString( post, "is_male", str );
+			
+			filesize = Script::CalculateBufferSize( content_man->m_upload_struct );
+			file_buffer = new char[filesize];
+			Script::WriteToBuffer( content_man->m_upload_struct, (uint8*) file_buffer, filesize );
+			checksum = Crc::GenerateCRC( file_buffer, filesize );
+			strcpy( filename, content_man->m_upload_filename );
+			ghttpPostAddString( post, "player", network_id );
+			sprintf( str, "%d", checksum );
+			ghttpPostAddString( post, "checksum", str );
+				
+			Dbg_Printf( "Uploading file: %s\n", filename );
+
+			break;
+		}
+
+		case 0x38dbe1d0:	// goals
+		{
+			ghttpPostAddString( post, "filetype", "cag" );
+			sprintf( str, "%d", content_man->m_NumGoals );
+			ghttpPostAddString( post, "num_goals", str );
+
+			filesize = Script::CalculateBufferSize( content_man->m_upload_struct );
+			file_buffer = new char[filesize];
+			Script::WriteToBuffer( content_man->m_upload_struct, (uint8*) file_buffer, filesize );
+			checksum = Crc::GenerateCRC( file_buffer, filesize );
+			strcpy( filename, content_man->m_upload_filename );
+			ghttpPostAddString( post, "player", network_id );
+			sprintf( str, "%d", checksum );
+			ghttpPostAddString( post, "checksum", str );
+			break;
+		}
+		case 0x1e26fd3e:	// tricks
+		{
+			ghttpPostAddString( post, "filetype", "cat" );
+			sprintf( str, "%d", content_man->m_Score );
+			ghttpPostAddString( post, "score", str );
+
+			filesize = Script::CalculateBufferSize( content_man->m_upload_struct );
+			file_buffer = new char[filesize];
+			Script::WriteToBuffer( content_man->m_upload_struct, (uint8*) file_buffer, filesize );
+			checksum = Crc::GenerateCRC( file_buffer, filesize );
+			strcpy( filename, content_man->m_upload_filename );
+			ghttpPostAddString( post, "player", network_id );
+			sprintf( str, "%d", checksum );
+			ghttpPostAddString( post, "checksum", str );
+			break;
+		}
+		default:
+			file_buffer = NULL;
+			filesize = 0;
+			Dbg_Assert( 0 );
+			break;
+	}
+		
+	content_man->m_buffer = file_buffer;
+	content_man->m_buffer_size = filesize;
+	ghttpPostAddFileFromMemory( post, "filename", file_buffer, filesize, filename, NULL );
+
+	sprintf( desc, "%s", content_man->m_upload_description );
+	ghttpPostAddString( post, "description", desc );
+
+	ghttpPostAddString( post, "uniqueid", GOAGetUniqueID() );
+	switch( content_man->m_vault_type )
+	{
+		case 0x5be5569f:	// parks
+			ghttpPostAddString( post, "filetype", "cap" );
+			break;
+		case 0x7364ea1:		// skaters
+			ghttpPostAddString( post, "filetype", "cas" );
+			break;
+		case 0x38dbe1d0:	// goals
+			ghttpPostAddString( post, "filetype", "cag" );
+			break;
+		case 0x1e26fd3e:	// tricks
+			ghttpPostAddString( post, "filetype", "cat" );
+			break;
+		default:
+			Dbg_Assert( 0 );
+			break;
+	}
+	
+	// debugger log
+	ghttpPostAddString( post, "dblog", "1" );
+
+	content_man->m_file_request = ghttpPostEx( "http://www.thugonline.com/cgi-bin/upload2.pl", NULL, post, GHTTPFalse, GHTTPFalse, s_progress_callback, s_ul_completed_callback, content_man );	
+	
+    
+    // Deregister this thread with the sockets API
+	sockAPIderegthr();
+	Mem::Manager::sHandle().PopContext();
+
+	SignalSema( content_man->m_thread_semaphore_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptUploadFile(Script::CScriptStructure *p_item_params, Script::CScript *pScript)
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	ContentMan* content_man;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Thread::PerThreadStruct	net_thread_data;
+	const char* desc, *filename;
+
+	content_man = gamenet_man->mpContentMan;
+	content_man->m_state = vCONTENT_STATE_UPLOADING;
+	content_man->m_result = vCONTENT_RESULT_UNDETERMINED;
+	mlp_man->AddLogicTask( *content_man->m_http_transfer_task );
+	content_man->m_percent_complete = 0;
+	content_man->m_last_comm_time = Tmr::GetTime();
+	content_man->m_transfer_failed_script = CRCD(0x7335ab7f,"LaunchTransferFailedDialog");
+
+	//Script::PrintContents( p_item_params );
+	p_item_params->GetChecksum( "type", &content_man->m_vault_type , true );
+	p_item_params->GetStructure( "data", &content_man->m_upload_struct );
+	p_item_params->GetString( "description", &desc, Script::ASSERT );
+	p_item_params->GetString( "filename", &filename, Script::ASSERT );
+	strcpy( content_man->m_upload_filename, filename );
+	strcpy( content_man->m_upload_description, desc );
+	
+	p_item_params->GetInteger( "score", &content_man->m_Score, false );
+	p_item_params->GetInteger( "width", &content_man->m_Width, false );
+	p_item_params->GetInteger( "length", &content_man->m_Height, false );
+	p_item_params->GetInteger( "num_pieces", &content_man->m_NumPieces, false );
+	p_item_params->GetInteger( "num_gaps", &content_man->m_NumGaps, false );
+	p_item_params->GetInteger( "num_goals", &content_man->m_NumGoals, false );
+	p_item_params->GetInteger( "theme", &content_man->m_Theme, false );
+	p_item_params->GetChecksum( "tod_script", &content_man->m_TODScript, false );
+	p_item_params->GetInteger( "is_male", &content_man->m_IsMale, false );
+    
+	net_thread_data.m_pEntry = s_threaded_upload_file;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+
+	WaitSema( content_man->m_thread_semaphore_id );
+	StartThread( gamenet_man->GetNetThreadId(), content_man );
+	SignalSema( content_man->m_thread_semaphore_id );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptFillVaultMenu(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	ContentMan* content_man;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Lst::Search< Content > sh;
+	Content* content;
+
+	content_man = gamenet_man->mpContentMan;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	for( content = sh.FirstItem( content_man->m_cur_dir->m_content_list ); content; content = sh.NextItem())
+	{
+		Script::CStruct* pParams, *pChooseParams;
+
+		pParams = new Script::CStruct;
+
+		pParams->AddString( "name", content->m_Name );
+		pParams->AddString( "creator", content->m_Creator );
+		pParams->AddString( "size", content->m_Size );
+		pParams->AddInteger( "score", content->m_Score );
+		pParams->AddInteger( "num_pieces", content->m_NumPieces );
+		pParams->AddInteger( "num_gaps", content->m_NumGaps );
+		pParams->AddInteger( "width", content->m_Width );
+		pParams->AddInteger( "height", content->m_Height );
+		pParams->AddInteger( "num_goals", content->m_NumGoals );
+		pParams->AddInteger( "theme", content->m_Theme );
+		pParams->AddChecksum( "tod_script", content->m_TODScript );
+		pParams->AddInteger( "is_male", content->m_IsMale );
+		pParams->AddString( "description", content->m_Description );
+
+		pChooseParams = new Script::CStruct;
+
+		pChooseParams->AddString( "file", content->m_Path );
+		
+		pParams->AddStructure( "pad_choose_params", pChooseParams );
+		switch( content_man->m_vault_type )
+		{
+			case 0x5be5569f:	// parks
+				Script::RunScript( "net_vault_menu_add_park", pParams );
+				break;
+			case 0x7364ea1:		// skaters
+				Script::RunScript( "net_vault_menu_add_skater", pParams );
+				break;
+			case 0x38dbe1d0:	// goals
+				Script::RunScript( "net_vault_menu_add_goal", pParams );
+				break;
+			case 0x1e26fd3e:	// tricks
+				Script::RunScript( "net_vault_menu_add_trick", pParams );
+				break;
+			default:
+				Dbg_Assert( 0 );
+				break;
+		}
+		
+
+		delete pParams;
+		delete pChooseParams;
+	}
+
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptNextVaultCategory(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	ContentMan* content_man;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Script::CStruct* passback_params;
+
+	content_man = gamenet_man->mpContentMan;
+	content_man->m_cur_dir = content_man->m_next_dir_sh.NextItem();
+	if( content_man->m_cur_dir == NULL )
+	{
+		content_man->m_cur_dir = content_man->m_next_dir_sh.FirstItem( content_man->m_directories );
+	}
+
+	passback_params = pScript->GetParams();
+	passback_params->AddString( "category", content_man->m_cur_dir->m_Name );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ContentMan::ScriptPrevVaultCategory(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	ContentMan* content_man;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Script::CStruct* passback_params;
+
+	content_man = gamenet_man->mpContentMan;
+	content_man->m_cur_dir = content_man->m_next_dir_sh.PrevItem();
+	if( content_man->m_cur_dir == NULL )
+	{
+		content_man->m_cur_dir = content_man->m_next_dir_sh.LastItem( content_man->m_directories );
+	}
+
+	passback_params = pScript->GetParams();
+	passback_params->AddString( "category", content_man->m_cur_dir->m_Name );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Directory::Directory( void )
+: Lst::Node< Directory > ( this )
+{
+	m_Uploads = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Directory::~Directory( void )
+{
+	Content* content, *next;
+	Lst::Search< Content > sh;
+
+	for( content = sh.FirstItem( m_content_list ); content; content = next )
+	{
+		next = sh.NextItem();
+		delete content;
+	}
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Content::Content( void )
+: Lst::Node< Content > ( this )
+{
+	m_Path[0] = '\0';
+	m_Name[0] = '\0';
+	m_Creator[0] = '\0';
+	m_Size[0] = '\0';
+	m_Checksum[0] = '\0';
+	m_Description[0] = '\0';
+	m_Score = 0;
+	m_NumPieces = 0;
+	m_NumGaps = 0;
+	m_NumGoals = 0;
+	m_Theme = 0;
+	m_TODScript = 0;
+	m_IsMale = 0;
+	m_Width = 0;
+	m_Height = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
diff --git a/Code/Sk/GameNet/Ngps/p_content.h b/Code/Sk/GameNet/Ngps/p_content.h
new file mode 100644
index 0000000..7bf6120
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_content.h
@@ -0,0 +1,260 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet													**
+**																			**
+**	File name:		p_content.h												**
+**																			**
+**	Created:		05/28/02	- spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__P_CONTENT_H
+#define	__P_CONTENT_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+namespace GameNet
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+enum
+{
+	vCONTENT_STATE_IDLE,
+	vCONTENT_STATE_GETTING_DIR_LIST,
+	vCONTENT_STATE_GETTING_SUB_DIR_LIST,
+	vCONTENT_STATE_DOWNLOADING,
+	vCONTENT_STATE_UPLOADING,
+	vCONTENT_STATE_GETTING_UPLOADED_LEVELS,
+	vCONTENT_STATE_DOWNLOADING_FACE
+};
+
+enum
+{
+	vCONTENT_RESULT_UNDETERMINED,
+	vCONTENT_RESULT_SUCCESS,
+	vCONTENT_RESULT_FAILURE
+};
+
+/*****************************************************************************
+**							     Class Definitions							**
+*****************************************************************************/
+
+class Content : public Lst::Node< Content >
+{
+public:
+	enum
+	{
+		vMAX_FILE_NAME_LEN = 23,
+		vMAX_PATH_NAME_LEN = 127,
+		vMAX_CREATOR_NAME_LEN = 15,
+		vMAX_DIMENSION_LEN = 15,
+		vMAX_CHECKSUM_LEN = 32,
+		vMAX_DESC_LEN = 255,
+		vMAX_NUM_LEN = 15,
+		vMAX_THEME_NAME_LEN = 23,
+		vMAX_SCRIPT_NAME_LEN = 23,
+		vMAX_SEX_NAME_LEN = 15,
+	};
+
+	Content( void );
+
+	char	m_Path[vMAX_PATH_NAME_LEN + 1];
+	char	m_Name[vMAX_FILE_NAME_LEN + 1];
+	char	m_Creator[vMAX_CREATOR_NAME_LEN + 1];
+	char	m_Size[vMAX_DIMENSION_LEN + 1];
+	char	m_Checksum[vMAX_CHECKSUM_LEN + 1];
+	char	m_Description[vMAX_DESC_LEN + 1];
+	int		m_Score;
+	int		m_Width;
+	int		m_Height;
+	int		m_NumPieces;
+	int		m_NumGaps;
+	int		m_NumGoals;
+	int		m_Theme;
+	uint32	m_TODScript;
+	int		m_IsMale;
+};
+
+class Directory : public Lst::Node< Directory >
+{
+public:
+	enum
+	{
+		vMAX_DIR_NAME_LEN = 23
+	};
+
+	Directory( void );
+	~Directory( void );
+
+	bool	m_Uploads;
+	char	m_Name[vMAX_DIR_NAME_LEN + 1];
+	Lst::Head< Content > 	m_content_list;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class DownloadContext
+{
+public:
+	int						m_max_size;
+	char					m_path[256];
+	ghttpCompletedCallback	m_dl_complete;
+	uint32					m_transfer_failed_script;
+	uint32					m_transfer_complete_script;
+	uint32					m_update_progress_script;
+	uint32					m_file_not_found_script;
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class ContentMan
+{
+public:
+					ContentMan( void );
+					~ContentMan( void );
+
+	void			DownloadDirectoryListing( void );
+	void			FillDirectoryListing( char* dir_text );
+	void			FreeDirectoryListing( void );
+	void			GetDirListing( char* path );
+	void			GetSubDirListing( Directory* dir );
+	void			DownloadFace( char* filename );
+	void			DownloadFile( DownloadContext* context );
+
+	void			SetPercentComplete( int percent_complete );
+	void			SetResult( int result );
+	void			SetError( int error );
+
+	static	bool	ScriptDownloadFace(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+    static	bool	ScriptDownloadDirectoryList(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptFreeDirectoryListing(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptDownloadFile(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptUploadFile(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+	static	bool	ScriptFillVaultMenu(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptNextVaultCategory(Script::CScriptStructure *pParams, Script::CScript *pScript);
+	static	bool	ScriptPrevVaultCategory(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+private:
+
+	Tsk::Task< ContentMan >*				m_http_transfer_task;
+	static	Tsk::Task< ContentMan >::Code   s_http_transfer_code;
+
+	static	void	s_threaded_upload_file( ContentMan* content_man );
+	static	void	s_threaded_download_file( const char* path );
+	static	void	s_threaded_download_face( const char* path );
+	static	void	s_threaded_download_directory_list( ContentMan* content_man );
+
+	static	void 	s_progress_callback(	GHTTPRequest request,       // The request.
+											GHTTPState state,           // The current state of the request.
+											const char* buffer,        	// The file's bytes so far, NULL if state m_directories;
+	Lst::Search< Directory > m_next_dir_sh;
+	Directory	*m_cur_dir;
+	uint32		m_vault_type;
+	char		m_vault_root[64];
+
+	uint32		m_transfer_failed_script;
+	uint32		m_transfer_complete_script;
+	uint32		m_update_progress_script;
+	uint32		m_file_not_found_script;
+	ghttpCompletedCallback	m_dl_complete;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+}	// namespace GameNet
+
+#endif	// __P_CONTENT_H
\ No newline at end of file
diff --git a/Code/Sk/GameNet/Ngps/p_ezcommon.h b/Code/Sk/GameNet/Ngps/p_ezcommon.h
new file mode 100644
index 0000000..086f751
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_ezcommon.h
@@ -0,0 +1,175 @@
+/*
+//  ------------------------------------------------------------------------
+
+File: common.h
+
+shared headers between EE and IOP projects.
+
+//  ------------------------------------------------------------------------
+*/
+
+#ifndef	_COMMON_H
+#define	_COMMON_H
+
+
+/*  --------------------------------------------------------
+ *  constants
+ *  --------------------------------------------------------
+ */
+
+#define kModName_ezNetCnf "eznetcnf"
+#define kModName_ezNetCtl "eznetctl"
+
+/*
+ *  SIF RPC identifier (aka SIFNUM)
+ *  used with sceSifRegisterRpc() and sceSifBindRpc()
+ */
+#define kRPCId_ezNetCnf	0x75499128
+#define kRPCId_ezNetCtl	0x75488909
+
+/*
+ *  SIF RPC buffer size
+ */
+#define kRPCBufferSize  (256 + 64)
+
+
+/*
+ *  SIF DMA are performed in 16-byte blocks from IOP -> EE.
+ *  enforce padding in both DMA directions.
+ */
+#define kDMAWait        0
+#define kDMANoWait      1
+#define kDMABlockSize   16
+#define DMA_PAD(_size)  (((_size) + (kDMABlockSize-1)) & ~(kDMABlockSize-1))
+
+
+/*  --------------------------------------------------------
+ *  enums
+ *  --------------------------------------------------------
+ */
+
+/*
+ *  eznetcnf RPC function ids
+ */
+typedef enum {
+  eCnfGetCount = 0,
+  eCnfGetCombinationList,
+  eCnfGetEnvData,
+
+  eCnfEnd = 0x1ffff           // for enum size consistency
+} ezNetCnfFunctionId;
+
+/*
+ *  eznetctl RPC function ids
+ */
+typedef enum {
+  eCtlGetEnvDataAddress = 0,
+  eCtlSetConfiguration,
+  eCtlSetCombination,
+  eCtlWatchStatus,
+  eCtlDownInterface,
+
+  eCtlEnd = 0x1ffff           // for enum size consistency
+} ezNetCtlFunctionId;
+
+/*
+ *  eznetcnf connection types (deprecated)
+ */
+typedef enum {
+  eConnInvalid = 0,           // invalid combination of devType and ifcType
+  eConnStatic,                // Ethernet (static IP)
+  eConnDHCP,                  // Ethernet (DHCP)
+  eConnPPPoE,                 // Ethernet (PPP over Ethernet)
+  eConnPPP,                   // modem (PPP)
+  eConnAOLPPP,                // modem (AOL PPP)
+
+  eConnPadding = 0x1ffff      // for enum size consistency
+} ezConnectionType;
+
+/*
+ *  network interface protocol family
+ */
+typedef enum {
+  eInterfacePPP,      // modem PPP
+  eInterfacePPPoE,    // PPP over Ethernet
+  eInterfaceARP       // Ethernet (static IP and DHCP)
+} ezInterfaceType;
+
+/*
+ *  netcnf file types
+ */
+typedef enum {
+  eCombinationFile = 0,       // net???.cnf
+  eInterfaceFile,             // ifc???.cnf
+  eDeviceFile,                // dev???.cnf
+
+  eFilePadding = 0x1ffff      // for enum size consistency
+} ezNetCnfType;
+
+
+/*  --------------------------------------------------------
+ *  typedefs
+ *  --------------------------------------------------------
+ */
+
+#define kMaxCombinations  10  // maximum number of Combinations
+#define kStrBufferSize    256 // maximum size for any text field
+#define kStrDisplaySize   32  // display size for any text field
+
+
+typedef struct ezNetCnfCombination {
+  int isActive;
+  ezConnectionType connectionType;
+  char combinationName[kStrDisplaySize];
+  char ifcName[kStrDisplaySize];
+  char devName[kStrDisplaySize];
+} ezNetCnfCombination_t;
+
+
+typedef struct ezNetCnfCombinationList {
+  int listLength;
+  unsigned int defaultIndex;
+  unsigned int netdbOrder[kMaxCombinations];
+  ezNetCnfCombination_t list[kMaxCombinations];
+} ezNetCnfCombinationList_t __attribute__((aligned(64)));
+
+
+typedef struct ezNetCtlStatus {
+  u_int clientAddr;
+  int id;
+  int event;
+  int state;
+  char message[kStrBufferSize];
+} ezNetCtlStatus_t __attribute__((aligned(64)));
+
+
+/*
+ *  RPC buffer type for ezNetCnfGetCount()
+ */
+typedef struct {
+  int result;
+  ezNetCnfType fileType;
+  char netdbPath[kStrBufferSize];
+} rpcGetCount_t;
+
+/*
+ *  RPC buffer type for ezNetCnfGetCombinationList()
+ */
+typedef struct {
+  int result;
+  u_int clientAddr;
+  char netdbPath[kStrBufferSize];
+} rpcGetCombinationList_t;
+
+/*
+ *  RPC buffer type for ezNetCnfGetEnvData()
+ */
+typedef struct {
+  int result;
+  u_int clientAddr;
+  char netdbPath[kStrBufferSize];
+  char combinationName[kStrDisplaySize];
+} rpcGetEnvData_t;
+
+#endif	// _COMMON_H
+
diff --git a/Code/Sk/GameNet/Ngps/p_ezconfig.h b/Code/Sk/GameNet/Ngps/p_ezconfig.h
new file mode 100644
index 0000000..a2b321e
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_ezconfig.h
@@ -0,0 +1,86 @@
+/*
+//  ------------------------------------------------------------------------
+
+File: config.h
+
+collection of global variables used for runtime configuration.
+
+//  ------------------------------------------------------------------------
+*/
+
+#ifndef _CONFIG_H
+#define _CONFIG_H
+
+
+/*  --------------------------------------------------------
+ *  global variables/constants
+ *  --------------------------------------------------------
+ *  choose between:
+ *  -> declaration (GLOBAL undefined), or
+ *  -> definition  (GLOBAL defined)
+ *
+ *  GLOBAL should be defined in exactly one file (per project).
+ */
+
+#ifdef GLOBAL
+  #define EXTERN
+#else
+  #define EXTERN extern
+#endif
+
+
+    //  select one of the following NET_DB defines.
+    //  note that the host0:net.db is unencoded.
+EXTERN  char *g_netdb
+#ifdef  GLOBAL
+        = "mc0:";
+//        = "mc1:";
+//        = "host0:/usr/local/sce/conf/net/net.db";
+//        = "host0:../conf/good/net.db";
+#endif
+;
+
+
+    //  specify path to SCE-supplied modules
+EXTERN  char *g_modRoot	
+#ifdef  GLOBAL
+        = "host0:/usr/local/sce/iop/modules/"
+#endif
+;
+
+
+EXTERN const char *kConnectionTypeDescs[]
+#ifdef  GLOBAL
+=
+{
+  "invalid",
+  "Ethernet, static IP",
+  "Ethernet, DHCP",
+  "Ethernet, PPPoE",
+  "Modem, PPP",
+  "Modem, AOL PPP"
+}
+#endif
+;
+
+
+/*  --------------------------------------------------------
+ *  debugging information enable/disable
+ *  --------------------------------------------------------
+ */
+
+#define DEBUG
+
+
+    //  use dprintf() for non-essential debug messages only.
+#ifdef  DEBUG
+#define dprintf(_args...) \
+        printf("%s> ", __FILE__); \
+        printf(_args);
+#else
+#define dprintf(_args...)
+#endif  // DEBUG
+
+
+#endif  // _CONFIG_H
+
diff --git a/Code/Sk/GameNet/Ngps/p_ezmain.h b/Code/Sk/GameNet/Ngps/p_ezmain.h
new file mode 100644
index 0000000..890b712
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_ezmain.h
@@ -0,0 +1,48 @@
+/*  
+//  ------------------------------------------------------------------------
+
+File: main.h
+
+headers for EE library components.
+
+//  ------------------------------------------------------------------------
+*/
+
+#ifndef _MAIN_H
+#define _MAIN_H
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+//  only present after 2.4.0
+// manually define SCE_LIBRARY_VERSION if necessary.
+#if (0)
+#define SCE_LIBRARY_VERSION     0x2340
+#else
+#include 
+#endif
+
+#include "p_netcnfif.h"
+
+#include "p_ezcommon.h"
+#include "p_ezconfig.h"
+//#include "util.h"
+#include "p_libezcnf.h"
+//#include "libezctl.h"
+//#include "trc_hdd.h"
+
+
+#endif  // _MAIN_H
diff --git a/Code/Sk/GameNet/Ngps/p_libezcnf.cpp b/Code/Sk/GameNet/Ngps/p_libezcnf.cpp
new file mode 100644
index 0000000..6c10de6
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_libezcnf.cpp
@@ -0,0 +1,232 @@
+/*
+//  ------------------------------------------------------------------------
+
+File: libezcnf.c
+
+//  ------------------------------------------------------------------------
+*/
+
+#include "p_ezmain.h"
+
+
+static int m_rpcRunning = 0;
+static sceSifClientData m_rpcCd;;
+static int m_rpcBuf[kRPCBufferSize/4] __attribute__((aligned(64)));
+
+
+#ifdef  DEBUG
+#define ASSERT_RPC(_size)           \
+  if (!m_rpcRunning) {              \
+    printf("**** RPC services are not currently running\n"); \
+    return -1;                      \
+  }                                 \
+  if (_size > kRPCBufferSize) {     \
+    printf("**** RPC data size 0x%x exceeds RPC buffer size 0x%x\n", \
+           _size, kRPCBufferSize);  \
+    return -1;                      \
+  }
+#else
+#define ASSERT_RPC(_size)
+#endif	// DEBUG
+
+
+/*  --------------------------------------------------------
+ *  PUBLIC library initialization and finalization
+ *  --------------------------------------------------------
+ */
+
+/*
+ *  binds to eznetcnf.irx RPC services.
+ *  r == 1  after RPC is bound.
+ */
+int
+initEzNetCnf( void )
+{
+#ifdef  DEBUG
+  int id = sceSifSearchModuleByName(kModName_ezNetCnf);
+  if (id < 0) {
+    printf("\"%s\" module not found (%d)\n", kModName_ezNetCnf, id);
+  }
+#endif
+
+  // bind RPC
+  while (1) {
+    int i = 0x10000;
+    if (sceSifBindRpc(&m_rpcCd, kRPCId_ezNetCnf, 0) < 0) {
+      printf("could not bind to eznetcnf service\n");
+    }
+    if (m_rpcCd.serve != 0) break;
+    while (i--)
+      ;
+  }
+
+  return m_rpcRunning = 1;
+}
+
+/*
+ *  exit and unload eznetcnf.irx, then unbind RPC services.
+ *  r == 0  after RPC is unbound.
+ */
+int
+exitEzNetCnf( void )
+{
+  if (m_rpcRunning == 1) {
+    // call using non-blocking mode,
+    // since eznetcnf unloads and the call cannot return.
+    sceSifCallRpc(&m_rpcCd, eCnfEnd, SIF_RPCM_NOWAIT, 
+                  0, 0, 0, 0, 0, 0);
+    // not sure if this is necessary.
+    sceSifInitRpc(0);
+  }
+
+  return m_rpcRunning = 0;
+}
+
+
+/*  --------------------------------------------------------
+ *  PUBLIC library interfaces
+ *  --------------------------------------------------------
+ */
+
+/*
+ *  given the path to a netdb database, and a netcnf file type,
+ *  return the number of files listed in the database. note
+ *  sceNetCnfGetCount() returns 0 normally when no netdb file
+ *  can be found, or even when there is no Memory Card.
+ *
+ *  r >  0  for valid netdb with at least 1 Combination,
+ *  r <= 0  for error or no Combinations.
+ */
+int 
+ezNetCnfGetCount(const char *netdb, ezNetCnfType type)
+{
+  rpcGetCount_t *rpcData = (rpcGetCount_t *)&m_rpcBuf;
+  
+  ASSERT_RPC(sizeof(rpcGetCount_t));
+  
+  // pack RPC data
+  strcpy(rpcData->netdbPath, netdb);
+  rpcData->fileType = type;
+  
+  // call RPC
+  sceSifCallRpc(&m_rpcCd, eCnfGetCount, 
+                0,
+                &m_rpcBuf, sizeof(rpcGetCount_t),
+                &m_rpcBuf, sizeof(rpcGetCount_t),
+                0, 0);
+  
+  dprintf("ezNetCnfGetCount returned %d\n", rpcData->result);
+  return rpcData->result;
+}
+
+/*
+ *  given the path to a netdb database, retrieve a list of Combinations 
+ *  to the memory address specified at addr. this list contains all the 
+ *  information needed to present to the user.
+ *
+ *  r == 6  for valid Memory Card netdb with at least 1 Combination,
+ *  r == 10 for valid PFS netdb with at least 1 Combination,
+ *  r == N  for valid host netdb with 0 < N <= 10 Combinations,
+ *  r <= 0  for error or no Combinations.
+ */
+int
+ezNetCnfGetCombinationList(char *netdb, ezNetCnfCombinationList_t *addr)
+{
+  rpcGetCombinationList_t *rpcData = (rpcGetCombinationList_t *)&m_rpcBuf;
+
+  ASSERT_RPC(sizeof(rpcGetCombinationList_t));
+  
+  // pack RPC data
+  strcpy(rpcData->netdbPath, netdb);
+  rpcData->clientAddr = (u_int)addr;
+  
+  // call RPC
+  sceSifCallRpc(&m_rpcCd, eCnfGetCombinationList, 
+                0,
+                &m_rpcBuf, sizeof(rpcGetCombinationList_t),
+                &m_rpcBuf, sizeof(rpcGetCombinationList_t),
+                0, 0);
+  
+  dprintf("ezNetCnfGetCombinationList returned %d\n", rpcData->result);
+  return rpcData->result;
+}
+
+/*
+ *  given a previously generated list of combination configurations,
+ *  return the Combination designated as the "default" in netdb.
+ *
+ *  r != NULL   for valid pList,
+ *  r == NULL   for empty or invalid pList.
+ */
+ezNetCnfCombination_t *
+ezNetCnfGetDefault(ezNetCnfCombinationList_t *pList)
+{
+  // check for valid parameters
+  if (pList == NULL || pList->listLength == 0) {
+    printf("ezNetCnfGetDefault: pList is empty\n");
+    return NULL;
+  }
+  if (pList->defaultIndex >= (unsigned int) pList->listLength) {
+    printf("ezNetCnfGetDefault: invalid defaultIndex (%d)\n",
+           pList->defaultIndex);
+    return NULL;
+  }
+
+  return &pList->list[pList->defaultIndex];
+}
+
+/*
+ *  given the list of combinations and an ordinal number (1..N),
+ *  return "CombinationN" -- REMEMBER to adjust zero-based indices.
+ *
+ *  r != NULL   for valid pList and valid number,
+ *  r == NULL   for empty or invalid pList, or invalid number.
+ */
+ezNetCnfCombination_t *
+ezNetCnfGetCombination(ezNetCnfCombinationList_t *pList, int number)
+{
+  // check for valid parameters
+  if (pList == NULL || pList->listLength == 0) {
+    printf("ezNetCnfGetCombination: pList is empty\n");
+    return NULL;
+  }
+  if ((number < 1) || (number > pList->listLength)) {
+    printf("ezNetCnfGetCombination: invalid Combination number (%d)\n",
+           number);
+    return NULL;
+  }
+    
+  return &pList->list[number-1];
+}
+
+/*
+ *  given the netdb path and a designated Combination, read and convert
+ *  the data to a sceNetcnfifData record at the specified memory address.
+ *  this data will contain all essential configuration properties.
+ *
+ *  r == 0  for successfully loading Combination,
+ *  r <  0  for error.
+ */
+int
+ezNetCnfGetEnvData(char *netdb, char *combinationName, sceNetcnfifData_t *addr)
+{
+  rpcGetEnvData_t *rpcData = (rpcGetEnvData_t *)&m_rpcBuf;
+
+  ASSERT_RPC(sizeof(rpcGetEnvData_t));
+  // pack the RPC request structure
+  strcpy(rpcData->netdbPath, netdb);
+  strcpy(rpcData->combinationName, combinationName);
+  rpcData->clientAddr = (u_int)addr;
+
+  // call RPC
+  sceSifCallRpc(&m_rpcCd, eCnfGetEnvData, 
+                0,
+                &m_rpcBuf, sizeof(rpcGetEnvData_t),
+                &m_rpcBuf, sizeof(rpcGetEnvData_t),
+                0, 0);
+  
+  dprintf("ezNetCnfGetEnvData returned %d\n", rpcData->result);
+  return rpcData->result;
+}
+
+
diff --git a/Code/Sk/GameNet/Ngps/p_libezcnf.h b/Code/Sk/GameNet/Ngps/p_libezcnf.h
new file mode 100644
index 0000000..eced40b
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_libezcnf.h
@@ -0,0 +1,24 @@
+/*  
+//  ------------------------------------------------------------------------
+
+File: libezcnf.h
+
+headers for libezcnf.c
+
+//  ------------------------------------------------------------------------
+*/
+
+#ifndef _LIBEZCNF_H
+#define _LIBEZCNF_H
+
+int initEzNetCnf(void);
+int exitEzNetCnf(void);
+
+int ezNetCnfGetCount(const char *, ezNetCnfType);
+int ezNetCnfGetCombinationList(char *, ezNetCnfCombinationList_t *);
+ezNetCnfCombination_t *ezNetCnfGetCombination(ezNetCnfCombinationList_t *, int);
+ezNetCnfCombination_t *ezNetCnfGetDefault(ezNetCnfCombinationList_t *);
+int ezNetCnfGetEnvData(char *, char *, sceNetcnfifData_t *);
+
+#endif	// _LIBEZCNF_H
+
diff --git a/Code/Sk/GameNet/Ngps/p_netcnfif.h b/Code/Sk/GameNet/Ngps/p_netcnfif.h
new file mode 100644
index 0000000..c03e85c
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_netcnfif.h
@@ -0,0 +1,236 @@
+/* SCE CONFIDENTIAL
+ "PlayStation 2" Programmer Tool Runtime Library Release 2.5
+ */
+/* 
+ *      Netcnf Interface Library
+ *
+ *                         Version 1.2
+ *                         Shift-JIS
+ *
+ *      Copyright (C) 2002 Sony Computer Entertainment Inc.
+ *                        All Rights Reserved.
+ *
+ *                         netcnfif.h
+ *
+ *       Version        Date            Design      Log
+ *  --------------------------------------------------------------------
+ *       1.1            2002.01.28      tetsu       First version
+ *       1.2            2002.02.10      tetsu       Add SCE_NETCNFIF_CHECK_ADDITIONAL_AT
+ *                                                  Add sceNETCNFIF_TOO_LONG_STR
+ */
+
+#ifndef __netcnfif_common_h_
+#define __netcnfif_common_h_
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Sifrpc —p */
+#define SCE_NETCNFIF_SSIZE     (4096)       /* ‘—ŽóM‚·‚éƒf[ƒ^ƒoƒbƒtƒ@‚̃TƒCƒY */
+#define SCE_NETCNFIF_INTERFACE (0x80001101) /* ƒŠƒNƒGƒXƒgŽ¯•ÊŽq */
+
+/* Sifrpc ƒT[ƒrƒXŠÖ”—p”Ô† */
+#define SCE_NETCNFIF_GET_COUNT           (0)   /* ƒlƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚Ì”‚ðŽæ“¾ */
+#define SCE_NETCNFIF_GET_LIST            (1)   /* ƒlƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚̃ŠƒXƒg‚ðŽæ“¾ */
+#define SCE_NETCNFIF_LOAD_ENTRY          (2)   /* ƒlƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚Ì“à—e‚ðŽæ“¾ */
+#define SCE_NETCNFIF_ADD_ENTRY           (3)   /* ƒlƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚̒ljÁ */
+#define SCE_NETCNFIF_EDIT_ENTRY          (4)   /* ƒlƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚Ì•ÒW */
+#define SCE_NETCNFIF_DELETE_ENTRY        (5)   /* ƒlƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚Ìíœ */
+#define SCE_NETCNFIF_SET_LATEST_ENTRY    (6)   /* ƒlƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚̃ŠƒXƒg‚ð•ÒW */
+#define SCE_NETCNFIF_DELETE_ALL          (7)   /* ‚ ‚È‚½‚̃lƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚ðíœ */
+#define SCE_NETCNFIF_CHECK_CAPACITY      (8)   /* ƒfƒoƒCƒX‚ÌŽc‚è—e—Ê‚ðƒ`ƒFƒbƒN */
+#define SCE_NETCNFIF_CHECK_ADDITIONAL_AT (9)   /* ’ljÁ AT ƒRƒ}ƒ“ƒh‚ðƒ`ƒFƒbƒN */
+#define SCE_NETCNFIF_GET_ADDR            (100) /* IOP ‘¤‚ÌŽóM—̈æ(sceNetcnfifData)‚̃AƒhƒŒƒX‚ðŽæ“¾ */
+#define SCE_NETCNFIF_ALLOC_WORKAREA      (101) /* IOP ‘¤‚̃[ƒNƒGƒŠƒA‚ðŠm•Û */
+#define SCE_NETCNFIF_FREE_WORKAREA       (102) /* IOP ‘¤‚̃[ƒNƒGƒŠƒA‚ð‰ð•ú */
+#define SCE_NETCNFIF_SET_ENV             (103) /* IOP ‘¤‚Ì sceNetCnfEnv —̈æ‚É sceNetcnfifData ‚Ì“à—e‚ðÝ’è */
+
+/* ƒGƒ‰[ƒR[ƒh(-18 ‚Ü‚Å‚Í netcnf.irx ‚Æ“¯“™) */
+#define sceNETCNFIF_NG               (-1)   /* ‚»‚Ì‘¼‚̃Gƒ‰[ */
+#define sceNETCNFIF_ALLOC_ERROR      (-2)   /* ƒƒ‚ƒŠ‚ÌŠm•Û‚ÉŽ¸”s */
+#define sceNETCNFIF_OPEN_ERROR       (-3)   /* ƒtƒ@ƒCƒ‹‚ðŠJ‚¯‚È‚¢ */
+#define sceNETCNFIF_READ_ERROR       (-4)   /* “Ç‚Ýž‚Ý‚ÉŽ¸”s */
+#define sceNETCNFIF_WRITE_ERROR      (-5)   /* ‘‚«ž‚Ý‚ÉŽ¸”s */
+#define sceNETCNFIF_SEEK_ERROR       (-6)   /* ƒtƒ@ƒCƒ‹ƒTƒCƒYŽæ“¾‚ÉŽ¸”s */
+#define sceNETCNFIF_REMOVE_ERROR     (-7)   /* 휂Ɏ¸”s */
+#define sceNETCNFIF_ENTRY_NOT_FOUND  (-8)   /* ݒ肪‚È‚¢ */
+#define sceNETCNFIF_INVALID_FNAME    (-9)   /* Ý’èŠÇ—ƒtƒ@ƒCƒ‹‚̃pƒX–¼‚ª•s³ */
+#define sceNETCNFIF_INVALID_TYPE     (-10)  /* ‚ ‚È‚½‚̃lƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚ÌŽí—Þ‚ª•s³ */
+#define sceNETCNFIF_INVALID_USR_NAME (-11)  /* Ý’è–¼‚ª•s³ */
+#define sceNETCNFIF_TOO_MANY_ENTRIES (-12)  /* ݒ蔂ªÅ‘唂ɒB‚µ‚Ä‚¢‚é */
+#define sceNETCNFIF_ID_ERROR         (-13)  /* ID ‚ªŽæ“¾‚Å‚«‚È‚¢ */
+#define sceNETCNFIF_SYNTAX_ERROR     (-14)  /* Ý’è“à—e‚ª•s³ */
+#define sceNETCNFIF_MAGIC_ERROR      (-15)  /* ‘¼‚Ì "PlayStation 2" ‚Å•Û‘¶‚³‚ꂽݒè */
+#define sceNETCNFIF_CAPACITY_ERROR   (-16)  /* ƒfƒoƒCƒX‚ÌŽc‚è—e—Ê‚ª‘«‚è‚È‚¢ */
+#define sceNETCNFIF_UNKNOWN_DEVICE   (-17)  /* –¢’m‚̃fƒoƒCƒX‚ªŽw’肳‚ê‚Ä‚¢‚é */
+#define sceNETCNFIF_IO_ERROR         (-18)  /* IO ƒGƒ‰[ */
+#define sceNETCNFIF_TOO_LONG_STR     (-19)  /* Žw’肳‚ꂽ•¶Žš—ñ‚ª’·‚·‚¬‚é */
+#define sceNETCNFIF_NO_DATA          (-100) /* ƒf[ƒ^‚ªÝ’肳‚ê‚Ä‚È‚¢ */
+
+/* Netcnf Interface ‚É•K—v‚ȃf[ƒ^ */
+typedef struct sceNetcnfifArg{
+    int data;               /* ‚»‚Ì‘¼‚̈ø”/‚»‚Ì‘¼‚Ì•Ô‚è’l */
+    int f_no_decode;        /* no_decode ƒtƒ‰ƒO */
+    int type;               /* ‚ ‚È‚½‚̃lƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚ÌŽí—Þ */
+    int addr;               /* EE ‘¤‚ÌŽóM—̈æ‚̃AƒhƒŒƒX/IOP ‘¤ƒAƒhƒŒƒX‚Ì•Ô‚è’l */
+    char fname[256];        /* Ý’èŠÇ—ƒtƒ@ƒCƒ‹‚̃pƒX–¼/’ljÁ AT ƒRƒ}ƒ“ƒh */
+    char usr_name[256];     /* Ý’è–¼ */
+    char new_usr_name[256]; /* V‚µ‚¢Ý’è–¼ */
+} sceNetcnfifArg_t;
+
+enum
+{
+    sceNetcnfifArg_f_no_decode_off, /* f_no_decode ‚ðݒ肵‚È‚¢ */
+    sceNetcnfifArg_f_no_decode_on   /* f_no_decode ‚ðÝ’è‚·‚é */
+};
+
+enum
+{
+    sceNetcnfifArg_type_net, /* ‘g‚݇‚킹 */
+    sceNetcnfifArg_type_ifc, /* Ú‘±ƒvƒƒoƒCƒ_Ý’è */
+    sceNetcnfifArg_type_dev  /* Ú‘±‹@ŠíÝ’è */
+};
+
+/* ‚ ‚È‚½‚̃lƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚̃ŠƒXƒg */
+typedef struct sceNetcnfifList{
+    int type;           /* ‚ ‚È‚½‚̃lƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚ÌŽí—Þ */
+    int stat;           /* ƒtƒ@ƒCƒ‹ƒXƒe[ƒ^ƒX */
+    char sys_name[256]; /* ƒtƒ@ƒCƒ‹–¼ */
+    char usr_name[256]; /* Ý’è–¼ */
+    int p0[14];         /* —\–ñ—̈æ0 */
+} sceNetcnfifList_t __attribute__((aligned(64)));
+
+/* ‚ ‚È‚½‚̃lƒbƒgƒ[ƒNÝ’èƒtƒ@ƒCƒ‹‚É•Û‘¶‚³‚ê‚éƒf[ƒ^ */
+typedef struct sceNetcnfifData{
+    char attach_ifc[256];      /* Ú‘±ƒvƒƒoƒCƒ_Ý’èƒtƒ@ƒCƒ‹–¼(net) */
+    char attach_dev[256];      /* Ú‘±‹@ŠíÝ’èƒtƒ@ƒCƒ‹–¼(net) */
+    char dhcp_host_name[256];  /* DHCP—pƒzƒXƒg–¼(ifc) */
+    char address[256];         /* IPƒAƒhƒŒƒX(ifc) */
+    char netmask[256];         /* ƒlƒbƒgƒ}ƒXƒN(ifc) */
+    char gateway[256];         /* ƒfƒtƒHƒ‹ƒgƒ‹[ƒ^(ifc) */
+    char dns1_address[256];    /* ƒvƒ‰ƒCƒ}ƒŠDNS(ifc) */
+    char dns2_address[256];    /* ƒZƒJƒ“ƒ_ƒŠDNS(ifc) */
+    char phone_numbers1[256];  /* Ú‘±æ“d˜b”Ô†1(ifc) */
+    char phone_numbers2[256];  /* Ú‘±æ“d˜b”Ô†2(ifc) */
+    char phone_numbers3[256];  /* Ú‘±æ“d˜b”Ô†3(ifc) */
+    char auth_name[256];       /* ƒ†[ƒUID(ifc) */
+    char auth_key[256];        /* ƒpƒXƒ[ƒh(ifc) */
+    char peer_name[256];       /* Ú‘±æ‚Ì”FØ–¼(ifc) */
+    char vendor[256];          /* ƒxƒ“ƒ_–¼(dev) */
+    char product[256];         /* ƒvƒƒ_ƒNƒg–¼(dev) */
+    char chat_additional[256]; /* ’ljÁATƒRƒ}ƒ“ƒh(dev) */
+    char outside_number[256];  /* ŠOü”­M”Ԇݒè(”Ԇݒ蕔•ª)(dev) */
+    char outside_delay[256];   /* ŠOü”­M”Ԇݒè(’x‰„Ý’è•”•ª)(dev) */
+    int ifc_type;              /* ƒfƒoƒCƒXƒŒƒCƒ„‚ÌŽí•Ê(ifc) */
+    int mtu;                   /* MTU‚ÌÝ’è(ifc) */
+    int ifc_idle_timeout;      /* ‰ñüØ’fÝ’è(ifc) */
+    int dev_type;              /* ƒfƒoƒCƒXƒŒƒCƒ„‚ÌŽí•Ê(dev) */
+    int phy_config;            /* ƒC[ƒTƒlƒbƒgÚ‘±‹@Ší‚Ì“®ìƒ‚[ƒh(dev) */
+    int dialing_type;          /* ƒ_ƒCƒAƒ‹•û–@(dev) */
+    int dev_idle_timeout;      /* ‰ñüØ’fÝ’è(dev) */
+    int p0;                    /* —\–ñ—̈æ0 */
+    unsigned char dhcp;        /* DHCPŽg—pE•sŽg—p(ifc) */
+    unsigned char dns1_nego;   /* DNSƒT[ƒoƒAƒhƒŒƒX‚ðŽ©“®Žæ“¾‚·‚éE‚µ‚È‚¢(ifc) */
+    unsigned char dns2_nego;   /* DNSƒT[ƒoƒAƒhƒŒƒX‚ðŽ©“®Žæ“¾‚·‚éE‚µ‚È‚¢(ifc) */
+    unsigned char f_auth;      /* ”FØ•û–@‚ÌŽw’è—L‚è(ifc) */
+    unsigned char auth;        /* ”FØ•û–@(ifc) */
+    unsigned char pppoe;       /* PPPoEŽg—pE•sŽg—p(ifc) */
+    unsigned char prc_nego;    /* PFCƒlƒSƒVƒG[ƒVƒ‡ƒ“‚Ì‹ÖŽ~(ifc) */
+    unsigned char acc_nego;    /* ACFCƒlƒSƒVƒG[ƒVƒ‡ƒ“‚Ì‹ÖŽ~(ifc) */
+    unsigned char accm_nego;   /* ACCMƒlƒSƒVƒG[ƒVƒ‡ƒ“‚Ì‹ÖŽ~(ifc) */
+    unsigned char p1;          /* —\–ñ—̈æ1 */
+    unsigned char p2;          /* —\–ñ—̈æ2 */
+    unsigned char p3;          /* —\–ñ—̈æ3 */
+    int p4[5];                 /* —\–ñ—̈æ4 */
+} sceNetcnfifData_t __attribute__((aligned(64)));
+
+enum
+{
+    sceNetcnfifData_type_no_set = -1, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_type_eth = 1,     /* type eth */
+    sceNetcnfifData_type_ppp,         /* type ppp */
+    sceNetcnfifData_type_nic          /* type nic */
+};
+
+enum
+{
+    sceNetcnfifData_mtu_no_set = -1,   /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_mtu_default = 1454 /* mtu 1454 */
+};
+
+enum
+{
+    sceNetcnfifData_idle_timeout_no_set = -1 /* ݒ肵‚È‚¢ */
+};
+
+enum
+{
+    sceNetcnfifData_phy_config_no_set = -1, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_phy_config_auto = 1,    /* phy_config auto */
+    sceNetcnfifData_phy_config_10,          /* phy_config 10 */
+    sceNetcnfifData_phy_config_10_FD,       /* phy_config 10_fd */
+    sceNetcnfifData_phy_config_TX = 5,      /* phy_config tx */
+    sceNetcnfifData_phy_config_TX_FD        /* phy_config tx_fd */
+};
+
+enum
+{
+    sceNetcnfifData_dialing_type_no_set = -1, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_dialing_type_tone = 0,    /* dialing_type tone */
+    sceNetcnfifData_dialing_type_pulse        /* dialing_type pulse */
+};
+
+enum
+{
+    sceNetcnfifData_dhcp_no_set = 0xff, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_dhcp_no_use = 0,    /* -dhcp */
+    sceNetcnfifData_dhcp_use            /* dhcp */
+};
+
+enum
+{
+    sceNetcnfifData_dns_nego_no_set = 0xff, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_dns_nego_on = 1         /* want.dns1_nego/want.dns2_nego */
+};
+
+enum
+{
+    sceNetcnfifData_f_auth_off, /* allow.auth chap/pap ‚ðݒ肵‚È‚¢ */
+    sceNetcnfifData_f_auth_on   /* allow.auth chap/pap ‚ðÝ’è‚·‚é */
+};
+
+enum
+{
+    sceNetcnfifData_auth_chap_pap = 4 /* allow.auth chap/pap */
+};
+
+enum
+{
+    sceNetcnfifData_pppoe_no_set = 0xff, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_pppoe_use = 1        /* pppoe */
+};
+
+enum
+{
+    sceNetcnfifData_prc_nego_no_set = 0xff, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_prc_nego_off = 0        /* -want.prc_nego */
+};
+
+enum
+{
+    sceNetcnfifData_acc_nego_no_set = 0xff, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_acc_nego_off = 0        /* -want.acc_nego */
+};
+
+enum
+{
+    sceNetcnfifData_accm_nego_no_set = 0xff, /* ݒ肵‚È‚¢ */
+    sceNetcnfifData_accm_nego_off = 0        /* -want.accm_nego */
+};
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /*__netcnfif_common_h_ */
diff --git a/Code/Sk/GameNet/Ngps/p_netconfig.cpp b/Code/Sk/GameNet/Ngps/p_netconfig.cpp
new file mode 100644
index 0000000..e9d4b57
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_netconfig.cpp
@@ -0,0 +1,596 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		p_netconfig.cpp											**
+**																			**
+**	Created by:		04/17/02	-	spg										**
+**																			**
+**	Description:	Netcnf wrapper											**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC p_netconfig
+// @module p_netconfig | None
+// @subindex Scripting Database
+// @index script | p_netconfig
+                          
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#define GLOBAL 1
+#include 
+
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+#define sceNETCNF_MAGIC_ERROR		(-15)
+
+namespace GameNet
+{
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+	/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void sceNetcnfifDataDump(sceNetcnfifData_t *data)
+{
+    Dbg_Printf("-----------------------\n");
+    Dbg_Printf("attach_ifc       : \"%s\"\n", data->attach_ifc);
+    Dbg_Printf("attach_dev       : \"%s\"\n", data->attach_dev);
+    Dbg_Printf("dhcp_host_name   : \"%s\"\n", data->dhcp_host_name);
+    Dbg_Printf("address          : \"%s\"\n", data->address);
+    Dbg_Printf("netmask          : \"%s\"\n", data->netmask);
+    Dbg_Printf("gateway          : \"%s\"\n", data->gateway);
+    Dbg_Printf("dns1_address     : \"%s\"\n", data->dns1_address);
+    Dbg_Printf("dns2_address     : \"%s\"\n", data->dns2_address);
+    Dbg_Printf("phone_numbers1   : \"%s\"\n", data->phone_numbers1);
+    Dbg_Printf("phone_numbers2   : \"%s\"\n", data->phone_numbers2);
+    Dbg_Printf("phone_numbers3   : \"%s\"\n", data->phone_numbers3);
+    Dbg_Printf("auth_name        : \"%s\"\n", data->auth_name);
+    Dbg_Printf("auth_key         : \"%s\"\n", data->auth_key);
+    Dbg_Printf("peer_name        : \"%s\"\n", data->peer_name);
+    Dbg_Printf("vendor           : \"%s\"\n", data->vendor);
+    Dbg_Printf("product          : \"%s\"\n", data->product);
+    Dbg_Printf("chat_additional  : \"%s\"\n", data->chat_additional);
+    Dbg_Printf("outside_number   : \"%s\"\n", data->outside_number);
+    Dbg_Printf("outside_delay    : \"%s\"\n", data->outside_delay);
+    Dbg_Printf("ifc_type         : \"%d\"\n", data->ifc_type);
+    Dbg_Printf("mtu              : \"%d\"\n", data->mtu);
+    Dbg_Printf("ifc_idle_timeout : \"%d\"\n", data->ifc_idle_timeout);
+    Dbg_Printf("dev_type         : \"%d\"\n", data->dev_type);
+    Dbg_Printf("phy_config       : \"%d\"\n", data->phy_config);
+    Dbg_Printf("dialing_type     : \"%d\"\n", data->dialing_type);
+    Dbg_Printf("dev_idle_timeout : \"%d\"\n", data->dev_idle_timeout);
+    Dbg_Printf("dhcp             : \"%d\"\n", data->dhcp);
+    Dbg_Printf("dns1_nego        : \"%d\"\n", data->dns1_nego);
+    Dbg_Printf("dns2_nego        : \"%d\"\n", data->dns2_nego);
+    Dbg_Printf("f_auth           : \"%d\"\n", data->f_auth);
+    Dbg_Printf("auth             : \"%d\"\n", data->auth);
+    Dbg_Printf("pppoe            : \"%d\"\n", data->pppoe);
+    Dbg_Printf("prc_nego         : \"%d\"\n", data->prc_nego);
+    Dbg_Printf("acc_nego         : \"%d\"\n", data->acc_nego);
+    Dbg_Printf("accm_nego        : \"%d\"\n", data->accm_nego);
+    Dbg_Printf("-----------------------\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+ *  given a combination configuration, display its details.
+ */
+static void showConfiguration(ezNetCnfCombination_t *p_conf, sceNetcnfifData_t *p_data)
+{
+	if (p_conf == NULL) 
+	{
+		Dbg_Printf("showConfiguration: p_conf is NULL\n");
+		return;
+	}
+  
+	if (p_conf->isActive) 
+	{
+		Dbg_Printf("ezNetCnfCombination_t ------------\n");
+		Dbg_Printf("net display name\t%s\n", p_conf->combinationName);
+		Dbg_Printf("ifc display name\t%s\n", p_conf->ifcName);
+		Dbg_Printf("dev display name\t%s\n", p_conf->devName);
+		Dbg_Printf("type\t\t%s\n", kConnectionTypeDescs[p_conf->connectionType]);
+//#if (SCE_LIBRARY_VERSION >= 0x2500)
+		sceNetcnfifDataDump(p_data);
+//#endif
+	} 
+	else 
+	{
+		Dbg_Printf("\tempty Combination slot\n");
+	}
+
+	Dbg_Printf("\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptLoadNetConfigs(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+    ezNetCnfCombination_t *p_combination = NULL;
+	int i, err;
+
+	Dbg_Printf( "****************** LOADING NETWORK CONFIG!!!! ************************ \n" );
+
+	static bool once = false;
+	if( !once )
+	{
+		SIO::LoadIRX("eznetcnf" );	// net config stuff
+		initEzNetCnf();
+	}
+
+    err = ezNetCnfGetCombinationList( g_netdb, &gamenet_man->m_combination_list );
+	if( err < 0 )
+	{
+		if( err != sceNETCNF_MAGIC_ERROR )
+		{
+			pScript->GetParams()->AddChecksum( NONAME, CRCD(0x492676a6,"corrupt"));
+		}
+		Dbg_Printf( "ezNetCnfGetCombinationList error = %d\n", err );
+		return false;
+	}
+
+	for( i = 0; i < gamenet_man->m_combination_list.listLength; i++ )
+	{
+		Dbg_Printf("getting combo %d\n", i + 1);
+		p_combination = ezNetCnfGetCombination(&gamenet_man->m_combination_list, i + 1);
+		if (p_combination != NULL && p_combination->isActive && ( p_combination->connectionType != eConnInvalid )) 
+		{
+			Dbg_Printf("found combo, %s\n", p_combination->combinationName);
+			err = ezNetCnfGetEnvData(g_netdb, p_combination->combinationName, &gamenet_man->m_env_data[i] );
+			if( err < 0 ) 
+			{
+				Dbg_Printf("ezNetCnfGetEnvData error = %d\n", err);
+			}
+			showConfiguration( p_combination, &gamenet_man->m_env_data[i] );
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*ezNetCnfCombination_t*	Manager::GetNetworkConfig( int index )
+{
+	return mp_combinations[index];
+}*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*void	Manager::ExitNetworkConfig( void )
+{
+	Dbg_Printf( "****************** EXITING NETWORK CONFIG!!!! ************************ \n" );
+	// call using non-blocking mode,
+	// since eznetcnf unloads and the call cannot return.
+	sceSifCallRpc(&g_cd, eFuncEnd, SIF_RPCM_NOWAIT, 
+	    		0, 0, 0, 0, 0, 0);
+	// not sure if this is necessary.
+	sceSifInitRpc(0);
+}*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptNoNetConfigFiles(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	ezNetCnfCombination_t *p_combination;
+
+	p_combination = ezNetCnfGetDefault(&gamenet_man->m_combination_list);
+	if( p_combination != NULL && p_combination->isActive ) 
+	{
+		return false;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::ScriptFillNetConfigList(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	ezNetCnfCombination_t *p_combination;
+	Script::CStruct* p_item_params;
+	Script::CStruct* p_script_params;
+	int i;
+	char config_title[64];
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+	for( i = 0; i < gamenet_man->m_combination_list.listLength; i++ )
+	{
+		// check the Default Combination
+		p_combination = ezNetCnfGetCombination(&gamenet_man->m_combination_list, i + 1);
+		if( p_combination != NULL && p_combination->isActive && ( p_combination->connectionType != eConnInvalid ))
+		{
+			sprintf( config_title, "%d %s", i + 1, p_combination->ifcName );
+		
+			p_item_params = new Script::CStruct;	
+			p_item_params->AddString( "text", config_title );
+			p_item_params->AddChecksum( "id", Script::GenerateCRC( "first_net_cfg" ) + i );
+			p_item_params->AddChecksum( "centered", CRCD(0x2a434d05,"centered") );
+			p_item_params->AddFloat( "scale", 0.8f );
+			p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("choose_net_config"));
+			p_item_params->AddChecksum( "focus_script",CRCD(0x6100d1f9,"main_theme_focus_noscale"));
+			p_item_params->AddChecksum( "unfocus_script",CRCD(0xe9602a90,"main_theme_unfocus_noscale"));
+			p_item_params->AddChecksum( "parent_menu_id", Script::GenerateCRC( "network_options_load_config_vmenu" ));
+		
+			// create the parameters that are passed to the X script
+			p_script_params= new Script::CStruct;
+			p_script_params->AddInteger( "index", i + 1 );	
+			p_item_params->AddStructure("pad_choose_params",p_script_params);			
+		
+			Script::RunScript("theme_menu_add_item",p_item_params);
+			delete p_item_params;
+			delete p_script_params;
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ScriptChooseNetConfig(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Prefs::Preferences* prefs;
+	Script::CScriptStructure* pTempStructure;
+	ezNetCnfCombination_t *p_combination;
+	int index;
+    sceNetcnfifData* data;
+	char outside_str[64];
+	char full_phone[128];
+	char full_name[256];
+	bool dns_set;
+
+	prefs = gamenet_man->GetNetworkPreferences();
+	p_combination = NULL;
+	data = NULL;
+	if( pParams->GetInteger( "index", &index, false ))
+	{
+		data = &gamenet_man->m_env_data[index];
+		p_combination = ezNetCnfGetCombination( &gamenet_man->m_combination_list, index );
+		sprintf( full_name, "%d %s", index, p_combination->ifcName );
+	}
+	else
+	{	
+		const char* name;
+		int i;
+		bool found;
+
+		pParams->GetString( "name", &name, Script::ASSERT );
+		found = false;
+		for( i = 0; i < gamenet_man->m_combination_list.listLength; i++ )
+		{
+			// check the Default Combination
+			p_combination = ezNetCnfGetCombination(&gamenet_man->m_combination_list, i + 1 );
+			
+			if(	p_combination != NULL )
+			{
+				sprintf( full_name, "%d %s", i + 1, p_combination->ifcName );
+			
+				if(	( p_combination->isActive ) && 
+					( strcmp( full_name, name ) == 0 ) && 
+					( p_combination->connectionType != eConnInvalid ))
+				{   
+					index = i;
+					found = true;
+					data = &gamenet_man->m_env_data[index];
+					break;
+				}
+			}
+		}
+
+		if( !found )
+		{
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, Script::GetString( "manual_settings_str" ));
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "config_manual" ));
+			prefs->SetPreference( Script::GenerateCRC("config_type"), pTempStructure );
+			delete pTempStructure;
+
+			Script::RunScript( "clear_net_options" );
+			return false;
+		}
+	}
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, full_name );
+	pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "config_sony" ));
+	prefs->SetPreference( Script::GenerateCRC("config_type"), pTempStructure );
+	delete pTempStructure;
+	
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->address );
+	prefs->SetPreference( Script::GenerateCRC("ip_address"), pTempStructure );
+	delete pTempStructure;
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->netmask );
+	prefs->SetPreference( Script::GenerateCRC("subnet_mask"), pTempStructure );
+	delete pTempStructure;
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->gateway );
+	prefs->SetPreference( Script::GenerateCRC("gateway"), pTempStructure );
+	delete pTempStructure;
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->dns1_address );
+	prefs->SetPreference( Script::GenerateCRC("dns_server"), pTempStructure );
+	delete pTempStructure;
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->dns2_address );
+	prefs->SetPreference( Script::GenerateCRC("dns_server2"), pTempStructure );
+	delete pTempStructure;
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->dhcp_host_name );
+	prefs->SetPreference( Script::GenerateCRC("host_name"), pTempStructure );
+	delete pTempStructure;
+
+	sprintf( outside_str, "%s%s", data->outside_number, data->outside_delay );
+	sprintf( full_phone, "%s%s", outside_str, data->phone_numbers1 );
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, full_phone );
+	prefs->SetPreference( Script::GenerateCRC("dialup_number"), pTempStructure );
+	delete pTempStructure;
+
+	sprintf( full_phone, "%s%s", outside_str, data->phone_numbers2 );
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, full_phone );
+	prefs->SetPreference( Script::GenerateCRC("dialup_number2"), pTempStructure );
+	delete pTempStructure;
+
+	sprintf( full_phone, "%s%s", outside_str, data->phone_numbers3 );
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, full_phone );
+	prefs->SetPreference( Script::GenerateCRC("dialup_number3"), pTempStructure );
+	delete pTempStructure;
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->auth_name );
+	prefs->SetPreference( Script::GenerateCRC("dialup_username"), pTempStructure );
+	delete pTempStructure;
+
+	pTempStructure = new Script::CScriptStructure;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, data->auth_key );
+	prefs->SetPreference( Script::GenerateCRC("dialup_password"), pTempStructure );
+	delete pTempStructure;
+
+	dns_set = false;
+			
+	if( ( strlen( data->dns1_address ) > 0 ) ||
+		( strlen( data->dns2_address ) > 0 ))
+	{
+		dns_set = true;
+	}
+
+	switch((int) p_combination->connectionType )
+	{
+		case eConnStatic:
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Ethernet (Network Adaptor for PS2)" );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "device_broadband_pc" ));
+			prefs->SetPreference( Script::GenerateCRC("device_type"), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Static IP Address" );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "ip_static" ));
+			prefs->SetPreference( Script::GenerateCRC("broadband_type"), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "No" );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "boolean_false" ));
+			prefs->SetPreference( Script::GenerateCRC("auto_dns"), pTempStructure );
+			delete pTempStructure;
+			break;
+		case eConnDHCP:
+		{
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Ethernet (Network Adaptor for PS2)" );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "device_broadband_pc" ));
+			prefs->SetPreference( Script::GenerateCRC("device_type"), pTempStructure );
+			delete pTempStructure;
+
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Auto-Detect (DHCP)" );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "ip_dhcp" ));
+			prefs->SetPreference( Script::GenerateCRC("broadband_type"), pTempStructure );
+			delete pTempStructure;
+
+			if( dns_set )
+			{
+				pTempStructure = new Script::CScriptStructure;
+				pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "No" );
+				pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "boolean_false" ));
+				prefs->SetPreference( Script::GenerateCRC("auto_dns"), pTempStructure );
+				delete pTempStructure;
+			}
+			else
+			{
+				pTempStructure = new Script::CScriptStructure;
+				pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Yes" );
+				pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "boolean_true" ));
+				prefs->SetPreference( Script::GenerateCRC("auto_dns"), pTempStructure );
+				delete pTempStructure;
+			}
+			
+			break;
+		}
+		case eConnPPPoE:
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Ethernet (Network Adaptor for PS2) (PPPoE)" );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "device_broadband_pc_pppoe" ));
+			prefs->SetPreference( Script::GenerateCRC("device_type"), pTempStructure );
+			delete pTempStructure;
+
+			if( dns_set )
+			{
+				pTempStructure = new Script::CScriptStructure;
+				pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "No" );
+				pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "boolean_false" ));
+				prefs->SetPreference( Script::GenerateCRC("auto_dns"), pTempStructure );
+				delete pTempStructure;
+			}
+			else
+			{
+				pTempStructure = new Script::CScriptStructure;
+				pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Yes" );
+				pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "boolean_true" ));
+				prefs->SetPreference( Script::GenerateCRC("auto_dns"), pTempStructure );
+				delete pTempStructure;
+			}
+			break;
+		case eConnPPP:
+		case eConnAOLPPP:
+			pTempStructure = new Script::CScriptStructure;
+			pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Modem (Network Adaptor for PS2)" );
+			pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "device_sony_modem" ));
+			prefs->SetPreference( Script::GenerateCRC("device_type"), pTempStructure );
+			delete pTempStructure;
+			if( dns_set )
+			{
+				pTempStructure = new Script::CScriptStructure;
+				pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "No" );
+				pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "boolean_false" ));
+				prefs->SetPreference( Script::GenerateCRC("auto_dns"), pTempStructure );
+				delete pTempStructure;
+			}
+			else
+			{
+				pTempStructure = new Script::CScriptStructure;
+				pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, "Yes" );
+				pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, Script::GenerateCRC( "boolean_true" ));
+				prefs->SetPreference( Script::GenerateCRC("auto_dns"), pTempStructure );
+				delete pTempStructure;
+			}
+			break;
+		default:
+			Dbg_Printf( "************** GOT %d\n", (int) p_combination->connectionType );
+			Dbg_Assert( 0 );
+			break;
+	}
+    
+	//Dbg_Printf("product          : \"%s\"\n", data->product);
+    //Dbg_Printf("chat_additional  : \"%s\"\n", data->chat_additional);
+    //Dbg_Printf("ifc_type         : \"%d\"\n", data->ifc_type);
+    //Dbg_Printf("mtu              : \"%d\"\n", data->mtu);
+    //Dbg_Printf("ifc_idle_timeout : \"%d\"\n", data->ifc_idle_timeout);
+    //Dbg_Printf("dev_type         : \"%d\"\n", data->dev_type);
+    //Dbg_Printf("phy_config       : \"%d\"\n", data->phy_config);
+    //Dbg_Printf("dialing_type     : \"%d\"\n", data->dialing_type);
+    //Dbg_Printf("dev_idle_timeout : \"%d\"\n", data->dev_idle_timeout);
+    //Dbg_Printf("dhcp             : \"%d\"\n", data->dhcp);
+    //Dbg_Printf("dns1_nego        : \"%d\"\n", data->dns1_nego);
+    //Dbg_Printf("dns2_nego        : \"%d\"\n", data->dns2_nego);
+    //Dbg_Printf("f_auth           : \"%d\"\n", data->f_auth);
+    //Dbg_Printf("auth             : \"%d\"\n", data->auth);
+    //Dbg_Printf("pppoe            : \"%d\"\n", data->pppoe);
+    //Dbg_Printf("prc_nego         : \"%d\"\n", data->prc_nego);
+    //Dbg_Printf("acc_nego         : \"%d\"\n", data->acc_nego);
+    //Dbg_Printf("accm_nego        : \"%d\"\n", data->accm_nego);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/Ngps/p_stats.cpp b/Code/Sk/GameNet/Ngps/p_stats.cpp
new file mode 100644
index 0000000..7afdc82
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_stats.cpp
@@ -0,0 +1,1453 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_stats.cpp												**
+**																			**
+**	Created by:		04/30/03	-	SPG										**
+**																			**
+**	Description:	PS2 Stat-tracking Code 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+extern bool g_CheatsEnabled;
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vSTATS_CONNECT_TIMEOUT Tmr::Seconds( 10 )
+#define vSTATS_REPORT_INTERVAL Tmr::Minutes( 1 )
+#define vMAX_STATS_FILE_SIZE ( 150 * 1024 );
+#define vMAX_ENTRIES_PER_LEVEL 3
+#define vSTATS_PLAYER_COUNT 50
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static 	Tmr::Time	s_connect_start_time = 0;
+uint32	s_levels[] = { 	0xf6c822d4, 0x7276630a, 0xd7720de9, 0xee1c63cf, 0x399bd4e8, 
+						0xd0f0229, 0x9a44ec8, 0x9db7727c, 0x991b5359, 
+						0x73be7e94, 0xa7ff414b, 0x8ca30405 };
+char	s_stats_files[vNUM_RATINGS_FILES][32] = {
+	"hs_at.txt",
+	"bc_at.txt",
+	"hs_mo.txt",
+	"bc_mo.txt",
+	"ratings.txt"
+};
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+class SortableStats : public Lst::Node< SortableStats >
+{
+public:
+	SortableStats( void );
+
+	char	m_Name[32];
+	uint32	m_Level;
+	int		m_Score;
+	int		m_Rating;
+};
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+void	StatsMan::parse_ratings( char* buffer )
+{
+	StatsKeeper* keeper;
+	char* token;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	keeper = &m_stats_keepers[vFILE_RATINGS];
+	token = strtok( buffer, ":" );
+    
+	while( token )
+	{
+		StatsPlayer* player;
+
+		player = new StatsPlayer;
+		keeper->m_Players.AddToTail( player );
+		player->m_Score = atoi( token );
+		
+		// Now parse, looking for the rating
+		token = strtok( NULL, ":" );
+		// Guard against corrupt files
+		if( token == NULL )
+		{
+			Mem::Manager::sHandle().PopContext();
+			return;
+		}
+
+		player->m_Rating = atoi( token );
+		token = strtok( NULL, "\n" );
+		// Guard against corrupt files
+		if( token == NULL )
+		{
+			Mem::Manager::sHandle().PopContext();
+			return;
+		}
+
+		strcpy( player->m_Name, token );
+        
+		// Now parse, looking for the next rating
+		token = strtok( NULL, ":" );
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::parse_score_list( int type, char* buffer, bool read_date )
+{
+	StatsLevel* level;
+	char* token;
+	uint32 level_crc;
+
+	Dbg_Assert( type <= vFILE_BEST_COMBO_MONTHLY );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+    if( read_date )
+	{
+		token = strtok( buffer, "\n" );
+
+		strcpy( m_stats_keepers[type].m_Date, token );
+		Dbg_Printf( "Date: %s\n", m_stats_keepers[type].m_Date );
+		token = strtok( NULL, ":" );
+	}
+	else
+	{
+		token = strtok( buffer, ":" );
+	}
+	
+	while( token && ( stricmp( token, "Level" ) == 0 ))
+	{
+		token = strtok( NULL, "\n" );
+		// Guard against corrupt files
+		if( token == NULL )
+		{
+			Mem::Manager::sHandle().PopContext();
+			return;
+		}
+		level_crc = atoi( token );
+		level = new StatsLevel;
+		level->m_Level = level_crc;
+
+		m_stats_keepers[type].m_Levels.AddToTail( level );
+
+		// Now parse, looking for a list of players and scores
+		token = strtok( NULL, ":" );
+		// Guard against corrupt files
+		if( token == NULL )
+		{
+			Mem::Manager::sHandle().PopContext();
+			return;
+		}
+
+		// Read until we reach another "level" or EOF
+		while( token && stricmp( token, "Level" ))
+		{
+			StatsPlayer* player;
+
+			player = new StatsPlayer;
+
+			level->m_Players.AddToTail( player );
+			player->m_Score = atoi( token );
+			Dbg_Printf( "Score %d  ", player->m_Score );
+			token = strtok( NULL, ":" );
+			// Guard against corrupt files
+			if( token == NULL )
+			{
+				Mem::Manager::sHandle().PopContext();
+				return;
+			}
+			player->m_Rating = atoi( token );
+			Dbg_Printf( "Rating %d  ", player->m_Rating );
+			token = strtok( NULL, "\n" );
+			// Guard against corrupt files
+			if( token == NULL )
+			{
+				Mem::Manager::sHandle().PopContext();
+				return;
+			}
+			strcpy( player->m_Name, token );
+			Dbg_Printf( "Name %s\n", player->m_Name );
+
+			// Now parse, looking for the next level or player
+			token = strtok( NULL, ":" );
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+GHTTPBool StatsMan::s_stats_file_dl_complete( GHTTPRequest request, GHTTPResult result, char* buffer,
+											  int buffer_len, void * param )
+{
+	ContentMan* content_man;
+
+	content_man = (ContentMan*) param;
+
+	Dbg_Printf( "Got result %d\n", result );
+
+	content_man->SetPercentComplete( 100 );
+	if( result == GHTTPSuccess )
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		StatsMan* stats_man;
+
+		content_man->SetResult( vCONTENT_RESULT_SUCCESS );
+		content_man->SetError( 0 );
+		
+		stats_man = gamenet_man->mpStatsMan;
+		switch( stats_man->m_cur_file_index )
+		{
+			case vFILE_HIGH_SCORE_ALL_TIME:
+			case vFILE_BEST_COMBO_ALL_TIME:
+				stats_man->parse_score_list( stats_man->m_cur_file_index, buffer, false );
+				break;
+			case vFILE_HIGH_SCORE_MONTHLY:
+			case vFILE_BEST_COMBO_MONTHLY:
+			{
+				stats_man->parse_score_list( stats_man->m_cur_file_index, buffer, true );
+				break;
+			}
+			case vFILE_RATINGS:
+			{
+				stats_man->parse_ratings( buffer );
+				break;
+			}
+		}
+	}
+	else
+	{
+		content_man->SetResult( vCONTENT_RESULT_FAILURE );
+		content_man->SetError( result );
+	}
+	
+	return GHTTPTrue;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::s_stats_logic_code( const Tsk::Task< StatsMan >& task )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	StatsMan& man = task.GetData();
+
+	switch( man.m_state )
+	{
+		case vSTATS_STATE_WAITING:
+			break;
+		case vSTATS_STATE_CONNECTING:
+			if(( Tmr::GetTime() - s_connect_start_time ) > vSTATS_CONNECT_TIMEOUT )
+			{
+				Script::RunScript( "StatsRetrievalFailedDialog" );
+				man.Disconnect();
+				break;
+			}
+			break;
+		case vSTATS_STATE_CONNECTED:
+			man.m_state = vSTATS_STATE_LOGGED_IN;
+			man.Connected();
+			break;
+		case vSTATS_STATE_FAILED_LOG_IN:
+			Script::RunScript( "StatsLoginFailedDialog" );
+			man.Disconnect();
+			break;
+		case vSTATS_STATE_RETRIEVING:
+			if(( Tmr::GetTime() - s_connect_start_time ) > vSTATS_CONNECT_TIMEOUT )
+			{
+				Script::RunScript( "StatsRetrievalFailedDialog" );
+				man.Disconnect();
+				break;
+			}
+			if( PersistThink() == 0 )
+			{
+				Script::RunScript( "StatsRetrievalFailedDialog" );
+				man.Disconnect();
+			}
+			break;
+		case vSTATS_STATE_LOGGED_IN:
+			break;
+		case vSTATS_STATE_IN_GAME:
+			if(( Tmr::GetTime() - man.m_time_since_last_report ) > vSTATS_REPORT_INTERVAL )
+			{
+				man.ReportStats( false );
+			}
+			break;
+	}
+
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	StatsMan::s_threaded_stats_connect( StatsMan* stats_man )
+{
+	int result;
+
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+    sprintf( gcd_gamename, "thps5ps2" );
+	sprintf( gcd_secret_key, "G2k8cF" );
+	Dbg_Printf( "About to connect to stats server...\n" );
+	result = InitStatsConnection( GameNet::vHOST_PORT );
+	switch( result )
+	{
+		case GE_NODNS:	// Unable to resolve stats server DNS
+			gamenet_man->mpStatsMan->m_state = vSTATS_STATE_FAILED_LOG_IN;
+			Dbg_Printf( "Error connecting to stats server: No DNS\n" );
+			break;
+		case GE_NOSOCKET: // Unable to create data socket
+			gamenet_man->mpStatsMan->m_state = vSTATS_STATE_FAILED_LOG_IN;
+			Dbg_Printf( "Error connecting to stats server: No Socket\n" );
+			break;
+		case GE_NOCONNECT: // Unable to connect to stats server
+			gamenet_man->mpStatsMan->m_state = vSTATS_STATE_FAILED_LOG_IN;
+			Dbg_Printf( "Error connecting to stats server: No Connect\n" );
+			break;
+		case GE_DATAERROR: // Unable to receive challenge from stats server, or bad challenge
+			gamenet_man->mpStatsMan->m_state = vSTATS_STATE_FAILED_LOG_IN;
+			Dbg_Printf( "Error connecting to stats server: Data error\n" );
+			break;
+		case GE_NOERROR: // Connected to stats server and ready to send data
+			Dbg_Printf( "Connected to stats server\n" );
+			gamenet_man->mpStatsMan->m_state = vSTATS_STATE_CONNECTED;
+			break;
+		default:
+			Dbg_Assert( 0 );
+			break;
+	}
+
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StatsMan::StatsMan( void )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	m_stats_logic_task = new Tsk::Task< StatsMan > ( s_stats_logic_code, *this );
+	m_logged_in = false;
+	m_state = vSTATS_STATE_WAITING;
+	m_cur_game = NULL;
+	m_need_to_retrieve_stats = true;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StatsMan::~StatsMan( void )
+{
+	delete m_stats_logic_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::Connected( void )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+	m_logged_in = true;
+	Script::RunScript( "stats_logged_in" );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::IsLoggedIn( void )
+{
+	m_logged_in = ( IsStatsConnected() == 1 );
+
+	return m_logged_in;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::NeedToRetrieveStats( void )
+{
+	return m_need_to_retrieve_stats;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::Connect( void )
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	Thread::PerThreadStruct	net_thread_data;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+	
+	m_state = vSTATS_STATE_CONNECTING;
+	s_connect_start_time = Tmr::GetTime();
+
+	Script::RunScript( "stats_log_in_profile" );
+
+    net_thread_data.m_pEntry = s_threaded_stats_connect;
+	net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+	net_thread_data.m_pStackBase = gamenet_man->GetNetThreadStack();
+	net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+	net_thread_data.m_utid = 0x152;
+	Thread::CreateThread( &net_thread_data );
+	gamenet_man->SetNetThreadId( net_thread_data.m_osId );
+	StartThread( gamenet_man->GetNetThreadId(), this );
+
+	if( !m_stats_logic_task->InList())
+	{
+		mlp_man->AddLogicTask( *m_stats_logic_task );
+	}
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::Disconnect( void )
+{   
+    CloseStatsConnection();
+	m_stats_logic_task->Remove();
+	m_logged_in = false;
+	m_state = vSTATS_STATE_WAITING;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptStatsLoggedIn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+
+	stats_man = gamenet_man->mpStatsMan;
+	return stats_man->IsLoggedIn();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptStatsLogIn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		
+	gamenet_man->mpStatsMan->Connect();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptStatsLogOff(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+
+	stats_man = gamenet_man->mpStatsMan;
+	stats_man->Disconnect();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptReportStats(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	bool final;
+
+	final = pParams->ContainsFlag( CRCD(0x132aa339,"final"));
+	stats_man = gamenet_man->mpStatsMan;
+	stats_man->ReportStats( final );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::s_stats_retrieval_callback( int localid, int profileid, persisttype_t type, 
+											  int index, int success, char *data, int len, 
+											  void *instance )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Stats* stats;
+
+	Dbg_Printf( "********* Got stats for %d type %d index %d success %d data %s len %d\n",
+				profileid, type, index, success, data, len );
+	
+	if( success < 1 )
+	{
+		return;
+	}
+	stats = gamenet_man->mpStatsMan->GetStats();
+
+	// Rating
+	if( index == 0 )
+	{
+		char* ptr;
+        
+		ptr = strtok( data, "\\" );
+		Dbg_Printf( "Ptr: %s\n", ptr );
+		if( ptr )
+		{
+			ptr = strtok( NULL, "\\" );
+			Dbg_Printf( "(2)Ptr: %s\n", ptr );
+			stats->m_rating = atoi( ptr );
+			Dbg_Printf( "(3)Rating: %d\n", stats->m_rating );
+		}
+	}
+	else
+	{
+		char* ptr;
+        
+		// Levels
+		ptr = strtok( data, "\\" );
+		if( ptr )
+		{
+			Dbg_Printf( "**** 1\n" );
+			if( stricmp( ptr, "HighScore" ) == 0 )
+			{
+				Dbg_Printf( "**** 2\n" );
+				ptr = strtok( NULL, "\\" );
+				if( ptr )
+				{
+					stats->m_highscore[index - 1] = atoi( ptr );
+					Dbg_Printf( "**** 3 %d\n", stats->m_highscore[index -1 ] );
+				}
+
+				ptr = strtok( NULL, "\\" );
+			}
+			
+			if( stricmp( ptr, "HighCombo" ) == 0 )
+			{
+				Dbg_Printf( "**** 4\n" );
+				ptr = strtok( NULL, "\\" );
+				if( ptr )
+				{
+					stats->m_bestcombo[index - 1] = atoi( ptr );
+					Dbg_Printf( "**** 5 %d\n", stats->m_bestcombo[index -1 ] );
+				}
+			}
+		}
+
+		if( index == ( vNUM_TRACKED_LEVELS - 1 ))
+		{
+			Script::RunScript( "stats_retrieved" );
+			gamenet_man->mpStatsMan->m_state = vSTATS_STATE_LOGGED_IN;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptRetrievePersonalStats(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	int i;
+
+	Dbg_Printf( "Requesting rating\n" );
+	GetPersistDataValues( 0, gamenet_man->mpBuddyMan->GetProfile(), pd_public_ro, 0,
+							  "\\Rating", s_stats_retrieval_callback, NULL );
+	for( i = 0; i < vNUM_TRACKED_LEVELS; i++ )
+	{
+		GetPersistDataValues( 0, gamenet_man->mpBuddyMan->GetProfile(), pd_public_ro, i + 1,
+								  "\\HighScore\\HighCombo", s_stats_retrieval_callback, NULL );
+		
+	}
+	
+	gamenet_man->mpStatsMan->m_state = vSTATS_STATE_RETRIEVING;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptCleanUpTopStats(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	Script::CArray *p_high_scores;
+	int i;
+
+	stats_man = gamenet_man->mpStatsMan;
+	if( stats_man->m_need_to_retrieve_stats == true )
+	{
+		return true;
+	}
+
+	for( i = 0; i < vNUM_RATINGS_FILES; i++ )
+	{
+		stats_man->m_stats_keepers[i].Cleanup();
+	}
+	stats_man->m_need_to_retrieve_stats = true;
+	 
+	p_high_scores = Script::GetArray("high_scores_array_all_time");
+	Script::CleanUpArray( p_high_scores );
+	p_high_scores = Script::GetArray("best_combos_array_all_time");
+	Script::CleanUpArray( p_high_scores );
+	p_high_scores = Script::GetArray("high_scores_array_monthly");
+	Script::CleanUpArray( p_high_scores );
+	p_high_scores = Script::GetArray("best_combos_array_monthly");
+	Script::CleanUpArray( p_high_scores );
+	p_high_scores = Script::GetArray("top_players_array");
+	Script::CleanUpArray( p_high_scores );
+	p_high_scores = Script::GetArray("personal_stats_array");
+	Script::CleanUpArray( p_high_scores );
+
+	ghttpCleanup();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptGetRank(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	int rank, rating;
+	 
+	stats_man = gamenet_man->mpStatsMan;
+	rating = stats_man->m_stats.GetRating();
+	rank = (int) (((float) rating / (float) vMAX_RATING ) * (float) vMAX_RANK );
+	pScript->GetParams()->AddInteger( CRCD(0x7786171a,"rank"), rank ); 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptFillStatsArrays(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	//Script::CArray *p_high_scores;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	uint32 array;
+	 
+	stats_man = gamenet_man->mpStatsMan;
+
+	pParams->GetChecksum( CRCD(0x5ef31148,"array"), &array, true );
+	Dbg_Printf( "Array 0x%x\n", array );
+	switch( array )
+	{
+		case 0xcb95928f:	//high_scores_array_monthly
+			stats_man->m_stats_keepers[vFILE_HIGH_SCORE_MONTHLY].FillSectionedMenu();
+			stats_man->m_stats_keepers[vFILE_HIGH_SCORE_MONTHLY].FillMenu();
+			break;
+		case 0x67f1ae8a:	//best_combos_array_monthly
+			stats_man->m_stats_keepers[vFILE_BEST_COMBO_MONTHLY].FillSectionedMenu();
+			stats_man->m_stats_keepers[vFILE_BEST_COMBO_MONTHLY].FillMenu();
+			break;
+		case 0x5299291b: 	//top_players_array
+			Dbg_Printf( "Filling top players menu\n" );
+			stats_man->m_stats_keepers[vFILE_RATINGS].FillMenu( true );
+			break;
+		case 0xef20d3a1:	//high_scores_array_all_time
+			stats_man->m_stats_keepers[vFILE_HIGH_SCORE_ALL_TIME].FillSectionedMenu();
+			stats_man->m_stats_keepers[vFILE_HIGH_SCORE_ALL_TIME].FillMenu();
+			break;
+		case 0x9fe64312:	//best_combos_array_all_time
+			stats_man->m_stats_keepers[vFILE_BEST_COMBO_ALL_TIME].FillSectionedMenu();
+			stats_man->m_stats_keepers[vFILE_BEST_COMBO_ALL_TIME].FillMenu();
+			break;
+		case 0xfef84fff:	//personal_stats_array
+			stats_man->m_stats.FillMenu();
+			break;
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptRetrieveTopStats(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	DownloadContext context;
+	int file_index;
+
+	stats_man = gamenet_man->mpStatsMan;
+
+	file_index = 0;
+	if( pParams->GetInteger( CRCD(0x7360c9ef,"file"), &file_index ))
+	{
+		stats_man->m_cur_file_index = file_index;
+	}
+	else
+	{
+		stats_man->m_cur_file_index++;
+	}
+    
+	sprintf( context.m_path, "http://%s/thug/%s", SSHOST, s_stats_files[stats_man->m_cur_file_index] );
+	context.m_file_not_found_script = CRCD(0x1c049404,"StatsRetrievalFailed");
+	context.m_transfer_failed_script = CRCD(0x1c049404,"StatsRetrievalFailed");
+	context.m_update_progress_script = CRCD(0x74e0cd35,"do_nothing");
+	context.m_max_size = vMAX_STATS_FILE_SIZE;
+
+	if( stats_man->m_cur_file_index == ( vNUM_RATINGS_FILES - 1 ))
+	{
+		context.m_transfer_complete_script = CRCD(0x7346e8e7,"stats_retrieval_complete");
+	}
+	else
+	{
+		context.m_transfer_complete_script = CRCD(0xd932b07b,"download_more_stats");
+	}
+	
+	context.m_dl_complete = s_stats_file_dl_complete;
+
+	gamenet_man->mpContentMan->DownloadFile( &context );
+
+	stats_man->m_need_to_retrieve_stats = false;
+
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	StatsMan::ScriptNeedToRetrieveTopStats(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	
+	stats_man = gamenet_man->mpStatsMan;
+	return stats_man->NeedToRetrieveStats();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::StartNewGame( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Script::CScriptStructure* pStructure;
+	Prefs::Preferences* pPreferences;
+	const char* server_name, *challenge;
+	int score_limit, time_limit;
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+	Net::Server* server;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	m_cur_game = NewGame( 1 );
+	challenge = GetChallenge( m_cur_game );
+	server = gamenet_man->GetServer();
+	Dbg_Assert( server != NULL );
+
+	pPreferences = gamenet_man->GetNetworkPreferences();
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("server_name") );
+	pStructure->GetText( "ui_string", &server_name, true );
+
+	time_limit = pPreferences->GetPreferenceValue( Script::GenerateCRC("time_limit"), Script::GenerateCRC("time") );
+	score_limit = pPreferences->GetPreferenceValue( Script::GenerateCRC("target_score"), Script::GenerateCRC("score") );
+
+    BucketStringOp( m_cur_game, "hostname", bo_set, (char*) server_name, bl_server, 0 );
+	BucketStringOp( m_cur_game, "mapname", bo_set, gamenet_man->GetLevelName(), bl_server, 0 );
+	BucketStringOp( m_cur_game, "gametype", bo_set, gamenet_man->GetGameModeName(), bl_server, 0 );
+	BucketIntOp( m_cur_game, "mapcrc", bo_set, gamenet_man->GetNetworkLevelId(), bl_server, 0 );
+	BucketIntOp( m_cur_game, "gametypecrc", bo_set, gamenet_man->GetGameTypeFromPreferences(), bl_server, 0 );
+	BucketIntOp( m_cur_game, "maxplayers", bo_set, gamenet_man->GetMaxPlayers(), bl_server, 0 );
+	BucketIntOp( m_cur_game, "timelimit", bo_set, time_limit, bl_server, 0 );
+	BucketIntOp( m_cur_game, "fraglimit", bo_set, score_limit, bl_server, 0 );
+
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		char key_name[64];
+		Net::MsgDesc msg;
+		uint32 id;
+
+		id = player->m_Skater->GetID();
+
+		NewPlayer( m_cur_game, id, player->m_Name );
+		
+		sprintf( key_name, "player" );
+		BucketStringOp( m_cur_game, key_name, bo_set, player->m_Name, bl_player, id );
+		sprintf( key_name, "pid" );
+		BucketIntOp( m_cur_game, key_name, bo_set, player->m_Profile, bl_player, id );
+
+		if( player->m_Profile > 0 )
+		{
+			msg.m_Data = (char*) challenge;
+			msg.m_Length = strlen( challenge ) + 1;
+			msg.m_Id = GameNet::MSG_ID_CHALLENGE;
+			msg.m_Queue = Net::QUEUE_SEQUENCED;
+			msg.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+	
+			server->EnqueueMessage( player->GetConnHandle(), &msg );
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	StatsMan::ReportStats( bool final )
+{
+	int result, cheating_occurred;
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	if( m_cur_game == NULL )
+	{
+		return;
+	}
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		uint32 id;
+		Mdl::Score* score_obj;
+				
+		Obj::CSkaterScoreComponent* p_skater_score_component = GetSkaterScoreComponentFromObject( player->m_Skater );
+		Dbg_Assert(p_skater_score_component);
+	
+		score_obj = p_skater_score_component->GetScore();
+
+		id = player->m_Skater->GetID();
+		
+        BucketIntOp( m_cur_game, "score", bo_set, gamenet_man->GetPlayerScore( id ), bl_player, id );
+		Dbg_Printf( "**** PLAYER %d, Best combo: %d\n", player->m_Skater->GetID(), player->m_BestCombo );
+		BucketIntOp( m_cur_game, "combo", bo_set, player->m_BestCombo, bl_player, id );
+	}
+
+	// 'highscore' will really means 'cheats', but we don't want to give hackers any hints. 
+	// It is meant to be misleading.
+	cheating_occurred = g_CheatsEnabled | gamenet_man->HasCheatingOccurred();
+	BucketIntOp( m_cur_game, "highscore", bo_set, cheating_occurred, bl_server, 0 );
+    result = SendGameSnapShot( m_cur_game, NULL, (int) final );
+	
+	switch( result )
+	{
+		case GE_DATAERROR: 	// If game is NULL and the last game created by NewGame failed (because the connection was lost and disk logging is disabled)
+			Dbg_Printf( "Error sending stats\n" );
+			break;
+		case GE_NOCONNECT: // If the connection is lost and disk logging is disabled
+			Dbg_Printf( "Error sending stats : No connect\n" );
+			break;
+		case GE_NOERROR: //The update was sent, or disk logging is enabled and the game was logged
+			Dbg_Printf( "Sent stats\n" );
+			break;
+	}
+
+	if( final )
+	{
+		EndGame();
+	}
+
+	m_time_since_last_report = Tmr::GetTime();
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::EndGame( void )
+{
+	m_state = vSTATS_STATE_LOGGED_IN;
+	if( m_cur_game )
+	{
+		FreeGame( m_cur_game );
+		m_cur_game = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::PlayerLeft( int id )
+{
+	if( m_state == vSTATS_STATE_IN_GAME )
+	{
+		RemovePlayer( m_cur_game, id );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	StatsMan::GenerateAuthResponse( char* challenge, char* password, char* response )
+{
+	return GenerateAuth( challenge, password, response );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsMan::AuthorizePlayer( int id, char* response )
+{
+	char key_name[64];
+
+	sprintf( key_name, "auth" );
+	BucketStringOp( m_cur_game, key_name, bo_set, response, bl_player, id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Stats*	StatsMan::GetStats( void )
+{
+	return &m_stats;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	StatsMan::GetLevelName( uint32 level_crc )
+{
+	int i;
+	Script::CArray* pArray = Script::GetArray("stats_level_names");
+	Dbg_Assert( pArray );
+
+	for( i = 0; i < (int)pArray->GetSize(); i++ )
+	{   
+		uint32 checksum;
+		Script::CStruct* pStructure = pArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+
+        pStructure->GetChecksum( "level", &checksum );
+		if( level_crc == checksum )
+		{
+			const char* level_name;
+
+			pStructure->GetText( "text", &level_name, true );
+			return (char *) level_name;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Stats::Stats( void )
+{
+	m_rating = 0;
+	memset( m_highscore, 0, sizeof( uint32 ) * vNUM_TRACKED_LEVELS );
+	memset( m_bestcombo, 0, sizeof( uint32 ) * vNUM_TRACKED_LEVELS );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Stats::GetRating( void )
+{
+	return m_rating;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Stats::GetHighScore( uint32 level )
+{
+	int i;
+
+	for( i = 0; i < vNUM_TRACKED_LEVELS; i++ )
+	{
+		if( level == s_levels[i] )
+		{
+			return m_highscore[i];
+		}
+	}
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Stats::GetBestCombo( uint32 level )
+{
+	int i;
+
+	for( i = 0; i < vNUM_TRACKED_LEVELS; i++ )
+	{
+		if( level == s_levels[i] )
+		{
+			return m_bestcombo[i];
+		}
+	}
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Stats::FillMenu( void )
+{
+	Lst::Search< SortableStats > sh;
+	SortableStats* p_stats, *next;
+	Lst::Head< SortableStats > stat_list;
+	int i, count, num_items;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+
+	stats_man = gamenet_man->mpStatsMan;
+
+	num_items = 0;
+	for( i = 0; i < vNUM_TRACKED_LEVELS; i++ )
+	{
+		if( m_highscore[i] > 0 )
+		{
+			p_stats = new SortableStats;
+			p_stats->SetPri( m_highscore[i] );
+			p_stats->m_Score = m_highscore[i];
+			p_stats->m_Level = s_levels[i];
+
+			stat_list.AddNode( p_stats );
+			num_items++;
+		}
+	}
+
+	count = 0;
+	for( p_stats = sh.FirstItem( stat_list ); p_stats; p_stats = sh.NextItem())
+	{
+		Script::CStruct* p_entry;
+			
+		p_entry = new Script::CStruct;
+		p_entry->AddInteger( "score", p_stats->m_Score );
+		p_entry->AddString( "level", stats_man->GetLevelName( p_stats->m_Level ));
+
+        Script::RunScript( CRCD(0xa6111f30,"add_stat_personal_menu_item"), p_entry );
+		//Script::RunScript( CRCD(0xac09e3b,"add_stat_player_menu_item"), p_entry );
+	}
+	for( p_stats = sh.FirstItem( stat_list ); p_stats; p_stats = next )
+	{
+		next = sh.NextItem();
+		delete p_stats;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StatsPlayer::StatsPlayer( void )
+: Lst::Node< StatsPlayer > ( this )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StatsLevel::StatsLevel( void )
+: Lst::Node< StatsLevel > ( this )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+StatsLevel::~StatsLevel( void )
+{
+	Lst::Search< StatsPlayer > sh;
+	StatsPlayer* player, *next;
+
+	for( player = sh.FirstItem( m_Players ); player; player = next )
+	{
+		next = sh.NextItem();
+		delete player;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		StatsKeeper::NumEntries( int max_entries_per_level )
+{
+	int num_entries;
+	Lst::Search< StatsLevel > sh;
+	Lst::Search< StatsPlayer > player_sh;
+	StatsLevel* level;
+	StatsPlayer* player;
+    
+	num_entries = 0;
+	for( level = sh.FirstItem( m_Levels ); level; level = sh.NextItem())
+	{
+		num_entries += ( (int) level->m_Players.CountItems() > max_entries_per_level ) ? 
+							max_entries_per_level : level->m_Players.CountItems();
+	}
+
+	for( player = player_sh.FirstItem( m_Players ); player; player = player_sh.NextItem())
+	{
+		num_entries++;
+	}
+
+	return num_entries;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsKeeper::FillSectionedMenu( void )
+{
+	int rank, count, num_items;
+	Lst::Search< StatsLevel > sh;
+	Lst::Search< StatsPlayer > player_sh;
+	StatsLevel* level;
+	StatsPlayer* player;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	Script::CStruct* p_header;
+
+	stats_man = gamenet_man->mpStatsMan;
+	num_items = NumEntries( vMAX_ENTRIES_PER_LEVEL );
+	count = 0;
+	
+	Dbg_Printf( "*** Filling sectioned menu\n" );
+	for( level = sh.FirstItem( m_Levels ); level; level = sh.NextItem())
+	{
+		int player_count;
+
+		if( level->m_Players.CountItems() > 0)
+		{
+			Script::CStruct* p_level_entry;
+
+			p_level_entry = new Script::CStruct;
+			p_level_entry->AddString( "text", stats_man->GetLevelName( level->m_Level ));
+			Script::RunScript( CRCD(0x1aadee21,"add_stat_header_menu_item"), p_level_entry );
+
+			delete p_level_entry;
+		}
+		
+
+		player_count = 0;
+		for( player = player_sh.FirstItem( level->m_Players ); player; player = player_sh.NextItem())
+		{
+			Script::CStruct* p_entry;
+			
+			if( player_count >= vMAX_ENTRIES_PER_LEVEL )
+			{
+				break;
+			}
+
+			p_entry = new Script::CStruct;
+			p_entry->AddInteger( "rating", player->m_Rating );
+			p_entry->AddInteger( "score", player->m_Score );
+			rank = (int) (((float) player->m_Rating / (float) vMAX_RATING ) * (float) vMAX_RANK );
+			p_entry->AddInteger( "rank", rank );
+			p_entry->AddString( "name", player->m_Name );
+			p_entry->AddString( "level", stats_man->GetLevelName( level->m_Level ));
+
+			Script::RunScript( CRCD(0x19f7bc28,"add_stat_score_menu_item"), p_entry );
+			
+			delete p_entry;
+
+			player_count++;
+		}
+	}
+
+    p_header = new Script::CStruct;
+	p_header->AddString( "text", Script::GetLocalString( CRCD(0xc2283319,"category_all_levels")));
+	Script::RunScript( CRCD(0x1aadee21,"add_stat_header_menu_item"), p_header );
+	delete p_header;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsKeeper::FillMenu( bool just_ratings )
+{
+	int rank;
+	Lst::Search< StatsLevel > sh;
+	Lst::Search< StatsPlayer > player_sh;
+	StatsLevel* level;
+	StatsPlayer* player;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	StatsMan* stats_man;
+	SortableStats* p_stats, *next;
+	Lst::Head< SortableStats > stat_list;
+	Lst::Search< SortableStats > stats_sh;
+	int count;
+
+	stats_man = gamenet_man->mpStatsMan;
+	if( just_ratings )
+	{
+		for( player = player_sh.FirstItem( m_Players ); player; player = player_sh.NextItem())
+		{
+			p_stats = new SortableStats;
+			p_stats->SetPri( player->m_Rating );
+			p_stats->m_Score = player->m_Score;
+			p_stats->m_Rating = player->m_Rating;
+			strcpy( p_stats->m_Name, player->m_Name );
+		
+			stat_list.AddNode( p_stats );
+		}
+	}
+	else
+	{
+		for( level = sh.FirstItem( m_Levels ); level; level = sh.NextItem())
+		{
+			for( player = player_sh.FirstItem( level->m_Players ); player; player = player_sh.NextItem())
+			{
+				p_stats = new SortableStats;
+				p_stats->SetPri( player->m_Score );
+				p_stats->m_Score = player->m_Score;
+				p_stats->m_Level = level->m_Level;
+				p_stats->m_Rating = player->m_Rating;
+				strcpy( p_stats->m_Name, player->m_Name );
+			
+				stat_list.AddNode( p_stats );
+			}
+		}
+	}
+			
+	count = 0;
+	for( p_stats = stats_sh.FirstItem( stat_list ); p_stats; p_stats = stats_sh.NextItem())
+	{
+		Script::CStruct* p_entry;
+
+		if(( p_stats->m_Score == 0 ) || ( count == vSTATS_PLAYER_COUNT ))
+		{
+			break;
+		}
+		p_entry = new Script::CStruct;
+		p_entry->AddInteger( "rating", p_stats->m_Rating );
+		p_entry->AddInteger( "score", p_stats->m_Score );
+		rank = (int) (((float) p_stats->m_Rating / (float) vMAX_RATING ) * (float) vMAX_RANK );
+		p_entry->AddInteger( "rank", rank );
+		p_entry->AddString( "name", p_stats->m_Name );
+
+		if( just_ratings )
+		{
+			Script::RunScript( CRCD(0xac09e3b,"add_stat_player_menu_item"), p_entry );
+		}
+		else
+		{
+			p_entry->AddString( "level", stats_man->GetLevelName( p_stats->m_Level ));
+			Script::RunScript( CRCD(0x19f7bc28,"add_stat_score_menu_item"), p_entry );
+		}
+		delete p_entry;
+		count++;
+	}
+
+	for( p_stats = stats_sh.FirstItem( stat_list ); p_stats; p_stats = next )
+	{
+		next = stats_sh.NextItem();
+		delete p_stats;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	StatsKeeper::Cleanup( void )
+{
+	Lst::Search< StatsLevel > sh;
+	Lst::Search< StatsPlayer > player_sh;
+	StatsLevel* level, *next;
+	StatsPlayer* player, *next_player;
+
+	for( level = sh.FirstItem( m_Levels ); level; level = next )
+	{
+		next = sh.NextItem();
+		delete level;
+	}
+
+	for( player = player_sh.FirstItem( m_Players ); player; player = next_player )
+	{
+		next_player = player_sh.NextItem();
+		delete player;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SortableStats::SortableStats( void )
+: Lst::Node< SortableStats > ( this )
+{
+	m_Score = 0;
+	m_Level = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/Ngps/p_stats.h b/Code/Sk/GameNet/Ngps/p_stats.h
new file mode 100644
index 0000000..51f1369
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/p_stats.h
@@ -0,0 +1,218 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate5													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_stats.h												**
+**																			**
+**	Created by:		04/30/03	-	SPG										**
+**																			**
+**	Description:	PS2 Stat-tracking code 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_NGPS_P_STATS_H
+#define __GAMENET_NGPS_P_STATS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vNUM_TRACKED_LEVELS	12
+
+namespace GameNet
+{
+	
+enum
+{
+	vFILE_HIGH_SCORE_ALL_TIME,
+	vFILE_BEST_COMBO_ALL_TIME,
+	vFILE_HIGH_SCORE_MONTHLY,
+	vFILE_BEST_COMBO_MONTHLY,
+	vFILE_RATINGS,
+	vNUM_RATINGS_FILES
+};
+
+enum
+{
+	vSTATS_STATE_WAITING,
+	vSTATS_STATE_CONNECTING,
+	vSTATS_STATE_CONNECTED,
+	vSTATS_STATE_FAILED_LOG_IN,
+	vSTATS_STATE_LOGGED_IN,
+	vSTATS_STATE_RETRIEVING,
+	vSTATS_STATE_IN_GAME,
+};
+
+
+class Stats
+{
+	friend class StatsMan;
+
+public:
+	Stats( void );
+
+	int		GetRating( void );
+	int		GetHighScore( uint32 level );
+	int		GetBestCombo( uint32 level );
+
+	void	FillMenu( void );
+private:
+	int		m_rating;
+	uint32	m_highscore[vNUM_TRACKED_LEVELS];
+	uint32	m_bestcombo[vNUM_TRACKED_LEVELS];
+};
+
+class StatsPlayer : public Lst::Node< StatsPlayer >
+{
+public:
+	enum
+	{
+		vMAX_STATS_PLAYER_NAME_LEN = 32
+	};
+	StatsPlayer( void );
+	
+	char	m_Name[vMAX_STATS_PLAYER_NAME_LEN];
+	uint32	m_Rating;
+	uint32	m_Score;
+};
+
+class StatsLevel : public Lst::Node< StatsLevel >
+{
+public:
+	StatsLevel( void );
+	~StatsLevel( void );
+
+	uint32		m_Level;
+	Lst::Head< StatsPlayer >	m_Players;
+};
+
+class StatsKeeper 
+{
+public:
+	
+	void	Cleanup( void );
+	int		NumEntries( int max_entries_per_level );
+	void	FillMenu( bool just_ratings = false );
+	void	FillSectionedMenu( void );
+	
+	Lst::Head< StatsLevel > 	m_Levels;
+	Lst::Head< StatsPlayer > 	m_Players;
+	char	m_Date[32];
+};
+
+class StatsMan
+{
+public:
+	StatsMan( void );
+	~StatsMan( void );
+
+	void	Connect( void );
+	void	Disconnect( void );
+	void	Connected( void );
+	bool	IsLoggedIn( void );
+	bool	NeedToRetrieveStats( void );
+	char*	GetLevelName( uint32 level_crc );
+
+	void	StartNewGame( void );
+	void	ReportStats( bool final );
+	char*	GenerateAuthResponse( char* challenge, char* password, char* response );
+	void	AuthorizePlayer( int id, char* response );
+	void	PlayerLeft( int id );
+	void	EndGame( void );
+	Stats*	GetStats( void );
+
+	static	bool		ScriptStatsLoggedIn(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptStatsLogOff(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptStatsLogIn(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptReportStats(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptRetrievePersonalStats(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptRetrieveTopStats(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptNeedToRetrieveTopStats(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptCleanUpTopStats(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptFillStatsArrays(Script::CStruct *pParams, Script::CScript *pScript);
+	static	bool		ScriptGetRank(Script::CStruct *pParams, Script::CScript *pScript);
+    
+private:
+	static	void s_threaded_stats_connect( StatsMan* stats_man );
+	static	void s_stats_retrieval_callback(int localid, int profileid, persisttype_t type, 
+											int index, int success, char *data, int len, 
+											void *instance );
+	static	GHTTPBool s_stats_file_dl_complete(	GHTTPRequest request,       // The request.
+											GHTTPResult result,         // The result (success or an error).
+											char* buffer,              // The file's bytes (only valid if ghttpGetFile[Ex] was used).
+											int buffer_len,              // The file's length.
+											void * param                // User-data.
+											);
+
+	void	parse_score_list( int type, char* buffer, bool read_date );
+	void	parse_ratings( char* buffer );
+
+	Tsk::Task< StatsMan >*					m_stats_logic_task;
+	static	Tsk::Task< StatsMan >::Code   	s_stats_logic_code;
+
+	bool		m_logged_in;
+	int			m_state;
+	statsgame_t	m_cur_game;
+	Tmr::Time	m_time_since_last_report;
+	Stats		m_stats;
+
+	int			m_cur_file_index;
+	StatsKeeper	m_stats_keepers[vNUM_RATINGS_FILES];
+	bool		m_need_to_retrieve_stats;
+};
+
+
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_NGPS_P_STATS_H
+
+
diff --git a/Code/Sk/GameNet/Ngps/snglue.cpp b/Code/Sk/GameNet/Ngps/snglue.cpp
new file mode 100644
index 0000000..30b6aab
--- /dev/null
+++ b/Code/Sk/GameNet/Ngps/snglue.cpp
@@ -0,0 +1,1387 @@
+/****************************************************************************/
+/*                                                                          */
+/* Copyright SN Systems Ltd 2003                                            */
+/*                                                                          */
+/* File:        netglue_sneebsd.c                                           */
+/* Version:     1.01                                                        */
+/* Description: Netglue implementation file for SN NDK EE BSD API           */
+/*                                                                          */
+/* Change History:                                                          */
+/* Vers Date        Author     Changes                                      */
+/* 1.00 19-May-2003 D.Lowther  File Created (for use with DNAS - LN 13419)  */
+/* 1.01 02-Jun-2003 D.Lowther  Fixed bug in sceNetGlueSocket() where it was */
+/*                             checking for iRetval == 0, should be > 0     */
+/*                                                                          */
+/*                                                                          */
+/****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/****************************************************************************/
+/* Stuff that should come from netglue header files but including netglue.h */
+/* causes clashes with snsocket.h                                           */
+/****************************************************************************/
+
+extern "C" {
+
+typedef long ssize_t;
+
+typedef u_char sceNetGlueSaFamily_t;
+typedef u_int  sceNetGlueSocklen_t;
+
+#define sa_family_t sceNetGlueSaFamily_t
+#define socklen_t   sceNetGlueSocklen_t
+
+typedef struct sceNetGlueSockaddr 
+{
+    u_char      sa_len;
+    sa_family_t sa_family;
+    char        sa_data[14];
+} sceNetGlueSockaddr_t;
+
+typedef struct sceNetGlueHostent
+{
+    char    *h_name;
+    char    **h_aliases;
+    int     h_addrtype;
+    int     h_length;
+    char    **h_addr_list;
+#define h_addr h_addr_list[0]
+} sceNetGlueHostent_t;
+
+typedef struct sceNetGlueInAddr 
+{
+    u_int s_addr;
+} sceNetGlueInAddr_t;
+
+/****************************************************************************/
+/* The netglue error codes in the 2.7 lib ref are wrong, Sony's advice is   */
+/* to use the values from ee/gcc/ee/include/sys/errno.h, but I can't        */
+/* include that file in here because of clashes with the same names in      */ 
+/* snsocket.h                                                               */ 
+/* So the following values are copied / pasted from the above errno.h file  */
+/* and I've stuck NG_ on the front of the names, they are listed in the     */
+/* same order as in the 2.7 lib ref for netglue (not numerical order)       */
+/****************************************************************************/
+
+#define NG_EBADF           9 /* Invalid descriptor specified                */
+#define NG_ENOMEM         12 /* Insufficient memory                         */
+#define NG_EBUSY          16 /* Library not yet available (before           */
+                             /* initialization completes, etc.)             */
+#define NG_EINVAL         22 /* Invalid arguments specified                 */
+#define NG_EPROTOTYPE    107 /* Unsupported protocol type specified         */
+#define NG_EOPNOTSUPP     95 /* Invalid call for socket                     */
+#define NG_EPFNOSUPPORT   96 /* Unsupported protocol family specified       */
+#define NG_EAFNOSUPPORT  106 /* Specified address family is an unsupported  */
+                             /* value for the socket protocol family        */
+#define NG_EADDRINUSE    112 /* Tried to bind to port already bound         */
+#define NG_EADDRNOTAVAIL 125 /* Invalid address specified                   */
+#define NG_ENETDOWN      115 /* Interface is down                           */
+#define NG_EHOSTUNREACH  118 /* Network is unreachable                      */
+#define NG_ECONNABORTED  113 /* Aborted by call to sceGlueAbort()           */
+#define NG_ECONNRESET    104 /* Connection was reset                        */
+#define NG_EISCONN       127 /* Specified connection has already been made  */
+#define NG_ENOTCONN      128 /* Specified connection does not exist         */
+#define NG_ETIMEDOUT     116 /* Timeout                                     */
+#define NG_ECONNREFUSED  111 /* Connection request was refused              */
+
+/* NDK can return other error codes e.g to indicate problems with the       */
+/* comms between the EE and IOP, where there isn't an obvious map from NDK  */
+/* to Netglue error code, I'll use NG_UNSPECIFIED                           */ 
+
+#define NG_UNSPECIFIED    NG_EINVAL /* Don't know what else to use          */
+
+/****************************************************************************/
+/* Netglue error codes for DNS operations from netdb.h + NG_ on front       */
+/****************************************************************************/
+
+#define NG_NETDB_INTERNAL -1 /* see errno */
+#define NG_NETDB_SUCCESS  0  /* no problem */
+#define NG_HOST_NOT_FOUND 1  /* Authoritative Answer Host not found */
+#define NG_TRY_AGAIN      2  /* Non-Authoritative Host not found, or SERVERFAIL */
+#define NG_NO_RECOVERY    3  /* Non recoverable errs, FORMERR, REFUSED, NOTIMP */
+#define NG_NO_DATA        4  /* Valid name, no data record of requested type */
+#define	NG_NO_ADDRESS   NG_NO_DATA /* no address, look for MX record */
+
+
+/****************************************************************************/
+/* Private data / types                                                     */
+/****************************************************************************/
+
+/* Absolute maximum number of EE threads which may use EE socket API */
+#define ABS_MAX_THREADS      10
+
+/* Structure to hold info for each thread using this netglue API */
+
+typedef struct snNetGlueThreadInfo 
+{
+    int                 iErrno;
+    int                 iHerrno;
+    int                 iThreadId;
+    sceNetGlueHostent_t NetGlueHostent;
+    /* Following are for sceNetGlueGethostbyaddr */
+    sn_uint32           *sn_h_addr_list[2]; /* one plus null terminator */
+    sn_uint32           sn_h_addr;          /* above [0] points to this */
+} snNetGlueThreadInfo_t;
+
+/* Array to hold info for all the threads using this netglue API */
+/* An element is 'free' if it's iThreadId is zero                */
+
+static snNetGlueThreadInfo_t s_ThreadInfo[ABS_MAX_THREADS] =
+
+{{ 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0}, 
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0},
+ { 0, 0, 0, {0,0,0,0,0}, {0,0}, 0}};
+
+/****************************************************************************/
+/* Private functions                                                        */
+/****************************************************************************/
+
+/****************************************************************************/
+/* Private functions - for accessing s_ThreadInfo                           */
+/****************************************************************************/
+
+static int snGetThreadInfoIndexForThreadId(int iThreadId)
+{
+
+    /* Searches s_ThreadInfo[].iThreadId for a match with iThreadId         */
+    /* If match found returns index in s_ThreadInfo, else returns -1        */
+
+    int i;
+
+    for (i=0; i= 0)
+    {
+        if(iInterruptsWereEnabled != 0)
+        {
+            EI();
+        }
+        printf("Warning: snAddToThreadInfo() was already added\n");
+        return iThreadInfoIndex;
+    }
+
+    /* Not already registered, so try and find a spare entry to use */
+    iThreadInfoIndex = snGetThreadInfoIndexFreeEntry();
+
+    /* If the list is full, print error message and return error */
+    if (iThreadInfoIndex < 0)
+    {
+        if(iInterruptsWereEnabled != 0)
+        {
+            EI();
+        }
+        printf("Error: snAddToThreadInfo() list is full\n");
+        return -1;
+    }
+
+    /* Store iThreadId in s_ThreadInfo */
+    s_ThreadInfo[iThreadInfoIndex].iThreadId = iThreadId;
+    s_ThreadInfo[iThreadInfoIndex].iErrno    = 0;
+    s_ThreadInfo[iThreadInfoIndex].iHerrno   = 0;
+
+    if(iInterruptsWereEnabled != 0)
+    {
+        EI();
+    }
+    return iThreadInfoIndex;
+}
+
+static int snRemoveFromThreadInfo(int iThreadId)
+{
+    /* Finds iThreadId in s_ThreadInfo and then sets it to zero             */
+    /* If it didn't find iThreadId returns -1, else returns index           */
+
+    int iInterruptsWereEnabled;
+    int iThreadInfoIndex;
+
+    /* Do this with interrupts disabled to serialise access */
+    iInterruptsWereEnabled = DI();
+
+    /* Search s_ThreadInfo for iThreadId */
+    iThreadInfoIndex = snGetThreadInfoIndexForThreadId(iThreadId);
+    if (iThreadInfoIndex >= 0)
+    {
+        /* Mark the entry where it was stored as free now */
+        s_ThreadInfo[iThreadInfoIndex].iThreadId = 0;
+    }
+
+    if(iInterruptsWereEnabled != 0)
+    {
+        EI();
+    }
+    return iThreadInfoIndex;
+}
+
+
+/****************************************************************************/
+/* Private functions - for converting / storing error codes                 */
+/****************************************************************************/
+
+static void snStoreErrno(int iErrno)
+{
+
+    /* iErrno must be a netglue error code, this function will store it     */
+    /* in s_ThreadInfo[].iErrno for the entry for this ThreadId             */
+
+    int iThreadInfoIndex;
+
+    /* Get index in s_ThreadInfo[] for this thread */
+    iThreadInfoIndex = snGetThreadInfoIndexForThreadId(GetThreadId());
+
+    /* This shouldn't happen, but be defensive */
+    if (iThreadInfoIndex < 0)
+    {
+        printf("Warning: snStoreErrno() thread not registered\n");
+        return;
+    }
+    
+    s_ThreadInfo[iThreadInfoIndex].iErrno = iErrno;
+}
+
+static int snConvertNdkErrToNetGlueErr(int iSnErrno)
+{
+    /* Converts NDK error number in iSnErrno into the closest NetGlue error */
+    /* number and returns it                                                */
+
+    int iRetval = 0;
+
+    switch(iSnErrno)
+    {
+        case ENOBUFS:                  /* No memory buffers are available   */
+            iRetval = NG_ENOMEM;       /* Insufficient memory               */
+        break;
+
+        case ETIMEDOUT:                /* Operation timed out               */
+            iRetval = NG_ETIMEDOUT;    /* Timeout                           */
+        break;
+
+        case EISCONN:                  /* The socket is already connected   */
+            iRetval = NG_EISCONN;      /* Connection has already been made  */
+        break;
+
+        case EOPNOTSUPP:               /* Operation not supported           */
+            iRetval = NG_EOPNOTSUPP;   /* Invalid call for socket           */
+        break;
+
+        case ECONNABORTED:             /* The connection was aborted        */
+            iRetval = NG_ECONNABORTED; /* Aborted by call to sceGlueAbort() */
+        break;
+
+        case EWOULDBLOCK:              /* Caller would block                */
+            iRetval = -1;              /* Shouldn't happen in blocking mode */
+        break;
+
+        case ECONNREFUSED:             /* The connection was refused        */
+            iRetval = NG_ECONNREFUSED; /* Connection request was refused    */
+        break;
+
+        case ECONNRESET:               /* The connection has been reset     */
+            iRetval = NG_ECONNRESET;   /* Connection was reset              */
+        break;
+
+        case ENOTCONN:                 /* The socket is not connected       */
+            iRetval = NG_ENOTCONN;     /* Connection does not exist         */
+        break;
+
+        case EALREADY:                 /* Operation is already in progress  */
+            iRetval = -1;              /* Shouldn't happen in blocking mode */
+        break;
+
+        case EINVAL:                   /* Invalid parameter                 */
+            iRetval = NG_EINVAL;       /* Invalid arguments specified       */
+        break;
+
+        case EMSGSIZE:                 /* Invalid message size              */
+            iRetval = -1;              /* Shouldn't happen (not using fn's) */              
+        break;
+
+        case EPIPE:                    /* Cannot send any more              */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_ENOMEM;       /* Insufficient memory               */
+        break;
+
+        case EDESTADDRREQ:             /* Destination address is missing    */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EINVAL;       /* Invalid arguments specified       */
+        break;
+
+        case ESHUTDOWN:                /* Connection has been shut down     */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_ENOTCONN;     /* Connection does not exist         */
+        break;
+
+        case ENOPROTOOPT:              /* Option unknown for this protocol  */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EINVAL;       /* Invalid arguments specified       */
+        break;
+
+        case EHAVEOOB:                 /* Have received out-of-band data    */
+            iRetval = -1;              /* Shouldn't happen                  */              
+        break;
+
+        case ENOMEM:                   /* No memory available               */
+            iRetval = NG_ENOMEM;       /* Insufficient memory               */
+        break;
+
+        case EADDRNOTAVAIL:            /* Specified address not available   */
+            iRetval = NG_EADDRNOTAVAIL;/* Invalid address specified         */
+        break;
+
+        case EADDRINUSE:               /* Specified address already in use  */
+            iRetval = NG_EADDRINUSE;   /* Tried bind to port already bound  */
+        break;
+
+        case EAFNOSUPPORT:             /* Address family is not supported   */
+            iRetval = NG_EAFNOSUPPORT; /* Address family is an unsupported  */
+        break;
+
+        case EINPROGRESS:              /* Operation is in progress          */
+            iRetval = -1;              /* Shouldn't happen in blocking mode */
+        break;
+
+        case ELOWER:                   /* Unused                            */
+            iRetval = -1;              /* Shouldn't happen                  */
+        break;
+
+        case EBADF:                    /* Socket descriptor is invalid      */
+            iRetval = NG_EBADF;        /* Invalid descriptor specified      */
+        break;
+
+        case SN_ENOTINIT:              /* Socket API not initialised        */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EBUSY;        /* Library not yet available         */
+        break;
+
+        case SN_ETHNOTREG:             /* Thread not registered with API    */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EINVAL;       /* Invalid arguments specified       */
+        break;
+
+        case SN_EMAXTHREAD:            /* Max no of threads exceeded        */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EINVAL;       /* Invalid arguments specified       */
+        break;
+
+        case SN_EIOPNORESP:            /* Failed to initialise IOP end      */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EBUSY;        /* Library not yet available         */
+        break;
+
+        case SN_ENOMEM:                /* Not enough mem to init sock API   */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_ENOMEM;       /* Insufficient memory               */
+        break;
+
+        case SN_EBINDFAIL:             /* sceSifBindRpc failed              */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EBUSY;        /* Library not yet available         */
+        break;
+
+        case SN_EINVTHREAD:            /* Invalid thread id                 */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EINVAL;       /* Invalid arguments specified       */
+        break;
+
+        case SN_EALRDYINIT:            /* Socket API already initialised    */
+            iRetval = -1;              /* Shouldn't happen                  */
+        break;
+
+        case SN_ESTKDOWN:              /* Stack has not been started        */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EBUSY;        /* Library not yet available         */
+        break;
+
+        case SN_EIFSTATE:              /* Error in inet_ifstate             */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_EBUSY;        /* Library not yet available         */
+        break;
+
+        case SN_EDNSFAIL:              /* General failure code for DNS      */
+            iRetval = -1;              /* Shouldn't get this for socket err */
+        break;
+
+        case SN_SWERROR:               /* Software error in SN sys code     */
+            iRetval = -1;              /* Shouldn't happen                  */
+        break;
+
+        case SN_EDNSBUSY:              /* DNS is busy try again later       */
+            iRetval = -1;              /* Shouldn't get this for socket err */
+        break;
+
+        case SN_REQSIZE:               /* RPC request is not expected size  */
+            iRetval = -1;              /* Shouldn't happen                  */
+        break;
+
+        case SN_REQINV:                /* RPC request is not valid          */
+            iRetval = -1;              /* Shouldn't happen                  */
+        break;
+
+        case SN_REQSYNC:               /* RPC general IOP s/w failure       */
+            iRetval = -1;              /* Shouldn't happen                  */
+        break;
+
+        case SN_RPCBAD:                /* RPC fn code unknown to IOP        */
+            iRetval = -1;              /* Shouldn't happen                  */
+        break;
+
+        case SN_NOTREADY:              /* Device not ready                  */
+                                       /* Closest NG error code is ...      */
+            iRetval = NG_ENETDOWN;     /* Interface is down                 */
+        break;
+
+        default:                       /* Unknown SN NDK error code         */
+            iRetval = -1;              /* Shouldn't happen                  */
+    }
+
+    /* If iRetval is set to -1 (unexpected error) has to be worth printing  */
+    /* a debug error message to indicate what the SN error code was         */
+
+    if (iRetval == -1)
+    {
+        printf("Warning: snConvertNdkErrToNetGlueErr(%d) unexpected\n",iSnErrno);
+        iRetval = NG_UNSPECIFIED;
+    }
+
+    return iRetval;
+}
+
+static void snGetConvStoreSocketError(int iSockId)
+{
+    /* This should only be called when an SN NDK function that operates on  */
+    /* a socket returns -1 indicating that it failed                        */
+    /*                                                                      */
+    /* Get the sn error number for the socket specified by iSockId, convert */
+    /* to a netglue error code and store it in s_ThreadInfo[].iErrno for    */
+    /* this thread id                                                       */
+
+    int iSnErrno;
+    int iNgErrno;
+
+    /* Get the NDK error code */
+    iSnErrno = sn_errno(iSockId);
+
+    /* Convert it to a netglue error code */
+    iNgErrno = snConvertNdkErrToNetGlueErr(iSnErrno);
+
+    /* Store it in s_ThreadInfo[].iErrno for this thread */
+    snStoreErrno(iNgErrno);
+}
+
+static void snStoreHerrno(int iHerrno)
+{
+    /* iHerrno must be a netglue error code, this function will store it   */
+    /* in s_ThreadInfo[].iErrno for the entry for this ThreadId             */
+
+    int iThreadInfoIndex;
+
+    /* Get index in s_ThreadInfo[] for this thread */
+    iThreadInfoIndex = snGetThreadInfoIndexForThreadId(GetThreadId());
+
+    /* This shouldn't happen, but be defensive */
+    if (iThreadInfoIndex < 0)
+    {
+        printf("Warning: snStoreHerrno() thread not registered\n");
+        return;
+    }
+    
+    s_ThreadInfo[iThreadInfoIndex].iHerrno = iHerrno;
+}
+
+static void snGetConvStoreDnsError(void)
+{
+    /* This should only be called when an SN NDK function that results in   */
+    /* an error that can be read by sn_h_errno()                            */
+    /*                                                                      */
+    /* Get the error number from sn_h_errno() and convert to a netglue      */
+    /* error code and store it in s_ThreadInfo[].iHerrno for this thread id */
+
+    int iSnHerrno;
+    int iNgHerrno = 0;
+
+    /* Get the NDK error code */
+    iSnHerrno = sn_h_errno();
+
+    /* Convert it to a netglue error code */
+    switch (iSnHerrno)
+    {
+        /* NDK does not support the standard BSD DNS error codes:           */
+        /* HOST_NOT_FOUND, TRY_AGAIN, NO_RECOVERY, NO_DATA, NO_ADDRESS, all */
+        /* of these conditions result in NDK returning the following error  */
+
+        case SN_EDNSFAIL:
+            iNgHerrno = NG_HOST_NOT_FOUND; /* not sure what else I can do */
+        break;
+
+        /* Internal errors in the TCP/IP stack state, or, invalid input     */
+        /* can result in the following errors:                              */
+        /* SN_ESTKDOWN The TCP/IP Stack has not been started                */
+        /* EINVAL name was too long or was a NULL pointer                   */
+
+        default:
+            printf("Warning: snGetConvStoreDnsError(%d) unexpected\n",iSnHerrno);
+            iNgHerrno = NG_NETDB_INTERNAL;
+            /* The sony comment for the above Herrno says "see errno", so I */
+            /* I assume I should store something in the errno.              */
+            snStoreErrno(NG_UNSPECIFIED);
+    }
+
+    /* Store the Herrno in s_ThreadInfo[].iHerrno for this thread */
+    snStoreHerrno(iNgHerrno);
+}
+
+/****************************************************************************/
+/* Private functions - convert sceNetGlueSockaddr_t <-> struct sockaddr     */
+/****************************************************************************/
+
+static void snConvGlueToNdkSockaddr(sceNetGlueSockaddr_t *psAddr)
+{
+    /* Does in place conversion                                             */
+    /* from: sceNetGlueSockaddr_t                                           */
+    /* to:   NDK struct sockaddr                                            */
+    
+    /* Do nothing if addr is NULL */
+    if (psAddr == NULL)
+        return;
+
+    /* Swap the byte order for NDK sa_family (16 bits) doesn't have sa_len  */
+    psAddr->sa_len    = psAddr->sa_family;
+    psAddr->sa_family = 0;
+}
+
+static void snConvNdkToGlueSockaddr(sceNetGlueSockaddr_t *psAddr)
+{
+    /* Does in place conversion                                             */
+    /* from: NDK struct sockaddr                                            */
+    /* to:   sceNetGlueSockaddr_t                                           */
+    
+    /* Do nothing if addr is NULL */
+    if (psAddr == NULL)
+        return;
+    
+    /* Swap the byte order for NDK sa_family (16 bits) doesn't have sa_len  */
+    psAddr->sa_family = psAddr->sa_len;
+    psAddr->sa_len    = 16;
+}
+
+/****************************************************************************/
+/* Private functions - misc                                                 */
+/****************************************************************************/
+
+static sceNetGlueHostent_t *snConvNdkToGlueHostent(struct hostent *psNdkHostent)
+{
+    /* convert hostent from NDK format to NetGlue format and store it in    */
+    /* s_ThreadInfo[].NetGlueHostent for this thread                        */
+
+    int iThreadInfoIndex;
+    sceNetGlueHostent_t *psRetval = NULL;
+
+    /* Get the index for this thread in s_ThreadInfo[] */
+    iThreadInfoIndex = snGetThreadInfoIndexForThreadId(GetThreadId());
+
+    if (iThreadInfoIndex < 0)
+    {
+        printf("Error: snConvNdkToGlueHostent() unregistered thread\n");
+        return NULL;
+    }
+
+    /* Found the index, so now convert from NDK struct hostent, into        */
+    /* sceNetGlueHostent_t, only difference is that the two fields          */
+    /* h_addrtype and h_length are 16 bit in NDK / 32 bit in Netglue        */
+
+    psRetval = &s_ThreadInfo[iThreadInfoIndex].NetGlueHostent;    
+    memset(psRetval, 0, sizeof(*psRetval)); /* just being defensive */
+    psRetval->h_addr_list = psNdkHostent->h_addr_list;
+    psRetval->h_addrtype  = psNdkHostent->h_addrtype;
+    psRetval->h_aliases   = psNdkHostent->h_aliases;
+    psRetval->h_length    = psNdkHostent->h_length;
+    psRetval->h_name      = psNdkHostent->h_name;
+
+    return psRetval;
+}
+
+/****************************************************************************/
+/* Functions exported by netglue (in same order as sony libref doc)         */
+/****************************************************************************/
+
+int *__sceNetGlueErrnoLoc(void)
+{
+    int iThreadInfoIndex;
+    int iThreadId;
+
+    /* Find the index for this thread in s_ThreadInfo */
+    iThreadId = GetThreadId();
+    iThreadInfoIndex = snGetThreadInfoIndexForThreadId(iThreadId);
+
+    /* This shouldn't happen, but be defensive */
+    if (iThreadInfoIndex < 0)
+    {
+        printf("Error: __sceNetGlueErrnoLoc() unregistered thread (%d)\n",iThreadId);
+        return NULL;
+    }
+    
+    return &s_ThreadInfo[iThreadInfoIndex].iErrno;
+}
+
+int *__sceNetGlueHErrnoLoc(void)
+{
+    int iThreadInfoIndex;
+    int iThreadId;
+
+    /* Find the index for this thread in s_ThreadInfo */
+    iThreadId = GetThreadId();
+    iThreadInfoIndex = snGetThreadInfoIndexForThreadId(iThreadId);
+
+    /* This shouldn't happen, but be defensive */
+    if (iThreadInfoIndex < 0)
+    {
+        printf("Error: __sceNetGlueHErrnoLoc() unregistered thread (%d)\n",iThreadId);
+        return NULL;
+    }
+    
+    return &s_ThreadInfo[iThreadInfoIndex].iHerrno;
+}
+
+int sceNetGlueAbort(int sockfd)
+{
+    /* The best approximation to this is to close the socket */
+    printf("Warning sceNetGlueAbort() just does closesocket\n");
+    closesocket(sockfd);
+    return 0;
+}
+
+int sceNetGlueAbortResolver(int thread_id)
+{
+    /* Can't see a way to implement this at the moment */
+    printf("Warning sceNetGlueAbortResolver() does nothing\n");
+    return 0;
+}
+
+int sceNetGlueAccept
+    (int s, sceNetGlueSockaddr_t *addr, sceNetGlueSocklen_t *paddrlen)
+{
+    int iRetval;
+
+    /* *addr is an output so no need to convert it before calling NDK */
+
+    /* Call the NDK equivalent */
+    iRetval = accept(s, (struct sockaddr*) addr, (int*) paddrlen);
+
+    /* NetGlue and NDK both use positive value for success */
+    if (iRetval > 0)
+    {
+        /* convert *addr from NDK to Netglue format */
+        snConvNdkToGlueSockaddr(addr);
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueBind
+    (int s, const sceNetGlueSockaddr_t *addr, sceNetGlueSocklen_t addrlen)
+{
+
+    int iRetval;
+
+    /* *addr is an input so need to convert it before calling NDK */
+    /* but it's a const, so have to clone it before converting it */
+    sceNetGlueSockaddr_t copyAddr;
+    memcpy(©Addr, addr, sizeof(copyAddr));
+
+    /* convert *addr from Netglue to NDK format */
+    snConvGlueToNdkSockaddr(©Addr);
+
+    /* Call the NDK equivalent */
+    iRetval = bind(s, (struct sockaddr*) ©Addr, addrlen);
+
+    /* NetGlue and NDK both use zero for success */
+    if (iRetval == 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueConnect
+    (int s, const sceNetGlueSockaddr_t *addr, sceNetGlueSocklen_t addrlen)
+{
+    int iRetval;
+
+    /* *addr is an input so need to convert it before calling NDK */
+    /* but it's a const, so have to clone it before converting it */
+    sceNetGlueSockaddr_t copyAddr;
+    memcpy(©Addr, addr, sizeof(copyAddr));
+
+    /* convert *addr from Netglue to NDK format */
+    snConvGlueToNdkSockaddr(©Addr);
+
+    /* Call the NDK equivalent */
+    iRetval = connect(s, (struct sockaddr*) ©Addr, addrlen);
+
+    /* NetGlue and NDK both use zero for success */
+    if (iRetval == 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+sceNetGlueHostent_t*
+    sceNetGlueGethostbyaddr(const char *addr, int len, int type)
+{
+    /* NDK does not currently support reverse DNS lookup. Advice from Sony  */
+    /* is that DNAS does not use this function (although it does appear in  */
+    /* the list of symbols for the DNAS lib)                                */
+
+    /* Without reverse DNS lookup, the best I can do is construct a         */
+    /* sceNetGlueHostent_t to return that contains the addr input param and */
+    /* and NULL name / alias                                                */
+
+    int iThreadInfoIndex;
+    sceNetGlueHostent_t *psRetval = NULL;
+    
+    iThreadInfoIndex = snGetThreadInfoIndexForThreadId(GetThreadId());
+
+    if (iThreadInfoIndex < 0)
+    {
+        printf("Error: sceNetGlueGethostbyaddr() unregistered thread\n");
+        /* Putting this in Herrno should make app look at Errno */
+        snStoreHerrno(NG_NETDB_INTERNAL);
+        snStoreErrno(NG_EINVAL);
+        return NULL;
+    }
+
+    /* Get pointer to sceNetGlueHostent_t for this thread */
+    psRetval = &s_ThreadInfo[iThreadInfoIndex].NetGlueHostent;
+
+    /* Set up the sceNetGlueHostent_t that's going to be returned */
+    psRetval->h_addr_list    = (char**)&s_ThreadInfo[iThreadInfoIndex].sn_h_addr_list;
+    psRetval->h_addr_list[0] = (char*) &s_ThreadInfo[iThreadInfoIndex].sn_h_addr;
+    psRetval->h_addr_list[1] = NULL;
+    psRetval->h_addrtype     = AF_INET; /* NDK and Netglue define this as 2 */
+    psRetval->h_aliases      = NULL;
+    psRetval->h_length       = 4;
+    psRetval->h_name         = NULL;
+
+    /* Copy the input addr to the sn_h_addr pointed to by above struct */
+    memcpy(&s_ThreadInfo[iThreadInfoIndex].sn_h_addr, addr, 4);
+
+    return psRetval;
+}
+
+sceNetGlueHostent_t *sceNetGlueGethostbyname(const char *name)
+{
+    struct hostent *psResult      = NULL;
+    sceNetGlueHostent_t *psRetval = NULL;
+
+    /* Call the NDK equivalent */
+    psResult = gethostbyname(name);
+
+    /* NetGlue and NDK both use NULL for error */
+    if (psResult != NULL)
+    {
+        /* convert hostent from NDK format to NetGlue format and store it   */
+        /* in s_ThreadInfo[].NetGlueHostent for this thread                 */
+        psRetval = snConvNdkToGlueHostent(psResult);
+        return psRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreDnsError();
+
+    /* NetGlue and NDK both use -1 for error */
+    return NULL;
+}
+
+int sceNetGlueGetpeername
+    (int s, sceNetGlueSockaddr_t *addr, sceNetGlueSocklen_t *paddrlen)
+{
+    int iRetval;
+
+    /* *addr is an output so no need to convert it before calling NDK */
+
+    /* Call the NDK equivalent */
+    iRetval = getpeername(s, (struct sockaddr*) addr, (int*) paddrlen);
+
+    /* NetGlue and NDK both use zero for success */
+    if (iRetval == 0)
+    {
+        /* convert *addr from NDK to Netglue format */
+        snConvNdkToGlueSockaddr(addr);
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueGetsockname
+    (int s, sceNetGlueSockaddr_t *addr, sceNetGlueSocklen_t *paddrlen)
+{
+    int iRetval;
+
+    /* *addr is an output so no need to convert it before calling NDK */
+
+    /* Call the NDK equivalent */
+    iRetval = getsockname(s, (struct sockaddr*) addr, (int*) paddrlen);
+
+    /* NetGlue and NDK both use zero for success */
+    if (iRetval == 0)
+    {
+        /* convert *addr from NDK to Netglue format */
+        snConvNdkToGlueSockaddr(addr);
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueGetsockopt
+    (int s, int level, int optname, void *optval,
+     sceNetGlueSocklen_t *optlen)
+{
+    /* At the moment the only option supported by NetGlue is TCP_NODELAY    */
+    /* which uses level = IPPROTO_TCP.                                      */
+    /* Because the values defined for IPPROTO_TCP and TCP_NODELAY are the   */
+    /* same in NDK and Netglue, there is no need to do any conversion here  */
+
+    int iRetval;
+
+    /* Call the NDK equivalent */
+    iRetval = getsockopt(s, level, optname, optval, (int*) optlen);
+
+    /* NetGlue and NDK both use zero for success */
+    if (iRetval == 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+u_int sceNetGlueHtonl(u_int hostlong)
+{
+    return htonl(hostlong);
+}
+
+u_short sceNetGlueHtons(u_short hostshort)
+{
+    return htons(hostshort);
+}
+
+u_int sceNetGlueInetAddr(const char *cp)
+{
+    int iRetval;
+
+    /* Call the NDK equivalent */
+    iRetval = inet_addr(cp);
+
+    /* NetGlue and NDK both use INADDR_NONE (all f's) for failure */
+    return iRetval;
+}
+
+int sceNetGlueInetAton(const char *cp, sceNetGlueInAddr_t *addr)
+{
+    /* sceNetGlueInAddr_t is the same as NDK struct in_addr */
+
+    int iRetval;
+
+    /* Call the NDK equivalent */
+    iRetval = inet_aton(cp, (struct in_addr*) addr);
+
+    /* NetGlue and NDK both use 1 for success / 0 for failure */
+    return iRetval;
+}
+
+u_int sceNetGlueInetLnaof(sceNetGlueInAddr_t in)
+{
+    /* to do not implemented yet - but not used by DNAS 2.7.1 */
+    printf("Warning: sceNetGlueInetLnaof() not implemented\n");
+    return 0;
+}
+
+sceNetGlueInAddr_t sceNetGlueInetMakeaddr(u_int net, u_int host)
+{
+    /* to do not implemented yet - but not used by DNAS 2.7.1 */
+    sceNetGlueInAddr_t sRetval;
+    memset(&sRetval, 0, sizeof(sRetval));
+    printf("Warning: sceNetGlueInetMakeaddr() not implemented\n");
+    return sRetval;
+}
+
+u_int sceNetGlueInetNetof(sceNetGlueInAddr_t in)
+{
+    /* to do not implemented yet - but not used by DNAS 2.7.1 */
+    printf("Warning: sceNetGlueInetNetof() not implemented\n");
+    return 0;
+}
+
+u_int sceNetGlueInetNetwork(const char *cp)
+{
+    /* to do not implemented yet - but not used by DNAS 2.7.1 */
+    printf("Warning: sceNetGlueInetNetwork() not implemented\n");
+    return INADDR_NONE;
+}
+
+char *sceNetGlueInetNtoa(sceNetGlueInAddr_t in)
+{
+    char *pszRetval;
+
+    /* sceNetGlueInAddr_t is the same as NDK struct in_addr, but can't cast */
+    /* non scalar in param passed to call, so copy to ndk_in                */
+    struct in_addr ndk_in;
+    ndk_in.s_addr = in.s_addr;
+
+    /* Call the NDK equivalent */
+    pszRetval = inet_ntoa(ndk_in);
+
+    /* If NDK fails it returns NULL, netglue doc has no mention of failure  */
+    return pszRetval;
+}
+
+int sceNetGlueListen(int s, int backlog)
+{
+    int iRetval;
+
+    /* Call the NDK equivalent */
+    iRetval = listen(s, backlog);
+
+    /* NetGlue and NDK both use 0 for success */
+    if (iRetval == 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+u_int sceNetGlueNtohl(u_int netlong)
+{
+    return ntohl(netlong);
+}
+
+u_short sceNetGlueNtohs(u_short netshort)
+{
+    return ntohs(netshort);
+}
+
+ssize_t sceNetGlueRecv(int s, void *buf, size_t len, int flags)
+{
+    int iRetval;
+
+    /* 2.7.0 Netglue doc says flags "Not supported (always set to zero)     */
+    /* So enforce this and print error if flags != 0                        */
+
+    if (flags != 0)
+    {
+        printf("Warning: sceNetGlueRecv() called with flags non zero (%d)\n",flags);
+        flags = 0;
+    }
+
+    /* Call the NDK equivalent */
+    iRetval = recv(s, buf, len, flags);
+
+    /* NetGlue and NDK both use >= 0 for success */
+    if (iRetval >= 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+ssize_t sceNetGlueRecvfrom
+    (int s, void *buf, size_t len, int flags,
+     sceNetGlueSockaddr_t *addr, sceNetGlueSocklen_t *paddrlen)
+{
+    int iRetval;
+
+    /* 2.7.0 Netglue doc says flags "Not supported (always set to zero)     */
+    /* So enforce this and print error if flags != 0                        */
+
+    if (flags != 0)
+    {
+        printf("Warning: sceNetGlueRecvfrom() called with flags non zero (%d)\n",flags);
+        flags = 0;
+    }
+
+    /* *addr is an output so no need to convert it before calling NDK */
+
+    /* Call the NDK equivalent */
+    iRetval = recvfrom(s, buf, len, flags, (struct sockaddr*) addr, (int*) paddrlen);
+
+    /* NetGlue and NDK both use >= 0 for success */
+    if (iRetval >= 0)
+    {
+        /* convert *addr from NDK to Netglue format */
+        snConvNdkToGlueSockaddr(addr);
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+ssize_t sceNetGlueSend(int s, const void *buf, size_t len, int flags)
+{
+    int iRetval;
+
+    /* 2.7.0 Netglue doc says flags "Not supported (always set to zero)     */
+    /* So enforce this and print error if flags != 0                        */
+
+    if (flags != 0)
+    {
+        printf("Warning: sceNetGlueSend() called with flags non zero (%d)\n",flags);
+        flags = 0;
+    }
+
+    /* Call the NDK equivalent */
+    iRetval = send(s, buf, len, flags);
+
+    /* NetGlue and NDK both use >= 0 for success */
+    if (iRetval >= 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+ssize_t sceNetGlueSendto
+    (int s, const void *buf, size_t len, int flags,
+     const sceNetGlueSockaddr_t *addr, sceNetGlueSocklen_t addrlen)
+{
+    int iRetval;
+
+    /* *addr is an input so need to convert it before calling NDK */
+    /* but it's a const, so have to clone it before converting it */
+    sceNetGlueSockaddr_t copyAddr;
+    memcpy(©Addr, addr, sizeof(copyAddr));
+
+    /* convert *addr from Netglue to NDK format */
+    snConvGlueToNdkSockaddr(©Addr);
+
+    /* 2.7.0 Netglue doc says flags "Not supported (always set to zero)     */
+    /* So enforce this and print error if flags != 0                        */
+
+    if (flags != 0)
+    {
+        printf("Warning: sceNetGlueSendto() called with flags non zero (%d)\n",flags);
+        flags = 0;
+    }
+
+    /* Call the NDK equivalent */
+    iRetval = sendto(s, buf, len, flags, (struct sockaddr*)©Addr, addrlen);
+
+    /* NetGlue and NDK both use >= 0 for success */
+    if (iRetval >= 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueSetSifMBindRpcValue
+    (u_int buffersize, u_int stacksize, int priority)
+{
+    /* According to Sony this function is deprecated and hasn't been        */
+    /* implemented in any release of netglue since 2.4                      */
+    /* NDK doesn't have the run-time options to support this in any case    */
+
+    /* Print a warning but return indicating success */
+    printf("Warning: sceNetGlueSetSifMBindRpcValue() not implemented\n");
+
+    return 0;
+}
+
+int sceNetGlueSetsockopt
+    (int s, int level, int optname, const void *optval,
+     sceNetGlueSocklen_t optlen)
+{
+    /* At the moment the only option supported by NetGlue is TCP_NODELAY    */
+    /* which uses level = IPPROTO_TCP.                                      */
+    /* Because the values defined for IPPROTO_TCP and TCP_NODELAY are the   */
+    /* same in NDK and Netglue, there is no need to do any conversion here  */
+
+    int iRetval;
+
+    /* Call the NDK equivalent */
+    iRetval = setsockopt(s, level, optname, optval, optlen);
+
+    /* NetGlue and NDK both use zero for success */
+    if (iRetval == 0)
+    {
+        return iRetval;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueShutdown(int s, int how)
+{
+    int iCloseResult;
+    int iShutdownResult;
+
+    /* NDK and netglue both use the same *values* for the how parameter but */
+    /* use different names, like this                                       */
+    /* NDK values                 Netglue values                            */
+    /* #define SD_RECEIVE  0      #define SHUT_RD    0                      */
+    /* #define SD_SEND     1      #define SHUT_WR    1                      */
+    /* #define SD_BOTH     2      #define SHUT_RDWR  2                      */
+
+    /* Also netglue only supports SHUT_RDWR (2.7.0 lib ref) and a call to   */
+    /* this also performs the function of closesocket()                     */
+    
+    if (how != SD_BOTH)
+    {
+        printf("Warning: sceNetGlueShutdown() called with how != SHUT_RDWR (%d)\n",how);
+        how = SD_BOTH;
+    }
+
+    /* Call the NDK equivalent */
+    iShutdownResult = shutdown(s, how);
+    iCloseResult    = closesocket(s);
+
+    /* NetGlue and NDK both use zero for success */
+    if ((iShutdownResult == 0) && (iCloseResult == 0))
+    {
+        return 0;
+    }
+    
+    /* Get / convert / store error detail */
+    snGetConvStoreSocketError(s);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueSocket(int family, int type, int protocol)
+{
+    /* NDK and netglue both use the same values and names for the various  */
+    /* #defines relevant to the paramters this function is passed:         */
+    /*                                                                     */
+    /* family may only be AF_INET, both NDK and netglue define this as 2   */
+    /*                                                                     */
+    /* type may be SOCK_STREAM (NDK and netglue define this as 1)          */
+    /*          or SOCK_DGRAM  (NDK and netglue define this as 2)          */
+    /*          or SOCK_RAW    (NDK not supported, netglue defines it as 3)*/
+    /*                                                                     */
+    /* Sony advise that DNAS does not use SOCK_RAW.                        */
+    /*                                                                     */
+    /* protocol is not supported by netglue (always set to zero), for      */
+    /* NDK, protcol is always passed in to socket() as PF_INET             */
+
+    int iRetval;
+
+    /* Trap any calls using type SOCK_RAW (and any other unsupported type) */
+
+    if ((type != SOCK_STREAM) && (type != SOCK_DGRAM))
+    {
+        printf("Error: sceNetGlueSocket() called with type = (%d)\n",type);
+        snStoreErrno(NG_EINVAL);
+        return -1;
+    }
+
+    /* Call the NDK equivalent */
+    iRetval = socket(family, type, PF_INET);
+
+    /* Positive value is a good socket descriptor, return it */ 
+    if (iRetval > 0) 
+    { 
+        return iRetval; 
+    }     
+
+    /* NDK doesn't provide a general errno, only provided per socket, but  */
+    /* failed to create the socket so no way of knowing why                */
+
+    snStoreErrno(NG_UNSPECIFIED);
+
+    /* NetGlue and NDK both use -1 for error */
+    return -1;
+}
+
+int sceNetGlueThreadInit(int thread_id)
+{
+    int iResult;
+    int iDontCare;
+    int iThisThreadId;
+
+    iThisThreadId = GetThreadId();
+
+    /* thread_id == 0 means this thread */
+    if (thread_id == 0)
+    {
+        thread_id = iThisThreadId;
+    }
+
+    /* NDK requires each thread that is going to use the socket API to      */
+    /* register *itself* with the NDK API by calling sockAPIregthr() i.e.   */
+    /* there is currently no way for thread A to register thread B          */
+    /* so trap this and report error.                                       */
+
+    /* If this restriction turns out to be a problem in practice then it    */
+    /* will be necessary for NDK to be modified so that thread A can        */
+    /* register thread B e.g. add new fn sockAPIregother()                  */
+
+    /* so trap this and report error.                                       */
+
+    if (iThisThreadId != thread_id)
+    {
+        printf("Error: sceNetGlueThreadInit() can only register own thread\n");
+        return -1;
+    }
+
+    /* Add this to our internal list of registered threads */
+    iResult = snAddToThreadInfo(thread_id);
+
+    /* If it failed (list is full), return now with failure */
+    if (iResult < 0)
+    {
+        return -1;
+    }
+       
+    /* Now register with NDK */
+    iResult = sockAPIregthr();
+
+    /* If it worked ok, return now indicating success */
+    if (iResult == 0)
+    {
+        return 0;
+    }
+
+    /* Failed to register with NDK so remove from local list */
+    iDontCare = snRemoveFromThreadInfo(thread_id);
+
+    return -1;
+}
+
+int sceNetGlueThreadTerminate(int thread_id)
+{
+    int iResult1;
+    int iResult2;
+    int iThisThreadId;
+
+    iThisThreadId = GetThreadId();
+
+    /* thread_id == 0 means this thread */
+    if (thread_id == 0)
+    {
+        thread_id = iThisThreadId;
+    }
+
+    /* Remove from local list */
+    iResult1 = snRemoveFromThreadInfo(thread_id);
+
+    /* De-register with NDK */
+    iResult2 = sockAPIderegother(thread_id);
+
+    /* If both operations successful return indicating success */
+    if ((iResult1 >= 0) && (iResult2 ==0))
+    {
+        return 0;
+    }
+
+    return -1;
+}
+
+} // end of extern "C"
+
diff --git a/Code/Sk/GameNet/Player.cpp b/Code/Sk/GameNet/Player.cpp
new file mode 100644
index 0000000..d7d2637
--- /dev/null
+++ b/Code/Sk/GameNet/Player.cpp
@@ -0,0 +1,1451 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate3													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		Player.cpp												**
+**																			**
+**	Created by:		08/09/01	-	spg										**
+**																			**
+**	Description:	PlayerInfo functionality								**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+//#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 		   // mostly getting score
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+enum
+{
+	vINVULNERABILITY_PERIOD = 10,		// in frames
+	vINVULNERABILITY_BAIL_SECONDS = 1,	// in seconds
+	vPROJECTILE_INVULNERABILITY_INTERVAL = 5000,// in ms
+};
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+int 	Manager::GetTeamScore( int team_id )
+{
+	PlayerInfo* player, *local_player;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Lst::Search< PlayerInfo > sh;
+	Mdl::Score* score_obj;
+	int score;
+	bool in_goal_attack;
+	 
+	score = 0;
+
+	local_player = GetLocalPlayer();
+	in_goal_attack = skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" );
+	// Next create a sorted list of the players (sorted in order of highest to lowest score)
+	for( player = FirstPlayerInfo( sh ); player; player = NextPlayerInfo( sh ))
+	{
+		// Only show the names/scores of players that are fully in the game
+		if((player->IsFullyIn() == false ) || ( player->m_Team != team_id ))
+		{
+			continue;
+		}
+
+		if( in_goal_attack && !local_player->IsObserving())
+		{
+			Game::CGoalManager* pGoalManager;
+			 
+			pGoalManager = Game::GetGoalManager();
+			return pGoalManager->NumGoalsBeatenByTeam( team_id );
+		}
+		else
+		{
+			Obj::CSkaterScoreComponent* p_skater_score_component = GetSkaterScoreComponentFromObject(player->m_Skater);
+			Dbg_Assert(p_skater_score_component);
+			
+			score_obj = p_skater_score_component->GetScore();
+			score += score_obj->GetTotalScore();
+		}
+	}
+
+	return score;
+}
+
+int 	Manager::GetPlayerScore( int obj_id )
+{
+	bool in_goal_attack;
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::PlayerInfo* local_player;
+	
+	local_player = GetLocalPlayer();
+	in_goal_attack = skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" );
+	if( in_goal_attack && !local_player->IsObserving())
+	{
+		Game::CGoalManager* pGoalManager;
+			 
+		pGoalManager = Game::GetGoalManager();
+		return pGoalManager->NumGoalsBeatenBy( obj_id );
+	}
+	else
+	{
+		Mdl::Score* score_obj;
+		PlayerInfo* player;
+
+		player = GetPlayerByObjectID( obj_id );
+		Dbg_Assert( player );
+		
+		Obj::CSkaterScoreComponent* p_skater_score_component = GetSkaterScoreComponentFromObject(player->m_Skater);
+		Dbg_Assert(p_skater_score_component);
+	
+		score_obj = p_skater_score_component->GetScore();
+		return score_obj->GetTotalScore();
+	}
+}
+
+void	Manager::s_render_scores_code( const Tsk::Task< Manager >& task )
+{
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Script::CScriptStructure *pParams;
+	Game::CGameMode* pGameMode;
+	PlayerInfo* player;
+	Lst::Search< PlayerInfo > sh;
+	static Tmr::Time last_update = 0;
+	Tmr::Time cur_time;
+	Lst::Head< ScoreRank > rank_list;
+	Lst::Search< ScoreRank > rank_sh;
+	ScoreRank* rank, *next;
+	bool show_score, king_mode;
+
+	Manager& man = task.GetData();
+    // Only draw the scores in modes in which the score accumulates
+	pGameMode = skate_mod->GetGameMode();
+	show_score =  skate_mod->GetGameMode()->GetNameChecksum() != CRCD( 0x1c471c60, "netlobby" );
+
+	// in King of the hill games, "score" is actually milliseconds with the crown
+	king_mode =	( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0x6ef8fda0, "netking" )) ||
+				( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0x5d32129c, "king" ));
+    
+	cur_time = Tmr::GetTime();
+	if(( cur_time - last_update ) >= Tmr::Seconds( 1 ))
+	{
+		int i, j;
+		uint32 id;
+		
+		last_update = cur_time;
+
+		// First clear all previous entries
+		Script::RunScript( "clear_scores" );
+        
+		if( show_score && man.ShouldDisplayTeamScores())
+		{
+			ScoreRank* ranks[vMAX_TEAMS];
+            
+			for( j = 0; j < skate_mod->GetGameMode()->NumTeams(); j++ )
+			{
+				if( man.NumTeamMembers( j ) > 0 )
+				{
+					uint32 team_name_checksum;
+					
+					switch( j )
+					{
+					case 0:
+						team_name_checksum = CRCD( 0x3224348d, "team_1_name" );
+						break;
+					case 1:
+						team_name_checksum = CRCD( 0xb4b04623, "team_2_name" );
+						break;
+					case 2:
+						team_name_checksum = CRCD( 0x7fec9586, "team_3_name" );
+						break;
+					case 3:
+						team_name_checksum = CRCD( 0x62e9a53e, "team_4_name" );
+						break;
+					default:
+						Dbg_Assert( 0 );
+						team_name_checksum = 0;
+						break;
+					}
+	
+					ranks[j] = new ScoreRank;
+					strcpy( ranks[j]->m_Name, Script::GetString( team_name_checksum ));
+					ranks[j]->m_IsKing = false;
+					ranks[j]->m_HasFlag = false;
+					ranks[j]->m_WhichFlags = 0;
+					ranks[j]->m_ColorIndex = j + 2;
+					ranks[j]->m_TotalScore = man.GetTeamScore( j );
+				}
+			}
+
+			// Next create a sorted list of the players (sorted in order of highest to lowest score)
+			for( player = man.FirstPlayerInfo( sh ); player; player = man.NextPlayerInfo( sh ))
+			{
+				// Only show the names/scores of players that are fully in the game
+				if( !( player->IsFullyIn()))
+				{
+					continue;
+				}
+				if( player->IsLocalPlayer())
+				{
+					char new_name[64];
+
+					// This is my team.  Make that obvious to the players
+					sprintf( new_name, "> %s", ranks[player->m_Team]->m_Name );
+					strcpy( ranks[player->m_Team]->m_Name, new_name );
+				}
+	
+				if( player->IsKing())
+				{
+					ranks[player->m_Team]->m_IsKing = true;
+				}
+				if( player->HasCTFFlag())
+				{
+					ranks[player->m_Team]->m_HasFlag = true;
+					ranks[player->m_Team]->m_WhichFlags |= ( 1 << player->HasWhichFlag());
+				}
+			}
+
+			for( j = 0; j < skate_mod->GetGameMode()->NumTeams(); j++ )
+			{
+				if( man.NumTeamMembers( j ) > 0 )
+				{
+					// Lists sort based on node's priority so set the node's priority to that of
+					// the team's score and add it to the list
+					ranks[j]->SetPri( ranks[j]->m_TotalScore );
+					rank_list.AddNode( ranks[j] );
+				}
+			}
+		}
+		else
+		{
+			// Next create a sorted list of the players (sorted in order of highest to lowest score)
+			for( player = man.FirstPlayerInfo( sh ); player; player = man.NextPlayerInfo( sh ))
+			{
+				// Only show the names/scores of players that are fully in the game
+				if( !( player->IsFullyIn()))
+				{
+					continue;
+				}
+	
+				rank = new ScoreRank;
+				if( player->IsLocalPlayer())
+				{
+					// This is me.  Make that obvious.
+					sprintf( rank->m_Name, "> %s", player->m_Name );
+				}
+				else
+				{
+					strcpy( rank->m_Name, player->m_Name );
+				}
+				
+				rank->m_IsKing = player->IsKing();
+				if( skate_mod->GetGameMode()->IsTeamGame())
+				{
+					rank->m_ColorIndex = player->m_Team + 2;
+				}
+				else
+				{
+					rank->m_ColorIndex = player->m_Skater->GetID() + 2;
+				}
+				if( player->HasCTFFlag())
+				{
+					rank->m_HasFlag = true;
+					rank->m_WhichFlags |= ( 1 << player->HasWhichFlag());
+				}
+				
+				if( show_score )
+				{
+					// Lists sort based on node's priority so set the node's priority to that of
+					// the player's score and add him to the list
+					rank->SetPri( man.GetPlayerScore( player->m_Skater->GetID()));
+					rank_list.AddNode( rank );
+				}
+				else
+				{
+					// Just add the player to the list so that the order is the same regardless of score
+					rank_list.AddToTail( rank );
+				}
+			}
+		}
+		
+
+		// Now loop through the sorted list in order of highest to lowest score and print them out
+		i = 0;
+		for( rank = rank_sh.FirstItem( rank_list ); rank; rank = next )
+		{   
+			char score_str[64];
+			char title[16];
+
+			title[0]='\0';
+			if( rank->m_IsKing )
+			{
+#if ENGLISH == 0
+				sprintf( title, Script::GetLocalString( "player_str_king_abbreviation" ));
+#else 
+				sprintf( title, "K" );
+#endif
+			}
+			else
+			{
+				if( rank->m_HasFlag )
+				{
+					if( rank->m_WhichFlags & 1 )
+					{
+						strcat( title, "\\s0" );
+					}
+					if( rank->m_WhichFlags & 2 )
+					{
+						strcat( title, "\\s1" );
+					}
+					if( rank->m_WhichFlags & 4 )
+					{
+						strcat( title, "\\s2" );
+					}
+					if( rank->m_WhichFlags & 8 )
+					{
+						strcat( title, "\\s3" );
+					}
+				}
+				else
+				{
+					title[0] = '\0';
+				}
+			}
+
+			next = rank_sh.NextItem();
+			if( show_score )
+			{
+				if( king_mode )
+				{
+					int seconds;
+
+					seconds = Tmr::InSeconds( rank->GetPri() );
+					if( seconds >= 3600 )
+					{
+						sprintf( score_str, "\\c4%s\\c%d %s \\c%d %d:%.2d:%.2d", 
+								   								title,
+																rank->m_ColorIndex, 
+																rank->m_Name,
+																rank->m_ColorIndex,
+																seconds / 3600,
+																( seconds % 3600 ) / 60,
+																seconds % 60 );
+					}
+					else
+					{
+						sprintf( score_str, "\\c4%s\\c%d %s \\c%d %d:%.2d", 
+										   						title,
+																rank->m_ColorIndex, 
+																rank->m_Name,
+																rank->m_ColorIndex,
+																seconds / 60,
+																seconds % 60 );
+					}
+					
+				}
+				else
+				{
+					sprintf( score_str, "%s\\c%d %s \\c%d %d", 	title,
+																rank->m_ColorIndex, 
+																rank->m_Name,
+																rank->m_ColorIndex,
+																rank->GetPri());
+				}
+
+			}
+			else
+			{
+				sprintf( score_str, "\\c%d%s", 	rank->m_ColorIndex, rank->m_Name );
+			}
+            
+			switch( i )
+			{
+			case 0:
+				id = CRCD( 0x2b083809, "net_score_1" );
+				break;
+			case 1:
+				id = CRCD( 0xb20169b3, "net_score_2" );
+				break;
+			case 2:
+				id = CRCD( 0xc5065925, "net_score_3" );
+				break;
+			case 3:
+				id = CRCD( 0x5b62cc86, "net_score_4" );
+				break;
+			case 4:
+				id = CRCD( 0x2c65fc10, "net_score_5" );
+				break;
+			case 5:
+				id = CRCD( 0xb56cadaa, "net_score_6" );
+				break;
+			case 6:
+				id = CRCD( 0xc26b9d3c, "net_score_7" );
+				break;
+			case 7:
+				id = CRCD( 0x52d480ad, "net_score_8" );
+				break;
+			default:
+				Dbg_Assert( 0 );
+				id = 0;
+				break;
+			}
+			
+			pParams = new Script::CScriptStructure;
+			pParams->AddComponent( CRCD( 0xc4745838, "text" ), ESYMBOLTYPE_STRING, score_str );
+			pParams->AddComponent( CRCD( 0x40c698af, "id" ), ESYMBOLTYPE_NAME, id );
+			
+			
+			Front::CScreenElement* p_name_elem = p_screen_elem_man->GetElement( id, Front::CScreenElementManager::DONT_ASSERT );
+			// If any operable menus are up, ignore input
+			if( p_name_elem )
+			{
+				//make sure the scores are enabled
+				//if(!Mdl::Skate::Instance()->GetCareer()->GetGlobalFlag(Script::GetInteger(CRCD(0xfbb66146,"NO_DISPLAY_NET_SCORES")))))
+					p_name_elem->SetProperties( pParams );
+			}
+			Script::RunScript( "update_score", pParams );
+
+			delete pParams;
+			delete rank;
+			i++;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo::PlayerInfo( int flags )
+: Lst::Node< PlayerInfo > ( this )
+{
+	int i, j;
+
+	sprintf( m_Name, "" );
+
+	for( i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		m_LastSentProps.m_LastSkaterFlagsUpdate[i] = -1;
+		m_LastSentProps.m_LastSkaterStateUpdate[i] = -1;
+		m_LastSentProps.m_LastDoingTrickUpdate[i] = -1;
+		for( j = 0; j < 3; j++ )
+		{
+			m_LastSentProps.m_LastSkaterPosUpdate[i][j] = -1;
+			m_LastSentProps.m_LastSkaterRotUpdate[i][j] = -1;
+		}
+	}
+
+	m_flags = flags;
+	m_observer_logic_task = NULL;
+	m_jump_in_frame = 0;
+	m_last_object_update_id = vMAX_PLAYERS - 1;
+	m_face_data = NULL;
+
+	m_Skater = NULL;
+	mp_SkaterProfile = NULL;
+	m_last_bail_time = 0;
+	m_last_hit_time = 0;
+	m_Team = vTEAM_RED;
+	m_Profile = 0;
+	m_Rating = 0;
+	m_VehicleControlType = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+PlayerInfo::~PlayerInfo( void )
+{
+	
+
+	if ( mp_SkaterProfile )
+		delete mp_SkaterProfile;
+
+	if( m_observer_logic_task )
+	{
+		delete m_observer_logic_task;
+	}
+
+	if( m_face_data )
+	{
+		delete [] m_face_data;
+		m_face_data = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::SetSkater( Obj::CSkater* skater )
+{
+	Manager * gamenet_man = Manager::Instance();
+	
+	m_Skater = skater;
+	if( !IsLocalPlayer())
+	{
+		if( gamenet_man->GetCurrentlyObservedPlayer() == this )
+		{
+			gamenet_man->ObservePlayer( this );
+		}
+	}
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::SetFaceData( uint8* face_data, int size )
+{
+	uint8 player_id;
+
+	Dbg_Assert( m_face_data == NULL );
+	Dbg_Assert( face_data != NULL );
+	Dbg_Assert( m_Skater != NULL );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetMiscHeap());
+
+	player_id = m_Skater->GetID();
+		 
+	// Store the player's skater id as the first byte of the face data
+	m_face_data = new uint8[size+1];
+	*m_face_data = player_id;
+	memcpy( m_face_data + 1, face_data, size );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The player's skater id is the first byte of the face data
+uint8*	PlayerInfo::GetFaceData( void )
+{
+	return m_face_data;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::CopyProfile( Obj::CSkaterProfile* pSkaterProfile )
+{
+	
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	Dbg_Assert( pSkaterProfile );
+
+#if 0
+	// copy it to the server's player info data
+	uint8* pTempBuffer = new uint8[vMAX_APPEARANCE_DATA_SIZE];
+	uint32 size = pSkaterProfile->WriteToBuffer(pTempBuffer, vMAX_APPEARANCE_DATA_SIZE);
+	Dbg_Printf("Appearance data size is %d bytes\n", size);
+	mp_SkaterProfile = new Obj::CSkaterProfile;
+	Dbg_Assert( mp_SkaterProfile );
+	mp_SkaterProfile->ReadFromBuffer(pTempBuffer);
+	delete pTempBuffer;
+#endif
+
+	// now uses assignment operator to copy over the info as well
+	mp_SkaterProfile = new Obj::CSkaterProfile;
+	*mp_SkaterProfile = *pSkaterProfile;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsLocalPlayer( void )
+{
+	return m_flags.TestMask( mLOCAL_PLAYER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsServerPlayer( void )
+{
+	return m_flags.TestMask( mSERVER_PLAYER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsObserving( void )
+{
+	return m_flags.TestMask( mOBSERVER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsSurveying( void )
+{
+	return m_flags.TestMask( mSURVEYING );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::MarkAsRestarting( void )
+{
+	m_flags.SetMask( mRESTARTING );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void	PlayerInfo::MarkAsNonCollidable( void )
+{
+	m_last_bail_time = Tmr::GetTime();
+	return m_flags.SetMask( mNON_COLLIDABLE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::ClearNonCollidable( void )
+{
+	return m_flags.ClearMask( mNON_COLLIDABLE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsNonCollidable( void )
+{
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	if( !m_Conn->TestStatus( Net::Conn::mSTATUS_READY ))
+	{
+		return true;
+	}
+
+	if( m_Skater->IsInWorld() == false )
+	{
+		return true;
+	}
+
+	// Make skaters invulnerable for the first 10 frames of the game
+	if( m_Skater->GetStateHistory()->GetNumPosUpdates() < vINVULNERABILITY_PERIOD )
+	{
+		return true;
+	}
+
+	if(( Tmr::GetTime() - m_last_bail_time ) < Tmr::Seconds( vINVULNERABILITY_BAIL_SECONDS ))
+	{
+		return true;
+	}
+
+	// Don't collide with eliminated players in firefight
+	if( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0xbff33600,"netfirefight"))
+	{
+		Mdl::Score* score_obj;
+		Obj::CSkaterScoreComponent* p_score_component = GetSkaterScoreComponentFromObject(m_Skater);
+		Dbg_Assert(p_score_component);
+		
+		score_obj = p_score_component->GetScore();
+		if( score_obj->GetTotalScore() <= 0 )
+		{
+			return true;
+		}
+	}
+	
+	// If they've been in this state for more than 5 seconds, that means that
+	// for one reason or another, we didn't get their "NotifyBailDone" message.
+	// This will rectify things.
+	if(( Tmr::GetTime() - m_last_bail_time ) > Tmr::Seconds( 5 ))
+	{
+		ClearNonCollidable();
+		return false;
+	}
+
+	if( m_flags.TestMask( mNON_COLLIDABLE ))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::MarkAsFullyIn( void )
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	m_flags.SetMask( mFULLY_IN );
+
+	if( gamenet_man->InNetGame() && 
+		gamenet_man->OnServer() &&
+		!IsLocalPlayer())
+	{
+		switch( gamenet_man->GetHostMode())
+		{
+			case vHOST_MODE_SERVE:
+				break;
+			case vHOST_MODE_AUTO_SERVE:
+			{
+				Net::Server* server;
+				Net::MsgDesc msg_desc;
+
+				server = gamenet_man->GetServer();
+				Dbg_Assert( server );
+
+				msg_desc.m_Id = MSG_ID_AUTO_SERVER_NOTIFICATION;
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = vSEQ_GROUP_PLAYER_MSGS;
+				// Tell the client that they're in an auto-serving server
+				server->EnqueueMessage( GetConnHandle(), &msg_desc );
+				break;
+			}
+			case vHOST_MODE_FCFS:
+			{
+				PlayerInfo* player;
+
+				player = gamenet_man->GetServerPlayer();
+
+				// If we don't currently have a player acting as the host
+				// make this new player the host
+				if( player == NULL )
+				{
+					gamenet_man->ChooseNewServerPlayer();
+				}
+				break;
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsFullyIn( void )
+{
+	return m_flags.TestMask( mFULLY_IN | mLOCAL_PLAYER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::MarkAsServerPlayer( void )
+{
+	m_flags.SetMask( mSERVER_PLAYER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::MarkAsNotServerPlayer( void )
+{
+	m_flags.ClearMask( mSERVER_PLAYER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::HasCTFFlag( void )
+{
+	return m_flags.TestMask( mHAS_ANY_FLAG );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		PlayerInfo::HasWhichFlag( void )
+{
+	if( m_flags.TestMask( mHAS_RED_FLAG ))
+	{
+		return 0;
+	}
+	if( m_flags.TestMask( mHAS_BLUE_FLAG ))
+	{
+		return 1;
+	}
+	if( m_flags.TestMask( mHAS_GREEN_FLAG ))
+	{
+		return 2;
+	}
+	if( m_flags.TestMask( mHAS_YELLOW_FLAG ))
+	{
+		return 3;
+	}
+
+	return -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::ClearCTFState( void )
+{
+	m_flags.ClearMask( mHAS_ANY_FLAG );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::CapturedFlag( int team )
+{   
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* local_player;
+	char team_str[64];
+	Script::CStruct* pParams;
+		
+	sprintf( team_str, "team_%d_name", team + 1 );
+
+	local_player = gamenet_man->GetLocalPlayer();
+
+	// Make sure they have a flag on them
+	Dbg_Assert( HasCTFFlag());
+
+	pParams = new Script::CStruct;
+	
+	if( IsLocalPlayer())
+	{
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		pParams->AddInteger( "team", team );
+		Script::RunScript( "captured_flag_you", pParams );
+		
+	}
+	else
+	{
+		Dbg_Printf( "***** Captured Flag : My Team %d, Flag Team %d\n", local_player->m_Team, team );
+		if( ( !local_player->IsObserving()) && 
+			( local_player->m_Team == team ))
+		{
+			Dbg_Printf( "***** Captured Flag 2\n" );
+			pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, m_Name );
+			pParams->AddInteger( "team", team );
+			Script::RunScript( "hide_ctf_arrow" );
+			Script::RunScript( "captured_your_flag", pParams );
+		}
+		else
+		{
+			pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, m_Name );
+			pParams->AddComponent( Script::GenerateCRC("String1"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+			pParams->AddInteger( "team", team );
+			Script::RunScript( "captured_flag_other", pParams );
+		}                                                      
+	}
+	
+	m_flags.ClearMask( mHAS_ANY_FLAG );
+
+	delete pParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::StoleFlag( int team )
+{
+	// Shouldn't have any flag already
+	Dbg_Assert( !HasCTFFlag());
+
+	Dbg_Printf( "********* In StoleFlag\n" );
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* local_player, *player;
+	Lst::Search< PlayerInfo > sh;
+	char team_str[64];
+	Script::CStruct* pParams;
+		
+	sprintf( team_str, "team_%d_name", team + 1 );
+	pParams = new Script::CStruct;
+
+	local_player = gamenet_man->GetLocalPlayer();
+
+	
+	if( IsLocalPlayer())
+	{
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		Script::RunScript( "stole_flag_you", pParams );
+		
+	}
+	else
+	{
+		Dbg_Printf( "***** FLAG STOLEN: Team %d, I had %d\n", team, local_player->HasWhichFlag() );
+		if( !local_player->IsObserving() && ( local_player->HasWhichFlag() == team ))
+		{
+
+			pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, m_Name );
+			pParams->AddComponent( Script::GenerateCRC("String1"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+			Script::RunScript( "stole_flag_from_you", pParams );
+		}
+		else
+		{
+			pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, m_Name );
+			pParams->AddComponent( Script::GenerateCRC("String1"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+			Script::RunScript( "stole_flag_other", pParams );
+		}
+	}
+
+	delete pParams;
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		if( player->HasWhichFlag() == team )
+		{
+			player->m_flags.ClearMask(( mHAS_RED_FLAG << team ));
+			player->ClearCTFState();
+			Dbg_Assert( !player->HasCTFFlag());
+		}
+	}
+
+	m_flags.SetMask(( mHAS_RED_FLAG << team ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::TookFlag( int team )
+{
+	// Shouldn't have any flag already
+	Dbg_Assert( !HasCTFFlag());
+
+	Manager * gamenet_man = Manager::Instance();
+	PlayerInfo* local_player;
+	char team_str[64];
+	Script::CStruct* pParams;
+		
+	sprintf( team_str, "team_%d_name", team + 1 );
+	pParams = new Script::CStruct;
+
+	local_player = gamenet_man->GetLocalPlayer();
+
+    if( IsLocalPlayer())
+	{
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		Script::RunScript( "took_flag_you", pParams );
+		
+	}
+	else
+	{
+		Script::CStruct* arrow_params;
+
+		if(( !local_player->IsObserving()) && ( team == local_player->m_Team ))
+		{
+			arrow_params = new Script::CStruct;
+			arrow_params->AddInteger( "team", local_player->m_Team );
+			Script::RunScript( "show_ctf_arrow", arrow_params );
+			delete arrow_params;
+
+			pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, m_Name );
+			Script::RunScript( "took_flag_yours", pParams );
+		}
+		else
+		{
+			pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, m_Name );
+			pParams->AddComponent( Script::GenerateCRC("String1"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+			Script::RunScript( "took_flag_other", pParams );
+		}
+	}
+
+	delete pParams;
+	m_flags.SetMask(( mHAS_RED_FLAG << team ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::RetrievedFlag( void )
+{
+	Manager * gamenet_man = Manager::Instance();
+	GameNet::PlayerInfo* player, *local_player;
+	Lst::Search< PlayerInfo > sh;
+	char team_str[64];
+	Script::CStruct* pParams;
+		
+	sprintf( team_str, "team_%d_name", m_Team + 1 );
+	pParams = new Script::CStruct;
+
+	if( IsLocalPlayer())
+	{
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		pParams->AddInteger( "team", m_Team );
+		Script::RunScript( "hide_ctf_arrow" );
+		Script::RunScript( "retrieved_flag_you", pParams );
+		
+	}
+	else
+	{
+		local_player = gamenet_man->GetLocalPlayer();
+		if( local_player && !local_player->IsObserving())
+		{
+			if( local_player->m_Team == m_Team )
+			{
+				Script::RunScript( "hide_ctf_arrow" );
+			}
+		}
+		pParams->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, m_Name );
+		pParams->AddComponent( Script::GenerateCRC("String1"), ESYMBOLTYPE_STRING, Script::GetString( team_str ));
+		pParams->AddInteger( "team", m_Team );
+		Script::RunScript( "retrieved_flag_other", pParams );
+	}
+
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		if( player->HasWhichFlag() == m_Team )
+		{
+			player->ClearCTFState();
+			break;
+
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::MarkAsKing( bool mark )
+{    
+	Manager * gamenet_man = Manager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    
+	if( mark )
+	{   
+		PlayerInfo* former_king;
+
+		// There can only be one king, so dethrone the last
+		former_king = gamenet_man->GetKingOfTheHill();
+		if( former_king )
+		{
+			former_king->MarkAsKing( false );
+		}
+
+		if( !m_flags.TestMask( mKING_OF_THE_HILL ))
+		{
+			m_flags.SetMask( mKING_OF_THE_HILL );
+
+			if( IsLocalPlayer())
+			{
+				Script::CStruct* pParams;
+
+				pParams = new Script::CStruct;
+				if( m_Skater->GetHeapIndex() == 0 )
+				{
+					pParams->AddChecksum( "player_1", Script::GenerateCRC( "player_1"));
+				}
+				else
+				{
+					pParams->AddChecksum( "player_2", Script::GenerateCRC( "player_2"));
+				}
+				// If we just picked up the crown, hide the arrow
+				Script::RunScript( "hide_crown_arrow", pParams );
+				delete pParams;
+				
+				/*gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC("net_message_new_king_you"),
+													NULL, NULL, m_Skater );*/
+				Script::RunScript( "NewKingYou" );
+				
+			}
+			else
+			{
+				Script::CStruct* pParams;
+
+				pParams = new Script::CStruct;
+				pParams->AddChecksum( "player_1", Script::GenerateCRC( "player_1"));
+				// If someone else just picked up the crown, show the arrow
+				Script::RunScript( "show_crown_arrow", pParams );
+				
+				pParams->AddString( "String0", m_Name );
+				Script::RunScript( "NewKingOther", pParams );
+				/*gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC( "net_message_new_king_other"),
+													m_Name, NULL );*/
+				delete pParams;
+
+				
+			}
+
+			if( !gamenet_man->InNetGame())
+			{
+				int other_id;
+				PlayerInfo* other_player;
+
+				// NOTE: This code only works for 2-player splitscreen. If we ever go to four,
+				// it needs to be more sophisticated
+				if( m_Skater->GetID() == 0 )
+				{
+					other_id = 1;
+				}
+				else
+				{ 
+					other_id = 0;
+				}
+	
+				other_player = gamenet_man->GetPlayerByObjectID( other_id );
+				if( other_player )
+				{
+					Script::CStruct* pParams;
+
+					pParams = new Script::CStruct;
+					pParams->AddString( "String0", (char*)m_Skater->GetDisplayName());
+					Script::RunScript( "NewKingOther", pParams );
+					/*gamenet_man->CreateNetPanelMessage( false, Script::GenerateCRC("net_message_new_king_other"),
+														(char*)m_Skater->GetDisplayName(), NULL, other_player->m_Skater );*/
+					delete pParams;
+				}
+			}
+			
+			
+			m_flags.SetMask( mKING_OF_THE_HILL );
+		}
+        
+        Obj::CCrown* crown;
+		crown = gamenet_man->GetCrown();
+		if( crown )
+		{
+			crown->PlaceOnKing( m_Skater );
+		}
+	}
+	else
+	{
+		if( m_flags.TestMask( mKING_OF_THE_HILL ))
+		{
+			if( IsLocalPlayer())
+			{
+				// If we lost the crown, show the arrow again, but only if we're still in koth mode
+				if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netking" ) ||
+					skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "king" ))
+				{
+					Script::CStruct* pParams;
+
+					pParams = new Script::CStruct;
+					pParams->AddChecksum( "player_1", Script::GenerateCRC( "player_1"));
+					pParams->AddChecksum( "player_2", Script::GenerateCRC( "player_2"));
+
+					Script::RunScript( "show_crown_arrow", pParams );
+					delete pParams;
+				}
+			}
+			m_flags.ClearMask( mKING_OF_THE_HILL );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsKing( void )
+{
+	return m_flags.TestMask( mKING_OF_THE_HILL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::MarkAsNotReady( int time )
+{
+	if( m_Conn && m_Conn->IsRemote())
+	{
+		if( time != 0 )
+		{
+			m_latest_ready_query = time;
+		}
+		m_Conn->ClearStatus( Net::Conn::mSTATUS_READY );
+		m_Conn->SetStatus( Net::Conn::mSTATUS_BUSY );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::SetReadyQueryTime( int time )
+{
+	m_latest_ready_query = time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::MarkAsReady( int time )
+{
+	if( m_latest_ready_query == time )
+	{
+		if( m_Conn )
+		{   
+			m_Conn->ClearStatus( Net::Conn::mSTATUS_BUSY );
+			m_Conn->SetStatus( Net::Conn::mSTATUS_READY );
+
+			// Now that they're ready, we need to keep track of how many times we're resending to
+			// them so that we can detect bad connections
+			m_Conn->ClearNumResends();
+		}
+		m_latest_ready_query = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		PlayerInfo::IsVulnerable( void )
+{
+	return (( Tmr::GetTime() - m_last_hit_time ) > vPROJECTILE_INVULNERABILITY_INTERVAL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		PlayerInfo::ResetProjectileVulnerability( void )
+{
+	m_last_hit_time = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		PlayerInfo::SetHitTime( Tmr::Time hit_time )
+{
+	m_last_hit_time = hit_time;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		PlayerInfo::GetConnHandle( void )
+{
+	if( m_Conn )
+	{
+		return m_Conn->GetHandle();
+	}
+	return Net::Conn::vHANDLE_INVALID;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		PlayerInfo::GetSkaterNumber( void )
+{
+	Dbg_Assert( m_Skater );
+
+	return m_Skater->GetSkaterNumber();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		PlayerInfo::GetLastObjectUpdateID( void )
+{
+	return m_last_object_update_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PlayerInfo::SetLastObjectUpdateID( int id )
+{
+	m_last_object_update_id = id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		PlayerInfo::GetMaxObjectUpdates( void )
+{
+	if( m_Conn->GetBandwidthType() == Net::Conn::vBROADBAND )
+	{
+		return 2;
+	}
+	
+	return 1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PlayerInfo::IsPendingPlayer( void )
+{
+	return m_flags.TestMask( mPENDING_PLAYER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NewPlayerInfo::NewPlayerInfo(void)
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	mpSkaterProfile = new Obj::CSkaterProfile;
+	JumpInFrame = 0;
+	Profile = 0;
+	Rating = 0;
+	Score = 0;
+	VehicleControlType = 0;
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+NewPlayerInfo::~NewPlayerInfo(void)
+{
+	
+
+	Dbg_Assert( mpSkaterProfile );
+
+	delete mpSkaterProfile;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/ServerList.cpp b/Code/Sk/GameNet/ServerList.cpp
new file mode 100644
index 0000000..14c5ee9
--- /dev/null
+++ b/Code/Sk/GameNet/ServerList.cpp
@@ -0,0 +1,2109 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate3													**
+**																			**
+**	Module:			GameNet					 								**
+**																			**
+**	File name:		ServerList.cpp											**
+**																			**
+**	Created by:		06/131/01	-	spg										**
+**																			**
+**	Description:	Server List functionality								**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+//#include 
+
+#ifdef __PLAT_NGPS__
+#include 
+//#include 
+#include 
+#include 
+#include 
+#include 
+#endif
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+#ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+// Gamespy frame counter
+//extern unsigned long goa_frame_ct;
+
+//extern "C" 
+//{
+//////////////////////////////////
+// Global IP addresses for gamespy
+//extern char	qr_hostname[64];
+//extern char ServerListHostname[64];
+
+//}
+
+extern void GOASetUniqueID( const char* id );
+
+#endif
+#endif
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vMOTD_WAIT_THRESHOLD		Tmr::Seconds( 7 )
+#define vTIME_BETWEEN_AUTO_REFRESH	Tmr::Seconds( 3 )
+#define vTIME_BETWEEN_MANUAL_REFRESH	Tmr::Seconds( 5 )
+
+#define	vLAN_SERVER_LIST_TIMEOUT	500
+#define	vHTTP_TIMEOUT				Tmr::Seconds( 15 )
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+#ifdef __PLAT_NGPS__
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static int			s_motd_retry = 0;
+#endif // __PLAT_NGPS__
+
+static Tmr::Time 	s_last_refresh_time = 0;
+static bool 		s_refresh_pending = false;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+bool	gEnteringFromMainMenu;
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+#ifdef __PLAT_NGPS__
+//static bool s_no_servers_dialog_box_handler( Front::EDialogBoxResult result );
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*SortKeyInfo* GetSortKey( int key_type )
+{
+	int i;
+
+	for( i = 0; i < vNUM_SORT_KEYS; i++ )
+	{
+		if( s_sort_keys[i].KeyType == key_type )
+		{
+			return &s_sort_keys[i];
+		}
+	}
+
+	return NULL;
+}*/
+#endif // __PLAT_NGPS__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if 0
+static bool not_posted_dialog_box_handler( Front::EDialogBoxResult result )
+{
+    Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+
+	front->SetActive( false );
+
+	return false;
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_server_list_state_code( const Tsk::Task< Manager >& task )
+{
+#ifdef __PLAT_NGPS__
+	Manager& man = task.GetData();
+
+	switch( man.GetServerListState())
+	{   
+		case vSERVER_LIST_STATE_WAIT:
+			// A wait time of zero indicates we should wait for an outside force to change the state
+			if( man.m_server_list_wait_time == 0 )
+			{
+				break;
+			}
+			if( Tmr::GetTime() > man.m_server_list_wait_time )
+			{
+				man.SetServerListState( man.GetNextServerListState());
+			}
+			break;
+
+		case vSERVER_LIST_STATE_RETRY_MOTD:
+		{
+			//Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+			//Front::DialogBox* dlg;
+
+			//dlg = front->GetDialogBox();
+			//dlg->GoBack();
+
+			// fall-through intentional
+		}
+		case vSERVER_LIST_STATE_STARTING_MOTD:
+		{
+			Thread::PerThreadStruct	net_thread_data;
+			
+			//Dbg_Printf( "Serverlist State: Starting MotD\n" );
+
+			// Pause the music & streams before doing DNS lookup
+			Pcm::PauseStream( 1 );
+			
+            
+			// First, show the dialog box
+			if( s_motd_retry == 0 )
+			{
+				Script::SpawnScript( "launch_motd_wait_dialog" );
+			}
+			else
+			{
+				char msg[256];
+				Script::CScriptStructure* pStructure;
+			
+				pStructure = new Script::CScriptStructure;
+				sprintf( msg, "(%d) %s", 	s_motd_retry,
+											Script::GetLocalString( "net_status_retry_motd" ));
+				pStructure->AddComponent( Script::GenerateCRC( "message" ), ESYMBOLTYPE_STRING, msg );
+				Script::SpawnScript( "CreateMotdRetryDialog", pStructure );
+				delete pStructure;
+			}
+
+			man.SetServerListState( vSERVER_LIST_STATE_WAIT );
+			man.m_server_list_wait_time = 0;
+
+			// Now spawn the thread that gets the message of the day
+			net_thread_data.m_pEntry = s_threaded_get_message_of_the_day;
+			net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+			net_thread_data.m_pStackBase = man.GetNetThreadStack();
+			net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+			net_thread_data.m_utid = 0x151;
+			Thread::CreateThread( &net_thread_data );
+			man.SetNetThreadId( net_thread_data.m_osId );
+			StartThread( man.GetNetThreadId(), &man );
+			
+			break;
+		}
+		case vSERVER_LIST_STATE_GETTING_MOTD:
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+			if(( Tmr::GetTime() - man.m_ghttp_start_time ) > vHTTP_TIMEOUT )
+			{
+				ghttpCancelRequest( man.m_ghttp_request );
+				man.SetServerListState( vSERVER_LIST_STATE_FAILED_MOTD );
+			}
+			else
+			{
+				ghttpThink(); 
+			}
+			
+			Mem::Manager::sHandle().PopContext();
+			break;
+		}
+		case vSERVER_LIST_STATE_TRACK_USAGE:
+		{
+			Thread::PerThreadStruct	net_thread_data;
+			
+			ghttpCleanup();
+
+			//Dbg_Printf( "Serverlist State: Starting track usage\n" );
+
+			// Pause the music & streams before doing DNS lookup
+			Pcm::PauseStream( 1 );
+            
+			// Now spawn the thread that gets the message of the day
+			net_thread_data.m_pEntry = s_threaded_track_usage;
+			net_thread_data.m_iInitialPriority = vSOCKET_THREAD_PRIORITY;
+			net_thread_data.m_pStackBase = man.GetNetThreadStack();
+			net_thread_data.m_iStackSize = vNET_THREAD_STACK_SIZE;
+			net_thread_data.m_utid = 0x151;
+			Thread::CreateThread( &net_thread_data );
+			man.SetNetThreadId( net_thread_data.m_osId );
+			StartThread( man.GetNetThreadId(), &man );
+			break;
+		}
+		case vSERVER_LIST_STATE_TRACKING_USAGE:
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+			if(( Tmr::GetTime() - man.m_ghttp_start_time ) > vHTTP_TIMEOUT )
+			{
+				ghttpCancelRequest( man.m_ghttp_request );
+				man.SetServerListState( vSERVER_LIST_STATE_SHOW_MOTD );
+			}
+			else
+			{
+				ghttpThink(); 
+			}
+			Mem::Manager::sHandle().PopContext();
+			break;
+		}
+		case vSERVER_LIST_STATE_SHOW_MOTD:
+		{
+			Script::CScriptStructure* pStructure;
+			
+			//Dbg_Printf( "Serverlist State: Got MotD\n" );
+
+			ghttpCleanup();
+
+			man.RemoveMessageOfTheDay();
+
+			pStructure = new Script::CScriptStructure;
+			pStructure->AddComponent( Script::GenerateCRC( "message" ), ESYMBOLTYPE_STRING, man.m_motd );
+			
+			Script::RunScript( "create_motd_menu", pStructure );
+			
+			delete pStructure;
+			
+			man.SetServerListState( vSERVER_LIST_STATE_WAIT );
+			man.m_server_list_wait_time = 0;
+			break;
+		}
+		case vSERVER_LIST_STATE_FAILED_MOTD:
+		{
+			Dbg_Printf( "In vSERVER_LIST_STATE_FAILED_MOTD\n" );
+			ghttpCleanup();
+			Script::RunScript( "CreateMotdFailedDialog" );
+
+			// NOTE: USED TO CALL THIS FROM THE "OK" DIALOG BOX HANDLER
+			// gamenet_man->SetServerListState( vSERVER_LIST_STATE_STARTING_LOBBY_LIST )
+						
+			//man.SetServerListState( vSERVER_LIST_STATE_WAIT );
+			man.SetServerListState( vSERVER_LIST_STATE_SHUTDOWN );
+			man.m_server_list_wait_time = 0;
+			break;
+		}
+		case vSERVER_LIST_STATE_STARTING_LOBBY_LIST:
+		{
+			// First, show the dialog box
+			Script::RunScript( "CreateGettingLobbyListDialog" );
+
+			// Free up the previous lobby list
+			man.mpLobbyMan->StopLobbyList();
+			
+			// Now spawn the lobby list -- this function also changes the state
+			man.mpLobbyMan->StartLobbyList();
+			break;
+		}
+		case vSERVER_LIST_STATE_GETTING_LOBBY_LIST:
+		{
+
+			//Dbg_Printf( "Serverlist State: Getting Lobby List\n" );
+            
+            man.SetServerListState( vSERVER_LIST_STATE_WAIT );
+			man.m_server_list_wait_time = 0;
+			break;
+		}
+		case vSERVER_LIST_STATE_FAILED_LOBBY_LIST:
+		{
+			Script::CScriptStructure* pStructure;
+			
+			//Dbg_Printf( "Failed lobby list\n" );
+			man.mpLobbyMan->StopLobbyList();
+		
+			pStructure = new Script::CScriptStructure;
+			pStructure->AddComponent( Script::GenerateCRC( "message" ), ESYMBOLTYPE_STRING, Script::GetLocalString( "net_status_gamespy_no_connect" ));
+
+			Script::RunScript( "CreateFailedLobbyListDialog", pStructure );
+
+			delete pStructure;
+					
+			man.SetServerListState( vSERVER_LIST_STATE_SHUTDOWN );
+			break;
+		}
+		case vSERVER_LIST_STATE_GOT_LOBBY_LIST:
+		{
+			// If we didn't get any lobbies, that either means the matchmaker's down or their
+			// connection isn't valid
+			if( man.mpLobbyMan->GetLobbyInfo( 0 ) == NULL )
+			{
+				Script::CScriptStructure* pStructure = new Script::CScriptStructure;
+				
+				pStructure->AddComponent( Script::GenerateCRC( "message" ), ESYMBOLTYPE_STRING, Script::GetLocalString( "net_status_gamespy_no_connect" ));
+
+				Script::RunScript( "CreateFailedLobbyListDialog", pStructure );
+
+				delete pStructure;
+				
+				man.SetServerListState( vSERVER_LIST_STATE_SHUTDOWN );
+			}
+			else
+			{
+				man.SetServerListState( vSERVER_LIST_STATE_FILL_LOBBY_LIST );
+			}
+			
+			Pcm::PauseStream( 0 );
+			break;
+		}
+		case vSERVER_LIST_STATE_FILL_LOBBY_LIST:
+		{
+			man.mpLobbyMan->FillLobbyList();
+			man.SetServerListState( vSERVER_LIST_STATE_SHUTDOWN );
+			break;
+		}
+		
+		default:
+			break;
+	}
+#endif
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::s_auto_refresh_code( const Tsk::Task< Manager >& task )
+{
+	Manager& man = task.GetData();
+	static Tmr::Time s_refresh_time = 0;
+
+	if(( Tmr::GetTime() - s_refresh_time ) > vTIME_BETWEEN_AUTO_REFRESH )
+	{
+		s_refresh_time = Tmr::GetTime();
+#ifdef __PLAT_XBOX__
+		man.RefreshServerList( true );
+#endif
+	}
+	else
+	{
+		if( s_refresh_pending )
+		{
+			if(( Tmr::GetTime() - s_last_refresh_time ) > vLAN_SERVER_LIST_TIMEOUT )
+			{
+				Lst::Search< ServerInfo > sh;
+				ServerInfo* server, *next;
+
+				man.ClearServerList();
+						
+				for( server = sh.FirstItem( man.m_temp_servers ); server; server = next )
+				{
+					next = sh.NextItem();
+					server->Remove();
+
+					man.AddServerToMenu( server, man.m_servers.CountItems());
+					man.m_servers.AddToTail( server );					
+				}
+				Script::RunScript( "update_lobby_server_list" );
+				
+				s_refresh_pending = false;
+			}
+		}
+	}
+}
+
+/*****************************************************************************
+**							  Handler Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/* Respond to a client who is looking for games to join on the LAN*/
+/* Tell them info about the game                                  */
+/******************************************************************/
+
+int Manager::s_handle_server_response( Net::MsgHandlerContext* context )
+{
+	MsgServerDescription* msg;
+	ServerInfo *server_info;
+	Manager* manager = (Manager*) context->m_Data;
+	int i;
+
+	//Dbg_Printf( "GOT SERVER RESPONSE!\n" );
+
+	msg = (MsgServerDescription*) context->m_Msg;
+
+	if( context->m_MsgLength != sizeof( MsgServerDescription ))
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	
+#ifdef	__PLAT_XBOX__
+    if( manager->ServerAlreadyInList( msg->m_Name, &msg->m_XboxAddr ))
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+#else
+	if( manager->ServerAlreadyInList( msg->m_Name, context->m_Conn->GetIP()))
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+#endif
+
+	
+#ifdef	__PLAT_XBOX__
+	if( memcmp( msg->m_Nonce, context->m_App->m_Nonce, 8 ))
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+#endif
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	server_info = new ServerInfo;
+	
+	strcpy( server_info->m_Name, msg->m_Name );
+	server_info->m_Ip = context->m_Conn->GetIP();
+	server_info->m_Latency = Tmr::GetTime() - msg->m_Timestamp;
+	strcpy( server_info->m_Level, msg->m_Level );
+	strcpy( server_info->m_Mode, msg->m_Mode );
+	server_info->m_MaxPlayers = msg->m_MaxPlayers;
+	server_info->m_NumPlayers = msg->m_NumPlayers;
+	server_info->m_MaxObservers = msg->m_MaxObservers;
+	server_info->m_NumObservers = msg->m_NumObservers;
+	server_info->m_Password = msg->m_Password;
+	server_info->m_GameStarted = msg->m_GameStarted;
+	server_info->m_HostMode = msg->m_HostMode;
+	server_info->m_Ranked = msg->m_Ranked;
+	server_info->m_SkillLevel = msg->m_SkillLevel;
+	server_info->m_Port = vHOST_PORT;
+#ifdef __PLAT_XBOX__
+	memcpy( &server_info->m_XboxKeyId, &msg->m_XboxKeyId, sizeof( XNKID ));
+	memcpy( &server_info->m_XboxKey, &msg->m_XboxKey, sizeof( XNKEY ));	
+	memcpy( &server_info->m_XboxAddr, &msg->m_XboxAddr, sizeof( XNADDR ));	
+#endif    
+    
+	for( i = 0; i < server_info->m_NumPlayers; i++ )
+	{
+		server_info->AddPlayer( msg->m_PlayerNames[i] );
+	}
+
+	//manager->AddServerToMenu( server_info, manager->m_servers.CountItems());
+
+	//manager->m_servers.AddToTail( server_info );
+	manager->m_temp_servers.AddToTail( server_info );
+    
+	Mem::Manager::sHandle().PopContext();
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGPS__
+
+void	Manager::s_create_game_callback( PEER peer, PEERBool success, PEERJoinResult result, RoomType roomType, 
+											void * param )
+{
+	if( success )
+	{
+		//Dbg_Printf( "Joined Created Room\n" );
+	}
+	else
+	{
+		switch( result )
+		{
+			case PEERFullRoom:
+				//Dbg_Printf( "Couldnt join created room. Full\n" );
+				break;
+			case PEERInviteOnlyRoom:
+				//Dbg_Printf( "Couldnt join created room. Invite Only\n" );
+				break;
+			case PEERBannedFromRoom:
+				//Dbg_Printf( "Couldnt join created room. Banned\n" );
+				break;
+			case PEERBadPassword:
+				//Dbg_Printf( "Couldnt join created room. Bad Password\n" );
+				break;
+			case PEERAlreadyInRoom:
+				//Dbg_Printf( "Couldnt join created room. Already in room\n" );
+				break;
+			case PEERNoTitleSet:
+				//Dbg_Printf( "Couldnt join created room. No Title Set\n" );
+				break;
+			default:
+				//Dbg_Printf( "Couldnt join created room. General error\n" );
+				break;
+		}
+	}
+}
+
+#endif // __PLAT_NGPS__
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::PostGame( void )
+{
+#ifdef __PLAT_NGPS__
+	Net::Manager * net_man = Net::Manager::Instance();
+	
+    Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	
+	mpLobbyMan->StartReportingGame();
+	if( mpBuddyMan->IsLoggedIn())
+	{
+		char location[1024];
+		const char *server_name;
+		Script::CScriptStructure* pStructure;
+		Prefs::Preferences* pPreferences;
+
+		pPreferences = GetNetworkPreferences();
+		pStructure = pPreferences->GetPreference( Script::GenerateCRC("server_name") );
+		pStructure->GetText( "ui_string", &server_name, true );
+
+		if( mpLobbyMan->GetPeer())
+		{
+			sprintf( location, "%d:%d:%d:%s (%s)", net_man->GetPublicIP(), peerGetPrivateIP( mpLobbyMan->GetPeer()), vHOST_PORT, server_name, mpLobbyMan->GetLobbyName());
+		}
+		mpBuddyMan->SetStatusAndLocation( GP_PLAYING, (char*) Script::GetString( "homie_status_hosting" ), location );
+	}
+	Mem::Manager::sHandle().PopContext();
+#endif
+}
+
+
+/*void Manager::RequestMatchmakerConnect( void )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+
+#ifdef __PLAT_NGPS__
+	int result;
+
+	m_server->SetForeignPacketHandler( gamespy_data_handler );
+	s_gamespy_parse_data[0] = '\0';
+
+	// Hardcode the IP of the master server until we get the message of the day squared away
+	result = qr_init_socket( NULL, m_server->GetSocket(), "thps4ps2", "H2r8W1", basic_callback,
+							 info_callback, rules_callback, players_callback, this );
+	// Uncomment for media burns
+	//result = qr_init_socket( NULL, m_server->GetSocket(), "thps3media", "tRKg39", basic_callback,
+							 //info_callback, rules_callback, players_callback, this );
+	 
+	switch( result )
+	{
+		case 0:
+			// success:
+			break;
+		case E_GOA_WSOCKERROR:
+			Dbg_MsgAssert( 0, ( "Failed to initialize GameSpy query report toolkit due to Socket Error\n" ));
+			break;
+		case E_GOA_BINDERROR:
+			Dbg_MsgAssert( 0, ( "Failed to initialize GameSpy query report toolkit due to Bind Error\n" ));
+			break;
+		case E_GOA_CONNERROR:
+			Dbg_MsgAssert( 0, ( "Failed to initialize GameSpy query report toolkit due to Connection Error\n" ));
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Failed to initialize Gamespy query report toolkit\n" ));
+			break;
+	}
+
+#endif
+	
+#ifdef __PLAT_NGPS__
+	s_game_start_time = Tmr::GetTime();
+	s_notified_user_not_connected = false;
+	s_retried_game_post = false;
+	gGotGamespyCallback = false;
+#endif		// __PLAT_NGC__
+	mlp_manager->AddLogicTask( *m_process_gamespy_queries_task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetServerFocus( ServerInfo* server )
+{
+	ClearServerFocus();
+	server->m_InFocus = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ClearServerFocus( void )
+{
+	Lst::Search< ServerInfo > sh;
+	ServerInfo* server;
+
+	for( server = sh.FirstItem( m_servers ); server; server = sh.NextItem())
+	{
+		server->m_InFocus = false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ServerInfo*		Manager::GetServerFocus( void )
+{
+	Lst::Search< ServerInfo > sh;
+	ServerInfo* server;
+
+	for( server = sh.FirstItem( m_servers ); server; server = sh.NextItem())
+	{
+		if( server->m_InFocus )
+		{
+			return server;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Manager::NumServersListed( void )
+{
+	Lst::Search< ServerInfo > sh;
+	ServerInfo* server;
+	int num_listed;
+
+	num_listed = 0;
+	for( server = sh.FirstItem( m_servers ); server; server = sh.NextItem())
+	{
+		if( server->m_Listed )
+		{
+			num_listed++;
+		}
+	}
+
+	return num_listed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::AddServerToMenu( ServerInfo* server, int index )
+{   
+	Script::CStruct* p_item_params;
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+	uint32 server_id;
+		
+	// If this is our first listed server, destroy the "no servers found" message
+	if( NumServersListed() == 0 )
+	{
+		Script::RunScript( "destroy_server_menu_children" );
+	}
+
+	Script::RunScript( "prepare_server_menu_for_new_children" );
+		
+	p_item_params = new Script::CStruct;	
+	p_item_params->AddString( "text", server->m_Name );
+	p_item_params->AddString( "mode", server->m_Mode );
+	printf("The game mode is: %s\n", server->m_Mode );
+#ifdef __PLAT_NGPS__
+	if( InInternetMode())
+	{
+		server_id = (uint32) server->m_GServer;
+		
+	}
+	else
+#endif
+	{
+		server_id = (uint32) server;
+	}
+	
+	p_item_params->AddChecksum( "id", server_id );
+	p_item_params->AddChecksum( "parent", Script::GenerateCRC( "server_list_menu" ));
+	p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("choose_selected_server"));
+	p_item_params->AddChecksum( "focus_script",Script::GenerateCRC("describe_selected_server"));
+
+	// create the parameters that are passed to the X script
+	Script::CStruct *p_script_params= new Script::CStruct;
+	p_script_params->AddChecksum( "id", server_id );	
+	p_item_params->AddStructure("pad_choose_params",p_script_params);			
+	p_item_params->AddStructure("focus_params",p_script_params);
+
+	Script::RunScript("server_list_menu_add_item",p_item_params);
+	
+	server->m_Listed = true;
+
+	delete p_item_params;
+	delete p_script_params;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGPS__
+
+GHTTPBool	Manager::MotdComplete( GHTTPRequest request, GHTTPResult result, char* buffer, int buffer_len, 
+						  void* param )
+{
+	Manager * gamenet_man = Manager::Instance();
+	bool succeeded;
+
+	
+    
+	succeeded = false;
+	if( result == GHTTPSuccess )
+	{   
+		char* token;
+		char* message, *ip_ptr;
+		char temp_motd[4096];
+		int server_ip;
+
+		strncpy( temp_motd, buffer, 4095 );
+		temp_motd[4095] = '\0';
+		//Dbg_Printf( "Got Message of the day: %s\n", temp_motd );
+		
+		// Checksum comes first and is delimited by a space
+		token = strtok( temp_motd, " " );
+		if( token )
+		{
+		 	uint32 checksum;
+
+			checksum = atoi( token );
+			//Dbg_Printf( "checksum is %d\n", checksum );
+			ip_ptr = token + strlen( token ) + 1;	// skip the checksum
+			if( checksum == Script::GenerateCRC( ip_ptr ))
+			{
+				//Dbg_Printf( "Matched!\n" );
+				// Next token is the IP address for the gamespy servers
+				ip_ptr = strtok( NULL, " " );
+				if( ip_ptr )
+				{
+					//Dbg_Printf( "Got an IP Pointer of %s!\n", ip_ptr );
+					message = strtok( NULL, "\n" );
+					if( message )
+					{
+						//Dbg_Printf( "Got a message of %s\n", message );
+						strncpy( gamenet_man->m_motd, message, 1023 );
+						gamenet_man->m_motd[1023] = '\0';
+
+						server_ip = atoi( ip_ptr );
+						
+                        //sprintf( ServerListHostname, "%s", inet_ntoa(*(struct in_addr*) &server_ip ));
+						//sprintf( qr_hostname, "%s", inet_ntoa(*(struct in_addr*) &server_ip ));
+
+						//Dbg_Printf( "GameSpy Server is %s\n", ServerListHostname );
+						succeeded = true;
+					}
+				}
+			}
+		}
+
+		if( succeeded )
+		{   
+			gamenet_man->m_got_motd = true;
+			gamenet_man->SetServerListState( vSERVER_LIST_STATE_TRACK_USAGE );
+		}
+	}
+
+	if( !succeeded )
+	{
+		Dbg_Printf( "Error Downloading motd: %d\n", result );
+
+		// No retries in THPS4. We're just going to use our neversoft website and that's it
+		/*s_motd_retry = s_motd_retry + 1;
+		if( gamenet_man->m_master_servers[ s_motd_retry ][0] != '\0' )
+		{
+			gamenet_man->SetServerListState( vSERVER_LIST_STATE_RETRY_MOTD );
+			return GHTTPTrue;
+		}*/
+
+		gamenet_man->SetServerListState( vSERVER_LIST_STATE_FAILED_MOTD );
+	}
+
+	return GHTTPTrue;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void MotdProgress( GHTTPRequest request, GHTTPState state, const char * buffer, int bufferLen,
+					int bytesReceived, int totalSize, void * param )
+{
+	
+
+	switch( state )
+	{
+		case GHTTPHostLookup:
+			Dbg_Printf( "Lookin up HTTP host...\n" );
+			break;
+		case GHTTPConnecting:
+			Dbg_Printf( "Connecting to HTTP host...\n" );
+			break;
+		case GHTTPSendingRequest:
+			Dbg_Printf( "Sending Request to HTTP host...\n" );
+			break;
+		case GHTTPPosting:
+			break;
+		case GHTTPWaiting:
+			Dbg_Printf( "Waiting for response from HTTP host...\n" );
+			break;
+		case GHTTPReceivingStatus:
+			Dbg_Printf( "Receiving status....\n" );
+			break;
+		case GHTTPReceivingHeaders:
+			Dbg_Printf( "Receiving headers....\n" );
+			break;
+		case GHTTPReceivingFile:
+			Dbg_Printf( "Receiving file: %d bytes....\n", bytesReceived );
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+GHTTPBool	Manager::TrackUsageComplete( GHTTPRequest request, GHTTPResult result, char* buffer, int buffer_len, 
+						  void* param )
+{
+	Manager * gamenet_man = Manager::Instance();
+
+	//Dbg_Printf( "Track usage complete!!!\n" );
+
+	gamenet_man->SetServerListState( vSERVER_LIST_STATE_SHOW_MOTD );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::s_threaded_get_message_of_the_day( Manager* gamenet_man )
+{
+	char motd_path[256];
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+
+	// Wait a bit before starting the http transfer 
+	msleep( 100 );
+
+	gamenet_man->SetServerListState( vSERVER_LIST_STATE_GETTING_MOTD );
+	
+	gamenet_man->m_ghttp_start_time = Tmr::GetTime();
+	sprintf( motd_path, "http://www.thugonline.com/motd.txt" );
+	//sprintf( motd_path, "http://www.neversoft.com/thps5/motd.txt" );
+	gamenet_man->m_ghttp_request = ghttpGetEx( 	motd_path,
+												NULL,
+												gamenet_man->m_motd,
+												1024,
+												NULL,
+												GHTTPFalse,
+												GHTTPFalse,
+												MotdProgress,
+												MotdComplete,
+												gamenet_man );
+
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+		
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::s_threaded_track_usage( Manager* gamenet_man )
+{
+	Prefs::Preferences*	prefs;
+	Script::CStruct* pStructure;
+	const char* unique_id = "123456789";
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+
+	// Register this thread with the sockets API
+	sockAPIregthr();
+        
+	prefs = gamenet_man->GetNetworkPreferences();
+	pStructure = prefs->GetPreference( Script::GenerateCRC("unique_id"));
+	pStructure->GetString( "ui_string", &unique_id );
+	//GOASetUniqueID( unique_id );
+
+	gamenet_man->m_ghttp_start_time = Tmr::GetTime();
+	gamenet_man->m_ghttp_request = ptTrackUsage( 	0,	// int userID ( if not using Presence & Messaging, send zero )
+													vGAMESPY_PRODUCT_ID,	// int productID,
+													"1.0",	//const char * versionUniqueID,
+													0,	//int distributionID,
+													GHTTPFalse,
+													TrackUsageComplete );	//PTBool blocking
+
+	gamenet_man->SetServerListState( vSERVER_LIST_STATE_TRACKING_USAGE );
+
+	// Deregister this thread with the sockets API
+	sockAPIderegthr();
+		
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::RemoveMessageOfTheDay( void )
+{
+	//ghttpCleanup();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::s_server_list_callback( PEER peer, PEERBool success, const char * name, SBServer server, PEERBool staging, 
+									  int msg, int progress, void * param )
+{
+	Manager* man = (Manager*) param;
+	if( success )
+	{
+		switch( msg )
+		{
+		
+			case PEER_ADD:
+			{
+				ServerInfo* server_info;
+				int i;
+				
+				Dbg_Printf( "****** ADDING SERVER %p : %s\n", server, name );
+				Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+	
+				server_info = new ServerInfo;
+				server_info->m_GServer = server;
+				strncpy( server_info->m_Name, name, vMAX_SERVER_NAME_LEN );
+				server_info->m_Name[vMAX_SERVER_NAME_LEN] = '\0';
+				server_info->m_Latency = SBServerGetPing( server );
+
+				// Check if we're behind the same NAT
+                if( SBServerHasPrivateAddress( server ))
+				{
+					// If so, we'll have the same public IP
+                    if( SBServerGetPublicInetAddress( server ) == peerGetLocalIP( peer ))
+					{
+						server_info->m_Ip = SBServerGetPrivateInetAddress( server );						
+						server_info->m_Port = vHOST_PORT;
+					}
+					else
+					{
+						// If we're not behind the same NAT, check if they are behind
+						// a promiscuous NAT or, perhaps, not behind a NAT at all.  If so
+						// we can directly connect to their public IP
+						if( SBServerDirectConnect( server ))
+						{
+							server_info->m_Ip = SBServerGetPublicInetAddress( server );
+							server_info->m_Port = SBServerGetPublicQueryPort( server );
+						}
+						else
+						{
+							server_info->m_Ip = SBServerGetPublicInetAddress( server );
+							server_info->m_Port = SBServerGetPublicQueryPort( server );
+							server_info->m_CanDirectConnect = false;
+							server_info->m_Latency = 9999;	// We can't get the ping of servers behind non-promiscuous NATs
+						}
+					}
+				}
+				else
+				{
+					if( SBServerDirectConnect( server ))
+					{
+						server_info->m_Ip = SBServerGetPublicInetAddress( server );
+						server_info->m_Port = SBServerGetPublicQueryPort( server );
+					}
+					else
+					{
+						server_info->m_Ip = SBServerGetPublicInetAddress( server );
+						server_info->m_Port = SBServerGetPublicQueryPort( server );
+						server_info->m_CanDirectConnect = false;
+						server_info->m_Latency = 9999;	// We can't get the ping of servers behind non-promiscuous NATs
+					}
+				}
+				
+				Dbg_Printf( "Percent Done: %d : Server %s ping (%d)\n", (int) progress, server_info->m_Name, server_info->m_Latency );
+				strcpy( server_info->m_Level, SBServerGetStringValue( server, "mapname","..."));
+				strcpy( server_info->m_Mode, SBServerGetStringValue( server, "gametype","..."));
+				server_info->m_NumPlayers = SBServerGetIntValue( server, "numplayers", 1 );
+				server_info->m_MaxPlayers = SBServerGetIntValue( server, "maxplayers", 8 );
+				server_info->m_NumObservers = SBServerGetIntValue( server, "numobservers", 0 );
+				server_info->m_MaxObservers = SBServerGetIntValue( server, "maxobservers", 1 );
+				server_info->m_Password = SBServerGetBoolValue( server, "password", false );
+				server_info->m_GameStarted = SBServerGetBoolValue( server, "started", false );
+				server_info->m_HostMode = SBServerGetIntValue( server, "hostmode", vHOST_MODE_SERVE );
+				server_info->m_Ranked = SBServerGetIntValue( server, "ranked", 0 );
+				server_info->m_SkillLevel = SBServerGetIntValue( server, "skilllevel", vSKILL_LEVEL_DEFAULT );
+
+				Dbg_Printf( "Numplayers %d\n", server_info->m_NumPlayers )
+				for( i = 0; i < server_info->m_NumPlayers; i++ )
+				{
+					int rating;
+
+					rating = SBServerGetPlayerIntValue( server, i, "rating", 0 );
+					Dbg_Printf( "Adding player %s with rating %d\n", (char*) SBServerGetPlayerStringValue( server, i, "player","..." ), rating );
+					server_info->AddPlayer((char*) SBServerGetPlayerStringValue( server, i, "player","..." ), rating );
+				}
+				
+				man->m_servers.AddToTail( server_info );
+				if( SBServerHasBasicKeys( server ))
+				{
+					man->AddServerToMenu( server_info, man->m_servers.CountItems() - 1 );
+					Script::RunScript( "update_lobby_server_list" );
+					server_info->m_HasBasicInfo = true;
+				}
+
+				Mem::Manager::sHandle().PopContext();
+				break;
+			}
+	
+			//   Remove this server from the list.  The server object for this server
+			//   should not be used again after this callback returns.
+			case PEER_REMOVE:
+			{
+				Lst::Search< ServerInfo > sh;
+				ServerInfo* server_info, *next;
+				Script::CStruct* pParams;
+				
+				Dbg_Printf( "****** REMOVING SERVER %p\n", server );
+
+				pParams = new Script::CStruct;
+				pParams->AddChecksum( "server_id", (uint32) server );
+				Script::RunScript( "destroy_lobby_server", pParams );
+				delete pParams;
+	    
+				for( server_info = sh.FirstItem( man->m_servers ); server_info; server_info = next )
+				{
+					next = sh.NextItem();
+					if( server_info->m_GServer == server )
+					{
+						delete server_info;
+						break;
+					}
+				}
+
+				Script::RunScript( "update_lobby_server_list" );
+                break;
+			}
+
+			//   Clear the list.  This has the same effect as if this was called
+			//   with PEER_REMOVE for every server listed.
+			case PEER_CLEAR:
+			{
+				Dbg_Printf( "****** CLEAR SERVER LIST\n" );
+
+				man->ClearServerList( false );
+				man->FreeServerList();
+
+				Script::RunScript( "update_lobby_server_list" );
+				break;
+			}
+			//   This server is already on the list, and its been updated.
+			case PEER_UPDATE:
+			{
+				Lst::Search< ServerInfo > sh;
+				ServerInfo* server_info;
+				int i;
+				
+				if( SBServerHasFullKeys( server ))
+				{
+					Dbg_Printf( "****** FULLY UPDATING SERVER %p\n", server );
+				}
+				else
+				{
+					Dbg_Printf( "****** PARTIALLY UPDATING SERVER %p\n", server );
+				}
+				
+	    
+				for( server_info = sh.FirstItem( man->m_servers ); server_info; server_info = sh.NextItem())
+				{
+					if( server_info->m_GServer == server )
+					{
+						strncpy( server_info->m_Name, name, vMAX_SERVER_NAME_LEN );
+						server_info->m_Name[vMAX_SERVER_NAME_LEN] = '\0';
+						//server_info->m_Ip = SBServerGetPublicInetAddress( server );
+						if( server_info->m_CanDirectConnect )
+						{
+							server_info->m_Latency = SBServerGetPing( server );
+						}
+						Dbg_Printf( "Percent Done: %d : Server %s ping (%d)\n", (int) progress, server_info->m_Name, SBServerGetPing(server));
+						strcpy( server_info->m_Level, SBServerGetStringValue( server, "mapname","..."));
+						strcpy( server_info->m_Mode, SBServerGetStringValue( server, "gametype","..."));
+						server_info->m_NumPlayers = SBServerGetIntValue( server, "numplayers", 1 );
+						server_info->m_MaxPlayers = SBServerGetIntValue( server, "maxplayers", 8 );
+						server_info->m_NumObservers = SBServerGetIntValue( server, "numobservers", 0 );
+						server_info->m_MaxObservers = SBServerGetIntValue( server, "maxobservers", 1 );
+						server_info->m_Password = SBServerGetBoolValue( server, "password", false );
+						server_info->m_GameStarted = SBServerGetBoolValue( server, "started", false );
+						server_info->m_HostMode = SBServerGetIntValue( server, "hostmode", vHOST_MODE_SERVE );
+						server_info->m_Ranked = SBServerGetIntValue( server, "ranked", 0 );
+						server_info->m_SkillLevel = SBServerGetIntValue( server, "skilllevel", vSKILL_LEVEL_DEFAULT );
+						//server_info->m_Port = SBServerGetPublicQueryPort( server );
+						server_info->ClearPlayerNames();
+						Dbg_Printf( "Game Mode: %s\n", server_info->m_Mode );
+						Dbg_Printf( "Numplayers %d\n", server_info->m_NumPlayers )
+						for( i = 0; i < server_info->m_NumPlayers; i++ )
+						{
+							int rating;
+
+							rating = SBServerGetPlayerIntValue( server, i, "rating", 0 );
+							Dbg_Printf( "Adding player %s with rating %d\n", (char*) SBServerGetPlayerStringValue( server, i, "player","..." ), rating );
+							server_info->AddPlayer((char*) SBServerGetPlayerStringValue( server, i, "player","..." ), rating );
+						}
+						
+						if( server_info->m_HasBasicInfo == false )
+						{
+							if( SBServerHasBasicKeys( server ))
+							{
+								man->AddServerToMenu( server_info, 0 );
+								Script::RunScript( "update_lobby_server_list" );
+								server_info->m_HasBasicInfo = true;
+							}
+						}
+
+						if( server_info->m_HasFullInfo == false )
+						{
+							if( SBServerHasFullKeys( server ))
+							{
+								server_info->m_HasFullInfo = true;
+								if( server_info->m_InFocus )
+								{
+									Script::CStruct* params;
+
+									params = new Script::CStruct;
+									params->AddChecksum( "id", (uint32) server );
+									Script::RunScript( "describe_selected_server", params );
+									delete params;
+								}
+							}
+						}
+						break;
+					}
+				}
+                break;
+			}
+		}
+	}
+}
+
+#endif 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::ScriptGetNumServersInLobby(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager * gamenet_man = Manager::Instance();
+	Script::CStruct* p_return_params;
+
+	p_return_params = pScript->GetParams();
+	p_return_params->AddInteger( "num_servers", gamenet_man->m_servers.CountItems());
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Manager::StartServerList( void )
+{
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+
+	if( InLanMode())
+	{
+		Net::Client* client;
+
+		MatchClientShutdown();
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+		client = SpawnMatchClient();
+
+		Mem::Manager::sHandle().PopContext();
+
+		if( client == NULL )
+		{
+			return false;
+		}
+
+        if( !m_auto_refresh_task->InList())
+		{
+			mlp_man->AddLogicTask( *m_auto_refresh_task );
+			return true;
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::StopServerList( void )
+{
+	
+
+	if( InInternetMode())
+	{   
+#ifdef __PLAT_NGPS__
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+		Dbg_Printf( "******** STOP LISTING GAMES **********\n" );
+		if( mpLobbyMan->GetPeer())
+		{
+			peerStopListingGames( mpLobbyMan->GetPeer());
+		}
+		Mem::Manager::sHandle().PopContext();
+#endif
+	}
+	else
+	{
+		Net::Client* client;
+
+		client = GetMatchClient();
+		if( client )
+		{
+			client->m_Dispatcher.Deinit();
+		}
+
+        m_auto_refresh_task->Remove();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::FreeServerList( void )
+{
+	Lst::Search< ServerInfo > sh;
+	ServerInfo* server, *next;
+	    
+	for( server = sh.FirstItem( m_temp_servers ); server; server = next )
+	{
+		next = sh.NextItem();
+		delete server;
+	}
+
+	for( server = sh.FirstItem( m_servers ); server; server = next )
+	{
+		next = sh.NextItem();
+		delete server;
+	}
+
+	if( InLanMode())
+	{
+		MatchClientShutdown();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::ClearServerList( bool refocus )
+{   
+	Script::CStruct* pParams;
+	
+	pParams = new Script::CStruct;
+	if( refocus )
+	{
+		pParams->AddChecksum( "refocus", Script::GenerateCRC( "refocus" ));
+	}
+	Script::RunScript( "destroy_server_menu_children", pParams );
+	Script::RunScript( "add_no_servers_found_message", pParams );
+	delete pParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::RefreshServerList( bool force_refresh )
+{
+	//Dbg_Printf( "In Refresh server list\n" );
+	if( force_refresh == false )
+	{
+		//Dbg_Printf( "No force\n" );
+		// Don't let people refresh like crazy
+		if(( Tmr::GetTime() - s_last_refresh_time ) < vTIME_BETWEEN_MANUAL_REFRESH )
+		{
+			//Dbg_Printf( "Not enough time\n" );
+			return;
+		}
+	}
+
+	s_last_refresh_time = Tmr::GetTime();
+	s_refresh_pending = true;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+
+	StopServerList();
+	FreeServerList();
+	StartServerList();
+
+	if( InInternetMode())
+	{
+#ifdef __PLAT_NGPS__
+		unsigned char key_list[] = {NUMPLAYERS_KEY,
+									MAXPLAYERS_KEY,
+									HOSTPORT_KEY,
+									GAMETYPE_KEY };
+
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+		
+		Dbg_Printf( "******** START LISTING GAMES **********\n" );
+		peerStartListingGames( mpLobbyMan->GetPeer(), key_list, sizeof(key_list), NULL, 
+								s_server_list_callback, this );
+		Mem::Manager::sHandle().PopContext();
+#endif
+	}
+	else
+	{   
+		FindServersOnLAN();
+	}
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __PLAT_XBOX__
+bool		Manager::ServerAlreadyInList( char* name, XNADDR* xbox_addr )
+{
+	Lst::Search< ServerInfo > sh;
+	ServerInfo* server;
+
+	for( server = sh.FirstItem( m_temp_servers ); server; server = sh.NextItem())
+	{
+		if(	( memcmp( &server->m_XboxAddr, xbox_addr, sizeof( XNADDR ) == 0 )) &&
+			( stricmp( server->m_Name, name ) == 0 ))
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+#else
+
+bool		Manager::ServerAlreadyInList( char* name, int ip )
+{
+	Lst::Search< ServerInfo > sh;
+	ServerInfo* server;
+
+	for( server = sh.FirstItem( m_servers ); server; server = sh.NextItem())
+	{
+		if(	( server->m_Ip == ip ) &&
+			( stricmp( server->m_Name, name ) == 0 ))
+		{
+			return true;
+		}
+	}
+
+	for( server = sh.FirstItem( m_temp_servers ); server; server = sh.NextItem())
+	{
+		if(	( server->m_Ip == ip ) &&
+			( stricmp( server->m_Name, name ) == 0 ))
+		{
+			return true;
+		}
+	}
+
+	
+	return false;
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::SortPreviousKey( void )
+{
+	if( m_sort_key == 0 )
+	{
+		m_sort_key = vNUM_SORT_KEYS - 1;
+	}
+	else
+	{
+		m_sort_key--;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::SortNextKey( void )
+{
+	m_sort_key = ( m_sort_key + 1 ) % vNUM_SORT_KEYS;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Manager::SortServerList( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ServerInfo*	Manager::GetServerInfo( uint32 id )
+{
+	Lst::Search< ServerInfo > sh;
+	ServerInfo* server;
+
+	for( server = sh.FirstItem( m_servers ); server; server = sh.NextItem())
+	{
+#ifdef __PLAT_NGPS__
+		if( InInternetMode())
+		{
+			if( id == (uint32) server->m_GServer )
+			{
+				return server;
+			}
+		}
+		else
+#endif
+		{
+			if( id == (uint32) server )
+		{
+			return server;
+		}
+		}
+	}
+	
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ServerInfo::ServerInfo( void ) : Lst::Node< ServerInfo > ( this )
+{
+	m_Listed = false;
+	m_InFocus = false;
+	m_Latency = 0;
+	sprintf( m_Level, "None" );
+	sprintf( m_Mode, "None" );
+	m_MaxPlayers = 8;
+	m_NumPlayers = 1;
+	m_MaxObservers = 1;
+	m_NumObservers = 0;
+	m_SkillLevel = vSKILL_LEVEL_DEFAULT;
+	m_HostMode = vHOST_MODE_SERVE;
+	m_Ranked = false;
+	m_GameStarted = false;
+	m_CanDirectConnect = true;
+	m_HasBasicInfo = false;
+	m_HasFullInfo = false;
+#ifdef __PLAT_NGPS__
+	m_GServer = NULL;
+#endif
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void ServerInfo::ClearPlayerNames( void )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player, *next;
+	Manager * gamenet_man = Manager::Instance();
+
+	for( player = sh.FirstItem( m_players ); player; player = next )
+	{
+		next = sh.NextItem();
+		gamenet_man->DestroyPlayer( player );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ServerInfo::~ServerInfo( void )
+{
+	ClearPlayerNames();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	ServerInfo::AddPlayer( char* name, int rating )
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	PlayerInfo* player;
+
+	if( gamenet_man->InInternetMode())
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetBottomUpHeap());
+	}
+	else
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().NetworkHeap());
+	}
+	
+	player = new PlayerInfo( 0 );
+	player->m_Rating = rating;
+	strncpy( player->m_Name, name, vMAX_PLAYER_NAME_LEN );
+	player->m_Name[ vMAX_PLAYER_NAME_LEN ] = '\0';
+	m_players.AddToTail( player );
+	Mem::Manager::sHandle().PopContext();
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char*	ServerInfo::GetPlayerName( int index )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+
+	for( player = sh.FirstItem( m_players ); player; player = sh.NextItem())
+	{
+		if( index == 0 )
+		{
+			return player->m_Name;
+		}
+		index--;
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		ServerInfo::GetPlayerRating( int index )
+{
+	Lst::Search< PlayerInfo > sh;
+	PlayerInfo* player;
+
+	for( player = sh.FirstItem( m_players ); player; player = sh.NextItem())
+	{
+		if( index == 0 )
+		{
+			return player->m_Rating;
+		}
+		index--;
+	}
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetServerListState( ServerListState state )
+{
+	//Dbg_Printf( "Setting server list state to %d from %d\n", state, m_server_list_state );
+	m_server_list_state = state;
+	
+	switch( m_server_list_state )
+	{
+		case vSERVER_LIST_STATE_INITIALIZE:
+		{
+			Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+			
+			//Dbg_Printf( "Serverlist State: Initializing\n" );
+
+			mlp_man->AddLogicTask( *m_server_list_state_task );
+			SetServerListState( GetNextServerListState());
+#ifdef __PLAT_NGPS__
+			s_motd_retry = 0;
+#endif // __PLAT_NGPS__
+			break;
+		}
+		
+		case vSERVER_LIST_STATE_SHUTDOWN:
+		{
+			//Dbg_Printf( "Serverlist State: Shutting down\n" );
+
+			m_server_list_state_task->Remove();
+			break;
+		}
+		
+		default:
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Manager::SetNextServerListState( ServerListState state )
+{
+	m_next_server_list_state = state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ServerListState Manager::GetServerListState( void )
+{
+	return m_server_list_state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+ServerListState Manager::GetNextServerListState( void )
+{
+	return m_next_server_list_state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ScriptRetrieveServerInfo(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Manager* gamenet_man = Manager::Instance();
+
+	if( gamenet_man->InLanMode())
+	{
+		// We have the data and can display it immediately
+		return true;
+	}
+	else
+	{
+#ifdef __PLAT_NGPS__
+		uint32 id;
+		ServerInfo* p_server;
+
+		pParams->GetChecksum( Script::GenerateCRC("id"), &id );
+
+		p_server = gamenet_man->GetServerInfo( id );
+		Dbg_Assert( p_server );
+
+		gamenet_man->SetServerFocus( p_server );
+		if( p_server->m_HasFullInfo )
+		{
+			// We have the data and can display it immediately
+			return true;
+		}
+		else
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().InternetTopDownHeap());
+
+			peerUpdateGame( gamenet_man->mpLobbyMan->GetPeer(), p_server->m_GServer, PEERTrue );
+
+			Mem::Manager::sHandle().PopContext();
+
+			// We have to request it from the server
+			return false;
+		}
+#else
+		return false;
+#endif
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ScriptDescribeServer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Script::CStruct* p_item_params;
+	ServerInfo* p_server;
+	Manager* gamenet_man = Manager::Instance();
+	int i;
+	char p_string[128];
+	Script::CArray* p_skill_array;
+	Script::CScriptStructure* p_skill_struct;
+	const char* p_skill_string;
+	uint32 id;
+
+	pParams->GetChecksum( Script::GenerateCRC("id"), &id );
+				
+	p_server = gamenet_man->GetServerInfo( id );
+	Dbg_Assert( p_server );
+
+	Script::RunScript( "destroy_server_desc_children" );
+
+    Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+	if( p_server->m_GameStarted )
+	{
+        // In Progress
+        p_item_params = new Script::CStruct;
+        sprintf( p_string, "%s:", Script::GetLocalString( "netoptions_str_game_in_progress" ) );
+        p_item_params->AddString( "name", p_string );
+        Script::RunScript("net_game_info_add_item", p_item_params );
+    	delete p_item_params;
+	}
+
+	if( p_server->m_HostMode == vHOST_MODE_AUTO_SERVE )
+	{
+		// Auto Serve
+        p_item_params = new Script::CStruct;
+        sprintf( p_string, "%s:", Script::GetLocalString( "netoptions_str_auto_serve" ) );
+        p_item_params->AddString( "name", p_string );
+        Script::RunScript("net_game_info_add_item", p_item_params );
+    	delete p_item_params;
+	}
+	else if( p_server->m_HostMode == vHOST_MODE_FCFS )
+	{
+		// FCFS
+        p_item_params = new Script::CStruct;
+        sprintf( p_string, "%s:", Script::GetLocalString( "netoptions_str_fcfs" ) );
+        p_item_params->AddString( "name", p_string );
+        Script::RunScript("net_game_info_add_item", p_item_params );
+    	delete p_item_params;
+	}
+
+#ifdef __PLAT_NGPS__
+	// Ping
+    p_item_params = new Script::CStruct;
+    sprintf( p_string, "%s:", Script::GetLocalString( "sort_title_ping" ) );
+    p_item_params->AddString( "name", p_string );
+	if( p_server->m_Latency == 9999 )
+	{
+		sprintf( p_string, "..." );
+	}
+	else
+	{
+		sprintf( p_string, "%d", p_server->m_Latency );
+	}
+    
+    p_item_params->AddString( "value", p_string );
+    Script::RunScript("net_game_info_add_item", p_item_params );
+	delete p_item_params;
+#endif
+
+	// Mode
+    p_item_params = new Script::CStruct;
+    sprintf( p_string, "%s:", Script::GetLocalString( "sort_title_mode" ) );
+    p_item_params->AddString( "name", p_string );
+    sprintf( p_string, "%s", p_server->m_Mode );
+    p_item_params->AddString( "value", p_string );
+    Script::RunScript("net_game_info_add_item", p_item_params );
+	delete p_item_params;
+    
+	// Skill Level
+    p_skill_array = Script::GetArray( "skill_level_info" );
+	p_skill_struct = p_skill_array->GetStructure( p_server->m_SkillLevel );
+	p_skill_struct->GetText( "name", &p_skill_string );
+	p_item_params = new Script::CStruct;	
+    sprintf( p_string, "%s:", Script::GetLocalString( "sort_title_skill" ) );
+    p_item_params->AddString( "name", p_string );
+    sprintf( p_string, "%s", p_skill_string );
+    p_item_params->AddString( "value", p_string );
+	Script::RunScript("net_game_info_add_item", p_item_params );
+	delete p_item_params;
+
+	// Level
+    p_item_params = new Script::CStruct;
+    sprintf( p_string, "%s:", Script::GetLocalString( "sort_title_level" ) );
+    p_item_params->AddString( "name", p_string );
+    sprintf( p_string, "%s", p_server->m_Level );
+    p_item_params->AddString( "value", p_string );
+    Script::RunScript("net_game_info_add_item", p_item_params );
+	delete p_item_params;
+
+	// Goals
+#ifndef __PLAT_XBOX__
+	if( gamenet_man->InInternetMode())
+	{
+		p_item_params = new Script::CStruct;
+		sprintf( p_string, "%s:", Script::GetLocalString( "sort_title_ranked" ) );
+		p_item_params->AddString( "name", p_string );
+		if( p_server->m_Ranked )
+		{
+			sprintf( p_string, "%s", Script::GetLocalString( "netoptions_str_yes" ));
+		}
+		else
+		{
+			sprintf( p_string, "%s", Script::GetLocalString( "netoptions_str_no" ));
+		}
+		
+		p_item_params->AddString( "value", p_string );
+		Script::RunScript("net_game_info_add_item", p_item_params );
+		delete p_item_params;
+	}
+#endif
+
+	// Player Ratio
+    p_item_params = new Script::CStruct;
+    sprintf( p_string, ": %d/%d", p_server->m_NumPlayers, p_server->m_MaxPlayers );
+    p_item_params->AddString( "text", p_string );
+    Script::RunScript("net_game_info_update_player_title", p_item_params );
+	delete p_item_params;
+    
+    // List of Players
+    for( i = 0; i < p_server->m_NumPlayers; i++ )
+	{
+		int rank;
+
+		rank = (int) (((float) p_server->GetPlayerRating( i ) / (float) vMAX_RATING ) * (float) vMAX_RANK );
+		p_item_params = new Script::CStruct;
+        sprintf( p_string, "%d:", i+1 );
+		p_item_params->AddInteger( "rank", rank );
+        p_item_params->AddString( "name", p_string );
+        sprintf( p_string, "%s", p_server->GetPlayerName( i ) );
+        p_item_params->AddString( "value", p_string );
+		Script::RunScript("net_game_info_add_player", p_item_params );
+		delete p_item_params;
+	}
+
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool		Manager::ScriptChooseServer(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	ServerInfo* p_server;
+	Manager* gamenet_man = Manager::Instance();
+	bool server_full;
+	uint32 reason_checksum;		
+	uint32 id;
+
+	pParams->GetChecksum( Script::GenerateCRC("id"), &id );
+				
+	gamenet_man->ClearServerFocus();
+
+	p_server = gamenet_man->GetServerInfo( id );
+	Dbg_Assert( p_server );
+								
+	server_full = false;
+	reason_checksum = 0;
+	if( gamenet_man->GetJoinMode() == vJOIN_MODE_PLAY )
+	{
+		if( p_server->m_NumPlayers >= p_server->m_MaxPlayers )
+		{
+			server_full = true;
+			reason_checksum = Script::GenerateCRC("net_reason_full");
+		}
+	}
+	else if( gamenet_man->GetJoinMode() == vJOIN_MODE_OBSERVE )
+	{
+		if( p_server->m_NumObservers >= p_server->m_MaxObservers )
+		{
+			server_full = true;
+			reason_checksum = Script::GenerateCRC("net_reason_full_observers");
+		}
+	}
+
+	if( server_full )
+	{   
+		Script::CStruct* p_structure = new Script::CStruct;
+		p_structure->AddChecksum( "reason", reason_checksum );
+		p_structure->AddChecksum( "just_dialog", 0 );
+
+		gamenet_man->m_join_state_task->Remove();
+		Script::RunScript( "CreateJoinRefusedDialog", p_structure );
+		delete p_structure;
+	}
+	else
+	{
+		Script::CScriptStructure* p_structure = new Script::CScriptStructure;
+
+#ifdef __PLAT_NGPS__							
+        if( gamenet_man->mpBuddyMan->IsLoggedIn())
+		{
+			gamenet_man->mpLobbyMan->SetServerName( p_server->m_Name );
+		}
+#endif
+
+#ifdef __PLAT_XBOX__							
+		IN_ADDR host_addr;		
+		
+		// Register the session key.
+		if( XNetRegisterKey( &p_server->m_XboxKeyId, 
+			&p_server->m_XboxKey ) != NO_ERROR )
+		{
+			OutputDebugString( "**** Failed to register key\n" );
+			return false;
+		}
+		
+		gamenet_man->m_XboxKeyRegistered = true;
+		// Translate the host’s XNADDR to an IN_ADDR.			
+		if( XNetXnAddrToInAddr( &p_server->m_XboxAddr, &p_server->m_XboxKeyId,
+				&host_addr ) != NO_ERROR )
+		{
+			OutputDebugString( "**** Failed to get inaddr from xnaddr\n" );
+			return false;
+		}
+		
+		memcpy( &gamenet_man->m_XboxKeyId, &p_server->m_XboxKeyId, sizeof( XNKID ));
+		
+		p_structure->AddComponent( Script::GenerateCRC( "Address" ), 
+								ESYMBOLTYPE_INTEGER, (int) host_addr.s_addr );
+		p_structure->AddComponent( Script::GenerateCRC( "Port" ), 
+								ESYMBOLTYPE_INTEGER, p_server->m_Port );
+#else
+		char *ip_addr;
+		struct in_addr addr;
+		uint32 cookie;
+		
+		addr.s_addr = p_server->m_Ip;
+		ip_addr = inet_ntoa( addr );
+		if( p_server->m_CanDirectConnect == false )
+		{
+			cookie = Mth::Rnd( vINT32_MAX );
+			p_structure->AddComponent( CRCD( 0x751f4599, "cookie" ), ESYMBOLTYPE_NAME, cookie );
+		}
+        p_structure->AddComponent( NONAME, ESYMBOLTYPE_INTEGER, p_server->m_Port );
+		p_structure->AddComponent( NONAME, ESYMBOLTYPE_STRING, ip_addr );
+#endif
+
+		p_structure->AddInteger( "MaxPlayers", p_server->m_MaxPlayers );
+		Script::RunScript( "net_chosen_join_server", p_structure );
+		delete p_structure;
+
+	}	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef __PLAT_NGPS__
+bool 		Manager::ScriptPostGame( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Manager* gamenet_man = Manager::Instance();
+
+	if( gamenet_man->mpLobbyMan->ReportedGame() == false )
+	{
+		gamenet_man->PostGame();
+	}
+
+	return true;
+}
+
+#endif //__PLAT_NGPS__
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+LobbyInfo::LobbyInfo( void ) : Lst::Node< LobbyInfo > ( this )
+{
+	m_Full = false;
+	m_Official = false;
+	m_OffLimits = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/XBox/p_auth.cpp b/Code/Sk/GameNet/XBox/p_auth.cpp
new file mode 100644
index 0000000..19b476f
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_auth.cpp
@@ -0,0 +1,787 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_auth.cpp												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	XBox Online Authorization Code		 					**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+const DWORD vMAX_MEMORY_UNITS  = 2 * XGetPortCount();
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+void	AuthMan::s_logon_state_code( const Tsk::Task< AuthMan >& task )
+{
+#	if 0
+	AuthMan& man = task.GetData();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	switch( man.m_state )
+	{
+		case vSTATE_FILL_ACCOUNT_LIST:		
+			if( man.gather_user_list() == 0 )
+			{
+				man.m_state = vSTATE_CREATE_ACCOUNT;
+			}
+			else
+			{
+				man.fill_user_list();
+				man.m_state = vSTATE_SELECT_ACCOUNT;
+			}
+			break;				
+		case vSTATE_SELECT_ACCOUNT:
+			break;			
+		case vSTATE_CREATE_ACCOUNT:		
+			man.create_account();
+			break;
+		
+		case vSTATE_GET_PIN:
+			Script::RunScript( "launch_pin_entry_menu" );
+			man.m_pin_entry.BeginInput( man.m_chosen_account );
+			man.m_state = vSTATE_WAIT;
+			break;
+		case vSTATE_PIN_COMPLETE:
+			man.PinAttempt( man.m_pin_entry.GetPin());
+			break;
+		case vSTATE_PIN_CANCELLED:
+			man.m_pin_entry.EndInput();
+			man.m_state = vSTATE_FILL_ACCOUNT_LIST;
+			break;
+		case vSTATE_PIN_CLEARED:
+			man.m_state = vSTATE_GET_PIN;
+			man.m_pin_entry.ClearInput();
+			break;
+		case vSTATE_LOG_ON:
+			man.logon();
+			man.m_state = vSTATE_LOGGING_ON;
+			break;
+		case vSTATE_LOGGING_ON:		
+			man.check_logon_progress();			
+			break;
+		case vSTATE_SUCCESS:
+			Script::RunScript( "launch_xbox_online_menu" );
+			man.m_signed_in = true;
+			man.m_state = vSTATE_LOGGED_IN;
+			gamenet_man->mpVoiceMan->Startup();
+			gamenet_man->mpBuddyMan->SpawnBuddyList( man.m_chosen_account );
+			break;
+		case vSTATE_LOGGED_IN:
+			man.pump_logon_task();
+			break;
+		case vSTATE_CANCEL:
+		{
+			Lst::DynamicTableDestroyer destroyer( &man.m_user_list );
+			destroyer.DeleteTableContents();
+
+			delete man.mp_logon_state_task;
+			man.mp_logon_state_task = NULL;
+			man.m_state = vSTATE_WAIT;		
+			break;
+		}
+		case vSTATE_WAIT:
+			break;
+		case vSTATE_ERROR:
+			man.m_state = man.m_next_state;
+			switch( man.m_error )
+			{
+				case vERROR_WRONG_PIN:
+					Script::RunScript( "launch_wrong_pin_dialog_box" );
+					break;
+				case vERROR_GENERAL_LOGIN:
+					break;
+				case vERROR_LOGIN_FAILED:
+					break;
+			}			
+			break;
+	}	
+#	endif
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static DWORD	s_get_mem_unit_slot( DWORD i )
+{
+	return( ( i % 2 ) ? XDEVICE_BOTTOM_SLOT : XDEVICE_TOP_SLOT );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static DWORD	s_get_mem_unit_port( DWORD i )
+{
+	return( i / 2 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static DWORD	s_get_mem_unit_mask( DWORD i )
+{
+	// The XGetDevices bitmask is formatted as follows:
+    //
+    //      0x00000001      port 0 top slot         i = 0
+    //      0x00010000      port 0 bottom slot          1
+    //      0x00000002      port 1 top slot             2
+    //      0x00020000      port 1 bottom slot          3
+    //      0x00000004      port 2 top slot             4
+    //      0x00040000      port 2 bottom slot          5
+    //      0x00000008      port 3 top slot             6
+    //      0x00080000      port 3 bottom slot          7
+
+    DWORD dwMask = 1 << s_get_mem_unit_port( i );
+    if( s_get_mem_unit_slot( i ) == XDEVICE_BOTTOM_SLOT )
+        dwMask <<= 16;
+
+    return( dwMask );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		AuthMan::gather_user_list( void )
+{
+    // Get accounts stored on the hard disk
+    DWORD num_users = 0;
+
+#	if 0
+	// On input, the list must have room for XONLINE_MAX_STORED_ONLINE_USERS
+    // accounts
+    XONLINE_USER user_list[ XONLINE_MAX_STORED_ONLINE_USERS ];
+
+    HRESULT hr = XOnlineGetUsers( user_list, &num_users);
+    if( SUCCEEDED(hr) )
+    {
+        for( DWORD i = 0; i < num_users; ++i )
+		{
+			XONLINE_USER* new_user;
+			
+			new_user = new XONLINE_USER;
+			*new_user = user_list[i];
+            m_user_list.Add( new_user );
+		}
+    }
+#	endif
+
+	return num_users;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::fill_user_list( void )
+{
+	int i;
+	
+	Script::RunScript( "create_account_list_menu" );	
+
+	for( i = 0; i < m_user_list.GetSize(); i++ )
+	{
+		Script::CStruct* p_item_params = new Script::CStruct;
+		XONLINE_USER* user;
+		
+		user = &m_user_list[i];
+
+		//p_item_params->AddString( "text", user->gamertag );
+		p_item_params->AddChecksum( "id", 123456 + i );		
+		p_item_params->AddChecksum( "pad_choose_script",Script::GenerateCRC("choose_selected_account"));
+
+		// create the parameters that are passed to the X script
+		Script::CStruct *p_script_params= new Script::CStruct;
+		p_script_params->AddInteger( "index", i );	
+		p_item_params->AddStructure("pad_choose_params",p_script_params);			
+
+		Script::RunScript("make_text_sub_menu_item",p_item_params);
+		delete p_item_params;
+		delete p_script_params;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::create_account( void )
+{
+	Script::RunScript( "launch_no_accounts_dialog" );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::logon( void )
+{	
+#	if 0
+	XONLINE_USER user_list[ XGetPortCount() ] = { 0 };	
+	
+	CopyMemory( &user_list[ 0 ], &m_user_list[ m_chosen_account ], sizeof( XONLINE_USER ));
+
+    // Initiate the login process. XOnlineTaskContinue() is used to poll
+    // the status of the login.
+    HRESULT hr = XOnlineLogon( user_list, mp_services, vNUM_SERVICES, 
+                               NULL, &mh_online_task );
+
+    if( FAILED(hr) || mh_online_task == NULL )
+    {
+		if( mh_online_task != NULL )
+		{
+			hr = XOnlineTaskClose( mh_online_task );
+			mh_online_task = NULL;
+		}        
+        m_state = vSTATE_ERROR;
+		m_next_state = vSTATE_WAIT;
+		m_error = vERROR_GENERAL_LOGIN;        
+    }
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	AuthMan::xbox_has_voice_device( void )
+{
+	return false;
+	
+//	DWORD dwConnectedMicrophones = XGetDevices( XDEVICE_TYPE_VOICE_MICROPHONE );
+//	DWORD dwConnectedHeadphones = XGetDevices( XDEVICE_TYPE_VOICE_HEADPHONE );
+
+    // Voice is available if there's at least one mike and one headphone
+//	return( dwConnectedMicrophones >= 1 && dwConnectedHeadphones  >= 1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::pump_logon_task( void )
+{
+#	if 0
+	// Pump the task
+	HRESULT hr;
+	hr = XOnlineTaskContinue( mh_online_task );
+	
+	// Check status
+	if( FAILED( hr ))
+	{	
+		//m_UI.SetErrorStr( L"Connection was lost. Must relogin" );
+	}
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::check_logon_progress( void )
+{
+#	if 0
+	// Pump the task
+	HRESULT hr;
+	hr = XOnlineTaskContinue( mh_online_task );
+	
+	// Check status
+	if ( hr != XONLINETASK_S_RUNNING )
+	{	
+		if ( hr != XONLINE_S_LOGON_CONNECTION_ESTABLISHED  )
+        {
+            XOnlineTaskClose( mh_online_task );
+
+			m_state = vSTATE_ERROR;
+			m_error = vERROR_LOGIN_FAILED;				
+            return;
+        }		
+	
+		// Check login status; partial results indicate that login itself
+		// has completed.
+		BOOL success = TRUE;
+		BOOL service_err = FALSE;
+		HRESULT hrService = S_OK;
+		DWORD i = 0;
+		
+		// Next, check if the user was actually logged on
+		PXONLINE_USER pLoggedOnUsers = XOnlineGetLogonUsers();
+		Dbg_Assert( pLoggedOnUsers );
+
+		hr = pLoggedOnUsers[ m_chosen_account ].hr;
+
+		// Check for general errors
+		if( FAILED(hr) )
+		{
+			XOnlineTaskClose( mh_online_task );
+
+			m_state = vSTATE_ERROR;
+			m_error = vERROR_LOGIN_FAILED;				
+            return;
+		}
+		
+		// Check for service errors
+		for( i = 0; i < vNUM_SERVICES; ++i )
+		{
+			if( FAILED( hrService = XOnlineGetServiceInfo( mp_services[i], NULL) ))
+			{
+				success = FALSE;
+				service_err = TRUE;
+				break;
+			}		
+		}
+
+		// If no errors, login was successful
+		m_state = success ? vSTATE_SUCCESS : vSTATE_ERROR;
+
+		if( !success )
+		{
+			if( service_err )
+			{
+				m_error = vERROR_SERVICE_DOWN;
+				/*m_UI.SetErrorStr( L"Login failed.\n\n"
+								  L"Error %x logging into service %d",
+								  mp_services[i].hr, mp_services[i].dwServiceID );*/
+			}
+			else
+			{
+				m_error = vERROR_LOGIN_FAILED;
+				/*m_UI.SetErrorStr( L"Login failed.\n\n"
+								  L"Error %x returned by "
+								  L"XOnlineTaskContinue", hr );*/					
+			}
+			XOnlineTaskClose( mh_online_task );
+			m_state = vSTATE_ERROR;
+			m_error = vERROR_LOGIN_FAILED;				
+		}			
+		else
+		{
+			DWORD player_state;
+
+			m_service_info_list.Reset();
+			for( i = 0; i < vNUM_SERVICES; ++i )
+			{
+				// Stored service information for UI
+				XONLINE_SERVICE_INFO* service_info;			
+
+				service_info = new XONLINE_SERVICE_INFO;                
+                XOnlineGetServiceInfo( mp_services[i], service_info );
+				m_service_info_list.Add( service_info );
+			}
+
+			// Notify the world of our state change
+			player_state = XONLINE_FRIENDSTATE_FLAG_ONLINE;
+			if( xbox_has_voice_device())
+			{
+				player_state |= XONLINE_FRIENDSTATE_FLAG_VOICE;
+			}
+
+			XOnlineNotificationSetState( m_chosen_account, player_state,
+                                         XNKID(), 0, NULL );			
+		}
+	}
+#	endif
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+AuthMan::AuthMan( void )
+: m_user_list( 1 ), m_service_info_list( 1 ), m_signed_in( false )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::UserLogon( void )
+{	
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+
+	mp_logon_state_task = new Tsk::Task< AuthMan > ( s_logon_state_code, *this,
+										Tsk::BaseTask::Node::vNORMAL_PRIORITY );
+	mlp_man->AddLogicTask( *mp_logon_state_task );
+	m_state = vSTATE_FILL_ACCOUNT_LIST;	
+	m_chosen_account = 0;
+	mp_services[0] = XONLINE_MATCHMAKING_SERVICE;
+    mp_services[1] = XONLINE_BILLING_OFFERING_SERVICE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::CancelLogon( void )
+{
+	m_state = vSTATE_CANCEL;		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::SelectAccount( int index )
+{
+	m_chosen_account = index;
+
+#	if 0
+	DWORD pin_required = m_user_list[ m_chosen_account ].dwUserOptions & XONLINE_USER_OPTION_REQUIRE_PIN;
+	if( pin_required )
+	{
+		m_state = vSTATE_GET_PIN;
+	}
+	else
+	{
+		m_state = vSTATE_LOG_ON;
+	}
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::PinAttempt( BYTE* pin )
+{	
+#	if 0
+	if(( memcmp( pin, m_user_list[m_chosen_account].pin, XONLINE_PIN_LENGTH ) == 0 ))
+	{
+		m_state = vSTATE_LOG_ON;		
+	}
+	else
+	{
+		m_state = vSTATE_ERROR;
+		m_next_state = vSTATE_CANCEL;
+		m_error = vERROR_WRONG_PIN;
+	}
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::SetState( LogonState state )
+{
+	m_state = state;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	AuthMan::SignedIn( void )
+{
+	return m_signed_in;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	AuthMan::SignOut( void )
+{
+#	if 0
+	XOnlineTaskClose( mh_online_task );
+	mh_online_task = NULL;
+	m_signed_in = false;
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PinEntry::s_pin_entry_input_logic_code ( const Inp::Handler < PinEntry >& handler )
+{	
+	PinEntry&	entry = handler.GetData();
+
+	// 'B' and 'Back' cancel the PIN entry
+	if(	( handler.m_Input->m_Buttons & Inp::Data::mD_SELECT ) ||
+		( handler.m_Input->m_Buttons & Inp::Data::mD_CIRCLE ))
+	{
+		entry.m_cancelled = true;
+		return;
+	}
+
+	// 'A' and 'Start' are ignored
+	if(	( handler.m_Input->m_Buttons & Inp::Data::mD_X ) ||
+		( handler.m_Input->m_Buttons & Inp::Data::mD_START ))
+	{
+		return;
+	}
+
+	XINPUT_GAMEPAD pad = {0};	
+
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_L3 )
+	{
+		entry.m_cleared = true;
+		return;
+	}
+
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_LEFT )
+	{
+		pad.wButtons |= XINPUT_GAMEPAD_DPAD_LEFT;
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_RIGHT )
+	{
+		pad.wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT;
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_UP )
+	{
+		pad.wButtons |= XINPUT_GAMEPAD_DPAD_UP;
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_DOWN )
+	{
+		pad.wButtons |= XINPUT_GAMEPAD_DPAD_DOWN;
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_TRIANGLE )
+	{
+		pad.bAnalogButtons[XINPUT_GAMEPAD_Y] = 255;		
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_SQUARE )
+	{
+		pad.bAnalogButtons[XINPUT_GAMEPAD_X] = 255;		
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_L1 )
+	{
+		pad.bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] = 255;		
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_R1 )
+	{
+		pad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] = 255;		
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_L2 )
+	{
+		pad.bAnalogButtons[XINPUT_GAMEPAD_WHITE] = 255;		
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_R2 )
+	{
+		pad.bAnalogButtons[XINPUT_GAMEPAD_BLACK] = 255;		
+	}		
+
+	XINPUT_STATE xis;
+    CopyMemory( &xis.Gamepad, &pad, sizeof( pad ));
+
+#	if 0
+    HRESULT hr = XOnlinePINDecodeInput( entry.m_pin_input, &xis, &entry.m_frame_entry );
+    if( hr == S_OK )
+    {
+		entry.m_got_entry = true;        
+    }
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PinEntry::s_pin_entry_logic_code( const Tsk::Task< PinEntry >& task )
+{
+#	if 0
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	PinEntry& entry = task.GetData();
+
+	if( entry.m_cleared )
+	{
+		gamenet_man->mp_AuthMan->SetState( AuthMan::vSTATE_PIN_CLEARED );		
+	}
+	else if( entry.m_cancelled )
+	{		
+		gamenet_man->mp_AuthMan->SetState( AuthMan::vSTATE_PIN_CANCELLED );		
+	}
+	else if( entry.m_got_entry )
+	{
+		entry.m_pin[entry.m_pin_length++] = entry.m_frame_entry;
+		entry.m_got_entry = false;
+		// Here update the UI with the latest pin entry
+
+		if( entry.Complete())
+		{
+			entry.EndInput();
+			
+			// Submit this entry as a our final pin
+			GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+			gamenet_man->mp_AuthMan->SetState( AuthMan::vSTATE_PIN_COMPLETE );
+		}
+	}
+#	endif
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PinEntry::ClearInput( void )
+{
+	m_pin_length = 0;
+	m_got_entry = false;
+	m_cleared = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PinEntry::BeginInput( int controller )
+{
+#	if 0
+	int i;
+
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	Inp::Manager * inp_manager = Inp::Manager::Instance();
+
+	mp_pin_entry_logic_task = new Tsk::Task< PinEntry > ( s_pin_entry_logic_code, *this,
+										Tsk::BaseTask::Node::vNORMAL_PRIORITY );
+	mlp_man->AddLogicTask( *mp_pin_entry_logic_task );
+	mp_pin_entry_input_handler = new Inp::Handler< PinEntry > ( controller,  
+														s_pin_entry_input_logic_code, *this, 
+														Tsk::BaseTask::Node::vNORMAL_PRIORITY-1 );
+	inp_manager->AddHandler( *mp_pin_entry_input_handler );	
+
+	for( i = 0; i < XONLINE_PIN_LENGTH; i++ )
+	{
+		m_pin[i] = 0;
+	}
+
+	m_pin_length = 0;
+	m_frame_entry = 0;
+	m_cancelled = false;
+	m_got_entry = false;
+	m_cleared = false;
+
+	XINPUT_STATE xis = {0};
+    m_pin_input = XOnlinePINStartInput( &xis );	
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	PinEntry::Complete( void )
+{
+//	return( m_pin_length == XONLINE_PIN_LENGTH );
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	PinEntry::EndInput( void )
+{
+	delete mp_pin_entry_logic_task;
+	mp_pin_entry_logic_task = NULL;
+	delete mp_pin_entry_input_handler;
+	mp_pin_entry_input_handler = NULL;
+
+//	XOnlinePINEndInput( m_pin_input );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+BYTE*	PinEntry::GetPin( void )
+{
+//	return m_pin;
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+	
+
diff --git a/Code/Sk/GameNet/XBox/p_auth.h b/Code/Sk/GameNet/XBox/p_auth.h
new file mode 100644
index 0000000..debc38d
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_auth.h
@@ -0,0 +1,169 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_auth.h												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	XBox Online Authorization Code							**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_XBOX_P_AUTH_H
+#define __GAMENET_XBOX_P_AUTH_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace GameNet
+{
+
+// Number of services to authenticate
+const DWORD vNUM_SERVICES = 2;						
+//typedef std::vector< XONLINE_USER > XBUserList;
+//typedef std::vector< XONLINE_SERVICE_INFO > ServiceInfoList;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class PinEntry
+{
+public:
+	void	BeginInput( int controller );    
+	void	EndInput( void );
+	void	ClearInput( void );
+	bool	Complete( void );
+	BYTE*	GetPin( void );
+
+private:
+//	XPININPUTHANDLE m_pin_input;                  // PIN input handle
+	BYTE		m_frame_entry;				// PIN entry this frame
+//    BYTE		m_pin[ XONLINE_PIN_LENGTH ];  // Current PIN
+    int			m_pin_length;                // number of buttons entered
+	bool		m_got_entry;
+	bool		m_cancelled;
+	bool		m_cleared;
+	
+	static	Inp::Handler< PinEntry >::Code 	s_pin_entry_input_logic_code;      
+	Inp::Handler< PinEntry >*	mp_pin_entry_input_handler;
+
+	static	Tsk::Task< PinEntry >::Code  s_pin_entry_logic_code; 
+	Tsk::Task< PinEntry >*		mp_pin_entry_logic_task;
+	
+};
+
+class AuthMan
+{
+public:
+	typedef enum
+	{
+		vSTATE_FILL_ACCOUNT_LIST,
+		vSTATE_CREATE_ACCOUNT,
+		vSTATE_SELECT_ACCOUNT,
+		vSTATE_GET_PIN,
+		vSTATE_PIN_COMPLETE,
+		vSTATE_PIN_CLEARED,
+		vSTATE_PIN_CANCELLED,
+		vSTATE_LOG_ON,
+		vSTATE_LOGGING_ON,		
+		vSTATE_LOGGED_IN,
+		vSTATE_SUCCESS,
+		vSTATE_CANCEL,
+		vSTATE_ERROR,
+		vSTATE_WAIT,
+	} LogonState;
+
+	enum
+	{
+		vERROR_WRONG_PIN,
+		vERROR_GENERAL_LOGIN,
+		vERROR_LOGIN_FAILED,
+		vERROR_SERVICE_DOWN,
+	};
+
+	AuthMan( void );
+
+	void	UserLogon( void );
+	void	CancelLogon( void );
+	void	SelectAccount( int index );
+	void	PinAttempt( BYTE* pin );
+	void	SetState( LogonState state );
+	void	SignOut( void );
+	bool	SignedIn( void );
+	
+private:
+	int		gather_user_list( void );
+	void	fill_user_list( void );
+	void	create_account( void );
+	void	logon( void );
+	void	check_logon_progress( void );
+	void	pump_logon_task( void );
+	bool	xbox_has_voice_device( void ); 
+
+	Lst::DynamicTable< XONLINE_USER > m_user_list;
+	LogonState	m_state;
+	LogonState	m_next_state;
+	int			m_error;
+	bool		m_signed_in;
+	int			m_chosen_account;
+	PinEntry	m_pin_entry;	
+	XONLINETASK_HANDLE	mh_online_task;
+	DWORD		mp_services[ vNUM_SERVICES ]; // List of desired services
+	Lst::DynamicTable< XONLINE_SERVICE_INFO > m_service_info_list;
+
+	static	Tsk::Task< AuthMan >::Code  s_logon_state_code; 
+	Tsk::Task< AuthMan >*		mp_logon_state_task;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_XBOX_P_AUTH_H
+
+
diff --git a/Code/Sk/GameNet/XBox/p_buddy.cpp b/Code/Sk/GameNet/XBox/p_buddy.cpp
new file mode 100644
index 0000000..43a7662
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_buddy.cpp
@@ -0,0 +1,206 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_buddy.cpp												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	XBox Buddy List Code 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+void	BuddyMan::s_buddy_state_code( const Tsk::Task< BuddyMan>& task )
+{
+	BuddyMan& man = task.GetData();
+
+	switch( man.m_state )
+	{
+		case vSTATE_UPDATE:
+			man.update_buddy_list();
+			break;
+		case vSTATE_FILL_BUDDY_LIST:
+			man.fill_buddy_list();
+			man.m_state = vSTATE_WAIT;
+			break;
+		case vSTATE_SUCCESS:
+			break;
+		case vSTATE_CANCEL:
+			break;
+		case vSTATE_ERROR:
+			break;
+		case vSTATE_WAIT:
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::fill_buddy_list( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	BuddyMan::update_buddy_list( void )
+{
+#	if 0
+	HRESULT hr = XOnlineTaskContinue( mh_friends_enum_task );
+    // Enumeration failed
+    if( FAILED( hr ) )
+    {
+        //m_UI.SetErrorStr( L"Friend enumeration failed. Error %x", hr );
+        //Reset( TRUE );
+        return;
+    }
+    
+    // Update generic status
+//    if( XOnlineNotificationIsPending( m_current_user, XONLINE_NOTIFICATION_TYPE_FRIENDREQUEST ))
+	{
+        //SetStatus( L"You have received a friend request" );
+	}
+//    else if( XOnlineNotificationIsPending( m_current_user, XONLINE_NOTIFICATION_TYPE_GAMEINVITE ))
+	{
+        //SetStatus( L"You have received a game invitation" );
+	}
+//    else
+	{
+        //SetStatus( L"Friend list refreshed" );
+	}
+    
+	XONLINE_FRIEND	friend_list[MAX_FRIENDS];
+	int	i, num_friends;
+
+	num_friends = XOnlineFriendsGetLatest( m_current_user, MAX_FRIENDS, friend_list );
+	if( num_friends != m_num_friends )
+	{
+		m_num_friends = num_friends;
+		Lst::DynamicTableDestroyer destroyer( &m_friends_list );
+		destroyer.DeleteTableContents();
+
+		for( i = 0; i < num_friends; i++ )
+		{
+			XONLINE_FRIEND* new_friend;
+
+			new_friend = new XONLINE_FRIEND;
+			memcpy( new_friend, &friend_list[i], sizeof( XONLINE_FRIEND ));
+			m_friends_list.Add( new_friend );
+		}
+	}    
+#	endif
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+void	BuddyMan::SpawnBuddyList( int user_index )
+{
+#	if 0
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+
+	m_current_user = user_index;
+
+	// Standard init
+    HRESULT hr = XOnlineFriendsStartup( NULL, &mh_friends_task );
+    if( FAILED(hr) )
+    {
+        //m_UI.SetErrorStr( L"Friends failed to initialize. Error %x", hr );
+        //Reset( TRUE );
+    }
+    
+    // Query server for latest list of friends
+    hr = XOnlineFriendsEnumerate( m_current_user, NULL, &mh_friends_enum_task );
+    if( FAILED(hr) )
+    {
+        //m_UI.SetErrorStr( L"Friend enum failed to initialize. Error %x", hr );
+        //Reset( TRUE );
+    }
+
+	mp_buddy_state_task = new Tsk::Task< BuddyMan > ( s_buddy_state_code, *this,
+										Tsk::BaseTask::Node::vNORMAL_PRIORITY );
+	mlp_man->AddLogicTask( *mp_buddy_state_task );	
+	m_state = vSTATE_UPDATE;	
+#	endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+BuddyMan::BuddyMan( void )
+: m_potential_friends_list( 1 ), m_friends_list( MAX_FRIENDS ), m_num_friends( 1 )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/XBox/p_buddy.h b/Code/Sk/GameNet/XBox/p_buddy.h
new file mode 100644
index 0000000..6ade1b9
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_buddy.h
@@ -0,0 +1,110 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_buddy.h												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	XBox Buddy List Code 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_XBOX_P_BUDDY_H
+#define __GAMENET_XBOX_P_BUDDY_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace GameNet
+{
+
+class BuddyMan
+{
+public:
+	typedef enum
+	{
+		vSTATE_FILL_BUDDY_LIST,
+		vSTATE_UPDATE,
+		vSTATE_SUCCESS,
+		vSTATE_CANCEL,
+		vSTATE_ERROR,
+		vSTATE_WAIT,
+	} BuddyState;
+
+	BuddyMan( void );
+
+	void	SpawnBuddyList( int user_index );
+
+private:
+
+	void	update_buddy_list( void );
+	void	fill_buddy_list( void );
+
+	int		m_current_user;
+	int		m_num_friends;
+	int		m_state;
+	int		m_next_state;
+	XONLINETASK_HANDLE	mh_friends_task;
+	XONLINETASK_HANDLE	mh_friends_enum_task;
+	Lst::DynamicTable< XONLINE_USER > m_potential_friends_list;
+	Lst::DynamicTable< XONLINE_FRIEND > m_friends_list;
+
+	static	Tsk::Task< BuddyMan >::Code  s_buddy_state_code; 
+	Tsk::Task< BuddyMan >*		mp_buddy_state_task;
+};
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_XBOX_P_BUDDY_H
+
+
diff --git a/Code/Sk/GameNet/XBox/p_match.cpp b/Code/Sk/GameNet/XBox/p_match.cpp
new file mode 100644
index 0000000..ba3c3ab
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_match.cpp
@@ -0,0 +1,77 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_match.cpp												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	XBox Match-Making Code									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+
+
diff --git a/Code/Sk/GameNet/XBox/p_match.h b/Code/Sk/GameNet/XBox/p_match.h
new file mode 100644
index 0000000..0efe7f2
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_match.h
@@ -0,0 +1,72 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_match.h												**
+**																			**
+**	Created by:		02/28/02	-	SPG										**
+**																			**
+**	Description:	XBox Matchmaking Code 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_XBOX_P_MATCH_H
+#define __GAMENET_XBOX_P_MATCH_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace GameNet
+{
+
+						
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_XBOX_P_MATCH_H
+
+
diff --git a/Code/Sk/GameNet/XBox/p_voice.cpp b/Code/Sk/GameNet/XBox/p_voice.cpp
new file mode 100644
index 0000000..3eddaad
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_voice.cpp
@@ -0,0 +1,104 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_voice.cpp												**
+**																			**
+**	Created by:		03/12/02	-	SPG										**
+**																			**
+**	Description:	XBox Online Voice Chat Code		 						**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace GameNet
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+VoiceMan::VoiceMan( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	VoiceMan::Startup( void )
+{	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	VoiceMan::Shutdown( void )
+{	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+
+	
+
diff --git a/Code/Sk/GameNet/XBox/p_voice.h b/Code/Sk/GameNet/XBox/p_voice.h
new file mode 100644
index 0000000..8ad09ec
--- /dev/null
+++ b/Code/Sk/GameNet/XBox/p_voice.h
@@ -0,0 +1,86 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			GameNet			 										**
+**																			**
+**	File name:		p_voice.h												**
+**																			**
+**	Created by:		03/12/02	-	SPG										**
+**																			**
+**	Description:	XBox Online Voice Chat Code								**
+**																			**
+*****************************************************************************/
+
+#ifndef __GAMENET_XBOX_P_VOICE_H
+#define __GAMENET_XBOX_P_VOICE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace GameNet
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class VoiceMan
+{
+public:
+	
+	VoiceMan( void );
+
+	void	Startup( void );
+	void	Shutdown( void );
+	
+private:	
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace GameNet
+
+#endif	// __GAMENET_XBOX_P_VOICE_H
+
+
diff --git a/Code/Sk/GameNet/scriptdebugger.h b/Code/Sk/GameNet/scriptdebugger.h
new file mode 100644
index 0000000..fc2577c
--- /dev/null
+++ b/Code/Sk/GameNet/scriptdebugger.h
@@ -0,0 +1,74 @@
+#ifndef __GAMENET_SCRIPTDEBUGGER_H__
+#define	__GAMENET_SCRIPTDEBUGGER_H__
+
+#include 
+
+namespace GameNet
+{
+
+#define	vSERVER_IP_VARIABLE	"VIEWER_IP"
+
+enum
+{
+	vSCRIPT_DEBUG_PORT = 10050, // Needs to be distinct from vEXPORT_COMM_PORT or any others
+};	
+
+enum
+{
+	vSCRIPT_DEBUG_GROUP = 10
+};
+
+enum
+{	
+	//	32 is the first available user-defined message id
+	vMSG_ID_DBG_PACKET							= 32, // PC -> Game
+	
+	vMSG_ID_DBG_CHECKSUM_NAMES_BUFFER_SIZE		= 33, // Game -> PC
+	vMSG_ID_DBG_CHECKSUM_NAMES					= 34, // Game -> PC
+	vMSG_ID_DBG_CSCRIPT_LIST					= 35, // Game -> PC
+	vMSG_ID_DBG_WATCH_THIS						= 36, // PC -> Game
+	vMSG_ID_DBG_WATCH_SCRIPT					= 37, // PC -> Game
+	vMSG_ID_DBG_SCRIPT_CREATED					= 38, // Game -> PC
+	vMSG_ID_DBG_SCRIPT_DIED						= 39, // Game -> PC
+	vMSG_ID_DBG_CSCRIPT_INFO					= 40, // Game -> PC
+	
+	vMSG_ID_DBG_COMPRESSION_LOOKUP_TABLE		= 41, // Game -> PC
+	vMSG_ID_DBG_BASIC_CSCRIPT_INFO				= 42, // Game -> PC
+	vMSG_ID_DBG_STOP							= 43, // PC -> Game
+	vMSG_ID_DBG_STEP_INTO						= 44, // PC -> Game
+	vMSG_ID_DBG_STEP_OVER						= 45, // PC -> Game
+	vMSG_ID_DBG_GO								= 46, // PC -> Game
+	vMSG_ID_DBG_SCRIPT_UPDATE_DELAY				= 47, // PC -> Game
+	vMSG_ID_DBG_CLEAR_SCRIPT_WATCHES			= 48, // PC -> Game
+	vMSG_ID_DBG_REFRESH							= 49, // PC -> Game
+	vMSG_ID_DBG_SEND_CSCRIPT_LIST				= 50, // PC -> Game
+	
+	vMSG_ID_DBG_SEND_WATCH_LIST					= 51, // PC -> Game
+	vMSG_ID_DBG_WATCH_LIST						= 52, // Game -> PC
+	vMSG_ID_DBG_STOP_WATCHING_THIS_INDEX		= 53, // PC -> Game
+	vMSG_ID_DBG_STOP_WATCHING_THIS_CSCRIPT		= 54, // PC -> Game
+	vMSG_ID_DBG_SEND_COMPOSITE_OBJECT_INFO		= 55, // PC -> Game
+	vMSG_ID_DBG_COMPOSITE_OBJECT_INFO			= 56, // Game -> PC
+	
+	vMSG_ID_DBG_MOUSE_POSITION					= 57, // PC -> Game
+	vMSG_ID_DBG_MOUSE_ON_SCREEN					= 58, // PC -> Game
+	vMSG_ID_DBG_MOUSE_OFF_SCREEN				= 59, // PC -> Game
+	vMSG_ID_DBG_MOUSE_LEFT_BUTTON_DOWN			= 60, // PC -> Game
+	vMSG_ID_DBG_MOUSE_LEFT_BUTTON_UP			= 61, // PC -> Game
+	vMSG_ID_DBG_MOUSE_RIGHT_BUTTON_DOWN			= 62, // PC -> Game
+	vMSG_ID_DBG_MOUSE_RIGHT_BUTTON_UP			= 63, // PC -> Game
+	
+	vMSG_ID_DBG_COMPOSITE_OBJECT_LIST			= 64, // Game -> PC
+	vMSG_ID_DBG_SEND_COMPOSITE_OBJECT_LIST		= 65, // PC -> Game
+	vMSG_ID_DBG_SET_OBJECT_UPDATE_MODE			= 66, // PC -> Game
+	vMSG_ID_DBG_VIEW_OBJECT						= 67, // PC -> Game
+	
+	vMSG_ID_DBG_SEND_SCRIPT_GLOBAL_INFO			= 68, // PC -> Game
+	vMSG_ID_DBG_SCRIPT_GLOBAL_INFO				= 69, // Game -> PC
+	
+	vMSG_ID_DBG_EXCEPTION_INFO					= 70, // Game -> PC
+	vMSG_ID_DBG_RUNNING_SCRIPT					= 71, // Game -> PC
+};
+
+}
+#endif	// __GAMENET_SCRIPTDEBUGGER_H__
\ No newline at end of file
diff --git a/Code/Sk/Main.cpp b/Code/Sk/Main.cpp
new file mode 100644
index 0000000..8882750
--- /dev/null
+++ b/Code/Sk/Main.cpp
@@ -0,0 +1,848 @@
+/*****************************************************************************
+**				   															**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Main		 											**
+**																			**
+**	File name:		main.cpp												**
+**																			**
+**	Created:		a long long time ago									**
+**																			**
+**	Description:	Main entry code											**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+
+extern int maintest(int argc, char *argv[]);
+extern void test_init();
+extern void test_render();
+
+#ifdef __PLAT_NGC__
+#ifdef DVDETH
+#include 
+#endif
+#include 
+#endif
+
+#include 	
+#include 
+#include 
+		 
+#include 
+#include 
+#include 
+#include 
+#include 										   
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  
+#include  
+#include  
+#include  
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#ifdef __PLAT_NGPS__
+#include 
+#endif
+
+#ifdef __PLAT_NGC__
+#include 
+#endif		// __PLAT_NGC__
+
+#define MAINLOOP_MEMMARKER 16
+
+#ifdef	__PLAT_NGPS__
+#include 
+#include 
+extern char _code_start[];
+extern char _code_end[];
+
+extern char __floatdisf[];
+extern char dptofp[];
+extern char __fixunssfdi[];
+extern char __udivdi3[];
+#endif
+
+
+
+#ifdef	__PLAT_NGPS__
+sceDemoEndReason		demo_exit_reason = SCE_DEMO_ENDREASON_PLAYABLE_QUIT;	// default reason is that we quit (timeouts overrride this, and we can't "complete")
+#define	DEBUG_FLASH(x)  //*GS_BGCOLOR = x  // 0xBBGGRR
+#else
+#define	DEBUG_FLASH(x)
+#endif
+
+int		inactive_time = 0;
+int		inactive_countdown = 0;
+int		gameplay_time = 0;
+
+
+
+bool	skip_startup = false;
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+Dbg_DefineProject( PS2, "Test Project" )
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+extern "C"
+{
+#ifdef __PLAT_XBOX__
+int pre_main( void );
+#endif
+#ifdef __PLAT_NGC__
+void __init_vm(void);
+#endif
+#ifdef __PLAT_NGPS__
+void pre_main( void );
+#endif
+void post_main( void );
+}
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+#ifdef __PLAT_XBOX__
+int pre_main( void )
+#endif
+#ifdef __PLAT_NGC__
+void __init_vm( void )
+#endif
+#ifdef __PLAT_NGPS__
+void pre_main( void )
+#endif
+{
+	DEBUG_FLASH(0x07f7f7f);		// initial white
+
+
+#ifdef __PLAT_NGPS__
+	printf ("calling Mike test\n");
+//	maintest(0, NULL); // the Mike code.....
+	printf ("DONE calling Mike test\n");
+#endif		// __PLAT_NGPS__
+
+#ifdef __PLAT_NGC__
+	OSInitFastCast();
+	NsDisplay::init();
+#endif		// __PLAT_NGC__
+
+	Dbg::SetUp();
+	Mem::Manager::sSetUp();
+	
+	DEBUG_FLASH(0x02050);		// brown
+
+#ifdef __PLAT_XBOX__
+	// Must return 0 here, as this is called from CRT initialization code.
+	return 0;
+}
+
+#pragma data_seg( ".CRT$RIX" )
+static int (*_mypreinit)(void) = pre_main;
+#pragma data_seg()
+
+#else
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void post_main (void)
+{
+	Mem::Manager::sCloseDown();
+	Dbg::CloseDown();
+}
+
+void mat_test(Mth::Matrix A,Mth::Matrix B,Mth::Matrix C);
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+int main ( sint argc, char** argv )
+{ 	
+#ifdef __PLAT_NGC__
+#ifdef DVDETH
+	// Defaults
+    u8   DvdServerAddr[4]    = { 192, 168,   0,   153 };// PC IP Address
+    u16  DvdServerPort       = 9001;                  // PC TCP PORT
+
+//    u8   ReportServerAddr[4] = { 192, 168,   0,   153 };// PC IP Address
+//    u16  ReportServerPort    = 9002;                  // PC TCP PORT
+
+    u8   GCClientAddr[4]     = { 192, 168,   5,   153 };// GC IP Adderss
+    u8   Netmask[4]          = { 255, 255,   0,   0 };// Netmask
+    u8   Gateway[4]          = { 192, 168,   0, 254 };// Gateway
+#endif		// DVDETH
+
+
+	// Add -a -pf to odemrun to enable printfs.
+	bool kill = true;
+	for ( int arg = 1; arg < argc; arg++ )
+	{
+		if ( argv[arg][0] != '-' ) continue;
+
+		switch ( argv[arg][1] )
+		{
+			case 'p':
+				// Kill printfs.
+				if ( argv[arg][2] == 'f' )
+				{
+					kill = false;
+				}
+				break;
+#ifdef DVDETH
+			case 'i':
+				// specify IP address.
+				switch ( argv[arg][2] )
+				{
+					case 'p':
+						{
+							// PC IP address
+							char num0[8];
+							char num1[8];
+							char num2[8];
+							char num3[8];
+							int c = 0;
+							int d;
+
+							// Get the numbers individually.
+							d = 0;      while ( argv[arg][3+c] != '.' ) { num0[d++] = argv[arg][3+c++]; } num0[d] = '\0';
+							d = 0; c++; while ( argv[arg][3+c] != '.' ) { num1[d++] = argv[arg][3+c++]; } num1[d] = '\0';
+							d = 0; c++; while ( argv[arg][3+c] != '.' ) { num2[d++] = argv[arg][3+c++]; } num2[d] = '\0';
+							d = 0; c++; while ( argv[arg][3+c] != '\0' ) { num3[d++] = argv[arg][3+c++]; } num3[d] = '\0';
+
+							DvdServerAddr[0] = atoi( num0 );
+							DvdServerAddr[1] = atoi( num1 );
+							DvdServerAddr[2] = atoi( num2 );
+							DvdServerAddr[3] = atoi( num3 );
+
+//							ReportServerAddr[0] = atoi( num0 );
+//							ReportServerAddr[1] = atoi( num1 );
+//							ReportServerAddr[2] = atoi( num2 );
+//							ReportServerAddr[3] = atoi( num3 );
+						}
+						break;
+					case 'g':
+						{
+							// GameCube IP address
+							char num0[8];
+							char num1[8];
+							char num2[8];
+							char num3[8];
+							int c = 0;
+							int d;
+
+							// Get the numbers individually.
+							d = 0;      while ( argv[arg][3+c] != '.' ) { num0[d++] = argv[arg][3+c++]; } num0[d] = '\0';
+							d = 0; c++; while ( argv[arg][3+c] != '.' ) { num1[d++] = argv[arg][3+c++]; } num1[d] = '\0';
+							d = 0; c++; while ( argv[arg][3+c] != '.' ) { num2[d++] = argv[arg][3+c++]; } num2[d] = '\0';
+							d = 0; c++; while ( argv[arg][3+c] != '\0' ) { num3[d++] = argv[arg][3+c++]; } num3[d] = '\0';
+
+							GCClientAddr[0] = atoi( num0 );
+							GCClientAddr[1] = atoi( num1 );
+							GCClientAddr[2] = atoi( num2 );
+							GCClientAddr[3] = atoi( num3 );
+						}
+						break;
+					default:
+						break;
+				}
+				break;
+#endif		// DVDETH
+			default:
+				break;
+		}
+	}
+
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+	if ( kill )
+	{
+		// Kill printfs
+		uint32 * p;
+		p = (uint32*)&OurPrintf;
+		*p = 0x4e800020;		// blr
+		p = (uint32*)&OSReport;
+		*p = 0x4e800020;        // blr
+	}
+#endif	// __NOPT_FINAL__
+
+#ifdef DVDETH
+
+    // Initialize Network
+    DVDEthInit(GCClientAddr, Netmask, Gateway);
+
+    // Initialize DVD
+    DVDLowInit(DvdServerAddr, DvdServerPort);
+
+//    // Initialize OSReport();
+//    OSReportInit(ReportServerAddr, ReportServerPort);
+#endif
+
+#endif		// __PLAT_NGC__
+
+	// Note: printf's will not work until after Config::Init() is called.
+
+	DEBUG_FLASH(0x007F0000);
+	
+	Mem::SetThreadSafe(false);	
+	Config::Init(argc,argv);
+
+#ifdef __PLAT_NGPS__
+	// a hack to disable the PS2 video output as early as poss
+	NxPs2::SetupPCRTC(0, SCE_GS_FIELD);
+#endif		// __PLAT_NGPS__
+
+	DEBUG_FLASH(0x00007f00);
+					  
+	Mem::Manager::sHandle().InitOtherHeaps();							
+
+	DEBUG_FLASH(0x0000007f);
+
+	#ifdef	__PLAT_NGPS__
+// Need to reconcile the Config::Init with the following code
+// so demo version can exist with regular version
+// just switched by examining command line
+
+	if (Config::SonyBootstrap())
+	{
+								   
+		unsigned short language;
+		unsigned short aspect;
+		unsigned short playmode;
+		unsigned short to_inactive;
+		unsigned short to_total;
+		unsigned short mediaType;
+		unsigned short masterVolumeScale = 10;
+		unsigned short directorySectorNum;
+			
+		sceDemoStart(argc,argv,&language,&aspect,&playmode,&to_inactive,&to_total,&mediaType,&masterVolumeScale,&directorySectorNum);
+		
+		printf("Tony Hawk's Pro Skater 4 - Demo Disk Version\n");
+		printf("l %d a %d pm %d toa %d tot %d\n",language,aspect,playmode,to_inactive,to_total);
+		printf("MEDIA TYPE %d\n",mediaType);
+		printf("MasterVol Scale %d\n",masterVolumeScale);
+		printf("My directory Sector is %d\n",directorySectorNum);
+		
+		inactive_time = to_inactive * 60;
+		inactive_countdown = to_inactive * 60;
+		gameplay_time = to_total * 60;
+		
+		if (Config::Bootstrap() && Config::CD())
+		{
+			Config::SetMasterVolume(masterVolumeScale * 10.0f);
+		}
+	}
+	#endif		   
+	
+	
+//	Config::SetMasterVolume(0.0f);
+	 		   
+	
+	
+	printf ("\n Start of main()\n");
+	printf ("argc = %d\n",argc); 
+	for (int arg = 0; arg < argc; arg++)
+	{
+		printf ("%2d: %s\n",arg,argv[arg]);
+	}
+	printf ("\n");
+	
+	//printf("Hardware=%s\n",Config::GetHardwareName());
+	//printf("GotExtraMemory=%d\n",Config::GotExtraMemory());
+	//printf("Language=%s\n",Config::GetLanguageName());
+	//printf("CD=%d\n",Config::CD());
+	
+	if (Config::GotExtraMemory())
+	{
+		Mem::Manager::sSetUpDebugHeap();
+	}	
+
+	Mem::PushMemProfile("Everything");
+	Mem::PushMemProfile("Initialization");
+
+	
+//	static char	profdata[8192];
+//	snDebugInit();
+//	snProfInit(_4KHZ, profdata, sizeof(profdata));
+
+	if (argc == 2 && strcmp(argv[1],"demo") == 0)
+	{
+		skip_startup = true;
+	}
+	
+	
+	DEBUG_FLASH(0x007f007f);		// magenta
+	
+
+
+#ifdef	__NOPT_ASSERT__
+#ifdef	__PLAT_NGPS__	
+	static u_long128 profdata[2048]; // quadword aligned, can be 2K
+									 // to 64K bytes
+
+	// we now always turn on profiling if running on a TOOL (unless we are on a CD)									 
+	if (Config::GetHardware() == Config::HARDWARE_PS2_DEVSYSTEM && !Config::CD())
+	{
+		snDebugInit();
+		sceSifInitRpc(0);
+		// Load the SNProfil module
+		if(sceSifLoadModule("host0:/usr/local/sce/iop/modules/SNProfil.irx", 0, NULL) < 0)
+		{
+			printf("Can't load SNProfil module, but don't worry, that's just for programmers...\n However, you could get the latest version of usr if you want it\n");
+	//		exit(-1);
+		}
+		else
+		{
+			if(snProfInit(_4KHZ, profdata, sizeof(profdata)) != 0)
+			{
+				printf("Profiler init failed\n"); // see SN_PRF… in LIBSN.H
+			}
+		}	
+	}	
+	// rest of user code follows on from here...
+#endif
+#endif
+
+	Dbg_Message ( "Begin Application" );						 
+	Mth::InitialRand(107482099);
+
+#if 0
+		Mth::InitSinLookupTable();
+#ifdef  __PLAT_NGPS__
+// (Mick)  THis might not be needed with the new libs
+													   
+		// Run through all the code and replace all calls to sinf, cosf
+		// with faster versions that use a lookup table.
+		// Note: This must be done after the call to Mth::InitSinLookupTable(); above,
+		// because that uses the original sinf to generate the lookup table.
+		int *pc = (int*)_code_start;
+		while (pc < (int*)_code_end)
+		{
+
+			int instruction = *pc;
+			if (instruction >> 24 == 0x0c)
+			{
+				int code = (instruction & 0xffffff)<<2;
+				if (code == (int)sinf)
+				{									
+					*pc=(*pc&0xff000000)| ((((uint32)Mth::Kensinf)>>2)&0xffffff);
+				}
+				else if (code == (int)cosf)
+				{
+					*pc=(*pc&0xff000000)| ((((uint32)Mth::Kencosf)>>2)&0xffffff);
+				}
+				else if (code == (int)acosf)
+				{
+					*pc=(*pc&0xff000000)| ((((uint32)Mth::Kenacosf)>>2)&0xffffff);
+				}
+			}
+			++pc;
+	   }
+	   
+#endif				 
+#endif				 
+
+
+#ifdef __NOPT_ASSERT__
+#ifdef  __PLAT_NGPS__
+	Mem::PushMemProfile("(debug only) Log::Init()");
+	Log::Init();
+	Mem::PopMemProfile();
+#endif
+#endif
+
+	
+	while (  true )
+	{	
+		Mem::Manager::sHandle().BottomUpHeap()->PushContext();
+		Mem::Manager::sHandle().PushMemoryMarker(MAINLOOP_MEMMARKER);
+		{
+			/*
+				Important Notes:
+				
+				Please put all module creation/registering/starting code in its proper section.
+				This helps prevents bugs caused by initialization code depending on objects
+				that have not been initialized.
+				
+				There are two stages to initialization. First, object creation (create singleton
+				section). Second, running the object's v_start function (start modules section).
+				Note that the latter doesn't happen when StartModule is called, but after the
+				main loop is entered.
+			
+				It would be a bad idea to make code in any module's constructor depend on initialization 
+				done in another's v_start function.
+			*/
+			
+			// We create the symbol hash table early, as it's kind of used my Music::Init, which I think
+			// is called both at startup (when the scripts are invalid), and later (when they are valid)
+			Script::CreateSymbolHashTable();
+								
+			
+			/***********************************************
+			General startup section
+			************************************************/
+			
+			Mem::PushMemProfile("Hash Item Pool Manager");
+#			if( defined( __PLAT_NGC__ ) || defined( __PLAT_XBOX__ ))
+			Mem::PoolManager::SSetupPool(Mem::PoolManager::vHASH_ITEM_POOL, 12500);	// Mick: increased from 5600 to 10000
+#			else
+			Mem::PoolManager::SSetupPool(Mem::PoolManager::vHASH_ITEM_POOL, 10000);	// Mick: increased from 5600 to 10000
+#			endif // __PLAT_NGC__ || __PLAT_XBOX__
+			Mem::PopMemProfile(/*"Hash Item Pool Manager"*/);
+
+				
+			// I'd prefer for this stuff to happen after the singleton section,
+			// but it won't work that way
+			
+			Spt::SingletonPtr< File::PreMgr> pre_mgr(true);		 // Note, moved here so SkateScript::Init can use it
+			   	
+			/***********************************************
+			Create singleton section
+			************************************************/
+			
+			/*
+				RJM:
+				
+				-SkateMod must come after panelMgr, because it references the panelMgr singleton
+			*/
+			
+			Tmr::Init();
+			
+			Mem::PushMemProfile("File System");
+			File::InstallFileSystem();               
+			Mem::PopMemProfile(/*"File System"*/);
+
+								
+			DEBUG_FLASH(0x007f7f00);		// cyan
+								
+			Mem::PushMemProfile("System Singletons");
+			Spt::SingletonPtr< Obj::CTracker >	obj_tracker( true );
+			Spt::SingletonPtr< Mlp::Manager >	mlp_manager( true );
+			Spt::SingletonPtr< Mdl::Manager >	mdl_manager( true );
+			Spt::SingletonPtr< SIO::Manager >	sio_manager( true );
+			Spt::SingletonPtr< Mc::Manager > 	mc_manager( true );
+			Spt::SingletonPtr< Inp::Manager >	inp_manager( true );
+			Spt::SingletonPtr< Gfx::Manager >	gfx_manager( true );
+			Spt::SingletonPtr< File::CAsyncFilePoll>	async_poll( true );
+			Mem::PopMemProfile(/*"System Singletons"*/);
+
+			DEBUG_FLASH(0x00007f7f);		// yellow
+
+
+			#ifdef	__PLAT_NGPS__
+			NxPs2::InitialiseEngine();
+			Nx::CLoadScreen::sDisplay( "loadscrn", false, false );
+			//Nx::CLoadScreen::sStartLoadingBar(duration);
+			#endif
+
+			
+			// NOTE:  I moved SkateScript::Init() down here, as
+			// we want to display the loading screen before
+			// we load any other files (so it loads as fast as possible)																  
+			// SO, NO SCRIPT FUNCTIONS MAY BE USED BEFORE THIS POINT
+
+			Mem::PushMemProfile("SkateScript::Init()");
+			SkateScript::Init();	 			
+
+			Mem::PushMemProfile("Game Singletons");
+			#ifdef __NOPT_ASSERT__
+			Spt::SingletonPtr< Dbg::CScriptDebugger> script_debugger(true);
+			#endif
+			Mem::PopMemProfile();
+
+#ifdef __PLAT_NGC__
+extern uint8 * RES_gamecube;
+			Spt::SingletonPtr< Script::CScriptCache>			script_cache( true );
+			Script::ParseQB( "scripts\\engine\\gamecube.qb", RES_gamecube );
+
+			if ( ( VIGetTvFormat() == VI_NTSC ) || ( VIGetTvFormat() == VI_MPAL ) )
+			{
+				NsDisplay::Check480P();
+			}
+			else
+			{
+				NsDisplay::Check60Hz();
+			}
+#endif		// __PLAT_NGC__
+			SkateScript::Preload();
+			Mem::PopMemProfile();
+			
+			Mem::PushMemProfile("Game Singletons");
+			Obj::CPathMan::Instance(); // Force creation of CPathMan singleton
+
+#ifndef __PLAT_NGC__
+			Spt::SingletonPtr< Script::CScriptCache>			script_cache( true );
+#endif		// __PLAT_NGC__
+			Spt::SingletonPtr< Ass::CAssMan > 					ass_manager( true );
+			Spt::SingletonPtr< Sfx::CSfxManager > 				sfx_manager( true );			
+			Spt::SingletonPtr< Net::Manager >					net_manager( true );	
+			Spt::SingletonPtr< Obj::CCompositeObjectManager >   composite_object_manager( true );
+			Spt::SingletonPtr< Mdl::Skate >						skate_mod( true );
+			Spt::SingletonPtr< Mdl::FrontEnd > 					front(true);
+			Spt::SingletonPtr< GameNet::Manager > 				gamenet_man( true );
+			Spt::SingletonPtr< Ed::CParkEditor>					grandpas_park_editor(true);
+
+			Spt::SingletonPtr< Front::CScreenElementManager > 	screen_elem_manager(true);
+#if __USE_REPLAYS__
+			Spt::SingletonPtr< Replay::Manager > 				replay_manager( true );
+#endif			
+			Mem::PopMemProfile(/*"Game Singletons"*/);
+
+			DEBUG_FLASH(0x007f7f7f);		// white
+
+			Mem::PushMemProfile("Resgistering and starting modules");
+
+			/***********************************************
+			Setup main loop tasks section
+			************************************************/
+
+			// Note, some managers/modules add their tasks in their constructors
+			// however, these three are hooked up in the main loop, for some reason
+			// and have to provide a global function to get a pointer to
+			// their appropiate tasks
+		
+//			mlp_manager->AddSystemTask ( tgt_manager->GetProcessBackGroundTasks() );
+			mlp_manager->AddSystemTask ( mdl_manager->GetProcessModulesTask() );   // Note: this hooks up the code that starts the modules
+			mlp_manager->AddSystemTask ( sio_manager->GetProcessDevicesTask() );
+			mlp_manager->AddLogicTask  ( inp_manager->GetProcessHandlersTask() );
+		
+			/***********************************************
+			Register modules section
+			************************************************/
+			
+			/* 
+				RJM: 
+				
+				The FrontEnd and Panel Managers must be initialized before the Skate Mod,
+				so that the game can be launched from a startup script without problems
+			
+			*/
+			
+			mdl_manager->RegisterModule ( *script_cache );
+			mdl_manager->RegisterModule ( *grandpas_park_editor );
+			mdl_manager->RegisterModule ( *front );
+			mdl_manager->RegisterModule ( *skate_mod );
+			mdl_manager->RegisterModule ( *async_poll );
+			#ifdef __NOPT_ASSERT__
+			mdl_manager->RegisterModule ( *script_debugger );
+			#endif
+			
+			/***********************************************
+			Start modules section
+			************************************************/
+			
+			mdl_manager->StartModule( *script_cache );
+			mdl_manager->StartModule( *front );
+			mdl_manager->StartModule( *skate_mod );      
+			mdl_manager->StartModule ( *grandpas_park_editor );
+			mdl_manager->StartModule ( *async_poll );
+			
+			Mem::PopMemProfile(/*"Resgistering and starting modules"*/);
+		
+						
+			/***********************************************
+			Main loop section
+			************************************************/
+			
+#ifdef		__USE_PROFILER__
+			Sys::Profiler::sSetUp(2);
+#endif			
+			
+			Mem::PushMemProfile("sStartEngine");
+			Nx::CEngine::sStartEngine();
+			Mem::PopMemProfile(/*"sStartEngine"*/);
+
+			Dbg_Message ( "Starting main loop" );
+// Note to the adventurer:
+// Where does control go now? .... you may well ask
+// The above "modules" have simply been flagged as wanting to start, they have not
+// actually started yet.
+// The code that actually runs them is called ultimately from "service_system" in mainloop.cpp
+// "service_system" iterates over the "system_task_list", and calls all the tasks
+// in that list.  One of those tasks is the task returned by GetProcessModulesTask()
+// which is added to the task list by the line:
+// 			mlp_manager->AddSystemTask ( mdl_manager->GetProcessModulesTask() );
+// above.
+// The GetProcessModulesTask() returns (via some indirection), Mdl::Manager::process_modules
+// and this function iterates over the modules registered with the module manager
+// in part it checks if (current->command == Module::vSTART), and if so, then it calls
+// 					current->v_start_cb();
+// which in the case of the skate module is Mdl::Skate::v_start_cb
+// which does some initialization of the skate module
+// and in the course of that, calls 
+//	Script::RunScript("default_system_startup");
+//	Script::RunScript("StartUp");	<<< but not on the CD version
+//
+// The "Startup" script differs per person during development, but will typically look like:
+//
+//	script startup
+//	    autolaunch level=load_sch game=career
+//	endscript
+//
+// which calls a bunch of commands to ultimately load the level geometry,
+// parse the triggers in the "node array" 
+// and drop in the skater
+// and start skating.
+// phew
+
+			Mem::PopMemProfile(/*Initilization*/);
+			
+			
+			Mem::PushMemProfile("MainLoop");
+			mlp_manager->MainLoop();
+			Mem::PopMemProfile(/*"MainLoop"*/);
+			
+// The following code is theoretical, as we actually never shut down the whole game			
+// On the PS2, we use the script command "LoadExecPS2", which calls "ScriptLoadExecPS2" in cfuncs.cpp
+// which allows us to load another executable.  This would either be a demo or another game
+// or it would be returning to the bootstrap loader.
+			
+			Dbg_Message ( "Ending main loop" );
+			
+#ifdef		__USE_PROFILER__
+			Sys::Profiler::sCloseDown();
+#endif			
+			mdl_manager->StopAllModules();
+			
+			/***********************************************
+			Unregister section
+			************************************************/
+			
+			mdl_manager->UnregisterModule ( *skate_mod );
+			mdl_manager->UnregisterModule ( *front );
+			mdl_manager->UnregisterModule ( *grandpas_park_editor );
+			mdl_manager->UnregisterModule ( *async_poll );
+			#ifdef __NOPT_ASSERT__
+			mdl_manager->UnregisterModule ( *script_debugger );
+			#endif
+			
+			/***********************************************
+			General closedown
+			************************************************/
+			
+			mlp_manager->RemoveAllDisplayTasks();			
+			mlp_manager->RemoveAllLogicTasks();
+			mlp_manager->RemoveAllSystemTasks();
+
+			Dbg_Message ( "End Application" );
+		}
+		Tmr::DeInit();
+		Mem::Manager::sHandle().PopMemoryMarker(MAINLOOP_MEMMARKER);
+		Mem::Manager::sHandle().BottomUpHeap()->PopContext();
+	Mth::Matrix A,B,C;
+	mat_test(A,B,C);				 
+	}
+
+				 
+	return 0;
+}
+
+void nob(Mth::Matrix A);
+
+void mat_test(const Mth::Matrix A,const Mth::Matrix B,Mth::Matrix C)
+{
+
+	Mth::Matrix D;
+	D = A * B;
+}
+
+void nob(Mth::Matrix A)
+{
+	printf ("%f",A[X][X]);
+}
+
+
+
+/*****************************************************************************
+**							                    							**
+*****************************************************************************/
+
+
+
+
+
+
diff --git a/Code/Sk/Modules/FrontEnd/FrontEnd.cpp b/Code/Sk/Modules/FrontEnd/FrontEnd.cpp
new file mode 100644
index 0000000..a46ad7e
--- /dev/null
+++ b/Code/Sk/Modules/FrontEnd/FrontEnd.cpp
@@ -0,0 +1,1056 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			 			                              				**
+**																			**
+**	File name:											                    **
+**																			**
+**	Created by:	    rjm, 9/12/2000											**
+**																			**
+**	Description:									                        **
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC frontend
+// @module frontend | None
+// @subindex Scripting Database
+// @index script | frontend
+                                    
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+
+#include 		   // just for pausing the skater, and for running the "run me now" script
+
+										   
+extern bool run_runmenow;
+
+namespace Mdl
+{
+DefineSingletonClass( FrontEnd, "Frontend module" );
+
+
+FrontEnd::FrontEnd()
+{
+
+	m_logic_task = new Tsk::Task< FrontEnd > ( FrontEnd::s_logic_code, *this, Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_FRONTEND);
+	m_handle_keyboard_task = new Tsk::Task< FrontEnd > ( FrontEnd::s_handle_keyboard_code, *this, Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_HANDLE_KEYBOARD);
+	
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		mp_input_handlers[i] = new Inp::Handler< FrontEnd > ( i, FrontEnd::s_input_logic_code, *this, Tsk::BaseTask::Node::vHANDLER_PRIORITY_FRONTEND_INPUT_LOGIC0);		
+		m_pad_info[i].mState =  NOT_DOWN;
+		m_pad_info[i].mMode = ACTIVE;
+	}
+	m_paused = false;
+
+	m_auto_repeat_time[0] = Tmr::vRESOLUTION * 3 / 10;
+	m_auto_repeat_time[1] = Tmr::vRESOLUTION * 5 / 100;
+	
+	
+	// m_pad_info[1].mState =  NOT_DOWN;
+	
+	// m_pad_info[1].mMode = INACTIVE;
+
+	m_temp_block_pad_input = false;
+
+	m_using_auto_input = false;
+	m_check_for_auto_input = true;
+	m_auto_input_script_id = 0;
+
+	for (int i = 0; i < MAX_BUTTON_EVENT_MAP_ENTRIES; i++)
+	{
+		m_digital_button_event_map[i].mEventType = DEAD_ENTRY;
+	}
+
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+		m_device_server_map[i] = i;
+}
+
+
+
+
+FrontEnd::~FrontEnd()
+{
+	delete m_logic_task;
+	
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		delete mp_input_handlers[i];
+		mp_input_handlers[i] = NULL;
+	}
+}
+
+
+
+
+void FrontEnd::v_start_cb ( void )
+{
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+		Mlp::Manager * mlp_manager = Mlp::Manager::Instance(); 
+		mlp_manager->AddLogicTask ( *m_logic_task );
+		Inp::Manager * inp_manager = Inp::Manager::Instance(); 
+		
+		for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+		{
+			Inp::Handler< FrontEnd >* p_handler = GetInputHandler( i );
+			inp_manager->AddHandler( *p_handler );
+		}
+	Mem::Manager::sHandle().PopContext();	
+}
+
+
+
+
+void FrontEnd::v_stop_cb ( void )
+{
+	m_logic_task->Remove();
+	
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		Inp::Handler< FrontEnd >* p_handler = GetInputHandler( i );
+		p_handler->Remove();
+	}
+}
+
+
+bool FrontEnd::PadsPluggedIn()
+{
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		Dbg_MsgAssert(mp_input_handlers[i],("NULL mp_input_handlers[%d]",i));
+		if (mp_input_handlers[i]->m_Device->IsPluggedIn())
+		{
+			return true;
+		}
+	}
+	return false;		
+}
+
+
+
+void FrontEnd::AddKeyboardHandler( int max_string_length )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance(); 
+
+	m_max_kb_string_length = max_string_length;
+	mlp_manager->AddLogicTask( *m_handle_keyboard_task );
+}
+
+
+
+
+void FrontEnd::RemoveKeyboardHandler( void )
+{
+	m_handle_keyboard_task->Remove();
+}
+
+
+
+
+void FrontEnd::SetAnalogStickActiveForMenus( bool isActive )
+{
+	Spt::SingletonPtr p_manager;
+	p_manager->SetAnalogStickOverride(isActive);
+}
+
+
+
+
+///////////////////////////////////////////
+// (Mick takes responsibility for this)
+// This function JUST pauses the game
+// this mainly consists of pausing all the tasks (for objects, cars, peds), but
+// first it pauses the skate module (for music), and the sio_manager (pad vibration)
+//
+// The pausing of stuff is fairly comvoluted, here we:
+//
+// Pause the PCM music
+// pause the pad vibration
+// pause all messages, but allow future messages to be launched
+// pause all spawned scripts, apart from the current one, and future spawned scritps 
+//
+// pause the skater, but still allow the reading of the pad from script 
+//
+// Overall, a rather mixed bag of things, which might be best refactored
+// but should do for now.....
+//
+// Note:  is_active == true ... implies the "Front End" is active, and the "game" paused
+
+void FrontEnd::PauseGame(bool paused)
+{
+	
+	// if already in the correct "paused" state, then just return
+	if (paused == m_paused)	
+	{
+		return;
+	}
+				
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if (paused)
+	{
+		// make game be paused
+		skate_mod->Pause( );
+		
+		// Ken: Pause the controller vibrations.
+		SIO::Manager * sio_man = SIO::Manager::Instance();
+		sio_man->Pause();
+	}
+	else
+	{
+		// make game be unpaused
+		// Ken: Un-pause the controller vibrations.
+		SIO::Manager * sio_man = SIO::Manager::Instance();
+		sio_man->UnPause();
+	}
+	
+	// Pause currently spawned scripts (any script with PauseGame in it will unpause itself)		
+	Script::PauseSpawnedScripts(paused);		
+	
+	// Pause or unpause all the skaters
+	uint32 NumSkaters = skate_mod->GetNumSkaters();
+	
+	for (uint32 i = 0; i < NumSkaters; ++i)
+	{
+		Obj::CSkater *pSkater = skate_mod->GetSkater(i);
+		if (pSkater) // Hmm, assert instead?
+		{
+			if (paused)
+			{
+				pSkater->Pause();
+			}
+			else
+			{
+				pSkater->UnPause();
+			}
+		}	
+	}	
+	
+	// Pause all composite objects
+	Obj::CCompositeObjectManager::Instance()->Pause(paused);
+	
+
+	// pause (or unpause) any screen elements in process of morphing
+	Front::CScreenElementManager* p_screen_element_manager = Front::CScreenElementManager::Instance();
+	p_screen_element_manager->SetPausedState(paused);
+	
+	m_paused = paused;
+
+	
+	if (m_paused)
+	{
+		// make game be paused
+		Mlp::Manager * mlp_manager = Mlp::Manager::Instance(); 
+		mlp_manager->SetLogicMask(Mlp::Manager::mGAME_OBJECTS | Mlp::Manager::mGFX_MANAGER);
+		Tmr::StoreTimerInfo( );
+	}
+	else
+	{
+		// make game be unpaused
+		Mlp::Manager * mlp_manager = Mlp::Manager::Instance(); 
+		mlp_manager->SetLogicMask(Mlp::Manager::mNONE);		
+//		rw_viewer->DisableMainLogic(false);
+		// unpause music and soundfx:
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		skate_mod->Pause( false );
+		Tmr::RecallTimerInfo( );
+	}
+}
+
+
+
+
+void FrontEnd::AddEntriesToEventButtonMap(Script::CStruct *pParams)
+{
+	#ifdef __PLAT_XBOX__
+	const char *p_array_name = "xbox";
+	#else
+	#ifdef __PLAT_NGC__
+	const char *p_array_name = "gamecube";
+	#else
+	const char *p_array_name = "ps2";
+	#endif
+	#endif
+	
+	// This seems a very hacky way of doing things.
+	// Black and white need to be in position 16 and 17, since they are button indices 16 and 17.
+	#ifdef __PLAT_XBOX__
+	const char *p_button_names[] =
+	{
+		"left_trigger_full",
+		"right_trigger_full",
+		"left_trigger_half",
+		"right_trigger_half",
+		"y",
+		"b",
+		"a",
+		"x",
+		"back",
+		"left_stick_button",
+		"right_stick_button",
+		"null",
+		"null",
+		"null",
+		"null",
+		"null",
+		"black",
+		"white"
+	};
+	#else
+	#ifdef __PLAT_NGC__
+	const char *p_button_names[] =
+	{
+		"left_trigger_full",
+		"right_trigger_full",
+		"left_trigger_half",
+		"right_trigger_half",
+		"y",
+		"x",
+		"a",
+		"b",
+		"null",
+		"z_plus_left_trigger",
+		"z_plus_right_trigger",
+		"null",
+		"null",
+		"null",
+		"null",
+		"null",
+		"null",
+		"null",
+		"z"
+	};
+	#else
+	const char *p_button_names[] =
+	{
+		"left_trigger2",
+		"right_trigger2",
+		"left_trigger1",
+		"right_trigger1",
+		"triangle",
+		"circle",
+		"x",
+		"square",
+		"select",
+		"left_stick_button",
+		"right_stick_button"
+	};
+	#endif
+	#endif
+	
+	// Cleaner way of allowing varying button names array per platform.
+	int num_mapped_buttons = sizeof( p_button_names ) / sizeof( char* );
+
+	uint32 null_crc = Script::GenerateCRC("null");
+	
+	/*
+		Indices:
+		
+		i: into script array
+		j: into button name array
+		x: into map array
+	*/
+	
+	Script::CArray *p_map;
+	if (!pParams->GetArray(p_array_name, &p_map))
+		return;
+
+	for (uint i = 0; i < p_map->GetSize(); i++)
+	{
+		Script::CArray *p_entry = p_map->GetArray(i);
+
+		uint32 button_crc = p_entry->GetChecksum(0);
+		uint32 event_crc = p_entry->GetChecksum(1);
+
+		for (uint j = 0; j < (uint) num_mapped_buttons; j++)
+		{
+			if (button_crc == Script::GenerateCRC(p_button_names[j]) || button_crc == null_crc)
+			{
+				#ifdef	__NOPT_ASSERT__
+				bool removing_entries = (button_crc == null_crc || event_crc == null_crc);
+				#endif
+				int x = 0;
+				for (; x < MAX_BUTTON_EVENT_MAP_ENTRIES; x++)
+				{
+					if (button_crc == null_crc && m_digital_button_event_map[x].mEventType == event_crc)
+					{
+						// in this case, I am removing existing entry (or entries)
+						m_digital_button_event_map[x].mEventType = DEAD_ENTRY;
+					}
+					else if (event_crc == null_crc && m_digital_button_event_map[x].mDigitalButtonIndex == j)
+					{
+						// in this case, I am removing existing entry (or entries)
+						m_digital_button_event_map[x].mEventType = DEAD_ENTRY;
+					}
+					else if (m_digital_button_event_map[x].mEventType == DEAD_ENTRY)
+					{
+						// in this case, I am adding a new entry, so need empty slot
+						m_digital_button_event_map[x].mDigitalButtonIndex = j;
+						m_digital_button_event_map[x].mEventType = event_crc;
+						break;
+					}
+				}
+				Dbg_MsgAssert(x < MAX_BUTTON_EVENT_MAP_ENTRIES || removing_entries, ("out of entries, increase size of MAX_BUTTON_EVENT_MAP_ENTRIES"));
+				
+				break;
+			}
+		}
+	}
+
+
+	/*
+		Paul:
+	
+		'Y' = PS2 'Triangle'. 
+		'X' = PS2 'Circle'.   
+		'A' = PS2 'X'.        
+		'B' = PS2 'Square'.   
+		'Left Trigger pressed down > halfway' = PS2 'L1'. 
+		'Right Trigger pressed down > halfway' = PS2 'R1'.
+		'Left Trigger' = PS2 'L2'.                        
+		'Right Trigger' = PS2 'R2'.                       
+		'Z' plus 'L' = PS2 'Left Stick Button'.  
+		'Z' plus 'R' = PS2 'Right Stick Button'
+		Z button without L & R is PS2 'Select'
+	*/
+
+	/*
+		Dave:
+	
+		Xbox start = start
+		Xbox back = select
+		The 4 xbox color buttons map to the Ps2 buttons in the same location.
+		Black and white buttons aren't mapped at all.
+		Triggers as per Ngc.
+		Left and right stick buttons map directly to Ps2 equivalents
+	*/
+}
+
+
+
+
+void FrontEnd::s_input_logic_code ( const Inp::Handler < FrontEnd >& handler )
+{	   
+	Dbg_AssertType ( &handler, Inp::Handler< FrontEnd > );
+    
+	FrontEnd& mdl = handler.GetData();
+
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	p_tracker->LogTick();
+	
+	for (int count = 0;; count++)
+	{
+		uint32 event_type = 0; 
+		
+		if (count == 0)
+		{
+			if (mdl.m_check_for_auto_input)
+			{
+				mdl.m_using_auto_input = (bool) Script::GetInteger("use_auto_menu_input");
+				if (mdl.m_using_auto_input)
+				{
+					Script::CScript *p_script = Script::SpawnScript("auto_menu_input");
+					#ifdef __NOPT_ASSERT__
+					p_script->SetCommentString("Spawned from FrontEnd::s_input_logic_code");
+					#endif
+					mdl.m_auto_input_script_id = p_script->GetUniqueId();
+				}
+				mdl.m_check_for_auto_input = false;
+			}
+			
+			uint32 timed_event_type = mdl.grab_timed_event_type(handler.m_Input->m_Buttons, handler.m_Index);
+			if (timed_event_type)
+				event_type = timed_event_type;
+		}
+		
+		uint32 mapped_event_type = mdl.turn_mask_into_event_type(handler.m_Input->m_Makes, count, handler.m_Index,handler.m_Input->m_Buttons);
+		if (mapped_event_type)
+			event_type = mapped_event_type;
+		
+		if (event_type && mdl.m_pad_info[handler.m_Index].mMode != INACTIVE && !mdl.m_temp_block_pad_input) 
+		{
+			Script::CStruct event_data;
+			int device_num = handler.m_Device->GetPort();
+			event_data.AddInteger("device_num", device_num );
+			// printf("device_num = %i\n", device_num);
+
+			if (mdl.m_pad_info[handler.m_Index].mMode == ACTIVE)
+			{
+				// get the controller this came from				
+				// event_data.AddInteger("controller", handler.m_Index);
+				event_data.AddInteger("controller", 0);
+				// event_data.AddInteger( "controller", Mdl::FrontEnd::Instance()->GetInputHandlerMapping( device_num ) );
+				// printf( "using controller %i\n", Mdl::FrontEnd::Instance()->GetInputHandlerMapping( device_num ) );
+			}
+			else
+			{
+				// printf("using 0\n");
+				event_data.AddInteger("controller", 0);
+			}
+			
+			//if (handler.m_Index)
+			// printf("firing pad event %s\n", Script::FindChecksumName(event_type));
+	
+			if (p_tracker->LaunchEvent(event_type, Obj::CEvent::vSYSTEM_EVENT, Obj::CEvent::vSYSTEM_EVENT, &event_data))
+			{
+				// will get here whenever menu systems responds to one of above occurences
+	
+				//handler.m_Input->MaskDigitalInput(Inp::Data::mD_ALL);
+				//handler.m_Input->MaskAnalogInput(Inp::Data::mA_ALL);
+			}
+		}
+
+		if (event_type == 0)
+			break;
+	}
+}
+
+
+
+
+void FrontEnd::s_logic_code ( const Tsk::Task< FrontEnd >& task )
+{
+	Dbg_AssertType ( &task, Tsk::Task< FrontEnd > );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+	FrontEnd&	mdl = task.GetData();
+
+	mdl.update(mdl.m_paused);
+	
+	
+#		ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PushContext( 0, 0, 128 ); 	// blue under green = screen element update
+#		endif // __USE_PROFILER__
+	
+	
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	p_screen_elem_man->Update();
+
+
+
+#		ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PopContext(  ); 	
+#		endif // __USE_PROFILER__
+	
+	Mem::Manager::sHandle().PopContext();		
+}
+
+
+void FrontEnd::s_handle_keyboard_code( const Tsk::Task< FrontEnd >& task )
+{
+	int i, num_chars;
+	char makes[256];
+	FrontEnd& mdl = task.GetData();
+		
+	num_chars = SIO::KeyboardRead( makes );
+
+	for( i = 0; i < num_chars; i++ )
+	{
+		if((( makes[i] >= 32 ) && ( makes[i] <= 126 ) ) || ( makes[i] == SIO::vKB_ENTER ) || ( makes[i] == SIO::vKB_BACKSPACE ))
+		{
+			Script::CStruct* pParams;
+			
+			pParams = new Script::CStruct;
+			if( makes[i] == SIO::vKB_ENTER )
+			{
+				pParams->AddChecksum( NONAME, Script::GenerateCRC( "got_enter" ));
+			}
+			else if( makes[i] == SIO::vKB_BACKSPACE )
+			{
+				pParams->AddChecksum( NONAME, Script::GenerateCRC( "got_backspace" ));
+			}
+			else
+			{
+				char text_string[2];
+
+				text_string[0] = makes[i];
+				text_string[1] = '\0';
+				pParams->AddString( "text", text_string );
+			}
+			
+			pParams->AddInteger( "max_length", mdl.m_max_kb_string_length );
+			Script::RunScript( "handle_keyboard_input", pParams );
+			delete pParams;
+		}
+	}
+
+	// Don't let anyone else handle the keypresses
+	SIO::KeyboardClear();
+}
+
+
+/*****************************************************************
+*	This friendly little function gets called every frame. 'game_is_paused'
+*	is true if the game is paused, or if the player is in the front end.
+*
+*	I will probably move this function to MainMod.cpp or some other more
+*	logical place later on.                                                                
+*                                                               
+******************************************************************/
+void FrontEnd::update(bool game_is_paused)
+{	
+	/*
+		===========================================
+		Non-front end Stuff
+		===========================================
+	*/
+
+	// Turn Keybard handler on and off
+
+	#ifdef	__PLAT_NGPS__	
+	if (	game_is_paused										// paused
+		|| 	GameNet::Manager::Instance()->InNetGame()			// or a network game
+		|| Mdl::Skate::Instance()->m_requested_level == 0x9f2bafb7        	// (0x9f2bafb7 === Load_SkateShop) In skateshop
+		|| m_handle_keyboard_task->InList() 					// if the keyboard is on-screen
+		)
+	{
+		SIO::SetKeyboardActive(true);
+	}
+	else
+	{
+		SIO::SetKeyboardActive(false);
+	}
+	#endif
+	
+
+	
+	// Call these functions every frame:
+	Pcm::Update( );	
+
+	Sfx::CSfxManager * sfx_manager = Sfx::CSfxManager::Instance();
+	sfx_manager->Update();
+
+	if ( !game_is_paused )
+	{
+		// Mick: using the default heap here....
+		// as spawned scripts can fill up the front end heap in an unpredictable manner
+		// like with the LA earthquake	
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+
+
+#		ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PushContext( 128, 0, 0 ); 	// red under green = Positional Sounds
+#		endif // __USE_PROFILER__
+
+		sfx_manager->UpdatePositionalSounds( );
+
+#		ifdef __USE_PROFILER__
+		Sys::CPUProfiler->PopContext( ); 	// 
+#		endif // __USE_PROFILER__
+
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PushContext( 128, 128, 128 ); 	// gray under green = spawned scripts
+#	endif // __USE_PROFILER__
+
+	// And call these functions every game frame:
+	Script::UpdateSpawnedScripts();
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->UpdateGameFlow();
+
+	// mick: run the special script I use for command line scripting	
+	if (run_runmenow)
+	{
+		run_runmenow = false;
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		Obj::CSkater *pSkater = skate_mod->GetLocalSkater();						   
+		Script::RunScript("RunMeNow",NULL,pSkater);
+	}
+
+
+#	ifdef __USE_PROFILER__
+	Sys::CPUProfiler->PopContext( ); 	// 
+#	endif // __USE_PROFILER__
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+
+
+
+uint32 FrontEnd::grab_timed_event_type(uint mask, int index)
+{
+    char makes[256];
+	int i, num_chars;
+	uint32 event_type = 0;
+		
+	/*
+		We check for these types of pad presses first, because of the timing element
+	*/
+	if (mask & Inp::Data::mD_LEFT)
+		event_type = Obj::CEvent::TYPE_PAD_LEFT;
+	else if (mask & Inp::Data::mD_RIGHT)
+		event_type = Obj::CEvent::TYPE_PAD_RIGHT;
+	else if (mask & Inp::Data::mD_UP)
+		event_type = Obj::CEvent::TYPE_PAD_UP;
+	else if (mask & Inp::Data::mD_DOWN)
+		event_type = Obj::CEvent::TYPE_PAD_DOWN;	
+	
+	// Let the pad take precedence over the keyboard
+	if(( index == 0 ) && ( event_type == 0 ))
+	{
+		// First check for keyboard events, if there were any
+		num_chars = SIO::KeyboardRead( makes );
+	
+		if( num_chars > 0 )
+		{
+			for( i = 0; i < num_chars; i++ )
+			{
+				if( makes[i] == SIO::vKB_LEFT )
+				{
+					event_type = Obj::CEvent::TYPE_PAD_LEFT;
+					break;
+				}
+				else if( makes[i] == SIO::vKB_RIGHT )
+				{
+					event_type = Obj::CEvent::TYPE_PAD_RIGHT;
+					break;
+				}
+				else if( makes[i] == SIO::vKB_UP )
+				{
+					event_type = Obj::CEvent::TYPE_PAD_UP;
+					break;
+				}
+				else if( makes[i] == SIO::vKB_DOWN )
+				{
+					event_type = Obj::CEvent::TYPE_PAD_DOWN;
+					break;
+				}
+			}
+		}
+	}
+
+	if (m_pad_info[index].mState == NOT_DOWN)
+	{
+		if (event_type)
+		{
+			// The directional pad was freshly pressed, so we will send an event.
+			// Begin next countdown until the pad input "counts" again.
+			m_pad_info[index].mAutorepeatCountdown = m_auto_repeat_time[0];
+			m_pad_info[index].mState = SLOW_REPEAT;
+		}
+	}
+	else if (event_type)
+	{ 		
+		// The directional pad was held down last frame	(and this one)
+		
+		// update countdown	time
+		Tmr::Time frame_time = (Tmr::Time) (Tmr::FrameLength() * Tmr::vRESOLUTION);
+		if (frame_time < m_pad_info[index].mAutorepeatCountdown)
+			m_pad_info[index].mAutorepeatCountdown -= frame_time;
+		else
+			m_pad_info[index].mAutorepeatCountdown = 0;
+
+		if (m_pad_info[index].mAutorepeatCountdown == 0)
+		{
+			// countdown finished, time to fire event, reset countdown time
+			if (m_pad_info[index].mState == SLOW_REPEAT)
+				m_pad_info[index].mState = FAST_REPEAT;
+			m_pad_info[index].mAutorepeatCountdown = m_auto_repeat_time[1];
+		}
+		else
+			// don't fire pad event, countdown hasn't finished
+			event_type = 0;
+	}
+	else
+	{
+		m_pad_info[index].mState = NOT_DOWN;
+	}	
+
+	if (m_using_auto_input && index == 0)
+	{
+		event_type = 0;
+
+		Script::CScript *p_script = Script::GetScriptWithUniqueId(m_auto_input_script_id);
+		if (p_script)
+		{
+			Script::UnpauseSpawnedScript(p_script);
+		}
+		else
+		{
+			m_using_auto_input = false;
+		}		
+	}
+	
+	return event_type;
+}
+
+
+
+
+uint32 FrontEnd::turn_mask_into_event_type(uint mask, int count, int index, uint buttons)
+{
+    for (int i = 0; i < MAX_BUTTON_EVENT_MAP_ENTRIES; i++)
+	{
+		if ((mask & (1< 0 )
+		{
+			for( i = 0; i < num_chars; i++ )
+			{   
+				// If the keyboard menu is showing, don't treat space and enter as X
+				if( makes[i] == SIO::vKB_ESCAPE )
+				{
+					
+					Script::CStruct* pParams;
+
+					pParams= new Script::CStruct;
+					pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "current_menu_anchor" ));
+					if( Obj::ScriptObjectExists( pParams, NULL ) == false )
+					{
+						event_type = Obj::CEvent::TYPE_PAD_START;
+					}
+					else
+					{
+						event_type = Obj::CEvent::TYPE_PAD_BACK;
+					}
+					
+					SIO::KeyboardClear();
+					delete pParams;
+					break;
+				}
+				else if(( makes[i] == SIO::vKB_ENTER ) || ( makes[i] == 32 ))
+				{
+					Script::CStruct* pParams;
+					bool menu_up;
+
+					menu_up = false;
+					pParams = new Script::CStruct;
+					pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "keyboard_anchor" ));
+					
+					// Enter and space act as "choose" only if you're not currently using the on-screen keyboard
+					if( Obj::ScriptObjectExists( pParams, NULL ) == false )
+					{
+						pParams->Clear();
+    					pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "actions_menu" ));
+                        
+                        // only allow enter in actions menu
+                        if( (Obj::ScriptObjectExists( pParams, NULL ) == true) && ( makes[i] == 32 ) )
+    					{
+                            return 0;
+                        }
+                        else
+                        {
+                            pParams->Clear();
+    						pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "current_menu_anchor" ));
+    						if( Obj::ScriptObjectExists( pParams, NULL ))
+    						{
+    							menu_up = true;
+    						}
+    						else
+    						{
+    							pParams->Clear();
+    							pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "dialog_box_anchor" ));
+    							if( Obj::ScriptObjectExists( pParams, NULL ))
+    							{
+    								menu_up = true;
+    							}
+    						}
+    
+    						if( menu_up )
+    						{
+    							event_type = Obj::CEvent::TYPE_PAD_CHOOSE;
+    							SIO::KeyboardClear();
+    						}
+                        }
+					}
+					delete pParams;
+				}
+			}
+		}
+
+		return event_type;
+	}
+	
+    return 0;
+}
+
+
+void FrontEnd::UpdateInputHandlerMappings()
+{
+	Spt::SingletonPtr< Inp::Manager > inp_man;
+	
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		printf("m_device_server_map[%i] = %i\n", i, m_device_server_map[i]);
+		Inp::Handler< FrontEnd >* p_handler = GetInputHandler( i );
+		if ( p_handler )
+		{
+			printf("got a handler\n");
+			inp_man->ReassignHandler( *p_handler, m_device_server_map[i] );
+		}
+	}
+}
+
+int FrontEnd::GetInputHandlerMapping( int device_num )
+{
+	return m_device_server_map[device_num];
+}
+
+
+bool ScriptLaunchMenuScreen(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	uint32 which_menu;
+	if (!pParams->GetChecksum("screen", &which_menu))
+		Dbg_MsgAssert(0, ("need screen=... with LaunchMenuScreen command"));
+
+	// XXX
+	Ryan("requesting game menu from LaunchMenuScreen\n");
+//	FrontEnd* frontend = FrontEnd::Instance();
+//	frontend->RequestMenuScreen(which_menu);
+
+	printf ("STUBBED: LaunchMenuScreen\n");
+	
+	return true;
+}
+
+
+
+
+bool ScriptSetMenuAutoRepeatTimes(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Script::CPair times;
+	pParams->GetPair(NONAME, ×, Script::ASSERT);
+
+	FrontEnd* p_front_end = FrontEnd::Instance();
+	p_front_end->SetAutoRepeatTimes((Tmr::Time) (times.mX * (float) Tmr::vRESOLUTION),
+									(Tmr::Time) (times.mY * (float) Tmr::vRESOLUTION));
+	return true;
+}
+
+
+
+
+bool ScriptSetMenuPadMappings(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Script::CArray *p_map_array;
+	pParams->GetArray(NONAME, &p_map_array, true);
+
+	FrontEnd* p_front_end = FrontEnd::Instance();
+	for (int i = 0; i < 2; i++)
+	{
+		uint32 flag = p_map_array->GetChecksum(i);
+		FrontEnd::EControllerMode mode = FrontEnd::INACTIVE;
+		if (flag == Script::GenerateCRC("inactive"))
+			mode = FrontEnd::INACTIVE;
+		else if (flag == Script::GenerateCRC("active"))
+			mode = FrontEnd::ACTIVE;
+		else if (flag == Script::GenerateCRC("use_as_first"))
+			mode = FrontEnd::MAP_TO_FIRST;
+		else
+			Dbg_MsgAssert(0, ("unknown controller mode"));
+
+		p_front_end->SetControllerMode(i, mode);
+	}
+
+	return true;
+}
+
+
+
+
+bool ScriptSetButtonEventMappings(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	FrontEnd* p_front_end = FrontEnd::Instance();
+	p_front_end->AddEntriesToEventButtonMap(pParams);
+	
+	if (pParams->ContainsFlag("block_menu_input"))
+		p_front_end->SetControllerTempBlock(true);
+	else if (pParams->ContainsFlag("unblock_menu_input"))
+		p_front_end->SetControllerTempBlock(false);
+	
+	return true;
+}
+
+
+// @script bool | SetAnalogStickActiveForMenus | Turn on/off analog simulation of digital buttons,
+// so you can turn them off during certain menus that use analog for other things. 
+// @uparmopt 1 | on or off, 1 or 0
+bool ScriptSetAnalogStickActiveForMenus(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int	active = 1;
+	pParams->GetInteger(NONAME,&active,false);
+	FrontEnd::Instance()->SetAnalogStickActiveForMenus((bool)active);
+	return true;
+}
+
+} // namespace FrontEnd
diff --git a/Code/Sk/Modules/FrontEnd/FrontEnd.h b/Code/Sk/Modules/FrontEnd/FrontEnd.h
new file mode 100644
index 0000000..4720e05
--- /dev/null
+++ b/Code/Sk/Modules/FrontEnd/FrontEnd.h
@@ -0,0 +1,200 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:															        **
+**																			**
+**	File name:												                **
+**																			**
+**	Created: 		09/12/2000	-	rjm										**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __FRONTEND_H
+#define __FRONTEND_H
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class FrontEnd;
+
+class FrontEnd : public  Module
+{
+	
+	DeclareSingletonClass( FrontEnd );
+
+public:
+	enum
+	{ 
+		vMAIN_MENU 			= 0x6249f70e, // "main_menu"
+		vGAME_MENU			= 0x69efa940, // "game_menu"
+		vPARKED_MENU		= 0xab42b78b, // "parked_menu"
+	};
+	
+	enum EControllerMode
+	{
+		INACTIVE,
+		ACTIVE,
+		MAP_TO_FIRST,
+	};
+
+	
+	void 							PauseGame(bool pause);
+	bool 							GamePaused() {return m_paused;}
+	// This function is used to temporarily switch the paused task list on and off. You should reset it to its previous
+	// state afterwards. Use SetActive() whenever you can.
+	void 							SetPausedTasks(bool is_active);
+
+	bool							PadsPluggedIn();
+	void 							UseFirstPadForPausedMenu() {m_use_second_pad_for_paused_menu=false; m_using_one_pad_for_paused_menu=true;}
+	void 							UseSecondPadForPausedMenu() {m_use_second_pad_for_paused_menu=true; m_using_one_pad_for_paused_menu=true;}
+
+	void 							SetAutoRepeatTimes(Tmr::Time kickoff, Tmr::Time repeat) {m_auto_repeat_time[0] = kickoff; m_auto_repeat_time[1] = repeat;}
+	void							GetAutoRepeatTimes(Tmr::Time &kickoff, Tmr::Time &repeat) {kickoff = m_auto_repeat_time[0]; repeat = m_auto_repeat_time[1];}
+	void 							SetControllerMode(int controllerNum, EControllerMode mode) {m_pad_info[controllerNum].mMode = mode;}
+	void 							SetControllerTempBlock(bool tempBlock) {m_temp_block_pad_input = tempBlock;}
+
+	void							AddEntriesToEventButtonMap(Script::CStruct *pParams);
+
+	void 							AddKeyboardHandler( int max_string_length );
+	void 							RemoveKeyboardHandler( void );
+	void 							SetAnalogStickActiveForMenus( bool isActive );
+
+private:
+
+	FrontEnd( void );
+	virtual							~FrontEnd( void );
+
+	void 							update(bool game_is_paused);
+	uint32							grab_timed_event_type(uint mask, int index);
+	uint32							turn_mask_into_event_type(uint mask, int count, int index, uint buttons);
+	
+	enum Commands 
+	{
+        mSELECTUP   			= nBit(0),
+        mSELECTDOWN 			= nBit(1),
+        mXDOWN 					= nBit(2),
+		mTRIANGLEDOWN			= nBit(3),
+		mSTARTDOWN				= nBit(4),
+		mTOGGLE					= nBit(5),
+		mSELECTLEFT				= nBit(6),
+		mSELECTRIGHT			= nBit(7),
+	};
+
+	static	Tsk::Task< FrontEnd >::Code		s_logic_code;
+	static	Tsk::Task< FrontEnd >::Code		s_handle_keyboard_code;
+	static	Tsk::Task< FrontEnd >::Code		s_display_code;
+	static	Inp::Handler< FrontEnd >::Code  s_input_logic_code;
+
+	void							v_start_cb( void );
+	void							v_stop_cb( void );
+    	
+	Tsk::Task< FrontEnd >*			m_logic_task;	
+	Tsk::Task< FrontEnd >*			m_handle_keyboard_task;
+	bool							m_use_second_pad_for_paused_menu;
+	bool							m_using_one_pad_for_paused_menu;
+	
+   	Inp::Handler< FrontEnd >*		mp_input_handlers[SIO::vMAX_DEVICES];
+public:
+	// Used by ScriptAnyControllerPressed in cfuncs.cpp
+	Inp::Handler< FrontEnd >*		GetInputHandler( int index ) { return mp_input_handlers[index]; }
+	void							UpdateInputHandlerMappings();
+	int								GetInputHandlerMapping( int device_num );
+
+	int								m_device_server_map[SIO::vMAX_DEVICES];
+
+private:
+
+	bool							m_paused;
+	bool							m_allowDeactivate;
+
+	// first is "kickoff" time, second is "repeat" time
+	Tmr::Time						m_auto_repeat_time[2];
+	int								m_max_kb_string_length;
+
+	enum EPadState
+	{
+		NOT_DOWN,
+		SLOW_REPEAT,
+		FAST_REPEAT,
+	};
+	
+	struct PadInfo
+	{
+		EPadState					mState;
+		Tmr::Time					mAutorepeatCountdown;
+		EControllerMode				mMode;
+	};
+	
+	PadInfo							m_pad_info[SIO::vMAX_DEVICES];
+	bool							m_temp_block_pad_input;
+
+	enum 
+	{
+		MAX_BUTTON_EVENT_MAP_ENTRIES	= 32,
+		// use in mEventType
+		DEAD_ENTRY						= 0,
+	};
+	
+	struct EventMapEntry
+	{
+		uint						mDigitalButtonIndex;
+		uint32						mEventType;
+	};
+	
+	EventMapEntry					m_digital_button_event_map[32];
+	
+	bool							m_check_for_auto_input;
+	bool							m_using_auto_input;
+	uint32							m_auto_input_script_id;
+};
+
+bool ScriptLaunchMenuScreen(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetMenuAutoRepeatTimes(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetMenuPadMappings(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetButtonEventMappings(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetAnalogStickActiveForMenus(Script::CStruct *pParams, Script::CScript *pScript);
+
+} // namespace FrontEnd
+
+#endif
+
+
diff --git a/Code/Sk/Modules/Skate/BettingGuy.cpp b/Code/Sk/Modules/Skate/BettingGuy.cpp
new file mode 100644
index 0000000..af8aea1
--- /dev/null
+++ b/Code/Sk/Modules/Skate/BettingGuy.cpp
@@ -0,0 +1,575 @@
+// BettingGuy.cpp
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+namespace Game
+{
+
+const int vMINSQUAREDIST		= 1000001;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBettingGuy::CBettingGuy( Script::CStruct* pParams ) 
+	: CGoal( pParams )
+{
+	printf("creating a CBettingGuy\n");
+	Reset();
+	m_shouldMove = true;
+	m_currentMinigame = 0;
+	m_straightWins = 0;
+	m_straightLosses = 0;
+	m_currentDifficulty = 1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBettingGuy::~CBettingGuy()
+{
+	// nothing yet
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::Activate()
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	CGoal* pGoal = pGoalManager->GetGoal( m_currentMinigame );
+	Dbg_Assert( pGoal );
+
+	Script::CStruct* pGoalParams = pGoal->GetParams();
+
+	// get the bet amount
+	int bet_amount;
+	if ( pGoalParams->GetInteger( "bet_amount", &bet_amount, Script::NO_ASSERT ) )
+	{
+		m_betAmount = bet_amount;
+	}
+
+	SetActive();
+	printf("activated!\n");
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBettingGuy::SetActive()
+{
+	// necessary?
+	m_active = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::BetIsActive( uint32 betId )
+{
+	// printf("betid: %x, current: %x\n", betId, m_currentMinigame );
+	return ( betId == m_currentMinigame && IsActive() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::IsActive()
+{
+	return ( m_hasOffered && !m_shouldMove );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::Update()
+{
+	// do we need to pick a new minigame?
+	if ( m_shouldMove )
+	{
+		// printf("I need to move\n");
+		MoveToNewNode();
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::IsExpired()
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::Deactivate( bool force, bool affect_tree )
+{
+	// no more minigame associated with this
+	Reset();
+	m_straightWins = 0;
+	m_straightLosses = 0;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// win function that gets called from a trickscript
+// checks that the calling minigame is the current minigame
+bool CBettingGuy::Win( uint32 goalId )
+{
+	if ( IsActive() && m_currentMinigame == goalId )
+	{
+		// this is the right minigame!
+		return Win();
+	}
+	return false;
+}
+
+// win func that gets called automatically by update endbetattempt func
+bool CBettingGuy::Win()
+{	
+	printf("CBettingGuy::Win\n");
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->AddCash( m_betAmount );
+	Reset();
+
+	RunCallbackScript( vSUCCESS );
+	m_numBetsWon++;
+	m_straightWins++;
+	if ( m_straightWins > 3 && m_currentDifficulty < 3 )
+	{
+		m_currentDifficulty++;
+		m_straightWins = 0;
+	}
+
+	m_straightLosses = 0;
+	m_shouldMove = true;
+
+	SetInactive();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::Lose()
+{
+	printf("CbettingGuy::Lose\n");
+	RunCallbackScript( vFAIL );
+	Reset();
+	m_straightLosses++;
+	if ( m_straightLosses > 3 && m_currentDifficulty > 1 )
+	{
+		m_currentDifficulty--;
+		m_straightLosses = 0;
+	}
+
+	m_straightWins = 0;
+	m_shouldMove = true;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBettingGuy::Reset()
+{
+	m_shouldMove = false;
+	// m_currentMinigame = 0;
+	m_numTries = 0;
+	// m_currentDifficulty = 1;
+	m_hasOffered = false;
+	m_inAttempt = false;
+	m_currentChallenge = 0;
+	m_betAmount = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::MoveToNewNode( bool force )
+{
+	CGoalManager* pGoalManager = Game::GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	uint32 minigameId = pGoalManager->GetRandomBettingMinigame();
+	if ( minigameId == 0 || minigameId == m_currentMinigame )
+		return false;	
+			
+	uint32 node_checksum;
+	Script::CStruct* p_minigame_params = pGoalManager->GetGoal( minigameId )->GetParams();
+	Dbg_Assert( p_minigame_params );
+	p_minigame_params->GetChecksum( "betting_guy_node", &node_checksum, Script::ASSERT );
+
+	if ( force )
+	{
+		// move him!
+		// printf("moving betting guy\n");
+
+		// record his suspend state and turn off suspension
+		Reset();
+		m_currentMinigame = minigameId;
+		SetupParams();
+
+		uint32 betting_guy_id;
+		mp_params->GetChecksum( "betting_guy_id", &betting_guy_id, Script::ASSERT );
+		
+//		Obj::CMovingObject* p_bet_guy = (Obj::CMovingObject*)(Obj::CMovingObject::m_hash_table.GetItem( betting_guy_id ));
+		Obj::CMovingObject* p_bet_guy = (Obj::CMovingObject*)(Obj::CCompositeObjectManager::Instance()->GetObjectByID( betting_guy_id ));
+		
+		Dbg_Assert( p_bet_guy );
+
+// Mick:  removed this, as suspension handled differently under new components system
+//		bool suspend_state = p_bet_guy->m_never_suspend;
+//		p_bet_guy->m_never_suspend = true;
+
+		Script::CStruct* p_temp_params = new Script::CStruct();
+		p_temp_params->AddChecksum( "goal_id", GetGoalId() );
+		p_temp_params->AddChecksum( "new_node", node_checksum );
+		Script::RunScript( "betting_guy_move_to_node", p_temp_params );
+		delete p_temp_params;
+
+		// make sure he's updated and restore his suspension state
+		p_bet_guy->Update();
+		
+		
+//		p_bet_guy->m_never_suspend = suspend_state;
+	
+		mp_params->AddChecksum( "current_minigame", minigameId );
+
+		m_currentNode = node_checksum;
+
+		return true;
+	}
+
+	// printf("got a minigameId of %x\n", minigameId);
+	
+	// get the local skater's position
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+	Dbg_Assert( pSkater );
+	Mth::Vector skater_pos = pSkater->GetPos();
+	
+	// see if the skater is too close to the betting guy
+	Mth::Vector current_pos;
+	if ( m_currentNode )
+	{
+		int current_node = SkateScript::FindNamedNode( m_currentNode );
+		SkateScript::GetPosition( current_node, ¤t_pos );
+	
+		float d = ( skater_pos - current_pos ).LengthSqr();
+		// printf("square distance from the skater to the betting guy: %f\n", d);
+		if ( d < vMINSQUAREDIST )
+		{
+			// printf("the skater is too close to the betting guy\n");
+			return false;
+		}
+	}
+	
+	// try out the destination node	
+	// get the node's position
+	int node = SkateScript::FindNamedNode( node_checksum );
+	Mth::Vector node_pos;
+	SkateScript::GetPosition(node, &node_pos);
+	
+	// get the square of the distance between them
+	float distSqr = ( skater_pos - node_pos ).LengthSqr();
+	
+	// make sure it isn't too close
+	// printf("sqaure distance from skater to next node: %f\n", distSqr);
+	if ( distSqr < vMINSQUAREDIST )
+	{
+		// printf("the skater is too close to the betting guy's destination node\n");
+		return false;
+	}
+	else
+	{			
+		// move him!
+		// printf("moving betting guy\n");
+		Reset();
+		m_currentMinigame = minigameId;
+		SetupParams();
+
+		Script::CStruct* p_temp_params = new Script::CStruct();
+		p_temp_params->AddChecksum( "goal_id", GetGoalId() );
+		p_temp_params->AddChecksum( "new_node", node_checksum );
+		Script::RunScript( "betting_guy_move_to_node", p_temp_params );
+		delete p_temp_params;
+	
+		mp_params->AddChecksum( "current_minigame", minigameId );
+
+		m_currentNode = node_checksum;
+		return true;
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::StartBetAttempt( uint32 goalId )
+{
+	// this acts like a secondary activate
+	if ( IsActive() && m_currentMinigame == goalId && !m_inAttempt && !m_shouldMove )
+	{
+		printf("starting bet attempt\n");
+		m_inAttempt = true;
+	}
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CBettingGuy::EndBetAttempt( uint32 goalId )
+{
+	// this acts like a secondary deactivate
+	if ( IsActive() && m_currentMinigame == goalId && m_inAttempt && !m_shouldMove )
+	{
+		//printf("CBettingGuy::EndBetAttempt\n");
+		// printf("step 2\n");
+		m_inAttempt = false;
+		
+		Script::CStruct* pBetParams = GetBetParams();
+		
+		// check that they haven't exceeded the number of tries
+		int max_attempts;
+		pBetParams->GetInteger( "tries", &max_attempts, Script::ASSERT );
+
+		// we're challenging them to break a record
+		CGoalManager* pGoalManager = GetGoalManager();
+		Dbg_Assert( pGoalManager );
+		Script::CStruct* pMinigameParams = pGoalManager->GetGoal( m_currentMinigame )->GetParams();
+		Dbg_Assert( pMinigameParams );
+		if ( m_currentChallenge )
+		{
+			// printf("step 3\n");
+			int last_attempt;
+			if ( pMinigameParams->GetInteger( "last_attempt", &last_attempt, Script::NO_ASSERT ) )
+			{
+				printf("got a last attempt of %i\n", last_attempt);
+				printf("m_currentChallenge is %i\n", m_currentChallenge);
+				if ( last_attempt >= m_currentChallenge )
+				{						
+					// printf("you won the bet!\n");
+					// beat the bet and call the minigame's bet_success script
+					this->Win();
+					return true;
+				}
+			}
+		}
+		// check that this isn't their last try
+		m_numTries++;
+		if ( m_numTries >= max_attempts )
+		{
+			Lose();
+			return true;
+		}
+		// if it's not a challenge minigame, it's probably a trickspot
+		// minigame.  In that case, we'll be told when the game is won
+		// the the gap script.
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CBettingGuy::GetBetParams()
+{
+	Script::CStruct* p_params = NULL;
+	
+	if ( m_currentMinigame )
+	{		
+		CGoalManager* pGoalManager = GetGoalManager();
+		Dbg_Assert( pGoalManager );
+		
+		CGoal* pGoal = pGoalManager->GetGoal( m_currentMinigame );
+		Dbg_Assert( pGoal );
+
+		Script::CStruct* p_temp = pGoal->GetParams();
+		
+		switch ( m_currentDifficulty )
+		{
+		case 1:
+			p_temp->GetStructure( "bet_easy", &p_params, Script::NO_ASSERT );
+			break;
+		case 2:
+			p_temp->GetStructure( "bet_medium", &p_params, Script::NO_ASSERT );
+			break;
+		case 3:
+			p_temp->GetStructure( "bet_hard", &p_params, Script::NO_ASSERT );
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Betting game had unknown difficulty level" ) );
+		}
+	}
+	return p_params;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBettingGuy::OfferMade()
+{
+	m_hasOffered = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBettingGuy::OfferRefused()
+{
+	m_shouldMove = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CBettingGuy::OfferAccepted()
+{
+}
+
+void CBettingGuy::SetupParams()
+{
+	Script::CStruct* pBetParams = GetBetParams();
+	Dbg_Assert( pBetParams );
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	Script::CStruct* p_minigameParams = pGoalManager->GetGoal( m_currentMinigame )->GetParams();
+	Dbg_Assert( p_minigameParams );
+	
+	pBetParams->GetInteger( CRCD(0x1d7750fa,"bet_amount"), &m_betAmount, Script::ASSERT );
+	
+	mp_params->RemoveComponent( CRCD(0x1d7750fa,"bet_amount") );
+	mp_params->AddInteger( CRCD(0x1d7750fa,"bet_amount"), m_betAmount );
+	
+	mp_params->RemoveComponent( CRCD(0xf3d0c702,"current_challenge") );
+	if ( pBetParams->GetInteger( CRCD(0x1931ac3b,"bet_challenge"), &m_currentChallenge, Script::NO_ASSERT ) )
+	{
+		// m_currentChallenge = current_challenge;
+		printf("got a challenge: %i\n", m_currentChallenge);
+		mp_params->AddInteger( CRCD(0xf3d0c702,"current_challenge"), m_currentChallenge );
+	}
+
+	int max_attempts;
+	pBetParams->GetInteger( CRCD(0x62396931,"tries"), &max_attempts, Script::ASSERT );
+	mp_params->RemoveComponent( "num_tries" );
+	mp_params->AddInteger( CRCD(0x25bad847,"num_tries"), max_attempts );
+	
+	mp_params->RemoveComponent( CRCD(0x6922090,"bet_unit") );
+	mp_params->RemoveComponent( CRCD(0xd41aac2a,"bet_action") );
+	mp_params->RemoveComponent( CRCD(0xa1617634,"location") );
+	// get the trick string if necessary
+	if ( p_minigameParams->ContainsFlag( CRCD(0xbc167894,"trickspot") ) )
+	{
+		Script::CArray* p_keyCombos;
+		pBetParams->GetArray( CRCD(0x79704516,"key_combos"), &p_keyCombos, Script::ASSERT );
+		Dbg_MsgAssert( p_keyCombos->GetType() == ESYMBOLTYPE_NAME, ( "Betting guy key_combos array has bad type" ) );
+		int key_combo_index = GetRandomIndexFromKeyCombos( p_keyCombos );
+		uint32 key_combo = p_keyCombos->GetChecksum( key_combo_index );
+	
+		uint32 trick_name_checksum;
+		// get the global trick mapping
+		Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+		Obj::CSkaterProfile* p_SkaterProfile = skate_mod->GetCurrentProfile();
+		Script::CStruct* p_trickMappings = p_SkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+		p_trickMappings->GetChecksum( key_combo, &trick_name_checksum, Script::ASSERT );
+	
+		const char* trick_text;
+		if ( trick_name_checksum )
+		{
+			Script::CStruct* p_trick_structure = Script::GetStructure( trick_name_checksum, Script::ASSERT );
+			Script::CStruct* p_trick_params;
+			p_trick_structure->GetStructure( CRCD(0x7031f10c,"Params"), &p_trick_params, Script::ASSERT );
+			p_trick_params->GetLocalText( CRCD(0xa1dc81f9,"Name"), &trick_text, Script::ASSERT );
+		}
+		mp_params->AddString( CRCD(0xd41aac2a,"bet_action"), trick_text );
+	
+		const char* location;
+		p_minigameParams->GetString( CRCD(0xa1617634,"location"), &location, Script::ASSERT );
+		mp_params->AddString( CRCD(0xa1617634,"location"), location );
+	}
+	else 
+	{
+		const char* bet_action;
+		// the bet action is global
+		p_minigameParams->GetString( CRCD(0xd41aac2a,"bet_action"), &bet_action, Script::ASSERT );
+		mp_params->AddString( CRCD(0xd41aac2a,"bet_action"), bet_action );
+		const char* bet_unit;
+		p_minigameParams->GetString( CRCD(0x6922090,"bet_unit"), &bet_unit, Script::ASSERT );
+		mp_params->AddString( CRCD(0x6922090,"bet_unit"), bet_unit );
+	}
+}
+
+}
diff --git a/Code/Sk/Modules/Skate/BettingGuy.h b/Code/Sk/Modules/Skate/BettingGuy.h
new file mode 100644
index 0000000..6c83a87
--- /dev/null
+++ b/Code/Sk/Modules/Skate/BettingGuy.h
@@ -0,0 +1,60 @@
+// BettingGuy.h
+#ifndef _SK_MODULES_SKATE_BETTINGGUY_H_
+#define _SK_MODULES_SKATE_BETTINGGUY_H_
+
+#include 
+
+#include 
+
+namespace Game
+{
+
+class CBettingGuy : public CGoal
+{
+public:
+						CBettingGuy( Script::CStruct* pParams );
+	virtual				~CBettingGuy();
+
+	bool				IsActive();
+	bool				ShouldUseTimer() { return false; }
+	bool				CountAsActive() { return false; }
+	bool				Activate();
+	void				SetActive();
+	bool				Update();
+	bool				MoveToNewNode( bool force = false );
+	bool				IsExpired();
+	bool				Deactivate( bool force = false, bool affect_tree = true );
+	bool				Win();
+	bool				Win( uint32 goalId );
+	bool				Lose();
+	void				Reset();
+
+	bool				EndBetAttempt( uint32 goalId );
+	bool				StartBetAttempt( uint32 goalId );
+	bool				BetIsActive( uint32 goalId );
+
+	void				OfferMade();
+	void				OfferRefused();
+	void				OfferAccepted();
+
+	Script::CStruct*	GetBetParams();
+	void				SetupParams();
+
+protected:
+	bool				m_shouldMove;
+	int					m_numBetsWon;
+	uint32				m_currentMinigame;
+	uint32				m_currentNode;
+	int					m_currentDifficulty;
+	bool				m_hasOffered;
+	bool				m_inAttempt;
+	int					m_numTries;
+	int					m_currentChallenge;
+	int					m_betAmount;
+	int					m_straightLosses;
+	int 				m_straightWins;
+};
+
+}	// namespace Game
+
+#endif
diff --git a/Code/Sk/Modules/Skate/CATGoal.cpp b/Code/Sk/Modules/Skate/CATGoal.cpp
new file mode 100644
index 0000000..207c114
--- /dev/null
+++ b/Code/Sk/Modules/Skate/CATGoal.cpp
@@ -0,0 +1,49 @@
+// Create-A-Trick goal!
+
+#include 
+#include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCatGoal::CCatGoal( Script::CStruct* pParams ) : CGoal( pParams )
+{
+	// uh...
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCatGoal::~CCatGoal()
+{
+	// hmm...
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CCatGoal::Activate()
+{
+	return CGoal::Activate();
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCatGoal::Deactivate( bool force, bool affect_tree )
+{
+	return CGoal::Deactivate( force, affect_tree );
+}
+
+
+}	// namespace game
diff --git a/Code/Sk/Modules/Skate/CATGoal.h b/Code/Sk/Modules/Skate/CATGoal.h
new file mode 100644
index 0000000..2dfb9bc
--- /dev/null
+++ b/Code/Sk/Modules/Skate/CATGoal.h
@@ -0,0 +1,29 @@
+// Create-A-Trick goal!
+
+#ifndef __SK_MODULES_SKATE_CATGOAL_H__
+#define __SK_MODULES_SKATE_CATGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+class CCatGoal : public CGoal
+{
+
+public:
+						CCatGoal( Script::CStruct* pParams );
+	virtual				~CCatGoal();
+
+	bool				Activate();
+	bool				Deactivate( bool force = false, bool affect_tree = true );
+protected:
+};
+
+}
+
+#endif // #ifndef __SK_MODULES_SKATE_FINDGAPSGOAL_H__
diff --git a/Code/Sk/Modules/Skate/CompetitionGoal.cpp b/Code/Sk/Modules/Skate/CompetitionGoal.cpp
new file mode 100644
index 0000000..73eb1ee
--- /dev/null
+++ b/Code/Sk/Modules/Skate/CompetitionGoal.cpp
@@ -0,0 +1,370 @@
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCompetitionGoal::CCompetitionGoal( Script::CStruct* pParams )
+	: CGoal( pParams )
+{
+	// uhh...necessary?
+	m_compPaused = false;
+	m_gotGold = m_gotSilver = m_gotBronze = false;
+	m_rewardGiven = 0;
+
+	m_winScoreRecord = vINT_MAX;
+
+	m_endRunType = vENDOFRUN;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CCompetitionGoal::~CCompetitionGoal()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::Expire()
+{
+	// don't deactivate the goal!
+	RunCallbackScript( Game::vEXPIRED );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CCompetitionGoal::IsActive()
+{
+	return m_active;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::SetActive()
+{
+	// printf("setting to active\n");
+	m_active = true;
+	m_compPaused = false;
+	m_paused = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::SetInactive()
+{
+	m_active = false;
+	m_compPaused = false;
+	m_paused = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CCompetitionGoal::HasWonGoal()
+{
+	return m_isbeat;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::MarkBeaten()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Mdl::CCompetition* pComp = skate_mod->GetCompetition();
+	
+	if ( pComp->PlaceIs( 1 ) )
+	{
+		m_gotGold = true;
+		m_gotSilver = true;
+		m_gotBronze = true;
+	}
+	else if ( pComp->PlaceIs( 2 ) )
+	{
+		m_gotSilver = true;
+		m_gotBronze = true;
+	}
+	else if ( pComp->PlaceIs( 3 ) )
+	{
+		m_gotBronze = true;
+	}
+
+	// m_gotGold = true;
+	// m_gotSilver = true;
+	// m_gotBronze = true;
+	m_isbeat = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CCompetitionGoal::UnBeatGoal()
+{
+	m_isbeat = false;
+	m_gotBronze = false;
+	m_gotSilver = false;
+	if ( HasWonGoal() )
+	{
+		m_gotGold = false;
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CCompetitionGoal::Win()
+{
+	mp_params->AddChecksum( NONAME, CRCD( 0xc309cad1, "just_won_goal" ) );
+	RunCallbackScript( Game::vDEACTIVATE );
+	mp_params->RemoveFlag( CRCD( 0xc309cad1, "just_won_goal" ) );
+	SetInactive();
+	
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	
+	if ( !gamenet_man->InNetGame() )
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		Mdl::CCompetition* pComp = skate_mod->GetCompetition();
+		
+		int place = 999;
+		if ( pComp->PlaceIs( 1 ) )
+			place = 1;
+		else if ( pComp->PlaceIs( 2 ) )
+			place = 2;
+		else if ( pComp->PlaceIs( 3 ) )
+			place = 3;
+
+		if ( place < m_winScoreRecord )
+		{
+			mp_params->AddInteger( "win_record", place );
+			m_winScoreRecord = place;
+		}
+	}
+
+#ifdef __NOPT_ASSERT__
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+#endif		// __NOPT_ASSERT__
+		
+	Script::CStruct* p_reward_params = new Script::CStruct();
+	
+	if ( !HasWonGoal() )
+	{
+		// don't add cash
+		Script::CStruct* pTempStruct = new Script::CStruct();
+		GetRewardsStructure( pTempStruct, true );
+		p_reward_params->AppendStructure( pTempStruct );
+		delete pTempStruct;
+		UnlockRewardGoals();
+	}
+	else
+		mp_params->RemoveComponent( "reward_params" );
+
+	// award any cash
+	// AwardCash( p_reward_params );
+
+	// mp_params->RemoveComponent( "reward_params" );
+	mp_params->AddStructure( "reward_params", p_reward_params );
+	RunCallbackScript( Game::vSUCCESS );
+	
+	delete p_reward_params;
+
+	// now it's safe to mark as beaten
+	if ( !HasWonGoal() )
+		MarkBeaten();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::AwardCash( Script::CStruct* p_reward_params )
+{
+#ifdef __NOPT_ASSERT__
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+#endif		// __NOPT_ASSERT__
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Mdl::CCompetition* pComp = skate_mod->GetCompetition();
+	
+	// figure any cash reward
+	if ( pComp->PlaceIs( 1 ) || pComp->PlaceIs( 2 ) || pComp->PlaceIs( 3 ) )
+	{		
+		int total_reward;
+		int current_reward = 0;
+		if ( mp_params->GetInteger( "reward_cash", &total_reward, Script::NO_ASSERT ) )
+		{			
+			if ( pComp->PlaceIs( 1 ) && !m_gotGold )
+			{
+				current_reward = total_reward - m_rewardGiven;
+				m_gotGold = true;
+				m_gotSilver = true;
+				m_gotBronze = true;
+			}
+			else if ( pComp->PlaceIs( 2 ) && !m_gotSilver )
+			{
+				current_reward = total_reward * 2 / 3;
+				if ( m_gotBronze )
+					current_reward -= total_reward / 3;
+
+				m_gotSilver = true;
+				m_gotBronze = true;				
+
+			}
+			else if ( pComp->PlaceIs( 3 ) && !m_gotBronze )
+			{
+				current_reward = total_reward / 3;
+				m_gotBronze = true;
+			}
+
+			if ( current_reward > 0 )
+			{
+				// pGoalManager->AddCash( current_reward );
+				// Script::RunScript( "goal_got_cash" );
+				p_reward_params->AddInteger( "cash", current_reward );
+				m_rewardGiven += current_reward;
+			}
+		}
+	}   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::PauseCompetition()
+{
+	m_compPaused = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::UnPauseCompetition()
+{
+	m_compPaused = false;
+	m_endRunCalled = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CCompetitionGoal::IsPaused()
+{
+	return ( m_paused || m_compPaused );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::LoadSaveData( Script::CStruct* pFlags )
+{
+	int gotBronze;
+	pFlags->GetInteger( "gotBronze", &gotBronze, Script::NO_ASSERT );
+	if ( gotBronze != 0 )
+		m_gotBronze = true;
+
+	int gotSilver;
+	pFlags->GetInteger( "gotSilver", &gotSilver, Script::NO_ASSERT );
+	if ( gotSilver != 0 )
+		m_gotBronze = true;
+
+	int gotGold;
+	pFlags->GetInteger( "gotGold", &gotGold, Script::NO_ASSERT );
+	if ( gotGold != 0 )
+		m_gotBronze = true;
+
+	int reward_given;
+	pFlags->GetInteger( "reward_given", &reward_given, Script::NO_ASSERT );
+	m_rewardGiven = reward_given;
+
+	int record;
+	if ( pFlags->GetInteger( "win_record", &record, Script::NO_ASSERT ) )
+		m_winScoreRecord = record;
+	
+	CGoal::LoadSaveData( pFlags );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CCompetitionGoal::GetSaveData( Script::CStruct* pFlags )
+{
+	CGoal::GetSaveData( pFlags );
+	
+	if ( m_gotBronze )
+		pFlags->AddInteger( "gotBronze", 1 );
+	else
+		pFlags->AddInteger( "gotBronze", 0 );
+
+	if ( m_gotSilver )
+		pFlags->AddChecksum( "gotSilver", 1 );
+	else
+		pFlags->AddChecksum( "gotSilver", 0 );
+
+	if ( m_gotGold )
+		pFlags->AddChecksum( "gotGold", 1 );
+	else
+		pFlags->AddChecksum( "gotGold", 0 );
+
+	pFlags->AddInteger( "win_record", m_winScoreRecord );
+	pFlags->AddInteger( "reward_given", m_rewardGiven );
+}
+
+}
diff --git a/Code/Sk/Modules/Skate/CompetitionGoal.h b/Code/Sk/Modules/Skate/CompetitionGoal.h
new file mode 100644
index 0000000..bc4bc13
--- /dev/null
+++ b/Code/Sk/Modules/Skate/CompetitionGoal.h
@@ -0,0 +1,51 @@
+#ifndef __SK_MODULES_SKATE_COMPETITIONGOAL_H__
+#define __SK_MODULES_SKATE_COMPETITIONGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+
+class CCompetitionGoal : public CGoal
+{
+
+public:
+					CCompetitionGoal( Script::CStruct* pParams );
+	virtual			~CCompetitionGoal();
+
+	void			Expire();
+	bool			IsActive();
+	void			SetActive();
+	void 			SetInactive();
+
+	void			LoadSaveData( Script::CStruct* pFlags );
+	void			GetSaveData( Script::CStruct* pFlags );
+
+	bool			Win();
+	bool			HasWonGoal();
+	void			MarkBeaten();
+	bool			UnBeatGoal();
+
+	void			AwardCash( Script::CStruct* p_reward_params );
+
+	void			PauseCompetition();
+	void			UnPauseCompetition();
+
+	bool			IsPaused();
+protected:
+
+	bool			m_compPaused;
+	bool			m_gotGold;
+	bool			m_gotSilver;
+	bool			m_gotBronze;
+	int				m_rewardGiven;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Modules/Skate/CreateATrick.cpp b/Code/Sk/Modules/Skate/CreateATrick.cpp
new file mode 100644
index 0000000..c914f3d
--- /dev/null
+++ b/Code/Sk/Modules/Skate/CreateATrick.cpp
@@ -0,0 +1,795 @@
+// CreateATrick.cpp
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCreateATrick::CCreateATrick() 
+	
+{
+	printf("creating a CCreateATrick\n");
+
+    // Other Params
+    Script::CStruct* p_other_params = Script::GetStructure( "Default_CAT_other_info", Script::ASSERT );
+    mp_other_params = new Script::CStruct();
+    mp_other_params->AppendStructure( p_other_params );
+    
+    // Rotations
+    Script::CArray* p_rotations;
+    p_rotations = Script::GetArray( "Default_CAT_rotation_info", Script::ASSERT );
+
+    mp_rotations = new Script::CArray();
+    mp_rotations->SetSizeAndType( vMAX_ROTATIONS, ESYMBOLTYPE_STRUCTURE );
+    Script::CopyArray( mp_rotations, p_rotations );
+    
+
+    // Animations
+    Script::CArray* p_animations;
+    p_animations = Script::GetArray( "Default_CAT_animation_info", Script::ASSERT );
+
+    mp_animations = new Script::CArray();
+    mp_animations->SetSizeAndType( vMAX_ANIMATIONS, ESYMBOLTYPE_STRUCTURE );
+    Script::CopyArray( mp_animations, p_animations );
+    
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCreateATrick::~CCreateATrick()
+{
+	if ( mp_other_params )
+    {
+        delete mp_other_params;
+    }
+    if ( mp_rotations )
+    {
+        Script::CleanUpArray( mp_rotations );
+        delete mp_rotations;
+    }
+    if ( mp_animations )
+    {
+        Script::CleanUpArray( mp_animations );
+        delete mp_animations;
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCreateATrickParams | Gets the values for rotations and animations of a particular skater
+bool ScriptGetCreateATrickParams(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    int trick_index;
+
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+	
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+        Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[trick_index];
+    	Dbg_Assert( pCreatedTrick );
+    
+        //Other params
+        pScript->GetParams()->AddStructure( "other_params", pCreatedTrick->mp_other_params );
+        
+        //Rotation params
+        pScript->GetParams()->AddArray( "rotation_info", pCreatedTrick->mp_rotations );
+        
+        //Animation params
+        pScript->GetParams()->AddArray( "animation_info", pCreatedTrick->mp_animations );
+        
+        return true;
+    }
+    return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCreateATrickOtherParams | Gets the values for trick name, score, etc.
+bool ScriptGetCreateATrickOtherParams(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    int trick_index;
+
+	Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+        Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[trick_index];
+    	Dbg_Assert( pCreatedTrick );
+        
+        //Other params
+        pScript->GetParams()->AddStructure( "other_params", pCreatedTrick->mp_other_params );
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCreateATrickRotations | Gets the values for rotations
+bool ScriptGetCreateATrickRotations(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    int trick_index;
+
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+        Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[trick_index];
+    	Dbg_Assert( pCreatedTrick );
+    
+        //Rotation params
+        pScript->GetParams()->AddArray( "rotation_info", pCreatedTrick->mp_rotations );
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCreateATrickAnimations | Gets the values for animations
+bool ScriptGetCreateATrickAnimations(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    int trick_index;
+
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+        Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[trick_index];
+    	Dbg_Assert( pCreatedTrick );
+    
+        //Animation params
+        pScript->GetParams()->AddArray( "animation_info", pCreatedTrick->mp_animations );
+    
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetCreateATrickParams | Sets the values for rotations and animations of a particular skater
+bool ScriptSetCreateATrickParams(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    int trick_index;
+    
+    Script::CStruct *p_other_params;
+    Script::CArray *p_rotation_info;
+    Script::CArray *p_animation_info;
+
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+    
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+        //Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[trick_index];
+    	//Dbg_Assert( pCreatedTrick );
+        
+        // make sure the skater actually has created tricks before we try to update them.
+        if ( !(pSkater->m_created_trick[1]) )
+        {
+            printf("Skater has no created tricks!!\n");
+            return false;
+        }
+        else
+        {
+            //Script::PrintContents( pSkater->m_created_trick[trick_index]->mp_other_params );
+        }
+        //Dbg_Assert( pSkater->m_created_trick[1] );
+    
+        //Params
+        if(pParams->GetStructure( "other_params", &p_other_params, Script::ASSERT ))
+        {
+            printf("Storing param info %i\n", trick_index);
+            pSkater->m_created_trick[trick_index]->mp_other_params->AppendStructure( p_other_params );
+        }
+        
+        //Rotations
+        if(pParams->GetArray( "rotation_info", &p_rotation_info, Script::ASSERT ))
+        {
+            printf("Storing rotation info %i\n", trick_index);
+            Script::CopyArray( pSkater->m_created_trick[trick_index]->mp_rotations, p_rotation_info );
+        }
+    
+        //Animations
+        if(pParams->GetArray( "animation_info", &p_animation_info, Script::ASSERT ))
+        {
+            printf("Storing animation info %i\n", trick_index);
+            Script::CopyArray( pSkater->m_created_trick[trick_index]->mp_animations, p_animation_info );
+        }
+        
+            
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetCreateATrickOtherParams | Sets name, score, etc.
+bool ScriptSetCreateATrickOtherParams(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    
+    int trick_index;
+    
+    Script::CStruct *p_other_params;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+	
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+    
+        Dbg_Assert( pSkater->m_created_trick[trick_index] );
+        
+        //Params
+        if(pParams->GetStructure( "other_params", &p_other_params, Script::ASSERT ))
+        {
+            printf("Storing param info %i\n", trick_index);
+            // pSkater->m_created_trick[trick_index]->mp_other_params->Clear();
+            pSkater->m_created_trick[trick_index]->mp_other_params->AppendStructure( p_other_params );
+            //Script::PrintContents(pSkater->m_created_trick[trick_index]->mp_other_params);
+        }
+    
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetCreateATrickRotations | Sets the values for rotations
+bool ScriptSetCreateATrickRotations(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    int trick_index;
+    
+    Script::CArray *p_rotation_info;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+    
+        Dbg_Assert( pSkater->m_created_trick[trick_index] );
+        
+        //Rotations
+        if(pParams->GetArray( "rotation_info", &p_rotation_info, Script::ASSERT ))
+        {
+            printf("Storing rotation info %i\n", trick_index);
+            Script::CopyArray( pSkater->m_created_trick[trick_index]->mp_rotations, p_rotation_info );
+        }
+    
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetCreateATrickAnimations | Sets the values for rotations
+bool ScriptSetCreateATrickAnimations(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+    int trick_index;
+    
+    Script::CArray *p_animation_info;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger( "trick_index", &trick_index, Script::ASSERT );
+        
+        Dbg_Assert( pSkater->m_created_trick[trick_index] );
+        
+        //Animations
+        if(pParams->GetArray( "animation_info", &p_animation_info, Script::ASSERT ))
+        {
+            printf("Storing animation info %i\n", trick_index);
+            Script::CopyArray( pSkater->m_created_trick[trick_index]->mp_animations, p_animation_info );
+        }
+    
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetNumAnims | 
+bool ScriptCAT_SetNumAnims(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        pSkater->m_num_cat_animations_on = value;
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetNumAnims | 
+bool ScriptCAT_GetNumAnims(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pScript->GetParams()->AddInteger( "num_animations_on", pSkater->m_num_cat_animations_on );
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetAnimsDone | 
+bool ScriptCAT_SetAnimsDone(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        if ( value == 0 )
+        {
+            pSkater->m_cat_animations_done = false;
+        }
+        else
+        {
+            pSkater->m_cat_animations_done = true;
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetAnimsDone | 
+bool ScriptCAT_GetAnimsDone(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        if ( pSkater->m_cat_animations_done )
+        {
+            pScript->GetParams()->AddInteger( "cat_animations_done", 1 );
+        }
+        else
+        {
+            pScript->GetParams()->AddInteger( "cat_animations_done", 0 );
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetRotsDone | 
+bool ScriptCAT_SetRotsDone(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        if ( value == 0 )
+        {
+            pSkater->m_cat_rotations_done = false;
+        }
+        else
+        {
+            pSkater->m_cat_rotations_done = true;
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetRotsDone | 
+bool ScriptCAT_GetRotsDone(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        if ( pSkater->m_cat_rotations_done )
+        {
+            pScript->GetParams()->AddInteger( "cat_rotations_done", 1 );
+        }
+        else
+        {
+            pScript->GetParams()->AddInteger( "cat_rotations_done", 0 );
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetBailDone | 
+bool ScriptCAT_SetBailDone(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        if ( value == 0 )
+        {
+            pSkater->m_cat_bail_done = false;
+        }
+        else
+        {
+            pSkater->m_cat_bail_done = true;
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetBailDone | 
+bool ScriptCAT_GetBailDone(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        if ( pSkater->m_cat_bail_done )
+        {
+            pScript->GetParams()->AddInteger( "bailtimescriptdone", 1 );
+        }
+        else
+        {
+            pScript->GetParams()->AddInteger( "bailtimescriptdone", 0 );
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetFlipSkater | 
+bool ScriptCAT_SetFlipSkater(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        if ( value == 0 )
+        {
+            pSkater->m_cat_flip_skater_180 = false;
+        }
+        else
+        {
+            pSkater->m_cat_flip_skater_180 = true;
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetFlipSkater | 
+bool ScriptCAT_GetFlipSkater(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        if ( pSkater->m_cat_flip_skater_180 )
+        {
+            pScript->GetParams()->AddInteger( "cat_flip_skater_180", 1 );
+        }
+        else
+        {
+            pScript->GetParams()->AddInteger( "cat_flip_skater_180", 0 );
+        }
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetHoldTime | 
+bool ScriptCAT_SetHoldTime(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    float value;
+
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetFloat(NONAME, &value, Script::ASSERT );
+        pSkater->m_cat_hold_time = value;
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetHoldTime | 
+bool ScriptCAT_GetHoldTime(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pScript->GetParams()->AddFloat( "cat_hold_time", pSkater->m_cat_hold_time );
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetTotalY | 
+bool ScriptCAT_SetTotalY(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        pSkater->m_cat_total_y = value;
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetTotalY | 
+bool ScriptCAT_GetTotalY(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pScript->GetParams()->AddInteger( "total_Y_angle", pSkater->m_cat_total_y );
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetTotalX | 
+bool ScriptCAT_SetTotalX(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        pSkater->m_cat_total_x = value;
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetTotalX | 
+bool ScriptCAT_GetTotalX(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pScript->GetParams()->AddInteger( "total_X_angle", pSkater->m_cat_total_x );
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_SetTotalZ | 
+bool ScriptCAT_SetTotalZ(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    int value;
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pParams->GetInteger(NONAME, &value, Script::ASSERT );
+        pSkater->m_cat_total_z = value;
+        
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptCAT_GetTotalZ | 
+bool ScriptCAT_GetTotalZ(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    
+    Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+    if ( pSkater )
+    {
+        pScript->GetParams()->AddInteger( "total_Z_angle", pSkater->m_cat_total_z );
+        return true;
+    }
+    return false;
+}
+
+ 
+}
diff --git a/Code/Sk/Modules/Skate/CreateATrick.h b/Code/Sk/Modules/Skate/CreateATrick.h
new file mode 100644
index 0000000..e47503f
--- /dev/null
+++ b/Code/Sk/Modules/Skate/CreateATrick.h
@@ -0,0 +1,84 @@
+
+// CreateATrick.h
+#ifndef _SK_MODULES_SKATE_CREATEATRICK_H_
+#define _SK_MODULES_SKATE_CREATEATRICK_H_
+
+#include 
+
+namespace Script
+{
+    class CStruct;
+}
+
+namespace Game
+{
+
+enum
+{
+    vMAX_CREATED_TRICKS=11,
+    vMAX_ROTATIONS=6,
+    vMAX_ANIMATIONS=6
+};
+
+class CCreateATrick
+{
+public:
+						CCreateATrick();
+	virtual				~CCreateATrick();
+
+    //char*   m_name;
+	//int     m_score;
+    //bool    m_rotateafter;
+
+    Script::CStruct *mp_other_params;
+    Script::CArray *mp_rotations;
+    Script::CArray *mp_animations;
+
+};
+
+// Get
+bool ScriptGetCreateATrickParams(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetCreateATrickOtherParams(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptGetCreateATrickRotations(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptGetCreateATrickAnimations(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+// Set
+bool ScriptSetCreateATrickParams(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetCreateATrickOtherParams(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptSetCreateATrickRotations(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptSetCreateATrickAnimations(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+// other scripts
+bool ScriptCAT_SetNumAnims(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetNumAnims(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetAnimsDone(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetAnimsDone(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetRotsDone(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetRotsDone(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetBailDone(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetBailDone(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetFlipSkater(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetFlipSkater(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetHoldTime(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetHoldTime(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetTotalY(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetTotalY(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetTotalX(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetTotalX(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+bool ScriptCAT_SetTotalZ(Script::CScriptStructure *pParams, Script::CScript *pScript);
+bool ScriptCAT_GetTotalZ(Script::CScriptStructure *pParams, Script::CScript *pScript);
+
+
+}	// namespace Game
+
+#endif
diff --git a/Code/Sk/Modules/Skate/FilmGoal.cpp b/Code/Sk/Modules/Skate/FilmGoal.cpp
new file mode 100644
index 0000000..45553d8
--- /dev/null
+++ b/Code/Sk/Modules/Skate/FilmGoal.cpp
@@ -0,0 +1,203 @@
+// filming goal
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CFilmGoal::CFilmGoal( Script::CStruct* pParams ) : CGoal( pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CFilmGoal::~CFilmGoal()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CFilmGoal::Activate()
+{
+	m_timeOnCamera = 0;
+	m_timeRequired = m_totalTime = 0;
+	m_numShotsRequired = 0;
+	m_filming = false;
+
+	// script param for displaying time on camera...
+	mp_params->AddInteger( CRCD( 0x9278ca8d, "time_on_camera" ), 0 );
+	
+	int time_required;
+	if ( mp_params->GetInteger( CRCD(0x184e30fd,"total_time_required"), &time_required, Script::NO_ASSERT ) )
+	{
+		m_timeRequired = (Tmr::Time)( time_required * 1000 );
+		int total_time;
+		mp_params->GetInteger( CRCD(0x906b67ba,"time"), &total_time, Script::ASSERT );
+		m_totalTime *= (Tmr::Time)( total_time * 1000 );
+	}
+	else if ( !mp_params->GetInteger( CRCD(0xc808f9fb,"total_shots_required"), &m_numShotsRequired, Script::NO_ASSERT ) )
+	{
+		Dbg_MsgAssert( 0, ( "Film goal %s requires either total_time_required or total_shots_required", Script::FindChecksumName( GetGoalId() ) ) );
+	}	
+	return CGoal::Activate();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CFilmGoal::Update()
+{
+	mp_params->AddInteger( CRCD(0xfc7fc309,"last_time_on_camera"), (int)m_timeOnCamera );
+
+    if ( IsActive() && !IsPaused() )
+	{
+		// check if they're still looking at the target object
+		bool should_win = false;
+		if ( m_filming )
+		{
+			if ( target_object_visible() )
+			{
+				m_timeOnCamera += (Tmr::Time)( Tmr::FrameLength() * 1000 );
+				
+				// script param for displaying time on camera...
+				mp_params->AddInteger( CRCD( 0x9278ca8d, "time_on_camera" ), (int)m_timeOnCamera );
+			}
+			// printf( "on camera: %i, required: %i\n", (int)m_timeOnCamera, (int)m_timeRequired );
+	
+			if ( m_timeOnCamera >= m_timeRequired )
+			{
+				should_win = true;
+			}
+			else if ( m_timeRequired - m_timeOnCamera > m_timeLeft )
+			{
+				// can't possibly win!
+				CGoalManager* pGoalManager = GetGoalManager();
+				Dbg_Assert( pGoalManager );
+				return pGoalManager->LoseGoal( GetGoalId() );
+			}
+		}
+		else if ( m_numShotsRequired && m_numShotsAchieved >= m_numShotsRequired )
+		{
+			should_win = true;
+		}
+	
+		if ( should_win )
+		{
+			CGoalManager* pGoalManager = GetGoalManager();
+			Dbg_Assert( pGoalManager );
+			return pGoalManager->WinGoal( GetGoalId() );		
+		}
+		else
+			return CGoal::Update();
+	}
+	else
+		return CGoal::Update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CFilmGoal::Deactivate( bool force, bool affect_tree )
+{
+	return CGoal::Deactivate( force, affect_tree );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFilmGoal::CheckpointHit()
+{
+	Dbg_MsgAssert( m_numShotsRequired, ( "GoalManager_FilmCheckpoint called on a non-checkpoint goal." ) );
+	if ( target_object_visible() )
+	{
+		m_numShotsAchieved++;
+
+		if ( m_numShotsAchieved > m_numShotsRequired )
+		{
+			CGoalManager* pGoalManager = GetGoalManager();
+			Dbg_Assert( pGoalManager );
+			pGoalManager->WinGoal( GetGoalId() );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// does a visibility check as well as a distance check - you can't just
+// look at him from a mile away
+bool CFilmGoal::target_object_visible()
+{
+	uint32 target_obj_id;
+	mp_params->GetChecksum( CRCD( 0xd262ed09, "film_target" ), &target_obj_id, Script::ASSERT );
+	Obj::CCompositeObject* pTargetObj = (Obj::CCompositeObject*)Obj::ResolveToObject( target_obj_id );
+	Dbg_Assert( pTargetObj );
+
+	bool visible = false;
+	if ( pTargetObj )
+	{
+		Obj::CModelComponent* pTargetObjModelComponent = GetModelComponentFromObject( pTargetObj );
+		Mth::Vector sphere = pTargetObjModelComponent->GetModel()->GetBoundingSphere();
+		Mth::Vector position = sphere;
+		position[3] = 1.0f;
+		position *= pTargetObj->GetMatrix();
+		position += pTargetObj->GetPos();
+		//if ( ( Nx::CEngine::sIsVisible( position, sphere[3] ) ) )
+		if ( ( Nx::CEngine::sIsVisible( position, 6.0f ) ) ) // TT#2555 (Mick), use a 6 inch radius sphere te ensure we can really see him
+		{
+			// get the cam
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+			Dbg_Assert( pSkater );
+			Gfx::Camera* pCurrentCamera = pSkater->GetActiveCamera();
+
+			float distance_to_target = Mth::Distance( pCurrentCamera->GetPos(), pTargetObj->GetPos() );
+			int max_distance;
+			mp_params->GetInteger( CRCD(0x856fab92,"max_distance_to_target"), &max_distance, Script::ASSERT );
+			if ( distance_to_target < (float)max_distance )
+			{
+				visible = true;
+			}
+		}
+	}
+	return visible;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFilmGoal::StartFilming()
+{
+	Dbg_MsgAssert( m_timeRequired, ( "StartFilming called on a non-filming goal." ) );
+	m_filming = true;
+}
+
+}	// namespace Game
+
diff --git a/Code/Sk/Modules/Skate/FilmGoal.h b/Code/Sk/Modules/Skate/FilmGoal.h
new file mode 100644
index 0000000..1f16e6f
--- /dev/null
+++ b/Code/Sk/Modules/Skate/FilmGoal.h
@@ -0,0 +1,45 @@
+// film goal.
+
+#ifndef __SK_MODULES_SKATE_FILMGOAL_H__
+#define __SK_MODULES_SKATE_FILMGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+class CFilmGoal : public CGoal
+{
+
+public:
+						CFilmGoal( Script::CStruct* pParams );
+	virtual				~CFilmGoal();
+
+	bool				Activate();
+	bool				Deactivate( bool force = false, bool affect_tree = true );
+	bool				Update();
+
+	void				CheckpointHit();
+	void				StartFilming();
+protected:
+	bool				target_object_visible();
+
+	bool				m_filming;
+	
+	Tmr::Time           m_timeOnCamera;
+	Tmr::Time			m_timeRequired;
+	Tmr::Time			m_totalTime;
+
+	int					m_numShotsRequired;
+	int					m_numShotsAchieved;
+
+};
+
+}
+
+#endif
+
diff --git a/Code/Sk/Modules/Skate/FindGapsGoal.cpp b/Code/Sk/Modules/Skate/FindGapsGoal.cpp
new file mode 100644
index 0000000..a8d8dce
--- /dev/null
+++ b/Code/Sk/Modules/Skate/FindGapsGoal.cpp
@@ -0,0 +1,106 @@
+// find x gaps
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+// #include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CFindGapsGoal::CFindGapsGoal( Script::CStruct* pParams ) : CGoal( pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CFindGapsGoal::~CFindGapsGoal()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CFindGapsGoal::Activate()
+{
+	mp_params->RemoveComponent( CRCD(0x735af2dd,"completedGaps") );
+	Script::CStruct* pCompletedGaps = new Script::CStruct();
+	mp_params->AddStructure( CRCD(0x735af2dd,"completedGaps"), pCompletedGaps );
+	delete pCompletedGaps;
+
+	mp_params->AddInteger( CRCD(0xf1e59083,"num_gaps_found"), 0 );
+	mp_params->AddInteger( CRCD(0x129daa10,"number_collected"), 0 );
+
+	int num_gaps_to_find;
+	mp_params->GetInteger( CRCD(0xe75c4bbf,"num_gaps_to_find"), &num_gaps_to_find, Script::ASSERT );
+	Dbg_MsgAssert( num_gaps_to_find > 0, ( "find gaps goal activated but num_gaps_to_find <= 0!" ) );
+
+	return CGoal::Activate();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CFindGapsGoal::Deactivate( bool force, bool affect_tree )
+{
+	return CGoal::Deactivate();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CFindGapsGoal::CheckGaps()
+{
+	// printf("CFindGapsGoal::CheckGaps\n");
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+
+	Script::CStruct* pCompletedGaps;
+	mp_params->GetStructure( CRCD(0x735af2dd,"completedGaps"), &pCompletedGaps, Script::ASSERT );
+
+	int num_gaps_found;
+	mp_params->GetInteger( CRCD(0xf1e59083,"num_gaps_found"), &num_gaps_found, Script::ASSERT );
+
+	Mdl::Score* pScore = pSkater->GetScoreObject();
+	int numTricks = pScore->GetCurrentTrickCount();
+	bool found_gap = false;
+	for ( int i = 0; i < numTricks; i++ )
+	{
+		if ( pScore->TrickIsGap( i ) )
+		{
+			uint32 gap_name = pScore->GetTrickId( i );
+			if ( !pCompletedGaps->ContainsComponentNamed( gap_name ) )
+			{
+				pCompletedGaps->AddInteger( gap_name, 1 );
+				num_gaps_found++;
+				found_gap = true;
+			}
+		}
+	}
+	mp_params->AddInteger( CRCD(0xf1e59083,"num_gaps_found"), num_gaps_found );
+	mp_params->AddInteger( CRCD(0x129daa10,"number_collected"), num_gaps_found );
+
+	if ( found_gap )
+	{
+		Script::CStruct* pScriptParams = new Script::CStruct();
+		pScriptParams->AddChecksum( CRCD(0x9982e501,"goal_id"), GetGoalId() );
+		Script::RunScript( CRCD(0x851aa6b8,"goal_find_gaps_found_gap"), pScriptParams );
+		delete pScriptParams;
+	}
+}
+
+
+}	// namespace Game
diff --git a/Code/Sk/Modules/Skate/FindGapsGoal.h b/Code/Sk/Modules/Skate/FindGapsGoal.h
new file mode 100644
index 0000000..2395b47
--- /dev/null
+++ b/Code/Sk/Modules/Skate/FindGapsGoal.h
@@ -0,0 +1,33 @@
+// find x gaps
+
+#ifndef __SK_MODULES_SKATE_FINDGAPSGOAL_H__
+#define __SK_MODULES_SKATE_FINDGAPSGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+class CFindGapsGoal : public CGoal
+{
+
+public:
+						CFindGapsGoal( Script::CStruct* pParams );
+	virtual				~CFindGapsGoal();
+
+	bool				Activate();
+	bool				Deactivate( bool force = false, bool affect_tree = true );
+
+	void				CheckGaps();
+protected:
+	int					m_numGapsToFind;
+};
+
+}	// namespace game
+
+#endif
+
diff --git a/Code/Sk/Modules/Skate/GameFlow.cpp b/Code/Sk/Modules/Skate/GameFlow.cpp
new file mode 100644
index 0000000..11e3511
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GameFlow.cpp
@@ -0,0 +1,206 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate													**
+**																			**
+**	File name:		Skate/GameFlow.cpp										**
+**																			**
+**	Created by:		04/27/01	-	gj										**
+**																			**
+**	Description:	Defines various game flows								**
+**																			**
+*****************************************************************************/
+
+//
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGameFlow::CGameFlow( void )
+{
+	mp_gameFlowScript = NULL;
+	mp_tester_script = NULL;
+	
+	m_requestedScript = 0;
+	m_requestNewScript = false;
+	m_paused = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGameFlow::~CGameFlow( void )
+{
+	if ( mp_gameFlowScript )
+		delete mp_gameFlowScript;
+		
+	if (mp_tester_script)
+	{
+		delete mp_tester_script;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGameFlow::Reset( uint32 scriptChecksum )
+{
+	m_requestedScript = scriptChecksum;
+	m_requestNewScript = true;
+	m_paused = false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameFlow::Pause( bool paused )
+{
+	bool old = m_paused;
+	m_paused = paused;
+	return old;
+}
+
+void CGameFlow::SetTesterScript(uint32 testerScript, Script::CStruct *p_params)
+{
+	if (!mp_tester_script)
+	{
+		mp_tester_script=new Script::CScript;
+		#ifdef __NOPT_ASSERT__
+		mp_tester_script->SetCommentString("Created in CGameFlow::SetTesterScript(...)");
+		#endif
+	}
+		
+	mp_tester_script->SetScript(testerScript, p_params);
+}
+
+// Returns true if the tester script did exist.
+// This is so that the corresponding script command can return true/false, and hence
+// messages can say stuff like 'test script killed' or 'no tester script present' if they want.
+bool CGameFlow::KillTesterScript()
+{
+	if (mp_tester_script)
+	{
+		delete mp_tester_script;
+		mp_tester_script=NULL;
+		return true;
+	}
+		
+	return false;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGameFlow::Update()
+{
+	if (mp_tester_script)
+	{
+		mp_tester_script->Update();
+	}	
+	
+
+	// GJ:  This keeps us from loading a new script
+	// while we're still in the middle of processing the old one
+	if ( m_requestNewScript )
+	{
+		m_requestNewScript = false;
+
+		if ( mp_gameFlowScript )
+			delete mp_gameFlowScript;
+
+		mp_gameFlowScript = new Script::CScript;
+		mp_gameFlowScript->SetScript( m_requestedScript );
+		#ifdef __NOPT_ASSERT__
+		mp_gameFlowScript->SetCommentString("Created in CGameFlow::Update()");
+		#endif
+		
+#ifdef __USER_GARY__
+		printf("Resetting to script %s %08x!\n", Script::FindChecksumName( m_requestedScript ), (int)mp_gameFlowScript);
+#endif
+	}
+
+	// if paused, then don't continue the script
+	if ( m_paused )
+		return;
+
+	if ( mp_gameFlowScript )
+	{
+		if ( mp_gameFlowScript->Update() == Script::ESCRIPTRETURNVAL_FINISHED )
+		{
+			delete mp_gameFlowScript;
+			mp_gameFlowScript = NULL;
+		}
+
+	}
+}
+
+} // namespace Game
+
diff --git a/Code/Sk/Modules/Skate/GameFlow.h b/Code/Sk/Modules/Skate/GameFlow.h
new file mode 100644
index 0000000..7d63265
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GameFlow.h
@@ -0,0 +1,94 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate													**
+**																			**
+**	File name:		Skate/GameFlow.h										**
+**																			**
+**	Created by:		04/27/01	-	gj										**
+**																			**
+**	Description:	Defines various game flows								**
+**																			**
+*****************************************************************************/
+
+#ifndef __MODULES_SKATE_GAMEFLOW_H
+#define __MODULES_SKATE_GAMEFLOW_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mdl
+{
+ 
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  CGameFlow  : public Spt::Class
+{
+	
+
+	public:
+		CGameFlow();
+		virtual						~CGameFlow();
+		void						Reset( uint32 script_checksum );
+		void						Update();
+		bool						Pause( bool paused );
+
+		void						SetTesterScript(uint32 testerScript, Script::CStruct *p_params);
+		bool						KillTesterScript();
+
+	protected:
+		bool						m_paused;
+		Script::CScript*			mp_gameFlowScript;
+		bool						m_requestNewScript;
+		uint32						m_requestedScript;
+		
+		Script::CScript*			mp_tester_script;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**						Inline Functions									**
+*****************************************************************************/
+
+
+} // namespace Mdl
+
+#endif // __MODULES_SKATE_GAMEFLOW_H
\ No newline at end of file
diff --git a/Code/Sk/Modules/Skate/GameMode.cpp b/Code/Sk/Modules/Skate/GameMode.cpp
new file mode 100644
index 0000000..54da6a7
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GameMode.cpp
@@ -0,0 +1,1022 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate													**
+**																			**
+**	File name:		Skate/GameMode.h										**
+**																			**
+**	Created by:		02/07/01	-	gj										**
+**																			**
+**	Description:	Defines various game modes								**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC gamemode
+// @module gamemode | None
+// @subindex Scripting Database
+// @index script | gamemode
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 		   // get player id
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Game
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGameMode::clear_victorycondition_list()
+{
+	
+
+	Lst::Node *pNode = m_VictoryConditionList.GetNext();
+	while(pNode)
+	{
+		Lst::Node *pNext = pNode->GetNext();
+		pNode->Remove();
+		delete pNode->GetData();
+		delete pNode;
+		pNode = pNext;
+	}
+
+	Dbg_Assert( m_VictoryConditionList.IsEmpty() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGameMode::build_victorycondition_list()
+{
+	
+
+	clear_victorycondition_list();
+
+	Script::CArray* pArray;
+	if ( !m_ModeDefinition.GetArray( CRCD(0x94a24c5e,"victory_conditions"), &pArray ) )
+	{
+		// no victory conditions found
+		return;
+	}
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	// loop through the victory conditions
+	for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+	{
+		Script::CScriptStructure* pStructure = pArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+
+		CVictoryCondition* pVictoryCondition = CreateVictoryConditionInstance( pStructure );
+		Dbg_Assert( pVictoryCondition );
+		m_VictoryConditionList.AddToTail( new Lst::Node( pVictoryCondition ) );
+	}
+
+	Mem::Manager::sHandle().PopContext();
+	// TODO: Scan for combinations that don't make sense
+	// (i.e. highest score AND best combo)
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::remove_component_if_exists( const char* p_component_name, Script::CScriptStructure* p_params )
+{
+	if ( p_params->ContainsComponentNamed( p_component_name ) )
+	{
+		m_ModeDefinition.RemoveComponent( Script::GenerateCRC( p_component_name ) );
+		return true;
+	}
+	
+	return false;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGameMode::CGameMode()
+{
+	
+
+	Reset();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGameMode::~CGameMode()
+{
+	
+
+	clear_victorycondition_list();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tmr::Time CGameMode::GetTimeLeft()
+{
+
+	CGoalManager* pGoalManager = Game::GetGoalManager();
+	uint32	nameChecksum = ConvertNetNameChecksum(GetNameChecksum());
+	CGoal* pGoal = pGoalManager->GetGoal(nameChecksum,false);
+
+	if( !pGoal )
+	{
+		return 0;
+	}
+
+	return pGoal->GetTimeLeft();
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CGameMode::SetTimeLeft( Tmr::Time time_left )
+{
+	CGoalManager* pGoalManager = Game::GetGoalManager();
+	uint32	nameChecksum = ConvertNetNameChecksum(GetNameChecksum());
+	CGoal* pGoal = pGoalManager->GetGoal(nameChecksum,false);
+
+	if( pGoal )
+	{
+		pGoal->SetTimer( time_left );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::EndConditionsMet()
+{
+	// this checks whether time has run out
+	if ( ShouldUseClock() )
+	{
+		CGoalManager* pGoalManager = Game::GetGoalManager();
+
+		if( pGoalManager->GetNumActiveGoals( true ) > 0 && pGoalManager->GetGoalTime() <= 0 )
+		{
+			return true;
+		}
+	}
+	
+	uint32 i;
+	int num_terminals = 0;
+	int num_completed_terminals = 0;
+
+	// TODO:  Allow boolean conditions (&'s and |'s)
+
+	// Look through the victory conditions also
+	for ( i = 0; i < m_VictoryConditionList.CountItems(); i++ )
+	{
+		Lst::Node *pNode = m_VictoryConditionList.GetItem( i );
+
+		if (pNode->GetData()->IsTerminal())
+		{
+			num_terminals++;
+			
+			if ( pNode->GetData()->ConditionComplete() )
+			{
+				num_completed_terminals++;
+			}
+		}
+	}
+
+	// If all of the terminal ones have been met,
+	// then we've also met our end conditions
+	if ( num_terminals && ( num_completed_terminals == num_terminals ) )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::ShouldUseClock()
+{
+	int time_limit;
+	
+	if(	( GetNameChecksum() == CRCD(0x5d32129c,"king")) ||
+		( GetNameChecksum() == CRCD(0x6ef8fda0,"netking")) ||
+		( GetNameChecksum() == CRCD(0xf135ecb6,"scorechallenge")) ||
+		( GetNameChecksum() == CRCD(0x1498240a,"netscorechallenge")) ||
+		// K: Added check for creategoals to fix bug where end of run triggered straight away on
+		// entering a level in creategoals mode.
+		( GetNameChecksum() == CRCD(0xe45f0ca2,"creategoals")) ||
+		( GetNameChecksum() == CRCD(0x3d6d444f,"firefight")) ||
+		( GetNameChecksum() == CRCD(0xbff33600,"netfirefight")))
+	{
+		return false;
+	}
+
+	m_ModeDefinition.GetInteger( CRCD(0x8d38a280,"default_time_limit"), &time_limit, true );
+	return ( time_limit != 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::ShouldStopAtZero()
+{
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	
+	if( skate_mod->IsMultiplayerGame() == false )
+	{
+		return false;
+	}
+
+	if( ShouldUseClock())
+	{
+		int should_stop;
+
+		m_ModeDefinition.GetInteger( CRCD(0x48e748b5,"stop_at_zero"), &should_stop, true );
+		return ( should_stop == 1 );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::ShouldAccumulateScore()
+{
+	int should_accumulate_score;
+	m_ModeDefinition.GetInteger( CRCD(0xc1f5864b,"accumulate_score"), &should_accumulate_score, true );
+	return should_accumulate_score;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::ShouldTrackBestCombo()
+{
+	int should_track;
+	if( m_ModeDefinition.GetInteger( CRCD(0xd57c16ba,"track_best_combo"), &should_track, false ) == false )
+	{
+		return false;
+	}
+
+	return should_track;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void
+CGameMode::SetScoreAccumulation( bool should_accumulate )
+{
+	m_ModeDefinition.AddInteger( CRCD(0xc1f5864b,"accumulate_score"), should_accumulate );
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGameMode::SetScoreFrozen( bool is_frozen )
+{
+	m_ModeDefinition.AddInteger( CRCD(0x78fb9f04,"score_frozen"), is_frozen );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void
+CGameMode::SetScoreDegradation( bool should_degrade )
+{
+	m_ModeDefinition.AddInteger( CRCD(0xca37e639,"degrade_score"), should_degrade );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void
+CGameMode::SetNumTeams( int num_teams )
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+	m_ModeDefinition.AddInteger( CRCD(0x2ebe6cd1,"num_teams"), num_teams );
+	if( GetNameChecksum() == CRCD(0x1c471c60,"netlobby") )
+	{
+		gamenet_man->CreateTeamFlags( num_teams );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::ScoreFrozen()
+{
+	int score_is_frozen;
+	m_ModeDefinition.GetInteger( CRCD(0x78fb9f04,"score_frozen"), &score_is_frozen, true );
+	return ( score_is_frozen != 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::ShouldTrackTrickScore()
+{
+	int should_track_trick_score;
+	m_ModeDefinition.GetInteger( CRCD(0x47f763ff,"track_trick_score"), &should_track_trick_score, true );
+	return should_track_trick_score;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::ShouldDegradeScore()
+{
+	int should_degrade_score;
+	m_ModeDefinition.GetInteger( CRCD(0xca37e639,"degrade_score"), &should_degrade_score, true );
+	return should_degrade_score;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::ShouldRunIntroCamera()
+{
+	int should_run_intro_camera;
+	if (Config::CD())
+	{
+		m_ModeDefinition.GetInteger( CRCD(0xe2355b5e,"should_run_intro_camera"), &should_run_intro_camera, true );
+	}
+	else
+	{
+		m_ModeDefinition.GetInteger( CRCD(0xea0d7454,"should_run_intro_camera_noncd"), &should_run_intro_camera, true );
+	}	
+	return should_run_intro_camera;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::ShouldShowRankingScreen()
+{
+	int should_show_ranking_screen;
+	m_ModeDefinition.GetInteger( CRCD(0xf18020f1,"show_ranking_screen"), &should_show_ranking_screen, true );
+	return should_show_ranking_screen;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::IsTimeLimitConfigurable()
+{
+	
+	
+	uint32 time_limit_type;
+	m_ModeDefinition.GetChecksum( CRCD(0x7d5e4bc1,"time_limit_type"), &time_limit_type, true );
+	Dbg_MsgAssert( time_limit_type == CRCD(0x2b75d083,"config") || time_limit_type == CRCD(0x613631cd,"fixed"),( "Unrecognized time limit type" ));
+	return ( time_limit_type == CRCD(0x2b75d083,"config") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::IsTeamGame()
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+	return ( gamenet_man->InNetGame() && ( NumTeams() > 0 ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int
+CGameMode::NumTeams()
+{
+	int num_teams;
+	m_ModeDefinition.GetInteger( CRCD( 0x2ebe6cd1, "num_teams" ), &num_teams, true );
+	return num_teams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGameMode::ShouldTimerBeep()
+{
+	int beeps;
+	m_ModeDefinition.GetInteger( CRCD(0x35c8073,"timer_beeps"), &beeps, true );
+	return beeps;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32
+CGameMode::GetTimeLimit()
+{
+	
+	int time_limit;
+	m_ModeDefinition.GetInteger( CRCD(0x8d38a280,"default_time_limit"), &time_limit, true );
+	return (uint32)time_limit;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32		CGameMode::GetTargetScore( void )
+{
+	uint32 i;
+	// Look through the victory conditions also
+	for ( i = 0; i < m_VictoryConditionList.CountItems(); i++ )
+	{
+		Lst::Node *pNode = m_VictoryConditionList.GetItem( i );
+
+		CScoreReachedVictoryCondition* condition;
+
+		condition = static_cast  (pNode->GetData());
+		if( condition )
+		{
+			return condition->GetTargetScore();
+		}
+	}
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGameMode::GetInitialNumberOfPlayers()
+{
+	
+
+	int num_players;
+	m_ModeDefinition.GetInteger( CRCD(0x6dc3d6f1,"initial_players"), &num_players, true );
+	return num_players;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CGameMode::SetMaximumNumberOfPlayers( uint32 num_players )
+{
+	m_ModeDefinition.AddInteger( CRCD(0x4d42e61d,"max_players"), num_players );
+	Dbg_Printf( "****** SETTING MAX PLAYERS TO %d\n", num_players );
+//	DumpUnwindStack( 20, 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGameMode::GetMaximumNumberOfPlayers()
+{
+	int num_players;
+	m_ModeDefinition.GetInteger( CRCD(0x4d42e61d,"max_players"), &num_players, true );
+	return num_players;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::ScreenMode CGameMode::GetScreenMode()
+{
+	
+
+	uint32 checksum;
+
+	m_ModeDefinition.GetChecksum( CRCD(0x9bab2eae,"screenmode"), &checksum, true );
+	switch (checksum)
+	{
+		case 0x3558d8e6: // single
+			return Nx::vONE_CAM;
+			break;
+		case 0x9d65d0e7: // horse
+			return Nx::vHORSE1;
+			break;
+		case 0x06ab02f2: // splitscreen
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Prefs::Preferences* pPreferences;
+
+			pPreferences = skate_mod->GetSplitScreenPreferences();
+			Dbg_Assert(pPreferences);
+
+			Script::CScriptStructure* pStructure = pPreferences->GetPreference( CRCD(0x6f82c71e,"viewport_type") );
+			Dbg_Assert(pStructure);
+
+			uint32 checksum;
+			pStructure->GetChecksum( CRCD(0x21902065,"checksum"), &checksum, true );
+			if ( checksum == CRCD(0x92f5b4cb,"viewport_type_vertical") )
+			{
+				return Nx::vSPLIT_V;
+			}
+			else if ( checksum == CRCD(0x7c442511,"viewport_type_horizontal") )
+			{
+				return Nx::vSPLIT_H;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+				return Nx::vSPLIT_V;
+			}
+		}
+		break;
+		default:
+			Dbg_Assert( 0 );
+			return (Nx::ScreenMode) 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::IsFrontEnd()
+{
+	
+
+	int is_frontend;
+	m_ModeDefinition.GetInteger( CRCD( 0xa6479767, "is_frontend" ), &is_frontend, true );
+	return is_frontend;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::IsParkEditor()
+{
+	
+
+	int is_parkeditor;
+	m_ModeDefinition.GetInteger( CRCD(0x66d9ddb9,"is_parkeditor"), &is_parkeditor, true );
+	return is_parkeditor;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CGameMode::GetFireballLevel()
+{
+	int level;
+
+	level = 3;
+	m_ModeDefinition.GetInteger( CRCD(0xce87e4e3,"fireball_level"), &level );
+	
+	return level;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::IsTrue( const char* pFieldName )
+{
+	
+
+	int field_value;
+	m_ModeDefinition.GetInteger( pFieldName, &field_value, true );
+	return field_value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::IsTrue( uint32 checksum )
+{
+	
+
+	int field_value;
+	m_ModeDefinition.GetInteger( checksum, &field_value, true );
+	return field_value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Returns the checksum of the name of the game mode definition structure
+// these structures are defined in gamemode.q, an example would be "graffiti"
+// however, you should NOT use these names to check for things to do
+// as there might be mutliple instance of similar modes (like, graffiti and netgraffiti)
+// so you should use the fields within the structure, 
+// use the IsTrue methods above, like IsTrue("is_graffiti");  
+
+uint32 CGameMode::GetNameChecksum()
+{
+	
+
+	uint32 checksum = 0;
+	m_ModeDefinition.GetChecksum(CRCD(0xa1dc81f9,"name"), &checksum);
+	return checksum;
+}
+
+					 
+// if the checksum is the name of a net game, then return the name of the regular equivalent					 
+// used by the time functions, wehre we need to know what type of game it is
+// and not if it's net or not
+uint32 CGameMode::ConvertNetNameChecksum(uint32 nameChecksum)
+{
+
+	uint32 checksum = nameChecksum;
+	
+	if( nameChecksum == CRCD(0x30c2ffa3,"nettrickattack"))
+	{
+		checksum =  CRCD( 0xf9283ee7,"trickattack" );
+	}
+	else if( nameChecksum == CRCD( 0x5e2ea50c, "netgraffiti" ))
+	{
+		checksum = CRCD(0xc8a82b5a, "graffiti");
+	}
+	else if( nameChecksum == CRCD( 0xc50affd0, "netcombomambo" ))
+	{
+		checksum = CRCD(0x23eb3da3, "combomambo");
+	}
+	else if ( nameChecksum == CRCD( 0xf9d5d933, "netslap" ))
+	{
+		checksum = CRCD( 0xca1f360f, "slap" );
+	}
+	else if ( nameChecksum == CRCD(0x6ef8fda0, "netking" ) )
+	{
+		checksum = CRCD( 0x5d32129c,"king" );
+	}
+	else if( nameChecksum == CRCD(0xbff33600,"netfirefight") )
+	{
+		checksum = CRCD(0x3d6d444f,"firefight");
+	}
+	else if( nameChecksum == CRCD(0x6c5ff266,"netctf") )
+	{
+		checksum = CRCD(0xa5ad2b0b,"ctf");
+	}
+	return checksum;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int compFunc( const void *arg1, const void *arg2 )
+{
+	SRanking* pRanking1 = (SRanking*)arg1;
+	SRanking* pRanking2 = (SRanking*)arg2;
+
+	int score1 = pRanking1->pScore->GetTotalScore();
+	int score2 = pRanking2->pScore->GetTotalScore();
+
+	if ( score1 == score2 )
+	{
+		return 0;
+	}
+	else if ( score1 < score2 )
+	{
+		return 1;
+	}
+	else
+	{
+		return -1;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGameMode::CalculateRankings( SRanking* p_player_rankings )
+{
+	
+
+	Dbg_AssertPtr( p_player_rankings );
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	for ( uint32 skater_num = 0; skater_num < skate_mod->GetNumSkaters(); skater_num++ )
+	{
+		Obj::CSkater* pSkater = skate_mod->GetSkater(skater_num);
+		Dbg_AssertPtr( pSkater );
+		
+		Obj::CSkaterScoreComponent* pSkaterScoreComponent = GetSkaterScoreComponentFromObject(pSkater);
+		Dbg_Assert( pSkaterScoreComponent );
+		
+		p_player_rankings[skater_num].obj_id = pSkater->GetID();
+		p_player_rankings[skater_num].pScore = pSkaterScoreComponent->GetScore();
+		p_player_rankings[skater_num].ranking = 0;
+	}
+
+	// sort them by score
+	// TODO:  make the sorting function variable depending on the game mode
+	qsort( &p_player_rankings[0], skate_mod->GetNumSkaters(), sizeof(SRanking), compFunc );
+
+	for ( uint32 i = 0; i < skate_mod->GetNumSkaters(); i++ )
+	{
+		if ( (i != 0) && (p_player_rankings[i].pScore->GetTotalScore() == p_player_rankings[i-1].pScore->GetTotalScore()) )
+		{
+			p_player_rankings[i].ranking = p_player_rankings[i-1].ranking;
+		}
+		else
+		{
+			p_player_rankings[i].ranking = i + 1;
+		}
+
+		printf("ranking = skater=%d score=%d ranking=%d\n", p_player_rankings[i].obj_id, p_player_rankings[i].pScore->GetTotalScore(), p_player_rankings[i].ranking );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::IsWinner( uint32 skater_num )
+{
+	
+
+	int completed_count[Mdl::Skate::vMAX_SKATERS];
+	uint32 j;
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	for ( j = 0; j < skate_mod->GetNumSkaters(); j++ )
+	{
+		completed_count[j] = 0;
+		
+		for ( uint32 i = 0; i < m_VictoryConditionList.CountItems(); i++ )
+		{
+			Lst::Node *pNode = m_VictoryConditionList.GetItem( i );
+			
+			if ( pNode->GetData()->IsWinner( j ) )
+			{
+				completed_count[j]++;
+			}
+		}
+	}
+
+	for ( j = 0; j < skate_mod->GetNumSkaters(); j++ )
+	{
+		if ( skater_num != j && completed_count[skater_num] < completed_count[j] )
+		{
+			return false;
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::Reset()
+{
+	clear_victorycondition_list();
+	
+	return LoadGameType( CRCD(0x34861a16,"freeskate") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::LoadGameType( uint32 gameTypeChecksum )
+{
+	GameNet::Manager* gamenet_man;
+	int max_players;
+	
+	max_players = Mdl::Skate::vMAX_SKATERS;
+	gamenet_man = GameNet::Manager::Instance();
+	if( gamenet_man && gamenet_man->InNetGame())
+	{
+		max_players = GetMaximumNumberOfPlayers();
+	}
+
+	// Reset the current game mode first
+	m_ModeDefinition.Clear();
+
+	Script::CArray* pArray = Script::GetArray( CRCD(0xca5f7ffb,"mode_info"), Script::ASSERT );
+
+	for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+	{
+		Script::CScriptStructure* pStructure = pArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+
+		uint32 checksum;
+		pStructure->GetChecksum( CRCD(0x21902065,"checksum"), &checksum, true );
+		
+		if ( checksum == gameTypeChecksum )
+		{
+
+			const char* definition;
+			pStructure->GetText( CRCD(0x97cfd027,"definition"), &definition, true );
+
+			Script::CScriptStructure* pRefersToStructure = Script::GetStructure( definition, Script::ASSERT );
+			m_ModeDefinition.AppendStructure( pRefersToStructure );
+
+			// clears the existing lists, as we're about to rebuild them
+			clear_victorycondition_list();
+			
+			// rebuilds the victory condition list based on the current game mode
+			build_victorycondition_list();
+
+			if( gamenet_man && gamenet_man->InNetGame())
+			{
+				SetMaximumNumberOfPlayers( max_players );
+			}
+			return true;
+		}
+	}
+
+	Dbg_MsgAssert( 0,( "Unrecognized game type %08x %s", gameTypeChecksum, Script::FindChecksumName( gameTypeChecksum ) ));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGameMode::OverrideOptions( Script::CScriptStructure *pParams )
+{
+	// first we should get rid of any named components that are duplicates
+	// the trick may be not to use any named components?
+	remove_component_if_exists( "time_limit_type", pParams );
+	remove_component_if_exists( "victory_condition_type", pParams );
+	remove_component_if_exists( "screenmode", pParams );
+	
+	m_ModeDefinition.AppendStructure( pParams );
+	
+	// clears the existing lists, as we're about to rebuild them
+	clear_victorycondition_list();
+
+	// rebuilds the victory condition list based on the current game mode
+	build_victorycondition_list();
+
+//#ifdef __USER_GARY__
+#if 1
+	Script::PrintContents( &m_ModeDefinition );
+#endif
+	
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | OverrideGameModeOptions | 
+bool ScriptOverrideGameModeOptions( Script::CScriptStructure *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	CGameMode* pGameMode = skate_mod->GetGameMode();
+	return pGameMode->OverrideOptions( pParams );
+}
+
+} // namespace Game
+
diff --git a/Code/Sk/Modules/Skate/GameMode.h b/Code/Sk/Modules/Skate/GameMode.h
new file mode 100644
index 0000000..7437cca
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GameMode.h
@@ -0,0 +1,177 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate													**
+**																			**
+**	File name:		Skate/GameMode.h										**
+**																			**
+**	Created by:		02/07/01	-	gj										**
+**																			**
+**	Description:	Defines various game modes								**
+**																			**
+*****************************************************************************/
+
+#ifndef __MODULES_SKATE_GAMEMODE_H
+#define __MODULES_SKATE_GAMEMODE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifndef	__SCRIPTING_UTILS_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mdl
+{
+	// forward declaration
+	class Score;
+}
+
+namespace Game
+{
+ 
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+struct SRanking
+{
+	int				obj_id;
+	int				ranking;
+	Mdl::Score*		pScore;
+};
+
+class  CGameMode  : public Spt::Class
+{
+	
+
+public:
+	enum
+	{
+		vGAMEMODE_NETLOBBY = 0x1c471c60,		// "netlobby"
+	};
+			
+public:
+	CGameMode();
+	virtual						~CGameMode();
+
+	// clears the game mode to default, which is effectively "freeskate"
+	bool						Reset();
+
+	// loads a game mode, as defined in a script
+	bool						LoadGameType( uint32 game_type );
+	bool						OverrideOptions( Script::CScriptStructure *pParams );
+	
+public:
+	void						CalculateRankings( SRanking* p_player_rankings );
+	void						SetScoreFrozen( bool is_frozen );
+	bool						ScoreFrozen();
+	void						SetScoreAccumulation( bool should_accumulate );
+	void						SetScoreDegradation( bool should_degrade );
+	void						SetNumTeams( int num_teams );
+	bool						ShouldAccumulateScore();
+	bool						ShouldTrackBestCombo();
+	bool						ShouldTrackTrickScore();
+	bool						ShouldDegradeScore();
+	bool						ShouldUseClock();
+	bool						ShouldStopAtZero();
+	bool						ShouldRunIntroCamera();
+	bool						ShouldShowRankingScreen();
+	bool						IsTeamGame();
+	int							NumTeams();
+	bool						IsTimeLimitConfigurable();
+	bool						ShouldTimerBeep();
+	uint32						GetTimeLimit();
+	uint32						GetTargetScore();
+	uint32						GetInitialNumberOfPlayers();
+	uint32						GetMaximumNumberOfPlayers();
+	void						SetMaximumNumberOfPlayers( uint32 num_players );
+	bool						EndConditionsMet();
+	Tmr::Time					GetTimeLeft();
+	void						SetTimeLeft( Tmr::Time time_left );
+	bool						IsWinner( uint32 skater_num );
+	Nx::ScreenMode				GetScreenMode();
+	bool						IsFrontEnd();
+	bool						IsParkEditor();
+
+	uint32 						GetNameChecksum();
+	uint32 						ConvertNetNameChecksum(uint32 nameChecksum);
+	
+	int							GetFireballLevel();
+
+	// for generic tests, like "is career mode"
+	bool						IsTrue( const char* pFieldName );
+	bool						IsTrue( uint32 checksum );
+	
+	void						PrintContents()
+	{
+#ifdef __NOPT_ASSERT__
+		Script::PrintContents(&m_ModeDefinition);
+#endif	
+	}
+
+protected:
+	void						clear_victorycondition_list();
+	void						build_victorycondition_list();
+	bool						remove_component_if_exists( const char* p_component_name, Script::CScriptStructure* p_params );
+	
+protected:
+	Script::CScriptStructure	m_ModeDefinition;
+	
+	// for determining who is the winner, as well
+	// as ending the game before time has run out
+	Lst::Head	m_VictoryConditionList;
+};
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+bool ScriptOverrideGameModeOptions( Script::CScriptStructure *pParams, Script::CScript *pScript );
+
+/*****************************************************************************
+**						Inline Functions									**
+*****************************************************************************/
+
+
+} // namespace Game
+
+#endif // __MODULES_SKATE_GAMEMODE_H
\ No newline at end of file
diff --git a/Code/Sk/Modules/Skate/Goal.cpp b/Code/Sk/Modules/Skate/Goal.cpp
new file mode 100644
index 0000000..6b446cb
--- /dev/null
+++ b/Code/Sk/Modules/Skate/Goal.cpp
@@ -0,0 +1,3554 @@
+// start autoduck documentation
+// @DOC goal
+// @module goal | None
+// @subindex Scripting Database
+// @index script | goal
+							   
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+								
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+					   
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+
+namespace Front
+{
+    extern void HideTimeTHPS4();
+}
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoalLink::CGoalLink()
+{
+	m_relative = 0;
+	mp_next = NULL;
+	m_beaten = false;
+}
+
+CGoalLink::~CGoalLink()
+{
+	// nothing yet
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoal::CGoal( Script::CStruct* pParams ) : Lst::Node( this )
+{
+	mp_children = new Game::CGoalLink();
+	mp_parents = new Game::CGoalLink();
+	
+	m_active = false;
+    m_hasSeen = false;
+    m_paused = false;
+    m_unlimitedTime = true;
+    m_isbeat = false;
+	m_netIsBeat = false;
+	m_beatenFlags = 0;
+	m_teamBeatenFlags = 0;
+	m_isselected = false;
+	m_isInitialized = false;
+
+	m_numTimesLost = 0;
+	m_numTimesWon = 0;
+	m_numTimesStarted = 0;
+
+	m_deactivateOnExpire = true;
+
+    m_timeLeft  = 0;
+
+	m_chapter = m_stage = -1;
+
+	mp_params = new Script::CStruct;
+	mp_params->AppendStructure( pParams );
+
+	mp_goalPed = new Game::CGoalPed( mp_params );
+
+	mp_goalFlags = new Script::CStruct;
+    
+	m_numflags = 0;
+    m_flagsSet = 0;
+
+	m_elapsedTime = 0;
+	m_elapsedTimeRecord = vINT32_MAX;
+	m_winScoreRecord = 0;
+
+	m_endRunType = vGOALENDOFRUN;
+
+    // set the level number
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    Obj::CSkaterCareer* pCareer = skate_mod->GetCareer();
+    m_levelNum = pCareer->GetLevel();
+    
+	CreateGoalFlags( pParams );
+
+	if ( pParams->ContainsFlag( CRCD( 0x4ab5f14, "single_timer" ) ) )
+	{
+		// Dbg_MsgAssert( mp_children->m_relative, ( "single_timer flag used on a goal without children" ) );
+		m_inherited_flags |= GOAL_INHERITED_FLAGS_ONE_TIMER;
+	}
+
+	if ( pParams->ContainsFlag( CRCD( 0xc6b4258f, "retry_stage" ) ) )
+	{
+		m_inherited_flags |= GOAL_INHERITED_FLAGS_RETRY_STAGE;
+	}
+    
+    // TODO: make goal lock stuff work right
+	if ( pParams->ContainsFlag( vUNLOCKED_BY_ANOTHER ) )
+    {
+        m_isLocked = true;
+    }
+    else if ( pParams->ContainsFlag( vPRO_GOAL ) )
+    {
+        m_isLocked = true;
+    }
+/*	else if ( pParams->ContainsFlag( vPRO_CHALLENGE ) )
+	{
+		m_isLocked = true;
+	}
+*/
+    else
+    {
+        m_isLocked = false;
+    }
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::Init()
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+
+	// overwrite any params with difficulty level params
+	AppendDifficultyLevelParams();
+
+	// make sure there's a goal description so that the intro cam anims can
+	// be killed and Gary will be happy
+	Dbg_MsgAssert( mp_params->ContainsComponentNamed( CRCD(0xc5d7e6b,"goal_description") ), ( "No goal description defined for %s\n", Script::FindChecksumName( GetGoalId() ) ) );
+	
+	RunCallbackScript( vINIT );
+	ReplaceTrickText();
+	m_isInitialized = true;
+	
+	// Dan, TT12965.  If a server switched between time limit and capture limit ctf games, m_unlimitedTime was not being reset to true.  Thus, ShouldExpire
+	// was returning true during the capture limit game, if the time on the previous time limit game had been allowed to run down.
+	int unlimited_time = 0;
+	mp_params->GetInteger( CRCD(0xf0e712d2,"unlimited_time"), &unlimited_time );
+	if ( unlimited_time )
+	{
+		m_unlimitedTime = true;
+	}
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::AddParent( uint32 id )
+{
+	// printf("adding parents\n");
+	if ( mp_parents->m_relative == 0 )
+	{
+		mp_parents->m_relative = id;
+	}
+	else
+	{
+		Game::CGoalLink* p_parent = new Game::CGoalLink();
+		p_parent->m_relative = id;
+		p_parent->mp_next = mp_parents;
+		mp_parents = p_parent;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::CreateGoalFlags( Script::CStruct* pParams )
+{
+    // set any goal flags specified in the params
+    Script::CArray *pTempArray;
+    mp_goalFlags->Clear();
+	m_numflags = 0;
+	if ( pParams->GetArray( CRCD(0xcc3c4cc4,"goal_flags"), &pTempArray ) )
+    {
+        for ( uint32 c = 0; c < pTempArray->GetSize(); c++)
+        {
+            mp_goalFlags->AddInteger( pTempArray->GetNameChecksum( c ), 0 );
+            m_numflags++;
+        }
+    }
+	mp_params->AddInteger( CRCD(0xb3790f33,"num_flags"), m_numflags );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::SetInheritableFlags( int recurse_level )
+{
+	// printf("SetInheritableFlags called on %s\n", Script::FindChecksumName( GetGoalId() ) );
+	Dbg_MsgAssert( recurse_level < 100, ( "SetInheritableFlags recursed too much" ) );
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoalLink* p_child = mp_children;
+	while ( p_child && p_child->m_relative != 0 )
+	{
+		CGoal *pGoal = pGoalManager->GetGoal( p_child->m_relative );
+		if ( pGoal )
+		{
+			pGoal->m_inherited_flags |= m_inherited_flags;
+			pGoal->SetInheritableFlags( recurse_level + 1 );
+		}
+		p_child = p_child->mp_next;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::AddChild( uint32 id )
+{
+	// printf("adding child\n");	
+	// any current children?
+	if ( mp_children->m_relative == 0 )
+	{
+		mp_children->m_relative = id;
+	}
+	else
+	{		
+		// make sure this child isn't already in the list
+		bool found = false;
+		Game::CGoalLink* p_test = mp_children;
+		while ( p_test && p_test->m_relative != 0 )
+		{
+			if ( p_test->m_relative == id )
+			{
+				found = true;
+				break;
+			}
+			p_test = p_test->mp_next;
+		}
+		
+		if ( found )
+		{
+			Dbg_MsgAssert( 0, ( "AddChild failed.  %s already a child of %s\n", Script::FindChecksumName( id ), Script::FindChecksumName( GetGoalId() ) ) );
+		}
+		else
+		{
+			// add new child to head of list
+			Game::CGoalLink* p_child = new Game::CGoalLink();
+			p_child->m_relative = id;
+			p_child->mp_next = mp_children;
+			mp_children = p_child;
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::ResetGoalLinks()
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	CGoalLink* p_link = mp_children;
+	while ( p_link && p_link->m_relative != 0 )
+	{
+		p_link->m_beaten = false;
+		CGoal* pGoal = pGoalManager->GetGoal( p_link->m_relative );
+		if ( pGoal )
+		{
+			// recurse!
+			pGoal->ResetGoalLinks();
+		}
+		
+		p_link = p_link->mp_next;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::DeleteLinks( CGoalLink* p_list )
+{
+	CGoalLink *p_a, *p_b;
+	for ( p_a = p_list; p_a; p_a = p_b )
+	{
+		p_b = p_a->mp_next;
+		delete p_a;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::Uninit( bool propogate, int recurse_level )
+{
+	Dbg_MsgAssert( recurse_level < 20, ( "CGoal::Uninit recursed too deep" ) );
+	
+	// uint32 trigger_obj_id = 0;
+
+	if ( IsActive() )
+	{
+		Deactivate( true );
+	}
+
+	RunCallbackScript( vUNINIT );
+	
+	m_isInitialized = false;
+	
+/*	
+	Script::RunScript( Script::GenerateCRC( "goal_kill_proset_geom" ), mp_params );
+	mp_params->GetChecksum( CRCD( 0x2d7e03d, "trigger_obj_id" ), &trigger_obj_id );
+	if ( trigger_obj_id )
+	{
+		Obj::CMovingObject* p_pos_obj = NULL;
+		// p_pos_obj = Obj::CMovingObject::m_hash_table.GetItem( trigger_obj_id );
+		p_pos_obj = (Obj::CMovingObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( trigger_obj_id );
+		if ( p_pos_obj )
+		{
+			delete p_pos_obj;
+		}
+	}
+*/
+	if ( propogate )
+	{
+		CGoalManager* pGoalManager = GetGoalManager();
+		Dbg_Assert( pGoalManager );
+
+		CGoalLink* pGoalLink = mp_children;
+		while ( pGoalLink && pGoalLink->m_relative != 0 )
+		{
+			CGoal* pChildGoal = pGoalManager->GetGoal( pGoalLink->m_relative );
+			Dbg_Assert( pChildGoal );
+			pChildGoal->Uninit( propogate, recurse_level + 1 );
+			pGoalLink = pGoalLink->mp_next;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CGoal::~CGoal()
+{
+	// does this really need to be run?
+	// (you may not necessarily want to go through the
+	// same destructor logic if we're clearing out the
+	// goals at the end of the level)
+    
+    RunCallbackScript( vDESTROY );
+	
+	delete mp_goalPed;
+
+	Dbg_Assert( mp_params );
+	delete mp_params;    
+	delete mp_goalFlags;
+
+	// delete all children and parent trees
+	DeleteLinks( mp_children );
+	DeleteLinks( mp_parents );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Script::CStruct* CGoal::GetParams()
+{
+	return mp_params;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetTimer()
+{
+	int unlimited_time = 0;
+
+	mp_params->GetInteger( CRCD(0xf0e712d2,"unlimited_time"), &unlimited_time );
+
+	// check for unlimited time
+	if ( unlimited_time == 1 )
+	{
+		return;
+	}
+	
+	// don't set default time for minigames.
+	if ( IsMinigame() )
+	{
+		if ( !mp_params->ContainsComponentNamed( CRCD(0x906b67ba,"time") ) )
+			return;
+	}
+
+	// default time limit of 2 minutes
+    m_timeLeft = 120;
+	int time;
+	if ( mp_params->GetInteger( CRCD(0x906b67ba,"time"), &time, Script::NO_ASSERT ) )
+	{
+		m_timeLeft = (Tmr::Time)time;
+	}
+/*	else
+	{
+		Script::CArray* pTimeLimits;
+		if ( mp_params->GetArray( CRCD(0x906b67ba,"time"), &pTimeLimits, Script::NO_ASSERT ) )
+		{
+			CGoalManager* pGoalManager = GetGoalManager();
+			Dbg_Assert( pGoalManager );
+			uint32 difficulty_level = pGoalManager->GetDifficultyLevel();
+			Dbg_Assert( difficulty_level > 0 && difficulty_level < pTimeLimits->GetSize() );
+			m_timeLeft = (Tmr::Time)( pTimeLimits->GetInteger( difficulty_level ) );
+		}
+	}
+*/
+	// convert to milliseconds
+	m_timeLeft *= 1000;
+    
+	// bump up timer so clock starts with expected amount of time left
+	m_timeLeft += 20;
+	// printf("timer set to %i\n", (int)m_timeLeft);
+
+	m_unlimitedTime = false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetTimer( int time )
+{
+	m_timeLeft = (Tmr::Time)time * 1000;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CGoal::GetTimeLeft( void )
+{
+	return m_timeLeft;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::AddTime( int time )
+{
+	// bump up time by 20ms so that it displays right
+	m_timeLeft += ( (Tmr::Time)time * 1000 ) + 20;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::Activate()
+{
+    if ( !IsActive() )
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+		// Warning! Make sure there are no 'return's between the above pushcontext and the 
+		// popcontext below ...
+		
+		m_elapsedTime = 0;
+		
+		m_numTimesStarted++;
+
+		// printf("goal is not active - now activating\n");
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+		Lst::Search< GameNet::PlayerInfo > sh;
+						
+		// TODO: reset just this skater's score
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ) )
+		{
+			Obj::CSkater *p_Skater = player->m_Skater;
+			Dbg_Assert( p_Skater );
+			
+			Obj::CSkaterScoreComponent* p_SkaterScoreComponent = GetSkaterScoreComponentFromObject(p_Skater);
+			Dbg_Assert( p_SkaterScoreComponent );
+			
+			Mdl::Score * p_Score = ( p_SkaterScoreComponent->GetScore() );
+			Dbg_Assert( p_Score );
+	
+			// don't reset the score when starting a minigame
+			if ( !IsMinigame() )
+				p_Score->Reset();
+		}
+
+		// Warning! Make sure there are no 'return's between the above pushcontext and the 
+		// popcontext below ...
+        
+		if ( mp_parents->m_relative != 0 )
+		{
+			CGoalManager* pGoalManager = GetGoalManager();
+			Dbg_Assert( pGoalManager );
+
+			CGoal* pParentGoal = NULL;
+
+			// get the root node and check the timer
+			CGoalLink* p_parent = mp_parents;
+			while ( p_parent && p_parent->m_relative != 0 )
+			{
+				pParentGoal = pGoalManager->GetGoal( p_parent->m_relative );
+				p_parent = pParentGoal->GetParents();
+			}
+			
+			if ( pParentGoal )
+			{
+				if ( m_inherited_flags & GOAL_INHERITED_FLAGS_ONE_TIMER )
+					SetTimer( pParentGoal->GetTimeLeft() / 1000.0f );
+				else
+					SetTimer();
+
+				if ( pParentGoal->IsInitialized() )
+				{
+					pParentGoal->Uninit();
+					
+					// reinitialize the goal in case uninitializing the parent
+					// screwed anything up.
+					if ( IsInitialized() )
+					{
+						Uninit();
+						Init();
+					}
+				}
+			}
+			else
+			{
+				// Dbg_Message( "Goal supposed to use a single timer but couldn't find parent" );
+				SetTimer();
+			}
+		}
+		else
+		{
+			SetTimer();
+		}
+		
+		if ( !IsInitialized() )
+		{
+			Init();
+		}
+		
+		m_endRunCalled = false;
+
+		// Warning! Make sure there are no 'return's between the above pushcontext and the 
+		// popcontext below ...
+
+		mp_params->RemoveFlag( CRCD(0x7100155d,"combo_started") );
+
+        // activate script
+		RunCallbackScript( vACTIVATE );
+        
+		if( gamenet_man->InNetGame() == false )
+		{
+			m_hasSeen = true;
+		}
+        
+		//if ( !mp_params->ContainsFlag( "quick_start" ) )
+			//PauseGoal();
+		SetActive();
+		Mem::Manager::sHandle().PopContext();
+		
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetActive()
+{
+	m_active = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::Deactivate( bool force, bool affect_tree )
+{
+	if ( IsActive() )
+	{
+		// special case for special trick goal...get rid of
+		// the temp key_combos array
+		if ( IsSpecialGoal() )
+			mp_params->RemoveComponent( CRCD(0x79704516,"key_combos") );
+
+		Front::HideTimeTHPS4();
+		
+		SetInactive();
+        
+        ResetGoalFlags();
+
+		ResetGoalLinks();
+
+        RunCallbackScript( vDEACTIVATE );
+
+		if (!mp_params->ContainsFlag( CRCD( 0x38221df4, "quick_start" ) ))
+		{
+			// If not retrying, then remove the testing_goal flag
+			// The presence of the testing_goal flag prevents the goal from being marked as
+			// beaten when it is beaten during testing.
+			mp_params->RemoveFlag(CRCD(0x54d3cac1,"testing_goal"));
+		}	
+		
+		if ( !mp_params->ContainsFlag( CRCD( 0xc309cad1, "just_won_goal" ) )
+			 && !mp_params->ContainsFlag( CRCD( 0x38221df4, "quick_start" ) ) )
+		{
+			Script::RunScript(CRCD(0x719ae783, "ready_skater_for_early_end_current_goal"), mp_params);
+			
+			if ( affect_tree && mp_parents && mp_parents->m_relative )
+			{
+				Uninit();
+	
+				// find parent node and reinitialize
+				CGoalManager* pGoalManager = GetGoalManager();
+				Dbg_Assert( pGoalManager );
+				CGoalLink* p_link = mp_parents;
+				CGoal* pParentGoal = NULL;
+				while ( p_link && p_link->m_relative )
+				{
+					pParentGoal = pGoalManager->GetGoal( p_link->m_relative );
+					Dbg_Assert( pParentGoal );
+					p_link = pParentGoal->GetParents();
+				}
+				if ( pParentGoal && !pParentGoal->HasWonGoal() )
+				{
+					// printf("initializing goal: %s\n", Script::FindChecksumName( pParentGoal->GetGoalId() ) );
+					pParentGoal->Init();
+				}
+			}
+		}
+
+        return true;
+    }
+	return false;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetInactive()
+{
+	m_active = false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::Win( void )
+{
+	mp_params->AddInteger( CRCD(0xc22a2b72,"win_record"), 0 );
+	// see if it's a leaf node
+	if ( mp_children->m_relative == 0 )
+	{
+		mp_params->AddChecksum( NONAME, CRCD( 0xc309cad1, "just_won_goal" ) );			
+
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		
+		m_numTimesWon++;
+		
+		// look for a new record if we're not in a net game
+		bool set_new_record = false;
+		uint32 record_type;
+		if ( !gamenet_man->InNetGame() && mp_params->GetChecksum( CRCD(0x96963902,"record_type"), &record_type, Script::NO_ASSERT ) )
+		{
+			// printf("record type is %s\n", Script::FindChecksumName( record_type ) );
+			if ( record_type == CRCD(0xcd66c8ae,"score") )
+			{
+				// check for custom score record
+				int custom_record;
+				if ( mp_params->GetInteger( CRCD(0xfc9630ab,"custom_score_record"), &custom_record, Script::NO_ASSERT ) )
+				{
+					if ( custom_record > m_winScoreRecord )
+					{
+						m_winScoreRecord = custom_record;
+						mp_params->AddInteger( CRCD(0xc22a2b72,"win_record"), m_winScoreRecord );
+						set_new_record = true;
+					}
+				}
+				else
+				{
+					// printf("found a score record type\n");
+					Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+					Obj::CSkater *pSkater = skate_mod->GetLocalSkater();
+					Dbg_Assert( pSkater );
+					
+					Obj::CSkaterScoreComponent* pSkaterScoreComponent = GetSkaterScoreComponentFromObject(pSkater);
+					Dbg_Assert( pSkaterScoreComponent );
+		
+					Mdl::Score * pScore = ( pSkaterScoreComponent->GetScore() );
+					Dbg_Assert( pScore );
+		
+					int total_score;
+					if ( mp_params->ContainsFlag( CRCD(0xc63432a7,"high_combo") ) )
+						 total_score = pScore->GetLastScoreLanded();
+					else
+						 total_score = pScore->GetTotalScore();
+
+					// printf("got a total_score of %i, had a previous record of %i\n", total_score, m_winScoreRecord );
+					if ( total_score > m_winScoreRecord )
+					{
+						m_winScoreRecord = total_score;
+						mp_params->AddInteger( CRCD(0xc22a2b72,"win_record"), m_winScoreRecord );
+						set_new_record = true;
+					}
+				}
+			}
+			else if ( record_type == CRCD(0x906b67ba,"time") )
+			{
+				// printf("got a time of %i, previous record was %i\n", (int)m_elapsedTime, (int)m_elapsedTimeRecord);
+				if ( m_elapsedTime < m_elapsedTimeRecord )
+				{
+					m_elapsedTimeRecord = m_elapsedTime;
+					mp_params->AddInteger( CRCD(0xc22a2b72,"win_record"), (int)m_elapsedTimeRecord );
+					set_new_record = true;
+				}
+			}
+		}
+	#ifdef __NOPT_ASSERT__
+		CGoalManager* pGoalManager = GetGoalManager();
+		Dbg_Assert( pGoalManager );
+	#endif		// __NOPT_ASSERT__
+		
+		if ( !HasWonGoal() && !gamenet_man->InNetGame() )
+		{
+			// printf("getting rewards struct\n");
+			Script::CStruct* p_reward_params = new Script::CStruct();
+			GetRewardsStructure( p_reward_params, true );
+			if ( set_new_record )
+				p_reward_params->AddChecksum( NONAME, CRCD(0xe7b8254f,"set_new_record") );
+	
+			mp_params->AddStructurePointer( CRCD(0x128b3088,"reward_params"), p_reward_params );
+	
+			UnlockRewardGoals();
+		}
+		else
+			mp_params->RemoveComponent( CRCD(0x128b3088,"reward_params") );
+		
+		// update set_new_record flag
+		if ( set_new_record )
+			mp_params->AddChecksum( NONAME, CRCD(0xe7b8254f,"set_new_record") );
+		else
+			mp_params->RemoveFlag( CRCD(0xe7b8254f,"set_new_record") );
+		
+		RunCallbackScript( vSUCCESS );
+
+		bool testing_goal=mp_params->ContainsFlag(CRCD(0x54d3cac1,"testing_goal"));
+		
+		if ( IsActive() )
+		{
+			// add a flag so the deactivate script knows that we just beat the goal
+			Deactivate();
+		}
+	
+		// The testing_goal flag is set when a created goal is being test played.
+		// Don't want it to be crossed off the view goals menu in that case. (TT3366)
+		if (!testing_goal)
+		{
+			MarkBeaten();
+		}
+
+		if ( gamenet_man->InNetGame() )
+		{
+			Uninit();
+		}
+				
+		mp_params->RemoveFlag( CRCD( 0xc309cad1, "just_won_goal" ) );
+	
+		return true;
+	}
+	else
+	{
+		// it's a link to another goal
+
+		// TODO: we need a way of deciding which child to pick
+		// for now, we'll just take the first child
+		Game::CGoalLink* p_link = mp_children;
+
+		CGoalManager* pGoalManager = GetGoalManager();
+		Dbg_Assert( pGoalManager );
+		Deactivate();
+		Uninit();
+		CGoal* pChildGoal = pGoalManager->GetGoal( p_link->m_relative );
+		pChildGoal->Init();
+		pGoalManager->ActivateGoal( p_link->m_relative, false );
+		
+		// mark the link beaten
+		p_link->m_beaten = true;
+
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::Lose( void )
+{
+	// Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	// Obj::CSkater *p_Skater = skate_mod->GetLocalSkater();
+	// Dbg_Assert( p_Skater );
+
+	// p_Skater->ClearAllExceptions();
+
+	if ( IsActive() )
+	{
+		m_numTimesLost++;
+		
+		RunCallbackScript( vFAIL );
+        
+		Deactivate( true );
+
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsExpired()
+{
+	return IsExpired( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::ShouldExpire()
+{
+	// returns true if the goal would expire if the skater was not in a combo
+	
+	// Dan: this logic needs to mimic IsExpired()
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Obj::CSkater* p_skater;
+	
+	p_skater = skate_mod->GetLocalSkater();
+	if ( p_skater == NULL )
+	{
+		return true;
+	}
+	
+#ifdef __NOPT_ASSERT__
+	Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(p_skater);
+	Dbg_Assert(p_skater_endrun_component);
+#endif		// __NOPT_ASSERT__
+	
+	// If no players are left, the goal should expire
+	if ( gamenet_man->GetNumPlayers() == 0 )
+	{
+		return true;
+	}
+	
+	// If it's an unlimited game, don't expire the goal
+	if ( m_unlimitedTime )
+	{
+		return false;
+	}
+	
+	if ( (int)m_timeLeft <= 0 )
+	{
+		// Dbg_Printf( "**** Already Expired\n" );
+		return true;
+	}
+	
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsExpired( bool force_end )
+{
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Obj::CSkater* p_skater;
+
+	p_skater = skate_mod->GetLocalSkater();
+	if ( p_skater == NULL )
+	{
+		return true;
+	}
+	
+	Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(p_skater);
+	Dbg_Assert(p_skater_endrun_component);
+
+	// If no players are left, the goal should expire
+	if ( gamenet_man->GetNumPlayers() == 0 )
+	{
+		return true;
+	}
+					
+	// If it's an unlimited game, don't expire the goal
+	if ( m_unlimitedTime )
+	{
+		return false;
+	}
+	
+	// check if the skaters have reached end of run BEFORE
+	// setting the exception, or you'll never catch it!
+	bool expired = false;
+	 
+	switch ( m_endRunType )
+	{
+	case vENDOFRUN:
+		expired = p_skater_endrun_component->RunHasEnded();
+		break;
+	case vGOALENDOFRUN:
+		expired = p_skater_endrun_component->GoalRunHasEnded();
+		break;
+	case vNONE:
+		// if we're not supposed to use end of run, we need only check
+		// the time
+		return ( (int)m_timeLeft <= 0 );
+		break;
+	default:
+		Dbg_Assert( 0 );
+	}
+
+	if ( expired && (int)m_timeLeft <= 0 )
+	{
+		// Dbg_Printf( "**** Already Expired\n" );
+		return true;
+	}
+
+	// if time is up, set the end of run exception
+	if ( (int)m_timeLeft <= 0 )
+	{
+		// Dbg_Printf( "EXPIRING GOAL: %d\n", GetGoalId());
+		// printf("&&&&&&&&&&&&&&&&& calling EndRun\n");
+		switch ( m_endRunType )
+		{
+		case vENDOFRUN:
+			m_endRunCalled = true;
+			// Dbg_Printf( "**** Calling End Run\n" );
+			p_skater_endrun_component->EndRun( force_end );
+			break;
+		case vGOALENDOFRUN:
+			// printf("reached goalendofrun\n");
+			m_endRunCalled = true;
+			p_skater_endrun_component->EndGoalRun( force_end );
+			break;
+		default:
+			Dbg_Assert( 0 );
+		}
+	}
+
+    // printf("AtEndOfRun returned %i\n", all_expired);
+	// return ( all_expired && ( (int)m_timeLeft <= 0 ) );
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::AllSkatersAtEndOfRun()
+{
+	// check all the goals
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+	{
+		Obj::CSkater *p_Skater = player->m_Skater;
+		// int skater_num = p_Skater->GetSkaterNumber();
+		Dbg_Assert( p_Skater );
+		
+		Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(p_Skater);
+		Dbg_Assert( p_skater_endrun_component );
+
+		if( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0x9d65d0e7, "horse" ))
+		{
+			if( p_Skater->IsPaused() || p_Skater->IsHidden())
+			{
+				continue;
+			}
+		}
+		switch ( m_endRunType )
+		{
+		case vENDOFRUN:
+			if ( !p_skater_endrun_component->RunHasEnded() )
+			{
+				return false;
+			}
+			break;
+		case vGOALENDOFRUN:
+			if ( !p_skater_endrun_component->GoalRunHasEnded() )
+			{
+				return false;
+			}
+			break;
+		default:
+			Dbg_Assert( 0 );
+		}
+	}    
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::EditParams( Script::CStruct* pParams )
+{
+	mp_params->AppendStructure( pParams );
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::Update( void )
+{
+    if ( IsActive() && !IsPaused() )
+    {
+        RunCallbackScript( vACTIVE );
+    }
+    
+    // the active script may have deactivated the goal, so we check 
+    // if it's active again...don't check mini games
+    if ( IsActive() && !IsPaused())
+	{
+		if( ShouldUseTimer()  )
+		{
+			if ( ( (int)m_timeLeft >= 1 ) )
+			{
+				int old_time_left = (int)m_timeLeft;
+				m_timeLeft -= (Tmr::Time)( Tmr::FrameLength() * 1000 );
+				m_elapsedTime += (Tmr::Time)( Tmr::FrameLength() * 1000 );
+
+				if ( (int)m_timeLeft < 10000 && ( (int)( old_time_left / 1000 ) > (int)( (int)m_timeLeft / 1000 ) ) )
+				{
+					if (Mdl::Skate::Instance()->GetGameMode()->ShouldTimerBeep())
+					{
+						Script::RunScript( CRCD(0xcfc7c3ad,"timer_runout_beep") );
+					}
+				}
+			}
+		}
+
+		if ( ShouldRequireCombo() )
+		{
+			if ( !mp_params->ContainsFlag( CRCD( 0x7100155d, "combo_started" ) ) )
+			{
+				Mdl::Skate *skate_mod =  Mdl::Skate::Instance();
+				Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+				if ( pSkater )
+				{
+					Mdl::Score* pScore = pSkater->GetScoreObject();
+					if ( pScore && pScore->GetScorePotValue() > 0 )
+					{
+						mp_params->AddChecksum( NONAME, CRCD(0x7100155d,"combo_started") );
+					}
+				}
+			}
+		}
+		
+		// add the amount of time left
+		// mp_params->AddInteger( "time_left", m_timeLeft );        
+		if ( IsExpired() )
+		{
+			Expire();
+		}
+	}
+	
+// haven't used the inactive yet	
+/*	else
+	{
+		if ( !IsLocked() )
+        {
+            // runs its inactive script (if any)
+    		RunCallbackScript( vINACTIVE );
+        }
+	}	
+*/
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::Expire()
+{
+	RunCallbackScript( vEXPIRED );
+	
+	// TODO: fix after e3
+	// this was done to make the horse and combo letter goals work right
+	if ( m_deactivateOnExpire )
+	{
+		Deactivate();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsActive( void )
+{
+	return ( m_active == true );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::HasSeenGoal( void )
+{
+	return m_hasSeen;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::PauseGoal( void )
+{
+	m_paused = true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::UnPauseGoal( void )
+{
+	m_paused = false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Tmr::Time CGoal::GetTime( void )
+{
+	return m_timeLeft;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/*void CGoal::CreateGoalFlag( uint32 flag )
+{
+    mp_goalFlags->AddInteger( flag, 0 );
+    m_numflags++;
+}*/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::SetGoalFlag( uint32 flag, int value )
+{
+    if ( !IsActive() )
+    {
+        return false;
+    }
+    
+	if ( mp_goalFlags->ContainsComponentNamed( flag ) )
+	{
+		// remember the old value
+		int old_value;
+		mp_goalFlags->GetInteger( flag, &old_value, Script::ASSERT );
+		
+		mp_goalFlags->AddInteger( flag, value );
+		if ( old_value != value )
+		{
+			if ( value == 0 )
+				m_flagsSet--;
+			else
+			{
+				m_flagsSet++;
+				if ( !mp_params->ContainsFlag( CRCD(0x524566e3,"no_midgoal_sound") ) )
+					Script::RunScript( CRCD(0x5ed34deb,"goal_got_flag_sound") );
+			}
+			if ( mp_params->ContainsFlag( CRCD(0xfd7ae1f4,"use_hud_counter") ) )
+				Script::RunScript( CRCD(0xf2e85a34,"goal_update_counter"), mp_params );
+
+			mp_params->AddInteger( CRCD(0xcf6cd3c1,"num_flags_set"), m_flagsSet );
+		}		
+		// printf("setting goal flag %s, %i of %i set\n", Script::FindChecksumName( flag ), m_flagsSet, m_numflags );
+		return true;
+	}
+    
+    Dbg_MsgAssert( 0, ("SetGoalFlag couldn't find flag\n" ) );
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::GetCreatedGoalGap(int gapIndex)
+{
+	Script::CArray *p_required_gaps=NULL;
+	if (!mp_params->GetArray(CRCD(0x52d4489e,"required_gaps"),&p_required_gaps))
+	{
+		return false;
+	}
+
+	const char *p_required_trick_name="";
+	mp_params->GetString(CRCD(0x730844a3,"required_trick_name"),&p_required_trick_name);
+	if (p_required_trick_name[0])
+	{
+		Mdl::Skate *p_skate_mod =  Mdl::Skate::Instance();
+		Obj::CSkater *p_skater = p_skate_mod->GetLocalSkater();
+		if (!p_skater)
+		{
+			return false;
+		}	
+		Mdl::Score *p_score = p_skater->GetScoreObject();
+	
+		uint32 trick_checksum=Script::GenerateCRC(p_required_trick_name);
+		
+		// Also check for fs and bs versions (TT9784)
+		uint32 bs_trick_checksum=CRCD(0x7491335e,"bs ");
+		bs_trick_checksum=Crc::ExtendCRCWithString(bs_trick_checksum, p_required_trick_name);
+		uint32 fs_trick_checksum=CRCD(0x73989b82,"fs ");
+		fs_trick_checksum=Crc::ExtendCRCWithString(fs_trick_checksum, p_required_trick_name);
+		
+		if (p_score->GetCurrentNumberOfOccurrencesByName(trick_checksum, 0, false) == 0 && 
+			p_score->GetCurrentNumberOfOccurrencesByName(bs_trick_checksum, 0, false) == 0 &&
+			p_score->GetCurrentNumberOfOccurrencesByName(fs_trick_checksum, 0, false) == 0)
+		{
+			return false;
+		}
+	}		
+	
+	for (uint i=0; iGetSize(); ++i)
+	{
+		if (gapIndex==p_required_gaps->GetInteger(i))
+		{
+			char p_foo[20];
+			sprintf(p_foo,"got_%d",i+1);
+			SetGoalFlag(Script::GenerateCRC(p_foo),1);
+			return true;
+		}
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::GoalFlagEquals( uint32 flag, int value )
+{
+	if ( mp_goalFlags->ContainsComponentNamed( flag ) )
+	{
+		int f;
+		mp_goalFlags->GetInteger( flag, &f, Script::ASSERT );
+		return ( value == f );
+	}
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::ResetGoalFlags()
+{
+	// reset the goal flags
+	Script::CComponent* p_comp = mp_goalFlags->GetNextComponent( NULL );
+	while ( p_comp )
+	{
+		Script::CComponent* p_next = mp_goalFlags->GetNextComponent( p_comp );
+		p_comp->mIntegerValue = 0;
+		// SetGoalFlag( p_comp->mNameChecksum, 0 );
+		
+		// printf("resetting goal flag %s\n", Script::FindChecksumName( flag_name ) );
+		p_comp = p_next;
+	}
+	m_flagsSet = 0;
+	mp_params->AddInteger( CRCD( 0xcf6cd3c1, "num_flags_set" ), 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::AllFlagsSet()
+{
+//    printf("%i flags, %i set\n", m_numflags, m_flagsSet);
+	int num_flags_to_win = m_numflags;
+	mp_params->GetInteger( CRCD(0x1171a555,"num_flags_to_win"), &num_flags_to_win, Script::NO_ASSERT );
+	
+	// K: Fix to TT9785, where created gap goals would succeed straight away if no gaps were chosen,
+	// causing an assert.
+	if (num_flags_to_win == 0)
+	{
+		return false;
+	}	
+	return ( m_flagsSet >= num_flags_to_win );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::GoalFlagSet( uint32 flag )
+{
+	int flag_value;
+	mp_goalFlags->GetInteger( flag, &flag_value, Script::ASSERT );
+	return ( flag_value != 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsLocked()
+{
+    return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::UnlockGoal()
+{
+    if ( m_isLocked )
+    {
+        m_isLocked = false;
+
+		// don't initialize goals automatically in net games
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		if ( gamenet_man->InNetGame() )
+			return;
+
+		// initialize goal
+        Init();
+        
+        // call the unlock script 
+        RunCallbackScript( vUNLOCK );
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::NextTourSpot()
+{
+    Script::CArray* p_waypoints;
+    // backward compatible with the horse_spots array naming convention
+	if ( !mp_params->GetArray( CRCD(0x160b3220,"tour_spots"), &p_waypoints, Script::NO_ASSERT ) )
+		mp_params->GetArray( CRCD(0x92c6e839,"horse_spots"), &p_waypoints, Script::ASSERT );
+
+    Script::CStruct* p_waypoint;
+    for ( uint32 i = 0; i < p_waypoints->GetSize(); i++ )
+    {
+        p_waypoint = p_waypoints->GetStructure( i );
+
+        // is this waypoint done?
+        uint32 flagId;
+        p_waypoint->GetChecksum( CRCD(0x2e0b1465,"flag"), &flagId, Script::ASSERT );
+        if ( GoalFlagEquals( flagId, 1 ) )
+            continue;
+        
+        // pause the goal and set the time for this tour spot
+        PauseGoal();
+		int time;
+		p_waypoint->GetInteger( CRCD(0x906b67ba,"time"), &time, Script::ASSERT );
+        // add time if necessary
+		if ( mp_params->ContainsFlag( CRCD(0xfb11cb01,"cumulative_timer") ) )
+		{
+			AddTime( time );
+		}
+		else
+		{
+			mp_params->AddInteger( CRCD(0x906b67ba,"time"), time );
+			SetTimer();
+		}
+            
+        // add the goalId and trigger id to the params
+        p_waypoint->AddChecksum( CRCD(0x9982e501,"goal_id"), GetGoalId() );
+
+		// send a flag if this is the first spot
+		if ( m_flagsSet == 0 )
+			p_waypoint->AddChecksum( NONAME, CRCD( 0xb13f3ab8, "first_spot" ) );
+
+		// send a flag if this is a restart
+		if ( mp_params->ContainsFlag( CRCD(0x38221df4,"quick_start") ) )
+			p_waypoint->AddChecksum( NONAME, CRCD(0x38221df4,"quick_start") );
+		else
+			p_waypoint->RemoveFlag( CRCD(0x38221df4,"quick_start") );
+
+        
+		#ifdef __NOPT_ASSERT__
+		// backward compatible with horse goal
+		Script::CScript *p_script=NULL;
+		if ( mp_params->ContainsFlag( CRCD(0x9d65d0e7,"horse") ) )
+			p_script=Script::SpawnScript( CRCD(0x681bc776,"goal_horse_next_spot"), p_waypoint );
+		else
+			p_script=Script::SpawnScript( CRCD(0x4c938553,"goal_tour_next_spot"), p_waypoint );
+		p_script->SetCommentString("Spawned from CGoal::NextTourSpot()");
+		#else	
+		// backward compatible with horse goal
+		if ( mp_params->ContainsFlag( CRCD(0x9d65d0e7,"horse") ) )
+			Script::SpawnScript( CRCD(0x681bc776,"goal_horse_next_spot"), p_waypoint );
+		else
+			Script::SpawnScript( CRCD(0x4c938553,"goal_tour_next_spot"), p_waypoint );
+		#endif			
+
+
+		// remove restart flag
+		p_waypoint->RemoveFlag( CRCD(0x38221df4,"quick_start") );
+
+
+/*        // move the skater to the node
+        uint32 reset_checksum;
+        p_waypoint->GetChecksum( "id", &reset_checksum, Script::ASSERT );
+        Mdl::Skate * p_skate = Mdl::Skate::Instance();
+        p_skate->ResetSkaters( SkateScript::FindNamedNode( reset_checksum ) );
+
+        uint32 waypoint_script;
+        if ( p_waypoint->GetChecksum( "scr", &waypoint_script, Script::NO_ASSERT ) )
+            Script::SpawnScript( waypoint_script );
+*/        
+        return true;
+
+    }
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::InitTrickObjects()
+{
+    // get the object id's
+    Script::CArray* p_trickObjectArray = NULL;
+    mp_params->GetArray( CRCD(0x2eb84a54,"trick_objects"), &p_trickObjectArray, Script::ASSERT );
+    
+    Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+    Obj::CTrickObjectManager* p_TrickObjectManager = skate_mod->GetTrickObjectManager();
+
+    for ( uint32 i = 0; i < p_trickObjectArray->GetSize(); i++ )
+    {
+        // add the trick object
+        uint32 goal_object;
+        Script::CStruct* p_trickObject = p_trickObjectArray[i].GetStructure( i );
+        p_trickObject->GetChecksum( CRCD(0x40c698af,"id"), &goal_object, Script::ASSERT );
+        
+        // p_TrickObjectManager->RequestLogTrick( 1, 
+        p_TrickObjectManager->ModulateTrickObjectColor( goal_object, 0 );
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::GotTrickObject( uint32 clusterId )
+{
+	// do we care?
+	if ( mp_params->ContainsFlag( CRCD( 0xf268c278, "SkateTheLine" ) ) )
+	{
+		// we do
+		uint32 current;
+		mp_params->GetChecksum( CRCD(0xb03713ee,"current_cluster"), ¤t, Script::ASSERT );
+		if ( current == clusterId )
+			Script::RunScript( CRCD( 0x62b12a8e, "Goal_SkateTheLine_got_node" ), mp_params );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsGraffitiGoal()
+{
+	return ( mp_params->ContainsFlag( CRCD(0xc8a82b5a,"graffiti") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::ShouldLogTrickObject()
+{
+	if ( mp_params->ContainsFlag( CRCD(0xc8a82b5a,"graffiti") ) || mp_params->ContainsFlag( CRCD(0xf268c278,"SkateTheLine") ) )
+	{
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::GotGraffitiCluster( uint32 clusterId, int score )
+{
+    // find the cluster in our cluster array
+    Script::CArray* pClusterArray;
+    mp_params->GetArray( CRCD(0x191f66e9,"kill_clusters"), &pClusterArray, Script::ASSERT );
+
+    for ( uint32 i = 0; i < pClusterArray->GetSize(); i++ )
+    {
+        Script::CStruct* pCluster = pClusterArray->GetStructure( i );
+        Dbg_MsgAssert( pCluster, ( "got null cluster from kill_cluster array\n" ) );
+
+        uint32 id;
+        pCluster->GetChecksum( "id", &id, Script::ASSERT );
+		
+		int minScore;
+		// get the score for this cluster, or the default score
+		if ( !pCluster->GetInteger( CRCD(0xcd66c8ae,"score"), &minScore, Script::NO_ASSERT ) )
+			mp_params->GetInteger( CRCD(0xcd66c8ae,"score"), &minScore, Script::ASSERT );
+		
+		// printf("minScore: %i, score: %i\n", minScore, score);
+
+        if ( id == clusterId && score > minScore )
+        {
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Obj::CTrickObjectManager* p_trickObjectMan = skate_mod->GetTrickObjectManager();
+
+			// printf("modulating color\n");
+			if ( !p_trickObjectMan->ModulateTrickObjectColor( clusterId, 0 ) )
+				printf("ModulateTrickObjectColor didn't work\n");
+
+			uint32 flag;
+            pCluster->GetChecksum( CRCD(0x2e0b1465,"flag"), &flag, Script::ASSERT );
+
+            SetGoalFlag( flag, 1 );
+            return;
+        }
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsSpecialGoal()
+{
+    return ( mp_params->ContainsFlag( CRCD(0xb394c01c,"special") ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::CheckSpecialGoal()
+{
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    Obj::CSkater *p_Skater = skate_mod->GetLocalSkater();
+    Dbg_Assert( p_Skater );
+	
+	Obj::CSkaterScoreComponent* p_SkaterScoreComponent = GetSkaterScoreComponentFromObject(p_Skater);
+	Dbg_Assert( p_SkaterScoreComponent );
+    
+    Mdl::Score * p_Score = ( p_SkaterScoreComponent->GetScore() );
+    Dbg_Assert( p_Score );
+
+    const char* trick_string;
+    mp_params->GetString( CRCD(0x3a54cc4b,"trick_string"), &trick_string, Script::ASSERT );
+
+	// if it's a grind, we need to check for the FS and BS version too
+	uint32 trick_type;
+	mp_params->GetChecksum( CRCD(0x3d929b66,"trick_type"), &trick_type, Script::ASSERT );
+	if ( trick_type == CRCD( 0x255ed86f, "grind" ) )
+	{
+		char test_trick[128];
+		// frontside
+		strcpy( test_trick, "FS " );
+		strcat( test_trick, trick_string );
+		// printf("testing FS version - %s\n", test_trick);
+		if ( p_Score->GetCurrentNumberOfOccurrencesByName( Script::GenerateCRC( test_trick ) ) )
+		{
+			// printf("got it\n");
+			return true;
+		}
+
+		// backside
+		strcpy( test_trick, "BS " );
+		strcat( test_trick, trick_string );
+		// printf("testing BS version - %s\n", test_trick);
+		if ( p_Score->GetCurrentNumberOfOccurrencesByName( Script::GenerateCRC( test_trick ) ) )
+		{
+			// printf("got it\n");
+			return true;
+		}
+	}
+
+	// check the bare trick string
+	if ( mp_params->ContainsFlag( CRCD(0x8e6014f6,"create_a_trick") ) )
+	{
+		// printf("testing cat version %s\n", new_string );
+		if ( p_Score->GetCurrentNumberOfOccurrencesByName( Script::GenerateCRC( trick_string ) ) > 0 )
+		{
+			return true;
+		}
+	}
+	else
+	{
+		// printf("testing normal version - %s\n", trick_string);
+		if ( p_Score->GetCurrentNumberOfOccurrencesByName( Script::GenerateCRC( trick_string ) ) > 0 )
+		{
+			return true;
+		}
+	}
+    return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsTetrisGoal()
+{
+    return ( mp_params->ContainsFlag( CRCD(0x4147922b,"tetris") ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoal::GetRandomIndexFromKeyCombos( Script::CArray* p_keyCombos )
+{
+	Dbg_MsgAssert( p_keyCombos->GetType() == ESYMBOLTYPE_NAME ||
+				   p_keyCombos->GetType() == ESYMBOLTYPE_STRUCTURE,
+				   ( "GetRandomIndexFromKeyCombos got array of the wrong type" ) );
+
+	// get the global trick mapping
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* p_SkaterProfile = skate_mod->GetCurrentProfile();
+	Script::CStruct* p_trickMappings = p_SkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+
+	// figure number of components in mapping
+	uint32 size = p_keyCombos->GetSize();
+
+	// safety first
+	int max_tries = 100;
+	
+	uint32 trick_name_checksum = 0;
+	uint32 key_combo;
+	while ( !trick_name_checksum )
+	{        
+		int i = Mth::Rnd( size );
+		if ( p_keyCombos->GetType() == ESYMBOLTYPE_NAME )
+		{
+			key_combo = p_keyCombos->GetChecksum( i );
+		}
+		else
+			p_keyCombos->GetStructure( i )->GetChecksum( CRCD(0xacfdb27a,"key_combo"), &key_combo, Script::ASSERT );
+
+		// see if this is a valid key_combo
+		int cat_index;
+		p_trickMappings->GetChecksum( key_combo, &trick_name_checksum, Script::NO_ASSERT );
+		if ( trick_name_checksum )
+		{
+			return i;
+		}
+		else if ( p_trickMappings->GetInteger( key_combo, &cat_index, Script::NO_ASSERT ) )
+		{
+			return i;
+		}
+		// reset it to 0
+		trick_name_checksum = 0;
+		max_tries--;
+		if ( max_tries == 0 )
+		{
+			// they've probably un-mapped all their keys.  check all the tricks in order
+			// just to be safe
+			for ( uint32 j = 0; j < size; j++ )
+			{
+				if ( p_keyCombos->GetType() == ESYMBOLTYPE_NAME )
+					key_combo = p_keyCombos->GetChecksum( j );
+				else
+					p_keyCombos->GetStructure( j )->GetChecksum( CRCD(0xacfdb27a,"key_combo"), &key_combo, Script::ASSERT );
+				p_trickMappings->GetChecksum( key_combo, &trick_name_checksum, Script::NO_ASSERT );
+				if ( trick_name_checksum )
+				{
+					return i;
+				}
+				trick_name_checksum = 0;
+			}
+			// they did un-map all their keys.  Fuck 'em
+			NoValidKeyCombos();
+			break;
+		}
+	}
+	return -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::NoValidKeyCombos()
+{
+	Script::CStruct* p_temp_params = new Script::CStruct();
+	p_temp_params->AddChecksum( CRCD(0x9982e501,"goal_id"), GetGoalId() );
+	Script::RunScript( CRCD( 0xf3c20af5, "goal_no_valid_key_combos" ), p_temp_params );
+	delete p_temp_params;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+uint32 CGoal::GetRootGoalId()
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	CGoal* pParentGoal = this;	
+	Dbg_Assert( pGoalManager );
+	
+	// get the root node
+	CGoalLink* p_parent = mp_parents;
+	while ( p_parent && p_parent->m_relative != 0 )
+	{
+		pParentGoal = pGoalManager->GetGoal( p_parent->m_relative );
+		p_parent = pParentGoal->GetParents();
+	}
+
+	return pParentGoal->GetGoalId();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+uint32 CGoal::GetGoalId()
+{
+    uint32 goalId = 0;
+    mp_params->GetChecksum( "goal_id", &goalId, Script::ASSERT );
+    return goalId;
+}
+
+bool CGoal::IsEditedGoal()
+{
+	Dbg_MsgAssert(mp_params,("NULL mp_params"));
+	return mp_params->ContainsFlag(CRCD(0x981d3ad0,"edited_goal"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::HasWonGoal()
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();	
+	if ( gamenet_man->InNetGame() && 
+		 ( skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0xec200eaa, "netgoalattack" )) ||
+		 ( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x1c471c60,"netlobby")))
+	{
+		return m_netIsBeat;
+	}
+	return m_isbeat;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::HasWonStage()
+{
+	if ( HasWonGoal() )
+		return true;	
+	else
+	{
+		CGoalLink* pLink = mp_children;
+		while ( pLink )
+		{
+			if ( pLink->m_beaten )
+				return true;
+			else
+				pLink = pLink->mp_next;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsSelected()
+{
+	return m_isselected;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::GotCounterObject()
+{
+	int number_collected = 0;
+    mp_params->GetInteger( CRCD(0x129daa10,"number_collected"), &number_collected, Script::NO_ASSERT );
+    // printf("alredy collected %i\n", number_collected);
+    number_collected++;
+
+    mp_params->AddInteger( CRCD(0x129daa10,"number_collected"), number_collected );
+
+	if ( mp_params->ContainsFlag( CRCD(0xfd7ae1f4,"use_hud_counter") ) )
+		Script::RunScript( CRCD(0xf2e85a34,"goal_update_counter"), mp_params );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::CounterGoalDone()
+{
+    int number_collected = 0;
+    mp_params->GetInteger( CRCD(0x129daa10,"number_collected"), &number_collected, Script::NO_ASSERT );
+
+    int number;
+    mp_params->GetInteger( CRCD(0x696fe0ab,"number"), &number, Script::ASSERT );
+
+    return ( number_collected >= number );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::MarkInvalid()
+{
+    m_invalid = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsInvalid()
+{
+    return m_invalid;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::HasProset( const char* proset_prefix )
+{
+    const char* p_local_proset;
+    if ( mp_params->GetString( CRCD(0x955c6305,"proset_prefix"), &p_local_proset, Script::NO_ASSERT ) )
+    {
+        if ( stricmp( p_local_proset, proset_prefix ) == 0 )
+            return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetHasSeen()
+{
+    m_hasSeen = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::MarkBeaten()
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	
+	if( gamenet_man->InNetGame()  && skate_mod->GetGameMode()->GetNameChecksum() == CRCD( 0xec200eaa, "netgoalattack" ) )
+	{
+		m_netIsBeat = true;
+	}
+	else
+	{
+		m_isbeat = true;
+	}
+	// mark all goals in the tree beaten
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	Game::CGoalLink* p_link = mp_parents;
+	while ( p_link && p_link->m_relative != 0 )
+	{
+		CGoal* pGoal = pGoalManager->GetGoal( p_link->m_relative );
+		pGoal->MarkBeaten();
+		p_link = p_link->mp_next;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::MarkBeatenBy( int id )
+{
+	Dbg_Printf( "**** Marking goal 0x%x as beaten\n", GetGoalId());
+	m_beatenFlags.Set( id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::MarkBeatenByTeam( int team_id )
+{
+	m_teamBeatenFlags.Set( team_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::HasWonGoal( int id )
+{
+	return m_beatenFlags.Test( id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::HasTeamWonGoal( int team_id )
+{
+	return m_teamBeatenFlags.Test( team_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::Select()
+{
+	m_isselected = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::Deselect()
+{
+	m_isselected = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetUnlocked()
+{
+    m_isLocked = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetLevelNum( int levelNum )
+{
+    m_levelNum = levelNum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CGoal::GetLevelNum()
+{
+    return m_levelNum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::CheckMinigameRecord( int value )
+{
+	int record = 0;
+	mp_params->GetInteger( CRCD(0x78ffb85c,"minigame_record"), &record, Script::NO_ASSERT );
+
+	mp_params->AddInteger( CRCD(0x819fc1d1,"last_attempt"), value );
+	if ( value > record )
+	{
+		mp_params->AddInteger( CRCD(0x78ffb85c,"minigame_record"), value );
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CGoal::GetMinigameRecord()
+{
+	int minigame_record = -1;
+	mp_params->GetInteger( CRCD(0x78ffb85c,"minigame_record"), &minigame_record, Script::NO_ASSERT );
+	return minigame_record;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::CanRetry()
+{
+	return ( !mp_params->ContainsFlag( CRCD(0xa612f12c,"no_restart") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsMinigame()
+{
+	return ( mp_params->ContainsFlag( CRCD(0x6bae094c,"minigame") ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::SetStartTime()
+{
+	mp_params->AddInteger( CRCD(0x5fd80edc,"current_time"), 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::UpdateComboTimer()
+{
+	if ( !IsPaused() )
+	{
+		Tmr::Time current_time = 0;
+		int time;
+		mp_params->GetInteger( CRCD(0x5fd80edc,"current_time"), &time, Script::NO_ASSERT );
+		current_time = (Tmr::Time)time;
+	
+		current_time +=  (Tmr::Time)( Tmr::FrameLength() * 1000 );
+	
+		mp_params->AddInteger( CRCD(0x5fd80edc,"current_time"), (int)current_time );
+
+		// add normal units for display
+		mp_params->AddInteger( CRCD(0x7b1fe9a4,"current_time_seconds"), (int)( current_time / 1000 ) );
+		mp_params->AddInteger( CRCD(0xb4db9cb7,"current_time_fraction"), (int)( ( current_time / 10 ) % 100 ) );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetStartHeight()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater *p_skater = skate_mod->GetLocalSkater();
+	Dbg_Assert( p_skater );
+
+	int start_height = static_cast< int >(GetSkaterStateComponentFromObject(p_skater)->GetHeight());
+	mp_params->AddInteger( CRCD(0xc07d8d60,"start_height"), start_height );
+
+	// printf("setting start height to %i\n", start_height);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::CheckHeightRecord()
+{
+	if ( !IsPaused() )
+	{
+		int record = 0;
+		mp_params->GetInteger( CRCD(0x78ffb85c,"minigame_record"), &record, Script::NO_ASSERT );
+
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		Obj::CSkater *p_skater = skate_mod->GetLocalSkater();
+		Dbg_Assert( p_skater );
+
+		int start_height = 10000;
+		mp_params->GetInteger( CRCD(0xc07d8d60,"start_height"), &start_height, Script::NO_ASSERT );
+		
+		int current_height = static_cast< int >(GetSkaterStateComponentFromObject(p_skater)->GetHeight()) - start_height;
+
+		//printf("current_height: %i\n", current_height);
+
+		if ( current_height < 0 )
+			current_height = 0;		
+
+		int feet = (int)( current_height / 12 );
+		int inches = current_height % 12;
+
+		mp_params->AddInteger( CRCD(0x50815d42,"current_height_feet"), feet );
+		mp_params->AddInteger( CRCD(0xb0c26927,"current_height_inches"), inches );
+		
+		if ( current_height > record )
+		{
+			mp_params->AddInteger( CRCD(0x78ffb85c,"minigame_record"), current_height );
+			
+			mp_params->AddInteger( CRCD(0x24bf3ae4,"record_feet"), feet );
+			mp_params->AddInteger( CRCD(0x9b49f28d,"record_inches"), inches );
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::CheckDistanceRecord()
+{
+	// find the object in question
+	uint32 objId;
+	mp_params->GetChecksum( CRCD(0x8e18f3b3,"distance_record_object"), &objId, Script::ASSERT );
+
+	// grab the object
+	Obj::CMovingObject* pObj = NULL;
+//	pObj = Obj::CMovingObject::m_hash_table.GetItem( objId );
+	pObj = (Obj::CMovingObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( objId);
+
+	// grab the skater
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+	
+	if ( pSkater && pObj )
+	{
+		float distSquared = Mth::DistanceSqr( pObj->m_pos, pSkater->m_pos );
+		
+		// get the height of the two
+		float objHeight = pObj->m_pos.GetY();
+		float skaterHeight = pSkater->m_pos.GetY();
+
+		// get the base of the right triangle formed by the three points
+		float distance = sqrtf(	distSquared - ( Mth::Sqr( skaterHeight - objHeight ) ) );
+		// printf("distance = %f\n", distance);
+
+		int feet = (int)( distance / 12 );
+		int inches = (int)distance % 12;
+		
+		mp_params->AddInteger( CRCD(0xcb48c8e8,"current_distance_feet"), feet );
+		mp_params->AddInteger( CRCD(0x644fc146,"current_distance_inches"), inches );
+		
+		int record = 0;
+		mp_params->GetInteger( CRCD(0x78ffb85c,"minigame_record"), &record, Script::NO_ASSERT );		
+		if ( distance > record )
+		{
+			mp_params->AddInteger( CRCD(0x78ffb85c,"minigame_record"), distance );
+		
+			mp_params->AddInteger( CRCD(0x24bf3ae4,"record_feet"), feet );
+			mp_params->AddInteger( CRCD(0x9b49f28d,"record_inches"), inches );
+			return true;
+		}
+	}	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetQuickStartFlag()
+{
+	mp_params->AddChecksum( NONAME, CRCD( 0x38221df4, "quick_start" ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::UnsetQuickStartFlag()
+{
+	mp_params->RemoveFlag( CRCD(0x38221df4,"quick_start") );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsProGoal()
+{
+	return ( mp_params->ContainsComponentNamed( CRCD(0xd303a1a3,"pro_goal") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsNetGoal()
+{
+	return ( mp_params->ContainsComponentNamed( CRCD(0xd15ea00,"net") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsCompetition()
+{
+	return ( mp_params->ContainsFlag( CRCD(0x4af5d34e,"competition") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsPaused()
+{
+	return m_paused;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::UnBeatGoal()
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	if( gamenet_man->InNetGame() )
+	{
+		m_beatenFlags = 0;
+		m_teamBeatenFlags = 0;
+		if( m_netIsBeat )
+		{
+			m_netIsBeat = false;
+			return true;
+		}
+	}
+	else
+	{
+		if ( m_isbeat )
+		{
+			m_isbeat = false;
+			return true;
+		}
+	}    
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::GetViewGoalsText( const char** p_view_goals_text )
+{
+	mp_params->GetText( "view_goals_text", p_view_goals_text, Script::NO_ASSERT );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsBettingGame()
+{
+	return ( mp_params->ContainsFlag( CRCD(0x8add4306,"betting_game") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsBettingGuy()
+{
+	return ( mp_params->ContainsFlag( CRCD(0x8cfee956,"betting_guy") ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::EndRunCalled()
+{
+	return m_endRunCalled;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::ClearEndRun( Script::CScript* pScript )
+{
+	// printf("CGoal::ClearEndRun called\n");
+	m_endRunCalled = false;
+
+	// TODO: this should pick the right skater somehow
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* p_skater = skate_mod->GetLocalSkater();
+
+	Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(p_skater);
+	Dbg_Assert(p_skater_endrun_component);
+
+	switch ( m_endRunType )
+	{
+	case vENDOFRUN:
+// Mick: the way these type of exception is handled has changed
+// I'm not sure if it still needs to be cleared here any more
+// as it should have been executed immediately
+// I'm removing this line for now, as the ExceptionComponent has gone  
+//		GetExceptionComponentFromObject(p_skater)->ClearException( CRCD( 0x822e13a9, "RunHasEnded" ));
+		p_skater->CallMemberFunction( CRCD( 0x4c58771e, "EndOfRunDone" ), NULL, pScript );
+		p_skater_endrun_component->ClearStartedEndOfRunFlag();
+		break;
+	case vGOALENDOFRUN:
+//		GetExceptionComponentFromObject(p_skater)->ClearException( CRCD( 0xab676175, "GoalHasEnded" ) );
+		p_skater->CallMemberFunction( CRCD( 0x69a9e37f, "Goal_EndOfRunDone" ), NULL, pScript );
+		p_skater_endrun_component->ClearStartedGoalEndOfRunFlag();
+		break;
+	default:
+		Dbg_Assert( 0 );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::FinishedEndOfRun()
+{
+	// TODO: make this pick the right skater
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* p_skater = skate_mod->GetLocalSkater();
+
+	Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(p_skater);
+	Dbg_Assert(p_skater_endrun_component);
+
+	switch ( m_endRunType )
+	{
+	case vENDOFRUN:
+		return p_skater_endrun_component->RunHasEnded();
+		break;
+	case vGOALENDOFRUN:
+		return p_skater_endrun_component->GoalRunHasEnded();
+		break;
+	default:
+		Dbg_Assert( 0 );
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::StartedEndOfRun()
+{
+	// TODO: make this choose the right skater
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* p_skater = skate_mod->GetLocalSkater();
+
+	Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(p_skater);
+	Dbg_Assert(p_skater_endrun_component);
+
+	switch ( m_endRunType )
+	{
+	case vENDOFRUN:
+		return p_skater_endrun_component->StartedEndOfRun();
+		break;
+	case vGOALENDOFRUN:
+		// return p_skater->StartedGoalEndOfRun();
+		return m_endRunCalled;
+		break;
+	default:
+		Dbg_Assert( 0 );
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// void CGoal::DisableEndOfRun()
+// {
+//	m_shouldEndRun = false;
+// }
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::AddTempSpecialTrick()
+{
+	// get the new special trick name and type	
+	uint32 trickType;
+	bool is_cat = mp_params->ContainsFlag( CRCD(0x8e6014f6,"create_a_trick") );
+	
+	mp_params->GetChecksum( "trick_type", &trickType, Script::ASSERT );
+	uint32 trickName;	
+	if ( is_cat )
+	{
+		int tempTrickName;
+		mp_params->GetInteger( CRCD(0xb502200,"premade_cat_index"), &tempTrickName, Script::ASSERT );
+		trickName = (uint32)tempTrickName;
+	}
+	else
+		mp_params->GetChecksum( "trick_name", &trickName, Script::ASSERT );
+	
+	// get the local string version of the trick and store it
+	if ( is_cat )
+	{
+		Script::CArray* pPremadeCatArray = Script::GetArray( CRCD(0xffd7913f,"Premade_CATS"), Script::ASSERT );
+		Script::CStruct* pPremadeCat = pPremadeCatArray->GetStructure( trickName );
+		const char* pPremadeCatName;
+		pPremadeCat->GetString( CRCD(0xa1dc81f9,"name"), &pPremadeCatName, Script::ASSERT );
+		mp_params->AddString( CRCD(0x3a54cc4b,"trick_string"), pPremadeCatName );
+	}
+	else
+	{
+		const char* trickName_string; 
+		Script::CStruct* p_trick = Script::GetStructure( trickName, Script::ASSERT );
+		Script::CStruct* p_trick_params;
+		p_trick->GetStructure( "Params", &p_trick_params, Script::ASSERT );
+		p_trick_params->GetLocalString( "Name", &trickName_string, Script::ASSERT );
+		mp_params->AddString( "trick_string", trickName_string );
+	}
+	
+	// get the list of valid key bindings for this trick type
+	Script::CStruct* p_keyBindings = NULL;
+	switch ( trickType )
+	{
+		case CRCC( 0x61a1bc57, "cat" ):
+		case CRCC( 0x439f4704, "air" ):
+			p_keyBindings = Script::GetStructure( CRCD( 0x4764231d, "goal_special_tricks_air" ), Script::ASSERT );
+			break;
+		case CRCC( 0xa549b57b, "lip" ):
+			p_keyBindings = Script::GetStructure( CRCD( 0xa1b2d162, "goal_special_tricks_lip" ), Script::ASSERT );
+			break;
+		case CRCC( 0x255ed86f, "grind" ):
+			p_keyBindings = Script::GetStructure( CRCD( 0xf481d0cd, "goal_special_tricks_grind" ), Script::ASSERT );
+			break;
+		case CRCC( 0xef24413b, "manual" ):
+			p_keyBindings = Script::GetStructure( CRCD( 0xd72d5cf7, "goal_special_tricks_manual" ), Script::ASSERT );
+			break;
+		default:
+			Dbg_MsgAssert( p_keyBindings, ( "AddTempSpecialTrick got weird trick type\n" ) );
+	}
+
+	Obj::CPlayerProfileManager* pPlayerProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile = pPlayerProfileManager->GetCurrentProfile();
+	// Script::CStruct* p_trickMapping = p_SkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+
+	int numTricks = pSkaterProfile->GetNumSpecialTrickSlots();
+	
+	// we need to remember the key combo
+	uint32 key_combo = 0;
+
+	// search through current special tricks
+	bool found_trick = false;
+	for ( int i = 0; i < numTricks; i++ )
+	{
+		Obj::SSpecialTrickInfo trick_info = pSkaterProfile->GetSpecialTrickInfo( i );
+		
+		if ( !is_cat && trick_info.m_TrickName == trickName )
+		{
+			// printf("the special trick is already bound!\n");
+			found_trick = true;
+			key_combo = trick_info.m_TrickSlot;
+			break;
+		}
+		p_keyBindings->RemoveFlag( trick_info.m_TrickSlot );
+	}
+
+	// if we didn't find the trick, grab the first available, unused key combo
+	if ( !found_trick )
+	{
+		// printf("binding special trick\n");
+		Script::CComponent* p_comp = p_keyBindings->GetNextComponent();
+		Dbg_MsgAssert( p_comp, ( "Could not find an open key binding\n" ) );
+		uint32 trickSlot = p_comp->mChecksum;
+		Dbg_MsgAssert( trickSlot, ( "Could not find key binding in structure" ) );
+		
+		// give everyone a new slot
+		/*int num_profiles = pPlayerProfileManager->GetNumProfileTemplates();
+		for ( int i = 0; i < num_profiles; i++ )
+		{
+			Obj::CSkaterProfile* pTempProfile = pPlayerProfileManager->GetProfileTemplateByIndex( i );
+			pTempProfile->AwardSpecialTrickSlot();
+		}*/
+		pSkaterProfile->AwardSpecialTrickSlot();
+
+		key_combo = trickSlot;
+		if ( is_cat )
+		{
+			// assign cat to trickslot
+			Script::CStruct* pScriptParams = new Script::CStruct();
+			pScriptParams->AddChecksum( CRCD(0xa92a2280,"trickSlot"), trickSlot );
+			pScriptParams->AddInteger( CRCD(0x7f8c98fe,"index"), trickName );
+			pScriptParams->AddInteger( CRCD(0x7f264be9,"special_trick_index"), numTricks );
+			Script::RunScript( CRCD(0x466576c8,"load_premade_cat"), pScriptParams );
+			delete pScriptParams;
+		}
+		else
+		{
+			// add new trick to new slot
+			Obj::SSpecialTrickInfo trickInfo;
+			trickInfo.m_TrickSlot = trickSlot;
+			trickInfo.m_TrickName = trickName;
+			pSkaterProfile->SetSpecialTrickInfo( numTricks, trickInfo );
+		}
+
+		// make a note of this so we can remove it later
+		mp_params->AddInteger( CRCD( 0xbc678826, "should_remove_trick" ), 1 );
+	}
+
+	// testing
+	// Script::PrintContents( p_SkaterProfile->GetSpecialTricksStructure() );
+
+	// replace the key combo array
+	mp_params->RemoveComponent( "key_combos" );
+	Script::CArray* p_key_combos = new Script::CArray();
+	p_key_combos->SetSizeAndType( 1, ESYMBOLTYPE_NAME );
+
+	p_key_combos->SetChecksum( 0, key_combo );
+
+	mp_params->AddArrayPointer( "key_combos", p_key_combos );
+	// printf("added special trick\n");
+	// Script::PrintContents( mp_params );
+	ReplaceTrickText();
+	
+	return !found_trick;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::RemoveTempSpecialTrick()
+{
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CPlayerProfileManager* pPlayerProfileManager = skate_mod->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile = pPlayerProfileManager->GetCurrentProfile();
+
+	// do we need to take out the trick we added?
+	int should_remove;
+	mp_params->GetInteger( "should_remove_trick", &should_remove, Script::ASSERT );
+	if ( should_remove == 1 )
+	{
+		// printf("removing special trick\n");
+		// find it and take it out
+		uint32 trickName;
+		
+		if ( mp_params->ContainsFlag( CRCD(0x8e6014f6,"create_a_trick") ) )
+		{
+			// int premade_cat_index;
+			// mp_params->GetInteger( CRCD(0xb502200,"premade_cat_index"), &premade_cat_index, Script::ASSERT );
+			// trickName = (uint32)premade_cat_index;
+			
+			// temp cat tricks are always on 0
+			trickName = 0;
+		}
+		else
+			mp_params->GetChecksum( CRCD( 0xef6fb249, "trick_name" ), &trickName, Script::ASSERT );
+
+		// int numTricks = p_SkaterProfile->GetNumSpecialTrickSlots();
+		// just look through all tricks
+		for ( int i = 0; i < Obj::CSkaterProfile::vMAXSPECIALTRICKSLOTS; i++ )
+		{
+			Obj::SSpecialTrickInfo trick_info = pSkaterProfile->GetSpecialTrickInfo( i );
+			// printf("m_TrickName: %x, trickName: %x\n", trick_info.m_TrickName, trickName );
+			if ( trick_info.m_TrickName == trickName )
+			{
+				// printf("Removing special trick\n");
+				Obj::SSpecialTrickInfo new_info;
+				new_info.m_TrickName = CRCD( 0xf60c9090, "unassigned" );
+				new_info.m_TrickSlot = CRCD( 0xf60c9090, "unassigned" );
+				pSkaterProfile->SetSpecialTrickInfo( i, new_info );
+				mp_params->AddInteger( CRCD( 0xbc678826, "should_remove_trick" ), 0 );
+				break;
+			}
+		}
+		
+		// take a slot away from everyone
+		/*int num_profiles = pPlayerProfileManager->GetNumProfileTemplates();
+		for ( int i = 0; i < num_profiles; i++ )
+		{
+			Obj::CSkaterProfile* pTempProfile = pPlayerProfileManager->GetProfileTemplateByIndex( i );
+			pTempProfile->AwardSpecialTrickSlot( -1 );
+		}*/
+		pSkaterProfile->AwardSpecialTrickSlot( -1 );
+	}
+	
+	mp_params->RemoveComponent( CRCD(0x79704516,"key_combos") );
+
+	Dbg_MsgAssert( (int)pSkaterProfile->GetNumSpecialTrickSlots() <= Script::GetInteger( CRCD(0x20297d6f,"max_number_of_special_trickslots") ), ( "You have too many trickslots!" ) );
+	// Script::CStruct* pTest = pSkaterProfile->GetSpecialTricksStructure();
+	// printf("done removing\n");
+	// Script::PrintContents( pTest );
+
+	// remove the display string just in case
+	mp_params->RemoveComponent( "key_combo_string" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::ShouldUseTimer()
+{
+	int unlimited_time = 0;
+
+	mp_params->GetInteger( CRCD(0xf0e712d2,"unlimited_time"), &unlimited_time );
+
+	return ( unlimited_time == 0 );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::CountAsActive()
+{
+	return IsActive();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::LoadSaveData( Script::CStruct *pFlags )
+{
+	int hasBeaten = 0;
+	pFlags->GetInteger( "hasBeaten", &hasBeaten, Script::ASSERT );
+	if ( hasBeaten != 0 )
+		MarkBeaten();
+	
+	int isLocked = 1;
+	pFlags->GetInteger( "isLocked", &isLocked, Script::ASSERT );
+	if ( isLocked == 0 )
+	{
+		// special case for pro challenges
+/*		if ( mp_params->ContainsFlag( "pro_specific_challenge" ) && hasBeaten == 0 )
+		{
+			if ( mp_goalPed->ProIsCurrentSkater() )
+				SetUnlocked();
+		}
+		else
+*/
+		SetUnlocked();
+	}
+
+	int hasSeen = 0;
+	pFlags->GetInteger( "hasSeen", &hasSeen, Script::ASSERT );
+	if ( hasSeen != 0 )
+		SetHasSeen();
+	
+	
+	// check for a completion record
+	int record = 0;
+	pFlags->GetInteger( CRCD( 0xc22a2b72, "win_record" ), &record, Script::ASSERT );
+	mp_params->AddInteger( "win_record", record );
+	uint32 record_type;
+	if ( mp_params->GetChecksum( "record_type", &record_type, Script::NO_ASSERT ) )
+	{
+		if ( record_type == Script::GenerateCRC( "time" ) )
+			m_elapsedTimeRecord = record;
+		else if ( record_type == Script::GenerateCRC( "score" ) )
+			m_winScoreRecord = record;
+	}
+
+	pFlags->GetInteger( "timesLost", &m_numTimesLost, Script::ASSERT );
+	pFlags->GetInteger( "timesWon", &m_numTimesWon, Script::ASSERT );
+	pFlags->GetInteger( "timesStarted", &m_numTimesStarted, Script::ASSERT );
+
+	uint32 team_pro;
+	pFlags->GetChecksum( CRCD(0xced8a3a4,"team_pro"), &team_pro, Script::ASSERT );
+	if ( team_pro != 0 )
+	{
+		mp_params->AddChecksum( CRCD(0xced8a3a4,"team_pro"), team_pro );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::GetSaveData( Script::CStruct* pFlags )
+{
+	pFlags->AddInteger( "levelNum", GetLevelNum() );
+
+	int isLocked = IsLocked();
+	pFlags->AddInteger( "isLocked", isLocked );
+
+	int hasSeen = HasSeenGoal();
+	pFlags->AddInteger( "hasSeen", hasSeen );
+
+	int hasBeaten = HasWonGoal();
+	pFlags->AddInteger( "hasBeaten", hasBeaten );
+	
+/*	int isProSpecificChallenge = 0;
+	if ( mp_params->ContainsFlag( "pro_specific_challenge" ) )
+		isProSpecificChallenge = 1;
+	pFlags->AddInteger( "isProSpecificChallenge", isProSpecificChallenge );
+*/
+
+	int record = 0;
+	uint32 record_type;
+	if ( mp_params->GetChecksum( "record_type", &record_type, Script::NO_ASSERT ) )
+	{
+		if ( record_type == Script::GenerateCRC( "time" ) )
+			record = (int)m_elapsedTimeRecord;
+		else if ( record_type == Script::GenerateCRC( "score" ) )
+			record = m_winScoreRecord;
+	}
+	pFlags->AddInteger( "win_record", record );
+
+	// TODO: should this be removed for the release?
+	pFlags->AddInteger( "timesLost", m_numTimesLost );
+	pFlags->AddInteger( "timesWon", m_numTimesWon );
+	pFlags->AddInteger( "timesStarted", m_numTimesStarted );
+
+	uint32 team_pro = 0;
+	mp_params->GetChecksum( CRCD(0xced8a3a4,"team_pro"), &team_pro, Script::NO_ASSERT );
+	pFlags->AddChecksum( CRCD(0xced8a3a4,"team_pro"), team_pro );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// utility used by ReplaceTrickText
+bool fill_trick_and_key_combo_arrays( Script::CArray* p_key_combos, Script::CArray* p_key_combo_strings, Script::CArray* p_trick_names, int premade_cat_index )
+{	
+	// make sure the arrays are the right size and type
+	Dbg_MsgAssert( p_key_combos->GetSize() == p_key_combo_strings->GetSize(), ( "key combo string array wrong size" ) );
+	Dbg_MsgAssert( p_key_combos->GetSize() == p_trick_names->GetSize(),	( "trick name array wrong size" ) );
+	Dbg_MsgAssert( p_key_combo_strings->GetType() == ESYMBOLTYPE_STRING, ( "array is the wrong type" ) );
+	Dbg_MsgAssert( p_trick_names->GetType() == ESYMBOLTYPE_STRING, ( "array is the wrong type" ) );
+	
+	Script::CStruct* p_key_combo_map = Script::GetStructure( CRCD(0x8856c817,"goal_tetris_trick_text"), Script::ASSERT );
+	
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+	Script::CStruct* pTricks = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+
+	// Script::PrintContents( pTricks );
+
+	int size = p_key_combos->GetSize();
+	
+	for ( int i = 0; i < size; i++ )
+	{
+		uint32 key_combo = p_key_combos->GetChecksum( i );
+		// printf("checking for key combo %s\n", Script::FindChecksumName( key_combo ) );
+		uint32 trick_checksum = 0;
+		bool found_a_match = false;
+		int cat_trick_index = 0;
+		bool found_cat = false;
+		if ( pTricks->GetInteger( key_combo, &cat_trick_index, Script::NO_ASSERT ) )
+		{
+			// load cat info
+			printf("found a cat trick - cat_trick_index = %i\n", cat_trick_index);
+			found_cat = true;
+			found_a_match = true;
+		}
+		else if ( pTricks->GetChecksum( key_combo, &trick_checksum, Script::NO_ASSERT ) )
+		{
+			found_a_match = true;
+		}
+		else
+		{
+			// look for a special key combo
+			int numSpecials = pSkaterProfile->GetNumSpecialTrickSlots();
+			// printf("checking special trick slots\n");
+			for ( int j = 0; j < numSpecials; j++ )
+			{
+				Obj::SSpecialTrickInfo trick_info = pSkaterProfile->GetSpecialTrickInfo( j );
+				// printf("trickslot: %s\n", Script::FindChecksumName( trick_info.m_TrickSlot ) );
+				if ( trick_info.m_TrickSlot == key_combo )
+				{
+					// printf("found a match\n");
+					trick_checksum = trick_info.m_TrickName;
+					found_a_match = true;
+					break;
+				}
+			}
+		}
+
+		if ( !found_a_match )
+		{
+			// Script::PrintContents( pTricks );
+			// this key combo isn't assigned!  bail out...
+			Dbg_MsgAssert( 0, ( "key combo %s not assigned!\n", Script::FindChecksumName( key_combo ) ) );
+			return false;
+		}
+
+		const char* p_key_combo_string;
+		p_key_combo_map->GetString( key_combo, &p_key_combo_string, Script::ASSERT );
+		const char* p_trick_name;
+		if ( found_cat )
+		{            
+			// printf("getting cat params\n");
+			Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+			if ( pSkater )
+			{
+				// printf("got skater\n");
+				Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[cat_trick_index];
+				Dbg_Assert( pCreatedTrick );
+				// Script::PrintContents( pCreatedTrick->mp_other_params );
+				pCreatedTrick->mp_other_params->GetString( CRCD( 0xa1dc81f9, "name" ), &p_trick_name, Script::ASSERT );
+			}
+		}
+		else
+		{
+			if ( premade_cat_index > -1 )
+			{
+				Script::CArray* pPremadeCats = Script::GetArray( CRCD(0xffd7913f,"Premade_CATS"), Script::ASSERT );
+				Script::CStruct* pPremadeCat = pPremadeCats->GetStructure( premade_cat_index );
+				pPremadeCat->GetString( CRCD(0xa1dc81f9,"name"), &p_trick_name, Script::ASSERT );
+			}
+			else
+			{
+				Script::CStruct* p_trick_struct = Script::GetStructure( trick_checksum, Script::ASSERT );
+				Script::CStruct* p_trick_params;
+				p_trick_struct->GetStructure( CRCD(0x7031f10c,"Params"), &p_trick_params, Script::ASSERT );
+				p_trick_params->GetLocalString( CRCD(0xa1dc81f9,"Name"), &p_trick_name, Script::ASSERT );
+			}
+		}
+
+		// printf("setting strings: %s\n%s\n", p_key_combo_string, p_trick_name );
+		
+		p_key_combo_strings->SetString( i, Script::CreateString( p_key_combo_string ) );
+		p_trick_names->SetString( i, Script::CreateString( p_trick_name ) );
+	}
+	return true;
+}
+
+// utility used by CGoal::ReplaceTrickText...finds and replaces \t and \k with
+// the trick name and key combo, respectively
+void find_and_replace_trick_string( const char* p_old, char* p_out, Script::CArray* p_key, Script::CArray* p_trick, uint out_buffer_size )
+{
+	#ifdef	__NOPT_ASSERT__
+	const char *p_out_start = p_out;
+	#endif
+	const char *p_in = p_old;	
+	while( *p_in )
+	{
+		int tag_num;
+		if ( *p_in == '\\' && *(p_in+1) == 't' && ( *(p_in+2) <= '9' && *(p_in+2) >= '0' ) )
+		{
+			// printf("found the trick tag\n");
+			
+			// get the index
+			tag_num = *(p_in+2) - 48;
+			if ( *(p_in+3) <= '9' && *(p_in+3) >= '0' )
+			{
+				tag_num *= 10;
+				tag_num += *(p_in+3) - 48;
+				
+				// move ahead an extra char
+				p_in++;
+			}
+			// printf( "found a tag num of %i\n", tag_num );
+			Dbg_MsgAssert( ( tag_num > 0 ) && tag_num <= (int)p_trick->GetSize(), ( "Tag value too large" ) );
+			const char* p_trick_string = p_trick->GetString( tag_num - 1 );
+
+			Dbg_MsgAssert( p_trick_string, ( "ReplaceTrickText found a \\t tag but no key combo." ) );
+
+			sprintf( p_out, p_trick_string );
+			int length = strlen( p_trick_string );
+			for ( int i = 0; i < length; i++ )
+				p_out++;
+			
+			// move past the tag
+			p_in++;
+			p_in++;
+			p_in++;
+		}
+		else if ( *p_in == '\\' && *(p_in+1) == 'k' && ( *(p_in+2) <= '9' && *(p_in+2) >= '0' ) )
+		{
+			// printf("found the key tag\n");
+			
+			// get the index
+			tag_num = *(p_in+2) - 48;
+			if ( *(p_in+3) <= '9' && *(p_in+3) >= '0' )
+			{
+				tag_num *= 10;
+				tag_num += *(p_in+3) - 48;
+				
+				// move ahead an extra char
+				p_in++;
+			}
+			// printf( "found a tag num of %i\n", tag_num );
+			Dbg_MsgAssert( ( tag_num > 0 ) && tag_num <= (int)p_key->GetSize(), ( "Tag value too large" ) );
+			const char* p_key_string = p_key->GetString( tag_num - 1 );
+
+			Dbg_MsgAssert( p_key_string, ( "ReplaceTrickText found a \\k tag but no key combo." ) );
+
+			sprintf( p_out, p_key_string );
+			int length = strlen( p_key_string );
+			for ( int i = 0; i < length; i++ )
+				p_out++;
+			
+			// move past the tag
+			p_in++;
+			p_in++;
+			p_in++;
+		}
+		else
+		{
+			*p_out++ = *p_in++;
+		}
+	}
+	*p_out++ = '\0';
+	Dbg_MsgAssert(strlen(p_out_start) < out_buffer_size,("String overflow with\n\n%s\n\nMake it shorter than %d chars, or get a bigger buffer",p_out_start, out_buffer_size));
+}
+
+bool CGoal::ReplaceTrickText()
+{
+	// printf("ReplaceTrickText %s\n", Script::FindChecksumName( GetGoalId() ) );
+	// see if there's a key_combo associated with this goal
+	Script::CArray* p_key_combo = NULL;
+	if ( !mp_params->GetArray( "key_combos", &p_key_combo, Script::NO_ASSERT ) )
+	{
+		// printf("no key combos\n");
+		return false;
+	}
+	
+	// Make sure we have copies of the original text.  We want
+	// to update properly if they change their trick bindings.
+	if ( !mp_params->ContainsComponentNamed( "original_goal_description" ) )
+	{
+		const char* p_ogd;
+		Script::CArray* p_ogd_array;
+		if ( mp_params->GetString( "goal_description", &p_ogd, Script::NO_ASSERT ) )
+			mp_params->AddString( "original_goal_description", p_ogd );
+		
+		else if ( mp_params->GetArray( "goal_description", &p_ogd_array, Script::ASSERT ) )
+			mp_params->AddArray( "original_goal_description", p_ogd_array );
+	}
+	if ( !mp_params->ContainsComponentNamed( "original_view_goals_text" ) )
+	{
+		const char* p_ovgt;
+		if ( mp_params->GetString( "view_goals_text", &p_ovgt, Script::NO_ASSERT ) )
+			mp_params->AddString( "original_view_goals_text", p_ovgt );
+	}
+	if ( !mp_params->ContainsComponentNamed( "original_goal_text" ) )
+	{
+		const char* p_ogt;
+		if ( mp_params->GetString( "goal_text", &p_ogt, Script::NO_ASSERT ) )
+			mp_params->AddString( "original_goal_text", p_ogt );
+	}
+	if ( !mp_params->ContainsComponentNamed( "original_key_combo_text" ) )
+	{
+		const char* p_okct;
+		if ( mp_params->GetString( "key_combo_text", &p_okct, Script::NO_ASSERT ) )
+			mp_params->AddString( "original_key_combo_text", p_okct );
+	}
+
+	// special case for cat goal
+	int premade_cat_index = -1;
+	if ( mp_params->ContainsFlag( CRCD(0x8e6014f6,"create_a_trick") ) )
+	{
+		mp_params->GetInteger( CRCD(0xb502200,"premade_cat_index"), &premade_cat_index, Script::ASSERT );
+	}
+
+	// get the new string arrays
+	Script::CArray* p_key = new Script::CArray();
+	Script::CArray* p_trick = new Script::CArray();
+	int size = p_key_combo->GetSize();
+	p_key->SetSizeAndType( size, ESYMBOLTYPE_STRING );
+	p_trick->SetSizeAndType( size, ESYMBOLTYPE_STRING );
+	if ( !fill_trick_and_key_combo_arrays( p_key_combo, p_key, p_trick, premade_cat_index ) )
+	{
+		Script::CleanUpArray( p_key );
+		Script::CleanUpArray( p_trick );
+		delete p_key;
+		delete p_trick;
+		return false;
+	}
+	
+	const char* p_string;
+	Script::CArray* p_string_array = NULL;
+
+	char new_string[Game::NEW_STRING_LENGTH];
+	
+	// replace the goal description
+	if ( mp_params->GetString( "original_goal_description", &p_string, Script::NO_ASSERT ) )
+	{
+		find_and_replace_trick_string( p_string, new_string, p_key, p_trick, Game::NEW_STRING_LENGTH );
+		mp_params->AddString( "goal_description", p_string );
+	}
+	else if ( mp_params->GetArray( "original_goal_description", &p_string_array, Script::ASSERT ) )
+	{
+		int size = p_string_array->GetSize();
+		Script::CArray* p_new_string_array = new Script::CArray();
+		p_new_string_array->SetSizeAndType( size, ESYMBOLTYPE_STRING );
+		for ( int i = 0; i < size; i++ )
+		{
+			p_string = p_string_array->GetString( i );
+			find_and_replace_trick_string( p_string, new_string, p_key, p_trick, Game::NEW_STRING_LENGTH );
+			p_new_string_array->SetString( i, Script::CreateString( new_string ) );
+		}
+		mp_params->AddArray( "goal_description", p_new_string_array );
+		Script::CleanUpArray( p_new_string_array );
+		delete p_new_string_array;
+	}
+
+	// replace the view goals text
+	if ( mp_params->GetString( "original_view_goals_text", &p_string, Script::NO_ASSERT ) )
+	{
+		find_and_replace_trick_string( p_string, new_string, p_key, p_trick, Game::NEW_STRING_LENGTH );
+		mp_params->AddString( "view_goals_text", new_string );
+	}
+	
+	// replace the goal text
+	if ( mp_params->GetString( "original_goal_text", &p_string, Script::NO_ASSERT ) )
+	{
+		find_and_replace_trick_string( p_string, new_string, p_key, p_trick, Game::NEW_STRING_LENGTH );
+		mp_params->AddString( "goal_text", new_string );
+	}
+
+	// replace the key combo text
+	if ( mp_params->GetString( "original_key_combo_text", &p_string, Script::NO_ASSERT ) )
+	{
+		find_and_replace_trick_string( p_string, new_string, p_key, p_trick, Game::NEW_STRING_LENGTH );
+		mp_params->AddString( "key_combo_text", new_string );
+	}
+
+	// cleanup!
+	Script::CleanUpArray( p_key );
+	Script::CleanUpArray( p_trick );
+	delete p_key;
+	delete p_trick;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CGoal::GetNumberCollected()
+{
+	int number_collected = 0;
+	
+	// check for special counter goal num
+	if ( mp_params->GetInteger( CRCD(0x129daa10,"number_collected"), &number_collected, Script::NO_ASSERT ) )
+		return number_collected;
+
+	// return the number of flags set
+	return m_flagsSet;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CGoal::GetNumberOfFlags()
+{
+	int num_flags_to_win = m_numflags;
+	mp_params->GetInteger( CRCD(0x1171a555,"num_flags_to_win"), &num_flags_to_win, Script::NO_ASSERT );
+	if ( num_flags_to_win != 0 )
+		return num_flags_to_win;
+	return -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::ColorTrickObjects( int seqIndex, bool clear )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CTrickObjectManager* p_trickObjMan = skate_mod->GetTrickObjectManager();
+	
+	Script::CArray* p_clusters = NULL;
+	if ( !mp_params->GetArray( "kill_clusters", &p_clusters, Script::NO_ASSERT ) )
+		return;
+
+	int numClusters = p_clusters->GetSize();
+	for ( int i = 0; i < numClusters; i++ )
+	{
+		Script::CStruct* p_temp = p_clusters->GetStructure( i );
+		uint32 cluster_id;
+		p_temp->GetChecksum( "id", &cluster_id, Script::ASSERT );
+		
+		Obj::CTrickCluster* p_trickCluster = p_trickObjMan->GetTrickCluster( cluster_id );
+		if ( p_trickCluster )
+		{
+			if ( clear )
+			{
+				// printf("clearing cluster %s\n", Script::FindChecksumName( cluster_id ) );
+				Obj::CTrickCluster* p_trickCluster = p_trickObjMan->GetTrickCluster( cluster_id );
+				if ( p_trickCluster )
+					p_trickCluster->ClearCluster( seqIndex );
+			}
+			else
+				p_trickObjMan->GetTrickCluster( cluster_id )->ModulateTrickObjectColor( seqIndex );
+		}
+		else
+			Dbg_Message( "WARNING: cluster %s not found\n", Script::FindChecksumName( cluster_id ) );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CGoal::GetNumberOfTimesGoalStarted()
+{
+	return m_numTimesStarted;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::GetRewardsStructure( Script::CStruct *p_structure, bool give_cash )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	
+	if ( !mp_params->ContainsFlag( CRCD(0x981d3ad0,"edited_goal") ) )
+		pGoalManager->AddGoalPoint();
+
+	p_structure->AddChecksum( NONAME, CRCD( 0xc7fef529, "awarded_goal_point" ) );
+
+	// No rewards in net games, only fanfare
+	if( gamenet_man->InNetGame() == false )
+	{
+		// special case for special trick goal
+		if ( mp_params->ContainsFlag( "reward_trickslot" ) && !mp_params->ContainsFlag( CRCD( 0x8e6014f6, "create_a_trick" ) ) )
+		{
+			// printf("found trickslot flag\n");
+			Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+			Obj::CSkaterProfile* p_SkaterProfile = skate_mod->GetCurrentProfile();
+			int num_slots = p_SkaterProfile->GetNumSpecialTrickSlots();
+			if ( num_slots <= Script::GetInteger( CRCD(0x20297d6f,"max_number_of_special_trickslots") ) )
+			{
+				// printf("has less than the max number of slots\n");
+				int should_remove = 0;
+				mp_params->GetInteger( "should_remove_trick", &should_remove, Script::NO_ASSERT );
+				if ( should_remove == 1 )
+				{
+					// printf("was going to remove the special trick, but don't!\n");
+					mp_params->AddInteger( "should_remove_trick", 0 );
+					// p_reward_params->AddChecksum( NONAME, CRCD( 0x65ca5ffc, "already_added_trickslot" ) );
+					mp_params->AddChecksum( NONAME, CRCD( 0x65ca5ffc, "already_added_trickslot" ) );
+					mp_params->AddInteger( CRCD(0xb45fd352,"just_added_trickslot"), 1 );
+				}					
+			}
+		}		
+		
+		// ************************
+		// new global reward stuff
+		// ************************
+		// grab the list of rewards
+		Script::CStruct* p_all_rewards = Script::GetStructure( "goal_rewards", Script::ASSERT );
+		Script::CStruct* p_rewards = NULL;
+		p_all_rewards->GetStructure( GetGoalId(), &p_rewards, Script::NO_ASSERT );
+		if ( p_rewards )
+		{
+			// map the reward_stat flag to an int
+			if ( p_rewards->ContainsFlag( "reward_stat" ) )
+			{
+				p_rewards->RemoveComponent( "reward_stat" );
+				p_rewards->AddInteger( "reward_stat", 1 );
+			}
+
+			p_structure->AppendStructure( p_rewards );
+			
+			if ( give_cash )
+			{
+				int reward_cash;
+				if ( p_rewards->GetInteger( "reward_cash", &reward_cash, Script::NO_ASSERT ) )
+					p_structure->AddInteger( "cash", reward_cash );
+			}
+		}
+		// **************************
+		// old reward stuff
+		// **************************
+		else
+		{
+			if ( give_cash )
+			{
+				int reward_cash;
+				// give 'em cash
+				if ( mp_params->GetInteger( "reward_cash", &reward_cash, Script::NO_ASSERT ) )
+					p_structure->AddInteger( "cash", reward_cash );
+			}
+
+			// stat points
+			int stat_points;
+			if ( mp_params->ContainsFlag( "reward_stat" ) )
+				p_structure->AddInteger( "reward_stat", 1 );
+			else if ( mp_params->GetInteger( "reward_stat", &stat_points, Script::NO_ASSERT ) )
+				p_structure->AddInteger( "reward_stat", stat_points );
+
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::UnlockRewardGoals()
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	
+	// unlock a new goal
+	Script::CArray* p_new_goal_array = NULL;
+	uint32 new_goal_checksum = 0;
+
+	if ( mp_params->GetChecksum( "reward_goal", &new_goal_checksum, Script::NO_ASSERT ) )
+		pGoalManager->UnlockGoal( new_goal_checksum );
+
+	// check for an array of goals to unlock
+	else if ( mp_params->GetArray( "reward_goal", &p_new_goal_array, Script::NO_ASSERT ) )
+	{
+		int num_goals_to_unlock = p_new_goal_array->GetSize();
+		for ( int i = 0; i < num_goals_to_unlock; i++ )
+		{
+			new_goal_checksum = p_new_goal_array->GetChecksum( i );
+			if ( new_goal_checksum )
+				pGoalManager->UnlockGoal( new_goal_checksum );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetEndRunType( EndRunType newEndRunType )
+{
+	m_endRunType = newEndRunType;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoal::SetShouldDeactivateOnExpire( bool should_deactivate )
+{
+	m_deactivateOnExpire = should_deactivate;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::IsHorseGoal()
+{
+	return mp_params->ContainsFlag( CRCD(0x9d65d0e7,"horse") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::IsFindGapsGoal()
+{
+    return ( mp_params->ContainsFlag( CRCD( 0xc5ec08e6, "findGaps" ) ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoal::ShouldRequireCombo()
+{
+	return mp_params->ContainsFlag( CRCD(0xf8e31760,"require_combo") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoal::AppendDifficultyLevelParams()
+{
+	Script::CArray* pLevelParams;
+	if ( mp_params->GetArray( CRCD( 0x85104ab3, "difficulty_level_params" ), &pLevelParams, Script::NO_ASSERT ) )
+	{
+		CGoalManager* pGoalManager = GetGoalManager();
+		Dbg_Assert( pGoalManager );
+		Game::GOAL_MANAGER_DIFFICULTY_LEVEL difficulty_level = pGoalManager->GetDifficultyLevel();
+		Dbg_MsgAssert( difficulty_level < (int)pLevelParams->GetSize(), ( "Difficulty params array has wrong size in goal %s", Script::FindChecksumName( GetGoalId() ) ) );
+		mp_params->AppendStructure( pLevelParams->GetStructure( difficulty_level ) );
+		CreateGoalFlags( mp_params );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoal::RunCallbackScript( uint32 script_name )
+{
+#if 1
+	uint32 checksum;
+	if ( mp_params->GetChecksum( script_name, &checksum, Script::NO_ASSERT ) )
+	{
+		Script::RunScript( checksum, mp_params );
+		return true;
+    }
+#else
+	Script::CStruct* pStruct;
+	if ( mp_params->GetStructure( script_name, &pStruct, Script::NO_ASSERT ) )
+	{
+		// target script name is required
+		uint32 checksum;
+		pStruct->GetChecksum( CRCD(0xb990d003,"target"), &checksum, Script::ASSERT );
+
+		// but not the params...
+		Script::CStruct* pSubStruct = NULL;
+		pStruct->GetStructure( CRCD(0x7031f10c,"params"), &pSubStruct, Script::NO_ASSERT );
+
+		if ( pParams )
+			pSubStruct->AppendStructure( pParams );
+		
+		Script::RunScript( checksum, pSubStruct );
+		return true;
+	}
+#endif
+	else
+	{
+		return false;
+	}
+}
+
+}	// namespace Game
diff --git a/Code/Sk/Modules/Skate/Goal.h b/Code/Sk/Modules/Skate/Goal.h
new file mode 100644
index 0000000..679877e
--- /dev/null
+++ b/Code/Sk/Modules/Skate/Goal.h
@@ -0,0 +1,265 @@
+#ifndef	__MODULES_SKATE_GOAL_H
+#define	__MODULES_SKATE_GOAL_H
+
+                           
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#ifndef __SYS_TIMER_H
+    #include  // For Tmr::Time
+#endif
+							  
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+
+namespace Game
+{
+	enum EndRunType
+	{
+		vGOALENDOFRUN,
+		vENDOFRUN,
+		vNONE,
+	};
+
+	enum
+	{
+		GOAL_INHERITED_FLAGS_ONE_TIMER		= ( 1 << 1  ),
+		GOAL_INHERITED_FLAGS_RETRY_STAGE	= ( 1 << 2  ),
+	};
+	
+// *************************************************************
+//			Class def
+// *************************************************************
+
+class CGoalLink
+{
+public:
+	CGoalLink();
+	~CGoalLink();
+	
+	uint32	   	m_relative;
+	CGoalLink*	mp_next;
+	bool		m_beaten;
+};
+
+class CGoal : public  Lst::Node
+{
+public:
+	CGoal();
+	CGoal(Script::CStruct* pParams);
+	~CGoal();
+
+public:
+	virtual bool		IsExpired();
+	bool				IsExpired( bool force_end );
+	virtual bool		ShouldExpire();
+	
+	virtual bool		IsActive();
+	virtual bool		CountAsActive(); // eg, minigames don't count
+    virtual void		Expire();
+
+	virtual void		LoadSaveData( Script::CStruct* pFlags );
+	virtual void		GetSaveData( Script::CStruct* pFlags );
+
+	void				AddChild( uint32 id );
+	void				AddParent( uint32 id );
+	void				ResetGoalLinks();
+	void				DeleteLinks( CGoalLink* p_list );
+
+	void                MarkInvalid();
+    bool                IsInvalid(); 
+	void				SetTimer();
+	void				AddTime( int time );
+	void				SetTimer( int time );
+	int					GetTimeLeft( void );
+	virtual bool		ShouldUseTimer();
+	virtual bool		Activate();
+	virtual void		SetActive();
+	virtual bool		Deactivate( bool force = false, bool affect_tree = true );
+	virtual void		SetInactive();
+	virtual bool		Win();
+	virtual bool		Lose();
+	bool				IsProGoal();
+	bool				IsNetGoal();
+
+	bool				RunCallbackScript( uint32 script_name );
+	Script::CStruct*	GetParams();
+	bool				EditParams(Script::CStruct* pParams);
+	virtual bool		Update();
+
+    bool                HasSeenGoal();
+    void                PauseGoal();
+    void                UnPauseGoal();
+	virtual bool		IsPaused();
+    Tmr::Time           GetTime();
+    // void                CreateGoalFlag( uint32 flag );
+    bool                SetGoalFlag( uint32 flag, int value );
+	bool				GetCreatedGoalGap(int gapIndex);
+	
+    void                ResetGoalFlags();
+	void				CreateGoalFlags( Script::CStruct* pParams );
+    bool                GoalFlagEquals( uint32 flag, int value );
+    bool                AllFlagsSet();
+	bool				GoalFlagSet( uint32 flag );
+    void                Init();
+	void                Uninit( bool propogate = false, int recurse_level = 0 );
+    bool                IsLocked();
+    void                UnlockGoal();
+    uint32              GetGoalId();
+	uint32              GetRootGoalId();
+	bool				IsEditedGoal();
+	void				GetViewGoalsText( const char** p_view_goals_text );
+    virtual bool        HasWonGoal();
+	virtual bool        HasWonGoal( int id );
+	virtual bool        HasTeamWonGoal( int team_id );
+	virtual bool		HasWonStage();
+	bool				IsSelected();
+	virtual bool		IsInitialized() { return m_isInitialized; }
+
+    bool                NextTourSpot();
+    void                InitTrickObjects();
+
+	void				GotTrickObject( uint32 clusterId );
+	bool				IsGraffitiGoal();
+	bool				ShouldLogTrickObject();
+    void                GotGraffitiCluster( uint32 clusterId, int score );
+    bool                IsSpecialGoal();
+    bool                IsTetrisGoal();
+    bool                CheckSpecialGoal();
+    
+    void                GotCounterObject();
+    bool                CounterGoalDone();
+    bool                HasProset( const char* proset_prefix );
+	bool				CheckMinigameRecord( int value );
+	int					GetMinigameRecord();
+	bool				IsMinigame();
+	bool				IsBettingGame();
+	bool				IsBettingGuy();
+	virtual bool		CanRetry();
+	void				SetStartTime();
+	void				UpdateComboTimer();
+
+    void                SetHasSeen();
+    virtual void        MarkBeaten();
+	virtual void		MarkBeatenBy( int id );
+	virtual void		MarkBeatenByTeam( int team_id );
+	void				Select();
+	void				Deselect();
+    void                SetUnlocked();
+    void                SetLevelNum( int levelNum );
+    int                 GetLevelNum();
+	void				SetStartHeight();
+	bool				CheckHeightRecord();
+	bool				CheckDistanceRecord();
+
+	void 				SetQuickStartFlag();
+	void				UnsetQuickStartFlag();
+
+	bool				IsCompetition();
+
+	bool				AllSkatersAtEndOfRun();
+	bool				EndRunCalled();
+	void				ClearEndRun( Script::CScript* pScript );
+	bool				FinishedEndOfRun();
+	bool				StartedEndOfRun();
+	void				SetEndRunType( EndRunType newEndRunType );
+
+	virtual bool		UnBeatGoal();
+
+	void				NoValidKeyCombos();
+	int 				GetRandomIndexFromKeyCombos( Script::CArray* p_KeyCombos );
+
+	bool				AddTempSpecialTrick();
+	void				RemoveTempSpecialTrick();
+
+	bool				ReplaceTrickText();
+
+	int					GetNumberCollected();
+	int					GetNumberOfFlags();
+
+	void				ColorTrickObjects( int seqIndex, bool clear = false );
+
+	int					GetNumberOfTimesGoalStarted();
+
+	void				GetRewardsStructure( Script::CStruct *p_structure, bool give_cash = true );
+	void				UnlockRewardGoals();
+
+	void				SetShouldDeactivateOnExpire( bool should_deactivate );
+
+	bool				IsHorseGoal();
+	bool				IsFindGapsGoal();
+
+	// family stuff	
+	Game::CGoalLink*	GetChildren() { return mp_children; }
+	Game::CGoalLink*	GetParents() { return mp_parents; }
+
+	void				SetInheritableFlags( int recurse_level = 0 );
+
+	Game::CGoalPed*		mp_goalPed;
+
+	// the inhereted flags of any root node
+	// in a family branch will be set in the goals of all offspring
+	uint32				m_inherited_flags;
+
+	// chatper/stage stuff
+	void				SetChapter( int chapter ) { m_chapter = chapter; }
+	void				SetStage( int stage ) { m_stage = stage; }
+	int					GetChapter() { return m_chapter; }
+	int					GetStage() { return m_stage; }
+
+	bool				ShouldRequireCombo();
+
+	void				AppendDifficultyLevelParams();
+protected:	
+	Script::CStruct*	mp_params;
+    Script::CStruct*	mp_goalFlags;
+	
+	EndRunType			m_endRunType;
+
+	Game::CGoalLink*	mp_children;
+	Game::CGoalLink*	mp_parents;
+	
+	// members stored by goal manager on level unload
+    int                 m_levelNum;
+	Flags< int >		m_beatenFlags;
+	Flags< int >		m_teamBeatenFlags;
+	int					m_numTimesLost;
+	int					m_numTimesWon;
+	int					m_numTimesStarted;
+	Tmr::Time			m_elapsedTimeRecord;
+	int					m_winScoreRecord;
+    Tmr::Time           m_timeLeft;
+	int					m_ownerId;
+    int                 m_numflags;
+    int                 m_flagsSet;
+	Tmr::Time			m_elapsedTime;
+	int					m_chapter;
+	int					m_stage;
+    
+	bool				m_isInitialized;
+	bool                m_hasSeen;
+    bool                m_isbeat;
+	bool				m_netIsBeat;
+	bool				m_isselected;
+    bool                m_isLocked;
+	bool				m_deactivateOnExpire;
+	bool				m_endRunCalled;
+	bool				m_active;
+    bool                m_invalid;
+    bool                m_paused;
+	bool				m_isOwned;
+    bool                m_unlimitedTime;
+};
+
+}	// namespace Game
+
+#endif // __MODULES_SKATE_GOAL_H
diff --git a/Code/Sk/Modules/Skate/GoalManager.cpp b/Code/Sk/Modules/Skate/GoalManager.cpp
new file mode 100644
index 0000000..9c05c18
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GoalManager.cpp
@@ -0,0 +1,7132 @@
+//****************************************************************************
+//* MODULE:         Sk/Modules/Skate
+//* FILENAME:       GoalManager.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  1/24/2001
+//****************************************************************************
+
+// start autoduck documentation
+// @DOC goalmanager
+// @module goalmanager | None
+// @subindex Scripting Database
+// @index script | goalmanager
+							   
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include        // for tetris tricks
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+								
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Game
+{
+
+	extern bool fill_trick_and_key_combo_arrays( Script::CArray* p_key_combos, Script::CArray* p_key_combo_strings, Script::CArray* p_trick_names, int premade_cat_index );
+	extern void find_and_replace_trick_string( const char* p_old, char* p_out, Script::CArray* p_key, Script::CArray* p_trick, uint out_buffer_size );
+	
+// TODO:
+// Uber time limit should be its own goal?
+// Each of the skate letters can either be its own goal
+// or maybe we can grab the goal state in script somehow
+
+// maybe there's some kind of goal factory
+
+// TODO:  Maybe the script should be spawned so that it can wait a while...
+// or maybe it could run a script that spawns a script...
+// or maybe a combination of the two?
+
+// TODO:  Edit goal.  changes the state of the goal, maybe to track flags and such?
+// or maybe a thing can be made of 5 subgoals?
+
+// TODO:  Need a way of binding the goal to a specific player 
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CGoalManager::CGoalManager()
+{
+    m_lastGoal = 0;
+    m_graffitiMode = 0;
+    m_goalPoints = 0;
+	m_totalGoalPointsEarned = 0;
+    m_cash = 0;
+	m_numGoalsBeaten = 0;
+	m_isPro = false;
+	// m_proChallengesUnlocked = false;
+	m_canStartGoal = true;
+
+	m_currentChapter = 0;
+	m_currentStage = 0;
+	m_sponsor = 0;
+	mp_team = new Script::CStruct();
+
+    mp_goalFlags = new Script::CStruct();
+	// mp_proSpecificChallenges = new Script::CStruct();
+	// mp_proSpecificChallenges->AppendStructure( Script::GetStructure( "goal_pro_specific_challenges_beaten", Script::ASSERT ) );
+
+	mp_difficulty_levels = new Script::CStruct();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CGoalManager::~CGoalManager()
+{
+	// delete all the goals in the list
+	RemoveAllGoals();
+    delete mp_goalFlags;
+	// delete mp_proSpecificChallenges;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::AddGoal( uint32 goalId, Script::CStruct* pParams )
+{
+	// don't add non-minigames in free skate
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	bool free_skate = skate_mod->GetGameMode()->IsTrue( CRCD(0x337e7779,"is_freeskate") );
+	if ( !CFuncs::ScriptInMultiplayerGame( NULL, NULL ) && free_skate && !pParams->ContainsFlag( CRCD(0x6bae094c,"minigame") ) )
+		return false;
+
+	if ( skate_mod->GetGameMode()->IsTrue( CRCD( 0x353961d7, "is_creategoals" ) ) && !pParams->ContainsFlag( CRCD( 0x981d3ad0, "edited_goal" ) ) )
+		return false;
+
+	// printf("addGoal called with goalId %x\n", goalId);
+	
+	Dbg_MsgAssert( !m_goalList.GetItem( goalId ), ( "duplicate goal id 0x%x (%s), please check your script", goalId, Script::FindChecksumName( goalId ) ) );
+	pParams->AddChecksum( CRCD(0x9982e501,"goal_id"), goalId );
+	
+	// figure the type
+	CGoal* pGoal = NULL;
+	if ( pParams->ContainsFlag( CRCD(0xc18fef36,"career_only") ) )
+	{   
+		if( CFuncs::ScriptInMultiplayerGame( NULL, NULL ))
+		{
+			return false;
+		}
+	}
+	if ( pParams->ContainsFlag( CRCD(0x4af5d34e,"competition") ) )
+	{
+		// printf("adding comp goal\n");
+		pGoal = new CCompetitionGoal( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD(0xd15ea00,"net") ) )
+	{
+		// printf("adding net goal\n");
+		pGoal = new CNetGoal( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD(0x25904450,"race") ) )
+	{
+		// printf("adding race goal\n");
+		pGoal = new CRaceGoal( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD(0x6bae094c,"minigame") ) )
+	{
+		// printf("adding minigame\n");
+		pGoal = new CMinigame( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD(0x8cfee956,"betting_guy") ) )
+	{
+		// printf("adding betting guy\n");
+		pGoal = new CBettingGuy( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD(0x4147922b,"tetris") ) )
+	{
+		// printf("adding skatetris goal\n");
+		pGoal = new CSkatetrisGoal( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD(0x9d65d0e7,"horse") ) )
+	{
+		// printf("adding horse goal\n");
+		pGoal = new CHorseGoal( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD( 0xc5ec08e6, "findGaps" ) ) )
+	{
+		pGoal = new CFindGapsGoal( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD( 0x7dbb41dd, "Film" ) ) )
+	{
+		pGoal = new CFilmGoal( pParams );
+	}
+	else if ( pParams->ContainsFlag( CRCD( 0x8e6014f6, "create_a_trick" ) ) )
+	{
+		pGoal = new CCatGoal( pParams );
+	}
+	else
+	{
+		pGoal = new CGoal( pParams );
+	}
+	Dbg_Assert( pGoal );
+
+	m_goalList.PutItem( goalId, pGoal );
+
+	pGoal->AppendDifficultyLevelParams();
+	Script::CStruct* pGoalParams = pGoal->GetParams();
+
+	// add children
+	uint32 childId;
+	Script::CArray* p_children;
+	if ( pGoalParams->GetArray( CRCD( 0x5e684e45, "children" ), &p_children, Script::NO_ASSERT ) )
+	{
+		int size = p_children->GetSize();
+		Dbg_MsgAssert( size > 0, ( "0 size children array for goal %s", Script::FindChecksumName( goalId ) ) );
+		Dbg_MsgAssert( p_children->GetType() == ESYMBOLTYPE_NAME, ( "children array has wrong type for goal %s", Script::FindChecksumName( goalId ) ) );
+		for ( int i = 0; i < size; i++ )
+		{
+			FindCircularLinks( goalId, p_children->GetChecksum( i ) );
+			pGoal->AddChild( p_children->GetChecksum( i ) );
+		}
+	}
+	else if ( pGoalParams->GetChecksum( CRCD( 0xdd4cabd6, "child" ), &childId, Script::NO_ASSERT ) )
+	{
+		FindCircularLinks( goalId, childId );
+		pGoal->AddChild( childId );
+	}
+	
+	if ( CFuncs::ScriptInMultiplayerGame( NULL, NULL ))
+	{
+		// SKATE SPECIFIC
+		pGoal->GetParams()->AddString( CRCD(0x944b2900,"pro"), "NetJudge" );
+		pGoal->GetParams()->AddString( CRCD(0x243b9c3b,"full_name"), Script::GetString( CRCD(0xa500bc5f,"judge_full_name") ) );
+		// END SKATE SPECIFIC
+	}
+	else
+	{
+		// SKATE SPECIFIC
+		const char* pFirstName;
+		uint32 pro_name;
+		if ( pGoal->GetParams()->GetString( CRCD(0x944b2900,"pro"), &pFirstName, Script::NO_ASSERT ) )
+		{
+			// printf("pro string: %s\n", pFirstName );
+			Script::CStruct* pLastNames = Script::GetStructure( CRCD(0x621d1828,"goal_pro_last_name_checksums") );
+			if ( pLastNames->GetChecksum( Script::GenerateCRC( pFirstName ), &pro_name, Script::NO_ASSERT ) )
+			{
+				// printf("got a last name: %s\n", Script::FindChecksumName( pro_name ) );
+				// get the current skater's name
+				Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+				Obj::CPlayerProfileManager* pPlayerProfileManager = skate_mod->GetPlayerProfileManager();
+				Obj::CSkaterProfile* pSkaterProfile = pPlayerProfileManager->GetCurrentProfile();
+				uint32 current_skater = pSkaterProfile->GetSkaterNameChecksum();
+				// printf("current_skater: %s\n", Script::FindChecksumName( current_skater ) );
+				if ( current_skater == pro_name )
+				{
+					const char* p_generic_pro_first_name = Script::GetString( CRCD(0x2245b93c,"generic_pro_first_name"), Script::ASSERT );
+					pGoal->GetParams()->AddString( CRCD(0x944b2900,"pro"), p_generic_pro_first_name );
+					const char* p_generic_pro_full_name = Script::GetString( CRCD(0xfe6ef7b8,"generic_pro_full_name"), Script::ASSERT );
+					pGoal->GetParams()->AddString( CRCD(0x243b9c3b,"full_name"), p_generic_pro_full_name );					
+				}
+			}
+		}
+		// END SKATE SPECIFIC
+	}
+
+	return ( pGoal != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoal* CGoalManager::GetGoal( uint32 goalId, bool assert )
+{
+	CGoal* pGoal = m_goalList.GetItem( goalId );
+	if ( assert )
+		Dbg_MsgAssert( pGoal, ( "Could not find goal %s\n", Script::FindChecksumName( goalId ) ) );
+	
+	return pGoal;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoal* CGoalManager::GetGoal( Script::CStruct* pParams, bool assert )
+{
+	uint32 goalId = GetGoalIdFromParams( pParams, assert );
+	if ( goalId )
+		return GetGoal( goalId, assert );
+	return NULL;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoal*	CGoalManager::GetGoalByIndex( int index )
+{
+	return m_goalList.GetItemByIndex( index, NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::RemoveGoal( uint32 goalId )
+{
+	// TODO:  Maybe add an "AssertOnFail" flag
+
+	CGoal* pGoal = m_goalList.GetItem( goalId );
+	Dbg_Assert( pGoal );
+		
+	pGoal->mp_goalPed->DestroyGoalPed();
+	m_goalList.FlushItem( goalId );
+
+	if (m_lastGoal == goalId)
+	{
+		m_lastGoal=0;
+	}
+		
+	// The FlushItem does not delete the CGoal* itself, it only deletes its own entry for
+	// it (which is a LookupItem*) so we need to delete the pGoal here.
+	delete pGoal;	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::ActivateGoal( uint32 goalId, bool dontAssert )
+{
+	CGoal* pGoal = GetGoal( goalId );
+
+    bool success = false;
+
+	if ( !pGoal )
+	{
+		if ( dontAssert )
+			return false;			
+		else
+			Dbg_MsgAssert( 0, ( "ActivateGoal could not find the goal" ) );
+	}
+	
+    if ( pGoal )
+    {
+		success = pGoal->Activate();
+		// set the last goal
+        if ( success && pGoal->CanRetry() )
+		{
+			// only set this for the root node
+			// BB - changed!  the retry goal option now
+			// always gives you the last stage.
+			
+			// Game::CGoalLink* p_parents = pGoal->GetParents();
+			// if ( p_parents->m_relative == 0 )
+			m_lastGoal = goalId;
+		}
+    }
+	
+    // move it back and forth from the inactive to the active list
+	// TODO:  Maybe add an "AssertOnFail" flag
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::DeactivateGoal( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	if ( pGoal )
+	{
+		pGoal->Deactivate();
+	}
+
+	// move it back and forth from the inactive to the active list
+	// would be an optimization?
+
+ 	// TODO:  Maybe add an "AssertOnFail" flag
+
+	return ( pGoal != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::ClearLastGoal( void )
+{
+	m_lastGoal = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::WinGoal( uint32 goalId )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	if ( pGoal )
+	{
+        // don't let them retry this goal
+		// only if it's a leaf node
+        if ( pGoal->GetChildren()->m_relative == 0 )
+			m_lastGoal = 0;
+
+		// we always want to call win, but if they
+		// already beat it, we don't want to give them 
+		// double credit
+		if ( !pGoal->HasWonGoal() )
+		{
+			// Report winning leaf node goals
+			if( ( pGoal->GetChildren()->m_relative == 0 ) &&
+				( gamenet_man->InNetGame()))
+			{
+				Net::Client* client;
+				Net::MsgDesc msg_desc;
+
+				Dbg_Printf( "*** Reporting won goal\n" );
+				// TODO: This should use the appropriate client for the skater completing this trick.
+				// Assuming client Zero will probably cause problems in split-screen games
+				client = gamenet_man->GetClient( 0 );
+				Dbg_Assert( client );
+
+				GameNet::MsgBeatGoal msg;
+
+				msg.m_GameId = gamenet_man->GetNetworkGameId();
+				msg.m_GoalId = pGoal->GetRootGoalId();
+				
+				Dbg_Printf( "**** Beat GOAL 0x%x  ROOT: 0x%x\n", pGoal->GetGoalId(), pGoal->GetRootGoalId());
+
+				msg_desc.m_Data = &msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgBeatGoal );
+				msg_desc.m_Id = GameNet::MSG_ID_BEAT_GOAL;
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+				client->EnqueueMessageToServer( &msg_desc );
+			}
+			else
+			{
+				// increase num goals beaten if this is a leaf node.
+				if ( pGoal->GetChildren()->m_relative == 0 )
+					if ( !pGoal->GetParams()->ContainsFlag( CRCD(0x981d3ad0,"edited_goal") ) )
+						m_numGoalsBeaten++;
+			}
+			
+		}
+		
+		// In net games, "won" goals need to be approved by the server
+		// Update: Win goals locally now to close the window where time can expire waiting for server's approval
+		pGoal->Win();
+	}
+
+	/*
+	if( gamenet_man->InNetGame() == false )
+	{
+		if ( !m_isPro && ( m_numGoalsBeaten >= m_numGoalsToPro ) )
+		{
+			TurnPro();
+		}
+	}
+	*/
+
+	return ( pGoal != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::LoseGoal( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	// expire takes priority over fail
+	if ( pGoal )
+	{
+		pGoal->Lose();
+	}
+
+	return ( pGoal != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::EditGoal( uint32 goalId, Script::CStruct* pParams )
+{
+	CGoal* pGoal = GetGoal( goalId );
+
+	if ( pGoal )
+	{
+		pGoal->EditParams( pParams );
+	}
+			 
+	// should edit the state of the goal here...
+	
+	// TODO:  Maybe add an "AssertOnFail" flag
+
+	return ( pGoal != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::SetGoalTimer( uint32 goalId, int time )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	if ( pGoal )
+	{
+		if (time == -1)
+		{
+			pGoal->SetTimer();
+		}
+		else
+		{
+			pGoal->SetTimer(time);
+		}
+	}
+			 
+	// should edit the state of the goal here...
+	
+	// TODO:  Maybe add an "AssertOnFail" flag
+
+	return ( pGoal != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::GoalIsActive( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId, false );
+
+	if ( pGoal )
+	{
+		return pGoal->IsActive();
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetActiveGoal( bool ignore_net_goals )
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if( ignore_net_goals && pGoal->IsNetGoal())
+		{
+			continue;
+		}
+		if ( pGoal->IsActive() )
+		{
+            return i;
+        }
+    }
+    return -1;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetNumGoals()
+{
+	return m_goalList.getSize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetNumActiveGoals( bool count_all )
+{
+	int numActiveGoals = 0;
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if( pGoal->IsNetGoal() && !count_all )
+		{
+			continue;
+		}
+		if ( count_all && pGoal->IsActive() )
+			numActiveGoals++;
+		else if ( pGoal->CountAsActive() )
+			numActiveGoals++;
+	}
+	return numActiveGoals;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetNumSelectedGoals()
+{
+	int num_selected = 0;
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		// this is now primarily used for determining if the timer should be on-screen
+		if ( pGoal->IsSelected() )
+		{
+			num_selected++;
+		}
+	}
+	return num_selected;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::RemoveAllGoals()
+{
+	// can't retry!
+    m_lastGoal = 0;
+    
+	// kill the ped
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		
+		pGoal->mp_goalPed->DestroyGoalPed();
+	}
+
+	// delete all the goals in the list
+	Lst::LookupTableDestroyer destroyer( &m_goalList );	
+	destroyer.DeleteTableContents();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::DeactivateAllGoals( bool include_net_goals )
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		if ( pGoal->IsActive() )
+		{
+			pGoal->Deactivate( include_net_goals );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UninitializeGoal( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+	pGoal->Uninit();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UninitializeGoalTree( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+	CGoalLink* pGoalLink = pGoal->GetParents();
+	while ( pGoalLink && pGoalLink->m_relative != 0 )
+	{
+		pGoal = GetGoal( pGoalLink->m_relative );
+		Dbg_Assert( pGoal );
+		pGoalLink = pGoal->GetParents();
+	}
+	
+	// the resulting goal should be the root node of the family tree
+	pGoal->Uninit( true );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UninitializeAllGoals()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		pGoal->Uninit();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::DeactivateAllMinigames()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		if ( pGoal->IsMinigame() && pGoal->IsActive() )
+			pGoal->Deactivate();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::InitializeAllMinigames()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		if( pGoal->IsMinigame())
+		{
+			pGoal->RunCallbackScript( vINIT );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::InitializeAllGoals()
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+			
+		if( gamenet_man->InNetGame() && !pGoal->IsNetGoal())
+		{
+			continue;
+		}
+
+		if ( !pGoal->IsLocked() && !pGoal->HasWonGoal() )
+		{
+			// check if this is a child of another goal
+			CGoalLink* pGoalParents = pGoal->GetParents();
+
+			// support an always_initialize_goal flag, which
+			// allows designers to have goals that aren't tied to
+			// a chapter/stage
+			Script::CStruct* pGoalParams = pGoal->GetParams();
+			
+			int goal_chapter = pGoal->GetChapter();
+			int goal_stage = pGoal->GetStage();
+			
+			// printf("%s: %i.%i\n", Script::FindChecksumName( pGoal->GetGoalId() ), goal_chapter, goal_stage );
+			if ( pGoalParents->m_relative == 0 )
+			{
+				// if the goal isn't listed in chapter_info.q at all,
+				// the chapter and stage will default to -1.
+				// Always create these goals
+				if ( ( goal_chapter == m_currentChapter && goal_stage == m_currentStage ) 
+					 || pGoalParams->ContainsFlag( CRCD( 0x4e863e93, "always_initialize_goal" ) )
+					 || pGoalParams->ContainsFlag( CRCD( 0x981d3ad0, "edited_goal" ) ) )
+				{
+					pGoal->Init();
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::InitializeAllSelectedGoals()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+			
+		if ( pGoal->IsSelected())
+		{       
+			Script::RunScript( Script::GenerateCRC( "goal_create_proset_geom" ), pGoal->GetParams());
+			pGoal->Init();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::DeselectAllGoals()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+			
+		pGoal->Deselect();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// TODO: make this smarter...it just deactivates the first
+// non-minigame goal it finds
+void CGoalManager::DeactivateCurrentGoal()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		if ( pGoal->IsActive() && !pGoal->IsNetGoal() && !pGoal->IsMinigame() )
+		{
+			pGoal->Deactivate();
+			return;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::Update()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->IsActive() )
+		{
+            pGoal->Update();
+        }
+    }
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+        if ( pGoal->IsInvalid() )
+        {
+            //Dbg_Printf("************** removing goal %p\n", pGoal->GetGoalId());
+            m_goalList.FlushItem( pGoal->GetGoalId() );
+            delete pGoal;
+        }
+/*        else 
+            {
+                pGoal->Update();
+            }
+*/
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::HasSeenGoal( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+
+	if ( pGoal )
+	{
+		return pGoal->HasSeenGoal();
+	}
+
+	return false;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::PauseAllGoals()
+{
+    for ( int i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+    
+        pGoal->PauseGoal();
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UnPauseAllGoals()
+{
+    for ( int i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+    
+        pGoal->UnPauseGoal();
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Tmr::Time CGoalManager::GetGoalTime()
+{
+    // TODO: allow some sort of goal priority 
+    // right now this just finds the fist active goal
+    // and returns that timer
+    for (int i = 0; i < GetNumGoals(); i++) {
+        uint32 key;
+        CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+        
+        if ( pGoal->IsActive() && pGoal->ShouldUseTimer() && !pGoal->IsExpired() )
+        {
+            Tmr::Time timeLeft = pGoal->GetTime();
+            if ( (int)timeLeft < 0 )
+                return 0;
+            return timeLeft;
+        }
+    }
+    // there are no active goals.
+    return 0;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::CanRetryGoal()
+{
+    if ( m_lastGoal != 0 )
+	{
+		CGoal* pGoal = GetGoal( m_lastGoal );
+		return pGoal->CanRetry();
+
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::QuickStartGoal( uint32 goalId, bool dontAssert )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	if ( !pGoal )
+	{
+		if ( dontAssert )
+			return false;
+		else
+			Dbg_MsgAssert( 0, ( "QuickStartGoal unable to find goal" ) );
+	}
+	pGoal->SetQuickStartFlag();
+	bool success = pGoal->Activate();
+	
+	// set the last goal
+	if ( success && pGoal->CanRetry() )
+		m_lastGoal = goalId;
+
+	pGoal->UnsetQuickStartFlag();
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::RestartLastGoal() 
+{
+	if ( m_lastGoal != 0 )
+    {
+		CGoal* pGoal = GetGoal( m_lastGoal );
+		Dbg_Assert( pGoal );
+
+		// set the restart flag so the (de)activate scripts knows not to show
+		// cam anims, etc.
+		pGoal->SetQuickStartFlag();
+
+		// if ( pGoal->IsActive() )
+			// pGoal->Deactivate();
+		DeactivateAllGoals();
+
+		// SKATE SPECIFIC
+		// kill any sk3_killscripts, as they will screw up the reset skater call
+		Script::KillSpawnedScriptsThatReferTo( ( CRCD(0xdea3057b,"SK3_KillSkater") ) );
+		Script::KillSpawnedScriptsThatReferTo( ( CRCD(0xb38ed6b,"SK3_Killskater_Finish") ) );
+		// END SKATE SPECIFIC
+
+		// kill any blur effect
+		Script::RunScript(( CRCD(0xbda66e7d,"kill_blur") ) );
+		
+		// uint32 reset_checksum = 0;
+        // pGoal->GetParams()->GetChecksum( "restart_node", &reset_checksum, Script::ASSERT );        
+        // Mdl::Skate * p_skate = Mdl::Skate::Instance();
+        // p_skate->ResetSkaters( SkateScript::FindNamedNode( reset_checksum ) );
+
+		// clear the trigger exceptions
+		if ( !pGoal->HasWonGoal() )
+		{
+			Script::CStruct* p_params = new Script::CStruct();
+			p_params->AddChecksum( "goal_id", pGoal->GetGoalId() );
+			Script::RunScript( CRCD( 0x17c2e09f, "GoalManager_ResetGoalTrigger" ), p_params );
+			delete p_params;
+		}
+		
+		pGoal->Activate();
+		pGoal->UnsetQuickStartFlag();
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::RestartStage()
+{
+    int activeGoal = GetActiveGoal();
+	if ( activeGoal == -1 )
+		return;
+
+	uint32 key;
+	CGoal* pGoal = m_goalList.GetItemByIndex( activeGoal, (int*)&key );
+	Dbg_Assert( pGoal );
+
+	pGoal->SetQuickStartFlag();
+
+	if ( pGoal->IsActive() )
+	{
+		pGoal->Deactivate( false, false );
+
+		// SKATE SPECIFIC
+		// kill any sk3_killscripts, as they will screw up the reset skater call
+		Script::KillSpawnedScriptsThatReferTo(  CRCD(0xdea3057b,"SK3_KillSkater")  );
+		Script::KillSpawnedScriptsThatReferTo( ( CRCD(0xb38ed6b,"SK3_Killskater_Finish") ) );
+		// END SKATE SPECIFIC
+
+		// kill any blur effect
+		Script::RunScript( ( CRCD(0xbda66e7d,"kill_blur") ) );
+		
+		// uint32 reset_checksum = 0;
+        // pGoal->GetParams()->GetChecksum( "restart_node", &reset_checksum, Script::ASSERT );        
+        // Mdl::Skate * p_skate = Mdl::Skate::Instance();
+        // p_skate->ResetSkaters( SkateScript::FindNamedNode( reset_checksum ) );
+
+		// clear the trigger exceptions
+		if ( !pGoal->HasWonGoal() )
+		{
+			Script::CStruct* p_params = new Script::CStruct();
+			p_params->AddChecksum( "goal_id", pGoal->GetGoalId() );
+			Script::RunScript( CRCD( 0x17c2e09f, "GoalManager_ResetGoalTrigger" ), p_params );
+			delete p_params;
+		}
+		
+		pGoal->Activate();
+		pGoal->UnsetQuickStartFlag();
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*void CGoalManager::CreateGoalFlag( uint32 goalId, uint32 flag )
+{
+	CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+    
+    pGoal->CreateGoalFlag( flag );
+}*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::SetGoalFlag( uint32 goalId, uint32 flag, int value )
+{
+    CGoal* pGoal = GetGoal( goalId );
+
+    if ( !pGoal )
+        return false;
+    if ( pGoal->SetGoalFlag( flag, value ) ) return true;
+    
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::AllFlagsSet( uint32 goalId )
+{
+    CGoal* pGoal = GetGoal( goalId );
+
+    Dbg_Assert( pGoal );
+    return pGoal->AllFlagsSet();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::GoalFlagSet( uint32 goalId, uint32 flag )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+	return pGoal->GoalFlagSet( flag );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CGoalManager::CreatedGapGoalIsActive()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* p_goal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( p_goal );
+		
+		if (p_goal->IsActive())
+		{
+			Script::CStruct *p_params=p_goal->GetParams();
+			Script::CArray *p_dummy=NULL;
+			if (p_params->GetArray(CRCD(0x52d4489e,"required_gaps"),&p_dummy))
+			{
+				return true;
+			}
+		}	
+	}	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::GetCreatedGoalGap(int gapIndex)
+{
+	bool return_value=false;
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* p_goal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( p_goal );
+		
+		return_value=p_goal->GetCreatedGoalGap(gapIndex);
+	}	
+	return return_value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CGoalManager::GetGoalParams( uint32 goalId )
+{
+	// printf("trying to get params for goalId %x\n", goalId);
+	CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+
+    return pGoal->GetParams( );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::CanStartGoal()
+{
+    return m_canStartGoal;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::NextRaceWaypoint( uint32 goalId )
+{
+    CRaceGoal* pGoal = (CRaceGoal*)GetGoal( goalId );
+	Dbg_MsgAssert( pGoal, ( "Couldn't find goal: %x\n", goalId ) );
+
+    return pGoal->NextRaceWaypoint( goalId );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::NextTourSpot( uint32 goalId )
+{
+    CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+
+    return pGoal->NextTourSpot();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::InitTrickObjects( uint32 goalId )
+{
+    CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+
+    pGoal->InitTrickObjects();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::SetGraffitiMode( int mode )
+{
+    m_graffitiMode = mode;
+
+/*    // do we need to reset the scores?
+    if ( mode == 0 )
+    {
+        Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+        Obj::CTrickObjectManager* p_TrickObjectManager = skate_mod->GetTrickObjectManager();
+        p_TrickObjectManager->ResetAllTrickObjects();
+    }
+*/
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::ShouldLogTrickObject()
+{
+    int activeGoal = GetActiveGoal( true );
+	if ( activeGoal == -1 )
+		return false;
+
+	uint32 key;
+	CGoal* pGoal = m_goalList.GetItemByIndex( activeGoal, (int*)&key );
+	Dbg_Assert( pGoal );
+	return pGoal->ShouldLogTrickObject();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::GotTrickObject( uint32 clusterId, int score )
+{
+	int activeGoal = GetActiveGoal( true );
+	if ( activeGoal == -1 )	// no active goal
+		return;
+	
+	uint32 key;
+	CGoal* pGoal = m_goalList.GetItemByIndex( activeGoal, (int*)&key );
+	Dbg_Assert( pGoal );
+
+	if ( pGoal->IsGraffitiGoal() )
+		pGoal->GotGraffitiCluster( clusterId, score );
+	else
+		pGoal->GotTrickObject( clusterId );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::CheckTrickText()
+{
+    int activeGoal = GetActiveGoal( true );
+    
+    if ( activeGoal == -1 )
+        return;
+
+    uint32 key;
+    CGoal* pGoal = m_goalList.GetItemByIndex( activeGoal, (int*)&key );
+
+    Dbg_Assert( pGoal );
+
+    Script::CStruct* p_goal_params = pGoal->GetParams();
+	if ( pGoal->IsSpecialGoal() || p_goal_params->ContainsFlag( CRCD(0x8e6014f6,"create_a_trick") ) )
+    {		
+		// don't check special goals that are setup with gaps
+		if ( !p_goal_params->ContainsFlag( "special_gap" ) )
+		{
+			if ( pGoal->CheckSpecialGoal() )
+			{
+				uint32 goalId = pGoal->GetGoalId();
+				WinGoal( goalId );
+			}
+		}
+    }
+    else if ( pGoal->IsTetrisGoal() )
+    {
+        CSkatetrisGoal* p_temp = (CSkatetrisGoal*)pGoal;
+		p_temp->CheckTetrisTricks();
+    }
+	else if ( pGoal->IsFindGapsGoal() )
+	{
+		CFindGapsGoal *p_temp = (CFindGapsGoal*)pGoal;
+		p_temp->CheckGaps();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UnlockGoal( uint32 goalId )
+{
+    CGoal* pGoal = GetGoal( goalId );
+    Dbg_MsgAssert( pGoal, ("Couldn't find goal to unlock") );
+
+    pGoal->UnlockGoal();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::HasWonGoal( uint32 goalId )
+{
+    CGoal* pGoal = GetGoal( goalId, false );
+
+    if ( pGoal )
+		return pGoal->HasWonGoal();
+	else
+	{
+		// check goal manager params struct in case the goal was in
+		// another level
+		int hasWonGoal = 0;
+		Script::CStruct* pGoalParams;
+		if ( mp_goalFlags->GetStructure( goalId, &pGoalParams, Script::NO_ASSERT ) )
+			pGoalParams->GetInteger( CRCD(0x49807745,"hasBeaten"), &hasWonGoal, Script::ASSERT );
+
+		return ( hasWonGoal != 0 );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::GoalIsSelected( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+
+    return pGoal->IsSelected();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::ToggleGoalSelection( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+
+	if( pGoal->IsSelected())
+	{
+		pGoal->Deselect();
+	}
+	else
+	{
+		pGoal->Select();
+	}                   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::GotCounterObject( uint32 goalId )
+{
+    CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+
+    if ( pGoal->IsActive() )
+		pGoal->GotCounterObject();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::CounterGoalDone( uint32 goalId )
+{
+    CGoal* pGoal = GetGoal( goalId );
+    Dbg_Assert( pGoal );
+
+    return pGoal->CounterGoalDone();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SpendGoalPoints( int num )
+{
+    m_goalPoints -= num;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::AddGoalPoint()
+{
+    m_goalPoints++;
+	m_totalGoalPointsEarned++;
+	Script::RunScript( CRCD(0x888ef05e,"GoalManager_ShowGoalPoints") );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::HasGoalPoints( int num )
+{
+    return ( m_goalPoints >= num );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetNumberOfGoalPoints()
+{
+    return m_goalPoints;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetTotalNumberOfGoalPointsEarned()
+{
+    return m_totalGoalPointsEarned;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::ClearGoalPoints()
+{
+	m_goalPoints = 0;
+	m_totalGoalPointsEarned = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::AddCash( int amount )
+{
+    m_cash += amount;
+	m_totalCash += amount;
+	if ( GetNumActiveGoals() < 1 )
+	{
+		Script::RunScript( CRCD(0x888ef05e,"GoalManager_ShowGoalPoints") );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::SpendCash( int amount )
+{
+    if ( amount <= m_cash)
+    {
+        m_cash -= amount;
+        return true;
+    } 
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetCash( bool get_total )
+{
+    if ( get_total )
+		return m_totalCash;
+	return m_cash;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::HasBeatenGoalWithProset( const char* proset_prefix )
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+
+        if ( pGoal->HasWonGoal() && pGoal->HasProset( proset_prefix ) )
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::LevelUnload()
+{
+	// don't do this in net games!
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	if ( gamenet_man->InNetGame() )
+		return;
+	
+	// save goal manager params
+	Script::CStruct *p_manager_params = new Script::CStruct();
+
+	p_manager_params->AddInteger( CRCD(0x6abb6fcb,"goalPoints"), m_goalPoints );
+	p_manager_params->AddInteger( CRCD(0x79ed60aa,"totalGoalPointsEarned"), m_totalGoalPointsEarned );
+	p_manager_params->AddInteger( CRCD(0xf9461a46,"cash"), m_cash );
+	p_manager_params->AddInteger( CRCD(0x875e89bb,"total_cash"), m_totalCash );
+	// p_manager_params->AddInteger( "numGoalsToPro", m_numGoalsToPro );
+	p_manager_params->AddInteger( CRCD(0x46f71d38,"numGoalsBeaten"), m_numGoalsBeaten );
+
+	p_manager_params->AddInteger( CRCD(0xf884773c,"currentChapter"), m_currentChapter );
+	p_manager_params->AddInteger( CRCD(0xaf1575eb,"currentStage"), m_currentStage );
+	p_manager_params->AddChecksum( CRCD(0x7e73362b,"sponsor"), m_sponsor );
+	p_manager_params->AddStructure( CRCD(0x3b1f59e0,"team"), mp_team );
+	p_manager_params->AddStructure( CRCD(0xb13d98d3,"difficulty_levels"), mp_difficulty_levels );
+	
+/*	int proChallengesUnlocked = 0;
+	if ( m_proChallengesUnlocked )
+		proChallengesUnlocked = 1;
+	p_manager_params->AddInteger( CRCD(0x24c226fa,"proChallengesUnlocked"), proChallengesUnlocked );
+*/
+	int isPro = 0;
+	if ( m_isPro )
+		isPro = 1;
+	p_manager_params->AddInteger( CRCD(0x40801d41,"isPro"), isPro );
+
+	// p_manager_params->AddStructure( CRCD(0xf8478579,"proSpecificChallenges"), mp_proSpecificChallenges );
+
+	mp_goalFlags->AddStructure( CRCD(0x23d4170a,"GoalManager_Params"), p_manager_params );
+
+	delete p_manager_params;
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+		Script::CStruct* pGoalParams = pGoal->GetParams();
+		
+		if ( !pGoalParams->ContainsFlag( CRCD(0x3d1cab0b,"null_goal") ) && !pGoal->IsEditedGoal() ) // K: Don't add edited goals
+		{
+			Script::CStruct* p_temp = new Script::CStruct();
+			pGoal->GetSaveData( p_temp );
+			mp_goalFlags->AddStructure( pGoal->GetGoalId(), p_temp );
+			delete p_temp;
+		}	
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::LevelLoad()
+{
+    Script::CComponent* p_comp = NULL;
+    p_comp = mp_goalFlags->GetNextComponent( p_comp );
+
+    while ( p_comp )
+    {
+        Script::CComponent* p_next = mp_goalFlags->GetNextComponent( p_comp );
+
+		// special goal manager params structure
+		if ( p_comp->mNameChecksum == CRCD( 0x23d4170a, "GoalManager_Params" ) )
+		{
+			p_comp->mpStructure->GetInteger( CRCD(0x6abb6fcb,"goalPoints"), &m_goalPoints, Script::NO_ASSERT );
+			p_comp->mpStructure->GetInteger( CRCD(0x79ed60aa,"totalGoalPointsEarned"), &m_totalGoalPointsEarned, Script::NO_ASSERT );
+			p_comp->mpStructure->GetInteger( CRCD(0xf9461a46,"cash"), &m_cash, Script::NO_ASSERT );
+			p_comp->mpStructure->GetInteger( CRCD(0x875e89bb,"total_cash"), &m_totalCash, Script::NO_ASSERT );
+			// p_comp->mpStructure->GetInteger( "numGoalsToPro", &m_numGoalsToPro, Script::NO_ASSERT );
+			p_comp->mpStructure->GetInteger( CRCD(0x46f71d38,"numGoalsBeaten"), &m_numGoalsBeaten, Script::NO_ASSERT );
+
+			int auto_change_chapter_and_stage = 0;
+			auto_change_chapter_and_stage = Script::GetInteger( CRCD(0x5a1d8804,"auto_change_chapter_and_stage"), Script::NO_ASSERT );
+			if ( auto_change_chapter_and_stage == 0 )
+			{
+				p_comp->mpStructure->GetInteger( CRCD(0xf884773c,"currentChapter"), &m_currentChapter, Script::NO_ASSERT );
+				p_comp->mpStructure->GetInteger( CRCD(0xaf1575eb,"currentStage"), &m_currentStage, Script::NO_ASSERT );				
+			}
+			p_comp->mpStructure->GetChecksum( CRCD(0x7e73362b,"sponsor"), &m_sponsor, Script::ASSERT );
+			
+			Script::CStruct* p_copy;
+			p_comp->mpStructure->GetStructure( CRCD(0x3b1f59e0,"team"), &p_copy, Script::ASSERT );
+			mp_team->Clear();
+			mp_team->AppendStructure( p_copy );
+
+			Script::CStruct* p_diff_copy;
+			p_comp->mpStructure->GetStructure( CRCD(0xb13d98d3,"difficulty_levels"), &p_diff_copy, Script::ASSERT );
+			mp_difficulty_levels->Clear();
+			mp_difficulty_levels->AppendStructure( p_diff_copy );
+			
+			int isPro;
+			p_comp->mpStructure->GetInteger( CRCD(0x40801d41,"isPro"), &isPro, Script::ASSERT );
+			if ( isPro != 0 )
+				TurnPro();
+			
+/*			int proChallengesUnlocked;
+			p_comp->mpStructure->GetInteger( CRCD(0x24c226fa,"proChallengesUnlocked"), &proChallengesUnlocked, Script::ASSERT );
+			if ( proChallengesUnlocked != 0 )
+				m_proChallengesUnlocked = true;
+
+			Script::CStruct* p_copy;
+			p_comp->mpStructure->GetStructure( CRCD(0xf8478579,"proSpecificChallenges"), &p_copy, Script::ASSERT );
+			mp_proSpecificChallenges->AppendStructure( p_copy );
+*/
+		}
+		else 
+		{
+			CGoal* pGoal = GetGoal( p_comp->mNameChecksum, false );
+			if ( pGoal )
+				pGoal->LoadSaveData( p_comp->mpStructure );
+		}
+        
+		p_comp = p_next;
+    }
+	
+	// make sure to unlock any pro goals (they may have turned pro in another level)
+	if ( m_isPro )
+		TurnPro();
+
+	// ...and unlock any pro challenges
+	// if ( m_proChallengesUnlocked )
+		// UnlockProSpecificChallenges();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::NumGoalsBeatenBy( int obj_id )
+{
+	int i, num_beaten;
+	CGoal* pGoal;
+
+	num_beaten = 0;
+	for ( i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+
+		if( pGoal->GetParents()->m_relative != 0 )
+		{
+			continue;
+		}
+
+		if( pGoal->HasWonGoal( obj_id ))
+		{
+			num_beaten++;
+		}
+	}
+
+	return num_beaten;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::NumGoalsBeatenByTeam( int team_id )
+{
+	int i, num_beaten;
+	CGoal* pGoal;
+
+	num_beaten = 0;
+	for ( i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+
+		if( pGoal->GetParents()->m_relative != 0 )
+		{
+			continue;
+		}
+		if( pGoal->HasTeamWonGoal( team_id ))
+		{
+			num_beaten++;
+		}
+	}
+
+	return num_beaten;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::NumGoalsBeaten()
+{
+	int i, num_beaten;
+	CGoal* pGoal;
+
+	num_beaten = 0;
+	for ( i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+
+		if( pGoal->GetParents()->m_relative != 0 )
+		{
+			continue;
+		}
+		if( pGoal->HasWonGoal())
+		{
+			num_beaten++;
+		}
+	}
+
+	return num_beaten;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CGoalManager::NumGoalsBeatenInLevel( int levelNum, bool count_pro_specific_challenges )
+{
+    // store all the goal info in the goalmanager's struct
+    // so we can grab it all in one place
+    LevelUnload();
+    
+    int numbeat = 0;
+    Script::CComponent* p_comp = mp_goalFlags->GetNextComponent( NULL );
+    Script::CComponent* p_next = NULL;
+    while ( p_comp )
+    {
+        // printf("checking p_comp\n");
+        p_next = mp_goalFlags->GetNextComponent( p_comp );
+
+        int p_comp_levelNum = 0;
+        if ( p_comp->mpStructure->GetInteger( CRCD(0x1a4cde06,"levelNum"), &p_comp_levelNum, Script::NO_ASSERT ) )
+        {
+            // printf("found levelNum\n");
+/*			if ( !count_pro_specific_challenges )
+			{
+				int isProSpecificChallenge;
+				p_comp->mpStructure->GetInteger( CRCD(0x3dbe3121,"isProSpecificChallenge"), &isProSpecificChallenge, Script::ASSERT );
+				if ( isProSpecificChallenge == 1 )
+				{
+					p_comp = p_next;
+					continue;
+				}
+			}
+*/
+			int hasBeaten = 0;
+			p_comp->mpStructure->GetInteger( CRCD(0x49807745,"hasBeaten"), &hasBeaten, Script::NO_ASSERT );
+            if ( p_comp_levelNum == levelNum && hasBeaten )
+            {
+                numbeat++;
+            }
+        }
+        p_comp = p_next;
+    }
+    return numbeat;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalManager::UnlockAllGoals()
+{
+    m_isPro = true;
+	// m_proChallengesUnlocked = true;
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+    {
+        uint32 key;
+        CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+        Dbg_Assert( pGoal );
+        // pGoal->UnlockGoal();
+		pGoal->SetHasSeen();
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::TurnPro()
+{
+	m_isPro = true;
+	// m_proChallengesUnlocked = true;
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		
+		if ( pGoal->IsProGoal() && pGoal->IsLocked() )
+		{
+			pGoal->UnlockGoal();
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalManager::SetStartTime( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->SetStartTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoalManager::CheckMinigameRecord( uint32 goalId, int value )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	return pGoal->CheckMinigameRecord( value );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UpdateComboTimer( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->UpdateComboTimer();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetStartHeight( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->SetStartHeight();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::CheckHeightRecord( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	return pGoal->CheckHeightRecord();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::CheckDistanceRecord( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	return pGoal->CheckDistanceRecord();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::PlayGoalStartStream( Script::CStruct* pParams )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->mp_goalPed->PlayGoalStartStream( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::PlayGoalWaitStream( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->mp_goalPed->PlayGoalWaitStream();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::PlayGoalStream( Script::CStruct* pParams )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	// if we got a specific stream, just play it and quit
+	uint32 stream_checksum = 0;
+	pParams->GetChecksum( CRCD(0x1a4e4fb9,"vo"), &stream_checksum, Script::NO_ASSERT );
+	if ( stream_checksum )
+	{
+		pGoal->mp_goalPed->PlayGoalStream( stream_checksum );
+		return;
+	}
+
+	// check for type
+	const char* type = NULL;
+	if ( pParams->GetString( CRCD(0x7321a8d6,"type"), &type, Script::NO_ASSERT ) )
+	{		
+		pGoal->mp_goalPed->PlayGoalStream( type, pParams );
+		return;
+	}		
+	
+	// you gotta have one or the other
+	if ( !type && !stream_checksum )
+		Dbg_MsgAssert( 0, ("GoalManager_PlayGoalStream called without a type or vo checksum") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::StopCurrentStream( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->mp_goalPed->StopCurrentStream();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::PlayGoalWinStream( Script::CStruct* pParams )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->mp_goalPed->PlayGoalWinStream( pParams );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::PauseGoal( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->PauseGoal();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UnPauseGoal( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->UnPauseGoal();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::PauseCompetition( uint32 goalId )
+{
+	Game::CCompetitionGoal* pComp = (Game::CCompetitionGoal*)GetGoal( goalId );
+	Dbg_Assert( pComp );
+
+	pComp->PauseCompetition();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UnPauseCompetition( uint32 goalId )
+{
+	Game::CCompetitionGoal* pComp = (Game::CCompetitionGoal*)GetGoal( goalId );
+	Dbg_Assert( pComp );
+
+	pComp->UnPauseCompetition();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UnBeatAllGoals()
+{	
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->UnBeatGoal() && !gamenet_man->InNetGame() )
+		{
+			// m_numGoalsToPro++;
+			m_numGoalsBeaten--;
+			m_goalPoints--;
+			m_totalGoalPointsEarned--;
+		}
+	}
+	// clear the goal manager's memory of goals too
+	if ( !gamenet_man->InNetGame() )
+		mp_goalFlags->Clear();
+
+	if ( GetNumActiveGoals() == 0 )
+	{
+		if( CFuncs::ScriptInMultiplayerGame( NULL, NULL ) == false )
+		{
+			Script::RunScript( CRCD(0x888ef05e,"GoalManager_ShowGoalPoints") );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::AddViewGoalsList()
+{
+	
+	int numGoals = GetNumGoals();
+	Script::CArray* p_ordered_goals = new Script::CArray();
+	Script::CArray* p_unordered_goals = new Script::CArray();	
+	p_ordered_goals->SetSizeAndType( numGoals, ESYMBOLTYPE_NAME );
+	p_unordered_goals->SetSizeAndType( numGoals, ESYMBOLTYPE_NAME );
+	
+	int num_ordered_goals = 0;
+	int num_unordered_goals = 0;
+	
+	// divide the list into goals with ordinals and goals without
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->GetParams()->ContainsComponentNamed( CRCD(0x9b048fc,"ordinal") ) )
+		{
+			p_ordered_goals->SetChecksum( num_ordered_goals, pGoal->GetGoalId() );
+			num_ordered_goals++;
+		}
+		else
+		{
+			p_unordered_goals->SetChecksum( num_unordered_goals, pGoal->GetGoalId() );
+			num_unordered_goals++;
+		}			
+	}
+
+	// sort the list of goals with ordinals
+	// printf("there are %i ordered goals\n", num_ordered_goals);
+	for ( int i = 0; i < num_ordered_goals; i++ )
+	{
+		// inefficient sorting, but there's only ~20 things - brad
+		for ( int j = i; j < num_ordered_goals; j++ )
+		{
+			uint32 temp = p_ordered_goals->GetChecksum( j );
+			CGoal* p_temp_goal = GetGoal( temp );
+			Dbg_Assert( p_temp_goal );
+			int ordinal_check;
+			p_temp_goal->GetParams()->GetInteger( CRCD(0x9b048fc,"ordinal"), &ordinal_check, Script::ASSERT );
+			Dbg_MsgAssert( ordinal_check - 1 < num_ordered_goals, ( "Ordinal value %i too big.", ordinal_check ) );
+			if ( ordinal_check - 1 == i )
+			{
+				if ( j == i )
+					break;
+
+				// swap them				
+				uint32 temp2 = p_ordered_goals->GetChecksum( i );
+				int double_check;
+				GetGoal( temp2 )->GetParams()->GetInteger( CRCD(0x9b048fc,"ordinal"), &double_check, Script::ASSERT );
+				Dbg_MsgAssert( double_check != i, ( "Found ordinal %i twice", double_check ) );
+
+				p_ordered_goals->SetChecksum( i, temp );
+				p_ordered_goals->SetChecksum( j, temp2 );
+				break;
+			}
+		}
+	}
+	
+	// tack on the list of unordered goals
+	for ( int i = 0; i < num_unordered_goals; i++ )
+		p_ordered_goals->SetChecksum( i + num_ordered_goals, p_unordered_goals->GetChecksum( i ) );
+	
+	// populate the menu
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 goalId = p_ordered_goals->GetChecksum( i );
+		CGoal* pGoal = GetGoal( goalId );
+		Dbg_Assert( pGoal );
+		Script::CStruct* pGoalParams = pGoal->GetParams();
+
+		const char* p_view_goals_text = NULL;
+		pGoal->GetViewGoalsText( &p_view_goals_text );
+		
+		// skip goals without an associated text (eg, minigames)
+		if ( !p_view_goals_text )
+			continue;
+
+		// skip pro goals if we're not pro
+		if ( !IsPro() && pGoalParams->ContainsFlag( CRCD(0xd303a1a3,"pro_goal") ) )
+			continue;
+
+		// skip pro challenges if we haven't unlocked them.
+		// if ( !ProSpecificChallengesUnlocked() && pGoalParams->ContainsFlag( CRCD(0xe0f96410,"pro_specific_challenge") ) )
+			// continue;
+		
+		// skip pro specific challenges if we're not the right skater
+/*		if ( !pGoal->HasWonGoal() && !IsPro() && pGoalParams->ContainsFlag( CRCD(0xe0f96410,"pro_specific_challenge") ) )
+		{
+			// check the current skater against the listed pro
+			Script::CStruct* pLastNames = Script::GetStructure( CRCD(0x621d1828,"goal_pro_last_name_checksums"), Script::ASSERT );
+			const char* pProChallengeProName;
+			pGoalParams->GetString( CRCD(0x4a91eceb,"pro_challenge_pro_name"), &pProChallengeProName, Script::ASSERT );
+			uint32 last_name = 0;
+			pLastNames->GetChecksum( Script::GenerateCRC( pProChallengeProName ), &last_name, Script::NO_ASSERT );
+			Mdl::Skate * pSkate = Mdl::Skate::Instance();
+			Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+			if ( last_name && last_name != pSkaterProfile->GetSkaterNameChecksum() )
+				continue;
+		}
+*/
+		if( pGoal->IsNetGoal())
+			continue;
+		
+		// create the screen element
+		Script::CStruct* p_elem_params = new Script::CStruct();
+		uint32 id = pGoal->GetGoalId();
+		
+		// add the id so we can use the same struct when calling the set_events script below
+		p_elem_params->AddChecksum( CRCD(0x9982e501,"goal_id"), id );
+		p_elem_params->AddString( CRCD(0xc4745838,"text"), p_view_goals_text );
+
+		// check for a win record
+		uint32 record_type;
+		if ( pGoal->HasWonGoal() )
+		{
+			int record = 0;
+			char record_string[128];
+
+			// special case for competition
+			if ( pGoal->IsCompetition() )
+			{
+				pGoalParams->GetInteger( CRCD(0xc22a2b72,"win_record"), &record, Script::NO_ASSERT );
+				sprintf( record_string, "%i", record );
+				switch ( record )
+				{
+					case 1:
+						strcat( record_string, "st place" );
+						break;
+					case 2:
+						strcat( record_string, "nd place" );
+						break;
+					case 3:
+						strcat( record_string, "rd place" );
+						break;
+					default:
+						Dbg_Message( "WARNING: competition place is %i", record );
+						break;
+				}						
+				p_elem_params->AddString( CRCD(0xbb48af8b,"win_record_string"), record_string );
+			}			
+			else if ( pGoalParams->GetChecksum( CRCD(0x96963902,"record_type"), &record_type, Script::NO_ASSERT ) )
+			{
+				// For the purposes of supporting different record types in the future,
+				// I've left some repeated code within each block.
+				if ( record_type == ( CRCD(0xcd66c8ae,"score") ) )
+				{
+					pGoalParams->GetInteger( CRCD(0xc22a2b72,"win_record"), &record, Script::NO_ASSERT );
+					sprintf( record_string, "%s", Str::PrintThousands( record ) );
+
+					p_elem_params->AddString( CRCD(0xbb48af8b,"win_record_string"), record_string );
+				}
+				else if ( record_type == ( CRCD(0x906b67ba,"time") ) )
+				{
+					int record = 0;
+					pGoalParams->GetInteger( CRCD(0xc22a2b72,"win_record"), &record, Script::NO_ASSERT );
+					
+					// break down into minutes, seconds, fraction
+					int min, sec, fraction;
+					sec = record / 1000;
+					min = ( sec / 60 );
+					sec -= ( min * 60 );
+					fraction = ( ( record % 1000 ) / 10 );
+					sprintf( record_string, "%i:%02i.%02i", min, sec, fraction );
+
+					p_elem_params->AddString( CRCD(0xbb48af8b,"win_record_string"), record_string );
+				}
+			}
+			else
+			{
+				p_elem_params->AddChecksum( NONAME,  CRCD(0xc80e196,"no_record_type")  );
+			}
+		}
+
+		// create the item
+		Script::RunScript( CRCD(0x380aea65,"view_goals_menu_add_item"), p_elem_params );
+
+		delete p_elem_params;
+	}
+	
+	// cleanup!
+	Script::CleanUpArray( p_ordered_goals );
+	Script::CleanUpArray( p_unordered_goals );
+	delete p_ordered_goals;
+	delete p_unordered_goals;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::AddGoalChoices( bool selected_only )
+{
+	
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		const char* p_view_goals_text = NULL;
+		pGoal->ReplaceTrickText();
+		pGoal->GetViewGoalsText( &p_view_goals_text );
+		
+		// skip goals without an associated text (eg, minigames)
+		if ( !p_view_goals_text )
+		{
+			continue;
+		}
+
+		if( pGoal->IsNetGoal())
+		{
+			continue;
+		}
+
+		if( pGoal->IsMinigame())
+		{
+			continue;
+		}
+
+        if( pGoal->GetParents()->m_relative != 0 )
+		{
+			continue;
+		}
+
+		if( GameNet::Manager::ScriptIsHost( NULL, NULL ))
+		{
+			// if( pGoal->HasSeenGoal() == false )
+			// {
+				// continue;
+			// }
+	
+			// if( pGoal->IsLocked())
+			// {
+				// continue;
+			// }
+		}
+
+		if( selected_only )
+		{
+			if( !pGoal->IsSelected())
+			{
+				
+				continue;
+			}
+		}
+		
+		// create the screen element
+		Script::CStruct* p_elem_params = new Script::CStruct();
+		uint32 id = pGoal->GetGoalId();
+		
+
+		// add the id so we can use the same struct when calling the set_events script below
+		p_elem_params->AddChecksum( CRCD(0x9982e501,"goal_id"), id );
+		p_elem_params->AddChecksum( CRCD(0xc2719fb0,"parent"), CRCD( 0x4d49ac0a, "current_menu" ) );
+		p_elem_params->AddString( CRCD(0xc4745838,"text"), p_view_goals_text );
+		p_elem_params->AddChecksum( CRCD(0x2f6bf72d,"font"), CRCD( 0x8aba15ec, "small" ) );		
+		p_elem_params->AddInteger( CRCD(0x13b9da7b,"scale"), 0 );
+		
+		
+		// set initial color and events for this item
+		if( selected_only )
+		{
+			Script::RunScript( CRCD(0xe704c86c,"view_selected_goals_menu_set_events"), p_elem_params);
+		}
+		else
+		{
+			Script::RunScript( CRCD(0x6790ac11,"choose_goals_menu_set_events"), p_elem_params);
+		}
+		// normally, script won't be updated until next frame -- we want it NOW
+		// p_new_script->Update();
+
+		delete p_elem_params;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::GoalIsLocked( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId, false );
+	
+	if ( pGoal )
+	{
+		return pGoal->IsLocked();
+	}
+	else
+	{
+		// check goal manager params struct in case the goal was in
+		// another level
+		int goalLocked = 0;
+		Script::CStruct* pGoalParams;
+		if ( mp_goalFlags->GetStructure( goalId, &pGoalParams, Script::NO_ASSERT ) )
+			pGoalParams->GetInteger( CRCD(0xd3e93882,"isLocked"), &goalLocked, Script::ASSERT );
+
+		return ( goalLocked != 0 );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoal* CGoalManager::IsInCompetition()
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->IsCompetition() && pGoal->IsActive() )
+		{
+			return pGoal;
+		}
+	}
+	return NULL;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::GetGoalAnimations( uint32 goalId, const char* type, Script::CScript* pScript )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	uint32 array_name = pGoal->mp_goalPed->GetGoalAnimations( type );
+	// Dbg_Assert( p_anims_array );
+
+	// never know
+	pScript->GetParams()->RemoveComponent( CRCD(0x18653764,"pro_stuff") );
+	if ( array_name )
+		pScript->GetParams()->AddChecksum( CRCD(0x18653764,"pro_stuff"), array_name );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGoalManager::GetRandomBettingMinigame()
+{
+	// create an index array and randomize
+	int numGoals = GetNumGoals();
+	int* indexes = new int[numGoals];
+	for ( int i = 0; i < numGoals; i++ )
+		indexes[i] = i;
+	for ( int i = 0; i < numGoals * 5; i++ )
+	{
+		int aIndex = Mth::Rnd( numGoals );
+		int bIndex = Mth::Rnd( numGoals );
+		int a = indexes[aIndex];
+		indexes[aIndex] = indexes[bIndex];
+		indexes[bIndex] = a;
+	}
+	
+	// look through until we find one that's suitable
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( indexes[i], (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->IsBettingGame() )
+		{
+			delete[] indexes;
+			return pGoal->GetGoalId();
+		}
+	}
+	delete[] indexes;
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::EndRunCalled( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	return pGoal->EndRunCalled();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGoalManager::GetBettingGuyId()
+{
+	// find the betting guy
+	int numGoals = GetNumGoals();
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->IsBettingGuy() )
+		{			
+			return pGoal->GetGoalId();
+		}
+	}
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::DeactivateMinigamesWithTimer()
+{
+	int numGoals = GetNumGoals();
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->IsMinigame() && pGoal->IsActive() && pGoal->ShouldUseTimer() )
+			pGoal->Deactivate();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CGoalManager::GetGoalManagerParams()
+{
+	return mp_goalFlags;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::ReplaceTrickText( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	return pGoal->ReplaceTrickText();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::ReplaceTrickText( uint32 goalId, const char* p_old_string, char* p_new_string, uint out_buffer_size )
+{
+#ifdef __NOPT_ASSERT__
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+#endif		// __NOPT_ASSERT__
+
+	// get the key combo array
+	Script::CStruct* p_goal_params = GetGoalParams( goalId );
+	Script::CArray* p_key_combo;
+	p_goal_params->GetArray( CRCD(0x79704516,"key_combos"), &p_key_combo, Script::ASSERT );
+
+	// build the string arrays
+	Script::CArray* p_key = new Script::CArray();
+	Script::CArray* p_trick = new Script::CArray();
+	int size = p_key_combo->GetSize();
+	p_key->SetSizeAndType( size, ESYMBOLTYPE_STRING );
+	p_trick->SetSizeAndType( size, ESYMBOLTYPE_STRING );
+
+	int premade_cat_index = -1;
+	if ( p_goal_params->ContainsFlag( CRCD(0x8e6014f6,"create_a_trick") ) )
+	{
+		p_goal_params->GetInteger( CRCD(0x5b077ce1,"trickName"), &premade_cat_index, Script::ASSERT );
+	}
+	
+	if ( !fill_trick_and_key_combo_arrays( p_key_combo, p_key, p_trick, premade_cat_index ) )
+	{
+		Script::CleanUpArray( p_key );
+		Script::CleanUpArray( p_trick );
+		delete p_key;
+		delete p_trick;
+		return false;
+	}
+
+	find_and_replace_trick_string( p_old_string, p_new_string, p_key, p_trick, out_buffer_size );
+	
+	// cleanup!
+	Script::CleanUpArray( p_key );
+	Script::CleanUpArray( p_trick );
+	delete p_key;
+	delete p_trick;
+
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::ReplaceAllTrickText()
+{
+	int numGoals = GetNumGoals();
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->IsInitialized() )
+			pGoal->ReplaceTrickText();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+void CGoalManager::UnlockProSpecificChallenges()
+{
+	m_proChallengesUnlocked = true;
+	int numGoals = GetNumGoals();
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		
+		if ( pGoal->GetParams()->ContainsFlag( CRCD(0xe0f96410,"pro_specific_challenge") ) )
+		{
+			// only unlock it if it's beaten or we're using the right skater
+			if ( pGoal->HasWonGoal() || pGoal->mp_goalPed->ProIsCurrentSkater() )
+				pGoal->UnlockGoal();
+		}
+	}
+}
+*/
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CGoalManager::ProSpecificChallengesUnlocked()
+{
+	return m_proChallengesUnlocked;
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetNumberCollected( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId, true );
+
+	return pGoal->GetNumberCollected();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetNumberOfFlags( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId, true );
+
+	return pGoal->GetNumberOfFlags();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::IsPro()
+{
+	return m_isPro;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::ResetGoalFlags( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+
+	pGoal->ResetGoalFlags();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::ColorTrickObjects( uint32 goalId, int seqIndex, bool clear )
+{
+	CGoal* pGoal = GetGoal( goalId );
+
+	pGoal->ColorTrickObjects( seqIndex, clear );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CGoalManager::GetNumberOfTimesGoalStarted( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId );
+
+	return pGoal->GetNumberOfTimesGoalStarted();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::GoalExists( uint32 goalId )
+{
+	CGoal* pGoal = GetGoal( goalId, false );
+	if ( pGoal )
+		return true;
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CGoalManager::GetGoalAttemptInfo()
+{
+	int numGoals = GetNumGoals();
+	int totalStarts = 0;
+	Script::CStruct* attemptInfo = new Script::CStruct();
+
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		if ( !pGoal->IsMinigame() && !pGoal->IsNetGoal() && pGoal->HasWonGoal() )
+			totalStarts += pGoal->GetNumberOfTimesGoalStarted();
+	}
+	attemptInfo->AddInteger( CRCD(0xd023adbe,"totalStarts"), totalStarts );
+	return attemptInfo;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetCanStartGoal( int value )
+{
+	if ( value != 0 )
+		m_canStartGoal = true;
+	else
+		m_canStartGoal = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::CheckTrickObjects()
+{
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+
+	Mdl::Score* pScore = pSkater->GetScoreObject();
+	int score = pScore->GetLastScoreLanded();
+	
+	Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+	Dbg_Assert(pTrickComponent);
+
+	Obj::CPendingTricks pendingTricks = pTrickComponent->m_pending_tricks;
+
+	int max_tricks = pendingTricks.m_NumTrickItems;
+	if ( max_tricks > pendingTricks.vMAX_PENDING_TRICKS )
+		max_tricks = pendingTricks.vMAX_PENDING_TRICKS;
+
+	for ( int i = 0; i < max_tricks; i++ )
+	{
+		GotTrickObject( pendingTricks.m_Checksum[i], score );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+void CGoalManager::MarkProSpecificChallengeBeaten( uint32 skater_name, int beaten )
+{
+	Dbg_MsgAssert( mp_proSpecificChallenges->ContainsComponentNamed( skater_name ), ("MarkProSpecificChallengeBeaten called with unknown skater") );
+	mp_proSpecificChallenges->AddInteger( skater_name, beaten );
+}
+*/
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool CGoalManager::SkaterHasBeatenProSpecificChallenge( uint32 skater_name )
+{
+	// Dbg_MsgAssert( mp_proSpecificChallenges->ContainsComponentNamed( skater_name ), ("SkaterHasBeatenProSpecificChallenge called with unknown skater %s", Script::FindChecksumName( skater_name ) ) );
+	if ( mp_proSpecificChallenges->ContainsComponentNamed( skater_name ) )
+	{
+		int beaten;
+		mp_proSpecificChallenges->GetInteger( skater_name, &beaten, Script::ASSERT );
+		return ( beaten != 0 );
+	}
+	return false;
+}
+*/
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetEndRunType( uint32 goalId, Game::EndRunType newEndRunType )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->SetEndRunType( newEndRunType );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::ShouldUseTimer()
+{
+	int numGoals = GetNumGoals();
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		
+		if ( pGoal->IsActive() && pGoal->ShouldUseTimer() )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetShouldDeactivateOnExpire( uint32 goalId, bool should_deactivate )
+{
+	CGoal* pGoal = GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->SetShouldDeactivateOnExpire( should_deactivate );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalManager::Land()
+{
+	if ( ShouldLogTrickObject() )
+		CheckTrickObjects();
+	else 
+	{
+		int numGoals = GetNumGoals();
+		for ( int i = 0; i < numGoals; i++ )
+		{
+			uint32 key;
+			CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+			Dbg_Assert( pGoal );
+
+			if ( pGoal->IsActive() && pGoal->IsHorseGoal() )
+			{
+				Game::CHorseGoal* pHorseGoal = (Game::CHorseGoal*)pGoal;
+				pHorseGoal->CheckScore();
+				break;
+			}
+			else if ( pGoal->IsActive() && pGoal->GetParams()->ContainsFlag( CRCD(0xc63432a7,"high_combo") ) )
+			{
+				// TODO: maybe the high combo goal should be a subclass rather than
+				// having a special case here?				
+				Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+				if ( pSkater )
+				{
+					Mdl::Score* pScore = pSkater->GetScoreObject();
+					if ( pScore->GetLastScoreLanded() > 0 )
+					{
+						Script::CStruct* pScriptParams = new Script::CStruct();
+						pScriptParams->AddChecksum( CRCD(0x9982e501,"goal_id"), pGoal->GetGoalId() );
+						pScriptParams->AddInteger( CRCD(0xb9072b65,"last_score_landed"), pScore->GetLastScoreLanded() );
+						Script::RunScript( CRCD(0x6b755ed8,"goal_highcombo_check_score"), pScriptParams );
+						delete pScriptParams;
+					}
+				}
+			}
+			else if ( pGoal->IsActive() && pGoal->ShouldRequireCombo() && pGoal->GetParams()->ContainsFlag( CRCD(0x7100155d,"combo_started") ) )
+			{
+				// lose the goal next frame
+				Script::CStruct* pScriptParams = new Script::CStruct();
+				pScriptParams->AddChecksum( CRCD(0x9982e501,"goal_id"), pGoal->GetGoalId() );
+				Script::SpawnScript( CRCD(0xfefda11,"goal_lose_next_frame"), pScriptParams );
+				delete pScriptParams;
+			}
+			else if ( pGoal->IsActive() && pGoal->GetParams()->ContainsFlag( CRCD( 0x293b95a7, "score_goal" ) ) )
+			{
+				Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+				if ( pSkater )
+				{
+					Mdl::Score* pScore = pSkater->GetScoreObject();
+					int required_score;
+					pGoal->GetParams()->GetInteger( CRCD(0xcd66c8ae,"score"), &required_score, Script::ASSERT );
+					if ( pScore->GetTotalScore() + pScore->GetScorePotValue() >= required_score )
+					{
+						pGoal->GetParams()->AddInteger( CRCD(0x3da891b6,"winning_score"), pScore->GetTotalScore() + pScore->GetScorePotValue() );
+						WinGoal( pGoal->GetGoalId() );
+						pGoal->GetParams()->RemoveComponent( CRCD(0x3da891b6,"winning_score") );
+					}
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalManager::Bail()
+{
+	int numGoals = GetNumGoals();
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+
+		if ( pGoal->IsActive() && pGoal->ShouldRequireCombo() && pGoal->GetParams()->ContainsFlag( CRCD(0x7100155d,"combo_started") ) )
+		{
+			// lose the goal next frame
+			Script::CStruct* pScriptParams = new Script::CStruct();
+			pScriptParams->AddChecksum( CRCD(0x9982e501,"goal_id"), pGoal->GetGoalId() );
+			Script::SpawnScript( CRCD(0xfefda11,"goal_lose_next_frame"), pScriptParams );
+			delete pScriptParams;
+		}
+		if ( pGoal->IsActive() && pGoal->IsTetrisGoal() )
+		{
+			int trick_flag;
+			if ( pGoal->GetParams()->GetInteger( CRCD( 0x60b827d5, "trick_flag" ), &trick_flag, Script::NO_ASSERT ) )
+			{
+				if ( trick_flag != 0 )
+				{
+					pGoal->GetParams()->AddInteger( CRCD(0x60b827d5,"trick_flag"), 0 );
+				}
+			}
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::AwardMinigameCash( uint32 goalId, int amount )
+{
+	CGoal* pGoal = GetGoal( goalId, true );
+	if ( pGoal && pGoal->IsMinigame() )
+	{
+		CMinigame* pMinigame = (CMinigame*)pGoal;
+		return pMinigame->AwardCash( amount );
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::ResetCareer()
+{
+	// reset all of the goal manager's memory
+	mp_goalFlags->Clear();
+
+	m_goalPoints = 0;
+	m_totalGoalPointsEarned = 0;
+	m_cash = 0;
+	m_totalCash = 0;
+	m_numGoalsBeaten = 0;
+	// m_proChallengesUnlocked = false;
+	m_isPro = false;
+	
+	// mp_proSpecificChallenges->AppendStructure( Script::GetStructure( CRCD(0xd8b84847,"goal_pro_specific_challenges_beaten"), Script::ASSERT ) );
+	m_currentChapter = m_currentStage = 0;
+
+	mp_difficulty_levels->Clear();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::AwardAllGoalCash()
+{
+	int numGoals = GetNumGoals();
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		
+		if ( !pGoal->IsMinigame() && !pGoal->HasWonGoal() )
+		{
+			// printf("found an unbeaten goal\n");
+			pGoal->MarkBeaten();
+			Script::CStruct* pRewardParams = new Script::CStruct();
+			pGoal->GetRewardsStructure( pRewardParams );
+			int cash;
+			if ( pRewardParams->GetInteger(	 "cash", &cash, Script::NO_ASSERT ) )
+			{
+				const char* p_goal_name;
+				pGoal->GetParams()->GetString( CRCD(0xbfecc45b,"goal_name"), &p_goal_name, Script::ASSERT );
+				// printf("adding $%i for goal %s\n", cash, p_goal_name);
+				AddCash( cash );
+			}
+			delete pRewardParams;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::UpdateFamilyTrees()
+{
+	int numGoals = GetNumGoals();
+	
+	// update all the parent lists
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		uint32 parent_id = pGoal->GetGoalId();
+		Game::CGoalLink* p_child = pGoal->GetChildren();
+		
+		while ( p_child && p_child->m_relative != 0 )
+		{
+			// printf("goal has a child\n");
+			uint32 child_id = p_child->m_relative;
+			// get the child
+			CGoal* pChildGoal = GetGoal( child_id, false );
+			Dbg_MsgAssert( pChildGoal, ( "Failed to find goal %s when binding child to parent\n", Script::FindChecksumName( child_id ) ) );
+
+			// search the parent list and add if necessary
+			Game::CGoalLink* p_parentNode = pChildGoal->GetParents();
+			bool found = false;
+			while ( p_parentNode && p_parentNode->m_relative != 0 )
+			{
+				if ( p_parentNode->m_relative == parent_id )
+				{
+					found = true;
+					break;
+				}
+				p_parentNode = p_parentNode->mp_next;
+			}
+
+			if ( !found )
+				pChildGoal->AddParent( parent_id );
+			
+			p_child = p_child->mp_next;
+		}
+	}
+
+	// send inherited params down the tree from root parent
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		
+		// SetInheritableFlags will automatically traverse down the tree,
+		// so we only need to call it on nodes without parents - ie, the root 
+		// node of the tree.
+		if ( pGoal->GetParents()->m_relative == 0 )
+		{
+			pGoal->SetInheritableFlags();
+		}
+	}
+	
+	// debugging shit.
+	for ( int i = 0; i < numGoals; i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+	
+		// print out children list
+		Game::CGoalLink* p_test = pGoal->GetChildren();
+		bool print_newline = false;
+		if ( p_test->m_relative != 0 )
+			// printf("children of %s\n", Script::FindChecksumName( pGoal->GetGoalId() ) );
+		while ( p_test && p_test->m_relative != 0 )
+		{
+			print_newline = true;
+			// printf("\t%s\n", Script::FindChecksumName( p_test->m_relative ) );
+			p_test = p_test->mp_next;
+		}
+		
+		// print out the parent list
+		p_test = pGoal->GetParents();
+		if ( p_test->m_relative != 0 )
+			// printf("parents of %s\n", Script::FindChecksumName( pGoal->GetGoalId() ) );
+		while ( p_test && p_test->m_relative != 0 )
+		{
+			print_newline = true;
+			// printf("\t%s\n", Script::FindChecksumName( p_test->m_relative ) );
+			p_test = p_test->mp_next;
+		}
+		// if ( print_newline )
+			// printf("\n");
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::FindCircularLinks( uint32 parent, uint32 child )
+{
+#ifdef __NOPT_ASSERT__
+	CGoal* pGoal = GetGoal( child, false );
+	if ( pGoal )
+	{
+		CGoalLink* p_link = pGoal->GetChildren();
+		while ( p_link && p_link->m_relative != 0 )
+		{
+			if ( p_link->m_relative == parent )
+				Dbg_MsgAssert( 0, ( "Circular goal link path found from %s to %s\n", Script::FindChecksumName( parent ), Script::FindChecksumName( child ) ) );
+			if ( p_link->m_relative == child )
+				Dbg_MsgAssert( 0, ( "Goal (%s) has itself as a child\n", Script::FindChecksumName( child ) ) );
+
+			// recurse!
+			FindCircularLinks( parent, p_link->m_relative );
+			p_link = p_link->mp_next;
+		}
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( checksum )
+	{
+		case 0x3ae9211b: // IsRootNode
+		{
+			CGoal* pGoal = GetGoal( pParams );
+			if ( pGoal )
+				return ( pGoal->GetParents()->m_relative == 0 );
+			return false;
+			break;
+		}
+	
+		case 0xd782f8de: // IsLeafNode
+		{
+			CGoal* pGoal = GetGoal( pParams );
+			if ( pGoal )
+				return ( pGoal->GetChildren()->m_relative == 0 );
+			return false;
+			break;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGoalManager::GetGoalIdFromParams( Script::CStruct* pParams, bool assert )
+{
+	uint32 goalId = 0;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, assert );
+	return goalId;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::CreateGoalLevelObjects( void )
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		Script::CStruct* pParams;
+		Script::CArray *pGeomArray;
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		pParams = pGoal->GetParams();
+
+		// Create the associated pieces of geometry
+		if ( pParams->GetArray( CRCD(0xf4eb5c60,"create_geometry"), &pGeomArray ) )
+		{
+			uint32 j, geometry;
+			for( j = 0; j < pGeomArray->GetSize(); j++ )
+			{
+				geometry = pGeomArray->GetChecksum( j );
+				CFuncs::ScriptCreateFromNodeIndex( SkateScript::FindNamedNode( geometry ));
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetDifficultyLevel( int level )
+{
+	Dbg_MsgAssert( level >= 0 && level <= vMAXDIFFICULTYLEVEL, ( "SetDifficultyLevel called with level %i", level ) );
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	uint32 gameMode = skate_mod->GetGameMode()->GetNameChecksum();
+	mp_difficulty_levels->AddInteger( gameMode, level );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+GOAL_MANAGER_DIFFICULTY_LEVEL CGoalManager::GetDifficultyLevel( bool career )
+{
+	uint32 gameMode;
+    if ( career)
+    {
+        gameMode = CRCD(0x4da4937b,"career");
+    }
+    else
+    {
+        Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+    	gameMode = skate_mod->GetGameMode()->GetNameChecksum();
+    }
+	
+    int level;
+	
+	// if no diff level assigned for this game mode yet, assume normal
+	if ( !mp_difficulty_levels->GetInteger( gameMode, &level, Script::NO_ASSERT ) )
+		level = 1;
+	
+	GOAL_MANAGER_DIFFICULTY_LEVEL diff_level = GOAL_MANAGER_DIFFICULTY_MEDIUM;
+	
+    switch ( level )
+	{
+		case 0:
+			diff_level = GOAL_MANAGER_DIFFICULTY_LOW;
+			break;
+		case 1:
+			diff_level = GOAL_MANAGER_DIFFICULTY_MEDIUM;
+			break;
+		case 2:
+			diff_level = GOAL_MANAGER_DIFFICULTY_HIGH;
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "bad difficulty level %i", level ) );
+			break;
+	}
+
+	return diff_level;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::CanRestartStage()
+{
+	printf("STUBBED\n");
+	return false;
+/*
+	int activeGoal = GetActiveGoal();
+	if ( activeGoal != -1 )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( activeGoal, (int*)&key );
+		Dbg_Assert( pGoal );
+	
+		if ( pGoal->m_inherited_flags & GOAL_INHERITED_FLAGS_RETRY_STAGE )
+		{
+			return true;
+		}
+	}
+	return false;
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetGoalChaptersAndStages()
+{
+	// grab the chapter info array
+	Script::CArray* pChapterGoalsArray = Script::GetArray( CRCD( 0xb892776f, "chapter_goals" ), Script::ASSERT );
+	Dbg_Assert( pChapterGoalsArray->GetType() == ESYMBOLTYPE_ARRAY );
+
+	int chapter_goals_array_size = pChapterGoalsArray->GetSize();
+	for ( int chapter_index = 0; chapter_index < chapter_goals_array_size; chapter_index++ )
+	{
+		Script::CArray* pStageArray = pChapterGoalsArray->GetArray( chapter_index );
+		Dbg_Assert( pStageArray->GetType() == ESYMBOLTYPE_ARRAY );
+		int stage_array_size = pStageArray->GetSize();
+		for ( int stage_index = 0; stage_index < stage_array_size; stage_index++ )
+		{
+			Script::CArray* pStage = pStageArray->GetArray( stage_index );
+			Dbg_Assert( pStage->GetType() == ESYMBOLTYPE_STRUCTURE );
+			int num_goals = pStage->GetSize();
+			for ( int goal_index = 0; goal_index < num_goals; goal_index++ )
+			{
+				Script::CStruct* pGoalStruct = pStage->GetStructure( goal_index );
+				uint32 goal_id;
+				if ( pGoalStruct->GetChecksum( CRCD( 0x9982e501, "goal_id" ), &goal_id, Script::NO_ASSERT ) )
+				{
+					CGoal* pGoal = GetGoal( goal_id, false );
+					if ( pGoal )
+					{
+						pGoal->SetChapter( chapter_index );
+						pGoal->SetStage( stage_index );
+					}
+				}
+			}
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGoalManager::AdvanceStage( bool force, uint32 just_won_goal_id )
+{
+	// printf("CGoalManager::AdvanceStage\n");
+	// DeactivateAllGoals();
+	
+	// K: This was to fix an assert when winning a created goal in the park editor.
+	// Fixed by just not doing any advance-stage logic when in created goals mode.
+	Mdl::Skate *p_skate_mod = Mdl::Skate::Instance();
+	if ( p_skate_mod->GetGameMode()->IsTrue( CRCD( 0x353961d7, "is_creategoals" ) ) )
+	{
+		return false;
+	}	
+	
+	// grab the chapter info array
+	Script::CArray* pChapterGoalsArray = Script::GetArray( CRCD( 0xb892776f, "chapter_goals" ), Script::ASSERT );
+	Dbg_Assert( pChapterGoalsArray->GetType() == ESYMBOLTYPE_ARRAY );
+	
+	// grab num goals to complete array
+	Script::CArray* pChapterNumGoalsToComplete = Script::GetArray( CRCD( 0x846f65f5, "CHAPTER_NUM_GOALS_TO_COMPLETE" ), Script::ASSERT );
+	Dbg_Assert( pChapterNumGoalsToComplete->GetType() == ESYMBOLTYPE_ARRAY );
+	
+	if ( m_currentChapter >= (int)pChapterGoalsArray->GetSize() )
+	{
+		// they beat the game
+		return false;
+	}
+	// printf("m_currentChapter = %i, m_currentStage = %i\n", m_currentChapter, m_currentStage);
+	Dbg_MsgAssert( m_currentChapter < (int)pChapterGoalsArray->GetSize(), ( "the current chapter is %i, but the chapter goals array has size %i.", m_currentChapter, pChapterGoalsArray->GetSize() ) );
+	Script::CArray* pCurrentChapter = pChapterGoalsArray->GetArray( m_currentChapter );
+	Dbg_MsgAssert( m_currentStage < (int)pCurrentChapter->GetSize(), ( "The current stage is set to %i, but the chapter array only has %i stage(s).", m_currentStage, pCurrentChapter->GetSize() ) );
+	Script::CArray* pCurrentStage = pCurrentChapter->GetArray( m_currentStage );
+
+	Script::CArray* pChapterTotals = pChapterNumGoalsToComplete->GetArray( m_currentChapter );
+	int stage_total_target = pChapterTotals->GetInteger( m_currentStage );
+	int num_stages = pChapterTotals->GetSize();
+
+	// resolve just won goal to parent goal, which is what's listed
+	// in the chapter info array
+	if ( just_won_goal_id )
+	{
+		// printf("just won goal %s\n", Script::FindChecksumName( just_won_goal_id ) );
+		CGoal* pJustWonGoal = GetGoal( just_won_goal_id );
+		CGoalLink* pJustWonParents = pJustWonGoal->GetParents();
+		while ( pJustWonParents && pJustWonParents->m_relative != 0 )
+		{
+			just_won_goal_id = pJustWonParents->m_relative;
+			pJustWonGoal = GetGoal( pJustWonParents->m_relative );
+			pJustWonParents = pJustWonGoal->GetParents();
+		}
+	}
+	
+	// check the required total
+	int num_goals = pCurrentStage->GetSize();
+	int stage_total = 0;
+
+	for ( int goal_index = 0; goal_index < num_goals; goal_index++ )
+	{
+		Script::CStruct* pGoalStruct = pCurrentStage->GetStructure( goal_index );
+		uint32 goal_id;
+		if ( pGoalStruct->GetChecksum( CRCD( 0x9982e501, "goal_id" ), &goal_id, Script::NO_ASSERT ) )
+		{
+			CGoal* pGoal = GetGoal( goal_id, false );
+			
+			if ( pGoal )
+			{
+				// resolve to parent goal id
+				CGoalLink* pParents = pGoal->GetParents();
+				while ( pParents && pParents->m_relative != 0 )
+				{
+					goal_id = pParents->m_relative;
+					pGoal = GetGoal( pParents->m_relative );
+					pParents = pGoal->GetParents();				
+				}
+			}
+
+			if ( pGoal )
+			{
+				if ( ( goal_id == just_won_goal_id && !pGoal->HasWonGoal() ) || ( pGoal->HasWonGoal() ) )
+				{
+					stage_total++;
+					// printf( "you've won %s, stage_total = %i\n", Script::FindChecksumName( goal_id ), stage_total );
+				}
+			}
+			else
+			{
+				// look for goal in other level
+				Script::CStruct* pTempGoalParams;
+				if ( mp_goalFlags->GetStructure( goal_id, &pTempGoalParams, Script::NO_ASSERT ) )
+				{
+					int hasBeaten = 0;
+					pTempGoalParams->GetInteger( "hasBeaten", &hasBeaten, Script::ASSERT );
+					if ( hasBeaten != 0 )
+					{
+						// printf("you've beaten %s in another level\n", Script::FindChecksumName( p_comp->mNameChecksum ) );
+						stage_total++;
+					}					
+				}
+			}
+		}
+	}
+	
+	// printf("stage_total = %i, stage_total_target = %i\n", stage_total, stage_total_target );
+	if ( stage_total >= stage_total_target || force )
+	{
+		// we've beaten the stage
+		if ( m_currentStage + 1 == num_stages )
+		{
+			// we've finished the chapter
+			m_currentChapter++;
+			m_currentStage = 0;
+		}
+		else
+		{
+			m_currentStage++;
+		}
+
+		// printf("m_currentStage = %i\n", m_currentStage);
+		// reinitialize the chapter and stage
+		UninitializeAllGoals();
+		InitializeAllGoals();
+
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::RunLastStageScript()
+{
+	// DeactivateAllGoals();
+	
+	// don't do anything if they beat the game
+	Script::CArray* pChapterGoalsArray = Script::GetArray( CRCD( 0xb892776f, "chapter_goals" ), Script::ASSERT );
+	Dbg_Assert( pChapterGoalsArray->GetType() == ESYMBOLTYPE_ARRAY );
+	if ( m_currentChapter >= (int)pChapterGoalsArray->GetSize() )
+	{
+		return;
+	}
+
+	// K: This was to fix an assert when winning a created goal in the park editor.
+	// Fixed by just not doing any advance-stage logic when in created goals mode.
+	Mdl::Skate *p_skate_mod = Mdl::Skate::Instance();
+	if ( p_skate_mod->GetGameMode()->IsTrue( CRCD( 0x353961d7, "is_creategoals" ) ) )
+	{
+		return;
+	}	
+	
+	// grab the chapter scripts arrray
+	Script::CArray* pChapterScriptsArray = Script::GetArray( CRCD(0x9f00892d,"CHAPTER_COMPLETION_SCRIPTS"), Script::ASSERT );
+	Dbg_Assert( pChapterScriptsArray->GetType() == ESYMBOLTYPE_ARRAY );
+	
+	int last_stage_index = m_currentStage;
+	int last_chapter_index = m_currentChapter;
+
+	if ( last_stage_index == 0 )
+	{
+		if ( last_chapter_index == 0 )
+		{
+			// we're on the first chapter and stage - don't do anything
+			return;
+		}
+		last_chapter_index--;
+
+		Dbg_MsgAssert( last_chapter_index < (int)pChapterScriptsArray->GetSize(), ( "last_chapter_index %i too big", last_chapter_index ) );
+
+		// figure the stage index
+		Script::CArray* pChapterArray = pChapterScriptsArray->GetArray( last_chapter_index );
+		Dbg_Assert( pChapterArray );
+		last_stage_index = pChapterArray->GetSize() - 1;
+		// [ { script_name=NJ_StageSwitch_0_to_1_1 }, { script_name=NJ_StageSwitch_1_1_to_1_2 }, { script_name=NJ_StageSwitch_1_2_to_2_1 } ]
+	}
+	else
+		last_stage_index--;
+
+	uint32 script_name;
+	Script::CArray* pChapterParamsArray = pChapterScriptsArray->GetArray( last_chapter_index );
+	Dbg_Assert( pChapterParamsArray );
+	Script::CStruct* pStageScriptStruct = pChapterParamsArray->GetStructure( last_stage_index );
+	Dbg_Assert( pStageScriptStruct );
+	if ( pStageScriptStruct->GetChecksum( CRCD(0x6166f3ad,"script_name"), &script_name, Script::NO_ASSERT ) )
+	{
+		Script::SpawnScript( script_name );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetTeamMember( uint32 pro, bool remove, bool mark_used )
+{
+	if ( remove )
+		mp_team->RemoveComponent( pro );
+	else
+	{
+		if ( mark_used )
+			mp_team->AddInteger( pro, 1 );	
+		else
+			mp_team->AddInteger( pro, 0 );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::KillTeamMembers( )
+{
+	mp_team->Clear();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::SetTeamName( const char* pTeamName )
+{
+	mp_team->AddString( CRCD(0x703d7582,"team_name"), pTeamName );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalManager::HideAllGoalPeds( bool hide )
+{
+	for ( int i = 0; i < GetNumGoals(); i++ )
+	{
+		uint32 key;
+		CGoal* pGoal = m_goalList.GetItemByIndex( i, (int*)&key );
+		Dbg_Assert( pGoal );
+		pGoal->mp_goalPed->Hide( hide );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoalManager* GetGoalManager()
+{
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	return skate_mod->GetGoalManager();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddGoal | 
+// @parm name | name | the goal id
+// @parm structure | params | goal parameters
+bool ScriptAddGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	Script::CStruct* pSubParams;
+	pParams->GetStructure( CRCD(0x7031f10c,"params"), &pSubParams, Script::ASSERT );
+
+	return pGoalManager->AddGoal( goalId, pSubParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_RemoveGoal | 
+// @parm name | name | the goal id
+bool ScriptRemoveGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	pGoalManager->RemoveGoal( goalId );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetGoal | currently does nothing at all!
+// @parm name | name | the goal id
+bool ScriptGetGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#if 0
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	if ( pGoal )
+	{
+	}
+	#endif
+	
+	// TODO: return it in the script components
+
+	// flags are not allowed as params,
+	// otherwise there will be a leak
+	// as things get appended, and re-appended
+
+	// activate goal->
+	// should turn off the score degrader
+	// and the score accumulation script 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_EditGoal | this will append the given structure
+// onto the goal's existing params structure
+// @parm name | name | the goal id
+// @parm structure | params | structure to append
+bool ScriptEditGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	Script::CStruct* pSubParams;
+	pParams->GetStructure( CRCD(0x7031f10c,"params"), &pSubParams, Script::ASSERT );
+
+	return pGoalManager->EditGoal( goalId, pSubParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ActivateGoal |
+// @parm name | name | the goal id
+// @flag dontAssert | pass this in if you don't want the goal manager 
+// to assert if it can't find the specified goal (not recommended)
+bool ScriptActivateGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    // put in for testing
+    bool dontAssert = false;
+    if ( pParams->ContainsFlag( CRCD(0x9ac8d54f,"dontAssert") ) )
+        dontAssert = true;
+	
+	return pGoalManager->ActivateGoal( goalId, dontAssert );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_DeactivateGoal | 
+// @parm name | name | the goal id
+bool ScriptDeactivateGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	return pGoalManager->DeactivateGoal( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_DeactivateGoal | 
+bool ScriptClearLastGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	pGoalManager->ClearLastGoal();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_WinGoal | 
+// @parm name | name | the goal id
+bool ScriptWinGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#ifdef	__NOPT_ASSERT__
+	bool had_object = false;
+	uint32	old_id = 0;
+	Obj::CObjectPtr p_obj;
+	if (pScript->mpObject)
+	{
+		had_object = true;
+		p_obj = pScript->mpObject;
+		old_id = p_obj->GetID();
+	}
+	#endif
+	
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	bool result = pGoalManager->WinGoal( goalId );
+
+	#ifdef	__NOPT_ASSERT__
+	if (had_object)
+	{
+		Dbg_MsgAssert(p_obj,("%s\nObject %s was deleted indirectly \nWhen running a script via GoalManager_WinGoal\n You probably use KILL PREFIX = ...., Maybe you could use DIE instead",pScript->GetScriptInfo(),Script::FindChecksumName(old_id)));
+	}
+	#endif
+		
+	return result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_LoseGoal | 
+// @parm name | name | the goal id
+bool ScriptLoseGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	#ifdef	__NOPT_ASSERT__
+	bool had_object = false;
+	uint32	old_id = 0;
+	Obj::CObjectPtr p_obj;
+	if (pScript->mpObject)
+	{
+		had_object = true;
+		p_obj = pScript->mpObject;
+		old_id = p_obj->GetID();
+	}
+	#endif
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	bool result = pGoalManager->LoseGoal( goalId );
+
+	#ifdef	__NOPT_ASSERT__
+	if (had_object)
+	{
+		Dbg_MsgAssert(p_obj,("%s\nObject %s was deleted indirectly when running a script via GoalManager_LoseGoal\n You probaly use KILL PREFIX = ...., Maybe you could use DIE instead",pScript->GetScriptInfo(),Script::FindChecksumName(old_id)));
+	}
+	#endif
+	
+	
+	return result;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_RemoveAllGoals | this will completely destroy
+// all the goals currently in existence...make sure you call GoalManager_LevelUnload
+// first if you want to remember anything about the state of the goals before
+// destroying them.
+bool ScriptRemoveAllGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->RemoveAllGoals();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_DeactivateAllGoals | 
+bool ScriptDeactivateAllGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool including_net_goals;
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	including_net_goals = false;
+	if( pParams->ContainsComponentNamed( CRCD(0xcc365298,"force_all")))
+	{
+		including_net_goals = true;
+	}
+
+	pGoalManager->DeactivateAllGoals( including_net_goals );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UninitializeAllGoals |
+bool ScriptUninitializeAllGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->UninitializeAllGoals();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UninitializeGoal |
+// @parm name | name | the goal id
+bool ScriptUninitializeGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	if ( pParams->ContainsFlag( CRCD(0x687de558,"affect_tree") ) )
+	{
+		pGoalManager->UninitializeGoalTree( goalId );
+	}
+	else
+	{
+		pGoalManager->UninitializeGoal( goalId );
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_InitializeAllGoals | 
+bool ScriptInitializeAllGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->InitializeAllGoals();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_InitializeAllSelectedGoals | 
+bool ScriptInitializeAllSelectedGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->InitializeAllSelectedGoals();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UpdateAllGoals | This forces the goal manager to run
+// an update cycle.
+bool ScriptUpdateAllGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->Update();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_HasActiveGoals | returns true if there is at least one
+// active goal (excluding minigames and other goals that don't count towards the total)
+bool ScriptHasActiveGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	bool count_all = pParams->ContainsFlag( CRCD(0xa21884b5,"count_all") );
+
+	return ( pGoalManager->GetNumActiveGoals( count_all ) > 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GoalIsActive | returns true if the specified goal
+// is currently active
+// @parm name | name | the goal id
+bool ScriptGoalIsActive(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	return pGoalManager->GoalIsActive( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetGoalTimer | resets the goal timer to it's inital
+// value.
+// @parm name | name | the goal id
+bool ScriptSetGoalTimer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	
+	return pGoalManager->SetGoalTimer( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ZeroGoalTimer | zeros the goal timer; added to allow a hack to fix split screen horse end runs to be minimally invasive
+// @parm name | name | the goal id
+bool ScriptZeroGoalTimer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	
+	return pGoalManager->SetGoalTimer( goalId, 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_PauseAllGoals | 
+bool ScriptPauseAllGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    pGoalManager->PauseAllGoals();
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UnPauseAllGoals | 
+bool ScriptUnPauseAllGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    pGoalManager->UnPauseAllGoals();
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetGoalFlag | for setting the special goal flags
+// @parm name | name | the goal id
+// @uparm name | the name of the flag to set
+// @uparm 1 | value to use 
+bool ScriptSetGoalFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	
+	uint32 flagChecksum;
+	pParams->GetChecksum( NONAME, &flagChecksum, Script::ASSERT );
+
+	int flagValue;
+	pParams->GetInteger( NONAME, &flagValue, Script::ASSERT );
+
+    bool success = pGoalManager->SetGoalFlag( goalId, flagChecksum, flagValue );
+
+    return success;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_HasSeenGoal | returns true if the player has
+// activated the specified goal at least once
+// @parm name | name | the goal id
+bool ScriptHasSeenGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    return ( pGoalManager->HasSeenGoal( goalId ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_RestartLastGoal | this will restart the last goal
+// It will fail silently if there is no goal to retry
+bool ScriptRestartLastGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    pGoalManager->RestartLastGoal();
+
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*// @script | GoalManager_CreateGoalFlag | 
+// @parm name | name | the goal id
+// @parm name | flag | the name of the flag to create
+bool ScriptCreateGoalFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 name;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+
+    uint32 flag;
+    pParams->GetChecksum( CRCD(0x2e0b1465,"flag"), &flag, Script::ASSERT ); 
+
+    pGoalManager->CreateGoalFlag( name, flag );
+    return true;
+}*/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AllFlagsSet | 
+// @parm name | name | the goal id
+bool ScriptAllFlagsSet(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 name;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+
+    return pGoalManager->AllFlagsSet( name );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GoalFlagSet | true if a goal flag has been set
+// @parm name | name | the goal id
+// @parm name | flag | the name of the flag to check
+bool ScriptGoalFlagSet(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	
+	uint32 flag;
+	pParams->GetChecksum( CRCD(0x2e0b1465,"flag"), &flag, Script::ASSERT );
+
+	return pGoalManager->GoalFlagSet( goalId, flag );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CanRetryGoal | returns true if there is a goal
+// to retry
+bool ScriptCanRetryGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    return pGoalManager->CanRetryGoal();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetGoalParams | this will append the goal's 
+// params onto the scripts params.  The goal manager scripts use this
+// extensively to avoid passing large structures back and forth.
+// @parm name | name | the goal id
+bool ScriptGetGoalParams(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+    
+    pScript->GetParams()->AppendStructure( pGoalManager->GetGoalParams( goalId ) );
+
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CanStartGoal |
+// @parm name | name | the goal id
+bool ScriptCanStartGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    // uint32 goalId;
+    // pParams->GetChecksum( "name", &goalId, Script::ASSERT );
+
+    return pGoalManager->CanStartGoal();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_NextRaceWaypoint | 
+// @parm name | name | the goal id
+bool ScriptNextRaceWaypoint(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    return pGoalManager->NextRaceWaypoint( goalId );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_NextTourSpot | 
+// @parm name | name | the goal id
+bool ScriptNextTourSpot(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    return pGoalManager->NextTourSpot( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_InitTrickObjects |
+// @parm name | name | the goal id
+bool ScriptInitTrickObjects( Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+    
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    pGoalManager->InitTrickObjects( goalId );
+
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CreateGoalName | 
+// @parm string | goal_type | the goal type
+bool ScriptCreateGoalName( Script::CStruct* pParams, Script::CScript* pScript )
+{
+    char goal_prefix[128];
+    const char* goal_type;
+
+    pParams->GetText( CRCD(0x6d11ed74,"goal_type"), &goal_type, Script::ASSERT );
+
+	// Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	// Obj::CSkaterCareer* pCareer = skate_mod->GetCareer();
+
+	// create the level prefix
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	Script::CStruct* p_level_structure = Script::GetStructure( pGoalManager->GetLevelStructureName(), Script::ASSERT );
+	const char* p_level_prefix;
+	p_level_structure->GetString( CRCD(0x651533ec,"level"), &p_level_prefix, Script::ASSERT );
+	strcpy( goal_prefix, p_level_prefix );
+	strcat( goal_prefix, "_goal_" );
+
+/*	// figure the level
+	switch ( level )
+	{
+	case 1:                                 // school
+		strcpy( goal_prefix, "Sch_goal_" );
+		break;
+	case 2:                                 // san francisco
+		strcpy( goal_prefix, "Sf2_goal_" );
+		break;
+	case 3:                                 // alcatraz
+		strcpy( goal_prefix, "Alc_goal_" );
+		break;
+	case 4:                                 // Kona park
+		strcpy( goal_prefix, "Kon_goal_" );
+		break;
+	case 5:                                 // junkyard
+		strcpy( goal_prefix, "Jnk_goal_" );
+		break;
+	case 6:                                 // london
+		strcpy( goal_prefix, "Lon_goal_" );
+		break;
+	case 7:                                 // zoo
+		strcpy( goal_prefix, "Zoo_goal_" );
+		break;
+	case 8:									// carnival
+		strcpy( goal_prefix, "Cnv_Goal_" );
+		break;
+	case 9:									// chicago
+		strcpy( goal_prefix, "Hof_goal_" );
+		break;
+	case 10:								// default test level
+		strcpy( goal_prefix, "Default_goal_" );
+		break;
+	default:
+		Dbg_MsgAssert( false, ( "GetGoalName couldn't figure level number" ) );
+	}
+*/
+    
+    // create the goal name
+    strcat( goal_prefix, (char*)goal_type );
+
+    // add to pParams
+    pScript->GetParams()->AddChecksum( CRCD(0x9982e501,"goal_id"), Script::GenerateCRC( goal_prefix ) );
+	pScript->GetParams()->AddString( CRCD(0xbfecc45b,"goal_name"), goal_prefix );
+
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetLevelPrefix | returns the current level's prefix in level_prefix
+bool ScriptGetLevelPrefix( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	Script::CStruct* p_level_structure = Script::GetStructure( pGoalManager->GetLevelStructureName(), Script::ASSERT );
+	const char* p_level_prefix;
+	p_level_structure->GetString( CRCD(0x651533ec,"level"), &p_level_prefix, Script::ASSERT );
+	
+	pScript->GetParams()->AddString( CRCD(0x15f818d0,"level_prefix"), p_level_prefix);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetGraffitiMode |
+// @uparmopt 1 | turn on or off with 1 or 0
+bool ScriptSetGraffitiMode( Script::CStruct* pParams, Script::CScript* pScript )
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+
+    int mode = 1;
+    pParams->GetInteger( NONAME, &mode, Script::NO_ASSERT ); 
+
+    return pGoalManager->SetGraffitiMode( mode );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_HasWonGoal | true if you've beat this goal
+// @parm name | name | the goal id
+bool ScriptHasWonGoal( Script::CStruct* pParams, Script::CScript* pScript )
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    return pGoalManager->HasWonGoal( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGoalIsSelected(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    return pGoalManager->GoalIsSelected( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptToggleGoalSelection(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    pGoalManager->ToggleGoalSelection( goalId );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGoalsAreSelected(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+	return ( pGoalManager->GetNumSelectedGoals() > 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GotCounterObject | 
+// @parm name | name | the goal id
+bool ScriptGotCounterObject( Script::CStruct* pParams, Script::CScript* pScript )
+{
+    uint32 goalId;
+    if ( pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::NO_ASSERT ) )
+    {
+        CGoalManager* pGoalManager = GetGoalManager();
+        pGoalManager->GotCounterObject( goalId );
+        return true;
+    }
+    else 
+    {
+        // printf("didn't get goalId\n");
+    }
+    return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CounterGoalDone | returns true if the counter goal
+// is complete
+// @parm name | name | the goal id
+bool ScriptCounterGoalDone(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    uint32 goalId;
+    pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+    CGoalManager* pGoalManager = GetGoalManager();
+
+    return pGoalManager->CounterGoalDone( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddGoalPoint | adds one goal point
+bool ScriptAddGoalPoint(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->AddGoalPoint();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SpendGoalPoints | 
+// @uparm 1 | number to spend
+bool ScriptSpendGoalPoints(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    int num;
+    pParams->GetInteger( NONAME, &num, Script::ASSERT );
+
+    pGoalManager->SpendGoalPoints( num );
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_HasGoalPoints | 
+// @uparm 1 | number to check for
+bool ScriptHasGoalPoints(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    int num;
+    pParams->GetInteger( NONAME, &num, Script::ASSERT );
+    
+    return pGoalManager->HasGoalPoints( num );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptClearGoalPoints(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    pGoalManager->ClearGoalPoints();
+    
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetNumberOfGoalPoints | places the number of
+// goal points in a variable named "goal_points" in the script params
+bool ScriptGetNumberOfGoalPoints(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    int goal_points = 0;
+	if ( pParams->ContainsFlag( CRCD(0x3dd01ea1,"total") ) )
+		goal_points = pGoalManager->GetTotalNumberOfGoalPointsEarned();
+	else
+		goal_points = pGoalManager->GetNumberOfGoalPoints();
+
+    pScript->GetParams()->AddInteger( CRCD(0x42c20492,"goal_points"), goal_points );
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddCash | gives the player more cash
+// @uparm 1 | amount of cash to give
+bool ScriptAddCash(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    int amount;
+    pParams->GetInteger( NONAME, &amount, Script::ASSERT );
+
+    pGoalManager->AddCash( amount );
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SpendCash | spends some of the player's cash
+// @uparm 1 | amount to spend
+bool ScriptSpendCash(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    int amount;
+    pParams->GetInteger( NONAME, &amount, Script::ASSERT );
+
+    return pGoalManager->SpendCash( amount );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetCash | places the current player's cash amount
+// in a parameter called "cash" in the script params
+bool ScriptGetCash(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+	bool get_total = pParams->ContainsFlag( CRCD(0x3dd01ea1,"total") );
+    
+    pScript->GetParams()->AddInteger( CRCD(0xf9461a46,"cash"), pGoalManager->GetCash( get_total ) );
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_HasWonGoalWithProset | 
+// @uparm "string" | the proset_prefix you wanna check
+bool ScriptHasBeatenGoalWithProset(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    const char* p_proset_prefix;
+    pParams->GetString( NONAME, &p_proset_prefix, Script::ASSERT );
+
+    CGoalManager* pGoalManager = GetGoalManager();
+    return pGoalManager->HasBeatenGoalWithProset( p_proset_prefix );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetProsetNotPrefix | 
+// @uparm "string" | the proset_prefix you wanna check
+bool ScriptGetProsetNotPrefix(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    const char* p_proset_prefix;
+    pParams->GetString( NONAME, &p_proset_prefix, Script::ASSERT );
+
+    char temp_string[128];
+    strcpy( temp_string, "");
+
+    int size = 0;    
+    while ( p_proset_prefix[size + 1] )
+    {
+        temp_string[size] = p_proset_prefix[size];
+        size++;
+    }
+	temp_string[size] = '\0';
+
+    // now we're at the last char
+    strcat( temp_string, "NOT_" );
+
+    pScript->GetParams()->AddString( CRCD(0xc60ce720,"proset_not_prefix"), temp_string );
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_LevelLoad | called when the player enters a level.
+// Loads data about goals seen, completed, etc. in a previous game
+bool ScriptLevelLoad(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    pGoalManager->LevelLoad();
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_LevelUnload | called when the player leaves a level.
+// Prompts the goal manager to save state data about all goals
+bool ScriptLevelUnload(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    pGoalManager->LevelUnload();
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_NumGoalsBeatenInLevel | This will return the number
+// of beaten goals in the scripts params as num_beaten
+// @uparm 1 | level number
+// @flag count_pro_specific_challenges | false by default
+bool ScriptNumGoalsBeatenInLevel(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    int levelNum;
+    pParams->GetInteger( NONAME, &levelNum, Script::ASSERT );
+
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    int numbeat = pGoalManager->NumGoalsBeatenInLevel( levelNum, pParams->ContainsFlag( CRCD(0x8690a9cf,"count_pro_specific_challenges") ) );
+    pScript->GetParams()->AddInteger( CRCD(0x8110dcdf,"num_beaten"), numbeat );
+    return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UnlockAllGoals | Unlocks all locked goals
+bool ScriptUnlockAllGoals(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    CGoalManager* pGoalManager = GetGoalManager();
+    Dbg_Assert( pGoalManager );
+
+    pGoalManager->UnlockAllGoals();
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CheckMinigameRecord | checks the current minigame
+// value against the one stored in minigame_record in the goal params
+// @parm name | name | the goal id
+// @uparm 100 | the value you want to check against the current record
+bool ScriptCheckMinigameRecord(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	int value;
+	pParams->GetInteger( NONAME, &value, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->CheckMinigameRecord( goalId, value );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_DeactivateCurrentGoal | Deactivates the currently
+// active goal (this function cannot always be trusted).
+bool ScriptDeactivateCurrentGoal(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->DeactivateCurrentGoal();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetStartTime | Records the start time of a combo
+// (only used for the timedcombo miingame right now).
+// @parm name | name | the goal id
+bool ScriptSetStartTime(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->SetStartTime( goalId );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UpdateComboTimer | Used for the timedcombo minigame
+// @parm name | name | the goal id
+bool ScriptUpdateComboTimer(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->UpdateComboTimer( goalId );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetStartHeight | used for the height minigame
+// @parm name | name | the goal id
+bool ScriptSetStartHeight(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->SetStartHeight( goalId );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CheckHeightRecord | used for height minigame
+// @parm name | name | the goal id
+bool ScriptCheckHeightRecord(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->CheckHeightRecord( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CheckDistanceRecord | used for height minigame
+// @parm name | name | the goal id
+bool ScriptCheckDistanceRecord(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->CheckDistanceRecord( goalId );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_PlayGoalStartStream | plays the intro vo for the goal
+// @parm name | name | the goal id
+bool ScriptPlayGoalStartStream(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->PlayGoalStartStream( pParams );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_PlayGoalWaitStream | plays the wait vo for the goal
+// @parm name | name | the goal id
+bool ScriptPlayGoalWaitStream(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->PlayGoalWaitStream( goalId );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_PlayGoalWinStream | plays the success vo for the goal
+// @parm name | name | the goal ID
+bool ScriptPlayGoalWinStream(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->PlayGoalWinStream( pParams );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_StopCurrentStream | stops any vo being played by
+// the goal trigger
+// @parm name | name | the goal ID
+bool ScriptStopCurrentStream(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->StopCurrentStream( goalId );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_TurnPro | instantly turns you pro, unlocks all 
+// the pro goals, and runs the "you just turned pro" script
+bool ScriptTurnPro(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->TurnPro();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_DeactivateAllMinigames | 
+bool ScriptDeactivateAllMinigames(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->DeactivateAllMinigames();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_InitializeAllMinigames | 
+bool ScriptInitializeAllMinigames(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->InitializeAllMinigames();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_PauseGoal | pauses an individual goal
+// @parm name | name | the goal id
+bool ScriptPauseGoal( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->PauseGoal( goalId );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UnPauseGoal | unpauses an individual goal
+// @parm name | name | the goal id
+bool ScriptUnPauseGoal( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->UnPauseGoal( goalId );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_PauseCompetition | Pauses a competition
+// @parm name | name | the goal id
+bool ScriptPauseCompetition( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->PauseCompetition( goalId );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UnPauseCompetition | The competition uses a secondary
+// pause mechanism...this is for unpausing the comp between rounds
+// @parm name | name | the goal id
+bool ScriptUnPauseCompetition( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->UnPauseCompetition( goalId );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UnBeatAllGoals | marks all goals as unbeaten and 
+// takes away any goal points you earned for beating them.  You have to 
+// reload the level to re-initialize the goals, however
+bool ScriptUnBeatAllGoals( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_MsgAssert( pGoalManager, ("couldn't get goal manager") );
+
+	pGoalManager->UnBeatAllGoals();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddViewGoalsList | adds the list of goals to the 
+// view goals menu.  This is called from one place within the create_view_Goals_menu
+// script.
+bool ScriptAddViewGoalsList( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_MsgAssert( pGoalManager, ("couldn't get goal manager") );
+
+	pGoalManager->AddViewGoalsList();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddGoalChoices | Adds the list of available goals to the "Choose Goals" menu
+bool ScriptAddGoalChoices( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	bool selected_only;
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_MsgAssert( pGoalManager, ("couldn't get goal manager") );
+
+	selected_only = pParams->ContainsFlag( CRCD(0x5685c1b7,"selected_only") );
+
+	pGoalManager->AddGoalChoices( selected_only );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GoalIsLocked | true if the goal is locked
+// @parm name | name | the goal id
+bool ScriptGoalIsLocked( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->GoalIsLocked( goalId );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_IsInCompetition | returns true if the player is
+// in a competition
+bool ScriptIsInCompetition( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->IsInCompetition();
+	if ( pGoal )
+	{
+		pScript->GetParams()->AddChecksum( CRCD(0x9982e501,"goal_id"), pGoal->GetGoalId() );
+		return true;
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetGoalAnimations | Gets the appropriate animations
+// array from script.  It wil first search for an array specific to this goal and 
+// type, then for an array specific to this pro, then for the generic array
+// @parm name | name | the goal id
+// @parmopt string | type | | the goal state - wait, start, midgoal, or success
+bool ScriptGetGoalAnimations( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	const char* type = NULL;
+	pParams->GetString( CRCD(0x7321a8d6,"type"), &type, Script::NO_ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->GetGoalAnimations( goalId, type, pScript );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_PlayGoalStream | plays the appropriate stream for this
+// goal/pro.  The appropriate anim arrays must be setup in script for this to work
+// @parm name | name | the goal id
+bool ScriptPlayGoalStream( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->PlayGoalStream( pParams );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_EndRunCalled | returns true if the goal manager
+// called endrun for this goal
+// @parm name | name | the goal id
+bool ScriptEndRunCalled(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->EndRunCalled( goalId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ClearEndRun | This will clear the skater's endrun exception
+// as well as any others.  It's used by the combo goal, which needs to clear
+// the exception if you bail.  In general, this function should not be used -
+// clearing the endrun exception is not usually a good idea.
+// @parm name | name | the goal id
+bool ScriptClearEndRun(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	pGoalManager->GetGoal( goalId )->ClearEndRun( pScript );
+	
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_FinishedEndOfRun | returns true if EndOfRun has
+// finished
+// @parm name | name | the goal id
+bool ScriptFinishedEndOfRun(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->GetGoal( goalId )->FinishedEndOfRun();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_StartedEndOfRun | this returns true if EndOfRun 
+// has started
+// @parm name | name | the goal id
+bool ScriptStartedEndOfRun(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->GetGoal( goalId )->StartedEndOfRun();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_EndBetAttempt | call this to end a bet attempt. 
+// If the goal id does not match the current bet, the call will be ignored.
+// @parm name | name | the goal id
+bool ScriptEndBetAttempt(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 id = pGoalManager->GetBettingGuyId();
+	if ( id )
+	{
+		CBettingGuy* pGoal = (CBettingGuy*)pGoalManager->GetGoal( id );
+		Dbg_Assert( pGoal );
+		return pGoal->EndBetAttempt( goalId );
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_StartBetAttempt | called by any minigame that is
+// also used as a betting game
+// @parm name | name | the goal id
+bool ScriptStartBetAttempt(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 id = pGoalManager->GetBettingGuyId();
+	if ( id )
+	{
+		CBettingGuy* pGoal = (CBettingGuy*)pGoalManager->GetGoal( id );
+		Dbg_Assert( pGoal );
+		return pGoal->StartBetAttempt( goalId );
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_WinBet | calling this will win the bet.
+// The goal id is used to verify that the specified bet is in fact
+// the active bet.  If it's not, the goal manager will ignore the call
+// @parm name | name | the goal id
+bool ScriptWinBet(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 id = pGoalManager->GetBettingGuyId();
+	if ( id )
+	{
+		CBettingGuy* pGoal = (CBettingGuy*)pGoalManager->GetGoal( id );
+		Dbg_Assert( pGoal );
+		pGoal->Win( goalId );
+	}
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_BetOffered | called once the betting guy makes 
+// an offer
+bool ScriptBetOffered( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 id = pGoalManager->GetBettingGuyId();
+	if ( id )
+	{	
+		CBettingGuy* pGoal = (CBettingGuy*)pGoalManager->GetGoal( id );
+		Dbg_Assert( pGoal );
+		pGoal->OfferMade();
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_BetRefused | called when the player declines a betting
+// guy bet
+bool ScriptBetRefused( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 id = pGoalManager->GetBettingGuyId();
+	if ( id )
+	{	
+		CBettingGuy* pGoal = (CBettingGuy*)pGoalManager->GetGoal( id );
+		Dbg_Assert( pGoal );
+		pGoal->OfferRefused();
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_MoveBettingGuyNow | this will force the betting guy to
+// immediately move to a new, random betting game.  This is useful when the betting guy
+// is created and you don't care where the skater is (otherwise the betting guy will avoid
+// moving where the skater could see him).
+bool ScriptMoveBettingGuyNow( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 id = pGoalManager->GetBettingGuyId();
+	if ( id )
+	{
+		CBettingGuy* pGoal = (CBettingGuy*)pGoalManager->GetGoal( id );
+		Dbg_Assert( pGoal );
+		pGoal->MoveToNewNode( true );
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_BetAccepted | called when the player accepts a bet
+bool ScriptBetAccepted(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 id = pGoalManager->GetBettingGuyId();
+	if ( id )
+	{
+		CBettingGuy* pGoal = (CBettingGuy*)pGoalManager->GetGoal( id );
+		Dbg_Assert( pGoal );
+		pGoal->OfferAccepted();
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_BetIsActive | returns true if the bet is currently
+// active - ie, the bet guy has offered the bet and the player accepted
+// @parm name | name | the goal id
+bool ScriptBetIsActive(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	uint32 betting_guy_id = pGoalManager->GetBettingGuyId();
+	if ( betting_guy_id )
+	{
+		CBettingGuy* pBetGuy = (CBettingGuy*)pGoalManager->GetGoal( betting_guy_id );
+		Dbg_Assert( pBetGuy );
+		return pBetGuy->BetIsActive( goalId );
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddMinigameTime | adds the specified amount of time
+// to the minigame.  
+// @parm name | name | the minigame id (usually goal_id)
+// @uparm 1 | the amount of time to add in seconds
+bool ScriptAddMinigameTime(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );	
+	
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	int amount;
+	pParams->GetInteger( NONAME, &amount, Script::ASSERT );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_MsgAssert( pGoal->IsMinigame(), ("This is not a minigame") );
+	CMinigame* pMinigame = (CMinigame*)pGoal;
+	return pMinigame->AddTime( amount );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddTempSpecialTrick | This is used only by the 
+// special goal.
+// @parm name | name | the goal id
+bool ScriptAddTempSpecialTrick(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	return pGoal->AddTempSpecialTrick();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_RemoveTempSpecialTrick | this is used only by the 
+// special goal.
+// @parm name | name | the goal id
+bool ScriptRemoveTempSpecialTrick(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->RemoveTempSpecialTrick();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetTrickFromKeyCombo | this will find the trick
+// currently bound to the given key combo.  the trick name (checksum value) will
+// be placed in the script params as trick_checksum.  The trick text
+// will be placed in the params as trick_string.  If no trick is bound to the 
+// key combo, the function will return false
+// @parm name | key_combo | the key combo to search for
+bool ScriptGetTrickFromKeyCombo(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 key_combo;
+	pParams->GetChecksum( CRCD(0xacfdb27a,"key_combo"), &key_combo, Script::ASSERT );
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+	Script::CStruct* pTricks = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+	
+	uint32 trick_checksum;
+    int cat_num;
+	if ( !pParams->ContainsFlag( CRCD(0xb394c01c,"special") ) )
+    {
+        if( pTricks->GetInteger( key_combo, &cat_num, Script::NO_ASSERT ) )
+        {
+            // printf("it's a created trick! %i\n", cat_num );
+    		pScript->GetParams()->AddInteger( CRCD(0xa75b8581,"cat_num"), cat_num );
+            return true;
+        }
+        
+        if( pTricks->GetChecksum( key_combo, &trick_checksum, Script::NO_ASSERT ) )
+    	{
+    		if ( trick_checksum == ( CRCD(0xf60c9090,"Unassigned") ) )
+               	return false;
+            
+    		// return the name and checksum
+    		const char* p_trick_string;
+    		Script::CStruct* p_trick = Script::GetStructure( trick_checksum, Script::ASSERT );
+    		Script::CStruct* p_trick_params;
+    		p_trick->GetStructure( CRCD(0x7031f10c,"Params"), &p_trick_params, Script::ASSERT );
+    		p_trick_params->GetLocalString( CRCD(0xa1dc81f9,"Name"), &p_trick_string, Script::ASSERT );
+    
+    		pScript->GetParams()->AddString( CRCD(0x3a54cc4b,"trick_string"), p_trick_string );
+    		pScript->GetParams()->AddChecksum( CRCD(0x4443a4da,"trick_checksum"), trick_checksum );
+    
+    		// look for any double tap tricks
+    		uint32 extra_trick = 0;
+    		if ( !p_trick_params->GetChecksum( CRCD(0x6e855102,"ExtraTricks"), &extra_trick, Script::NO_ASSERT ) )
+    		{
+    			// look for an array of extra tricks and grab the first one
+    			Script::CArray* p_extra_tricks;
+    			if ( p_trick_params->GetArray( CRCD(0x6e855102,"ExtraTricks"), &p_extra_tricks, Script::NO_ASSERT ) )
+    				extra_trick = p_extra_tricks->GetChecksum( 0 );
+    		}
+    
+    		// add any extra trick info
+    		if ( extra_trick )
+    		{
+    			// grab this extra trick
+    			Script::CArray* p_temp = Script::GetArray( extra_trick, Script::ASSERT );
+    			Script::CStruct* p_extra_trick = p_temp->GetStructure( 0 );
+    			Dbg_Assert( p_extra_trick );
+    			Script::CStruct* p_extra_trick_params;
+    			p_extra_trick->GetStructure( CRCD(0x7031f10c,"Params"), &p_extra_trick_params, Script::ASSERT );
+    			const char* p_extra_trick_string;
+    			p_extra_trick_params->GetString( CRCD(0xa1dc81f9,"Name"), &p_extra_trick_string, Script::ASSERT );
+    			pScript->GetParams()->AddString( CRCD(0x3daaf58b,"extra_trick_string"), p_extra_trick_string );
+    			pScript->GetParams()->AddChecksum( CRCD(0xd0bcfc62,"extra_trick_checksum"), extra_trick );
+    		}
+    		return true;
+    	}
+    }
+	else if ( pParams->ContainsFlag( CRCD(0xb394c01c,"special") ) )
+	{		
+		// check the special tricks
+		int numTricks = pSkaterProfile->GetNumSpecialTrickSlots();
+
+		// search through current special tricks
+		for ( int i = 0; i < numTricks; i++ )
+		{
+            Obj::SSpecialTrickInfo trick_info = pSkaterProfile->GetSpecialTrickInfo( i );
+		
+			if ( trick_info.m_TrickSlot == key_combo )
+			{
+				if ( trick_info.m_TrickName == ( CRCD(0xf60c9090,"Unassigned") ) )
+					return false;
+				
+				// printf("the special trick is already bound!\n");
+				if ( Script::CStruct* p_trick = Script::GetStructure( trick_info.m_TrickName ) )
+                {
+                    const char* p_trick_string;
+                    Script::CStruct* p_trick_params;
+    				p_trick->GetStructure( CRCD(0x7031f10c,"Params"), &p_trick_params, Script::ASSERT );
+    				p_trick_params->GetLocalString( CRCD(0xa1dc81f9,"Name"), &p_trick_string, Script::ASSERT );
+                    pScript->GetParams()->AddString( CRCD(0x3a54cc4b,"trick_string"), p_trick_string );
+                }
+                
+                if ( trick_info.m_isCat )
+                {
+                    pScript->GetParams()->AddInteger( CRCD(0x4443a4da,"trick_checksum"), trick_info.m_TrickName );
+                }
+                else
+                {
+                    pScript->GetParams()->AddChecksum( CRCD(0x4443a4da,"trick_checksum"), trick_info.m_TrickName );
+                }
+                pScript->GetParams()->AddInteger( CRCD(0xdb92effa,"current_index"), i );
+				return true;
+			}
+		}
+	}
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UnlockGoal | unlocks the specified goal
+// @parm name | name | the goal id
+bool ScriptUnlockGoal(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+
+	pGoal->UnlockGoal();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_QuickStartGoal | starts a goal without going
+// through the intro movie, etc.
+// @parm name | name | the goal id
+// @flag dontAssert | used by autotester
+bool ScriptQuickStartGoal(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	bool dontAssert = false;
+	if ( pParams->ContainsFlag( CRCD(0x9ac8d54f,"dontAssert") ) )
+		dontAssert = true;
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->QuickStartGoal( goalId, dontAssert );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_InitializeGoal | initialize the specified goal
+// @parm name | name | the goal id
+bool ScriptInitializeGoal( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->Init();
+	return true;
+}
+// TODO:  it would be nice to have the cfuncs call the goalmanager's
+// member functions directly...
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AddTime | adds the specified amount of time to the goal
+// @parm name | name | the goal id
+// @uparm 10 | time to add, in seconds
+bool ScriptAddTime( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	int time;
+	pParams->GetInteger( NONAME, &time, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->AddTime( time );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ReplaceTrickText | replaces the trick name and 
+// key combo with the appropriate text.  The key combo should be represented 
+// in the string by \k, and the text by \t
+// @parmopt name | name | | the goal id
+// @flag all | in place of a goal id - update all goals
+bool ScriptReplaceTrickText( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	if ( pParams->ContainsFlag( CRCD(0xc4e78e22,"all") ) )
+	{
+		pGoalManager->ReplaceAllTrickText();
+		return true;
+	}
+	else if ( pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::NO_ASSERT ) )
+	{
+		const char* p_old_string;
+		if ( pParams->GetString( CRCD(0xc4745838,"text"), &p_old_string, Script::NO_ASSERT ) )
+		{
+			char new_string[NEW_STRING_LENGTH];
+			if ( pGoalManager->ReplaceTrickText( goalId, p_old_string, new_string, NEW_STRING_LENGTH ) )
+			{
+                pScript->GetParams()->RemoveComponent( CRCD(0x3eafa520,"TrickText") );
+				pScript->GetParams()->AddString( CRCD(0x3eafa520,"TrickText"), new_string );
+			}
+		}
+		else 
+			return pGoalManager->ReplaceTrickText( goalId );
+	}
+	else
+		Dbg_MsgAssert( 0, ( "GoalManager_ReplaceTrickText requires a goal id or the 'all' flag." ) );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+// @script | GoalManager_UnlockProSpecificChallenges | called once, when you 
+// first get enough goals to unlock the pro challenge
+bool ScriptUnlockProSpecificChallenges( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->UnlockProSpecificChallenges();
+	return true;
+}
+*/
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+// @script | GoalManager_ProSpecificChallengesUnlocked | true if the pro challenges 
+// have been unlocked
+bool ScriptProSpecificChallengesUnlocked( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->ProSpecificChallengesUnlocked();
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetNumberCollected | returns the number of objects
+// collected so far in this goal.  If the goal is not a counter goal, it just returns
+// the number of flags set.  The value will be stored in the number_collected var.
+// @parm name | name | the goal id
+bool ScriptGetNumberCollected( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	int number_collected = pGoalManager->GetNumberCollected( goalId );
+	pScript->GetParams()->AddInteger( CRCD(0x129daa10,"number_collected"), number_collected );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetNumberOfFlags | returns the number of flags in this
+// goal.  If there are no flags, it returns false
+// @parm name | name | the goal id
+bool ScriptGetNumberOfFlags( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	int number_of_flags = pGoalManager->GetNumberOfFlags( goalId );
+	if ( number_of_flags != -1 )
+	{
+		pScript->GetParams()->AddInteger( CRCD(0xcb9d1224,"number_of_flags"), number_of_flags );
+		return true;
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_IsPro | returns true if you're pro
+bool ScriptIsPro( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->IsPro();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ResetGoalFlags | resets all of the goals flags
+bool ScriptResetGoalFlags( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->ResetGoalFlags( goalId );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ColorTrickObjects | colors all trick objects 
+// associated with this goal
+// @parm name | name | the goal id
+// @flag clear | pass this in if you want to clear the color
+// @parmopt int | skater | 0 | the skater num
+bool ScriptColorTrickObjects( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	bool clear = pParams->ContainsFlag( CRCD(0x1a4e0ef9,"clear") );
+
+	int skaterId = 0;
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterId, Script::NO_ASSERT );
+
+	int seqIndex = skaterId + 1;
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->ColorTrickObjects( goalId, seqIndex, clear );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetNumberOfTimesGoalStarted | gets the total number
+// of times the player has lost this goal.  The number will be stored in
+// the calling scripts params as times_lost
+// @parm name | name | the goal id
+bool ScriptGetNumberOfTimesGoalStarted( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	int numTimesLost = pGoalManager->GetNumberOfTimesGoalStarted( goalId );
+	pScript->GetParams()->AddInteger( CRCD(0xff61ba23,"times_started"), numTimesLost );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GoalExists | returns true if the goal exists
+// @parm name | name | the goal id
+bool ScriptGoalExists( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->GoalExists( goalId );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetGoalAttemptInfo | grabs the number of times each
+// goal has been lost, and stores all the data in your times_lost_info
+bool ScriptGetGoalAttemptInfo( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	pScript->GetParams()->AddStructurePointer( CRCD(0xd23ad129,"goal_attempt_info"), pGoalManager->GetGoalAttemptInfo() );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetCanStartGoal | tell the goal manager that it can
+// or cannot start any new goals
+// @parmopt int | | 1 | 0 to block new goals
+bool ScriptSetCanStartGoal( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	int value = 1;
+	pParams->GetInteger( NONAME, &value, Script::NO_ASSERT );
+	pGoalManager->SetCanStartGoal( value );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetLastGoalId | returns the last unbeaten goal
+// as a checksum param goal_id.  If the player hasn't played a goal yet
+// or beat the last goal, the function will return false.
+bool ScriptGetLastGoalId( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId = pGoalManager->GetLastGoalId();
+	if ( goalId )
+	{
+		pScript->GetParams()->AddChecksum( CRCD(0x9982e501,"goal_id"), goalId );
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ClearTetrisTricks | clears the entire stack of
+// tetris tricks.
+bool ScriptClearTetrisTricks( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_MsgAssert( pGoal->IsTetrisGoal(), ( "ClearTetrisTricks called on a non-tetris goal" ) );
+
+	CSkatetrisGoal* pSkatetrisGoal = (CSkatetrisGoal*)pGoal;
+	pSkatetrisGoal->ClearAllTricks();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool ScriptMarkProSpecificChallengeBeaten( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 skater_name;
+	pParams->GetChecksum( CRCD(0x5b8ab877,"skater"), &skater_name, Script::ASSERT );
+
+	int beaten = 1;
+	pParams->GetInteger( NONAME, &beaten, Script::NO_ASSERT );
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->MarkProSpecificChallengeBeaten( skater_name, beaten );
+	return true;
+}
+*/
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool ScriptSkaterHasBeatenProSpecificChallenge( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 skater_name;
+	pParams->GetChecksum( CRCD(0x5b8ab877,"skater"), &skater_name, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->SkaterHasBeatenProSpecificChallenge( skater_name );
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAnnounceGoalStarted( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client;
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal != NULL );
+
+	if( pGoal->IsNetGoal())
+	{
+		return true;
+	}
+	if( pGoal->GetParents()->m_relative != 0 )
+	{
+		return true;
+	}
+	
+	client = gamenet_man->GetClient( 0 );
+	Dbg_Assert( client );
+	GameNet::MsgStartedGoal msg;
+	GameNet::PlayerInfo* local_player;
+	Net::MsgDesc msg_desc;
+
+	local_player = gamenet_man->GetLocalPlayer();
+	Dbg_Assert( local_player );
+	
+	msg.m_GameId = gamenet_man->GetNetworkGameId();
+	msg.m_GoalId = pGoal->GetGoalId();
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( GameNet::MsgStartedGoal );
+	msg_desc.m_Id = GameNet::MSG_ID_STARTED_GOAL;
+	client->EnqueueMessageToServer( &msg_desc );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetEndRunType | one of the flags is required
+// @parm name | name | the goal id
+// @flag Goal_EndOfRun | use GoalEndOfRun
+// @flag EndOfRun | use EndOfRun
+// @flag none | don't use end of run
+bool ScriptSetEndRunType( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	Game::EndRunType newEndRunType = Game::vENDOFRUN;
+	if ( pParams->ContainsFlag( CRCD(0x54585419,"Goal_EndOfRun") ) )
+		newEndRunType = Game::vGOALENDOFRUN;
+	else if ( pParams->ContainsFlag( CRCD(0xe022c12c,"EndOfRun") ) )
+		newEndRunType = Game::vENDOFRUN;
+	else if ( pParams->ContainsFlag( CRCD(0x806fff30,"none") ) )
+		newEndRunType = Game::vNONE;
+	else
+		Dbg_MsgAssert( 0, ( "GoalManager_SetEndRunType called without type" ) );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->SetEndRunType( goalId, newEndRunType );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetShouldDeactivateOnExpire | 
+// @uparm 1 | 0 for no, anything else for yes
+bool ScriptSetShouldDeactivateOnExpire( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	int should_deactivate;
+	pParams->GetInteger( NONAME, &should_deactivate, Script::ASSERT );
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->SetShouldDeactivateOnExpire( goalId, should_deactivate != 0 );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetActiveGoalId | returns the active goal's goal_id.
+// Returns false if there are no active goals
+bool ScriptGetActiveGoalId( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId = 0;
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	if ( pGoalManager )
+	{
+		int activeGoal = pGoalManager->GetActiveGoal( true );
+		if ( activeGoal == -1 )
+			return false;
+
+		CGoal* pGoal = pGoalManager->GetGoalByIndex( activeGoal );
+		Dbg_Assert( pGoal );
+		if ( pGoal )
+		{
+			goalId = pGoal->GetGoalId();
+			pScript->GetParams()->AddChecksum( "goal_id", goalId );
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AwardMinigameCash | 
+// @parm name | name | the goal id
+// @uparm 25 | amount of cash to award
+bool ScriptAwardMinigameCash( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	int amount;
+	pParams->GetInteger( NONAME, &amount, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	if ( pGoalManager )
+	{
+		return pGoalManager->AwardMinigameCash( goalId, amount );
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_ResetCareer | 
+bool ScriptResetCareer( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->ResetCareer();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAwardAllGoalCash( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->AwardAllGoalCash();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UpdateFamilyTrees |
+bool ScriptUpdateFamilyTrees( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pGoalManager->UpdateFamilyTrees();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_IsLeafNode | returns true if the passed goal
+// is a leaf node in the goal tree
+// @parm name | name | the goal id
+bool ScriptIsLeafNode( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->CallMemberFunction( ( CRCD(0xd782f8de,"IsLeafNode") ), pParams, pScript );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_IsRootNode | returns true if the passed
+// goal is the root node of a family tree.  This is true if the goal
+// is the only goal in a tree.
+// @parm name | name | the goal id
+bool ScriptIsRootNode( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->CallMemberFunction( ( CRCD(0x3ae9211b,"IsRootNode") ), pParams, pScript );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSuspendGoalPedLogic( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	int suspend = 1;
+	pParams->GetInteger( CRCD(0xce0ca665,"suspend"), &suspend, Script::NO_ASSERT );
+
+	pGoalManager->GetGoal( pParams )->mp_goalPed->SuspendGoalLogic( suspend != 0 );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptRememberLevelStructureName( Script::CStruct *pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 level;
+	pParams->GetChecksum( CRCD(0x63b1214e,"level_structure_name"), &level, Script::ASSERT );
+	pGoalManager->RememberLevelStructureName( level );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetDifficultyLevel | sets the current level
+// @uparm 1 | difficulty level (starts at 0)
+bool ScriptSetDifficultyLevel( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int level;
+	pParams->GetInteger( NONAME, &level, Script::ASSERT );
+	Dbg_MsgAssert( level >= 0 && level <= vMAXDIFFICULTYLEVEL, ( "GoalManager_SetDifficultyLevel called with level of %i", level ) );
+	
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->SetDifficultyLevel( level );
+	return ( level != 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetDifficultyLevel | returns the current
+// difficulty level as an integer param called difficulty_level
+bool ScriptGetDifficultyLevel( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	int level = (int)pGoalManager->GetDifficultyLevel();
+	pScript->GetParams()->AddInteger( CRCD(0x3671f748,"difficulty_level"), level );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+/*
+bool ScriptGetGoalParam( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+
+	uint32 type_checksum;
+	Script::ESymbolType type = ESYMBOLTYPE_NONE;
+	pParams->GetChecksum( CRCD(0x7321a8d6,"type"), &type_checksum, Script::ASSERT );
+
+	// convert type
+	switch ( type_checksum )
+	{
+		case CRCC(0x21902065,"checksum"):
+			type=ESYMBOLTYPE_NAME;
+			break;
+		case CRCC(0x365aa16a,"float"):
+			type=ESYMBOLTYPE_FLOAT;
+			break;
+		case CRCC(0xa5f054f4,"integer"):
+			type=ESYMBOLTYPE_INTEGER;
+			break;
+		case CRCC(0x90fec815,"structure"):
+			type=ESYMBOLTYPE_STRUCTURE;
+			break;
+		case CRCC(0x5ef31148,"array"):
+			type=ESYMBOLTYPE_ARRAY;
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Uknown type %x", type ) );
+			break;
+	}
+
+	uint32 name;
+	pParams->GetChecksum( NONAME, &name, Script::ASSERT );
+
+	// printf("looking for %s with type %s\n", Script::FindChecksumName( name ), Script::FindChecksumName( type ) );
+
+	return pGoal->ExtractDifficultyLevelParam( type, name, pScript->GetParams() );
+}
+*/
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_RestartStage | restarts the current/last stage
+bool ScriptRestartStage( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->RestartStage();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_CanRestartStage | returns true if the player
+// can retry a goal stage.
+bool ScriptCanRestartStage( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	return pGoalManager->CanRestartStage();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSetGoalChaptersAndStages( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->SetGoalChaptersAndStages();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_AdvanceStage | returns true if the stage/chapter
+// advances, false if you haven't reached an advancement point yet.
+// @flag force | force the stage to advance
+// @flag just_won_goal | this flag needs to be passed in if the function is called
+// from a success script, as the goal is not marked beaten until after the success
+// script finishes.  Therefore, the total number of goals beaten will be one more
+// than the number returned by searching through the goals.
+bool ScriptAdvanceStage( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	uint32 just_won_goal_id = 0;
+	pParams->GetChecksum( CRCD(0x5d82c486,"just_won_goal_id"), &just_won_goal_id, Script::NO_ASSERT );
+	return pGoalManager->AdvanceStage( pParams->ContainsFlag( CRCD( 0x68977cd7, "force" ) ), just_won_goal_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetCurrentChapterAndStage | returns the 
+// current chapter and stage as currentChapter and currentStage
+bool ScriptGetCurrentChapterAndStage( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	int chapter = pGoalManager->GetCurrentChapter();
+	int stage = pGoalManager->GetCurrentStage();
+	
+	// return the last chapter if they beat the game
+	Script::CArray* pChapterGoalsArray = Script::GetArray( CRCD( 0xb892776f, "chapter_goals" ), Script::ASSERT );
+	Dbg_Assert( pChapterGoalsArray->GetType() == ESYMBOLTYPE_ARRAY );
+	if ( chapter >= (int)pChapterGoalsArray->GetSize() )
+	{
+		chapter = pChapterGoalsArray->GetSize() - 1;
+	}
+	pScript->GetParams()->AddInteger( CRCD( 0xf884773c, "CurrentChapter" ), chapter );
+	pScript->GetParams()->AddInteger( CRCD( 0xaf1575eb, "CurrentStage" ), stage );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetCurrentChapterAndStage | 
+// @parmopt int | chapter | current | 
+// @parmopt int | stage | current |
+bool ScriptSetCurrentChapterAndStage( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	int chapter = pGoalManager->GetCurrentChapter();
+	int stage = pGoalManager->GetCurrentStage();
+	pParams->GetInteger( CRCD(0x67e4ad1,"chapter"), &chapter, Script::NO_ASSERT );
+	pParams->GetInteger( CRCD(0x3d836c96,"stage"), &stage, Script::NO_ASSERT );
+	pGoalManager->SetCurrentChapter( chapter );
+	pGoalManager->SetCurrentStage( stage );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// @script | GoalManager_FilmGoalCheckpoint | Call this when the filming
+// target reaches a checkpoint.  This is only used for filming goals that
+// require the skater to catch X of Y checkpoints, not goals which requrie
+// the skater to film the target for X of Y seconds.
+// @parm name | name | the goal_id
+bool ScriptFilmGoalCheckpoint( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	CFilmGoal* pFilmGoal = (CFilmGoal*)pGoalManager->GetGoal( pParams, true );
+
+	pFilmGoal->CheckpointHit();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// @script | GoalManager_StartFilming | Called when the start cam anims finish.
+// @parm name | name | the goal_id
+bool ScriptStartFilming( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	CFilmGoal* pFilmGoal = (CFilmGoal*)pGoalManager->GetGoal( pParams, true );
+
+	pFilmGoal->StartFilming();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// @script | GoalManager_GoalShouldExpire | setup for endrun exceptions
+bool ScriptGoalShouldExpire( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	if ( pGoalManager )
+	{
+		int activeGoal = pGoalManager->GetActiveGoal();
+		if ( activeGoal == -1 )
+			return false;
+
+		CGoal* pGoal = pGoalManager->GetGoalByIndex( activeGoal );
+		Dbg_Assert( pGoal );
+		if ( pGoal )
+		{
+			return pGoal->ShouldExpire();
+		}
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetSponsor | 
+// @uparm sponsor_name | the checksum of the sponsor name
+bool ScriptSetSponsor( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	uint32 sponsor;
+	pParams->GetChecksum( NONAME, &sponsor, Script::ASSERT );
+	pGoalManager->SetSponsor( sponsor );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetSponsor | returns sponsor as param "sponsor"
+// the sponsor will be 0 if not set
+bool ScriptGetSponsor( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	pScript->GetParams()->AddChecksum( CRCD(0x7e73362b,"sponsor"), pGoalManager->GetSponsor() );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GetTeam | returns team structure as param named
+// team.  It also returns the current number of team members, in a param 
+// named num_team_members
+bool ScriptGetTeam( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	Script::CStruct* pTeam = pGoalManager->GetTeam();
+	pScript->GetParams()->AddStructure( CRCD(0x3b1f59e0,"team"), pTeam );
+	
+	// throw in the number of team members
+    Script::CComponent* p_comp = NULL;
+    p_comp = pTeam->GetNextComponent( p_comp );
+
+    int total = 0;
+	while ( p_comp )
+    {
+        Script::CComponent* p_next = pTeam->GetNextComponent( p_comp );
+		total++;
+		p_comp = p_next;
+	}
+	pScript->GetParams()->AddInteger( CRCD(0xeb8dd330,"num_team_members"), total );
+	
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetTeamMember | add or remove a team member
+// @parm name | pro | name of the pro to add or remove
+// @flag remove | use this flag to remove the pro - will add by default
+bool ScriptSetTeamMember( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	uint32 pro;
+	pParams->GetChecksum( CRCD(0x944b2900,"pro"), &pro, Script::ASSERT );
+	bool remove = pParams->ContainsFlag( CRCD(0x977fe2cf,"remove") );
+	bool mark_used = pParams->ContainsFlag( CRCD(0x531ff702,"mark_used") );
+	Dbg_MsgAssert( !( remove && mark_used ), ( "GoalManager_SetTeamMember called with remove and mark_used flags." ) );
+	pGoalManager->SetTeamMember( pro, remove, mark_used );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_KillTeamMembers | removes all team members
+bool ScriptKillTeamMembers( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->KillTeamMembers( );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_SetTeamName | set the team name
+// @uparm "team name" | team name
+bool ScriptSetTeamName( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	const char* p_string;
+	pParams->GetString( NONAME, &p_string, Script::ASSERT );
+	pGoalManager->SetTeamName( p_string );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptRunLastStageScript( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	pGoalManager->RunLastStageScript();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_GoalInitialized | returns true if the goal
+// is initialized
+// @parm name | name | goal id
+bool ScriptGoalInitialized( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	return pGoal->IsInitialized();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_UnloadLastFam | unloads the last fam file 
+// loaded for the goal ped.  If the fam has already been unloaded, it
+// will return
+bool ScriptUnloadLastFam( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->mp_goalPed->UnloadLastFam();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_StopLastStream | stops the last stream played 
+// for a cam anim
+bool ScriptStopLastSream( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 goalId;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &goalId, Script::ASSERT );
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	CGoal* pGoal = pGoalManager->GetGoal( goalId );
+	Dbg_Assert( pGoal );
+
+	pGoal->mp_goalPed->StopLastStream();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalManager_HideAllGoalPeds | hide/unhide all goal peds
+// @uparm 0 | 0 to unhide, anything else to hide
+bool ScriptHideAllGoalPeds( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	int hide;
+	pParams->GetInteger( NONAME, &hide, Script::ASSERT );
+	pGoalManager->HideAllGoalPeds( ( hide != 0 ) );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+} // namespace Game
+
diff --git a/Code/Sk/Modules/Skate/GoalManager.h b/Code/Sk/Modules/Skate/GoalManager.h
new file mode 100644
index 0000000..3c1fbd3
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GoalManager.h
@@ -0,0 +1,521 @@
+//****************************************************************************
+//* MODULE:         Sk/Modules/Skate
+//* FILENAME:       GoalManager.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  1/24/2001
+//****************************************************************************
+
+#ifndef	__MODULES_SKATE_GOALMANAGER_H
+#define	__MODULES_SKATE_GOALMANAGER_H
+
+                           
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#ifndef __SYS_TIMER_H
+    #include  // For Tmr::Time
+#endif
+							  
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Game
+{
+	// script names
+	const uint32 vINIT			= 0x398b1b8b;
+	const uint32 vUNINIT		= 0xd4e0306a;
+	const uint32 vDESTROY		= 0x83b57984;
+	const uint32 vACTIVATE		= 0x4999fb52;
+	const uint32 vDEACTIVATE	= 0x01c92cac;	
+	const uint32 vACTIVE		= 0xb4e103fd;
+	const uint32 vINACTIVE		= 0x742cf11f;
+	const uint32 vEXPIRED		= 0xd34eb52b;
+	const uint32 vSUCCESS		= 0x90ff204d;
+	const uint32 vFAIL			= 0x79d8d4b6;
+    const uint32 vUNLOCK        = 0x951b4f10;
+
+	const uint32 vBET_SUCCESS	= 0x782bdda1;
+
+    // flag array name - goal_flags
+    const uint32 vFLAGS         = 0xcc3c4cc4;
+
+	// number of goal flags allowed
+	const uint32 vNUMGOALFLAGS = 10;
+
+    // number of completed goals required to unlock this goal
+    const int vUNLOCKED_BY_ANOTHER  = 0x8a3a324e;   // type - unlocked_by_another goal
+    const int vPRO_GOAL             = 0xd303a1a3;   // type - pro_goal
+	// const int vPRO_CHALLENGE		= 0xe0f96410;	// type - pro_specific_challenge
+
+	// number of streams to search through for goal wait vo
+	const int vMAXWAITSTREAMS		= 10;
+	const int vMAXWINSTREAMS		= 10;
+	const int vMAXMIDGOALSTREAMS	= 10;
+	const int vMAXCALLSKATERBYNAMESTREAMS = 5;
+
+	// Size of the local buffer used for replacing trick names in goals
+	const uint32	NEW_STRING_LENGTH = 512;
+
+	const int vMAXDIFFICULTYLEVEL = 2;	
+	enum GOAL_MANAGER_DIFFICULTY_LEVEL
+	{
+		GOAL_MANAGER_DIFFICULTY_LOW = 0,
+		GOAL_MANAGER_DIFFICULTY_MEDIUM,
+		GOAL_MANAGER_DIFFICULTY_HIGH,
+	};
+	
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CGoalManager : public Spt::Class
+{
+public:
+	CGoalManager();
+	~CGoalManager();
+
+public:
+	bool				CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	uint32				GetGoalIdFromParams( Script::CStruct* pParams, bool assert = true );
+	
+	bool				AddGoal( uint32 goalId, Script::CStruct* pParams );
+	CGoal*				GetGoal( uint32 goalId, bool assert = true );
+	CGoal*				GetGoal( Script::CStruct* pParams, bool assert = true );
+	CGoal*				GetGoalByIndex( int index );
+	bool				ActivateGoal( uint32 goalId, bool dontAssert );
+	bool				DeactivateGoal( uint32 goalId );
+	bool				WinGoal( uint32 goalId );
+	bool				LoseGoal( uint32 goalId );
+	bool				RemoveGoal( uint32 goalId );
+    void                UnlockGoal( uint32 goalId );
+    void                LevelUnload();
+    void                LevelLoad();
+
+	void				UpdateFamilyTrees();
+	void				FindCircularLinks( uint32 parent, uint32 child );
+	
+    bool				GoalIsActive( uint32 goalId );
+	bool				EditGoal( uint32 goalId, Script::CStruct* pParams );
+	bool				SetGoalTimer( uint32 goalId, int time = -1 );
+	int					GetNumGoals();
+	int					GetNumActiveGoals( bool count_all = false );
+	int					GetNumSelectedGoals();
+	void				DeactivateAllGoals( bool include_net_goals = false );
+	void				UninitializeGoal( uint32 goalId );
+	void				UninitializeGoalTree( uint32 start_goal );
+	void				UninitializeAllGoals();
+	void				DeactivateAllMinigames();
+	void				InitializeAllMinigames();
+	void				InitializeAllGoals();
+	void				InitializeAllSelectedGoals();
+	void				DeselectAllGoals();
+	void				RemoveAllGoals();
+	void				Update();
+    bool                HasSeenGoal( uint32 goalId );
+    void                PauseAllGoals();
+    void                UnPauseAllGoals();
+    Tmr::Time           GetGoalTime();
+    int                 GetActiveGoal( bool ignore_net_goals = false );
+    bool                QuickStartGoal( uint32 goalId, bool dontAssert = false );
+	void                RestartLastGoal();
+	bool				CanRestartStage();
+	void				RestartStage();
+    bool                HasWonGoal( uint32 goalId );
+	bool				GoalIsSelected( uint32 goalId );
+	void				ToggleGoalSelection( uint32 goalId );
+	void				CreateGoalLevelObjects( void );
+    
+    // void                CreateGoalFlag( uint32 goalId, uint32 flag );
+    bool                SetGoalFlag( uint32 goalId, uint32 flag, int value );
+	bool				GoalFlagSet( uint32 goalId, uint32 flag );
+    bool                AllFlagsSet( uint32 goalId );
+    
+	bool				CreatedGapGoalIsActive(); // Added by Ken
+	bool				GetCreatedGoalGap(int gapIndex); // Also added by Ken
+
+    bool                CanRetryGoal();
+    Script::CStruct*    GetGoalParams( uint32 goalId );
+    bool                CanStartGoal();
+    
+    bool                NextRaceWaypoint( uint32 goalId );
+    bool                NextTourSpot( uint32 goalId );
+    void                InitTrickObjects( uint32 goalId );
+	bool				ShouldLogTrickObject();
+    bool                SetGraffitiMode( int mode );
+
+	void				GotTrickObject( uint32 clusterId, int score );
+    void                SetSpecialGoal( uint32 goalId, int set );
+	void                CheckTrickText();
+//	void                CheckTetrisTricks();
+    void                GotCounterObject( uint32 goalId );
+    bool                CounterGoalDone( uint32 goalId );
+    void                SpendGoalPoints( int num );
+    bool                HasGoalPoints( int num );
+    int                 GetNumberOfGoalPoints();
+	int					GetTotalNumberOfGoalPointsEarned();
+	void				ClearGoalPoints();
+    void                AddGoalPoint();
+    void                AddCash( int amount );
+    int                 GetCash( bool get_total = false );
+    bool                SpendCash( int amount );
+    bool                HasBeatenGoalWithProset( const char* proset_prefix );
+	int                 NumGoalsBeatenBy( int obj_id );
+	int                 NumGoalsBeatenByTeam( int team_id );
+	int                 NumGoalsBeaten();
+    int                 NumGoalsBeatenInLevel( int levelNum, bool count_pro_specific_challenges = false );
+    void                UnlockAllGoals();
+	void				TurnPro();
+	bool				CheckMinigameRecord( uint32 goalId, int value );
+	void				DeactivateMinigamesWithTimer();
+	void				DeactivateCurrentGoal();
+	void				SetStartTime( uint32 goalId );
+	void				UpdateComboTimer( uint32 goalId );
+	void				SetStartHeight( uint32 goalId );
+	bool				CheckHeightRecord( uint32 goalId );
+	bool				CheckDistanceRecord( uint32 goalId );
+
+	void				PlayGoalStartStream( Script::CStruct* pParams );
+	void				StopCurrentStream( uint32 goalId );
+	void				PlayGoalWinStream( Script::CStruct* pParams );
+	void				PlayGoalWaitStream( uint32 goalId );
+	void				PlayGoalStream( Script::CStruct* pParams );
+
+	void				GetGoalAnimations( uint32 goalId, const char* type, Script::CScript* pScript );
+
+	void				PauseGoal( uint32 goalId );
+	void				UnPauseGoal( uint32 goalId );
+
+	void				PauseCompetition( uint32 goalId );
+	void				UnPauseCompetition( uint32 goalId );
+
+	void				UnBeatAllGoals();
+	void				AddViewGoalsList();
+	void				AddGoalChoices( bool selected_only );
+
+	bool				GoalIsLocked( uint32 goalId );
+
+	CGoal*				IsInCompetition();
+
+	uint32				GetRandomBettingMinigame();
+	bool				EndRunCalled( uint32 goalId );
+
+	uint32				GetBettingGuyId();
+
+	Script::CStruct*	GetGoalManagerParams();
+
+	bool				ReplaceTrickText( uint32 goalId );
+	bool				ReplaceTrickText( uint32 goalId, const char* p_old_string, char* p_new_string, uint out_buffer_size );
+	void				ReplaceAllTrickText();
+
+	// void				UnlockProSpecificChallenges();
+	// bool				ProSpecificChallengesUnlocked();
+
+	int					GetNumberCollected( uint32 goalId );
+	int					GetNumberOfFlags( uint32 goalId );
+
+	bool 				IsPro();
+
+	void				ResetGoalFlags( uint32 goalId );
+
+	void				ColorTrickObjects( uint32 goalId, int seqIndex, bool clear = false );
+
+	int					GetNumberOfTimesGoalStarted( uint32 goalId );
+
+	bool				GoalExists( uint32 goalId );
+
+	Script::CStruct*	GetGoalAttemptInfo();
+
+	void				SetCanStartGoal( int value );
+	void				CheckTrickObjects();
+
+	uint32				GetLastGoalId() { return m_lastGoal; }
+	void				ClearLastGoal( void );
+
+	// void				MarkProSpecificChallengeBeaten( uint32 skater_name, int beaten = 1 );
+	// bool				SkaterHasBeatenProSpecificChallenge( uint32 skater_name );
+
+	void				SetEndRunType( uint32 goalId, EndRunType newEndRunType );
+	bool				ShouldUseTimer();
+	void				SetShouldDeactivateOnExpire( uint32 goalId, bool should_deactivate );
+	void				Land();
+	void				Bail();
+	bool				AwardMinigameCash( uint32 goalId, int amount );
+	void				ResetCareer();
+	void				AwardAllGoalCash();
+
+	void				RememberLevelStructureName( uint32 level ) { m_levelStructureName = level; }
+	uint32				GetLevelStructureName() { return m_levelStructureName; }
+
+	void				SetDifficultyLevel( int level );
+	GOAL_MANAGER_DIFFICULTY_LEVEL GetDifficultyLevel( bool career = false );
+
+	void				SetGoalChaptersAndStages();
+	bool				AdvanceStage( bool force = false, uint32 just_won_goal_id = 0 );
+	int					GetCurrentChapter() { return m_currentChapter; }
+	int					GetCurrentStage() { return m_currentStage; }
+	void				SetCurrentChapter( int chapter ) { m_currentChapter = chapter; }
+	void				SetCurrentStage( int stage ) { m_currentStage = stage; }
+	void				RunLastStageScript();
+	void				SetSponsor( uint32 sponsor ) { m_sponsor = sponsor; }
+	uint32				GetSponsor() { return m_sponsor; }
+	void				SetTeamMember( uint32 pro, bool remove = false, bool mark_used = false );
+	void				KillTeamMembers( );
+	void				SetTeamName( const char* pTeamName );
+	Script::CStruct*	GetTeam() { return mp_team; }
+	void				HideAllGoalPeds( bool hide );
+protected:
+	Lst::LookupTable	    m_goalList;
+	Script::CStruct*            mp_goalFlags;
+    
+    uint32                      m_lastGoal;
+	uint32						m_lastStage;
+    int                         m_graffitiMode;
+    int                         m_goalPoints;
+	int							m_totalGoalPointsEarned;
+    int                         m_cash;
+	int							m_totalCash;
+    
+	int							m_numGoalsBeaten;
+	bool 						m_isPro;
+	// bool						m_proChallengesUnlocked;
+	bool						m_canStartGoal;
+
+	int							m_currentChapter;
+	int							m_currentStage;
+
+	uint32						m_sponsor;
+	Script::CStruct*			mp_team;
+
+	// Script::CStruct*			mp_proSpecificChallenges;
+
+	uint32						m_levelStructureName;
+
+	Script::CStruct* 			mp_difficulty_levels;
+};
+
+CGoalManager*           GetGoalManager();
+
+bool ScriptAddGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRemoveGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEditGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptActivateGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDeactivateGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptClearLastGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptWinGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoseGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRemoveAllGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDeactivateAllGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUninitializeAllGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUninitializeGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUpdateAllGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInitializeAllGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInitializeAllSelectedGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptHasActiveGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGoalIsActive(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetGoalTimer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptZeroGoalTimer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetGoalFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptHasSeenGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPauseAllGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnPauseAllGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRestartLastGoal(Script::CStruct *pParams, Script::CScript *pScript);
+// bool ScriptCreateGoalFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGoalFlagSet(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAllFlagsSet(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCanRetryGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetGoalParams(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCanStartGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetCanStartGoal( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptNextRaceWaypoint(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptNextTourSpot(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInitTrickObjects(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCreateGoalName(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGetLevelPrefix( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetGraffitiMode(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptSetSpecialGoal(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGetNumGoalsCompleted(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGetNumGoalsNeeded(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptHasWonGoal(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGoalIsSelected(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptToggleGoalSelection(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGoalsAreSelected(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGotCounterObject(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptCounterGoalDone(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptAddGoalPoint(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptSpendGoalPoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptHasGoalPoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptClearGoalPoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGetNumberOfGoalPoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptAddCash(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGetCash(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptSpendCash(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptHasBeatenGoalWithProset(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGetProsetNotPrefix(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptLevelLoad(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptLevelUnload(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptNumGoalsBeatenInLevel(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptUnlockAllGoals(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptCheckMinigameRecord(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptDeactivateCurrentGoal(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptSetStartTime(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptUpdateComboTimer(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptSetStartHeight(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptCheckHeightRecord(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptCheckDistanceRecord(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptShowGoalPoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptHideGoalPoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptHidePoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptShowPoints(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptTurnPro(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptDeactivateAllMinigames(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptInitializeAllMinigames(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptPauseGoal(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptUnPauseGoal(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptPauseCompetition(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptUnPauseCompetition(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptPlayGoalStartStream(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptPlayGoalWinStream(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptStopCurrentStream(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptPlayGoalWaitStream(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptUnBeatAllGoals(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptAddViewGoalsList(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptGoalIsLocked(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptIsInCompetition(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptGetGoalAnimations(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptPlayGoalStream(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptSetEndRunType(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptEndRunCalled(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptClearEndRun(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptFinishedEndOfRun(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptStartedEndOfRun(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptEndBetAttempt(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptStartBetAttempt(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptBetOffered(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptBetRefused(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptWinBet(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptMoveBettingGuyNow(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptBetAccepted(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptBetIsActive(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptAddMinigameTime(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptAddTempSpecialTrick(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptRemoveTempSpecialTrick(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptGetTrickFromKeyCombo(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptUnlockGoal(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptQuickStartGoal( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptInitializeGoal( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGoalInitialized( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAddGoalChoices( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptAddTime( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptReplaceTrickText( Script::CStruct* pParams, Script::CScript* pScript );
+
+// bool ScriptUnlockProSpecificChallenges( Script::CStruct* pParams, Script::CScript* pScript );
+// bool ScriptProSpecificChallengesUnlocked( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetNumberCollected( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetNumberOfFlags( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptIsPro( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptResetGoalFlags( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptColorTrickObjects( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetNumberOfTimesGoalStarted( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGoalExists( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetGoalAttemptInfo( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetLastGoalId( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptClearTetrisTricks( Script::CStruct* pParams, Script::CScript* pScript );
+
+// bool ScriptMarkProSpecificChallengeBeaten( Script::CStruct* pParams, Script::CScript* pScript );
+// bool ScriptSkaterHasBeatenProSpecificChallenge( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAnnounceGoalStarted( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAnnounceGoalExited( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSetShouldDeactivateOnExpire( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetActiveGoalId( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptAwardMinigameCash( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptResetCareer( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptAwardAllGoalCash( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptUpdateFamilyTrees( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptIsLeafNode( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptIsRootNode( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSuspendGoalPedLogic( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptRememberLevelStructureName( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSetDifficultyLevel( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetDifficultyLevel( Script::CStruct* pParams, Script::CScript* pScript );
+
+// bool ScriptGetGoalParam( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptRestartStage( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptCanRestartStage( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSetGoalChaptersAndStages( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAdvanceStage( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetCurrentChapterAndStage( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetCurrentChapterAndStage( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptRunLastStageScript( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptFilmGoalCheckpoint( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptStartFilming( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGoalShouldExpire( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSetSponsor( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetSponsor( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetTeam( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetTeamMember( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptKillTeamMembers( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetTeamName( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptUnloadLastFam( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptStopLastSream( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptHideAllGoalPeds( Script::CStruct* pParams, Script::CScript* pScript );
+}
+
+#endif // __MODULES_SKATE_GOALMANAGER_H
+
diff --git a/Code/Sk/Modules/Skate/GoalPed.cpp b/Code/Sk/Modules/Skate/GoalPed.cpp
new file mode 100644
index 0000000..f5e4d73
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GoalPed.cpp
@@ -0,0 +1,1139 @@
+// goal ped!
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Game
+{
+
+CGoalPed::CGoalPed( Script::CStruct* pParams )
+{
+	pParams->GetChecksum( CRCD(0x9982e501,"goal_id"), &m_goalId, Script::ASSERT );
+	m_goalLogicSuspended = false;
+	m_lastFam = 0;
+	m_lastFamObj = 0;
+	m_lastStreamId = 0;
+}
+
+CGoalPed::~CGoalPed()
+{
+	// nothing yet.
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/	
+
+inline void parse_first_name( char* pFirstName, const char** ppDisplayName, int buffer_size )
+{
+	if ( !strstr( *ppDisplayName, " " ) )
+	{		
+		Dbg_MsgAssert( strlen( *ppDisplayName ) < (uint32)buffer_size, ( "buffer overflow" ) );
+		strcpy( pFirstName, *ppDisplayName );
+		// printf("first name has no spaces, using %s\n", pFirstName);
+	}
+	else
+	{
+		char *p_in = pFirstName;
+		int length = 0;
+		while ( **ppDisplayName != ' ' )
+		{
+			*p_in++ = **ppDisplayName;
+			(*ppDisplayName)++;
+			length++;
+		}
+		*p_in++ = '\0';
+		length++;
+		Dbg_MsgAssert( length < buffer_size, ( "buffer overflow" ) );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/	
+
+const char* CGoalPed::GetProFirstName()
+{	
+	// get params
+	Script::CStruct* pParams = GetGoalParams();
+	
+	// grab the pro
+	const char* p_default_pro_name;
+	const char* p_alternate_pro_name;
+	if ( !pParams->GetText( CRCD(0x944b2900,"pro"), &p_default_pro_name ) )
+	{
+		// printf("no pro associated with this goal\n");
+		return 0;
+	}
+
+	// check what pro the player is using
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();	
+	const char* p_current_pro_name;
+	p_current_pro_name = pSkaterProfile->GetDisplayName();
+	// printf("the current display name is %s\n", p_current_pro_name);
+	
+	// only check the display name if it's a pro
+	bool use_alternate = false;
+	if ( pSkaterProfile->IsPro() )
+	{	
+		// chop off the first name
+		char current_pro_first_name[128];
+		parse_first_name( current_pro_first_name, &p_current_pro_name, 128 );
+
+		Dbg_MsgAssert( strlen( current_pro_first_name ) > 0, ( "No first name found for the current pro" ) );
+		// printf("the player's first name is %s\n", current_pro_first_name);
+	
+		// see if we should use the alternate pro
+		if ( pSkaterProfile->IsPro() && ( Script::GenerateCRC( current_pro_first_name ) == Script::GenerateCRC( p_default_pro_name ) ) )
+		{
+			if ( pParams->GetText( CRCD(0x878ccc12,"alternate_pro"), &p_alternate_pro_name ) )
+			{
+				use_alternate = true;
+				// printf("found an alternate name of %s\n", p_alternate_pro_name);
+			}
+			else
+			{
+				// printf("no alternate pro found for this goal\n");
+				return 0;
+			}
+		}
+	}
+	
+	// build the stream name
+	if ( use_alternate )
+		return p_alternate_pro_name;
+	else
+		return p_default_pro_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/	
+
+inline void possibly_add_success_suffix( bool success, char* buffer, int buffer_length )
+{
+	if ( success )
+	{
+		Dbg_MsgAssert( (int)strlen( buffer ) + 8 < buffer_length, ( "buffer overflow" ) );
+		sprintf( buffer, "%s_success", buffer );
+	}
+	// printf("done creating stream %s\n", buffer );
+}
+
+// returns true if it found a lip version of the stream, signifying
+// that there exists a fam associated with the stream
+bool CGoalPed::GetStreamChecksum( uint32* p_streamChecksum, int cam_anim_index, bool success, const char* p_speaker_name, bool last_anim )
+{
+	Script::CStruct* pGoalParams = GetGoalParams();
+
+	const char* p_first_name = GetProFirstName();
+	if ( !p_first_name )
+		return 0;
+
+	const char* p_goal_name;
+	pGoalParams->GetText( CRCD(0xbfecc45b,"goal_name"), &p_goal_name, Script::ASSERT );
+	
+	// build stream name
+	char p_stream_name[MAX_STREAM_NAME_LENGTH];
+	Dbg_MsgAssert( strlen( p_first_name ) + strlen( p_goal_name ) + 1 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+	sprintf( p_stream_name, "%s_%s", p_first_name, p_goal_name );
+
+	#ifdef	__NOPT_ASSERT__
+	uint32 stream_name_length = (uint32)strlen( p_stream_name );
+	#endif
+	
+	// get string representation of difficulty level
+	const char* p_difficulty_level_string;
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	GOAL_MANAGER_DIFFICULTY_LEVEL difficulty_level = pGoalManager->GetDifficultyLevel();
+	switch ( difficulty_level )
+	{
+		case ( GOAL_MANAGER_DIFFICULTY_LOW ):
+			p_difficulty_level_string = "easy";
+			break;
+		case ( GOAL_MANAGER_DIFFICULTY_MEDIUM ):
+			p_difficulty_level_string = "medium";
+			break;
+		case ( GOAL_MANAGER_DIFFICULTY_HIGH ):
+			p_difficulty_level_string = "hard";
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Unknown difficulty level" ) );
+			return 0;
+			break;
+	}
+
+	// build different prefix possibilities
+	// format is: _____success_lip
+	// where difficulty, camAnimXX, speaker, success, and lip are optional
+
+	// figure out if we need to look for a female vo
+	bool is_female_skater = false;
+	if ( p_speaker_name && Script::GenerateCRC( p_speaker_name ) == CRCD(0x5b8ab877,"skater") )
+	{
+		Mdl::Skate * pSkate = Mdl::Skate::Instance();
+		Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();	
+		int male_test;
+		pSkaterProfile->GetInfo()->GetInteger( CRCD(0x3f813177,"is_male"), &male_test, Script::ASSERT );
+		if ( !male_test )
+		{
+			is_female_skater = true;
+		}
+	}
+	
+	// diff level
+	char p_diff_stream_name[MAX_STREAM_NAME_LENGTH];
+	Dbg_MsgAssert( stream_name_length + strlen( p_difficulty_level_string ) + 1 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+	sprintf( p_diff_stream_name, "%s_%s", p_stream_name, p_difficulty_level_string );
+	#ifdef	__NOPT_ASSERT__
+	uint32 diff_stream_name_length = (uint32)strlen( p_diff_stream_name );
+	#endif
+	
+	// if we have a cam anim index...
+	char p_diff_cam_stream_name[MAX_STREAM_NAME_LENGTH];
+	strcpy( p_diff_cam_stream_name, "" );
+	char p_cam_stream_name[MAX_STREAM_NAME_LENGTH];
+	strcpy( p_cam_stream_name, "" );
+	char p_cam_speaker_stream_name[MAX_STREAM_NAME_LENGTH];
+	strcpy( p_cam_speaker_stream_name, "" );
+	char p_diff_cam_speaker_stream_name[MAX_STREAM_NAME_LENGTH];
+	strcpy( p_diff_cam_speaker_stream_name, "" );
+	if ( cam_anim_index >= 0 && !last_anim )
+	{	
+		// diff level and cam anim
+		Dbg_MsgAssert( diff_stream_name_length + 10 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+		sprintf( p_diff_cam_stream_name, "%s_camAnim%02i", p_diff_stream_name, cam_anim_index );
+		possibly_add_success_suffix( success, p_diff_cam_stream_name, MAX_STREAM_NAME_LENGTH );
+		
+		// cam anim
+		Dbg_MsgAssert( stream_name_length + 10 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+		sprintf( p_cam_stream_name, "%s_camAnim%02i", p_stream_name, cam_anim_index );
+		possibly_add_success_suffix( success, p_cam_stream_name, MAX_STREAM_NAME_LENGTH );
+
+		if ( p_speaker_name )
+		{
+			#ifdef	__NOPT_ASSERT__
+			int speaker_name_length = strlen( p_speaker_name );
+			// cam anim and speaker name
+			Dbg_MsgAssert( stream_name_length + 10 + speaker_name_length + 1 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+			#endif
+			
+			sprintf( p_cam_speaker_stream_name, "%s_camAnim%02i_%s", p_stream_name, cam_anim_index, p_speaker_name );
+			if ( is_female_skater )
+			{
+				Dbg_MsgAssert( strlen( p_cam_speaker_stream_name ) + 2 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+				sprintf( p_cam_speaker_stream_name, "%s_f", p_cam_speaker_stream_name );
+			}
+			possibly_add_success_suffix( success, p_cam_speaker_stream_name, MAX_STREAM_NAME_LENGTH );
+
+			
+			// diff level, cam anim, and speaker name
+			Dbg_MsgAssert( diff_stream_name_length + 10 + speaker_name_length + 1 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+			sprintf( p_diff_cam_speaker_stream_name, "%s_camAnim%02i_%s", p_diff_stream_name, cam_anim_index, p_speaker_name );
+			if ( is_female_skater )
+			{
+				Dbg_MsgAssert( strlen( p_diff_cam_speaker_stream_name ) + 2 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+				sprintf( p_diff_cam_speaker_stream_name, "%s_f", p_diff_cam_speaker_stream_name );
+			}
+			possibly_add_success_suffix( success, p_diff_cam_speaker_stream_name, MAX_STREAM_NAME_LENGTH );
+		}		
+	}
+
+	char p_diff_speaker_stream_name[MAX_STREAM_NAME_LENGTH];
+	strcpy( p_diff_speaker_stream_name, "" );
+	char p_speaker_stream_name[MAX_STREAM_NAME_LENGTH];
+	strcpy( p_speaker_stream_name, "" );
+	if ( p_speaker_name )
+	{
+		#ifdef	__NOPT_ASSERT__
+		int speaker_name_length = strlen( p_speaker_name );
+		// diff level and speaker name
+		Dbg_MsgAssert( diff_stream_name_length + speaker_name_length + 1 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+		#endif
+		sprintf( p_diff_speaker_stream_name, "%s_%s", p_diff_stream_name, p_speaker_name );
+		if ( is_female_skater )
+		{
+			Dbg_MsgAssert( strlen( p_diff_speaker_stream_name ) + 2 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+			sprintf( p_diff_speaker_stream_name, "%s_f", p_diff_speaker_stream_name );
+		}
+		possibly_add_success_suffix( success, p_diff_speaker_stream_name, MAX_STREAM_NAME_LENGTH );
+	
+		// speaker name
+		Dbg_MsgAssert( stream_name_length + speaker_name_length + 1 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+		sprintf( p_speaker_stream_name, "%s_%s", p_stream_name, p_speaker_name );
+		if ( is_female_skater )
+		{
+			Dbg_MsgAssert( strlen( p_speaker_stream_name ) + 2 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+			sprintf( p_speaker_stream_name, "%s_f", p_speaker_stream_name );
+		}
+		possibly_add_success_suffix( success, p_speaker_stream_name, MAX_STREAM_NAME_LENGTH );
+	}
+	
+	// now that we're done using the diff stream  and stream name
+	// as pieces, add the success suffix
+	possibly_add_success_suffix( success, p_diff_stream_name, MAX_STREAM_NAME_LENGTH );
+	possibly_add_success_suffix( success, p_stream_name, MAX_STREAM_NAME_LENGTH );
+	
+	// array of pointers to my streams to make the code more readable
+	// 0 - p_stream_name
+	// 1 - p_diff_stream_name
+	// 2 - p_diff_cam_stream_name
+	// 3 - p_cam_stream_name
+	// 4 - p_cam_speaker_stream_name
+	// 5 - p_diff_cam_speaker_stream_name
+	// 6 - p_diff_speaker_stream_name
+	// 7 - p_speaker_stream_name
+	char* pp_stream_names[8];
+	pp_stream_names[0] = p_stream_name;
+	pp_stream_names[1] = p_diff_stream_name;
+	pp_stream_names[2] = p_speaker_stream_name;
+	pp_stream_names[3] = p_diff_speaker_stream_name;
+	pp_stream_names[4] = p_diff_cam_stream_name;
+	pp_stream_names[5] = p_cam_stream_name;
+	pp_stream_names[6] = p_cam_speaker_stream_name;
+	pp_stream_names[7] = p_diff_cam_speaker_stream_name;
+
+	// search!
+	// if there's a cam anim index defined, don't search for 
+	// ones without cam anim index
+	int start_index = 0;
+	if ( cam_anim_index >= 0 )
+	{
+		start_index = 4;
+	}
+
+	// search for regular or lip version of stream
+	uint32 existing_stream = 0;
+	bool found_lip = false;
+	for ( int i = start_index; i < 8; i++ )
+	{
+		// make sure there's something to check
+		int stream_name_length = strlen( pp_stream_names[i] );
+		if ( stream_name_length == 0 )
+		{
+			continue;
+		}
+		
+		// test lip version first - it's more likely to happen
+		char p_lip_stream[MAX_STREAM_NAME_LENGTH];
+		Dbg_MsgAssert( (uint32)stream_name_length + 4 < MAX_STREAM_NAME_LENGTH, ( "buffer overflow" ) );
+		sprintf( p_lip_stream, "%s_lip", pp_stream_names[i] );
+		// printf("looking for %s\n", p_lip_stream );
+		uint32 lip_stream_checksum = Script::GenerateCRC( p_lip_stream );
+		if ( Pcm::StreamExists( lip_stream_checksum ) )
+		{
+			// printf("Existing stream: %s\n", p_lip_stream);
+			existing_stream = lip_stream_checksum;
+			found_lip = true;
+			break;
+		}
+		else
+		{
+			uint32 stream_checksum = Script::GenerateCRC( pp_stream_names[i] );
+			// printf("looking for %s\n", pp_stream_names[i]);
+			if ( Pcm::StreamExists( stream_checksum ) )
+			{
+				// printf( "Existing stream: %s\n", pp_stream_names[i] );
+				existing_stream = stream_checksum;
+				break;				
+			}
+		}
+	}
+	*p_streamChecksum = existing_stream;
+	return found_lip;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/	
+
+void CGoalPed::GetPlayerFirstName( char* p_first_name, int buffer_size )
+{
+	// check what pro the player is using
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();	
+	
+	const char* p_current_pro_name;
+	p_current_pro_name = pSkaterProfile->GetDisplayName();
+	
+	strcpy( p_first_name, "" );
+	parse_first_name( p_first_name, &p_current_pro_name, buffer_size );
+	Dbg_MsgAssert( strlen( p_first_name ) > 0, ( "No current player name found" ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalPed::PlayGoalStream( uint32 stream_checksum, bool use_pos_info, uint32 speaker_obj_id, bool should_load_fam )
+{
+	if ( m_goalLogicSuspended )
+		return;
+
+	if ( !Pcm::StreamExists( stream_checksum ) )
+		return;
+
+	// get params
+	Script::CStruct* pParams = GetGoalParams();
+
+	// printf("PlayGoalStream: %x\n", stream_checksum );
+	
+	// run script and associate with trigger
+	uint32 trigger_obj_id = speaker_obj_id;
+	if ( !trigger_obj_id )
+	{
+		pParams->GetChecksum( CRCD(0x02d7e03d,"trigger_obj_id"), &trigger_obj_id, Script::ASSERT );
+	}
+	
+	Obj::CCompositeObject* p_pos_obj = NULL;
+	if ( trigger_obj_id == CRCD(0x5b8ab877,"skater") )
+	{		
+		Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+		p_pos_obj = skate_mod->GetLocalSkater();
+	}
+	else
+	{
+		//p_pos_obj = Obj::CMovingObject::m_hash_table.GetItem( trigger_obj_id );
+		p_pos_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( trigger_obj_id);
+	}
+	
+	if ( p_pos_obj )
+	{		
+		Script::CStruct* p_tempParams = new Script::CStruct();
+		p_tempParams->AddChecksum( CRCD(0x0572ea28,"stream_checksum"), stream_checksum );
+
+		if ( !use_pos_info )
+		{
+			p_tempParams->AddInteger( CRCD(0xc9b0deb7,"use_pos_info"), 0 );
+			
+			// get the reference checksum, so that we can play anims on the correct skeleton
+			// (either animload_thps5_human or animload_ped_female)
+			Obj::CAnimationComponent* pAnimComponent = GetAnimationComponentFromObject( p_pos_obj );
+			Dbg_Assert( pAnimComponent );
+			uint32 reference_checksum = pAnimComponent->GetAnimScriptName();
+	
+			if ( should_load_fam )
+			{
+				LoadFam( stream_checksum, reference_checksum );
+			}
+		
+			// preload the stream
+			uint32 streamId = Pcm::PreLoadStream( stream_checksum, 100 );
+			p_tempParams->AddChecksum( CRCD(0x8a68ab90,"streamId"), streamId );
+			m_lastStreamId = streamId;
+	
+			// Script::KillSpawnedScriptsThatReferTo( CRCD(0x5376a011,"goal_play_stream") );
+			
+			// delete p_tempParams;
+		
+			// look for an FAM (frame amplitude) with that name
+			// (needs an actual skeleton name, which we will
+			// hardcode as "animload_thps5_human" for now)
+			Ass::CAssMan* ass_man = Ass::CAssMan::Instance();
+			if ( ass_man->GetAsset( stream_checksum + reference_checksum, false ) )
+			{
+				p_tempParams->AddChecksum( NONAME, CRCD(0x93516575,"play_anim") );
+				m_lastFamObj = trigger_obj_id;
+				
+				/*Script::CStruct* pTempStruct = new Script::CStruct;
+				pTempStruct->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0x659bf355,"partialAnim") );
+				pTempStruct->AddChecksum( CRCD(0x40c698af,"id"), CRCD(0xc0ba0665,"jawRotation") );
+				
+				// first we want to kill off any old jaw rotation anims that might be playing...
+				// pAnimComponent->CallMemberFunction( CRCD(0x986d274e,"RemoveAnimController"), pTempStruct, NULL );
+				// Script::RunScript( CRCD(0x986d274e,"RemoveAnimController"), pTempStruct, p_pos_obj );
+																				  
+				pTempStruct->AddChecksum( CRCD(0x6c2bfb7f,"animName"), stream_checksum );
+				pTempStruct->AddChecksum( CRCD(0x46e55e8f,"from"), CRCD(0x6086aa70,"start") );
+				pTempStruct->AddChecksum( CRCD(0x28782d3b,"to"), CRCD(0xff03cc4e,"end") );
+				//pTempStruct->AddChecksum( NONAME, CRCD(0x4f792e6c,"cycle") );
+				pTempStruct->AddFloat( CRCD(0xf0d90109,"speed"), 1.0f );
+				// printf("adding animcontroller to %s\n", Script::FindChecksumName( trigger_obj_id ) );
+				pAnimComponent->CallMemberFunction( CRCD(0x83654874,"AddAnimController"), pTempStruct, NULL );
+				// Script::RunScript( CRCD(0x83654874,"AddAnimController"), pTempStruct, p_pos_obj );
+				
+				delete pTempStruct;*/
+			}
+			else
+			{
+				Dbg_Message( "Couldn't find FAM file '%s'...  is it preloaded?.\n", Script::FindChecksumName(stream_checksum) );
+			}
+		}
+		Script::CScript* pSpawnedScript = Script::SpawnScript( CRCD(0x5376a011,"goal_play_stream"), p_tempParams );
+		pSpawnedScript->mpObject = p_pos_obj;
+		delete p_tempParams;
+	}
+	else
+	{
+		Dbg_Message( "WARNING: Could not find pro to associate with goal_play_stream" );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalPed::PlayGoalStream( const char* type, Script::CStruct* pParams )
+{
+	// Garrett: This function can take over 1 ms to execute when searching for multiple streams.
+	//uint64 start_time = Tmr::GetTimeInUSeconds();	
+
+	if ( m_goalLogicSuspended )
+		return;
+
+	// check for any flags
+	bool play_random = pParams->ContainsFlag( CRCD(0x2be2056a,"play_random") );
+	bool call_player_by_name = pParams->ContainsFlag( CRCD(0x9f38b332,"call_player_by_name") );
+
+	bool found_player_name_vo = false;
+
+	if ( play_random && call_player_by_name )
+	{
+		Dbg_MsgAssert( 0, ("GoalManager_PlayGoalStream called with play_random and call_player_by_name flags") );
+	}
+
+	uint32 stream_checksum;
+	if ( pParams->GetChecksum( CRCD(0x1a4e4fb9,"vo"), &stream_checksum, Script::NO_ASSERT ) )
+	{
+		// play it!
+		PlayGoalStream( stream_checksum );
+		//uint64 end_time = Tmr::GetTimeInUSeconds();	
+		//if ((end_time - start_time) > 250)
+		//	Dbg_Message("PlayGoalStream String 1 Time %d", (end_time - start_time));
+		return;
+	}
+	// else if ( call_player_by_name )
+	else if ( play_random || call_player_by_name )
+	{
+		// check what pro the player is using
+		Mdl::Skate * pSkate = Mdl::Skate::Instance();
+		Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();	
+
+		// don't play the special vo unless they're using a pro.
+		if ( pSkaterProfile->IsPro() )
+		{
+			//uint64 mid_time = Tmr::GetTimeInUSeconds();	
+
+			const char* p_speaker_name = GetProFirstName();
+			char p_player_name[128];
+			GetPlayerFirstName( p_player_name, 128 );
+			char stream_name[128];
+
+			Script::CArray* p_stream_numbers = new Script::CArray();
+			p_stream_numbers->SetSizeAndType( vMAXCALLSKATERBYNAMESTREAMS, ESYMBOLTYPE_INTEGER );
+			for ( int num = 0; num < vMAXCALLSKATERBYNAMESTREAMS; num++ )
+			{
+				p_stream_numbers->SetInteger( num, num + 1 );   // stream numbers start at 1, not 0
+			}
+			for ( int i = 0; i < vMAXCALLSKATERBYNAMESTREAMS; i++ )
+			{
+				// grab a random index and switch with the current index
+				int random_index = Mth::Rnd( vMAXCALLSKATERBYNAMESTREAMS );
+				int random_value = p_stream_numbers->GetInteger( random_index );
+				p_stream_numbers->SetInteger( random_index, p_stream_numbers->GetInteger( i ) );
+				p_stream_numbers->SetInteger( i, random_value );
+			}
+
+			//uint64 mid2_time = Tmr::GetTimeInUSeconds();	
+			// find one!
+			for ( int i = 0; i < vMAXCALLSKATERBYNAMESTREAMS; i++ )
+			{
+				sprintf( stream_name, "%s_Far%s%02i", p_speaker_name, p_player_name, i + 1 );
+				
+				if ( Pcm::StreamExists( Script::GenerateCRC( stream_name ) ) )
+				{
+					// printf("playing targetpro vo directly: %s\n", stream_name);
+					found_player_name_vo = true;
+					PlayGoalStream( Script::GenerateCRC( stream_name ) );
+					break;
+				}				
+			}
+			//uint64 end_time = Tmr::GetTimeInUSeconds();	
+			//if ((end_time - start_time) > 250)
+			//	Dbg_Message("******** PlayGoalStream String 2 Time %d Mid %d Mid2 %d", (end_time - start_time), (mid2_time - mid_time), (end_time - mid2_time));
+			
+			// clean up the array
+			Script::CleanUpArray( p_stream_numbers );
+			delete p_stream_numbers;
+		}
+		// else
+		if ( !found_player_name_vo )
+		{
+			// go ahead and play a random one
+			call_player_by_name = false;
+			play_random = true;
+		}
+	}
+	
+	
+	if ( ( call_player_by_name || play_random ) && !found_player_name_vo )
+	{
+		switch ( Script::GenerateCRC( type ) )
+		{
+		case ( CRCC(0x82117c1a,"wait") ):
+			PlayGoalWaitStream();
+			break;
+		// case ( CRCC(0x90ff204d,"Success") ):
+			// PlayGoalWinStream();
+			// break;
+		case ( CRCC(0x1623b689,"Midgoal") ):
+			PlayGoalMidStream();
+			break;
+		// case ( CRCC(0x6657a075,"Talking") ):
+			// PlayGoalStartStream();
+			// break;
+		default:
+			//Pcm::StopStreams();
+			PlayGoalProStream( type, vMAXWAITSTREAMS, false );
+			break;
+		}
+	}
+
+	//uint64 end_time = Tmr::GetTimeInUSeconds();	
+	//if ((end_time - start_time) > 250)
+	//	Dbg_Message("PlayGoalStream String 3 Time %d", (end_time - start_time));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalPed::PlayGoalStartStream( Script::CStruct* pParams )
+{
+	if ( m_goalLogicSuspended )
+		return;
+
+	// get goal params
+	Script::CStruct* pGoalParams = GetGoalParams();
+	
+	// K: Added this to prevent an assert when created-goal peds try to play a non-existent stream.
+	if ( pGoalParams->ContainsFlag( CRCD( 0x102a8591, "no_stream" ) ) )
+	{
+		return;
+	}	
+	
+	int cam_anim_index = -1;
+	pParams->GetInteger( CRCD(0x64eee2e6,"cam_anim_index"), &cam_anim_index, Script::NO_ASSERT );
+
+	const char* p_speaker_name = NULL;
+	pParams->GetString( CRCD(0x82cbe8d1,"speaker_name"), &p_speaker_name, Script::NO_ASSERT );
+
+	bool last_anim = pParams->ContainsFlag( CRCD(0xe7f5ff8,"last_anim") );
+	bool should_load_fam = false;
+	uint32 stream_checksum;
+	if ( GetStreamChecksum( &stream_checksum, cam_anim_index, false, p_speaker_name, last_anim ) )
+	{
+		// load the fam as well.
+		should_load_fam = true;
+	}
+	
+	if ( stream_checksum )
+	{		
+		// stop any current vo's
+		StopCurrentStream();
+
+		uint32 speaker_obj_id = 0;
+		pParams->GetChecksum( CRCD(0xf0c598fa,"speaker_obj_id"), &speaker_obj_id, Script::NO_ASSERT );
+		PlayGoalStream( stream_checksum, false, speaker_obj_id, should_load_fam );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::LoadFam( uint32 stream_checksum, uint32 reference_checksum )
+{
+	UnloadLastFam();
+	char p_famName[32];
+	sprintf( p_famName, "fam\\%08x.fam", stream_checksum );
+	// printf("loading %s\n", p_famName);
+	Ass::CAssMan* ass_man =  Ass::CAssMan::Instance();
+	
+	uint32 old_ref_checksum = ass_man->GetReferenceChecksum();
+	if ( ass_man->LoadAnim( p_famName, stream_checksum, reference_checksum, false, false ) )
+	{
+		// printf("loaded\n");
+		m_lastFam = Script::GenerateCRC( p_famName );
+	}
+	ass_man->SetReferenceChecksum( old_ref_checksum );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::UnloadLastFam()
+{
+	if ( m_lastFam )
+	{		
+		if ( m_lastFamObj )
+		{			
+			Obj::CCompositeObject* p_pos_obj = NULL;
+			if ( m_lastFamObj == CRCD(0x5b8ab877,"skater") )
+			{		
+				// printf("getting skater\n");
+				Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+				p_pos_obj = skate_mod->GetLocalSkater();
+			}
+			else
+			{
+				//p_pos_obj = Obj::CMovingObject::m_hash_table.GetItem( trigger_obj_id );
+				// printf("getting ped\n");
+				p_pos_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( m_lastFamObj );
+			}
+
+			Script::StopScriptsUsingThisObject_Proper( p_pos_obj, CRCD(0x5376a011,"goal_play_stream") );
+			Script::CStruct* pTempStruct = new Script::CStruct;
+			pTempStruct->AddChecksum( CRCD(0x7321a8d6,"type"), CRCD(0x659bf355,"partialAnim") );
+			pTempStruct->AddChecksum( CRCD(0x40c698af,"id"), CRCD(0xc0ba0665,"jawRotation") );
+
+			// first we want to kill off any old jaw rotation anims that might be playing...		
+			if ( p_pos_obj )
+			{
+				// printf("removing animcontroller\n");
+				Obj::CAnimationComponent* pAnimComponent = GetAnimationComponentFromObject( p_pos_obj );
+				Dbg_Assert( pAnimComponent );
+				pAnimComponent->CallMemberFunction( CRCD(0x986d274e,"RemoveAnimController"), pTempStruct, NULL );				
+			}
+			delete pTempStruct;
+			m_lastFamObj = 0;
+		}
+
+		// get the assman
+		Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+
+		Ass::CAsset* pAsset = ass_man->GetAssetNode( m_lastFam, false );
+
+		if ( pAsset )
+		{
+			// printf("unloading fam\n");
+			ass_man->DestroyReferences( pAsset );
+			ass_man->UnloadAsset( pAsset );
+			Script::RunScript( CRCD( 0xb3dce47f, "invalidate_anim_cache" ) );
+		}
+		m_lastFam = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::StopLastStream()
+{
+	if ( m_lastStreamId )
+	{
+		if ( m_lastFamObj )
+		{			
+			Obj::CCompositeObject* p_pos_obj = NULL;
+			if ( m_lastFamObj == CRCD(0x5b8ab877,"skater") )
+			{		
+				Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+				p_pos_obj = skate_mod->GetLocalSkater();
+			}
+			else
+			{
+				p_pos_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( m_lastFamObj );
+			}
+			Script::StopScriptsUsingThisObject_Proper( p_pos_obj, CRCD(0x5376a011,"goal_play_stream") );
+		}
+		Pcm::StopStreamFromID( m_lastStreamId );
+		m_lastStreamId = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::PlayGoalWaitStream()
+{
+	if ( m_goalLogicSuspended )
+		return;
+
+	PlayGoalProStream( "Far", vMAXWAITSTREAMS, true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::PlayGoalWinStream( Script::CStruct* pParams )
+{
+	if ( m_goalLogicSuspended )
+		return;
+
+	// get params
+	const char* p_speaker_name = NULL;
+	pParams->GetString( CRCD(0x82cbe8d1,"speaker_name"), &p_speaker_name, Script::NO_ASSERT );
+	// int cam_anim_index = -1;
+	// pParams->GetInteger( CRCD(0x64eee2e6,"cam_anim_index"), &cam_anim_index, Script:::NO_ASSERT );
+	// bool last_anim = pParams->ContainsFlag( CRCD(0xe7f5ff8,"last_anim") );
+
+	uint32 stream_checksum;
+	bool should_load_fam = false;
+
+	if ( GetStreamChecksum( &stream_checksum, -1, true, p_speaker_name, true ) )
+	{
+		should_load_fam = true;
+	}
+
+	uint32 speaker_obj_id = 0;
+	pParams->GetChecksum( CRCD(0xf0c598fa,"speaker_obj_id"), &speaker_obj_id, Script::NO_ASSERT );
+	
+	if ( stream_checksum )
+		PlayGoalStream( stream_checksum, false, speaker_obj_id, should_load_fam );
+	// else
+		// PlayGoalProStream( "Success", vMAXWINSTREAMS, false, false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::PlayGoalMidStream()
+{
+	if ( m_goalLogicSuspended )
+		return;
+	PlayGoalProStream( "Midgoal", vMAXMIDGOALSTREAMS, false );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::PlayGoalProStream( const char* p_stream_type, int max_number_streams, bool use_player_name, bool use_pos_info )
+{
+	Dbg_MsgAssert( p_stream_type, ("PlayGoalProStream called without stream type") );
+	Dbg_MsgAssert( max_number_streams, ("PlayGoalProStream called without stream type") );
+	
+	if ( m_goalLogicSuspended )
+		return;
+	
+	// stop any current vo's
+	StopCurrentStream();
+	
+	// get the first name of the speaker and the player
+	const char* p_speaker_name = GetProFirstName();
+	if ( !p_speaker_name )
+		return;
+	
+	// check what pro the player is using
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();	
+	
+	const char* p_current_pro_name;
+	p_current_pro_name = pSkaterProfile->GetDisplayName();
+	
+	char p_player_name[128];
+	parse_first_name( p_player_name, &p_current_pro_name, 128 );
+	Dbg_MsgAssert( strlen( p_player_name ) > 0, ( "No player name found" ) );
+	// printf("got a first name of %s\n", p_player_name);
+	
+	// create an array of all possible stream numbers
+	int num_possible_streams = max_number_streams;
+
+	if ( use_player_name )
+		// there are x wait streams plus 20 possible streams directed specifically at the player ("Hey, tony!")
+		num_possible_streams += 20;
+
+	Script::CArray* p_stream_numbers = new Script::CArray();
+	p_stream_numbers->SetSizeAndType( num_possible_streams, ESYMBOLTYPE_INTEGER );
+	for ( int num = 0; num < num_possible_streams; num++ )
+	{
+		p_stream_numbers->SetInteger( num, num + 1 );   // stream numbers start at 1, not 0
+	}
+	for ( int i = 0; i < num_possible_streams; i++ )
+	{
+		// grab a random index and switch with the current index
+		int random_index = Mth::Rnd( num_possible_streams );
+		int random_value = p_stream_numbers->GetInteger( random_index );
+		p_stream_numbers->SetInteger( random_index, p_stream_numbers->GetInteger( i ) );
+		p_stream_numbers->SetInteger( i, random_value );
+	}
+
+	// go through until we find the first valid stream
+	// uint32 current_vo_stream = 0;
+	for ( int i = 0; i < num_possible_streams; i++ )
+	{
+		int stream_number = p_stream_numbers->GetInteger( i );
+		// printf( "got a stream number of %i\n", stream_number );
+		char stream_name[128];
+
+		// check if this is the special "hey tony" type stream number
+		if ( ( stream_number > max_number_streams ) )
+		{
+			// don't play the special vo unless they're using a pro.
+			if ( pSkaterProfile->IsPro() )
+			{
+				// offset the stream number.
+				stream_number -= max_number_streams;
+				sprintf( stream_name, "%s_%s%s%02i", p_speaker_name, p_stream_type, p_player_name, stream_number );
+				// printf("figured a special vo of %s\n", stream_name);
+				if ( Pcm::StreamExists( Script::GenerateCRC( stream_name ) ) )
+				{
+					PlayGoalStream( Script::GenerateCRC( stream_name ), use_pos_info );
+					// current_vo_stream = Pcm::PlayStream( Script::GenerateCRC( stream_name ), 100, 100 );
+					break;
+				}
+			}
+		}
+		else
+		{
+			sprintf( stream_name, "%s_%s%02i", p_speaker_name, p_stream_type, stream_number );
+			// printf("figured vo: %s\n", stream_name);
+			if ( Pcm::StreamExists( Script::GenerateCRC( stream_name ) ) )
+			{
+				PlayGoalStream( Script::GenerateCRC( stream_name ), use_pos_info );
+				// current_vo_stream = Pcm::PlayStream( Script::GenerateCRC( stream_name ), 100, 100 );
+				break;
+			}
+		}
+	}
+	
+	Script::CleanUpArray( p_stream_numbers );
+	delete p_stream_numbers;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CGoalPed::StopCurrentStream()
+{
+	// get params
+	Script::CStruct* pParams = GetGoalParams();
+
+	uint32 current_vo_stream = 0;
+	if ( pParams->GetChecksum( CRCD(0xdc14d454,"current_vo_stream"), ¤t_vo_stream, Script::NO_ASSERT ) )
+	{
+		pParams->RemoveComponent( CRCD(0xdc14d454,"current_vo_stream") );
+		if ( current_vo_stream )
+			Pcm::StopStreamFromID( current_vo_stream );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CGoalPed::ProIsCurrentSkater()
+{
+	// get params
+	Script::CStruct* pParams = GetGoalParams();
+	
+	// are they using the right skater?
+	Script::CStruct* p_last_names = Script::GetStructure( CRCD(0x621d1828,"goal_pro_last_name_checksums"), Script::ASSERT );
+	const char* first_name_string;
+	pParams->GetString( CRCD(0x4a91eceb,"pro_challenge_pro_name"), &first_name_string, Script::ASSERT );
+	uint32 last_name;
+	p_last_names->GetChecksum( Script::GenerateCRC( first_name_string ), &last_name, Script::ASSERT );
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CPlayerProfileManager* pProfileManager = skate_mod->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile = pProfileManager->GetCurrentProfile();
+	uint32 current_skater = pSkaterProfile->GetSkaterNameChecksum();
+
+	return ( current_skater == last_name );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CGoalPed::GetGoalAnimations( const char* type )
+{
+	// get params
+	Script::CStruct* pParams = GetGoalParams();
+	
+	uint32 array_name = 0;
+	
+	// check for specific goal animation
+	char goal_specific_array_name[128];
+	const char* p_goal_name;
+	pParams->GetString( CRCD(0xbfecc45b,"goal_name"), &p_goal_name, Script::ASSERT );
+	sprintf( goal_specific_array_name, "%s_%s", p_goal_name, type );
+	if ( ( Script::GetArray( goal_specific_array_name, Script::NO_ASSERT ) ) )
+	{
+		// printf("got a specific array name of %s\n", p_goal_specific_array_name );
+		array_name = Script::GenerateCRC( goal_specific_array_name );
+	}
+	else if ( type )
+	{
+		// build the name of the pro's generic anims array
+		const char* p_pro_name = GetProFirstName();
+		char pro_array_name[128];
+		sprintf( pro_array_name, "%s_goal_%s", p_pro_name, type );
+
+		if ( ( Script::GetArray( pro_array_name, Script::NO_ASSERT ) ) )
+		{
+			// pro's generic array
+			// printf("got generic pro array: %s\n", pro_array_name );
+			array_name = Script::GenerateCRC( pro_array_name );
+		}
+		else
+		{
+			// get the generic anim array
+			char generic_array_name[128];
+			sprintf( generic_array_name, "generic_pro_anims_%s", type );
+			// printf("looking for array: %s\n", generic_array_name);
+			if ( !Script::GetArray( generic_array_name, Script::NO_ASSERT ) )
+			{
+				 Dbg_MsgAssert( 0, ("Unable to find generic pro array '%s'",generic_array_name) );
+			}
+			array_name = Script::GenerateCRC( generic_array_name );
+		}
+	}
+	return array_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::DestroyGoalPed()
+{
+	// get params
+	Script::CStruct* pParams = GetGoalParams();
+	
+	uint32 trigger_obj_id = 0;
+
+	pParams->GetChecksum( CRCD(0x02d7e03d,"trigger_obj_id"), &trigger_obj_id );
+	if( trigger_obj_id )
+	{
+		Obj::CCompositeObject* p_pos_obj = NULL;
+		//p_pos_obj = Obj::CMovingObject::m_hash_table.GetItem( trigger_obj_id );
+		p_pos_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( trigger_obj_id);
+		
+		if( p_pos_obj )
+		{
+			delete p_pos_obj;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::SuspendGoalLogic( bool suspend )
+{
+	m_goalLogicSuspended = suspend;
+
+	if ( suspend )
+	{
+		Script::RunScript( CRCD(0xbf67a3cf,"goal_pro_stop_anim_scripts"), GetGoalParams() );
+		StopCurrentStream();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CGoalPed::Hide( bool hide )
+{
+	// get params
+	Script::CStruct* pParams = GetGoalParams();
+	
+	if ( pParams->ContainsFlag( CRCD( 0x3d1cab0b, "null_goal" ) ) )
+	{
+		return;
+	}
+	
+	uint32 trigger_obj_id = 0;
+
+	pParams->GetChecksum( CRCD(0x02d7e03d,"trigger_obj_id"), &trigger_obj_id );
+	if ( trigger_obj_id )
+	{
+		Obj::CCompositeObject* p_pos_obj = NULL;
+		//p_pos_obj = Obj::CMovingObject::m_hash_table.GetItem( trigger_obj_id );
+		p_pos_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID( trigger_obj_id );
+		
+		if ( p_pos_obj )
+		{
+			p_pos_obj->Hide( hide );
+		}
+
+		// kill/create arrow
+		Script::CStruct* pTempParams = new Script::CStruct();
+		pTempParams->AddChecksum( CRCD(0x9982e501,"goal_id"), m_goalId );
+		if ( hide )
+			Script::RunScript( CRCD(0x6d39f2f3,"goal_ped_kill_arrow"), pTempParams );
+		else
+		{
+			// check if this goal has been beaten...hack for mike v special goal
+			CGoalManager* pGoalManager = GetGoalManager();
+			Dbg_Assert( pGoalManager );
+			CGoal* pGoal = pGoalManager->GetGoal( m_goalId, false );
+			bool should_add_arrow = true;
+			if ( pGoal && pGoal->HasWonGoal() )
+				should_add_arrow = false;
+			
+			if ( should_add_arrow )
+				Script::RunScript( CRCD(0x6ea4d309,"goal_add_ped_arrow"), pTempParams );
+		}
+		delete pTempParams;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CGoalPed::GetGoalParams()
+{
+	CGoalManager* pGoalManager = Game::GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	return pGoalManager->GetGoalParams( m_goalId );
+}
+
+}	// namespace Game
diff --git a/Code/Sk/Modules/Skate/GoalPed.h b/Code/Sk/Modules/Skate/GoalPed.h
new file mode 100644
index 0000000..3a468d6
--- /dev/null
+++ b/Code/Sk/Modules/Skate/GoalPed.h
@@ -0,0 +1,61 @@
+// goal ped!
+
+#ifndef __MODULES_SKATE_GOALPED_H
+#define __MODULES_SKATE_GOALPED_H
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Game
+{
+
+const uint32	MAX_STREAM_NAME_LENGTH = 256;
+	
+class CGoalPed
+{
+public:
+	CGoalPed( Script::CStruct* pParams );
+	~CGoalPed();
+	
+	void				DestroyGoalPed();
+	
+	void		 		GetPlayerFirstName( char* p_first_name, int buffer_size );
+	const char*			GetProFirstName();
+	bool				GetStreamChecksum( uint32* p_streamChecksum, int cam_anim_index = -1, bool success = false, const char* p_speaker_name = NULL, bool last_anim = false);
+	void				LoadFam( uint32 stream_checksum, uint32 reference_checksum );
+	void				UnloadLastFam();
+	void				StopLastStream();
+	void				PlayGoalStream( uint32 stream_checksum, bool use_pos_info = true, uint32 speaker_obj_id = 0, bool should_load_fam = false );
+	void				PlayGoalStream( const char* type, Script::CStruct* pParams );
+	void				PlayGoalStartStream( Script::CStruct* pParams );
+	void				StopCurrentStream();
+	void				PlayGoalProStream( const char* p_stream_type, int max_number_streams, bool use_player_name, bool use_pos_info = true );
+	void				PlayGoalWinStream( Script::CStruct* pParams );
+	void				PlayGoalWaitStream();
+	void				PlayGoalMidStream();
+
+	uint32				GetGoalAnimations( const char* type );
+
+	bool				ProIsCurrentSkater();
+
+	void				SuspendGoalLogic( bool suspend = true );
+
+	Script::CStruct*	GetGoalParams();
+
+	void				Hide( bool hide );
+protected:
+	uint32				m_goalId;
+	bool				m_goalLogicSuspended;
+	uint32				m_lastFam;
+	uint32				m_lastFamObj;
+	uint32				m_lastStreamId;
+};
+
+
+} // namespace Game
+
+#endif // __MODULES_SKATE_GOALPED_H
diff --git a/Code/Sk/Modules/Skate/HorseGoal.cpp b/Code/Sk/Modules/Skate/HorseGoal.cpp
new file mode 100644
index 0000000..9a58d5c
--- /dev/null
+++ b/Code/Sk/Modules/Skate/HorseGoal.cpp
@@ -0,0 +1,135 @@
+// horse goal subclass
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CHorseGoal::CHorseGoal( Script::CStruct* pParams ) : CGoal( pParams )
+{
+	// nothing special
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CHorseGoal::~CHorseGoal()
+{
+	// nothing yet
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CHorseGoal::CheckScore()
+{
+	// get the right flag
+	Script::CArray* pHorseSpots;
+	mp_params->GetArray( "horse_spots", &pHorseSpots, Script::ASSERT );
+
+	int size = pHorseSpots->GetSize();
+	int i;
+	int score;
+	uint32 flag;
+	for ( i = 0; i < size; i++ )
+	{
+		Script::CStruct* pSpot = pHorseSpots->GetStructure( i );
+		pSpot->GetChecksum( "flag", &flag, Script::ASSERT );
+		int flag_value;
+		mp_goalFlags->GetInteger( flag, &flag_value, Script::ASSERT );
+		
+		// find the first unset flag
+		if ( flag_value == 0 )
+		{
+			if ( !pSpot->GetInteger( "score", &score, Script::NO_ASSERT ) )
+				mp_params->GetInteger( "score", &score, Script::ASSERT );
+			break;
+		}
+	}
+
+	// make sure they're not all set...
+	if ( i == size )
+		return;
+
+	// check the score
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+	Mdl::Score* pScore = pSkater->GetScoreObject();
+
+	int last_score_landed = pScore->GetLastScoreLanded();
+	if ( last_score_landed > 0 )
+	{
+		uint32 goalId = GetGoalId();
+		CGoalManager* pGoalManager = GetGoalManager();
+		if ( last_score_landed > score )
+		{			
+			pGoalManager->SetGoalFlag( goalId, flag, 1 );
+			pGoalManager->NextTourSpot( goalId );
+		}
+		else
+			pGoalManager->LoseGoal( goalId );
+	}
+	mp_params->AddInteger( "should_check_trick", 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CHorseGoal::ShouldExpire()
+{
+	int should_check_trick = 0;
+	mp_params->GetInteger( "should_check_trick", &should_check_trick, Script::ASSERT );
+
+	if ( should_check_trick == 1 )
+		return false;
+	
+	return CGoal::ShouldExpire();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CHorseGoal::IsExpired()
+{
+	int should_check_trick = 0;
+	mp_params->GetInteger( "should_check_trick", &should_check_trick, Script::ASSERT );
+
+	if ( should_check_trick == 1 )
+		return false;
+	
+	return CGoal::IsExpired();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void CHorseGoal::Expire()
+{
+	CGoal::Expire();
+}
+
+}	// namespace Game
diff --git a/Code/Sk/Modules/Skate/HorseGoal.h b/Code/Sk/Modules/Skate/HorseGoal.h
new file mode 100644
index 0000000..fd709e3
--- /dev/null
+++ b/Code/Sk/Modules/Skate/HorseGoal.h
@@ -0,0 +1,30 @@
+// horse goal subclass
+#ifndef __SK_MODULES_SKATE_HORSEGOAL_H__
+#define __SK_MODULES_SKATE_HORSEGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+
+class CHorseGoal : public CGoal
+{
+
+public:
+					CHorseGoal( Script::CStruct* pParams );
+	virtual			~CHorseGoal();
+	
+	void			CheckScore();
+	bool			ShouldExpire();
+	bool			IsExpired();
+	void			Expire();
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Modules/Skate/Minigame.cpp b/Code/Sk/Modules/Skate/Minigame.cpp
new file mode 100644
index 0000000..32e4f03
--- /dev/null
+++ b/Code/Sk/Modules/Skate/Minigame.cpp
@@ -0,0 +1,269 @@
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include        // for minigame timer
+#include 
+#include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CMinigame::CMinigame( Script::CStruct* pParams ) : CGoal( pParams )
+{
+	Script::CArray *pGeomArray;
+
+	// Create the associated pieces of geometry
+    if ( pParams->GetArray( "create_geometry", &pGeomArray ) )
+    {
+		uint32 i, geometry;
+
+		for( i = 0; i < pGeomArray->GetSize(); i++ )
+        {
+            geometry = pGeomArray->GetChecksum( i );
+			CFuncs::ScriptCreateFromNodeIndex( SkateScript::FindNamedNode( geometry ));
+        }
+	}
+	
+	m_record = 0;
+	m_cashLimit = 0;
+	pParams->GetInteger( "cash_limit", &m_cashLimit, Script::NO_ASSERT );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+CMinigame::~CMinigame()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CMinigame::Activate()
+{
+	if ( !IsActive() && ShouldUseTimer() )
+	{
+		CGoalManager* pGoalManager = Game::GetGoalManager();
+		Dbg_Assert( pGoalManager );
+		pGoalManager->DeactivateMinigamesWithTimer();
+	}
+	return CGoal::Activate();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CMinigame::Update()
+{
+	if ( IsActive() && ShouldUseTimer() )
+	{
+		UpdateMinigameTimer();
+	}
+	return CGoal::Update();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CMinigame::ShouldUseTimer()
+{
+	return ( mp_params->ContainsComponentNamed( "time" ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+// this is used for minigames that have their own timer.
+void CMinigame::UpdateMinigameTimer()
+{
+	return;
+/*	
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+
+	Tmr::Time time_left = m_timeLeft / 1000;		
+	if( time_left < 1 )
+		time_left = 0;
+	
+	int seconds = time_left % 60;
+	int minutes = time_left / 60;
+		
+	Front::CScreenElementPtr p_element = p_screen_elem_man->GetElement( CRCD( 0xcd9671b4, "minigame_timer") );
+	if (p_element)
+	{
+		Dbg_MsgAssert(p_element->GetType() == CRCD(0x5200dfb6, "TextElement"), ("type is 0x%x", p_element->GetType()));
+		Front::CTextElement *p_time_element = (Front::CTextElement *) p_element.Convert();
+	
+		char time_text[128];
+		sprintf(time_text, "%2d:%.2d", minutes, seconds);
+		
+		// get the description if there is one
+		const char* timer_description;
+		if ( mp_params->GetString( CRCD( 0xe1fc4f74, "timer_description" ), &timer_description, Script::NO_ASSERT ) )
+		{
+			strcat( time_text, " " );
+			strcat( time_text, timer_description );
+		}
+		p_time_element->SetText(time_text);
+	}
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CMinigame::IsExpired()
+{
+	return ( ShouldUseTimer() && ( (int)m_timeLeft <= 0 ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CMinigame::CheckRecord( int value )
+{
+	if ( value > m_record )
+	{
+		m_record = value;
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CMinigame::AddTime( int amount )
+{
+	if ( IsActive() )
+	{
+		m_timeLeft += (Tmr::Time)( amount * 1000 );
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+int CMinigame::GetRecord()
+{
+	mp_params->AddInteger( "minigame_record", m_record );
+	return m_record;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CMinigame::CanRetry()
+{
+	// you cannot retry minigames - ie, they don't show up 
+	// on the pause menu as "Retry last goal"
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMinigame::LoadSaveData( Script::CStruct*  pFlags )
+{
+	// minigame record
+	int minigame_record;
+	pFlags->GetInteger( "minigame_record", &minigame_record, Script::NO_ASSERT );
+	if ( minigame_record != -1 )
+	{
+		// this will check and set the record
+		CheckMinigameRecord( minigame_record );
+	}
+
+	int cash_limit;
+	if ( pFlags->GetInteger( "cash_limit", &cash_limit, Script::NO_ASSERT ) )
+		m_cashLimit = cash_limit;
+	CGoal::LoadSaveData( pFlags );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMinigame::GetSaveData( Script::CStruct* pFlags )
+{
+	int minigame_record = GetMinigameRecord();
+	pFlags->AddInteger( "minigame_record", minigame_record );
+	pFlags->AddInteger( "cash_limit", m_cashLimit );
+	CGoal::GetSaveData( pFlags );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMinigame::AwardCash( int amount )
+{
+	if ( m_cashLimit <= 0 )
+	{
+		Script::RunScript( "minigame_cash_depleted" );
+		return false;
+	}
+
+	// clamp it
+	if ( amount > m_cashLimit )
+		amount = m_cashLimit;
+
+	CGoalManager* pGoalManager = GetGoalManager();
+	Dbg_Assert( pGoalManager );
+	if ( pGoalManager )
+	{
+		pGoalManager->AddCash( amount );
+		m_cashLimit -= amount;
+		Script::CStruct* pScriptParams = new Script::CStruct();
+		pScriptParams->AddInteger( "amount", amount );
+		Script::RunScript( "minigame_got_cash", pScriptParams );
+		delete pScriptParams;
+		return true;
+	}
+	return false;
+}
+
+}
diff --git a/Code/Sk/Modules/Skate/Minigame.h b/Code/Sk/Modules/Skate/Minigame.h
new file mode 100644
index 0000000..dec956b
--- /dev/null
+++ b/Code/Sk/Modules/Skate/Minigame.h
@@ -0,0 +1,48 @@
+#ifndef __SK_MODULES_SKATE_MINIGAME_H__
+#define __SK_MODULES_SKATE_MINIGAME_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+
+class CMinigame : public CGoal
+{
+
+public:						
+					CMinigame( Script::CStruct* pParams );
+	virtual			~CMinigame();
+
+	bool			Activate();
+
+	void			LoadSaveData( Script::CStruct* pFlags );
+	void			GetSaveData( Script::CStruct* pFlags );
+	
+	bool			ShouldUseTimer();
+	bool			AddTime( int amount );
+	bool			CountAsActive() { return false; }
+	bool			IsExpired();
+	bool			CheckRecord( int value );
+	int				GetRecord();
+	bool			CanRetry();
+	void			SetStartTime();
+	void			UpdateTimer();
+
+	bool			Update();
+	void			UpdateMinigameTimer();
+
+	bool			AwardCash( int amount );
+
+protected:
+	int				m_record;
+	int				m_cashLimit;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Modules/Skate/NetGoal.cpp b/Code/Sk/Modules/Skate/NetGoal.cpp
new file mode 100644
index 0000000..5ef3eba
--- /dev/null
+++ b/Code/Sk/Modules/Skate/NetGoal.cpp
@@ -0,0 +1,163 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+namespace Game
+{
+
+CNetGoal::CNetGoal( Script::CStruct* pParams ) : CGoal( pParams )
+{
+	m_endRunType = vENDOFRUN;
+	m_initialized = false;
+}
+
+CNetGoal::~CNetGoal()
+{
+	// nothing yet
+}
+
+bool CNetGoal::Update()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if ( !m_initialized )
+	{
+		Init();
+		m_initialized = true;
+	}
+
+	if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" ))
+	{
+		if( GoalAttackComplete())
+		{
+			if( IsActive())
+			{
+				Expire();
+
+				return true;
+			}
+		}
+	}
+
+	return CGoal::Update();
+}
+
+bool CNetGoal::Activate( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	gamenet_man->ClearGameOver();
+
+	return CGoal::Activate();
+}
+
+bool CNetGoal::Deactivate( bool force, bool affect_tree )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	if( force == false )
+	{
+		if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" ))
+		{
+			return false;
+		}
+	}
+
+	return CGoal::Deactivate( force, affect_tree );
+}
+
+bool CNetGoal::IsExpired()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" ))
+	{
+		return GoalAttackComplete() && AllSkatersAtEndOfRun();
+	}
+	else
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		GameNet::PlayerInfo* player;
+		Lst::Search< GameNet::PlayerInfo > sh;
+	
+		// If no players are left, the goal should expire
+		if( gamenet_man->GetNumPlayers() == 0 )
+		{
+			//Dbg_Printf( "IsExpired: 1\n" );
+			return true;
+		}
+						
+		// If it's an unlimited game, don't expire the goal
+		if ( m_unlimitedTime )
+		{
+			//Dbg_Printf( "IsExpired: 2\n" );
+			return false;
+		}
+		
+		if( ( gamenet_man->HaveReceivedFinalScores() == false ) && 
+			( gamenet_man->InNetGame()))
+		{
+			//Dbg_Printf( "IsExpired: 3\n" );
+			return false;
+		}
+
+		// if we're not supposed to use end of run, we need only check
+		// the time
+		if ( m_endRunType == vNONE )
+		{
+			//Dbg_Printf( "Timeleft: %d\n", (int) m_timeLeft );
+			return ( (int)m_timeLeft <= 0 );
+		}
+	
+		// check if the skaters have reached end of run BEFORE
+		// setting the exception, or you'll never catch it!
+		if ( AllSkatersAtEndOfRun() && (int)m_timeLeft <= 0 )
+		{
+			//Dbg_Printf( "IsExpired: 4\n" );
+			return true;
+		}
+	
+		// reset all players' scores
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			Obj::CSkater *p_Skater = player->m_Skater;
+			Dbg_Assert( p_Skater );
+
+			Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(p_Skater);
+			Dbg_Assert( p_skater_endrun_component );
+
+			if( p_skater_endrun_component->RunHasEnded() || p_Skater->IsPaused())
+			{
+				continue;
+			}
+			// if time is up, set the end of run exception
+			// if ( m_shouldEndRun && !m_endRunCalled && (int)m_timeLeft <= 0 )
+			if ( m_endRunType != vNONE && (int)m_timeLeft <= 0 )
+			{
+				m_endRunCalled = true;
+				//Dbg_Printf( "EXPIRING GOAL: %d\n", GetGoalId());
+				// printf("calling EndRun\n");
+				p_skater_endrun_component->EndRun();
+			}
+		}
+
+		//Dbg_Printf( "IsExpired: 6 : %d\n", gamenet_man->GameIsOver());
+		return gamenet_man->GameIsOver();
+
+	}
+}
+
+void	CNetGoal::Expire( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	gamenet_man->MarkGameOver();
+	return CGoal::Expire();
+}
+
+}	// namespace Game
diff --git a/Code/Sk/Modules/Skate/NetGoal.h b/Code/Sk/Modules/Skate/NetGoal.h
new file mode 100644
index 0000000..428a4a8
--- /dev/null
+++ b/Code/Sk/Modules/Skate/NetGoal.h
@@ -0,0 +1,33 @@
+#ifndef __SK_MODULES_SKATE_NETGOAL_H__
+#define __SK_MODULES_SKATE_NETGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+
+class CNetGoal : public CGoal
+{
+
+public:
+					CNetGoal( Script::CStruct* pParams );
+	virtual			~CNetGoal();
+	
+	bool			Update();
+	bool			Deactivate( bool force = false, bool affect_tree = true );
+	bool			Activate();
+	bool			IsExpired();
+	void			Expire();
+
+protected:
+	bool			m_initialized;
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Modules/Skate/RaceGoal.cpp b/Code/Sk/Modules/Skate/RaceGoal.cpp
new file mode 100644
index 0000000..276e90f
--- /dev/null
+++ b/Code/Sk/Modules/Skate/RaceGoal.cpp
@@ -0,0 +1,113 @@
+#include 
+#include 
+
+namespace Game
+{
+
+
+CRaceGoal::CRaceGoal( Script::CStruct* pParams ) : CGoal( pParams )
+{
+	// nothing yet
+}
+
+CRaceGoal::~CRaceGoal()
+{
+	// nope
+}
+
+bool CRaceGoal::ShouldExpire()
+{
+	if ( !m_unlimitedTime && (int)m_timeLeft <= 0)
+	{
+		return true;
+	}
+	return false;
+}
+
+bool CRaceGoal::IsExpired()
+{
+	if ( !m_unlimitedTime && (int)m_timeLeft <= 0)
+	{
+		CGoal::IsExpired();
+		return true;
+	}
+	return false;
+}
+
+void CRaceGoal::Expire()
+{
+	RunCallbackScript( vEXPIRED );
+	Deactivate();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+bool CRaceGoal::NextRaceWaypoint( uint32 goalId )
+{
+    if ( IsExpired() )
+	{
+		printf("expired!\n");
+		return false;
+	}
+	Script::CArray* p_waypoints;
+    mp_params->GetArray( "race_waypoints", &p_waypoints, Script::ASSERT );
+
+    Script::CStruct* p_waypoint;
+    for ( uint32 i = 0; i < p_waypoints->GetSize(); i++ )
+    {
+        p_waypoint = p_waypoints->GetStructure( i );
+
+        // is this waypoint done?
+        uint32 flagId;
+        p_waypoint->GetChecksum( "flag", &flagId, Script::ASSERT );
+        if ( GoalFlagEquals( flagId, 1 ) )
+        {
+            continue;
+        }
+        
+		if ( m_flagsSet == 0)
+		{
+			m_timeLeft = 0;
+			p_waypoint->AddInteger( "first_waypoint", 1 );
+		}
+        
+		// add the goalId to the params
+        p_waypoint->AddChecksum( "goal_id", goalId );
+
+		// add quick start flag
+		if ( mp_params->ContainsFlag( CRCD(0x38221df4,"quick_start") ) )
+			p_waypoint->AddChecksum( NONAME, CRCD(0x38221df4,"quick_start") );
+		else
+			p_waypoint->RemoveFlag( CRCD(0x38221df4,"quick_start") );
+
+		#ifdef __NOPT_ASSERT__
+        Script::CScript *p_script=Script::SpawnScript( "goal_race_next_waypoint", p_waypoint);
+		p_script->SetCommentString("Spawned from CRaceGoal::NextRaceWaypoint");
+		#else
+        Script::SpawnScript( "goal_race_next_waypoint", p_waypoint);
+		#endif
+
+		// set the timer
+		if ( !m_unlimitedTime )
+		{
+			int time;
+			if ( !p_waypoint->GetInteger( "time", &time, Script::NO_ASSERT ) )
+			{
+				Dbg_MsgAssert( 0, ("Each race waypoint must have a 'time' param") );
+			}
+			CGoal::AddTime( time );
+		}
+
+/*        uint32 waypoint_script;
+        if ( p_waypoint->GetChecksum( "scr", &waypoint_script, Script::NO_ASSERT ) )
+            Script::SpawnScript( waypoint_script );
+*/
+        return true;
+    }
+    return true;
+}
+
+}
diff --git a/Code/Sk/Modules/Skate/RaceGoal.h b/Code/Sk/Modules/Skate/RaceGoal.h
new file mode 100644
index 0000000..0832db5
--- /dev/null
+++ b/Code/Sk/Modules/Skate/RaceGoal.h
@@ -0,0 +1,29 @@
+#ifndef __SK_MODULES_SKATE_RACEGOAL_H__
+#define __SK_MODULES_SKATE_RACEGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+class CRaceGoal : public CGoal
+{
+	friend class CGoalManager;
+
+public:
+					CRaceGoal( Script::CStruct* pParams );
+	virtual			~CRaceGoal();
+
+	bool			ShouldExpire();
+	bool			IsExpired();
+	void			Expire();
+	bool			NextRaceWaypoint( uint32 goalId );
+};
+
+}
+
+#endif
diff --git a/Code/Sk/Modules/Skate/SkatetrisGoal.cpp b/Code/Sk/Modules/Skate/SkatetrisGoal.cpp
new file mode 100644
index 0000000..c038b36
--- /dev/null
+++ b/Code/Sk/Modules/Skate/SkatetrisGoal.cpp
@@ -0,0 +1,1134 @@
+// skatetris goal
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include        // for tetris tricks
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Game
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkatetrisGoal::CSkatetrisGoal( Script::CStruct* pParams ) : CGoal( pParams )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkatetrisGoal::~CSkatetrisGoal()
+{
+//	delete[] m_tetrisTricks;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::Activate()
+{
+	if ( !IsActive() )
+	{
+		StartTetrisGoal();
+		return CGoal::Activate();
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::Deactivate( bool force, bool affect_tree )
+{
+	// printf("CSkatetrisGoal::Deactivate\n");
+	EndTetrisGoal();
+	return CGoal::Deactivate( force, affect_tree );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::Win()
+{
+	// the win function is called after time runs out, 
+	// so deactivate must always be called
+
+	// K: TT12058, Deactivate may remove the testing_goal flag, so store it and put it back in
+	// afterwards if necessary. (The flag is required by the call to CGoal::Win below)
+	bool testing_goal=mp_params->ContainsFlag(CRCD(0x54d3cac1,"testing_goal"));
+	
+	mp_params->AddChecksum( NONAME, CRCD( 0xc309cad1, "just_won_goal" ) );
+	Deactivate();
+	mp_params->RemoveFlag( CRCD( 0xc309cad1, "just_won_goal" ) );
+	
+	if (testing_goal)
+	{
+		mp_params->AddChecksum(NONAME,CRCD(0x54d3cac1,"testing_goal"));
+	}
+		
+	/*if( gamenet_man->InNetGame())
+	{
+		CGoalManager* pGoalManager = GetGoalManager();
+        Dbg_Assert( pGoalManager );
+        return pGoalManager->WinGoal( GetGoalId());
+	}
+	else*/
+	{
+		bool ret_val=CGoal::Win();
+		// Make sure the testing_goal flag is definitely gone.
+		mp_params->RemoveFlag(CRCD(0x54d3cac1,"testing_goal"));
+		return ret_val;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::Update()
+{
+	int wait_to_add_tricks = 0;
+	mp_params->GetInteger( CRCD(0x7824963c,"wait_to_add_tricks"), &wait_to_add_tricks, Script::NO_ASSERT );
+	if ( wait_to_add_tricks )
+	{
+		// bail out...
+		return CGoal::Update();
+	}
+
+	int minTimeLeft = 1;
+	if ( !IsSingleComboSkatetris() )
+	{
+		if ( mp_params->GetInteger( CRCD(0xa6d96603,"time_to_stop_adding_tricks"), &minTimeLeft, Script::NO_ASSERT ) )
+			minTimeLeft *= 1000;
+	}
+	
+	// check for win conditions
+	if ( IsTricktris() )
+	{
+		if ( IsActive() && !IsPaused() )
+		{
+			// tricktris is won when you've completed the pre-determined number
+			// of tricks
+			int tricks_completed = 0;
+			mp_params->GetInteger( CRCD(0x8d958f01,"tricks_completed"), &tricks_completed, Script::NO_ASSERT );
+			int total_to_win;
+			mp_params->GetInteger( CRCD(0xbfd3a831,"tricktris_total_to_win"), &total_to_win, Script::ASSERT );
+			// printf("tricks_completed = %i, total_to_win = %i\n", tricks_completed, total_to_win);
+			if ( tricks_completed >= total_to_win )
+			{
+				CGoalManager* pGoalManager = GetGoalManager();
+				pGoalManager->WinGoal( GetGoalId() );
+				return true;			
+			}
+		}
+	}
+	else if ( IsActive() && (int)m_timeLeft < minTimeLeft && !IsPaused() && AllTricksCleared() )
+	{
+		// win the goal if they've cleared all tricks and there's no time to add more
+		CGoalManager* pGoalManager = GetGoalManager();
+		pGoalManager->WinGoal( GetGoalId() );
+		return true;
+	}
+	
+	// update faded tricks if we're active - even if the time has run out
+	if ( IsActive() && !IsPaused() )
+	{
+		UpdateFadedTricks();
+	}
+	
+	if ( IsTricktris() )
+	{
+		if ( ( m_unlimitedTime || (int)m_timeLeft > 0.0f ) && !IsPaused() )
+		{
+			int tricks_completed = 0;
+			mp_params->GetInteger( CRCD(0x8d958f01,"tricks_completed"), &tricks_completed, Script::NO_ASSERT );
+			int total_to_win;
+			mp_params->GetInteger( CRCD(0xbfd3a831,"tricktris_total_to_win"), &total_to_win, Script::ASSERT );
+	
+			// count number of valid entries and add if necessary
+			int total_in_stack = 0;
+			for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+			{
+				if ( m_tetrisTricks[i].invalid == false )
+					total_in_stack++;
+			}
+			int desired_stack_size;
+			mp_params->GetInteger( CRCD(0x45a375c9,"tricktris_block_size"), &desired_stack_size, Script::ASSERT );
+			if ( total_in_stack < desired_stack_size && total_in_stack + tricks_completed < total_to_win )
+			{
+				int num_to_add = desired_stack_size - total_in_stack;
+				if ( total_to_win - tricks_completed - total_in_stack < num_to_add )
+					num_to_add = total_to_win - tricks_completed - total_in_stack;
+				AddTetrisTrick( num_to_add );
+			}
+		}
+	}
+	else if ( IsActive() && ( m_unlimitedTime || (int)m_timeLeft >= minTimeLeft ) && !IsPaused() )
+	{
+		m_tetrisTime += (int)( Tmr::FrameLength() * 1000 );
+
+		if ( IsSingleComboSkatetris() )
+		{
+			// throw in a delay so the single combo doesn't appear right away
+			// during the goal intro movie
+			if ( !mp_params->ContainsFlag( CRCD(0xc93da683,"single_combo_added") ) && m_tetrisTime > 50 )
+			{
+				int combo_size;
+				mp_params->GetInteger( CRCD(0x67dd90ca,"combo_size"), &combo_size, Script::ASSERT );
+				AddTetrisTrick( combo_size );
+				mp_params->AddChecksum( NONAME, CRCD(0xc93da683,"single_combo_added") );
+			}
+		}
+		else
+		{
+			int trickTime;
+			mp_params->GetInteger( CRCD(0x648d8d2a,"adjusted_trick_time"), &trickTime, Script::ASSERT );
+			if ( m_tetrisTime > trickTime )
+			{
+				m_tetrisTime = 0;
+				if ( IsComboSkatetris() )
+				{
+					int combo_size;
+					mp_params->GetInteger( CRCD(0x67dd90ca,"combo_size"), &combo_size, Script::ASSERT );
+					AddTetrisTrick( combo_size );
+				}
+				else
+					AddTetrisTrick();
+			}
+		}
+	}
+	return CGoal::Update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::StartTetrisGoal()
+{
+    m_numTetrisTricks = 0;
+    m_tetrisTime = 0;
+    int original_time;
+    mp_params->GetInteger( CRCD( 0xded8540a, "trick_time" ), &original_time, Script::ASSERT );
+    mp_params->AddInteger( CRCD( 0x648d8d2a, "adjusted_trick_time" ), original_time );
+	mp_params->AddInteger( CRCD( 0x8d958f01, "tricks_completed" ), 0 );
+
+	// start almost out of time so a trick gets added right away
+	if ( IsSingleComboSkatetris() )
+		m_tetrisTime = 0;
+	else
+		m_tetrisTime = original_time - 200;
+    
+    for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+    {
+        m_tetrisTricks[i].invalid = true;
+        m_tetrisTricks[i].trickNameChecksum = 0;
+		m_tetrisTricks[i].altTrickNameChecksum = 0;
+		m_tetrisTricks[i].keyCombo = 0;
+		m_tetrisTricks[i].spin_mult = 0;
+		m_tetrisTricks[i].num_taps = 1;
+		m_tetrisTricks[i].require_perfect = false;
+
+		m_validGroups[i] = false;
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::EndTetrisGoal()
+{
+    m_numTetrisTricks = 0;
+    m_tetrisTime = 0;
+    
+    for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+    {
+        m_tetrisTricks[i].invalid = false;
+        m_tetrisTricks[i].trickNameChecksum = 0;
+		m_tetrisTricks[i].altTrickNameChecksum = 0;
+		m_tetrisTricks[i].keyCombo = 0;
+		m_tetrisTricks[i].spin_mult = 0;
+		m_tetrisTricks[i].num_taps = 1;
+		m_tetrisTricks[i].require_perfect = false;
+
+		m_validGroups[i] = false;
+    }
+
+	mp_params->RemoveFlag( CRCD(0xc93da683,"single_combo_added") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::AddTetrisTrick( int num_to_add )
+{
+    int max_tricks;
+	mp_params->GetInteger( CRCD(0x89473db7,"max_tricks"), &max_tricks, Script::ASSERT );
+	
+	int tricks_in_group = num_to_add;
+	int possible_num_tricks = m_numTetrisTricks + tricks_in_group;
+    
+	bool add_combo = false;
+	if ( IsComboSkatetris() || IsSingleComboSkatetris() )
+		add_combo = true;
+	
+/*	if ( add_combo )
+	{
+		mp_params->GetInteger( CRCD(0x67dd90ca,"combo_size"), &tricks_in_group, Script::ASSERT );
+		possible_num_tricks += tricks_in_group;
+	}
+	else
+	{
+		possible_num_tricks++;
+	}
+*/
+	
+	// check that they haven't lost
+	if ( possible_num_tricks > max_tricks )
+    {
+        uint32 goalId = GetGoalId();
+        Dbg_Assert( goalId );
+        CGoalManager* pGoalManager = GetGoalManager();
+        Dbg_Assert( pGoalManager );
+        pGoalManager->LoseGoal( goalId );
+    }
+    else 
+    {
+		// set the group id and increment number of trick groups
+		int group_id = 0;
+		if ( add_combo )
+		{
+			for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+			{
+				if ( !m_validGroups[i] )
+				{
+					group_id = i;
+					m_validGroups[group_id] = true;
+					break;
+				}
+			}
+			Dbg_Assert( !( group_id == Game::vMAXTETRISTRICKS ) );
+		}		
+		
+		// add as many tricks as we need (1 by default)
+		while ( tricks_in_group > 0 )
+		{
+			Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+			
+			// grab list of valid key combos
+			Script::CArray* p_keyCombos = NULL;
+			mp_params->GetArray( CRCD(0x7be1e689,"goal_tetris_key_combos"), &p_keyCombos, Script::NO_ASSERT );
+
+			uint32 key_combo = 0;
+			uint32 trick_name_checksum = 0;
+			char trick_text[Game::NEW_STRING_LENGTH];
+			
+			// find the first empty trick
+			int index = 0;
+
+			for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+			{
+				if ( m_tetrisTricks[i].invalid == true )
+				{
+					index = i;
+					m_tetrisTricks[index].invalid = false;
+					m_tetrisTricks[index].group_id = group_id;
+					m_tetrisTricks[index].keyCombo = 0;
+					m_tetrisTricks[index].trickNameChecksum = 0;
+					m_tetrisTricks[index].altTrickNameChecksum = 0;
+					m_tetrisTricks[index].spin_mult = 0;
+					m_tetrisTricks[index].num_taps = 1;
+					m_tetrisTricks[index].require_perfect = false;
+					break;
+				}
+			}
+			Dbg_Assert( !( index == Game::vMAXTETRISTRICKS ) );
+			
+			// check the type of the structure...this will tell us if this is a standard
+			// skatetris goal (array of key combos), or a special one (array of structures
+			// containing the name and text of the trick).
+			const char* p_key_combo_text = NULL;
+			int num_taps = 1;
+			if ( !p_keyCombos )
+			{
+				// check for a trick array
+				Script::CArray* p_tetris_tricks = NULL;
+				mp_params->GetArray( CRCD(0xeb79fb49,"goal_tetris_tricks"), &p_tetris_tricks, Script::ASSERT );
+				
+				// grab a random trick
+				// no safety checks, since we don't care if the trick is mapped
+				Script::CStruct* p_trick = p_tetris_tricks->GetStructure( Mth::Rnd( p_tetris_tricks->GetSize() ) );
+				const char* p_trick_display_text;
+				p_trick->GetString( CRCD(0x270f56e1,"trick"), &p_trick_display_text, Script::ASSERT );
+				
+				if ( p_trick->ContainsFlag( CRCD( 0x255ed86f, "grind" ) ) )
+				{
+					// store FS and BS versions of the trick
+					char grind_string[128];
+					strcpy( grind_string, "FS " );
+					strcat( grind_string, p_trick_display_text );
+					m_tetrisTricks[index].trickNameChecksum = Script::GenerateCRC( grind_string );
+
+					strcpy( grind_string, "BS " );
+					strcat( grind_string, p_trick_display_text );
+					m_tetrisTricks[index].altTrickNameChecksum = Script::GenerateCRC( grind_string );
+				}
+				else
+				{
+					m_tetrisTricks[index].trickNameChecksum = Script::GenerateCRC( p_trick_display_text );
+				}
+
+				const char* p_text;
+				p_trick->GetString( CRCD(0xc4745838,"text"), &p_text, Script::ASSERT );
+				strcpy( trick_text, p_text );
+
+				if ( !p_trick->GetChecksum( CRCD(0xacfdb27a,"key_combo"), &key_combo, Script::NO_ASSERT ) )
+					p_trick->GetString( CRCD(0xacfdb27a,"key_combo"), &p_key_combo_text, Script::NO_ASSERT );
+
+				// printf("setting trick checksum: %s\n", Script::FindChecksumName( trick_name_checksum ) );
+			}
+			else
+			{
+				// see if this is a valid key_combo
+				int key_combo_index = GetRandomIndexFromKeyCombos( p_keyCombos );
+				if ( key_combo_index == -1 )
+				{
+					// couldn't find one - the goal is going to be decativated.
+					return;
+				}
+				if ( p_keyCombos->GetType() == ESYMBOLTYPE_NAME )
+				{
+					key_combo = p_keyCombos->GetChecksum( key_combo_index );
+				}
+				else
+				{
+					Dbg_MsgAssert( p_keyCombos->GetType() == ESYMBOLTYPE_STRUCTURE, ( "goal_tetris_key_combos array has bad type" ) );
+					Script::CStruct* p_subStruct = p_keyCombos->GetStructure( key_combo_index );
+					p_subStruct->GetChecksum( CRCD(0xacfdb27a,"key_combo"), &key_combo, Script::ASSERT );
+					
+					int spin_mult = 0;
+					if ( p_subStruct->GetInteger( CRCD(0x8c8abd19,"random_spin"), &spin_mult, Script::NO_ASSERT ) )
+					{
+						// make sure it's a multiple of 180
+						Dbg_MsgAssert( spin_mult % 180 == 0, ( "Skatetris goal %s has bad spin_mult value %i", Script::FindChecksumName( GetGoalId() ), spin_mult ) );
+						if (spin_mult < 360)
+						{
+							spin_mult=360;
+						}
+						
+						// K: Make it so that spins of 180 are excluded, since they are too hard to do on vert.
+						// Ie:
+						// spin_mult=360 gives spins of 0, or 360
+						// spin_mult=540 gives spins of 0, 360 or 540
+						// spin_mult=720 gives spins of 0, 360, 540 or 720
+						// etc ...
+						m_tetrisTricks[index].spin_mult = Mth::Rnd(spin_mult / 180) + 1;
+						if (m_tetrisTricks[index].spin_mult == 1)
+						{
+							m_tetrisTricks[index].spin_mult=0;
+						}	
+					}
+					if ( p_subStruct->GetInteger( CRCD(0xedf5db70,"spin"), &spin_mult, Script::NO_ASSERT ) )
+					{
+						// make sure it's a multiple of 180
+						Dbg_MsgAssert( spin_mult % 180 == 0, ( "Skatetris goal %s has bad spin_mult value %i", Script::FindChecksumName( GetGoalId() ), spin_mult ) );
+						m_tetrisTricks[index].spin_mult = spin_mult / 180;
+					}
+
+					if ( p_subStruct->ContainsFlag( CRCD(0x1c39f1b9,"perfect") ) )
+						m_tetrisTricks[index].require_perfect = true;
+
+					if ( p_subStruct->GetInteger( CRCD(0xa4bee6a1,"num_taps"), &num_taps, Script::NO_ASSERT ) )
+						m_tetrisTricks[index].num_taps = num_taps;
+				}
+				
+				// get the global trick mapping
+				Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+				Obj::CSkaterProfile* p_SkaterProfile = skate_mod->GetCurrentProfile();
+				Script::CStruct* p_trickMappings = p_SkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+		
+				const char* p_text = NULL;
+				
+				// if it's not a regular assignment, grab cat info
+				if ( !p_trickMappings->GetChecksum( key_combo, &trick_name_checksum, Script::NO_ASSERT ) )
+				{
+					int cat_index;
+					p_trickMappings->GetInteger( key_combo, &cat_index, Script::ASSERT );
+					Dbg_Assert( cat_index >= 0 && cat_index < vMAX_CREATED_TRICKS );
+					Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+					if ( pSkater )
+					{
+						Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[cat_index];
+						pCreatedTrick->mp_other_params->GetString( CRCD( 0xa1dc81f9, "name" ), &p_text, Script::ASSERT );
+						
+						// mark it
+						// no cat tricks have double versions!
+						m_tetrisTricks[index].num_taps = 1;
+					}					
+				}
+	
+				if ( !p_text )
+				{
+					Script::CStruct* p_trick_structure = NULL;
+					p_trick_structure = Script::GetStructure( trick_name_checksum, Script::NO_ASSERT );
+					if ( !p_trick_structure )
+					{
+						Script::CArray* p_trick_array = Script::GetArray( trick_name_checksum, Script::ASSERT );
+						p_trick_structure = p_trick_array->GetStructure( 0 );
+		
+						Dbg_MsgAssert( p_trick_structure, ( "Could not find trick structure" ) );
+					}
+					Script::CStruct* p_trick_params;
+					p_trick_structure->GetStructure( CRCD(0x7031f10c,"Params"), &p_trick_params, Script::ASSERT );
+					
+					
+					// get any multiple tap info
+					while ( num_taps > 1 )
+					{
+						num_taps--;
+	
+						uint32 extra_trick;
+						if ( p_trick_params->GetChecksum( CRCD(0x6e855102,"ExtraTricks"), &extra_trick, Script::NO_ASSERT ) )
+						{
+							Script::CArray* p_extra_trick_array = Script::GetArray( extra_trick, Script::ASSERT );
+							Dbg_MsgAssert( p_extra_trick_array->GetType() == ESYMBOLTYPE_STRUCTURE, ( "ExtraTricks array %s has wrong type", Script::FindChecksumName( extra_trick ) ) );
+							Script::CStruct* p_extra_trick_struct = p_extra_trick_array->GetStructure( 0 );
+							p_extra_trick_struct->GetStructure( CRCD(0x7031f10c,"Params"), &p_trick_params, Script::ASSERT );
+						}
+						else
+						{
+							num_taps = 1;
+							m_tetrisTricks[index].num_taps = 1;
+						}
+					}
+					
+					p_trick_params->GetLocalText( CRCD(0xa1dc81f9,"Name"), &p_text, Script::ASSERT );
+				}
+
+				// build the text string
+				strcpy( trick_text, "" );
+
+				// spin value
+				if ( m_tetrisTricks[index].spin_mult != 0 )
+				{
+					// perfect modifier
+					if ( m_tetrisTricks[index].require_perfect )
+						sprintf( trick_text, "Perfect " );
+
+					sprintf( trick_text, "\\c5%i\\c0 ", ( m_tetrisTricks[index].spin_mult * 180 ) );
+				}
+				// trick text
+				strcat( trick_text, p_text );
+				
+				// store key combo
+				m_tetrisTricks[index].keyCombo = key_combo;
+			}
+			
+			// create the container for this element
+			Script::CStruct *p_container_params = new Script::CStruct();
+			Front::CContainerElement *p_container = new Front::CContainerElement();
+			uint32 container_id = Obj::CBaseManager::vNO_OBJECT_ID;
+			p_container->SetID( container_id );
+			p_screen_elem_man->RegisterObject( *p_container );
+
+			// figure the dims
+			int y_dim = 20;
+			if ( add_combo && tricks_in_group == 1 )
+			{
+				y_dim *= 2;
+			}
+			p_container_params->AddPair( CRCD(0x34a68574,"dims"), 100, y_dim );
+
+			p_container->SetProperties( p_container_params );
+			
+			// set the parent
+			Front::CScreenElement* p_vmenu = p_screen_elem_man->GetElement(CRCD(0xe35ec715,"tetris_tricks_menu"), Front::CScreenElementManager::ASSERT );
+			p_vmenu->SetChildLockState( Front::CScreenElement::UNLOCK );
+			p_screen_elem_man->SetParent( p_vmenu, p_container );
+
+			delete p_container_params;						
+			
+			// create text name element
+			Script::CStruct *p_elem_params = new Script::CStruct();
+			p_elem_params->AddString( CRCD(0xc4745838,"text"), trick_text );
+			p_elem_params->AddChecksum( CRCD(0x2f6bf72d,"font"), CRCD( 0xbc19c379, "newtrickfont" ) );
+			p_elem_params->AddChecksum( NONAME, CRCD( 0x1d944426, "not_focusable" ) );
+			uint32 id = Obj::CBaseManager::vNO_OBJECT_ID;
+	
+			Front::CTextElement* p_new_element = new Front::CTextElement();
+			p_new_element->SetID( id );
+			p_screen_elem_man->RegisterObject( *p_new_element );
+			
+			p_new_element->SetProperties( p_elem_params );
+	
+			// set the parent
+			p_screen_elem_man->SetParent( p_container, p_new_element );
+			
+			delete p_elem_params;
+			
+			
+			// create button combo element
+			if ( p_key_combo_text || key_combo )
+			{
+				Script::CStruct* p_button_combos;
+				if ( m_tetrisTricks[index].num_taps == 2 )
+					p_button_combos = Script::GetStructure( CRCD(0xe7abfb95,"goal_tetris_trick_text_double_tap"), Script::ASSERT );
+				else
+					p_button_combos = Script::GetStructure( CRCD(0x8856c817,"goal_tetris_trick_text"), Script::ASSERT );
+
+				if ( !p_key_combo_text && !p_button_combos->GetString( key_combo, &p_key_combo_text, Script::NO_ASSERT ) )
+				{
+					Dbg_MsgAssert( 0, ( "Could not find button text for skatetris trick" ) );
+				}
+
+				Script::CStruct *p_button_params = new Script::CStruct();
+				p_button_params->AddString( CRCD(0xc4745838,"text"), p_key_combo_text );
+				p_button_params->AddChecksum( CRCD(0x2f6bf72d,"font"), CRCD( 0x8aba15ec, "small" ) );
+				p_button_params->AddChecksum( NONAME, CRCD( 0x1d944426, "not_focusable" ) );
+				uint32 button_id = Obj::CBaseManager::vNO_OBJECT_ID;
+		
+				Front::CTextElement* p_button_element = new Front::CTextElement();
+				p_button_element->SetID( button_id );
+				p_screen_elem_man->RegisterObject( *p_button_element );
+				
+				p_button_element->SetProperties( p_button_params );
+				
+				// the button is a child of the container
+				p_screen_elem_man->SetParent( p_container, p_button_element );
+		
+				delete p_button_params;
+			}
+	
+			// store the id
+			m_tetrisTricks[index].screenElementId = p_container->GetID();
+			m_numTetrisTricks++;
+	
+			// run entrance animation
+			Script::CStruct* p_script_params = new Script::CStruct();
+			if ( !p_key_combo_text )
+				p_script_params->AddChecksum( NONAME, CRCD( 0x7b716096, "no_key_combo" ) );
+			uint32 script = CRCD(0x668cc9e3,"goal_tetris_add_trick");
+
+			if ( !IsTricktris() && !IsSingleComboSkatetris() && m_numTetrisTricks > ( .75 * (float)max_tricks ) )
+				script = CRCD(0x8e68afc2,"goal_tetris_add_red_trick");
+	
+			Script::CScript *p_new_script = Script::SpawnScript( script, p_script_params );
+			#ifdef __NOPT_ASSERT__
+			p_new_script->SetCommentString("Spawned from CSkatetrisGoal::AddTetrisTrick()");
+			#endif
+			p_new_script->mpObject = p_container;
+			// normally, script won't be updated until next frame -- we want it NOW, motherfucker
+			p_new_script->Update();
+			delete p_script_params;
+	
+			// turn the elements red if you're over 75% of the way to the top,
+			// but not if we're doing a single combo
+			if ( !IsTricktris() && !IsSingleComboSkatetris() && m_numTetrisTricks > ( .75 * (float)max_tricks ) )
+			{
+				mp_params->AddInteger( CRCD(0xd02ac196,"list_is_red"), 1 );
+				
+				for ( int j = 0; j < Game::vMAXTETRISTRICKS; j++ )
+				{
+					if ( !m_tetrisTricks[j].invalid )
+					{
+						Front::CScreenElement* p_trick = p_screen_elem_man->GetElement( m_tetrisTricks[j].screenElementId , Front::CScreenElementManager::ASSERT );
+						script = CRCD(0xeb016d1e,"goal_tetris_turn_trick_red");
+						Script::CScript* p_trick_script = Script::SpawnScript( script, NULL );
+						#ifdef __NOPT_ASSERT__
+						p_trick_script->SetCommentString("Spawned from CSkatetrisGoal::AddTetrisTrick()");
+						#endif
+						p_trick_script->mpObject = p_trick;
+						p_trick_script->Update();
+					}
+				}
+			}
+			tricks_in_group--;
+		}
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::CheckTetrisTricks()
+{
+	// special case trick flag...used for an additional condition that must be
+	// met before any skatetris tricks can be cleared.
+	// The flag is cleared each time the tricks are checked
+	int trick_flag;
+	if ( mp_params->GetInteger( CRCD(0x60b827d5,"trick_flag"), &trick_flag, Script::NO_ASSERT ) )
+	{
+		if ( trick_flag == 0 )
+			return;
+		else 
+			mp_params->AddInteger( CRCD(0x60b827d5,"trick_flag"), 0 );
+	}
+	
+	bool check_combo = false;
+	if ( IsSingleComboSkatetris() || IsComboSkatetris() )
+		check_combo = true;
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    Obj::CSkater *pSkater = skate_mod->GetLocalSkater();
+    Dbg_Assert( pSkater );
+  
+    Mdl::Score * p_score = ( pSkater->GetScoreObject() );
+    Dbg_Assert( p_score );
+    
+	// check for combos!
+	if ( check_combo )
+	{
+		CheckTetrisCombos();
+	}
+	else
+	{
+		// check for single tricks
+		bool trick_removed = false;
+		for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+		{
+			if ( !m_tetrisTricks[i].invalid )
+			{
+				if ( m_tetrisTricks[i].trickNameChecksum != 0 )
+				{
+					if ( p_score->GetCurrentNumberOfOccurrencesByName( m_tetrisTricks[i].trickNameChecksum, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect ) )
+					{
+						if ( !trick_removed )
+						{
+							Script::RunScript( "goal_tetris_play_trick_removed_sound" );
+						}
+						trick_removed = true;
+						RemoveTrick( i );
+					}
+					else if ( m_tetrisTricks[i].altTrickNameChecksum != 0 )
+					{
+						// check alternate trick name - usually BS version of grind trick
+						if ( p_score->GetCurrentNumberOfOccurrencesByName( m_tetrisTricks[i].altTrickNameChecksum, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect ) )
+						{
+							if ( !trick_removed )
+							{
+								Script::RunScript( "goal_tetris_play_trick_removed_sound" );
+							}
+							trick_removed = true;
+							RemoveTrick( i );							
+						}
+					}
+				}
+				else if ( m_tetrisTricks[i].keyCombo != 0 )
+				{
+					// printf( "checking for a key combo %s\n", Script::FindChecksumName( m_tetrisTricks[i].keyCombo ));
+					if ( p_score->GetCurrentNumberOfOccurrences( m_tetrisTricks[i].keyCombo, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect, m_tetrisTricks[i].num_taps ) )
+					{
+						if ( !trick_removed )
+							Script::RunScript( "goal_tetris_play_trick_removed_sound" );
+						trick_removed = true;
+						RemoveTrick( i );
+					}
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::CheckTetrisCombos()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    Obj::CSkater *p_Skater = skate_mod->GetLocalSkater();
+    Dbg_Assert( p_Skater );
+  
+    Mdl::Score * p_Score = ( p_Skater->GetScoreObject() );
+    Dbg_Assert( p_Score );
+    
+	int group_size;
+	mp_params->GetInteger( CRCD(0x67dd90ca,"combo_size"), &group_size, Script::ASSERT );
+	
+	for ( int group_id = 0; group_id < Game::vMAXTETRISTRICKS; group_id++ )
+	{
+		bool trick_removed = false;
+
+		// skip invalid groups
+		if ( !m_validGroups[group_id] )
+			continue;
+		
+		// see how many tricks they got out of this group
+		int group_total = 0;
+		for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+		{
+			if ( m_tetrisTricks[i].keyCombo != 0 )
+			{
+				if ( !m_tetrisTricks[i].invalid &&
+					 m_tetrisTricks[i].group_id == group_id &&
+					 p_Score->GetCurrentNumberOfOccurrences( m_tetrisTricks[i].keyCombo, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect, m_tetrisTricks[i].num_taps ) > 0
+				   )
+					group_total++;
+			}
+			else if ( m_tetrisTricks[i].trickNameChecksum != 0 )
+			{
+				if ( !m_tetrisTricks[i].invalid &&
+					 m_tetrisTricks[i].group_id == group_id &&
+					 p_Score->GetCurrentNumberOfOccurrencesByName( m_tetrisTricks[i].trickNameChecksum, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect, m_tetrisTricks[i].num_taps ) > 0
+				   )
+				{
+					group_total++;
+				}
+				else if ( m_tetrisTricks[i].altTrickNameChecksum != 0 )
+				{
+					// check alternate version of trick - usually BS version of grind
+					if ( !m_tetrisTricks[i].invalid &&
+						 m_tetrisTricks[i].group_id == group_id &&
+						 p_Score->GetCurrentNumberOfOccurrencesByName( m_tetrisTricks[i].altTrickNameChecksum, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect, m_tetrisTricks[i].num_taps ) > 0
+					   )
+						group_total++;					
+				}
+			}
+		}
+	
+		// remove this group?
+		if ( group_total >= group_size )
+		{
+			// kill all tricks in this group
+			for ( int j = 0; j < Game::vMAXTETRISTRICKS; j++ )
+			{
+				if ( !m_tetrisTricks[j].invalid && m_tetrisTricks[j].group_id == group_id )
+				{
+					if ( !trick_removed )
+						Script::RunScript( CRCD(0xe4ea743a,"goal_tetris_play_trick_removed_sound") );
+					trick_removed = true;
+
+					RemoveTrick( j );
+				}
+			}
+
+			// this group id is dead
+			m_validGroups[group_id] = false;
+
+			// was this a single combo goal?
+			if ( IsSingleComboSkatetris() )
+			{
+				Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+				pGoalManager->WinGoal( GetGoalId() );
+			}
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::RemoveTrick( int index )
+{	
+	int max_tricks;
+	mp_params->GetInteger( CRCD(0x89473db7,"max_tricks"), &max_tricks, Script::ASSERT );
+
+	// printf("found a completed trick at %i\n", i);
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	uint32 id = m_tetrisTricks[index].screenElementId;
+	Dbg_Assert( id );
+	Front::CScreenElementPtr p_trick = p_screen_elem_man->GetElement( id, Front::CScreenElementManager::ASSERT );
+
+	// kill any scripts running on this element so the animations don't conflict
+	Script::CStruct* p_temp_params = new Script::CStruct();
+	p_temp_params->AddChecksum( CRCD(0x40c698af,"id"), id );
+	Script::RunScript( CRCD(0x235bde21,"goal_tetris_reset_trick_container"), p_temp_params );
+	delete p_temp_params;
+	
+	uint32 script;
+	
+	// turn the elements back to white if you fell to under 75% full
+	int list_is_red = 0;
+	mp_params->GetInteger( CRCD(0xd02ac196,"list_is_red"), &list_is_red, Script::NO_ASSERT );
+	if ( ( list_is_red == 1 ) && ( ( m_numTetrisTricks - 1 ) < ( .75 * (float)max_tricks ) ) )
+	{
+		mp_params->AddInteger( CRCD(0xd02ac196,"list_is_red"), 0 );
+		for ( int j = 0; j < Game::vMAXTETRISTRICKS; j++ )
+		{
+			if ( !m_tetrisTricks[j].invalid )
+			{
+				Front::CScreenElement* p_trick = p_screen_elem_man->GetElement( m_tetrisTricks[j].screenElementId , Front::CScreenElementManager::ASSERT );
+				script = CRCD(0xa39b24e0,"goal_tetris_turn_trick_white");
+				Script::CScript* p_trick_script = Script::SpawnScript( script, NULL );
+				#ifdef __NOPT_ASSERT__
+				p_trick_script->SetCommentString("Spawned from CSkatetrisGoal::RemoveTrick()");
+				#endif
+
+				p_trick_script->mpObject = p_trick;
+				p_trick_script->Update();
+			}
+		}
+	}
+	
+	// remove the trick
+	script = CRCD(0x23a6a456,"goal_tetris_remove_trick");
+	Script::CStruct* pScriptParams = new Script::CStruct();
+	
+	// flag for removing tricktris tricks immediately
+	if ( IsTricktris() )
+		pScriptParams->AddChecksum( NONAME, CRCD(0xea7ba666,"tricktris") );
+	
+	Script::CScript *p_new_script = Script::SpawnScript( script, pScriptParams );
+
+	#ifdef __NOPT_ASSERT__
+	p_new_script->SetCommentString("Spawned from CSkatetrisGoal::RemoveTrick()");
+	#endif
+	p_new_script->mpObject = p_trick;
+	p_new_script->Update();
+	delete pScriptParams;
+	
+	// grab the number of tricks completed
+	int tricks_completed = 0;
+	mp_params->GetInteger( CRCD(0x8d958f01,"tricks_completed"), &tricks_completed, Script::NO_ASSERT );
+	tricks_completed++;
+	
+	// accelerate if needed
+	int acceleration_interval = 0;
+	mp_params->GetInteger( CRCD(0x1181b29,"acceleration_interval"), &acceleration_interval, Script::NO_ASSERT );
+	// printf("acceleration interval = %i, tricks completed = %i\n", acceleration_interval, tricks_completed );
+	if ( acceleration_interval && ( tricks_completed % acceleration_interval == 0 ) )
+	{
+		// adjust the time between new tricks
+		float acceleration_percent = 0;
+		mp_params->GetFloat( CRCD(0x76f65c8,"acceleration_percent"), &acceleration_percent, Script::NO_ASSERT );
+		int trick_time;
+		mp_params->GetInteger( CRCD(0x648d8d2a,"adjusted_trick_time"), &trick_time, Script::ASSERT );
+		trick_time = (int)( ( 1 - acceleration_percent ) * trick_time );
+		mp_params->AddInteger( CRCD(0x648d8d2a,"adjusted_trick_time"), trick_time );
+		// printf("setting trick time to %i\n", trick_time);
+	}
+	// store the number of tricks completed
+	// printf("%i tricks completed\n", tricks_completed );
+	mp_params->AddInteger( CRCD(0x8d958f01,"tricks_completed"), tricks_completed );
+	
+	// update number of tricks in menu
+	m_numTetrisTricks--;
+	
+	// printf("setting %s trick to invalid at index %i\n", m_tetrisTricks[i].trickText, i );
+	m_tetrisTricks[index].invalid = true;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::UpdateFadedTricks()
+{
+	// special case trick flag...used for an additional condition that must be
+	// met before any skatetris tricks can be cleared.
+	// The flag is cleared each time the tricks are checked
+	bool found_trick_flag = false;
+	int trick_flag;
+	if ( mp_params->GetInteger( CRCD(0x60b827d5,"trick_flag"), &trick_flag, Script::NO_ASSERT ) )
+	{
+		found_trick_flag = true;
+	}
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    Obj::CSkater *pSkater = skate_mod->GetLocalSkater();
+    Dbg_Assert( pSkater );
+  
+    Mdl::Score * pScore = ( pSkater->GetScoreObject() );
+    Dbg_Assert( pScore );
+
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	Dbg_Assert( p_screen_elem_man );
+
+	float faded_trick_alpha = Script::GetFloat( CRCD( 0x855e6da4, "goal_tetris_faded_trick_alpha" ), Script::ASSERT );
+	float unfaded_trick_alpha = Script::GetFloat( CRCD( 0xdbe5b19e, "goal_tetris_unfaded_trick_alpha" ), Script::ASSERT );
+
+	for ( int i = 0; i < vMAXTETRISTRICKS; i++ )
+	{
+		if ( !m_tetrisTricks[i].invalid )
+		{
+			Front::CScreenElement* p_trick = p_screen_elem_man->GetElement( m_tetrisTricks[i].screenElementId , Front::CScreenElementManager::ASSERT );
+			bool should_be_faded = false;
+			if ( found_trick_flag && !trick_flag )
+			{
+				should_be_faded = false;
+			}
+			else if ( m_tetrisTricks[i].keyCombo != 0 )
+			{
+				int n = pScore->GetCurrentNumberOfOccurrences( m_tetrisTricks[i].keyCombo, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect, m_tetrisTricks[i].num_taps );
+				if ( n > 0 )
+				{
+					should_be_faded = true;
+				}
+			}
+			else if ( m_tetrisTricks[i].trickNameChecksum != 0 )
+			{
+				int n = pScore->GetCurrentNumberOfOccurrencesByName( m_tetrisTricks[i].trickNameChecksum, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect );
+				if ( n > 0 )
+				{
+					should_be_faded = true;
+				}
+				else if ( m_tetrisTricks[i].altTrickNameChecksum != 0 )
+				{
+					// check alternate version of trick - usually BS version of grind
+					int n = pScore->GetCurrentNumberOfOccurrencesByName( m_tetrisTricks[i].altTrickNameChecksum, m_tetrisTricks[i].spin_mult, m_tetrisTricks[i].require_perfect );
+					if ( n > 0 )
+					{
+						should_be_faded = true;
+					}
+				}
+			}
+
+			if ( should_be_faded )
+				p_trick->SetAlpha( faded_trick_alpha, Front::CScreenElement::FORCE_INSTANT );
+			else
+				p_trick->SetAlpha( unfaded_trick_alpha, Front::CScreenElement::FORCE_INSTANT );
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::Expire()
+{
+	// expire during normal skatetris is winning...during single combo it's losing
+	if ( IsSingleComboSkatetris() || IsTricktris() )
+		CGoal::Lose();
+	else
+	{
+		if ( AllTricksCleared() )
+		{
+			CGoalManager* pGoalManager = GetGoalManager();
+			pGoalManager->WinGoal( GetGoalId() );
+		}
+		else
+		{
+			RunCallbackScript( vEXPIRED );
+	
+			// TODO: fix after e3
+			// this was done to make the horse and combo letter goals work right
+			if ( m_endRunType != vNONE )
+				Deactivate();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::AllTricksCleared()
+{
+    for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+    {
+        if ( m_tetrisTricks[i].invalid == false )
+			return false;
+    }
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkatetrisGoal::ClearAllTricks()
+{
+    m_numTetrisTricks = 0;
+    m_tetrisTime = 0;
+    
+    for ( int i = 0; i < Game::vMAXTETRISTRICKS; i++ )
+    {
+        m_tetrisTricks[i].invalid = true;
+        m_tetrisTricks[i].trickNameChecksum = 0;
+		m_tetrisTricks[i].altTrickNameChecksum = 0;
+		m_tetrisTricks[i].keyCombo = 0;
+
+		m_validGroups[i] = false;
+    }
+
+	mp_params->RemoveFlag( CRCD(0xc93da683,"single_combo_added") );
+
+	// clear all the screen elements
+	Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+	Script::CScript* pScript = NULL;
+	p_screen_elem_man->DestroyElement( CRCD(0xe35ec715,"tetris_tricks_menu"), Front::CScreenElementManager::RECURSE, Front::CScreenElementManager::PRESERVE_PARENT, pScript);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::IsComboSkatetris()
+{
+	int combo_test = 0;
+	mp_params->GetInteger( CRCD(0x4ec3cfb5,"combo"), &combo_test, Script::NO_ASSERT );
+	return ( combo_test || mp_params->ContainsFlag( CRCD(0x4ec3cfb5,"combo") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::IsSingleComboSkatetris()
+{
+	int single_combo_test = 0;
+	mp_params->GetInteger( CRCD(0xdf47b144,"single_combo"), &single_combo_test, Script::NO_ASSERT );
+	return ( single_combo_test || mp_params->ContainsFlag( CRCD(0xdf47b144,"single_combo") ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkatetrisGoal::IsTricktris()
+{
+	int tricktris_test = 0;
+	mp_params->GetInteger( CRCD(0xea7ba666,"tricktris"), &tricktris_test, Script::NO_ASSERT );
+	return ( tricktris_test || mp_params->ContainsFlag( CRCD(0xea7ba666,"tricktris") ) );
+}
+
+
+}		// namespace Game
diff --git a/Code/Sk/Modules/Skate/SkatetrisGoal.h b/Code/Sk/Modules/Skate/SkatetrisGoal.h
new file mode 100644
index 0000000..9039b6d
--- /dev/null
+++ b/Code/Sk/Modules/Skate/SkatetrisGoal.h
@@ -0,0 +1,71 @@
+// skatetris subclass
+
+#ifndef __SK_MODULES_SKATE_SKATETRISGOAL_H__
+#define __SK_MODULES_SKATE_SKATETRISGOAL_H__
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+
+namespace Game
+{
+
+const int vMAXTETRISTRICKS		= 30;
+
+struct tetrisTrick 
+{
+	uint32 		trickNameChecksum;
+	uint32		altTrickNameChecksum;
+    uint32 		keyCombo;
+    uint32 		screenElementId;
+    bool 		invalid;
+	bool		require_perfect;
+	int 		group_id;
+	int 		spin_mult;
+	int 		num_taps;
+};
+
+class CSkatetrisGoal : public CGoal
+{
+
+public:
+						CSkatetrisGoal( Script::CStruct* pParams );
+	virtual				~CSkatetrisGoal();
+
+	bool				Activate();
+	bool				Deactivate( bool force = false, bool affect_tree = true );
+	bool				Win();
+	void				Expire();
+	
+	bool				Update();
+
+	void                StartTetrisGoal();
+	void                EndTetrisGoal();
+	void                CheckTetrisTricks();
+	void				CheckTetrisCombos();
+	void                AddTetrisTrick( int num_to_add = 1 );
+	void				UpdateFadedTricks();
+
+	void				RemoveTrick( int index );
+
+	bool				AllTricksCleared();
+
+	void				ClearAllTricks();
+
+	bool				IsSingleComboSkatetris();
+	bool				IsComboSkatetris();
+	bool				IsTricktris();
+protected:
+
+    tetrisTrick         m_tetrisTricks[vMAXTETRISTRICKS];
+    int                 m_numTetrisTricks;
+    int					m_tetrisTime;
+	bool				m_validGroups[vMAXTETRISTRICKS];
+};
+
+}
+
+#endif
+
diff --git a/Code/Sk/Modules/Skate/VictoryCond.cpp b/Code/Sk/Modules/Skate/VictoryCond.cpp
new file mode 100644
index 0000000..f620519
--- /dev/null
+++ b/Code/Sk/Modules/Skate/VictoryCond.cpp
@@ -0,0 +1,621 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate													**
+**																			**
+**	File name:		Skate/VictoryCond.cpp									**
+**																			**
+**	Created by:		02/07/01	-	gj										**
+**																			**
+**	Description:	Defines various victory conditions for a game			**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Game
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   PrivateFunctions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CVictoryCondition::CVictoryCondition() //: Lst::Node< CVictoryCondition >( this )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CHighestScoreVictoryCondition::CHighestScoreVictoryCondition() : CVictoryCondition()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CHighestScoreVictoryCondition::ConditionComplete()
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CHighestScoreVictoryCondition::PrintDebugInfo()
+{
+//	printf( "Found CHighestScoreVictoryCondition\n" );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CHighestScoreVictoryCondition::IsTerminal()
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CHighestScoreVictoryCondition::IsWinner( uint32 skater_num )
+{
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	for ( uint32 i = 0; i < skate_mod->GetNumSkaters(); i++)
+	{
+		if ( i != skater_num )
+		{
+			Mdl::Score* pTestThisScore = skate_mod->GetSkater(skater_num)->GetScoreObject();
+
+			// score object
+			Mdl::Score* pScore = skate_mod->GetSkater(i)->GetScoreObject();
+
+			if ( pTestThisScore->GetTotalScore() < pScore->GetTotalScore() )
+			{
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CScoreReachedVictoryCondition::CScoreReachedVictoryCondition( uint32 target_score ) : CVictoryCondition()
+{
+	m_TargetScore = target_score;
+
+//	PrintDebugInfo();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CScoreReachedVictoryCondition::ConditionComplete()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	if( skate_mod->GetGameMode()->IsTeamGame())
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		Lst::Search< GameNet::PlayerInfo > sh;
+		GameNet::PlayerInfo* player;
+		int i;
+		int total_score[GameNet::vMAX_TEAMS] = {0};
+
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			Mdl::Score* pScore = player->m_Skater->GetScoreObject();
+
+			total_score[player->m_Team] += pScore->GetTotalScore();
+		}
+
+		for( i = 0; i < GameNet::vMAX_TEAMS; i++ )
+		{
+			if( total_score[i] >= (int32)m_TargetScore )
+			{
+				return true;
+			}
+		}
+	}
+	else
+	{
+		for ( uint32 i = 0; i < skate_mod->GetNumSkaters(); i++)
+		{
+			// score object
+			Mdl::Score* pScore = skate_mod->GetSkater(i)->GetScoreObject();
+			
+			if ( pScore->GetTotalScore() >= (int32)m_TargetScore )
+			{
+				return true;
+			}
+		}
+	}
+	
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CScoreReachedVictoryCondition::PrintDebugInfo()
+{
+	printf( "Found CScoreReachedVictoryCondition %d\n", m_TargetScore );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CScoreReachedVictoryCondition::GetTargetScore( void )
+{
+	return m_TargetScore;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CScoreReachedVictoryCondition::IsTerminal()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CScoreReachedVictoryCondition::IsWinner( uint32 skater_num )
+{
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	for ( uint32 i = 0; i < skate_mod->GetNumSkaters(); i++)
+	{
+		if ( i != skater_num )
+		{
+			Mdl::Score* pTestThisScore = skate_mod->GetSkater(skater_num)->GetScoreObject();
+
+			// score object
+			Mdl::Score* pScore = skate_mod->GetSkater(i)->GetScoreObject();
+
+			if ( pTestThisScore->GetTotalScore() < pScore->GetTotalScore() )
+			{
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CGoalsCompletedVictoryCondition::CGoalsCompletedVictoryCondition( void )
+: CVictoryCondition()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool GoalAttackComplete()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Game::CGoalManager* pGoalManager;
+	
+	pGoalManager = Game::GetGoalManager();
+	if( skate_mod->GetGameMode()->IsTeamGame())
+	{
+		int i, num_selected_goals;
+
+		num_selected_goals = pGoalManager->GetNumSelectedGoals();
+		for( i = 0; i < skate_mod->GetGameMode()->NumTeams(); i++ )
+		{
+			if( gamenet_man->GetTeamScore( i ) >= num_selected_goals )
+			{
+				return true;
+			}
+		}
+	}
+	else
+	{
+		uint32 i, j, num_goals;
+		bool completed_all;
+		Game::CGoal* pGoal;
+		
+		num_goals = pGoalManager->GetNumGoals();
+		for( i = 0; i < skate_mod->GetNumSkaters(); i++ )
+		{
+			Obj::CSkater* skater = skate_mod->GetSkater(i);
+			completed_all = true;
+			for( j = 0; j < num_goals; j++ )
+			{
+				pGoal = pGoalManager->GetGoalByIndex( j );
+
+				if( pGoal->GetParents()->m_relative != 0 )
+				{
+					continue;
+				}
+				if( pGoal->IsSelected())
+				{
+					if( pGoal->HasWonGoal( skater->GetID()) == false )
+					{
+						completed_all = false;
+						break;
+					}
+				}
+			}    
+
+			if( completed_all )
+			{
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGoalsCompletedVictoryCondition::ConditionComplete()
+{
+	return GoalAttackComplete();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGoalsCompletedVictoryCondition::IsWinner( uint32 skater_num )
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGoalsCompletedVictoryCondition::IsTerminal()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CGoalsCompletedVictoryCondition::PrintDebugInfo()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CBestComboVictoryCondition::CBestComboVictoryCondition( uint32 num_combos ) : CVictoryCondition()
+{
+	m_NumCombos = num_combos;
+
+//	PrintDebugInfo();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CBestComboVictoryCondition::ConditionComplete()
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CBestComboVictoryCondition::PrintDebugInfo()
+{
+	printf( "Found CBestComboVictoryCondition %d\n", m_NumCombos );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CBestComboVictoryCondition::IsTerminal()
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool
+CBestComboVictoryCondition::IsWinner( uint32 skater_num )
+{
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	for ( uint32 i = 0; i < skate_mod->GetNumSkaters(); i++)
+	{
+		if ( i != skater_num )
+		{
+			Mdl::Score* pTestThisScore = skate_mod->GetSkater(skater_num)->GetScoreObject();
+
+			// score object
+			Mdl::Score* pScore = skate_mod->GetSkater(i)->GetScoreObject();
+
+			if ( pTestThisScore->GetTotalScore() < pScore->GetTotalScore() )
+			{
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CVictoryCondition*	CreateVictoryConditionInstance( Script::CScriptStructure* p_structure )
+{
+	
+
+	Dbg_Assert( p_structure );
+
+	uint32 condition_type;
+	p_structure->GetChecksum( "type", &condition_type, true );
+
+	CVictoryCondition* pVictoryCondition = NULL;
+	
+	switch ( condition_type )
+	{
+		case vVICTORYCOND_HIGHESTSCORE:
+		{
+			pVictoryCondition = new CHighestScoreVictoryCondition( );
+		}
+		break;
+		case vVICTORYCOND_TARGETSCORE:
+		{
+			int target_score;
+			p_structure->GetInteger( "score", &target_score, true );
+			pVictoryCondition = new CScoreReachedVictoryCondition( (uint32)target_score );
+		}
+		break;
+		case vVICTORYCOND_BESTCOMBO:
+		{
+			int num_combos;
+			p_structure->GetInteger( "num_combos", &num_combos, true );
+			pVictoryCondition = new CBestComboVictoryCondition( (uint32)num_combos );
+		}
+		break;
+		case vVICTORYCOND_COMPLETEGOALS:
+		{
+			pVictoryCondition = new CGoalsCompletedVictoryCondition;
+		}
+		break;
+		case vVICTORYCOND_LASTMANSTANDING:
+		{
+			pVictoryCondition = new CLastManStandingVictoryCondition;
+		}
+		break;
+		default:
+		{
+			Dbg_MsgAssert( 0,( "Unrecognized victory condition type %08x %s", condition_type, Script::FindChecksumName( condition_type ) ));
+		}
+		break;
+	}
+
+	return pVictoryCondition;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CLastManStandingVictoryCondition::CLastManStandingVictoryCondition( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CLastManStandingVictoryCondition::ConditionComplete()
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* player;
+
+	// This logic may seem strange. Basically, when ConditionComplete() returns true,
+	// the local skater ends his run and he can observe other players. So, it will return
+	// true when his hit points have been depleted rather than when he has won.
+
+	player = gamenet_man->GetLocalPlayer();
+	if( player && player->m_Skater )
+	{
+		Mdl::Score* pScore = player->m_Skater->GetScoreObject();
+
+		if( pScore->GetTotalScore() <= 0 )
+		{
+			return true;
+		}
+	}
+		
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CLastManStandingVictoryCondition::PrintDebugInfo()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CLastManStandingVictoryCondition::IsTerminal()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CLastManStandingVictoryCondition::IsWinner( uint32 skater_num )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	// score object
+	Mdl::Score* pScore = skate_mod->GetSkater(skater_num)->GetScoreObject();
+	if( pScore && ( pScore->GetTotalScore() > 0 ))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Game
+
diff --git a/Code/Sk/Modules/Skate/VictoryCond.h b/Code/Sk/Modules/Skate/VictoryCond.h
new file mode 100644
index 0000000..454e088
--- /dev/null
+++ b/Code/Sk/Modules/Skate/VictoryCond.h
@@ -0,0 +1,147 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate													**
+**																			**
+**	File name:		Skate/VictoryCond.h										**
+**																			**
+**	Created by:		02/07/01	-	gj										**
+**																			**
+**	Description:	Defines various victory conditions for a game			**
+**																			**
+*****************************************************************************/
+
+#ifndef __MODULES_SKATE_VICTORYCOND_H
+#define __MODULES_SKATE_VICTORYCOND_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+    class CStruct;
+}
+        
+namespace Game
+{
+ 
+const uint32 vVICTORYCOND_HIGHESTSCORE = 0x8f30fd26;
+const uint32 vVICTORYCOND_TARGETSCORE = 0x71cd53a2;
+const uint32 vVICTORYCOND_BESTCOMBO = 0x6fad9811;
+const uint32 vVICTORYCOND_COMPLETEGOALS = 0x6fc19399;	// complete_goals
+const uint32 vVICTORYCOND_LASTMANSTANDING = 0xcc06880f;	// last_man_standing
+   
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CVictoryCondition : public Spt::Class
+{
+	
+
+public:
+	CVictoryCondition();
+	virtual					~CVictoryCondition() {}
+	virtual bool			IsWinner( uint32 skater_num ) { return false; }
+	virtual bool			IsTerminal() = 0;
+	virtual bool			ConditionComplete() = 0;
+	virtual bool			PrintDebugInfo() = 0;
+};
+
+class CHighestScoreVictoryCondition : public  CVictoryCondition
+{
+public:
+	CHighestScoreVictoryCondition();
+	bool					ConditionComplete();
+	bool					PrintDebugInfo();
+	bool					IsTerminal();
+	bool					IsWinner( uint32 skater_num );
+};
+
+class CScoreReachedVictoryCondition : public  CVictoryCondition
+{
+public:
+	CScoreReachedVictoryCondition( uint32 target_score );
+	bool					ConditionComplete();
+	bool					PrintDebugInfo();
+	bool					IsTerminal();
+	bool					IsWinner( uint32 skater_num );
+	int						GetTargetScore( void );
+protected:
+	uint32				m_TargetScore;
+};
+
+bool GoalAttackComplete();
+
+class CGoalsCompletedVictoryCondition : public  CVictoryCondition
+{
+public:
+	CGoalsCompletedVictoryCondition();
+	bool					ConditionComplete();
+	bool					PrintDebugInfo();
+	bool					IsTerminal();
+	bool					IsWinner( uint32 skater_num );
+};
+
+class CBestComboVictoryCondition : public  CVictoryCondition
+{
+public:
+	CBestComboVictoryCondition( uint32 num_combos );
+	bool					ConditionComplete();
+	bool					PrintDebugInfo();
+	bool					IsTerminal();
+	bool					IsWinner( uint32 skater_num );
+protected:
+	uint32				m_NumCombos;
+};
+
+class CLastManStandingVictoryCondition : public CVictoryCondition
+{
+public:
+	CLastManStandingVictoryCondition( void );
+	bool					ConditionComplete();
+	bool					PrintDebugInfo();
+	bool					IsTerminal();
+	bool					IsWinner( uint32 skater_num );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+CVictoryCondition*	CreateVictoryConditionInstance( Script::CStruct* p_structure );
+
+/*****************************************************************************
+**						Inline Functions									**
+*****************************************************************************/
+					
+} // namespace Game
+
+#endif // __MODULES_SKATE_VICTORYCOND_H
\ No newline at end of file
diff --git a/Code/Sk/Modules/Skate/competition.cpp b/Code/Sk/Modules/Skate/competition.cpp
new file mode 100644
index 0000000..faad5d4
--- /dev/null
+++ b/Code/Sk/Modules/Skate/competition.cpp
@@ -0,0 +1,842 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:			skate			 										**
+**																			**
+**	File name:		competition.cpp											**
+**																			**
+**	Created by:		18/06/01	-	Mick									**
+**																			**
+**	Description:	handles playing single player competitions				**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 						 // for Mth::Rnd2()
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include  
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+CCompetition::CCompetition( Script::CStruct* pParams )
+{
+	mp_params = new Script::CStruct();
+}
+
+CCompetition::~CCompetition()
+{
+}
+	
+void CCompetition::EditParams( Script::CStruct* pParams )
+{
+	mp_params->AppendStructure( pParams );
+}
+
+void CCompetition::ResetJudgement()
+{
+	m_total_score = 0.0f;
+	m_bail_points = 0.0f;
+}
+
+int CLeaderBoardEntry::GetTotalScore(int round)
+{
+	int score1 = 0;
+	int score2 = 0;
+	
+	for (int i=0;i score1)
+		{
+			score2 = score1;
+			score1 = m_score[i];
+		}
+		else
+		if (m_score[i] > score2)
+		{
+			score2 = m_score[i];
+		}
+	}
+	return score1 + score2;	
+}
+
+
+// start the competition, and pre-calculate all
+// the scores
+void	   CCompetition::StartCompetition(float bronze, float silver, float gold, float bronze_score, float silver_score, float gold_score, float bail)
+{
+	// store the defining parameters
+
+	m_bronze = bronze;
+	m_silver = silver;
+	m_gold = gold;
+	m_bronze_score = bronze_score;
+	m_silver_score = silver_score;
+	m_gold_score = gold_score;
+	m_bail = bail;
+	
+	
+	m_end_competition = false;
+
+	// Get the current profile
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile=pPlayerProfileManager->GetCurrentProfile();
+
+	// need to use GetDisplayName() wrapper function instead of GetUIString(), bec. ui string can now theoretically be empty
+	m_leader_board[0].m_name = pSkaterProfile->GetDisplayName();
+
+	// Let's count how many pros there are, so we can pcik from them at random
+
+	int	possible_pros = pPlayerProfileManager->GetNumProfileTemplates();	
+
+// get an array of pros, except the one that has the same name as me
+	int	pro_order[100];
+	int num_pros = 0;
+	for (int i = 0;iGetProfileTemplateByIndex(i);
+		
+//		printf ("Pro=%d, Secret = %d, name = %s",pProfile->IsPro(), pProfile->IsSecret(), pProfile->GetDisplayName());
+				
+		if (!pProfile->IsPro() || pProfile->IsSecret() || Script::GenerateCRC(m_leader_board[0].m_name.getString()) == Script::GenerateCRC(pProfile->GetDisplayName()))
+		{
+			// skip over this one
+		}
+		else
+		{
+			pro_order[num_pros++] = i;
+		}
+	}
+	
+	// shuffle them
+	for (int i = 0;i= gold)
+	{
+		silver = gold - 0.1f;
+	}
+	if (bronze >= silver)
+	{
+		bronze = silver - 0.1f;
+	}
+	
+	
+	// go through all remaining players, and fill in the blanks	
+	for (int i = 0;iGetArray( CRCD(0x1cc6f48d,"leader_board_names"), &pLeaderBoardNames, Script::NO_ASSERT ) )
+		{
+			Dbg_MsgAssert( pLeaderBoardNames->GetSize() == vMAX_PLAYERS - 1, ( "leader_board_names has wrong size.  Needs to be %i", vMAX_PLAYERS - 1 ) );
+			m_leader_board[player].m_name = pLeaderBoardNames->GetString( i );
+		}
+		else
+			m_leader_board[player].m_name = pPlayerProfileManager->GetProfileTemplate(pPlayerProfileManager->GetProfileTemplateChecksum(pro_order[i]))->GetDisplayName();
+		
+		
+		// now calculate the score
+		float score = 0.0f;
+		if (i == 0) score = gold;
+		else if (i == 1) score = silver;
+		else if (i == 2) score = bronze;
+		else
+		{
+			// scores for the player after bronze are essentially any random number less than bronze
+			score = bronze - (bronze/8) + Mth::PlusOrMinus(bronze/8);
+			if (score >= bronze)
+			{
+				score = bronze - 0.1f;
+			}			
+		}
+		// now we have the total score, we calculate the random components that
+		// will add up to that score
+		// the final sore is calculated as the average of the best of two scores
+		// So... calculate two scores that add up to twice the total score
+		// then add a third that is less than both of these
+		// and put these in random order
+		// 
+		float score1 = score + Mth::PlusOrMinus(2.0f);
+		float score2 = score*2.0f - score1;
+		float lower = score1;
+		if (score2 GetString( CRCD(0xfaf19012,"first_place_name"), &p_first_place_name, Script::NO_ASSERT ) )
+	{
+		m_leader_board[0].m_name = p_first_place_name;
+	}
+	
+	printf ("\nSorted\n");	
+	// let up now print out the fruits of our efforts
+	for (int i = 0;i 99.9f)
+		m_total_score = 99.9f;
+
+	// we calculate a "varience", which is the maximum amount
+	// a score can be randomly varied
+	// this ranges from 10 to 0 as the actual score goes from 0 to 99.9
+													   
+	float variance = ( 99.9f - m_total_score ) / 10.0f;
+
+	// perform some initial varying by half the variance
+	// to make the score seen more random 
+	m_total_score += Mth::PlusOrMinus(variance/2);
+
+	// clamp it, just to be safe	
+	if (m_total_score > 99.9f)
+		m_total_score = 99.9f;
+	if (m_total_score < 0.0f)
+		m_total_score = 0.0f;
+								
+	// and find the n Judges scores
+	// here the final score is the average of the top three scores.
+	// so we need to generate three scores that add up to the 
+	// total score
+	
+	
+	m_score[0] = m_total_score + Mth::PlusOrMinus(variance);
+	if (m_score[0] > 99.85f)
+		m_score[0] = 99.9f;
+	if (m_score[0] < 0.0f)
+		m_score[0] = 0.0f;	
+	m_score[1] = m_total_score + Mth::PlusOrMinus(variance);
+	if (m_score[1] > 99.85f)
+		m_score[1] = 99.9f;
+	if (m_score[1] < 0.0f)
+		m_score[1] = 0.0f;
+	m_score[2] = (m_total_score * 3.0f) - (m_score[0] + m_score[1]);
+	if (m_score[2] < 0.0f)
+		m_score[2] = 0.0f;
+	if (m_score[2] > 99.85f)
+		m_score[2] = 99.9f;
+		
+		
+	m_score[0] = (float)( (int) (m_score[0] * 10.0f)) /10.0f + 0.0001f;
+	m_score[1] = (float)( (int) (m_score[1] * 10.0f)) /10.0f + 0.0001f;
+	m_score[2] = (float)( (int) (m_score[2] * 10.0f)) /10.0f + 0.0001f;
+	
+	m_score[0] = (int)Mth::Round(m_score[0]);
+	m_score[1] = (int)Mth::Round(m_score[1]);
+	m_score[2] = (int)Mth::Round(m_score[2]);
+
+	if ( m_score[0] > 99 )
+		m_score[0] = 99;
+	if ( m_score[1] > 99 )
+		m_score[1] = 99;
+	if ( m_score[2] > 99 )
+		m_score[2] = 99;
+	
+	// recalculate the total score
+	m_total_score = ((float)(m_score[0]) + (float)(m_score[1]) + (float)(m_score[2])) / 3;
+	m_total_score = (int)Mth::Round( m_total_score );
+	if ( m_total_score > 99 )
+		m_total_score = 99;
+	// m_judge_score[m_current_round] = (int)(10.0f * m_total_score + 0.5f);
+	m_judge_score[m_current_round] = (int)m_total_score;
+
+	printf ("3 scores are %f,%f,%f, total is %f\n",m_score[0],m_score[1],m_score[2],m_total_score);
+
+	// Stick the total score in the correct place in the leaderboard	
+	for (i = 0;iIsInCompetition() )
+	{
+		// and let us have a look at what we come up with;
+		Script::CStruct* p_run_scores = new Script::CStruct();
+		// pack the scores in a carray to make them easy to deal with in script
+		Script::CArray* p_run_scores_array = new Script::CArray();
+		p_run_scores_array->SetSizeAndType( vNUM_JUDGES, ESYMBOLTYPE_STRUCTURE );
+		printf( "\nJudgement Day\n\n" );
+		for ( i = 0; i < vNUM_JUDGES; i++ )
+		{
+			// add an integer version of this score to the params, so it's 
+			// easy to deal with in script
+			Script::CStruct* p_temp = new Script::CStruct();
+			p_temp->AddInteger( "score", (int)Mth::Round( m_score[i] ) );
+
+			// add a flag to the top judges
+			if ( IsTopJudge( i ) )
+			{
+				p_temp->AddChecksum( NONAME, CRCD( 0x7b1b959f, "top_judge" ) );
+			}
+
+			p_run_scores_array->SetStructure( i, p_temp );
+			printf ("%d: %f Top=%d\n",i,m_score[i],IsTopJudge(i));
+		}	
+		
+		p_run_scores->AddArray( "scores", p_run_scores_array );
+
+		// add the total score
+		// p_run_scores->AddInteger( "total_score", (int)Mth::Round( ( m_total_score * 10 ) ) );
+		p_run_scores->AddInteger( "total_score", (int)Mth::Round( ( m_total_score ) ) );
+
+		Script::CleanUpArray( p_run_scores_array );
+		delete p_run_scores_array;
+		#ifdef __NOPT_ASSERT__
+		Script::CScript *p_script=Script::SpawnScript( "goal_comp_show_run_scores", p_run_scores );
+		p_script->SetCommentString("Spawned from CCompetition::EndRun(...)");
+		#else
+		Script::SpawnScript( "goal_comp_show_run_scores", p_run_scores );
+		#endif
+		delete p_run_scores;
+	}
+}
+
+// This function should be called every time the skater bails
+void 	   CCompetition::Bail()
+{
+		m_bail_points += m_bail + Mth::PlusOrMinus(m_bail/2);
+}
+
+void	   CCompetition::Sort()
+{
+	// sort the list, based on total score 
+
+	int round = m_current_round;									  
+	// if the competition has ended (maybe early), then total up for all the rounds	
+	if (CompetitionEnded())
+	{
+		round = 2;
+	}
+							 
+	for (int i=0;i m_leader_board[high].GetTotalScore(round))
+			{
+				high = j;
+			}
+			// give the player the higher position if there is a tie
+			else if ( m_leader_board[j].m_player_number == 0 && m_leader_board[j].GetTotalScore(round) == m_leader_board[high].GetTotalScore(round) )
+			{
+				high = j;
+			}
+		}
+		if (high != i)
+		{
+			CLeaderBoardEntry t = m_leader_board[high];
+			m_leader_board[high] = m_leader_board[i];
+			m_leader_board[i] = t;
+		}
+	}
+}
+	
+int 	   CCompetition::GetPosition(int player)
+{
+	return 0;
+}
+
+	   
+char *		CCompetition::GetJudgeName(int judge)
+{
+	return m_judge_name[judge];
+}
+
+float		CCompetition::GetJudgeScore(int judge)
+{
+	return m_score[judge];
+}
+
+void CCompetition::SetupJudgeText()
+{
+	Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+	Dbg_Assert( pGoalManager );
+
+	if ( pGoalManager->IsInCompetition() )
+	{
+		Script::CStruct* p_score_params = new Script::CStruct();
+
+		Script::CArray* p_score_array = new Script::CArray();
+		p_score_array->SetSizeAndType( vNUM_JUDGES , ESYMBOLTYPE_STRUCTURE );
+
+		printf ("\nSetupJudgeText\n");	
+		// let up now print out the fruits of our efforts
+		for (int i = 0;iAddString( "name", m_leader_board[i].m_name.getString() );
+			printf("m_current_round: %i\n", m_current_round);
+			p_leader_board_entry->AddInteger( "score", m_leader_board[i].GetTotalScore(m_current_round) );
+
+			if ( m_leader_board[i].m_player_number == 0 )
+			{
+				p_leader_board_entry->AddChecksum( NONAME, CRCD( 0x67e6859a, "player" ) );
+			}
+
+			p_score_array->SetStructure( i, p_leader_board_entry );
+		}
+
+		p_score_params->AddArray( "leader_board", p_score_array );
+		Script::CleanUpArray( p_score_array );
+		delete p_score_array;
+		#ifdef __NOPT_ASSERT__
+		Script::CScript *p_script=Script::SpawnScript( "goal_comp_add_leader_board", p_score_params );
+		p_script->SetCommentString("Spawned from CCompetition::SetupJudgeText()");
+		#else
+		Script::SpawnScript( "goal_comp_add_leader_board", p_score_params );
+		#endif
+		delete p_score_params;
+	}
+
+/*//	int i_total = 0;
+	for (int i = 0;i=1)
+		{
+			if (round == 2 && small == 1)
+				sprintf(Script::GetScriptString(12 + line * 5 + 2),xpC21f, (float)(m_leader_board[line].m_score[1]/10.0f));
+			else
+				sprintf(Script::GetScriptString(12 + line * 5 + 2),pC21f, (float)(m_leader_board[line].m_score[1]/10.0f));
+		}
+		else
+			sprintf(Script::GetScriptString(12 + line * 5 + 2), pC,"-");
+
+		if (round >=2)
+		{
+			if (round == 2 && small == 2)
+				sprintf(Script::GetScriptString(12 + line * 5 + 3),xpC21f, (float)(m_leader_board[line].m_score[2]/10.0f));
+			else
+				sprintf(Script::GetScriptString(12 + line * 5 + 3),pC21f, (float)(m_leader_board[line].m_score[2]/10.0f));
+		}
+		else
+		{
+			sprintf(Script::GetScriptString(12 + line * 5 + 3),pC, "-");				
+		}
+		
+		sprintf(Script::GetScriptString(12 + line * 5 + 4),pC21f, (float)(m_leader_board[line].GetTotalScore(round)/10.0f));
+		
+	}
+	
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile=pPlayerProfileManager->GetCurrentProfile();
+	sprintf (Script::GetScriptString(12 + line *5),	pSkaterProfile->GetDisplayName());
+*/	
+}
+
+// Return true if this is a top 3 judge
+// note, has to handle cases where score is the same
+// works by sorting the judges, and then seeing if we are in the top three 
+bool CCompetition::IsTopJudge(int judge)
+{
+	int	judge_order[vNUM_JUDGES];
+	
+	for (int i=0;i		 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __SK_MODULES_COMPETITION_H
+#define __SK_MODULES_COMPETITION_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Mdl
+{
+
+						
+
+	enum 
+	{
+		vMAX_PLAYERS = 8,
+		vNUM_ROUNDS = 3,
+		vNUM_JUDGES = 5,
+	};
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+class		CLeaderBoardEntry
+{
+
+public:
+	int 						GetTotalScore(int round);	
+
+	Str::String					m_name;
+	int							m_player_number;
+	int							m_score[vNUM_ROUNDS]; 	// score now stored in hundreths
+	bool						visible;	
+};
+
+
+class  CCompetition  : public Spt::Class
+{
+	
+	
+public:	
+	CCompetition( Script::CStruct* pParams = NULL );
+	~CCompetition();
+	
+	void						StartCompetition(float bronze = 90.0f, float silver = 93.0f, float gold = 95.0f, float bonze_score=20000, float silver_score=40000,float gold_score = 60000,float bail = 1.0f);
+	void						StartRun();
+	void 						EndRun(int score);
+	void 						Bail();
+	void						Sort();
+	
+	int 						GetPosition(int player = 0);
+	char *						GetJudgeName(int judge);
+	float						GetJudgeScore(int judge);
+	void						SetupJudgeText();
+	bool 						IsTopJudge(int judge);
+	bool 						PlaceIs(int place);
+	bool 						RoundIs(int place);
+	void 						EndCompetition();
+	bool 						CompetitionEnded();
+
+	void						EditParams( Script::CStruct* pParams );
+
+private:
+	void						ResetJudgement();
+	
+	// BB - i'm lazy, so I'm just adding a struct to hold various
+	// params that i can use to change the behavior in the code
+	Script::CStruct*			mp_params;
+	
+	int							m_current_round;
+
+	CLeaderBoardEntry			m_leader_board[vMAX_PLAYERS];
+
+	float 						m_bronze;		 	// score required to get bronze
+	float						m_silver;
+	float 						m_gold;
+	float 						m_bronze_score;		// points required to get that score
+	float 						m_silver_score;
+	float 						m_gold_score;
+	float 						m_bail;			    // points deducted for bailing	
+
+	float						m_bail_points;			   	
+	float						m_score[vNUM_JUDGES];  	// m_score is generated from m_total_score
+	char *						m_judge_name[vNUM_JUDGES];  	//
+	float						m_total_score;
+
+	int							m_judge_score[vNUM_ROUNDS];
+
+	bool						m_end_competition;
+	
+	int	m_place;
+
+};
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace 
+
+#endif		// __SK_MODULES_COMPETITION_H
+
+
+
diff --git a/Code/Sk/Modules/Skate/horse.cpp b/Code/Sk/Modules/Skate/horse.cpp
new file mode 100644
index 0000000..8e70650
--- /dev/null
+++ b/Code/Sk/Modules/Skate/horse.cpp
@@ -0,0 +1,559 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:			skate			 										**
+**																			**
+**	File name:		horse.cpp												**
+**																			**
+**	Created by:		07/17/01	-	Gary									**
+**																			**
+**	Description:			 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 						 // for Mth::Rnd()
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static uint32 s_horse_status[] = {
+   0x0fd80eba,	// GotLetter
+   0x006be86f,	// TieScore
+   0xbaa12466,	// BeatScore
+   0xae7d29b0,	// Ended
+   0x23db4aea,	// Idle
+   0xa71dd8eb,	// NoScoreSet
+   0			// Terminator
+};
+
+// should match the above
+enum
+{
+	vGOTLETTER = 0,
+	vTIESCORE,
+	vBEATSCORE,
+	vENDED,
+	vIDLE,
+	vNOSCORESET,
+};
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static uint32 get_num_horse_restarts()
+{
+	uint32 dummy_nodes[128];
+	return Obj::FindQualifyingNodes( Script::GenerateCRC("horse"), &dummy_nodes[0] );
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CHorse::CHorse()
+{
+	strcpy( m_string, "HORSE" );
+
+	m_currentRestartIndex = 0;
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CHorse::Init()
+{
+	// reset the score to beat
+	m_scoreToBeat = 0;
+
+	// setting the "current horse player"
+	m_currentSkaterId = 0;
+	m_nextSkaterId = 0;
+	sprintf( m_nextHorseMessage, "unknown" );
+
+	// resetting each of their accumulated horse words
+	for ( int i = 0; i < Skate::vMAX_SKATERS; i++ )
+	{
+		m_numLetters[i] = 0;
+	}
+
+	// select a random restart point
+	uint32 num_restarts = get_num_horse_restarts();
+	//Dbg_MsgAssert ( num_restarts > 0, ( "Couldn't find any horse restarts" ) );
+	Dbg_Printf( "-- COULDN'T FIND ANY HORSE RESTARTS --\n" );
+	m_currentRestartIndex = Mth::Rnd( num_restarts );
+
+	// maybe do some kind of handicapping?
+	// or separate words for each player?
+	
+	// their panels need to be suspended, but that's
+	// done in the panel manager code.
+	// TODO:  move it to some accessor in the panel stuff
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CHorse::GetCurrentSkaterId()
+{
+	return m_currentSkaterId;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CHorse::StartRun()
+{
+	
+	
+	//Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+
+	//m_currentSkaterId = m_nextSkaterId;
+
+	m_status = s_horse_status[vIDLE];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CHorse::EndRun()
+{
+	// calculate the score post-run
+	// update the horse data structure so that we know that
+	// the score to beat is
+
+	sprintf( m_nextHorseMessage, "unknown" );
+	
+	// by default, don't change the skater
+	m_nextSkaterId = m_currentSkaterId;
+
+	int score = get_score( m_currentSkaterId );
+
+	printf( "End run = %d\n", score );
+
+	if ( score > m_scoreToBeat )
+	{
+		// current player wins the run
+		m_scoreToBeat = score;
+
+		sprintf( m_nextHorseMessage, "%s %s", Script::GetLocalString( "horse_str_setscoreis" ), Str::PrintThousands(m_scoreToBeat) );
+		m_status = s_horse_status[vBEATSCORE];
+	}
+	else if ( score < m_scoreToBeat )
+	{
+		// current player loses the run
+		m_scoreToBeat = 0;
+		
+		m_numLetters[ m_currentSkaterId ]++;
+
+		// skip to next restart point
+		SetNextRestartPoint();
+		
+		if ( Ended() )
+		{
+			// GJ:  this string is never used...  it says "Player 1 is a horse" somewhere else
+			sprintf( m_nextHorseMessage, "You are a %s!", m_string );
+
+			m_status = s_horse_status[vENDED];
+		}
+		else
+		{
+			char letter[2];
+			letter[0] = *(m_string + m_numLetters[m_currentSkaterId] - 1);
+			// make it uppercase
+			if ( letter[0] >= 'a' && letter[0] <= 'z' )
+			{
+				letter[0] -= 'a';
+				letter[0] += 'A';
+			}
+			letter[1] = 0;
+			sprintf( m_nextHorseMessage, "%s %s", Script::GetLocalString( "horse_str_getstheletter" ), letter );
+			m_status = s_horse_status[vGOTLETTER];
+		}
+	}
+	else
+	{
+		// current player draws
+		if ( score == 0 )
+		{
+			strcpy( m_nextHorseMessage, Script::GetLocalString( "horse_str_noscoreset" ));
+			m_status = s_horse_status[vNOSCORESET];
+		}
+		else
+		{
+			strcpy( m_nextHorseMessage, Script::GetLocalString( "horse_str_youtiedthetargetscore" ));
+			m_status = s_horse_status[vTIESCORE];
+		}
+	}
+
+#ifdef __USER_GARY__
+	printf( "Current Score:  %s - %s\n",
+			GetWordForPlayer( 0 ).getString(),
+			GetWordForPlayer( 1 ).getString() );
+#endif
+	
+	//SwitchPlayers();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CHorse::SwitchPlayers()
+{
+	m_nextSkaterId = get_next_valid_skater_id();
+	m_currentSkaterId = m_nextSkaterId;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CHorse::StatusEquals( Script::CStruct* pParams )
+{
+	int i = 0;
+	while ( s_horse_status[i] )
+	{
+		if ( pParams->ContainsFlag( s_horse_status[i] ) )
+		{
+			return ( m_status == s_horse_status[i] );
+		}
+		
+		i++;
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CHorse::Ended()
+{
+	for ( int i = 0; i < Skate::vMAX_SKATERS; i++ )
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		if ( !skate_mod->GetSkaterById( i ) )
+			continue;
+
+		// if the skater exists...
+		if ( m_numLetters[i] >= strlen( m_string ) )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CHorse::SetWord( Str::String word )
+{
+	// validate string
+	// make sure there's at least 1 letter
+
+	Dbg_Assert( word.getString() && strlen( word.getString() ) > 0 );
+
+	strcpy( m_string, word.getString());
+
+	printf( "New word = %s\n", m_string );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String CHorse::GetWordForPlayer( int skater_id )
+{
+	
+
+	Dbg_Assert( skater_id >= 0 && skater_id < Skate::vMAX_SKATERS );
+	
+	char msg[256];
+	
+	strcpy( msg, m_string );
+	for ( uint32 i = 0; i < strlen(msg); i++ )
+	{
+		if ( i >= m_numLetters[ skater_id ] )
+			msg[i] = '-';
+	}
+
+	return msg;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char* CHorse::GetWord()
+{
+	return m_string;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CHorse::get_next_valid_skater_id()
+{
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	Obj::CSkater *pSkater = NULL;
+	int check_id = 0;
+
+	for ( int i = 0; i < Skate::vMAX_SKATERS; i++ )
+	{
+		check_id = ( m_currentSkaterId + i + 1 ) % Skate::vMAX_SKATERS;
+		pSkater = skate_mod->GetSkaterById( check_id );
+		if ( pSkater )
+			break;
+	}
+	
+	Dbg_Assert( pSkater );
+
+	printf( "new skater id = %d\n", check_id );
+
+	if ( Script::GetInteger( "debug_horse" ) )
+	{
+		// DEBUGGING
+		check_id = 0;
+	}
+	
+	return check_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CHorse::get_score( int skater_id )
+{
+	
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetSkaterById( skater_id );
+	Dbg_Assert( pSkater );
+
+	Mdl::Score * pScore = ( pSkater->GetScoreObject() );
+	Dbg_Assert( pScore );
+
+	return pScore->GetTotalScore();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String	CHorse::GetString( uint32 checksum )
+{
+	
+	
+	char msg[256];
+	strcpy( msg, "Unknown" );
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetSkaterById( m_currentSkaterId );
+	Dbg_Assert( pSkater );
+
+	switch ( checksum )
+	{
+		case 0x9196d920: // playerName
+		{
+//			int currentPlayerNumber = pSkater->GetID() + 1;
+//			sprintf( msg, "Player %d", currentPlayerNumber );
+			sprintf( msg, "%s", skate_mod->GetSkaterById( pSkater->GetID() )->GetDisplayName() );
+		}
+		break;
+		
+		case 0x5078bc7c: // horsePreRun
+		{
+			if ( m_scoreToBeat > 0 )
+			{
+				sprintf( msg, "%s %s", Script::GetLocalString( "horse_str_scoretobeat" ), Str::PrintThousands(m_scoreToBeat) );
+			}
+			else
+			{
+				sprintf( msg, Script::GetLocalString( "horse_str_setscore" ) );
+			}
+		}
+		break;
+		
+		case 0x2a13f850: // horsePostRun
+		{
+			strcpy( msg, m_nextHorseMessage );
+		}
+		break;
+
+		case 0xde802177: // panelString1
+		{
+			sprintf( msg, "%s: %s", skate_mod->GetSkaterById( 0 )->GetDisplayName(), GetWordForPlayer( 0 ).getString() );
+		}
+		break;
+
+		case 0x478970cd: // panelString2
+		{
+			sprintf( msg, "%s: %s", skate_mod->GetSkaterById( 1 )->GetDisplayName(), GetWordForPlayer( 1 ).getString() );
+		}
+		break;
+
+		case 0x2523853d: // youAreA
+		{
+			bool starts_with_vowel = false;
+			char first_letter = GetWord()[0];
+			if ( first_letter >= 'a' && first_letter <= 'z' )
+			{
+				// make them upper case
+				first_letter -= 'a';
+				first_letter += 'A';
+			}
+			if ( first_letter == 'A' || first_letter == 'E' || first_letter == 'I'
+				 || first_letter == 'O' || first_letter == 'U' )
+			{
+				starts_with_vowel = true;
+			}
+
+			sprintf( msg, "%s %s", GetString( Script::GenerateCRC("playerName") ).getString(), starts_with_vowel ?  Script::GetLocalString( "horse_str_isan" ) :  Script::GetLocalString( "horse_str_isa" ) );
+		}
+		break;
+
+		case 0x5f7d73e9: // finalWord
+		{
+			sprintf( msg, "%s", GetWord() );
+		}
+		break;
+	}
+	
+	return msg;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CHorse::GetCurrentRestartIndex( void )
+{
+	
+
+	uint32 num_restarts = get_num_horse_restarts();
+	if ( m_currentRestartIndex >= num_restarts )
+	{
+		//Dbg_MsgAssert( 0, ( "Out of range horse restart index %d %d", m_currentRestartIndex, num_restarts ) );
+	}
+
+	return m_currentRestartIndex;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CHorse::SetNextRestartPoint( void )
+{
+	m_currentRestartIndex++;
+
+	if ( m_currentRestartIndex >= get_num_horse_restarts() )
+	{
+		m_currentRestartIndex = 0;
+	}
+
+#ifdef __USER_GARY__
+	printf( "\n*** Setting next restart point %d ***\n", m_currentRestartIndex );
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
+
+
+
+
diff --git a/Code/Sk/Modules/Skate/horse.h b/Code/Sk/Modules/Skate/horse.h
new file mode 100644
index 0000000..abfd6e3
--- /dev/null
+++ b/Code/Sk/Modules/Skate/horse.h
@@ -0,0 +1,121 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3  												**
+**																			**
+**	Module:			sk						 								**
+**																			**
+**	File name:		horse.h													**
+**																			**
+**	Created by:		7/17/01 - Gary											**
+**																			**
+**	Description:			 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __SK_MODULES_HORSE_H
+#define __SK_MODULES_HORSE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+    class CStruct;
+};
+                                
+namespace Mdl
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+class  CHorse  : public Spt::Class
+{
+	
+
+	enum
+	{
+		vMAX_RESTARTS = 32
+	};
+			
+public:
+	CHorse();
+
+public:
+	void						Init();
+	void						SwitchPlayers();
+	void						StartRun();
+	void 						EndRun();
+	bool						Ended();
+	char*						GetWord();
+	void						SetWord( Str::String word );
+	Str::String					GetWordForPlayer( int skater_id );
+	int							GetCurrentSkaterId();
+	Str::String					GetString( uint32 checksum );
+	uint32						GetCurrentRestartIndex();
+	void						SetNextRestartPoint(void);
+	bool						StatusEquals( Script::CStruct* pParams );
+
+protected:
+	int							get_next_valid_skater_id();
+	int							get_score( int id );
+	
+protected:
+	int							m_scoreToBeat;
+	int							m_currentSkaterId;
+	int							m_nextSkaterId;
+	char						m_string[512];
+	char						m_nextHorseMessage[512];
+	uint32						m_numLetters[Skate::vMAX_SKATERS];
+	uint32						m_numRestarts;
+	uint32						m_currentRestartIndex;
+	uint32						m_status;
+};
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
+
+#endif		// __SK_MODULES_HORSE_H
+
+
+
diff --git a/Code/Sk/Modules/Skate/score.cpp b/Code/Sk/Modules/Skate/score.cpp
new file mode 100644
index 0000000..0d274f4
--- /dev/null
+++ b/Code/Sk/Modules/Skate/score.cpp
@@ -0,0 +1,2855 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:									 								**
+**																			**
+**	File name:																**
+**																			**
+**	Created by:		rjm														**
+**																			**
+**	Description:						 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 		   // for control and trick stuff
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+namespace Front
+{
+	extern void SetScoreTHPS4(char* score_text, int skater_num);
+}
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define NO_SCORE_DURING_UBER_FRIG
+#define MAX_SCORE_DUE_TO_TWEAK (20000)
+	
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static const int MAX_SPIN_VALUES					= 6;
+static const int SPIN_MULT_VALUES[MAX_SPIN_VALUES]	= {2, 3, 4, 5, 6, 7};
+static const int MAX_DEPREC							= 5;
+static const int DEPREC_VALUES[MAX_DEPREC]			= {100, 75, 50, 25, 10};
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+// given a score increment, return a value by which it should be decremented
+// basically return the closest multiple of 10 below score
+int	score_increment(int score)
+{
+	if (score >1000000) return 1000000;
+	if (score >100000) return 100000;
+	if (score >10000) return 10000;
+	if (score >10000) return 1000;
+	if (score >1000) return 100;
+	if (score >100) return 100;
+	if (score >10) return 10;
+	return 1;	
+}
+
+
+
+
+Score::Score() :
+	m_historyTab(16),
+	m_infoTab(8)
+{
+	m_skaterId = 0;
+	m_totalScore = 0;
+	m_scorePot = 0;
+	m_recentScorePot = 0;
+	m_recentSpecialScorePot = 0;
+	m_currentTrick = 0;
+	m_currentMult = 0;
+	m_currentBlockingTrick = -1;
+	m_currentSpinTrick = -1;
+
+	m_specialScore = 0;
+	set_special_is_active(false);
+
+	m_special_interpolator = 0.0f;
+
+	m_longestCombo = 0;
+	m_bestCombo = 0;
+	m_bestGameCombo = 0;
+	
+	//mp_trickWindow = NULL;
+
+	m_debug = (bool) Script::GetInteger("print_trick_info");
+
+	setup_balance_meter_stuff();
+}
+
+
+
+
+Score::~Score()
+{
+	Reset();
+}
+
+int Score::get_packed_score( int start, int end )
+{
+	int packed_score = 0;
+	int current_spin_mult = 2;		// n over 2
+	for (int i = start; i < end; i++)
+	{
+		// if a blocking trick, use default spin multiplier
+		if (m_infoTab[i].flags & vBLOCKING)
+		{
+			current_spin_mult = 2;
+		}
+		// if not a blocking trick, but the last trick was, then use the spin multiplier here
+		// to apply to subsequent tricks up to next blocking one
+		else if (i == 0 || (m_infoTab[i-1].flags & vBLOCKING))
+		{
+			current_spin_mult = spinMult(m_infoTab[i].spin_mult_index);
+		}
+
+		int deprec_mult = deprecMult(m_infoTab[i].mult_index);
+		
+//		if (m_debug)
+//			printf("   base score %d, reduction mult %.2f, spin mult %.1f\n", 
+//				   m_infoTab[i].score, (float) deprec_mult / 100, (float) current_spin_mult / 2);
+		
+		int score = (m_infoTab[i].score * deprec_mult * current_spin_mult) / 200;
+		if (m_infoTab[i].switch_mode)
+		{
+			score	= score * 120 / 100;
+		}
+		
+		packed_score += score;
+	}
+	return packed_score;
+}
+
+void Score::pack_trick_info_table()
+{
+	TrickInfo theTrickInfo;
+	strcpy( theTrickInfo.trickNameText, "... + " );
+	strcpy( theTrickInfo.trickTextFormatted, "...\\_+\\_" );
+	theTrickInfo.score = 0;
+	theTrickInfo.id = 0;
+	theTrickInfo.switch_mode = 0;
+	theTrickInfo.flags = 0;
+	theTrickInfo.mult_index = 0;
+	theTrickInfo.spin_mult_index = 0;
+	theTrickInfo.spin_degrees = 0;
+
+	int firstBlockingTrick = m_infoTab.GetSize();
+
+	for ( int i = 0; i < m_infoTab.GetSize(); i++ )
+	{
+//		if ( ( i != 0 ) && ( m_infoTab[i].flags & vBLOCKING ) )
+		if ( ( i != 0 ) && ( m_infoTab[i].trickNameText[0] == 0 ) )
+		{
+			firstBlockingTrick = i;
+			break;
+		}
+	}
+	
+	// GJ:  see note below
+	if ( ( firstBlockingTrick > m_currentSpinTrick )
+		 || ( firstBlockingTrick > m_currentBlockingTrick ) )
+	{
+		return;
+	}
+
+	theTrickInfo.score = get_packed_score( 0, firstBlockingTrick );
+
+	// shift them all...
+	for ( int i = 0; i < firstBlockingTrick; i++ )
+	{
+		m_infoTab.Remove( 1 );
+
+		m_currentTrick--;
+		m_currentSpinTrick--;
+		m_currentBlockingTrick--;
+
+		// GJ:  these asserts will fire off if the trick list is filled
+		// with more non-blocking tricks than spin tricks...	 it rarely
+		// happens, except for those glitches that lets the player do
+		// infinite tricks.  The if-test above will abort early,
+		// allowing the calling function ( Trigger() ) to gracefully
+		// handle it
+		Dbg_MsgAssert( m_currentTrick > -1, ( "m_currentTrick is negative (%d)", m_currentTrick ) );
+		Dbg_MsgAssert( m_currentSpinTrick > -1, ( "m_currentSpinTrick is negative (%d)", m_currentSpinTrick ) );
+		Dbg_MsgAssert( m_currentBlockingTrick > -1, ( "m_currentBlockingTrick is negative (%d)", m_currentBlockingTrick ) );
+	}
+	
+	m_infoTab[0] = theTrickInfo;
+	
+	// so that the packed score will be correct next time
+	m_infoTab[0].flags |= vBLOCKING;
+
+#ifdef __USER_GARY__
+	Dbg_Message( "Info table has %d elements.", m_infoTab.GetSize() );
+#endif
+}
+
+void Score::print_trick_info_table()
+{
+	for ( int i = 0; i < m_infoTab.GetSize(); i++ )
+	{
+		printf( "info table %d\n", i );
+		printf( "\ttrickname = %s\n", m_infoTab[i].trickNameText); 
+		printf( "\ttrickformatted = %s\n", m_infoTab[i].trickTextFormatted); 
+		printf( "\tscore = %d\n", m_infoTab[i].score); 
+		printf( "\tmult_index = %d\n", m_infoTab[i].mult_index); 
+		printf( "\tspin_mult_index = %d\n", m_infoTab[i].spin_mult_index); 
+		printf( "\tswitch_mode = %d\n", m_infoTab[i].switch_mode);
+/*
+		m_infoTab[i].id,
+		m_infoTab[i].switch_mode,
+		m_infoTab[i].flags,
+*/
+		printf( "-------------------------------------\n" );
+	}
+}
+
+void Score::Update()
+{
+	if (m_specialScore)
+	{
+		// shrink special bar
+		if (m_specialIsActive)
+			m_specialScore = (int) ((float) m_specialScore - (float) Tmr::FrameLength() * 200.0f / Tmr::vRESOLUTION);			
+		else
+			m_specialScore = (int) ((float) m_specialScore - (float) Tmr::FrameLength() * 50.0f / Tmr::vRESOLUTION);			
+		if (m_specialScore <= 0)
+		{
+			set_special_is_active(false);
+			m_specialScore = 0;
+		}
+	}
+
+	if (m_scorePotState == WAITING_TO_COUNT_SCORE_POT)
+	{
+		m_scorePotCountdown--;
+		if (!m_scorePotCountdown)
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+			int index;
+
+			index = 0;
+			if( pSkater )
+			{
+				index = pSkater->GetHeapIndex();
+			}
+			TrickTextCountdown(index);
+			Replay::WriteTrickTextCountdown();
+
+			m_scorePotState = SHOW_COUNTED_SCORE_POT;
+		}
+	}
+	else if (m_scorePotState == SHOW_COUNTED_SCORE_POT)
+	{
+		dispatch_score_pot_value_to_screen(m_countedScorePot, 0);
+		m_countedScorePot -= score_increment(m_countedScorePot);
+		if (m_countedScorePot < 0) 
+		{
+			m_countedScorePot = 0;
+			m_scorePotState = SHOW_ACTUAL_SCORE_POT;
+		}
+	}
+	
+	#if 0
+	Image::RGBA						m_special_rgba[3];
+	float							m_special_interpolator;
+	float							m_special_interpolator_rate;
+	#endif
+	
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Front::CScreenElementPtr p_special_bar;
+	
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+	if( pSkater )
+	{
+		p_special_bar = p_manager->GetElement(CRCD(0xfe1dc544,"the_special_bar_sprite") + pSkater->GetHeapIndex());
+	}
+	else
+	{
+		p_special_bar = p_manager->GetElement(CRCD(0xfe1dc544,"the_special_bar_sprite"));
+	}
+	// clamp scale of bar, so that it doesn't get all crazy
+	float special_mult = m_specialScore / 3000.0f;
+	if (special_mult > 1.0f)
+		special_mult = 1.0f;
+	else if (special_mult < 0.0f)
+		special_mult = 0.0f;
+	p_special_bar->SetScale(special_mult, p_special_bar->GetScaleY());
+	Image::RGBA special_rgba = m_special_rgba[REGULAR_SPECIAL];
+	if (m_specialIsActive)
+	{
+		float interpolate_mult = (cosf(m_special_interpolator) + 1.0f) / 2.0f;
+		m_special_interpolator += m_special_interpolator_rate;
+		
+		special_rgba.r = (uint8) ((float) m_special_rgba[1].r + ((float) (m_special_rgba[2].r - m_special_rgba[1].r)) * interpolate_mult);
+		special_rgba.g = (uint8) ((float) m_special_rgba[1].g + ((float) (m_special_rgba[2].g - m_special_rgba[1].g)) * interpolate_mult);
+		special_rgba.b = (uint8) ((float) m_special_rgba[1].b + ((float) (m_special_rgba[2].b - m_special_rgba[1].b)) * interpolate_mult);
+		special_rgba.a = (uint8) ((float) m_special_rgba[1].a + ((float) (m_special_rgba[2].a - m_special_rgba[1].a)) * interpolate_mult);
+	}
+	p_special_bar->SetRGBA(special_rgba);
+
+	/*
+	HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+	HUD::Panel* panel;
+
+	if(( panel = panel_mgr->GetPanelBySkaterId(m_skaterId, false)))
+	{
+		panel->SetSpecialPercent((float) m_specialScore / 3000.0f, m_specialIsActive);
+	}
+	*/
+}
+
+
+
+
+/*
+	Called from CSkater constructor
+*/
+void Score::SetSkaterId(short skater_id) 
+{
+	m_skaterId = skater_id;
+	//HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+	//mp_trickWindow = panel_mgr->GetTrickWindow(m_skaterId);
+}
+
+
+
+
+void Score::copy_trick_name_with_special_spaces(char *pOut, const char *pIn)
+{
+	int count = 0;
+	while(*pIn != '\0')
+	{
+		Dbg_Assert(count < TrickInfo::TEXT_BUFFER_SIZE);
+		if (*pIn == ' ')
+		{
+			*pOut++ = '\\';
+			*pOut++ = '_';
+			count += 2;
+		}
+		else
+		{
+			*pOut++ = *pIn;
+			count++;
+		}
+		pIn++;
+	}
+	*pOut = '\0';
+}
+
+/*
+	Added by Ken. Returns the Id of the last trick added, or 0 if there are none.
+	Added for use by the skater Display commmand when the AddSpin flag is specified.
+	In that case, if the skater's current trick is the same as the last one in the score object,
+	then instead of adding the same trick again it will add spin to the last one instead.
+	Used by some flatland tricks.
+	(See skater.cpp for the above code, search for | Display |)
+*/
+uint32 Score::GetLastTrickId()
+{
+	if (m_infoTab.GetSize())
+	{
+		return m_infoTab.Last().id;
+	}
+	return 0;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32 Score::GetTrickId( int trickIndex )
+{
+	Dbg_MsgAssert( trickIndex >= 0 && trickIndex < m_currentTrick, ( "trickIndex out of range" ) );
+	return m_infoTab[trickIndex].id;
+}
+
+void Score::TrickTextPulse(int index)
+{
+	Script::CStruct* pParams=new Script::CStruct;
+	pParams->AddChecksum( CRCD(0x33124d2e,"trick_text_container_id"), CRCD(0x1e55886e,"trick_text_container")  + index);
+	pParams->AddChecksum( CRCD(0x6e7c7ba7,"the_trick_text_id"), CRCD(0x44727dae,"the_trick_text")  + index);
+	pParams->AddChecksum( CRCD(0x6d02989c,"the_score_pot_text_id"), CRCD(0xf4d3a70e,"the_score_pot_text") + index);
+	Script::RunScript(CRCD(0x3bd51fe5,"trick_text_pulse"), pParams);
+	delete pParams;
+}
+
+void Score::TrickTextCountdown(int index)
+{
+	Script::CStruct* pParams=new Script::CStruct;
+	pParams->AddChecksum( CRCD(0x33124d2e,"trick_text_container_id"), CRCD(0x1e55886e,"trick_text_container")  + index );
+	pParams->AddChecksum( CRCD(0x6e7c7ba7,"the_trick_text_id"), ( CRCD(0x44727dae,"the_trick_text") ) + index );
+	pParams->AddChecksum( CRCD(0x6d02989c,"the_score_pot_text_id"), ( CRCD(0xf4d3a70e,"the_score_pot_text") ) + index );
+	Script::RunScript(CRCD(0xee9ce723,"trick_text_countdown"), pParams );
+	delete pParams;
+}
+
+void Score::TrickTextLanded(int index)
+{
+	Script::CStruct* pParams=new Script::CStruct;
+	pParams->AddChecksum( CRCD(0x33124d2e,"trick_text_container_id"), ( CRCD(0x1e55886e,"trick_text_container") ) + index );
+	pParams->AddChecksum( CRCD(0x6e7c7ba7,"the_trick_text_id"), ( CRCD(0x44727dae,"the_trick_text") ) + index );
+	pParams->AddChecksum( CRCD(0x6d02989c,"the_score_pot_text_id"), ( CRCD(0xf4d3a70e,"the_score_pot_text") ) + index );
+	Script::RunScript(CRCD(0xc890e875,"trick_text_landed"), pParams);
+	delete pParams;
+}
+
+void Score::TrickTextBail(int index)
+{
+	Script::CStruct* pParams=new Script::CStruct;
+	pParams->AddChecksum( CRCD(0x33124d2e,"trick_text_container_id"), ( CRCD(0x1e55886e,"trick_text_container") ) + index );
+	pParams->AddChecksum( CRCD(0x6e7c7ba7,"the_trick_text_id"), ( CRCD(0x44727dae,"the_trick_text") ) + index );
+	pParams->AddChecksum( CRCD(0x6d02989c,"the_score_pot_text_id"), ( CRCD(0xf4d3a70e,"the_score_pot_text") ) + index );
+	Script::RunScript(CRCD(0xaa7f404d,"trick_text_bail"), pParams );
+	delete pParams;
+}
+
+/*
+	Called when a trick is done, adding it to the trick sequence.
+*/
+const int TRICK_LIMIT = 250;
+void Score::Trigger(char *trick_name, int base_score, Flags flags)
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	
+	bool null_trick=false;
+	if (!trick_name[0])
+	{
+		null_trick=true;
+	}
+		
+	// once some trick/score has appeared on-screen
+	// we know that the trick has started
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+	Dbg_Assert( pSkater );
+	Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+	Dbg_Assert( pTrickComponent );
+    Obj::CStatsManagerComponent* pStatsManagerComponent = GetStatsManagerComponentFromObject(pSkater);
+	Dbg_Assert( pStatsManagerComponent );
+	
+	// Only flag a trick as having started if a real trick is being triggered
+	if( trick_name && trick_name[0] )
+	{
+		pTrickComponent->SetFirstTrickStarted( true );
+	}
+	
+    bool is_switch = false; // need to make this grab the switch flag eventually!!!
+    pStatsManagerComponent->SetTrick( trick_name, base_score, is_switch );
+
+	// We place a high limit on tricks, so if they 
+	// cheat with perfect baclance, and keep tricking
+	// then they will not run out of memory and crash
+	if (m_currentTrick > TRICK_LIMIT-1)
+	{
+		pack_trick_info_table();
+
+		if (m_currentTrick > TRICK_LIMIT-1)
+		{
+			// GJ:  You should never be able to hit the trick limit any more
+
+			if ( !null_trick )
+			{
+				printf ("Trick Limit (%d) Reached\n",TRICK_LIMIT);
+				//tweak_last_valid_trick(base_score);
+				m_infoTab.Last().score 	+= base_score;  // give them the score, just stop recording tricks
+				m_currentMult++;						// but still keep up the multipler 
+				captureScore();
+			}
+			return;
+		}
+	}
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+	
+	// trick type is based on id + switch mode
+	uint32 id = Script::GenerateCRC(trick_name);
+	uint32 switch_mode = (flags & (vSWITCH | vNOLLIE)) >> 1;
+		
+	if (m_infoTab.GetSize() < TRICK_LIMIT)
+	{
+		m_infoTab.Add(new TrickInfo);
+		m_infoTab.Last().score 				= 0;
+	}
+	// if we have filled up the trick limit, then we just use the last one we added
+	
+	m_infoTab.Last().score 				+= base_score;
+	//printf("triggering trick with id %s\n",trick_name);
+	m_infoTab.Last().id 				= id;
+	m_infoTab.Last().switch_mode		= switch_mode;
+	m_infoTab.Last().flags 				= flags;
+	m_infoTab.Last().spin_mult_index 	= 0;
+	m_infoTab.Last().trickNameText[0]	= 0;
+	m_infoTab.Last().trickTextFormatted[0]	= 0;
+	m_infoTab.Last().spin_degrees		= 0;
+	
+	if (!null_trick)
+	{
+		copy_trick_name_with_special_spaces(m_infoTab.Last().trickNameText, trick_name);
+		
+		if ( flags & Score::vCAT )
+		{
+			sprintf(m_infoTab.Last().trickTextFormatted, "\\c3%s\\c0 ", m_infoTab.Last().trickNameText);
+		}
+		else if ( flags & Score::vSPECIAL )
+		{
+			// K: If the 'special' flag is set, use color 2
+			sprintf(m_infoTab.Last().trickTextFormatted, "\\c2%s\\c0 ", m_infoTab.Last().trickNameText);
+		}
+		else
+		{
+			sprintf(m_infoTab.Last().trickTextFormatted, "%s ", m_infoTab.Last().trickNameText);
+		}
+		
+		// toss the "+" onto the end of the previous trick
+		
+		// K: This bit of code used to do a sprintf, but I changed it to use a strcat instead
+		// to preserve any color formatting added previously, such as the special-trick color
+		// added above.
+		
+		// This is a loop so that null tricks (tricks with no name) get skipped over.
+		int index=m_infoTab.GetSize() - 2;
+		while (true)
+		{
+			if (index<0)
+			{
+				break;
+			}
+				
+			char *p_trick_text_formatted=m_infoTab[index].trickTextFormatted;
+			Dbg_MsgAssert(p_trick_text_formatted,("NULL p_trick_text_formatted"));
+			
+			int len=strlen(p_trick_text_formatted);
+			if (*p_trick_text_formatted == '.')
+			{
+				// skip items that begin with "..."
+				// (or else you get things like
+				// "... +  + blah blah")
+			}
+			else if (len)
+			{
+				// Remove any trailing space character		
+				if (p_trick_text_formatted[len-1]==' ')
+				{
+					p_trick_text_formatted[len-1]=0;
+				}	
+				
+				// Wack on the +
+				strcat(p_trick_text_formatted,"\\_+ ");
+				break;
+			}
+			// Keep searching backwards until a named trick is found.
+			--index;
+		}
+	}
+			
+	// if the last trick was a blocking trick, make this trick the spin trick
+	// (or if this is the first trick)
+	if (m_currentBlockingTrick == m_currentTrick - 1)
+		m_currentSpinTrick = m_currentTrick;
+	if (flags & vBLOCKING)
+	{
+		m_currentBlockingTrick = m_currentTrick;
+	}
+
+	// set depreciation stuff
+	//Game::CGameMode* pGameMode = skate_mod->GetGameMode();
+	if (!(flags & vGAP) && !(flags & vNODEGRADE) /*&& pGameMode->ShouldDegradeScore()*/)
+	{
+		// trick is not a gap, so figure out what its depreciation index will be
+		TrickHistory *pHistory = m_historyTab.GetItem(id);
+		if (!pHistory)
+		{
+			// if no history for this trick, create one
+			pHistory = new TrickHistory;
+			m_historyTab.PutItem(id, pHistory);
+			for (int s = 0; s < 4; s++)
+			{
+				pHistory->total_count[s] = 0;
+				pHistory->combo_count[s] = 0;
+			}
+		}
+		m_infoTab.Last().mult_index = pHistory->total_count[switch_mode] + (pHistory->combo_count[switch_mode]++);
+	}
+	else
+	{
+		// gaps don't depreciate
+		// no depreciation used in free skate
+		m_infoTab.Last().mult_index = 0;
+	}
+	
+	m_currentTrick++;
+	if (!null_trick)
+	{
+		#ifdef NO_SCORE_DURING_UBER_FRIG
+		int previousMult = m_currentMult;
+		#endif
+		
+		m_currentMult++;
+		if (m_currentMult == 1)
+		{
+			Obj::CSkaterRunTimerComponent* pSkaterRunTimerComponent = GetSkaterRunTimerComponentFromObject(pSkater);
+			Dbg_Assert(pSkaterRunTimerComponent);
+			pSkaterRunTimerComponent->ComboStarted();
+			
+			pSkater->BroadcastEvent(CRCD(0x670fda8c, "SkaterEnterCombo"));
+		}
+
+		#if 1	
+		// (Mick) check if this is the last of a long line of non-blocking tricks 
+		// and if so, then decrement the multiplier (leaving it unchanged)
+		int non_block_count = 0;   
+		for (int i = m_currentTrick-1; i >0; i--)
+		{
+			// if a blocking trick, use default spin multiplier
+			if (m_infoTab[i].flags & vBLOCKING)
+			{
+				break;
+			}
+			non_block_count++;
+		}	
+//		printf ("%3d: ",non_block_count);
+		if (non_block_count > 10)
+		{
+			#ifdef	__NOPT_ASSERT__
+			printf ("CHEAT PREVENTION:  Limiting non blocking combo to 10\n");
+			#endif
+			m_infoTab.Last().score = 0;
+			m_currentMult--;
+		}
+		#endif
+		
+		#ifdef NO_SCORE_DURING_UBER_FRIG
+		Obj::CSkaterAdjustPhysicsComponent* pSkaterAdjustPhysicsComponent = GetSkaterAdjustPhysicsComponentFromObject(pSkater);
+		if (pSkaterAdjustPhysicsComponent && pSkaterAdjustPhysicsComponent->UberFriggedThisFrame())
+		{
+			m_infoTab.Last().score = 0;
+			m_currentMult = previousMult;
+		}
+		#endif
+	}
+	
+	if (m_debug)
+	{
+		printf("Adding trick %s\n", trick_name);
+	}
+	
+	captureScore();
+
+	dispatch_trick_sequence_to_screen();
+
+	TrickTextPulse(pSkater->GetHeapIndex());
+	Replay::WriteTrickTextPulse();	
+		
+	m_scorePotState = SHOW_ACTUAL_SCORE_POT;
+	dispatch_score_pot_value_to_screen(m_scorePot, m_currentMult);
+	
+	/*
+	if (mp_trickWindow)
+	{
+		Dbg_MsgAssert(mp_trickWindow,( "no trick window defined"));
+	
+		HUD::TrickWindow::TrickType trick_type = HUD::TrickWindow::vREGULAR;
+		if (flags & vGAP) trick_type = HUD::TrickWindow::vGAP;	
+		if (flags & vSPECIAL) trick_type = HUD::TrickWindow::vSPECIAL;	
+		
+		mp_trickWindow->AddTrick(trick_name, m_currentTrick, trick_type);
+	}
+	*/
+	Mem::Manager::sHandle().PopContext();
+}
+
+
+
+
+/* 
+	Called when spin changes. (From CSkater::HandleAirRotation()). 
+*/
+void Score::UpdateSpin(int spin_degrees)
+{   
+	// if score pot was reset, m_currentSpinTrick is no longer valid
+	if (m_currentTrick == 0 || m_currentSpinTrick < 0)
+		return;
+	
+	// If a trick is blocking, then we don't want to add any
+	// more spin to it after the initial spin		
+	if (m_infoTab[m_currentSpinTrick].flags & vBLOCKING)
+	{
+		// printf ("Trick %d is blocing, %d degs\n",m_currentSpinTrick,spin_degrees);
+		return;
+	}
+	
+	SetSpin(spin_degrees);
+}
+
+/* 
+	Called right after call to Trigger(). Current spin trick might be trick just added. Also 
+	called by Update() (above).
+	
+	Updates the spin multiplier on the current spin trick. If the skater has increased his
+	spin over the highest spin last recorded (increments of 180), then save the new spin
+	multiplier.
+*/
+void Score::SetSpin(int spin_degrees)
+{
+	// if score pot was reset, m_currentSpinTrick is no longer valid
+	if (m_currentTrick == 0 || m_currentSpinTrick < 0)
+		return;
+
+	int spin_position = spin_degrees;
+    const char *p_direction = "";
+
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+    Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+    Dbg_Assert( pSkater );
+
+    Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( pSkater );
+	bool is_flipped = pAnimationComponent->IsFlipped();
+
+    // frontside or backside?
+    if (spin_position < 0)
+    { 
+        spin_position = -spin_position;
+        if ( is_flipped )
+        {
+            p_direction = "BS";
+        }
+        else
+        {
+            p_direction = "FS";
+        }
+    }
+    else
+    {
+        if ( is_flipped )
+        {
+            p_direction = "FS";
+        }
+        else
+        {
+            p_direction = "BS";
+        }
+    }
+
+    // add a little fudge factor: doesn't need to fully rotate 180 to earn 180
+	spin_position += (int)Obj::GetPhysicsFloat(CRCD(0x50c5cc2f, "spin_count_slop"));	
+	int spin_index = spin_position / 180;
+
+	// TT#3426 - Clamp spins if too high, to prevent cheaters getting stuck and doing a lot of tricks	
+	// Not needed as spins only give you extra multiplier up to 900 anyway
+	#if 0
+	if (spin_index > 11)
+	{
+		#ifdef	__NOPT_ASSERT__
+		printf ("CHEAT WARNING, clamping spins\n");
+		#endif
+		spin_index = 11;
+	}
+	#endif
+	
+	// update the rotation amount of this trick
+	m_infoTab[m_currentSpinTrick].spin_degrees = spin_degrees;
+
+	// don't bother updating text if spin index isn't big enough
+	if (m_infoTab[m_currentSpinTrick].spin_mult_index < spin_index)
+	{
+		if (m_debug)
+		{
+			printf("New spin value %d:\n", spin_index * 180);
+		}
+		
+        // Not sure why this is necessary, but Ollies w/o another trick
+        // have the wrong spin direction on half rotations. i.e. 180, 540, 900, etc.
+        if ( m_infoTab[m_currentSpinTrick].id == CRCD(0x9b65d7b8,"Ollie") )
+        {
+            if ( (spin_index%2) == 1 )
+            {
+                if ( p_direction == "FS")
+                {
+                    p_direction = "BS";
+                }
+                else
+                {
+                    p_direction = "FS";
+                }
+            }
+        }
+        
+		int last_trick = m_infoTab.GetSize() - 1;
+		Dbg_Assert(last_trick >= 0);
+		int shown_spin = spin_index * 180;
+		
+		const char *p_format="%d\\_%s";
+		if (last_trick > m_currentSpinTrick)
+		{
+			if (m_infoTab[m_currentSpinTrick].flags & Score::vCAT)
+			{
+				p_format="\\c3%s\\_%d\\_%s\\c0\\_+ ";
+			}	
+			else
+			{
+				if (m_infoTab[m_currentSpinTrick].flags & Score::vSPECIAL)
+    			{
+    				p_format="\\c2%s\\_%d\\_%s\\c0\\_+ ";
+    			}
+                else
+                {
+                    p_format="%s\\_%d\\_%s\\_+ ";
+                }
+			}	
+		}			
+		else
+		{
+			if (m_infoTab[m_currentSpinTrick].flags & Score::vCAT)
+			{
+				p_format="\\c3%s\\_%d\\_%s\\c0";
+			}	
+			else
+			{
+				if (m_infoTab[m_currentSpinTrick].flags & Score::vSPECIAL)
+    			{
+    				p_format="\\c2%s\\_%d\\_%s\\c0";
+    			}
+                else
+                {
+                    p_format="%s\\_%d\\_%s";
+                }
+			}	
+		}		
+		
+        sprintf(m_infoTab[m_currentSpinTrick].trickTextFormatted, p_format, p_direction, shown_spin, m_infoTab[m_currentSpinTrick].trickNameText);
+		dispatch_trick_sequence_to_screen();
+		
+		/*
+		if (mp_trickWindow)
+		{
+			mp_trickWindow->ChangeSpin(spin_index * 180, m_currentSpinTrick);
+		}
+		*/
+		m_infoTab[m_currentSpinTrick].spin_mult_index = spin_index;
+		captureScore();
+
+        Obj::CStatsManagerComponent* pStatsManagerComponent = GetStatsManagerComponentFromObject( pSkater );
+        Dbg_Assert( pStatsManagerComponent );
+        pStatsManagerComponent->SetSpin( (spin_index*180) );
+		
+		m_scorePotState = SHOW_ACTUAL_SCORE_POT;
+		dispatch_score_pot_value_to_screen(m_scorePot, m_currentMult);
+		
+		pSkater->BroadcastEvent(CRCD(0x68b887bb, "SkaterSpinDisplayed"));
+	}
+}
+
+void Score::TweakTrick(int tweak_value )
+{	
+	if (m_currentTrick <= 0) return;
+	// K: If the last trick was a 'Null' trick, then do not add the score.
+	// This is to fix TT5718, where it was possible to do a manual, then jump out of it
+	// really quickly whilst still getting some score appearing on screen, even though you
+	// did not really get those points because the non-null trick name had not been displayed yet.
+	// (At the start of the manual, a 'null' trick is done using SetTrickName "", simply in order
+	// to do a BlockSpin, but we don't want that null trick to enable the score counter)
+	if (m_infoTab[m_currentTrick-1].trickNameText[0]==0) return;
+	
+	#if 1	
+	// (Mick) check if this is the last of a long line of non-blocking tricks 
+	// and if so, then decrement the multiplier (leaving it unchanged)
+	int non_block_count = 0;   
+	for (int i = m_currentTrick-1; i >0; i--)
+	{
+		// if a blocking trick, use default spin multiplier
+		if (m_infoTab[i].flags & vBLOCKING)
+		{
+			break;
+		}
+		non_block_count++;
+	}	
+//		printf ("%3d: ",non_block_count);
+	if (non_block_count > 10)
+	{
+		#ifdef	__NOPT_ASSERT__
+		printf ("CHEAT PREVENTION:  Limiting tweak after 10 nonblocking trick\n");
+		#endif
+		return;
+	}
+	
+	if (!(m_infoTab[m_currentTrick-1].flags & vBLOCKING) && m_infoTab[m_currentTrick-1].score > MAX_SCORE_DUE_TO_TWEAK) return;
+	#endif
+	
+	#ifdef NO_SCORE_DURING_UBER_FRIG
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById(m_skaterId);
+	Obj::CSkaterAdjustPhysicsComponent* pSkaterAdjustPhysicsComponent = GetSkaterAdjustPhysicsComponentFromObject(pSkater);
+	if (pSkaterAdjustPhysicsComponent && pSkaterAdjustPhysicsComponent->UberFriggedThisFrame()) return;
+	#endif
+	
+	m_infoTab[m_currentTrick-1].score += tweak_value;
+	captureScore();
+	
+	dispatch_trick_sequence_to_screen();
+	m_scorePotState = SHOW_ACTUAL_SCORE_POT;
+	dispatch_score_pot_value_to_screen(m_scorePot, m_currentMult);
+}
+
+void Score::Land( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client;
+    
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Game::CGameMode* pGameMode = skate_mod->GetGameMode();
+	
+	captureScore();
+	
+	/*
+	if (mp_trickWindow)
+	{
+		Dbg_MsgAssert(mp_trickWindow,( "no trick window defined"));
+		mp_trickWindow->Count(true);
+    }
+	*/
+	
+	// GLOOBY
+	int trick_score = m_scorePot * m_currentMult;
+
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+	Dbg_Assert( pSkater );
+	Dbg_Assert( pSkater->IsLocalClient());
+	// if playing graffiti, then update the colors as appropriate
+	bool debug_graffiti = Script::GetInteger( "debug_graffiti" );
+    Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+	pGoalManager->Land();
+	if ( debug_graffiti || ( pGameMode->IsTrue("should_modulate_color") ) && ( trick_score > 0 ) )
+	{
+		// update the server		
+		LogTrickObjectRequest( trick_score );
+	}
+
+	// flushes the graffiti trick buffer
+	GetTrickComponentFromObject(pSkater)->SetGraffitiTrickStarted( false );
+    
+    if (!Script::GetInteger("NewSpecial"))	
+	{
+		// update special meter (when we land)
+		m_specialScore += m_scorePot * m_currentMult;
+		if (m_specialScore >= 3000)
+		{
+			set_special_is_active(true);
+			m_specialScore = 3000;
+		}
+		if (m_specialScore < 0)
+		{
+			m_specialScore = 0;
+		}
+	}
+	else
+	{
+		m_recentSpecialScorePot = 0;		// ensure next trick is not infuenced by this trick
+	}
+	
+	// update combo records
+	if ( m_currentMult > m_longestCombo )
+	{
+		// printf("length: old record - %i, new record - %i\n", m_longestCombo, m_currentMult);
+		m_longestCombo = m_currentMult;
+	}
+	if ( GetLastScoreLanded() > m_bestCombo )
+	{
+		m_bestCombo = GetLastScoreLanded();
+		// printf("points: old record - %i, new record - %i\n", m_bestCombo, GetLastScoreLanded());
+	}
+
+	if ( GetLastScoreLanded() > m_bestGameCombo )
+	{
+		if( gamenet_man->InNetGame() && ( pGameMode->GetNameChecksum() != CRCD(0x1c471c60,"netlobby")))
+		{
+			Net::MsgDesc msg_desc;
+			Net::Client* client;
+			GameNet::MsgScoreLanded msg;
+	
+			msg.m_Score = GetLastScoreLanded();
+			msg.m_GameId = gamenet_man->GetNetworkGameId();
+			
+			msg_desc.m_Id = GameNet::MSG_ID_COMBO_REPORT;
+			msg_desc.m_Data = &msg;
+			msg_desc.m_Length = sizeof( GameNet::MsgScoreLanded );
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+	
+			client = gamenet_man->GetClient( pSkater->GetSkaterNumber());
+			Dbg_Assert( client );
+					
+			client->EnqueueMessageToServer( &msg_desc );
+		}
+		m_bestGameCombo = GetLastScoreLanded();
+	}
+
+
+	// if not degrading score, we need to clear our history
+	if (!pGameMode->ShouldDegradeScore())  
+	{
+		// clear combo history, and total history as well, althought that really should never ahve been set.
+		for (int i = 0; i < m_historyTab.getSize(); i++)
+		{
+			TrickHistory *pHistory = m_historyTab.GetItemByIndex(i);
+			for (int s = 0; s < 4; s++)
+			{
+				pHistory->total_count[s] = 0;
+				pHistory->combo_count[s] = 0;
+			}
+		}
+		// and reset the rail counters between combos  
+		reset_robot_detection();
+	}
+	else
+	{
+		// update trick history -- all counts from trick sequence are added to counts from session
+		for (int i = 0; i < m_historyTab.getSize(); i++)
+		{
+			TrickHistory *pHistory = m_historyTab.GetItemByIndex(i);
+			for (int s = 0; s < 4; s++)
+			{
+				pHistory->total_count[s] += pHistory->combo_count[s];
+				pHistory->combo_count[s] = 0;
+			}
+		}
+	}
+	
+	if (m_currentTrick)
+	{
+		m_scorePotState = WAITING_TO_COUNT_SCORE_POT;
+		m_countedScorePot = m_scorePot * m_currentMult;
+		m_scorePotCountdown = 150; // about 3 seconds		
+		dispatch_score_pot_value_to_screen(m_scorePot * m_currentMult, 0);
+
+		int index;
+		index = pSkater->GetHeapIndex();
+		TrickTextLanded(index);
+		Replay::WriteTrickTextLanded();
+	}
+	
+	resetScorePot();
+
+	int old_score = GetTotalScore(); 
+
+	// if we're not in graffiti mode, then update the total score
+	// and panel (in graffiti mode, the client doesn't have the
+	// authority to set its own scores)
+	if ( !pGameMode->IsTrue(CRCD(0x11941568, "should_modulate_color")) )
+	{
+		if( pGameMode->ShouldTrackTrickScore())
+		{
+			if( ( pGameMode->ShouldAccumulateScore()) || 
+				( ( pGameMode->ShouldTrackBestCombo()) &&
+				  ( GetTotalScore() < trick_score )))
+			{
+				if( !gamenet_man->OnServer())
+				{
+					GameNet::MsgScoreLanded msg;
+					Net::MsgDesc msg_desc;
+
+					msg.m_GameId = gamenet_man->GetNetworkGameId();
+					msg.m_Score = trick_score;
+					
+					msg_desc.m_Data = &msg;
+					msg_desc.m_Length = sizeof( GameNet::MsgScoreLanded );
+					msg_desc.m_Id = GameNet::MSG_ID_LANDED_TRICK;
+					msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+					msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+
+					client = gamenet_man->GetClient( pSkater->GetSkaterNumber());
+					Dbg_Assert( client );
+					
+					client->EnqueueMessageToServer( &msg_desc );
+				}
+
+				if( pGameMode->ShouldTrackBestCombo())
+				{
+					SetTotalScore( trick_score );
+				}
+				else
+				{
+					SetTotalScore( m_totalScore + trick_score );
+				}
+			}
+			else if( !pGameMode->ShouldTrackBestCombo())
+			{
+				// don't reset if the score is frozen
+				if ( !pGameMode->ScoreFrozen() )
+					SetTotalScore( trick_score );
+			}
+		}
+	}
+
+	int new_score = GetTotalScore();
+	check_high_score(old_score, new_score);
+
+	
+	Obj::CSkaterRunTimerComponent* pSkaterRunTimerComponent = GetSkaterRunTimerComponentFromObject(pSkater);
+	Dbg_Assert(pSkaterRunTimerComponent);
+	pSkaterRunTimerComponent->ComboEnded();
+
+	// XXX
+//	printf("TRICK SCORE = %d\n", new_score);	
+}
+
+
+void Score::ForceSpecial( void )
+{
+	set_special_is_active(true);
+	m_specialScore = 3000;
+}
+
+
+
+
+/*
+	Called whenever skater bails. Clears combo history, resets score pot, stops special
+*/
+void Score::Bail( bool clearScoreText )
+{    
+	// update trick history -- clear counts for this combo
+	for (int i = 0; i < m_historyTab.getSize(); i++)
+	{
+		TrickHistory *pHistory = m_historyTab.GetItemByIndex(i);
+		for (int s = 0; s < 4; s++)
+			pHistory->combo_count[s] = 0;
+	}
+
+	if (m_currentTrick)
+	{
+		m_scorePotState = SHOW_ACTUAL_SCORE_POT;
+		
+		if (!clearScoreText)
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+			Dbg_Assert( pSkater );
+	
+			dispatch_score_pot_value_to_screen(m_scorePot * m_currentMult, 0);
+			
+			int index;
+			index = pSkater->GetHeapIndex();
+			TrickTextBail(index);
+		}
+		else
+		{
+			dispatch_trick_sequence_to_screen(true);
+			dispatch_score_pot_value_to_screen( 0, 0 );
+		}
+		
+		// Replay::WriteTrickTextBail();
+	}
+	
+	/*
+	if (mp_trickWindow)
+	{
+		Dbg_MsgAssert(mp_trickWindow,( "no trick window defined"));
+		mp_trickWindow->Collapse();
+	}
+	*/
+	
+	resetScorePot();
+
+	m_specialScore = 0;
+	set_special_is_active(false);
+	
+	Obj::CSkaterRunTimerComponent* pSkaterRunTimerComponent
+		= GetSkaterRunTimerComponentFromObject(Mdl::Skate::Instance()->GetSkaterById( m_skaterId ));
+	Dbg_Assert(pSkaterRunTimerComponent);
+	pSkaterRunTimerComponent->ComboEnded();
+
+    Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+	pGoalManager->Bail();
+}
+
+
+
+// completely resets score object
+void Score::Reset()
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+	//GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	//Net::Server* server; 
+
+	printf ("Score::RESET ...............\n");
+
+	reset_robot_detection();
+
+	resetScorePot();
+
+	Lst::LookupTableDestroyer destroyer(&m_historyTab);	
+	destroyer.DeleteTableContents();
+	
+	/*
+	if (mp_trickWindow)
+	{
+		mp_trickWindow->ResetWindow();
+		mp_trickWindow->ResetRecords();
+	}
+	*/
+
+	if( pSkater && pSkater->IsLocalClient())
+	{
+		dispatch_trick_sequence_to_screen();
+		dispatch_score_pot_value_to_screen( 0, 0 );
+	}
+	
+	if( ( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0xbff33600,"netfirefight")) ||
+		( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x3d6d444f,"firefight")))
+	{
+		m_totalScore = 100;
+	}
+	else
+	{
+		m_totalScore = 0;
+	}
+	
+	m_scorePot = 0;
+	m_recentScorePot = 0;
+	m_recentSpecialScorePot = 0;
+	m_specialScore = 0;
+	m_bestGameCombo = 0;
+	
+	set_special_is_active(false);
+
+	// Mick:  The next two lines clear any pending "countdown" score that might show up again after
+	// we switch levels (or return to the skateshop)
+	m_countedScorePot = 0;
+	m_scorePotState = SHOW_ACTUAL_SCORE_POT;
+
+	setSpecialBarColors(); // Split into seperate function so that special bar colors could be updated for themes
+	
+	Obj::CSkater* p_skater = Mdl::Skate::Instance()->GetSkaterById( m_skaterId );
+	if (p_skater && p_skater->IsLocalClient())
+	{
+		Obj::CSkaterRunTimerComponent* pSkaterRunTimerComponent = GetSkaterRunTimerComponentFromObject(p_skater);
+		Dbg_Assert(pSkaterRunTimerComponent);
+		pSkaterRunTimerComponent->ComboEnded();
+	}
+
+	/*
+	HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+	HUD::Panel* panel;
+
+	if(( panel = panel_mgr->GetPanelBySkaterId(m_skaterId, false)))
+	{
+		panel->SetScore(0, true);
+	}
+	*/
+}
+
+void Score::setSpecialBarColors()
+{
+    Script::CArray *p_special_bar_colors = Script::GetArray("special_bar_colors", Script::ASSERT);
+	for (int i = 0; i < 3; i++)
+	{
+		Script::CArray *p_rgba = p_special_bar_colors->GetArray(i);
+
+		m_special_rgba[i].r = p_rgba->GetInteger(0);
+		m_special_rgba[i].g = p_rgba->GetInteger(1);
+		m_special_rgba[i].b = p_rgba->GetInteger(2);
+		m_special_rgba[i].a = p_rgba->GetInteger(3);
+	}
+	m_special_interpolator_rate = Script::GetFloat("special_bar_iterpolator_rate", Script::ASSERT);
+
+}
+
+
+/*
+	Totals up score pot of present trick sequence, sends number to trick text window
+*/
+void Score::captureScore()
+{
+	// if no tricks in list, then no score to capture 
+	if (m_currentTrick == 0) 
+	{
+		m_recentSpecialScorePot = 0;
+		m_recentScorePot = 0;
+		return;
+	}
+
+	if (m_debug)
+		printf("Score pot:\n");
+	
+	// compute score pot
+	m_scorePot = 0;
+	int current_spin_mult = 2;		// n over 2
+	for (int i = 0; i < m_currentTrick; i++)
+	{
+		// if a blocking trick, use default spin multiplier
+		if (m_infoTab[i].flags & vBLOCKING)
+		{
+			current_spin_mult = 2;
+		}
+		// if not a blocking trick, but the last trick was, then use the spin multiplier here
+		// to apply to subsequent tricks up to next blocking one
+		else
+		{
+			if (i == 0 || (m_infoTab[i-1].flags & vBLOCKING))
+			{
+				current_spin_mult = spinMult(m_infoTab[i].spin_mult_index);
+			}
+		}
+
+		int deprec_mult = deprecMult(m_infoTab[i].mult_index);
+		
+		if (m_debug)
+			printf("   base score %d, reduction mult %.2f, spin mult %.1f\n", 
+				   m_infoTab[i].score, (float) deprec_mult / 100, (float) current_spin_mult / 2);
+		
+		int score = (m_infoTab[i].score * deprec_mult * current_spin_mult) / 200;
+		if (m_infoTab[i].switch_mode)
+		{
+			score	= score * 120 / 100;
+		}
+		
+		m_scorePot += score;
+	}
+	if (m_debug)
+		printf("   total %d\n", m_scorePot);
+	
+	/*
+	if( mp_trickWindow )
+	{
+		mp_trickWindow->SetScore(m_scorePot);
+	}
+	*/
+
+	if (Script::GetInteger("NewSpecial"))	
+	{
+//		printf ("Score = %d, Recent = %d\n",m_scorePot * m_currentTrick , m_recentScorePot);
+		if (m_scorePot * m_currentMult > m_recentSpecialScorePot)
+		{
+			// update special meter (constantly, as we score)
+			m_specialScore += m_scorePot * m_currentMult - m_recentSpecialScorePot;
+			if (m_specialScore <0)
+			{
+				set_special_is_active(false);
+				m_specialScore = 0;
+			}
+			if (m_specialScore >= 3000)
+			{
+				set_special_is_active(true);
+				m_specialScore = 3000;
+			}	
+		}
+	}
+	m_recentScorePot = m_scorePot * m_currentMult;
+	m_recentSpecialScorePot = m_scorePot * m_currentMult;
+}
+
+
+
+void Score::resetScorePot()
+{
+	m_scorePot = 0;
+	Lst::DynamicTableDestroyer destroyer(&m_infoTab);	
+	destroyer.DeleteTableContents();
+	m_currentTrick = 0;
+	m_currentMult = 0;
+	m_currentBlockingTrick = -1;
+	m_currentSpinTrick = -1;
+	reset_robot_detection_combo();
+	//m_recentScorePot = 0;
+}
+
+
+
+
+void Score::dispatch_trick_sequence_to_screen ( bool clear_text )
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+	int index = 0;
+
+	if( pSkater )
+	{
+		index = pSkater->GetHeapIndex();
+	}
+	Front::CTextBlockElement *p_text_block = (Front::CTextBlockElement *) p_manager->GetElement(Script::GenerateCRC("the_trick_text") + index ).Convert();
+	if (!p_text_block) return;
+
+	const char *pp_string_tab[TRICK_LIMIT];
+
+	if (!clear_text)
+	{
+		Dbg_Assert(m_infoTab.GetSize() <= TRICK_LIMIT);
+		for (int i = 0; i < m_infoTab.GetSize(); i++)
+		{
+			pp_string_tab[i] = m_infoTab[i].trickTextFormatted;
+		}
+
+		p_text_block->SetText(pp_string_tab, m_infoTab.GetSize());
+		// Replay::WriteTrickText(pp_string_tab, m_infoTab.GetSize());
+	}
+	else
+	{
+		p_text_block->SetText(pp_string_tab, 0);
+	}
+
+	Script::RunScript("UpdateTrickText");
+}
+
+
+
+
+int Score::spinMult(int x)
+{
+	// defines how the multiplier for spins ramps up.  .5 and 1 for 180 and 360, then 2,3,4,5,... for 540,720,900,1080,... etc.
+	//return (((x)GetArray("arrow_positions", &p_arrow_array);
+	Dbg_Assert(p_arrow_array);
+	m_numArrowPositions = p_arrow_array->GetSize();
+	for (int i = 0; i < m_numArrowPositions; i++)
+	{
+		m_arrowPosTab[i] = *p_arrow_array->GetPair(i);
+	}
+	// for good measure
+	m_arrowPosTab[m_numArrowPositions] = m_arrowPosTab[m_numArrowPositions-1];
+	m_arrowInterval = 1.0f / ((float) m_numArrowPositions);
+
+	Script::CArray *p_bar_pos_array = NULL;
+	if( ( CFuncs::ScriptInSplitScreenGame( NULL, NULL )) && 
+		( skate_mod->GetGameMode()->GetNameChecksum() != Script::GenerateCRC( "horse" )) &&
+		( skate_mod->GetGameMode()->GetNameChecksum() != Script::GenerateCRC( "nethorse" )))
+	{
+		if( Nx::CViewportManager::sGetScreenMode() == Nx::vSPLIT_V )
+		{
+			p_balance_meter_struct->GetArray("bar_positions_mp_v", &p_bar_pos_array);
+		}
+		else
+		{
+			p_balance_meter_struct->GetArray("bar_positions_mp_h", &p_bar_pos_array);
+		}
+	}
+	else
+	{
+		p_balance_meter_struct->GetArray("bar_positions", &p_bar_pos_array);
+	}
+
+	Dbg_Assert(p_bar_pos_array);
+	for (int i = 0; i < 2; i++)
+	{
+		m_meterPos[i] = *p_bar_pos_array->GetPair(i);
+	}
+}
+
+
+
+
+void Score::position_balance_meter(bool state, float value, bool isVertical)
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+
+	Front::CSpriteElement *p_balance_meter = (Front::CSpriteElement *) p_manager->GetElement(0xa4db8a4b + pSkater->GetHeapIndex()).Convert(); // "the_balance_meter"
+	Dbg_Assert(p_balance_meter);
+	
+	int is_shown = 0;
+	p_balance_meter->GetIntegerTag(0xc22eb9a0, &is_shown); // "tag_turned_on"
+	if (state != (bool) is_shown)
+	{
+		Script::CStruct* pParams;
+		pParams = new Script::CStruct;
+		pParams->AddChecksum( "id", 0xa4db8a4b + pSkater->GetHeapIndex());
+		if (state)
+		{   
+			Script::RunScript(CRCD(0xba95da16, "show_balance_meter"), pParams );
+		}
+		else
+		{
+			Script::RunScript(CRCD(0x58783b40, "hide_balance_meter"), pParams );
+		}
+
+		delete pParams;
+	}
+	
+	if (isVertical)
+		p_balance_meter->SetChecksumTag(0x863a07e2, 0xef24413b); // "tag_mode", "manual"
+	else
+		p_balance_meter->SetChecksumTag(0x863a07e2, 0x530be001); // "tag_mode", "balance"
+	
+	// figure out position of arrow
+	float abs_value = Mth::Abs(value);
+	int index = (int) ( abs_value / m_arrowInterval);
+	float mix =  abs_value - ((float) index) * m_arrowInterval;
+	m_arrowPos.mX = m_arrowPosTab[index].mX + (m_arrowPosTab[index+1].mX - m_arrowPosTab[index].mX) * mix / m_arrowInterval;
+	m_arrowPos.mY = m_arrowPosTab[index].mY + (m_arrowPosTab[index+1].mY - m_arrowPosTab[index].mY) * mix / m_arrowInterval;
+	//printf("arrow at %f, %f, index %d, mix %f\n", m_arrowPos.mX, m_arrowPos.mY, index, mix);
+	m_arrowRot = -value * 45.0f;
+	
+	Front::CSpriteElement *p_balance_arrow = (Front::CSpriteElement *) p_balance_meter->GetFirstChild();//.Convert();
+	Dbg_Assert(p_balance_arrow);
+	
+	if (isVertical)
+	{
+		p_balance_meter->SetPos(m_meterPos[1].mX, m_meterPos[1].mY, Front::CScreenElement::FORCE_INSTANT);
+		p_balance_meter->SetRotate(-90.0f);
+		if (value >= 0.0f)
+			p_balance_arrow->SetPos(p_balance_meter->GetBaseW() / 2.0f + m_arrowPos.mY, p_balance_meter->GetBaseH() / 2.0f - m_arrowPos.mX, Front::CScreenElement::FORCE_INSTANT);
+		else
+			p_balance_arrow->SetPos(p_balance_meter->GetBaseW() / 2.0f + m_arrowPos.mY, p_balance_meter->GetBaseH() / 2.0f + m_arrowPos.mX, Front::CScreenElement::FORCE_INSTANT);
+		p_balance_arrow->SetRotate(-m_arrowRot - 90.0f);
+	}
+	else
+	{
+		p_balance_meter->SetPos(m_meterPos[0].mX, m_meterPos[0].mY, Front::CScreenElement::FORCE_INSTANT);
+		p_balance_meter->SetRotate(0);
+		if (value >= 0.0f)
+			p_balance_arrow->SetPos(p_balance_meter->GetBaseW() / 2.0f + m_arrowPos.mX, p_balance_meter->GetBaseH() / 2.0f + m_arrowPos.mY, Front::CScreenElement::FORCE_INSTANT);
+		else
+			p_balance_arrow->SetPos(p_balance_meter->GetBaseW() / 2.0f - m_arrowPos.mX, p_balance_meter->GetBaseH() / 2.0f + m_arrowPos.mY, Front::CScreenElement::FORCE_INSTANT);
+		p_balance_arrow->SetRotate(-m_arrowRot);
+	}	
+}
+
+
+
+
+void Score::dispatch_score_pot_value_to_screen(int score, int multiplier)
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+	int index = 0;
+
+	if( pSkater )
+	{
+		index = pSkater->GetHeapIndex();
+	}
+	Front::CTextElement *p_score_pot_text = (Front::CTextElement *) p_manager->GetElement(0xf4d3a70e + index ).Convert(); // "the_score_pot_text"
+	Dbg_Assert(p_score_pot_text);
+
+	char p_string[128];
+	if (!score)
+		strcpy(p_string, " ");
+	else if (multiplier)
+		sprintf(p_string, "%s X %d", Str::PrintThousands(score), multiplier);
+	else
+		sprintf(p_string, "%s", Str::PrintThousands(score));
+
+	
+	p_score_pot_text->SetText(p_string);
+	
+	Replay::WriteScorePotText(p_string);
+	Script::RunScript("UpdateScorepot");
+}
+
+// Check for a high score goals being achieved	   
+// for each goal that is actieved, then run the script
+// on the skater that got the score
+void Score::check_high_score(int old_score, int new_score)
+{
+	#ifdef	DEBUG_HIGH_SCORE_GOALS
+	printf("Checking High Scores, score changed from %d to %d\n",old_score, new_score); 
+	#endif
+	
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	// if we are not in career mode, then do not check for high score			   
+	if (!skate_mod->GetGameMode()->IsTrue( CRCD(0x1ded1ea4, "is_career") ))
+	{
+		return;
+	}
+
+	for (int i=0;i < Mdl::Skate::vMAX_SCORE_GOALS;i++)
+	{
+		int score = skate_mod->GetScoreGoalScore(i);
+		int goal = skate_mod->GetScoreGoalGoal(i);
+		uint32 script = skate_mod->GetScoreGoalScript(i);
+		if (score)		
+		{
+			#ifdef	DEBUG_HIGH_SCORE_GOALS
+			printf("For goal %2d, score %7d, script %x\n",goal,score,script); 
+			#endif
+			if (old_score < score && new_score >= score)
+			{
+				#ifdef	DEBUG_HIGH_SCORE_GOALS
+				dodgy_test(); printf("Score change works\n"); 
+				#endif
+				if( !skate_mod->GetCareer()->GetGoal(goal))
+				{
+					#ifdef	DEBUG_HIGH_SCORE_GOALS
+					printf("awarding this goal, and running script"); 
+					#endif
+					// not got this goal yet, so give it
+					skate_mod->GetCareer()->SetGoal(goal);
+					
+					// and run the script
+					skate_mod->GetSkaterById( m_skaterId )->SpawnAndRunScript(script);					
+				}
+				else
+				{
+					#ifdef	DEBUG_HIGH_SCORE_GOALS
+					printf("Already got this goal\n"); 
+					#endif
+				}
+			}  
+			else
+			{
+				#ifdef	DEBUG_HIGH_SCORE_GOALS
+				printf("Score change did not straddel this score\n"); 
+				#endif
+			}
+		}
+	}
+}
+
+void Score::SetBalanceMeter(bool state, float value)
+{
+	Replay::WriteBalanceMeter(state,value);
+	//Ryan("balance: %f\n", value);
+	position_balance_meter(state, value, false);
+}
+
+
+
+
+void Score::SetManualMeter(bool state, float value)
+{
+	Replay::WriteManualMeter(state,value);
+	
+	//Ryan("balance: %f\n", value);
+	position_balance_meter(state, value, true);
+}
+
+
+
+
+void Score::SetTotalScore( int score )
+{
+	m_totalScore = score;
+
+	// update the panel
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	/*Game::CGameMode* pGameMode = */skate_mod->GetGameMode();
+//	bool can_zero_score = pGameMode->IsTrue("should_modulate_color");
+
+	//	printf("Setting total score for %d: %d %d %s\n", m_skaterId, score, GetTotalScore(), Script::FindChecksumName(pGameMode->GetNameChecksum()) );
+	
+	Obj::CSkater* pSkater = skate_mod->GetSkaterById( m_skaterId );
+	Dbg_Assert( pSkater );
+	if ( pSkater->IsLocalClient() )
+	{
+		//HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+		//HUD::Panel* panel;
+		char score_text[64];
+		
+		if( ( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netking" )) ||
+			( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "king" )))
+		{
+			sprintf( score_text, "%d:%.2d", Tmr::InSeconds( m_totalScore ) / 60, Tmr::InSeconds( m_totalScore ) % 60 );
+		}
+		else
+		{
+			sprintf( score_text, "%d", m_totalScore );
+		}
+
+		Front::SetScoreTHPS4( score_text, pSkater->GetHeapIndex());
+
+		/*
+		if(( panel = panel_mgr->GetPanelBySkaterId(m_skaterId, false)))
+		{
+			panel->SetScore( m_totalScore, can_zero_score );
+		}
+		*/
+	}
+}
+
+
+
+
+int Score::GetTotalScore()
+{	
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	if( gamenet_man->OnServer())
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		Game::CGameMode* pGameMode = skate_mod->GetGameMode();
+
+		// server is also responsible for calculating graffiti scores
+		bool debug_graffiti = Script::GetInteger( "debug_graffiti" );
+		if ( debug_graffiti || pGameMode->IsTrue("should_modulate_color") )
+		{
+			return skate_mod->GetTrickObjectManager()->GetScore( m_skaterId );
+		}
+		else
+		{
+			return m_totalScore;
+		}
+	}
+	else
+	{
+		// clients already knows their total scores
+		return m_totalScore;
+	}
+
+}
+
+
+
+
+/*
+	Returns the network connection.
+*/
+Net::Conn* Score::GetAssociatedNetworkConnection( void )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Obj::CSkater* skater;
+	GameNet::PlayerInfo* player;
+
+	
+
+	skater = skate_mod->GetSkaterById( m_skaterId );
+	if( skater )
+	{
+		player = gamenet_man->GetPlayerByObjectID( skater->GetID() );
+		if( player )
+		{
+			return player->m_Conn;
+		}
+	}
+
+	return NULL;
+}
+
+
+
+
+void Score::LogTrickObjectRequest( int score )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* player;
+	GameNet::MsgScoreLogTrickObject msg;
+	Net::MsgDesc msg_desc;
+	Net::Client* client;   
+
+	player = gamenet_man->GetPlayerByObjectID( m_skaterId );
+	Dbg_Assert( player );
+    
+	client = gamenet_man->GetClient( player->m_Skater->GetSkaterNumber());
+	Dbg_MsgAssert( client, ( "Couldn't find client for skater %d", player->m_Skater->GetSkaterNumber() ) );
+
+	msg.m_SubMsgId = GameNet::SCORE_MSG_ID_LOG_TRICK_OBJECT;
+	msg.m_OwnerId = m_skaterId;
+	msg.m_Score = score;
+	msg.m_GameId = gamenet_man->GetNetworkGameId();
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* skater = skate_mod->GetSkaterById( m_skaterId );
+	Dbg_Assert( skater );
+	
+	Obj::CTrickComponent* p_trick_component = GetTrickComponentFromObject(skater);
+	Dbg_Assert(p_trick_component);
+
+	// send out variable length data representing the trick chain
+	uint32 max_pending_trick_buffer_size = GameNet::MsgScoreLogTrickObject::vMAX_PENDING_TRICKS * sizeof(uint32);
+	uint32 actual_pending_trick_buffer_size = p_trick_component->WritePendingTricks( msg.m_PendingTrickBuffer, max_pending_trick_buffer_size );
+	msg.m_NumPendingTricks = actual_pending_trick_buffer_size / sizeof( uint32 );
+
+	printf( "Client -> server %d tricks\n", msg.m_NumPendingTricks );
+
+	msg_desc.m_Data = &msg;
+	msg_desc.m_Length = sizeof( GameNet::MsgScoreLogTrickObject ) - max_pending_trick_buffer_size + actual_pending_trick_buffer_size;
+	msg_desc.m_Id = GameNet::MSG_ID_SCORE;
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+	//client->EnqueueMessageToServer( &msg_desc );
+	
+	client->StreamMessageToServer( msg_desc.m_Id, msg_desc.m_Length, 
+						   msg_desc.m_Data, "TrickObj Buffer", GameNet::vSEQ_GROUP_PLAYER_MSGS );
+
+}
+
+
+
+
+void Score::LogTrickObject( int skater_id, int score, uint32 num_pending_tricks, uint32* p_pending_tricks, bool propagate )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::MsgScoreLogTrickObject msg;
+	GameNet::MsgObsScoreLogTrickObject obs_msg;
+	Net::Server* server;
+	Net::Conn* conn;
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+
+	if( propagate )
+	{
+		bool send_steal_message;
+
+		server = gamenet_man->GetServer();
+		Dbg_Assert( server );
+
+		conn = GetAssociatedNetworkConnection();
+
+		// keep track of whom we need to send the steal message to
+		int i;
+		char previous_owner_flags[GameNet::vMAX_PLAYERS];
+		for ( i = 0; i < GameNet::vMAX_PLAYERS; i++ )
+		{
+			previous_owner_flags[i] = 0;
+		}
+		
+		num_pending_tricks = skate_mod->GetTrickObjectManager()->RequestLogTrick( num_pending_tricks, p_pending_tricks, previous_owner_flags, skater_id, score );
+		
+		if ( !num_pending_tricks )
+		{
+			return;
+		}
+
+		for ( i = 0; i < GameNet::vMAX_PLAYERS; i++ )
+		{
+			if ( previous_owner_flags[i] )
+			{
+				// GJ:  This only sends the stolen message to the two parties involved
+
+				GameNet::PlayerInfo* new_owner, *old_owner;
+				GameNet::MsgStealMessage steal_msg;
+
+				steal_msg.m_NewOwner = skater_id;
+				steal_msg.m_OldOwner = i;
+				steal_msg.m_GameId = gamenet_man->GetNetworkGameId();
+
+				new_owner = gamenet_man->GetPlayerByObjectID(skater_id);
+				old_owner = gamenet_man->GetPlayerByObjectID( i );
+				Dbg_Assert( new_owner );
+				
+				send_steal_message = true;
+				if( skate_mod->GetGameMode()->IsTeamGame())
+				{
+					if( old_owner && new_owner && ( old_owner->m_Team == new_owner->m_Team ))
+					{
+						send_steal_message = false;
+					}
+				}
+
+				if( send_steal_message )
+				{
+					Net::MsgDesc msg_desc;
+
+					msg_desc.m_Data = &steal_msg;
+					msg_desc.m_Length = sizeof( GameNet::MsgStealMessage );
+					msg_desc.m_Id = GameNet::MSG_ID_STEAL_MESSAGE;
+					server->EnqueueMessage( new_owner->GetConnHandle(), &msg_desc );
+	
+					steal_msg.m_NewOwner = skater_id;
+					steal_msg.m_OldOwner = i;
+					steal_msg.m_GameId = gamenet_man->GetNetworkGameId();
+	
+                    //Dbg_Assert( pPlayerInfo );
+	
+					// For now, don't assert if the player doesn't exist anymore. Just don't do anything.
+					// Eventually, Gary will fix this so that pieces of exiting players are reset
+					if( old_owner )
+					{
+						server->EnqueueMessage( old_owner->GetConnHandle(), &msg_desc );
+					}
+				}
+			}
+		}
+		
+#ifdef __USER_GARY__
+		printf( "Broadcasting %d tricks\n", num_pending_tricks );
+#endif
+		
+		msg.m_SubMsgId = GameNet::SCORE_MSG_ID_LOG_TRICK_OBJECT;
+		msg.m_OwnerId = skater_id;
+		msg.m_Score = score;
+		msg.m_NumPendingTricks = num_pending_tricks;
+		msg.m_GameId = gamenet_man->GetNetworkGameId();
+
+		uint32 max_pending_trick_buffer_size = GameNet::MsgScoreLogTrickObject::vMAX_PENDING_TRICKS * sizeof(uint32);
+		uint32 actual_pending_trick_buffer_size = msg.m_NumPendingTricks * sizeof(uint32);
+		memcpy( msg.m_PendingTrickBuffer, p_pending_tricks, actual_pending_trick_buffer_size );
+
+		Net::MsgDesc score_msg_desc;
+
+		score_msg_desc.m_Data = &msg;
+		score_msg_desc.m_Length = sizeof( GameNet::MsgScoreLogTrickObject ) - max_pending_trick_buffer_size + actual_pending_trick_buffer_size;
+		score_msg_desc.m_Id = GameNet::MSG_ID_SCORE;
+		score_msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		score_msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		// tell players to change their colors
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; 
+				player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			server->StreamMessage( player->GetConnHandle(), score_msg_desc.m_Id, score_msg_desc.m_Length, 
+						   score_msg_desc.m_Data, "TrickObj Buffer", GameNet::vSEQ_GROUP_PLAYER_MSGS );
+			//server->EnqueueMessage( player->GetConnHandle(), &score_msg_desc );
+		}
+
+		obs_msg.m_OwnerId = skater_id;
+		obs_msg.m_NumPendingTricks = num_pending_tricks;
+		obs_msg.m_GameId = gamenet_man->GetNetworkGameId();
+		max_pending_trick_buffer_size = GameNet::MsgScoreLogTrickObject::vMAX_PENDING_TRICKS * sizeof(uint32);
+		actual_pending_trick_buffer_size = obs_msg.m_NumPendingTricks * sizeof(uint32);
+		memcpy( obs_msg.m_PendingTrickBuffer, p_pending_tricks, actual_pending_trick_buffer_size );
+
+		score_msg_desc.m_Data = &obs_msg;
+		score_msg_desc.m_Length = sizeof( GameNet::MsgObsScoreLogTrickObject ) - max_pending_trick_buffer_size + actual_pending_trick_buffer_size;
+		score_msg_desc.m_Id = GameNet::MSG_ID_OBSERVER_LOG_TRICK_OBJ;
+		// tell observers to change their colors
+		for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+				player = gamenet_man->NextPlayerInfo( sh, true ))
+		{
+			if( player->IsObserving())
+			{
+				server->StreamMessage( player->GetConnHandle(), score_msg_desc.m_Id, score_msg_desc.m_Length, 
+						   score_msg_desc.m_Data, "TrickObj Buffer", GameNet::vSEQ_GROUP_PLAYER_MSGS );
+				//server->EnqueueMessage( player->GetConnHandle(), &score_msg_desc );
+			}
+		}
+
+		// send score updates, as something has changed
+		skate_mod->SendScoreUpdates( false );
+		
+		// Let the server's client do the rest of the work
+		if( conn->IsLocal())
+		{
+			return;
+		}
+	}
+
+#ifdef __USER_GARY__
+	printf( "Client is receiving %d tricks\n", num_pending_tricks );
+#endif
+	
+	int seq;
+
+	if( skate_mod->GetGameMode()->IsTeamGame())
+	{
+		player = gamenet_man->GetPlayerByObjectID( skater_id );
+		seq = player->m_Team;
+	}
+	else
+	{
+		seq = skater_id;
+	}
+
+	// modulate the color here
+	for ( uint32 i = 0; i < num_pending_tricks; i++ )
+	{
+		
+		skate_mod->GetTrickObjectManager()->ModulateTrickObjectColor( p_pending_tricks[i], seq );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// utilities used by the trick counting functions to resolve a key_combo
+// or trick to its trick text checksum after taking into account the number
+// of taps.
+inline uint32 get_trickname_checksum_from_trick_and_taps( uint32 trick_checksum, int num_taps = 1 )
+{
+	// get the trick text and associated checksum
+	Script::CStruct* p_trick = Script::GetStructure( trick_checksum, Script::ASSERT );
+	Script::CStruct* p_trick_params;
+	p_trick->GetStructure( CRCD( 0x7031f10c, "params" ), &p_trick_params, Script::ASSERT );
+
+	const char* p_trick_name;
+
+	// get any double or triple tap info
+	while ( num_taps > 1 )
+	{			
+		num_taps--;
+
+		Script::CArray* p_extra_trick_array;
+		uint32 extra_trick;
+
+		if ( !p_trick_params->GetChecksum( CRCD( 0x6e855102, "ExtraTricks" ), &extra_trick, Script::NO_ASSERT ) )
+			Dbg_MsgAssert( 0, ( "Couldn't find ExtraTricks to get multiple tap version of trick" ) );
+		
+		// printf("got extra trick %s\n", Script::FindChecksumName( extra_trick ) );
+		
+		p_extra_trick_array = Script::GetArray( extra_trick, Script::ASSERT );
+		Dbg_MsgAssert( p_extra_trick_array->GetType() == ESYMBOLTYPE_STRUCTURE, ( "Extra trick array %s had wrong type", Script::FindChecksumName( extra_trick ) ) );
+		Script::CStruct* p_sub_struct = p_extra_trick_array->GetStructure( 0 );
+		p_sub_struct->GetStructure( CRCD( 0x7031f10c, "params" ), &p_trick_params, Script::ASSERT );
+	}
+	
+	p_trick_params->GetLocalString( CRCD( 0xa1dc81f9, "name" ), &p_trick_name, Script::ASSERT );
+	// printf("found name %s\n", p_trick_name);
+	return Script::GenerateCRC( p_trick_name );
+}
+
+inline uint32 get_trickname_checksum_from_key_combo( uint32 key_combo, int num_taps = 1 )
+{
+	// printf("\tget_trickname_checksum_from_key_combo( %s, %i )\n", Script::FindChecksumName( key_combo ), num_taps );
+	
+	// get the key mapping and trick name
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+	Script::CStruct* pTricks = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );	
+	uint32 trick_checksum = 0;
+	
+	// see if this is a normal or special trick
+	if ( !pTricks->GetChecksum( key_combo, &trick_checksum, Script::NO_ASSERT ) )
+	{
+		Script::CStruct* pSpecialTricks = pSkaterProfile->GetSpecialTricksStructure();
+		Script::CArray* pSpecialTricksArray;
+		pSpecialTricks->GetArray( NONAME, &pSpecialTricksArray, Script::ASSERT );
+		int numSpecials = pSpecialTricksArray->GetSize();
+		for ( int i = 0; i < numSpecials; i++ )
+		{
+			Script::CStruct* pSpecial = pSpecialTricksArray->GetStructure( i );
+			uint32 trickSlot;
+			pSpecial->GetChecksum( CRCD( 0xa92a2280, "TrickSlot" ), &trickSlot, Script::ASSERT );
+			if ( trickSlot == key_combo )
+			{
+				pSpecial->GetChecksum( CRCD( 0x5b077ce1, "TrickName" ), &trick_checksum, Script::ASSERT );
+				break;
+			}
+		}
+	}
+
+	if ( trick_checksum == 0 )
+		return 0;
+
+	return get_trickname_checksum_from_trick_and_taps( trick_checksum, num_taps );	
+}
+
+
+inline uint32 get_cat_index_from_key_combo( uint32 key_combo )
+{
+	// printf("\tget_trickname_checksum_from_key_combo( %s, %i )\n", Script::FindChecksumName( key_combo ), num_taps );
+	
+	// get the key mapping and trick name
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+	Script::CStruct* pTricks = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );	
+	int cat_trick = -1;
+	
+	// see if this is a normal or special trick
+
+	if ( !pTricks->GetInteger( key_combo, &cat_trick, Script::NO_ASSERT ) )
+	{
+		Script::CStruct* pSpecialTricks = pSkaterProfile->GetSpecialTricksStructure();
+		Script::CArray* pSpecialTricksArray;
+		pSpecialTricks->GetArray( NONAME, &pSpecialTricksArray, Script::ASSERT );
+		int numSpecials = pSpecialTricksArray->GetSize();
+		for ( int i = 0; i < numSpecials; i++ )
+		{
+			Script::CStruct* pSpecial = pSpecialTricksArray->GetStructure( i );
+			int is_cat = 0;
+			pSpecial->GetInteger( CRCD(0xb56a8816,"isCat"), &is_cat, Script::NO_ASSERT );
+			if ( is_cat != 0 )
+			{
+				uint32 trickslot;
+				pSpecial->GetChecksum( CRCD(0xa92a2280,"trickSlot"), &trickslot, Script::ASSERT );
+				if ( trickslot == key_combo )
+				{
+					uint32 cat_trick_checksum;
+					pSpecial->GetChecksum( CRCD(0x5b077ce1,"trickName"), &cat_trick_checksum, Script::ASSERT );
+					cat_trick = (int)cat_trick_checksum;
+					break;
+				}
+			}
+		}
+	}
+
+	return cat_trick;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Score::VerifyTrickMatch( int info_tab_index, uint32 trick_checksum, int spin_mult, bool require_perfect )
+{
+	if ( info_tab_index >= m_currentTrick )
+		return false;
+	
+	// printf("\tVerifyTrickMatch( %i, %s, %i, %i )\n", info_tab_index, Script::FindChecksumName( trick_checksum ), spin_mult, require_perfect );
+	// printf("m_infoTab[%i].id = %s\n", info_tab_index, Script::FindChecksumName( m_infoTab[info_tab_index].id ) );
+	if ( m_infoTab[info_tab_index].id == trick_checksum )
+	{
+		if ( spin_mult == 0 )
+			return true;
+		else 
+		{
+			// printf("spin_degrees for this trick = %i\n", m_infoTab[i].spin_degrees);
+			if ( spin_mult == m_infoTab[info_tab_index].spin_mult_index )
+			{
+				if ( !require_perfect )
+					return true;
+				else if ( Mth::Abs( Mth::Abs(m_infoTab[info_tab_index].spin_degrees) - ( spin_mult * 180 ) ) < Script::GetInteger( CRCD( 0xfcfacab8, "perfect_landing_slop" ) ) )
+					return true;
+			}
+		}		
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Score::CountTrickMatches( uint32 trick_checksum, int spin_mult, bool require_perfect )
+{
+	// printf("CountTrickMatches( %s, %i, %i )\n", Script::FindChecksumName( trick_checksum ), spin_mult, require_perfect );
+	int current_spin = 1;
+	
+	int total = 0;
+
+	for ( int i = 0; i < m_currentTrick; i++ )
+	{
+		if ( m_infoTab[i].flags & vBLOCKING )
+		{
+			current_spin = 1;
+		}
+		else if ( i == 0 || ( i >= 1 && m_infoTab[i-1].flags & vBLOCKING ) )
+		{
+			current_spin = m_infoTab[i].spin_mult_index;
+		}
+		// printf("current_spin = %i\n", current_spin );
+		
+		if ( m_infoTab[i].id == trick_checksum )
+		{
+			if ( spin_mult == 0 )
+				total++;
+			else 
+			{
+				// printf("spin_degrees for this trick = %i\n", m_infoTab[i].spin_degrees);
+				if ( spin_mult == current_spin )
+				{
+					if ( !require_perfect )
+						total++;
+					else if ( Mth::Abs( Mth::Abs(m_infoTab[i].spin_degrees) - ( spin_mult * 180 ) ) < Script::GetInteger( CRCD( 0xfcfacab8, "perfect_landing_slop" ) ) )
+						total++;
+				}
+			}		
+		}
+	}
+	return total;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Score::CountStringMatches( const char* string )
+{
+	int total = 0;
+    
+    for ( int i = 0; i < m_currentTrick; i++ )
+	{
+        if ( Str::StrStr( m_infoTab[i].trickNameText, string ) )
+		{
+            total++;		
+		}
+	}
+	return total;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Score::IsLatestTrickByName( uint32 trick_text_checksum, int spin_mult, bool require_perfect, int num_taps )
+{
+	int index = m_currentTrick;
+	do
+	{
+		if (index <= 0) return false;
+		index--;
+	}
+	while (m_infoTab[index].trickNameText[0] == 0);
+	
+	// resolve multiple tap tricks
+	if (num_taps > 1)
+	{
+		trick_text_checksum = get_trickname_checksum_from_trick_and_taps(trick_text_checksum, num_taps);
+	}
+	
+	return VerifyTrickMatch(index, trick_text_checksum, spin_mult, require_perfect);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Score::IsLatestTrick( uint32 key_combo, int spin_mult, bool require_perfect, int num_taps )
+{
+	int index = m_currentTrick;
+	do
+	{
+		if (index <= 0) return false;
+		index--;
+	}
+	while (m_infoTab[index].trickNameText[0] == 0);
+	
+	// get the key mapping and trick name
+	uint32 trick_name_checksum = get_trickname_checksum_from_key_combo( key_combo, num_taps );
+	if ( trick_name_checksum == 0 )
+	{
+		// check for cat trick		
+		int cat_trick = get_cat_index_from_key_combo( key_combo );
+		if ( cat_trick != -1 )
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Dbg_Assert( skate_mod );
+			Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+			if ( pSkater )
+			{
+				Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[cat_trick];
+				Dbg_Assert( pCreatedTrick );
+				const char* p_trick_name;
+				pCreatedTrick->mp_other_params->GetString( CRCD( 0xa1dc81f9, "name" ), &p_trick_name, Script::ASSERT );
+				trick_name_checksum = Script::GenerateCRC( p_trick_name );
+			}
+		}
+		else
+		{
+			return false;
+		}
+	}
+	
+	return VerifyTrickMatch(index, trick_name_checksum, spin_mult, require_perfect);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Score::GetNumberOfNonGapTricks()
+{
+	int non_gap_trick_count = 0;
+	
+	for ( int i = 0; i < m_currentTrick; i++ )
+	{
+		if (!TrickIsGap(i))
+		{
+			non_gap_trick_count++;
+		}
+	}
+	
+	return non_gap_trick_count;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// find a trick by name, rather than by key combo
+int Score::GetCurrentNumberOfOccurrencesByName( uint32 trickTextChecksum, int spin_mult, bool require_perfect, int num_taps )
+{
+	// resolve multiple tap tricks
+	if ( num_taps > 1 )
+	{
+		trickTextChecksum = get_trickname_checksum_from_trick_and_taps( trickTextChecksum, num_taps );
+	}
+	
+	// uint32 trick_name_checksum = Script::GenerateCRC( trick_string );
+	int num = CountTrickMatches( trickTextChecksum, spin_mult, require_perfect );
+/*	for ( int i = 0; i < m_currentTrick; i++ )
+	{
+		if ( VerifyTrickMatch( i, trickTextChecksum, spin_mult, require_perfect ) )
+			num++;
+	}*/
+	return num;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// searches by key combo
+int Score::GetCurrentNumberOfOccurrences( uint32 key_combo, int spin_mult, bool require_perfect, int num_taps )
+{
+	// get the key mapping and trick name
+	uint32 trick_name_checksum = get_trickname_checksum_from_key_combo( key_combo, num_taps );
+	if ( trick_name_checksum == 0 )
+	{
+		// check for cat trick		
+		int cat_trick = get_cat_index_from_key_combo( key_combo );
+		if ( cat_trick != -1 )
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Dbg_Assert( skate_mod );
+			Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+			if ( pSkater )
+			{
+				Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[cat_trick];
+				Dbg_Assert( pCreatedTrick );
+				const char* p_trick_name;
+				pCreatedTrick->mp_other_params->GetString( CRCD( 0xa1dc81f9, "name" ), &p_trick_name, Script::ASSERT );
+				trick_name_checksum = Script::GenerateCRC( p_trick_name );
+			}
+		}
+		else
+		{
+			return 0;
+		}
+	}
+
+	int num = this->CountTrickMatches( trick_name_checksum, spin_mult, require_perfect );
+/*    for ( int i = 0; i < m_currentTrick; i++ )
+    {
+        if ( this->VerifyTrickMatch( i, trick_name_checksum, spin_mult, require_perfect) )
+			num++;
+    }
+*/
+	return num;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Score::GetPreviousNumberOfOccurrencesByName( uint32 trickTextChecksum, int spin_mult, int num_taps )
+{
+	// resolve multiple tap tricks
+	if ( num_taps > 1 )
+	{
+		trickTextChecksum = get_trickname_checksum_from_trick_and_taps( trickTextChecksum, num_taps );
+	}
+	
+	int num = 0;
+    bool justSawTrick = false;
+    for ( int i = 0; i < m_currentTrick; i++ )
+    {
+        // printf("checking %s\n", m_infoTab[i].trickNameText );
+        if ( m_infoTab[i].id == trickTextChecksum )
+        {
+            // printf("found trick\n");
+            if ( spin_mult == 0 )
+			{
+				num++;
+				justSawTrick = true;
+			}
+			else if ( spin_mult == m_infoTab[i].spin_mult_index )
+			{
+				num++;
+				justSawTrick = true;
+			}
+            
+        }
+        else 
+        {
+            if ( ( m_infoTab[i].flags & vGAP ) && justSawTrick )
+            {
+                // printf("found gap after trick\n");
+            }
+            else
+            {
+                // if ( justSawTrick ) printf("found a non-gap trick after the trick\n");
+                justSawTrick = false;
+            }
+        }
+    }
+    if ( justSawTrick )
+    {
+        // printf("followed by gaps\n");
+        num--;
+    }
+    // printf("found %i previous occurences\n", num );
+    return num;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Score::GetPreviousNumberOfOccurrences( uint32 key_combo, int spin_mult, int num_taps )
+{
+	// printf ( "GetPreviousNumberOfOccurrences( %s, %i, %i )\n", Script::FindChecksumName( key_combo ), spin_mult, num_taps );
+	uint32 trick_name_checksum = get_trickname_checksum_from_key_combo( key_combo, num_taps );
+	
+	if ( trick_name_checksum == 0 )
+	{
+		// check for cat trick		
+		int cat_trick = get_cat_index_from_key_combo( key_combo );
+		if ( cat_trick != -1 )
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Dbg_Assert( skate_mod );
+			Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+			if ( pSkater )
+			{
+				Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[cat_trick];
+				Dbg_Assert( pCreatedTrick );
+				const char* p_trick_name;
+				pCreatedTrick->mp_other_params->GetString( CRCD( 0xa1dc81f9, "name" ), &p_trick_name, Script::ASSERT );
+				trick_name_checksum = Script::GenerateCRC( p_trick_name );
+			}
+		}
+		else
+		{
+			return 0;
+		}
+	}
+	
+	int num = 0;
+    bool justSawTrick = false;
+    for ( int i = 0; i < m_currentTrick; i++ )
+    {
+        // printf("checking %s\n", m_infoTab[i].id );
+        if ( m_infoTab[i].id == trick_name_checksum )
+        {
+            // printf("found trick\n");
+            if ( spin_mult == 0 )
+			{
+				num++;
+				justSawTrick = true;
+			}
+			else if ( spin_mult == m_infoTab[i].spin_mult_index )
+			{
+				num++;
+				justSawTrick = true;
+			}            
+        }
+        else 
+        {
+            if ( ( m_infoTab[i].flags & vGAP ) && justSawTrick )
+            {
+                // printf("found gap after trick\n");
+            }
+            else
+            {
+                // if ( justSawTrick ) printf("found a non-gap trick after the trick\n");
+                justSawTrick = false;
+            }
+        }
+    }
+    if ( justSawTrick )
+    {
+        // printf("followed by gaps\n");
+        num--;
+    }
+    // printf("found %i previous occurences\n", num );
+    return num;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// utility used by GetCurrentNumberOfOccurrences( Script::CArray* )
+inline void GetTrickInfoFromTrickArray( Script::CArray* pTricks, int trick_array_index, uint32 *p_trick_name_checksum, int *p_spin, bool *p_require_perfect, int *p_num_taps )
+{
+	// reset spin and perfect first
+	*p_spin = 0;
+	*p_require_perfect = false;
+
+	int num_taps = 1;
+	uint32 key_combo;
+
+	if ( pTricks->GetType() == ESYMBOLTYPE_STRUCTURE )
+	{
+		// grab the key combo and any spin or perfect modifiers from the struct
+		Script::CStruct* pSubStruct = pTricks->GetStructure( trick_array_index );
+		pSubStruct->GetChecksum( CRCD( 0x95e16467, "KeyCombo" ), &key_combo, Script::ASSERT );
+		pSubStruct->GetInteger( CRCD( 0xa4bee6a1, "num_taps" ), &num_taps, Script::NO_ASSERT );
+		pSubStruct->GetInteger( CRCD(0xa4bee6a1,"num_taps"), &*p_num_taps, Script::NO_ASSERT );
+		*p_trick_name_checksum = get_trickname_checksum_from_key_combo( key_combo, num_taps );
+		if ( pSubStruct->GetInteger( CRCD( 0xedf5db70, "spin" ), &*p_spin, Script::NO_ASSERT ) )
+		{
+			Dbg_MsgAssert( *p_spin % 180 == 0, ( "Bad spin value %i in gap tricks structure", *p_spin ) );
+			*p_spin = *p_spin / 180;
+			*p_require_perfect = pSubStruct->ContainsFlag( CRCD( 0x1c39f1b9, "perfect" ) );
+		}
+		// printf("\tlooking for key_combo %s\n", Script::FindChecksumName( key_combo ) );
+	}
+	else
+	{
+		Dbg_MsgAssert( pTricks->GetType() == ESYMBOLTYPE_NAME, ( "Tricks array has wrong type" ) );
+		*p_trick_name_checksum = get_trickname_checksum_from_key_combo( pTricks->GetChecksum( trick_array_index ) );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Score::GetCurrentNumberOfOccurrences( Script::CArray* pTricks, int start_trick_index )
+{
+	Dbg_Message("DEPRECATED! Tell Brad if you need this.\n");
+	return 0;
+
+
+/*	Dbg_MsgAssert( start_trick_index <= m_currentTrick, ( "GetCurrentNumberOfOccurrences got bad start_trick_index of %i - m_currentTrick = %i", start_trick_index, m_currentTrick ) );
+	
+	int trick_array_size = pTricks->GetSize();
+	int total = 0;
+	
+	// parse through the current list of tricks
+	for ( int trick_array_index = 0; trick_array_index )
+	{
+		
+	}
+	for ( int i = start_trick_index; i < m_currentTrick; i++ )
+	{
+		// printf("\tcurrent trick = %s\n", Script::FindChecksumName( m_infoTab[i].id ) );
+		int trick_array_index = 0;
+		uint32 trick_name_checksum;
+		int spin = 0;
+		bool require_perfect = false;
+		int num_taps = 0;
+		GetTrickInfoFromTrickArray( pTricks, trick_array_index, &trick_name_checksum, &spin, &require_perfect, &num_taps );
+
+		while ( VerifyTrickMatch( i + trick_array_index, trick_name_checksum, spin, require_perfect ) )
+		{
+			// printf("\tfound a match\n");
+			// if we've matched as many tricks as are in the list, we know we've
+			// found a complete match
+			if ( trick_array_index == trick_array_size - 1 )
+			{
+				total++;
+				break;
+			}
+			trick_array_index++;
+			GetTrickInfoFromTrickArray( pTricks, trick_array_index, &trick_name_checksum, &spin, &require_perfect, &num_taps );
+		}
+	}
+*/
+	// printf("found %i occurrence(s)\n", total);
+	// return total;
+}
+
+void	Score::RepositionMeters( void )
+{
+	setup_balance_meter_stuff();
+}
+
+void Score::ResetComboRecords()
+{
+	m_longestCombo = 0;
+	m_bestCombo = 0;
+	m_bestGameCombo = 0;
+}
+
+/******************************************************************/
+/* Handle network scoring messages                                */
+/*                                                                */
+/******************************************************************/
+
+int	Score::s_handle_score_message ( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgEmbedded* p_msg = (GameNet::MsgEmbedded*) ( context->m_Msg );
+	Mdl::Score* pScore = ((Obj::CSkater*) ( context->m_Data ))->GetScoreObject();
+	
+	switch ( p_msg->m_SubMsgId )
+	{
+		case GameNet::SCORE_MSG_ID_LOG_TRICK_OBJECT:
+		{
+			GameNet::MsgScoreLogTrickObject* log_msg = (GameNet::MsgScoreLogTrickObject*) ( context->m_Msg );
+			GameNet::Manager* gamenet_man =  GameNet::Manager::Instance();
+
+			if ( log_msg->m_GameId == gamenet_man->GetNetworkGameId())
+			{
+				pScore->LogTrickObject( log_msg->m_OwnerId, log_msg->m_Score, log_msg->m_NumPendingTricks, log_msg->m_PendingTrickBuffer, false );
+			}
+			break;
+		}
+	}
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Score::TrickIsGap( int trickIndex )
+{
+	Dbg_MsgAssert( trickIndex >= 0 && trickIndex < m_currentTrick, ( "trickIndex out of range" ) );
+	return ( m_infoTab[trickIndex].flags & vGAP );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Score::set_special_is_active ( bool is_active )
+{
+	if (is_active != m_specialIsActive)
+	{
+		m_specialIsActive = is_active;
+		
+		Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkaterById(m_skaterId);
+		if( pSkater )
+		{
+			pSkater->BroadcastEvent(m_specialIsActive ? CRCD(0xd0cd8081, "SkaterEnterSpecial") : CRCD(0x7ccf5ee2, "SkaterExitSpecial"));
+		}
+	}
+}
+
+
+void Score::reset_robot_detection()
+{
+	m_num_robot_landed = 0;
+	m_num_robot_unique = 0;
+	for (int i=0;i= MAX_ROBOT_NODES)
+	{
+		m_robot_rail_mult = 1.0f;
+		return;
+	}
+
+
+
+	m_num_robot_landed++;
+	m_num_robot_landed_combo++;
+	if (m_robot_detection_count[node] == 0)
+	{	
+		m_num_robot_unique++;
+		m_num_robot_first_combo++;
+	}
+	if (m_robot_detection_count[node] < 255)
+	{
+		m_robot_detection_count[node]++;
+	}
+	
+	if (m_robot_detection_count_combo[node] == 0)
+	{	
+		m_num_robot_unique_combo++;
+	}
+	if (m_robot_detection_count_combo[node] < 255)
+	{
+		m_robot_detection_count_combo[node]++;
+	}
+	
+//	GetRobotMult();
+
+								  
+	int 	kick_in = Script::GetInteger(CRCD(0x6ee127b7,"robot_kick_in_count"));							  
+
+	if (m_robot_detection_count_combo[node] > kick_in)
+	{
+		int ramp = 20;
+		// The first time you go over robot_kick_count, we want 1/2, everything before that will be 1.0f
+		m_robot_rail_mult = 1.0f/(m_robot_detection_count_combo[node]-(kick_in)+1) * ( (float)(ramp + 1 - m_robot_detection_count[node])/(float)ramp);
+		// Might go small or negative after a really long time, so clamp it
+		m_robot_rail_mult = Mth::Max(0.1f, m_robot_rail_mult);	
+	}	
+	else
+	{
+		m_robot_rail_mult = 1.0f;
+	}
+	
+
+}
+
+
+float	Score::GetRobotMult()
+{
+	
+	printf ("landed = %3d, unique = %3d .. ",m_num_robot_landed,m_num_robot_unique);
+	printf ("combo landed = %3d, unique = %3d, first = %d\n",m_num_robot_landed_combo,m_num_robot_unique_combo,m_num_robot_first_combo);
+	
+//	float unique_ratio = (float)m_num_robot_unique_combo/(float)m_num_robot_landed_combo;
+//	float first_ratio = (float)m_num_robot_first_combo/(float)m_num_robot_landed_combo;
+//	float punish = 1.0f - 0.5f * logf(robot);
+//	float reward = logf(m_num_robot_unique_combo) - logf(robot);
+//	printf ("robot = %2.3f, punish = x%2.3f, reward = x%2.3f\n",robot, punish, reward);	
+
+	int	min_robot_effect = 3;  // number of repetitions at which "robot" adjustment begins
+
+	float sub = 0.0f;
+	// Scale any effect by the size of the combo, beyond the minimun needed to take effect	
+//	float robot_scale = 0.0f;
+	if ((m_num_robot_landed_combo - m_num_robot_unique_combo) > min_robot_effect && m_num_robot_unique_combo < m_num_robot_landed_combo)
+	{
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+		float landed = m_num_robot_landed_combo - min_robot_effect;
+		float dupes = m_num_robot_landed_combo - m_num_robot_unique_combo - min_robot_effect;
+//		float first =  m_num_robot_first_combo;
+		
+//		robot_scale = 0.1f * logf(landed);   // with min of 10, 10=0, 50 =1.6, 100 = 1.95 
+		// find an amount to subtract from a 1.0f multiplier, based on how repeditive this line is
+//		float dupe_sub = (dupes/landed); 
+//		float first_sub = 0.0f;	//0.05f * logf(landed - first); 
+//		float sub = robot_scale * (dupe_sub + first_sub);
+		float sub = dupes/landed;
+//		printf ("(dupe = %2.3f + first = %2.3f) * scale %2.3f == sub %2.3f\n", dupe_sub, first_sub, robot_scale,  sub);
+		printf ("sub = %2.3f\n",sub);
+#endif		// __NOPT_FINAL__
+	}
+	
+	float mult = 1.0f - sub;
+	if (mult > 1.0f)
+	{
+		mult = 1.0f;
+	}
+	if (mult < 0.2f)
+	{
+		mult = 0.2f;
+	}
+	printf ("mult = %2.3f\n",mult);
+	return mult;
+}	
+
+
+
+} // namespace HUD
+
+
+
+
diff --git a/Code/Sk/Modules/Skate/score.h b/Code/Sk/Modules/Skate/score.h
new file mode 100644
index 0000000..a0c2b1e
--- /dev/null
+++ b/Code/Sk/Modules/Skate/score.h
@@ -0,0 +1,303 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2001 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:									 								**
+**																			**
+**	File name:																**
+**																			**
+**	Created by:		rjm, 1/23/2001											**
+**																			**
+**	Description:						 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __MODULES_SKATE_SCORE_H
+#define __MODULES_SKATE_SCORE_H
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Net
+{
+	class	Conn;
+	class	MsgHandlerContext;
+}
+
+namespace Mdl
+{
+
+						
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Score  : public Spt::Class
+{
+	
+
+public:
+	
+	typedef uint32 Flags;
+	enum
+	{
+		vBLOCKING 		= nBit(1),
+		vSWITCH			= nBit(2),
+		vNOLLIE			= nBit(3),
+		vGAP			= nBit(4),
+		vSPECIAL		= nBit(5),
+		vNODEGRADE		= nBit(6),
+		vCAT			= nBit(7),
+	};
+
+	enum
+	{
+		MAX_ROBOT_NODES = 2500
+	};
+									Score();
+									~Score();
+
+	void 							Update();
+	void 							SetSkaterId(short skater_id);
+
+	void 							SetTotalScore( int score );
+	int 							GetTotalScore();
+	int 							GetLastScoreLanded() {return m_recentScorePot;}
+	int 							GetScorePotValue() {return m_scorePot * m_currentMult;}  // current pot value
+	bool 							GetSpecialState() {return m_specialIsActive;}
+	void 							ForceSpecial( void );	
+
+	uint32							GetLastTrickId(); // Added by Ken
+	uint32							GetTrickId( int trickIndex );
+
+	void							TrickTextPulse(int index); // Added by Ken so that replay can use it
+	void							TrickTextCountdown(int index); // Added by Ken so that replay can use it
+	void							TrickTextLanded(int index); // Added by Ken so that replay can use it
+	void							TrickTextBail(int index); // Added by Ken so that replay can use it
+	void 							Trigger(char *trick_name, int base_score, Flags flags = 0);
+	void 							SetSpin(int spin_degrees );
+	void 							UpdateSpin(int spin_degrees );
+	void 							TweakTrick(int tweak_value );
+	void 							Land( void );
+	void 							Bail( bool clearScoreText = false );
+
+	void 							Reset();
+
+	Net::Conn* 						GetAssociatedNetworkConnection( void );
+	void 							LogTrickObject( int skater_id, int score, uint32 num_pending_tricks, uint32* p_pending_tricks, bool propagate );
+	void 							LogTrickObjectRequest( int score );
+
+	void 							SetBalanceMeter(bool state, float value = 0.0f);
+	void 							SetManualMeter(bool state, float value = 0.0f);
+	void							RepositionMeters( void );
+
+	int								GetNumberOfNonGapTricks();
+	
+	bool							IsLatestTrickByName( uint32 trick_text_checksum, int spin_mult, bool require_perfect, int num_taps );
+	bool							IsLatestTrick( uint32 key_combo, int spin_mult, bool require_perfect, int num_taps );
+
+    // counting occurrences of trick
+    int                             GetPreviousNumberOfOccurrencesByName( uint32 trickTextChecksum, int spin_degrees = 0, int num_taps = 1 );
+    int                             GetPreviousNumberOfOccurrences( uint32 trick_checksum, int spin_degrees = 0, int num_taps = 1 );
+//	int								GetPreviousNumberOfOccurrences( Script::CArray* pTricks );
+    
+    int                             GetCurrentNumberOfOccurrencesByName( uint32 trickTextChecksum, int spin_degrees = 0, bool require_perfect = false, int num_taps = 1 );
+    int                             GetCurrentNumberOfOccurrences( uint32 trick_checksum, int spin_degrees = 0, bool require_perfect = false, int num_taps = 1 );
+	int                             GetCurrentNumberOfOccurrences( Script::CArray* pTricks, int start_trick_index = 0 );
+
+	bool							VerifyTrickMatch( int info_tab_index, uint32 trick_checksum, int spin_degrees = 0, bool require_perfect = false );
+	int								CountTrickMatches( uint32 trick_checksum, int spin_degrees = 0, bool require_perfect = false );
+    int								CountStringMatches( const char* string );
+
+	int								GetCurrentTrickCount() { return m_currentTrick; }
+    int								GetCurrentMult() { return m_currentMult; }
+
+	// track the best and longest combos
+	int								GetLongestCombo() { return m_longestCombo; }
+	int								GetBestCombo() { return m_bestCombo; }
+	void							SetBestCombo( int best_combo ) { m_bestCombo = best_combo; }
+	void							ResetComboRecords();
+    void 							setSpecialBarColors();
+
+	bool							TrickIsGap( int trickIndex );
+	
+	static int						s_handle_score_message ( Net::MsgHandlerContext* context );
+
+	void							UpdateRobotDetection(int node);
+	float							GetRobotMult();
+	float							GetRobotRailMult() {return m_robot_rail_mult;}
+	
+
+private:
+
+	void							reset_robot_detection();
+	void							reset_robot_detection_combo();
+
+	int								get_packed_score( int start, int end );
+	void							pack_trick_info_table();
+	void							print_trick_info_table();
+	void 							copy_trick_name_with_special_spaces(char *pOut, const char *pIn);
+	
+    void 							captureScore();
+	void 							resetScorePot();
+	void							dispatch_trick_sequence_to_screen(bool clear_text=false);
+	int 							spinMult(int x);
+	int 							deprecMult(int x);
+
+	void							setup_balance_meter_stuff();
+	void							position_balance_meter(bool state, float value, bool isVertical);
+
+	void							dispatch_score_pot_value_to_screen(int score, int multiplier);
+	
+	void							check_high_score(int old_score, int new_score);
+	
+	void							set_special_is_active ( bool is_active );
+	
+	short							m_skaterId;
+
+	enum EScorePotState
+	{
+		SHOW_ACTUAL_SCORE_POT,
+		WAITING_TO_COUNT_SCORE_POT,
+		SHOW_COUNTED_SCORE_POT,
+	};
+	
+	int								m_totalScore;
+	EScorePotState					m_scorePotState;
+	int 							m_scorePot;
+	int 							m_countedScorePot;
+	int								m_scorePotCountdown;
+	int    							m_recentScorePot;
+	int    							m_recentSpecialScorePot;
+
+	int								m_longestCombo;
+	int								m_bestCombo;
+	int								m_bestGameCombo;
+
+	//HUD::TrickWindow*				mp_trickWindow;
+
+	struct TrickHistory
+	{
+		uint32						id;
+		// there are 4 switch modes, so 4 entries
+		int							total_count[4]; 	// done during session 
+		int	 						combo_count[4];		// done	during combo
+	};
+	Lst::LookupTable	m_historyTab;
+	
+	struct TrickInfo
+	{
+		enum
+		{
+			TEXT_BUFFER_SIZE		= 64,
+		};
+		
+		int							score;
+		int 						mult_index;
+		int 						spin_mult_index;
+		int							spin_degrees;
+		uint32						id;
+		uint32						switch_mode;
+		Flags						flags;
+		char						trickNameText[TEXT_BUFFER_SIZE];
+		char						trickTextFormatted[TEXT_BUFFER_SIZE];
+	};
+	// keeps track of tricks done in current sequence
+	Lst::DynamicTable	m_infoTab;
+	int								m_currentTrick;	 // Tricks cap out at 250
+	int								m_currentMult;	 // but multiplier will keep going
+	// Indicates most recent blocking trick
+	int								m_currentBlockingTrick;
+	// Most recent spin trick. Must come after a blocking trick.
+	// Can be a blocking trick itself, but will be treated differently
+	// if so.
+	int								m_currentSpinTrick;
+
+	int								m_specialScore;
+	bool							m_specialIsActive;
+
+	enum
+	{
+		REGULAR_SPECIAL	=			0,
+		SPECIAL_SPECIAL
+	};
+	Image::RGBA						m_special_rgba[3];
+	float							m_special_interpolator;
+	float							m_special_interpolator_rate;
+
+	bool 							m_debug;
+
+	// Balance meter stuff -- here for now
+	int								m_numArrowPositions;
+	Script::CPair 					m_arrowPosTab[20];
+	float							m_arrowInterval;
+	Script::CPair					m_arrowPos;
+	float							m_arrowRot;
+	Script::CPair					m_meterPos[2];
+	
+	uint8							m_robot_detection_count[MAX_ROBOT_NODES];
+	uint8							m_robot_detection_count_combo[MAX_ROBOT_NODES];
+	int								m_num_robot_landed;
+	int								m_num_robot_landed_combo;
+	int								m_num_robot_unique;
+	int								m_num_robot_first_combo;
+	int								m_num_robot_unique_combo;
+	float							m_robot_rail_mult;
+	
+};
+
+
+
+} // namespace Mdl
+
+#endif	// __MODULES_SKATE_SCORE_H
+
+
diff --git a/Code/Sk/Modules/Skate/skate.cpp b/Code/Sk/Modules/Skate/skate.cpp
new file mode 100644
index 0000000..03359ac
--- /dev/null
+++ b/Code/Sk/Modules/Skate/skate.cpp
@@ -0,0 +1,3525 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Skate Module (SKATE) 									**
+**																			**
+**	File name:		modules/skate.cpp										**
+**																			**
+**	Created by:		06/07/2000	-	spg										**
+**																			**
+**	Description:	Skate Module											**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC skate
+// @module skate | None
+// @subindex Scripting Database
+// @index script | skate
+
+// define this to call the movie updatign from the Skate::Mdl update, instead of the fronend up[date
+#define	__MOVIES_FROM_SKATE_UPDATE__	
+
+                           
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+    #include 
+#endif
+#endif
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef TESTING_GUNSLINGER
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+extern bool	skip_startup;
+
+
+namespace Front
+{
+	extern void SetTimeTHPS4(int minutes, int seconds);
+    extern void HideTimeTHPS4();
+}
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+DefineSingletonClass( Skate, "Skate Module" );
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Classes								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+// When going into observer mode the local skater gets destroyed, and is later
+// restored from his profile. The created tricks are not stored in the profile,
+// so we are storing them out seperately.
+static Script::CStruct *spCATData=NULL;
+static Script::CStruct *spStatData=NULL;
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Skate::s_logic_code ( const Tsk::Task< Skate >& task )
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+    
+    Dbg_AssertType ( &task, Tsk::Task< Skate > );
+
+	Skate&	mdl = task.GetData();
+
+	mdl.DoUpdate();
+
+	if( gamenet_man->OnServer() && ( mdl.GetNumSkaters() > 0 ))
+	{
+		mdl.CheckSkaterCollisions();
+	}
+
+	if ( mdl.GetGameMode()->IsFrontEnd() )
+	{
+	}
+	else if( ( mdl.GetGameMode()->GetNameChecksum() == CRCD(0x6ef8fda0,"netking") ) ||
+			 ( mdl.GetGameMode()->GetNameChecksum() ==  CRCD(0x5d32129c,"king") ))
+	{
+        if( !mdl.GetGameMode()->EndConditionsMet())
+		{
+			GameNet::PlayerInfo* king;
+			
+			king = gamenet_man->GetKingOfTheHill();
+			if( king && !king->m_Skater->IsPaused())
+			{
+				Mdl::Score* score;
+				int current_score, total_score, goal_score;
+	
+				score = king->m_Skater->GetScoreObject();
+				current_score = score->GetTotalScore();
+				total_score = current_score + (int) (	( Tmr::FrameLength() * 60.0f ) * 
+														( Tmr::vRESOLUTION / 60.0f ));
+				score->SetTotalScore( total_score );
+
+				goal_score = mdl.GetGameMode()->GetTargetScore();
+
+				if( mdl.GetGameMode()->IsTeamGame())
+				{
+					total_score = gamenet_man->GetTeamScore( king->m_Team );
+				}
+				else
+				{
+					total_score = score->GetTotalScore();
+				}
+
+				// When within 10 seconds of victory, play a timer sound periodically
+				mdl.BeepTimer( (float) ( goal_score - total_score) / 1000.0f,
+							   10.0f,
+							   10.0f,
+							   "timer_runout_beep");
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Skate::v_start_cb ( void )
+{
+	// TODO:  Should assert that the top-down heap is empty
+	// before the permanent prefiles are loaded
+//	Script::RunScript( "DumpHeaps" );
+	
+	// these prefiles will sit on the top-down heap permanently
+	// this is done before other stuff gets put on the top-down
+	// heap
+	Script::RunScript( "load_permanent_prefiles" );
+
+	m_stat_override = 0.0f;
+	   
+	Mlp::Manager * mlp_manager =  Mlp::Manager::Instance(); 
+                           
+	Obj::Proxim_Init();						//
+	Obj::CEmitterManager::sInit();
+
+	m_logic_task->SetMask(1<<1);
+												   
+    // Initialise logic and display tasks
+	mlp_manager->AddLogicTask ( *m_logic_task );
+    
+	GetGameMode()->Reset();
+
+    Script::RunScript("init_loading_bar");
+    Script::RunScript("startup_loading_screen");
+
+	// By default we are both client and server
+	//gamenet_manager->m_Flags.SetMask( GameNet::mSERVER | GameNet::mCLIENT );
+    
+	// Run the script for loading sounds that are permanently loaded into sound RAM...
+    Script::RunScript("LoadPermSounds");
+
+	// Run the script for loading the skater animations
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	
+	Mem::PushMemProfile("Permanent Assets");
+	Script::RunScript( "load_permanent_assets" );
+	Mem::PopMemProfile(/*"Permanent Assets"*/);
+
+	Mem::Manager::sHandle().PopContext();
+
+	// initialize the pro skater profiles here
+	GetPlayerProfileManager()->Init();
+
+	// add the trick checksums used to the lookup table
+	mp_trickChecksumTable->Init();
+
+	Script::RunScript("default_system_startup");
+
+	if ( !Config::CD() )
+	{
+		if ( !skip_startup )
+		{
+			// Run the personal startup script.
+			Script::RunScript("Call_Personal_StartUp_Script");
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::v_stop_cb ( void )
+{
+    m_logic_task->Remove();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::HideSkater( Obj::CSkater* skater, bool should_hide )
+{
+	if( should_hide )
+	{
+		skater->Pause();
+	}
+	else
+	{
+		skater->UnPause();
+	}
+
+	skater->Hide( should_hide );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int				Skate::GetNextUnusedSkaterHeapIndex( bool for_local_skater )
+{
+	int start_index, heap_index, num_tested;
+	bool taken;
+
+	// Ok... kinda complicated, but it was the simplest solution I could come up with that solves the problem.
+	// The issue is that we only have vMAX_SKATERS skater heaps.  And we always want to reserve skater heap 0 
+	// for our local skater.  But we might be observing a game that has vMAX_SKATERS, in which case you will
+	// not have a heap for the local skater at all.  But if we observe a server that has ( vMAX_SKATERS - 1 )
+	// skaters and join late, we want to make sure that our skater (the last one to be loaded) gets skater 
+	// heap 0
+	start_index = 1;
+	if( for_local_skater )
+	{
+		start_index = 0;
+	}
+
+	heap_index = start_index;
+	for( num_tested = 0; num_tested < (int) GetGameMode()->GetMaximumNumberOfPlayers(); num_tested ++ )
+	{
+		taken = false;
+		for( uint i = 0; i < GetNumSkaters(); i++ )
+		{
+			if( m_skaters.GetItem(i) != NULL )
+			{
+				Obj::CSkater *pSkater = m_skaters.GetItem(i)->GetData();
+				if( pSkater->GetHeapIndex() == heap_index )
+				{
+					taken = true;
+					break;
+				}
+			}
+		}
+		if( taken == false )
+		{
+			break;
+		}
+
+		heap_index = ( heap_index + 1 ) % GetGameMode()->GetMaximumNumberOfPlayers();
+	}
+	
+	// We assume that these skater heaps are totally empty
+	// so if something is aleft on them, then assert
+
+#ifdef	__NOPT_ASSERT__	
+	Mem::Heap *skater_heap = Mem::Manager::sHandle().SkaterHeap(heap_index);
+	Dbg_MsgAssert( skater_heap != NULL, ( "Invalid skater heap : %d\n", heap_index ));
+	if (skater_heap->mUsedBlocks.m_count)
+	{
+		printf ("Skater heap has %d used blocks still on it, it should be empty\n",skater_heap->mUsedBlocks.m_count);
+#ifndef __PLAT_NGC__
+		MemView_AnalyzeHeap(skater_heap);
+		MemView_DumpHeap(skater_heap);
+#endif		// __PLAT_NGC__
+		Dbg_MsgAssert(0,("Skater heap has %d used blocks still on it, it should be empty\n",skater_heap->mUsedBlocks.m_count));
+	}
+
+	Mem::Heap *geom_heap = Mem::Manager::sHandle().SkaterGeomHeap(heap_index);
+	if (geom_heap->mUsedBlocks.m_count)
+	{
+		printf ("SkaterGeom heap has %d used blocks still on it, it should be empty\n",geom_heap->mUsedBlocks.m_count);
+#ifndef __PLAT_NGC__
+		MemView_AnalyzeHeap(geom_heap);
+		MemView_DumpHeap(geom_heap);
+#endif		// __PLAT_NGC__
+		Dbg_MsgAssert(0,("SkaterGeom heap has %d used blocks still on it, it should be empty\n",geom_heap->mUsedBlocks.m_count));
+	}
+	
+	#endif
+	
+
+	return heap_index;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CSkater*	Skate::add_skater ( Obj::CSkaterProfile* pSkaterProfile, bool local_client, int obj_id, int player_num )
+{
+	Lst::Node< Obj::CSkater > *node;
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	int heap_index;
+    
+	// GJ:  copy the face texture (if it has one) from the local client's skater profile
+	// we need to do this because the code that sends the face texture data as a separate
+	// network message hasn't been implemented yet.
+	//Dbg_MsgAssert( local_client, ( "Gary's kludge for face textures only works on local client" ) );
+	if ( local_client )
+	{
+		Gfx::CModelAppearance* pModelAppearance = pSkaterProfile->GetAppearance();
+		Obj::CSkaterProfile* pOriginalProfile;
+		if (CFuncs::ScriptInSplitScreenGame( NULL, NULL ))
+		{
+			// (Mick) Kluge modified for 2 players
+			// get the correct profile
+			pOriginalProfile = mp_PlayerProfileManager->GetProfile( player_num );
+		}
+		else
+		{
+			// Otherwise, use the old kluge
+			pOriginalProfile = mp_PlayerProfileManager->GetProfile( 0 );
+		}
+		
+		
+		Gfx::CModelAppearance* pOriginalAppearance = pOriginalProfile->GetAppearance();
+
+		*pModelAppearance = *pOriginalAppearance;
+	}
+	
+	// Change our ID to what the server says it should be to avoid ID collision amongs clients
+	if( local_client )
+	{
+		Obj::CSkater* skater;
+
+		Script::RunScript( "show_panel_stuff" );
+		skater = GetLocalSkater();
+		if( skater )
+		{   
+			// In Split-screen games, we'll have two local skaters
+			if( CFuncs::ScriptInSplitScreenGame( NULL, NULL ))
+			{
+				if( skater->GetID() == (uint32) obj_id )
+				{
+					return skater;
+				}
+#ifdef __PLAT_NGC__
+				else
+				{
+					File::PreMgr* pre_mgr = File::PreMgr::Instance();
+					pre_mgr->LoadPre( "skaterparts.pre", false);
+				}
+#endif
+			}
+			else
+			{
+				// In Net games, we'll only have one, but his ID can change in order to avoid
+				// ID collision over the net.  So, if someone is trying to add another local skater with a different
+				// ID, assume that they just want to assign a new ID to our already-existing local skater
+				if( skater->GetID() != (uint32) obj_id )
+				{   
+					Score* score;
+	
+					node = skater->GetLinkNode();
+					node->SetPri( obj_id );
+					node->Remove();
+					m_skaters.AddUniqueSequence( node );
+	
+////////////////////////////////////////////////////////////////////////////////////					
+// Changing the id means we have to destroy and re-create the particle systems
+// as they are referenced by the id of the skater.					
+					Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterHeap(skater->GetHeapIndex()));
+					Script::RunScript( "DestroySkaterParticles", NULL, skater );
+
+					skater->SetID( obj_id );
+					
+					Script::RunScript( "InitSkaterParticles", NULL, skater );
+					Mem::Manager::sHandle().PopContext();
+////////////////////////////////////////////////////////////////////////////////////					
+					
+					score = skater->GetScoreObject();
+					score->SetSkaterId( obj_id );
+				}
+				
+				return skater;
+			}
+		}
+		else
+		{
+			Obj::CCompositeObject* cam_obj;
+
+			// If we're about to re-create the skater, delete the skatercam as it is about to
+			// be re-created.  This is to handle the case where someone goes into observer mode.
+			// In this case, we destroy the skater, but leave the camera. So now we're going back
+			// to the front end and it's re-creating the skater.
+			cam_obj = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x967c138c,"skatercam0"));
+			if( cam_obj )
+			{
+				cam_obj->SetLockOff();
+				delete cam_obj;
+			}
+		}
+	}
+	else
+	{
+		// Stop vibration when loading client skaters in net games
+		if ( gamenet_man->InNetGame() )
+		{
+			Obj::CSkater* p_skater;
+
+			p_skater = GetLocalSkater();
+			if( p_skater )
+			{
+				Obj::CVibrationComponent* p_vibration_component = GetVibrationComponentFromObject(p_skater);
+				if (p_vibration_component)
+				{
+					p_vibration_component->StopAllVibration();
+				}
+			}
+		}
+	}
+
+	if ( gamenet_man->InNetGame() )
+	{
+		Dbg_MsgAssert( GetNumSkaters() < GetGameMode()->GetMaximumNumberOfPlayers(),( "Trying to add too many players %d : %d", GetNumSkaters(), GetGameMode()->GetMaximumNumberOfPlayers()));
+	}
+
+    Mem::PushMemProfile("Skater"); // Could possibly replace this with the name of the skater, when we have one, so can compare in multi player
+
+		
+	Dbg_MsgAssert(( obj_id >= 0 ) && ( obj_id < vMAX_SKATERS ),( "Invalid skater object id\n" ));
+	
+	heap_index = GetNextUnusedSkaterHeapIndex( local_client );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterHeap(heap_index));
+
+	Obj::CSkater* skater;
+
+	Dbg_MsgAssert( pSkaterProfile,( "No profile specified" ));
+
+	// Garrett: We may need to pause the music here if we get glitches on the network server
+	//Pcm::PauseMusic(1);
+
+	skater = new Obj::CSkater ( player_num, GetObjectManager(),
+								local_client, obj_id, heap_index, !GetGameMode()->IsFrontEnd() );
+	//Pcm::PauseMusic(0);
+	skater->Init(pSkaterProfile);
+
+	// Initialize the skater's special items
+	if( !skater->IsLocalClient())
+	{
+		Script::RunScript( "ClientSkaterInit", NULL, skater );
+	}
+
+    // This bit of code will order our skaters in the linked list according to their object id
+	// so the order will be consistent across all clients
+	node = skater->GetLinkNode();
+	node->SetPri( skater->GetID() );
+	m_skaters.AddUniqueSequence( node );
+
+	if ( GetGameMode()->IsFrontEnd() )
+	{
+		// front end doesn't have a server, so we need
+		// to treat it as a special case.
+		skip_to_restart_point( skater );
+		Script::RunScript("reset_skateshop_skater");
+	}
+	
+    // check if we should bind this skater to a different controller
+	// printf("skaterIndex = %i, player_num = %i\n", mp_controller_preferences[player_num].skaterIndex, player_num);
+	UpdateSkaterInputHandlers();
+
+    // restore CAT's
+    if( skater->IsLocalClient() && ( skater->GetSkaterNumber() == 0 ))
+    {
+        if (spCATData)
+        {
+            printf("CAT: Restoring Created Tricks\n");
+            skater->LoadCATInfo(spCATData);
+            delete spCATData;
+    		spCATData=NULL;
+        }
+        
+        // restore stat goal data too!
+        if (spStatData)
+        {
+            printf("CAT: Restoring Stat Goal data\n");
+            skater->LoadStatGoalInfo(spStatData);
+            delete spStatData;
+    		spStatData=NULL;
+        }
+    }
+
+    // update trick mappings if this is the second skater in a split screen game
+	if ( skater->GetID() == 0x00000001 && CFuncs::ScriptInSplitScreenGame( NULL, NULL ) )
+	{
+		// Obj::CPlayerProfileManager* pProfileMan = GetPlayerProfileManager();
+		// Obj::CSkaterProfile* pProfile = pProfileMan->GetProfile( 1 );
+		
+		Obj::CTrickComponent* p_trick_component = GetTrickComponentFromObject(skater);
+		Dbg_Assert(p_trick_component);
+		p_trick_component->UpdateTrickMappings( pSkaterProfile );
+	}
+
+	Mem::PopMemProfile(/*"Skater"*/);
+	Mem::Manager::sHandle().PopContext();
+
+    return skater;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::GetNextStartingPointData( Mth::Vector* pos, Mth::Matrix* matrix, int obj_id )
+{   
+    int node;
+
+	pos->Set( 0.0f, 0.0f, 0.0f, 0.0f );
+	matrix->Ident();
+
+	node = find_restart_node( obj_id );
+
+    // ...and jump to it
+	if (node != -1)
+	{
+		Mth::Vector rot;
+
+		printf ("Got restart node %d\n",node);
+		
+		SkateScript::GetAngles( node, &rot );
+		if ( rot[ X ] || rot[ Y ] || rot[ Z ] )
+		{
+			// 3DSMAX_ANGLES Mick: 3/19/03 - Changed all rotation orders to X,Y,Z
+			matrix->RotateX( rot[ X ] );
+			matrix->RotateY( rot[ Y ] );
+			matrix->RotateZ( rot[ Z ] );
+		}
+
+		SkateScript::GetPosition( node, pos );
+	}
+}
+
+/******************************************************************/
+/* Find the zero-based restart node. So index 0 is Player1		  */
+/*                                                                */
+/******************************************************************/
+
+int Skate::find_restart_node( int index )
+{
+	int node = -1;
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	// get appropriate restart point...
+	if( IsMultiplayerGame())
+	{
+		if( GetGameMode()->IsTeamGame())
+		{
+			if( GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netlobby" ))
+			{
+				node = Obj::GetRestartNode( Script::GenerateCRC( "team" ), 0 );
+			}
+			else if( GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netctf" ))
+			{
+				GameNet::PlayerInfo* player;
+				
+				player = gamenet_man->GetLocalPlayer();
+				if( player )
+				{
+					switch( player->m_Team )
+					{
+						case GameNet::vTEAM_RED:
+							node = SkateScript::FindNamedNode( "TRG_CTF_Restart_Red" );
+							break;
+						case GameNet::vTEAM_BLUE:
+							node = SkateScript::FindNamedNode( "TRG_CTF_Restart_Blue" );
+							break;
+						case GameNet::vTEAM_GREEN:
+							node = SkateScript::FindNamedNode( "TRG_CTF_Restart_Green" );
+							break;
+						case GameNet::vTEAM_YELLOW:
+							node = SkateScript::FindNamedNode( "TRG_CTF_Restart_Yellow" );
+							break;
+					}
+				}
+			}
+			else
+			{
+				index = gamenet_man->GetSkaterStartingPoint( index );
+				node = Obj::GetRestartNode( Script::GenerateCRC( "Multiplayer" ), index );
+				// if we did not find one, then try the first generic restart
+				if (node == -1)
+				{
+					printf ("\n\nWARNING - no multiplayer restart point, trying player1\n\n");
+					// If no Playern, then try player1
+					node = Obj::GetRestartNode( Script::GenerateCRC( "Player1" ), 0 );
+				}
+			}
+		}
+		// In the skateshop, use the Player1, Player2 restart points
+		else if( GetGameMode()->IsFrontEnd())
+		{
+			char buff[32];
+
+			sprintf( buff, "Player%d", index + 1 );
+			node = Obj::GetRestartNode( Script::GenerateCRC( buff ), 0 );
+		}
+		else if ( GetGameMode()->IsTrue( "is_horse" ) )
+		{
+			// grab the current horse node
+			node = Obj::GetRestartNode( Script::GenerateCRC( "horse" ), GetHorse()->GetCurrentRestartIndex() );
+		}
+		else
+		{
+			index = gamenet_man->GetSkaterStartingPoint( index );
+			node = Obj::GetRestartNode( Script::GenerateCRC( "Multiplayer" ), index );
+			// if we did not find one, then try the first generic restart
+			if (node == -1)
+			{
+				printf ("\n\nWARNING - no multiplayer restart point, trying player1\n\n");
+				// If no Playern, then try player1
+				node = Obj::GetRestartNode( Script::GenerateCRC( "Player1" ), 0 );
+			}
+		}
+	}
+	else
+	{
+		node = Obj::GetRestartNode( Script::GenerateCRC( "Player1" ), index );
+	}
+		
+	// If No player 1, then use generic
+	if (node == -1)
+	{		
+		printf ("\n\nWARNING - no Player1 restart point, trying Generic\n\n");
+		node = Obj::GetRestartNode( 0, 0 );
+	}
+
+	// If No player 1, then use generic
+	if( node == -1 )
+	{
+		printf( "\n\nWARNING - No restart points found\n\n" );
+	}
+
+	return node;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::skip_to_restart_point( Obj::CSkater* skater, int node, bool walk )
+{
+	if (node == -1)
+	{
+		node = find_restart_node( skater->GetID() );
+	}
+
+	// ...and jump to it
+	if (node != -1)
+	{
+		skater->SkipToRestart( node, walk );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::move_to_restart_point( Obj::CSkater* skater )
+{
+	skip_to_restart_point(skater, -1);
+	
+/*  Dan: MoveToRestart is deprecated.  We'll use skip instead.
+	int node;
+	
+	node = find_restart_node( skater->GetID() );
+    
+	if (node != -1)
+	{
+		skater->MoveToRestart(node);   					   
+	}
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::remove_skater( Obj::CSkater* skater )
+{   	
+	Dbg_Assert( skater );
+
+    // (Mick) Now check for bailboard, and kill it
+	// Bit of a patch really, but a fairly safe high level one.
+	// (need a more general solution for killing model that has
+	// instances...)
+	
+	Obj::CCompositeObject*p_bailboard = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x884ef81b, "bailboard") + skater->GetID());
+	if( p_bailboard )
+	{
+		p_bailboard->MarkAsDead();
+		Obj::CCompositeObjectManager::Instance()->FlushDeadObjects();
+	}
+	
+	// destroy any car the non-local skater might have
+	if( !skater->IsLocalClient() )
+	{
+		Script::CStruct* p_params = new Script::CStruct;
+		p_params->AddChecksum(CRCD(0x5b24faaa, "SkaterId"), skater->GetID());
+		Script::RunScript(CRCD(0x5b3fdccb, "remove_car_from_non_local_skater"), p_params);
+		delete p_params;
+	}
+
+    // save off CAT params
+    if( skater->IsLocalClient() && ( skater->GetSkaterNumber() == 0 ))
+    {
+        if (spCATData)
+    	{
+    		Dbg_Assert( 0 );
+            delete spCATData;
+    		spCATData=NULL;
+    	}
+        
+        printf("CAT: Storing Created Trick data\n");
+        spCATData=new Script::CStruct;
+        skater->AddCATInfo(spCATData);
+
+        // save stat goal info too...
+        if (spStatData)
+    	{
+    		Dbg_Assert( 0 );
+            delete spStatData;
+    		spStatData=NULL;
+    	}
+        
+        printf("CAT: Storing Stat Goal data\n");
+        spStatData=new Script::CStruct;
+        skater->AddStatGoalInfo(spStatData);
+    }
+    
+	skater->GetLinkNode()->Remove();
+	//Obj::Unlock( skater );
+	//Obj::Destroy( skater );
+	delete skater;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Skate::SkatersAreIdle()
+{
+	 //GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+#ifdef __USER_GARY__
+	// GJ:  this assert was firing off for some reason.
+	// it doesn't seem to have any negative impact, but i'd
+	// like to look at it later, when i've got more time
+//	Dbg_Assert( GetGameMode()->EndConditionsMet() );
+#endif
+
+	bool all_idle = true;
+
+	for ( uint32 i = 0; i < GetNumSkaters(); i++ )
+	{
+		Obj::CSkater* pSkater = GetSkater( i );
+		Dbg_Assert( pSkater );
+
+		if ( GetGameMode()->IsTrue( "is_horse" ) )
+		{
+			// only need to check the current horse skater
+			if ( GetHorse()->GetCurrentSkaterId() != (int) pSkater->GetID() )
+			{
+				continue;
+			}
+		}
+
+		Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(pSkater);
+		Dbg_Assert(p_skater_endrun_component);
+		if ( !p_skater_endrun_component->RunHasEnded() )
+		{
+			all_idle = false;
+			if( pSkater->IsLocalClient())
+			{
+				p_skater_endrun_component->EndRun();
+			}                     
+		}
+	}
+	
+	// In network games, don't base it on whether skater objects are idle as this is unreliable.
+	// Instead, base it on "GameIsOver()" which uses reliable transport messages
+	/*if( gamenet_man->InNetGame())
+	{
+		return gamenet_man->GameIsOver();
+	}*/
+
+	return all_idle;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Skate::FirstTrickStarted()
+{
+	// you should only call this function in horse mode
+	Dbg_Assert( GetGameMode()->IsTrue("is_horse") );
+	
+	Obj::CSkater* pSkater = GetSkaterById( GetHorse()->GetCurrentSkaterId() );
+	Dbg_Assert( pSkater );
+															  
+	Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+	Dbg_Assert( pTrickComponent );
+
+	if ( pTrickComponent->FirstTrickStarted() )
+	{
+		// if the trick has started, then end the run, yo!
+		Obj::CSkaterEndRunComponent* p_skater_endrun_component = GetSkaterEndRunComponentFromObject(pSkater);
+		Dbg_Assert(p_skater_endrun_component);
+		p_skater_endrun_component->EndRun();
+	}
+
+	return pTrickComponent->FirstTrickStarted();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Skate::FirstTrickCompleted()
+{
+	// you should only call this function in horse mode
+	Dbg_Assert( GetGameMode()->IsTrue("is_horse") );
+	
+	Obj::CSkater* pSkater = GetSkaterById( GetHorse()->GetCurrentSkaterId() );
+	Dbg_Assert( pSkater );
+	
+	Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+	Dbg_Assert( pTrickComponent );
+	
+	return pTrickComponent->FirstTrickCompleted();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::PauseGameFlow( bool paused )
+{
+	
+
+	Dbg_AssertPtr( mp_gameFlow );
+	mp_gameFlow->Pause( paused );	
+}
+
+//////////////////////////////////////////////////////////////////////
+// void Skate::BeepTimer(float time, float beep_time, float beep_speed, const char *p_script_name)
+// given a time, run a script if that time is less than beep_time, and again, with increasing
+// frequency.  Note use of static vars, means this is a single use fn.  Intetended for a single timer
+// time = time remaining in seconds
+// beep_time = time at which to start beeping
+// beep_speed = relative speed at whcih beeps increase 10.0 is good
+// p_script_name = script to run for each beep
+//
+// example:
+//
+//			BeepTimer(time,10.0f, 10.0f,"timer_runout_beep");			
+//
+
+void Skate::BeepTimer(float time, float beep_time, float beep_speed, const char *p_script_name)
+{
+	static float next_beep_time;
+	if (time > 0.01f && time <= beep_time)
+	{
+		if (timeCheckModelActive();
+	}
+}
+ 
+
+void Skate::DoUpdate()
+{
+
+#ifdef	__MOVIES_FROM_SKATE_UPDATE__	
+	if ( !Mdl::FrontEnd::Instance()->GamePaused() && !Mdl::CViewer::sGetViewMode() )
+	{
+		if ( mp_movieManager->IsRolling() )
+		{
+			if ( mp_movieManager->GetActiveCamera() )
+			{
+				// hardcoded to viewport 0...  if we need the other
+				// viewports to play movies then we can store it
+				// in the details...
+				Nx::CViewportManager::sSetCamera( 0, mp_movieManager->GetActiveCamera() );
+			}
+
+			// hides the loading screen, if any
+			if ( m_lastFrameWasMovieCamera )
+			{
+				Script::RunScript( CRCD(0xc5c9373a,"hide_loading_screen") );
+			}
+			mp_movieManager->Update();
+			m_lastFrameWasMovieCamera = true;
+		}
+		else
+		{
+			// the camera just finished
+			if ( m_lastFrameWasMovieCamera )
+			{
+				Script::RunScript( CRCD(0x15674315,"restore_skater_camera") );
+				m_lastFrameWasMovieCamera = false;
+			}
+		}
+		
+		// play any extra object anims that might be playing
+		// (this might be phased out later)
+		if ( mp_objectAnimManager->IsRolling() )
+		{
+			mp_objectAnimManager->Update();
+		}
+	}
+#endif
+	
+	
+	
+	// Fills in a little structure with some stuff so that SkipLogic is really fast.
+	pre_calculate_object_update_info();
+	
+	// called once per frame
+	Script::RunScript( CRCD(0x1aef674f,"Game_Update") );
+
+	// Mick: PATCH																		 
+	// if the game is paused, then manually update the visibility of the game object
+	// as we might be moving the camera around (Like in the view-goals screen)
+	if ( Mdl::FrontEnd::Instance()->GamePaused())
+	{
+		GetObjectManager()->ProcessAllObjects(do_CheckModelActive);
+	}
+
+// 
+
+	#if 1
+	
+	// Temp hack, for each viewport, run proxim-update on it, hooking it up to the appropiate camera
+	
+	// Perhaps a better solution would be to have a non-suspendable component
+	// that is attached to the skater, and grabs the viewport/camera itself
+	// But what about when the game is paused?	
+	if( GetLocalSkater())
+	{
+		int viewports = Nx::CViewportManager::sGetNumActiveViewports();
+		switch (viewports)
+		{
+			case 1:
+				if (Nx::CViewportManager::sGetActiveCamera(0))
+				{
+					Proxim_Update(1<<0, GetLocalSkater(), Nx::CViewportManager::sGetActiveCamera(0)->GetPos());
+				}
+				else
+				{
+					#ifdef	__NOPT_ASSERT__
+					printf ("WARNING: 1 viewport, but no active camera 0\n");
+					#endif
+				}
+				break;
+			case 2:
+				if (Nx::CViewportManager::sGetCamera(0))
+				{
+					Proxim_Update(1<<0, Obj::CCompositeObjectManager::Instance()->GetObjectByID(0), Nx::CViewportManager::sGetCamera(0)->GetPos());
+				}
+				// Need to check that the second skater exists, as well as the viewport
+				if (Nx::CViewportManager::sGetCamera(1) && Obj::CCompositeObjectManager::Instance()->GetObjectByID(1))
+				{
+					Proxim_Update(1<<1, Obj::CCompositeObjectManager::Instance()->GetObjectByID(1), Nx::CViewportManager::sGetCamera(1)->GetPos());
+				}
+				break;
+			default:
+				Dbg_MsgAssert(0,("Don't handle %d viewport proxim updating",viewports));
+		}
+	}
+	else if (CFuncs::ScriptIsObserving(NULL, NULL))		// TT12029: Run without object in observer mode
+	{
+		int viewports = Nx::CViewportManager::sGetNumActiveViewports();
+		switch (viewports)
+		{
+			case 1:
+				if (Nx::CViewportManager::sGetActiveCamera(0))
+				{
+					Obj::Proxim_Update(1<<0, NULL, Nx::CViewportManager::sGetActiveCamera(0)->GetPos());
+				}
+				else
+				{
+					#ifdef	__NOPT_ASSERT__
+					printf ("WARNING: 1 viewport, but no active camera 0\n");
+					#endif
+				}
+				break;
+			default:
+				Dbg_MsgAssert(0,("Don't handle %d viewport proxim updating in observer mode",viewports));
+		}
+	}
+	else
+	{
+		#ifdef	__NOPT_ASSERT__
+		// printf ("WARNING: No local skater, so no Proxim updates\n");
+		#endif
+	}
+	#endif	
+
+
+
+
+	
+	if ( m_levelChanged )
+	{
+#ifdef __DEFERRED_CLEANUP_TEST__
+		CFuncs::ScriptPrintMemInfo( NULL, NULL );
+#endif
+		m_levelChanged = false;
+	}
+
+	// don't think i need this anymore
+	if (GetGameMode()->IsFrontEnd())
+	{
+		// if the front end is active, then don't update
+		return;
+	}
+
+	// =================== TIMER STUFF ===========================
+	// display the appropriate time if necessary
+	// (we don't use or show a clock in certain game modes)
+	Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+	if ( !Replay::RunningReplay() && pGoalManager->ShouldUseTimer() )
+	{
+		Tmr::Time time_left = (pGoalManager->GetGoalTime() + 999) / 1000;
+
+		// int time_left = m_time_limit - Tmr::InSeconds( front_end->GetGameTime());
+
+		if( time_left < 1 )
+		{
+			time_left = 0;
+		}
+
+		int seconds = time_left % 60;
+		int minutes = time_left / 60;
+		
+		Front::SetTimeTHPS4(minutes, seconds);
+		
+		//m_panelMgr->SetTime(minutes, seconds);
+		
+/*		if (GetGameMode()->ShouldTimerBeep())
+		{
+			//float time = (float)m_time_limit - front_end->GetGameTime()/1000.0f;
+			float time = float(time_left);
+			BeepTimer(time,10.0f, 10.0f,"timer_runout_beep");			
+		}
+*/
+	}
+	else
+	{
+		Front::HideTimeTHPS4();
+	}
+
+	// make sure everyone's controller is still plugged in
+#ifdef __PLAT_XBOX__
+	int is_changing_levels = Script::GetInteger( CRCD(0xe997db9f,"is_changing_levels"), Script::ASSERT );
+	int check_controllers = Script::GetInteger( CRCD(0x77a5662c,"check_for_unplugged_controllers"), Script::ASSERT );
+	if ( check_controllers && !is_changing_levels )
+	{
+		if ( m_first_input_received )
+		{
+			Front::CScreenElementManager* p_screen_elem_man = Front::CScreenElementManager::Instance();
+			Front::CScreenElement* p_root_window = p_screen_elem_man->GetElement( CRCD(0x56a1eae3,"root_window")  , Front::CScreenElementManager::DONT_ASSERT );
+			if ( p_root_window )
+			{
+				// check that the box isn't already up
+				Front::CScreenElement* p_error_box = p_screen_elem_man->GetElement( CRCD(0x2edb780f,"controller_unplugged_dialog_anchor") , Front::CScreenElementManager::DONT_ASSERT );
+				if ( !p_error_box )
+				{
+					bool controller_unplugged = false;
+					for ( int i = 0; i < vMAX_SKATERS; i++ )
+					{
+						Obj::CSkater* pSkater = GetSkater( i );
+						if ( pSkater && pSkater->IsLocalClient() )
+						{
+							Inp::Handler< Obj::CInputComponent >* pHandler = pSkater->mp_input_component->GetInputHandler();
+							if ( !pHandler->m_Device->IsPluggedIn() )
+							{
+								// m_controller_unplugged_frame_count++;
+								controller_unplugged = true;
+								if ( m_controller_unplugged_frame_count > Script::GetInteger( CRCD(0xf7fabb50,"controller_unplugged_frame_count"), Script::ASSERT ) )
+								{
+									m_controller_unplugged_frame_count = 0;
+									Script::CStruct* pScriptParams = new Script::CStruct();
+									GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+									if ( gamenet_man->InNetGame() )
+									{
+										Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+										Dbg_Assert( pGoalManager );
+										pGoalManager->DeactivateAllGoals();
+										pGoalManager->UninitializeAllGoals();
+
+										pScriptParams->AddChecksum( NONAME, CRCD(0xeaa48e14,"leaving_net_game") );
+										Skate::LeaveServer();
+										if ( gamenet_man->OnClient() )
+										{
+											gamenet_man->CleanupPlayers();
+											gamenet_man->ClientShutdown();
+										}
+									}
+	
+									// get the device that's unplugged
+									int device_num = pHandler->m_Device->GetPort();
+									
+									pScriptParams->AddInteger( CRCD(0xc9428a08,"device_num"), device_num );
+									Script::RunScript( CRCD(0xf77aca62,"controller_unplugged"), pScriptParams );
+									delete pScriptParams;
+	
+									break;
+								}
+							}
+						}
+					}
+					if ( controller_unplugged )
+						m_controller_unplugged_frame_count++;
+					else
+						m_controller_unplugged_frame_count = 0;
+				}
+			}
+		}
+	}
+#endif		// __PLAT_NGC__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::SetTimeLimit( int seconds )
+{
+	m_time_limit = seconds;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetTimeLimit | sets the time limit
+// @parmopt int | seconds | 120 | number of seconds for time limit
+bool ScriptSetTimeLimit(Script::CStruct *pParams, Script::CScript *pScript) {
+    int seconds = 120;
+
+    pParams->GetInteger( "seconds", &seconds );
+
+    Skate* skate = Skate::Instance();
+
+    skate->SetTimeLimit( seconds );
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Skate::GetTimeLimit( void )
+{
+	return m_time_limit;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::UpdateGameFlow()
+{
+	Dbg_Assert( mp_movieManager );
+
+#ifndef	__MOVIES_FROM_SKATE_UPDATE__	
+	if ( !Mdl::FrontEnd::Instance()->GamePaused() && !Mdl::CViewer::sGetViewMode() )
+	{
+		if ( mp_movieManager->IsRolling() )
+		{
+			if ( mp_movieManager->GetActiveCamera() )
+			{
+				// hardcoded to viewport 0...  if we need the other
+				// viewports to play movies then we can store it
+				// in the details...
+				Nx::CViewportManager::sSetCamera( 0, mp_movieManager->GetActiveCamera() );
+			}
+
+			// hides the loading screen, if any
+			if ( m_lastFrameWasMovieCamera )
+			{
+				Script::RunScript( CRCD(0xc5c9373a,"hide_loading_screen") );
+			}
+			mp_movieManager->Update();
+			m_lastFrameWasMovieCamera = true;
+		}
+		else
+		{
+			// the camera just finished
+			if ( m_lastFrameWasMovieCamera )
+			{
+				Script::RunScript( CRCD(0x15674315,"restore_skater_camera") );
+				m_lastFrameWasMovieCamera = false;
+			}
+		}
+		
+		// play any extra object anims that might be playing
+		// (this might be phased out later)
+		if ( mp_objectAnimManager->IsRolling() )
+		{
+			mp_objectAnimManager->Update();
+		}
+	}
+#endif
+
+	Dbg_AssertPtr( mp_gameFlow );
+	mp_gameFlow->Update();
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Skate::Skate ( void )
+{   
+	m_first_input_received = false;
+	m_controller_unplugged_frame_count = 0;
+	
+	m_logic_task = new Tsk::Task< Skate > ( Skate::s_logic_code, *this );
+
+	net_setup();
+
+	mp_PlayerProfileManager = new Obj::CPlayerProfileManager;
+	mp_PlayerProfileManager->Reset();
+
+	mp_gameFlow = new CGameFlow;
+	mp_goalManager = new Game::CGoalManager;
+
+//    mp_obj_manager = new Obj::CGeneralManager;
+	// Bit of a patch, but the engine now needs to be told what is managing the moving objects		
+	// When Skate.cpp is broken up, then the mp_obj_manager needs extracting into some more generic "game object" manager
+	// which Nx can query, if it really needs to													
+	Nx::CEngine::sSetMovableObjectManager(GetObjectManager());
+
+    mp_gameMode = new Game::CGameMode;
+	mp_career = new Obj::CSkaterCareer;				// new local careeer
+	
+	mp_railManager = new Obj::CRailManager;
+
+#	ifdef TESTING_GUNSLINGER
+	mp_navManager = new Obj::CNavManager;
+#	endif
+	
+	mp_trickChecksumTable = new Obj::CTrickChecksumTable;
+
+	mp_trickObjectManager = new Obj::CTrickObjectManager;
+	mp_trickObjectManager->DeleteAllTrickObjects();
+
+	m_recording = false;
+	m_playing = false;
+	m_fd = 0;
+
+	m_gameInProgress = false;
+	m_levelLoaded = false;
+	SetGameType( Script::GenerateCRC("freeskate") );
+
+	// initialize the splitscreen preferences
+    mp_splitscreen_preferences = new Prefs::Preferences;
+	mp_splitscreen_preferences->Load( Script::GenerateCRC("default_splitscreen_preferences" ) );
+
+	mp_competition = new CCompetition;
+	mp_horse = new CHorse;
+	mp_movieManager = new Obj::CMovieManager;
+	mp_objectAnimManager = new Obj::CMovieManager;
+
+	mp_gameRecords = new Records::CGameRecords(Obj::CSkaterCareer::vMAX_CAREER_LEVELS);
+
+	m_shadow_mode = Gfx::vSIMPLE_SHADOW;
+	
+	// Initialise the controller preferences.
+	// These might get changed by an autoload off the memory card.
+	for (int i=0; i=0 && indexm_spin_taps=state;
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::SetAutoKick(int index, bool state)
+{
+	Dbg_MsgAssert(index>=0 && indexm_auto_kick=state;
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::SetVibration(int index, bool state)
+{
+	Dbg_MsgAssert(index>=0 && indexSetActiveState(state);
+		}
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CSkater* 	Skate::GetLocalSkater( void )
+{
+	unsigned int i;
+
+	for( i = 0; i < GetNumSkaters(); i++ )
+	{
+		if( m_skaters.GetItem(i) != NULL )
+		{
+			Obj::CSkater *pSkater = m_skaters.GetItem(i)->GetData();
+			if( pSkater->IsLocalClient())
+			{
+				return pSkater;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CSkater 	*Skate::GetSkater( uint num )
+{
+	
+	if( GetNumSkaters() <= num )
+	{
+		return NULL;
+	}   
+	else 
+	{
+		Dbg_Assert( m_skaters.GetItem( num ) );
+        return m_skaters.GetItem( num )->GetData();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CSkater *Skate::GetSkaterById( uint32 id )
+{
+	for (uint i = 0; i < GetNumSkaters(); i++)
+	{
+		if( m_skaters.GetItem(i) != NULL )
+		{
+			Obj::CSkater *pSkater = m_skaters.GetItem(i)->GetData();
+			if (pSkater->GetID() == id)
+				return pSkater;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CGeneralManager*	Skate::GetObjectManager( void )
+{
+//	return mp_obj_manager;
+	return	Obj::CCompositeObjectManager::Instance();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CTrickObjectManager*	Skate::GetTrickObjectManager( void )
+{
+	return mp_trickObjectManager;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CPlayerProfileManager*	Skate::GetPlayerProfileManager( void )
+{
+	return mp_PlayerProfileManager;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Prefs::Preferences* Skate::GetSplitScreenPreferences( void )
+{
+	return mp_splitscreen_preferences;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float Skate::GetHandicap( int id )
+{
+	Prefs::Preferences* pPreferences = GetSplitScreenPreferences();
+
+	Script::CStruct* pStructure = NULL;
+	
+	switch ( id )
+	{
+		case 0:
+			pStructure = pPreferences->GetPreference( Script::GenerateCRC("player1_handicap") );
+			break;
+		case 1:
+			pStructure = pPreferences->GetPreference( Script::GenerateCRC("player2_handicap") );
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Out of range handicap id %d", id ) );
+			break;
+	}
+
+	Dbg_Assert( pStructure );
+
+	int val = 0;
+	pStructure->GetInteger( "time", &val );	// stored in the time field...  ugly!
+	return val;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// The Skate::Cleanup function is essentially cleaning up
+// the "Session" objects.
+// It does not unload any assets, just deletes objects that were valid for the session 
+
+void	Skate::Cleanup()
+{
+	uint32 i;
+
+	printf("Skate::Cleanup() - Deleting all session specific objects\n");
+
+// Resetting the skater is complicated
+// in that we have to lock him, to make sure he is not deleted
+// and then reset him manually, as we can't just delete and re-create him (yet)	
+	for (i=0;iSetLockOn();					// Sets the "lock" flag, so he won't get deleted by DestroyAllObjects
+			if( skater->IsLocalClient())
+			{
+				skater->ResetPhysics(false, true);
+				// stop any movies associated with each skater
+				this->GetMovieManager()->ClearMovieQueue();
+			}
+			skater->DeleteResources();	   // blood, gap checkslistm shodow
+			//skater->SwitchOffShadow();
+			skater->RemoveFromCurrentWorld();
+		}
+	}
+
+// Clear the movies used for moving object anims	
+	GetMovieManager()->ClearMovieQueue();
+	GetObjectAnimManager()->ClearMovieQueue();
+
+	// clear out the viewer object, if any
+	Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+	if ( pViewer )
+	{
+		pViewer->RemoveViewerObject();   	
+	}
+
+// Destroy all level objects.  Basically anything created with the node array
+	GetObjectManager()->DestroyAllObjects();
+	GetObjectManager()->FlushDeadObjects();
+
+// For now we destory all the composite objects here as well
+//	Obj::CCompositeObjectManager::Instance()->UnlockAllObjects();
+//	Obj::CCompositeObjectManager::Instance()->DestroyAllObjects();
+//	Obj::CCompositeObjectManager::Instance()->FlushDeadObjects();
+
+
+	// Now remove the lock on the skaters' objects
+	for (i=0;iSetLockOff();					// Clears the "lock" flag
+			skater->AddToCurrentWorld();
+		}
+	}
+
+	// The gamenet managers objects (crown, compass... )
+	// have now been destroyed so Nullify references to them
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	gamenet_man->CleanupObjects();
+
+	// Little sanity check, to make sure everyhting except the skater is gone	
+//	sint pTypes[]={SKATE_TYPE_SKATER,-1};
+//	GetObjectManager()->AssertIfObjectsRemainApartFrom(pTypes);
+
+	// unlock the skaters, as they will be the only remaining objects	
+	//GetObjectManager()->UnlockAllObjects();
+
+	// Delete any leftover spawned scripts.
+	// (NB, might not want to do this, if the gameflow has spawned scripts?
+	// but we probably do, as most of it will be session specific scripts	
+	Script::DeleteSpawnedScripts();
+
+	// Delete all the trick objects that were created for this session
+	mp_trickObjectManager->DeleteAllTrickObjects();
+	
+	// destroy the list of gapchecks (checklist of all gaps in the level)
+	// (maybe we would keep this from session to session?)
+//	m_gapcheck_list.DestroyAllNodes();
+		
+	// Destroy the new style particle systems....
+    // or not, as they are now all destroyed by the controlling objects, or the object manager
+    // so, if we did destroy them here, then the blood splat would stop working.
+	Nx::destroy_all_temp_particles( );
+
+	// Destroy rails, they get re-created from the node array
+	GetRailManager()->Cleanup();
+	
+	// Destroy Proximity nodes, similar to rails	
+	Obj::Proxim_Cleanup();
+
+	// Destroy EmitterObjects   
+	Obj::CEmitterManager::sCleanup();
+
+	// cleanup particles
+	Dbg_Printf( "Destroying particle systems..." );
+	Nx::CEngine::sGetParticleManager()->Cleanup();
+
+	// This is a debugging function to see what's
+	// currently in the anim cache (at this point,
+	// only a handful of skater anims should be
+	// in the cache)
+//	Nx::PrintAnimCache();
+}
+
+
+// Cleaup stuff that might have been created in the park editor test play
+void Skate::CleanupForParkEditor()
+{
+	mp_trickObjectManager->DeleteAllTrickObjects();
+	Nx::KillAllTextureSplats();
+	Nx::destroy_all_temp_particles( );
+	GetRailManager()->Cleanup();
+	Obj::Proxim_Cleanup();
+	Obj::CEmitterManager::sCleanup();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::Pause( bool pause )
+{
+	
+	if ( pause )
+	{
+		 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+		sfx_manager->PauseSounds( );
+//		Pcm::PauseMusic( 1 );
+//		Pcm::PauseStream( 1 );
+	}
+	else
+	{
+		// unpause music if game is active:
+		if ( IsGameInProgress( ) )
+		{
+//			Pcm::PauseMusic( 0 );
+//			Pcm::PauseStream( 0 );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::RequestGameEnd()
+{
+	m_gameInProgress = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Game::CGameMode *Skate::GetGameMode(void)
+{
+	return mp_gameMode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Game::CGoalManager* Skate::GetGoalManager(void)
+{
+	return mp_goalManager;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CSkaterProfile* Skate::GetCurrentProfile( Script::CStruct *pParams )
+{
+	Dbg_MsgAssert( mp_PlayerProfileManager,( "No skater profile manager\n" ));
+
+	int skaterNum;
+	if ( pParams && pParams->GetInteger( "skater", &skaterNum ) )
+	{
+		return mp_PlayerProfileManager->GetProfile( skaterNum );
+	}
+	else
+	{
+		return mp_PlayerProfileManager->GetCurrentProfile();
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CSkaterProfile* Skate::GetProfile( int skaterNum )
+{
+	Dbg_MsgAssert( mp_PlayerProfileManager,( "No skater profile manager\n" ));
+
+	return mp_PlayerProfileManager->GetProfile( skaterNum );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::SetGameType(uint32 gameType)
+{
+// Don't set this right away...	
+//	GetGameMode()->LoadGameType( gameType );
+	m_requested_game_type = gameType;
+				 
+// Instead, remember it, and
+// commit the change when we do the "setgamestate on"
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::SetCurrentGameType()
+{
+    printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<LoadGameType( m_requested_game_type );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::OpenLevel(uint32 level_script)
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+//	CloseLevel();
+
+	Mem::PushMemProfile("LaunchLevel");
+
+
+	if( gamenet_man->InNetGame())
+	{
+		if( gamenet_man->OnServer())
+		{
+			Lst::Search< GameNet::PlayerInfo > sh;
+			GameNet::PlayerInfo* player;
+
+			for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+			{
+				player->MarkAsNotReady( 0 );
+			}
+		}
+	}
+
+
+	if (level_script == Script::GenerateCRC("custom_park"))
+	{
+        ;
+	}
+	else
+	{
+#ifdef	__NOPT_ASSERT__
+		// Make sure it exists, otherwise we crash obscurely...
+		Script::CSymbolTableEntry *p_entry=Script::Resolve(level_script);
+		Dbg_MsgAssert(p_entry,("Level script %x (%s) not found",level_script,Script::FindChecksumName(level_script)));
+#endif
+		
+		Script::RunScript(level_script);
+	}
+
+	m_levelLoaded = true;
+	m_cur_level = level_script;
+
+	if( gamenet_man->InNetGame())
+	{
+		int i;
+		for( i = 0; i < GameNet::vMAX_LOCAL_CLIENTS; i++ )
+		{
+			Lst::Search< Net::Conn > sh;
+			Net::Conn* server_conn;
+			Net::Client* client;
+	
+			client = gamenet_man->GetClient( i );
+			if( client )
+			{
+				server_conn = client->FirstConnection( &sh );
+				if( server_conn )
+				{
+					server_conn->UpdateCommTime( Tmr::Seconds( 12 ));	// update the current comm time so it doesn't time out after
+																		// loading the skater
+				}
+			}
+		}
+
+		if( gamenet_man->OnServer())
+		{
+			Lst::Search< Net::Conn > sh;
+			Net::Conn* conn;
+			Net::Server* server;
+
+			server = gamenet_man->GetServer();
+			if( server )
+			{
+				for( conn = server->FirstConnection( &sh ); conn; 
+						conn = server->NextConnection( &sh ))
+				{
+					conn->UpdateCommTime( Tmr::Seconds( 8 ));	// update the current comm time so it doesn't time out after
+																// loading the skater
+				}
+			}
+		}
+		else
+		{
+			// If we have some queued up new players, we'll respond to the ready query
+			// after we finish loading them. Otherwise, if this is just a standard change level
+			// call, tell the server we're finished loading it and ready to communicate
+			Lst::Search< GameNet::NewPlayerInfo > sh;
+
+			if( gamenet_man->FirstNewPlayerInfo( sh ) == NULL )
+			{
+				gamenet_man->RespondToReadyQuery();
+			}
+		}
+	}
+
+	Mem::PopMemProfile(/*"LaunchLevel"*/);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// RestartLevel is just intended to be called when a level is restarted
+// it does not actually restart the level, it just sets a few flags
+// and does something with disabling the the viewer log that's only related to screenshot mode 
+
+void Skate::RestartLevel()
+{
+	// clear the end run flag
+	ClearEndRun();
+	
+	// GJ:  in a network lobby, the skater is loaded AFTER the
+	// level so this return wouldn't work...  come to think
+	// of it, what's it needed for?
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	if ( !GetNumSkaters() && !gamenet_man->InNetGame() )
+	{
+		Ryan("BAD BAD BAD\n");
+		return;
+	}
+
+	if (!m_levelLoaded)
+	{
+		Ryan("BAD BAD BAD\n");
+		return;
+	}
+
+	// the game is now on
+	if ( GetGameMode()->IsFrontEnd() )
+	{
+		// don't let the career timer go until
+		// we're actually in a real level,
+		// otherwise, we'll get "Requested MenuScreen
+		// not active" asserts
+		m_gameInProgress = false;
+
+	}
+	else
+	{
+		m_gameInProgress = true;
+
+//		 Mdl::RwViewer * rwviewer_mod =  Mdl::RwViewer::Instance();
+//		rwviewer_mod->DisableMainLogic( false );		// reset
+	}
+
+	if ( gamenet_man->InNetGame() )
+	{
+		#if 0
+		// unpause the game if we're in the network lobby
+		Mdl::FrontEnd* frontend = Mdl::FrontEnd::Instance();
+		frontend->SetActive(false);
+		#endif
+	}
+
+	// Mick:  tell the career mode that we are just starting this level
+	// so it will remember what the flags were at the start of the level	
+	GetCareer()->StartLevel();
+	
+	// print debug info
+	GetTrickObjectManager()->PrintContents();	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::ChangeLevel( uint32 level_id )
+{
+	 //GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	// changes level while preserving the skaters
+
+// Mick - moved this in ScriptCleanup
+//    Game::CGoalManager* p_GoalManager = Game::GetGoalManager();
+//    Dbg_MsgAssert( p_GoalManager, ( "couldn't get GoalManager\n" ) );
+//    p_GoalManager->LevelUnload();
+//    p_GoalManager->DeactivateAllGoals();
+//    p_GoalManager->RemoveAllGoals();
+
+	printf ("-----------------------------------------Skate::ChangeLevel(%d)\n",level_id);
+
+	//uint32 i;
+	Script::CStruct* pTempStructure;
+
+	// TODO:  is this necessary?   since we push individual heaps below
+	// maybe, if there is anyhting else that might allocate stuff 
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	// requests the new level...
+	pTempStructure = new Script::CStruct;
+	pTempStructure->Clear();
+	pTempStructure->AddComponent( Script::GenerateCRC("level"), ESYMBOLTYPE_NAME, (int)level_id );
+	Script::RunScript( "request_level", pTempStructure );
+	delete pTempStructure;
+
+	m_prev_level = m_cur_level;
+	m_cur_level=level_id;
+	
+	// removes the skaters from the existing world
+	// (skate::cleanup func used to take care of this
+	// but now it's in ScriptCleanup
+	/*if( !gamenet_man->InNetGame())
+	{
+		for ( i = 0; i < GetNumSkaters(); i++ )
+		{
+			Obj::CSkater* pSkater = GetSkater( i );
+			pSkater->RemoveFromCurrentWorld();
+		}
+	}*/
+
+	
+	// cleans up everything but the skaters
+	pTempStructure = new Script::CStruct;
+	pTempStructure->Clear();
+	// If we're going back to the skateshop, clean up any extra skater heaps we may have allocated
+	if( level_id != Script::GenerateCRC( "load_skateshop" ))
+	{
+		pTempStructure->AddComponent( NONAME, ESYMBOLTYPE_NAME, (int)Script::GenerateCRC("preserve_skaters") );
+	}
+	CFuncs::ScriptCleanup( pTempStructure, NULL );
+	delete pTempStructure;
+	
+	m_levelChanged = true;
+	
+	//if( gamenet_man->InNetGame())
+	//{
+		//Script::RunScript( "create_score_menu" );
+	//}
+
+	// restarts the gameflow
+	Dbg_AssertPtr( mp_gameFlow );
+	mp_gameFlow->Reset( Script::GenerateCRC( "ChangeLevelGameFlow" ) );
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+
+void Skate::ResetLevel()
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+	// Cleanup existing level specific stuff
+	Cleanup();
+
+	// Re-trigger all the objects in the level										  
+	CFuncs::ScriptParseNodeArray(NULL,NULL);
+	
+	// clear the end run flag
+	ClearEndRun();
+
+	// now that the trick object database is set,
+	// we can apply any graffiti state queued up in the trick
+	// object manager
+	GetTrickObjectManager()->ApplyObserverGraffitiState();
+
+	Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+	if( gamenet_man->InNetGame())
+	{                                                         
+		pGoalManager->InitializeAllMinigames();
+		pGoalManager->UnBeatAllGoals();
+	}
+	else
+	{
+		pGoalManager->InitializeAllGoals();
+	}
+
+	pGoalManager->CreateGoalLevelObjects();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void Skate::ResetSkaters(int node, bool walk)
+{
+    // Skip skaters to restart points (will also reset their physics states)
+    for (uint32 i=0;iIsLocalClient() )
+        {
+             skip_to_restart_point( pSkater, node, walk );			
+        }
+
+        // We Resync() the skater here, as he has just restarted
+        // he also moves on the client
+        // however, the client does not know he has to restart yet
+        // so will still be sending old positions
+        // we need to tell the clients all the restart
+        // and then not take any positions from them until they acknowledge 
+        // that they have carried out this restart
+        // This would involved calling Resync each frame until they acknowledge having restarted
+        // or we could just resync every frame for a period of time
+        // however, that seems rather dodgy, as you have no way of knowing
+        // how long you should resync for 
+		pSkater->Resync();
+
+        // So... how do we do that then Steve?
+
+    }
+    ClearEndRun();
+}		
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::LaunchGame()
+{
+	 Mdl::FrontEnd * front =  Mdl::FrontEnd::Instance();
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	front->PauseGame(false);
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+
+	printf ("\nLAUNCH GAME CALLED with %d skaters \n\n",GetNumSkaters());			   
+			   
+	//if (!GetNumSkaters())
+	if( !gamenet_man->GetLocalPlayer())
+	{
+		Dbg_Printf( "*** NO LOCAL PLAYER\n" );
+		// If we are starting a game with possibly more than two players
+		// then we want to allocate 	
+		Dbg_Printf( "*** Max players: %d\n", GetGameMode()->GetMaximumNumberOfPlayers());
+		if (GetGameMode()->GetMaximumNumberOfPlayers() > NUM_PERM_SKATER_HEAPS)
+		{
+			Mem::Manager::sHandle().InitSkaterHeaps(GetGameMode()->GetMaximumNumberOfPlayers());
+		}
+	
+		// start the game flow
+		Dbg_AssertPtr( mp_gameFlow );
+		mp_gameFlow->Reset( Script::GenerateCRC( "InitializeGameFlow" ) );
+		mp_gameFlow->Update();
+		printf( "\nLeaving Update\n" );
+	}
+	else
+	{
+		// Reset everything in the level to its initial state
+		ResetLevel();
+
+		// GJ:  we are intentionally not rerunning the intro script here
+		m_gameInProgress = true;
+
+		// start the game flow
+		Dbg_AssertPtr( mp_gameFlow );
+		mp_gameFlow->Reset( Script::GenerateCRC( "StandardGameFlow" ) );
+		printf("Restarting game flow\n");
+	}
+
+	// Clear the king of the hill
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+	if(( player = gamenet_man->GetKingOfTheHill()))
+	{
+		player->MarkAsKing( false );
+	}
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		player->ClearCTFState();
+	}
+
+	if( GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netlobby" ))
+	{
+		gamenet_man->CreateTeamFlags( GetGameMode()->NumTeams());
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::CheckSkaterCollisions( void )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Obj::CSkater* subject;
+	int i;
+	
+	if( gamenet_man->GameIsOver())
+	{
+		return;
+	}
+
+	// Only perform the collision logic if player collision is enabled
+	if( ( gamenet_man->PlayerCollisionEnabled() == false ) &&
+		( GetGameMode()->GetNameChecksum() != CRCD(0xbff33600,"netfirefight")) &&
+		( GetGameMode()->GetNameChecksum() != CRCD(0x3d6d444f,"firefight")) &&
+		( GetGameMode()->GetNameChecksum() != CRCD( 0x6ef8fda0, "netking" )) &&
+		( GetGameMode()->GetNameChecksum() != CRCD( 0x5d32129c, "king" )) &&
+		( GetGameMode()->GetNameChecksum() != CRCD( 0x6c5ff266, "netctf" )))
+	{
+		return;
+	}
+
+	// Loop through all other skaters and check for collisions
+	for( i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		subject = GetSkater( i );
+		if(	subject == NULL )
+		{
+			continue;
+		}
+		
+		subject->mp_skater_state_history_component->CollideWithOtherSkaters( 0 );
+	}
+
+	// If we're in king of the hill mode and the crown is on the ground
+	// perform collision checks to see if someone snags it
+	if(	( GetGameMode()->GetNameChecksum() == CRCD( 0x6ef8fda0, "netking" )) ||
+		( GetGameMode()->GetNameChecksum() == CRCD( 0x5d32129c, "king" )))
+	{   
+		Obj::CCrown* crown;
+
+		crown = gamenet_man->GetCrown();
+		if( crown && !crown->OnKing())
+		{
+			for( i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+			{
+				subject = GetSkater( i );
+				if(	subject == NULL )
+				{
+					continue;
+				}
+				
+				if( subject->mp_skater_state_history_component->CheckForCrownCollision())
+				{
+					GameNet::PlayerInfo* player;
+					Lst::Search< GameNet::PlayerInfo > sh;
+		
+					player = gamenet_man->GetPlayerByObjectID( subject->GetID() );
+					Dbg_Assert( player );
+
+					// It is important that we mark the king immediately (rather than through a
+					// network message) since we do logic based on the "current" king
+					player->MarkAsKing( true );
+					for( player = gamenet_man->FirstPlayerInfo( sh, true ); player;
+							player = gamenet_man->NextPlayerInfo( sh, true ))
+					{
+						// Already marked the king for the local player (above)
+						if( player->IsLocalPlayer())
+						{
+							continue;
+						}
+						
+						GameNet::MsgByteInfo msg;
+						Net::MsgDesc msg_desc;
+						Net::Server* server;
+
+						server = gamenet_man->GetServer();
+						Dbg_Assert( server );
+						
+						msg.m_Data = subject->GetID();
+
+						msg_desc.m_Data = &msg;
+						msg_desc.m_Length = sizeof( GameNet::MsgByteInfo );
+						msg_desc.m_Id = GameNet::MSG_ID_NEW_KING;
+						msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+						msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+						server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+					}
+					break;
+				}
+			}	   
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::ObserveNextSkater( void )
+{
+	GameNet::PlayerInfo* player;
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+    player = gamenet_man->GetNextPlayerToObserve();
+	gamenet_man->ObservePlayer( player );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* Skate::GetSkaterDisplayName( int id )
+{	
+	for ( uint32 j = 0; j < GetNumSkaters(); j++ )
+	{
+		if ( (int) GetSkater(j)->GetID() == id )
+		{
+			return GetSkater(j)->GetDisplayName();
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Skate::ShouldBeAbsentNode( Script::CStruct* pNode )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	Dbg_Assert( pNode );
+	
+	return ( pNode->ContainsFlag( 0x68910ac6 /*"AbsentInNetGames"*/ ) && 
+			 ( IsMultiplayerGame() || gamenet_man->InNetMode()));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Skate::IsMultiplayerGame( void )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	return ( gamenet_man->InNetGame() || GetGameMode()->GetInitialNumberOfPlayers() > 1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::SetShadowMode( Gfx::EShadowType shadowMode )
+{
+	Obj::CSkater* skater;
+	Lst::Search< Obj::CSkater > sh;
+
+	m_shadow_mode = shadowMode;
+
+	for( skater = sh.FirstItem( m_skaters ); skater; skater = sh.NextItem())
+	{
+		Obj::CShadowComponent* p_shadow_component = GetShadowComponentFromObject(skater);
+		Dbg_Assert(p_shadow_component);
+		p_shadow_component->SwitchOnSkaterShadow();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::EShadowType Skate::GetShadowMode( void ) const
+{
+	return m_shadow_mode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetGameType | Sets the type of game to be played. Examples: 
+// SetGameType FreeSkate 
+// SetGameType SingleSession
+// @uparm name | Game type
+bool ScriptSetGameType(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Skate* skate = Skate::Instance();
+	uint32 game_type;
+	pParams->GetChecksum(NONAME, &game_type, true);
+	skate->SetGameType(game_type);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TestGameType | Tests if current game type is equal to the 
+// game type specified as the first parameter
+// @uparm name | Game type
+bool ScriptTestGameType(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 game_type;
+	pParams->GetChecksum(NONAME, &game_type, true);
+	
+	Skate* skate = Skate::Instance();
+	return (game_type == skate->GetGameMode()->GetNameChecksum());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptInTeamGame(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Skate* skate = Skate::Instance();
+
+	return skate->GetGameMode()->IsTeamGame();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TestRequestedGameType |
+// This is so that I can tell what the last value sent to SetGameType
+// was, since SetGameType will not set the game type straight away,
+// so TestGameType will not work.
+// @uparm name | Game type
+bool ScriptTestRequestedGameType(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 game_type;
+	pParams->GetChecksum(NONAME, &game_type, true);
+	
+	Skate* skate = Skate::Instance();
+	return (game_type == skate->GetGameType());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ChangeLevel | 
+// @parm string | level | The level to change to
+bool ScriptChangeLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	bool show_warning;
+	uint32 level;
+
+	pParams->GetChecksum(Script::GenerateCRC("level"), &level, true);
+	if( level == Script::GenerateCRC("use_preferences") )
+	{
+		level = gamenet_man->GetLevelFromPreferences();
+	}
+		
+	gamenet_man->SetLevel( level );
+	show_warning = pParams->ContainsComponentNamed( "show_warning" );
+
+#ifdef __NOPT_ASSERT__
+	printf("Send message to ChangeLevel level=%s here!\n", Script::FindChecksumName(level) );
+#endif
+
+	Pcm::StopMusic();
+	
+	Net::Server* server;
+	GameNet::MsgChangeLevel msg;
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+
+	server = gamenet_man->GetServer();
+	Dbg_Assert( server );
+
+	msg.m_Level = level;
+	msg.m_ShowWarning = (char) show_warning;
+	
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+			player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		GameNet::MsgReady ready_msg;
+		Net::MsgDesc msg_desc;
+		
+		ready_msg.m_Time = Tmr::GetTime();
+
+		if( player->IsLocalPlayer() == false )
+		{
+			if( level == CRCD(0xb664035d,"Load_Sk5Ed_gameplay"))
+			{
+				server->StreamMessage( player->GetConnHandle(), GameNet::MSG_ID_LEVEL_DATA, Ed::CParkManager::COMPRESSED_MAP_SIZE, 
+									   Ed::CParkManager::sInstance()->GetCompressedMapBuffer(), "level data", 
+									   GameNet::vSEQ_GROUP_PLAYER_MSGS, false, true );
+						   
+				server->StreamMessage( player->GetConnHandle(), GameNet::MSG_ID_RAIL_DATA, Obj::GetRailEditor()->GetCompressedRailsBufferSize(), 
+									   Obj::GetRailEditor()->GetCompressedRailsBuffer(), "rail data", 
+									   GameNet::vSEQ_GROUP_PLAYER_MSGS, false, true );
+						   
+			}
+		}
+
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof(GameNet::MsgChangeLevel);
+		msg_desc.m_Id = GameNet::MSG_ID_CHANGE_LEVEL;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+
+		if( player->IsLocalPlayer() == false )
+		{
+			if( gamenet_man->UsingCreatedGoals())
+			{
+				gamenet_man->LoadGoals( level );
+				server->StreamMessage( player->GetConnHandle(), GameNet::MSG_ID_GOALS_DATA, gamenet_man->GetGoalsDataSize(),
+							   gamenet_man->GetGoalsData(), "goals data", GameNet::vSEQ_GROUP_PLAYER_MSGS, false, true );
+			}
+		}
+
+		// Don't send them any non-important messages until they're finished loading
+		msg_desc.m_Data = &ready_msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgReady );
+		msg_desc.m_Id = GameNet::MSG_ID_READY_QUERY;
+		server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+				
+		player->SetReadyQueryTime( ready_msg.m_Time );
+	}
+
+	// In net games, the changing of levels is deferred. No need to send immediately - we can
+	// wait until the enqueued message is naturally added to the outgoing stream.  In 2p mode,
+	// however, the scripts expect this message to be sent and handled in a one-frame window
+	// so we need to send it immediately
+	if ( ! gamenet_man->InNetGame())
+	{
+		server->SendData();		// Mick, (true) because we want to send it immediatly
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LaunchLevel | launches currently requested level
+bool ScriptLaunchLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Skate* skate_mod = Skate::Instance();
+	uint32 level = skate_mod->m_requested_level;
+	skate_mod->OpenLevel(level);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RequestLevel | 
+// @uparm name | level to request
+bool ScriptRequestLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Skate* skate_mod = Skate::Instance();
+	uint32 level;
+
+	
+//	printf ("\n%s\n",pScript->GetScriptInfo());
+	
+	
+	if( !pParams->GetChecksum(NONAME, &level, false))
+	{
+		const char* level_name;
+		char checksum_name[128];
+
+		if( pParams->GetString( NONAME, &level_name, Script::ASSERT ))
+		{
+			sprintf( checksum_name, "load_%s", level_name );
+			Dbg_Printf( "Got checksum name of %s\n", checksum_name );
+			level = Script::GenerateCRC( checksum_name );
+		}
+	}
+
+	if( level == Script::GenerateCRC("use_preferences") )
+	{
+		level = gamenet_man->GetLevelFromPreferences();
+		gamenet_man->SetLevel( level );
+	}
+
+	skate_mod->m_requested_level = level;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Retry | retry current level
+bool ScriptRetry(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Skate* skate_mod = Skate::Instance();
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	skate_mod->LaunchGame();	
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LaunchGame | 
+bool ScriptLaunchGame(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	Skate::Instance()->LaunchGame();	
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptFillRankingScreen(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Lst::Search< GameNet::PlayerInfo > sh;
+	GameNet::PlayerInfo* player;
+	Lst::Head< GameNet::ScoreRank > rank_list;
+	Lst::Search< GameNet::ScoreRank > rank_sh;
+	GameNet::ScoreRank* rank, *next;
+	bool in_goal_attack;
+	int i;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+
+	in_goal_attack = skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" );
+
+	if( skate_mod->GetGameMode()->IsTeamGame())
+	{
+		for( i = 0; i < skate_mod->GetGameMode()->NumTeams(); i++ )
+		{
+			if( gamenet_man->NumTeamMembers( i ) == 0 )
+			{
+				continue;
+			}
+			char team_name_str[64];
+						
+			rank = new GameNet::ScoreRank;
+			rank->m_IsKing = false;
+			rank->m_ColorIndex = i + 2;
+			rank->m_TeamId = i;
+			rank->m_TotalScore = gamenet_man->GetTeamScore( i );
+			
+			sprintf( team_name_str, "team_%d_name", i + 1 );
+			sprintf( rank->m_Name, "\\c%d%s %s", rank->m_ColorIndex, Script::GetString( team_name_str ), Script::GetString( "total_str" ));
+						
+			rank->SetPri( gamenet_man->GetTeamScore( i ));
+			rank_list.AddNode( rank );
+		}
+
+		// Now loop through the sorted list in order of highest to lowest score and print them out
+		i = 0;
+		for( rank = rank_sh.FirstItem( rank_list ); rank; rank = next )
+		{   
+			Script::CStruct* p_item_params;
+			char score_str[64];
+	
+			next = rank_sh.NextItem();
+	
+			for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+			{
+				char player_score_str[128];
+				int player_score;
+                
+				if( player->m_Team != rank->m_TeamId )
+				{
+					continue;
+				}
+
+				if( in_goal_attack )
+				{
+					Game::CGoalManager* pGoalManager;
+			 
+					pGoalManager = Game::GetGoalManager();
+					player_score = pGoalManager->NumGoalsBeatenBy( player->m_Skater->GetID());
+				}
+				else
+				{
+					player_score = gamenet_man->GetPlayerScore( player->m_Skater->GetID());
+				}
+				
+
+				if( player->IsLocalPlayer())
+				{
+					sprintf( player_score_str, "\\c%d> %s", rank->m_ColorIndex, player->m_Name );
+				}
+				else
+				{
+					sprintf( player_score_str, "\\c%d%s", rank->m_ColorIndex, player->m_Name );
+				}
+				
+				p_item_params = new Script::CStruct;	
+				p_item_params->AddString( "text", player_score_str );
+				// p_item_params->AddChecksum( "id", 123456 + i );
+				p_item_params->AddChecksum( "parent", Script::GenerateCRC( "player_list_menu" ));
+
+				Script::RunScript("player_menu_add_item", p_item_params);
+				delete p_item_params;
+		
+				p_item_params = new Script::CStruct;	
+				if(	skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netking" ) ||
+					skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "king" ))
+				{
+					sprintf( score_str, "%.2d:%.2d", Tmr::InSeconds( player_score ) / 60, Tmr::InSeconds( player_score ) % 60 );
+				}
+				else
+				{
+					sprintf( score_str, "%d", player_score );
+				}
+				p_item_params->AddString( "text", score_str );
+				p_item_params->AddChecksum( "font", Script::GenerateCRC( "dialog" ) );
+				p_item_params->AddChecksum( "id", 234567 + i );
+				p_item_params->AddChecksum( "parent", Script::GenerateCRC( "rankings_list_menu" ));
+		
+				Script::RunScript("score_menu_add_item",p_item_params);
+				delete p_item_params;
+
+				i++;
+			}
+			p_item_params = new Script::CStruct;	
+			p_item_params->AddString( "text", rank->m_Name );
+			// p_item_params->AddChecksum( "id", 123456 + i );
+			p_item_params->AddChecksum( NONAME, Script::GenerateCRC( "team_score" ) );
+			p_item_params->AddChecksum( "parent", Script::GenerateCRC( "player_list_menu" ));
+	
+			Script::RunScript("player_menu_add_item",p_item_params);
+			delete p_item_params;
+	
+			p_item_params = new Script::CStruct;	
+			if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netking" ) ||
+				skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "king" ))
+			{
+				sprintf( score_str, "\\c%d%.2d:%.2d", rank->m_ColorIndex, Tmr::InSeconds( rank->GetPri()) / 60, Tmr::InSeconds( rank->GetPri()) % 60 );
+			}
+			else
+			{
+				sprintf( score_str, "\\c%d%d", rank->m_ColorIndex, rank->GetPri());
+			}
+			
+			p_item_params->AddString( "text", score_str );
+			p_item_params->AddChecksum( "id", 234567 + i );
+			p_item_params->AddChecksum( "font", Script::GenerateCRC( "dialog" ) );
+			p_item_params->AddChecksum( NONAME, Script::GenerateCRC( "team_score" ) );
+			p_item_params->AddChecksum( "parent", Script::GenerateCRC( "rankings_list_menu" ));
+	
+			Script::RunScript("score_menu_add_item",p_item_params);
+			delete p_item_params;
+	
+			delete rank;
+			i++;
+		}
+	}
+	else
+	{
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			int player_score;
+
+			if( in_goal_attack )
+			{
+				Game::CGoalManager* pGoalManager;
+		 
+				pGoalManager = Game::GetGoalManager();
+				player_score = pGoalManager->NumGoalsBeatenBy( player->m_Skater->GetID());
+			}
+			else
+			{
+				player_score = gamenet_man->GetPlayerScore( player->m_Skater->GetID());
+			}
+				
+			rank = new GameNet::ScoreRank;
+			strcpy( rank->m_Name, player->m_Skater->GetDisplayName() );
+	
+			// Lists sort based on node's priority so set the node's priority to that of
+			// the player's score and add him to the list
+			rank->SetPri( player_score );
+			rank_list.AddNode( rank );
+		}
+	
+		// Now loop through the sorted list in order of highest to lowest score and print them out
+		i = 0;
+		for( rank = rank_sh.FirstItem( rank_list ); rank; rank = next )
+		{   
+			Script::CStruct* p_item_params;
+			char score_str[64];
+	
+			next = rank_sh.NextItem();
+	
+			//Script::RunScript( "prepare_server_menu_for_new_children" );
+			
+			p_item_params = new Script::CStruct;	
+			p_item_params->AddString( "text", rank->m_Name );
+			// p_item_params->AddChecksum( "id", 123456 + i );
+			p_item_params->AddChecksum( "parent", Script::GenerateCRC( "player_list_menu" ));
+	
+			Script::RunScript("player_menu_add_item",p_item_params);
+			delete p_item_params;
+	
+			p_item_params = new Script::CStruct;	
+			if( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netking" ) ||
+				skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "king" ))
+			{
+				sprintf( score_str, "%.2d:%.2d", Tmr::InSeconds( rank->GetPri()) / 60, Tmr::InSeconds( rank->GetPri()) % 60 );
+			}
+			else
+			{
+				sprintf( score_str, "%d", rank->GetPri());
+			}
+
+			p_item_params->AddString( "text", score_str );
+			p_item_params->AddChecksum( "id", 234567 + i );
+			p_item_params->AddChecksum( "font", Script::GenerateCRC( "dialog" ) );
+			p_item_params->AddChecksum( "parent", Script::GenerateCRC( "rankings_list_menu" ));
+	
+			Script::RunScript("score_menu_add_item",p_item_params);
+			delete p_item_params;
+	
+			delete rank;
+			i++;
+		}
+	}
+
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// K: This function used to use GetMaximumNumberOfPlayers,
+// but I changed it to use p_gamenet_man->GetMaxPlayers so that only
+// the required amount of memory is allocated, hence allowing bigger
+// parks that allow fewer players.
+// p_gamenet_man->GetMaxPlayers will return the num players as chosen from the Players menu
+// when starting a network game.
+
+// @script | InitSkaterHeaps | 
+bool ScriptInitSkaterHeaps(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	
+	
+	int num_skater_heaps_required=1;
+	
+    Skate *p_skate = Skate::Instance();
+	if (p_skate->GetGameType()==CRCD(0x5c8f5d66,"freeskate2p"))
+	{
+		num_skater_heaps_required=2;
+	}
+	else
+	{
+		GameNet::Manager * p_gamenet_man = GameNet::Manager::Instance();
+		num_skater_heaps_required=p_gamenet_man->GetMaxPlayers();
+	}
+		
+	Dbg_Printf( "\nInitializing %d Skater Heaps\n", num_skater_heaps_required);
+			   
+	// If we are startinga  game with possibly more than two players
+	// then we want to allocate     
+	if( num_skater_heaps_required > NUM_PERM_SKATER_HEAPS )
+	{
+		Mem::Manager::sHandle().DeleteSkaterHeaps();
+		Mem::Manager::sHandle().InitSkaterHeaps(num_skater_heaps_required);
+	}
+
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetLevel | resets the level
+bool ScriptResetLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Skate* skate_mod = Skate::Instance();
+
+	// A hacky way of preserving the running script, since skate's cleanup will destroy all
+	// spawned scripts.
+	bool was_spawned = false;
+	if( pScript )
+	{
+		was_spawned = pScript->mIsSpawned;
+		pScript->mIsSpawned = false;
+	}
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	skate_mod->ResetLevel();	
+	Mem::Manager::sHandle().PopContext();
+	
+	if( pScript )
+	{
+		pScript->mIsSpawned = was_spawned;
+	}
+
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if 0
+
+static char * maybe_new(int n, bool new_record)
+{
+	char *p = Script::GetScriptString(n);
+	if (new_record)
+	{
+		sprintf (p,"&c1");
+		p+=3;
+	}
+	return p;	
+}
+
+// insert a single line into the script strings
+void populate_line(int &index, Records::CRecord *pRecord)
+{
+		sprintf(maybe_new(index++,pRecord->GetNewRecord()),"%s",Str::PrintThousands(pRecord->GetValue()));
+		sprintf(maybe_new(index++,pRecord->GetNewRecord()),"%d",pRecord->GetNumber());
+		sprintf(maybe_new(index++,pRecord->GetNewRecord()),pRecord->GetInitials());
+}
+
+#ifndef __PLAT_NGC__
+// insert a single line into the script strings
+static void populate_line_secs(int &index, Records::CRecord *pRecord)
+{
+		#if (ENGLISH == 0)		
+		sprintf(maybe_new(index++,pRecord->GetNewRecord()),"%3.2f %s",(float)pRecord->GetValue()/100.0f,Script::GetLocalString( "skate_str_secs"));
+		#else		
+		sprintf(maybe_new(index++,pRecord->GetNewRecord()),"%3.2f secs",(float)pRecord->GetValue()/100.0f);
+		#endif
+		sprintf(maybe_new(index++,pRecord->GetNewRecord()),"%d",pRecord->GetNumber());
+		sprintf(maybe_new(index++,pRecord->GetNewRecord()),pRecord->GetInitials());
+}
+
+
+static void  populate_table(int &index, Records::CRecordTable *pRecordTable)
+{
+
+	int entries = pRecordTable->GetSize();
+	for (int i = 0; i < entries; i++)
+	{
+		populate_line(index, pRecordTable->GetRecord(i));		
+	}
+}
+#endif		// __PLAT_NGC__
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Update the records with whatever we just did this session 
+void Skate::UpdateRecords()
+{
+	m_new_record = false;
+	
+	int level = GetCareer()->GetLevel();
+
+	// get the records for this level	
+	Records::CLevelRecords *pRecords = GetGameRecords()->GetLevelRecords(level);
+
+   	// find the skater, get the score landed:
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetLocalSkater(); 
+	if( pSkater == NULL )
+	{
+		return;
+	}          
+
+	Mdl::Score * pScore = ( pSkater->GetScoreObject() );
+	/*
+	HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+	HUD::Panel* pPanel;
+	pPanel = panel_mgr->GetPanelBySkaterId(pSkater->GetID(), false);
+	*/
+
+	// Insert the score into the high score table if it is high enough	 
+	m_new_record |=  (-1 != pRecords->GetHighScores()->MaybeNewEntry(pScore->GetTotalScore(),0));												  
+	
+	m_new_record |=  (-1 != pRecords->GetBestCombos()->MaybeNewEntry(pScore->GetBestCombo(),0));
+
+	Obj::CSkaterBalanceTrickComponent* p_skater_balance_trick_component = GetSkaterBalanceTrickComponentFromObject(pSkater);
+	Dbg_Assert(p_skater_balance_trick_component);
+	
+	int this_grind = (int)(p_skater_balance_trick_component->mGrind.GetMaxTime()*100);
+	int this_manual = (int)(p_skater_balance_trick_component->mManual.GetMaxTime()*100);
+	int	this_lip = (int)(p_skater_balance_trick_component->mLip.GetMaxTime()*100);
+	// int this_combo = 0;
+	int this_combo = pScore->GetLongestCombo();
+
+	m_new_record |= pRecords->GetLongestGrind()->MaybeNewRecord(this_grind,0);	
+	m_new_record |= pRecords->GetLongestManual()->MaybeNewRecord(this_manual,0);	
+	m_new_record |= pRecords->GetLongestLipTrick()->MaybeNewRecord(this_lip,0);		
+	m_new_record |= pRecords->GetLongestCombo()->MaybeNewRecord(this_combo,0);		
+}
+
+#define	MAX_COMBO_LINES  200			// more that we need...
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifndef __PLAT_NGC__
+#if 0
+static float text_width(const char *p_string, const char *p_font)
+{
+	Fnt::Drawer drawer;
+	drawer.SetFont(p_font);
+	drawer.SetText(p_string);
+	return drawer.GetWidth();
+}
+#endif
+#endif		// __PLAT_NGC__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Utility function to get the info out of the level records
+// and into the script strings
+void Skate::GetRecordsText(int level)
+{
+#if 0
+#ifndef __PLAT_NGC__
+	
+
+	// get the records for this level	
+	Records::CLevelRecords *pRecords = GetGameRecords()->GetLevelRecords(level);
+
+	int index = 0;
+	
+		// find the skater, get the score landed:
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetLocalSkater();
+	Mdl::Score * pScore = ( pSkater->GetScoreObject() );
+	/*
+	HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+	HUD::Panel* pPanel;
+	pPanel = panel_mgr->GetPanelBySkaterId(pSkater->GetID(), false);
+	Dbg_MsgAssert(pPanel,("Could not get panel for skater on records screen"));
+	*/
+
+	int this_grind = (int)(pSkater->mGrind.GetMaxTime()*100);
+	int this_manual = (int)(pSkater->mManual.GetMaxTime()*100);
+	int	this_lip = (int)(pSkater->mLip.GetMaxTime()*100);
+	
+	// get total score for this level																		
+	sprintf(Script::GetScriptString(index++),"%s",Str::PrintThousands(pScore->GetTotalScore()));		 // 0
+	// get highest score from high score table
+	sprintf(Script::GetScriptString(index++),"%s",Str::PrintThousands(pRecords->GetHighScores()->GetRecord(0)->GetValue()));	  // 1
+	
+	
+	#if (ENGLISH == 0)
+	
+	sprintf(Script::GetScriptString(index++),"%3.2f %s",(float)this_grind/100.0f,Script::GetLocalString( "skate_str_secs"));					  // 2
+	sprintf(Script::GetScriptString(index++),"%3.2f %s",(float)this_manual/100.0f,Script::GetLocalString( "skate_str_secs"));					  // 3
+	sprintf(Script::GetScriptString(index++),"%3.2f %s",(float)this_lip/100.0f,Script::GetLocalString( "skate_str_secs"));						  // 4
+	sprintf(Script::GetScriptString(index++),"%d %s",pPanel->GetNumLongestCombo(),Script::GetLocalString( "skate_str_tricks"));			// 5
+	sprintf(Script::GetScriptString(index++),Script::GetLocalString( "skate_str_points_tricks"),Str::PrintThousands(pPanel->GetScoreBestCombo()),pPanel->GetNumBestCombo());	// 6
+
+	#else
+	
+	sprintf(Script::GetScriptString(index++),"%3.2f secs",(float)this_grind/100.0f);					  // 2
+	sprintf(Script::GetScriptString(index++),"%3.2f secs",(float)this_manual/100.0f);					  // 3
+	sprintf(Script::GetScriptString(index++),"%3.2f secs",(float)this_lip/100.0f);						  // 4
+	sprintf(Script::GetScriptString(index++),"1 tricks");			// 5
+	sprintf(Script::GetScriptString(index++),"2 points in 2 tricks");	// 6
+	//sprintf(Script::GetScriptString(index++),"%d tricks",pPanel->GetNumLongestCombo());			// 5
+	//sprintf(Script::GetScriptString(index++),"%s points in %d tricks",Str::PrintThousands(pPanel->GetScoreBestCombo()),pPanel->GetNumBestCombo());	// 6
+
+	#endif
+
+	// What we want is to get the last N lines of combo text, word wrapped
+	// to the correct size for the font we will use on the statistics screen
+	//
+	// What we have is the text for the individual tricks, and the number of tricks.
+	//
+	// - Set up a textdrawer with the correct size and font
+	// - add text to it, and to a string, until full
+	// - copy string into a ScriptString, and repeat
+
+	// find font we will be using by looking in the appropiate property 
+	Script::CStruct * p_record_font_props = Script::GetStructure("statistics_font_props");
+	const char *p_font;
+	p_record_font_props->GetText("font",&p_font);	
+	Script::CStruct * p_record_box3_props = Script::GetStructure("statistics_box_3_props");
+	int	box_width;
+	p_record_box3_props->GetInteger("box_w",&box_width);
+	  	
+	
+	Str::String line_table[MAX_COMBO_LINES];
+	int line = 0;
+	float current_w = 0;
+	int num_combos = 0;
+	//int num_combos = pPanel->GetNumBestCombo();
+	for (int trick =0; trick < num_combos; trick++)
+	{
+		const char * trick_chars = "blah";
+		//const char * trick_chars = pPanel->GetStringBestCombo(trick);
+		float trick_w = text_width(trick_chars, p_font);
+		float new_w = current_w + trick_w;
+		if (current_w > 0.0f && new_w > box_width)
+		{
+			line++;
+			Dbg_MsgAssert(line < MAX_COMBO_LINES,("Far to many combo lines"));
+			current_w = 0.0f;
+		}
+		if (current_w == 0.0f)
+		{
+			line_table[line] = trick_chars;
+		}
+		else
+		{
+			// combine the two strings into one string by sprintfing them into a temporary buffer
+			char temp[1024];	  	// good job we have a huge stack!!
+			sprintf (temp,"%s%s",line_table[line].getString(),trick_chars);
+			Dbg_MsgAssert(strlen(temp) < 1024,("line insanely too long"));  // not taking any chances
+			line_table[line] = temp;	 // this will delete the old string contents
+		}
+		current_w += trick_w;
+	}
+//	int num_lines = line+1;
+
+	line = 0;
+	while (index < 20)
+	{
+		sprintf(Script::GetScriptString(index++),line_table[line++].getString());		
+	}
+
+/* functions we can use
+	int  	GetScoreBestCombo();
+	int		GetNumBestCombo();
+	const char	* GetStringBestCombo(int trick);
+	int  	GetScoreLongestCombo();
+	int		GetNumLongestCombo();
+*/
+
+	index = 20;
+
+	populate_table(index, pRecords->GetHighScores());								  			// 20-34
+	populate_table(index, pRecords->GetBestCombos());											// 35-49
+	populate_line_secs(index,pRecords->GetLongestGrind());											// 50-52
+	populate_line_secs(index,pRecords->GetLongestManual());											// 53-55
+	populate_line_secs(index,pRecords->GetLongestLipTrick());										// 56-58
+	populate_line(index,pRecords->GetLongestCombo());											// 59-61
+#endif		// __PLAT_NGC__
+#endif
+}
+																			 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::UpdateSkaterInputHandlers()
+{	
+	printf("&&&&&&&&&&&&&&&&&&&&&&&&&& UpdateSkaterInputHandlers called\n");
+	
+	for ( int i = 0; i < vMAX_SKATERS; i++ )
+	{
+		Obj::CSkater* pSkater = GetSkater( i );
+		if ( pSkater && pSkater->IsLocalClient() )
+		{
+			printf("found a local skater - binding controller\n");
+			int heap_index = pSkater->GetHeapIndex();
+			Dbg_MsgAssert( heap_index >= 0 && heap_index < vMAX_SKATERS, ( "heap index %i out of range", heap_index ) );
+			
+			Dbg_Assert(GetInputComponentFromObject(pSkater));
+			GetInputComponentFromObject(pSkater)->BindToController(m_device_server_map[heap_index]);
+			
+			// Dan: Pretty sure this method is wrong.
+			//char skatecam[16];
+			//sprintf(skatecam,"SkaterCam%d",i);
+			//if (Obj::CCompositeObject *p_obj
+				//= static_cast< Obj::CCompositeObject* >(Obj::CCompositeObjectManager::Instance()->GetObjectByID(Script::GenerateCRC(skatecam))))
+			if (Obj::CCompositeObject *p_obj = pSkater->GetCamera())
+			{
+				Dbg_Assert(GetInputComponentFromObject(p_obj));
+				GetInputComponentFromObject(p_obj)->BindToController(m_device_server_map[heap_index]);
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Fills in a little structure with some stuff so that SkipLogic is really fast.
+void Skate::pre_calculate_object_update_info()
+{
+	if (Nx::CViewportManager::sGetNumActiveViewports() > 1 || ! Nx::CViewportManager::sGetActiveCamera(0))
+	{
+		// Nothing should be suspended if multiplayer, so leave the flag true.
+		m_precalculated_object_update_info.mDoNotSuspendAnything=true;
+	}
+	else
+	{
+		m_precalculated_object_update_info.mDoNotSuspendAnything=false;
+		m_precalculated_object_update_info.mActiveCameraPosition = Nx::CViewportManager::sGetActiveCamera(0)->GetPos();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Prefs::Preferences* GetPreferences( Script::CStruct* pParams, bool assert_on_fail )
+{   
+	uint32 checksum;
+	pParams->GetChecksum( "prefs", &checksum, true );
+
+	return GetPreferences( checksum, assert_on_fail );
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Prefs::Preferences* GetPreferences( uint32 checksum, bool assert_on_fail )
+{	
+	if ( checksum == Script::GenerateCRC("network") )
+	{
+		 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+		return gamenet_man->GetNetworkPreferences();
+	}
+	else if ( checksum == Script::GenerateCRC("taunt") )
+	{
+		 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+		return gamenet_man->GetTauntPreferences();
+	}
+	else if ( checksum == Script::GenerateCRC("splitscreen") )
+	{
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		return skate_mod->GetSplitScreenPreferences();
+	}
+
+	if ( assert_on_fail )
+	{
+		Dbg_MsgAssert( 0, ( "Couldn't find preferences for %s", Script::FindChecksumName(checksum) ) );
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Just a simple debugging interface function to call the main rail 
+// manager's DebugRender() function
+void			Rail_DebugRender()
+{
+	Mdl::Skate* skate_mod =  Mdl::Skate::Instance();
+	if (!skate_mod->GetDrawRails())
+	{
+		return;
+	}
+
+	skate_mod->GetRailManager()->DebugRender();
+
+	for (Obj::CRailManagerComponent* p_rail_manager_component = static_cast< Obj::CRailManagerComponent* >(Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_RAILMANAGER));
+		p_rail_manager_component;
+		p_rail_manager_component = static_cast< Obj::CRailManagerComponent* >(p_rail_manager_component->GetNextSameType()))
+	{
+		Obj::CCompositeObject* p_movable_object = p_rail_manager_component->GetObject();
+
+		// form a transformation matrix
+		Mth::Matrix total_mat = p_movable_object->GetMatrix();
+		total_mat[X][W] = 0.0f;
+		total_mat[Y][W] = 0.0f;
+		total_mat[Z][W] = 0.0f;
+		total_mat[W] = p_movable_object->GetPos();
+		total_mat[W][W] = 1.0f;
+
+		
+		Obj::CRailManager *p_railman = p_rail_manager_component->GetRailManager();								   
+		if (p_railman)
+		{
+			p_railman->DebugRender(&total_mat);			
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// return the current gap checklist for the level that we are in														 
+Obj::CGapChecklist* Skate::GetGapChecklist()
+{
+	Obj::CGapChecklist* p_gap_checklist = GetCareer()->GetGapChecklist(); 
+	Dbg_Assert(p_gap_checklist);
+	return p_gap_checklist;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Skate::ShouldAllocateNetMiscHeap( void )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	return ( gamenet_man->InNetGame() && ( m_cur_level != Script::GenerateCRC( "Load_Skateshop" ))) ;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Skate::ShouldAllocateInternetHeap( void )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	if( gamenet_man->InNetMode())
+	{
+		return true;
+	}
+
+	// GJ:  need to temporarily remove LOAD_CAS, because 
+	// the internet heap is coming in after the unloadable
+	// anims, and the unloadable anims need to be the last
+	// thing on the topdown heap
+	if( ( m_requested_level == Script::GenerateCRC( "load_skateshop" ) ) 
+		|| ( m_requested_level == Script::GenerateCRC( "load_CAS" )))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::FirstInputReceived()
+{
+	m_first_input_received = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
diff --git a/Code/Sk/Modules/Skate/skate.h b/Code/Sk/Modules/Skate/skate.h
new file mode 100644
index 0000000..ffa2cd1
--- /dev/null
+++ b/Code/Sk/Modules/Skate/skate.h
@@ -0,0 +1,524 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			SKATE													**
+**																			**
+**	File name:		modules/skate/skate.h									**
+**																			**
+**	Created: 		06/07/2000	-	spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef __MODULES_SKATE_SKATE_H
+#define __MODULES_SKATE_SKATE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include  // For EShadowType
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+enum
+{
+	SKATE_TYPE_UNDEFINED 		= 0,
+	SKATE_TYPE_SKATER 			= 1,
+	SKATE_TYPE_PED				= 2,
+	SKATE_TYPE_CAR				= 3,
+	SKATE_TYPE_GAME_OBJ			= 4,
+	SKATE_TYPE_BOUNCY_OBJ		= 5,
+	SKATE_TYPE_CASSETTE			= 6,
+	SKATE_TYPE_ANIMATING_OBJECT	= 7,
+	SKATE_TYPE_CROWN			= 8,
+	SKATE_TYPE_PARTICLE			= 9,
+	SKATE_TYPE_REPLAY_DUMMY		= 10,
+	SKATE_TYPE_COMPOSITE		= 11,		// just a temp type, to exclude it from certain checks
+	
+	// Note: If a new type is added here, please also add it to the switch statement in
+	// the CCompositeObject::GetDebugInfo function in gel\object\compositeobject.cpp and also add
+	// the name to data\scripts\debugger\debugger_names.q
+	// That way the m_type member will appear as a name in the script debugger rather than as
+	// an integer.
+};
+
+namespace Game
+{
+    class CGameMode;
+	class CGoalManager;
+}
+
+namespace GameNet
+{
+	class PlayerInfo;
+}
+
+namespace Obj
+{
+    class CGapChecklist;
+    class CGeneralManager;
+	class CMovieManager;
+    class CObject;
+	class CPlayerProfileManager;
+	class CProximManager;
+	class CRailManager;
+	class CSkater;
+	class CSkaterCareer;
+	class CSkaterProfile;
+    class CTrickChecksumTable;
+    class CTrickObjectManager;
+
+#	ifdef TESTING_GUNSLINGER
+	class CNavManager;
+#	endif
+};
+
+namespace Prefs
+{
+	class Preferences;
+};
+
+namespace Records
+{
+	class CGameRecords;
+};
+
+namespace Script
+{
+    class CScript;
+    class CStruct;
+};
+
+namespace Mdl
+{
+    class CCompetition;
+    class CGameFlow;
+    class CHorse;
+
+// Little structure for storing information about a high score goal
+// currently there are just two, the "high score" and "pro score"
+// we did previously have an additional "sick score", which may return
+// hence, I'm trying to be flexible here, if not totally object oriented...   
+struct	SScoreGoal
+{
+	int				score;					// What score we try to get
+	uint32			script;					// what script it triggers
+	int				goal;					// what goal we get (can be -1 for none, if you want to do it in script)
+};
+   
+// There is an array of vMAX_SKATERS of these in the Skate class.
+// They get set from the controller config menu in the skateshop, and
+// get saved out to the memory card. (see ScriptSaveToMemoryCard in cfuncs.cpp)
+//
+// Brad - the skaterIndex is used to map a controller to a skater.
+// This is needed to meet an XBox requirement that whatever controller they
+// use to start the game can be used to play.
+struct SControllerPreferences
+{
+	int skaterIndex;
+	bool VibrationOn;
+	bool AutoKickOn;
+	bool SpinTapsOn;
+};
+
+// Ken: A small structure for holding info needed by SkipLogic & MovingObj_Update
+// to save them having to calculate the same things for billions of objects every frame.
+// This structure gets filled in once per frame, and read by SkipLogic & MovingObj_Update
+struct SPreCalculatedObjectUpdateInfo
+{
+	// This is set if it is a multiplayer game or if a cutscene is playing
+	bool mDoNotSuspendAnything;
+	
+	// SkipLogic calculates the distance of the object from this.
+	Mth::Vector mActiveCameraPosition;
+};
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class Skate : public Module 
+{
+
+public:
+	
+	enum
+	{
+		vMAX_SKATERS 		= 8,
+		vMAX_SCORE_GOALS    = 64,		// Don't really need 32, but just to cover the maximun number of goals
+	};
+									Skate( void );
+	virtual							~Skate( void );
+		
+	// Cleans up game objects and optionally skaters
+	void							Cleanup();
+	void 							CleanupForParkEditor();
+
+	
+	void							Pause( bool pause = true );
+	
+	/************************************
+	* skater control functions
+	*************************************/
+	Obj::CSkater* 					GetLocalSkater( void );
+	Obj::CSkater*					GetSkater( uint num );
+	Obj::CSkater*					GetSkaterById( uint32 id );
+	uint32							GetNumSkaters( void );
+	int								GetNextUnusedSkaterHeapIndex( bool for_local_skater );
+	void		 					GetNextStartingPointData( Mth::Vector* pos, Mth::Matrix* matrix, int obj_id );
+	void							HideSkater( Obj::CSkater* skater, bool should_hide = true );
+	Obj::CSkater*					add_skater( Obj::CSkaterProfile* pSkaterProfile, bool local_client, int obj_id, int player_num );
+	void							remove_skater( Obj::CSkater* skater );	// Remove a specific skater
+	int								find_restart_node( int index );
+	void							skip_to_restart_point( Obj::CSkater* skater, int node = -1, bool walk = false );
+	void							move_to_restart_point( Obj::CSkater* skater );
+	
+	void							PauseGameFlow( bool paused );
+	void							SetTimeLimit( int seconds );
+	int								GetTimeLimit( void );
+
+	void							FirstInputReceived();
+	
+	/************************************
+	* game control functions
+	*************************************/
+	
+public:
+	// game control
+	void							SetGameType(uint32 gameType);
+	// Added by Ken, for use by the cassette menu so that it can tell whether it is to be
+	// used for career, free skate or session.
+	uint32							GetGameType() {return m_requested_game_type;}
+	
+	void							SetCurrentGameType();
+
+	// returns checksum of level script name
+	uint32							GetGameLevel();		  
+
+	// informs the skate module that we want the game in progress to end
+	void							RequestGameEnd();
+	bool							IsGameInProgress() {return m_gameInProgress;}
+
+	void							SetLaunchingQueuedScripts( void );
+	void							ClearLaunchingQueuedScripts( void );
+	bool							LaunchingQueuedScripts( void );
+
+	// used to not load objects that were meant to be left
+	// out for performance/bandwidth/gameplay reasons
+	bool							IsMultiplayerGame( void );
+	bool							ShouldBeAbsentNode( Script::CStruct* pNode );
+	
+	void							OpenLevel(uint32 level_script);
+	void							RestartLevel();
+	void							ResetLevel();
+	void							ResetSkaters(int node = -1, bool walk = false);
+	void							ChangeLevel( uint32 level_id );
+	void							LaunchGame();
+	void							LaunchNetworkGame();
+
+	/************************************
+	* network functions					*
+	*************************************/
+	void							StartServer( void );
+	void							AddNetworkMsgHandlers( Net::Client* client, int index );
+	void							LeaveServer( void );
+	void							SendScoreUpdates( bool final = false );
+	void 							ObserveNextSkater( void );
+	uint32							GetCheatChecksum( void );
+	void							SendCheatList( GameNet::PlayerInfo* player );
+
+	/************************************
+	* other functions
+	*************************************/
+
+	const char*						GetSkaterDisplayName( int id );
+	Obj::CSkaterProfile*			GetProfile( int i );
+	Obj::CSkaterProfile*			GetCurrentProfile( Script::CStruct* pParams = NULL );
+	Obj::CSkaterCareer*				GetCareer(){return mp_career;} 
+	Game::CGameMode*				GetGameMode();
+	Game::CGoalManager*				GetGoalManager();
+	Obj::CGeneralManager*			GetObjectManager( void );
+	Obj::CTrickObjectManager*		GetTrickObjectManager( void );
+	Obj::CPlayerProfileManager*		GetPlayerProfileManager( void );
+	Obj::CTrickChecksumTable*		GetTrickChecksumTable() { return mp_trickChecksumTable; }
+
+	Prefs::Preferences*				GetSplitScreenPreferences( void );
+	float							GetHandicap( int id );
+
+	void 							CheckSkaterCollisions( void );
+	
+    static Tsk::Task< Skate >::Code   	s_logic_code; 
+	static Tsk::Task< Skate >::Code   	s_object_update_code; 
+	static Tsk::Task< Skate >::Code   	s_score_update_code; 
+	static void	s_object_update( Obj::CObject* object, void* data );
+	
+private:
+			
+	void							v_start_cb( void );
+	void							v_stop_cb( void );
+	
+	Tsk::Task< Skate >*				m_logic_task;	
+	Tsk::Task< Skate >*				m_object_update_task;	
+	Tsk::Task< Skate >*				m_score_update_task;    
+
+
+	Obj::CSkaterCareer*				mp_career;
+	
+public:
+	Obj::CRailManager*	            mp_railManager;    
+	Obj::CProximManager*	        mpProximManager;
+	void 							UpdateGameFlow();
+	Obj::CRailManager*	            GetRailManager() { return mp_railManager; }
+
+#	ifdef TESTING_GUNSLINGER
+	Obj::CNavManager*				mp_navManager;
+	Obj::CNavManager*	            GetNavManager() { return mp_navManager; }
+#	endif
+
+	void			                Rail_DebugRender();
+
+private:
+	void 							DoUpdate();	// called by s_logic
+
+	
+//	Obj::CGeneralManager*			mp_obj_manager;
+	Lst::Head< Obj::CSkater >		m_skaters;
+	    
+	bool 							m_recording;
+	bool 							m_playing;
+	int 							m_fd; 
+
+	bool							m_first_input_received;
+	int								m_controller_unplugged_frame_count;
+
+	// Network msg handlers
+	void							net_setup( void );
+	void							net_shutdown( void );
+	bool							should_send_update( GameNet::PlayerInfo* send_player, Obj::CObject* object );
+
+	bool							m_launching_queued_scripts;
+	    
+	static Net::MsgHandlerCode		handle_cheats;
+	static Net::MsgHandlerCode		handle_cheat_list;
+	static Net::MsgHandlerCode		handle_projectile;
+	static Net::MsgHandlerCode		handle_enter_vehicle_client;
+	
+	static Net::MsgHandlerCode		handle_anims;
+	static Net::MsgHandlerCode		handle_flip_anim;
+	static Net::MsgHandlerCode		handle_enter_vehicle_server;
+	
+	static Net::MsgHandlerCode		handle_msg_relay;
+	static Net::MsgHandlerCode		handle_selective_msg_relay;
+	static Net::MsgHandlerCode		handle_script_relay;
+	static Net::MsgHandlerCode		handle_score_request;
+	static Net::MsgHandlerCode		handle_landed_trick;
+	static Net::MsgHandlerCode		handle_combo_report;
+	static Net::MsgHandlerCode		handle_object_update;
+	static Net::MsgHandlerCode		handle_object_update_relay;
+	static Net::MsgHandlerCode		handle_player_quit;
+	static Net::MsgHandlerCode		handle_disconn_accepted;
+	static Net::MsgHandlerCode		handle_kicked;
+	static Net::MsgHandlerCode		handle_play_sound;
+	static Net::MsgHandlerCode		handle_sparks;
+	static Net::MsgHandlerCode		handle_blood;
+	static Net::MsgHandlerCode		handle_score_update;
+	static Net::MsgHandlerCode		handle_bail_done;
+	static Net::MsgHandlerCode		handle_change_level;
+	static Net::MsgHandlerCode		handle_run_script;
+	static Net::MsgHandlerCode		handle_spawn_run_script;
+	static Net::MsgHandlerCode		handle_start_info;
+	static Net::MsgHandlerCode		handle_free_trick_objects;
+	static Net::MsgHandlerCode		handle_obs_log_trick_objects;
+	static Net::MsgHandlerCode		handle_obs_init_graffiti_state;
+	static Net::MsgHandlerCode		handle_face_data;
+		
+	friend bool ScriptSetGameType(Script::CStruct *pParams, Script::CScript *pScript);
+	friend bool ScriptInTeamGame(Script::CStruct *pParams, Script::CScript *pScript);
+	friend bool ScriptLaunchLevel(Script::CStruct *pParams, Script::CScript *pScript);
+    friend bool ScriptSetTimeLimit(Script::CStruct *pParams, Script::CScript *pScript);
+	
+	// Exactly what it says. This should be the master flag for determining if a game is in progress.
+	bool							m_gameInProgress;
+	bool							m_levelLoaded;
+
+	bool							m_endRun;
+
+	uint32 							m_gameType;
+
+	// needed so that we can restore the skatercamera
+	// after skatercams and cutscenes...
+	bool							m_lastFrameWasMovieCamera;
+
+
+	// Keeps track of all the rules/options for this particular game mode
+	Game::CGameMode*				mp_gameMode;
+
+	// Keeps track of all the trick objects in the scene
+	Obj::CTrickObjectManager*		mp_trickObjectManager;
+
+	// Keeps track of all the trick names in the game
+	Obj::CTrickChecksumTable*		mp_trickChecksumTable;
+
+	// Keeps track of skater appearance, stats, etc.
+	Obj::CPlayerProfileManager*		mp_PlayerProfileManager;
+
+	// Keeps track of all the high scores and suchlike
+	Records::CGameRecords*			mp_gameRecords;
+
+	// Keeps track of splitscreen preferences
+	Prefs::Preferences*				mp_splitscreen_preferences;
+
+	uint32							m_requested_game_type;
+	bool							m_levelChanged;
+
+	SScoreGoal						m_ScoreGoal[vMAX_SCORE_GOALS];
+	CCompetition*					mp_competition;
+	CHorse*							mp_horse;
+	Obj::CMovieManager*				mp_movieManager;
+	Obj::CMovieManager*				mp_objectAnimManager;
+
+	Gfx::EShadowType				m_shadow_mode;
+	
+	int								m_time_limit;
+
+public:
+	void 							BeepTimer(float time, float beep_time, float beep_speed, const char *p_script_name);
+
+	bool							ShouldAllocateInternetHeap( void );
+	bool							ShouldAllocateNetMiscHeap( void );
+
+	uint32							m_requested_level;
+	uint32							m_cur_level;
+	uint32							m_prev_level;
+	
+	bool							m_new_record;
+		
+	void							GetRecordsText(int level);
+	void 							UpdateRecords();
+
+	void							SetShadowMode( Gfx::EShadowType shadowMode );
+	Gfx::EShadowType				GetShadowMode( void ) const;
+	
+	Records::CGameRecords*			GetGameRecords() {return mp_gameRecords;}
+
+	Obj::CGapChecklist		*		GetGapChecklist();
+	
+// Some handy dandy functions for accessing the high score goals 	
+	int								GetScoreGoalScore(int n){return m_ScoreGoal[n].score;}
+	void							SetScoreGoalScore(int n, int score){m_ScoreGoal[n].score = score;}
+	uint32							GetScoreGoalScript(int n){return m_ScoreGoal[n].script;}
+	void							SetScoreGoalScript(int n, uint32 script){m_ScoreGoal[n].script = script;}	
+	int								GetScoreGoalGoal(int n){return m_ScoreGoal[n].goal;}
+	void							SetScoreGoalGoal(int n, uint32 goal){m_ScoreGoal[n].goal = goal;}
+	void							ClearScoreGoals() {for (int i =0;i
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+//#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mdl
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define	vFORCE_UPDATE_INTERVAL	60
+#define vOBJ_UPDATE_THRESHOLD	FEET( 150 )
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+class ObjUpdateInfo
+{
+public:
+	Net::BitStream			m_BitStream;
+	int 					m_Handle;
+	GameNet::PlayerInfo* 	m_Player;
+};
+
+/*****************************************************************************
+**							   Private Classes								**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/* Create all network-related tasks and handlers                  */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::net_setup( void )
+{
+	// Server net tasks
+	m_object_update_task = new Tsk::Task< Skate > ( Skate::s_object_update_code, *this,
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_OBJECT_UPDATE );
+	m_score_update_task = new Tsk::Task< Skate > ( Skate::s_score_update_code, *this,
+ 										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_SCORE_UPDATE );
+}
+
+/******************************************************************/
+/* delete all network-related tasks and handlers                  */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::net_shutdown( void )
+{
+	// Server net tasks
+	delete m_object_update_task;
+	delete m_score_update_task;
+}
+
+/******************************************************************/
+/* Determines whether we should send "send_player" an update 	  */
+/* concerning "object"                                            */
+/******************************************************************/
+
+bool		Skate::should_send_update( GameNet::PlayerInfo* send_player, Obj::CObject* object )
+{
+	Obj::SPosEvent* latest_state;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* skater_player;
+	Obj::CSkater* skater_obj;
+	int id, index;
+	
+	// Dont send a client updates on his own skater. He is up to date.
+	skater_player = gamenet_man->GetPlayerByObjectID( object->GetID() );
+	if(	( skater_player == NULL ) ||
+		( skater_player == send_player ))
+	{
+		return false;
+	}
+	
+	skater_obj = skater_player->m_Skater;
+	// Check if we have something to report
+	if( skater_obj->GetStateHistory()->GetNumPosUpdates() == 0 )
+	{
+		//Dbg_Printf( "Not enough pos updates\n" );
+		return false;
+	}
+
+	id = skater_obj->GetID();
+	index = ( skater_obj->GetStateHistory()->GetNumPosUpdates() - 1 ) % Obj::CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+	latest_state = &skater_obj->GetStateHistory()->GetPosHistory()[index];
+
+	// Only send an update if there's something new to tell
+	if( latest_state->LoTime == send_player->m_LastSentProps.m_LastSkaterUpdateTime[id] )
+	{
+		//Dbg_Printf( "Nothing new to tell\n" );
+		return false;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/* Decide which fields need to be updated for this object		  */
+/*                                                                */
+/******************************************************************/
+
+static	int		s_get_update_flags( GameNet::PlayerInfo* player, 
+									Obj::SPosEvent* latest_state,
+									int skater_id ) 
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	int i, update_flags;
+	short pos[3];
+	short rot[3];
+	Flags< int > skater_flags;
+	Flags< int > end_run_flags;
+	Mth::Vector eulers;
+	char state, doing_trick, terrain, walking, driving;
+	bool forced_send;
+	unsigned char id;
+	sint16 rail_node;
+
+	update_flags = 0;
+
+	// Force an object update every N frames
+	forced_send = !( gamenet_man->GetServer()->m_FrameCounter % vFORCE_UPDATE_INTERVAL ); 
+	id = skater_id;
+
+	for( i = 0; i < 3; i++ )
+	{
+		eulers[i] = latest_state->Eulers[i];
+	}
+
+	// Write out the object's position as three shorts (fixed-point)
+	for( i = 0; i < 3; i++ )
+	{
+		if( i == Y )
+		{
+			pos[i] = (short) ( latest_state->Position[i] * 4.0f );
+			//pos[i] = latest_state->Position[i];
+		}
+		else
+		{
+			pos[i] = (short) ( latest_state->Position[i] * 2.0f );
+			//pos[i] = latest_state->Position[i];
+		}
+		if(( pos[i] != player->m_LastSentProps.m_LastSkaterPosUpdate[id][i] ) || forced_send )
+		{
+			if( i == X )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_POS_X;
+			}
+			else if( i == Y )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_POS_Y;
+			}
+			else if( i == Z )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_POS_Z;
+			}
+		}
+	}
+	
+	// Write out the object's orientation as three short euler angles (fixed-point)
+	for( i = 0; i < 3; i++ )
+	{
+		rot[i] = (short) ( eulers[i] * 4096.0f );
+		if(( rot[i] != player->m_LastSentProps.m_LastSkaterRotUpdate[id][i] ) || forced_send )
+		{   
+			if( i == X )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_ROT_X;
+			}
+			else if( i == Y )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_ROT_Y;
+			}
+			else if( i == Z )
+			{
+				update_flags |= GameNet::mUPDATE_FIELD_ROT_Z;
+			}
+		}
+	}                                          
+
+	state = (char) latest_state->State;
+	doing_trick = (char) latest_state->DoingTrick;
+	terrain = (char) latest_state->Terrain;
+	walking = (char) latest_state->Walking;
+	driving = (char) latest_state->Driving;
+
+	if(( state != player->m_LastSentProps.m_LastSkaterStateUpdate[id] ) ||
+	   ( doing_trick != player->m_LastSentProps.m_LastDoingTrickUpdate[id] ) ||
+	   ( terrain != player->m_LastSentProps.m_LastSkaterTerrain[id] ) || 
+	   ( walking != player->m_LastSentProps.m_LastSkaterWalking[id] ) || 
+	   ( driving != player->m_LastSentProps.m_LastSkaterDriving[id] ) || 
+	   ( forced_send == true ))
+	{
+		update_flags |= GameNet::mUPDATE_FIELD_STATE;
+	}
+
+	skater_flags = latest_state->SkaterFlags;
+	end_run_flags = latest_state->EndRunFlags;
+
+	if(( skater_flags != player->m_LastSentProps.m_LastSkaterFlagsUpdate[id] ) || 
+	   ( end_run_flags != player->m_LastSentProps.m_LastEndRunFlagsUpdate[id] ) || 
+	   ( forced_send == true ))
+	{
+		update_flags |= GameNet::mUPDATE_FIELD_FLAGS;
+	}
+
+	rail_node = latest_state->RailNode;
+	if(( rail_node != player->m_LastSentProps.m_LastRailNodeUpdate[id] ) ||
+	   ( forced_send == true ))
+	{
+		update_flags |= GameNet::mUPDATE_FIELD_RAIL_NODE;
+	}
+
+	return update_flags;
+}
+
+/******************************************************************/
+/* Place positional/state update data in the outbound stream	  */
+/*                                                                */
+/******************************************************************/
+
+void		Skate::s_object_update( Obj::CObject* object, void* data )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* player, *skater_player;
+	ObjUpdateInfo* update_info;
+	Obj::CSkater* skater_obj;
+	Mth::Vector eulers;
+	short pos[3];
+	short rot[3];
+	unsigned char id;
+	char state, doing_trick, terrain, walking, driving;
+	Flags< int > skater_flags;
+	Flags< int > end_run_flags;
+	int update_flags;
+	Obj::SPosEvent* latest_state;
+	int i, index;
+	unsigned short time;
+	
+	Dbg_Assert( data );
+
+	update_info = (ObjUpdateInfo *) data;
+
+	skater_player = gamenet_man->GetPlayerByObjectID( object->GetID() );
+	skater_obj = skater_player->m_Skater;
+	id = (char) skater_obj->GetID();
+	player = update_info->m_Player;
+	index = ( skater_obj->GetStateHistory()->GetNumPosUpdates() - 1 ) % Obj::CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+	latest_state = &skater_obj->GetStateHistory()->GetPosHistory()[index];
+	player->m_LastSentProps.m_LastSkaterUpdateTime[id] = latest_state->GetTime();
+
+	for( i = 0; i < 3; i++ )
+	{
+		eulers[i] = latest_state->Eulers[i];
+	}
+
+	time = latest_state->LoTime;
+	update_info->m_BitStream.WriteValue( time, sizeof( uint16 ) * 8 );
+
+	update_flags = s_get_update_flags( player, latest_state, id );
+	update_info->m_BitStream.WriteValue( update_flags, 9 );
+
+	//Dbg_Printf( "*** Sending Pos[%.2f %.2f %.2f]\n", latest_state->Position[X], latest_state->Position[Y], latest_state->Position[Z] );
+	// Write out the object's position as three shorts (fixed-point)
+	for( i = 0; i < 3; i++ )
+	{
+		if( i == Y )
+		{
+			pos[i] = (short) ( latest_state->Position[i] * 4.0f );
+			//pos[i] = latest_state->Position[i];
+		}
+		else
+		{
+			pos[i] = (short) ( latest_state->Position[i] * 2.0f );
+			//pos[i] = latest_state->Position[i];
+		}
+		
+		if( i == X )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_POS_X )
+			{
+				update_info->m_BitStream.WriteValue( pos[i], sizeof( short ) * 8 );
+				//update_info->m_BitStream.WriteFloatValue( pos[i] );
+			}
+		}
+		else if( i == Y )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_POS_Y )
+			{
+				update_info->m_BitStream.WriteValue( pos[i], sizeof( short ) * 8 );
+				//update_info->m_BitStream.WriteFloatValue( pos[i] );
+			}
+		}
+		else if( i == Z )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_POS_Z )
+			{
+				update_info->m_BitStream.WriteValue( pos[i], sizeof( short ) * 8 );
+				//update_info->m_BitStream.WriteFloatValue( pos[i] );
+			}
+		}
+		
+		player->m_LastSentProps.m_LastSkaterPosUpdate[id][i] = pos[i];
+	}
+	
+	// Write out the object's orientation as three short euler angles (fixed-point)
+	for( i = 0; i < 3; i++ )
+	{
+		rot[i] = (short) ( eulers[i] * 4096.0f );
+		if( i == X )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_ROT_X )
+			{
+				update_info->m_BitStream.WriteValue( rot[i], sizeof( short ) * 8 );
+			}
+		}
+		else if( i == Y )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_ROT_Y )
+			{
+				update_info->m_BitStream.WriteValue( rot[i], sizeof( short ) * 8 );
+			}
+		}
+		else if( i == Z )
+		{
+			if( update_flags & GameNet::mUPDATE_FIELD_ROT_Z )
+			{
+				update_info->m_BitStream.WriteValue( rot[i], sizeof( short ) * 8 );
+			}
+		}
+		
+		player->m_LastSentProps.m_LastSkaterRotUpdate[id][i] = rot[i];
+	}                                          
+
+	state = (char) latest_state->State;
+	doing_trick = (char) latest_state->DoingTrick;
+	terrain = (char) latest_state->Terrain;
+	walking = (char) latest_state->Walking;
+	driving = (char) latest_state->Driving;
+
+	// Write out the skaters' state
+	// Write out the skater's "doing trick" flag
+	// Write out the skater's terrain type
+	if( update_flags & GameNet::mUPDATE_FIELD_STATE )
+	{
+		char mask;
+
+		mask = state;
+		if( doing_trick )
+		{
+			mask |= GameNet::mDOING_TRICK_MASK;
+		}
+
+		update_info->m_BitStream.WriteValue( mask, 4 );
+		update_info->m_BitStream.WriteValue( terrain, 6 );
+		update_info->m_BitStream.WriteValue( walking, 1 );
+		update_info->m_BitStream.WriteValue( driving, 1 );
+
+		player->m_LastSentProps.m_LastSkaterStateUpdate[id] = state;
+		player->m_LastSentProps.m_LastDoingTrickUpdate[id] = doing_trick;
+		player->m_LastSentProps.m_LastSkaterTerrain[id] = terrain;
+		player->m_LastSentProps.m_LastSkaterWalking[id] = walking;
+		player->m_LastSentProps.m_LastSkaterDriving[id] = driving;
+	}
+
+	// Write out the skaters' flags
+	skater_flags = latest_state->SkaterFlags;
+	end_run_flags = latest_state->EndRunFlags;
+
+	if( update_flags & GameNet::mUPDATE_FIELD_FLAGS )
+	{
+		player->m_LastSentProps.m_LastSkaterFlagsUpdate[id] = skater_flags;
+		player->m_LastSentProps.m_LastEndRunFlagsUpdate[id] = end_run_flags;
+		update_info->m_BitStream.WriteValue( skater_flags, 5 );
+		update_info->m_BitStream.WriteValue( end_run_flags, 3 );
+	}
+
+	// Write out the skaters' rail node
+	if( update_flags & GameNet::mUPDATE_FIELD_RAIL_NODE )
+	{
+		player->m_LastSentProps.m_LastRailNodeUpdate[id] = latest_state->RailNode;
+		update_info->m_BitStream.WriteValue( latest_state->RailNode, sizeof( sint16 ) * 8 );
+	}
+	
+}
+
+/******************************************************************/
+/* Update Clients with objects' positional/rotational/anim data	  */
+/*                                                                */
+/******************************************************************/
+
+void		Skate::s_object_update_code ( const Tsk::Task< Skate >& task )
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+	Net::Server* server;
+    
+    Dbg_AssertType ( &task, Tsk::Task< Skate > );
+	Skate& mdl = task.GetData();
+	
+    server = gamenet_man->GetServer();
+
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = 
+				gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		Net::MsgMax update_msg;
+		unsigned int length;
+		ObjUpdateInfo update_info;
+		GameNet::PlayerInfo* update_player;
+		int i, start_id, obj_id, num_updates;
+        
+		// Don't send updates to the local client. His stuff is already up-to-date
+		if( player->m_Conn->IsRemote())
+		{
+			int obj_id_mask;
+
+			update_info.m_BitStream.SetOutputData( update_msg.m_Data, 4096 );	// skip Obj ID Mask
+			update_info.m_Handle = player->m_Conn->GetHandle();
+			update_info.m_Player = player;
+					
+			obj_id_mask = 0;
+
+			// First decide which objects' updates we will send, then append them to the stream
+			// in increasing id order 
+			num_updates = 0;
+			start_id = ( player->GetLastObjectUpdateID() + 1 ) % vMAX_SKATERS;
+			for( i = 0; i < vMAX_SKATERS; i++ )
+			{
+				obj_id = ( start_id + i ) % vMAX_SKATERS;
+				update_player = gamenet_man->GetPlayerByObjectID( obj_id );
+				if( update_player )
+				{
+					if( mdl.should_send_update( player, update_player->m_Skater ))
+					{
+						obj_id_mask |= ( 1 << obj_id );
+						if( ++num_updates >= player->GetMaxObjectUpdates())
+						{
+							break;
+						}
+					}
+				}
+			}
+
+			if( num_updates == 0 )
+			{
+				continue;
+			}
+
+			update_info.m_BitStream.WriteValue( obj_id_mask, sizeof( char ) * 8 );
+			for( i = 0; i < vMAX_SKATERS; i++ )
+			{
+				if( obj_id_mask & ( 1 << i ))
+				{
+					update_player = gamenet_man->GetPlayerByObjectID( i );
+
+					s_object_update( update_player->m_Skater, &update_info );
+					player->SetLastObjectUpdateID( i );
+				}
+			}
+
+			update_info.m_BitStream.Flush();
+			length = update_info.m_BitStream.GetByteLength();
+
+			if( length > 0 )
+			{   
+				Net::MsgDesc msg_desc;
+
+				msg_desc.m_Data = &update_msg;
+				msg_desc.m_Length = length;
+				msg_desc.m_Id = GameNet::MSG_ID_OBJ_UPDATE_STREAM;
+				server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/* Update Clients with other players' scores					  */
+/*                                                                */
+/******************************************************************/
+
+void		Skate::SendScoreUpdates( bool final )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* player, *score_player;
+	Lst::Search< GameNet::PlayerInfo > sh, score_sh;
+
+    for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = 
+				gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		GameNet::MsgScoreUpdate score_msg;
+		char num_scores;
+		int length;
+		char* data;
+        
+		score_msg.m_Final = (char) final;
+		if( final )
+		{
+			score_msg.m_TimeLeft = 0;
+		}
+		else
+		{
+			score_msg.m_TimeLeft = Tmr::InSeconds( GetGameMode()->GetTimeLeft());
+		}
+		num_scores = 0;
+		data = score_msg.m_ScoreData;
+		for( score_player = gamenet_man->FirstPlayerInfo( score_sh ); score_player; score_player = 
+			gamenet_man->NextPlayerInfo( score_sh ))
+		{
+			bool send_all_scores;
+
+			send_all_scores = 	final ||
+								( GetGameMode()->IsTrue( "should_modulate_color" )) ||
+								( GetGameMode()->ShouldTrackTrickScore() == false );
+			if( ( score_player != player ) || ( send_all_scores ) )
+			{
+				char id;
+				int score;
+				bool in_goal_attack;
+				
+				id = (char) score_player->m_Skater->GetID();
+				in_goal_attack = GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" );
+
+				if( in_goal_attack )
+				{
+					Game::CGoalManager* pGoalManager;
+			 
+					pGoalManager = Game::GetGoalManager();
+					score = pGoalManager->NumGoalsBeatenBy( score_player->m_Skater->GetID());
+				}
+				else
+				{
+					score = gamenet_man->GetPlayerScore( score_player->m_Skater->GetID());
+				}
+
+				*data = id;
+				data++;
+				memcpy( data, &score, sizeof( int ));
+				data += sizeof( int );
+
+				num_scores++;
+			}
+		}
+
+		if( num_scores > 0 )
+		{
+			Net::MsgDesc msg_desc;
+            
+			score_msg.m_Cheats = GetCheatChecksum();
+			score_msg.m_NumScores = num_scores;
+			length = 	sizeof( int ) +		// m_TimeLeft
+						sizeof( int ) +		// m_Cheats
+						sizeof( char ) + 	// m_Final
+						sizeof( char ) +	// m_NumScores
+						(( sizeof( char ) + sizeof( int )) * num_scores );
+
+			msg_desc.m_Data = &score_msg;
+			msg_desc.m_Length = length;
+			msg_desc.m_Id = GameNet::MSG_ID_SCORE_UPDATE;
+			// If it's the final tally or if it's reporting cheats, it HAS to get there
+			if( final || ( score_msg.m_Cheats != 0 ))
+			{
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+			}
+
+			gamenet_man->GetServer()->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+
+			if( !player->IsLocalPlayer() && player->IsFullyIn())
+			{
+				GameNet::MsgCheatChecksum cheat_msg;
+
+				cheat_msg.m_ServerChecksum = GetCheatChecksum();
+				cheat_msg.m_ServerChecksum ^= 0xDEADFACE;
+				msg_desc.m_Id = GameNet::MSG_ID_CHEAT_CHECKSUM_REQUEST;
+				msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+				msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+				msg_desc.m_Data = &cheat_msg;
+				msg_desc.m_Length = sizeof( GameNet::MsgCheatChecksum );
+	
+				gamenet_man->GetServer()->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			}
+		}
+	}
+
+	gamenet_man->SetLastScoreUpdateTime( gamenet_man->GetServer()->m_Timestamp );
+}
+
+/******************************************************************/
+/* Periodic task to update Clients with other players' scores	  */
+/*                                                                */
+/******************************************************************/
+
+void		Skate::s_score_update_code ( const Tsk::Task< Skate >& task )
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Skate&	mdl = task.GetData();
+
+	
+
+	Dbg_AssertType ( &task, Tsk::Task< Skate > );
+
+	if( gamenet_man->ShouldSendScoreUpdates() == false )
+	{
+		return;
+	}
+
+	if(( gamenet_man->GetServer()->m_Timestamp - gamenet_man->GetLastScoreUpdateTime()) <
+			GameNet::vSCORE_UPDATE_FREQUENCY )
+	{
+		return;
+	}
+
+	mdl.SendScoreUpdates();
+}
+
+/*****************************************************************************
+**						Server Handlers										**
+*****************************************************************************/
+
+/******************************************************************/
+/* The client has requested a score event.  Decide whether or not */
+/* to allow it (don't allow it if time is up, for example), and   */
+/* relay the results back whenever we want to (which right now is */
+/* immediately, but it doesn't have to be)						  */
+/******************************************************************/
+
+int	Skate::handle_score_request( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgEmbedded* msg;
+	GameNet::PlayerInfo* player;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+    
+	msg = (GameNet::MsgEmbedded* ) context->m_Msg;
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+    
+	Mdl::Score *pScore = player->m_Skater->GetScoreObject();
+
+	switch( msg->m_SubMsgId )
+	{
+		case GameNet::SCORE_MSG_ID_LOG_TRICK_OBJECT:
+		{
+			GameNet::MsgScoreLogTrickObject* log_msg;
+			
+			log_msg = (GameNet::MsgScoreLogTrickObject* ) context->m_Msg;
+			if( log_msg->m_GameId == gamenet_man->GetNetworkGameId())
+			{
+				pScore->LogTrickObject( log_msg->m_OwnerId, log_msg->m_Score, log_msg->m_NumPendingTricks, log_msg->m_PendingTrickBuffer, true );
+			}
+			break;
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Skater is reporting his high combo							  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_combo_report( Net::MsgHandlerContext* context )
+{
+	GameNet::PlayerInfo* player;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Skate* skate_mod;
+	GameNet::MsgScoreLanded* msg;
+    
+	skate_mod = (Skate*) context->m_Data;
+
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	msg = (GameNet::MsgScoreLanded*) context->m_Msg;
+
+	// Ignore combo messages in freeskate
+	if( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x1c471c60,"netlobby"))
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	// Make sure this reported score is for this game (could be late -- could be a cheat replay packet)
+    if( gamenet_man->GetNetworkGameId() != msg->m_GameId )
+	{
+		Dbg_Printf( "Wrong game id! %d %d\n", gamenet_man->GetNetworkGameId(), msg->m_GameId );
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	// We should never get a score that's less than or equal to our recorded best combo for this
+	// player since they are only supposed to report combo scores if they eclipse their current
+	// mark.  This is most-likely cheating
+	/*if( msg->m_Score <= player->m_BestCombo )
+	{
+		Dbg_Printf( "**** COMBO CHEAT DETECTED: Combo reported: %d , previous best: %d\n", msg->m_Score, player->m_BestCombo );
+		gamenet_man->CheatingOccured();
+		return Net::HANDLER_MSG_DONE;
+	}*/
+
+	//Dbg_Printf( "Player: %d  Combo: %d\n", player->m_Skater->GetID(), msg->m_Score );
+	player->m_BestCombo = msg->m_Score;
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Skater landed a trick and is reporting his score		  		  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_landed_trick( Net::MsgHandlerContext* context )
+{
+	GameNet::PlayerInfo* player;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	int trick_score;
+	Skate* skate_mod;
+	GameNet::MsgScoreLanded* msg;
+    
+	skate_mod = (Skate*) context->m_Data;
+
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	msg = (GameNet::MsgScoreLanded*) context->m_Msg;
+
+	// Make sure this reported score is for this game (could be late -- could be a cheat replay packet)
+    if( gamenet_man->GetNetworkGameId() != msg->m_GameId )
+	{
+		Dbg_Printf( "Wrong game id! %d %d\n", gamenet_man->GetNetworkGameId(), msg->m_GameId );
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	trick_score = msg->m_Score;
+	Mdl::Score *pScore = player->m_Skater->GetScoreObject();
+
+	if( trick_score > pScore->GetBestCombo())
+	{
+		pScore->SetBestCombo( trick_score );
+	}
+	if ( !skate_mod->GetGameMode()->IsTrue("should_modulate_color") )
+	{
+		if( skate_mod->GetGameMode()->ShouldTrackTrickScore())
+		{
+			if (skate_mod->GetGameMode()->ShouldAccumulateScore())
+			{
+				pScore->SetTotalScore( pScore->GetTotalScore() + trick_score );
+			}
+			else if( skate_mod->GetGameMode()->ShouldTrackBestCombo())
+			{
+				if( pScore->GetTotalScore() < trick_score )
+				{
+					pScore->SetTotalScore( trick_score );
+				}
+			}
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Skater is entering a car								  		  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_enter_vehicle_server( Net::MsgHandlerContext* context )
+{
+	GameNet::PlayerInfo* player;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Skate* skate_mod;
+	GameNet::MsgEnterVehicle* msg;
+    
+	skate_mod = (Skate*) context->m_Data;
+
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	msg = (GameNet::MsgEnterVehicle*) context->m_Msg;
+	
+	player->m_VehicleControlType = msg->m_ControlType;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Relay non-important msgs to all other clients		  		  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_msg_relay( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Lst::Search< GameNet::PlayerInfo > sh;
+	GameNet::PlayerInfo* player, *orig_player;
+
+	orig_player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+
+	// Make sure this is from a current player
+	if( orig_player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player;
+			player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		Net::MsgDesc msg_desc;
+
+		// Don't relay it back to the original client
+		if( player == orig_player )
+		{
+			continue;
+		}
+
+		msg_desc.m_Data = context->m_Msg;
+		msg_desc.m_Length = context->m_MsgLength;
+		msg_desc.m_Id = context->m_MsgId;
+		if( ( context->m_MsgId == GameNet::MSG_ID_BLOOD_OFF ) ||
+			( context->m_MsgId == GameNet::MSG_ID_CHAT ) ||
+			( context->m_MsgId == GameNet::MSG_ID_ENTER_VEHICLE ))
+		{
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		}
+		else if( context->m_MsgId == GameNet::MSG_ID_SPAWN_PROJECTILE )
+		{
+			Skate* mod = (Skate*) context->m_Data;
+
+			if(	( mod->GetGameMode()->GetNameChecksum() == CRCD(0x3d6d444f,"firefight") ) ||
+				( mod->GetGameMode()->GetNameChecksum() == CRCD(0xbff33600,"netfirefight")))
+			{
+				GameNet::MsgProjectile* msg;
+	
+				msg = (GameNet::MsgProjectile*) context->m_Msg;
+				if( ( msg->m_Scale <= 10.0f ) &&
+					( msg->m_Radius <= 240 ))
+				{
+					msg->m_Id = orig_player->m_Skater->GetID();
+					msg->m_Latency = context->m_Conn->GetAveLatency();
+				}
+			}
+		}
+			
+		context->m_App->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	}
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Relay script msgs to nearby clients only		  		  	  	  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_selective_msg_relay( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Lst::Search< GameNet::PlayerInfo > sh;
+	GameNet::PlayerInfo* player, *orig_player;
+	Obj::CSkater* orig_skater, *skater;
+
+	orig_player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+
+	// Make sure this is from a current player
+	if( orig_player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	orig_skater = orig_player->m_Skater;
+	if( orig_skater == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player;
+			player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		bool send;
+
+		send = false;
+		// Don't relay it back to the original client
+		if( player != orig_player )
+		{
+			if( player->IsObserving())
+			{
+				send = true;
+			}
+			else
+			{
+				skater = player->m_Skater;
+				if( skater )
+				{
+					Mth::Vector diff;
+
+					diff = skater->GetPos() - orig_skater->GetPos();
+					if( diff.Length() < vOBJ_UPDATE_THRESHOLD )
+					{
+						send = true;
+					}
+				}
+			}
+		}
+
+		if( send )
+		{
+			Net::MsgDesc msg_desc;
+
+			msg_desc.m_Data = context->m_Msg;
+			msg_desc.m_Length = context->m_MsgLength;
+			msg_desc.m_Id = context->m_MsgId;
+			context->m_App->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+		}
+	}
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Relay script msgs to all other clients		  		  	  	  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_script_relay( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Lst::Search< GameNet::PlayerInfo > sh;
+	GameNet::PlayerInfo* player, *orig_player;
+
+	orig_player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+
+	// Make sure this is from a current player
+	if( orig_player == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	for( player = gamenet_man->FirstPlayerInfo( sh, true ); player;
+			player = gamenet_man->NextPlayerInfo( sh, true ))
+	{
+		Net::MsgDesc msg_desc;
+		
+		// Don't relay it back to the original client
+		if( player == orig_player )
+		{
+			continue;
+		}
+
+		msg_desc.m_Data = context->m_Msg;
+		msg_desc.m_Length = context->m_MsgLength;
+		msg_desc.m_Id = context->m_MsgId;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		context->m_App->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+	}
+
+	if( context->m_MsgId == GameNet::MSG_ID_SPAWN_AND_RUN_SCRIPT )
+	{
+		GameNet::MsgSpawnAndRunScript* msg;
+
+		msg = (GameNet::MsgSpawnAndRunScript *) context->m_Msg;
+
+		// Only save the event if it changes the level in a permanent way
+		if( msg->m_Permanent )
+		{
+			gamenet_man->AddSpawnedTriggerEvent( msg->m_Node, msg->m_ObjID, msg->m_ScriptName );
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Relay non-important msgs to all other clients		  		  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_bail_done( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* player;
+    
+	player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+	if( player )
+	{
+		player->ClearNonCollidable();
+	}
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Change levels	  											  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_change_level( Net::MsgHandlerContext* context )
+{
+	Skate* skate;
+	skate = (Skate*)context->m_Data;
+
+	GameNet::MsgChangeLevel* pMsg;
+	pMsg = (GameNet::MsgChangeLevel*) context->m_Msg;
+	
+	skate->ChangeLevel( pMsg->m_Level );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Receive and store client skater states for relay later on	  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_object_update( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Obj::CSkater* skater;
+	GameNet::PlayerInfo* player;
+	int i, index, last_index;
+	unsigned int timestamp;
+	char state, doing_trick, terrain, walking, driving;
+	Flags< int > skater_flags;
+	Flags< int > end_run_flags;
+	short pos[3];
+	Mth::Vector eulers;
+	sint16 rail_node;
+    short fixed;
+	int update_flags;
+	Obj::SPosEvent* latest_state, *previous_state;
+	Net::BitStream stream;
+
+    player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+
+	// Ignore late/foreign updates and update from non-ready/non-existent clients
+	if( ( player == NULL ) || 
+		( player->m_Conn->TestStatus( Net::Conn::mSTATUS_READY ) == false ) ||
+        ( context->m_PacketFlags & ( Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN )))
+	{
+		int size, value, bit_data;
+
+		size = 0;
+		bit_data = 0;
+		
+		// Even though we don't process the data, we need to calculate the msg length
+		// so that dispatcher knows how many bytes to skip
+		stream.SetInputData( context->m_Msg, 1024 );
+
+		bit_data += sizeof( int ) * 8;	// Timestamp size
+		value = stream.ReadSignedValue( sizeof( int ) * 8 );
+		update_flags = stream.ReadUnsignedValue( 9 );
+		bit_data += 9;
+
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_X )
+		{
+			//bit_data += sizeof( float ) * 8;
+			bit_data += sizeof( short ) * 8;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_Y )
+		{
+			//bit_data += sizeof( float ) * 8;
+			bit_data += sizeof( short ) * 8;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_Z )
+		{
+			//bit_data += sizeof( float ) * 8;
+			bit_data += sizeof( short ) * 8;
+		}   
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_X )
+		{
+			bit_data += sizeof( short ) * 8;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_Y )
+		{
+			bit_data += sizeof( short ) * 8;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_Z )
+		{
+			bit_data += sizeof( short ) * 8;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_STATE )
+		{   
+			bit_data += 12;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_FLAGS )
+		{
+			bit_data += 5;
+			bit_data += 3;
+		}              
+		if( update_flags & GameNet::mUPDATE_FIELD_RAIL_NODE )
+		{
+			bit_data += 16;
+		}
+
+		size += (( bit_data + 7 ) >> 3 );
+
+		context->m_MsgLength = size;
+		return Net::HANDLER_CONTINUE;
+	}
+
+	skater = player->m_Skater;
+	if( skater == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	
+	index = skater->GetStateHistory()->GetNumPosUpdates() % Obj::CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+	latest_state = &skater->GetStateHistory()->GetPosHistory()[index];
+	previous_state = NULL;
+    
+	if( skater->GetStateHistory()->GetNumPosUpdates() > 0 )
+	{
+		last_index = ( skater->GetStateHistory()->GetNumPosUpdates() + ( Obj::CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS - 1 )) % 
+					Obj::CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+		previous_state = &skater->GetStateHistory()->GetPosHistory()[last_index];
+
+        doing_trick = previous_state->DoingTrick;
+		state = previous_state->State;
+		skater_flags = previous_state->SkaterFlags;
+		end_run_flags = previous_state->EndRunFlags;
+		terrain = previous_state->Terrain;
+		walking = previous_state->Walking;
+		driving = previous_state->Driving;
+		rail_node = previous_state->RailNode;
+		for( i = 0; i < 3; i++ )
+		{
+			pos[i] = previous_state->ShortPos[i];
+		}
+		for( i = 0; i < 3; i++ )
+		{
+			eulers[i] = previous_state->Eulers[i];
+		}
+	}
+	else
+	{
+		doing_trick = 0;
+		state = 0;
+		skater_flags = 0;
+		end_run_flags = 0;
+		terrain = 0;
+		walking = 0;
+		driving = 0;
+		rail_node = -1;
+		for( i = 0; i < 3; i++ )
+		{
+			pos[i] = 0;
+		}
+		for( i = 0; i < 3; i++ )
+		{
+			eulers[i] = 0;
+		}
+	}
+	
+
+	stream.SetInputData( context->m_Msg, 1024 );
+
+	// Read in timestamp
+	timestamp = stream.ReadUnsignedValue( sizeof( int ) * 8 );
+	update_flags = stream.ReadUnsignedValue( 9 );
+	
+	// Read in fixed point position
+	if( update_flags & GameNet::mUPDATE_FIELD_POS_X )
+	{
+		pos[X] = stream.ReadSignedValue( sizeof( short ) * 8 );
+		//pos[X] = stream.ReadFloatValue();
+	}
+	if( update_flags & GameNet::mUPDATE_FIELD_POS_Y )
+	{
+		pos[Y] = stream.ReadSignedValue( sizeof( short ) * 8 );
+		//pos[Y] = stream.ReadFloatValue();
+	}
+	if( update_flags & GameNet::mUPDATE_FIELD_POS_Z )
+	{
+		pos[Z] = stream.ReadSignedValue( sizeof( short ) * 8 );
+		//pos[Z] = stream.ReadFloatValue();
+	}   
+			
+	// Read in fixed point eulers
+	if( update_flags & GameNet::mUPDATE_FIELD_ROT_X )
+	{
+		fixed = stream.ReadSignedValue( sizeof( short ) * 8 );
+		eulers[X] = ((float) fixed ) / 4096.0f;
+	}
+	if( update_flags & GameNet::mUPDATE_FIELD_ROT_Y )
+	{
+		fixed = stream.ReadSignedValue( sizeof( short ) * 8 );
+		eulers[Y] = ((float) fixed ) / 4096.0f;
+	}
+	if( update_flags & GameNet::mUPDATE_FIELD_ROT_Z )
+	{
+		fixed = stream.ReadSignedValue( sizeof( short ) * 8 );
+		eulers[Z] = ((float) fixed ) / 4096.0f;
+	}
+
+	// Read in skater's 'state'
+	if( update_flags & GameNet::mUPDATE_FIELD_STATE )
+	{   
+		char mask;
+
+		mask = stream.ReadUnsignedValue( 4 );
+
+		state = mask & GameNet::mSTATE_MASK;
+		doing_trick = (( mask & GameNet::mDOING_TRICK_MASK ) != 0 );
+		
+		terrain = stream.ReadUnsignedValue( 6 );
+		
+		walking = stream.ReadUnsignedValue( 1 );
+		driving = stream.ReadUnsignedValue( 1 );
+	}
+
+	if( update_flags & GameNet::mUPDATE_FIELD_FLAGS )
+	{
+		skater_flags = stream.ReadUnsignedValue( 5 );
+		end_run_flags = stream.ReadUnsignedValue( 3 );
+	}                           
+
+	if( update_flags & GameNet::mUPDATE_FIELD_RAIL_NODE )
+	{
+		rail_node = stream.ReadSignedValue( sizeof( sint16 ) * 8 );
+	}                           
+	
+
+	// Tell the dispatcher the size of this message because it does not know (optimization)
+	context->m_MsgLength = stream.GetByteLength();
+
+	if( ( skater->GetStateHistory()->GetNumPosUpdates() > 0 ) &&
+		( timestamp < previous_state->GetTime()))
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	// Use those euler angles to compose a matrix
+	Mth::Matrix obj_matrix( eulers[X], eulers[Y], eulers[Z] );
+    
+	latest_state->Matrix = obj_matrix;
+	latest_state->Position.Set(	((float) pos[X] ) / 2.0f,//pos[X],
+				  				((float) pos[Y] ) / 4.0f,//pos[Y],
+				  				((float) pos[Z] ) / 2.0f,//pos[Z],
+				  				0.0f );
+
+	latest_state->SetTime( timestamp );
+
+	// Update short versions in the player history
+	for( i = 0; i < 3; i++ )
+	{
+		latest_state->ShortPos[i] = pos[i];
+		latest_state->Eulers[i] = eulers[i];
+	}
+
+	latest_state->DoingTrick = doing_trick;
+	latest_state->SkaterFlags = skater_flags;
+	latest_state->EndRunFlags = end_run_flags;
+	latest_state->State = state;
+	latest_state->Terrain = (ETerrainType) terrain;
+	latest_state->RailNode = rail_node;
+	latest_state->Walking = walking;
+	latest_state->Driving = driving;
+
+	skater->GetStateHistory()->IncrementNumPosUpdates();
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/*****************************************************************************
+**						Client Handlers										**
+*****************************************************************************/
+
+/******************************************************************/
+/* Execute a script	  									  		  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_run_script( Net::MsgHandlerContext* context )
+{
+	Skate* mod;
+	GameNet::MsgRunScript* msg;
+    Script::CScriptStructure *pParams;
+	Obj::CMovingObject* obj;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+
+	mod = (Skate *) context->m_Data;
+	msg = (GameNet::MsgRunScript *) context->m_Msg;
+
+	obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjID );
+
+    pParams = new Script::CScriptStructure;
+	Script::ReadFromBuffer(pParams,(uint8 *) msg->m_Data );
+
+	Script::RunScript( msg->m_ScriptName, pParams, obj );
+
+	delete pParams;
+
+	Mem::Manager::sHandle().PopContext();
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Spawn and run a script 										  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_spawn_run_script( Net::MsgHandlerContext* context )
+{
+	Skate* mod;
+	Script::CScript* pScript;
+	Obj::CMovingObject* obj;
+	GameNet::MsgSpawnAndRunScript* msg;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	mod = (Skate *) context->m_Data;
+	msg = (GameNet::MsgSpawnAndRunScript *) context->m_Msg;
+
+    if( gamenet_man->ReadyToPlay())
+	{
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjID );
+	
+		pScript = Script::SpawnScript( msg->m_ScriptName, NULL, 0, NULL, msg->m_Node ); // K: The 0,NULL bit means no callback script specified
+		#ifdef __NOPT_ASSERT__
+		pScript->SetCommentString("Spawned from Skate::handle_spawn_run_script");
+		#endif
+		pScript->mpObject = obj; 
+
+		pScript->Update(); 
+	}
+	else
+	{
+		gamenet_man->AddSpawnedTriggerEvent( msg->m_Node, msg->m_ObjID, msg->m_ScriptName );
+	}
+	
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Unpacks wobble details	  								  	  */
+/* 						  										  */
+/******************************************************************/
+
+static float s_get_wobble_details_from_mask( char mask, int field )
+{
+	float value;
+
+	value = 0;
+	switch( field )
+	{
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_A:
+		{
+			int field_mask;
+
+			field_mask = mask & GameNet::MsgSetWobbleDetails::mWOBBLE_AMPA_MASK;
+            if( field_mask == 0 )
+			{
+				value = 0.05f;
+			}
+			else if( field_mask == 1 )
+			{
+				value = 0.1f;
+			}
+			else if( field_mask == 2 )
+			{
+				value = 10.1f;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+			break;
+		}
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_B:
+		{
+			int field_mask;
+
+			field_mask = mask & GameNet::MsgSetWobbleDetails::mWOBBLE_AMPB_MASK;
+			field_mask >>= 2;
+            if( field_mask == 0 )
+			{
+				value = 0.04f;
+			}
+			else if( field_mask == 1 )
+			{
+				value = 10.1f;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+			break;
+		}
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_K1:
+		{
+			int field_mask;
+
+			field_mask = mask & GameNet::MsgSetWobbleDetails::mWOBBLE_K1_MASK;
+			field_mask >>= 3;
+            if( field_mask == 0 )
+			{
+				value = 0.0022f;
+			}
+			else if( field_mask == 1 )
+			{
+				value = 20.0f;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+			break;
+		}
+		case GameNet::MsgSetWobbleDetails::vWOBBLE_K2:
+		{
+			int field_mask;
+
+			field_mask = mask & GameNet::MsgSetWobbleDetails::mWOBBLE_K2_MASK;
+			field_mask >>= 4;
+            if( field_mask == 0 )
+			{
+				value = 0.0017f;
+			}
+			else if( field_mask == 1 )
+			{
+				value = 10.0f;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+			break;
+		}
+		case GameNet::MsgSetWobbleDetails::vSPAZFACTOR:
+		{
+			int field_mask;
+
+			field_mask = mask & GameNet::MsgSetWobbleDetails::mSPAZFACTOR_MASK;
+			field_mask >>= 5;
+            if( field_mask == 0 )
+			{
+				value = 1.5f;
+			}
+			else if( field_mask == 1 )
+			{
+				value = 1.0f;
+			}
+			else
+			{
+				Dbg_Assert( 0 );
+			}
+			break;
+		}
+		default:
+			Dbg_Assert( 0 );
+			break;
+	}
+
+	return value;
+}
+
+/******************************************************************/
+/* A client has launched a projectile							  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_projectile( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgProjectile* msg;
+	Script::CStruct* pParams;
+	Obj::CSkater* skater;
+	Skate* mod;
+	Mth::Vector vel, dir, pos;
+	Tmr::Time lag;
+	float time;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+    
+	mod = (Skate *) context->m_Data;
+
+	msg = (GameNet::MsgProjectile*) context->m_Msg;
+	skater = mod->GetSkaterById( msg->m_Id );
+	if( skater == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	
+	vel = msg->m_Vel;
+	pos = msg->m_Pos;
+	dir = vel;
+	dir.Normalize();
+	lag = msg->m_Latency;
+
+	// If not on the server, also add the average latency from server to us
+	if( gamenet_man->OnServer() == false )
+	{
+		Lst::Search< Net::Conn > sh;
+		Net::Conn* server_conn;
+
+		server_conn = context->m_App->FirstConnection( &sh );
+		lag += server_conn->GetAveLatency();
+	}
+
+    time = (float) lag / 1000.0f;
+    pos += dir * time;	// Time is in units of seconds. Velocity is in terms of feet/sec
+
+	pParams = new Script::CStruct;
+	pParams->AddChecksum( CRCD(0x81c39e06,"owner_id"), msg->m_Id );
+	pParams->AddInteger( CRCD(0x7323e97c,"x"), pos[X] );
+	pParams->AddInteger( CRCD(0x424d9ea,"y"), pos[Y] );
+	pParams->AddInteger( CRCD(0x9d2d8850,"z"), pos[Z] );
+	pParams->AddInteger( CRCD(0xbded7a76,"scaled_x"), msg->m_Vel[X] );
+	pParams->AddInteger( CRCD(0xcaea4ae0,"scaled_y"), msg->m_Vel[Y] );
+	pParams->AddInteger( CRCD(0x53e31b5a,"scaled_z"), msg->m_Vel[Z] );
+	pParams->AddInteger( CRCD(0xc48391a5,"radius"), msg->m_Radius );
+	pParams->AddFloat( CRCD(0x13b9da7b,"scale"), msg->m_Scale );
+	
+	Script::RunScript( CRCD(0xaa2b5552,"ClientLaunchFireball"), pParams, skater );
+	delete pParams;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* A client has entered a vehicle								  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_enter_vehicle_client( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgEnterVehicle* msg;
+	Obj::CSkater* skater;
+	Skate* mod;
+    
+	mod = (Skate *) context->m_Data;
+
+	msg = (GameNet::MsgEnterVehicle*) context->m_Msg;
+	skater = mod->GetSkaterById( msg->m_Id );
+	if( skater == NULL )
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+	
+	Dbg_Assert(GetSkaterStateHistoryComponentFromObject(skater));
+	GetSkaterStateHistoryComponentFromObject(skater)->SetCurrentVehicleControlType(msg->m_ControlType);
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The server has sent a list of cheats that are enabled		  */
+/* 						  										  */
+/******************************************************************/
+
+int	Skate::handle_cheat_list( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgEnabledCheats* msg;
+	int i;
+
+	Dbg_Printf( "**** HANDLING CHEAT LIST\n" );
+	CFuncs::ScriptClearCheats( NULL, NULL );
+
+	msg = (GameNet::MsgEnabledCheats*) context->m_Msg;
+	for( i = 0; i < msg->m_NumCheats; i++ )
+	{
+		Script::CStruct* pParams;
+
+		pParams = new Script::CStruct;
+		pParams->AddChecksum( CRCD(0xf649d637,"on"), 0 );
+		pParams->AddChecksum( CRCD(0xae94c183,"cheat_flag"), msg->m_Cheats[i] );
+		Script::RunScript( CRCD(0x3cf9e86d,"client_toggle_cheat"), pParams );
+
+		delete pParams;
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* The server has toggled a cheat. Mimic behavior on this end	  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_cheats( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgToggleCheat* msg;
+	Script::CStruct* pParams;
+
+	pParams = new Script::CStruct;
+	msg = (GameNet::MsgToggleCheat*) context->m_Msg;
+	
+	if( msg->m_On )
+	{
+		pParams->AddChecksum( CRCD(0xf649d637,"on"), 0 );
+	}
+
+	pParams->AddChecksum( CRCD(0xae94c183,"cheat_flag"), msg->m_Cheat );
+
+	Script::RunScript( CRCD(0x3cf9e86d,"client_toggle_cheat"), pParams );
+
+	delete pParams;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Get the number of times the 16-bit value was flipped since the */
+/* last time			  										  */
+/******************************************************************/
+
+int	s_get_num_flips( uint32 new_time, uint32 old_time )
+{
+	uint32 hi_new, hi_old;
+
+	hi_new = new_time >> 16;
+	hi_old = old_time >> 16;
+
+	return ( hi_new - hi_old );
+}
+
+/******************************************************************/
+/* Animate non-local clients	  								  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_anims( Net::MsgHandlerContext* context )
+{
+	Obj::SAnimEvent* latest_event;
+	Obj::CMovingObject* obj;
+	Obj::CSkater* skater;
+	Skate* mod;
+    
+	mod = (Skate *) context->m_Data;
+    if( context->m_MsgId == GameNet::MSG_ID_PRIM_ANIM_START )
+	{
+		GameNet::MsgPlayPrimaryAnim anim_msg;
+		char* stream;
+        
+        stream = context->m_Msg;
+        
+		//memcpy( &anim_msg.m_Time, stream, sizeof( unsigned short ));
+		//Dbg_Printf( "(%d) Got Primary %d\n", context->m_App->m_FrameCounter, anim_msg.m_Time );
+		//stream += sizeof( int );
+		memcpy( &anim_msg.m_LoopingType, stream, sizeof( char ));
+		stream++;
+		memcpy( &anim_msg.m_ObjId, stream, sizeof( char ));
+		stream++;
+		memcpy( &anim_msg.m_Index, stream, sizeof( uint32 ));
+		stream += sizeof( uint32 );
+		memcpy( &anim_msg.m_StartTime, stream, sizeof( unsigned short ));
+		stream += sizeof( unsigned short );
+		memcpy( &anim_msg.m_EndTime, stream, sizeof( unsigned short ));
+		stream += sizeof( unsigned short );
+		memcpy( &anim_msg.m_BlendPeriod, stream, sizeof( unsigned short ));
+		stream += sizeof( unsigned short );
+		memcpy( &anim_msg.m_Speed, stream, sizeof( unsigned short ));
+		stream += sizeof( unsigned short );
+
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( anim_msg.m_ObjId );
+		if( obj )
+		{
+			GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+			float blend_period;
+
+			skater = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater != NULL,( "Got anim for non-CSkater" ));
+			
+			blend_period = (float) anim_msg.m_BlendPeriod / 4096.0f;
+
+			// Only blend skaters that we are observing
+			if ( gamenet_man->InNetGame() )
+			{
+				GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+				GameNet::PlayerInfo* observed_player, *anim_player;
+
+				anim_player = gamenet_man->GetPlayerByObjectID( obj->GetID() );
+				observed_player = gamenet_man->GetCurrentlyObservedPlayer();
+				if(( observed_player == NULL ) || ( anim_player != observed_player ))
+				{
+					blend_period = 0.0f;
+				}
+			}
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+				latest_event->m_MsgId = GameNet::MSG_ID_PRIM_ANIM_START;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				//Dbg_Printf( "(%d) Setting anm time: %d\n", context->m_App->m_FrameCounter, latest_event->GetTime());
+
+				latest_event->m_ObjId = anim_msg.m_ObjId;
+				latest_event->m_Asset = anim_msg.m_Index;
+				latest_event->m_StartTime = (float) anim_msg.m_StartTime / 4096.0f;
+				latest_event->m_EndTime = (float) anim_msg.m_EndTime / 4096.0f;
+				latest_event->m_BlendPeriod = blend_period;
+				latest_event->m_Speed = (float) anim_msg.m_Speed / 4096.0f;
+				latest_event->m_LoopingType = anim_msg.m_LoopingType;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_SET_WOBBLE_TARGET )
+	{
+		GameNet::MsgSetWobbleTarget* msg;
+
+		msg = (GameNet::MsgSetWobbleTarget *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{   
+			skater = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater != NULL,( "Got anim for non-CSkater" ));
+
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+				latest_event->m_MsgId = GameNet::MSG_ID_SET_WOBBLE_TARGET;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				//Dbg_Printf( "(%d) Got Wobble %d\n", context->m_App->m_FrameCounter, msg->m_Time );
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_Alpha = (float) msg->m_Alpha / 255.0f;
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_ROTATE_SKATEBOARD )
+	{
+		GameNet::MsgRotateSkateboard* msg;
+
+		msg = (GameNet::MsgRotateSkateboard *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater != NULL,( "Got anim for non-CSkater" ));
+
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+				latest_event->m_MsgId = GameNet::MSG_ID_ROTATE_SKATEBOARD;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+                //Dbg_Printf( "(%d) Got Rotate %d\n", context->m_App->m_FrameCounter, msg->m_Time );
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_Rotate = msg->m_Rotate;
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_SET_WOBBLE_DETAILS )
+	{
+		GameNet::MsgSetWobbleDetails* msg;
+
+		Obj::CMovingObject* obj;
+
+		msg = (GameNet::MsgSetWobbleDetails *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater != NULL,( "Got anim for non-CSkater" ));
+
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+				latest_event->m_MsgId = GameNet::MSG_ID_SET_WOBBLE_DETAILS;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				//Dbg_Printf( "(%d) Got details %d\n", context->m_App->m_FrameCounter, msg->m_Time );
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_WobbleAmpA = s_get_wobble_details_from_mask( msg->m_WobbleDetails, GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_A );
+				latest_event->m_WobbleAmpB = s_get_wobble_details_from_mask( msg->m_WobbleDetails, GameNet::MsgSetWobbleDetails::vWOBBLE_AMP_B );
+				latest_event->m_WobbleK1 = s_get_wobble_details_from_mask( msg->m_WobbleDetails, GameNet::MsgSetWobbleDetails::vWOBBLE_K1 );
+				latest_event->m_WobbleK2 = s_get_wobble_details_from_mask( msg->m_WobbleDetails, GameNet::MsgSetWobbleDetails::vWOBBLE_K2 );
+				latest_event->m_SpazFactor = s_get_wobble_details_from_mask( msg->m_WobbleDetails, GameNet::MsgSetWobbleDetails::vSPAZFACTOR );
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_SET_LOOPING_TYPE )
+	{
+		GameNet::MsgSetLoopingType* msg;
+
+		Obj::CMovingObject* obj;
+
+		msg = (GameNet::MsgSetLoopingType *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater != NULL,( "Got anim for non-CSkater" ));
+
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_SET_LOOPING_TYPE;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				//Dbg_Printf( "(%d) Got looping %d\n", context->m_App->m_FrameCounter, msg->m_Time );
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_LoopingType = msg->m_LoopingType;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_SET_HIDE_ATOMIC )
+	{
+		GameNet::MsgHideAtomic* msg;
+
+		Obj::CMovingObject* obj;
+
+		msg = (GameNet::MsgHideAtomic *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater != NULL,( "Got anim for non-CSkater" ));
+
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_SET_HIDE_ATOMIC;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_Asset = msg->m_AtomicName;
+				latest_event->m_Hide = msg->m_Hide;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_SET_ANIM_SPEED )
+	{
+		GameNet::MsgSetAnimSpeed* msg;
+
+		Obj::CMovingObject* obj;
+
+		msg = (GameNet::MsgSetAnimSpeed *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater != NULL,( "Got anim for non-CSkater" ));
+
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_SET_ANIM_SPEED;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				//Dbg_Printf( "(%d) Got speed %d\n", context->m_App->m_FrameCounter, msg->m_Time );
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_Speed = msg->m_AnimSpeed;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_FLIP_ANIM )
+	{
+		GameNet::MsgFlipAnim* msg;
+
+		Obj::CMovingObject* obj;
+		
+		msg = (GameNet::MsgFlipAnim *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = mod->GetSkaterById( msg->m_ObjId );
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_FLIP_ANIM;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				//Dbg_Printf( "(%d) Got flip %d\n", context->m_App->m_FrameCounter, msg->m_Time );
+				latest_event->m_Flipped = msg->m_Flipped;
+				latest_event->m_ObjId = msg->m_ObjId;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+
+				// If we got a flip message for a skater, we've now handled it. Otherwise,
+				// let it fall through as it is for some other object in the game
+				return Net::HANDLER_MSG_DONE;
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_ROTATE_DISPLAY )
+	{
+		GameNet::MsgRotateDisplay* msg;
+
+		Obj::CMovingObject* obj;
+		
+		msg = (GameNet::MsgRotateDisplay *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = mod->GetSkaterById( msg->m_ObjId );
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_ROTATE_DISPLAY;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				//Dbg_Printf( "(%d) Got rotate %d\n", context->m_App->m_FrameCounter, msg->m_Time );
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_Duration = msg->m_Duration;
+				latest_event->m_SinePower = msg->m_SinePower;
+				latest_event->m_StartAngle = msg->m_StartAngle;
+				latest_event->m_DeltaAngle = msg->m_DeltaAngle;
+				latest_event->m_HoldOnLastAngle = msg->m_HoldOnLastAngle;
+				latest_event->m_Flags = msg->m_Flags;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_CLEAR_ROTATE_DISPLAY )
+	{
+		GameNet::MsgObjMessage* msg;
+
+		Obj::CMovingObject* obj;
+		
+		msg = (GameNet::MsgObjMessage *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = mod->GetSkaterById( msg->m_ObjId );
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_CLEAR_ROTATE_DISPLAY;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				latest_event->m_ObjId = msg->m_ObjId;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_CREATE_SPECIAL_ITEM )
+	{    
+		Obj::CMovingObject* obj;
+		GameNet::MsgSpecialItem* msg;
+
+		msg = (GameNet::MsgSpecialItem *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = mod->GetSkaterById( msg->m_ObjId );
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_CREATE_SPECIAL_ITEM;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_Bone = msg->m_Bone;
+				latest_event->m_Index = msg->m_Index;
+				latest_event->m_Asset = msg->m_Params;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_DESTROY_SPECIAL_ITEM )
+	{
+		Obj::CMovingObject* obj;
+		GameNet::MsgSpecialItem* msg;
+
+		msg = (GameNet::MsgSpecialItem *) context->m_Msg;
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+		if( obj )
+		{
+			skater = mod->GetSkaterById( msg->m_ObjId );
+			if( skater )
+			{
+				latest_event = skater->GetStateHistory()->GetLatestAnimEvent();
+                latest_event->m_MsgId = GameNet::MSG_ID_DESTROY_SPECIAL_ITEM;
+				latest_event->SetTime( skater->GetStateHistory()->GetLastPosEvent()->GetTime());
+				latest_event->m_ObjId = msg->m_ObjId;
+				latest_event->m_Index = msg->m_Index;
+
+				skater->GetStateHistory()->IncrementNumAnimUpdates();
+			}
+		}
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Skate::handle_flip_anim( Net::MsgHandlerContext* context ) 
+{
+	Skate* mod;
+
+	mod = (Skate *) context->m_Data;
+		
+	GameNet::MsgFlipAnim* msg;
+
+	Obj::CMovingObject* obj;
+	msg = (GameNet::MsgFlipAnim *) context->m_Msg;
+	obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+	if( obj )
+	{
+		Obj::CMovingObject* moving_obj;
+	 
+		moving_obj = static_cast< Obj::CMovingObject *> ( obj );
+		Dbg_MsgAssert( moving_obj != NULL,( "Got object update for non-CMovingObj\n" ));
+		
+		if( moving_obj )
+		{
+			Obj::CAnimationComponent* pAnimationComponent = GetAnimationComponentFromObject( moving_obj );
+			if ( pAnimationComponent )
+			{
+				pAnimationComponent->FlipAnimation( msg->m_ObjId, msg->m_Flipped, context->m_App->m_Timestamp, false );
+			}
+		}
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Skate::handle_player_quit( Net::MsgHandlerContext* context )
+{
+	GameNet::PlayerInfo* player;
+	Skate* mod;
+	GameNet::MsgPlayerQuit* msg;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	Dbg_Printf( "Got player quit message" );
+
+	mod = (Skate *) context->m_Data;
+	msg = (GameNet::MsgPlayerQuit *) context->m_Msg; 
+	player = gamenet_man->GetPlayerByObjectID( msg->m_ObjId );
+	if( player )
+	{   
+		Obj::CSkater* quitting_skater;
+
+		quitting_skater = player->m_Skater;
+		
+		Dbg_Printf( "Got player quit message 2" );
+		gamenet_man->DropPlayer( player, (GameNet::DropReason) msg->m_Reason );
+		mod->remove_skater( quitting_skater );
+	}
+	else
+	{
+		GameNet::NewPlayerInfo* pending_player;
+
+		pending_player = gamenet_man->GetNewPlayerInfoByObjectID( msg->m_ObjId );
+		if( pending_player )
+		{
+			gamenet_man->DestroyNewPlayer( pending_player );
+		}
+	}
+
+	// The server may have been waiting for us to load this player.  If so, tell the server we're ready now.
+	gamenet_man->RespondToReadyQuery();
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if 0
+static bool HostQuitDialogHandler( Front::EDialogBoxResult result )
+{
+	
+
+	if( result == Front::DB_OK )
+	{   
+		Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+            
+		Script::RunScript( "chosen_leave_server" );
+		front->PauseGame( false );
+		return false;
+	}
+
+	return true;
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Skate::handle_disconn_accepted( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+
+	Dbg_Message( "Logging off of server\n" );
+
+	Script::RunScript( "CreateServerQuitDialog" );
+	gamenet_man->CleanupPlayers();
+	gamenet_man->ClientShutdown();
+ 
+	return Net::HANDLER_HALT;
+}
+
+/******************************************************************/
+/* You've been kicked by the server								  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_kicked( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Skate * skate_mod = Skate::Instance();
+
+	
+
+	Dbg_Message( "Logging off of server\n" );
+
+	Script::DeleteSpawnedScripts();
+
+	Script::CScriptStructure* pStructure = new Script::CScriptStructure;
+	
+	pStructure->AddComponent( Script::GenerateCRC( "width" ), ESYMBOLTYPE_INTEGER, 300 );
+	pStructure->AddComponent( Script::GenerateCRC( "title" ), ESYMBOLTYPE_NAME, (int) Script::GenerateCRC("net_notice_msg"));
+	if( context->m_MsgId == GameNet::MSG_ID_KICKED )
+	{
+		Script::RunScript( "CreateServerRemovedYouDialog" );
+	}
+	else if( context->m_MsgId == GameNet::MSG_ID_LEFT_OUT )
+	{
+		Script::RunScript( "CreateServerMovedOnDialog" );
+	}
+	
+	if( skate_mod->m_requested_level == Script::GenerateCRC( "load_skateshop" ))
+	{
+		// If we got partially in, cancel the join and remove partially-loaded players
+		// Shut the client down so that it doesn't bombard the server ip with messages
+		gamenet_man->CancelJoinServer();
+		gamenet_man->SetJoinState( GameNet::vJOIN_STATE_FINISHED );
+	}
+	else
+	{
+		gamenet_man->CleanupPlayers();
+		gamenet_man->ClientShutdown();
+	}
+
+	delete pStructure;	
+ 
+	return Net::HANDLER_HALT;
+}
+
+/******************************************************************/
+/* Observer-specific trick object handling						  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_obs_log_trick_objects( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::MsgObsScoreLogTrickObject* msg;
+	Skate* skate_mod;
+
+	skate_mod = (Skate*) context->m_Data;
+	msg = (GameNet::MsgObsScoreLogTrickObject*) context->m_Msg;
+
+	if( msg->m_GameId != gamenet_man->GetNetworkGameId())
+	{
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	int seq;
+
+	if( skate_mod->GetGameMode()->IsTeamGame())
+	{
+		GameNet::PlayerInfo* player;
+
+		player = gamenet_man->GetPlayerByObjectID( msg->m_OwnerId );
+		seq = player->m_Team;
+	}
+	else
+	{
+		seq = msg->m_OwnerId;
+	}
+
+	// modulate the color here
+	for( uint32 i = 0; i < msg->m_NumPendingTricks; i++ )
+	{
+		skate_mod->GetTrickObjectManager()->ModulateTrickObjectColor( msg->m_PendingTrickBuffer[i], seq );
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* handle custom face data						  				  */
+/* 						  										  */
+/******************************************************************/
+
+int Skate::handle_face_data( Net::MsgHandlerContext* context )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::PlayerInfo* sender;
+	Obj::CSkater* skater;
+	uint8* face_data;
+	int face_data_length;
+	uint8 skater_id;
+
+	Dbg_Printf( "**** HANDLING FACE DATA****" );
+
+    // Create a new temporary CFaceTexture.  
+	Gfx::CFaceTexture* pFaceTexture = new Gfx::CFaceTexture;
+
+	face_data = (uint8*) context->m_Msg;
+	face_data_length = context->m_MsgLength;
+	skater_id = 0;
+	if( gamenet_man->OnServer())
+	{
+		sender = gamenet_man->GetPlayerByConnection( context->m_Conn );
+		if( sender == NULL )
+		{
+			return Net::HANDLER_MSG_DONE;
+		}
+		
+		skater = sender->m_Skater;
+		if( skater == NULL ) 
+		{
+			return Net::HANDLER_MSG_DONE;
+		}
+	}
+	else
+	{
+		skater_id = *face_data++;
+		face_data_length -= sizeof( char );
+
+		sender = gamenet_man->GetPlayerByObjectID( skater_id );
+		if( sender == NULL )
+		{
+			return Net::HANDLER_MSG_DONE;
+		}
+		
+		skater = sender->m_Skater;
+		if( skater == NULL ) 
+		{
+			return Net::HANDLER_MSG_DONE;
+		}
+	}
+	
+	// Read in data from network message
+	pFaceTexture->ReadFromBuffer((uint8*) face_data, face_data_length );
+	pFaceTexture->SetValid( true );
+
+	// Get the skater's model
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( skater ); 
+	Dbg_Assert( pModelComponent );
+	Nx::CModel* pModel = pModelComponent->GetModel();
+	Dbg_Assert( pModel );
+	
+	// Apply face texture to new model (eventually these parameters 
+	// might need to come through the same network message, 
+	// depending on the sex of the skater) 
+	const char* pSrcTexture = "CS_NSN_facemap.png";
+	pModel->ApplyFaceTexture( pFaceTexture, pSrcTexture, Nx::CModel::vREPLACE_GLOBALLY );
+	
+	if( gamenet_man->OnServer())
+	{
+		GameNet::PlayerInfo* face_player, *player;
+		Lst::Search< GameNet::PlayerInfo > sh;
+		Net::Server* server;
+
+		server = (Net::Server*) context->m_App;
+
+		// need to copy face texture data somewhere
+		// so that future joining players can get 
+		// everyone else's face textures (unimplemented)
+		face_player = gamenet_man->GetPlayerByConnection( context->m_Conn );
+		face_player->SetFaceData((uint8*) context->m_Msg, context->m_MsgLength );
+
+		for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; player = gamenet_man->NextPlayerInfo( sh, true ))
+		{
+			if( player->IsLocalPlayer() || ( player == face_player ))
+			{
+				continue;
+			}
+
+			server->StreamMessageToConn( player->m_Conn, GameNet::MSG_ID_FACE_DATA, Gfx::CFaceTexture::vTOTAL_CFACETEXTURE_SIZE + 1, face_player->GetFaceData(), "Face Data",
+									   GameNet::vSEQ_GROUP_FACE_MSGS, false, true );
+		}
+	}
+	
+	delete pFaceTexture;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Observer-specific trick object handling						  */
+/* for when the level starts up (observer needs to "catch up"     */
+/* to everyone else's trick object state)   					  */
+/******************************************************************/
+
+int Skate::handle_obs_init_graffiti_state( Net::MsgHandlerContext* context )
+{
+	
+	
+	GameNet::MsgInitGraffitiState* msg;
+	Skate* skate_mod;
+	int i;
+
+	skate_mod = (Skate*) context->m_Data;
+	msg = (GameNet::MsgInitGraffitiState*) context->m_Msg;
+
+	if ( !skate_mod->GetGameMode()->IsTrue( "should_modulate_color" ) )
+	{
+		printf( "No need to continue cause we're not in graffiti mode" );
+		
+		// no need to process the msg here unless it's graffiti mode
+		return Net::HANDLER_MSG_DONE;
+	}
+
+	uint8* pStartOfStream = &msg->m_TrickObjectStream[0];
+
+	Obj::CTrickObjectManager* pTrickObjectManager = skate_mod->GetTrickObjectManager();
+	Dbg_Assert( pTrickObjectManager );
+
+	// push heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+
+	Script::CScriptStructure* pStructure = new Script::CScriptStructure;
+
+	// modulate the color here
+	for( i = 0; i < vMAX_SKATERS; i++ )
+	{
+		if ( msg->m_NumTrickObjects[i] )
+		{
+			Script::CArray* pArray = new Script::CArray;
+			
+			pArray->SetArrayType( msg->m_NumTrickObjects[i], ESYMBOLTYPE_NAME );
+			
+			for ( int j = 0; j < msg->m_NumTrickObjects[i]; j++ )
+			{
+				uint32 trick_object_checksum = pTrickObjectManager->GetUncompressedTrickObjectChecksum( *pStartOfStream );
+				pArray->SetChecksum( j, trick_object_checksum );
+//				pTrickObjectManager->ModulateTrickObjectColor( trick_object_checksum, i );
+				pStartOfStream++;
+			}
+
+			char arrayName[32];
+			sprintf( arrayName, "Skater%d", i );			
+			pStructure->AddComponent( Script::GenerateCRC( arrayName ), pArray );
+		}
+	}
+
+	pTrickObjectManager->SetObserverGraffitiState( pStructure );
+	pTrickObjectManager->ApplyObserverGraffitiState();
+
+	delete pStructure;
+
+	// apply color modulation a few frames later...
+	//
+	//
+	// This was never defined, so I commented it out. I just apply it immediately and this
+	// seems to work for now
+	//Script::SpawnScript( "deferred_observer_trick_modulation", pStructure, 0, NULL );
+
+	// pop heap
+	Mem::Manager::sHandle().PopContext();
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Set terrain type										  		  */
+/*                           									  */
+/******************************************************************/
+
+int	Skate::handle_score_update( Net::MsgHandlerContext* context )
+{
+    GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+	Mdl::FrontEnd* front_end = Mdl::FrontEnd::Instance();
+	GameNet::MsgScoreUpdate* msg;
+	Obj::CMovingObject *obj;
+	Skate* mod;
+	int i, score;
+	char* data;
+	char id;
+    
+	msg = (GameNet::MsgScoreUpdate*) context->m_Msg;
+	data = msg->m_ScoreData;
+    mod = (Skate*) context->m_Data;
+
+	if( !gamenet_man->OnServer())
+	{
+		mod->GetGameMode()->SetTimeLeft( msg->m_TimeLeft );
+	}
+
+	for( i = 0; i < msg->m_NumScores; i++ )
+	{           
+		id = *data;
+		data++;
+		memcpy( &score, data, sizeof( int ));
+		data += sizeof( int );
+
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( id );
+		if( obj )
+		{
+			Obj::CSkater* skater_obj;
+			
+			skater_obj = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater_obj != NULL,( "Got score update for non-CSkater\n" ));
+
+			if(	( mod->GetGameMode()->IsTrue( "should_modulate_color" )) ||
+				( mod->GetGameMode()->ShouldTrackTrickScore() == false ))
+			{
+				Mdl::Score *pScore = skater_obj->GetScoreObject();
+
+				// Check if a skater has just been eliminated in a firefight game
+				if(	( mod->GetGameMode()->GetNameChecksum() == CRCD(0xbff33600,"netfirefight" )) &&
+					( skater_obj->IsLocalClient() == false ))
+				{
+					// If this is the positive edge then hide the skater
+					if(( pScore->GetTotalScore() > 0 ) && ( score == 0 ))
+					{
+						Script::CStruct* params;
+						params = new Script::CStruct;
+						params->AddString( CRCD(0xa1dc81f9,"name"), skater_obj->GetDisplayName());
+						Script::RunScript( CRCD(0x9b043179,"announce_elimination"), params );
+						delete params;
+						mod->HideSkater( skater_obj, true );
+					}
+				}
+
+				pScore->SetTotalScore( score );
+			}
+			else if( skater_obj && !skater_obj->IsLocalClient())
+			{
+				Mdl::Score *pScore = skater_obj->GetScoreObject();
+				pScore->SetTotalScore( score );
+			}
+		}
+	}
+
+	if( msg->m_Final )
+	{          
+		gamenet_man->MarkReceivedFinalScores( true );
+	}
+
+	if( msg->m_Cheats != mod->GetCheatChecksum())
+	{
+		if( gamenet_man->HasCheatingOccurred() == false )
+		{
+			Script::RunScript( CRCD(0x1cedb62c,"notify_cheating"));
+			gamenet_man->CheatingOccured();
+		}
+	}
+    
+	if( mod->GetGameMode()->IsTeamGame())
+	{
+		int num_teams, score, highest_score, leading_team;
+		GameNet::PlayerInfo* my_player;
+
+		num_teams = mod->GetGameMode()->NumTeams();
+		leading_team = GameNet::vNO_TEAM;
+		highest_score = 0;
+		for( i = 0; i < num_teams; i++ )
+		{
+			score = gamenet_man->GetTeamScore( i );
+			if( score > highest_score )
+			{
+				highest_score = score;
+				leading_team = i;
+			}
+			else if( score == highest_score )
+			{
+				// A tie doesn't result in the panel message
+				leading_team = GameNet::vNO_TEAM;
+			}
+		}
+
+		if( leading_team != GameNet::vNO_TEAM )
+		{
+			my_player = gamenet_man->GetLocalPlayer();
+			if( msg->m_Final )
+			{
+				if(( my_player->m_Skater ) && ( my_player->m_Team == leading_team ))
+				{
+					my_player->m_Skater->SelfEvent(CRCD(0x96935417,"WonGame"));
+				}
+				else
+				{
+					if( my_player->m_Skater )
+					{
+						my_player->m_Skater->SelfEvent(CRCD(0xe3ec7e9e,"LostGame"));
+					}
+				}
+			}
+			else if( ( gamenet_man->GetCurrentLeadingTeam() != leading_team ) &&
+					 ( mod->GetGameMode()->IsTrue( "show_leader_messages" ) ) &&
+                     ( !gamenet_man->GameIsOver()))
+	
+			{
+				// only print the message if you're competing against someone
+				if(( my_player->m_Skater ) && ( my_player->m_Team == leading_team ))
+				{   
+					Script::RunScript( "NewScoreLeaderYourTeam", NULL, my_player->m_Skater );
+				}
+				else
+				{   
+					char team_name[128];
+
+					sprintf( team_name, "team_%d_name", leading_team + 1 );
+					Script::CScriptStructure* pTempStructure = new Script::CScriptStructure;
+					pTempStructure->Clear();
+					pTempStructure->AddComponent( Script::GenerateCRC("String0"), ESYMBOLTYPE_STRING, Script::GetString( team_name ));
+					Script::RunScript( "NewScoreLeaderOtherTeam", pTempStructure, my_player->m_Skater );
+					delete pTempStructure;
+				}
+				gamenet_man->SetCurrentLeadingTeam( leading_team );
+			}
+		}
+	}
+	else
+	{
+		GameNet::PlayerInfo* player, *leader, *my_player;
+		Lst::Search< GameNet::PlayerInfo > sh;
+		int score, highest_score, num_players;
+
+		// Do the logic that figures out if there's a new winner and provides feedback
+		leader = NULL;
+		highest_score = 0;
+		num_players = 0;
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player;
+				player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			//if( player->IsLocalPlayer() && mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netgoalattack" ))
+			{
+				score = gamenet_man->GetPlayerScore( player->m_Skater->GetID());
+			}
+			//else
+			//{
+				//Mdl::Score *pScore = player->m_Skater->GetScoreObject();
+				
+				//score = pScore->GetTotalScore();
+			//}
+			if( score > highest_score )
+			{
+				highest_score = score;
+				leader = player;
+			}
+			else if( score == highest_score )
+			{
+				// A tie doesn't result in the panel message
+				leader = NULL;
+			}
+			num_players++;
+		}
+        
+		if( leader )
+		{   
+			my_player = gamenet_man->GetLocalPlayer();
+			if( msg->m_Final )
+			{
+				if( leader->IsLocalPlayer())
+				{
+					leader->m_Skater->SelfEvent(CRCD(0x96935417,"WonGame"));
+				}
+				else
+				{
+					if( my_player->m_Skater )
+					{
+						leader->m_Skater->SelfEvent( CRCD(0xe3ec7e9e,"LostGame") );
+					}
+				}
+			}
+			else if( ( gamenet_man->GetCurrentLeader() != leader ) &&
+					 ( num_players > 1 ) &&
+					 ( mod->GetGameMode()->IsTrue( CRCD(0xd63dfd0f,"show_leader_messages") ) ) &&
+                     ( !front_end->GamePaused()))
+	
+			{
+				// only print the message if you're competing against someone
+				if( leader->IsLocalPlayer())
+				{   
+					Script::RunScript( CRCD(0xa1d334d0,"NewScoreLeaderYou"), NULL, leader->m_Skater );
+				}
+				else
+				{   
+					Script::CScriptStructure* pTempStructure = new Script::CScriptStructure;
+					pTempStructure->Clear();
+					pTempStructure->AddComponent( CRCD(0xa4b08520,"String0"), ESYMBOLTYPE_STRING, leader->m_Name );
+					Script::RunScript( CRCD(0x7a4b9d14,"NewScoreLeaderOther"), pTempStructure, my_player->m_Skater );
+					delete pTempStructure;
+				}
+				gamenet_man->SetCurrentLeader( leader );
+			}
+		}
+	}
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Start/Stop spark effect										  */
+/*                           									  */
+/******************************************************************/
+
+int	Skate::handle_sparks( Net::MsgHandlerContext* context )
+{
+	Skate* mod;
+	GameNet::MsgObjMessage *msg;
+	Obj::CMovingObject *obj;
+
+	
+
+	msg = (GameNet::MsgObjMessage *) context->m_Msg;
+	
+	mod = (Skate *) context->m_Data;
+	obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+	if( obj )
+	{
+		Obj::CSkater* skater_obj;
+		
+		skater_obj = static_cast< Obj::CSkater *> ( obj );
+		Dbg_MsgAssert( skater_obj != NULL,( "Got sparks message for non-CSkater\n" ));
+		
+		if( skater_obj )
+		{   
+			switch( context->m_MsgId )
+			{
+				case GameNet::MSG_ID_SPARKS_ON:
+					skater_obj->SparksOn();
+					break;
+				case GameNet::MSG_ID_SPARKS_OFF:
+					skater_obj->SparksOff();
+					break;
+				default:
+					break;
+			}
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Start/Stop blood effect										  */
+/*                           									  */
+/******************************************************************/
+
+int	Skate::handle_blood( Net::MsgHandlerContext* context )
+{
+	Skate* mod;
+	Obj::CMovingObject *obj;
+	GameNet::MsgObjMessage* msg;
+
+	
+
+	mod = (Skate *) context->m_Data;
+	msg = (GameNet::MsgObjMessage* ) context->m_Msg;
+	obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( msg->m_ObjId );
+	if( obj )
+	{
+		Obj::CSkater* skater_obj;
+
+		skater_obj = static_cast< Obj::CSkater *> ( obj );
+		Dbg_MsgAssert( skater_obj != NULL,( "Got blood message for non-CSkater\n" ));
+		if( skater_obj )
+		{   
+			switch( context->m_MsgId )
+			{
+				case GameNet::MSG_ID_BLOOD_ON:
+				{
+					GameNet::MsgBlood *msg;
+
+					msg = (GameNet::MsgBlood *) context->m_Msg;
+//					skater_obj->BloodOn( msg->m_BodyPart, msg->m_Size, msg->m_Frequency, msg->m_RandomRadius );
+					break;
+				}
+				case GameNet::MSG_ID_BLOOD_OFF:
+				{
+					GameNet::MsgBloodOff *msg;
+
+					msg = (GameNet::MsgBloodOff *) context->m_Msg;
+//					skater_obj->BloodOff( msg->m_BodyPart );
+					break;
+				}
+				default:
+					break;
+			}
+		}
+	}
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Play a one-off sound given a position  						  */
+/*                           									  */
+/******************************************************************/
+
+int	Skate::handle_play_sound( Net::MsgHandlerContext* context )
+{
+	GameNet::MsgPlaySound *msg;
+	Mth::Vector pos;
+	float vol, pitch, choice;
+
+	msg = (GameNet::MsgPlaySound *) context->m_Msg;
+	
+	pos[X] = msg->m_Pos[0];
+	pos[Y] = msg->m_Pos[1];
+	pos[Z] = msg->m_Pos[2];
+
+	vol = (float) msg->m_VolPercent;
+	pitch = (float) msg->m_PitchPercent;
+	choice = (float) msg->m_SoundChoice;
+
+	Env::CTerrainManager::sPlaySound( (Env::ETerrainActionType) msg->m_WhichArray, (ETerrainType) msg->m_SurfaceFlag, pos, vol, pitch, choice, false );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Get all the game options  						  			  */
+/*                           									  */
+/******************************************************************/
+
+int	Skate::handle_start_info( Net::MsgHandlerContext* context )
+{   
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Skate* skate_mod;
+	GameNet::MsgStartInfo* msg;
+	Script::CArray* mode_array;
+	int i;
+
+	Dbg_Printf( "********************************* Got lobby info\n" );
+
+	gamenet_man->SetJoinState( GameNet::vJOIN_STATE_CONNECTED );
+	gamenet_man->FreeServerList();
+
+	skate_mod = (Skate *) context->m_Data;
+	msg = (GameNet::MsgStartInfo*) context->m_Msg;
+
+	// Note whether or not the server is narrowband/broadband
+	if( msg->m_Broadband )
+	{
+		context->m_Conn->SetBandwidthType( Net::Conn::vBROADBAND );
+		context->m_Conn->SetSendInterval( Tmr::VBlanks( 2 ));
+	}
+	else
+	{
+		context->m_Conn->SetBandwidthType( Net::Conn::vNARROWBAND );
+		context->m_Conn->SetSendInterval( Tmr::VBlanks( 2 ));
+	}
+
+	gamenet_man->SetNetworkGameId( msg->m_GameId );
+	gamenet_man->SetLevel( msg->m_LevelId );
+	gamenet_man->SetCrownSpawnPoint( msg->m_CrownSpawnPoint );
+	gamenet_man->SetSkaterStartingPoints( msg->m_StartPoints );
+	
+	// set up the game mode for the client cause we're now in the lobby
+	// anything that's lobby-dependent should go after this line
+	Script::CScriptStructure* pTempStructure = new Script::CScriptStructure;
+	pTempStructure->Clear();
+	pTempStructure->AddComponent( Script::GenerateCRC("level"), ESYMBOLTYPE_NAME, (int)msg->m_LevelId );					
+	Script::RunScript( "request_level", pTempStructure, NULL );
+	delete pTempStructure;
+
+	Script::RunScript( "leave_front_end" );
+	Script::RunScript( "cleanup_before_loading_level" );
+	
+	context->m_Conn->UpdateCommTime();	// update the current comm time so it doesn't time out after
+										// laoding the level
+
+	skate_mod->GetGameMode()->SetMaximumNumberOfPlayers( msg->m_MaxPlayers );
+	skate_mod->GetGameMode()->LoadGameType( msg->m_GameMode );
+	//skate_mod->GetGameMode()->SetMaximumNumberOfPlayers( msg->m_MaxPlayers );
+	skate_mod->LaunchGame();
+	skate_mod->GetGameMode()->LoadGameType( msg->m_GameMode );
+	skate_mod->GetGameMode()->SetNumTeams( msg->m_TeamMode );
+	
+	gamenet_man->SetProSetFlags( msg->m_ProSetFlags );
+	for( i = 0; i < GameNet::vMAX_NUM_PROSETS; i++ )
+	{
+		if(( 1 << i ) & msg->m_ProSetFlags )
+		{
+			Script::CStruct* pSetParams;
+			pSetParams = new Script::CStruct;
+			pSetParams->AddInteger( "flag", Script::GetInt( "FLAG_PROSET1_GEO_ON" ) + i );
+			Script::RunScript( "SetFlag", pSetParams );
+			delete pSetParams;
+		}
+	}
+	
+    {
+		Script::CScriptStructure* pTempStructure = new Script::CScriptStructure;
+		
+		if( msg->m_GameMode != Script::GenerateCRC("netlobby" ))
+		{
+			// In king of the hill, interpret time limit as a target time
+			if(	( msg->m_TimeLimit == 0 ) &&
+				( msg->m_GameMode != Script::GenerateCRC( "netctf" )) &&
+				( msg->m_GameMode != Script::GenerateCRC( "netgoalattack" )))
+			{
+				if( ( msg->m_GameMode != CRCD(0x3d6d444f,"firefight")) &&
+					( msg->m_GameMode != CRCD(0xbff33600,"netfirefight")))
+				{
+					Script::CArray* pArray = new Script::CArray;
+					Script::CopyArray(pArray,Script::GetArray("targetScoreArray"));
+					
+					Script::CScriptStructure* pSubStruct = pArray->GetStructure(0);
+					Dbg_Assert(pSubStruct);
+					pSubStruct->AddComponent(Script::GenerateCRC("score"),ESYMBOLTYPE_INTEGER, msg->m_TargetScore );
+					pTempStructure->AddComponent( Script::GenerateCRC("victory_conditions"), pArray );
+				}
+				pTempStructure->AddComponent( Script::GenerateCRC("default_time_limit"), ESYMBOLTYPE_INTEGER, (int) 0 );
+				skate_mod->SetTimeLimit( 0 );
+				skate_mod->GetGameMode()->OverrideOptions( pTempStructure );
+			}
+			else
+			{
+				pTempStructure->AddComponent( Script::GenerateCRC("default_time_limit"), ESYMBOLTYPE_INTEGER, (int) msg->m_TimeLimit );
+				if( msg->m_GameMode == Script::GenerateCRC( "netctf" ))
+				{
+					Script::CArray* pArray = new Script::CArray;
+					Script::CopyArray(pArray,Script::GetArray("highestScoreArray") );
+					pTempStructure->AddComponent( Script::GenerateCRC("victory_conditions"), pArray );
+				}
+				skate_mod->SetTimeLimit( msg->m_TimeLimit );
+				skate_mod->GetGameMode()->SetTimeLeft( msg->m_TimeLeft );
+				skate_mod->GetGameMode()->OverrideOptions( pTempStructure );
+			}
+		}
+
+		delete pTempStructure;
+	}
+
+	if( gamenet_man->InNetGame())
+	{
+		mode_array = Script::GetArray( "net_game_type_info" );
+	}
+	else
+	{
+		mode_array = Script::GetArray( "mp_game_type_info" );
+	}
+	Dbg_Assert( mode_array );
+
+	for( i = 0; i < (int)mode_array->GetSize(); i++ )
+	{   
+		uint32 value, script;
+		Script::CStruct* mode_struct;
+		 
+		mode_struct = mode_array->GetStructure( i );
+		Dbg_Assert( mode_struct );
+		
+		mode_struct->GetChecksum( "checksum", &value, true );
+		if( value == msg->m_GameMode )
+		{
+			Script::CStruct* params;
+						
+			params = new Script::CStruct;
+			params->AddInteger( "time", msg->m_TimeLimit );
+			params->AddInteger( "score", msg->m_TargetScore );
+			if( msg->m_TimeLimit == 0 )
+			{
+				params->AddInteger( CRCD(0xf0e712d2,"unlimited_time"), 1 );
+			}
+			else
+			{
+				params->AddInteger( CRCD(0xf0e712d2,"unlimited_time"), 0 );
+			}
+			//Dbg_Printf( "Target Score : %d\n", msg->m_TargetScore );
+			mode_struct->GetChecksum( "goal_script", &script, true );
+			Script::RunScript( script, params );
+			delete params;
+			break;
+		}
+	}
+
+	if( ( gamenet_man->InNetGame()) &&
+		( gamenet_man->GetJoinMode() == GameNet::vJOIN_MODE_PLAY ))
+	{
+		if( !CFuncs::ScriptIsObserving( NULL, NULL ))
+		{
+			gamenet_man->SendFaceDataToServer();
+		}
+	}
+
+    return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/* Parse the object update stream from the server and update the  */
+/* appropriate objects                          				  */
+/******************************************************************/
+
+int Skate::handle_object_update_relay( Net::MsgHandlerContext* context )
+{
+	Skate* mod;
+	int update_flags;
+	unsigned char obj_id, obj_id_mask;
+	Net::BitStream stream;
+    
+    Dbg_Assert( context );
+     
+	mod = (Skate *) context->m_Data;
+	stream.SetInputData( context->m_Msg, 1024 );
+	obj_id_mask = stream.ReadUnsignedValue( sizeof( char ) * 8 );
+	
+	for( obj_id = 0; obj_id < vMAX_SKATERS; obj_id++ )
+	{
+		GameNet::ObjectUpdate obj_update;
+		Obj::CMovingObject* obj;
+		Mth::Vector eulers;
+		short pos[3];
+		short fixed;
+		char doing_trick, state, terrain, walking, driving;
+		Flags< int > skater_flags;
+		Flags< int > end_run_flags;
+		sint16 rail_node;
+		int i;
+		unsigned short timestamp;
+		 
+		// If the bit for this player number is not set, that means there is no 
+		// object update for them. Continue
+		if(( obj_id_mask & ( 1 << obj_id )) == 0 )
+		{
+			continue;
+		}
+
+		//Dbg_Printf( "================ Got update for player %d\n", obj_id );
+
+    	timestamp = stream.ReadUnsignedValue( sizeof( uint16 ) * 8 );
+		update_flags = stream.ReadUnsignedValue( 9 );
+		
+		//Dbg_Printf( "Got Object Update For %d, Time: %d Mask %d\n", obj_id, timestamp, obj_id_mask );
+
+		doing_trick = 0;
+		state = 0;
+		skater_flags = 0;
+		end_run_flags = 0;
+		terrain = 0;
+		walking = 0;
+		driving = 0;
+		rail_node = -1;
+        
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_X )
+		{   
+			pos[X] = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//pos[X] = stream.ReadFloatValue();
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_Y )
+		{
+			pos[Y] = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//pos[Y] = stream.ReadFloatValue();
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_POS_Z )
+		{
+			pos[Z] = stream.ReadSignedValue( sizeof( short ) * 8 );
+			//pos[Z] = stream.ReadFloatValue();
+		}   
+				
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_X )
+		{
+			fixed = stream.ReadSignedValue( sizeof( short ) * 8 );
+			eulers[X] = ((float) fixed ) / 4096.0f;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_Y )
+		{
+			fixed = stream.ReadSignedValue( sizeof( short ) * 8 );
+			eulers[Y] = ((float) fixed ) / 4096.0f;
+		}
+		if( update_flags & GameNet::mUPDATE_FIELD_ROT_Z )
+		{
+			fixed = stream.ReadSignedValue( sizeof( short ) * 8 );
+			eulers[Z] = ((float) fixed ) / 4096.0f;
+		}
+        
+		if( update_flags & GameNet::mUPDATE_FIELD_STATE )
+		{   
+			char mask;
+
+			mask = stream.ReadUnsignedValue( 4 );
+		
+			state = mask & GameNet::mSTATE_MASK;
+			doing_trick = (( mask & GameNet::mDOING_TRICK_MASK ) != 0 );
+			terrain = stream.ReadUnsignedValue( 6 );
+			walking = stream.ReadUnsignedValue( 1 );
+			driving = stream.ReadUnsignedValue( 1 );
+		}
+
+		if( update_flags & GameNet::mUPDATE_FIELD_FLAGS )
+		{
+			skater_flags = stream.ReadUnsignedValue( 5 );
+			end_run_flags = stream.ReadUnsignedValue( 3 );
+		}
+
+		if( update_flags & GameNet::mUPDATE_FIELD_RAIL_NODE )
+		{
+			rail_node = stream.ReadSignedValue( sizeof( sint16 ) * 8 );
+			//Dbg_Printf( "Got rail node %d\n", rail_node );
+		}
+
+		obj = (Obj::CMovingObject *) mod->GetObjectManager()->GetObjectByID( obj_id );
+		
+		// Make sure that we actually have this object in our list of objects
+		// Also, reject late/foreign object update messages. We still needed to do all the stream
+		// parsing though because we need to return the length of the message to the dispatcher
+		if( ( obj != NULL ) &&
+			(( context->m_PacketFlags & ( Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN )) == 0 ))
+		{
+			Obj::CSkater* skater_obj;
+			 
+			skater_obj = static_cast< Obj::CSkater *> ( obj );
+			Dbg_MsgAssert( skater_obj != NULL,( "Got object update for non-CSkater\n" ));
+												   
+			if( skater_obj )
+			{
+				Obj::SPosEvent* p_pos_history = skater_obj->GetStateHistory()->GetPosHistory();
+				
+				int index, last_index;
+	
+				if( skater_obj->GetStateHistory()->GetNumPosUpdates() > 0 )
+				{
+					last_index = ( skater_obj->GetStateHistory()->GetNumPosUpdates() - 1 ) % Obj::CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+				}
+				else
+				{
+					Mth::Matrix obj_matrix;
+					Mth::Vector eulers;
+					last_index = 0;
+
+					obj_matrix = skater_obj->GetMatrix();
+					obj_matrix.GetEulers( eulers );
+					for( i = 0; i < 3; i++ )
+					{
+						if( i == Y )
+						{
+							p_pos_history[0].ShortPos[i] = (short) ( skater_obj->m_pos[i] * 4.0f );
+							//p_pos_history[0].ShortPos[i] = skater_obj->m_pos[i];
+						}
+						else
+						{
+							p_pos_history[0].ShortPos[i] = (short) ( skater_obj->m_pos[i] * 2.0f );
+							//p_pos_history[0].ShortPos[i] = skater_obj->m_pos[i];
+						}
+						p_pos_history[0].Eulers[i] = eulers[i];
+					}
+				}
+				
+				index = skater_obj->GetStateHistory()->GetNumPosUpdates() % Obj::CSkaterStateHistoryComponent::vNUM_POS_HISTORY_ELEMENTS;
+				
+                if( !(  update_flags & GameNet::mUPDATE_FIELD_POS_X ))
+				{
+					pos[X] = p_pos_history[last_index].ShortPos[X];
+				}
+				if( !(  update_flags & GameNet::mUPDATE_FIELD_POS_Y ))
+				{
+					pos[Y] = p_pos_history[last_index].ShortPos[Y];
+				}
+				if( !(  update_flags & GameNet::mUPDATE_FIELD_POS_Z ))
+				{
+					pos[Z] = p_pos_history[last_index].ShortPos[Z];
+				}
+				
+				for( i = 0; i < 3; i++ )
+				{
+					p_pos_history[index].ShortPos[i] = pos[i];
+				}
+
+				obj_update.m_Pos.Set(	((float) pos[X] ) / 2.0f,//pos[X],
+										((float) pos[Y] ) / 4.0f,//pos[Y],
+										((float) pos[Z] ) / 2.0f,//pos[Z],
+										0.0f );
+				//Dbg_Printf( "*** Got Pos[%.2f %.2f %.2f]\n", obj_update.m_Pos[X], obj_update.m_Pos[Y], obj_update.m_Pos[Z] );
+
+				if( !(  update_flags & GameNet::mUPDATE_FIELD_ROT_X ))
+				{
+					eulers[X] = p_pos_history[last_index].Eulers[X];
+				}
+				if( !(  update_flags & GameNet::mUPDATE_FIELD_ROT_Y ))
+				{
+					eulers[Y] = p_pos_history[last_index].Eulers[Y];
+				}
+				if( !(  update_flags & GameNet::mUPDATE_FIELD_ROT_Z ))
+				{
+					eulers[Z] = p_pos_history[last_index].Eulers[Z];
+				}
+
+				// Update history
+				for( i = 0; i < 3; i++ )
+				{
+					p_pos_history[index].Eulers[i] = eulers[i];
+				}
+
+				// Use those euler angles to compose a matrix
+				Mth::Matrix obj_matrix( eulers[X], eulers[Y], eulers[Z] );
+				obj_update.m_Matrix = obj_matrix;
+
+                p_pos_history[index].Matrix = obj_update.m_Matrix;
+				p_pos_history[index].Position = obj_update.m_Pos;
+                
+				Mth::Vector vel;
+				vel = p_pos_history[index].Position - p_pos_history[last_index].Position;
+				//Dbg_Printf( "(%d) Vel: %f %f %f\n", timestamp, vel[X], vel[Y], vel[Z] );
+
+				if( !( update_flags & GameNet::mUPDATE_FIELD_STATE ))
+				{
+					state = p_pos_history[last_index].State;
+					doing_trick = p_pos_history[last_index].DoingTrick;
+					terrain = p_pos_history[last_index].Terrain;
+					walking = p_pos_history[last_index].Walking;
+					driving = p_pos_history[last_index].Driving;
+				}
+				if( !( update_flags & GameNet::mUPDATE_FIELD_FLAGS ))
+				{
+					skater_flags = p_pos_history[last_index].SkaterFlags;
+					end_run_flags = p_pos_history[last_index].EndRunFlags;
+				}
+				if( !( update_flags & GameNet::mUPDATE_FIELD_RAIL_NODE ))
+				{
+					rail_node = p_pos_history[last_index].RailNode;
+				}
+                
+				p_pos_history[index].SkaterFlags = skater_flags;
+				p_pos_history[index].EndRunFlags = end_run_flags;
+				p_pos_history[index].RailNode = rail_node;
+				p_pos_history[index].State = state;
+				p_pos_history[index].DoingTrick = doing_trick;
+				p_pos_history[index].Terrain = (ETerrainType) terrain;
+				p_pos_history[index].Walking = walking;
+				p_pos_history[index].Driving = driving;
+
+				// Update the lo and hi words of the timestamp
+				p_pos_history[index].HiTime = p_pos_history[last_index].HiTime;
+
+				// If the lo-word has flipped, increment the hi-word
+				if( p_pos_history[last_index].LoTime > timestamp )
+				{
+					p_pos_history[index].HiTime++;
+				}
+				p_pos_history[index].LoTime = timestamp;
+				//Dbg_Printf( "(%d) Got time: %d\n", context->m_App->m_FrameCounter, p_pos_history[index].GetTime());
+				
+				
+				skater_obj->GetStateHistory()->IncrementNumPosUpdates();				
+			}
+		}
+	}
+			
+	return Net::HANDLER_CONTINUE;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::StartServer( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance(); 
+	Net::Server* server;
+	bool net_game;
+
+	net_game = false;
+	// prevents fragmentation
+	//CFuncs::ScriptCleanup( NULL, NULL );
+	if( gamenet_man->InNetMode())
+	{	
+		net_game = true;
+		gamenet_man->ClientShutdown();
+	}
+	
+	server = gamenet_man->SpawnServer( !net_game, true );
+	if( server == NULL )
+	{
+		return;
+	}
+
+	mlp_manager->AddLogicTask ( *m_object_update_task );
+	mlp_manager->AddLogicTask ( *m_score_update_task );
+	
+	// These handlers remaio active throughout the server's life
+	server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SCORE, handle_score_request );
+	server->m_Dispatcher.AddHandler( GameNet::MSG_ID_LANDED_TRICK, handle_landed_trick, 0, this );
+	server->m_Dispatcher.AddHandler( GameNet::MSG_ID_COMBO_REPORT, handle_combo_report, 0, this );
+    server->m_Dispatcher.AddHandler( GameNet::MSG_ID_CHAT, handle_msg_relay, Net::mHANDLE_LATE );
+	server->m_Dispatcher.AddHandler( GameNet::MSG_ID_BAIL_DONE, handle_bail_done, Net::mHANDLE_LATE );
+	server->m_Dispatcher.AddHandler( GameNet::MSG_ID_OBJ_UPDATE_STREAM, handle_object_update, 
+									 Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN );
+
+	// In Split Screen Games, Don't relay any messages
+	if( !server->IsLocal())
+	{
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_FACE_DATA, handle_face_data );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPAWN_PROJECTILE, handle_msg_relay, 0, this );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_ENTER_VEHICLE, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_ENTER_VEHICLE, handle_enter_vehicle_server );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_PRIM_ANIM_START, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_WOBBLE_TARGET, handle_selective_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_ROTATE_SKATEBOARD, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_ROTATE_DISPLAY, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_CLEAR_ROTATE_DISPLAY, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_CREATE_SPECIAL_ITEM, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_DESTROY_SPECIAL_ITEM, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_FLIP_ANIM, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_WOBBLE_DETAILS, handle_selective_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_LOOPING_TYPE, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_HIDE_ATOMIC, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_ANIM_SPEED, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_PLAY_SOUND, handle_selective_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_PLAY_LOOPING_SOUND, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_BLOOD_ON, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_BLOOD_OFF, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPARKS_ON, handle_msg_relay );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPARKS_OFF, handle_msg_relay, Net::mHANDLE_LATE );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_RUN_SCRIPT, handle_script_relay, Net::mHANDLE_LATE );
+		server->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPAWN_AND_RUN_SCRIPT, handle_script_relay, Net::mHANDLE_LATE );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Skate::LaunchNetworkGame( void )
+{
+	RestartLevel();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::AddNetworkMsgHandlers( Net::Client* client, int index )
+{
+	Obj::CSkater* skater;
+	
+	Dbg_Assert( client );
+    
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_START_INFO, handle_start_info, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_OBJ_UPDATE_STREAM, handle_object_update_relay, 
+									 Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_PRIM_ANIM_START, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_TOGGLE_CHEAT, handle_cheats, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_WOBBLE_TARGET, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_ROTATE_DISPLAY, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_CLEAR_ROTATE_DISPLAY, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_CREATE_SPECIAL_ITEM, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_DESTROY_SPECIAL_ITEM, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_ROTATE_SKATEBOARD, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_FLIP_ANIM, handle_anims, 0, this, Net::HIGHEST_PRIORITY );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_FLIP_ANIM, handle_flip_anim, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_WOBBLE_DETAILS, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_LOOPING_TYPE, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_HIDE_ATOMIC, handle_anims, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SET_ANIM_SPEED, handle_anims, 0, this );     
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_PLAYER_QUIT, handle_player_quit, 
+									 Net::mHANDLE_LATE, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_HOST_QUIT, handle_disconn_accepted, 
+									 Net::mHANDLE_LATE, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_PLAY_SOUND, handle_play_sound, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_PLAY_LOOPING_SOUND, handle_play_sound, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPARKS_ON, handle_sparks, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPARKS_OFF, handle_sparks, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_BLOOD_ON, handle_blood, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_BLOOD_OFF, handle_blood, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SCORE_UPDATE, handle_score_update, 
+									 Net::mHANDLE_LATE, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_CHANGE_LEVEL, handle_change_level, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_RUN_SCRIPT, handle_run_script, Net::mHANDLE_LATE, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPAWN_AND_RUN_SCRIPT, handle_spawn_run_script, Net::mHANDLE_LATE, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_KICKED, handle_kicked, Net::mHANDLE_LATE, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_LEFT_OUT, handle_kicked, Net::mHANDLE_LATE, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_OBSERVER_LOG_TRICK_OBJ, handle_obs_log_trick_objects, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_OBSERVER_INIT_GRAFFITI_STATE, handle_obs_init_graffiti_state, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_FACE_DATA, handle_face_data, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_CHEAT_LIST, handle_cheat_list, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SPAWN_PROJECTILE, handle_projectile, 0, this );
+	client->m_Dispatcher.AddHandler( GameNet::MSG_ID_ENTER_VEHICLE, handle_enter_vehicle_client, 0, this );
+
+	for( uint i = 0; i < vMAX_SKATERS; i++ )
+	{
+		skater = GetSkater( i );
+		if( skater && skater->IsLocalClient() && ( skater->GetHeapIndex() == index ))
+		{
+			client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SCORE, Mdl::Score::s_handle_score_message, 0, skater );
+			client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_COLLIDE_WON, Obj::CSkaterStateHistoryComponent::sHandleCollision, 0, skater );
+			client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_COLLIDE_LOST, Obj::CSkaterStateHistoryComponent::sHandleCollision, 0, skater );
+			client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_PROJECTILE_HIT_TARGET, Obj::CSkaterStateHistoryComponent::sHandleProjectileHit, 0, skater );
+			client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_HIT_BY_PROJECTILE, Obj::CSkaterStateHistoryComponent::sHandleProjectileHit, 0, skater );
+			client->m_Dispatcher.AddHandler( GameNet::MSG_ID_STEAL_MESSAGE, Obj::CSkaterLocalNetLogicComponent::sHandleStealMessage, 0, skater );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::LeaveServer( void )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client;
+    
+	
+    
+	if( gamenet_man->OnServer())
+	{   
+		Net::App* server;
+		Net::MsgDesc msg_desc;
+
+		server = gamenet_man->GetServer();
+		Dbg_Assert( server );
+
+		// If we were auto serving, shut that service down
+		if( gamenet_man->GetHostMode() == GameNet::vHOST_MODE_AUTO_SERVE )
+		{
+			gamenet_man->AutoServerShutdown();
+		}
+
+		// First, wait for any pending network tasks to finish before sending out final disconn
+		// message
+#ifdef __PLAT_NGPS__
+		//server->WaitForAsyncCallsToFinish();
+#endif  		
+
+		// Send off last message, notifying clients that I'm about to quit
+		Dbg_Printf( "Starting server shutdown\n" );
+		msg_desc.m_Id = GameNet::MSG_ID_HOST_QUIT;
+		msg_desc.m_Priority = Net::HIGHEST_PRIORITY;
+		server->EnqueueMessage( Net::HANDLE_ID_BROADCAST, &msg_desc );
+		// Wake the network thread up to send the data
+		server->SendData();
+		
+		// Wait for that final send to complete
+#ifdef __PLAT_NGPS__
+		//server->WaitForAsyncCallsToFinish();
+#endif  
+		gamenet_man->ServerShutdown();
+
+		m_object_update_task->Remove();
+		m_score_update_task->Remove();
+	}
+	else
+	{
+		client = gamenet_man->GetClient( 0 );
+		if( client )
+		{
+			Net::MsgDesc msg_desc;
+			// First, wait for any pending network tasks to finish before sending out final disconn
+			// message
+#ifdef __PLAT_NGPS__
+			//client->WaitForAsyncCallsToFinish();
+#endif  		
+			Dbg_Printf( "Leaving server\n" );
+			
+			msg_desc.m_Id = Net::MSG_ID_DISCONN_REQ;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+			// Send off last message, notifying server that I'm about to quit
+			client->EnqueueMessageToServer( &msg_desc );
+			// Wake the network thread up to send the data
+			client->SendData();
+		
+			// Wait for that final send to complete
+#ifdef __PLAT_NGPS__
+			//client->WaitForAsyncCallsToFinish();
+#endif  
+
+		}
+	}
+    
+	gamenet_man->DisconnectFromServer();
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::SetLaunchingQueuedScripts( void )
+{
+	m_launching_queued_scripts = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::ClearLaunchingQueuedScripts( void )
+{
+	m_launching_queued_scripts = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Skate::LaunchingQueuedScripts( void )
+{
+	return m_launching_queued_scripts;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Skate::SendCheatList( GameNet::PlayerInfo* player )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::MsgEnabledCheats cheat_msg;
+	Net::MsgDesc msg_desc;
+
+	cheat_msg.m_NumCheats = 0;
+
+	if( mp_career->GetCheat( CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL");
+	}
+	if( mp_career->GetCheat( CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL");
+	}
+	if( mp_career->GetCheat( CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL");
+	}
+	if( mp_career->GetCheat( CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH");
+	}
+	if( mp_career->GetCheat( CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL");
+	}
+	if( mp_career->GetCheat( CRCD(0x6b0f24bc, "CHEAT_STATS_13")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0x6b0f24bc, "CHEAT_STATS_13");
+	}
+	if( mp_career->GetCheat( CRCD(0x520a66ef,"FLAG_G_EXPERT_MODE_NO_REVERTS")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0x520a66ef,"FLAG_G_EXPERT_MODE_NO_REVERTS");
+	}
+	if( mp_career->GetCheat( CRCD(0xeba89179,"FLAG_G_EXPERT_MODE_NO_WALKING")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0xeba89179,"FLAG_G_EXPERT_MODE_NO_WALKING");
+	}
+	if( mp_career->GetCheat( CRCD(0xc4ffc672,"FLAG_G_EXPERT_MODE_NO_MANUALS")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0xc4ffc672,"FLAG_G_EXPERT_MODE_NO_MANUALS");
+	}
+	if( mp_career->GetCheat( CRCD(0xd5982173,"NO_G_DISPLAY_BALANCE")))
+	{
+		cheat_msg.m_Cheats[cheat_msg.m_NumCheats++] = CRCD(0xd5982173,"NO_G_DISPLAY_BALANCE");
+	}
+
+	// Send it, even if there are zero cheats. This triggers the clearing of all cheats on
+	// the client side
+	msg_desc.m_Id = GameNet::MSG_ID_CHEAT_LIST;
+	msg_desc.m_Data = &cheat_msg;
+	msg_desc.m_Length = sizeof( int ) +( sizeof( uint32 ) * cheat_msg.m_NumCheats );
+
+	msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+	msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+	
+	gamenet_man->GetServer()->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	Skate::GetCheatChecksum( void )
+{
+	uint32 checksum;
+
+	checksum = 0;
+	
+	if( mp_career->GetCheat( CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL")))
+	{
+		checksum |= CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL");
+	}
+	if( mp_career->GetCheat( CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL")))
+	{
+		checksum |= CRCD(0xb38341c9, "CHEAT_PERFECT_MANUAL");
+	}
+	if( mp_career->GetCheat( CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL")))
+	{
+		checksum |= CRCD(0xcd09e062, "CHEAT_PERFECT_RAIL");
+	}
+	if( mp_career->GetCheat( CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH")))
+	{
+		checksum |= CRCD(0x69a1ce96, "CHEAT_PERFECT_SKITCH");
+	}
+	if( mp_career->GetCheat( CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL")))
+	{
+		checksum |= CRCD(0xeefecf1b, "CHEAT_ALWAYS_SPECIAL");
+	}
+	if( mp_career->GetCheat( CRCD(0x6b0f24bc, "CHEAT_STATS_13")))
+	{
+		checksum |= CRCD(0x6b0f24bc, "CHEAT_STATS_13");
+	}
+	if( mp_career->GetCheat( CRCD(0x520a66ef,"FLAG_G_EXPERT_MODE_NO_REVERTS")))
+	{
+		checksum |= CRCD(0x520a66ef,"FLAG_G_EXPERT_MODE_NO_REVERTS");
+	}
+	if( mp_career->GetCheat( CRCD(0xeba89179,"FLAG_G_EXPERT_MODE_NO_WALKING")))
+	{
+		checksum |= CRCD(0xeba89179,"FLAG_G_EXPERT_MODE_NO_WALKING");
+	}
+	if( mp_career->GetCheat( CRCD(0xc4ffc672,"FLAG_G_EXPERT_MODE_NO_MANUALS")))
+	{
+		checksum |= CRCD(0xc4ffc672,"FLAG_G_EXPERT_MODE_NO_MANUALS");
+	}
+	if( mp_career->GetCheat( CRCD(0xd5982173,"NO_G_DISPLAY_BALANCE")))
+	{
+		checksum |= CRCD(0xd5982173,"NO_G_DISPLAY_BALANCE");
+	}
+
+	return checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
diff --git a/Code/Sk/Modules/Viewer/Viewer.cpp b/Code/Sk/Modules/Viewer/Viewer.cpp
new file mode 100644
index 0000000..c30165c
--- /dev/null
+++ b/Code/Sk/Modules/Viewer/Viewer.cpp
@@ -0,0 +1,961 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2001 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			Viewer			 										**
+**																			**
+**	File name:		Viewer.cpp												**
+**																			**
+**	Created by:		11/28/01	-	SPG										**
+**																			**
+**	Description:	Viewer module		 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include 
+
+#include 
+
+#include 
+#include 			// just to see if the select button is pressed!!!
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+bool	run_runmenow = false;		// patch for signalling that the runnow script should be run
+
+namespace Mdl
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+float	CViewer::s_movement_vel;	// in inches per sec
+float	CViewer::s_rotate_vel;	// in degrees per sec
+int		CViewer::s_view_mode=0;
+
+// GJ:  I'm assuming that there's only going to be one CViewer at a time.
+// I needed to be able to access this class from cfuncs.cpp, so I added
+// this pointer.  Perhaps when Steve gets back from his surgery, we can
+// decide a more appropriate interface between cfuncs.cpp and viewer.cpp.
+CViewer* CViewer::sp_viewer = NULL;
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+void        CViewer::s_shift_input_logic_code ( const Inp::Handler < CViewer >& handler )
+{
+	Dbg_AssertType ( &handler, Inp::Handler< CViewer > );
+
+	CViewer&   mdl = handler.GetData();
+
+	mdl.m_shift_commands.ClearAll();
+
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_SELECT )
+	{
+		mdl.m_shift_commands.SetMask( handler.m_Input->m_Makes );
+		if (Script::GetInt(CRCD(0xf3e055e1,"select_shift")))
+		{
+			// only mask the buttons if we are using select as a shift button 
+			handler.m_Input->MaskDigitalInput( Inp::Data::mD_ALL && ~Inp::Data::mD_LEFT && ~Inp::Data::mD_RIGHT && ~Inp::Data::mD_UP && ~Inp::Data::mD_DOWN);
+			handler.m_Input->MaskAnalogInput( Inp::Data::mA_ALL && ~Inp::Data::mA_LEFT && ~Inp::Data::mA_RIGHT && ~Inp::Data::mA_UP && ~Inp::Data::mA_DOWN && ~Inp::Data::mA_LEFT_X && ~Inp::Data::mA_LEFT_Y && ~Inp::Data::mA_RIGHT_X && ~Inp::Data::mA_RIGHT_Y );
+		}
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CViewer::s_input_logic_code ( const Inp::Handler < CViewer >& handler )
+{       
+	CViewer&	mdl = handler.GetData();
+
+//	printf ("s_view_mode = %d   rightx = %d\n",s_view_mode,mdl.m_right[X]);
+
+// just return if we are not in viewer mode
+	if (!s_view_mode)
+	{
+		return;
+	}
+							  
+	// GJ:  no longer clearing the last frame's data
+	// at the beginning of the loop...  this allows
+	// the viewer object to add additional m_commands
+	// to the viewer (useful for customizing controller
+	// configs in different viewer modes)
+//	mdl.m_commands.ClearAll();
+
+	// translate m_Input data to module-specific commands
+	mdl.m_right[X] = handler.m_Input->m_Event[Inp::Data::vA_RIGHT_X_UNCLAMPED] - 128;
+	mdl.m_right[Y] = handler.m_Input->m_Event[Inp::Data::vA_RIGHT_Y_UNCLAMPED] - 128;
+	mdl.m_left[X] = handler.m_Input->m_Event[Inp::Data::vA_LEFT_X_UNCLAMPED] - 128;
+	mdl.m_left[Y] = handler.m_Input->m_Event[Inp::Data::vA_LEFT_Y_UNCLAMPED] - 128;
+
+
+	if (Script::GetInt(CRCD(0x89bef647,"viewer_buttons_enabled")))
+	{
+		if( ( handler.m_Input->m_Buttons & Inp::Data::mD_R1 ) &&
+			( handler.m_Input->m_Makes & Inp::Data::mD_R2 ))
+		{
+			if (!Config::CD())
+			{
+	//  Mick - took out viewport changing for non-PS2, as it messes up X-Box artists, with only one shoulder button
+				#ifdef	__PLAT_NGPS__
+				mdl.m_commands.SetMask( mCHANGE_SCREEN_MODE );
+				#endif
+			}	
+		}
+		else if( handler.m_Input->m_Buttons & Inp::Data::mD_R1 )
+		{
+			if( !( handler.m_Input->m_Buttons & Inp::Data::mD_R2 ))
+			{
+				mdl.m_commands.SetMask( mSTRAFE_UP );
+			}
+		}
+		else if( handler.m_Input->m_Buttons & Inp::Data::mD_R2 )
+		{
+			mdl.m_commands.SetMask( mSTRAFE_DOWN );
+		}
+		
+	//	if (!Config::CD())
+		{
+			if( handler.m_Input->m_Makes & Inp::Data::mD_START )
+			{
+				mdl.m_commands.SetMask( mRESET );
+			}
+			if( mdl.m_rotation_mode == vROTATE_CAMERA )
+			{   
+				if( handler.m_Input->m_Makes & Inp::Data::mD_TRIANGLE )
+				{
+					mdl.m_commands.SetMask( mVIEW_TOP );
+				}
+				if( handler.m_Input->m_Makes & Inp::Data::mD_SQUARE )
+				{
+					mdl.m_commands.SetMask( mVIEW_LEFT );
+				}
+				if( handler.m_Input->m_Makes & Inp::Data::mD_CIRCLE )
+				{
+					mdl.m_commands.SetMask( mVIEW_RIGHT );
+				}
+				if( handler.m_Input->m_Makes & Inp::Data::mD_X )
+				{
+					mdl.m_commands.SetMask( mVIEW_FRONT );
+				}
+			}
+			else if( mdl.m_rotation_mode == vROTATE_OBJECTS )
+			{
+				if( handler.m_Input->m_Makes & Inp::Data::mD_X )
+				{
+					mdl.m_commands.SetMask( mRESET_ROTATIONS );
+				}
+			}
+			if( handler.m_Input->m_Buttons & Inp::Data::mD_UP )
+			{
+				if ( handler.m_Input->m_Makes & Inp::Data::mD_UP )
+				{
+					mdl.m_commands.SetMask( mLIGHT_FW );
+				}
+			}
+			if( handler.m_Input->m_Buttons & Inp::Data::mD_DOWN )
+			{
+				if ( handler.m_Input->m_Makes & Inp::Data::mD_DOWN )
+				{
+					mdl.m_commands.SetMask( mLIGHT_BK );
+				}
+			}
+			if( handler.m_Input->m_Buttons & Inp::Data::mD_RIGHT )
+			{
+				mdl.m_commands.SetMask( mLIGHT_RT );
+			}
+			if( handler.m_Input->m_Buttons & Inp::Data::mD_LEFT )
+			{
+				mdl.m_commands.SetMask( mLIGHT_LT );
+			}
+			if( handler.m_Input->m_Makes & Inp::Data::mD_L3 )
+			{
+				mdl.m_commands.SetMask( mTOGGLE_ROTATE_MODE );
+			}
+			if ( handler.m_Input->m_Buttons & Inp::Data::mD_L1 )
+			{
+				if ( !( handler.m_Input->m_Buttons & Inp::Data::mD_L2 ))
+				{
+					mdl.m_commands.SetMask( mROLL_LEFT );
+				}
+			}
+			else if ( handler.m_Input->m_Buttons & Inp::Data::mD_L2 )
+			{
+				mdl.m_commands.SetMask( mROLL_RIGHT );
+			}
+	
+			
+			if ( ! (handler.m_Input->m_Buttons & Inp::Data::mD_SELECT) )
+			{
+				// Patch on buttonscript calls to center the camera in various ways when in a view mode
+				if ( handler.m_Input->m_Makes & Inp::Data::mD_SQUARE )
+				{
+					Script::RunScript("UserViewerSquare");
+				}
+				
+				if ( handler.m_Input->m_Makes & Inp::Data::mD_CIRCLE )
+				{
+					Script::RunScript("UserViewerCIRCLE");
+				}
+				
+				if ( handler.m_Input->m_Makes & Inp::Data::mD_TRIANGLE )
+				{
+					Script::RunScript("UserViewerTRIANGLE");
+				}
+				
+				if ( handler.m_Input->m_Makes & Inp::Data::mD_X )
+				{
+					Script::RunScript("UserViewerX");
+				}
+			}		
+			
+		}	
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CViewer::s_logic_code ( const Tsk::Task< CViewer >& task )
+{
+	CViewer&	mdl = task.GetData();
+
+	Dbg_AssertType ( &task, Tsk::Task< CViewer > );
+
+	//static const    float   ldelta = 1.0f;
+	//static const    float   lcolordelta = .0125f;
+	static const    float   scale = ( 1.0f / ( 128.0f - Inp::vANALOGUE_TOL ));
+
+//    Gfx::Camera* pCam = Nx::CViewportManager::sGetCamera( 0 );
+//	if ( !pCam )
+//	{
+//		return;
+//	}
+
+	float elapsed_seconds;
+	Tmr::Time elapsed_time;
+	static Tmr::Time last_time = 0;
+
+	// store the old camera pos before updating anything...
+//	pCam->StoreOldPos( );
+
+	elapsed_time = Tmr::ElapsedTime( last_time );
+	elapsed_seconds = (int)elapsed_time / 1000.0f;
+	last_time = last_time + elapsed_time;   
+
+	if ( mdl.m_commands.TestMask( mTOGGLE_ROTATE_MODE ))
+	{
+		// Mick: Don't switch to rotating the camera around the object unless there is an object
+		if ( mdl.mp_viewerObject && mdl.m_rotation_mode == vROTATE_CAMERA )
+		{
+			mdl.m_rotation_mode = vROTATE_OBJECTS;
+		}
+		else
+		{
+			mdl.m_rotation_mode = vROTATE_CAMERA;
+		}      
+	}
+
+	mdl.m_right[Y] =
+	( mdl.m_right[Y] >  Inp::vANALOGUE_TOL ) ? ( mdl.m_right[Y] - Inp::vANALOGUE_TOL ) * scale :
+	( mdl.m_right[Y] < -Inp::vANALOGUE_TOL ) ? ( mdl.m_right[Y] + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+	mdl.m_right[X] =
+	( mdl.m_right[X] >  Inp::vANALOGUE_TOL ) ? ( mdl.m_right[X] - Inp::vANALOGUE_TOL ) * scale :
+	( mdl.m_right[X] < -Inp::vANALOGUE_TOL ) ? ( mdl.m_right[X] + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+
+	mdl.m_left[Y] =
+	( mdl.m_left[Y] >  Inp::vANALOGUE_TOL ) ? ( mdl.m_left[Y] - Inp::vANALOGUE_TOL ) * scale :
+	( mdl.m_left[Y] < -Inp::vANALOGUE_TOL ) ? ( mdl.m_left[Y] + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+	mdl.m_left[X] =
+	( mdl.m_left[X] >  Inp::vANALOGUE_TOL ) ? ( mdl.m_left[X] - Inp::vANALOGUE_TOL ) * scale :
+	( mdl.m_left[X] < -Inp::vANALOGUE_TOL ) ? ( mdl.m_left[X] + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+
+	Obj::CCompositeObject * p_obj = (Obj::CCompositeObject *) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0xeb17151b,"viewer_cam"));
+	if (p_obj)
+	{
+		if ( mdl.m_rotation_mode == vROTATE_CAMERA )
+		{
+			if (mdl.m_left[Y])
+			{
+				p_obj->GetMatrix().RotateLocal( Mth::Vector( 1, 0, 0 ),
+							mdl.m_left[Y] * elapsed_seconds * mdl.s_rotate_vel * Mth::PI / 180.0f );
+			}
+			if (mdl.m_left[X])
+			{
+				// this rotation is *NOT* local, as the designers seem to prefer it this way.
+				Mth::Matrix mat = p_obj->GetMatrix();
+				mat.RotateY( -mdl.m_left[X] * elapsed_seconds * mdl.s_rotate_vel * Mth::PI / 180.0f );
+				p_obj->SetMatrix( mat );
+			}
+		}
+		else if ( mdl.m_rotation_mode == vROTATE_OBJECTS )
+		{
+			Dbg_Assert( mdl.mp_viewerObject );
+			mdl.mp_viewerObject->SetRotation( mdl.m_left[X], mdl.m_left[Y] );
+		}
+
+
+	
+		// Move with right analog stick:
+		if (mdl.m_right[X] || mdl.m_right[Y])
+		{
+			Mth::Vector transVec;
+			transVec[X] = mdl.m_right[X] * ( elapsed_seconds * mdl.s_movement_vel );
+			transVec[Y] = 0.0f;
+			transVec[Z] = mdl.m_right[Y] * ( elapsed_seconds * mdl.s_movement_vel );
+			transVec[W] = 1.0f;
+			s_translate_local( p_obj, transVec );
+		}
+	}
+
+	if ( mdl.m_commands.TestMask( mSTRAFE_UP ))
+	{
+		s_translate_local( p_obj, Mth::Vector( 0, elapsed_seconds * mdl.s_movement_vel, 0 ) );
+	}
+	if ( mdl.m_commands.TestMask( mSTRAFE_DOWN ))
+	{
+		s_translate_local( p_obj, Mth::Vector( 0, -elapsed_seconds * mdl.s_movement_vel, 0 ) );
+	}
+
+	if ( !Script::GetInt( CRCD(0xd8c91578,"DisableViewerRoll"), false ) )
+	{
+		if ( mdl.m_commands.TestMask( mROLL_LEFT ))
+		{
+			p_obj->GetMatrix().RotateLocal( Mth::Vector( 0, 0, 1 ),
+									  -0.5f * elapsed_seconds * mdl.s_rotate_vel * Mth::PI / 180.0f );
+		}
+		if ( mdl.m_commands.TestMask( mROLL_RIGHT ))
+		{
+			p_obj->GetMatrix().RotateLocal( Mth::Vector( 0, 0, 1 ),
+									  0.5f * elapsed_seconds * mdl.s_rotate_vel * Mth::PI / 180.0f );
+		}
+	}
+	
+	p_obj->Pause(false);		// dang, it's suspended due to game being paused, so need to update manually...
+							// THIS IS A PATCH - needs to be more explicity handled.....
+	
+	
+	if ( mdl.m_commands.TestMask( mCHANGE_SCREEN_MODE ))
+	{
+		Nx::CViewportManager::sSetScreenMode ( (Nx::ScreenMode) (( (int) Nx::CViewportManager::sGetScreenMode() + 1 ) % Nx::vNUM_SCREEN_MODES) );
+	}
+
+	// GJ:  clear the frame's data, now that we're done with it
+	// (this used to be done at the beginning of the loop)
+	mdl.m_commands.ClearAll();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		CViewer::s_translate_local( Obj::CCompositeObject* p_obj, const Mth::Vector& vec )
+{
+	// need to build a temporary matrix,
+	// because the rotation and translation
+	// are stored separately in the camera
+	Mth::Matrix mat = p_obj->GetMatrix();
+	mat[Mth::POS] = p_obj->GetPos();
+	
+	mat.TranslateLocal( vec );
+	
+	p_obj->SetPos(mat[Mth::POS]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CScript *spawn_if_exists(uint32 script)
+{
+	if (Script::ScriptExists(script))
+	{
+		return  Script::SpawnScript(script);
+	}
+	return NULL;
+}
+
+void        CViewer::s_shift_logic_code ( const Tsk::Task< CViewer >& task )
+{
+	Dbg_AssertType ( &task, Tsk::Task< CViewer > );
+
+	CViewer&   mdl = task.GetData();
+
+	if (!Script::GetInt(CRCD(0xf3e055e1,"select_shift")))
+	{
+//		if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_SELECT ))
+//		{
+//			Script::RunScript(CRCD(0x60871393,"UserSelectSelect"));
+//		}
+	}
+	else
+	{
+		Script::CScript *p_script=NULL;
+		if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_TRIANGLE ))
+		{
+			p_script=spawn_if_exists(CRCD(0xcbb91022,"UserSelectTriangle"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_SQUARE ))
+		{
+			p_script=spawn_if_exists(CRCD(0xe69691fa,"UserSelectSquare"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_CIRCLE ))
+		{
+			// (launches the game)
+			p_script=spawn_if_exists(CRCD(0xffc29c2a,"UserSelectCircle"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_START ))
+		{
+			p_script=spawn_if_exists(CRCD(0x51b0e413,"UserSelectStart"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_X ))
+		{
+			p_script=spawn_if_exists(CRCD(0x746d2598,"UserSelectX"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_L1 ))
+		{
+			p_script=spawn_if_exists(CRCD(0x81d0a13c,"UserSelectL1"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_L2 ))
+		{
+			p_script=spawn_if_exists(CRCD(0x18d9f086,"UserSelectL2"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_R1 ))
+		{
+			p_script=spawn_if_exists(CRCD(0x55919ee3,"UserSelectR1"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_R2 ))
+		{
+			p_script=spawn_if_exists(CRCD(0xcc98cf59,"UserSelectR2"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_UP ))
+		{
+			p_script=spawn_if_exists(CRCD(0x1b0b7922,"UserSelectUp"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_DOWN ))
+		{
+			p_script=spawn_if_exists(CRCD(0xbbc0e36a,"UserSelectDown"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_LEFT ))
+		{
+			p_script=spawn_if_exists(CRCD(0xdd589439,"UserSelectLeft"));
+		}
+		else if ( mdl.m_shift_commands.TestMask( Inp::Data::mD_RIGHT ))
+		{
+			p_script=spawn_if_exists(CRCD(0x7a03c488,"UserSelectRight"));
+		}
+		   
+		if (p_script)
+		{
+			#ifdef __NOPT_ASSERT__
+			p_script->SetCommentString("Spawned from CViewer::s_shift_logic_code()");
+			#endif
+		}	
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewer::s_handle_quickview( Net::MsgHandlerContext* context )
+{
+	Net::MsgQuickview* p_msg;
+	Script::CStruct* pStructure;
+
+	p_msg = (Net::MsgQuickview*) context->m_Msg;
+
+	Dbg_Printf( "Got quickview request to load file %s....\n", p_msg->m_Filename );
+
+    pStructure = new Script::CStruct;
+
+	pStructure->AddComponent( Script::GenerateCRC( "scene" ), ESYMBOLTYPE_STRING, p_msg->m_Filename );
+
+	Script::RunScript( "ReloadScene", pStructure );
+
+	delete pStructure;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewer::s_handle_incremental_update( Net::MsgHandlerContext* context )
+{
+	Net::MsgQuickview* p_msg;
+	Script::CStruct* pStructure;
+
+	p_msg = (Net::MsgQuickview*) context->m_Msg;
+
+	Dbg_Printf( "Got quickview request to load file %s....\n", p_msg->m_Filename );
+
+    pStructure = new Script::CStruct;
+
+	pStructure->AddComponent( Script::GenerateCRC( "scene" ), ESYMBOLTYPE_STRING, p_msg->m_Filename );
+
+	pStructure->AddComponent( Script::GenerateCRC( "add" ), ESYMBOLTYPE_STRING, p_msg->m_UpdateFilename );
+	Script::RunScript( "AddToScene", pStructure );
+
+	delete pStructure;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewer::s_handle_update_material( Net::MsgHandlerContext* context )
+{
+	Dbg_Printf( "Got material update request....\n" );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewer::s_handle_rq( Net::MsgHandlerContext* context )
+{
+	char* path = context->m_Msg;
+
+	if ((strcmp("..\\runnow.qb",path) == 0) || (strcmp("runnow.qb",path) == 0))
+	{
+		run_runmenow = true;
+	}
+	else
+	{
+		Dbg_Printf( "******* GOT PATH %s ********\n", path );
+	}
+	SkateScript::LoadQB( path );
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewer::s_handle_load_model( Net::MsgHandlerContext* context )
+{        
+	Dbg_Message( "Got request to load model/anim...\n" );
+	
+	Mdl::CViewer* pViewer = (Mdl::CViewer*) context->m_Msg;
+	Dbg_Assert( pViewer );
+	
+	Script::CStruct* pStruct = new Script::CStruct;
+
+	Net::MsgViewObjLoadModel* p_msg;
+	p_msg = (Net::MsgViewObjLoadModel*) context->m_Msg;
+	pStruct->AddString( CRCD(0xb974f2fc,"modelName"), p_msg->m_ModelName );
+	pStruct->AddChecksum( CRCD(0x6c2bfb7f,"animName"), p_msg->m_AnimScriptName );
+	pStruct->AddChecksum( CRCD(0x09794932,"skeletonName"), p_msg->m_SkeletonName );
+	pViewer->AddViewerObject( pStruct );
+	
+	delete pStruct;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+		 
+int CViewer::s_handle_unload_model( Net::MsgHandlerContext* context )
+{
+	Dbg_Message( "Got request to unload model/anim...\n" );
+
+	Mdl::CViewer* pViewer = (Mdl::CViewer*) context->m_Msg;
+	Dbg_Assert( pViewer );
+	pViewer->RemoveViewerObject();
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewer::s_handle_run_script_command( Net::MsgHandlerContext* context )
+{
+	uint32 *p_data=(uint32*)context->m_Msg;
+	
+	uint32 command_name=*p_data++;
+	
+	Script::CStruct *p_params=new Script::CStruct;
+	Script::ReadFromBuffer(p_params,(uint8*)p_data);
+//	Dbg_Printf( "Running script command %p\n", command_name );
+	Script::RunScript(command_name,p_params);
+	delete p_params;
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewer::AddViewerObject( Script::CStruct* pParams )
+{
+	if (Config::GotExtraMemory())
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+	}
+	else
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	}
+		
+	// if the viewer object exists already,
+	// clear it out
+	if ( mp_viewerObject )
+	{
+		RemoveViewerObject();
+	}
+
+	Dbg_Assert( !mp_viewerObject );
+	mp_viewerObject = new Obj::CViewerObject( mp_server );
+	mp_viewerObject->LoadModel( pParams );
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewer::RemoveViewerObject()
+{
+	if ( mp_viewerObject )
+	{
+		mp_viewerObject->UnloadModel();
+		delete mp_viewerObject;
+		mp_viewerObject = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CViewer::v_start_cb ( void )
+{
+	Inp::Manager* inp_manager = Inp::Manager::Instance();
+	Mlp::Manager* mlp_manager = Mlp::Manager::Instance();
+
+	#ifdef		__USE_PROFILER__	
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ProfilerHeap());
+	#else
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+	#endif
+
+	// Sorry Steve, but we need the shift input handler on the CD, so I'm going
+	// to have to start this module all the time.
+	// but on the CD, we don't initialize any of the actual view stuff
+	if (!Config::CD())
+	{
+		
+		
+	
+		mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_QUICKVIEW, 
+											s_handle_quickview, 
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN );
+		mp_server->m_Dispatcher.AddHandler(	Net::vMSG_ID_UPDATE_MATERIAL, 
+											s_handle_update_material, 
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN );
+		mp_server->m_Dispatcher.AddHandler(	Net::vMSG_ID_REMOTE_Q, 
+											s_handle_rq, 
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN );
+		mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_INCREMENTAL_UPDATE,
+											s_handle_incremental_update,
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN );
+		mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_LOAD_MODEL, 
+											s_handle_load_model,
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+		mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_UNLOAD_MODEL, 
+											s_handle_unload_model,
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+		mp_server->m_Dispatcher.AddHandler(	Net::vMSG_ID_RUN_SCRIPT_COMMAND, 
+											s_handle_run_script_command, 
+											Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	
+	
+	
+		Dbg_Printf( "Starting Viewer Module....\n" );
+	}	
+
+	// We also add the regular input handler and logic handler on CD
+	mlp_manager->AddLogicTask( *mp_logic_task );
+	inp_manager->AddHandler( *mp_input_handler );
+	
+	// The "shift_input" handler and logic are just for the user-select buttons
+	// specifically the screenshot (Select+Square)
+	// generally they are disabled. 
+	// see buttonscripts.q for the scripts that get run
+
+	printf("Adding shift input handlers\n)");	
+	inp_manager->AddHandler( *mp_shift_input_handler );
+	mlp_manager->AddLogicTask( *mp_shift_logic_task );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CViewer::v_stop_cb ( void )
+{   
+	if ( mp_viewerObject )
+	{
+    	delete mp_viewerObject;
+		mp_viewerObject = NULL;
+	}
+    
+	mp_input_handler->Remove();
+	mp_logic_task->Remove();
+	mp_shift_logic_task->Remove();
+	mp_shift_input_handler->Remove();
+
+	mp_server->m_Dispatcher.Deinit();
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+CViewer::CViewer( void )
+{
+	if (!Config::CD())
+	{
+		uint32 local_ip;
+
+		Net::Manager * net_man = Net::Manager::Instance();
+			
+#ifdef __PLAT_NGC__
+		SOInAddr local_addr;
+		SOInetAtoN( net_man->GetLocalIP(), &local_addr );
+		local_ip = local_addr.addr;
+#else
+		local_ip = inet_addr( net_man->GetLocalIP());
+#endif
+		net_man->NetworkEnvironmentSetup();
+		mp_server = net_man->CreateNewAppServer( 0, "Skate4 Viewer", 4, Net::vEXPORT_COMM_PORT,
+													inet_addr( net_man->GetLocalIP()), Net::App::mACCEPT_FOREIGN_CONN );
+	
+		Dbg_Assert( mp_server );
+	}	
+
+
+	int viewer_controller = Script::GetInt(CRCD(0x702247a5,"Viewer_controller"), false);
+
+	mp_logic_task = new Tsk::Task< CViewer > ( CViewer::s_logic_code, *this );
+	mp_input_handler = new Inp::Handler< CViewer > ( viewer_controller,  s_input_logic_code, *this, Tsk::BaseTask::Node::vNORMAL_PRIORITY );
+	
+	s_movement_vel = 400.0f;	// default movement velocity in inches per second
+	s_rotate_vel = 120.0f;		// 120 degrees per second. In 3 seconds you should complete 1 revolution
+
+	printf("Creating shift input handlers\n)");	
+	mp_shift_logic_task = new Tsk::Task< CViewer > ( CViewer::s_shift_logic_code, *this );
+	mp_shift_input_handler = new Inp::Handler< CViewer > ( viewer_controller,  CViewer::s_shift_input_logic_code, *this, Tsk::BaseTask::Node::vHANDLER_PRIORITY_VIEWER_SHIFT_INPUT_LOGIC );
+
+	Dbg_Assert( !sp_viewer );
+	sp_viewer = this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewer::~CViewer( void )
+{
+	Dbg_Assert( mp_server );
+
+	delete mp_server;
+	delete mp_input_handler;
+	delete mp_logic_task;
+	delete mp_shift_logic_task;
+	delete mp_shift_input_handler;
+
+	Dbg_Assert( sp_viewer );
+	sp_viewer = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CViewerObject* CViewer::GetViewerObject()
+{
+	return mp_viewerObject;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CViewer::sSetViewMode(int viewMode)
+{
+	s_view_mode = viewMode;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void    CViewer::sSetMovementVelocity( float in_per_sec )
+{
+	s_movement_vel = in_per_sec;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				 
+void    CViewer::sSetRotateVelocity( float deg_per_sec )
+{
+	s_rotate_vel = deg_per_sec;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				 
+bool	CViewer::ResetCameraToViewerObject()
+{
+	if ( !Script::GetInteger( CRCD(0xc965e06f,"ResetCameraToViewerObject"), Script::NO_ASSERT ) )
+	{
+		return true;
+	}
+
+//	Gfx::Camera* pCamera = Nx::CViewportManager::sGetCamera(0);
+	Obj::CCompositeObject * p_obj = (Obj::CCompositeObject *) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0xeb17151b,"viewer_cam"));
+
+	if ( p_obj )
+	{
+		// ideally we would want to hide the level/sky when
+		// switching to the object viewer, but for now,
+		// just put the viewer object in the sky...
+
+		// set the camera
+		Mth::Matrix ident;
+		ident.Ident();
+		p_obj->SetMatrix( ident );
+
+		// the viewer object gets placed at (0, 1000, 0),
+		// so put the camera slightly offset from here
+		p_obj->SetPos( Mth::Vector(0.0f, 35.0f, 100.0f) );
+	}
+
+	return true;
+}			
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				 
+CViewer* CViewer::sGetViewer()
+{
+	return sp_viewer;
+}
+							  
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				 
+void CViewer::SetCommand( const uint32 command )
+{
+	m_commands.SetMask( command );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				 
+} // namespace Mdl
+
diff --git a/Code/Sk/Modules/Viewer/Viewer.h b/Code/Sk/Modules/Viewer/Viewer.h
new file mode 100644
index 0000000..9ab368b
--- /dev/null
+++ b/Code/Sk/Modules/Viewer/Viewer.h
@@ -0,0 +1,178 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2001 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Skate4													**
+**																			**
+**	Module:			Viewer			 										**
+**																			**
+**	File name:		Viewer.h												**
+**																			**
+**	Created by:		11/28/01	-	SPG										**
+**																			**
+**	Description:	Viewer module		 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __SK_MODULES_VIEWER_H__
+#define __SK_MODULES_VIEWER_H__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+    class CViewerObject;
+    class CCompositeObject;
+};
+
+namespace Script
+{
+	class CStruct;
+};
+			  
+namespace Mdl
+{
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CViewer : public Module
+{
+public:
+	CViewer( void );
+	~CViewer( void );
+
+	static int	sGetViewMode() {return s_view_mode; }
+	static void	sSetViewMode(int viewMode);
+	static void	sSetMovementVelocity( float in_per_sec );
+	static void	sSetRotateVelocity( float deg_per_sec );
+	
+	// GJ:  viewer mode stuff...
+	// there's probably a better way to do this...
+	static CViewer*		sGetViewer();
+	bool				ResetCameraToViewerObject();
+	void				AddViewerObject( Script::CStruct* pParams );
+	void				RemoveViewerObject();
+	Obj::CViewerObject*	GetViewerObject();
+	static CViewer*		sp_viewer;
+	void				SetCommand( const uint32 command );
+
+	enum ECommands
+	{
+		mSTRAFE_UP          = nBit( 1 ),
+		mSTRAFE_DOWN        = nBit( 2 ),
+		mLIGHT_BK			= nBit( 3 ),
+		mLIGHT_FW			= nBit( 4 ),
+		mLIGHT_LT			= nBit( 5 ),
+		mLIGHT_RT			= nBit( 6 ),
+		mVIEW_FRONT         = nBit( 7 ),
+		mVIEW_LEFT          = nBit( 8 ),
+		mVIEW_RIGHT         = nBit( 9 ),
+		mVIEW_TOP           = nBit( 10 ),
+		mRESET              = nBit( 11 ),
+		mCHANGE_SCREEN_MODE = nBit( 12 ),
+		mTOGGLE_RENDER_MODE = nBit( 13 ),
+		mTOGGLE_ROTATE_MODE	= nBit( 14 ),
+		mRESET_ROTATIONS	= nBit( 15 ),
+		mROLL_LEFT          = nBit( 16 ),
+		mROLL_RIGHT         = nBit( 17 ),
+	};
+
+	enum ERotationMode
+	{
+		vROTATE_CAMERA,
+		vROTATE_OBJECTS
+	};
+
+private:
+	void			v_start_cb ( void );
+	void			v_stop_cb ( void );
+	
+	static 			Net::MsgHandlerCode				s_handle_quickview;
+	static 			Net::MsgHandlerCode				s_handle_incremental_update;
+	static 			Net::MsgHandlerCode				s_handle_update_material;
+	static 			Net::MsgHandlerCode				s_handle_rq;
+	static 			Net::MsgHandlerCode				s_handle_load_model;
+	static 			Net::MsgHandlerCode				s_handle_unload_model;
+	static 			Net::MsgHandlerCode				s_handle_run_script_command;
+	
+	static			Tsk::Task< CViewer >::Code		s_logic_code;       
+	static			Tsk::Task< CViewer >::Code		s_shift_logic_code;       
+	
+	static			Inp::Handler< CViewer >::Code	s_input_logic_code;
+	static			Inp::Handler< CViewer >::Code	s_shift_input_logic_code;
+	static			Inp::Handler< CViewer >::Code	s_shift_input_logic_code2;
+	
+	static 			void							s_translate_local( Obj::CCompositeObject* p_obj, const Mth::Vector& vec );
+	
+
+	Inp::Handler< CViewer >*	mp_input_handler;
+	Inp::Handler< CViewer >*	mp_shift_input_handler;      
+	Tsk::Task< CViewer >*		mp_logic_task;
+	Tsk::Task< CViewer >*		mp_shift_logic_task;	
+
+	// Input
+    Mth::Vector		m_right;
+    Mth::Vector		m_left;
+    Flagsm_commands;
+	Flags     m_shift_commands;
+	ERotationMode	m_rotation_mode;
+	static float	s_movement_vel;	// in inches per sec
+	static float	s_rotate_vel;	// in degrees per sec
+	static int		s_view_mode;	// 0 if inactive
+	
+	Net::Server*	mp_server;
+
+    Obj::CViewerObject*    mp_viewerObject;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+CViewer* GetViewer();
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mdl
+
+#endif	// __SK_MODULES_VIEWER_H__
+
+
+
diff --git a/Code/Sk/Ngps/crt0.s b/Code/Sk/Ngps/crt0.s
new file mode 100644
index 0000000..8132554
--- /dev/null
+++ b/Code/Sk/Ngps/crt0.s
@@ -0,0 +1,275 @@
+
+/* SCEI CONFIDENTIAL
+ "PlayStation 2" Programmer Tool Runtime Library Release 2.4
+ */
+/*
+ *                      Emotion Engine Library
+ *                          Version 1.10
+ *                           Shift-JIS
+ *
+ *      Copyright (C) 1998-1999 Sony Computer Entertainment Inc.
+ *                        All Rights Reserved.
+ *
+ *                       libkernel - crt0.s
+ *                        kernel libraly
+ *
+ *       Version        Date            Design      Log
+ *  --------------------------------------------------------------------
+ *       1.10           Oct.12.1999     horikawa    renewal
+ *       1.50           May.16.2000     horikawa
+ */
+
+/* Ken: Added so that I can use register names like a0 rather than $4	*/
+#include "C:\usr\local\sce\iop\gcc\mipsel-scei-elfl\include\cpureg.h"
+
+#define DS_SOUND
+
+#ifdef __mips16
+	.set nomips16	/* This file contains 32 bit assembly code. */
+#endif
+
+#define	ARG_SIZ     256 + 16*4 + 1*4
+
+         _stack	=		_std_stack
+         
+	.set noat
+    	.set noreorder
+	.global ENTRYPOINT
+	.global _start
+	.ent	_start
+	.text				# 0x00200000
+	nop
+	nop
+ENTRYPOINT:
+_start:
+#if defined(DS_SOUND)
+	/* $4 = Âè 1 °ú¿ô¡Ê¥ì¥¸¥¹¥¿ÅϤ·¡Ë*/
+   	lui	$2, %hi(_args_ptr)
+   	addiu	$2, $2, %lo(_args_ptr)
+    	sw	$4, ($2)
+#endif
+/*
+ * clear .bss
+ */
+zerobss:
+	lui	$2, %hi(_fbss)
+	lui	$3, %hi(_end)
+	addiu	$2, $2, %lo(_fbss)
+	addiu	$3, $3, %lo(_end)
+1:
+	sq	$0, ($2)
+	nop
+	sltu	$1, $2, $3
+	nop
+	nop
+	bne	$1, $0, 1b
+	addiu	$2, $2, 16
+
+/*
+ * initialize main thread
+ */
+	lui	$4, %hi(_gp)
+	lui	$5, %hi(_stack)
+	lui	$6, %hi(_stack_size)
+	lui	$7, %hi(_args)
+	lui	$8, %hi(_root)
+	addiu	$4, $4, %lo(_gp)
+	addiu	$5, $5, %lo(_stack)
+	addiu	$6, $6, %lo(_stack_size)
+	addiu	$7, $7, %lo(_args)
+	addiu	$8, $8, %lo(_root)
+	move	$28, $4
+	addiu	$3, $0, 60
+	syscall
+	move	$29, $2
+
+/*
+ * initialize heap area
+ */
+	lui	$4, %hi(_end)
+	lui	$5, %hi(_heap_size)
+	addiu	$4, $4, %lo(_end)
+	addiu	$5, $5, %lo(_heap_size)
+	addiu	$3, $0, 61
+	syscall
+
+/*
+ *	Ken addition.
+ *	Detect whether we are running on a ProView system by seeing whether 
+ *	snputs works.
+ */
+
+	jal sceScfGetLanguage		# snputs will always fail until this is called, for some reason.
+	nop
+	
+	la a0,TestStringForSnputs
+	jal snputs
+	nop
+
+	bltz v0,notproview			# If the return value is -1, it is not a proview.
+	nop
+
+/*
+ *	Now we know we're running on a ProView, so run the startup code copied from
+ *	the ProView crt0.s
+ */
+
+	
+/*
+ * flush data cache
+ */
+	jal	FlushCache
+	move	$4, $0
+
+/*
+ * call main program
+ */
+	ei
+
+/*
+ * This is a Neversoft addition, we need to call pre_main to set up the mem manager & debug stuff
+ */ 
+    jal pre_main
+	nop
+	
+#if defined(DS_SOUND)	
+ 	lui	$2, %hi(_args_ptr)
+ 	addiu	$2, $2, %lo(_args_ptr)
+
+ 	lw	$3, ($2)
+	
+ 	beq	$3, $0, _skipArgV
+ 	nop
+
+ 	addiu	$2, $3, 4
+	b	_run	
+	nop
+
+_skipArgV:
+	lui	$2, %hi(_args)
+	addiu	$2, $2, %lo(_args)
+
+#else
+	lui	$2, %hi(_args)
+	addiu	$2, $2, %lo(_args)
+#endif
+
+_run:
+
+	lw	$4, ($2)
+	jal	main
+	addiu	$5, $2, 4
+
+/*
+ * This is a Neversoft addition, we call post_main at the end
+ */ 
+    jal post_main
+	nop
+
+#if defined(DS_SOUND)
+	j	_root
+	nop
+#else
+	j	Exit
+	move	$4, $2
+#endif
+
+		
+notproview:
+
+/*
+ * initialize System
+ */
+	jal	_InitSys
+	nop
+
+/*
+ * flush data cache
+ */
+	jal	FlushCache
+	move	$4, $0
+
+/*
+ * call main program
+ */
+	ei
+			
+/*
+ * This is a Neversoft addition, we need to call pre_main to set up the mem manager & debug stuff
+ */ 
+    jal pre_main
+	nop
+        
+	lui	$2, %hi(_args)
+	addiu	$2, $2, %lo(_args)
+	lw	$4, ($2)
+	jal	main
+	addiu	$5, $2, 4
+
+/*
+ * This is a Neversoft addition, we call post_main at the end
+ */ 
+    jal post_main
+	nop
+        
+	j	Exit
+	move	$4, $2
+	.end	_start
+
+/**************************************/
+	.align	3
+	.global	_exit
+	.ent	_exit
+_exit:
+#if defined(DS_SOUND)
+	j	_root
+	nop
+#else
+	j	Exit			# Exit(0);
+	move	$4, $0
+#endif
+	.end	_exit
+    
+	.align	3
+	.ent	_root
+_root:
+#if defined(DS_SOUND)
+/*
+ *	K: If not ProView, don't do the next bit, or it will crash.
+ */
+	la a0,TestStringForSnputs
+	jal snputs
+	nop
+	bltz v0,notproviewsogotoexitthread	# If the return value is -1, it is not a proview.
+	nop
+
+	lui	$2, %hi(_args_ptr)
+	addiu	$2, $2, %lo(_args_ptr)
+	lw	$3, ($2)
+	jal	SignalSema
+	lw	$4, ($3)
+	
+notproviewsogotoexitthread:	
+#endif
+	addiu	$3, $0, 35		# ExitThread();
+	syscall
+	.end	_root
+
+	.bss
+	.align	6
+_args: .space	ARG_SIZ
+
+#if defined(DS_SOUND)
+	.data
+_args_ptr:
+	.space 4
+#endif
+
+
+
+/*
+ * Ken: This is used by ProView detection code
+ */
+	.section	.rodata
+TestStringForSnputs:   .string "crt0.s detected ProView ...\n"
+
diff --git a/Code/Sk/Objects/FollowOb.h b/Code/Sk/Objects/FollowOb.h
new file mode 100644
index 0000000..6ab573d
--- /dev/null
+++ b/Code/Sk/Objects/FollowOb.h
@@ -0,0 +1,92 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/followob.h    									**
+**																			**
+**	Created: 		05/02/02	-	ksh										**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_FOLLOWOB_H
+#define __OBJECTS_FOLLOWOB_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __CORE_MATH_VECTOR_H
+#include 
+#endif
+
+//#include 
+
+//#include 
+
+//#include 
+
+//#include 
+//#include 
+
+//#include 
+
+//#include 
+//#include 
+
+//#include 
+
+//#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+#define MAX_FOLLOWOB_PATH_POINTS 64
+class  CFollowOb  : public Spt::Class
+{
+	Mth::Vector mp_path_points[MAX_FOLLOWOB_PATH_POINTS];
+	int m_num_path_points;
+	int m_most_recent_path_point;
+	
+	uint32 m_leader_name;
+	Mth::Vector m_leader_pos;
+	float m_distance;
+	bool m_orient_y;
+	
+public:
+	CFollowOb();
+	~CFollowOb();
+	
+	void	Reset();
+	void	SetLeaderName(uint32 name) {m_leader_name=name;}
+	void	SetDistance(float distance) {m_distance=distance;}
+	
+	void	GetNewPathPointFromObjectBeingFollowed();
+	void	CalculatePositionAndOrientation(Mth::Vector *p_pos, Mth::Matrix *p_orientation);
+	void	OrientY() {m_orient_y=true;}
+	void	DoNotOrientY() {m_orient_y=false;}
+};
+
+} // namespace Obj
+
+#endif	// __OBJECTS_FOLLOWOB_H
+
diff --git a/Code/Sk/Objects/GameObj.cpp b/Code/Sk/Objects/GameObj.cpp
new file mode 100644
index 0000000..1db4187
--- /dev/null
+++ b/Code/Sk/Objects/GameObj.cpp
@@ -0,0 +1,223 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       gameobj.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/02/2000
+//****************************************************************************
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include   // needs some stupid special setup
+
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Obj
+{	
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+#define		OLD_GO_SYSTEM  0
+
+Obj::CCompositeObject* create_game_obj( CGeneralManager* p_obj_man, Script::CStruct* pNodeData, bool is_level_obj )
+{
+
+
+	#if OLD_GO_SYSTEM	
+	CMovingObject* pGameObj = new CMovingObject;
+	pGameObj->MovingObjectCreateComponents();
+	#else
+
+// Create a composite object from this node, but don't finalize it
+		Obj::CCompositeObject* pGameObj = Obj::CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(
+			Script::GetArray("gameobj_composite_structure"), pNodeData, false);
+
+//
+    CLockObjComponent* p_lockObjComponent = new CLockObjComponent;
+	pGameObj->AddComponent(p_lockObjComponent);
+
+	
+	#endif
+	
+	#if OLD_GO_SYSTEM		
+	pGameObj->SetType( SKATE_TYPE_GAME_OBJ );
+	Dbg_MsgAssert(pGameObj, ("Failed to create gameobj."));
+	p_obj_man->RegisterObject(*pGameObj);
+	SkateScript::GetPosition( pNodeData, &pGameObj->m_pos );
+	pGameObj->MovingObjectInit( pNodeData, p_obj_man );
+	#endif
+
+	Script::RunScript( CRCD(0xf3b84da6,"gameobj_add_components"), pNodeData, pGameObj );
+
+	pGameObj->Finalize();
+
+	// need to synchronize rendered model's position to initial world position
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pGameObj );
+	if ( pModelComponent )
+	{
+		pModelComponent->FinalizeModelInitialization();
+	}
+
+//	#if !OLD_GO_SYSTEM
+	// This used to be in MovingObjectInit	
+	// get any script associated with the object, and run it on the object
+	uint32 AIScriptChecksum=0;
+	if ( pNodeData->GetChecksum(CRCD(0x2ca8a299,"TriggerScript"),&AIScriptChecksum) )
+	{
+		Script::CScriptStructure *pScriptParams=NULL;
+		pNodeData->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptParams);
+		pGameObj->SwitchScript( AIScriptChecksum, pScriptParams );
+	}
+//	#endif
+
+	return pGameObj;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject* CreateGameObj( CGeneralManager* p_obj_man, Script::CStruct* pNodeData )
+{   
+	return create_game_obj( p_obj_man, pNodeData, false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CreateLevelObj( CGeneralManager* p_obj_man, Script::CStruct* pNodeData )
+{
+	if (pNodeData->ContainsFlag("Bouncy"))
+	{
+		Obj::CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(Script::GetArray("bouncy_composite_structure"), pNodeData);
+	}
+	else
+	{
+		create_game_obj( p_obj_man, pNodeData, true );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CreateParticleObject( CGeneralManager* p_obj_man, Script::CStruct* pNodeData )
+{
+	if( pNodeData->ContainsComponentNamed( "HasMotion" ))
+	{
+		Obj::CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(
+					Script::GetArray("moving_particle_composite_structure"), pNodeData);
+	}
+	else
+	{
+		Obj::CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(
+					Script::GetArray("particle_composite_structure"), pNodeData);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CreateParticleEmitter( CGeneralManager* p_obj_man, Script::CStruct* pNodeData )
+{
+	Dbg_MsgAssert( pNodeData, ( "Couldn't find node script." ) );
+
+	// Run the trigger script, and this will create the game object
+	// then need to find the game object	
+	uint32 AIScriptChecksum=0;
+	pNodeData->GetChecksum( "TriggerScript", &AIScriptChecksum, true );
+	uint32 NameChecksum=0;
+	pNodeData->GetChecksum( "Name", &NameChecksum, true );
+	if ( AIScriptChecksum )
+	{
+		// Make sure we don't have an unnamed particle system already
+		const uint32 PARTICLE_UNNAMED = 0x502335a1;   // Particle_UnNamed			
+		Dbg_MsgAssert(!Nx::CEngine::sGetParticleTable()->GetItem(PARTICLE_UNNAMED),("Particle_UnNamed should not exist"));		
+			
+		// Get the parameters for the TriggerScript, and run it
+		// this should create a particle system
+		Script::CScriptStructure *pScriptParams=NULL;
+		pNodeData->GetStructure("Params",&pScriptParams);
+		Script::RunScript(AIScriptChecksum,pScriptParams);		// this will create the particle emitter
+		
+		// Get the newly created particle system
+		Nx::CParticle *p_system = Nx::CEngine::sGetParticleTable()->GetItem(PARTICLE_UNNAMED);
+		Dbg_MsgAssert(p_system,("Script did not create Particle_UnNamed!"));
+
+		// Remove the entry from the Hash table			
+		Nx::CEngine::sGetParticleTable()->FlushItem(PARTICLE_UNNAMED);
+
+		// Add it back with the name as specifed in this node			
+		Nx::CEngine::sGetParticleTable()->PutItem(NameChecksum,p_system);
+																  
+		p_system->set_checkum(NameChecksum);																  					   
+		p_system->SetID(NameChecksum);																  					   
+		   
+		Mth::Vector	angles;
+		Mth::Vector dir (0.0f,1.0f,0.f);
+		pNodeData->GetVector("Angles",&angles);
+		printf("Angles(%f,%f,%f)\n",angles[X],angles[Y],angles[Z]);
+		// 3DSMAX_ANGLES Mick: 3/19/03 - Changed all rotation orders to X,Y,Z
+		dir.RotateX(angles[X]);
+		dir.RotateY(angles[Y]);
+		dir.RotateZ(angles[Z]);
+		
+		p_system->set_emit_target( dir[X], dir[Y], dir[Z] );
+
+		// Register it with the object manager.....
+		// This means that the system can be refered to like a regular moving object
+		p_obj_man->RegisterObject(*p_system);
+			   
+		p_system->MovingObjectInit( pNodeData, p_obj_man ); 	// get LOD and suspend info
+		
+		// Bastard crap code.... Since I set them up to be objects
+		// they now run their scripts with the new component system
+		// meaning the scripts run twice, dammit...												
+		p_system->GetScript()->ClearScript();
+		p_system->GetScript()->ClearEventHandlerTable();
+	}
+}
+
+
+} // namespace Obj
+
diff --git a/Code/Sk/Objects/GameObj.h b/Code/Sk/Objects/GameObj.h
new file mode 100644
index 0000000..403b3fb
--- /dev/null
+++ b/Code/Sk/Objects/GameObj.h
@@ -0,0 +1,43 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       gameobj.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/02/2000
+//****************************************************************************
+
+#ifndef __OBJECTS_GAMEOBJ_H
+#define __OBJECTS_GAMEOBJ_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+namespace Script
+{
+	class CStruct;
+}
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+	class CCompositeObject;
+	class CGeneralManager;
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+void CreateParticleEmitter( CGeneralManager* p_obj_man, Script::CStruct* pNodeData );
+CCompositeObject* CreateGameObj( CGeneralManager* p_obj_man, Script::CStruct* pNodeData );
+void CreateLevelObj( CGeneralManager* p_obj_man, Script::CStruct* pNodeData );
+void CreateParticleObject( CGeneralManager* p_obj_man, Script::CStruct* pNodeData );
+
+} // namespace Obj
+
+#endif	// __OBJECTS_GAMEOBJ_H
diff --git a/Code/Sk/Objects/MovingObject.cpp b/Code/Sk/Objects/MovingObject.cpp
new file mode 100644
index 0000000..708a15e
--- /dev/null
+++ b/Code/Sk/Objects/MovingObject.cpp
@@ -0,0 +1,871 @@
+/*
+	MovingObject.cpp
+	
+	Skaters, Peds, Cars are derived from CMovingObject.
+
+	These objects are controlled through scripts.  Script commands are
+	sent through CallMemberFunction( ).  See all available commands by
+	looking at CMovingObject::CallMemberFunction().
+*/
+
+// start autoduck documentation
+// @DOC movingobject
+// @module movingobject | None
+// @subindex Scripting Database
+// @index script | movingobject
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include  // for AddDebugLine( )
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovingObject::DrawBoundingBox( SBBox *pBox, Mth::Vector *pOffset, int numFrames, Mth::Vector *pRot )
+{
+    Gfx::AddDebugBox( m_matrix, m_pos, pBox, pOffset, numFrames, pRot);
+} 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovingObject::CMovingObject()
+{
+	// set up default bounding box
+    m_bbox.m_max.Set(10.0f, 10.0f, 10.0f);
+    m_bbox.m_min.Set(-10.0f, -10.0f, -10.0f);    
+    m_bbox.centerOfGravity.Set();			// zero center of gravity......    
+
+	// suspend component should come first, because it
+	// controls whether the rest of the components
+	// should be updated
+	CSuspendComponent* p_suspendComponent = new CSuspendComponent;
+	AddComponent(p_suspendComponent);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovingObject::~CMovingObject( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovingObject::MovingObjectCreateComponents()
+{
+	CMotionComponent* p_motionComponent = new CMotionComponent;
+	AddComponent(p_motionComponent);
+
+	CSpecialItemComponent* p_specialItemComponent = new CSpecialItemComponent;
+	AddComponent(p_specialItemComponent);
+
+	CSkaterProximityComponent* p_skaterProximityComponent = new CSkaterProximityComponent;
+	AddComponent( p_skaterProximityComponent );
+
+	CSoundComponent* p_soundComponent = new CSoundComponent;
+	AddComponent(p_soundComponent);
+	
+	CStreamComponent* p_streamComponent = new CStreamComponent;
+	AddComponent(p_streamComponent);
+	
+    CLockObjComponent* p_lockObjComponent = new CLockObjComponent;
+	AddComponent(p_lockObjComponent);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovingObject::MovingObjectInit( Script::CStruct* pNodeData, CGeneralManager* p_obj_man )
+{
+	// Dan: a hack for now to detect whether anyone has called CreateComponents() yet or not
+	// once components are created in a consistent manner across object types, we can remove this sort of foolishness
+	if (!GetMotionComponent())
+	{
+		MovingObjectCreateComponents();
+	}
+	
+	Dbg_MsgAssert( GetMotionComponent(), ( "Motion component doesn't exist yet" ) );
+	
+	GetMotionComponent()->OrientToNode( pNodeData );
+	
+	//----------------------------------------
+	// MOVED FROM POSOBJECT.H
+	Dbg_MsgAssert( pNodeData, ( "Couldn't find node structure." ));
+	
+	// Get the checksum ...
+	uint32	NodeNameChecksum = 0;
+	pNodeData->GetChecksum(CRCD(0xa1dc81f9,"Name"),&NodeNameChecksum);
+	if (NodeNameChecksum)
+	{
+		SetID(NodeNameChecksum);
+	}	
+	
+	uint32 AIScriptChecksum=0;
+	if ( pNodeData->GetChecksum(CRCD(0x2ca8a299,"TriggerScript"),&AIScriptChecksum) )
+	{
+		Script::CScriptStructure *pScriptParams=NULL;
+		pNodeData->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptParams);
+		SwitchScript( AIScriptChecksum, pScriptParams );
+	}
+	//----------------------------------------
+	
+	GetMotionComponent()->InitFromStructure( pNodeData );
+	GetSuspendComponent()->InitFromStructure( pNodeData );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::CSkeleton* CMovingObject::GetSkeleton()
+{
+	Obj::CSkeletonComponent* p_skeleton_component = GetSkeletonComponent();
+	if ( p_skeleton_component )
+	{
+		return p_skeleton_component->GetSkeleton();
+	}
+
+    return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Nx::CModel* CMovingObject::GetModel()
+{
+	Obj::CModelComponent* p_model_component = GetModelComponent();
+    if ( p_model_component )
+    {
+        return p_model_component->GetModel();
+    }
+    
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::LookAtObject_Init( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 typeChecksum;
+	if ( pParams->GetChecksum( 0x7321a8d6, &typeChecksum ) ) // "type"
+	{
+		int type;
+		switch ( typeChecksum )
+		{
+			case 0x88c21962: // car
+				type = SKATE_TYPE_CAR;
+				break;
+			case 0x61a741e: // ped
+				type = SKATE_TYPE_PED;
+				break;
+			case 0x5b8ab877: // skater
+				type = SKATE_TYPE_SKATER;
+				break;
+			case 0x3b5737a6:  // gameobj
+				type = SKATE_TYPE_GAME_OBJ;
+				break;
+			default:
+				Dbg_MsgAssert( 0,( "\n%s\nUnknown type %s", pScript->GetScriptInfo( ), Script::FindChecksumName( typeChecksum ) ));
+				return false;
+				break;
+		}
+		CCompositeObject* pClosestObject = GetClosestObjectOfType( type );
+		if ( !pClosestObject )
+		{
+			Dbg_Message( "\n%s\nWarning:  Looking at object that doesn't exist.", pScript->GetScriptInfo( ) );
+			return false;
+		}
+		return GetMotionComponent()->LookAt_Init( pParams, pClosestObject->m_pos );
+	}
+	
+	// find a named object:
+	uint32 nameChecksum;
+	if ( pParams->GetChecksum( 0xa1dc81f9, &nameChecksum ) ) // "name"
+	{
+		CCompositeObject* pNext;
+		CCompositeObject* pObj;
+		Lst::Search< CObject >	sh;
+		Dbg_MsgAssert(mp_manager,("NULL mp_Manager in MovingObject"));
+		pNext = (Obj::CCompositeObject*) sh.FirstItem( mp_manager->GetRefObjectList() );
+		while ( pNext )
+		{
+			Dbg_AssertType( pNext, CCompositeObject );
+			pObj = pNext;
+			pNext = (Obj::CCompositeObject*) sh.NextItem();
+			// find objects spawned from this node, return true if out of this radius...
+			if ( ( pObj != this ) && ( pObj->GetID() == nameChecksum ) )
+			{
+				return GetMotionComponent()->LookAt_Init( pParams, pObj->m_pos );
+			}
+		}
+		Dbg_Message( "\n%s\nWarning:  Looking at object that doesn't exist.", pScript->GetScriptInfo( ) );
+		return false;
+	}
+	Dbg_Message( "\n%s\nWarning: No object specified for Obj_LookAtObject", pScript->GetScriptInfo( ) );
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::ObjectFromNodeWithinRange( int nodeIndex, int radiusSqr, SiteBox* pBox )
+{
+	uint32	nodeNameChecksum = SkateScript::GetNodeNameChecksum(nodeIndex);
+
+// TODO:  Since there can only be one object from a node, then should be able to get it directly
+	
+	bool retVal = false;
+	CMovingObject* pNext;
+	CMovingObject* pObj;
+	Lst::Search< CObject >	sh;
+//	Dbg_MsgAssert(mp_manager,("NULL mp_Manager in MovingObject. Node Checksum = 0x%x (%s)",GetID(),Script::FindChecksumName(GetID())));
+	Dbg_MsgAssert(mp_manager,("NULL mp_Manager in MovingObject. Node Checksum = 0x%x (%s)",GetID(),Script::FindChecksumName(GetID())));
+	pNext = (Obj::CMovingObject *) sh.FirstItem( mp_manager->GetRefObjectList() );
+	while ( pNext )
+	{
+		Dbg_AssertType( pNext, CMovingObject );
+		pObj = (Obj::CMovingObject *) pNext;
+		pNext = (Obj::CMovingObject *) sh.NextItem();
+		
+		if ( ( pObj->GetID() == nodeNameChecksum ) && ( pObj != this ) )
+		{
+			if ( pBox )
+			{
+				if ( ObjInSiteBox( pObj, pBox ) )
+				{
+					retVal = true;
+				}
+			}
+			else if ( Mth::DistanceSqr( pObj->m_pos, m_pos ) < radiusSqr )
+			{
+				retVal = true;
+			}
+		}
+	}
+	return ( retVal );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CMovingObject::GetSiteBoxMaxRadiusSquared( SiteBox* pBox )
+{
+	float maxBoxDist;
+	if ( pBox->maxDistInitialized )
+	{
+		return ( pBox->maxDistSquared );
+	}
+	maxBoxDist = fabsf( pBox->dist );
+	if ( fabsf( pBox->height ) > maxBoxDist )
+	{
+		maxBoxDist = fabsf( pBox->height );
+	}
+	if ( fabsf( pBox->width ) > maxBoxDist )
+	{
+		maxBoxDist = fabsf( pBox->width );
+	}
+
+	float maxOffset;
+	Mth::Vector* pOffset;
+	pOffset = &pBox->bbox.centerOfGravity;
+	maxOffset = fabsf( pOffset->GetZ( ) );
+	if ( fabsf( pOffset->GetY( ) ) > maxOffset )
+	{
+		maxOffset = fabsf( pOffset->GetY( ) );
+	}
+	if ( fabsf( pOffset->GetX( ) ) > maxOffset )
+	{
+		maxOffset = fabsf( pOffset->GetX( ) );
+	}
+
+	pBox->maxDistSquared = maxOffset + maxBoxDist;
+	pBox->maxDistSquared *= pBox->maxDistSquared;
+	pBox->maxDistInitialized = true;
+	return ( pBox->maxDistSquared );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovingObject::InitializeSiteBox( SiteBox* pBox )
+{
+	if ( pBox->initialized )
+	{
+		// already initialized!
+		return;
+	}
+	
+	pBox->matrix = m_matrix;
+	if ( pBox->angle )
+	{
+		pBox->matrix = pBox->matrix.RotateYLocal( pBox->angle );
+	}
+	pBox->matrix.Invert( );
+	
+	SBBox* pBBox = &pBox->bbox;
+	// start by setting min/max to half the dimentions:
+	pBBox->m_max.Set( pBox->width / 2.0f, pBox->height / 2.0f, pBox->dist );
+	pBBox->m_min = -pBBox->m_max;
+	pBBox->m_min[ Z ] = 0.0f;
+
+	// using the vector in pBox->bbox.centerOfGravity to store offset
+	// sent by the player! MISNOMER:
+	pBBox->m_max += pBBox->centerOfGravity;
+	pBBox->m_min += pBBox->centerOfGravity;
+		
+	// gots ta move the site box up so that it comes from the object's
+	// center of gravity (as the object's position isn't usually the center
+	// of gravity):
+/*	if ( mp_model )
+	{
+		//Mth::Vector temp3;
+		//temp3 = m_pos + centerOfGravity;		
+		//Gfx::AddDebugLine( m_pos, temp3 );
+		pBBox->m_max += m_bbox.centerOfGravity;
+		pBBox->m_min += m_bbox.centerOfGravity;
+	}*/
+
+#ifndef __PLAT_NGC__	
+#ifdef __NOPT_ASSERT__	
+	if ( pBox->debug )
+	{
+		pBBox->centerOfGravity.Set( ); // zero that out for the render...
+		Mth::Vector drawOffset;
+//		drawOffset.Set( 0, m_ground_offset, 0 );
+		drawOffset.Set( 0, 0, 0 );
+		if ( pBox->angle )
+		{
+			Mth::Vector angles;
+			angles.Set( 0, pBox->angle, 0 );
+			DrawBoundingBox( pBBox, &drawOffset, 10, &angles );
+		}
+		else
+		{
+			DrawBoundingBox( pBBox, &drawOffset, 10 );
+		}
+	}
+#endif
+#endif
+	
+	// Phew... now we can quickly check objects by rotating their position
+	// into our world, and seeing if they're in the motherfuckin' box:
+	pBox->initialized = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::ObjInSiteBox( CMovingObject* pObj, SiteBox* pBox )
+{
+	// K: There was a bug whereby the initial distance check below was not taking
+	// into account the offset of the box.
+	// The offset is stored in centerOfGravity which may get zeroed in InitializeSiteBox,
+	// so store it here.
+	Mth::Vector off=pBox->bbox.centerOfGravity;
+	
+	// initialize the site box and the BBox contained therein:
+	InitializeSiteBox( pBox );
+
+	// sorta trivial radius check and return...
+	float distSquared;
+	distSquared = Mth::DistanceSqr( pObj->m_pos+off, m_pos );
+	float boxRadiusSquared = GetSiteBoxMaxRadiusSquared( pBox );
+	if ( distSquared > boxRadiusSquared )
+	{
+		return false;
+	}
+
+	Mth::Vector objPos;
+	objPos = pObj->m_pos;
+	
+	// center of the object...
+	objPos[ Y ] += ( pObj->m_bbox.m_max[ Y ] - pObj->m_bbox.m_min[ Y ] ) / 2.0f;
+
+#ifndef __PLAT_NGC__	
+#ifdef __NOPT_ASSERT__
+	Mth::Vector preTransform = objPos;
+#endif	
+#endif	
+
+	objPos -= m_pos;
+	// now we have the head and toes at the right distance from us and from each other...
+	// rotate them into our world:
+	objPos = pBox->matrix.Transform( objPos );
+	
+#ifndef __PLAT_NGC__	
+#ifdef __NOPT_ASSERT__
+	if ( pBox->debug )
+	{
+		if ( Gfx::PointInsideBox( objPos, pBox->bbox.m_max, pBox->bbox.m_min ) )
+		{
+			Gfx::AddDebugLine( m_pos, preTransform, MAKE_RGB( 150, 50, 50 ), MAKE_RGB( 150, 50, 50 ), 10 );
+			return true;
+		}
+		else
+		{
+			Gfx::AddDebugLine( m_pos, preTransform, MAKE_RGB( 50, 50, 100 ), MAKE_RGB( 50, 50, 100 ), 10 );
+			return false;
+		}
+	}
+#endif
+#endif
+	return ( Gfx::PointInsideBox( objPos, pBox->bbox.m_max, pBox->bbox.m_min ) );
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovingObject::FillInSiteBox(SiteBox* p_siteBox, Script::CStruct* pParams)
+{
+	Dbg_MsgAssert(p_siteBox,("NULL p_siteBox"));
+	Dbg_MsgAssert(pParams,("NULL pParams"));
+	
+	p_siteBox->initialized = false;
+	p_siteBox->angle = 0;
+	p_siteBox->maxDistInitialized = false;
+	
+	// Misnomer: used for offsetting the site box...
+	// But we will want it to default to the center of gravity:
+	p_siteBox->bbox.centerOfGravity = m_bbox.centerOfGravity;
+	
+    p_siteBox->height = p_siteBox->width = p_siteBox->dist = 0.0f;
+	
+	Mth::Vector offset;
+	offset[W]=0.0f; //
+	if ( pParams->GetVector( 0xa6f5352f, &offset ) ) // offset
+	{
+		offset.FeetToInches( );
+		p_siteBox->bbox.centerOfGravity += offset;
+	}
+
+	if ( pParams->GetFloat( 0xff7ebaf6, &p_siteBox->angle ) ) // angle
+	{
+		p_siteBox->angle = DEGREES_TO_RADIANS( p_siteBox->angle );
+	}
+
+	if ( pParams->GetFloat( 0xab21af0, &p_siteBox->height ) ) // height
+	{
+		p_siteBox->height = FEET_TO_INCHES( p_siteBox->height );
+	}
+	if ( pParams->GetFloat( 0x7e832f08, &p_siteBox->dist ) ) // dist
+	{
+		p_siteBox->dist = FEET_TO_INCHES( p_siteBox->dist  );
+	}
+	if ( pParams->GetFloat( 0x73e5bad0, &p_siteBox->width ) ) // width
+	{
+		p_siteBox->width = FEET_TO_INCHES( p_siteBox->width );
+	}
+
+	#ifdef	__NOPT_ASSERT__	
+	p_siteBox->debug = 0;
+
+	p_siteBox->debug = pParams->ContainsFlag( 0x935ab858 ); // debug
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::ObjectWithinRect( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	SiteBox siteBox;
+	FillInSiteBox(&siteBox,pParams);
+	
+	return ( ObjectWithinRange( pParams, pScript, &siteBox ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::SkaterInRange( SiteBox* pBox, float radiusSqr )
+{
+	int numSkaters = Mdl::Skate::Instance()->GetNumSkaters( );
+	
+	for ( int i = 0; i < numSkaters; i++ )
+	{
+		CMovingObject* pObj = Mdl::Skate::Instance()->GetSkater( i );
+		if ( pObj != this )
+		{
+			if ( pBox )
+			{
+				if ( ObjInSiteBox( pObj, pBox ) )
+				{
+					return true;
+				}
+			}
+			else if ( Mth::DistanceSqr( pObj->m_pos, m_pos ) < radiusSqr )
+			{
+				return true;
+			}
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::ObjTypeInRange( Script::CStruct* pParams, Script::CScript* pScript, float radiusSqr, SiteBox* pBox, uint32 typeChecksum )
+{
+	
+	int type;
+	Search objSearch;		
+	CMovingObject* pObj;
+	switch ( typeChecksum )
+	{
+		case 0x88c21962: // car
+			type = SKATE_TYPE_CAR;
+			break;
+		case 0x61a741e: // ped
+			type = SKATE_TYPE_PED;
+			break;
+		case 0x5b8ab877: // skater
+			return ( SkaterInRange( pBox, radiusSqr ) );
+			break;
+		case 0x3b5737a6: // gameobj
+			type = SKATE_TYPE_GAME_OBJ;
+			break;
+		default:
+			Dbg_MsgAssert( "\n%s\nUnknown type %s",((char *)( pScript->GetScriptInfo( ), Script::FindChecksumName( typeChecksum ) )));
+			return false;
+			break;
+	}
+	Dbg_MsgAssert(mp_manager,("NULL mp_Manager in MovingObject"));
+	pObj = (Obj::CMovingObject *) objSearch.FindFirstObjectOfType( mp_manager->GetRefObjectList(), type );
+	while ( pObj )
+	{
+		if ( pObj != this )
+		{
+			if ( pBox )
+			{
+				if ( ObjInSiteBox( pObj, pBox ) )
+				{
+					return true;
+				}
+			}
+			else if ( Mth::DistanceSqr( pObj->m_pos, m_pos ) < radiusSqr )
+			{
+				return true;
+			}
+		}
+		pObj = (Obj::CMovingObject *) objSearch.FindNextObjectOfType( );
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::ObjectWithinRange( Script::CStruct* pParams, Script::CScript* pScript, SiteBox* pBox )
+{
+	float radiusSqr = 0.0f;
+	
+	if ( !pBox )
+	{
+		if( !( pParams->GetFloat( 0xc48391a5, &radiusSqr ) ) )//"radius"
+		{
+			Dbg_MsgAssert( 0,( "\n%s\nNo radius specified in Obj_*InRadius.", pScript->GetScriptInfo( ) ));
+			return false;
+		}
+		radiusSqr = FEET_TO_INCHES( radiusSqr );
+		radiusSqr *= radiusSqr;
+	}
+
+	Script::CArray* pArray=NULL;
+	uint32 typeChecksum;
+	if ( pParams->GetChecksum( 0x7321a8d6, &typeChecksum ) ) // "type"
+	{
+		return ( ObjTypeInRange( pParams, pScript, radiusSqr, pBox, typeChecksum ) );
+	}
+	else if ( pParams->GetArray( 0x7321a8d6, &pArray, Script::ASSERT ) ) // "type"
+	{
+		Dbg_MsgAssert( pArray->GetType( ) == ESYMBOLTYPE_NAME,( "\n%s\nObjectWithinRange: Array must be of names",pScript->GetScriptInfo()));
+		uint32 i;
+		for ( i = 0; i < pArray->GetSize( ); ++i )
+		{
+			if ( ObjTypeInRange( pParams, pScript, radiusSqr, pBox, pArray->GetChecksum( i ) ) )
+			{
+				return true;
+			}
+		}
+		return false;		
+	}
+
+	// find a named object:
+	uint32 nameChecksum;
+	if ( pParams->GetChecksum( 0xa1dc81f9, &nameChecksum ) ) // "name"
+	{
+		int nodeNum = SkateScript::FindNamedNode( nameChecksum );
+		return ( ObjectFromNodeWithinRange( nodeNum, radiusSqr, pBox ) );
+	}
+    const char* pPrefix;
+	if ( pParams->GetText( 0x6c4e7971, &pPrefix ) ) // checksum 'prefix'
+	{
+		// Create with a prefix specified:
+		uint16 numNodes = 0;
+		const uint16* pMatchingNodes = SkateScript::GetPrefixedNodes( pPrefix, &numNodes );
+		int i;
+		for ( i = 0; i < numNodes; i++ )
+		{
+			if ( ObjectFromNodeWithinRange( pMatchingNodes[ i ], radiusSqr, pBox ) )
+			{
+				//printf("Oof old\n");
+				return true;
+			}
+		}
+		return false;
+	}
+	
+//	Dbg_Message( "\n%s\nWarning: Unprocessed radius ( or site box ) check... unrecognized syntax. needs 'type', 'name' or 'prefix'", pScript->GetScriptInfo( ) );
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovingObject::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{   
+	switch (Checksum)
+	{				
+		// @script | Obj_LookAtObject | 
+		// Returns true if the object will rotate itself to point in the required direction. Ie, if
+		// the object is already pointing the right way, the command will return false.
+        // @parmopt name | lockAxis | | can lock any two axes (LOCK_X, LOCK_Y, LOCK_Z)
+        // @parmopt float | time | vel/acceleration | in seconds
+        // @parmopt float | speed | 360  | angular velocity in degrees per second
+        // @parmopt float | acceleration | 0 | angular acceleration in degrees per second
+        // per second
+        // @parmopt name | name | | name of object
+        // @parmopt name | type | | type of object to look at - will look at closest if 
+        // there are several in the world
+        case 0x33e04032:  // Obj_LookAtObject
+			return LookAtObject_Init( pParams, pScript );
+			break;
+
+		case 0x37185d8a:  // Obj_AngleToNearestSkaterGreaterThan
+		{
+			int type = SKATE_TYPE_SKATER;
+			Obj::CCompositeObject* pClosestObject = GetClosestObjectOfType( type );
+			if ( !pClosestObject )
+			{
+				Dbg_Message( "\n%s\nWarning:  Checking angle to skater that doesn't exist.", pScript->GetScriptInfo() );
+				return false;
+				break;
+			}
+
+			float threshold;
+			pParams->GetFloat( NONAME, &threshold, Script::ASSERT );
+			Dbg_MsgAssert( threshold >= 0, ( "Negative angle passed to Obj_AngleToNearestSkaterGreaterThan" ) );
+			threshold = DEGREES_TO_RADIANS( threshold );
+
+			Mth::Vector pathHeading = pClosestObject->m_pos - m_pos;
+			float test = Mth::GetAngle( m_matrix, pathHeading, 2, 1 );
+			if ( fabs( test ) > threshold )
+			{
+				return true;
+				break;
+			}
+			return false;
+			break;
+		}
+
+        // @script | Obj_ObjectInRadius | 
+        // @parmopt name | name | | use one of: name, prefix, or type
+        // @parmopt name | prefix | | use one of: name, prefix, or type
+        // @parmopt name | type | | use one of: name, prefix, or type
+        // (type values are skater, ped, car)
+        // @parm float | radius | radius in feet
+		case 0x67660fe5:  // Obj_ObjectInRadius
+			return ( ObjectWithinRange( pParams, pScript ) );
+			break;
+
+        // @script | Obj_ObjectInRect | 
+        // @parmopt float | angle | 0 | in degrees - default is  
+        // along the direction the object is facing
+        // @parmopt float | height | (height of object) | 
+        // @parmopt float | distance | (length of object) | in feet - 
+        // offset by half of the object in the direction determined by the angle
+        // @parmopt float | width | (width of object) | in feet
+        // @parmopt vector | offset | | in feet - the rectangle by default will be located
+        // sticking out of the XZ plane (or XY plane in Max coords ),
+        // rotated by number of degrees in angle parameter
+		// @flag debug | just a parameter to turn on debugging info, 
+        // to quickly set up rectangles on an object.
+        // @parmopt name | name | | use one of: name, prefix, or type
+        // @parmopt name | prefix | | use one of: name, prefix, or type
+        // @parmopt name | type | | use one of: name, prefix, or type
+        // (type values are skater, ped, car)
+        case 0x6520b902: // Obj_ObjectInRect
+		{
+			return ( ObjectWithinRect( pParams, pScript ) );
+			break;
+		}
+
+        // @script | Obj_ObjectNotInRect | 
+        // @parmopt float | angle | 0 | in degrees - default is  
+        // along the direction the object is facing
+        // @parmopt float | height | (height of object) | 
+        // @parmopt float | distance | (length of object) | in feet - 
+        // offset by half of the object in the direction determined by the angle
+        // @parmopt float | width | (width of object) | in feet
+        // @parmopt vector | offset | | in feet - the rectangle by default will be located
+        // sticking out of the XZ plane (or XY plane in Max coords ),
+        // rotated by number of degrees in angle parameter
+        // @flag debug | just a parameter to turn on debugging info, 
+        // to quickly set up rectangles on an object.
+        // @parmopt name | name | | use one of: name, prefix, or type
+        // @parmopt name | prefix | | use one of: name, prefix, or type
+        // @parmopt name | type | | use one of: name, prefix, or type
+        // (type values are skater, ped, car)
+		case 0x3b6b66c3: // Obj_ObjectNotInRect
+			return ( !ObjectWithinRect( pParams, pScript ) );
+			break;
+
+
+		// @script | Obj_SetBodyShape | Sets the body shape for this object
+        // @parm structure | body_shape | a list of per-bone scales (see scaling.q for format)
+		case 0x802a7600: // Obj_SetBodyShape
+		{
+			Script::CStruct* pBodyShapeStructure;
+			pParams->GetStructure( CRCD(0x812684ef,"body_shape"), &pBodyShapeStructure, true );
+
+			if ( GetModel() )
+			{
+				Gfx::CSkeleton* pSkeleton = GetSkeleton();
+				Dbg_Assert( pSkeleton );
+				pSkeleton->ApplyBoneScale( pBodyShapeStructure );
+			
+				Mth::Vector theScale( 1.0f, 1.0f, 1.0f );
+				if ( Gfx::GetScaleFromParams( &theScale, pBodyShapeStructure ) )
+				{
+					GetModel()->SetScale( theScale );
+				}
+			}			
+		}
+		break;
+
+		default:
+			return ( CCompositeObject::CallMemberFunction( Checksum, pParams, pScript ) );
+			break;
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCompositeObject* CMovingObject::GetClosestObjectOfType( int type )
+{
+	Search objSearch;
+	CCompositeObject *pObj;		
+	pObj = (CCompositeObject *) objSearch.FindFirstObjectOfType( mp_manager->GetRefObjectList(), type );
+	if ( pObj == this )
+	{
+		if ( !( pObj = (CCompositeObject*) objSearch.FindNextObjectOfType( ) ) )
+		{
+			return ( NULL );
+		}
+	}
+	if ( !( pObj ) )
+	{
+		return ( NULL );
+	}
+	float closestDist = FEET_TO_INCHES( 666.0f * 666.0f );
+	float dist;
+	CCompositeObject* pClosestObj = pObj;
+	while ( pObj )
+	{
+		dist = Mth::Distance( pObj->m_pos, m_pos );
+		if ( ( pObj != this ) && ( dist < closestDist ) )
+		{
+			closestDist = dist;
+			pClosestObj = pObj;
+		}
+		pObj = (CCompositeObject*) objSearch.FindNextObjectOfType( );
+	}
+	return ( pClosestObj );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/MovingObject.h b/Code/Sk/Objects/MovingObject.h
new file mode 100644
index 0000000..a07d4ed
--- /dev/null
+++ b/Code/Sk/Objects/MovingObject.h
@@ -0,0 +1,159 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       MovingObject.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/25/2000
+//****************************************************************************
+
+#ifndef __OBJECTS_MOVINGOBJECT_H
+#define __OBJECTS_MOVINGOBJECT_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Gfx
+{
+	class 	CSkeleton;
+	class 	CModelAppearance;
+};
+											  
+namespace Nx
+{
+    class 	CModel;
+	class 	CCollObj;
+};
+
+namespace Obj
+{
+    class 	CFollowOb;
+	class	CRailManager;
+	class	CObjectHookManager;
+    
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+struct SiteBox
+{
+	Mth::Matrix     matrix;
+	float           height;
+	float           dist;
+	float           width;
+	float           angle;
+	SBBox           bbox;
+	bool            initialized;
+	bool            maxDistInitialized;
+	float           maxDistSquared;
+
+#ifdef __NOPT_ASSERT__	
+	bool            debug;
+#endif
+};
+
+class CMovingObject : public CCompositeObject 
+{
+public :
+		CMovingObject();
+		virtual ~CMovingObject( void );
+
+public:
+		void					MovingObjectCreateComponents();
+		void 					MovingObjectInit( Script::CStruct* pNodeData, CGeneralManager* p_obj_man );
+
+		virtual bool			CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript );
+
+		Gfx::CSkeleton*			GetSkeleton( void );
+		Nx::CModel*				GetModel( void );
+				
+protected:
+		void					FillInSiteBox(SiteBox *p_siteBox, Script::CStruct *pParams);
+		bool					ObjInSiteBox( CMovingObject *pObj, SiteBox *pBox );
+
+public:
+//		bool					IsFlipped( void );
+		void					GetHoverOrgPos( Mth::Vector *p_orgPos );
+		
+protected:
+		bool					ObjectWithinRange( Script::CStruct *pParams, Script::CScript *pScript, SiteBox *pBox = NULL );
+		bool 					ObjectWithinRect( Script::CStruct *pParams, Script::CScript *pScript );
+		bool					ObjectFromNodeWithinRange( int nodeIndex, int radiusSqr, SiteBox *pBox );
+		void					InitializeSiteBox( SiteBox *pBox );
+		float					GetSiteBoxMaxRadiusSquared( SiteBox *pBox );
+		bool					ObjTypeInRange( Script::CStruct *pParams, Script::CScript *pScript, float radiusSqr, SiteBox *pBox, uint32 typeChecksum );
+		bool					SkaterInRange( SiteBox *pBox, float radiusSqr );
+		
+protected:
+		void					DrawBoundingBox( SBBox *pBox, Mth::Vector *pOffset = NULL, int numFrames = 1, Mth::Vector *pRot = NULL );
+		
+		void					ToggleFlipState( void );
+
+protected:
+		// float			   		m_time;					// time since last frame
+  
+		// the rest of the flags stay here...
+//	    int						m_general_flags;
+		
+		// will add rotation vector (or rotation class?) to this ASAP...
+
+private:		
+		bool					LookAtObject_Init( Script::CStruct *pParams, Script::CScript *pScript );
+		void					FollowLeader_Init( Script::CStruct *pParams );
+
+		// GJ:  MOVED THESE FROM POSOBJECT.H
+public:
+		// Currently used by the PauseSkaters script command, which is used to pause all the skaters whilst
+		// a camera animation is running.
+		virtual void 			Pause() {mPaused=true; CCompositeObject::Pause(true);}
+		virtual void 			UnPause() {mPaused=false; CCompositeObject::Pause(false);}
+		bool		 			IsPaused() {return mPaused;}
+
+		SBBox					m_bbox;
+
+	protected:
+		CCompositeObject*		GetClosestObjectOfType( int type );
+
+	public :
+		// Whether the object is paused.
+		bool					mPaused;
+};
+							  
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_MOVINGOBJECT_H
diff --git a/Code/Sk/Objects/PathMan.cpp b/Code/Sk/Objects/PathMan.cpp
new file mode 100644
index 0000000..338f243
--- /dev/null
+++ b/Code/Sk/Objects/PathMan.cpp
@@ -0,0 +1,269 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+CPathObjectTracker *CPathMan::TrackObject(CCompositeObject *p_ob, int node_number)
+{
+	Script::CStruct *p_node=SkateScript::GetNode(node_number);
+	#ifdef __NOPT_ASSERT__
+	Dbg_MsgAssert(p_node,("NULL p_node for node %d",node_number));
+	#endif
+	
+	int path_number=0;
+	if (!p_node->GetInteger("PathNum",&path_number))
+	{
+		Dbg_MsgAssert(0,("Node %d does not contain a PathNum",node_number));
+	}
+		
+	Dbg_MsgAssert(path_number>=0 && path_numberGetInteger("PathNum",&path_number))
+	{
+		Dbg_MsgAssert(0,("Node %d does not contain a PathNum",node_number));
+	}
+		
+	// Dbg_MsgAssert(path_number>=0 && path_numbermNodeNameChecksum));
+
+	for (int i=0; iGetSize();
+	for ( uint16 i = 0; i < array_size; ++i )
+	{
+		Script::CStruct *p_start_node=p_node_array->GetStructure( i );
+		
+		// Rail nodes are linked into a path, but don't need path numbers, so don't bother
+		// adding them to save memory.
+		uint32 class_type=0;
+		p_start_node->GetChecksum( CRCD(0x12b4e660,"Class"), &class_type );
+		if ( class_type == CRCD( 0x8e6b02ad, "RailNode" ) ||
+			 class_type == CRCD(0x30c19600, "ClimbingNode") )
+		{
+			continue;
+		}
+
+		// make sure this isn't a pedai waypoint
+		uint32 type;
+		if ( class_type == CRCD( 0x4c23a77e, "Waypoint" )
+			 && p_start_node->GetChecksum( CRCD( 0x7321a8d6, "type" ), &type, Script::NO_ASSERT )
+			 && type == CRCD( 0xcba10ffa, "PedAI" ) )
+		{
+			continue;
+		}
+		
+		int num_links=SkateScript::GetNumLinks( p_start_node );
+		int node_path_number = 0;
+		// Check if the node is linked to something, and has not already had a PathNum
+		// given to it.
+		if ( ( num_links || class_type == CRCD( 0x4c23a77e, "WayPoint" ) ) && 
+			!p_start_node->ContainsComponentNamed( CRCD( 0x9c91c3ca, "PathNum" ) ) )
+		{
+			// printf("found a non PedAI waypoint\n");
+			// The node and subsequent nodes that it is linked to require a PathNum				
+			Script::CStruct *p_node=p_start_node;
+			
+			// The path number that is going to be written in is m_num_paths to start with,
+			// but that might change if we hit a node that already has a path number. In that
+			// case we'll rewind and write that number in instead.
+			int path_number_to_write=m_num_paths;
+			
+			while (true)
+			{
+				if (p_node->GetInteger( CRCD( 0x9c91c3ca, "PathNum" ),&node_path_number))
+				{
+					// We've hit a node that has already had a PathNum written in.
+					
+					if (node_path_number==path_number_to_write)
+					{
+						// The path num matches what we started with, so must have looped
+						// back to the start, so finished.
+						break;
+					}	
+					
+					// We've hit a node that has a different PathNum. So we need to rewind
+					// back to where we started and write in that path number instead.
+					// This happens due to the fact that the nodes could be in any order in
+					// the node array, so paths can start being traversed from any point, not
+					// necessarily the start.
+					path_number_to_write=node_path_number;
+					p_node=p_start_node;
+				}
+				
+				// Wack in the path number
+				p_node->AddInteger(0x9c91c3ca/*PathNum*/,path_number_to_write);
+				
+				// Check if this is a terminating node of the path, and stop if so.
+				int num_links=SkateScript::GetNumLinks(p_node);
+				if (num_links==0)
+				{
+					break;
+				}	
+				
+				// On to the next node. Note the if the path branches, it will only
+				// follow the first branch. The other branches will end up getting
+				// their own path numbers. Don't know if this will be a problem yet.
+				p_node=p_node_array->GetStructure(SkateScript::GetLink( p_node, 0 ));
+			}
+			
+			// If path_number got used, increment it ready for the next path.
+			if (path_number_to_write==m_num_paths)
+			{
+				++m_num_paths;
+				Dbg_MsgAssert(m_num_pathsAllocateObjectTrackerMemory();
+	return true;
+}
+
+} // namespace Obj
+
diff --git a/Code/Sk/Objects/PathMan.h b/Code/Sk/Objects/PathMan.h
new file mode 100644
index 0000000..9b2fcf2
--- /dev/null
+++ b/Code/Sk/Objects/PathMan.h
@@ -0,0 +1,93 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/pathman.h    									**
+**																			**
+**	Created: 		5/08/02	-	ksh											**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_PATHMAN_H
+#define __OBJECTS_PATHMAN_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef __GEL_OBJPTR_H
+#include 
+#endif
+
+#include 
+#include 
+
+namespace Obj
+{
+class CCompositeObject;
+
+class CPathObjectTracker
+{
+public:	
+enum
+{
+	MAX_OBJECTS_PER_PATH=50
+};	
+private:
+	CSmtPtr mpp_objects[MAX_OBJECTS_PER_PATH];
+	
+public:	
+	CPathObjectTracker();
+	~CPathObjectTracker();
+	void Clear();
+	void AddObjectPointer(CCompositeObject *p_ob);
+	void StopTrackingObject(CCompositeObject *p_ob);
+
+	const CSmtPtr *GetObjectList() {return mpp_objects;}
+};
+
+class CPathMan
+{
+public:
+	static CPathMan *Instance();
+	void RecursivelyAddPathInfo( int path_number_to_write, int node_number, int start_node_number, int recursion_depth = 0 );
+protected:
+	CPathMan();
+private:
+	static CPathMan *mp_instance;		
+	
+	#define MAX_PATHS 300
+	CPathObjectTracker* mp_path_object_trackers;
+	CPathObjectTracker mp_ped_object_tracker;
+	int m_num_paths;
+	
+public:
+	void AddPathInfoToNodeArray();
+	void ClearPathObjectTrackers();
+
+	void DeallocateObjectTrackerMemory();
+	void AllocateObjectTrackerMemory();
+	
+	CPathObjectTracker *TrackObject(CCompositeObject *p_ob, int node_number);
+	CPathObjectTracker *TrackPed(CCompositeObject *p_ob, int node_number);
+};
+
+//char foo[sizeof(CPathMan)/0];
+bool ScriptAllocatePathManMemory( Script::CStruct* pParams, Script::CScript* pScript );
+
+} // namespace Obj
+
+#endif
+
diff --git a/Code/Sk/Objects/PathOb.cpp b/Code/Sk/Objects/PathOb.cpp
new file mode 100644
index 0000000..b702aa4
--- /dev/null
+++ b/Code/Sk/Objects/PathOb.cpp
@@ -0,0 +1,1046 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       PathOb.cpp
+//* OWNER:          Matt Duncan
+//* CREATION DATE:  11/2/2000
+//****************************************************************************
+
+/*
+	Pathob.cpp
+	
+	This module contains path-navigation functionality.
+
+	A moving object contains a pointer to this pathob.  When
+	the moving object wants to follow a path along waypoints, it
+	just initializes this pathob with the current position and tells
+	the pathob where it wants to go.  Each frame, the moving object
+	calls TraversePath() and sends in the distance travelled, and
+	its own heading.  The pathob moves accordingly, but the point remains
+	on the plane defined by the points of the path.
+
+	The pathob is its own entity: it's essentially a point in
+	space and an orientation.  When the moving object calls TraversePath(),
+	the pathob just does the math and figures out where along the path
+	to move to.  The moving object takes the new path-ob position, finds
+	the ground above or below this position, and possibly sets its heading
+	so that it's looking the correct direction down the path.
+
+   	The pathob logic handles a simple path between two points... it also
+	handles the curves from one simple path to the next (so the path along
+	three waypoints, but instead of a sharp turn the pathob follows a curve
+	between the two lines).
+
+	As the pathob hits waypoints, it communicates to the moving object (in
+	case the moving object needs to run a script at the waypoint).
+*/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// PATHOB flags:
+#define PATHOBFLAG_LINEAR_INTERP			( 1 << 0 )
+#define PATHOBFLAG_END_OF_PATH				( 1 << 1 )
+#define PATHOBFLAG_SEND_HIT_WAYPOINT_MSG	( 1 << 2 )
+
+#define MAX_RATIO_OF_HYPOTENUSE_TO_OPPOSITE_FOR_TURN_CIRCLE    6
+
+// NOTE: if you change this enum, make sure to update the GetDebugInfo func
+enum
+{
+	PATHOB_STATE_UNINITIALIZED,
+	PATHOB_STATE_FOLLOW_LINE,
+	PATHOB_STATE_FOLLOW_CURVE,
+	PATHOB_STATE_LINEAR_INTERP,
+	PATHOB_STATE_IDLE,
+};
+
+enum
+{
+	TURN_METHOD_LINEAR_INTERP,
+	TURN_METHOD_CURVE,
+	TURN_METHOD_INSTANT_TURN,
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPathOb::CPathOb( CCompositeObject* pObj )
+{
+	mp_object = pObj;
+	m_turn_matrix.Ident();
+	
+	m_wp_pos_from.Set();
+	m_wp_pos_next.Set();
+	m_wp_pos_to.Set();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPathOb::~CPathOb( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPathOb::Busy( void )
+{
+	switch ( m_pathob_state )
+	{
+		case PATHOB_STATE_UNINITIALIZED:
+		case PATHOB_STATE_IDLE:
+			return false;
+		default:
+			return true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPathOb::SetUpNavPointLineMotion( void )
+{
+	// assumes we're already on the line.
+	
+	// pre-calculations for navigating between the waypoints...
+	
+	Mth::Vector vect_first_to_pos = m_nav_pos - m_wp_pos_from;
+	m_dist_traveled = vect_first_to_pos.Length();
+			  
+	Mth::Vector vect_first_to_second = m_wp_pos_from - m_wp_pos_to;
+	m_dist_to_wp = vect_first_to_second.Length();
+    
+    m_dist_traveled += m_extra_dist;
+	m_extra_dist = 0;
+	
+	/* Shouldn't need this, now that curve code is independent of turn dist
+    if ( m_dist_to_wp < m_current_turn_dist )
+	{
+		Dbg_MsgAssert( 0,( "Waypoints %d and %d too close or turn radius too large.", m_node_from, m_node_to ));
+	}*/
+	
+	// set up the heading:
+	m_turn_matrix[ Z ] = m_wp_pos_to - m_wp_pos_from;
+	m_turn_matrix[ Z ].Normalize();
+	
+	SetUpTurnDist();
+	
+	return( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CPathOb::SetUpTurningCircle( void )
+{
+	Mth::Vector vectA, vectB;
+	
+	// m_nav_pos holds one point on the circle.
+	// on the line from m_wp_pos_from to m_wp_pos_to
+	// (at the distance of nav pos from m_wp_pos_from)
+	// will be the other point on the circle.
+	vectB = m_nav_pos - m_wp_pos_from;
+
+	float actualTurnDist = vectB.Length();
+						
+	m_circle_arrival_pos = m_wp_pos_from;
+	vectA = m_wp_pos_to - m_wp_pos_from;
+	float distBetweenWps = vectA.Length();
+    vectA *= ( actualTurnDist / distBetweenWps );
+	m_circle_arrival_pos += vectA;
+
+	// That's all the info from the turning circle we need, if we're doing a linear interpolation:
+	if ( m_pathob_flags & PATHOBFLAG_LINEAR_INTERP )
+	{
+		return TURN_METHOD_LINEAR_INTERP;
+	}
+	
+//	Dbg_MsgAssert( actualTurnDist, "Traveling too fast for turn dist... increase turn dist or decrease velocity." );
+	if ( !actualTurnDist )
+	{
+		return TURN_METHOD_INSTANT_TURN;
+	}
+	
+	// find the radius of the turning circle
+	// the angle between the line we're on and the line to
+	// the arrival pos on the next line is the same as half
+	// the angle that the two radii where the circle touches
+	// the waypoint lines form...  use the similar triangles
+	// to get the circle radius:
+	vectA = m_circle_arrival_pos - m_nav_pos;
+	vectA /= 2; // vector from current pos to midpoint along current pos to arrival pos...
+	float lengthPosToMidpoint = vectA.Length();
+
+	vectB = m_nav_pos + vectA;
+	vectB -= m_wp_pos_from;
+	float distWPtoMP = vectB.Length();
+	
+	if ( ( !distWPtoMP ) | ( ( distWPtoMP * MAX_RATIO_OF_HYPOTENUSE_TO_OPPOSITE_FOR_TURN_CIRCLE ) < lengthPosToMidpoint ) )
+	{
+		// almost a straight line... use linear interpolation method:
+		return TURN_METHOD_LINEAR_INTERP;
+	}
+	// to understand this, draw the triangles and derive circle radius
+	// in terms of the known lengths of the similar triangles:
+	m_circle_radius = lengthPosToMidpoint * actualTurnDist;
+	m_circle_radius /= distWPtoMP;
+
+	if ( !m_circle_radius )
+	{
+		// tiny turn radius... snap to the point and shit like that!
+		return TURN_METHOD_INSTANT_TURN;
+	}
+	
+	// find the location of the center of the circle...
+	// the triangle formed by the current pos, the waypoint pos,
+	// and the midpoint between the current pos and the arrival pos
+	// is a similar triangle to the current pos, the waypoint pos,
+	// and the center of the circle...  therefore the distance from
+	// the center of the circle to the waypoint pos can be found:
+	float distFromWaypointToCircleCenter = actualTurnDist * actualTurnDist;
+	distFromWaypointToCircleCenter /= vectB.Length();
+	// take the vector from the waypoint to the midpoint, make it this length,
+	// and viola we have the circle center:
+	m_circle_center = vectB;
+	m_circle_center.Normalize( distFromWaypointToCircleCenter );
+	m_circle_center += m_wp_pos_from;
+
+	// test the distance from the current pos... should equal the frickin' radius:
+	/*
+	{
+		Mth::Vector temp = m_circle_center;
+		temp -= m_nav_pos;
+		Dbg_Message( "dist %f radius %f", temp.Length(), m_circle_radius );
+	}*/
+	
+	if ( m_circle_radius < m_min_turn_circle_radius )
+	{
+		Dbg_Message( "angle too acute to path between nodes %d and %d", m_node_from, m_node_to );
+		Dbg_MsgAssert( 0,( "Turn is too tight for this object...  your waypoints are just too acute for me!" ));
+	}
+
+	// find the "rotation axis" (the y axis if we're traveling along the xz plane)
+	// and set up a rotation matrix where the axis along the 1st waypoint path [ r0 ]
+	// sweeps along the circle as we rotate about the 'up'[ r1 ] axis in the direction if r2...
+	m_turn_matrix[ X ] = m_circle_center - m_nav_pos;
+	m_turn_matrix[ Z ] = m_circle_center - m_circle_arrival_pos;
+	if ( !( m_turn_matrix[ X ].Length() && m_turn_matrix[ Z ].Length() ) )
+	{
+		return TURN_METHOD_INSTANT_TURN;
+	}
+	m_turn_matrix[ X ].Normalize();
+	m_turn_matrix[ Z ].Normalize();
+
+	// while we have these two vectors, find the angle between them...
+	float angCos = Mth::DotProduct( m_turn_matrix[ X ], m_turn_matrix[ Z ] );
+#ifdef __PLAT_NGC__
+	float	ang = angCos;
+	if ( ang > 1.0f ) ang = 1.0f;
+	if ( ang < -1.0f ) ang = -1.0f;
+	m_arc_ang = acosf( ang );  // angles given in radians!
+#else
+	m_arc_ang = acosf( angCos );  // angles given in radians!
+#endif		// __PLAT_NGC__
+	
+	// find the total circumference of the arc,
+	// which is the circumference of the circle ( ang * radius )
+	// multiplied by ang / ( 2 * pi ) [ the fraction of the circle we actually traverse... ]
+	m_arc_dist = m_circle_radius * m_arc_ang;
+	
+	// continue setting up the matrix now...
+	m_turn_matrix[ Y ] = Mth::CrossProduct( m_turn_matrix[ X ], m_turn_matrix[ Z ] );
+	if ( !m_turn_matrix[ Y ].Length() )
+	{
+		return TURN_METHOD_INSTANT_TURN;
+	}
+	m_turn_matrix[ Y ].Normalize();
+	m_turn_matrix[ Z ] = Mth::CrossProduct( m_turn_matrix[ X ], m_turn_matrix[ Y ] );
+	if ( !m_turn_matrix[ Z ].Length() )
+	{
+		return TURN_METHOD_INSTANT_TURN;
+	}
+	m_turn_matrix[ Z ].Normalize();
+	
+    // Phew!!!  Now we can rotate this matrix along r1 and get the position on
+	// the circle by taking r0 multiplied by the circle radius and adding that to
+	// the center of the circle... and r2 will be the direction the car is pointed!!
+	   
+	// now to find the position along the arc, just get the
+	// current distance traveled along the arc, divide by
+	// the total circumference of the arc, multiply by the
+	// angle the arc traverses to get the angle currently
+	// traversed...  by rotating our curve matrix along
+	// y in this fashion, we can find the position on the
+	// circle!
+	
+	m_dist_traveled = m_extra_dist;
+	m_extra_dist = 0;
+	
+//	Gfx::AddDebugLine( m_nav_pos, m_circle_center, MAKE_RGB( 255, 0, 0 ), MAKE_RGB( 255, 0, 0 ) );
+//	Gfx::AddDebugLine( m_circle_center, m_circle_arrival_pos, MAKE_RGB( 0, 0, 255 ), MAKE_RGB( 0, 255, 0 ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::UpdateNavPosAlongPath( void )
+{	
+	Mth::Vector pointAlongPath;
+	
+	m_nav_pos = m_wp_pos_from;
+	pointAlongPath = m_wp_pos_to - m_wp_pos_from;
+	pointAlongPath *= ( m_dist_traveled / m_dist_to_wp );
+	m_nav_pos += pointAlongPath;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::UpdateNavPosAlongCurve( float distTraveled )
+{	
+	float fractionOfTotal;
+	fractionOfTotal = distTraveled / m_arc_dist;
+
+	m_y_rot = m_arc_ang * fractionOfTotal;
+	m_turn_matrix.RotateYLocal( m_y_rot );
+
+	Mth::Vector temp;
+	temp = m_turn_matrix[ Y ];
+	if ( temp[ Y ] > 0.0f )
+	{
+		m_y_rot *= -1.0f;
+	}
+
+	m_nav_pos = m_turn_matrix[ X ] * -m_circle_radius;
+	m_nav_pos += m_circle_center;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::AlignToPath( void )
+{   
+	m_turn_matrix[ Z ] = m_wp_pos_to - m_nav_pos;
+	m_turn_matrix[ Z ].Normalize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// If the distance between the next two waypoints is less than
+// the turn dist, decrease the turn dist and shit (otherwise
+// the arrival pos will be on the line but PAST the two waypoints
+// rather than between them!
+bool CPathOb::SetUpTurnDist( void )
+{	
+	Mth::Vector temp;
+	float wpLength;
+
+	if ( m_enter_turn_dist )
+	{
+		if ( !SkateScript::GetNumLinks( m_node_to ) )
+			return false;
+        SkateScript::GetPosition( SkateScript::GetLink( m_node_to, 0 ), &temp );
+		temp -= m_wp_pos_to;
+		// Need to optimise?  Store this length instead of recalculating it
+		// when getting next waypoint!
+		wpLength = temp.Length();
+		if ( wpLength < m_enter_turn_dist )
+		{
+			m_current_turn_dist = wpLength / 2.0f;
+			temp = m_wp_pos_to - m_nav_pos;
+			float distToCurrentWP = temp.Length();
+			if ( m_current_turn_dist > distToCurrentWP )
+			{
+				m_current_turn_dist = distToCurrentWP;
+			}
+			m_pathob_flags |= PATHOBFLAG_LINEAR_INTERP;
+			return true;
+		}
+	}
+
+	m_pathob_flags &= ~PATHOBFLAG_LINEAR_INTERP;
+
+	m_current_turn_dist = m_enter_turn_dist;
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::NewPath( int nodeNum, const Mth::Vector ¤tPos )
+{
+	m_pathob_state = PATHOB_STATE_UNINITIALIZED;
+	m_pathob_flags = 0;
+	m_node_to = nodeNum;
+	m_dist_traveled = m_extra_dist = 0;
+	m_nav_pos = m_wp_pos_from = currentPos;
+	SetUpPath();
+	
+	while ( Mth::Distance( m_nav_pos, m_wp_pos_to ) < 1.0f )
+	{
+		if ( !GetNextWaypoint() )
+		{
+			return;
+		}
+	}
+	
+	AlignToPath();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Set up the path according to m_node_from and m_node_to...
+// Do this after getting the next waypoint in a path, or when
+// beginning a new path.  m_node_from should indicate where we
+// are now, m_node_to should be the node index of where we're
+// going!
+void CPathOb::SetUpPath( void )
+{	
+	// get data out of waypoints...
+	Script::CScriptStructure *pNode = SkateScript::GetNode( m_node_to );
+	SkateScript::GetPosition( pNode, &m_wp_pos_to );
+	
+	if ( !SkateScript::GetNumLinks( m_node_to ) )
+	{
+		// the path will end on arrival to m_node_to:
+		m_pathob_flags |= PATHOBFLAG_END_OF_PATH;
+	}
+	else
+	{
+		m_pathob_flags &= ~PATHOBFLAG_END_OF_PATH;
+	}
+
+//	Gfx::AddDebugLine( m_wp_pos_from, m_wp_pos_to, MAKE_RGB( 255, 0, 0 ), MAKE_RGB( 0, 0, 255 ) );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::DrawPath()
+{
+	Gfx::AddDebugLine( m_wp_pos_from, m_wp_pos_to, MAKE_RGB( 255, 0, 0 ), MAKE_RGB( 0, 0, 255 ) );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPathOb::GetNextWaypoint( void )
+{
+	
+	int numLinks;
+	numLinks = SkateScript::GetNumLinks( m_node_to );
+	if ( !numLinks )
+	{
+		return false;
+	}
+	m_node_from = m_node_to;
+	Dbg_Assert( GetMotionComponentFromObject( mp_object ) );
+	if ( GetMotionComponentFromObject(mp_object)->PickRandomWaypoint() )
+	{
+		m_node_to = SkateScript::GetLink( m_node_to, Mth::Rnd( numLinks ) );
+	}
+	else
+	{
+		m_node_to = SkateScript::GetLink( m_node_to, 0 );
+	}
+	m_wp_pos_from = m_wp_pos_to;
+	
+	SetUpPath();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::SetHeading( Mth::Vector *p_heading )
+{
+	// Gets the current heading:	
+	*p_heading = m_turn_matrix[ Z ];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+float CPathOb::GetDistToWaypoint( void )
+{
+	switch ( m_pathob_state )
+	{
+		case PATHOB_STATE_UNINITIALIZED:
+			//Dbg_Message( "%f line %d", HUGE_DISTANCE, __LINE__ );		
+			return HUGE_DISTANCE;
+			break;
+		
+		case PATHOB_STATE_FOLLOW_LINE:
+			//Dbg_Message( "%f line %d", m_dist_to_wp - m_dist_traveled, __LINE__ );		
+			return ( m_dist_to_wp - m_dist_traveled );
+			break;
+		
+		case PATHOB_STATE_FOLLOW_CURVE:
+			if ( m_dist_traveled < ( m_arc_dist / 2 ) )
+			{
+				//Dbg_Message( "%f line %d", ( m_arc_dist / 2 ) - m_dist_traveled, __LINE__ );		
+				return ( ( m_arc_dist / 2 ) - m_dist_traveled );
+			}
+			else
+			{
+				Mth::Vector temp = m_wp_pos_to - m_wp_pos_from;
+				//Dbg_Message( "%f line %d", temp.Length(), __LINE__ );		
+				return temp.Length();
+			}
+			break;
+		
+		case PATHOB_STATE_LINEAR_INTERP:
+			if ( m_dist_traveled < m_dist_to_wp )
+			{
+				//Dbg_Message( "%f line %d", m_dist_to_wp - m_dist_traveled, __LINE__ );		
+				return ( m_dist_to_wp - m_dist_traveled );
+			}
+			else
+			{
+				Mth::Vector temp = m_wp_pos_to - m_wp_pos_from;
+				//Dbg_Message( "%f line %d", temp.Length(), __LINE__ );		
+				return temp.Length();
+			}
+			break;
+		
+		default:
+			Dbg_MsgAssert( 0, ( "unhandled pathob state" ) );
+			break;
+	}
+	
+	return HUGE_DISTANCE;
+}
+
+#if FINDING_GLITCHES_IN_PATH_TRAVERSAL
+// this function tracks down glitches in the path traversal...
+void CPathOb::FindGlitch( void )
+{	
+	static float totalDist = 0;
+	static int numEntries = 0;
+	float thisDist = Distance( m_old_nav_pos, m_nav_pos );
+	totalDist += thisDist;
+	numEntries++;
+	float averageDist = totalDist / numEntries;
+	if ( numEntries == 100 )
+	{
+		numEntries = 50;
+		totalDist /= 2;
+	}
+	if ( fabs( thisDist - averageDist ) > 4.0f )
+	{
+		Gfx::AddDebugLine( m_nav_pos, m_old_nav_pos, MAKE_RGB( 255, 255, 255 ), MAKE_RGB( 255, 255, 255 ) );
+		Dbg_Message( "off by %f dist %f state %d prev state %d", thisDist - averageDist, thisDist, m_pathob_state, m_prev_pathob_state );
+	}
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::PrepareTurn( void )
+{	
+	int retVal = SetUpTurningCircle();
+	switch ( retVal )
+	{
+		case TURN_METHOD_LINEAR_INTERP:
+		{
+			// store the current pos in circle center:
+			// the angle is too wide (too close to PI radians)...
+			// just move along the two lines and gradually turn and shit like that...
+			
+			m_circle_center = m_nav_pos;
+			Mth::Vector temp = m_nav_pos - m_wp_pos_from;
+			m_dist_to_wp = temp.Length();
+			m_dist_traveled = m_extra_dist;
+			m_pathob_state = PATHOB_STATE_LINEAR_INTERP;
+			break;
+		}
+		
+		case TURN_METHOD_CURVE:
+			m_pathob_state = PATHOB_STATE_FOLLOW_CURVE;
+			break;
+		
+		case TURN_METHOD_INSTANT_TURN:
+			// just hit the waypoint, and set up the path for the next one:
+			Dbg_Assert( GetMotionComponentFromObject( mp_object ) );
+			GetMotionComponentFromObject( mp_object )->HitWaypoint( m_node_from );
+			m_pathob_flags &= ~PATHOBFLAG_SEND_HIT_WAYPOINT_MSG;
+			m_dist_traveled = m_dist_to_wp - m_dist_traveled;
+			// now move along the line...
+			SetUpNavPointLineMotion();
+			UpdateNavPosAlongPath();
+			m_pathob_state = PATHOB_STATE_FOLLOW_LINE;
+			break;
+
+		default:
+			Dbg_MsgAssert( 0,( "Unknown turn method %d", retVal ));
+			break;
+	}   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Function TraversePath()
+// This function is called by the moving object each frame, and causes the
+// pathob to move a little further down the path (by the projection of the moving
+// object's forward vector along the line or curve in the path).
+// Returns 'TRUE' when the end of the path is reached...
+//bool CPathOb::TraversePath( bool is_being_skitched, const float dist, Mth::Vector *pHeading )
+bool CPathOb::TraversePath( const float dist, Mth::Vector *pHeading )
+{
+	//if (is_being_skitched) printf("#");	
+	
+	if ( m_pathob_state == PATHOB_STATE_IDLE )
+	{
+		return true;
+	}
+	
+#if FINDING_GLITCHES_IN_PATH_TRAVERSAL
+	m_old_nav_pos = m_nav_pos;
+#endif
+	
+	float distTraveled;
+	if ( pHeading )
+	{
+		float cosHeadingToNavPath = Mth::DotProduct( *pHeading, m_turn_matrix[ Z ] );
+		if ( !cosHeadingToNavPath )
+		{
+			//if (is_being_skitched) printf("a");	
+			// can't assert on this case... car could be pointing at the sky temporarily!
+			distTraveled = 1;
+		}
+		else
+		{		
+			//if (is_being_skitched) printf("b");	
+			distTraveled = dist * cosHeadingToNavPath;
+		}
+	}
+	else
+	{
+		//if (is_being_skitched) printf("c");	
+		distTraveled = dist;
+	}	
+	
+#if FINDING_GLITCHES_IN_PATH_TRAVERSAL
+	m_prev_pathob_state = m_pathob_state;
+#endif
+		
+	switch ( m_pathob_state )
+	{
+		case PATHOB_STATE_UNINITIALIZED:
+			//if (is_being_skitched) printf("d");	
+			SetUpNavPointLineMotion();
+			m_y_rot = 0;
+			m_pathob_state = PATHOB_STATE_FOLLOW_LINE;
+			// intentional fall thru...
+		case PATHOB_STATE_FOLLOW_LINE:
+			//if (is_being_skitched) printf("e");	
+			// will convert this to GetDistTraveled(), once the
+			// nav point stuff is disconnected from the car stuff
+			m_dist_traveled += distTraveled;
+			
+            if ( m_pathob_flags & PATHOBFLAG_END_OF_PATH )
+			{
+				// see if we're at the end of the path:
+				if ( m_dist_traveled >= m_dist_to_wp )
+				{
+					m_node_from = m_node_to;
+					m_nav_pos = m_wp_pos_from = m_wp_pos_to;
+					m_pathob_state = PATHOB_STATE_IDLE;
+					return true;
+				}
+			}
+			else if ( m_dist_traveled >= ( m_dist_to_wp - m_current_turn_dist ) )
+			{
+				// we've reached the waypoint... save the extra!
+				//m_extra_dist = m_dist_traveled - ( m_dist_to_wp - m_current_turn_dist );
+				if ( !m_current_turn_dist )
+				{
+					m_nav_pos = m_wp_pos_to;
+					Dbg_Assert( GetMotionComponentFromObject( mp_object ) );
+					GetMotionComponentFromObject( mp_object )->HitWaypoint( m_node_to );
+					if ( !GetNextWaypoint() )
+					{
+						Dbg_MsgAssert( 0,( "Tell Matt that the code got here, and that he's an idiot but you still love him." ));
+						return true;
+					}
+					else
+					{
+						m_pathob_state = PATHOB_STATE_UNINITIALIZED;
+					}
+					break;
+				}
+				
+                // get the circle along the turn, using the turn radius...
+				// first, set the nav pos equal to the first point on the circle
+				// ( the current position... )
+				//m_dist_traveled -= m_extra_dist;
+				UpdateNavPosAlongPath();
+				
+				// then get the next waypoint, which will allow us to figure out
+				// the second point along the circle...
+				GetNextWaypoint();
+				m_pathob_flags |= PATHOBFLAG_SEND_HIT_WAYPOINT_MSG;
+				PrepareTurn();
+				
+				break;
+			}
+			UpdateNavPosAlongPath();
+            break;
+
+		case PATHOB_STATE_FOLLOW_CURVE:
+			// moving around the circumference of the circle now!!
+			{
+				//if (is_being_skitched) printf("f");	
+				//Mth::Vector oldpos=m_nav_pos;
+				
+				// will convert this to GetDistTraveled(), once the
+				// nav point stuff is disconnected from the car stuff
+				m_dist_traveled += distTraveled;
+				//if (is_being_skitched) printf("[dt=%.2f, %.2f]",distTraveled,m_dist_traveled);
+				
+				if ( ( m_pathob_flags & PATHOBFLAG_SEND_HIT_WAYPOINT_MSG ) &&
+					( m_dist_traveled >= ( m_arc_dist / 2.0f ) ) )
+				{
+					m_pathob_flags &= ~PATHOBFLAG_SEND_HIT_WAYPOINT_MSG;
+					Dbg_Assert( GetMotionComponentFromObject( mp_object ) );
+					GetMotionComponentFromObject( mp_object )->HitWaypoint( m_node_from );
+				}
+				if ( m_dist_traveled > m_arc_dist )
+				{
+					
+					m_extra_dist = m_dist_traveled - m_arc_dist;
+					//if (is_being_skitched) printf("[mdt=%.2f][ad=%.2f][ed=%.2f]",m_dist_traveled,m_arc_dist,m_extra_dist);
+					
+					// Calculates m_nav_pos. Ignores m_dist_traveled and m_extra_dist
+					UpdateNavPosAlongCurve( distTraveled - m_extra_dist );
+					//if (is_being_skitched) printf("[cpd=%.2f]",Mth::Distance(m_nav_pos,oldpos));
+					
+					// now move along the line...
+					
+					// Ken: Skitch Glitch fix
+					// If this is not zeroed there is an increase in speed for one frame when
+					// going from this state to PATHOB_STATE_FOLLOW_LINE
+					// Ie, the car moves too far in this transition.
+					m_extra_dist=0.0f;
+					
+					// Recalculates m_dist_traveled using m_nav_pos, then adds m_extra_dist, 
+					// then zeros m_extra_dist
+					SetUpNavPointLineMotion();
+					
+					// Calculates m_nav_pos using m_dist_traveled
+					UpdateNavPosAlongPath();
+					
+					m_y_rot = 0;
+					m_pathob_state = PATHOB_STATE_FOLLOW_LINE;
+					
+					//if (is_being_skitched) printf("[%.2f]",Mth::Distance(oldpos,m_nav_pos));
+					break;
+				}
+				// find the angular rotation (angle changes relative to
+				// total arc angle as the distance changes relative to
+				// the total distance...
+				//if (is_being_skitched) printf("{dt=%.2f r=%.2f ad=%.2f ang=%.2f}",distTraveled,m_circle_radius,m_arc_dist,m_arc_ang);
+				UpdateNavPosAlongCurve( distTraveled );
+				//if (is_being_skitched) printf("[%.2f]",Mth::Distance(oldpos,m_nav_pos));
+				
+				//temp debuggery:
+				/*{
+					Mth::Vector tempVect;
+					tempVect = m_turn_matrix[ 0 ] * 150;
+					tempVect += m_circle_center;
+					Gfx::AddDebugLine( tempVect, m_circle_center, MAKE_RGB( 255, 0, 0 ), MAKE_RGB( 0, 0, 0 ) );
+					tempVect = m_turn_matrix[ 1 ] * 150;
+					tempVect += m_circle_center;
+					Gfx::AddDebugLine( tempVect, m_circle_center, MAKE_RGB( 0, 255, 0 ), MAKE_RGB( 0, 0, 0 ) );
+					tempVect = m_turn_matrix[ 2 ] * 150;
+					tempVect += m_circle_center;
+					Gfx::AddDebugLine( tempVect, m_circle_center, MAKE_RGB( 0, 0, 255 ), MAKE_RGB( 0, 0, 0 ) );
+				}*/
+			}
+			break;
+		
+		case PATHOB_STATE_LINEAR_INTERP:
+		{
+			//if (is_being_skitched) printf("g");	
+			// very minimal turn...
+			// move along the current line until we get to the next line...
+			// gradually change the heading.
+			m_dist_traveled += distTraveled;
+			float fracTraveled = m_dist_traveled / m_dist_to_wp;
+			
+			if ( m_dist_traveled < m_dist_to_wp )
+			{
+				m_nav_pos = m_circle_center;
+				Mth::Vector alongPath = m_wp_pos_from - m_circle_center;
+				alongPath *= fracTraveled;
+				m_nav_pos += alongPath;
+			}
+			else
+			{
+				m_nav_pos = m_wp_pos_from;
+				Mth::Vector alongPath = m_circle_arrival_pos - m_wp_pos_from;
+				alongPath *= ( fracTraveled - 1 ) / 1;
+				m_nav_pos += alongPath;
+			}
+			
+			// set the heading, just a weighted average of the two headings...
+			Mth::Vector heading1, heading2;
+			heading1 = m_wp_pos_from - m_circle_center;
+			heading2 = m_circle_arrival_pos - m_wp_pos_from;
+			// fraction traveled over both lines...
+			fracTraveled /= 2.0f;
+			if ( ( fracTraveled ) >= 0.5f && ( m_pathob_flags & PATHOBFLAG_SEND_HIT_WAYPOINT_MSG ) )
+			{
+				m_pathob_flags &= ~PATHOBFLAG_SEND_HIT_WAYPOINT_MSG;
+				Dbg_Assert( GetMotionComponentFromObject( mp_object ) );
+				GetMotionComponentFromObject( mp_object )->HitWaypoint( m_node_from );
+			}
+			if ( fracTraveled >= 1.0f )
+			{
+				// set all the variables back so we can traverse the next path:
+				fracTraveled = 1;
+				
+				// Ken: Skitch Glitch fix
+				//m_extra_dist = m_dist_traveled - ( 2 * m_dist_to_wp );
+				m_extra_dist=0.0f;
+				
+				SetUpNavPointLineMotion();
+				//Dbg_Message( "hmm %f\n", m_dist_traveled );
+				m_pathob_state = PATHOB_STATE_FOLLOW_LINE;
+				//if (is_being_skitched)
+				//{
+				//	printf("Going from interp back to line\n");	
+				//}	
+			}
+			m_turn_matrix[ Z ] = ( heading1 * ( 1 - fracTraveled ) );
+			// by the time we get to the arrival pos, the heading will be along the next line!
+			m_turn_matrix[ Z ] += ( heading2 * ( fracTraveled ) );
+			m_turn_matrix[ Z ].Normalize();
+			break;
+		}
+
+		case PATHOB_STATE_IDLE:
+			break;
+			
+		default:
+			Dbg_MsgAssert( 0,( "Unknown substate!" ));
+			break;
+	}
+	
+#if FINDING_GLITCHES_IN_PATH_TRAVERSAL
+	FindGlitch();
+#endif
+	
+#if DRAW_COLOR_CODED_NAV_PATH
+	int rgb;
+	switch ( m_pathob_state )
+	{
+		case PATHOB_STATE_LINEAR_INTERP:
+			rgb = MAKE_RGB( 255, 0, 0 );
+			break;
+			
+		case PATHOB_STATE_FOLLOW_CURVE:
+			rgb = MAKE_RGB( 0, 255, 0 );
+			break;
+			
+		case PATHOB_STATE_FOLLOW_LINE:
+			rgb = MAKE_RGB( 0, 0, 255 );
+			break;
+		
+		case PATHOB_STATE_UNINITIALIZED:
+			rgb = 255;
+			break;
+			
+		default:
+			rgb = MAKE_RGB( 255, 255, 255 );					  
+	}
+	m_old_nav_pos[ Y ] += 200;
+	m_nav_pos[ Y ] += 200;
+	Gfx::AddDebugLine( m_old_nav_pos, m_nav_pos, rgb, rgb );
+	m_old_nav_pos[ Y ] -= 200;
+	m_nav_pos[ Y ] -= 200;
+#endif
+
+	//if (is_being_skitched) printf("#");	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPathOb::GetDebugInfo( Script::CStruct* p_info )
+{
+#ifdef	__DEBUG_CODE__
+	Dbg_MsgAssert( p_info, ( "NULL p_info passed to CPathOb::GetDebugInfo" ) );
+
+	p_info->AddVector( "m_nav_pos", m_nav_pos );
+	
+	// nodes
+	p_info->AddInteger( "m_node_from", m_node_from );
+	p_info->AddInteger( "m_node_to", m_node_to );
+	p_info->AddInteger( "m_node_next", m_node_next );
+	p_info->AddStructure( "m_node_from", SkateScript::GetNode( m_node_from ) );
+	p_info->AddStructure( "m_node_to", SkateScript::GetNode( m_node_to ) );
+	p_info->AddStructure( "m_node_next", SkateScript::GetNode( m_node_next ) );
+	
+	// waypoint positions:
+	p_info->AddVector( "m_wp_pos_from", m_wp_pos_from );
+	p_info->AddVector( "m_wp_pos_to", m_wp_pos_to );
+	p_info->AddVector( "m_wp_pos_next", m_wp_pos_next );
+
+    // DESIGNER SETTABLE, through script file:
+    p_info->AddFloat( "m_enter_turn_dist", m_enter_turn_dist );
+	p_info->AddFloat( "m_min_turn_circle_radius", m_min_turn_circle_radius );
+	
+	// float				m_current_turn_dist;
+	
+	// navigational info (save runtime calculations...)
+	// float				m_dist_to_wp;
+	// float				m_dist_traveled;
+	
+	// could be private:
+	// int					m_pathob_flags;
+
+	// float				m_extra_dist;
+
+	// for navigating around corners:
+	// Mth::Vector			m_circle_center;
+	// Mth::Vector			m_circle_arrival_pos; // do we really need this? no.  so fuck you!
+	// use member m_turn_matrix from object structure...
+//	Mth::Matrix			m_arc_matrix;	// Y axis is rotation axis, X is along radius
+	// float				m_arc_ang;		// total sweep along the radius....
+	// float				m_arc_dist;		// total distance along the arc...
+	// float				m_circle_radius;
+
+	uint32 pathob_state_checksum = 0;
+	switch ( m_pathob_state )
+	{
+	case 0:
+		pathob_state_checksum = CRCD( 0x16d6676f, "PATHOB_STATE_UNINITIALIZED" );
+		break;
+	case 1:
+		pathob_state_checksum = CRCD( 0x55b5bee1, "PATHOB_STATE_FOLLOW_LINE" );
+		break;
+	case 2:
+		pathob_state_checksum = CRCD( 0x6cc96f7, "PATHOB_STATE_FOLLOW_CURVE" );
+		break;
+	case 3:
+		pathob_state_checksum = CRCD( 0xac01db8, "PATHOB_STATE_LINEAR_INTERP" );
+		break;
+	case 4:
+		pathob_state_checksum = CRCD( 0x5bc8724c, "PATHOB_STATE_IDLE" );
+		break;
+	default:
+		Dbg_MsgAssert( 0, ( "CPathOb::GetDebugInfo got bad m_pathob_state.  Was the enum changed?" ) );
+		break;
+	}
+	p_info->AddChecksum( "m_pathob_state", pathob_state_checksum );
+#endif				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+
diff --git a/Code/Sk/Objects/PathOb.h b/Code/Sk/Objects/PathOb.h
new file mode 100644
index 0000000..ecfd482
--- /dev/null
+++ b/Code/Sk/Objects/PathOb.h
@@ -0,0 +1,166 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       PathOb.h
+//* OWNER:          Matt Duncan
+//* CREATION DATE:  11/2/2000
+//****************************************************************************
+
+#ifndef __OBJECTS_PATHOB_H
+#define __OBJECTS_PATHOB_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// debugging options:
+#define FINDING_GLITCHES_IN_PATH_TRAVERSAL	0
+#define DRAW_COLOR_CODED_NAV_PATH			0
+
+#define HUGE_DISTANCE		FEET_TO_INCHES( 666 * 666 )
+
+namespace Obj
+{
+
+class CCompositeObject;											 
+											 
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class Manager;
+
+class CPathOb : public Spt::Class
+{
+	
+public:
+	CPathOb( CCompositeObject* pObj );
+	virtual ~CPathOb();
+
+	virtual	void		GetDebugInfo( Script::CStruct* p_info );
+
+	Mth::Vector			m_nav_pos;		//car pos is set by finding the ground under the nav pos
+	CCompositeObject* 	mp_object;		// owner
+
+	// for the curve from one path to the next...
+	Mth::Matrix			m_turn_matrix;	// Y axis is rotation axis, X is along radius
+	
+	// waypoint indices:
+	int					m_node_from;
+	int					m_node_to;
+	int					m_node_next;
+	
+	// waypoint positions:
+	Mth::Vector			m_wp_pos_from;
+	Mth::Vector			m_wp_pos_to;
+	Mth::Vector			m_wp_pos_next;
+
+    // DESIGNER SETTABLE, through script file:
+    float				m_enter_turn_dist;				// in inches...
+	float				m_min_turn_circle_radius;	// in inches
+    // end DESIGNER SETTABLE variables...
+	
+	// varies depending on the distance between waypoints:
+	float				m_current_turn_dist;
+	
+	// navigational info (save runtime calculations...)
+	float				m_dist_to_wp;
+	float				m_dist_traveled;
+	
+	// could be private:
+	int					m_pathob_flags;
+
+	float				m_extra_dist;
+
+	// for navigating around corners:
+	Mth::Vector			m_circle_center;
+	Mth::Vector			m_circle_arrival_pos; // do we really need this? no.  so fuck you!
+	// use member m_turn_matrix from object structure...
+//	Mth::Matrix			m_arc_matrix;	// Y axis is rotation axis, X is along radius
+	float				m_arc_ang;		// total sweep along the radius....
+	float				m_arc_dist;		// total distance along the arc...
+	float				m_circle_radius;
+
+	int					m_pathob_state;
+ 
+	bool				Busy( void );
+	float				GetDistToWaypoint( void );
+			   
+	// Returns 'true' when path has been traversed (end of path!)
+    bool				TraversePath( const float deltaDist, Mth::Vector *pHeading = NULL );
+	
+	void				SetHeading( Mth::Vector *p_heading );
+
+	bool				GetNextWaypoint( void );
+
+	// call when beginning a new path:
+	void 				InitPath( int nodeNum );
+	void				NewPath( int nodeNum, const Mth::Vector ¤tPos );
+
+	float				m_y_rot;
+	void				DrawPath( void );
+
+private:
+
+	// Set up the path according to m_node_from and m_node_to...
+	// Do this after getting the next waypoint in a path, or when
+	// beginning a new path.  m_node_from should indicate where we
+	// are now, m_node_to should be the node index of where we're
+	// going!
+	void				SetUpPath( void );
+
+	// set heading vector to correspond to new path...
+	void				AlignToPath( void );
+
+	bool 				SetUpNavPointLineMotion( );
+	int 				SetUpTurningCircle( void );
+	void				PrepareTurn( void );
+	bool				SetUpTurnDist( void );
+
+	void				UpdateNavPosAlongPath( void );
+	void				UpdateNavPosAlongCurve( float distTraveled );
+
+#if FINDING_GLITCHES_IN_PATH_TRAVERSAL
+	// can remove if no longer debugging:
+	int					m_prev_pathob_state;
+	// nav pos moves smoothly between the waypoints or along the curve connecting paths...
+	Mth::Vector 		m_old_nav_pos;	//for debuggery...
+	// debugging function:
+	void				FindGlitch( void );
+#endif
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_PATHOB_H
+
diff --git a/Code/Sk/Objects/PlayerProfileManager.cpp b/Code/Sk/Objects/PlayerProfileManager.cpp
new file mode 100644
index 0000000..cc07b72
--- /dev/null
+++ b/Code/Sk/Objects/PlayerProfileManager.cpp
@@ -0,0 +1,769 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       PlayerProfileManager.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  4/29/2002
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+// Selects which one is the current player profile
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPlayerProfileManager::CPlayerProfileManager() : m_Profiles(vMAX_PROFILES)
+{
+	m_CurrentProfileIndex = 0;
+
+	for ( int i = 0; i < vMAX_PLAYERS; i++ )
+	{
+		mp_CurrentProfile[i] = NULL;
+	}
+
+	m_LockCurrentSkaterProfileIndex = false;
+
+	m_TemporaryProfileCount = 0;
+
+#if 0
+
+/*
+appearance_custom_skater_worst_case_net_packet = 
+{
+    body = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    board = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    skater_m_head = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    skater_m_torso = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    skater_m_legs = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    skater_m_hair = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    skater_m_backpack = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    skater_m_jaw = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    skater_m_socks = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    sleeves = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    kneepads = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    elbowpads = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    shoes = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    sleeves = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    front_logo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+    back_logo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+    hat = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    helmet = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+    accessories = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+	hat_logo = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+	helmet_logo = { desc_id=#"Wrist Bands" h=350 s=99 v=98 use_default_hsv = 1}
+
+	// pos items...
+	griptape = { desc_id=#"Wrist Bands" }
+	deck_graphic = { desc_id=#"Wrist Bands" }
+	cad_graphic = { desc_id=#"Wrist Bands" h=350 s=99 v=98 uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	deck_layer1 = { desc_id=#"Wrist Bands" h=350 s=99 v=98 uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	deck_layer2 = { desc_id=#"Wrist Bands" h=350 s=99 v=98 uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	deck_layer3 = { desc_id=#"Wrist Bands" h=350 s=99 v=98 uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	deck_layer4 = { desc_id=#"Wrist Bands" h=350 s=99 v=98 uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	deck_layer5 = { desc_id=#"Wrist Bands" h=350 s=99 v=98 uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+		
+	head_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	left_bicep_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	left_forearm_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	right_bicep_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	right_forearm_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	chest_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	back_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	left_leg_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+	right_leg_tattoo = { desc_id=#"Wrist Bands" uv_u=199.999999 uv_v=199.999999 uv_scale=199.999999 uv_rot=355.000000 use_default_uv = 1}
+
+	headtop_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	Jaw_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	nose_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	head_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	torso_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	stomach_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	upper_arm_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	lower_arm_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	hands_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	upper_leg_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	lower_leg_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	feet_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	board_bone_group = { x=160 y=160 z=160 use_default_scale = 1}
+	object_scaling = { x=160 y=160 z=160 use_default_scale = 1}
+}
+*/
+
+	uint8 tBuf[vMAX_APPEARANCE_BUFFER_SIZE];
+	Script::CStruct* pTempStructure = Script::GetStructure("appearance_custom_skater_worst_case_net_packet",Script::ASSERT);
+	Obj::CSkaterProfile* pPlayerProfile = new Obj::CSkaterProfile;
+	pPlayerProfile->GetAppearance()->Load( pTempStructure, false );	
+	uint32 size = pPlayerProfile->WriteToBuffer(tBuf, vMAX_APPEARANCE_BUFFER_SIZE);
+	Dbg_Assert( size < vMAX_PRACTICAL_APPEARANCE_DATA_SIZE );
+	Dbg_Message("*********** Worst case model appearance data size = %d bytes\n", size);
+	delete pTempStructure;
+	delete pPlayerProfile;
+	Dbg_Assert( 0 );
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::Init()
+{
+	int dummy;
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	// TODO:  this is an optimization to go down from 4-byte
+	// checksums to a 1- or 2-byte index into a checksum table.
+	// This is probably not a very good system, because it's not
+	// scalable to downloadable content
+//	Cas::InitDescChecksumTable();
+	
+	// initialize the pro skater profiles here
+	Script::RunScript( "init_pro_skaters" );
+
+	// the number of profiles must exceed the number of players
+	Dbg_MsgAssert( m_Profiles.getSize() >= vMAX_PLAYERS, ( "Not enough profiles (must be greater than maxplayers)" ) );
+
+	// profile #0 points to an actual profile
+	mp_CurrentProfile[0] = m_Profiles.GetItemByIndex( 0, &dummy );
+
+	// the others point to temp copies...  this is so that player
+	// #1 through N can edit their appearances without affecting
+	// the source profile
+	for ( int i = 1; i < vMAX_PLAYERS; i++ )
+	{
+		mp_CurrentProfile[i] = new CSkaterProfile;
+		*mp_CurrentProfile[i] = *m_Profiles.GetItemByIndex( i, &dummy );
+	}
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPlayerProfileManager::~CPlayerProfileManager()
+{
+	uint32 tableSize = m_Profiles.getSize( );
+	for ( uint32 i = 0; i < tableSize; i++ )
+	{
+		int key;
+		CSkaterProfile* pProfile = m_Profiles.GetItemByIndex( 0, &key );
+		delete pProfile;
+		m_Profiles.FlushItem( key );
+	}
+
+	for ( int i = 1; i < vMAX_PLAYERS; i++ )
+	{
+		Dbg_Assert( mp_CurrentProfile[i] );
+		delete mp_CurrentProfile[i];
+		mp_CurrentProfile[i] = NULL;
+	}
+
+	for ( int i = 0; i < m_TemporaryProfileCount; i++ )
+	{
+		Dbg_Assert( mp_TemporaryProfiles[i] );
+		delete mp_TemporaryProfiles[i];
+		mp_TemporaryProfiles[i] = NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CPlayerProfileManager::GetNumProfileTemplates( void )
+{
+	return m_Profiles.getSize();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CPlayerProfileManager::GetProfileTemplateChecksum( int i )
+{
+	Dbg_MsgAssert( i >= 0 && i < m_Profiles.getSize(),( "Profile %d out of range (0-%d)", i, m_Profiles.getSize()-1 ));
+
+	int key;
+	m_Profiles.GetItemByIndex( i, &key );
+	return (uint32)key;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile*		CPlayerProfileManager::GetProfileTemplateByIndex( int i)
+{
+	Dbg_MsgAssert( i >= 0 && i < m_Profiles.getSize(),( "Profile %d out of range (0-%d)", i, m_Profiles.getSize()-1 ));
+
+	return m_Profiles.GetItemByIndex( i );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::SyncPlayer2()
+{
+	// GJ:  need to push the skaterinfoheap or else the custom skater's face
+	// texture may end up on the bottom up heap (you'd only notice that
+	// if you went into 2 player mode and both characters were already
+	// custom skaters, because pressing LEFT to switch from a pro skater
+	// to a custom skater would automatically push the skater info heap)
+	// this whole process is probably worth looking at next time because
+	// it's kind of kludgy, but it's too risky to fix in any other
+	// way right now...
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	int profile_index = 1;
+
+	// get the currently selected skater for player #1
+	uint32 checksum = mp_CurrentProfile[profile_index]->GetSkaterNameChecksum();
+
+	// sync up the profile from the root profile
+	*mp_CurrentProfile[profile_index] = *m_Profiles.GetItem( checksum );
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::SetCurrentProfileIndex(int i)
+{
+	if ( m_LockCurrentSkaterProfileIndex )
+	{
+		// we lock it so that we make sure no one accesses
+		// this during network games or splitscreen games,
+		// which aren't guaranteed to return the correct
+		// value
+		Dbg_MsgAssert( 0, ( "Can't access current profile index! Are you playing a network game? (See Gary)" ) );
+	}
+
+	Dbg_MsgAssert( i >= 0 && i < vMAX_PLAYERS,( "Profile %d out of range (0-%d)", i, vMAX_PLAYERS-1 ));
+	
+	m_CurrentProfileIndex = i;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CPlayerProfileManager::GetCurrentProfileIndex()
+{
+	if ( m_LockCurrentSkaterProfileIndex )
+	{
+		// we lock it so that we make sure no one accesses
+		// this during network games or splitscreen games,
+		// which aren't guaranteed to return the correct
+		// value
+		Dbg_MsgAssert( 0, ( "Can't access current profile index! Are you playing a network game? (See Gary)" ) );
+	}
+
+	return m_CurrentProfileIndex;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::ApplyTemplateToCurrentProfile( uint32 checksum )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+	if ( m_CurrentProfileIndex == 0 )
+	{
+		mp_CurrentProfile[m_CurrentProfileIndex] = GetProfileTemplate( checksum );
+	}
+	else
+	{
+		*mp_CurrentProfile[m_CurrentProfileIndex] = *GetProfileTemplate( checksum );
+	}
+	Mem::Manager::sHandle().PopContext();	//Mem::Manager::sHandle().SkaterInfoHeap());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile* CPlayerProfileManager::GetCurrentProfile()
+{
+	return mp_CurrentProfile[m_CurrentProfileIndex];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile* CPlayerProfileManager::GetProfile( int i )
+{
+	Dbg_MsgAssert( i >= 0 && i < vMAX_PLAYERS,( "Profile %d out of range (0-%d)", i, vMAX_PLAYERS-1 ));
+
+	return mp_CurrentProfile[i];
+}
+
+/******************************************************************/
+/*                                                                
+	If any of the skater careers have this flag set, it returns true.
+	Used by the cassette menu when used for free skate, session etc,
+	to find out what levels need to be selectable.
+																  */
+/******************************************************************/
+
+/*
+bool CPlayerProfileManager::GetGlobalFlag(int flag)
+{
+	Dbg_MsgAssert(0,("CPlayerProfileManager has been deprecated - careers no longer in profiles\n"));
+	uint32 tableSize = m_Profiles.getSize( );
+	for ( uint32 i = 0; i < tableSize; i++ )
+	{
+		int key;
+		CSkaterProfile* pProfile = m_Profiles.GetItemByIndex( i, &key );
+		Dbg_MsgAssert(pProfile,("NULL pProfile"));
+
+		CSkaterCareer *pCareer=pProfile->GetCareer();
+		Dbg_MsgAssert(pCareer,("NULL pCareer"));
+
+		if (pCareer->GetGlobalFlag(flag))
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile* CPlayerProfileManager::GetProfileTemplate( uint32 checksum )
+{
+	Dbg_MsgAssert( m_Profiles.GetItem( checksum ), ( "Couldn't find profile %s", Script::FindChecksumName(checksum) ) );
+
+	return m_Profiles.GetItem( checksum );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::Reset()
+{
+	uint32 tableSize = m_Profiles.getSize( );
+	for ( uint32 i = 0; i < tableSize; i++ )
+	{
+		int key;
+		CSkaterProfile* pProfile = m_Profiles.GetItemByIndex( i, &key );
+		pProfile->Reset();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::AddAllProProfileInfo(Script::CStruct *pStuff)
+{
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+	uint32 tableSize = m_Profiles.getSize( );
+	for ( uint32 i = 0; i < tableSize; i++ )
+	{
+		int key;
+		CSkaterProfile* pProfile = m_Profiles.GetItemByIndex( i, &key );
+
+		// Only add the pro's, not the create-a-skater.
+		if (pProfile->IsPro())
+		{
+			pProfile->WriteIntoStructure(pStuff);
+		}
+	}
+	
+	
+	Dbg_MsgAssert(mp_CurrentProfile[m_CurrentProfileIndex],("NULL mp_CurrentProfile[%d]",m_CurrentProfileIndex));
+	
+	uint32 CurrentSkaterNameChecksum = mp_CurrentProfile[m_CurrentProfileIndex]->GetSkaterNameChecksum();
+	pStuff->AddComponent(Script::GenerateCRC("CurrentSkater"),ESYMBOLTYPE_NAME,(int)CurrentSkaterNameChecksum);
+	
+	Str::String DisplayName=mp_CurrentProfile[m_CurrentProfileIndex]->GetUIString("display_name");
+	const char *pDisplayName=DisplayName.getString();
+	pStuff->AddComponent(Script::GenerateCRC("DisplayName"),ESYMBOLTYPE_STRING,pDisplayName);
+	
+	/* The CAS file name is no longer needed since the cas info is now in the unified save file.
+	
+	const char *pFileName=mp_CurrentProfile[m_CurrentProfileIndex]->GetCASFileName();
+	pStuff->AddString("CASFileName",pFileName);
+	
+	// The s_insert_game_save_info function in mcfuncs.cpp is also used to determine ahead of time
+	// how much space a particular type of save will use, so that we can check that there is enough
+	// space on the memcard.
+	// However, at that point the file name for the cas save is not known, because the user has not
+	// chosen it yet.
+	// So add a pad string, so that whatever the current filename happens to be, the amount of
+	// space used will be the same once the user has chosen a filename.
+	int pad=Script::GetInteger("MAX_MEMCARD_FILENAME_LENGTH",Script::ASSERT)-strlen(pFileName);
+	Dbg_MsgAssert(pad>=0 && pad<100,("Oops! '%s'",pFileName));
+	char p_foo[100];
+	for (int i=0; iAddString("FileNamePad",p_foo);
+	*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::LoadAllProProfileInfo(Script::CStruct *pStuff)
+{
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+	
+	uint32 tableSize = m_Profiles.getSize( );
+	for ( uint32 i = 0; i < tableSize; i++ )
+	{
+		int key;
+		CSkaterProfile* pProfile = m_Profiles.GetItemByIndex( i, &key );
+
+		if (pProfile->IsPro())
+		{
+			uint32 Name=pProfile->GetSkaterNameChecksum();
+			pProfile->ReadFromStructure(Name,pStuff);
+		}	
+	}
+	
+	uint32 CurrentSkaterNameChecksum=0;
+	pStuff->GetChecksum("CurrentSkater",&CurrentSkaterNameChecksum);
+	ApplyTemplateToCurrentProfile(CurrentSkaterNameChecksum);
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::AddCASProfileInfo(Script::CStruct *pStuff)
+{
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+	uint32 tableSize = m_Profiles.getSize( );
+	for ( uint32 i = 0; i < tableSize; i++ )
+	{
+		int key;
+		CSkaterProfile* pProfile = m_Profiles.GetItemByIndex( i, &key );
+
+		if (!pProfile->IsPro())
+		{
+			pProfile->WriteIntoStructure(pStuff);
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::LoadCASProfileInfo( Script::CStruct *pStuff, bool load_info )
+{
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+
+	ApplyTemplateToCurrentProfile(0xa7be964/*custom*/);
+	CSkaterProfile* pProfile=GetCurrentProfile();
+
+	uint32 Name = Script::GenerateCRC("custom");
+	Script::CStruct *pSkaterInfo=NULL;
+	pStuff->GetStructure(Name,&pSkaterInfo);
+   
+	Dbg_MsgAssert(pSkaterInfo,("Memory card data is missing info for skater '%s'",Script::FindChecksumName(Name)));
+	
+	// load only if there is a version number and it is current.
+	int Version=0;
+	if (pSkaterInfo->GetInteger("version_number",&Version))
+	{
+		if (Version==CSkaterProfile::vVERSION_NUMBER)
+		{
+			Script::CStruct *pTemp=NULL;
+	
+			pSkaterInfo->GetStructure("Appearance",&pTemp);
+			Gfx::CModelAppearance* pAppearance=pProfile->GetAppearance();
+			Dbg_MsgAssert(pAppearance,("NULL pAppearance"));
+			pAppearance->Load(pTemp);
+	
+//			CSkaterCareer* pCareer=pProfile->GetCareer();
+//			Dbg_MsgAssert(pCareer,("NULL pCareer"));
+//			pCareer->ReadFromStructure(pSkaterInfo);
+	
+			// GJ:  maybe this function could be refactored to use
+			// CSkaterProfile's ReadFromStructure() function?
+			// i'm not sure why there's one set of code for the pros
+			// and one for the custom skater
+			Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+			Dbg_MsgAssert( (!pProfile->IsPro()) == (pFaceTexture!=NULL), ( "Only custom skaters should have face textures (Pro=%d, FaceTexture=%p)", pProfile->IsPro(), pFaceTexture ) )
+			if ( pFaceTexture )
+			{
+				bool faceTextureFound = pSkaterInfo->GetStructure( CRCD(0xf0d3bc94,"FaceTexture"), &pTemp );
+				if ( faceTextureFound )
+				{
+					// load the face texture, if it exists
+					pFaceTexture->ReadFromStructure( pTemp );
+					printf( "Face texture found\n" );
+				}
+				else
+				{
+					printf( "Face texture not found\n" );
+				}
+				pFaceTexture->SetValid( faceTextureFound );
+			}
+            
+            // store current stats
+            int air_value;
+            int run_value;
+            int ollie_value;
+            int speed_value;
+            int spin_value;
+            int flip_speed_value;
+            int switch_value;
+            int rail_balance_value;
+            int lip_balance_value;
+            int manual_balance_value;
+            air_value = pProfile->GetStatValue(CRCD(0x439f4704,"air"));
+            run_value = pProfile->GetStatValue(CRCD(0xaf895b3f,"run"));
+            ollie_value = pProfile->GetStatValue(CRCD(0x9b65d7b8,"ollie"));
+            speed_value = pProfile->GetStatValue(CRCD(0xf0d90109,"speed"));
+            spin_value = pProfile->GetStatValue(CRCD(0xedf5db70,"spin"));
+            flip_speed_value = pProfile->GetStatValue(CRCD(0x6dcb497c,"flip_speed"));
+            switch_value = pProfile->GetStatValue(CRCD(0x9016b4e7,"switch"));
+            rail_balance_value = pProfile->GetStatValue(CRCD(0xf73a13e3,"rail_balance"));
+            lip_balance_value = pProfile->GetStatValue(CRCD(0xae798769,"lip_balance"));
+            manual_balance_value = pProfile->GetStatValue(CRCD(0xb1fc0722,"manual_balance"));
+
+			int num_trickslots;
+			num_trickslots = pProfile->GetNumSpecialTrickSlots();
+			Script::CStruct* pSpecialTricks = new Script::CStruct();
+			pSpecialTricks->AppendStructure( pProfile->GetSpecialTricksStructure() );
+            
+            
+            pTemp=NULL;
+            pSkaterInfo->GetStructure("Info",&pTemp);
+            Script::CStruct* pInfo = pProfile->GetInfo();
+            Dbg_Assert(pInfo);
+            pInfo->Clear();
+            pInfo->AppendStructure(pTemp);
+            
+            if ( !load_info )
+            {
+                // restore stats
+                pProfile->SetPropertyValue(CRCD(0x439f4704,"air"), air_value);
+                pProfile->SetPropertyValue(CRCD(0xaf895b3f,"run"), run_value);
+                pProfile->SetPropertyValue(CRCD(0x9b65d7b8,"ollie"), ollie_value);
+                pProfile->SetPropertyValue(CRCD(0xf0d90109,"speed"), speed_value);
+                pProfile->SetPropertyValue(CRCD(0xedf5db70,"spin"), spin_value);
+                pProfile->SetPropertyValue(CRCD(0x6dcb497c,"flip_speed"), flip_speed_value);
+                pProfile->SetPropertyValue(CRCD(0x9016b4e7,"switch"), switch_value);
+                pProfile->SetPropertyValue(CRCD(0xf73a13e3,"rail_balance"), rail_balance_value);
+                pProfile->SetPropertyValue(CRCD(0xae798769,"lip_balance"), lip_balance_value);
+                pProfile->SetPropertyValue(CRCD(0xb1fc0722,"manual_balance"), manual_balance_value);
+
+				// restore trickslot number
+				pProfile->SetPropertyValue( CRCD(0xac9b9eda,"max_specials"), num_trickslots );
+				pProfile->GetSpecialTricksStructure()->Clear();
+				pProfile->GetSpecialTricksStructure()->AppendStructure( pSpecialTricks );
+            }
+			delete pSpecialTricks;
+            
+
+            // First deck is always unlocked, so make sure this is the default just in case DeckFlags is not found.
+			int DeckFlags=1;
+			pSkaterInfo->GetInteger("DeckFlags",&DeckFlags);
+			//Front::SetDeckFlags(Name,(uint32)DeckFlags);
+			
+			return;
+		}
+		Dbg_Warning("\n\nBad version number of %d in saved custom skater. Required version = %d\n\n",Version,CSkaterProfile::vVERSION_NUMBER);
+		return;
+	}		
+	Dbg_Warning("\n\nNo version number in saved custom skater. Required version = %d\n\n",CSkaterProfile::vVERSION_NUMBER);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPlayerProfileManager::AddTemporaryProfile( uint32 profileName )
+{
+	Dbg_MsgAssert( m_TemporaryProfileCount < vMAX_TEMPORARY_PROFILES, ( "Too many temporary profiles (max=%d)", vMAX_TEMPORARY_PROFILES ) );
+
+	for ( int i = 0; i < m_TemporaryProfileCount; i++ )
+	{
+		if ( m_TemporaryProfileChecksums[i] == profileName )
+		{
+			Dbg_MsgAssert( 0, ( "Temporary profile checksum %s was already used\n", Script::FindChecksumName(profileName) ) );
+		}
+	}
+
+	mp_TemporaryProfiles[m_TemporaryProfileCount] = new Obj::CSkaterProfile;
+	m_TemporaryProfileChecksums[m_TemporaryProfileCount] = profileName;
+
+	m_TemporaryProfileCount++;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile* CPlayerProfileManager::GetTemporaryProfile(uint32 profileName)
+{
+	for ( int i = 0; i < m_TemporaryProfileCount; i++ )
+	{
+		if ( m_TemporaryProfileChecksums[i] == profileName )
+		{
+			return mp_TemporaryProfiles[i];
+		}
+	}
+	
+	Dbg_MsgAssert( 0, ( "Couldn't find temporary profile with checksum %s\n", Script::FindChecksumName(profileName) ) );
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPlayerProfileManager::AddNewProfile( Script::CStruct* pParams )
+{
+	Dbg_Assert( pParams );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	// profileChecksum = hawk, thomas, custom, etc.
+	uint32 profileChecksum;
+	pParams->GetChecksum( "name", &profileChecksum, true );
+	
+	// add to the lookup table
+	CSkaterProfile* pPlayerProfile = new CSkaterProfile;
+	pPlayerProfile->Reset( pParams );
+	m_Profiles.PutItem( profileChecksum, pPlayerProfile );
+
+#ifdef __USER_GARY__
+//	uint8 tBuf[vMAX_APPEARANCE_BUFFER_SIZE];
+//	uint32 size = pPlayerProfile->WriteToBuffer(tBuf, vMAX_APPEARANCE_BUFFER_SIZE);
+//	Dbg_Assert( size < vMAX_PRACTICAL_APPEARANCE_DATA_SIZE );
+//	Dbg_Message("*********** AddNewProfile %s appearance data size = %d bytes\n", Script::FindChecksumName(profileChecksum), size);
+#endif
+
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CPlayerProfileManager::LockCurrentSkaterProfileIndex( bool locked )
+{
+	// GJ:  This is purely for debugging purposes,
+	// to catch any accesses to the current skater profile index.
+	// When using the CModelBuilder class, we NEVER want to use
+	// the current skater profile index, because it may or may
+	// not be valid in a network or splitscreen game.
+	// So, the CModelBuilder class will turn this lock on
+	// at the beginning of the model creation process, and
+	// turn it off at the end.  Any calls to GetCurrentSkaterProfileIndex
+	// will assert while locked.
+
+	m_LockCurrentSkaterProfileIndex = locked;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
diff --git a/Code/Sk/Objects/PlayerProfileManager.h b/Code/Sk/Objects/PlayerProfileManager.h
new file mode 100644
index 0000000..161b849
--- /dev/null
+++ b/Code/Sk/Objects/PlayerProfileManager.h
@@ -0,0 +1,128 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       PlayerProfileManager.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  4/29/2002
+//****************************************************************************
+
+#ifndef __OBJECTS_PLAYERPROFILEMANAGER_H
+#define __OBJECTS_PLAYERPROFILEMANAGER_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+                               
+#include 
+                                              
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class CPlayerProfileManager : public Spt::Class
+{
+	enum
+	{
+		vMAX_PROFILES = 32,
+		vMAX_PLAYERS = GameNet::vMAX_LOCAL_CLIENTS,
+
+		// this is the biggest packet we can reasonably send
+		// over a network packet (it's not really based on 
+		// the max packet size, but rather the biggest packet
+		// that we've seen work so far...  the max packet size
+		// specified in net.h seems to be too aggressive)
+		vMAX_PRACTICAL_APPEARANCE_DATA_SIZE = 1150,
+
+		// this is just the size of a temp buffer
+		vMAX_APPEARANCE_BUFFER_SIZE = 4096,
+
+		// temporary profiles, used for remembering/restoring
+		// skater profiles (like for credits)
+		vMAX_TEMPORARY_PROFILES = 3,
+	};
+	
+	public:
+		CPlayerProfileManager();
+		~CPlayerProfileManager();
+
+	public:
+		void					Init();
+
+		// these deal with players (0 to numLocalClients)
+		void					SetCurrentProfileIndex(int i);
+		int						GetCurrentProfileIndex();
+		void					ApplyTemplateToCurrentProfile(uint32 checksum);
+		CSkaterProfile*			GetCurrentProfile();
+		CSkaterProfile*			GetProfile(int i);
+//		bool					GetGlobalFlag(int flag);
+		void					SyncPlayer2();
+
+		// these deal with templates (0 to numPros)
+		CSkaterProfile*			GetProfileTemplate(uint32 checksum);
+		CSkaterProfile*			GetProfileTemplateByIndex(int i);
+
+		// for populating front end menus
+		uint32					GetNumProfileTemplates();
+		uint32					GetProfileTemplateChecksum(int i);
+		
+		void					AddAllProProfileInfo(Script::CStruct *pStuff);
+		void					LoadAllProProfileInfo(Script::CStruct *pStuff);
+		void					AddCASProfileInfo(Script::CStruct *pStuff);
+		void					LoadCASProfileInfo(Script::CStruct *pStuff, bool load_info);
+		
+		void					Reset();
+		bool					AddNewProfile(Script::CStruct* pParams);
+		void					LockCurrentSkaterProfileIndex( bool locked );
+		
+		bool					AddTemporaryProfile(uint32 checksum);
+		CSkaterProfile*			GetTemporaryProfile(uint32 checksum);
+
+	protected:
+		Lst::LookupTable	m_Profiles;
+		CSkaterProfile*			mp_CurrentProfile[GameNet::vMAX_LOCAL_CLIENTS];
+		int						m_CurrentProfileIndex;
+		bool					m_LockCurrentSkaterProfileIndex;
+		
+		int						m_TemporaryProfileCount;
+		uint32					m_TemporaryProfileChecksums[vMAX_TEMPORARY_PROFILES];
+		CSkaterProfile*			mp_TemporaryProfiles[vMAX_TEMPORARY_PROFILES];
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+
+} // namespace Obj
+
+#endif	// __OBJECTS_PLAYERPROFILEMANAGER_H
diff --git a/Code/Sk/Objects/SkaterButton.cpp b/Code/Sk/Objects/SkaterButton.cpp
new file mode 100644
index 0000000..6ad457c
--- /dev/null
+++ b/Code/Sk/Objects/SkaterButton.cpp
@@ -0,0 +1,142 @@
+// skaterbutton.cpp
+
+#include	
+
+#include	
+
+const	int	pressure_threshold = 64;
+
+#ifdef __NOPT_ASSERT__
+void		CSkaterButton::Update(int current_pressure, bool debug) 		// Update the pad based on teh current pressure
+#else
+void		CSkaterButton::Update(int current_pressure) 		// Update the pad based on teh current pressure
+#endif
+{
+	// if were are debouncing the button then update the debounce state
+	// based on time and release, and ignore the button if still debouncing
+	if (m_debounce_time)
+	{
+		// if not pressed, or debounce time expired
+		if (current_pressure <= pressure_threshold)
+		{
+//			dodgy_test(); printf("Ending Debouncing as not pressed\n");
+			m_debounce_time = 0;
+		}
+		else if ((float)Tmr::GetTime() > m_debounce_time)
+		{
+//			dodgy_test(); printf("Ending Debouncing as time expired\n");
+			m_debounce_time = 0;
+		}
+		else
+		{
+//			dodgy_test(); printf("Debouncing button\n");
+			return;
+		}
+	}
+
+	m_pressure = current_pressure;
+	if (m_pressure > pressure_threshold)
+	{
+		// button is currently pressed
+		if (!m_pressed)
+		{
+			m_pressed_time = Tmr::ElapsedTime(0);
+			#ifdef __NOPT_ASSERT__
+			if (debug)			
+			{
+				printf("%d: +++ Triggered %s +++\n",(int)Tmr::GetRenderFrame(),Script::FindChecksumName(m_name_checksum));
+			}
+			#endif
+			// but not previously pressed, so set the "triggered" flag
+			m_triggered = true;
+		}
+		m_pressed = true;
+	}
+	else
+	{
+		// button is currently NOT pressed
+		if (m_pressed)
+		{
+			// but WAS previously pressed, so set the "released" flag
+			m_released_time = Tmr::ElapsedTime(0);
+			#ifdef __NOPT_ASSERT__
+			if (debug)			
+			{
+				printf("%d: --- Released  %s ----\n",(int)Tmr::GetRenderFrame(),Script::FindChecksumName(m_name_checksum));
+			}
+			#endif
+			m_released = true;
+		}
+		m_pressed = false;
+	}
+
+}
+
+void		CSkaterButton::SetPressed( bool pressed )		// Set the button's "Pressed" state
+{
+	if( pressed )
+	{
+		// button is currently pressed
+		if( !m_pressed )
+		{
+			m_pressed_time = Tmr::ElapsedTime(0);
+			// but not previously pressed, so set the "triggered" flag
+			m_triggered = true;
+		}
+		m_pressed = true;
+	}
+	else
+	{
+		// button is currently NOT pressed
+		if (m_pressed)
+		{
+			// but WAS previously pressed, so set the "released" flag
+			m_released = true;
+		}
+		m_pressed = false;
+	}
+	m_pressed = pressed;
+}
+
+uint32		CSkaterButton::GetReleasedTime()					// Get time elapsed since m_released_time
+{
+	return	Tmr::ElapsedTime(m_released_time);
+}
+
+uint32		CSkaterButton::GetPressedTime()					// Get time elapsed since m_presssed_time
+{
+	return	Tmr::ElapsedTime(m_pressed_time);
+}
+
+void		CSkaterButton::ClearTrigger()						// Clear the trigger
+{
+	m_triggered = false;
+}
+
+void		CSkaterButton::ClearRelease()						// Clear the trigger
+{
+	m_released = false;
+}
+
+
+void 		CSkaterButton::SetName(const char *p_name)
+{
+	m_name_checksum = Crc::GenerateCRCFromString(p_name);
+}
+
+
+void		CSkaterButton::SetDebounce(float time)
+{
+	// Mick:  When debouncing, we ignore furthur input until the time has elapsed
+	// or the button "pressed" state changes from what it was at the start of the debounce 
+
+//	printf ("SetDebounce #########################################################\n");
+//	
+//	printf ("GetTime = %d, m_pressed = %d, m_pressed_time = %d, m_triggered = %d\n",
+//			Tmr::ElapsedTime(0), m_pressed  , m_pressed_time , m_triggered );
+	
+	
+	m_debounce_time = time;
+}
+
+
diff --git a/Code/Sk/Objects/SkaterButton.h b/Code/Sk/Objects/SkaterButton.h
new file mode 100644
index 0000000..f1a773e
--- /dev/null
+++ b/Code/Sk/Objects/SkaterButton.h
@@ -0,0 +1,81 @@
+// SkaterButton.h
+
+#ifndef	__SK_OBJECTS_SKATERBUTTON_H__
+#define	__SK_OBJECTS_SKATERBUTTON_H__
+
+#include 
+#include 
+#include 
+
+
+// A SkaterButton contains the state of a single pad button
+// in a way that is specific to control issues
+// initially this is similar to the old SPad structure in THPS-PSX
+// but will probably be extended to handle analog buttons
+// and storing, inspecting and flagging the history of button events 
+
+class CSkaterButton : public Spt::Class
+{
+
+
+public:
+#ifdef __NOPT_ASSERT__
+	void		Update(int current_pressure, bool debug = false); 		// Update the button based on the current pressure
+#else
+	void		Update(int current_pressure); 		// Update the button based on the current pressure
+#endif
+	bool		GetPressed() {return m_pressed;}	// Get current "Pressed" state
+	bool		GetTriggered();						// Get current "Triggered" state
+	void		SetPressed( bool pressed );			// Set the button's "Pressed" state
+	int			GetPressure();						// Get current pressure
+	void		ClearTrigger();						// Clear the trigger
+	void 		SetName(const char *p_name);		// Set name of button (mostly for dubugging)
+
+	void		SetDebounce(float time);			// set time to debounce
+	Tmr::Time	GetDebounceTime() { return static_cast< Tmr::Time >(m_debounce_time); }
+
+	bool		GetReleased();
+	void		ClearRelease();				
+	
+	uint32		GetPressedTime();  					// Get m_pressed_time
+	uint32		GetReleasedTime();					// Get m_released_time
+
+private:		
+	
+	
+	bool		m_pressed;  						// true if pressed now, indicates the current state of the button
+	uint32		m_pressed_time;						// time when button was last pressed
+	uint32		m_released_time;					// time when button was last released
+	int			m_pressure;							// current button presure;
+	bool		m_triggered;	  					// true if was triggered, can be cleared
+	int 		m_released;							// true if we released, can be cleared (for debouncing)
+	uint32		m_name_checksum;					// Checksum of button name
+	float		m_debounce_time;					// time until we we remain debounced
+	//bool		m_debounce_pressed;					// state when "debounce" called
+	//const char	*	mp_name;			  	 	// Pointer to name, used for debugging;
+	
+public:
+	uint32 GetName() {return m_name_checksum;}
+};
+
+inline bool		CSkaterButton::GetTriggered()		// Get current "Triggered" state
+{
+	return m_triggered;
+}
+
+inline bool		CSkaterButton::GetReleased()		
+{
+	return m_released;
+}
+
+inline int			CSkaterButton::GetPressure()	// Get current pressure
+{
+	return	m_pressure;
+}
+
+
+
+#endif	__SK_OBJECTS_SKATERBUTTON_H__
+
+
+
diff --git a/Code/Sk/Objects/SkaterPad.h b/Code/Sk/Objects/SkaterPad.h
new file mode 100644
index 0000000..8b83281
--- /dev/null
+++ b/Code/Sk/Objects/SkaterPad.h
@@ -0,0 +1,112 @@
+// SkaterPad.h
+
+#ifndef	__SK_OBJ_SKATERPAD_H__
+#define	__SK_OBJ_SKATERPAD_H__
+
+
+#include 
+#include 
+#include 
+#include 
+
+namespace Inp
+{
+	class Data;
+}
+
+// A CSkaterPad class contains the state of the pad controlling the skater 
+class  CSkaterPad  : public Spt::Class
+{
+	
+public:
+	CSkaterPad();
+
+	uint32			GetPressedMask( void );
+	
+	#ifdef __NOPT_ASSERT__
+	void			Update ( Inp::Data* input, bool debug = false );
+	#else
+	void			Update ( Inp::Data* input );
+	#endif
+	
+	void			Zero (   );
+	void			Reset (   );
+	
+	float			GetScaledAnalogStickMagnitude ( float analog_x, float analog_y, float analog_angle  );
+	float			GetScaledLeftAnalogStickMagnitude (   ) { return GetScaledAnalogStickMagnitude(m_leftX, m_leftY, m_leftAngle); }
+	float			GetScaledRightAnalogStickMagnitude (   ) { return GetScaledAnalogStickMagnitude(m_rightX, m_rightY, m_rightAngle); }
+	
+	bool			IsLeftAnalogUpPressed();
+	bool			IsLeftAnalogDownPressed();
+	void			DebounceLeftAnalogUp ( float duration );
+	void			DebounceLeftAnalogDown( float duration );
+
+	CSkaterButton 	m_up;
+	CSkaterButton 	m_down;
+	CSkaterButton 	m_left;
+	CSkaterButton 	m_right;
+	CSkaterButton 	m_L1;
+	CSkaterButton 	m_L2;
+	CSkaterButton 	m_L3;
+	CSkaterButton 	m_R1;
+	CSkaterButton 	m_R2;
+	CSkaterButton 	m_R3;
+	CSkaterButton 	m_circle;
+	CSkaterButton 	m_square;
+	CSkaterButton 	m_triangle;
+	CSkaterButton 	m_x;
+	CSkaterButton 	m_start;
+	CSkaterButton 	m_select;
+
+	float			m_rightX;
+	float			m_rightY;
+	float			m_leftX;
+	float			m_leftY;
+
+	float			m_scaled_rightX;
+	float			m_scaled_rightY;
+	float			m_scaled_leftX;
+	float			m_scaled_leftY;
+
+	// angle and amount we are push the direction stick
+	float			m_rightAngle;
+	float			m_rightLength;
+	float			m_leftAngle;
+	float			m_leftLength;
+	
+	Tmr::Time		m_leftAnalogUpDebounceTime;
+	Tmr::Time		m_leftAnalogDownDebounceTime;
+	
+	// Given the checksum of a button name, this will return that button.
+	CSkaterButton*	GetButton(uint32 NameChecksum);
+
+	// related utility functions
+	
+	static uint32	sGetDirection(bool Up, bool Down, bool Left, bool Right);
+	static float	sGetAngleFromDPad(bool Up, bool Down, bool Left, bool Right);
+};
+
+inline bool CSkaterPad::IsLeftAnalogUpPressed (   )
+{
+	return m_leftAnalogUpDebounceTime == 0 && m_leftLength != 0.0f && Mth::Abs(m_leftAngle) < Mth::DegToRad(45.0f);
+}
+
+inline bool CSkaterPad::IsLeftAnalogDownPressed (   )
+{
+	return m_leftAnalogDownDebounceTime == 0 && m_leftLength != 0.0f && (Mth::PI - Mth::Abs(m_leftAngle)) < Mth::DegToRad(45.0f);
+}
+
+inline void CSkaterPad::DebounceLeftAnalogUp ( float duration )
+{
+	m_leftAnalogUpDebounceTime = Tmr::GetTime() + static_cast< Tmr::Time >(1000.0f * duration);
+}
+
+inline void CSkaterPad::DebounceLeftAnalogDown ( float duration )
+{
+	m_leftAnalogDownDebounceTime = Tmr::GetTime() + static_cast< Tmr::Time >(1000.0f * duration);
+}
+
+typedef CSkaterPad CControlPad;
+
+#endif	//	__SK_OBJ_SKATERPAD_H__
+
diff --git a/Code/Sk/Objects/SkaterProfile.cpp b/Code/Sk/Objects/SkaterProfile.cpp
new file mode 100644
index 0000000..a7d6b92
--- /dev/null
+++ b/Code/Sk/Objects/SkaterProfile.cpp
@@ -0,0 +1,1044 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       SkaterProfile.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/29/2000
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+// If possible, try to remove the dependency on these include files...
+#include 
+#include                          // for updatetrickmapping
+#include            // for updatetrickmapping
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+// TODO:  Add trick-config and special-trick-config classes.
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile::CSkaterProfile(void)
+{
+	Reset();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile::CSkaterProfile(const CSkaterProfile& skaterProfile)
+{
+	// use the overridden assignment operator
+	*this = skaterProfile;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterProfile& CSkaterProfile::operator=(const CSkaterProfile& skaterProfile)
+{
+	if ( &skaterProfile == this )
+	{
+		return *this;
+	}
+
+	// use the overridden assignment operator
+	m_Appearance = skaterProfile.m_Appearance;
+
+	// info assignment is okay, bec. structs have an overridden assignment operator
+	m_Info = skaterProfile.m_Info;
+
+	return *this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::Reset(Script::CStruct* pParams)
+{
+	// this should get called once at the beginning of the game,
+	// and then whenever you want to clear it out to its initial state
+	// (like creating a new pro)
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	// destroy the face texture, if one exists
+	m_Appearance.DestroyFaceTexture();
+	
+	m_Appearance.Init();
+	
+	m_Info.Clear();
+
+	Script::CStruct* pTemp;
+	
+	// TODO:  The trick mapping and the specials
+	// should be in their own structures, not within the
+	// info...
+
+	// at a minimum, the info structure must contain the trick_mapping structure
+	pTemp = new Script::CStruct;
+	m_Info.AddComponent( CRCD(0xd544aa2d,"trick_mapping"), ESYMBOLTYPE_STRUCTUREPOINTER, (int)pTemp );
+
+	// as well as a special tricks array
+	pTemp = new Script::CStruct;
+	m_Info.AddComponent( CRCD(0xddbee809,"specials"), ESYMBOLTYPE_STRUCTUREPOINTER, (int)pTemp );
+
+	Mem::Manager::sHandle().PopContext();
+
+	if ( pParams )
+	{
+		//	Dbg_Message( "Alternate constructor for skater profile called\n");
+
+		uint32 appearanceStructure;
+		pParams->GetChecksum( CRCD(0x1d6f290c,"default_appearance"), &appearanceStructure, true );
+		m_Appearance.Load( appearanceStructure );
+
+		// set the trick style, whether he's a pro, etc.
+		m_Info.Clear();
+		m_Info.AppendStructure( pParams );
+
+		if ( !IsPro() )
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+			// create a face texture, only if it's a custom skater
+			m_Appearance.CreateFaceTexture();
+			
+			// for debugging purposes, set up a test face
+			// Gfx::CFaceTexture* pFaceTexture = m_Appearance.GetFaceTexture();
+			// pFaceTexture->LoadFace( "faces\\CS_NSN_head_test_kurt" );
+			
+			Mem::Manager::sHandle().PopContext();
+		}		
+
+		// initialize the trick mappings to the default values, defined in protricks.q
+		uint32 trickMappingChecksum;
+		pParams->GetChecksum( CRCD(0xe365363e,"default_trick_mapping"), &trickMappingChecksum, Script::ASSERT );
+
+		Script::CStruct* pGlobalTrickMappingStructure;
+		pGlobalTrickMappingStructure = Script::GetStructure(trickMappingChecksum, Script::ASSERT);
+
+		Script::CStruct* pLocalTrickMappingStructure;
+		m_Info.GetStructure( CRCD(0xd544aa2d,"trick_mapping"), &pLocalTrickMappingStructure, Script::ASSERT );
+		pLocalTrickMappingStructure->Clear();
+		pLocalTrickMappingStructure->AppendStructure( pGlobalTrickMappingStructure );
+	}
+	else
+	{
+//		Dbg_Message( "Warning: profile was created without valid data" );
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::PartialReset(Script::CStruct* pParams)
+{
+    Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+	// destroy the face texture, if one exists
+	m_Appearance.DestroyFaceTexture();
+	
+	m_Appearance.Init();
+	
+    Script::CStruct* pTemp;
+	
+    // at a minimum, the info structure must contain the trick_mapping structure
+	pTemp = new Script::CStruct;
+	m_Info.AddComponent( CRCD(0xd544aa2d,"trick_mapping"), ESYMBOLTYPE_STRUCTUREPOINTER, (int)pTemp );
+
+	// as well as a special tricks array
+	pTemp = new Script::CStruct;
+	m_Info.AddComponent( CRCD(0xddbee809,"specials"), ESYMBOLTYPE_STRUCTUREPOINTER, (int)pTemp );
+
+	Mem::Manager::sHandle().PopContext();
+
+	if ( pParams )
+	{
+        uint32 appearanceStructure;
+		pParams->GetChecksum( CRCD(0x1d6f290c,"default_appearance"), &appearanceStructure, true );
+		m_Appearance.Load( appearanceStructure );
+
+        // copy just the info I want into it
+        const char* name;
+        int num;
+        uint32 checksum;
+
+        pParams->GetString( CRCD(0x2ab66cb8,"display_name"), &name, Script::ASSERT );
+        m_Info.AddString("display_name", name );
+        pParams->GetString( CRCD(0x562e3ecd,"first_name"), &name, Script::ASSERT );
+        m_Info.AddString("first_name", name );
+        pParams->GetString( CRCD(0x2820e997,"file_name"), &name, Script::ASSERT );
+        m_Info.AddString("file_name", name );
+        pParams->GetString( CRCD(0x6c375b95,"hometown"), &name, Script::ASSERT );
+        m_Info.AddString("hometown", name );
+
+        pParams->GetInteger( CRCD(0x6f14c39c,"skater_index"), &num, Script::ASSERT );
+        m_Info.AddInteger("skater_index", num );
+        pParams->GetInteger( CRCD(0xd82f8ac8,"is_pro"), &num, Script::ASSERT );
+        m_Info.AddInteger("is_pro", num );
+        pParams->GetInteger( CRCD(0x3f813177,"is_male"), &num, Script::ASSERT );
+        m_Info.AddInteger("is_male", num );
+        pParams->GetInteger( CRCD(0x9f350e5,"is_head_locked"), &num, Script::ASSERT );
+        m_Info.AddInteger("is_head_locked", num );
+        pParams->GetInteger( CRCD(0x4564bab1,"is_locked"), &num, Script::ASSERT );
+        m_Info.AddInteger("is_locked", num );
+        pParams->GetInteger( CRCD(0x27eb9b9d,"is_hidden"), &num, Script::NO_ASSERT );
+        m_Info.AddInteger("is_hidden", num );
+        pParams->GetInteger( CRCD(0x5ecfef4d,"age"), &num, Script::ASSERT );
+        m_Info.AddInteger("age", num );
+        
+        pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &checksum, Script::ASSERT );
+        m_Info.AddChecksum("name", checksum );
+        pParams->GetChecksum( CRCD(0x7d02bcc3,"stance"), &checksum, Script::ASSERT );
+        m_Info.AddChecksum("stance", checksum );
+        pParams->GetChecksum( CRCD(0xc15dbf86,"pushstyle"), &checksum, Script::ASSERT );
+        m_Info.AddChecksum("pushstyle", checksum );
+        pParams->GetChecksum( CRCD(0xdfd4043e,"trickstyle"), &checksum, Script::ASSERT );
+        m_Info.AddChecksum("trickstyle", checksum );
+
+        Script::CStruct* p_special;
+        pParams->GetStructure( CRCD(0xddbee809,"specials"), &p_special, Script::ASSERT );
+        m_Info.AddStructure("specials", p_special );
+        
+#ifdef __PLAT_NGPS__
+        if ( !IsPro() )
+		{
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+			// create a face texture, only if it's a custom skater
+			m_Appearance.CreateFaceTexture();
+			
+			// for debugging purposes, set up a test face
+			// Gfx::CFaceTexture* pFaceTexture = m_Appearance.GetFaceTexture();
+			// pFaceTexture->LoadFace( "faces\\CS_NSN_head_test_kurt" );
+			
+			Mem::Manager::sHandle().PopContext();
+		}
+#endif
+
+		// initialize the trick mappings to the default values, defined in protricks.q
+		uint32 trickMappingChecksum;
+		pParams->GetChecksum( CRCD(0xe365363e,"default_trick_mapping"), &trickMappingChecksum, Script::ASSERT );
+
+		Script::CStruct* pGlobalTrickMappingStructure;
+		pGlobalTrickMappingStructure = Script::GetStructure(trickMappingChecksum, Script::ASSERT);
+
+		Script::CStruct* pLocalTrickMappingStructure;
+		m_Info.GetStructure( CRCD(0xd544aa2d,"trick_mapping"), &pLocalTrickMappingStructure, Script::ASSERT );
+		pLocalTrickMappingStructure->Clear();
+		pLocalTrickMappingStructure->AppendStructure( pGlobalTrickMappingStructure );
+	}
+	else
+	{
+//		Dbg_Message( "Warning: profile was created without valid data" );
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkaterProfile::WriteToBuffer(uint8 *pBuffer, uint32 BufferSize, bool ignoreFaceData )
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+
+	uint32 totalSize = 0;
+	uint32 chunkSize = 0;
+
+	chunkSize = m_Appearance.WriteToBuffer(pBuffer, BufferSize, ignoreFaceData);
+	totalSize += chunkSize;
+
+	// write out trick mappings
+	BufferSize -= chunkSize;
+	pBuffer += chunkSize;
+//	compress_trick_mappings( "trick_mapping" );
+	chunkSize = Script::WriteToBuffer(GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") ), pBuffer, BufferSize );
+	totalSize += chunkSize;
+//	decompress_trick_mappings( "trick_mapping" );
+
+	// write out special tricks...  these could
+	// theoretically be compressed, but not using
+	// the same method as the regular tricks
+	// (because they're in a different format)
+	BufferSize -= chunkSize;
+	pBuffer += chunkSize;
+	Script::CStruct* pSpecialsStructure = GetSpecialTricksStructure();
+	chunkSize = Script::WriteToBuffer(pSpecialsStructure, pBuffer, BufferSize);
+	totalSize += chunkSize;
+
+	// write out any extra info that needs to go across
+	// (i.e. stats, name, etc.)
+	// (some data, such as your default specials
+	// and your default appearance, aren't needed)
+	BufferSize -= chunkSize;
+	pBuffer += chunkSize;
+	chunkSize = write_extra_info_to_buffer(pBuffer, BufferSize);
+	totalSize += chunkSize;
+	
+	Mem::Manager::sHandle().PopContext();
+
+	return totalSize;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8* CSkaterProfile::ReadFromBuffer(uint8 *pBuffer)
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+
+	pBuffer = m_Appearance.ReadFromBuffer(pBuffer);
+
+	GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") )->Clear();
+	pBuffer = Script::ReadFromBuffer(GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") ), pBuffer);
+	
+//	decompress_trick_mappings( "trick_mapping" );
+
+	Script::CStruct* pStructure = GetSpecialTricksStructure();
+	Dbg_Assert( pStructure );
+	pStructure->Clear();
+	pBuffer = Script::ReadFromBuffer(pStructure, pBuffer);
+
+	// read any extra info that is needed in-game
+	// (some data, such as your default specials
+	// and your default appearance, aren't needed)
+	pBuffer = read_extra_info_from_buffer(pBuffer);
+	
+	Mem::Manager::sHandle().PopContext();
+
+	return pBuffer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkaterProfile::write_extra_info_to_buffer(uint8 *pBuffer, uint32 BufferSize)
+{	
+	Script::CStruct* pTempStructure = new Script::CStruct;
+
+	Dbg_Assert( pTempStructure );
+	
+	// for each component in info, see if it's needed in-game...
+	// such as the stats, trick style, etc.
+	// (most of the other information is only needed in the front end)
+	Script::CComponent *pComp = m_Info.GetNextComponent();
+	while ( pComp )
+	{
+		Script::CComponent* pNextComp = m_Info.GetNextComponent( pComp );
+
+		switch ( pComp->mNameChecksum )
+		{
+			case 0xa1dc81f9:	// name
+			case 0x7d02bcc3:	// stance
+			case 0xc15dbf86:	// pushstyle
+				pTempStructure->AddComponent( pComp->mNameChecksum, ESYMBOLTYPE_NAME, (int)pComp->mChecksum );
+				break;
+
+			case 0x2ab66cb8:	// display_name
+			case 0x2820e997:	// file_name
+			case 0x562e3ecd:	// first_name
+				pTempStructure->AddComponent( pComp->mNameChecksum, ESYMBOLTYPE_STRING, pComp->mpString );
+				break;
+
+			case 0x439f4704:	// air
+			case 0xaf895b3f:	// run
+			case 0x9b65d7b8:	// ollie
+			case 0xf0d90109:	// speed
+			case 0xedf5db70:	// spin
+			case 0x9016b4e7:	// switch
+			case 0xf73a13e3:	// rail_balance
+			case 0xae798769:	// lip_balance
+			case 0xb1fc0722:	// manual_balance
+			case 0xd82f8ac8:	// is_pro
+			case 0x6dcb497c:	// flip_speed
+			case 0x3f813177:	// is_male
+				pTempStructure->AddComponent( pComp->mNameChecksum, ESYMBOLTYPE_INTEGER, (int)pComp->mIntegerValue );
+				break;
+
+			default:
+				// doesn't need to go across
+				break;
+		}
+
+		pComp = pNextComp;
+	}
+
+	uint32 totalSize = Script::WriteToBuffer(pTempStructure, pBuffer, BufferSize);
+	
+	delete pTempStructure;
+	
+	return totalSize;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8* CSkaterProfile::read_extra_info_from_buffer(uint8 *pBuffer)
+{
+	Script::CStruct* pTempStructure = new Script::CStruct;
+
+	pBuffer = Script::ReadFromBuffer(pTempStructure, pBuffer);
+
+	m_Info.AppendStructure( pTempStructure );
+	
+	delete pTempStructure;
+	
+	return pBuffer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::compress_trick_mappings( const char* pTrickMappingName )
+{
+
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	CTrickChecksumTable* pTrickChecksumTable = skate_mod->GetTrickChecksumTable();
+
+	Script::CStruct* pTempStructure;
+	if ( !m_Info.GetStructure( pTrickMappingName, &pTempStructure ) )
+	{
+		Dbg_MsgAssert( 0, ( "Trick mapping %s not found...  already compressed?", pTrickMappingName ) );
+	}
+
+	int compressed = 0;
+	pTempStructure->GetInteger( CRCD(0x68a9b4e1,"compressed"), &compressed );
+	if ( compressed )
+	{
+		Dbg_MsgAssert( 0, ( "This structure is already compressed" ) );
+		return;
+	}
+
+	// take all the integers, and convert them to checksums
+	Script::CComponent *pComp = pTempStructure->GetNextComponent();
+	while ( pComp )
+	{
+		Script::CComponent* pNextComp = pTempStructure->GetNextComponent( pComp );
+
+
+		if (pComp && pComp->mType==ESYMBOLTYPE_NAME && pComp->mNameChecksum != CRCD(0x68a9b4e1,"compressed") )
+		{
+			int compressedIndex = pTrickChecksumTable->GetIndexFromChecksum( pComp->mChecksum );
+			if ( compressedIndex != -1 )
+			{			
+				uint32	nameChecksum = pComp->mNameChecksum; // Mick, need to store this now, as we are about to remove pComp
+				pTempStructure->RemoveComponent( nameChecksum );
+				pTempStructure->AddComponent( nameChecksum, ESYMBOLTYPE_INTEGER, compressedIndex );
+			}
+		}
+		pComp = pNextComp;
+	}
+
+	// add the compressed flag
+	pTempStructure->AddComponent( CRCD(0x68a9b4e1,"compressed"), ESYMBOLTYPE_INTEGER, 1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::decompress_trick_mappings( const char* pTrickMappingName )
+{
+	
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	CTrickChecksumTable* pTrickChecksumTable = skate_mod->GetTrickChecksumTable();
+	
+	Script::CStruct* pTempStructure;
+	if ( !m_Info.GetStructure( pTrickMappingName, &pTempStructure ) )
+	{
+		Dbg_MsgAssert( 0, ( "Trick mapping %s not found...  already compressed?", pTrickMappingName ) );
+	}
+
+	int compressed = 0;
+	pTempStructure->GetInteger( CRCD(0x68a9b4e1,"compressed"), &compressed );
+	if ( !compressed )
+	{
+#ifdef __NOPT_ASSERT__
+		Script::PrintContents(&m_Info);
+		Script::PrintContents(pTempStructure);
+#endif		
+		Dbg_MsgAssert( 0, ( "This structure is not compressed" ) );
+		return;
+	}
+
+	// take all the integers, and convert them to checksums
+	Script::CComponent *pComp = pTempStructure->GetNextComponent();
+	while ( pComp )
+	{
+		Script::CComponent* pNextComp = pTempStructure->GetNextComponent( pComp );
+
+		if (pComp && pComp->mType==ESYMBOLTYPE_INTEGER && pComp->mNameChecksum != CRCD(0x68a9b4e1,"compressed") )
+		{
+			int uncompressedChecksum = pTrickChecksumTable->GetChecksumFromIndex( pComp->mIntegerValue );
+			uint32	nameChecksum = pComp->mNameChecksum; // Mick, need to store this now, as we are about to remove pComp
+			pTempStructure->RemoveComponent( nameChecksum );
+			pTempStructure->AddComponent( nameChecksum, ESYMBOLTYPE_NAME, uncompressedChecksum );
+		}
+
+		pComp = pNextComp;
+	}
+
+	// reset the compressed flag
+	pTempStructure->AddComponent( CRCD(0x68a9b4e1,"compressed"), ESYMBOLTYPE_INTEGER, 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::WriteIntoStructure( Script::CStruct* pStuff )
+{	
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+	Script::CStruct *pSkaterInfo=new Script::CStruct;
+
+	pSkaterInfo->AddComponent( CRCD(0x1902c8f0,"version_number"), ESYMBOLTYPE_INTEGER, CSkaterProfile::vVERSION_NUMBER );
+	
+	Script::CStruct *pTemp=NULL;
+	
+	if (!IsPro())
+	{
+		pTemp=new Script::CStruct;
+		pTemp->AppendStructure( m_Appearance.GetStructure() );
+		pSkaterInfo->AddComponent( CRCD(0x554c7d6f,"Appearance"), ESYMBOLTYPE_STRUCTUREPOINTER, (int)pTemp );
+	}
+		
+	Gfx::CFaceTexture* pFaceTexture = m_Appearance.GetFaceTexture();
+	if ( pFaceTexture && pFaceTexture->IsValid() )
+	{
+		pTemp=new Script::CStruct;
+		pFaceTexture->WriteIntoStructure( pTemp );
+		pSkaterInfo->AddComponent( CRCD(0xf0d3bc94,"FaceTexture"), ESYMBOLTYPE_STRUCTUREPOINTER, (int)pTemp );
+	}
+
+	pTemp=new Script::CStruct;
+	pTemp->AppendStructure(&m_Info);
+	pSkaterInfo->AddComponent( CRCD(0x3476cea8,"Info"), ESYMBOLTYPE_STRUCTUREPOINTER, (int)pTemp );
+
+	uint32 Name=GetSkaterNameChecksum();
+	//pSkaterInfo->AddComponent(CRCD(0x95388a60,"DeckFlags"),ESYMBOLTYPE_INTEGER,(int)Front::GetDeckFlags(Name));
+	pSkaterInfo->AddComponent( CRCD(0x95388a60,"DeckFlags"), ESYMBOLTYPE_INTEGER, 0 );
+	
+	pStuff->AddComponent(Name,ESYMBOLTYPE_STRUCTUREPOINTER,(int)pSkaterInfo);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::ReadFromStructure( uint32 SkaterName, Script::CStruct* pStuff )
+{
+	// should only get called for pros,
+	// otherwise we need to implement face texture loading
+
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+	Script::CStruct *pSkaterInfo=NULL;
+	pStuff->GetStructure(SkaterName,&pSkaterInfo);
+	// If no skater, just return. Could be no skater if a new skater name has been added, but autoloading off
+	// a memory card with no info for that skater, so no assert.
+	if (!pSkaterInfo)
+	{
+		return;
+	}	
+		
+	Script::CStruct *pTemp=NULL;
+	
+	// There may not be an Appearance member, eg for the Pros.	
+	if (pSkaterInfo->GetStructure( CRCD(0x554c7d6f,"Appearance"), &pTemp ))
+	{
+		m_Appearance.Load(pTemp);
+	}	
+		
+	pTemp=NULL;
+	pSkaterInfo->GetStructure( CRCD(0x3476cea8,"Info"), &pTemp );
+	m_Info.Clear();
+	m_Info.AppendStructure( pTemp);
+	
+	// First deck is always unlocked, so make sure this is the default just in case DeckFlags is not found.
+	int DeckFlags=1;
+	pSkaterInfo->GetInteger( CRCD(0x95388a60,"DeckFlags"), &DeckFlags );
+	
+	//Front::SetDeckFlags(SkaterName,(uint32)DeckFlags);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CSkaterProfile::GetStatValue( uint32 property )
+{
+	int value;
+	m_Info.GetInteger( property, &value, true );
+	return value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::SetPropertyValue( uint32 property, int value )
+{
+	if ( value >= 0 && value <= 10 )
+	{
+		m_Info.AddInteger( property, value );
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::AwardStatPoint( int inc_val )
+{
+	int value;
+	m_Info.GetInteger( CRCD(0x471fc4dd,"points_available"), &value, true );
+	value+=inc_val;
+	m_Info.AddComponent( CRCD(0x471fc4dd,"points_available"), ESYMBOLTYPE_INTEGER, value );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CSkaterProfile::GetNumStatPointsAvailable()
+{
+	int value;
+	m_Info.GetInteger( CRCD(0x471fc4dd,"points_available"), &value, true );
+	return value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::AwardSpecialTrickSlot( int inc_val )
+{
+	int value;
+	m_Info.GetInteger( CRCD(0xac9b9eda,"max_specials"), &value, true );
+	value+=inc_val;
+	m_Info.AddComponent( CRCD(0xac9b9eda,"max_specials"), ESYMBOLTYPE_INTEGER, value );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkaterProfile::GetNumSpecialTrickSlots( void )
+{
+	int value;
+	m_Info.GetInteger( CRCD(0xac9b9eda,"max_specials"), &value, true );
+	return value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SSpecialTrickInfo CSkaterProfile::GetSpecialTrickInfo( int slot_num )
+{	
+	SSpecialTrickInfo theInfo;
+	
+	Script::CStruct* pStructure = GetSpecialTricksStructure();
+	Dbg_Assert( pStructure );
+
+	Script::CArray* pArray;
+	pStructure->GetArray( NONAME, &pArray, true );
+	Dbg_MsgAssert( pArray->GetSize() == vMAXSPECIALTRICKSLOTS, ( "Expected %i elements in specials array instead of %d", vMAXSPECIALTRICKSLOTS, pArray->GetSize() ) );
+
+	if ( !(slot_num >= 0 && slot_num < (int)pArray->GetSize()) )
+	{
+		Script::PrintContents( &m_Info );
+		Script::PrintContents( pStructure );
+		Dbg_MsgAssert( 0, ( "Slot index %d is out of range (must be between 0 and %d", slot_num, vMAXSPECIALTRICKSLOTS ) );
+	}
+	
+	Script::CStruct* pSubStructure = pArray->GetStructure( slot_num );
+	pSubStructure->GetChecksum( CRCD(0x5b077ce1,"trickname"), &theInfo.m_TrickName, true );
+	pSubStructure->GetChecksum( CRCD(0xa92a2280,"trickslot"), &theInfo.m_TrickSlot, true );
+	int is_cat = 0;
+	pSubStructure->GetInteger( CRCD(0xb56a8816,"isCat"), &is_cat, false );
+	if ( is_cat != 0 )
+	{
+		theInfo.m_isCat = true;
+	}
+	
+	return theInfo;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::SetSpecialTrickInfo( int slot_num, const SSpecialTrickInfo& theInfo, bool update_mappings )
+{
+	Script::CStruct* pStructure = GetSpecialTricksStructure();
+	Dbg_Assert( pStructure );
+
+	Script::CArray* pArray;
+	pStructure->GetArray( NONAME, &pArray, true );
+	Dbg_MsgAssert( pArray->GetSize() == vMAXSPECIALTRICKSLOTS, ( "Expected %i elements in specials array instead of %d", vMAXSPECIALTRICKSLOTS, pArray->GetSize() ) );
+
+	if ( !(slot_num >= 0 && slot_num < (int)pArray->GetSize()) )
+	{
+		// out of range index...
+		// this is possible if you press the square button while on the "Done" element
+		// this isn't the best place to screen this out, but it's the safest...
+		return false;
+	}
+	
+	uint32 trickName = theInfo.m_TrickName;
+	uint32 trickSlot = theInfo.m_TrickSlot;
+
+	if ( theInfo.IsUnassigned() )
+	{
+		// if the incoming trick is "unassigned"
+		// then clear that slot
+		trickName = CRCD(0xf60c9090,"unassigned");
+		trickSlot = CRCD(0xf60c9090,"unassigned");
+	}
+	
+	Script::CStruct* pSubStructure = pArray->GetStructure( slot_num );
+	pSubStructure->AddComponent( CRCD(0x5b077ce1,"trickname"), ESYMBOLTYPE_NAME, (int)trickName );
+	pSubStructure->AddComponent( CRCD(0xa92a2280,"trickslot"), ESYMBOLTYPE_NAME, (int)trickSlot );
+
+	if ( theInfo.m_isCat )
+	{
+		pSubStructure->AddInteger( CRCD(0xb56a8816,"isCat"), 1 );
+	}
+	else
+		pSubStructure->RemoveComponent( CRCD(0xb56a8816,"isCat") );
+
+	// now that a trick config has changed,
+	// update the trick mappings on any existing skaters
+	if ( update_mappings )
+	{
+        Mdl::Skate * pSkate = Mdl::Skate::Instance();
+        Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+        if ( pSkater )
+         {
+            Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+    		Dbg_Assert( pTrickComponent );
+    		pTrickComponent->UpdateTrickMappings( this );
+        }
+	}
+	Script::RunScript( CRCD(0xfab5c6eb,"disable_replays") );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CSkaterProfile::GetChecksumValue( uint32 field_id )
+{
+	uint32 value = 0;
+
+	switch ( field_id )
+	{
+		case 0xc15dbf86:	// pushstyle
+		{
+			m_Info.GetChecksum( CRCD(0xc15dbf86,"pushstyle"), &value, true );
+		}
+		break;
+		case 0x7d02bcc3:	// stance
+		{
+			m_Info.GetChecksum( CRCD(0x7d02bcc3,"stance"), &value, true );
+		}
+		break;
+		default:
+			Dbg_MsgAssert( 0, ("Unrecognized property %s", Script::FindChecksumName(field_id) ) );
+			break;
+	}
+
+	return value;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::SetSkaterProperty( uint32 field_id, const char* pPropertyString )
+{
+	Dbg_Message("Stub:  setting skater property %s: %s", Script::FindChecksumName(field_id), pPropertyString);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String	CSkaterProfile::GetUIString( const char* pFieldName )
+{
+	return GetUIString( Script::GenerateCRC( pFieldName ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String	CSkaterProfile::GetUIString( uint32 fieldID )
+{
+	return "Unimplemented";
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CSkaterProfile::GetTrickMapping( uint32 TrickMappingName )
+{
+	// pTrickMapping must be "trick_mapping"...  specials are accessed through special trick slots
+	Dbg_Assert( TrickMappingName==CRCD(0xd544aa2d,"trick_mapping") );
+	
+	Script::CStruct* pTrickMappingStructure;
+	m_Info.GetStructure(TrickMappingName,&pTrickMappingStructure,Script::ASSERT);
+	return pTrickMappingStructure;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CSkaterProfile::GetSpecialTricksStructure()
+{
+	Script::CStruct* pTrickMappingStructure;
+	m_Info.GetStructure( CRCD(0xddbee809,"specials"), &pTrickMappingStructure, Script::ASSERT);
+	return pTrickMappingStructure;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CSkaterProfile::GetSkaterNameChecksum( void )
+{
+	uint32 checksum;
+	m_Info.GetChecksum( CRCD(0xa1dc81f9,"name"), &checksum, true );
+	return checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::IsPro()
+{
+	int is_pro;
+	m_Info.GetInteger( CRCD(0xd82f8ac8,"is_pro"), &is_pro, true );
+	return is_pro;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::HeadIsLocked()
+{
+	int is_locked;
+	m_Info.GetInteger( CRCD(0x09f350e5,"is_head_locked"), &is_locked, true );
+	return is_locked;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::IsLocked()
+{
+	int is_locked;
+	m_Info.GetInteger( CRCD(0x4564bab1,"is_locked"), &is_locked, true );
+	return is_locked;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterProfile::IsSecret()
+{
+	return m_Info.ContainsFlag( CRCD(0xf3149ac5,"is_secret") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::SetHeadIsLocked( bool is_locked )
+{
+	m_Info.AddComponent( CRCD(0x09f350e5,"is_head_locked"), ESYMBOLTYPE_INTEGER, (int)is_locked );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char *CSkaterProfile::GetCASFileName()
+{
+	const char *p_file_name="Unimplemented";
+	m_Info.GetString( CRCD(0xf36c1878,"CASFileName"), &p_file_name);
+	return p_file_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::SetCASFileName(const char *pFileName)
+{
+	m_Info.AddString( CRCD(0xf36c1878,"CASFileName"), pFileName );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* CSkaterProfile::GetDisplayName()
+{
+	const char* pDisplayName;
+	m_Info.GetText( CRCD(0x2ab66cb8,"display_name"), &pDisplayName, Script::ASSERT );
+	return pDisplayName;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::ResetDefaultAppearance()
+{
+	Dbg_Message( "Stub:  Reset appearance here" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::ResetDefaultStats()
+{
+	Dbg_Message( "Stub:  Reset stats here" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterProfile::ResetDefaultTricks()
+{
+	Dbg_Message( "Stub:  Reset tricks here" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/SkaterProfile.h b/Code/Sk/Objects/SkaterProfile.h
new file mode 100644
index 0000000..8666cb5
--- /dev/null
+++ b/Code/Sk/Objects/SkaterProfile.h
@@ -0,0 +1,181 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       SkaterProfile.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  11/29/2000
+//****************************************************************************
+
+#ifndef __OBJECTS_SKATERPROFILE_H
+#define __OBJECTS_SKATERPROFILE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+// TODO:  Eventually, rename this CPlayerProfile,
+// and subclass to get the CSkaterProfile
+
+
+class CSkaterProfile : public Spt::Class
+{
+public:
+	enum
+	{
+		vVERSION_NUMBER = 2,
+		vMAXSPECIALTRICKSLOTS = 12,
+	};
+
+public:
+	// slowly phase this first constructor out
+	CSkaterProfile();							// set up default profile
+	CSkaterProfile( Script::CStruct* pParams );
+	CSkaterProfile( const CSkaterProfile& rhs );
+	CSkaterProfile&	operator=( const CSkaterProfile& rhs );
+
+public:
+	uint32						WriteToBuffer(uint8 *pBuffer, uint32 BufferSize, 
+											bool ignoreFaceData = false );
+	uint8*						ReadFromBuffer(uint8 *pBuffer);
+	void 						WriteIntoStructure( Script::CStruct* pStuff );
+	void 						ReadFromStructure( uint32 SkaterName, Script::CStruct* pStuff );
+
+	bool						Reset( Script::CStruct* pParams = NULL );						// reset to initial conditions
+    bool						PartialReset( Script::CStruct* pParams = NULL );				// reset non story stuff
+	
+	// for querying whether the profile meets certain criteria (i.e. is vert skater?  is rodney mullen?)
+	bool						ProfileEquals( Script::CStruct* pStructure );
+	
+	void						ResetDefaultAppearance();										   	
+	void						ResetDefaultTricks();
+	void						ResetDefaultStats();
+	
+public:
+	// general accessors
+	Gfx::CModelAppearance*		GetAppearance() {return &m_Appearance;}
+	Script::CStruct*			GetInfo() {return &m_Info;}
+	
+	// query functions
+	uint32						GetSkaterNameChecksum();
+	Script::CStruct*			GetTrickMapping( uint32 TrickMappingName );
+	Script::CStruct*			GetSpecialTricksStructure();
+	void						SetHeadIsLocked( bool is_locked );
+	bool						IsPro();
+	bool						IsSecret();
+	bool						HeadIsLocked();
+	bool						IsLocked();
+	const char*					GetCASFileName();
+	void						SetCASFileName(const char *pFileName);
+	
+	// UI
+	void						SetSkaterProperty( uint32 fieldId, const char* pPropertyName );
+	const char*					GetDisplayName();
+	uint32						GetChecksumValue( uint32 field_id );
+	Str::String					GetUIString( uint32 field_id );
+	Str::String					GetUIString( const char* field_name );
+	
+	// stats
+	int							GetStatValue( uint32 property );
+	bool						SetPropertyValue( uint32 property, int value );
+	bool						AwardStatPoint( int inc_val = 1 );
+	int							GetNumStatPointsAvailable();
+	
+	// tricks
+	bool						AwardSpecialTrickSlot( int inc_val = 1 );
+	uint32						GetNumSpecialTrickSlots();
+	SSpecialTrickInfo			GetSpecialTrickInfo( int slot_num );
+	bool						SetSpecialTrickInfo( int slot_num, const SSpecialTrickInfo& theInfo, bool update_mappings = true );
+
+protected:
+	void						compress_trick_mappings( const char* pTrickMappingName );
+	void						decompress_trick_mappings( const char* pTrickMappingName );
+	
+protected:
+	// net packet compression
+	uint32						write_extra_info_to_buffer(uint8* pBuffer, uint32 BufferSize);
+	uint8*						read_extra_info_from_buffer(uint8* pBuffer);
+
+private:
+	Gfx::CModelAppearance		m_Appearance;
+	Script::CStruct				m_Info;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+
+} // namespace Obj
+
+#endif	// __OBJECTS_SKATERPROFILE_H
+
+#if 0
+	// UI
+	void						SetSkaterProperty( uint32 fieldId, int value );
+	int							GetUIValue( uint32 checksum );
+	Str::String					GetUIString( uint32 checksum );
+#endif
+
+#if 0
+	// temp functions
+	bool						part_matches( uint32 list_name, uint32 desc_name, Script::CStruct* pIfStructure );
+	bool						sponsor_is_disqualified( uint32 partChecksum, uint32 descChecksum, Script::CArray* pSponsorArray );
+	bool						part_is_disqualified( uint32 list_name, uint32 desc_id );
+	bool						appearance_currently_contains( Script::CStruct* pIfStructure );
+#endif
+
+#if 0
+	// for setting up the skater model
+	bool						PartIsDisqualified( uint32 list_name, uint32 desc_id );
+	bool						ColorModulationAllowed( uint32 list_name );
+	
+	bool						SetCASOption(uint32 setName, const char* pDesiredName);
+	bool						SetOption(uint32 setName, uint32 descId);
+
+	void						SetHue( uint32 setName, int value );
+	void						SetSaturation( uint32 setName, int value );
+	void						SetValue( uint32 setName, int value );
+	
+	int							GetNumOptions( uint32 list_name );
+	Str::String					OptionName( uint32 list_name, int i );
+	uint32						OptionId( uint32 list_name, int i );
+	bool						OptionSelected( uint32 list_name, int i );
+	bool						OptionDisqualified( uint32 list_name, int i );
+	uint32						CurrentOption( uint32 list_name );
+	Str::String					GetOptionNameFromDescId( uint32 list_name, uint32 desc_id );
+	void						RandomizeAppearance();
+#endif
+
diff --git a/Code/Sk/Objects/SkaterTricks.cpp b/Code/Sk/Objects/SkaterTricks.cpp
new file mode 100644
index 0000000..49cbc46
--- /dev/null
+++ b/Code/Sk/Objects/SkaterTricks.cpp
@@ -0,0 +1,240 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		objects/SkaterTricks.h									**
+**																			**
+**	Created: 		10/29/01	-	gj										**
+**																			**
+**	Description:	Skater tricks code										**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SSpecialTrickInfo::SSpecialTrickInfo()
+{
+	m_isCat = false;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool SSpecialTrickInfo::IsUnassigned ( void ) const
+{
+	return m_TrickName == Script::GenerateCRC( "Unassigned" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String SSpecialTrickInfo::GetTrickNameString ( void ) const
+{
+	if ( IsUnassigned() )
+	{
+		return Script::GetLocalString( "sp_str_unassigned" );
+	}
+
+	Script::CScriptStructure* pTrickStructure;
+	if ( ( Script::GetStructure( m_TrickName, Script::ASSERT ) )->GetStructure( "params", &pTrickStructure ) )
+	{
+		// get the trick name
+		const char* pTrickName;
+		pTrickStructure->GetLocalText( "name", &pTrickName, true );
+		return pTrickName;
+	}
+
+	return "Unknown";
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String SSpecialTrickInfo::GetTrickSlotString ( void ) const
+{
+	if ( IsUnassigned() )
+	{
+		return Script::GetLocalString( "sp_str_unassigned" );
+	}
+
+	Script::CArray* pButtonArray = Script::GetArray( "SpecialCombos" );
+	Dbg_Assert( pButtonArray );
+
+	for ( int i = 0; i < (int)pButtonArray->GetSize(); i++ )
+	{
+		Script::CScriptStructure* pStructure = pButtonArray->GetStructure( i );
+		Dbg_Assert( pStructure );
+
+		uint32 currTrickSlot;
+		pStructure->GetChecksum( "trickslot", &currTrickSlot, true );
+
+		if ( currTrickSlot == m_TrickSlot )
+		{
+			const char* pTrickSlotString;
+			pStructure->GetText( "desc", &pTrickSlotString, true );
+			return pTrickSlotString;
+		}	
+	}
+
+	return "Unknown";
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Str::String SSpecialTrickInfo::GetSlotMenuProperty ( void ) const
+{
+	if ( IsUnassigned() )
+	{
+		return "trick_menu_property";
+	}
+	else
+	{
+		return "trickslot_menu_property";
+	}
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickChecksumTable::CTrickChecksumTable( void )
+{
+	m_NumChecksums = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickChecksumTable::Init( void )
+{
+	m_NumChecksums = 0;
+
+	Script::CArray* pArray = Script::GetArray( "ConfigurableTricks" );
+
+	for ( int i = 0; i < (int)pArray->GetSize(); i++ )
+	{
+		uint32 trickChecksum = pArray->GetNameChecksum( i );
+
+		// not found
+		if ( GetIndexFromChecksum( trickChecksum ) == -1 )
+		{
+			Dbg_MsgAssert( m_NumChecksums < vMAX_TRICK_CHECKSUMS, ("Too many trick checksums (limit = %d)", vMAX_TRICK_CHECKSUMS) );
+			m_Checksum[m_NumChecksums] = trickChecksum;
+			m_NumChecksums++;
+		}
+	}
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CTrickChecksumTable::GetChecksumFromIndex( int index )
+{
+		
+	Dbg_Assert( index >= 0 && index < m_NumChecksums );
+
+	return m_Checksum[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CTrickChecksumTable::GetIndexFromChecksum( uint32 checksum )
+{
+	for ( int i = 0; i < m_NumChecksums; i++ )
+	{
+		if ( checksum == m_Checksum[i] )
+		{
+			// found index
+			return i;
+		}
+	}
+
+	// not found
+	return -1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/SkaterTricks.h b/Code/Sk/Objects/SkaterTricks.h
new file mode 100644
index 0000000..dab6a7c
--- /dev/null
+++ b/Code/Sk/Objects/SkaterTricks.h
@@ -0,0 +1,103 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/SkaterTricks.h									**
+**																			**
+**	Created: 		10/29/01	-	gj										**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_SKATERTRICKS_H
+#define __OBJECTS_SKATERTRICKS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class  SSpecialTrickInfo : public Spt::Class
+{
+	public:
+		uint32	m_TrickName;
+		uint32	m_TrickSlot;
+		bool	m_isCat;
+
+	public:
+		SSpecialTrickInfo();
+		bool			IsUnassigned() const;
+		Str::String		GetTrickNameString() const;
+		Str::String		GetTrickSlotString() const;
+		Str::String		GetSlotMenuProperty() const;
+};
+
+class CTrickChecksumTable : public Spt::Class
+{
+	enum
+	{
+		// must be 256 or less, otherwise
+		// the trick mapping database might
+		// not compress to an acceptable size
+		vMAX_TRICK_CHECKSUMS = 256
+	};
+
+	public:
+		CTrickChecksumTable();
+
+	public:
+		void			Init( void );
+		uint32			GetChecksumFromIndex( int index );
+		int				GetIndexFromChecksum( uint32 checksum );
+
+	protected:
+		uint32			m_Checksum[vMAX_TRICK_CHECKSUMS];
+		int				m_NumChecksums;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_SKATERTRICKS_H
diff --git a/Code/Sk/Objects/TrickObject.cpp b/Code/Sk/Objects/TrickObject.cpp
new file mode 100644
index 0000000..d5c489a
--- /dev/null
+++ b/Code/Sk/Objects/TrickObject.cpp
@@ -0,0 +1,1578 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			TrickObject (OBJ)										**
+**																			**
+**	File name:		TrickObject.cpp											**
+**																			**
+**	Created by:		03/05/01	-	gj										**
+**																			**
+**	Description:	trick object code										**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 		   // just needed in mygetassociatednetworkconnection
+
+#include 
+
+#include 
+
+#ifdef __NOPT_ASSERT__	
+//#include 
+#endif
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+DefinePoolableClass(Lst::HashItem);
+
+
+enum
+{
+	vMAX_TRICKS_TO_FREE = 128
+};
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+   
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickObject::CTrickObject( void ) : Lst::Node< CTrickObject >(this)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickObject::CTrickObject( uint32 name_checksum ) : Lst::Node< CTrickObject >(this)
+{
+	m_NameChecksum = name_checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObject::InitializeTrickObjectColor( int seqIndex )
+{
+	// Garrett: shouldn't need to do anything except clear color here
+	// checks for the wibbling data, and creates
+	// it if it doesn't already exist
+
+	// TODO:  Should also screen out if it doesn't have any geometry
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_NameChecksum);				
+	Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",m_NameChecksum,Script::FindChecksumName(m_NameChecksum)));
+	p_sector->ClearColor();
+	if (!Config::CD())
+	{
+		if ( Script::GetInt( "show_all_trick_objects", false ) )
+		{
+			// quick way to see all the trick objects in the scene
+			ModulateTrickObjectColor( 2 );
+		}
+	}	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObject::ModulateTrickObjectColor( int seqIndex )
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_NameChecksum);				
+	Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",m_NameChecksum,Script::FindChecksumName(m_NameChecksum)));
+
+	Script::CArray* p_graffiti_col_tab = Script::GetArray( "graffitiColors" );
+	Dbg_MsgAssert( (uint) seqIndex < p_graffiti_col_tab->GetSize(), ( "graffitiColors array too small" ) );
+
+	Script::CArray *p_entry = p_graffiti_col_tab->GetArray(seqIndex);
+	
+	#ifdef	__NOPT_ASSERT__
+	int size = p_entry->GetSize();
+	Dbg_MsgAssert(size >= 3 && size <= 4, ("wrong size %d for color array", size));
+	#endif
+	
+	Image::RGBA color;
+
+	color.r = p_entry->GetInteger( 0 );
+	color.g = p_entry->GetInteger( 1 );
+	color.b = p_entry->GetInteger( 2 );
+	color.a = 128;
+
+	p_sector->SetColor(color);
+	return true;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObject::ClearTrickObjectColor( int seqIndex )
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_NameChecksum);				
+	Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",m_NameChecksum,Script::FindChecksumName(m_NameChecksum)));
+
+	Script::CArray* p_graffiti_col_tab = Script::GetArray( "graffitiColors" );
+	Dbg_MsgAssert( (uint) seqIndex < p_graffiti_col_tab->GetSize(), ( "graffitiColors array too small" ) );
+
+	Script::CArray *p_entry = p_graffiti_col_tab->GetArray(seqIndex);
+	
+	#ifdef	__NOPT_ASSERT__
+	int size = p_entry->GetSize();
+	Dbg_MsgAssert(size >= 3 && size <= 4, ("wrong size %d for color array", size));
+	#endif
+
+	Image::RGBA color;
+
+	color.r = p_entry->GetInteger( 0 );
+	color.g = p_entry->GetInteger( 1 );
+	color.b = p_entry->GetInteger( 2 );
+	color.a = 128;
+
+	Image::RGBA orig_color = p_sector->GetColor();
+	if ((orig_color.r == color.r) &&
+		(orig_color.g == color.g) &&
+		(orig_color.b == color.b))
+	{
+		p_sector->ClearColor();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickCluster::CTrickCluster( void ) : Lst::Node< CTrickCluster >(this)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickCluster::CTrickCluster( uint32 name_checksum ) : Lst::Node< CTrickCluster >(this)
+{
+	
+
+	m_NameChecksum = name_checksum;
+
+	Reset();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickCluster::~CTrickCluster( void )
+{
+	
+
+	uint32 count = m_TrickObjectList.CountItems();
+
+	// first iterate through all the items,
+	// and destroy the associated objects
+	for ( uint32 i = 0; i < count; i++ )
+	{
+		Lst::Node< CTrickObject >* pNode = m_TrickObjectList.GetItem( 0 );
+		Dbg_Assert( pNode );
+		CTrickObject* pObject = pNode->GetData();
+		Dbg_Assert( pObject );
+		delete pObject;
+	}
+
+	// now remove all the nodes from the list
+	m_TrickObjectList.RemoveAllNodes();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CTrickCluster::GetScore( uint32 skater_id )
+{
+	return ( m_IsOwned && (skater_id == m_OwnerId) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickCluster::Reset( void )
+{
+	
+
+	m_Score = 0;
+	m_OwnerId = 0;
+	m_IsOwned = false;	// no owner
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CTrickCluster::ModulateTrickObjectColor( int seqIndex )
+{
+	
+
+	uint32 count = m_TrickObjectList.CountItems();
+
+	for ( uint32 i = 0; i < count; i++ )
+	{
+		Lst::Node< CTrickObject >* pNode = m_TrickObjectList.GetItem( i );
+		Dbg_Assert( pNode );
+		CTrickObject* pObject = pNode->GetData();
+		Dbg_Assert( pObject );
+		pObject->ModulateTrickObjectColor( seqIndex );
+	}
+
+	// If playing a created park, update any created rails for this cluster.
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	if (p_skate_mod->m_cur_level == CRCD(0xb664035d,"load_sk5ed_gameplay"))
+	{
+		Obj::GetRailEditor()->ModulateRailColor(m_NameChecksum, seqIndex);
+	}	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickObject* CTrickCluster::find_trick_object( uint32 name_checksum )
+{
+	
+
+	uint32 count = m_TrickObjectList.CountItems();
+
+	// find the appropriate trick object
+	for ( uint32 i = 0; i < count; i++ )
+	{
+		Lst::Node< CTrickObject >* pNode = m_TrickObjectList.GetItem( i );
+		Dbg_Assert( pNode );
+		CTrickObject* pObject = pNode->GetData();
+		Dbg_Assert( pObject );
+
+		if ( pObject->m_NameChecksum == name_checksum )
+		{
+			return pObject;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickCluster::AddTrickObject( uint32 name_checksum )
+{
+	
+
+	Dbg_MsgAssert( !find_trick_object( name_checksum ),( "Trick object %s is already in list", Script::FindChecksumName(name_checksum) ));
+	
+	CTrickObject* pObject = new CTrickObject( name_checksum );
+
+	if ( !pObject )
+	{
+		return false;
+	}
+
+	if ( pObject->InitializeTrickObjectColor( name_checksum ) )
+	{
+//		printf( "Adding %s to trick object list\n", Script::FindChecksumName( name_checksum ) );
+		m_TrickObjectList.AddToTail( pObject );
+		return true;
+	}
+	else
+	{
+		// if not valid
+		delete pObject;
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickObjectManager::CTrickObjectManager( void ) : m_TrickClusterList(8), m_TrickAliasList(8)
+{
+//	m_TrickAliasCount = 0;
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+
+	mp_ObserverState = new Script::CScriptStructure;
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickObjectManager::~CTrickObjectManager( void )
+{
+	this->DeleteAllTrickObjects();
+}
+
+struct STrickObjectFreeInfo
+{
+	uint32	skater_id;			// which objects to free
+	uint32	num_tricks;			// num tricks to free
+	uint32	trick_buffer[vMAX_TRICKS_TO_FREE];
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickCluster::FreeCluster( uint32 skater_id )
+{
+	if ( this->m_IsOwned && ( this->m_OwnerId == skater_id ) )
+	{
+		this->m_IsOwned = false;
+		this->m_Score = 0;
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickCluster::ClearCluster( int seqIndex )
+{
+
+	uint32 count = m_TrickObjectList.CountItems();
+
+	// find the appropriate trick object
+	for ( uint32 i = 0; i < count; i++ )
+	{
+		Lst::Node< CTrickObject >* pNode = m_TrickObjectList.GetItem( i );
+		Dbg_Assert( pNode );
+		CTrickObject* p_object = pNode->GetData();
+		Dbg_Assert( p_object );
+
+		if (!p_object->ClearTrickObjectColor(seqIndex))
+		{
+			return false;
+		}
+	}
+	
+	// If playing a created park, clear any created rail colors for this cluster.
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	if (p_skate_mod->m_cur_level == CRCD(0xb664035d,"load_sk5ed_gameplay"))
+	{
+		Obj::GetRailEditor()->ClearRailColor(m_NameChecksum);
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void free_trick_cluster(CTrickCluster* pCluster, void* pData)
+{	
+	Dbg_AssertPtr(pCluster);
+	Dbg_AssertPtr(pData);
+
+	STrickObjectFreeInfo* pFreeInfo = (STrickObjectFreeInfo*)pData;
+
+	if ( pCluster->FreeCluster( pFreeInfo->skater_id ) )
+	{
+		Dbg_MsgAssert( pFreeInfo->num_tricks < vMAX_TRICKS_TO_FREE, ( "Too many trick objects to free" ) );
+		pFreeInfo->trick_buffer[pFreeInfo->num_tricks] = pCluster->GetNameChecksum();		
+		pFreeInfo->num_tricks++;
+
+#ifdef __USER_GARY__
+		Dbg_Message( "Freeing up %s\n", Script::FindChecksumName(pCluster->GetNameChecksum()) );
+#endif
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::FreeTrickObjects( uint32 skater_id )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+    
+	// clear the observer state when you exit the game
+	printf( "Clearing observer state\n" );
+	mp_ObserverState->Clear();
+	
+    if( gamenet_man->OnServer())
+	{
+		STrickObjectFreeInfo theFreeInfo;
+		theFreeInfo.skater_id = skater_id;
+		theFreeInfo.num_tricks = 0;
+
+		m_TrickClusterList.HandleCallback(free_trick_cluster, (void*)&theFreeInfo);
+
+		if ( theFreeInfo.num_tricks <= 0 )
+		{
+			// no tricks to free
+			return false;
+		}
+
+		// send score updates, as something has changed
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		skate_mod->SendScoreUpdates( false );
+	}
+    
+	clear_trick_clusters(skater_id + 1);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CTrickObjectManager::TrickObjectExists( uint32 name_checksum ){
+	// gets cluster checksum
+	CTrickCluster* pCluster = get_aliased_cluster( name_checksum );
+	if ( pCluster )
+	{
+		return pCluster->m_NameChecksum;
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CTrickObjectManager::RequestLogTrick( uint32 num_pending_tricks, uint32* p_pending_tricks, char* p_inform_prev_owner, int skater_id, uint32 score )
+{
+    printf("RequestLogTrick called\n");
+
+// GJ:  Note that this function only logs the trick;  a message
+// will be sent out to the clients to actually modify the color
+
+	uint32 i;
+
+	Dbg_Assert( p_pending_tricks );
+	Dbg_Assert( p_inform_prev_owner );
+
+	Dbg_Assert( num_pending_tricks < 128 );
+	static char s_change_mask[128];
+	for ( i = 0; i < num_pending_tricks; i++ )
+	{
+		s_change_mask[i] = 0;
+	}
+
+	for ( i = 0; i < num_pending_tricks; i++ )
+	{
+		CTrickCluster* pCluster = get_aliased_cluster( p_pending_tricks[i] );
+
+		// change only objects marked as trick objects
+		if ( !pCluster )
+			continue;
+
+		// tell the goal manager
+		Game::CGoalManager* pGoalManager = Game::GetGoalManager();
+		Dbg_Assert( pGoalManager );
+		pGoalManager->GotTrickObject( pCluster->GetNameChecksum(), score );
+
+		// if the score beats the score
+		if ( score > pCluster->m_Score )
+		{
+            bool owner_changed = ( pCluster->m_OwnerId != (uint32)skater_id );
+			
+			if ( !pCluster->m_IsOwned || owner_changed )
+			{
+				// that the trick object has changed states
+				s_change_mask[i] = 1;
+			}
+			
+			if ( pCluster->m_IsOwned && owner_changed )
+			{
+				// for tracking steal messages
+				Dbg_Assert( pCluster->m_OwnerId >= 0 && pCluster->m_OwnerId < GameNet::vMAX_PLAYERS );
+				p_inform_prev_owner[pCluster->m_OwnerId] = 1;
+			}
+
+			pCluster->m_OwnerId = skater_id;
+			pCluster->m_Score = score;
+			pCluster->m_IsOwned = true;
+
+/*            Game::CGoalManager* p_GoalManager = Game::GetGoalManager();
+            if ( p_GoalManager->IsInGraffitiGoal() )
+            {
+                p_GoalManager->GotGraffitiCluster( pCluster->GetNameChecksum() );
+                // printf("tricked in %s\n", Script::FindChecksumName( pCluster->GetNameChecksum() ) );
+            }
+*/
+		}
+	}
+
+	uint32 num_changed = 0;
+	
+	// shift them so that the changed ones are at the beginning
+	for ( i = 0; i < num_pending_tricks; i++ )
+	{
+		if ( s_change_mask[i] )
+		{
+			p_pending_tricks[num_changed] = p_pending_tricks[i];
+			num_changed++;
+		}
+	}
+	
+	return num_changed;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickCluster* CTrickObjectManager::GetTrickCluster( uint32 name_checksum )
+{
+	return find_trick_cluster( name_checksum );
+}
+
+struct SAutoTrick
+{
+	uint32	pPendingTricks[32];
+	uint32	numPendingTricks;
+};
+
+Net::Conn* MyGetAssociatedNetworkConnection( uint32 skater_id )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Obj::CSkater* skater;
+	GameNet::PlayerInfo* player;
+
+	skater = skate_mod->GetSkaterById( skater_id );
+	if( skater )
+	{
+		player = gamenet_man->GetPlayerByObjectID( skater->GetID() );
+		if( player )
+		{
+			return player->m_Conn;
+		}
+	}
+
+	return NULL;
+}
+
+void MyLogTrickObject( int skater_id, int score, uint32 num_pending_tricks, uint32* p_pending_tricks, bool propagate )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	GameNet::MsgScoreLogTrickObject msg;
+	GameNet::MsgObsScoreLogTrickObject obs_msg;
+	Net::MsgDesc msg_desc;
+	Net::Server* server;
+	Net::Conn* conn;
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+
+	if( propagate )
+	{
+		server = gamenet_man->GetServer();
+		Dbg_Assert( server );
+
+		conn = MyGetAssociatedNetworkConnection( skater_id );
+
+		// keep track of whom we need to send the steal message to
+		int i;
+		char previous_owner_flags[GameNet::vMAX_PLAYERS];
+		for ( i = 0; i < GameNet::vMAX_PLAYERS; i++ )
+		{
+			previous_owner_flags[i] = 0;
+		}
+
+		num_pending_tricks = skate_mod->GetTrickObjectManager()->RequestLogTrick( num_pending_tricks, p_pending_tricks, previous_owner_flags, skater_id, score );
+
+		if ( !num_pending_tricks )
+		{
+			return;
+		}
+
+		for ( i = 0; i < GameNet::vMAX_PLAYERS; i++ )
+		{
+			if ( previous_owner_flags[i] )
+			{
+				// GJ:  This only sends the stolen message to the two parties involved
+
+				GameNet::PlayerInfo* pPlayerInfo;
+				GameNet::MsgStealMessage steal_msg;
+				Net::MsgDesc steal_msg_desc;
+
+				steal_msg.m_NewOwner = skater_id;
+				steal_msg.m_OldOwner = i;
+				steal_msg.m_GameId = gamenet_man->GetNetworkGameId();
+
+				pPlayerInfo = gamenet_man->GetPlayerByObjectID(skater_id);
+				Dbg_Assert( pPlayerInfo );
+
+				steal_msg_desc.m_Data = &steal_msg;
+				steal_msg_desc.m_Length = sizeof( GameNet::MsgStealMessage );
+				steal_msg_desc.m_Id = GameNet::MSG_ID_STEAL_MESSAGE;
+				server->EnqueueMessage( pPlayerInfo->GetConnHandle(), &steal_msg_desc );
+
+				steal_msg.m_NewOwner = skater_id;
+				steal_msg.m_OldOwner = i;
+				steal_msg.m_GameId = gamenet_man->GetNetworkGameId();
+
+				pPlayerInfo = gamenet_man->GetPlayerByObjectID(i);
+				//Dbg_Assert( pPlayerInfo );
+
+				// For now, don't assert if the player doesn't exist anymore. Just don't do anything.
+				// Eventually, Gary will fix this so that pieces of exiting players are reset
+				if( pPlayerInfo )
+				{
+					server->EnqueueMessage( pPlayerInfo->GetConnHandle(), &msg_desc );
+				}
+			}
+		}
+
+#ifdef __USER_GARY__
+		printf( "Broadcasting %d tricks\n", num_pending_tricks );
+#endif
+
+		msg.m_SubMsgId = GameNet::SCORE_MSG_ID_LOG_TRICK_OBJECT;
+		msg.m_OwnerId = skater_id;
+		msg.m_Score = score;
+		msg.m_NumPendingTricks = num_pending_tricks;
+		msg.m_GameId = gamenet_man->GetNetworkGameId();
+
+		uint32 max_pending_trick_buffer_size = GameNet::MsgScoreLogTrickObject::vMAX_PENDING_TRICKS * sizeof(uint32);
+		uint32 actual_pending_trick_buffer_size = msg.m_NumPendingTricks * sizeof(uint32);
+		memcpy( msg.m_PendingTrickBuffer, p_pending_tricks, actual_pending_trick_buffer_size );
+
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgScoreLogTrickObject ) - max_pending_trick_buffer_size + actual_pending_trick_buffer_size;
+		msg_desc.m_Id = GameNet::MSG_ID_SCORE;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		// tell players to change their colors
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; 
+			 player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			//server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+			server->StreamMessage( player->GetConnHandle(), msg_desc.m_Id, msg_desc.m_Length, 
+						   msg_desc.m_Data, "TrickObj Buffer", GameNet::vSEQ_GROUP_PLAYER_MSGS );
+		}
+
+		obs_msg.m_OwnerId = skater_id;
+		obs_msg.m_NumPendingTricks = num_pending_tricks;
+		obs_msg.m_GameId = gamenet_man->GetNetworkGameId();
+		max_pending_trick_buffer_size = GameNet::MsgScoreLogTrickObject::vMAX_PENDING_TRICKS * sizeof(uint32);
+		actual_pending_trick_buffer_size = obs_msg.m_NumPendingTricks * sizeof(uint32);
+		memcpy( obs_msg.m_PendingTrickBuffer, p_pending_tricks, actual_pending_trick_buffer_size );
+
+		msg_desc.m_Data = &obs_msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgObsScoreLogTrickObject ) - max_pending_trick_buffer_size + actual_pending_trick_buffer_size;
+		msg_desc.m_Id = GameNet::MSG_ID_OBSERVER_LOG_TRICK_OBJ;
+		// tell observers to change their colors
+		for( player = gamenet_man->FirstPlayerInfo( sh, true ); player; 
+			 player = gamenet_man->NextPlayerInfo( sh, true ))
+		{
+			if( player->IsObserving())
+			{
+				//server->EnqueueMessage( player->GetConnHandle(), &msg_desc );
+				server->StreamMessage( player->GetConnHandle(), msg_desc.m_Id, msg_desc.m_Length, 
+						   msg_desc.m_Data, "TrickObj Buffer", GameNet::vSEQ_GROUP_PLAYER_MSGS );
+			}
+		}
+
+		// send score updates, as something has changed
+		skate_mod->SendScoreUpdates( false );
+
+		// Let the server's client do the rest of the work
+		if( conn->IsLocal())
+		{
+			return;
+		}
+	}
+
+#ifdef __USER_GARY__
+	printf( "Client is receiving %d tricks\n", num_pending_tricks );
+#endif
+
+	// modulate the color here
+	for ( uint32 i = 0; i < num_pending_tricks; i++ )
+	{
+		skate_mod->GetTrickObjectManager()->ModulateTrickObjectColor( p_pending_tricks[i], skater_id );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void trick_off_object(CTrickCluster* pCluster, void* pData)
+{
+	
+
+	Dbg_AssertPtr(pCluster);
+
+	SAutoTrick* pAutoTrick = (SAutoTrick*)pData;
+
+	if ( pAutoTrick->numPendingTricks >= 32 )
+	{
+		return;
+	}
+
+	if ( !pCluster->m_IsOwned )
+	{
+		pAutoTrick->pPendingTricks[pAutoTrick->numPendingTricks] = pCluster->GetNameChecksum();
+		pAutoTrick->numPendingTricks++;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickObjectManager::TrickOffAllObjects( uint32 skater_id )
+{
+	SAutoTrick theAutoTrick;
+	theAutoTrick.numPendingTricks = 0;
+	
+	m_TrickClusterList.HandleCallback(trick_off_object, &theAutoTrick);
+
+	if ( theAutoTrick.numPendingTricks > 0 )
+	{
+		MyLogTrickObject( skater_id, 1000, theAutoTrick.numPendingTricks, theAutoTrick.pPendingTricks, true );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::ModulateTrickObjectColor( uint32 name_checksum, int skater_id )
+{
+	
+
+	CTrickCluster* pCluster = get_aliased_cluster( name_checksum );
+	
+	// change only objects marked as trick objects
+	if ( !pCluster )
+	{
+		// not a trick object
+		return false;
+	}
+
+	// convert skater_id ->seqIndex, as it's 1-based,
+	// or -1 to reset
+	int seqIndex = -1;
+	if ( skater_id != -1 )
+	{
+		seqIndex = skater_id + 1;
+	}
+	
+	Dbg_MsgAssert( seqIndex == -1 || ( seqIndex >= 1 && seqIndex <= 8 ) ,( "Out of range seq index %d", seqIndex ));
+
+	pCluster->ModulateTrickObjectColor( seqIndex );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickCluster* CTrickObjectManager::get_aliased_cluster( uint32 alias_checksum )
+{
+#if 0
+	for ( uint32 i = 0; i < m_TrickAliasCount; i++ )
+	{
+		if ( m_TrickAliasList[i].m_AliasChecksum == alias_checksum )
+		{
+			return m_TrickAliasList[i].mp_TrickCluster;
+		}
+	}
+	return NULL;
+#endif
+	return m_TrickAliasList.GetItem(alias_checksum, false);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CTrickCluster* CTrickObjectManager::find_trick_cluster( uint32 cluster_checksum )
+{
+	
+
+#if 0
+	uint32 count = m_TrickClusterList.CountItems();
+
+	// find the appropriate trick object
+	for ( uint32 i = 0; i < count; i++ )
+	{
+		Lst::Node< CTrickCluster >* pNode = m_TrickClusterList.GetItem( i );
+		Dbg_Assert( pNode );
+		CTrickCluster* pCluster = pNode->GetData();
+		Dbg_Assert( pCluster );
+
+		if ( pCluster->m_NameChecksum == cluster_checksum )
+		{
+			return pCluster;
+		}
+	}
+	return NULL;
+#endif
+	return m_TrickClusterList.GetItem( cluster_checksum, false );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::clear_trick_clusters( int seqIndex )
+{
+
+	m_TrickClusterList.IterateStart();
+	CTrickCluster *p_cluster;
+	while ((p_cluster = m_TrickClusterList.IterateNext()))
+	{
+		if (!p_cluster->ClearCluster(seqIndex))
+		{
+			return false;
+		}
+	}
+
+	return true;
+}
+
+// this is useful if you want to examine the functionality
+// of a single trick object without sorting through hundreds of
+// other trick objects...  it represents one single piece in
+// the foundry
+//#define __TESTSINGLETRICKOBJECT__
+
+#ifdef __TESTSINGLETRICKOBJECT__
+	const char* p_single_cluster_name = "ParkingLotBulldozer";
+	const char* p_single_object_name = "Parking_Lot_Bulldozer0";
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::AddTrickCluster( uint32 name_checksum )
+{
+	
+
+
+#ifdef __TESTSINGLETRICKOBJECT__
+	if ( name_checksum != Script::GenerateCRC(p_single_cluster_name) )
+		return false;
+#endif
+
+#ifdef __USER_GARY__
+//	printf( "+++  Adding trick object %s (%x)\n", Script::FindChecksumName(name_checksum), name_checksum );
+#endif
+
+	if ( find_trick_cluster( name_checksum ) )
+	{
+		return false;
+	}
+	
+	// If playing a created park, clear any created rail colors for this cluster.
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	if (p_skate_mod->m_cur_level == CRCD(0xb664035d,"load_sk5ed_gameplay"))
+	{
+		Obj::GetRailEditor()->ClearRailColor(name_checksum);
+	}
+	
+	// add the cluster since it doesn't already exist
+	CTrickCluster* pCluster = new CTrickCluster( name_checksum );
+	Dbg_Assert( pCluster );
+//	m_TrickClusterList.AddToTail( pCluster );
+	m_TrickClusterList.PutItem( name_checksum, pCluster );
+	m_NumTrickClusters++;
+	
+#if 0	
+	Ed::ParkEditor* park_ed_mod = Ed::ParkEditor::Instance();
+	if (!park_ed_mod->IsInitialized())
+	{
+		Dbg_Assert( m_NumTrickClusters <= 256 );
+	}
+#endif
+	
+	// alias the cluster to itself
+	AddTrickAlias( name_checksum, name_checksum );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::AddTrickObjectToCluster( uint32 name_checksum, uint32 cluster_checksum )
+{
+	
+	
+#ifdef __TESTSINGLETRICKOBJECT__
+	if ( name_checksum != Script::GenerateCRC(p_single_object_name) )
+		return false;
+#endif
+
+	CTrickCluster* pCluster = find_trick_cluster( cluster_checksum );
+	Dbg_MsgAssert( pCluster,( "cluster %s not found", Script::FindChecksumName( cluster_checksum ) ));
+
+	bool success = pCluster->AddTrickObject( name_checksum );
+
+	// only if not clustering to self
+	if ( success && ( name_checksum != cluster_checksum ) )
+	{
+		// bind the environment object to the cluster
+		AddTrickAlias( name_checksum, cluster_checksum );
+	}
+	
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::AddTrickAlias( uint32 alias_checksum, uint32 cluster_checksum )
+{
+	
+	
+#ifdef __TESTSINGLETRICKOBJECT__
+	if ( cluster_checksum != Script::GenerateCRC(p_single_cluster_name) )
+		return false;
+#endif
+	
+	CTrickCluster* pCluster = get_aliased_cluster( alias_checksum );
+
+	if( pCluster )
+	{
+		Dbg_MsgAssert( 0,( "%s has already been aliased to %s cluster", Script::FindChecksumName(alias_checksum), Script::FindChecksumName(pCluster->m_NameChecksum) ));
+	}
+
+	pCluster = find_trick_cluster( cluster_checksum );
+
+	if( pCluster == NULL )
+	{
+		Dbg_MsgAssert( 0,( "cluster %s not found", Script::FindChecksumName(cluster_checksum) ));
+	}
+
+	m_TrickAliasList.PutItem(alias_checksum, pCluster);
+	
+#if 0
+	Dbg_MsgAssert( m_TrickAliasCount < vMAX_ALIASES,( "Too many aliases.  Couldn't alias %s to cluster %s", Script::FindChecksumName(alias_checksum), Script::FindChecksumName(cluster_checksum) ));
+	m_TrickAliasList[m_TrickAliasCount].m_AliasChecksum = alias_checksum;
+	pCluster = find_trick_cluster( cluster_checksum );
+	m_TrickAliasList[m_TrickAliasCount].mp_TrickCluster = pCluster;
+	Dbg_MsgAssert( pCluster,( "cluster %s not found", Script::FindChecksumName(cluster_checksum) ));
+	m_TrickAliasCount++;
+#endif
+
+//	printf("Adding trick alias %s\n", Script::FindChecksumName(alias_checksum));
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void delete_trick_cluster(CTrickCluster* pCluster, void* pData)
+{
+	
+
+	Dbg_AssertPtr(pCluster);
+
+	delete pCluster;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::DeleteAllTrickObjects( void )
+{
+	
+
+#if 0
+	uint32 count = m_TrickClusterList.CountItems();
+	for ( uint32 i = 0; i < count; i++ )
+	{
+		Lst::Node< CTrickCluster >* pNode = m_TrickClusterList.GetItem( 0 );
+		Dbg_Assert( pNode );
+		CTrickCluster* pCluster = pNode->GetData();
+		Dbg_Assert( pCluster );
+		delete pCluster;
+	}
+
+	// now remove all the nodes from the list
+	m_TrickClusterList.RemoveAllNodes();
+#endif
+
+	m_TrickClusterList.HandleCallback(delete_trick_cluster, NULL);
+	m_TrickClusterList.FlushAllItems();
+	m_NumTrickClusters = 0;
+	
+//	m_TrickAliasCount = 0;
+	m_TrickAliasList.FlushAllItems();
+	
+	return true;
+}
+
+struct STrickObjectScore
+{
+	int		running_score;
+	uint32	skater_id;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void count_score(CTrickCluster* pCluster, void* pData)
+{
+	
+
+	Dbg_AssertPtr(pCluster);
+	Dbg_AssertPtr(pData);
+	
+	STrickObjectScore* pScore = (STrickObjectScore*)pData;
+	pScore->running_score += pCluster->GetScore( pScore->skater_id );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CTrickObjectManager::GetScore( uint32 skater_id )
+{
+	STrickObjectScore theScore;
+	theScore.running_score = 0;
+	theScore.skater_id = skater_id;
+
+	m_TrickClusterList.HandleCallback(count_score, (void*)&theScore);
+
+	return theScore.running_score;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+struct SSearchInfo
+{
+	uint32 checksum;
+	uint8 index;
+	bool found;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void find_compressed_index(CTrickCluster* pCluster, void* pData)
+{
+	
+
+	Dbg_AssertPtr(pCluster);
+	Dbg_AssertPtr(pData);
+
+	SSearchInfo* pInfo = (SSearchInfo*)pData;
+	
+	if ( pInfo->found )
+	{
+		// already found...
+		return;
+	}
+	
+	if ( pInfo->checksum == pCluster->GetNameChecksum() )
+	{
+		pInfo->found = true;
+		return;
+	}
+
+	// make sure it's less than 256
+	Dbg_MsgAssert( pInfo->index < 256, ( "Too many trick aliases" ) );
+	
+	pInfo->index++;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void find_uncompressed_checksum(CTrickCluster* pCluster, void* pData)
+{
+	
+
+	Dbg_AssertPtr(pCluster);
+	Dbg_AssertPtr(pData);
+
+	SSearchInfo* pInfo = (SSearchInfo*)pData;
+
+	if ( pInfo->found )
+	{
+		// already found...
+		return;
+	}
+
+	if ( pInfo->index == 0 )
+	{
+		pInfo->found = true;
+		pInfo->checksum = pCluster->GetNameChecksum();
+		return;
+	}
+
+	pInfo->index--;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8 CTrickObjectManager::GetCompressedTrickObjectIndex( uint32 name_checksum )
+{
+	
+	
+	SSearchInfo theInfo;
+	theInfo.index = 0;
+	theInfo.checksum = name_checksum;
+	theInfo.found = false;
+	
+	m_TrickClusterList.HandleCallback( find_compressed_index, (void*)&theInfo );
+
+	Dbg_Assert( theInfo.found );
+
+	return theInfo.index;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CTrickObjectManager::GetUncompressedTrickObjectChecksum( uint8 compressed_index )
+{
+	
+	
+	SSearchInfo theInfo;
+	theInfo.index = compressed_index;
+	theInfo.checksum = 0;
+	theInfo.found = false;
+
+	m_TrickClusterList.HandleCallback( find_uncompressed_checksum, (void*)&theInfo );
+
+	Dbg_Assert( theInfo.found );
+	
+	return theInfo.checksum;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+struct SInitGraffitiStateInfo
+{
+	uint32 skater_id;
+	int num_trick_objects;
+	GameNet::MsgInitGraffitiState* pMsg;
+	Obj::CTrickObjectManager* pManager;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void init_graffiti_state(CTrickCluster* pCluster, void* pData)
+{
+	
+	
+	SInitGraffitiStateInfo* pInfo = (SInitGraffitiStateInfo*)pData;
+
+	if ( pCluster->m_IsOwned && pCluster->m_OwnerId == pInfo->skater_id )
+	{
+		// TODO:  check whether it's already in the list?
+		
+		Dbg_MsgAssert( pInfo->num_trick_objects < GameNet::vMAX_TRICK_OBJECTS_IN_LEVEL, ( "Too many trick objects in level" ) );
+		
+		Obj::CTrickObjectManager* pManager = pInfo->pManager;
+		pInfo->pMsg->m_TrickObjectStream[pInfo->num_trick_objects] = pManager->GetCompressedTrickObjectIndex( pCluster->GetNameChecksum() );
+		pInfo->num_trick_objects++;
+		pInfo->pMsg->m_NumTrickObjects[pInfo->skater_id]++;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickObjectManager::SetObserverGraffitiState( Script::CScriptStructure* pScriptStructure )
+{
+	mp_ObserverState->Clear();
+	if (pScriptStructure)
+	{
+		*mp_ObserverState+=*pScriptStructure;
+	}	
+
+#ifdef __NOPT_ASSERT__
+	Script::PrintContents(mp_ObserverState);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickObjectManager::ApplyObserverGraffitiState( void )
+{
+	for( int i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		Script::CArray* pArray;
+
+		char arrayName[32];
+		sprintf( arrayName, "Skater%d", i );
+
+		if ( mp_ObserverState->GetArray( arrayName, &pArray ) )
+		{
+			for ( int j = 0; j < (int)pArray->GetSize(); j++ )
+			{			
+				uint32 checksum = pArray->GetNameChecksum( j );
+#ifdef __NOPT_ASSERT__				
+				printf( "Modulating array %s\n", Script::FindChecksumName(checksum) );
+#endif
+				ModulateTrickObjectColor( checksum, i );
+			}
+		}
+	}
+
+	// now that we've done it, we can clear the observer state.
+	mp_ObserverState->Clear();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CTrickObjectManager::SetInitGraffitiStateMessage( void* pData )
+{
+	
+
+	SInitGraffitiStateInfo theInfo;
+	theInfo.pMsg = (GameNet::MsgInitGraffitiState*)pData;
+	theInfo.pManager = this;
+	theInfo.num_trick_objects = 0;
+
+	for ( uint32 i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		theInfo.pMsg->m_NumTrickObjects[i] = 0;
+		theInfo.skater_id = i;
+		m_TrickClusterList.HandleCallback( init_graffiti_state, (void*)&theInfo );
+        
+		printf( "Building graffiti state message %d\n", theInfo.pMsg->m_NumTrickObjects[i] );
+	}
+	
+	return ( sizeof(uint8) * Mdl::Skate::vMAX_SKATERS ) + ( sizeof(uint8) * theInfo.num_trick_objects );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CTrickObjectManager::ResetAllTrickObjects( void )
+{
+	
+
+	// TODO:
+	printf("Resetting all trick objects\n");
+	return false;
+
+#if 0
+	uint32 count = m_TrickClusterList.CountItems();
+
+	// call the reset function on each trick object in the scene
+	for ( uint32 i = 0; i < count; i++ )
+	{
+		Lst::Node< CTrickCluster >* pNode = m_TrickClusterList.GetItem( i );
+		Dbg_Assert( pNode );
+		CTrickCluster* pCluster = pNode->GetData();
+		Dbg_Assert( pCluster );
+		pCluster->Reset();
+	}
+#endif
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CTrickObjectManager::PrintContents( void )
+{
+	
+
+	// Remove this line if you need to see the contents
+	return;
+
+	printf("**********************\n");
+	printf("* TRICK CLUSTER LIST:\n");
+	printf("**********************\n");
+
+#if 0
+	uint32 i;
+
+	// call the reset function on each trick object in the scene
+	for ( i = 0; i < m_TrickClusterList.CountItems(); i++ )
+	{
+		Lst::Node< CTrickCluster >* pNode = m_TrickClusterList.GetItem( i );
+		Dbg_Assert( pNode );
+		CTrickCluster* pCluster = pNode->GetData();
+		Dbg_Assert( pCluster );
+		printf( "Trick cluster %d of %d: %s\n", i, m_TrickClusterList.CountItems(), Script::FindChecksumName( pCluster->m_NameChecksum ) );
+	}
+#endif
+	
+#if 0
+	printf("**********************\n");
+	printf("* TRICK ALIAS LIST:\n");
+	printf("**********************\n");
+
+	for ( i = 0; i < m_TrickAliasCount; i++ )
+	{
+//		printf( "%d: %s is aliased to %s\n", i, Script::FindChecksumName(m_TrickAliasList[i].m_AliasChecksum), Script::FindChecksumName(m_TrickAliasList[i].mp_TrickCluster->m_NameChecksum) );
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPendingTricks::CPendingTricks( void )
+{
+	m_NumTrickItems = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPendingTricks::TrickOffObject( uint32 checksum )
+{
+	
+	
+	uint32 num_trick_items = m_NumTrickItems;
+	if ( num_trick_items > vMAX_PENDING_TRICKS )
+		num_trick_items = vMAX_PENDING_TRICKS;
+
+	// not a valid trick item
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	CTrickObjectManager* pTrickObjectManager = skate_mod->GetTrickObjectManager();
+	Dbg_Assert( pTrickObjectManager );
+	checksum = pTrickObjectManager->TrickObjectExists( checksum );
+	if ( !checksum )
+	{
+		return false;
+	}
+	
+	for ( uint32 i = 0; i < num_trick_items; i++ )
+	{
+		if ( m_Checksum[i] == checksum )
+			return false;
+	}
+
+//	printf( "Adding object %s to pending tricks:\n", Script::FindChecksumName(checksum) );
+	
+	// circular buffer
+	m_Checksum[(m_NumTrickItems++)%vMAX_PENDING_TRICKS] = checksum;
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CPendingTricks::WriteToBuffer( uint32* p_buffer, uint32 max_size )
+{
+	
+
+	// TODO:  Account for the circular buffer so that the buffer
+	// size can be bigger than the pending trick size
+	Dbg_Assert( ( vMAX_PENDING_TRICKS * sizeof(uint32) ) <= max_size );
+	
+	uint32 num_trick_items = m_NumTrickItems;
+	if ( num_trick_items > vMAX_PENDING_TRICKS )
+		num_trick_items = vMAX_PENDING_TRICKS;
+
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	CTrickObjectManager* pTrickObjectManager = skate_mod->GetTrickObjectManager();
+	Dbg_Assert( pTrickObjectManager );
+
+	uint32 size = 0;
+	for ( uint32 i = 0; i < num_trick_items; i++ )
+	{
+#ifdef __NOPT_ASSERT__
+		printf( "Looking for %s\n", Script::FindChecksumName( m_Checksum[i] ) );
+#endif		
+		if ( pTrickObjectManager->TrickObjectExists( m_Checksum[i] ) )
+		{
+			*p_buffer = m_Checksum[i];
+			p_buffer++ ;
+
+			// increment the size
+			size += sizeof(uint32);
+		}
+		
+		if ( size >= max_size )
+			break;
+	}
+	return size;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CPendingTricks::FlushTricks( void )
+{
+	// player has bailed
+	m_NumTrickItems = 0;
+
+	return true;
+}
+
+} // namespace Obj
+
+
diff --git a/Code/Sk/Objects/TrickObject.h b/Code/Sk/Objects/TrickObject.h
new file mode 100644
index 0000000..80b82f5
--- /dev/null
+++ b/Code/Sk/Objects/TrickObject.h
@@ -0,0 +1,206 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			TrickObject (OBJ)										**
+**																			**
+**	File name:		TrickObject.cpp											**
+**																			**
+**	Created by:		03/05/01	-	gj										**
+**																			**
+**	Description:	trick object code										**
+**																			**
+*****************************************************************************/
+
+#ifndef __TRICK_OBJECT_H
+#define __TRICK_OBJECT_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+#include 
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Script
+{
+	class CStruct;
+}
+	
+namespace Obj
+{
+
+    class CTrickObjectManager;
+    class CTrickCluster;
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+// this is the interface through which we will
+// modify a specific world sector's colors
+class  CTrickObject : public  Lst::Node< CTrickObject > 
+{
+	
+
+	public:
+		friend class	CTrickCluster;
+
+	public:
+		CTrickObject( uint32 name_checksum );
+		bool		InitializeTrickObjectColor( int seqIndex );
+		bool		ModulateTrickObjectColor( int seqIndex );
+		bool		ClearTrickObjectColor( int seqIndex );
+
+	protected:
+		uint32		m_NameChecksum;
+
+	private:
+		CTrickObject( void );
+};
+
+// this represents a group of linked trick objects
+// when the score contained in the cluster is beaten
+// all of the trick objects will change color
+class  CTrickCluster : public  Lst::Node< CTrickCluster > 
+{
+	
+
+	public:
+		friend class	CTrickObjectManager;
+		friend void trick_off_object( CTrickCluster*, void* );
+		friend void init_graffiti_state( CTrickCluster*, void* );
+
+	public:
+		CTrickCluster( uint32 name_checksum );
+		virtual			~CTrickCluster();
+		int				GetScore( uint32 skater_id );
+		uint32			GetNameChecksum() { return m_NameChecksum; }
+		bool			AddTrickObject( uint32 name_checksum );
+		bool			Reset();
+		bool			ModulateTrickObjectColor( int seqIndex );
+		bool			FreeCluster( uint32 skater_id );
+		bool			ClearCluster( int seqIndex );
+
+	protected:
+		CTrickObject*	find_trick_object( uint32 name_checksum );
+
+	protected:
+		uint32			m_NameChecksum;
+		uint32			m_Score;
+		uint32			m_OwnerId;
+		bool			m_IsOwned;
+
+	// list of objects that get colored when one of the cluster get triggered
+		Lst::Head< CTrickObject >		m_TrickObjectList;
+
+	private:
+		CTrickCluster( void );
+};
+
+class  CTrickObjectManager  : public Spt::Class
+{
+	
+
+	public :
+		CTrickObjectManager( void );
+		virtual			~CTrickObjectManager( void );
+
+	public:
+		int				GetScore( uint32 skater_id );
+		bool			AddTrickCluster( uint32 name_checksum );
+		bool			AddTrickObjectToCluster( uint32 name_checksum, uint32 cluster_checksum );
+		bool			AddTrickAlias( uint32 alias_checksum, uint32 cluster_checksum );
+		bool			DeleteAllTrickObjects();	// wipes out all the trick objects
+		bool			ResetAllTrickObjects();		// resets all points to 0
+		void			PrintContents();
+		bool			ModulateTrickObjectColor( uint32 name_checksum, int skater_id );
+		uint32			RequestLogTrick( uint32 num_pending_tricks, uint32* p_pending_tricks, char* p_inform_prev_owner, int skater_id, uint32 score );
+		uint32			TrickObjectExists( uint32 name_checksum );
+		bool			FreeTrickObjects( uint32 skater_id );
+		void			TrickOffAllObjects( uint32 skater_id );
+		uint8			GetCompressedTrickObjectIndex( uint32 name_checksum );
+		uint32			GetUncompressedTrickObjectChecksum( uint8 compressed_index );
+		uint32			SetInitGraffitiStateMessage( void* pMsg );
+		void			SetObserverGraffitiState( Script::CStruct* pScriptStructure );
+		void			ApplyObserverGraffitiState( void );
+		CTrickCluster*	GetTrickCluster( uint32 name_checksum );
+
+		int 			m_NumTrickClusters;
+	protected:
+		CTrickCluster*	get_aliased_cluster( uint32 alias_checksum );
+		CTrickCluster*	find_trick_cluster( uint32 name_checksum );
+		bool			clear_trick_clusters( int seqIndex );		
+		
+	protected:
+		// full list of clusters
+		Lst::HashTable< CTrickCluster >	m_TrickClusterList;
+
+		// list of checksums that will fire off this cluster
+		Lst::HashTable< CTrickCluster >	m_TrickAliasList;
+
+		Script::CStruct* mp_ObserverState;
+};
+
+// per-skater
+class  CPendingTricks  : public Spt::Class
+{
+	public:
+		enum
+		{
+			vMAX_PENDING_TRICKS = 512,		// max number of items we can trick off in a combo
+			// for now this number must be less or equal to the buffer size in gamemsg.h
+		};
+
+		CPendingTricks( void );
+		bool		TrickOffObject( uint32 checksum );
+		uint32		WriteToBuffer( uint32* p_buffer, uint32 max_size );
+		bool		FlushTricks( void );
+		uint32		m_Checksum[vMAX_PENDING_TRICKS];
+		uint32		m_NumTrickItems;
+
+	protected:
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __TRICK_OBJECT_H
diff --git a/Code/Sk/Objects/ViewerObj.cpp b/Code/Sk/Objects/ViewerObj.cpp
new file mode 100644
index 0000000..5c1a6b5
--- /dev/null
+++ b/Code/Sk/Objects/ViewerObj.cpp
@@ -0,0 +1,876 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       ViewerObj.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  1/21/2002
+//****************************************************************************
+
+#include 
+								 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+// TODO:  Remove the rest of the level when the viewer is active								   
+// TODO:  Launch a primary sequence by name, pointer, or index
+// TODO:  The shoulder buttons should increment the current anim (next/prev anim)
+// TODO:  Don't crash if can't find the animation...
+// TODO:  Add handlers so that it can launch new anims and such
+// TODO:  Camera controls
+// TODO:  Be able to go back to the game
+// TODO:  Wireframe	mode
+// TODO:  Change skater profile, in real time
+// TODO:  Take LODs into account
+								   
+namespace Obj
+{
+
+	const float vSPEED_INCREMENT_AMOUNT = 0.25;
+	const Mth::Vector vPOS_VIEWEROBJECT( 0.0f, 0.0f, 0.0f );
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewerObject::s_handle_set_anim( Net::MsgHandlerContext* context )
+{
+	CViewerObject* pViewerObject;
+	pViewerObject = (CViewerObject*) context->m_Data;
+	Dbg_Assert( pViewerObject );
+	
+	Net::MsgViewObjSetAnim* p_msg;
+	p_msg = (Net::MsgViewObjSetAnim*) context->m_Msg;
+	pViewerObject->SetAnim( p_msg->m_AnimName );
+	
+	Dbg_Message( "Got request to set anim %s...", Script::FindChecksumName(p_msg->m_AnimName) );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewerObject::s_handle_set_anim_speed( Net::MsgHandlerContext* context )
+{
+	Dbg_Message( "Got request to set anim speed..." );
+	
+	CViewerObject* pViewerObject;
+	pViewerObject = (CViewerObject*) context->m_Data;
+	Dbg_Assert( pViewerObject );
+
+	Net::MsgViewObjSetAnimSpeed* p_msg;
+	p_msg = (Net::MsgViewObjSetAnimSpeed*) context->m_Msg;
+	pViewerObject->ChangeAnimSpeed( p_msg->m_AnimSpeed );
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewerObject::s_handle_increment_frame( Net::MsgHandlerContext* context )
+{
+	Dbg_Message( "Got request to increment frame..." );
+
+	CViewerObject* pViewerObject;
+	pViewerObject = (CViewerObject*) context->m_Data;
+	Dbg_Assert( pViewerObject );
+					
+	Net::MsgViewObjIncrementFrame* p_msg;
+	p_msg = (Net::MsgViewObjIncrementFrame*) context->m_Msg;
+	pViewerObject->IncrementFrame( p_msg->m_Forwards );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewerObject::s_handle_reload_anim( Net::MsgHandlerContext* context )
+{
+	Dbg_Message( "Got request to reload anim..." );
+
+	CViewerObject* pViewerObject;
+	pViewerObject = (CViewerObject*) context->m_Data;
+	Dbg_Assert( pViewerObject );
+					
+	Net::MsgViewObjSetAnimFile* p_msg;
+	p_msg = (Net::MsgViewObjSetAnimFile*) context->m_Msg;
+	pViewerObject->ReloadAnim( p_msg->m_Filename, p_msg->m_checksum );
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewerObject::s_handle_sequence_preview( Net::MsgHandlerContext* context )
+{
+	char path[256];
+	char* script_name;
+	CViewerObject* pViewerObject;
+	pViewerObject = (CViewerObject*) context->m_Data;
+	Dbg_Assert( pViewerObject );
+
+	script_name = context->m_Msg;
+	sprintf( path, "scripts\\animview\\%s.qb", script_name );
+	SkateScript::LoadQB( path );
+	//Script::RunScriptOnObject("RunMeNow",NULL,pViewerObject);
+	Script::CScript *pNewScript=Script::SpawnScript(script_name,NULL,0,NULL);
+	pNewScript->mpObject = pViewerObject;
+	
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CViewerObject::s_handle_reload_cam_anim( Net::MsgHandlerContext* context )
+{
+	Dbg_Message( "Got request to reload cam anim..." );
+
+	Net::MsgViewObjSetAnimFile* p_msg;
+	p_msg = (Net::MsgViewObjSetAnimFile*) context->m_Msg;
+	
+	Script::CStruct* pTempStruct = new Script::CStruct;
+								  
+	pTempStruct->AddString( NONAME, p_msg->m_Filename );
+	pTempStruct->AddChecksum( NONAME, p_msg->m_checksum );
+
+	CFuncs::ScriptReloadSkaterCamAnim( pTempStruct, NULL );
+
+	delete pTempStruct;
+
+	return Net::HANDLER_CONTINUE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewerObject::CViewerObject( Net::Server* pServer )
+{
+	Inp::Manager * inp_manager = Inp::Manager::Instance();
+    mp_input_handler = new Inp::Handler< CViewerObject > ( 0,  s_input_logic_code, *this, Tsk::BaseTask::Node::vNORMAL_PRIORITY );
+	inp_manager->AddHandler( *mp_input_handler );
+
+    m_animSpeed = 1.0f;
+	m_showPanel = true;
+
+	m_matrix.Ident();
+
+	m_paused = false;
+	
+	mp_server = pServer;
+
+	Dbg_Assert( mp_server );
+
+	mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_SET_ANIM, 
+									  s_handle_set_anim,
+									  Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_SET_ANIM_SPEED, 
+									  s_handle_set_anim_speed,
+									  Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_INCREMENT_FRAME, 
+									  s_handle_increment_frame,
+									  Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_SET_ANIM_FILE, 
+									  s_handle_reload_anim,
+									  Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_SET_CAMANIM_FILE, 
+									  s_handle_reload_cam_anim,
+									  Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	mp_server->m_Dispatcher.AddHandler( Net::vMSG_ID_VIEWOBJ_PREVIEW_SEQUENCE, 
+									  s_handle_sequence_preview,
+									  Net::mHANDLE_LATE | Net::mHANDLE_FOREIGN, this );
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CViewerObject::~CViewerObject()
+{
+	mp_input_handler->Remove();
+
+    delete mp_input_handler;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void s_remove_asset( const char* pFileName )
+{
+	// if the file is in the asset manager, unload it so
+	// that the artists can display the latest version
+	// ("cas_artist" must be set to 1, or else it'll just
+	// reload it from the PRE file)
+	
+	if ( !Script::GetInt( "cas_artist", false ) )
+	{
+		Dbg_Message( "Warning:  Can't reload asset %s from disk (need to define cas_artist=1)", pFileName );
+		return;
+	}
+
+	// get the assman
+	Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+
+	// kills the original asset
+	Ass::CAsset* pAsset = ass_man->GetAssetNode( Script::GenerateCRC( pFileName ), false );
+
+	if ( pAsset )
+	{		
+		Dbg_Message( "Unloading asset %s", pFileName );
+		ass_man->DestroyReferences( pAsset );
+		ass_man->UnloadAsset( pAsset );
+	}
+	else
+	{
+		Dbg_Message( "Couldn't find asset %s to unload", pFileName );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::LoadModel( Script::CStruct* pNodeData )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->GetObjectManager()->RegisterObject( *this );
+	MovingObjectCreateComponents();
+	MovingObjectInit( pNodeData, skate_mod->GetObjectManager() );
+
+	Nx::CEngine::sFinishRendering();
+
+	if (Config::GotExtraMemory())
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+	}
+	else
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	}
+		
+	Dbg_Assert( pNodeData );
+	
+	// get rid of old model, if
+	// it was stored in the assman
+	int skaterProfileNumber;
+	const char* pModelName;
+	uint32 profileName;
+
+	for ( int i = 0; i < Obj::CModelComponent::vNUM_LODS; i++ )
+	{
+		char paramName[256];
+		sprintf( paramName, "model%d", i ); 
+
+		if ( pNodeData->GetText( paramName, &pModelName, false ) )
+		{
+			Dbg_Message( "Removing model LOD data from asset manager, in order to replace it with new data" );
+
+			Str::String fullModelName;
+			fullModelName = Gfx::GetModelFileName(pModelName, ".skin");		
+			s_remove_asset( fullModelName.getString() );
+
+			fullModelName = Gfx::GetModelFileName(pModelName, ".mdl");		
+			s_remove_asset( fullModelName.getString() );
+		}
+	}
+
+	if ( pNodeData->GetText( "modelName", &pModelName, false ) 
+		 || pNodeData->GetText( "model", &pModelName, false ) )
+	{
+		Dbg_Message( "Removing model data from asset manager, in order to replace it with new data" );
+
+		Str::String fullModelName;
+		fullModelName = Gfx::GetModelFileName(pModelName, ".skin");		
+		s_remove_asset( fullModelName.getString() );
+		
+		fullModelName = Gfx::GetModelFileName(pModelName, ".mdl");		
+		s_remove_asset( fullModelName.getString() );
+	}
+	else if ( pNodeData->GetChecksum( "profile", &profileName, false ) )
+	{
+		Dbg_Message( "Warning:  can't remove old model data from asset manager...  asset not reloaded from disk." );
+	}
+	else if ( pNodeData->GetInteger( "skater_profile_index", &skaterProfileNumber, false ) )
+	{
+		Dbg_Message( "Warning:  can't remove old model data from asset manager...  asset not reloaded from disk." );
+
+		Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( skaterProfileNumber );
+		Script::CStruct* pAppearanceStruct = pSkaterProfile->GetAppearance()->GetStructure();
+		Script::CStruct* pNewStruct = new Script::CStruct;
+		pNewStruct->AppendStructure( pAppearanceStruct );
+		pNodeData->AddStructurePointer( "profile", pNewStruct );
+		// don't need to delete the structure, 
+		// because it gets added permanently to the struct
+	}
+	else
+	{
+		Dbg_MsgAssert( 0, ( "Unrecognized parameters for model loading" ) );
+	}
+
+	m_pos = vPOS_VIEWEROBJECT;
+	m_matrix.Ident();
+	
+	Script::RunScript( CRCD(0x24c71bc7,"viewerobj_add_components"), pNodeData, this );
+
+	Finalize();
+
+	Script::RunScript( CRCD(0xbbab79de,"viewerobj_init_model"), pNodeData, this );
+
+	// loop the idle anim, if it exists
+	CViewerObject::SetAnim( CRCD(0x23db4aea,"idle") );
+
+	Mem::Manager::sHandle().PopContext();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::UnloadModel()
+{
+	Script::RunScript( "kill_viewer_object_panel" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::SetAnim( uint32 animName )
+{
+    if ( GetAnimationComponent() )
+    {
+        if ( GetAnimationComponent()->AnimExists( animName ) )
+        {
+            GetAnimationComponent()->PlayPrimarySequence( animName, false, 0.0f, GetAnimationComponent()->AnimDuration( animName ), Gfx::LOOPING_CYCLE);
+        
+/*
+			Script::CStruct* pTempStruct = new Script::CStruct;
+			pTempStruct->AddChecksum( "type", Script::GenerateCRC("partialAnim" ) );
+			pTempStruct->AddChecksum( "AnimName", Script::GetChecksum("TestPartialAnim" ) );
+			pTempStruct->AddChecksum( "from", Script::GenerateCRC("start") );
+			pTempStruct->AddChecksum( "to", Script::GenerateCRC("end") );
+			pTempStruct->AddChecksum( NONAME, Script::GenerateCRC("cycle") );
+			pTempStruct->AddFloat( "speed", 1.0f );
+			Script::RunScript( "AddAnimController", pTempStruct, this );
+			delete pTempStruct;
+*/
+		}
+        else
+        {
+#ifdef	__NOPT_ASSERT__
+            Dbg_Message( "Unrecognized anim %s", Script::FindChecksumName(animName) );
+#endif
+        }
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::SetRotation( float rotZ, float rotX )
+{
+	m_matrix.Ident();
+	m_matrix.RotateZ( rotZ );
+	m_matrix.RotateX( rotX );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::ChangeAnimSpeed( float animSpeed )
+{
+	m_animSpeed = animSpeed;
+
+	if ( m_animSpeed > 1.0f )
+	{
+		m_animSpeed = 1.0f;
+	}
+
+	if ( m_animSpeed < 0.0f )
+	{
+		m_animSpeed = 0.0f;
+	}
+
+    if ( GetAnimationComponent() )
+    {
+        GetAnimationComponent()->SetAnimSpeed( m_animSpeed, false );
+		
+		// update partial anims
+		Script::CStruct* pTempStruct = new Script::CStruct;
+		pTempStruct->AddFloat( CRCD(0xf0d90109,"speed"), m_animSpeed );
+		GetAnimationComponent()->CallMemberFunction( CRCD(0xbd4edd44,"SetPartialAnimSpeed"), pTempStruct, NULL );
+		delete pTempStruct;
+    }
+
+	if ( GetAnimationComponent() )
+	{
+		// reset the paused state...
+		GetAnimationComponent()->Suspend( false );
+	}
+
+	m_paused = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::IncrementFrame( bool forwards )
+{
+    if ( GetAnimationComponent() )
+    {
+		if ( forwards )
+        {
+            // frame-rate independent
+            GetAnimationComponent()->AddTime( 1.0f / 60.0f );
+			GetAnimationComponent()->PrintStatus();
+        
+			Script::CStruct* pTempStruct = new Script::CStruct;
+			pTempStruct->AddFloat( CRCD(0x06c9f278,"incVal"), 1.0f / 60.0f );
+			GetAnimationComponent()->CallMemberFunction( CRCD(0x6aaeb76f,"IncrementPartialAnimTime"), pTempStruct, NULL );
+			delete pTempStruct;
+		}
+        else
+        {
+			// reverse direction so that it loops properly
+			GetAnimationComponent()->ReverseDirection( false );
+
+			// frame-rate independent
+            GetAnimationComponent()->AddTime( -1.0f / 60.0f );
+            GetAnimationComponent()->PrintStatus();
+
+			GetAnimationComponent()->ReverseDirection( false );
+    		
+			GetAnimationComponent()->CallMemberFunction( CRCD(0xf5e2b871,"ReversePartialAnimDirection"), NULL, NULL );
+			
+			Script::CStruct* pTempStruct = new Script::CStruct;
+			pTempStruct->AddFloat( CRCD(0x06c9f278,"incVal"), -1.0f / 60.0f );
+			GetAnimationComponent()->CallMemberFunction( CRCD(0x6aaeb76f,"IncrementPartialAnimTime"), pTempStruct, NULL );
+			delete pTempStruct;
+			
+			GetAnimationComponent()->CallMemberFunction( CRCD(0xf5e2b871,"ReversePartialAnimDirection"), NULL, NULL );
+		}
+		
+		// don't want to call the animation component's
+		// Update() function, because that changes the
+		// time...  instead, call UpdateSkeleton()
+		// which syncs the skeleton to the current time
+		GetAnimationComponent()->UpdateSkeleton();
+
+		// reset the paused state...
+		GetAnimationComponent()->Suspend( true );
+	}
+
+	m_paused = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::ReloadAnim( const char* pFileName, uint32 animName )
+{
+    if ( GetAnimationComponent() )
+    {
+        Dbg_Message( "Reloading anim..." );
+        Dbg_Message( "Warning!  This function fragments memory.  Use it at your own risk!" );
+
+        // get the assman
+        Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+
+        // kills the original asset
+        Ass::CAsset* pAsset = ass_man->GetAssetNode( Script::GenerateCRC( pFileName ), false );
+        if ( pAsset )
+        {
+            ass_man->DestroyReferences( pAsset );
+            ass_man->UnloadAsset( pAsset );
+        }
+        else
+        {
+#ifdef __NOPT_ASSERT__
+            Dbg_Message( "Couldn't find asset %s to unload", Script::FindChecksumName( animName ) );
+#endif
+        }
+
+        // load anim asset here!!!!
+        ass_man->LoadAnim( pFileName, animName, GetAnimationComponent()->GetAnimScriptName(), false, false );
+
+        this->SetAnim( animName );
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::UpdateWheels()
+{
+	Nx::CHierarchyObject* pHierarchyObjects = GetModel()->GetHierarchy();
+	Dbg_Assert( pHierarchyObjects );
+
+	Gfx::CSkeleton* pCarSkeleton = GetSkeletonComponent()->GetSkeleton();
+	Dbg_Assert( pCarSkeleton );
+
+	Dbg_MsgAssert( GetModel()->GetNumObjectsInHierarchy()==pCarSkeleton->GetNumBones(),
+				   ( "Skeleton and hierarchy doesn't match (%d hierarchy objs vs %d bones)", 
+				    GetModel()->GetNumObjectsInHierarchy(), pCarSkeleton->GetNumBones() ) );
+
+	static float s_wheelRotationX = 0.0f;
+	
+	// not frame rate independent (doesn't have valid m_time)
+	s_wheelRotationX += ( Script::GetFloat("max_wheel_speed", Script::ASSERT) * m_animSpeed );
+
+	// make sure it's within bounds
+	while ( s_wheelRotationX > 360.0f )
+	{
+		s_wheelRotationX -= 360.0f;
+	}
+
+	while ( s_wheelRotationX < 0.0f )
+	{
+		s_wheelRotationX += 360.0f;
+	}
+
+	if ( GetModel()->GetNumObjectsInHierarchy() > 5 )
+	{
+		Mth::Matrix* pMatrices = pCarSkeleton->GetMatrices();
+		Dbg_Assert( pMatrices );
+
+		// initialize the setup matrix once per frame
+		for ( int i = 0; i < GetModel()->GetNumObjectsInHierarchy(); i++ )
+		{						
+			*(pMatrices + i) = ( pHierarchyObjects + i )->GetSetupMatrix();
+		}
+
+		// apply the appropriate rotations to the wheels
+		for ( int i = 2; i < 6; i++ )
+		{
+			(pMatrices + i)->RotateXLocal( s_wheelRotationX * 2.0f * Mth::PI / 360.0f );
+		}
+
+		// get children into object space
+		for ( int i = 1; i < 6; i++ )
+		{
+			*(pMatrices + i) *= *(pMatrices + 0); 
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::Update()
+{
+	if ( GetAnimationComponent() )
+    {
+		if ( m_showPanel )
+		{
+			if ( GetAnimationComponent()->AnimExists(GetAnimationComponent()->GetCurrentSequence()) )
+			{
+				// pass the pertinent info to the panel:
+				Script::CStruct* pTempStruct = new Script::CStruct;
+
+				char line1[128];
+				sprintf( line1, "name %s",
+		#ifdef	__NOPT_ASSERT__
+						 Script::FindChecksumName( GetAnimationComponent()->GetCurrentSequence() )
+		#else
+						 "Unknown"
+		#endif				  
+				  );
+
+				pTempStruct->AddString( "line1", (const char*)&line1 );
+
+				char line2[128];
+				sprintf( line2, "speed %f", m_paused ? 0.0f : m_animSpeed );
+				pTempStruct->AddString( "line2", (const char*)&line2 );
+
+				char line3[128];
+				sprintf( line3, "time %f of %f", 
+						 GetAnimationComponent()->GetCurrentAnimTime(), 
+						 GetAnimationComponent()->AnimDuration( GetAnimationComponent()->GetCurrentSequence() ) );
+				pTempStruct->AddString( "line3", (const char*)&line3 );
+
+				char line4[128];
+				sprintf( line4, "frame %d of %d", (int)(GetAnimationComponent()->GetCurrentAnimTime() * 60.0f),
+						 (int)(GetAnimationComponent()->AnimDuration( GetAnimationComponent()->GetCurrentSequence() ) * 60.0f) );
+				pTempStruct->AddString( "line4", (const char*)&line4 );
+
+				// TODO:  Print out anim size
+
+				Script::RunScript( "draw_viewer_object_panel", pTempStruct );
+				delete pTempStruct;
+			}
+		}
+	}
+
+	if ( GetModel()->GetNumObjectsInHierarchy() )
+	{
+		UpdateWheels();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::s_input_logic_code ( const Inp::Handler < CViewerObject >& handler )
+{       
+	CViewerObject& obj = handler.GetData();
+ 
+	// this is the minimum amount of time
+	// you need to hold down the left/right keys
+	// before it will auto repeat
+	int vAUTO_REPEAT_TIME = Script::GetInteger( CRCD(0xe60442d5,"ViewerAutoRepeatTime"), Script::NO_ASSERT );
+
+	// this is how fast it auto repeats
+	int vAUTO_REPEAT_SPEED = Script::GetInteger( CRCD(0x2da212e5,"ViewerAutoRepeatSpeed"), Script::NO_ASSERT );
+
+	static int s_auto_repeat_delay = vAUTO_REPEAT_TIME;
+
+	// GJ:  no longer clearing the last frame's data
+	// at the beginning of the loop...  this allows
+	// us to override the m_commands from an external
+	// class (useful for doing custom controller
+	// configs for different viewer modes)
+//	obj.m_commands.ClearAll();
+
+    if( handler.m_Input->m_Makes & Inp::Data::mD_TRIANGLE )
+	{
+        obj.m_commands.SetMask( mTOGGLE_PANEL );
+	}
+
+	if ( handler.m_Input->m_Makes & Inp::Data::mD_LEFT )
+	{
+		obj.m_commands.SetMask( mDECREMENT_FRAME );
+		s_auto_repeat_delay = vAUTO_REPEAT_TIME;
+	}
+	else if ( handler.m_Input->m_Makes & Inp::Data::mD_RIGHT )
+	{
+		obj.m_commands.SetMask( mINCREMENT_FRAME );
+		s_auto_repeat_delay = vAUTO_REPEAT_TIME;
+	}
+	else if ( handler.m_Input->m_Buttons & Inp::Data::mD_LEFT )
+	{
+		s_auto_repeat_delay--;
+		if ( s_auto_repeat_delay <= 0 )
+		{
+			obj.m_commands.SetMask( mDECREMENT_FRAME );
+			s_auto_repeat_delay = vAUTO_REPEAT_SPEED;
+		}
+	}
+	else if ( handler.m_Input->m_Buttons & Inp::Data::mD_RIGHT )
+	{
+		s_auto_repeat_delay--;
+		if ( s_auto_repeat_delay <= 0 )
+		{
+			obj.m_commands.SetMask( mINCREMENT_FRAME );
+			s_auto_repeat_delay = vAUTO_REPEAT_SPEED;
+		}
+	}
+	else
+	{
+		s_auto_repeat_delay = vAUTO_REPEAT_TIME;
+	}
+	
+	// the viewer uses the global var "viewer_controls_enabled"
+	// to decide whether certain viewer controls should be
+	// active while in the regular viewer/light viewer.
+	// rather than adding a new global var to distinguish
+	// between the model viewer/light viewer, i've decided
+	// to just have the viewer object handle those viewer
+	// controls here
+	if( handler.m_Input->m_Makes & Inp::Data::mD_L3 )
+	{
+		obj.m_commands.SetMask( mTOGGLE_ROTATE_MODE );
+	}
+	
+	if ( handler.m_Input->m_Buttons & Inp::Data::mD_R1 )
+	{
+		if( !( handler.m_Input->m_Buttons & Inp::Data::mD_R2 ))
+		{
+			obj.m_commands.SetMask( mSTRAFE_UP );
+		}
+	}
+	else if ( handler.m_Input->m_Buttons & Inp::Data::mD_R2 )
+	{
+		obj.m_commands.SetMask( mSTRAFE_DOWN );
+	}
+	
+	if ( handler.m_Input->m_Makes & Inp::Data::mD_L1 )
+	{
+		obj.m_commands.SetMask( mINCREASE_SPEED );
+	}
+	else if ( handler.m_Input->m_Makes & Inp::Data::mD_L2 )
+	{
+		obj.m_commands.SetMask( mDECREASE_SPEED );
+	}
+	
+	if ( !GetAnimationComponentFromObject( &obj ) )
+	{
+		if ( handler.m_Input->m_Buttons & Inp::Data::mD_CIRCLE )
+		{
+			obj.m_commands.SetMask( mROLL_LEFT );
+		}
+		else if ( handler.m_Input->m_Buttons & Inp::Data::mD_SQUARE )
+		{
+			obj.m_commands.SetMask( mROLL_RIGHT );
+		}
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CViewerObject::DoGameLogic()
+{
+	if ( m_commands.TestMask( mTOGGLE_PANEL )	)
+	{
+		m_showPanel = !m_showPanel;
+		
+		Script::RunScript( "kill_viewer_object_panel" );
+	}
+
+	if ( m_commands.TestMask( mINCREASE_SPEED ) )
+	{
+		ChangeAnimSpeed( m_animSpeed + vSPEED_INCREMENT_AMOUNT );
+	}
+
+	if ( m_commands.TestMask( mDECREASE_SPEED ) )
+	{
+		ChangeAnimSpeed( m_animSpeed - vSPEED_INCREMENT_AMOUNT );
+	}
+										
+	if ( m_commands.TestMask( mINCREMENT_FRAME ) )
+	{
+		IncrementFrame( true );
+	}
+
+	if ( m_commands.TestMask( mDECREMENT_FRAME ) )
+	{
+		IncrementFrame( false );
+	}
+
+	// yuck:  the following creates a cyclic dependency
+	// on viewer...  should get rid of this later.
+	if ( m_commands.TestMask( mTOGGLE_ROTATE_MODE ) )
+	{
+		Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+		if ( pViewer )
+		{
+			pViewer->SetCommand( Mdl::CViewer::mTOGGLE_ROTATE_MODE );
+		}
+	}
+
+	if ( m_commands.TestMask( mSTRAFE_UP ) )
+	{
+		Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+		if ( pViewer )
+		{
+			pViewer->SetCommand( Mdl::CViewer::mSTRAFE_UP );
+		}
+	}
+	
+	if ( m_commands.TestMask( mSTRAFE_DOWN ) )
+	{
+		Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+		if ( pViewer )
+		{
+			pViewer->SetCommand( Mdl::CViewer::mSTRAFE_DOWN );
+		}
+	}
+	
+	if ( m_commands.TestMask( mROLL_LEFT ) )
+	{
+		Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+		if ( pViewer )
+		{
+			pViewer->SetCommand( Mdl::CViewer::mROLL_LEFT );
+		}
+	}
+	
+	if ( m_commands.TestMask( mROLL_RIGHT ) )
+	{
+		Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+		if ( pViewer )
+		{
+			pViewer->SetCommand( Mdl::CViewer::mROLL_RIGHT );
+		}
+	}
+
+	Update();
+	
+	// GJ:  clear the frame's data, now that we're done with it
+	// (this used to be done at the beginning of the loop)
+	m_commands.ClearAll();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/ViewerObj.h b/Code/Sk/Objects/ViewerObj.h
new file mode 100644
index 0000000..d1200d1
--- /dev/null
+++ b/Code/Sk/Objects/ViewerObj.h
@@ -0,0 +1,128 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       ViewerObj.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  1/21/2002
+//****************************************************************************
+
+#ifndef __VIEWER_OBJ_H
+#define __VIEWER_OBJ_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+#include 
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**							Forward Declarations						**
+*****************************************************************************/
+
+namespace Net
+{
+	class Server;
+};
+
+namespace Nx
+{
+    class CModel;
+};
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+	class CSkeletonComponent;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+// a viewer object is similar to a moving object,
+// but without the physics.  a viewer object always
+// exists in the viewer, although it doesn't necessarily
+// have renderable model data associated with it
+
+class CViewerObject : public CMovingObject
+{
+public:
+
+enum ECommands
+{
+    mLOAD_SKATER        = nBit( 1 ),
+	mLOAD_PEDESTRIAN	= nBit( 2 ),
+    mGOTO_NEXT_ANIM		= nBit( 3 ),
+    mGOTO_PREV_ANIM		= nBit( 4 ),
+    mGOTO_FIRST_ANIM	= nBit( 5 ),
+    mINCREASE_SPEED 	= nBit( 6 ),
+    mDECREASE_SPEED     = nBit( 7 ),
+    mINCREMENT_FRAME 	= nBit( 8 ),
+    mDECREMENT_FRAME    = nBit( 9 ),
+    mSHOW_PANEL    		= nBit( 10 ),
+    mHIDE_PANEL    		= nBit( 11 ),
+    mTOGGLE_PANEL    	= nBit( 12 ),
+	mSTRAFE_UP   	    = nBit( 13 ),
+	mSTRAFE_DOWN        = nBit( 14 ),
+	mROLL_LEFT          = nBit( 15 ),
+	mROLL_RIGHT         = nBit( 16 ),
+	mTOGGLE_ROTATE_MODE = nBit( 17 ),
+};
+
+public:
+    CViewerObject( Net::Server* pServer );
+    virtual     ~CViewerObject();
+	void		SetRotation( float rotZ, float rotX );
+	void        LoadModel( Script::CStruct* pParams );
+	void		UnloadModel();
+	void		SetAnim( uint32 animName );
+	void		ReloadAnim( const char* pAnim, uint32 animName );
+    
+protected:
+	void		DoGameLogic();
+    void        Update();
+	void		UpdateWheels();
+    
+protected:
+	void		ChangeAnimSpeed( float animSpeed );
+	void		IncrementFrame( bool forwards );
+
+protected:
+	static 		Net::MsgHandlerCode		s_handle_set_anim;
+	static 		Net::MsgHandlerCode		s_handle_set_anim_speed;
+	static 		Net::MsgHandlerCode		s_handle_increment_frame;
+	static		Net::MsgHandlerCode		s_handle_reload_anim;
+	static		Net::MsgHandlerCode		s_handle_reload_cam_anim;
+	static		Net::MsgHandlerCode		s_handle_sequence_preview;
+
+    static		Inp::Handler< CViewerObject >::Code 	s_input_logic_code;
+
+    Inp::Handler< CViewerObject >*	mp_input_handler;
+    Tsk::Task< CViewerObject >*     mp_logic_task;
+
+	Flags				m_commands;
+
+protected:
+	Net::Server*					mp_server;
+    float       					m_animSpeed;
+	bool							m_paused;
+	bool							m_showPanel;
+};
+
+} // namespace Obj
+
+#endif	// __VIEWER_OBJ_H
+
diff --git a/Code/Sk/Objects/car.cpp b/Code/Sk/Objects/car.cpp
new file mode 100644
index 0000000..33d99fd
--- /dev/null
+++ b/Code/Sk/Objects/car.cpp
@@ -0,0 +1,576 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       car.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/24/2000
+//****************************************************************************
+
+/*
+	MD:  The car is just a moving object with a special ground collision 
+	function, and the functionality to rotate wheels based on the car's 
+	velocity and turn the wheels based on the change in the car's direction. 
+*/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 				
+#include 				
+                         
+#include 
+#include 	 
+#include 	 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 	 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+#define DEFAULT_CAR_TURN_DIST				20.0f
+#define DEFAULT_CAR_MAX_VEL 				30.0f
+#define	DEFAULT_CAR_ACCELERATION			18.0f
+#define DEFAULT_CAR_HEIGHT_OFFSET  			0.0f
+#define DEFAULT_CAR_DECELERATION			32.0f
+#define DEFAULT_EMERGENCY_DECELERATION		20.0f
+#define DEFAULT_CAR_MIN_STOP_VEL			4.0f		//mph at which the brakes overpower momentum.
+#define DEFAULT_CAR_STOP_DIST_SPEED_MULT	1.0f		//stopping distance required depends on speed*this
+
+#define MAX_CAR_SPEED 85
+	
+#define MAX_WHEEL_Y_ROT ( 70.0f )
+#define MAX_CAR_X_ROT ( 5.0f )
+#define MIN_CAR_X_ROT ( -5.0f )
+#define STEP_CAR_X_ROT ( 0.125f )
+#define ACCEL_TO_CAR_ROT_FACTOR ( 3.0f )
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCar::update_wheels()
+{
+#if 1
+	// rotate based on forward speed
+	GetCarPhysicsComponent()->m_wheelRotationX += ( ( Tmr::FrameRatio() ) * ( GetMotionComponent()->m_vel_z * 90.0f ) / ( MPH_TO_IPS( MAX_CAR_SPEED ) ) );
+#else
+	// rotate at constant rate
+	GetCarPhysicsComponent()->m_wheelRotationX += ( 2.0f * Tmr::FrameRatio() );
+#endif
+	
+	// make sure it's within bounds
+	while ( GetCarPhysicsComponent()->m_wheelRotationX > 360.0f )
+	{
+		GetCarPhysicsComponent()->m_wheelRotationX -= 360.0f;
+	}
+
+	while ( GetCarPhysicsComponent()->m_wheelRotationX < 0.0f )
+	{
+		GetCarPhysicsComponent()->m_wheelRotationX += 360.0f;
+	}
+
+	if ( GetMotionComponent()->GetPathOb() )
+	{
+		if ( GetMotionComponent()->GetPathOb()->m_y_rot )
+		{
+			Mth::Vector radiusVec = GetMotionComponent()->GetPathOb()->m_nav_pos - GetMotionComponent()->GetPathOb()->m_circle_center;
+			float turningRadius = radiusVec.Length();
+
+#ifdef __PLAT_NGPS__
+			float ang = asinf( GetCarPhysicsComponent()->m_distanceBetweenTires / turningRadius );
+#else
+			float a_ang = GetCarPhysicsComponent()->m_distanceBetweenTires / turningRadius;
+			if ( a_ang > 1.0f ) a_ang = 1.0f;
+			if ( a_ang < -1.0f ) a_ang = -1.0f;
+			float ang = asinf( a_ang );
+#endif		// __PLAT_NGPS__
+			GetCarPhysicsComponent()->m_targetWheelRotationY = Mth::RadToDeg(-ang);
+			if ( GetMotionComponent()->GetPathOb()->m_y_rot < 0.0f )
+			{
+				GetCarPhysicsComponent()->m_targetWheelRotationY = -GetCarPhysicsComponent()->m_targetWheelRotationY;
+			}
+		}
+		else
+		{
+			GetCarPhysicsComponent()->m_targetWheelRotationY = 0.0f;
+		}
+		
+	    GetCarPhysicsComponent()->m_wheelRotationY = Mth::FRunFilter( GetCarPhysicsComponent()->m_targetWheelRotationY, GetCarPhysicsComponent()->m_wheelRotationY, Tmr::FrameRatio() );
+	}																				  
+	else
+	{
+		GetCarPhysicsComponent()->m_wheelRotationY = 0.0f;
+	}
+
+	float deltaZ = GetMotionComponent()->m_vel_z - GetCarPhysicsComponent()->m_old_vel_z;
+
+	GetCarPhysicsComponent()->m_old_vel_z = GetMotionComponent()->m_vel_z;
+
+	if ( Script::GetInt( CRCD(0x4cf13d8f,"car_debug"), Script::NO_ASSERT ) )
+	{
+		GetCarPhysicsComponent()->m_minCarRot = Script::GetFloat( CRCD(0x877db8da,"accelCarRot"), Script::ASSERT );
+		GetCarPhysicsComponent()->m_maxCarRot = Script::GetFloat( CRCD(0xfb3464df,"decelCarRot"), Script::ASSERT );
+		GetCarPhysicsComponent()->m_stepCarRot = Script::GetFloat( CRCD(0xfd8bc8d6,"speedCarRot"), Script::ASSERT );
+		GetCarPhysicsComponent()->m_accelToCarRotFactor = Script::GetFloat( CRCD(0xb2c7447b,"accelCarRotFactor"), Script::ASSERT );
+//		GetCarPhysicsComponent()->m_enter_turn_dist = Script::GetFloat( "turnDist", Script::ASSERT );
+	}
+
+	// vACCELERATION_TO_ROTATION_CONSTANT = magic number 
+	// that happens to look good, but it can be tweaked 
+	// later if necessary (or moved into script on a 
+	// per-car basis).
+	GetCarPhysicsComponent()->m_targetCarRotationX = -deltaZ / GetCarPhysicsComponent()->m_accelToCarRotFactor;
+
+	if ( GetCarPhysicsComponent()->m_targetCarRotationX > GetCarPhysicsComponent()->m_maxCarRot )
+	{
+		GetCarPhysicsComponent()->m_targetCarRotationX = GetCarPhysicsComponent()->m_maxCarRot;
+	}
+	if ( GetCarPhysicsComponent()->m_targetCarRotationX < GetCarPhysicsComponent()->m_minCarRot )
+	{
+		GetCarPhysicsComponent()->m_targetCarRotationX = GetCarPhysicsComponent()->m_minCarRot;
+	}
+
+    GetCarPhysicsComponent()->m_carRotationX = Mth::FRunFilter( GetCarPhysicsComponent()->m_targetCarRotationX, GetCarPhysicsComponent()->m_carRotationX, GetCarPhysicsComponent()->m_stepCarRot * Tmr::FrameRatio() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CalculateCarHierarchyMatrices( Gfx::CSkeleton* pSkeleton,
+                                    Nx::CModel *p_renderedModel, 
+									float carRotationX, 
+									float wheelRotationX, 
+									float wheelRotationY)
+{
+	Dbg_Assert( p_renderedModel );
+	Gfx::CSkeleton* pCarSkeleton = pSkeleton;
+	Dbg_Assert( pCarSkeleton );
+
+	// TODO:  Possibly set up the setup matrix only once,
+	// and then prerotate the rotation?
+	
+	Nx::CHierarchyObject* pHierarchyObjects = p_renderedModel->GetHierarchy();
+	Mth::Matrix* pMatrices = pCarSkeleton->GetMatrices();
+
+	float carRotationXInRadians = Mth::DegToRad( carRotationX );
+	float wheelRotationYInRadians = Mth::DegToRad( wheelRotationY );
+	float wheelRotationXInRadians = Mth::DegToRad( wheelRotationX );
+
+	Nx::CHierarchyObject* pCurrentHierarchyObject = pHierarchyObjects;
+	for ( int i = 0; i < pCarSkeleton->GetNumBones(); i++ )
+	{
+		Mth::Matrix* pMatrix = pMatrices + pCurrentHierarchyObject->GetBoneIndex();
+		
+		// initialize the setup matrix once per frame
+		*pMatrix = pCurrentHierarchyObject->GetSetupMatrix();	
+
+		switch ( pCurrentHierarchyObject->GetChecksum() )
+		{
+			// front wheels
+			case 0x6e2f434e:	// car_wheel01
+			case 0xf72612f4:	// car_wheel02
+				
+				// apply steering to the front tires
+				pMatrix->RotateZLocal( wheelRotationYInRadians );
+
+				// spin wheels
+				pMatrix->RotateXLocal( wheelRotationXInRadians );
+
+				break;
+
+			// back wheels
+			case 0x80212262:	// car_wheel03
+			case 0x1e45b7c1:	// car_wheel04
+				
+				// spin wheels
+				pMatrix->RotateXLocal( wheelRotationXInRadians );
+				break;
+
+			// middle wheels
+			case 0x69428757:	// car_wheel05
+			case 0xf04bd6ed:	// car_wheel06
+				
+				// spin wheels
+				pMatrix->RotateXLocal( wheelRotationXInRadians );
+				break;
+
+			case 0x88c21962:	// car
+
+				// rock the car back and forth
+				pMatrix->RotateXLocal( carRotationXInRadians );
+				break;
+
+			case 0x92e19120:	// car_shadow
+				// do nothing
+				break;
+
+			default:
+				Dbg_MsgAssert( 0, ( "Unrecognized bone name %s while animating car parts (bone %d)\nCar parts MUST be named be car_wheel01, car_wheel02, car_wheel03, car_wheel04, car_wheel05, car_wheel06, car, or car_shadow", Script::FindChecksumName(pCurrentHierarchyObject->GetChecksum() ), i) );
+				break;
+		}
+
+		// get children into object space
+		if ( i != 0 )
+		{
+			*pMatrix *= *( pMatrices + pCurrentHierarchyObject->GetParentIndex() );
+		}
+
+		pCurrentHierarchyObject++;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCar::display_wheels()
+{
+	CalculateCarHierarchyMatrices(this->GetSkeleton(),GetModel(),GetCarPhysicsComponent()->m_carRotationX,GetCarPhysicsComponent()->m_wheelRotationX,GetCarPhysicsComponent()->m_wheelRotationY);
+	
+	// scale down the shadow to nothing...  must be done after all the 
+	// children's matrices have been calculated...  this also assumes
+	// the shadow is the first matrix...  if it's not, we'd have to
+	// search for the correct matrix by name
+	if ( !GetCarPhysicsComponent()->m_shadowEnabled )
+	{
+		Mth::Matrix mat;
+		mat.Ident();
+		mat.Scale(Mth::Vector(0.0f,0.0f,0.0f,1.0f));
+		
+		Mth::Matrix* pMatrices = GetSkeleton()->GetMatrices();
+		*pMatrices = mat;
+	}
+}
+
+#if 0
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCar::debug_wheels()
+{
+	Dbg_Assert( GetModel() );
+	Gfx::CSkeleton* pCarSkeleton = this->GetSkeleton();
+	Dbg_Assert( pCarSkeleton );
+
+	// draw skeleton
+	for ( int i = 0; i < 6; i++ )
+	{
+		Mth::Matrix objMatrix = m_matrix;
+		objMatrix[Mth::POS] = m_pos;
+
+		Mth::Matrix wsMatrix = *(pCarSkeleton->GetMatrices() + i) * objMatrix;
+
+		Mth::Vector pos = wsMatrix[Mth::POS];
+		pos[Y] += 48.0f;
+		wsMatrix[Mth::POS] = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
+        
+		// set up bounding box
+		SBBox theBox;
+		theBox.m_max.Set(10.0f, 10.0f, 10.0f);
+		theBox.m_min.Set(-10.0f, -10.0f, -10.0f);    
+
+		// For now, draw a bounding box
+		Gfx::AddDebugBox( wsMatrix, pos, &theBox, NULL, 1, NULL ); 
+	}
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCar::InitCar( CGeneralManager* p_obj_man, Script::CStruct* pNodeData )
+{
+	p_obj_man->RegisterObject(*this);
+
+	MovingObjectCreateComponents();
+
+	uint32 skeletonName = CRCD(0x88c21962,"car");
+	bool use_skeletal_cars = false;
+
+	if ( pNodeData->GetChecksum( CRCD(0x09794932,"skeletonName"), &skeletonName, false ) )
+	{
+		use_skeletal_cars = true;
+	}
+
+	if ( use_skeletal_cars )
+	{
+//		m_model_restoration_info.mSkeletonName=skeletonName;
+		// component-based
+		Dbg_MsgAssert( GetSkeletonComponent() == NULL, ( "Skeleton component already exists" ) );
+		Script::CStruct* pSkeletonStruct = new Script::CStruct;
+		pSkeletonStruct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_SKELETON );
+		pSkeletonStruct->AddChecksum( CRCD(0x222756d5,"skeleton"), skeletonName );
+		this->CreateComponentFromStructure(pSkeletonStruct, NULL);
+		delete pSkeletonStruct;
+
+#ifdef	__NOPT_ASSERT__
+		Gfx::CSkeleton* pSkeleton = GetSkeletonComponent()->GetSkeleton();
+		Dbg_Assert( pSkeleton );
+		Dbg_Assert( pSkeleton->GetNumBones() > 0 );
+#endif
+	}
+	else
+	{
+//		m_model_restoration_info.mSkeletonName=0;
+	}	
+	
+	MovingObjectInit( pNodeData, p_obj_man );
+	
+	Obj::CModelComponent* pModelComponent = new Obj::CModelComponent;
+	this->AddComponent( pModelComponent );
+	pModelComponent->InitFromStructure( pNodeData );
+	
+	Dbg_Assert( GetModel() );
+
+	// set up the skeleton for this car model, if any
+	// needs to come after the geom is loaded (because
+	// that's where the hierarchy info is)
+	if ( use_skeletal_cars )
+	{		
+		const char* p_model_name;
+		pNodeData->GetText( CRCD(0x286a8d26,"model"), &p_model_name, true );
+
+		// sanity check on number of bones in skeleton
+		Dbg_MsgAssert( GetModel()->GetNumObjectsInHierarchy()==GetSkeleton()->GetNumBones(), 
+					   ( "Expected to find %d bones in the %s skeleton (found %d in %s - %s)",
+						 GetSkeleton()->GetNumBones(),
+						 Script::FindChecksumName( skeletonName ),
+						 GetModel()->GetNumObjectsInHierarchy(),
+						 Script::FindChecksumName( GetID() ),
+						 p_model_name ) );
+		
+		GetCarPhysicsComponent()->init_car_skeleton();		
+	}
+	else
+	{
+		if ( GetModel()->GetNumObjectsInHierarchy()!=0 )
+		{
+			uint32 name = 0;
+			pNodeData->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+			Dbg_MsgAssert( 0, ( "Skeletal model requires a skeleton %s", Script::FindChecksumName(name) ) );
+		}
+	}
+
+	GetCarPhysicsComponent()->InitFromStructure( pNodeData );
+
+	if ( !pNodeData->ContainsFlag( CRCD(0x0bf29bc0,"NoCollision") ) )
+	{
+		// GJ:  collision component should be added after the model component,
+		// because we want the same m_pos/m_matrix that is used for displaying
+		// the model
+		Dbg_MsgAssert( GetCollisionComponent() == NULL, ( "Collision component already exists" ) );
+		Script::CStruct* pCollisionStruct = new Script::CStruct;
+		pCollisionStruct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_COLLISION );
+		pCollisionStruct->AddChecksum( CRCD(0x2d7e583b,"collisionMode"), CRCD(0x6aadf154,"geometry") );
+		this->CreateComponentFromStructure(pCollisionStruct, NULL);
+		delete pCollisionStruct;
+	}
+
+	// designer controlled variables:
+	// set defaults, to be overridden by script values if they exist:
+	GetMotionComponent()->m_max_vel = ( MPH_TO_INCHES_PER_SECOND ( DEFAULT_CAR_MAX_VEL ) );
+	GetMotionComponent()->m_acceleration = FEET_TO_INCHES( DEFAULT_CAR_ACCELERATION );
+	GetMotionComponent()->m_deceleration = FEET_TO_INCHES( DEFAULT_CAR_DECELERATION );
+	
+	// stick to ground tests against wheels, rather than origin
+	// (eventually, we'll move this functionality into a custom
+	// sticktoground component)
+	GetMotionComponent()->m_point_stick_to_ground = false;
+
+	GetMotionComponent()->EnsurePathobExists( this );
+	GetMotionComponent()->GetPathOb()->m_enter_turn_dist = FEET_TO_INCHES( DEFAULT_CAR_TURN_DIST );
+	
+	// Add a NodeArrayComponent to the Car, so it will request loading of the associated node array.
+	Obj::CNodeArrayComponent *p_node_array_component = new Obj::CNodeArrayComponent;
+	this->AddComponent( p_node_array_component );
+	p_node_array_component->InitFromStructure( pNodeData );
+
+	if ( !pNodeData->ContainsFlag( CRCD(0x1fb9e477,"NoRail") ) )
+	{
+		// Add a RailManagerComponent to the Car, so it can be used for grinding etc.
+		Obj::CRailManagerComponent *p_rail_manager_component = new Obj::CRailManagerComponent;
+		this->AddComponent( p_rail_manager_component );
+		p_rail_manager_component->InitFromStructure( pNodeData );
+	}
+
+	if ( !pNodeData->ContainsFlag( CRCD(0x59793e2b,"NoSkitch") ) )
+	{
+		// Add an ObjectHookManagerComponent to the Car, for use by the SkitchComponent.
+		Obj::CObjectHookManagerComponent *p_object_hook_manager_component = new Obj::CObjectHookManagerComponent;
+		this->AddComponent( p_object_hook_manager_component );
+		p_object_hook_manager_component->InitFromStructure( pNodeData );
+
+		// Add a SkitchComponent to the Car, so it can be used for skitching etc.
+		Obj::CSkitchComponent *p_skitch_component = new Obj::CSkitchComponent;
+		this->AddComponent( p_skitch_component );
+		p_skitch_component->InitFromStructure( pNodeData );
+	}
+
+	// Finalize the object, saying we've added all the components
+	Finalize();
+	
+	// need to synchronize rendered model's position to initial world position
+	GetModelComponent()->FinalizeModelInitialization();
+
+	SetProfileColor(0xc0c000);				// cyan = car
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCar::DoGameLogic()
+{
+	Dbg_MsgLog(("DoGameLogic for car '%s'",Script::FindChecksumName(GetID())));
+	
+	if ( GetSuspendComponent()->SkipLogic( ) )
+	{
+		return;
+	}
+	
+	if ( IsDead() )
+	{
+		return;
+	}
+
+	// Don't update the wheels if we should not animate
+	// (and this cunningly lets the SuspendComponment actually work, by clearing the initial_animations	
+	if (GetSuspendComponent()->should_animate( ))
+	{
+		// check to see if it's got a hierarchy
+		// if it does, then update the wheels
+		Gfx::CSkeleton* pCarSkeleton = this->GetSkeleton();
+		if ( pCarSkeleton )
+		{
+			update_wheels();
+	
+			display_wheels();
+			
+//			debug_wheels();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCar::CallMemberFunction( uint32 Checksum, Script::CScriptStructure *pParams, Script::CScript *pScript )
+{   
+	return CMovingObject::CallMemberFunction( Checksum, pParams, pScript );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCar::CCar ( void )
+{
+	m_type = SKATE_TYPE_CAR;
+
+	Obj::CCarPhysicsComponent* pCarPhysicsComponent = new Obj::CCarPhysicsComponent;
+	AddComponent( pCarPhysicsComponent );
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCar::~CCar ( void )
+{   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CreateCar(CGeneralManager* p_obj_man, Script::CStruct* pNodeData)
+{
+	CCar* pCar = new CCar;
+    Dbg_MsgAssert(pCar, ("Failed to create car."));
+	
+	// get position, from the node that created us:
+	SkateScript::GetPosition( pNodeData, &pCar->m_pos );
+    
+	pCar->InitCar(p_obj_man, pNodeData);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CreateCar(CGeneralManager* p_obj_man, int nodeNumber)
+{
+	Script::CStruct* pNodeData = SkateScript::GetNode(nodeNumber);
+	CreateCar(p_obj_man, pNodeData);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/car.h b/Code/Sk/Objects/car.h
new file mode 100644
index 0000000..f125b39
--- /dev/null
+++ b/Code/Sk/Objects/car.h
@@ -0,0 +1,97 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/car.h	    									**
+**																			**
+**	Created: 		10/15/00	-	mjd										**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_CAR_H
+#define __OBJECTS_CAR_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+				 
+namespace Obj
+{
+
+	class CGeneralManager;
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+void CreateCar( CGeneralManager *p_obj_man, int nodeNum );
+void CreateCar( CGeneralManager* p_obj_man, Script::CStruct* pNodeData);
+
+class CCar : public CMovingObject 
+{		
+
+public:
+					CCar ( void );
+	virtual			~CCar ( void );
+	void			InitCar( CGeneralManager* p_obj_man, Script::CStruct* pNodeData );
+	bool			CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript );
+	
+protected:
+	void			update_wheels();
+	void			display_wheels();
+	void			debug_wheels();
+	void			DoGameLogic();	
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+void CalculateCarHierarchyMatrices( Gfx::CSkeleton* pSkeleton,
+                                    Nx::CModel *p_renderedModel, 
+									float carRotationX, 
+									float m_wheelRotationX, 
+									float m_wheelRotationY);
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_CAR_H
diff --git a/Code/Sk/Objects/crown.cpp b/Code/Sk/Objects/crown.cpp
new file mode 100644
index 0000000..b7fe6ef
--- /dev/null
+++ b/Code/Sk/Objects/crown.cpp
@@ -0,0 +1,242 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate4													**
+**																			**
+**	Module:			CCrown			 										**
+**																			**
+**	File name:		Crown.cpp												**
+**																			**
+**	Created by:		08/06/01	-	SPG										**
+**																			**
+**	Description:	King of the Hill Crown Object							**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+					 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+const float CCrown::vCROWN_RADIUS = FEET( 8.0f );
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCrown::DoGameLogic()
+{
+	if ( m_on_king )
+	{
+		Gfx::CSkeleton* pSkeleton;
+		pSkeleton = mp_king->GetSkeleton();
+		Dbg_Assert( pSkeleton );
+
+		pSkeleton->GetBoneMatrix( CRCD(0xddec28af,"Bone_Head"), &m_matrix );
+
+		m_matrix.RotateXLocal( 90.0f );
+
+		// raise the crown by 1 foot
+		//m_matrix.TranslateLocal( Mth::Vector( 0.0f, FEET(1.0f), 0.0f, 0.0f ) );
+		m_matrix.TranslateLocal( Mth::Vector( 0.0f, 0.0f, -FEET(1.5f), 0.0f ) );
+
+		Mth::Vector crownOffset = m_matrix[Mth::POS];
+		m_matrix[Mth::POS] = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
+		
+// for some reason, the below doesn't work as expected.
+// so instead, use the skater's root matrix for the orientation
+
+		m_matrix = m_matrix * mp_king->GetMatrix();
+
+//		m_matrix = mp_king->GetMatrix();
+
+		m_pos = mp_king->GetMatrix().Transform( crownOffset );
+		m_pos += mp_king->m_pos;
+		
+//		Gfx::AddDebugStar( m_pos, 10, 0x0000ffff, 1 );
+	}
+
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+CCrown::CCrown( void )
+{
+	SetID( Script::GenerateCRC( "crown_object" ));
+
+#if 0	
+	// GJ:  Why is the crown not registered here, instead of in InitCrown?
+	p_obj_man->RegisterObject( *this );
+#endif
+	
+    m_on_king = false;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCrown::~CCrown( void )
+{	
+	RemoveFromWorld();
+
+	m_on_king = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCrown::PlaceOnKing( CSkater* king )
+{   
+	mp_king = king;
+    
+    m_on_king = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCrown::RemoveFromWorld( void )
+{
+	m_on_king = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CCrown::RemoveFromKing( void )
+{
+	m_on_king = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CCrown::OnKing( void )
+{
+	return m_on_king;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCrown::InitCrown( CGeneralManager* p_obj_man, Script::CStruct* pNodeData )
+{   
+	p_obj_man->RegisterObject( *this );
+	
+	MovingObjectCreateComponents();
+
+	// Create an empty model 
+	Dbg_MsgAssert( GetModelComponent() == NULL, ( "Model component already exists" ) );
+	Script::CStruct* pModelStruct = new Script::CStruct;
+	pModelStruct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_MODEL );
+	this->CreateComponentFromStructure(pModelStruct, NULL);
+	delete pModelStruct;
+	
+	MovingObjectInit( pNodeData, p_obj_man );
+	SetID( Script::GenerateCRC( "crown_object" ));
+	
+	Obj::CSuspendComponent* pSuspendComponent = GetSuspendComponent();
+	Dbg_MsgAssert( pSuspendComponent, ( "No suspend component?" ) );
+	pSuspendComponent->m_lod_dist[0] = 1000000;
+	pSuspendComponent->m_lod_dist[1] = 1000001;
+	pSuspendComponent->m_lod_dist[2] = 1000002;  // Note, this is used as a magic number to turn off the high level culling
+	pSuspendComponent->m_lod_dist[3] = 1000003;
+
+	Str::String fullModelName;
+//	fullModelName = Gfx::GetModelFileName("gameobjects/skate/letter_a/letter_a.mdl", ".mdl");
+	fullModelName = Gfx::GetModelFileName("crown", ".mdl");
+	// Model file name should look like this:  "models/testcar/testcar.mdl"
+	Dbg_Printf( "Initializing Crown using file %s\n", fullModelName.getString() );
+	GetModel()->AddGeom( fullModelName.getString(), 0, true );
+	
+	GetModel()->SetActive( true );
+	SkateScript::GetPosition( pNodeData, &m_pos );
+	Dbg_Printf( "Placing crown at %f %f %f\n", m_pos[X], m_pos[Y], m_pos[Z] );
+
+	Finalize();
+
+	GetModelComponent()->FinalizeModelInitialization();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+} // namespace Obj
+
diff --git a/Code/Sk/Objects/crown.h b/Code/Sk/Objects/crown.h
new file mode 100644
index 0000000..bf9fc5c
--- /dev/null
+++ b/Code/Sk/Objects/crown.h
@@ -0,0 +1,107 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:			Crown			 										**
+**																			**
+**	File name:		Crown.h													**
+**																			**
+**	Created by:		08/06/01	-	SPG										**
+**																			**
+**	Description:	King of the Hill crown object							**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_CROWN_H
+#define __OBJECTS_CROWN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Obj
+{
+
+class CSkater;
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  CCrown : public CMovingObject 
+{		
+public:
+	static const float vCROWN_RADIUS;
+
+public:
+	CCrown ( void );
+	virtual	~CCrown ( void );
+	
+	void			InitCrown( CGeneralManager* p_obj_man, Script::CStruct* pNodeData );
+	void			PlaceOnKing( CSkater* king );
+	void			RemoveFromWorld( void );
+	void			RemoveFromKing( void );
+	Mth::Vector& 	GetPosition( void ) {return m_pos;}
+	void			SetPosition( Mth::Vector& pos ) { m_pos = pos; }
+	bool			OnKing( void );
+	void			DoGameLogic();
+
+protected:	
+	CSkater* 		mp_king;
+	uint32			m_head_bone_index;
+	bool			m_on_king;
+		
+//	static	Tsk::Task< CCrown >::Code s_logic_code;
+//	Tsk::Task< CCrown >*	mp_logic_task;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_CROWN_H
+
+
diff --git a/Code/Sk/Objects/cutscenedetails.cpp b/Code/Sk/Objects/cutscenedetails.cpp
new file mode 100644
index 0000000..5350aa4
--- /dev/null
+++ b/Code/Sk/Objects/cutscenedetails.cpp
@@ -0,0 +1,5461 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       cutscenedetails.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  01/13/2003
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// make sure the texture dictionary offsets don't
+// clash with skater's texture dictionaries
+// in case we're using actual skater assets
+// (such as the board) 
+const int vCUTSCENE_TEXDICT_RANGE = 200;
+ 
+// flags that are set during cifconv
+#define vCUTSCENEOBJECTFLAGS_ISSKATER		(1<<31)
+#define vCUTSCENEOBJECTFLAGS_ISHEAD			(1<<30)
+
+#ifdef __PLAT_NGC__
+extern bool g_in_cutscene;
+#endif		// __PLAT_NGC__
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*
+char testWeightIndices[] = {
+	30, -1, -1, // vertex 0
+	30, -1, -1, // vertex 1
+	30, -1, -1, // vertex 2
+	30, -1, -1, // vertex 3
+	30, -1, -1, // vertex 4
+	30, -1, -1, // vertex 5
+	30, -1, -1, // vertex 6
+	30, -1, -1, // vertex 7
+	30, -1, -1, // vertex 8
+	30, 32, -1, // vertex 9
+	30, 32, -1, // vertex 10
+	30, 32, -1, // vertex 11
+	30, 32, -1, // vertex 12
+	30, 32, -1, // vertex 13
+	30, 32, -1, // vertex 14
+	30, 32, -1, // vertex 15
+	30, 32, -1, // vertex 16
+	30, 32, -1, // vertex 17
+	30, 32, -1, // vertex 18
+	30, 32, -1, // vertex 19
+	30, 32, -1, // vertex 20
+	30, 32, -1, // vertex 21
+	30, -1, -1, // vertex 22
+	30, -1, -1, // vertex 23
+	30, -1, -1, // vertex 24
+	30, -1, -1, // vertex 25
+	30, -1, -1, // vertex 26
+	30, -1, -1, // vertex 27
+	30, 31, -1, // vertex 28
+	30, -1, -1, // vertex 29
+	30, 32, -1, // vertex 30
+	30, -1, -1, // vertex 31
+	30, 32, -1, // vertex 32
+	30, -1, -1, // vertex 33
+	30, 32, -1, // vertex 34
+	30, 32, -1, // vertex 35
+	30, 32, -1, // vertex 36
+	30, 32, -1, // vertex 37
+	30, 32, -1, // vertex 38
+	30, 32, -1, // vertex 39
+	30, 32, -1, // vertex 40
+	30, 32, -1, // vertex 41
+	30, 32, -1, // vertex 42
+	30, -1, -1, // vertex 43
+	30, -1, -1, // vertex 44
+	30, -1, -1, // vertex 45
+	30, -1, -1, // vertex 46
+	30, -1, -1, // vertex 47
+	30, -1, -1, // vertex 48
+	30, -1, -1, // vertex 49
+	30, -1, -1, // vertex 50
+	30, 32, -1, // vertex 51
+	30, 32, -1, // vertex 52
+	30, 32, -1, // vertex 53
+	30, 32, -1, // vertex 54
+	30, 32, -1, // vertex 55
+	30, 32, -1, // vertex 56
+	30, 32, -1, // vertex 57
+	30, 32, -1, // vertex 58
+	30, 32, -1, // vertex 59
+	30, 32, -1, // vertex 60
+	30, 32, -1, // vertex 61
+	30, 32, -1, // vertex 62
+	30, 32, -1, // vertex 63
+	30, 32, -1, // vertex 64
+	30, 32, -1, // vertex 65
+	30, 32, -1, // vertex 66
+	30, 32, -1, // vertex 67
+	30, 32, -1, // vertex 68
+	30, 32, -1, // vertex 69
+	30, 32, -1, // vertex 70
+	30, 32, -1, // vertex 71
+	30, 32, -1, // vertex 72
+	30, -1, -1, // vertex 73
+	30, 32, -1, // vertex 74
+	30, -1, -1, // vertex 75
+	30, 32, -1, // vertex 76
+	30, -1, -1, // vertex 77
+	30, -1, -1, // vertex 78
+	30, -1, -1, // vertex 79
+	30, -1, -1, // vertex 80
+	30, -1, -1, // vertex 81
+	30, -1, -1, // vertex 82
+	30, -1, -1, // vertex 83
+	30, -1, -1, // vertex 84
+	30, -1, -1, // vertex 85
+	30, -1, -1, // vertex 86
+	30, -1, -1, // vertex 87
+	30, -1, -1, // vertex 88
+	30, -1, -1, // vertex 89
+	30, -1, -1, // vertex 90
+	30, -1, -1, // vertex 91
+	30, -1, -1, // vertex 92
+	30, -1, -1, // vertex 93
+	30, -1, -1, // vertex 94
+	30, 31, -1, // vertex 95
+	30, 31, -1, // vertex 96
+	30, -1, -1, // vertex 97
+	30, -1, -1, // vertex 98
+	30, -1, -1, // vertex 99
+	30, 29, -1, // vertex 100
+	30, 29, -1, // vertex 101
+	30, -1, -1, // vertex 102
+	30, -1, -1, // vertex 103
+	30, 31, -1, // vertex 104
+	30, -1, -1, // vertex 105
+	30, -1, -1, // vertex 106
+	30, -1, -1, // vertex 107
+	30, -1, -1, // vertex 108
+	30, -1, -1, // vertex 109
+	30, -1, -1, // vertex 110
+	30, -1, -1, // vertex 111
+	30, -1, -1, // vertex 112
+	30, -1, -1, // vertex 113
+	30, -1, -1, // vertex 114
+	29, 30, -1, // vertex 115
+	30, -1, -1, // vertex 116
+	29, 30, -1, // vertex 117
+	29, 30, -1, // vertex 118
+	30, 29, -1, // vertex 119
+	30, 29, -1, // vertex 120
+	30, 33, -1, // vertex 121
+	30, 33, -1, // vertex 122
+	33, 30, -1, // vertex 123
+	33, 30, -1, // vertex 124
+	33, 30, -1, // vertex 125
+	33, 30, -1, // vertex 126
+	33, 30, -1, // vertex 127
+	33, 30, -1, // vertex 128
+	33, 30, -1, // vertex 129
+	33, 30, -1, // vertex 130
+	33, 30, -1, // vertex 131
+	33, 30, -1, // vertex 132
+	33, 30, -1, // vertex 133
+	33, 30, -1, // vertex 134
+	30, 33, -1, // vertex 135
+	33, 30, -1, // vertex 136
+	33, 30, -1, // vertex 137
+	33, 30, -1, // vertex 138
+	33, 30, -1, // vertex 139
+	33, 30, -1, // vertex 140
+	30, 33, -1, // vertex 141
+	33, 30, -1, // vertex 142
+	30, 33, -1, // vertex 143
+	33, 30, -1, // vertex 144
+	30, 33, -1, // vertex 145
+	30, 33, -1, // vertex 146
+	30, -1, -1, // vertex 147
+	30, -1, -1, // vertex 148
+	30, -1, -1, // vertex 149
+	30, -1, -1, // vertex 150
+	30, -1, -1, // vertex 151
+	30, -1, -1, // vertex 152
+	30, -1, -1, // vertex 153
+	30, 32, -1, // vertex 154
+	30, -1, -1, // vertex 155
+	30, 32, -1, // vertex 156
+	30, 32, -1, // vertex 157
+	30, 32, -1, // vertex 158
+	30, 32, -1, // vertex 159
+	30, 32, -1, // vertex 160
+	30, 32, -1, // vertex 161
+	30, 32, -1, // vertex 162
+	30, 32, -1, // vertex 163
+	30, 32, -1, // vertex 164
+	30, 32, -1, // vertex 165
+	30, 32, -1, // vertex 166
+	30, 32, -1, // vertex 167
+	30, 32, -1, // vertex 168
+	30, 32, -1, // vertex 169
+	30, 32, -1, // vertex 170
+	30, 32, -1, // vertex 171
+	30, 32, -1, // vertex 172
+	30, 32, -1, // vertex 173
+	30, 32, -1, // vertex 174
+	30, 32, -1, // vertex 175
+	30, -1, -1, // vertex 176
+	30, 32, -1, // vertex 177
+	30, -1, -1, // vertex 178
+	30, -1, -1, // vertex 179
+	30, -1, -1, // vertex 180
+	30, -1, -1, // vertex 181
+	30, -1, -1, // vertex 182
+	30, -1, -1, // vertex 183
+	30, -1, -1, // vertex 184
+	30, 33, -1, // vertex 185
+	30, 33, -1, // vertex 186
+	33, 30, -1, // vertex 187
+	30, 33, -1, // vertex 188
+	33, 30, -1, // vertex 189
+	30, 33, -1, // vertex 190
+	33, 30, -1, // vertex 191
+	33, 30, -1, // vertex 192
+	33, 30, -1, // vertex 193
+	33, 30, -1, // vertex 194
+	33, 30, -1, // vertex 195
+	30, 33, -1, // vertex 196
+	33, 30, -1, // vertex 197
+	30, 31, -1, // vertex 198
+	30, 31, -1, // vertex 199
+	30, 31, -1, // vertex 200
+	30, 31, -1, // vertex 201
+	30, 31, -1, // vertex 202
+	30, 31, -1, // vertex 203
+	30, 31, -1, // vertex 204
+	30, 31, -1, // vertex 205
+	30, 31, -1, // vertex 206
+	30, 31, -1, // vertex 207
+	30, 31, -1, // vertex 208
+	30, 31, -1, // vertex 209
+	30, 31, -1, // vertex 210
+	30, 31, -1, // vertex 211
+	31, 30, -1, // vertex 212
+	31, 30, -1, // vertex 213
+	31, 30, -1, // vertex 214
+	31, 30, -1, // vertex 215
+	30, 31, -1, // vertex 216
+	30, 31, -1, // vertex 217
+	30, 31, -1, // vertex 218
+	30, 31, -1, // vertex 219
+	30, 31, -1, // vertex 220
+	30, 31, -1, // vertex 221
+	30, 31, -1, // vertex 222
+	30, -1, -1, // vertex 223
+	30, 31, -1, // vertex 224
+	30, -1, -1, // vertex 225
+	30, 31, -1, // vertex 226
+	30, 29, -1, // vertex 227
+	30, -1, -1, // vertex 228
+	30, -1, -1, // vertex 229
+	30, -1, -1, // vertex 230
+	30, 31, -1, // vertex 231
+	30, -1, -1, // vertex 232
+	30, -1, -1, // vertex 233
+	30, -1, -1, // vertex 234
+	30, -1, -1, // vertex 235
+	30, -1, -1, // vertex 236
+	30, -1, -1, // vertex 237
+	30, -1, -1, // vertex 238
+	30, 31, -1, // vertex 239
+	30, 31, -1, // vertex 240
+	30, -1, -1, // vertex 241
+	30, -1, -1, // vertex 242
+	30, -1, -1, // vertex 243
+	30, -1, -1, // vertex 244
+	30, -1, -1, // vertex 245
+	30, -1, -1, // vertex 246
+	30, -1, -1, // vertex 247
+	30, -1, -1, // vertex 248
+	30, -1, -1, // vertex 249
+	30, -1, -1, // vertex 250
+	30, -1, -1, // vertex 251
+	30, -1, -1, // vertex 252
+	30, 33, -1, // vertex 253
+	30, -1, -1, // vertex 254
+	30, 33, -1, // vertex 255
+	30, -1, -1, // vertex 256
+	30, 33, -1, // vertex 257
+	30, -1, -1, // vertex 258
+	33, 30, -1, // vertex 259
+	30, -1, -1, // vertex 260
+	33, 30, -1, // vertex 261
+	30, 31, -1, // vertex 262
+	30, 31, -1, // vertex 263
+	30, 31, -1, // vertex 264
+	30, -1, -1, // vertex 265
+	30, -1, -1, // vertex 266
+	30, -1, -1, // vertex 267
+	30, -1, -1, // vertex 268
+	30, -1, -1, // vertex 269
+	30, -1, -1, // vertex 270
+	30, -1, -1, // vertex 271
+	30, -1, -1, // vertex 272
+	30, -1, -1, // vertex 273
+	30, -1, -1, // vertex 274
+	30, -1, -1, // vertex 275
+	30, -1, -1, // vertex 276
+	30, -1, -1, // vertex 277
+	30, 33, -1, // vertex 278
+	30, -1, -1, // vertex 279
+	30, 32, -1, // vertex 280
+	30, -1, -1, // vertex 281
+	30, -1, -1, // vertex 282
+	30, -1, -1, // vertex 283
+	30, -1, -1, // vertex 284
+	30, -1, -1, // vertex 285
+	30, -1, -1, // vertex 286
+	30, -1, -1, // vertex 287
+	30, -1, -1, // vertex 288
+	30, -1, -1, // vertex 289
+	30, 32, -1, // vertex 290
+	30, 32, -1, // vertex 291
+	30, 32, -1, // vertex 292
+	30, 32, -1, // vertex 293
+	30, -1, -1, // vertex 294
+	30, -1, -1, // vertex 295
+	30, -1, -1, // vertex 296
+	30, 32, -1, // vertex 297
+	30, -1, -1, // vertex 298
+	30, 32, -1, // vertex 299
+	30, -1, -1, // vertex 300
+	30, 32, -1, // vertex 301
+	30, -1, -1, // vertex 302
+	30, 32, -1, // vertex 303
+	30, 32, -1, // vertex 304
+	30, -1, -1, // vertex 305
+	30, 33, -1, // vertex 306
+	30, -1, -1, // vertex 307
+	30, 33, -1, // vertex 308
+	30, 32, -1, // vertex 309
+	30, 32, -1, // vertex 310
+	30, 32, -1, // vertex 311
+	30, 32, -1, // vertex 312
+	30, 32, -1, // vertex 313
+	30, 32, -1, // vertex 314
+	30, 32, -1, // vertex 315
+	30, 32, -1, // vertex 316
+	30, 32, -1, // vertex 317
+	30, 32, -1, // vertex 318
+	30, 32, -1, // vertex 319
+	30, 32, -1, // vertex 320
+	30, 32, -1, // vertex 321
+	30, 32, -1, // vertex 322
+	30, 32, -1, // vertex 323
+	30, 32, -1, // vertex 324
+	30, 32, -1, // vertex 325
+	30, 32, -1, // vertex 326
+	30, 32, -1, // vertex 327
+	30, 32, -1, // vertex 328
+	30, 32, -1, // vertex 329
+	30, 32, -1, // vertex 330
+	30, 32, -1, // vertex 331
+	30, 32, -1, // vertex 332
+	30, 32, -1, // vertex 333
+	30, 32, -1, // vertex 334
+	30, -1, -1, // vertex 335
+	30, -1, -1, // vertex 336
+	30, -1, -1, // vertex 337
+	30, -1, -1, // vertex 338
+	30, -1, -1, // vertex 339
+	30, -1, -1, // vertex 340
+	30, -1, -1, // vertex 341
+	30, -1, -1, // vertex 342
+	30, -1, -1, // vertex 343
+	30, -1, -1, // vertex 344
+	30, -1, -1, // vertex 345
+	30, 31, -1, // vertex 346
+	30, 31, -1, // vertex 347
+	30, -1, -1, // vertex 348
+	30, -1, -1, // vertex 349
+	30, -1, -1, // vertex 350
+	30, -1, -1, // vertex 351
+	30, -1, -1, // vertex 352
+	30, -1, -1, // vertex 353
+	30, -1, -1, // vertex 354
+	30, -1, -1, // vertex 355
+	30, -1, -1, // vertex 356
+	30, -1, -1, // vertex 357
+	30, -1, -1, // vertex 358
+	30, -1, -1, // vertex 359
+	30, -1, -1, // vertex 360
+	30, -1, -1, // vertex 361
+	30, 33, -1, // vertex 362
+	30, 33, -1, // vertex 363
+	33, 30, -1, // vertex 364
+	30, 31, -1, // vertex 365
+	30, 31, -1, // vertex 366
+	30, 31, -1, // vertex 367
+	30, 31, -1, // vertex 368
+	30, 31, -1, // vertex 369
+	31, 30, -1, // vertex 370
+	30, 31, -1, // vertex 371
+	30, 31, -1, // vertex 372
+	30, 31, -1, // vertex 373
+	30, 31, -1, // vertex 374
+	30, 31, -1, // vertex 375
+	30, 31, -1, // vertex 376
+	30, 31, -1, // vertex 377
+	30, 31, -1, // vertex 378
+	30, 31, -1, // vertex 379
+	30, 31, -1, // vertex 380
+	30, 31, -1, // vertex 381
+	30, -1, -1, // vertex 382
+	30, -1, -1, // vertex 383
+	30, -1, -1, // vertex 384
+	30, -1, -1, // vertex 385
+	30, -1, -1, // vertex 386
+	30, -1, -1, // vertex 387
+	30, -1, -1, // vertex 388
+	30, -1, -1, // vertex 389
+	30, -1, -1, // vertex 390
+	29, 30, -1, // vertex 391
+	30, -1, -1, // vertex 392
+	30, 29, -1, // vertex 393
+	30, 31, -1, // vertex 394
+	30, -1, -1, // vertex 395
+	30, 31, -1, // vertex 396
+	30, -1, -1, // vertex 397
+	30, -1, -1, // vertex 398
+	30, -1, -1, // vertex 399
+	30, -1, -1, // vertex 400
+	30, -1, -1, // vertex 401
+	30, -1, -1, // vertex 402
+	30, -1, -1, // vertex 403
+	30, -1, -1, // vertex 404
+	30, -1, -1, // vertex 405
+	30, 31, -1, // vertex 406
+	30, 31, -1, // vertex 407
+	30, 31, -1, // vertex 408
+	33, 30, -1, // vertex 409
+	30, -1, -1, // vertex 410
+	33, 30, -1, // vertex 411
+	30, -1, -1, // vertex 412
+	30, 33, -1, // vertex 413
+	30, -1, -1, // vertex 414
+	30, 33, -1, // vertex 415
+	30, -1, -1, // vertex 416
+	30, 33, -1, // vertex 417
+	30, -1, -1, // vertex 418
+	30, -1, -1, // vertex 419
+	30, -1, -1, // vertex 420
+	30, -1, -1, // vertex 421
+	30, 33, -1, // vertex 422
+	30, -1, -1, // vertex 423
+	30, -1, -1, // vertex 424
+	30, -1, -1, // vertex 425
+	30, -1, -1, // vertex 426
+	30, -1, -1, // vertex 427
+	30, -1, -1, // vertex 428
+	30, -1, -1, // vertex 429
+	30, -1, -1, // vertex 430
+	30, -1, -1, // vertex 431
+	30, -1, -1, // vertex 432
+	30, -1, -1, // vertex 433
+	30, -1, -1, // vertex 434
+	30, -1, -1, // vertex 435
+	30, 31, -1, // vertex 436
+	30, 31, -1, // vertex 437
+	30, -1, -1, // vertex 438
+	30, -1, -1, // vertex 439
+	30, -1, -1, // vertex 440
+	30, 32, -1, // vertex 441
+	30, -1, -1, // vertex 442
+	30, -1, -1, // vertex 443
+	30, -1, -1, // vertex 444
+	30, -1, -1, // vertex 445
+	30, -1, -1, // vertex 446
+	30, -1, -1, // vertex 447
+	30, -1, -1, // vertex 448
+	30, -1, -1, // vertex 449
+	30, -1, -1, // vertex 450
+	30, 32, -1, // vertex 451
+	30, 32, -1, // vertex 452
+	30, 32, -1, // vertex 453
+	30, 32, -1, // vertex 454
+	30, -1, -1, // vertex 455
+	30, 32, -1, // vertex 456
+	30, -1, -1, // vertex 457
+	30, 32, -1, // vertex 458
+	30, -1, -1, // vertex 459
+	30, 32, -1, // vertex 460
+	30, -1, -1, // vertex 461
+	30, -1, -1, // vertex 462
+	30, 32, -1, // vertex 463
+	30, 32, -1, // vertex 464
+	30, 32, -1, // vertex 465
+	30, 32, -1, // vertex 466
+	30, 32, -1, // vertex 467
+	30, 32, -1, // vertex 468
+	30, 32, -1, // vertex 469
+	30, 32, -1, // vertex 470
+	30, 32, -1, // vertex 471
+	30, 32, -1, // vertex 472
+	30, 32, -1, // vertex 473
+	30, 32, -1, // vertex 474
+	30, 32, -1, // vertex 475
+	30, 32, -1, // vertex 476
+	30, 32, -1, // vertex 477
+	30, 32, -1, // vertex 478
+	30, 32, -1, // vertex 479
+	30, 32, -1, // vertex 480
+	30, 32, -1, // vertex 481
+	30, 32, -1, // vertex 482
+	30, 32, -1, // vertex 483
+	30, 32, -1, // vertex 484
+	30, 32, -1, // vertex 485
+	30, -1, -1, // vertex 486
+	30, -1, -1, // vertex 487
+	30, -1, -1, // vertex 488
+	30, -1, -1, // vertex 489
+	30, -1, -1, // vertex 490
+	30, -1, -1, // vertex 491
+	30, -1, -1, // vertex 492
+	30, -1, -1, // vertex 493
+	30, -1, -1, // vertex 494
+	30, -1, -1, // vertex 495
+	30, -1, -1, // vertex 496
+	30, -1, -1, // vertex 497
+	30, -1, -1, // vertex 498
+	30, -1, -1, // vertex 499
+	30, -1, -1, // vertex 500
+	29, 30, -1, // vertex 501
+	30, -1, -1, // vertex 502
+	30, -1, -1, // vertex 503
+	30, -1, -1, // vertex 504
+	30, -1, -1, // vertex 505
+	30, -1, -1, // vertex 506
+	30, -1, -1, // vertex 507
+	30, -1, -1, // vertex 508
+	30, -1, -1, // vertex 509
+	30, -1, -1, // vertex 510
+	30, -1, -1, // vertex 511
+	30, -1, -1, // vertex 512
+	30, -1, -1, // vertex 513
+	30, -1, -1, // vertex 514
+	30, -1, -1, // vertex 515
+	30, 32, -1, // vertex 516
+	30, 32, -1, // vertex 517
+	30, 32, -1, // vertex 518
+	30, -1, -1, // vertex 519
+	30, -1, -1, // vertex 520
+	30, -1, -1, // vertex 521
+	30, 31, -1, // vertex 522
+	30, 31, -1, // vertex 523
+	30, 31, -1, // vertex 524
+	30, 31, -1, // vertex 525
+	30, 31, -1, // vertex 526
+	30, 31, -1, // vertex 527
+	30, 31, -1, // vertex 528
+	30, 31, -1, // vertex 529
+	30, 31, -1, // vertex 530
+	30, 31, -1, // vertex 531
+	30, 31, -1, // vertex 532
+	30, 31, -1, // vertex 533
+	30, 31, -1, // vertex 534
+	30, 31, -1, // vertex 535
+	30, 31, -1, // vertex 536
+	30, 31, -1, // vertex 537
+	30, 31, -1, // vertex 538
+	30, 31, -1, // vertex 539
+	30, 31, -1, // vertex 540
+	30, 31, -1, // vertex 541
+	30, 31, -1, // vertex 542
+	30, 31, -1, // vertex 543
+	30, 31, -1, // vertex 544
+	30, 31, -1, // vertex 545
+	30, 31, -1, // vertex 546
+	30, 31, -1, // vertex 547
+	30, 31, -1, // vertex 548
+	30, 31, -1, // vertex 549
+	30, 31, -1, // vertex 550
+	30, 31, -1, // vertex 551
+	30, 31, -1, // vertex 552
+	31, 30, -1, // vertex 553
+	30, 31, -1, // vertex 554
+	31, 30, -1, // vertex 555
+	31, 30, -1, // vertex 556
+	30, 31, -1, // vertex 557
+	30, 31, -1, // vertex 558
+	30, 31, -1, // vertex 559
+	30, 31, -1, // vertex 560
+	30, 31, -1, // vertex 561
+	31, 30, -1, // vertex 562
+	30, 31, -1, // vertex 563
+	30, 31, -1, // vertex 564
+	30, 31, -1, // vertex 565
+	30, 31, -1, // vertex 566
+	30, 31, -1, // vertex 567
+	30, 31, -1, // vertex 568
+	31, 30, -1, // vertex 569
+	31, 30, -1, // vertex 570
+	30, -1, -1, // vertex 571
+	30, -1, -1, // vertex 572
+	30, -1, -1, // vertex 573
+	30, -1, -1, // vertex 574
+	30, -1, -1, // vertex 575
+	30, -1, -1, // vertex 576
+	30, -1, -1, // vertex 577
+	30, -1, -1, // vertex 578
+	30, -1, -1, // vertex 579
+	30, -1, -1, // vertex 580
+	30, -1, -1, // vertex 581
+	30, -1, -1, // vertex 582
+	30, -1, -1, // vertex 583
+	30, -1, -1, // vertex 584
+	30, -1, -1, // vertex 585
+	30, -1, -1, // vertex 586
+	30, -1, -1, // vertex 587
+	30, -1, -1, // vertex 588
+	30, -1, -1, // vertex 589
+	30, -1, -1, // vertex 590
+	30, -1, -1, // vertex 591
+	30, 31, -1, // vertex 592
+	30, -1, -1, // vertex 593
+	30, 31, -1, // vertex 594
+	30, 29, -1, // vertex 595
+	30, 29, -1, // vertex 596
+	30, -1, -1, // vertex 597
+	29, 30, -1, // vertex 598
+	30, 29, -1, // vertex 599
+	30, 29, -1, // vertex 600
+	30, 31, -1, // vertex 601
+	30, -1, -1, // vertex 602
+	30, 31, -1, // vertex 603
+	33, 30, -1, // vertex 604
+	33, 30, -1, // vertex 605
+	33, 30, -1, // vertex 606
+	33, 30, -1, // vertex 607
+	33, 30, -1, // vertex 608
+	30, 33, -1, // vertex 609
+	30, 33, -1, // vertex 610
+	30, 33, -1, // vertex 611
+	30, 32, -1, // vertex 612
+	30, 33, -1, // vertex 613
+	30, 33, -1, // vertex 614
+	33, 30, -1, // vertex 615
+	33, 30, -1, // vertex 616
+	33, 30, -1, // vertex 617
+	33, 30, -1, // vertex 618
+	33, 30, -1, // vertex 619
+	33, 30, -1, // vertex 620
+	33, 30, -1, // vertex 621
+	33, 30, -1, // vertex 622
+	33, 30, -1, // vertex 623
+	33, 30, -1, // vertex 624
+	33, 30, -1, // vertex 625
+	30, 33, -1, // vertex 626
+	30, 33, -1, // vertex 627
+	30, 33, -1, // vertex 628
+	30, 32, -1, // vertex 629
+	30, 32, -1, // vertex 630
+	30, -1, -1, // vertex 631
+	30, -1, -1, // vertex 632
+	30, -1, -1, // vertex 633
+	30, -1, -1, // vertex 634
+	30, -1, -1, // vertex 635
+	30, -1, -1, // vertex 636
+	30, 33, -1, // vertex 637
+	30, 33, -1, // vertex 638
+	30, -1, -1, // vertex 639
+	30, 32, -1, // vertex 640
+	30, -1, -1, // vertex 641
+	30, 32, -1, // vertex 642
+	30, -1, -1, // vertex 643
+	30, 32, -1, // vertex 644
+	30, -1, -1, // vertex 645
+	30, -1, -1, // vertex 646
+	30, -1, -1, // vertex 647
+	30, 32, -1, // vertex 648
+	30, 32, -1, // vertex 649
+	30, 32, -1, // vertex 650
+	30, 32, -1, // vertex 651
+	30, 32, -1, // vertex 652
+	30, 32, -1, // vertex 653
+	30, 32, -1, // vertex 654
+	30, 32, -1, // vertex 655
+	30, 32, -1, // vertex 656
+	30, 32, -1, // vertex 657
+	30, 32, -1, // vertex 658
+	30, -1, -1, // vertex 659
+	30, -1, -1, // vertex 660
+	30, -1, -1, // vertex 661
+	33, 30, -1, // vertex 662
+	33, 30, -1, // vertex 663
+	33, 30, -1, // vertex 664
+	33, 30, -1, // vertex 665
+	33, 30, -1, // vertex 666
+	33, 30, -1, // vertex 667
+	33, 30, -1, // vertex 668
+	33, 30, -1, // vertex 669
+	33, 30, -1, // vertex 670
+	33, 30, -1, // vertex 671
+	33, 30, -1, // vertex 672
+	33, 30, -1, // vertex 673
+	33, 30, -1, // vertex 674
+	33, 30, -1, // vertex 675
+	30, 33, -1, // vertex 676
+	30, 33, -1, // vertex 677
+	33, 30, -1, // vertex 678
+	33, 30, -1, // vertex 679
+	33, 30, -1, // vertex 680
+	33, 30, -1, // vertex 681
+	33, 30, -1, // vertex 682
+	30, -1, -1, // vertex 683
+	30, -1, -1, // vertex 684
+	30, -1, -1, // vertex 685
+	30, -1, -1, // vertex 686
+	30, -1, -1, // vertex 687
+	30, -1, -1, // vertex 688
+	30, 32, -1, // vertex 689
+	30, -1, -1, // vertex 690
+	30, 32, -1, // vertex 691
+	30, -1, -1, // vertex 692
+	30, 32, -1, // vertex 693
+	30, -1, -1, // vertex 694
+	33, 30, -1, // vertex 695
+	30, 33, -1, // vertex 696
+	30, 33, -1, // vertex 697
+	30, -1, -1, // vertex 698
+	30, -1, -1, // vertex 699
+	30, -1, -1, // vertex 700
+	30, 32, -1, // vertex 701
+	30, 32, -1, // vertex 702
+	30, 32, -1, // vertex 703
+	30, 32, -1, // vertex 704
+	30, 32, -1, // vertex 705
+	30, 32, -1, // vertex 706
+	30, 32, -1, // vertex 707
+	30, 32, -1, // vertex 708
+	30, 32, -1, // vertex 709
+	30, 32, -1, // vertex 710
+	30, 32, -1, // vertex 711
+	30, -1, -1, // vertex 712
+	30, -1, -1, // vertex 713
+	30, -1, -1, // vertex 714
+	30, 31, -1, // vertex 715
+	30, 31, -1, // vertex 716
+	30, 31, -1, // vertex 717
+	30, 31, -1, // vertex 718
+	30, 31, -1, // vertex 719
+	30, 31, -1, // vertex 720
+	30, 31, -1, // vertex 721
+	30, 31, -1, // vertex 722
+	30, 31, -1, // vertex 723
+	30, 31, -1, // vertex 724
+	30, 31, -1, // vertex 725
+	30, 31, -1, // vertex 726
+	30, 31, -1, // vertex 727
+	30, 31, -1, // vertex 728
+	30, 31, -1, // vertex 729
+	30, 31, -1, // vertex 730
+	30, 31, -1, // vertex 731
+	30, 31, -1, // vertex 732
+	30, 31, -1, // vertex 733
+	30, 31, -1, // vertex 734
+	30, 31, -1, // vertex 735
+	30, 31, -1, // vertex 736
+	30, 31, -1, // vertex 737
+	30, 31, -1, // vertex 738
+	30, 31, -1, // vertex 739
+	30, 31, -1, // vertex 740
+	30, 31, -1, // vertex 741
+	30, 31, -1, // vertex 742
+	30, 31, -1, // vertex 743
+	30, 31, -1, // vertex 744
+	30, 31, -1, // vertex 745
+	30, 31, -1, // vertex 746
+	30, 31, -1, // vertex 747
+	30, 31, -1, // vertex 748
+	30, 31, -1, // vertex 749
+	30, -1, -1, // vertex 750
+	30, -1, -1, // vertex 751
+	30, 31, -1, // vertex 752
+	30, -1, -1, // vertex 753
+	30, -1, -1, // vertex 754
+	30, 32, -1, // vertex 755
+	30, 32, -1, // vertex 756
+	30, 32, -1, // vertex 757
+	30, 32, -1, // vertex 758
+	30, 32, -1, // vertex 759
+	30, 32, -1, // vertex 760
+	30, 32, -1, // vertex 761
+	30, 32, -1, // vertex 762
+	30, -1, -1, // vertex 763
+	30, -1, -1, // vertex 764
+	30, 31, -1, // vertex 765
+	30, -1, -1, // vertex 766
+	30, -1, -1, // vertex 767
+	30, -1, -1, // vertex 768
+	30, -1, -1, // vertex 769
+	30, 31, -1, // vertex 770
+	30, 31, -1, // vertex 771
+	30, 31, -1, // vertex 772
+	30, 31, -1, // vertex 773
+	30, 31, -1, // vertex 774
+	30, 31, -1, // vertex 775
+	30, 31, -1, // vertex 776
+	30, 33, 31, // vertex 777
+	30, 31, -1, // vertex 778
+	30, 33, 31, // vertex 779
+	30, 31, -1, // vertex 780
+	30, 33, 31, // vertex 781
+	30, 33, 31, // vertex 782
+	30, 33, -1, // vertex 783
+	30, 33, -1, // vertex 784
+	30, 33, 31, // vertex 785
+	30, 33, -1, // vertex 786
+	30, 33, 31, // vertex 787
+	30, 33, -1, // vertex 788
+	30, 33, 31, // vertex 789
+	33, 30, -1, // vertex 790
+	30, 31, -1, // vertex 791
+	30, 31, -1, // vertex 792
+	30, 31, -1, // vertex 793
+	30, 31, -1, // vertex 794
+	30, 31, -1, // vertex 795
+	30, -1, -1, // vertex 796
+	30, 31, -1, // vertex 797
+	30, 31, -1, // vertex 798
+	30, 31, -1, // vertex 799
+	30, 31, -1, // vertex 800
+	30, 31, -1, // vertex 801
+	30, 31, -1, // vertex 802
+	30, 31, -1, // vertex 803
+	30, 31, -1, // vertex 804
+	30, 31, -1, // vertex 805
+	30, 31, -1, // vertex 806
+	30, 31, -1, // vertex 807
+	30, 31, -1, // vertex 808
+	30, 31, -1, // vertex 809
+	30, 31, -1, // vertex 810
+	30, 31, -1, // vertex 811
+	30, 31, -1, // vertex 812
+	30, 31, -1, // vertex 813
+	30, 31, -1, // vertex 814
+	30, 31, -1, // vertex 815
+	30, 31, -1, // vertex 816
+	30, 31, -1, // vertex 817
+	30, -1, -1, // vertex 818
+	30, 31, -1, // vertex 819
+	30, 31, -1, // vertex 820
+	30, 31, -1, // vertex 821
+	30, 31, -1, // vertex 822
+	30, 31, -1, // vertex 823
+	30, 31, -1, // vertex 824
+	30, 31, -1, // vertex 825
+	30, 31, -1, // vertex 826
+	30, 31, -1, // vertex 827
+	30, 31, -1, // vertex 828
+	30, 31, -1, // vertex 829
+	30, 31, -1, // vertex 830
+	30, 33, 31, // vertex 831
+	30, 31, -1, // vertex 832
+	30, 31, -1, // vertex 833
+	30, 31, -1, // vertex 834
+	30, 31, -1, // vertex 835
+	30, 31, -1, // vertex 836
+	30, 31, -1, // vertex 837
+	30, 31, -1, // vertex 838
+	30, 31, -1, // vertex 839
+	30, 31, -1, // vertex 840
+	30, -1, -1, // vertex 841
+	30, -1, -1, // vertex 842
+	30, 31, -1, // vertex 843
+	30, 31, -1, // vertex 844
+	30, 31, -1, // vertex 845
+	30, 31, -1, // vertex 846
+	30, 31, -1, // vertex 847
+	33, 30, -1, // vertex 848
+	30, 31, -1, // vertex 849
+	30, 33, -1, // vertex 850
+	30, 33, 31, // vertex 851
+	30, 31, -1, // vertex 852
+	30, 31, -1, // vertex 853
+	30, 31, -1, // vertex 854
+	30, 31, -1, // vertex 855
+	30, 31, -1, // vertex 856
+	30, 31, -1, // vertex 857
+	30, 31, -1, // vertex 858
+	30, 31, -1, // vertex 859
+	30, 31, -1, // vertex 860
+	30, 31, -1, // vertex 861
+	30, 31, -1, // vertex 862
+	30, 31, -1, // vertex 863
+	30, 31, -1, // vertex 864
+	30, 31, -1, // vertex 865
+	30, 31, -1, // vertex 866
+	30, 31, -1, // vertex 867
+	30, 31, -1, // vertex 868
+	30, 31, -1, // vertex 869
+	30, 31, -1, // vertex 870
+	30, 31, -1, // vertex 871
+	30, 31, -1, // vertex 872
+	30, 31, -1, // vertex 873
+	30, 31, -1, // vertex 874
+	30, 31, -1, // vertex 875
+	30, 31, -1, // vertex 876
+	30, 31, -1, // vertex 877
+	30, 31, -1, // vertex 878
+	30, 31, -1, // vertex 879
+	30, 31, -1, // vertex 880
+	30, 31, -1, // vertex 881
+	30, 31, -1, // vertex 882
+	30, 31, -1, // vertex 883
+	30, 31, -1, // vertex 884
+	30, -1, -1, // vertex 885
+	30, 31, -1, // vertex 886
+	30, 31, -1, // vertex 887
+	30, 31, -1, // vertex 888
+	30, 31, -1, // vertex 889
+	30, 31, -1, // vertex 890
+	30, 31, -1, // vertex 891
+	30, 31, -1, // vertex 892
+	30, 31, -1, // vertex 893
+	30, -1, -1, // vertex 894
+	30, 31, -1, // vertex 895
+	30, 31, -1, // vertex 896
+	30, 31, -1, // vertex 897
+	30, 32, -1, // vertex 898
+	30, 32, -1, // vertex 899
+	30, 32, -1, // vertex 900
+	30, 32, -1, // vertex 901
+	30, 32, -1, // vertex 902
+	30, 32, -1, // vertex 903
+	30, 32, -1, // vertex 904
+	30, 32, -1, // vertex 905
+	30, 32, -1, // vertex 906
+	30, 32, -1, // vertex 907
+	30, 32, -1, // vertex 908
+	30, 32, -1, // vertex 909
+	30, 32, -1, // vertex 910
+	30, 32, -1, // vertex 911
+	30, 32, -1, // vertex 912
+	30, 32, -1, // vertex 913
+	30, 32, -1, // vertex 914
+	30, 32, -1, // vertex 915
+	30, 32, -1, // vertex 916
+	30, 32, -1, // vertex 917
+	30, 32, -1, // vertex 918
+	30, 32, -1, // vertex 919
+	30, 32, -1, // vertex 920
+	30, 32, -1, // vertex 921
+	30, 32, -1, // vertex 922
+	30, 32, -1, // vertex 923
+	30, 32, -1, // vertex 924
+	30, 32, -1, // vertex 925
+	30, 32, -1, // vertex 926
+	30, 32, -1, // vertex 927
+	30, 32, -1, // vertex 928
+	30, 32, -1, // vertex 929
+	30, 32, -1, // vertex 930
+	30, 32, -1, // vertex 931
+	30, 32, -1, // vertex 932
+	30, 32, -1, // vertex 933
+	30, 32, -1, // vertex 934
+	30, 32, -1, // vertex 935
+	30, 32, -1, // vertex 936
+	30, 32, -1, // vertex 937
+	30, 32, -1, // vertex 938
+	30, 32, -1, // vertex 939
+	30, 32, -1, // vertex 940
+	30, 32, -1, // vertex 941
+	30, 32, -1, // vertex 942
+	30, 32, -1, // vertex 943
+	30, 32, -1, // vertex 944
+	30, 32, -1, // vertex 945
+	30, 32, -1, // vertex 946
+	30, 32, -1, // vertex 947
+	30, 32, -1, // vertex 948
+	30, 32, -1, // vertex 949
+	30, 32, -1, // vertex 950
+	30, 32, -1, // vertex 951
+	30, 32, -1, // vertex 952
+	30, 32, -1, // vertex 953
+	30, 32, -1, // vertex 954
+	30, 32, -1, // vertex 955
+	30, 32, -1, // vertex 956
+	30, 32, -1, // vertex 957
+	30, 32, -1, // vertex 958
+	30, 32, -1, // vertex 959
+	30, 32, -1, // vertex 960
+	30, 32, -1, // vertex 961
+	30, 32, -1, // vertex 962
+	30, 32, -1, // vertex 963
+	30, 32, -1, // vertex 964
+	30, 32, -1, // vertex 965
+	30, 32, -1, // vertex 966
+	30, 32, -1, // vertex 967
+	30, 32, -1, // vertex 968
+	30, 32, -1, // vertex 969
+	30, 32, -1, // vertex 970
+	30, 32, -1, // vertex 971
+	30, 32, -1, // vertex 972
+	30, 32, -1, // vertex 973
+	30, 32, -1, // vertex 974
+	30, 32, -1, // vertex 975
+	30, -1, -1, // vertex 976
+	30, -1, -1, // vertex 977
+	30, -1, -1, // vertex 978
+	30, -1, -1, // vertex 979
+	30, -1, -1, // vertex 980
+	30, -1, -1, // vertex 981
+	30, -1, -1, // vertex 982
+	30, -1, -1, // vertex 983
+	30, -1, -1, // vertex 984
+	30, -1, -1, // vertex 985
+	30, -1, -1, // vertex 986
+	30, -1, -1, // vertex 987
+	30, -1, -1, // vertex 988
+	30, -1, -1, // vertex 989
+	30, -1, -1, // vertex 990
+	30, -1, -1, // vertex 991
+	30, -1, -1, // vertex 992
+	30, -1, -1, // vertex 993
+	30, -1, -1, // vertex 994
+	30, -1, -1, // vertex 995
+	30, -1, -1, // vertex 996
+	30, -1, -1, // vertex 997
+	30, -1, -1, // vertex 998
+	30, -1, -1, // vertex 999
+	30, -1, -1, // vertex 1000
+	30, -1, -1, // vertex 1001
+	30, -1, -1, // vertex 1002
+	30, -1, -1, // vertex 1003
+	30, -1, -1, // vertex 1004
+	30, -1, -1, // vertex 1005
+	30, -1, -1, // vertex 1006
+	30, -1, -1, // vertex 1007
+	30, -1, -1, // vertex 1008
+	30, -1, -1, // vertex 1009
+	30, -1, -1, // vertex 1010
+	30, -1, -1, // vertex 1011
+	30, -1, -1, // vertex 1012
+	30, -1, -1, // vertex 1013
+	30, -1, -1, // vertex 1014
+	30, -1, -1, // vertex 1015
+	30, -1, -1, // vertex 1016
+	30, -1, -1, // vertex 1017
+	30, -1, -1, // vertex 1018
+	30, -1, -1, // vertex 1019
+	30, -1, -1, // vertex 1020
+	30, -1, -1, // vertex 1021
+	30, -1, -1, // vertex 1022
+	30, -1, -1, // vertex 1023
+	30, -1, -1, // vertex 1024
+	30, -1, -1, // vertex 1025
+	30, -1, -1, // vertex 1026
+	30, -1, -1, // vertex 1027
+	30, -1, -1, // vertex 1028
+	30, -1, -1, // vertex 1029
+	30, -1, -1, // vertex 1030
+	30, -1, -1, // vertex 1031
+	30, -1, -1, // vertex 1032
+	30, -1, -1, // vertex 1033
+	30, -1, -1, // vertex 1034
+	30, -1, -1, // vertex 1035
+	30, -1, -1, // vertex 1036
+	30, -1, -1, // vertex 1037
+	30, -1, -1, // vertex 1038
+	30, -1, -1, // vertex 1039
+	30, -1, -1, // vertex 1040
+	30, -1, -1, // vertex 1041
+	30, -1, -1, // vertex 1042
+	30, -1, -1, // vertex 1043
+	30, -1, -1, // vertex 1044
+	30, -1, -1, // vertex 1045
+	31, -1, -1, // vertex 1046
+	31, -1, -1, // vertex 1047
+	31, -1, -1, // vertex 1048
+	31, -1, -1, // vertex 1049
+	31, -1, -1, // vertex 1050
+	31, -1, -1, // vertex 1051
+	31, -1, -1, // vertex 1052
+	31, -1, -1, // vertex 1053
+	31, -1, -1, // vertex 1054
+	31, -1, -1, // vertex 1055
+	31, -1, -1, // vertex 1056
+	31, -1, -1, // vertex 1057
+	31, -1, -1, // vertex 1058
+	31, -1, -1, // vertex 1059
+	31, -1, -1, // vertex 1060
+	31, -1, -1, // vertex 1061
+	31, -1, -1, // vertex 1062
+	31, -1, -1, // vertex 1063
+	31, -1, -1, // vertex 1064
+	31, -1, -1, // vertex 1065
+	31, -1, -1, // vertex 1066
+	31, -1, -1, // vertex 1067
+	31, -1, -1, // vertex 1068
+	31, -1, -1, // vertex 1069
+	31, -1, -1, // vertex 1070
+	31, -1, -1, // vertex 1071
+	31, -1, -1, // vertex 1072
+	31, -1, -1, // vertex 1073
+	31, -1, -1, // vertex 1074
+	31, -1, -1, // vertex 1075
+	31, -1, -1, // vertex 1076
+	31, -1, -1, // vertex 1077
+	31, -1, -1, // vertex 1078
+	31, -1, -1, // vertex 1079
+	31, -1, -1, // vertex 1080
+	30, -1, -1, // vertex 1081
+	30, -1, -1, // vertex 1082
+	30, -1, -1, // vertex 1083
+	30, -1, -1, // vertex 1084
+	30, -1, -1, // vertex 1085
+	30, -1, -1, // vertex 1086
+	30, -1, -1, // vertex 1087
+	30, -1, -1, // vertex 1088
+	30, -1, -1, // vertex 1089
+	30, -1, -1, // vertex 1090
+	30, -1, -1, // vertex 1091
+	30, -1, -1, // vertex 1092
+	30, -1, -1, // vertex 1093
+	30, -1, -1, // vertex 1094
+	30, -1, -1, // vertex 1095
+	30, -1, -1, // vertex 1096
+	30, -1, -1, // vertex 1097
+	30, -1, -1, // vertex 1098
+	30, -1, -1, // vertex 1099
+	30, -1, -1, // vertex 1100
+	30, -1, -1, // vertex 1101
+	30, -1, -1, // vertex 1102
+	30, -1, -1, // vertex 1103
+	31, -1, -1, // vertex 1104
+	31, -1, -1, // vertex 1105
+	31, -1, -1, // vertex 1106
+	31, -1, -1, // vertex 1107
+	31, -1, -1, // vertex 1108
+	31, -1, -1, // vertex 1109
+	31, -1, -1, // vertex 1110
+	31, -1, -1, // vertex 1111
+	31, -1, -1, // vertex 1112
+	31, -1, -1, // vertex 1113
+	31, -1, -1, // vertex 1114
+	31, -1, -1, // vertex 1115
+	31, -1, -1, // vertex 1116
+	31, -1, -1, // vertex 1117
+	31, -1, -1, // vertex 1118
+	31, -1, -1, // vertex 1119
+	31, -1, -1, // vertex 1120
+	31, -1, -1, // vertex 1121
+	31, -1, -1, // vertex 1122
+	31, -1, -1, // vertex 1123
+	31, -1, -1, // vertex 1124
+	31, -1, -1, // vertex 1125
+	31, -1, -1, // vertex 1126
+	31, -1, -1, // vertex 1127
+	31, -1, -1, // vertex 1128
+	31, -1, -1, // vertex 1129
+	31, -1, -1, // vertex 1130
+	31, -1, -1, // vertex 1131
+	31, -1, -1, // vertex 1132
+	31, -1, -1, // vertex 1133
+	31, -1, -1, // vertex 1134
+	30, -1, -1, // vertex 1135
+	30, -1, -1, // vertex 1136
+	30, -1, -1, // vertex 1137
+	30, -1, -1, // vertex 1138
+	30, -1, -1, // vertex 1139
+	30, -1, -1, // vertex 1140
+	30, -1, -1, // vertex 1141
+	30, -1, -1, // vertex 1142
+	30, -1, -1, // vertex 1143
+	30, -1, -1, // vertex 1144
+	30, -1, -1, // vertex 1145
+	30, -1, -1, // vertex 1146
+	30, -1, -1, // vertex 1147
+	30, -1, -1, // vertex 1148
+	30, -1, -1, // vertex 1149
+	30, -1, -1, // vertex 1150
+	30, -1, -1, // vertex 1151
+	30, -1, -1, // vertex 1152
+	30, -1, -1, // vertex 1153
+	30, -1, -1, // vertex 1154
+	30, -1, -1, // vertex 1155
+	30, -1, -1, // vertex 1156
+	30, -1, -1, // vertex 1157
+	30, -1, -1, // vertex 1158
+	30, -1, -1, // vertex 1159
+	30, -1, -1, // vertex 1160
+	30, -1, -1, // vertex 1161
+	30, -1, -1, // vertex 1162
+	30, -1, -1, // vertex 1163
+	30, -1, -1, // vertex 1164
+	30, -1, -1, // vertex 1165
+	30, -1, -1, // vertex 1166
+	30, -1, -1, // vertex 1167
+	30, -1, -1, // vertex 1168
+	30, -1, -1, // vertex 1169
+	30, -1, -1, // vertex 1170
+	30, -1, -1, // vertex 1171
+	30, -1, -1, // vertex 1172
+	30, -1, -1, // vertex 1173
+	30, -1, -1, // vertex 1174
+	30, -1, -1, // vertex 1175
+	30, -1, -1, // vertex 1176
+	30, -1, -1, // vertex 1177
+	30, -1, -1, // vertex 1178
+	30, -1, -1, // vertex 1179
+	30, 31, -1, // vertex 1180
+	30, -1, -1, // vertex 1181
+	30, -1, -1, // vertex 1182
+	30, 32, -1, // vertex 1183
+	30, 32, -1, // vertex 1184
+	30, 32, -1, // vertex 1185
+	30, 32, -1, // vertex 1186
+	30, 32, -1, // vertex 1187
+	30, 32, -1, // vertex 1188
+	30, 32, -1, // vertex 1189
+	30, 32, -1, // vertex 1190
+	30, -1, -1, // vertex 1191
+	30, -1, -1, // vertex 1192
+	30, 31, -1, // vertex 1193
+	30, -1, -1, // vertex 1194
+	30, -1, -1, // vertex 1195
+	30, -1, -1, // vertex 1196
+	30, -1, -1, // vertex 1197
+	30, 31, -1, // vertex 1198
+	30, 31, -1, // vertex 1199
+	30, 31, -1, // vertex 1200
+	30, 31, -1, // vertex 1201
+	30, 31, -1, // vertex 1202
+	30, 31, -1, // vertex 1203
+	30, 31, -1, // vertex 1204
+	30, 33, 31, // vertex 1205
+	30, 31, -1, // vertex 1206
+	30, 33, 31, // vertex 1207
+	30, 31, -1, // vertex 1208
+	30, 33, 31, // vertex 1209
+	30, 33, 31, // vertex 1210
+	30, 33, -1, // vertex 1211
+	30, 33, -1, // vertex 1212
+	30, 33, 31, // vertex 1213
+	30, 33, -1, // vertex 1214
+	30, 33, 31, // vertex 1215
+	30, 33, -1, // vertex 1216
+	30, 33, 31, // vertex 1217
+	33, 30, -1, // vertex 1218
+	30, 31, -1, // vertex 1219
+	30, 31, -1, // vertex 1220
+	30, 31, -1, // vertex 1221
+	30, 31, -1, // vertex 1222
+	30, 31, -1, // vertex 1223
+	30, -1, -1, // vertex 1224
+	30, 31, -1, // vertex 1225
+	30, 31, -1, // vertex 1226
+	30, 31, -1, // vertex 1227
+	30, 31, -1, // vertex 1228
+	30, 31, -1, // vertex 1229
+	30, 31, -1, // vertex 1230
+	30, 31, -1, // vertex 1231
+	30, 31, -1, // vertex 1232
+	30, 31, -1, // vertex 1233
+	30, 31, -1, // vertex 1234
+	30, 31, -1, // vertex 1235
+	30, 31, -1, // vertex 1236
+	30, 31, -1, // vertex 1237
+	30, 31, -1, // vertex 1238
+	30, 31, -1, // vertex 1239
+	30, 31, -1, // vertex 1240
+	30, 31, -1, // vertex 1241
+	30, 31, -1, // vertex 1242
+	30, 31, -1, // vertex 1243
+	30, 31, -1, // vertex 1244
+	30, 31, -1, // vertex 1245
+	30, -1, -1, // vertex 1246
+	30, 31, -1, // vertex 1247
+	30, 31, -1, // vertex 1248
+	30, 31, -1, // vertex 1249
+	30, 31, -1, // vertex 1250
+	30, 31, -1, // vertex 1251
+	30, 31, -1, // vertex 1252
+	30, 31, -1, // vertex 1253
+	30, 31, -1, // vertex 1254
+	30, 31, -1, // vertex 1255
+	30, 31, -1, // vertex 1256
+	30, 31, -1, // vertex 1257
+	30, 31, -1, // vertex 1258
+	30, 33, 31, // vertex 1259
+	30, 31, -1, // vertex 1260
+	30, 31, -1, // vertex 1261
+	30, 31, -1, // vertex 1262
+	30, 31, -1, // vertex 1263
+	30, 31, -1, // vertex 1264
+	30, 31, -1, // vertex 1265
+	30, 31, -1, // vertex 1266
+	30, 31, -1, // vertex 1267
+	30, 31, -1, // vertex 1268
+	30, -1, -1, // vertex 1269
+	30, -1, -1, // vertex 1270
+	30, 31, -1, // vertex 1271
+	30, 31, -1, // vertex 1272
+	30, 31, -1, // vertex 1273
+	30, 31, -1, // vertex 1274
+	30, 31, -1, // vertex 1275
+	33, 30, -1, // vertex 1276
+	30, 31, -1, // vertex 1277
+	30, 33, -1, // vertex 1278
+	30, 33, 31, // vertex 1279
+	30, 31, -1, // vertex 1280
+	30, 31, -1, // vertex 1281
+	30, 31, -1, // vertex 1282
+	30, 31, -1, // vertex 1283
+	30, 31, -1, // vertex 1284
+	30, 31, -1, // vertex 1285
+	30, 31, -1, // vertex 1286
+	30, 31, -1, // vertex 1287
+	30, 31, -1, // vertex 1288
+	30, 31, -1, // vertex 1289
+	30, 31, -1, // vertex 1290
+	30, 31, -1, // vertex 1291
+	30, 31, -1, // vertex 1292
+	30, 31, -1, // vertex 1293
+	30, 31, -1, // vertex 1294
+	30, 31, -1, // vertex 1295
+	30, 31, -1, // vertex 1296
+	30, 31, -1, // vertex 1297
+	30, 31, -1, // vertex 1298
+	30, 31, -1, // vertex 1299
+	30, 31, -1, // vertex 1300
+	30, 31, -1, // vertex 1301
+	30, 31, -1, // vertex 1302
+	30, 31, -1, // vertex 1303
+	30, 31, -1, // vertex 1304
+	30, 31, -1, // vertex 1305
+	30, 31, -1, // vertex 1306
+	30, 31, -1, // vertex 1307
+	30, 31, -1, // vertex 1308
+	30, 31, -1, // vertex 1309
+	30, 31, -1, // vertex 1310
+	30, 31, -1, // vertex 1311
+	30, 31, -1, // vertex 1312
+	30, -1, -1, // vertex 1313
+	30, 31, -1, // vertex 1314
+	30, 31, -1, // vertex 1315
+	30, 31, -1, // vertex 1316
+	30, 31, -1, // vertex 1317
+	30, 31, -1, // vertex 1318
+	30, 31, -1, // vertex 1319
+	30, 31, -1, // vertex 1320
+	30, 31, -1, // vertex 1321
+	30, -1, -1, // vertex 1322
+	30, 31, -1, // vertex 1323
+	30, 31, -1, // vertex 1324
+	30, 31, -1, // vertex 1325
+	30, 32, -1, // vertex 1326
+	30, 32, -1, // vertex 1327
+	30, 32, -1, // vertex 1328
+	30, 32, -1, // vertex 1329
+	30, 32, -1, // vertex 1330
+	30, 32, -1, // vertex 1331
+	30, 32, -1, // vertex 1332
+	30, 32, -1, // vertex 1333
+	30, 32, -1, // vertex 1334
+	30, 32, -1, // vertex 1335
+	30, 32, -1, // vertex 1336
+	30, 32, -1, // vertex 1337
+	30, 32, -1, // vertex 1338
+	30, 32, -1, // vertex 1339
+	30, 32, -1, // vertex 1340
+	30, 32, -1, // vertex 1341
+	30, 32, -1, // vertex 1342
+	30, 32, -1, // vertex 1343
+	30, 32, -1, // vertex 1344
+	30, 32, -1, // vertex 1345
+	30, 32, -1, // vertex 1346
+	30, 32, -1, // vertex 1347
+	30, 32, -1, // vertex 1348
+	30, 32, -1, // vertex 1349
+	30, 32, -1, // vertex 1350
+	30, 32, -1, // vertex 1351
+	30, 32, -1, // vertex 1352
+	30, 32, -1, // vertex 1353
+	30, 32, -1, // vertex 1354
+	30, 32, -1, // vertex 1355
+	30, 32, -1, // vertex 1356
+	30, 32, -1, // vertex 1357
+	30, 32, -1, // vertex 1358
+	30, 32, -1, // vertex 1359
+	30, 32, -1, // vertex 1360
+	30, 32, -1, // vertex 1361
+	30, 32, -1, // vertex 1362
+	30, 32, -1, // vertex 1363
+	30, 32, -1, // vertex 1364
+	30, 32, -1, // vertex 1365
+	30, 32, -1, // vertex 1366
+	30, 32, -1, // vertex 1367
+	30, 32, -1, // vertex 1368
+	30, 32, -1, // vertex 1369
+	30, 32, -1, // vertex 1370
+	30, 32, -1, // vertex 1371
+	30, 32, -1, // vertex 1372
+	30, 32, -1, // vertex 1373
+	30, 32, -1, // vertex 1374
+	30, 32, -1, // vertex 1375
+	30, 32, -1, // vertex 1376
+	30, 32, -1, // vertex 1377
+	30, 32, -1, // vertex 1378
+	30, 32, -1, // vertex 1379
+	30, 32, -1, // vertex 1380
+	30, 32, -1, // vertex 1381
+	30, 32, -1, // vertex 1382
+	30, 32, -1, // vertex 1383
+	30, 32, -1, // vertex 1384
+	30, 32, -1, // vertex 1385
+	30, 32, -1, // vertex 1386
+	30, 32, -1, // vertex 1387
+	30, 32, -1, // vertex 1388
+	30, 32, -1, // vertex 1389
+	30, 32, -1, // vertex 1390
+	30, 32, -1, // vertex 1391
+	30, 32, -1, // vertex 1392
+	30, 32, -1, // vertex 1393
+	30, 32, -1, // vertex 1394
+	30, 32, -1, // vertex 1395
+	30, 32, -1, // vertex 1396
+	30, 32, -1, // vertex 1397
+	30, 32, -1, // vertex 1398
+	30, 32, -1, // vertex 1399
+	30, 32, -1, // vertex 1400
+	30, 32, -1, // vertex 1401
+	30, 32, -1, // vertex 1402
+	30, 32, -1, // vertex 1403
+};
+
+float testWeights[] = {
+	1.000000f, 0.000000f, 0.000000f, // vertex 0
+	1.000000f, 0.000000f, 0.000000f, // vertex 1
+	1.000000f, 0.000000f, 0.000000f, // vertex 2
+	1.000000f, 0.000000f, 0.000000f, // vertex 3
+	1.000000f, 0.000000f, 0.000000f, // vertex 4
+	1.000000f, 0.000000f, 0.000000f, // vertex 5
+	1.000000f, 0.000000f, 0.000000f, // vertex 6
+	1.000000f, 0.000000f, 0.000000f, // vertex 7
+	1.000000f, 0.000000f, 0.000000f, // vertex 8
+	0.500000f, 0.500000f, 0.000000f, // vertex 9
+	0.500000f, 0.500000f, 0.000000f, // vertex 10
+	0.500000f, 0.500000f, 0.000000f, // vertex 11
+	0.500000f, 0.500000f, 0.000000f, // vertex 12
+	0.500000f, 0.500000f, 0.000000f, // vertex 13
+	0.500000f, 0.500000f, 0.000000f, // vertex 14
+	0.555556f, 0.444444f, 0.000000f, // vertex 15
+	0.555556f, 0.444444f, 0.000000f, // vertex 16
+	0.833333f, 0.166667f, 0.000000f, // vertex 17
+	0.666667f, 0.333333f, 0.000000f, // vertex 18
+	0.833333f, 0.166667f, 0.000000f, // vertex 19
+	0.833333f, 0.166667f, 0.000000f, // vertex 20
+	0.833333f, 0.166667f, 0.000000f, // vertex 21
+	1.000000f, 0.000000f, 0.000000f, // vertex 22
+	1.000000f, 0.000000f, 0.000000f, // vertex 23
+	1.000000f, 0.000000f, 0.000000f, // vertex 24
+	1.000000f, 0.000000f, 0.000000f, // vertex 25
+	1.000000f, 0.000000f, 0.000000f, // vertex 26
+	1.000000f, 0.000000f, 0.000000f, // vertex 27
+	0.926651f, 0.073349f, 0.000000f, // vertex 28
+	1.000000f, 0.000000f, 0.000000f, // vertex 29
+	0.833333f, 0.166667f, 0.000000f, // vertex 30
+	1.000000f, 0.000000f, 0.000000f, // vertex 31
+	0.833333f, 0.166667f, 0.000000f, // vertex 32
+	1.000000f, 0.000000f, 0.000000f, // vertex 33
+	0.666667f, 0.333333f, 0.000000f, // vertex 34
+	0.833333f, 0.166667f, 0.000000f, // vertex 35
+	0.666667f, 0.333333f, 0.000000f, // vertex 36
+	0.555556f, 0.444444f, 0.000000f, // vertex 37
+	0.500000f, 0.500000f, 0.000000f, // vertex 38
+	0.555556f, 0.444444f, 0.000000f, // vertex 39
+	0.500000f, 0.500000f, 0.000000f, // vertex 40
+	0.555556f, 0.444444f, 0.000000f, // vertex 41
+	0.500000f, 0.500000f, 0.000000f, // vertex 42
+	1.000000f, 0.000000f, 0.000000f, // vertex 43
+	1.000000f, 0.000000f, 0.000000f, // vertex 44
+	1.000000f, 0.000000f, 0.000000f, // vertex 45
+	1.000000f, 0.000000f, 0.000000f, // vertex 46
+	1.000000f, 0.000000f, 0.000000f, // vertex 47
+	1.000000f, 0.000000f, 0.000000f, // vertex 48
+	1.000000f, 0.000000f, 0.000000f, // vertex 49
+	1.000000f, 0.000000f, 0.000000f, // vertex 50
+	0.833333f, 0.166667f, 0.000000f, // vertex 51
+	0.833333f, 0.166667f, 0.000000f, // vertex 52
+	0.833333f, 0.166667f, 0.000000f, // vertex 53
+	0.666667f, 0.333333f, 0.000000f, // vertex 54
+	0.833333f, 0.166667f, 0.000000f, // vertex 55
+	0.555556f, 0.444444f, 0.000000f, // vertex 56
+	0.555556f, 0.444444f, 0.000000f, // vertex 57
+	0.500000f, 0.500000f, 0.000000f, // vertex 58
+	0.500000f, 0.500000f, 0.000000f, // vertex 59
+	0.500000f, 0.500000f, 0.000000f, // vertex 60
+	0.500000f, 0.500000f, 0.000000f, // vertex 61
+	0.500000f, 0.500000f, 0.000000f, // vertex 62
+	0.500000f, 0.500000f, 0.000000f, // vertex 63
+	0.500000f, 0.500000f, 0.000000f, // vertex 64
+	0.555556f, 0.444444f, 0.000000f, // vertex 65
+	0.500000f, 0.500000f, 0.000000f, // vertex 66
+	0.555556f, 0.444444f, 0.000000f, // vertex 67
+	0.500000f, 0.500000f, 0.000000f, // vertex 68
+	0.555556f, 0.444444f, 0.000000f, // vertex 69
+	0.666667f, 0.333333f, 0.000000f, // vertex 70
+	0.833333f, 0.166667f, 0.000000f, // vertex 71
+	0.666667f, 0.333333f, 0.000000f, // vertex 72
+	1.000000f, 0.000000f, 0.000000f, // vertex 73
+	0.833333f, 0.166667f, 0.000000f, // vertex 74
+	1.000000f, 0.000000f, 0.000000f, // vertex 75
+	0.833333f, 0.166667f, 0.000000f, // vertex 76
+	1.000000f, 0.000000f, 0.000000f, // vertex 77
+	1.000000f, 0.000000f, 0.000000f, // vertex 78
+	1.000000f, 0.000000f, 0.000000f, // vertex 79
+	1.000000f, 0.000000f, 0.000000f, // vertex 80
+	1.000000f, 0.000000f, 0.000000f, // vertex 81
+	1.000000f, 0.000000f, 0.000000f, // vertex 82
+	1.000000f, 0.000000f, 0.000000f, // vertex 83
+	1.000000f, 0.000000f, 0.000000f, // vertex 84
+	1.000000f, 0.000000f, 0.000000f, // vertex 85
+	1.000000f, 0.000000f, 0.000000f, // vertex 86
+	1.000000f, 0.000000f, 0.000000f, // vertex 87
+	1.000000f, 0.000000f, 0.000000f, // vertex 88
+	1.000000f, 0.000000f, 0.000000f, // vertex 89
+	1.000000f, 0.000000f, 0.000000f, // vertex 90
+	1.000000f, 0.000000f, 0.000000f, // vertex 91
+	1.000000f, 0.000000f, 0.000000f, // vertex 92
+	1.000000f, 0.000000f, 0.000000f, // vertex 93
+	1.000000f, 0.000000f, 0.000000f, // vertex 94
+	0.926651f, 0.073349f, 0.000000f, // vertex 95
+	0.926651f, 0.073349f, 0.000000f, // vertex 96
+	1.000000f, 0.000000f, 0.000000f, // vertex 97
+	1.000000f, 0.000000f, 0.000000f, // vertex 98
+	1.000000f, 0.000000f, 0.000000f, // vertex 99
+	0.500000f, 0.500000f, 0.000000f, // vertex 100
+	0.500000f, 0.500000f, 0.000000f, // vertex 101
+	1.000000f, 0.000000f, 0.000000f, // vertex 102
+	1.000000f, 0.000000f, 0.000000f, // vertex 103
+	0.926651f, 0.073349f, 0.000000f, // vertex 104
+	1.000000f, 0.000000f, 0.000000f, // vertex 105
+	1.000000f, 0.000000f, 0.000000f, // vertex 106
+	1.000000f, 0.000000f, 0.000000f, // vertex 107
+	1.000000f, 0.000000f, 0.000000f, // vertex 108
+	1.000000f, 0.000000f, 0.000000f, // vertex 109
+	1.000000f, 0.000000f, 0.000000f, // vertex 110
+	1.000000f, 0.000000f, 0.000000f, // vertex 111
+	1.000000f, 0.000000f, 0.000000f, // vertex 112
+	1.000000f, 0.000000f, 0.000000f, // vertex 113
+	1.000000f, 0.000000f, 0.000000f, // vertex 114
+	0.500000f, 0.500000f, 0.000000f, // vertex 115
+	1.000000f, 0.000000f, 0.000000f, // vertex 116
+	0.500000f, 0.500000f, 0.000000f, // vertex 117
+	0.500000f, 0.500000f, 0.000000f, // vertex 118
+	0.666667f, 0.333333f, 0.000000f, // vertex 119
+	0.666667f, 0.333333f, 0.000000f, // vertex 120
+	0.800000f, 0.200000f, 0.000000f, // vertex 121
+	0.800000f, 0.200000f, 0.000000f, // vertex 122
+	0.500000f, 0.500000f, 0.000000f, // vertex 123
+	0.500000f, 0.500000f, 0.000000f, // vertex 124
+	0.500000f, 0.500000f, 0.000000f, // vertex 125
+	0.500000f, 0.500000f, 0.000000f, // vertex 126
+	0.500000f, 0.500000f, 0.000000f, // vertex 127
+	0.500000f, 0.500000f, 0.000000f, // vertex 128
+	0.500000f, 0.500000f, 0.000000f, // vertex 129
+	0.500000f, 0.500000f, 0.000000f, // vertex 130
+	0.500000f, 0.500000f, 0.000000f, // vertex 131
+	0.500000f, 0.500000f, 0.000000f, // vertex 132
+	0.500000f, 0.500000f, 0.000000f, // vertex 133
+	0.500000f, 0.500000f, 0.000000f, // vertex 134
+	0.800000f, 0.200000f, 0.000000f, // vertex 135
+	0.500000f, 0.500000f, 0.000000f, // vertex 136
+	0.500000f, 0.500000f, 0.000000f, // vertex 137
+	0.500000f, 0.500000f, 0.000000f, // vertex 138
+	0.500000f, 0.500000f, 0.000000f, // vertex 139
+	0.500000f, 0.500000f, 0.000000f, // vertex 140
+	0.666667f, 0.333333f, 0.000000f, // vertex 141
+	0.500000f, 0.500000f, 0.000000f, // vertex 142
+	0.666667f, 0.333333f, 0.000000f, // vertex 143
+	0.500000f, 0.500000f, 0.000000f, // vertex 144
+	0.833333f, 0.166667f, 0.000000f, // vertex 145
+	0.833333f, 0.166667f, 0.000000f, // vertex 146
+	1.000000f, 0.000000f, 0.000000f, // vertex 147
+	1.000000f, 0.000000f, 0.000000f, // vertex 148
+	1.000000f, 0.000000f, 0.000000f, // vertex 149
+	1.000000f, 0.000000f, 0.000000f, // vertex 150
+	1.000000f, 0.000000f, 0.000000f, // vertex 151
+	1.000000f, 0.000000f, 0.000000f, // vertex 152
+	1.000000f, 0.000000f, 0.000000f, // vertex 153
+	0.833333f, 0.166667f, 0.000000f, // vertex 154
+	1.000000f, 0.000000f, 0.000000f, // vertex 155
+	0.800000f, 0.200000f, 0.000000f, // vertex 156
+	0.666667f, 0.333333f, 0.000000f, // vertex 157
+	0.666667f, 0.333333f, 0.000000f, // vertex 158
+	0.555556f, 0.444444f, 0.000000f, // vertex 159
+	0.555556f, 0.444444f, 0.000000f, // vertex 160
+	0.500000f, 0.500000f, 0.000000f, // vertex 161
+	0.555556f, 0.444444f, 0.000000f, // vertex 162
+	0.500000f, 0.500000f, 0.000000f, // vertex 163
+	0.555556f, 0.444444f, 0.000000f, // vertex 164
+	0.500000f, 0.500000f, 0.000000f, // vertex 165
+	0.500000f, 0.500000f, 0.000000f, // vertex 166
+	0.555556f, 0.444444f, 0.000000f, // vertex 167
+	0.500000f, 0.500000f, 0.000000f, // vertex 168
+	0.555556f, 0.444444f, 0.000000f, // vertex 169
+	0.500000f, 0.500000f, 0.000000f, // vertex 170
+	0.555556f, 0.444444f, 0.000000f, // vertex 171
+	0.555556f, 0.444444f, 0.000000f, // vertex 172
+	0.666667f, 0.333333f, 0.000000f, // vertex 173
+	0.666667f, 0.333333f, 0.000000f, // vertex 174
+	0.800000f, 0.200000f, 0.000000f, // vertex 175
+	1.000000f, 0.000000f, 0.000000f, // vertex 176
+	0.833333f, 0.166667f, 0.000000f, // vertex 177
+	1.000000f, 0.000000f, 0.000000f, // vertex 178
+	1.000000f, 0.000000f, 0.000000f, // vertex 179
+	1.000000f, 0.000000f, 0.000000f, // vertex 180
+	1.000000f, 0.000000f, 0.000000f, // vertex 181
+	1.000000f, 0.000000f, 0.000000f, // vertex 182
+	1.000000f, 0.000000f, 0.000000f, // vertex 183
+	1.000000f, 0.000000f, 0.000000f, // vertex 184
+	0.833333f, 0.166667f, 0.000000f, // vertex 185
+	0.833333f, 0.166667f, 0.000000f, // vertex 186
+	0.500000f, 0.500000f, 0.000000f, // vertex 187
+	0.666667f, 0.333333f, 0.000000f, // vertex 188
+	0.500000f, 0.500000f, 0.000000f, // vertex 189
+	0.666667f, 0.333333f, 0.000000f, // vertex 190
+	0.500000f, 0.500000f, 0.000000f, // vertex 191
+	0.500000f, 0.500000f, 0.000000f, // vertex 192
+	0.500000f, 0.500000f, 0.000000f, // vertex 193
+	0.500000f, 0.500000f, 0.000000f, // vertex 194
+	0.500000f, 0.500000f, 0.000000f, // vertex 195
+	0.800000f, 0.200000f, 0.000000f, // vertex 196
+	0.500000f, 0.500000f, 0.000000f, // vertex 197
+	0.655738f, 0.344262f, 0.000000f, // vertex 198
+	0.833333f, 0.166667f, 0.000000f, // vertex 199
+	0.819001f, 0.180999f, 0.000000f, // vertex 200
+	0.833333f, 0.166667f, 0.000000f, // vertex 201
+	0.819001f, 0.180999f, 0.000000f, // vertex 202
+	0.819001f, 0.180999f, 0.000000f, // vertex 203
+	0.819001f, 0.180999f, 0.000000f, // vertex 204
+	0.655738f, 0.344262f, 0.000000f, // vertex 205
+	0.819001f, 0.180999f, 0.000000f, // vertex 206
+	0.819001f, 0.180999f, 0.000000f, // vertex 207
+	0.819001f, 0.180999f, 0.000000f, // vertex 208
+	0.819001f, 0.180999f, 0.000000f, // vertex 209
+	0.819001f, 0.180999f, 0.000000f, // vertex 210
+	0.655738f, 0.344262f, 0.000000f, // vertex 211
+	0.512195f, 0.487805f, 0.000000f, // vertex 212
+	0.512195f, 0.487805f, 0.000000f, // vertex 213
+	0.512195f, 0.487805f, 0.000000f, // vertex 214
+	0.512195f, 0.487805f, 0.000000f, // vertex 215
+	0.500000f, 0.500000f, 0.000000f, // vertex 216
+	0.655738f, 0.344262f, 0.000000f, // vertex 217
+	0.500000f, 0.500000f, 0.000000f, // vertex 218
+	0.500000f, 0.500000f, 0.000000f, // vertex 219
+	0.500000f, 0.500000f, 0.000000f, // vertex 220
+	0.500000f, 0.500000f, 0.000000f, // vertex 221
+	0.500000f, 0.500000f, 0.000000f, // vertex 222
+	1.000000f, 0.000000f, 0.000000f, // vertex 223
+	0.500000f, 0.500000f, 0.000000f, // vertex 224
+	1.000000f, 0.000000f, 0.000000f, // vertex 225
+	0.500000f, 0.500000f, 0.000000f, // vertex 226
+	0.500000f, 0.500000f, 0.000000f, // vertex 227
+	1.000000f, 0.000000f, 0.000000f, // vertex 228
+	1.000000f, 0.000000f, 0.000000f, // vertex 229
+	1.000000f, 0.000000f, 0.000000f, // vertex 230
+	0.833941f, 0.166059f, 0.000000f, // vertex 231
+	1.000000f, 0.000000f, 0.000000f, // vertex 232
+	1.000000f, 0.000000f, 0.000000f, // vertex 233
+	1.000000f, 0.000000f, 0.000000f, // vertex 234
+	1.000000f, 0.000000f, 0.000000f, // vertex 235
+	1.000000f, 0.000000f, 0.000000f, // vertex 236
+	1.000000f, 0.000000f, 0.000000f, // vertex 237
+	1.000000f, 0.000000f, 0.000000f, // vertex 238
+	0.952381f, 0.047619f, 0.000000f, // vertex 239
+	0.909091f, 0.090909f, 0.000000f, // vertex 240
+	1.000000f, 0.000000f, 0.000000f, // vertex 241
+	1.000000f, 0.000000f, 0.000000f, // vertex 242
+	1.000000f, 0.000000f, 0.000000f, // vertex 243
+	1.000000f, 0.000000f, 0.000000f, // vertex 244
+	1.000000f, 0.000000f, 0.000000f, // vertex 245
+	1.000000f, 0.000000f, 0.000000f, // vertex 246
+	1.000000f, 0.000000f, 0.000000f, // vertex 247
+	1.000000f, 0.000000f, 0.000000f, // vertex 248
+	1.000000f, 0.000000f, 0.000000f, // vertex 249
+	1.000000f, 0.000000f, 0.000000f, // vertex 250
+	1.000000f, 0.000000f, 0.000000f, // vertex 251
+	1.000000f, 0.000000f, 0.000000f, // vertex 252
+	0.833333f, 0.166667f, 0.000000f, // vertex 253
+	1.000000f, 0.000000f, 0.000000f, // vertex 254
+	0.666667f, 0.333333f, 0.000000f, // vertex 255
+	1.000000f, 0.000000f, 0.000000f, // vertex 256
+	0.666667f, 0.333333f, 0.000000f, // vertex 257
+	1.000000f, 0.000000f, 0.000000f, // vertex 258
+	0.500000f, 0.500000f, 0.000000f, // vertex 259
+	1.000000f, 0.000000f, 0.000000f, // vertex 260
+	0.500000f, 0.500000f, 0.000000f, // vertex 261
+	0.952381f, 0.047619f, 0.000000f, // vertex 262
+	0.909091f, 0.090909f, 0.000000f, // vertex 263
+	0.909091f, 0.090909f, 0.000000f, // vertex 264
+	1.000000f, 0.000000f, 0.000000f, // vertex 265
+	1.000000f, 0.000000f, 0.000000f, // vertex 266
+	1.000000f, 0.000000f, 0.000000f, // vertex 267
+	1.000000f, 0.000000f, 0.000000f, // vertex 268
+	1.000000f, 0.000000f, 0.000000f, // vertex 269
+	1.000000f, 0.000000f, 0.000000f, // vertex 270
+	1.000000f, 0.000000f, 0.000000f, // vertex 271
+	1.000000f, 0.000000f, 0.000000f, // vertex 272
+	1.000000f, 0.000000f, 0.000000f, // vertex 273
+	1.000000f, 0.000000f, 0.000000f, // vertex 274
+	1.000000f, 0.000000f, 0.000000f, // vertex 275
+	1.000000f, 0.000000f, 0.000000f, // vertex 276
+	1.000000f, 0.000000f, 0.000000f, // vertex 277
+	0.833333f, 0.166667f, 0.000000f, // vertex 278
+	1.000000f, 0.000000f, 0.000000f, // vertex 279
+	0.833333f, 0.166667f, 0.000000f, // vertex 280
+	1.000000f, 0.000000f, 0.000000f, // vertex 281
+	1.000000f, 0.000000f, 0.000000f, // vertex 282
+	1.000000f, 0.000000f, 0.000000f, // vertex 283
+	1.000000f, 0.000000f, 0.000000f, // vertex 284
+	1.000000f, 0.000000f, 0.000000f, // vertex 285
+	1.000000f, 0.000000f, 0.000000f, // vertex 286
+	1.000000f, 0.000000f, 0.000000f, // vertex 287
+	1.000000f, 0.000000f, 0.000000f, // vertex 288
+	1.000000f, 0.000000f, 0.000000f, // vertex 289
+	0.555556f, 0.444444f, 0.000000f, // vertex 290
+	0.555556f, 0.444444f, 0.000000f, // vertex 291
+	0.833333f, 0.166667f, 0.000000f, // vertex 292
+	0.833333f, 0.166667f, 0.000000f, // vertex 293
+	1.000000f, 0.000000f, 0.000000f, // vertex 294
+	1.000000f, 0.000000f, 0.000000f, // vertex 295
+	1.000000f, 0.000000f, 0.000000f, // vertex 296
+	0.833333f, 0.166667f, 0.000000f, // vertex 297
+	1.000000f, 0.000000f, 0.000000f, // vertex 298
+	0.833333f, 0.166667f, 0.000000f, // vertex 299
+	1.000000f, 0.000000f, 0.000000f, // vertex 300
+	0.833333f, 0.166667f, 0.000000f, // vertex 301
+	1.000000f, 0.000000f, 0.000000f, // vertex 302
+	0.833333f, 0.166667f, 0.000000f, // vertex 303
+	0.666667f, 0.333333f, 0.000000f, // vertex 304
+	1.000000f, 0.000000f, 0.000000f, // vertex 305
+	0.833333f, 0.166667f, 0.000000f, // vertex 306
+	1.000000f, 0.000000f, 0.000000f, // vertex 307
+	0.833333f, 0.166667f, 0.000000f, // vertex 308
+	0.833333f, 0.166667f, 0.000000f, // vertex 309
+	0.833333f, 0.166667f, 0.000000f, // vertex 310
+	0.714286f, 0.285714f, 0.000000f, // vertex 311
+	0.833333f, 0.166667f, 0.000000f, // vertex 312
+	0.714286f, 0.285714f, 0.000000f, // vertex 313
+	0.833333f, 0.166667f, 0.000000f, // vertex 314
+	0.714286f, 0.285714f, 0.000000f, // vertex 315
+	0.833333f, 0.166667f, 0.000000f, // vertex 316
+	0.800000f, 0.200000f, 0.000000f, // vertex 317
+	0.555556f, 0.444444f, 0.000000f, // vertex 318
+	0.666667f, 0.333333f, 0.000000f, // vertex 319
+	0.666667f, 0.333333f, 0.000000f, // vertex 320
+	0.500000f, 0.500000f, 0.000000f, // vertex 321
+	0.500000f, 0.500000f, 0.000000f, // vertex 322
+	0.555556f, 0.444444f, 0.000000f, // vertex 323
+	0.555556f, 0.444444f, 0.000000f, // vertex 324
+	0.666667f, 0.333333f, 0.000000f, // vertex 325
+	0.833333f, 0.166667f, 0.000000f, // vertex 326
+	0.666667f, 0.333333f, 0.000000f, // vertex 327
+	0.833333f, 0.166667f, 0.000000f, // vertex 328
+	0.833333f, 0.166667f, 0.000000f, // vertex 329
+	0.500000f, 0.500000f, 0.000000f, // vertex 330
+	0.500000f, 0.500000f, 0.000000f, // vertex 331
+	0.555556f, 0.444444f, 0.000000f, // vertex 332
+	0.500000f, 0.500000f, 0.000000f, // vertex 333
+	0.500000f, 0.500000f, 0.000000f, // vertex 334
+	1.000000f, 0.000000f, 0.000000f, // vertex 335
+	1.000000f, 0.000000f, 0.000000f, // vertex 336
+	1.000000f, 0.000000f, 0.000000f, // vertex 337
+	1.000000f, 0.000000f, 0.000000f, // vertex 338
+	1.000000f, 0.000000f, 0.000000f, // vertex 339
+	1.000000f, 0.000000f, 0.000000f, // vertex 340
+	1.000000f, 0.000000f, 0.000000f, // vertex 341
+	1.000000f, 0.000000f, 0.000000f, // vertex 342
+	1.000000f, 0.000000f, 0.000000f, // vertex 343
+	1.000000f, 0.000000f, 0.000000f, // vertex 344
+	1.000000f, 0.000000f, 0.000000f, // vertex 345
+	0.926651f, 0.073349f, 0.000000f, // vertex 346
+	0.833941f, 0.166059f, 0.000000f, // vertex 347
+	1.000000f, 0.000000f, 0.000000f, // vertex 348
+	1.000000f, 0.000000f, 0.000000f, // vertex 349
+	1.000000f, 0.000000f, 0.000000f, // vertex 350
+	1.000000f, 0.000000f, 0.000000f, // vertex 351
+	1.000000f, 0.000000f, 0.000000f, // vertex 352
+	1.000000f, 0.000000f, 0.000000f, // vertex 353
+	1.000000f, 0.000000f, 0.000000f, // vertex 354
+	1.000000f, 0.000000f, 0.000000f, // vertex 355
+	1.000000f, 0.000000f, 0.000000f, // vertex 356
+	1.000000f, 0.000000f, 0.000000f, // vertex 357
+	1.000000f, 0.000000f, 0.000000f, // vertex 358
+	1.000000f, 0.000000f, 0.000000f, // vertex 359
+	1.000000f, 0.000000f, 0.000000f, // vertex 360
+	1.000000f, 0.000000f, 0.000000f, // vertex 361
+	0.800000f, 0.200000f, 0.000000f, // vertex 362
+	0.800000f, 0.200000f, 0.000000f, // vertex 363
+	0.500000f, 0.500000f, 0.000000f, // vertex 364
+	0.655738f, 0.344262f, 0.000000f, // vertex 365
+	0.819001f, 0.180999f, 0.000000f, // vertex 366
+	0.833333f, 0.166667f, 0.000000f, // vertex 367
+	0.833333f, 0.166667f, 0.000000f, // vertex 368
+	0.500000f, 0.500000f, 0.000000f, // vertex 369
+	0.512195f, 0.487805f, 0.000000f, // vertex 370
+	0.500000f, 0.500000f, 0.000000f, // vertex 371
+	0.500000f, 0.500000f, 0.000000f, // vertex 372
+	0.500000f, 0.500000f, 0.000000f, // vertex 373
+	0.655738f, 0.344262f, 0.000000f, // vertex 374
+	0.500000f, 0.500000f, 0.000000f, // vertex 375
+	0.500000f, 0.500000f, 0.000000f, // vertex 376
+	0.500000f, 0.500000f, 0.000000f, // vertex 377
+	0.500000f, 0.500000f, 0.000000f, // vertex 378
+	0.500000f, 0.500000f, 0.000000f, // vertex 379
+	0.500000f, 0.500000f, 0.000000f, // vertex 380
+	0.500000f, 0.500000f, 0.000000f, // vertex 381
+	1.000000f, 0.000000f, 0.000000f, // vertex 382
+	1.000000f, 0.000000f, 0.000000f, // vertex 383
+	1.000000f, 0.000000f, 0.000000f, // vertex 384
+	1.000000f, 0.000000f, 0.000000f, // vertex 385
+	1.000000f, 0.000000f, 0.000000f, // vertex 386
+	1.000000f, 0.000000f, 0.000000f, // vertex 387
+	1.000000f, 0.000000f, 0.000000f, // vertex 388
+	1.000000f, 0.000000f, 0.000000f, // vertex 389
+	1.000000f, 0.000000f, 0.000000f, // vertex 390
+	0.500000f, 0.500000f, 0.000000f, // vertex 391
+	1.000000f, 0.000000f, 0.000000f, // vertex 392
+	0.666667f, 0.333333f, 0.000000f, // vertex 393
+	0.500000f, 0.500000f, 0.000000f, // vertex 394
+	1.000000f, 0.000000f, 0.000000f, // vertex 395
+	0.500000f, 0.500000f, 0.000000f, // vertex 396
+	1.000000f, 0.000000f, 0.000000f, // vertex 397
+	1.000000f, 0.000000f, 0.000000f, // vertex 398
+	1.000000f, 0.000000f, 0.000000f, // vertex 399
+	1.000000f, 0.000000f, 0.000000f, // vertex 400
+	1.000000f, 0.000000f, 0.000000f, // vertex 401
+	1.000000f, 0.000000f, 0.000000f, // vertex 402
+	1.000000f, 0.000000f, 0.000000f, // vertex 403
+	1.000000f, 0.000000f, 0.000000f, // vertex 404
+	1.000000f, 0.000000f, 0.000000f, // vertex 405
+	0.909091f, 0.090909f, 0.000000f, // vertex 406
+	0.952381f, 0.047619f, 0.000000f, // vertex 407
+	0.952381f, 0.047619f, 0.000000f, // vertex 408
+	0.500000f, 0.500000f, 0.000000f, // vertex 409
+	1.000000f, 0.000000f, 0.000000f, // vertex 410
+	0.500000f, 0.500000f, 0.000000f, // vertex 411
+	1.000000f, 0.000000f, 0.000000f, // vertex 412
+	0.666667f, 0.333333f, 0.000000f, // vertex 413
+	1.000000f, 0.000000f, 0.000000f, // vertex 414
+	0.666667f, 0.333333f, 0.000000f, // vertex 415
+	1.000000f, 0.000000f, 0.000000f, // vertex 416
+	0.833333f, 0.166667f, 0.000000f, // vertex 417
+	1.000000f, 0.000000f, 0.000000f, // vertex 418
+	1.000000f, 0.000000f, 0.000000f, // vertex 419
+	1.000000f, 0.000000f, 0.000000f, // vertex 420
+	1.000000f, 0.000000f, 0.000000f, // vertex 421
+	0.833333f, 0.166667f, 0.000000f, // vertex 422
+	1.000000f, 0.000000f, 0.000000f, // vertex 423
+	1.000000f, 0.000000f, 0.000000f, // vertex 424
+	1.000000f, 0.000000f, 0.000000f, // vertex 425
+	1.000000f, 0.000000f, 0.000000f, // vertex 426
+	1.000000f, 0.000000f, 0.000000f, // vertex 427
+	1.000000f, 0.000000f, 0.000000f, // vertex 428
+	1.000000f, 0.000000f, 0.000000f, // vertex 429
+	1.000000f, 0.000000f, 0.000000f, // vertex 430
+	1.000000f, 0.000000f, 0.000000f, // vertex 431
+	1.000000f, 0.000000f, 0.000000f, // vertex 432
+	1.000000f, 0.000000f, 0.000000f, // vertex 433
+	1.000000f, 0.000000f, 0.000000f, // vertex 434
+	1.000000f, 0.000000f, 0.000000f, // vertex 435
+	0.909091f, 0.090909f, 0.000000f, // vertex 436
+	0.909091f, 0.090909f, 0.000000f, // vertex 437
+	1.000000f, 0.000000f, 0.000000f, // vertex 438
+	1.000000f, 0.000000f, 0.000000f, // vertex 439
+	1.000000f, 0.000000f, 0.000000f, // vertex 440
+	0.833333f, 0.166667f, 0.000000f, // vertex 441
+	1.000000f, 0.000000f, 0.000000f, // vertex 442
+	1.000000f, 0.000000f, 0.000000f, // vertex 443
+	1.000000f, 0.000000f, 0.000000f, // vertex 444
+	1.000000f, 0.000000f, 0.000000f, // vertex 445
+	1.000000f, 0.000000f, 0.000000f, // vertex 446
+	1.000000f, 0.000000f, 0.000000f, // vertex 447
+	1.000000f, 0.000000f, 0.000000f, // vertex 448
+	1.000000f, 0.000000f, 0.000000f, // vertex 449
+	1.000000f, 0.000000f, 0.000000f, // vertex 450
+	0.833333f, 0.166667f, 0.000000f, // vertex 451
+	0.833333f, 0.166667f, 0.000000f, // vertex 452
+	0.555556f, 0.444444f, 0.000000f, // vertex 453
+	0.555556f, 0.444444f, 0.000000f, // vertex 454
+	1.000000f, 0.000000f, 0.000000f, // vertex 455
+	0.833333f, 0.166667f, 0.000000f, // vertex 456
+	1.000000f, 0.000000f, 0.000000f, // vertex 457
+	0.833333f, 0.166667f, 0.000000f, // vertex 458
+	1.000000f, 0.000000f, 0.000000f, // vertex 459
+	0.833333f, 0.166667f, 0.000000f, // vertex 460
+	1.000000f, 0.000000f, 0.000000f, // vertex 461
+	1.000000f, 0.000000f, 0.000000f, // vertex 462
+	0.666667f, 0.333333f, 0.000000f, // vertex 463
+	0.833333f, 0.166667f, 0.000000f, // vertex 464
+	0.800000f, 0.200000f, 0.000000f, // vertex 465
+	0.833333f, 0.166667f, 0.000000f, // vertex 466
+	0.714286f, 0.285714f, 0.000000f, // vertex 467
+	0.833333f, 0.166667f, 0.000000f, // vertex 468
+	0.714286f, 0.285714f, 0.000000f, // vertex 469
+	0.833333f, 0.166667f, 0.000000f, // vertex 470
+	0.714286f, 0.285714f, 0.000000f, // vertex 471
+	0.833333f, 0.166667f, 0.000000f, // vertex 472
+	0.833333f, 0.166667f, 0.000000f, // vertex 473
+	0.666667f, 0.333333f, 0.000000f, // vertex 474
+	0.833333f, 0.166667f, 0.000000f, // vertex 475
+	0.666667f, 0.333333f, 0.000000f, // vertex 476
+	0.555556f, 0.444444f, 0.000000f, // vertex 477
+	0.555556f, 0.444444f, 0.000000f, // vertex 478
+	0.500000f, 0.500000f, 0.000000f, // vertex 479
+	0.500000f, 0.500000f, 0.000000f, // vertex 480
+	0.500000f, 0.500000f, 0.000000f, // vertex 481
+	0.500000f, 0.500000f, 0.000000f, // vertex 482
+	0.555556f, 0.444444f, 0.000000f, // vertex 483
+	0.500000f, 0.500000f, 0.000000f, // vertex 484
+	0.500000f, 0.500000f, 0.000000f, // vertex 485
+	1.000000f, 0.000000f, 0.000000f, // vertex 486
+	1.000000f, 0.000000f, 0.000000f, // vertex 487
+	1.000000f, 0.000000f, 0.000000f, // vertex 488
+	1.000000f, 0.000000f, 0.000000f, // vertex 489
+	1.000000f, 0.000000f, 0.000000f, // vertex 490
+	1.000000f, 0.000000f, 0.000000f, // vertex 491
+	1.000000f, 0.000000f, 0.000000f, // vertex 492
+	1.000000f, 0.000000f, 0.000000f, // vertex 493
+	1.000000f, 0.000000f, 0.000000f, // vertex 494
+	1.000000f, 0.000000f, 0.000000f, // vertex 495
+	1.000000f, 0.000000f, 0.000000f, // vertex 496
+	1.000000f, 0.000000f, 0.000000f, // vertex 497
+	1.000000f, 0.000000f, 0.000000f, // vertex 498
+	1.000000f, 0.000000f, 0.000000f, // vertex 499
+	1.000000f, 0.000000f, 0.000000f, // vertex 500
+	0.500000f, 0.500000f, 0.000000f, // vertex 501
+	1.000000f, 0.000000f, 0.000000f, // vertex 502
+	1.000000f, 0.000000f, 0.000000f, // vertex 503
+	1.000000f, 0.000000f, 0.000000f, // vertex 504
+	1.000000f, 0.000000f, 0.000000f, // vertex 505
+	1.000000f, 0.000000f, 0.000000f, // vertex 506
+	1.000000f, 0.000000f, 0.000000f, // vertex 507
+	1.000000f, 0.000000f, 0.000000f, // vertex 508
+	1.000000f, 0.000000f, 0.000000f, // vertex 509
+	1.000000f, 0.000000f, 0.000000f, // vertex 510
+	1.000000f, 0.000000f, 0.000000f, // vertex 511
+	1.000000f, 0.000000f, 0.000000f, // vertex 512
+	1.000000f, 0.000000f, 0.000000f, // vertex 513
+	1.000000f, 0.000000f, 0.000000f, // vertex 514
+	1.000000f, 0.000000f, 0.000000f, // vertex 515
+	0.555556f, 0.444444f, 0.000000f, // vertex 516
+	0.666667f, 0.333333f, 0.000000f, // vertex 517
+	0.666667f, 0.333333f, 0.000000f, // vertex 518
+	1.000000f, 0.000000f, 0.000000f, // vertex 519
+	1.000000f, 0.000000f, 0.000000f, // vertex 520
+	1.000000f, 0.000000f, 0.000000f, // vertex 521
+	0.833333f, 0.166667f, 0.000000f, // vertex 522
+	0.833333f, 0.166667f, 0.000000f, // vertex 523
+	0.833333f, 0.166667f, 0.000000f, // vertex 524
+	0.833333f, 0.166667f, 0.000000f, // vertex 525
+	0.833333f, 0.166667f, 0.000000f, // vertex 526
+	0.833333f, 0.166667f, 0.000000f, // vertex 527
+	0.833333f, 0.166667f, 0.000000f, // vertex 528
+	0.769231f, 0.230769f, 0.000000f, // vertex 529
+	0.769231f, 0.230769f, 0.000000f, // vertex 530
+	0.769231f, 0.230769f, 0.000000f, // vertex 531
+	0.500000f, 0.500000f, 0.000000f, // vertex 532
+	0.769231f, 0.230769f, 0.000000f, // vertex 533
+	0.500000f, 0.500000f, 0.000000f, // vertex 534
+	0.833333f, 0.166667f, 0.000000f, // vertex 535
+	0.500000f, 0.500000f, 0.000000f, // vertex 536
+	0.833333f, 0.166667f, 0.000000f, // vertex 537
+	0.655738f, 0.344262f, 0.000000f, // vertex 538
+	0.833333f, 0.166667f, 0.000000f, // vertex 539
+	0.900901f, 0.099099f, 0.000000f, // vertex 540
+	0.900901f, 0.099099f, 0.000000f, // vertex 541
+	0.833333f, 0.166667f, 0.000000f, // vertex 542
+	0.900901f, 0.099099f, 0.000000f, // vertex 543
+	0.833333f, 0.166667f, 0.000000f, // vertex 544
+	0.655738f, 0.344262f, 0.000000f, // vertex 545
+	0.833333f, 0.166667f, 0.000000f, // vertex 546
+	0.900901f, 0.099099f, 0.000000f, // vertex 547
+	0.833333f, 0.166667f, 0.000000f, // vertex 548
+	0.833333f, 0.166667f, 0.000000f, // vertex 549
+	0.833333f, 0.166667f, 0.000000f, // vertex 550
+	0.655738f, 0.344262f, 0.000000f, // vertex 551
+	0.500000f, 0.500000f, 0.000000f, // vertex 552
+	0.512195f, 0.487805f, 0.000000f, // vertex 553
+	0.500000f, 0.500000f, 0.000000f, // vertex 554
+	0.512195f, 0.487805f, 0.000000f, // vertex 555
+	0.512195f, 0.487805f, 0.000000f, // vertex 556
+	0.833333f, 0.166667f, 0.000000f, // vertex 557
+	0.833333f, 0.166667f, 0.000000f, // vertex 558
+	0.900901f, 0.099099f, 0.000000f, // vertex 559
+	0.655738f, 0.344262f, 0.000000f, // vertex 560
+	0.655738f, 0.344262f, 0.000000f, // vertex 561
+	0.512195f, 0.487805f, 0.000000f, // vertex 562
+	0.500000f, 0.500000f, 0.000000f, // vertex 563
+	0.500000f, 0.500000f, 0.000000f, // vertex 564
+	0.500000f, 0.500000f, 0.000000f, // vertex 565
+	0.500000f, 0.500000f, 0.000000f, // vertex 566
+	0.655738f, 0.344262f, 0.000000f, // vertex 567
+	0.500000f, 0.500000f, 0.000000f, // vertex 568
+	0.512195f, 0.487805f, 0.000000f, // vertex 569
+	0.512195f, 0.487805f, 0.000000f, // vertex 570
+	1.000000f, 0.000000f, 0.000000f, // vertex 571
+	1.000000f, 0.000000f, 0.000000f, // vertex 572
+	1.000000f, 0.000000f, 0.000000f, // vertex 573
+	1.000000f, 0.000000f, 0.000000f, // vertex 574
+	1.000000f, 0.000000f, 0.000000f, // vertex 575
+	1.000000f, 0.000000f, 0.000000f, // vertex 576
+	1.000000f, 0.000000f, 0.000000f, // vertex 577
+	1.000000f, 0.000000f, 0.000000f, // vertex 578
+	1.000000f, 0.000000f, 0.000000f, // vertex 579
+	1.000000f, 0.000000f, 0.000000f, // vertex 580
+	1.000000f, 0.000000f, 0.000000f, // vertex 581
+	1.000000f, 0.000000f, 0.000000f, // vertex 582
+	1.000000f, 0.000000f, 0.000000f, // vertex 583
+	1.000000f, 0.000000f, 0.000000f, // vertex 584
+	1.000000f, 0.000000f, 0.000000f, // vertex 585
+	1.000000f, 0.000000f, 0.000000f, // vertex 586
+	1.000000f, 0.000000f, 0.000000f, // vertex 587
+	1.000000f, 0.000000f, 0.000000f, // vertex 588
+	1.000000f, 0.000000f, 0.000000f, // vertex 589
+	1.000000f, 0.000000f, 0.000000f, // vertex 590
+	1.000000f, 0.000000f, 0.000000f, // vertex 591
+	0.500000f, 0.500000f, 0.000000f, // vertex 592
+	1.000000f, 0.000000f, 0.000000f, // vertex 593
+	0.500000f, 0.500000f, 0.000000f, // vertex 594
+	0.500000f, 0.500000f, 0.000000f, // vertex 595
+	0.500000f, 0.500000f, 0.000000f, // vertex 596
+	1.000000f, 0.000000f, 0.000000f, // vertex 597
+	0.500000f, 0.500000f, 0.000000f, // vertex 598
+	0.666667f, 0.333333f, 0.000000f, // vertex 599
+	0.666667f, 0.333333f, 0.000000f, // vertex 600
+	0.500000f, 0.500000f, 0.000000f, // vertex 601
+	1.000000f, 0.000000f, 0.000000f, // vertex 602
+	0.500000f, 0.500000f, 0.000000f, // vertex 603
+	0.500000f, 0.500000f, 0.000000f, // vertex 604
+	0.500000f, 0.500000f, 0.000000f, // vertex 605
+	0.500000f, 0.500000f, 0.000000f, // vertex 606
+	0.500000f, 0.500000f, 0.000000f, // vertex 607
+	0.500000f, 0.500000f, 0.000000f, // vertex 608
+	0.833333f, 0.166667f, 0.000000f, // vertex 609
+	0.833333f, 0.166667f, 0.000000f, // vertex 610
+	0.714286f, 0.285714f, 0.000000f, // vertex 611
+	0.833333f, 0.166667f, 0.000000f, // vertex 612
+	0.714286f, 0.285714f, 0.000000f, // vertex 613
+	0.714286f, 0.285714f, 0.000000f, // vertex 614
+	0.500000f, 0.500000f, 0.000000f, // vertex 615
+	0.500000f, 0.500000f, 0.000000f, // vertex 616
+	0.500000f, 0.500000f, 0.000000f, // vertex 617
+	0.500000f, 0.500000f, 0.000000f, // vertex 618
+	0.500000f, 0.500000f, 0.000000f, // vertex 619
+	0.500000f, 0.500000f, 0.000000f, // vertex 620
+	0.500000f, 0.500000f, 0.000000f, // vertex 621
+	0.500000f, 0.500000f, 0.000000f, // vertex 622
+	0.500000f, 0.500000f, 0.000000f, // vertex 623
+	0.500000f, 0.500000f, 0.000000f, // vertex 624
+	0.500000f, 0.500000f, 0.000000f, // vertex 625
+	0.833333f, 0.166667f, 0.000000f, // vertex 626
+	0.714286f, 0.285714f, 0.000000f, // vertex 627
+	0.833333f, 0.166667f, 0.000000f, // vertex 628
+	0.833333f, 0.166667f, 0.000000f, // vertex 629
+	0.833333f, 0.166667f, 0.000000f, // vertex 630
+	1.000000f, 0.000000f, 0.000000f, // vertex 631
+	1.000000f, 0.000000f, 0.000000f, // vertex 632
+	1.000000f, 0.000000f, 0.000000f, // vertex 633
+	1.000000f, 0.000000f, 0.000000f, // vertex 634
+	1.000000f, 0.000000f, 0.000000f, // vertex 635
+	1.000000f, 0.000000f, 0.000000f, // vertex 636
+	0.833333f, 0.166667f, 0.000000f, // vertex 637
+	0.833333f, 0.166667f, 0.000000f, // vertex 638
+	1.000000f, 0.000000f, 0.000000f, // vertex 639
+	0.833333f, 0.166667f, 0.000000f, // vertex 640
+	1.000000f, 0.000000f, 0.000000f, // vertex 641
+	0.833333f, 0.166667f, 0.000000f, // vertex 642
+	1.000000f, 0.000000f, 0.000000f, // vertex 643
+	0.833333f, 0.166667f, 0.000000f, // vertex 644
+	1.000000f, 0.000000f, 0.000000f, // vertex 645
+	1.000000f, 0.000000f, 0.000000f, // vertex 646
+	1.000000f, 0.000000f, 0.000000f, // vertex 647
+	0.833333f, 0.166667f, 0.000000f, // vertex 648
+	0.833333f, 0.166667f, 0.000000f, // vertex 649
+	0.555556f, 0.444444f, 0.000000f, // vertex 650
+	0.833333f, 0.166667f, 0.000000f, // vertex 651
+	0.555556f, 0.444444f, 0.000000f, // vertex 652
+	0.500000f, 0.500000f, 0.000000f, // vertex 653
+	0.555556f, 0.444444f, 0.000000f, // vertex 654
+	0.666667f, 0.333333f, 0.000000f, // vertex 655
+	0.833333f, 0.166667f, 0.000000f, // vertex 656
+	0.833333f, 0.166667f, 0.000000f, // vertex 657
+	0.666667f, 0.333333f, 0.000000f, // vertex 658
+	1.000000f, 0.000000f, 0.000000f, // vertex 659
+	1.000000f, 0.000000f, 0.000000f, // vertex 660
+	1.000000f, 0.000000f, 0.000000f, // vertex 661
+	0.500000f, 0.500000f, 0.000000f, // vertex 662
+	0.500000f, 0.500000f, 0.000000f, // vertex 663
+	0.500000f, 0.500000f, 0.000000f, // vertex 664
+	0.500000f, 0.500000f, 0.000000f, // vertex 665
+	0.500000f, 0.500000f, 0.000000f, // vertex 666
+	0.500000f, 0.500000f, 0.000000f, // vertex 667
+	0.500000f, 0.500000f, 0.000000f, // vertex 668
+	0.500000f, 0.500000f, 0.000000f, // vertex 669
+	0.500000f, 0.500000f, 0.000000f, // vertex 670
+	0.500000f, 0.500000f, 0.000000f, // vertex 671
+	0.500000f, 0.500000f, 0.000000f, // vertex 672
+	0.500000f, 0.500000f, 0.000000f, // vertex 673
+	0.500000f, 0.500000f, 0.000000f, // vertex 674
+	0.500000f, 0.500000f, 0.000000f, // vertex 675
+	0.714286f, 0.285714f, 0.000000f, // vertex 676
+	0.714286f, 0.285714f, 0.000000f, // vertex 677
+	0.500000f, 0.500000f, 0.000000f, // vertex 678
+	0.500000f, 0.500000f, 0.000000f, // vertex 679
+	0.500000f, 0.500000f, 0.000000f, // vertex 680
+	0.500000f, 0.500000f, 0.000000f, // vertex 681
+	0.500000f, 0.500000f, 0.000000f, // vertex 682
+	1.000000f, 0.000000f, 0.000000f, // vertex 683
+	1.000000f, 0.000000f, 0.000000f, // vertex 684
+	1.000000f, 0.000000f, 0.000000f, // vertex 685
+	1.000000f, 0.000000f, 0.000000f, // vertex 686
+	1.000000f, 0.000000f, 0.000000f, // vertex 687
+	1.000000f, 0.000000f, 0.000000f, // vertex 688
+	0.833333f, 0.166667f, 0.000000f, // vertex 689
+	1.000000f, 0.000000f, 0.000000f, // vertex 690
+	0.833333f, 0.166667f, 0.000000f, // vertex 691
+	1.000000f, 0.000000f, 0.000000f, // vertex 692
+	0.833333f, 0.166667f, 0.000000f, // vertex 693
+	1.000000f, 0.000000f, 0.000000f, // vertex 694
+	0.500000f, 0.500000f, 0.000000f, // vertex 695
+	0.714286f, 0.285714f, 0.000000f, // vertex 696
+	0.833333f, 0.166667f, 0.000000f, // vertex 697
+	1.000000f, 0.000000f, 0.000000f, // vertex 698
+	1.000000f, 0.000000f, 0.000000f, // vertex 699
+	1.000000f, 0.000000f, 0.000000f, // vertex 700
+	0.555556f, 0.444444f, 0.000000f, // vertex 701
+	0.833333f, 0.166667f, 0.000000f, // vertex 702
+	0.555556f, 0.444444f, 0.000000f, // vertex 703
+	0.833333f, 0.166667f, 0.000000f, // vertex 704
+	0.833333f, 0.166667f, 0.000000f, // vertex 705
+	0.555556f, 0.444444f, 0.000000f, // vertex 706
+	0.500000f, 0.500000f, 0.000000f, // vertex 707
+	0.666667f, 0.333333f, 0.000000f, // vertex 708
+	0.666667f, 0.333333f, 0.000000f, // vertex 709
+	0.833333f, 0.166667f, 0.000000f, // vertex 710
+	0.833333f, 0.166667f, 0.000000f, // vertex 711
+	1.000000f, 0.000000f, 0.000000f, // vertex 712
+	1.000000f, 0.000000f, 0.000000f, // vertex 713
+	1.000000f, 0.000000f, 0.000000f, // vertex 714
+	0.655738f, 0.344262f, 0.000000f, // vertex 715
+	0.833333f, 0.166667f, 0.000000f, // vertex 716
+	0.900901f, 0.099099f, 0.000000f, // vertex 717
+	0.900901f, 0.099099f, 0.000000f, // vertex 718
+	0.833333f, 0.166667f, 0.000000f, // vertex 719
+	0.500000f, 0.500000f, 0.000000f, // vertex 720
+	0.833333f, 0.166667f, 0.000000f, // vertex 721
+	0.500000f, 0.500000f, 0.000000f, // vertex 722
+	0.769231f, 0.230769f, 0.000000f, // vertex 723
+	0.500000f, 0.500000f, 0.000000f, // vertex 724
+	0.833333f, 0.166667f, 0.000000f, // vertex 725
+	0.833333f, 0.166667f, 0.000000f, // vertex 726
+	0.833333f, 0.166667f, 0.000000f, // vertex 727
+	0.833333f, 0.166667f, 0.000000f, // vertex 728
+	0.833333f, 0.166667f, 0.000000f, // vertex 729
+	0.833333f, 0.166667f, 0.000000f, // vertex 730
+	0.833333f, 0.166667f, 0.000000f, // vertex 731
+	0.769231f, 0.230769f, 0.000000f, // vertex 732
+	0.769231f, 0.230769f, 0.000000f, // vertex 733
+	0.769231f, 0.230769f, 0.000000f, // vertex 734
+	0.769231f, 0.230769f, 0.000000f, // vertex 735
+	0.769231f, 0.230769f, 0.000000f, // vertex 736
+	0.833333f, 0.166667f, 0.000000f, // vertex 737
+	0.500000f, 0.500000f, 0.000000f, // vertex 738
+	0.500000f, 0.500000f, 0.000000f, // vertex 739
+	0.500000f, 0.500000f, 0.000000f, // vertex 740
+	0.500000f, 0.500000f, 0.000000f, // vertex 741
+	0.769231f, 0.230769f, 0.000000f, // vertex 742
+	0.833333f, 0.166667f, 0.000000f, // vertex 743
+	0.769231f, 0.230769f, 0.000000f, // vertex 744
+	0.833333f, 0.166667f, 0.000000f, // vertex 745
+	0.833333f, 0.166667f, 0.000000f, // vertex 746
+	0.833333f, 0.166667f, 0.000000f, // vertex 747
+	0.833333f, 0.166667f, 0.000000f, // vertex 748
+	0.833333f, 0.166667f, 0.000000f, // vertex 749
+	1.000000f, 0.000000f, 0.000000f, // vertex 750
+	1.000000f, 0.000000f, 0.000000f, // vertex 751
+	0.909091f, 0.090909f, 0.000000f, // vertex 752
+	1.000000f, 0.000000f, 0.000000f, // vertex 753
+	1.000000f, 0.000000f, 0.000000f, // vertex 754
+	0.833333f, 0.166667f, 0.000000f, // vertex 755
+	0.833333f, 0.166667f, 0.000000f, // vertex 756
+	0.666667f, 0.333333f, 0.000000f, // vertex 757
+	0.666667f, 0.333333f, 0.000000f, // vertex 758
+	0.666667f, 0.333333f, 0.000000f, // vertex 759
+	0.666667f, 0.333333f, 0.000000f, // vertex 760
+	0.833333f, 0.166667f, 0.000000f, // vertex 761
+	0.833333f, 0.166667f, 0.000000f, // vertex 762
+	1.000000f, 0.000000f, 0.000000f, // vertex 763
+	1.000000f, 0.000000f, 0.000000f, // vertex 764
+	0.909091f, 0.090909f, 0.000000f, // vertex 765
+	1.000000f, 0.000000f, 0.000000f, // vertex 766
+	1.000000f, 0.000000f, 0.000000f, // vertex 767
+	1.000000f, 0.000000f, 0.000000f, // vertex 768
+	1.000000f, 0.000000f, 0.000000f, // vertex 769
+	0.500000f, 0.500000f, 0.000000f, // vertex 770
+	0.833333f, 0.166667f, 0.000000f, // vertex 771
+	0.500000f, 0.500000f, 0.000000f, // vertex 772
+	0.833333f, 0.166667f, 0.000000f, // vertex 773
+	0.714286f, 0.285714f, 0.000000f, // vertex 774
+	0.900901f, 0.099099f, 0.000000f, // vertex 775
+	0.655738f, 0.344262f, 0.000000f, // vertex 776
+	0.772187f, 0.154437f, 0.073375f, // vertex 777
+	0.819001f, 0.180999f, 0.000000f, // vertex 778
+	0.772187f, 0.154437f, 0.073375f, // vertex 779
+	0.819001f, 0.180999f, 0.000000f, // vertex 780
+	0.772187f, 0.154437f, 0.073375f, // vertex 781
+	0.772187f, 0.154437f, 0.073375f, // vertex 782
+	0.800000f, 0.200000f, 0.000000f, // vertex 783
+	0.800000f, 0.200000f, 0.000000f, // vertex 784
+	0.772187f, 0.154437f, 0.073375f, // vertex 785
+	0.800000f, 0.200000f, 0.000000f, // vertex 786
+	0.772187f, 0.154437f, 0.073375f, // vertex 787
+	0.800000f, 0.200000f, 0.000000f, // vertex 788
+	0.772187f, 0.154437f, 0.073375f, // vertex 789
+	0.500000f, 0.500000f, 0.000000f, // vertex 790
+	0.900901f, 0.099099f, 0.000000f, // vertex 791
+	0.952381f, 0.047619f, 0.000000f, // vertex 792
+	0.833333f, 0.166667f, 0.000000f, // vertex 793
+	0.909091f, 0.090909f, 0.000000f, // vertex 794
+	0.909091f, 0.090909f, 0.000000f, // vertex 795
+	1.000000f, 0.000000f, 0.000000f, // vertex 796
+	0.500000f, 0.500000f, 0.000000f, // vertex 797
+	0.500000f, 0.500000f, 0.000000f, // vertex 798
+	0.500000f, 0.500000f, 0.000000f, // vertex 799
+	0.500000f, 0.500000f, 0.000000f, // vertex 800
+	0.500000f, 0.500000f, 0.000000f, // vertex 801
+	0.500000f, 0.500000f, 0.000000f, // vertex 802
+	0.500000f, 0.500000f, 0.000000f, // vertex 803
+	0.500000f, 0.500000f, 0.000000f, // vertex 804
+	0.500000f, 0.500000f, 0.000000f, // vertex 805
+	0.500000f, 0.500000f, 0.000000f, // vertex 806
+	0.500000f, 0.500000f, 0.000000f, // vertex 807
+	0.500000f, 0.500000f, 0.000000f, // vertex 808
+	0.500000f, 0.500000f, 0.000000f, // vertex 809
+	0.655738f, 0.344262f, 0.000000f, // vertex 810
+	0.714286f, 0.285714f, 0.000000f, // vertex 811
+	0.500000f, 0.500000f, 0.000000f, // vertex 812
+	0.500000f, 0.500000f, 0.000000f, // vertex 813
+	0.500000f, 0.500000f, 0.000000f, // vertex 814
+	0.500000f, 0.500000f, 0.000000f, // vertex 815
+	0.500000f, 0.500000f, 0.000000f, // vertex 816
+	0.500000f, 0.500000f, 0.000000f, // vertex 817
+	1.000000f, 0.000000f, 0.000000f, // vertex 818
+	0.500000f, 0.500000f, 0.000000f, // vertex 819
+	0.500000f, 0.500000f, 0.000000f, // vertex 820
+	0.500000f, 0.500000f, 0.000000f, // vertex 821
+	0.500000f, 0.500000f, 0.000000f, // vertex 822
+	0.500000f, 0.500000f, 0.000000f, // vertex 823
+	0.500000f, 0.500000f, 0.000000f, // vertex 824
+	0.500000f, 0.500000f, 0.000000f, // vertex 825
+	0.500000f, 0.500000f, 0.000000f, // vertex 826
+	0.500000f, 0.500000f, 0.000000f, // vertex 827
+	0.500000f, 0.500000f, 0.000000f, // vertex 828
+	0.500000f, 0.500000f, 0.000000f, // vertex 829
+	0.819001f, 0.180999f, 0.000000f, // vertex 830
+	0.772187f, 0.154437f, 0.073375f, // vertex 831
+	0.819001f, 0.180999f, 0.000000f, // vertex 832
+	0.819001f, 0.180999f, 0.000000f, // vertex 833
+	0.655738f, 0.344262f, 0.000000f, // vertex 834
+	0.900901f, 0.099099f, 0.000000f, // vertex 835
+	0.714286f, 0.285714f, 0.000000f, // vertex 836
+	0.833333f, 0.166667f, 0.000000f, // vertex 837
+	0.500000f, 0.500000f, 0.000000f, // vertex 838
+	0.833333f, 0.166667f, 0.000000f, // vertex 839
+	0.500000f, 0.500000f, 0.000000f, // vertex 840
+	1.000000f, 0.000000f, 0.000000f, // vertex 841
+	1.000000f, 0.000000f, 0.000000f, // vertex 842
+	0.909091f, 0.090909f, 0.000000f, // vertex 843
+	0.909091f, 0.090909f, 0.000000f, // vertex 844
+	0.833333f, 0.166667f, 0.000000f, // vertex 845
+	0.952381f, 0.047619f, 0.000000f, // vertex 846
+	0.900901f, 0.099099f, 0.000000f, // vertex 847
+	0.500000f, 0.500000f, 0.000000f, // vertex 848
+	0.819001f, 0.180999f, 0.000000f, // vertex 849
+	0.800000f, 0.200000f, 0.000000f, // vertex 850
+	0.772187f, 0.154437f, 0.073375f, // vertex 851
+	0.500000f, 0.500000f, 0.000000f, // vertex 852
+	0.655738f, 0.344262f, 0.000000f, // vertex 853
+	0.500000f, 0.500000f, 0.000000f, // vertex 854
+	0.714286f, 0.285714f, 0.000000f, // vertex 855
+	0.500000f, 0.500000f, 0.000000f, // vertex 856
+	0.500000f, 0.500000f, 0.000000f, // vertex 857
+	0.500000f, 0.500000f, 0.000000f, // vertex 858
+	0.500000f, 0.500000f, 0.000000f, // vertex 859
+	0.500000f, 0.500000f, 0.000000f, // vertex 860
+	0.500000f, 0.500000f, 0.000000f, // vertex 861
+	0.500000f, 0.500000f, 0.000000f, // vertex 862
+	0.500000f, 0.500000f, 0.000000f, // vertex 863
+	0.500000f, 0.500000f, 0.000000f, // vertex 864
+	0.500000f, 0.500000f, 0.000000f, // vertex 865
+	0.500000f, 0.500000f, 0.000000f, // vertex 866
+	0.500000f, 0.500000f, 0.000000f, // vertex 867
+	0.500000f, 0.500000f, 0.000000f, // vertex 868
+	0.500000f, 0.500000f, 0.000000f, // vertex 869
+	0.500000f, 0.500000f, 0.000000f, // vertex 870
+	0.500000f, 0.500000f, 0.000000f, // vertex 871
+	0.500000f, 0.500000f, 0.000000f, // vertex 872
+	0.500000f, 0.500000f, 0.000000f, // vertex 873
+	0.500000f, 0.500000f, 0.000000f, // vertex 874
+	0.500000f, 0.500000f, 0.000000f, // vertex 875
+	0.500000f, 0.500000f, 0.000000f, // vertex 876
+	0.500000f, 0.500000f, 0.000000f, // vertex 877
+	0.500000f, 0.500000f, 0.000000f, // vertex 878
+	0.500000f, 0.500000f, 0.000000f, // vertex 879
+	0.500000f, 0.500000f, 0.000000f, // vertex 880
+	0.500000f, 0.500000f, 0.000000f, // vertex 881
+	0.500000f, 0.500000f, 0.000000f, // vertex 882
+	0.714286f, 0.285714f, 0.000000f, // vertex 883
+	0.500000f, 0.500000f, 0.000000f, // vertex 884
+	1.000000f, 0.000000f, 0.000000f, // vertex 885
+	0.909091f, 0.090909f, 0.000000f, // vertex 886
+	0.833333f, 0.166667f, 0.000000f, // vertex 887
+	0.833333f, 0.166667f, 0.000000f, // vertex 888
+	0.500000f, 0.500000f, 0.000000f, // vertex 889
+	0.500000f, 0.500000f, 0.000000f, // vertex 890
+	0.500000f, 0.500000f, 0.000000f, // vertex 891
+	0.500000f, 0.500000f, 0.000000f, // vertex 892
+	0.500000f, 0.500000f, 0.000000f, // vertex 893
+	1.000000f, 0.000000f, 0.000000f, // vertex 894
+	0.833333f, 0.166667f, 0.000000f, // vertex 895
+	0.909091f, 0.090909f, 0.000000f, // vertex 896
+	0.833333f, 0.166667f, 0.000000f, // vertex 897
+	0.666667f, 0.333333f, 0.000000f, // vertex 898
+	0.800000f, 0.200000f, 0.000000f, // vertex 899
+	0.666667f, 0.333333f, 0.000000f, // vertex 900
+	0.714286f, 0.285714f, 0.000000f, // vertex 901
+	0.666667f, 0.333333f, 0.000000f, // vertex 902
+	0.714286f, 0.285714f, 0.000000f, // vertex 903
+	0.666667f, 0.333333f, 0.000000f, // vertex 904
+	0.714286f, 0.285714f, 0.000000f, // vertex 905
+	0.714286f, 0.285714f, 0.000000f, // vertex 906
+	0.666667f, 0.333333f, 0.000000f, // vertex 907
+	0.555556f, 0.444444f, 0.000000f, // vertex 908
+	0.666667f, 0.333333f, 0.000000f, // vertex 909
+	0.555556f, 0.444444f, 0.000000f, // vertex 910
+	0.666667f, 0.333333f, 0.000000f, // vertex 911
+	0.555556f, 0.444444f, 0.000000f, // vertex 912
+	0.666667f, 0.333333f, 0.000000f, // vertex 913
+	0.666667f, 0.333333f, 0.000000f, // vertex 914
+	0.714286f, 0.285714f, 0.000000f, // vertex 915
+	0.714286f, 0.285714f, 0.000000f, // vertex 916
+	0.800000f, 0.200000f, 0.000000f, // vertex 917
+	0.714286f, 0.285714f, 0.000000f, // vertex 918
+	0.666667f, 0.333333f, 0.000000f, // vertex 919
+	0.666667f, 0.333333f, 0.000000f, // vertex 920
+	0.555556f, 0.444444f, 0.000000f, // vertex 921
+	0.555556f, 0.444444f, 0.000000f, // vertex 922
+	0.800000f, 0.200000f, 0.000000f, // vertex 923
+	0.666667f, 0.333333f, 0.000000f, // vertex 924
+	0.666667f, 0.333333f, 0.000000f, // vertex 925
+	0.555556f, 0.444444f, 0.000000f, // vertex 926
+	0.555556f, 0.444444f, 0.000000f, // vertex 927
+	0.500000f, 0.500000f, 0.000000f, // vertex 928
+	0.500000f, 0.500000f, 0.000000f, // vertex 929
+	0.500000f, 0.500000f, 0.000000f, // vertex 930
+	0.500000f, 0.500000f, 0.000000f, // vertex 931
+	0.500000f, 0.500000f, 0.000000f, // vertex 932
+	0.500000f, 0.500000f, 0.000000f, // vertex 933
+	0.500000f, 0.500000f, 0.000000f, // vertex 934
+	0.500000f, 0.500000f, 0.000000f, // vertex 935
+	0.500000f, 0.500000f, 0.000000f, // vertex 936
+	0.500000f, 0.500000f, 0.000000f, // vertex 937
+	0.500000f, 0.500000f, 0.000000f, // vertex 938
+	0.500000f, 0.500000f, 0.000000f, // vertex 939
+	0.500000f, 0.500000f, 0.000000f, // vertex 940
+	0.500000f, 0.500000f, 0.000000f, // vertex 941
+	0.500000f, 0.500000f, 0.000000f, // vertex 942
+	0.500000f, 0.500000f, 0.000000f, // vertex 943
+	0.500000f, 0.500000f, 0.000000f, // vertex 944
+	0.500000f, 0.500000f, 0.000000f, // vertex 945
+	0.500000f, 0.500000f, 0.000000f, // vertex 946
+	0.500000f, 0.500000f, 0.000000f, // vertex 947
+	0.500000f, 0.500000f, 0.000000f, // vertex 948
+	0.500000f, 0.500000f, 0.000000f, // vertex 949
+	0.500000f, 0.500000f, 0.000000f, // vertex 950
+	0.500000f, 0.500000f, 0.000000f, // vertex 951
+	0.500000f, 0.500000f, 0.000000f, // vertex 952
+	0.500000f, 0.500000f, 0.000000f, // vertex 953
+	0.500000f, 0.500000f, 0.000000f, // vertex 954
+	0.500000f, 0.500000f, 0.000000f, // vertex 955
+	0.500000f, 0.500000f, 0.000000f, // vertex 956
+	0.500000f, 0.500000f, 0.000000f, // vertex 957
+	0.500000f, 0.500000f, 0.000000f, // vertex 958
+	0.500000f, 0.500000f, 0.000000f, // vertex 959
+	0.500000f, 0.500000f, 0.000000f, // vertex 960
+	0.500000f, 0.500000f, 0.000000f, // vertex 961
+	0.500000f, 0.500000f, 0.000000f, // vertex 962
+	0.500000f, 0.500000f, 0.000000f, // vertex 963
+	0.500000f, 0.500000f, 0.000000f, // vertex 964
+	0.500000f, 0.500000f, 0.000000f, // vertex 965
+	0.500000f, 0.500000f, 0.000000f, // vertex 966
+	0.500000f, 0.500000f, 0.000000f, // vertex 967
+	0.500000f, 0.500000f, 0.000000f, // vertex 968
+	0.500000f, 0.500000f, 0.000000f, // vertex 969
+	0.500000f, 0.500000f, 0.000000f, // vertex 970
+	0.500000f, 0.500000f, 0.000000f, // vertex 971
+	0.500000f, 0.500000f, 0.000000f, // vertex 972
+	0.500000f, 0.500000f, 0.000000f, // vertex 973
+	0.500000f, 0.500000f, 0.000000f, // vertex 974
+	0.500000f, 0.500000f, 0.000000f, // vertex 975
+	1.000000f, 0.000000f, 0.000000f, // vertex 976
+	1.000000f, 0.000000f, 0.000000f, // vertex 977
+	1.000000f, 0.000000f, 0.000000f, // vertex 978
+	1.000000f, 0.000000f, 0.000000f, // vertex 979
+	1.000000f, 0.000000f, 0.000000f, // vertex 980
+	1.000000f, 0.000000f, 0.000000f, // vertex 981
+	1.000000f, 0.000000f, 0.000000f, // vertex 982
+	1.000000f, 0.000000f, 0.000000f, // vertex 983
+	1.000000f, 0.000000f, 0.000000f, // vertex 984
+	1.000000f, 0.000000f, 0.000000f, // vertex 985
+	1.000000f, 0.000000f, 0.000000f, // vertex 986
+	1.000000f, 0.000000f, 0.000000f, // vertex 987
+	1.000000f, 0.000000f, 0.000000f, // vertex 988
+	1.000000f, 0.000000f, 0.000000f, // vertex 989
+	1.000000f, 0.000000f, 0.000000f, // vertex 990
+	1.000000f, 0.000000f, 0.000000f, // vertex 991
+	1.000000f, 0.000000f, 0.000000f, // vertex 992
+	1.000000f, 0.000000f, 0.000000f, // vertex 993
+	1.000000f, 0.000000f, 0.000000f, // vertex 994
+	1.000000f, 0.000000f, 0.000000f, // vertex 995
+	1.000000f, 0.000000f, 0.000000f, // vertex 996
+	1.000000f, 0.000000f, 0.000000f, // vertex 997
+	1.000000f, 0.000000f, 0.000000f, // vertex 998
+	1.000000f, 0.000000f, 0.000000f, // vertex 999
+	1.000000f, 0.000000f, 0.000000f, // vertex 1000
+	1.000000f, 0.000000f, 0.000000f, // vertex 1001
+	1.000000f, 0.000000f, 0.000000f, // vertex 1002
+	1.000000f, 0.000000f, 0.000000f, // vertex 1003
+	1.000000f, 0.000000f, 0.000000f, // vertex 1004
+	1.000000f, 0.000000f, 0.000000f, // vertex 1005
+	1.000000f, 0.000000f, 0.000000f, // vertex 1006
+	1.000000f, 0.000000f, 0.000000f, // vertex 1007
+	1.000000f, 0.000000f, 0.000000f, // vertex 1008
+	1.000000f, 0.000000f, 0.000000f, // vertex 1009
+	1.000000f, 0.000000f, 0.000000f, // vertex 1010
+	1.000000f, 0.000000f, 0.000000f, // vertex 1011
+	1.000000f, 0.000000f, 0.000000f, // vertex 1012
+	1.000000f, 0.000000f, 0.000000f, // vertex 1013
+	1.000000f, 0.000000f, 0.000000f, // vertex 1014
+	1.000000f, 0.000000f, 0.000000f, // vertex 1015
+	1.000000f, 0.000000f, 0.000000f, // vertex 1016
+	1.000000f, 0.000000f, 0.000000f, // vertex 1017
+	1.000000f, 0.000000f, 0.000000f, // vertex 1018
+	1.000000f, 0.000000f, 0.000000f, // vertex 1019
+	1.000000f, 0.000000f, 0.000000f, // vertex 1020
+	1.000000f, 0.000000f, 0.000000f, // vertex 1021
+	1.000000f, 0.000000f, 0.000000f, // vertex 1022
+	1.000000f, 0.000000f, 0.000000f, // vertex 1023
+	1.000000f, 0.000000f, 0.000000f, // vertex 1024
+	1.000000f, 0.000000f, 0.000000f, // vertex 1025
+	1.000000f, 0.000000f, 0.000000f, // vertex 1026
+	1.000000f, 0.000000f, 0.000000f, // vertex 1027
+	1.000000f, 0.000000f, 0.000000f, // vertex 1028
+	1.000000f, 0.000000f, 0.000000f, // vertex 1029
+	1.000000f, 0.000000f, 0.000000f, // vertex 1030
+	1.000000f, 0.000000f, 0.000000f, // vertex 1031
+	1.000000f, 0.000000f, 0.000000f, // vertex 1032
+	1.000000f, 0.000000f, 0.000000f, // vertex 1033
+	1.000000f, 0.000000f, 0.000000f, // vertex 1034
+	1.000000f, 0.000000f, 0.000000f, // vertex 1035
+	1.000000f, 0.000000f, 0.000000f, // vertex 1036
+	1.000000f, 0.000000f, 0.000000f, // vertex 1037
+	1.000000f, 0.000000f, 0.000000f, // vertex 1038
+	1.000000f, 0.000000f, 0.000000f, // vertex 1039
+	1.000000f, 0.000000f, 0.000000f, // vertex 1040
+	1.000000f, 0.000000f, 0.000000f, // vertex 1041
+	1.000000f, 0.000000f, 0.000000f, // vertex 1042
+	1.000000f, 0.000000f, 0.000000f, // vertex 1043
+	1.000000f, 0.000000f, 0.000000f, // vertex 1044
+	1.000000f, 0.000000f, 0.000000f, // vertex 1045
+	1.000000f, 0.000000f, 0.000000f, // vertex 1046
+	1.000000f, 0.000000f, 0.000000f, // vertex 1047
+	1.000000f, 0.000000f, 0.000000f, // vertex 1048
+	1.000000f, 0.000000f, 0.000000f, // vertex 1049
+	1.000000f, 0.000000f, 0.000000f, // vertex 1050
+	1.000000f, 0.000000f, 0.000000f, // vertex 1051
+	1.000000f, 0.000000f, 0.000000f, // vertex 1052
+	1.000000f, 0.000000f, 0.000000f, // vertex 1053
+	1.000000f, 0.000000f, 0.000000f, // vertex 1054
+	1.000000f, 0.000000f, 0.000000f, // vertex 1055
+	1.000000f, 0.000000f, 0.000000f, // vertex 1056
+	1.000000f, 0.000000f, 0.000000f, // vertex 1057
+	1.000000f, 0.000000f, 0.000000f, // vertex 1058
+	1.000000f, 0.000000f, 0.000000f, // vertex 1059
+	1.000000f, 0.000000f, 0.000000f, // vertex 1060
+	1.000000f, 0.000000f, 0.000000f, // vertex 1061
+	1.000000f, 0.000000f, 0.000000f, // vertex 1062
+	1.000000f, 0.000000f, 0.000000f, // vertex 1063
+	1.000000f, 0.000000f, 0.000000f, // vertex 1064
+	1.000000f, 0.000000f, 0.000000f, // vertex 1065
+	1.000000f, 0.000000f, 0.000000f, // vertex 1066
+	1.000000f, 0.000000f, 0.000000f, // vertex 1067
+	1.000000f, 0.000000f, 0.000000f, // vertex 1068
+	1.000000f, 0.000000f, 0.000000f, // vertex 1069
+	1.000000f, 0.000000f, 0.000000f, // vertex 1070
+	1.000000f, 0.000000f, 0.000000f, // vertex 1071
+	1.000000f, 0.000000f, 0.000000f, // vertex 1072
+	1.000000f, 0.000000f, 0.000000f, // vertex 1073
+	1.000000f, 0.000000f, 0.000000f, // vertex 1074
+	1.000000f, 0.000000f, 0.000000f, // vertex 1075
+	1.000000f, 0.000000f, 0.000000f, // vertex 1076
+	1.000000f, 0.000000f, 0.000000f, // vertex 1077
+	1.000000f, 0.000000f, 0.000000f, // vertex 1078
+	1.000000f, 0.000000f, 0.000000f, // vertex 1079
+	1.000000f, 0.000000f, 0.000000f, // vertex 1080
+	1.000000f, 0.000000f, 0.000000f, // vertex 1081
+	1.000000f, 0.000000f, 0.000000f, // vertex 1082
+	1.000000f, 0.000000f, 0.000000f, // vertex 1083
+	1.000000f, 0.000000f, 0.000000f, // vertex 1084
+	1.000000f, 0.000000f, 0.000000f, // vertex 1085
+	1.000000f, 0.000000f, 0.000000f, // vertex 1086
+	1.000000f, 0.000000f, 0.000000f, // vertex 1087
+	1.000000f, 0.000000f, 0.000000f, // vertex 1088
+	1.000000f, 0.000000f, 0.000000f, // vertex 1089
+	1.000000f, 0.000000f, 0.000000f, // vertex 1090
+	1.000000f, 0.000000f, 0.000000f, // vertex 1091
+	1.000000f, 0.000000f, 0.000000f, // vertex 1092
+	1.000000f, 0.000000f, 0.000000f, // vertex 1093
+	1.000000f, 0.000000f, 0.000000f, // vertex 1094
+	1.000000f, 0.000000f, 0.000000f, // vertex 1095
+	1.000000f, 0.000000f, 0.000000f, // vertex 1096
+	1.000000f, 0.000000f, 0.000000f, // vertex 1097
+	1.000000f, 0.000000f, 0.000000f, // vertex 1098
+	1.000000f, 0.000000f, 0.000000f, // vertex 1099
+	1.000000f, 0.000000f, 0.000000f, // vertex 1100
+	1.000000f, 0.000000f, 0.000000f, // vertex 1101
+	1.000000f, 0.000000f, 0.000000f, // vertex 1102
+	1.000000f, 0.000000f, 0.000000f, // vertex 1103
+	1.000000f, 0.000000f, 0.000000f, // vertex 1104
+	1.000000f, 0.000000f, 0.000000f, // vertex 1105
+	1.000000f, 0.000000f, 0.000000f, // vertex 1106
+	1.000000f, 0.000000f, 0.000000f, // vertex 1107
+	1.000000f, 0.000000f, 0.000000f, // vertex 1108
+	1.000000f, 0.000000f, 0.000000f, // vertex 1109
+	1.000000f, 0.000000f, 0.000000f, // vertex 1110
+	1.000000f, 0.000000f, 0.000000f, // vertex 1111
+	1.000000f, 0.000000f, 0.000000f, // vertex 1112
+	1.000000f, 0.000000f, 0.000000f, // vertex 1113
+	1.000000f, 0.000000f, 0.000000f, // vertex 1114
+	1.000000f, 0.000000f, 0.000000f, // vertex 1115
+	1.000000f, 0.000000f, 0.000000f, // vertex 1116
+	1.000000f, 0.000000f, 0.000000f, // vertex 1117
+	1.000000f, 0.000000f, 0.000000f, // vertex 1118
+	1.000000f, 0.000000f, 0.000000f, // vertex 1119
+	1.000000f, 0.000000f, 0.000000f, // vertex 1120
+	1.000000f, 0.000000f, 0.000000f, // vertex 1121
+	1.000000f, 0.000000f, 0.000000f, // vertex 1122
+	1.000000f, 0.000000f, 0.000000f, // vertex 1123
+	1.000000f, 0.000000f, 0.000000f, // vertex 1124
+	1.000000f, 0.000000f, 0.000000f, // vertex 1125
+	1.000000f, 0.000000f, 0.000000f, // vertex 1126
+	1.000000f, 0.000000f, 0.000000f, // vertex 1127
+	1.000000f, 0.000000f, 0.000000f, // vertex 1128
+	1.000000f, 0.000000f, 0.000000f, // vertex 1129
+	1.000000f, 0.000000f, 0.000000f, // vertex 1130
+	1.000000f, 0.000000f, 0.000000f, // vertex 1131
+	1.000000f, 0.000000f, 0.000000f, // vertex 1132
+	1.000000f, 0.000000f, 0.000000f, // vertex 1133
+	1.000000f, 0.000000f, 0.000000f, // vertex 1134
+	1.000000f, 0.000000f, 0.000000f, // vertex 1135
+	1.000000f, 0.000000f, 0.000000f, // vertex 1136
+	1.000000f, 0.000000f, 0.000000f, // vertex 1137
+	1.000000f, 0.000000f, 0.000000f, // vertex 1138
+	1.000000f, 0.000000f, 0.000000f, // vertex 1139
+	1.000000f, 0.000000f, 0.000000f, // vertex 1140
+	1.000000f, 0.000000f, 0.000000f, // vertex 1141
+	1.000000f, 0.000000f, 0.000000f, // vertex 1142
+	1.000000f, 0.000000f, 0.000000f, // vertex 1143
+	1.000000f, 0.000000f, 0.000000f, // vertex 1144
+	1.000000f, 0.000000f, 0.000000f, // vertex 1145
+	1.000000f, 0.000000f, 0.000000f, // vertex 1146
+	1.000000f, 0.000000f, 0.000000f, // vertex 1147
+	1.000000f, 0.000000f, 0.000000f, // vertex 1148
+	1.000000f, 0.000000f, 0.000000f, // vertex 1149
+	1.000000f, 0.000000f, 0.000000f, // vertex 1150
+	1.000000f, 0.000000f, 0.000000f, // vertex 1151
+	1.000000f, 0.000000f, 0.000000f, // vertex 1152
+	1.000000f, 0.000000f, 0.000000f, // vertex 1153
+	1.000000f, 0.000000f, 0.000000f, // vertex 1154
+	1.000000f, 0.000000f, 0.000000f, // vertex 1155
+	1.000000f, 0.000000f, 0.000000f, // vertex 1156
+	1.000000f, 0.000000f, 0.000000f, // vertex 1157
+	1.000000f, 0.000000f, 0.000000f, // vertex 1158
+	1.000000f, 0.000000f, 0.000000f, // vertex 1159
+	1.000000f, 0.000000f, 0.000000f, // vertex 1160
+	1.000000f, 0.000000f, 0.000000f, // vertex 1161
+	1.000000f, 0.000000f, 0.000000f, // vertex 1162
+	1.000000f, 0.000000f, 0.000000f, // vertex 1163
+	1.000000f, 0.000000f, 0.000000f, // vertex 1164
+	1.000000f, 0.000000f, 0.000000f, // vertex 1165
+	1.000000f, 0.000000f, 0.000000f, // vertex 1166
+	1.000000f, 0.000000f, 0.000000f, // vertex 1167
+	1.000000f, 0.000000f, 0.000000f, // vertex 1168
+	1.000000f, 0.000000f, 0.000000f, // vertex 1169
+	1.000000f, 0.000000f, 0.000000f, // vertex 1170
+	1.000000f, 0.000000f, 0.000000f, // vertex 1171
+	1.000000f, 0.000000f, 0.000000f, // vertex 1172
+	1.000000f, 0.000000f, 0.000000f, // vertex 1173
+	1.000000f, 0.000000f, 0.000000f, // vertex 1174
+	1.000000f, 0.000000f, 0.000000f, // vertex 1175
+	1.000000f, 0.000000f, 0.000000f, // vertex 1176
+	1.000000f, 0.000000f, 0.000000f, // vertex 1177
+	1.000000f, 0.000000f, 0.000000f, // vertex 1178
+	1.000000f, 0.000000f, 0.000000f, // vertex 1179
+	0.909091f, 0.090909f, 0.000000f, // vertex 1180
+	1.000000f, 0.000000f, 0.000000f, // vertex 1181
+	1.000000f, 0.000000f, 0.000000f, // vertex 1182
+	0.833333f, 0.166667f, 0.000000f, // vertex 1183
+	0.833333f, 0.166667f, 0.000000f, // vertex 1184
+	0.666667f, 0.333333f, 0.000000f, // vertex 1185
+	0.666667f, 0.333333f, 0.000000f, // vertex 1186
+	0.666667f, 0.333333f, 0.000000f, // vertex 1187
+	0.666667f, 0.333333f, 0.000000f, // vertex 1188
+	0.833333f, 0.166667f, 0.000000f, // vertex 1189
+	0.833333f, 0.166667f, 0.000000f, // vertex 1190
+	1.000000f, 0.000000f, 0.000000f, // vertex 1191
+	1.000000f, 0.000000f, 0.000000f, // vertex 1192
+	0.909091f, 0.090909f, 0.000000f, // vertex 1193
+	1.000000f, 0.000000f, 0.000000f, // vertex 1194
+	1.000000f, 0.000000f, 0.000000f, // vertex 1195
+	1.000000f, 0.000000f, 0.000000f, // vertex 1196
+	1.000000f, 0.000000f, 0.000000f, // vertex 1197
+	0.500000f, 0.500000f, 0.000000f, // vertex 1198
+	0.833333f, 0.166667f, 0.000000f, // vertex 1199
+	0.500000f, 0.500000f, 0.000000f, // vertex 1200
+	0.833333f, 0.166667f, 0.000000f, // vertex 1201
+	0.714286f, 0.285714f, 0.000000f, // vertex 1202
+	0.900901f, 0.099099f, 0.000000f, // vertex 1203
+	0.655738f, 0.344262f, 0.000000f, // vertex 1204
+	0.772187f, 0.154437f, 0.073375f, // vertex 1205
+	0.819001f, 0.180999f, 0.000000f, // vertex 1206
+	0.772187f, 0.154437f, 0.073375f, // vertex 1207
+	0.819001f, 0.180999f, 0.000000f, // vertex 1208
+	0.772187f, 0.154437f, 0.073375f, // vertex 1209
+	0.772187f, 0.154437f, 0.073375f, // vertex 1210
+	0.800000f, 0.200000f, 0.000000f, // vertex 1211
+	0.800000f, 0.200000f, 0.000000f, // vertex 1212
+	0.772187f, 0.154437f, 0.073375f, // vertex 1213
+	0.800000f, 0.200000f, 0.000000f, // vertex 1214
+	0.772187f, 0.154437f, 0.073375f, // vertex 1215
+	0.800000f, 0.200000f, 0.000000f, // vertex 1216
+	0.772187f, 0.154437f, 0.073375f, // vertex 1217
+	0.500000f, 0.500000f, 0.000000f, // vertex 1218
+	0.900901f, 0.099099f, 0.000000f, // vertex 1219
+	0.952381f, 0.047619f, 0.000000f, // vertex 1220
+	0.833333f, 0.166667f, 0.000000f, // vertex 1221
+	0.909091f, 0.090909f, 0.000000f, // vertex 1222
+	0.909091f, 0.090909f, 0.000000f, // vertex 1223
+	1.000000f, 0.000000f, 0.000000f, // vertex 1224
+	0.500000f, 0.500000f, 0.000000f, // vertex 1225
+	0.500000f, 0.500000f, 0.000000f, // vertex 1226
+	0.500000f, 0.500000f, 0.000000f, // vertex 1227
+	0.500000f, 0.500000f, 0.000000f, // vertex 1228
+	0.500000f, 0.500000f, 0.000000f, // vertex 1229
+	0.500000f, 0.500000f, 0.000000f, // vertex 1230
+	0.500000f, 0.500000f, 0.000000f, // vertex 1231
+	0.500000f, 0.500000f, 0.000000f, // vertex 1232
+	0.500000f, 0.500000f, 0.000000f, // vertex 1233
+	0.500000f, 0.500000f, 0.000000f, // vertex 1234
+	0.500000f, 0.500000f, 0.000000f, // vertex 1235
+	0.500000f, 0.500000f, 0.000000f, // vertex 1236
+	0.500000f, 0.500000f, 0.000000f, // vertex 1237
+	0.655738f, 0.344262f, 0.000000f, // vertex 1238
+	0.714286f, 0.285714f, 0.000000f, // vertex 1239
+	0.500000f, 0.500000f, 0.000000f, // vertex 1240
+	0.500000f, 0.500000f, 0.000000f, // vertex 1241
+	0.500000f, 0.500000f, 0.000000f, // vertex 1242
+	0.500000f, 0.500000f, 0.000000f, // vertex 1243
+	0.500000f, 0.500000f, 0.000000f, // vertex 1244
+	0.500000f, 0.500000f, 0.000000f, // vertex 1245
+	1.000000f, 0.000000f, 0.000000f, // vertex 1246
+	0.500000f, 0.500000f, 0.000000f, // vertex 1247
+	0.500000f, 0.500000f, 0.000000f, // vertex 1248
+	0.500000f, 0.500000f, 0.000000f, // vertex 1249
+	0.500000f, 0.500000f, 0.000000f, // vertex 1250
+	0.500000f, 0.500000f, 0.000000f, // vertex 1251
+	0.500000f, 0.500000f, 0.000000f, // vertex 1252
+	0.500000f, 0.500000f, 0.000000f, // vertex 1253
+	0.500000f, 0.500000f, 0.000000f, // vertex 1254
+	0.500000f, 0.500000f, 0.000000f, // vertex 1255
+	0.500000f, 0.500000f, 0.000000f, // vertex 1256
+	0.500000f, 0.500000f, 0.000000f, // vertex 1257
+	0.819001f, 0.180999f, 0.000000f, // vertex 1258
+	0.772187f, 0.154437f, 0.073375f, // vertex 1259
+	0.819001f, 0.180999f, 0.000000f, // vertex 1260
+	0.819001f, 0.180999f, 0.000000f, // vertex 1261
+	0.655738f, 0.344262f, 0.000000f, // vertex 1262
+	0.900901f, 0.099099f, 0.000000f, // vertex 1263
+	0.714286f, 0.285714f, 0.000000f, // vertex 1264
+	0.833333f, 0.166667f, 0.000000f, // vertex 1265
+	0.500000f, 0.500000f, 0.000000f, // vertex 1266
+	0.833333f, 0.166667f, 0.000000f, // vertex 1267
+	0.500000f, 0.500000f, 0.000000f, // vertex 1268
+	1.000000f, 0.000000f, 0.000000f, // vertex 1269
+	1.000000f, 0.000000f, 0.000000f, // vertex 1270
+	0.909091f, 0.090909f, 0.000000f, // vertex 1271
+	0.909091f, 0.090909f, 0.000000f, // vertex 1272
+	0.833333f, 0.166667f, 0.000000f, // vertex 1273
+	0.952381f, 0.047619f, 0.000000f, // vertex 1274
+	0.900901f, 0.099099f, 0.000000f, // vertex 1275
+	0.500000f, 0.500000f, 0.000000f, // vertex 1276
+	0.819001f, 0.180999f, 0.000000f, // vertex 1277
+	0.800000f, 0.200000f, 0.000000f, // vertex 1278
+	0.772187f, 0.154437f, 0.073375f, // vertex 1279
+	0.500000f, 0.500000f, 0.000000f, // vertex 1280
+	0.655738f, 0.344262f, 0.000000f, // vertex 1281
+	0.500000f, 0.500000f, 0.000000f, // vertex 1282
+	0.714286f, 0.285714f, 0.000000f, // vertex 1283
+	0.500000f, 0.500000f, 0.000000f, // vertex 1284
+	0.500000f, 0.500000f, 0.000000f, // vertex 1285
+	0.500000f, 0.500000f, 0.000000f, // vertex 1286
+	0.500000f, 0.500000f, 0.000000f, // vertex 1287
+	0.500000f, 0.500000f, 0.000000f, // vertex 1288
+	0.500000f, 0.500000f, 0.000000f, // vertex 1289
+	0.500000f, 0.500000f, 0.000000f, // vertex 1290
+	0.500000f, 0.500000f, 0.000000f, // vertex 1291
+	0.500000f, 0.500000f, 0.000000f, // vertex 1292
+	0.500000f, 0.500000f, 0.000000f, // vertex 1293
+	0.500000f, 0.500000f, 0.000000f, // vertex 1294
+	0.500000f, 0.500000f, 0.000000f, // vertex 1295
+	0.500000f, 0.500000f, 0.000000f, // vertex 1296
+	0.500000f, 0.500000f, 0.000000f, // vertex 1297
+	0.500000f, 0.500000f, 0.000000f, // vertex 1298
+	0.500000f, 0.500000f, 0.000000f, // vertex 1299
+	0.500000f, 0.500000f, 0.000000f, // vertex 1300
+	0.500000f, 0.500000f, 0.000000f, // vertex 1301
+	0.500000f, 0.500000f, 0.000000f, // vertex 1302
+	0.500000f, 0.500000f, 0.000000f, // vertex 1303
+	0.500000f, 0.500000f, 0.000000f, // vertex 1304
+	0.500000f, 0.500000f, 0.000000f, // vertex 1305
+	0.500000f, 0.500000f, 0.000000f, // vertex 1306
+	0.500000f, 0.500000f, 0.000000f, // vertex 1307
+	0.500000f, 0.500000f, 0.000000f, // vertex 1308
+	0.500000f, 0.500000f, 0.000000f, // vertex 1309
+	0.500000f, 0.500000f, 0.000000f, // vertex 1310
+	0.714286f, 0.285714f, 0.000000f, // vertex 1311
+	0.500000f, 0.500000f, 0.000000f, // vertex 1312
+	1.000000f, 0.000000f, 0.000000f, // vertex 1313
+	0.909091f, 0.090909f, 0.000000f, // vertex 1314
+	0.833333f, 0.166667f, 0.000000f, // vertex 1315
+	0.833333f, 0.166667f, 0.000000f, // vertex 1316
+	0.500000f, 0.500000f, 0.000000f, // vertex 1317
+	0.500000f, 0.500000f, 0.000000f, // vertex 1318
+	0.500000f, 0.500000f, 0.000000f, // vertex 1319
+	0.500000f, 0.500000f, 0.000000f, // vertex 1320
+	0.500000f, 0.500000f, 0.000000f, // vertex 1321
+	1.000000f, 0.000000f, 0.000000f, // vertex 1322
+	0.833333f, 0.166667f, 0.000000f, // vertex 1323
+	0.909091f, 0.090909f, 0.000000f, // vertex 1324
+	0.833333f, 0.166667f, 0.000000f, // vertex 1325
+	0.666667f, 0.333333f, 0.000000f, // vertex 1326
+	0.800000f, 0.200000f, 0.000000f, // vertex 1327
+	0.666667f, 0.333333f, 0.000000f, // vertex 1328
+	0.714286f, 0.285714f, 0.000000f, // vertex 1329
+	0.666667f, 0.333333f, 0.000000f, // vertex 1330
+	0.714286f, 0.285714f, 0.000000f, // vertex 1331
+	0.666667f, 0.333333f, 0.000000f, // vertex 1332
+	0.714286f, 0.285714f, 0.000000f, // vertex 1333
+	0.714286f, 0.285714f, 0.000000f, // vertex 1334
+	0.666667f, 0.333333f, 0.000000f, // vertex 1335
+	0.555556f, 0.444444f, 0.000000f, // vertex 1336
+	0.666667f, 0.333333f, 0.000000f, // vertex 1337
+	0.555556f, 0.444444f, 0.000000f, // vertex 1338
+	0.666667f, 0.333333f, 0.000000f, // vertex 1339
+	0.555556f, 0.444444f, 0.000000f, // vertex 1340
+	0.666667f, 0.333333f, 0.000000f, // vertex 1341
+	0.666667f, 0.333333f, 0.000000f, // vertex 1342
+	0.714286f, 0.285714f, 0.000000f, // vertex 1343
+	0.714286f, 0.285714f, 0.000000f, // vertex 1344
+	0.800000f, 0.200000f, 0.000000f, // vertex 1345
+	0.714286f, 0.285714f, 0.000000f, // vertex 1346
+	0.666667f, 0.333333f, 0.000000f, // vertex 1347
+	0.666667f, 0.333333f, 0.000000f, // vertex 1348
+	0.555556f, 0.444444f, 0.000000f, // vertex 1349
+	0.555556f, 0.444444f, 0.000000f, // vertex 1350
+	0.800000f, 0.200000f, 0.000000f, // vertex 1351
+	0.666667f, 0.333333f, 0.000000f, // vertex 1352
+	0.666667f, 0.333333f, 0.000000f, // vertex 1353
+	0.555556f, 0.444444f, 0.000000f, // vertex 1354
+	0.555556f, 0.444444f, 0.000000f, // vertex 1355
+	0.500000f, 0.500000f, 0.000000f, // vertex 1356
+	0.500000f, 0.500000f, 0.000000f, // vertex 1357
+	0.500000f, 0.500000f, 0.000000f, // vertex 1358
+	0.500000f, 0.500000f, 0.000000f, // vertex 1359
+	0.500000f, 0.500000f, 0.000000f, // vertex 1360
+	0.500000f, 0.500000f, 0.000000f, // vertex 1361
+	0.500000f, 0.500000f, 0.000000f, // vertex 1362
+	0.500000f, 0.500000f, 0.000000f, // vertex 1363
+	0.500000f, 0.500000f, 0.000000f, // vertex 1364
+	0.500000f, 0.500000f, 0.000000f, // vertex 1365
+	0.500000f, 0.500000f, 0.000000f, // vertex 1366
+	0.500000f, 0.500000f, 0.000000f, // vertex 1367
+	0.500000f, 0.500000f, 0.000000f, // vertex 1368
+	0.500000f, 0.500000f, 0.000000f, // vertex 1369
+	0.500000f, 0.500000f, 0.000000f, // vertex 1370
+	0.500000f, 0.500000f, 0.000000f, // vertex 1371
+	0.500000f, 0.500000f, 0.000000f, // vertex 1372
+	0.500000f, 0.500000f, 0.000000f, // vertex 1373
+	0.500000f, 0.500000f, 0.000000f, // vertex 1374
+	0.500000f, 0.500000f, 0.000000f, // vertex 1375
+	0.500000f, 0.500000f, 0.000000f, // vertex 1376
+	0.500000f, 0.500000f, 0.000000f, // vertex 1377
+	0.500000f, 0.500000f, 0.000000f, // vertex 1378
+	0.500000f, 0.500000f, 0.000000f, // vertex 1379
+	0.500000f, 0.500000f, 0.000000f, // vertex 1380
+	0.500000f, 0.500000f, 0.000000f, // vertex 1381
+	0.500000f, 0.500000f, 0.000000f, // vertex 1382
+	0.500000f, 0.500000f, 0.000000f, // vertex 1383
+	0.500000f, 0.500000f, 0.000000f, // vertex 1384
+	0.500000f, 0.500000f, 0.000000f, // vertex 1385
+	0.500000f, 0.500000f, 0.000000f, // vertex 1386
+	0.500000f, 0.500000f, 0.000000f, // vertex 1387
+	0.500000f, 0.500000f, 0.000000f, // vertex 1388
+	0.500000f, 0.500000f, 0.000000f, // vertex 1389
+	0.500000f, 0.500000f, 0.000000f, // vertex 1390
+	0.500000f, 0.500000f, 0.000000f, // vertex 1391
+	0.500000f, 0.500000f, 0.000000f, // vertex 1392
+	0.500000f, 0.500000f, 0.000000f, // vertex 1393
+	0.500000f, 0.500000f, 0.000000f, // vertex 1394
+	0.500000f, 0.500000f, 0.000000f, // vertex 1395
+	0.500000f, 0.500000f, 0.000000f, // vertex 1396
+	0.500000f, 0.500000f, 0.000000f, // vertex 1397
+	0.500000f, 0.500000f, 0.000000f, // vertex 1398
+	0.500000f, 0.500000f, 0.000000f, // vertex 1399
+	0.500000f, 0.500000f, 0.000000f, // vertex 1400
+	0.500000f, 0.500000f, 0.000000f, // vertex 1401
+	0.500000f, 0.500000f, 0.000000f, // vertex 1402
+	0.500000f, 0.500000f, 0.000000f, // vertex 1403                                                          
+};
+*/
+
+Mth::Vector testBonePositions[] = {
+	Mth::Vector( 0.000000f, 0.000000f, 0.000000f, 1.000000f ),
+	Mth::Vector( 0.003876f, 3.697956f, 1.117333f, 1.000000f ),
+	Mth::Vector( 0.003872f, 4.297436f, 2.893996f, 1.000000f ),
+	Mth::Vector( 0.003868f, 9.568745f, 1.807596f, 1.000000f ),
+	Mth::Vector( 0.003863f, 7.351619f, 5.444900f, 1.000000f ),
+};											    
+
+Mth::Vector testBoneScales[] = {
+	Mth::Vector( 1.000000f, 1.000000f, 1.000000f, 1.000000f ),
+	Mth::Vector( 1.000000f, 1.000000f, 1.000000f, 1.000000f ),
+	Mth::Vector( 1.000000f, 1.000000f, 1.000000f, 1.000000f ),
+	Mth::Vector( 1.000000f, 1.000000f, 1.000000f, 1.000000f ),
+	Mth::Vector( 1.000000f, 1.000000f, 1.000000f, 1.000000f ),
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCutsceneData::CCutsceneData()
+{
+	mp_fileLibrary = NULL;
+
+	mp_cameraQuickAnim = NULL;
+	mp_objectQuickAnim = NULL;
+
+	m_numObjects = 0;
+	m_numAssets = 0;
+
+	m_audioStarted = false;
+	m_currentTime = 0.0f;
+
+	m_original_params_set = false;
+
+	Pcm::StopMusic();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCutsceneData::~CCutsceneData()
+{
+	if ( mp_fileLibrary )
+	{
+		delete mp_fileLibrary;
+		mp_fileLibrary = NULL;
+	}
+
+	if ( mp_cameraQuickAnim )
+	{
+		delete mp_cameraQuickAnim;
+		mp_cameraQuickAnim = NULL;
+	}
+
+	if ( mp_objectQuickAnim )
+	{
+		delete mp_objectQuickAnim;
+		mp_objectQuickAnim = NULL;
+	}
+	
+	for ( int i = 0; i < m_numBonedAnims; i++ )
+	{											 
+		SCutsceneAnimInfo* pAnimInfo = &m_bonedAnims[i];
+	
+		Dbg_MsgAssert( pAnimInfo->mp_bonedQuickAnim, ( "No quick anim to delete" ) );
+		delete pAnimInfo->mp_bonedQuickAnim;
+		pAnimInfo->mp_bonedQuickAnim = NULL;
+	}
+
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+// 		Don't want to mark 'em as dead, because the assets
+//		contained in these dummy objects will be obliterated
+//		very soon...  we don't want any dummy objects hanging
+//		around without valid assets...
+//		mp_object[i]->MarkAsDead();
+		
+		SCutsceneObjectInfo* pObjectInfo = &m_objects[i];
+		
+		Dbg_MsgAssert( pObjectInfo->mp_object, ( "No object to delete" ) );
+		
+		Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pObjectInfo->mp_object );
+		Dbg_Assert( pModelComponent );
+		
+		if ( pObjectInfo->m_doPlayerSubstitution )
+		{
+			Dbg_Assert( pModelComponent->HasRefObject() );
+			// put back the skater's model component,
+			// if we changed the bounding sphere...
+			uint32 refObjectName = pModelComponent->GetRefObjectName();
+			Obj::CCompositeObject* pRefObject = (Obj::CCompositeObject*)Obj::ResolveToObject( refObjectName );
+			Dbg_Assert( pRefObject );
+			Obj::CModelComponent* pRefModelComponent = GetModelComponentFromObject( pRefObject );
+			Dbg_Assert( pRefModelComponent );
+			Nx::CModel* pRefModel = pRefModelComponent->GetModel();
+			pRefModel->SetBoundingSphere( m_original_player_bounding_sphere );
+			pRefModel->Hide( m_original_hidden );
+			pRefModel->EnableScaling( m_original_scale_enabled );
+			
+			// send the object back to where it came from...
+			// (we had to change it for the duration of the cutscene
+			// in case the object was affected by a FakeLight)
+			pRefObject->SetPos( m_original_pos );
+		}
+
+		delete pObjectInfo->mp_object;
+		pObjectInfo->mp_object = NULL;
+	}
+	
+	for ( int i = 0; i < m_numAssets; i++ )
+	{
+		// remove them from the assman
+		// (do i need to make sure the objects are completely dead first?)
+		Ass::CAssMan* pAssMan = Ass::CAssMan::Instance();
+		Ass::CAsset* pAsset = pAssMan->GetAssetNode( m_assetName[i], true );
+		pAssMan->UnloadAsset( pAsset );
+		m_assetName[i] = NULL;
+		
+		// TODO:  Need to handle anims slightly
+		// if we use references...  fortunately,
+		// we're not using references right now
+	}
+
+	// stop the stream (may not need this, unless we abort)
+	Pcm::StopMusic();
+	
+	Script::RunScript( CRCD(0x7216d8da,"CutsceneKillObjects") );
+
+	Script::UnloadQB( CRCD(0xa1408e6e,"cutscenefrommemory.qb") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneData::OverridesCamera()
+{
+	return LoadFinished() && m_audioStarted;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector get_scale( Script::CStruct* pAppearance, uint32 bone_group_name )
+{
+	Script::CStruct* pVirtualDescStruct = NULL;
+
+	if ( pAppearance->GetStructure( bone_group_name, &pVirtualDescStruct, Script::NO_ASSERT ) )
+	{
+		int use_default_scale = 1;
+		pVirtualDescStruct->GetInteger( CRCD(0x5a96985d,"use_default_scale"), &use_default_scale, Script::NO_ASSERT );
+		
+		if ( !use_default_scale )
+		{
+			int x, y, z;
+			pVirtualDescStruct->GetInteger( CRCD(0x7323e97c,"x"), &x, Script::ASSERT );
+			pVirtualDescStruct->GetInteger( CRCD(0x0424d9ea,"y"), &y, Script::ASSERT );
+			pVirtualDescStruct->GetInteger( CRCD(0x9d2d8850,"z"), &z, Script::ASSERT );
+			return Mth::Vector( ((float)x)/100.0f, ((float)y)/100.0f, ((float)z)/100.0f, 1.0f ); 
+		}
+		else 
+		{
+			// use_default_scale parameter
+			return Mth::Vector( 1.0f, 1.0f, 1.0f, 1.0f );
+		}
+	}
+	else
+	{
+		// part not found
+		return Mth::Vector( 1.0f, 1.0f, 1.0f, 1.0f );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::load_models()
+{
+	Dbg_MsgAssert( mp_fileLibrary, ( "No file library?!?" ) );
+
+	int numFiles = mp_fileLibrary->GetNumFiles();
+	for ( int i = 0; i < numFiles; i++ )
+	{
+		const File::SFileInfo* pFileInfo = mp_fileLibrary->GetFileInfo( i );
+
+#ifdef __PLAT_NGC__
+		uint32* pData = NULL;
+		switch ( pFileInfo->fileExtensionChecksum )
+		{
+			case 0x5ac14717:	// CIF
+			case 0x1512808d:	// TEX
+			case 0xffc529f4:	// CAS
+			case 0x2cd4107d:	// WGT
+				break;
+			default:
+				pData = mp_fileLibrary->GetFileData( i );
+				break;
+		}
+#else
+		uint32* pData = mp_fileLibrary->GetFileData( i );
+#endif		// __PLAT_NGC__
+			
+		// read each file one by one, and do something different based on the file type
+		switch ( pFileInfo->fileExtensionChecksum )
+		{
+			case 0xeab51346:	// SKA
+				add_boned_anim( pFileInfo->fileNameChecksum, pData, pFileInfo->fileSize );
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, pFileInfo->fileExtensionChecksum );				
+				break;
+		
+			case 0x2e4bf21b:	// OBA
+			{
+				Dbg_MsgAssert( !mp_objectQuickAnim, ( "Cutscene file should have exactly 1 object anim\n" ) );
+				add_anim( CRCD(0x3b423a24,"cutscene_oba"), pData, pFileInfo->fileSize );
+				mp_objectQuickAnim = new Nx::CQuickAnim;
+				mp_objectQuickAnim->SetAnimAssetName( CRCD(0x3b423a24,"cutscene_oba") );
+				mp_objectQuickAnim->ResetCustomKeys();
+//				m_objectAnimController.PlaySequence( 0, 0.0f, mp_objectAnimData->GetDuration(), Gfx::LOOPING_HOLD, 0.0f, 1.0f );
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, pFileInfo->fileExtensionChecksum );				
+			}
+			break;
+		
+			case 0x05ca1497:	// CAM
+			{
+				// this presumes that there's only one cutscene_cam asset at a time,
+				// which may not be true if we're queueing up multiple cutscenes
+				// in this case, we'd need to run an init function before each
+				// cutscene is played, not in the cutscene's InitFromStructure,
+				// which gets called at the time the cutscene gets added to the list...
+				add_anim( CRCD(0x10c3dca8,"cutscene_cam"), pData, pFileInfo->fileSize );
+				Dbg_MsgAssert( !mp_cameraQuickAnim, ( "Cutscene file should have exactly 1 Camera\n" ) );
+				mp_cameraQuickAnim = new Nx::CQuickAnim;
+				mp_cameraQuickAnim->SetAnimAssetName( CRCD(0x10c3dca8,"cutscene_cam") );
+				mp_cameraQuickAnim->ResetCustomKeys();
+//				m_cameraController.PlaySequence( 0, 0.0f, mp_cameraData->GetDuration(), Gfx::LOOPING_HOLD, 0.0f, 1.0f );
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, pFileInfo->fileExtensionChecksum );				
+			}
+			break;
+			
+			case 0x2bbea5c3:	// QB
+			{
+				Script::LoadQBFromMemory( "cutscenefrommemory.qb", (uint8*)pData, Script::ASSERT_IF_DUPLICATE_SYMBOLS );
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, pFileInfo->fileExtensionChecksum );
+			}
+			break;
+
+			case 0xe7308c1f:	// GEOM
+			case 0xfd8697e1:	// SKIN
+			case 0x0524fd4e:	// MDL
+			{
+				char model_asset_filename[512];
+				sprintf( model_asset_filename, "%08x", pFileInfo->fileNameChecksum + pFileInfo->fileExtensionChecksum );
+				uint32 model_asset_name = pFileInfo->fileNameChecksum + CRCD(0xfd8697e1,"SKIN");// pFileInfo->fileExtensionChecksum;
+//				Dbg_Message( "Adding model asset %08x", model_asset_name );
+						
+				Ass::SCutsceneModelDataInfo theInfo;
+				theInfo.modelChecksum = model_asset_name;
+				theInfo.pModelData = pData;
+				theInfo.modelDataSize = pFileInfo->fileSize;
+				theInfo.pTextureData = mp_fileLibrary->GetFileData( pFileInfo->fileNameChecksum, CRCD(0x1512808d,"TEX"), true );
+				const File::SFileInfo* pTextureFileInfo;
+				pTextureFileInfo = mp_fileLibrary->GetFileInfo( pFileInfo->fileNameChecksum, CRCD(0x1512808d,"TEX"), true );
+				Dbg_Assert( pTextureFileInfo );
+				theInfo.textureDataSize = pTextureFileInfo->fileSize;
+					
+				// CAS data isn't always required;  only for certain SKIN files
+				theInfo.pCASData = (uint8*)mp_fileLibrary->GetFileData( pFileInfo->fileNameChecksum, CRCD(0xffc529f4,"CAS"), false );;
+
+				theInfo.isSkin = pFileInfo->fileExtensionChecksum == CRCD(0xfd8697e1,"SKIN");		
+				theInfo.texDictChecksum = pTextureFileInfo->fileNameChecksum + pTextureFileInfo->fileExtensionChecksum;
+				
+				// TODO:  Should be true if this is the skater model...
+				theInfo.doShadowVolume = false;	
+
+				// GJ:  make sure the texture dictionary offset doesn't clash with skaters
+				// there must be a cleaner way to do this...  there must!
+				if ( pFileInfo->fileExtensionChecksum == CRCD(0xe7308c1f,"GEOM") )
+				{
+					// geoms shouldn't specify tex dict offset, because
+					// non-zero tex dict offsets will try to load themselves
+					// up as a MDL, rather than as a GEOM
+					theInfo.texDictOffset = 0;
+				}
+				else
+				{
+					theInfo.texDictOffset = vCUTSCENE_TEXDICT_RANGE + i;
+				}
+
+				// set up some default parameters
+				bool permanent = false;
+				int group = 0;
+
+				// these file types get added to the asset manager
+				// (if they don't already exist)...  remember to remove
+				// them when the cutscene is done!
+				Ass::CAssMan* pAssMan = Ass::CAssMan::Instance();
+
+				// by default, load the asset...  the cutscene heads
+				// go through an additional piece of code to figure out
+				// whether they will actually be needed during the cutscene
+				bool should_load_asset = true;
+
+				// used for cutscene head scaling
+				uint8* pWeightMapData = (uint8*)mp_fileLibrary->GetFileData( pFileInfo->fileNameChecksum, CRCD(0x2cd4107d,"WGT"), false );
+				if ( pWeightMapData )
+				{
+					Nx::SMeshScalingParameters theParams;
+					
+#ifdef __NOPT_ASSERT__
+					if ( Script::GetInteger( CRCD(0xf28e1301,"CutsceneScaleTest"), Script::NO_ASSERT ) )
+					{
+						Script::CVector* pVec = NULL;
+						pVec = Script::GetVector( CRCD(0x513a55f6,"CutsceneScaleTestNeck") );
+						testBoneScales[0] = Mth::Vector( pVec->mX, pVec->mY, pVec->mZ, 1.0f );
+						pVec = Script::GetVector( CRCD(0xd6d87539,"CutsceneScaleTestHead") );
+						testBoneScales[1] = Mth::Vector( pVec->mX, pVec->mY, pVec->mZ, 1.0f );
+						pVec = Script::GetVector( CRCD(0x9dea75ff,"CutsceneScaleTestJaw") );
+						testBoneScales[2] = Mth::Vector( pVec->mX, pVec->mY, pVec->mZ, 1.0f );
+						pVec = Script::GetVector( CRCD(0x0767f02b,"CutsceneScaleTestHat") );
+						testBoneScales[3] = Mth::Vector( pVec->mX, pVec->mY, pVec->mZ, 1.0f );
+						pVec = Script::GetVector( CRCD(0xba314cf8,"CutsceneScaleTestBrow") );
+						testBoneScales[4] = Mth::Vector( pVec->mX, pVec->mY, pVec->mZ, 1.0f );
+					}
+					else
+#endif
+					{
+						// get it from the profile
+						Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+						Gfx::CModelAppearance* pCASModelAppearance = pSkaterProfile->GetAppearance();
+						Script::CStruct* pCASModelStruct = pCASModelAppearance->GetStructure();
+
+						testBoneScales[0] = get_scale( pCASModelStruct, CRCD(0xa28fab7f,"head_bone_group") );
+						testBoneScales[1] = get_scale( pCASModelStruct, CRCD(0xa28fab7f,"head_bone_group") );
+						testBoneScales[2] = get_scale( pCASModelStruct, CRCD(0x0442d3c1,"jaw_bone_group") );
+						testBoneScales[3] = get_scale( pCASModelStruct, CRCD(0xb90f08d0,"headtop_bone_group") );
+						testBoneScales[4] = get_scale( pCASModelStruct, CRCD(0x1dbcfae8,"nose_bone_group") );
+					}
+
+					// skip past version number
+					pWeightMapData += sizeof(int); 
+
+					int numVerts = *(int*)pWeightMapData;
+					pWeightMapData += sizeof(int);
+
+	    			theParams.pWeights = (float*)pWeightMapData;
+					theParams.pWeightIndices = (char*)(pWeightMapData + (numVerts * 3 * sizeof(float)));
+					
+					//theParams.pWeights = &testWeights[0];
+					//theParams.pWeightIndices = &testWeightIndices[0];
+					theParams.pBonePositions = &testBonePositions[0];
+					theParams.pBoneScales = &testBoneScales[0];
+
+					// it's a cutscene head, so only load the model if it's the head that 
+					// we're going to need in the cutscene...  the 4 cutscene heads are always
+					// ordered male/nonfacemapped, male/facemapped, female/nonfacemapped, 
+					// female/facemapped, so we should be able to figure out which head
+					// is which depending on what other files exist...
+					int is_male_head = ( mp_fileLibrary->GetFileInfo( pFileInfo->fileNameChecksum + 2, CRCD(0xfd8697e1,"SKIN"), false ) != NULL );
+					int is_facemapped_head;
+					if ( is_male_head )
+					{
+						is_facemapped_head = ( mp_fileLibrary->GetFileInfo( pFileInfo->fileNameChecksum - 1, CRCD(0xfd8697e1,"SKIN"), false ) != NULL );
+					}
+					else
+					{
+						is_facemapped_head = ( mp_fileLibrary->GetFileInfo( pFileInfo->fileNameChecksum + 1, CRCD(0xfd8697e1,"SKIN"), false ) == NULL );
+					}
+
+					int is_male_skater;
+					int is_facemapped_skater;
+					Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+					Gfx::CModelAppearance* pCASModelAppearance = pSkaterProfile->GetAppearance();
+					Gfx::CFaceTexture* pCASFaceTexture = pCASModelAppearance->GetFaceTexture();
+					is_facemapped_skater = ( pCASFaceTexture && pCASFaceTexture->IsValid() );					
+					Script::CStruct* pInfo = pSkaterProfile->GetInfo();
+					pInfo->GetInteger( CRCD(0x3f813177,"is_male"), &is_male_skater, Script::ASSERT );
+
+					// only load the correct head from the 4 cutscene heads
+					should_load_asset = ( ( is_facemapped_head == is_facemapped_skater ) && ( is_male_head == is_male_skater) );
+				
+					if ( should_load_asset )
+					{
+						// only want to set the parameters if we're sure we're going to be loading the asset...
+						Nx::CEngine::sSetMeshScalingParameters( &theParams );
+					}
+				}
+
+				if ( should_load_asset )
+				{
+					pAssMan->LoadAssetFromStream( model_asset_name, 
+												  Ass::ASSET_SKIN, 
+												  (uint32*)&theInfo, 
+												  sizeof(Ass::SCutsceneModelDataInfo), 
+												  permanent,
+												  group);
+
+					// keep track of which assets are loaded, 
+					// so that we know to remove it in the destructor...
+					Dbg_MsgAssert( m_numAssets < vMAX_CUTSCENE_ASSETS, ( "Too many assets (increase vMAX_CUTSCENE_ASSETS > %d)", vMAX_CUTSCENE_ASSETS ) );
+					m_assetName[m_numAssets] = model_asset_name;
+					m_numAssets++;
+				}
+				
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, pFileInfo->fileExtensionChecksum );
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, CRCD(0x1512808d,"TEX") );
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, CRCD(0xffc529f4,"CAS") );
+				mp_fileLibrary->ClearFile( pFileInfo->fileNameChecksum, CRCD(0x2cd4107d,"WGT") );
+		  }
+			break;
+/*
+			case 0xedd8d75f:	// SKE
+				{
+					char skeleton_asset_filename[512];
+					sprintf( skeleton_asset_filename, "%08x", pFileInfo->fileNameChecksum + pFileInfo->fileExtensionChecksum );
+					uint32 skeleton_asset_name = pFileInfo->fileNameChecksum + pFileInfo->fileExtensionChecksum;
+					Dbg_Message( "Adding asset %08x", skeleton_asset_name );
+						
+					// set up some default parameters
+					bool permanent = false;
+					int group = 0;
+					 
+					// these file types get added to the asset manager
+					// (if they don't already exist)...  remember to remove
+					// them when the cutscene is done!
+					Ass::CAssMan* pAssMan = Ass::CAssMan::Instance();
+					pAssMan->LoadAssetFromStream( skeleton_asset_name, 
+												  Ass::ASSET_SKELETON, 
+												  (uint32*)pData, 
+												  pFileInfo->fileSize, 
+												  permanent,
+												  group);
+
+					// keep track of which assets are loaded, 
+					// so that we know to remove it in the destructor...
+					Dbg_MsgAssert( m_numAssets < vMAX_CUTSCENE_ASSETS, ( "Too many assets (increase vMAX_CUTSCENE_ASSETS > %d)", vMAX_CUTSCENE_ASSETS ) );
+					m_assetName[m_numAssets] = skeleton_asset_name;
+					m_numAssets++;
+				}
+				break;
+*/
+
+			case 0x5ac14717:	// CIF
+				// do nothing;  cif packet is handled during a separate step
+				break;
+
+			case 0x1512808d:	// TEX
+				// do nothing;  loading of texture dictionaries is handled
+				// automatically by the loading of models/skins
+				break;
+
+			case 0xffc529f4:	// CAS
+				// do nothing;  loading of CAS flags are handled
+				// automatically by the loading of models/skins
+				break;
+
+			case 0x2cd4107d:	// WGT
+				// do nothing;  loading of mesh scaling weight maps are handled
+				// automatically by the loading of models/skins
+				break;
+			
+			default:
+				// file type ignored on this pass
+				break;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::init_objects()
+{
+	// now create the objects once the assets have been loaded
+	uint32* pCIFData = mp_fileLibrary->GetFileData( 0, CRCD(0x5ac14717,"CIF"), true );
+	Dbg_Assert( pCIFData );
+	create_objects( pCIFData );
+	mp_fileLibrary->ClearFile( 0, CRCD(0x5ac14717,"CIF") );
+
+#ifdef __NOPT_ASSERT__
+	/*
+	can't do this test any more, because we're clearing out unused data
+	
+	// check for valid skeleton, should be moved to the exporter
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+		SCutsceneObjectInfo* pObjectInfo = &m_objects[i];
+		
+		if ( pObjectInfo->m_doPlayerSubstitution )
+		{
+			// no checks
+		}
+		else if ( pObjectInfo->m_skeletonName == 0 )
+		{
+			// if the object has no skeleton, make sure that this is an MDL, and not a SKIN
+			if ( mp_fileLibrary->GetFileData( pObjectInfo->m_objectName, Crc::GenerateCRCFromString("SKIN"), false ) )
+			{
+				Dbg_MsgAssert( 0, ( "No skeleton specified for object %08x, even though it was listed as a SKIN file", pObjectInfo->m_objectName ) );
+			}
+		}
+		else
+		{
+			if ( !mp_fileLibrary->GetFileData( pObjectInfo->m_objectName, Crc::GenerateCRCFromString("SKIN"), false ) )
+			{
+				Dbg_MsgAssert( 0, ( "Skeleton specified for object %08x, even though it was not listed as a SKIN file", pObjectInfo->m_objectName ) );
+			}
+		}
+	}
+	*/
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneData::PostLoad( bool assertOnFail )
+{
+	Dbg_MsgAssert( mp_fileLibrary, ( "No file library?!?" ) );
+
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().CutsceneBottomUpHeap() );
+
+	load_models();
+	init_objects();
+	
+	Mem::Manager::sHandle().PopContext();
+
+	delete mp_fileLibrary;
+	mp_fileLibrary = NULL;
+
+	//-------------------------------------------------------------
+	// There's a blue glitch that appears for a few frames while
+	// the cutscene manager waits for the stream to be preloaded.
+	// The following loop will ensure that the stream is ready
+	// to go before the video starts...
+
+#ifndef __PLAT_NGC__
+	while ( !m_audioStarted )
+	{
+		Pcm::Update();
+		
+		if ( Pcm::PreLoadMusicStreamDone() )
+		{
+			// TODO:  Fill in with appropriate music volume parameter?
+			Pcm::StartPreLoadedMusicStream();
+
+			m_audioStarted = true;
+			break;
+		}
+
+		Tmr::VSync();
+	}
+#endif		// __PLAT_NGC__
+	//-------------------------------------------------------------
+	
+	return true;
+}
+			   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::add_anim( uint32 animAssetName, uint32* pData, int fileSize )
+{
+	// set up some default parameters
+	bool permanent = false;
+	int group = 0;
+	uint32 anim_asset_name = animAssetName;
+					 
+	// these file types get added to the asset manager
+	// (if they don't already exist)...  remember to remove
+	// them when the cutscene is done!
+	Ass::CAssMan* pAssMan = Ass::CAssMan::Instance();
+	pAssMan->LoadAssetFromStream( anim_asset_name, 
+								  Ass::ASSET_ANIM, 
+								  pData,
+								  fileSize,
+								  permanent,
+								  group);
+
+	// keep track of which assets are loaded, 
+	// so that we know to remove it in the destructor...
+	Dbg_MsgAssert( m_numAssets < vMAX_CUTSCENE_ASSETS, ( "Too many assets (increase vMAX_CUTSCENE_ASSETS > %d)", vMAX_CUTSCENE_ASSETS ) );
+	m_assetName[m_numAssets] = anim_asset_name;
+	m_numAssets++;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::add_boned_anim( uint32 animName, uint32* pData, int fileSize )
+{
+	Dbg_MsgAssert( m_numBonedAnims < vMAX_CUTSCENE_ANIMS, ( "Too many anims in cutscene (increase vMAX_CUTSCENE_ANIMS > %d).", vMAX_CUTSCENE_ANIMS ) );
+
+	SCutsceneAnimInfo* pAnimInfo = &m_bonedAnims[m_numBonedAnims];
+
+	Dbg_MsgAssert( !get_anim_info( animName ), ( "Anim already exists %s", Script::FindChecksumName(animName) ) );
+
+	add_anim( animName, pData, fileSize );
+
+#ifdef __PLAT_NGC__
+	int size = sizeof( Nx::CQuickAnim );
+	int mem_available;
+	bool need_to_pop = false;
+	if ( g_in_cutscene )
+	{
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+		mem_available = Mem::Manager::sHandle().Available();
+		if ( size < ( mem_available - ( 40 * 1024 ) ) )
+		{
+			need_to_pop = true;
+		}
+		else
+		{
+			Mem::Manager::sHandle().PopContext();
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( size < ( mem_available - ( 5 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( size < ( mem_available - ( 40 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+				}
+			}
+		}
+	}
+#endif	// __PLAT_NGC__
+
+	Nx::CQuickAnim* pBonedQuickAnim = new Nx::CQuickAnim;
+
+#ifdef __PLAT_NGC__
+	if ( need_to_pop )
+	{
+		Mem::Manager::sHandle().PopContext();
+	}
+#endif	// __PLAT_NGC__
+
+	pBonedQuickAnim->SetAnimAssetName( animName );
+	pAnimInfo->mp_bonedQuickAnim = pBonedQuickAnim;
+	
+	pAnimInfo->m_animName = animName;
+
+//	Dbg_Message( "Boned anim 0x%08x has %d bones", animName, pBonedQuickAnim->GetNumBones() );
+
+	m_numBonedAnims++;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SCutsceneObjectInfo* CCutsceneData::get_object_info( uint32 objectName )
+{
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+		if ( m_objects[i].m_objectName == objectName )
+		{
+			return &m_objects[i];
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+SCutsceneAnimInfo* CCutsceneData::get_anim_info( uint32 animName )
+{
+	for ( int i = 0; i < m_numBonedAnims; i++ )
+	{
+		if ( m_bonedAnims[i].m_animName == animName )
+		{
+			return &m_bonedAnims[i];
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Obj::CCompositeObject* create_cutscene_dummy_object( Script::CStruct* pNodeData )
+{  
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	CGeneralManager* p_obj_man = skate_mod->GetObjectManager();
+
+	Obj::CCompositeObject* pDummyObject = new CCompositeObject;
+	
+	pDummyObject->SetType( SKATE_TYPE_COMPOSITE );
+	
+	p_obj_man->RegisterObject(*pDummyObject);
+	
+	Script::RunScript( CRCD(0x1f29c5a5,"cutsceneobj_add_components"), pNodeData, pDummyObject );
+	
+	pDummyObject->Finalize();
+
+	pDummyObject->m_pos = Mth::Vector( 0.0f, 0.0f, 0.0f, 1.0f );
+
+	return pDummyObject;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void apply_cas_scaling( Obj::CCompositeObject* pObject, Gfx::CSkeleton* pSourceSkeleton )
+{
+	Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( pObject );
+
+	if ( pSkeletonComponent )
+	{
+		Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pObject );
+		if ( pModelComponent->HasRefObject() )
+		{
+			Gfx::CSkeleton* pSkeleton = pSkeletonComponent->GetSkeleton();
+			Dbg_Assert( pSkeleton );
+
+			pSkeleton->CopyBoneScale( pSourceSkeleton );
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneData::create_objects( uint32* pData )
+{
+	// skip the version number
+	pData++;
+
+	// get the number of objects
+	int numObjects = *pData;
+	pData++;
+
+	Dbg_Message("Found %d objects in the CIF file", numObjects);
+
+	for ( int i = 0; i < numObjects; i++ )
+	{
+//		Dbg_Message( "Adding cutscene object #%d:", i );
+		
+		// remember the objectName
+		uint32 objectName = *pData;
+//		Dbg_Message( "objectName = %08x", *pData );
+		pData++;
+
+		// get the model name
+		uint32 modelName = *pData;
+		uint32 modelAssetName;
+//		Dbg_Message( "modelName = %08x", *pData );
+		modelAssetName = *pData + Script::GenerateCRC("SKIN");
+//		Dbg_Message( "munged modelAssetName = %08x", modelAssetName );
+		pData++;
+
+		// get the skeleton name
+		uint32 skeletonName;
+		skeletonName = *pData;
+		pData++;
+
+		// get the anim name (the associated SKA file
+		// which should be found somewhere in this CUT file)
+		uint32 animName;
+		animName = *pData;
+		pData++;
+
+		// get any extra flags
+		uint32 flags;
+		flags = *pData;
+		pData++;
+
+		Script::CStruct* pNodeData = new Script::CStruct;
+										
+		// This substitutes the	given model with the current player model
+		// TODO:  Eventually phase out the name "skater"
+		bool do_player_substitution = ( modelName == CRCD(0x5b8ab877,"skater") || modelName == CRCD(0x67e6859a,"player") );
+		if ( do_player_substitution )
+		{
+			uint32 refObjectName = 0;
+			pNodeData->AddChecksum( CRCD(0x153a84de,"refObjectName"), refObjectName );
+		}
+		else
+		{
+			// if it's not a skater model, then check for correct skeleton/anims
+			if ( !mp_fileLibrary->FileExists( modelName, CRCD(0xfd8697e1,"SKIN") ) )
+			{
+//				Dbg_MsgAssert( skeletonName == 0, ( "Non-skinned model %08x has skeleton name", objectName ) );
+				skeletonName = 0;
+//				Dbg_MsgAssert( animName == 0, ( "Non-skinned model %08x has anim name", objectName ) );
+				animName = 0;
+			}
+			else
+			{
+				Dbg_MsgAssert( skeletonName != 0, ( "Skinned model %08x has no skeleton name", objectName ) );
+				Dbg_MsgAssert( animName != 0, ( "Skinned model %08x has no anim name", objectName ) );
+			}
+
+			// if it's the skater's head model and the player has a face texture,
+			// then we need to swap in the eyeball-less head model...
+			bool is_head_model = flags & vCUTSCENEOBJECTFLAGS_ISHEAD;
+			bool is_skater_model = flags & vCUTSCENEOBJECTFLAGS_ISSKATER;
+			if ( is_head_model && is_skater_model )
+			{
+				Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+				Gfx::CModelAppearance* pCASModelAppearance = pSkaterProfile->GetAppearance();
+				Gfx::CFaceTexture* pCASFaceTexture = pCASModelAppearance->GetFaceTexture();
+				if ( pCASFaceTexture && pCASFaceTexture->IsValid() )
+				{
+					Dbg_MsgAssert( mp_fileLibrary->FileExists( modelName + 1, CRCD(0xfd8697e1,"SKIN") ), ( "Couldn't find eyeball-less head model" ) );
+
+					// eyeball-less head model checksum = regular head model checksum + 1
+					modelAssetName += 1;
+				}
+
+				// Load up the correct head, based on the skater's sex
+				Script::CStruct* pInfo = pSkaterProfile->GetInfo();
+				int is_male = 0;
+				pInfo->GetInteger( CRCD(0x3f813177,"is_male"), &is_male, Script::ASSERT );
+				if ( !is_male )
+				{
+					// female eyeball = male eyeball + 2
+					// female eyeball-less = male eyeball-less + 2
+					modelAssetName += 2;
+				}
+
+				// this will allow changing facial hair color (needed only for males)
+				pNodeData->AddInteger( CRCD(0x92b43e79,"multicolor"), is_male );
+			}
+			
+			pNodeData->AddChecksum( CRCD(0x86bd5b8f,"assetName"), modelAssetName );
+		}
+
+		// if you've got the special-case skull head,
+		// then we don't want to subsitute the hi-res skeleton
+		{
+			bool is_head_model = flags & vCUTSCENEOBJECTFLAGS_ISHEAD;
+			bool is_skater_model = flags & vCUTSCENEOBJECTFLAGS_ISSKATER;
+			if ( is_head_model && is_skater_model )
+			{
+				Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+				Gfx::CModelAppearance* pCASModelAppearance = pSkaterProfile->GetAppearance();
+
+				Script::CStruct* pHeadStruct = NULL;
+				pHeadStruct = pCASModelAppearance->GetActualDescStructure( CRCD(0x650fab6d,"skater_m_head") );
+				if ( !pHeadStruct )
+				{
+					pHeadStruct = pCASModelAppearance->GetActualDescStructure( CRCD(0x0fc85bae,"skater_f_head") );
+				}
+				if ( pHeadStruct && pHeadStruct->ContainsFlag( CRCD(0xc4c5b2cc,"NoCutsceneHead") ) )
+				{
+					Script::RunScript( CRCD(0xf37d9f53,"UnhideLoResHeads") );
+					delete pNodeData;
+					continue;
+				}
+
+				// another special case for the paper bag...
+				Script::CStruct* pHatStruct = NULL;
+				pHatStruct = pCASModelAppearance->GetActualDescStructure( CRCD(0x6df453b6,"hat") );
+				if ( pHatStruct && pHatStruct->ContainsFlag( CRCD(0xc4c5b2cc,"NoCutsceneHead") ) )
+				{
+					Script::RunScript( CRCD(0xf37d9f53,"UnhideLoResHeads") );
+					delete pNodeData;
+					continue;
+				}
+#if 0				
+				Script::RunScript( CRCD(0xf37d9f53,"UnhideLoResHeads") );
+				delete pNodeData;
+				continue;
+#endif
+			}
+		}
+
+//		Dbg_Message( "skeletonName = %08x", skeletonName );
+//		Dbg_Message( "animName = %08x", animName );
+		
+		// TODO:  if the skeleton asset were included
+		// in the CUT file, then we wouldn't need the
+		// artists to fill in this field manually:	
+		if ( skeletonName )
+		{
+			pNodeData->AddChecksum( CRCD(0x09794932,"skeletonName"), skeletonName );
+
+			// this disables the "bone skip list" optimization,
+			// which would otherwise make the cutscene object's
+			// hands disappear
+			pNodeData->AddInteger( CRCD(0xd3982061,"max_bone_skip_lod"), 0 );
+		}
+
+		// shouldn't have an animation component, because
+		// we want the cutscene details to fill up the skeleton
+		// with the appropriate data (we don't want the
+		// animation component's update() function to get
+		// called once per frame and wipe out the cutscene
+		// details' changes
+		//pNodeData->AddChecksum( CRCD(0x6c2bfb7f,"animName"), anim_name );
+
+		SCutsceneObjectInfo* pObjectInfo = &m_objects[m_numObjects];
+
+		Dbg_MsgAssert( m_numObjects < vMAX_CUTSCENE_OBJECTS, ( "Too many objects in cutscene (increase vMAX_CUTSCENE_OBJECTS > %d).", vMAX_CUTSCENE_OBJECTS ) );
+		pObjectInfo->mp_object = create_cutscene_dummy_object( pNodeData ); 
+
+		// this	prevents the skateboard from clipping out
+		// when it gets far enough away from the skater
+		// (like in the NJ pool cutscene) 
+		Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pObjectInfo->mp_object );
+		if ( pModelComponent )
+		{
+			Nx::CModel* pModel = pModelComponent->GetModel();
+			Dbg_Assert( pModel );
+			pModel->SetBoundingSphere( Mth::Vector(0.0f,0.0f,0.0f,50000.0f) );
+
+	//		printf( "pModel has %d bones, transforms = %p\n", pModel->GetNumBones(), pModel->GetBoneTransforms() );
+
+			if ( pModelComponent->HasRefObject() )
+			{
+				uint32 refObjectName = pModelComponent->GetRefObjectName();
+				Obj::CCompositeObject* pRefObject = (Obj::CCompositeObject*)Obj::ResolveToObject( refObjectName );
+				Dbg_Assert( pRefObject );
+				Obj::CModelComponent* pRefModelComponent = GetModelComponentFromObject( pRefObject );
+				Dbg_Assert( pRefModelComponent );
+				Nx::CModel* pRefModel = pRefModelComponent->GetModel();
+				
+				// "m_original_params_set" ensures that we don't try
+				// to write the same data out multiple times if there
+				// are multiple create-a-skaters (which happens in the
+				// final.cut cutscene)
+				if ( !m_original_params_set )
+				{
+					m_original_player_bounding_sphere = pRefModel->GetBoundingSphere();
+					m_original_hidden = pRefModel->IsHidden();
+					m_original_scale_enabled = pRefModel->IsScalingEnabled();
+					m_original_pos = pRefObject->GetPos();
+					m_original_params_set = true;
+				}
+				
+				pRefModel->SetBoundingSphere( Mth::Vector(0.0f,0.0f,0.0f,50000.0f) );
+				pRefModel->Hide( false );
+				pRefModel->EnableScaling( false );
+
+				pRefModelComponent->UpdateBrightness();
+			}
+
+			// don't run the normal update() function,
+			// or else it will screw up the double-buffer
+			pModelComponent->Suspend(true);
+		}
+
+		pObjectInfo->m_objectName = objectName;
+
+		// set the id, so that we can run scripts on it
+		Dbg_MsgAssert( !Obj::ResolveToObject(objectName), ( "Object with name %s already exists", Script::FindChecksumName(objectName) ) );
+		pObjectInfo->mp_object->SetID( objectName );
+
+		// for debugging...
+		pObjectInfo->m_skeletonName = skeletonName;
+		pObjectInfo->m_doPlayerSubstitution = do_player_substitution;
+		pObjectInfo->m_flags = flags;
+
+		pObjectInfo->mp_animInfo = NULL;
+		pObjectInfo->mp_parentObject = NULL;
+		pObjectInfo->m_obaIndex = -1;
+
+		// the animation doesn't necessarily exist for all the objects
+		if ( animName )
+		{
+			// enforce that the anim with that name exists,
+			pObjectInfo->mp_animInfo = get_anim_info( animName );
+			Dbg_MsgAssert( pObjectInfo->mp_animInfo, ( "Couldn't find SKA with id 0x%08x in LIB file", animName ) );
+		}
+		
+//		Dbg_Message( "---------------" );
+
+		m_numObjects++;
+
+		// done with temp struct
+		delete pNodeData;
+	}
+
+	// apply cas scaling to the appropriate parts
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+		SCutsceneObjectInfo* pObjectInfo = &m_objects[i];
+
+		if ( pObjectInfo->m_doPlayerSubstitution )
+		{
+			Obj::CModelComponent* pModelComponent = GetModelComponentFromObject(pObjectInfo->mp_object);
+			Dbg_Assert( pModelComponent );
+			
+			uint32 refObjectName = pModelComponent->GetRefObjectName();
+			Obj::CCompositeObject* pRefObject = (Obj::CCompositeObject*)Obj::ResolveToObject( refObjectName );
+			Dbg_Assert( pRefObject );
+			
+			// body
+			Obj::CSkeletonComponent* pRefSkeletonComponent = GetSkeletonComponentFromObject(pRefObject);
+			Dbg_Assert( pRefSkeletonComponent );
+			apply_cas_scaling( pObjectInfo->mp_object, pRefSkeletonComponent->GetSkeleton() );
+		}
+		
+		SCutsceneObjectInfo* pParentObjectInfo = get_object_info( pObjectInfo->m_objectName - 1 );
+		if ( pParentObjectInfo && pParentObjectInfo->m_doPlayerSubstitution )
+		{
+			Obj::CModelComponent* pModelComponent = GetModelComponentFromObject(pParentObjectInfo->mp_object);
+			Dbg_Assert( pModelComponent );
+			
+			uint32 refObjectName = pModelComponent->GetRefObjectName();
+			Obj::CCompositeObject* pRefObject = (Obj::CCompositeObject*)Obj::ResolveToObject( refObjectName );
+			Dbg_Assert( pRefObject );
+			
+			// head
+			Obj::CSkeletonComponent* pRefSkeletonComponent = GetSkeletonComponentFromObject(pRefObject);
+			Dbg_Assert( pRefSkeletonComponent );
+			apply_cas_scaling( pObjectInfo->mp_object, pRefSkeletonComponent->GetSkeleton() );
+		}
+	}
+
+	// do basic poly removal on all objects
+	// w/ some extra poly removal on the player's head
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+		SCutsceneObjectInfo* pObjectInfo = &m_objects[i];
+
+		Obj::CModelComponent* pModelComponent = GetModelComponentFromObject(pObjectInfo->mp_object);
+		if ( pModelComponent )
+		{
+			Nx::CModel* pModel = pModelComponent->GetModel();
+
+			uint32 polyRemovalMask = pModel->GetPolyRemovalMask();
+
+			SCutsceneObjectInfo* pParentObjectInfo = get_object_info( pObjectInfo->m_objectName - 1 );
+			if ( pParentObjectInfo && pParentObjectInfo->m_doPlayerSubstitution )
+			{
+				// treat the player's head as a special case
+				// because we need to grab the poly removal
+				// mask from the player's body
+
+				Obj::CModelComponent* pModelComponent = GetModelComponentFromObject(pParentObjectInfo->mp_object);
+				Dbg_Assert( pModelComponent );
+
+				uint32 refObjectName = pModelComponent->GetRefObjectName();
+				Obj::CCompositeObject* pRefObject = (Obj::CCompositeObject*)Obj::ResolveToObject( refObjectName );
+				Dbg_Assert( pRefObject );
+
+				Obj::CModelComponent* pRefModelComponent = GetModelComponentFromObject(pRefObject);
+				Dbg_Assert( pRefModelComponent );
+
+				Nx::CModel* pRefModel = pRefModelComponent->GetModel();
+				Dbg_Assert( pRefModel );
+
+				// head
+				polyRemovalMask |= pRefModel->GetPolyRemovalMask();
+			}
+
+			pModel->HidePolys( polyRemovalMask );
+		}
+	}
+
+	// relink parents if necessary
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+		SCutsceneObjectInfo* pObjectInfo = &m_objects[i];
+
+		// checks for parents, if any...  based on naming convention
+		SCutsceneObjectInfo* pParentObjectInfo = get_object_info( pObjectInfo->m_objectName - 1 );
+		if ( pParentObjectInfo )
+		{
+			pObjectInfo->mp_parentObject = pParentObjectInfo->mp_object;
+
+			if ( pParentObjectInfo->m_doPlayerSubstitution )
+			{
+				// it's the hires create-a-skater head,
+				// so need to do some extra texture	replacement on it
+				Obj::CModelComponent* pCASModelComponent = GetModelComponentFromObject(pObjectInfo->mp_object);
+				Dbg_Assert( pCASModelComponent );
+				
+				Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+				Gfx::CModelAppearance* pCASModelAppearance = pSkaterProfile->GetAppearance();
+
+				pCASModelComponent->InitModelFromProfile(pCASModelAppearance,false,0,CRCD(0x8fb01036,"replace_cutscene_skater_from_appearance"));
+			
+				// apply color from the skater appearance
+				// (we can't do this in replace_cutscene_skater_from_appearance
+				// because it might have an object color to set, which you can
+				// only do if you have the correct geom name)
+				Script::CStruct* pHeadStruct = NULL;
+
+				pCASModelAppearance->PrintContents();
+
+				pHeadStruct = pCASModelAppearance->GetVirtualDescStructure( CRCD(0x650fab6d,"skater_m_head") );
+				
+				if ( !pHeadStruct )
+				{
+					pHeadStruct = pCASModelAppearance->GetVirtualDescStructure( CRCD(0x0fc85bae,"skater_f_head") );
+				}
+				
+				if ( pHeadStruct )
+				{
+					Nx::CModel* pCASModel = pCASModelComponent->GetModel();
+					Dbg_Assert( pCASModel );
+
+					int use_default_hsv = 1;
+					pHeadStruct->GetInteger( CRCD(0x97dbdde6,"use_default_hsv"), &use_default_hsv );
+					if ( !use_default_hsv )
+					{
+						int h, s, v;
+						if ( pHeadStruct->GetInteger( CRCD(0x6e94f918,"h"), &h, false )
+							 && pHeadStruct->GetInteger( CRCD(0xe4f130f4,"s"), &s, false )
+							 && pHeadStruct->GetInteger( CRCD(0x949bc47b,"v"), &v, false ) )
+						{
+							Script::CStruct* pTempStruct = new Script::CStruct;
+							pTempStruct->AddChecksum( CRCD(0xb6f08f39,"part"), 0 );
+							pTempStruct->AddChecksum( CRCD(0x83418a6a,"material"), CRCD(0xaa2206ef,"cashead_head") );
+							pTempStruct->AddInteger( CRCD(0x318f2bdb,"pass"), 0 );
+							pCASModel->SetColor( pTempStruct, (float)h, (float)s / 100.0f, (float)v / 100.0f );
+							delete pTempStruct;
+						}
+					}
+				}
+			}
+		}
+		
+		Dbg_Assert( mp_objectQuickAnim );
+		for ( int j = 0; j < mp_objectQuickAnim->GetNumBones(); j++ )
+		{
+			uint32 obaObjectName = mp_objectQuickAnim->GetBoneName(j);
+			if ( pObjectInfo->m_objectName == obaObjectName )
+			{
+				pObjectInfo->m_obaIndex	= j;
+				break;
+			}
+		}
+	}
+
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+char* get_cut_name( const char* p_fileName )
+{
+	static char cutsceneName[256];
+
+	for ( int i = strlen(p_fileName); i >= 0; i-- )
+	{
+		if ( p_fileName[i] == '\\' || p_fileName[i] == '/' )
+		{
+			strcpy( cutsceneName, &p_fileName[i+1] );
+			break;
+		}
+	}
+
+	// strip off extension, if one exists
+	char* pExt = strstr( cutsceneName, "." );
+	if ( pExt )
+	{
+		*pExt = NULL;
+	}
+
+	return cutsceneName;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneData::Load( const char* p_fileName, bool assertOnFail, bool async_load )
+{   
+	// now that the async code is working, 
+	// there's no reason to load it synchronously
+	async_load = true;
+
+	// GJ:  Disabled async load to remove
+	// the glitch when starting cutscenes
+	async_load = false;
+	
+	// ...  but turn off async for platforms that
+	// don't support it	anyway
+	if ( !File::CAsyncFileLoader::sAsyncSupported() )
+	{
+		async_load = false;
+	}
+	
+	// based on the name of the CUT file, 
+	// we can build the name of the stream associated with it
+	char* pCutsceneName = get_cut_name( p_fileName );
+
+	//-------------------------------------------------------------
+	// Load up the correct stream, based on the skater's sex
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( 0 );
+	Script::CStruct* pInfo = pSkaterProfile->GetInfo();
+	int is_male = 0;
+	pInfo->GetInteger( CRCD(0x3f813177,"is_male"), &is_male, Script::ASSERT );
+	
+	char streamName[256];
+	if ( is_male )
+	{
+		sprintf( streamName, "%s_Male", pCutsceneName );
+	}
+	else
+	{
+		sprintf( streamName, "%s_Female", pCutsceneName );
+	}
+	
+	uint32 streamNameChecksum;
+	streamNameChecksum = Crc::GenerateCRCFromString(streamName);
+
+#ifndef __PLAT_NGC__
+	if ( !Pcm::PreLoadMusicStream( streamNameChecksum ) )
+	{
+		Dbg_Message( "Couldn't start stream...  stream %s not found?", streamName );
+		m_audioStarted = true;
+	}
+#endif		// __PLAT_NGC__
+	//-------------------------------------------------------------
+
+	//-------------------------------------------------------------
+	// Create a cutscene heap here...  it will only be used for the 
+	// cutscene, and will prevent fragmentation on the bottom up heap
+	// by things that might get allocated during the cutscene,
+	// such as proxim nodes.
+	Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().TopDownHeap() );
+		
+	// reserve about 100K for any allocations that need to go on the 
+	// bottom up heap (such as proxim nodes).  Also need to reserve
+	// some room on the topdown heap for texture dictionary loading
+	// on the PS2
+	int mem_available = Mem::Manager::sHandle().Available();
+#	if defined( __PLAT_NGC__ )
+#ifdef DVDETH
+	mem_available -= 6 * 1024 * 1024;
+#else
+#ifdef __NOPT_ASSERT__
+	mem_available = 3 * 1024 * 1024;
+#else
+
+# define toupper(c) ( ( (c) >= 'a' ) && ( (c) <= 'z' ) ) ? (c) += ( 'A' - 'a' ) : (c)
+	char name[64];
+	char * ps = (char *)p_fileName;
+	char * pd = name;
+	while ( *ps != '\0' )
+	{
+		*pd = toupper( *ps );
+		ps++;
+		pd++;
+	}
+
+	if ( strstr( name, "FL_03" ) )
+	{
+		mem_available -= 200 * 1024;
+		OSReport( "******* Reducing by 200k\n" );
+	}
+	else
+	{
+		if ( strstr( name, "NY_01V" ) )
+		{
+			mem_available -= 100 * 1024;
+			OSReport( "******* Reducing by 80k\n" );
+		}
+		else
+		{
+			if ( strstr( name, "NY_02" ) )
+			{
+				mem_available -= 80 * 1024;
+				OSReport( "******* Reducing by 80k\n" );
+			}
+			else
+			{
+				mem_available -= 120 * 1024;
+				OSReport( "******* Reducing by 120k\n" );
+			}
+		}
+	}
+#endif		// __NOPT_ASSERT__ 
+#endif		// DVDETH
+#	elif defined( __PLAT_XBOX__ )
+	// Reserve more for Xbox since audio streams will be allocated from bottom up heap.
+	mem_available -= 512 * 1024;
+#	else
+	mem_available -= 200 * 1024;
+#	endif		// __PLAT_NGC__
+
+#ifdef __PLAT_NGC__
+		if ( mem_available < ( 150 * 1024 ) )
+		{
+			// If heap is too small, allocate a 200k heap from the theme region.
+			Mem::Manager::sHandle().PopContext();
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+			mem_available = 150 * 1024;
+		}
+#endif
+
+	Mem::Manager::sHandle().InitCutsceneHeap( mem_available );
+	Mem::Manager::sHandle().PopContext(); 
+	//-------------------------------------------------------------
+#	if defined( __PLAT_NGC__ )
+	g_in_cutscene = true;
+#	endif		// __PLAT_NGC__
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneBottomUpHeap());	
+	Dbg_MsgAssert( !mp_fileLibrary, ( "File library already exists" ) );
+	mp_fileLibrary = new File::CFileLibrary;
+	Dbg_MsgAssert( mp_fileLibrary, ( "Couldn't create file library %s", p_fileName ) );
+	Mem::Manager::sHandle().PopContext();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneTopDownHeap());	
+
+	bool success = mp_fileLibrary->Load( p_fileName, assertOnFail, async_load );
+	
+	if ( !success )
+	{
+		Dbg_MsgAssert( 0, ( "Couldn't load file library %s", p_fileName ) );
+	}
+	
+	Mem::Manager::sHandle().PopContext();
+
+	if ( !async_load )
+	{
+		PostLoad( assertOnFail );
+		m_dataLoaded = true;
+	}
+
+#ifdef __PLAT_NGC__
+	if ( !Pcm::PreLoadMusicStream( streamNameChecksum ) )
+	{
+		Dbg_Message( "Couldn't start stream...  stream %s not found?", streamName );
+		m_audioStarted = true;
+	}
+
+	while ( !m_audioStarted )
+	{
+		Pcm::Update();
+		
+		if ( Pcm::PreLoadMusicStreamDone() )
+		{
+			// TODO:  Fill in with appropriate music volume parameter?
+			Pcm::StartPreLoadedMusicStream();
+
+			m_audioStarted = true;
+			break;
+		}
+
+		Tmr::VSync();
+	}
+#endif		// __PLAT_NGC__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneData::LoadFinished()
+{
+	if ( m_dataLoaded )
+	{
+		// already finished before this...
+		return true;
+	}
+
+	// check to see whether the file has finished loading
+	if ( !mp_fileLibrary->LoadFinished() )
+	{
+		Dbg_Message( "Waiting for async load to finish..." );
+		return false;
+	}
+	else
+	{
+		PostLoad( true );
+		m_dataLoaded = true;
+		return true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+										    
+void CCutsceneData::update_video_playback( Gfx::Camera* pCamera, Script::CStruct* pStruct )
+{
+	if ( !m_videoStarted )
+	{
+		// need to pass in the fadeintime
+//		Script::SpawnScript( CRCD(0x55665639,"FadeInCutscene"), pStruct );
+		m_videoStarted = true;
+		
+		Script::RunScript( CRCD(0xe794a164,"CutsceneStartVideo") );
+
+		// initialize the camera (don't really need to do this,
+		// since the camera animation will override it)
+		Dbg_Assert( pCamera );
+//		Mth::Vector& camPos = pCamera->GetPos();
+//		Mth::Matrix& camMatrix = pCamera->GetMatrix();
+//		camPos = Mth::Vector(0.0f,0.0f,0.0f,1.0f);
+//		camMatrix.Ident();
+	
+		// reset the camera to some default FOV,
+		// or else it will use the last FOV used
+		float fov_in_degrees = Script::GetFloat( CRCD(0x99529205, "camera_fov") );
+		pCamera->SetHFOV(fov_in_degrees);
+
+		m_oldTime = 0.0f;
+		m_initialVBlanks = Tmr::GetVblanks();
+	}
+
+	// update universal cutscene time
+	// (shouldn't use the animcontroller's update function
+	// because it caps the frame length...  although
+	// there's no real reason that it needs to... 
+	// maybe we can have the animcontroller use the
+	// uncapped version as well...  it's only capped
+	// for things like the physics so that objects
+	// don't travel too far in a single frame...)
+	m_oldTime = m_currentTime;
+	
+#if 0
+	// here's some debug code for debugging cutscene glitches...
+	// this will loop through 1 second's worth of cutscene data
+	// at half speed.  it was handy for tracking down 
+	// SK5:TT10351 - "pop" in camera animation when going to
+	// a moving camera cut
+	static float cutscene_test_time_offset = 0.0f;
+	cutscene_test_time_offset += ( 1/120.0f );
+	if (cutscene_test_time_offset >= 1.0f )
+	{
+		cutscene_test_time_offset -= 1.0f;
+	}
+
+	m_currentTime = Script::GetFloat( "cutscene_test_time" ) + cutscene_test_time_offset;//(float)( Tmr::GetVblanks() - m_initialVBlanks ) / Config::FPS();
+#else
+	m_currentTime = (float)( Tmr::GetVblanks() - m_initialVBlanks ) / Config::FPS();
+#endif
+
+	// if the CUT file is really small, then chances are
+	// the Makes will still be set from choosing the
+	// item on the menu...  so, the following makes
+	// sure that you don't try to skip it before the
+	// cutscene has played for at least 1 frame...
+	if ( m_videoStarted && ( m_currentTime != 0.0f ) )
+	{
+		Mdl::FrontEnd* pFront = Mdl::FrontEnd::Instance();
+		
+		for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+		{
+			Inp::Handler< Mdl::FrontEnd >* pHandler = pFront->GetInputHandler( i );
+			if ( pHandler->m_Device->IsPluggedIn() && pHandler->m_Device->HasValidControlData() && pHandler->m_Input->m_Makes & Inp::Data::mD_X )
+			{
+				// GJ FIX FOR TT12703:  "If the player disconnects the 
+				// controller during any cut scene in the game, the player
+				// can reconnect the controller into any of the other 3
+				// controller ports and bypass the cut scene by pressing
+				// the A button. Since story mode is a one player game, the
+				// player should not be able to use multiple ports at the
+				// same time."
+				if ( i == Mdl::Skate::Instance()->m_device_server_map[0] )
+				{
+					// skip to the end of the movie...
+					m_currentTime = mp_cameraQuickAnim->GetDuration();
+					m_videoAborted = true;
+				}
+				else
+				{
+					Dbg_Message( "Can only abort cutscenes from first controller" );
+				}
+			}
+		}
+	}
+
+	update_camera( pCamera );
+
+	// boned anims should come before moving objects,
+	// because some moving objects depend on the 
+	// current position of the neck bone
+	update_boned_anims();	
+
+	update_moving_objects();
+
+	update_extra_cutscene_objects();
+
+/*
+	// display moving objects here
+	// (need to do it after the object anims AND
+	// the skeletal anims have been updated for
+	// this frame)
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+		if ( m_objects[i].mp_animInfo )
+		{
+			Obj::CCompositeObject* pCompositeObject = m_objects[i].mp_object;	
+			Dbg_Assert( pCompositeObject );
+
+			Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( pCompositeObject );
+			if ( pSkeletonComponent )
+			{
+				Gfx::CSkeleton* pSkeleton = pSkeletonComponent->GetSkeleton();
+				Dbg_Assert( pSkeleton );
+	
+				// if we're going to grab the display matrix, need to grab it
+				// *after* the object anims have been updated
+				Mth::Matrix theMat = pCompositeObject->GetDisplayMatrix();
+				theMat[Mth::POS] = pCompositeObject->GetPos();
+				pSkeleton->Display( &theMat, 1.0f, 0.0f, 0.0f );
+			}
+		}
+	}
+*/
+	if ( Script::GetInteger( CRCD(0x5ecc0073,"debug_cutscenes") ) )
+	{
+		// pass the pertinent info to the panel:
+		Script::CStruct* pTempStruct = new Script::CStruct;
+
+//			char line1[128];
+//			sprintf( line1, "camera = %f", m_cameraController.GetCurrentAnimTime() );
+//			pTempStruct->AddString( "line1", (const char*)&line1 );
+
+		char line1[128];
+		sprintf( line1, "time = %f", m_currentTime );
+		pTempStruct->AddString( "line1", (const char*)&line1 );
+
+		Script::RunScript( CRCD(0xd9157174,"draw_cutscene_panel"), pTempStruct );
+
+		delete pTempStruct;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::update_extra_cutscene_objects()
+{
+	// GJ:  the cutscene manager updates the camera position after
+	// the object manager updates the extra peds in the scene,
+	// meaning that the extra peds will still be using the
+	// previous frame's camera position to calculate whether it
+	// should be visible.  effectively, this causes a 1-frame glitch
+	// whenever there's a new camera cut and a ped appears for the
+	// first time on-screen.  to patch this, i will loop through
+	// the list of all the extra peds for the cutscene and re-apply
+	// their model positions.
+	
+	Script::CArray* pArray = Script::GetArray( CRCD(0x67f08671,"CutsceneObjectNames"), Script::NO_ASSERT );
+	if ( pArray )
+	{
+		for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+		{
+			uint32 objectName = pArray->GetChecksum( i );
+			Obj::CCompositeObject* pObject = (Obj::CCompositeObject*)Obj::ResolveToObject( objectName );
+			if ( pObject )
+			{
+				Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pObject );
+				if ( pModelComponent && pModelComponent->GetModel() )
+				{
+					pModelComponent->GetModel()->SetActive( true );
+					pModelComponent->Update();
+				}
+			}
+		}
+	}
+
+	/*
+	// repeat for the skater
+	Obj::CCompositeObject* pObject = (Obj::CCompositeObject*)Obj::ResolveToObject( 0 );
+	if ( pObject )
+	{
+		Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pObject );
+		if ( pModelComponent && pModelComponent->GetModel() )
+		{
+			pModelComponent->GetModel()->SetActive( true );
+//			pModelComponent->Update();
+		}
+	}
+	*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::Update( Gfx::Camera* pCamera, Script::CStruct* pStruct )
+{
+	if ( !m_audioStarted )
+	{
+		// TODO:  Maybe have some timeout, so that if it 
+		// can't find the stream, it won't wait forever...
+
+		if ( Pcm::PreLoadMusicStreamDone() )
+		{
+			// TODO:  Fill in with appropriate music volume parameter
+			Pcm::StartPreLoadedMusicStream();
+
+			m_audioStarted = true;
+			
+			// GJ:  This seemed to cause sync problems
+			// when the CUT file is really small (quick
+			// to load), and the streams aren't ready
+			// to go yet...  need to look into it more
+
+			// now that the music stream has started,
+			// we should start updating the video
+			// on this frame
+			update_video_playback( pCamera, pStruct );
+		}
+		else
+		{
+			Dbg_Message( "Waiting for streams to preload to finish..." );
+		}
+	}
+	else
+	{
+		update_video_playback( pCamera, pStruct );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneData::IsAnimComplete()
+{
+	// we don't call the Update() function any more
+	// (because Update() uses the capped frame rate)
+	//return m_cameraController.IsAnimComplete();
+
+	return m_currentTime >= mp_cameraQuickAnim->GetDuration(); 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::update_camera( Gfx::Camera* pCamera )
+{
+	Dbg_Assert( pCamera );
+
+	// Not sure if this is still needed for the Doppler effect code
+	// Maybe we can add the functionality automatically to the Gfx::Camera?
+	pCamera->StoreOldPos();
+
+	Mth::Vector& camPos = pCamera->GetPos();
+	Mth::Matrix& camMatrix = pCamera->GetMatrix();
+	
+	Mth::Quat theQuat;
+	Mth::Vector theTrans;
+
+	get_current_frame( &theQuat, &theTrans );
+		
+	// update the camera position
+	camPos = theTrans;
+
+	// update the camera orientation
+	Mth::Vector nullTrans;
+	nullTrans.Set(0.0f,0.0f,0.0f,1.0f);		// Mick: added separate initialization
+	Mth::QuatVecToMatrix( &theQuat, &nullTrans, &camMatrix );
+
+	if ( !m_videoAborted )
+	{
+		// any allocations during the cutscene should go on the
+		// cutscene heap (such as pedestrians).  at the end
+		// of the cutscene, we require that the cutscene heap
+		// is empty before it is deleted...
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneBottomUpHeap());	
+
+		// process any new keys since last frame...
+		// do this AFTER the camera is updated for
+		// the frame, because a custom key might
+		// override the normal value (this is a fix
+		// for the double-cut problem, which
+		// tries to interpolate between the end
+		// of one camera and the beginning of the
+		// next...  what we really want is to 
+		// keep on the end of the previous camera)
+		mp_cameraQuickAnim->ProcessCustomKeys( m_oldTime, m_currentTime, pCamera );
+		
+		Mem::Manager::sHandle().PopContext();
+	}
+
+//	printf( "Done for frame\n" );
+
+//	camPos.PrintContents();
+//	camMatrix.PrintContents();
+}
+
+// TODO:  Check for overflow of temp arrays
+static Mth::Quat s_temp_quats[96];
+static Mth::Vector s_temp_vectors[96];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::update_boned_anims()
+{
+	for ( int i = 0; i < m_numObjects; i++ )
+	{
+		if ( m_objects[i].mp_animInfo )
+		{
+			Obj::CCompositeObject* pCompositeObject = m_objects[i].mp_object;	
+			Dbg_Assert( pCompositeObject );
+
+			Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( pCompositeObject );
+			if ( pSkeletonComponent )
+			{
+				Gfx::CSkeleton* pSkeleton = pSkeletonComponent->GetSkeleton();
+				Dbg_Assert( pSkeleton );
+
+				Nx::CQuickAnim* pBonedQuickAnim = m_objects[i].mp_animInfo->mp_bonedQuickAnim;
+
+				// turn off the bone skipping, because we're
+				// not going through the normal animation code
+				pSkeleton->SetBoneSkipDistance( 0.0f );
+
+				pBonedQuickAnim->GetInterpolatedFrames( &s_temp_quats[0], &s_temp_vectors[0], pSkeleton->GetBoneSkipList(), pSkeleton->GetBoneSkipIndex(), m_currentTime );
+
+				if ( m_objects[i].m_doPlayerSubstitution && pSkeleton->GetNumBones() == 55 )
+				{					
+					int bone_index = pSkeleton->GetBoneIndexById( CRCD(0x7268c230,"bone_jaw") );
+					
+					// GJ:  the hi-res jaw bone and the low-res jaw bone aren't in the
+					// exact same position, which is required for the skinning to
+					// work properly (otherwise, it would create a gap between the
+					// head and body models.)  ideally on the next project, we will
+					// change the exporter so that it puts the correct low-res jaw
+					// position into the body's SKA, and the correct hi-res jaw into
+					// the head's SKA.  For now, since we only have access to the
+					// correct hi-res jaw bone position, we need to explicitly
+					// offset the position of the low-res jaw bone here.  the offset
+					// was derived by subtracting the low-res jaw bone bone-space 
+					// translation (-0.000004, -1.776663, 0.599479) from the hi-res
+					// jaw's bone-space translation (0, -1.57249, 0.08276)
+					s_temp_vectors[bone_index] += Mth::Vector(0.000000f, -1.776663f-(-1.57249f), 0.599479f-0.08276f, 1.000000f);
+
+//					s_temp_vectors[bone_index] = Mth::Vector(0.000000f, -2.531250f, 1.406250f, 1.000000f);
+				}
+
+				pSkeleton->Update( &s_temp_quats[0], &s_temp_vectors[0] );
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void update_model_component( Obj::CCompositeObject* pCompositeObject )
+{
+	CModelComponent* pModelComponent = GetModelComponentFromObject( pCompositeObject );
+	if ( pModelComponent )
+	{
+		// if it's hidden, there's no reason to update it...
+		// (as a side benefit, this also allows us to have multiple
+		// objects referencing the same skater, such as in final.cut...
+		// otherwise, each object would try to update the same model's 
+		// position, and only the last object processed would actually
+		// look correct)
+		Nx::CModel* pModel = pModelComponent->GetModel();
+		Dbg_Assert( pModel );
+		if ( pModel->IsHidden() )
+		{
+			return;
+		}
+
+		if ( pModelComponent->HasRefObject() )
+		{
+			uint32 refObjectName = pModelComponent->GetRefObjectName();
+			Obj::CCompositeObject* pRefObject = (Obj::CCompositeObject*)Obj::ResolveToObject( refObjectName );
+			Dbg_Assert( pRefObject );
+			Obj::CModelComponent* pRefModelComponent = GetModelComponentFromObject( pRefObject );
+			Dbg_Assert( pRefModelComponent );
+			Nx::CModel* pRefModel = pRefModelComponent->GetModel();
+			   
+			// turn off skater brightness, so that it matches the other objects...
+			pRefModelComponent->GetModel()->GetModelLights()->SetBrightness( 0.0f );
+
+			// update brightness in case there's any time of day changes
+			pRefModelComponent->UpdateBrightness();
+			
+			bool should_animate = true;
+			Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( pCompositeObject );
+			Dbg_Assert( pSkeletonComponent );
+			Mth::Matrix theMatrix = pCompositeObject->GetDisplayMatrix();
+			theMatrix[Mth::POS] = pCompositeObject->GetPos();
+			pRefModel->Render( &theMatrix, !should_animate, pSkeletonComponent->GetSkeleton() );
+			pRefModel->SetBoneMatrixData( pSkeletonComponent->GetSkeleton() );
+
+			// TODO:  Should do some check that the object's skeleton matches up with the ref object's models.
+		
+			// Need to update the fake skater's position with the
+			// real skater's position, or else the FakeLights will
+			// be set incorrectly (FakeLights works by taking the 
+			// closest light to the m_pos that is pointed to by the
+			// model lights)
+			pRefObject->SetPos( pCompositeObject->GetPos() );
+		}
+		else
+		{
+			// update brightness in case there's any time of day changes
+			pModelComponent->UpdateBrightness();
+			
+			// remember:
+			// if there's no model specified, then it will draw
+			// a red box.  the compositeobject's update() loop
+			// will draw a red box with identity parameters, and then
+			// this will draw a second one.  so don't freak out
+			// when you see more boxes than you are expecting
+			pModelComponent->Update();
+
+			// GJ:  If you call the Update() function explicitly,
+			// the model's double-buffer will be messed up...
+			// in this case, however, we have suspended the model
+			// component upon creation, so it doesn't get wonky
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::update_moving_objects()
+{
+	if ( !mp_objectQuickAnim )
+	{
+		// No OBA data found in CUT file
+		return;
+	}
+
+	mp_objectQuickAnim->GetInterpolatedHiResFrames( &s_temp_quats[0], &s_temp_vectors[0], m_currentTime );
+
+	// update all the OBA bones that don't have an object associated with it
+	for ( int i = 0; i < mp_objectQuickAnim->GetNumBones(); i++ )
+	{
+		uint32 boneName = mp_objectQuickAnim->GetBoneName(i);
+		
+		bool objectExists = false;
+
+		for ( int j = m_numObjects - 1; j >= 0; j-- )
+		{
+			SCutsceneObjectInfo* pInfo = &m_objects[j];
+			if ( pInfo->m_objectName == boneName )
+			{
+				objectExists = true;
+			}
+		}
+
+		if ( !objectExists )
+		{
+			Obj::CCompositeObject* pCompositeObject = (Obj::CCompositeObject*)Obj::ResolveToObject( boneName );
+			if ( pCompositeObject )
+			{
+				// update the object's orientation
+				Mth::Matrix theMatrix;
+
+				Mth::Vector nullTrans;
+				nullTrans.Set(0.0f,0.0f,0.0f,1.0f);		// Mick: added separate initialization
+				Mth::QuatVecToMatrix( &s_temp_quats[i], &nullTrans, &theMatrix );
+
+				//theMatrix.Ident();
+				pCompositeObject->SetMatrix( theMatrix );
+				pCompositeObject->SetDisplayMatrix( theMatrix );
+
+				// update the object's position
+				pCompositeObject->m_pos = s_temp_vectors[i];
+			}
+		}
+	}
+
+
+	// need to go backwards because the head is dependent
+	// on the body's matrices being set up correctly
+	// for that frame...
+	for ( int i = m_numObjects - 1; i >= 0; i-- )
+	{
+		SCutsceneObjectInfo* pInfo = &m_objects[i];
+
+		Obj::CCompositeObject* pCompositeObject = pInfo->mp_object;
+
+		if ( pInfo->mp_parentObject )
+		{
+			// we handle the hi-res heads specially...  if we
+			// were to use the OBA data, the hi-res head wouldn't
+			// match up exactly with the body, possibly due to
+			// accumulated round-off error as we move down the
+			// parent-child hierarchy
+
+			Obj::CCompositeObject* pParentObject = pInfo->mp_parentObject;
+			Dbg_Assert( pParentObject );
+
+			// if it's a neck bone, then use the previous item to link it
+			Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( pParentObject );
+			Dbg_Assert( pSkeletonComponent );
+			Gfx::CSkeleton* pSkeleton = pSkeletonComponent->GetSkeleton();
+			Dbg_Assert( pSkeleton );
+			Mth::Vector neckPos;
+			bool success = pSkeleton->GetBonePosition( CRCD(0x5a0e0860,"bone_neck"), &neckPos );
+			if ( !success )
+			{
+				Dbg_MsgAssert( 0, ( "Couldn't find the neck bone" ) );
+			}
+
+			pCompositeObject->m_pos = pParentObject->m_pos + ( neckPos * pParentObject->GetDisplayMatrix() );
+			pCompositeObject->m_pos[W] = 1.0f;
+
+			Mth::Matrix bone_matrix;
+			success = pSkeleton->GetBoneMatrix( CRCD(0x5a0e0860,"bone_neck"), &bone_matrix );
+			if ( !success )
+			{
+				Dbg_MsgAssert( 0, ( "Couldn't find the neck bone" ) );
+			}
+
+			// build the object's matrix
+			Mth::Matrix object_matrix = pParentObject->GetDisplayMatrix();
+			object_matrix[W] = pParentObject->GetPos();
+			object_matrix[X][W] = 0.0f;
+			object_matrix[Y][W] = 0.0f;
+			object_matrix[Z][W] = 0.0f;
+			
+			// rotate by 90:  doesn't seem to be needed
+			// any more, perhaps due to an exporter bug
+			// being fixed
+			//Mth::Vector temp;
+			//temp = bone_matrix[Y];
+			//bone_matrix[Y] = bone_matrix[Z];
+			//bone_matrix[Z] = -temp;
+
+			// map the bone to world space
+			bone_matrix *= object_matrix; 
+			bone_matrix[W] = Mth::Vector(0.0f,0.0f,0.0f,1.0f);
+
+			pCompositeObject->SetMatrix( bone_matrix );
+			pCompositeObject->SetDisplayMatrix( bone_matrix );
+
+			// need to update the component manually,
+			// because it is probably out of range for
+			// the suspension logic
+			update_model_component( pCompositeObject );
+		}
+		else if ( pInfo->m_obaIndex != -1 )
+		{
+			// if the object is directly referenced in the OBA file,
+			// then use the position and rotation specified in the OBA.
+
+			// update the object's orientation
+			Mth::Matrix theMatrix;
+
+			Mth::Vector nullTrans;
+			nullTrans.Set(0.0f,0.0f,0.0f,1.0f);		// Mick: added separate initialization
+			Mth::QuatVecToMatrix( &s_temp_quats[pInfo->m_obaIndex], &nullTrans, &theMatrix );
+
+			// GJ:  this used to be a kludge to fix objects
+			// being rotated incorrectly by 90 degrees.  it
+			// was probably due to an exporter bug, which
+			// seems to have been fixed.  in any case,
+			// it doesn't make sense why we would need
+			// to rotate it in code, so i will comment
+			// this out permanently and we can fix the
+			// bug in the exporter if it ever arises again.
+			if ( pInfo->m_skeletonName == 0 )
+			{
+				// but we still need to do it for MDL files
+				// because it doesn't have SKA data that
+				// automatically rotates it
+				// theMatrix.RotateXLocal( Mth::DegToRad(90.0f) );
+			}
+
+			//theMatrix.Ident();
+			pCompositeObject->SetMatrix( theMatrix );
+			pCompositeObject->SetDisplayMatrix( theMatrix );
+
+			// update the object's position
+			pCompositeObject->m_pos = s_temp_vectors[pInfo->m_obaIndex];
+
+//			Dbg_Message( "Object %d:   ", i );
+//	 		pCompositeObject->m_pos.PrintContents();
+//			pCompositeObject->GetDisplayMatrix().PrintContents();
+
+			// need to update the component manually,
+			// because it is probably out of range for
+			// the suspension logic
+			update_model_component( pCompositeObject );
+		}
+
+//		if ( pCompositeObject->GetID() == Script::GenerateCRC( "Skin_CAS2" ) )
+//		{
+//			printf( "Setting %s at time %d"\n, Script::FindChecksumName(pCompositeObject->GetID()), (int)(m_currentTime/60.0f+0.5f) );
+//			pCompositeObject->GetPos().PrintContents();
+//		}
+
+#if 0
+		// print out world-space position of board for adam:
+		{
+			Obj::CSkeletonComponent* pSkeletonComponent = GetSkeletonComponentFromObject( pCompositeObject );
+			if ( pSkeletonComponent )
+			{
+				Gfx::CSkeleton* pSkeleton = pSkeletonComponent->GetSkeleton();
+				Mth::Vector theVec;
+				if ( pSkeleton->GetBonePosition( Script::GenerateCRC("bone_board_root"), &theVec ) )
+				{
+					printf( "Position of %s at time %f:\n\t", Script::FindChecksumName(pCompositeObject->GetID()), m_currentTime );
+					pCompositeObject->GetPos().PrintContents();
+					
+					printf( "Position of %s's board at time %f:\n\t", Script::FindChecksumName(pCompositeObject->GetID()), m_currentTime );
+					theVec = pCompositeObject->GetPos() + ( theVec * pCompositeObject->GetDisplayMatrix() );
+					theVec.PrintContents();
+             					
+					printf( "--------------------------------\n" );
+				}
+			}
+		}
+#endif	  
+
+		if ( Script::GetInteger( CRCD(0x5ecc0073,"debug_cutscenes"), Script::NO_ASSERT ) )
+		{
+			// set up bounding box
+			SBBox theBox;
+			theBox.m_max.Set(1.0f, 10.0f, 1.0f);
+			theBox.m_min.Set(-1.0f, -1.0f, -1.0f);    
+
+			// For now, draw a bounding box
+			Gfx::AddDebugBox( pCompositeObject->GetDisplayMatrix(), pCompositeObject->GetPos(), &theBox, NULL, 1, NULL ); 
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneData::get_current_frame(Mth::Quat* pQuat, Mth::Vector* pTrans)
+{
+	Dbg_Assert( pQuat );
+	Dbg_Assert( pTrans );
+
+	// with cutscenes, i don't actually want to get 
+	// the interpolated frames, i actually want the closest frame
+	// this is for camera positions that are held on a certain
+	// frame for a period of time, and then jumps somewhere else
+	// in this case we would want either the old pose or the new pose,
+	// not something in the middle
+
+	// this means we will not want compression on it
+
+	// grab the frame from the animation controller
+	Dbg_MsgAssert( mp_cameraQuickAnim, ( "Camera data has not been initialized for this cutscene" ) );
+	Dbg_Assert( mp_cameraQuickAnim->GetNumBones() == 1 );
+	mp_cameraQuickAnim->GetInterpolatedHiResFrames( pQuat, pTrans, m_currentTime );
+}
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCutsceneDetails::CCutsceneDetails()
+{				  
+	mp_cutsceneAsset = NULL;
+	m_cutsceneAssetName = 0;
+
+	mp_cutsceneStruct = new Script::CStruct;
+
+	m_numHiddenObjects = 0;
+	mp_hiddenObjects = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CCutsceneDetails::~CCutsceneDetails()
+{
+	if ( mp_cutsceneStruct )
+	{
+		delete mp_cutsceneStruct;
+	}
+
+	if ( mp_cutsceneAsset )
+	{
+		Ass::CAssMan* pAssMan = Ass::CAssMan::Instance();
+		Ass::CAsset* pAsset = pAssMan->GetAssetNode( m_cutsceneAssetName, true );
+		pAssMan->UnloadAsset( pAsset );
+		mp_cutsceneAsset = NULL;
+	}
+
+	if ( mp_hiddenObjects )
+	{
+		delete[] mp_hiddenObjects;
+		mp_hiddenObjects = NULL;
+	}
+	
+	// restore the dynamic light modulation factor
+	Nx::CLightManager::sSetAmbientLightModulationFactor( m_oldAmbientLightModulationFactor );
+	Nx::CLightManager::sSetDiffuseLightModulationFactor( 0, m_oldDiffuseLightModulationFactor[0] );
+	Nx::CLightManager::sSetDiffuseLightModulationFactor( 1, m_oldDiffuseLightModulationFactor[1] );
+
+	// don't need to refresh the skater, because
+	// it will be applied on the next frame anyway
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneDetails::InitFromStructure( Script::CStruct* pParams )
+{
+	bool success = false;
+
+	/// need to create a temp copy of the params...  this is because
+	// the PreCutscene script destroys the screen element that launched
+	// the cutscene, meaning that afterwards the data inside pParams 
+	// will become corrupt...
+	Script::CStruct* pTempParams = new Script::CStruct;
+	pTempParams->AppendStructure( pParams );
+
+	this->SetParams( pTempParams );
+
+	// TODO:  Handle async loads of cutscene
+	bool async = false;
+//	bool dont_assert = false;
+
+	// TODO:  Need to account for the possibility that the same
+	// cutscene will be loaded twice
+
+	// TODO:  Push the correct heap
+
+	// turn off the modulation for the duration of the cutscene
+	m_oldAmbientLightModulationFactor = Nx::CLightManager::sGetAmbientLightModulationFactor();
+	m_oldDiffuseLightModulationFactor[0] = Nx::CLightManager::sGetDiffuseLightModulationFactor(0);
+	m_oldDiffuseLightModulationFactor[1] = Nx::CLightManager::sGetDiffuseLightModulationFactor(1);
+
+	Nx::CLightManager::sSetAmbientLightModulationFactor( 0.0f );
+	Nx::CLightManager::sSetDiffuseLightModulationFactor( 0, 0.0f );
+	Nx::CLightManager::sSetDiffuseLightModulationFactor( 1, 0.0f );
+
+	// force the fake lights to allocate its memory now
+	// (otherwise, the next call to RenderWorld() would
+	// alloc it which would end up fragmenting memory)
+	Nx::CLightManager::sUpdateVCLights();
+
+	// also need to turn it off on the skater to refresh it
+	// (but that will be done if there's a ref model)
+
+	const char* pCutsceneFileName;
+	pTempParams->GetText( CRCD(0xa1dc81f9,"name"), &pCutsceneFileName, Script::ASSERT );
+	
+	Ass::CAssMan* pAssMan = Ass::CAssMan::Instance();
+	if ( pAssMan->GetAsset( pCutsceneFileName, false ) )
+	{
+		Dbg_Message( "Cutscene %s is already playing!", pCutsceneFileName );
+		goto init_failed;
+	}
+	else
+	{
+		char* pCutName = get_cut_name( pCutsceneFileName );
+		char structName[256];
+		strcpy( structName, pCutName );
+		strcat( structName, "_params" );
+
+		Script::CStruct* pCutParams = Script::GetStructure(structName, Script::NO_ASSERT);
+		if ( pCutParams )
+		{
+			mp_cutsceneStruct->AppendStructure( pCutParams );
+		}
+
+		// first look at "dont_unload_anims" flag in cutscene struct
+		int should_unload_anims = !mp_cutsceneStruct->ContainsFlag( CRCD(0xbafa9f1c,"dont_unload_anims") );
+		int should_reload_anims = !mp_cutsceneStruct->ContainsFlag( CRCD(0xbafa9f1c,"dont_unload_anims") );
+
+		if ( pParams->ContainsFlag( CRCD(0x7ddde00e,"from_cutscene_menu") ) )
+		{
+			// if we're coming from the cutscene menu, we never want to unload anims
+			should_unload_anims = true;
+			should_reload_anims = true;
+		}
+
+		mp_cutsceneStruct->AddInteger( CRCD(0xcdf2b13e,"unload_anims"), should_unload_anims );
+		mp_cutsceneStruct->AddInteger( CRCD(0x9a4bdff5,"reload_anims"), should_reload_anims );
+
+		// by default, unload the goals
+		mp_cutsceneStruct->AddInteger( CRCD(0xed064302,"unload_goals"), 1 );
+		mp_cutsceneStruct->AddInteger( CRCD(0xbabf2dc9,"reload_goals"), 1 );
+
+		// "unload_anims" and "reload_anims" can be overridden from playcutscene line
+		mp_cutsceneStruct->AppendStructure( pTempParams );
+
+		// hide all moving objects here...
+		// (do it before normal hide/unhide scripts happen)
+		hide_moving_objects();
+
+		Script::RunScript( CRCD(0xe1ce826a,"PreCutscene"), mp_cutsceneStruct );
+
+		// run the script after the cutscene, so
+		// that we can unpause the objects and such...
+		char startScriptName[256];
+		strcpy( startScriptName, pCutName );
+		strcat( startScriptName, "_begin" );
+		char endScriptName[256];
+		strcpy( endScriptName, pCutName );
+		strcat( endScriptName, "_end" );
+		m_startScriptName = Crc::GenerateCRCFromString( startScriptName );
+		m_endScriptName = Crc::GenerateCRCFromString( endScriptName );
+
+		// GJ:  "use_pip" flag would be handy here!
+
+		mp_cutsceneAsset = (CCutsceneData*)pAssMan->LoadAsset( pCutsceneFileName, async, false, false, 0 );
+
+		// couldn't get cutscene
+		Dbg_MsgAssert( mp_cutsceneAsset, ( "Couldn't create cutscene data %s", pCutsceneFileName ) );
+
+		m_cutsceneAssetName = Script::GenerateCRC( pCutsceneFileName );
+    }
+
+	success = true;
+
+init_failed:
+	delete pTempParams;
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneDetails::Cleanup()
+{
+#ifdef __PLAT_NGC__
+	g_in_cutscene = false;
+#endif		// __PLAT_NGC__
+
+	// kill the cutscene panel, if it exists
+	Script::RunScript( CRCD(0xe7bc7efd,"kill_cutscene_panel") );
+
+	// need to delete the cutscene asset,
+	// so that there will be enough room for all
+	// the anims reloaded in "PostCutscene"
+	if ( mp_cutsceneAsset )
+	{
+		Ass::CAssMan* pAssMan = Ass::CAssMan::Instance();
+		Ass::CAsset* pAsset = pAssMan->GetAssetNode( m_cutsceneAssetName, true );
+		pAssMan->UnloadAsset( pAsset );
+		mp_cutsceneAsset = NULL;
+	}
+
+	if ( m_endScriptName )
+	{
+		Script::RunScript( m_endScriptName );
+	}
+		
+	// kill special effects, in case the artist forgot to do it 
+	// in the individual cutscene end scripts... (need to do this
+	// before the cutscene heap is destroyed)
+	Script::RunScript( CRCD(0xf49a8c9c,"kill_cutscene_camera_hud") );
+
+	// Clear any currently running FakeLights commands, in case the player
+	// aborted while they were still taking effect during the cutscene
+	// (in which case the fake lights would have gone on the cutscene heap)
+	Nx::CLightManager::sClearCurrentFakeLightsCommand();
+
+	// destroy the cutscene heap (at this point, any allocations made during 
+	// the cutscene should have been freed up)
+	Mem::Manager::sHandle().DeleteCutsceneHeap();
+
+	Script::RunScript( CRCD(0xbca1791c,"PostCutscene"), mp_cutsceneStruct );
+
+	// unhide moving objects, based on whether they were hidden...
+	unhide_moving_objects();
+
+	CMovieDetails::Cleanup();
+
+	Script::RunScript( CRCD(0x15674315,"restore_skater_camera") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneDetails::Update()
+{
+	if ( !mp_cutsceneAsset )
+	{
+		Dbg_Message( "No cutscene asset!" );
+		return;
+	}
+
+	if ( mp_cutsceneAsset->LoadFinished() )
+	{
+		if ( !m_startScriptAlreadyRun && m_startScriptName )
+		{
+			Script::RunScript( m_startScriptName );
+			m_startScriptAlreadyRun = true;
+		}
+
+		mp_cutsceneAsset->Update( mp_camera, mp_cutsceneStruct );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneDetails::HasMovieStarted()
+{
+	if ( !mp_cutsceneAsset )
+	{
+		Dbg_MsgAssert( 0, ( "No cutscene asset!" ) );
+		return false;
+	}
+
+	return mp_cutsceneAsset->HasMovieStarted();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneDetails::ResetCustomKeys()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneDetails::IsComplete()
+{
+	if ( m_aborted )
+	{
+		return true;
+	}
+
+	if ( m_holdOnLastFrame )
+	{
+		return false;
+	}
+
+	if ( mp_cutsceneAsset )
+	{
+		return mp_cutsceneAsset->LoadFinished() && mp_cutsceneAsset->IsAnimComplete();
+	}
+		
+	return false;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneDetails::IsHeld()
+{
+	return false;
+}
+									  
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CCutsceneDetails::OverridesCamera()
+{
+	if ( mp_cutsceneAsset )
+	{
+		return mp_cutsceneAsset->OverridesCamera();
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneDetails::hide_moving_objects()
+{
+	// hides the goal peds
+	Script::RunScript( CRCD(0x722d9ca7,"cutscene_hide_objects") );
+
+	// first count the number of objects, so we can allocate the proper number of hide flags
+	int numObjects = 0;
+	Obj::CBaseComponent *p_component = Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRCD(0x286a8d26,"model") );
+	while( p_component )
+	{
+		Obj::CModelComponent* pModelComponent = ((Obj::CModelComponent*)p_component);
+		Nx::CModel* pModel = pModelComponent->GetModel();
+		if ( p_component->GetObject()->GetID() == 0 )
+		{
+			// skip the skater...
+		}
+		else if ( !pModel->IsHidden() )
+		{
+			numObjects++;
+		}
+		p_component = p_component->GetNextSameType();		
+	}
+	Dbg_MsgAssert( mp_hiddenObjects	== NULL, ( "hidden objects array already exists?" ) );
+
+	if ( !numObjects)
+		return;
+
+
+	mp_hiddenObjects = new uint32[numObjects];
+	m_numHiddenObjects = 0;
+	
+	// now actually go through and hide them
+	p_component = Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRCD(0x286a8d26,"model") );
+	while( p_component )
+	{
+		Obj::CModelComponent* pModelComponent = ((Obj::CModelComponent*)p_component);
+		Nx::CModel* pModel = pModelComponent->GetModel();
+		if ( p_component->GetObject()->GetID() == 0 )
+		{
+			// skip the skater...
+		}
+		else if ( !pModel->IsHidden() )
+		{
+			// if this fires off, that would be really weird because we just counted the exact
+			// number we need a few moments ago...
+			Dbg_MsgAssert( m_numHiddenObjects < numObjects, ( "Too many objects in scene to hide?" ) );
+
+//			printf( "Found model to hide (%s)\n", Script::FindChecksumName(p_component->GetObject()->GetID()) );
+			mp_hiddenObjects[m_numHiddenObjects] = p_component->GetObject()->GetID();
+			pModel->Hide( true );
+			m_numHiddenObjects++;
+		}
+
+		p_component = p_component->GetNextSameType();		
+	}
+
+	printf( "Hid %d objects\n", m_numHiddenObjects );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CCutsceneDetails::unhide_moving_objects()
+{
+	if ( mp_hiddenObjects )
+	{
+		for ( int i = 0; i < m_numHiddenObjects; i++ )
+		{
+			Obj::CCompositeObject* pObject = (Obj::CCompositeObject*)Obj::ResolveToObject( mp_hiddenObjects[i] );
+			if ( pObject )
+			{
+				Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pObject );
+				Nx::CModel* pModel = pModelComponent->GetModel();
+				pModel->Hide( false );
+			}
+			else
+			{
+				// it's possible that they won't exist anymore
+				// because of the call to uninitialize all goals...
+				//Dbg_MsgAssert( pObject, ( "Couldn't find object %s to unhide?", Script::FindChecksumName(mp_hiddenObjects[i]) ) );
+			}
+		}
+
+		delete[] mp_hiddenObjects;
+		mp_hiddenObjects = NULL;
+	}
+
+	m_numHiddenObjects = 0;
+	
+	// unhides the goal peds
+	Script::RunScript( CRCD(0x94d0d528,"cutscene_unhide_objects") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/cutscenedetails.h b/Code/Sk/Objects/cutscenedetails.h
new file mode 100644
index 0000000..ca4b5ce
--- /dev/null
+++ b/Code/Sk/Objects/cutscenedetails.h
@@ -0,0 +1,217 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       cutscenedetails.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  01/13/2003
+//****************************************************************************
+
+#ifndef __OBJECTS_CUTSCENEDETAILS_H
+#define __OBJECTS_CUTSCENEDETAILS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 							   
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace File
+{
+	class CFileLibrary;
+}
+		 
+namespace Nx
+{
+	class CQuickAnim;
+}
+
+namespace Mth
+{
+	class Matrix;
+	class Quat;
+	class Vector;
+}
+				   
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+
+namespace Obj
+{
+
+	class CCutsceneData;
+	class CCompositeObject;
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+struct SCutsceneAnimInfo
+{
+	uint32						m_animName;
+	Nx::CQuickAnim*				mp_bonedQuickAnim;
+};
+
+struct SCutsceneObjectInfo
+{
+	uint32						m_objectName;
+	uint32						m_flags;
+	uint32						m_skeletonName;
+	bool						m_doPlayerSubstitution;
+	SCutsceneAnimInfo*			mp_animInfo;
+	Obj::CCompositeObject*		mp_object;
+	Obj::CCompositeObject*		mp_parentObject;
+	int							m_obaIndex;			// -1 if not valid
+};
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class CCutsceneData	: public Spt::Class
+{
+protected:
+	enum
+	{
+		vMAX_CUTSCENE_ANIMS = 64,
+		vMAX_CUTSCENE_OBJECTS = 64,
+		vMAX_CUTSCENE_ASSETS = 64
+	};
+
+public:
+	CCutsceneData();
+	virtual ~CCutsceneData();
+
+public:
+    bool    			   		Load(const char* p_fileName, bool assertOnFail, bool async);
+    bool    			   		PostLoad(bool assertOnFail);
+	bool						LoadFinished();
+	void						Update( Gfx::Camera* pCamera, Script::CStruct* pStruct );
+	bool						IsAnimComplete();
+	bool						OverridesCamera();
+	bool						HasMovieStarted()
+	{
+		return m_videoStarted;
+	}
+
+protected:
+	void						get_current_frame( Mth::Quat* pQuat, Mth::Vector* pTrans );
+	void						update_camera( Gfx::Camera* pCamera );
+	void						update_moving_objects();
+	void						update_extra_cutscene_objects();
+	void						update_boned_anims();	
+	void 						add_boned_anim( uint32 objectName, uint32* pData, int fileSize );
+	void						add_anim( uint32 animName, uint32* pData, int fileSize );
+	void 						add_object( Script::CStruct* pParams );
+	bool						create_objects( uint32* pData );
+	SCutsceneObjectInfo*		get_object_info( uint32 objectName );
+	SCutsceneAnimInfo*			get_anim_info( uint32 animName );
+	void						update_video_playback( Gfx::Camera* pCamera, Script::CStruct* pStruct );
+
+protected:
+	void						init_objects();
+	void						load_models();
+
+protected:
+	// need this pointer only during async loads
+	File::CFileLibrary* 		mp_fileLibrary;
+	bool						m_dataLoaded;
+	
+	Nx::CQuickAnim*				mp_cameraQuickAnim;
+	Nx::CQuickAnim*				mp_objectQuickAnim;
+	
+	Mth::Vector					m_original_player_bounding_sphere;
+	bool						m_original_hidden;
+	bool						m_original_scale_enabled;
+	Mth::Vector					m_original_pos;
+
+	// this is used so that if there are multiple create-a-skaters,
+	// they don't all try to overwrite the same m_original_*
+	// fields...
+	bool						m_original_params_set;
+
+	SCutsceneObjectInfo			m_objects[vMAX_CUTSCENE_OBJECTS];
+	int							m_numObjects;
+
+	SCutsceneAnimInfo			m_bonedAnims[vMAX_CUTSCENE_ANIMS];
+	int							m_numBonedAnims;
+
+	uint32						m_assetName[vMAX_CUTSCENE_ASSETS];
+	int							m_numAssets;
+	
+	bool						m_audioStarted;
+	bool						m_videoStarted;
+	bool						m_videoAborted;
+
+	float						m_oldTime;
+	float						m_currentTime;
+	uint64						m_initialVBlanks;
+};
+
+class CCutsceneDetails : public CMovieDetails
+{
+public:
+	CCutsceneDetails();
+	virtual			~CCutsceneDetails();
+
+public:
+	virtual bool 				InitFromStructure(Script::CStruct *pParams);
+	virtual void 				Update();
+	virtual bool 				ResetCustomKeys();
+	virtual bool 				IsComplete();
+	virtual bool				IsHeld();
+	virtual bool				OverridesCamera();
+	virtual void				Cleanup();
+	virtual bool				HasMovieStarted();
+	
+protected:
+	void						hide_moving_objects();
+	void						unhide_moving_objects();
+
+protected:
+	CCutsceneData*				mp_cutsceneAsset;
+	uint32						m_cutsceneAssetName;
+	uint32						m_startScriptName;
+	bool						m_startScriptAlreadyRun;
+	uint32						m_endScriptName;
+	Script::CStruct*			mp_cutsceneStruct;
+
+	float						m_oldAmbientLightModulationFactor;
+	float						m_oldDiffuseLightModulationFactor[2];
+	
+	uint32*						mp_hiddenObjects;
+	int							m_numHiddenObjects;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_CUTSCENEDETAILS_H
+
diff --git a/Code/Sk/Objects/emitter.cpp b/Code/Sk/Objects/emitter.cpp
new file mode 100644
index 0000000..4bf7a40
--- /dev/null
+++ b/Code/Sk/Objects/emitter.cpp
@@ -0,0 +1,442 @@
+///////////////////////////////////////////////////////
+// Emitter.cpp
+
+		  
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 		   // used to get position of the skater's camera
+
+//#define	DEBUG_EMITTER
+
+namespace Obj
+{
+
+Lst::HashTable *	CEmitterManager::sp_emitter_lookup = NULL;
+
+void CEmitterManager::sInit()
+{
+	if (sp_emitter_lookup)
+	{
+		sCleanup();
+	}
+	else
+	{
+		sp_emitter_lookup = new Lst::HashTable(4);	
+	}
+}
+
+void CEmitterManager::sCleanup()
+{
+	if (sp_emitter_lookup)
+	{
+		sp_emitter_lookup->IterateStart();
+		CEmitterObject *pNode = sp_emitter_lookup->IterateNext();
+		while (pNode)
+		{
+			CEmitterObject *pNext = sp_emitter_lookup->IterateNext();
+			uint32 checksum = pNode->m_name;   
+			delete pNode;
+			sp_emitter_lookup->FlushItem(checksum);			
+			pNode = pNext;
+	   	}
+
+//		delete sp_emitter_lookup;
+//		sp_emitter_lookup = NULL;
+	}
+}
+
+void CEmitterManager::sAddEmitter(int node,bool active)
+{
+							
+						
+//	printf ("It's a Emitter (%d)\n",node);
+	
+	// create a new EmitterObject
+	CEmitterObject *pEmitterObject = new CEmitterObject();
+
+	pEmitterObject->m_node = node;
+
+	pEmitterObject->m_active = active;  // created at start or not?
+	
+	pEmitterObject->mp_sector = NULL;
+	pEmitterObject->m_shape_type = CEmitterObject::vSHAPE_SPHERE;  
+
+	// Get pointer to the Emitter data	
+	Script::CScriptStructure *pNode = SkateScript::GetNode(node);
+	
+	// Get name
+	if (pNode->GetChecksum("name",&pEmitterObject->m_name))
+	{
+#ifdef	DEBUG_EMITTER
+		printf ("Emitter: Got Name %s\n",Script::FindChecksumName(pEmitterObject->m_name));
+#endif	
+	}
+	else
+	{
+		Dbg_MsgAssert(0,("No name in proxim node %d\n",node));
+	}
+
+	// Add to hash table
+	sp_emitter_lookup->PutItem(pEmitterObject->m_name, pEmitterObject);
+
+	// Get shape type
+	uint32 shape_checksum;
+	if (pNode->GetChecksum("type", &shape_checksum))
+	{
+		pEmitterObject->m_shape_type = CEmitterObject::sGetShapeFromChecksum(shape_checksum);
+	}
+
+	// Try to find geometry
+	pEmitterObject->InitGeometry(Nx::CEngine::sGetMainScene()->GetSector(pEmitterObject->m_name));
+
+	SkateScript::GetPosition(pNode,&pEmitterObject->m_pos);
+#ifdef	DEBUG_EMITTER				
+		Dbg_Message("Emitter: Pos = (%f, %f, %f)", pEmitterObject->m_pos[X], pEmitterObject->m_pos[Y], pEmitterObject->m_pos[Z]);
+#endif	
+
+	pNode->GetFloat (0xc48391a5 /*radius*/, &pEmitterObject->m_radius);  
+	Dbg_MsgAssert(pEmitterObject->mp_sector || pEmitterObject->m_radius > 0.0f,( "Radius %f too small in proxim node %d\n",pEmitterObject->m_radius,node));	
+#ifdef	DEBUG_EMITTER
+	printf ("Emitter:  Radius = %f\n",pEmitterObject->m_radius);
+#endif	
+	pEmitterObject->m_radius_squared = pEmitterObject->m_radius * pEmitterObject->m_radius;	
+
+	if (pNode->GetChecksum(0x2ca8a299,&pEmitterObject->m_script))		// Checksum of TriggerScript
+	{
+#ifdef	DEBUG_EMITTER
+		printf ("Emitter: Got Script %s\n",Script::FindChecksumName(pEmitterObject->m_script));
+#endif	
+	}
+	else
+	{
+		//Dbg_MsgAssert(0,("No triggerscript in proxim node %d\n",node));
+	}
+
+	
+	//pEmitterObject->m_outside_flags = 0;			// say all inside for now....
+}
+
+void CEmitterManager::sAddedAll()
+{
+	
+//	printf("Added all Emitters\n");		 
+		 
+	Dbg_Assert(sp_emitter_lookup);
+
+	sp_emitter_lookup->IterateStart();
+	CEmitterObject *pEmitterObject = sp_emitter_lookup->IterateNext();
+
+	while (pEmitterObject)
+	{
+		// do any final per-node processing here
+		pEmitterObject = sp_emitter_lookup->IterateNext();
+	}				 
+				 	
+}			   
+
+CEmitterObject * CEmitterManager::sGetEmitter(uint32 checksum)
+{
+	Dbg_Assert(sp_emitter_lookup);
+
+	CEmitterObject *pEmitter = sp_emitter_lookup->GetItem(checksum);
+
+	if (!pEmitter)
+	{
+		Dbg_Message("Warning: Can't find emitter %s", Script::FindChecksumName(checksum));
+	}
+
+	return pEmitter;
+}
+
+void CEmitterManager::sSetEmitterActive( int node, int active )
+{
+	Dbg_Assert(sp_emitter_lookup);
+
+	sp_emitter_lookup->IterateStart();
+	CEmitterObject *pEmitterObject = sp_emitter_lookup->IterateNext();
+	while (pEmitterObject)
+	{
+		if (pEmitterObject->m_node == node)
+		{
+			pEmitterObject->m_active = active;
+			return;
+		}
+		pEmitterObject = sp_emitter_lookup->IterateNext();
+	}				 
+}
+
+#if 0
+// Update all the proximity nodes				
+// for a certain skater
+// checking for proximity either with the skater or the camera					
+void CEmitterManager::sUpdateEmitters(CSkater *pSkater, Gfx::Camera* camera)
+{
+	Dbg_Assert(sp_emitter_lookup);
+
+	sp_emitter_lookup->IterateStart();
+	CEmitterObject *pEmitterObject = sp_emitter_lookup->IterateNext();
+
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if( skate_mod->mpProximManager == NULL )
+	{
+		return;
+	}
+	
+	int	skater_num = 0;						// 0 for now, should be viewport index
+
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	if (!gamenet_man->InNetGame())
+	{
+		if (skate_mod->GetSkater(1) == pSkater)
+		{
+			skater_num = 1;
+		}
+	}
+
+	int	skater_mask = 1 << skater_num;		// the bitmask for this skater
+
+//	Mth::Vector	skater_pos = pSkater->m_pos;	
+//	Mth::Vector	skater_old_pos = pSkater->m_old_pos;	
+
+	// Aieeeeee!!!
+	// to get the camera's position, we have to dig down to a low level
+	// the CSkater::GetSkaterCam returns a *CSkaterCam
+	// CSkaterCam::GetCamera returns a 	reference to Gfx::Camera
+	// Gfx::Camera::GetCamera returns a *NsCamera (pointer)
+	// so we pass that to RwCameraGetFrame, so we can get the position of the camera...
+	// Matt added m_pos to CSkaterCam and removed the code below to find the camera:
+	
+//	Gfx::Camera* 	p_camera = pSkater->GetSkaterCam()->mpCamera ;
+	Mth::Vector	camera_pos;
+	// Local clients can get the camera's position directly from the skater cam
+	// but observers need to get it from the camera that was passed in since
+	// the skater they're following doesn't have a skatercam
+
+// Mick - camera passed in is the active camera
+//	if( pSkater->IsLocalClient())
+//	{
+//		camera_pos = pSkater->GetSkaterCam()->GetPos();
+//	}
+//	else
+//	{
+		camera_pos = camera->GetPos();
+//	}
+	
+	while (pEmitterObject)
+	{
+	
+	//Mth::Vector to_skater = skater_pos - pEmitterObject->m_pos;
+	// Bitfield per viewport....
+			
+//		printf ("Proxim: Update,  Script %s\n",Script::FindChecksumName(pEmitterObject->m_script));	
+
+		// find the squared dist from this node:
+		
+		// This code would crash with observers since the skater they were following had no cam
+		//Mth::Vector		node_to_camera = pSkater->GetSkaterCam( )->m_pos - pEmitterObject->m_pos;
+        
+		bool now_inside = pEmitterObject->CheckInside(camera_pos);
+
+		if ( skater_mask & pEmitterObject->m_outside_flags)
+		{
+			// skater is currently outside, check if moved to inside
+			if (now_inside)
+			{
+			
+				pEmitterObject->m_outside_flags &= ~skater_mask;
+				pSkater->m_inside = true;
+				
+#ifdef	DEBUG_EMITTER				
+				printf ("+++++ NOW INSIDE, running script %s \n",Script::FindChecksumName(pEmitterObject->m_script));
+#endif				
+				pSkater->SpawnAndRunScript(pEmitterObject->m_script,pEmitterObject->m_node );
+			}			
+		}
+		else
+		{
+			// skater is currently inside, check if moved outside
+			if (!now_inside)
+			{
+				pEmitterObject->m_outside_flags |= skater_mask;				
+				pSkater->m_inside = false;
+	#ifdef	DEBUG_EMITTER				
+				printf ("----- NOW OUTSIDE, running script %s \n",Script::FindChecksumName(pEmitterObject->m_script));
+	#endif				
+					
+				pSkater->SpawnAndRunScript(pEmitterObject->m_script,pEmitterObject->m_node );
+			}			
+		}		
+		pEmitterObject = sp_emitter_lookup->IterateNext();
+	}				 
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CEmitterObject::EShape CEmitterObject::sGetShapeFromChecksum(uint32 checksum)
+{
+	switch (checksum)
+	{
+	case 0xaa069978: // Sphere
+		return vSHAPE_SPHERE;
+
+	case 0xe460abde: // BoundingBox
+		return vSHAPE_BBOX;
+
+	case 0x3f1e5a7: // NonAxisAlignedBoundingBox
+		return vSHAPE_NON_AA_BBOX;
+
+	default:
+		Dbg_MsgAssert(0, ("Unknown shape checksum %x\n", checksum));
+		return vSHAPE_SPHERE;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CEmitterObject::InitGeometry(Nx::CSector *p_sector)
+{
+	mp_sector = p_sector;
+
+	if (!mp_sector)
+	{
+		// No Geometry
+		Dbg_MsgAssert(m_shape_type == CEmitterObject::vSHAPE_SPHERE, ("EmitterObject with shape %d has no geometry.", m_shape_type));
+		return;
+	}
+
+#ifdef	DEBUG_EMITTER				
+	Dbg_Message("Found Geometry for EmitterObject %s at address %x", Script::FindChecksumName(m_name), mp_sector);
+#endif
+
+	switch (m_shape_type)
+	{
+	case CEmitterObject::vSHAPE_SPHERE:
+		Dbg_MsgAssert(0, ("EmitterObject with sphere shape has geometry."));
+		break;
+
+	case CEmitterObject::vSHAPE_BBOX:
+		m_bbox = mp_sector->GetBoundingBox();
+#ifdef	DEBUG_EMITTER				
+		Dbg_Message("Bounding box of geometry (%f, %f, %f) - (%f, %f, %f)",
+					m_bbox.GetMin()[X], m_bbox.GetMin()[Y], m_bbox.GetMin()[Z],
+					m_bbox.GetMax()[X], m_bbox.GetMax()[Y], m_bbox.GetMax()[Z]);
+#endif	
+		break;
+
+	case CEmitterObject::vSHAPE_NON_AA_BBOX:
+		Dbg_MsgAssert(0, ("EmitterObject with non axis aligned bounding box shape not supported yet."));
+		break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CEmitterObject::CheckInside(const Mth::Vector &object_pos) const
+{
+	Mth::Vector		node_to_object = object_pos - m_pos;
+	float			dist_squared = node_to_object.LengthSqr();	   
+
+	bool inside = false;
+
+	switch (m_shape_type)
+	{
+	case vSHAPE_SPHERE:
+		inside = (dist_squared <= m_radius_squared);
+		break;
+
+	case vSHAPE_BBOX:
+		inside = m_bbox.Within(object_pos);
+#ifdef	DEBUG_EMITTER
+		m_bbox.DebugRender(0xff0000ff);
+#endif //	DEBUG_EMITTER
+		break;
+
+	default:
+		Dbg_MsgAssert(0, ("Can't handle EmitterObject type %d", m_shape_type));
+		break;
+	}
+
+	return inside;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector 		CEmitterObject::GetClosestPoint(const Mth::Vector &object_pos) const
+{
+	// Return original position if within the object
+	if (CheckInside(object_pos))
+	{
+		return object_pos;
+	}
+
+	Mth::Vector intersect_point;
+
+	switch (m_shape_type)
+	{
+	case vSHAPE_SPHERE:
+		{
+			// Same for either within or outside sphere
+			Mth::Vector direction = object_pos - m_pos;
+			direction.Normalize();
+			direction *= m_radius;
+
+			intersect_point = m_pos + direction;
+		}
+		break;
+
+	case vSHAPE_BBOX:
+		m_bbox.GetClosestIntersectPoint(object_pos, intersect_point);
+		break;
+
+	default:
+		Dbg_MsgAssert(0, ("Can't handle EmitterObject type %d", m_shape_type));
+		break;
+	}
+
+#ifdef	DEBUG_EMITTER
+	Gfx::AddDebugStar(intersect_point);
+#endif //	DEBUG_EMITTER
+
+	return intersect_point;
+}
+
+/*
+{
+	// Node 572 (Mick Test)
+	Position = (-4060.666992,131.999329,-4604.379395)
+	Angles = (0.000000,-3.141593,0.000000)
+	Name = TRG_Micks_test
+	Class = ProximNode
+	Type = skater
+    radius = 1200
+	TriggerScript = xxx_test
+}
+*/
+
+
+}
+			   
+
diff --git a/Code/Sk/Objects/emitter.h b/Code/Sk/Objects/emitter.h
new file mode 100644
index 0000000..30a3729
--- /dev/null
+++ b/Code/Sk/Objects/emitter.h
@@ -0,0 +1,113 @@
+//////////////////////////////////////////
+// Emitter.h
+
+#ifndef __OBJECTS_Emitter_H
+#define __OBJECTS_Emitter_H
+
+#include 
+#include 
+
+namespace Nx
+{
+	class	CSector;
+}
+
+namespace Gfx
+{
+	class	Camera;
+}
+
+namespace Script
+{
+	class	CScript;
+}
+
+namespace Obj
+{
+
+	class	CSkater;
+	class CEmitterManager;
+
+class CEmitterObject : public Spt::Class
+{
+public:
+	enum EShape
+	{
+		vSHAPE_BBOX = 0,		// Axis-aligned bounding box
+		vSHAPE_NON_AA_BBOX,		// Non-axis-aligned bounding box
+		vSHAPE_SPHERE,
+	};
+
+	///////////
+	// Functions
+	void				InitGeometry(Nx::CSector *p_sector);
+	bool				CheckInside(const Mth::Vector &object_pos) const;
+	Mth::Vector 		GetClosestPoint(const Mth::Vector &object_pos) const;
+
+	// Checksum conversion functions
+	static EShape		sGetShapeFromChecksum(uint32 checksum);
+
+protected:
+	///////////
+	// Variables
+	int			m_node;				// Number of the node in the trigger array
+	int 		m_link;				// link number
+	uint32		m_name;				// checksum of name of the node in the trigger array
+	Mth::Vector m_pos;				// position of the node
+	Mth::Vector m_angles;			// angles of the node
+	bool 		m_active;			// Emitter segment starting at this node: active or not?
+
+	//int			m_outside_flags;	// bitmask of flags, 0 = inside the radius, 1 = outside
+
+	EShape 		m_shape_type;
+
+	// Sphere data
+	float		m_radius;
+	float		m_radius_squared;
+
+	// Geometry Data
+	Nx::CSector *mp_sector;
+
+	// BBox data
+	Mth::CBBox	m_bbox;
+	
+	float		m_last_distance_squared;
+	
+	uint32 		m_script;
+
+	//CEmitterObject *m_pNext;
+
+
+	// friends
+	friend CEmitterManager;
+};
+
+ 
+
+class CEmitterManager
+{
+public:
+	static void				sInit();
+	static void				sCleanup();
+	static void				sAddEmitter(int node, bool active);
+	static void				sAddedAll();
+	static CEmitterObject * sGetEmitter(uint32 checksum);
+
+	// activate/deactivate Emitter:
+	static void				sSetEmitterActive(int node, int active);
+
+	//static void				sUpdateEmitters(CSkater *pSkater, Gfx::Camera* camera);
+
+protected:
+	static Lst::HashTable *	sp_emitter_lookup;	
+};
+
+
+
+} 
+
+			
+#endif			// #ifndef __OBJECTS_Emitter_H
+			
+
+
diff --git a/Code/Sk/Objects/followob.cpp b/Code/Sk/Objects/followob.cpp
new file mode 100644
index 0000000..5d6f44f
--- /dev/null
+++ b/Code/Sk/Objects/followob.cpp
@@ -0,0 +1,159 @@
+#include 
+#include 
+#include 
+#include 
+
+namespace Obj
+{
+
+CFollowOb::CFollowOb()
+{
+	Reset();
+}
+
+CFollowOb::~CFollowOb()
+{
+}
+
+void CFollowOb::Reset()
+{
+	m_num_path_points=0;
+	m_most_recent_path_point=0;
+	m_leader_name=0;
+	m_leader_pos.Set();
+	m_distance=0.0f;
+	m_orient_y=false;
+}
+
+// Adds a new position taken from the leader object, if that position is suitably interesting.
+// (Ie, it is not too close to the last position)
+// FOLLOW_RESOLUTION should be kept as big as possible. That way, the array of positions does
+// not need to be so big, and calculating the position will be faster because it won't have to
+// step over so many segments.
+// If it is too big though the object won't follow the leader's path so accurately and 
+// will start cutting corners noticeably.
+#define FOLLOW_RESOLUTION 40.0f
+void CFollowOb::GetNewPathPointFromObjectBeingFollowed()
+{
+	//CMovingObject *p_obj=CMovingObject::m_hash_table.GetItem(m_leader_name);
+	CMovingObject *p_obj=(CMovingObject*)Obj::ResolveToObject(m_leader_name);
+	
+	if (!p_obj)
+	{
+		// Can't find the object, oh well.
+		return;
+	}	
+
+	m_leader_pos=p_obj->m_pos;
+	
+	if (m_num_path_points==0)
+	{
+		// Insert the first point.
+		m_most_recent_path_point=0;
+		mp_path_points[0]=m_leader_pos;
+		++m_num_path_points;
+		return;
+	}	
+	
+	if (Mth::DistanceSqr(mp_path_points[m_most_recent_path_point],m_leader_pos) > 
+		FOLLOW_RESOLUTION*FOLLOW_RESOLUTION)
+	{
+		// The new point is sufficiently far from the last, so insert it into the cyclic buffer.
+		++m_most_recent_path_point;
+		if (m_most_recent_path_point>=MAX_FOLLOWOB_PATH_POINTS)
+		{
+			m_most_recent_path_point=0;
+		}
+		
+		mp_path_points[m_most_recent_path_point]=m_leader_pos;
+		
+		// m_num_path_points increases till it hits MAX_FOLLOWOB_PATH_POINTS then stays there.
+		if (m_num_path_points seg_length)
+		{
+			dist_remaining-=seg_length;
+			seg_start=seg_end;
+			if (!num_points_left)
+			{
+				break;
+			}	
+			--i;
+			if (i < 0)
+			{
+				i=MAX_FOLLOWOB_PATH_POINTS-1;
+			}
+			seg_end=mp_path_points[i];	
+			--num_points_left;
+		}
+		else
+		{
+			break;
+		}
+	}
+	
+	if (seg_length>0.0f)
+	{
+		Mth::Vector seg_dir=seg_vec/-seg_length;
+		
+		*p_pos=seg_start-seg_dir*dist_remaining;
+		
+		if (m_orient_y)
+		{
+			// Instead of setting z to seg_dir directly, use some of the previous value
+			// so that the orientation gradually moves towards the new dir rather than snapping.
+			(*p_orientation)[Z]=10.0f*(*p_orientation)[Z]+seg_dir;
+			(*p_orientation)[Z].Normalize();
+			
+			Mth::Vector Up(0.0f,1.0f,0.0f);
+			(*p_orientation)[X]=Mth::CrossProduct(Up,(*p_orientation)[Z]);
+			(*p_orientation)[X].Normalize();
+			(*p_orientation)[Y]=Mth::CrossProduct((*p_orientation)[Z],(*p_orientation)[X]);
+		}
+		else
+		{
+			(*p_orientation)[Z]=10.0f*(*p_orientation)[Z]+seg_dir;
+			(*p_orientation)[Z][Y]=0;
+			(*p_orientation)[Z].Normalize();
+			(*p_orientation)[Y].Set(0.0f,1.0f,0.0f);
+			(*p_orientation)[X]=Mth::CrossProduct((*p_orientation)[Y],(*p_orientation)[Z]);
+		}
+	}	
+}
+
+}
+
diff --git a/Code/Sk/Objects/gap.cpp b/Code/Sk/Objects/gap.cpp
new file mode 100644
index 0000000..b69d2f6
--- /dev/null
+++ b/Code/Sk/Objects/gap.cpp
@@ -0,0 +1,726 @@
+//////////////////////////////////////////////////////////
+// Gap.cpp
+//
+// Functions for CGap and CGapMan
+//
+// A "Gap" in the game is a pair of events that cause something to happen
+// Typically this is jumping across something, such as a gap between two buildings
+// but might also be grinding a rail for a certain distance
+// or doing something specific, like doing a kickflip over a barrel.
+//
+// The aim of the gap system is to provide lots of flexibility in setting up
+// these pairs of events, and also to allow the user to set gaps quickly and simply
+//
+// Gaps are set up via script commands
+// so they can easily be inserted in trigger scripts
+// the simplest might look like:
+/*
+
+script TRG_Obj010
+	StartGap GapId = JumpOverLog Cancel = [CANCEL_GROUND CANCEL_RAIL CANCEL_WALLRIDE] Expire = 20
+endscript
+
+
+script TRG_Obj012
+	EndGap GapId = JumpOverLog Score = 100 Text = "You jumped over a log" Script = ScreenFlash01
+endscript
+
+Using the "StartGap" command, you can set up as many gaps as you like, starting from
+a particular event.
+
+For that gap, you specify what cancels the gap, if nothing is specified, then this should default
+to CANCEL_GROUND.  These conditions are checked every frame
+
+All gaps are cancelled by a bail (perhaps unless we have CANCEL_BAIL_NOT set????)
+
+We can also have a specific "CancelGap"  command, for fuller control.
+
+The optional "Expire" parameter says when the gap expires.  By default gaps do not expire				   
+
+The "EndGap" command will check to see if the gap with the specified GapID had been started
+(and not cancelled, or expired), and will then Execute the gap, having an optional Score,
+text (defaults to "Gap") and an optional script, which is executed.
+
+If there is no score, then it will not count as a trick, and you just get the script executing.
+
+
+
+
+
+*/
+
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+namespace Obj
+{
+
+/*
+	a CGapCheck is a single entry representing a gap with a unique name
+	these are stored in a list: Mdl::Skate::Instance()->m_gapcheck_list
+	which is re-created individually for each level whan the level is loaded
+	
+	This list is used to create the "View Gaps" menu (from gamemenu.q, script launch_gap_menu)
+	
+	The gap list is updated in game with CGapCheck::GetGap() whena  gap is got
+	
+	TODO -> add LandPendingGaps to "solidlfy" gaps.  The gap should be flagged
+	as "pending" when you "get" it (with an EndGap).  But it should only
+	be really flagged when you sucessfully land the combo
+	and it should be cleared if you bail while doing the gap 
+
+
+*/	
+
+// Constructors are needed for types derived from the list node class
+// so the "this" pointer can be passed down, so it ends up
+// in the data member
+
+CGapCheck::CGapCheck(): Lst::Node< CGapCheck > ( this )
+{	
+	m_got = 0;
+	m_got_pending = 0;
+	m_valid = true;			// newly created gaps are always valid
+}
+
+int				CGapCheck::Got()
+{
+	return m_got;
+}
+
+void				CGapCheck::GetGap()
+{
+	m_got+= m_got_pending;
+	m_got_pending = 0;
+}
+
+void				CGapCheck::GetGapPending()
+{
+	m_got_pending += 1;
+}
+
+void				CGapCheck::UnGetGapPending()
+{
+	m_got_pending = 0;
+}
+
+int				CGapCheck::GotGapPending()
+{
+	 return m_got_pending;
+}
+
+
+void				CGapCheck::UnGetGap()
+{
+	m_got = false;
+}
+
+const char	*			CGapCheck::GetText()
+{
+	return m_name.getString();
+}
+
+void				CGapCheck::SetText(const char *p)
+{
+	m_name = p;
+}
+
+void CGapCheck::SetInfoForCamera(Mth::Vector pos, Mth::Vector dir)
+{
+	m_skater_start_pos=pos;
+	m_skater_start_dir=dir;
+}
+
+void				CGapCheck::SetCount(int count)
+{
+	m_got = count;
+}
+
+void 				CGapCheck::SetValid(bool valid)
+{
+	m_valid = valid;
+}
+
+void				CGapCheck::Reset()
+{
+	m_got = 0;
+	m_got_pending = 0;
+	m_valid = true;			// newly created gaps are always valid
+}
+
+
+///////////////////////////////////////////////////////////////////////
+
+CGap::CGap(): Lst::Node< CGap > ( this )
+{
+    m_trickChecksum = 0;
+    m_numTrickOccurrences = 0;
+	m_spinMult = 0;
+	m_requirePerfect = false;
+	mp_tricks = new Script::CArray();
+	m_startGapTrickCount = 0;
+	m_numTaps = 0;
+}
+
+CGap::~CGap()
+{
+	if ( mp_tricks )
+		Script::CleanUpArray( mp_tricks );
+	delete mp_tricks;
+}
+
+CGapTrick::CGapTrick(): Lst::Node< CGapTrick > ( this )
+{
+}
+
+void CGapTrick::GetGapTrick()
+{
+    m_got = true;
+}
+
+
+// delete all gapchecks
+// used when we load a level						  
+// K: Also used when choosing test play in the park editor, just before adding all the park editor
+// gaps.
+void	CGapChecklist::FlushGapChecks()
+{
+	CGapCheck *pOther = (CGapCheck*)m_gapcheck_list.FirstItem();
+	while (pOther != NULL)
+	{
+		delete pOther;
+		pOther = (CGapCheck*)m_gapcheck_list.FirstItem();
+	}
+	m_valid = false;
+}
+
+// Given a name and a count, then add a GapCheck to the list
+// if it is already in the list, then just set it valid (leaving the count)						   
+// (that is so when we re-scan the level's gaps after loading a level, we can remove any
+// that do not exist in the level any more)
+void	CGapChecklist::AddGapCheck(const char *p_name, int count, int score)
+{
+	
+	m_valid = true;	 							// the list becomes valid when at least one gapcheck is in it
+	
+	CGapCheck *pOther = (CGapCheck*)m_gapcheck_list.FirstItem();
+	CGapCheck *pAppend = NULL;				// last item we find that is lower than this
+	while (pOther)						 		
+	{
+		if (!strcmp(pOther->GetText(),p_name) && pOther->GetScore() == score)   // if it matches an existing gap in both text and score
+		{
+			pOther->SetValid(true);				// then set that one valid (as we might be pruning later)
+			return;								// then break out
+		}		  
+		
+		if (pOther->GetScore() < score)
+		{
+			pAppend = pOther;
+		}
+		else
+		{
+			if (pOther->GetScore() == score && Str::Compare(pOther->GetText(),p_name)<0)
+			{
+				pAppend = pOther;
+			}
+		}
+		  		
+		pOther = (CGapCheck*)pOther->GetNext();
+	}
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); 
+	
+	CGapCheck* pGapCheck = new CGapCheck;		// create new gap		
+	pGapCheck->UnGetGap();						// say not got it yet
+
+	pGapCheck->SetText(p_name);			   		// set the text for it
+	pGapCheck->SetCount(count);			   		// and the count
+	pGapCheck->SetScore(score);			   		// and the count
+	if (pAppend)
+	{
+		// Append after the last node that is smaller or equal to than this
+		pAppend->Append( pGapCheck );
+	}
+	else
+	{
+		// add to the ehad of the list if it's smaller than everything
+		m_gapcheck_list.AddToHead( pGapCheck );	 
+	}
+	
+	Mem::Manager::sHandle().PopContext();
+}
+
+																					  
+void CGapChecklist::got_gap(Script::CStruct *pScriptStruct, const uint8 *pPC)
+{
+	int score=0;
+	pScriptStruct->GetInteger(CRCD(0xcd66c8ae,"score"),&score);
+	
+	const char *p_name="";
+	pScriptStruct->GetText(CRCD(0xc4745838,"text"),&p_name);
+
+	// only add gaps with some text, and a score
+	if (score && p_name[0])
+	{
+		AddGapCheck(p_name,0,score);
+	}	
+	else
+	{
+		if (Script::GetInteger("PrintGaps"))
+		{
+			printf ("(Name '%s' Score %d - not in checklist)\n",p_name,score); // we don't add gaps with no name or score
+		}
+	}
+}
+
+
+static CGapChecklist * p_current;
+
+// slightly dodgy callback needed to call memmber function
+static void Gap_GotGap(Script::CStruct *pScriptStruct, const uint8 *pPC)
+{
+	p_current->got_gap(pScriptStruct,pPC);		   
+}
+
+
+CGapChecklist::CGapChecklist()
+{
+	m_valid = false;
+}
+
+
+//  Find all the gaps in the level
+// note, we scan regardless, so during development, we 
+// can add gaps to an already saved game
+// (might want to also remove gaps if they have been deleted)
+void CGapChecklist::FindGaps()
+{	
+	Dbg_Message("Looking for gaps ...\n");
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap()); 
+	
+	p_current = this;	  			// store curent 'this' pointer for the callback
+	m_valid = true;					// say that we have looked for gaps in this level
+	
+	// Maybe - scan all gaps, and flag them as not found
+	
+	
+	if (Script::GetInteger("PrintGaps"))
+	{
+		printf("\nGAP LIST\n--------------------------------------\n");
+	}
+
+	SkateScript::ScanNodeScripts(CRCD(0x2ca8a299,"TriggerScript"), CRCD(0xe5399fb2,"EndGap"), Gap_GotGap);	
+	
+	// Maybe - and then re-scan them here, and delete them if not flagged by got_gap
+	
+	Mem::Manager::sHandle().PopContext(); 
+	Dbg_Message("Finished looking for gaps.\n");
+}
+
+// K: Given a gap name, this will return its index withing the gap checklist, or -1 if not found.
+// Used by the end_gap function in skatergapcomponent when a created gap goal is active.
+int CGapChecklist::GetGapChecklistIndex(const char *p_name)
+{
+	int index=0;
+	CGapCheck *p_search =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	while (p_search)						 		
+	{
+		if (!strcmp(p_search->GetText(),p_name))
+		{
+			return index;
+		}	
+		p_search = (CGapCheck*)p_search->GetNext();
+		++index;
+	}
+	return -1;
+}
+
+void CGapChecklist::SetInfoForCamera(const char *p_name, Mth::Vector pos, Mth::Vector dir)
+{
+	CGapCheck *p_search =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	while (p_search)						 		
+	{
+		if (!strcmp(p_search->GetText(),p_name))
+		{
+			p_search->SetInfoForCamera(pos,dir);
+			break;
+		}	
+		p_search = (CGapCheck*)p_search->GetNext();
+	}
+}
+
+void CGapChecklist::GetGapByText(const char *p_text)
+{
+	CGapCheck *pOther =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	while (pOther)						 		
+	{
+		if (!strcmp(pOther->GetText(),p_text))   // if it matches and existing gap 
+		{
+			pOther->GetGapPending();	// flag it as pending
+		
+			break;								// then break out
+		}	
+		else
+		{
+		}
+		pOther = (CGapCheck*)pOther->GetNext();
+	}
+}
+
+void CGapChecklist::AwardPendingGaps()
+{
+	int gap_index=0;
+	CGapCheck *pOther =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	char ran_got_gap_for_first_time = 0;
+
+	while (pOther)						 		
+	{
+		if(pOther->GotGapPending())
+		{
+			// K: The gap is being awarded, so let the goal manager know that the gap has
+			// just been got in case a create-gap-goal is active.
+			Game::CGoalManager* p_goal_manager = Game::GetGoalManager();
+			Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager ?"));
+			if (p_goal_manager->CreatedGapGoalIsActive())
+			{
+				// Let the goal manager know that this gap has just been gone through so that
+				// it can tick it off it's list of required gaps, if it is one of them.
+				p_goal_manager->GetCreatedGoalGap(gap_index);
+			}
+			
+			// The code below is commented out so multiple new gap messages can be displayed
+			if ( pOther->Got() == 0 )//&& !ran_got_gap_for_first_time )
+			{
+				++ran_got_gap_for_first_time;
+				
+				// this is the first time we've gotten the gap, so give them a message
+				Script::CStruct* pScriptParams = new Script::CStruct();
+
+				if( ran_got_gap_for_first_time > 1)
+					pScriptParams->AddInteger( Script::GenerateCRC("multiple_new_gaps"), ( int ) ran_got_gap_for_first_time  );
+
+				pScriptParams->AddString( CRCD(0xc69ce365,"gap_text"), pOther->GetText() );
+				Script::RunScript( CRCD(0x318eaf64,"got_gap_for_first_time"), pScriptParams );
+				delete pScriptParams;
+			}
+			
+			pOther->GetGap();
+		}
+		pOther->UnGetGapPending();			
+		pOther = (CGapCheck*)pOther->GetNext();
+		++gap_index;
+	}
+}
+
+void CGapChecklist::AwardAllGaps()
+{
+
+	CGapCheck *pOther =  (CGapCheck*) m_gapcheck_list.FirstItem();
+
+	while (pOther)						 		
+	{  	
+		pOther->SetCount( pOther->Got() + 1 );			
+		pOther = (CGapCheck*)pOther->GetNext();
+	}
+}
+
+void CGapChecklist::ClearPendingGaps()
+{
+	CGapCheck *pOther =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	while (pOther)						 		
+	{
+		pOther->UnGetGapPending();			
+		pOther = (CGapCheck*)pOther->GetNext();
+	}
+}
+
+int	CGapChecklist::NumGaps()
+{
+	return m_gapcheck_list.CountItems();
+}
+
+
+bool CGapChecklist::GotAllGaps()
+{
+	CGapCheck *pOther =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	while (pOther)						 		
+	{
+		if ( pOther->Got() == 0 )
+			return false;
+		pOther = (CGapCheck*)pOther->GetNext();
+	}
+	return true;
+}
+
+void CGapChecklist::Reset()
+{
+	CGapCheck *pOther =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	while (pOther)						 		
+	{
+		pOther->Reset();
+		pOther = (CGapCheck*)pOther->GetNext();
+	}
+}
+
+bool CGapChecklist::ScriptGetLevelGapTotals( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGapCheck *pGap =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	
+	int total_gaps = 0;
+	int total_got = 0;
+	while (pGap)						 		
+	{
+		CGapCheck* pNext = (CGapCheck*)pGap->GetNext();
+		total_gaps++;
+	
+		if ( pGap->Got() )
+			total_got++;
+
+		pGap = (CGapCheck*)pNext;
+	}
+	pScript->GetParams()->AddInteger( "num_gaps", total_gaps );
+	pScript->GetParams()->AddInteger( "num_collected_gaps", total_got );
+	return true;
+}
+
+
+bool CGapChecklist::ScriptAddGapsToMenu( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 choose_script=CRCD(0xf7e902c,"nullscript");
+	pParams->GetChecksum(CRCD(0xa7d7c610,"choose_script"),&choose_script);
+
+
+	CGapCheck *pGap =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	
+	Script::CStruct* p_gap_params;
+	int entry = 0;
+	while (pGap)						 		
+	{
+		p_gap_params = new Script::CStruct;
+
+		// check if this is the first or last gap
+		CGapCheck* pNext = (CGapCheck*)pGap->GetNext();
+		if ( entry == 0 )
+			p_gap_params->AddChecksum( NONAME, CRCD( 0x171665d5, "first_item" ) );
+		else if ( !pNext )
+			p_gap_params->AddChecksum( NONAME, CRCD( 0x76cf1ebd, "last_item" ) );
+
+		p_gap_params->AddInteger( "gap_score", pGap->GetScore() );
+		p_gap_params->AddString( "gap_name", pGap->GetText() );
+		p_gap_params->AddInteger( "times", pGap->Got() );
+
+		p_gap_params->AddInteger( "gap_number", entry );
+		
+		Mth::Vector v=pGap->GetSkaterStartPos();
+		p_gap_params->AddVector(CRCD(0x26a4e85a,"skater_start_pos"),v[X],v[Y],v[Z]);
+		v=pGap->GetSkaterStartDir();
+		p_gap_params->AddVector(CRCD(0x1cd674e6,"skater_start_dir"),v[X],v[Y],v[Z]);
+		
+		p_gap_params->AddChecksum(CRCD(0xa7d7c610,"choose_script"),choose_script);
+		
+		Script::RunScript( "gap_menu_add_item", p_gap_params );
+		delete p_gap_params;
+
+		pGap = (CGapCheck*)pNext;
+		entry++;
+	}
+
+	if (entry == 0)
+	{
+		return false;
+	}
+	return true;
+}
+
+bool CGapChecklist::ScriptCreateGapList( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CGapCheck *p_gap =  (CGapCheck*) m_gapcheck_list.FirstItem();
+
+	int num_gaps=0;
+	while (p_gap)						 		
+	{
+		p_gap = (CGapCheck*)p_gap->GetNext();
+		++num_gaps;
+	}	
+	pScript->GetParams()->AddInteger(CRCD(0x7f5b3d4d,"array_size"),num_gaps);
+	
+	if (!num_gaps)
+	{
+		pScript->GetParams()->AddInteger(CRCD(0x6a8a0061,"num_gaps_got"),0);
+		return false;
+	}
+		
+	Script::CArray *p_gap_list=new Script::CArray;
+	p_gap_list->SetSizeAndType(num_gaps,ESYMBOLTYPE_STRUCTURE);
+	
+	p_gap =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	
+	int gap_number = 0;
+	int num_gaps_got=0;
+	while (p_gap)						 		
+	{
+		if (p_gap->Got())
+		{
+			++num_gaps_got;
+		}
+			
+		Script::CStruct* p_gap_params = new Script::CStruct;
+
+		p_gap_params->AddInteger( "gap_score", p_gap->GetScore() );
+		p_gap_params->AddString( "gap_name", p_gap->GetText() );
+		p_gap_params->AddInteger( "times", p_gap->Got() );
+
+		p_gap_params->AddInteger( "gap_number", gap_number );
+		
+		Mth::Vector v=p_gap->GetSkaterStartPos();
+		p_gap_params->AddVector(CRCD(0x26a4e85a,"skater_start_pos"),v[X],v[Y],v[Z]);
+		v=p_gap->GetSkaterStartDir();
+		p_gap_params->AddVector(CRCD(0x1cd674e6,"skater_start_dir"),v[X],v[Y],v[Z]);
+		
+		p_gap_list->SetStructure(gap_number,p_gap_params);
+		p_gap = (CGapCheck*)p_gap->GetNext();
+		++gap_number;
+	}
+
+	pScript->GetParams()->AddArrayPointer(CRCD(0x737449de,"GapList"),p_gap_list);
+	pScript->GetParams()->AddInteger(CRCD(0x6a8a0061,"num_gaps_got"),num_gaps_got);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AddgapsToMenu | adds all gaps to the menu
+bool ScriptAddGapsToMenu( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	
+	bool result = 	Mdl::Skate::Instance()->GetGapChecklist()->ScriptAddGapsToMenu(pParams,pScript); 
+	
+	
+	return result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CreateGapList | Similar to AddGapsToMenu, but instead creates an array of structures called GapList
+// and adds it to the calling script's parameters.
+bool ScriptCreateGapList( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	return Mdl::Skate::Instance()->GetGapChecklist()->ScriptCreateGapList(pParams,pScript); 
+}
+
+// @script | GetLevelGapTotals | returns the total number of gaps and the number
+// of gaps that have been completed in the level (num_gaps, num_completed_gaps)
+// @parm int | level | the level number
+bool ScriptGetLevelGapTotals( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int level;
+	pParams->GetInteger( "level", &level, Script::ASSERT );
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkaterCareer* pCareer = skate_mod->GetCareer();
+	
+	bool result = pCareer->GetGapChecklist( level )->ScriptGetLevelGapTotals( pParams, pScript );
+	return result;
+}
+
+// @script | GiveAllGaps | "Gets" all the gaps in level or current level if none is specified
+// @parm int | level | the level number | optional
+bool ScriptGiveAllGaps( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkaterCareer* pCareer = skate_mod->GetCareer();
+
+	int level;
+	if(!pParams->GetInteger( "level", &level, Script::NO_ASSERT ))
+		level = pCareer->GetLevel();
+	
+	pCareer->GetGapChecklist( level )->AwardAllGaps();
+	return true;
+}
+
+
+CGapCheck *CGapChecklist::get_indexed_gapcheck(int gap_index)
+{
+
+	CGapCheck *pGap =  (CGapCheck*) m_gapcheck_list.FirstItem();
+	int 	entry = 0;
+	while (pGap)						 		
+	{
+		if (entry == gap_index)
+		{
+			return pGap;		
+		}
+		pGap = (CGapCheck*)pGap->GetNext();
+		entry++;
+	}
+	Dbg_MsgAssert(0,("Gap index %ds invalid\n",gap_index));	
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CGapChecklist::ScriptGotGap( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int	gap_index = 0;
+	pParams->GetInteger(NONAME,&gap_index,true);		 
+	return get_indexed_gapcheck(gap_index)->Got();
+}
+
+const char *	CGapChecklist::GetGapText(int gap)
+{
+	return get_indexed_gapcheck(gap)->GetText();
+}
+
+int	CGapChecklist::GetGapCount(int gap)
+{
+	return get_indexed_gapcheck(gap)->Got();
+}
+
+int	CGapChecklist::GetGapScore(int gap)
+{
+	return get_indexed_gapcheck(gap)->GetScore();
+}
+
+void CGapChecklist::GetSkaterStartPosAndDir(int gap, Mth::Vector *p_skaterStartPos, Mth::Vector *p_skaterStartDir)
+{
+	CGapCheck *p_gap_check=get_indexed_gapcheck(gap);
+	
+	*p_skaterStartPos=p_gap_check->GetSkaterStartPos();
+	*p_skaterStartDir=p_gap_check->GetSkaterStartDir();
+}
+
+// @script | GotGap | returns true if we have got the indicated gap
+// this really only makes sense in the context of the gap menu (see AddGapsToMenu)
+bool ScriptGotGap( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	return Mdl::Skate::Instance()->GetGapChecklist()->ScriptGotGap(pParams,pScript); 
+}
+
+
+ 
+}	// end of Ojb namespace
+
+
diff --git a/Code/Sk/Objects/gap.h b/Code/Sk/Objects/gap.h
new file mode 100644
index 0000000..c38103c
--- /dev/null
+++ b/Code/Sk/Objects/gap.h
@@ -0,0 +1,211 @@
+////////////////////////////////////////////////////////////
+// gap.h
+
+#ifndef	__GAP_H__
+#define	__GAP_H__
+
+#define	__USE_MJB_LIST__
+	  
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __CORE_MATH_H
+#include 
+#endif
+
+#include 
+#include 
+
+#include 
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+
+namespace Obj
+{
+
+#define	CANCEL_GROUND				0x00000001
+#define	CANCEL_AIR              	0x00000002
+#define	CANCEL_RAIL             	0x00000004
+#define	CANCEL_WALL             	0x00000008
+#define	CANCEL_LIP              	0x00000010
+#define	CANCEL_WALLPLANT			0x00000020
+#define	CANCEL_MANUAL				0x00000040
+#define CANCEL_HANG     	    	0x00000080
+#define CANCEL_LADDER	         	0x00000100
+#define CANCEL_SKATE				0x00000200
+#define CANCEL_WALK					0x00000400
+#define CANCEL_DRIVE				0x00000800
+
+#define	CANCEL_MASK					0x0000FFFF
+
+#define	REQUIRE_GROUND				0x00010000
+#define	REQUIRE_AIR             	0x00020000
+#define	REQUIRE_RAIL            	0x00040000
+#define	REQUIRE_WALL            	0x00080000
+#define	REQUIRE_LIP             	0x00100000
+#define	REQUIRE_WALLPLANT			0x00200000
+#define	REQUIRE_MANUAL				0x00400000
+#define REQUIRE_HANG	        	0x00800000
+#define REQUIRE_LADDER	        	0x01000000
+#define REQUIRE_SKATE				0x02000000
+#define REQUIRE_WALK				0x04000000
+#define REQUIRE_DRIVE				0x08000000
+                                
+#define	REQUIRE_MASK				0xFFFF0000
+
+
+class CGapCheck : public Lst::Node< CGapCheck >
+{
+public:
+						CGapCheck();
+	int					Got();
+	void				GetGap();
+	
+	void				GetGapPending();
+	void				UnGetGapPending();
+	int					GotGapPending();
+	
+	void				SetInfoForCamera(Mth::Vector pos, Mth::Vector dir);
+	Mth::Vector&		GetSkaterStartPos() {return m_skater_start_pos;}
+	Mth::Vector&		GetSkaterStartDir() {return m_skater_start_dir;}
+	
+	
+	void				UnGetGap();
+	const char	*		GetText();
+	void				SetText(const char *p);
+	void				SetCount(int count);
+	void				SetScore(int score) {m_score = score;}
+	int					GetScore() {return m_score;};
+	void 				SetValid(bool valid);
+
+	void				Reset();
+private:
+	Mth::Vector			m_skater_start_pos;
+	Mth::Vector			m_skater_start_dir;
+	
+	int					m_got_pending;			// number of times we have got this gap, but the landing of the combo is still pending
+	int					m_got;					// number of times we have got this gap
+	int					m_score;				// score for this gap
+	bool				m_valid;				// flag used when parsing gaps to remove any that no longer exist (from an old save)
+	Str::String			m_name;
+};
+
+
+
+// CGapChecklist contains a list of gaps for the level
+// these gaps (CGap) count how many times they have been got,
+// and so this list is used to display the in-game gap checklist
+// there is one of these for each level in an array in CSkaterCareer
+// 
+class CGapChecklist 
+{
+
+public:
+										CGapChecklist();
+	void 								FindGaps();
+	void 								GetGapByText(const char *p_text);
+	int									GetGapChecklistIndex(const char *p_name);
+	void								SetInfoForCamera(const char *p_name, Mth::Vector pos, Mth::Vector dir);
+	void 								AwardPendingGaps(); 
+	void 								AwardAllGaps();
+	void 								ClearPendingGaps();
+	bool 								ScriptAddGapsToMenu( Script::CStruct* pParams, Script::CScript* pScript );
+	bool								ScriptCreateGapList( Script::CStruct* pParams, Script::CScript* pScript );
+	
+	bool								ScriptGotGap( Script::CStruct* pParams, Script::CScript* pScript );
+	bool								ScriptGetLevelGapTotals( Script::CStruct* pParams, Script::CScript* pScript );
+	
+	bool								IsValid() {return m_valid;}
+	int									NumGaps();
+
+	const char *						GetGapText(int gap);
+	int									GetGapCount(int gap);
+	int									GetGapScore(int gap);
+	void								GetSkaterStartPosAndDir(int gap, Mth::Vector *p_skaterStartPos, Mth::Vector *p_skaterStartDir);
+
+	void								AddGapCheck(const char *p_name, int count, int score);
+	void								FlushGapChecks();
+
+	bool								GotAllGaps();
+	void								Reset();
+
+// semi-private, dammit...	
+	void 								got_gap(Script::CStruct *pScriptStruct, const uint8 *pPC);
+private:
+
+	Obj::CGapCheck *					get_indexed_gapcheck(int gap_index);
+	bool								m_valid;			// true if we'd parsed the scripts for this level
+	Lst::Head< Obj::CGapCheck >			m_gapcheck_list;	// list of unique gaps in level, or NULL if none
+};
+
+// CGap is a gap we are in the middle of
+class CGap : public Lst::Node< CGap >
+{
+	public:	
+		CGap();
+		~CGap();
+	public:
+		uint32			m_id;					// checksum of the gap's ID
+		uint32			m_flags;				// bit fields saying what cancles it
+		uint32			m_node;					// source node
+        uint32          m_trickChecksum;
+        int             m_numTrickOccurrences;
+        uint32          m_trickscript;
+		uint32			m_trickTextChecksum;
+		int				m_spinMult;
+		bool			m_requirePerfect;
+		int				m_startGapTrickCount;
+		int				m_numTaps;
+
+		// K: The position & direction the skater is facing when the StartGap command was executed.
+		// Used to determine a good camera position when showing the gap in the view-gaps menu.
+		Mth::Vector		m_skater_start_pos;
+		Mth::Vector		m_skater_start_dir;
+				
+		// these arrays are used for arrays of tricks that must
+		// be completed in order to get the gap.
+		Script::CArray* mp_tricks;
+};
+
+// CGaptrick contains list if gaps we are in the middle of, that contain tricks
+class CGapTrick : public Lst::Node< CGapTrick >
+{
+public:
+	CGapTrick();
+    void                GetGapTrick();
+public:
+    Str::String         m_trickString;
+    uint32  			m_id;                   // matches the gap's ID
+	uint32	 			m_node;					// node this came from
+	uint32	 			m_script;				// script to run when sucessful
+    bool                m_got;
+	uint32				m_frame;				// Logic frame number on which this was triggered
+};
+
+// Some gap utility functions
+bool ScriptAddGapsToMenu( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptCreateGapList( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetLevelGapTotals( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGiveAllGaps( Script::CStruct* pParams, Script::CScript* pScript );
+
+}  // end namespace obj
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#endif
diff --git a/Code/Sk/Objects/manual.cpp b/Code/Sk/Objects/manual.cpp
new file mode 100644
index 0000000..f2b139f
--- /dev/null
+++ b/Code/Sk/Objects/manual.cpp
@@ -0,0 +1,616 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		manual.cpp												**
+**																			**
+**	Created:		01/31/01	-	ksh										**
+**																			**
+**	Description:	Skater object code										**
+**																			**
+*****************************************************************************/
+
+//#define		__DEBUG_CHEESE__
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include  
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+CManual::CManual()
+{
+	Reset();
+}
+
+CManual::~CManual()
+{
+	
+}
+
+void CManual::Init ( CCompositeObject *pSkater )
+{
+	mpSkater = pSkater;
+	Dbg_Assert(mpSkater);
+	
+	mpSkaterBalanceTrickComponent = GetSkaterBalanceTrickComponentFromObject(mpSkater);
+	Dbg_Assert(mpSkaterBalanceTrickComponent);
+	
+	mpSkaterScoreComponent = GetSkaterScoreComponentFromObject(mpSkater);
+	Dbg_Assert(mpSkaterScoreComponent);
+	
+	mpInputComponent = GetInputComponentFromObject(mpSkater);
+	Dbg_Assert(mpInputComponent);
+}
+
+void CManual::Reset()
+{
+
+#ifdef		__DEBUG_CHEESE__
+	printf ("%d:   Resetting cheese to 0 in CManual::Reset\n",__LINE__);
+#endif	
+	mManualCheese=0.0f;
+	mActualManualTime=0.0f;
+	mManualTime=0.0f;
+	mManualLean=0.0f;
+	mManualLeanDir=0.0f;
+	mManualReleased=false;
+	mOldRailNode=-1;
+	mWobbleEnabled=false;
+}
+
+// a utility function to scale a linear value based on 
+// the framerate
+// m_time will be 1/60 when running at 60fps
+// and 1/50 when running at 50fps (for pal)
+// So, this 60.0f is correct for PAL
+// as the adjustment is done to m_time					
+float CManual::scale_with_frame_length ( float frame_length, float f )
+{
+	return f * frame_length * 60.0f;	  // Do NOT change this in PAL mode, leave it at 60
+}
+
+// clear the record time for this level, only call this at start of a session
+void CManual::ClearMaxTime()
+{
+	
+	mMaxTime=0.0f;
+}
+
+
+void CManual::SwitchOffMeters()
+{
+	
+	mNeverShowMeters=true;
+}
+
+void CManual::SwitchOnMeters()
+{
+	
+	mNeverShowMeters=false;
+}
+
+// Switched off the meters and sets the buttons to zero so that calling DoManualPhysics will
+// have no effect.
+void CManual::Stop()
+{
+	
+	// Switch off the meters.
+	if (mpSkaterScoreComponent->GetScore())
+	{
+		mpSkaterScoreComponent->GetScore()->SetBalanceMeter(false);
+		mpSkaterScoreComponent->GetScore()->SetManualMeter(false);
+	} 
+	mButtonAChecksum=0;
+	mButtonBChecksum=0;
+}
+
+
+// Check to see if this is a new record				 
+void CManual::UpdateRecord()
+{
+    // Update the longest grind/manual/lip
+	if (mActualManualTime>mMaxTime)
+	{
+		mMaxTime=mActualManualTime;
+	}
+	mActualManualTime = 0.0f;
+}
+
+
+
+void CManual::SetUp(uint32 ButtonAChecksum, uint32 ButtonBChecksum, int Tweak, bool DoFlipCheck, bool PlayRangeAnimBackwards)
+{
+	
+	mActualManualTime = 0.0f;
+	mManualReleased=false;
+	mButtonAChecksum=ButtonAChecksum;
+	mButtonBChecksum=ButtonBChecksum;
+	mTweak=Tweak;
+	mDoFlipCheck=DoFlipCheck;
+	mPlayRangeAnimBackwards=PlayRangeAnimBackwards;
+
+	Dbg_MsgAssert(mpSkater,("NULL mpSkater"));
+
+	// Check for whether the skater has re-railed onto the same rail
+	// or a different one.
+	if (mpSkaterBalanceTrickComponent->mBalanceTrickType==0x255ed86f || // Grind
+		mpSkaterBalanceTrickComponent->mBalanceTrickType==0x8d10119d) // Slide
+	{
+		const CRailNode* p_rail_node = GetSkaterCorePhysicsComponentFromObject(mpSkater)->mp_rail_node;
+		
+		Dbg_MsgAssert(p_rail_node,("What, NULL mp_rail_node ??"));
+		
+		if (mOldRailNode==-1)
+		{
+			// This is a fresh rail.
+		}
+		else
+		{
+		
+			if (p_rail_node->ProbablyOnSameRailAs(mOldRailNode))
+			{
+				// Jumped and landed on the same rail without ending
+				// the combo.
+				// Add some time to make this more difficult.
+				mManualTime+=mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xfbec5b6c, "Same_Grind_Add_Time"));
+				#ifdef		__DEBUG_CHEESE__
+					printf ("%d:  %.2f:  leaving cheese, probably on same rail\n",__LINE__,mManualCheese);
+				#endif					
+			}
+			else
+			{
+				// It's a different rail from the last one, ie jumped from a rail to
+				// another rail without ending the combo.
+				// Reward the player by subtracting a bit of time to make it easier.
+				mManualTime-=mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xc5e1c8eb,"New_Grind_Sub_Time"));
+				if (mManualTime<0.0f)
+				{
+					mManualTime=0.0f;
+				}	
+				// And clear the cheese
+				// as it's not cheesey to change rails
+				// so we don't to penalize the player for doing so
+				#ifdef		__DEBUG_CHEESE__
+					printf ("%d:   Resetting cheese to 0 as it's a new grind\n",__LINE__);
+				#endif	
+
+				mManualCheese = 0.0f;
+			}	
+		}	
+		mOldRailNode=p_rail_node->GetNode();
+		
+		Mdl::Score *pScore=mpSkaterScoreComponent->GetScore();
+		float robot = pScore->GetRobotRailMult();
+		if ( robot < 0.5f)	   // <0.5 means any time you are on the same rail twice in a combo
+		{
+//			printf ("SHOULD ADJUST BALANCE HERE, AS ON A ROBOT LINE\n");
+			// Need to add something to the Manual Time
+			mManualTime += Script::GetFloat(CRCD(0xf54ca12f,"robot_rail_add_time") )* 0.5f / robot;
+			
+			// And give him  percentage nudge away from 0
+			// (actually, an absolute value would be better, more consistent
+			// perhaps not even based on the amount, just trigger 20% whenever  
+			// you land on something robotesque
+			mManualLean += Script::GetFloat(CRCD(0x30990680,"robot_rail_nudge")) * Mth::Sgn(mManualLeanDir) * 0.5f / robot;
+//			printf ("Time = %.2f,  Lean = %.2f, dir = %.2f\n",mManualTime, mManualLean, mManualLeanDir);
+		}
+		
+		
+	}
+
+	float repeat_min = mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x8f4db5d3,"Repeat_Min"));
+	if ( Mth::Abs(mManualLeanDir) != 0.0f)
+	{
+		// first multiplied by the reat multipler
+		mManualLeanDir = mManualLeanDir * mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x93dd6a10,"Repeat_Multiplier"));		
+				
+		// use the sign of previous balance
+		if ( Mth::Abs(mManualLeanDir) < repeat_min)
+		{
+			//  if too small, then set to minimum
+			mManualLeanDir = repeat_min * Mth::Sgn(mManualLeanDir);
+		}	
+		else
+		{
+		}
+	}
+	else
+	{
+		// No previous balance
+		// so just set to default minimum
+		mManualLeanDir = repeat_min;
+		// Adjust the sign of the lean to point in the direction of a bail.
+		switch (mpSkaterBalanceTrickComponent->mBalanceTrickType)
+		{
+			case 0xef24413b: // Manual
+				// Manuals keep positive sign.
+				break;
+			case 0x0ac90769: // NoseManual	
+				// Nosemanuals fall the other way.
+				mManualLeanDir=-mManualLeanDir;
+				break;
+			default:
+				// If in a grind, choose a random side to fall towards.
+				// Hmm, should choose differently for a lip?
+				if (Mth::Rnd(2))
+				{
+					mManualLeanDir=-mManualLeanDir;
+				}
+				break;
+		}		
+
+	}
+	
+	
+	// multiply this initial direction by some value based on the time
+//	mManualLean = Mth::Abs(mManualLean) * Mth::Sgn(mManualLeanDir);		// possibly alter lean to face the bail direction
+
+	mManualLean *= mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x6ce10ea4,"Lean_Repeat_Multiplier"));
+
+	// add cheese after lean multiply (otherwise, it can be ineffective)																				 
+	#ifdef		__DEBUG_CHEESE__
+		printf ("%d:  %.2f + %.2f = %.2f:  Adding cheese to lean \n",__LINE__,mManualLean, mManualCheese * Mth::Sgn(mManualLean),mManualLean += mManualCheese * Mth::Sgn(mManualLean));
+	#endif					
+
+	mManualLean += mManualCheese * Mth::Sgn(mManualLean);				// add in the cheese	to the lean
+	
+	float BailAngle=mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xf81ab0e9,"Lean_Bail_Angle"));
+	if (Mth::Abs(mManualLean) > BailAngle)
+	{
+		#ifdef		__DEBUG_CHEESE__
+			printf ("%d:  %.2f:  Cheese caused an instant bail!!! \n",__LINE__,mManualCheese);
+		#endif					
+		// The cheese has caused an instant bail, so in order that the meter does become
+		// visible, make the lean a wee bit less that the bail angle.
+		mManualLean=BailAngle*0.9f;
+		mManualLean = Mth::Abs(mManualLean) * Mth::Sgn(mManualLeanDir);
+		mManualLeanDir = Mth::Sgn(mManualLeanDir) * BailAngle;  // ensure a bail next frame
+	}
+
+	
+	// Reset the cheese.
+	mManualCheese=mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x1231ae3c,"Cheese"));
+
+	#ifdef		__DEBUG_CHEESE__
+		printf ("%d:  %.2f:  Cheese reset to initial scripted value \n",__LINE__,mManualCheese);
+	#endif					
+
+	
+	// The programmatic wobble applied to the animation is off initially, so that it does not get applied
+	// to the init anim.
+	mWobbleEnabled=false;
+	
+	mStartTime=Tmr::ElapsedTime(0);
+}
+
+// Gets called by the EnableWobble skater script command.
+void CManual::EnableWobble()
+{
+	
+	mWobbleEnabled=true;
+}
+
+//static float MaxTime=0;
+void CManual::DoManualPhysics()
+{
+	float frame_length = Tmr::FrameLength();
+	
+	// Do nothing if no buttons are specified.
+	if (!mButtonAChecksum || !mButtonBChecksum)
+	{
+		return;
+	}
+		
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Net::Server* server;
+	GameNet::PlayerInfo* player;
+	Net::Conn* skater_conn;
+	
+	Dbg_MsgAssert(mpSkater,("NULL mpSkater"));
+	
+	skater_conn = NULL;
+	if(( server = gamenet_man->GetServer()))
+	{
+		if(( player = gamenet_man->GetPlayerByObjectID( mpSkater->GetID() )))
+		{
+			skater_conn = player->m_Conn;
+		}
+	}
+	
+	// decrement the cheese value, which is added to the lean angle when we do the next manual
+	// this is to prevent "cheesing" the manuals (by rapidly doing several manuals that only
+	// last a frame or two, and don't give the balancing physics any time to take effect).
+
+	if (mManualCheese>0.01f)
+	{
+		mManualCheese -= scale_with_frame_length(frame_length, mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x1231ae3c,"Cheese"))/mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xa4362662,"CheeseFrames")));
+		if (mManualCheese < 0.0f)
+			mManualCheese = 0.0f;
+		#ifdef		__DEBUG_CHEESE__
+			printf ("%d:  %.2f:  Cheese decremented\n",__LINE__,mManualCheese);
+		#endif					
+
+	}
+//	printf(Cheese=%f\n",mManualCheese);
+
+	// Add in the tweak to the score.
+	Mdl::Score *pScore=mpSkaterScoreComponent->GetScore();
+	//pScore->TweakTrickRequest(mTweak);
+	pScore->TweakTrick(mTweak);
+
+	mActualManualTime+=frame_length;
+	mManualTime+=frame_length;
+
+    Obj::CStatsManagerComponent* pStatsManagerComponent = GetStatsManagerComponentFromObject(mpSkater);
+	Dbg_Assert( pStatsManagerComponent );
+    
+    pStatsManagerComponent->CheckTimedRecord( mpSkaterBalanceTrickComponent->mBalanceTrickType, mActualManualTime, false );
+	
+	float ManualLeanGravity=mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x6851748,"Lean_Gravity_Stat"));
+	float ManualInstableRate=mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xa9fef5ab,"Instable_Rate"));
+	float ManualBase = mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xb6a634f3,"Instable_base")); 
+	float ManualInstability = ManualBase + (mManualTime * ManualInstableRate);
+
+	
+	// the more you lean, the more you lean
+//	mManualLean += scale_with_frame_length(mManualLean/ManualLeanGravity * (0.5f + mManualTime/2)/ManualInstableRate);
+//	mManualLean += scale_with_frame_length(mManualLeanDir * (0.5f + mManualTime/2)/ManualInstableRate);	 // speed of turn
+	
+	mManualLean += scale_with_frame_length(frame_length, mManualLean * ManualLeanGravity * ManualInstability);
+	mManualLean += scale_with_frame_length(frame_length, mManualLeanDir * ManualInstability);	 	
+
+
+	CSkaterButton *pButt=mpInputComponent->GetControlPad().GetButton(mButtonAChecksum);
+	bool APressed=pButt->GetPressed();
+	
+	pButt=mpInputComponent->GetControlPad().GetButton(mButtonBChecksum);
+	bool BPressed=pButt->GetPressed();
+
+	// The released flag stays false until they let go of the buttons. This is so that if the player
+	// was holding one of the control buttons at the instant the balance trick started, as they probably
+	// will be if doing a manual, then the button won't have any effect on the balance, so as not to
+	// unbalance them straight away.
+	if (!APressed && !BPressed)
+	{
+		mManualReleased = true;
+	}
+	
+	
+	uint32   safe_period = (uint32)Script::GetInt(CRCD(0x8cc8f176,"BalanceSafeButtonPeriod"));
+	uint32	 elapsed_time = Tmr::ElapsedTime(mStartTime);
+
+	// If the buttons have not technically been released, but have been held for longer than a certain
+	// amount of time, then assume that the player does want the button to have an effect after all.
+	if (!mManualReleased && elapsed_time>(uint32)Script::GetInt(CRCD(0x7da1621,"BalanceIgnoreButtonPeriod")))
+	{
+		mManualReleased = true;
+	}
+	
+	
+	if (APressed && mManualReleased)
+	{
+		// this ramping up of the accleration for a few hundred miliseconds
+		// will prevent us from overcompensating
+		// due to the button being held down
+		// and will actually make it easier to "Stick" the mManualLeanDir
+		// the ramping is only in effect if you press in the direction opposite 
+		// from what you need to get more upright
+		// so it prevents you from becoming more unbalanced
+		float mult = 1.0f;
+		if (mManualLean < 0.0f && elapsed_time < safe_period)
+		{
+			mult =  elapsed_time / safe_period; 
+		}
+		
+		mManualLeanDir -= scale_with_frame_length(frame_length, mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x61ad5596,"Lean_Acc"))) * mult;
+	}
+	else if (BPressed && mManualReleased)
+	{
+		float mult = 1.0f;
+		if (mManualLean > 0.0f && elapsed_time < safe_period)
+		{
+			mult =  elapsed_time / safe_period; 
+		}
+		mManualLeanDir += scale_with_frame_length(frame_length, mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x61ad5596,"Lean_Acc"))) * mult;
+	}
+	else
+	{
+		if (Mth::Abs(mManualLeanDir) < mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xef27e8ff,"Lean_Min_Speed")))
+		{
+			int r=(int)mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0x32eb3308,"Lean_Rnd_Speed"));
+			r|=1;
+//			mManualLeanDir = Mth::Rnd(r)-r/2;
+			mManualLeanDir = Mth::Rnd(r)*Mth::Sgn(mManualLeanDir);
+		}
+		else
+		{
+			// always add some randomness to it, to make sure we don't ever get stuck
+			mManualLeanDir += (Mth::Rnd(50))/100.0f * Mth::Sgn(mManualLeanDir);
+		}
+	}
+
+	
+	/*
+	HUD::PanelMgr* panelMgr = HUD::PanelMgr::Instance();
+	HUD::Panel *pPanel=panelMgr->GetPanelBySkaterId(mpSkater->GetID());
+	
+	char pBuf[100];
+	sprintf(pBuf,"Record %.2f",mMaxTime);
+	panelMgr->SendConsoleMessage(pBuf, 2);
+	sprintf(pBuf,"Time %.2f",mActualManualTime);
+	panelMgr->SendConsoleMessage(pBuf, 3);
+	*/
+
+	if (!mpSkater->GetScript())
+	{
+		mpSkater->SetScript(new Script::CScript);
+	}
+
+	if (Mth::Abs(mManualLean)>mpSkaterBalanceTrickComponent->GetBalanceStat(CRCD(0xf81ab0e9,"Lean_Bail_Angle")))
+	{
+		if (mManualLean>0)
+		{
+			mpSkater->SelfEvent(CRCD(0xd77b6ff9,"OffMeterTop"));
+		}
+		else	
+		{
+			mpSkater->SelfEvent(CRCD(0x47d44b84,"OffMeterBottom"));
+		}
+
+		mManualLean=0;
+		mpSkaterBalanceTrickComponent->mBalanceTrickType=0;
+	}	
+
+	if (mWobbleEnabled)
+	{
+		bool flip=mPlayRangeAnimBackwards;
+		if (mDoFlipCheck && GetSkaterCorePhysicsComponentFromObject(mpSkater)->GetFlag(FLIPPED))
+		{
+			flip=!flip;
+		}	
+		// Update the anim frame.
+		Obj::CAnimationComponent* pAnimComponent = GetAnimationComponentFromObject(mpSkater);
+        if ( pAnimComponent )
+        {
+            if (flip)
+            {
+                pAnimComponent->SetWobbleTarget((-mManualLean+4096)/8192, true);
+            }
+            else
+            {
+                pAnimComponent->SetWobbleTarget((mManualLean+4096)/8192, true);
+            }		
+        }
+	}	
+
+
+	// Update the balance meter.
+	GameNet::MsgByteInfo msg;
+	float final_val = -mManualLean/4096;
+	
+	if( server && skater_conn )
+	{
+		msg.m_Data = (char) ( final_val * 127.0f );
+		/*server->EnqueueMessage( skater_conn->GetHandle(), GameNet::MSG_ID_SET_MANUAL_METER, 
+								sizeof(GameNet::MsgByteInfo), &msg );*/
+	}
+	
+	// Need to also check we are still doing a balance trick, since exceptions
+	// that we fired above (offmetertop/bottom) might have canceled it
+	if (!mNeverShowMeters && mpSkaterBalanceTrickComponent->mBalanceTrickType)
+	{
+		if ((mButtonAChecksum==0xbc6b118f/*Up*/ || mButtonAChecksum==0xe3006fc4/*Down*/) &&
+			(mButtonBChecksum==0xbc6b118f/*Up*/ || mButtonBChecksum==0xe3006fc4/*Down*/))
+		{
+			if (mpSkaterScoreComponent->GetScore())
+			{
+				mpSkaterScoreComponent->GetScore()->SetManualMeter( true, final_val );
+			}
+		}
+		else
+		{
+			if (mpSkaterScoreComponent->GetScore())
+			{
+				mpSkaterScoreComponent->GetScore()->SetBalanceMeter( true, final_val );
+			}
+		}
+	}	 
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+
diff --git a/Code/Sk/Objects/manual.h b/Code/Sk/Objects/manual.h
new file mode 100644
index 0000000..eb10a3f
--- /dev/null
+++ b/Code/Sk/Objects/manual.h
@@ -0,0 +1,145 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/manual.h										**
+**																			**
+**	Created: 		01/31/01	-	ksh										**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_MANUAL_H
+#define __OBJECTS_MANUAL_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+namespace Obj
+{
+	class CSkaterBalanceTrickComponent;
+	class CSkaterScoreComponent;
+	class CInputComponent;
+	
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class  CManual  : public Spt::Class
+{
+	
+public:								// for debugging
+
+	CCompositeObject *mpSkater;
+	CSkaterBalanceTrickComponent* mpSkaterBalanceTrickComponent;
+	CSkaterScoreComponent* mpSkaterScoreComponent;
+	CInputComponent* mpInputComponent;
+	
+	Mth::Vector mManualPos;
+	float mActualManualTime;
+	float mManualTime;
+	float mMaxTime;
+	
+	float mManualLean;
+	float mManualLeanDir;
+	float mManualCheese;
+	bool mManualReleased;
+	
+	uint32 mStartTime;
+	uint32 mButtonAChecksum;
+	uint32 mButtonBChecksum;
+	
+	int mTweak;
+	
+	int mOldRailNode;
+	
+	bool mWobbleEnabled;
+	bool mNeverShowMeters;
+
+	// If true, then the animation will be moved in the opposite direction
+	// when the skater is flipped.
+	// This is required when the animation moves from side to side as in
+	// the case of most grinds, but not for anims that move forward and
+	// back, such as manuals.
+	bool mDoFlipCheck;
+	
+	// If set, the range anim will be played backwards. Can be used in conjunction with
+	// mDoFlipCheck.
+	bool mPlayRangeAnimBackwards;
+		
+public:
+	CManual();
+	virtual ~CManual();
+	
+	void Init ( CCompositeObject *pSkater );
+
+	void ResetCheese() {mManualCheese=0.0f;}	
+	void Reset();
+	void ClearMaxTime();
+	void SwitchOffMeters();
+	void SwitchOnMeters();
+	void Stop();
+	void UpdateRecord();
+
+	float	GetMaxTime() {return mMaxTime;}
+	
+	void SetUp(uint32 ButtonAChecksum, uint32 ButtonBChecksum, int Tweak, bool DoFlipCheck, bool PlayRangeAnimBackwards);
+	void EnableWobble();
+	void DoManualPhysics();
+	
+private:
+	inline float scale_with_frame_length ( float frame_length, float f );
+	
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_MANUAL_H
diff --git a/Code/Sk/Objects/moviecam.cpp b/Code/Sk/Objects/moviecam.cpp
new file mode 100644
index 0000000..377d9f4
--- /dev/null
+++ b/Code/Sk/Objects/moviecam.cpp
@@ -0,0 +1,759 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       moviemanager.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  02/07/2002
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+// TODO:  Remove dependency on CSkater class...
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CMovieManager::get_movie_count()
+{
+	return m_movieDetailsList.CountItems();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovieDetails* CMovieManager::get_movie_details( int index )
+{
+	if ( get_movie_count() == 0 )
+	{
+		return NULL;
+	}
+
+	return (CMovieDetails*)m_movieDetailsList.GetItem( index );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovieDetails* CMovieManager::get_movie_details_by_name( uint32 name )
+{
+	for ( int i = 0; i < get_movie_count(); i++ )
+	{
+		CMovieDetails* pDetails = get_movie_details( i );
+		Dbg_Assert( pDetails );
+		if ( pDetails->GetName() == name )
+		{
+			return pDetails;
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovieManager::setup_last_movie_camera( CMovieDetails* pDetails )
+{
+	// Check if this is an overridding camera
+	if ( !( pDetails && pDetails->OverridesCamera() ) )
+	{
+		return;
+	}
+
+	// Don't set up if the game is paused or in the skateshop
+	if ( pDetails->NeedsCameraTransition() &&
+		 !Mdl::FrontEnd::Instance()->GamePaused() &&
+		 (Mdl::Skate::Instance()->m_cur_level != CRCD(0x9f2bafb7,"Load_Skateshop") ) )
+	{
+		m_last_movie_camera = *(pDetails->GetCamera());
+		m_last_movie_frame = Tmr::GetRenderFrame() + 1;		// End it one frame later so there aren't any glitches
+		m_use_last_movie_camera = true;
+	}
+	else
+	{
+//		Script::RunScript( CRCD( 0x15674315, "Restore_skater_camera" ) );  // Temp patch to get the skater camera back at end of cutscenes
+	}
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovieManager::CMovieManager()
+{
+	m_use_last_movie_camera = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovieManager::~CMovieManager()
+{
+	ClearMovieQueue();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::Camera* CMovieManager::GetActiveCamera()
+{
+	CMovieDetails* pDetails = get_movie_details( 0 );
+	if ( pDetails && pDetails->OverridesCamera() )
+	{
+		return pDetails->GetCamera();
+	}
+	else if ( m_use_last_movie_camera )
+	{
+		return &m_last_movie_camera;
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovieManager::ClearMovieQueue( void )
+{
+	if ( !m_movieDetailsList.IsEmpty() )
+	{
+		setup_last_movie_camera( get_movie_details( 0 ) );
+		//Script::RunScript( CRCD( 0x15674315, "Restore_skater_camera" ) );  // Temp patch to get the skater camera back at end of cutscenes
+	}
+	m_movieDetailsList.DestroyAllNodes();
+	//Dbg_Message("Removing all movies\n");
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovieManager::CleanupMovie( CMovieDetails* pDetails )
+{
+	Dbg_MsgAssert( pDetails, ( "No details?" ) );
+
+	// remember the exit script, for use
+	// after the movie has been removed
+	// from the list
+	uint32 exitScriptName = pDetails->GetExitScriptName();
+	Script::CStruct* pTempExitParams = new Script::CStruct;
+	pTempExitParams->AppendStructure( pDetails->GetExitParams() );
+										  
+	// Check if last movie
+	if (get_movie_count() == 1)
+	{
+		setup_last_movie_camera(pDetails);
+	}
+
+	pDetails->Cleanup();
+	pDetails->Remove();
+	delete pDetails;
+	printf( "Details have been removed %ld\n", Tmr::GetRenderFrame() );
+	
+	// GJ:  run the exit script AFTER we delete it
+	// need to do it after the details are deleted
+	// in case the exit script affects the camera
+	// somehow (would cause a glitch otherwise)
+	if ( exitScriptName != 0 )
+	{
+		Script::RunScript( exitScriptName, pTempExitParams, NULL );
+	}
+
+	delete pTempExitParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::AbortCurrentMovie( bool only_if_skippable )
+{
+	bool aborted = false;
+	
+	if ( !only_if_skippable )
+	{
+		CMovieDetails* pDetails = get_movie_details( 0 );
+		if ( pDetails )
+		{
+			CleanupMovie( pDetails );
+			
+			aborted = true;
+		}
+	}
+	else
+	{
+		// delete as many skippable movies as there are...
+		CMovieDetails* pDetails = get_movie_details( 0 );
+		while ( pDetails && pDetails->IsSkippable() )
+		{
+			CleanupMovie( pDetails );
+			
+			pDetails = get_movie_details( 0 );
+			aborted = true;
+		}
+	}
+
+	// now that a new movie is about to begin,
+	// reset its custom keys
+	CMovieDetails* pDetails = get_movie_details( 0 );
+	if ( pDetails )
+	{
+		pDetails->ResetCustomKeys();
+	}
+
+	// restore skater cam
+	// Nx::CViewportManager::sSetCamera( 0, Mdl::Skate::Instance()->GetSkater(0)->GetCamera() );
+
+	// if there are no more movies, then restore the camera
+//	if (aborted)  <-- causes one frame glitch with queued up cameras
+	if ( !aborted && (get_movie_count() == 0) )
+	{
+//		Script::RunScript(CRCD(0x15674315, "Restore_skater_camera"));		// Temp pathc to get the skater camera back at end of cutscenes
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::SetMovieSkippable( uint32 name, bool skippable )
+{
+	if ( m_movieDetailsList.CountItems() == 0 )
+	{
+		// no movies in the queue
+		return false;
+	}
+
+	if ( name == 0 )
+	{
+		// no name specified, so set the current movie
+		CMovieDetails* pDetails = get_movie_details( 0 );
+		Dbg_Assert( pDetails );
+		pDetails->SetSkippable( skippable );
+	}
+	else
+	{
+		// a name was specified, so only do this movie
+		CMovieDetails* pDetails = get_movie_details_by_name( name );
+		if ( pDetails )
+		{
+			pDetails->SetSkippable( skippable );
+		}
+		else
+		{
+			Dbg_MsgAssert( 0, ( "Movie to skip (%s) not found\n", Script::FindChecksumName(name) ) );
+		}
+	}
+
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::SetMoviePauseMode( uint32 name, bool pause_mode )
+{
+	if ( m_movieDetailsList.CountItems() == 0 )
+	{
+		// no movies in the queue
+		return false;
+	}
+
+	if ( name == 0 )
+	{
+		// no name specified, so set the current movie
+		CMovieDetails* pDetails = get_movie_details( 0 );
+		Dbg_Assert( pDetails );
+		pDetails->SetPauseMode( pause_mode );
+	}
+	else
+	{
+		// a name was specified, so only do this movie
+		CMovieDetails* pDetails = get_movie_details_by_name( name );
+		if ( pDetails )
+		{
+			pDetails->SetPauseMode( pause_mode );
+		}
+		else
+		{
+			Dbg_MsgAssert( 0, ( "Movie to set pause mode (%s) not found\n", Script::FindChecksumName(name) ) );
+		}
+	}
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::IsMovieComplete( uint32 name )
+{
+	if ( get_movie_count() == 0 )
+	{
+		return true;
+	}
+
+	if ( name != 0 )
+	{
+		if ( get_movie_details_by_name( name ) )
+		{
+			// the movie still exists in the queue,
+			// so it's obviously not done yet
+			return false;
+		}
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::IsMovieHeld( uint32 name )
+{
+	if ( m_movieDetailsList.CountItems() == 0 )
+	{
+		return false;
+	}
+
+	if ( name != 0 )
+	{
+		CMovieDetails* pDetails = get_movie_details_by_name( name );
+		
+		if ( pDetails )
+		{
+			return pDetails->IsHeld();
+		}
+		else
+		{
+			// movie not found in list!
+			return false;
+		}
+	}
+	else
+	{
+		CMovieDetails* pDetails = get_movie_details( 0 );
+		Dbg_Assert( pDetails );
+		return pDetails->IsHeld();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::RemoveMovieFromQueue( uint32 name )
+{
+	CMovieDetails* pDetails = get_movie_details_by_name( name );
+
+	if ( pDetails )
+	{
+		if ( pDetails == get_movie_details( 0 ) )
+		{
+			// first movie?  then just abort current
+			AbortCurrentMovie( false );
+			return true;
+		}
+		else
+		{
+			Dbg_Message( "Removing %s from movie queue", Script::FindChecksumName(name) );
+			pDetails->Remove();
+			delete pDetails;
+			return true;
+		}
+	}
+	
+	//Dbg_Message( "RemoveMovieFromQueue:  Movie %s not found.", Script::FindChecksumName(name) );
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::AddMovieToQueue( uint32 name, Script::CStruct* pParams, uint32 movieType )
+{
+	CMovieDetails* pDetails = NULL;
+
+    bool old_last_movie_camera = m_use_last_movie_camera;
+
+	// GJ:  a new camera has been queued up, so don't need
+	// to do anything special to fix the 1-frame glitch
+	// (otherwise, some of the cutscenes will have the
+	// wrong camera for the first frame...)  must be done before
+	// the cutscenedetails constructor, which updates the model component,
+	// which contains a check for "IsRolling"
+	m_use_last_movie_camera = false;
+
+	switch ( movieType )
+	{
+		case 0xf5012f52: // cutscene
+			
+			// check for multiple movies in the queue
+			if ( get_movie_count() != 0 )
+			{
+				for ( int i = 0; i < get_movie_count(); i++ )
+				{
+					CMovieDetails* pDetails = get_movie_details( i );
+					if ( pDetails->GetName() == CRCD(0xf5012f52,"cutscene") )
+					{
+						// GJ:  theoretically, we should be able to just clear the movie queue
+						// to get rid of the extra cutscene, but i suspect that there will be
+						// problems with cleanup, and that it would be too risky to do
+						// that so close to the end of the project...
+						Dbg_MsgAssert( 0, ( "Cutscenes should not be queued (only 1 cutscene at a time, please)" ) );
+					}
+				}
+				// if we've currently got skatercamanims in the queue,
+				// then trash them...  (due to the convoluted level
+				// loading process, it's possible for a skatercamanim
+				// to be launched on the same frame as a cutscene;
+				// in this case, we want the cutscene to take
+				// precedence)
+				ClearMovieQueue();
+								
+				// we should set this to false, since
+				// we know we're about to add a new movie
+				// in a moment anyway.  (otherwise, modelcomponent
+				// will assert because it doesn't expect the
+				// cutscene camera to be active...
+				m_use_last_movie_camera = false;
+			}
+
+			{
+				// fix 1-frame glitch with a loading screen here...
+				char* pFileName = NULL;
+				bool freeze = true;
+				bool blank = false;
+				Nx::CLoadScreen::sDisplay( pFileName, freeze, blank );
+			}										  
+
+			pDetails = new CCutsceneDetails;
+			Dbg_Message( "Trying to create new cutscene" );
+			break;
+		case 0x12743edb: // objectanim
+			pDetails = new CObjectAnimDetails;
+			Dbg_Message( "Trying to create new object anim" );
+			break;
+		case 0x66b7dd11: // skatercamanim
+			pDetails = new CSkaterCamDetails;
+			Dbg_Message( "Trying to create new skater cam anim" );
+			break;
+		default:
+			Dbg_MsgAssert( 0, ( "Unrecognized movie type %s", Script::FindChecksumName(movieType) ) );
+			break;
+	}
+
+	Dbg_Assert( pDetails );
+
+	pDetails->SetName( name );
+
+	if ( !pDetails->InitFromStructure( pParams ) )
+	{
+		// failed
+		delete pDetails;
+
+		// restore the old "last movie camera" flag
+		m_use_last_movie_camera = old_last_movie_camera;
+		
+		return false;
+	}
+
+	m_movieDetailsList.AddToTail( pDetails );
+	//Dbg_Message("Adding a movie on frame %ld.  Count now %d\n", Tmr::GetRenderFrame(), get_movie_count());
+
+	// GJ:  if it's the first movie in the queue,
+	// then set the correct camera (to prevent
+	// one frame glitch between movies)
+	if ( get_movie_count() == 1 )
+	{
+		Nx::CViewportManager::sSetCamera( 0, this->GetActiveCamera() );
+	}
+
+	// TODO:  Support the following parameters
+//	pDetails->SetTarget( Obj::MovingObject* pObject );	
+//	pDetails->SetFocusSkater( pParams->ContainsFlag("focus_skater") );
+//	pDetails->SetFocusTarget( pParams->ContainsFlag("focus_target") );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovieManager::ApplyLastCamera()
+{
+   	Nx::CViewportManager::sSetCamera( 0, &m_last_movie_camera );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::Update()
+{
+	// Clear out old camera if we advanced a frame
+	if (m_use_last_movie_camera && (m_last_movie_frame <= Tmr::GetRenderFrame()))
+	{
+		//Dbg_Message("Turning off camera on frame %d", Tmr::GetRenderFrame());
+		m_use_last_movie_camera = false;
+
+		if ( get_movie_count() == 0 )
+		{
+//			Script::RunScript(CRCD(0x15674315, "Restore_skater_camera"));		// Temp pathc to get the skater camera back at end of cutscenes
+		}
+	}
+
+	CMovieDetails* pDetails = get_movie_details( 0 );
+
+	// there are no movies in the queue,
+	// so return "no movies" code
+	// (the camera manager should then immediately
+	// pass control back to the skater cam)
+	if ( !pDetails )
+	{
+		return false;
+	}
+
+#if 0
+	printf( "Movie manager update %d:\n", get_movie_count() );
+	for ( int i = 0; i < get_movie_count(); i++ )
+	{
+		printf( "\tmovie name = %s\n", Script::FindChecksumName(get_movie_details(i)->GetName()) );
+	}
+#endif
+	
+	if ( Mdl::FrontEnd::Instance()->GamePaused() && ( Mdl::CViewer::sGetViewMode() == 0 ) )
+	{
+		// printf("game is paused in normal view mode\n");
+		if ( !pDetails->ShouldPauseMovie() )
+		{
+			pDetails->Update();
+		}
+	}
+	else
+	{
+		// printf("view mode is not normal\n");
+		pDetails->Update();
+	}
+		
+	// if the animation is done,
+	// the remove it from the list
+	if ( pDetails->IsComplete() )
+	{
+		// TODO:  run callback function if necessary?
+
+		AbortCurrentMovie( false );
+	}
+
+	return ( get_movie_count() != 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CMovieManager::GetMovieParams( uint32 name )
+{
+	if ( m_movieDetailsList.CountItems() == 0 )
+	{
+		// no movies in the queue
+		return NULL;
+	}
+
+	if ( name == 0 )
+	{
+		// no name specified, so set the current movie
+		CMovieDetails* pDetails = get_movie_details( 0 );
+		Dbg_Assert( pDetails );
+		if ( pDetails )
+			return pDetails->GetParams();
+	}
+	else
+	{
+		// a name was specified, so only do this movie
+		CMovieDetails* pDetails = get_movie_details_by_name( name );
+		if ( pDetails )
+			return pDetails->GetParams();
+	}
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	CMovieDetails* pDetails = get_movie_details( 0 );
+	Dbg_MsgAssert( pDetails, ("No current movie?") );
+	bool success = pDetails->CallMemberFunction( checksum, pParams, pScript );
+	if ( success )
+	{
+		return success;
+	}
+
+	return Obj::CObject::CallMemberFunction( checksum, pParams, pScript );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::OverridesCamera()
+{
+	CMovieDetails* pDetails = get_movie_details( 0 );
+
+	if ( pDetails )
+	{
+		return pDetails->OverridesCamera();
+	}
+
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CMovieManager::GetCurrentMovieName()
+{
+	CMovieDetails* pDetails = get_movie_details( 0 );
+
+	if ( pDetails )
+	{
+		return pDetails->GetName();
+	}
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::HasMovieStarted()
+{
+	CMovieDetails* pDetails = get_movie_details( 0 );
+
+	if ( pDetails )
+	{
+		return pDetails->HasMovieStarted();
+	}
+
+	Dbg_MsgAssert( 0, ( "Shouldn't call this without knowing there's a movie queued %d", get_movie_count() ) );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieManager::IsMovieQueued()
+{
+	return ( get_movie_count() != 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/moviecam.h b/Code/Sk/Objects/moviecam.h
new file mode 100644
index 0000000..6e1c7ad
--- /dev/null
+++ b/Code/Sk/Objects/moviecam.h
@@ -0,0 +1,121 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       moviemanager.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  02/07/2002
+//****************************************************************************
+
+#ifndef __OBJECTS_MOVIEMANAGER_H
+#define __OBJECTS_MOVIEMANAGER_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+// CMovieManager
+class CMovieManager : public Obj::CObject
+{
+	public:
+	CMovieManager();
+	virtual			~CMovieManager();
+
+public:
+	virtual bool				Update();
+	void						ClearMovieQueue();
+	bool						AddMovieToQueue( uint32 name, Script::CStruct* pParams, uint32 movieType );
+	bool						RemoveMovieFromQueue( uint32 name );
+	void						ApplyLastCamera();
+	bool						SetMovieSkippable( uint32 name, bool skippable );
+	bool						SetMoviePauseMode( uint32 name, bool pause_mode );
+	Script::CStruct*			GetMovieParams( uint32 name );
+	bool						AbortCurrentMovie( bool only_if_skippable );
+	bool						IsMovieComplete( uint32 name );
+	bool						IsMovieHeld( uint32 name );
+	bool						IsRolling() { return ( get_movie_count() != 0 ); }// || m_use_last_movie_camera;}
+	bool						OverridesCamera();
+	Gfx::Camera*				GetActiveCamera();
+	uint32						GetCurrentMovieName();
+	void						CleanupMovie( CMovieDetails* pMovieDetails );
+	bool						HasMovieStarted();
+	bool						IsMovieQueued();
+
+public:
+	bool						CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript ); 
+
+protected:
+	int							get_movie_count();
+	CMovieDetails*				get_movie_details( int index );
+	CMovieDetails*				get_movie_details_by_name( uint32 name );
+	void						update_movie_frame();
+	void						setup_last_movie_camera( CMovieDetails* pDetails );
+
+protected:
+	Lst::Head	m_movieDetailsList;
+
+	// Currently, when we switch movie cameras, we delete the old one before adding
+	// a new one.  Because the new one isn't added until a frame later, we need the
+	// ability to have a smooth transition.  So we keep the old camera around for
+	// an extra frame.
+	// In the future, this code should go away and the cameras should be switched
+	// at the same time.
+	Gfx::Camera					m_last_movie_camera;
+	uint32						m_last_movie_frame;
+	bool						m_use_last_movie_camera;
+};
+	
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_MOVIEMANAGER_H
+
diff --git a/Code/Sk/Objects/moviedetails.cpp b/Code/Sk/Objects/moviedetails.cpp
new file mode 100644
index 0000000..841f7a1
--- /dev/null
+++ b/Code/Sk/Objects/moviedetails.cpp
@@ -0,0 +1,959 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       moviedetails.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  06/17/2002
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+								
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovieDetails::CMovieDetails() : Lst::Node( this )
+{
+	mp_exitParams = NULL;
+
+	// defaults to not skippable
+	m_skippable = false;
+	m_holdOnLastFrame = false;
+
+	m_allowPause = false;
+
+	mp_camera = new Gfx::Camera;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CMovieDetails::~CMovieDetails()
+{
+	if ( mp_exitParams )
+	{
+		delete mp_exitParams;
+	}
+   
+	if ( mp_camera )
+	{
+		if (!Nx::CViewportManager::sMarkCameraForDeletion(mp_camera))
+		{
+			delete mp_camera;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovieDetails::SetParams( Script::CStruct* pParams )
+{
+	Dbg_Assert( pParams );
+
+	Dbg_MsgAssert( m_exitScript == 0, ( "exit script was already assigned to %s", Script::FindChecksumName( m_name ) ) );
+
+	if ( !mp_exitParams )
+	{
+		mp_exitParams = new Script::CStruct;
+	}
+
+	mp_exitParams->Clear();
+
+	if ( pParams->GetChecksum( CRCD(0x1a005041,"exitScript"), &m_exitScript, Script::NO_ASSERT ) )
+	{
+		Script::CStruct* pSubParams = NULL;
+		if ( pParams->GetStructure( CRCD(0x894fd988,"exitParams"), &pSubParams, Script::NO_ASSERT ) )
+		{
+			mp_exitParams->AppendStructure( pSubParams );
+		}
+	}
+
+	int skippable = 0;
+	pParams->GetInteger( CRCD(0x8dde72c0,"skippable"), &skippable, Script::NO_ASSERT );
+	m_skippable = skippable;
+
+	// if we want the movie to continue to hold on the last frame (i.e. don't go on to the next anim)
+	m_holdOnLastFrame = pParams->ContainsFlag( CRCD(0xeb300c29,"play_hold") );
+
+	int allow_pause = 0;
+	pParams->GetInteger( CRCD(0xb8ca12c5,"allow_pause"), &allow_pause, Script::NO_ASSERT );
+	m_allowPause = ( allow_pause != 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* CMovieDetails::GetParams()
+{
+	Script::CStruct* pReturnParams = new Script::CStruct();
+	pReturnParams->AddInteger( CRCD(0x8dde72c0,"skippable"), m_skippable );
+	pReturnParams->AddInteger( CRCD(0xb8ca12c5,"allow_pause"), m_allowPause );
+	return pReturnParams;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CMovieDetails::Cleanup()
+{
+	// run the exit script
+
+//	if ( m_exitScript != 0 )
+	{
+//		Dbg_Message( "Running exit script (%s)\n", Script::FindChecksumName(m_exitScript) );
+
+//		Script::RunScript( m_exitScript, mp_exitParams, NULL );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieDetails::Abort( bool only_if_skippable )
+{
+	if ( !only_if_skippable )
+	{
+		m_aborted = true;
+	}
+	if ( m_skippable )
+	{
+		m_aborted = true;
+	}
+
+	return m_aborted;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieDetails::SetSkippable( bool skippable )
+{
+	m_skippable = skippable;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieDetails::SetPauseMode( bool pause_mode )
+{
+	m_shouldPause = pause_mode;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieDetails::SetName( uint32 name )
+{
+	m_name = name;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32 CMovieDetails::GetName()
+{
+	return m_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CMovieDetails::CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CObjectAnimDetails::CObjectAnimDetails() : CMovieDetails()
+{
+//	Gfx::CBonedAnimFrameData*	mp_frameData;
+	
+	mp_frameData = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CObjectAnimDetails::~CObjectAnimDetails()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CObjectAnimDetails::InitFromStructure( Script::CStruct* pParams )
+{
+	this->SetParams( pParams );
+
+	if ( !pParams->ContainsFlag( CRCD(0xee721991,"virtual_cam") ) )
+	{
+		Ass::CAssMan* ass_man = Ass::CAssMan::Instance();
+		Gfx::CBonedAnimFrameData* pData = (Gfx::CBonedAnimFrameData*)ass_man->GetAsset( this->GetName(), false );
+		if ( !pData )
+		{
+			Dbg_Message( "Warning: couldn't find movie with name %s", Script::FindChecksumName( this->GetName() ) );
+			return false;
+		}
+		
+		this->set_frame_data( pData, 
+							pParams->ContainsFlag( CRCD(0x5ea0e211,"loop") ) ? Gfx::LOOPING_CYCLE : Gfx::LOOPING_HOLD,
+							pParams->ContainsFlag( CRCD(0xf8cfd515,"backwards") ) );
+	}
+	else
+	{
+		int numFrames = 0;
+		pParams->GetInteger( CRCD(0x019176c5,"frames"), &numFrames, Script::ASSERT );
+		this->set_movie_length( numFrames / 60.0f );
+	}
+
+	// by default, pause movie when game is paused 
+	this->SetPauseMode( true );
+	   
+	// now that the new movie is about to start,
+	// reset all of its custom keys
+	this->ResetCustomKeys();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObjectAnimDetails::Update()
+{
+	update_moving_objects();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObjectAnimDetails::set_frame_data( Gfx::CBonedAnimFrameData* pFrameData, Gfx::EAnimLoopingType loopingType, bool reverse )
+{
+	Dbg_Assert( pFrameData );
+
+	mp_frameData = pFrameData;
+
+	if ( !reverse )
+	{
+		m_animController.PlaySequence( 0, 0.0f, pFrameData->GetDuration(), loopingType, 0.0f, 1.0f );
+	}
+	else
+	{
+// 26Mar03 JCB - When playing backwards, start on the final frame, not beyond the end of the anim.
+//
+//		m_animController.PlaySequence( 0, pFrameData->GetDuration(), 0.0f, loopingType, 0.0f, 1.0f );
+		m_animController.PlaySequence( 0, pFrameData->GetDuration() - (1.0f/60.0f), 0.0f, loopingType, 0.0f, 1.0f );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObjectAnimDetails::set_movie_length( float duration )
+{
+	mp_frameData = NULL;
+
+	m_animController.PlaySequence( 0, 0.0f, duration, Gfx::LOOPING_HOLD, 0.0f, 1.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CObjectAnimDetails::ResetCustomKeys()
+{
+	// eventually, this will go in the decompressed version of the
+	// frame data, rather than the frame data itself...
+	if ( mp_frameData )
+	{
+		mp_frameData->ResetCustomKeys();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CObjectAnimDetails::process_custom_keys(float startTime, float endTime, bool end_time_inclusive)
+{
+	// This code currently assumes that you're not going to pause the
+	// camera.  If so, then you run the risk of calling these user keys
+	// more than once...
+
+	// TODO:  Handle this gracefully!
+
+	if ( mp_frameData )
+	{
+		Dbg_Assert( mp_camera );
+
+		// It depends on the direction also...
+		mp_frameData->ProcessCustomKeys( startTime, endTime, mp_camera, end_time_inclusive );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CObjectAnimDetails::update_moving_objects()
+{
+	float oldTime = m_animController.GetCurrentAnimTime();
+
+	m_animController.Update();
+	
+	// Print out some useful information...
+	//if ( Script::GetInt( CRCD(0x2f510f45,"moviecam_debug"), false ) )
+	{
+		//Dbg_Message( "Moving object anim %s @ %f seconds (%d frames)", Script::FindChecksumName(m_name), m_animController.GetCurrentAnimTime(), (int)(m_animController.GetCurrentAnimTime() * 60.0f) );
+	}
+
+	// process associated keys since last frame
+	process_custom_keys( oldTime, m_animController.GetCurrentAnimTime(), false );
+
+	Mth::Quat* pQuat = new Mth::Quat[mp_frameData->GetNumBones()];
+	Mth::Vector* pVector = new Mth::Vector[mp_frameData->GetNumBones()];
+	mp_frameData->GetInterpolatedCameraFrames( pQuat, pVector, m_animController.GetCurrentAnimTime() );
+
+	// get the position of each bone
+	for ( int i = 0; i < mp_frameData->GetNumBones(); i++ )
+	{
+		// grab the frame from the animation controller
+		Dbg_Assert( mp_frameData );
+		
+		uint32 objName;
+		objName = mp_frameData->GetBoneName( i );
+
+		Obj::CObject* pObject = Obj::ResolveToObject( objName );
+		Dbg_MsgAssert( pObject, ( "Can't find lookup target %s", Script::FindChecksumName( objName ) ) );
+		if ( pObject )
+		{
+			Obj::CCompositeObject* pCompositeObject = (Obj::CCompositeObject*)pObject;
+
+			// update the object's orientation
+			Mth::Matrix theMatrix;
+			
+			Mth::Vector nullTrans;
+			nullTrans.Set(0.0f,0.0f,0.0f,1.0f);		// Mick: added separate initialization
+			Mth::QuatVecToMatrix( pQuat + i, &nullTrans, &theMatrix );
+			
+			pCompositeObject->SetMatrix( theMatrix );
+			pCompositeObject->SetDisplayMatrix( theMatrix );
+
+			// update the object's position
+			pCompositeObject->m_pos = pVector[i];
+
+			if ( Script::GetInt( CRCD(0x2f510f45,"moviecam_debug") ) )
+			{
+				printf( "Object %s @ time %f:  ", Script::FindChecksumName(objName), m_animController.GetCurrentAnimTime() );
+				pCompositeObject->m_pos.PrintContents();
+				pCompositeObject->m_matrix.PrintContents();
+			}
+		
+			// if we want to change the position/rotation,
+			// then we also need to update the model component
+			CModelComponent* pModelComponent = GetModelComponentFromObject( pCompositeObject );
+			if ( pModelComponent )
+			{
+				pModelComponent->Update();
+			}
+		}
+		else
+		{
+			printf( "object animation:  couldn't find object %s\n", Script::FindChecksumName(objName) );
+		}
+	}
+	
+	delete[] pQuat;
+	delete[] pVector;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CObjectAnimDetails::IsComplete()
+{
+	if ( m_aborted )
+	{
+		return true;
+	}
+
+	if ( m_holdOnLastFrame )
+	{
+		return false;
+	}
+
+	return m_animController.IsAnimComplete();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CObjectAnimDetails::IsHeld()
+{
+	/* Added by Ken. Used to determine when camera anims used in	  */
+	/* the skate shop front-end have got to their last frame, so that */
+	/* scripts can trigger things to happen such as the video menu    */
+	/* making the video cassettes pan out to the foreground.          */
+	
+	return ( m_holdOnLastFrame && m_animController.IsAnimComplete() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CObjectAnimDetails::OverridesCamera()
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterCamDetails::CSkaterCamDetails() : CMovieDetails()
+{
+	mp_frameData = NULL;
+	
+	// defaults to no target
+	m_hasTarget = false;
+	m_targetID = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterCamDetails::~CSkaterCamDetails()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCamDetails::InitFromStructure( Script::CStruct* pParams )
+{
+	this->SetParams( pParams );
+
+	if ( !pParams->ContainsFlag( CRCD(0xee721991,"virtual_cam") ) )
+	{
+		Ass::CAssMan* ass_man = Ass::CAssMan::Instance();
+		Gfx::CBonedAnimFrameData* pData = (Gfx::CBonedAnimFrameData*)ass_man->GetAsset( this->GetName(), false );
+		if ( !pData )
+		{
+			Dbg_Message( "Warning: couldn't find movie with name %s", Script::FindChecksumName( this->GetName() ) );
+			return false;
+		}
+		
+		this->set_frame_data( pData, 
+							pParams->ContainsFlag( CRCD(0x5ea0e211,"loop") ) ? Gfx::LOOPING_CYCLE : Gfx::LOOPING_HOLD,
+							pParams->ContainsFlag( CRCD(0xf8cfd515,"backwards") ) );
+	}
+	else
+	{
+		int numFrames = 0;
+		pParams->GetInteger( CRCD(0x019176c5,"frames"), &numFrames, Script::ASSERT );
+		this->set_movie_length( numFrames / 60.0f );
+	}
+
+	// by default, pause movie when game is paused 
+	this->SetPauseMode( true );
+	   
+	// look for target parameters
+	this->set_target_params( pParams );
+	
+	// now that the new movie is about to start,
+	// reset all of its custom keys
+	this->ResetCustomKeys();
+
+	update_camera();
+
+	// process initial keys, end time inclusive...
+	// this to fix an FOV glitch on the first frame
+	process_custom_keys( m_animController.GetCurrentAnimTime(), m_animController.GetCurrentAnimTime(), true );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::Update()
+{
+	update_camera_time();
+
+	update_camera();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::set_target_params( Script::CStruct* pParams )
+{
+	Dbg_Assert( pParams );
+
+	m_hasTarget = pParams->GetChecksum( CRCD(0x531e4d28,"targetID"), &m_targetID, Script::NO_ASSERT );
+	
+	if ( m_hasTarget )
+	{
+		// target offset, if any
+		m_targetOffset.Set(0.0f,0.0f,0.0f);
+		pParams->GetVector( CRCD(0x906642bf,"targetOffset"), &m_targetOffset, Script::NO_ASSERT );
+		m_hasTargetOffset = ( m_targetOffset[X] != 0.0f || m_targetOffset[Y] != 0.0f || m_targetOffset[Z] != 0.0f );
+
+		// position offset, if any
+		m_positionOffset.Set(0.0f,0.0f,0.0f);
+		m_hasPositionOffset = pParams->GetVector( CRCD(0xa00d3d6e,"positionOffset"), &m_positionOffset, Script::NO_ASSERT );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::clear_target_params()
+{
+	m_hasTarget = false;
+	m_targetID = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::set_frame_data( Gfx::CBonedAnimFrameData* pFrameData, Gfx::EAnimLoopingType loopingType, bool reverse )
+{
+	Dbg_Assert( pFrameData );
+
+	mp_frameData = pFrameData;
+
+	if ( !reverse )
+	{
+		m_animController.PlaySequence( 0, 0.0f, pFrameData->GetDuration(), loopingType, 0.0f, 1.0f );
+	}
+	else
+	{
+// 26Mar03 JCB - When playing backwards, start on the final frame, not beyond the end of the anim.
+//
+//		m_animController.PlaySequence( 0, pFrameData->GetDuration(), 0.0f, loopingType, 0.0f, 1.0f );
+		m_animController.PlaySequence( 0, pFrameData->GetDuration() - (1.0f/60.0f), 0.0f, loopingType, 0.0f, 1.0f );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::set_movie_length( float duration )
+{
+	mp_frameData = NULL;
+
+	m_animController.PlaySequence( 0, 0.0f, duration, Gfx::LOOPING_HOLD, 0.0f, 1.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCamDetails::ResetCustomKeys()
+{
+	// eventually, this will go in the decompressed version of the
+	// frame data, rather than the frame data itself...
+	if ( mp_frameData )
+	{
+		mp_frameData->ResetCustomKeys();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::get_current_frame(Mth::Quat* pQuat, Mth::Vector* pTrans)
+{
+	Dbg_Assert( pQuat );
+	Dbg_Assert( pTrans );
+
+	// grab the frame from the animation controller
+	Dbg_Assert( mp_frameData );
+	Dbg_Assert( mp_frameData->GetNumBones() == 1 );
+	mp_frameData->GetInterpolatedCameraFrames( pQuat, pTrans, m_animController.GetCurrentAnimTime() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCamDetails::process_custom_keys(float startTime, float endTime, bool end_time_inclusive)
+{
+	// This code currently assumes that you're not going to pause the
+	// camera.  If so, then you run the risk of calling these user keys
+	// more than once...
+
+	// TODO:  Handle this gracefully!
+
+	if ( mp_frameData )
+	{
+		Dbg_Assert( mp_camera );
+
+		// It depends on the direction also...
+		mp_frameData->ProcessCustomKeys( startTime, endTime, mp_camera, end_time_inclusive );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::update_camera_time()
+{
+	float oldTime = m_animController.GetCurrentAnimTime();
+
+	m_animController.Update();
+
+	// Not sure if this is still needed for the sound code
+	// Maybe we can add the functionality automatically to the Gfx::Camera?
+	mp_camera->StoreOldPos();
+	
+	// process associated keys since last frame
+	process_custom_keys( oldTime, m_animController.GetCurrentAnimTime(), false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCamDetails::update_camera()
+{
+	Dbg_Assert( mp_camera );
+
+	Mth::Vector& camPos = mp_camera->GetPos();
+	Mth::Matrix& camMatrix = mp_camera->GetMatrix();
+	
+	Mdl::FrontEnd* pFront = Mdl::FrontEnd::Instance();
+
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		if ( pFront->GetInputHandler( i )->m_Input->m_Makes & Inp::Data::mD_X )
+		{
+			// abort if skippable
+			Abort( true );
+		}
+	}
+	
+	if ( mp_frameData )
+	{
+		Mth::Quat theQuat;
+		Mth::Vector theTrans;
+
+		get_current_frame( &theQuat, &theTrans );
+		
+		// update the camera position
+		camPos = theTrans;
+
+		// update the camera orientation
+		Mth::Vector nullTrans;
+		nullTrans.Set(0.0f,0.0f,0.0f,1.0f);		// Mick: added separate initialization
+		Mth::QuatVecToMatrix( &theQuat, &nullTrans, &camMatrix );
+	}
+	else
+	{
+		// clear out the cam matrix to the identity
+		// if there's no associated animation.
+		// the assumption is that the following m_hasTarget
+		// code block will overrride it with the correct data...
+		camPos = Mth::Vector( 0.0f, 0.0f, 0.0f );
+		camMatrix.Ident();
+		
+		// if there's no frame data, then we should
+		// reset the camera to some default FOV,
+		// or else it will use the last FOV used
+		float fov_in_degrees = Script::GetFloat( CRCD(0x99529205, "camera_fov") );
+		mp_camera->SetHFOV(fov_in_degrees);
+	}
+	
+	if ( m_hasTarget )
+	{
+		if ( m_targetID != CRCD(0xc588eebc,"world") )
+		{
+			// calculate the orientation from the target
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			Obj::CGeneralManager* pObjectManager = skate_mod->GetObjectManager();
+			Dbg_Assert( pObjectManager );
+			Obj::CObject* pObject = pObjectManager->GetObjectByID( m_targetID );
+			Dbg_MsgAssert( pObject, ( "Can't find lookup target %s", Script::FindChecksumName(m_targetID) ) );
+			if ( pObject )
+			{
+				//Dbg_MsgAssert( static_cast( pObject ), ( "This function only works on moving objects." ) ); 
+				Obj::CCompositeObject* pCompositeObject = (Obj::CCompositeObject*)pObject;
+	
+				// override it with the moving object's position
+				Mth::Vector pos = pCompositeObject->GetPos();
+				
+				// patch for positions that don't have 1.0 in the final column...
+				// not sure why this would be the case, but it seems to happen
+				// with the skater...  (possibly a result of skater's mp_physics
+				// disappearing)
+				pos[W] = 1.0f;
+	
+				// not properly working yet...
+				if ( m_hasTargetOffset )
+				{    
+					Mth::Matrix mat = pCompositeObject->GetMatrix();
+					mat[Mth::POS] = pos;				                                                         
+					mat.TranslateLocal( m_targetOffset );
+					pos = mat[Mth::POS];
+				}
+	
+				if ( m_hasPositionOffset )
+				{
+					camPos = pos;
+	
+					Mth::Matrix mat = pCompositeObject->GetMatrix();
+					mat[Mth::POS] = pos;
+					mat.TranslateLocal( m_positionOffset );
+					camPos = mat[Mth::POS];
+				}
+	
+				// set rotation from the camera's position (always up)
+				Mth::Vector atVec = camPos - pos;
+				atVec.Normalize();
+				
+				camMatrix.Ident();
+				camMatrix[Mth::AT]		= atVec;
+				camMatrix[Mth::RIGHT]	= Mth::CrossProduct( camMatrix[Mth::UP], camMatrix[Mth::AT] );
+				camMatrix[Mth::RIGHT].Normalize();
+				camMatrix[Mth::UP]		= Mth::CrossProduct( camMatrix[Mth::AT], camMatrix[Mth::RIGHT] );
+			}
+		}
+		else
+		{
+			// World coordinates are a lot simpler
+			Mth::Vector pos = m_targetOffset;
+			camPos = m_positionOffset;
+			Mth::Vector atVec = camPos - pos;
+			atVec[W] = 1.0f;
+			atVec.Normalize();
+			camMatrix.Ident();
+			camMatrix[Mth::AT]		= atVec;
+			camMatrix[Mth::RIGHT]	= Mth::CrossProduct( camMatrix[Mth::UP], camMatrix[Mth::AT] );
+			camMatrix[Mth::RIGHT].Normalize();
+			camMatrix[Mth::UP]		= Mth::CrossProduct( camMatrix[Mth::AT], camMatrix[Mth::RIGHT] );
+		}		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCamDetails::CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript )
+{
+	switch ( checksum )
+	{
+		case 0xa8d5a188:		// SetTargetObject
+			{
+				Dbg_Message( "*** Calling SetTargetObject" );
+				
+				set_target_params( pParams );
+				
+				return true;
+			}
+		case 0xd7241a6c:		// ClearTargetObject
+			{
+				Dbg_Message( "*** Calling ClearTargetObject" );
+				
+				clear_target_params();
+				
+				return true;
+			}
+			break;
+	}
+
+	return CMovieDetails::CallMemberFunction( checksum, pParams, pScript );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCamDetails::IsComplete()
+{
+	if ( m_aborted )
+	{
+		return true;
+	}
+
+	if ( m_holdOnLastFrame )
+	{
+		return false;
+	}
+
+	return m_animController.IsAnimComplete();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCamDetails::IsHeld()
+{
+	/* Added by Ken. Used to determine when camera anims used in	  */
+	/* the skate shop front-end have got to their last frame, so that */
+	/* scripts can trigger things to happen such as the video menu    */
+	/* making the video cassettes pan out to the foreground.          */
+	
+	return ( m_holdOnLastFrame && m_animController.IsAnimComplete() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCamDetails::OverridesCamera()
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/moviedetails.h b/Code/Sk/Objects/moviedetails.h
new file mode 100644
index 0000000..17dbada
--- /dev/null
+++ b/Code/Sk/Objects/moviedetails.h
@@ -0,0 +1,216 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       moviedetails.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  06/17/2002
+//****************************************************************************
+
+#ifndef __OBJECTS_MOVIEDETAILS_H
+#define __OBJECTS_MOVIEDETAILS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+#include 
+
+#include 						 
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Gfx
+{
+	class Camera;
+	class CBonedAnimFrameData;
+}
+
+namespace Mth
+{
+	class Matrix;
+	class Quat;
+	class Vector;
+}
+				   
+namespace Script
+{
+	class CScript;
+	class CStruct;
+}
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+// CMovieDetails
+class CMovieDetails : public Lst::Node
+{
+public:
+	CMovieDetails();
+	virtual 				~CMovieDetails();
+
+public:
+	virtual bool			InitFromStructure( Script::CStruct* pParams ) = 0;
+	virtual void			Update() = 0;
+	virtual	bool			ResetCustomKeys() = 0;
+	virtual bool			CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript ); 
+	virtual bool			IsComplete() = 0;
+	virtual bool			IsHeld() = 0;
+	virtual bool			OverridesCamera() = 0;
+	virtual void			Cleanup();
+	virtual bool			HasMovieStarted() = 0;
+	virtual bool			NeedsCameraTransition() { return false; }		// Only set to true on classes that need the
+																			// old camera to stay around an extra frame
+												  
+public:
+	void			    	SetParams( Script::CStruct* pParams );
+	Script::CStruct* 		GetParams();
+	bool					SetSkippable( bool skippable );
+	bool					IsSkippable() { return m_skippable; }
+	bool					SetName( uint32 name );
+	uint32					GetName();
+	bool					Abort( bool only_if_skippable );
+
+	bool					ShouldPauseMovie() { return m_shouldPause; }
+	bool					SetPauseMode( bool pause_mode );
+
+	Gfx::Camera*			GetCamera() { return mp_camera; }
+	
+	uint32					GetExitScriptName() const
+	{
+		return m_exitScript;
+	}
+	Script::CStruct*		GetExitParams()
+	{
+		return mp_exitParams;
+	}
+
+protected:
+	Gfx::Camera*			mp_camera;
+	bool					m_holdOnLastFrame;
+	bool					m_skippable;
+	bool					m_aborted;
+	bool					m_shouldPause;
+	bool					m_allowPause;
+	uint32					m_name;
+
+	// exit parameters
+	uint32					m_exitScript;
+	Script::CStruct*		mp_exitParams;
+};
+
+// this can probably be split up into
+// a target version and a non-target version
+class CSkaterCamDetails : public CMovieDetails
+{
+public:
+	CSkaterCamDetails();
+	~CSkaterCamDetails();
+
+public:
+	virtual bool 	InitFromStructure(Script::CStruct *pParams);
+	virtual void 	Update();
+	virtual bool 	ResetCustomKeys();
+	virtual bool	CallMemberFunction( uint32 checksum, Script::CStruct* pParams, Script::CScript* pScript ); 
+	virtual bool 	IsComplete();
+	virtual bool	IsHeld();
+	virtual bool	OverridesCamera();
+	virtual bool	HasMovieStarted()
+	{
+		return true;
+	}
+	virtual bool	NeedsCameraTransition() { return true; }		// Uses hack to work around glitches where there isn't a camera
+
+protected:
+	void			set_target_params( Script::CStruct* pParams );
+	void			clear_target_params();
+	void			set_frame_data( Gfx::CBonedAnimFrameData* pFrameData, Gfx::EAnimLoopingType loopingType, bool reverse );
+	void			set_movie_length( float duration );
+	void			get_current_frame( Mth::Quat* pQuat, Mth::Vector* pTrans );
+	bool			process_custom_keys( float startTime, float endTime, bool end_time_inclusive );
+	void			update_camera();
+	void			update_camera_time();
+
+protected:
+	// target parameters
+	bool			m_hasTarget;
+	uint32			m_targetID;
+	Mth::Vector		m_targetOffset;
+	Mth::Vector		m_positionOffset;
+	bool			m_hasTargetOffset;
+	bool			m_hasPositionOffset;
+	
+	Gfx::CBonedAnimFrameData*	mp_frameData;
+	
+	// time-keeper
+	Gfx::CAnimChannel		m_animController;
+};					
+
+class CObjectAnimDetails : public CMovieDetails
+{
+public:
+	CObjectAnimDetails();
+	virtual 		~CObjectAnimDetails();
+
+public:
+	virtual bool	InitFromStructure( Script::CStruct* pParams );
+	virtual void	Update();
+	virtual bool	ResetCustomKeys();
+	virtual bool	IsComplete();
+	virtual bool	IsHeld();
+	virtual bool	OverridesCamera();
+	virtual bool	HasMovieStarted()
+	{
+		return true;
+	}
+	
+protected:
+	void			set_frame_data( Gfx::CBonedAnimFrameData* pFrameData, Gfx::EAnimLoopingType loopingType, bool reverse );
+	void			set_movie_length( float duration );
+	bool			process_custom_keys( float startTime, float endTime, bool end_time_inclusive );
+	void			update_moving_objects();
+
+protected:
+	Gfx::CBonedAnimFrameData*	mp_frameData;
+	
+	// time-keeper
+	Gfx::CAnimChannel		m_animController;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_MOVIEDETAILS_H
+
diff --git a/Code/Sk/Objects/navigation.cpp b/Code/Sk/Objects/navigation.cpp
new file mode 100644
index 0000000..b15cc2e
--- /dev/null
+++ b/Code/Sk/Objects/navigation.cpp
@@ -0,0 +1,756 @@
+///////////////////////////////////////////////////////
+// rail.cpp
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+		  
+
+namespace Obj
+{
+
+
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sAStarOpenList
+{
+	CNavNode*	m_open_list[512];
+	int			m_num_open;
+
+				sAStarOpenList( void );
+				~sAStarOpenList( void );
+
+	bool		IsEmpty( void )				{ return m_num_open == 0; }
+	void		Clear();
+	void		Empty();
+	void		Push( CNavNode* p_node );
+	CNavNode*	Pop( void );
+	void		SortOnTotalCost();
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sAStarClosedList
+{
+	CNavNode*	m_closed_list[512];
+	int			m_num_closed;
+
+				sAStarClosedList( void );
+				~sAStarClosedList( void );
+
+	bool		IsEmpty( void )				{ return m_num_closed == 0; }
+	void		Clear();
+	void		Empty( void );
+	void		Add( CNavNode* p_node );
+	void		Remove( CNavNode* p_node );
+};
+
+
+
+static sAStarOpenList	openList;
+static sAStarClosedList	closedList;
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarNode::Reset( void )
+{
+	m_flags	= 0;
+	m_cost_from_start	= 0.0f;
+	m_cost_to_goal		= 0.0f;
+	m_total_cost		= 0.0f;
+	mp_parent			= NULL;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool normal_sensitive_collision( CFeeler& feeler, float normal_y_sensitivity )
+{
+	// Safety check.
+	int iteration = 0;
+
+	// The feeler should already have been set up with the start and end points.
+	while( feeler.GetCollision())
+	{
+		// If this collision is with a normal that passes ouyr sensitivity test, consider it valid.
+		if( Mth::Abs( feeler.GetNormal()[Y] ) < normal_y_sensitivity )
+		{
+			return true;
+		}
+
+		// Otherwise move the start location on by 0.1 inch and continue.
+		feeler.m_start += ( feeler.m_end - feeler.m_start ).Normalize( 0.1f );
+
+		++iteration;
+		if( iteration > 8 )
+		{
+			Dbg_Message( "Multiple iterations in normal_sensitive_collision()" );
+
+			// Err on the side of caution.
+			return true;
+		}
+	}
+	return false;
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float path_cost_estimate( Mth::Vector& pos_a, Mth::Vector& pos_b )
+{
+	// Currently just use the distance between them. No consideration of terrain type.
+	return Mth::Distance( pos_a, pos_b );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float traverse_cost( CNavNode* p_node_a, CNavNode* p_node_b )
+{
+	// Currently just use the distance between them. No consideration of terrain type.
+	return Mth::Distance( p_node_a->GetPos(), p_node_b->GetPos());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int sort_on_total_cost( const void *p1, const void *p2 )
+{
+	CNavNode*	p_node1	= *((CNavNode**)p1 );
+	CNavNode*	p_node2	= *((CNavNode**)p2 );
+	
+	return ( p_node1->GetAStarNode()->m_total_cost < p_node2->GetAStarNode()->m_total_cost ) ? 1 : (( p_node1->GetAStarNode()->m_total_cost > p_node2->GetAStarNode()->m_total_cost ) ? -1 : 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNavNode* get_nearest_visible_node( Mth::Vector& pos, Mth::Vector& heading )
+{
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Obj::CNavManager* p_nav_man = skate_mod->GetNavManager();
+
+	float		best_dp		= -0.707106f;	// cosine of 135 degrees
+	CNavNode*	p_best_node	= NULL;
+
+	int num_nodes = p_nav_man->GetNumNodes();
+	for( int i = 0; i < num_nodes; ++i )
+	{
+		CNavNode*	p_node = p_nav_man->GetNavNodeByIndex( i );
+
+		// Discard nodes that are not in the required direction.
+		Mth::Vector node_pos	= p_node->GetPos() + Mth::Vector( 0.0f, 60.0f, 0.0f, 0.0f );
+		Mth::Vector pos_to_node	= node_pos - pos;
+		pos_to_node[Y]			= 0.0f;
+		pos_to_node[W]			= 0.0f;
+		pos_to_node.Normalize();
+		float dp				= Mth::DotProduct( pos_to_node, heading );
+
+		// Do we already have a better node.
+		if( dp < best_dp )
+		{
+			continue;
+		}
+
+		// Discard nodes that are not visible.
+		CFeeler feeler;
+		feeler.m_start	= pos;
+		feeler.m_end	= node_pos;
+		if( normal_sensitive_collision( feeler, 0.2f ))
+		{
+			continue;
+		}
+
+		best_dp		= dp;
+		p_best_node	= p_node;
+	}
+	return p_best_node;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNavNode* CalculateNodePath( Mth::Vector pos, Mth::Vector target_pos )
+{
+	// Clear open and closed nodes.
+	openList.Clear();
+	closedList.Clear();
+
+	// Find nearest visible node from current location, preferably in direction currently facing.
+	Mth::Vector heading = target_pos - pos;
+	heading[Y] = 0.0f;
+	heading[W] = 0.0f;
+	heading.Normalize();
+	CNavNode* p_nearest_visible = get_nearest_visible_node( pos, heading );
+	if( p_nearest_visible == NULL )
+	{
+		return false;
+	}
+
+	// Initialise the A* section of the node.
+	p_nearest_visible->GetAStarNode()->m_cost_from_start	= path_cost_estimate( pos, p_nearest_visible->GetPos());
+	p_nearest_visible->GetAStarNode()->m_cost_to_goal		= path_cost_estimate( p_nearest_visible->GetPos(), target_pos );
+	p_nearest_visible->GetAStarNode()->m_total_cost			= p_nearest_visible->GetAStarNode()->m_cost_from_start + p_nearest_visible->GetAStarNode()->m_cost_to_goal;
+	p_nearest_visible->GetAStarNode()->mp_parent			= NULL;
+
+	// Push this node on to the open list.
+	openList.Push( p_nearest_visible );
+
+	while( !openList.IsEmpty())
+	{
+		// Pop lowest total cost node from open list.
+		CNavNode* p_this_node = openList.Pop();
+
+		// If goal visible from this node, we're done.
+		Mth::Vector raised_node_pos = p_this_node->GetPos() + Mth::Vector( 0.0f, 60.0f, 0.0f, 0.0f );
+		CFeeler feeler;
+		feeler.m_start	= raised_node_pos;
+		feeler.m_end	= target_pos;
+		if( !normal_sensitive_collision( feeler, 0.2f ))
+		{
+			// Build list of nodes.
+
+			// Empty lists.
+			openList.Empty();
+			closedList.Empty();
+
+			return p_this_node;
+		}
+
+		// For each node linked to this node...
+		for( int l = 0; l < p_this_node->GetNumLinks(); ++l )
+		{
+			CNavNode*	p_new_node	= p_this_node->GetLink( l );
+			float		new_cost	= p_this_node->GetAStarNode()->m_cost_from_start + traverse_cost( p_this_node, p_new_node );
+
+			// Ignore this node if it is already in a list and provides no improvement.
+			if(( p_new_node->GetAStarNode()->InOpen() || p_new_node->GetAStarNode()->InClosed()) &&
+				 p_new_node->GetAStarNode()->m_cost_from_start <= new_cost )
+			{
+				continue;
+			}
+
+			// Store the new or improved information.
+			p_new_node->GetAStarNode()->mp_parent			= p_this_node;
+			p_new_node->GetAStarNode()->m_cost_from_start	= new_cost;
+			p_new_node->GetAStarNode()->m_cost_to_goal		= path_cost_estimate( p_new_node->GetPos(), target_pos );
+			p_new_node->GetAStarNode()->m_total_cost		= p_new_node->GetAStarNode()->m_cost_from_start + p_new_node->GetAStarNode()->m_cost_to_goal;
+
+			// Remove new node from closed list if it's in there.
+			if( p_new_node->GetAStarNode()->InClosed())
+			{
+				closedList.Remove( p_new_node );
+			}
+
+			// Push new node onto open list (if it's not already in there).
+			// This will re-sort the open list.
+			if( !p_new_node->GetAStarNode()->InOpen())
+			{
+				openList.Push( p_new_node );
+			}
+		}
+
+		// Add node onto the closed list.
+		closedList.Add( p_this_node );
+	}
+
+	// Empty lists.
+	openList.Empty();
+	closedList.Empty();
+
+	// If we get here we have failed to find any valid path.
+	return NULL;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sAStarOpenList::sAStarOpenList( void )
+{
+	Clear();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sAStarOpenList::~sAStarOpenList( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarOpenList::Clear( void )
+{
+	memset( m_open_list, 0, sizeof( m_open_list ));
+	m_num_open = 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarOpenList::Empty( void )
+{
+	while( !IsEmpty())
+	{
+		Pop();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarOpenList::Push( CNavNode* p_node )
+{
+	Dbg_Assert( m_num_open < 512 );
+
+	m_open_list[m_num_open++] = p_node;
+
+	// Flag this node as now being in the open list.
+	p_node->GetAStarNode()->m_flags |= sAStarNode::IN_OPEN;
+
+	SortOnTotalCost();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNavNode* sAStarOpenList::Pop( void )
+{
+	Dbg_Assert( m_num_open > 0 );
+
+	// Assumes always sorted, so just return lowest cost node.
+	--m_num_open;
+	CNavNode* p_return		= m_open_list[m_num_open];
+	m_open_list[m_num_open]	= NULL;
+
+	// Flag this node as no longer being in the open list.
+	p_return->GetAStarNode()->m_flags &= ~sAStarNode::IN_OPEN;
+
+	return p_return;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarOpenList::SortOnTotalCost( void )
+{
+	if( m_num_open > 1 )
+	{
+		qsort( m_open_list, m_num_open, sizeof( CNavNode* ), sort_on_total_cost );
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sAStarClosedList::sAStarClosedList( void )
+{
+	Clear();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+sAStarClosedList::~sAStarClosedList( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarClosedList::Clear( void )
+{
+	memset( m_closed_list, 0, sizeof( m_closed_list ));
+	m_num_closed = 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarClosedList::Empty( void )
+{
+	if( !IsEmpty())
+	{
+		for( int i = 0; i < 512; ++i )
+		{
+			if( m_closed_list[i] )
+			{
+				Remove( m_closed_list[i] );
+			}
+		}
+		Dbg_Assert( IsEmpty());
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarClosedList::Add( CNavNode* p_node )
+{
+	for( int i = 0; i < 512; ++i )
+	{
+		if( m_closed_list[i] == NULL )
+		{
+			m_closed_list[i] = p_node;
+			++m_num_closed;
+			p_node->GetAStarNode()->m_flags |= sAStarNode::IN_CLOSED;
+			return;
+		}
+	}
+	Dbg_Assert( 0 );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void sAStarClosedList::Remove( CNavNode* p_node )
+{
+	for( int i = 0; i < 512; ++i )
+	{
+		if( m_closed_list[i] == p_node )
+		{
+			m_closed_list[i] = NULL;
+			--m_num_closed;
+			p_node->GetAStarNode()->m_flags &= ~sAStarNode::IN_CLOSED;
+			return;
+		}
+	}
+	Dbg_Assert( 0 );
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNavManager::CNavManager()
+{
+	mp_nodes		= NULL;
+	m_num_nodes		= 0;
+	mp_node_array	= NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CNavManager::~CNavManager()
+{	 
+	Cleanup();  
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNavManager::Cleanup()
+{
+	m_num_nodes = 0;
+	
+	Mem::Free( mp_nodes );
+	mp_nodes		= NULL;
+	mp_node_array	= NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNavManager::AddNavNode( int node_number, Script::CStruct* p_node_struct )
+{
+	CNavNode* pNavNode = &mp_nodes[m_current_node];
+	
+	uint32 class_checksum;
+	p_node_struct->GetChecksum( CRCD( 0x12b4e660, "class" ), &class_checksum );
+	
+	uint32 type_checksum;
+	p_node_struct->GetChecksum( CRCD( 0x7321a8d6, "type" ), &type_checksum );
+
+	// No links as yet.
+	pNavNode->m_num_links = 0;
+	for( int i = 0;i < MAX_NAV_LINKS; ++i )
+	{
+		pNavNode->mp_links[i] = NULL;
+	}
+
+	// The node_number is use primarily for calculating links.
+	pNavNode->m_node		= node_number;
+	
+	// Reset the A* node.
+	pNavNode->m_astar_node.Reset();
+	
+//	pNavNode->m_flags	= 0;
+//	pNavNode->SetActive( p_node_struct->ContainsFlag( 0x7c2552b9 /*"CreatedAtStart"*/ ));	// Created at start or not?
+
+	// Set the position from the node structure.
+	SkateScript::GetPosition( p_node_struct, &pNavNode->m_pos );
+
+	int links = SkateScript::GetNumLinks( p_node_struct );
+	if( links )
+	{
+		Dbg_MsgAssert( links <= MAX_NAV_LINKS, ( "Nav node %d has %d linkes, max is %d", node_number, links, MAX_NAV_LINKS ));
+
+		pNavNode->m_num_links = links;
+		for( int i = 0; i < links; ++i )
+		{
+			// For now we just set the link to be the node number in the node array.
+			pNavNode->mp_links[i] = (CNavNode*)SkateScript::GetLink( p_node_struct, i );
+		}
+	}	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CNavManager::AddNavsFromNodeArray( Script::CArray *p_nodearray )
+{
+	Dbg_MsgAssert( !mp_node_array, ( "Setting two node arrays in rail manager" ));
+	mp_node_array = p_nodearray;
+	
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+
+	uint32 i;	
+
+	Dbg_MsgAssert( m_num_nodes == 0, ("Can only addd nodes once, already %d there\n", m_num_nodes ));
+
+	// First iterate over the node array, and count the number of nodes needed.
+	m_num_nodes = 0;										  
+	for( i = 0; i < p_nodearray->GetSize(); ++i )
+	{
+		Script::CStruct* p_node_struct = p_nodearray->GetStructure( i );
+		Dbg_MsgAssert( p_node_struct, ( "Error getting node from node array for navigation" ));
+		if( !skate_mod->ShouldBeAbsentNode( p_node_struct ))
+		{
+			uint32 class_checksum = 0;		
+			p_node_struct->GetChecksum( CRCD( 0x12b4e660, "Class" ), &class_checksum );
+			if( class_checksum == CRCD( 0x4c23a77e, "Waypoint" ))
+			{
+				uint32 type_checksum = 0;		
+				p_node_struct->GetChecksum( CRCD( 0x7321a8d6, "Type" ), &type_checksum );
+				if( type_checksum == CRCD( 0x9784feb5, "HorseNav" ))
+				{
+					++m_num_nodes;
+				}
+			}
+		}
+	}
+
+	// No nodes found, so just return
+	if( m_num_nodes == 0 )
+	{
+		return;
+	}
+	
+	mp_nodes = (CNavNode*)Mem::Malloc( m_num_nodes * sizeof( CNavNode ));
+
+	// Iterate over the nodes and add them to the array.
+	m_current_node = 0;	
+	
+	for( i = 0; i < p_nodearray->GetSize(); ++i )
+	{
+		Script::CStruct *p_node_struct = p_nodearray->GetStructure( i );
+		Dbg_MsgAssert( p_node_struct, ("Error getting node from node array for navigation" ));
+
+		if( !skate_mod->ShouldBeAbsentNode( p_node_struct ))
+		{
+			uint32 class_checksum = 0;		
+			p_node_struct->GetChecksum( CRCD( 0x12b4e660, "Class" ), &class_checksum );
+			if( class_checksum == CRCD( 0x4c23a77e, "Waypoint" ))
+			{
+				uint32 type_checksum = 0;		
+				p_node_struct->GetChecksum( CRCD( 0x7321a8d6, "Type" ), &type_checksum );
+				if( type_checksum == CRCD( 0x9784feb5, "HorseNav" ))
+				{
+					AddNavNode( i, p_node_struct );
+					++m_current_node;
+				}
+			}
+		}
+	}
+	
+	// We are creating a table of all nodes, and the pointer to the CNavNode for that node, so we can do a reverse lookup.
+	int num_nodes = p_nodearray->GetSize();
+	CNavNode **pp_navnodes = (CNavNode**)Mem::Malloc( num_nodes * sizeof( CNavNode* ));
+	for( int i = 0; i < num_nodes; ++i )
+	{
+		pp_navnodes[i] = NULL;
+	}
+
+	// Now fill it in.
+	CNavNode* p_node;
+	for( int node = 0; node < m_num_nodes; ++node )
+	{
+		p_node = &mp_nodes[node];
+		pp_navnodes[p_node->GetNode()] = p_node;
+	}
+
+	// Now go through all the nodes.
+	for( int node = 0; node < m_num_nodes; ++node )
+	{
+		p_node = &mp_nodes[node];
+		for( int i = 0; i < p_node->m_num_links; ++i )
+		{
+			int index = (int)p_node->mp_links[i];
+
+			Dbg_MsgAssert( index < num_nodes, ( "Node %d, Nav link node (%d) out of range (0 .. %d). Bad Node array?", p_node->m_node, index, num_nodes ));
+			Dbg_MsgAssert( pp_navnodes[index], ( "NavNode %d linked to something (node %d) that is not a NavNode", p_node->m_node, index ));
+
+			p_node->mp_links[i] = pp_navnodes[index];
+		}
+	}
+
+	// Don't need the temporary array anymore.
+	Mem::Free( pp_navnodes );
+}
+
+
+
+Mth::Vector	CNavManager::GetPos( const CNavNode* p_node ) const
+{
+	Dbg_Assert( p_node );
+	return p_node->GetPos();
+}
+
+
+}
+			   
+
diff --git a/Code/Sk/Objects/navigation.h b/Code/Sk/Objects/navigation.h
new file mode 100644
index 0000000..d596633
--- /dev/null
+++ b/Code/Sk/Objects/navigation.h
@@ -0,0 +1,154 @@
+//////////////////////////////////////////
+// navigation.h
+
+#ifndef __OBJECTS_NAVIGATION_H
+#define __OBJECTS_NAVIGATION_H
+                        
+#include 
+#include 
+#include 
+                           
+namespace Script
+{
+	class	CArray;
+	class	CStruct;
+}
+
+#define	MAX_NAV_LINKS 12
+
+namespace Obj
+{
+
+class	CNavManager;
+//class	CWalkComponent;
+//struct	SHangNavData;
+//struct	SLadderNavData;
+
+//enum ENavNodeFlag
+//{	
+//	LIP_OVERRIDE,		// always do a lip trick on this
+//	DEFAULT_LINE,
+//	ACTIVE,
+//	CLIMBING,
+//	LADDER
+//};
+
+
+
+// Forward declaration.
+class CNavNode;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+struct sAStarNode
+{
+	enum EAStarNodeFlag
+	{
+		IN_OPEN		= 0x01,
+		IN_CLOSED	= 0x02
+	};
+
+	uint32			m_flags;
+	float			m_cost_from_start;
+	float			m_cost_to_goal; 
+	float			m_total_cost; 
+	CNavNode*		mp_parent;
+
+
+	bool			InOpen( void )		{ return m_flags & IN_OPEN; }
+	bool			InClosed( void )	{ return m_flags & IN_CLOSED; }
+	void			Reset();
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CNavNode : public Spt::Class
+{
+	friend class		CNavManager;
+
+public:						  
+	
+	const sint16		GetNode() const				{ return m_node; }
+	sAStarNode*			GetAStarNode( void )		{ return &m_astar_node; }
+	Mth::Vector&		GetPos( void )				{ return m_pos; }
+	int					GetNumLinks( void )			{ return m_num_links; }
+	CNavNode*			GetLink( int l )			{ return mp_links[l]; }
+
+protected:
+
+	Mth::Vector			m_pos;						// Position of the node.
+	CNavNode*			mp_links[MAX_NAV_LINKS];
+	sint16				m_num_links;
+	sint16				m_node;						// Number of the node in the trigger array.
+	sAStarNode			m_astar_node;				// Used during the A* pathfinding routine.
+
+private:
+	const Mth::Vector&	GetPos() const;
+};
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+class CNavManager : public Spt::Class
+{
+
+public:
+						CNavManager();
+						~CNavManager();	
+	
+	void 				Cleanup();
+									 
+	void				AddNavsFromNodeArray( Script::CArray * p_node_array );
+	void 				AddNavNode( int node_number, Script::CStruct *p_node_struct );
+	
+	int					GetNumNodes()									{ return m_num_nodes; }
+	Script::CArray*		GetNodeArray()									{ return mp_node_array; }
+	void				SetNodeArray( Script::CArray* p_nodeArray )		{ mp_node_array = p_nodeArray; }
+
+	CNavNode*			GetNavNodeByIndex( int index )					{ return mp_nodes + index; }
+	CNavNode*			GetNavNodeByNodeNumber( int node_num );
+	
+	Mth::Vector			GetPos( const CNavNode *p_node ) const;	 
+
+private:
+
+	CNavNode*	  		mp_nodes;				// pointer to array of nodes
+	Script::CArray*		mp_node_array;
+	int					m_num_nodes;
+	int					m_current_node;
+};
+ 
+
+
+CNavNode* CalculateNodePath( Mth::Vector pos, Mth::Vector target_pos );
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+inline const Mth::Vector& CNavNode::GetPos() const
+{
+	return m_pos;
+}
+
+
+
+
+
+} 
+
+			
+#endif			// #ifndef __OBJECTS_RAIL_H
+			
+
+
diff --git a/Code/Sk/Objects/objecthook.cpp b/Code/Sk/Objects/objecthook.cpp
new file mode 100644
index 0000000..c5d0766
--- /dev/null
+++ b/Code/Sk/Objects/objecthook.cpp
@@ -0,0 +1,204 @@
+///////////////////////////////////////////////////////
+// ObjectHook.cpp
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#ifdef	__PLAT_NGPS__	
+#include 
+#endif
+
+
+
+
+namespace Obj
+{
+
+
+CObjectHookManager::CObjectHookManager()
+{
+	mp_first_node = NULL;
+	m_is_transformed = false;
+	mp_node_array = NULL;
+	
+}
+
+CObjectHookManager::~CObjectHookManager()
+{	 
+	Cleanup();  
+}
+
+void CObjectHookManager::Cleanup()
+{
+	while (mp_first_node)
+	{
+		CObjectHookNode *pNext = mp_first_node->m_pNext;
+		delete mp_first_node;
+		mp_first_node = pNext;			
+	}    
+	m_is_transformed = false;	
+	// Unhook from node array
+	mp_node_array = NULL;
+}
+
+
+void CObjectHookManager::AddObjectHookNode(int node_number, Script::CStruct *p_node_struct)
+{
+
+	// create a new ObjectHook node										   
+	CObjectHookNode *pObjectHookNode = new CObjectHookNode();
+	// Link into the head of the ObjectHook manager	
+	// Note, this must be done before we attempt to link up with other nodes								   
+	pObjectHookNode->m_pNext = mp_first_node; 							  
+	mp_first_node = pObjectHookNode;
+
+	pObjectHookNode->m_node = node_number;			// The node_number is use primarily for calculating links
+	pObjectHookNode->m_active = p_node_struct->ContainsFlag( 0x7c2552b9 /*"CreatedAtStart"*/ );	// created at start or not?
+	pObjectHookNode->m_flags = 0;						// all flags off by default
+
+	SkateScript::GetPosition(p_node_struct,&pObjectHookNode->m_pos);
+
+}
+
+void CObjectHookManager::UpdateTransform(Mth::Matrix& transform)
+{
+
+	m_is_transformed = true;
+	m_transform = transform;
+}
+
+
+
+void	CObjectHookManager::DebugRender(Mth::Matrix *p_transform)
+{
+	#ifdef	__PLAT_NGPS__
+
+	Mth::Vector up(0.0f,1.0f,0.0f);	
+
+	uint32	rgb = 0x0000ff;
+	
+	uint32 current = 12345678;
+
+	
+	
+	CObjectHookNode *pObjectHookNode;
+	pObjectHookNode = mp_first_node;
+	int count = 0;
+	// then draw the node positions as litle red lines	
+	pObjectHookNode = mp_first_node;
+	while (pObjectHookNode)
+	{
+		count++;
+		if (pObjectHookNode->m_active)
+		{
+			rgb = 0xff0000;
+		}
+		else
+		{
+			rgb = 0x00ff00;
+		}
+	
+		// check for context changes
+		if (current != rgb)
+		{
+			if ( current != 12345678)
+			{
+				NxPs2::EndLines3D();
+			}
+			
+			current = rgb;
+			NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));
+		}
+	
+		Mth::Vector v0, v1;
+		v0 = pObjectHookNode->m_pos;
+		v1 = v0 + up * 24.0f;
+		v0[W] = 1.0f;		// Make homgeneous
+		v1[W] = 1.0f;
+
+		if (p_transform)
+		{
+			v0 = p_transform->Transform(v0);
+			v1 = p_transform->Transform(v1);
+		}
+
+		NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
+		
+		pObjectHookNode = pObjectHookNode->m_pNext;
+		
+	}
+
+	
+	
+	// only if we actually drew some
+	if ( current != 12345678)
+	{
+		NxPs2::EndLines3D();
+	}
+
+
+	#endif
+
+}
+
+
+
+
+void	CObjectHookManager::AddObjectHooksFromNodeArray(Script::CArray * p_nodearray)
+{
+	Dbg_MsgAssert(!mp_node_array,("Setting two node arrays in ObjectHook manager"));
+	mp_node_array = p_nodearray;
+	
+	
+	uint32 i;	 
+	for (i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *p_node_struct=p_nodearray->GetStructure(i);
+		Dbg_MsgAssert(p_node_struct,("Error getting node from node array for ObjectHook generation"));
+
+		uint32 class_checksum = 0;		
+		p_node_struct->GetChecksum( 0x12b4e660 /*"Class"*/, &class_checksum );
+		if (class_checksum == 0xd18719d5)		  // checksum 'ObjectHook'
+		{
+			AddObjectHookNode( i, p_node_struct);
+		}
+	}
+}
+
+
+const Mth::Vector&	CObjectHookNode::GetPos() const
+{
+	return m_pos;
+}
+
+
+Mth::Vector	CObjectHookManager::GetPos(const CObjectHookNode *p_node) 	const
+{
+	if (!m_is_transformed)
+	{
+		return p_node->GetPos();
+	}
+	else
+	{
+		Mth::Vector v = m_transform.Transform(p_node->GetPos());
+		return v;
+	}
+}
+
+
+}
+			   
+
diff --git a/Code/Sk/Objects/objecthook.h b/Code/Sk/Objects/objecthook.h
new file mode 100644
index 0000000..26d2f2a
--- /dev/null
+++ b/Code/Sk/Objects/objecthook.h
@@ -0,0 +1,108 @@
+//////////////////////////////////////////
+// ObjectHook.h
+
+#ifndef __OBJECTS_OBJECTHOOK_H
+#define __OBJECTS_OBJECTHOOK_H
+
+namespace Script
+{
+	class	CArray;
+	class	CStruct;
+}
+
+
+
+namespace Obj
+{
+
+class	CObjectHookManager;
+
+enum EObjectHookNodeFlag
+{
+	GRAB_OVERRIDE = 0x01,
+};
+
+class CObjectHookNode : public Spt::Class
+{
+	friend class  CObjectHookManager;
+
+public:						  
+	const sint16			GetNode() const 	  		{return m_node;}
+	void					SetActive(bool active)		{m_active = active;}
+	const bool				IsActive() const				{return m_active;}
+
+	void					SetFlag(EObjectHookNodeFlag flag) {m_flags |= flag;}
+	void					ClearFlag(EObjectHookNodeFlag flag) {m_flags &= ~flag;}
+	bool					GetFlag(EObjectHookNodeFlag flag) const {return m_flags & flag;}
+
+	CObjectHookNode *		GetNext(){return m_pNext;}
+
+protected:
+
+	Mth::Vector m_pos;			// position of the node
+	CObjectHookNode *m_pNext;			// Pointer to next node in master list	 	(or NULL if none)
+	sint16 m_node;					// Number of the node in the trigger array
+	char m_active;				// ObjectHook segment starting at this node: active or not?
+	char m_flags;				// flags for ObjectHook segment
+
+// the position getting functions are private
+// so you can only access them through the ObjectHook manager
+// (as you need to know if it's transformed or not)
+private:
+	const Mth::Vector&	GetPos() const;	 
+						  
+};
+
+
+
+class CObjectHookManager : public Spt::Class
+{
+
+public:
+					CObjectHookManager();
+				   ~CObjectHookManager();	
+
+	void 			Cleanup();
+								 
+	void			AddObjectHooksFromNodeArray(Script::CArray * p_node_array);
+	void 			AddObjectHookNode(int node_number, Script::CStruct *p_node_struct);
+	void 			AddedAll();
+	void			UpdateTransform(Mth::Matrix& transform);
+	
+	void 			SetActive( int node, int active, bool wholeObjectHook );
+	bool 			IsActive( int node );
+	bool			IsMoving(){return m_is_transformed;}
+	
+	void			DebugRender(Mth::Matrix *p_transform = NULL);
+	void			RemoveOverlapping();
+	
+	CObjectHookNode*		GetFirstNode(){return mp_first_node;}
+	
+	Script::CArray *	GetNodeArray(){return mp_node_array;}
+	void				SetNodeArray(Script::CArray *p_nodeArray){mp_node_array = p_nodeArray;}
+
+// accessor functions for the ObjectHook node
+// we only access their positions via the ObjectHook manager
+// as only the ObjectHook manager knows the transform of the object to
+// which they are attached
+// These nodes are "instances", in that the same node might be used in
+// more than one place
+// so these functions only make sense in conjunction with the ObjectHook_manager
+	Mth::Vector	GetPos(const CObjectHookNode *p_node) 	const;	 
+
+private:
+	CObjectHookNode*  			mp_first_node;	
+	bool						m_is_transformed;
+	Mth::Matrix					m_transform;
+	Script::CArray *			mp_node_array;
+
+};
+
+
+} 
+
+			
+#endif			// #ifndef __OBJECTS_OBJECTHOOK_H
+			
+
+
diff --git a/Code/Sk/Objects/ped.cpp b/Code/Sk/Objects/ped.cpp
new file mode 100644
index 0000000..dad1054
--- /dev/null
+++ b/Code/Sk/Objects/ped.cpp
@@ -0,0 +1,140 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       ped.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/15/2000
+//****************************************************************************
+
+/*
+	MD:  Derived from CMovingObject, the ped just adds some functionality
+	for jumping out of the way of the skater.  That's all!  Other than
+	that, the ped inherits all the abilities of the moving object class,
+	such as having an animating model, that can follow paths, or play
+	sounds, etc...
+*/
+
+/*
+	GJ:  The above comment used to be true, but now peds are defined as
+	having skinned models and animations, running extra scripts to shut
+	off extra bones, etc.  Some of this stuff should be moved up to
+	the CMovingObject level (in particular, the skinned model/anim stuff)
+*/
+
+// start autoduck documentation
+// @DOC ped
+// @module ped | None
+// @subindex Scripting Database
+// @index script | ped
+    
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+const float DEFAULT_PED_TURN_DIST = 2.0f;
+const float DEFAULT_PED_MAX_VEL = 2.5f;
+const float DEFAULT_PED_ACCELERATION = 4.0f;
+const float DEFAULT_PED_DECELERATION = 1.0f;
+const float DEFAULT_PED_STOP_DIST = 2.0f;		// stopping distance from wp
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CreatePed(CGeneralManager* p_obj_man, Script::CStruct* pNodeData)
+{
+	CMovingObject* pPed = new CMovingObject;
+	
+	pPed->MovingObjectCreateComponents();
+
+	pPed->SetType( SKATE_TYPE_PED );
+
+	Dbg_MsgAssert(pPed, ("Failed to create pedestrian."));
+	p_obj_man->RegisterObject(*pPed);
+	
+	// GJ:  This should come before the model gets initialized
+	// get position, from the node that created us:
+	SkateScript::GetPosition( pNodeData, &pPed->m_pos );
+		
+	pPed->MovingObjectInit( pNodeData, p_obj_man );
+
+	Script::RunScript( CRCD(0xf3eec45e,"ped_add_components"), pNodeData, pPed );
+	// Finalize the components, after everything has been added	
+	pPed->Finalize();
+	Script::RunScript( CRCD(0x955a2767,"ped_init_model"), pNodeData, pPed );
+
+	// designer controlled variables:
+	// set defaults, to be overridden by script values if they exist:
+	Obj::CMotionComponent* pMotionComponent = GetMotionComponentFromObject( pPed );
+	
+	pMotionComponent->m_max_vel = ( MPH_TO_INCHES_PER_SECOND ( DEFAULT_PED_MAX_VEL ) );
+	pMotionComponent->m_acceleration = FEET_TO_INCHES( DEFAULT_PED_ACCELERATION );
+	pMotionComponent->m_deceleration = FEET_TO_INCHES( DEFAULT_PED_DECELERATION );
+	pMotionComponent->m_stop_dist = FEET_TO_INCHES( DEFAULT_PED_STOP_DIST );
+	pMotionComponent->EnsurePathobExists( pPed );
+	pMotionComponent->GetPathOb()->m_enter_turn_dist = FEET_TO_INCHES( DEFAULT_PED_TURN_DIST );
+
+	// don't let his pitch get messed up when going up hills...
+	pMotionComponent->m_movingobj_flags |= MOVINGOBJ_FLAG_NO_PITCH_PLEASE;
+
+	// need to synchronize rendered model's position to initial world position
+	GetModelComponentFromObject( pPed )->FinalizeModelInitialization();
+
+	// now that the skeleton has been updated at least once,
+	// we can turn off the extra bones that don't animate:	
+	Script::RunScript( CRCD(0x48ca2d26,"ped_disable_bones"), NULL, pPed );
+	pPed->SetProfileColor(0xc000c0);				// magenta = ped
+
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
diff --git a/Code/Sk/Objects/ped.h b/Code/Sk/Objects/ped.h
new file mode 100644
index 0000000..05f51b9
--- /dev/null
+++ b/Code/Sk/Objects/ped.h
@@ -0,0 +1,44 @@
+//****************************************************************************
+//* MODULE:         Sk/Objects
+//* FILENAME:       ped.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  10/15/2000
+//****************************************************************************
+
+#ifndef __OBJECTS_PED_H
+#define __OBJECTS_PED_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Script
+{
+	class CStruct;
+}
+				   
+namespace Obj
+{
+
+	class CGeneralManager;
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+	void CreatePed(CGeneralManager* p_obj_man, Script::CStruct* pNodeData);
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_PED_H
diff --git a/Code/Sk/Objects/proxim.cpp b/Code/Sk/Objects/proxim.cpp
new file mode 100644
index 0000000..321ebc5
--- /dev/null
+++ b/Code/Sk/Objects/proxim.cpp
@@ -0,0 +1,619 @@
+///////////////////////////////////////////////////////
+// Proxim.cpp
+
+		  
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 		   // used to get position of the skater's camera
+#include 
+
+//#define	DEBUG_PROXIM
+
+namespace Obj
+{
+
+void Proxim_Init()
+{
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	
+	if (skate_mod->mpProximManager)
+	{
+		Proxim_Cleanup();
+	}
+	skate_mod->mpProximManager = new CProximManager();
+	skate_mod->mpProximManager->m_pFirstNode = NULL;
+}
+
+void Proxim_Cleanup()
+{
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	if (skate_mod->mpProximManager)
+	{
+		CProximNode *pNode = skate_mod->mpProximManager->m_pFirstNode;	   
+		while (pNode)
+		{
+			CProximNode *pNext = pNode->m_pNext;
+			delete pNode;
+			pNode = pNext;			
+		}
+
+		delete skate_mod->mpProximManager;
+		skate_mod->mpProximManager = NULL;
+	}
+}
+
+#define CHECKSUM_TERRAIN_TYPE	0x54cf8532  // "terrainType"
+
+void Proxim_AddNode(int node,bool active)
+{
+//	printf ("It's a Proxim (%d)\n",node);
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Dbg_Assert(skate_mod->mpProximManager);
+	
+	// create a new Proxim node										   
+	CProximNode *pProximNode = new CProximNode();
+
+	// Link into the head of the Proxim manager									   
+	pProximNode->m_pNext = skate_mod->mpProximManager->m_pFirstNode; 							  
+	skate_mod->mpProximManager->m_pFirstNode = pProximNode;
+
+	pProximNode->m_node = node;
+
+	pProximNode->m_active = active;  // created at start or not?
+	
+	pProximNode->mp_sector = NULL;
+	pProximNode->m_shape = CProximNode::vSHAPE_SPHERE;  
+
+	// Get pointer to the Proxim data	
+	Script::CScriptStructure *pNode = SkateScript::GetNode(node);
+	
+	// Get name
+	if (pNode->GetChecksum("name",&pProximNode->m_name))
+	{
+#ifdef	DEBUG_PROXIM
+		printf ("Proxim: Got Name %s\n",Script::FindChecksumName(pProximNode->m_name));
+#endif	
+	}
+	else
+	{
+		Dbg_MsgAssert(0,("No name in proxim node %d\n",node));
+	}
+
+	// Get shape
+	uint32 shape_checksum;
+	if (pNode->GetChecksum("shape", &shape_checksum))
+	{
+		pProximNode->m_shape = CProximNode::sGetShapeFromChecksum(shape_checksum);
+	}
+
+	// Try to find geometry
+	pProximNode->InitGeometry(Nx::CEngine::sGetMainScene()->GetSector(pProximNode->m_name));
+
+	SkateScript::GetPosition(pNode,&pProximNode->m_pos);
+#ifdef	DEBUG_PROXIM				
+		Dbg_Message("Proxim: Pos = (%f, %f, %f)", pProximNode->m_pos[X], pProximNode->m_pos[Y], pProximNode->m_pos[Z]);
+#endif	
+
+	pProximNode->m_type = 0;  
+	pNode->GetInteger( 0x7321a8d6 /* type */, &pProximNode->m_type );
+#ifdef	DEBUG_PROXIM
+	printf ("Proxim:  Type = %d\n",pProximNode->m_type);
+#endif
+	pNode->GetFloat (0xc48391a5 /*radius*/, &pProximNode->m_radius);  
+	Dbg_MsgAssert(pProximNode->mp_sector || pProximNode->m_radius > 0.0f,( "Radius %f too small in proxim node %d\n",pProximNode->m_radius,node));	
+#ifdef	DEBUG_PROXIM
+	printf ("Proxim:  Radius = %f\n",pProximNode->m_radius);
+#endif	
+	pProximNode->m_radius_squared = pProximNode->m_radius * pProximNode->m_radius;	
+
+	if (pNode->GetChecksum(0x2ca8a299,&pProximNode->m_script))		// Checksum of TriggerScript
+	{
+#ifdef	DEBUG_PROXIM
+		printf ("Proxim: Got Script %s\n",Script::FindChecksumName(pProximNode->m_script));
+#endif	
+	}
+	else
+	{
+		Dbg_MsgAssert(0,("No triggerscript in proxim node %d\n",node));
+	}
+
+	
+	pProximNode->m_proxim_object = 0;	
+	pProximNode->m_active_objects = 0;	
+	pProximNode->m_active_scripts = 0;	
+	if (pNode->ContainsFlag(CRCD(0x5113f19c,"ProximObject")))
+	{
+		// for proxim objects, it's best if we start "outside" the objects
+		// so if we are actually inside, then the event will trigger
+		pProximNode->m_outside_flags = 0xffffffff;			// say all outside
+		pNode->GetChecksum("Name",&pProximNode->m_proxim_object);	
+	}
+	else
+	{
+		// for non-proxim objects, it's best if we start "inside" the node
+		// in case something is depending on the outside script being called first
+		// to keep things shut off.  We'll do this by starting with it active
+		// and then setting it to inactive.
+		pProximNode->m_outside_flags = 0;			// say all inside for now....
+		pProximNode->m_active = true;
+
+		// Set the script active flag for each skater that exists
+		Obj::CObject *p_obj;
+		int object_id = 0;
+		while ((p_obj  = Obj::CCompositeObjectManager::Instance()->GetObjectByID(object_id)) && (object_id < 32))
+		{
+			Obj::CSkater *p_skater = (Obj::CSkater *) p_obj;
+			if (p_skater->IsLocalClient())
+			{
+				pProximNode->m_active_scripts |= 1 << object_id;
+			}
+			object_id++;
+		}
+
+		pProximNode->SetActive(false);
+		pProximNode->SetActive(active);
+	}
+				
+}
+
+void Proxim_AddedAll()
+{
+	
+//	printf("Added all Proxims\n");		 
+		 
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Dbg_Assert(skate_mod->mpProximManager);
+	CProximNode *pProximNode = skate_mod->mpProximManager->m_pFirstNode;
+	while (pProximNode)
+	{
+		// do any final per-node processing here
+		pProximNode = pProximNode->m_pNext;
+	}				 
+				 	
+}			   
+
+void Proxim_SetActive( int node, int active )
+{
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	
+	if (!skate_mod->mpProximManager) return;
+	
+	CProximNode *pProximNode = skate_mod->mpProximManager->m_pFirstNode;
+	while (pProximNode)
+	{
+		if (pProximNode->m_node == node)
+		{
+			pProximNode->SetActive(active);
+			return;
+		}
+		pProximNode = pProximNode->m_pNext;
+	}				 
+}
+
+CProximNode * CProximManager::sGetNode(uint32 checksum)
+{
+    Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Dbg_Assert(skate_mod->mpProximManager);
+	CProximNode *pProximNode = skate_mod->mpProximManager->m_pFirstNode;
+	while (pProximNode)
+	{
+		if (pProximNode->m_name == checksum)
+		{
+			return pProximNode;
+		}
+		pProximNode = pProximNode->m_pNext;
+	}				 
+
+	return NULL;
+}
+
+// Update all the proximity nodes				
+// for a certain skater
+// checking for proximity either with the skater or the camera					
+void Proxim_Update ( uint32 trigger_mask, CObject* p_script_target, const Mth::Vector& pos )
+{
+#if 0		// TT9254: Too many things were having problems with this.  The proxim node scripts themselves
+			// will have to do this.
+	// Don't update if the game is paused
+	if( Mdl::FrontEnd::Instance()->GamePaused() && !(Script::GetInteger(CRCD(0x45e46afd,"goal_editor_placement_mode"))) )
+	{
+		return;
+	}
+
+#if 0
+	// Don't update if the game is playing a CamAnim
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	if ( skate_mod->GetMovieManager()->IsRolling() )
+	{
+		return;
+	}
+#endif
+#endif
+
+	CProximManager* proxim_manager = Mdl::Skate::Instance()->mpProximManager;
+	
+	if (!proxim_manager) return;
+	
+	CProximNode *pProximNode = proxim_manager->m_pFirstNode;
+
+	while (pProximNode)
+	{
+		if (!pProximNode->m_active)
+		{
+			pProximNode = pProximNode->m_pNext;
+			continue;
+		}
+			
+		bool now_inside = pProximNode->CheckInside(pos);
+
+		if ( trigger_mask & pProximNode->m_outside_flags)
+		{
+			// trigger is currently outside, check if moved to inside
+			if (now_inside)
+			{
+			
+				pProximNode->m_outside_flags &= ~trigger_mask;
+				
+				if ( ! pProximNode->m_proxim_object )
+				{
+#ifdef	DEBUG_PROXIM				
+				printf ("+++++ NOW INSIDE, mask = %d running script %s \n",trigger_mask, Script::FindChecksumName(pProximNode->m_script));
+#endif				
+					proxim_manager->m_inside_flag = true;
+					proxim_manager->m_inside_flag_valid = true;
+					
+					if (p_script_target)
+					{
+						p_script_target->SpawnAndRunScript(pProximNode->m_script,pProximNode->m_node );
+						Dbg_MsgAssert(p_script_target->GetID() < 32, ("Object ID %d is greater than 31", pProximNode->m_proxim_object));
+						pProximNode->m_active_scripts |= 1 << p_script_target->GetID();
+					}
+					else
+					{
+						//Script::SpawnScript(pProximNode->m_script, NULL, NO_NAME, NULL, pProximNode->m_node );
+						Script::RunScript(pProximNode->m_script);
+					}
+
+					proxim_manager->m_inside_flag_valid = false;
+				}
+				else if (p_script_target)
+				{
+					// Need to create an object with the id pProximNode->m_proxim_object
+
+#ifdef	DEBUG_PROXIM				
+				printf ("+++++ NOW INSIDE, creating object %s running script %s \n",Script::FindChecksumName(pProximNode->m_proxim_object),Script::FindChecksumName(pProximNode->m_script));
+#endif				
+					
+					Obj::CCompositeObject* pObj = Obj::CCompositeObjectManager::Instance()->CreateCompositeObject();
+					pObj->SetID(pProximNode->m_proxim_object + p_script_target->GetID());  // Mick mangle the proxim object ID with the player's ID
+					pObj->m_pos = pProximNode->m_pos  ;
+
+					Dbg_MsgAssert(p_script_target->GetID() < 32, ("Object ID %d is greater than 31", pProximNode->m_proxim_object));
+					pProximNode->m_active_objects |= 1 << p_script_target->GetID();
+
+					Script::CStruct component_params;	// ProximNode params
+					if (pProximNode->HasGeometry())
+					{
+						// Only add if we want the geometry used
+						component_params.AddChecksum("ProximNode", pProximNode->m_name);
+					}
+					pObj->CreateComponentsFromArray(Script::GetArray("proximobj_composite_structure"), &component_params);   
+				 	pObj->Finalize();
+   
+					Script::CStruct params;	// dummy empty params
+					pObj->SwitchScript(pProximNode->m_script,¶ms);
+				}
+			}			
+		}
+		else
+		{
+			// skater is currently inside, check if moved outside
+			if (!now_inside)
+			{
+				pProximNode->m_outside_flags |= trigger_mask;				
+				if ( ! pProximNode->m_proxim_object )
+				{
+	#ifdef	DEBUG_PROXIM				
+					printf ("----- NOW OUTSIDE, mask = %d running script %s \n",trigger_mask,Script::FindChecksumName(pProximNode->m_script));
+	#endif				
+					proxim_manager->m_inside_flag = false;
+					proxim_manager->m_inside_flag_valid = true;
+
+					if (p_script_target)
+					{
+						p_script_target->SpawnAndRunScript(pProximNode->m_script,pProximNode->m_node );
+						pProximNode->m_active_scripts &= ~(1 << p_script_target->GetID());
+					}
+					else
+					{
+						//Script::SpawnScript(pProximNode->m_script, NULL, NO_NAME, NULL, pProximNode->m_node );
+						Script::RunScript(pProximNode->m_script);
+					}
+
+					proxim_manager->m_inside_flag_valid = false;
+				}									
+				else if (p_script_target)
+				{
+					// Need to create an object with the id pProximNode->m_proxim_object
+
+#ifdef	DEBUG_PROXIM				
+					printf ("+++++ NOW OUTSIDE, killing %s running script %s \n",Script::FindChecksumName(pProximNode->m_proxim_object),Script::FindChecksumName(pProximNode->m_script));
+#endif				
+					Obj::CObject *p_obj  = Obj::CCompositeObjectManager::Instance()->GetObjectByID(pProximNode->m_proxim_object  + p_script_target->GetID());
+					if (p_obj)
+					{
+						p_obj->MarkAsDead();					
+					}
+					pProximNode->m_active_objects &= ~(1 << p_script_target->GetID());
+				}
+			}			
+		}		
+		pProximNode = pProximNode->m_pNext;
+	}				 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CProximNode::EShape CProximNode::sGetShapeFromChecksum(uint32 checksum)
+{
+	switch (checksum)
+	{
+	case 0xaa069978: // Sphere
+		return vSHAPE_SPHERE;
+
+	case 0xe460abde: // BoundingBox
+		return vSHAPE_BBOX;
+
+	case 0x3f1e5a7: // NonAxisAlignedBoundingBox
+		return vSHAPE_NON_AA_BBOX;
+
+	case 0x64fba415: // Cylinder
+		return vSHAPE_CYLINDER;
+
+	case 0x6aadf154: // Geometry
+		return vSHAPE_GEOMETRY;
+
+	default:
+		Dbg_MsgAssert(0, ("Unknown shape checksum %x\n", checksum));
+		return vSHAPE_SPHERE;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CProximNode::SetActive(bool active)
+{
+	// Check for changes
+	if (m_active != active)
+	{
+		if (m_active)
+		{
+			// Turning off node, so treat like were going outside
+			if ( m_proxim_object )
+			{
+				// Delete all created objects
+#ifdef	DEBUG_PROXIM			    
+				printf ("----- SETTING ProximNode INACTIVE, killing %s running script %s \n",Script::FindChecksumName(m_proxim_object),Script::FindChecksumName(m_script));
+#endif				
+				int object_id = 0;
+				while (m_active_objects)
+				{
+					if (m_active_objects & 0x1)	// Check if this ID is turned on
+					{
+						Obj::CObject *p_obj  = Obj::CCompositeObjectManager::Instance()->GetObjectByID(m_proxim_object + object_id);
+						if (p_obj)
+						{
+							p_obj->MarkAsDead();					
+						}
+					}
+
+					// Advance ID
+					m_active_objects = m_active_objects >> 1;
+					object_id++;
+				}
+			}									
+			else
+			{
+#ifdef	DEBUG_PROXIM				
+				printf ("----- SETTING ProximNode INACTIVE, running script %s \n",Script::FindChecksumName(m_script));
+#endif				
+				CProximManager* proxim_manager = Mdl::Skate::Instance()->mpProximManager;
+
+				if (proxim_manager)
+				{
+					int object_id = 0;
+					while (m_active_scripts)
+					{
+						if (m_active_scripts & 0x1)	// Check if this ID is turned on
+						{
+							Obj::CObject *p_obj  = Obj::CCompositeObjectManager::Instance()->GetObjectByID(object_id);
+							Dbg_MsgAssert(p_obj, ("Can't find object to play script on"));
+							proxim_manager->m_inside_flag = false;
+
+							proxim_manager->m_inside_flag_valid = true;
+							p_obj->SpawnAndRunScript( m_script, m_node );
+							proxim_manager->m_inside_flag_valid = false;
+						}
+
+						// Advance ID
+						m_active_scripts = m_active_scripts >> 1;
+						object_id++;
+					}
+				}
+			}
+		}
+
+		// Make everything outside
+		m_outside_flags = 0xffffffff;
+
+		m_active = active;
+	}
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CProximNode::InitGeometry(Nx::CSector *p_sector)
+{
+	mp_sector = p_sector;
+
+	if (!mp_sector)
+	{
+		// No Geometry
+		Dbg_MsgAssert(m_shape == CProximNode::vSHAPE_SPHERE, ("ProximNode %s with shape %d has no geometry.", Script::FindChecksumName(m_name), m_shape));
+		return;
+	}
+
+#ifdef	DEBUG_PROXIM				
+	Dbg_Message("Found Geometry for Proxim node %s at address %x", Script::FindChecksumName(m_name), mp_sector);
+#endif
+
+	switch (m_shape)
+	{
+	case CProximNode::vSHAPE_SPHERE:
+		Dbg_MsgAssert(0, ("ProximNode with sphere shape has geometry."));
+		break;
+
+	case CProximNode::vSHAPE_BBOX:
+		m_bbox = mp_sector->GetBoundingBox();
+#ifdef	DEBUG_PROXIM				
+		Dbg_Message("Bounding box of geometry (%f, %f, %f) - (%f, %f, %f)",
+					m_bbox.GetMin()[X], m_bbox.GetMin()[Y], m_bbox.GetMin()[Z],
+					m_bbox.GetMax()[X], m_bbox.GetMax()[Y], m_bbox.GetMax()[Z]);
+#endif	
+		break;
+
+	case CProximNode::vSHAPE_NON_AA_BBOX:
+		Dbg_MsgAssert(0, ("ProximNode with non axis aligned bounding box shape not supported yet."));
+		break;
+
+	case CProximNode::vSHAPE_CYLINDER:
+		Dbg_MsgAssert(0, ("ProximNode with cylinder shape not supported yet."));
+		break;
+	case CProximNode::vSHAPE_GEOMETRY:
+		Dbg_MsgAssert(0, ("ProximNode with geometry shape not supported yet."));
+		break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool				CProximNode::HasGeometry() const
+{
+	return mp_sector != NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CProximNode::CheckInside(const Mth::Vector &object_pos) const
+{
+	Mth::Vector		node_to_object = object_pos - m_pos;
+	float			dist_squared = node_to_object.LengthSqr();	   
+
+	bool inside = false;
+
+	switch (m_shape)
+	{
+	case vSHAPE_SPHERE:
+		inside = (dist_squared <= m_radius_squared);
+		break;
+
+	case vSHAPE_BBOX:
+		inside = m_bbox.Within(object_pos);
+#ifdef	DEBUG_PROXIM
+		m_bbox.DebugRender(0xff0000ff);
+		{
+			Mth::Vector closest_point;
+			m_bbox.GetClosestIntersectPoint(object_pos, closest_point);
+//			Gfx::AddDebugStar(closest_point);
+		}
+#endif //	DEBUG_PROXIM
+		break;
+
+	default:
+		Dbg_MsgAssert(0, ("Can't handle ProximNode type %d", m_shape));
+	}
+
+	return inside;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector 		CProximNode::GetClosestIntersectionPoint(const Mth::Vector &object_pos) const
+{
+	Mth::Vector intersect_point;
+
+	switch (m_shape)
+	{
+	case vSHAPE_SPHERE:
+		{
+			// Same for either within or outside sphere
+			Mth::Vector direction = object_pos - m_pos;
+			direction.Normalize();
+			direction *= m_radius;
+
+			intersect_point = m_pos + direction;
+		}
+		break;
+
+	case vSHAPE_BBOX:
+		m_bbox.GetClosestIntersectPoint(object_pos, intersect_point);
+		break;
+
+	default:
+		Dbg_MsgAssert(0, ("Can't handle ProximNode type %d", m_shape));
+		break;
+	}
+
+#ifdef	DEBUG_PROXIM
+//	Gfx::AddDebugStar(intersect_point);
+#endif //	DEBUG_PROXIM
+
+	return intersect_point;
+}
+
+/*
+{
+	// Node 572 (Mick Test)
+	Position = (-4060.666992,131.999329,-4604.379395)
+	Angles = (0.000000,-3.141593,0.000000)
+	Name = TRG_Micks_test
+	Class = ProximNode
+	Type = skater
+    radius = 1200
+	TriggerScript = xxx_test
+}
+*/
+
+
+}
+			   
+
diff --git a/Code/Sk/Objects/proxim.h b/Code/Sk/Objects/proxim.h
new file mode 100644
index 0000000..f3777a1
--- /dev/null
+++ b/Code/Sk/Objects/proxim.h
@@ -0,0 +1,135 @@
+//////////////////////////////////////////
+// Proxim.h
+
+#ifndef __OBJECTS_Proxim_H
+#define __OBJECTS_Proxim_H
+
+#include 
+
+#include 
+
+namespace Nx
+{
+	class	CSector;
+}
+
+namespace Script
+{
+	class	CScript;
+}
+
+namespace Obj
+{
+
+	class	CSkater;
+
+class CProximNode : public Spt::Class
+{
+public:
+	enum EShape
+	{
+		vSHAPE_SPHERE = 0,
+		vSHAPE_BBOX,			// Axis-aligned bounding box
+		vSHAPE_NON_AA_BBOX,		// Non-axis-aligned bounding box
+		vSHAPE_CYLINDER,		// Cylinder
+		vSHAPE_GEOMETRY,		// Geometry data
+	};
+
+	///////////
+	// Functions
+	void		SetActive(bool active);
+	void		InitGeometry(Nx::CSector *p_sector);
+	bool		HasGeometry() const;
+	bool		CheckInside(const Mth::Vector &object_pos) const;
+	Mth::Vector GetClosestIntersectionPoint(const Mth::Vector &object_pos) const;
+
+	// Checksum conversion functions
+	static EShape sGetShapeFromChecksum(uint32 checksum);
+
+	///////////
+	// Variables
+	int			m_node;				// Number of the node in the trigger array
+	int 		m_link;				// link number
+	uint32		m_name;				// checksum of name of the node in the trigger array
+	Mth::Vector m_pos;				// position of the node
+	Mth::Vector m_angles;			// angles of the node
+	bool 		m_active;			// Proxim segment starting at this node: active or not?
+
+	uint32 		m_proxim_object;	// checksum of name, if this is a ProximObject
+	uint32		m_active_objects;	// bit number corresponds to originating object ID
+	uint32		m_active_scripts;	// bit number corresponds to originating object ID
+
+	int			m_outside_flags;	// bitmask of flags, 0 = inside the radius, 1 = outside
+
+	int 		m_type;
+	EShape 		m_shape;
+
+	// Sphere data
+	float		m_radius;
+	float		m_radius_squared;
+
+	// Geometry Data
+	Nx::CSector *mp_sector;
+
+	// BBox data
+	Mth::CBBox	m_bbox;
+	
+	float		m_last_distance_squared;
+	
+	uint32 		m_script;
+
+	CProximNode *m_pNext;
+
+};
+
+ 
+
+void Proxim_AddNode(int node, bool active);
+void Proxim_Init();
+void Proxim_Cleanup();
+void Proxim_AddedAll();
+// activate/deactivate Proxim:
+void Proxim_SetActive( int node, int active);
+void Proxim_Update ( uint32 trigger_mask, CObject* p_script_target, const Mth::Vector& pos );
+
+class CProximManager : public Spt::Class
+{
+private:
+	static const int vMAX_NUM_PROXIM_TRIGGERS = 8 * sizeof(uint32);
+	
+public:
+	CProximManager() { m_inside_flag_valid = false; }
+
+	static CProximNode * sGetNode(uint32 checksum);
+
+	CProximNode*  m_pFirstNode;	
+		
+	uint32 GetProximTriggerMask ( int viewport );
+	
+	bool IsInsideFlagValid (   ) { return m_inside_flag_valid; }
+	bool IsInside (   ) { return m_inside_flag; }
+	
+private:
+	bool m_inside_flag;
+	bool m_inside_flag_valid;
+	
+	friend void Proxim_Update ( uint32 trigger_mask, CObject* p_script_target, const Mth::Vector& pos );
+	friend CProximNode;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline uint32 CProximManager::GetProximTriggerMask ( int viewport )
+{
+	return 1 << viewport;
+}
+
+} 
+			
+#endif // #ifndef __OBJECTS_Proxim_H
+			
+
+
diff --git a/Code/Sk/Objects/rail.cpp b/Code/Sk/Objects/rail.cpp
new file mode 100644
index 0000000..043165d
--- /dev/null
+++ b/Code/Sk/Objects/rail.cpp
@@ -0,0 +1,3154 @@
+///////////////////////////////////////////////////////
+// rail.cpp
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+		  
+// REMOVE
+#include 
+		  
+#ifdef	__PLAT_NGPS__	
+#include 			// for camera
+#include 
+#include 
+#endif
+
+//#define		DEBUG_EDITOR_RAILS
+
+namespace Obj
+{
+
+
+void Rail_ComputeBB(Mth::Vector &pos1, Mth::Vector &pos2, Mth::Vector &bb_min, Mth::Vector &bb_max)
+{
+	float x0 = pos1.GetX();
+	float x1 = pos2.GetX();
+	float min_x, max_x;
+	if (x0 < x1)
+	{
+		min_x = x0; max_x = x1;
+	}
+	else
+	{
+		min_x = x1; max_x = x0;
+	}
+
+	float y0 = pos1.GetY();
+	float y1 = pos2.GetY();
+	float min_y, max_y;
+	if (y0 < y1)
+	{
+		min_y = y0; max_y = y1;
+	}
+	else
+	{
+		min_y = y1; max_y = y0;
+	}
+
+	float z0 = pos1.GetZ();
+	float z1 = pos2.GetZ();
+	float min_z, max_z;
+	if (z0 < z1)
+	{
+		min_z = z0; max_z = z1;
+	}
+	else
+	{
+		min_z = z1; max_z = z0;
+	}
+
+	bb_min.Set(min_x, min_y, min_z);
+	bb_max.Set(max_x, max_y, max_z);
+}
+
+CRailManager::CRailManager()
+{
+//	mp_first_node = NULL;
+	mp_nodes = NULL;
+	mp_links = NULL;
+	m_num_nodes = 0;
+	m_is_transformed = false;
+	mp_node_array = NULL;
+	
+}
+
+CRailManager::~CRailManager()
+{	 
+	Cleanup();  
+}
+
+void CRailManager::Cleanup()
+{
+//	while (mp_first_node)
+//	{
+//		CRailNode *pNext = mp_first_node->m_pNext;
+//		delete mp_first_node;
+//		mp_first_node = pNext;			
+//	}    
+	
+	m_num_nodes = 0;
+	
+	Mem::Free(mp_nodes);
+	mp_nodes = NULL;
+	
+	
+	m_is_transformed = false;	
+	// Unhook from node array
+	mp_node_array = NULL;
+}
+
+
+#define CHECKSUM_TERRAIN_TYPE	0x54cf8532  // "terrainType"
+
+// Given two rail nodes that should be linked in a rail segment
+// then link them up as well as we are able
+// given that we only have a NEXT and a PREV pointer
+// so we give precedence to links to nodes that
+// are flagged "DefaultLine"
+
+void CRailManager::NewLink(CRailNode *p_from, CRailNode *p_to)
+{
+	// one of our links links to this node
+	// so we need to establish next and prev links
+	// or if they are established, then possibly override them			
+	if (!p_from->m_pNextLink)
+	{
+		// this is the first time we are linking to something
+		p_from->m_pNextLink = p_to;
+		Rail_ComputeBB(p_from->m_pos, p_to->m_pos, p_from->m_BBMin, p_from->m_BBMax);
+	}
+	else
+	{
+	
+		// already linked to something, so only override it
+		// if the new node is not DEFAULT_LINE
+		if (p_to->GetFlag(DEFAULT_LINE))
+		{
+			// This new link is DEFAULT_LINE
+			// so, as long as the previous link was not, we can override it
+			Dbg_MsgAssert(!p_from->m_pNextLink->GetFlag(DEFAULT_LINE),
+						  ("Node %d links to both %d and %d and both are DEFAULT_LINE",
+						   p_from->m_node,p_from->m_pNextLink->m_node,p_to->m_node));
+			// Overriding an existing NEXT link
+			p_from->m_pNextLink = p_to;
+			Rail_ComputeBB(p_from->m_pos, p_to->m_pos, p_from->m_BBMin, p_from->m_BBMax);
+		}
+		else
+		{
+			// The new link is not DEFAULT_LINE
+			// if the old one was, then leave it
+			// if neither is, then leave it, and maybe print a warning?
+			
+		}
+	}
+	
+	// Now handle the PREV link back from p_to to p_from
+	
+	if (!p_to->m_pPrevLink)
+	{
+		p_to->m_pPrevLink = p_from;
+	}
+	else
+	{
+
+		// something already linked to p_from via prev from p_to
+		if (p_from->GetFlag(DEFAULT_LINE))
+		{
+			// this is the default line, so make sure the old PREV node was not
+			Dbg_MsgAssert(!p_to->m_pPrevLink->GetFlag(DEFAULT_LINE),
+						  ("Node %d linked from both %d and %d and both are DEFAULT_LINE",
+						   p_to->m_node,p_to->m_pPrevLink->m_node,p_from->m_node));
+			
+			p_to->m_pPrevLink = p_from;
+			// Note, we don't re-calculate the bounding box here
+			// as that only applies to NEXT links
+		}
+	}
+}
+
+
+void CRailManager::AddRailNode(int node_number, Script::CStruct *p_node_struct)
+{
+
+							
+	// create a new rail node										   
+//	CRailNode *pRailNode = new CRailNode();
+	// Link into the head of the rail manager	
+	// Note, this must be done before we attempt to link up with other nodes								   
+//	pRailNode->m_pNext = mp_first_node; 							  
+//	mp_first_node = pRailNode;
+	
+	CRailNode *pRailNode = &mp_nodes[m_current_node];
+	CRailLinks *pLinkNode = &mp_links[m_current_node++];
+	
+	uint32 class_checksum;
+	p_node_struct->GetChecksum(CRCD(0x12b4e660, "class"), &class_checksum);
+	bool climbing = class_checksum == CRCD(0x30c19600, "ClimbingNode");
+	
+	uint32 type_checksum;
+	p_node_struct->GetChecksum(CRCD(0x7321a8d6, "type"), &type_checksum);
+
+	pRailNode->m_pNextLink = NULL;
+	pRailNode->m_pPrevLink = NULL;
+	for (int i=0;im_link[i] = -1;						// say it's not linked to anything, initially
+	}
+	pRailNode->m_node = node_number;			// The node_number is use primarily for calculating links
+	pRailNode->m_flags = 0;						// all flags off by default
+	pRailNode->SetActive(p_node_struct->ContainsFlag( 0x7c2552b9 /*"CreatedAtStart"*/ ));	// created at start or not?
+	
+    // set the position from the node structure
+	SkateScript::GetPosition(p_node_struct,&pRailNode->m_pos);
+
+	if (p_node_struct->ContainsFlag( 0xf4c67f0f /*"LipOverride"*/))
+	{
+		pRailNode->m_flags.Set(LIP_OVERRIDE);
+	}
+
+	if (p_node_struct->ContainsFlag(  0x4de721de/*"DefaultLine"*/))
+	{
+		pRailNode->m_flags.Set(DEFAULT_LINE);
+	}
+	
+	if (climbing)
+	{
+		pRailNode->m_flags.Set(ONLY_CLIMBING);
+		pRailNode->m_flags.Set(LADDER, type_checksum == CRCD(0xc84243da, "Ladder"));
+	}
+	
+	pRailNode->m_flags.Set(NO_HANG_WITH_RIGHT_ALONG_RAIL, p_node_struct->ContainsFlag(CRCD(0xf6dd21ca, "HangLeft")));
+	pRailNode->m_flags.Set(NO_HANG_WITH_LEFT_ALONG_RAIL, p_node_struct->ContainsFlag(CRCD(0x5e9ce29b, "HangRight")));
+	pRailNode->m_flags.Set(NO_CLIMBING, p_node_struct->ContainsFlag(CRCD(0xf22bfc6d, "NoClimbing")));
+    
+	if (climbing)
+	{
+		// set the orientation from the node structure
+		Mth::Vector angles;
+		SkateScript::GetAngles(p_node_struct, &angles);
+		Mth::Matrix matrix;
+		pRailNode->m_orientation = matrix.SetFromAngles(angles);
+	}
+
+	// no terrain types for climbing nodes
+	if (!climbing)
+	{
+		pRailNode->m_terrain_type = 0;  // default terrain type...
+		uint32 terrain_checksum;
+		int terrain = 0;
+		p_node_struct->GetChecksum( CHECKSUM_TERRAIN_TYPE, &terrain_checksum, Script::ASSERT );
+		terrain = Env::CTerrainManager::sGetTerrainFromChecksum(terrain_checksum);
+		Dbg_MsgAssert(terrain >= 0 && terrain < 256,("Rail node %d, terrain type %d too big\n",node_number,terrain));
+		pRailNode->m_terrain_type = (uint8) terrain ;
+	}
+
+
+	int links = SkateScript::GetNumLinks(p_node_struct);
+	if (links)
+	{
+		Dbg_MsgAssert(links <= MAX_RAIL_LINKS,("Rail node %d has %d linkes, max is %d",node_number,links,MAX_RAIL_LINKS));
+		for (int i=0;im_link[i] = SkateScript::GetLink(p_node_struct,i);
+		}
+	}	
+	
+	Rail_ComputeBB(pRailNode->m_pos, pRailNode->m_pos, pRailNode->m_BBMin, pRailNode->m_BBMax);
+
+// Linking is now donw after they are all loaded
+// Making it O(n) rather than O(n^2)
+/*	
+	// try going through all the nodes to find one linked to this one
+	// and to find the target node, if it exists
+	CRailNode *p_link_node = pRailNode->m_pNext;
+	while (p_link_node)
+	{
+		// Check links from the new node to all the existing nodes
+		for (int i=0;im_link[i] == p_link_node->m_node)
+			{
+				NewLink(pRailNode,p_link_node);
+			}
+		}
+		
+		// check links from the existing nodes to the new node													  
+		for (int i=0;im_link[i] == pRailNode->m_node)
+			{
+				NewLink(p_link_node,pRailNode);
+			}
+		}
+	   	p_link_node = p_link_node->m_pNext;		
+	}
+*/	
+}
+
+// returns a positive number that is the amount of an axis
+// required to totally encompass the 1D line segments a-b and c-d
+static inline float span_4(float a, float b, float c, float d)
+{
+	float min = a;
+	if (b < min) min = b;
+	if (c < min) min = c;
+	if (d < min) min = d;
+	float max = a;
+	if (b > max) max = b;
+	if (c > max) max = c;
+	if (d > max) max = d;
+	return max-min;	
+}
+
+															 
+// returns the amount two 1D line segments overlap
+// works by adding together the length of both lines
+// and then subtracting the "span" of the the min and max points of the lines															 
+static inline float overlap(float a, float b, float c, float d)
+{
+	return  Mth::Abs(b-a) + Mth::Abs(d-c) - span_4(a,b,c,d);
+}
+
+static inline bool nearly_the_same(float a, float b, float c, float d, float nearly = 1.0f)
+{
+	return ( Mth::Abs(a-b) < nearly
+		 && Mth::Abs(b-c) < nearly
+		 && Mth::Abs(c-d) < nearly);
+}	
+
+void	CRailManager::RemoveOverlapping()
+{
+	printf ("Starting overlapping rail removal\n");
+	
+	int	removed =0;
+//	CRailNode *pRailNode = mp_first_node;
+//	while (pRailNode)
+//	{
+	for (int node=0;nodem_pNextLink)	   	// it's a segment
+		{
+
+			Mth::Vector	start = pRailNode->m_pos;
+			Mth::Vector	end = pRailNode->m_pNextLink->m_pos;
+
+			Mth::Vector dir = end-start;
+			dir.Normalize();
+
+			// we expand the bounding box, as they might not really be that close			
+			Mth::Vector bb_min = pRailNode->m_BBMin;
+			bb_min[X] -= 16.0f;
+			bb_min[Y] -= 16.0f;
+			bb_min[Z] -= 16.0f;
+			Mth::Vector bb_max = pRailNode->m_BBMax;
+			bb_max[X] += 16.0f;
+			bb_max[Y] += 16.0f;
+			bb_max[Z] += 16.0f;
+			
+			
+			int check_node = node+1;
+//			CRailNode *pCheckNode = pRailNode->m_pNext;
+			while (check_nodeIsActive())
+			{
+				CRailNode *pCheckNode = &mp_nodes[check_node];
+				if (pCheckNode->m_pNextLink)	   	// it's a segment
+				{
+				   	// first check to see if bounding boxes overlap
+					  
+					if (!(pCheckNode->m_BBMin.GetX() > bb_max.GetX()))
+					if (!(pCheckNode->m_BBMax.GetX() < bb_min.GetX()))
+					if (!(pCheckNode->m_BBMin.GetZ() > bb_max.GetZ()))
+					if (!(pCheckNode->m_BBMax.GetZ() < bb_min.GetZ()))
+					if (!(pCheckNode->m_BBMin.GetY() > bb_max.GetY()))
+					if (!(pCheckNode->m_BBMax.GetY() < bb_min.GetY()))
+					{					
+					
+						// bounding boxes overlap 						
+						// check to see if rails are roughly parallel
+						Mth::Vector check_start = pCheckNode->m_pos;
+						Mth::Vector check_end = pCheckNode->m_pNextLink->m_pos;
+						Mth::Vector check_dir = check_end - check_start;
+
+						check_dir.Normalize();						
+						float dot = Mth::DotProduct(dir, check_dir);
+//						printf ("Bounding Box Overlap, dot = %f\n",dot);
+						
+						if (dot < -0.99f || dot > 0.99f)
+						{
+							// lines are roughly parallel
+							// so check if they overlap significantly in at least one axis
+							// and/or are very close in one other axis
+							// we only check X and Z
+							// as we don't have any vertical rails in the park
+							// editor
+							// (note, I'm prematurely optimizing here...)
+							
+							int overlaps = 0;
+							int close =  0;
+							
+							// now check the distance between the lines
+							// which is the components of start -> check_start
+							// that is perpendicular to start -> end
+							const float significant = 6.0f;	// six inches is significant, I feel...
+							Mth::Vector	s_cs = check_start - start;
+							Mth::Vector	s_e = end - start;
+							s_e.Normalize();
+							Mth::Vector perp = s_cs;
+							perp.ProjectToPlane(s_e); 
+							if ( perp.Length() significant)
+									overlaps++;
+								if (overlap(start[Z],end[Z],check_start[Z],check_end[Z]) > significant)
+									overlaps++;
+								if (nearly_the_same(start[X],end[X],check_start[X],check_end[X],6.0f))
+									close++;
+								if (nearly_the_same(start[Z],end[Z],check_start[Z],check_end[Z],6.0f))
+									close++;
+//								printf ("dot close, overlaps = %d, close = %d\n",overlaps,close);
+								if (overlaps + close >= 2)
+								{
+									// it's a duplicate
+//									printf("Removing duplicate rail !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+									
+									// add a magenta line for rails that have been removed
+									#ifdef		DEBUG_EDITOR_RAILS
+									pRailNode->m_pos += Mth::Vector(0.0f,2.0f,0.0f);
+									pRailNode->m_pNextLink->m_pos += Mth::Vector(0.0f,2.0f,0.0f);
+									Gfx::AddDebugLine( pRailNode->m_pos, pRailNode->m_pNextLink->m_pos, MAKE_RGB( 255, 0, 255 ),MAKE_RGB( 255,0, 255 ) );
+									pRailNode->m_pos -= Mth::Vector(0.0f,2.0f,0.0f);
+									pRailNode->m_pNextLink->m_pos -= Mth::Vector(0.0f,2.0f,0.0f);
+									#endif
+									
+									removed ++;
+									// Remove the one that is lower
+									if (pRailNode->m_pos[Y] < pCheckNode->m_pos[Y])
+									{
+										pRailNode->SetActive(false);
+									}
+									else
+									{
+										pCheckNode->SetActive(false);
+									}
+									break;
+								}
+							}
+						}
+					}
+				}
+				check_node++;
+//				pCheckNode = pCheckNode->m_pNext;
+			}				
+		}
+//		pRailNode = pRailNode->m_pNext;
+	}		 
+	printf ("Done overlapping rail removal, removed %d\n", removed);
+}
+
+
+void CRailManager::AddedAll()
+{
+	
+//	printf("Added all rails\n");		 
+
+	#if 1
+	if (Ed::CParkEditor::Instance()->UsingCustomPark()) 		// is it a custom park???
+	{
+		// yes, so remove overlapping rails
+		RemoveOverlapping();	
+	}	 
+	#endif
+
+	
+}			   
+
+void CRailManager::SetActive( int node, int active, bool wholeRail )
+{
+	
+//	CRailNode *pRailNode = mp_first_node;
+//	while (pRailNode)
+	for (int check_node = 0;check_nodem_node == node)
+		{
+			if ( wholeRail )
+			{
+				while ( pRailNode )
+				{
+					pRailNode->SetActive(active);
+					pRailNode = pRailNode->m_pNextLink;
+				}
+				return;
+			}
+			pRailNode->SetActive(active);
+			return;
+		}
+//		pRailNode = pRailNode->m_pNext;
+	}				 
+}
+
+bool CRailManager::IsActive( int node )
+{
+//	CRailNode *pRailNode = mp_first_node;
+//	while (pRailNode)
+	for (int check_node = 0;check_nodem_node == node)
+		{
+			return ( pRailNode->GetActive() );
+		}
+//		pRailNode = pRailNode->m_pNext;
+	}
+	return ( false );
+}
+
+
+// A node in the node array has moved (due to realtime editing)
+// so find that node here, and update the position
+// and update bounding boxes as needed 
+//
+
+void CRailManager::MoveNode( int node, Mth::Vector &pos )
+{
+	for (int check_node = 0;check_nodem_node == node)
+		{
+			// Set The position
+			pRailNode->m_pos = pos;
+			
+			// calculate the bounding box of any segment this node starts
+			// (there can be only one)
+			if (pRailNode->GetNextLink())
+			{
+				Mth::Vector from_pos = pRailNode->m_pos; 
+				Mth::Vector to_pos = pRailNode->GetNextLink()->m_pos;
+				Rail_ComputeBB(from_pos ,to_pos , pRailNode->m_BBMin, pRailNode->m_BBMax);				
+			}
+			
+			// Check to see if there are any nodes leading to this one
+			// (this initial check is only for speed)
+			if (pRailNode->GetPrevLink())
+			{
+				// There is one, but there might be more that one
+				// the only way to know, is to go through all the nodes and see if any points to this one
+				// 
+				for (int from_node = 0;from_nodeGetNextLink() == pRailNode)
+					{
+						Rail_ComputeBB(p_from->m_pos, pRailNode->m_pos, p_from->m_BBMin, p_from->m_BBMax);
+					}
+				}
+			}
+			// There is only going to be one mathcing node, so we can now return		
+			return;
+		} // end if (pRailNode->m_node == node)
+	}				 
+}
+
+
+
+
+// Given a velocity, and a rail node
+// then see which side of the rail (left or right)
+// has no collision
+// returns -1 if left, 0 if neither or both, and +1 if right
+// Used in the park editor to give preference to those
+// rails that keep the same sideness as the prior rail we were on
+int	CRailNode::Side(const Mth::Vector &vel) const
+{
+	Mth::Vector Start = m_pos;
+	Mth::Vector End = 	m_pNextLink->m_pos;
+
+	CFeeler	feeler;
+
+ // Find a point "Mid", which is 1/4 of the way along the rail   
+ // and lowered six inches
+	Mth::Vector	Mid = (Start + End) / 2.0f;			// Half way along
+	Mid = Start + ((Mid - Start) / 2.0f);		    // quarter of the way along	
+	Mid[Y] -= 6.0f;									// lowered a bit
+
+// Find a vector "Side" which is horizontal, perpendicular to the rail, and 4 inches long	
+	Mth::Vector	Side = End - Start;	   			// vector along the rail
+	Side[Y] = 0.0f;								// horizontal
+	Side.Normalize(4.0f);						// this is half the width of the thickest rail.  Was 4.0, changed for Tokyo mega funbox
+	float temp = Side[X];			 				// make perpendicular to rail
+	Side[X] = Side[Z];
+	Side[Z] = -temp;
+
+
+	bool	left = false;
+	bool	right = false;
+	
+// if collision above me, left to right
+	feeler.SetLine(Mid + Side, Mid-Side);	
+	if (feeler.GetCollision())
+	{
+		left = true;
+	}
+
+// if collision above me, right to left
+	feeler.FlipDirection();
+	if (feeler.GetCollision())
+	{
+		right = true;
+	}
+	else
+	{
+	}
+	
+	int side = 0;
+	if (left && !right)
+	{
+		side = -1;
+	}
+	else
+	{
+		if (right && ! left)
+		{
+			side = 1;
+		}
+	}
+	
+	// flip is we are going the opposite direction on the rail
+	if ( side && (Mth::DotProduct(vel,End-Start) < 0.0f) )
+	{
+		side = -side;
+	}
+	
+	return side;	
+}
+
+
+void CRailManager::UpdateTransform(Mth::Matrix& transform)
+{
+
+	m_is_transformed = true;
+	m_transform = transform;
+}
+
+
+bool CRailManager::CheckForLadderRail ( const Mth::Vector& pos, float max_horizontal_snap_distance, float max_vertical_snap_distance, bool up, CWalkComponent* p_walk_component, SLadderRailData& rail_data, const CRailNode** pp_rail_node )
+{
+    float max_horizontal_snap_distance_sqr = Mth::Sqr(max_horizontal_snap_distance);
+    
+    for (int check_node_idx = m_num_nodes; check_node_idx--; )
+    {
+        CRailNode* p_node = &mp_nodes[check_node_idx];
+        
+		if (!p_node->GetFlag(LADDER) || !p_node->GetActive()) continue;
+        
+        // quick oriented bounding box check
+        if (Mth::Abs(p_node->m_pos[X] - pos[X]) > max_horizontal_snap_distance) continue;
+        if (Mth::Abs(p_node->m_pos[Z] - pos[Z]) > max_horizontal_snap_distance) continue;
+        if (Mth::Abs(p_node->m_pos[Y] - pos[Y]) > max_vertical_snap_distance) continue;
+        
+        // actual distance check
+        Mth::Vector offset = p_node->m_pos - pos;
+        if (offset[X] * offset[X] + offset[Z] * offset[Z] > max_horizontal_snap_distance_sqr) continue;
+        
+        // find the ladder's partiner node
+        const CRailNode* p_partiner_node = p_node->GetNextLink() ? p_node->GetNextLink() : p_node->GetPrevLink();
+        Dbg_Assert(p_partiner_node);
+        
+		if (up)
+		{
+			if (!p_walk_component->FilterLadderUpRail(p_node->m_pos, p_partiner_node->m_pos, GetMatrix(p_node), rail_data)) continue;
+		}
+		else
+		{
+			if (!p_walk_component->FilterLadderDownRail(p_partiner_node->m_pos, p_node->m_pos, GetMatrix(p_partiner_node), rail_data)) continue;
+		}
+        
+        // for now we do no comparison between ladders; they should be sparce enough that we can just take the first ladder rail we find
+		
+		// return the bottom node of the ladder
+		if (up)
+		{
+			*pp_rail_node = p_node;
+		}
+		else
+		{
+			*pp_rail_node = p_partiner_node;
+		}
+        return true;
+    }
+    
+    return false;
+}
+
+bool CRailManager::CheckForAirGrabLadderRail ( const Mth::Vector& start_pos, const Mth::Vector& end_pos, CWalkComponent* p_walk_component, SLadderRailData& rail_data, const CRailNode** pp_rail_node )
+{
+	*pp_rail_node = NULL;
+	
+	Mth::Line movement;
+	movement.m_start = start_pos;
+	movement.m_end = end_pos;
+	
+	// find bounding box for movement
+	Mth::Vector bb_min, bb_max;
+	Rail_ComputeBB(movement.m_start, movement.m_end, bb_min, bb_max);
+	float snap_dist_sqr = Script::GetFloat(CRCD(0x30ce7f2c, "Climb_Max_Snap"));
+	bb_min.Set(bb_min.GetX() - snap_dist_sqr, bb_min.GetY() - snap_dist_sqr, bb_min.GetZ() - snap_dist_sqr);	
+	bb_max.Set(bb_max.GetX() + snap_dist_sqr, bb_max.GetY() + snap_dist_sqr, bb_max.GetZ() + snap_dist_sqr);	
+	snap_dist_sqr *= snap_dist_sqr;
+	
+	float closest_dist_sqr = 10000000.0f * 10000000.0f;
+
+	for (int check_node = 0; check_node < m_num_nodes; check_node++)
+	{
+		CRailNode* pRailNode = &mp_nodes[check_node];
+		if (!pRailNode->GetFlag(LADDER) || !pRailNode->GetActive() || !pRailNode->m_pNextLink) continue;
+		
+		// First do bounding box test, before time-intensive LineLineIntersect test 
+		if (bb_min.GetX() > pRailNode->m_BBMax.GetX()) continue;
+		if (bb_max.GetX() < pRailNode->m_BBMin.GetX()) continue;
+		if (bb_min.GetZ() > pRailNode->m_BBMax.GetZ()) continue;
+		if (bb_max.GetZ() < pRailNode->m_BBMin.GetZ()) continue;
+		if (bb_min.GetY() > pRailNode->m_BBMax.GetY()) continue;
+		if (bb_max.GetY() < pRailNode->m_BBMin.GetY()) continue;
+		
+		// we have a rail segment with a BB match
+		// so see if we are close to it 
+		Mth::Line segment;
+		segment.m_start = pRailNode->m_pos;
+		segment.m_end = pRailNode->m_pNextLink->m_pos;
+		Mth::Vector p1, p2;
+		float f1,f2;
+		if (!Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2)) continue;
+		
+		Mth::Vector	to_rail = p2 - p1;
+		float dist_sqr = to_rail.LengthSqr();
+		
+		if (dist_sqr > snap_dist_sqr || dist_sqr > closest_dist_sqr) continue;
+		
+		SLadderRailData this_data;
+		if (pRailNode->m_pos[Y] < pRailNode->m_pNextLink->m_pos[Y])
+		{
+			if (!p_walk_component->FilterLadderAirGrabRail(pRailNode->m_pos, pRailNode->m_pNextLink->m_pos, GetMatrix(pRailNode), p2, f2, this_data)) continue;
+			*pp_rail_node = pRailNode;
+		}
+		else
+		{
+			if (!p_walk_component->FilterLadderAirGrabRail(pRailNode->m_pNextLink->m_pos, pRailNode->m_pos, GetMatrix(pRailNode->m_pNextLink), p2, 1.0f - f2, this_data)) continue;
+			*pp_rail_node = pRailNode->m_pNextLink;
+		}
+		
+		closest_dist_sqr = dist_sqr;
+		rail_data = this_data;
+	}
+
+	return *pp_rail_node;
+}
+
+/*
+// check for rails near but perpendicular to check_line
+bool CRailManager::CheckForHopToHangRail ( const Mth::Vector& check_line_start, float check_line_height, const Mth::Vector& facing, float max_forward_reach, float max_backward_reach, CWalkComponent* p_walk_component, SHangRailData& rail_data, const CRailNode** pp_rail_node )
+{
+	// this logic assumes Y is up, which might not be true if we're checking a movable rail manager
+	// for now, no hopping to hang from movable rail managers which are not nicely oriented
+	if (Mth::Abs(facing[Y]) > 0.1f) return false;
+	
+	// The check box is aligned with the Y axis and the facing, is check_line_height tall,
+	// and has a max_foward_reach + max_backward_reach by vCHECK_BOX_WIDTH foot print in the horizontal plane.
+	
+	*pp_rail_node = NULL;
+	float best_facing_distance = 0.0f;
+	float best_height = 0.0f;
+	
+	float check_line_X_start = check_line_start[X] - max_backward_reach * facing[X];
+	float check_line_X_delta = (max_backward_reach + max_forward_reach) * facing[X];
+	float check_line_Z_start = check_line_start[Z] - max_backward_reach * facing[Z];
+	float check_line_Z_delta = (max_backward_reach + max_forward_reach) * facing[Z];
+	
+	for (int check_node_idx = m_num_nodes; check_node_idx--; )
+	{
+		CRailNode* p_node = &mp_nodes[check_node_idx];
+		
+		if (p_node->GetFlag(LADDER) || p_node->GetFlag(NO_CLIMBING) || !p_node->GetActive() || !p_node->m_pNextLink) continue;
+		
+		// determine the clostest point on the rail in the horizontal plane
+		float s;
+		if (!Nx::CCollObj::s2DLineLineCollision(
+			p_node->m_pos[X],
+			p_node->m_pos[Z],
+			p_node->m_pNextLink->m_pos[X] - p_node->m_pos[X],
+			p_node->m_pNextLink->m_pos[Z] - p_node->m_pos[Z],
+			check_line_X_start,
+			check_line_Z_start,
+			check_line_X_delta,
+			check_line_Z_delta,
+			&s
+		)) continue;
+		Mth::Vector closest_point = p_node->m_pos + s * (p_node->m_pNextLink->m_pos - p_node->m_pos);
+		
+		// calculate the offset vector from the bottom of the check line to the closest point
+		Mth::Vector offset_to_rail = closest_point - check_line_start;
+		
+		// calculate distance along our facing to the closest point
+		float facing_distance = offset_to_rail[X] * facing[X] + offset_to_rail[Z] * facing[Z];
+		
+		// check if the closest point is within reach
+		if (facing_distance > max_forward_reach) continue;
+		if (facing_distance < -max_backward_reach) continue;
+		
+		// check to see if the closest point is within the allowed height range
+		if (closest_point[Y] < check_line_start[Y]) continue;
+		if (closest_point[Y] > check_line_start[Y] + check_line_height) continue;
+		
+		SHangRailData this_data;
+		if (!p_walk_component->FilterHangRail(GetPos(p_node), GetPos(p_node->m_pNextLink), LocalToWorldTransform(closest_point), s, this_data, false)) continue;
+		
+		if (*pp_rail_node)
+		{
+			// logic determining if this rail is better
+			
+			// take rail in front over rail behind
+			if (best_facing_distance >= 0.0f && facing_distance < 0.0f) continue;
+			if (!(facing_distance >= 0.0f && best_facing_distance < 0.0f))
+			{
+				// otherwise, take lowest rail
+				if (offset_to_rail[Y] > best_height) continue;
+				if (offset_to_rail[Y] == best_height)
+				{
+					// otherwise, take the closest rail horizontally
+					if (Mth::Abs(facing_distance) > Mth::Abs(best_facing_distance)) continue;
+				}
+			}
+		}
+		
+		// this is the best (or first) rail so far
+		*pp_rail_node = p_node;
+		best_facing_distance = facing_distance;
+		best_height = offset_to_rail[Y];
+		rail_data = this_data;
+		
+		Gfx::AddDebugStar(closest_point,  36.0f,  MAKE_RGB(255, 0, 0), 1);
+	}
+	
+	return *pp_rail_node;
+}
+*/
+
+bool CRailManager::CheckForHangRail ( const Mth::Vector& start_pos, const Mth::Vector& end_pos, const Mth::Vector& facing, CWalkComponent* p_walk_component, SHangRailData& rail_data, float snap_distance )
+{
+	bool rail_found = false;
+	
+	Mth::Line movement;
+	movement.m_start = start_pos;
+	movement.m_end = end_pos;
+	
+	// find bounding box for movement
+	Mth::Vector bb_min, bb_max;
+	Rail_ComputeBB(movement.m_start, movement.m_end, bb_min, bb_max);
+	float snap_dist_sqr = snap_distance;
+	bb_min.Set(bb_min.GetX() - snap_dist_sqr, bb_min.GetY() - snap_dist_sqr, bb_min.GetZ() - snap_dist_sqr);	
+	bb_max.Set(bb_max.GetX() + snap_dist_sqr, bb_max.GetY() + snap_dist_sqr, bb_max.GetZ() + snap_dist_sqr);	
+	snap_dist_sqr *= snap_dist_sqr;
+	
+	float closest_dist_sqr = 10000000.0f * 10000000.0f;
+
+	for (int check_node = 0; check_node < m_num_nodes; check_node++)
+	{
+		CRailNode* pRailNode = &mp_nodes[check_node];
+		if (pRailNode->GetFlag(LADDER) || pRailNode->GetFlag(NO_CLIMBING) || !pRailNode->GetActive() || !pRailNode->m_pNextLink) continue;
+		
+		// First do bounding box test, before time-intensive LineLineIntersect test 
+		if (bb_min.GetX() > pRailNode->m_BBMax.GetX()) continue;
+		if (bb_max.GetX() < pRailNode->m_BBMin.GetX()) continue;
+		if (bb_min.GetZ() > pRailNode->m_BBMax.GetZ()) continue;
+		if (bb_max.GetZ() < pRailNode->m_BBMin.GetZ()) continue;
+		if (bb_min.GetY() > pRailNode->m_BBMax.GetY()) continue;
+		if (bb_max.GetY() < pRailNode->m_BBMin.GetY()) continue;
+		
+		// we have a rail segment with a BB match
+		// so see if we are close to it 
+		Mth::Line segment;
+		segment.m_start = pRailNode->m_pos;
+		segment.m_end = pRailNode->m_pNextLink->m_pos;
+		Mth::Vector p1, p2;
+		float f1,f2;
+		if (!Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2)) continue;
+		
+		Mth::Vector	to_rail = p2 - p1;
+		if (to_rail.LengthSqr() > snap_dist_sqr) continue;
+		
+		// count rails in front of us as three times closer
+		float dot = Mth::DotProduct(facing, to_rail);
+		if (dot > 0.0f)
+		{
+			to_rail -= (2.0f / 3.0f * dot) * facing;
+		}
+		float dist_sqr = to_rail.LengthSqr();
+		
+		if (dist_sqr > closest_dist_sqr) continue;
+		
+		if (!Rail_ValidInEditor(GetPos(pRailNode), GetPos(pRailNode->m_pNextLink))) continue;
+		
+		SHangRailData this_data;
+		this_data.p_rail_node = pRailNode;
+		if (!p_walk_component->FilterHangRail(GetPos(pRailNode), GetPos(pRailNode->m_pNextLink), LocalToWorldTransform(p2), LocalToWorldTransform(p1), f2, this_data, false)) continue;
+		
+		closest_dist_sqr = dist_sqr;
+		rail_data = this_data;
+		rail_found = true;
+	}
+	
+	return rail_found;
+}
+
+bool CRailManager::RailNodesAreCoincident ( const CRailNode* p_node_a, const CRailNode* p_node_b )
+{
+	const float epsilon = 1.0f;
+	
+	if (Mth::Abs(p_node_a->m_pos[X] - p_node_b->m_pos[X]) > epsilon) return false;
+	if (Mth::Abs(p_node_a->m_pos[Y] - p_node_b->m_pos[Y]) > epsilon) return false;
+	if (Mth::Abs(p_node_a->m_pos[Z] - p_node_b->m_pos[Z]) > epsilon) return false;
+	return true;
+}
+
+// look for another rail node within a few inches of the given node's position
+bool CRailManager::CheckForCoincidentRailNode ( const CRailNode* p_node, uint32 ignore_mask, const CRailNode** pp_next_node )
+{
+	for (int check_node = 0; check_nodem_pNextLink && !pRailNode->m_pPrevLink) continue;
+		
+		if (pRailNode == p_node) continue;
+		
+		if (pRailNode->m_pNextLink && pRailNode->IsActive() && !pRailNode->m_flags.TestMask(ignore_mask))
+		{
+			// MESSAGE("found potential coincident");
+			if (Rail_ValidInEditor(GetPos(pRailNode), GetPos(pRailNode->m_pNextLink)))
+			{
+				*pp_next_node = pRailNode;
+				// MESSAGE("found good coincident");
+				return true;
+			}
+			else
+			{
+				// MESSAGE("not valid coincident");
+			}
+		}
+		
+		if (pRailNode->m_pPrevLink && pRailNode->m_pPrevLink->IsActive() && !pRailNode->m_pPrevLink->m_flags.TestMask(ignore_mask))
+		{
+			// MESSAGE("found potential coincident");
+			if (Rail_ValidInEditor(GetPos(pRailNode->m_pPrevLink), GetPos(pRailNode)))
+			{
+				*pp_next_node = pRailNode->m_pPrevLink;
+				// MESSAGE("found good coincident");
+				return true;
+			}
+			else
+			{
+				// MESSAGE("not valid coincident");
+			}
+		}
+	}
+	
+	return false;
+}
+
+// see if we can stick to a rail	
+bool CRailManager::StickToRail(const Mth::Vector &pos1, const Mth::Vector &pos2, Mth::Vector *p_point, CRailNode **pp_rail_node, const CRailNode *p_ignore_node, float min_dot, int side)
+{
+	
+	// Go through all the rail segments, and find the shortest distance to line
+	// and there is your rail
+	
+	Mth::Line  movement;
+	movement.m_start = pos1;
+	movement.m_end = pos2;
+	
+	// find bounding box for skater
+	Mth::Vector bb_min, bb_max;
+	Rail_ComputeBB(movement.m_start, movement.m_end, bb_min, bb_max);
+	float snap_dist = Script::GetFloat(CRCD(0xf840753e, "Rail_Max_Snap"));
+	bb_min.Set(bb_min.GetX() - snap_dist, bb_min.GetY() - snap_dist, bb_min.GetZ() - snap_dist);	
+	bb_max.Set(bb_max.GetX() + snap_dist, bb_max.GetY() + snap_dist, bb_max.GetZ() + snap_dist);	
+	
+	float		closest_dist = 10000000.0f;
+	CRailNode   * p_closest_rail = NULL;
+	Mth::Vector		closest_point; 
+
+	bool	found = false;
+						
+
+//	CRailNode *pRailNode = mp_first_node;
+//	while (pRailNode)
+	for (int check_node = 0;check_nodeGetFlag(ONLY_CLIMBING) && pRailNode != p_ignore_node && pRailNode->GetActive())
+		{
+			if (pRailNode->m_pNextLink)
+			{
+				// First do bounding box test, before time-intensive LineLineIntersect test 
+				
+				// *** IMPORTANT ***
+				// There should be indentations for each 'if', but I left them out for readability
+				if (!(bb_min.GetX() > pRailNode->m_BBMax.GetX()))
+				if (!(bb_max.GetX() < pRailNode->m_BBMin.GetX()))
+				if (!(bb_min.GetZ() > pRailNode->m_BBMax.GetZ()))
+				if (!(bb_max.GetZ() < pRailNode->m_BBMin.GetZ()))
+				if (!(bb_min.GetY() > pRailNode->m_BBMax.GetY()))
+				if (!(bb_max.GetY() < pRailNode->m_BBMin.GetY()))
+				{
+					// we have a rail segment with a BB match
+					// so see if we are close to it 
+					Mth::Line segment;
+					segment.m_start = pRailNode->m_pos;
+					segment.m_end = pRailNode->m_pNextLink->m_pos;
+	
+					if (Rail_ValidInEditor(pRailNode->m_pos,pRailNode->m_pNextLink->m_pos))
+					{
+						Mth::Vector p1,p2;
+						float  f1,f2;
+						if (Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2))
+						{
+		
+							Mth::Vector	to_rail = p2-p1;
+							float 	dist = to_rail.Length();
+							float	old_dist = dist; 
+							
+							// calculate the dot product of the
+							// the movement and the rail in the XZ plane
+							Mth::Vector  v1,v2;
+							v1 = segment.m_end - segment.m_start;
+							v2 = pos2 - pos1;
+							v1[Y] = 0.0f;
+							v2[Y] = 0.0f;
+							v1.Normalize();
+							v2.Normalize();
+							float dot = Mth::Abs(Mth::DotProduct(v1,v2));
+	
+							// if now v2 (the skater's movement vector) is all zero
+							// and the dot is 0.0f
+							// then that means we were going precisely straght up
+							// so we set the dot to 1,
+							// as normally (if we we slightly left or right)
+							// we would be going along the rail at the top of the pipe
+							// in the XZ plane (albeit slowly)
+							if (v2[X] == 0.0f && v2[Z] == 0.0f && dot == 0.0f)
+							{
+								dot = 1.0f;
+							}
+	
+	
+							dist = dist * (0.122f + 1.0f-dot);		// was (50 + (4096-dist)) on PS1						
+							old_dist = old_dist * (2.0f - dot);		// was (8192-dot) on PS1 
+							
+	//						printf ("dot=%.2f, dist=%.2f, old_dist=%.2f, min_dot=%.2f\n",dot,dist,old_dist,min_dot);
+	
+							if (side)
+							{
+								Mth::Vector vel = pos2-pos1;
+								if (pRailNode->Side(vel) != side)
+								{
+									dist *= 2.0f;
+	//								printf ("side change, dist doubled to %.2f\n",dist);
+								}
+							}
+							
+		
+							if (dist < closest_dist)
+							{						
+								bool close = true;
+								// if we have a maximum dot, then check we don't go over it
+								if (min_dot != 1.0f)
+								{
+									if (Mth::Abs(dot) < min_dot)
+									{
+										close = false; 	 
+									}
+								}							
+								if (close)
+								{
+									if (old_dist > snap_dist)
+									{
+										// there is a good rail, but too far away
+										// so kill any rail we've found so far
+										// and make this the new target
+										closest_dist = dist;
+										found = false; 
+									}
+									else
+									{
+										// good rail, and close enough
+										closest_dist = dist;
+										closest_point = p2;
+										p_closest_rail = pRailNode;	  
+										found = true; 
+									}							
+								}
+							}						  	
+						} // end if (bb_test && Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2))
+					} // end if (Rail_ValidInEditor(pRailNode->m_pos,pRailNode->m_pNextLink->m_pos))
+				} // end whole big bounding box test
+			} // end if (pRailNode->m_pNextLink)
+			else if (!pRailNode->m_pPrevLink && !pRailNode->m_pNextLink)
+			{
+				// special logic for a single node rail
+				if (!(bb_min.GetX() > pRailNode->m_BBMax.GetX()))
+				if (!(bb_max.GetX() < pRailNode->m_BBMin.GetX()))
+				if (!(bb_min.GetZ() > pRailNode->m_BBMax.GetZ()))
+				if (!(bb_max.GetZ() < pRailNode->m_BBMin.GetZ()))
+				if (!(bb_min.GetY() > pRailNode->m_BBMax.GetY()))
+				if (!(bb_max.GetY() < pRailNode->m_BBMin.GetY()))
+				{
+					// calculate the distance from the movement to the rail point 
+					Mth::Vector offset = pRailNode->m_pos - pos1;
+					Mth::Vector movement_direction = (pos2 - pos1).Normalize();
+					offset -= Mth::DotProduct(offset, movement_direction) * movement_direction;
+					float distance = offset.Length();
+					
+					if (distance > snap_dist) continue;
+					
+					// single node rails count as twice the distance when looking for the closest rail
+					if (closest_dist < 2.0f * distance) continue;
+					
+					closest_dist = 2.0f * distance;
+					closest_point = pRailNode->m_pos;
+					p_closest_rail = pRailNode;
+					found = true;
+				}
+			}
+		} // end if (active && etc)
+		
+		//pRailNode = pRailNode->m_pNext;
+	}				 
+
+	if (found)
+	{
+		// note, the line from pos1 to closest_point will not reflect the line segment found above
+		// as the line segment will actually start somewhere between pos1 and pos2, and not always on pos1
+//		if ( closest_dist > Script::GetFloat("Rail_Max_Snap"))
+//		{
+//			found = false;
+//		}
+//		else
+		{
+			*p_point = closest_point;
+			*pp_rail_node = p_closest_rail;
+		}
+	}
+	return found;	
+}
+
+// Added by Ken.
+// Returns true if the passed Node is on the same rail as the rail node.
+// Note, with the "merging" rails, where two nodes can link to
+// a new node, then this is not guarenteed to work
+//  
+// Given that the rails can now form a "loop with a tail"
+// we now detect loops by traversign the list with two pointers
+// one moving at half the speed of the other
+
+bool CRailNode::ProbablyOnSameRailAs(int SearchNode) const
+{		
+	
+
+	// First check if this node is the required node.
+	if (m_node==SearchNode)
+	{
+		// Well that was easy.
+		return true;
+	}	
+	
+	
+	// MICK:  Modified to return true only if on the same rail segment
+	
+	return false;
+
+
+	
+
+	const CRailNode *p_trailing = this;
+	bool		advance_trailing = false;
+	
+	// Scan forwards.
+	CRailNode *pNode=m_pNextLink;
+	while (pNode)
+	{
+		if (pNode->m_node==SearchNode)
+		{
+			// Found it.
+			return true;
+		}	
+		if (pNode == p_trailing)
+		{
+			// We've got back where we started without finding
+			// the node, so return false.
+			return false;
+		}	
+		pNode=pNode->m_pNextLink;
+		
+		// Advance the trailing node every other time
+		// we advance the search node
+		if (advance_trailing)
+		{
+			p_trailing = p_trailing->m_pNextLink;
+		}
+		advance_trailing = !advance_trailing;
+	}
+
+	p_trailing = this;
+	advance_trailing = false;
+		
+	// Didn't find anything that way, so now scan backwards.
+	pNode=m_pPrevLink;
+	while (pNode)
+	{
+		if (pNode->m_node==SearchNode)
+		{
+			// Found it.
+			return true;
+		}	
+		if (pNode == p_trailing)
+		{
+			// We've got back where we started without finding
+			// the node, so return false.
+			return false;
+		}	
+		pNode=pNode->m_pPrevLink;
+		// Advance the trailing node every other time
+		// we advance the search node
+		if (advance_trailing)
+		{
+			p_trailing = p_trailing->m_pPrevLink;
+		}
+		advance_trailing = !advance_trailing;
+
+	}
+	
+	return false;
+}
+
+
+// use in-game collision checks to determine if a rail is valid
+bool  Rail_ValidInEditor(Mth::Vector Start, Mth::Vector End)
+{
+
+// MAYBE TODO:  a rail should only need disqualifying if it is along the edge of a cell
+// (or even more accurately, a meta-piece
+// so could maybe check for that before we try to disqualify it. 
+	
+
+	if (!Ed::CParkEditor::Instance()->UsingCustomPark()) 		// is it a custom park???
+	{
+		#ifdef		DEBUG_EDITOR_RAILS
+		printf ("not in editor\n");
+		#endif
+		// if not a custom park, then everything is valid.
+		return true;
+	}
+
+	CFeeler		feeler;
+
+// Find a point "Mid", which is 1/4 of the way along the rail   
+	Mth::Vector	Mid = (Start + End) / 2.0f;			// Half way along
+	Mid = Start + ((Mid - Start) / 2.0f);		    // quarter of the way along	
+	Mid[Y] += 6.0f;									// raised up a bit
+
+// Find a vector "Side" which is horizontal, perpendicular to the rail, and 4 inches long	
+	Mth::Vector	Side = End - Start;	   			// vector along the rail
+	Side[Y] = 0.0f;								// horizontal
+	Side.Normalize(7.0f);						// this is half the width of the thickest rail.  Was 4.0, changed for Tokyo mega funbox
+	float temp = Side[X];			 				// make perpendicular to rail
+	Side[X] = Side[Z];
+	Side[Z] = -temp;
+
+												   
+	
+// if collision above me, left to right, then invalid
+	feeler.SetLine(Mid + Side, Mid-Side);	
+	if (feeler.GetCollision())
+	{
+		#ifdef		DEBUG_EDITOR_RAILS
+		printf ("l-r above collision, invalid\n");
+		feeler.DebugLine(255,0,0);
+		#endif
+		return false;
+	}
+	else
+	{
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine();
+		#endif
+	}
+
+// if collision above me, right to left, then invalid 	
+	feeler.FlipDirection();
+	if (feeler.GetCollision())
+	{
+		#ifdef		DEBUG_EDITOR_RAILS
+		printf ("r-l above collision, invalid\n");
+		feeler.DebugLine(255,0,0);
+		#endif
+		return false;
+	}
+	else
+	{
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine();
+		#endif
+	}
+
+#if 1
+
+// Get a vector "Down", which is a line straight down 7 inches
+	Mth::Vector	DeepBelow(0.0f,-12.0f,0.0f);	 		// six inches below the rail
+	
+	float	left_height 		= 0.0f;
+	float 	right_height 		= 0.0f;
+
+// find reltive height of slope (to rail) on left side
+	feeler.SetLine(Mid + Side, Mid + Side + DeepBelow);				  
+	if (feeler.GetCollision())
+	{
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine(0,255,0);	   	// green = possible bad one side
+		#endif
+		left_height = feeler.GetPoint().GetY() - (Mid[Y] -6.0f);
+	}
+	else
+	{
+		return true;
+	}
+
+	feeler.SetLine(Mid - Side, Mid - Side + DeepBelow);				  
+	if (feeler.GetCollision())
+	{
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine(0,255,0);	   	// green = possible bad one side
+		#endif
+		right_height = feeler.GetPoint().GetY() - (Mid[Y]-6.0f);
+	}
+	else
+	{
+		return true;
+	}
+	
+	
+//	printf("Left = %f, Right = %f\n",left_height, right_height);
+	
+	if (left_height > -1.0f && right_height > -1.0f)
+	{
+		// both faces tilt upwards, or are roughtly horizontal
+		// so return false
+		return false;
+	}
+
+	// Make it so left is higher than right for subsequent tests	
+	if (left_height < right_height)
+	{
+		float t = left_height;
+		left_height = right_height;
+		right_height = t;
+	}
+	
+	// check for steep left side
+	if (left_height > 1.0f)
+	{
+		// sloped down right side, so return
+		if (right_height > -2.0f)
+		{
+//			printf ("Sloped, false\n");
+			return false;
+		}
+	}
+
+
+#else
+
+// Get a vector "Down", which is a line straight down 7 inches
+	Mth::Vector	DownBelow(0.0f,-7.0f,0.0f);	 		// inch below the rail
+	Mth::Vector	DownAbove(0.0f,-4.0f,0.0f);			// 2 inch above the rail
+
+
+	// we do fource collision checks, two on each side of the rail
+	// one to a height that goes below the rail
+	// and the other that goes to a height above it
+	// if two or more of these return a collision
+	// then the rail is invalid
+	// the majority of bad rails will be eliminated with two checks
+
+	int cols = 0;
+	feeler.SetLine(Mid + Side, Mid + Side + DownBelow);				  
+	if (feeler.GetCollision())
+	{
+		cols++;
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine(0,255,0);	   	// green = possible bad one side
+		printf ("first side collision (greeen), checking other side.....\n");
+		#endif
+	}
+	
+	feeler.SetLine(Mid-Side, Mid-Side+DownBelow);				  
+	if (feeler.GetCollision())
+	{
+		cols++;
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine(0,255,0);	   	// green = possible bad one side
+		printf ("second side collision (greeen), checking other side.....\n");
+		#endif
+		if (cols > 1)
+		{
+			return false;
+		}	
+	}
+	
+	feeler.SetLine(Mid+Side, Mid+Side+DownAbove);				  
+	if (feeler.GetCollision())
+	{
+		cols++;
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine(0,255,0);	   	// green = possible bad one side
+		printf ("third side collision (greeen), checking other side.....\n");
+		#endif
+		if (cols > 1)
+		{
+			return false;
+		}	
+	}
+	
+	feeler.SetLine(Mid-Side, Mid-Side+DownAbove);				  
+	if (feeler.GetCollision())
+	{
+		cols++;
+		#ifdef		DEBUG_EDITOR_RAILS
+		feeler.DebugLine(0,255,0);	   	// green = possible bad one side
+		printf ("forth side collision (greeen)\n");
+		#endif
+		if (cols > 1)
+		{
+			return false;
+		}	
+	}
+#endif			
+		
+// Not found two collisions
+// so we can return true, indicating this rail is valid in the park editor
+//	printf ("everything ok, valid\n");
+	return true;
+}
+
+
+
+void	CRailManager::DebugRender(Mth::Matrix *p_transform)
+{
+#ifdef	__DEBUG_CODE__
+	
+	#ifdef	__PLAT_NGPS__	
+	
+	NxPs2::DMAOverflowOK = 2;
+						   
+	
+	Mth::Vector	cam_fwd;
+	
+	if (Nx::CViewportManager::sGetActiveCamera())
+	{
+		cam_fwd = Nx::CViewportManager::sGetActiveCamera()->GetMatrix()[Z];
+	}
+	else
+	{
+		printf("WARNING: called CRailManager::DebugRender without a camera\n");
+		return;
+	}
+
+	
+	
+	bool	draw_arrows = Script::GetInt( CRCD(0xc57f95d7,"rail_arrows"), false );
+
+	Mth::Vector up(0.0f,1.0f,0.0f);	
+
+	uint32	rgb = 0x0000ff;
+	
+	uint32 current = 12345678;
+
+	
+	
+//	CRailNode *pRailNode;
+//	pRailNode = mp_first_node;
+	int count = 0;
+	for (int check_node = 0;check_nodem_pNextLink;
+		
+		int terrain = pRailNode->GetTerrain();
+			
+		if (pNext)
+		{
+		
+			switch (terrain)
+			{
+			
+				case vTERRAIN_CONCSMOOTH:
+				case vTERRAIN_CONCROUGH:	
+					rgb = 0x0000ff;			// red = concrete
+					break;
+				case vTERRAIN_METALSMOOTH:
+				case vTERRAIN_METALROUGH:	
+				case vTERRAIN_METALCORRUGATED:	
+				case vTERRAIN_METALGRATING:
+				case vTERRAIN_METALTIN: 
+					rgb = 0xff0000;			// blue = metal
+					break;
+				 
+				case vTERRAIN_WOOD:		
+				case vTERRAIN_WOODMASONITE:
+				case vTERRAIN_WOODPLYWOOD:
+				case vTERRAIN_WOODFLIMSY:	
+				case vTERRAIN_WOODSHINGLE:
+				case vTERRAIN_WOODPIER:	
+					rgb = 0x00ff00;			// green = wood
+					break;
+				default:
+					rgb = 0xffffff;			// white = everything else 
+					break;
+			}
+			
+			if (pRailNode->m_flags.Test(LADDER))
+			{
+				rgb = 0xffff00;
+			}
+		
+
+			// check for context changes
+			if (current != rgb)
+			{
+				if ( current != 12345678)
+				{
+					NxPs2::EndLines3D();
+				}
+				
+				current = rgb;
+				NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));
+			}
+		
+			Mth::Vector v0, v1;
+			v0 = pRailNode->m_pos/* + up*/;	  	// the +up is the lift them off the surface, so they render better
+			v1 = pNext->m_pos/* + up*/;
+			v0[W] = 1.0f;		// Make homgeneous
+			v1[W] = 1.0f;
+
+			if (p_transform)
+			{
+				v0 = p_transform->Transform(v0);
+				v1 = p_transform->Transform(v1);
+			}
+
+			if (pRailNode->GetTerrain() || ((Tmr::GetTime() % 100) > 50))
+			{
+				NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
+				NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
+			
+				if (draw_arrows)
+				{
+					// Calculate and draw an arrowhead 
+					// 1/4th the length, at ~30 degrees					   
+					Mth::Vector	a = v0;
+					Mth::Vector b = v1;
+					Mth::Vector	ab = (b-a);
+					ab /= 4.0f;
+					Mth::Vector out;
+					out = ab;
+					out.Normalize();
+					out = Mth::CrossProduct(out, cam_fwd);
+					out *= ab.Length()/3.0f;			
+					
+					Mth::Vector left =  b - ab + out;
+					Mth::Vector right = b - ab - out;
+		
+					NxPs2::DrawLine3D(left[X],left[Y],left[Z],b[X],b[Y],b[Z]);
+					NxPs2::DrawLine3D(right[X],right[Y],right[Z],b[X],b[Y],b[Z]);
+					NxPs2::DrawLine3D(right[X],right[Y],right[Z],left[X],left[Y],left[Z]);	 // crossbar
+					// have to draw it twice for some stupid reason	(final segment of a particular color is not rendered)
+					NxPs2::DrawLine3D(left[X],left[Y],left[Z],b[X],b[Y],b[Z]);
+					NxPs2::DrawLine3D(right[X],right[Y],right[Z],b[X],b[Y],b[Z]);
+					NxPs2::DrawLine3D(right[X],right[Y],right[Z],left[X],left[Y],left[Z]);
+				}		
+			
+			
+			
+			}
+		}
+		
+//		pRailNode = pRailNode->m_pNext;
+		
+	}
+	
+
+	// then draw the node positions as litle red lines	
+//	pRailNode = mp_first_node;
+	count = 0;
+	for (int check_node = 0;check_nodeGetActive())
+		{
+			rgb = 0xff0000;		 		// blue for active
+		}
+		else
+		{
+			rgb = 0x00ff00;				// green for inactive
+		}
+		
+		if (!pRailNode->GetPrevLink())
+		{
+			rgb |= 0x0000ff;			// blue+red = cyan = no prev node (or green+red = yellow for no prev inactive)
+		}
+	
+		// check for context changes
+		if (current != rgb)
+		{
+			if ( current != 12345678)
+			{
+				NxPs2::EndLines3D();
+			}
+			
+			current = rgb;
+			NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));
+		}
+	
+		Mth::Vector v0, v1;
+		v0 = pRailNode->m_pos;
+		v1 = v0 + up * 24.0f;
+		v0[W] = 1.0f;		// Make homgeneous
+		v1[W] = 1.0f;
+
+		if (p_transform)
+		{
+			v0 = p_transform->Transform(v0);
+			v1 = p_transform->Transform(v1);
+		}
+
+		if (pRailNode->GetTerrain() || ((Tmr::GetTime() % 100) > 50))
+		{
+			NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
+			NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
+		}
+//		pRailNode = pRailNode->m_pNext;
+		
+	}
+
+	
+	
+	// only if we actually drew some
+	if ( current != 12345678)
+	{
+		NxPs2::EndLines3D();
+	}
+
+
+	#endif
+	#endif
+
+}
+
+
+
+
+void	CRailManager::AddRailsFromNodeArray(Script::CArray * p_nodearray)
+{
+	Dbg_MsgAssert(!mp_node_array,("Setting two node arrays in rail manager"));
+	mp_node_array = p_nodearray;
+	
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+
+	
+	uint32 i;	
+	
+
+	Dbg_MsgAssert(m_num_nodes == 0,("Can only addd nodes once, already %d there\n",m_num_nodes));										  
+	m_num_nodes = 0;										  
+										  
+	// First iterate over the node array, and count the number of nodes needed
+
+
+	for (i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *p_node_struct=p_nodearray->GetStructure(i);
+		Dbg_MsgAssert(p_node_struct,("Error getting node from node array for rail generation"));
+		if ( !skate_mod->ShouldBeAbsentNode( p_node_struct ) )
+		{
+			uint32 class_checksum = 0;		
+			p_node_struct->GetChecksum( 0x12b4e660 /*"Class"*/, &class_checksum );
+			if (class_checksum == CRCD(0x8e6b02ad, "railnode") || class_checksum == CRCD(0x30c19600, "ClimbingNode"))
+			{
+				m_num_nodes++;										  
+			}
+			
+			// if (class_checksum == CRCD(0x16d1e502, "climbnode"))
+			// {
+				// printf("Found climb node!\n");
+			// }
+		}
+	}
+
+
+	// No nodes found, so just return
+	if (!m_num_nodes)
+	{
+		return;
+	}
+	
+	mp_nodes = (CRailNode*)Mem::Malloc(m_num_nodes * sizeof(CRailNode));
+	mp_links = (CRailLinks*)Mem::Malloc(m_num_nodes * sizeof(CRailLinks));
+
+	// iterate over the nodes and add them to the array	
+	m_current_node = 0;	
+	
+	 
+	for (i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *p_node_struct=p_nodearray->GetStructure(i);
+		Dbg_MsgAssert(p_node_struct,("Error getting node from node array for rail generation"));
+
+		if ( !skate_mod->ShouldBeAbsentNode( p_node_struct ) )
+		{
+	
+	
+			uint32 class_checksum = 0;		
+			p_node_struct->GetChecksum( 0x12b4e660 /*"Class"*/, &class_checksum );
+			if (class_checksum == CRCD(0x8e6b02ad, "railnode") || class_checksum == CRCD(0x30c19600, "ClimbingNode"))
+			{
+				
+	
+				AddRailNode( i, p_node_struct);
+	
+				bool is_trick_object = p_node_struct->ContainsComponentNamed( "TrickObject" );
+	
+	#if 1
+				bool debug_graffiti = Script::GetInt( "create_all_trick_objects", false );
+				if ( debug_graffiti )
+					is_trick_object = true;
+	#endif
+	
+				if ( is_trick_object )
+				{
+					// get the node name
+					uint32 checksumName;
+					p_node_struct->GetChecksum( "name", &checksumName, true );
+	
+					// must have a cluster associated with it
+					uint32 clusterName = checksumName;
+	#if 0
+					// automatically link all rail nodes to the TestCluster for now
+					clusterName = Script::GenerateCRC("TestCluster");
+	#else
+					p_node_struct->GetChecksum( "Cluster", &clusterName, true );
+	#endif
+					skate_mod->GetTrickObjectManager()->AddTrickCluster( clusterName );
+	
+					// bind the name of the rail node to the cluster name
+					skate_mod->GetTrickObjectManager()->AddTrickAlias( checksumName, clusterName );
+				}
+			}
+		}
+	}
+
+
+	
+	int num_nodes  = p_nodearray->GetSize();   
+	
+	// we are creating a table of all nodes, and the pointer to the CRailNode
+	// for that node, so we can do a reverse lookup
+	
+	CRailNode **pp_railnodes = (CRailNode **) Mem::Malloc(num_nodes * sizeof(CRailNode*));
+	for (int i=0;iGetNode()] = p_node;
+//		p_node = p_node->m_pNext;
+	}
+
+	// now go through all the node	
+//	p_node = mp_first_node;
+	for (int node=0;nodem_node, mp_links[node].m_link[i], num_nodes));
+				Dbg_MsgAssert(pp_railnodes[mp_links[node].m_link[i]], ("RailNode %d linked to something (node %d) that is not a RailNode",
+												p_node->m_node, mp_links[node].m_link[i]));
+				NewLink(p_node,pp_railnodes[mp_links[node].m_link[i]]);
+			}
+		}
+//		p_node = p_node->m_pNext;
+	}
+	Mem::Free(pp_railnodes);
+	
+	Mem::Free(mp_links);
+	mp_links = NULL;
+	
+	//printf ("THERE ARE %d rails\n",m_num_nodes);
+	
+}
+
+Mth::Vector CRailManager::LocalToWorldTransform ( const Mth::Vector& vector ) const
+{
+	if (!m_is_transformed)
+	{
+		return vector;
+	}
+	else
+	{
+		return m_transform.Transform(vector);
+	}
+}
+
+Mth::Vector	CRailManager::GetPos(const CRailNode *p_node) 	const
+{
+	Dbg_Assert(p_node);
+	
+	if (!m_is_transformed)
+	{
+		return p_node->GetPos();
+	}
+	else
+	{
+		Mth::Vector v = m_transform.Transform(p_node->GetPos());
+		return v;
+	}
+}
+
+Mth::Matrix	CRailManager::GetMatrix(const CRailNode *p_node) 	const
+{
+	Dbg_Assert(p_node);
+	
+	if (!m_is_transformed)
+	{
+		return Mth::Matrix(p_node->GetOrientation());
+	}
+	else
+	{
+		Mth::Matrix m = m_transform * Mth::Matrix(p_node->GetOrientation());
+		return m;
+	}
+}
+
+
+Mth::Vector	CRailManager::GetBBMin(const CRailNode *p_node) const
+{
+	if (!m_is_transformed)
+	{
+		return p_node->GetBBMin();
+	}
+	else
+	{
+		Mth::Vector v = m_transform.Transform(p_node->GetBBMin());
+		return v;
+	}
+}
+
+Mth::Vector	CRailManager::GetBBMax(const CRailNode *p_node) const
+{
+	if (!m_is_transformed)
+	{
+		return p_node->GetBBMax();
+	}
+	else
+	{
+		Mth::Vector v = m_transform.Transform(p_node->GetBBMax());
+		return v;
+	}
+}
+
+// Auto-generation of rails
+
+// a binning data structure similar to a hashtable which will allow us to quickly find nearby rail endpoints
+// differs from the standard hashtable in that we will be able to add an element multiple times
+// its algorithm is just as inefficient as the standard hashtable
+
+struct SAutoRail;
+class BinTable;
+
+class BinItem
+{
+	friend class BinTable;
+
+private:
+	BinItem (   )
+		: mp_value(NULL), mp_next(NULL)
+		{   }
+
+	SAutoRail* mp_value;
+	BinItem* mp_next;
+};
+
+class BinTable
+{
+public:
+	BinTable ( uint32 numBits );
+	~BinTable (   );
+
+	bool PutItem ( const uint32 key, SAutoRail* item );
+	bool PutItemTwice ( const uint32 first_key, const uint32 second_key, SAutoRail* item );
+	SAutoRail* GetFirstItem ( const uint32 key );
+	SAutoRail* GetNextItem ( const uint32 key, SAutoRail* item );
+	void FlushAllItems (   );
+
+	int GetSize (   ) { return m_size; }
+
+protected:
+	uint32 m_numBits;
+	BinItem* mp_table;
+	int m_size;
+
+private:
+	uint32 key_to_bin ( const uint32 key )
+		{ return key & ((1 << m_numBits) - 1); }
+};
+
+BinTable::BinTable ( uint32 numBits )
+	: m_numBits(numBits), mp_table(new BinItem[1 << numBits]), m_size(0)
+	{   }
+
+BinTable::~BinTable (   )
+{
+	Dbg_AssertPtr(mp_table);
+
+	FlushAllItems();
+
+	delete [] mp_table;
+	mp_table = NULL;
+}
+
+bool BinTable::PutItem ( const uint32 key, SAutoRail* item )
+{    
+    Dbg_AssertPtr(item);
+
+	Dbg_AssertPtr(mp_table);
+
+	Dbg_MsgAssert(key || item, ("Both key and item are 0 (NULL) in bin table"));
+	
+	Dbg_MsgAssert(item, ("NULL item added to bin table"));
+
+	BinItem *pEntry = &mp_table[key_to_bin(key)];
+	if (pEntry->mp_value)
+	{
+#ifndef __PLAT_WN32__
+		BinItem *pNew = new (Mem::PoolManager::SCreateItem(Mem::PoolManager::vHASH_ITEM_POOL)) BinItem();
+#else
+		BinItem *pNew = new BinItem;
+#endif
+		pNew->mp_value = item;
+		pNew->mp_next = pEntry->mp_next;
+		pEntry->mp_next = pNew;
+	}
+	else
+	{
+		pEntry->mp_value = item;
+	}
+
+	m_size++;	
+	return true;
+}
+
+// this feature is needed to insure a rail is never added twice to the same bin; if it were, it would become
+// impossible to traverse the bin
+bool BinTable::PutItemTwice ( const uint32 first_key, const uint32 second_key, SAutoRail* item )
+{
+	PutItem(first_key, item);
+	if (key_to_bin(first_key) != key_to_bin(second_key))
+		PutItem(second_key, item);
+	return true;
+}
+
+SAutoRail* BinTable::GetFirstItem ( const uint32 key )
+{
+    Dbg_AssertPtr(mp_table);
+
+	BinItem* pEntry = &mp_table[key_to_bin(key)];
+
+	while (pEntry)
+	{
+		if (pEntry->mp_value)
+		{
+			return pEntry->mp_value;
+		}	
+		pEntry = pEntry->mp_next;
+	}
+
+	return NULL;
+}
+
+SAutoRail* BinTable::GetNextItem ( const uint32 key, SAutoRail* p_item )
+{
+    Dbg_AssertPtr(mp_table);
+
+	BinItem* pEntry = &mp_table[key_to_bin(key)];
+
+	while (pEntry)
+	{
+		if (pEntry->mp_value == p_item)
+		{
+			break;
+		}	
+		pEntry = pEntry->mp_next;
+	}
+	if (!pEntry)
+	{
+		return NULL;
+	}	
+
+	pEntry = pEntry->mp_next;
+	while (pEntry)
+	{
+		if (pEntry->mp_value)
+		{
+			return pEntry->mp_value;
+		}	
+		pEntry = pEntry->mp_next;
+	}
+
+	return NULL;
+}
+
+void BinTable::FlushAllItems (   )
+{
+	Dbg_AssertPtr(mp_table);
+	if (!mp_table)
+		return;
+
+	BinItem* pMainEntry = mp_table;
+	uint32 hashTableSize = 1 << m_numBits;
+	for (uint32 i = 0; i < hashTableSize; ++i)
+	{
+		BinItem* pLinkedEntry = pMainEntry->mp_next;
+		while (pLinkedEntry)
+		{
+			BinItem* pNext = pLinkedEntry->mp_next;
+#ifndef __PLAT_WN32__
+			Mem::PoolManager::SFreeItem(Mem::PoolManager::vHASH_ITEM_POOL, pLinkedEntry);
+#else
+			delete pLinkedEntry;
+#endif
+			pLinkedEntry = pNext;
+		}
+		++pMainEntry;
+	}	
+	m_size = 0;
+}
+
+// memory managment defines; control temporary memory usage
+
+// maximum number of edges in a sector
+#define MAX_NUM_EDGES					(5000)
+// maximum number of rails
+#define	MAX_NUM_RAILS					(30000)
+// maximum number of railsets
+#define MAX_NUM_RAILSETS 				(6000)
+// size of the rail endpoint bintable
+#define ENDPOINT_BINTABLE_SIZE_BITS		(12) // 4096 entries
+
+// default rail generation parameter values
+// when changing these, don't forget to CHANGE THE DOCUMENTATION of the default script parameter values in cfuncs.cpp
+
+// angle below which edges do not generate rails
+#define MIN_RAIL_EDGE_ANGLE				(30.0f)
+// maximum rail assent
+// NOTE: can't sync with max skate slop because not a explicitly defined physics value (I think)
+#define MAX_RAIL_ANGLE_OF_ASSENT		(45.0f)
+// minimum length of a independent rail or a railset
+#define MIN_RAILSET_LENGTH				(36.0f)
+// minimum length of a rail, whether in a railset or not
+#define MIN_EDGE_LENGTH					(0.0f)
+// distance increment along rails at which collision detection is done
+#define FEELER_INCREMENT				(36.0f)
+// maximum heigh of a curb before it will get a rail 
+// NOTE: sync with low-curb skate height from physics engine? i think that's 8.0f but I don't really know what I'm talking about
+#define MAX_LOW_CURB_HEIGHT				(8.0f)
+// angle of assent above face of low-curb feelers
+#define LOW_CURB_FEELER_ANGLE_OF_ASSENT	(30.0f)
+// maximum angle between two connected rails in a railset
+#define MAX_CORNER_IN_RAILSET			(50.0f)
+// height of vertical feeler
+#define VERTICAL_FEELER_LENGTH			(5.0f * 12.0f)
+// distance above rail at which crossbar feeler is used
+#define CROSSBAR_FEELER_ELEVATION		(12.0f)
+// half length of crossbar feeler
+#define CROSSBAR_FEELER_LENGTH			(1.0f * 12.0f)
+// farthest distance between an auto-generated rail and an old rail for which the auto-generated rail will be culled
+#define FARTHEST_DEGENERATE_RAIL		(12.0f)
+// angle between an old and and auto-generated rail below which they may be considered degenerate
+#define MAX_DEGENERATE_RAIL_ANGLE		(20.0f)
+// distance below which verticies are concidered equal
+#define CONNECTION_SLOP					(0.1f)
+
+
+// feeler usage bits
+#define VERTICAL_FEELER_BIT				(1 << 0)
+#define CROSSBAR_FEELER_BIT				(1 << 1)
+#define LOW_CURB_FEELER_BIT				(1 << 2)
+#define ALL_FEELERS_ON					(-1)
+
+struct SEdge {
+	// has a match for this edge been found?
+	bool matched;
+
+	// edge is from a vert poly; maybe I should just save all the face flag bits
+	bool vert;
+
+	// edge endpoints
+	Mth::Vector p0, p1;
+
+	// edge's face's unit normal
+	Mth::Vector	normal;
+
+	// edge's unit perp vector along face
+	Mth::Vector perp;
+};
+
+struct SAutoRailEndpoint {
+	// endpoint position
+	Mth::Vector p;
+
+	// connection at this end; -1 is no connection
+	int connection;
+};
+
+struct SAutoRail {
+	// rail endpoints
+	SAutoRailEndpoint endpoints[2];
+
+	// unit vector along rail
+	Mth::Vector	para;
+
+	// railset id; -1 for solo rail
+	int railset;
+
+	// rail length
+	float length;
+
+	// if found to be too sort or once added to the rail nodes, a rail is disabled
+	bool disabled;
+};
+
+// dereference endpoints
+enum { START = 0, END };
+
+struct SRailSet {
+	// length of rail set
+	float length;
+};
+
+// collect the autorail state into a single structure to ease transportation
+struct SAutoRailGeneratorState {
+	SEdge* p_edges;
+	SAutoRail* p_rails;
+	SRailSet* p_railsets;
+
+	// hash of rail endpoints; most rails are in hash twice, keyed off endpoint's X*Y rounded to nearest inch
+	BinTable* p_rail_endpoint_list;
+
+	int	num_rails;	
+	int num_active_rails;
+	int num_railsets;
+};
+
+// collect all the algorithm's input parameters into a structure
+// see default values for explaination of meanings
+struct SAutoRailGeneratorParameters {
+	// input parameters
+	float min_rail_edge_angle;
+	float max_rail_angle_of_assent;
+	float min_railset_length;
+	float min_edge_length;
+	float feeler_increment;
+	float max_low_curb_height;
+	float curb_feeler_angle_of_assent;
+	float max_corner_in_railset;
+	float vertical_feeler_length;
+	float crossbar_feeler_elevation;
+	float half_crossbar_feeler_length;
+	float farthest_degenerate_rail;
+	float max_degenerate_rail_angle;
+	float connection_slop;
+	int feeler_usage;
+
+	// dependent parameters
+	float sin_max_rail_angle_of_assent;
+	float low_curb_feeler_length;
+	float cos_min_rail_edge_angle;
+	float cos_max_corner_in_railset;
+	float sin_curb_feeler_angle_of_assent;
+	float cos_curb_feeler_angle_of_assent;
+	float cos_max_degenerate_rail_angle;
+
+	// additional flags
+	bool remove_old_rails;
+};
+
+inline bool very_close ( Mth::Vector a, Mth::Vector b, const SAutoRailGeneratorParameters& arp )
+{
+	return(Mth::Abs(a[X]-b[X]) < arp.connection_slop	
+		&& Mth::Abs(a[Y]-b[Y]) < arp.connection_slop
+		&& Mth::Abs(a[Z]-b[Z]) < arp.connection_slop
+	);
+}
+
+inline int generate_endpoint_key ( Mth::Vector& end_point )
+{
+	return static_cast((end_point[X] * end_point[Z]) * (1.0f / 6.0f));
+}
+
+// checks for duplicate rails and then adds rails to rail array and endpoint bintable
+inline bool add_rail ( const Mth::Vector& pa, const Mth::Vector& pb, SAutoRailGeneratorState& arg, const SAutoRailGeneratorParameters& arp )
+{
+	// cull out duplicate rails
+	// NOTE: what is causing these? some levels do not have them; perhaps sloppy geometry
+	for (int n = arg.num_rails; n--; )
+	{
+		// if this is a duplicate, bail
+		if (very_close(arg.p_rails[n].endpoints[START].p, pa, arp) && very_close(arg.p_rails[n].endpoints[END].p, pb, arp)
+			|| very_close(arg.p_rails[n].endpoints[START].p, pb, arp) && very_close(arg.p_rails[n].endpoints[END].p, pa, arp))
+		{
+			return true;
+		}
+	}
+
+	// if we get to here, we've decided this is a valid rail
+	
+	if (arg.num_rails + 1 == MAX_NUM_RAILS) return false;
+
+	// add rail to rail array
+
+	arg.p_rails[arg.num_rails].endpoints[START].p = pa;
+	arg.p_rails[arg.num_rails].endpoints[END].p = pb;
+	arg.p_rails[arg.num_rails].endpoints[START].connection = -1;
+	arg.p_rails[arg.num_rails].endpoints[END].connection = -1;
+	arg.p_rails[arg.num_rails].railset = -1;
+	arg.p_rails[arg.num_rails].disabled = false;
+
+	arg.p_rails[arg.num_rails].para = pb - pa;
+	arg.p_rails[arg.num_rails].length = arg.p_rails[arg.num_rails].para.Length();
+	arg.p_rails[arg.num_rails].para.Normalize();
+
+	// add rail to endpoint bintable
+	arg.p_rail_endpoint_list->PutItemTwice(
+		generate_endpoint_key(arg.p_rails[arg.num_rails].endpoints[START].p),
+		generate_endpoint_key(arg.p_rails[arg.num_rails].endpoints[END].p),
+		&arg.p_rails[arg.num_rails]
+	);
+
+	arg.num_rails++;
+	
+	return true;
+}
+
+// handles nearby collision detection
+// use feelers to insure that this is a good rail; if the full rail doesn't work, attempt to snip it up into smaller
+// valid rails
+inline bool consider_rail ( const Mth::Vector& pa, const Mth::Vector& pb, int matched_edge, const Mth::Vector& para, const Mth::Vector& normal,
+	const Mth::Vector& perp, float edge_length, SAutoRailGeneratorState& arg, SAutoRailGeneratorParameters& arp )
+{
+	// four feelers are used at each step along the edge
+	// 1, 2) short feelers up from the edge's two faces
+	// 3) longer feeler straight up from the edge
+	// 4) medium feeler crossbar on feeler 3
+
+	// there are full_step_length + 2 feeler points along an edge
+	// 0 corresponds the the edge's start point
+	// n corresponds to a points n steps in from the start point
+	// full_step_length + 1 corresponds to the edge's end point
+	int full_step_length = static_cast(edge_length / arp.feeler_increment);
+
+	int start_step = 0;
+	Mth::Vector r0 = pa;
+	// attempt to create rail snippets until we've used up the rail
+	do
+	{
+		// start a new rail snippet
+		int end_step = start_step;
+		Mth::Vector r1 = r0;
+
+		// attempt to extent the rail snippet along the edge
+		do
+		{
+			bool fail = false;
+
+			// check for nearby collidables
+
+			CFeeler feeler;
+            
+			// first side feeler
+			// angled up from face in perp-normal plane starting a fraction of an inch from edge
+			if ((arp.feeler_usage & LOW_CURB_FEELER_BIT))
+			{
+				Mth::Vector feeler_direction = arp.sin_curb_feeler_angle_of_assent * normal + arp.cos_curb_feeler_angle_of_assent * perp;
+
+				feeler.m_start = r1 + 0.1f * feeler_direction;
+				feeler.m_end = r1 + arp.low_curb_feeler_length * feeler_direction;
+
+				fail = feeler.GetCollision(false);
+			}
+
+			// second side feeler
+			// angled up from face in perp-normal plane starting a fraction of an inch from edge
+			if (!fail && (arp.feeler_usage & LOW_CURB_FEELER_BIT))
+			{
+				Mth::Vector feeler_direction = arp.sin_curb_feeler_angle_of_assent * arg.p_edges[matched_edge].normal
+					+ arp.cos_curb_feeler_angle_of_assent * arg.p_edges[matched_edge].perp;
+
+				feeler.m_start = r1 + 0.2f * feeler_direction;
+				feeler.m_end = r1 + arp.low_curb_feeler_length * feeler_direction;
+
+				fail = feeler.GetCollision(false);
+			}
+
+			// vertical feeler
+			// straight up starting a fraction of an inch above the edge
+			if (!fail && (arp.feeler_usage & VERTICAL_FEELER_BIT))
+			{
+				feeler.m_start = r1;
+				feeler.m_start[Y] += 0.2f;
+
+				feeler.m_end = r1;
+				feeler.m_end[Y] += arp.vertical_feeler_length;
+			}
+
+			// crossbar feeler
+			// some height above rail, flush with XZ plane, perp to edge
+			if (!fail && (arp.feeler_usage & CROSSBAR_FEELER_BIT))
+			{
+				Mth::Vector bar;
+
+				bar[X] = para[Z];
+				bar[Y] = 0.0f;
+				bar[Z] = -para[X];
+				bar.Normalize();
+
+				feeler.m_start = r1;
+				feeler.m_start[Y] += arp.crossbar_feeler_elevation;
+				feeler.m_start += arp.half_crossbar_feeler_length * bar;
+
+				feeler.m_end = r1;
+				feeler.m_end[Y] += arp.crossbar_feeler_elevation;
+				feeler.m_end += -arp.half_crossbar_feeler_length * bar;
+
+				fail = feeler.GetCollision(false);
+			}
+
+			// if there is collidables, the extention attempt failed
+			if (fail) break;
+
+			// there are no nearby collidables
+			// so we'll attempt to stretch the snippet one step or to the end of the edge
+			end_step++;
+
+			// if we're beyond the end of the edge, we're done
+			if (end_step == full_step_length + 2) break;
+
+			if (end_step == full_step_length + 1)
+			{
+				r1 = pb;
+			}
+			else
+			{
+				r1 = pa + end_step * arp.feeler_increment * para;
+			}
+		} while (true);
+
+		// restore r1 to the last value it had before the failed extention
+		end_step--;
+		if (end_step == full_step_length + 1)
+			r1 = pb;
+		else
+			r1 = pa + end_step * arp.feeler_increment * para;
+
+		// the snippet is now as long as it can be
+
+		// if the snippet is long enough
+		if (end_step - start_step > 0 && (r0 - r1).LengthSqr() >= (arp.min_edge_length * arp.min_edge_length))
+		{
+			// use it
+			if (!add_rail(r0, r1, arg, arp)) return false;
+		}
+
+		// jump to next start point
+		start_step = end_step + 2;
+		r0 = pa + start_step * arp.feeler_increment * para;
+
+	} while (start_step < full_step_length + 1);
+
+	return true;
+}
+
+CRailNode* CRailManager::GetRailNodeByNodeNumber( int node_num )
+{
+	int i;
+
+	if( mp_nodes )
+	{
+		for( i = 0; i < m_num_nodes; i++ )
+		{
+			if( mp_nodes[i].GetNode() == node_num )
+			{
+				return &mp_nodes[i];
+			}
+		}
+	}
+
+	return NULL;
+}
+
+// knowns issues or ideas:
+// snap rail endpoints - do we want to snap the endpoints of railset together; if so, definitely make the slop parameter adjustable
+// vertical feeler - the vertical feeler is causing random snipping up of rails in odd places
+// don't destory old rails - instead, when considering a rail, check for nearly parallel rails and don't add a new rail if you find one; bin old rails first
+// cross-section edges finding - look for potential rails by joining up across sectors; time consuming; edge endpoints won't match, so we have to look for overlapping edges instead of just looking for matching endpoints
+void CRailManager::AutoGenerateRails ( Script::CStruct* pParams )
+{
+// 27K
+#ifdef	__DEBUG_CODE__
+	printf("-----------------------\n");
+
+	// let's use the Debug heap, since we might need a lot of memory
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+
+	// auto-generator's state
+	SAutoRailGeneratorState arg;
+
+	arg.num_rails = 0;	
+	arg.num_railsets = 0;
+	arg.num_active_rails = 0;
+
+	arg.p_edges = new SEdge[MAX_NUM_EDGES];
+	arg.p_rails = new SAutoRail[MAX_NUM_RAILS];
+	arg.p_railsets = new SRailSet[MAX_NUM_RAILSETS];
+
+	arg.p_rail_endpoint_list = new BinTable(ENDPOINT_BINTABLE_SIZE_BITS);
+
+	// auto-generator's modifiable parameters
+	SAutoRailGeneratorParameters arp;
+
+	// set parameters to defaults
+	arp.min_rail_edge_angle = MIN_RAIL_EDGE_ANGLE;
+	arp.max_rail_angle_of_assent = MAX_RAIL_ANGLE_OF_ASSENT;
+	arp.min_railset_length = MIN_RAILSET_LENGTH;
+	arp.min_edge_length = MIN_EDGE_LENGTH;
+	arp.feeler_increment = FEELER_INCREMENT;
+	arp.max_low_curb_height = MAX_LOW_CURB_HEIGHT;
+	arp.curb_feeler_angle_of_assent = LOW_CURB_FEELER_ANGLE_OF_ASSENT;
+	arp.max_corner_in_railset = MAX_CORNER_IN_RAILSET;
+	arp.vertical_feeler_length = VERTICAL_FEELER_LENGTH;
+	arp.crossbar_feeler_elevation = CROSSBAR_FEELER_ELEVATION;
+	arp.half_crossbar_feeler_length = CROSSBAR_FEELER_LENGTH;
+	arp.farthest_degenerate_rail = FARTHEST_DEGENERATE_RAIL;
+	arp.max_degenerate_rail_angle = MAX_DEGENERATE_RAIL_ANGLE;
+	arp.connection_slop = CONNECTION_SLOP;
+
+	// look for overrides in script parameters
+	pParams->GetFloat("min_rail_edge_angle", &arp.min_rail_edge_angle, Script::NO_ASSERT);
+	pParams->GetFloat("max_rail_angle_of_assent", &arp.max_rail_angle_of_assent, Script::NO_ASSERT);
+	pParams->GetFloat("min_railset_length", &arp.min_railset_length, Script::NO_ASSERT);
+	pParams->GetFloat("min_edge_length", &arp.min_edge_length, Script::NO_ASSERT);
+	pParams->GetFloat("feeler_increment", &arp.feeler_increment, Script::NO_ASSERT);
+	pParams->GetFloat("max_low_curb_height", &arp.max_low_curb_height, Script::NO_ASSERT);
+	pParams->GetFloat("curb_feeler_angle_of_assent", &arp.curb_feeler_angle_of_assent, Script::NO_ASSERT);
+	pParams->GetFloat("max_corner_in_railset", &arp.max_corner_in_railset, Script::NO_ASSERT);
+	pParams->GetFloat("vertical_feeler_length", &arp.vertical_feeler_length, Script::NO_ASSERT);
+	pParams->GetFloat("crossbar_feeler_elevation", &arp.crossbar_feeler_elevation, Script::NO_ASSERT);
+	pParams->GetFloat("crossbar_feeler_length", &arp.half_crossbar_feeler_length, Script::NO_ASSERT);
+	pParams->GetFloat("farthest_degenerate_rail", &arp.farthest_degenerate_rail, Script::NO_ASSERT);
+	pParams->GetFloat("max_degenerate_rail_angle", &arp.max_degenerate_rail_angle, Script::NO_ASSERT);
+	pParams->GetFloat("connection_slop", &arp.connection_slop, Script::NO_ASSERT);
+
+	arp.half_crossbar_feeler_length /= 2.0f;
+	arp.low_curb_feeler_length = arp.max_low_curb_height / cosf(DEGREES_TO_RADIANS(arp.curb_feeler_angle_of_assent));
+	arp.sin_max_rail_angle_of_assent = sinf(DEGREES_TO_RADIANS(arp.max_rail_angle_of_assent));
+	arp.cos_min_rail_edge_angle = cosf(DEGREES_TO_RADIANS(arp.min_rail_edge_angle));
+	arp.cos_max_corner_in_railset = cosf(DEGREES_TO_RADIANS(arp.max_corner_in_railset));
+	arp.sin_curb_feeler_angle_of_assent = sinf(DEGREES_TO_RADIANS(arp.curb_feeler_angle_of_assent));
+	arp.cos_curb_feeler_angle_of_assent = cosf(DEGREES_TO_RADIANS(arp.curb_feeler_angle_of_assent));
+	arp.cos_max_degenerate_rail_angle = cosf(DEGREES_TO_RADIANS(arp.max_degenerate_rail_angle));
+
+	// turn on all feelers by default
+	arp.feeler_usage = ALL_FEELERS_ON;
+
+	// check for overrides in script parameters
+	if (pParams->ContainsFlag("no_vertical_feeler"))
+		arp.feeler_usage &= ~VERTICAL_FEELER_BIT;
+	if (pParams->ContainsFlag("no_crossbar_feeler"))
+		arp.feeler_usage &= ~CROSSBAR_FEELER_BIT;
+	if (pParams->ContainsFlag("no_low_curb_feeler"))
+		arp.feeler_usage &= ~LOW_CURB_FEELER_BIT;
+	if (pParams->ContainsFlag("no_feelers"))
+		arp.feeler_usage = 0;
+
+	// check additional flags
+	arp.remove_old_rails = false;
+	if (pParams->ContainsFlag("overwrite"))
+	{
+		arp.remove_old_rails = true;
+	}
+
+	// dump parameter state
+
+	printf("min_rail_edge_angle = %f\n", arp.min_rail_edge_angle);
+	printf("max_rail_angle_of_assent = %f\n", arp.max_rail_angle_of_assent);
+	printf("min_railset_length = %f\n", arp.min_railset_length);
+	printf("min_edge_length = %f\n", arp.min_edge_length);
+	printf("connection_slop = %f\n", arp.connection_slop);
+	printf("feeler_increment = %f\n", arp.feeler_increment);
+	printf("max_low_curb_height = %f\n", arp.max_low_curb_height);
+	printf("curb_feeler_angle_of_assent = %f\n", arp.curb_feeler_angle_of_assent);
+	printf("max_corner_in_railset = %f\n", arp.max_corner_in_railset);
+	printf("vertical_feeler_length = %f\n", arp.vertical_feeler_length);
+	printf("crossbar_feeler_length = %f\n", 2.0f * arp.half_crossbar_feeler_length);
+	printf("crossbar_feeler_elevation = %f\n", arp.crossbar_feeler_elevation);
+
+	if (!arp.remove_old_rails)
+	{
+		printf("farthest_degenerate_rail = %f\n", arp.farthest_degenerate_rail);
+		printf("max_degenerate_rail_angle = %f\n", arp.max_degenerate_rail_angle);
+	}
+
+	if (!(arp.feeler_usage & VERTICAL_FEELER_BIT))
+		printf("vertical feeler deactivated\n");
+	if (!(arp.feeler_usage & CROSSBAR_FEELER_BIT))
+		printf("crossbar feeler deactivated\n");
+	if (!(arp.feeler_usage & LOW_CURB_FEELER_BIT))
+		printf("low-curb feeler deactivated\n");
+	if (arp.feeler_usage == ALL_FEELERS_ON)
+		printf("all feelers active\n");
+
+	if (arp.remove_old_rails)
+	{
+		printf("removing existing rails\n");
+	}
+	else
+	{
+		printf("retaining existing rails\n");
+	}
+
+	printf("generating rails...\n");
+
+	Nx::CScene *p_scene = Nx::CEngine::sGetMainScene();
+	Lst::HashTable* p_sector_list = p_scene->GetSectorList();
+	if (!p_sector_list)
+		return;
+
+	// loop through every sector in the scene
+	// NOTE: to do a better job, we may need to attempt to match edges between sectors
+	// if so, we'd have to deal with the issue of not being able to expect edge vertices to match up
+	p_sector_list->IterateStart();	
+	Nx::CSector *p_sector = p_sector_list->IterateNext();		
+	while(p_sector)
+	{
+		Nx::CCollObjTriData *p_coll_obj = p_sector->GetCollSector()->GetGeometry();
+		if (!p_coll_obj || !p_sector->IsActive() || !p_sector->IsCollidable())
+		{
+			p_sector = p_sector_list->IterateNext();		
+			continue;
+		}
+
+		// we have the collision data, now build a list of edges
+		// based solely on positions of verts
+
+		int	num_edges = 0;
+
+		Mth::Vector p[3];
+		
+		// loop though the faces of the sector
+		int num_faces = p_coll_obj->GetNumFaces();
+		for (int face = 0; face < num_faces; face++)
+		{
+			// get the normal													 
+			Mth::Vector normal = p_coll_obj->GetFaceNormal(face);
+			
+			// Get the three vertex indicies for this face
+			// and the vertex position into the above arrays
+			int v[3];
+            for (int i = 0; i < 3; i++)			
+			{
+				v[i] = p_coll_obj->GetFaceVertIndex(face,i);
+				p[i] = p_coll_obj->GetRawVertexPos(v[i]);
+					
+			}
+			// iterate over the edges (0->1, 1->2, 2->0)
+			for (int a = 0; a < 3; a++)	   		// 'a' is the first point on the edge
+			{				
+				int b = (a + 1) % 3;			// 'b' is the second point on the edge
+				int c = (b + 1) % 3;			// 'c' is the opposite point on the triangle
+				// we are considering the edge a->b
+
+				// generate a unit vector along the edge from a to b, from start to end
+				Mth::Vector	para = (p[b] - p[a]);
+				float edge_length = para.Length();
+				if (edge_length != 0.0f) para /= edge_length; // normalize by hand since we have the length
+
+				// cull certain edges at this point; we don't cull faces at this point in
+				// order to better match edges
+
+				// cull very short edges
+				if (edge_length < arp.min_edge_length) continue;
+
+				// use edges only if less than some angle from horizontal
+				if (Mth::Abs(para[Y]) > arp.sin_max_rail_angle_of_assent) continue;
+
+				// generate a unit vector along the triangle face perpendicular to the edge
+				Mth::Vector perp = (p[c] - p[a]);
+				perp -= Mth::DotProduct(perp, para) * para;
+				perp.Normalize();
+
+				// check to see if we already have this edge's match; bail if we find a match
+				bool new_edge = true;
+				for (int edge = 0; edge < num_edges && new_edge; edge++)
+				{
+					if (arg.p_edges[edge].matched) continue;
+				
+					// check for matched edge 
+					if (very_close(arg.p_edges[edge].p0, p[a], arp) && very_close(arg.p_edges[edge].p1, p[b], arp)
+						|| 	very_close(arg.p_edges[edge].p0, p[b], arp) && very_close(arg.p_edges[edge].p1, p[a], arp))
+					{
+						new_edge = false;
+						arg.p_edges[edge].matched = true;
+						
+						// we've found a match, let's see if the edge is a good rail
+						{
+							// ignore upside down faces			
+							// NOTE: we could probably come up with a better heuristic using both edges' normals
+							if (normal[Y] < -0.707 || arg.p_edges[edge].normal[Y] < -0.707) break;
+
+							// ignore face if non-collidable
+							if (p_coll_obj->GetFaceFlags(face) & mFD_NON_COLLIDABLE) break;
+							
+							// exclude roughly coplanar polygons
+							if (Mth::DotProduct(normal, arg.p_edges[edge].normal) > arp.cos_min_rail_edge_angle) break;
+														
+							// cull out interior edges; that is, if sum of the normals points along the sum of the perps
+							if (Mth::DotProduct(perp + arg.p_edges[edge].perp, normal + arg.p_edges[edge].normal) > 0.0f) break;
+
+							// we don't want the edges of verts to generate rails, only the tops; so cull non-horizontal vert rails
+							if ((arg.p_edges[edge].vert || (p_coll_obj->GetFaceFlags(face) & mFD_VERT)) && Mth::Abs(para[Y]) > 0.1f) break;
+
+							// if we haven't removed the old rails
+							if (!arp.remove_old_rails)
+							{
+								// loop over all old rails and check for degeneracy
+								int check_node = 0;
+								for (check_node = 0; check_node < m_num_nodes; check_node++)
+								{
+									CRailNode *pRailNode = &mp_nodes[check_node];
+	
+									if (!pRailNode->m_pNextLink) continue;
+	
+									// is the new rail within a loose bounding box of the old rail
+									if (p[a][X] < pRailNode->m_BBMin[X] - arp.farthest_degenerate_rail) continue;
+									if (p[a][Y] < pRailNode->m_BBMin[Y] - arp.farthest_degenerate_rail) continue;
+									if (p[a][Z] < pRailNode->m_BBMin[Z] - arp.farthest_degenerate_rail) continue;
+									if (p[a][X] > pRailNode->m_BBMax[X] + arp.farthest_degenerate_rail) continue;
+									if (p[a][Y] > pRailNode->m_BBMax[Y] + arp.farthest_degenerate_rail) continue;
+									if (p[a][Z] > pRailNode->m_BBMax[Z] + arp.farthest_degenerate_rail) continue;
+									if (p[b][X] < pRailNode->m_BBMin[X] - arp.farthest_degenerate_rail) continue;
+									if (p[b][Y] < pRailNode->m_BBMin[Y] - arp.farthest_degenerate_rail) continue;
+									if (p[b][Z] < pRailNode->m_BBMin[Z] - arp.farthest_degenerate_rail) continue;
+									if (p[b][X] > pRailNode->m_BBMax[X] + arp.farthest_degenerate_rail) continue;
+									if (p[b][Y] > pRailNode->m_BBMax[Y] + arp.farthest_degenerate_rail) continue;
+									if (p[b][Z] > pRailNode->m_BBMax[Z] + arp.farthest_degenerate_rail) continue;
+	
+									// is the new rail parallel to the old rail
+									Mth::Vector old_para = pRailNode->m_pNextLink->m_pos - pRailNode->m_pos;
+									old_para.Normalize();
+									if (Mth::Abs(Mth::DotProduct(para, old_para)) < arp.cos_max_degenerate_rail_angle) continue;
+	
+									// if the perpendicular distance from the start of the new rail to the start of the old rail is small, it's degenerate
+									perp = p[a] - pRailNode->m_pos;
+									perp -= Mth::DotProduct(para, perp) * para;
+									if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
+	
+									// if the perpendicular distance from the end of the new rail to the start of the old rail is small, it's degenerate
+									perp = p[b] - pRailNode->m_pos;
+									perp -= Mth::DotProduct(para, perp) * para;
+									if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
+	
+									// if the perpendicular distance from the start of the new rail to the end of the old rail is small, it's degenerate
+									perp = p[a] - pRailNode->m_pNextLink->m_pos;
+									perp -= Mth::DotProduct(para, perp) * para;
+									if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
+	
+									// if the perpendicular distance from the end of the new rail to the end of the old rail is small, it's degenerate
+									perp = p[b] - pRailNode->m_pNextLink->m_pos;
+									perp -= Mth::DotProduct(para, perp) * para;
+									if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
+								}
+
+								// if we broke from the loop, cull the rail due to degeneracy
+								if (check_node != m_num_nodes) break;
+							}
+
+							// use feelers to test for good rails within this edge
+							if (!consider_rail(p[a], p[b], edge, para, normal, perp, edge_length, arg, arp))
+							{
+								printf("failed: more than %d rails\n", MAX_NUM_RAILS);
+								goto ABORT;
+							}
+
+							
+						} // END valid rail test block
+					} // END if (this edge matches)
+				} // END loop over previous edges looking for match
+				
+				// if we didn't find a match
+				if (new_edge)
+				{
+					if (num_edges + 1 < MAX_NUM_EDGES)
+					{
+						// save the new edge
+						arg.p_edges[num_edges].p0 = p[a];
+						arg.p_edges[num_edges].p1 = p[b];
+						arg.p_edges[num_edges].normal = normal;
+						arg.p_edges[num_edges].perp = perp;
+						arg.p_edges[num_edges].matched = false;
+						arg.p_edges[num_edges].vert = p_coll_obj->GetFaceFlags(face) & mFD_VERT;
+
+						num_edges++;
+					}
+					else
+					{
+						printf("failed: more that %d edges in an object\n", MAX_NUM_EDGES);
+						goto ABORT;	// sorry dijkstra ...
+					}
+				} // END if (new_edge)
+			} // END for (int a=0;a<3;a++) (iterating over 3 edges in a face)
+		} // END for (int face = 0; face < num_faces; face++) (iterate over faces in a collision sector)
+
+		p_sector = p_sector_list->IterateNext();
+	} // END loop over sectors
+
+	printf("building railsets...\n");
+
+	// we need to group in the individual rails into continuous rail sets, so we can cull short solo rails and short railsets
+	// note that the connectivity is not perfect in the sense that it can make loops but not loops with tails
+
+	// loop through rail set
+	for (int r = 0; r < arg.num_rails; r++)
+	{
+		SAutoRail& rail = arg.p_rails[r];
+
+		// loop over rail's endpoints
+		for (int endpoint = START; endpoint <= END; endpoint++)
+		{
+			// if the endpoint is connected
+			if (rail.endpoints[endpoint].connection != -1) continue;
+
+			// now we'll check for connections
+
+			int rail_key = generate_endpoint_key(rail.endpoints[endpoint].p);
+	
+			// loop over all rails in same endpoint bin as rail's endpoint's bin
+			// we will check only these rails for connections
+			for (SAutoRail* next_rail = arg.p_rail_endpoint_list->GetFirstItem(rail_key);
+				next_rail;
+				next_rail = arg.p_rail_endpoint_list->GetNextItem(rail_key, next_rail))
+			{
+				SAutoRail& match_rail = *next_rail;
+
+				// we'll always find ourself
+				if (&match_rail == &rail) continue;
+	
+				// check only rails with unconnected endpoints
+				if (match_rail.endpoints[START].connection != -1 && match_rail.endpoints[END].connection != -1) continue;
+
+				// rails connect only if the angle between them is small; this should be adjusted or adjustable
+				float dot = Mth::DotProduct(rail.para, match_rail.para);
+				if (Mth::Abs(dot) < arp.cos_max_corner_in_railset) continue;
+
+				bool reverse_connection = false;
+
+				// see if the start of the match rail is connected to our endpoint
+				if (match_rail.endpoints[START].connection == -1
+					&& very_close(rail.endpoints[endpoint].p, match_rail.endpoints[START].p, arp))
+				{
+					// rails connect only for obtuse angles
+					if (endpoint == START)
+					{
+						reverse_connection = true;
+						if (dot > 0.0f) continue;
+					}
+					else
+					{
+						if (dot < 0.0f) continue;
+					}
+
+					rail.endpoints[endpoint].connection = &match_rail - arg.p_rails;
+					match_rail.endpoints[START].connection = r;
+				}
+
+				// else see if the end of the match rail is connected to our endpoint
+				else if (match_rail.endpoints[END].connection == -1
+					&& very_close(rail.endpoints[endpoint].p, match_rail.endpoints[END].p, arp))
+				{
+					// rails connect only for obtuse angles
+					if (endpoint == END)
+					{
+						reverse_connection = true;
+						if (dot > 0.0f) continue;
+					}
+					else
+					{
+						if (dot < 0.0f) continue;
+					}
+                    
+					rail.endpoints[endpoint].connection = &match_rail - arg.p_rails;
+					match_rail.endpoints[END].connection = r;
+				} // END ifelse determining connectivity
+
+				// if this endpoint of the rail is not found to be connected, continue
+				if (rail.endpoints[endpoint].connection == -1) continue;
+
+				// otherwise, we'll add the rail to a railset
+
+				// if both rails are not in railset
+				if (match_rail.railset == -1 && rail.railset == -1)
+				{
+					if (arg.num_railsets == MAX_NUM_RAILSETS)
+					{
+						printf("failed: more that %d railsets\n", MAX_NUM_RAILSETS);
+						goto ABORT;
+					}
+
+					// setup a new railset
+					rail.railset = arg.num_railsets;
+					arg.p_railsets[arg.num_railsets].length = rail.length;
+					arg.num_railsets++;
+
+					// add match_rail to rail's new railset
+					match_rail.railset = rail.railset;
+					arg.p_railsets[match_rail.railset].length += match_rail.length;
+
+					// swap the rail's endpoints if the connection is reverse
+					if (reverse_connection)
+					{
+						SAutoRailEndpoint temp = rail.endpoints[START];
+						rail.endpoints[START] = rail.endpoints[END];
+						rail.endpoints[END] = temp;
+					}
+
+				// if only match_rail has a railset
+				}
+				else if (match_rail.railset != -1 && rail.railset == -1)
+				{
+					// add rail to match_rail's railset
+					rail.railset = match_rail.railset;
+					arg.p_railsets[rail.railset].length += rail.length;
+
+					// swap the rail's endpoints if the connection is reverse
+					if (reverse_connection)
+					{
+						SAutoRailEndpoint temp = rail.endpoints[START];
+						rail.endpoints[START] = rail.endpoints[END];
+						rail.endpoints[END] = temp;
+					}
+
+				// if only rail has a railset
+				}
+				else if (match_rail.railset == -1 && rail.railset != -1)
+				{
+					// add match_rail to rail's railset
+					match_rail.railset = rail.railset;
+					arg.p_railsets[match_rail.railset].length += rail.length;
+
+					// swap the match rail's endpoints if the connection is reverse
+					if (reverse_connection)
+					{
+						SAutoRailEndpoint temp = match_rail.endpoints[START];
+						match_rail.endpoints[START] = match_rail.endpoints[END];
+						match_rail.endpoints[END] = temp;
+					}
+
+				// if both rails have multrails
+				}
+				else
+				{
+					// join railsets
+					// NOTE: this currently waists a railset; more flexible railset data structure would fix this;
+					// can i use STL? if geometry is ordered in any reasonable fashion, waist should be minimal
+
+					// match_rail's railset survives
+					if (match_rail.railset != rail.railset)
+					{
+						match_rail.length += arg.p_railsets[rail.railset].length;
+						for (int n = arg.num_rails; n--; )
+						{
+							if (arg.p_rails[n].railset == rail.railset) {
+								arg.p_rails[n].railset = match_rail.railset;
+							}
+						}
+						rail.railset = match_rail.railset;
+					}
+
+					// swap all of the rail's railset's endpoints if the connection is reverse
+					// if we're closing a ring, the connection should never be reversed; so that shouldn't be a worry
+					if (reverse_connection)
+					{
+						// traverse in the opposite direction of the connection
+						int traversal_direction = endpoint ^ 1;
+
+						// traverse the rail's railset
+						for (int s = r; s != -1; s = arg.p_rails[s].endpoints[traversal_direction].connection)
+						{
+							SAutoRailEndpoint temp = arg.p_rails[s].endpoints[START];
+							arg.p_rails[s].endpoints[START] = arg.p_rails[s].endpoints[END];
+							arg.p_rails[s].endpoints[END] = temp;
+						}
+					}
+				} // END ifelse block determining rail and match_rail's previous connectivity
+			} // END loop over potential match rails
+
+		} // END loop over endpoints
+	} // END loop over all rails
+
+	printf("culling short rails...\n");
+
+	// now we have rails grouped into railsets; we can cull rails based on their length or the length of their railset
+	for (int r = 0; r < arg.num_rails; r++)
+	{
+		SAutoRail& rail = arg.p_rails[r];
+
+		// solo rails
+		if (rail.railset == -1)
+		{
+			// cull short solo rails
+			if (rail.length < arp.min_railset_length)
+			{
+				rail.disabled = true;
+				continue;
+			}
+		}
+		// railsets
+		else
+		{
+			// cull short railsets
+			if (arg.p_railsets[rail.railset].length < arp.min_railset_length)
+			{
+				rail.disabled = true;
+				continue;
+			}
+		}
+
+		arg.num_active_rails++;
+	} // END loop over rails
+
+	if (!arp.remove_old_rails && arg.num_rails == 0)
+	{
+		printf("failed: no rails found\n");
+		goto ABORT;
+	}
+
+	printf("creating rail nodes...\n");
+
+	// block to scope the final-rail-list generation variables
+	{
+		// we need to count the number of nodes required for the new rails
+		// new_num_nodes = (num rails) + (num rails with no end connection)	+ (num old nodes)
+		int new_num_nodes = arg.num_active_rails;
+		for (int r = arg.num_rails; r--; )
+		{
+			if (!arg.p_rails[r].disabled && arg.p_rails[r].endpoints[END].connection == -1)
+			{
+				new_num_nodes++;
+			}
+		}
+		if (!arp.remove_old_rails)
+		{
+			new_num_nodes += m_num_nodes;
+		}
+	
+		// and create the array for the rails	
+		CRailNode* p_new_nodes = (CRailNode*)Mem::Malloc(new_num_nodes * sizeof(CRailNode));
+	
+		// add the old rails to the new data structure
+		int next_node = 0;
+		if (!arp.remove_old_rails)
+		{
+			// loop over the old nodes
+			for (; next_node < m_num_nodes; next_node++)
+			{
+				CRailNode* pOldRailNode = &mp_nodes[next_node];
+				CRailNode* pRailNode = &p_new_nodes[next_node];
+
+				pRailNode->m_node = next_node;
+
+				// calculate connectivity using pointer offsets
+				pRailNode->m_pNextLink = (pOldRailNode->m_pNextLink
+										  ? pOldRailNode->m_pNextLink - mp_nodes + p_new_nodes
+										  : NULL);
+				pRailNode->m_pPrevLink = (pOldRailNode->m_pPrevLink
+										  ? pOldRailNode->m_pPrevLink - mp_nodes + p_new_nodes
+										  : NULL);
+
+				// copy in their state
+				pRailNode->m_flags = pOldRailNode->m_flags;
+				pRailNode->m_pos = pOldRailNode->m_pos;
+				pRailNode->m_terrain_type = pOldRailNode->m_terrain_type; // debug colors for now pOldRailNode->m_terrain_type;
+				pRailNode->m_BBMin = pOldRailNode->m_BBMin;
+				pRailNode->m_BBMax = pOldRailNode->m_BBMax;
+			} // END loop over old nodes
+		} // END if retaining old rails
+	
+		// iterate over the nodes and add the new rails to the array; each time we hit a railset, we move to the head, then traverse the set, adding the
+		// rails; not the most optimal algorithm but simple; we skip previously added rails and we move through the array
+		for (int r = 0; r < arg.num_rails; r++)
+		{
+			// because we add whole railsets at once, we may already have added any given rail
+			if (arg.p_rails[r].disabled) continue;
+
+			// traverse to start of this rail's railset watching for a loop
+			int s = r;
+			while (arg.p_rails[s].endpoints[START].connection != -1)
+			{
+				s = arg.p_rails[s].endpoints[START].connection;
+				if (s == r) break;
+			}
+
+			// traverse the railset, adding nodes as we go
+			int starting_rail = s;
+			CRailNode* p_starting_node = &p_new_nodes[next_node];
+			int last_s;
+			do {
+				CRailNode* pRailNode = &p_new_nodes[next_node];
+				pRailNode->m_node = next_node;
+
+				if (arg.p_rails[s].endpoints[START].connection != -1)
+				{
+					pRailNode->m_pPrevLink = &p_new_nodes[next_node - 1];
+				}
+				else
+				{
+					pRailNode->m_pPrevLink = NULL;
+				}
+
+				// check for a loop
+				if (arg.p_rails[s].endpoints[END].connection != starting_rail)
+				{
+					pRailNode->m_pNextLink = &p_new_nodes[next_node + 1];
+				}
+				else
+				{
+					pRailNode->m_pNextLink = p_starting_node;
+				}
+
+				pRailNode->m_flags = 0;
+				pRailNode->SetActive(true);
+				pRailNode->m_pos = arg.p_rails[s].endpoints[START].p;
+				if (arg.p_rails[s].railset == -1)
+					pRailNode->m_terrain_type = vTERRAIN_CONCSMOOTH; // red
+				else
+					pRailNode->m_terrain_type = vTERRAIN_METALSMOOTH; // blue
+				Rail_ComputeBB(arg.p_rails[s].endpoints[START].p, arg.p_rails[s].endpoints[END].p, pRailNode->m_BBMin, pRailNode->m_BBMax);
+
+				// mark as having been added
+				arg.p_rails[s].disabled = true;
+				next_node++;
+
+				last_s = s;
+				s = arg.p_rails[s].endpoints[END].connection;
+			} while (s != -1 && s != starting_rail);
+
+			// if not a loop
+			if (s != starting_rail)
+			{
+				// add extra ending node of railset
+
+				CRailNode* pRailNode = &p_new_nodes[next_node];
+				pRailNode->m_node = next_node;
+	
+				pRailNode->m_pPrevLink = &p_new_nodes[next_node - 1];
+				pRailNode->m_pNextLink = NULL;
+
+				pRailNode->m_pos = arg.p_rails[last_s].endpoints[END].p;
+	
+				next_node++;
+			}
+		} // END final-rail-list generation scope
+	
+		// first reset the manager (this)
+		// Note this invalidates mp_node_array (setting it to NULL)
+		// so I had to patch up a couple of placed in skater.cpp
+		// that were using the rail manager's node array
+		Cleanup();
+	
+		mp_nodes = p_new_nodes;
+ 		m_num_nodes = new_num_nodes;
+	
+		printf("complete.\n");
+
+	}
+
+ABORT:
+
+	delete	arg.p_rail_endpoint_list;
+
+	delete	[] arg.p_railsets;	
+	delete	[] arg.p_edges;
+	delete  [] arg.p_rails;
+	
+	Mem::Manager::sHandle().PopContext();
+	
+	printf("-----------------------\n");
+#endif	
+}
+
+}
+			   
+
diff --git a/Code/Sk/Objects/rail.h b/Code/Sk/Objects/rail.h
new file mode 100644
index 0000000..6e798ed
--- /dev/null
+++ b/Code/Sk/Objects/rail.h
@@ -0,0 +1,209 @@
+//////////////////////////////////////////
+// rail.h
+
+#ifndef __OBJECTS_RAIL_H
+#define __OBJECTS_RAIL_H
+                        
+#include 
+#include 
+#include 
+                           
+namespace Script
+{
+	class	CArray;
+	class	CStruct;
+}
+
+#define	MAX_RAIL_LINKS 4
+
+
+namespace Obj
+{
+
+class	CRailManager;
+class	CWalkComponent;
+struct	SHangRailData;
+struct	SLadderRailData;
+
+enum ERailNodeFlag
+{	
+	LIP_OVERRIDE,		// always do a lip trick on this
+	DEFAULT_LINE,
+	ACTIVE,
+	NO_CLIMBING,
+	ONLY_CLIMBING,
+	LADDER,
+	NO_HANG_WITH_RIGHT_ALONG_RAIL,
+	NO_HANG_WITH_LEFT_ALONG_RAIL
+};
+
+
+// seperating out a parallel structure of CRailLinks, as only used 
+// when rails are first parsed
+class	CRailLinks
+{
+	friend class CRailManager;
+	sint16 m_link[MAX_RAIL_LINKS];	// link numbers	
+};
+
+class CRailNode : public Spt::Class
+{
+	friend class  CRailManager;
+
+public:						  
+	bool 			ProbablyOnSameRailAs(int SearchNode) const;
+	int				Side(const Mth::Vector &vel) const;
+	
+	const CRailNode	*	GetNextLink() const				{return m_pNextLink;}	
+	const CRailNode	*	GetPrevLink() const				{return m_pPrevLink;}	
+	const sint16			GetNode() const 	  				{return m_node;}
+//	const sint16			GetLink() const	  				{return m_link;}
+	const ETerrainType	GetTerrain() const	  			{return (ETerrainType) m_terrain_type;}
+	
+	void				SetActive(bool active)		{if (active) SetFlag(ACTIVE); else ClearFlag(ACTIVE);}
+	const bool			GetActive() const					{return GetFlag(ACTIVE);}
+	const bool			IsActive() const					{return GetActive();}
+
+	void			SetFlag(ERailNodeFlag flag) {m_flags.Set(flag);}
+	void			ClearFlag(ERailNodeFlag flag) {m_flags.Clear(flag);}
+	bool			GetFlag(ERailNodeFlag flag) const {return m_flags.Test(flag);}
+
+protected:
+
+// MEMOPT:  In theory this could all fit in 3x16 = 48 bytes, instead it is 64, becaute the Mth::Vectors are 16 byte aligned
+// possibly could define a Mth::Vector3 that uses 12 bytes, with appropiate copy operators.
+
+	Mth::Vector m_BBMin;		// bounding box for this node and the subsequent one
+	Mth::Vector m_BBMax;		// (or just a zero-sized box, if not applicable)
+	Mth::Vector m_pos;			// position of the node
+
+    Mth::Quat m_orientation;    // orientation of the rail node; only set for climbing nodes
+	
+	CRailNode *m_pNextLink;		// Pointer to next Node	in rail(or NULL if none)
+	CRailNode *m_pPrevLink;		// Pointer to previous Node	(or NULL if none)
+
+	Flags< ERailNodeFlag > m_flags; // flags for rail segment
+	
+	sint16 m_node;				// Number of the node in the trigger array	
+	uint8 m_terrain_type;		// play the correct grind sound on the rail...
+	
+    
+
+// the position getting functions are private
+// so you can only access them through the rail manager
+// (as you need to know if it's transformed or not)
+private:
+	const Mth::Vector&	GetPos() const;	 
+	const Mth::Quat&	GetOrientation() const;	 
+	const Mth::Vector&	GetBBMin() const;
+	const Mth::Vector&	GetBBMax() const;
+						  
+};
+
+
+
+class CRailManager : public Spt::Class
+{
+
+public:
+					CRailManager();
+				   ~CRailManager();	
+
+	
+	void 			Cleanup();
+								 
+	void			AddRailsFromNodeArray(Script::CArray * p_node_array);
+	void 			AddRailNode(int node_number, Script::CStruct *p_node_struct);
+	void 			AddedAll();
+	
+	void			UpdateTransform(Mth::Matrix& transform);
+    bool			CheckForLadderRail ( const Mth::Vector& pos, float max_horizontal_snap_distance, float max_vertical_snap_distance, bool up, CWalkComponent* p_walk_component, SLadderRailData& rail_data, const CRailNode** pp_rail_node );
+	bool			CheckForAirGrabLadderRail ( const Mth::Vector& start_pos, const Mth::Vector& end_pos, CWalkComponent* p_walk_component, SLadderRailData& rail_data, const CRailNode** pp_rail_node );
+	// bool			CheckForHopToHangRail ( const Mth::Vector& check_line_start, float check_line_height, const Mth::Vector& facing, float max_forward_reach, float max_backward_reach, CWalkComponent* p_walk_component, SHangRailData& rail_data, const CRailNode** pp_rail_node );
+	bool			CheckForHangRail ( const Mth::Vector& start_pos, const Mth::Vector& end_pos, const Mth::Vector& facing, CWalkComponent* p_walk_component, SHangRailData& rail_data, float snap_distance );
+	bool			RailNodesAreCoincident ( const CRailNode* p_node_a, const CRailNode* p_node_b );
+	bool			CheckForCoincidentRailNode ( const CRailNode* p_node, uint32 ignore_mask, const CRailNode** pp_next_node );
+    bool 			StickToRail(const Mth::Vector &pos1, const Mth::Vector &pos2, Mth::Vector *p_point, Obj::CRailNode **pp_rail_node, const Obj::CRailNode *p_ignore_node = NULL, float min_dot = 1.0f, int side = 0);
+	
+	void 			SetActive( int node, int active, bool wholeRail );
+	bool 			IsActive( int node );
+	bool			IsMoving(){return m_is_transformed;}
+
+	void 			MoveNode( int node, Mth::Vector &pos );
+
+	
+	void			DebugRender(Mth::Matrix *p_transform = NULL);
+	void			RemoveOverlapping();
+	
+	int				GetNumNodes(){return m_num_nodes;}
+	
+	Script::CArray *	GetNodeArray(){return mp_node_array;}
+	void				SetNodeArray(Script::CArray *p_nodeArray){mp_node_array = p_nodeArray;}
+
+	void			NewLink(CRailNode *p_from, CRailNode *p_to);
+
+	void			AutoGenerateRails(Script::CStruct* pParams);
+
+	CRailNode*		GetRailNodeByNodeNumber( int node_num );
+	
+	int				GetNodeIndex(const CRailNode *p_node) {return (((int)(p_node)-(int(mp_nodes)))/sizeof(CRailNode));}
+	
+// accessor functions for the rail node
+// we only access their positions via the rail manager
+// as only the rail manager knows the transform of the object to
+// which they are attached
+// These nodes are "instances", in that the same node might be used in
+// more than one place
+// so these functions only make sense in conjunction with the rail_manager
+	Mth::Vector LocalToWorldTransform ( const Mth::Vector& vector ) const;
+	Mth::Vector	GetPos(const CRailNode *p_node) 	const;	 
+    Mth::Matrix GetMatrix(const CRailNode *p_node) 	const;
+	Mth::Vector	GetBBMin(const CRailNode *p_node)   const;
+	Mth::Vector	GetBBMax(const CRailNode *p_node)   const;
+
+
+private:
+//	CRailNode*  		mp_first_node;			// was a pointer to a list of nodes
+	Mth::Matrix			m_transform;
+	CRailNode*  		mp_nodes;				// pointer to array of nodes
+	CRailLinks*			mp_links;				// pointer to temp array of links
+	Script::CArray *	mp_node_array;
+	int					m_num_nodes;
+	int					m_current_node;
+	bool				m_is_transformed;
+	
+};
+
+
+
+// Global rail type functions that don't need a manager
+
+bool  			Rail_ValidInEditor(Mth::Vector Start, Mth::Vector End);
+ 
+inline const Mth::Vector&	CRailNode::GetPos() const
+{
+	return m_pos;
+}
+
+inline const Mth::Quat&	CRailNode::GetOrientation() const
+{
+	return m_orientation;
+}
+
+inline const Mth::Vector&	CRailNode::GetBBMin() const
+{
+	return m_BBMin;
+}
+
+inline const Mth::Vector&	CRailNode::GetBBMax()  const
+{
+	return m_BBMax;
+}
+
+} 
+
+			
+#endif			// #ifndef __OBJECTS_RAIL_H
+			
+
+
diff --git a/Code/Sk/Objects/records.cpp b/Code/Sk/Objects/records.cpp
new file mode 100644
index 0000000..b53d7dc
--- /dev/null
+++ b/Code/Sk/Objects/records.cpp
@@ -0,0 +1,639 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Objects	(Record)										**
+**																			**
+**	File name:		objects/Record.cpp										**
+**																			**
+**	Created: 		7/11/01	- Mick											**
+**																			**
+**	Description:	Record Handling, high score tables and suchlike			**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include  
+#include  
+#include  
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Records
+{
+	
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+		enum {
+					vNumHighScores = 5,
+					vNumBestCombos = 5,
+			 };
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CInitials::Set(const char *initials) // K: Had to add a const so I could pass it a const got from GetText
+{
+	
+
+	m_initials[0] = initials[0];
+	m_initials[1] = initials[1];
+	m_initials[2] = initials[2];
+	m_initials[3] = 0;
+
+}
+
+char * CInitials::Get(void)
+{
+	
+
+	return m_initials;			// note, returning a pointer
+
+}
+
+
+
+CRecord::CRecord()
+{
+	
+	
+	mp_initials = new CInitials();
+	
+	Set("XXX",1000,0); 			// set a default.  Will not hurt anything
+	
+}
+
+CRecord::~CRecord()
+{
+	
+	
+	delete mp_initials;
+}
+
+
+// SetInitials will copy in the first three digits of the string   
+void	CRecord::SetInitials(char *initials)
+{
+	
+	mp_initials->Set(initials);
+
+}
+
+void	CRecord::SetValue(int value)
+{
+	
+	m_value = value;
+
+}
+
+void	CRecord::SetNumber(int number)
+{
+	
+	m_number = number;
+
+}
+
+void	CRecord::SetNewRecord(bool new_record)
+{
+	
+	m_new_record = new_record;
+
+}
+
+
+void	CRecord::Set(char *initials, int value, int number)
+{
+	
+	
+	SetInitials(initials);
+	SetValue(value);
+	SetNumber(number);
+}
+
+void	CRecord::Set(CRecord *pRecord)
+{
+	
+	
+	SetInitials(pRecord->GetInitials());
+	SetValue(pRecord->GetValue());
+	SetNumber(pRecord->GetNumber());
+}
+
+
+char *	CRecord::GetInitials(void)	// note, returns a pointer to the initials, not the initials itself
+{
+	
+	return mp_initials->Get();
+}
+
+int	   CRecord::GetValue(void)
+{
+	
+	
+	return m_value;
+}
+
+int	   CRecord::GetNumber(void)
+{
+	
+	
+	return m_number;
+
+}
+
+bool	   CRecord::GetNewRecord(void)
+{
+	
+	
+	return m_new_record;
+
+}
+
+
+bool  CRecord::BeatRecord(int value)		
+{
+	
+	
+	return value > GetValue();
+}
+
+bool  CRecord::MaybeNewRecord(int value, int number)		
+{
+	
+	
+	if (BeatRecord(value))
+	{
+		SetValue(value);
+		SetNumber(number);
+		SetNewRecord(true);
+		return true;
+	}
+	else
+	{
+		SetNewRecord(false);
+		return false;
+	}
+}
+
+
+// if this was flagged as a new record, then set it to these initials
+void 	CRecord::UpdateInitials(CInitials* pInitials)
+{
+	if (GetNewRecord())
+	{
+//		SetNewRecord(false);
+		mp_initials->Set(pInitials->Get());
+	}
+}
+
+
+
+void CRecord::WriteIntoStructure(Script::CStruct *pIn)
+{
+	
+
+	Dbg_MsgAssert(pIn,("NULL pIn sent to CRecord::WriteIntoStructure"));
+
+	pIn->AddComponent(Script::GenerateCRC("Value"),ESYMBOLTYPE_INTEGER,m_value);
+	pIn->AddComponent(Script::GenerateCRC("Number"),ESYMBOLTYPE_INTEGER,m_number);
+	Dbg_MsgAssert(mp_initials,("NULL mp_initials"));
+	pIn->AddComponent(Script::GenerateCRC("Initials"),ESYMBOLTYPE_STRING,mp_initials->Get());
+}
+
+void CRecord::ReadFromStructure(Script::CStruct *pIn)
+{
+	
+	Dbg_MsgAssert(pIn,("NULL pIn sent to CRecord::ReadFromStructure"));
+	
+	if (!pIn->GetInteger("Value",&m_value))
+	{
+		Dbg_MsgAssert(0,("Structure is missing Value parameter"));
+	}	
+	if (!pIn->GetInteger("Number",&m_number))
+	{
+		Dbg_MsgAssert(0,("Structure is missing Number parameter"));
+	}	
+	
+	const char *pInitials="XXX";
+	if (!pIn->GetText("Initials",&pInitials))
+	{
+		Dbg_MsgAssert(0,("Structure is missing Initials parameter"));
+	}
+	Dbg_MsgAssert(mp_initials,("NULL mp_initials"));
+	mp_initials->Set(pInitials);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CRecordTable, a table of records, like a high score table
+CRecordTable::CRecordTable(int num_records)
+{
+	
+	
+	m_numRecords = num_records;
+	mp_records = new CRecord[m_numRecords];
+}
+			
+CRecordTable::~CRecordTable()
+{
+	
+
+	delete [] mp_records;
+	
+}
+
+CRecord*   CRecordTable::GetRecord(int line)
+{
+	
+	
+	Dbg_MsgAssert(line < m_numRecords,("Bad Line %d in recordtable",line));
+	return &mp_records[line];
+}
+
+int			CRecordTable::GetSize(void)
+{
+	
+	
+	return m_numRecords;	
+}
+
+
+void 	CRecordTable::UpdateInitials(CInitials* pInitials)
+{
+	for (int line= 0; line < m_numRecords; line++)
+	{
+		mp_records[line].UpdateInitials(pInitials);
+	}	
+}
+
+// given a new "value", then insert it into the table in the position
+// that matches this value, and move other entries down (losing the final entry)
+// returns the entery number (fisr tline is 0)
+// or -1 if it does not make the cut.
+int			CRecordTable::MaybeNewEntry(int value, int number)
+{
+	
+
+	int line;
+	
+	for (line= 0; line < m_numRecords; line++)
+	{
+		mp_records[line].SetNewRecord(false);
+	}
+
+	for (line= 0; line < m_numRecords; line++)
+	{
+		if (value > mp_records[line].GetValue())
+		{
+			// alright! this value belongs at "line"
+			// so maove down any that are below this line
+			// and insert this one
+			for (int dest = m_numRecords - 1; dest > line; dest--)
+			{
+				mp_records[dest].Set(&mp_records[dest-1]);
+			}
+			mp_records[line].SetValue(value);	
+			mp_records[line].SetNumber(number);	
+			mp_records[line].SetInitials("XXX");	
+			mp_records[line].SetNewRecord(true);
+			return line;		
+		}
+	}
+
+	return -1;
+}
+
+
+			
+void 		CRecordTable::ReadFromStructure(Script::CStruct *pIn)
+{
+	
+
+	Dbg_MsgAssert(pIn, ("NULL pIn sent to CRecordTable::ReadFromStructure"));
+	Script::CArray *pArray=NULL;
+	pIn->GetArray("RecordTable",&pArray);
+	Dbg_MsgAssert(pArray,("Structure is missing RecordTable parameter"));
+	
+	if (m_numRecords!=(int)pArray->GetSize())
+	{
+		Dbg_Warning("m_numRecords mismatch: m_numRecords=%d, but num records in structure=%d",m_numRecords,pArray->GetSize());
+		return;
+	}
+	Dbg_MsgAssert(mp_records,("NULL mp_records"));
+		
+	for (int i=0; iGetStructure(i));
+	}	
+}
+			
+void 		CRecordTable::WriteIntoStructure(Script::CStruct *pIn)
+{
+	
+
+	Dbg_MsgAssert(pIn, ("NULL pIn sent to CRecordTable::WriteIntoStructure"));
+	
+	Script::CArray *pArray=new Script::CArray;
+	pArray->SetArrayType(m_numRecords,ESYMBOLTYPE_STRUCTURE);
+
+	Dbg_MsgAssert(mp_records,("NULL mp_records"));
+	for (int i=0; iSetStructure(i,pStruct);
+	}
+
+	pIn->AddComponent(Script::GenerateCRC("RecordTable"),ESYMBOLTYPE_ARRAY,(int)pArray);
+}
+			
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CLevelRecords - Records for a particualr level (Skate3 specific)
+CLevelRecords::CLevelRecords(/*int numHighScores, int numBestCombos*/)
+{
+	
+	
+	
+   	mp_highScores = new CRecordTable(vNumHighScores);
+	
+   	mp_bestCombos = new CRecordTable(vNumBestCombos);
+   	mp_longestCombo = new CRecord;
+	mp_longestCombo->SetValue(0);
+   	mp_longestGrind = new CRecord;
+	mp_longestGrind->SetValue(0);
+   	mp_longestLipTrick = new CRecord;
+	mp_longestLipTrick->SetValue(0);
+   	mp_longestManual = new CRecord;
+	mp_longestManual->SetValue(0);
+	
+	
+}
+			
+CLevelRecords::~CLevelRecords()
+{
+	
+	delete mp_longestManual;
+	delete mp_longestLipTrick;
+	delete mp_longestGrind;
+	delete mp_longestCombo;
+	delete mp_bestCombos;
+	delete mp_highScores;
+}
+
+
+void		CLevelRecords::UpdateInitials(CInitials* pInitials)
+{
+	mp_longestManual->UpdateInitials(pInitials);
+	mp_longestLipTrick->UpdateInitials(pInitials);
+	mp_longestGrind->UpdateInitials(pInitials);
+	mp_longestCombo->UpdateInitials(pInitials);
+	mp_bestCombos->UpdateInitials(pInitials);
+	mp_highScores->UpdateInitials(pInitials);
+	
+}
+ 
+
+
+			
+void		CLevelRecords::ReadFromStructure(Script::CStruct *pIn)
+{
+	
+
+	Dbg_MsgAssert(pIn,("NULL pIn sent to CLevelRecords::ReadFromStructure"));
+
+	Script::CStruct *pStruct=NULL;
+	
+	pIn->GetStructure("HighScores",&pStruct);
+	Dbg_MsgAssert(pStruct,("Structure is missing HighScores"));
+   	Dbg_MsgAssert(mp_highScores,("NULL mp_highScores"));
+	mp_highScores->ReadFromStructure(pStruct);
+
+	pIn->GetStructure("BestCombos",&pStruct);
+	Dbg_MsgAssert(pStruct,("Structure is missing BestCombos"));
+   	Dbg_MsgAssert(mp_bestCombos,("NULL mp_bestCombos"));
+	mp_bestCombos->ReadFromStructure(pStruct);
+
+	pIn->GetStructure("LongestCombo",&pStruct);
+	Dbg_MsgAssert(pStruct,("Structure is missing LongestCombo"));
+   	Dbg_MsgAssert(mp_longestCombo,("NULL mp_longestCombo"));
+	mp_longestCombo->ReadFromStructure(pStruct);
+
+	pIn->GetStructure("LongestGrind",&pStruct);
+	Dbg_MsgAssert(pStruct,("Structure is missing LongestGrind"));
+   	Dbg_MsgAssert(mp_longestGrind,("NULL mp_longestGrind"));
+	mp_longestGrind->ReadFromStructure(pStruct);
+
+	pIn->GetStructure("LongestLipTrick",&pStruct);
+	Dbg_MsgAssert(pStruct,("Structure is missing LongestLipTrick"));
+   	Dbg_MsgAssert(mp_longestLipTrick,("NULL mp_longestLipTrick"));
+	mp_longestLipTrick->ReadFromStructure(pStruct);
+
+	pIn->GetStructure("LongestManual",&pStruct);
+	Dbg_MsgAssert(pStruct,("Structure is missing LongestManual"));
+   	Dbg_MsgAssert(mp_longestManual,("NULL mp_longestManual"));
+	mp_longestManual->ReadFromStructure(pStruct);
+}
+			
+void		CLevelRecords::WriteIntoStructure(Script::CStruct *pIn)
+{
+	
+	
+	Dbg_MsgAssert(pIn,("NULL pIn sent to CLevelRecords::WriteIntoStructure"));
+	
+
+	Script::CStruct *pStruct=new Script::CStruct;
+   	Dbg_MsgAssert(mp_highScores,("NULL mp_highScores"));
+	mp_highScores->WriteIntoStructure(pStruct);
+	pIn->AddComponent(Script::GenerateCRC("HighScores"),ESYMBOLTYPE_STRUCTUREPOINTER,(int)pStruct);
+	
+	pStruct=new Script::CStruct;
+   	Dbg_MsgAssert(mp_bestCombos,("NULL mp_bestCombos"));
+	mp_bestCombos->WriteIntoStructure(pStruct);
+	pIn->AddComponent(Script::GenerateCRC("BestCombos"),ESYMBOLTYPE_STRUCTUREPOINTER,(int)pStruct);
+	
+	pStruct=new Script::CStruct;
+   	Dbg_MsgAssert(mp_longestCombo,("NULL mp_longestCombo"));
+	mp_longestCombo->WriteIntoStructure(pStruct);
+	pIn->AddComponent(Script::GenerateCRC("LongestCombo"),ESYMBOLTYPE_STRUCTUREPOINTER,(int)pStruct);
+	
+	pStruct=new Script::CStruct;
+   	Dbg_MsgAssert(mp_longestGrind,("NULL mp_longestGrind"));
+	mp_longestGrind->WriteIntoStructure(pStruct);
+	pIn->AddComponent(Script::GenerateCRC("LongestGrind"),ESYMBOLTYPE_STRUCTUREPOINTER,(int)pStruct);
+	
+	pStruct=new Script::CStruct;
+   	Dbg_MsgAssert(mp_longestLipTrick,("NULL mp_longestLipTrick"));
+	mp_longestLipTrick->WriteIntoStructure(pStruct);
+	pIn->AddComponent(Script::GenerateCRC("LongestLipTrick"),ESYMBOLTYPE_STRUCTUREPOINTER,(int)pStruct);
+   	
+	pStruct=new Script::CStruct;
+	Dbg_MsgAssert(mp_longestManual,("NULL mp_longestManual"));
+	mp_longestManual->WriteIntoStructure(pStruct);
+	pIn->AddComponent(Script::GenerateCRC("LongestManual"),ESYMBOLTYPE_STRUCTUREPOINTER,(int)pStruct);
+}
+			
+
+
+///////////////////////////////////////////////////////////////////////////////
+// CGameRecords - Records for the whole game
+
+CGameRecords::CGameRecords(int numLevels /*, int numHighScores, int numBestCombos*/)
+{
+	
+	m_numLevels = numLevels;
+
+	// VC++ complains about constructing an array with parameters
+	// so I (Mick) changed it to use and enum for those values,
+	// as they are not likely to change anyway  
+	mp_levelRecords = new CLevelRecords[numLevels]; /*(numHighScores,numBestCombos)*/
+	mp_defaultInitials = new CInitials;
+	mp_defaultInitials->Set("ABC");
+}
+			
+CGameRecords::~CGameRecords()
+{
+	
+	delete [] mp_levelRecords;
+	delete mp_defaultInitials;
+	
+}
+
+
+CInitials * CGameRecords::GetDefaultInitials(void)
+{
+	
+
+	return mp_defaultInitials;
+}
+
+void CGameRecords::SetDefaultInitials(const char *p_initials)
+{
+	
+
+	mp_defaultInitials->Set( p_initials );
+}
+
+
+
+CLevelRecords * CGameRecords::GetLevelRecords(int level)
+{
+	
+	Dbg_MsgAssert(level>=0 && level <= m_numLevels,("bad level number %d (max %d)\n",level,m_numLevels));
+	
+	return &mp_levelRecords[level];
+}
+
+			
+void 		CGameRecords::ReadFromStructure(Script::CStruct *pIn)
+{
+	
+
+	Dbg_MsgAssert(pIn, ("NULL pIn sent to CGameRecords::ReadFromStructure"));	
+
+	Script::CArray *pArray=NULL;
+	pIn->GetArray("GameRecords",&pArray);
+	Dbg_MsgAssert(pArray,("Structure is missing GameRecords parameter"));
+	
+	if (m_numLevels!=(int)pArray->GetSize())
+	{
+		Dbg_Warning("m_numLevels mismatch: m_numLevels=%d, but num levels in structure=%d",m_numLevels,pArray->GetSize());
+		return;
+	}
+	Dbg_MsgAssert(mp_levelRecords,("NULL mp_levelRecords"));
+		
+	for (int i=0; iGetStructure(i));
+	}	
+	
+	const char *pDefaultInitials="ABC";
+	pIn->GetText("DefaultInitials",&pDefaultInitials);
+	Dbg_MsgAssert(mp_defaultInitials,("NULL mp_defaultInitials"));
+	mp_defaultInitials->Set(pDefaultInitials);
+	
+}
+			
+void 		CGameRecords::WriteIntoStructure(Script::CStruct *pIn)
+{
+	
+	Dbg_MsgAssert(pIn, ("NULL pIn sent to CGameRecords::WriteIntoStructure"));	
+	
+	Script::CArray *pArray=new Script::CArray;
+	pArray->SetArrayType(m_numLevels,ESYMBOLTYPE_STRUCTURE);
+
+	Dbg_MsgAssert(mp_levelRecords,("NULL mp_levelRecords"));
+	for (int i=0; iSetStructure(i,pStruct);
+	}
+
+	pIn->AddComponent(Script::GenerateCRC("GameRecords"),ESYMBOLTYPE_ARRAY,(int)pArray);
+	pIn->AddComponent(Script::GenerateCRC("DefaultInitials"),ESYMBOLTYPE_STRING,mp_defaultInitials->Get());
+}
+			
+
+
+
+
+
+
+} // namespace Records
+
+
diff --git a/Code/Sk/Objects/records.h b/Code/Sk/Objects/records.h
new file mode 100644
index 0000000..858bace
--- /dev/null
+++ b/Code/Sk/Objects/records.h
@@ -0,0 +1,225 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/records.h									   	**
+**																			**
+**	Created: 		11/7/01 - Mick 											**
+**																			**
+*****************************************************************************/
+// Notes:
+// as a bit of an experiment, I'm writing the whole structure using 
+// dynamic containers,
+// so each data type is more independent of the others
+// but the constructor (and destructor) of the higher level types
+// will have to create (and destroy) the dynamic instances of the others
+//
+// this gives us benefits, namely:
+//   No cross includes needed between header files if we split into seperate classes
+//   Number of elements in arrays (for high score tables) can be decided at run time.
+//
+// But has drawback:
+//   Uses more memory, as each element is allocated individually
+//   might be slower?
+//   more code?  Dunno, that's why I'm experimenting....
+
+
+#ifndef __OBJECTS_RECORDS_H
+#define __OBJECTS_RECORDS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+
+namespace Script
+{
+	class CStruct;
+};
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Records
+{
+
+/*****************************************************************************
+**							Forward Declarations						**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class  CInitials : public Spt::Class
+{
+	
+
+	public:
+		char *	Get(void);
+		void	Set(const char *initials);				
+	
+	private:
+		char 	m_initials[4];
+		
+};
+				
+class  CRecord  : public Spt::Class
+{
+	
+
+
+	public:
+								CRecord();
+								~CRecord();
+		
+		// for memory card loading/saving
+		void 						ReadFromStructure(Script::CStruct *pIn);
+		void 						WriteIntoStructure(Script::CStruct *pIn);
+
+		void						Set(char *initials, int value, int number);
+		void						Set(CRecord *pRecord);
+		
+		void						SetInitials(char *initials);
+		void						SetValue(int value);
+		void						SetNumber(int number);
+		void						SetNewRecord(bool new_record);
+		
+		char 	*					GetInitials(void);	// note, returns a pointer to the initials, not the initials itself
+		int							GetValue(void);
+		int							GetNumber(void);
+		bool						GetNewRecord(void);	
+
+		// Return true if we beat the record		
+		bool						BeatRecord(int value);		
+
+		// set the record and return true if we beat it
+		bool						MaybeNewRecord(int value, int number);
+		void 						UpdateInitials(CInitials* pInitials);
+		
+
+
+	private:
+		CInitials		*			mp_initials;		// Initials
+		int							m_value;		 	// 
+		int							m_number;			// pro skater number (or could be used for anything)
+		bool						m_new_record;		// set if it's a new record
+};
+
+
+class  CRecordTable  : public Spt::Class
+{
+	public:
+									CRecordTable(int num_records);
+									~CRecordTable();
+		CRecord*   					GetRecord(int line);
+		int		   					GetSize(void);
+		int							MaybeNewEntry(int value, int number);
+		// for memory card loading/saving
+		void 						ReadFromStructure(Script::CStruct *pIn);
+		void 						WriteIntoStructure(Script::CStruct *pIn);
+		void 						UpdateInitials(CInitials* pInitials);
+
+	private:		
+		int							m_numRecords;		// Number of records
+		CRecord		*				mp_records;			// pointer to records					
+};
+
+
+// CLevelRecords - Records for a particualr level
+// Include table of high scores, and combo scores
+// and individual records for other things like longest grind 
+class  CLevelRecords  : public Spt::Class
+{
+	public:
+									CLevelRecords(/*int numHighScores, int numBestCombos*/);
+									~CLevelRecords();
+		
+		void 						UpdateInitials(CInitials* pInitials);
+		
+		// for memory card loading/saving
+		void 						ReadFromStructure(Script::CStruct *pIn);
+		void 						WriteIntoStructure(Script::CStruct *pIn);
+	
+	private:
+		CRecordTable		*		mp_highScores;
+		CRecordTable		*		mp_bestCombos;
+		CRecord				*		mp_longestGrind;
+		CRecord				*		mp_longestManual;
+		CRecord				*		mp_longestLipTrick;
+		CRecord				*		mp_longestCombo;	
+	public:
+		CRecordTable		*		GetHighScores()      {return mp_highScores;}        
+		CRecordTable		*		GetBestCombos()      {return mp_bestCombos;}        
+		CRecord				*		GetLongestGrind()    {return mp_longestGrind;}      
+		CRecord				*		GetLongestManual()   {return mp_longestManual;}     
+		CRecord				*		GetLongestLipTrick() {return mp_longestLipTrick;}   
+		CRecord				*		GetLongestCombo()	 {return mp_longestCombo;}      
+				
+			
+};
+
+// Records for the whole game
+// stores a single CLevelRecords for each level, in an array
+// also stores the default initials used by the game records
+								  
+class CGameRecords : public Spt::Class
+{
+	public:
+									CGameRecords(int numLevels /*, int numHighScores, int numBestCombos*/);
+									~CGameRecords();
+									
+		CInitials 			* 		GetDefaultInitials(void);
+		void				 		SetDefaultInitials(const char *p_initials);
+		
+		CLevelRecords 		*	 	GetLevelRecords(int level);
+									
+		// for memory card loading/saving
+		void 						ReadFromStructure(Script::CStruct *pIn);
+		void 						WriteIntoStructure(Script::CStruct *pIn);
+	
+	private:
+		int							m_numLevels;
+		CInitials			*		mp_defaultInitials;
+		CLevelRecords		*		mp_levelRecords;
+};
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Records
+
+#endif	// __OBJECTS_RECORDS_H
+
diff --git a/Code/Sk/Objects/restart.cpp b/Code/Sk/Objects/restart.cpp
new file mode 100644
index 0000000..8a47319
--- /dev/null
+++ b/Code/Sk/Objects/restart.cpp
@@ -0,0 +1,237 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:													**
+**																			**
+**	Module:						 								**
+**																			**
+**	File name:		restart.cpp											**
+**																			**
+**	Created by:		00/00/00	-	initials								**
+**																			**
+**	Description:			 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+// Return tru is this node is a restart node
+bool IsRestart(Script::CScriptStructure *pNode)
+{
+	uint32	ClassChecksum;
+	pNode->GetChecksum("Class",&ClassChecksum);
+	if (ClassChecksum == 0x1806ddf8)					// checksum of "Restart"
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+bool IsRestart(int node)
+{
+	// get the node array	
+	Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+	Script::CScriptStructure *pNode=pNodeArray->GetStructure(node);
+	return	IsRestart(pNode);
+}
+
+int FindQualifyingNodes( uint32 type, uint32 *node_buffer, int first = -1 )
+{
+	int num_qualifiers;
+	int i;
+
+	num_qualifiers = 0;
+	i = 0;
+	
+	// get the node array	
+	Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+
+	// if there is no node array, then there are no restart points
+	if (!pNodeArray)
+	{
+		return 0;
+	}
+						 
+	if( first >= 0 )
+	{
+		i = first + 1;
+	}
+	// scan through it from the start point
+	while( i < (int)pNodeArray->GetSize() )
+	{
+		Script::CScriptStructure *pNode=pNodeArray->GetStructure(i);
+		if (IsRestart(pNode))					// checksum of "Restart"
+		{   
+			Script::CArray *pArray;
+			int j;
+
+			// The caller wants all restart points
+			if (type == 0)
+			{
+				node_buffer[num_qualifiers++] = i;
+				i++;
+				continue;
+			}
+
+			// Loop through supported game types, gathering matches
+			if (pNode->GetArray("restart_types", &pArray))
+			{
+				for (j = 0; j < (int)pArray->GetSize(); j++)
+				{
+					uint32 game_type;
+						
+					game_type = pArray->GetNameChecksum( j );
+					if( game_type == type )
+					{
+						node_buffer[num_qualifiers++] = i;
+					}
+				}
+			}
+		}		
+		i++;	
+	}	
+
+	return num_qualifiers;
+}
+
+
+// given the checksum of a type of restart
+// then get the node index  
+// if "first" is specified then resturn the first on after this								  
+// if "type"  is zero, then it will be ignored, and will return restarts of any type.
+// returns -1 if no restarts matching "type" are found.
+int GetRestartNode(uint32 type, int index, int first = -1)
+{
+	// decide where to start, based on the "first" parameter
+	int num_qualifiers;
+	uint32 qualifying_nodes[256];
+	
+	num_qualifiers = FindQualifyingNodes( type, qualifying_nodes, first );
+	if( num_qualifiers == 0 )
+	{
+		return -1;
+	}
+	
+	#ifdef	__NOPT_ASSERT__
+	if (num_qualifiers > 1 && (type == CRCD(0xbae0e4da,"multiplayer") || type == CRCD(0x9d65d0e7,"horse")))
+	{
+		// Scan through the nodes we've found, and check that there are not any
+		// that are too close to each other (< 6 feet)
+		bool ok = true;
+		for (int i=0;iGetChecksum("Name",&i_name_checksum);
+					uint32	j_name_checksum;
+					SkateScript::GetNode(qualifying_nodes[j])->GetChecksum("Name",&j_name_checksum);
+					printf ("%2.2f"" %s to %s \n",
+					 dist,
+					 Script::FindChecksumName(i_name_checksum),
+					 Script::FindChecksumName(j_name_checksum)
+					 );
+				}
+			}
+		}
+		if (!ok) printf ("\n\n\n");
+		Dbg_MsgAssert(ok, ("Restart nodes of type %s too close, see above for list\n",Script::FindChecksumName(type)));
+	}
+	#endif	
+	
+	//Ryan("index is %d, num_qualifiers %d\n", index, num_qualifiers);
+	
+	// If we've found matches, return the 
+	if( num_qualifiers > 0 )
+	{
+		if( index >= num_qualifiers )
+		{
+			return qualifying_nodes[index % num_qualifiers];
+		}
+		else
+		{
+			return qualifying_nodes[index];
+		}
+	}
+	return -1;
+}
+				
+				
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+
+
+
diff --git a/Code/Sk/Objects/restart.h b/Code/Sk/Objects/restart.h
new file mode 100644
index 0000000..2d3b4b3
--- /dev/null
+++ b/Code/Sk/Objects/restart.h
@@ -0,0 +1,76 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:			obj						 								**
+**																			**
+**	File name:		tmeplate.h  											**
+**																			**
+**	Created by:		00/00/00	-	mick									**
+**																			**
+*****************************************************************************/
+
+#ifndef __SK_OBJECTS_RESTART_H
+#define __SK_OBJECTS_RESTART_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Obj
+{
+
+						
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+bool IsRestart(Script::CScriptStructure pNode);
+bool IsRestart(int node);
+int GetRestartNode(uint32 type, int index, int first = -1);
+int FindQualifyingNodes( uint32 type, uint32 *node_buffer, int first = -1 );
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
+
+#endif	// __DIR_SUBDIR_FILE_H
+
+
diff --git a/Code/Sk/Objects/skater.cpp b/Code/Sk/Objects/skater.cpp
new file mode 100644
index 0000000..2d9321f
--- /dev/null
+++ b/Code/Sk/Objects/skater.cpp
@@ -0,0 +1,2638 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**							    											**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		skater.cpp												**
+**																			**
+**	Created:		01/25/00												**
+**																			**
+**	Description:	Skater													**
+**															
+****************************************************************************/
+  								 									  
+// start autoduck documentation
+// @DOC skater        																	  
+// @module skater | None
+// @subindex Scripting Database
+// @index script | skater
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#ifdef TESTING_GUNSLINGER
+#include 
+#include 
+#endif
+
+
+#include 
+#include 
+#include 				// for debug lines
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 						// for sGetMovableObjects
+#include 
+
+                                   
+#include  // for SKATE_TYPE_*
+#include 
+#include 	  	// for CCompetition
+#include 
+#include    // for special goal
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGC__
+#include "gfx/ngc/nx/nx_init.h"
+#endif		// __PLAT_NGC__
+
+
+#define	FLAGEXCEPTION(X)	SelfEvent(X)
+
+
+////////////////////////////////////////////////////////////////////////
+
+inline void	dodgy_test()
+{
+//		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+//		if (skate_mod->GetSkater(0))
+//		{
+//			skate_mod->GetSkater(0)->DodgyTest();
+//		}
+}
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+extern uint64	Gfx_LastVBlank;
+
+namespace Gfx
+{
+	void DebuggeryLines_CleanUp( void );		// just for debugging
+}
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+// All the CSkater code sits in the Obj namespace, along with other objects,
+// like peds and cars
+namespace Obj
+{
+
+bool DebugSkaterScripts=false;			// Set to true to dump out a lot of info about the skater's script
+										// generally you would set it with the script command SkaterDebugOn/Off
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/////////////////////////////////////////////////////////////////////////////
+// Physics patching, so physics vars can come from different structs
+// or globals, in a nice relaxed manner
+
+Script::CStruct * get_physics_struct()
+{
+	// if (Script::GetInteger(CRCD(0x9e86d1c1,"use_bike_physics")))
+	// {
+		// return Script::GetStructure(CRCD(0x9d728121,"bike_physics"));
+	// }
+	// else if (Script::GetInteger(CRCD(0x9ae06a83,"use_walk_physics")))
+	// {
+		// return Script::GetStructure(CRCD(0x8cbc89dd,"skater_physics"));
+	// }
+	// else
+	// {
+		return Script::GetStructure(CRCD(0x8cbc89dd,"skater_physics"));
+	// }
+}
+			  
+float GetPhysicsFloat(uint32 id, Script::EAssertType assert)
+{
+
+	Script::CStruct	*p_struct = get_physics_struct();	
+	float x;
+	if (p_struct->GetFloat(id,&x))
+	{
+		return x;
+	}
+
+	return Script::GetFloat(id,assert);
+}
+
+int GetPhysicsInt(uint32 id, Script::EAssertType assert)
+{
+	
+	Script::CStruct	*p_struct = get_physics_struct();	
+	int x;
+	if (p_struct->GetInteger(id,&x))
+	{
+		return x;
+	}
+
+	return Script::GetInteger(id,assert);
+}
+
+// End of physics patching
+/////////////////////////////////////////////////////////////////////////////
+
+
+// This Test generally not called
+// can stick code in here that will be called from dodgy_test
+// whcih gets called from every printf
+// generaly you'd have printfs enabled via something like DEBUG_POSITION
+void 	CSkater::DodgyTest()
+{
+		CFeeler feeler;
+		feeler.SetEnd(m_pos);
+		feeler.SetStart(m_pos + Mth::Vector(0.0f,100.0f,0.0f,0.0f));
+		if (feeler.GetCollision())
+		{
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+			float above = feeler.GetPoint()[Y] - m_pos[Y];
+			printf(" collision %.2f above me, normal = (%.2f,%.2f,%.2f)\n",above,feeler.GetNormal()[X],feeler.GetNormal()[Y],feeler.GetNormal()[Z]);
+#endif		// __NOPT_FINAL__
+		}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// K: This is a factored out code snippet that is used quite a bit.
+// It get's a pointer to this skater's score object and asserts if it's NULL.
+Mdl::Score *CSkater::GetScoreObject()
+{
+	Dbg_Assert(mp_skater_score_component);
+	return mp_skater_score_component->GetScore();
+}	
+
+// Just move the skater to the correct position and orientation for
+// this restart point
+// used to set intial position of other skaters on clients
+void CSkater::MoveToRestart(int node)
+{
+	Dbg_Assert(mp_skater_core_physics_component);
+	
+	m_matrix.Ident();
+	Script::CStruct *pNode = NULL;
+	if (node >= 0)
+	{
+		Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+		pNode=pNodeArray->GetStructure(node);
+		Dbg_MsgAssert(pNode,( "null restart"));
+		SkateScript::GetPosition( node, &m_pos );
+		#ifdef	CHEAT_RUBBER
+		m_rubber_pos = m_pos;
+		m_rubber_vel.Set();
+		#endif
+		
+		m_old_pos = m_pos;
+		mp_skater_core_physics_component->m_safe_pos = m_pos;
+		
+		SetTeleported();
+
+		Mth::Vector angles;
+		SkateScript::GetAngles(pNode, &angles);
+		m_matrix.SetFromAngles(angles);
+		
+		mp_skater_core_physics_component->ResetLerpingMatrix();
+		mp_skater_core_physics_component->m_display_normal = mp_skater_core_physics_component->m_last_display_normal = mp_skater_core_physics_component->m_current_normal = m_matrix[Y];
+		
+		#ifdef		DEBUG_DISPLAY_MATRIX
+		dodgy_test(); printf("%d: Setting display_matrix[Y][Y] to %f, [X][X] to %f\n",__LINE__,mp_skater_core_physics_component->m_lerping_display_matrix[Y][Y],mp_skater_core_physics_component->m_lerping_display_matrix[X][X]);
+		#endif
+	
+		// set the shadow to stick to his feet, assuming we are on the ground
+		// Note: These are used by the simple shadow, not the detailed one.
+		Dbg_Assert(mp_shadow_component)
+		mp_shadow_component->SetShadowPos( m_pos );
+		mp_shadow_component->SetShadowNormal( m_matrix[Y] );
+	
+		// update for the camera
+		m_display_matrix = mp_skater_core_physics_component->m_lerping_display_matrix;
+	}
+}
+
+void CSkater::SkipToRestart(int node, bool walk)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+#	ifdef	DEBUG_TRIGGERS			
+	dodgy_test(); printf("SkipToRestart(%d)\n",node);
+#	endif
+
+    GetSkaterPhysicsControlComponent()->NotifyReset();
+	
+	if( m_local_client )
+	{
+		// The Skater might have been in the middle of a move that disabled his input, so re-enable it
+		// and also enable the camera control
+		mp_input_component->EnableInput();
+		// Also player should ot be paused
+		UnPause();
+
+        if (mp_stats_manager_component)
+        {
+            mp_stats_manager_component->m_restarted_this_frame = true;
+        }
+
+        /*		
+        // If the player had locked the camera in lookaround, clear it back.
+		if ( mp_skaterCamera )
+		{
+			mp_skaterCamera->ClearLookaroundLock();
+		}
+		*/
+		//printf ("STUBBED: Skater.cpp line %d - not clearing lookaroundlock\n",__LINE__);
+
+		// Reset the skater back to a collidable state
+		if( gamenet_man->InNetGame())
+		{   
+			CFuncs::ScriptNotifyBailDone( NULL, NULL );
+			mp_skater_core_physics_component->SetFlagFalse( IS_BAILING );
+		}
+	}
+
+    if (mp_vibration_component)
+	{
+		mp_vibration_component->Reset();
+	}
+
+	if (node != -1)
+	{
+		MoveToRestart(node);
+	}
+	       
+	ResetPhysics(walk);
+
+	dodgy_test(); //printf("Setting animation flipped to %d in SkipToRestart\n",mp_skater_core_physics_component->GetFlag(FLIPPED));
+	
+	if (IsLocalClient())
+	{
+		// reset any flippedness of animation
+		GetSkaterFlipAndRotateComponent()->ApplyFlipState();
+	}
+
+	// Only local skaters should run the script
+	if( m_local_client )
+	{
+		if (!m_viewing_mode)
+		{
+			UpdateCameras( true );
+			
+			Obj::CCompositeObject *p_obj = GetCamera();
+			if (p_obj)
+			{
+				p_obj->Update();	 // Not the best way of doing it...
+			}
+
+			
+		}
+		if (node != -1)
+		{
+			Script::CStruct *pNode = NULL;	
+			Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+			pNode=pNodeArray->GetStructure(node);
+			// Now run any TriggerScript associated with this node
+			uint32 script;
+			if (pNode->GetChecksum(0x2ca8a299,&script))
+			{
+	#			ifdef	DEBUG_TRIGGERS			
+				dodgy_test(); printf("%d: Restart Node Script triggered, script name %s, node %d\n",(int)Tmr::GetVblanks(),(char*)Script::FindChecksumName(script),node);
+	#			endif			
+				SpawnAndRunScript(script,node);
+			}
+		}	  	
+	}
+			
+	SetTeleported();
+
+	if(	( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x6c5ff266,"netctf") ) ||
+		( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x6ef8fda0,"netking") ) ||
+		( skate_mod->GetGameMode()->GetNameChecksum() == CRCD(0x5d32129c,"king") ))
+	{
+		Net::Client* client;
+		GameNet::PlayerInfo* player;
+		Net::MsgDesc msg_desc;
+
+		client = gamenet_man->GetClient( m_skater_number );
+		Dbg_Assert( client );
+		player = gamenet_man->GetPlayerByObjectID( GetID());
+		Dbg_Assert( player );
+		player->MarkAsRestarting();
+
+		msg_desc.m_Id = GameNet::MSG_ID_MOVED_TO_RESTART;
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		client->EnqueueMessageToServer( &msg_desc );
+	}
+
+}
+
+//////////////////////////////////////////////////////////////////////////
+// START of "Stat" code
+
+
+float	CSkater::GetStat(EStat stat)
+{
+	
+
+	float value;			   
+
+	float	override;
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	override = skate_mod->GetStatOverride();
+	
+	if (override != 0.0f)
+	{
+		value = override;
+	}
+	else
+	{
+		if (m_stat[stat] == -1.0f)
+		{
+			value =  GetPhysicsFloat(CRCD(0x6357d57c,"Skater_Default_Stats"));
+		}
+		else
+		{
+			value =  m_stat[stat];
+		}
+	}
+
+	// In a network game, always set stats to 10	
+//	if( GameNet::Manager::Instance()->InNetGame() )
+//	{
+//		value = 10.0f;
+//	}
+
+	if( GameNet::Manager::Instance()->InNetGame())
+	{
+		GameNet::PlayerInfo* player;
+
+		player = GameNet::Manager::Instance()->GetPlayerByObjectID( GetID());
+		if( player )
+		{
+			if( player->IsKing())
+			{
+				value = 1.0f;
+			}
+			else if( player->HasCTFFlag())
+			{
+				value = 6.0f;
+			}
+			else
+			{
+				value = 10.0f;
+			}
+		}
+		else
+		{
+			value = 10.0f;
+		}
+	}
+	else if( CFuncs::ScriptInSplitScreenGame( NULL, NULL ))
+	{
+		GameNet::PlayerInfo* player;
+
+		player = GameNet::Manager::Instance()->GetPlayerByObjectID( GetID());
+		if( player )
+		{
+			if( player->IsKing())
+			{
+				value = 1.0f;
+			}
+		}
+	}
+
+	if( GameNet::Manager::Instance()->InNetGame())
+	{
+		GameNet::PlayerInfo* player;
+
+		player = GameNet::Manager::Instance()->GetPlayerByObjectID( GetID());
+		if( player )
+		{
+			if( player->IsKing())
+			{
+				value = 1.0f;
+			}
+			else if( player->HasCTFFlag())
+			{
+				value = 6.0f;
+			}
+			else
+			{
+				value = 10.0f;
+			}
+		}
+		else
+		{
+			value = 10.0f;
+		}
+	}
+	else if( CFuncs::ScriptInSplitScreenGame( NULL, NULL ))
+	{
+		GameNet::PlayerInfo* player;
+
+		player = GameNet::Manager::Instance()->GetPlayerByObjectID( GetID());
+		if( player )
+		{
+			if( player->IsKing())
+			{
+				value = 1.0f;
+			}
+		}
+	}
+
+	if (CHEAT_STATS_13)
+	{
+		value = 15.0f;
+	}
+
+	Dbg_Assert(mp_skater_score_component);
+	if (mp_skater_score_component->GetScore()->GetSpecialState())
+	{
+		value += 3.0f;
+	}
+
+	return value;
+	
+}
+
+
+void	CSkater::SetStat(EStat stat, float value)
+{
+
+	// factor in the handicap if any...
+	if ( CFuncs::ScriptInSplitScreenGame( NULL, NULL ) )
+	{
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		value += skate_mod->GetHandicap( m_id );
+
+		// clamp it to the min, max values
+		if ( value > 10.0f ) value = 10.0f;
+		if ( value < 0.0f ) value = 0.0f;
+	}
+	
+	m_stat[stat] = value;
+}
+
+
+// Gets a stat value given a pointer to the stat structure.
+float	CSkater::GetScriptedStat(Script::CStruct *pSS)
+{
+	
+	Script::CPair range;
+	Script::CPair switch_range;
+	int stat;
+	float limit;
+
+    Dbg_MsgAssert(pSS,("NULL pSS sent to GetScriptedStat"));
+	
+	if (!pSS->GetPair(NONAME,&range))
+	{
+		Dbg_MsgAssert(0,("range pair not found in structure."));		
+	}
+
+	float stat_value;
+	if (!pSS->GetInteger(NONAME,&stat))
+	{
+		stat_value = 10.0f;	   		// default to 10 if no stats
+	}
+	else
+	{
+		stat_value = GetStat((CSkater::EStat)stat);
+	}
+	
+	float lower = range.mX;
+	float upper = range.mY;  
+	float len = upper - lower;
+	float value = lower + (len * stat_value / 10);
+
+
+	if (mp_skater_core_physics_component->IsSwitched())		// if skating in switch stance
+	{
+		if (pSS->GetPair(0x9016b4e7,&switch_range))	   		// switch
+		{
+			float	switch_stat = GetStat(STATS_SWITCH);   	// returns 0..13
+			float 	mult = switch_range.mX + (switch_range.mY - switch_range.mX) * switch_stat/10.0f;
+			
+			if (mult <0.0f) mult = 0.0f;
+			if (mult >1.0f) mult = 1.0f;		// this ensures we are never Better in switch
+
+			value *= mult;			
+			
+		}
+	}
+	
+	Game::GOAL_MANAGER_DIFFICULTY_LEVEL level = Mdl::Skate::Instance()->GetGoalManager()->GetDifficultyLevel();
+	if (level != Game::GOAL_MANAGER_DIFFICULTY_MEDIUM)
+	{
+		Script::CPair diff;	
+		if (pSS->GetPair(CRCD(0xba8fb854,"diff"),&diff))
+		{
+			if (level == Game::GOAL_MANAGER_DIFFICULTY_LOW)
+			{
+				value *= diff.mX;
+			}
+			else if (level == Game::GOAL_MANAGER_DIFFICULTY_HIGH)
+			{
+				value *= diff.mY;
+			}
+		}
+	}	
+	
+	// if there is a limit, then clamp the value to it
+	// accounting for the direction of the range, this may be a high limit
+	// or a low limit, but applies to the value for higher values of stat_value.
+	// so if lower > upper, then it's a low limit
+	if (pSS->GetFloat(0x8069179f/*"limit"*/,&limit))
+	{
+		if (lower < upper)
+		{
+			if (value > limit)
+			{
+				value = limit;
+			}
+		}
+		else
+		{
+			if (value < limit)
+			{
+				value = limit;
+			}
+		}
+	}
+	else
+	{
+		// there is no limit, so we do nothing.
+	}
+	
+	return value;
+}
+
+// Gets a stat value given the name of the stat, where the stat is defined as a global structure.
+float	CSkater::GetScriptedStat(const uint32 checksum)
+{
+	
+   // Get the structure that contains the range, stat and limit
+	Script::CStruct *pSS = NULL; 
+	Script::CStruct *pPhysics; 
+	// Try to get it from the skater/biker physics structure first
+	pPhysics = get_physics_struct();
+	if (! pPhysics->GetStructure(checksum,&pSS))
+	{
+		pSS= Script::GetStructure(checksum);
+	}
+	
+	Dbg_MsgAssert(pSS, ("State %s not found", Script::FindChecksumName(checksum)));
+
+	// These asserts are also in the other GetScriptedStat,
+	// but included here too so that it can print the name of the structure.
+	#ifdef __NOPT_ASSERT__
+	Script::CPair range;
+	if (!pSS->GetPair(NONAME,&range))
+	{
+		Dbg_MsgAssert(0,("range pair not found in %s.",Script::FindChecksumName(checksum)));		
+	}
+
+	int stat;
+	if (!pSS->GetInteger(NONAME,&stat))
+	{
+		Dbg_MsgAssert(0,("stat not found in %s.",Script::FindChecksumName(checksum)));		
+	}
+	#endif
+		
+	return GetScriptedStat(pSS);
+}
+
+// Gets a stat value given the name of the stat, where the stat is defined as a global structure.
+
+
+// Gets a stat value given the name of the stat within a structure pSetOfStats.
+// This is used in manual.cpp.
+// Different sets of control & wobble stats are stored in structures in physics.q
+float CSkater::GetScriptedStat(uint32 Checksum, Script::CStruct *pSetOfStats)
+{
+	Dbg_MsgAssert(pSetOfStats,("NULL pSetOfStats."));
+	Script::CStruct *pSS=NULL;
+		pSetOfStats->GetStructure(Checksum,&pSS);
+	Dbg_MsgAssert(pSS,("Could not find stat called %s in pSetOfStats",Script::FindChecksumName(Checksum)));
+	
+	return GetScriptedStat(pSS);
+}
+
+
+// END of "Stat" code
+/////////////////////////////////////////////////////////////////////////////
+
+void CSkater::MoveToRandomRestart( void )
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	int node, retry;
+	
+	
+
+	// Try to go to a different restart point than last time.  But still, limit
+	// the number of retries just in case there is only one restart node in the level
+	retry = 0;
+	do
+	{
+		node = skate_mod->find_restart_node( Mth::Rnd( Mdl::Skate::vMAX_SKATERS ));
+		retry++;
+	} while(( node == m_last_restart_node ) && ( retry < 20 ));
+
+	m_last_restart_node = node;
+
+	Dbg_Printf( "In MoveToRandomRestart: Node %d\n", node );
+    
+	if (node != -1)
+	{
+		SkipToRestart(node);   					   
+	}
+}
+
+void CSkater::UpdateCameras( bool instantly )
+{
+//	printf ("%d: SUTUBBBEDDDDDDDDDDDDDDDDDDDDDDDD  UpdateCameras(%d)\n",__LINE__,instantly);	
+	if (instantly)
+	{
+		SetTeleported( false );
+	}
+}
+
+void CSkater::UpdateCamera( bool instantly )
+{
+//	printf ("%d: SUTUBBBEDDDDDDDDDDDDDDDDDDDDDDDD  UpdateCamera(%d)\n",__LINE__,instantly);	
+	if (instantly)
+	{
+		SetTeleported( false );
+	}
+}
+
+Gfx::Camera* CSkater::GetActiveCamera( void )
+{
+
+	return Nx::CViewportManager::sGetActiveCamera(m_skater_number);
+}
+	
+CCompositeObject* CSkater::GetCamera()
+{
+	uint32 camera_name = CRCD(0x967c138c,"SkaterCam0");
+	if (m_skater_number == 1)
+	{
+		camera_name = CRCD(0xe17b231a,"SkaterCam1");
+	}
+	return static_cast< CCompositeObject* >(CCompositeObjectManager::Instance()->GetObjectByID(camera_name));
+}
+
+void CSkater::SetViewMode( EViewMode view_mode )
+{
+	
+	 Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+	//HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+
+	if( m_viewing_mode == view_mode )
+	{
+		return;
+	}
+	
+
+	// don't want to do it to skaters that don't have this component
+	// (which would be the nonlocal skaters)	
+	if (!GetSkaterPhysicsControlComponent())
+	{
+		return;
+	}
+	
+	m_viewing_mode = view_mode;
+			  
+	switch ( m_viewing_mode )
+	{
+		// Normal skaing around with camera
+		case GAMEPLAY_SKATER_ACTIVE:
+		{
+			// Regular gameplay, panel and console on
+			if (GetPhysicsInt(CRCD(0x9816e1e3,"ScreenShotMode")))
+			{
+				dodgy_test(); printf("UnPausing game\n");
+				front->PauseGame(false);
+			}
+		}
+		break;
+
+		// select+X pressed once, skater is frozen										  
+		case VIEWER_SKATER_PAUSED:
+		{
+			if (GetPhysicsInt(CRCD(0x9816e1e3,"ScreenShotMode")))
+			{
+				dodgy_test(); printf("Freezing game\n");
+				front->PauseGame(true);
+			}
+			else
+			{
+				GetSkaterPhysicsControlComponent()->SuspendPhysics(true);
+			}
+		}
+		break;
+		
+		// select+X pressed again, skater skates around					 
+		case VIEWER_SKATER_ACTIVE:
+		{
+			// pause the  game, turn off console
+			if (GetPhysicsInt(CRCD(0x9816e1e3,"ScreenShotMode")))
+			{
+				// Dan: should we skip VIEWER_SKATER_ACTIVE then ScreenShotMode is true?
+			}
+			else
+			{
+				GetSkaterPhysicsControlComponent()->SuspendPhysics(false);
+			}
+		}
+		break;
+		
+		
+		default:
+		Dbg_Assert( 0 );
+	}
+}
+
+void CSkater::Pause()
+{
+	if (!mPaused && m_local_client )
+	{
+		MARK;
+		
+		// Pause the base object
+		CMovingObject::Pause();
+
+		// Switch off the meters. They will come back on by themselves when the skater is unpaused
+		// and his logic is called again.
+		Dbg_Assert(mp_skater_score_component)	   // Might not have a panel in the front end
+		
+		// NOTE:  These two lines make the balance meter vanish in "Screenshot" mode
+		// But are needed for regular game pausing
+		mp_skater_score_component->GetScore()->SetBalanceMeter(false);
+		mp_skater_score_component->GetScore()->SetManualMeter(false);
+		
+		Script::RunScript(CRCD(0x1277e3fd,"pause_run_timer"), NULL);
+		
+		// Pause the input device (such as vibrations)
+		if (mp_input_component)
+		{
+			mp_input_component->PauseDevice();
+		}
+        
+		// Pause the game sounds
+		Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+		sfx_manager->PauseSounds();
+	}
+}
+
+void CSkater::UnPause()
+{
+	if (mPaused && m_local_client)
+	{
+		MARK;
+		
+		CMovingObject::UnPause();
+		
+		if (mp_input_component)
+		{
+			mp_input_component->UnPauseDevice();
+		}
+
+		CSkaterLoopingSoundComponent *p_sound_loop_component = GetSkaterLoopingSoundComponent();
+		if (p_sound_loop_component)
+		{
+			p_sound_loop_component->UnPause();
+		}
+	}	
+}
+
+// Ken: Factored this out so that the replay code can call it too.
+void CSkater::UpdateShadow(const Mth::Vector& pos, const Mth::Matrix& matrix)
+{
+#ifndef __PLAT_NGPS__
+	static Mth::Vector ground_dir( 0.8f, -0.8f, 0.3f );
+	static Mth::Vector air_dir( 0.0f, -1.0f, 0.0f );
+	
+	// If lights are active, set the ground direction to be that of the primary light.
+	if( GetModel())
+	{
+		Nx::CModelLights *p_lights = GetModel()->GetModelLights();
+		if( p_lights )
+		{
+			ground_dir = p_lights->GetLightDirection( 0 ) * -1.0f;
+
+			if( ground_dir[Y] > -0.65f )
+			{
+				// Lighting direction is too shallow, leading to overly extended shadows. Limit direction.
+				// In the new vector, we know we want the [Y] component to be -0.65, so it follows that we
+				// want ( [X]^2 + [Z]^2 ) to be ( 1.0 - ( -0.65 ^2 )), or 0.5775. First, figure out the
+				// current value of ( [X]^2 + [Z]^2 ).
+				float xz_squared	= ( ground_dir[X] * ground_dir[X] ) + ( ground_dir[Z] * ground_dir[Z] );
+				float multiple		= sqrtf( 0.5775f / xz_squared );
+				ground_dir[X]		= ground_dir[X] * multiple;
+				ground_dir[Y]		= -0.65f;
+				ground_dir[Z]		= ground_dir[Z] * multiple;
+			}
+		}
+	}
+	
+	bool ground = true;
+	if( mp_skater_physics_control_component && ( mp_skater_physics_control_component->IsSkating()))
+	{
+		ground = mp_skater_core_physics_component->GetState() == GROUND;
+	}
+	else if( mp_walk_component )
+	{
+		ground = mp_walk_component->GetState() != CWalkComponent::WALKING_AIR;
+	}
+	
+	// Interpolate between the two shadow directions based on Skater state.
+	if( ground )
+	{
+		m_air_timer = ( m_air_timer > 0 ) ? ( m_air_timer - 1 ) : 0;
+	}
+	else
+	{
+		m_air_timer = ( m_air_timer < 16 ) ? ( m_air_timer + 1 ) : 16;
+	}
+
+	Mth::Vector dir = Mth::Lerp(ground_dir, air_dir, m_air_timer * (1.0f / 16.0f));
+	// Mth::Vector dir = ground_dir + (( air_dir - ground_dir ) * ((float)m_air_timer / 16.0f ));
+	mp_shadow_component->SetShadowDirection( dir );
+#ifdef __PLAT_NGC__
+	NxNgc::EngineGlobals.skater_shadow_dir.x = dir[X];
+	NxNgc::EngineGlobals.skater_shadow_dir.y = dir[Y];
+	NxNgc::EngineGlobals.skater_shadow_dir.z = dir[Z];
+	NxNgc::EngineGlobals.skater_height = GetSkaterStateComponent()->GetHeight();
+
+#endif		// __PLAT_NGC__ 
+
+#endif
+
+	/*
+	GJ:  The following has been moved to CShadowComponent::Update()
+	
+	Mth::Vector shadow_target_pos = pos + ( matrix.GetUp() * 36.0f );
+
+	if ( pShadowComponent->mp_shadow )
+	{
+		if (pShadowComponent->mp_shadow->GetShadowType()==Gfx::vSIMPLE_SHADOW)
+		{
+			pShadowComponent->mp_shadow->UpdatePosition(m_shadow_pos,m_matrix,pShadowComponent->m_shadow_normal);
+		}
+		else
+		{
+			pShadowComponent->mp_shadow->UpdatePosition(shadow_target_pos); // at this point m_pos is the same as m_pos
+		}
+	}
+	*/
+}
+
+// K: Added for use by replay code so that it can playback pad vibrations
+SIO::Device *CSkater::GetDevice()
+{
+	// Stubbed for now
+	// Dbg_MsgAssert(m_input_handler,("NULL m_input_handler"));
+	// Dbg_MsgAssert(m_input_handler->m_Device,("NULL m_input_handler->m_Device ?"));
+	// return m_input_handler->m_Device;
+	return NULL;
+}
+
+int			CSkater::GetHeapIndex( void )
+{
+	return m_heap_index;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+CSkater::CSkater ( int player_num, CBaseManager* p_obj_manager, bool local_client, int obj_id,
+						int heap_num, bool should_tristrip ) : m_link_node( this ), m_local_client( local_client )
+{
+	m_type = SKATE_TYPE_SKATER;
+	
+	m_in_world = false;
+	m_heap_index = heap_num;
+	m_id = obj_id;
+
+	// Mick:  Set up the Skater and Skater2 aliases	
+	// Note we use heap_num rather than player_num, as player_num is unreliable in network games
+	if (heap_num == 0)
+	{
+		Obj::CTracker::Instance()->AddAlias(CRCD(0x5b8ab877,"Skater"),this);		
+	}
+	if (heap_num == 1)
+	{
+		Obj::CTracker::Instance()->AddAlias(CRCD(0x6ed3fa7,"Skater2"),this);				
+	}
+	
+	// low priority (but not lower than cameras) ensures that moving objects we may skate on or
+	// cars we may drive will be updated before the skater each frame
+	m_node.SetPri(-500);
+	p_obj_manager->RegisterObject(*this);
+	
+	m_skater_number = player_num;
+	
+	DUMPI(m_skater_number);
+	DUMPI(m_id);
+
+	SetProfileColor(0x00c0c0);				// yellow = skater
+	
+	mp_animation_component=NULL;
+	mp_model_component=NULL;
+	mp_shadow_component=NULL;
+	mp_trick_component=NULL;
+	mp_vibration_component=NULL;
+	mp_input_component=NULL;
+	mp_skater_sound_component=NULL;
+	mp_skater_gap_component=NULL;
+	mp_skater_score_component=NULL;
+	mp_skater_core_physics_component=NULL;
+    mp_stats_manager_component=NULL;
+	mp_walk_component=NULL;
+	mp_skater_physics_control_component=NULL;
+
+	// create the following components before CMovingObject creates components
+	
+	Script::CStruct* component_struct = new Script::CStruct;
+	
+	component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERSTATE);
+	CreateComponentFromStructure(component_struct);
+																  
+	if (m_local_client)
+	{
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_INPUT);
+		CreateComponentFromStructure(component_struct);
+		mp_input_component = GetInputComponent();
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERSCORE);
+		CreateComponentFromStructure(component_struct);
+		mp_skater_score_component = GetSkaterScoreComponent();
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERMATRIXQUERIES);
+		CreateComponentFromStructure(component_struct);
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_TRICK);
+		CreateComponentFromStructure(component_struct);
+		mp_trick_component = GetTrickComponent();
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERPHYSICSCONTROL);
+		CreateComponentFromStructure(component_struct);
+		mp_skater_physics_control_component=GetSkaterPhysicsControlComponent();
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERCOREPHYSICS);
+		CreateComponentFromStructure(component_struct);
+		mp_skater_core_physics_component = GetSkaterCorePhysicsComponent();
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERROTATE);
+		CreateComponentFromStructure(component_struct);
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERGAP);
+		CreateComponentFromStructure(component_struct);
+		mp_skater_gap_component = GetSkaterGapComponent();
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERADJUSTPHYSICS);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_TRIGGER);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERFINALIZEPHYSICS);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERCLEANUPSTATE);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_WALK);
+		CreateComponentFromStructure(component_struct);
+		mp_walk_component = GetWalkComponent();
+		
+#		ifdef TESTING_GUNSLINGER
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_RIDER);
+		CreateComponentFromStructure(component_struct);
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_WEAPON);
+		CreateComponentFromStructure(component_struct);
+#		endif
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERLOCALNETLOGIC);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERENDRUN);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERBALANCETRICK);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERLOOPINGSOUND);
+		CreateComponentFromStructure(component_struct);
+
+        component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_STATSMANAGER);
+		CreateComponentFromStructure(component_struct);
+        mp_stats_manager_component = GetStatsManagerComponent();
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_MOVABLECONTACT);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERRUNTIMER);
+		CreateComponentFromStructure(component_struct);
+	}
+	else
+	{
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERSCORE);
+		CreateComponentFromStructure(component_struct);
+		mp_skater_score_component = GetSkaterScoreComponent();
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERNONLOCALNETLOGIC);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERCLEANUPSTATE);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERFLOATINGNAME);
+		CreateComponentFromStructure(component_struct);
+
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERENDRUN);
+		CreateComponentFromStructure(component_struct);
+		
+		component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERLOOPINGSOUND);
+		CreateComponentFromStructure(component_struct);
+	}
+	
+	component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERSTATEHISTORY);
+	CreateComponentFromStructure(component_struct);
+	mp_skater_state_history_component = GetSkaterStateHistoryComponent();
+	
+	component_struct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_SKATERFLIPANDROTATE);
+	CreateComponentFromStructure(component_struct);
+									
+    delete component_struct;
+}
+
+void CSkater::Construct ( Obj::CSkaterProfile* pSkaterProfile)
+{
+	// only skater 0 should update the camera in the
+	// skateshop (otherwise, reloading skater 1 would
+	// cause a momentary flicker)
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	bool should_create_cam = ( !skate_mod->GetGameMode()->IsFrontEnd() ) || ( m_skater_number == 0 );
+
+	if( m_local_client && should_create_cam )
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+
+		uint32	name = CRCD(0x967c138c,"SkaterCam0");
+		if (m_skater_number == 1)
+		{
+			name = CRCD(0xe17b231a,"SkaterCam1");
+		}
+		
+			   
+		CCompositeObject *p_cam_object = (CCompositeObject*)CCompositeObjectManager::Instance()->GetObjectByID(name);
+		if (p_cam_object)
+		{
+			// Camera already exists, was being used as an observer camera,
+			// so just attache it back to this skater
+			GetSkaterCameraComponentFromObject(p_cam_object)->SetSkater( this );
+			GetWalkCameraComponentFromObject(p_cam_object)->SetSkater( this );
+		}
+		else
+		
+		{
+			// Add the parameters for the skater camera component		
+			// so it can get me as the target
+			Script::CStruct * p_component_params = new Script::CStruct;
+			p_component_params->AddChecksum(CRCD(0x4c48da58,"CameraTarget"), GetID());
+			// p_component_params->AddChecksum(CRCD(0x1313e3c,"ProximTriggerTarget"), GetID());
+			p_component_params->AddChecksum(CRCD(0xa1dc81f9,"name"),name);
+	
+			p_cam_object = CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(
+													Script::GetArray("skatercam_composite_structure"),p_component_params);
+			delete	p_component_params;
+		}		
+		
+		GetWalkComponent()->SetAssociatedCamera(p_cam_object);
+		CFuncs::SetActiveCamera(name, m_skater_number, false);
+		
+#if 0 
+// proximtrigger components are deprecated  	
+		// point the viewer camera's proximity trigger at the skater
+		// if it exists, which it might not, if the viewer is not active (Like for the XBox)
+		CObject* p_viewer_cam = CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0xeb17151b,"viewer_cam"));
+		if (p_viewer_cam)
+		{
+			GetProximTriggerComponentFromObject(static_cast< CCompositeObject* >(p_viewer_cam))->SetScriptTarget(this);
+		}
+#endif		
+		
+		// Lock the object so it won't get deleted.
+		p_cam_object->SetLockOn();
+		
+			// Set default mode, so "behind" etc, is set tosomething sensible 
+		GetSkaterCameraComponentFromObject(p_cam_object)->SetMode(CSkaterCameraComponent::SKATERCAM_MODE_NORMAL_MEDIUM, 0.0f);
+
+		/*
+		CCameraComponent *p_cam_component				= new CCameraComponent();
+		CSkaterCameraComponent *p_skatercam_component	= new CSkaterCameraComponent();
+
+		// We want the CameraComponent processed *after* the SkaterCameraComponent.
+		p_cam_object->AddComponent( p_skatercam_component );
+		p_cam_object->AddComponent( p_cam_component );
+
+		p_skatercam_component->InitFromStructure( NULL );
+		p_cam_component->InitFromStructure( NULL );
+
+		p_skatercam_component->SetSkater( this );
+		*/
+		
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	// Set up the AI script.
+	Dbg_MsgAssert(mp_script==NULL,("mp_script not NULL?"));
+
+	mp_script=new Script::CScript;
+
+
+// SkaterInit used to be run there --------------> *	
+	
+	Script::CStruct* pSkaterInfoParams = Script::GetStructure(CRCD(0x962e5cf7,"skater_asset_info"));
+
+	uint32 skeleton_name = CRCD(0x5a9d2a0a,"human");
+	if (pSkaterInfoParams)
+	{
+		pSkaterInfoParams->GetChecksum("skater_skeleton",&skeleton_name,Script::NO_ASSERT);
+	}
+    
+//--------------------------------------------------------
+	// components need to be created in a specific
+	// order...  the model needs to come as late as
+	// possible, so that all the other components can
+	// play around with its display matrix.  (maybe 
+	// should have some kind of priority system for 
+	// components)?
+	
+	MovingObjectCreateComponents();
+
+	Dbg_MsgAssert( !GetAnimationComponent(), ( "Animation component already exists" ) );
+	Script::CStruct* pAnimationStruct = new Script::CStruct;
+	pAnimationStruct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_ANIMATION );
+	pAnimationStruct->AddChecksum( CRCD(0x5b8c6dc2,"AnimEventTableName"), CRCD(0x3fec31a6,"SkaterAnimEventTable") );
+	CreateComponentFromStructure(pAnimationStruct, NULL);
+    delete pAnimationStruct;
+	mp_animation_component = GetAnimationComponent();
+		
+	Dbg_MsgAssert( !GetSkeletonComponent(), ( "Skeleton component already exists" ) );
+	Script::CStruct* pSkeletonStruct = new Script::CStruct;
+	pSkeletonStruct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_SKELETON );
+    pSkeletonStruct->AddChecksum( CRCD(0x222756d5,"skeleton"), skeleton_name );
+    pSkeletonStruct->AddInteger( CRCD(0xd3982061,"max_bone_skip_lod"), 0 );
+	CreateComponentFromStructure(pSkeletonStruct, NULL);
+    delete pSkeletonStruct;
+	
+	#if 0
+	// Dan: testing
+	Script::CStruct* pStruct = new Script::CStruct;
+	pStruct->AddChecksum(CRCD(0xb6015ea8,"component"), CRC_RIBBON);
+    pStruct->AddChecksum(CRCD(0xcab94088,"bone"), CRCD(0x7ee14cfe,"Bone_Wrist_L"));
+    pStruct->AddInteger(CRCD(0x69feef91,"num_links"), 15);
+    pStruct->AddFloat(CRCD(0x9f4625c2,"link_length"), 5.0f);
+    pStruct->AddChecksum(CRCD(0x99a9b716,"color"), MAKE_RGB(0, 0, 255));
+	CreateComponentFromStructure(pStruct, NULL);
+    delete pStruct;
+	#endif
+	
+//--------------------------------------------------------
+ 	
+	uint32 anim_script_name = CRCD(0x501949bd,"animload_human");
+	if (pSkaterInfoParams)
+	{
+		pSkaterInfoParams->GetChecksum(CRCD(0x8b7488e,"skater_anims"),&anim_script_name,Script::NO_ASSERT);
+	}
+	mp_animation_component->SetAnims( anim_script_name );
+	mp_animation_component->EnableBlending( true );
+	
+	// Create an empty model 
+	Dbg_MsgAssert( !GetModelComponent(), ( "Model component already exists" ) );
+	Script::CStruct* pModelStruct = new Script::CStruct;
+	pModelStruct->AddChecksum( NONAME, CRCD(0x10079f2d,"UseModelLights") );
+	
+	// Enables the shadow volume on any meshes that get added in the future
+	int shadowVolumeEnabled = 0;
+	if ( m_local_client )
+	{
+		shadowVolumeEnabled = true;
+	}
+	pModelStruct->AddInteger( CRCD(0x2a1f92c0,"ShadowVolume"), shadowVolumeEnabled ); 
+	
+	pModelStruct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_MODEL );
+	CreateComponentFromStructure(pModelStruct, NULL);
+	delete pModelStruct;
+	mp_model_component = GetModelComponent();
+	
+	// create the model
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterGeomHeap(m_heap_index));
+	
+	Ass::CAssMan * ass_man =  Ass::CAssMan::Instance();
+	bool defaultPermanent = ass_man->GetDefaultPermanent();
+	ass_man->SetDefaultPermanent( false );
+	
+	// load up the model from a profile
+	mp_model_component->InitModelFromProfile( pSkaterProfile->GetAppearance(), false, m_heap_index );
+	
+	ass_man->SetDefaultPermanent( defaultPermanent );
+	
+	Mem::Manager::sHandle().PopContext();
+
+	// Create Model lights
+	// (This has been moved to the CModelComponent)
+//	GetModel()->CreateModelLights();
+//	Nx::CModelLights *p_lights = GetModel()->GetModelLights();
+//	p_lights->SetPositionPointer(&m_pos);
+
+	// now that the model has been created,
+	// create a shadow component for the skater
+	Dbg_MsgAssert( !GetShadowComponent(), ( "Shadow component already exists" ) );
+	Script::CStruct* pShadowStruct = new Script::CStruct;
+	pShadowStruct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_SHADOW );
+    pShadowStruct->AddChecksum( CRCD(0x9ac24b18,"shadowType"), (CRCD(0x76a54cd1,"detailed")) );
+	CreateComponentFromStructure(pShadowStruct, NULL);
+    delete pShadowStruct;
+	mp_shadow_component = GetShadowComponent();
+
+	if ( m_local_client )
+	{
+		Dbg_MsgAssert( !GetVibrationComponent(), ( "Vibration component already exists" ) );
+		Script::CStruct* p_vibration_struct = new Script::CStruct;
+		p_vibration_struct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_VIBRATION );
+		CreateComponentFromStructure(p_vibration_struct, NULL);
+		delete p_vibration_struct;
+		mp_vibration_component = GetVibrationComponent();
+
+		Dbg_MsgAssert( !GetSkaterStancePanelComponent(), ( "StancePanel component already exists" ) );
+		Script::CStruct* p_stancepanel_struct = new Script::CStruct;
+		p_stancepanel_struct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_SKATERSTANCEPANEL );
+		CreateComponentFromStructure(p_stancepanel_struct, NULL);
+		delete p_stancepanel_struct;
+	}
+	else
+	{
+		mp_vibration_component = NULL;
+	}
+	
+	if ( m_local_client )
+	{
+		Dbg_MsgAssert( !GetSkaterSoundComponent(), ( "SkaterSound component already exists" ) );
+		Script::CStruct* p_skater_sound_struct = new Script::CStruct;
+		p_skater_sound_struct->AddChecksum( CRCD(0xb6015ea8,"component"), CRC_SKATERSOUND );
+		CreateComponentFromStructure(p_skater_sound_struct, NULL);
+		delete p_skater_sound_struct;
+		mp_skater_sound_component = GetSkaterSoundComponent();
+	}
+	
+	// Finalize Components
+	Finalize();									
+	
+	// Set up the controller preferences.
+	Dbg_MsgAssert(m_skater_number>=0 && m_skater_numberm_auto_kick = skate_mod->mp_controller_preferences[m_skater_number].AutoKickOn;
+		mp_skater_core_physics_component->m_spin_taps = skate_mod->mp_controller_preferences[m_skater_number].SpinTapsOn;
+		mp_vibration_component->SetActiveState(skate_mod->mp_controller_preferences[m_skater_number].VibrationOn);
+	}
+
+
+	// Mick:  Run the SkaterInit Script
+	// this used to be done before all the components are created, but it makes more sense to do it after
+	// and the old way boke after I moved the "SetSkaterCamOverride" into SkaterCameraComponent
+	if ( m_local_client )
+	{
+		// Start running the ground AI.
+		#ifdef __NOPT_ASSERT__
+		mp_script->SetCommentString("Created in CSkater constructor, assigned to CSkater::mp_script");
+		#endif
+		mp_script->SetScript("SkaterInit",NULL,this);
+	}
+
+	
+	UpdateSkaterInfo( pSkaterProfile );
+
+	UpdateStats( pSkaterProfile );
+	
+	if ( m_local_client )
+	{
+		GetSkaterSoundComponent()->SetMaxSpeed(GetScriptedStat(CRCD(0xcc5f87aa,"Skater_Max_Max_Speed_Stat")));
+	}
+	
+
+	/*
+	// K: For the debug info above the skater.
+	mp_drawer=new Fnt::Drawer();
+	mp_drawer->SetRenderState(Image::Drawer::vRS_ZBUFFER | Image::Drawer::vRS_ALPHABLENDING);
+	mp_drawer->SetFont("small.fnt");
+	mp_drawer->SetJust(Image::vJUST_CENTER_X,Image::vJUST_CENTER_Y);
+	mp_drawer->SetShadowState(true,0,0);
+	*/
+
+	if (mp_trick_component)
+	{
+		// K: Create and fill in the trick mappings structure.
+		mp_trick_component->UpdateTrickMappings( pSkaterProfile );
+
+		// Initialise the event buffer.
+		mp_trick_component->ClearEventBuffer();
+		
+		// Initialise the trick queue
+		mp_trick_component->ClearTrickQueue();
+		mp_trick_component->ClearManualTrick();
+		mp_trick_component->ClearExtraGrindTrick();
+
+		mp_trick_component->SetAssociatedScore(mp_skater_score_component->GetScore());
+	}
+	if (mp_skater_gap_component)
+	{
+		mp_skater_gap_component->SetAssociatedScore(mp_skater_score_component->GetScore());
+	}
+	
+	mSparksRequireRail=true;
+
+
+	Obj::CCollisionComponent* p_collision_component = GetCollisionComponent();
+	if ( p_collision_component )
+	{
+		if (p_collision_component->GetCollision())
+		{
+			// Garrett: Make skater non-collidable for now
+			p_collision_component->GetCollision()->SetObjectFlags(mSD_NON_COLLIDABLE);
+		}
+	}
+	
+	// Init collision cache for CFeelers
+	if (m_local_client)
+	{
+		// mp_collision_cache = Nx::CCollCacheManager::sCreateCollCache();
+	}
+	else
+	{
+		// mp_collision_cache = NULL;
+	}
+
+	// make sure it doesn't flash at the origin for a brief moment,
+	mp_model_component->FinalizeModelInitialization();
+
+	// this needs to happen after the m_id has been assigned
+	Script::RunScript( CRCD(0xae5438af,"InitSkaterParticles"), NULL, this );
+
+	// safety-check to make sure he doesn't start out in the blair witch position
+	// (because of our animation LOD-ing system)
+	// (must be done before the models get initialized)
+    mp_animation_component->PlaySequence(CRCD(0x1ca1ff20,"default"));
+    
+    if (m_local_client)
+	{
+		// add created tricks
+		for (int i=0; i < Game::vMAX_CREATED_TRICKS; i++)
+		{
+			m_created_trick[i] = new Game::CCreateATrick;
+		}
+        Script::RunScript( CRCD(0xbdc1c89,"spawn_add_premade_cats_to_skater"), NULL, this );
+        
+		// setup the correct physics state
+		CCompositeObject::CallMemberFunction(CRCD(0x5c038f9b,"SkaterPhysicsControl_SwitchWalkingToSkating"), NULL, NULL);
+	}
+}
+
+void CSkater::Init(Obj::CSkaterProfile* pSkaterProfile)
+{
+	Construct(pSkaterProfile);
+
+	GameNet::Manager * gamenet_manager =  GameNet::Manager::Instance();
+
+// Note, both tasks are registered as LOW_PRIORITY, so they come after everything else
+// this ensures the movement of the skater is in sync with objects the skater stands on
+	
+	if( m_local_client && CFuncs::ScriptInMultiplayerGame( NULL, NULL ))
+	{
+		Net::App* client;
+	
+		client = gamenet_manager->GetClient( m_skater_number );
+		Dbg_Assert( client );
+		   
+		client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SCORE, Mdl::Score::s_handle_score_message, 0, this );
+		client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_COLLIDE_WON, CSkaterStateHistoryComponent::sHandleCollision, 0, this );
+		client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_COLLIDE_LOST, CSkaterStateHistoryComponent::sHandleCollision, 0, this );
+		client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_PROJECTILE_HIT_TARGET, CSkaterStateHistoryComponent::sHandleProjectileHit, 0, this );
+		client->m_Dispatcher.AddHandler( GameNet::MSG_ID_SKATER_HIT_BY_PROJECTILE, CSkaterStateHistoryComponent::sHandleProjectileHit, 0, this );
+		client->m_Dispatcher.AddHandler( GameNet::MSG_ID_STEAL_MESSAGE, CSkaterLocalNetLogicComponent::sHandleStealMessage, 0, this );
+	}
+		
+	// moved to CSkaterLocalNetLogicComponent
+	// m_last_update_time = 0;
+    
+	Obj::CSuspendComponent* pSuspendComponent = GetSuspendComponent();
+	if ( pSuspendComponent )
+	{
+		pSuspendComponent->m_lod_dist[0] = 3000;	// was 500, but in multi-player, we generally want to see subtle animations animating
+													// and in single player, you should always be close to the camera anyway
+		pSuspendComponent->m_lod_dist[1] = 5000; 	// we pretty much always want to see the other player, even at a great distance
+		pSuspendComponent->m_lod_dist[2] = 10000000;
+		pSuspendComponent->m_lod_dist[3] = 10000000;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkater::UpdateStats( CSkaterProfile* pSkaterProfile )
+{		
+	// GJ:  apply the stats contained in the skater profile;  we'll need
+	// need to override them after this function, if we're applying cheats
+	
+	this->SetStat( STATS_AIR, pSkaterProfile->GetStatValue((CRCD(0x439f4704,"air"))) );
+	this->SetStat( STATS_RUN, pSkaterProfile->GetStatValue((CRCD(0xaf895b3f,"run"))) );
+	this->SetStat( STATS_OLLIE, pSkaterProfile->GetStatValue((CRCD(0x9b65d7b8,"ollie"))) );
+	this->SetStat( STATS_SPEED, pSkaterProfile->GetStatValue((CRCD(0xf0d90109,"speed"))) );
+	this->SetStat( STATS_SPIN, pSkaterProfile->GetStatValue((CRCD(0xedf5db70,"spin"))) );
+	this->SetStat( STATS_FLIPSPEED, pSkaterProfile->GetStatValue((CRCD(0x6dcb497c,"flip_speed"))) );
+	this->SetStat( STATS_SWITCH, pSkaterProfile->GetStatValue((CRCD(0x9016b4e7,"switch"))) );
+	this->SetStat( STATS_RAILBALANCE, pSkaterProfile->GetStatValue((CRCD(0xf73a13e3,"rail_balance"))) );
+	this->SetStat( STATS_LIPBALANCE, pSkaterProfile->GetStatValue((CRCD(0xae798769,"lip_balance"))) );
+	this->SetStat( STATS_MANUAL, pSkaterProfile->GetStatValue((CRCD(0xb1fc0722,"manual_balance"))) );
+
+#ifdef __USER_GARY__
+	dodgy_test(); printf("Air    %2.2f\n",GetStat(STATS_AIR));		
+	dodgy_test(); printf("Run    %2.2f\n",GetStat(STATS_RUN));		
+	dodgy_test(); printf("Ollie  %2.2f\n",GetStat(STATS_OLLIE));		
+	dodgy_test(); printf("Speed  %2.2f\n",GetStat(STATS_SPEED));		
+	dodgy_test(); printf("Spin   %2.2f\n",GetStat(STATS_SPIN));		
+	dodgy_test(); printf("Flip   %2.2f\n",GetStat(STATS_FLIPSPEED));		
+	dodgy_test(); printf("Switch %2.2f\n",GetStat(STATS_SWITCH));		
+	dodgy_test(); printf("Rail   %2.2f\n",GetStat(STATS_RAILBALANCE));		
+	dodgy_test(); printf("Lip    %2.2f\n",GetStat(STATS_LIPBALANCE));		
+	dodgy_test(); printf("Manual %2.2f\n",GetStat(STATS_MANUAL));
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkater::UpdateSkaterInfo( Obj::CSkaterProfile* pSkaterProfile )
+{
+	// get some info from the skater profile
+	// while we still have access to it...
+	Dbg_MsgAssert( pSkaterProfile,( "No profile supplied" ));
+
+	m_pushStyle = pSkaterProfile->GetChecksumValue( (CRCD(0xc15dbf86,"pushstyle")) );
+	m_isGoofy = pSkaterProfile->GetChecksumValue( (CRCD(0x7d02bcc3,"stance")) ) == ( CRCD(0x287fcd4e,"goofy") );
+	m_isPro = pSkaterProfile->IsPro();
+	m_displayName = pSkaterProfile->GetDisplayName();
+	
+	// needed for playing correct streams
+	pSkaterProfile->GetInfo()->GetInteger( CRCD(0x3f813177,"is_male"), &m_isMale, Script::ASSERT );
+
+	const char* pFirstName;
+	pSkaterProfile->GetInfo()->GetText( CRCD(0x562e3ecd,"first_name"), &pFirstName, Script::ASSERT );
+	Dbg_MsgAssert( strlen( pFirstName) < MAX_LEN_FIRST_NAME,
+				   ( "First name %s is too long (max = %d chars)", m_firstName, MAX_LEN_FIRST_NAME ) );
+	strcpy( m_firstName, pFirstName );
+	
+	m_skaterNameChecksum = pSkaterProfile->GetSkaterNameChecksum();
+	
+	if (IsLocalClient())
+	{
+		mp_skater_core_physics_component->SetFlag(FLIPPED, !m_isGoofy);
+		GetSkaterFlipAndRotateComponent()->ApplyFlipState();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkater::~CSkater ( void )
+{ 
+// Taken care of by the shadow component's destructor
+//	Obj::CShadowComponent* pShadowComponent = GetShadowComponent();
+//	if ( pShadowComponent )
+//	{
+//		pShadowComponent->SwitchOffShadow();
+//	}
+
+	// if (mp_collision_cache)
+	// {
+		// Nx::CCollCacheManager::sDestroyCollCache(mp_collision_cache);
+	// }
+	
+	// We must unpause the controller before we destruct ourselves.  Otherwise, the
+	// paused/unpaused toggle will become out of sync with any future skater which
+	// uses this controller.
+	if (mPaused && m_local_client && mp_input_component)
+	{
+		mp_input_component->GetDevice()->UnPause();
+		mp_input_component->GetDevice()->StopAllVibrationIncludingSaved();
+	}
+
+	DeleteResources();	   
+
+	Script::RunScript( CRCD(0xf429e0ac,"DestroySkaterParticles"), NULL, this );
+
+	for (int i=0; i < Game::vMAX_CREATED_TRICKS; i++)
+    {
+        delete m_created_trick[i];
+    }
+
+	// (Mick) If there is a camera still associated with this skater, then delete it
+	// this will be either SkaterCam0 or SkaterCam1, and only on local clients 	
+	// (Steve) only delete player 2's camera. For now, at least, player 1's camera
+	// is persistent because it is used in observer mode
+	if (IsLocalClient() && ( m_skater_number == 1 ))
+	{
+		CCompositeObject *p_cam = GetCamera();
+		if (p_cam)
+		{
+			//delete p_cam;
+			p_cam->MarkAsDead();
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Reset skater state (physics, score, et al.), such as when you're retrying
+void CSkater::Reset()
+{
+	// MARK;
+	
+	Dbg_Assert(mp_skater_score_component);
+	mp_skater_score_component->Reset();
+	
+	// clear max times (records) for the various balance tricks
+	Dbg_Assert(GetSkaterBalanceTrickComponent());
+	GetSkaterBalanceTrickComponent()->ClearMaxTimes();	
+	
+	ResetAnimation();
+	
+	Dbg_Assert(mp_skater_core_physics_component);
+	int i;
+	for ( i = 0; i < NUM_ESKATERFLAGS; i++ )
+	{
+		mp_skater_core_physics_component->SetFlag( ( ESkaterFlag )i, true );
+		mp_skater_core_physics_component->SetFlag( ( ESkaterFlag )i, false );
+	}
+
+	// Reset the pad:
+	Dbg_Assert(mp_input_component);
+	mp_input_component->GetControlPad().Reset();
+}
+
+
+// DeleteResources will delete things the skater has allocated that
+// we do not want to stick around when we do a cleanup of everything else
+// we should really split the skater into temporary and permanent parts
+void CSkater::DeleteResources(void)
+{
+	if (IsLocalClient())
+	{
+		mp_skater_gap_component->ClearActiveGaps();
+		
+		GetSkaterBalanceTrickComponent()->ClearBalanceParameters();
+	}
+	
+	// we don't want to carry over special items
+	// from one level to another (they would
+	// fragment memory anyway)
+	
+	Obj::CSpecialItemComponent* pSpecialItemComponent = GetSpecialItemComponent();
+	Dbg_Assert(pSpecialItemComponent);
+	pSpecialItemComponent->DestroyAllSpecialItems();
+
+	// need to get rid of any animations
+	// that the skater is currently playing,
+	// or else there will be CBlendChannels
+	// fragmenting the bottom-up heap.
+	mp_animation_component->Reset();
+	
+	
+#ifdef	__SCRIPT_EVENT_TABLE__		
+	if (mp_script)
+	{
+		delete mp_script;
+		mp_script = NULL;
+		
+	}
+#else
+	// Clear the event receiver associated with this objects
+	// They will get set up again next time we add or remove one
+	mp_event_handler_table->unregister_all(this);
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void CSkater::ResetStates()
+{
+	Dbg_Assert( IsLocalClient());
+
+	GetSkaterPhysicsControlComponent()->SuspendPhysics(false);
+	
+	GetSkaterFlipAndRotateComponent()->Reset();
+	
+//	#ifdef __PLAT_NGC__
+//	mDoDeferredRotateAfter=false;
+//	mDeferredFlipAndRotate=false;
+//	#endif
+
+	// Ken stuff, reset other misc stuff such as the event buffer, trick queue, blaa blaa
+	
+//	GetExceptionComponent()->ClearExceptions();
+
+	Dbg_Assert( mp_trick_component );
+	mp_trick_component->ClearQueueTricksArrays();
+	mp_trick_component->ClearManualTrick();
+	mp_trick_component->ClearExtraGrindTrick();
+	mp_trick_component->ClearEventBuffer();
+	mp_trick_component->ClearTrickQueue();
+	mp_trick_component->ClearMiscellaneous();
+	
+	GetSkaterStateComponent()->Reset();
+
+	// If set this will make the call to UpdateModel() use the display matrix flipped.
+	// Used when running the lip-trick out-anim, to stop it appearing flipped until
+	// the anim has finished.
+	mp_model_component->mFlipDisplayMatrix=false;
+	
+	// Make sure the meters are off.
+	Dbg_Assert( mp_skater_score_component )
+	mp_skater_score_component->GetScore()->SetBalanceMeter(false);
+	mp_skater_score_component->GetScore()->SetManualMeter(false);
+	
+	// Reset the panel
+//	Mdl::Score *pScore=GetScoreObject();
+//	pScore->BailRequest();	// effectively cancels the current score pot
+
+	// Reset doing-trick flag so that he doesn't bail on the drop-in if he died whilst
+	// doing a trick.
+	
+	if (mp_vibration_component)
+	{
+		mp_vibration_component->StopAllVibration();
+	}	
+
+	// set the shadow to stick to his feet, assuming we are on the ground
+	// Note: These are used by the simple shadow, not the detailed one.
+	mp_shadow_component->SetShadowPos( m_pos );
+	mp_shadow_component->SetShadowNormal( m_matrix[Y] );
+
+	mSparksRequireRail=true;
+	
+	GetSkaterStancePanelComponent()->Reset();
+}
+
+void CSkater::ResetAnimation()
+{
+	Dbg_Assert( IsLocalClient());
+    
+	// Put the board orientation back to normal
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Net::Client* client;
+
+	client = gamenet_man->GetClient( GetHeapIndex());
+	Dbg_Assert( client );
+
+	CSkaterFlipAndRotateComponent* p_flip_and_rotate_component = GetSkaterFlipAndRotateComponent();
+	p_flip_and_rotate_component->RotateSkateboard(GetID(), false, client->m_Timestamp, IsLocalClient());
+	p_flip_and_rotate_component->ApplyFlipState();
+	
+	// Make sure the board is visible
+	mp_model_component->HideGeom( CRCD(0xa7a9d4b8,"board"), false, IsLocalClient() );
+	
+	/*
+	// Make sure all atomics are visible
+	// (GJ:  Not really sure why the previous board unhiding is still necessary)
+	pModelComponent->HideGeom((CRCD(0xc4e78e22,"all")), false, IsLocalClient());
+	
+	// unhiding all the atomics will conflict with the "invisible" cheat	
+	*/
+	  
+	// getting into certain physics states
+	// will reset the animation, so we want to 
+	// make sure we've always got some animation
+	// to be playing...
+	mp_animation_component->Reset();
+
+//	GJ:  don't start playing a new animation, because
+//  it's possible that the animations have been unloaded
+//	mp_animation_component->PlaySequence( CRCD(0x1ca1ff20,"default") );
+//	mp_animation_component->SetLoopingType( Gfx::LOOPING_CYCLE, true );
+}
+
+void CSkater::ResetInitScript(bool walk, bool in_cleanup)
+{
+// Mick: Finally update the "SkaterInit" script, so it does any flag clearing that 
+// is needed by the game
+// we do this now, otherwise some other script could issue a MakeSkaterGoto, and clear this script
+// so it would never get run.
+	if ( !mp_script )
+	{
+		mp_script = new Script::CScript;
+	}
+	
+	Script::CStruct* p_params = NULL;
+	if (walk || in_cleanup)
+	{
+		p_params = new Script::CStruct;
+		if (walk)
+		{
+			p_params->AddChecksum(NO_NAME, CRCD(0x726e85aa,"Walk"));
+		}
+		if (in_cleanup)
+		{
+			p_params->AddChecksum(NO_NAME, CRCD(0x7b738580,"InCleanup"));
+		}
+	}
+	
+	mp_script->SetScript(CRCD(0xe740f458,"SkaterInit"), p_params, this);
+	if( IsLocalClient())
+	{
+		mp_script->Update();
+	}
+	if (p_params)
+	{
+		delete p_params;
+	}
+}
+
+// Reset physics flags, position, velocity, etc...
+void CSkater::ResetPhysics(bool walk, bool in_cleanup)
+{
+	// MARK;
+	Dbg_Assert( IsLocalClient());
+
+	// Massive function to reset all skater's members so that
+	// replay becomes deterministicalistic
+	// ResetEverything( );
+	
+	// switch back to walking
+	if (mp_skater_physics_control_component->IsWalking())
+	{
+		Script::CStruct* p_params = new Script::CStruct;
+		p_params->AddChecksum(CRCD(0x91d0d784, "NewScript"), CRCD(0x1dde4804, "OnGroundAi"));
+		JumpToScript(CRCD(0x93a4e402, "Switch_OnGroundAI"), p_params);
+		delete p_params;
+	}
+	
+	// make sure the OnExitRun script gets run before DeleteResources clears the script
+	if (mp_script && mp_script->GetOnExitScriptChecksum())
+	{
+		uint32 on_exit_script_name_checksum = mp_script->GetOnExitScriptChecksum();
+		mp_script->SetOnExitScriptChecksum(0);
+		mp_script->Interrupt(on_exit_script_name_checksum);
+	}
+	
+	mp_skater_core_physics_component->Reset();
+
+	GetSkaterCameraComponentFromObject(GetCamera())->ResetMode();
+
+	ResetStates();
+	DeleteResources();
+	ResetAnimation();		  
+	ResetInitScript(walk, in_cleanup);
+	
+	GetSkaterEndRunComponent()->ClearIsEndingRun();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkater::SkaterEquals( Script::CStruct* pStructure )
+{
+	// TODO:  Maybe stick this data inside the skater's tags,
+	// so that we can create new fields without having to
+	// update this function?
+
+	Dbg_MsgAssert( pStructure,( "No structure supplied" ));
+
+	if ( pStructure->ContainsFlag( CRCD(0xd82f8ac8,"is_pro") ) )
+	{
+		if ( !m_isPro )
+			return false;
+	}
+
+	if ( pStructure->ContainsFlag( CRCD(0x5a3264bb,"is_custom") ) )
+	{
+		if ( m_isPro )
+			return false;
+	}
+
+	if ( pStructure->ContainsFlag( CRCD(0x3f813177,"is_male") ) )
+	{
+		if ( !m_isMale )
+			return false;
+	}
+	
+	if ( pStructure->ContainsFlag( CRCD(0xe5c2f84c,"is_female") ) )
+	{
+		if ( m_isMale )
+			return false;
+	}
+	
+	uint32 is_named;
+	if ( pStructure->GetChecksum( CRCD(0xc2e3d008,"is_named"), &is_named ) )
+	{
+		if ( is_named != m_skaterNameChecksum )
+			return false;
+	}
+
+	uint32 stance;
+	if ( pStructure->GetChecksum( CRCD(0x7d02bcc3,"stance"), &stance ) )
+	{
+		uint32 myStance = ( m_isGoofy ? CRCD(0x287fcd4e,"goofy") : CRCD(0xb58efc2b,"regular") );
+		if ( stance != myStance )
+			return false;
+	}
+
+	// if we've gotten to the end of the function,
+	// that means that all of the criteria matched
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkater::CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	Dbg_MsgAssert(pScript,("NULL pScript"));
+	
+	return _function0( Checksum, pParams, pScript );
+}
+
+bool CSkater::_function0( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	switch (Checksum)
+	{
+        // @script | InLocalSkater | returns true if the skater is a local client
+		case 0x7015bc33: // IsLocalSkater
+		{
+			return IsLocalClient();
+		}
+		
+        // @script | MoveToRandomRestart | moves the skater to a random
+        // restart node
+		case 0xbf450bab: // MoveToRandomRestart
+		{
+			MoveToRandomRestart();
+			break;
+		}
+
+        // @script | SkaterIsNamed | true if skater name matches specified name
+        // @uparm name | skater name
+		case 0x1c5612ee: // SkaterIsNamed
+		{
+			uint32 checksum;
+			pParams->GetChecksum( NONAME, &checksum, Script::ASSERT );
+			return ( checksum == m_skaterNameChecksum );
+		}
+		break;
+		
+		// @script | GetCameraId | sets the skater's camera id to CameraId
+		case CRCC(0x97c8285a,"GetCameraId"):
+		{
+			uint32 camera_name = CRCD(0x967c138c,"SkaterCam0");
+			if (m_skater_number == 1)
+			{
+				camera_name = CRCD(0xe17b231a,"SkaterCam1");
+			}
+			pScript->GetParams()->AddChecksum(CRCD(0x7a039889,"CameraId"), camera_name);
+			break;
+		}
+
+        // @script | RemoveSkaterFromWorld | 
+		case 0xbce85494:	// RemoveSkaterFromWorld
+		{
+			 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+			skate_mod->HideSkater( this, true );
+			return true;
+		}
+		break;
+
+        // @script | AddSkaterToWorld | 
+		case 0x0c09c1b7:	// AddSkaterToWorld
+		{
+			 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+			skate_mod->HideSkater( this, false );
+			//AddToCurrentWorld();
+			return true;
+		}
+		break;
+
+		#if 0
+		case 0x7fa39d17:	// ResetLookAround
+		{
+			Dbg_Message("Skater:ResetLookAround does not work");
+			if (GetSkaterCameraComponent())
+			{
+				GetSkaterCameraComponent()->ResetLookAround();	
+			}
+			return true;
+		}
+		break;
+		#endif
+		
+		case 0x72aecfc0:	// ResetRigidBodyCollisionRadiusBoost
+			ResetRigidBodyCollisionRadiusBoost();
+			break;
+
+		default:
+			return _function1( Checksum, pParams, pScript );
+			break;
+	}
+	return true;
+}
+			
+bool CSkater::_function1( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	
+	switch (Checksum)
+	{
+		/////////////////////////////////////////////////////////////////////////////////////////////
+        // @script | KillSkater | Killskater will reset the skater
+        // to a node, and set his speed to 0. If this command is
+        // run from a node, and that node is linked to a restart
+        // node, then the skater will skip to that restart node,
+        // and the script in that restart node will be executed
+		// @parmopt name | Node | | name of node to jump to
+		// @parmopt name | prefix | | restart at nearest node matching this prefix
+		// @flag RestartWalking | restart the skater in walk physics mode
+		case 0x1b572abf: // KillSkater 
+		{
+			// default just clear skater skater
+			// and reset to origin		
+			
+			bool walk = pParams->ContainsComponentNamed(CRCD(0xd6f06bf6,"RestartWalking"));
+			
+			// If there is a "node" parameter, then skip to that 
+			// Mick, optionally add a "prefix" parameter, and skip to the nearest node that matches
+			// this prefix (will need a new node funtion:
+			//  int	GetNearestNodeByPrefix(const char *p_prefix, const Mth::Vector &pos)
+
+			const char *p_prefix = NULL;
+			uint32	prefix_name = 0;
+			if (!pParams->GetChecksum(CRCD(0x6c4e7971,"prefix"),&prefix_name))
+			{
+				if (pParams->GetString(CRCD(0x6c4e7971,"prefix"),&p_prefix))
+				{
+					prefix_name = Crc::GenerateCRCFromString(p_prefix);
+				}
+			}
+			if (prefix_name)
+			{
+				int	target_node = SkateScript::GetNearestNodeByPrefix(prefix_name,m_pos);
+				Dbg_MsgAssert(target_node != -1,("%s\nNo restart node found with prefix 0x%x, %s",pScript->GetScriptInfo(),prefix_name, p_prefix));
+				SkipToRestart(target_node, walk);  
+				break;
+			}
+			
+			
+			uint32 node_name=0;
+			if (pParams->GetChecksum(CRCD(0x7a8017ba,"Node"),&node_name))
+			{
+				int target_node = SkateScript::FindNamedNode(node_name);
+				Dbg_MsgAssert(target_node != -1,("%\n Tried to kill skater to non-existant node",pScript->GetScriptInfo()));
+				SkipToRestart(target_node, walk); 
+				break;
+			}
+			else
+			{
+				
+				int node = pScript->mNode;
+				Dbg_MsgAssert(node !=  -1,( "%s\nKillSkater called from non node script with no 'node' parameter",pScript->GetScriptInfo()));			
+				{
+					// Otherwise, check the links to see if we are linked to a restart point
+				
+					int links = SkateScript::GetNumLinks(node);
+					int link;
+					for (link = 0;linkUsingCustomPark()) 		// is it a custom park???
+						{
+							Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+							skate_mod->skip_to_restart_point( this, -1, walk );	// just skip to standard restart node			
+						}
+						else
+						#endif
+						{
+							Dbg_MsgAssert(0,( "%s\nKillSkater called, but node %d not linked to restart",pScript->GetScriptInfo(),node));			
+						}
+					}
+				}
+			}			
+			break;
+		}
+		
+        // @script | SparksOn | turn sparks on
+        // @flag RailNotRequired | 
+		case 0xe62f94a2: // SparksOn
+			Replay::WriteSparksOn();
+			Script::RunScript( CRCD(0xaf4475de,"sparks_on"), pParams, this );
+			mSparksRequireRail=true;
+			mSparksOn=true;
+			if (pParams->ContainsFlag("RailNotRequired"))
+			{
+				mSparksRequireRail=false;
+			}	
+			break;
+
+        // @script | SparksOff | turn sparks off
+		case 0x5436a335: // SparksOff
+			Replay::WriteSparksOff();
+			Script::RunScript( CRCD(0x0dccf5c3,"sparks_off"), pParams, this );
+			mSparksOn=false;
+			mSparksRequireRail=true;
+			break;
+
+        // @script | BloodOff | turn blood off
+        // @parm string | bone | bone name
+		case 0xc67c18d7: // BloodOff
+		{
+			const char* p_bone_name;
+			if (!pParams->GetText( CRCD(0xcab94088,"bone"), &p_bone_name ))
+			{
+				Dbg_MsgAssert(0,("%s\nBloodOff requires a bone name"));
+				return 0;
+			}
+			
+			// nothing doing......
+
+            break;
+		}
+   
+        // @script | Die | can't be called on the player
+		case 0xc6870028:// "Die"
+			Dbg_MsgAssert( 0,( "\n%s\nDie can't be called on the player.\nIs somebody putting 'Die' in a trigger script?", pScript->GetScriptInfo( ) ));
+			break;
+
+        // @script | ShouldMongo | true if should mongo
+		case 0x3e8eb713:	// ShouldMongo
+		{
+			if( !IsLocalClient())
+			{
+				return false;
+			}
+			switch ( m_pushStyle )
+			{
+				case vALWAYS_MONGO:
+					return true;
+					break;
+				case vMONGO_WHEN_SWITCH:
+					return ( mp_skater_core_physics_component->IsSwitched() );
+					break;
+				case vNEVER_MONGO:
+				default:
+					return false;
+					break;
+			}
+		}
+			break;
+
+        // @script | SetCustomRestart |  Skater command to set the current position as the "custom restart point" 
+		// and also to query or clear the status of this restart, based on:
+        // @flag set | set the restart
+		// @flag clear | clear the restart
+		// if no flags are passed, then it will return true if restart point is valid 
+		case 0x42832375:			// SetCustomRestart
+		{
+			if (pParams->ContainsFlag(CRCD(0x19ebda23,"set")))
+			{
+				m_restart_valid = true;
+				m_restart_pos = m_pos;
+				m_restart_matrix = m_matrix;			
+				m_restart_walking = mp_skater_physics_control_component->IsWalking();
+			}
+			else if (pParams->ContainsFlag(CRCD(0x1a4e0ef9,"clear")))
+			{
+				m_restart_valid = false;
+				break;
+			}
+			else
+			{
+				return m_restart_valid;
+			}
+			
+			//break;
+		}
+		// Note: Intentional fallthrough!  We want setting the point to have the same effect as skipping to it
+		
+        // @script | SkipToCustomRestart |  Skater command to jump to previously set custom restart 		
+		case 0x5a3c19e9:		// SkipToCustomRestart
+		{
+			SkipToRestart(-1, m_restart_walking);
+			m_pos = m_restart_pos;
+			DUMP_SPOSITION
+			#ifdef	DEBUG_POSITION
+			dodgy_test(); printf("%d: Position = (%.2f,%.2f,%.2f)\n",__LINE__,m_pos[X],m_pos[Y],m_pos[Z]);
+			#endif
+			m_matrix = m_restart_matrix;
+			mp_skater_core_physics_component->m_safe_pos = m_pos;				// for uberfrig
+			m_old_pos = m_pos;				// ensure we don't get any nasty triggerings....
+			SetTeleported();
+			
+			mp_skater_core_physics_component->ResetLerpingMatrix();
+			#ifdef		DEBUG_DISPLAY_MATRIX
+			dodgy_test(); printf("%d: Setting display_matrix[Y][Y] to %f, [X][X] to %f\n",__LINE__,mp_skater_core_physics_component->m_lerping_display_matrix[Y][Y],mp_skater_core_physics_component->m_lerping_display_matrix[X][X]);
+			#endif
+			mp_skater_core_physics_component->m_display_normal = mp_skater_core_physics_component->m_last_display_normal = mp_skater_core_physics_component->m_current_normal = m_matrix[Y];
+			// set the shadow to stick to his feet, assuming we are on the ground
+			// Note: These are used by the simple shadow, not the detailed one.
+			mp_shadow_component->SetShadowPos( m_pos );
+			mp_shadow_component->SetShadowNormal( m_matrix[Y] );
+			break;
+		}
+
+        // @script | GetStat |  Get a stat value in the float stat_value, so it can be used in a script expression
+		// @parm int | Stat | index of stat in stat array, see 
+		case 0x7915aa31:			// GetStat
+		{
+			int stat;
+			pParams->GetInteger(CRCD(0xdf4700de,"Stat"),&stat,true);
+			pScript->GetParams()->AddFloat(CRCD(0x8eaf7add,"stat_value"),GetStat((EStat)stat));
+			break;
+		}
+
+        // @script | GetScriptedStat |  Get a stat value in the float stat_value
+		// parameters are as used in physics.q
+		// and you would generally pass in a structure, like Skater_Max_Speed_Stat
+		// rather than the individual values
+		case 0x9abe8a21:			// GetScriptedStat
+		{
+//			Script::PrintContents(pParams);
+			Script::CStruct *p_struct = NULL;
+			pParams->GetStructure(NONAME,&p_struct,true);
+			pScript->GetParams()->AddFloat(CRCD(0x8eaf7add,"stat_value"),GetScriptedStat(p_struct));
+			break;
+		}
+		
+		// @script | GetSkaterNumber | Gets the skater number, and puts it into a parameter called
+		// SkaterNumber
+		case 0xf4c52f72: // GetSkaterNumber 
+		{
+			pScript->GetParams()->AddInteger(CRCD(0xf3cf5755,"SkaterNumber"),m_skater_number);
+			break;
+		}
+		
+		case 0xd38e8b6c: // PlaceBeforeCamera
+			{
+				if ( GetActiveCamera() )
+				{
+					Mth::Vector	&cam_pos = GetActiveCamera()->GetPos();
+					Mth::Matrix	&cam_mat = GetActiveCamera()->GetMatrix();
+
+
+					m_matrix.Ident();
+					m_pos = cam_pos;
+					m_pos[Y] += FEET( 0 );
+					m_pos -= cam_mat[Z] * FEET( 12 );
+					DUMP_SPOSITION
+					m_old_pos = m_pos;			// MoveToRestart 
+					mp_skater_core_physics_component->m_safe_pos = m_pos;			// MoveToRestart  
+					m_matrix = cam_mat;
+					m_matrix[Z] = -m_matrix[Z];
+					m_matrix[X] = -m_matrix[X];
+					mp_skater_core_physics_component->ResetLerpingMatrix();
+					#ifdef		DEBUG_DISPLAY_MATRIX
+					dodgy_test(); printf("%d: Setting display_matrix[Y][Y] to %f, [X][X] to %f\n",__LINE__,mp_skater_core_physics_component->m_lerping_display_matrix[Y][Y],mp_skater_core_physics_component->m_lerping_display_matrix[X][X]);
+					#endif
+					mp_skater_core_physics_component->m_display_normal = mp_skater_core_physics_component->m_last_display_normal = mp_skater_core_physics_component->m_current_normal = m_matrix[Y];
+					// set the shadow to stick to his feet, assuming we are on the ground
+					// Note: These are used by the simple shadow, not the detailed one.
+					mp_shadow_component->SetShadowPos( m_pos );
+					mp_shadow_component->SetShadowNormal( m_matrix[Y] );
+					// update these for the camera
+					m_vel = -cam_mat[Z];  
+					m_vel[Y] = 0;  
+					m_vel.Normalize(10);  
+					UpdateCamera(true);	  	// re-snap the camera to the correct position
+					
+					#ifdef	DEBUG_VELOCITY
+					dodgy_test(); printf("%d: Velocity = (%.2f,%.2f,%.2f) (%f)\n",__LINE__,m_vel[X],m_vel[Y],m_vel[Z],m_vel.Length());
+					#endif
+				}
+			}
+			break;
+
+  			Dbg_MsgAssert(0,("Forgot a break?"));		
+		
+		default:
+			return CMovingObject::CallMemberFunction( Checksum, pParams, pScript );
+			break;
+	}
+	return true;
+}
+
+// End of CallMemberFunction
+
+void CSkater::SparksOn()
+{
+	Dbg_Assert( 0 );
+
+	// GJ:  It would be nice to keep all the spark logic in
+	// script, but it requires some skater state info in C
+	// (like the mSparksRequireRail).  Eventually, we should
+	// move this all to script, instead of calling the following
+	// script from C code.
+
+	Script::RunScript( CRCD(0xaf4475de,"sparks_on"), NULL, this );
+	mSparksOn = true;
+}
+
+void CSkater::SparksOff()
+{
+	// GJ:  It would be nice to keep all the spark logic in
+	// script, but it requires some skater state info in C
+	// (like the mSparksRequireRail).  Eventually, we should
+	// move this all to script, instead of calling the following
+	// script from C code.
+	if ( mSparksOn )
+	{
+		Replay::WriteSparksOff();
+		Script::RunScript( CRCD(0x430efc31,"TurnOffSkaterSparks"), NULL, this );
+		mSparksOn=false;
+	}
+}
+	
+// Used by the MakeSkaterGoto script function in cfuncs.cpp
+// Causes the skater to jump to the specified script straight away.
+void CSkater::JumpToScript(uint32 ScriptChecksum, Script::CStruct *pParams)
+{
+	Dbg_Assert(IsLocalClient());
+		
+//	Dbg_MsgAssert(mp_script,("NULL mp_script"));
+	if ( !mp_script )
+	{
+		mp_script = new Script::CScript;
+	}
+
+	GetSkaterFlipAndRotateComponent()->DoAnyFlipRotateOrBoardRotateAfters(); // <- See huge comment above definition of this function.
+	mp_script->SetScript(ScriptChecksum,pParams,this);
+	mp_script->Update();
+}
+	
+void CSkater::JumpToScript(const char *pScriptName, Script::CStruct *pParams)
+{
+	
+	JumpToScript(Script::GenerateCRC(pScriptName),pParams);
+}
+
+Mth::Matrix&				CSkater::GetDisplayMatrix( void )
+{
+	return mp_skater_core_physics_component->m_lerping_display_matrix;
+}
+
+void						CSkater::SetDisplayMatrix( Mth::Matrix& matrix )
+{
+	mp_skater_core_physics_component->m_lerping_display_matrix = matrix;
+}
+
+Lst::Node< CSkater >*		CSkater::GetLinkNode( void )
+{
+	return &m_link_node;
+}
+
+void CSkater::Resync( void )
+{
+	if (!m_local_client)
+	{
+		GetSkaterNonLocalNetLogicComponent()->Resync();
+	}
+	
+	mp_skater_state_history_component->ResetPosHistory();
+	//mp_skater_state_history_component->ResetAnimHistory();
+	//mp_skater_state_history_component->SetLatestAnimTimestamp( 0 );
+	
+}
+
+void CSkater::RemoveFromCurrentWorld( void )
+{
+	if (IsLocalClient())
+	{
+		GetSkaterLoopingSoundComponent()->StopLoopingSound();
+	}
+	
+	mp_shadow_component->SwitchOffShadow();
+    
+	Hide( true );
+	m_in_world = false;
+}
+
+void	CSkater::AddToCurrentWorld ( void )
+{
+	mp_shadow_component->SwitchOnSkaterShadow();
+	Hide( false );
+	m_in_world = true;
+}
+
+bool	CSkater::IsInWorld( void )
+{
+	return m_in_world;
+}
+
+const char* CSkater::GetDisplayName()
+{
+	// set the player's display name for rankings screen,
+	// "You got bitch-slapped by X" messages, etc.
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	if( gamenet_man->InNetGame() )
+	{
+		GameNet::PlayerInfo* pInfo = gamenet_man->GetPlayerByObjectID( m_id );
+		Dbg_Assert( pInfo );
+		// (Mick) don't assign it to m_displayName, as that can cause memory allocations
+		// and fragments the heap
+		// ensure you set the appropiate context if you want to do this.
+		//m_displayName =  pInfo->m_Name;
+		return pInfo->m_Name;
+	}
+	
+	return m_displayName.getString();
+}
+
+void CSkater::AddCATInfo(Script::CStruct *pStuff)
+{
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+    Script::CArray *p_tricks=new Script::CArray;
+    p_tricks->SetSizeAndType(Game::vMAX_CREATED_TRICKS,ESYMBOLTYPE_STRUCTURE);
+    for (int i=0; iAddStructure(CRCD(0x26a7cadf,"other"),m_created_trick[i]->mp_other_params);
+        p_new_trick_struct->AddArray(CRCD(0x6b19fc8f,"animations"),m_created_trick[i]->mp_animations);
+        p_new_trick_struct->AddArray(CRCD(0x2e628ee6,"rotations"),m_created_trick[i]->mp_rotations);
+
+        //Script::PrintContents( p_new_trick_struct );
+
+        p_tricks->SetStructure(i,p_new_trick_struct);
+    }
+    pStuff->AddArrayPointer(CRCD(0x1e26fd3e,"Tricks"),p_tricks);
+}
+
+void CSkater::LoadCATInfo(Script::CStruct *pStuff)
+{
+    printf("LoadCATInfo was called\n");
+	Dbg_MsgAssert(pStuff,("NULL pStuff"));
+
+    Script::CArray *pCATInfo=NULL;
+    pStuff->GetArray(CRCD(0x1e26fd3e,"tricks"),&pCATInfo);
+
+    Dbg_Assert( m_created_trick[1] );
+
+    for (int i=0; iGetStructure(i);
+        
+        // other params
+        Script::CStruct *p_other_params=NULL;
+        pTemp->GetStructure(CRCD(0x26a7cadf,"other"),&p_other_params,Script::ASSERT);
+        m_created_trick[i]->mp_other_params->AppendStructure( p_other_params );
+
+        // animations
+        Script::CArray *p_animation_info=NULL;
+        pTemp->GetArray(CRCD(0x6b19fc8f,"animations"),&p_animation_info,Script::ASSERT);
+        Script::CopyArray( m_created_trick[i]->mp_animations, p_animation_info );
+        
+        // rotations
+        Script::CArray *p_rotation_info=NULL;
+        pTemp->GetArray(CRCD(0x2e628ee6,"rotations"),&p_rotation_info,Script::ASSERT);
+        Script::CopyArray( m_created_trick[i]->mp_rotations, p_rotation_info );
+
+        //Script::PrintContents( m_created_trick[i]->mp_other_params );
+        //Script::PrintContents( m_created_trick[i]->mp_rotations );
+    }
+
+    Script::RunScript(CRCD(0x33fe5817,"kill_load_premade_cats"), NULL);
+}
+
+void CSkater::AddStatGoalInfo(Script::CStruct *pStuff)
+{
+    Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+    Script::CArray *p_stat_goals=new Script::CArray;
+    p_stat_goals->SetSizeAndType(Obj::NUM_STATS_GOALS,ESYMBOLTYPE_INTEGER);
+    
+    for (int i=0; iSetInteger(i,mp_stats_manager_component->StatGoalGetStatus(i));
+    }
+    pStuff->AddArrayPointer(CRCD(0x44b66a9f,"StatGoals"),p_stat_goals);
+}
+
+void CSkater::LoadStatGoalInfo(Script::CStruct *pStuff)
+{
+    Dbg_MsgAssert(pStuff,("NULL pStuff"));
+
+    // Reset Stats Goals and set to proper difficulty
+    Script::CStruct* p_params = NULL;
+    mp_stats_manager_component->RefreshFromStructure(p_params);
+
+    Script::CArray *pStatGoalInfo=NULL;
+    pStuff->GetArray(CRCD(0x44b66a9f,"StatGoals"),&pStatGoalInfo);
+
+    for (int i=0; iStatGoalSetStatus(i,pStatGoalInfo->GetInteger(i));
+    }
+}
+
+void CSkater::AddChapterStatusInfo(Script::CStruct *pStuff)
+{
+    Dbg_MsgAssert(pStuff,("NULL pStuff"));
+	
+    pStuff->AddArray(CRCD(0x8e8ee0c0,"ChapterStatus"),Script::GetArray( CRCD(0xdfa17d68,"CHAPTER_STATUS"), Script::ASSERT ));
+}
+
+void CSkater::LoadChapterStatusInfo(Script::CStruct *pStuff)
+{
+    Dbg_MsgAssert(pStuff,("NULL pStuff"));
+
+	uint32 Name = Script::GenerateCRC("ChapterStatus");
+    Script::CArray *pChapterStatusInfo=NULL;
+    pStuff->GetArray(Name,&pChapterStatusInfo);
+
+    Script::CopyArray(Script::GetArray( CRCD(0xdfa17d68,"CHAPTER_STATUS"), Script::ASSERT ), pChapterStatusInfo);
+}
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/skater.h b/Code/Sk/Objects/skater.h
new file mode 100644
index 0000000..df656e8
--- /dev/null
+++ b/Code/Sk/Objects/skater.h
@@ -0,0 +1,612 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/skater.h										**
+**																			**
+**	Created: 		01/25/99	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_SKATER_H
+#define __OBJECTS_SKATER_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+// #include 
+
+// #include 
+
+// #include 
+// #include 
+#include 
+#include 
+// #include 
+
+// #include 
+
+#include 
+// #include 
+
+#include 
+#include 
+
+#include 
+
+#define		SNAP_OVER_THIN_WALLS  	// snap over thin walls (like rails)
+#define		STICKY_WALLRIDES		 // attempt to snap sideways to wallrides
+
+// temp debug macros
+#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPB(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a ? "true" : "false"); }
+#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
+#define DUMPC(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, Script::FindChecksumName(a)); }
+#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
+#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
+#define DUMPM(a) { DUMP4(a[X]); DUMP4(a[Y]); DUMP4(a[Z]); DUMP4(a[W]); }
+#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
+#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
+
+#define RED MAKE_RGB(255, 0, 0)
+#define GREEN MAKE_RGB(0, 255, 0)
+#define BLUE MAKE_RGB(0, 0, 255)
+#define YELLOW MAKE_RGB(200, 200, 0)
+#define PURPLE MAKE_RGB(200, 0, 200)
+#define CYAN MAKE_RGB(0, 200, 200)
+
+// skater specific debug defines
+#ifdef __NOPT_ASSERT__
+#define HEADER { printf("%i:%s:%i: ", Tmr::GetRenderFrame(), __FILE__ + 15, __LINE__); }
+#define DUMP_VELOCITY { if (Script::GetInteger(CRCD(0x71f0478a, "debug_skater_vel"))) { HEADER; printf("m_vel = %g, %g, %g (%g)\n", GetObject()->m_vel[X], GetObject()->m_vel[Y], GetObject()->m_vel[Z], GetObject()->m_vel.Length()); } }
+#define DUMP_POSITION { if (Script::GetInteger(CRCD(0x029ade47, "debug_skater_pos"))) { HEADER; printf("m_pos = %g, %g, %g\n", GetObject()->m_pos[X], GetObject()->m_pos[Y], GetObject()->m_pos[Z]); } }
+#define DUMP_SPOSITION { if (Script::GetInteger(CRCD(0x029ade47, "debug_skater_pos"))) { HEADER; printf("m_pos = %g, %g, %g\n", m_pos[X], m_pos[Y], m_pos[Z]); } }
+#else
+#define DUMP_VELOCITY {   }
+#define DUMP_POSITION {   }
+#define DUMP_SPOSITION {   }
+#endif
+
+// if uncommented here, uncomment in skaterpad.cpp too								   
+//#define		PS2_SIMULATING_XBOX
+
+#ifdef	__PLAT_NGPS__
+	#ifdef	PS2_SIMULATING_XBOX
+		#define	BREAK_SPINE_BUTTONS (control_pad.m_L2.GetPressed() && control_pad.m_R2.GetPressed())
+		#define	WALK_SPINE_BUTTONS (control_pad.m_L2.GetPressed() && control_pad.m_R2.GetPressed())
+	#else	
+		#define	BREAK_SPINE_BUTTONS (control_pad.m_L2.GetPressed() || control_pad.m_R2.GetPressed())
+		#define	WALK_SPINE_BUTTONS (control_pad.m_R2.GetPressed())
+	#endif
+#endif
+
+#ifdef	__PLAT_XBOX__
+#define	BREAK_SPINE_BUTTONS (control_pad.m_L2.GetPressed() && control_pad.m_R2.GetPressed())
+#define	WALK_SPINE_BUTTONS (control_pad.m_L2.GetPressed() && control_pad.m_R2.GetPressed())
+#endif
+
+#ifdef	__PLAT_NGC__
+#define	BREAK_SPINE_BUTTONS (control_pad.m_L1.GetPressed() && control_pad.m_R1.GetPressed())
+#define	WALK_SPINE_BUTTONS (control_pad.m_L1.GetPressed() && control_pad.m_R1.GetPressed())
+#endif
+
+
+#define	VERT_RECOVERY_BUTTONS (BREAK_SPINE_BUTTONS)
+
+// #define	USE_BIKE_PHYSICS (Script::GetInteger(CRCD(0x9e86d1c1,"use_bike_physics")))
+// #define	USE_WALK_PHYSICS (Script::GetInteger(CRCD(0x9ae06a83,"use_walk_physics")))
+// #define	CESS_SLIDE_BUTTONS (control_pad.m_R1.GetPressed() && USE_BIKE_PHYSICS)
+
+#define	SKITCH_BUTTON (control_pad.m_up.GetPressed() && control_pad.m_up.GetPressedTime()>(uint32)GetPhysicsInt(CRCD(0x19b2c752,"Skitch_Hold_Time")))
+
+// These are defined here, so they can easily be switched from	
+// script flags to some more general cheat flags
+//#define		CHEAT_SNOWBOARD		(control_pad.m_R1.GetPressed()) //   (GetPhysicsInt("CheatSnowBoard",false) || GetCheat(GetPhysicsInt(0x9bc667b5)))  //CHEAT_SNOWBOARD
+#define		CHEAT_SNOWBOARD		0	//(GetPhysicsInt("CheatSnowBoard",false) || GetCheat(GetPhysicsInt(0x9bc667b5)))  //CHEAT_SNOWBOARD
+#define		CHEAT_MATRIX		(Mdl::Skate::Instance()->GetCareer()->GetPhysicsInt(CRCD(0xc41d8a34,"CheatMatrix"),false)  || GetCheat(GetPhysicsInt(0xf9514c9e)))	  // CHEAT_MATRIX
+#define		CHEAT_FIRST_PERSON	0//(GetPhysicsInt("CheatFirstPerson",false)  || GetCheat(GetPhysicsInt(0xca459091))) // CHEAT_FIRST_PERSON
+#define		CHEAT_PERFECT_RAIL	 (Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0xcd09e062)))	  // CHEAT_PERFECT_RAIL
+#define		CHEAT_PERFECT_MANUAL (Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0xb38341c9)))	  // CHEAT_PERFECT_MANUAL
+#define		CHEAT_PERFECT_SKITCH (Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0x69a1ce96)))	  // CHEAT_PERFECT_SKITCH
+
+#define		CHEAT_STATS_13   (Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0x6b0f24bc)))  
+#define		CHEAT_KID        0//(GetCheat(GetPhysicsInt(0x406e5a16))) 
+#define		CHEAT_SIM        (Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0x2b87107a)))  
+#define		CHEAT_MOON       (Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0x9c8c6df1)))  
+
+#define		CHEAT_SLOMO		(Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0xf163e7e0)))		// CHEAT_SLOMO
+#define		CHEAT_DISCO		(Mdl::Skate::Instance()->GetCareer()->GetCheat(GetPhysicsInt(0x9fc22bda)))		// CHEAT_DISCO
+
+////////////////////////////////////////////////////////////////////////
+// The following #defines generally enable a bunch of prints
+// that track the changes in value or state of a particular 
+// aspect of the physics.
+// Some of them will also display debugging lines
+//
+
+
+//#define	DEBUG_WALKING
+//#define	DEBUG_ACC_LINES
+//#define	DEBUG_INSIDE_OBJECT
+//#define	DEBUG_SKITCHING
+//#define	DEBUG_BOUNCE_OFF_WALL
+//#define	DEBUG_WALLRIDE
+//#define	DEBUG_VELOCITY
+//#define	DEBUG_POSITION
+//#define	DEBUG_SPINE
+//#define	SHOW_MOVEMENT_LINES
+//#define	DEBUG_UBERFRIG
+//#define	DEBUG_DISPLAY_MATRIX
+//#define	DEBUG_MATRIX
+//#define	DEBUG_COL_FLAGS 
+//#define	DEBUG_RAILS
+//#define	DEBUG_TERRAIN
+//#define	DEBUG_AIR_SNAP_UP
+//#define	DEBUG_SHOW_ALL_COLLISION_LINES
+//#define	DEBUG_SHOW_SIDE_PUSH_LINES
+//#define	DEBUG_TRIGGERS
+//#define	DEBUG_GAPS
+//#define	DEBUG_STATES
+//#define	DEBUG_VERT
+//#define	DEBUG_RECOVER
+//#define	DEBUG_BRAKE
+//#define	DEBUG_FRICTION
+//#define	DEBUG_HIGH_SCORE_GOALS
+					   
+#define		STOPPED_TURN_RAMP_TIME		600		// ms ramp time for turning when stopped
+
+/*****************************************************************************
+**						   Forward Declarations								**
+*****************************************************************************/
+	
+// These are for network games
+#define vNUM_LAG_FRAMES	1.0f
+#define vFORCE_SEND_INTERVAL	15	// every 15 frames, send a full network object update
+
+class	CContact;
+
+namespace Script
+{
+	class CComponent;
+}
+	
+namespace Mdl
+{
+	class Skate;
+}
+
+namespace Nx
+{
+	class CCollCache;
+}
+
+namespace Gfx
+{
+	class	Camera;
+}
+
+namespace Obj
+{
+	class 	CRailNode;							   
+	class 	CSkaterProfile;
+	class	CMovieManager;
+
+	class	CTrickBufferComponent;
+	
+	class	CAnimationComponent;
+	class	CModelComponent;
+	class	CShadowComponent;
+	class	CTrickComponent;
+	class	CTriggerComponent;
+	class	CInputComponent;
+	class	CSkaterCameraComponent;
+	class	CVibrationComponent;
+	class	CSkaterSoundComponent;
+	class	CSkaterGapComponent;
+	class	CSkaterRotateComponent;
+	class	CSkaterScoreComponent;
+	class	CSkaterStateHistoryComponent;
+	class	CSkaterCorePhysicsComponent;
+	class	CSkaterPhysicsControlComponent;
+	class	CWalkComponent;
+    class	CStatsManagerComponent;
+	
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+enum EViewMode
+{
+	GAMEPLAY_SKATER_ACTIVE,
+	VIEWER_SKATER_PAUSED,
+	VIEWER_SKATER_ACTIVE,
+	NUM_VIEW_MODES
+};
+
+#define MAX_TRICK_NAME_CHARS 50
+
+// Used for storing any info needed to restore the skater after it is
+// hidden during a replay.
+// Doesn't contain much at the moment.
+class CSkaterModelRestorationInfo
+{
+public:
+	//bool mShadowOn;
+	Mth::Vector mPreReplayPos;
+	Mth::Matrix mPreReplayDisplayMatrix;
+	uint32 mAtomicStates;
+};
+
+class  CSkater : public  CMovingObject 
+{
+	friend class Mdl::Skate;
+
+public:
+
+	void DodgyTest();
+
+	enum	EPushStyle
+	{
+		vNEVER_MONGO = 0x8b966093,			// "never_mongo"
+		vALWAYS_MONGO = 0x16c6df0b,			// "always_mongo"
+		vMONGO_WHEN_SWITCH = 0x0cdba4f9		// "mongo_when_switch"
+	};
+
+	enum	EStat
+	{
+		STATS_AIR = 0,
+		STATS_RUN,
+		STATS_OLLIE,
+		STATS_SPEED,
+		STATS_SPIN,
+		STATS_FLIPSPEED,
+		STATS_SWITCH,
+		STATS_RAILBALANCE,
+		STATS_LIPBALANCE,
+		STATS_MANUAL,	
+			   
+		NUM_ESTATS
+	};
+
+///////////////////////////////////////////////////////////////////////////////////////////////	
+// Player specific	
+public:
+	void						UpdateStats( Obj::CSkaterProfile* pSkaterProfile );
+	void 						UpdateSkaterInfo( Obj::CSkaterProfile* pSkaterProfile );
+	float						GetStat(EStat stat);
+	void						SetStat(EStat stat, float value);
+	float 						GetStattedValue(const char *pName, EStat stat);
+	float 				  	  	GetScriptedStat(Script::CStruct *pSS);
+	float						GetScriptedStat(const uint32 checksum);
+	float 						GetScriptedStat(uint32 Checksum, Script::CStruct *pSetOfStats);
+	// float						GetHeight();
+							   
+	// Here's some data that needs to be stored
+	// from the skater profile, in case various
+	// scripts ask for it
+	uint32	m_pushStyle;
+	bool	m_isGoofy;
+	bool	m_isPro;
+	int		m_isMale;
+
+	enum
+	{
+		MAX_LEN_FIRST_NAME = 64
+	};
+
+	char	m_firstName[MAX_LEN_FIRST_NAME];
+
+	// Name of the skater, eg Hawk, Caballero, etc.
+	// Grabbed from the skater profile.
+	uint32	m_skaterNameChecksum;
+	Str::String	m_displayName;
+
+protected:
+	
+	CSkaterModelRestorationInfo m_model_restoration_info;
+	
+public :
+	CSkater ( int player_num, CBaseManager* p_obj_man, bool local_client, int obj_id, int heap_num, bool should_tristrip);
+	virtual						~CSkater ( void );
+
+	// void HideForReplayPlayback();
+	// void RestoreAfterReplayPlayback();
+
+///////////////////////////////////////////////////////////////////////////
+// Sub Games
+	
+public:
+	bool						m_always_special;
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// Kind of redundant	
+public:
+	void						RemoveFromCurrentWorld( void );
+	void						AddToCurrentWorld ( void );
+	bool						IsInWorld( void );
+	
+///////////////////////////////////////////////////////////////////////////////////////////
+// fundamental  to both skater and physics	
+public:
+	virtual void 				Pause();
+	virtual void 				UnPause();
+	virtual bool				CallMemberFunction( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript ); 
+	virtual void 				Init(Obj::CSkaterProfile* pSkaterProfile);
+
+protected :
+	void 						Construct(Obj::CSkaterProfile* pSkaterProfile);
+	bool _function0( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript );
+	bool _function1( uint32 Checksum, Script::CStruct *pParams, Script::CScript *pScript );
+
+///////////////////////////////////////////////////////////////////////////////////
+// Network
+
+public:
+	void	Resync( void );
+protected:
+
+	int							m_last_restart_node;
+
+////////////////////////////////////////////////////////////////////////////////////////////
+// Rendering related
+public:
+	Mth::Matrix&				GetDisplayMatrix( void );
+	void						SetDisplayMatrix( Mth::Matrix& matrix );
+	
+protected:
+	// True if sparks will be automatically switched off when
+	// the skater comes off a rail.
+	bool mSparksRequireRail;
+	bool mSparksOn;
+	void SparksOff();
+	void SparksOn();
+
+	// The skater's shadow will move beneath him (if directional) when he is in the air.
+	int			m_air_timer;
+	
+//////////////////////////////////////////////////////////////////////////////////////////////
+// high level accessors	
+public:
+	int							GetHeapIndex( void );
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+// Level gameplay related
+public:
+	void						MoveToRandomRestart( void );
+	virtual void				Reset();
+	virtual void				SkipToRestart(int node, bool walk=false);
+	virtual void				MoveToRestart(int node);
+
+// should be private:	
+	// Dan: I'd like to remove this, but it's still used by CEmiter, which is skater specific and does not
+	// currently use ProximTriggerComponents
+	bool						m_inside;		// flag set up by proxim node triggering
+
+
+protected:
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+// Misc
+public:
+	Lst::Node< CSkater >*		GetLinkNode( void );
+
+//////////////////////////////////////////////////////////////////////////////
+// Tricks 
+public:	
+    // CreateATrick 
+    Game::CCreateATrick* m_created_trick[Game::vMAX_CREATED_TRICKS];
+    void					AddCATInfo(Script::CStruct *pStuff);
+    void					LoadCATInfo(Script::CStruct *pStuff);
+    int                     m_num_cat_animations_on;
+    bool                    m_cat_bail_done;
+    bool                    m_cat_flip_skater_180;
+    bool                    m_cat_animations_done;
+    bool                    m_cat_rotations_done;
+    float                   m_cat_hold_time;
+    int                     m_cat_total_x;
+    int                     m_cat_total_y;
+    int                     m_cat_total_z;
+    // Stat Goals
+    void					AddStatGoalInfo(Script::CStruct *pStuff);
+    void					LoadStatGoalInfo(Script::CStruct *pStuff);
+    // Chapter Status
+    void					AddChapterStatusInfo(Script::CStruct *pStuff);
+    void					LoadChapterStatusInfo(Script::CStruct *pStuff);
+	
+public:
+////////////////////////////////////////////////////////////////////////////////
+// script
+
+	void	JumpToScript(uint32 ScriptChecksum, Script::CStruct *pParams=NULL);
+	void	JumpToScript(const char *pScriptName, Script::CStruct *pParams=NULL);
+
+protected :
+	
+	void UpdateCamera(bool instantly = false);
+	void UpdateCameras( bool instantly );
+	
+public:
+	
+	void DeleteResources(void);
+	void ResetPhysics(bool walk=false,bool in_cleanup=false);
+	virtual void ResetStates();
+	virtual void ResetAnimation();		  
+	virtual void ResetInitScript(bool walk, bool in_cleanup);
+
+protected:
+	// Moved this from the CMovingObject class
+	// since we're trying to remove functionality
+	// from the moving object class, and because
+	// the skater was the only thing that was
+	// calling it...
+	void MovingObj_Reset( void );
+
+public:	
+	void UpdateShadow(const Mth::Vector& pos, const Mth::Matrix& matrix);
+	// K: Added for use by replay code so that it can playback pad vibrations
+	SIO::Device *GetDevice();
+
+protected:
+
+	Lst::Node< CSkater >		m_link_node;			// linked list node for list of skaters
+
+	// pointers to oft used components of the skater
+	CAnimationComponent*			mp_animation_component;
+	CModelComponent*				mp_model_component;
+	CShadowComponent*				mp_shadow_component;
+	CTrickComponent*				mp_trick_component;
+	CVibrationComponent*			mp_vibration_component;
+	CTriggerComponent*				mp_trigger_component;
+	CInputComponent*				mp_input_component;
+	CSkaterSoundComponent*			mp_skater_sound_component;
+	CSkaterGapComponent*			mp_skater_gap_component;
+	CSkaterRotateComponent*			mp_skater_rotate_component;
+	CSkaterScoreComponent*			mp_skater_score_component;
+	CSkaterStateHistoryComponent*	mp_skater_state_history_component;
+	CSkaterCorePhysicsComponent*	mp_skater_core_physics_component;
+	CSkaterPhysicsControlComponent* mp_skater_physics_control_component;
+	CWalkComponent*					mp_walk_component;
+    CStatsManagerComponent*			mp_stats_manager_component;
+	
+public:
+	// allow external code rapid access to these components
+	CSkaterStateHistoryComponent*	GetStateHistory (   )
+	{
+		Dbg_Assert(mp_skater_state_history_component);
+		return mp_skater_state_history_component;
+	}
+	
+protected:
+	
+	float						m_stat[NUM_ESTATS];	 	// all the stats
+
+	int							m_heap_index;			// Which skater heap is this skater using
+											 
+	bool						m_local_client;			// Is this cient being controlled by my PS2
+
+	
+	int							m_skater_number;		// 0 or 1 (maybe 2 or 3) 	  
+
+	int							m_viewing_mode;					// 0 = normal, 1 = frozen, move cam, 2 = move, freeze cam
+	
+public:	
+	int							GetViewingMode (   ) { return m_viewing_mode; }
+	
+protected:	
+	// is the skater in the world?
+	bool						m_in_world;
+	
+	// For custom restart point
+	
+	Mth::Vector					m_restart_pos;
+	Mth::Matrix					m_restart_matrix;
+	bool						m_restart_walking;
+	bool						m_restart_valid;
+	
+public:	
+
+	const char*	GetDisplayName();
+	void		SetAlwaysSpecial(bool on) {m_always_special = on;}
+	void		ToggleAlwaysSpecial() {m_always_special = !m_always_special;}
+
+	// this used to be in the skater profile, but I changed it
+	// because it was confusing when called from multiplayer games.
+	bool		SkaterEquals( Script::CStruct* pParams );
+
+public:
+	// Returns a pointer to this skater's score object.
+	Mdl::Score *GetScoreObject();
+	bool	IsLocalClient() {return m_local_client;}
+
+	int		GetSkaterNumber() {return m_skater_number;}
+	
+	Gfx::Camera *GetActiveCamera( void );
+	
+	CCompositeObject* CSkater::GetCamera (    );
+
+	void    SetViewMode( EViewMode view_mode );
+	int		GetViewMode() { return m_viewing_mode; }
+	
+	// hack to allow the collision radius to be adjusted while in a car
+	float	m_rigid_body_collision_radius_boost;
+	void	SetRigidBodyCollisionRadiusBoost ( float rigid_body_collision_radius_boost ) { m_rigid_body_collision_radius_boost = rigid_body_collision_radius_boost; }
+	void	ResetRigidBodyCollisionRadiusBoost (   ) { m_rigid_body_collision_radius_boost = 0.0f; }
+	float	GetRigidBodyCollisionRadiusBoost (   ) { return m_rigid_body_collision_radius_boost; }
+
+	friend class CSkaterCam;
+	
+	friend class CSkaterCameraComponent;
+	friend class CTrickComponent;
+	friend class CSkaterLoopingSoundComponent;
+	friend class CSkaterSoundComponent;
+	friend class CSkaterGapComponent;
+	friend class CSkaterRotateComponent;
+	friend class CSkaterLocalNetLogicComponent;
+	friend class CSkaterNonLocalNetLogicComponent;
+	friend class CSkaterScoreComponent;
+	friend class CSkaterMatrixQueriesComponent;
+	friend class CSkaterStateHistoryComponent;
+	friend class CSkaterPhysicsControlComponent;
+	friend class CSkaterCorePhysicsComponent;
+	friend class CSkaterAdjustPhysicsComponent;
+	friend class CSkaterFinalizePhysicsComponent;
+	friend class CSkaterCleanupStateComponent;
+	friend class CSkaterFlipAndRotateComponent;
+	
+private:
+	// skater physics variables beyond those inherited from CCompositeObject
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+extern bool DebugSkaterScripts;
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+int GetPhysicsInt(const char *p_name, Script::EAssertType assert = Script::NO_ASSERT);
+int GetPhysicsInt(uint32 id, Script::EAssertType assert = Script::NO_ASSERT);
+float GetPhysicsFloat(const char *p_name, Script::EAssertType assert = Script::NO_ASSERT);
+float GetPhysicsFloat(uint32 id, Script::EAssertType assert = Script::NO_ASSERT);
+
+} // namespace Obj
+
+#endif	// __OBJECTS_SKATER_H
diff --git a/Code/Sk/Objects/skatercam.cpp b/Code/Sk/Objects/skatercam.cpp
new file mode 100644
index 0000000..ecdc6a2
--- /dev/null
+++ b/Code/Sk/Objects/skatercam.cpp
@@ -0,0 +1,1646 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       skatercam.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/05/2001
+//****************************************************************************
+
+/*
+
+(Mick) 
+Problem:
+
+If you stop, and then jump in place while holding "down", you will lean backwards while 
+you jump.  This will not really be noticable, but when you land, the camera will seem to 
+snap backwards for a single frame
+
+the problem was m_display_matrix is lagging behind
+so I have patched it by setting skater_up to mpSkate->m_display_normal when on the ground
+this fixes it for the skater glitching, however the world around him is still glitching
+as the angle still changes abruptly
+
+one fix might be to somehow smoothly interpolate this angle , which might also help 
+with any fix we do for skating off ledges
+
+I need to clean up and comment this code, so it can be modified without fear.....
+
+
+*/
+
+
+//#define	DEBUG_CAMERA
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+		
+		
+static void _xrotlocal ( Mth::Matrix& m, float a )
+{
+	m.RotateXLocal( a );
+}
+
+namespace Obj
+{
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+	const float LEAN_TO_SKATERCAM_LEAN	= (( Mth::PI / 6.0f ) / -4096.0f );
+	const float	SKATERCAM_INTERPOLATOR_TIMESTEP = ( 1.0f / 60.0f );
+
+	const float VERT_AIR_LANDED_TIME	= 10.0f / 60.0f;
+	const float TILT_MAX				= 0.34907f;						// 20 degees.
+	const float TILT_MIN				= -0.34907f;					// -20 degees.
+	const float TILT_INCREMENT			= 2.0f * ( TILT_MAX / 60.0f );	// 40 degrees/second.
+	const float TILT_DECREMENT			= 2.0f * ( TILT_MAX / 60.0f );	// 40 degrees/second.
+	const float TILT_RESTORE			= 8.0f * ( TILT_MAX / 60.0f );	// 160 degrees/second.
+
+	// This is the value that mAbove should tend to when the behind value is closer than mBehind.
+	const float PERFECT_ABOVE = 3.0f;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::s_input_logic_code( const Inp::Handler < CSkaterCam >& handler )
+{    
+    Dbg_AssertType( &handler, Inp::Handler< CSkaterCam > );
+	
+	bool skip_button;
+	bool right_button;
+	int right_x;
+	int right_y;
+
+    CSkaterCam&	obj = handler.GetData();
+	
+	if ( obj.mpSkater && !obj.mpSkater->IsLocalClient( ) )
+	{
+		obj.mSkipButton = handler.m_Input->m_Makes & ( Inp::Data::mD_X );
+		obj.mRightButton = handler.m_Input->m_Makes & ( Inp::Data::mD_R3 );
+		obj.mRightX = handler.m_Input->m_Event[Inp::Data::vA_RIGHT_X] - 128;
+		obj.mRightY = handler.m_Input->m_Event[Inp::Data::vA_RIGHT_Y] - 128;
+	}
+	skip_button = obj.mSkipButton;
+	right_button = obj.mRightButton;
+	right_x = obj.mRightX;
+	right_y = obj.mRightY;
+
+	if (obj.m_input_enabled)
+	{
+		if( right_button )
+		{
+			obj.mLookaroundLock = !obj.mLookaroundLock;
+		}
+	
+		if( !obj.mLookaroundLock && !obj.mLookaroundOverride )
+		{
+			// We want this to map to (PI/2, ~-2PI/10). Switched this 7/10/01 to be opposite up/down to previously.
+			float target = ((float)(( right_y < 0 ) ? right_y : ( 0.4f * right_y ))) * ( -1.4f / 128.0f );
+			obj.mLookaroundTilt += ( target - obj.mLookaroundTilt ) * 0.0625f;
+	
+			// We want this to map to (-~PI, ~PI).
+			target = ((float)right_x ) * ( 3.0f / 128.0f );
+			obj.mLookaroundHeading	+= ( target - obj.mLookaroundHeading ) * 0.0625f;
+		}
+	
+/*		Hey -- Let's not mask these since I need to read them in the player's s_input_logic function
+		for replay stuff.  Thanks.  If it breaks something please tell me.  Love Matt
+		// Mask off analog right stick input.
+		handler.m_Input->MaskDigitalInput( Inp::Data::mD_R3 );
+		handler.m_Input->MaskAnalogInput( Inp::Data::mA_RIGHT_X );
+		handler.m_Input->MaskAnalogInput( Inp::Data::mA_RIGHT_Y );
+*/
+	}
+}
+
+/*****************************************************************************
+**								Public Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterCam::CSkaterCam( int player )
+{
+	mMode	= SKATERCAM_MODE_UNDEFINED;
+
+#ifdef	DEBUG_CAMERA
+	printf ("+++ SkaterCam Created ..........................\n");				  
+#endif
+				  
+	// Create the SLERP interpolator here.
+	mpSlerp = new Mth::SlerpInterpolator;
+
+	// Set current zoom and LERP rates.
+	mCurrentZoom		= 1.0f;
+	mZoomLerp			= 0.0625f;
+	mLastDot			= 1.0f;
+
+	mLerpXZ			= 0.25f;
+	mLerpY			= 0.5f;
+	mVertAirLerpXZ	= 1.0f;
+	mVertAirLerpY	= 1.0f;
+	mGrindLerp		= 0.1f;
+
+	mLookaroundTilt		= 0.0f;
+	mLookaroundHeading	= 0.0f;
+	mLookaroundZoom		= 1.0f;
+
+	Inp::Manager * inp_manager = Inp::Manager::Instance();
+
+	// Register input task.
+	m_input_handler			= new Inp::Handler< CSkaterCam >( player, s_input_logic_code, *this, Tsk::BaseTask::Node::vNORMAL_PRIORITY + 1 );
+//	m_input_handler_special	= new Inp::Handler< CSkaterCam >( player, s_input_logic_code_special, *this, Tsk::BaseTask::Node::vNORMAL_PRIORITY + 1 );
+
+	m_input_enabled = true;
+	inp_manager->AddHandler( *m_input_handler );
+
+    // clear the frame matrix
+    // so that future slerps make sense
+    Mth::Matrix& frame_matrix = Gfx::Camera::GetMatrix();
+    frame_matrix.Ident();
+
+	mLastActualRight 	= frame_matrix[X];
+	mLastActualUp 		= frame_matrix[Y];
+	mLastActualAt 		= frame_matrix[Z];
+	
+	Dbg_MsgAssert(false, ("CSkaterCam is not supported."));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterCam::~CSkaterCam( void )
+{
+#ifdef	DEBUG_CAMERA
+	printf ("--- SkaterCam Destroyed ..........................\n");				  
+#endif
+
+	if( mpSlerp )
+	{
+		delete mpSlerp;
+	}
+
+	#if 0
+	// for cutscene camera
+
+	if( mpCopy )
+	{
+		Mem::Free( mpCopy );
+		mpCopy = NULL;
+	}
+	#endif
+
+	// Unregister input task.
+	if( m_input_handler )
+	{
+		if( m_input_handler->InList() )
+		{
+			m_input_handler->Remove();
+		}
+		delete m_input_handler;
+	}
+/*	
+	if( m_input_handler_special )
+	{
+		if( m_input_handler_special->InList() )
+		{
+			m_input_handler_special->Remove();
+		}
+		delete m_input_handler_special;
+	}
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::EnableInputHandler( bool enable )
+{
+	m_input_enabled = enable;
+
+/*
+	if( enable )
+	{
+		// Remove the special handler and add the standard handler.
+		if( m_input_handler_special->InList() )
+		{
+			m_input_handler_special->Remove();
+		}
+		if( !m_input_handler->InList() )
+		{
+			Inp::Manager * inp_manager = Inp::Manager::Instance();
+			inp_manager->AddHandler( *m_input_handler );
+		}
+	}
+	else
+	{
+		// Remove the standard handler and add the special handler.
+		if( m_input_handler->InList() )
+		{
+			m_input_handler->Remove();
+		}
+		if( !m_input_handler_special->InList() )
+		{
+			Inp::Manager * inp_manager = Inp::Manager::Instance();
+			inp_manager->AddHandler( *m_input_handler_special );
+		}
+
+	}
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::ResetLookAround( void )
+{
+	mLookaroundTilt		= 0.0f;
+	mLookaroundHeading	= 0.0f;
+
+	Update( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// toggle between replay modes:
+void CSkaterCam::SwitchReplayMode( bool increment )  // true to increment, false to decrement
+{
+	
+	int mode = ( int )mMode;
+	if ( increment )
+	{
+		mode++;
+	}
+	else
+	{
+		mode--;
+	}
+	if ( mode < SKATERCAM_FIRST_REPLAY_MODE )
+	{
+		mode = SKATERCAM_LAST_REPLAY_MODE;
+	}
+	else if ( mode > SKATERCAM_LAST_REPLAY_MODE )
+	{
+		mode = SKATERCAM_FIRST_REPLAY_MODE;
+	}
+	SetMode( ( ESkaterCamMode )mode, 0.0f );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::SetMode( ESkaterCamMode mode, float time )
+{
+	
+
+#if 0	// Garrett: This doesn't work because it doesn't check to see if the screen mode has changed
+	if( mode == mMode )
+	{
+		// Nothing to do here.
+		return;
+	}
+#endif
+
+	Dbg_MsgAssert( mode < SKATERCAM_NUM_MODES,( "Bad mode" ));
+
+	// Obtain details for this mode.
+	float h_fov, behind, above, tilt, origin_offset, zoom_lerp;
+	const char* p_name;
+
+	// Obtain a pointer to the array entry for the desired mode. The array is now dependent
+	// on which game mode is currently being played, 1 player, 2 player vert, or 2 player horiz.
+	Nx::ScreenMode screen_mode = Nx::CViewportManager::sGetScreenMode();
+
+	Script::CArray* p_array;
+
+	switch( screen_mode )
+	{
+		case Nx::vSPLIT_V:
+		{
+			p_array = Script::GetArray( "Skater_Camera_2P_Vert_Array" );
+			break;
+		}
+
+		case Nx::vSPLIT_H:
+		{
+			p_array = Script::GetArray( "Skater_Camera_2P_Horiz_Array" );
+			break;
+		}
+
+		default:
+		{			
+			p_array = Script::GetArray( "Skater_Camera_Array" );
+			break;
+		}
+	}
+
+
+	// Extra check here, since the array in physics.q may change.
+	Dbg_MsgAssert( mode < (int)p_array->GetSize(), ( "Bad mode" ));
+
+	uint32 cs = p_array->GetNameChecksum( mode );
+	Script::CScriptStructure* p_struct = Script::GetStructure( cs );
+
+	mMode = mode;
+
+	mLipTrickTilt = 0.0f;
+
+	// Read the values from the array.
+	p_struct->GetFloat( "horiz_fov",				&h_fov );
+	p_struct->GetFloat( "behind",					&behind );
+	p_struct->GetFloat( "above",					&above );
+	p_struct->GetFloat( "tilt",						&tilt );
+	p_struct->GetFloat( "origin_offset",			&origin_offset );
+	p_struct->GetFloat( "lip_trick_tilt",			&mLipTrickTilt );
+	p_struct->GetFloat( "lip_trick_above",			&mLipTrickAbove );
+
+	// Get SLERP values.
+	p_struct->GetFloat( "slerp",					&mSlerp );
+	p_struct->GetFloat( "vert_air_slerp",			&mVertAirSlerp );
+	p_struct->GetFloat( "vert_air_landed_slerp",	&mVertAirLandedSlerp );
+
+	// Get LERP values.
+	p_struct->GetFloat( "lerp_xz",					&mLerpXZ );
+	p_struct->GetFloat( "lerp_y",					&mLerpY );
+	p_struct->GetFloat( "vert_air_lerp_xz",			&mVertAirLerpXZ );
+	p_struct->GetFloat( "vert_air_lerp_y",			&mVertAirLerpY );
+	p_struct->GetFloat( "grind_lerp",				&mGrindLerp );
+
+	// Get zoom values.
+	mGrindZoom			= 1.0f;
+	mLipTrickZoom		= 1.0f;
+	mBigAirTrickZoom	= 1.0f;
+
+	p_struct->GetFloat( "zoom_lerp",			&zoom_lerp );
+	p_struct->GetFloat( "grind_zoom",			&mGrindZoom );
+	p_struct->GetFloat( "big_air_trick_zoom",	&mBigAirTrickZoom );
+	p_struct->GetFloat( "lip_trick_zoom",		&mLipTrickZoom );
+	mZoomLerp			= zoom_lerp;
+
+	// Get camera style name.
+	p_struct->GetText( "name", &p_name );
+
+	// Send the new camera mode to console line 1.
+	//HUD::PanelMgr* panelMgr = HUD::PanelMgr::Instance();
+	//panelMgr->SendConsoleMessage( p_name, 1 );
+
+	// Need to calculate interpolate timers here.
+	mHorizFOV		= h_fov;
+	mTilt			= tilt;
+	mOriginOffset	= FEET( origin_offset );		// Convert to inches.
+
+	// Set up interpolators for those values that require it.
+	if( time > 0.0f )
+	{
+		behind						= FEET( behind );				// Convert to inches.
+		above						= FEET( above );				// Convert to inches.
+		mBehindInterpolatorTime		= time;
+		mBehindInterpolatorDelta	= (( behind - mBehind ) * ( SKATERCAM_INTERPOLATOR_TIMESTEP / time ));
+		mAboveInterpolatorTime		= time;
+		mAboveInterpolatorDelta		= (( above - mAbove ) * ( SKATERCAM_INTERPOLATOR_TIMESTEP / time ));
+	}
+	else
+	{
+		mBehind						= FEET( behind );
+		mAbove						= FEET( above );
+		mBehindInterpolatorTime		= 0.0f;
+		mAboveInterpolatorTime		= 0.0f;
+	}
+
+	// Set focal length immediately.
+	if( this )
+	{
+		Nx::ScreenMode	mode = Nx::CViewportManager::sGetScreenMode();
+
+		switch( mode )
+		{
+			case Nx::vSPLIT_V:
+			{
+#ifdef __PLAT_NGC__
+				//Gfx::Camera::SetFocalLength( Script::GetFloat("Skater_Cam_Horiz_FOV"), Nx::vBASE_ASPECT_RATIO );
+				Gfx::Camera::SetHFOV(Script::GetFloat("Skater_Cam_Horiz_FOV") / 2.0f);
+#else
+				//Gfx::Camera::SetFocalLength( mFocalLength, Nx::vBASE_ASPECT_RATIO * 0.5f );
+				Gfx::Camera::SetHFOV(mHorizFOV / 2.0f);
+#endif // __PLAT_NGC__
+				break;
+			}
+
+			case Nx::vSPLIT_H:
+			{
+				//Gfx::Camera::SetFocalLength( mFocalLength * 2.0f, Nx::vBASE_ASPECT_RATIO * 2.0f );
+				Gfx::Camera::SetHFOV(mHorizFOV);
+				break;
+			}
+
+			default:
+			{			
+				//Gfx::Camera::SetFocalLength( mFocalLength, Nx::vBASE_ASPECT_RATIO );
+				Gfx::Camera::SetHFOV(mHorizFOV);
+				break;
+			}
+		}
+	}
+
+	switch ( mode )
+	{
+		case ( SKATERCAM_MODE_REPLAY_FRONT ):
+		case ( SKATERCAM_MODE_REPLAY_FRONT_ZOOM ):
+			mLookaroundHeadingStartingPoint = Mth::PI;
+			break;
+		case ( SKATERCAM_MODE_REPLAY_LEFT ):
+		case ( SKATERCAM_MODE_REPLAY_LEFT_ZOOM ):
+			mLookaroundHeadingStartingPoint = Mth::PI / 2.0f;
+			break;
+		case ( SKATERCAM_MODE_REPLAY_BEHIND ):
+		case ( SKATERCAM_MODE_REPLAY_BEHIND_ZOOM ):
+			mLookaroundHeadingStartingPoint = 0;
+			break;
+		case ( SKATERCAM_MODE_REPLAY_RIGHT ):
+		case ( SKATERCAM_MODE_REPLAY_RIGHT_ZOOM ):
+			mLookaroundHeadingStartingPoint = -( Mth::PI / 2.0f );
+			break;
+		default:
+			mLookaroundHeadingStartingPoint = 0;
+			break;
+	}   
+}
+
+/*******************************************************************************************************
+logic for getTimeAdjustedSlerp()
+
+If the camera is lagging a distance D behind the target, and the target is moving with velocity V, and the lerp is set to L.
+
+Then when the camera is stable, it will be moving at the same rate as the target (V), and since the distance it will move is equal to the lerp multiplied by the distance from the target (which will be the old distance plus the new movement of the target V)
+
+Then:
+
+L*(D+V) = V 
+
+L*D + L*V = V
+D = V * (1 - L) / L
+
+Assuming in the above T = 1, then if we have a variable T, the speed of the skater will be T*V, yet we want the distance (Dt) moved to remain unchanged (D), so for a time adjusted lerp Lt
+
+Dt = T * V * (1-Lt)/Lt
+
+Since D = Dt
+
+V * (1 - L) / L = T * V * (1-Lt)/Lt
+
+V cancels out, and we get
+
+Lt = TL / (1 - L + TL)
+
+Sanity check,  
+
+if L is 0.25, and T is 1, then Lt = 1*0.25 / (1 - 0.25 + 1*0.25)  = 0.25
+if L is 0.25, and T is 2, then Lt = 2*0.25 / (1 - 0.25 + 2*0.25)  = 0.5 / 1.25 = 0.40
+if L is 0.25, and T is 5, then Lt = 5*0.25 / (1 - 0.25 + 5*0.25)  = 1.25 / 2 = 0.625
+
+Sounds about right.
+
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static float getTimeAdjustedSlerp( float slerp, float delta )
+{
+#if	1
+	// Mick's method, tries to guarantee that the distance from the target point
+	// will never alter at constant velocity, with changing frame rate
+	// Lt = TL / (1 - L + TL)
+	
+	float t = delta * 60.0f;
+	float Lt =  t * slerp / (1.0f - slerp + t * slerp);
+	return Lt;	
+	
+#else
+	// Dave's method. simulates the effect of lerp over multiple frames
+	// curiously seems to be within 2% of the above method.  
+
+	float result	= 0.0f;
+	float remainder	= 1.0f;
+
+	while( true )
+	{
+		if( delta >= ( 1.0f / 60.0f ))
+		{
+			delta -= ( 1.0f / 60.0f );
+
+			float extra = slerp * remainder;
+			remainder -= extra;
+			result += extra;
+		}
+		else
+		{
+			// Partial fragment, or nothing at all?
+			if( delta > 0.0f )
+			{
+				// The slerp this time will be reduced.
+				slerp *= delta / ( 1.0f / 60.0f );
+
+				float extra = slerp * remainder;
+				remainder -= extra;
+				result += extra;
+			}
+//			printf ("Result %f, Lt %f, diff = %f\n",result,Lt,result-Lt);
+			return result;
+		}
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::SetLookaround( float heading, float tilt, float time, float zoom )
+{
+	mLookaroundOverride			= true;
+
+	mLookaroundHeadingTarget	= heading;
+	mLookaroundTiltTarget		= tilt;
+
+	// Calculate delta - the amount to move in 1 second.
+	if( time > 0.0f )
+	{
+		mLookaroundHeadingDelta		= ( heading - mLookaroundHeading ) / time;
+		mLookaroundTiltDelta		= ( tilt - mLookaroundTilt ) / time;
+	}
+
+	mLookaroundDeltaTimer		= time;
+	mLookaroundZoom				= zoom;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCam::ClearLookaround( void )
+{
+	mLookaroundOverride = false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CSkaterCam::SetShake( float duration, float vert_amp, float horiz_amp, float vert_vel, float horiz_vel )
+{
+	mShakeDuration			= duration;
+	mShakeInitialDuration	= duration;
+	mShakeMaxVertAmp		= vert_amp;
+	mShakeMaxHorizAmp		= horiz_amp;
+	mShakeVertVel			= vert_vel;
+	mShakeHorizVel			= horiz_vel;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Added by Ken. 
+bool CSkaterCam::UseVertCam( void )
+{   
+	// Use vert cam when in the lip state.
+	if( mpSkaterStateComponent->GetState() == LIP )
+	{
+#ifdef	DEBUG_CAMERA
+		printf ("SkaterCam Using Vert Cam as LIP\n");
+#endif
+		return true;
+	}	
+		
+	if( mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT )  && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS))
+	{
+#ifdef	DEBUG_CAMERA
+		printf ("SkaterCam Using Vert Cam as VERT_AIR\n");
+#endif
+		return true;
+	}
+	
+	if( mpSkaterStateComponent->GetJumpedOutOfLipTrick())
+	{
+#ifdef	DEBUG_CAMERA
+		printf ("SkaterCam Using Vert Cam as Jumped out of lip trick\n");
+#endif
+		return true;
+	}
+
+#ifdef	DEBUG_CAMERA
+		printf ("Skatecam NOT using vert cam\n");
+#endif
+
+
+	return false;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+					 
+void CSkaterCam::addShake( Mth::Vector& cam, Mth::Matrix& frame )
+{
+	float damping		= mShakeDuration / mShakeInitialDuration;
+	float vert_angle	= mShakeDuration * mShakeVertVel * Mth::PI * 2.0f;
+	float horiz_angle	= mShakeDuration * mShakeHorizVel * Mth::PI * 2.0f;
+	float vert_offset	= mShakeMaxVertAmp * sinf( vert_angle ) * damping;
+	float horiz_offset	= mShakeMaxHorizAmp * sinf( horiz_angle ) * damping;
+
+	cam += frame[X] * horiz_offset;
+	cam += frame[Y] * vert_offset;
+
+	mShakeDuration		-= Tmr::FrameLength();
+	if( mShakeDuration < 0.0f )
+		mShakeDuration = 0.0f;
+}
+
+// updates the camera position
+// returns the dist from the camera to the focus point 					 
+float CSkaterCam::Update( bool instantly )
+{
+	
+	if( instantly )
+	{
+		mInstantCount = 3;
+	}
+
+	if( mInstantCount > 0 )
+	{
+		--mInstantCount;
+		instantly = true;
+	}
+
+	// Length of last frame in seconds.
+	float delta_time = Tmr::FrameLength();
+
+	if (mpSkater && mpSkater->mPaused)	  	// Mick: early out of ithe skater is paused, just allow scripted cameras
+	{
+		//return 100000.0f;
+	}
+
+	// Update any values we are currently interpolating.
+	UpdateInterpolators();
+
+		// Would have used a SetPos() function, but the camera update in rwviewer.cpp is spread out all over the place,
+		// so it's not easy to do. We'll just store the old position before updating anything...
+		Gfx::Camera::StoreOldPos();
+
+		Mth::Matrix& frame_matrix	= Gfx::Camera::GetMatrix();
+
+		frame_matrix[X]   		= mLastActualRight;
+		frame_matrix[Y]			= mLastActualUp;
+		frame_matrix[Z]			= mLastActualAt;
+
+		Mth::Vector	current_at_vector( frame_matrix[Z][X], frame_matrix[Z][Y], frame_matrix[Z][Z] ); 
+
+		// Obtain skater state.
+		EStateType	state			= mpSkaterStateComponent->GetState();
+		Mth::Matrix target;
+		if( state == WALL )
+		{
+#ifdef	DEBUG_CAMERA
+		printf ("SkaterCam Using mLastPreWallSkaterMatrix, as wallriding\n");
+#endif
+			target						= mLastPreWallSkaterMatrix;
+		}
+		else
+		{
+#ifdef	DEBUG_CAMERA
+		printf ("SkaterCam Using GetCameraDisplayMatrix\n");
+#endif
+			target						= mpSkater->GetDisplayMatrix();
+			mLastPreWallSkaterMatrix	= target;
+		}
+
+		// Lerp towards the required lean.
+		float cam_lean = 0.0f;
+		if( state == RAIL )
+		{		
+			// Need to translate the lean value [-4096, 4096] into a reasonable camera lean angle.
+			cam_lean = GetSkaterBalanceTrickComponentFromObject(mpSkater)->mGrind.mManualLean * LEAN_TO_SKATERCAM_LEAN;
+		}
+
+		if( cam_lean != mLean )
+		{
+			if( instantly )
+			{
+				mLean = cam_lean;
+			}
+			else
+			{
+				mLean += (( cam_lean - mLean ) * mGrindLerp );
+			}
+		}
+		
+
+		Mth::Vector skater_up;
+					
+		if (state != GROUND)
+		{
+			skater_up	= target[Y];
+		}
+		else
+		{
+			skater_up	= mpSkaterPhysicsComponent->m_display_normal;
+		}
+
+//		Gfx::AddDebugLine( p_skater->m_pos, ( target[X] * 72.0f ) + p_skater->m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 4 );
+//		Gfx::AddDebugLine( p_skater->m_pos, ( target[Y] * 72.0f ) + p_skater->m_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 4 );
+//		Gfx::AddDebugLine( p_skater->m_pos, ( target[Z] * 72.0f ) + p_skater->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+		
+
+		// Use vel for forward facing, if moving in xz plane.
+		Mth::Vector vel_xz( mpSkater->GetVel().GetX(), 0.0f, mpSkater->GetVel().GetZ());
+
+		bool use_vert_cam = UseVertCam();
+
+		// use velocity if physics not paused, and if we are moving fast enough, or if we are doing vert cam
+		// or if we are doing spine physics
+		if( /* !mpSkater->m_physics_paused && */ (vel_xz.Length() > 0.1f || use_vert_cam  || mpSkaterStateComponent->GetFlag( SPINE_PHYSICS )))
+		{
+			if( use_vert_cam )
+			{
+				// If it's vert, then set target direction to near straight down.
+				target[Z].Set( 0.0f, -1.0f, 0.0f );
+
+				// Zero lookaround when in vert air (and not in lookaround locked or override mode, or doing a lip trick).
+				if( !mLookaroundLock && !mLookaroundOverride && ( state != LIP ))
+				{
+					mLookaroundTilt		= 0.0f;
+					mLookaroundHeading	= 0.0f;
+				}
+			}
+			else
+			{
+				
+			
+				target[Z]		= mpSkater->GetVel();
+				#if 1	// for use with non-straight spine transfer physics
+				if (mpSkaterStateComponent->GetFlag( SPINE_PHYSICS ))
+				{
+					// just use the up velocity, plus the velocity over the spine
+					// will be straight down when coing down the other side
+					target[Z][X] = 0.0f;
+					target[Z][Z] = 0.0f;
+					target[Z] += mpSkaterStateComponent->GetSpineVel();				
+				}
+				#endif
+				target[Z].Normalize();
+
+//				printf ("Using Velocity (%f, %f, %f)\n", target[Z][X],target[Z][Y], target[Z][Z]);
+
+//				Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 128, 128, 128 ), MAKE_RGB( 128, 128, 128 ), 4 );
+
+				// Flatten out the velocity component perpendicular to the ground. However, if the skater is in the air, the
+				// m_last_normal will contain the last ground normal, which we don't want, so set it to be 'up'.
+				float			dot;
+				Mth::Vector		normal;
+
+				if( state == LIP )
+				{
+					
+				
+					// Patch for spine physics
+					// only reduce component perpendicular to plane by 60%
+					// so we still get some up/down camera movmeent 
+					// only do this when moving down though, otherwise it looks poo
+					if (mpSkaterStateComponent->GetFlag(SPINE_PHYSICS) && target[Z][Y] < 0.0f)
+					{
+//						normal	= mpSkaterPhysicsComponent->m_current_normal;
+						normal.Set( 0.0f, 1.0f, 0.0f );
+						//dot		= (( target[Z].GetX() * normal.GetX() ) + ( target[Z].GetY() * normal.GetY() ) + ( target[Z].GetZ() * normal.GetZ() ));
+						//dot		= dot * 0.8f;
+						dot =  0.7f * Mth::DotProduct(target[Z],normal);	// change this to 0.8 to look down when coming down other side of a spine
+					}
+					else
+					{
+						normal.Set( 0.0f, 1.0f, 0.0f );
+						dot		= target[Z].GetY();
+					}
+					// Set world up as the target vector.
+					target[Y].Set( 0.0f, 1.0f, 0.0f );
+				}
+				else if( state == WALL )
+				{
+					normal.Set( 0.0f, 1.0f, 0.0f );
+					dot		= target[Z].GetY();
+				}
+				else
+				{
+					normal	= mpSkaterPhysicsComponent->m_current_normal;
+					//dot		= (( target[Z].GetX() * normal.GetX() ) + ( target[Z].GetY() * normal.GetY() ) + ( target[Z].GetZ() * normal.GetZ() ));
+					//dot		= dot * 0.8f;
+					dot =  0.8f * Mth::DotProduct(target[Z],normal);
+				}
+
+				//target[Z].Set(	target[Z].GetX() - ( dot * normal.GetX() ), target[Z].GetY() - ( dot * normal.GetY() ), target[Z].GetZ() - ( dot * normal.GetZ() ));
+				target[Z] -= normal * dot;
+				
+				
+				target[Z].Normalize();
+
+//				Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 256, 256, 256 ), MAKE_RGB( 256, 256, 256 ), 4 );
+			}
+
+			// We need to orthonormalize in a specific order, as when the velocity is going backwards, then 
+			// the X orient will be wrong, and so plonk the skater upside down.
+			target[X]			= Mth::CrossProduct( target[Y], target[Z] );
+			target[X].Normalize();
+			target[Y]			= Mth::CrossProduct( target[Z], target[X] );
+			target[Y].Normalize();
+
+//			Gfx::AddDebugLine( mpSkater->m_pos, ( target[X] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 4 );
+//			Gfx::AddDebugLine( mpSkater->m_pos, ( target[Y] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 4 );
+//			Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+		}    							
+							
+		// Tilt further if in regular air (going off a big jump, for example), or doing a lip trick.
+		if( state == LIP )
+		{
+			mTiltAddition += TILT_INCREMENT * Tmr::FrameRatio();
+			if( mTiltAddition > TILT_MAX )
+			{
+				mTiltAddition = TILT_MAX;
+			}				
+		}
+		else if( mTiltAddition > 0.0f )
+		{
+			mTiltAddition -= TILT_RESTORE * Tmr::FrameRatio();
+			if( mTiltAddition < 0.0f )
+			{
+				mTiltAddition = 0.0f;
+			}				
+		}
+		else if( mTiltAddition < 0.0f )
+		{
+			mTiltAddition += TILT_RESTORE * Tmr::FrameRatio();
+			if( mTiltAddition > 0.0f )
+			{
+				mTiltAddition = 0.0f;
+			}				
+		}
+
+		// PJR: Needed to do this to fix a compiler crash on NGC. Hopefully SN will
+		// fix this & I can revert this hack...
+		target.RotateYLocal( mLookaroundHeading + mLookaroundHeadingStartingPoint );
+
+		// Now tilt the matrix down a bit.
+		if( state == LIP )
+		{
+//			target.RotateYLocal( 0.8f );
+//			target.RotateXLocal( mLipTrickTilt + mTiltAddition );
+			_xrotlocal ( target, mLipTrickTilt + mTiltAddition );
+		}					
+		else if( !(mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT )  && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS) ))
+		{
+//			target.RotateXLocal( mTilt + mTiltAddition );
+			_xrotlocal ( target, mTilt + mTiltAddition );
+		}					
+
+		// Now adjust for 'lookaround'.
+//		target.RotateXLocal( mLookaroundTilt );
+		_xrotlocal ( target, mLookaroundTilt );
+
+		static float lip_rotate = 0.0f;
+
+		if( state == LIP )
+		{
+			lip_rotate = 1.5707963f;
+
+			if( mLipSideDecided == 0 )
+			{
+				target.RotateY( lip_rotate );
+
+				// Do collision check.
+				CFeeler feeler;
+
+				Mth::Vector horiz_z( target[Z][X], 0.0f, target[Z][Z] );
+				horiz_z.Normalize();
+				Mth::Vector a = mpSkater->m_pos - ( target[Z] * 3.0f );
+				Mth::Vector b = a - ( horiz_z * 72.0f );
+
+				feeler.SetIgnore(0, mFD_CAMERA_COLLIDABLE);
+				bool collision = feeler.GetCollision(a, b, true);
+				if( !collision )
+				{
+					// Side 1 is fine.
+					mLipSideDecided = 1;
+				}
+				else
+				{
+					float dist = feeler.GetDist();
+					target.RotateY( -lip_rotate * 2.0f );
+					horiz_z.Set( target[Z][X], 0.0f, target[Z][Z] );
+					horiz_z.Normalize();
+					a = mpSkater->m_pos - ( target[Z] * 3.0f );
+					b = a - ( horiz_z * 72.0f );
+
+					collision = feeler.GetCollision(a, b, true);
+					if( !collision )
+					{
+						// Side 2 is fine.
+						mLipSideDecided = 2;
+					}
+					else
+					{
+						if( feeler.GetDist() < dist )
+						{
+							// Side 2 is better than side 1.
+							mLipSideDecided = 2;
+						}
+						else
+						{
+							// Side 1 is better than side 2.
+							target.RotateY( lip_rotate * 2.0f );
+							mLipSideDecided = 1;
+						}
+					}
+				}
+			}
+			else
+			{
+				if( mLipSideDecided == 1 )
+				{
+					target.RotateY( lip_rotate );
+				}
+				else
+				{
+					target.RotateY( -lip_rotate );
+				}
+			}
+		}
+		else
+		{
+			mLipSideDecided = 0;
+		}					
+
+		// Set skater flag to indicate when the liptrick camera has spun to the opposite side.
+		if( mpSkater && ( mLipSideDecided == 2 ))
+		{
+			mpSkater->mScriptFlags |= ( 1 << Script::GetInteger( 0x16b8e4cb /* "FLAG_SKATER_LIPTRICK_CAM_REVERSED" */ ));
+		}
+		else
+		{
+			mpSkater->mScriptFlags &= ~( 1 << Script::GetInteger( 0x16b8e4cb /* "FLAG_SKATER_LIPTRICK_CAM_REVERSED" */ ));
+		}
+		
+//		Gfx::AddDebugLine( mpSkater->m_pos, ( target[X] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 4 );
+//		Gfx::AddDebugLine( mpSkater->m_pos, ( target[Y] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 128, 0 ), MAKE_RGB( 0, 128, 0 ), 4 );
+//		Gfx::AddDebugLine( mpSkater->m_pos, ( target[Z] * 72.0f ) + mpSkater->m_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+
+		// Why doing this, when setting below?
+		Mth::Matrix t_matrix(0.0f, 0.0f, 0.0f);
+		
+		// Since camera points in -Z, but player in +Z, we must negate the X and Z axes
+        t_matrix[X][X]		= -target[0][0];
+		t_matrix[X][Y]		= -target[0][1];
+		t_matrix[X][Z]		= -target[0][2];
+		t_matrix[X][W]		= 0.0f;
+	
+		t_matrix[Y][X]		= target[1][0];
+		t_matrix[Y][Y]		= target[1][1];
+		t_matrix[Y][Z]		= target[1][2];
+		t_matrix[Y][W]		= 0.0f;
+	
+		t_matrix[Z][X]		= -target[2][0];
+		t_matrix[Z][Y]		= -target[2][1];
+		t_matrix[Z][Z]		= -target[2][2];
+		t_matrix[Z][W]		= 0.0f;
+			
+        t_matrix[W][X]		= 0.0f;
+		t_matrix[W][Y]		= 0.0f;
+		t_matrix[W][Z]		= 0.0f;
+		t_matrix[W][W]		= 1.0f;
+        
+		frame_matrix[W][X]	= 0.0f;
+		frame_matrix[W][Y]	= 0.0f;
+		frame_matrix[W][Z]	= 0.0f;
+		frame_matrix[W][W]	= 1.0f;
+
+
+		// if we want an instant update, then just move it
+		if (instantly)
+		{
+			frame_matrix = t_matrix;
+		}
+
+
+		float dotx = Mth::DotProduct( t_matrix[X], frame_matrix[X] );
+		float doty = Mth::DotProduct( t_matrix[Y], frame_matrix[Y] );
+		float dotz = Mth::DotProduct( t_matrix[Z], frame_matrix[Z] );
+
+#define CAMERA_SLERP_STOP 0.9999f
+		
+		if( dotx > CAMERA_SLERP_STOP && doty > CAMERA_SLERP_STOP && dotz > CAMERA_SLERP_STOP )
+		{
+			// Do nothing, camera has reached the target so we just leave the frame matrix exactly where it is.
+//		    frame_matrix = t_matrix;
+        }
+		else
+		{
+			// Initialise the SLERP interpolator, with the current matrix as the start, and the ideal
+			// matrix as the end (target).
+
+            // GARY:  is this right?  i assume p_matrix = &t_matrix
+			mpSlerp->setMatrices( &frame_matrix, &t_matrix );
+
+			// Copy flags and shit (why copying all this when most of the values will be overwritten?)
+			Mth::Matrix stored_frame;
+            stored_frame = frame_matrix;
+            frame_matrix = t_matrix;			
+
+			// Continue to slerp towards the target, updating frame_matrix with the slerped values.
+			if( !(mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS)))
+			{
+				// If the skater has just landed from big air, use the big air land slerp value.
+				if(( mpSkaterStateComponent->GetState() == GROUND ) && ( mVertAirLandedTimer >= delta_time ))
+				{
+					mVertAirLandedTimer -= delta_time;
+					mpSlerp->getMatrix( &frame_matrix, getTimeAdjustedSlerp( mVertAirLandedSlerp, delta_time ));
+				}
+				else
+				{
+					// Clear the vert air landed timer.
+					mVertAirLandedTimer = 0.0f;
+
+					mpSlerp->getMatrix( &frame_matrix, getTimeAdjustedSlerp( mSlerp, delta_time ));
+
+					// At this point we can check to see the angle that the camera's 'at' vector moved through this frame.
+					// If this angle is too large, we need to limit it, regardless of the slerp. (This is primarily to avoid
+					// the nasty jerk when transitioning rails etc.
+					float this_dot = ( current_at_vector.GetX() * frame_matrix[Mth::AT][X] ) 
+                        + ( current_at_vector.GetY() * frame_matrix[Mth::AT][Y] ) 
+                        + ( current_at_vector.GetZ() * frame_matrix[Mth::AT][Z] ); 
+
+					// Dot values will be in the range [0,1], with 1 being no change.
+					if( this_dot < ( mLastDot * 0.9998f ))
+					{
+						// The angular change this frame is too big. Need to recalculate with an adjusted slerp value.
+						float new_slerp = mSlerp * ( this_dot / ( mLastDot * 0.9998f ));
+						mpSlerp->setMatrices( &stored_frame, &t_matrix );
+						mpSlerp->getMatrix( &frame_matrix, getTimeAdjustedSlerp( new_slerp, delta_time ));
+
+						this_dot = mLastDot * 0.9998f;
+					}
+                    
+					mLastDot = this_dot;
+				}
+			}
+			else
+			{
+				mpSlerp->getMatrix( &frame_matrix, getTimeAdjustedSlerp( mVertAirSlerp, delta_time ));
+
+				// Set the vert air landed timer to the max, since the skater is in vert air.
+				mVertAirLandedTimer = VERT_AIR_LANDED_TIME;
+			}						
+		}		
+
+		// At this point, frame_matrix is valid to store.
+		mLastActualRight	= frame_matrix[X];
+		mLastActualUp		= frame_matrix[Y];
+		mLastActualAt		= frame_matrix[Z];
+
+		// Set camera position to be the same as the skater.
+		Mth::Vector	cam_pos = GetTripodPos( instantly );
+
+		// If in the air doing a trick, we slowly reduce the behind value to 'zoom' in on the skater.
+		float above, behind;
+		if (instantly)
+		{
+			float temp = mZoomLerp;
+			mZoomLerp = 1.0f;					// fully lerp instantly
+			CalculateZoom( &above, &behind );
+			mZoomLerp = temp;
+		}
+		else
+		{
+			CalculateZoom( &above, &behind );
+		}
+
+
+
+		
+
+//        printf( "above = %f, behind %f\n", above, behind );
+		
+		//Dbg_Message("Camera before: pos (%5.1f,%5.1f,%5.1f)", cam_pos[X], cam_pos[Y], cam_pos[Z]);
+
+		// Note we use the camera's at vector, as this will keep the skater in the middle of the frame.
+		//cam_pos -= frame_matrix[Z] * behind;
+		cam_pos += frame_matrix[Z] * behind;
+		
+		// Move camera along the Skater's up vector.
+//		cam_pos += mpSkater->GetMatrix()[Y] * above;
+		cam_pos += skater_up * above;
+
+		Mth::Vector	actual_focus_pos = mpSkater->m_pos + ( skater_up * above );
+
+
+//		Gfx::AddDebugLine( mpSkater->m_pos, actual_focus_pos, MAKE_RGB( 0, 0, 128 ), MAKE_RGB( 0, 0, 128 ), 4 );
+													  
+//		Dbg_Message("Matrix Z  (%5.4f,%5.4f,%5.4f)", frame_matrix[Z][X], frame_matrix[Z][Y], frame_matrix[Z][Z]);
+//		Dbg_Message("Matrix UP (%5.4f,%5.4f,%5.4f)", skater_up[X], skater_up[Y], skater_up[Z]);
+//		Dbg_Message("Above %f Behind %f", above, behind);
+//		Dbg_Message("focus pos (%5.1f,%5.1f,%5.1f)", actual_focus_pos[X], actual_focus_pos[Y], actual_focus_pos[Z]);
+//		Dbg_Message("Camera after: pos (%5.1f,%5.1f,%5.1f)", cam_pos[X], cam_pos[Y], cam_pos[Z]);
+
+		// Reorient the camera to look directly at the skater. We don't want to look at the interpolated position,
+		// since this can lead to nasty jerkiness, especially on rails etc.
+		
+
+		target[Z].Set( actual_focus_pos.GetX() - cam_pos.GetX(), actual_focus_pos.GetY() - cam_pos.GetY(), actual_focus_pos.GetZ() - cam_pos.GetZ());
+		target[Z].Normalize();
+
+		// Read back the Y from the current matrix.
+		target[Y][0]	= frame_matrix[Y][X];
+		target[Y][1]	= frame_matrix[Y][Y];
+		target[Y][2]	= frame_matrix[Y][Z];
+
+		// Generate new orthonormal X and Y axes.
+		target[X]		= Mth::CrossProduct( target[Y], target[Z] );
+		target[X].Normalize();
+		target[Y]		= Mth::CrossProduct( target[Z], target[X] );
+		target[Y].Normalize();
+
+		// Here is where lean may safely be applied without moving the focus position on screen.
+		if( mLean != 0.0f )
+		{
+			target[Y].Rotate( target[Z], mLean );
+			target[X].Rotate( target[Z], mLean );
+		}
+
+		// Write back into camera matrix.
+		// Since camera points in -Z, but player in +Z, we must negate the X and Z axes
+        frame_matrix[X][X]		= -target[0][0];
+        frame_matrix[X][Y]		= -target[0][1];
+        frame_matrix[X][Z]		= -target[0][2];
+        frame_matrix[X][W]		= 0.0f;
+
+        frame_matrix[Y][X]		= target[1][0];
+        frame_matrix[Y][Y]		= target[1][1];
+        frame_matrix[Y][Z]		= target[1][2];
+        frame_matrix[Y][W]		= 0.0f;
+
+        frame_matrix[Z][X]		= -target[2][0];
+        frame_matrix[Z][Y]		= -target[2][1];
+        frame_matrix[Z][Z]		= -target[2][2];
+        frame_matrix[Z][W]		= 0.0f;
+
+		// Update the position if there is a shake active.
+		if(	mShakeDuration > 0.0f )
+		{
+			addShake( cam_pos, frame_matrix );
+		}
+		
+		// Now do collision detection.
+		Mth::Vector at_pos = cam_pos - ((Mth::Vector)( frame_matrix[Z] )) * behind;
+
+		// camera collision detection
+		
+		if (mpSkaterPhysicsComponent->mp_movable_contact_component->GetContact() && state == LIP)
+		{
+			// no camera collision if doing a lipt trick on a moving object
+			//printf ("Lip trick on moving object\n");
+			
+		}
+		else
+		{
+			CFeeler feeler;
+			// Ignore faces with mFD_NON_COLLIDABLE set, and ignore faces with mFD_CAMERA_COLLIDABLE not set.
+			feeler.SetIgnore(0, mFD_CAMERA_COLLIDABLE);
+			bool collision = feeler.GetCollision(at_pos, cam_pos, true);
+			if( collision )
+			{
+				
+				//Gfx::AddDebugLine( at_pos, cam_pos, MAKE_RGB( 128, 0, 0 ), MAKE_RGB( 128, 0, 0 ), 2000 );
+	
+				// Limits the camera to getting nearer than 11.9 inches to the focus point.
+				float distance = feeler.GetDist();
+				if(( behind * distance * 0.9f ) < 11.9f )
+				{
+					distance = 11.9f / ( behind * 0.9f );
+				}
+				
+	
+				cam_pos = at_pos + (( cam_pos - at_pos ) * ( distance * 0.9f ));
+			}
+	
+#			ifdef __PLAT_NGPS__
+			// Now do two additional checks 1 foot to either side of the camera.
+			Mth::Vector	left( frame_matrix[X][X], 0.0f, frame_matrix[X][Z], 0.0f );
+			left.Normalize( 8.0f );
+			left += cam_pos;
+	
+			collision = feeler.GetCollision(cam_pos, left, true);
+			if( collision )
+			{
+				left		   -= feeler.GetPoint();
+				cam_pos		   -= left;
+			}
+			else
+			{
+				Mth::Vector	right( -frame_matrix[X][X], 0.0f, -frame_matrix[X][Z], 0.0f );
+				right.Normalize( 8.0f );
+				right += cam_pos;
+	
+				collision = feeler.GetCollision(cam_pos, right, true);
+				if( collision )
+				{
+					right		   -= feeler.GetPoint();
+					cam_pos		   -= right;
+				}
+			}
+	
+			if(collision )
+			{
+				// Re-orient the camera again.
+				target[Z].Set( actual_focus_pos.GetX() - cam_pos.GetX(), actual_focus_pos.GetY() - cam_pos.GetY(), actual_focus_pos.GetZ() - cam_pos.GetZ());
+				target[Z].Normalize();
+	
+				// Read back the Y from the current matrix.
+	//			target[Y][0]	= p_frame_matrix->up.x;
+	//			target[Y][1]	= p_frame_matrix->up.y;
+	//			target[Y][2]	= p_frame_matrix->up.z;
+	
+				// Generate new orthonormal X and Y axes.
+				target[X]		= Mth::CrossProduct( target[Y], target[Z] );
+				target[X].Normalize();
+				target[Y]		= Mth::CrossProduct( target[Z], target[X] );
+				target[Y].Normalize();
+	
+				// Write back into camera matrix.
+				frame_matrix[X][X]	= -target[0][0];
+				frame_matrix[X][Y]	= -target[0][1];
+				frame_matrix[X][Z]	= -target[0][2];
+				frame_matrix[Y][X]	= target[1][0];
+				frame_matrix[Y][Y]	= target[1][1];
+				frame_matrix[Y][Z]	= target[1][2];
+				frame_matrix[Z][X]	= -target[2][0];
+				frame_matrix[Z][Y]  = -target[2][1];
+				frame_matrix[Z][Z]	= -target[2][2];
+			}
+#			endif
+		}
+
+		cam_pos[W] = 1.0f;
+		//Dbg_MsgAssert(cam_pos[W] == 1.0f, ("cam_pos W is %f, not 1.0", cam_pos[W]));
+		Gfx::Camera::SetPos(cam_pos);
+
+		// store the position of the camera in CSkaterCam also (used for proximity nodes)
+		m_pos = cam_pos;
+		Mth::Vector		to_target = at_pos - cam_pos;
+				
+				
+//		Dbg_Message("Camera Final: pos (%5.1f,%5.1f,%5.1f)", cam_pos[X], cam_pos[Y], cam_pos[Z]);
+
+		
+		
+		return to_target.Length();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Mth::Vector CSkaterCam::GetTripodPos( bool instantly )
+{
+	float		lerp_xz;
+	float		lerp_y;
+	float		delta		= Tmr::FrameLength();
+	Mth::Vector now			= mpSkater->m_pos;
+
+	// The tripod pos is always *tending* towards the skater position, but the rate at which it does so is
+	// definable, to allow for some lag, which improves the feeling of speed.
+	if( mLerpReductionTimer > 0.0f )
+	{
+		mLerpReductionTimer -= delta;
+		if( mLerpReductionTimer > 0.0f )
+		{
+			mLerpReductionMult += ( mLerpReductionDelta * delta );
+		}
+		else
+		{
+			mLerpReductionMult	= 1.0f;
+			mLerpReductionTimer	= 0.0f;
+		}
+	}
+	else
+	{
+		mLerpReductionMult	= 1.0f;
+	}
+
+	if(( mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS)))
+	{
+		#ifdef	DEBUG_CAMERA
+		printf ("Skatercam using mVertAirLerp\n");
+		#endif
+
+		lerp_xz	= getTimeAdjustedSlerp( mVertAirLerpXZ * mLerpReductionMult, delta );
+		lerp_y	= getTimeAdjustedSlerp( mVertAirLerpY * mLerpReductionMult, delta );
+	}
+	else
+	{
+		lerp_xz	= getTimeAdjustedSlerp( mLerpXZ * mLerpReductionMult, delta );
+		lerp_y	= getTimeAdjustedSlerp( mLerpY * mLerpReductionMult, delta );
+
+		// Added the following check (Dave 7/16/02) to help eliminate the camera rattle when snapping on/off a curb.
+		// The flag is set for one frame only.
+		if( mpSkaterStateComponent->GetFlag( SNAPPED_OVER_CURB ))
+		{
+			lerp_y = 1.0f;
+		}
+	}
+
+	if( instantly )
+	{
+		mLastTripodPos = now;
+	}
+	else
+	{
+		mLastTripodPos.Set( mLastTripodPos.GetX() + (( now.GetX() - mLastTripodPos.GetX() ) * lerp_xz ),
+							mLastTripodPos.GetY() + (( now.GetY() - mLastTripodPos.GetY() ) * lerp_y ),
+							mLastTripodPos.GetZ() + (( now.GetZ() - mLastTripodPos.GetZ() ) * lerp_xz ));
+	}
+
+	return mLastTripodPos;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::CalculateZoom( float* p_above, float* p_behind )
+{
+	mTargetZoom = 1.0f;
+
+	// Deal with zoom for Big Air. Set the flag only when a trick is started.
+	if( !mBigAirTrickZoomActive && (mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS)) && mpSkaterStateComponent->DoingTrick() )
+	{
+		mBigAirTrickZoomActive = true;
+	}
+	else if( !(mpSkaterStateComponent->GetFlag( VERT_AIR ) && !mpSkaterStateComponent->GetFlag( CAN_BREAK_VERT ) && !mpSkaterStateComponent->GetFlag(SPINE_PHYSICS)))
+	{
+		// Must have landed.
+		mBigAirTrickZoomActive = false;
+	}
+
+	if( mBigAirTrickZoomActive )
+	{
+		mTargetZoom = mBigAirTrickZoom;
+	}
+	else
+	{
+		EStateType state = mpSkaterStateComponent->GetState();
+
+		// If the skater is grinding, zoom also.
+		if( state == RAIL )
+		{
+			mTargetZoom = mGrindZoom;
+		}
+		else if( state == LIP )
+		{
+			mTargetZoom = mLipTrickZoom;
+		}
+		else if( state == LIP )
+		{
+			mTargetZoom = mLipTrickZoom;
+		}
+	}
+
+	
+	// If lookaround override is set, factor in the lookaround override zoom.
+	if( mLookaroundOverride && ( mLookaroundZoom != 1.0f ))
+	{
+		mCurrentZoom = mTargetZoom;
+		mCurrentZoom *= mLookaroundZoom;
+	}
+	else
+	{
+		mCurrentZoom += (( mTargetZoom - mCurrentZoom ) * mZoomLerp );
+	}
+	
+	*p_behind = mBehind * mCurrentZoom;
+
+
+	// Behind is also shortened when the lookaround camera is tilting upwards.
+	if( mLookaroundTilt < 0.0f )
+	{
+		float max_tilt = 3.14f * 0.2f;
+		*p_behind = *p_behind * ( 0.4f + ( 0.6f * (( max_tilt + mLookaroundTilt ) / max_tilt )));
+	}
+
+
+	// Use lip_trick_above when doing a lip trick.
+	float above_val = mAbove;
+	if( mpSkaterStateComponent->GetState() == LIP )
+	{
+		above_val = mLipTrickAbove;
+	}
+	
+
+	// Figure above tending towards the perfect above, if zoom is < 1.0.
+	if( mCurrentZoom < 1.0f )
+	{
+		*p_above = PERFECT_ABOVE + (( above_val - PERFECT_ABOVE ) * mCurrentZoom );
+	}
+	else
+	{
+		*p_above = above_val;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::UpdateInterpolators( void )
+{
+	float delta_time = Tmr::FrameLength();
+
+	if( mLookaroundOverride )
+	{
+		if( mLookaroundDeltaTimer > 0.0f )
+		{
+			if( mLookaroundDeltaTimer > delta_time )
+			{
+				mLookaroundHeading		+= mLookaroundHeadingDelta * delta_time;
+				mLookaroundTilt			+= mLookaroundTiltDelta * delta_time;
+				mLookaroundDeltaTimer	-= delta_time;
+			}
+			else
+			{
+				mLookaroundHeading		= mLookaroundHeadingTarget;
+				mLookaroundTilt			= mLookaroundTiltTarget;
+				mLookaroundDeltaTimer	= 0.0f;
+			}
+		}
+	}
+
+	if( mBehindInterpolatorTime > 0.0f )
+	{
+		mBehindInterpolatorTime -= SKATERCAM_INTERPOLATOR_TIMESTEP;
+		mBehind += mBehindInterpolatorDelta;		
+	}
+	else
+	{
+		mBehindInterpolatorTime = 0.0f;
+	}
+
+	if( mAboveInterpolatorTime > 0.0f )
+	{
+		mAboveInterpolatorTime -= SKATERCAM_INTERPOLATOR_TIMESTEP;
+		mAbove += mAboveInterpolatorDelta;		
+	}
+	else
+	{
+		mAboveInterpolatorTime = 0.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::SetLerpReductionTimer( float time )
+{
+	if( time > 0.0f )
+	{
+		mLerpReductionTimer	= time;
+		mLerpReductionDelta	= 1.0f / time;
+		mLerpReductionMult	= 0.0f;
+	}
+	else
+	{
+		mLerpReductionTimer	= 0.0f;
+		mLerpReductionDelta	= 0.0f;
+		mLerpReductionMult	= 1.0f;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::SetSkater( CSkater* skater )
+{
+	mpSkater = skater;
+	Dbg_Assert(mpSkater);
+	
+	mpSkaterPhysicsComponent = GetSkaterCorePhysicsComponentFromObject(skater);
+	Dbg_Assert(mpSkaterPhysicsComponent);
+	
+	mpSkaterStateComponent = GetSkaterStateComponentFromObject(skater);
+	Dbg_Assert(mpSkaterStateComponent);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkater* CSkaterCam::GetSkater( void )
+{
+	return mpSkater;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCam::BindToController( int controller )
+{
+	// swap
+	Spt::SingletonPtr< Inp::Manager > inp_manager;
+	inp_manager->ReassignHandler( *m_input_handler, controller );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Obj
diff --git a/Code/Sk/Objects/skatercam.h b/Code/Sk/Objects/skatercam.h
new file mode 100644
index 0000000..fe8ded3
--- /dev/null
+++ b/Code/Sk/Objects/skatercam.h
@@ -0,0 +1,229 @@
+//****************************************************************************
+//* MODULE:         Gfx
+//* FILENAME:       skatercam.cpp
+//* OWNER:          Dave Cowling
+//* CREATION DATE:  02/05/2001
+//****************************************************************************
+
+#ifndef __OBJECTS_SKATERCAM_H
+#define __OBJECTS_SKATERCAM_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**							Forward Declarations							**
+*****************************************************************************/
+
+namespace Mth
+{
+    class SlerpInterpolator;
+};
+
+#if 0
+namespace NxPlugin
+{
+	class CameraHeader;
+};
+#endif
+                   
+namespace Obj
+{
+    class CSkater;
+    class CSkaterCorePhysicsComponent;
+    class CSkaterStateComponent;
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class CSkaterCam : public Gfx::Camera
+{
+public:
+	enum ESkaterCamMode
+	{
+		SKATERCAM_MODE_UNDEFINED		= 0,
+		SKATERCAM_MODE_FIRST_VALID		= 1,
+		SKATERCAM_MODE_NORMAL_NEAR		= 1,
+		SKATERCAM_MODE_NORMAL_MEDIUM,
+		SKATERCAM_MODE_NORMAL_FAR,
+		SKATERCAM_MODE_NORMAL_MEDIUM_LTG,
+        SKATERCAM_NUM_NORMAL_MODES,
+		SKATERCAM_FIRST_REPLAY_MODE = SKATERCAM_NUM_NORMAL_MODES,
+		SKATERCAM_MODE_REPLAY_FRONT = SKATERCAM_NUM_NORMAL_MODES,
+		SKATERCAM_MODE_REPLAY_FRONT_ZOOM,
+        SKATERCAM_MODE_REPLAY_LEFT,
+		SKATERCAM_MODE_REPLAY_LEFT_ZOOM,
+		SKATERCAM_MODE_REPLAY_BEHIND,
+		SKATERCAM_MODE_REPLAY_BEHIND_ZOOM,
+		SKATERCAM_MODE_REPLAY_RIGHT,
+		SKATERCAM_MODE_REPLAY_RIGHT_ZOOM,
+		SKATERCAM_LAST_REPLAY_MODE = SKATERCAM_MODE_REPLAY_RIGHT_ZOOM,
+		SKATERCAM_NUM_MODES,
+	};
+
+public:
+
+					CSkaterCam( int player );
+					~CSkaterCam( void );
+	bool			UseVertCam();
+	float			Update( bool instantly = false );
+	bool			IsAnimFinished( void );
+	bool			IsAnimHeld( void );
+	void			SetAnimSkippable( bool skippable );
+	void			SwitchReplayMode( bool increment );
+	void			SetMode( ESkaterCamMode mode, float time = 1.0f );
+	int				GetMode( void )								{ return (int)mMode; }
+	void			SetLookaround( float heading, float tilt, float time, float zoom = 1.0f );
+	void			ClearLookaround( void );
+	void			ClearLookaroundLock( void )							{ mLookaroundLock = false; }
+	void			SetShake( float duration, float vert_amp, float horiz_amp, float vert_vel, float horiz_vel );
+	void			EnableInputHandler( bool enable );
+	void			SetSkater( CSkater* skater );
+	void			SetLerpReductionTimer( float time );
+	CSkater*		GetSkater( void );
+	void 			ResetLookAround( void );
+
+	void			BindToController( int controller );
+
+	// needed for proxim.cpp (each camera stores its position 
+	// after writing that position to render camera)
+//	Mth::Vector		m_pos;
+
+	bool			mSkipButton;
+	bool			mRightButton;
+	int				mRightX;
+	int				mRightY;
+	ESkaterCamMode	mMode;
+	
+private:
+
+	Mth::Vector		GetTripodPos( bool instantly = false );
+	void			UpdateInterpolators( void );
+	void			CalculateZoom( float* p_above, float* p_behind );
+	void			addShake( Mth::Vector& cam, Mth::Matrix& frame );
+
+	Mth::SlerpInterpolator*	mpSlerp;
+
+	CSkater*		mpSkater;
+	CSkaterCorePhysicsComponent*	mpSkaterPhysicsComponent;
+	CSkaterStateComponent*	mpSkaterStateComponent;
+
+	float			mLastDot;
+	Mth::Vector		mLastTripodPos;
+
+	static						Inp::Handler< CSkaterCam >::Code s_input_logic_code;
+	Inp::Handler< CSkaterCam >*	m_input_handler;
+//	static						Inp::Handler< CSkaterCam >::Code s_input_logic_code_special;
+//	Inp::Handler< CSkaterCam >*	m_input_handler_special;
+
+	bool			m_input_enabled;
+	
+	int				mInstantCount;				// Used to maintain the 'instantly' flag during update for several frames.
+
+	float			mHorizFOV;
+
+	Mth::Vector		mLastActualRight;			// Used because the frame is adjusted after focus LERPING.
+	Mth::Vector		mLastActualUp;
+	Mth::Vector		mLastActualAt;
+
+	Mth::Matrix		mLastPreWallSkaterMatrix;	// Used because we don't want to use the skater's matrix during wall rides.
+
+	float			mBehind;
+	float			mBehindInterpolatorTime;
+	float			mBehindInterpolatorDelta;
+
+	float			mAbove;
+	float			mAboveInterpolatorTime;
+	float			mAboveInterpolatorDelta;
+
+	bool			mBigAirTrickZoomActive;
+	float			mCurrentZoom;
+	float			mTargetZoom;
+	float			mZoomLerp;				// LERP rate for zoom.
+	float			mBigAirTrickZoom;		// Target zoom for big air trick.
+	float			mGrindZoom;				// Target zoom for grind.
+	float			mLipTrickZoom;			// Target zoom for lip trick.
+	int				mLipSideDecided;
+
+	float			mVertAirLandedTimer;	// Defines how long the vert air landed slerp gets applied.
+
+	float			mLerpReductionTimer;
+	float			mLerpReductionDelta;
+	float			mLerpReductionMult;
+
+	bool			mLookaroundLock;
+	bool			mLookaroundOverride;	// For when the designer is scripting the lookaround values.
+	float			mLookaroundZoom;		// Allows designers to adjust the zoom when overrideing the lookaround values.
+	float			mLookaroundTilt;
+	float			mLookaroundHeading;
+	float			mLookaroundHeadingStartingPoint;
+	float			mLookaroundTiltTarget;
+	float			mLookaroundHeadingTarget;
+	float			mLookaroundTiltDelta;
+	float			mLookaroundHeadingDelta;
+	float			mLookaroundDeltaTimer;
+
+	float			mTilt;
+	float			mTiltAddition;
+	float			mLipTrickTilt;
+	float			mLipTrickAbove;
+	float			mSlerp;
+	float			mVertAirSlerp;
+	float			mVertAirLandedSlerp;
+	float			mOriginOffset;
+
+	float			mShakeDuration;			// Shake duration in seconds (0.0 means no shake).
+	float			mShakeInitialDuration;
+	float			mShakeMaxVertAmp;
+	float			mShakeMaxHorizAmp;
+	float			mShakeVertVel;
+	float			mShakeHorizVel;
+	
+	float			mLean;					// For lean during grind imbalance.
+
+	float			mLerpXZ;
+	float			mLerpY;
+	float			mVertAirLerpXZ;
+	float			mVertAirLerpY;
+	float			mGrindLerp;
+};
+	
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_SKATERCAM_H
diff --git a/Code/Sk/Objects/skatercareer.cpp b/Code/Sk/Objects/skatercareer.cpp
new file mode 100644
index 0000000..183c0d0
--- /dev/null
+++ b/Code/Sk/Objects/skatercareer.cpp
@@ -0,0 +1,832 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Objects (OBJ) 											**
+**																			**
+**	File name:		objects/SkaterCareer.cpp								**
+**																			**
+**	Created: 		21/5/01 - Mick (Based on SkaterApperance.cpp by gj)		**
+**																			**
+**	Description:	Skater career code 										**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+bool g_CheatsEnabled = false;
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Obj
+{
+
+
+static bool						s_is_competition[CSkaterCareer::vMAX_CAREER_LEVELS];
+	
+	
+	
+
+int CountOnes(uint32 v)
+{
+	
+
+	int NumOnes=0;
+	for (int i=0; i<32; ++i)
+	{
+		if (v&1)
+		{
+			++NumOnes;
+		}
+		v>>=1;
+	}		
+	return NumOnes;
+}
+
+int CountOnes(uint64 v)
+{
+	
+
+	int NumOnes=0;
+	for (int i=0; i<64; ++i)
+	{
+		if (v&1)
+		{
+			++NumOnes;
+		}
+		v>>=1;
+	}		
+	return NumOnes;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CSkaterCareer::CSkaterCareer()
+{
+   
+	for (unsigned int i = 0; iReset();
+		}
+	}
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCareer::Reset()
+{
+	
+	
+	Init(false);
+	
+	return true;
+}
+
+
+/******************************************************************/
+// Return the gap checklist, either for the current level
+// of for the level specified
+					 
+					 
+Obj::CGapChecklist	*		CSkaterCareer::GetGapChecklist(int level)
+{
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+
+	Dbg_MsgAssert(level >= 0 && level < (int)vMAX_CAREER_LEVELS,("bad level number (%d) in  GetGapChecklist",level));
+	return mp_gap_checklist[level];
+}
+
+bool CSkaterCareer::GotAllGaps()
+{
+	//Rulon: Made this function more robust to handle the changing level arrangement.
+	Script::CArray *p_array = Script::GetArray(CRCD(0xeb1a4bc9,"levels_with_gaps"),Script::ASSERT);
+	uint32 array_size = p_array->GetSize();
+
+	for ( uint32 i = 0; i < vMAX_CAREER_LEVELS; ++i )
+	{
+		// Skip up levels we dont care about
+		uint32 j = 0;
+		for ( j = 0; j < array_size; ++j) 
+		{
+			uint32 check = p_array->GetChecksum(j);
+			uint32 level_num = Script::GetInt(check ,Script::ASSERT);
+			if( i == level_num )
+			{
+				//printf("level %i\n", i);
+				break;
+			}
+		}
+		
+		if (j == array_size)
+			continue;
+
+		Obj::CGapChecklist* pGapChecklist = GetGapChecklist( i );
+		
+		// If this level should be taken into account but is unvisited
+		if ( !this->HasVisitedLevel( i ) )
+		{
+			 //printf("haven't been to level %i\n", i);
+			return false;
+		}
+
+		if ( !pGapChecklist->GotAllGaps() )
+		{
+			//printf("didn't get all the gaps in level %i\n", i);
+			return false;
+		}
+		 //printf("got all gaps in level %i\n", i);
+	}
+	  return true;
+	
+}
+					 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterCareer::StartLevel(int level)
+{
+	
+
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	else
+	{	
+		m_current_level = level;
+	}
+
+	this->MarkLevelVisited( m_current_level );
+
+	Dbg_MsgAssert(m_current_level < (int)vMAX_CAREER_LEVELS,( "level number %d too big",m_current_level)); 
+	m_start_goal_flags[0]	= m_goal_flags[level][0];
+	m_start_goal_flags[1]	= m_goal_flags[level][1];
+	m_start_level_flags[0]	= m_level_flags[level][0];
+	m_start_level_flags[1]	= m_level_flags[level][1];
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int CSkaterCareer::GetLevel()
+{
+	
+	return m_current_level;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterCareer::MarkLevelVisited( int level_num )
+{
+	Dbg_MsgAssert( level_num >= 0 && level_num < (int)vMAX_CAREER_LEVELS, ( "MarkLevelVisited called with bad level num %i", level_num ) );
+	m_level_visited[level_num] = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CSkaterCareer::HasVisitedLevel( int level_num )
+{
+	Dbg_MsgAssert( level_num >= 0 && level_num < (int)vMAX_CAREER_LEVELS, ( "MarkLevelVisited called with bad level num %i", level_num ) );
+	return m_level_visited[level_num];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterCareer::SetGoal(int goal, int level)
+{
+	
+
+	Dbg_MsgAssert(goal >= 0 && goal <256, ("goal %d out of range"));
+	
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	
+	m_goal_flags[level][goal>>5] |= (1<<(goal&31));
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterCareer::UnSetGoal(int goal, int level)
+{
+	
+	
+	Dbg_MsgAssert(goal >= 0 && goal <256, ("goal %d out of range"));
+	
+	
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	
+	m_goal_flags[level][goal>>5] &= ~(1<<(goal&31));
+	m_start_goal_flags[goal>>5]  &= ~(1<<(goal&31));
+	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSkaterCareer::GetGoal(int goal, int level)
+{
+	
+	Dbg_MsgAssert(goal >= 0 && goal <256, ("goal %d out of range"));
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	
+	if (m_goal_flags[level][goal>>5] & (1<<(goal&31)))
+	{
+		return true;
+	}
+	
+	return false;
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSkaterCareer::JustGotGoal(int goal)
+{
+	
+	Dbg_MsgAssert(goal >= 0 && goal <256, ("goal %d out of range"));
+
+	// if start goal flag is different from the current one, then we must
+	// have just got the goal	
+	if ((m_goal_flags[m_current_level][goal>>5] ^ m_start_goal_flags[goal>>5]) & (1<<(goal&31)) )
+	{
+		return true;
+	}
+	
+	return false;
+
+}
+
+int	CSkaterCareer::CountGoalsCompleted(int level)
+{
+	
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	return CountOnes(m_goal_flags[level][0]) +  CountOnes(m_goal_flags[level][1]);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	CSkaterCareer::CountTotalGoalsCompleted()
+{
+	
+	int Goals=0;
+	for (uint i=0; i 0) ? 1:0;		// count one per level
+		}
+	}
+	return Medals;
+}
+
+// Returns 0 if no medal was won.
+// If a medal was won, it returns 1+the best-medal number, where the medal number is whatever it is defined to be
+// in goal_scripts.q
+int CSkaterCareer::GetBestMedal(int Level)
+{
+	
+	
+	Dbg_MsgAssert(Level>=0 && Level<(int)vMAX_CAREER_LEVELS,("Bad Level of %d sent to GetBestMedal",Level));
+	
+	int Gold=Script::GetInteger("GOAL_GOLD");
+	int Silver=Script::GetInteger("GOAL_SILVER");
+	int Bronze=Script::GetInteger("GOAL_BRONZE");
+	
+	Dbg_MsgAssert(s_is_competition[Level],("Level %d is not a competition level",Level));
+	
+	if (m_goal_flags[Level][Gold>>5] & (1<>5] & (1<>5] & (1<InNetGame()) return false;
+	return g_CheatsEnabled;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterCareer::SetGlobalFlag(int flag)
+{
+	
+	Dbg_MsgAssert(flag<512,("Flag %d is over 255",flag));
+	m_global_flags[flag>>5] |= (1<<(flag&31));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterCareer::UnSetGlobalFlag(int flag)
+{
+	
+	Dbg_MsgAssert(flag<512,("Flag %d is over 255",flag));
+	m_global_flags[flag>>5] &= ~(1<<(flag&31));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSkaterCareer::GetGlobalFlag(int flag)
+{
+	
+	Dbg_MsgAssert(flag<512,("Flag %d is over 255",flag));
+	if (m_global_flags[flag>>5] & (1<<(flag&31)))
+	{
+		return true;
+	}
+	
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterCareer::SetFlag(int flag, int level)
+{
+	
+	Dbg_MsgAssert(flag<256,("Flag %d is over 255",flag));
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	m_level_flags[level][flag>>5] |= (1<<(flag&31));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CSkaterCareer::UnSetFlag(int flag, int level)
+{
+	
+	Dbg_MsgAssert(flag<256,("Flag %d is over 255",flag));
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	m_level_flags[level][flag>>5] &= ~(1<<(flag&31));
+	m_start_level_flags[flag>>5]  &= ~(1<<(flag&31));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSkaterCareer::GetFlag(int flag, int level)
+{
+	
+	Dbg_MsgAssert(flag<256,("Flag %d is over 255",flag));
+	if (level == -1)
+	{
+		level = m_current_level;
+	}
+	
+	if (m_level_flags[level][flag>>5] & (1<<(flag&31)))
+	{
+		return true;
+	}
+	
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	CSkaterCareer::JustGotFlag(int flag)
+{
+	
+	Dbg_MsgAssert(flag<256,("Flag %d is over 255",flag));
+	// if start goal flag is different from the current one, then we must
+	// have just got the goal	
+	if ((m_level_flags[m_current_level][flag>>5] ^ m_start_level_flags[flag>>5]) & (1<<(flag&31)) )
+	{
+		return true;
+	}
+	
+	return false;
+}
+
+
+void CSkaterCareer::WriteIntoStructure(Script::CScriptStructure *pIn)
+{
+	
+	// Create an array for holding the goal flags
+	Script::CArray *pGoalFlags=new Script::CArray;
+	pGoalFlags->SetSizeAndType(8*vMAX_CAREER_LEVELS, ESYMBOLTYPE_INTEGER);
+
+	// Wack 'em all in
+	for (unsigned int i = 0; iSetInteger(i*8+j,m_goal_flags[i][j]);
+		}
+	}	
+	
+	// Create an array for holding the level flags
+	Script::CArray *pLevelFlags=new Script::CArray;
+	pLevelFlags->SetSizeAndType(8*vMAX_CAREER_LEVELS, ESYMBOLTYPE_INTEGER);
+	
+	// Wack 'em all in
+	for (unsigned int i = 0; iSetInteger(i*8+j,m_level_flags[i][j]);
+		}
+	}	
+
+	// Create an array for holding the global flags
+	Script::CArray *pGlobalFlags=new Script::CArray;
+	pGlobalFlags->SetSizeAndType(vMAX_GLOBAL_FLAGS, ESYMBOLTYPE_INTEGER);
+	
+	// Wack 'em all in
+	for (unsigned int i = 0; iSetInteger(i,m_global_flags[i]);
+	}
+
+#if 1
+	// Create an array for holding the gap checklists
+	
+	Script::CArray *pGapChecklists=new Script::CArray;
+	pGapChecklists->SetSizeAndType(vMAX_CAREER_LEVELS, ESYMBOLTYPE_STRUCTURE);
+	
+	for (unsigned int i = 0; iAddInteger(CRCD(0x651533ec,"level"),i);
+		if (!p_checklist->IsValid())
+		{
+			p_gap_struct->AddInteger(CRCD(0x73f8ca00,"valid"),0);
+		}
+		else
+		{
+			p_gap_struct->AddInteger(CRCD(0x73f8ca00,"valid"),1);
+			int num_gaps = p_checklist->NumGaps();
+			
+			Script::CArray *p_gaps_array=new Script::CArray;
+			p_gaps_array->SetSizeAndType(num_gaps, ESYMBOLTYPE_STRUCTURE);
+
+			for (int gap=0; gapAddString(CRCD(0x699fcfc0,"GapName"),p_checklist->GetGapText(gap));
+				p_struct->AddInteger(CRCD(0x25eb70db,"GapCount"),p_checklist->GetGapCount(gap));
+				p_struct->AddInteger(CRCD(0x92ab03e8,"GapScore"),p_checklist->GetGapScore(gap));
+				
+				Mth::Vector skater_start_pos;
+				Mth::Vector skater_start_dir;
+				skater_start_pos.Set();
+				skater_start_dir.Set();
+				if (p_checklist->GetGapCount(gap))
+				{
+					p_checklist->GetSkaterStartPosAndDir(gap,&skater_start_pos,&skater_start_dir);
+				}				
+				// The vectors are always added to keep the save structure size as constant as possible.
+				p_struct->AddVector(CRCD(0xa4605544,"SkaterStartPos"),skater_start_pos[X],skater_start_pos[Y],skater_start_pos[Z]);
+				p_struct->AddVector(CRCD(0x9e12c9f8,"SkaterStartDir"),skater_start_dir[X],skater_start_dir[Y],skater_start_dir[Z]);
+				
+				p_gaps_array->SetStructure(gap,p_struct);
+			}
+						
+			p_gap_struct->AddArrayPointer(CRCD(0xd76c173e,"Gaps"),p_gaps_array);
+		}
+		pGapChecklists->SetStructure(i,p_gap_struct);
+	}
+#endif
+
+	Script::CArray* pVisitedLevels = new Script::CArray();
+	pVisitedLevels->SetSizeAndType( vMAX_CAREER_LEVELS, ESYMBOLTYPE_INTEGER );
+	for ( unsigned int i = 0; i < vMAX_CAREER_LEVELS; ++i )
+		pVisitedLevels->SetInteger( i, m_level_visited[i] );
+
+	// Insert the above arrays into a new structure.
+	Script::CScriptStructure *pTemp=new Script::CScriptStructure;
+	pTemp->AddComponent(Script::GenerateCRC("GoalFlags"),ESYMBOLTYPE_ARRAY,(int)pGoalFlags);
+	pTemp->AddComponent(Script::GenerateCRC("LevelFlags"),ESYMBOLTYPE_ARRAY,(int)pLevelFlags);
+	pTemp->AddComponent(Script::GenerateCRC("GlobalFlags"),ESYMBOLTYPE_ARRAY,(int)pGlobalFlags);
+	
+#if 1	
+	pTemp->AddComponent(Script::GenerateCRC("GapChecklists"),ESYMBOLTYPE_ARRAY,(int)pGapChecklists);
+#endif
+	
+	pTemp->AddArrayPointer( "VisitedLevels", pVisitedLevels );
+
+	// K: Added these for Zac so that when autoloading on startup it can automatically go
+	// straight to the last level played
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	pTemp->AddChecksum(CRCD(0xe3335d2f,"LastLevelLoadScript"),p_skate_mod->m_cur_level);
+	pTemp->AddChecksum(CRCD(0x2cc06f5e,"LastGameMode"),p_skate_mod->GetGameMode()->GetNameChecksum());
+    pTemp->AddInteger("current_theme",Script::GetInteger("stored_theme_prefix"));
+					 
+
+	// Thrust the new structure into the passed structure.
+	Dbg_MsgAssert(pIn,("NULL pIn"));
+	pIn->AddComponent(Script::GenerateCRC("Career"),ESYMBOLTYPE_STRUCTUREPOINTER,(int)pTemp);
+	
+	// Note: The above arrays and structure are not deleted here, because pointers to them have
+	// been given to the passed structure, so it will delete them when it gets deleted.
+
+//	Script::PrintContents(pTemp);
+}
+
+void CSkaterCareer::ReadFromStructure(Script::CScriptStructure *pIn)
+{
+	
+	Dbg_MsgAssert(pIn,("NULL pIn"));
+
+	Script::CScriptStructure *pCareer=NULL;
+	pIn->GetStructure("Career",&pCareer);
+	Dbg_MsgAssert(pCareer,("Missing Career structure in structure passed to CSkaterCareer::ReadFromStructure"));
+	
+	Script::CArray *pGoalFlags=NULL;
+	pCareer->GetArray("GoalFlags",&pGoalFlags);
+	Dbg_MsgAssert(pGoalFlags,("Missing GoalFlags array in Career structure"));
+	Dbg_MsgAssert(pGoalFlags->GetSize()==8*vMAX_CAREER_LEVELS,("Bad size of %d for goal flags array, expected %d",pGoalFlags->GetSize(),vMAX_CAREER_LEVELS));
+
+	Script::CArray *pLevelFlags=NULL;
+	pCareer->GetArray("LevelFlags",&pLevelFlags);
+	Dbg_MsgAssert(pLevelFlags,("Missing LevelFlags array in Career structure"));
+	Dbg_MsgAssert(pLevelFlags->GetSize()==8*vMAX_CAREER_LEVELS,("Bad size of %d for level flags array, expected %d",pGoalFlags->GetSize(),vMAX_CAREER_LEVELS));
+
+	for (unsigned int i = 0; iGetInteger(i*8+j);
+		}
+		
+		for (j = 0;j<8;j++)
+		{
+			m_level_flags[i][j]=pLevelFlags->GetInteger(i*8+j);
+		}
+	}	
+	
+	Script::CArray *pGlobalFlags=NULL;
+	pCareer->GetArray("GlobalFlags",&pGlobalFlags);
+	Dbg_MsgAssert(pGlobalFlags,("Missing GlobalFlags array in Career structure"));
+	Dbg_MsgAssert(pGlobalFlags->GetSize()==vMAX_GLOBAL_FLAGS,("Bad size of %d for global flags array, expected %d",pGlobalFlags->GetSize(),vMAX_GLOBAL_FLAGS));
+	for (unsigned int i = 0; iGetInteger(i);
+	}
+	
+	Script::CArray *pGapChecklists = NULL;
+	pCareer->GetArray("GapChecklists",&pGapChecklists);	
+	//Dbg_MsgAssert(pGapChecklists,("Missing Gap checklist in Career structure"));
+	Dbg_MsgAssert(pGapChecklists->GetSize()==vMAX_CAREER_LEVELS,("Bad size of %d for gap checklist array, expected %d",pGapChecklists->GetSize()==vMAX_CAREER_LEVELS));
+	if (pGapChecklists)
+	{
+		for (unsigned int i = 0; iFlushGapChecks();
+			Script::CStruct *p_gap_struct = pGapChecklists->GetStructure(i);
+			int level = 0;
+			int valid = 0;
+			p_gap_struct->GetInteger("level",&level,true);
+			p_gap_struct->GetInteger("valid",&valid,true);
+			if (valid)
+			{
+				Script::CArray *p_gaps=NULL;
+				p_gap_struct->GetArray(CRCD(0xd76c173e,"Gaps"),&p_gaps);
+				Dbg_MsgAssert(p_gaps,("Missing gaps array"));
+				
+				int num_gaps = p_gaps->GetSize();
+				for (int gap = 0; gapGetStructure(gap);
+					
+					const char *p_name = NULL;
+					int count = 0;
+					int score = 0;
+					p_struct->GetString(CRCD(0x699fcfc0,"GapName"),&p_name);
+					p_struct->GetInteger(CRCD(0x25eb70db,"GapCount"),&count);
+					p_struct->GetInteger(CRCD(0x92ab03e8,"GapScore"),&score);
+					p_checklist->AddGapCheck(p_name,count,score);  
+					
+					if (count)
+					{
+						Mth::Vector skater_start_pos;
+						Mth::Vector skater_start_dir;
+						p_struct->GetVector(CRCD(0xa4605544,"SkaterStartPos"),&skater_start_pos);
+						p_struct->GetVector(CRCD(0x9e12c9f8,"SkaterStartDir"),&skater_start_dir);
+						
+						p_checklist->SetInfoForCamera(p_name,skater_start_pos,skater_start_dir);
+					}
+					
+					//printf ("%d: %s (%d)\n",level,p_name,count);				
+				}
+			}
+		}
+	}
+
+	Script::CArray* pVisitedLevels = NULL;
+	pCareer->GetArray( "VisitedLevels", &pVisitedLevels, Script::NO_ASSERT );
+	if ( pVisitedLevels )
+	{
+#ifdef __NOPT_ASSERT__
+		int size = pVisitedLevels->GetSize();
+		Dbg_MsgAssert( size <= (int)vMAX_CAREER_LEVELS, ( "wrong array size" ) );
+#endif		// __NOPT_ASSERT__
+		for ( int i = 0; i < (int)vMAX_CAREER_LEVELS; i++ )
+			m_level_visited[i] = pVisitedLevels->GetInteger( i );
+	}
+}
+
+} // namespace Obj
+
diff --git a/Code/Sk/Objects/skatercareer.h b/Code/Sk/Objects/skatercareer.h
new file mode 100644
index 0000000..4b6c18d
--- /dev/null
+++ b/Code/Sk/Objects/skatercareer.h
@@ -0,0 +1,163 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Object (OBJ)											**
+**																			**
+**	File name:		objects/SkaterCareer.h								   	**
+**																			**
+**	Created: 		21/5/01 - Mick (Based on SkaterApperance.h by gj)		**
+**																			**
+*****************************************************************************/
+
+#ifndef __OBJECTS_SKATERCAREER_H
+#define __OBJECTS_SKATERCAREER_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+    #include 
+#endif
+
+namespace Script
+{
+	class CStruct;
+}	
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+
+/*****************************************************************************
+**							Forward Declarations						**
+*****************************************************************************/
+
+class	CGapChecklist;
+
+/*****************************************************************************
+**							   Class Definitions							**
+*****************************************************************************/
+
+class  CSkaterCareer  : public Spt::Class
+{
+	
+
+	public:
+		enum
+		{
+			vVERSION_NUMBER = 0x00000001,
+			vMAX_CAREER_LEVELS = 21, 		// changes in this are handled automatically, so don't change the version number
+			vMAX_GLOBAL_FLAGS = 16,
+			vMAGIC_NUMBER = 0x84751129,		// magic terminator, for integrity check
+			vNUM_FLAG_BITS = 64,			// used for assertions
+		};
+	
+		CSkaterCareer();
+		virtual						~CSkaterCareer();
+
+	public:
+		// for memory card loading/saving
+		uint32						WriteToBuffer(uint8* pBuffer, uint32 bufferSize);
+		uint8*						ReadFromBuffer(uint8* pBuffer);
+		void 						ReadFromStructure(Script::CStruct *pIn);
+		void 						WriteIntoStructure(Script::CStruct *pIn);
+
+		// Initialize the career to "Nothing Done Yet" 
+		bool						Init( bool init_gaps = true );
+
+		// Reset's the career to "Nothing Done Yet"
+		bool						Reset( void );
+
+
+		// Start the level, setting the default level number
+		// and remembering which goals and flags were set at the start of this level
+		void						StartLevel(int level = -1);
+		
+		// Access function for Level. Added by Ken, used by CareerLevelIs script command.
+		int							GetLevel();
+
+		// marks a level visited
+		void						MarkLevelVisited( int level_num );
+		bool						HasVisitedLevel( int level_num );
+
+		// Access functions for goals and flags
+		void						SetGoal(int goal, int level = -1);
+		void						UnSetGoal(int goal, int level = -1);
+		bool						GetGoal(int goal, int level = -1);
+		int							CountGoalsCompleted(int level = -1);
+		bool						JustGotGoal(int goal);
+		
+		void						SetFlag(int flag, int level = -1);
+		void						UnSetFlag(int flag, int level = -1);
+		bool						GetFlag(int flag, int level = -1);
+		
+		bool						GetCheat(uint32 cheat_checksum);
+		
+		void						SetGlobalFlag(int flag);
+		void						UnSetGlobalFlag(int flag);
+		bool						GetGlobalFlag(int flag);
+
+		bool						JustGotFlag(int flag);
+		
+		
+		int							CountTotalGoalsCompleted();
+		int							CountMedals();
+		int 						GetBestMedal(int Level);
+		
+		Obj::CGapChecklist	*		GetGapChecklist(int level = -1);
+		bool						GotAllGaps();
+
+
+	public:
+		
+	protected:
+		uint32						m_goal_flags[vMAX_CAREER_LEVELS][8];
+		uint32						m_level_flags[vMAX_CAREER_LEVELS][8];
+		uint32						m_start_goal_flags[8];
+		uint32						m_start_level_flags[8];
+		
+		uint32						m_global_flags[vMAX_GLOBAL_FLAGS];		// 8x32 = 256 global flags
+		
+		int							m_current_level;
+		
+		Obj::CGapChecklist		*   mp_gap_checklist[vMAX_CAREER_LEVELS];
+		bool						m_level_visited[vMAX_CAREER_LEVELS];
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Obj
+
+#endif	// __OBJECTS_SKATERCAREER_H
diff --git a/Code/Sk/Objects/skaterflags.h b/Code/Sk/Objects/skaterflags.h
new file mode 100644
index 0000000..32046be
--- /dev/null
+++ b/Code/Sk/Objects/skaterflags.h
@@ -0,0 +1,61 @@
+// Skaterflags.h
+
+#ifndef	__SK_OBJECTS_SKATERFLAGS_H__	
+#define	__SK_OBJECTS_SKATERFLAGS_H__	
+
+namespace Obj
+{
+	// Flags that can be toggled on and off, and also store how long since last toggle
+	// This is used as an index into CSkater::m_flags
+	enum	ESkaterFlag
+	{
+		TENSE = 0, 					// tensing for a jump, usually X pressed											 0
+		// SWITCH,					// skating switch																	 1		NOTE: unused?
+		// LEAN,					// leaning L/R (might prohibit us doing other things								 2		NOTE: unused?
+		// TENSE_ON_GROUND,			// we were on the ground, and the tense flag was set								 3		NOTE: unused?
+		FLIPPED,					// animation is flipped																 4
+		VERT_AIR,					// True if we are going to vert air													 5
+		TRACKING_VERT,				// True if we are tracking a vert surface below us 									 6
+		LAST_POLY_WAS_VERT,			// True if the last polygon we skated on was vert									 7
+		CAN_BREAK_VERT,				// True if we can break the vert poly we are on										 8
+		CAN_RERAIL,					// can get back on the rail again, ahead of rerail time								 9
+		// STARTED_END_OF_RUN,		// true if the skater has started the "end of run" script							 10
+		// FINISHED_END_OF_RUN,		// True if the skater has returned to his idle state after the "end of run" script	 11
+		// STARTED_GOAL_END_OF_RUN,	// True if the goal_endofrun script has started
+		// FINISHED_GOAL_END_OF_RUN,
+		RAIL_SLIDING,				// True if skater is railsliding													 12
+		CAN_HIT_CAR,				// True is we can hit a car.  Set when on ground, or have negative y vel			 13
+		AUTOTURN,					// True if we can autoturn															 14
+		IS_BAILING,					// True if skater is bailing...														 15
+		// IS_ENDING_RUN,			// True is skater is in the process of ending his run								 16
+		// IS_ENDING_GOAL,			// True if we're ending a goal
+		SPINE_PHYSICS,				// True is skater is going over a spine												 17
+		IN_RECOVERY,				// True if recovering from going off vert											 18
+		SKITCHING,					// True if we are being towed by a car												 19
+		OVERRIDE_CANCEL_GROUND,		// True if we want to ignore the "CANCEL_GROUND" flag for gaps						 20
+		SNAPPED_OVER_CURB,			// True if we snapped up or down a curb this frame (for use by camera)				 21
+		SNAPPED,					// True if we snapped slightly this frame (for use by camera)
+		IN_ACID_DROP,				// True if we are in an acid drop
+		AIR_ACID_DROP_DISALLOWED,	// True if in-air acid drops are not currently allowed
+		CANCEL_WALL_PUSH,			// True if the current wallpush event is canceled via script
+		NO_ORIENTATION_CONTROL,		// True if spins and leans are turned off due to switching from walking
+		NEW_RAIL,					// True if the rail we land on is a "new" rail
+		OLLIED_FROM_RAIL,			// True if entered current air via an ollie out of a rail
+		// NOTE: If anyone adds any more flags so that NUM_ESKATERFLAGS > 32, let Steve G know
+
+		NUM_ESKATERFLAGS
+	};
+	
+	enum EStateType
+	{
+		GROUND,
+		AIR,
+		WALL,
+		LIP,
+		RAIL,
+		WALLPLANT
+	};
+}
+	
+#endif
+
diff --git a/Code/Sk/Objects/skaterpad.cpp b/Code/Sk/Objects/skaterpad.cpp
new file mode 100644
index 0000000..023a45b
--- /dev/null
+++ b/Code/Sk/Objects/skaterpad.cpp
@@ -0,0 +1,520 @@
+// SkaterPad.cpp
+
+#include 
+#include 
+#include 
+
+//#define		PS2_SIMULATING_XBOX
+
+// The constructor of a skater pad just sets all the names
+CSkaterPad::CSkaterPad()
+{
+	// Note: These names must match those used when defining event queries in .q files.
+	// (Otherwise the queries won't work)
+	m_up.SetName("UP");
+	m_down.SetName("DOWN");
+	m_left.SetName("LEFT");
+	m_right.SetName("RIGHT");
+	m_square.SetName("SQUARE");
+	m_circle.SetName("CIRCLE");
+	m_triangle.SetName("TRIANGLE");
+	m_x.SetName("X");
+	m_L1.SetName("L1");
+	m_L2.SetName("L2");
+	m_L3.SetName("L3");
+	m_R1.SetName("R1");
+	m_R2.SetName("R2");
+	m_R3.SetName("R3");
+	m_start.SetName("START");
+	m_select.SetName("SELECT");
+	
+	m_leftAnalogUpDebounceTime = 0;
+	m_leftAnalogDownDebounceTime = 0;
+}
+
+#ifdef __NOPT_ASSERT__
+void CSkaterPad::Update ( Inp::Data* input, bool debug )
+#else
+void CSkaterPad::Update ( Inp::Data* input )
+#endif
+{
+	#ifdef __NOPT_ASSERT__
+	
+	m_up.Update(input->m_Event[Inp::Data::vA_UP] ? 255 : 0, debug);
+	m_down.Update(input->m_Event[Inp::Data::vA_DOWN] ? 255 : 0, debug);
+	m_left.Update(input->m_Event[Inp::Data::vA_LEFT] ? 255 : 0, debug);
+	m_right.Update(input->m_Event[Inp::Data::vA_RIGHT] ? 255 : 0, debug);
+
+	m_square.Update(input->m_Event[Inp::Data::vA_SQUARE] ? 255 : 0, debug);
+	m_circle.Update(input->m_Event[Inp::Data::vA_CIRCLE] ? 255 : 0, debug);
+	m_x.Update(input->m_Event[Inp::Data::vA_X] ? 255 : 0, debug);
+	m_triangle.Update(input->m_Event[Inp::Data::vA_TRIANGLE] ? 255 : 0, debug);
+
+	m_L1.Update(input->m_Event[Inp::Data::vA_L1] ? 255 : 0, debug);
+	m_R1.Update(input->m_Event[Inp::Data::vA_R1] ? 255 : 0, debug);
+
+	#ifndef	PS2_SIMULATING_XBOX
+	m_L2.Update(input->m_Event[Inp::Data::vA_L2] ? 255 : 0, debug);
+	m_R2.Update(input->m_Event[Inp::Data::vA_R2] ? 255 : 0, debug);
+	#else	// update both 1 and 2 from L1/R1, to simulate the X-Box
+	m_L2.Update(input->m_Event[Inp::Data::vA_L1] ? 255 : 0, debug);
+	m_R2.Update(input->m_Event[Inp::Data::vA_R1] ? 255 : 0, debug);
+	#endif
+	
+	m_L3.Update(input->m_Buttons & Inp::Data::mD_L3 ? 255 : 0, debug);
+	m_R3.Update(input->m_Buttons & Inp::Data::mD_R3 ? 255 : 0, debug);
+	
+	
+	m_select.Update(input->m_Buttons & Inp::Data::mD_SELECT ? 255 : 0, debug);
+	
+	#else
+	
+	m_up.Update(input->m_Event[Inp::Data::vA_UP] ? 255 : 0);
+	m_down.Update(input->m_Event[Inp::Data::vA_DOWN] ? 255 : 0);
+	m_left.Update(input->m_Event[Inp::Data::vA_LEFT] ? 255 : 0);
+	m_right.Update(input->m_Event[Inp::Data::vA_RIGHT] ? 255 : 0);
+
+	m_square.Update(input->m_Event[Inp::Data::vA_SQUARE] ? 255 : 0);
+	m_circle.Update(input->m_Event[Inp::Data::vA_CIRCLE] ? 255 : 0);
+	m_x.Update(input->m_Event[Inp::Data::vA_X] ? 255 : 0);
+	m_triangle.Update(input->m_Event[Inp::Data::vA_TRIANGLE] ? 255 : 0);
+
+	m_L1.Update(input->m_Event[Inp::Data::vA_L1] ? 255 : 0);
+	m_R1.Update(input->m_Event[Inp::Data::vA_R1] ? 255 : 0);
+
+	#ifndef	PS2_SIMULATING_XBOX
+	m_L2.Update(input->m_Event[Inp::Data::vA_L2] ? 255 : 0);
+	m_R2.Update(input->m_Event[Inp::Data::vA_R2] ? 255 : 0);
+	#else	// update both 1 and 2 from L1/R1, to simulate the X-Box
+	m_L2.Update(input->m_Event[Inp::Data::vA_L1] ? 255 : 0);
+	m_R2.Update(input->m_Event[Inp::Data::vA_R1] ? 255 : 0);
+	#endif
+	
+	m_L3.Update(input->m_Buttons & Inp::Data::mD_L3 ? 255 : 0);
+	m_R3.Update(input->m_Buttons & Inp::Data::mD_R3 ? 255 : 0);
+
+	m_select.Update(input->m_Buttons & Inp::Data::mD_SELECT ? 255 : 0);
+	
+	#endif
+	
+	int in;
+	
+	in = input->m_Event[Inp::Data::vA_RIGHT_X] - 128;
+	if (in == 0)
+	{
+		m_scaled_rightX = 0.0f;
+	}
+	else
+	{
+		if (in == -128)
+		{
+			in = -127;
+		}
+		m_scaled_rightX = (in + (in > 0 ? -Inp::vANALOGUE_TOL : Inp::vANALOGUE_TOL)) / (127.0f - Inp::vANALOGUE_TOL);
+	}
+	
+	in = input->m_Event[Inp::Data::vA_RIGHT_Y] - 128;
+	if (in == 0)
+	{
+		m_scaled_rightY = 0.0f;
+	}
+	else
+	{
+		if (in == -128)
+		{
+			in = -127;
+		}
+		m_scaled_rightY = (in + (in > 0 ? -Inp::vANALOGUE_TOL : Inp::vANALOGUE_TOL)) / (127.0f - Inp::vANALOGUE_TOL);
+	}
+	
+	in = input->m_Event[Inp::Data::vA_LEFT_X] - 128;
+	if (in == 0)
+	{
+		m_scaled_leftX = 0.0f;
+	}
+	else
+	{
+		if (in == -128)
+		{
+			in = -127;
+		}
+		m_scaled_leftX = (in + (in > 0 ? -Inp::vANALOGUE_TOL : Inp::vANALOGUE_TOL)) / (127.0f - Inp::vANALOGUE_TOL);
+	}
+
+	in = input->m_Event[Inp::Data::vA_LEFT_Y] - 128;
+	if (in == 0)
+	{
+		m_scaled_leftY = 0.0f;
+	}
+	else
+	{
+		if (in == -128)
+		{
+			in = -127;
+		}
+		m_scaled_leftY = (in + (in > 0 ? -Inp::vANALOGUE_TOL : Inp::vANALOGUE_TOL)) / (127.0f - Inp::vANALOGUE_TOL);
+	}
+	
+	m_rightX = input->m_Event[Inp::Data::vA_RIGHT_X_UNCLAMPED] - 128;
+	m_rightY = input->m_Event[Inp::Data::vA_RIGHT_Y_UNCLAMPED] - 128;
+	m_leftX = input->m_Event[Inp::Data::vA_LEFT_X_UNCLAMPED] - 128;
+	m_leftY = input->m_Event[Inp::Data::vA_LEFT_Y_UNCLAMPED] - 128;
+	
+	// Calculate the direction, and the amount
+
+	m_rightLength = sqrtf(m_rightX * m_rightX + m_rightY * m_rightY);
+	m_rightAngle = atan2f(m_rightX, -m_rightY);
+
+	m_leftLength = sqrtf(m_leftX * m_leftX + m_leftY * m_leftY);
+	if (m_leftLength > 0.001f)
+	{
+		m_leftAngle = atan2f(m_leftX, -m_leftY);
+	}
+	else
+	{
+		// if left analog stick not pressed, then get it from the D-Pad
+		bool Up;
+		bool Down;
+		bool Left;
+		bool Right;
+		Up = input->m_Event[Inp::Data::vA_UP];
+		Down = input->m_Event[Inp::Data::vA_DOWN];
+		Left = input->m_Event[Inp::Data::vA_LEFT];
+		Right = input->m_Event[Inp::Data::vA_RIGHT];
+
+		if (Up || Down || Left || Right)
+		{
+			m_leftAngle = sGetAngleFromDPad(Up, Down, Left, Right);
+			m_leftLength = 127.0f;
+		}
+		else
+		{
+			m_leftAngle = 0.0f;
+			m_leftLength = 0.0f;				
+		}
+	}
+	
+	if (m_leftAnalogUpDebounceTime)
+	{
+		// if not pressed, or debounce time expired
+		if (m_leftLength == 0.0f || !(Mth::Abs(m_leftAngle) < Mth::DegToRad(45.0f)))
+		{
+			m_leftAnalogUpDebounceTime = 0;
+		}
+		else if ((float)Tmr::GetTime() > m_leftAnalogUpDebounceTime)
+		{
+			m_leftAnalogUpDebounceTime = 0;
+		}
+	}
+	
+	if (m_leftAnalogDownDebounceTime)
+	{
+		// if not pressed, or debounce time expired
+		if (m_leftLength == 0.0f || !((Mth::PI - Mth::Abs(m_leftAngle)) < Mth::DegToRad(45.0f)))
+		{
+			m_leftAnalogDownDebounceTime = 0;
+		}
+		else if ((float)Tmr::GetTime() > m_leftAnalogDownDebounceTime)
+		{
+			m_leftAnalogDownDebounceTime = 0;
+		}
+	}
+}
+
+CSkaterButton *CSkaterPad::GetButton(uint32 NameChecksum)
+{
+	
+	// Maybe optimize using a hash table later for speed.
+	if (m_up.GetName()==NameChecksum) return &m_up;
+	else if (m_down.GetName()==NameChecksum) return &m_down;
+	else if (m_left.GetName()==NameChecksum) return &m_left;
+	else if (m_right.GetName()==NameChecksum) return &m_right;
+	else if (m_square.GetName()==NameChecksum) return &m_square;
+	else if (m_circle.GetName()==NameChecksum) return &m_circle;
+	else if (m_triangle.GetName()==NameChecksum) return &m_triangle;
+	else if (m_x.GetName()==NameChecksum) return &m_x;
+	else if (m_L1.GetName()==NameChecksum) return &m_L1;
+	else if (m_L2.GetName()==NameChecksum) return &m_L2;
+	else if (m_L3.GetName()==NameChecksum) return &m_L3;
+	else if (m_R1.GetName()==NameChecksum) return &m_R1;
+	else if (m_R2.GetName()==NameChecksum) return &m_R2;
+	else if (m_R3.GetName()==NameChecksum) return &m_R3;
+	else if (m_start.GetName()==NameChecksum) return &m_start;
+	else if (m_select.GetName()==NameChecksum) return &m_select;
+	else 
+	{
+		Dbg_MsgAssert(0,("Bad checksum '%x' sent to GetButton",NameChecksum));
+	}
+	return NULL;
+}	
+																			
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+																			
+float	CSkaterPad::GetScaledAnalogStickMagnitude ( float analog_x, float analog_y, float analog_angle )
+{
+	float x = analog_x / (analog_x > 0.0f ? 127.0f : 128.0f);
+	float y = analog_y / (analog_y > 0.0f ? 127.0f : 128.0f);
+	
+	#ifndef __PLAT_NGC__
+	
+	float angle;
+	if (analog_angle > (-Mth::PI / 4.0f) && analog_angle < (Mth::PI / 4.0f))
+	{
+		// top quadrant
+		angle = analog_angle;
+	}
+	else if (analog_angle > (Mth::PI / 4.0f) && analog_angle < (3.0f * Mth::PI / 4.0f))
+	{
+		// right quadrant
+		angle = analog_angle - (Mth::PI / 2.0f);
+	}
+	else if (Mth::Abs(analog_angle) > (3.0f * Mth::PI / 4.0f))
+	{
+		// bottom quadrant
+		angle = Mth::Abs(analog_angle) - Mth::PI;
+	}
+	else
+	{
+		// left quadrant
+		angle = analog_angle + (Mth::PI / 2.0f);
+	}
+	
+	return sqrtf(x * x + y * y) * Mth::Abs(cosf(angle));
+	
+	#else
+	
+	return sqrtf(x * x + y * y);
+	
+	#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint32	CSkaterPad::GetPressedMask( void )
+{
+	uint32 mask;
+
+	mask = 0;
+
+	if( m_up.GetPressed())
+	{
+		mask |= Inp::Data::mA_UP;
+	}
+	if( m_down.GetPressed())
+	{
+		mask |= Inp::Data::mA_DOWN;
+	}
+	if( m_left.GetPressed())
+	{
+		mask |= Inp::Data::mA_LEFT;
+	}
+	if( m_right.GetPressed())
+	{
+		mask |= Inp::Data::mA_RIGHT;
+	}
+	if( m_L1.GetPressed())
+	{
+		mask |= Inp::Data::mA_L1;
+	}
+	if( m_L2.GetPressed())
+	{
+		mask |= Inp::Data::mA_L2;
+	}
+	if( m_R1.GetPressed())
+	{
+		mask |= Inp::Data::mA_R1;
+	}
+	if( m_R2.GetPressed())
+	{
+		mask |= Inp::Data::mA_R2;
+	}
+	if( m_circle.GetPressed())
+	{
+		mask |= Inp::Data::mA_CIRCLE;
+	}
+	if( m_square.GetPressed())
+	{
+		mask |= Inp::Data::mA_SQUARE;
+	}
+	if( m_triangle.GetPressed())
+	{
+		mask |= Inp::Data::mA_TRIANGLE;
+	}
+	if( m_x.GetPressed())
+	{
+		mask |= Inp::Data::mA_X;
+	}
+		
+	return mask;
+}
+																			
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the pressed state of the four cardinal directions
+// then return a sensible value including diagonals
+// given that we might be pressing opposing directions (Up+Down)
+// and we still want to get some sensible response
+// Here Up+Down is mapped to PAD_D
+// all mapping where there is some conflict needing resolution are
+// marked with an asterix
+uint32 CSkaterPad::sGetDirection(bool Up, bool Down, bool Left, bool Right)
+{
+	int Dir = Up + (Down << 1) + (Left << 2) + (Right << 3);
+
+	static uint DirectionMap[16] =
+	{
+		// RLDU
+		0,				// 0000
+		PAD_U,		// 0001
+		PAD_D,		// 0010
+		PAD_D,		// 0011	 *
+		PAD_L,		// 0100
+		PAD_UL,		// 0101
+		PAD_DL,		// 0110
+		PAD_DL,		// 0111	 *
+		PAD_R,		// 1000
+		PAD_UR,		// 1001
+		PAD_DR,		// 1010
+		PAD_DR,		// 1011	 *
+		PAD_R,		// 1100	 *
+		PAD_UR,		// 1101	 *
+		PAD_DR,		// 1110	 *
+		PAD_DR,		// 1111	 *
+	};
+
+	return DirectionMap[Dir];
+}
+																			
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define		PAD_ANGLE_U		( 0.0f )
+#define		PAD_ANGLE_UR	( Mth::PI / 4.0f)
+#define		PAD_ANGLE_R     ( Mth::PI / 2.0f) 
+#define		PAD_ANGLE_DR    ( Mth::PI * 3.0f / 4.0f)
+#define		PAD_ANGLE_D		( Mth::PI )
+#define		PAD_ANGLE_DL	( - Mth::PI * 3.0f / 4.0f) 
+#define		PAD_ANGLE_L		( - Mth::PI / 2.0f ) 
+#define		PAD_ANGLE_UL	( - Mth::PI / 4.0f) 
+
+float CSkaterPad::sGetAngleFromDPad(bool Up, bool Down, bool Left, bool Right)
+{
+	int Dir = Up + (Down << 1) + (Left << 2) + (Right << 3);
+
+	static float DirectionMap[16] =
+	{
+		// RLDU
+		0.0f,				// 0000
+		PAD_ANGLE_U,		// 0001
+		PAD_ANGLE_D,		// 0010
+		PAD_ANGLE_D,		// 0011	 *
+		PAD_ANGLE_L,		// 0100
+		PAD_ANGLE_UL,		// 0101
+		PAD_ANGLE_DL,		// 0110
+		PAD_ANGLE_DL,		// 0111	 *
+		PAD_ANGLE_R,		// 1000
+		PAD_ANGLE_UR,		// 1001
+		PAD_ANGLE_DR,		// 1010
+		PAD_ANGLE_DR,		// 1011	 *
+		PAD_ANGLE_R,		// 1100	 *
+		PAD_ANGLE_UR,		// 1101	 *
+		PAD_ANGLE_DR,		// 1110	 *
+		PAD_ANGLE_DR,		// 1111	 *
+	};
+
+	return DirectionMap[Dir];
+}
+																			
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+																			
+void CSkaterPad::Zero (   )
+{
+	m_up.Update( 0 );
+	m_down.Update( 0 );
+	m_left.Update( 0 );
+	m_right.Update( 0 );
+	m_L1.Update( 0 );
+	m_L2.Update( 0 );
+	m_L3.Update( 0 );
+	m_R1.Update( 0 );
+	m_R2.Update( 0 );
+	m_R3.Update( 0 );
+	m_square.Update( 0 );
+	m_circle.Update( 0 );
+	m_x.Update( 0 );
+	m_triangle.Update( 0 );
+	m_start.Update( 0 );
+	m_select.Update( 0 );
+	
+	m_rightX = 0.0f;
+	m_rightY = 0.0f;
+	m_leftX = 0.0f;
+	m_leftY = 0.0f;
+
+	m_scaled_rightX = 0.0f;
+	m_scaled_rightY = 0.0f;
+	m_scaled_leftX = 0.0f;
+	m_scaled_leftY = 0.0f;
+
+	m_rightAngle = 0.0f;
+	m_rightLength = 0.0f;
+	m_leftAngle = 0.0f;
+	m_leftLength = 0.0f;
+}
+																			
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CSkaterPad::Reset (   )
+{
+	Zero();
+	
+	m_up.ClearTrigger( );
+	m_up.ClearRelease( );
+	m_down.ClearTrigger( );
+	m_down.ClearRelease( );
+	m_left.ClearTrigger( );
+	m_left.ClearRelease( );
+	m_right.ClearTrigger( );
+	m_right.ClearRelease( );
+	m_L1.ClearTrigger( );
+	m_L1.ClearRelease( );
+	m_L2.ClearTrigger( );
+	m_L2.ClearRelease( );
+	m_L3.ClearTrigger( );
+	m_L3.ClearRelease( );
+	m_R1.ClearTrigger( );
+	m_R1.ClearRelease( );
+	m_R2.ClearTrigger( );
+	m_R2.ClearTrigger( );
+	m_R3.ClearRelease( );
+	m_R3.ClearRelease( );
+	m_square.ClearTrigger( );
+	m_square.ClearRelease( );
+	m_circle.ClearTrigger( );
+	m_circle.ClearRelease( );
+	m_x.ClearTrigger( );
+	m_x.ClearRelease( );
+	m_triangle.ClearTrigger( );
+	m_triangle.ClearRelease( );
+	m_start.ClearTrigger( );
+	m_start.ClearRelease( );
+	m_select.ClearTrigger( );
+	m_select.ClearRelease( );
+}
diff --git a/Code/Sk/ParkEditor/EdRail.cpp b/Code/Sk/ParkEditor/EdRail.cpp
new file mode 100644
index 0000000..3114b62
--- /dev/null
+++ b/Code/Sk/ParkEditor/EdRail.cpp
@@ -0,0 +1,395 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:					      			 								**
+**																			**
+**	File name:																**
+**																			**
+**	Created by:		rjm										                **
+**																			**
+**	Description:															**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+DefinePoolableClass(Ed::RailPoint)
+DefinePoolableClass(Ed::RailString)
+
+namespace Ed
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+RailPoint::RailPoint() :
+	m_pos(0.0f, 0.0f, 0.0f, 0.0f)
+{
+	mp_next = NULL;
+}
+
+
+
+
+RailPoint::~RailPoint() 
+{
+}
+
+
+
+
+RailString::RailString()
+{
+	mp_pointList = NULL;
+	m_numPoints = 0;
+	mp_next = NULL;
+
+	m_isLoop = false;
+}
+
+
+
+
+RailString::~RailString()
+{
+}
+
+
+
+
+void RailString::AddPoint(RailPoint *pPoint)
+{
+	if (!mp_pointList)
+		mp_pointList = pPoint;
+	else
+		mp_lastPoint->mp_next = pPoint;
+	mp_lastPoint = pPoint;
+	m_numPoints++;
+}
+
+
+
+
+void RailString::Destroy()
+{
+	RailPoint *pPoint = mp_pointList;
+	while(pPoint)
+	{
+		RailPoint *pNext = pPoint->mp_next;
+		mp_parentSet->DestroyRailPoint(pPoint);
+		pPoint = pNext;
+	}
+	mp_pointList = NULL;
+	mp_lastPoint = NULL;
+	m_numPoints = 0;
+}
+
+
+
+
+int	RailString::CountLinkedPoints()
+{
+	
+
+	if (m_isLoop)
+		return Count();
+
+	int count = 0;
+	for (RailPoint *pPoint = mp_pointList; pPoint != NULL; pPoint = pPoint->mp_next)
+		count++;
+
+	Dbg_MsgAssert(count > 1, ("invalid number of linked rail points in a rail (%d) need 2 or more",count));
+	return count - 1;
+}
+
+
+
+
+void RailString::CopyOffsetAndRot(RailString *pSource, Mth::Vector &pos, int rot, uint32 piece_id)
+{
+	RailPoint *pSourcePt = pSource->GetList();
+
+	while(pSourcePt)
+	{
+		RailPoint *pOut = mp_parentSet->CreateRailPoint();
+		float x = 0.0f, z = 0.0f;
+		switch(rot)
+		{
+			case 0:
+				x =  pSourcePt->m_pos.GetX();
+				z = pSourcePt->m_pos.GetZ();
+// GJ:  No longer need to negate the Z component, now that we're using new-style "Pos" vectors!
+//				z = -pSourcePt->m_pos.GetZ();
+				break;
+			case 1:
+				x = pSourcePt->m_pos.GetZ();
+				z = -pSourcePt->m_pos.GetX();
+// GJ:  No longer need to negate the Z component, now that we're using new-style "Pos" vectors!
+//				z = pSourcePt->m_pos.GetX();
+				break;
+			case 2:
+				x = -pSourcePt->m_pos.GetX();
+				z = -pSourcePt->m_pos.GetZ();
+// GJ:  No longer need to negate the Z component, now that we're using new-style "Pos" vectors!
+//				z = pSourcePt->m_pos.GetZ();
+				break;
+			case 3:
+				x =  -pSourcePt->m_pos.GetZ();
+				z =  pSourcePt->m_pos.GetX();
+// GJ:  No longer need to negate the Z component, now that we're using new-style "Pos" vectors!
+//				z =  -pSourcePt->m_pos.GetX();
+				break;
+		}
+		float y = pSourcePt->m_pos.GetY() +	pos.GetY();
+		x += pos.GetX();
+		z += pos.GetZ();
+// GJ:  No longer need to negate the Z component, now that we're using new-style "Pos" vectors!
+		pOut->m_pos.Set(x, y, z);
+//		pOut->m_pos.Set(x, y, -z);
+		pOut->m_type = pSourcePt->m_type;
+		pOut->m_objectId = piece_id;
+
+		AddPoint(pOut);
+
+		pSourcePt = pSourcePt->GetNext();
+	}
+
+	m_id = pSource->m_id;
+	m_isLoop = pSource->m_isLoop;
+}
+
+
+
+
+RailSet::RailSet()
+{
+	mp_stringList = NULL;
+	m_numStrings = 0;
+
+	mp_pointAllocator = NULL;
+	mp_stringAllocator = NULL;
+}
+
+
+
+
+RailSet::~RailSet()
+{
+}
+
+
+
+
+void RailSet::AddString(RailString *pString)
+{
+	if (!mp_stringList)
+		mp_stringList = pString;
+	else
+		mp_lastString->mp_next = pString;
+	mp_lastString = pString;
+	m_numStrings++;
+}
+
+
+
+
+void RailSet::Destroy()
+{
+	RailString *pString = mp_stringList;
+	while(pString)
+	{
+		RailString *pNext = pString->mp_next;
+		pString->Destroy();
+		DestroyRailString(pString);
+		pString = pNext;
+	}
+	mp_stringList = NULL;
+	mp_lastString = NULL;
+	m_numStrings = 0;
+}
+
+
+
+
+int RailSet::CountPoints()
+{
+	int num_points = 0;
+	RailString *pString = mp_stringList;
+	while (pString)
+	{
+		num_points += pString->Count();
+		pString = pString->GetNext();
+	}
+
+	return num_points;
+}
+
+
+
+
+RailString *RailSet::GetString(uint32 id, int requested_num)
+{
+	RailString *pString = mp_stringList;
+	int num = 0;
+	while(pString)
+	{
+		if (pString->m_id == id)
+		{
+			if (num == requested_num)
+				return pString;
+			else
+				num++;
+		}
+		pString = pString->GetNext();
+	}
+	return NULL;
+}
+
+
+
+
+void RailSet::SetupAllocators(int num_points, int num_strings, bool in_set)
+{
+	if (in_set)
+		strcpy(m_setName, "In set");
+	else
+		strcpy(m_setName, "Out set");
+	char point_name[64];
+	sprintf(point_name, "%s RailPoint", m_setName);
+	char string_name[64];
+	sprintf(string_name, "%s RailString", m_setName);
+	
+	Dbg_MsgAssert(mp_pointAllocator==NULL,("mp_pointAllocator not NULL"));
+	mp_pointAllocator = new Mem::CCompactPool(sizeof(RailPoint), num_points, point_name);
+	Dbg_MsgAssert(mp_stringAllocator==NULL,("mp_stringAllocator not NULL"));
+	mp_stringAllocator = new Mem::CCompactPool(sizeof(RailString), num_strings, string_name);
+}
+
+
+
+
+void RailSet::FreeAllocators()
+{
+	if (mp_stringAllocator)
+	{
+		delete mp_stringAllocator;
+		mp_stringAllocator=NULL;
+	}
+	if (mp_pointAllocator)
+	{
+		delete mp_pointAllocator;
+		mp_pointAllocator=NULL;
+	}	
+}
+
+
+
+
+RailPoint *RailSet::CreateRailPoint()
+{
+	
+	Dbg_Assert(mp_pointAllocator);
+
+	RailPoint::SAttachPool(mp_pointAllocator);
+	RailPoint *pNew = new RailPoint;
+	RailPoint::SRemovePool();
+	return pNew;
+}
+
+
+
+
+RailString *RailSet::CreateRailString()
+{
+	
+	Dbg_Assert(mp_stringAllocator);
+
+	RailString::SAttachPool(mp_stringAllocator);
+	RailString *pString = new RailString;
+	RailString::SRemovePool();
+	pString->mp_parentSet = this;
+	return pString;
+}
+
+
+
+
+void RailSet::DestroyRailPoint(RailPoint *pPoint)
+{
+	
+	Dbg_Assert(mp_pointAllocator);
+
+	RailPoint::SAttachPool(mp_pointAllocator);
+	delete pPoint;
+	RailPoint::SRemovePool();
+}
+
+
+
+
+void RailSet::DestroyRailString(RailString *pString)
+{
+	
+	Dbg_Assert(mp_stringAllocator);
+
+	RailString::SAttachPool(mp_stringAllocator);
+	delete pString;
+	RailString::SRemovePool();
+}
+
+
+
+
+} // namespace Ed
+
+
+
+
diff --git a/Code/Sk/ParkEditor/EdRail.h b/Code/Sk/ParkEditor/EdRail.h
new file mode 100644
index 0000000..e133eb3
--- /dev/null
+++ b/Code/Sk/ParkEditor/EdRail.h
@@ -0,0 +1,174 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		            											**
+**																			**
+**	Module:			              			 								**
+**																			**
+**	File name:		                    									**
+**																			**
+**	Created by:     rjm        				    			                **
+**																			**
+**	Description:					                                        **
+**																			**
+*****************************************************************************/
+
+#ifndef __SK_PARKEDITOR_EDRAIL_H
+#define __SK_PARKEDITOR_EDRAIL_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Ed
+{
+
+						
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class  RailPoint  : public Mem::CPoolable
+{
+	
+
+public:
+//	enum RailType
+//	{
+//		vMETAL,
+//		vWOOD,
+//	};
+
+	typedef		uint32		RailType;
+	
+	RailPoint();
+	~RailPoint();
+
+	RailPoint *								GetNext() {return mp_next;}
+
+	Mth::Vector								m_pos;
+	RailType								m_type;
+	// id of associated Piece (and therefore trick object)
+	uint32									m_objectId;
+	RailPoint *								mp_next;
+};
+
+
+
+
+class RailSet;
+class  RailString  : public Mem::CPoolable
+{
+	
+
+public:
+	RailString();
+	~RailString();
+
+	void									AddPoint(RailPoint *pPoint);
+	void									Destroy();
+	RailPoint *								GetList() {return mp_pointList;}
+	RailString *							GetNext() {return mp_next;}
+	int										Count() {return m_numPoints;}
+	int										CountLinkedPoints();
+
+	void									CopyOffsetAndRot(RailString *pString, Mth::Vector &pos, int rot, uint32 piece_id);
+	
+	uint8									m_isLoop;
+	uint32									m_id; // of associated piece
+	
+	RailPoint *								mp_pointList;
+	RailPoint *								mp_lastPoint;
+	uint16									m_numPoints;
+	RailString *							mp_next;
+
+	RailSet *								mp_parentSet;
+};
+
+
+
+
+class  RailSet  : public Spt::Class
+{
+	
+
+public:
+	RailSet();
+	~RailSet();
+
+	void									AddString(RailString *pString);
+	void									Destroy();
+	RailString *							GetList() {return mp_stringList;}
+	int										Count() {return m_numStrings;}
+	int										CountPoints();
+
+	RailString *							GetString(uint32 id, int num);
+	
+	void									SetupAllocators(int num_points, int num_strings, bool in_set);
+	void									FreeAllocators();
+	RailPoint *								CreateRailPoint();
+	RailString *							CreateRailString();
+	void									DestroyRailPoint(RailPoint *pPoint);
+	void									DestroyRailString(RailString *pString);
+
+protected:	
+	RailString *							mp_stringList;
+	RailString *							mp_lastString;
+	uint16									m_numStrings;
+
+	Mem::CCompactPool *						mp_pointAllocator;
+	Mem::CCompactPool *						mp_stringAllocator;
+
+	char									m_setName[32];
+};
+
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+} // namespace Ed
+
+#endif	// __SK_PARKEDITOR_EDRAIL_H
+
+
diff --git a/Code/Sk/ParkEditor/LoadPath.txt b/Code/Sk/ParkEditor/LoadPath.txt
new file mode 100644
index 0000000..8cf1974
--- /dev/null
+++ b/Code/Sk/ParkEditor/LoadPath.txt
@@ -0,0 +1,157 @@
+**************************************************
+Level Loading Pathway for THPS3
+Updated 7/17/2001
+**************************************************
+
+=========================================================
+*** REGULAR LEVEL ***
+change_level level=Load_LevelName (script)
+-setgametype freeskate
+-request_level <...>
+	-RequestLevel  -- sets skate_mod->m_requested_level
+-ScriptChangeLevel()
+	-does some network stuff, or might apply to single player
+-Return to regular game flow, update function (below) does more later
+
+
+=========================================================
+*** CUSTOM PARK, PLAYED AS REGULAR ***
+change_level level=custom_park (script, launched from menu)
+-setgametype freeskate
+-request_level <...>
+	-RequestLevel  -- sets skate_mod->m_requested_level
+-ScriptChangeLevel()
+	-does some network stuff, or might apply to single player
+-Return to regular game flow, update function (below) does more later
+
+
+=========================================================
+*** GOING INTO PARK EDITOR ***
+AutoLaunch level=custom_park game=parkeditor
+-SetServerMode on (command)
+-request_level level=
+	-RequestLevel  -- sets skate_mod->m_requested_level
+-SetGameType 
+-launchmenuscreen screen=parked_menu
+-Return to regular game flow, update function (below) does more later
+
+
+=========================================================
+*** UPDATE FUNCTION, ALL ***
+-CallMemberFunction, request==GameFlow_RequestLevel
+	-*** REGULAR LEVEL ***
+		-LoadLevel (script, see details below)
+	-*** PARK EDITOR ***
+		-DoParkGeneration (script, see below)
+
+
+=========================================================
+*** REGULAR LEVEL ***
+LoadLevel (script)
+-SetServerMode (command)
+-PreLevelLoad (script, see details below)
+-LaunchLevel (command, see details below)
+-PostLevelLoad
+-ResetCamera
+
+
+=========================================================
+*** PARK EDITOR ***
+DoParkGeneration (script)
+-PreLevelLoad (script, see details below)
+-LaunchLevel (command, see details below)
+-PostLevelLoad
+-ResetCamera
+-Do color, velocity, fogging stuff normally found in Load_LevName script
+
+
+=========================================================
+-PreLevelLoad (script)
+	-Cleanup
+	-Stopping, pausing music
+	-SetArenaSize 1
+		-call to RwResourcesSetArenaSize()
+	-Sounds
+	-SetLevelExists
+		-rwviewer::SetLevelExists()
+	-SetupBasicLights
+	-DisplayLoadingScreen
+
+
+=========================================================
+LaunchLevel (command)
+-Skate::OpenLevel() 
+	-**** REGULAR LEVEL ***
+		-runs level script, e.g. Load_Can
+			-loadlevelgeometry (command)
+				-rwviewer::RequestLoad() (see details below)
+			-loadnodearray (command)
+				-Script::RemoveOldTriggerScripts(), DeleteSpawnedScripts()
+				-Script::LoadQB()
+				-ScriptParseNodeArray()
+			-LoadTerrain (loads sounds)
+			-SetRenderModeVU
+			-Set colors, velocity, fog and other level attributes
+			-Call level startup, e.g. Can_startup
+	-*** PARK EDITOR ***
+		-ParkEditor::commandGeneratePark()
+			-ParkEditor::Initialize() [need ParkEditor::mp_compressedMap to be set up at this point]
+				-Map::InitializeWorld()
+					-World::CreateParkHeap()
+						-rwviewer::RequestLoad("sk3ed.bsp") (see details below)
+						-Create new heap for generated pieces
+					-World::PreparePieceSet()
+						-disconnect world sectors (source piece set) from just-loaded RpWorld
+						-Script::RemoveOldTriggerScripts(), DeleteSpawnedScripts()
+						-Script::LoadQB()
+						-Sk3Ed_Startup (script)
+				-Map::Initialize()
+					-Map::readMapFromBuf()
+						-Clone world sectors and link to them from the RpWorld
+				-Map::GenerateWorld()
+					-Generate super sectors
+					-Obj::Proxim_Init()
+					-Obj::Rail_Init()
+
+
+=========================================================
+// Probably safe for going into editor
+
+rwviewer::RequestLoad() 
+	-world_deinit()
+	-sky_deinit()
+	-world_init()
+		-set m_level_exists
+		-set m_dff_light_active
+		-scene_load()
+			-load bits texture dictionary
+			-Gfx::SetImageDirectoryFromFileName()
+			-Gfx::TexDictionaryLoad(), set it up
+			-RwStreamOpen(), stream in world
+			-GenerateSuperSectors()
+			-load models from DFF file, gather textures from clumps, add clumps to AssMan
+			-Pipeline::Chooser::FinishLoadingWorld()
+			-Upload texture dictionary
+		-Add cameras to world
+		-run SetupViewerLights?
+		-Mip K value stuff (NGPS)
+		-Add lights
+		-Get world sector render callback function
+		-Set up particle system
+
+
+
+
+
+=========================================================
+*** LEAVING CUSTOM PARK ***
+
+chosen_leave_server
+-ParkEditor::Cleanup()
+	-Map::Destroy()
+	-Map::DestroyWorld()
+-ScriptLeaveServer()
+	-Skate::LeaveServer()
+-SetNetworkMode
+
+
diff --git a/Code/Sk/ParkEditor2/EdMap.cpp b/Code/Sk/ParkEditor2/EdMap.cpp
new file mode 100644
index 0000000..2a8e1a2
--- /dev/null
+++ b/Code/Sk/ParkEditor2/EdMap.cpp
@@ -0,0 +1,5184 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+DefinePoolableClass(Ed::CMapListNode);
+
+#ifdef __PLAT_NGC__
+#define _16(a) (((a>>8)&0x00ff)|((a<<8)&0xff00))
+#define _32(a) (((a>>24)&0x000000ff)|((a>>8)&0x0000ff00)|((a<<8)&0x00ff0000)|((a<<24)&0xff000000)) 
+#else
+#define _16(a) a
+#define _32(a) a
+#endif		// __PLAT_NGC__
+
+#if 1
+#ifdef	__PLAT_NGPS__
+#define	__USE_EXTERNAL_BUFFER__
+#include 
+#include 
+#define	__EXTERNAL_BUFFER__ NxPs2::dma::pRuntimeBuffer
+#define	 ACQUIRE_BUFFER Nx::CEngine::sFinishRendering();
+#endif
+#endif
+
+namespace Ed
+{
+
+
+
+
+DefineSingletonClass(CParkManager, "Park Manager");
+
+
+
+CParkManager *CParkManager::sp_instance = NULL;
+
+GridDims::GridDims(uint8 x, sint8 y, uint8 z, uint8 w, uint8 h, uint8 l)
+{
+	m_dims[0] = x;
+	m_dims[1] = (uint8) y;
+	m_dims[2] = z;
+	m_dims[3] = w;
+	m_dims[4] = h;
+	m_dims[5] = l;
+}
+
+
+
+
+uint8 &GridDims::operator [](sint i)
+{
+	Dbg_Assert(i < 6);
+	return m_dims[i];
+}
+
+
+
+
+// Effectively gives the area an H of infinity
+void GridDims::MakeInfinitelyHigh()
+{
+	m_dims[4] = 127 - (sint8) m_dims[1];
+}
+
+
+
+
+// Effectively gives the area an H of infinity
+void GridDims::MakeInfiniteOnY()
+{
+	m_dims[1] = (uint8) -100;
+	m_dims[4] = 200;
+}
+
+
+
+
+void GridDims::PrintContents() const
+{
+	printf("pos=(%d,%d,%d) dims=(%d,%d,%d)\n", 
+		   m_dims[0], (sint8) m_dims[1], m_dims[2],
+		   m_dims[3], m_dims[4], m_dims[5]);
+}
+
+
+
+
+CMetaPiece::CMetaPiece()
+{
+	m_flags = EFlags(0);
+	mp_additional_desc_tab = NULL;
+	m_total_entries = 0;
+	m_num_used_entries = 0;
+}
+
+
+
+
+CMetaPiece::~CMetaPiece()
+{
+	if (mp_additional_desc_tab)
+		delete mp_additional_desc_tab;
+}
+
+
+
+
+CConcreteMetaPiece *CMetaPiece::CastToConcreteMeta()
+{
+	Dbg_MsgAssert(this == NULL || (m_flags & mCONCRETE_META), ("not a concrete metapiece"));
+	return (CConcreteMetaPiece *) this;
+}
+
+
+
+
+CAbstractMetaPiece *CMetaPiece::CastToAbstractMeta()
+{
+	Dbg_MsgAssert(this == NULL || (m_flags & mABSTRACT_META), ("not an abstract metapiece"));
+	return (CAbstractMetaPiece *) this;
+}
+
+bool CMetaPiece::IsRestartOrFlag()
+{
+	switch (m_name_checksum)
+	{
+		case CRCC(0xdc09a6a4,"Sk3Ed_RS_1p"):
+		case CRCC(0xca07b4fc,"Sk3Ed_RS_Mp"):
+		case CRCC(0x3a784d4c,"Sk3Ed_Rs_Ho"):
+		case CRCC(0x4e2efd2a,"Sk3Ed_Rs_KOTH"):
+		case CRCC(0x613c7766,"Sk4Ed_Team_Yellow"):
+		case CRCC(0xec82433c,"Sk4Ed_Team_Green"):
+		case CRCC(0xd7eb62b5,"Sk4Ed_Team_Red"):
+		case CRCC(0x8a1576b6,"Sk4Ed_Team_Blue"):
+			return true;
+			break;
+			
+		default:
+			break;
+	}		
+	
+	return false;
+}
+
+bool CMetaPiece::IsCompletelyWithinArea(const GridDims &area)
+{
+	if (m_cell_area.GetX() >= area.GetX() && m_cell_area.GetX()+m_cell_area.GetW() <= area.GetX()+area.GetW() &&
+		m_cell_area.GetZ() >= area.GetZ() && m_cell_area.GetZ()+m_cell_area.GetL() <= area.GetZ()+area.GetL())
+	{
+		return true;
+	}
+	return false;	
+}
+
+/*
+	Use to set rotation of abstract metapiece for testing purposes (in GetMetaPiecesAt()), or
+	to set rotation of concrete metapiece before adding to park.
+*/
+void CMetaPiece::SetRot(Mth::ERot90 newRot)
+{
+	Dbg_MsgAssert(!(m_flags & CMetaPiece::mIN_PARK), ("metapiece already in park -- can't change rotation"));
+	
+	// just want to change relative to current rotation
+	Mth::ERot90 rot = Mth::ERot90((newRot + 4 - m_rot) & 3);
+	m_rot = newRot;
+	if (rot & 1)
+	{
+		// switch W and L
+		m_cell_area.SetWHL(m_cell_area.GetL(), m_cell_area.GetH(), m_cell_area.GetW());
+	}
+	
+	SMetaDescriptor temp_new_first[USUAL_NUM_DESCRIPTORS];
+	
+	// Create a new table, which will soon replace the old one
+	SMetaDescriptor *p_meta_tab = NULL;
+	if (mp_additional_desc_tab)
+	{
+		Mem::Manager::sHandle().PushContext(CParkManager::sInstance()->GetGenerator()->GetParkEditorHeap());
+		p_meta_tab = new SMetaDescriptor[m_total_entries-USUAL_NUM_DESCRIPTORS];
+		Mem::Manager::sHandle().PopContext();
+	}
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		// "dims" contains position of upper-left, after rotation
+		SMetaDescriptor &out = (i < USUAL_NUM_DESCRIPTORS) ? temp_new_first[i] : p_meta_tab[i-USUAL_NUM_DESCRIPTORS];
+		SMetaDescriptor &in = (i < USUAL_NUM_DESCRIPTORS) ? m_first_desc_tab[i] : mp_additional_desc_tab[i-USUAL_NUM_DESCRIPTORS];
+		out = in;
+
+		// dimensions with current rotation (not new)
+		// OOG!
+		GridDims in_dims(0, 0, 0);
+		if (m_flags & mABSTRACT_META)
+		{
+			CAbstractMetaPiece *p_meta = CParkManager::sInstance()->GetAbstractMeta(in.mPieceName);
+			in_dims = p_meta->GetArea();
+			if ((in.mRot & 1))
+			{
+				// for a concrete metapiece, the retrieved cell dimensions will already reflect
+				// the current rotation, so no need to enter this block
+				in_dims.SetWHL(in_dims.GetL(), in_dims.GetH(), in_dims.GetW()); // switch W & L
+			}
+		}
+		else
+		{
+			in.mpPiece->GetCellDims(&in_dims);
+		}
+
+		switch(rot)
+		{
+			case Mth::ROT_0:
+				out.mX = in.mX;
+				out.mZ = in.mZ;
+				break;
+			case Mth::ROT_90:
+				out.mX = in.mZ;
+				out.mZ = m_cell_area.GetL() - in.mX - in_dims.GetW();
+				break;
+			case Mth::ROT_180:
+				out.mX = m_cell_area.GetW() - in.mX - in_dims.GetW();
+				out.mZ = m_cell_area.GetL() - in.mZ - in_dims.GetL();
+				break;
+			case Mth::ROT_270:
+				out.mX = m_cell_area.GetW() - in.mZ - in_dims.GetL();
+				out.mZ = in.mX;
+				break;
+			default:
+				Dbg_MsgAssert(0, ("ahhh-oooop?"));
+				break;
+		}		
+		
+		out.mY = in.mY;
+		out.mRot = Mth::ERot90((in.mRot + rot) & 3);
+		
+		// this will also copy in.mPieceName, if this is an abstract metapiece
+		out.mpPiece = in.mpPiece;
+	} // end for
+
+	if (mp_additional_desc_tab)
+		delete mp_additional_desc_tab;
+	mp_additional_desc_tab = p_meta_tab;
+	for (int i = 0; i < USUAL_NUM_DESCRIPTORS; i++)
+	{
+		m_first_desc_tab[i] = temp_new_first[i];
+		//printf("desc at (%d,%d,%d)\n", m_first_desc_tab[i].mX, m_first_desc_tab[i].mY, m_first_desc_tab[i].mZ);
+	}
+}
+
+
+
+
+// returns postion of piece's center, relative to center of metapiece
+Mth::Vector CMetaPiece::GetRelativePosOfContainedPiece(int index)
+{
+	// center_pos in world coordinates
+	Mth::Vector center_pos;
+	center_pos[X] = (float) m_cell_area.GetW() * CParkGenerator::CELL_WIDTH / 2.0f;
+	center_pos[Y] = 0;
+	center_pos[Z] = (float) m_cell_area.GetL() * CParkGenerator::CELL_LENGTH / 2.0f;
+
+	SMetaDescriptor &desc = get_desc_at_index(index);	
+	CPiece *p_piece = GetContainedPiece(index);
+	Mth::Vector piece_pos(desc.mX * CParkGenerator::CELL_WIDTH + p_piece->GetDimsWithRot(Mth::ERot90(desc.mRot)).GetX() / 2.0f,
+						  desc.mY * CParkGenerator::CELL_HEIGHT,
+						  desc.mZ * CParkGenerator::CELL_LENGTH + p_piece->GetDimsWithRot(Mth::ERot90(desc.mRot)).GetZ() / 2.0f);
+	return piece_pos - center_pos;
+}
+
+
+
+
+void CMetaPiece::BuildElement3dSectorsArray(Script::CArray *pArray)
+{
+	pArray->SetSizeAndType(m_num_used_entries, ESYMBOLTYPE_STRUCTURE);
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		SMetaDescriptor &desc = get_desc_at_index(i);	
+		
+		Script::CStruct *p_struct = new Script::CStruct();
+		p_struct->AddChecksum(NONAME, desc.mPieceName);
+		Mth::Vector pos = GetRelativePosOfContainedPiece(i);
+		p_struct->AddVector(NONAME, pos.GetX(), pos.GetY(), pos.GetZ());
+		
+		pArray->SetStructure(i, p_struct);
+	}
+}
+
+
+
+
+/*
+	Creates	new table of SMetaDescriptors. Might copy old one.
+*/
+void CMetaPiece::initialize_desc_table(int numEntries)
+{
+	SMetaDescriptor *p_new_tab = NULL;
+	if (numEntries > USUAL_NUM_DESCRIPTORS) 
+	{
+		Mem::Manager::sHandle().PushContext(CParkManager::sInstance()->GetGenerator()->GetParkEditorHeap());
+		p_new_tab = new SMetaDescriptor[numEntries-USUAL_NUM_DESCRIPTORS];
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	// Copy old table, if any. Only copied if there are to be at least as many entries as
+	// there were before.
+	if (p_new_tab && mp_additional_desc_tab && m_total_entries <= (uint) numEntries)
+	{
+		for (uint i = USUAL_NUM_DESCRIPTORS; i < m_total_entries; i++)
+			p_new_tab[i-USUAL_NUM_DESCRIPTORS] = mp_additional_desc_tab[i-USUAL_NUM_DESCRIPTORS];
+	}
+	
+	if (mp_additional_desc_tab)
+		delete mp_additional_desc_tab;
+	mp_additional_desc_tab = p_new_tab; 
+	m_total_entries = numEntries;
+}
+								
+
+
+
+/*
+	Returns specified descriptor.
+*/
+SMetaDescriptor &CMetaPiece::get_desc_at_index(int i)
+{
+	if (i < USUAL_NUM_DESCRIPTORS) return m_first_desc_tab[i];
+
+	Dbg_MsgAssert(mp_additional_desc_tab && (uint) i < m_num_used_entries, ("descriptor entry doesn't exist"));
+	return mp_additional_desc_tab[i-USUAL_NUM_DESCRIPTORS];
+}
+
+
+
+
+CConcreteMetaPiece::CConcreteMetaPiece()
+{
+	m_flags = EFlags(m_flags | mCONCRETE_META);
+	m_rot = 0;
+}
+
+
+
+
+int	CConcreteMetaPiece::CountContainedPieces()
+{
+	return m_num_used_entries;
+}
+
+
+
+
+CPiece *CConcreteMetaPiece::GetContainedPiece(int index)
+{
+	if ((uint) index >= m_num_used_entries || index < 0)
+		return NULL;
+
+	return get_desc_at_index(index).mpPiece;
+}
+
+
+
+
+/*
+	Used for the cursor piece, allowing any position and rotation, not just cell-aligned with 90 degree
+	increments. Doesn't affect "hard" position, rotation.
+*/
+void CConcreteMetaPiece::SetSoftRot(Mth::Vector pos, float rot)
+{
+	//Ryan("moving metapiece to (%.2f,%.2f,%.2f)\n", pos[X], pos[Y], pos[Z]);
+	
+	Mth::Vector center(m_cell_area.GetW() * CParkGenerator::CELL_WIDTH / 2.0f,
+					   0.0f,
+					   m_cell_area.GetL() * CParkGenerator::CELL_LENGTH / 2.0f);
+	
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		SMetaDescriptor &desc = get_desc_at_index(i);
+		
+		// BAKU
+		Mth::Vector piece_dims = desc.mpPiece->GetDims();
+		Mth::Vector piece_pos(desc.mX * CParkGenerator::CELL_WIDTH + piece_dims[X] / 2.0f - center[X],
+							  desc.mY * CParkGenerator::CELL_HEIGHT - center[Y],
+							  desc.mZ * CParkGenerator::CELL_LENGTH + piece_dims[Z] / 2.0f - center[Z]);
+
+		Mth::Matrix rot_mat;
+		rot_mat.Ident();
+		rot_mat.SetPos(piece_pos);
+		rot_mat.RotateY(Mth::DegToRad(rot));
+		rot_mat.Translate(pos);
+
+		//Ryan("    sub-piece position (%d) (%.2f,%.2f,%.2f)\n", desc.mY, rot_mat[Mth::POS][X], rot_mat[Mth::POS][Y], rot_mat[Mth::POS][Z]);
+		
+		desc.mpPiece->CastToCClonedPiece()->SetDesiredPos(rot_mat[Mth::POS], CClonedPiece::CHANGE_SECTOR);		
+		//desc.mpPiece->CastToCClonedPiece()->SetDesiredRot(Mth::ROT_0, CClonedPiece::CHANGE_SECTOR);
+		
+		float soft_rot = 90.0f * (float) desc.mRot + rot;
+		desc.mpPiece->CastToCClonedPiece()->SetSoftRot(soft_rot);
+	}
+}
+
+
+// Turn on or off the highlight effect for this concrete meta piece
+// which just turns the effect on or off for the cloned pieces that make it up 
+void CConcreteMetaPiece::Highlight(bool on, bool gapHighlight)
+{
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		SMetaDescriptor &desc = get_desc_at_index(i);
+		if (desc.mpPiece->GetFlags() & CPiece::mCLONED_PIECE)
+		{
+			desc.mpPiece->CastToCClonedPiece()->Highlight(on, gapHighlight);
+		}	
+	}
+}
+
+
+
+void CConcreteMetaPiece::SetVisibility(bool visible)
+{
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		SMetaDescriptor &desc = get_desc_at_index(i);
+		desc.mpPiece->CastToCClonedPiece()->SetActive(visible);
+	}
+}
+
+
+
+
+/*
+	Use after calls to add_piece() have been made. Once metapiece has been locked, it can be added to 
+	the world or used as a cursor
+*/
+void CConcreteMetaPiece::lock()
+{
+	// work out dimensions of metapiece from contained pieces
+	Mth::Vector min_pos, max_pos;
+	get_max_min_world_coords(max_pos, min_pos);
+	
+	Mth::Vector my_dims;
+	my_dims.Set(max_pos.GetX() - min_pos.GetX(), max_pos.GetY() - min_pos.GetY(), max_pos.GetZ() - min_pos.GetZ());
+
+	m_cell_area.SetWHL((uint8) ((my_dims.GetX() + 2.0f) / CParkGenerator::CELL_WIDTH),
+					   (uint8) ((my_dims.GetY() + 2.0f) / CParkGenerator::CELL_HEIGHT),
+					   (uint8) ((my_dims.GetZ() + 2.0f) / CParkGenerator::CELL_LENGTH));
+
+	if (m_cell_area[H] < 1) m_cell_area[H] = 1;
+	
+	//Dbg_Assert(m_cell_area.mW > 0 && m_cell_area.mL > 0);
+
+	// reposition all contained pieces relative to center bottom
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		CClonedPiece *p_piece = get_desc_at_index(i).mpPiece->CastToCClonedPiece();
+		
+		//Mth::Vector piece_dims = p_piece->GetDims();
+		p_piece->m_pos[X] = p_piece->m_pos.GetX() - min_pos.GetX() - my_dims.GetX() / 2.0f;
+		p_piece->m_pos[Z] = p_piece->m_pos.GetZ() - min_pos.GetZ() - my_dims.GetZ() / 2.0f;
+		p_piece->m_pos[Y] = p_piece->m_pos.GetY(); // - min_pos.GetY();
+	}
+}
+
+
+
+
+/*
+	Called by lock()
+*/
+void CConcreteMetaPiece::get_max_min_world_coords(Mth::Vector &max, Mth::Vector &min)
+{
+	min.Set(100000.0f, 100000.0f, 100000.0f, 0.0f);
+	max.Set(-100000.0f, -100000.0f, -100000.0f, 0.0f);
+
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		CClonedPiece *p_piece = get_desc_at_index(i).mpPiece->CastToCClonedPiece();
+		
+		Mth::Vector piece_dims = p_piece->GetDims();
+		Mth::Vector piece_pos = p_piece->GetPos();
+
+		if (piece_pos.GetX() - piece_dims.GetX() / 2.0f < min.GetX())
+			min[X] = piece_pos.GetX() - piece_dims.GetX() / 2.0f;
+		if (piece_pos.GetX() + piece_dims.GetX() / 2.0f > max.GetX())
+			max[X] = piece_pos.GetX() + piece_dims.GetX() / 2.0f;
+		
+		if (piece_pos.GetZ() - piece_dims.GetZ() / 2.0f < min.GetZ())
+			min[Z] = piece_pos.GetZ() - piece_dims.GetZ() / 2.0f;
+		if (piece_pos.GetZ() + piece_dims.GetZ() / 2.0f > max.GetZ())
+			max[Z] = piece_pos.GetZ() + piece_dims.GetZ() / 2.0f;
+		
+		if (piece_pos.GetY() < min.GetY())
+			min[Y] = piece_pos.GetY();
+		if (piece_pos.GetY() + piece_dims.GetY() > max.GetY())
+			max[Y] = piece_pos.GetY() + piece_dims.GetY();
+	}
+
+	//Dbg_Assert(max[X] - min[X] > 1.0f && max[Z] - min[Z] > 1.0f);
+}
+
+
+
+
+/* 
+	Determines if overlap between specified area of park and this metapiece.
+	
+	If excludeRisers set, then we ignore overlaps with contained CPieces that are risers.
+*/
+bool CConcreteMetaPiece::test_for_conflict(GridDims area, bool excludeRisers)
+{
+	// for now, we simply see if this metapiece overlaps with the area
+	// later, we may add code that tests against contained pieces
+
+	Dbg_Assert(m_cell_area.GetW() > 0 && m_cell_area.GetL() > 0 && m_cell_area.GetH() > 0);
+	Dbg_MsgAssert(area.GetW() > 0 && area.GetL() > 0 && area.GetH() > 0, ("invalid dimensions (%d,%d,%d)", 
+																		  area.GetW(), area.GetW(), area.GetL()));
+
+	// do quick test
+	
+	if (m_cell_area.GetX() + m_cell_area.GetW() <= area.GetX())
+		return false;
+	if (m_cell_area.GetX() >= area.GetX() + area.GetW())
+		return false;
+	if (m_cell_area.GetY() + m_cell_area.GetH() <= area.GetY())
+		return false;
+	if (m_cell_area.GetY() >= area.GetY() + area.GetH())
+		return false;
+	if (m_cell_area.GetZ() + m_cell_area.GetL() <= area.GetZ())
+		return false;
+	if (m_cell_area.GetZ() >= area.GetZ() + area.GetL())
+		return false;
+
+	bool contains_risers = false;
+   	if (excludeRisers)
+	{
+		for (uint i = 0; i < m_num_used_entries; i++)
+		{
+			SMetaDescriptor &desc = get_desc_at_index(i);
+			if (desc.mRiser) contains_risers = true;
+		}
+	}
+	// '|| if !excludeRisers' is implicit
+	if (!contains_risers) return true;
+	
+	for (uint i = 0; i < m_num_used_entries; i++)
+	{
+		SMetaDescriptor &desc = get_desc_at_index(i);
+		CClonedPiece *p_piece = GetContainedPiece(i)->CastToCClonedPiece();
+		GridDims piece_area;
+		p_piece->GetCellDims(&piece_area);
+		piece_area[X] = m_cell_area.GetX() + desc.mX;
+		piece_area[Y] = m_cell_area.GetY() + desc.mY;
+		piece_area[Z] = m_cell_area.GetZ() + desc.mZ;
+	
+		if (!desc.mRiser &&
+			piece_area.GetX() + piece_area.GetW() > area.GetX() &&
+			piece_area.GetY() + piece_area.GetH() > area.GetY()	&&
+			piece_area.GetZ() + piece_area.GetL() > area.GetZ() &&
+			piece_area.GetX() < area.GetX() + area.GetW() &&
+			piece_area.GetY() < area.GetY() + area.GetH() &&
+			piece_area.GetZ() < area.GetZ() + area.GetL())
+		{
+			return true;
+		}
+	}
+		
+	return false;
+}
+
+
+
+
+/*
+	See comments with original declaration.
+	
+	It is fine if pPiece has been rotated already.
+*/
+void CConcreteMetaPiece::add_piece(CPiece *pPiece, GridDims *pPos, Mth::ERot90 rot, bool isRiser)
+{
+	Dbg_Assert(pPiece);
+	Dbg_MsgAssert(!(pPiece->GetFlags() & CPiece::mIN_WORLD), ("piece already in world"));
+	
+	CClonedPiece *p_cloned = pPiece->CastToCClonedPiece();
+	#ifdef __NOPT_ASSERT__
+	if (m_num_used_entries > 0)
+	{
+		if (get_desc_at_index(0).mpPiece->GetFlags() & CPiece::mSOFT_PIECE)
+		{
+			Dbg_MsgAssert(pPiece->GetFlags() & CPiece::mSOFT_PIECE, ("must be a soft piece"));
+		}
+		else
+		{
+			Dbg_MsgAssert(!(pPiece->GetFlags() & CPiece::mSOFT_PIECE), ("must be a hard piece"));
+		}
+	}
+	#endif
+
+	Dbg_MsgAssert(m_num_used_entries < m_total_entries, ("out of entries"));
+	SMetaDescriptor &desc = get_desc_at_index(m_num_used_entries++);
+
+	// Set position relative to northwest bottom of piece. For now.
+	Mth::Vector piece_dims = p_cloned->GetDims();
+	Mth::Vector desired_pos;
+	desired_pos.Set(pPos->GetX() * CParkGenerator::CELL_WIDTH + piece_dims.GetX() / 2.0f,
+						  pPos->GetY() * CParkGenerator::CELL_HEIGHT, // + piece_dims.GetY() / 2.0f,
+						  pPos->GetZ() * CParkGenerator::CELL_LENGTH + piece_dims.GetZ() / 2.0f);
+	p_cloned->SetDesiredPos(desired_pos, CClonedPiece::MARKER_ONLY);
+
+	desc.mpPiece = p_cloned;
+	desc.mX = pPos->GetX();
+	desc.mY = pPos->GetY();
+	desc.mZ = pPos->GetZ();
+	desc.mRot = rot;
+	desc.mRiser = isRiser ? 1 : 0;
+
+	/*
+	if (isRiser)
+	{
+		printf("hoogaboog 2 (%d,%d,%d), desc.mRiser is %d, desc.mRiser at 0x%x, index %d\n", 
+			   desc.mX, desc.mY, desc.mZ, desc.mRiser, &desc.mRiser, m_num_used_entries-1);
+	}
+	*/
+}
+
+
+
+
+CAbstractMetaPiece::CAbstractMetaPiece()
+{
+	m_flags = EFlags(m_flags | mABSTRACT_META);
+}
+
+
+
+
+CPiece *CAbstractMetaPiece::GetContainedPiece(int index)
+{
+	if ((uint) index >= m_num_used_entries || index < 0)
+		return NULL;
+
+	CPiece *p_piece = CParkManager::sInstance()->GetGenerator()->GetMasterPiece(get_desc_at_index(index).mPieceName);
+	Dbg_Assert(p_piece);
+	return p_piece;
+}
+
+
+
+
+/*
+	See comments with original declaration. It is fine if pPiece has been rotated already.
+	
+	Automatically changes stored cell WHL dimensions of metapiece to include new addition.
+*/
+void CAbstractMetaPiece::add_piece(CPiece *pPiece, GridDims *pPos, Mth::ERot90 rot, bool isRiser)
+{
+	Dbg_Assert(pPiece);
+	CSourcePiece *p_source = NULL;
+	if (pPiece->GetFlags() & CPiece::mSOURCE_PIECE)
+		p_source = pPiece->CastToCSourcePiece();
+	if (!p_source)
+		Dbg_MsgAssert(0, ("not supported yet"));
+
+	Dbg_MsgAssert(m_num_used_entries < m_total_entries, ("out of entries"));
+	SMetaDescriptor &desc = get_desc_at_index(m_num_used_entries++);
+
+	if (p_source)
+	{
+		desc.mX = pPos->GetX();
+		desc.mY = pPos->GetY();
+		desc.mZ = pPos->GetZ();
+		desc.mRot = rot;
+		desc.mRiser = (uint8) isRiser;
+		desc.mPieceName = p_source->GetType();
+
+		int highest_x = pPos->GetX() + pPos->GetW();
+		int highest_y = pPos->GetY() + pPos->GetH();
+		int highest_z = pPos->GetZ() + pPos->GetL();
+
+		uint8 current_w = m_cell_area.GetW();
+		uint8 current_h = m_cell_area.GetH();
+		uint8 current_l = m_cell_area.GetL();
+		
+		if (highest_x > current_w)
+			current_w = highest_x;
+		if (highest_y > current_h)
+			current_h = highest_y;
+		if (highest_z > current_l)
+			current_l = highest_z;
+
+		m_cell_area.SetWHL(current_w, current_h, current_l);
+		if (m_cell_area[H] < 1) m_cell_area[H] = 1;
+	}
+	
+	/*
+	if (isRiser)
+	{
+		printf("hoogaboog 1 (%d,%d,%d), index %d\n", desc.mX, desc.mY, desc.mZ, m_num_used_entries-1);
+	}
+	*/
+}
+
+
+
+
+void CAbstractMetaPiece::add_piece_dumb(CAbstractMetaPiece *pMeta, GridDims *pPos, Mth::ERot90 rot, bool isRiser)
+{
+	Dbg_MsgAssert(m_num_used_entries < m_total_entries, ("out of entries"));
+	SMetaDescriptor &desc = get_desc_at_index(m_num_used_entries++);
+
+	if (pMeta)
+	{
+		desc.mX = pPos->GetX();
+		desc.mY = pPos->GetY();
+		desc.mZ = pPos->GetZ();
+		desc.mRot = rot;
+		desc.mRiser = (uint8) isRiser;
+		desc.mPieceName = pMeta->GetNameChecksum();
+
+		int highest_x = pPos->GetX() + pPos->GetW();
+		int highest_y = pPos->GetY() + pPos->GetH();
+		int highest_z = pPos->GetZ() + pPos->GetL();
+
+		uint8 current_w = m_cell_area.GetW();
+		uint8 current_h = m_cell_area.GetH();
+		uint8 current_l = m_cell_area.GetL();
+		
+		if (highest_x > current_w)
+			current_w = highest_x;
+		if (highest_y > current_h)
+			current_h = highest_y;
+		if (highest_z > current_l)
+			current_l = highest_z;
+
+		m_cell_area.SetWHL(current_w, current_h, current_l);
+	}
+	
+	/*
+	if (isRiser)
+	{
+		printf("hoogaboog 1 (%d,%d,%d), index %d\n", desc.mX, desc.mY, desc.mZ, m_num_used_entries-1);
+	}
+	*/
+}
+
+
+
+
+SMetaDescriptor CAbstractMetaPiece::get_desc_with_expansion(int index)
+{
+	#if 1
+	int count = 0;
+	for (uint j = 0; j < m_num_used_entries; j++)
+	{
+		SMetaDescriptor &outer_desc = get_desc_at_index(j);
+
+		CAbstractMetaPiece *p_contained_meta = CParkManager::sInstance()->GetAbstractMeta(outer_desc.mPieceName);
+		if (p_contained_meta->m_flags & CMetaPiece::mSINGULAR)
+		{
+			if (count == index)
+				return outer_desc;
+			count++;
+		}
+		else
+		{
+			for (uint i = 0; i < p_contained_meta->m_num_used_entries; i++)
+			{
+				SMetaDescriptor &inner_desc = p_contained_meta->get_desc_at_index(i);
+				if (count == index)
+				{
+					SMetaDescriptor ret_desc = inner_desc;
+					ret_desc.mPieceName = inner_desc.mPieceName;
+					ret_desc.mRot = inner_desc.mRot + outer_desc.mRot;
+					
+					GridDims outer_dims = GetArea();
+
+					GridDims inner_dims = p_contained_meta->GetArea();
+					
+					ret_desc.mX = outer_desc.mX;
+					ret_desc.mZ = outer_desc.mZ;
+					ret_desc.mY = outer_desc.mY + inner_desc.mY;
+					
+					switch(outer_desc.mRot)
+					{
+						case Mth::ROT_0:
+							ret_desc.mX += inner_desc.mX;
+							ret_desc.mZ += inner_desc.mZ;
+							break;
+						case Mth::ROT_90:
+							ret_desc.mX += inner_desc.mZ;
+							ret_desc.mZ += outer_dims.GetW() - inner_desc.mX - inner_dims.GetW();
+							break;
+						case Mth::ROT_180:
+							ret_desc.mX += outer_dims.GetW() - inner_desc.mX - inner_dims.GetW();
+							ret_desc.mZ += outer_dims.GetL() - inner_desc.mZ - inner_dims.GetL();
+							break;
+						case Mth::ROT_270:
+							ret_desc.mX += outer_dims.GetL() - inner_desc.mZ - inner_dims.GetL();
+							ret_desc.mZ += inner_desc.mX;
+							break;
+					}
+					
+					return ret_desc;
+				}
+				count++;
+			}
+		}
+	}
+	Dbg_MsgAssert(0, ("what the bloody 'ell?"));
+	SMetaDescriptor dumb;
+	return dumb;
+	#else
+	return get_desc_at_index(index);
+	#endif
+}
+
+
+
+
+int CAbstractMetaPiece::count_descriptors_expanded()
+{
+	#if 1
+	int count = 0;
+	for (uint j = 0; j < m_num_used_entries; j++)
+	{
+		SMetaDescriptor &outer_desc = get_desc_at_index(j);
+
+		CAbstractMetaPiece *p_contained_meta = CParkManager::sInstance()->GetAbstractMeta(outer_desc.mPieceName);
+		Dbg_MsgAssert(p_contained_meta, ("couldn't find metapiece %s", Script::FindChecksumName(outer_desc.mPieceName)));
+		if (p_contained_meta->m_flags & CMetaPiece::mSINGULAR)
+		{
+			count++;
+		}
+		else
+		{
+			for (uint i = 0; i < p_contained_meta->m_num_used_entries; i++)
+			{
+				count++;
+			}
+		}
+	}
+	return count;
+	#else
+	return m_num_used_entries;
+	#endif
+}
+
+
+
+
+CMetaPiece *CMapListNode::GetMeta()
+{
+	Dbg_Assert(this);
+	Dbg_MsgAssert(((uint32)this) != 0x03030303,("Bad CMapListNode pointer ! (0x03030303)"));
+	Dbg_MsgAssert(((uint32)this) != 0x55555555,("Bad CMapListNode pointer ! (0x55555555)"));
+	Dbg_Assert(mp_meta);
+	return mp_meta;
+}
+
+
+
+
+void CMapListNode::DestroyList()
+{
+	CMapListNode *p_node = this;
+	while (p_node)
+	{
+		CMapListNode *p_next_node = p_node->mp_next;
+		delete p_node;
+		p_node = p_next_node;
+	}
+}
+
+
+
+
+void CMapListNode::VerifyIntegrity()
+{
+	CMapListNode *p_outer_node = this;
+	while (p_outer_node)
+	{
+		CMapListNode *p_inner_node = p_outer_node->mp_next;
+		while (p_inner_node)
+		{
+			Dbg_Assert(p_inner_node->GetMeta() != p_outer_node->GetMeta());
+			p_inner_node = p_inner_node->mp_next;
+		}
+		p_outer_node = p_outer_node->mp_next;
+	}
+}
+
+
+
+
+CMapListTemp::CMapListTemp(CMapListNode *p_list)
+{
+	mp_list = p_list;
+}
+
+
+
+
+CMapListTemp::~CMapListTemp()
+{
+	if (mp_list)
+		mp_list->DestroyList();
+}
+
+
+
+
+CMapListNode *CMapListTemp::GetList()
+{
+	return mp_list;
+}
+
+
+
+
+void CMapListTemp::PrintContents()
+{
+	printf("contents of map list:\n");
+	CMapListNode *p_node = mp_list;
+	while(p_node)
+	{
+		printf("   address=%p area=", p_node->GetMeta());
+		p_node->GetMeta()->GetArea().PrintContents();
+		p_node = p_node->GetNext();
+	}
+}
+
+
+
+
+CParkManager::CParkManager()
+{
+	mp_generator = new CParkGenerator();
+	
+	mp_concrete_metapiece_list = NULL;
+	mp_abstract_metapiece_list = NULL;
+	m_num_concrete_metapieces = 0;
+	m_num_general_concrete_metapieces = 0;
+	m_dma_piece_count = 0;
+	m_num_abstract_metapieces = 0;
+
+	m_floor_height_map = NULL;
+	
+	for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+		for (int j = 0; j < FLOOR_MAX_LENGTH; j++)
+			mp_bucket_list[i][j] = NULL;
+
+	m_state_on = false;
+
+	sp_instance = this;
+
+	mp_compressed_map_buffer = new uint8[COMPRESSED_MAP_SIZE];
+	m_compressed_map_flags = mNO_FLAGS;
+	m_park_is_valid = false;
+
+	mp_gap_manager = NULL;
+	
+	for (int i = 0; i < NUM_RISER_INFO_SLOTS; i++)
+	{
+		char name0[128];
+		sprintf(name0, "floor_wall_block%d", i+1);
+		m_riser_piece_checksum[i][0] = Script::GenerateCRC(name0);
+		
+		char name1[128];
+		sprintf(name1, "wall_block%d", i+1);
+		m_riser_piece_checksum[i][1] = Script::GenerateCRC(name1);
+		
+		char name2[128];
+		sprintf(name2, "floor_block%d", i+1);
+		m_riser_piece_checksum[i][2] = Script::GenerateCRC(name2);
+	}
+
+	m_theme = 0;
+	
+	// Mick: Create a compressed map buffer, equivalent to an empty park								
+	fake_compressed_map_buffer();
+
+	mp_column_slide_list = NULL;	
+	
+	mp_park_name[0]=0;
+	
+	#ifdef USE_BUILD_LIST
+	m_build_list_size=0;
+	mp_build_list_entry=NULL;
+	#endif
+}
+
+
+
+
+CParkManager::~CParkManager()
+{
+	#ifdef USE_BUILD_LIST
+	if (mp_build_list_entry)
+	{
+		#ifndef	__USE_EXTERNAL_BUFFER__
+		Mem::Free(mp_build_list_entry);
+		#endif
+		mp_build_list_entry=NULL;
+	}	
+	#endif
+	
+	delete mp_generator;
+	delete mp_compressed_map_buffer;
+
+	sp_instance = NULL;
+}
+
+
+
+
+/*
+	Boots up park editor.
+	
+	-Loads up files needed
+	-Initializes buffers
+	-Initializes CParkGenerator module
+	-Calls Rebuild(), which:
+		-Clears map
+		-Generates riser geometry
+	-Creates piece set database
+*/
+void CParkManager::Initialize()
+{
+	ParkEd("CParkManager::Initialize()");
+	
+	if (m_state_on)
+		return;
+	m_state_on = true;
+
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	Mem::CPoolable::SCreatePool(16000, "CMapListNode");
+	Mem::CPoolable::SPrintInfo();
+
+	Mem::CPoolable::SCreatePool(MAX_CLIPBOARD_METAS, "CClipboardEntry");
+	Mem::CPoolable::SCreatePool(MAX_CLIPBOARDS, "CClipboard");
+	Mem::Manager::sHandle().PopContext();
+
+	mp_generator->InitializeMasterPieces(MAX_WIDTH, 16, MAX_LENGTH, GetTheme());
+	mp_generator->ReadInRailInfo();
+
+	create_abstract_metapieces();
+
+	setup_road_mask();
+	
+	Dbg_MsgAssert(!m_floor_height_map, ("bad monkey!!!"));
+	m_floor_height_map = new FloorHeightEntry*[FLOOR_MAX_WIDTH];
+	for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+	{
+		m_floor_height_map[i] = new FloorHeightEntry[FLOOR_MAX_LENGTH];		
+		for (int j = 0; j < FLOOR_MAX_LENGTH; j++)
+		{
+			m_floor_height_map[i][j].mMarkAsSlid = false;
+			m_floor_height_map[i][j].mSlideAmount = 0;
+		}
+	}
+
+
+	setup_default_dimensions();
+		
+	mp_gap_manager = new CGapManager(this);
+	
+	RebuildWholePark(false);
+
+	Script::CArray *p_set_array = Script::GetArray("Ed_piece_sets", Script::ASSERT);
+	m_total_sets = p_set_array->GetSize();
+	Dbg_Assert(m_total_sets <= MAX_SETS);
+
+	Script::CArray *p_piece_array = Script::GetArray("Ed_standard_metapieces", Script::ASSERT);
+	int last_piece_index=p_piece_array->GetSize();
+	int last_set_index=-1;
+	
+	for (int s = m_total_sets-1; s >=0 ; --s)
+	{
+		Script::CStruct *p_set = p_set_array->GetStructure(s);
+		
+		m_palette_set[s].mpName = NULL;
+		p_set->GetText("name", &m_palette_set[s].mpName, Script::ASSERT);
+		m_palette_set[s].mNameCrc = Script::GenerateCRC(m_palette_set[s].mpName);
+		m_palette_set[s].mSelectedEntry = 0;
+		
+		m_palette_set[s].mTotalEntries=0;
+
+		uint32 first_checksum;
+		if (p_set->GetChecksum("first", &first_checksum))
+		{
+			int first_index=0;
+			if (!GetAbstractMeta(first_checksum, &first_index))
+			{
+				Dbg_MsgAssert(0, ("couldn't find first-in-set entry"));
+			}
+
+			// make sure index in previous first-in_set_entry is less than this one
+			if (last_set_index>=0)
+			{
+				Dbg_MsgAssert(first_index <
+							  last_piece_index,
+							  ("sets %s and %s are out of order in EdPieces2.q", 
+							   m_palette_set[s].mpName,
+							   m_palette_set[last_set_index].mpName));
+			}
+			else
+			{
+				Dbg_MsgAssert(first_index <
+							  last_piece_index,
+							  ("set %s has a bad first piece in EdPieces2.q", 
+							   m_palette_set[s].mpName));
+			}
+			m_palette_set[s].mTotalEntries=last_piece_index-first_index;
+			last_piece_index=first_index;
+			last_set_index=s;
+			
+			// Now that we know the number of entries, create the set.
+			int entry=first_index;
+			for (int i=0; iGetNameChecksum();
+				
+				Script::CStruct *p_piece_entry = p_piece_array->GetStructure(entry);
+				if (!p_piece_entry->GetLocalString("text_name", &m_palette_set[s].mEntryTab[i].mpName))
+				{
+					m_palette_set[s].mEntryTab[i].mpName = NULL;
+				}
+			
+				++entry;
+			}
+		}
+
+		m_palette_set[s].mIsClipboardSet=false;
+		if (p_set->ContainsFlag(CRCD(0x45c6c158,"clipboard_set")))
+		{
+			m_palette_set[s].mIsClipboardSet=true;
+			m_palette_set[s].mTotalEntries=MAX_CLIPBOARDS;
+			
+			const char *p_clipboard_title=Script::GetString(CRCD(0x4e677220,"ClipboardTitle"));
+			
+			for (int i=0; iContainsFlag("hidden");
+		#ifdef __PLAT_NGC__
+		if (p_set->ContainsFlag("no_gamecube"))
+			m_palette_set[s].mVisible = false;
+		#endif
+	}
+}
+
+float CParkManager::GetClipboardProportionUsed()
+{
+	float proportion_clipboard_entries=((float)Mem::CPoolable::SGetNumUsedItems())/Mem::CPoolable::SGetTotalItems();
+	float proportion_clipboard_slots=((float)Mem::CPoolable::SGetNumUsedItems())/Mem::CPoolable::SGetTotalItems();
+
+	if (proportion_clipboard_entries > proportion_clipboard_slots)
+	{
+		return proportion_clipboard_entries;
+	}
+	return proportion_clipboard_slots;
+}
+	
+// This is just a public version of the private function
+// needed so the parks can be saved from within the park editor
+// This just updates the contents of the map buffer that will be used
+// by the enxt two public functions
+void CParkManager::WriteCompressedMapBuffer()
+{
+	if (m_state_on)
+		write_compressed_map_buffer();	
+}
+
+uint8* CParkManager::GetCompressedMapBuffer(bool markSaved)
+{
+	/*
+		The compressed map buffer is valid if:
+		
+		-The park manager is active AND (in sync with built park OR newer than built park) OR
+		-The park manager is not active
+	*/
+	
+	Dbg_MsgAssert((m_compressed_map_flags & mIS_VALID), ("compressed map buffer not valid")); 
+	if (m_state_on)
+	{
+		Dbg_MsgAssert((m_compressed_map_flags & mIN_SYNC_WITH_PARK) || (m_compressed_map_flags & mIS_NEWER_THAN_PARK),
+					  ("park has changed, compressed map buffer out of sync"));
+	}
+
+#ifdef __NOPT_ASSERT__
+	CompressedMapHeader *p_header = (CompressedMapHeader *) mp_compressed_map_buffer;
+	Dbg_Printf( "************ NUM METAS: %d\n", p_header->mNumMetas );
+#endif		// __NOPT_ASSERT__
+
+	if (markSaved)
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mNOT_SAVED_LOCAL);
+	
+	return mp_compressed_map_buffer;
+}
+
+
+
+
+void	CParkManager::SetCompressedMapBuffer( uint8* src_buffer, bool markNotSaved )
+{
+	ParkEd("SetCompressedMapBuffer()");
+	
+	
+	memcpy( mp_compressed_map_buffer, src_buffer, COMPRESSED_MAP_SIZE );
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mIS_VALID);
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mIS_NEWER_THAN_PARK);
+
+	if (markNotSaved)
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mNOT_SAVED_LOCAL);
+
+#ifdef __NOPT_ASSERT__
+	CompressedMapHeader *p_header = (CompressedMapHeader *) mp_compressed_map_buffer;
+	//uint32 checksum=Crc::GenerateCRCCaseSensitive((const char*)mp_compressed_map_buffer+4,COMPRESSED_MAP_SIZE-4);
+	//Dbg_MsgAssert(checksum==p_header->mChecksum,("Park editor buffer checksum mismatch !"));
+	
+	Dbg_Printf( "************ NUM METAS: %d\n", p_header->mNumMetas );
+#endif		// __NOPT_ASSERT__
+}
+
+
+
+
+bool CParkManager::IsMapSavedLocally()
+{
+	if ((!(m_compressed_map_flags & mIN_SYNC_WITH_PARK) && !(m_compressed_map_flags & mIS_NEWER_THAN_PARK)) || 
+		(m_compressed_map_flags & mNOT_SAVED_LOCAL))
+		return false;
+	return true;
+}
+
+
+void CParkManager::WriteIntoStructure(Script::CStruct *p_struct)
+{
+	WriteCompressedMapBuffer();		// Ensure map buffer is correct
+	uint32	*p_map_buffer = (uint32*) GetCompressedMapBuffer(true);
+	int		size = COMPRESSED_MAP_SIZE ;
+
+	// create the array
+	Script::CArray *p_map=new Script::CArray;
+	p_map->SetArrayType(size/4,ESYMBOLTYPE_INTEGER);
+	// copy the park editor into it
+	for (int i=0;iSetInteger(i,p_map_buffer[i]);
+	}
+	
+	// append it to p_struct
+	p_struct->AddArrayPointer(CRCD(0x337c5289,"Park_editor_map"),p_map);
+
+	// Now also add in any created goals, so that the goals do not have to saved out to a separate file.
+	Script::CStruct *p_goals_struct=new Script::CStruct;
+	
+	Obj::CGoalEditorComponent *p_goal_editor=Obj::GetGoalEditor();
+	Dbg_MsgAssert(p_goal_editor,("No goal editor component ???"));
+	p_goal_editor->WriteIntoStructure(CRCD(0xe8b4b836,"Load_Sk5Ed"),p_goals_struct);
+	
+	
+	p_struct->AddStructurePointer(CRCD(0xd8eb825e,"Park_editor_goals"),p_goals_struct);
+
+	// Add in the created rails.			
+	Obj::GetRailEditor()->WriteIntoStructure(p_struct);
+
+	p_struct->AddInteger(CRCD(0xb7e39b53,"MaxPlayers"),GetGenerator()->GetMaxPlayers());
+}
+
+#ifdef __PLAT_NGC__
+void CParkManager::ReadFromStructure(Script::CStruct *p_struct, bool do_post_mem_card_load, bool preMadePark)
+#else
+void CParkManager::ReadFromStructure(Script::CStruct *p_struct, bool do_post_mem_card_load)
+#endif
+{
+	Obj::CGoalEditorComponent *p_goal_editor=Obj::GetGoalEditor();
+	Dbg_MsgAssert(p_goal_editor,("No goal editor component ???"));
+	
+	p_goal_editor->ClearOnlyParkGoals();
+	
+	// Read in any created goals.
+	Script::CStruct *p_goals_struct=NULL;
+	if (p_struct->GetStructure(CRCD(0xd8eb825e,"Park_editor_goals"),&p_goals_struct))
+	{
+		p_goal_editor->ReadFromStructure(p_goals_struct,Obj::CGoalEditorComponent::LOADING_PARK_GOALS);
+	}
+
+	// Read in the created rails
+	Spt::SingletonPtr p_park_ed;
+	if (!p_park_ed->EditingCustomPark())
+	{
+		// Do not call UpdateSuperSectors after deleting any existing rails if playing a park,
+		// otherwise the entire park will disappear from under the skater due to all the sectors
+		// having been flagged for deletion.
+		Obj::CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=false;
+	}	
+	Obj::GetRailEditor()->ReadFromStructure(p_struct);
+	Obj::CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors=true;
+	
+	int max_players=2;
+	p_struct->GetInteger(CRCD(0xb7e39b53,"MaxPlayers"),&max_players);
+	Ed::CParkManager::Instance()->GetGenerator()->SetMaxPlayers(max_players);
+				
+	int old_theme = GetTheme();
+	WriteCompressedMapBuffer();
+	uint32	*p_map_buffer = (uint32*) GetCompressedMapBuffer();
+	int		size = Ed::CParkManager::COMPRESSED_MAP_SIZE ;
+	// get the array
+	Script::CArray *p_map=NULL;
+	if (p_struct->GetArray("Park_editor_map",&p_map,true))
+	{
+		// copy the park editor map from structure to buffer
+		for (int i=0;iGetInteger(i);
+				p_map_buffer[i] = ((v>>24) | (((v>>16)&0xff)<<8) | (((v>>8)&0xff)<<16) | ((v&0xff)<<24));
+			}	
+			else
+			{
+				p_map_buffer[i] = p_map->GetInteger(i);
+			}		
+			#else
+			p_map_buffer[i] = p_map->GetInteger(i);
+			#endif
+		}
+
+		#ifdef __PLAT_NGC__
+		if (preMadePark)
+		{
+			// If loading a pre-made park, it will need the map buffer endianness swapped since
+			// it was saved from the PS2.
+			SwapMapBufferEndianness();
+		}	
+		#endif
+		
+		if (do_post_mem_card_load)
+		{
+			Spt::SingletonPtr p_park_ed;
+			p_park_ed->PostMemoryCardLoad((uint8*) p_map_buffer, old_theme);
+		}	
+	}
+}
+
+int CParkManager::GetTheme()
+{
+	if (m_compressed_map_flags & mIS_VALID)
+	{
+		int theme = (((CompressedMapHeader *) mp_compressed_map_buffer)->mTheme);
+		Dbg_MsgAssert(theme >= 0 && theme <= MAX_THEMES, ("nonsense theme %d", theme));
+		return theme;
+	}
+
+	return 0;
+}
+
+
+
+
+void CParkManager::SetTheme(int theme)
+{
+	Dbg_MsgAssert(theme >= 0 && theme < MAX_THEMES , ("nonsense theme %d", theme));
+	m_theme = theme;
+	write_compressed_map_buffer();
+}
+
+
+
+
+const char *CParkManager::GetParkName()
+{
+	if (mp_compressed_map_buffer && ((CompressedMapHeader *) mp_compressed_map_buffer)->mVersion == (VERSION))
+		return ((CompressedMapHeader *) mp_compressed_map_buffer)->mParkName;
+	else
+		return NULL;
+}
+
+uint32 CParkManager::GetParkChecksum()
+{
+	if (mp_compressed_map_buffer && ((CompressedMapHeader *) mp_compressed_map_buffer)->mVersion == (VERSION))
+		return ((CompressedMapHeader *) mp_compressed_map_buffer)->mChecksum;
+	else
+		return 0;
+}
+
+int	CParkManager::GetCompressedParkWidth()
+{
+	if (mp_compressed_map_buffer && ((CompressedMapHeader *) mp_compressed_map_buffer)->mVersion == (VERSION))
+		return ((CompressedMapHeader *) mp_compressed_map_buffer)->mW;
+	else
+		return 0;
+}
+
+int	CParkManager::GetCompressedParkLength()
+{
+	if (mp_compressed_map_buffer && ((CompressedMapHeader *) mp_compressed_map_buffer)->mVersion == (VERSION))
+		return ((CompressedMapHeader *) mp_compressed_map_buffer)->mL;
+	else
+		return 0;
+}
+
+
+void CParkManager::SetParkName(const char *pName)
+{
+	if (!mp_compressed_map_buffer || ((CompressedMapHeader *) mp_compressed_map_buffer)->mVersion != (VERSION)) return;
+	Dbg_Assert(pName);
+	Dbg_MsgAssert(strlen(pName) < 64, ("park name too long"));
+	strcpy(((CompressedMapHeader *) mp_compressed_map_buffer)->mParkName, pName);
+	strcpy(mp_park_name, pName);
+}
+
+
+
+
+void CParkManager::AccessDisk(bool save, int fileSlot)
+{
+	char fullname[64];
+	sprintf(fullname, "CustomParks\\custom%d.prk", fileSlot);
+
+	if (save)
+	{
+//		Ryan("I am saving park %s\n", fullname);
+//		printf("I am saving park %s\n", fullname);
+		
+		Script::CStruct *p_struct=new Script::CStruct;
+		WriteIntoStructure(p_struct);
+		uint32 size=Script::CalculateBufferSize(p_struct);
+		uint8 *p_buffer=(uint8*)Mem::Malloc(size);
+		Script::WriteToBuffer(p_struct,p_buffer,size);
+		
+		void *fp = File::Open(fullname, "wb");
+		Dbg_MsgAssert(fp, ("failed to open file %s for write", fullname));
+		File::Write(p_buffer, size, 1, fp);
+		File::Close(fp);
+		
+		Mem::Free(p_buffer);
+		delete p_struct;
+	}
+	else
+	{
+//		Ryan("I am loading park %s\n", fullname);
+//		printf("I am loading park %s\n", fullname);
+		void *fp = File::Open(fullname, "rb");
+		Dbg_MsgAssert(fp, ("failed to open file %s for read", fullname));
+		
+		int size=File::GetFileSize(fp);
+
+		uint8 *p_buffer=(uint8*)Mem::Malloc(size);
+		
+		File::Read(p_buffer, size, 1, fp);
+		File::Close(fp);
+		
+		Script::CStruct *p_struct=new Script::CStruct;
+		Script::ReadFromBuffer(p_struct,p_buffer);
+		Mem::Free(p_buffer);
+
+		#ifdef __PLAT_NGC__
+		ReadFromStructure(p_struct,false,true);
+		#else
+		ReadFromStructure(p_struct,false);
+		#endif
+		
+		delete p_struct;
+				
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | (mIS_NEWER_THAN_PARK + mIS_VALID));
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mIN_SYNC_WITH_PARK);
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mNOT_SAVED_LOCAL);
+	}	
+}
+
+
+
+
+/*
+	Destroys everything, concrete metas, abstract metas, floor map.
+*/
+// K: Added type so that DESTROY_ONLY_PIECES can be passed when this function is used to clean
+// up the park editor heap for deletion when playing a park.
+void CParkManager::Destroy(CParkGenerator::EDestroyType type)
+{
+	ParkEd("CParkManager::Destroy(), m_compressed_map_flags=%08x", m_compressed_map_flags);
+	
+	if (!m_state_on)
+		return;
+	
+	if (!(m_compressed_map_flags & mIN_SYNC_WITH_PARK) && !(m_compressed_map_flags & mIS_NEWER_THAN_PARK))
+	{
+		write_compressed_map_buffer();
+	}
+	
+	Dbg_Assert(mp_gap_manager);
+	mp_gap_manager->RemoveAllGaps();
+	
+	// remove all abstract metapieces
+	CMapListNode *p_node = NULL;
+	while((p_node = mp_abstract_metapiece_list))
+	{
+		CMetaPiece *p_meta = p_node->GetMeta();
+		remove_metapiece_from_node_list(p_meta, &mp_abstract_metapiece_list);
+		delete p_meta;
+	}
+	m_num_abstract_metapieces = 0;
+	
+	destroy_concrete_metapieces(type);
+	
+	mp_generator->UnloadMasterPieces();
+	mp_generator->DestroyRailInfo();   		// info from node array (mostly rails)
+	
+	Dbg_MsgAssert(m_floor_height_map, ("bad bad monkey!!!"));
+	for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+		delete [] m_floor_height_map[i];
+	delete [] m_floor_height_map;
+	m_floor_height_map = NULL;
+	
+	m_state_on = false;
+	// since park is destroyed, buffer is out of sync and newer than park
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mIN_SYNC_WITH_PARK);
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mIS_NEWER_THAN_PARK);
+	m_park_is_valid = false;
+	
+	Dbg_Assert(mp_gap_manager);
+	delete mp_gap_manager;
+	mp_gap_manager = NULL;
+	
+	if (mp_column_slide_list)
+		mp_column_slide_list->DestroyList();
+	mp_column_slide_list = NULL;
+	
+	Mem::CPoolable::SRemovePool();	
+	Mem::CPoolable::SRemovePool();	
+	Mem::CPoolable::SRemovePool();
+}
+
+
+void CParkManager::RebuildWholePark(bool clearPark)
+{
+	ParkEd("CParkManager::RebuildWholePark() 0x%x", m_compressed_map_flags);
+
+	setup_lighting();  // ensure light arrays are correct for current theme
+	
+	Dbg_Assert(m_state_on);
+	
+	if (!(m_compressed_map_flags & mIN_SYNC_WITH_PARK) && 
+		!(m_compressed_map_flags & mIS_NEWER_THAN_PARK) && 
+		m_park_is_valid)
+	{
+		//Ryan("writing compressed map buffer\n");
+		write_compressed_map_buffer();
+	}
+	
+	Dbg_Printf( "************* Rebuilding whole park : %d\n", (int) clearPark );
+	destroy_concrete_metapieces(CParkGenerator::DESTROY_PIECES_AND_SECTORS);
+	mp_generator->GenerateCollisionInfo(false);
+
+	if (clearPark || !(m_compressed_map_flags & mIS_VALID))
+	{
+		//Ryan("clearing park\n");
+		Dbg_MsgAssert(m_floor_height_map, ("bad bad monkey!!!"));
+		
+		for (int j = 0; j < FLOOR_MAX_LENGTH; j++)
+		{
+			for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+			{
+				int x = i;
+				int z = j;
+				m_floor_height_map[x][z].mHeight = 0; //Mth::Rnd(4);
+				m_floor_height_map[x][z].mEnclosedFloor = 0;
+				m_floor_height_map[x][z].mMarkAsSlid = false;
+				m_floor_height_map[i][j].mSlideAmount = 0; 
+			}
+		}
+		
+		write_compressed_map_buffer();
+		SetParkName("unnamed park");
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mNOT_SAVED_LOCAL);
+		clearPark = true;
+	}	
+	else
+	{
+		// use compressed map
+		read_from_compressed_map_buffer();
+	}
+	
+	RebuildInnerShell(m_park_near_bounds);
+	generate_floor_pieces_from_height_map(clearPark, false);	
+	
+	mp_generator->GenerateCollisionInfo();
+//	mp_generator->GenerateNodeInfo(mp_concrete_metapiece_list);
+
+	m_park_is_valid = true;
+
+	if (clearPark)
+	{
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mIN_SYNC_WITH_PARK);
+	}
+	
+	// K: Now that the park geometry has been built and collision info generated, 
+	// update the created rails.
+	Obj::GetRailEditor()->RefreshGeometry();
+}
+
+
+
+
+/*
+	-Might clear map
+	-Generates riser geometry (from height map)
+
+	The only place we call this function with firstTestMemory true is from CCursor::ChangeFloorHeight()
+*/
+bool CParkManager::RebuildFloor(bool firstTestMemory)
+{
+	ParkEd("CParkManager::RebuildFloor()");
+	
+	Dbg_Assert(m_state_on);
+	
+	//Dbg_Printf( "************* Rebuilding floor\n" );
+
+	if (firstTestMemory)
+	{
+		if (!generate_floor_pieces_from_height_map(false, true))
+			return false;
+	}
+
+	generate_floor_pieces_from_height_map(false, false);
+	
+	//CConcreteMetaPiece *p_out = CreateConcreteMeta(GetAbstractMeta(Script::GenerateCRC("gloopy")));
+	//GridDims dims(m_park_near_bounds.GetX(), 0, m_park_near_bounds.GetZ());
+	//AddMetaPieceToPark(dims, p_out);
+	
+	mp_generator->GenerateCollisionInfo();
+
+	return true;
+	
+}
+
+
+void CParkManager::RebuildNodeArray()
+{
+	ParkEd("CParkManager::RebuildNodeArray()");
+
+
+	// clear any pre-exisiting automatic restarts
+	// so we have slots free to add new ones	
+	mp_generator->ClearAutomaticRestarts();
+
+ 
+	// add default restarts
+	// (they won't be added if player-placed ones already exist)
+	// there are 18 points
+	// 1p, 2p, 4 flags, 6 horse, 6 KOTH
+	for (int p = 0; p < 18; p++)
+	{
+		CParkGenerator::RestartType restart_type = CParkGenerator::vONE_PLAYER;
+		
+		GridDims restart_area;
+		restart_area = GetParkNearBounds();		 // returns the rectangle enclosed by the fence
+		restart_area.SetXYZ(restart_area.GetX()+restart_area.GetW()/2, 0, restart_area.GetZ()+restart_area.GetL()/2);	// sets xyz to the midpoint
+		restart_area.SetWHL(1,1,1);		// needed for when we get the floor height
+						
+
+		bool	find_ground = true;			// most restarts need to find the ground, except copied horse points
+						
+		int need = 1;		// Most restarts only need one.  Horse/Multi being the current exception						
+		if (p == 1)
+		{
+			restart_type = CParkGenerator::vMULTIPLAYER;
+			restart_area.SetXYZ(restart_area.GetX()+1, 0, restart_area.GetZ());	
+		}
+
+		if (p == 2)
+		{
+			restart_type = CParkGenerator::vRED_FLAG;
+			restart_area.SetXYZ(restart_area.GetX()-2, 0, restart_area.GetZ()+2);	
+		}
+
+		if (p == 3)
+		{
+			restart_type = CParkGenerator::vGREEN_FLAG;
+			restart_area.SetXYZ(restart_area.GetX()+0, 0, restart_area.GetZ()+2);	
+		}
+
+		if (p == 4)
+		{
+			restart_type = CParkGenerator::vBLUE_FLAG;
+			restart_area.SetXYZ(restart_area.GetX()+2, 0, restart_area.GetZ()+2);	
+		}
+
+		if (p == 5)
+		{
+			restart_type = CParkGenerator::vYELLOW_FLAG;
+			restart_area.SetXYZ(restart_area.GetX()+4, 0, restart_area.GetZ()+2);	
+		}
+
+		// all the dummy horse/multi restart points go in a line at Z-2		
+		if (p >= 6 && p <=11)				 	// 6,7,8,9,10,11
+		{
+			need = 6;			
+			restart_type = CParkGenerator::vHORSE;
+			
+			#if 0
+			// Code to make horse points be evenly distributed between 1p/2p nodes
+			// but it's not usable, as the players will start at the same point
+			// either all multi points need to be different, of the code needs
+			// to check in 2P (and multi) for same node positions
+			// either are too risky to implement right now 			
+			static GridDims horse_points[6];    // needs to be static, so I can include it in this conditional
+			GridDims  dims;
+			if (p==6)
+			{
+				// about to try the first horse point
+				// so fill the array with appropriate values
+				int num_actual_horse = mp_generator->NumRestartsOfType(restart_type);
+//				printf ("There are %d actual horse points\n",num_actual_horse);
+				if (num_actual_horse == 0)
+				{
+//					printf ("No horse points, using 1p/2p\n");
+					// not even got one, so fill the array with 1p,2p,1p,2p,1p,2p
+					for (int i = 0;i<6;i+=2)
+					{
+						dims = mp_generator->GetRestartDims(CParkGenerator::vONE_PLAYER,0);
+						horse_points[i] = restart_area;
+						horse_points[i].SetXYZ(dims.GetX(),0,dims.GetZ());
+						dims = mp_generator->GetRestartDims(CParkGenerator::vMULTIPLAYER,0);
+						horse_points[i+1] = restart_area;
+						horse_points[i+1].SetXYZ(dims.GetX(),0,dims.GetZ());
+					}					 
+				}
+				else
+				{
+					// we have one or more real horse points, so find out where they are
+					// and copy those values for any automatic horse points, if needed
+					for (int i = 0;i<6;i++)
+					{
+						if (i < num_actual_horse)
+						{
+							// copy the n initial values into the array
+							horse_points[i] = mp_generator->GetRestartDims(CParkGenerator::vHORSE,i);
+						}
+						else
+						{
+							// and duplicate these through the rest of the array
+							//  so we will get an even distribution
+							//  n = 1  gives [ 1 1 1 1 1 1 ]
+							//  n = 2  gives [ 1 2 1 2 1 2 ]
+							//  n = 3  gives [ 1 2 3 1 2 3 ]
+							//  n = 4  gives [ 1 2 3 4 1 2 ]
+							//  n = 5  gives [ 1 2 3 4 5 1 ]
+							//  n = 6  gives [ 1 2 3 4 5 6 ]
+							horse_points[i] = horse_points[i % num_actual_horse];
+						}
+//						printf ("Horse %d: (%d,%d,%d)\n",horse_points[i].GetX(),horse_points[i].GetY(),horse_points[i].GetX());
+					}					 					
+				}
+			}
+			
+			// copy the appropiate value from the array
+			restart_area = horse_points[p-6];
+			find_ground = false;			// Don't find the ground when copying existing restarts, as we will be raised up by 1p/2p/horse pieces
+			//printf ("Using Restart Area(%d,%d,%d)\n",restart_area.GetX(), restart_area.GetY(), restart_area.GetZ());
+
+															 
+			#else			
+			restart_area.SetXYZ(restart_area.GetX()-3 + (p-6), 0, restart_area.GetZ()-2);	
+			//printf ("Using Restart Area(%d,%d,%d)\n",restart_area.GetX(), restart_area.GetY(), restart_area.GetZ());
+			#endif
+		
+		}
+		
+		bool	auto_copy = false;
+		
+		if ( p >= 12 && p <= 17 )  					// 12,13,14,15,16,17
+		{
+			auto_copy = true;						// copy any existing restart point position, if there is one
+			need = 6;
+			restart_type = CParkGenerator::vKING_OF_HILL;
+			restart_area.SetXYZ(restart_area.GetX()-3 + (p-12), 0, restart_area.GetZ()-3);	 
+		}
+
+		if (mp_generator->NotEnoughRestartsOfType(restart_type, need))
+		{
+			if (find_ground)
+			{
+				restart_area.SetXYZ(restart_area.GetX(),GetFloorHeight(restart_area),restart_area.GetZ());
+				CMapListTemp meta_list = GetMetaPiecesAt(restart_area);
+				if (!meta_list.IsEmpty())
+				{
+					restart_area[Y] = restart_area.GetY() + meta_list.GetList()->GetConcreteMeta()->GetArea().GetH();
+				}
+			}
+
+			Mth::Vector restart_pos = GridCoordinatesToWorld(restart_area);
+			restart_pos[X] = restart_pos[X] + CParkGenerator::CELL_WIDTH / 2.0f;
+			restart_pos[Z] = restart_pos[Z] + CParkGenerator::CELL_LENGTH / 2.0f;
+			
+			// Fix to TT12221, where the flags would be floating if over a pool.
+			CFeeler feeler;
+			feeler.SetStart(restart_pos+Mth::Vector(0.0f, 10000.0f, 0.0f));
+			feeler.SetEnd(restart_pos+Mth::Vector(0.0f, -1000.0f, 0.0f));
+			if (feeler.GetCollision())
+			{
+				restart_pos[Y]=feeler.GetPoint().GetY();
+			}	
+
+//			printf("restart point %d at: cell=(%d,%d,%d) world=(%.4f,%.4f,%.4f)\n", p, 
+//				 restart_area.GetX(), restart_area.GetY(), restart_area.GetZ(),
+//				 restart_pos[X], restart_pos[Y], restart_pos[Z]);
+
+			mp_generator->AddRestart(restart_pos, 0, restart_area, restart_type, true, auto_copy);
+		}
+	} // end for p
+
+	mp_generator->GenerateNodeInfo(mp_concrete_metapiece_list);	
+}
+
+
+uint32 CParkManager::GetShellSceneID()
+{
+	char p_shell_name[40];
+	if (GetTheme())
+	{
+		sprintf(p_shell_name,"sk5ed%d_shell",GetTheme()+1);
+	}
+	else
+	{
+		strcpy(p_shell_name,"sk5ed_shell");
+	}
+	return Script::GenerateCRC(p_shell_name);
+}
+
+void CParkManager::RebuildInnerShell(GridDims newNearBounds)
+{
+	ParkEd("CParkManager::RebuildInnerShell()");
+	
+	Dbg_Printf( "************* Rebuilding inner shell\n" );
+
+	#if 1
+	ParkEd("old shell bounds: (%d,%d,%d)-(%d,%d,%d)\n", 
+		 m_park_near_bounds.GetX(), m_park_near_bounds.GetY(), m_park_near_bounds.GetZ(),
+		 m_park_near_bounds.GetW(), m_park_near_bounds.GetH(), m_park_near_bounds.GetL());
+	#endif
+	m_park_near_bounds = newNearBounds;
+	#if 1
+	ParkEd("new shell bounds: (%d,%d,%d)-(%d,%d,%d)\n", 
+		 newNearBounds.GetX(), newNearBounds.GetY(), newNearBounds.GetZ(),
+		 newNearBounds.GetW(), newNearBounds.GetH(), newNearBounds.GetL());
+	#endif
+	
+	// west
+	m_road_mask_tab[0][X] = m_park_near_bounds.GetX() - 1;
+	m_road_mask_tab[0][Z] = m_park_near_bounds.GetZ() - 1;
+	m_road_mask_tab[0][W] = 1;
+	m_road_mask_tab[0][L] = m_park_near_bounds.GetL() + 2;
+	
+	// east
+	m_road_mask_tab[1][X] = m_park_near_bounds.GetX() + m_park_near_bounds.GetW();
+	m_road_mask_tab[1][Z] = m_park_near_bounds.GetZ() - 1;
+	m_road_mask_tab[1][W] = 1;
+	m_road_mask_tab[1][L] = m_park_near_bounds.GetL() + 2;
+	
+	// north
+	m_road_mask_tab[2][X] = m_park_near_bounds.GetX() - 1;
+	m_road_mask_tab[2][Z] = m_park_near_bounds.GetZ() - 1;
+	m_road_mask_tab[2][W] = m_park_near_bounds.GetW() + 2;
+	m_road_mask_tab[2][L] = 1;
+	
+	// south
+	m_road_mask_tab[3][X] = m_park_near_bounds.GetX() - 1;
+	m_road_mask_tab[3][Z] = m_park_near_bounds.GetZ() + m_park_near_bounds.GetL();
+	m_road_mask_tab[3][W] = m_park_near_bounds.GetW() + 2;
+	m_road_mask_tab[3][L] = 1;
+	
+	//#if DEBUG_THIS_DAMN_THING
+	//int destroy_type2_count = 0;
+	//#endif
+		
+	// destroy all existing inner shell pieces
+	// also: kill everything that's outside the near bounds
+	CMapListNode *p_node = mp_concrete_metapiece_list;
+	while(p_node)
+	{
+		CMapListNode *p_next_node = p_node->GetNext();
+		GridDims meta_area = p_node->GetConcreteMeta()->GetArea();
+		
+		if (p_node->GetConcreteMeta()->IsInnerShell())
+		{
+			DestroyConcreteMeta(p_node->GetConcreteMeta(), mDONT_DESTROY_PIECES_ABOVE);
+		}
+		#if 1
+		else if ( !p_node->GetConcreteMeta()->IsRiser() &&  
+			p_node->GetConcreteMeta()->IsInPark() &&
+			(meta_area.GetX() < m_park_near_bounds.GetX() ||
+			meta_area.GetZ() < m_park_near_bounds.GetZ() ||
+			meta_area.GetX() + meta_area.GetW() > m_park_near_bounds.GetX() + m_park_near_bounds.GetW() ||
+			meta_area.GetZ() + meta_area.GetL() > m_park_near_bounds.GetZ() + m_park_near_bounds.GetL()))
+		{
+			DestroyConcreteMeta(p_node->GetConcreteMeta(), mDONT_DESTROY_PIECES_ABOVE);
+			//#if DEBUG_THIS_DAMN_THING
+			//destroy_type2_count++;
+			//#endif
+		}
+		#endif
+
+		p_node = p_next_node;
+	}
+	
+	uint32 fence_meta_name_crc = Script::GenerateCRC("Sk4Ed_Fence_20x20");
+	CAbstractMetaPiece *p_abstract_fence_meta = GetAbstractMeta(fence_meta_name_crc);
+	
+	for (int x = m_park_near_bounds.GetX(); x < m_park_near_bounds.GetX() + m_park_near_bounds.GetW(); x += 2)
+	{
+		GridDims road_test_area;
+		road_test_area.SetWHL(2, 1, 1);
+
+		// north
+		GridDims pos(x, 0, m_park_near_bounds.GetZ() - 1);
+		road_test_area.SetXYZ(pos.GetX(), 0, pos.GetZ() + 1);
+		if (!IsOccupiedByRoad(road_test_area))
+		{
+			CConcreteMetaPiece *p_meta = CreateConcreteMeta(p_abstract_fence_meta);
+			p_meta->SetRot(Mth::ROT_0);
+			p_meta->MarkAsInnerShell();
+			AddMetaPieceToPark(pos, p_meta);
+		}
+	
+		// south
+		pos.SetXYZ(x, 0, m_park_near_bounds.GetZ() + m_park_near_bounds.GetL());
+		road_test_area.SetXYZ(pos.GetX(), 0, pos.GetZ() - 1);
+		if (!IsOccupiedByRoad(road_test_area))
+		{
+			CConcreteMetaPiece *p_meta = CreateConcreteMeta(p_abstract_fence_meta);
+			p_meta->SetRot(Mth::ROT_180);
+			p_meta->MarkAsInnerShell();
+			AddMetaPieceToPark(pos, p_meta);
+		}
+	}
+
+	for (int z = m_park_near_bounds.GetZ(); z < m_park_near_bounds.GetZ() + m_park_near_bounds.GetL(); z += 2)
+	{
+		GridDims road_test_area;
+		road_test_area.SetWHL(1, 1, 2);
+
+		// west
+		GridDims pos(m_park_near_bounds.GetX() - 1, 0, z);
+		road_test_area.SetXYZ(pos.GetX() + 1, 0, pos.GetZ());
+		if (!IsOccupiedByRoad(road_test_area))
+		{
+			CConcreteMetaPiece *p_meta = CreateConcreteMeta(p_abstract_fence_meta);
+			p_meta->SetRot(Mth::ROT_90);
+			p_meta->MarkAsInnerShell();
+			AddMetaPieceToPark(pos, p_meta);
+		}
+	
+		// east
+		pos.SetXYZ(m_park_near_bounds.GetX() + m_park_near_bounds.GetW(), 0, z);
+		road_test_area.SetXYZ(pos.GetX() - 1, 0, pos.GetZ());
+		if (!IsOccupiedByRoad(road_test_area))
+		{
+			CConcreteMetaPiece *p_meta = CreateConcreteMeta(p_abstract_fence_meta);
+			p_meta->SetRot(Mth::ROT_270);
+			p_meta->MarkAsInnerShell();
+			AddMetaPieceToPark(pos, p_meta);
+		}
+	}
+
+	//ParkEd("destroyed %d non-riser pieces\n", destroy_type2_count); 
+
+	for (int x = 0; x < MAX_WIDTH; x++)
+	{
+		for (int z = 0; z < MAX_LENGTH; z++)
+		{
+			if (x < m_park_near_bounds.GetX() ||
+				z < m_park_near_bounds.GetZ() ||
+				x >= m_park_near_bounds.GetX() + m_park_near_bounds.GetW() ||
+				z >= m_park_near_bounds.GetZ() + m_park_near_bounds.GetL())
+			{
+				m_floor_height_map[x][z].mHeight = 0;
+			}
+		}
+	}
+
+	mp_generator->RemoveOuterShellPieces(GetTheme());
+	mp_generator->GenerateCollisionInfo();
+
+//	mp_generator->GenerateNodeInfo(mp_concrete_metapiece_list);
+	ParkEd("end RebuildInnerShell()\n");
+}
+
+
+
+
+/*
+	Retrieves the abstract meta with the specified name checksum. Returns its index.
+*/
+CAbstractMetaPiece *CParkManager::GetAbstractMeta(uint32 type, int *pRetIndex)
+{
+	Dbg_Assert(m_state_on);
+	
+	CMapListNode *p_node = mp_abstract_metapiece_list;
+	int index = m_num_abstract_metapieces-1;
+	while(p_node)
+	{
+		CAbstractMetaPiece *p_meta = p_node->GetAbstractMeta();
+		Dbg_Assert(p_meta);
+		if (p_meta->m_name_checksum == type)
+		{
+			if (pRetIndex)
+				*pRetIndex = index;
+			return p_meta;
+		}
+
+		index--;
+		p_node = p_node->GetNext();
+	}
+
+	return NULL;
+}
+
+
+
+
+/*
+	Retrieves the abstract meta with the specified index.
+*/
+CAbstractMetaPiece *CParkManager::GetAbstractMetaByIndex(int index)
+{
+	Dbg_Assert(m_state_on);
+	
+	CMapListNode *p_node = mp_abstract_metapiece_list;
+	int i = m_num_abstract_metapieces-1;
+	while(p_node)
+	{
+		CAbstractMetaPiece *p_meta = p_node->GetAbstractMeta();
+		Dbg_Assert(p_meta);
+		if (i == index)
+			return p_meta;
+
+		i--;
+		p_node = p_node->GetNext();
+	}
+
+	Dbg_MsgAssert(0, ("index %d out of range", i));
+	return NULL;	
+}
+
+
+
+
+/*
+	Creates a concrete copy of the specified abstract meta.
+*/
+CConcreteMetaPiece *CParkManager::CreateConcreteMeta(CAbstractMetaPiece *pSource, bool isSoft)
+{
+	Dbg_Assert(m_state_on);
+	Dbg_Assert(pSource);
+	
+	Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+	CConcreteMetaPiece *p_meta = new CConcreteMetaPiece();
+	Mem::Manager::sHandle().PopContext();
+	
+	int num_entries = pSource->count_descriptors_expanded();
+	p_meta->initialize_desc_table(num_entries);
+	p_meta->m_name_checksum = pSource->m_name_checksum;
+	
+	CPiece::EFlags clone_flags = (isSoft) ? CPiece::mSOFT_PIECE : CPiece::mNO_FLAGS;
+	
+	if (pSource->IsRiser())
+	{
+		p_meta->set_flag(CMetaPiece::mIS_RISER);
+		clone_flags	= CPiece::EFlags(clone_flags | CPiece::mFLOOR);
+	}
+	if (pSource->NoRails())
+	{
+		p_meta->set_flag(CMetaPiece::mNO_RAILS);
+		clone_flags	= CPiece::EFlags(clone_flags | CPiece::mNO_RAILS);
+	}
+	if (pSource->m_flags & CMetaPiece::mCONTAINS_RISERS)
+	{
+		p_meta->set_flag(CMetaPiece::mCONTAINS_RISERS);
+	}
+
+	// pick through source piece, for each descriptor entry, make new cloned piece
+	for (int i = 0; i < num_entries; i++)
+	{
+		SMetaDescriptor desc = pSource->get_desc_with_expansion(i);
+		
+		CClonedPiece *p_piece = 
+			mp_generator->ClonePiece(CParkManager::sInstance()->GetGenerator()->GetMasterPiece(desc.mPieceName), clone_flags);
+		p_piece->SetDesiredRot(Mth::ERot90(desc.mRot), CClonedPiece::MARKER_ONLY);
+		GridDims dims(desc.mX, desc.mY, desc.mZ);
+		p_meta->add_piece(p_piece, &dims, Mth::ERot90(desc.mRot), (desc.mRiser != 0));
+
+		#if 0
+		// Ryan: this call is unnecessary
+		if (!isSoft)
+		{
+			mp_generator->CalculateLighting(p_piece);
+		}
+		#endif
+
+		#if 0
+		if (pSource->GetNameChecksum() == Script::GenerateCRC("Low_Short_Deck"))
+		{
+			printf("   rotation: %d\n", desc.mRot);
+		}
+		#endif
+	}
+
+	p_meta->lock();
+	   
+	#if 1
+	#ifdef __NOPT_ASSERT__
+	GridDims test_area = p_meta->GetArea();
+	Dbg_MsgAssert(test_area.GetW() > 0 && test_area.GetH() > 0 && test_area.GetL() > 0, 
+				  ("metapiece %s has invalid dimensions (%d,%d,%d)", Script::FindChecksumName(p_meta->GetNameChecksum()),
+				   test_area.GetW(), test_area.GetH(), test_area.GetL()));
+	
+	#if 0
+	if (1 || p_meta->GetNameChecksum() == Script::GenerateCRC("Sk4Ed_Fence_10x10"))
+		printf("OY! metapiece %s has dimensions (%d,%d,%d)\n", Script::FindChecksumName(p_meta->GetNameChecksum()),
+				   test_area.GetW(), test_area.GetH(), test_area.GetL());
+	#endif
+
+	#endif
+	#endif
+	
+	Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+	CMapListNode *p_node = new CMapListNode();
+	Mem::Manager::sHandle().PopContext();
+	p_node->mp_meta = p_meta;
+
+	#if 0	
+	p_node->mp_next = mp_concrete_metapiece_list;
+	mp_concrete_metapiece_list = p_node;
+	#else
+	p_node->mp_next = NULL;		
+	if (!mp_concrete_metapiece_list)
+	{
+		mp_concrete_metapiece_list = p_node;
+	}
+	else
+	{
+		CMapListNode *p_add = mp_concrete_metapiece_list;
+		while( p_add->mp_next)
+		{
+			p_add = p_add->mp_next;
+		}
+		p_add->mp_next = p_node;
+	}
+	#endif
+	
+	m_num_concrete_metapieces++;
+
+	return p_meta;
+}
+
+
+
+
+void CParkManager::DestroyConcreteMeta(CConcreteMetaPiece *pMeta, EDestroyFlags flags)
+{
+	Dbg_Assert(m_state_on);
+	if (pMeta->m_flags & CMetaPiece::mLOCK_AGAINST_DELETE) return;
+	Dbg_MsgAssert(!(pMeta->m_flags & CMetaPiece::mLOCK_AGAINST_DELETE), ("Metapiece is locked, can't deleted"));
+
+	if (CCursor::sInstance(false))
+		CCursor::sInstance()->InformOfMetapieceDeletion(pMeta);
+	
+	// remove any gaps attached to this piece
+	RemoveGap(pMeta);
+
+	if (!pMeta->IsInnerShell() && pMeta->IsInPark())
+	{
+		decrement_meta_count(pMeta->GetNameChecksum());
+		if (!pMeta->IsRiser())
+		{
+			m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~(mIN_SYNC_WITH_PARK | mIS_NEWER_THAN_PARK));
+		}	
+	}
+	
+	// destroy all metas that are ABOVE this one
+	if (!pMeta->IsRiser() && (flags & mDESTROY_PIECES_ABOVE))
+	{
+		GridDims test_area = pMeta->GetArea();
+		test_area[Y] = test_area.GetY() + test_area.GetH();
+		test_area.MakeInfinitelyHigh();
+		// XXX
+		Ryan("die now: ");
+		//test_area.PrintContents();
+		DestroyMetasInArea(test_area, EDestroyFlags(mEXCLUDE_RISERS	| mDESTROY_PIECES_ABOVE));
+	}
+	
+	apply_meta_contained_risers_to_floor(pMeta, false);
+	
+	debucketify_metapiece(pMeta);
+	remove_metapiece_from_node_list(pMeta, &mp_concrete_metapiece_list);
+	
+	#if 0
+	printf("concrete metapiece %s destroyed: cell dims (%d,%d,%d)-(%d,%d,%d), flags=0x%x\n",
+		   Script::FindChecksumName(pMeta->GetNameChecksum()),
+		   pMeta->m_cell_area.GetX(), pMeta->m_cell_area.GetY(), pMeta->m_cell_area.GetZ(), 
+		   pMeta->m_cell_area.GetW(), pMeta->m_cell_area.GetH(), pMeta->m_cell_area.GetL(),
+		   pMeta->m_flags);
+	#endif
+
+										 
+	CParkGenerator::RestartType restart_type = IsRestartPiece(pMeta->GetNameChecksum());		
+	if (restart_type != CParkGenerator::vEMPTY)
+	{
+		mp_generator->RemoveRestart(pMeta->GetArea(),restart_type);
+	}
+	
+	// take out metapiece
+	for (uint i = 0; i < pMeta->m_num_used_entries; i++)
+	{
+		CClonedPiece *p_piece = pMeta->get_desc_at_index(i).mpPiece->CastToCClonedPiece();
+		Dbg_Assert(p_piece);
+		mp_generator->DestroyClonedPiece(p_piece);
+	}
+	
+	delete pMeta;
+	m_num_concrete_metapieces--;
+}
+
+
+void CParkManager::HighlightMetasInArea(GridDims area, bool on)
+{
+	CMapListTemp list = GetAreaMetaPieces(area);
+	CMapListNode *p_entry = list.GetList();
+
+	while(p_entry)
+	{
+		CMetaPiece *p_meta=p_entry->GetMeta();
+		if (p_meta)
+		{
+			if (p_meta->IsConcrete() && p_meta->IsCompletelyWithinArea(area))
+			{
+				p_entry->GetConcreteMeta()->Highlight(on);
+			}	
+		}	
+		p_entry = p_entry->GetNext();
+	}
+}
+
+/*
+GridDims CParkManager::FindBoundingArea(GridDims area)
+{
+	int min_x=1000000;	
+	int min_z=1000000;	
+	int max_x=-1000000;
+	int max_z=-1000000;
+	
+	CMapListTemp list = GetAreaMetaPieces(area);
+	CMapListNode *p_entry = list.GetList();
+
+	while(p_entry)
+	{
+		CMetaPiece *p_meta=p_entry->GetMeta();
+		if (p_meta && p_meta->IsConcrete())
+		{
+			GridDims meta_area = p_entry->GetConcreteMeta()->GetArea();
+			int x1=meta_area.GetX();
+			int z1=meta_area.GetZ();
+			int x2=x1+meta_area.GetW()-1;
+			int z2=z1+meta_area.GetL()-1;
+			
+			if (x1 < min_x) min_x=x1;
+			if (x2 > max_x) max_x=x2;
+			if (z1 < min_z) min_z=z1;
+			if (z2 > max_z) max_z=z2;
+		}	
+		p_entry = p_entry->GetNext();
+	}
+	
+	GridDims bounding_area=area;
+	if (min_x <= max_x && min_z <= max_z)
+	{
+		bounding_area.SetXYZ(min_x, 0, min_z);
+		bounding_area.SetWHL(max_x-min_x+1, 1, max_z-min_z+1);
+	}
+	return bounding_area;
+}
+*/
+
+// K: Does the same as CCursor::AttemptRemove() but for any area
+bool CParkManager::DestroyMetasInArea(GridDims area, bool doNotDestroyRestartsOrFlags, bool onlyIfWithinArea)
+{
+	bool destroyed_something=false;
+	CMapListTemp metas_at_pos = GetAreaMetaPieces(area);
+	CMapListNode *p_entry = metas_at_pos.GetList();
+	while (p_entry)
+	{
+		CConcreteMetaPiece *p_meta = p_entry->GetConcreteMeta();
+		if (p_meta->IsRiser() || 
+			(doNotDestroyRestartsOrFlags && p_meta->IsRestartOrFlag()) ||
+			(onlyIfWithinArea && !p_meta->IsCompletelyWithinArea(area)))
+		{
+		}
+		else
+		{
+			DestroyConcreteMeta(p_meta, CParkManager::mDESTROY_PIECES_ABOVE);
+			destroyed_something=true;
+		}	
+		
+		p_entry = p_entry->GetNext();
+	}
+
+	// Also destroy any rail points in the area.
+	Mth::Vector corner_pos;
+	Mth::Vector area_dims;
+	corner_pos=Ed::CParkManager::Instance()->GridCoordinatesToWorld(area,&area_dims);
+	Obj::GetRailEditor()->ClipRails(corner_pos[X], corner_pos[Z],
+									corner_pos[X]+area_dims[X], corner_pos[Z]+area_dims[Z],
+									Obj::CRailEditorComponent::DELETE_POINTS_INSIDE);
+	
+	if (destroyed_something)
+	{
+		RebuildFloor();
+		return true;
+	}
+		
+	return false;
+}
+
+
+/*
+	Destroys concrete metapieces in specified area, according to flags.
+	
+	Flags:
+	-mDESTROY_PIECES_ABOVE: if set, whenever a meta is destroyed, recursively
+		destroy all metas above it
+		
+	Return value:
+	upper 16 bits: number metas killed
+	lower 16 bits: number pieces killed
+*/
+uint32 CParkManager::DestroyMetasInArea(GridDims area, EDestroyFlags flags)
+{
+	uint16 metas_killed = 0;
+	uint16 pieces_killed = 0;
+	CMapListNode *p_list = GetMetaPiecesAt(area, Mth::ROT_0, NULL, (flags & mEXCLUDE_RISERS));
+	CMapListNode *p_node = p_list;
+	while (p_node)
+	{
+		if (!(flags & mEXCLUDE_PIECES_MARKED_AS_SLID) || !(p_node->GetConcreteMeta()->m_flags & CMetaPiece::mMARK_AS_SLID))
+		{
+			metas_killed++;
+			pieces_killed += p_node->GetConcreteMeta()->m_num_used_entries;
+			DestroyConcreteMeta(p_node->GetConcreteMeta(), flags);
+		}
+		p_node = p_node->GetNext();
+	}
+	p_list->DestroyList();
+
+	return ((metas_killed<<16) | pieces_killed);
+}
+
+int CParkManager::get_dma_usage(uint32 pieceChecksum)
+{
+	Script::CStruct *p_dma_usage=Script::GetStructure(CRCD(0x9547da8d,"DMA_Usage"),Script::ASSERT);
+	int usage=1;
+	p_dma_usage->GetInteger(pieceChecksum, &usage);
+	return usage;
+}	
+
+void CParkManager::increment_meta_count(uint32 pieceChecksum)
+{
+	++m_num_general_concrete_metapieces;
+	m_dma_piece_count += get_dma_usage(pieceChecksum);
+	
+	//printf("IncrementMetaCount '%s', %d   DMA=%d\n",Script::FindChecksumName(pieceChecksum),m_num_general_concrete_metapieces,m_dma_piece_count);
+}
+
+void CParkManager::decrement_meta_count(uint32 pieceChecksum)
+{
+	--m_num_general_concrete_metapieces;
+	m_dma_piece_count -= get_dma_usage(pieceChecksum);
+	
+	//printf("DecrementMetaCount '%s' %d   DMA=%d\n",Script::FindChecksumName(pieceChecksum),m_num_general_concrete_metapieces,m_dma_piece_count);
+	Dbg_MsgAssert(m_dma_piece_count >= 0,("Negative m_dma_piece_count of %d\n",m_dma_piece_count));
+}
+
+/*
+	Position is in cell coordinates, relative to northwest corner of 256X256 grid.
+	Y coordinate is relative to "ground level", can be + or -.
+
+	Rotation is 0-3.
+*/
+void CParkManager::AddMetaPieceToPark(GridDims pos, CConcreteMetaPiece *pMetaPiece)
+{
+	Dbg_Assert(m_state_on);
+	
+	Dbg_MsgAssert(!(pMetaPiece->m_flags & CMetaPiece::mIN_PARK), ("metapiece already in park"));
+	pMetaPiece->set_flag(CMetaPiece::mIN_PARK);
+
+	pMetaPiece->m_cell_area.SetXYZ(pos.GetX(), pos.GetY(), pos.GetZ());
+
+	// center_pos in world coordinates
+	Mth::Vector center_pos;
+	center_pos[X] = ((float) pos.GetX() + (float) pMetaPiece->m_cell_area.GetW() / 2.0f) * CParkGenerator::CELL_WIDTH;
+	center_pos[X] -= (CParkGenerator::CELL_WIDTH * (float) CParkManager::FLOOR_MAX_WIDTH) / 2.0f;
+	center_pos[Z] = ((float) pos.GetZ() + (float) pMetaPiece->m_cell_area.GetL() / 2.0f) * CParkGenerator::CELL_LENGTH;
+	center_pos[Z] -= (CParkGenerator::CELL_LENGTH * (float) CParkManager::FLOOR_MAX_LENGTH) / 2.0f;
+	center_pos[Y] = pos.GetY() * CParkGenerator::CELL_HEIGHT;
+
+	// XXX
+	#if 0
+	Dbg_Printf("adding piece to park: (%d,%d,%d), (%.2f,%.2f,%.2f)\n", 
+		 pos.GetX(), pos.GetY(), pos.GetZ(), 
+		 center_pos[X], center_pos[Y], center_pos[Z]);
+	#endif
+	
+	for (uint i = 0; i < pMetaPiece->m_num_used_entries; i++)
+	{
+		Mth::Vector rel_pos = pMetaPiece->GetRelativePosOfContainedPiece(i);
+		CClonedPiece *p_piece = pMetaPiece->GetContainedPiece(i)->CastToCClonedPiece();
+		
+		// reposition piece from metapiece-relative coordinates to world-relative
+		Mth::Vector abs_pos;
+		abs_pos[X] = center_pos[X] + rel_pos[X]; 
+		abs_pos[Z] = center_pos[Z] + rel_pos[Z]; 
+		abs_pos[Y] = center_pos[Y] + rel_pos[Y];
+		
+		p_piece->SetDesiredPos(abs_pos, CClonedPiece::CHANGE_SECTOR);
+		p_piece->SetDesiredRot(Mth::ERot90((p_piece->GetRot() + pMetaPiece->m_rot) & 3), CClonedPiece::CHANGE_SECTOR);
+		mp_generator->AddClonedPieceToWorld(p_piece);
+		mp_generator->CalculateLighting(p_piece);
+	}
+
+	apply_meta_contained_risers_to_floor(pMetaPiece, true);		
+	bucketify_metapiece(pMetaPiece);
+
+	// If it's a restart point, then add that to the list of restart points
+	CParkGenerator::RestartType restart_type = IsRestartPiece(pMetaPiece->GetNameChecksum());		
+	if (restart_type != CParkGenerator::vEMPTY)
+	{
+		mp_generator->AddRestart(center_pos,pMetaPiece->m_rot,pos,restart_type,false);
+	}
+
+	if (!pMetaPiece->IsInnerShell())
+	{
+		if (!pMetaPiece->IsRiser())
+		{
+			m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~(mIN_SYNC_WITH_PARK | mIS_NEWER_THAN_PARK));
+		}
+		increment_meta_count(pMetaPiece->GetNameChecksum());
+	}
+	
+}
+
+
+
+
+/*
+	"Moves" a metapiece from one position to another. Really, the old metapiece is destroyed, and a new copy created,
+	so be careful to replace any pointers to old one.
+*/
+CConcreteMetaPiece *CParkManager::RelocateMetaPiece(CConcreteMetaPiece *pMeta, int changeX, int changeY, int changeZ)
+{
+	GridDims area = pMeta->GetArea();
+	uint32 name = pMeta->GetNameChecksum();
+	Mth::ERot90 rot = pMeta->GetRot();
+	CMetaPiece::EFlags old_flags = pMeta->m_flags;
+
+	// get gap associated with metapiece
+	int gap_half;
+	bool gap_exists = false;
+	CGapManager::GapDescriptor *p_gap_desc = GetGapDescriptor(area, &gap_half);
+	CGapManager::GapDescriptor new_gap_desc;
+	if (p_gap_desc)
+	{
+		gap_exists = true;
+		new_gap_desc = *p_gap_desc;
+		
+		// remove it
+		RemoveGap(*p_gap_desc);
+
+		// shift gap descriptor to new position
+		new_gap_desc.loc[gap_half][X] = new_gap_desc.loc[gap_half].GetX() + changeX;
+		new_gap_desc.loc[gap_half][Y] = new_gap_desc.loc[gap_half].GetY() + changeY;
+		new_gap_desc.loc[gap_half][Z] = new_gap_desc.loc[gap_half].GetZ() + changeZ;
+	}
+	
+	DestroyConcreteMeta(pMeta, mDONT_DESTROY_PIECES_ABOVE);
+	
+	area[X] += changeX;
+	area[Y] = area.GetY() + changeY;
+	area[Z] += changeZ;
+
+	CConcreteMetaPiece *p_new_meta = CreateConcreteMeta(GetAbstractMeta(name), false);
+	p_new_meta->SetRot(rot);
+	p_new_meta->m_flags = old_flags;
+	p_new_meta->clear_flag(CMetaPiece::mIN_PARK);
+	AddMetaPieceToPark(area, p_new_meta);
+
+	if (gap_exists)
+	{
+		AddGap(new_gap_desc);
+	}
+
+	return p_new_meta;
+}
+
+
+
+
+/*
+	Returns list of concrete metapieces in specified area.
+	
+	Modes:
+	pPiece == NULL, grab all pieces in area specified by 'dims', excluding risers and riser-containing metas if desired.
+	
+	pPiece != NULL, does pretty much the same thing right now.
+*/
+
+CMapListNode *CParkManager::GetMetaPiecesAt(GridDims dims, Mth::ERot90 rot, CAbstractMetaPiece *pPiece, bool excludeRisers)
+{
+	Dbg_Assert(m_state_on);
+	
+	if (!pPiece)
+	{
+		CMapListNode *p_out_list = NULL;
+		
+		// need to be scanning an area bigger than 0
+		//if (dims.mW == 0) dims.mW++;
+		//if (dims.mL == 0) dims.mL++;
+		
+		// no piece supplied, just scan through and grab all metapieces
+		int end_x = dims.GetX() + dims.GetW();
+		for (int x = dims.GetX(); x < end_x; x++)
+		{
+			int end_z = dims.GetZ() + dims.GetL();
+			for (int z = dims.GetZ(); z < end_z; z++)
+			{
+				CMapListNode *p_node = mp_bucket_list[x][z];
+				
+				while(p_node)
+				{
+					//Ryan("boogah %d\n", x);
+					if ((!excludeRisers || !(p_node->GetMeta()->IsRiser())) &&
+						p_node->GetConcreteMeta()->test_for_conflict(dims, excludeRisers))
+					{
+						add_metapiece_to_node_list(p_node->GetMeta(), &p_out_list);
+					}
+					
+					p_node = p_node->GetNext();
+				}
+			}
+		}
+		
+		p_out_list->VerifyIntegrity();
+		return p_out_list;
+	}
+	else
+	{
+		Dbg_MsgAssert(pPiece->GetRot() == Mth::ROT_0, ("abstract metapiece needs 0 rotation"));
+		
+		// build a descriptor table rotated and shifted to correct world coordinates
+		
+		// rotate the abstract meta piece to the desired rotation
+		pPiece->SetRot(rot);
+		dims[W] = pPiece->m_cell_area.GetW();
+		dims[H] = pPiece->m_cell_area.GetH();
+		dims[L] = pPiece->m_cell_area.GetL();
+
+		CMapListNode *p_out_list = NULL;
+		
+		// grab list of all concrete metas
+		CMapListTemp metas_in_park = GetMetaPiecesAt(dims);
+
+		// for each contained piece
+		for (uint i = 0; i < pPiece->m_num_used_entries; i++)
+		{
+			SMetaDescriptor &desc = pPiece->get_desc_at_index(i);
+			GridDims test_area(desc.mX, desc.mY, desc.mZ);
+			test_area[X] += dims.GetX();
+			test_area[Y] += dims.GetY();
+			test_area[Z] += dims.GetZ();
+
+			CMapListNode *p_in_park_entry = metas_in_park.GetList();
+			while (p_in_park_entry)
+			{
+				if (p_in_park_entry->GetConcreteMeta()->test_for_conflict(test_area, false))
+					add_metapiece_to_node_list(p_in_park_entry->GetMeta(), &p_out_list);
+
+				p_in_park_entry = p_in_park_entry->GetNext();
+			}
+		}
+		
+		// return abstract meta to a zero rotation
+		pPiece->SetRot(Mth::ROT_0);
+		p_out_list->VerifyIntegrity();
+		return p_out_list;
+	}
+	
+	return NULL;
+}
+
+// This is just a slightly faster version of GetMetaPiecesAt, which gets all
+CMapListNode *CParkManager::GetAreaMetaPieces(GridDims dims)
+{
+	Dbg_Assert(m_state_on);
+
+	CMapListNode *p_out_list = NULL;
+	
+	int end_x = dims.GetX() + dims.GetW();
+	for (int x = dims.GetX(); x < end_x; x++)
+	{
+		int end_z = dims.GetZ() + dims.GetL();
+		for (int z = dims.GetZ(); z < end_z; z++)
+		{
+			CMapListNode *p_node = mp_bucket_list[x][z];
+			while(p_node)
+			{
+				add_metapiece_to_node_list(p_node->GetMeta(), &p_out_list);
+				p_node = p_node->GetNext();
+			}
+		}
+	}
+		
+	return p_out_list;
+}
+
+// K: These two functions used by CClipboard::CopySelectionToClipboard
+CMapListNode *CParkManager::GetMapListNode(int x, int z)
+{
+	return mp_bucket_list[x][z];
+}
+
+int CParkManager::GetFloorHeight(int x, int z)
+{
+	return m_floor_height_map[x][z].mHeight;
+}
+	
+/*
+	Used to determine if resizing park will destroy existing metas.
+*/
+bool CParkManager::AreMetasOutsideBounds(GridDims new_dims)
+{
+	//Ryan("New Park Bounds are: (%d,%d,%d)-(%d,%d,%d)\n", 
+	//	 new_dims.GetX(), new_dims.GetY(), new_dims.GetZ(),
+	//	 new_dims.GetW(), new_dims.GetH(), new_dims.GetL());
+	CMapListNode *p_node = mp_concrete_metapiece_list;
+	while(p_node)
+	{
+		CConcreteMetaPiece *p_meta = p_node->GetConcreteMeta();
+		GridDims meta_area = p_meta->GetArea();
+		//Ryan("Checking if meta in bounds: is_riser=%d, inner_shell=%d (%d,%d,%d)-(%d,%d,%d)\n", 
+		//	 p_meta->IsRiser(), p_meta->IsInnerShell(), 
+		//	 meta_area.GetX(), meta_area.GetY(), meta_area.GetZ(),
+		//	 meta_area.GetW(), meta_area.GetH(), meta_area.GetL());
+		if ((meta_area.GetX() < new_dims.GetX() ||
+			meta_area.GetZ() < new_dims.GetZ() ||
+			meta_area.GetX() + meta_area.GetW() > new_dims.GetX() + new_dims.GetW() ||
+			meta_area.GetZ() + meta_area.GetL() > new_dims.GetZ() + new_dims.GetL()) &&
+			!p_meta->IsRiser() &&
+			!p_meta->IsInnerShell() &&
+			(p_meta->m_flags & CMetaPiece::mIN_PARK))
+			return true;
+		p_node = p_node->GetNext();
+	}
+
+	return false;
+}
+
+
+
+
+/*
+	Used to determine if resizing park will destroy existing metas.
+*/
+bool CParkManager::EnoughMemoryToResize(GridDims new_dims)
+{
+	int current_num_tiles = m_park_near_bounds.GetW() * m_park_near_bounds.GetL();
+	int new_num_tiles = new_dims.GetW() * new_dims.GetL();
+
+	if (new_num_tiles - current_num_tiles > GetGenerator()->GetResourceSize("max_dma_pieces") - GetDMAPieceCount())
+	{
+		return false;
+	}
+		
+	int park_add_amount = (new_num_tiles - current_num_tiles) * mp_generator->GetResourceSize("floor_piece_size_park");
+	int main_add_amount = (new_num_tiles - current_num_tiles) * mp_generator->GetResourceSize("floor_piece_size_main");
+
+	CParkGenerator::MemUsageInfo usage_info = mp_generator->GetResourceUsageInfo();
+	if (usage_info.mParkHeapFree < park_add_amount) 
+	{
+		Ryan("not enough memory for resize on park heap, need %d, have %d\n", park_add_amount, usage_info.mParkHeapFree);
+		return false;
+	}
+	if (usage_info.mMainHeapFree < main_add_amount) 
+	{
+		Ryan("not enough memory for resize on main heap, need %d, have %d\n", main_add_amount, usage_info.mMainHeapFree);
+		return false;
+	}
+	
+	#ifdef __PLAT_NGC__
+	// NGC has problems resizing if the top down heap is low due to UpdateSuperSectors requiring some temporary space.
+	int required_top_down_heap=TOP_DOWN_REQUIRED_MARGIN;
+	
+	// If increasing the size of the park, then in the worse case the TD heap may reduce by main_add_amount,
+	// if none of the fragments got used.
+	// We want there to be more than TOP_DOWN_REQUIRED_MARGIN after increasing the size, so that the user
+	// will be able to reduce the size afterwards, so add in main_add_amount to the requirement.
+	if (main_add_amount > 0)
+	{
+		required_top_down_heap += main_add_amount;
+	}	
+	
+	Mem::Heap *p_top_down_heap = Mem::Manager::sHandle().TopDownHeap();
+	// Check if there is enough TD heap at the moment
+	if (p_top_down_heap->mp_region->MemAvailable() < required_top_down_heap)
+	{
+		return false;
+	}
+	#endif
+	
+	return true;
+}
+
+
+
+
+/*
+	Returns max floor height within specified
+*/
+int	CParkManager::GetFloorHeight(GridDims dims, bool *pRetUniformHeight)
+{
+	Dbg_Assert(m_state_on);
+	Dbg_MsgAssert(m_floor_height_map, ("bad bad monkey!!!"));
+	
+	int max_height = -10000;
+	
+	if (pRetUniformHeight)
+		*pRetUniformHeight = true;
+	int corner_height = m_floor_height_map[dims.GetX()][dims.GetZ()].mHeight;
+	
+	for (int x = dims.GetX(); x < dims.GetX() + dims.GetW(); x++)
+	{
+		for (int z = dims.GetZ(); z < dims.GetZ() + dims.GetL(); z++)
+		{
+			Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+						  ("out of bounds (%d,%d)", x, z));
+			if (pRetUniformHeight && m_floor_height_map[x][z].mHeight != corner_height)
+				*pRetUniformHeight = false;
+			if (m_floor_height_map[x][z].mHeight > max_height)
+				max_height = m_floor_height_map[x][z].mHeight;
+		}
+	}
+	
+	return max_height;
+}
+
+
+
+
+#if 0
+/*
+	Changes the floor height stored in the height map, A subsequent call to
+	generate_floor_pieces_from_height_map() will make the geometry.
+*/
+void CParkManager::SetFloorHeight(GridDims area, int newHeight, bool justDropHighest)
+{
+	for (int x = area.GetX(); x < area.GetX() + area.GetW(); x++)
+	{
+		for (int z = area.GetZ(); z < area.GetZ() + area.GetL(); z++)
+		{
+			Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+						  ("out of bounds (%d,%d)", x, z));
+			
+			GridDims test_area(x, 0, z, 1, 1, 1);
+			if (IsOccupiedByRoad(test_area))
+			{
+				m_floor_height_map[x][z].mHeight = 0;
+			}
+			if (justDropHighest)
+			{
+				if (m_floor_height_map[x][z].mHeight > newHeight)
+					m_floor_height_map[x][z].mHeight = newHeight;
+			}
+			else
+				m_floor_height_map[x][z].mHeight = newHeight;
+		}
+	}
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~(mIN_SYNC_WITH_PARK | mIS_NEWER_THAN_PARK));
+}
+#endif
+
+
+void CParkManager::ResetFloorHeights(GridDims area)
+{
+	// First move all the pieces in the area such that their y's are zero.
+	CMapListTemp metas_at_pos = GetAreaMetaPieces(area);
+	CMapListNode *p_entry = metas_at_pos.GetList();
+	while (p_entry)
+	{
+		CConcreteMetaPiece *p_meta = p_entry->GetConcreteMeta();
+		if (!p_meta->IsRiser())
+		{
+			GridDims meta_area=p_meta->GetArea();
+			RelocateMetaPiece(p_meta,0,-meta_area.GetY(),0);
+			
+			// Set the floor heights under the piece to zero. Need to do this as well
+			// as zeroing the heights for the whole area, in case the piece sticks out
+			// of the area.
+			for (int x = meta_area.GetX(); x < meta_area.GetX() + meta_area.GetW(); x++)
+			{
+				for (int z = meta_area.GetZ(); z < meta_area.GetZ() + meta_area.GetL(); z++)
+				{
+					Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+								  ("out of bounds (%d,%d)", x, z));
+					m_floor_height_map[x][z].mHeight=0;
+				}
+			}		
+		}
+		
+		p_entry = p_entry->GetNext();
+	}
+
+	// Then set all the floor heights to zero
+	for (int x = area.GetX(); x < area.GetX() + area.GetW(); x++)
+	{
+		for (int z = area.GetZ(); z < area.GetZ() + area.GetL(); z++)
+		{
+			Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+						  ("out of bounds (%d,%d)", x, z));
+			m_floor_height_map[x][z].mHeight=0;
+		}
+	}		
+
+	// K: Do I need this?
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~(mIN_SYNC_WITH_PARK | mIS_NEWER_THAN_PARK));
+	
+	// Then rebuild the floor so that the above changes take effect.
+	RebuildFloor();
+}	
+
+
+/*
+	A "column" is a 3D region defined by the XZ rectangle of 'area'. Its bottom is at height 'y'. All the 
+	metapieces within the column will change y position by the amount specified. Floor heights within the
+	column will change as well.
+	
+	Returns true if success.
+*/
+bool CParkManager::SlideColumn(GridDims area, int bottom, int top, bool up, bool uneven)
+{
+	ESlideColumnFlags flags = ESlideColumnFlags(mFIRST_RECURSE);
+	if (up) flags = ESlideColumnFlags(flags | mUP);
+	if (uneven) flags = ESlideColumnFlags(flags | mUNEVEN);
+	
+	/* Remove bookkeeping left from last SlideColumn */
+	finish_slide_column();
+	if (mp_column_slide_list)
+		mp_column_slide_list->DestroyList();
+	mp_column_slide_list = NULL;
+	m_num_metas_killed_in_slide = 0;
+	m_num_pieces_killed_in_slide = 0;
+	
+	#if 1
+	if (uneven && intersection_with_riser_containing_metas(area))
+		return false;
+	#endif
+	
+	if (!slide_column_part_one(area, bottom, top, flags, &mp_column_slide_list))
+	{
+		fix_slide_column_part_one_failure();
+		return false;
+	}
+	slide_column_part_two(mp_column_slide_list);
+	
+	if (!up)
+		kill_pieces_in_layer_under_area(area);
+	
+	return true;
+}
+
+
+
+
+
+/*
+	Undoes the results of a successful SlideColumn()
+*/
+void CParkManager::UndoSlideColumn()
+{
+	fix_slide_column_part_one_failure();
+	
+	CMapListNode *p_node = mp_column_slide_list;
+	while(p_node)
+	{
+		p_node->mp_meta = RelocateMetaPiece(p_node->GetConcreteMeta(), 0, -p_node->mSlideAmount, 0);
+		p_node = p_node->GetNext();
+	}
+}
+
+// K: This is copied from CCursor::ChangeFloorHeight
+// Modified to take a GridDims parameter so that it can be used by the clipboard pasting code.
+bool CParkManager::ChangeFloorHeight(GridDims dims, int dir)
+{
+	/*
+		Uneven floor:
+			If going up, raise to height of topmost
+			If going down, only lower ones on same level
+	*/
+
+	bool uniform_height;
+	int height = GetFloorHeight(dims, &uniform_height);
+
+	/*
+		Now, 
+	*/
+
+	bool success = true;
+	if (SlideColumn(dims, dims.GetY(), height, (dir > 0), !uniform_height))
+	{
+		//Ryan("Slide column, dir = %d, dims=", dir);
+		//dims.PrintContents();
+
+		// K: This was required in the CCursor version of ChangeFloorHeight, can't
+		// call it here though cos it is a member function of CCursor ...
+		//change_cell_pos(0, 0);
+		
+		Spt::SingletonPtr p_editor;
+		if (RebuildFloor(p_editor->IsParkFull()))
+		{
+			HighlightMetasWithGaps(false, NULL);
+		}
+		else
+		{
+			// out of memory, so reverse slide
+			UndoSlideColumn();
+			RebuildFloor();
+			success = false;
+		}
+	}
+	else
+		success = false;
+
+	return success;
+}
+
+
+
+/*
+	Converts a cell based XYZ coordinate to its world coordinate equivalent.
+*/
+Mth::Vector	CParkManager::GridCoordinatesToWorld(const GridDims &cellDims, Mth::Vector *pRetDims)
+{
+	Mth::Vector world_pos(((int) cellDims.GetX() - (int) CParkManager::FLOOR_MAX_WIDTH / 2) * CParkGenerator::CELL_WIDTH,
+		(int) cellDims.GetY() * CParkGenerator::CELL_HEIGHT,
+		((int) cellDims.GetZ() - (int) CParkManager::FLOOR_MAX_LENGTH / 2) * CParkGenerator::CELL_LENGTH);
+
+	if (pRetDims)
+	{
+		(*pRetDims)[X] = cellDims.GetW() * CParkGenerator::CELL_WIDTH;
+		(*pRetDims)[Y] = cellDims.GetH() * CParkGenerator::CELL_HEIGHT;
+		(*pRetDims)[Z] = cellDims.GetL() * CParkGenerator::CELL_LENGTH;
+		#if 0
+		printf("dims: (%d,%d,%d)-(%d,%d,%d)\n", 
+			   cellDims.GetW(), cellDims.GetH(), cellDims.GetL(),
+			   (int) cellDims.GetX() - (int) CParkManager::FLOOR_MAX_WIDTH / 2,
+			   (int) cellDims.GetY(),
+			   (int) cellDims.GetZ() - (int) CParkManager::FLOOR_MAX_LENGTH / 2);
+		#endif
+	}
+	
+	return world_pos;
+}
+
+
+
+
+CParkManager *CParkManager::sInstance()
+{
+	Dbg_Assert(sp_instance);
+	return sp_instance;
+}
+
+
+
+
+/*
+	The manager keeps track of a piece database that matches the selection set the user sees at the top of the screen.
+*/
+CParkManager::CPieceSet &CParkManager::GetPieceSet(int setNumber, int *pMenuSetNumber)
+{
+	Dbg_Assert(setNumber >= 0 && setNumber < MAX_SETS);
+	
+	int menu_set_number = 0;
+	for (int s = 0; s < setNumber; s++)
+	{
+		if (m_palette_set[s].mVisible)
+			menu_set_number++;
+	}
+	
+	if (pMenuSetNumber)
+		*pMenuSetNumber = menu_set_number;
+
+	return m_palette_set[setNumber];
+}
+
+
+
+
+/*
+	y coordinates, dimensions of 'area' not used.
+*/
+bool CParkManager::IsOccupiedByRoad(GridDims area)
+{
+	for (uint i = 0; i < m_road_mask_tab_size; i++)
+	{
+		bool overlap = true;
+		if (area.GetX() + area.GetW() <= m_road_mask_tab[i].GetX()) overlap = false;
+		else if (area.GetX() >= m_road_mask_tab[i].GetX() + m_road_mask_tab[i].GetW()) overlap = false;
+		else if (area.GetZ() + area.GetL() <= m_road_mask_tab[i].GetZ()) overlap = false;
+		else if (area.GetZ() >= m_road_mask_tab[i].GetZ() + m_road_mask_tab[i].GetL()) overlap = false;
+
+		if (overlap) return true;
+	}
+	return false;
+}
+
+bool CParkManager::CanPlacePiecesIn(GridDims cellDims, bool ignoreFloorHeight)
+{
+	Spt::SingletonPtr p_editor;
+	if (p_editor->IsParkFull())
+	{
+		ParkEd("p_editor->IsParkFull()");
+		return false;
+	}	
+
+	// Check if there are any pieces in the area already.
+	// Not using GetMetaPiecesAt because it does not detect pieces in lowered cells for some reason.
+	CMapListTemp metas_at_pos = GetAreaMetaPieces(cellDims);
+	CMapListNode *p_entry = metas_at_pos.GetList();
+	while (p_entry)
+	{
+		CConcreteMetaPiece *p_meta=p_entry->GetConcreteMeta();
+		if (!p_meta->IsRiser())
+		{
+			return false;
+		}
+			
+		p_entry = p_entry->GetNext();
+	}
+	
+	if (!ignoreFloorHeight)
+	{
+		bool uniform_floor;
+		int floor_height = GetFloorHeight(cellDims, &uniform_floor);
+		if (floor_height != cellDims.GetY() || !uniform_floor)
+		{
+			ParkEd("floor_height != cellDims.GetY() || !uniform_floor");
+			return false;
+		}	
+	}
+		
+	if (IsOccupiedByRoad(cellDims))
+	{
+		ParkEd("IsOccupiedByRoad(cellDims)");
+		return false;
+	}	
+	
+	return true;
+}
+
+
+// pHalf returns the half number associated with the piece, if any gap found
+CGapManager::GapDescriptor *CParkManager::GetGapDescriptor(GridDims &area, int *pHalf)
+{
+	area.SetWHL(1,1,1);
+	CMapListTemp meta_list = GetMetaPiecesAt(area);
+	if (meta_list.IsEmpty())
+		return NULL;
+	CConcreteMetaPiece *p_meta = meta_list.GetList()->GetConcreteMeta();
+
+	int tab_index;
+	Dbg_Assert(mp_gap_manager);
+	if (!mp_gap_manager->IsGapAttached(p_meta, &tab_index))
+		return NULL;
+	
+	CGapManager::GapDescriptor *pDesc = mp_gap_manager->GetGapDescriptor(tab_index, pHalf);
+	Dbg_MsgAssert(pDesc, ("couldn't find gap descriptor"));
+	return pDesc;
+}
+
+
+
+
+/*
+	Call from:
+	
+	read_from_compressed_map_buffer(), CParkEditor, gap adjustment code, gap name changer
+	
+	-see if meta at cursor loc
+	-if user working on gap already, fail
+	-if no prexisiting, remove gap-in-progress
+	-build descriptor
+	-add gap
+*/
+bool CParkManager::AddGap(CGapManager::GapDescriptor &descriptor)
+{
+	Dbg_Assert(mp_gap_manager);
+	int gap_tab_index = mp_gap_manager->GetFreeGapIndex();
+	if (gap_tab_index == -1) return false;
+
+	for (int i = 0; i < descriptor.numCompleteHalves; i++)
+	{
+		// fetch piece associated with this gap half
+		descriptor.loc[i].SetWHL(1,1,1);
+		CMapListTemp meta_list = GetMetaPiecesAt(descriptor.loc[i]);
+		if (meta_list.IsEmpty())
+			return false;
+		Dbg_MsgAssert(!meta_list.IsEmpty(), ("am expecting a metapiece here"));
+		CConcreteMetaPiece *p_meta = meta_list.GetList()->GetConcreteMeta();
+		Dbg_MsgAssert(p_meta, ("no pieces found at (%d,%d,%d)", descriptor.loc[i].GetX(), descriptor.loc[i].GetY(), descriptor.loc[i].GetZ()));
+		Dbg_MsgAssert(!mp_gap_manager->IsGapAttached(p_meta, NULL), ("gap is already attached"));
+		p_meta->set_flag(CMetaPiece::mGAP_ATTACHED);
+		
+		descriptor.loc[i] = p_meta->GetArea();
+		
+		#if 0
+		Ryan("  Placing gap half %d, loc=(%d,%d,%d-%d,%d,%d), rot=%d, left=%d, right=%d\n", i,
+			 descriptor.loc[i].GetX(), descriptor.loc[i].GetY(), descriptor.loc[i].GetZ(), 
+			 descriptor.loc[i].GetW(), descriptor.loc[i].GetH(), descriptor.loc[i].GetL(),
+			 descriptor.rot[i], descriptor.leftExtent[i], descriptor.rightExtent[i]);
+		#endif
+		
+		GridDims area = p_meta->GetArea();
+		
+		#if 1
+		// work out gap positioning
+		Mth::Vector gap_pos;
+		float gap_length = 0.0f;
+		int gap_rot = 0;
+		switch(descriptor.rot[i])
+		{
+			case 0:	// east
+				gap_length = (area.GetL() + descriptor.leftExtent[i] + descriptor.rightExtent[i]) * CParkGenerator::CELL_LENGTH;
+				gap_pos[X] = (area.GetX() + area.GetW()) * CParkGenerator::CELL_WIDTH - CParkGenerator::CELL_WIDTH / 2.0f - 1.0f;
+				gap_pos[Z] = (area.GetZ() - descriptor.rightExtent[i]) * CParkGenerator::CELL_LENGTH + gap_length / 2.0f - CParkGenerator::CELL_LENGTH;
+				gap_pos[Y] = area.GetY() * CParkGenerator::CELL_HEIGHT;
+				gap_rot = 1;
+				break;
+			case 1:	// north
+				gap_length = (area.GetW() + descriptor.leftExtent[i] + descriptor.rightExtent[i]) * CParkGenerator::CELL_WIDTH;
+				gap_pos[X] = (area.GetX() - descriptor.rightExtent[i]) * CParkGenerator::CELL_WIDTH + gap_length / 2.0f - CParkGenerator::CELL_WIDTH;
+				gap_pos[Z] = area.GetZ() * CParkGenerator::CELL_LENGTH - CParkGenerator::CELL_LENGTH / 2.0f + 1.0f;
+				gap_pos[Y] = area.GetY() * CParkGenerator::CELL_HEIGHT;
+				gap_rot = 0;
+				break;
+			case 2:	// west
+				gap_length = (area.GetL() + descriptor.leftExtent[i] + descriptor.rightExtent[i]) * CParkGenerator::CELL_LENGTH;
+				gap_pos[X] = area.GetX() * CParkGenerator::CELL_WIDTH - CParkGenerator::CELL_WIDTH / 2.0f + 1.0f;
+				gap_pos[Z] = (area.GetZ() - descriptor.leftExtent[i]) * CParkGenerator::CELL_LENGTH + gap_length / 2.0f - CParkGenerator::CELL_LENGTH;
+				gap_pos[Y] = area.GetY() * CParkGenerator::CELL_HEIGHT;
+				gap_rot = 1;
+				break;
+			case 3:	// south
+				gap_length = (area.GetW() + descriptor.leftExtent[i] + descriptor.rightExtent[i]) * CParkGenerator::CELL_WIDTH;
+				gap_pos[X] = (area.GetX() - descriptor.leftExtent[i]) * CParkGenerator::CELL_WIDTH + gap_length / 2.0f - CParkGenerator::CELL_WIDTH;
+				gap_pos[Z] = (area.GetZ() + area.GetL()) * CParkGenerator::CELL_LENGTH - CParkGenerator::CELL_LENGTH / 2.0f - 1.0f;
+				gap_pos[Y] = area.GetY() * CParkGenerator::CELL_HEIGHT;
+				gap_rot = 0;
+				break;
+			default:
+				Dbg_MsgAssert(0, ("not a valid gap rotation"));
+				break;
+		}
+		#endif
+		
+		gap_pos[X] -= MAX_WIDTH * CParkGenerator::CELL_WIDTH / 2.0f;
+		gap_pos[Z] -= MAX_LENGTH * CParkGenerator::CELL_LENGTH / 2.0f;
+
+		CClonedPiece *p_gap_piece = mp_generator->CreateGapPiece(gap_pos, gap_length, gap_rot);
+		p_gap_piece->SetScaleX(gap_length / CParkGenerator::CELL_WIDTH);
+		
+		// create gap
+		if (i == 0)
+			mp_gap_manager->StartGap(p_gap_piece, p_meta, gap_tab_index);
+		else	
+			mp_gap_manager->EndGap(p_gap_piece, p_meta, gap_tab_index);
+	}	
+
+	descriptor.tabIndex = gap_tab_index;
+	mp_gap_manager->SetGapInfo(gap_tab_index, descriptor);
+
+	return true;
+}
+
+
+
+/*
+	Called from: change cursor, cleanup, gap placement, gap adjustment, name change
+*/
+void CParkManager::RemoveGap(CGapManager::GapDescriptor &descriptor)
+{
+	#if 1
+	// XXX
+	for (int half = 0; half < 2; half++)
+		Ryan("   removing gap half %d (%d,%d,%d),(%d,%d,%d) left=%d right=%d, index=%d\n", half,
+			 descriptor.loc[half].GetX(), descriptor.loc[half].GetY(), descriptor.loc[half].GetZ(),
+			 descriptor.loc[half].GetW(), descriptor.loc[half].GetH(), descriptor.loc[half].GetL(),
+			 descriptor.leftExtent[half], descriptor.rightExtent[half],
+				descriptor.tabIndex);
+	#endif
+	
+	CConcreteMetaPiece *p_meta[2];
+	for (int half = 0; half < descriptor.numCompleteHalves; half++)
+	{
+		CMapListTemp meta_list = GetMetaPiecesAt(descriptor.loc[half]);
+		Dbg_MsgAssert(!meta_list.IsEmpty(), ("no pieces found at (%d,%d,%d)", descriptor.loc[half].GetX(), descriptor.loc[half].GetY(), descriptor.loc[half].GetZ()));
+		
+		#if 1
+		if (!meta_list.IsSingular())
+		{
+//			printf("failed singularity, half %d\n", half);
+			meta_list.PrintContents();
+		}
+		#endif
+		
+		// go through list, find metapiece matching location above
+		// there is only SUPPOSED to be one metapiece in list, but sometimes there are more
+		// and I have no idea why, so fuck it
+		CMapListNode *p_node = meta_list.GetList();
+		while(p_node)
+		{
+			p_meta[half] = p_node->GetConcreteMeta();
+			GridDims area = p_meta[half]->GetArea();
+
+			if (area.GetX() == descriptor.loc[half].GetX() &&
+				area.GetY() == descriptor.loc[half].GetY() &&
+				area.GetZ() == descriptor.loc[half].GetZ())
+				break;
+
+			p_node = p_node->GetNext();
+		}
+
+		//p_meta[half] = meta_list.GetList()->GetConcreteMeta();
+		p_meta[half]->Highlight(false);
+		p_meta[half]->clear_flag(CMetaPiece::mGAP_ATTACHED);
+	}
+
+	int tab_index;
+	Dbg_Assert(mp_gap_manager);
+	if (mp_gap_manager->IsGapAttached(p_meta[0], &tab_index))
+	{
+		mp_gap_manager->RemoveGap(tab_index);
+	}
+
+	descriptor.tabIndex = -1;
+	// Reset the cancel flags to the default, to fix TT9493
+	descriptor.mCancelFlags=Script::GetInteger(CRCD(0x9a27e74d,"Cancel_Ground"));
+}
+
+
+
+
+void CParkManager::RemoveGap(CConcreteMetaPiece *pMeta)
+{
+	int tab_index;
+	Dbg_Assert(mp_gap_manager);
+	if (!mp_gap_manager->IsGapAttached(pMeta, &tab_index))
+		return;
+	
+	int half;
+	CGapManager::GapDescriptor *pDesc = mp_gap_manager->GetGapDescriptor(tab_index, &half);
+	Dbg_MsgAssert(pDesc, ("couldn't find gap descriptor"));
+	RemoveGap(*pDesc);
+}
+
+
+
+
+void CParkManager::HighlightMetasWithGaps(bool highlightOn, CGapManager::GapDescriptor *pDesc)
+{
+	CMapListNode *p_node = mp_concrete_metapiece_list;
+	while (p_node)
+	{
+		if (p_node->GetConcreteMeta()->m_flags & CMetaPiece::mGAP_ATTACHED)
+			p_node->GetConcreteMeta()->Highlight(highlightOn, true);
+		p_node = p_node->GetNext();
+	}
+
+	mp_generator->SetGapPiecesVisible(false);
+	
+	if (highlightOn && pDesc && pDesc->tabIndex >= 0)
+	{
+		mp_gap_manager->MakeGapWireframe(pDesc->tabIndex);
+	}
+	else if (!highlightOn)
+	{
+		mp_generator->SetGapPiecesVisible(false);
+	}
+}
+
+// Added for use by s_generate_summary_info in mcfunc.cpp
+void CParkManager::GetSummaryInfoFromBuffer(uint8 *p_buffer, int *p_numGaps, int *p_numMetas, uint16 *p_theme, uint32 *p_todScript, int *p_width, int *p_length)
+{
+	CompressedMapHeader *p_header=(CompressedMapHeader*)p_buffer;
+	*p_width=p_header->mW;
+	*p_length=p_header->mL;
+	*p_numGaps=p_header->mNumGaps;
+	*p_numMetas=p_header->mNumMetas;
+	*p_theme=p_header->mTheme;
+	if (p_header->mTODScript)
+	{
+		*p_todScript=p_header->mTODScript;
+	}
+	else
+	{
+		*p_todScript=CRCD(0x1ca1ff20,"default");
+	}	
+}
+
+
+/*
+	Creates a "stack" of risers within the specified vertical column. If !simpleBuild, will keep exisiting
+	riser metapieces if they do the job.
+	
+	'top' refers to cell position of top of topmost riser
+*/
+void CParkManager::output_riser_stack(BuildFloorParams ¶ms)
+{
+	Dbg_MsgAssert(m_floor_height_map, ("bad bad monkey!!!"));
+	
+	Dbg_MsgAssert(params.top >= -16 && params.top <= 15, ("faulty floor height %d, at (%d,%d)", params.top, params.x, params.z));
+	
+	int lowest_neighbor = 10000;
+	
+	// work out lowest neighbor
+	if (params.x > m_park_far_bounds.GetX())
+	{
+		if (m_floor_height_map[params.x-1][params.z].mHeight < lowest_neighbor)
+			lowest_neighbor = m_floor_height_map[params.x-1][params.z].mHeight;
+	}
+	else
+		lowest_neighbor = (lowest_neighbor > 0) ? 0 : lowest_neighbor;
+
+	if (params.x < m_park_far_bounds.GetX() + m_park_far_bounds.GetW() - 1)
+	{
+		if (m_floor_height_map[params.x+1][params.z].mHeight < lowest_neighbor)
+			lowest_neighbor = m_floor_height_map[params.x+1][params.z].mHeight;
+	}
+	else
+		lowest_neighbor = (lowest_neighbor > 0) ? 0 : lowest_neighbor;
+
+	if (params.z > m_park_far_bounds.GetZ())
+	{
+		if (m_floor_height_map[params.x][params.z-1].mHeight < lowest_neighbor)
+			lowest_neighbor = m_floor_height_map[params.x][params.z-1].mHeight;
+	}
+	else
+		lowest_neighbor = (lowest_neighbor > 0) ? 0 : lowest_neighbor;
+
+	if (params.z < m_park_far_bounds.GetZ() + m_park_far_bounds.GetL() - 1)
+	{
+		if (m_floor_height_map[params.x][params.z+1].mHeight < lowest_neighbor)
+			lowest_neighbor = m_floor_height_map[params.x][params.z+1].mHeight;
+	}
+	else
+		lowest_neighbor = (lowest_neighbor > 0) ? 0 : lowest_neighbor;
+
+	EFloorFlags flags = mHAS_BOTTOM;
+	GridDims road_test_area(params.x, 0, params.z, 1, 1, 1);
+	if (IsOccupiedByRoad(road_test_area))
+	{
+		flags = EFloorFlags(flags | mNO_COVER);
+		params.top = 0;
+	}
+	
+	if (lowest_neighbor < params.top)
+	{
+		params.bottom = lowest_neighbor;
+	}
+	else
+	{
+		params.bottom = params.top-1;
+		flags = EFloorFlags(flags & ~mHAS_BOTTOM);
+	}
+
+	params.flags = flags;
+	for (int y = params.bottom; y < params.top; y++)
+	{
+		if (y == params.top-1 && !(flags & mNO_COVER))
+			params.flags = EFloorFlags(params.flags | mHAS_TOP);
+		
+		if (y == -1)
+		{
+			if (y <= lowest_neighbor)
+				params.heightSlotOffset = 3; // group 4 -- bottom level, just below surface
+			else
+				params.heightSlotOffset = 2; // group 3 -- not bottom level, just below surface
+		}
+		else if (y < -1)
+		{
+			if (y <= lowest_neighbor)
+				params.heightSlotOffset = 0; // group 1 -- bottom level, not touching surface
+			else
+				params.heightSlotOffset = 1; // group 2 -- not bottom level, not touching surface
+		}
+		else
+		{
+			if (y == params.top-1)
+			{
+				if (y == 0)
+					params.heightSlotOffset = 4; // group 5 -- top level risers sitting on surface
+				else
+					params.heightSlotOffset = 7; // group 8 -- risers sitting above surface, top level
+			}
+			else if (y == 0)
+				params.heightSlotOffset = 5; // group 6 -- risers sitting on surface, but not top level
+			else
+				params.heightSlotOffset = 6; // group 7 -- risers sitting above surface, not top level
+		}		
+		
+		params.y = y;
+		if (params.simpleBuild)
+			build_add_floor_piece_simple(params); 
+		else
+			build_add_floor_piece(params); 
+	}
+}
+
+
+
+
+/*
+	Creates a riser metapiece, if one is needed.
+*/
+void CParkManager::build_add_floor_piece(BuildFloorParams ¶ms)
+{
+	uint32 type = TYPE_EMPTY_BLOCK;
+	if (params.flags & mHAS_BOTTOM)
+	{
+		if (params.flags & mHAS_TOP)
+			type = m_riser_piece_checksum[params.heightSlotOffset][0];
+		else
+			type = m_riser_piece_checksum[params.heightSlotOffset][1];
+	}
+	else if (params.flags & mHAS_TOP)
+	{
+		type = m_riser_piece_checksum[params.heightSlotOffset][2];
+	}
+	else
+		return;
+	
+	// see if floor already included in metapiece at this position
+	int floor_height = params.y + 1;
+	Dbg_MsgAssert(floor_height >= -16 && floor_height <= 15, ("faulty floor height %d, at (%d,%d,%d)", 
+															  floor_height, params.x, params.y, params.z));
+	uint32 floor_bit = (1<<(floor_height+16));
+	if (m_floor_height_map[params.x][params.z].mEnclosedFloor & floor_bit)
+	{
+		#if 0
+		printf("preexisting riser at %d (%d,%d)!\n", floor_height, params.x, params.z);
+		#endif
+		return;
+	}
+	
+	// see if floor piece is already there and if it has desired type
+	GridDims test_dims(params.x, params.y, params.z, 1, 1, 1);
+	CMapListNode *p_node = GetMetaPiecesAt(test_dims);
+	if (p_node)
+	{
+		if (p_node->GetMeta()->m_name_checksum == type)
+		{
+			/*
+			if (type == TYPE_FLOOR_BLOCK && 0)
+				printf("preserving floor block at %d,%d,%d\n", 
+					   p_node->GetMeta()->GetArea().GetX(),
+					   p_node->GetMeta()->GetArea().GetY(),
+					   p_node->GetMeta()->GetArea().GetZ());
+			*/
+			
+			// undo mark for kill
+			p_node->GetMeta()->clear_flag(CMetaPiece::mMARK_FOR_KILL);
+			p_node->DestroyList();
+			return;
+		}
+		else
+			p_node->DestroyList();
+	}
+	
+	if (!params.dryRun)
+	{
+		#ifdef USE_BUILD_LIST
+		enum
+		{
+			MAX_BUILD_LIST_SIZE=10000,
+		};
+		Dbg_MsgAssert(m_build_list_sizem_num_used_entries;
+	}
+	params.addCount++;
+}
+
+
+
+
+/*
+	Creates a riser metapiece, period.
+*/
+void CParkManager::build_add_floor_piece_simple(BuildFloorParams ¶ms)
+{
+	uint32 type = TYPE_EMPTY_BLOCK;
+	if (params.flags & mHAS_BOTTOM)
+	{
+		if (params.flags & mHAS_TOP)
+			type = m_riser_piece_checksum[params.heightSlotOffset][0];
+		else
+			type = m_riser_piece_checksum[params.heightSlotOffset][1];
+	}
+	else if (params.flags & mHAS_TOP)
+	{
+		type = m_riser_piece_checksum[params.heightSlotOffset][2];
+	}
+	else
+		return;
+	
+	
+	CConcreteMetaPiece *p_out = CreateConcreteMeta(GetAbstractMeta(type));
+	GridDims pos(params.x, params.y, params.z);
+	AddMetaPieceToPark(pos, p_out);
+	params.addCount++;
+}
+
+
+
+
+/*
+	Some metapieces contain risers.
+*/
+void CParkManager::apply_meta_contained_risers_to_floor(CConcreteMetaPiece *pMetaPiece, bool add)
+{
+	for (uint i = 0; i < pMetaPiece->m_num_used_entries; i++)
+	{
+		CClonedPiece *p_piece = pMetaPiece->GetContainedPiece(i)->CastToCClonedPiece();
+		
+		// see if this piece affects floor height
+		SMetaDescriptor &desc = pMetaPiece->get_desc_at_index(i);
+		if (desc.mRiser)
+		{
+			GridDims riser_area;
+			p_piece->GetCellDims(&riser_area);
+			riser_area[X] = pMetaPiece->m_cell_area.GetX() + desc.mX;
+			riser_area[Y] = pMetaPiece->m_cell_area.GetY() + desc.mY;
+			riser_area[Z] = pMetaPiece->m_cell_area.GetZ() + desc.mZ;
+	
+			int start_x = riser_area.GetX();
+			int end_x = start_x + riser_area.GetW();
+			int start_z = riser_area.GetZ();
+			int end_z = start_z + riser_area.GetL();
+			#if 0
+			printf("raising floor at: (%d,%d,%d)+(%d,%d,%d), desc.mRiser is %d at 0x%x, index %d\n",
+				   pMetaPiece->m_cell_area.GetX(), pMetaPiece->m_cell_area.GetY(), pMetaPiece->m_cell_area.GetZ(),
+				   desc.mX, desc.mY, desc.mZ, desc.mRiser, &desc.mRiser, i);
+			#endif
+			for (int x = start_x; x < end_x; x++)
+			{
+				for (int z = start_z; z < end_z; z++)
+				{
+					Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+								  ("out of bounds (%d,%d)", x, z));
+					if (add)
+					{
+						if (m_floor_height_map[x][z].mHeight < riser_area.GetY() + riser_area.GetH())
+							m_floor_height_map[x][z].mHeight = riser_area.GetY() + riser_area.GetH();
+						if (m_floor_height_map[x][z].mHeight > 15)
+							m_floor_height_map[x][z].mHeight = 15;
+						if (m_floor_height_map[x][z].mHeight < -16)
+							m_floor_height_map[x][z].mHeight = -16;
+					}
+					else
+					{
+						// Are there non-riser metapieces above this one?
+						GridDims area_above = riser_area;
+						area_above[Y] = riser_area.GetY() + riser_area.GetH();
+						area_above.MakeInfinitelyHigh();
+						CMapListTemp meta_list = GetMetaPiecesAt(area_above, Mth::ROT_0, NULL, true);
+						if (meta_list.IsEmpty())
+						{
+							if (m_floor_height_map[x][z].mHeight > riser_area.GetY())
+								m_floor_height_map[x][z].mHeight = riser_area.GetY();
+						}
+					}
+
+					for (int y = riser_area.GetY(); y < riser_area.GetY() + riser_area.GetH(); y++)
+					{
+						if (add)
+							m_floor_height_map[x][z].mEnclosedFloor |= (1<<(y+16+1));
+						else
+							m_floor_height_map[x][z].mEnclosedFloor &= ~(1<<(y+16+1));
+					}
+					
+					#if 0
+					printf("  floor change: (%d,%d) to %d, flags 0x%x, bottom %d, top %d\n", 
+						   x, z, m_floor_height_map[x][z].mHeight, m_floor_height_map[x][z].mEnclosedFloor,
+						   riser_area.GetY(), riser_area.GetY() + riser_area.GetH());
+					#endif
+				}
+			}
+		}
+	}
+}
+
+
+
+
+bool CParkManager::intersection_with_riser_containing_metas(GridDims area)
+{
+	area.MakeInfiniteOnY();
+	
+	CMapListTemp list = GetMetaPiecesAt(area, Mth::ROT_0, NULL, true);
+	CMapListNode *p_node = list.GetList();
+	while (p_node)
+	{
+		if (p_node->GetConcreteMeta()->m_flags & CMetaPiece::mCONTAINS_RISERS)
+			return true;
+		p_node = p_node->GetNext();
+	}
+
+	return false;
+}
+
+
+
+
+/*
+	Change map height. Add metapieces in column to move list. Repeat this for every metapiece in move list. No
+	metapiece will end up in move list twice, though.
+	
+	Good LUCK figuring this out. :)
+*/
+bool CParkManager::slide_column_part_one(GridDims area, int bottom, int top, ESlideColumnFlags flags, CMapListNode **ppMoveList)
+{
+	GridDims test_area = area;
+	test_area[Y] = (uint8) bottom;
+	if (flags & mFIRST_RECURSE)
+		test_area.MakeInfiniteOnY();
+	else
+		test_area.MakeInfinitelyHigh();
+
+	for (int x = test_area.GetX(); x < test_area.GetX() + test_area.GetW(); x++)
+	{
+		for (int z = test_area.GetZ(); z < test_area.GetZ() + test_area.GetL(); z++)
+		{
+			Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+						  ("out of bounds (%d,%d)", x, z));
+			if (!m_floor_height_map[x][z].mMarkAsSlid)
+			{
+				int change_amount = 0;
+				if (flags & mUNEVEN)
+				{
+					if (flags & mUP)
+						change_amount = top - m_floor_height_map[x][z].mHeight;
+					else
+						change_amount = (m_floor_height_map[x][z].mHeight == top) ? -1 : 0;
+				}
+				else
+				{
+					if (flags & mUP)
+						change_amount = 1;
+					else
+						change_amount = -1;
+				}
+				
+				m_floor_height_map[x][z].mHeight += change_amount;
+				m_floor_height_map[x][z].mSlideAmount = change_amount;
+
+				if (m_floor_height_map[x][z].mHeight < -16 || m_floor_height_map[x][z].mHeight > 15)
+				{
+					return false;
+				}
+			}
+			m_floor_height_map[x][z].mMarkAsSlid = true;
+		}
+	}
+
+	CMapListTemp list = GetMetaPiecesAt(test_area, Mth::ROT_0, NULL, true);
+	CMapListNode *p_node = list.GetList();
+	while (p_node)
+	{
+		CConcreteMetaPiece *p_meta = p_node->GetConcreteMeta();
+		GridDims meta_area = p_meta->GetArea();
+		
+		if (!p_meta->IsRiser() && !(p_meta->m_flags & CMetaPiece::mMARK_AS_SLID))
+		{
+			/*
+				Are we actually going to move this meta?
+				
+				Even surface:
+				-yes
+				
+				Uneven
+				-up: only if lower than top
+				-down
+			*/
+			
+			p_meta->set_flag(CMetaPiece::mMARK_AS_SLID);
+			Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+			CMapListNode *p_new_node = new CMapListNode();
+			Mem::Manager::sHandle().PopContext();
+			p_new_node->mp_next = *ppMoveList;
+			p_new_node->mp_meta = p_meta;			
+			*ppMoveList = p_new_node;
+
+			#if 0
+			Ryan("rising meta at: ");
+			p_meta->GetArea().PrintContents();
+			#endif
+			
+			p_new_node->mSlideAmount = 0;
+			if (flags & mUNEVEN)
+			{
+				if (flags & mUP)
+					p_new_node->mSlideAmount = top - meta_area.GetY();
+				else
+					p_new_node->mSlideAmount = (meta_area.GetY() == top) ? -1 : 0;
+			}
+			else
+			{
+				if (flags & mUP)
+					p_new_node->mSlideAmount = 1;
+				else
+					p_new_node->mSlideAmount = -1;
+			}
+			
+			ESlideColumnFlags recurse_flags = ESlideColumnFlags(flags & ~mFIRST_RECURSE);
+			if (!slide_column_part_one(meta_area, meta_area.GetY(), top, recurse_flags, ppMoveList))
+				return false;
+		}
+		p_node = p_node->GetNext();
+	}
+
+	return true;
+}
+
+
+
+
+void CParkManager::fix_slide_column_part_one_failure()
+{
+	for (int x = 0; x < FLOOR_MAX_WIDTH; x++)
+	{
+		for (int z = 0; z < FLOOR_MAX_LENGTH; z++)
+		{
+			if (m_floor_height_map[x][z].mMarkAsSlid)
+			{
+				m_floor_height_map[x][z].mMarkAsSlid = false;
+				m_floor_height_map[x][z].mHeight -= m_floor_height_map[x][z].mSlideAmount;
+			}
+			if (m_floor_height_map[x][z].mHeight > 15)
+				m_floor_height_map[x][z].mHeight = 15;
+			if (m_floor_height_map[x][z].mHeight < -16)
+				m_floor_height_map[x][z].mHeight = -16;
+		}
+	}
+}
+
+
+
+
+/*
+	Go through move list, relocate each metapiece
+*/
+void CParkManager::slide_column_part_two(CMapListNode *pMoveList)
+{
+	//Mem::Heap *p_top_down_heap = Mem::Manager::sHandle().TopDownHeap();
+	
+	CMapListNode *p_node = pMoveList;
+	while (p_node)
+	{
+		CConcreteMetaPiece *p_meta = p_node->GetConcreteMeta();
+		
+		if (p_node->mSlideAmount < 0)
+		{
+			kill_pieces_in_layer_under_area(p_meta->GetArea());
+		}
+
+		p_node->mp_meta = RelocateMetaPiece(p_meta, 0, p_node->mSlideAmount, 0);
+
+		//if (p_top_down_heap->mp_region->MemAvailable() < 200000)
+		//{
+		//	GetGenerator()->GetClonedScene()->UpdateSuperSectors();
+		//}	
+		
+		p_node = p_node->GetNext();
+	}	
+}
+
+
+
+
+void CParkManager::kill_pieces_in_layer_under_area(GridDims area)
+{
+	GridDims kill_area = area;
+	kill_area[Y] = area.GetY() - 1;
+	kill_area[H] = 1;
+
+	#if 0
+	printf("killing metas in area: (%d,%d,%d)-(%d,%d,%d)\n",
+		   kill_area.GetX(), kill_area.GetY(), kill_area.GetZ(),
+		   kill_area.GetW(), kill_area.GetH(), kill_area.GetL());
+	#endif
+
+	uint32 killed_result = DestroyMetasInArea(kill_area, mEXCLUDE_PIECES_MARKED_AS_SLID);
+	m_num_metas_killed_in_slide += killed_result>>16;
+	m_num_pieces_killed_in_slide += killed_result & 0xFFFF;
+}
+
+
+
+
+/*
+	Undo 'slid' markers in floor map array. Turn off MARK_AS_SLID flags on metapieces.
+*/
+void CParkManager::finish_slide_column()
+{
+	for (int x = m_park_far_bounds.GetX(); x < m_park_far_bounds.GetX() + m_park_far_bounds.GetW(); x++)
+	{
+		for (int z = m_park_far_bounds.GetZ(); z < m_park_far_bounds.GetZ() + m_park_far_bounds.GetL(); z++)
+		{
+			Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+						  ("out of bounds (%d,%d)", x, z));
+			m_floor_height_map[x][z].mMarkAsSlid = false;
+			m_floor_height_map[x][z].mSlideAmount = 0;
+		}
+	}
+
+	CMapListNode *p_node = mp_concrete_metapiece_list;
+	while(p_node)
+	{
+		p_node->GetConcreteMeta()->clear_flag(CMetaPiece::mMARK_AS_SLID);
+		p_node = p_node->GetNext();
+	}
+}
+
+#ifdef USE_BUILD_LIST
+void CParkManager::create_metas_in_build_list()
+{
+	for (int i=0; iGetMeta();
+		if (p_meta->IsRiser())
+		{
+			p_meta->set_flag(CMetaPiece::mMARK_FOR_KILL);
+			mark_count++;
+		}
+		p_node = p_node->GetNext();
+	}
+	mark_count += m_num_metas_killed_in_slide;
+
+	#ifdef USE_BUILD_LIST
+	m_build_list_size=0;
+	Dbg_MsgAssert(mp_build_list_entry==NULL,("Non-NULL mp_build_list_entry"));
+	#endif
+
+	
+	Dbg_MsgAssert(m_floor_height_map, ("bad bad monkey!!!"));
+	BuildFloorParams riser_build_params;
+	riser_build_params.simpleBuild = simpleBuild;
+	riser_build_params.dryRun = dryRun;
+	riser_build_params.addCount = 0; // counts all risers added by the output_riser_stack() calls
+	riser_build_params.addCount2 = 0;
+	for (int x = m_park_near_bounds.GetX() - 1; x < m_park_near_bounds.GetX() + m_park_near_bounds.GetW() + 1; x++)
+	{
+		for (int z = m_park_near_bounds.GetZ() - 1; z < m_park_near_bounds.GetZ() + m_park_near_bounds.GetL() + 1; z++)
+		{
+			Dbg_MsgAssert(x >= 0 && z >= 0 && x < FLOOR_MAX_WIDTH && z < FLOOR_MAX_LENGTH,
+						  ("out of bounds (%d,%d)", x, z));
+			riser_build_params.x = x;
+			riser_build_params.z = z;
+			riser_build_params.top = m_floor_height_map[x][z].mHeight;
+			riser_build_params.bottom = m_park_far_bounds.GetY();
+			output_riser_stack(riser_build_params);
+		}
+
+		#ifdef __PLAT_XBOX__
+		// On the XBox the large amount of CPU time taken up by rebuilding the park causes a glitch when
+		// wma music is playing. So make sure Pcm::Update gets called often whilst in this inner loop.
+		DirectSoundDoWork();
+		Pcm::Update();
+		#endif // __PLAT_XBOX__
+		#ifdef __PLAT_NGC__
+		// Same for the NGC ...
+		Pcm::Update();
+		#endif // __PLAT_NGC__
+	}
+
+	// remove all marked floor piece
+	int kill_count = 0; // counts risers killed
+	int kill_count2 = 0; // counts contained pieces
+	int left_count = 0;	// counts risers not killed plus those added
+	p_node = mp_concrete_metapiece_list;
+	while(p_node)
+	{
+		CMapListNode *p_nextNode = p_node->GetNext();		// get next node now, as DestroyConcretMeta, below, can delete current node
+		CMetaPiece *p_meta = p_node->GetMeta();
+		if (p_meta->m_flags & CMetaPiece::mMARK_FOR_KILL)
+		{
+			kill_count++;
+			kill_count2 += p_meta->m_num_used_entries;
+			if (!dryRun)
+			{
+				//p_node->GetConcreteMeta()->GetArea().PrintContents();
+				DestroyConcreteMeta(p_node->GetConcreteMeta(), mDONT_DESTROY_PIECES_ABOVE);
+			}
+		}
+		else if (p_meta->IsRiser())
+			left_count++;
+		p_node = p_nextNode;
+	}
+	kill_count += m_num_metas_killed_in_slide;
+	kill_count2 += m_num_pieces_killed_in_slide;
+	
+
+	#ifdef USE_BUILD_LIST
+	mp_generator->GetClonedScene()->UpdateSuperSectors();	
+	if (!dryRun)
+	{
+		left_count+=m_build_list_size;
+		create_metas_in_build_list();
+	}
+	Dbg_MsgAssert(mp_build_list_entry==NULL,("Non-NULL mp_build_list_entry"));
+	#endif
+		
+	// risers preexisting + risers added - risers killed == risers left
+	Dbg_Assert(dryRun || riser_build_params.addCount == left_count + kill_count - mark_count);
+	
+	//printf("riser_build_params.addCount = %d kill_count = %d, kill_count2 = %d\n",riser_build_params.addCount,kill_count,kill_count2);
+	
+	//if (dryRun)
+	//	printf("DRYRUN: ");
+	//printf("marked %d pieces, killed %d, not killed %d, left %d, added %d\n", 
+	//	   mark_count, kill_count, mark_count - kill_count, left_count, riser_build_params.addCount);
+	//printf("addCount2 = %d, killCount2 = %d\n", riser_build_params.addCount2, kill_count2);
+
+	if (dryRun && (riser_build_params.addCount > kill_count || riser_build_params.addCount2 > kill_count2))
+	{
+		return false;
+	}
+	
+	//if (dryRun && (riser_build_params.addCount - kill_count > 200))
+	//{
+	//	// Don't allow height changes that will result in too many pieces being created, to prevent
+	//	// running out of memory.
+	//	return false;
+	//}
+
+	return true;
+}
+
+
+
+
+/*
+	Create new list if none
+*/
+void CParkManager::add_metapiece_to_node_list(CMetaPiece *pMetaToAdd, CMapListNode **ppList)
+{
+	CMapListNode *p_test_node = *ppList;
+	while(p_test_node)
+	{
+		if (p_test_node->GetMeta() == pMetaToAdd) return; // it's in there
+		p_test_node = p_test_node->GetNext();
+	}
+
+	// not in list already, so add it
+	Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+	CMapListNode *p_new_node = new CMapListNode();
+	Mem::Manager::sHandle().PopContext();
+	p_new_node->mp_meta = pMetaToAdd;
+	p_new_node->mp_next = *ppList;
+	*ppList = p_new_node;
+}
+
+
+
+
+/*
+	Opposite of above
+*/
+void CParkManager::remove_metapiece_from_node_list(CMetaPiece *pMetaToRemove, CMapListNode **ppList)
+{
+	CMapListNode *p_node = *ppList;
+	CMapListNode *p_prev = NULL;
+	while(p_node)
+	{
+		if (p_node->GetMeta() == pMetaToRemove)
+		{
+			if (p_prev)
+				p_prev->mp_next = p_node->GetNext();
+			else
+				*ppList = p_node->GetNext();
+
+			delete p_node;
+			return;
+		}
+		
+		p_prev = p_node;
+		p_node = p_node->GetNext();
+	}
+}
+
+
+
+
+void CParkManager::bucketify_metapiece(CConcreteMetaPiece *pMeta)
+{
+	for (int x = 0; x < pMeta->m_cell_area.GetW(); x++)
+	{
+		for (int z = 0; z < pMeta->m_cell_area.GetL(); z++)
+		{
+			Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+			CMapListNode *p_new_entry = new CMapListNode();
+			Mem::Manager::sHandle().PopContext();
+			p_new_entry->mp_meta = pMeta;
+			
+			p_new_entry->mp_next = mp_bucket_list[x + pMeta->m_cell_area.GetX()][z + pMeta->m_cell_area.GetZ()];
+			mp_bucket_list[x + pMeta->m_cell_area.GetX()][z + pMeta->m_cell_area.GetZ()] = p_new_entry;
+		}
+	}
+}
+
+
+
+
+void CParkManager::debucketify_metapiece(CConcreteMetaPiece *pMeta)
+{
+	for (int x = 0; x < pMeta->m_cell_area.GetW(); x++)
+	{
+		for (int z = 0; z < pMeta->m_cell_area.GetL(); z++)
+		{
+			CMapListNode *p_prev = NULL;
+			CMapListNode *p_entry = mp_bucket_list[x + pMeta->m_cell_area.GetX()][z + pMeta->m_cell_area.GetZ()];
+			while(p_entry)
+			{
+				// piece should occur only once per list
+				
+				if (p_entry->mp_meta == pMeta)
+				{
+					if (p_prev)
+						p_prev->mp_next = p_entry->mp_next;
+					else
+						mp_bucket_list[x + pMeta->m_cell_area.GetX()][z + pMeta->m_cell_area.GetZ()] = p_entry->mp_next;
+					delete p_entry;
+					break;
+				}
+	
+				p_prev = p_entry;
+				p_entry = p_entry->mp_next;
+			}
+		} // end for z
+	} // end for x
+}
+
+
+
+
+void CParkManager::create_abstract_metapieces()
+{
+	ParkEd("CParkManager::create_abstract_metapieces()");
+	
+	/*
+		This add_later stuff is so we can create singular metapieces that weren't defined in 
+		Ed_standard_metapieces. Every CPiece that exists must have a corresponding singular
+		metapiece, at least an abstract one.
+	*/
+	uint32 add_later_tab[1000];
+	uint add_later_count = 0;
+	
+	Script::CArray *p_metapiece_array = Script::GetArray("Ed_standard_metapieces", Script::ASSERT);
+	
+	// this first loop makes sure that metapieces named in EdPieces2.q actually exist
+	for (uint i = 0; i < p_metapiece_array->GetSize(); i++)
+	{
+		Script::CStruct *p_entry = p_metapiece_array->GetStructure(i);
+		
+		// see if singular or multiple metapiece
+		uint32 single_crc = 0;
+		if (p_entry->GetChecksum("single", &single_crc))
+		{
+			mp_generator->GetMasterPiece(single_crc, false);
+		}		
+	} // end for
+
+
+	// now, build abstract metapieces from Ed_standard_metapieces array
+	for (uint i = 0; i < p_metapiece_array->GetSize(); i++)
+	{
+		Script::CStruct *p_entry = p_metapiece_array->GetStructure(i);
+		
+		Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+		CAbstractMetaPiece *p_meta = new CAbstractMetaPiece();
+		Mem::Manager::sHandle().PopContext();
+		
+		// see if singular or multiple metapiece
+		uint32 single_crc = 0;
+		Script::CArray *p_multiple_array = NULL;
+		if (p_entry->GetChecksum("single", &single_crc))
+		{
+			p_meta->initialize_desc_table(1);
+			
+			CSourcePiece *p_source_piece = mp_generator->GetMasterPiece(single_crc, true);
+			
+			GridDims area(0, 0, 0);
+			p_source_piece->GetCellDims(&area);
+			Script::CArray *p_pos_array = NULL;
+			if (p_entry->GetArray("pos", &p_pos_array))
+			{
+				area.SetXYZ(p_pos_array->GetInteger(0), p_pos_array->GetInteger(1), p_pos_array->GetInteger(2));
+			}
+
+			p_meta->add_piece(p_source_piece, &area);
+
+			// if just a singular meta, use the name checksum already associated with single piece
+			p_meta->m_name_checksum = p_source_piece->GetType();
+			Dbg_Assert(add_later_count < 1000);
+			add_later_tab[add_later_count++] = p_meta->m_name_checksum;			
+			p_meta->set_flag(CMetaPiece::mSINGULAR);
+			
+			// Finally, Check to see if it is a special type
+			// and add it to the table of special types: 
+			
+			uint32 special_type;
+			if (p_entry->GetChecksum(CRCD(0xf176a9fd,"special_type"), &special_type))
+			{
+
+				// gap piece detection here ----- need to reinstate this when we do gaps				
+				if (special_type == CRCD(0x2813ab8a,"gap_placement"))
+					p_meta->set_flag(CMetaPiece::mGAP_PLACEMENT);
+				if (special_type == CRCD(0xfc26b6f4,"area_selection"))
+					p_meta->set_flag(CMetaPiece::mAREA_SELECTION);
+				if (special_type == CRCD(0xffd81c08,"rail_placement"))
+					p_meta->set_flag(CMetaPiece::mRAIL_PLACEMENT);
+				
+				if (special_type == CRCD(0x48fa9c7b,"restart_1"))
+					SetRestartTypeId(CParkGenerator::vONE_PLAYER, single_crc);
+				if (special_type == CRCD(0x419183b2,"restart_multi"))
+					SetRestartTypeId(CParkGenerator::vMULTIPLAYER, single_crc);
+				if (special_type == CRCD(0xe69aefaf,"restart_horse"))
+					SetRestartTypeId(CParkGenerator::vHORSE, single_crc);
+				if (special_type == CRCD(0xa091ce25,"king_of_hill"))
+					SetRestartTypeId(CParkGenerator::vKING_OF_HILL, single_crc);
+				
+				if (special_type == CRCD(0x74b0f4e9,"red_flag"))
+				{
+					SetRestartTypeId(CParkGenerator::vRED_FLAG, single_crc);
+					p_meta->set_flag(CMetaPiece::mNOT_ON_GC);
+				}
+				if (special_type == CRCD(0xc893acf8,"green_flag"))
+				{
+					SetRestartTypeId(CParkGenerator::vGREEN_FLAG, single_crc);
+					p_meta->set_flag(CMetaPiece::mNOT_ON_GC);
+				}
+				if (special_type == CRCD(0x40bdfa7e,"blue_flag"))
+				{
+					SetRestartTypeId(CParkGenerator::vBLUE_FLAG, single_crc);
+					p_meta->set_flag(CMetaPiece::mNOT_ON_GC);
+				}
+				if (special_type == CRCD(0x3ae047fc,"yellow_flag"))
+				{
+					SetRestartTypeId(CParkGenerator::vYELLOW_FLAG, single_crc);
+					p_meta->set_flag(CMetaPiece::mNOT_ON_GC);
+				}
+					
+			}
+		}
+		else if (p_entry->GetArray("multiple", &p_multiple_array))
+		{
+			bool contains_risers = false;
+			p_meta->initialize_desc_table(p_multiple_array->GetSize());
+			
+			for (uint j = 0; j < p_multiple_array->GetSize(); j++)
+			{
+				Script::CStruct *p_name_pos = p_multiple_array->GetStructure(j);
+				
+				Script::CComponent *p_name_crc_comp = p_name_pos->GetNextComponent(NULL);
+				Dbg_Assert(p_name_crc_comp->mType == ESYMBOLTYPE_NAME);				
+				uint32 name_crc = p_name_crc_comp->mChecksum;
+
+				Script::CArray *p_pos_array = NULL;
+				p_name_pos->GetArray(NONAME, &p_pos_array, Script::ASSERT);
+				
+				CSourcePiece *p_source_piece = mp_generator->GetMasterPiece(name_crc, true);
+				Dbg_Assert(add_later_count < 1000);
+				add_later_tab[add_later_count++] = name_crc;
+				
+				GridDims area;
+				p_source_piece->GetCellDims(&area);
+				area.SetXYZ(p_pos_array->GetInteger(0), p_pos_array->GetInteger(1), p_pos_array->GetInteger(2));
+	
+				Mth::ERot90 rot = Mth::ROT_0;
+				if (p_pos_array->GetSize() > 3)
+					rot = (Mth::ERot90) p_pos_array->GetInteger(3);
+				
+				if (p_name_pos->ContainsFlag(CRCD(0xcd75a3f,"riser")))
+					contains_risers = true;
+				p_meta->add_piece(p_source_piece, &area, rot, p_name_pos->ContainsFlag(CRCD(0xcd75a3f,"riser")));
+			} // end for
+
+			if (contains_risers)
+				p_meta->set_flag(CMetaPiece::mCONTAINS_RISERS);
+		}
+		else if (p_entry->GetArray("ack", &p_multiple_array))
+		{
+			p_meta->initialize_desc_table(p_multiple_array->GetSize());
+			
+			for (uint j = 0; j < p_multiple_array->GetSize(); j++)
+			{
+				Script::CStruct *p_name_pos = p_multiple_array->GetStructure(j);
+				
+				Script::CComponent *p_name_crc_comp = p_name_pos->GetNextComponent(NULL);
+				Dbg_Assert(p_name_crc_comp->mType == ESYMBOLTYPE_NAME);				
+				uint32 name_crc = p_name_crc_comp->mChecksum;
+
+				Script::CArray *p_pos_array = NULL;
+				p_name_pos->GetArray(NONAME, &p_pos_array, Script::ASSERT);
+				
+				CAbstractMetaPiece *p_source_meta = GetAbstractMeta(name_crc);
+				
+				GridDims area = p_source_meta->GetArea();
+				area.SetXYZ(p_pos_array->GetInteger(0), p_pos_array->GetInteger(1), p_pos_array->GetInteger(2));
+	
+				Mth::ERot90 rot = Mth::ROT_0;
+				if (p_pos_array->GetSize() > 3)
+					rot = (Mth::ERot90) p_pos_array->GetInteger(3);
+				
+				p_meta->add_piece_dumb(p_source_meta, &area, rot, p_name_pos->ContainsFlag(CRCD(0xcd75a3f,"riser")));
+			} // end for
+		}
+		else
+		{
+			Dbg_MsgAssert(0, ("screwy entry %d in Ed_standard_metapieces", i));
+		}
+		
+		#if 1
+		#ifdef __NOPT_ASSERT__
+		GridDims test_area = p_meta->GetArea();
+		Dbg_MsgAssert(test_area.GetW() > 0 && test_area.GetH() > 0 && test_area.GetL() > 0, 
+					  ("metapiece %s has invalid dimensions (%d,%d,%d)", Script::FindChecksumName(p_meta->GetNameChecksum()),
+					   test_area.GetW(), test_area.GetH(), test_area.GetL()));
+		#if 0
+		if (p_meta->GetNameChecksum() == Script::GenerateCRC("Sk4Ed_Fence_10x10"))
+			printf("OY! metapiece %s has dimensions (%d,%d,%d)\n", Script::FindChecksumName(p_meta->GetNameChecksum()),
+					   test_area.GetW(), test_area.GetH(), test_area.GetL());
+		#endif
+
+		#endif		
+		#endif
+		
+		// the name provided in EdPieces.q, if any, takes precedence
+		if (p_entry->GetChecksum("name", &p_meta->m_name_checksum))
+		{
+			// this metapiece has been renamed, so we must treat it as a non-singular piece
+			p_meta->clear_flag(CMetaPiece::mSINGULAR);
+		}
+		// make sure no piece with checksum currently exists
+		Dbg_MsgAssert(!GetAbstractMeta(p_meta->m_name_checksum), ("name checksum already used for metapiece"));
+
+		if (p_entry->ContainsFlag("is_riser"))
+			p_meta->set_flag(CMetaPiece::mIS_RISER);
+		if (p_entry->ContainsFlag("no_rails"))
+		{
+#			ifdef __NOPT_ASSERT__
+			Ryan("No rails for %s\n", Script::FindChecksumName(p_meta->m_name_checksum));
+#			endif
+			p_meta->set_flag(CMetaPiece::mNO_RAILS);
+		}
+		
+		Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+		CMapListNode *p_node = new CMapListNode();
+		Mem::Manager::sHandle().PopContext();
+		p_node->mp_meta = p_meta;
+		p_node->mp_next = mp_abstract_metapiece_list;
+		mp_abstract_metapiece_list = p_node;
+		m_num_abstract_metapieces++;
+	} // end for
+
+	
+	for (uint i = 0; i < add_later_count; i++)
+	{
+		if (GetAbstractMeta(add_later_tab[i]))
+			continue;
+		
+		Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+		CAbstractMetaPiece *p_meta = new CAbstractMetaPiece();
+		Mem::Manager::sHandle().PopContext();
+		
+		p_meta->initialize_desc_table(1);
+		CSourcePiece *p_source_piece = mp_generator->GetMasterPiece(add_later_tab[i], true);
+
+		GridDims area(0, 0, 0);
+		p_source_piece->GetCellDims(&area);
+		p_meta->add_piece(p_source_piece, &area);
+
+		p_meta->m_name_checksum = add_later_tab[i];
+		p_meta->set_flag(CMetaPiece::mSINGULAR);
+
+		Mem::Manager::sHandle().PushContext(mp_generator->GetParkEditorHeap());
+		CMapListNode *p_node = new CMapListNode();
+		Mem::Manager::sHandle().PopContext();
+		p_node->mp_meta = p_meta;
+		p_node->mp_next = mp_abstract_metapiece_list;
+		mp_abstract_metapiece_list = p_node;
+		m_num_abstract_metapieces++;
+	}
+}
+
+
+
+
+void CParkManager::SetRestartTypeId(CParkGenerator::RestartType type, uint32 id)
+{
+
+	// Mick	
+//	printf ("SetRestartTypeId(%d,%x)\n",type,id);	
+	if (type > CParkGenerator::vEMPTY && type < CParkGenerator::vNUM_RESTART_TYPES)
+	{
+		m_restartPieceIdTab[type] = id;		
+	}
+
+/*	
+	switch(type)
+	{
+		case CParkGenerator::vONE_PLAYER:
+//			printf ("Found vONE_PLAYER\n");
+			m_restartPieceIdTab[0] = id;
+			break;
+		case CParkGenerator::vMULTIPLAYER:
+//			printf ("Found vMULTIPLAYER\n");
+			m_restartPieceIdTab[1] = id;
+			break;
+		case CParkGenerator::vHORSE:
+//			printf ("Found vHORSE\n");
+			m_restartPieceIdTab[2] = id;
+			break;
+		case CParkGenerator::vKING_OF_HILL:
+//			printf ("Found vKING_OF_HILL\n");
+			m_restartPieceIdTab[3] = id;
+			break;
+		default:
+			break;
+	
+	}
+*/
+}
+
+
+
+
+// returns the type of restart piece this is, or vEMPTY is it is not a restart piece
+// (vEMPTY is 0, so the return value can be treated as true/false)
+CParkGenerator::RestartType CParkManager::IsRestartPiece(uint32 id)
+{
+	for (int type = CParkGenerator::vONE_PLAYER; type < CParkGenerator::vNUM_RESTART_TYPES; type++)
+	{
+		if (id == m_restartPieceIdTab[type])
+		{
+			return (CParkGenerator::RestartType) type;
+		}
+	}
+	return CParkGenerator::vEMPTY;
+
+/*
+	if (id == m_restartPieceIdTab[0])
+		return CParkGenerator::vONE_PLAYER;
+	if (id == m_restartPieceIdTab[1])
+		return CParkGenerator::vMULTIPLAYER;
+	if (id == m_restartPieceIdTab[2])
+		return CParkGenerator::vHORSE;
+	if (id == m_restartPieceIdTab[3])
+		return CParkGenerator::vKING_OF_HILL;
+	return CParkGenerator::vEMPTY;
+*/
+}
+
+/*
+Commented out cos I just discovered Destroy() does pretty much the same thing!
+
+// K: This is for freeing up as much memory as possible when playing a park, so that
+// the number of players can be increased. Each player requires 830K.
+void CParkManager::FreeUpMemoryForPlayingPark()
+{
+	// Delete everything in the park editor heap, so that it can be removed, freeing
+	// up 900K.
+	
+	// First delete the cursor.
+	Ed::CParkEditor::Instance()->DeleteCursor();
+	
+	printf("After deleting cursor ...\n");	
+	printf("Num CMapListNode's = %d\n",Ed::CMapListNode::SGetNumUsedItems());
+	MemView_AnalyzeHeap(mp_generator->GetParkEditorHeap());
+
+	mp_generator->DeleteSourceAndClonedPieces();
+	
+	// Delete the concrete metapieces
+	CMapListNode *p_node = mp_concrete_metapiece_list;
+	while(p_node)
+	{
+		CMapListNode *p_next_node = p_node->GetNext();
+		delete p_node->GetConcreteMeta();
+		delete p_node;
+		p_node = p_next_node;
+	}
+	m_num_concrete_metapieces=0;
+	mp_concrete_metapiece_list=NULL;
+
+	printf("After deleting CParkManager::mp_concrete_metapiece_list ...\n");	
+	printf("Num CMapListNode's = %d\n",Ed::CMapListNode::SGetNumUsedItems());
+	MemView_AnalyzeHeap(Ed::CParkManager::sInstance()->GetGenerator()->GetParkEditorHeap());
+
+	// Free up the abstract metapieces.	
+	p_node = mp_abstract_metapiece_list;
+	while(p_node)
+	{
+		CMapListNode *p_next_node = p_node->GetNext();
+		delete p_node->GetAbstractMeta();
+		delete p_node;
+		p_node = p_next_node;
+	}
+	m_num_abstract_metapieces=0;
+	mp_abstract_metapiece_list=NULL;
+
+
+	printf("After deleting CParkManager::mp_abstract_metapiece_list ...\n");	
+	printf("Num CMapListNode's = %d\n",Ed::CMapListNode::SGetNumUsedItems());
+	MemView_AnalyzeHeap(Ed::CParkManager::sInstance()->GetGenerator()->GetParkEditorHeap());
+
+
+	// The park editor heap is now totally empty!
+	// So delete it.
+	mp_generator->DeleteParkEditorHeap();
+	
+	
+	
+	printf("After deleting stuff ...\n");	
+	printf("Num CMapListNode's = %d\n",Ed::CMapListNode::SGetNumUsedItems());
+	MemView_AnalyzeHeap(Ed::CParkManager::sInstance()->GetGenerator()->GetParkEditorHeap());
+}
+*/
+
+// K: Added type so that DESTROY_ONLY_PIECES can be passed when this function is used to clean
+// up the park editor heap for deletion when playing a park.
+void CParkManager::destroy_concrete_metapieces(CParkGenerator::EDestroyType type)
+{
+	ParkEd("CParkManager::destroy_concrete_metapieces()");
+
+	Dbg_Assert(mp_gap_manager);
+	mp_gap_manager->RemoveAllGaps();
+	
+	// Commented these out because destroy_concrete_metapieces gets called via 
+	// ScriptFreeUpMemoryForPlayingPark, which must not delete the edited rails.
+	// (Fix to TT2125)
+	//Obj::GetRailEditor()->DestroyRailGeometry();
+	//Obj::GetRailEditor()->DestroyPostGeometry();
+	
+	// remove all concrete metapieces, except locked ones
+	CMapListNode *p_node = mp_concrete_metapiece_list;
+	while(p_node)
+	{
+		CMapListNode *p_next_node = p_node->GetNext();
+		DestroyConcreteMeta(p_node->GetConcreteMeta(), mDONT_DESTROY_PIECES_ABOVE);
+		p_node = p_next_node;
+	}
+	
+	mp_generator->DestroyAllClonedPieces(type); // if any are left
+	
+	mp_generator->KillRestarts();		// This should have been taken care of by destrying the actual concrete metas, but just to be safe....
+}
+
+
+#ifdef __PLAT_NGC__
+void CParkManager::SwapMapBufferEndianness()
+{
+	CompressedMapHeader *p_header = (CompressedMapHeader *)mp_compressed_map_buffer;
+	
+	p_header->mChecksum=_32(p_header->mChecksum);
+	p_header->mSizeInBytes=_16(p_header->mSizeInBytes);
+	p_header->mVersion=_16(p_header->mVersion);
+	p_header->mTheme=_16(p_header->mTheme);
+	p_header->mParkSize=_16(p_header->mParkSize);
+	p_header->mNumMetas=_16(p_header->mNumMetas);
+	p_header->mNumGaps=_16(p_header->mNumGaps);
+	p_header->mTODScript=_32(p_header->mTODScript);
+	
+	uint32 *p_metas=(uint32*)((uint8*)(p_header+1) + FLOOR_MAX_WIDTH * FLOOR_MAX_LENGTH);
+	for (int i=0; imNumMetas; ++i)
+	{
+		p_metas[0]=_32(p_metas[0]);
+		p_metas[1]=_32(p_metas[1]);
+		p_metas+=2;
+	}
+	
+	CompressedGap *p_gap=(CompressedGap*)p_metas;
+	for (int i=0; imNumGaps; ++i)
+	{
+		p_gap->score=_16(p_gap->score);
+		p_gap->mCancelFlags=_32(p_gap->mCancelFlags);
+		++p_gap;
+	}
+}
+#endif
+
+/*
+Park editor map file format:
+
+Header
+-size
+-version
+-theme
+-X, Z, W, L
+-number of metas
+
+Floor height array, each entry:
+-height
+-texture
+(included risers taken care of by placed metapieces)
+
+Meta entries:
+-10 bits: index
+-8 bits: X
+-8 bits: Z
+-6 bits: padding
+-2 bits: rot
+-5 bits: Y
+-1 bit: padding
+(total: 40)
+
+Gap info
+
+User-defined stamps
+-number contained
+	-source meta
+	-8 bits: X
+	-8 bits: Z
+	-6 bits: Y
+	-2 bits: rot
+*/
+
+void CParkManager::write_compressed_map_buffer()
+{
+	Dbg_Assert(m_state_on);
+	ParkEd("CParkManager::write_compressed_map_buffer()");	
+	//DumpUnwindStack( 20, 0 );
+	
+	if (m_compressed_map_flags & mIS_NEWER_THAN_PARK)
+		// we just loaded this park, so don't touch it
+		return;
+	
+	for (int i=0; iGetSize();
+	
+	CompressedMapHeader *p_header = (CompressedMapHeader *) mp_compressed_map_buffer;
+	p_header->mVersion = (VERSION);
+	p_header->mTheme = (m_theme);
+	p_header->mParkSize = (0);
+	p_header->mX = m_park_near_bounds.GetX();
+	p_header->mZ = m_park_near_bounds.GetZ();
+	p_header->mW = m_park_near_bounds.GetW();
+	p_header->mL = m_park_near_bounds.GetL();
+	p_header->mTODScript = Ed::CParkEditor::Instance()->GetTimeOfDayScript();
+	p_header->mMaxPlayers=GetGenerator()->GetMaxPlayers();
+	Dbg_MsgAssert(strlen(mp_park_name) < 64,("Park name '%s' too long",mp_park_name));
+	strcpy(p_header->mParkName,mp_park_name);
+
+	CompressedFloorEntry *p_floor_entry = (CompressedFloorEntry *) (p_header + 1);		
+	for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+	{
+		for (int j = 0; j < FLOOR_MAX_LENGTH; j++)
+		{
+			p_floor_entry->mHeight = (sint8) m_floor_height_map[i][j].mHeight;
+			p_floor_entry++;
+		}
+	}
+
+	uint8 *p_meta_entry = (uint8 *) p_floor_entry;
+
+	int num_metas_outputted = 0;
+	CMapListNode *p_meta_node = mp_concrete_metapiece_list;
+	while(p_meta_node)
+	{
+		CConcreteMetaPiece *p_meta = p_meta_node->GetConcreteMeta();
+		if (!p_meta->IsRiser() && !p_meta->IsInnerShell() && p_meta->IsInPark())
+		{
+			uint32 *p_meta_entry_first = (uint32 *) p_meta_entry;
+			uint32 *p_meta_entry_second = (uint32 *) (p_meta_entry + 4);
+
+			GridDims meta_area = p_meta->GetArea();
+			
+			uint32 metapiece_index = 0;
+			uint32 metapiece_name_checksum = p_meta->GetNameChecksum();
+			for (; metapiece_index < save_map_array_size; metapiece_index++)
+			{	
+				if (p_save_map_array->GetChecksum(metapiece_index) == metapiece_name_checksum)
+					break;
+			}
+			
+			if (metapiece_index < save_map_array_size)
+			{			
+				*p_meta_entry_first = metapiece_index & 1023;
+				*p_meta_entry_first |= meta_area.GetX()	<< 10;
+				*p_meta_entry_first |= meta_area.GetZ()	<< 18;
+	
+				*p_meta_entry_second = p_meta->GetRot() & 3;
+				*p_meta_entry_second |= ((meta_area.GetY() + 16) & 31) << 2;
+				
+				*p_meta_entry_first = (*p_meta_entry_first);
+				*p_meta_entry_second = (*p_meta_entry_second);
+				//Ryan("compressed piece: 0x%x 0x%x\n", *p_meta_entry_first, *p_meta_entry_second);
+				
+				p_meta_entry += 8;
+				num_metas_outputted++;
+			}
+		}
+
+		p_meta_node = p_meta_node->GetNext();
+	}
+
+	int buffer_used_size = (uint32) p_meta_entry - (uint32) p_header;
+	Dbg_MsgAssert(buffer_used_size <= COMPRESSED_MAP_SIZE, ("compressed map buffer (%d) needs to be bigger than (%d)",COMPRESSED_MAP_SIZE,buffer_used_size));
+	p_header->mNumMetas = (num_metas_outputted);	
+	
+	/* Gap Section */
+
+	// count number of gaps
+	CGapManager::GapDescriptor *p_desc_tab[CGapManager::vMAX_GAPS];
+	int num_gaps = 0;
+	int gap;
+	for (gap = 0; gap < CGapManager::vMAX_GAPS; gap++)
+	{
+		int half = 0;
+		CGapManager::GapDescriptor *pDesc = mp_gap_manager->GetGapDescriptor(gap, &half);
+		if (pDesc && half == 0 && pDesc->numCompleteHalves == 2)
+			p_desc_tab[num_gaps++] = pDesc;
+	}
+	p_header->mNumGaps = (num_gaps);
+	
+	// write gaps
+	CompressedGap *p_out_gap = (CompressedGap *) p_meta_entry;
+	for (gap = 0; gap < num_gaps; gap++)
+	{
+		p_out_gap->rot = 0;
+		for (int half = 0; half < 2; half++)
+		{
+			p_out_gap->x[half] = p_desc_tab[gap]->loc[half].GetX(); 
+			p_out_gap->y[half] = p_desc_tab[gap]->loc[half].GetY() + (MAX_HEIGHT>>1); 
+			p_out_gap->z[half] = p_desc_tab[gap]->loc[half].GetZ(); 
+			p_out_gap->rot |= p_desc_tab[gap]->rot[half]<<(half*4);
+			
+			p_out_gap->extent[half] = (uint8) ((p_desc_tab[gap]->leftExtent[half] & 15) << 4);
+			p_out_gap->extent[half] |= (uint8) (p_desc_tab[gap]->rightExtent[half] & 15);
+			//Ryan("adding gap at (%d,%d,%d)\n", p_out_gap->x[half], p_out_gap->y[half], p_out_gap->z[half]);
+		}
+		Dbg_MsgAssert(strlen(p_desc_tab[gap]->text) <= 31, ("too many characters in gap name %s", p_desc_tab[gap]->text));
+		strcpy(p_out_gap->text, p_desc_tab[gap]->text);
+		p_out_gap->score = ((uint16) p_desc_tab[gap]->score);
+		
+		p_out_gap->mCancelFlags=p_desc_tab[gap]->mCancelFlags;
+		
+		p_out_gap++;
+	}
+  
+	buffer_used_size = (uint32) p_out_gap - (uint32) p_header;
+	Dbg_MsgAssert(buffer_used_size <= COMPRESSED_MAP_SIZE, ("compressed map buffer (%d) needs to be bigger than (%d)",COMPRESSED_MAP_SIZE,buffer_used_size));
+	p_header->mSizeInBytes = buffer_used_size;
+	
+	/* Flag section */
+
+	if (!(m_compressed_map_flags & mIN_SYNC_WITH_PARK) && !(m_compressed_map_flags & mIS_NEWER_THAN_PARK))
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mNOT_SAVED_LOCAL);
+	
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | (mIN_SYNC_WITH_PARK + mIS_VALID));
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mIS_NEWER_THAN_PARK);
+
+
+	p_header->mChecksum = (Crc::GenerateCRCCaseSensitive((const char *) mp_compressed_map_buffer + 4, COMPRESSED_MAP_SIZE-4));
+	
+	// XXX
+	ParkEd("end CParkManager::write_compressed_map_buffer()");	
+}
+
+
+// Create a minimal compressed map buffer
+void CParkManager::fake_compressed_map_buffer()
+{
+	ParkEd("CParkManager::fake_compressed_map_buffer()");	
+	
+//	Script::CArray *p_save_map_array = Script::GetArray("Ed_Save_Map", Script::ASSERT);
+//	uint save_map_array_size = p_save_map_array->GetSize();
+	
+	setup_default_dimensions();
+	
+	CompressedMapHeader *p_header = (CompressedMapHeader *) mp_compressed_map_buffer;
+	p_header->mVersion = (VERSION);
+	p_header->mTheme = (m_theme);
+	p_header->mParkSize = (0);
+	p_header->mX = m_park_near_bounds.GetX();
+	p_header->mZ = m_park_near_bounds.GetZ();
+	p_header->mW = m_park_near_bounds.GetW();
+	p_header->mL = m_park_near_bounds.GetL();
+	
+	Ed::CParkEditor *p_editor=Ed::CParkEditor::Instance();
+	if (p_editor)
+	{
+		p_header->mTODScript = p_editor->GetTimeOfDayScript();
+	}
+	else
+	{
+		p_header->mTODScript=0;
+	}	
+	
+	p_header->mMaxPlayers=2;
+	strcpy(p_header->mParkName, "unnamed park");
+
+	CompressedFloorEntry *p_floor_entry = (CompressedFloorEntry *) (p_header + 1);		
+	for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+	{
+		for (int j = 0; j < FLOOR_MAX_LENGTH; j++)
+		{
+			p_floor_entry->mHeight = 0;		// (sint8) m_floor_height_map[i][j].mHeight;
+			p_floor_entry++;
+		}
+	}
+
+	uint8 *p_meta_entry = (uint8 *) p_floor_entry;
+	int num_metas_outputted = 0;
+	int buffer_used_size = (uint32) p_meta_entry - (uint32) p_header;
+	p_header->mNumMetas = num_metas_outputted;	
+	p_header->mChecksum = Crc::GenerateCRCCaseSensitive((const char *) mp_compressed_map_buffer + 4, buffer_used_size - 4);
+	int num_gaps = 0;
+	p_header->mNumGaps = num_gaps;
+	
+	// write gaps
+	CompressedGap *p_out_gap = (CompressedGap *) p_meta_entry;
+	buffer_used_size = (uint32) p_out_gap - (uint32) p_header;
+	Dbg_MsgAssert(buffer_used_size <= COMPRESSED_MAP_SIZE, ("compressed map buffer (%d) needs to be bigger than (%d)",COMPRESSED_MAP_SIZE,buffer_used_size));
+	p_header->mSizeInBytes = buffer_used_size;
+	
+	/* Flag section */
+
+	if (!(m_compressed_map_flags & mIN_SYNC_WITH_PARK) && !(m_compressed_map_flags & mIS_NEWER_THAN_PARK))
+		m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mNOT_SAVED_LOCAL);
+	
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | (mIN_SYNC_WITH_PARK + mIS_VALID));
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mIS_NEWER_THAN_PARK);
+	
+	// XXX
+	ParkEd("end CParkManager::fake_compressed_map_buffer()");	
+}
+
+
+
+
+void CParkManager::read_from_compressed_map_buffer()
+{
+	Dbg_Assert(m_state_on);
+	// XXX
+	ParkEd("CParkManager::read_from_compressed_map_buffer()");	
+	
+	Script::CArray *p_save_map_array = Script::GetArray("Ed_Save_Map", Script::ASSERT);
+	uint32 dead_piece_checksum = Script::GenerateCRC("Sk4Ed_Dead");
+	
+	Dbg_MsgAssert(m_compressed_map_flags & mIS_VALID, ("compressed map is invalid"));
+	
+	CompressedMapHeader *p_header = (CompressedMapHeader *) mp_compressed_map_buffer;
+	// make sure latest version
+	if (p_header->mVersion != VERSION)
+	{
+		read_from_compressed_map_buffer_old();
+		GetGenerator()->SetMaxPlayers(1);
+		return;
+	}
+
+	m_park_near_bounds[X] = p_header->mX;
+	m_park_near_bounds[Z] = p_header->mZ;
+	m_park_near_bounds[W] = p_header->mW;
+	m_park_near_bounds[L] = p_header->mL;
+	m_theme = p_header->mTheme;
+	Ed::CParkEditor::Instance()->SetTimeOfDayScript(p_header->mTODScript);
+	#ifdef __PLAT_NGC__
+	p_header->mMaxPlayers=2;
+	#endif
+	GetGenerator()->SetMaxPlayers(p_header->mMaxPlayers);
+	// SG: Was setting to the incorrect number (8 when it should have been 2). I think
+	// it is redundant anyway as max players is set elsewhere already
+	//GameNet::Manager::Instance()->SetMaxPlayers(p_header->mMaxPlayers);
+	
+	CompressedFloorEntry *p_floor_entry = (CompressedFloorEntry *) (p_header + 1);		
+	for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+	{
+		for (int j = 0; j < FLOOR_MAX_LENGTH; j++)
+		{
+			m_floor_height_map[i][j].mHeight = (int) p_floor_entry->mHeight;
+			m_floor_height_map[i][j].mEnclosedFloor = 0;
+			p_floor_entry++;
+		}
+	}
+
+	uint8 *p_meta_entry = (uint8 *) p_floor_entry;
+	Dbg_Printf( "****** NUM METAS WHILE READING FROM BUFFER : %d\n", p_header->mNumMetas );
+	for (int i = 0; i < p_header->mNumMetas; i++)
+	{
+		uint32 *p_meta_entry_first = (uint32 *) p_meta_entry;
+		uint32 *p_meta_entry_second = (uint32 *) (p_meta_entry + 4);
+		uint32 meta1 = *p_meta_entry_first;
+		uint32 meta2 = *p_meta_entry_second;
+		
+		//Ryan("compressed piece: 0x%x 0x%x\n", *p_meta_entry_first, *p_meta_entry_second);
+		
+		uint32 metapiece_index = meta1 & 1023;
+		uint32 metapiece_checksum = p_save_map_array->GetChecksum(metapiece_index);
+
+		if (metapiece_checksum != dead_piece_checksum)
+		{
+			CAbstractMetaPiece *p_source_meta = GetAbstractMeta(metapiece_checksum);
+			Dbg_MsgAssert(p_source_meta, ("couldn't find abstract meta %s", Script::FindChecksumName(metapiece_checksum)));
+			#ifdef __PLAT_NGC__
+			if (!(p_source_meta->m_flags & CMetaPiece::mNOT_ON_GC))			
+			#endif
+			{			
+				CConcreteMetaPiece *p_meta = CreateConcreteMeta(p_source_meta);
+				GridDims pos;
+				pos[X] = (meta1 >> 10) & 255;
+				pos[Z] = (meta1 >> 18) & 255;
+				pos[Y] = ((meta2 >> 2) & 31) - 16;
+				int rot = meta2 & 3;
+				p_meta->SetRot(Mth::ERot90(rot));
+				AddMetaPieceToPark(pos, p_meta);
+			}
+		}
+		
+		p_meta_entry += 8;
+	}	
+	
+	/* Gap section */
+	
+	CompressedGap *p_in_gap = (CompressedGap *) p_meta_entry;
+	for (int g = 0; g < p_header->mNumGaps; g++)
+	{
+		CGapManager::GapDescriptor desc;
+		
+		for (int half = 0; half < 2; half++)
+		{
+			desc.loc[half][X] = p_in_gap->x[half]; 
+			desc.loc[half][Y] = p_in_gap->y[half] - (MAX_HEIGHT>>1); 
+			desc.loc[half][Z] = p_in_gap->z[half]; 
+			desc.rot[half] = (p_in_gap->rot>>(half*4)) & 15;
+			
+			desc.leftExtent[half] = (p_in_gap->extent[half]>>4) & 15;
+			desc.rightExtent[half] = (p_in_gap->extent[half]) & 15;
+			//Ryan("adding gap at (%d,%d,%d)\n", p_in_gap->x[half], p_in_gap->y[half], p_in_gap->z[half]);
+		}
+		Dbg_Assert(strlen(p_in_gap->text) < 32);
+		strcpy(desc.text, p_in_gap->text);
+		desc.score = (int) p_in_gap->score;
+		desc.numCompleteHalves = 2;
+		desc.mCancelFlags=p_in_gap->mCancelFlags;
+		
+		AddGap(desc);
+		
+		p_in_gap++;
+	}
+	
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mIN_SYNC_WITH_PARK);
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mIS_NEWER_THAN_PARK);
+	
+	// XXX
+	ParkEd("end CParkManager::read_from_compressed_map_buffer()");	
+}
+
+
+
+void CParkManager::read_from_compressed_map_buffer_old()
+{
+	Dbg_Assert(m_state_on);
+	// XXX
+	ParkEd("CParkManager::read_from_compressed_map_buffer()");	
+	
+	Script::CArray *p_save_map_array = Script::GetArray("Ed_Save_Map", Script::ASSERT);
+	uint32 dead_piece_checksum = Script::GenerateCRC("Sk4Ed_Dead");
+	
+	Dbg_MsgAssert(m_compressed_map_flags & mIS_VALID, ("compressed map is invalid"));
+	
+	CompressedMapHeader *p_header = (CompressedMapHeader *) mp_compressed_map_buffer;
+
+	// make sure proper version
+	Dbg_MsgAssert((p_header->mVersion) == VERSION-1, ("can't read park with this version #: %d", (p_header->mVersion)));
+	
+	// XXX
+//	for (int zzz = 0; zzz < 16; zzz++)
+//		printf("OUT OF DATE PARK FORMAT -- SAVE NOW. OUT OF DATE PARK FORMAT -- SAVE NOW. OUT OF DATE PARK FORMAT -- SAVE NOW.\n");
+
+	m_park_near_bounds[X] = p_header->mX;
+	m_park_near_bounds[Z] = p_header->mZ;
+	m_park_near_bounds[W] = p_header->mW;
+	m_park_near_bounds[L] = p_header->mL;
+	m_theme = (p_header->mTheme);
+	Ed::CParkEditor::Instance()->SetTimeOfDayScript(p_header->mTODScript);
+	
+	GetGenerator()->SetMaxPlayers(p_header->mMaxPlayers);
+	// SG: Was setting to the incorrect number (8 when it should have been 2). I think
+	// it is redundant anyway as max players is set elsewhere already
+	//GameNet::Manager::Instance()->SetMaxPlayers(p_header->mMaxPlayers);
+	
+	CompressedFloorEntry *p_floor_entry = (CompressedFloorEntry *) (p_header + 1);		
+	for (int i = 0; i < FLOOR_MAX_WIDTH; i++)
+	{
+		for (int j = 0; j < FLOOR_MAX_LENGTH; j++)
+		{
+			m_floor_height_map[i][j].mHeight = (int) p_floor_entry->mHeight;
+			m_floor_height_map[i][j].mEnclosedFloor = 0;
+			p_floor_entry++;
+		}
+	}
+
+	uint8 *p_meta_entry = (uint8 *) p_floor_entry;
+	Dbg_Printf( "****** NUM METAS WHILE READING FROM BUFFER : %d\n", p_header->mNumMetas );
+	for (int i = 0; i < (p_header->mNumMetas); i++)
+	{
+		uint32 *p_meta_entry_first = (uint32 *) p_meta_entry;
+		uint32 *p_meta_entry_second = (uint32 *) (p_meta_entry + 4);
+		uint32 meta1 = (*p_meta_entry_first);
+		uint32 meta2 = (*p_meta_entry_second);
+		
+		//Ryan("compressed piece: 0x%x 0x%x\n", *p_meta_entry_first, *p_meta_entry_second);
+		
+		uint32 metapiece_index = meta1 & 1023;
+		uint32 metapiece_checksum = p_save_map_array->GetChecksum(metapiece_index);
+
+		if (metapiece_checksum != dead_piece_checksum)
+		{
+			CAbstractMetaPiece *p_source_meta = GetAbstractMeta(metapiece_checksum);
+			Dbg_MsgAssert(p_source_meta, ("couldn't find abstract meta %s", Script::FindChecksumName(metapiece_checksum)));
+			#ifdef __PLAT_NGC__
+			if (!(p_source_meta->m_flags & CMetaPiece::mNOT_ON_GC))			
+			#endif
+			{			
+				CConcreteMetaPiece *p_meta = CreateConcreteMeta(p_source_meta);
+				GridDims pos;
+				pos[X] = (meta1 >> 10) & 255;
+				pos[Z] = (meta1 >> 18) & 255;
+				pos[Y] = ((meta2 >> 2) & 31) - 16;
+				int rot = meta2 & 3;
+				p_meta->SetRot(Mth::ERot90(rot));
+				AddMetaPieceToPark(pos, p_meta);
+			}
+		}
+		
+		p_meta_entry += 8;
+	}	
+	
+	// Gap section
+	
+	CompressedGapOld *p_in_gap = (CompressedGapOld *) p_meta_entry;
+	for (int g = 0; g < (p_header->mNumGaps); g++)
+	{
+		CGapManager::GapDescriptor desc;
+		
+		for (int half = 0; half < 2; half++)
+		{
+			desc.loc[half][X] = p_in_gap->x[half]; 
+			desc.loc[half][Y] = p_in_gap->y[half] - (MAX_HEIGHT>>1); 
+			desc.loc[half][Z] = p_in_gap->z[half]; 
+			desc.rot[half] = (p_in_gap->rot>>(half*4)) & 15;
+			
+			desc.leftExtent[half] = (p_in_gap->extent[half]>>4) & 15;
+			desc.rightExtent[half] = (p_in_gap->extent[half]) & 15;
+			//Ryan("adding gap at (%d,%d,%d)\n", p_in_gap->x[half], p_in_gap->y[half], p_in_gap->z[half]);
+		}
+		Dbg_Assert(strlen(p_in_gap->text) < 32);
+		strcpy(desc.text, p_in_gap->text);
+		desc.score = (int) (p_in_gap->score);
+		desc.numCompleteHalves = 2;
+		
+		AddGap(desc);
+		
+		p_in_gap++;
+	}
+	
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags | mIN_SYNC_WITH_PARK);
+	m_compressed_map_flags = ECompressedMapFlags(m_compressed_map_flags & ~mIS_NEWER_THAN_PARK);
+	
+	// XXX
+	ParkEd("end CParkManager::read_from_compressed_map_buffer_old()");	
+}
+
+void	CParkManager::setup_default_dimensions()
+{
+	int default_inner_dim = Script::GetInt("ed_default_inner_dim", Script::ASSERT);
+	Dbg_Assert(default_inner_dim <= MAX_WIDTH && default_inner_dim <= MAX_LENGTH);
+
+	m_park_far_bounds.SetXYZ(1, -4, 1);
+	m_park_far_bounds.SetWHL(MAX_WIDTH, 8, MAX_LENGTH);
+	m_park_near_bounds.SetXYZ(1 + (MAX_WIDTH - default_inner_dim) / 2, -4, 1 + (MAX_LENGTH - default_inner_dim) / 2);
+	m_park_near_bounds.SetWHL(default_inner_dim, 8, default_inner_dim);
+}
+
+
+
+void CParkManager::setup_road_mask()
+{
+	/*
+		The first four entries in m_road_mask_tab will be set up in RebuildInnerShell()
+	*/
+	
+	Script::CArray *p_road_mask_array = Script::GetArray("Ed_Roadmask", Script::ASSERT);
+	m_road_mask_tab_size = p_road_mask_array->GetSize();
+	for (uint i = 4; i < 4 + m_road_mask_tab_size; i++)
+	{
+		Dbg_Assert(i < NUM_ROAD_PIECES);
+		Script::CArray *p_entry = p_road_mask_array->GetArray(i);
+
+		m_road_mask_tab[i][X] = p_entry->GetInt(0);
+		m_road_mask_tab[i][Z] = p_entry->GetInt(1);
+		m_road_mask_tab[i][W] = p_entry->GetInt(2);
+		m_road_mask_tab[i][L] = p_entry->GetInt(3);
+		m_road_mask_tab[i][Y] = 0;
+		m_road_mask_tab[i].MakeInfinitelyHigh();
+	}
+
+	m_road_mask_tab_size += 4;
+}
+
+
+void CParkManager::setup_lighting()
+{
+	
+	
+	Script::CArray *pThemeArray = Script::GetArray("Editor_Light_Info");
+//	Script::CArray *pSizeArray = pThemeArray->GetArray(m_currentThemeIndex);
+//	Script::CScriptStructure *pLightInfo = pSizeArray->GetStructure(m_currentShellSizeIndex);
+	Script::CArray *pSizeArray = pThemeArray->GetArray(0);
+	Script::CScriptStructure *pLightInfo = pSizeArray->GetStructure(0);
+
+	float amb_const;
+	pLightInfo->GetFloat("ambient_const", &amb_const, true);
+	Mth::Vector ambient_rgb;
+	pLightInfo->GetVector("ambient_rgb", &ambient_rgb, true);
+	float falloff_const;
+	pLightInfo->GetFloat("falloff_const", &falloff_const, true);
+	Mth::Vector falloff_rgb;
+	pLightInfo->GetVector("falloff_rgb", &falloff_rgb, true);
+	float cursor_ambience;
+	pLightInfo->GetFloat("cursor_ambience", &cursor_ambience, true);
+	
+	
+	Script::CArray *pLightArray;
+	pLightInfo->GetArray("pos", &pLightArray, true);
+
+	mp_generator->SetLightProps(pLightArray->GetSize(), 
+							amb_const * ambient_rgb.GetX(), amb_const * ambient_rgb.GetY(), amb_const * ambient_rgb.GetZ(), 
+							falloff_const * falloff_rgb.GetX(), falloff_const * falloff_rgb.GetY(), falloff_const * falloff_rgb.GetZ(),
+							cursor_ambience);
+
+	for (int i = 0; i < (int)pLightArray->GetSize(); i++)
+	{
+		Script::CVector *pInPos = pLightArray->GetVector(i);
+		Mth::Vector outPos;
+		outPos.Set(pInPos->mX * CParkGenerator::CELL_WIDTH,
+				   pInPos->mY * CParkGenerator::CELL_HEIGHT,
+				   pInPos->mZ * CParkGenerator::CELL_LENGTH);
+		mp_generator->SetLight(outPos, i);
+	}
+}
+
+
+
+
+}
diff --git a/Code/Sk/ParkEditor2/EdMap.h b/Code/Sk/ParkEditor2/EdMap.h
new file mode 100644
index 0000000..e95f17b
--- /dev/null
+++ b/Code/Sk/ParkEditor2/EdMap.h
@@ -0,0 +1,745 @@
+#ifndef __SK_PARKEDITOR2_EDMAP_H
+#define __SK_PARKEDITOR2_EDMAP_H
+
+#include 
+#include 
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+//#ifdef	__PLAT_NGPS__
+#define USE_BUILD_LIST
+//#endif
+
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+
+
+namespace Ed
+{
+
+class CPiece;
+class CSourcePiece;
+class CClonedPiece;
+class CParkGenerator;
+class CConcreteMetaPiece;
+class CAbstractMetaPiece;
+
+/*
+	Describes how to incorporate a CPiece or hardcoded metapiece into a CMetaPiece.
+	
+	The position is relative to the NW corner of the piece, in its current rotation.
+	
+	Likewise, the rotation value includes the rotation of the whole metapiece.
+*/
+struct SMetaDescriptor
+{
+	uint8						mX;
+	uint8						mY;
+	uint8						mZ;
+	uint8						mRot;
+	uint8						mRiser; // true if a riser
+	
+	/* 
+		The piece pointer is active within a concrete metapiece, the name in an abstract one.
+		In the latter case, the name refers to a metapiece.
+	*/
+	union
+	{
+		uint32					mPieceName;
+		CPiece *				mpPiece;
+	};
+};
+
+
+
+
+/*
+	What is a metapiece?
+	
+	-Wraps a group of cloned pieces that are in the park (or about to be)
+	
+	-Wraps a group of cloned pieces that make up the cursor. Contains extra position,
+		rotation info.
+	
+	-Wraps a group of cloned pieces that make up a menu item. Refers to a 3D element.
+		
+	-Wraps a descriptor table that tells how to build a full metapiece.
+	
+	Rules:
+	-If metapiece is in world, positions of contained pieces are in world
+	-Otherwise, positions are relative to center bottom of metapiece
+	
+	Other notes:
+	-An abstract user-created metapiece can contain hard-coded metapieces
+	-A concrete metapiece cannot
+*/
+class CMetaPiece
+{
+	friend class CParkManager;
+
+public:
+
+	enum EFlags
+	{
+		mCONCRETE_META			= (1<<0),
+		mABSTRACT_META			= (1<<1),
+
+		mIN_PARK				= (1<<2),
+
+		mIS_RISER				= (1<<3),
+		mMARK_FOR_KILL			= (1<<4),
+		mMARK_AS_SLID			= (1<<5),
+
+		mUSER_CREATED			= (1<<6),
+		mSINGULAR				= (1<<7),
+
+		mINNER_SHELL			= (1<<8),
+
+		mLOCK_AGAINST_DELETE	= (1<<9),
+		mNO_RAILS				= (1<<10),
+
+		mGAP_PLACEMENT			= (1<<11),
+		mGAP_ATTACHED			= (1<<12),
+
+		mNOT_ON_GC				= (1<<13), // this metapiece does not appear on Gamecube
+
+		mCONTAINS_RISERS		= (1<<14),
+		mAREA_SELECTION			= (1<<15),
+		
+		mRAIL_PLACEMENT			= (1<<16),
+	};
+
+	CConcreteMetaPiece *		CastToConcreteMeta();
+	CAbstractMetaPiece *		CastToAbstractMeta();
+
+	bool						IsRestartOrFlag();
+	bool						IsCompletelyWithinArea(const GridDims &area);
+	
+	
+	virtual CPiece *			GetContainedPiece(int index) = 0;
+
+	void						SetRot(Mth::ERot90 rot);
+	Mth::Vector 				GetRelativePosOfContainedPiece(int index);
+	void 						BuildElement3dSectorsArray(Script::CArray *pArray);
+
+	/*
+		Returns cell dimensions occupied by metapiece (post-rotation). Coordinates in GridDims structure
+		are not valid in an abstract metapiece, only in a concrete one that's in the park.
+	*/
+	const GridDims &			GetArea() {return m_cell_area;}
+	uint32						GetNameChecksum() {return m_name_checksum;}
+	// Returns rotation of whole metapiece
+	Mth::ERot90					GetRot() {return Mth::ERot90(m_rot);}
+
+	bool						IsInPark() {return m_flags & mIN_PARK;}
+	// Returns true if the WHOLE metapiece is a riser
+	bool 						IsRiser() {return m_flags & mIS_RISER;}
+	bool						IsInnerShell() {return m_flags & mINNER_SHELL;}
+	bool						IsSingular() {return m_flags & mSINGULAR;}
+	bool 						NoRails() {return m_flags & mNO_RAILS;}
+	bool						IsGapPlacement() {return m_flags & mGAP_PLACEMENT;}
+	bool						IsAreaSelection() {return m_flags & mAREA_SELECTION;}
+	bool						IsRailPlacement() {return m_flags & mRAIL_PLACEMENT;}
+	bool 						IsConcrete() {return m_flags & mCONCRETE_META;}
+
+	void						MarkAsInnerShell() {m_flags = EFlags(m_flags | mINNER_SHELL);}
+	void						MarkLocked() {m_flags = EFlags(m_flags | mLOCK_AGAINST_DELETE);}
+	void						MarkUnlocked() {m_flags = EFlags(m_flags & ~mLOCK_AGAINST_DELETE);}
+	
+
+protected:
+								CMetaPiece();
+	virtual						~CMetaPiece();
+
+	void						initialize_desc_table(int numEntries);
+	SMetaDescriptor &			get_desc_at_index(int i);
+
+	/*
+		Adds a CPiece to the metapiece. If the metapiece is abstract, the CPiece must be a CSourcePiece. If the metapiece is
+		concrete, the CPiece must be a CClonedPiece. 
+		
+		pPos specifies the position of the pPiece relative to the northwest corner of the metapiece.
+		
+		isRiser set if pPiece is meant to act as a riser.
+	*/
+	virtual	void				add_piece(CPiece *pPiece, GridDims *pPos, Mth::ERot90 rot = Mth::ROT_0, bool isRiser = false) = 0;
+	
+	void						set_flag(EFlags flag) {m_flags = EFlags(m_flags | flag);}
+	void						clear_flag(EFlags flag) {m_flags = EFlags(m_flags & ~flag);}
+	
+	// takes into account current rotation
+	GridDims					m_cell_area;			//  6 ( 8) + 2
+									
+	EFlags 						m_flags;   				// 	4 (only uses 17 bits, grr)
+	int							m_rot;					//  4 (only uses  2 bits, grr)   +4 (if combined)
+	
+	/* 
+		-See comment above SMetaDescriptor declaration for details on what descriptors are
+		-Most metapieces will only contain one piece, so mp_additional_desc_tab is only used if there are more
+	*/
+
+	enum
+	{
+		USUAL_NUM_DESCRIPTORS =	2
+	};
+	
+	SMetaDescriptor 			m_first_desc_tab[USUAL_NUM_DESCRIPTORS];  //  9 (12), + 9 (12)	  + 6
+	SMetaDescriptor *			mp_additional_desc_tab;	  				  //  4	
+	uint						m_total_entries;						  //  2 (4) 			  + 2
+	uint 						m_num_used_entries;						  //  2 (4) 			  +2
+	
+	uint32						m_name_checksum; 						  //  4
+};
+
+// Total 													
+
+
+
+class CConcreteMetaPiece : public CMetaPiece
+{
+	friend class CParkManager;
+
+public:
+	
+	int							CountContainedPieces();
+	CPiece *					GetContainedPiece(int index);
+
+	void						SetSoftRot(Mth::Vector pos, float rot);
+
+	void						Highlight(bool on, bool gapHighlight = false);
+	void						SetVisibility(bool visible);
+
+protected:
+	
+								CConcreteMetaPiece();
+	
+	void						lock();
+	void						get_max_min_world_coords(Mth::Vector &max, Mth::Vector &min);
+	
+	bool 						test_for_conflict(GridDims area, bool excludeRisers);
+	
+	// see comments with original declaration
+	virtual void				add_piece(CPiece *pPiece, GridDims *pPos, Mth::ERot90 rot = Mth::ROT_0, bool isRiser = false);
+};
+
+
+
+
+class CAbstractMetaPiece : public CMetaPiece
+{
+	friend class CParkManager;
+
+public:
+protected:
+								CAbstractMetaPiece();
+
+	CPiece *					GetContainedPiece(int index);
+	
+	// see comments with original declaration
+	virtual void				add_piece(CPiece *pPiece, GridDims *pPos, Mth::ERot90 rot = Mth::ROT_0, bool isRiser = false);
+	void 						add_piece_dumb(CAbstractMetaPiece *pMeta, GridDims *pPos, Mth::ERot90 rot, bool isRiser);
+
+	SMetaDescriptor 			get_desc_with_expansion(int index);
+	int 						count_descriptors_expanded();
+};
+
+
+
+
+class CMapListNode : public Mem::CPoolable
+{
+	friend class CParkManager;
+	friend class CCursor;
+
+public:
+	CMetaPiece *				GetMeta();
+	CConcreteMetaPiece *		GetConcreteMeta() {return GetMeta()->CastToConcreteMeta();}
+	CAbstractMetaPiece *		GetAbstractMeta() {return GetMeta()->CastToAbstractMeta();}
+	CMapListNode *				GetNext() {return mp_next;}
+	void						DestroyList();
+	void						VerifyIntegrity();
+
+	int							mSlideAmount;
+
+private:
+	CMetaPiece *				mp_meta;
+	CMapListNode *				mp_next;
+};
+
+
+
+
+class CMapListTemp
+{
+public:
+	CMapListTemp(CMapListNode *p_list);
+	~CMapListTemp();
+	
+	bool						IsEmpty() {return (mp_list == NULL);}
+	bool						IsSingular() {return (mp_list->GetNext() == NULL);}
+	CMapListNode *				GetList();
+	void						PrintContents();
+
+private:
+
+	CMapListNode *				mp_list;
+};
+
+
+
+
+class CParkManager
+{
+	DeclareSingletonClass( CParkManager );
+
+public:
+
+	enum
+	{
+		MAX_WIDTH =				56,
+		MAX_LENGTH =			56,
+		MAX_HEIGHT =			16,
+		/* 
+			These next two account for the border around the whole park. Riser pieces must be added when the user
+			digs a hole at the edge of the park.
+		*/
+		FLOOR_MAX_WIDTH =		MAX_WIDTH + 2,
+		FLOOR_MAX_LENGTH =		MAX_LENGTH + 2,
+
+		COMPRESSED_MAP_SIZE		= 15000,		// (Mick) it can acutally be bigger than this, but I've made it this size so network code won't crash
+	};
+
+								CParkManager();
+								~CParkManager();
+
+	/* ======== General Functions ======== */
+
+	CParkGenerator *			GetGenerator() {return mp_generator;}								
+	void						Initialize();
+	bool						IsInitialized() {return m_state_on;}
+	void						AccessDisk(bool save, int fileSlot);
+	void						Destroy(CParkGenerator::EDestroyType type);
+
+	float						GetClipboardProportionUsed();
+	void						WriteCompressedMapBuffer();
+	uint8*						GetCompressedMapBuffer(bool markSaved = false);
+	void						SetCompressedMapBuffer(uint8* src_buffer, bool markNotSaved = false);
+	bool						IsMapSavedLocally();
+	void						WriteIntoStructure(Script::CStruct *p_struct);
+	#ifdef __PLAT_NGC__
+	void 						ReadFromStructure(Script::CStruct *p_struct, bool do_post_mem_card_load=true, bool preMadePark=false);
+	#else
+	void						ReadFromStructure(Script::CStruct *p_struct, bool do_post_mem_card_load=true);
+	#endif
+	
+	int							GetTheme();
+	void						SetTheme(int theme);
+	uint32						GetShellSceneID();
+	const char *				GetParkName();
+	void						SetParkName(const char *pName);
+	uint32						GetParkChecksum();
+	int							GetCompressedParkWidth();
+	int							GetCompressedParkLength();
+
+	/* ======== Rebuild functions ======== */
+
+	void						RebuildWholePark(bool clearPark);	
+	bool						RebuildFloor(bool firstTestMemory = false);
+	void						RebuildNodeArray();
+	void						RebuildInnerShell(GridDims newNearBounds);
+	
+	/* ======== Abstract metapiece functions ======== */
+	
+	CAbstractMetaPiece *		GetAbstractMeta(uint32 type, int *pRetIndex = NULL);
+	CAbstractMetaPiece *		GetAbstractMetaByIndex(int index);
+	
+	/* ======== Concrete metapiece functions ======== */
+	
+	enum EDestroyFlags
+	{
+		mDONT_DESTROY_PIECES_ABOVE		= 0,
+		mDESTROY_PIECES_ABOVE			= (1<<0),
+		mEXCLUDE_RISERS					= (1<<1),
+		mEXCLUDE_PIECES_MARKED_AS_SLID	= (1<<2),
+	};
+	
+	CConcreteMetaPiece *		CreateConcreteMeta(CAbstractMetaPiece *pSource, bool isSoft = false);
+	void						DestroyConcreteMeta(CConcreteMetaPiece *pMeta, EDestroyFlags flags);
+	uint32						DestroyMetasInArea(GridDims area, EDestroyFlags flags);
+	bool						DestroyMetasInArea(GridDims area, bool doNotDestroyRestartsOrFlags=false, bool onlyIfWithinArea=false);
+	void						HighlightMetasInArea(GridDims area, bool on);
+	//GridDims					FindBoundingArea(GridDims area);
+	void						AddMetaPieceToPark(GridDims pos, CConcreteMetaPiece *pPiece);
+	CConcreteMetaPiece *		RelocateMetaPiece(CConcreteMetaPiece *pMeta, int changeX, int changeY, int changeZ);
+	CMapListNode *				GetMetaPiecesAt(GridDims dims, Mth::ERot90 rot = Mth::ROT_0, CAbstractMetaPiece *pPiece = NULL, bool excludeRisers = false);
+	CMapListNode *				GetAreaMetaPieces(GridDims dims);
+	CMapListNode *				GetMapListNode(int x, int z);
+	CMapListNode *				GetConcreteMetaList() {return mp_concrete_metapiece_list;}
+	int 						GetFloorHeight(int x, int z);
+	
+	
+	bool 						AreMetasOutsideBounds(GridDims new_dims);
+	bool 						EnoughMemoryToResize(GridDims new_dims);
+
+	int 						CountGeneralMetas() {return m_num_general_concrete_metapieces;}
+	int							GetDMAPieceCount() {return m_dma_piece_count;}
+	
+	
+	/* ======== Floor height functions ======== */
+	
+	int							GetFloorHeight(GridDims dims, bool *pRetUniformHeight = NULL);
+	#if 0
+	void						SetFloorHeight(GridDims area, int newHeight, bool justDropHighest);
+	#endif
+	void						ResetFloorHeights(GridDims area);
+	
+	bool						SlideColumn(GridDims area, int bottom, int top, bool up, bool uneven);
+	void						UndoSlideColumn();
+	bool						ChangeFloorHeight(GridDims dims, int dir);
+
+	/* ======== Coordinate info functions ======== */
+	
+	const GridDims &			GetParkNearBounds() {return m_park_near_bounds;}
+	const GridDims &			GetParkFarBounds() {return m_park_far_bounds;}
+	Mth::Vector					GridCoordinatesToWorld(const GridDims &cellDims, Mth::Vector *pRetDims = NULL);
+
+	static CParkManager *		sInstance();
+	
+	/* ======== Piece set (group, category) functions ======== */
+	
+	class CPieceEntry
+	{
+	public:
+		uint32									mNameCrc;
+		const char *							mpName;
+	};
+	
+	class CPieceSet
+	{
+	public:
+		enum 
+		{
+			MAX_ENTRIES =						40,
+		};
+		
+		uint32									mNameCrc;
+		const char *							mpName;
+		CPieceEntry 							mEntryTab[MAX_ENTRIES];
+		int										mTotalEntries;
+		int										mSelectedEntry;
+		char									mVisible;
+		char									mIsClipboardSet;
+	};
+
+	enum
+	{
+		MAX_SETS =								40,
+	};
+
+	CPieceSet &					GetPieceSet(int setNumber, int *pMenuSetNumber = NULL);
+	int							GetTotalNumPieceSets() {return m_total_sets;}
+
+	/* ======== Road mask functions ======== */
+	
+	bool						IsOccupiedByRoad(GridDims area);
+	bool						CanPlacePiecesIn(GridDims cellDims, bool ignoreFloorHeight=false);
+
+
+	/* ======== Restart node functions ===== */
+
+	void						SetRestartTypeId(CParkGenerator::RestartType type, uint32 id);
+	CParkGenerator::RestartType	IsRestartPiece(uint32 id);
+
+	/* ======== Gap functions ===== */
+	
+	CGapManager::GapDescriptor *GetGapDescriptor(GridDims &area, int *pHalf);
+	bool 						AddGap(CGapManager::GapDescriptor &descriptor);
+	void 						RemoveGap(CGapManager::GapDescriptor &descriptor);
+	void						RemoveGap(CConcreteMetaPiece *pMeta);
+	void						HighlightMetasWithGaps(bool highlightOn, CGapManager::GapDescriptor *pDesc);
+
+	//void						FreeUpMemoryForPlayingPark();
+	
+	void						GetSummaryInfoFromBuffer(uint8 *p_buffer, int *p_numGaps, int *p_numMetas, uint16 *p_theme, uint32 *p_todScript, int *p_width, int *p_length);
+	
+	#ifdef __PLAT_NGC__
+	void						SwapMapBufferEndianness();
+	#endif
+	
+protected:
+
+	/* ======== Key members ======== */
+	
+	CParkGenerator *			mp_generator;
+	bool						m_state_on;
+	bool						m_park_is_valid;
+
+	/* ======== Riser/floor related functions ======== */
+	
+	enum EFloorFlags
+	{
+		NONE =					0,
+		mHAS_BOTTOM =			(1<<0),
+		mHAS_TOP =				(1<<1),
+		mNO_COVER =				(1<<2),
+	};
+	
+	struct BuildFloorParams
+	{
+		/* for build_add_floor_piece() */
+		int 					x, y, z;
+		EFloorFlags 			flags;
+		int 					heightSlotOffset;
+		/* for output_riser_stack() */
+		int						bottom;
+		int						top;
+		bool					simpleBuild;
+		bool					dryRun;
+		int						addCount; 	// counts metapieces
+		int						addCount2;	// counts contained pieces
+	};
+
+	void						output_riser_stack(BuildFloorParams ¶ms);
+	void						build_add_floor_piece(BuildFloorParams ¶ms);
+	void						build_add_floor_piece_simple(BuildFloorParams ¶ms);
+	void						apply_meta_contained_risers_to_floor(CConcreteMetaPiece *pMetaPiece, bool add);
+	
+	CMapListNode *				mp_column_slide_list;
+	int							m_num_metas_killed_in_slide;
+	int							m_num_pieces_killed_in_slide;
+		
+	enum ESlideColumnFlags
+	{
+		mUNEVEN =				(1<<0),
+		mUP	=					(1<<1),
+		mFIRST_RECURSE =		(1<<2),
+	};
+	bool 						intersection_with_riser_containing_metas(GridDims area);
+	bool						slide_column_part_one(GridDims area, int bottom, int top, ESlideColumnFlags flags, CMapListNode **ppMoveList);
+	void						fix_slide_column_part_one_failure();
+	void						slide_column_part_two(CMapListNode *pMoveList);
+	void						kill_pieces_in_layer_under_area(GridDims area);
+	void						finish_slide_column();
+	bool						generate_floor_pieces_from_height_map(bool simpleBuild, bool dryRun);
+
+	void						increment_meta_count(uint32 pieceChecksum);
+	void						decrement_meta_count(uint32 pieceChecksum);
+	int							get_dma_usage(uint32 pieceChecksum);
+
+	enum
+	{
+		NUM_RISER_INFO_SLOTS = 	8,
+	};
+	
+	uint32						m_riser_piece_checksum[NUM_RISER_INFO_SLOTS][3];
+
+	#ifdef USE_BUILD_LIST
+	struct SBuildListEntry
+	{
+		uint32 mType;
+		GridDims mPos;
+	};	
+	int							m_build_list_size;
+	SBuildListEntry				*mp_build_list_entry;
+	void						create_metas_in_build_list();
+	#endif
+	
+	/* ======== metapiece management stuff ======== */
+	
+	CMapListNode *				mp_concrete_metapiece_list;
+	CMapListNode *				mp_abstract_metapiece_list;
+	int 						m_num_concrete_metapieces;
+	int							m_num_general_concrete_metapieces; // concrete metapieces not including riser, shell metapieces. Must be in park.
+	int 						m_num_abstract_metapieces;
+	// The DMA piece count counts pieces weighted according to their contribution to DMA usage.
+	// Some pieces contribute more than 1, eg the lava piece, and any piece that has wibbling.
+	// Needed so that the gauge can take into account DMA usage and not allow DMA overflow.
+	int							m_dma_piece_count;
+	CMapListNode * 				mp_bucket_list[FLOOR_MAX_WIDTH][FLOOR_MAX_LENGTH];
+
+	void 						add_metapiece_to_node_list(CMetaPiece *pPieceToAdd, CMapListNode **ppList);
+	void 						remove_metapiece_from_node_list(CMetaPiece *pPieceToRemove, CMapListNode **ppList);
+	void						bucketify_metapiece(CConcreteMetaPiece *pPiece);
+	void						debucketify_metapiece(CConcreteMetaPiece *pPiece);
+		
+	void						create_abstract_metapieces();
+	void						destroy_concrete_metapieces(CParkGenerator::EDestroyType type);
+
+	/* ======== Floor map/park bounds stuff ======== */
+	
+	int							m_theme;
+	
+	/*
+		Within the 256X256 "super map"
+		
+		X and Z are positive -- must subtract 128 to get true world cell coordinates
+	*/
+	GridDims					m_park_near_bounds;
+	GridDims					m_park_far_bounds;
+
+	enum
+	{
+		TYPE_EMPTY_BLOCK		= 0,
+		TYPE_FLOOR_BLOCK		= 0xaec511eb,
+		TYPE_FLOOR_WALL_BLOCK	= 0x829cfc8d,
+		TYPE_WALL_BLOCK			= 0x304bd5f7,
+	};
+	
+	struct FloorHeightEntry
+	{
+		int						mHeight;
+		/*
+			Bit 0 = -16Y, 31 = 15Y
+			
+			Each bit set true of corresponding riser is part of metapiece at that position.
+		*/
+		uint32					mEnclosedFloor;
+		bool					mMarkAsSlid;
+		int						mSlideAmount;
+	};
+
+	FloorHeightEntry **			m_floor_height_map;
+
+	/* ======== piece set stuff ======== */
+	
+	CPieceSet					m_palette_set[MAX_SETS];
+	int							m_total_sets;
+
+	/* ======== map buffer access stuff ======== */
+	
+	struct CompressedMapHeader
+	{
+		uint32					mChecksum; // CRC of every byte in file except these 4
+		uint16					mSizeInBytes;
+		uint16					mVersion;
+		uint16					mTheme;
+		uint16					mParkSize;
+		uint8 					mX;
+		uint8					mZ;
+		uint8					mW;
+		uint8					mL;
+		uint16					mNumMetas;
+		uint16					mNumGaps;
+		uint8					mMaxPlayers;
+		uint32					mTODScript;
+		char					mParkName[64];
+	};
+
+	struct CompressedMapHeaderOld
+	{
+		uint32					mChecksum; // CRC of every byte in file except these 4
+		uint16					mSizeInBytes;
+		uint16					mVersion;
+		uint16					mTheme;
+		uint16					mParkSize;
+		uint8 					mX;
+		uint8					mZ;
+		uint8					mW;
+		uint8					mL;
+		uint16					mNumMetas;
+		uint16					mNumGaps;
+		char					mParkName[64];
+	};
+
+	struct CompressedFloorEntry
+	{
+		sint8					mHeight;
+	};
+
+	// K: To allow loading of older parks that do not have gap cancel-flag info 
+	struct CompressedGapOld
+	{
+		uint8					x[2];
+		uint8					y[2];
+		uint8					z[2];
+		// bits 0-3 left extent, 4-7 right
+		uint8					extent[2];
+		// bits 0-3 first rot, 4-7 second
+		uint8					rot;
+		uint8					flags;
+		char					text[32];
+		uint16					score;
+	};
+	
+	struct CompressedGap
+	{
+		uint8					x[2];
+		uint8					y[2];
+		uint8					z[2];
+		// bits 0-3 left extent, 4-7 right
+		uint8					extent[2];
+		// bits 0-3 first rot, 4-7 second
+		uint8					rot;
+		uint8					flags;
+		char					text[32];
+		uint16					score;
+		
+		uint32					mCancelFlags;
+	};
+	
+	enum
+	{   
+		VERSION					= 20004,
+	};
+	uint8 *						mp_compressed_map_buffer;
+	
+	enum ECompressedMapFlags
+	{
+		mNO_FLAGS				= 0,
+		mIN_SYNC_WITH_PARK		= (1<<0),	// cleared if park hasn't been built yet
+		mIS_VALID				= (1<<1),
+		mIS_NEWER_THAN_PARK		= (1<<2),	// also set if park hasn't been built yet
+
+		mNOT_SAVED_LOCAL		= (1<<3),
+	};
+	ECompressedMapFlags			m_compressed_map_flags;
+
+	void						fake_compressed_map_buffer();
+	void						write_compressed_map_buffer();
+	void						read_from_compressed_map_buffer();
+	void 						read_from_compressed_map_buffer_old();
+
+	void						setup_default_dimensions();
+	
+	char						mp_park_name[64];
+	
+	/* ======== Road mask section ======== */
+
+	enum
+	{
+		NUM_ROAD_PIECES			= 8,
+	};
+	GridDims					m_road_mask_tab[NUM_ROAD_PIECES];
+	uint						m_road_mask_tab_size;
+
+	void						setup_road_mask();
+
+	/* ======== Restart Node section ======= */
+
+	uint32						m_restartPieceIdTab[CParkGenerator::vNUM_RESTART_TYPES];	 // Table of checksums of the names of restart pieces
+
+
+	/* ======== Lights Section ============= */
+	void						setup_lighting();
+
+
+	/* ======== Gap Section ============= */
+	
+	CGapManager	*				mp_gap_manager;
+	
+	static CParkManager *		sp_instance;
+};
+
+
+
+
+}
+
+#endif // __SK_PARKEDITOR2_EDMAP_H
+
diff --git a/Code/Sk/ParkEditor2/GapManager.cpp b/Code/Sk/ParkEditor2/GapManager.cpp
new file mode 100644
index 0000000..00bcd81
--- /dev/null
+++ b/Code/Sk/ParkEditor2/GapManager.cpp
@@ -0,0 +1,410 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+namespace Ed
+{
+
+
+
+
+CGapManager *CGapManager::sp_instance = NULL;
+
+
+GapInfo::GapInfo()
+{
+	mp_gapPiece[0] 		= NULL;
+	mp_gapPiece[1] 		= NULL;
+	mp_regularMeta[0] 	= NULL;
+	mp_regularMeta[1] 	= NULL;
+	
+	m_descriptor.mCancelFlags=0;
+}
+
+
+
+
+CGapManager::CGapManager(CParkManager *pManager)
+{
+	m_currentId = 2000;
+
+	for (int i = 0; i < vMAX_GAPS; i++)
+		mp_gapInfoTab[i] = NULL;
+
+	mp_manager = pManager;
+	SetInstance();
+}
+
+
+
+
+CGapManager::~CGapManager()
+{
+	// XXX
+	Ryan("in ~CGapManager()\n");
+	RemoveAllGaps();
+	// XXX
+	Ryan("leaving ~CGapManager()\n");
+	sp_instance = NULL;
+}
+
+CGapManager& CGapManager::operator=( const CGapManager& rhs )
+{
+	m_currentId = rhs.m_currentId;
+	
+	for (int i = 0; i < vMAX_GAPS; i++)
+	{
+		if (rhs.mp_gapInfoTab[i])
+		{
+			GapInfo *p_new = new GapInfo;
+			
+			p_new->m_id[0] = rhs.mp_gapInfoTab[i]->m_id[0];
+			p_new->m_id[1] = rhs.mp_gapInfoTab[i]->m_id[1];
+			p_new->mp_gapPiece[0] = NULL;
+			p_new->mp_gapPiece[1] = NULL;
+			p_new->mp_regularMeta[0] = NULL;
+			p_new->mp_regularMeta[1] = NULL;
+			Dbg_MsgAssert(strlen(rhs.mp_gapInfoTab[i]->mp_text) < 128,("Bad source gap text"));
+			strcpy(p_new->mp_text, rhs.mp_gapInfoTab[i]->mp_text);
+			p_new->m_descriptor = rhs.mp_gapInfoTab[i]->m_descriptor;
+			
+			mp_gapInfoTab[i] = p_new;
+		}
+		else
+		{
+			mp_gapInfoTab[i]=NULL;
+		}	
+		
+		m_startOfGap[i] = rhs.m_startOfGap[i];
+	}
+	
+	mp_manager=NULL;
+	
+	return *this;
+}
+
+
+
+int CGapManager::GetFreeGapIndex()
+{
+	for (int i = 0; i < vMAX_GAPS; i++)
+		if (!mp_gapInfoTab[i])
+			return i;
+	return -1;
+}
+
+
+
+
+void CGapManager::StartGap(CClonedPiece *pGapPiece, CConcreteMetaPiece *pRegularMeta, int tab_index)
+{
+	
+	Dbg_Assert(pGapPiece);
+	Dbg_Assert(pRegularMeta);
+
+	//Ryan("Starting gap 0x%x\n", m_currentId);
+	
+	// find empty slot
+	// BAD MEM
+	mp_gapInfoTab[tab_index] = new GapInfo();
+	mp_gapInfoTab[tab_index]->m_id[0] = m_currentId++;
+	mp_gapInfoTab[tab_index]->mp_gapPiece[0] = pGapPiece;
+	//mp_gapInfoTab[tab_index]->mp_gapPiece[0]->MarkAsGapInProgress();
+	mp_gapInfoTab[tab_index]->mp_regularMeta[0] = pRegularMeta;
+	mp_gapInfoTab[tab_index]->m_descriptor.tabIndex = tab_index;
+	m_startOfGap[tab_index] = true;
+}
+
+
+
+
+void CGapManager::EndGap(CClonedPiece *pGapPiece, CConcreteMetaPiece *pRegularMeta, int tab_index)
+{
+	
+	Dbg_Assert(pGapPiece);
+	Dbg_Assert(pRegularMeta);
+	Dbg_MsgAssert(tab_index >= 0 && tab_index < vMAX_GAPS, ("invalid tab_index"));
+
+	GapInfo *pStartInfo = mp_gapInfoTab[tab_index];
+	Dbg_Assert(pStartInfo);
+	
+	//Ryan("Ending gap 0x%x\n", m_currentId);
+	
+	// find empty slot
+	for (int i = 0; i < vMAX_GAPS; i++)
+		if (!mp_gapInfoTab[i])
+		{
+			mp_gapInfoTab[i] = pStartInfo;
+			mp_gapInfoTab[i]->m_id[1] = m_currentId++;
+			mp_gapInfoTab[i]->mp_gapPiece[1] = pGapPiece;
+			//mp_gapInfoTab[i]->mp_gapPiece[0]->UnmarkAsGapInProgress();
+			//mp_gapInfoTab[i]->mp_gapPiece[1]->UnmarkAsGapInProgress();
+			mp_gapInfoTab[i]->mp_regularMeta[1] = pRegularMeta;
+			mp_gapInfoTab[i]->m_descriptor.tabIndex = tab_index;
+			m_startOfGap[i] = false;
+			return;
+		}
+
+	Dbg_MsgAssert(0, ("out of gap slots"));
+	return;
+}
+
+// K: Called when CParkEditor::SetState switches to test play or regular play.
+// This is necessary to make the gaps appear in the view-gaps menu, which lists those
+// in the skater career.
+void CGapManager::RegisterGapsWithSkaterCareer()
+{
+	Obj::CGapChecklist	*p_gap_checklist=Mdl::Skate::Instance()->GetCareer()->GetGapChecklist();
+	if (!p_gap_checklist)
+	{
+		return;
+	}
+
+	p_gap_checklist->FlushGapChecks();
+
+	for (int i = 0; i < vMAX_GAPS; i++)
+	{
+		if (mp_gapInfoTab[i])
+		{
+			p_gap_checklist->AddGapCheck(mp_gapInfoTab[i]->m_descriptor.text, 0,
+										 mp_gapInfoTab[i]->m_descriptor.score);
+		}
+	}	
+}
+
+void CGapManager::SetGapInfo(int tab_index, GapDescriptor &descriptor)
+{
+	
+	Dbg_MsgAssert(tab_index >= 0 && tab_index < vMAX_GAPS, ("invalid tab_index"));
+	Dbg_Assert(mp_gapInfoTab[tab_index]);
+	Dbg_Assert(descriptor.tabIndex >= 0 && descriptor.tabIndex < vMAX_GAPS);
+
+	sprintf(mp_gapInfoTab[tab_index]->mp_text, "%s", descriptor.text);
+	mp_gapInfoTab[tab_index]->m_descriptor = descriptor;
+
+	RegisterGapsWithSkaterCareer();
+	//mp_gapInfoTab[tab_index]->mp_gapPiece[0]->MakeTriggerable(true);
+	//mp_gapInfoTab[tab_index]->mp_gapPiece[1]->MakeTriggerable(true);
+}
+
+
+
+
+bool CGapManager::IsGapAttached(const CConcreteMetaPiece *pRegularMeta, int *pTabIndex)
+{
+	for (int i = 0; i < vMAX_GAPS; i++)
+		if (mp_gapInfoTab[i])
+		{
+			int half_num = (m_startOfGap[i]) ? 0 : 1;
+			#if 0
+			if (i == 16 || i == 17)
+				Ryan("           testing gap index %d, pMeta[0]=%x, pMeta[1]=%x, half=%d, test_meta=%p\n", 
+					 i,
+					 mp_gapInfoTab[i]->mp_regularMeta[0],
+					 mp_gapInfoTab[i]->mp_regularMeta[1], 
+					 half_num, pRegularMeta);  
+			#endif
+			if (mp_gapInfoTab[i]->mp_regularMeta[half_num] == pRegularMeta)
+			{
+				#if 0
+				Ryan("     found gap at table index %d, position=(%d,%d,%d), half=%d, pMeta=%p\n", 
+					 i, 
+					 mp_gapInfoTab[i]->m_descriptor.loc[half_num].GetX(), 
+					 mp_gapInfoTab[i]->m_descriptor.loc[half_num].GetY(),
+					 mp_gapInfoTab[i]->m_descriptor.loc[half_num].GetZ(),
+					 half_num,
+					 pRegularMeta);
+				#endif
+				if (pTabIndex)
+					*pTabIndex = i;
+				return true;
+			}
+		}
+	return false;
+}
+
+
+
+
+void CGapManager::RemoveGap(int tab_index, bool unregisterGapFromGoalEditor)
+{
+	
+	Dbg_MsgAssert(tab_index >= 0 && tab_index < vMAX_GAPS, ("invalid tab_index"));
+
+	GapInfo *pRemoveInfo = mp_gapInfoTab[tab_index];
+	Dbg_Assert(pRemoveInfo);
+	
+	
+	for (int gp = 0; gp < 2; gp++)
+	{
+		if (pRemoveInfo->mp_gapPiece[gp])
+			mp_manager->GetGenerator()->DestroyClonedPiece(pRemoveInfo->mp_gapPiece[gp]);
+	}
+
+	// Note: Need to check mp_manager cos it could be NULL in the case of this being
+	// the CParkEditor's mp_play_mode_gap_manager. (Fixes TT12087)
+	if (unregisterGapFromGoalEditor)
+	{
+		// Determine the gap number of the gap as stored in the career gap check list	
+		int gap_number=Mdl::Skate::Instance()->GetGapChecklist()->GetGapChecklistIndex(pRemoveInfo->m_descriptor.text);
+		if (gap_number >= 0)
+		{
+			Obj::GetGoalEditor()->RefreshGapGoalsAfterGapRemovedFromPark(gap_number);
+		}	
+	}
+		
+	for (int i = 0; i < vMAX_GAPS; i++)
+	{
+		if (mp_gapInfoTab[i] == pRemoveInfo)
+		{
+			mp_gapInfoTab[i] = NULL;
+		}
+	}
+	delete pRemoveInfo;
+}
+
+
+
+
+void CGapManager::RemoveAllGaps()
+{
+	for (int i = 0; i < vMAX_GAPS; i++)
+	{
+		if (mp_gapInfoTab[i])
+		{
+			// Normally, when a gap piece is deleted such as when editing a park, it gets unregistered
+			// from the goal editor.
+			// However, when cleaning up the park, we don't want to do this.
+			// The false means do not unregister the gap from the goal editor.
+			RemoveGap(i, false);
+		}
+	}	
+}
+
+
+
+
+uint32 CGapManager::GetGapTriggerScript(CClonedPiece *pGapPiece)
+{
+	for (int i = 0; i < vMAX_GAPS; i++)
+		if (mp_gapInfoTab[i])
+		{
+			int half_num = (m_startOfGap[i]) ? 0 : 1;
+			if (mp_gapInfoTab[i]->mp_gapPiece[half_num] == pGapPiece)
+			{
+				if (i&1  && Script::GetInt("PrintGaps",false))
+				{
+					printf ("Editor Gap %d points: %s\n",mp_gapInfoTab[i]->m_descriptor.score, mp_gapInfoTab[i]->m_descriptor.text);  
+				}
+				
+				char gap_trigger_script[48];
+				sprintf(gap_trigger_script, "EditorTrgGap%d", i);
+				return Script::GenerateCRC(gap_trigger_script);
+			}
+		}
+
+	Dbg_MsgAssert(0, ("no gap trigger script"));
+	return 0;
+}
+
+
+
+	
+CGapManager::GapDescriptor *CGapManager::GetGapDescriptor(int tab_index, int *pHalfNum)
+{
+	
+	if (!mp_gapInfoTab[tab_index])
+		return NULL;
+	
+	*pHalfNum = (m_startOfGap[tab_index]) ? 0 : 1;
+	strcpy(mp_gapInfoTab[tab_index]->m_descriptor.text, mp_gapInfoTab[tab_index]->mp_text);
+	return &mp_gapInfoTab[tab_index]->m_descriptor;
+}
+
+
+
+
+void CGapManager::MakeGapWireframe(int tab_index)
+{
+	
+	Dbg_MsgAssert(tab_index >= 0 && tab_index < vMAX_GAPS, ("invalid tab_index %d", tab_index));
+
+	if (!mp_gapInfoTab[tab_index])
+		return;
+
+	if (mp_gapInfoTab[tab_index]->mp_gapPiece[0])
+		mp_gapInfoTab[tab_index]->mp_gapPiece[0]->SetVisibility(true);
+	if (mp_gapInfoTab[tab_index]->mp_gapPiece[1])
+		mp_gapInfoTab[tab_index]->mp_gapPiece[1]->SetVisibility(true);
+}
+
+
+
+
+void CGapManager::LaunchGap(int tab_index, Script::CScript *pScript)
+{
+#if 1	
+	Dbg_MsgAssert(tab_index >= 0 && tab_index < vMAX_GAPS, ("invalid tab_index"));
+	Dbg_Assert(mp_gapInfoTab[tab_index]);
+	Dbg_Assert(pScript);
+	Dbg_Assert(pScript->mpObject);
+		
+	for (int i = 0; i < 2; i++)
+	{
+		int half_num = (m_startOfGap[tab_index]) ? i : 1 - i;
+
+		Script::CStruct *pGapParms = new Script::CStruct();
+		pGapParms->AddChecksum(CRCD(0x3b442e26,"GapID"), mp_gapInfoTab[tab_index]->m_id[half_num]);
+		pGapParms->AddInteger(CRCD(0xcd66c8ae,"score"), mp_gapInfoTab[tab_index]->m_descriptor.score);
+		pGapParms->AddString(CRCD(0xc4745838,"text"), mp_gapInfoTab[tab_index]->mp_text);
+		//pGapParms->AddComponent(Script::GenerateCRC("text"), ESYMBOLTYPE_STRING, "boogah");
+		
+		if (i == 0)
+		{
+			uint32 cancel_flags=mp_gapInfoTab[tab_index]->m_descriptor.mCancelFlags;
+			if (!cancel_flags)
+			{
+				// If no flags are set for some reason, use the default of just CANCEL_GROUND
+				cancel_flags=Script::GetInteger(CRCD(0x9a27e74d,"CANCEL_GROUND"));
+			}
+			pGapParms->AddChecksum(CRCD(0x2760de9e,"combined_flags"),cancel_flags);
+		
+			pScript->mpObject->CallMemberFunction(Script::GenerateCRC("StartGap"), pGapParms, pScript);
+		}	
+		else
+		{
+			pScript->mpObject->CallMemberFunction(Script::GenerateCRC("EndGap"), pGapParms, pScript);
+		}
+		
+		delete pGapParms;	
+	}
+#endif
+}
+
+
+
+
+CGapManager *CGapManager::sInstance()
+{
+	Dbg_Assert(sp_instance);
+	return sp_instance;
+}
+
+
+
+
+}
+
+
+
+
+
diff --git a/Code/Sk/ParkEditor2/GapManager.h b/Code/Sk/ParkEditor2/GapManager.h
new file mode 100644
index 0000000..04b1516
--- /dev/null
+++ b/Code/Sk/ParkEditor2/GapManager.h
@@ -0,0 +1,107 @@
+#ifndef __SK_PARKEDITOR2_GAPMANAGER_H
+#define __SK_PARKEDITOR2_GAPMANAGER_H
+
+#include 
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+
+
+namespace Ed
+{
+
+class CGapManager;
+class GapInfo;
+class CParkManager;
+
+class  CGapManager
+{
+	
+	
+public:
+
+	struct GapDescriptor
+	{
+		GridDims				loc[2];
+		int						rot[2];
+		int 					leftExtent[2];
+		int						rightExtent[2];
+		char 					text[32];
+		int						score;
+		int						numCompleteHalves; // will be 0, 1, or 2
+		int						tabIndex; // set to -1 if not in table
+		
+		// An 'or' of the CANCEL_ values as defined in skutils.q, eg CANCEL_GROUND
+		uint32					mCancelFlags;
+	};
+	
+	// actually max gap HALVES, divide by two to get gaps
+	static const int			vMAX_GAPS = 32;
+
+								CGapManager(CParkManager *pManager);
+								~CGapManager();
+
+	int 						GetFreeGapIndex();
+	void						StartGap(CClonedPiece *pGapPiece, CConcreteMetaPiece *pRegularMeta, int tab_index);
+	void						EndGap(CClonedPiece *pGapPiece, CConcreteMetaPiece *pRegularMeta, int tab_index);
+	void						SetGapInfo(int tab_index, GapDescriptor &descriptor);
+	bool						IsGapAttached(const CConcreteMetaPiece *pRegularMeta, int *pTabIndex);
+	void						RemoveGap(int tab_index, bool unregisterGapFromGoalEditor=true);
+	void						RemoveAllGaps();
+	void						RegisterGapsWithSkaterCareer();
+
+	uint32						GetGapTriggerScript(CClonedPiece *pGapPiece);
+	GapDescriptor *				GetGapDescriptor(int tab_index, int *pHalfNum);
+	void						MakeGapWireframe(int tab_index);
+	
+	void						LaunchGap(int tab_index, Script::CScript *pScript);
+
+	static CGapManager *		sInstance();
+	void						SetInstance() {sp_instance=this;}
+
+	static CGapManager *		sInstanceNoAssert() {return sp_instance;}
+
+	CGapManager& operator=( const CGapManager& rhs );
+	
+private:
+
+	uint32						m_currentId;
+
+	GapInfo *					mp_gapInfoTab[vMAX_GAPS];
+	bool						m_startOfGap[vMAX_GAPS];
+
+	CParkManager				*mp_manager;
+	
+	static CGapManager *		sp_instance;
+};
+
+
+
+
+class  GapInfo  : public Spt::Class
+{
+	
+	friend class CGapManager;
+
+private:
+
+	GapInfo();
+
+	uint32						m_id[2];
+	CClonedPiece *				mp_gapPiece[2];
+	CConcreteMetaPiece *		mp_regularMeta[2];
+	char						mp_text[128];
+
+	CGapManager::GapDescriptor	m_descriptor;
+};
+
+
+
+
+}
+
+#endif
+
diff --git a/Code/Sk/ParkEditor2/ParkEd.cpp b/Code/Sk/ParkEditor2/ParkEd.cpp
new file mode 100644
index 0000000..0bdde58
--- /dev/null
+++ b/Code/Sk/ParkEditor2/ParkEd.cpp
@@ -0,0 +1,3814 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+//#define DONT_USE_CURSOR_META
+
+namespace Ed
+{
+
+
+
+
+const float CParkEditor::vMAX_CAM_DIST				= 1500.0f;
+const float CParkEditor::vMIN_CAM_DIST				= 500.0f;
+const float CParkEditor::vCAM_DIST_INC				= 16.0f;
+const float CParkEditor::vCAM_TARGET_ELEVATION		= 300.0f;
+
+DefineSingletonClass( CParkEditor, "Park editor module" );
+
+
+
+
+CParkEditor::CParkEditor() :
+	mp_park_manager(true)
+{
+	m_logic_task 	= new Tsk::Task< CParkEditor > ( CParkEditor::s_logic_code, *this );
+	m_display_task 	= new Tsk::Task< CParkEditor > ( CParkEditor::s_display_code, *this, Tsk::BaseTask::Node::vDISPLAY_TASK_PRIORITY_CPARKEDITOR_DISPLAY );
+	m_input_handler = new Inp::Handler< CParkEditor > ( 0,  CParkEditor::s_input_logic_code, *this);
+
+	m_movement_vel = 480.0f; //8.0f;		// default movement velocity in inches per second
+	m_rotate_vel = 120.0f;		// 120 degrees per second. In 3 seconds you should complete 1 revolution
+
+	mp_camera = NULL;
+	m_camDist = 1400.0f;
+	m_camAngle = 0.0f; //225.0f;
+	m_camAngleVert = 10.0f;
+
+	m_cursor_state = vMOVEMENT;
+	m_state = vINACTIVE;
+	m_paused = false;
+
+	mp_cursor = NULL;
+	mp_menu_manager = NULL;
+	
+	m_pct_resources_used = 1.0f;
+	m_last_main_heap_free=0;
+	
+	m_tod_script=0;
+
+	mp_play_mode_gap_manager=NULL;
+
+	m_allow_defrag=false;
+		
+	RegisterWithTracker(NULL);
+}
+
+
+
+
+CParkEditor::~CParkEditor()
+{
+}
+
+
+
+
+void CParkEditor::Initialize(bool editMode)
+{
+	ParkEd("CParkEditor::Initialize()");
+	//printf("CParkEditor::Initialize\n");
+	
+	mp_park_manager->Initialize();
+	
+	if (editMode && !mp_cursor)
+	{
+		mp_cursor = new CCursor();
+		mp_menu_manager = new CUpperMenuManager();
+	}
+}
+
+
+
+
+void CParkEditor::Rebuild(bool justRebuildFloor, bool clearMap, bool makeCursor)
+{
+	ParkEd("CParkEditor::Rebuild(justRebuildFloor = %d, clearMap = %d)", justRebuildFloor, clearMap);
+	
+	if (justRebuildFloor)
+	{
+		mp_park_manager->RebuildFloor();
+	}
+	else
+	{
+		if (mp_cursor)
+			delete mp_cursor;
+		mp_cursor = NULL;
+		mp_park_manager->RebuildWholePark(clearMap);
+	}	   
+	
+	if (makeCursor && !mp_cursor)
+	{
+		mp_cursor = new CCursor();
+	}
+	
+	// Fix to TT1437, where the 1P restart piece was able to be placed outside the map if X pressed really quickly
+	// just after nuking a park.
+	if (mp_cursor)
+	{
+		mp_cursor->ForceInBounds();
+	}
+		
+	// Regenerate the node array when the park is rebuilt (usually only happens when we are going to play the level)
+	mp_park_manager->RebuildNodeArray();
+	
+	// and re register the gaps so that any old gaps are removed from the career.
+	CGapManager::sInstance()->RegisterGapsWithSkaterCareer();			
+}
+
+
+
+
+void CParkEditor::SetState(EEditorState desiredState)
+{
+	ParkEd("CParkEditor::SetState(desiredState = %d)", desiredState);
+	
+	if (desiredState != vKEEP_SAME_STATE)
+	{
+		m_state = desiredState;
+	}	
+	
+	// this prevents menu input from placing a piece
+	m_commands.ClearAll();
+	if (mp_cursor)
+		mp_cursor->SetVisibility(m_state == vEDITING);
+
+	if (desiredState == vTEST_PLAY || desiredState == vREGULAR_PLAY)
+	{
+	   	// XXX
+		Ryan("$$$MakeEditorStuffVisible(false)\n");
+		MakeEditorStuffVisible(false); 
+		
+		CGapManager::sInstance()->RegisterGapsWithSkaterCareer();	
+	}
+	else
+	{
+		// XXX
+		Ryan("$$$MakeEditorStuffVisible(true)\n");
+		MakeEditorStuffVisible(true); 	
+	}
+
+	Spt::SingletonPtr p_front_end;	
+	if (desiredState == vEDITING)
+	{
+		Mdl::Skate::Instance()->CleanupForParkEditor();
+	
+		p_front_end->SetAutoRepeatTimes(320, 100);
+		mp_park_manager->HighlightMetasWithGaps(false, NULL);
+	}
+	else
+	{
+		// same as above, but what the hell
+		p_front_end->SetAutoRepeatTimes(320, 50);
+	}
+	update_analog_stick_menu_control_state();
+}
+
+
+
+
+void CParkEditor::MakeEditorStuffVisible(bool visible)
+{
+	ParkEd("MakeEditorStuffVisible(visible = %d)", visible);
+	
+	// it would be nice to sort out the encapsulation sometime...	
+	Ed::CParkManager::Instance()->GetGenerator()->HighlightAllPieces(false);
+	Ed::CParkManager::Instance()->GetGenerator()->SetGapPiecesVisible(visible);
+	Ed::CParkManager::Instance()->GetGenerator()->SetRestartPiecesVisible(visible);
+	
+	if (visible)
+	{
+		Script::RunScript("pause_trick_text");
+		Script::RunScript("GoalManager_HidePoints");
+		Script::RunScript("GoalManager_HideGoalPoints");
+		Script::RunScript("SK4_Hide_Death_Message");
+	}
+	else
+	{
+		Script::RunScript("unpause_trick_text");
+		Script::RunScript("GoalManager_ShowPoints");
+		Script::RunScript("GoalManager_ShowGoalPoints");
+	}
+}
+
+
+
+
+void CParkEditor::AccessDisk(bool save, int fileSlot, bool blockRebuild)
+{
+	ParkEd("CParkEditor::AccessDisk()");
+	
+	if (save)
+	{
+		CParkManager::sInstance()->AccessDisk(true, fileSlot);
+	}
+	else
+	{
+		int old_theme = CParkManager::sInstance()->GetTheme();
+		
+		if (m_state != vINACTIVE && !blockRebuild)
+		{
+			// need to destroy cursor geometry now, otherwise code will think park has changed
+			Dbg_Assert(mp_cursor);
+			mp_cursor->DestroyGeometry();
+		}
+		if (mp_cursor)
+		{
+			mp_cursor->DestroyAnyClipboardCursors();
+		}	
+		
+		CParkManager::sInstance()->AccessDisk(false, fileSlot);
+		if (m_state != vINACTIVE && !blockRebuild)
+		{
+			if (CParkManager::sInstance()->GetTheme() != old_theme)
+			{
+				Script::CStruct params;
+				params.AddChecksum("level", CRCD(0xe8b4b836,"Load_Sk5Ed"));
+				
+				Script::RunScript("change_level", ¶ms);
+			}
+			else
+			{
+				CParkManager::sInstance()->RebuildWholePark(false);
+				mp_cursor->ChangePieceInSet(0);
+				mp_park_manager->HighlightMetasWithGaps(false, NULL);
+			}
+		}
+	}
+}
+
+
+
+
+void CParkEditor::PostMemoryCardLoad(uint8 * p_map_buffer, int oldTheme)
+{
+	bool needs_rebuild = (m_state == vEDITING || m_state == vTEST_PLAY);
+	
+	if (needs_rebuild)
+	{
+		// need to destroy cursor geometry now, otherwise code will think park has changed
+		Dbg_Assert(mp_cursor);
+		mp_cursor->DestroyGeometry();
+	}
+	// PATCH:  Copy buffer back over itself to set the flags
+	Ed::CParkManager::Instance()->SetCompressedMapBuffer(p_map_buffer);
+	if (needs_rebuild)
+	{
+		// XXX
+		Ryan("old theme %d, new theme %d\n", oldTheme, CParkManager::sInstance()->GetTheme());
+		if (CParkManager::sInstance()->GetTheme() != oldTheme)
+		{
+			Script::CStruct params;
+			params.AddChecksum("level", CRCD(0xe8b4b836,"Load_Sk5Ed"));
+
+			Script::RunScript("change_level", ¶ms);
+		}
+		else
+		{
+			CParkManager::sInstance()->RebuildWholePark(false);
+			mp_cursor->ChangePieceInSet(0);
+			mp_park_manager->HighlightMetasWithGaps(false, NULL);
+		}
+	}
+}
+
+
+// K: Added to allow cleanup of the park editor heap during play
+void CParkEditor::DeleteCursor()
+{
+	if (mp_cursor)
+	{
+		delete mp_cursor;
+		mp_cursor=NULL;
+	}
+}
+
+// Creates a copy of the gap manager on the script heap.
+// CGapManager::sInstance() will still return the original gap manager afterwards however.
+// This function is needed because to free up memory before playing a park, everything on the
+// park editor heap is deleted and then the heap is deleted, and the original gap manager is on that heap.
+// So a copy is made for play mode, since the gap manager is needed when a gap fires.
+void CParkEditor::CreatePlayModeGapManager()
+{
+	Dbg_MsgAssert(mp_play_mode_gap_manager==NULL,("Expected mp_play_mode_gap_manager to be NULL"));
+	Ed::CGapManager *p_original_gap_manager = CGapManager::sInstance();
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+	mp_play_mode_gap_manager=new Ed::CGapManager(NULL);
+	
+	// Use the overloaded assignement operator to make the copy.
+	*mp_play_mode_gap_manager = *p_original_gap_manager;
+	
+	Mem::Manager::sHandle().PopContext();
+	
+	
+	// The constructor of CGapManager sets the sp_instance pointer, so set it back to the original so that
+	// things don't get confused.
+	p_original_gap_manager->SetInstance();
+}
+
+void CParkEditor::SwitchToPlayModeGapManager()
+{
+	Dbg_MsgAssert(CGapManager::sInstanceNoAssert() == NULL,("Expected CGapManager::sInstance() to be NULL"));
+	Dbg_MsgAssert(mp_play_mode_gap_manager,("NULL mp_play_mode_gap_manager"));
+	mp_play_mode_gap_manager->SetInstance();
+}	
+
+void CParkEditor::DeletePlayModeGapManager()
+{
+	if (mp_play_mode_gap_manager)
+	{
+		delete mp_play_mode_gap_manager;
+		mp_play_mode_gap_manager=NULL;
+	}
+	Dbg_MsgAssert(CGapManager::sInstanceNoAssert() == NULL,("Expected CGapManager::sInstance() to be NULL"));
+}
+
+void CParkEditor::PlayModeGapManagerChecks()
+{
+	//printf("CGapManager::sInstance = 0x%08x\n",CGapManager::sInstanceNoAssert());
+	//printf("mp_play_mode_gap_manager = 0x%08x\n",mp_play_mode_gap_manager);
+}
+
+void CParkEditor::Cleanup()
+{
+	ParkEd("CParkEditor::Cleanup()");
+
+	m_state = vINACTIVE;
+	if (mp_cursor)
+	{
+		delete mp_cursor;
+		mp_cursor = NULL;
+	}
+	if (mp_menu_manager)
+	{
+		delete mp_menu_manager;
+		mp_menu_manager = NULL;
+	}
+	
+	mp_park_manager->Destroy(CParkGenerator::DESTROY_PIECES_AND_SECTORS);
+	
+	Obj::CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors = false;
+	Obj::GetRailEditor()->DestroyRailGeometry();
+	Obj::GetRailEditor()->DestroyPostGeometry();
+	Obj::CRailEditorComponent::sUpdateSuperSectorsAfterDeletingRailSectors = true;
+
+	printf("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ DEATH OR GLORY 2 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n");
+}
+
+void CParkEditor::SetAppropriateCamera()
+{
+	if (mp_cursor && mp_cursor->InRailPlacementMode())
+	{
+		CFuncs::SetActiveCamera(CRCD(0x5b509ad3,"raileditor"), 0, false);
+	}
+	else
+	{
+		CFuncs::SetActiveCamera(CRCD(0x64716bee,"parked_cam"), 0, false);
+	}	
+}
+
+void CParkEditor::do_piece_select_commands()
+{
+	if (m_commands.TestMask(mPREV_SET))
+	{
+		mp_cursor->ChangeSet(-1); 
+		turn_on_or_update_piece_menu();
+		Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+		p_tracker->LaunchEvent(Script::GenerateCRC("parked_menu_up"), Script::GenerateCRC("set_menu_container"));
+		//mp_menu_manager->SetSourceMeta(mp_cursor->GetChecksum());
+	}
+	if (m_commands.TestMask(mNEXT_SET))
+	{
+		mp_cursor->ChangeSet(1); 
+		turn_on_or_update_piece_menu();
+		Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+		p_tracker->LaunchEvent(Script::GenerateCRC("parked_menu_down"), Script::GenerateCRC("set_menu_container"));
+		//mp_menu_manager->SetSourceMeta(mp_cursor->GetChecksum());
+	}
+	if (m_commands.TestMask(mPREV_PIECE))
+	{
+		mp_cursor->ChangePieceInSet(-1); 
+		turn_on_or_update_piece_menu();
+		Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+		p_tracker->LaunchEvent(Script::GenerateCRC("parked_menu_left"), Script::GenerateCRC("piece_menu_container"));
+		//mp_menu_manager->SetSourceMeta(mp_cursor->GetChecksum());
+	}
+	if (m_commands.TestMask(mNEXT_PIECE))
+	{
+		mp_cursor->ChangePieceInSet(1); 
+		turn_on_or_update_piece_menu();
+		Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+		p_tracker->LaunchEvent(Script::GenerateCRC("parked_menu_right"), Script::GenerateCRC("piece_menu_container"));
+		//mp_menu_manager->SetSourceMeta(mp_cursor->GetChecksum());
+	}
+}
+
+void CParkEditor::regular_command_logic()
+{
+	Dbg_MsgAssert(mp_cursor,("NULL mp_cursor"));
+
+	Mth::Matrix cam_matrix;
+	cam_matrix.Ident();
+	cam_matrix.SetPos(Mth::Vector(0.0f, 0.0f, m_camDist, 1.0f));
+	cam_matrix.RotateX(Mth::DegToRad(-m_camAngleVert));
+	cam_matrix.RotateY(Mth::DegToRad(m_camAngle));
+	cam_matrix.Translate(Mth::Vector(mp_cursor->m_pos[X], vCAM_TARGET_ELEVATION, mp_cursor->m_pos[Z], 0.0f));
+	
+	Obj::CCompositeObject * p_obj = (Obj::CCompositeObject *) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x64716bee,"parked_cam"));
+	if (p_obj)
+	{
+		//printf("Setting cam pos\n");
+		//CFuncs::SetActiveCamera(CRCD(0x64716bee,"parked_cam"), 0, false);
+		
+		//Obj::CCameraComponent * p_cam_comp = GetCameraComponentFromObject(p_obj); 			
+		p_obj->SetPos(cam_matrix[Mth::POS]);
+		p_obj->SetMatrix(cam_matrix);
+	}
+
+
+	if (!m_paused)
+	{
+		// XXX
+		//Ryan("enraged monkey\n");
+
+		// We unhighlight the meta-pieces intersecting the cursor before
+		// we change or move it.
+		if (!mp_cursor->InAreaSelectMode())
+		{
+			mp_cursor->HighlightIntersectingMetas(false); 
+		}	
+		
+		Tmr::Time current_time = Tmr::GetTime();
+		float elapsed_seconds = (int) (current_time - m_last_time) / 1000.0f;	
+		m_last_time = current_time;
+		
+		static const	float	scale = ( 1.0f / ( 128.0f - Inp::vANALOGUE_TOL ));
+		
+		m_leftStick.m_Y =
+			( m_leftStick.m_Y >  Inp::vANALOGUE_TOL ) ? ( m_leftStick.m_Y - Inp::vANALOGUE_TOL ) * scale :
+			( m_leftStick.m_Y < -Inp::vANALOGUE_TOL ) ? ( m_leftStick.m_Y + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+		m_leftStick.m_X =
+			( m_leftStick.m_X >  Inp::vANALOGUE_TOL ) ? ( m_leftStick.m_X - Inp::vANALOGUE_TOL ) * scale :
+			( m_leftStick.m_X < -Inp::vANALOGUE_TOL ) ? ( m_leftStick.m_X + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+			
+		float rel_shift_x = m_leftStick.m_X * elapsed_seconds * m_movement_vel;
+		float rel_shift_z = m_leftStick.m_Y * elapsed_seconds * m_movement_vel;
+		float angle_rad = m_camAngle * Mth::PI / 180.0f;
+		float shift_x = cosf(angle_rad) * rel_shift_x + sinf(angle_rad) * rel_shift_z;
+		float shift_z = cosf(angle_rad) * rel_shift_z - sinf(angle_rad) * rel_shift_x;
+	
+		m_rightStick.m_Y =
+			( m_rightStick.m_Y >  Inp::vANALOGUE_TOL ) ? ( m_rightStick.m_Y - Inp::vANALOGUE_TOL ) * scale :
+			( m_rightStick.m_Y < -Inp::vANALOGUE_TOL ) ? ( m_rightStick.m_Y + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+		m_rightStick.m_X =
+			( m_rightStick.m_X >  Inp::vANALOGUE_TOL ) ? ( m_rightStick.m_X - Inp::vANALOGUE_TOL ) * scale :
+			( m_rightStick.m_X < -Inp::vANALOGUE_TOL ) ? ( m_rightStick.m_X + Inp::vANALOGUE_TOL ) * scale : 0.0f;
+	
+#ifdef __PLAT_NGC__DAN
+		if ( !( m_commands.TestMask(mZOOM_CAMERA_IN) ) )
+#endif		// __PLAT_NGC__
+		{
+			m_camAngle += m_rightStick.m_X * elapsed_seconds * m_rotate_vel;	
+		}
+		if (m_camAngle < 0) m_camAngle += 360.0f;
+		else if (m_camAngle >= 360.0) m_camAngle -= 360.0f;
+#ifdef __PLAT_NGC__DAN
+		if ( !( m_commands.TestMask(mZOOM_CAMERA_IN) ) )
+#endif		// __PLAT_NGC__
+		{
+			m_camAngleVert -= m_rightStick.m_Y * elapsed_seconds * m_rotate_vel;	
+		}
+		if (m_camAngleVert < 10.0f) m_camAngleVert = 10.0f;
+		else if (m_camAngleVert > 90.0) m_camAngleVert = 90.0f;
+
+		/*
+			PlayEdPlaceSound for placing items (might have wrong sound hooked up right now)
+			PlayEdEraseSound  for removing items
+			PlayRaiseGroundSound for raising
+			PlayLowerGroundSound for lowering
+			PlayRotatePieceSound for rotating both directions
+		*/
+		
+		int rot_inc = 0;
+		if (m_commands.TestMask(mROTATE_CW))
+		{
+			Script::RunScript("PlayRotatePieceSound");
+			if (mp_cursor->InGapMode())
+				mp_cursor->AdjustGap(-1, 0, 0);
+			else
+				rot_inc = -1;
+		}
+		if (m_commands.TestMask(mROTATE_CCW))
+		{
+			Script::RunScript("PlayRotatePieceSound");
+			if (mp_cursor->InGapMode())
+				mp_cursor->AdjustGap(-1, 0, 0);
+			else
+				rot_inc = 1;
+		}
+		if (m_commands.TestMask(mPLACE_PIECE))
+		{
+			if (mp_cursor->InGapMode())
+			{
+				bool success = mp_cursor->AttemptGap();
+				if ((success || mp_cursor->IsSittingOnGap()) && !mp_cursor->HalfFinishedGap())
+				{
+					Script::RunScript("PlayEdPlaceSound");
+					// XXX
+					Ryan("the hell, you say\n");
+					Script::RunScript("parked_setup_gap_menu");
+				}
+				else
+					Script::RunScript("PlayEdBuzzSound");
+					
+				m_allow_defrag=true;
+			}
+			else if (mp_cursor->InAreaSelectMode())
+			{
+				switch (mp_cursor->AttemptAreaSelect())
+				{
+				case 1:
+					// Maybe have a different sound for the first corner ?
+					Script::RunScript("PlayEdPlaceSound");
+					break;
+				case 2:
+					Script::RunScript("PlayEdPlaceSound");
+					Script::RunScript("parked_setup_area_select_menu");
+					break;
+				default:
+					Script::RunScript("PlayEdBuzzSound");
+					break;
+				}
+				// Note: Not setting m_allow_defrag here, because otherwise it may defrag
+				// as soon as the user places one of the area corners, which becomes a pain
+				// especially if trying to select a set of pieces to delete to free up memory.
+			}
+			else if (mp_cursor->InPasteMode())
+			{
+				mp_cursor->PasteCurrentClipboard();
+				m_allow_defrag=true;
+			}
+			else	
+			{
+				if (mp_cursor->AttemptStamp())
+					Script::RunScript("PlayEdPlaceSound");
+				else
+					Script::RunScript("PlayEdBuzzSound");
+				m_allow_defrag=true;
+			}
+		}
+		if (m_commands.TestMask(mREMOVE_PIECE))
+		{
+			if (mp_cursor->InGapMode())
+			{
+				if (mp_cursor->AttemptRemoveGap())
+				{
+					Script::RunScript("PlayEdEraseSound");
+				}
+				else
+					Script::RunScript("PlayEdBuzzSound");
+			}
+			else if (mp_cursor->InAreaSelectMode())
+			{
+				mp_cursor->ClearAreaSelection();
+				m_allow_defrag=true;
+			}
+			else	
+			{
+				if (mp_cursor->AttemptRemove())
+				{
+					Script::RunScript("PlayEdEraseSound");
+				}
+				else
+				{
+					Script::RunScript("PlayEdBuzzSound");
+				}
+				m_allow_defrag=true;
+			}
+			// and re register the gaps so that any old gaps are removed from the career.
+			CGapManager::sInstance()->RegisterGapsWithSkaterCareer();			
+		}
+		
+		do_piece_select_commands();
+		
+		if (m_commands.TestMask(mRAISE_FLOOR))
+		{
+			if (!mp_cursor->InGapMode())
+			{
+				if (mp_cursor->ChangeFloorHeight(1))
+					Script::RunScript("PlayRaiseGroundSound");
+				else
+					Script::RunScript("PlayEdBuzzSound");
+			}
+			m_allow_defrag=true;
+		}
+		if (m_commands.TestMask(mLOWER_FLOOR))
+		{
+			if (!mp_cursor->InGapMode())
+			{
+				if (mp_cursor->ChangeFloorHeight(-1))
+					Script::RunScript("PlayLowerGroundSound");
+				else
+					Script::RunScript("PlayEdBuzzSound");
+			}
+			m_allow_defrag=true;
+		}
+
+#ifdef __PLAT_NGC__DAN
+		if ( m_commands.TestMask(mZOOM_CAMERA_IN) )
+		{
+			// Use y component of stick to zoom camera in or out.
+			m_camDist += vCAM_DIST_INC * m_rightStick.m_Y;
+			if (m_camDist > vMAX_CAM_DIST)
+				m_camDist = vMAX_CAM_DIST;
+			if (m_camDist < vMIN_CAM_DIST)
+				m_camDist = vMIN_CAM_DIST;
+		}
+#else
+		if (m_commands.TestMask(mZOOM_CAMERA_OUT) && !mp_cursor->InGapMode())
+		{
+			m_camDist += vCAM_DIST_INC;
+			if (m_camDist > vMAX_CAM_DIST)
+				m_camDist = vMAX_CAM_DIST;
+		}
+		if (m_commands.TestMask(mZOOM_CAMERA_IN) && !mp_cursor->InGapMode())
+		{
+			m_camDist -= vCAM_DIST_INC;
+			if (m_camDist < vMIN_CAM_DIST)
+				m_camDist = vMIN_CAM_DIST;
+		}
+#endif		// __PLAT_NGC__
+		
+		if (m_commands.TestMask(mINCREASE_GAP_LEFT))
+		{
+			if (mp_cursor->InGapMode())
+				mp_cursor->AdjustGap(0, 1, 0);
+		}
+		if (m_commands.TestMask(mINCREASE_GAP_RIGHT))
+		{
+			if (mp_cursor->InGapMode())
+				mp_cursor->AdjustGap(0, 0, 1);
+		}
+		if (m_commands.TestMask(mDECREASE_GAP_LEFT))
+		{
+			if (mp_cursor->InGapMode())
+				mp_cursor->AdjustGap(0, -1, 0);
+		}
+		if (m_commands.TestMask(mDECREASE_GAP_RIGHT))
+		{
+			if (mp_cursor->InGapMode())
+				mp_cursor->AdjustGap(0, 0, -1);
+		}
+
+
+		// If an operation has occurred that has modified collidable geometry,
+		// run through all the existing created-rails and update their posts so that they
+		// maintain ground contact.
+		if (m_commands.TestMask(mPLACE_PIECE) ||
+			m_commands.TestMask(mREMOVE_PIECE) ||
+			m_commands.TestMask(mRAISE_FLOOR) ||
+			m_commands.TestMask(mLOWER_FLOOR))
+		{
+			Obj::CRailEditorComponent *p_rail_editor=Obj::GetRailEditor();
+			p_rail_editor->AdjustYs();
+			p_rail_editor->UpdateRailGeometry();
+			p_rail_editor->UpdatePostGeometry();
+			
+			Obj::GetGoalEditor()->RefreshGoalPositionsUsingCollisionCheck();
+		}
+		
+		mp_cursor->Update(shift_x, shift_z, rot_inc);
+		//printf("cursor position: (%.2f,%.2f)\n", mp_cursor->m_target_pos[X], mp_cursor->m_target_pos[Z]);		
+		
+		if (mp_cursor->InAreaSelectMode())
+		{
+			mp_cursor->RefreshSelectionArea();
+		}
+		else if (mp_cursor->InRailPlacementMode())
+		{
+			mp_cursor->DestroyGeometry();
+		}
+		else
+		{
+			// Highlight the pieces intersecting the cursor after it has changed or moved
+			mp_cursor->HighlightIntersectingMetas(true); 
+		}	
+	}	
+}
+
+void CParkEditor::rail_placement_logic()
+{
+	Dbg_MsgAssert(mp_cursor,("NULL mp_cursor"));
+
+	if (!m_paused)
+	{
+		do_piece_select_commands();
+	}
+}
+	
+void CParkEditor::Update()
+{
+	if (!m_paused)
+	{
+		if (m_state == vEDITING)
+		{
+			Script::CStruct params;
+			params.AddChecksum(NONAME, Script::GenerateCRC("hide"));
+			CFuncs::ScriptPauseSkaters(¶ms, NULL);
+		}
+		else if (m_state == vTEST_PLAY)
+		{
+			CFuncs::ScriptUnPauseSkaters(NULL, NULL);
+		}
+	}
+
+	if (m_state == vEDITING && mp_cursor)
+	{
+		if (!m_paused)
+		{
+			if (mp_cursor->InRailPlacementMode())
+			{
+				if (Obj::GetRailEditor()->IsSuspended())
+				{
+					Script::RunScript(CRCD(0x2c3b17ea,"SwitchOnRailEditor"));
+				}
+				
+				rail_placement_logic();
+				
+				if (!mp_cursor->InRailPlacementMode())
+				{
+					Script::RunScript(CRCD(0x32428a49,"SwitchOffRailEditor"));
+				}
+			}
+			else
+			{
+				if (!Obj::GetRailEditor()->IsSuspended())
+				{
+					Script::RunScript(CRCD(0x32428a49,"SwitchOffRailEditor"));
+				}
+				regular_command_logic();
+				if (mp_cursor->InRailPlacementMode())
+				{
+					Script::RunScript(CRCD(0x2c3b17ea,"SwitchOnRailEditor"));
+				}
+			}	
+		}
+
+		if (!m_paused)		
+		{
+			CParkGenerator *p_generator = mp_park_manager->GetGenerator();
+			CParkGenerator::MemUsageInfo usage_info = p_generator->GetResourceUsageInfo();
+	
+			#ifdef	__NOPT_ASSERT__
+			if (Script::GetInteger(CRCD(0xcc55a87b,"show_parked_main_heap_free")))
+			{
+				printf("usage_info.mMainHeapFree = %d   usage_info.mParkHeapFree=%d\n",usage_info.mMainHeapFree,usage_info.mParkHeapFree);
+			}	
+			#endif
+			
+
+			if (usage_info.mMainHeapFree >= 0)
+			{
+				m_pct_resources_used=1.0f-((float)usage_info.mMainHeapFree)/((float)p_generator->GetResourceSize("main_heap_base"));
+			}
+			else
+			{
+				#ifdef	__NOPT_ASSERT__
+				printf("Memory overflow! Gone over the end of the gauge by %d bytes\n",-usage_info.mMainHeapFree);
+				#endif
+				m_pct_resources_used=1.0f;
+			}	
+
+			Mem::Heap *p_bottom_up_heap = Mem::Manager::sHandle().BottomUpHeap();
+			Mem::Heap *p_top_down_heap = Mem::Manager::sHandle().TopDownHeap();
+			#ifdef	__NOPT_ASSERT__
+			if (Script::GetInteger(CRCD(0xe2ca7dfc,"show_actual_heap_status")))
+			{
+				printf("Bottom up = %d  Top down = %d\n",p_bottom_up_heap->mFreeMem.m_count,p_top_down_heap->mp_region->MemAvailable());
+			}	
+			#endif
+			
+			// top_down_pct is a metric based on the amount of top down heap available. The reason for the 2.0 is to
+			// make the top_down_pct be >= 1.0 if there is less than TOP_DOWN_REQUIRED_MARGIN available, and < 1.0
+			// if there is more than that available.
+			float top_down_pct=2.0f-(((float)p_top_down_heap->mp_region->MemAvailable()) / ((float)TOP_DOWN_REQUIRED_MARGIN));
+			if (top_down_pct > m_pct_resources_used)
+			{
+				m_pct_resources_used = top_down_pct;
+			}	
+
+
+			float park_heap_pct=1.0f-((float)usage_info.mParkHeapFree)/((float)p_generator->GetResourceSize("park_heap_base"));
+			if (park_heap_pct > m_pct_resources_used)
+			{
+				m_pct_resources_used=park_heap_pct;
+			}	
+
+			m_last_main_heap_free=usage_info.mMainHeapFree;
+
+			//printf("2: m_pct_resources_used = %f\n",m_pct_resources_used);
+			
+			//float rail_point_pct = (float) usage_info.mTotalRailPoints / (float) (p_generator->GetResourceSize("out_railpoint_pool") - 25);
+			int component_use_est = usage_info.mTotalClonedPieces * 7 + usage_info.mTotalRailPoints * 9 + usage_info.mTotalLinkedRailPoints;
+			int base_component_use = p_generator->GetResourceSize("component_use_base");
+			float component_pct = (float) (component_use_est - base_component_use) / (float) (p_generator->GetResourceSize("max_components") - base_component_use);
+			if (m_pct_resources_used < component_pct)
+				m_pct_resources_used = component_pct;
+			//printf("component_use_est=%d\n",component_use_est);
+			//ParkEd("3: m_pct_resources_used=%f",m_pct_resources_used);
+			
+			int vector_use_est = usage_info.mTotalClonedPieces * 2 + usage_info.mTotalRailPoints * 2;
+			int base_vector_use = p_generator->GetResourceSize("vector_use_base");
+			float vector_pct = (float) (vector_use_est - base_vector_use) / (float) (p_generator->GetResourceSize("max_vectors") - base_vector_use);
+			if (m_pct_resources_used < vector_pct)
+				m_pct_resources_used = vector_pct;
+			//printf("vector_use_est=%d\n",vector_use_est);	
+			//ParkEd("4: m_pct_resources_used=%f",m_pct_resources_used);
+			//printf("3: m_pct_resources_used = %f\n",m_pct_resources_used);
+
+			float pieces_pct=(float)mp_park_manager->GetDMAPieceCount() / (float)p_generator->GetResourceSize("max_dma_pieces");
+			if (pieces_pct > m_pct_resources_used)
+			{
+				m_pct_resources_used = pieces_pct;
+			}
+			
+			//printf("4: m_pct_resources_used = %f IsParkFull=%d\n",m_pct_resources_used,IsParkFull());
+			
+			float shown_pct_resources_used = m_pct_resources_used;
+			if (shown_pct_resources_used < 0.0f) shown_pct_resources_used = 0.0f;
+			else if (shown_pct_resources_used > 1.0f) shown_pct_resources_used = 1.0f;
+
+
+
+			bool do_defragment=false;
+			
+			// Don't defrag if in rail placement mode, cos the resulting mode switch causes an assert.
+			if (mp_cursor->InRailPlacementMode())
+			{
+				m_allow_defrag=false;
+			}
+			// m_allow_defrag is a flag that gets set by any operation that may have increased
+			// memory usage, such as placing a piece, or raising the ground.
+			// It is required to prevent an infinite loop of defrags which could occur if defrags
+			// were allowed every frame.
+			
+			if (shown_pct_resources_used >= 1.0f && p_top_down_heap->mp_region->MemAvailable() < TOP_DOWN_REQUIRED_MARGIN)
+			{
+				// We're in trouble, there may not be enough top down heap for UpdateSuperSectors
+				// to execute. This can happen if trying to raise a large chunk of ground.
+				if (p_bottom_up_heap->mFreeMem.m_count + p_top_down_heap->mp_region->MemAvailable() > (TOP_DOWN_REQUIRED_MARGIN + TOP_DOWN_REQUIRED_MARGIN_LEEWAY))
+				{
+					// ... but there would be more than enough if all the fragments could be utilised,
+					// so do a defragment to see if that helps.
+					if (m_allow_defrag)
+					{
+						do_defragment=true;
+					}	
+				}
+			}	
+
+			
+			// The old logic from THPS4, which would only defrag if this flag was set.
+			/*
+			if (usage_info.mIsFragmented)
+			{
+				if (m_allow_defrag)
+				{
+					do_defragment=true;
+				}	
+			}
+			*/
+
+			m_allow_defrag=false;
+			
+			if (do_defragment)
+			{
+				#ifdef __PLAT_NGC__
+				// The regular defragment script often does not fully cure the fragmentation, which
+				// is a problem on the NGC because of its lower memory. So do a full reload instead.
+				Script::SpawnScript(CRCD(0x57c05a9a,"reload_park"));
+				#else
+					// XXX
+					Ryan("defragging, memory free (largest block) is %d\n", usage_info.mMainHeapFree);
+					SetPaused(true);
+					// run a script to defragment memory
+					#ifdef __NOPT_ASSERT__
+					Script::CScript *p_script=Script::SpawnScript("parked_defragment_memory");
+					p_script->SetCommentString("Spawned by CParkEditor::Update()");
+					#else
+					Script::SpawnScript("parked_defragment_memory");
+					#endif
+				#endif
+				return;
+			}
+
+
+
+			// percent_bar_colored_part
+			Front::CScreenElementManager* p_elem_man = Front::CScreenElementManager::Instance();
+			Front::CScreenElement *p_pct_bar = p_elem_man->GetElement(CRCD(0xcd9c729a, "percent_bar_colored_part"), Front::CScreenElementManager::DONT_ASSERT).Convert();
+			if (p_pct_bar)
+				p_pct_bar->SetScale(shown_pct_resources_used, 1.0f, false, Front::CScreenElement::FORCE_INSTANT);
+
+			if (Script::GetInteger(CRCD(0x7a9c1acd,"parked_show_memory_stats")))
+			{
+				Script::RunScript(CRCD(0xb935948d,"CreateMemStatsScreenElements"));
+				
+				Front::CScreenElementManager* p_elem_man = Front::CScreenElementManager::Instance();
+				Front::CTextElement *p_park_free_elem = (Front::CTextElement *) p_elem_man->GetElement(CRCD(0xa5a33eb9, "parked_mem_stats_park_free"), Front::CScreenElementManager::ASSERT).Convert();
+				char park_free_text[128];
+				sprintf(park_free_text, "park heap free:\\c2 %s", Str::PrintThousands(usage_info.mParkHeapFree));
+				p_park_free_elem->SetText(park_free_text);
+	
+				Front::CTextElement *p_main_free_elem = (Front::CTextElement *) p_elem_man->GetElement(CRCD(0xcd38c218, "parked_mem_stats_main_free"), Front::CScreenElementManager::ASSERT).Convert();
+				char main_free_text[128];
+				if (usage_info.mIsFragmented)
+					sprintf(main_free_text, "main heap free:\\c2 %s \\c1FRAG", Str::PrintThousands(usage_info.mMainHeapFree));
+				else
+					sprintf(main_free_text, "main heap free:\\c2 %s", Str::PrintThousands(usage_info.mMainHeapFree));
+				p_main_free_elem->SetText(main_free_text);
+	
+				Front::CTextElement *p_last_op_elem = (Front::CTextElement *) p_elem_man->GetElement(CRCD(0x9dd4532f, "parked_mem_stats_last_op"), Front::CScreenElementManager::ASSERT).Convert();
+				char last_op_text[128];
+				sprintf(last_op_text, "last op size:\\c2 %d", usage_info.mMainHeapUsed - usage_info.mLastMainUsed);
+				p_last_op_elem->SetText(last_op_text);
+	
+				Front::CTextElement *p_other_elem = (Front::CTextElement *) p_elem_man->GetElement(CRCD(0xb42704a3, "parked_mem_stats_other"), Front::CScreenElementManager::ASSERT).Convert();
+				char other_text[128];
+				sprintf(other_text, "components free:\\c2 %d %d", 
+						p_generator->GetResourceSize("max_components") - component_use_est - base_component_use,
+						p_generator->GetResourceSize("max_vectors") - vector_use_est - base_vector_use);
+				p_other_elem->SetText(other_text);
+	
+				Front::CTextElement *p_pct_elem = (Front::CTextElement *) p_elem_man->GetElement(CRCD(0x65f2394f, "parked_mem_stats_pct"), Front::CScreenElementManager::ASSERT).Convert();
+				char pct_text[128];
+				sprintf(pct_text, "pct:\\c3 %.2f", m_pct_resources_used * 100.0f);
+				p_pct_elem->SetText(pct_text);
+	
+				Front::CTextElement *p_pieces_remaining_elem = (Front::CTextElement *) p_elem_man->GetElement(CRCD(0x3da56dac, "parked_mem_pieces_remaining"), Front::CScreenElementManager::ASSERT).Convert();
+				char pieces_remaining_text[128];
+				sprintf(pieces_remaining_text, "pieces remaining:\\c2 %d", p_generator->GetResourceSize("max_dma_pieces") - mp_park_manager->GetDMAPieceCount());
+				p_pieces_remaining_elem->SetText(pieces_remaining_text);
+
+				Front::CTextElement *p_main_used_elem = (Front::CTextElement *) p_elem_man->GetElement(CRCD(0xf95160c8, "parked_mem_stats_main_used"), Front::CScreenElementManager::ASSERT).Convert();
+				char main_used_text[128];
+				sprintf(main_used_text, "main heap used:\\c2 %s", Str::PrintThousands(usage_info.mMainHeapUsed));
+				p_main_used_elem->SetText(main_used_text);
+			}
+			else
+			{
+				Script::RunScript(CRCD(0x7a449180,"DestroyMemStatsScreenElements"));
+			}	
+
+			// Update the clipboard usage bar.			
+			/*
+			Front::CScreenElement *p_clipboard_percent_bar = p_elem_man->GetElement(CRCD(0xf07100ce,"clipboard_percent_bar_colored_part"), Front::CScreenElementManager::DONT_ASSERT).Convert();
+			if (p_clipboard_percent_bar)
+			{
+				p_clipboard_percent_bar->SetScale(mp_park_manager->GetClipboardProportionUsed(), 1.0f, false, Front::CScreenElement::FORCE_INSTANT);
+			}
+			*/
+		} // end if (!m_paused)
+	}
+	else
+	{
+		m_commands.ClearAll();
+	}  
+}
+
+void CParkEditor::SwitchMenuPieceToMostRecentClipboard()
+{
+	CCursor::sInstance()->SwitchMenuPieceToMostRecentClipboard();
+	
+	turn_on_or_update_piece_menu();
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	p_tracker->LaunchEvent(Script::GenerateCRC("parked_menu_up"), Script::GenerateCRC("set_menu_container"));
+}
+
+// Returns true if there is enough memory to copy or paste an area of the given size.
+bool CParkEditor::RoomToCopyOrPaste(int w, int l)
+{
+	#ifdef __PLAT_NGC__
+	Mem::Heap *p_top_down_heap = Mem::Manager::sHandle().TopDownHeap();
+	if (p_top_down_heap->mp_region->MemAvailable() < TOP_DOWN_REQUIRED_MARGIN)
+	{
+		return false;
+	}	
+	#endif
+	
+	// Assumes that in the worst case 1 cell uses up 40K, for example when a bunch of fountain pieces are raised
+	// to the maximum height.
+	return w * l * 40000 < m_last_main_heap_free;
+}
+
+void CParkEditor::SetPaused(bool paused)
+{
+	// XXX
+	Ryan("some fucker has made my pause state = %d\n", paused);
+	m_paused = paused;
+
+	if (mp_menu_manager)
+	{
+		if (paused)
+		{
+			Script::RunScript("parked_kill_wait_message");
+			mp_menu_manager->Disable();
+		}
+		else
+		{
+			mp_menu_manager->Enable();
+			turn_on_or_update_piece_menu();
+		}
+	}
+
+	if (mp_cursor)
+	{
+		if (mp_cursor->InRailPlacementMode())
+		{
+			mp_cursor->SetVisibility(false);
+		}
+		else
+		{
+			if (paused)
+			{
+				mp_cursor->SetVisibility(false);
+			}		
+			else
+			{
+				mp_cursor->SetVisibility(true);
+			}		
+		}	
+		mp_cursor->RefreshHelperText();
+	}	
+
+	update_analog_stick_menu_control_state();
+}
+
+
+
+
+void CParkEditor::turn_on_or_update_piece_menu()
+{
+	int menu_set_number;
+	int set_num = mp_cursor->GetSelectedSet(&menu_set_number);
+	// XXX
+	Ryan("piece set number is %d\n", set_num);
+	Dbg_Assert(mp_menu_manager);
+	mp_menu_manager->SetPieceSet(&mp_park_manager->GetPieceSet(set_num), set_num, menu_set_number);
+	
+	if (mp_cursor)
+	{
+		mp_cursor->RefreshHelperText();
+	}	
+}
+
+
+
+
+void CParkEditor::update_analog_stick_menu_control_state()
+{
+	Spt::SingletonPtr p_front_end;	
+	if (m_state == vEDITING && !m_paused)
+	{
+		p_front_end->SetAnalogStickActiveForMenus(false);
+	}
+	else
+		p_front_end->SetAnalogStickActiveForMenus(true);
+}
+
+
+
+
+void CParkEditor::pass_event_to_listener(Obj::CEvent *pEvent)
+{
+	if (m_state != vEDITING || !mp_cursor || m_paused)
+		return;
+	
+	uint32 type = pEvent->GetType();
+	uint32 target = pEvent->GetTarget();
+
+	if (target != CRCD(0x56a1eae3, "root_window"))
+		return;
+
+#ifdef __PLAT_NGC__
+	if ( m_commands.TestMask( mINCREASE_GAP_RIGHT ) ) return;
+	if ( m_commands.TestMask( mDECREASE_GAP_RIGHT ) ) return;
+	if ( m_commands.TestMask( mINCREASE_GAP_LEFT ) ) return;
+	if ( m_commands.TestMask( mDECREASE_GAP_LEFT ) ) return;
+#endif		// __PLAT_NGC__
+
+	switch(type)
+	{
+		case Obj::CEvent::TYPE_PAD_UP:
+			m_commands.SetMask( mPREV_SET );
+			break;
+		case Obj::CEvent::TYPE_PAD_DOWN:
+			m_commands.SetMask( mNEXT_SET );
+			break;
+		case Obj::CEvent::TYPE_PAD_LEFT:
+			m_commands.SetMask( mPREV_PIECE );
+			break;
+		case Obj::CEvent::TYPE_PAD_RIGHT:
+			m_commands.SetMask( mNEXT_PIECE );
+			break;
+		default:
+			break;
+	}
+	//Ryan("I got an event!!\n");
+}
+
+
+
+
+void CParkEditor::v_start_cb()
+{
+	
+	Ryan("CParkEditor2::v_start_cb\n");
+
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance(); 
+	mlp_manager->AddLogicTask ( *m_logic_task );
+	Inp::Manager * inp_manager = Inp::Manager::Instance(); 
+	inp_manager->AddHandler( *m_input_handler );
+	mlp_manager->AddDisplayTask ( *m_display_task );
+}
+
+
+
+
+void CParkEditor::v_stop_cb()
+{
+	Ryan("CParkEditor2::v_stop_cb\n");
+
+	m_logic_task->Remove();
+	m_input_handler->Remove();
+}
+
+
+
+
+void CParkEditor::s_input_logic_code ( const Inp::Handler < CParkEditor >& handler )
+{
+	CParkEditor &mdl = handler.GetData();
+	// first clear last frame's data
+	mdl.m_commands.ClearAll();
+
+	//if (!mdl.mp_map || mdl.GameGoingOrOutsideEditor() || mdl.InMenu() || !mdl.m_initialized) return;
+	
+	// translate m_Input data to module-specific commands
+	mdl.m_rightStick.m_X = handler.m_Input->m_Event[Inp::Data::vA_RIGHT_X] - 128;
+	mdl.m_rightStick.m_Y = handler.m_Input->m_Event[Inp::Data::vA_RIGHT_Y] - 128;
+	mdl.m_leftStick.m_X = handler.m_Input->m_Event[Inp::Data::vA_LEFT_X] - 128;
+	mdl.m_leftStick.m_Y = handler.m_Input->m_Event[Inp::Data::vA_LEFT_Y] - 128;
+
+#ifdef __PLAT_NGC__
+	if( handler.m_Input->m_Makes & Inp::Data::mD_TRIANGLE )
+	{
+		mdl.m_commands.SetMask( mROTATE_CCW );
+	}
+#else
+	if( handler.m_Input->m_Makes & Inp::Data::mD_SQUARE )
+	{
+		mdl.m_commands.SetMask( mROTATE_CCW );
+	}
+#endif		// __PLAT_NGC__
+	if( handler.m_Input->m_Makes & Inp::Data::mD_CIRCLE )
+	{
+		mdl.m_commands.SetMask( mROTATE_CW );
+	}
+	if( handler.m_Input->m_Makes & Inp::Data::mD_X )
+	{
+		mdl.m_commands.SetMask( mPLACE_PIECE );
+	}
+#ifdef __PLAT_NGC__
+	if( handler.m_Input->m_Makes & Inp::Data::mD_SQUARE )
+	{
+		mdl.m_commands.SetMask( mREMOVE_PIECE );
+	}
+#else
+	if( handler.m_Input->m_Makes & Inp::Data::mD_TRIANGLE )
+	{
+		mdl.m_commands.SetMask( mREMOVE_PIECE );
+	}
+#endif		// __PLAT_NGC__
+#ifdef __PLAT_NGC__
+	if( !(handler.m_Input->m_Buttons & Inp::Data::mD_Z) )
+	{
+		if( handler.m_Input->m_Makes & Inp::Data::mD_L1 )
+		{
+			mdl.m_commands.SetMask( mRAISE_FLOOR );
+		}
+		if( handler.m_Input->m_Makes & Inp::Data::mD_R1 )
+		{
+			mdl.m_commands.SetMask( mLOWER_FLOOR );
+		}
+	}
+#else
+	if( handler.m_Input->m_Makes & Inp::Data::mD_L1 )
+	{
+		mdl.m_commands.SetMask( mRAISE_FLOOR );
+	}
+	if( handler.m_Input->m_Makes & Inp::Data::mD_L2 )
+	{
+		mdl.m_commands.SetMask( mLOWER_FLOOR );
+	}
+#endif		// __PLAT_NGC__
+#ifdef __PLAT_NGC__
+	if( ( handler.m_Input->m_Makes & Inp::Data::mD_UP ) && ( handler.m_Input->m_Buttons & Inp::Data::mD_R1 ) )
+	{
+		mdl.m_commands.SetMask( mINCREASE_GAP_RIGHT );
+	}
+	if( ( handler.m_Input->m_Makes & Inp::Data::mD_DOWN ) && ( handler.m_Input->m_Buttons & Inp::Data::mD_R1 ) )
+	{
+		mdl.m_commands.SetMask( mDECREASE_GAP_RIGHT );
+	}
+	if( ( handler.m_Input->m_Makes & Inp::Data::mD_UP ) && ( handler.m_Input->m_Buttons & Inp::Data::mD_L1 ) )
+	{
+		mdl.m_commands.SetMask( mINCREASE_GAP_LEFT );
+	}
+	if( ( handler.m_Input->m_Makes & Inp::Data::mD_DOWN ) && ( handler.m_Input->m_Buttons & Inp::Data::mD_L1 ) )
+	{
+		mdl.m_commands.SetMask( mDECREASE_GAP_LEFT );
+	}
+#else
+	#ifdef __PLAT_XBOX__
+		if( handler.m_Input->m_Makes & Inp::Data::mD_L1 )
+		{
+			mdl.m_commands.SetMask( mINCREASE_GAP_RIGHT );
+		}
+		if( handler.m_Input->m_Makes & Inp::Data::mD_L2 )
+		{
+			mdl.m_commands.SetMask( mDECREASE_GAP_RIGHT );
+		}
+		if( handler.m_Input->m_Makes & Inp::Data::mD_BLACK )
+		{
+			mdl.m_commands.SetMask( mINCREASE_GAP_LEFT );
+		}
+		if( handler.m_Input->m_Makes & Inp::Data::mD_WHITE )
+		{
+			mdl.m_commands.SetMask( mDECREASE_GAP_LEFT );
+		}
+	#else
+		if( handler.m_Input->m_Makes & Inp::Data::mD_R1 )
+		{
+			mdl.m_commands.SetMask( mINCREASE_GAP_RIGHT );
+		}
+		if( handler.m_Input->m_Makes & Inp::Data::mD_R2 )
+		{
+			mdl.m_commands.SetMask( mDECREASE_GAP_RIGHT );
+		}
+		if( handler.m_Input->m_Makes & Inp::Data::mD_L1 )
+		{
+			mdl.m_commands.SetMask( mINCREASE_GAP_LEFT );
+		}
+		if( handler.m_Input->m_Makes & Inp::Data::mD_L2 )
+		{
+			mdl.m_commands.SetMask( mDECREASE_GAP_LEFT );
+		}
+	#endif
+	
+#endif		// __PLAT_NGC__
+#ifdef __PLAT_NGC__
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_Z )
+	{
+		if( handler.m_Input->m_Buttons & Inp::Data::mD_L1 )
+		{
+			mdl.m_commands.SetMask( mZOOM_CAMERA_IN );
+		}
+		if( handler.m_Input->m_Buttons & Inp::Data::mD_R1 )
+		{
+			mdl.m_commands.SetMask( mZOOM_CAMERA_OUT );
+		}
+	}
+#else
+#ifdef __PLAT_XBOX__
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_BLACK )
+	{
+		mdl.m_commands.SetMask( mZOOM_CAMERA_OUT );
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_WHITE )
+	{
+		mdl.m_commands.SetMask( mZOOM_CAMERA_IN );
+	}
+#else
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_R1 )
+	{
+		mdl.m_commands.SetMask( mZOOM_CAMERA_OUT );
+	}
+	if( handler.m_Input->m_Buttons & Inp::Data::mD_R2 )
+	{
+		mdl.m_commands.SetMask( mZOOM_CAMERA_IN );
+	}
+#endif		// __PLAT_XBOX__	
+#endif		// __PLAT_NGC__
+}
+
+
+
+
+void CParkEditor::s_logic_code ( const Tsk::Task< CParkEditor >& task )
+{
+	
+
+	CParkEditor &mdl = task.GetData();
+	//if (mdl.mp_commandParams)
+	//	mdl.runCommand();	
+	//if (!mdl.mp_map || mdl.GameGoingOrOutsideEditor() || !mdl.m_initialized) return;
+
+	/*
+	// put skater up in air HACK
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetLocalSkater();
+	if (pSkater)
+		pSkater->m_pos.Set(4000.0f, 4000.0f, 0.0f);
+	*/
+	
+	Tmr::Time elapsed_time;
+	static Tmr::Time last_time = 0;
+	
+	elapsed_time = Tmr::ElapsedTime( last_time );
+	last_time = last_time + elapsed_time;   
+	
+	mdl.Update();
+}
+
+
+
+
+void CParkEditor::s_display_code ( const Tsk::Task< CParkEditor >& task )
+{
+	//CParkEditor &mdl = task.GetData();
+	//if (!mdl.mp_map || mdl.GameGoingOrOutsideEditor() || !mdl.m_initialized) return;
+	
+	//mdl.Draw();
+}
+
+void CParkEditor::BindParkEditorToController( int controller )
+{
+	Spt::SingletonPtr< Inp::Manager > inp_manager;
+	inp_manager->ReassignHandler( *m_input_handler, controller );
+}
+
+Mth::Vector& CParkEditor::GetCursorPos()
+{
+	Dbg_MsgAssert(mp_cursor,("NULL mp_cursor"));
+	return mp_cursor->m_pos;
+}
+
+void CParkEditor::DestroyClipboardsWhichAreTooBigToFit()
+{
+	if (mp_cursor)
+	{
+		mp_cursor->DestroyClipboardsWhichAreTooBigToFit();
+	}	
+}
+
+void CParkEditor::SetTimeOfDayScript(uint32 tod_script)
+{
+	m_tod_script=tod_script;
+}	
+
+CCursor *CCursor::sp_instance = NULL;
+const float CCursor::MOTION_PCT_INC = .15f;
+const float CCursor::ROT_DEG_INC = 15.0f;
+
+
+
+
+CCursor::CCursor() :
+	m_cell_dims(0, 0, 0),
+	m_target_pos(0.0f, 0.0f, 0.0f),
+	m_pos(0.0f, 0.0f, 0.0f),
+	m_hard_rot(Mth::ROT_0),
+	m_target_rot(0.0f),
+	m_rot(0.0f),
+	mp_meta(NULL)
+{
+	m_num_corners_selected=0;
+	m_initialised_highlight=false;
+	
+	mp_clipboards=NULL;
+	mp_current_clipboard=NULL;
+	
+	m_hard_rot = Mth::ROT_0;
+
+	m_motion_pct = 1.0f;
+
+	m_selected_set = 0;
+	m_menu_set_number = 0;
+	
+	m_current_gap.numCompleteHalves = 0;
+	m_current_gap.mCancelFlags=0;
+	
+	m_mode = NONE;
+	
+	CParkManager::CPieceSet &piece_set = m_manager->GetPieceSet(m_selected_set, &m_menu_set_number);
+	set_source_meta(piece_set.mEntryTab[piece_set.mSelectedEntry].mNameCrc);
+
+	m_gap_suffix_counter=1;
+	
+	sp_instance = this;
+}
+
+CCursor::~CCursor()
+{
+	#ifndef DONT_USE_CURSOR_META
+	if (mp_meta)
+	{
+		mp_meta->MarkUnlocked();
+		m_manager->DestroyConcreteMeta(mp_meta, CParkManager::mDONT_DESTROY_PIECES_ABOVE);
+	}
+	
+	#endif
+
+	destroy_clipboards();
+	
+	sp_instance = NULL;
+}
+
+// K: Added to test something
+void CCursor::DeleteMeta()
+{
+	if (mp_meta)
+	{
+		delete mp_meta;
+		mp_meta=NULL;
+	}
+}
+		
+void CCursor::destroy_clipboards()
+{
+	CClipboard *p_clipboard=mp_clipboards;
+	while (p_clipboard)
+	{
+		CClipboard *p_next=p_clipboard->mpNext;
+		delete p_clipboard;
+		p_clipboard=p_next;
+	}	
+	mp_current_clipboard=NULL;
+}
+
+// Called when the park is resized.
+void CCursor::DestroyClipboardsWhichAreTooBigToFit()
+{
+	CClipboard *p_last=NULL;
+	CClipboard *p_clipboard=mp_clipboards;
+	while (p_clipboard)
+	{
+		CClipboard *p_next=p_clipboard->mpNext;
+		
+		GridDims area;
+		p_clipboard->GetArea(this,&area);
+
+		if (area.GetW() > m_manager->GetParkNearBounds().GetW() ||
+			area.GetW() > m_manager->GetParkNearBounds().GetL() ||
+			area.GetL() > m_manager->GetParkNearBounds().GetW() ||
+			area.GetL() > m_manager->GetParkNearBounds().GetL())
+		{
+			if (p_last)
+			{
+				p_last->mpNext=p_next;
+			}	
+			if (p_clipboard==mp_clipboards)
+			{
+				mp_clipboards=p_next;
+			}
+				
+			delete p_clipboard;
+			p_clipboard=p_next;
+		}
+		else
+		{
+			p_last=p_clipboard;
+			p_clipboard=p_next;
+		}	
+	}	
+	
+	mp_current_clipboard=mp_clipboards;
+
+	// Refresh since mp_current_clipboard has changed.
+	ChangePieceInSet(0);
+}
+
+void CCursor::DestroyGeometry()
+{
+	if (mp_meta)
+	{
+		mp_meta->MarkUnlocked();
+		m_manager->DestroyConcreteMeta(mp_meta, CParkManager::mDONT_DESTROY_PIECES_ABOVE);
+		mp_meta = NULL;
+	}
+}
+
+void CCursor::DestroyAnyClipboardCursors()
+{
+	CClipboard *p_clipboard=mp_clipboards;
+	while (p_clipboard)
+	{
+		p_clipboard->DestroyMetas();
+		p_clipboard=p_clipboard->mpNext;
+	}	
+}
+
+void CCursor::Update(float shiftX, float shiftZ, int rotInc)
+{
+	/*
+		==================================================
+		ROTATION SECTION
+		==================================================
+	*/
+	
+	if (rotInc > 0)
+	{
+		m_hard_rot = Mth::ERot90((m_hard_rot + 1) & 3);
+		m_target_rot += 90.0f;
+		if (m_target_rot > 360.0f)
+		{
+			m_target_rot -= 360.0f;
+			m_rot -= 360.0f;
+		}
+		m_cell_dims.SetWHL(m_cell_dims.GetL(), m_cell_dims.GetH(), m_cell_dims.GetW());
+	}
+	else if (rotInc < 0)
+	{
+		m_hard_rot = Mth::ERot90((m_hard_rot - 1) & 3);
+		m_target_rot -= 90.0f;
+		if (m_target_rot < 0.0f)
+		{
+			m_target_rot += 360.0f;
+			m_rot += 360.0f;
+		}
+		m_cell_dims.SetWHL(m_cell_dims.GetL(), m_cell_dims.GetH(), m_cell_dims.GetW());
+	}
+
+	/*
+		==================================================
+		CELL POSITIONING SECTION
+		==================================================
+	*/
+	
+	const float near_zero = .0001f;
+	//const float tolerance = .5f;
+	
+	float shift_x_squared = shiftX * shiftX;
+	float shift_z_squared = shiftZ * shiftZ;
+
+	bool actual_shift = (shift_x_squared > near_zero * near_zero || shift_z_squared > near_zero * near_zero);
+	
+	// can't shift to new cell if cursor currently in motion
+	if (actual_shift && m_motion_pct <= 0.0f)
+	{
+		if (shift_z_squared > 5.8284f * shift_x_squared)
+		{
+			if (shiftZ < 0.0f)
+				change_cell_pos(0, -1);
+			else
+				change_cell_pos(0, 1);
+			m_motion_pct = 1.0f;
+		}
+		else if (shift_x_squared > 5.8284f * shift_z_squared)
+		{
+			if (shiftX < 0.0f)
+				change_cell_pos(-1, 0);
+			else
+				change_cell_pos(1, 0);
+			m_motion_pct = 1.0f;
+		}
+		else
+		{
+			if (shiftZ < 0.0f)
+				change_cell_pos(0, -1);
+			else
+				change_cell_pos(0, 1);
+	
+			if (shiftX < 0.0f)
+				change_cell_pos(-1, 0);
+			else
+				change_cell_pos(1, 0);
+			m_motion_pct = 1.0f;
+		}
+	}
+	else
+	{
+		change_cell_pos(0, 0);
+	}
+	
+	#if 0
+	printf("cell (%d,%d), shown (%.2f,%.2f)\n", 
+		   m_cell_dims.GetX(), m_cell_dims.GetZ(), 
+		   m_pos[X], m_pos[Z]);
+	#endif
+	
+	/*
+		==================================================
+		ANIMATION SECTION
+		==================================================
+	*/
+
+	//m_cell_dims.PrintContents();
+	
+	// derive m_target_pos from m_cell_dims
+	
+	Mth::Vector world_dims;
+	m_target_pos = m_manager->GridCoordinatesToWorld(m_cell_dims, &world_dims);
+	m_target_pos[X] += world_dims[X] / 2.0f;
+	m_target_pos[Z] += world_dims[Z] / 2.0f;
+
+	if (m_motion_pct > 0.0f)
+	{
+		m_pos[X] += (m_target_pos[X] - m_pos[X]) * MOTION_PCT_INC / m_motion_pct;
+		m_pos[Z] += (m_target_pos[Z] - m_pos[Z]) * MOTION_PCT_INC / m_motion_pct;
+		m_pos[Y] = m_target_pos[Y];
+		m_motion_pct -= MOTION_PCT_INC;
+		if (m_motion_pct < 0.0f) m_motion_pct = 0.0f;
+	}
+	else
+	{
+		m_pos = m_target_pos;
+		m_motion_pct = 0.0f;
+	}
+	
+	if (m_rot < m_target_rot)
+	{
+		m_rot += ROT_DEG_INC;
+		if (m_rot > m_target_rot)
+			m_rot = m_target_rot;
+	}
+	else if (m_rot > m_target_rot)
+	{
+		m_rot -= ROT_DEG_INC;
+		if (m_rot < m_target_rot)
+			m_rot = m_target_rot;
+	}
+
+	/*	
+	Script::CStruct *p_params=new Script::CStruct;
+	p_params->AddVector(CRCD(0x7f261953,"pos"),m_pos[X],m_pos[Y],m_pos[Z]);
+	Script::RunScript(CRCD(0x9796fc9f,"ParkEdCursorPos"),p_params);
+	delete p_params;
+	*/
+	
+	#ifndef DONT_USE_CURSOR_META
+	// Mick:  TT#3804
+	// Adding the vector fixes Z-Fighting between cursor and identical piece
+	if (m_mode==PASTE)
+	{
+		if (mp_current_clipboard)
+		{
+			//printf("m_clipboard_y=%d\n",m_clipboard_y);
+			mp_current_clipboard->ShowMetas(m_pos + Mth::Vector(-1.5f,+1.5f,-1.5f), m_rot, m_clipboard_y);
+		}	
+	}
+	else
+	{
+		DestroyAnyClipboardCursors();
+		if (m_mode != RAIL_PLACEMENT)
+		{
+			Dbg_Assert(mp_meta);
+			mp_meta->SetSoftRot(m_pos + Mth::Vector(-1.5f,+1.5f,-1.5f), m_rot);		  // Mick: Changed to move up, so water does not vanish on GC
+		}	
+	}	
+	#endif
+}
+
+
+
+bool CCursor::AttemptStamp()
+{
+	Dbg_Assert(mp_meta);
+	ParkEd("CCursor::AttemptStamp()");
+	
+	if (!m_manager->CanPlacePiecesIn(m_cell_dims))
+	{
+		return false;
+	}
+	
+	// If we are trying to put down a restart point then we need to see if there are 
+	// any free slots avaiable	
+	CParkGenerator::RestartType restart_type = m_manager->IsRestartPiece(mp_meta->GetNameChecksum());
+	if (restart_type != CParkGenerator::vEMPTY)
+	{
+		//printf("Attempting to stamp a restart piece type %d\n", restart_type);			
+		if (! m_manager->GetGenerator()->FreeRestartExists(restart_type))
+		{
+			// no free slots for this restart point
+			printf ("No free slots exist for this type of restart point\n");
+			return false;
+		}
+	}
+		
+	#ifndef DONT_USE_CURSOR_META
+	CAbstractMetaPiece *p_abstract = m_manager->GetAbstractMeta(mp_meta->GetNameChecksum());
+	Dbg_MsgAssert(p_abstract, ("couldn't find abstract piece %s", Script::FindChecksumName(mp_meta->GetNameChecksum())));
+	CConcreteMetaPiece *p_concrete = m_manager->CreateConcreteMeta(p_abstract);
+	p_concrete->SetRot(m_hard_rot);
+	m_manager->AddMetaPieceToPark(m_cell_dims, p_concrete);
+	#endif
+	
+	m_manager->RebuildFloor();
+	
+	return true;
+}
+
+bool CCursor::AttemptRemove()
+{
+	ParkEd("CCursor::AttemptRemove()");
+	
+	GridDims area;
+	if (m_mode==PASTE)
+	{
+		if (mp_current_clipboard)
+		{
+			mp_current_clipboard->GetArea(this,&area);
+		}	
+		else
+		{
+			return false;
+		}	
+	}
+	else
+	{
+		area=m_cell_dims;
+	}	
+	
+	return m_manager->DestroyMetasInArea(area);
+}
+
+
+
+
+/*
+	A callback called by CParkManager::DestroyConcreteMeta()
+*/
+void CCursor::InformOfMetapieceDeletion(CConcreteMetaPiece *pMeta)
+{
+	int gap_half;
+	GridDims meta_area = pMeta->GetArea();
+	CGapManager::GapDescriptor *p_gap_desc = m_manager->GetGapDescriptor(meta_area, &gap_half);
+	if (p_gap_desc && is_gap_descriptor_same_as_current(p_gap_desc))
+		m_current_gap.numCompleteHalves = 0;
+}
+
+
+
+
+bool CCursor::AttemptGap()
+{
+	Spt::SingletonPtr p_editor;
+	if (p_editor->IsParkFull())
+		return false;
+	
+	CMapListTemp meta_list = m_manager->GetMetaPiecesAt(m_cell_dims);
+	if (meta_list.IsEmpty()) 
+	{
+		// XXX
+		Ryan("no gap for you!!!!\n");
+		return false;
+	}
+	CConcreteMetaPiece *p_meta = meta_list.GetList()->GetConcreteMeta();
+	// XXX
+	Ryan("attempting gap at: ");
+	m_cell_dims.PrintContents();
+	
+	int half;
+	CGapManager::GapDescriptor *pDesc = m_manager->GetGapDescriptor(m_cell_dims, &half);
+	if (pDesc)
+	{
+		// gap already attached at this location
+		if (m_current_gap.numCompleteHalves == 1)
+		{
+			// user already working on gap, so fail
+			return false;
+		}
+	}
+	else if (p_meta)
+	{
+		// metapiece found at this location without gap attached
+		if (m_current_gap.numCompleteHalves == 1)
+			// remove gap half placed so far. It'll get rebuilt later.
+			m_manager->RemoveGap(m_current_gap);
+		else
+			// make sure not 2
+			m_current_gap.numCompleteHalves = 0;
+
+		int active_gap_half = m_current_gap.numCompleteHalves;
+		
+		GridDims gap_area = p_meta->GetArea();
+		
+		// start gap 
+		m_current_gap.loc[active_gap_half] = gap_area;
+		m_current_gap.rot[active_gap_half] = 0;
+		m_current_gap.leftExtent[active_gap_half] = 0;
+		m_current_gap.rightExtent[active_gap_half] = 0;
+		m_current_gap.score = 100;
+		m_current_gap.numCompleteHalves = active_gap_half + 1;
+		m_current_gap.tabIndex = -1;
+	
+		if (active_gap_half == 0)
+		{
+			// new gap, so give default name
+			if (m_gap_suffix_counter > 1)
+			{
+				// K: Make sure the default names are different otherwise when registered in the 
+				// CSkaterCareer it will not get added cos it will think it is the same gap
+				// as the last.
+				sprintf(m_current_gap.text, "gap%d",m_gap_suffix_counter);
+			}
+			else
+			{
+				strcpy(m_current_gap.text, "gap");
+			}
+			++m_gap_suffix_counter;
+		}
+		
+		if (!m_manager->AddGap(m_current_gap))
+		{
+			m_current_gap.numCompleteHalves = 0;
+			return false;
+		}
+		m_manager->RebuildFloor();
+		if (m_current_gap.numCompleteHalves > 0)
+			m_manager->HighlightMetasWithGaps(true, &m_current_gap);
+
+		return true;
+	}	
+	
+	return false;
+}
+
+
+
+
+bool CCursor::AttemptRemoveGap()
+{
+	if (m_current_gap.numCompleteHalves == 1)
+	{
+		// remove gap being worked on
+		m_manager->RemoveGap(m_current_gap);
+		m_current_gap.numCompleteHalves = 0;
+		m_manager->RebuildFloor();
+		return true;
+	}
+
+	int half;
+	CGapManager::GapDescriptor *pDesc = m_manager->GetGapDescriptor(m_cell_dims, &half);
+	if (pDesc)
+	{
+		// remove gap at this position
+		m_manager->RemoveGap(*pDesc);
+		m_manager->RebuildFloor();
+		m_manager->HighlightMetasWithGaps(true, NULL);
+		return true;
+	}
+
+	return false;
+}
+
+
+
+
+bool CCursor::AdjustGap(int rot, int leftChange, int rightChange)
+{
+	// grab gap at this location
+	int half;
+	CGapManager::GapDescriptor *p_desc = m_manager->GetGapDescriptor(m_cell_dims, &half);
+	if (!p_desc)
+	{
+		// no gap, so leave
+		return false;
+	}
+
+	if (!is_gap_descriptor_same_as_current(p_desc))
+		// we are currently working on a gap and it's not the same as the one at this location, so fail
+		return false;
+	
+	CGapManager::GapDescriptor desc_temp = *p_desc;
+	bool needs_change = false;
+
+	if (rot != 0)
+	{
+		desc_temp.rot[half] -= rot;
+		desc_temp.leftExtent[half] = 0;
+		desc_temp.rightExtent[half] = 0;
+		needs_change = true;
+	}
+
+	if (leftChange != 0)
+	{
+		desc_temp.leftExtent[half] += leftChange;
+		if (desc_temp.leftExtent[half] < 0)
+			desc_temp.leftExtent[half] = 0;
+		else if (desc_temp.leftExtent[half] > 6)
+			desc_temp.leftExtent[half] = 6;
+		needs_change = true;
+	}
+	
+	if (rightChange != 0)
+	{
+		desc_temp.rightExtent[half] += rightChange;
+		if (desc_temp.rightExtent[half] < 0)
+			desc_temp.rightExtent[half] = 0;
+		if (desc_temp.rightExtent[half] > 6)
+			desc_temp.rightExtent[half] = 6;
+		needs_change = true;
+	}
+
+	if (needs_change)
+	{
+		m_manager->RemoveGap(*p_desc);
+		desc_temp.rot[half] &= 3;
+
+		GridDims park_bounds = m_manager->GetParkNearBounds();
+		
+		// make sure gap boundaries are within map
+		if (desc_temp.rot[half] == 0)
+		{
+			if (desc_temp.loc[half].GetZ() - desc_temp.rightExtent[half] < park_bounds.GetZ())
+				desc_temp.rightExtent[half]--;
+			if (desc_temp.loc[half].GetZ() + desc_temp.loc[half].GetL() + desc_temp.leftExtent[half] > park_bounds.GetZ() + park_bounds.GetL())
+				desc_temp.leftExtent[half]--;
+		}
+		else if (desc_temp.rot[half] == 2)
+		{
+			if (desc_temp.loc[half].GetZ() - desc_temp.leftExtent[half] < park_bounds.GetZ())
+				desc_temp.leftExtent[half]--;
+			if (desc_temp.loc[half].GetZ() + desc_temp.loc[half].GetL() + desc_temp.rightExtent[half] > park_bounds.GetZ() + park_bounds.GetL())
+				desc_temp.rightExtent[half]--;
+		}
+		else if (desc_temp.rot[half] == 1)
+		{
+			if (desc_temp.loc[half].GetX() - desc_temp.rightExtent[half] < park_bounds.GetX())
+				desc_temp.rightExtent[half]--;
+			if (desc_temp.loc[half].GetX() + desc_temp.loc[half].GetW() + desc_temp.leftExtent[half] > park_bounds.GetX() + park_bounds.GetW())
+				desc_temp.leftExtent[half]--;
+		}
+		else if (desc_temp.rot[half] == 3)
+		{
+			if (desc_temp.loc[half].GetX() - desc_temp.leftExtent[half] < park_bounds.GetX())
+				desc_temp.leftExtent[half]--;
+			if (desc_temp.loc[half].GetX() + desc_temp.loc[half].GetW() + desc_temp.rightExtent[half] > park_bounds.GetX() + park_bounds.GetW())
+				desc_temp.rightExtent[half]--;
+		}
+
+		// XXX
+		for (half = 0; half < 2; half++)
+			Ryan("gap details for half %d (%d,%d,%d),(%d,%d,%d) left=%d right=%d\n", half,
+				 desc_temp.loc[half].GetX(), desc_temp.loc[half].GetY(), desc_temp.loc[half].GetZ(),
+				 desc_temp.loc[half].GetW(), desc_temp.loc[half].GetH(), desc_temp.loc[half].GetL(),
+				 desc_temp.leftExtent[half], desc_temp.rightExtent[half]);
+
+		m_manager->AddGap(desc_temp);
+		m_manager->RebuildFloor();
+		
+		if (m_current_gap.numCompleteHalves == 1)
+		{
+			m_current_gap = desc_temp;
+		}
+	}
+
+	return true;
+}
+
+
+
+
+void CCursor::SetGapNameAndScore(const char *pGapName, int score)
+{
+	if (pGapName)
+	{
+		Dbg_MsgAssert(strlen(pGapName) <= 31, ("too many characters in gap name"));
+		if (*pGapName == '\0')
+			strcpy(m_current_gap.text, "x");
+		else
+			strcpy(m_current_gap.text, pGapName);
+	}
+	if (score >= 0)
+	{
+		m_current_gap.score = score;
+	}
+	Dbg_Assert(m_current_gap.tabIndex >= 0);
+	CGapManager::sInstance()->SetGapInfo(m_current_gap.tabIndex, m_current_gap);
+}
+
+void CCursor::SetGapCancelFlags(Script::CStruct *p_cancelFlags)
+{
+	if (!p_cancelFlags)
+	{
+		return;
+	}
+	
+	uint32 cancel_flags=0;
+	Script::CComponent *p_comp=p_cancelFlags->GetNextComponent();
+	while (p_comp)
+	{
+		Dbg_MsgAssert(p_comp->mNameChecksum==0 && p_comp->mType==ESYMBOLTYPE_NAME,("Gap cancel flag structure must contain only names"));
+		cancel_flags |= Script::GetInteger(p_comp->mChecksum);
+		p_comp=p_cancelFlags->GetNextComponent(p_comp);
+	}	
+	
+	m_current_gap.mCancelFlags=cancel_flags;
+	
+	Dbg_Assert(m_current_gap.tabIndex >= 0);
+	CGapManager::sInstance()->SetGapInfo(m_current_gap.tabIndex, m_current_gap);
+}
+
+
+const char *CCursor::GetGapName()
+{
+	return m_current_gap.text;
+}
+
+
+int CCursor::AttemptAreaSelect()
+{
+	Dbg_MsgAssert(m_mode==AREA_SELECT,("Called AttemptAreaSelect() with m_mode!=AREA_SELECT"));
+	Dbg_MsgAssert(m_num_corners_selected<=2,("Bad m_num_corners_selected of %d",m_num_corners_selected));
+
+	if (m_num_corners_selected==0)
+	{
+		mp_area_corners[0]=m_cell_dims;
+		mp_area_corners[1]=m_cell_dims;
+		m_num_corners_selected=1;
+		ForceSelectionAreaHighlightRefresh();
+	}
+	else if (m_num_corners_selected==1)
+	{
+		GridDims area;
+		GetAreaSelectDims(&area);
+		if (area.GetW() <= CLIPBOARD_MAX_W && area.GetL() <= CLIPBOARD_MAX_L)
+		{
+			m_num_corners_selected=2;
+		}	
+	}
+	
+	return m_num_corners_selected;
+}
+
+void CCursor::ClearAreaSelection()
+{
+	GridDims area;
+	GetAreaSelectDims(&area);
+	
+	m_manager->HighlightMetasInArea(area,false);
+	
+	m_num_corners_selected=0;
+	m_initialised_highlight=false;
+}
+
+void CCursor::ContinueAreaSelection()
+{
+	m_num_corners_selected=1;
+	// Need to force it to re-highlight the selection area next time around
+	// because when quitting the paused menu something resets all the highlights.
+	ForceSelectionAreaHighlightRefresh();
+}
+
+void CCursor::DeleteSelectedPieces()
+{
+	if (!m_num_corners_selected)
+	{
+		return;
+	}
+		
+	GridDims dims;
+	GetAreaSelectDims(&dims);
+
+	// First true means do not destroy any flags or restarts
+	// Second true means only destroy if completely within the area.
+	m_manager->DestroyMetasInArea(dims, true, true);
+
+	ContinueAreaSelection();
+}
+
+void CCursor::ResetSelectedHeights()
+{
+	if (!m_num_corners_selected)
+	{
+		return;
+	}
+		
+	GridDims dims;
+	GetAreaSelectDims(&dims);
+
+	m_manager->ResetFloorHeights(dims);
+	
+	ContinueAreaSelection();
+}
+	
+void CCursor::get_selected_area_coords(uint8 *p_x1, uint8 *p_z1, uint8 *p_x2, uint8 *p_z2)
+{
+	uint8 x1=mp_area_corners[0].GetX();
+	uint8 z1=mp_area_corners[0].GetZ();
+	uint8 x2=mp_area_corners[1].GetX();
+	uint8 z2=mp_area_corners[1].GetZ();
+	
+	if (x2 < x1)
+	{
+		uint8 temp=x1;
+		x1=x2;
+		x2=temp;
+	}
+
+	if (z2 < z1)
+	{
+		uint8 temp=z1;
+		z1=z2;
+		z2=temp;
+	}
+	
+	*p_x1=x1;
+	*p_z1=z1;
+	*p_x2=x2;
+	*p_z2=z2;
+}
+
+void CCursor::GetAreaSelectDims(GridDims *p_dims)
+{
+	if (m_num_corners_selected==0)
+	{
+		p_dims->SetXYZ(0,0,0);
+		p_dims->SetWHL(0,0,0);
+		return;
+	}	
+	
+	uint8 x1,z1,x2,z2;
+	get_selected_area_coords(&x1,&z1,&x2,&z2);
+	
+	p_dims->SetXYZ(x1,0,z1);
+	p_dims->SetWHL(x2-x1+1,1,z2-z1+1);
+}
+
+bool CCursor::DestroyMetasInCurrentArea()
+{
+	GridDims area;
+	GetAreaSelectDims(&area);
+
+	return CParkManager::sInstance()->DestroyMetasInArea(area);
+}
+
+void CCursor::DeleteOldestClipboard()
+{
+	// Find p_last, the last clipboard in the list.
+	CClipboard *p_clip=mp_clipboards;
+	CClipboard *p_last=NULL;
+	CClipboard *p_next_to_last=NULL;
+	while (p_clip)
+	{
+		p_next_to_last=p_last;
+		p_last=p_clip;
+		
+		p_clip=p_clip->mpNext;
+	}
+	if (!p_last)
+	{
+		// Cannot do anything because there are no clipboards to delete
+		return;
+	}
+		
+	delete p_last;
+	
+	if (p_next_to_last)
+	{
+		p_next_to_last->mpNext=NULL;
+	}
+	else
+	{
+		mp_clipboards=NULL;
+	}
+}
+
+bool CCursor::SelectionAreaTooBigToCopy()
+{
+	GridDims area;
+	GetAreaSelectDims(&area);
+
+	if (area.GetW() > CLIPBOARD_MAX_W || area.GetL() > CLIPBOARD_MAX_L)
+	{
+		return true;
+	}
+	
+	Spt::SingletonPtr p_editor;
+	if (!p_editor->RoomToCopyOrPaste(area.GetW(), area.GetL()))
+	{
+		return true;
+	}	
+	
+	return false;
+}
+		
+bool CCursor::CopySelectionToClipboard()
+{
+	if (SelectionAreaTooBigToCopy())
+	{
+		return false;
+	}
+		
+	GridDims area;
+	GetAreaSelectDims(&area);
+	
+	// TODO TODO TODO
+	// If not enough rail points to copy the rails, check to see if any of the existing clipboards contain
+	// rails. If so, delete enough of them to free up enough points. If still not enough points, bail out.
+	// If enough, carry on with the next loop.
+	Mth::Vector corner_pos;
+	Mth::Vector area_dims;
+	corner_pos=Ed::CParkManager::Instance()->GridCoordinatesToWorld(area,&area_dims);
+	int num_rail_points_needed=Obj::GetRailEditor()->CountPointsInArea(corner_pos[X], corner_pos[Z], 
+																	   corner_pos[X]+area_dims[X], corner_pos[Z]+area_dims[Z]);
+	if (num_rail_points_needed > Obj::GetRailEditor()->GetNumFreePoints())
+	{
+		return false;
+	}	
+
+
+
+	if (GetNumClipboards()==MAX_CLIPBOARDS)
+	{
+		// All the CClipboard's in the pool have already been allocated.
+		
+		// Find the oldest clipboard and delete it.
+		DeleteOldestClipboard();
+		
+		// That will have freed up space for one more CClipboard
+		// so the new CClipboard below should not assert.
+		// If it does then something strange is going on, like MAX_CLIPBOARDS is 0 or something.
+	}
+		
+	CClipboard *p_clipboard=new CClipboard;
+
+	
+	
+	// CopySelectionToClipboard will use up CClipboardEntry's, of which there are only
+	// a certain number allocated in a pool.
+	// So CopySelectionToClipboard may fail, even though the CClipboard pool may not be full.
+	
+	// Keep deleting old clipboards until CopySelectionToClipboard succeeds.
+	// Note that the above newly allocated p_clipboard has not been put in the list
+	// yet so it will not get deleted by accident here.
+	while (!p_clipboard->CopySelectionToClipboard(area))
+	{
+		if (!mp_clipboards)
+		{
+			// Run out of clipboards to delete! If this happens then CopySelectionToClipboard
+			// must be failing because the total number of pieces the user wants to copy 
+			// exceeds the total number of CClipboardEntry's available in the pool, which
+			// is defined by MAX_CLIPBOARD_METAS in clipboard.h
+			break;
+		}			
+		DeleteOldestClipboard();
+	}	
+	
+	if (p_clipboard->IsEmpty())
+	{
+		// Nothing got copied into the clipboard, which means either the user tried to copy a 
+		// blank area or the above attempts at copying failed due to lack of memory.
+		
+		delete p_clipboard;
+		p_clipboard=NULL;
+		
+		return false;
+	}	
+	
+	// Add the new clipboard to the list.
+	p_clipboard->mpNext=mp_clipboards;
+	mp_clipboards=p_clipboard;
+	return true;
+}	
+
+int CCursor::GetNumClipboards()
+{
+	int n=0;
+	CClipboard *p_clipboard=mp_clipboards;
+	while (p_clipboard)
+	{
+		++n;
+		p_clipboard=p_clipboard->mpNext;
+	}
+	return n;	
+}
+
+void CCursor::PasteCurrentClipboard()
+{
+	if (mp_current_clipboard)
+	{
+		mp_current_clipboard->Paste(this);
+	}
+}
+
+CClipboard *CCursor::get_clipboard(int index)
+{
+	CClipboard *p_clipboard=mp_clipboards;
+	
+	for (int i=0; impNext;
+		}
+		else
+		{
+			break;
+		}	
+	}
+	
+	return p_clipboard;
+}
+
+void CCursor::set_current_clipboard(int index)
+{
+	DestroyAnyClipboardCursors();		
+	mp_current_clipboard=get_clipboard(index);
+	
+	/*
+	if (mp_current_clipboard)
+	{
+		m_cell_dims.SetWHL(mp_current_clipboard->GetWidth(),1,mp_current_clipboard->GetLength());
+		if (m_hard_rot & 1)
+		{
+			m_cell_dims.SetWHL(m_cell_dims.GetL(), m_cell_dims.GetH(), m_cell_dims.GetW());
+		}
+	}	
+	*/
+}
+
+void CCursor::remove_clipboard(int index)
+{
+	CClipboard *p_remove=get_clipboard(index);
+	if (!p_remove)
+	{
+		return;
+	}
+	
+	CClipboard *p_search=mp_clipboards;
+	CClipboard *p_last=NULL;
+	while (p_search)
+	{
+		if (p_search==p_remove)
+		{
+			if (p_last)
+			{
+				p_last->mpNext=p_remove->mpNext;
+			}
+			else
+			{
+				mp_clipboards=p_remove->mpNext;
+			}
+			delete p_remove;
+			break;
+		}
+				
+		p_last=p_search;
+		p_search=p_search->mpNext;
+	}	
+}
+
+void CCursor::RefreshSelectionArea()
+{
+	switch (m_num_corners_selected)
+	{
+	case 0:
+		break;
+	case 1:
+		if (!m_initialised_highlight)
+		{
+			GridDims area;
+			GetAreaSelectDims(&area);
+			
+			m_manager->HighlightMetasInArea(area,true);
+			
+			m_initialised_highlight=true;
+		}	
+		else
+		{
+			if (m_cell_dims.GetX() != mp_area_corners[1].GetX() ||
+				m_cell_dims.GetZ() != mp_area_corners[1].GetZ())
+			{
+				GridDims area;
+				GetAreaSelectDims(&area);
+
+				m_manager->HighlightMetasInArea(area,false);
+
+				mp_area_corners[1]=m_cell_dims;
+
+				GetAreaSelectDims(&area);
+
+				m_manager->HighlightMetasInArea(area,true);
+			}
+		}
+		break;
+	default:
+		break;		
+	}
+}
+
+// Find all the pieces intersecting the cursor
+// and turn the highlight effect on or off
+// (In THPS3, the highlight was a wireframe.  Here I think we are going to try coloring it red)
+void CCursor::HighlightIntersectingMetas(bool on)
+{
+	if (m_mode==PASTE)
+	{
+		if (mp_current_clipboard)
+		{
+			mp_current_clipboard->HighlightIntersectingMetas(this,on);
+			return;
+		}	
+	}
+	
+	// Get the list of (concrete) meta pieces that intersect the
+	// dimension of the cursor's m_cell_dims
+	CMapListTemp metas_at_pos = m_manager->GetMetaPiecesAt(m_cell_dims);
+	if (metas_at_pos.IsEmpty())
+	{
+		// Nothing intersecting, so do nothing and return
+		return;
+	}
+
+	// Iterate over the list of metapieces we just found, and highlight each one
+	// (Whcih will actually highlight the pieces
+	CMapListNode *p_entry = metas_at_pos.GetList();
+	while(p_entry)
+	{
+		p_entry->GetConcreteMeta()->Highlight(on);		
+		p_entry = p_entry->GetNext();
+	}
+}
+
+void CCursor::SetVisibility(bool visible)
+{
+	//Dbg_Assert(mp_meta);
+	if (mp_meta)
+	{
+		mp_meta->SetVisibility(visible);
+	}	
+	
+	if (!visible)
+	{
+		if (mp_current_clipboard)
+		{
+			mp_current_clipboard->DestroyMetas();
+		}
+	}		
+	
+	m_initialised_highlight=false;
+}
+
+
+
+void CCursor::ChangePieceInSet(int dir)
+{
+	CParkManager::CPieceSet &piece_set = m_manager->GetPieceSet(m_selected_set, &m_menu_set_number);
+	int selected_entry = piece_set.mSelectedEntry;
+	selected_entry += dir;
+	if (selected_entry < 0)
+		selected_entry = piece_set.mTotalEntries - 1;
+	else if (selected_entry >= piece_set.mTotalEntries)
+		selected_entry = 0;
+	piece_set.mSelectedEntry = selected_entry;
+
+	if (piece_set.mIsClipboardSet)
+	{
+		set_current_clipboard(piece_set.mSelectedEntry);
+	}
+	else
+	{
+		set_source_meta(piece_set.mEntryTab[selected_entry].mNameCrc);
+	}	
+}
+
+void CCursor::ChangeSet(int dir)
+{
+	for (int count = m_manager->GetTotalNumPieceSets(); count > 0; count--)
+	{
+		m_selected_set += dir;
+		if (m_selected_set < 0)
+			m_selected_set = m_manager->GetTotalNumPieceSets() - 1;
+		if (m_selected_set >= m_manager->GetTotalNumPieceSets())
+			m_selected_set = 0;
+		//printf("set is: %s\n", m_palette_set[m_selected_set].mpName);
+		if (m_manager->GetPieceSet(m_selected_set, &m_menu_set_number).mVisible) break;
+	}
+	CParkManager::CPieceSet &piece_set = m_manager->GetPieceSet(m_selected_set, &m_menu_set_number);
+	if (piece_set.mIsClipboardSet)
+	{
+		set_current_clipboard(piece_set.mSelectedEntry);
+		// Destroy the regular cursor meta
+		DestroyGeometry();
+		
+		
+		change_mode(PASTE);
+	}
+	else
+	{
+		set_source_meta(piece_set.mEntryTab[piece_set.mSelectedEntry].mNameCrc);
+	}	
+}
+
+void CCursor::SwitchMenuPieceToMostRecentClipboard()
+{
+	if (!GetNumClipboards())
+	{
+		return;
+	}
+		
+	int num_sets=m_manager->GetTotalNumPieceSets();
+	CParkManager::CPieceSet *p_piece_set=NULL;
+	
+	for (int i=0; iGetPieceSet(i, &m_menu_set_number);
+		if (piece_set.mIsClipboardSet)
+		{
+			m_selected_set=i;
+			p_piece_set=&piece_set;
+			break;
+		}
+	}
+	Dbg_MsgAssert(p_piece_set,("Clipboard piece set not found?"));
+	
+	p_piece_set->mSelectedEntry=0;
+	set_current_clipboard(p_piece_set->mSelectedEntry);
+	// Destroy the regular cursor meta
+	DestroyGeometry();
+	change_mode(PASTE);
+	
+	// Fix to TT14349: Crash bug if copying tree pieces at the edge of the largest map by highlighting starting at the 
+	// lower right corner of the set of trees.
+	// Caused by the cursor being out of bounds for a frame, hence causing it to read outside the map array.
+	ForceInBounds();
+}
+
+bool CCursor::ChangeFloorHeight(GridDims dims, int height, int dir, bool uniformHeight)
+{
+	//printf("CCursor::ChangeFloorHeight: ");
+	
+	/*
+		Uneven floor:
+			If going up, raise to height of topmost
+			If going down, only lower ones on same level
+	*/
+
+	//printf("height=%d uniform_height=%d\n",height,uniformHeight);
+	/*
+		Now, 
+	*/
+
+	if (dims.GetW() > CLIPBOARD_MAX_W || dims.GetL() > CLIPBOARD_MAX_L)
+	{
+		return false;
+	}
+	
+	//Spt::SingletonPtr p_editor;
+	//if (p_editor->IsParkFull())
+	//{
+	//	return false;
+	//}
+		
+	bool success = true;
+	
+		
+	if (m_manager->SlideColumn(dims, dims.GetY(), height, (dir > 0), !uniformHeight))
+	{
+		//Ryan("Slide column, dir = %d, dims=", dir);
+		//dims.PrintContents();
+		
+		change_cell_pos(0, 0);
+		Spt::SingletonPtr p_editor;
+		if (m_manager->RebuildFloor(p_editor->IsParkFull()))
+		{
+			m_manager->HighlightMetasWithGaps(false, NULL);
+		}
+		else
+		{
+			// out of memory, so reverse slide
+			m_manager->UndoSlideColumn();
+			m_manager->RebuildFloor();
+			success = false;
+		}
+	}
+	else
+		success = false;
+
+	return success;
+}
+
+bool CCursor::ChangeFloorHeight(int dir)
+{
+	GridDims dims;
+	switch (m_mode)
+	{
+	case AREA_SELECT:
+		if (m_num_corners_selected)
+		{
+			GetAreaSelectDims(&dims);
+		}
+		else
+		{
+			dims=m_cell_dims;
+		}	
+		break;
+	case PASTE:
+		if (mp_current_clipboard)
+		{
+			mp_current_clipboard->GetArea(this,&dims);
+		}
+		break;
+	default:
+		dims=m_cell_dims;
+		break;
+	}	
+
+	// If the dims exceeds the area of the park, then do not raise or lower.
+	// This can happen if the park is been resized to its smallest possible size and then a big pool
+	// piece selected. If the raise was allowed to go ahead, it would result in the fence raising too. (TT11825)
+	GridDims park_bounds = CParkManager::sInstance()->GetParkNearBounds();	
+	if (dims.GetW() > park_bounds.GetW() || dims.GetL() > park_bounds.GetL())
+	{
+		return false;
+	}	
+	
+	
+	bool uniform_height;
+	int height = m_manager->GetFloorHeight(dims, &uniform_height);
+
+	// If pasting, destroy the clipboard metas, otherwise the dry run that ChangeFloorHeight
+	// does to determine whether it is safe to raise or lower will incorrectly think it
+	// is safe when it is not. I think the function generate_floor_pieces_from_height_map
+	// gets confused by the presence of the clipboard metas. (TT7495 & TT6152)
+	if (m_mode==PASTE)
+	{
+		if (mp_current_clipboard)
+		{
+			mp_current_clipboard->DestroyMetas();
+		}
+	}		
+	bool ret_val=ChangeFloorHeight(dims,height,dir,uniform_height);
+	
+	if (m_mode==AREA_SELECT)
+	{
+		ForceSelectionAreaHighlightRefresh();
+	}
+	return ret_val;	
+}
+
+
+
+
+bool CCursor::IsSittingOnGap()
+{
+	int half;
+	CGapManager::GapDescriptor *pDesc = m_manager->GetGapDescriptor(m_cell_dims, &half);
+	if (pDesc)
+		return true;
+	return false;
+}
+
+
+
+
+CCursor *CCursor::sInstance(bool assert)
+{
+	Dbg_Assert(!assert || sp_instance);
+	return sp_instance;
+}
+
+
+
+
+void CCursor::ForceInBounds()
+{
+	if (m_cell_dims[X] < m_manager->GetParkNearBounds().GetX())
+	{
+		m_cell_dims[X] = m_manager->GetParkNearBounds().GetX();
+	}
+	if (m_cell_dims[X] + m_cell_dims[W] > m_manager->GetParkNearBounds().GetX() + m_manager->GetParkNearBounds().GetW())
+	{
+		m_cell_dims[X] = m_manager->GetParkNearBounds().GetX() + m_manager->GetParkNearBounds().GetW() - m_cell_dims[W];
+	}
+	if (m_cell_dims[Z] < m_manager->GetParkNearBounds().GetZ())
+	{
+		m_cell_dims[Z] = m_manager->GetParkNearBounds().GetZ();
+	}
+	if (m_cell_dims[Z] + m_cell_dims[L] > m_manager->GetParkNearBounds().GetZ() + m_manager->GetParkNearBounds().GetL())
+	{
+		m_cell_dims[Z] = m_manager->GetParkNearBounds().GetZ() + m_manager->GetParkNearBounds().GetL() - m_cell_dims[L];
+	}
+	
+	if (m_mode==PASTE)
+	{
+		if (mp_current_clipboard)
+		{
+			GridDims area;
+			mp_current_clipboard->GetArea(this,&area);
+						
+			int dx=0;
+			int park_x_left=m_manager->GetParkNearBounds().GetX();
+			int park_x_right=m_manager->GetParkNearBounds().GetX()+m_manager->GetParkNearBounds().GetW()-1;
+			sint8 x_left=area.GetX();
+			if (x_left < park_x_left)
+			{
+				dx=park_x_left-x_left;
+			}
+			sint8 x_right=area.GetX();
+			x_right+=area.GetW()-1;	
+			if (x_right > park_x_right)
+			{
+				dx=park_x_right-x_right;
+			}	
+			m_cell_dims[X]+=dx;
+			
+			int dz=0;
+			int park_z_top=m_manager->GetParkNearBounds().GetZ();
+			int park_z_bottom=m_manager->GetParkNearBounds().GetZ()+m_manager->GetParkNearBounds().GetL()-1;
+			
+			sint8 z_top=area.GetZ();
+			if (z_top < park_z_top)
+			{
+				dz=park_z_top-z_top;
+			}
+			sint8 z_bottom=area.GetZ();
+			z_bottom+=area.GetL()-1;	
+			if (z_bottom > park_z_bottom)
+			{
+				dz=park_z_bottom-z_bottom;
+			}	
+			
+			m_cell_dims[Z]+=dz;
+		}	
+	}
+	
+}
+
+
+
+
+void CCursor::change_cell_pos(int incX, int incZ)
+{
+	//printf("change_cell_pos:\n");
+	//printf("Before:");
+	//m_cell_dims.PrintContents();
+	
+	ForceInBounds();
+	GridDims old_pos = m_cell_dims;
+	
+	m_cell_dims[X] += incX;
+	m_cell_dims[Z] += incZ;
+
+	ForceInBounds();
+
+	if (m_mode==PASTE)
+	{
+		if (mp_current_clipboard)
+		{
+			m_clipboard_y=mp_current_clipboard->FindMaxFloorHeight(this);
+			m_cell_dims[Y]=m_clipboard_y;
+		}	
+		else
+		{
+			m_cell_dims[Y]=0;
+		}	
+	}
+	else
+	{
+		int current_floor_height = m_manager->GetFloorHeight(m_cell_dims);
+		int floor_height_old_pos = m_manager->GetFloorHeight(old_pos);
+		m_cell_dims[Y] = (current_floor_height > floor_height_old_pos) ? current_floor_height : floor_height_old_pos;
+	}
+		
+	//Ryan("cursor at %d,%d\n", m_cell_dims[X], m_cell_dims[Z]);	 
+
+	//printf("After:");
+	//m_cell_dims.PrintContents();
+	
+	#if 0
+	printf("park bounds: %d,%d,%d,%d, cell_dims: %d,%d\n", 
+		   m_manager->GetParkNearBounds().GetX(), m_manager->GetParkNearBounds().GetZ(),
+		   m_manager->GetParkNearBounds().GetW(), m_manager->GetParkNearBounds().GetL(),
+		   m_cell_dims[W], m_cell_dims[L]);
+	#endif
+	
+	if (m_mode == GAP || m_mode == GAP_ADJUST)
+	{
+		ECursorMode new_mode = GAP;
+		
+		// see if cursor is over a metapiece with attached gap
+		CMapListTemp meta_list = m_manager->GetMetaPiecesAt(m_cell_dims);
+		if (!meta_list.IsEmpty())
+		{
+			int half;
+			GridDims area = meta_list.GetList()->GetConcreteMeta()->GetArea();
+			CGapManager::GapDescriptor *p_desc = m_manager->GetGapDescriptor(area, &half);
+			if (p_desc)
+			{
+				if (m_current_gap.numCompleteHalves != 1)
+				{
+					m_current_gap = *p_desc;
+				}
+				new_mode = GAP_ADJUST;
+			}
+		}
+		
+		change_mode(new_mode);
+		
+		if (m_current_gap.numCompleteHalves > 0)
+			m_manager->HighlightMetasWithGaps(true, &m_current_gap);
+		else
+			m_manager->HighlightMetasWithGaps(true, NULL);
+	}
+}
+	
+
+
+
+void CCursor::set_source_meta(uint32 nameChecksum)
+{
+	#ifndef DONT_USE_CURSOR_META
+	if (mp_meta)
+	{
+		mp_meta->MarkUnlocked();
+		m_manager->DestroyConcreteMeta(mp_meta, CParkManager::mDONT_DESTROY_PIECES_ABOVE);
+		mp_meta = NULL;
+	}
+	
+	CAbstractMetaPiece *p_source_meta = m_manager->GetAbstractMeta(nameChecksum);
+	Dbg_MsgAssert(p_source_meta, ("couldn't find metapiece named %s", Script::FindChecksumName(nameChecksum)));
+	
+	if (p_source_meta->IsRailPlacement())
+	{
+		m_cell_dims.SetWHL(1, 1, 1);	
+	}
+	else
+	{
+		mp_meta = m_manager->CreateConcreteMeta(p_source_meta, true);
+		mp_meta->MarkLocked();
+		m_cell_dims.SetWHL(mp_meta->GetArea().GetW(), mp_meta->GetArea().GetH(), mp_meta->GetArea().GetL());
+		if (m_hard_rot & 1)
+			m_cell_dims.SetWHL(m_cell_dims.GetL(), m_cell_dims.GetH(), m_cell_dims.GetW());
+	}
+	#else
+	m_cell_dims.SetWHL(1, 1, 1);	
+	#endif
+
+	ClearAreaSelection();
+
+	if (p_source_meta->IsGapPlacement())
+	{
+		change_mode(GAP);
+		//m_manager->HighlightMetasWithGaps(true, NULL);
+	}
+	else if (p_source_meta->IsAreaSelection())
+	{
+		change_mode(AREA_SELECT);
+		m_manager->HighlightMetasWithGaps(false, NULL);
+	}
+	else if (p_source_meta->IsRailPlacement())
+	{
+		change_mode(RAIL_PLACEMENT);
+		m_manager->HighlightMetasWithGaps(false, NULL);
+	}
+	else
+	{
+		change_mode(REGULAR);
+		m_manager->HighlightMetasWithGaps(false, NULL);
+	}
+		
+	//m_manager->AddMetaPieceToPark(GridDims(0, 8, 0), mp_meta);
+
+	//printf("selected set %d, entry %d/%d\n", m_selected_set, m_palette_set[m_selected_set].mSelectedEntry, m_palette_set[m_selected_set].mTotalEntries);
+}
+
+void CCursor::change_mode(ECursorMode newMode)
+{
+	if (m_mode != newMode)
+	{
+		m_mode = newMode;
+		RefreshHelperText();
+	}
+}
+
+
+
+
+void CCursor::RefreshHelperText()
+{
+	Script::CStruct params;
+	if (m_mode == GAP_ADJUST)
+		params.AddChecksum(CRCD(0x6835b854,"mode"), CRCD(0xe75a3a17,"gap_adjust"));
+	else if (m_mode == GAP)
+		params.AddChecksum(CRCD(0x6835b854,"mode"), CRCD(0x780a5cf3,"gap_regular"));
+	else if (m_mode == RAIL_PLACEMENT)
+		params.AddChecksum(CRCD(0x6835b854,"mode"), CRCD(0xffd81c08,"rail_placement"));
+	else
+		params.AddChecksum(CRCD(0x6835b854,"mode"), CRCD(0xb58efc2b,"regular"));
+	Script::RunScript("parked_set_helper_text_mode", ¶ms);
+}
+
+
+
+
+bool CCursor::is_gap_descriptor_same_as_current(CGapManager::GapDescriptor *pDesc)
+{
+	if (m_current_gap.numCompleteHalves == 1 && 
+		(pDesc->loc[0].GetX() != m_current_gap.loc[0].GetX() ||
+		 pDesc->loc[0].GetY() != m_current_gap.loc[0].GetY() ||
+		 pDesc->loc[0].GetZ() != m_current_gap.loc[0].GetZ()))
+	{
+		// we are currently working on a gap and it's not the same as the one at this location, so fail
+		return false;
+	}
+
+	return true;
+}
+	
+
+
+
+CUpperMenuManager::CUpperMenuManager()
+{
+	//Rulon: Commented this out to prevent menu set items from being added twice
+	//Enable();
+
+	mp_current_set = NULL;
+	m_current_entry_in_set = 0;
+	m_current_set_index = 0;
+}
+
+
+
+
+CUpperMenuManager::~CUpperMenuManager()
+{
+	Disable();
+}
+
+
+
+
+void CUpperMenuManager::SetSourceMeta(uint32 nameChecksum)
+{
+	//Script::RunScript("parked_make_piece_menu");
+	
+	CAbstractMetaPiece *p_source_meta = m_manager->GetAbstractMeta(nameChecksum);
+	Dbg_MsgAssert(p_source_meta, ("couldn't find metapiece named %s", Script::FindChecksumName(nameChecksum)));
+	
+	Script::CStruct params;
+	params.AddChecksum("metapiece_id", nameChecksum);
+	if (p_source_meta->IsSingular())
+	{
+		params.AddChecksum("sector", nameChecksum);
+		Script::RunScript("parked_make_piece_menu_item", ¶ms);			
+	}
+	else
+	{
+		Script::CArray sectors_array;
+		p_source_meta->BuildElement3dSectorsArray(§ors_array);			
+		params.AddArray("sectors", §ors_array);
+		Script::RunScript("parked_make_piece_menu_item", ¶ms);			
+		Script::CleanUpArray(§ors_array);
+	}
+}
+
+
+
+
+void CUpperMenuManager::SetPieceSet(CParkManager::CPieceSet *pSet, int set_number, int menuSetNumber)
+{
+	ParkEd("CUpperMenuManager::SetPieceSet(pSet = %p, set_number = %d)", pSet, set_number);
+	
+	Dbg_Assert(pSet);
+	
+	bool is_clipboard_set=false;
+	int num_clipboards=0;
+	if (pSet->mIsClipboardSet)
+	{
+		is_clipboard_set=true;
+		num_clipboards=CCursor::sInstance()->GetNumClipboards();
+		CCursor::sInstance()->ClearAreaSelection();
+	}
+
+	Obj::CTracker* p_tracker = Obj::CTracker::Instance();
+	if (mp_current_set)
+	{
+		//uint32 name_crc = pSet->mEntryTab[m_current_entry_in_set].mNameCrc;
+		uint32 name_crc = mp_current_set->mEntryTab[m_current_entry_in_set].mNameCrc;
+		
+		ParkEd("unfocusing item in piece menu %s", Script::FindChecksumName(name_crc));
+		if (p_tracker->GetObject(name_crc))
+			p_tracker->LaunchEvent(Obj::CEvent::TYPE_UNFOCUS, name_crc);
+	}
+	
+	ParkEd("current set is %p\n", mp_current_set);
+	if (pSet != mp_current_set)
+	{
+		//printf("Script::RunScript(\"parked_make_piece_menu\");");
+		Script::RunScript("parked_make_piece_menu");
+		
+		for (int i = 0; i < pSet->mTotalEntries; i++)
+		{
+			uint32 name_checksum = pSet->mEntryTab[i].mNameCrc;
+			
+			if (is_clipboard_set)
+			{
+				Script::CStruct params;
+				params.AddChecksum("metapiece_id", name_checksum);
+				if (iGetAbstractMeta(name_checksum);
+				Dbg_MsgAssert(p_source_meta, ("couldn't find metapiece named %s", Script::FindChecksumName(name_checksum)));
+				
+				Script::CStruct params;
+				params.AddChecksum("metapiece_id", name_checksum);
+				if (p_source_meta->IsSingular())
+				{
+					params.AddChecksum("sector", name_checksum);
+					Script::RunScript("parked_make_piece_menu_item", ¶ms);			
+				}
+				else
+				{
+					Script::CArray sectors_array;
+					p_source_meta->BuildElement3dSectorsArray(§ors_array);			
+					params.AddArray("sectors", §ors_array);
+					Script::RunScript("parked_make_piece_menu_item", ¶ms);			
+					Script::CleanUpArray(§ors_array);
+				}
+			}	
+		}
+
+		Script::RunScript("parked_make_set_menu");
+		
+		Script::CStruct params;
+		params.AddInteger("set_number", menuSetNumber);
+		params.AddInteger("last_set_number", m_current_set_index);
+		Script::RunScript("parked_lock_piece_and_set_menus", ¶ms);
+		
+		mp_current_set = pSet;
+		m_current_set_index = menuSetNumber;
+	}
+
+	// m_current_entry_in_set
+	
+	//Dbg_Assert(m_current_entry_in_set >= 0 && m_current_entry_in_set < 
+	
+	ParkEd("focusing item in piece menu %s", Script::FindChecksumName(pSet->mEntryTab[pSet->mSelectedEntry].mNameCrc));
+	if (p_tracker->GetObject(pSet->mEntryTab[pSet->mSelectedEntry].mNameCrc))
+		p_tracker->LaunchEvent(Obj::CEvent::TYPE_FOCUS, pSet->mEntryTab[pSet->mSelectedEntry].mNameCrc);
+	m_current_entry_in_set = pSet->mSelectedEntry;
+
+	/* Make the slider reflect the selection window */
+	
+	int first_in_window = m_current_entry_in_set - 3;
+	int last_in_window = m_current_entry_in_set + 3;
+	if (pSet->mTotalEntries <= 7)
+	{
+		first_in_window = 0;
+		last_in_window = pSet->mTotalEntries - 1;
+	}
+	else if (first_in_window < 0)
+	{
+		last_in_window += -first_in_window;
+		first_in_window = 0;
+	}
+	else if (last_in_window >= pSet->mTotalEntries)
+	{
+		first_in_window += pSet->mTotalEntries - last_in_window - 1;
+		last_in_window = pSet->mTotalEntries - 1;
+	}
+	
+	Front::CScreenElementManager* p_element_manager = Front::CScreenElementManager::Instance();
+	Front::CScreenElementPtr p_slider = p_element_manager->GetElement(Script::GenerateCRC("piece_slider_orange"), Front::CScreenElementManager::ASSERT);
+	float slider_x, slider_y;
+	p_slider->GetLocalPos(&slider_x, &slider_y);
+	float slider_start = 375.0f * first_in_window / pSet->mTotalEntries;
+	float slider_end = 375.0f * (last_in_window + 1) / pSet->mTotalEntries;
+	p_slider->SetPos(slider_start, slider_y, Front::CScreenElement::FORCE_INSTANT);
+	p_slider->SetScale((slider_end - slider_start) / 4.0f, 1.0f, false, Front::CScreenElement::FORCE_INSTANT);
+
+	Front::CScreenElementPtr p_name_text = p_element_manager->GetElement(Script::GenerateCRC("piece_menu_name_text"), Front::CScreenElementManager::ASSERT);
+	if (pSet->mEntryTab[m_current_entry_in_set].mpName)
+		((Front::CTextElement *) p_name_text.Convert())->SetText(pSet->mEntryTab[m_current_entry_in_set].mpName);
+	else
+		((Front::CTextElement *) p_name_text.Convert())->SetText("---");
+}
+
+
+
+
+void CUpperMenuManager::Enable()
+{
+	ParkEd("CUpperMenuManager::Enable()");
+	//printf("CUpperMenuManager::Enable()");
+	
+	Script::RunScript("parked_make_piece_menu");			
+	Script::RunScript("parked_make_set_menu");			
+	
+	for (int i = 0; i < m_manager->GetTotalNumPieceSets(); i++)
+	{
+		CParkManager::CPieceSet &set = m_manager->GetPieceSet(i);
+		if (set.mVisible)
+		{
+			Script::CStruct params;
+			params.AddString("set_name", set.mpName);
+			Script::RunScript("parked_make_set_menu_item", ¶ms);
+		}
+	}
+
+	// will force menu to be rebuilt in SetPieceSet()
+	mp_current_set = NULL;
+}
+
+
+
+
+void CUpperMenuManager::Disable()
+{
+	ParkEd("CUpperMenuManager::Disable()");
+	
+	Script::RunScript("parked_destroy_piece_menu");
+	Script::RunScript("parked_destroy_set_menu");
+	mp_current_set= NULL;
+}
+
+bool ScriptSetParkEditorTimeOfDay(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 tod_script=0;
+	pParams->GetChecksum(CRCD(0x4c72ed98,"tod_script"),&tod_script);
+	Ed::CParkEditor::Instance()->SetTimeOfDayScript(tod_script);
+	return true;
+}
+
+bool ScriptGetParkEditorTimeOfDayScript(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 tod_script=Ed::CParkEditor::Instance()->GetTimeOfDayScript();
+	if (tod_script)
+	{
+		pScript->GetParams()->AddChecksum(CRCD(0x4c72ed98,"tod_script"),tod_script);
+		return true;
+	}	
+	return false;
+}
+
+// Added so that when choosing to create a goal, the node array can be initialised, otherwise the
+// cursor will not be able to detect kill polys (unless a test play had been done)
+bool ScriptRebuildParkNodeArray(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Ed::CParkManager::sInstance()->RebuildNodeArray();	
+	return true;
+}
+	
+bool ScriptFreeUpMemoryForPlayingPark(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// The following will free up everything on the park editor heap, then delete the
+	// heap itself.
+
+	Ed::CParkEditor::Instance()->CreatePlayModeGapManager();
+	
+	Ed::CParkEditor::Instance()->DeleteCursor();
+	// Note: Only destroying pieces, not the cloned sectors, otherwise the whole park
+	// will disappear from beneath the skater.
+	Ed::CParkManager::sInstance()->Destroy(Ed::CParkGenerator::DESTROY_ONLY_PIECES);
+	
+	Ed::CParkEditor::Instance()->SwitchToPlayModeGapManager();
+	
+	return true;
+}
+
+bool ScriptCalibrateMemoryGauge(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Old
+	return true;
+}
+
+bool ScriptGetParkEditorCursorPos(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CParkEditor* p_editor = CParkEditor::Instance();
+	Mth::Vector pos=p_editor->GetCursorPos();
+	pScript->GetParams()->AddVector(CRCD(0x7f261953,"pos"),pos[X],pos[Y],pos[Z]);
+	return true;
+}
+
+bool ScriptSwitchToParkEditorCamera(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Create the park editor camera if it does not exist, then switch to it.
+	Obj::CCompositeObject * p_obj = (Obj::CCompositeObject *) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x64716bee,"parked_cam"));
+	if (!p_obj)
+	{
+		Script::CStruct * p_cam_params = new Script::CStruct;
+		p_cam_params->AddChecksum("name",CRCD(0x64716bee,"parked_cam"));
+		
+		Obj::CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(
+											Script::GetArray("parkedcam_composite_structure"),p_cam_params);
+		
+		delete	p_cam_params;
+	}
+	
+	CParkEditor::Instance()->SetAppropriateCamera();	
+
+	return true;
+}
+	
+/*
+	Parameters:
+	
+	state
+	command
+	
+	States:
+	-edit
+		Command:
+		-initialize
+		-clear whole park
+		-regenerate from compressed map
+		-build floor
+	-test_play
+	-off
+	
+	Will
+*/
+bool ScriptSetParkEditorState(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	ParkEd("CCursor::ScriptSetParkEditorState()");
+	#if DEBUG_THIS_DAMN_THING
+	Script::PrintContents(pParams, 4);
+	#endif
+	
+	CParkEditor* p_editor = CParkEditor::Instance();
+	
+	CParkEditor::EEditorState new_state = CParkEditor::vKEEP_SAME_STATE;
+	pParams->GetChecksum("state", (uint32 *) &new_state);
+	Dbg_MsgAssert(new_state == CParkEditor::vEDITING ||
+				  new_state == CParkEditor::vINACTIVE ||
+				  new_state == CParkEditor::vREGULAR_PLAY ||
+				  new_state == CParkEditor::vTEST_PLAY, 
+				  ("invalid park editor state"));
+
+	CParkEditor::EEditorCommand command = CParkEditor::vNO_COMMAND;
+	pParams->GetChecksum("command", (uint32 *) &command);
+	Dbg_MsgAssert(command == CParkEditor::vINITIALIZE ||
+				  command == CParkEditor::vCLEAR ||
+				  command == CParkEditor::vREGENERATE_FROM_MAP ||
+				  command == CParkEditor::vBUILD_FLOOR ||
+				  command == CParkEditor::vNO_COMMAND,
+				  ("invalid park editor command"));
+	
+	if (new_state == CParkEditor::vINACTIVE)
+	{
+		p_editor->SetState(new_state);					
+		p_editor->Cleanup();
+		p_editor->SetPaused(false);
+	}
+	else
+	{
+		if (command == CParkEditor::vINITIALIZE)
+		{
+			p_editor->Initialize((new_state == CParkEditor::vEDITING));
+		}
+		else
+		{
+			if (command == CParkEditor::vCLEAR)
+				p_editor->Rebuild(false, true, (new_state == CParkEditor::vEDITING));
+			else if (command == CParkEditor::vREGENERATE_FROM_MAP)
+				p_editor->Rebuild(false, false, (new_state == CParkEditor::vEDITING));
+			else if (command == CParkEditor::vBUILD_FLOOR)
+				p_editor->Rebuild(true, false, (new_state == CParkEditor::vEDITING));			
+		}
+		p_editor->SetState(new_state);					
+	}
+
+	return true;
+}
+
+
+
+
+bool ScriptSetParkEditorPauseMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	ParkEd("CCursor::ScriptSetParkEditorPauseMode()");
+	#if DEBUG_THIS_DAMN_THING
+	Script::PrintContents(pParams, 4);
+	#endif
+	
+	CParkEditor* p_editor = CParkEditor::Instance();
+	
+	if (pParams->ContainsFlag("pause"))
+		p_editor->SetPaused(true);
+	else if (pParams->ContainsFlag("unpause"))
+		p_editor->SetPaused(false);
+	
+	return true;
+}
+
+
+
+
+bool ScriptCustomParkMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CParkEditor* p_editor = CParkEditor::Instance();
+	if (pParams->ContainsFlag("editing"))
+	{
+		return p_editor->EditingCustomPark();
+	}
+	else if (pParams->ContainsFlag("testing"))
+	{
+		return p_editor->TestingCustomPark();
+	}
+	else if (pParams->ContainsFlag("just_using"))
+	{
+		return p_editor->UsingCustomPark();
+	}
+	
+	Dbg_MsgAssert(0, ("need a parameter -- 'editing' or 'just_using'"));
+	return false;
+}
+
+bool ScriptSetParkEditorMaxPlayers(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int max_players=1;
+	pParams->GetInteger(NONAME,&max_players);
+	Dbg_MsgAssert(max_players>=1 && max_players<=8,("\n%s\nBad value of %d sent to SetParkEditorMaxPlayers",pScript->GetScriptInfo(),max_players));
+	
+	CParkManager::Instance()->GetGenerator()->SetMaxPlayers(max_players);
+
+	return true;
+}	
+
+bool ScriptGetParkEditorMaxPlayers(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddInteger("MaxPlayers",CParkManager::Instance()->GetGenerator()->GetMaxPlayers());
+	return true;
+}	
+
+bool ScriptGetParkEditorMaxPlayersPossible(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddInteger("MaxPlayersPossible",CParkManager::Instance()->GetGenerator()->GetMaxPlayersPossible());
+	return true;
+}	
+
+
+// a helper function used by ScriptCanCleanlyResizePark(), ScriptResizePark()
+GridDims ComputeResizedDims(Script::CStruct *pParams)
+{
+	// these are the current bounds, may be replaced with new info
+	GridDims new_bounds = CParkManager::sInstance()->GetParkNearBounds();	
+	
+	// assume current w,l unless new parms passed in
+	int w = new_bounds.GetW();
+	int l = new_bounds.GetL();
+	pParams->GetInteger("w", &w);
+	pParams->GetInteger("l", &l);
+
+	// these diffs may be replaced below
+	int w_diff = w - (int) new_bounds.GetW();
+	int l_diff = l - (int) new_bounds.GetL();
+
+	if (pParams->GetInteger("inc_w", &w_diff))
+		w = new_bounds.GetW() + w_diff;
+	if (pParams->GetInteger("inc_l", &l_diff))
+		l = new_bounds.GetL() + l_diff;
+	
+	new_bounds[X] = new_bounds[X] - w_diff / 2;
+	new_bounds[Z] = new_bounds[Z] - l_diff / 2;
+	new_bounds[W] = w;
+	new_bounds[L] = l;
+
+	
+	return new_bounds;
+}
+
+// Prevents rail points or goal peds being placed too close to the edge of the park.
+bool IsWithinParkBoundaries(Mth::Vector pos, float margin)
+{
+	GridDims park_bounds = CParkManager::sInstance()->GetParkNearBounds();	
+	Mth::Vector corner_pos;
+	Mth::Vector area_dims;
+	corner_pos=Ed::CParkManager::Instance()->GridCoordinatesToWorld(park_bounds,&area_dims);
+
+	if (pos[X] > corner_pos[X]+margin && pos[X] < corner_pos[X]+area_dims[X]-margin &&
+		pos[Z] > corner_pos[Z]+margin && pos[Z] < corner_pos[Z]+area_dims[Z]-margin)
+	{
+		return true;
+	}
+	
+	return false;
+}		
+
+
+bool ScriptCanCleanlyResizePark(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GridDims new_bounds;
+	
+	Script::CStruct *p_new_bounds=NULL;
+	if (pParams->GetStructure(CRCD(0xb4f88771,"NewBounds"),&p_new_bounds))
+	{
+		int x,z,w,l;
+		p_new_bounds->GetInteger(CRCD(0x7323e97c,"x"),&x);
+		p_new_bounds->GetInteger(CRCD(0x9d2d8850,"z"),&z);
+		p_new_bounds->GetInteger(CRCD(0xe39cf4ed,"w"),&w);
+		p_new_bounds->GetInteger(CRCD(0x69f93d01,"l"),&l);
+		new_bounds.SetXYZ(x,0,z);
+		new_bounds.SetWHL(w,1,l);
+		
+		bool can_resize=true;
+		if (CParkManager::sInstance()->AreMetasOutsideBounds(new_bounds))
+		{
+			can_resize=false;
+		}
+		
+		Mth::Vector corner_pos;
+		Mth::Vector area_dims;
+		corner_pos=Ed::CParkManager::Instance()->GridCoordinatesToWorld(new_bounds,&area_dims);
+		if (Obj::GetRailEditor()->ThereAreRailPointsOutsideArea(corner_pos[X]+PARK_BOUNDARY_MARGIN, 
+																corner_pos[Z]+PARK_BOUNDARY_MARGIN,
+																corner_pos[X]+area_dims[X]-PARK_BOUNDARY_MARGIN, 
+																corner_pos[Z]+area_dims[Z]-PARK_BOUNDARY_MARGIN))
+		{
+			can_resize=false;
+		}
+
+		if (Obj::GetGoalEditor()->ThereAreGoalsOutsideArea(corner_pos[X], corner_pos[Z], corner_pos[X]+area_dims[X], corner_pos[Z]+area_dims[Z]))
+		{
+			can_resize=false;
+		}
+		
+		return can_resize;	
+	}
+
+	return false;
+}
+
+
+void ResizePark(GridDims newBounds)
+{
+	CParkManager::sInstance()->RebuildInnerShell(newBounds);
+	CParkManager::sInstance()->RebuildFloor();
+
+	Mth::Vector corner_pos;
+	Mth::Vector area_dims;
+	corner_pos=Ed::CParkManager::Instance()->GridCoordinatesToWorld(newBounds,&area_dims);
+	Obj::GetRailEditor()->ClipRails(corner_pos[X]+PARK_BOUNDARY_MARGIN, corner_pos[Z]+PARK_BOUNDARY_MARGIN,
+									corner_pos[X]+area_dims[X]-PARK_BOUNDARY_MARGIN, corner_pos[Z]+area_dims[Z]-PARK_BOUNDARY_MARGIN,
+									Obj::CRailEditorComponent::DELETE_POINTS_OUTSIDE);
+	Obj::GetGoalEditor()->DeleteGoalsOutsideArea(corner_pos[X], corner_pos[Z], corner_pos[X]+area_dims[X], corner_pos[Z]+area_dims[Z]);
+	
+	CParkEditor::Instance()->DestroyClipboardsWhichAreTooBigToFit();
+}
+
+bool ScriptResizePark(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	static GridDims stored_dims;
+	
+	GridDims new_bounds;
+	
+	Script::CStruct *p_new_bounds=NULL;
+	if (pParams->GetStructure(CRCD(0xb4f88771,"NewBounds"),&p_new_bounds))
+	{
+		int x,z,w,l;
+		p_new_bounds->GetInteger(CRCD(0x7323e97c,"x"),&x);
+		p_new_bounds->GetInteger(CRCD(0x9d2d8850,"z"),&z);
+		p_new_bounds->GetInteger(CRCD(0xe39cf4ed,"w"),&w);
+		p_new_bounds->GetInteger(CRCD(0x69f93d01,"l"),&l);
+		new_bounds.SetXYZ(x,0,z);
+		new_bounds.SetWHL(w,1,l);
+	}
+	else
+	{
+		new_bounds = ComputeResizedDims(pParams);
+	}
+	
+	
+	if (pParams->ContainsFlag("delayed"))
+	{
+		stored_dims = new_bounds;
+	}
+	else 
+	{
+		if (pParams->ContainsFlag("use_stored"))
+		{
+			new_bounds = stored_dims;
+		}
+		if (!CParkManager::sInstance()->EnoughMemoryToResize(new_bounds))
+		{
+			Script::RunScript("parked_show_out_of_memory_message");
+			return false;
+		}
+				
+		ResizePark(new_bounds);
+		// Refresh the rail geometry so that UpdateSuperSectors gets called on them.
+		//Obj::GetRailEditor()->RefreshGeometry();
+	}
+	
+	return true;
+}
+
+
+bool ScriptGetCurrentParkBounds(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GridDims bounds = CParkManager::sInstance()->GetParkNearBounds();	
+	pScript->GetParams()->AddInteger(CRCD(0x7323e97c,"x"),bounds.GetX());
+	pScript->GetParams()->AddInteger(CRCD(0x9d2d8850,"z"),bounds.GetZ());
+	pScript->GetParams()->AddInteger(CRCD(0xe39cf4ed,"w"),bounds.GetW());
+	pScript->GetParams()->AddInteger(CRCD(0x69f93d01,"l"),bounds.GetL());
+	return true;
+}	
+
+bool ScriptCanChangeParkDimension(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GridDims current_bounds = CParkManager::sInstance()->GetParkNearBounds();
+	GridDims max_bounds = CParkManager::sInstance()->GetParkFarBounds();
+	
+	Script::CArray *p_dim_array = NULL;
+	if (pParams->GetArray(NONAME, &p_dim_array))
+	{
+		int w = p_dim_array->GetInt(0);
+		int l = p_dim_array->GetInt(1);
+		if (w != current_bounds.GetW() || l != current_bounds.GetL())
+			return true;
+	}
+	else
+	{
+		// otherwise, decrease
+		bool test_for_increase = pParams->ContainsFlag("up");
+	
+		if (pParams->ContainsFlag("width"))
+		{
+			if (test_for_increase && current_bounds.GetW() < max_bounds.GetW()) return true;
+			else if (!test_for_increase && current_bounds.GetW() > 16) return true;
+		}
+		else
+		{
+			if (test_for_increase && current_bounds.GetL() < max_bounds.GetL()) return true;
+			else if (!test_for_increase && current_bounds.GetL() > 16) return true;
+		}
+	}
+		
+	return false;
+}
+
+
+
+
+bool ScriptSaveParkToDisk(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int slot = 0;
+	pParams->GetInteger("slot", &slot, Script::ASSERT);
+	// XXX
+	Ryan("saving park at slot %d\n", slot);
+	CParkEditor* p_editor = CParkEditor::Instance();
+	p_editor->AccessDisk(true, slot, false);
+	return true;
+}
+
+
+
+
+bool ScriptLoadParkFromDisk(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int slot = 0;
+	pParams->GetInteger("slot", &slot, Script::ASSERT);
+
+	bool block_rebuild = pParams->ContainsFlag("block_rebuild");
+
+	// XXX
+	Ryan("loading park in slot %d\n", slot);
+	CParkEditor* p_editor = CParkEditor::Instance();
+	p_editor->AccessDisk(false, slot, block_rebuild);
+	return true;
+}
+
+
+
+
+bool ScriptIsParkUnsaved(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return !CParkManager::sInstance()->IsMapSavedLocally();
+}
+
+
+
+
+bool ScriptFireCustomParkGap(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int gap_index;
+	pParams->GetInteger("gap_index", &gap_index, true);
+
+	// XXX
+	Ryan("ScriptFireCustomParkGap() %d\n", gap_index);
+	
+	CGapManager::sInstance()->LaunchGap(gap_index, pScript);
+	return true;
+}
+
+bool ScriptSetEditedParkGapInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_name = NULL;
+	pParams->GetString("name", &p_name);
+	int score = -1;
+	pParams->GetInteger("score", &score);
+	
+	CCursor::sInstance()->SetGapNameAndScore(p_name, score);
+
+	Script::CStruct *p_cancel_flags=NULL;
+	pParams->GetStructure(CRCD(0x49ec3f9,"cancel_flags"),&p_cancel_flags);
+	CCursor::sInstance()->SetGapCancelFlags(p_cancel_flags);
+	return true;
+}
+
+
+
+
+bool ScriptGetEditedParkGapName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddString("name", CCursor::sInstance()->GetGapName());
+
+	return true;
+}
+
+bool ScriptParkEditorSelectionAreaTooBigToCopy(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return CCursor::sInstance()->SelectionAreaTooBigToCopy();
+}
+
+bool ScriptCopyParkEditorSelectionToClipboard(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool success=CCursor::sInstance()->CopySelectionToClipboard();
+	CCursor::sInstance()->ClearAreaSelection();	
+	return success;
+}
+
+bool ScriptSwitchParkEditorMenuPieceToMostRecentClipboard(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CParkEditor::Instance()->SwitchMenuPieceToMostRecentClipboard();
+	return true;
+}
+
+bool ScriptCutParkEditorAreaSelection(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool success=CCursor::sInstance()->CopySelectionToClipboard();
+	if (success)
+	{
+		CCursor::sInstance()->DeleteSelectedPieces();
+		CCursor::sInstance()->ResetSelectedHeights();
+	}
+	CCursor::sInstance()->ClearAreaSelection();	
+	return success;
+}
+
+bool ScriptParkEditorAreaSelectionDeletePieces(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CCursor::sInstance()->DeleteSelectedPieces();
+	return true;
+}
+
+bool ScriptContinueParkEditorAreaSelection(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CCursor::sInstance()->ContinueAreaSelection();
+	return true;
+}
+	
+bool ScriptGetEditorTheme(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddInteger(CRCD(0x688a18f7,"theme"), CParkManager::sInstance()->GetTheme());
+	return true;
+}
+
+bool ScriptSetEditorTheme(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int theme=0;
+	pParams->GetInteger(CRCD(0x688a18f7,"theme"),&theme);
+	CParkManager::sInstance()->SetTheme(theme);
+	return true;
+}
+
+bool ScriptGetEditorMaxThemes(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddInteger(CRCD(0x7ddc0da8,"max_themes"), MAX_THEMES);
+	return true;
+}
+
+bool ScriptGetCustomParkName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int truncate_char_num = -1;
+	pParams->GetInteger(NONAME, &truncate_char_num);
+	
+	const char *p_name = CParkManager::sInstance()->GetParkName();
+	
+	if (truncate_char_num == -1)
+	{
+		if (p_name)
+			pScript->GetParams()->AddString("name", p_name);
+		else
+			pScript->GetParams()->AddString("name", "unnamed park");
+	}
+	else
+	{
+		char out_name[64];
+		for (int i = 0; i < truncate_char_num; i++)
+			out_name[i] = p_name[i];
+		out_name[truncate_char_num] = '\0';
+		pScript->GetParams()->AddString("name", out_name);
+	}
+	return true;
+}
+
+
+
+
+bool ScriptSetCustomParkName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_name = NULL;
+	pParams->GetString("name", &p_name, Script::ASSERT);
+	CParkManager::sInstance()->SetParkName(p_name);
+	return true;
+}
+
+
+// @script | IsCustomPark | returns true if this is a custom park
+bool ScriptIsCustomPark(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	return Ed::CParkEditor::Instance()->UsingCustomPark();
+}
+
+
+
+bool ScriptBindParkEditorToController( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int controller;
+	pParams->GetInteger( NONAME, &controller, Script::ASSERT );
+	CParkEditor* p_editor = CParkEditor::Instance();
+	p_editor->BindParkEditorToController( controller );
+	return true;
+}
+
+#ifdef __PLAT_NGC__
+// Required in order that the park rebuild when defragmenting during resizing does not
+// reset the map heights.
+// (Used in the script parked_defragment_memory_for_resize in sk5ed_scripts)
+bool ScriptWriteCompressedMapBuffer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	CParkManager::sInstance()->WriteCompressedMapBuffer();
+	return true;
+}
+
+bool ScriptRequiresDefragment(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mem::Heap *p_top_down_heap = Mem::Manager::sHandle().TopDownHeap();
+	if (p_top_down_heap->mp_region->MemAvailable() < TOP_DOWN_REQUIRED_MARGIN)
+	{
+		return true;
+	}
+	
+        Mem::Heap *p_main_heap = Mem::Manager::sHandle().BottomUpHeap();
+        if (p_main_heap->mFreeMem.m_count > 200000)
+	{
+		return true;
+	}
+
+	return false;
+}
+
+bool ScriptTopDownHeapTooLow(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mem::Heap *p_top_down_heap = Mem::Manager::sHandle().TopDownHeap();
+	if (p_top_down_heap->mp_region->MemAvailable() < TOP_DOWN_REQUIRED_MARGIN)
+	{
+		return true;
+	}
+	return false;
+}	
+
+#endif
+
+
+}
diff --git a/Code/Sk/ParkEditor2/ParkEd.h b/Code/Sk/ParkEditor2/ParkEd.h
new file mode 100644
index 0000000..f4a1865
--- /dev/null
+++ b/Code/Sk/ParkEditor2/ParkEd.h
@@ -0,0 +1,454 @@
+#ifndef __SK_PARKEDITOR2_PARKED_H
+#define __SK_PARKEDITOR2_PARKED_H
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// At least this much is required on the top down heap during editing to ensure that
+// UpdateSuperSectors will have enough space to execute when raising large areas of ground.
+#ifdef __PLAT_NGC__
+#define TOP_DOWN_REQUIRED_MARGIN 1000000
+#define TOP_DOWN_REQUIRED_MARGIN_LEEWAY 100000
+#else
+#define TOP_DOWN_REQUIRED_MARGIN 1000000
+// A defrag will only occur if freeing up the fragments will make the top down heap have this much
+// more than the above margin. Could probably make this zero, just included to make sure it doesn't
+// try to defrag too much.
+#define TOP_DOWN_REQUIRED_MARGIN_LEEWAY 2000
+#endif
+
+
+class Coord
+{
+public:
+	float m_X;
+	float m_Y;
+};
+
+
+namespace Ed
+{
+
+enum
+{
+	MAX_THEMES=5
+};
+
+class CCursor;
+class CUpperMenuManager;
+class CClipboard;
+
+
+
+class CParkEditor : public Mdl::Module, public Obj::CEventListener 
+{
+	
+	DeclareSingletonClass( CParkEditor );
+	
+	CParkEditor();
+	~CParkEditor();
+
+public:
+
+	enum EEditorState
+	{
+		vINACTIVE								= 0xd443a2bc,	// "off"
+		vEDITING								= 0x7eca21e5,	// "edit"
+		vTEST_PLAY								= 0xa40f0b8a,	// "test_play"
+		vREGULAR_PLAY							= 0xcd3682c1,	// "regular_play"
+		
+		// use this as parameter only
+		vKEEP_SAME_STATE						= 0,
+	};
+
+	enum EEditorCommand
+	{
+		vINITIALIZE								= 0x27f2117d,	// "initialize"
+		vCLEAR									= 0x1a4e0ef9,	// "clear"
+		vREGENERATE_FROM_MAP					= 0x2a907cbc,	// "regenerate_from_map"
+		vBUILD_FLOOR							= 0x1625eda,	// "build_floor"
+
+		vNO_COMMAND								= 0,
+	};
+
+	void										Initialize(bool edit_mode);
+	void 										Rebuild(bool justRebuildFloor, bool clearMap, bool makeCursor);
+	void										SetState(EEditorState desiredState);
+	void										AccessDisk(bool save, int slot, bool blockRebuild);
+	void 										PostMemoryCardLoad(uint8 * p_map_buffer, int oldTheme);
+	void										Cleanup();
+	
+	void										SetAppropriateCamera();
+	
+	// K: Added to allow cleanup of the park editor heap during play
+	void										DeleteCursor();
+
+	void										Update();
+
+	void										SwitchMenuPieceToMostRecentClipboard();
+	
+	void										SetPaused(bool paused);
+	void 										MakeEditorStuffVisible( bool visible);
+	
+	bool										UsingCustomPark() {return m_state != vINACTIVE;}
+	// returns true if actually editing right now
+	bool										EditingCustomPark() {return m_state == vEDITING;}
+	bool										TestingCustomPark() {return m_state == vTEST_PLAY;}
+
+	// returns true if park is full
+	bool	   									IsParkFull() {return m_pct_resources_used >= 1.0f;} 								
+	bool										RoomToCopyOrPaste(int w, int l);
+
+	void										BindParkEditorToController( int controller );
+	Mth::Vector&								GetCursorPos();
+	
+	void										DestroyClipboardsWhichAreTooBigToFit();
+	
+	void										SetTimeOfDayScript(uint32 tod_script);
+	uint32										GetTimeOfDayScript() {return m_tod_script;}
+	
+	void										CreatePlayModeGapManager();
+	void										SwitchToPlayModeGapManager();
+	void										DeletePlayModeGapManager();
+	void										PlayModeGapManagerChecks();
+	
+private:	
+
+	void										do_piece_select_commands();
+	void										regular_command_logic();
+	void										rail_placement_logic();
+	
+	int											m_last_main_heap_free;
+	
+	// A flag which is set whenever an operation is done that could increase memory usage.
+	// This permits a defrag to be done if required. The flag is reset every frame.
+	// The flag is required to prevent an infinite loop of defrags.
+	bool 										m_allow_defrag;
+	
+	void										turn_on_or_update_piece_menu();
+	void										update_analog_stick_menu_control_state();
+	
+	void										pass_event_to_listener(Obj::CEvent *pEvent);
+
+	enum Commands
+	{
+		// some are aliases
+		
+		mROTATE_CW         	= nBit( 1 ),
+		mROTATE_CCW        	= nBit( 2 ),
+		mPREV_PIECE        	= nBit( 3 ),
+		mNEXT_PIECE        	= nBit( 4 ),
+		mPLACE_PIECE       	= nBit( 5 ),
+		mREMOVE_PIECE      	= nBit( 6 ),
+		mRAISE_FLOOR      	= nBit( 7 ),
+		mLOWER_FLOOR      	= nBit( 8 ),
+		mPREV_SET        	= nBit( 9 ),
+		mNEXT_SET        	= nBit( 10 ),
+		mZOOM_CAMERA_OUT	= nBit( 13 ),
+		mZOOM_CAMERA_IN		= nBit( 14 ),
+
+		mROTATE_GAP_CW		= nBit( 1 ),
+		mROTATE_GAP_CCW		= nBit( 2 ),
+		mPLACE_GAP       	= nBit( 5 ),
+		mREMOVE_GAP       	= nBit( 6 ),
+#ifdef __PLAT_NGC__
+		mINCREASE_GAP_LEFT	= nBit( 0 ),
+		mDECREASE_GAP_LEFT	= nBit( 15 ),
+		mINCREASE_GAP_RIGHT	= nBit( 11 ),
+		mDECREASE_GAP_RIGHT	= nBit( 12 ),
+#else
+		mINCREASE_GAP_LEFT	= nBit( 15 ),
+		mDECREASE_GAP_LEFT	= nBit( 16 ),
+		mINCREASE_GAP_RIGHT	= nBit( 11 ),
+		mDECREASE_GAP_RIGHT	= nBit( 12 ),
+#endif		// __PLAT_NGC__
+	};
+
+	static const float				vMAX_CAM_DIST;
+	static const float				vMIN_CAM_DIST;
+	static const float				vCAM_DIST_INC;
+	static const float				vCAM_TARGET_ELEVATION;
+	
+	static	Tsk::Task< CParkEditor >::Code		s_logic_code;
+	static	Tsk::Task< CParkEditor >::Code		s_display_code;
+	static	Inp::Handler< CParkEditor >::Code  	s_input_logic_code;
+
+	Tsk::Task< CParkEditor >*					m_logic_task;	
+    Tsk::Task< CParkEditor >*					m_display_task;
+    Inp::Handler< CParkEditor >*				m_input_handler;
+	
+	float										m_movement_vel;	// in inches per sec
+	float										m_rotate_vel;	// in degrees per sec
+    
+	Coord                      			m_rightStick;
+    Coord                      			m_leftStick;
+    Flags                 			m_commands;
+	
+	Gfx::Camera*								mp_camera;
+	float										m_camAngle;
+	float										m_camAngleVert;
+	float										m_camDist;
+	
+	enum CursorState
+	{
+												vMOVEMENT,
+												vGAP_MOVE,
+												vGAP_ADJUST,
+	};
+	
+	CursorState									m_cursor_state;
+	
+	void										v_start_cb( void );
+	void										v_stop_cb( void );
+    	
+	Spt::SingletonPtr				mp_park_manager;
+
+	Mth::Vector									m_cursor_pos;
+	CCursor *									mp_cursor;
+	CUpperMenuManager *							mp_menu_manager;
+
+	Tmr::Time									m_last_time;
+
+	EEditorState								m_state;
+	bool										m_paused;
+	
+	float										m_pct_resources_used;
+	
+	// This is the name of the time-of-day script which gets passed to the
+	// script_change_tod script in the parameter tod_action.
+	// (see timeofday.q for the above scripts)
+	// The time of day gets set in Sk5Ed_Startup and in parked_test_play (both in sk5ed_scripts.q)
+	// and in GameFlow_StartRun
+	uint32										m_tod_script;
+	
+	CGapManager *								mp_play_mode_gap_manager;
+	/* Debugging */
+};
+
+
+
+
+class CCursor
+{
+	friend class CParkEditor;
+
+public:												
+												CCursor();
+												~CCursor();
+
+	void										DeleteMeta();
+	void 										DestroyGeometry();
+	void										DestroyAnyClipboardCursors();
+	void										Update(float shiftX, float shiftZ, int rotInc);
+	bool										AttemptStamp();
+	bool										AttemptRemove();
+	void 										InformOfMetapieceDeletion(CConcreteMetaPiece *pMeta);
+	bool										AttemptGap();
+	bool										AttemptRemoveGap();
+	bool 										AdjustGap(int rot, int leftChange, int rightChange);
+	void										SetGapNameAndScore(const char *pGapName, int score);
+	void										SetGapCancelFlags(Script::CStruct *p_cancelFlags);
+	
+	const char *								GetGapName();
+
+	int											AttemptAreaSelect();
+	void										ClearAreaSelection();
+	void										DeleteSelectedPieces();
+	void										ResetSelectedHeights();
+	void										ContinueAreaSelection();
+	void										GetAreaSelectDims(GridDims *p_dims);
+	bool										DestroyMetasInCurrentArea();
+	void										RefreshSelectionArea();
+	void										ForceSelectionAreaHighlightRefresh() {m_initialised_highlight=false;}
+	
+	void										ChangePieceInSet(int dir);
+	void										ChangeSet(int dir);
+	void										SwitchMenuPieceToMostRecentClipboard();
+	
+	bool										ChangeFloorHeight(GridDims dims, int height, int dir, bool uniformHeight);
+	bool										ChangeFloorHeight(int dir);
+	void 										HighlightIntersectingMetas(bool on);
+	void										SetVisibility(bool visible);
+
+	int											GetSelectedSet(int *pMenuSetNumber) {*pMenuSetNumber = m_menu_set_number; return m_selected_set;}
+	uint32										GetChecksum() {return mp_meta->GetNameChecksum();}
+
+	enum ECursorMode
+	{
+		NONE,
+		REGULAR,
+		GAP,
+		GAP_ADJUST,
+		AREA_SELECT,
+		PASTE,
+		RAIL_PLACEMENT,
+	};
+
+	ECursorMode									GetCursorMode() {return m_mode;}
+	bool										InGapMode() {return (m_mode == GAP || m_mode == GAP_ADJUST);}
+	bool										InAreaSelectMode() {return m_mode==AREA_SELECT;}
+	bool										InRailPlacementMode() {return m_mode==RAIL_PLACEMENT;}
+	bool										InPasteMode() {return m_mode==PASTE;}
+	Mth::ERot90									GetRotation() {return m_hard_rot;}
+	const GridDims &							GetPosition() {return m_cell_dims;}
+	bool										SelectionAreaTooBigToCopy();
+	bool										CopySelectionToClipboard();
+	void										DeleteOldestClipboard();
+	
+	void										PasteCurrentClipboard();
+	int											GetNumClipboards();
+	int											GetClipboardY() {return m_clipboard_y;}
+		
+	bool										IsSittingOnGap();
+	bool										HalfFinishedGap() {return m_current_gap.numCompleteHalves == 1;}
+
+	// K: Made this public so that it can be called from CParkEditor::Rebuild
+	void										ForceInBounds();
+
+	void										DestroyClipboardsWhichAreTooBigToFit();
+
+	static	CCursor *							sInstance(bool assert = true);
+
+protected:
+
+	void										destroy_clipboards();
+	void										get_selected_area_coords(uint8 *p_x1, uint8 *p_z1, uint8 *p_x2, uint8 *p_z2);
+	
+	void										change_cell_pos(int incX, int incZ);
+	void										set_source_meta(uint32 nameChecksum);
+	void										change_mode(ECursorMode newMode);
+	void										RefreshHelperText();
+
+	bool										is_gap_descriptor_same_as_current(CGapManager::GapDescriptor *pDesc);
+	
+	static const float							MOTION_PCT_INC;
+	static const float							ROT_DEG_INC;
+	
+	Spt::SingletonPtr				m_manager;
+
+	GridDims									m_cell_dims;
+	
+	float										m_motion_pct;
+	float										m_motion_pct_inc;
+	
+	Mth::Vector									m_target_pos;
+	Mth::Vector									m_pos;
+	bool										m_am_moving;
+
+	Mth::ERot90									m_hard_rot;
+	float										m_target_rot;
+	float										m_rot; // rotation on y, in degrees
+
+
+	CConcreteMetaPiece *						mp_meta;
+	
+	// Clipboard stuff
+	uint8										m_num_corners_selected; // 0,1 or 2
+	GridDims									mp_area_corners[2];
+	bool										m_initialised_highlight;
+	
+	// List of clipboards
+	CClipboard *								mp_clipboards;
+	// This is the clip that will get placed when in paste mode.
+	CClipboard *								mp_current_clipboard;
+	CClipboard *								get_clipboard(int index);
+	void										set_current_clipboard(int index);
+	void										remove_clipboard(int index);
+	// The y that must be added to the y of each clipboard entry such that
+	// they align with the ground as best as possible.
+	int											m_clipboard_y;
+	
+
+	
+	
+	int											m_selected_set;
+	int											m_menu_set_number;
+
+	ECursorMode									m_mode;
+
+	/* Gap Stuff */
+
+	CGapManager::GapDescriptor					m_current_gap;
+	uint32										m_gap_suffix_counter;
+	
+	static CCursor *							sp_instance;
+};
+
+
+
+
+class CUpperMenuManager
+{
+public:
+												CUpperMenuManager();
+												~CUpperMenuManager();
+
+	void 										SetSourceMeta(uint32 nameChecksum);
+	void										SetPieceSet(CParkManager::CPieceSet *pSet, int set_number, int menuSetNumber);
+	void										Enable();
+	void										Disable();
+
+protected:
+
+	Spt::SingletonPtr				m_manager;
+	CParkManager::CPieceSet *					mp_current_set;
+	int											m_current_set_index;
+	int											m_current_entry_in_set;
+};
+
+
+#define PARK_BOUNDARY_MARGIN 10.0f
+bool IsWithinParkBoundaries(Mth::Vector pos, float margin);
+
+
+bool ScriptSetParkEditorTimeOfDay(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetParkEditorTimeOfDayScript(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRebuildParkNodeArray(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFreeUpMemoryForPlayingPark(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCalibrateMemoryGauge(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetParkEditorCursorPos(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSwitchToParkEditorCamera(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetParkEditorState(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetParkEditorPauseMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCustomParkMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetParkEditorMaxPlayers(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetParkEditorMaxPlayers(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetParkEditorMaxPlayersPossible(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCanCleanlyResizePark(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResizePark(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetCurrentParkBounds(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCanChangeParkDimension(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSaveParkToDisk(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadParkFromDisk(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIsParkUnsaved(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFireCustomParkGap(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetEditedParkGapInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetEditedParkGapName(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptParkEditorSelectionAreaTooBigToCopy(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCopyParkEditorSelectionToClipboard(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSwitchParkEditorMenuPieceToMostRecentClipboard(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCutParkEditorAreaSelection(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptParkEditorAreaSelectionDeletePieces(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptContinueParkEditorAreaSelection(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetEditorTheme(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetEditorTheme(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetEditorMaxThemes(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptGetCustomParkName(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetCustomParkName(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptBindParkEditorToController(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIsCustomPark(Script::CStruct *pParams, Script::CScript *pScript);
+#ifdef __PLAT_NGC__
+bool ScriptWriteCompressedMapBuffer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRequiresDefragment(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptTopDownHeapTooLow(Script::CStruct *pParams, Script::CScript *pScript);
+#endif
+
+
+}
+#endif
diff --git a/Code/Sk/ParkEditor2/ParkGen.cpp b/Code/Sk/ParkEditor2/ParkGen.cpp
new file mode 100644
index 0000000..fcad8bc
--- /dev/null
+++ b/Code/Sk/ParkEditor2/ParkGen.cpp
@@ -0,0 +1,2959 @@
+#include 
+#include 
+#include 			// Mick:  Added as we need to iterate over the ConcreteMetaPieces
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 	   // Needed to get the size of the replay buffer, for adjusting available memory
+
+#include 
+#include 
+
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+//#define	DEBUG_RESTARTS
+
+#ifdef __PLAT_NGC__
+#include 
+#endif		// __PLAT_NGC__
+
+#ifdef __PLAT_NGPS__
+#define PRINT_RAW_RESULTS 0
+
+#if PRINT_RAW_RESULTS
+#include 
+#include 
+#include 
+#include 
+
+
+namespace Nx
+{
+	extern void find_geom_leaves( NxPs2::CGeomNode *p_node, NxPs2::CGeomNode **leaf_array, int & num_nodes);
+}
+#endif // PRINT_RAW_RESULTS
+
+#endif
+
+
+namespace Ed
+{
+
+
+
+const float CParkGenerator::CELL_WIDTH = 120.0f;
+const float CParkGenerator::CELL_HEIGHT = 48.0f;
+const float CParkGenerator::CELL_LENGTH = 120.0f;
+
+
+// the first range of ids are used for regular objects and object nodes
+// the second for rail nodes
+const uint32 CParkGenerator::vFIRST_ID_FOR_OBJECTS 			= 2000;
+const uint32 CParkGenerator::vMAX_ID_FOR_OBJECTS 			= 2000000;
+const uint32 CParkGenerator::vFIRST_ID_FOR_RAILS 			= 2000001;
+const uint32 CParkGenerator::vMAX_ID_FOR_RAILS 				= 4000000;
+const uint32 CParkGenerator::vFIRST_ID_FOR_CREATED_RAILS	= 4000001;
+const uint32 CParkGenerator::vMAX_ID_FOR_CREATED_RAILS		= 6000000;
+const uint32 CParkGenerator::vFIRST_ID_FOR_RESTARTS 		= 6000001;
+
+
+
+CPiece::CPiece()
+{
+	m_flags = EFlags(0);
+
+	//mp_sector = NULL;
+	m_sector_checksum = 0;
+	
+	mp_next_in_list = NULL;
+}
+
+
+
+
+CPiece::~CPiece()
+{
+}
+
+
+
+
+void CPiece::GetCellDims(GridDims *pDims)
+{
+	Mth::Vector dims = GetDims();
+	pDims->SetWHL((uint8) ((dims.GetX() + 2.0f) / CParkGenerator::CELL_WIDTH),
+				  (uint8) ((dims.GetY() + 2.0f) / CParkGenerator::CELL_HEIGHT),
+				  (uint8) ((dims.GetZ() + 2.0f) / CParkGenerator::CELL_LENGTH));
+	
+	/*
+	if (m_sector_checksum == Script::GenerateCRC("Sk3Ed_MTra_02"))
+	{
+		Ryan("Sk3Ed_MTra_02 cells dims are (%d,%d,%d), world (%.4f,%.4f,%.4f)\n", 
+			 pDims->GetW(), pDims->GetH(), pDims->GetL(),
+			 dims.GetX(), dims.GetY(), dims.GetZ());
+	}
+	*/
+}
+
+
+
+
+CSourcePiece *CPiece::CastToCSourcePiece()
+{
+	Dbg_MsgAssert(this == NULL || (m_flags & mSOURCE_PIECE), ("not a source piece!"));
+	return (CSourcePiece *) this;
+}
+
+
+
+
+CClonedPiece *CPiece::CastToCClonedPiece()
+{
+	Dbg_MsgAssert(this == NULL || (m_flags & mCLONED_PIECE), ("not a cloned piece!"));
+	return (CClonedPiece *) this;
+}
+
+
+
+
+Mth::Vector	CPiece::GetDimsWithRot(Mth::ERot90 rot)
+{
+	CSourcePiece *p_source_piece;
+	if (m_flags & mSOURCE_PIECE)
+		p_source_piece = CastToCSourcePiece();
+	else
+		p_source_piece = CastToCClonedPiece()->GetSourcePiece();
+
+	Dbg_Assert(p_source_piece);
+
+	Mth::Vector result;
+	if (rot & 1)
+	{
+		result[X] = p_source_piece->GetDims().GetZ();
+		result[Z] = p_source_piece->GetDims().GetX();
+	}
+	else
+	{
+		result[X] = p_source_piece->GetDims().GetX();
+		result[Z] = p_source_piece->GetDims().GetZ();
+	}
+	result[Y] = p_source_piece->GetDims().GetY();
+
+	return result;
+}
+
+
+
+
+CSourcePiece::CSourcePiece()
+{
+	set_flag(CPiece::mSOURCE_PIECE);
+	m_domain = mREGULAR;
+	m_triggerScriptId = 0;
+}
+
+
+
+
+CSourcePiece::~CSourcePiece()
+{
+	//printf("killed CSourcePiece\n");
+}
+
+
+
+
+uint32 CSourcePiece::GetType()
+{
+	return m_sector_checksum;
+}
+
+
+
+
+CClonedPiece::CClonedPiece()
+{
+	m_id = 0;
+	set_flag(CPiece::mCLONED_PIECE);
+	mp_source_piece = NULL;
+}
+
+
+
+
+uint32 CClonedPiece::GetID()
+{
+	return m_id;
+}
+
+
+
+
+/*
+	Instant function. Piece arrives at new location right away.
+*/
+void CClonedPiece::SetDesiredPos(const Mth::Vector & pos, ESectorEffect sectorEffect)
+{
+	//Dbg_MsgAssert(!(m_flags & CPiece::mIN_WORLD), ("can't change position, piece already in world"));
+	m_pos = pos;
+
+	if (sectorEffect == CHANGE_SECTOR)
+	{
+		#if 0
+		//printf("cloned piece at (%.2f,%.2f,%.2f), dims=(%.2f,%.2f,%.2f)\n", 
+		//if (mp_source_piece->GetType() == Script::GenerateCRC("Sk3Ed_Rd1b_10x10x4"))
+//			printf("   cloned piece at (%.3f,%.3f,%.3f), dims=(%.3f,%.3f,%.3f)\n", 
+//				   pos.GetX(), pos.GetY(), pos.GetZ(),
+//				   mp_source_piece->m_dims.GetX(), mp_source_piece->m_dims.GetY(), mp_source_piece->m_dims.GetZ());
+		#endif
+
+		/*
+		if (pos.GetX() == -1140.0f && pos.GetZ() == -1140.0f)
+			printf("    add (%f,%f,%f), sector 0x%x\n", 
+				   pos.GetX(), pos.GetY(), pos.GetZ(), m_sector_checksum);
+		*/
+		
+		Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_sector_checksum);
+		Dbg_Assert(p_sector);
+	
+		p_sector->SetWorldPosition(pos);
+	}
+}
+
+
+
+
+/*
+	Instant function. Piece arrives at new rotation right away.
+*/
+void CClonedPiece::SetDesiredRot(Mth::ERot90 rot, ESectorEffect sectorEffect)
+{
+	//Dbg_MsgAssert(!(m_flags & CPiece::mIN_WORLD), ("can't change rotation, piece already in world"));
+	m_rot = rot;
+
+	if (sectorEffect == CHANGE_SECTOR)
+	{
+		Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_sector_checksum);
+		Dbg_Assert(p_sector);
+
+		//Dbg_Message("Rotating piece with to %d", (int) rot * 90);
+		p_sector->SetYRotation(rot);
+	}
+}
+
+
+
+
+void CClonedPiece::SetSoftRot(float rot)
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_sector_checksum);
+	Dbg_Assert(p_sector);
+
+	Nx::CGeom *p_geom = p_sector->GetGeom();
+	if (p_geom)
+	{
+		Mth::Matrix rot_mat;
+		rot_mat.Ident();
+		rot_mat.RotateY(Mth::DegToRad(rot));
+		p_geom->SetOrientation(rot_mat);
+	}
+}
+
+
+
+
+void CClonedPiece::SetScaleX(float scaleX)
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_sector_checksum);
+	Dbg_Assert(p_sector);
+	Mth::Vector scale_vect;
+	if (m_rot & 1)
+	{
+		scale_vect[X] = 1.0f;
+		scale_vect[Z] = scaleX;
+	}
+	else
+	{
+		scale_vect[X] = scaleX;
+		scale_vect[Z] = 1.0f;
+	}
+	scale_vect[Y] = 1.0f;
+	p_sector->SetScale(scale_vect);
+}
+
+
+
+
+// Turn on or off the highlight effect for the geometry that makes up a cloned piece
+void CClonedPiece::Highlight(bool on, bool makePurple)
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_sector_checksum);
+	if (!p_sector)
+	{
+		return;
+	}	
+	//Dbg_Assert(p_sector);
+
+	Nx::CGeom *p_geom = p_sector->GetGeom();
+	if (p_geom)
+	{
+		// This effect is somewhat arbitary.   I just want to get SOME effect
+		if (on)
+		{
+			if (makePurple)
+				p_geom->SetColor(Image::RGBA(160,100,160,0));
+			else
+				p_geom->SetColor(Image::RGBA(160,100,100,0));
+			//Ryan("set color for sector=%p\n", p_sector);
+		}
+		else
+		{
+			// ClearColor flags the geom as being NOT COLORED, rather than setting the color to neutral.
+			p_geom->ClearColor();		
+			//Ryan("clear color for sector=%p\n", p_sector);
+		}
+	}
+}
+
+
+
+
+void CClonedPiece::SetActive(bool active)
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_sector_checksum);
+	Dbg_Assert(p_sector);
+	p_sector->SetActive(active);
+}
+
+
+
+
+void CClonedPiece::SetVisibility(bool visible)
+{
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(m_sector_checksum);
+	Dbg_Assert(p_sector);
+	uint32 mask = visible ? 0xFF : 0;
+	p_sector->SetVisibility(mask);
+}
+
+
+
+
+Mth::Vector	CClonedPiece::GetDims()
+{
+	Dbg_Assert(mp_source_piece);
+
+	Mth::Vector result;
+	if (m_rot & 1)
+	{
+		result[X] = mp_source_piece->m_dims.GetZ();
+		result[Z] = mp_source_piece->m_dims.GetX();
+	}
+	else
+	{
+		result[X] = mp_source_piece->m_dims.GetX();
+		result[Z] = mp_source_piece->m_dims.GetZ();
+	}
+	result[Y] = mp_source_piece->m_dims.GetY();
+
+	return result;
+}
+
+
+
+
+CParkGenerator::CParkGenerator()
+{
+	m_flags = EFlags(0);
+	mp_cloned_piece_list = NULL;
+	mp_source_piece_list = NULL;
+
+	mp_cloned_scene = NULL;
+	mp_source_scene = NULL;
+	m_num_cloned_pieces = 0;
+	m_num_source_pieces = 0;
+
+	m_next_id = 32;
+
+	mp_mem_region = NULL;
+	mp_mem_heap = NULL;
+
+	m_mem_usage_info.mMainHeapFree = 0;
+	m_mem_usage_info.mMainHeapUsed = 0;
+	m_mem_usage_info.mParkHeapFree = 0;
+	m_mem_usage_info.mParkHeapUsed = 0;
+	m_mem_usage_info.mLastMainUsed = 0;
+	
+	m_total_rail_points = 0;
+	m_total_rail_linked_points = 0;
+	
+	m_max_players=2;
+}
+
+
+
+
+CParkGenerator::~CParkGenerator()
+{
+	// Clean up scenes (do this last)
+	if (mp_cloned_scene)
+	{
+		delete mp_cloned_scene;
+	}
+	if (mp_source_scene)
+	{
+		delete mp_source_scene;
+	}
+}
+
+int	CParkGenerator::GetResourceSize(char *name)
+{
+	Script::CStruct *p_resource_struct = Script::GetStructure("Ed_Resources_Info");
+	int size;
+
+	if (strcmp(name, "main_heap_base") == 0)
+	{
+		Script::CArray *p_values;
+		p_resource_struct->GetArray(name, &p_values);
+		#ifdef __PLAT_NGPS__
+		size = p_values->GetInteger(0);
+		#else
+			#ifdef __PLAT_NGC__
+			size = 4654800+((2 * 1024 * 1024)-900000);	//p_values->GetInteger(1);
+			#else
+			size = p_values->GetInteger(2);
+			#endif
+		#endif
+	}
+	else if(strcmp(name, "main_heap_pad") == 0)
+	{
+		Script::CArray *p_values;
+		p_resource_struct->GetArray(name, &p_values);
+#		if defined( __PLAT_NGPS__ )
+		size = p_values->GetInteger(0);
+#		elif defined( __PLAT_NGC__ )
+		size = -2294188;	//p_values->GetInteger(1);
+#		else
+		size = p_values->GetInteger(2);
+#		endif
+
+	}
+	else if (strcmp(name, "theme_pad") == 0)
+	{
+		int theme=CParkManager::sInstance()->GetTheme();
+		#ifdef __PLAT_NGC__
+		int theme_size[5] =
+		{
+			0,
+			179296,
+			822784,
+			482944,
+			444544
+		};
+		size = theme_size[theme];
+		#else
+			#ifdef __PLAT_XBOX__
+				int theme_size[5] =
+				{
+					0,
+					253728,
+					564688,
+					161344,
+					441856
+				};
+				size = theme_size[theme];
+			#else		
+				Script::CArray *p_values;
+				p_resource_struct->GetArray(name, &p_values);
+				size = p_values->GetInteger(theme);
+			#endif
+		#endif		// __PLAT_NGC__
+	}
+	#ifdef __PLAT_NGC__
+	else if(strcmp(name, "floor_piece_size_main") == 0)
+	{
+		size = 1000;
+	}
+	#endif
+	else
+	{
+		if (!p_resource_struct->GetInteger(name, &size))
+		{
+			Dbg_MsgAssert(0, ("couldn't get resource size '%s'", name));
+			return 0;
+		}
+	}
+
+	return size;
+}
+
+
+
+
+Mem::Heap *CParkGenerator::GetParkEditorHeap()
+{
+	Dbg_MsgAssert(mp_mem_heap, ("no park editor heap"));
+	return mp_mem_heap;
+}
+
+
+
+void CParkGenerator::SetMaxPlayers(int maxPlayers)
+{
+	Dbg_MsgAssert(maxPlayers>1 && maxPlayers<=GameNet::vMAX_PLAYERS,("Bad maxPlayers of %d",maxPlayers));
+	m_max_players=maxPlayers;
+}	
+
+CParkGenerator::MemUsageInfo CParkGenerator::GetResourceUsageInfo(bool printInfo)
+{
+	Dbg_Assert(mp_mem_heap);
+	Dbg_Assert(this);
+	
+	//int base_park_heap = 0;
+	//int max_base_park_heap = 0;
+	
+	//int padding_size = GetResourceSize("park_heap_pad");
+	//int used_park_heap = mp_mem_heap->mUsedMem.m_count;
+	//int free_park_heap = mp_mem_heap->mFreeMem.m_count + mp_mem_region->MemAvailable();
+
+
+	// allowed to be used, that is
+	//int allowed_park_heap_mem = free_park_heap + used_park_heap - padding_size;
+	//float park_heap_pct = (float) (used_park_heap - base_park_heap) / (float) (allowed_park_heap_mem - max_base_park_heap);
+	//float actual_park_heap_pct = (float) (used_park_heap) / (float) (allowed_park_heap_mem);
+	/*
+	if (largest_free_block < padding_size)
+	{
+		// there is no guarantee we can safetly fit the next piece
+		// placed, so make the memory meter full
+		park_heap_pct = actual_park_heap_pct = 1.0001f;
+	}
+	*/
+
+	Mem::Heap *p_main_heap = Mem::Manager::sHandle().BottomUpHeap();
+	Mem::Region *p_main_region = p_main_heap->mp_region;
+	Dbg_Assert(p_main_region);
+	if (m_mem_usage_info.mMainHeapUsed != p_main_heap->mUsedMem.m_count)
+	{
+		m_mem_usage_info.mLastMainUsed = m_mem_usage_info.mMainHeapUsed;
+	}
+	
+	// Mick, the replay buffer is allocated in the park editor
+	// but we want to ignore it in our calculations
+	// so we just subtract it from the main_heap_pad																			 
+#ifdef __PLAT_NGC__
+	int main_padding_size = GetResourceSize("main_heap_pad");
+#else
+#if __USE_REPLAYS__
+	int main_padding_size = GetResourceSize("main_heap_pad") - Replay::GetBufferSize();
+#else	
+	int main_padding_size = GetResourceSize("main_heap_pad");
+#endif
+#endif
+	
+	main_padding_size+=(m_max_players-1)*(SKATER_HEAP_SIZE+SKATER_GEOM_HEAP_SIZE);
+	
+	int total_mem_available=p_main_heap->mFreeMem.m_count + p_main_region->MemAvailable();
+	if (main_padding_size > total_mem_available)
+	{
+		//Dbg_MsgAssert(0,("main_padding_size > total_mem_available !! (%d > %d)",main_padding_size,total_mem_available));
+		#ifdef	__NOPT_ASSERT__
+		//printf("main_padding_size > total_mem_available !! (%d > %d)\n",main_padding_size,total_mem_available);
+		#endif
+		//ParkEd("main_padding_size > total_mem_available !! (%d > %d)\n",main_padding_size,total_mem_available);
+	}
+		
+	m_mem_usage_info.mMainHeapUsed = p_main_heap->mUsedMem.m_count;
+	m_mem_usage_info.mMainHeapFree = p_main_heap->mFreeMem.m_count + p_main_region->MemAvailable() - main_padding_size;
+	
+	
+	//printf("p_main_heap->mFreeMem.m_count=%d\np_main_region->MemAvailable()=%d\nmain_padding_size=%d\n",p_main_heap->mFreeMem.m_count,p_main_region->MemAvailable(),main_padding_size);
+	int park_padding_size = GetResourceSize("park_heap_pad");
+	m_mem_usage_info.mParkHeapUsed = mp_mem_heap->mUsedMem.m_count;
+	m_mem_usage_info.mParkHeapFree = mp_mem_heap->mFreeMem.m_count + mp_mem_region->MemAvailable() - park_padding_size;
+
+	if (p_main_heap->LargestFreeBlock() < m_mem_usage_info.mMainHeapFree && 	// heap definitely fragmented
+		p_main_heap->LargestFreeBlock() < main_padding_size && 					// no block big enough for single piece
+		m_mem_usage_info.mMainHeapFree >= main_padding_size) 					// there is enough memory for single piece
+	{
+		// XXX
+		Ryan("===================== DEFRAG ===========================\n");
+		Ryan("largest: %d, free: %d, padding %d\n", 
+			 p_main_heap->LargestFreeBlock(), m_mem_usage_info.mMainHeapFree, main_padding_size);
+		Ryan("========================================================\n");
+		m_mem_usage_info.mIsFragmented = true;
+		int false_free_amt = m_mem_usage_info.mMainHeapFree - p_main_heap->LargestFreeBlock();
+		m_mem_usage_info.mMainHeapFree -= false_free_amt;
+		m_mem_usage_info.mMainHeapUsed += false_free_amt;
+	}
+	else
+		m_mem_usage_info.mIsFragmented = false;
+		
+		
+	#if 0
+	if ( p_main_heap->mFreeMem.m_count > 200000)
+	{
+		m_mem_usage_info.mIsFragmented = true;	
+	}
+	#endif
+
+	m_mem_usage_info.mTotalClonedPieces = m_num_cloned_pieces;
+	m_mem_usage_info.mTotalRailPoints = m_total_rail_points;
+	m_mem_usage_info.mTotalLinkedRailPoints = m_total_rail_linked_points;
+
+
+	// subtract theme specific padding
+	if (CParkManager::sInstance()->GetTheme() > 0)
+	{
+		m_mem_usage_info.mMainHeapFree -= GetResourceSize("theme_pad");
+	}
+
+	
+	if (printInfo)
+	{
+		Ryan("=====================================================\nParkGen Usage Stats:\n\n");
+		Ryan("largest free block: %d\n", mp_mem_heap->LargestFreeBlock());
+		Ryan("used park heap: %d\n", m_mem_usage_info.mParkHeapUsed);
+		Ryan("free park heap: %d\n", m_mem_usage_info.mParkHeapFree);
+		Ryan("main heap used: %d\n", m_mem_usage_info.mMainHeapUsed);
+		Ryan("main heap available: %d\n", m_mem_usage_info.mMainHeapFree);
+	}
+	
+	return m_mem_usage_info;
+	
+}
+
+int	CParkGenerator::GetMaxPlayersPossible()
+{
+	CParkGenerator::MemUsageInfo usage_info = GetResourceUsageInfo();
+	// GetResourceUsageInfo has already subtracted the memory used by the currently set max players,
+	// so add that back in so that the max max can be calculated.
+	usage_info.mMainHeapFree += (m_max_players-1)*(SKATER_HEAP_SIZE+SKATER_GEOM_HEAP_SIZE);
+	
+	// The +1 is because at least 1 skater is assumed.
+	return 	usage_info.mMainHeapFree/(SKATER_HEAP_SIZE+SKATER_GEOM_HEAP_SIZE)+1;
+}
+
+
+
+/*
+	Given checksum of piece name, returns pointer to source piece.
+*/
+CSourcePiece *CParkGenerator::GetMasterPiece(uint32 pieceType, bool assert)
+{
+	CPiece *p_piece = mp_source_piece_list;
+	while(p_piece)
+	{
+		if (p_piece->CastToCSourcePiece()->m_sector_checksum == pieceType)
+			return p_piece->CastToCSourcePiece();
+		p_piece = p_piece->mp_next_in_list;
+	}
+	
+	if (assert)
+	{
+		Dbg_MsgAssert(0, ("couldn't not find master piece %s", Script::FindChecksumName(pieceType)));
+	}
+	else
+		printf("couldn't not find master piece %s\n", Script::FindChecksumName(pieceType));
+
+	return NULL;
+}
+
+
+
+
+CSourcePiece *CParkGenerator::GetNextMasterPiece(CSourcePiece *pLast)
+{	
+	if (!pLast)
+		return mp_source_piece_list->CastToCSourcePiece();
+	else
+		return pLast->mp_next_in_list->CastToCSourcePiece();
+}
+
+
+
+
+/*
+	Makes a copy of a source piece. The copy can then be added to a metapiece, or to the world.
+
+	A soft piece can be smoothly rotated, like a piece used in a cursor.
+*/
+CClonedPiece *CParkGenerator::ClonePiece(CPiece *pMasterPiece, CPiece::EFlags flags)
+{
+	// make sure flags make sense
+	flags = CPiece::EFlags(flags & (CPiece::mSOFT_PIECE | CPiece::mFLOOR | CPiece::mNO_RAILS));
+	
+	Dbg_Assert(pMasterPiece);
+
+	CSourcePiece *pSource;
+	if (pMasterPiece->m_flags & CPiece::mSOURCE_PIECE)
+		pSource = pMasterPiece->CastToCSourcePiece();
+	else
+		pSource = pMasterPiece->CastToCClonedPiece()->mp_source_piece;
+
+	Dbg_Assert(pSource);
+	Dbg_Assert(mp_source_scene);
+	Dbg_Assert(mp_cloned_scene);
+
+	// putting cloned sectors on main heap seems best right now
+	//Mem::Manager::sHandle().PushContext(mp_mem_heap);
+	uint32 new_sector_checksum;
+	if (flags & CPiece::mSOFT_PIECE)
+	{
+		// create fresh soft piece (hmm, that sounds kind of disgusting)
+		new_sector_checksum = mp_source_scene->CloneSector(pSource->m_sector_checksum, mp_cloned_scene, true, false); // no instance for now (should be instance)
+		//Dbg_Message("************************* New soft checksum %x", new_sector_checksum);
+	}
+	else
+	{
+		// create fresh hard piece (no, this sounds worse!)
+		new_sector_checksum = mp_source_scene->CloneSector(pSource->m_sector_checksum, mp_cloned_scene, false, true); // no instance
+		
+		if (!(flags & CPiece::mNO_RAILS))
+		{
+			m_total_rail_points += pSource->m_num_rail_points;
+			m_total_rail_linked_points += pSource->m_num_linked_rail_points;
+		}
+	}
+	//Mem::Manager::sHandle().PopContext();
+
+	// Create cloned piece, will soon live in world or as animated piece
+	Mem::Manager::sHandle().PushContext(mp_mem_heap);
+	CClonedPiece *p_cloned_piece = new CClonedPiece();
+	Mem::Manager::sHandle().PopContext();
+	p_cloned_piece->m_sector_checksum = new_sector_checksum;
+	p_cloned_piece->m_id = m_next_id++;
+	p_cloned_piece->mp_source_piece = pSource;
+	p_cloned_piece->SetDesiredRot(Mth::ERot90(0), CClonedPiece::MARKER_ONLY);
+	
+	m_num_cloned_pieces++;
+
+	p_cloned_piece->set_flag(flags);
+
+	set_flag(mSECTORS_GENERATED);
+	
+	return p_cloned_piece;
+}
+
+
+
+
+/*
+	Must be called to finalized cloned piece's presence in world.
+*/
+void CParkGenerator::AddClonedPieceToWorld(CClonedPiece *pPiece)
+{
+	Dbg_MsgAssert(!(pPiece->GetFlags() & CPiece::mIN_WORLD), ("piece already in world"));
+	pPiece->set_flag(CPiece::mIN_WORLD);
+
+	pPiece->mp_next_in_list = mp_cloned_piece_list;
+	mp_cloned_piece_list = pPiece;
+	
+	set_flag(mPIECES_IN_WORLD);
+}
+
+
+
+
+void CParkGenerator::DestroyClonedPiece(CClonedPiece *pPiece)
+{
+	destroy_piece_impl(pPiece, DESTROY_PIECES_AND_SECTORS);
+}
+
+
+#ifdef __PLAT_NGPS__
+#if PRINT_RAW_RESULTS
+void print_ps2_data(NxPs2::CGeomNode *p_node)
+{
+	uint8 *p_dma = p_node->GetDma();
+
+	int num_verts = NxPs2::dma::GetNumVertices(p_dma);
+	if( num_verts >= 3 )
+	{
+		bool short_xyz = (NxPs2::dma::GetBitLengthXYZ(p_dma) == 16);
+		Mth::Vector mesh_center = p_node->GetBoundingSphere();
+
+		// Get DMA arrays
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+		sint32 *p_vert_array = new sint32[4 * num_verts];
+		sint32 *p_uv_array = new sint32[2 * num_verts];
+		uint32 *p_rgb_array = new uint32[num_verts];
+		Mem::Manager::sHandle().PopContext();
+
+		// Copy DMA data
+		NxPs2::dma::ExtractXYZs(p_dma, (uint8 *) p_vert_array);
+		NxPs2::dma::ExtractSTs(p_dma, (uint8 *) p_uv_array);
+		NxPs2::dma::ExtractRGBAs(p_dma, (uint8 *) p_rgb_array);
+
+		Mth::Vector v0;
+		float u, v;
+		Image::RGBA col;
+
+		Dbg_Message("Number of verts: %d", num_verts);
+
+		for (int idx = 0; idx < num_verts; idx++)
+		{
+			if (short_xyz)
+			{
+				NxPs2::dma::ConvertXYZToFloat(v0, &(p_vert_array[idx * 4]), mesh_center);
+			} else {
+				NxPs2::dma::ConvertXYZToFloat(v0, &(p_vert_array[idx * 4]));
+			}
+			NxPs2::dma::ConvertSTToFloat(u, v, &(p_uv_array[idx * 2]));
+			col = *((Image::RGBA *) &(p_rgb_array[idx]));
+
+			Dbg_Message("Vert %d", idx);
+			Dbg_Message("Raw Pos (%x, %x, %x, %x)", p_vert_array[idx * 4 + 0], p_vert_array[idx * 4 + 1], p_vert_array[idx * 4 + 2], p_vert_array[idx * 4 + 3]);
+			Dbg_Message("Pos (%f, %f, %f, %f)", v0[X], v0[Y], v0[Z], v0[W]);
+			Dbg_Message("UV (%f, %f)", u, v);
+			Dbg_Message("Color (%d, %d, %d, %d)", col.r, col.g, col.b, col.a);
+
+		}
+
+		// Free DMA buffers
+		delete [] p_vert_array;
+		delete [] p_uv_array;
+		delete [] p_rgb_array;
+	}
+}
+#endif // PRINT_RAW_RESULTS
+#endif
+
+
+/*
+	Creates the set of source pieces. The data will have been loaded by this point.
+*/
+void CParkGenerator::InitializeMasterPieces(int parkW, int parkH, int parkL, int theme)
+{
+	if (flag_on(mMASTER_STUFF_LOADED))
+		UnloadMasterPieces();
+	
+	set_flag(mMASTER_STUFF_LOADED);
+
+	// When we reload the master piece, we reset the id of the generate pieces
+	// so that net games will be syncronized
+	m_next_id = 32;
+
+	// set up park editor heap
+	Dbg_MsgAssert(!mp_mem_region && !mp_mem_heap,(	"park editor heap already exists"));
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	#ifdef __PLAT_NGC__
+	// Added 200K to the TOP_DOWN_REQUIRED_MARGIN for safety, and cancelled that by removing
+	// 200K from the park editor heap. (Normally 900K)
+	mp_mem_region = new Mem::AllocRegion(700000);	
+	#else
+	mp_mem_region = new Mem::AllocRegion(GetResourceSize("heap"));	
+	#endif
+	Mem::Manager::sHandle().PopContext();
+	Ryan("  allocated park editor region at %p\n", mp_mem_region);
+	mp_mem_heap = Mem::Manager::sHandle().CreateHeap( mp_mem_region, Mem::Allocator::vBOTTOM_UP, "Park Editor" );
+
+	// fetch scene
+	const char *p_scene_name="sk5ed";
+		
+	mp_source_scene = Nx::CEngine::sGetScene(p_scene_name);
+	
+	// set up all the source pieces
+	Dbg_MsgAssert(mp_source_scene, ("couldn't get scene %s", p_scene_name));
+	Lst::HashTable *p_sector_list = mp_source_scene->GetSectorList();
+	Nx::CSector *p_sector;
+
+//	printf("Setting up park editor source pieces:\n");
+	p_sector_list->IterateStart();
+	while ((p_sector = p_sector_list->IterateNext()))
+	{
+		Mem::Manager::sHandle().PushContext(mp_mem_heap);
+		CSourcePiece *p_new_piece = new CSourcePiece();
+		Mem::Manager::sHandle().PopContext();
+		p_new_piece->mp_next_in_list = mp_source_piece_list;
+		mp_source_piece_list = p_new_piece;
+
+		//printf ("Piece with checksum 0x%x\n",p_sector->GetChecksum());
+#ifdef __PLAT_NGPS__
+#if PRINT_RAW_RESULTS
+		if (p_sector->GetChecksum() == Crc::GenerateCRCFromString("Sk3Ed_F_10x10"))
+		{
+			Dbg_Message("******* Found the target piece");
+			Mth::CBBox piece_bbox(p_sector->GetBoundingBox());
+			Dbg_Message("Bounding Box (%.8f, %.8f, %.8f) - (%.8f, %.8f, %.8f)",
+						piece_bbox.GetMin()[X], piece_bbox.GetMin()[Y], piece_bbox.GetMin()[Z],
+						piece_bbox.GetMax()[X], piece_bbox.GetMax()[Y], piece_bbox.GetMax()[Z]);
+
+			Nx::CPs2Geom *p_geom = static_cast(p_sector->GetGeom());
+			NxPs2::CGeomNode *p_geom_node = p_geom->GetEngineObject();
+			Dbg_Assert(p_geom_node);
+
+			// Put all the meshes in an array
+			int num_meshes = 0;
+			NxPs2::CGeomNode *mesh_array[10];
+			Nx::find_geom_leaves(p_geom_node, mesh_array, num_meshes);
+			Dbg_Assert(num_meshes == 1);
+			p_geom_node = mesh_array[0];
+
+			print_ps2_data(p_geom_node);
+		}
+#endif
+#endif
+		p_new_piece->m_sector_checksum = p_sector->GetChecksum();
+
+		// Init dimensions
+		Mth::CBBox piece_bbox(p_sector->GetBoundingBox());
+		
+		int cell_w = (int) ((piece_bbox.GetMax()[X] - piece_bbox.GetMin()[X] + 2.0f) / CELL_WIDTH);
+		if (cell_w < 1) cell_w = 1;
+		p_new_piece->m_dims[X] = cell_w * CELL_WIDTH;
+		
+		int cell_l = (int) ((piece_bbox.GetMax()[Z] - piece_bbox.GetMin()[Z] + 2.0f) / CELL_LENGTH);
+		if (cell_l < 1) cell_l = 1;
+		p_new_piece->m_dims[Z] = cell_l * CELL_LENGTH;
+		
+		p_new_piece->m_dims[Y] = (piece_bbox.GetMax()[Y] - piece_bbox.GetMin()[Y]);
+
+		// piece cannot have any dimension less the that of a park editor cell
+		if (p_new_piece->m_dims[Y] < CParkGenerator::CELL_HEIGHT && p_new_piece->m_dims[Y] > 0.0f)
+			p_new_piece->m_dims[Y] = CParkGenerator::CELL_HEIGHT;
+		
+		m_num_source_pieces++;
+		
+		#if 0
+		//printf("   %s\n      [%f,%f,%f]-[%f,%f,%f]\n", Script::FindChecksumName(p_new_piece->m_sector_checksum),
+		//	   piece_bbox.GetMin()[X], piece_bbox.GetMin()[Y], piece_bbox.GetMin()[Z],
+		//	   piece_bbox.GetMax()[X], piece_bbox.GetMax()[Y], piece_bbox.GetMax()[Z]);
+		printf("      %s [%.2f,%.2f,%.2f]\n", Script::FindChecksumName(p_new_piece->m_sector_checksum),
+			   p_new_piece->m_dims[X], p_new_piece->m_dims[Y], p_new_piece->m_dims[Z]);
+		#endif
+	}
+
+	mp_cloned_scene = Nx::CEngine::sCreateScene("cloned", mp_source_scene->GetTexDict(), true);
+
+#ifdef __PLAT_NGC__
+	// Need to copy scene data here, so stuff added to the cloned scene has geometry pools.
+	Nx::CNgcScene *p_Ngc_scene = static_cast( mp_cloned_scene );
+	Nx::CNgcScene *p_source_scene = static_cast( mp_source_scene );
+	NxNgc::sScene *p_scene = p_Ngc_scene->GetEngineScene();
+	memcpy( p_scene, p_source_scene->GetEngineScene(), sizeof( NxNgc::sScene ) );
+	p_scene->m_num_meshes			= 0;		// No meshes as yet.
+	p_scene->m_num_filled_meshes	= 0;
+	p_scene->mpp_mesh_list			= NULL;
+
+	p_scene->mp_hierarchyObjects	= NULL;
+	p_scene->m_numHierarchyObjects	= 0;
+
+	p_scene->mp_hierarchyObjects	= NULL;
+	p_scene->m_numHierarchyObjects	= 0;
+
+	// Make it renderable, and make sure it doesn't try to double-delete the scene data.
+	p_scene->m_is_dictionary		= false;
+	p_scene->m_flags				= SCENE_FLAG_CLONED_GEOM;
+#endif		// __PLAT_NGC__
+
+	Mth::CBBox b_box;
+	Mth::Vector vmin(-((float) parkW * CELL_WIDTH) / 2.0f, -((float) parkH * CELL_HEIGHT) / 2.0f, -((float) parkL * CELL_LENGTH) / 2.0f);
+	b_box.AddPoint(vmin);
+	Mth::Vector vmax(((float) parkW * CELL_WIDTH) / 2.0f, ((float) parkH * CELL_HEIGHT) / 2.0f, ((float) parkL * CELL_LENGTH) / 2.0f);
+	b_box.AddPoint(vmax);
+	mp_cloned_scene->CreateCollision(b_box);
+
+	KillRestarts();
+}
+
+/*
+// K: Added to allow cleanup of the park editor heap during play
+void CParkGenerator::DeleteSourceAndClonedPieces()
+{
+	CPiece *p_piece = mp_source_piece_list;
+	while (p_piece)
+	{
+		CPiece *p_next = p_piece->mp_next_in_list;
+		delete p_piece;
+		p_piece = p_next;
+	}
+	mp_source_piece_list = NULL;
+	m_num_source_pieces=0;
+
+	printf("After deleting CParkGenerator::mp_source_piece_list ...\n");	
+	printf("Num CMapListNode's = %d\n",Ed::CMapListNode::SGetNumUsedItems());
+	MemView_AnalyzeHeap(Ed::CParkManager::sInstance()->GetGenerator()->GetParkEditorHeap());
+	
+	p_piece = mp_cloned_piece_list;
+	while(p_piece)
+	{
+		CPiece *p_next = p_piece->mp_next_in_list;
+		delete p_piece;
+		p_piece = p_next;
+	}
+	m_num_cloned_pieces=0;
+	mp_cloned_piece_list=NULL;
+	
+	printf("After deleting CParkGenerator::mp_cloned_piece_list ...\n");	
+	printf("Num CMapListNode's = %d\n",Ed::CMapListNode::SGetNumUsedItems());
+	MemView_AnalyzeHeap(Ed::CParkManager::sInstance()->GetGenerator()->GetParkEditorHeap());
+}
+*/
+
+/*
+	Destroys the set of source pieces.
+*/
+void CParkGenerator::UnloadMasterPieces()
+{
+	if (flag_on(mPIECES_IN_WORLD))
+		DestroyAllClonedPieces(DESTROY_PIECES_AND_SECTORS);
+
+	// GARRETT: not sure if you need to do anything here
+	
+	CPiece *p_piece = mp_source_piece_list;
+	while (p_piece)
+	{
+		CPiece *p_next = p_piece->mp_next_in_list;
+		delete p_piece;
+		p_piece = p_next;
+	}
+	mp_source_piece_list = NULL;
+	m_num_source_pieces--;
+	//m_num_source_pieces=0;
+	
+	clear_flag(mMASTER_STUFF_LOADED);
+	
+	Mem::Manager::sHandle().RemoveHeap(mp_mem_heap);
+	delete mp_mem_region;
+	mp_mem_heap = NULL;
+	mp_mem_region = NULL;
+}
+
+/*
+void CParkGenerator::DeleteParkEditorHeap()
+{
+	if (mp_mem_heap)
+	{
+		Mem::Manager::sHandle().RemoveHeap(mp_mem_heap);
+		delete mp_mem_region;
+		mp_mem_heap = NULL;
+		mp_mem_region = NULL;
+	}	
+}
+*/
+
+/* 
+	Should be called after adding/removing sectors
+*/
+void CParkGenerator::PostGenerate()
+{
+	Dbg_MsgAssert(flag_on(mPIECES_IN_WORLD) && flag_on(mSECTORS_GENERATED), ("no pieces or sectors available"));
+}
+
+
+
+
+/*
+	Should be called after adding a group of cloned pieces to world.
+*/
+void CParkGenerator::GenerateCollisionInfo(bool assert)
+{
+	// if this flag IS on, then we need to call UpdateSuperSectors() to free the memory
+	if (flag_on(mSECTOR_MEMORY_NEEDS_FREE))
+	{
+	}
+	else if (!assert)
+	{
+		if (!flag_on(mPIECES_IN_WORLD) || !flag_on(mSECTORS_GENERATED))
+			return;
+	}
+	else
+		Dbg_MsgAssert(flag_on(mPIECES_IN_WORLD) && flag_on(mSECTORS_GENERATED), ("no pieces or sectors available"));
+	
+	// GARRETT
+	Dbg_Assert(mp_cloned_scene);
+	mp_cloned_scene->UpdateSuperSectors();
+	//Dbg_Message("Adding collision to scene %x", mp_cloned_scene->GetID());
+	
+	// XXX
+	//printf("there are %d source pieces, %d cloned pieces\n", m_num_source_pieces, m_num_cloned_pieces);
+	
+	set_flag(mCOLLISION_INFO_GENERATED);
+	clear_flag(mSECTOR_MEMORY_NEEDS_FREE);
+}
+
+
+void CParkGenerator::RemoveOuterShellPieces(int theme)
+{
+	uint32 shell_id=CParkManager::Instance()->GetShellSceneID();
+	
+	Nx::CScene *p_scene = Nx::CEngine::sGetScene(shell_id);
+	if (p_scene)
+	{
+		// Turn everything on simply by using a huge bounding box
+		Mth::CBBox a(Mth::Vector(-200000,-200000,-200000),Mth::Vector(200000,200000,200000));
+		p_scene->SetActiveInBox(a,true,0.0f);
+		
+																	 
+		// Calculate the bounding box of the park up to the edges 
+ 		GridDims  fence;
+		fence = CParkManager::Instance()->GetParkNearBounds();		 					// returns the rectangle enclosed by the fence
+		Mth::Vector start = CParkManager::Instance()->GridCoordinatesToWorld(fence);		// top left cornder
+		
+		fence.SetXYZ(fence.GetX()+fence.GetW()-1,fence.GetY(),fence.GetZ()+fence.GetL()-1);
+		Mth::Vector end = CParkManager::Instance()->GridCoordinatesToWorld(fence);		// bot right corner, in one square
+
+		// offset into the park by half a piece		
+		start[X] += CParkGenerator::CELL_WIDTH/2;
+		start[Z] += CParkGenerator::CELL_LENGTH/2;
+		end[X] += CParkGenerator::CELL_WIDTH/2;
+		end[Z] += CParkGenerator::CELL_LENGTH/2;
+		
+		// The Y of the world box calculated above is not correct.... so just make it bigh enough to encompass everything
+		start[Y] = -100000;
+		end[Y]   =  100000;
+		Mth::CBBox b(start,end);
+		// Note: the above bounding box is based on the centers of cells
+		// so it misses a half cell boundry around the inside of the fence
+		// however, this is actually close to what I want than the true bounding box
+		// as I want to provide a little slack so we don't accidentally kill 
+		// things that just come inside the fence by an inch or two.
+		// (remember the shell collision is not active) 
+		
+		
+		// and turn off everything that intersects the bounding box we just calculated
+		p_scene->SetActiveInBox(b,false,0.0f);
+		
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Where is my scene?! %s", Script::FindChecksumName(shell_id)));
+	}
+}
+
+void CParkGenerator::CleanUpOutRailSet()
+{
+	m_out_rail_set.Destroy();
+	m_out_rail_set.FreeAllocators();
+}
+
+// K: Factored this code out of GenerateNodeInfo because it is required by FindNearestRailPoint
+void CParkGenerator::GenerateOutRailSet(CMapListNode * p_concrete_metapiece_list)
+{
+	CleanUpOutRailSet();
+	
+	Mdl::Skate * p_skate_mod =  Mdl::Skate::Instance();
+	if (p_skate_mod->m_cur_level == CRCD(0xe8b4b836,"load_sk5ed"))
+	{
+		// K: Not using the top down heap whilst in the editor to avoid running out of memory, 
+		// since this causes a 192K spike in memory usage when doing a test play.
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap());
+	}
+	else	
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	}
+	m_out_rail_set.SetupAllocators(GetResourceSize("out_railpoint_pool"), GetResourceSize("out_railstring_pool"), false);
+	Mem::Manager::sHandle().PopContext();
+
+	// Need to iterate over the pieces here, meaning we'd iterate over the meta pieces
+	// and then find the pieces they are composed of.....
+	// should be similar to rebuilding the park (in terms of iterating over the pieces)
+	
+	// We have been passed in the list of concrete metapiece
+	// (the pieces that have actually been placed in the level
+	CMapListNode *p_node = p_concrete_metapiece_list;
+	while(p_node)
+	{
+		CConcreteMetaPiece *p_meta = p_node->GetConcreteMeta();
+
+		if (p_meta->IsInPark() && !p_meta->NoRails())
+		{
+			// Iterate over the pieces in the metapiece
+			int pieces = p_meta->CountContainedPieces();
+			for (int i=0;iGetContainedPiece(i);
+				CClonedPiece *pPiece = p_base_piece->CastToCClonedPiece();	    
+	
+	//			if (!(pPiece->m_type & CPiece::vNO_RAILS))
+				{
+					// go through all rail strings associated with source object
+					for (int n = 0; ;n++)
+					{
+						uint32 piece_checksum = pPiece->mp_source_piece->m_sector_checksum;
+						RailString *pSrcString = m_in_rail_set.GetString(piece_checksum, n);
+						
+						if (pSrcString)
+						{
+//							printf("creating rail string %d for %s, meta=%s\n", 
+//								   n, Script::FindChecksumName(piece_checksum), Script::FindChecksumName(p_meta->GetNameChecksum()));
+							RailString *pOutString = m_out_rail_set.CreateRailString();
+							uint32	trickob_id = pPiece->GetID();
+							if (p_meta->IsRiser())
+							{
+								// objects that are JUST risers cannot be trickobs
+								trickob_id = 0;
+							}
+							pOutString->CopyOffsetAndRot(pSrcString, pPiece->m_pos, pPiece->m_rot, trickob_id);
+							m_out_rail_set.AddString(pOutString);
+						}
+						else
+						{
+							//printf ("No rails here for %x: %s\n", piece_checksum, Script::FindChecksumName(piece_checksum));
+							break;
+						}
+					}		
+				} // end if (!(pPiece->m_type & Piece::vNO_RAILS))
+			}
+		}
+		p_node = p_node->GetNext();
+	}
+}
+
+/*
+	For rail generation, etc.
+*/
+void CParkGenerator::GenerateNodeInfo(CMapListNode * p_concrete_metapiece_list)
+{
+	Dbg_MsgAssert(flag_on(mPIECES_IN_WORLD) && flag_on(mSECTORS_GENERATED), ("no pieces or sectors available"));
+	
+	int		total_nodes = 0;
+	
+	
+	/*
+	========================================================
+	Begin Rail Generation
+	========================================================
+	*/
+
+	printf("Making rail objects\n");
+
+	GenerateOutRailSet(p_concrete_metapiece_list);	
+	
+	total_nodes	+= m_out_rail_set.CountPoints();
+	
+	
+	
+	/*
+	========================================================
+	End Rail Generation
+	========================================================
+	*/
+
+
+	#if 1	
+
+
+	printf("Generating node array\n");
+	
+	// count up regular object nodes
+	int object_nodes = 0;
+	CPiece *p_piece = mp_cloned_piece_list;
+	while (p_piece)
+	{
+		if (!(p_piece->m_flags & CPiece::mFLOOR))
+			object_nodes++;
+		p_piece = p_piece->mp_next_in_list;
+	}
+	// count up object nodes
+	total_nodes += object_nodes;
+	
+	#endif
+	
+
+	#if 1
+	/*
+	========================================================
+	Do Some Restart Stuff
+	========================================================
+	*/
+	
+	// count up restart nodes
+	for (int res = 0; res < vNUM_RESTARTS; res++)
+	{
+		if (m_restart_tab[res].type != vEMPTY)
+			total_nodes++;
+		// one player restarts are repeated as multiplayer
+		if (m_restart_tab[res].type == vONE_PLAYER)
+			total_nodes++;
+		// HORSE restarts are repeated as multiplayer
+		if (m_restart_tab[res].type == vHORSE)
+			total_nodes++;
+		// flag restart nodes will generate 3 extra nodes in addition to a restart
+		if (
+			m_restart_tab[res].type == vRED_FLAG
+			|| m_restart_tab[res].type == vGREEN_FLAG
+			|| m_restart_tab[res].type == vBLUE_FLAG
+			|| m_restart_tab[res].type == vYELLOW_FLAG
+		)
+		{
+		    total_nodes += 3; 
+		}
+			
+	}
+
+	#endif
+	
+	// Add in the number of nodes needed for the created rails.	
+	int num_nodes_required=Obj::GetRailEditor()->GetTotalRailNodesRequired();
+	Dbg_MsgAssert((uint32)num_nodes_required <= vMAX_ID_FOR_CREATED_RAILS-vFIRST_ID_FOR_CREATED_RAILS+1,("Too many created-rail nodes"));
+	total_nodes+=num_nodes_required;
+	
+	
+	/*
+	========================================================
+	Actually Create Node Array
+	========================================================
+	*/
+
+
+	SkateScript::CreateNodeArray(total_nodes, false);
+
+	Script::CArray *pNodeArray = Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+	int current_node_num = 0;
+	#if 1
+	set_up_object_nodes(pNodeArray, ¤t_node_num);
+	#endif
+	
+	set_up_rail_nodes(pNodeArray, ¤t_node_num);
+	
+	Obj::GetRailEditor()->SetUpRailNodes(pNodeArray, ¤t_node_num, vFIRST_ID_FOR_CREATED_RAILS);
+	
+	#if 1
+	set_up_restart_nodes(pNodeArray, ¤t_node_num);
+	#endif
+	
+	CleanUpOutRailSet();
+
+	
+	printf("Calling CreateNodeNameHashTable()\n");
+	SkateScript::CreateNodeNameHashTable();
+	printf("Calling ScriptParseNodeArray()\n");
+	CFuncs::ScriptParseNodeArray(NULL, NULL);
+
+//	m_node_array_is_set_up = true;
+
+	
+	set_flag(mNODE_INFO_GENERATED);
+}
+
+
+
+
+/*
+	Can use 'type' parameter only to remove only CPieces, but not underlying sector. This is useful
+	for freeing up memory no longer needed once park is generated.
+*/
+void CParkGenerator::DestroyAllClonedPieces(EDestroyType type)
+{
+	if (type == DESTROY_PIECES_AND_SECTORS)
+	{
+		Obj::GetRailEditor()->DestroyRailGeometry();
+		Obj::GetRailEditor()->DestroyPostGeometry();
+	}	
+	
+	CPiece *p_entry = mp_cloned_piece_list;
+	while(p_entry)
+	{
+		CPiece *p_next_entry = p_entry->mp_next_in_list;
+		destroy_piece_impl(p_entry->CastToCClonedPiece(), type);
+		p_entry = p_next_entry;
+	}
+	
+	if (type == DESTROY_PIECES_AND_SECTORS)
+		clear_flag(mSECTORS_GENERATED);
+	clear_flag(mPIECES_IN_WORLD);
+
+	if (type == DESTROY_PIECES_AND_SECTORS)
+	{
+		// CHUNKY
+		Dbg_Assert(mp_cloned_scene);
+		mp_cloned_scene->ClearSuperSectors();
+	}	
+}
+
+
+
+
+void	CParkGenerator::HighlightAllPieces(bool highlight)
+{
+	CPiece *p_entry = mp_cloned_piece_list;
+	while(p_entry)
+	{
+		CPiece *p_next_entry = p_entry->mp_next_in_list;
+		p_entry->CastToCClonedPiece()->Highlight(highlight);
+		p_entry = p_next_entry;
+	}
+}
+
+
+
+
+void	CParkGenerator::SetGapPiecesVisible(bool visible)
+{
+#if 1	
+	// Nothing doing here yet, eventually will turn off the wireframe pieces
+	CPiece *p_entry = mp_cloned_piece_list;
+	while(p_entry)
+	{
+		CPiece *p_next_entry = p_entry->mp_next_in_list;
+		if (p_entry->CastToCClonedPiece()->GetFlags() & CPiece::mGAP)
+		{
+			//Dbg_Assert(0);
+			p_entry->CastToCClonedPiece()->SetVisibility(visible);
+		}
+		p_entry = p_next_entry;
+	}
+#endif
+}
+
+
+
+
+void	CParkGenerator::SetRestartPiecesVisible(bool visible)
+{
+	Ryan("the stars go in and the stars go out %d\n", visible);
+	CPiece *p_entry = mp_cloned_piece_list;
+	while(p_entry)
+	{
+		CPiece *p_next_entry = p_entry->mp_next_in_list;
+		uint32 id = p_entry->CastToCClonedPiece()->mp_source_piece->GetType();
+		if (CParkManager::Instance()->IsRestartPiece(id))
+		{
+			p_entry->CastToCClonedPiece()->SetActive(visible);	
+		}
+		p_entry = p_next_entry;
+	}
+}
+
+
+
+
+void CParkGenerator::SetLightProps(int num_lights, 
+						  float amb_const_r, float amb_const_g, float amb_const_b, 
+						  float falloff_const_r, float falloff_const_g, float falloff_const_b,
+						  float cursor_ambience)
+{
+	
+	Dbg_MsgAssert(num_lights <= vMAX_LIGHTS, ("too many lights"));
+	m_numLights = num_lights;
+	
+	m_ambientLightIntensity.r = amb_const_r;
+	m_ambientLightIntensity.g = amb_const_g;
+	m_ambientLightIntensity.b = amb_const_b;
+	m_falloffLightIntensity.r = falloff_const_r;
+	m_falloffLightIntensity.g = falloff_const_g;
+	m_falloffLightIntensity.b = falloff_const_b;
+
+	m_cursorAmbience = cursor_ambience;
+}
+
+
+
+
+void CParkGenerator::SetLight(Mth::Vector &light_pos, int light_num)
+{
+	
+	Dbg_MsgAssert(light_num < m_numLights, ("out of light slots"));
+
+	m_lightTab[light_num] = light_pos;
+}
+
+											   
+// Given the id of a piece in the world, apply the lights to it											   
+void CParkGenerator::CalculateLighting(CClonedPiece *p_piece)
+{	
+	Nx::CSector *p_sector= Nx::CEngine::sGetSector(p_piece->m_sector_checksum);
+	Dbg_MsgAssert(p_sector,("Trying to Calculate lighting on non-existant sector"))
+	Nx::CGeom 	*p_geom = p_sector->GetGeom();
+	Dbg_MsgAssert(p_geom,("sector does not have geometry"))
+	
+	Nx::CSector *p_source_sector= Nx::CEngine::sGetSector(p_piece->GetSourcePiece()->GetType());
+	Dbg_MsgAssert(p_source_sector,("Trying to Calculate lighting on sector with no source sector"))
+	Nx::CGeom 	*p_source_geom = p_source_sector->GetGeom();
+	Dbg_MsgAssert(p_source_geom,("source sector does not have geometry"))
+
+	//
+	// Do renderable geometry
+	int verts = p_geom->GetNumRenderVerts();
+	Dbg_MsgAssert(verts == p_source_geom->GetNumRenderVerts(),("source and clone have differing no of verts (%d, %d)\n",verts,p_source_geom->GetNumRenderVerts()));
+
+	if (verts)
+	{
+		Mth::Vector	*p_verts = new Mth::Vector[verts];
+		Image::RGBA	*p_colors = new Image::RGBA[verts];
+		p_geom->GetRenderVerts(p_verts);
+		p_source_geom->GetRenderColors(p_colors);
+	
+		Image::RGBA *p_color = p_colors;
+		Mth::Vector *p_vert = p_verts;
+		for (int i = 0; i < verts; i++)
+		{
+			CalculateVertexLighting(*p_vert, *p_color);
+
+			p_color++;
+			p_vert++;
+		} // end for
+
+		// Set the colors on the actual new geom, not on the source...		
+		p_geom->SetRenderColors(p_colors);
+		
+		delete [] p_verts;
+		delete [] p_colors;
+	} // end if
+	else
+	{
+		// debuggery
+		//p_geom->SetColor(Image::RGBA(0,0,100,0));
+	}
+
+
+	//
+	// Do collision geometry
+	Nx::CCollObjTriData *p_coll_tri_data = p_geom->GetCollTriData();
+	Nx::CCollObjTriData *p_source_coll_tri_data = p_source_geom->GetCollTriData();
+
+	Dbg_MsgAssert(p_coll_tri_data, ("sector does not have collision"));
+	Dbg_MsgAssert(p_source_coll_tri_data, ("source sector does not have collision"));
+
+#ifdef __PLAT_NGC__
+	int faces = p_coll_tri_data->GetNumFaces();
+	Dbg_MsgAssert(faces == p_source_coll_tri_data->GetNumFaces(), ("source and clone have differing no of collision faces (%d, %d)\n",verts,p_source_coll_tri_data->GetNumFaces()));
+
+	Image::RGBA color;
+	unsigned char intensity;
+	unsigned int avg_color;
+	for (int f = 0; f < faces; f++)
+	{
+		for ( int c = 0; c < 3; c++ )
+		{
+			intensity = p_source_coll_tri_data->GetVertexIntensity(f, c);
+
+			color.r = intensity;
+			color.g = intensity;
+			color.b = intensity;
+			color.a = 255;
+
+			CalculateVertexLighting(p_coll_tri_data->GetRawVertexPos(p_coll_tri_data->GetFaceVertIndex(f, c)), color);
+			avg_color = (color.r + color.g + color.b) / 3;
+			p_coll_tri_data->SetVertexIntensity(f, c, avg_color);
+		}
+	}
+#else
+	verts = p_coll_tri_data->GetNumVerts();
+	Dbg_MsgAssert(verts == p_source_coll_tri_data->GetNumVerts(), ("source and clone have differing no of collision verts (%d, %d)\n",verts,p_source_coll_tri_data->GetNumVerts()));
+
+	Image::RGBA color;
+	unsigned char intensity;
+	unsigned int avg_color;
+	for (int i = 0; i < verts; i++)
+	{
+		intensity = p_source_coll_tri_data->GetVertexIntensity(i);
+
+		color.r = intensity;
+		color.g = intensity;
+		color.b = intensity;
+		color.a = 255;
+
+		CalculateVertexLighting(p_coll_tri_data->GetRawVertexPos(i), color);
+
+		avg_color = (color.r + color.g + color.b) / 3;
+		p_coll_tri_data->SetVertexIntensity(i, avg_color);
+
+	}
+#endif // __PLAT_NGC__
+}
+
+
+// apply lights to a vert											   
+void CParkGenerator::CalculateVertexLighting(const Mth::Vector & vert, Image::RGBA & color)
+{
+	LightIntensity i_amb;
+
+	i_amb.r = (float) color.r 	* m_ambientLightIntensity.r;
+	i_amb.g = (float) color.g 	* m_ambientLightIntensity.g;
+	i_amb.b = (float) color.b 	* m_ambientLightIntensity.b;
+
+	float i_spot_light = 0.0f;
+	Mth::Vector *pLightTabEntry = m_lightTab;
+	for (int l = 0; l < m_numLights; l++)
+	{
+		float dist_x = vert.GetX() - pLightTabEntry->GetX();
+		//float dist_y = vert.GetY() - pLightTabEntry->GetY();
+		float dist_y = 400;	// Mick:  Patched to ignore height, otherwise we have burningly bright spots on high walls
+							// you can decrease this number make the lighhing more intense
+							// (note, this means no verticle variation in brightness in the park editor
+		float dist_z = vert.GetZ() - pLightTabEntry->GetZ();
+
+		float dist_squared = dist_x * dist_x + dist_y * dist_y + dist_z * dist_z;
+		i_spot_light += 1.0f / dist_squared;
+
+		pLightTabEntry++;
+	}
+	i_spot_light *= 255.0f;
+
+	i_amb.r += i_spot_light * m_falloffLightIntensity.r;
+	if (i_amb.r > 255.0)
+		color.r = 255;
+	else
+		color.r = (uint8) (i_amb.r);
+
+	i_amb.g += i_spot_light * m_falloffLightIntensity.g;
+	if (i_amb.g > 255.0)
+		color.g = 255;
+	else 
+		color.g = (uint8) (i_amb.g);
+
+	i_amb.b += i_spot_light * m_falloffLightIntensity.b;
+	if (i_amb.b > 255.0)
+		color.b = 255;
+	else
+		color.b	= (uint8) (i_amb.b);
+}
+
+
+/*
+	See comments in DestroyAllClonedPieces() for 'destroyType'
+*/
+void CParkGenerator::destroy_piece_impl(CClonedPiece *pPiece, EDestroyType destroyType)
+{
+	// XXX
+	if (pPiece->m_flags & CPiece::mGAP)
+		Ryan("@@@ destroying gap piece 0x%x\n", pPiece);
+
+	if (!(pPiece->m_flags & CClonedPiece::mSOFT_PIECE))
+	{
+		// remove from list	first
+		CPiece *p_entry = mp_cloned_piece_list;
+		CPiece *p_prev = NULL;
+		while(p_entry)
+		{
+			if (p_entry == pPiece)
+			{
+				if (p_prev)
+					p_prev->mp_next_in_list = p_entry->mp_next_in_list;
+				else
+					mp_cloned_piece_list = p_entry->mp_next_in_list;
+				break;
+			}
+			
+			p_prev = p_entry;
+			p_entry = p_entry->mp_next_in_list;
+		}
+		Dbg_MsgAssert(p_entry, ("piece not found"));
+		
+		if (!(pPiece->m_flags & CPiece::mNO_RAILS))
+		{
+			m_total_rail_points -= pPiece->mp_source_piece->m_num_rail_points;
+			m_total_rail_linked_points -= pPiece->mp_source_piece->m_num_linked_rail_points;
+		}
+	}
+
+	// now actually destroy piece
+	if (destroyType == DESTROY_PIECES_AND_SECTORS)
+	{
+		#if 0
+		printf("    Gott mit uns (%f,%f,%f)\n", 
+			   pPiece->GetPos().GetX(), pPiece->GetPos().GetY(), pPiece->GetPos().GetZ());
+		#endif
+		// destroy underlying sector
+		mp_cloned_scene->DeleteSector(pPiece->m_sector_checksum);
+		
+		#ifdef __PLAT_NGC__
+		//printf("Called DeleteSector\n");
+		Nx::CEngine::sFinishRendering();
+		#endif
+	}
+	
+	m_num_cloned_pieces--;
+	
+	delete pPiece;
+
+	if (!mp_cloned_piece_list)
+		clear_flag(mPIECES_IN_WORLD);
+	set_flag(mSECTOR_MEMORY_NEEDS_FREE);
+}
+
+
+
+// Read in the node array containing the rail information
+// and parse this into the park editor format....
+
+// called from CParkManager::Initialize()
+void CParkGenerator::ReadInRailInfo()
+{
+	const uint32 crc_cluster 		= 0x1a3a966b;
+    const uint32 crc_class 			= 0x12b4e660;
+	const uint32 crc_links 			= 0x2e7d5ee7;
+
+	printf ("Starting ReadInRailInfo()\n");
+
+// Mick - Not sure where this should be set up
+	m_in_rail_set.SetupAllocators(GetResourceSize("in_railpoint_pool"), GetResourceSize("in_railstring_pool"), true);
+
+	
+	// Ken: This function has gone now, since the new ability to unload a qb has made it redundant.
+	//Script::RemoveOldTriggerScripts();
+	
+	// Remove any spawned scripts that might have been left over from the previous level.
+	Script::DeleteSpawnedScripts();	
+		
+	// Load the QB
+//	SkateScript::LoadQB(PIECE_SET_QB[m_currentThemeNumber]);
+//	SkateScript::LoadQB("levels\\sk4ed\\sk4ed.qb");			  // assume it has already been loaded (NOTE: If not, then you will have problems with PRE files, as the .QBs do not go in the pre file)
+	// Ensure it has a node array in it
+	Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+	Dbg_MsgAssert(pNodeArray,("Park editor node array not found"));
+		
+	// this will load up sounds
+	Script::RunScript("LoadTerrain");
+	
+	Ryan("\n*** Reading in rail info ***\n");
+	m_in_rail_set.Destroy();
+	// BAD MEM
+	m_processed_node_tab = new int[256];
+	m_processed_node_tab_entries = 0;
+	m_temp_node_tab = new TempNodeInfo[2048];
+
+	
+	int index = 0;
+	for ( ; index < (int)pNodeArray->GetSize(); index++)
+	{
+		Script::CScriptStructure *pStruct = pNodeArray->GetStructure(index);
+	
+		m_temp_node_tab[index].classCrc = 0;
+		pStruct->GetChecksum(crc_class, &m_temp_node_tab[index].classCrc);
+		m_temp_node_tab[index].cluster = 0;
+		pStruct->GetChecksum(crc_cluster, &m_temp_node_tab[index].cluster);
+		m_temp_node_tab[index].pLinkArray = NULL;
+		pStruct->GetArray(crc_links, &m_temp_node_tab[index].pLinkArray);
+	}
+
+	
+	const bool debug_rail_read = true;
+	
+	// XXX
+	Ryan("number of node entries is %d\n", pNodeArray->GetSize());
+	
+	index = 0;
+	// loop through all LevelGeometry objects in node array.
+	// for each one, locate associated rail nodes (if any) and create RailString.
+	while(1)
+	{
+		// get cluster checksum of LevelGeometry object, will be used to
+		// look up rail nodes
+		uint32 cluster_crc = scan_for_cluster(pNodeArray, index);
+		if (index >= (int)pNodeArray->GetSize())
+		{
+			printf ("No more clusters, exiting\n"); 
+			break;
+		}
+		
+//		printf("source piece %s for cluster \n", Script::FindChecksumName(cluster_crc));		
+		
+		//debug_rail_read = (cluster_crc == 0x21997899);
+#ifdef __NOPT_ASSERT__
+		CPiece *pMasterPiece = GetMasterPiece(cluster_crc);
+		
+		CSourcePiece *pSourcePiece = pMasterPiece->CastToCSourcePiece();
+		
+		Dbg_MsgAssert(pSourcePiece, ("no source piece %s found", Script::FindChecksumName(cluster_crc)));		
+#endif		// __NOPT_ASSERT__
+		if (debug_rail_read)
+			Ryan("found cluster %s at index %d\n", Script::FindChecksumName(cluster_crc), index-1);
+
+		Mth::Vector v;
+		RailPoint::RailType type;
+			   
+		// find all rail strings associated with current LevelGeometry object
+		// m_processed_node_tab[] keeps track of strings already processed
+		m_processed_node_tab_entries = 0;
+		while (1)
+		{
+			bool string_is_loop = false;
+			
+			// try to locate a rail node that matches the cluster and has no links
+			// if it fails to find one, it will try to return one that matches the cluster and does have links
+			int last_node_in_set = scan_for_rail_node(pNodeArray, cluster_crc, -1, &v, &string_is_loop, &type);
+			if (last_node_in_set == -1)
+				break;
+
+			int current_node = last_node_in_set;
+			
+			RailString *pCurrentString = m_in_rail_set.CreateRailString();
+			pCurrentString->m_id = cluster_crc;
+			pCurrentString->m_isLoop = string_is_loop;
+
+			RailPoint *pPoint = m_in_rail_set.CreateRailPoint();
+			//pPoint->m_pos.Set(v.GetX() - pSourcePiece->m_pos.GetX(), v.GetY() - pSourcePiece->m_pos.GetY(), v.GetZ() + pSourcePiece->m_pos.GetZ());
+			pPoint->m_pos.Set(v.GetX(), v.GetY(), v.GetZ());
+			pPoint->m_type = type;
+			if (debug_rail_read)
+				Ryan("* [%.1f,%.1f,%.1f]", pPoint->m_pos.GetX(), pPoint->m_pos.GetY(), pPoint->m_pos.GetZ());
+			pCurrentString->AddPoint(pPoint);
+			
+			// go through all points in the string (after the first)
+			while (1)
+			{
+				current_node = scan_for_rail_node(pNodeArray, cluster_crc, current_node, &v, &string_is_loop, &type);
+				if (current_node == -1)
+				{
+					if (debug_rail_read && pCurrentString->m_isLoop)
+						Ryan(" (loop)");
+					break;
+				}
+
+				
+				pPoint = m_in_rail_set.CreateRailPoint();
+				//pPoint->m_pos.Set(v.GetX() - pSourcePiece->m_pos.GetX(), v.GetY() - pSourcePiece->m_pos.GetY(), v.GetZ() + pSourcePiece->m_pos.GetZ());
+				pPoint->m_pos.Set(v.GetX(), v.GetY(), v.GetZ());
+				pPoint->m_type = type;
+				if (debug_rail_read)
+					Ryan("-[%.1f,%.1f,%.1f]", pPoint->m_pos.GetX(), pPoint->m_pos.GetY(), pPoint->m_pos.GetZ());
+				pCurrentString->AddPoint(pPoint);
+			}
+
+			if (debug_rail_read)
+				Ryan("\n");
+			m_in_rail_set.AddString(pCurrentString);
+		} // end while
+	} // end while
+
+	delete m_processed_node_tab;
+	delete m_temp_node_tab;
+
+	scan_in_trigger_info(pNodeArray);
+//
+//	m_nodeArrayIsSetup = true;
+//	DestroyCollisionDataAndNodeArray();
+
+	// now that rail info is set up, it's safe to do this next block of code
+	CPiece *p_piece = mp_source_piece_list;
+	while(p_piece)
+	{
+		CSourcePiece *p_source_piece = p_piece->CastToCSourcePiece();
+
+		p_source_piece->m_num_rail_points = 0;
+		p_source_piece->m_num_linked_rail_points = 0;
+		int string_num = 0;
+		while(1)
+		{
+			RailString *p_rail_string = m_in_rail_set.GetString(p_source_piece->GetType(), string_num++);
+			if (!p_rail_string)
+				break;
+			p_source_piece->m_num_rail_points += p_rail_string->Count();
+			p_source_piece->m_num_linked_rail_points += p_rail_string->CountLinkedPoints();
+		}
+
+		p_piece = p_piece->mp_next_in_list;
+	}
+}
+
+
+
+
+// called from CParkManager::Destroy()
+void CParkGenerator::DestroyRailInfo()
+{
+	m_in_rail_set.Destroy();
+	m_in_rail_set.FreeAllocators();
+}
+
+
+
+
+
+
+
+// returns checksum of first found cluster, increases index appropriately
+// called from ReadInRailInfo()
+uint32 CParkGenerator::scan_for_cluster(Script::CArray *pNodeArray, int &index)
+{
+    const uint32 crc_class 			= 0x12b4e660;
+    const uint32 crc_levelgeometry = 0xbf4d3536;		//0xdabd3086;
+	const uint32 crc_name 			= 0xa1dc81f9;
+
+	int total_entries = pNodeArray->GetSize();
+
+	/*
+		Given a node like...
+		
+		{
+			// Node 471
+			Position = (-0.002991,1200.000000,0.000854)
+			Angles = (0.000000,0.000000,0.000000)
+			Name = Sk3Ed_Shell_Col_16x16_01
+			Class = levelgeometry
+			CreatedAtStart
+		}
+		
+		... make sure it's level geometry, then find the value
+		of 'Name'
+	*/
+	while (index < total_entries)
+	{
+		Script::CStruct *pStruct = pNodeArray->GetStructure(index);
+		
+		uint32 node_class = 0;
+		pStruct->GetChecksum(crc_class, &node_class);
+		
+		if (node_class == crc_levelgeometry)
+		{
+			uint32 name;
+			if (pStruct->GetChecksum(crc_name, &name))
+			{
+				index++;
+				return name;
+			}			
+		}
+
+		index++;
+	}
+
+	return 0;
+}
+
+
+
+
+/* 
+	returns number of node
+	3 ways of calling:
+		-link set: find rail node with that link
+		-cluster set, link -1: find rail node with that cluster, but no link
+		-cluster set, link -2: find rail node with that cluster, can have a link
+	
+	Called from ReadInRailInfo()
+*/
+int CParkGenerator::scan_for_rail_node(Script::CArray *pNodeArray, uint32 cluster, int link, Mth::Vector *pVector, bool *pHasLinks, RailPoint::RailType *pType)
+{
+	
+	
+//	const uint32 crc_position 		= 0xb9d31b0a;
+	//const uint32 crc_links 			= 0x2e7d5ee7;
+	//const uint32 crc_cluster 		= 0x1a3a966b;
+	const uint32 crc_terraintype	= 0x54cf8532;
+//	const uint32 crc_terrain_wood	= 0x39075ea5;
+	
+    //const uint32 crc_class 			= 0x12b4e660;
+    const uint32 crc_railnode 		= 0x8e6b02ad;
+
+	int found_node_with_link_index = -1;
+	int found_node_without_link_index = -1;
+	
+	/*
+		A little optimization:
+	
+		We first call this function with link<0, so at that time
+		store the index of the first node belonging to the requested
+		cluster.
+		
+		Several following calls to this function will start the node
+		search from this point
+	*/
+	
+	static int first_node_in_cluster;
+	if (link < 0)
+		first_node_in_cluster = 0;
+
+	int total_entries = pNodeArray->GetSize();
+	for (int n = first_node_in_cluster; n < total_entries; n++)
+	{
+		//Script::CScriptStructure *pStruct = pNodeArray->GetStructure(n);
+	
+		uint32 node_class = m_temp_node_tab[n].classCrc;
+		if (node_class == crc_railnode)
+		{
+			// find link
+			int found_link = -1;
+			Script::CArray *pLinkArray = m_temp_node_tab[n].pLinkArray;
+			if (pLinkArray)
+			{
+				Dbg_MsgAssert(pLinkArray->GetSize() == 1, ("Rail node %d has more than one link (%d)",n,pLinkArray->GetSize()));
+				found_link = pLinkArray->GetInt(0);
+			}
+			
+			uint32 found_cluster = m_temp_node_tab[n].cluster;
+			//Ryan("   cluster is %s, link is %d\n", Script::FindChecksumName(found_cluster), found_link);
+
+			if (link >= 0)
+			{
+				if (found_link == link)
+				{
+					found_node_with_link_index = n;
+					for (int i = 0; i < m_processed_node_tab_entries; i++)
+					{
+						if (m_processed_node_tab[i] == found_node_with_link_index)
+							// we've already processed this node
+							found_node_with_link_index = -1;
+					}
+					if (found_node_with_link_index != -1)
+						// in this case, we have all the info we need, so exit loop
+						break;
+				}
+			}
+			else if (cluster == found_cluster)
+			{
+				if (!first_node_in_cluster)
+					first_node_in_cluster = n;
+				
+				if (found_link == -1)
+				{
+					found_node_without_link_index = n;
+					for (int i = 0; i < m_processed_node_tab_entries; i++)
+					{
+						if (m_processed_node_tab[i] == found_node_without_link_index)
+							// we've already processed this node
+							found_node_without_link_index = -1;
+					}
+					if (found_node_without_link_index != -1)
+						// this is the node we're looking for, so exit loop
+						break;
+				}
+				else if (found_node_with_link_index == -1) // && found_link >= 0
+				{
+					found_node_with_link_index = n;
+					for (int i = 0; i < m_processed_node_tab_entries; i++)
+					{
+						if (m_processed_node_tab[i] == found_node_with_link_index)
+							// we've already processed this node
+							found_node_with_link_index = -1;
+					}
+					// even if not already processed, this node is secondary priority, 
+					// so we will not be exiting the loop
+				}
+			} // end else if
+		} // if (node_class == crc_railnode)
+	}
+		
+	// the node without a link has greater priority
+	int match_index = -1;
+	if (found_node_with_link_index != -1)
+	{
+		match_index = found_node_with_link_index;
+		*pHasLinks = true;
+	}
+	if (found_node_without_link_index != -1)
+	{
+		match_index = found_node_without_link_index;	
+		*pHasLinks = false;
+	}
+	if (match_index == -1) return -1;
+
+	// store this node so we won't use it twice
+	m_processed_node_tab[m_processed_node_tab_entries++] = match_index;
+	// fetch position info (will be returned to caller)
+	Script::CScriptStructure *pFound = pNodeArray->GetStructure(match_index);
+	
+	pFound->GetVector(CRCD(0x7f261953,"Pos"), pVector, true);
+
+// GJ:  The following was incorrect, because the Z component should have been negated,
+// however, the function that was supposed to use this data should have negated it again,
+// but didn't, meaning that the 2 bugs ended up cancelling each other out.
+// The new-style 'pos' vector is already correct and needs no negation.
+//	pFound->GetVector(CRCD(0xb9d31b0a,"Position"), pVector, false);
+	
+	uint32 terrain_type; // terrain_wood
+	pFound->GetChecksum(crc_terraintype, &terrain_type, true);
+	
+//	if (terrain_type == crc_terrain_wood)
+//		*pType = RailPoint::vWOOD;
+//	else
+//		*pType = RailPoint::vMETAL;
+
+	*pType =  terrain_type; 
+
+	return match_index;
+}
+
+
+
+
+// 	Should be called from ReadInRailInfo(), but isn't
+void CParkGenerator::scan_in_trigger_info(Script::CArray *pNodeArray)
+{
+	//const uint32 crc_position 		= 0xb9d31b0a;
+    const uint32 crc_class 			= 0x12b4e660;
+    const uint32 crc_levelgeometry = 0xbf4d3536;		//0xdabd3086;
+	const uint32 crc_name 			= 0xa1dc81f9;
+    const uint32 crc_triggerscript 	= 0x2ca8a299;
+
+	for (uint32 n = 0; n < pNodeArray->GetSize(); n++)
+	{
+		Script::CScriptStructure *pStruct = pNodeArray->GetStructure(n);
+		
+		uint32 node_class = 0;
+		pStruct->GetChecksum(crc_class, &node_class);
+		
+		if (node_class == crc_levelgeometry)
+		{
+			uint32 piece_name = 0;
+			pStruct->GetChecksum(crc_name, &piece_name);
+			uint32 trigger_name = 0;
+			pStruct->GetChecksum(crc_triggerscript, &trigger_name);
+			
+			if (piece_name && trigger_name)
+			{
+				// XXX
+				Ryan("Setting up trigger script: piece=%s script=%s\n", Script::FindChecksumName(piece_name), Script::FindChecksumName(trigger_name));
+				CPiece *pPiece = GetMasterPiece(piece_name, true);
+				CSourcePiece *pSourcePiece = pPiece->CastToCSourcePiece();
+				pSourcePiece->m_triggerScriptId = trigger_name;
+			}
+		}
+	}
+	Ryan("done with all rail reading\n");
+}
+
+
+
+
+void CParkGenerator::set_up_object_nodes(Script::CArray *pNodeArray, int *pNodeNum)
+{
+	
+
+	Ryan("set_up_object_nodes: %d/%d\n", Mem::CPoolable::SGetNumUsedItems(), Mem::CPoolable::SGetTotalItems());
+	Ryan("CVector use: %d/%d\n", Mem::CPoolable::SGetNumUsedItems(), Mem::CPoolable::SGetTotalItems());
+
+#if 1
+//	uint32 crc_position 			= Script::GenerateCRC("position");
+	uint32 crc_angles 				= Script::GenerateCRC("angles");
+	uint32 crc_name 				= Script::GenerateCRC("name");
+	uint32 crc_class 				= Script::GenerateCRC("class");
+	uint32 crc_levelgeometry 		= Script::GenerateCRC("LevelGeometry");
+	uint32 crc_createdatstart 		= Script::GenerateCRC("CreatedAtStart");
+	uint32 crc_triggerscript 		= Script::GenerateCRC("TriggerScript");
+	uint32 crc_trickobject 			= Script::GenerateCRC("TrickObject");
+	uint32 crc_cluster 				= Script::GenerateCRC("Cluster");
+	
+	// 7
+	CPiece *p_piece = mp_cloned_piece_list;
+	while (p_piece)
+	{
+		CClonedPiece *p_cloned = p_piece->CastToCClonedPiece();
+		if (!(p_cloned->GetFlags() & CPiece::mFLOOR) && 
+			!(p_cloned->GetFlags() & CPiece::mRESTART))
+		{
+			
+			Script::CScriptStructure *pNode = pNodeArray->GetStructure(*pNodeNum);
+			Mth::Vector pos = p_cloned->GetPos();
+			pNode->AddComponent(CRCD(0x7f261953,"pos"), pos.GetX(), pos.GetY(), pos.GetZ());
+// GJ:  The following was incorrect, and the Z component should have been negated,
+// but no one ever really used it, so we never noticed that it was a problem...
+// The new-style 'pos' vector is already correct and needs no negation.
+//			pNode->AddComponent(CRCD(0xb9d31b0a,"position"), pos.GetX(), pos.GetY(), pos.GetZ());
+			pNode->AddComponent(crc_angles, 0.0f, 0.0f, 0.0f);
+			// hopefully safe
+			// XXX
+			Ryan("id is 0x%x\n", p_cloned->m_id);
+			pNode->AddComponent(crc_name, ESYMBOLTYPE_NAME, (int) p_cloned->m_sector_checksum);
+			pNode->AddComponent(crc_class, ESYMBOLTYPE_NAME, (int) crc_levelgeometry);
+			pNode->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int) crc_createdatstart);
+			
+																				  
+			pNode->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int) crc_trickobject);
+// Mick:  use the cloned pieces ID, but EOR with with 0xffffffff so it's different to the original name
+			pNode->AddComponent(crc_cluster, ESYMBOLTYPE_NAME, (int) p_cloned->GetID() ^ 0xffffffff);
+
+			
+			// trigger script
+			if (p_cloned->GetFlags() & CPiece::mGAP)
+			{
+				Dbg_Assert(CGapManager::sInstance());
+				pNode->AddComponent(crc_triggerscript, ESYMBOLTYPE_NAME, (int) CGapManager::sInstance()->GetGapTriggerScript(p_cloned));
+			}
+			else if (p_cloned->GetSourcePiece()->m_triggerScriptId)
+				pNode->AddComponent(crc_triggerscript, ESYMBOLTYPE_NAME, (int) p_cloned->GetSourcePiece()->m_triggerScriptId);
+			
+			(*pNodeNum)++;
+		}
+		p_piece = p_piece->mp_next_in_list;
+	}
+#endif
+}
+
+// Used by the rail editor for snapping to the nearest rail point.
+bool CParkGenerator::FindNearestRailPoint(Mth::Vector &pos, Mth::Vector *p_nearest_pos, float *p_dist_squared)
+{
+	float min_dist_squared=100000000.0f;
+	bool found_point=false;
+	RailString *p_string = m_out_rail_set.GetList();
+	
+	while (p_string)
+	{
+		RailPoint *p_point = p_string->GetList();
+		
+		while (p_point)
+		{
+			Mth::Vector diff=p_point->m_pos-pos;
+			float dd=diff.LengthSqr();
+			
+			if (dd < min_dist_squared)
+			{
+				min_dist_squared=dd;
+				
+				*p_nearest_pos=p_point->m_pos;
+				*p_dist_squared=min_dist_squared;
+				found_point=true;
+			}
+			
+			p_point = p_point->GetNext();
+		}
+			
+		p_string = p_string->GetNext();
+	}
+
+	return found_point;
+}
+
+// called from GenerateNodeInfo()
+void CParkGenerator::set_up_rail_nodes(Script::CArray *pNodeArray, int *pNodeNum)
+{
+
+	Ryan("set_up_rail_nodes: %d/%d\n", Mem::CPoolable::SGetNumUsedItems(), Mem::CPoolable::SGetTotalItems());
+
+#if 1	
+	
+	const bool debug_rail_out = false;
+	
+	uint32 crc_angles 			= Script::GenerateCRC("angles");
+	uint32 crc_name 			= Script::GenerateCRC("name");
+	uint32 crc_class 			= Script::GenerateCRC("class");
+	uint32 crc_railnode 		= Script::GenerateCRC("railnode");
+	uint32 crc_createdatstart 	= Script::GenerateCRC("CreatedAtStart");
+	uint32 crc_links 			= Script::GenerateCRC("links");
+	
+//	uint32 crc_type				= Script::GenerateCRC("type");
+//	uint32 crc_metal			= Script::GenerateCRC("metal");
+//	uint32 crc_wood				= Script::GenerateCRC("wood");
+	uint32 crc_terraintype		= Script::GenerateCRC("terraintype");
+//	uint32 crc_terrain_wood		= Script::GenerateCRC("terrain_wood");
+//	uint32 crc_terrain_metal	= Script::GenerateCRC("terrain_metal");
+	
+	uint32 crc_trickobject 		= Script::GenerateCRC("TrickObject");
+	uint32 crc_cluster 			= Script::GenerateCRC("Cluster");
+	
+	/*
+	Position = (-1619.978027,-336.000031,-6870.965332)
+	Angles = (0.000000,-3.141589,0.000000)
+	Name = TRG_Conc_Park_Rail0
+	Class = RailNode
+	Type = Concrete
+	CreatedAtStart
+	TerrainType = TERRAIN_CONCSMOOTH
+	TrickObject Cluster = ParkingLotSpine1
+	TriggerScript = CanTRG_Conc_Park_Rail0Script
+	Links=[1]
+	*/
+	
+	int num_points = m_out_rail_set.CountPoints();
+	if (!num_points) return;
+
+	RailString *pString = m_out_rail_set.GetList();
+	RailPoint *pPoint = pString->GetList();
+	
+	if (debug_rail_out)
+		Ryan("String %s: ", Script::FindChecksumName(pString->m_id));	
+	int first_node_in_loop = *pNodeNum;
+	
+	// 10
+	uint32 rail_point_id = vFIRST_ID_FOR_RAILS;
+	int stop_node_num = *pNodeNum + num_points;
+	for (; *pNodeNum < stop_node_num; (*pNodeNum)++)
+	{
+		Dbg_Assert(pPoint);
+		
+		Script::CStruct *pNode = pNodeArray->GetStructure(*pNodeNum);
+		pNode->AddComponent(CRCD(0x7f261953,"pos"), pPoint->m_pos.GetX(), pPoint->m_pos.GetY(), pPoint->m_pos.GetZ());
+//		pNode->AddComponent(CRCD(0xb9d31b0a,"Position"), pPoint->m_pos.GetX(), pPoint->m_pos.GetY(), pPoint->m_pos.GetZ());
+		if (debug_rail_out)
+			Ryan("[%.1f,%.1f,%.1f]", pPoint->m_pos.GetX(), pPoint->m_pos.GetY(), pPoint->m_pos.GetZ());
+		pNode->AddComponent(crc_angles, 0.0f, 0.0f, 0.0f);
+		Dbg_MsgAssert(rail_point_id < vMAX_ID_FOR_RAILS, ("out of rail ids"));
+		pNode->AddComponent(crc_name, ESYMBOLTYPE_NAME, (int) rail_point_id++);
+		pNode->AddComponent(crc_class, ESYMBOLTYPE_NAME, (int) crc_railnode);
+		pNode->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int) crc_createdatstart);
+
+/*	
+		uint32 type, terrain_type;
+
+		
+		if (pPoint->m_type == RailPoint::vWOOD)
+		{
+			type = crc_wood;
+			terrain_type = crc_terrain_wood;
+		}
+		else
+		{
+			type = crc_metal;
+			terrain_type = crc_terrain_metal;
+		}
+*/		
+
+// Don't need type?		
+//		pNode->AddComponent(crc_type, ESYMBOLTYPE_NAME, (int) type);
+		
+		pNode->AddComponent(crc_terraintype, ESYMBOLTYPE_NAME, (int) pPoint->m_type);
+		
+		// Mick: m_objectId will be zero if this rail is not part of a trickob
+		if (pPoint->m_objectId)
+		{
+			pNode->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int) crc_trickobject);
+			pNode->AddComponent(crc_cluster, ESYMBOLTYPE_NAME, (int) pPoint->m_objectId ^ 0xffffffff);
+		}
+		
+		if (pPoint->GetNext() != NULL || pString->m_isLoop)
+		{
+			int link_num = *pNodeNum + 1;
+			if (pPoint->GetNext() == NULL && pString->m_isLoop)
+				link_num = first_node_in_loop;
+			
+			Script::CArray *pLinks = new Script::CArray();
+			pLinks->SetSizeAndType(1,ESYMBOLTYPE_INTEGER);
+			pLinks->SetInteger(0, link_num);
+			pNode->AddComponent(crc_links, pLinks);
+			delete pLinks;
+		}
+
+		pPoint = pPoint->GetNext();
+		if (pPoint)
+		{
+			if (debug_rail_out)
+				Ryan("-");
+		}
+		else
+		{
+			if (debug_rail_out)
+			{
+				if (pString->m_isLoop) Ryan(" (loop)");
+				Ryan("\n");
+			}
+			pString = pString->GetNext();
+			if (pString)
+			{
+				if (debug_rail_out)
+					Ryan("String %s: ", Script::FindChecksumName(pString->m_id));
+				pPoint = pString->GetList();
+				first_node_in_loop = *pNodeNum + 1;
+			}
+		}
+	}
+	
+#endif	
+}
+
+
+
+
+// called from GenerateNodeInfo()
+void CParkGenerator::set_up_restart_nodes(Script::CArray *pNodeArray, int *pNodeNum)
+{
+	uint32 crc_angles 			= Script::GenerateCRC("angles");
+	uint32 crc_name 			= Script::GenerateCRC("name");
+	uint32 crc_class 			= Script::GenerateCRC("class");
+	uint32 crc_type 			= Script::GenerateCRC("type");
+	uint32 crc_restart 			= Script::GenerateCRC("restart");
+	uint32 crc_genericnode		= Script::GenerateCRC("GenericNode");
+	uint32 crc_createdatstart 	= Script::GenerateCRC("CreatedAtStart");
+	uint32 crc_restartname 		= Script::GenerateCRC("RestartName");
+	uint32 crc_restart_types 	= Script::GenerateCRC("restart_types");
+	uint32 crc_model		 	= Script::GenerateCRC("model");
+	uint32 crc_triggerscript 	= Script::GenerateCRC("TriggerScript");
+	
+	uint32 restart_id = vFIRST_ID_FOR_RESTARTS;
+	
+	for (int i = 0; i < vNUM_RESTARTS; i++)
+	{
+		if (m_restart_tab[i].type != vEMPTY)
+		{
+			//Ryan("yo, restart at (%.2f,%.2f,%.2f)\n", m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), m_restart_tab[i].pos.GetZ());
+			
+			// if restart is ONE_PLAYER or HORSE type, it will also be repeated 
+			// as a multiplayer
+			int repeat_num = 1;
+			if (m_restart_tab[i].type == vHORSE)
+				repeat_num = 2;
+			if (m_restart_tab[i].type == vONE_PLAYER)
+				repeat_num = 2;				
+
+			for (int n = 0; n < repeat_num; n++)
+			{
+				/*
+				{
+					// Node 1965
+					Position = (-476.991364,-70.263161,-5703.516602)
+					Angles = (0.000000,0.000000,0.000000)
+					Name = TRG_Crown01
+					Class = GenericNode
+					Type = Crown
+					CreatedAtStart
+				}
+				{
+					// Node 2912
+					Position = (2940.254639,-1368.771729,-753.127625)
+					Angles = (0.000000,2.687807,0.000000)
+					Name = TRG_CTF_Restart_Red
+					Class = Restart
+					Type = CTF
+					CreatedAtStart
+					CollisionMode = BoundingBox
+					RestartName = "Team: CTF"
+					restart_types = [ CTF ]
+				}
+				
+				*/
+				
+				
+				int restart_name = restart_id++;		// use generic name for most restarts
+				uint32 type_checksum = 0;
+				if (m_restart_tab[i].type == vONE_PLAYER)
+				{
+					type_checksum = Script::GenerateCRC("Player1");
+				}
+				if (m_restart_tab[i].type == vHORSE)
+				{
+					type_checksum = Script::GenerateCRC("Horse");
+				}
+				if (m_restart_tab[i].type == vMULTIPLAYER || n == 1)
+				{	
+					type_checksum = Script::GenerateCRC("Multiplayer");
+				}
+				if (m_restart_tab[i].type == vKING_OF_HILL)
+				{
+					type_checksum = Script::GenerateCRC("Crown");
+				}
+				if (
+					m_restart_tab[i].type == vRED_FLAG
+					|| m_restart_tab[i].type == vGREEN_FLAG
+					|| m_restart_tab[i].type == vBLUE_FLAG
+					|| m_restart_tab[i].type == vYELLOW_FLAG
+					)
+				{
+					type_checksum = Script::GenerateCRC("CTF");
+					switch (m_restart_tab[i].type)
+					{
+						case vRED_FLAG:
+							restart_name = Script::GenerateCRC("TRG_CTF_Restart_Red");
+							break;
+						case vGREEN_FLAG:
+							restart_name = Script::GenerateCRC("TRG_CTF_Restart_Green");
+							break;
+						case vBLUE_FLAG:
+							restart_name = Script::GenerateCRC("TRG_CTF_Restart_Blue");
+							break;
+						case vYELLOW_FLAG:
+							restart_name = Script::GenerateCRC("TRG_CTF_Restart_Yellow");
+							break;
+						default:
+							Dbg_MsgAssert(0,("Impossible!"));
+							break;
+					}
+				}
+				
+				if (type_checksum != 0)
+				{
+	
+					Script::CScriptStructure *pNode = pNodeArray->GetStructure(*pNodeNum);
+					if (m_restart_tab[i].type == vKING_OF_HILL)
+					{
+						pNode->AddComponent(CRCD(0x7f261953,"pos"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY() + 50.0f, m_restart_tab[i].pos.GetZ());
+//						pNode->AddComponent(CRCD(0xb9d31b0a,"Position"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY() + 50.0f, -m_restart_tab[i].pos.GetZ());
+						pNode->AddComponent(crc_class, ESYMBOLTYPE_NAME, (int) crc_genericnode);
+					}
+					else
+					{
+						pNode->AddComponent(CRCD(0x7f261953,"pos"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY() + 50.0f, m_restart_tab[i].pos.GetZ());
+//						pNode->AddComponent(CRCD(0xb9d31b0a,"Position"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), -m_restart_tab[i].pos.GetZ());
+						pNode->AddComponent(crc_class, ESYMBOLTYPE_NAME, (int) crc_restart);
+					}
+					pNode->AddComponent(crc_angles, 0.0f, Mth::PI * 0.5f * ((float) m_restart_tab[i].dir + 2), 0.0f);
+					pNode->AddComponent(crc_name, ESYMBOLTYPE_NAME, (int) restart_name);
+					pNode->AddComponent(crc_type, ESYMBOLTYPE_NAME, (int) type_checksum);
+					pNode->AddComponent(NONAME, ESYMBOLTYPE_NAME, (int) crc_createdatstart);
+					if (m_restart_tab[i].type != vKING_OF_HILL)
+					{
+						char *p_name = "Multi Restart";
+						if (n == 0)
+						{
+							switch (m_restart_tab[i].type)
+							{
+								case vONE_PLAYER:
+									p_name = "Player 1 Restart";
+									break;
+								case vMULTIPLAYER:
+									p_name = "Player 2 Restart";
+									break;
+								case vHORSE:
+									p_name = "Horse Restart";
+									break;
+								case vRED_FLAG:
+								case vGREEN_FLAG:
+								case vBLUE_FLAG:
+								case vYELLOW_FLAG:
+									p_name = "Team: CTF";
+									break;
+								default:
+									break;
+									 
+							}
+						}						
+						pNode->AddComponent(crc_restartname, ESYMBOLTYPE_STRING, p_name);
+					
+						Script::CArray *pRestartTypes = new Script::CArray();
+						pRestartTypes->SetSizeAndType(1, ESYMBOLTYPE_NAME);
+						pRestartTypes->SetChecksum(0, type_checksum);
+						pNode->AddComponent(crc_restart_types, pRestartTypes);
+						delete pRestartTypes;
+					}
+					//Script::PrintContents(pNode);								   
+					(*pNodeNum)++;
+				}
+				
+				// Flags have the 3 additional nodes for the team flags, bases, CTF flag
+				
+				if (
+					m_restart_tab[i].type == vRED_FLAG
+					|| m_restart_tab[i].type == vGREEN_FLAG
+					|| m_restart_tab[i].type == vBLUE_FLAG
+					|| m_restart_tab[i].type == vYELLOW_FLAG
+					)
+				{
+					// Not a normal restart point, so must be a flag
+					// so need to generate the flag, the CTF base and the CTF restart all in the same place
+
+				/*					
+				// team flag is like:
+				{
+					// Node 2678
+					Position = (864.618835,-408.157806,-3771.989014)
+					Angles = (0.000000,2.268928,0.000000)
+					Name = TRG_Flag_Red
+					Class = GameObject
+					Type = Flag_Red
+					Model = "gameobjects\flags\flag_red\flag_red.mdl"
+					SuspendDistance = 0
+					lod_dist1 = 800
+					lod_dist2 = 801
+					TriggerScript = TRG_Flag_RedScript
+				}
+				
+				then pretty much the same for CTF flags, just a different name
+				
+				{
+					// Node 3360
+					Position = (4060.947998,408.885132,1926.812012)
+					Angles = (0.000000,0.000000,0.000000)
+					Name = TRG_CTF_Blue						<<<<<<<<<<<<<<   Different name
+					Class = GameObject
+					Type = Flag_Blue
+					Model = "gameobjects\flags\flag_blue\flag_blue.mdl"
+					SuspendDistance = 0
+					lod_dist1 = 800
+					lod_dist2 = 801
+					TriggerScript = TRG_CTF_BlueScript		<<<<<<<<<<<<<<  script can actually stay the same
+				}
+				
+				and then the base
+				{
+					// Node 3252
+					Position = (-2448.872314,-373.527405,5749.742188)
+					Angles = (0.000000,-2.792527,0.000000)
+					Name = TRG_CTF_Yellow_Base		 <<<<<<<<<<<<<<<<<<<<<<<<<<<<  different name
+					Class = GameObject
+					Type = Flag_Yellow_Base			   <<<<<<<<<<<<<<<<<<<<<<<<<<<  different type
+					Model = "gameobjects\flags\flag_yellow_base\flag_yellow_base.mdl"   <<<<<< differnt model
+					SuspendDistance = 0
+					lod_dist1 = 800
+					lod_dist2 = 801
+													<<<<<<<<<<<<<<<  no script
+				}
+
+
+				
+				*/	
+				
+	
+					Script::CScriptStructure *pNode = pNodeArray->GetStructure(*pNodeNum);
+
+					const	char *color = NULL;					
+					if (m_restart_tab[i].type == vRED_FLAG) color = "red";
+					if (m_restart_tab[i].type == vGREEN_FLAG) color = "green";
+					if (m_restart_tab[i].type == vBLUE_FLAG) color = "blue";
+					if (m_restart_tab[i].type == vYELLOW_FLAG) color = "yellow";
+
+					char name_string[256];
+					char type_string[256];
+					char model_string[256];
+					char triggerscript_string[256];
+					sprintf(type_string,"Flag_%s",color);
+					sprintf(model_string,"gameobjects\\flags\\flag_%s\\flag_%s.mdl",color,color);
+					sprintf(triggerscript_string,"TRG_Flag_%sScript_Parked",color);
+					
+					sprintf(name_string,"TRG_Flag_%s",color);
+					pNode->AddComponent(CRCD(0x7f261953,"pos"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), m_restart_tab[i].pos.GetZ());
+//					pNode->AddComponent(CRCD(0xb9d31b0a,"Position"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), -m_restart_tab[i].pos.GetZ());
+					pNode->AddComponent(crc_angles, 0.0f, Mth::PI * 0.5f * ((float) m_restart_tab[i].dir + 2), 0.0f);
+					pNode->AddComponent(crc_name, ESYMBOLTYPE_NAME, Script::GenerateCRC(name_string));
+					pNode->AddComponent(crc_class, ESYMBOLTYPE_NAME, Script::GenerateCRC("GameObject"));
+					pNode->AddComponent(crc_type, ESYMBOLTYPE_NAME, Script::GenerateCRC(type_string));
+					pNode->AddComponent(crc_model, ESYMBOLTYPE_STRING, model_string);
+					pNode->AddComponent(crc_triggerscript, ESYMBOLTYPE_NAME, Script::GenerateCRC(triggerscript_string));
+
+					pNode->AddInteger("nodeIndex", *pNodeNum);
+					pNode->AddInteger("SuspendDistance", 0);
+					pNode->AddInteger("lod_dist1", 800);
+					pNode->AddInteger("lod_dist2", 801);
+//					Script::PrintContents(pNode);								   
+					(*pNodeNum)++;				
+					
+					// then repeat exactly for the CTF node, with different name
+					
+					pNode = pNodeArray->GetStructure(*pNodeNum);
+					sprintf(name_string,"TRG_CTF_%s",color);
+					pNode->AddComponent(CRCD(0x7f261953,"pos"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), m_restart_tab[i].pos.GetZ());
+//					pNode->AddComponent(CRCD(0xb9d31b0a,"Position"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), -m_restart_tab[i].pos.GetZ());
+					pNode->AddComponent(crc_angles, 0.0f, Mth::PI * 0.5f * ((float) m_restart_tab[i].dir + 2), 0.0f);
+					pNode->AddComponent(crc_name, ESYMBOLTYPE_NAME, Script::GenerateCRC(name_string));
+					pNode->AddComponent(crc_class, ESYMBOLTYPE_NAME, Script::GenerateCRC("GameObject"));
+					pNode->AddComponent(crc_type, ESYMBOLTYPE_NAME, Script::GenerateCRC(type_string));
+					pNode->AddComponent(crc_model, ESYMBOLTYPE_STRING, model_string);
+					pNode->AddComponent(crc_triggerscript, ESYMBOLTYPE_NAME, Script::GenerateCRC(triggerscript_string));
+
+					pNode->AddInteger("nodeIndex", *pNodeNum);
+					pNode->AddInteger("SuspendDistance", 0);
+					pNode->AddInteger("lod_dist1", 800);
+					pNode->AddInteger("lod_dist2", 801);
+//					Script::PrintContents(pNode);								   
+					(*pNodeNum)++;				
+
+					// different for base (note, no triggerscript)
+					pNode = pNodeArray->GetStructure(*pNodeNum);
+					sprintf(name_string,"TRG_CTF_%s_Base",color);
+					sprintf(type_string,"Flag_%s_Base",color);
+					sprintf(model_string,"gameobjects\\flags\\flag_%s_base\\flag_%s_base.mdl",color,color);
+					
+					pNode->AddComponent(CRCD(0x7f261953,"pos"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), m_restart_tab[i].pos.GetZ());
+//					pNode->AddComponent(CRCD(0xb9d31b0a,"Position"), m_restart_tab[i].pos.GetX(), m_restart_tab[i].pos.GetY(), -m_restart_tab[i].pos.GetZ());
+					pNode->AddComponent(crc_angles, 0.0f, Mth::PI * 0.5f * ((float) m_restart_tab[i].dir + 2), 0.0f);
+					pNode->AddComponent(crc_name, ESYMBOLTYPE_NAME, Script::GenerateCRC(name_string));
+					pNode->AddComponent(crc_class, ESYMBOLTYPE_NAME, Script::GenerateCRC("GameObject"));
+					pNode->AddComponent(crc_type, ESYMBOLTYPE_NAME, Script::GenerateCRC(type_string));
+					pNode->AddComponent(crc_model, ESYMBOLTYPE_STRING, model_string);
+
+					pNode->AddInteger("nodeIndex", *pNodeNum);
+					pNode->AddInteger("SuspendDistance", 0);
+					pNode->AddInteger("lod_dist1", 800);
+					pNode->AddInteger("lod_dist2", 801);
+//					Script::PrintContents(pNode);								   
+					(*pNodeNum)++;				
+				}
+			} // end for n
+		} // end if
+	}
+
+/*	
+	Position = (2456.617188,-218.955109,-6663.688477)
+	Angles = (0.741766,-1.570794,-0.000002)
+	Name = TRG_Restart_Parking_Lot04
+	Class = Restart
+	Type = Player1
+	CreatedAtStart
+	RestartName = "P1: Restart"
+	restart_types =
+	[
+	Player1
+	]
+*/
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// Restart stuff
+
+/* 
+	Should be called from 
+	-CParkManager::RebuildNodeArray() (was in Map::GenerateWorld())
+	-AddMetaPieceToPark::AddMetaPieceToPark() (was in Map::AddPiece())
+*/
+// Add a restart point if space
+// Non-automatic restart points will overwrite automatic restart points
+// (but not if they find an empty slot first)
+bool CParkGenerator::AddRestart(Mth::Vector &pos, int dir, GridDims &dims, RestartType type, bool automatic, bool auto_copy)
+{
+	int first_slot = 0, last_slot = 0;
+	get_restart_slots(type, &first_slot, &last_slot);
+
+	Mth::Vector	use_pos = pos;
+	GridDims use_dims = dims;
+
+	// When "auto_copy" is set
+	// If this is an automatic point, then copy the position
+	// from the last non-automatic point we find
+	if (automatic && auto_copy)
+	{
+		for (int i = first_slot; i <= last_slot; i++)
+		{
+			if (m_restart_tab[i].type == type && !m_restart_tab[i].automatic)
+			{
+				use_pos = m_restart_tab[i].pos;				
+				use_dims = m_restart_tab[i].dims;				
+				#ifdef	DEBUG_RESTARTS
+				printf ("%d: restart copying position from slot %d",__LINE__,i);
+				#endif
+			}
+		}
+	}
+	
+	
+	
+	for (int i = first_slot; i <= last_slot; i++)
+	{
+		
+		// add the restart to any empty slot, or overwrite an "automatic" slot if this is not automatic 
+		if (m_restart_tab[i].type == vEMPTY || (m_restart_tab[i].automatic && !automatic))		
+		{
+			m_restart_tab[i].pos = use_pos;
+			m_restart_tab[i].dir = dir;
+			m_restart_tab[i].dims = use_dims;
+			m_restart_tab[i].type = type;
+			m_restart_tab[i].automatic = automatic;
+			#ifdef	DEBUG_RESTARTS
+			printf ("%d: restart (%.2f,%.2f,%.2f) set in slot %d  (type = %d, auto = %d\n)",__LINE__,use_pos[X],use_pos[Y],use_pos[Z],i,type,automatic);
+			#endif
+			return true;
+		}		
+	}
+	
+	return false;
+}
+
+
+int CParkGenerator::NumRestartsOfType(RestartType type)
+{
+	int first_slot = 0, last_slot = 0;
+	get_restart_slots(type, &first_slot, &last_slot);
+
+	int got = 0;									// count of number of actual restarts
+	for (int i = first_slot; i <= last_slot; i++)
+	{
+		if (m_restart_tab[i].type != vEMPTY)		// got some kind of restart
+		{
+				got++; 								// counting an actual restart
+		}
+	}
+	return got;
+}
+
+
+// should be called from CParkManager::RebuildNodeArray() (was in Map::GenerateWorld())
+// returns true if there are not enough restart points (ie, true if we need more)
+// Note:  we include "automatic" restart points now.
+bool CParkGenerator::NotEnoughRestartsOfType(RestartType type, int need)
+{
+	return NumRestartsOfType(type) < need;		  // if got less than we need, return true, (we need more)
+}
+
+
+Mth::Vector	 CParkGenerator::GetRestartPos(RestartType type, int index)
+{
+
+	int first_slot = 0, last_slot = 0;
+	get_restart_slots(type, &first_slot, &last_slot);
+
+	for (int i = first_slot; i <= last_slot; i++)
+	{
+		if (m_restart_tab[i].type != vEMPTY)		// got some kind of restart
+		{
+			index--;								
+			if (index < 0)
+			{
+				return m_restart_tab[i].pos;
+			}
+		}
+	}
+		
+	Dbg_MsgAssert(0,("could not find restart pos"));	
+	return Mth::Vector(0,0,0);
+
+}
+
+GridDims	 CParkGenerator::GetRestartDims(RestartType type, int index)
+{
+
+	int first_slot = 0, last_slot = 0;
+	get_restart_slots(type, &first_slot, &last_slot);
+
+	for (int i = first_slot; i <= last_slot; i++)
+	{
+		if (m_restart_tab[i].type != vEMPTY)		// got some kind of restart
+		{
+			index--;								
+			if (index < 0)
+			{
+				return m_restart_tab[i].dims;
+			}
+		}
+	}
+		
+	Dbg_MsgAssert(0,("could not find restart dims"));	
+	return	GridDims();
+
+}
+
+
+
+// called by various restart functions in this class
+void CParkGenerator::get_restart_slots(RestartType type, int *pFirstSlot, int *pLastSlot)
+{
+	switch (type)
+	{
+		case vONE_PLAYER:
+			*pFirstSlot = 1;
+			*pLastSlot = 1;
+			break;
+		case vMULTIPLAYER:
+			*pFirstSlot = 0;
+			*pLastSlot = 0;
+			break;
+		case vHORSE:
+			*pFirstSlot = 2;
+			*pLastSlot = 7;
+			break;
+		case vKING_OF_HILL:
+			*pFirstSlot = 8;
+			*pLastSlot = 13;
+			break;
+		case vRED_FLAG:	
+			*pFirstSlot = 14;
+			*pLastSlot = 14;
+			break;
+		case vGREEN_FLAG:	
+			*pFirstSlot = 15;
+			*pLastSlot = 15;
+			break;
+		case vBLUE_FLAG:	
+			*pFirstSlot = 16;
+			*pLastSlot = 16;
+			break;
+		case vYELLOW_FLAG:	
+			*pFirstSlot = 17;
+			*pLastSlot = 17;
+			break;
+		default:
+			Dbg_MsgAssert(0,("Unhandled restart type %d",type));
+			break;
+	}
+}
+
+
+
+
+// should be called by CParkManager::DestroyConcreteMeta() (was Map::RemovePiece())
+void CParkGenerator::RemoveRestart(const GridDims &dims, RestartType type)
+{
+	int first_slot = 0, last_slot = 0;
+	get_restart_slots(type, &first_slot, &last_slot);
+	
+	for (int i = first_slot; i <= last_slot; i++)
+	{
+//		if (m_restart_tab[i].area.x == area.x &&
+//			m_restart_tab[i].area.y == area.y &&
+//			m_restart_tab[i].area.z == area.z)
+		if (
+			m_restart_tab[i].dims.GetX() == dims.GetX()
+			&& m_restart_tab[i].dims.GetY() == dims.GetY()
+			&& m_restart_tab[i].dims.GetZ() == dims.GetZ()
+			)
+		{
+			m_restart_tab[i].type = vEMPTY;
+		}
+	}
+}
+
+
+
+
+// should be called from CCursor::AttemptStamp() (was Cursor::AttemptPlacePiece())
+bool CParkGenerator::FreeRestartExists(RestartType type)
+{
+	int first_slot = 0, last_slot = 0;
+	get_restart_slots(type, &first_slot, &last_slot);
+	
+	for (int i = first_slot; i <= last_slot; i++)
+		if (m_restart_tab[i].type == vEMPTY || m_restart_tab[i].automatic)
+		{
+//			m_restart_tab[i].type = vEMPTY;
+			return true;
+		}
+	return false;
+}
+
+
+
+
+/* 
+	should be called from:
+	-CParkGenerator::InitializeMasterPieces() (was World::CreateParkHeap())
+	-CParkGenerator::DestroyAllPieces() or CParkManager::destroy_concrete_metapieces() (was World::DestroyAllPieces())
+*/
+void CParkGenerator::KillRestarts()
+{
+	#ifdef	DEBUG_RESTARTS
+	printf ("%d: KillRestarts called, all restarts set to vEMPTY",__LINE__);
+	#endif
+
+	for (int i = 0; i < vNUM_RESTARTS; i++)
+		m_restart_tab[i].type = vEMPTY;
+}
+
+
+void CParkGenerator::ClearAutomaticRestarts()
+{
+
+	#ifdef	DEBUG_RESTARTS
+	printf ("%d: ClearAutomaticRestarts called, all automatic restarts set to vEMPTY",__LINE__);
+	#endif
+	
+	for (int i = 0; i < vNUM_RESTARTS; i++)
+	{
+		if (m_restart_tab[i].automatic)
+		{
+			m_restart_tab[i].type = vEMPTY;
+		}
+	}
+}
+
+
+
+
+/*
+	Left edge of gap is at position pos. Bottom edge sticks out in direction
+	indicated by rot, for length units.
+*/
+CClonedPiece *CParkGenerator::CreateGapPiece(Mth::Vector &pos, float length, int rot)
+{
+	// maken der gapen piece
+	CClonedPiece *p_gap_piece = ClonePiece(GetMasterPiece(Script::GenerateCRC("Sk4Ed_Gap_10x10"), true), CPiece::mNO_FLAGS);
+	p_gap_piece->SetDesiredPos(pos, CClonedPiece::CHANGE_SECTOR);
+	p_gap_piece->SetDesiredRot(Mth::ERot90(rot), CClonedPiece::CHANGE_SECTOR);
+	p_gap_piece->set_flag(CPiece::mGAP);
+	AddClonedPieceToWorld(p_gap_piece);
+	// XXX
+	Ryan("@@@ made gap piece 0x%x\n", p_gap_piece);
+	return p_gap_piece;
+}
+
+
+
+
+}
+
+
diff --git a/Code/Sk/ParkEditor2/ParkGen.h b/Code/Sk/ParkEditor2/ParkGen.h
new file mode 100644
index 0000000..ba8ba49
--- /dev/null
+++ b/Code/Sk/ParkEditor2/ParkGen.h
@@ -0,0 +1,497 @@
+#ifndef __SK_PARKEDITOR2_PARKGEN_H
+#define __SK_PARKEDITOR2_PARKGEN_H
+
+#include 
+#include 
+
+#define DEBUG_THIS_DAMN_THING 0
+
+#if defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ )
+	inline void ParkEd(const char* A ...) {};
+#else
+
+	#if DEBUG_THIS_DAMN_THING
+		#define ParkEd(A...)  printf("PARKED: "); printf(##A); printf("\n") 		
+	#else
+		#define ParkEd(A...)
+	#endif
+#endif
+
+namespace Image
+{
+	struct RGBA;
+}
+
+namespace Nx
+{
+	class CScene;
+	class CSector;
+}
+
+namespace Script
+{
+	class	CArray;
+}
+
+namespace Ed
+{
+
+	class CSourcePiece;
+	class CClonedPiece;
+	class CMapListNode;
+
+struct MapArea		 	// Mick - OLD
+{
+	int x, y, z;
+	int w, h, l;
+};
+
+
+enum
+{
+	H = 4,
+	L = 5
+};
+
+
+
+
+/*
+	Describes a 3D box in cell coordinates. Sometimes only the position part or only the area part is used.
+*/
+class GridDims
+{
+public:
+								GridDims(uint8 x = 0, sint8 y = 0, uint8 z = 0, uint8 w = 0, uint8 h = 0, uint8 l = 0);
+
+	void						SetXYZ(uint8 x, sint8 y, uint8 z) {m_dims[0] = x; m_dims[1] = (uint8) y; m_dims[2] = z;}
+	void						SetWHL(uint8 w, uint8 h, uint8 l) {m_dims[3] = w; m_dims[4] = h; m_dims[5] = l;}
+
+	uint8 &						operator[](sint i);
+	
+	uint8						GetX() const {return m_dims[0];}
+	sint8						GetY() const {return (sint8) m_dims[1];}
+	uint8						GetZ() const {return m_dims[2];}
+	uint8						GetW() const {return m_dims[3];}
+	uint8						GetH() const {return m_dims[4];}
+	uint8						GetL() const {return m_dims[5];}
+
+	void						MakeInfinitelyHigh();
+	void						MakeInfiniteOnY();
+
+	void						PrintContents() const;
+
+private:
+
+	uint8						m_dims[6];
+};
+
+
+/*
+	Roles of CPiece:
+	-wraps object placed in park
+	-wraps fluid object contained in world (cursor)
+	
+	Class hierarchy:
+	-CPiece
+		-CSourcePiece (dims)
+		-CClonedPiece (pos, rot, pointer to master)
+		
+	Rotations:				  
+	-Hard: underlying verts are flipped around directly, stored dims changed to match
+	-Soft: matrix sent down directly, not stored with piece
+	
+	Relationship with metapiece:
+	-pieces contained by metapiece must be of the same type, all hard pieces or all soft pieces
+*/
+class CPiece
+{
+	friend class CParkGenerator;
+	friend class CMetaPiece;
+	friend class CConcreteMetaPiece;
+
+public:
+
+	enum EFlags
+	{
+		mNO_FLAGS =					0,
+
+		// only one of following can be set
+		mSOURCE_PIECE =				(1<<0),
+		mCLONED_PIECE =				(1<<1),
+		
+		// Applied to a cloned piece. Set if an animated piece, like part of cursor
+		mSOFT_PIECE =				(1<<2),
+		
+		// set if piece is being displayed
+		mIN_WORLD =					(1<<3),
+
+		mSHELL = 					(1<<4),
+		mFLOOR = 					(1<<5),
+		mGAP = 						(1<<6),
+		mRESTART =					(1<<7),
+		mNO_RAILS =					(1<<8),
+	};
+
+	EFlags							GetFlags() {return m_flags;}
+	virtual Mth::Vector				GetDims() = 0;
+	void							GetCellDims(GridDims *pDims);
+
+	CSourcePiece *					CastToCSourcePiece();
+	CClonedPiece *					CastToCClonedPiece();
+
+	Mth::Vector						GetDimsWithRot(Mth::ERot90 rot);
+
+protected:
+
+	/*
+		Hard rotation: flipping of verts
+		
+		Soft: rotation of underlying piece, matrix is set for CSector directly.
+	*/									
+	
+									CPiece();
+	virtual							~CPiece();
+
+	void							set_flag(EFlags flag) {m_flags = EFlags(m_flags | flag);}
+	void							clear_flag(EFlags flag) {m_flags = EFlags(m_flags & ~flag);}
+
+	union
+	{
+		uint32						m_sector_checksum;		// probably will refer to CSectors this way
+		// 							mp_other_thing // for soft pieces
+	};
+
+	EFlags							m_flags;
+
+	CPiece *						mp_next_in_list;
+	//CPiece *						mp_next_in_metapiece;
+};
+
+
+
+
+class CSourcePiece : public CPiece
+{
+	friend class CParkGenerator;
+	friend class CMetaPiece;
+	friend class CClonedPiece;
+
+public:
+	/*
+		pieces in different domains can coexist at same GRID coordinates
+	*/
+	enum EDomain
+	{
+		mREGULAR				= (1<<0),
+		mOFFSET_RAIL			= (1<<1),
+		mGAP					= (1<<2),
+	};
+
+	uint32							GetType();
+	Mth::Vector						GetDims() {return m_dims;}
+
+protected:
+									CSourcePiece();
+									~CSourcePiece();
+
+	Mth::Vector						m_dims;
+	EDomain							m_domain;	
+	uint32							m_triggerScriptId;  // Checksum of trigger script associated with this node (for stuff like chainlink fence SFX)
+
+	int								m_num_rail_points;
+	int								m_num_linked_rail_points;
+};
+
+
+
+
+class CClonedPiece : public CPiece
+{
+	friend class CParkGenerator;
+	friend class CMetaPiece;
+	friend class CConcreteMetaPiece;
+
+public:
+	uint32							GetID();
+	
+	enum ESectorEffect
+	{
+		MARKER_ONLY,
+		CHANGE_SECTOR,
+	};
+	void							SetDesiredPos(const Mth::Vector& pos, ESectorEffect sectorEffect);
+	void							SetDesiredRot(Mth::ERot90 rot, ESectorEffect sectorEffect);
+	void							SetSoftRot(float rot); // in degrees
+	void							SetScaleX(float scaleX);
+	void							Highlight(bool on, bool makePurple = false);
+	void							SetActive(bool active);
+	void							SetVisibility(bool visible);
+	CSourcePiece *					GetSourcePiece() {return mp_source_piece;}
+	Mth::Vector						GetPos() {return m_pos;}
+	Mth::ERot90						GetRot() {return m_rot;}
+	Mth::Vector						GetDims();
+	uint32							GetType() {return mp_source_piece->GetType();}
+
+protected:
+									CClonedPiece();
+
+	uint32							m_id;
+	CSourcePiece *					mp_source_piece;
+
+	Mth::Vector						m_pos; // center, bottom
+	Mth::ERot90						m_rot; // in units of 90 degrees, CCW
+};
+
+
+
+
+class CParkGenerator
+{
+
+public:
+
+	static const float 			CELL_WIDTH;
+	static const float 			CELL_HEIGHT;
+	static const float 			CELL_LENGTH;
+
+
+	static const uint32				vFIRST_ID_FOR_OBJECTS;
+	static const uint32				vMAX_ID_FOR_OBJECTS;
+	static const uint32				vFIRST_ID_FOR_RAILS;
+	static const uint32				vMAX_ID_FOR_RAILS;
+	static const uint32				vFIRST_ID_FOR_CREATED_RAILS;
+	static const uint32				vMAX_ID_FOR_CREATED_RAILS;
+	static const uint32				vFIRST_ID_FOR_RESTARTS;
+
+	
+	enum EDestroyType
+	{
+		DESTROY_PIECES_AND_SECTORS,
+		DESTROY_ONLY_PIECES,
+	};
+
+	enum RestartType
+	{
+		vEMPTY,
+		vONE_PLAYER,
+		vMULTIPLAYER,
+		vHORSE,
+		vKING_OF_HILL,
+		vRED_FLAG,
+		vGREEN_FLAG,
+		vBLUE_FLAG,
+		vYELLOW_FLAG,
+		
+		
+		vNUM_RESTART_TYPES,
+	};
+
+
+									CParkGenerator();
+									~CParkGenerator();
+
+	int								GetResourceSize(char *name);
+	Mem::Heap *						GetParkEditorHeap();
+	
+	struct MemUsageInfo
+	{
+		int							mParkHeapUsed;
+		int							mParkHeapFree; // after padding is deducted
+		int							mMainHeapUsed;
+		int							mMainHeapFree; // after padding is deducted
+		
+		int							mLastMainUsed;
+		bool						mIsFragmented;
+
+		int							mTotalRailPoints;
+		int							mTotalLinkedRailPoints;
+		int							mTotalClonedPieces;
+	};
+	MemUsageInfo					GetResourceUsageInfo(bool printInfo = false);
+	void							SetMaxPlayers(int maxPlayers);
+	int 							GetMaxPlayers() {return m_max_players;}
+	int 							GetMaxPlayersPossible();
+		
+	CSourcePiece *					GetMasterPiece(uint32 pieceType, bool assert = false);
+	CSourcePiece *					GetNextMasterPiece(CSourcePiece *pLast);
+	CClonedPiece *					ClonePiece(CPiece *pMasterPiece, CPiece::EFlags flags);
+	void							AddClonedPieceToWorld(CClonedPiece *pPiece);
+	void							DestroyClonedPiece(CClonedPiece *pPiece);
+
+
+	void							InitializeMasterPieces(int parkW, int parkH, int parkL, int theme);
+	
+	// K: Added to allow cleanup of the park editor heap during play
+	//void							DeleteSourceAndClonedPieces();
+	//void							DeleteParkEditorHeap();
+	
+	void							UnloadMasterPieces();
+	
+	void							PostGenerate();
+	void							GenerateCollisionInfo(bool assert = true);
+	void 							RemoveOuterShellPieces(int theme);
+	void							GenerateNodeInfo(CMapListNode * p_concrete_metapiece_list);
+	void 							ReadInRailInfo();
+	void							DestroyRailInfo();
+
+	void							HighlightAllPieces(bool highlight);
+	void							SetGapPiecesVisible(bool visible);
+	void							SetRestartPiecesVisible(bool visible);
+
+
+	void 							SetLightProps(int num_lights, 
+												  float amb_const_r, float amb_const_g, float amb_const_b, 
+												  float falloff_const_r, float falloff_const_g, float falloff_const_b,
+												  float cursor_ambience);
+	void 							SetLight(Mth::Vector &light_pos, int light_num);
+	void 							CalculateLighting(CClonedPiece *p_piece);
+	void 							CalculateVertexLighting(const Mth::Vector & vert, Image::RGBA & color);
+	
+	
+	void							DestroyAllClonedPieces(EDestroyType type);
+
+
+	int								NumRestartsOfType(RestartType type);
+	bool							NotEnoughRestartsOfType(RestartType type, int need);
+	Mth::Vector	 					GetRestartPos(RestartType type, int index);
+	GridDims	 					GetRestartDims(RestartType type, int index);
+	bool							AddRestart(Mth::Vector &pos, int dir, GridDims &dims, RestartType type, bool automatic, bool auto_copy=false);
+	void							RemoveRestart(const GridDims &dims, RestartType type);
+	bool 							FreeRestartExists(RestartType type);
+	void							KillRestarts();	
+	void							ClearAutomaticRestarts();
+
+	
+	CClonedPiece *					CreateGapPiece(Mth::Vector &pos, float length, int rot);
+
+	Nx::CScene *					GetClonedScene() {return mp_cloned_scene;}
+
+	void							CleanUpOutRailSet();
+	void							GenerateOutRailSet(CMapListNode * p_concrete_metapiece_list);
+	bool							FindNearestRailPoint(Mth::Vector &pos, Mth::Vector *p_nearest_pos, float *p_dist_squared);
+	
+private:
+
+	enum EFlags
+	{
+		mMASTER_STUFF_LOADED		= (1<<1),
+		mSECTORS_GENERATED			= (1<<2),
+		mPIECES_IN_WORLD			= (1<<3),
+		mCOLLISION_INFO_GENERATED	= (1<<4),
+		mNODE_INFO_GENERATED		= (1<<5),
+		// set when we destroy a piece
+		mSECTOR_MEMORY_NEEDS_FREE	= (1<<6),
+	};
+
+	void 							destroy_piece_impl(CClonedPiece *pPiece, EDestroyType destroyType);
+	bool							flag_on(EFlags flag) {return ((m_flags & flag) != 0);}
+	void							set_flag(EFlags flag) {m_flags = EFlags(m_flags | flag);}
+	void							clear_flag(EFlags flag) {m_flags = EFlags(m_flags & ~flag);}
+	
+	uint32 							scan_for_cluster(Script::CArray *pNodeArray, int &index);
+	int 							scan_for_rail_node(Script::CArray *pNodeArray, uint32 cluster, int link, Mth::Vector *pVector, bool *pHasLinks, RailPoint::RailType *pType);
+	void							scan_in_trigger_info(Script::CArray *pNodeArray);
+	void 							set_up_object_nodes(Script::CArray *pNodeArray, int *pNodeNum);
+	void							set_up_rail_nodes(Script::CArray *pNodeArray, int *pNodeNum);
+	void							set_up_restart_nodes(Script::CArray *pNodeArray, int *pNodeNum);
+
+	
+	/*
+	Things
+	
+		-loading master geometry, loading master rails (one operation)
+		-generating world from compressed map
+		-collision setup
+		-rail setup
+		-cleanup world (maybe just destroys pieces)
+	*/
+	
+	EFlags							m_flags;
+
+	CPiece *						mp_cloned_piece_list;
+	CPiece *						mp_source_piece_list;
+	int								m_num_cloned_pieces;
+	int								m_num_source_pieces;
+
+	Nx::CScene *					mp_source_scene;				// Scene used as a manager of pieces
+	Nx::CScene *					mp_cloned_scene;
+
+	uint32							m_next_id;
+	
+	Mem::Region*					mp_mem_region;
+	Mem::Heap*						mp_mem_heap;
+
+	int								m_max_players;
+		
+//////////////////////////////////////////////////////////////////////////
+// rail and node stuff patched in by Mick
+
+	RailSet							m_in_rail_set;
+	RailSet							m_out_rail_set;
+	// in world, at editing time
+	int								m_total_rail_points;
+	int								m_total_rail_linked_points;
+
+	struct TempNodeInfo
+	{
+		uint32						classCrc;
+		uint32						cluster;
+		Script::CArray *			pLinkArray;
+	};
+
+	TempNodeInfo *					m_temp_node_tab;
+	int	*							m_processed_node_tab;
+	int								m_processed_node_tab_entries;
+
+	/*
+		Restart stuff
+	*/
+
+
+
+	void 				get_restart_slots(RestartType type, int *pFirstSlot, int *pLastSlot);
+
+
+	struct RestartInfo
+	{
+		Mth::Vector					pos;
+		int							dir;
+		GridDims					dims;
+		RestartType					type;
+		bool						automatic;
+	};
+	static const int				vNUM_RESTARTS = 18;
+	
+	RestartInfo						m_restart_tab[vNUM_RESTARTS];
+
+	/*
+		Light info
+	*/
+	
+	struct LightIntensity
+	{
+		float r;
+		float g;
+		float b;
+	};
+	
+	static const int				vMAX_LIGHTS = 16;
+	
+	int								m_numLights;
+	LightIntensity					m_ambientLightIntensity;
+	LightIntensity					m_falloffLightIntensity;
+	Mth::Vector						m_lightTab[vMAX_LIGHTS];
+	float							m_cursorAmbience;
+
+	
+	MemUsageInfo					m_mem_usage_info;
+};
+
+
+
+
+} // end namespace
+
+
+#endif
diff --git a/Code/Sk/ParkEditor2/clipboard.cpp b/Code/Sk/ParkEditor2/clipboard.cpp
new file mode 100644
index 0000000..b7bb0c3
--- /dev/null
+++ b/Code/Sk/ParkEditor2/clipboard.cpp
@@ -0,0 +1,997 @@
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+DefinePoolableClass(Ed::CClipboardEntry)
+DefinePoolableClass(Ed::CClipboard)
+
+namespace Ed
+{
+
+static void s_rotate_map_vector(int *p_x, int *p_z, Mth::ERot90 rotation)
+{
+	int in_x=*p_x;
+	int in_z=*p_z;
+	switch (rotation)
+	{
+		case Mth::ROT_0:
+			break;
+		case Mth::ROT_90:
+			*p_x=in_z;
+			*p_z=-in_x;
+			break;
+		case Mth::ROT_180:
+			*p_x=-in_x;
+			*p_z=-in_z;
+			break;
+		case Mth::ROT_270:
+			*p_x=-in_z;
+			*p_z=in_x;
+			break;
+		default:
+			Dbg_MsgAssert(0, ("Wacky rotation"));
+			break;
+	}		
+}
+
+CClipboardEntry::CClipboardEntry()
+{
+	mpMeta=NULL;
+	mpConcreteMeta=NULL;
+	mX=mY=mZ=0;
+	mWidth=0;
+	mLength=0;
+	m_rot=Mth::ROT_0;
+	mpNext=NULL;
+}
+
+CClipboardEntry::~CClipboardEntry()
+{
+	DestroyMeta();
+}
+
+void CClipboardEntry::DestroyMeta()
+{
+	if (mpConcreteMeta)
+	{
+		mpConcreteMeta->MarkUnlocked();
+		CParkManager::sInstance()->DestroyConcreteMeta(mpConcreteMeta, CParkManager::mDONT_DESTROY_PIECES_ABOVE);
+		mpConcreteMeta=NULL;
+	}
+}
+
+// This function takes the vector from the centre to the piece, rotates it according to 
+// the cursor's current rotation, then adds that vector to the cursor's position and places the result in
+// p_mapCoords
+void CClipboardEntry::CalculateMapCoords(GridDims *p_mapCoords, uint8 centre_x, uint8 centre_z, CCursor *p_cursor)
+{
+	GridDims meta_area;
+	if (mpMeta)
+	{
+		meta_area=mpMeta->GetArea();
+	}
+	else
+	{
+		// If there is no meta then the clipboard entry is defining a height for one cell, so
+		// just set the w and l to 1.
+		meta_area.SetWHL(1,1,1);
+	}
+		
+	Mth::ERot90 total_rotation=Mth::ERot90((p_cursor->GetRotation()+m_rot)&3);
+	switch ( total_rotation )
+	{
+		case Mth::ROT_0:
+		case Mth::ROT_180:
+			p_mapCoords->SetWHL(meta_area.GetW(),1,meta_area.GetL());
+			break;
+		case Mth::ROT_90:
+		case Mth::ROT_270:
+			p_mapCoords->SetWHL(meta_area.GetL(),1,meta_area.GetW());
+			break;
+		default:
+			Dbg_MsgAssert(0, ("Wacky rotation"));
+			break;
+	}		
+	
+	// Get the vector from the centre of the selection area to the piece.
+	int vector_x=mX-centre_x;
+	int vector_z=mZ-centre_z;
+	
+	// Rotate the vector according to the cursor's current rotation
+	s_rotate_map_vector(&vector_x, &vector_z, p_cursor->GetRotation());
+
+	// A frig is needed after rotation because the piece always appears to be
+	// displayed with the coords referring to the top-left corner, even when the
+	// piece is rotated.
+	switch ( p_cursor->GetRotation() )
+	{
+		case Mth::ROT_0:
+			break;
+		case Mth::ROT_90:
+			vector_z-=mWidth-1;
+			break;
+		case Mth::ROT_180:
+			vector_x-=mWidth-1;
+			vector_z-=mLength-1;
+			break;	
+		case Mth::ROT_270:
+			vector_x-=mLength-1;
+			break;
+		default:
+			Dbg_MsgAssert(0, ("Wacky rotation"));
+			break;
+	}		
+
+	// Get the new map coords of the meta such that it is positioned relative to the cursor.
+	GridDims cursor_position=p_cursor->GetPosition();
+	p_mapCoords->SetXYZ(cursor_position.GetX()+vector_x, mY, cursor_position.GetZ()+vector_z);
+}
+
+bool CClipboardEntry::CanPaste(uint8 centre_x, uint8 centre_z, CCursor *p_cursor)
+{
+	GridDims map_coords;
+	CalculateMapCoords(&map_coords,centre_x,centre_z,p_cursor);
+	// Make sure the y is set to zero because the GetMetaPiecesAt function that gets called in
+	// CanPlacePiecesIn will think there is something in the cell if the y is -1
+	map_coords.SetXYZ(map_coords.GetX(),0,map_coords.GetZ());
+	
+	CParkManager *p_manager=CParkManager::sInstance();
+	return p_manager->CanPlacePiecesIn(map_coords, true); // The true means ignore floor height problems
+}
+
+// centre_x,centre_z are the map coords of the centre of the original selection area.
+// The coords of the entry (mX,mZ) are also in map coords.
+void CClipboardEntry::Paste(uint8 centre_x, int raiseAmount, uint8 centre_z, CCursor *p_cursor)
+{
+	if (mpMeta && mpMeta->IsRiser())
+	{
+		return;
+	}
+
+	GridDims map_coords;
+	CalculateMapCoords(&map_coords,centre_x,centre_z,p_cursor);
+	
+	int new_y=mY+raiseAmount;
+	if (new_y >= CParkManager::MAX_HEIGHT)
+	{
+		new_y=CParkManager::MAX_HEIGHT-1;
+	}
+
+	map_coords.SetXYZ(map_coords.GetX(),new_y,map_coords.GetZ());
+		
+	CParkManager *p_manager=CParkManager::sInstance();
+	
+	// Adjust the floor height to match that of the piece
+	int count=0;	
+	while (true)
+	{
+		bool uniform_height=false;
+		int current_height = p_manager->GetFloorHeight(map_coords, &uniform_height);
+		
+		if (current_height > new_y)
+		{
+			break;
+		}	
+		
+		if (count>40)
+		{
+			Dbg_MsgAssert(0,("Too many recursions of ChangeFloorHeight in CClipboardEntry::Paste"));
+			break;
+		}	
+			
+		if (uniform_height && current_height==new_y)
+		{
+			break;
+		}
+		
+		p_manager->ChangeFloorHeight(map_coords,1); // 1 is the step amount, 1 upwards
+		++count;
+	}		
+
+	if (mpMeta)
+	{
+		// Place the piece	
+		CConcreteMetaPiece *p_concrete = p_manager->CreateConcreteMeta(mpMeta);
+		p_concrete->SetRot(Mth::ERot90((m_rot+p_cursor->GetRotation())&3));
+		p_manager->AddMetaPieceToPark(map_coords, p_concrete);
+	}	
+}
+
+int CClipboardEntry::GetFloorHeight(uint8 centre_x, uint8 centre_z, CCursor *p_cursor)
+{
+	GridDims map_coords;
+	CalculateMapCoords(&map_coords,centre_x,centre_z,p_cursor);
+	
+	bool uniform_height;
+	return CParkManager::sInstance()->GetFloorHeight(map_coords, &uniform_height);
+}
+
+void CClipboardEntry::HighlightIntersectingMetas(uint8 centre_x, uint8 centre_z, CCursor *p_cursor, bool on)
+{
+	GridDims map_coords;
+	CalculateMapCoords(&map_coords,centre_x,centre_z,p_cursor);
+	// Make sure the y is set to zero because the GetMetaPiecesAt function that gets called in
+	// CanPlacePiecesIn will think there is something in the cell if the y is -1
+	map_coords.SetXYZ(map_coords.GetX(),0,map_coords.GetZ());
+	
+	CParkManager *p_manager=CParkManager::sInstance();
+	
+	CMapListTemp metas_at_pos = p_manager->GetAreaMetaPieces(map_coords);
+	if (metas_at_pos.IsEmpty())
+	{
+		return;
+	}
+	
+	// Iterate over the list of metapieces we just found, and highlight each one
+	CMapListNode *p_entry = metas_at_pos.GetList();
+	while(p_entry)
+	{
+		CConcreteMetaPiece *p_meta=p_entry->GetConcreteMeta();
+		if (!p_meta->IsRiser())
+		{
+			p_meta->Highlight(on);		
+		}
+			
+		p_entry = p_entry->GetNext();
+	}
+}
+
+// This gets called when the CCursor is in paste mode, and needs to display the clipboard contents at
+// the cursor position.
+// pos and rot are the world position and rotation in degrees of the cursor.
+// They may not be cell aligned, and the rotation may not be a multiple of 90 degrees.
+// This function will position the meta such that the centre of the clipboard selection is at pos.
+void CClipboardEntry::ShowMeta(uint8 centre_x, int raiseAmount, uint8 centre_z, Mth::Vector pos, float rot)
+{
+	if (!mpMeta)
+	{
+		// No meta to display, meaning the clipboard entry is defining a cell height
+		return;
+	}
+
+	// Get the vector (in cell units) from the centre of the selection area to the piece.
+	float vector_x=mX-centre_x;
+	float vector_z=mZ-centre_z;
+
+	// Calculate the vector, in world coords, from the centre of the selection to the centre of the piece.	
+	Mth::Vector p(0.0f,0.0f,0.0f);
+	p[X]=vector_x*CParkGenerator::CELL_WIDTH+(mWidth-1)*CParkGenerator::CELL_WIDTH/2.0f;
+	p[Z]=vector_z*CParkGenerator::CELL_LENGTH+(mLength-1)*CParkGenerator::CELL_LENGTH/2.0f;
+
+	// Rotate by rot degrees	
+	float rad=rot*3.141592654f/180.0f;
+	float s=sinf(rad);
+	float c=cosf(rad);
+
+	Mth::Vector rotated;
+	rotated[X]=c*p[X]+s*p[Z];
+	rotated[Y]=0.0f;
+	rotated[Z]=c*p[Z]-s*p[X];
+	
+	// Add the passed pos to get the final world position of the piece.
+	Mth::Vector world_pos=pos+rotated;
+
+	// Add in the cell height
+	world_pos[Y]+=mY*CParkGenerator::CELL_HEIGHT;
+	if (mpMeta->IsRiser())
+	{
+		world_pos[Y]-=CParkGenerator::CELL_HEIGHT;
+	}	
+	
+	// Create the concrete meta if it does not exist already.
+	if (!mpConcreteMeta)
+	{
+		Dbg_MsgAssert(mpMeta,("NULL mpMeta ?"));
+		mpConcreteMeta=CParkManager::sInstance()->CreateConcreteMeta(mpMeta,true);
+		mpConcreteMeta->MarkLocked();
+	}	
+	
+	// Position the piece, adding in the pieces local rotation value.
+	mpConcreteMeta->SetSoftRot(world_pos,rot+90.0f*m_rot);
+}
+
+bool CClipboardEntry::CreateGapFillerPieces()
+{
+	if (!mpMeta)
+	{
+		//printf("y=%d\n",mY);
+		return true;
+	}
+
+	//printf("y=%d Meta=%s\n",mY,Script::FindChecksumName(mpMeta->GetNameChecksum()));
+
+	switch (mpMeta->GetNameChecksum())
+	{
+	case CRCC(0x66a8fedb,"floor_block1"):
+	case CRCC(0xffa1af61,"floor_block2"):
+	case CRCC(0x88a69ff7,"floor_block3"):
+	case CRCC(0x16c20a54,"floor_block4"):
+	case CRCC(0x61c53ac2,"floor_block5"):
+	case CRCC(0xf8cc6b78,"floor_block6"):
+	case CRCC(0x8fcb5bee,"floor_block7"):
+	case CRCC(0x1f74467f,"floor_block8"):
+		break;
+	default:
+		return true;
+		break;
+	}		
+
+	//printf("y=%d Meta=%s\n",mY,Script::FindChecksumName(mpMeta->GetNameChecksum()));
+
+	bool ran_out_of_memory=false;
+	CClipboardEntry *p_new_list=NULL;
+	CClipboardEntry *p_last_in_list=NULL;
+	for (int y=mY; y>0; --y)
+	{
+		if (Mem::CPoolable::SGetNumUsedItems()==
+			Mem::CPoolable::SGetTotalItems())
+		{
+			// Not enough space to create a new CClipboardEntry !
+			ran_out_of_memory=true;
+			break;
+		}	
+				
+		CClipboardEntry *p_new_entry=new CClipboardEntry;
+		if (!p_last_in_list)
+		{
+			p_last_in_list=p_new_entry;
+		}	
+
+		if (y==mY)
+		{
+			p_new_entry->mpMeta = CParkManager::sInstance()->GetAbstractMeta(CRCD(0x2c5b0277,"floor_wall_block3"));
+		}
+		else
+		{
+			p_new_entry->mpMeta = CParkManager::sInstance()->GetAbstractMeta(CRCD(0x72372c50,"wall_block1"));
+		}
+			
+		p_new_entry->mX=mX;
+		p_new_entry->mY=y;
+		p_new_entry->mZ=mZ;
+		p_new_entry->mWidth=1;
+		p_new_entry->mLength=1;
+		p_new_entry->m_rot=m_rot;
+		
+		// Insert into the list.
+		p_new_entry->mpNext=p_new_list;
+		p_new_list=p_new_entry;
+	}
+	
+	if (p_new_list)
+	{
+		Dbg_MsgAssert(p_last_in_list,("Eh?"));
+		p_last_in_list->mpNext=mpNext;
+		mpNext=p_new_list;
+	}
+		
+	if (ran_out_of_memory)
+	{
+		return false;
+	}
+		
+	return true;
+}
+	
+CClipboard::CClipboard()
+{
+	mp_entries=NULL;
+	m_min_x=m_min_z=255;
+	m_max_x=m_max_z=0;
+	
+	mpNext=NULL;
+	m_area.SetXYZ(0,0,0);
+	m_area.SetWHL(0,0,0);
+	
+	mp_rails=NULL;
+}
+
+CClipboard::~CClipboard()
+{
+	delete_entries();
+}
+
+void CClipboard::delete_entries()
+{
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		CClipboardEntry *p_next=p_entry->mpNext;
+		delete p_entry;
+		p_entry=p_next;
+	}	
+	mp_entries=NULL;
+	
+	m_min_x=m_min_z=255;
+	m_max_x=m_max_z=0;
+	
+	m_area.SetXYZ(0,0,0);
+	m_area.SetWHL(0,0,0);
+	
+	// Call UpdateSuperSectors to refresh the collision before deleting the rails otherwise the 
+	// collision code will assert when the rail sectors are deleted. (TT6874)
+	// UpdateSuperSectors isn;t normally called on the rails displayed on the cursor because
+	// they don't need collision.
+	Nx::CScene *p_cloned_scene=Ed::CParkManager::sInstance()->GetGenerator()->GetClonedScene();
+	p_cloned_scene->UpdateSuperSectors();
+
+	Obj::CEditedRail *p_rail=mp_rails;
+	while (p_rail)
+	{
+		Obj::CEditedRail *p_next=p_rail->mpNext;
+		delete p_rail;
+		p_rail=p_next;
+	}
+	mp_rails=NULL;
+}
+
+void CClipboard::update_extents(uint8 x, uint8 z)
+{
+	if (x < m_min_x)
+	{
+		m_min_x=x;
+	}
+	if (x > m_max_x)
+	{
+		m_max_x=x;
+	}
+	if (z < m_min_z)
+	{
+		m_min_z=z;
+	}
+	if (z > m_max_z)
+	{
+		m_max_z=z;
+	}
+}
+
+// Returns false if it fails due to running out of space on the CClipboardEntry pool
+bool CClipboard::AddMeta(CConcreteMetaPiece *p_meta, int raiseAmount)
+{
+	Dbg_MsgAssert(p_meta,("NULL p_meta"));
+
+	GridDims dims=p_meta->GetArea();
+	dims.SetXYZ(dims.GetX(),dims.GetY()+raiseAmount,dims.GetZ());
+	//printf("AddMeta y=%d raiseAmount=%d, \t%s Riser=%d\n",dims.GetY(),raiseAmount,Script::FindChecksumName(p_meta->GetNameChecksum()),p_meta->IsRiser());
+	
+	if (p_meta->IsRiser())
+	{
+		dims.SetXYZ(dims.GetX(),dims.GetY()+1,dims.GetZ());
+		
+		//printf("Riser: %d,%d  %d\n",dims.GetX(),dims.GetZ(),dims.GetY());
+		
+		if (dims.GetY())
+		{
+			if (!AddHeight(dims.GetX(),dims.GetY(),dims.GetZ()))
+			{
+				return false;
+			}	
+		}	
+		// Zero height riser pieces (ie, floor pieces) still get added so that they
+		// are displayed on the cursor.
+	}	
+
+	CAbstractMetaPiece *p_abstract_meta=CParkManager::sInstance()->GetAbstractMeta(p_meta->GetNameChecksum());
+	
+	// Don't add meta if it is in the list already
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		if (p_entry->mpMeta==p_abstract_meta &&
+			p_entry->mX==dims.GetX() &&
+			p_entry->mY==dims.GetY() &&
+			p_entry->mZ==dims.GetZ() &&
+			p_entry->mWidth==dims.GetW() &&
+			p_entry->mLength==dims.GetL() &&
+			p_entry->m_rot==p_meta->GetRot())
+		{
+			return true;
+		}
+		p_entry=p_entry->mpNext;
+	}	
+
+	if (Mem::CPoolable::SGetNumUsedItems()==
+		Mem::CPoolable::SGetTotalItems())
+	{
+		// Not enough space to create a new CClipboardEntry !
+		return false;
+	}	
+			
+	CClipboardEntry *p_new_entry=new CClipboardEntry;
+
+	// Store a pointer to the abstract meta so that a copy can be made when the time comes to paste.	
+	// Mustn't store a pointer to the concrete meta because the user may delete that piece later.
+	p_new_entry->mpMeta = p_abstract_meta;
+
+	// Store the grid coords of the concrete meta because the abstract meta's x,y,z will be zero.
+	p_new_entry->mX=dims.GetX();
+	p_new_entry->mY=dims.GetY();
+	p_new_entry->mZ=dims.GetZ();
+	// Also store the width and length, needed for calculating the world pos of the centre when
+	// displaying the piece on the cursor.
+	// Note that the width and height will already take into account any rotation that the concrete meta has.
+	p_new_entry->mWidth=dims.GetW();
+	p_new_entry->mLength=dims.GetL();
+
+	// Update the min & max x & z for the clipboard selection for calculation of the centre later.
+	update_extents(dims.GetX(),					dims.GetZ());
+	update_extents(dims.GetX()+dims.GetW()-1,	dims.GetZ()+dims.GetL()-1);
+	
+	p_new_entry->m_rot=p_meta->GetRot();
+	
+	// Insert into the list.
+	p_new_entry->mpNext=mp_entries;
+	mp_entries=p_new_entry;
+	return true;
+}
+
+// Returns false if it fails due to running out of space on the CClipboardEntry pool
+bool CClipboard::AddHeight(int x, int y, int z)
+{
+	// If there is a piece covering this position already then do not add
+	// a new height, to prevent unnecessary column slides.
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		if (p_entry->mpMeta && !p_entry->mpMeta->IsRiser() &&
+			p_entry->mX <= x && x < p_entry->mX+p_entry->mWidth &&
+			p_entry->mZ <= z && z < p_entry->mZ+p_entry->mLength)
+		{
+			return true;
+		}
+		p_entry=p_entry->mpNext;
+	}	
+	
+	// If there is a height defined for x,z already then just update it's y
+	// if the passed y is bigger.
+	p_entry=mp_entries;
+	while (p_entry)
+	{
+		if (p_entry->mpMeta==NULL &&
+			p_entry->mX <= x && x < p_entry->mX+p_entry->mWidth &&
+			p_entry->mZ <= z && z < p_entry->mZ+p_entry->mLength)
+		{
+			if (y >= p_entry->mY)
+			{
+				p_entry->mY=y;
+			}
+			return true;
+		}
+		p_entry=p_entry->mpNext;
+	}	
+	
+	if (Mem::CPoolable::SGetNumUsedItems()==
+		Mem::CPoolable::SGetTotalItems())
+	{
+		// Not enough space to create a new CClipboardEntry !
+		return false;
+	}	
+
+	CClipboardEntry *p_new_entry=new CClipboardEntry;
+
+	p_new_entry->mpMeta = NULL;
+
+	p_new_entry->mX=x;
+	p_new_entry->mY=y;
+	p_new_entry->mZ=z;
+	p_new_entry->mWidth=1;
+	p_new_entry->mLength=1;
+
+	// Update the min & max x & z for the clipboard selection for calculation of the centre later.
+	update_extents(x,z);
+	
+	// Insert into the list.
+	p_new_entry->mpNext=mp_entries;
+	mp_entries=p_new_entry;
+	return true;
+}
+
+// Creates clipboard entries for all the things in the passed area.
+// A clipboard entry will be created if there is a piece in a cell, or if
+// the cell has a non-zero height.
+// Returns false if it fails due to running out of space on the CClipboardEntry pool.
+// If that happens, it will delete any CClipboardEntry's that it already allocated.
+bool CClipboard::CopySelectionToClipboard(GridDims area)
+{
+	m_area=area;
+	
+	CParkManager *p_manager=CParkManager::sInstance();
+
+	// First, figure out how much the selection needs to be raised up so that
+	// all the pieces are above ground level.
+	int most_negative_y=0;	
+	for (int x = area.GetX(); x < area.GetX() + area.GetW(); x++)
+	{
+		for (int z = area.GetZ(); z < area.GetZ() + area.GetL(); z++)
+		{
+			CMapListNode *p_entry=p_manager->GetMapListNode(x,z);
+			while (p_entry)
+			{
+				CConcreteMetaPiece *p_meta=p_entry->GetConcreteMeta();
+				
+				//printf("Meta name = %s\n",Script::FindChecksumName(p_meta->GetNameChecksum()));
+				int y=p_meta->GetArea().GetY();
+				switch (p_meta->GetNameChecksum())
+				{
+				case CRCC(0x66a8fedb,"floor_block1"):
+				case CRCC(0xffa1af61,"floor_block2"):
+				case CRCC(0x88a69ff7,"floor_block3"):
+				case CRCC(0x16c20a54,"floor_block4"):
+				case CRCC(0x61c53ac2,"floor_block5"):
+				case CRCC(0xf8cc6b78,"floor_block6"):
+				case CRCC(0x8fcb5bee,"floor_block7"):
+				case CRCC(0x1f74467f,"floor_block8"):
+					// With floor blocks, the ground position is 1 unit higher up than
+					// their stored y, because their stored y is the y of their base.
+					++y;
+					break;
+				default:
+					break;
+				}		
+				if (y < most_negative_y)
+				{
+					most_negative_y=y;
+				}
+
+				//printf("y=%d  %s\n",p_meta->GetArea().GetY(),Script::FindChecksumName(p_meta->GetNameChecksum()));
+				
+				p_entry = p_entry->GetNext();
+			}
+		}
+	}
+	
+	
+	int raise_amount=-most_negative_y;
+	
+	
+	for (int x = area.GetX(); x < area.GetX() + area.GetW(); x++)
+	{
+		for (int z = area.GetZ(); z < area.GetZ() + area.GetL(); z++)
+		{
+			CMapListNode *p_entry=p_manager->GetMapListNode(x,z);
+			while (p_entry)
+			{
+				CConcreteMetaPiece *p_meta=p_entry->GetConcreteMeta();
+				if (p_meta->IsRestartOrFlag())
+				{
+					// Don't copy restarts or flags (TT7793)
+				}
+				else if (!p_meta->IsCompletelyWithinArea(area))
+				{
+				}
+				else
+				{
+					if (!AddMeta(p_meta,raise_amount))
+					{
+						// Delete the existing CClipboardEntry's so that another attempt
+						// at calling CopySelectionToClipboard can be made later after the
+						// calling code has freed up some memory.
+						delete_entries();
+						return false;
+					}
+				}		
+				
+				p_entry = p_entry->GetNext();
+			}
+		}
+	}	
+
+	// Now one last ugly bit. Raising the selection may have moved some simple ground pieces
+	// to above the ground, in which case they won't have pieces filling the gap, which looks odd.
+	// So create these pieces if needed.
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		if (!p_entry->CreateGapFillerPieces())
+		{
+			// Could not create the pieces, due to low memory.
+			delete_entries();
+			return false;
+		}	
+		p_entry=p_entry->mpNext;
+	}
+
+
+	Dbg_MsgAssert(mp_rails==NULL,("Expected mp_rails=NULL"));
+	Mth::Vector corner_pos;
+	Mth::Vector area_dims;
+	corner_pos=Ed::CParkManager::Instance()->GridCoordinatesToWorld(area,&area_dims);
+	mp_rails=Obj::GetRailEditor()->GenerateDuplicateRails(corner_pos[X], corner_pos[Z], 
+														  corner_pos[X]+area_dims[X], corner_pos[Z]+area_dims[Z]);
+		
+	return true;
+}
+
+bool CClipboard::IsEmpty()
+{
+	if (mp_entries)
+	{
+		return false;
+	}
+	if (mp_rails)
+	{
+		return false;
+	}	
+	return true;
+}
+		
+// Pastes the contents of the clipboard into the position that the cursor is at,
+// using the cursor's rotation value.
+bool CClipboard::Paste(CCursor *p_cursor)
+{
+	Spt::SingletonPtr p_editor;
+	if (!p_editor->RoomToCopyOrPaste(m_max_x-m_min_x+1, m_max_z-m_min_z+1))
+	{
+		return false;
+	}	
+
+	// Get the map coords of the centre cell of the set of meta's
+	uint8 centre_x=(m_min_x+m_max_x)/2;
+	uint8 centre_z=(m_min_z+m_max_z)/2;
+	
+	// Bail out if any entry cannot be pasted
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		if (!p_entry->CanPaste(centre_x,centre_z,p_cursor))
+		{
+			return false;
+		}	
+		p_entry=p_entry->mpNext;
+	}	
+
+	// Bail out if there are not enough free rail points to create the edited rails
+	int num_rail_points_to_paste=0;
+	Obj::CEditedRail *p_rail=mp_rails;
+	while (p_rail)
+	{
+		num_rail_points_to_paste += p_rail->CountPoints();
+		p_rail=p_rail->mpNext;
+	}
+	if (num_rail_points_to_paste > Obj::GetRailEditor()->GetNumFreePoints() )
+	{
+		return false;
+	}	
+
+	
+	// OK to proceed, so paste the entries
+	p_entry=mp_entries;
+	while (p_entry)
+	{
+		p_entry->Paste(centre_x,p_cursor->GetClipboardY(),centre_z,p_cursor);
+		p_entry=p_entry->mpNext;
+	}	
+	
+	// and paste the rails
+	Mth::Vector clipboard_pos=GetClipboardWorldPos();
+	Mth::Vector cursor_pos=p_editor->GetCursorPos();
+	
+	p_rail=mp_rails;
+	while (p_rail)
+	{
+		Obj::CEditedRail *p_new_rail=Obj::GetRailEditor()->NewRail();
+		p_new_rail->CopyRail(p_rail);
+		p_new_rail->RotateAndTranslate(clipboard_pos, cursor_pos, p_cursor->GetRotation()*90.0f);
+		
+		p_new_rail->UpdateRailGeometry();
+		p_new_rail->UpdatePostGeometry();
+		
+		p_rail=p_rail->mpNext;
+	}
+		
+	return true;
+}
+
+
+int CClipboard::GetFloorHeight(CCursor *p_cursor)
+{
+	// Get the map coords of the centre cell of the set of meta's
+	uint8 centre_x=(m_min_x+m_max_x)/2;
+	uint8 centre_z=(m_min_z+m_max_z)/2;
+
+	int max_height=-1000;	
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		int height=p_entry->GetFloorHeight(centre_x,centre_z,p_cursor);
+		if (height > max_height)
+		{
+			max_height=height;
+		}	
+		p_entry=p_entry->mpNext;
+	}	
+	
+	if (max_height==-1000)
+	{
+		return 0;
+	}
+	return max_height;
+}
+
+int CClipboard::FindMaxFloorHeight(CCursor *p_cursor)
+{
+	// Get the map coords of the centre cell of the set of meta's
+	uint8 centre_x=(m_min_x+m_max_x)/2;
+	uint8 centre_z=(m_min_z+m_max_z)/2;
+
+	int max_floor_height=-1000;	
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		int height=p_entry->GetFloorHeight(centre_x,centre_z,p_cursor);
+		if (height > max_floor_height)
+		{
+			max_floor_height=height;
+		}	
+			
+		p_entry=p_entry->mpNext;
+	}	
+	
+	if (max_floor_height != -1000)
+	{
+		return max_floor_height;
+	}
+	return 0;	
+}
+
+void CClipboard::HighlightIntersectingMetas(CCursor *p_cursor, bool on)
+{
+	// Get the map coords of the centre cell of the set of meta's
+	uint8 centre_x=(m_min_x+m_max_x)/2;
+	uint8 centre_z=(m_min_z+m_max_z)/2;
+	
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		p_entry->HighlightIntersectingMetas(centre_x,centre_z,p_cursor,on);
+		p_entry=p_entry->mpNext;
+	}	
+}
+
+int CClipboard::GetWidth()
+{
+	return m_max_x-m_min_x+1;
+}
+
+int CClipboard::GetLength()
+{
+	return m_max_z-m_min_z+1;
+}
+
+void CClipboard::GetArea(CCursor *p_cursor, GridDims *p_area)
+{
+	// Get the map coords of the centre cell of the set of meta's
+	uint8 centre_x=(m_min_x+m_max_x)/2;
+	uint8 centre_z=(m_min_z+m_max_z)/2;
+	
+	int idx=m_min_x-centre_x;
+	int idz=m_min_z-centre_z;
+	int dx=0;
+	int dz=0;
+	
+	switch ( p_cursor->GetRotation() )
+	{
+		case Mth::ROT_0:
+			dx=idx;
+			dz=idz;
+			p_area->SetWHL(m_area.GetW(),1,m_area.GetL());
+			break;
+		case Mth::ROT_270:
+			dx=-idz;
+			dz=idx;
+			p_area->SetWHL(m_area.GetL(),1,m_area.GetW());
+			// Shift dx,dz so that they refer to the top-left point again
+			dx=dx-m_area.GetL()+1;
+			break;
+		case Mth::ROT_180:
+			dx=-idx;
+			dz=-idz;
+			p_area->SetWHL(m_area.GetW(),1,m_area.GetL());
+			// Shift dx,dz so that they refer to the top-left point again
+			dx=dx-m_area.GetW()+1;
+			dz=dz-m_area.GetL()+1;
+			break;
+		case Mth::ROT_90:
+			dx=idz;
+			dz=-idx;
+			p_area->SetWHL(m_area.GetL(),1,m_area.GetW());
+			// Shift dx,dz so that they refer to the top-left point again
+			dz=dz-m_area.GetW()+1;
+			break;
+		default:
+			break;
+	}		
+
+	GridDims cursor_position=p_cursor->GetPosition();
+	p_area->SetXYZ(cursor_position.GetX()+dx,0,cursor_position.GetZ()+dz);
+}
+
+Mth::Vector CClipboard::GetClipboardWorldPos()
+{
+	// Get the map coords of the centre cell of the set of meta's
+	uint8 centre_x=(m_min_x+m_max_x)/2;
+	uint8 centre_z=(m_min_z+m_max_z)/2;
+	
+	GridDims centre_cell;
+	centre_cell.SetXYZ(centre_x,m_area.GetY(),centre_z);
+	centre_cell.SetWHL(1,1,1);
+	
+	Mth::Vector corner_pos;
+	Mth::Vector area_dims;
+	corner_pos=Ed::CParkManager::Instance()->GridCoordinatesToWorld(centre_cell,&area_dims);
+	// corner_pos is the world pos of the top left corner of the centre cell
+	
+	Mth::Vector clipboard_pos=corner_pos;
+	clipboard_pos[X]+=area_dims[X]/2.0f;
+	clipboard_pos[Z]+=area_dims[Z]/2.0f;
+	// clipboard_pos is the world pos of the centre of the cell
+	
+	return clipboard_pos;
+}
+
+// This gets called when the CCursor is in paste mode, and needs to display the clipboard contents at
+// the cursor position.
+void CClipboard::ShowMetas(Mth::Vector pos, float rot, int clipboardY)
+{
+	// Get the map coords of the centre cell of the set of meta's
+	uint8 centre_x=(m_min_x+m_max_x)/2;
+	uint8 centre_z=(m_min_z+m_max_z)/2;
+	
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		p_entry->ShowMeta(centre_x,clipboardY,centre_z,pos,rot);
+		p_entry=p_entry->mpNext;
+	}	
+	
+	
+	Mth::Vector clipboard_pos=GetClipboardWorldPos();
+	
+	Obj::CEditedRail *p_rail=mp_rails;
+	while (p_rail)
+	{
+		p_rail->UpdateRailGeometry(clipboard_pos,pos,rot);
+		p_rail->UpdatePostGeometry(clipboard_pos,pos,rot);
+		p_rail=p_rail->mpNext;
+	}	
+}
+
+void CClipboard::DestroyMetas()
+{
+	CClipboardEntry *p_entry=mp_entries;
+	while (p_entry)
+	{
+		p_entry->DestroyMeta();
+		p_entry=p_entry->mpNext;
+	}	
+
+	Obj::CEditedRail *p_rail=mp_rails;
+	while (p_rail)
+	{
+		p_rail->DestroyPostGeometry();
+		p_rail->DestroyRailGeometry();
+		p_rail=p_rail->mpNext;
+	}	
+}
+
+} // namespace Ed
+
diff --git a/Code/Sk/ParkEditor2/clipboard.h b/Code/Sk/ParkEditor2/clipboard.h
new file mode 100644
index 0000000..62623cc
--- /dev/null
+++ b/Code/Sk/ParkEditor2/clipboard.h
@@ -0,0 +1,105 @@
+#ifndef __SK_PARKEDITOR2_CLIPBOARD_H
+#define __SK_PARKEDITOR2_CLIPBOARD_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __SYS_MEM_POOLABLE_H
+#include 
+#endif
+
+#ifndef __SK_PARKEDITOR2_PARKGEN_H
+#include 
+#endif
+
+namespace Obj
+{
+	class CEditedRail;
+}
+	
+namespace Ed
+{
+
+class CAbstractMetaPiece;
+
+enum
+{
+	MAX_CLIPBOARD_METAS=1000,
+	MAX_CLIPBOARDS=6,
+	CLIPBOARD_MAX_W=10,
+	CLIPBOARD_MAX_L=10,
+};
+
+class CClipboardEntry : public Mem::CPoolable
+{
+public:
+	CClipboardEntry();
+	~CClipboardEntry();
+
+	void CalculateMapCoords(GridDims *p_mapCoords, uint8 centre_x, uint8 centre_z, CCursor *p_cursor);
+	bool CanPaste(uint8 centre_x, uint8 centre_z, CCursor *p_cursor);
+	void Paste(uint8 centre_x, int raiseAmount, uint8 centre_z, CCursor *p_cursor);
+	int	 GetFloorHeight(uint8 centre_x, uint8 centre_z, CCursor *p_cursor);
+	void HighlightIntersectingMetas(uint8 centre_x, uint8 centre_z, CCursor *p_cursor, bool on);
+	void ShowMeta(uint8 centre_x, int raiseAmount, uint8 centre_z, Mth::Vector pos, float rot);
+	void DestroyMeta();
+	bool CreateGapFillerPieces();
+	
+	CAbstractMetaPiece *mpMeta;
+	uint8 mX;
+	sint8 mY;
+	uint8 mZ;
+	Mth::ERot90 m_rot;
+	uint8 mWidth;
+	uint8 mLength;
+	
+	// Used when displaying the cursor
+	CConcreteMetaPiece *mpConcreteMeta;
+	
+	CClipboardEntry *mpNext;
+};
+
+class CClipboard : public Mem::CPoolable
+{
+	GridDims m_area;
+	
+	uint8 m_min_x;
+	uint8 m_min_z;
+	uint8 m_max_x;
+	uint8 m_max_z;
+	void update_extents(uint8 x, uint8 z);
+	
+	CClipboardEntry *mp_entries;
+	void delete_entries();
+	
+	Obj::CEditedRail *mp_rails;
+	
+public:
+	CClipboard();
+	~CClipboard();
+
+	bool AddMeta(CConcreteMetaPiece *p_meta, int raiseAmount);
+	bool AddHeight(int x, int y, int z);
+	bool CopySelectionToClipboard(GridDims area);
+	bool IsEmpty();
+	bool Paste(CCursor *p_cursor);
+	int	 GetFloorHeight(CCursor *p_cursor);
+	int	 FindMaxFloorHeight(CCursor *p_cursor);
+	void HighlightIntersectingMetas(CCursor *p_cursor, bool on);
+	void ShowMetas(Mth::Vector pos, float rot, int clipboardY);
+	void DestroyMetas();
+	void GetArea(CCursor *p_cursor, GridDims *p_area);
+	Mth::Vector GetClipboardWorldPos();
+
+	int GetWidth();
+	int GetLength();
+	
+	CClipboard *mpNext;	
+};
+
+}
+
+#endif
+
+
diff --git a/Code/Sk/Scripting/cfuncs.cpp b/Code/Sk/Scripting/cfuncs.cpp
new file mode 100644
index 0000000..f67c464
--- /dev/null
+++ b/Code/Sk/Scripting/cfuncs.cpp
@@ -0,0 +1,14850 @@
+//****************************************************************************
+//* MODULE:         Sk/Scripting
+//* FILENAME:       CFuncs.cpp
+//* OWNER:          Kendall Harrison
+//* CREATION DATE:  9/14/2000
+//****************************************************************************
+
+// start autoduck documentation
+// @DOC cfuncs
+// @module cfuncs | None
+// @subindex Scripting Database
+// @index script | cfuncs
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+//#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGPS__
+//    #include 
+//    #include 
+    #include 
+	//#include 
+	#include 
+#endif
+
+#ifdef __PLAT_NGC__
+#include 
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+
+#include  
+#include  
+#include  
+#include  
+#include  
+#include  
+#include  
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+ 
+#include  
+#include  
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef TESTING_GUNSLINGER
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+//#include 
+//#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+
+#ifdef __PLAT_XBOX__
+#include 
+#endif
+
+#include 
+
+bool DumpFunctionTimesTrigger=false;
+
+void MemViewToggle();
+
+namespace Script
+{
+	extern void DumpLastStructs();
+}
+
+namespace Gfx
+{
+	void DebuggeryLines_CleanUp( void );		// just for debugging
+}
+
+#ifdef	__PLAT_NGPS__
+namespace NxPs2
+{
+#ifdef	__NOPT_ASSERT__
+extern uint32		gPassMask1; 		// 1<<6 | 1<<7  (0x40, 0x80)
+extern uint32		gPassMask0; 		// 1<<6 | 1<<7  (0x40, 0x80)
+#endif 
+}
+#endif // __PLAT_NGPS__
+
+
+// This is the level specific qb file.
+#if 0
+static uint32 s_level_specific_qb=0;
+#else
+// extended to have more than one
+#define	MAX_LEVEL_QBS  2
+static uint32 s_level_specific_qbs[MAX_LEVEL_QBS];
+#endif
+
+
+///////////////////////////////////////////////////////////////////
+// some temp debugging functions
+#define	DUMP_LEN	1024
+static void * dump_handle;	
+static char p_buffer[1024];
+static char *p_pos;
+
+int dumping_printfs = 0;		
+
+bool dump_open(const char *name)
+{	
+	dump_handle = File::Open( name, "wb");		
+	p_pos = p_buffer;
+
+	if (dump_handle != NULL)
+	{
+		dumping_printfs = 1;
+	}
+
+	return ( dump_handle != NULL );
+}
+
+void dump_flush()
+{
+	int len = p_pos - p_buffer;
+	if (len)
+	{
+		File::Write( p_buffer, len, 1, dump_handle );     		
+		p_pos = p_buffer;
+	}
+}
+
+// print to file, replacing lf with cr/lf, so I can read them in notepad
+void dump_printf(char *p)
+{
+	int len = strlen(p);
+	if ((int)(p_pos + len) > (int)(p_buffer + DUMP_LEN-16))
+	{
+		dump_flush();
+	}
+	
+	for (int i= 0; i*	s_network_test_task;
+static Tsk::Task< int >*		s_second_controller_check_task = NULL;
+
+// GJ:  viewer mode stuff...  will eventually be moved to CViewer class, hopefully.
+static int s_view_mode = 0;
+static uint32	s_last_broadcast_cheat = 0;
+														
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*
+static void		test_network_task( const Tsk::Task< Tmr::Time >& task )
+{
+	// Wait a second before testing so the user can see the status box
+	if(( Tmr::GetTime() - s_start_network_test_time ) < Tmr::Seconds( 1 ))
+	{
+		return;
+	}
+	
+	Net::Manager * net_man =  Net::Manager::Instance();
+	bool properly_setup;
+	 
+	properly_setup = net_man->NetworkEnvironmentSetup();
+	if( !properly_setup )
+	{
+		if( net_man->GetLastError() == Net::vRES_ERROR_DEVICE_NOT_HOT )
+		{
+			Script::CStruct* pStructure;
+			
+			pStructure = new Script::CStruct;
+			
+			pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_not_connected" ));
+			Script::RunScript( "create_net_startup_error_dialog", pStructure );
+
+			delete pStructure;
+		}
+		else if( ( net_man->GetLastError() == Net::vRES_ERROR_DEVICE_NOT_CONNECTED ) ||
+				 ( net_man->GetLastError() == Net::vRES_ERROR_UNKNOWN_DEVICE ))
+		{
+			Script::CStruct* pStructure;
+			
+			pStructure = new Script::CStruct;
+			
+			pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_not_detected" ));
+			Script::RunScript( "create_net_startup_error_dialog", pStructure );
+
+			delete pStructure;
+		}
+		else if( net_man->GetLastError() == Net::vRES_ERROR_DHCP )
+		{
+			Script::CStruct* pStructure;
+			
+			pStructure = new Script::CStruct;
+			
+			pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_dhcp_error" ));
+			Script::RunScript( "create_net_startup_error_dialog", pStructure );
+
+			delete pStructure;
+		}
+		else if( net_man->GetLastError() == Net::vRES_ERROR_DEVICE_CHANGED )
+		{
+			Script::CStruct* pStructure;
+			
+			pStructure = new Script::CStruct;
+			
+			pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_changed_device" ));
+			Script::RunScript( "create_net_startup_error_dialog", pStructure );
+
+			delete pStructure;
+		}
+		else
+		{
+			Script::CStruct* pStructure;
+			
+			pStructure = new Script::CStruct;
+			
+			pStructure->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_general_error" ));
+			Script::RunScript( "create_net_startup_error_dialog", pStructure );
+
+			delete pStructure;
+		}
+	}
+	else
+	{   
+		Script::CStruct* pParams;
+
+		Script::RunScript( "dialog_box_exit" );
+		//Script::RunScript( "launch_network_select_menu" );
+		
+        pParams = new Script::CStruct;
+
+		pParams->AddChecksum( "change_gamemode", Script::GenerateCRC( "change_gamemode_net" ));
+		Script::RunScript( "launch_select_skater_menu", pParams );
+		
+		delete pParams;
+	}
+
+	delete s_network_test_task;
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static	void	s_second_controller_check_code( const Tsk::Task< int > &task )
+{
+	// Get the second controller.
+	 SIO::Manager * pSioMan =  SIO::Manager::Instance();
+	SIO::Device *pFirstController=pSioMan->GetDeviceByIndex(0);
+	SIO::Device *pSecondController=pSioMan->GetDeviceByIndex(1);
+	
+#ifdef __PLAT_XBOX__
+	int num_plugged_devices;
+	SIO::Device *pThirdController=pSioMan->GetDeviceByIndex(2);
+	SIO::Device *pFourthController=pSioMan->GetDeviceByIndex(3);
+	
+	num_plugged_devices = 0;
+	if( pFirstController->IsPluggedIn())
+	{
+		num_plugged_devices++;
+	}
+	if( pSecondController->IsPluggedIn())
+	{
+		num_plugged_devices++;
+	}
+	if( pThirdController->IsPluggedIn())
+	{
+		num_plugged_devices++;
+	}
+	if( pFourthController->IsPluggedIn())
+	{
+		num_plugged_devices++;
+	}
+
+	if( num_plugged_devices >= 2 )
+	{
+		Script::RunScript( "enable_two_player_option" );
+	}	
+	else
+	{
+		Script::RunScript( "disable_two_player_option" );
+	}         
+#else
+	if (pSecondController->IsPluggedIn() && pFirstController->IsPluggedIn())
+	{
+		Script::RunScript( "enable_two_player_option" );
+	}	
+	else
+	{
+		Script::RunScript( "disable_two_player_option" );
+	}         
+#endif
+#ifdef __PLAT_XBOX__
+	DWORD result;
+	static Tmr::Time s_last_link_test;
+	
+	if(( Tmr::GetTime() - s_last_link_test ) >= Tmr::Seconds( 1 ))
+	{
+		result = XNetGetEthernetLinkStatus();
+        if(( result & XNET_ETHERNET_LINK_ACTIVE ) == 0 )
+		{
+			Script::RunScript( "disable_system_link_option" );
+		}
+		else
+		{
+			Script::RunScript( "enable_system_link_option" );
+		}
+		s_last_link_test = Tmr::GetTime();
+	}
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+static bool SetupNetworkDeviceDialogHandler( Front::EDialogBoxResult Result )
+{
+	
+	switch (Result)
+	{
+		case Front::DB_YES:
+		{
+			Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+			Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+			Front::DialogBox* dlg;
+			 
+			dlg = front->GetDialogBox();
+			dlg->GoBack();
+					
+			Front::MenuEvent event;
+			event.SetTypeAndTarget( Front::MenuEvent::vLINK, Script::GenerateCRC("net_network_setup_menu") );
+			menu_factory->LaunchEvent(&event);
+			
+			return false;
+		}
+		default:
+			break;
+	}		
+
+	return true;
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+bool ScriptDummyCommand(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// C++ version of the script command	
+bool SetActiveCamera(uint32 id, int viewport, bool move_to_current)
+{
+	Obj::CCompositeObject *p_obj = (Obj::CCompositeObject *)Obj::CCompositeObjectManager::Instance()->GetObjectByID(id);
+	if (!p_obj)
+	{
+		return false;
+	}
+	
+	Obj::CCameraComponent * p_cam_comp = GetCameraComponentFromObject(p_obj); 
+	
+	if (p_cam_comp)
+	{
+		// Move this camera to the current camera position.  Useful for seamlessly 
+		// transitioning between cameras
+		if (move_to_current)
+		{
+			Dbg_MsgAssert(Nx::CViewportManager::sGetActiveCamera(viewport), ("No active camera for viewport %d\n",viewport));
+			p_obj->m_matrix = Nx::CViewportManager::sGetActiveCamera(viewport)->GetMatrix();
+			p_obj->m_pos = Nx::CViewportManager::sGetActiveCamera(viewport)->GetPos();			
+			p_cam_comp->Update();  // bit dodgy...  force the camera to update itself from the parent object
+		}
+		
+		Nx::CViewportManager::sSetCamera( viewport, p_cam_comp->GetCamera() );
+	}
+	else
+	{
+		Dbg_Message("WARNING:  Trying to set camera from object with no camera component");
+	}
+
+#if 0
+// this is not needed any more, as the proxim trigger component has been deprecated in favour 
+// of using whatever camera directly	
+	// deactivate all proxim trigger components with this viewport on other objects with cameras and activate any proxim trigger on the new camera object
+	Obj::CProximTriggerComponent* p_prox_comp
+		= static_cast< Obj::CProximTriggerComponent* >(Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_PROXIMTRIGGER));
+	while (p_prox_comp)
+	{
+		Obj::CCameraComponent* p_peer_cam_comp = GetCameraComponentFromObject(p_prox_comp->GetObject());
+		
+		if (p_peer_cam_comp)
+		{
+			if (p_peer_cam_comp == p_cam_comp)
+			{
+				p_prox_comp->SetActive();
+				p_prox_comp->SetViewport(viewport);
+			}
+			else
+			{
+				if (p_prox_comp->GetViewport() == viewport)
+				{
+					p_prox_comp->SetInactive();
+				}
+			}
+		}
+		
+		p_prox_comp = static_cast< Obj::CProximTriggerComponent* >(p_prox_comp->GetNextSameType());
+	}
+
+	// K: If a different camera is switched to when the game is paused it could be that the geometry
+	// it is looking at was switched off by the previous camera. So refresh the proxim component
+	// to make sure it switches back on.
+	// (This fixes a bug when scrolling through the view-gaps menu, where geometry was switching off)
+	p_prox_comp=GetProximTriggerComponentFromObject(p_obj);
+	if (p_prox_comp)
+	{
+		p_prox_comp->ProximUpdate();
+	}	
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/* Waits for n CScript::Update calls.                             */
+/******************************************************************/
+
+// @script | Wait | Waits for a period of time, where the time can be specified in various units. 
+// For example:  Wait 1200       By default, the units are milliseconds, so 
+// this will wait for 1.2 seconds 
+// Wait 2.1 seconds     This will wait for 2.1 seconds. 
+// Wait 60 frames       This will wait for 60 sixtieths of a second, regardless of the framerate. 
+// Wait 60 gameframes       This will wait for exactly 60 logic calls, 
+// so the actual time it takes will depend on the current framerate. 
+// In all the above, the units do not have to be plural, so one could type 
+// ‘Wait 1 second’ instead of ‘Wait 1 seconds’
+// @parm float |  | number of units to wait
+// @flag seconds | measure time in seconds
+// @flag frames | measure time in frames
+// @flag gameframes | measure time in gameframes 
+bool ScriptWait(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	float Period=0.0f;
+	pParams->GetFloat(NONAME,&Period);
+	
+	if (pParams->ContainsFlag(0xd029f619/*"seconds"*/) || pParams->ContainsFlag(0x49e0ee96/*"second"*/))
+	{
+		pScript->WaitTime((int)(Period*1000.0f));
+		return true;
+	}	
+
+	if (pParams->ContainsFlag(0xfc37df05/*"gameframe"*/) || pParams->ContainsFlag(0xb99ae3d6/*"gameframes"*/) || pParams->ContainsFlag(0xdcd4ce73/*"game"*/))
+	{
+		pScript->Wait((int)Period);
+		return true;
+	}	
+
+	if (pParams->ContainsFlag(0x4a07c332/*"frame"*/) || pParams->ContainsFlag(0x19176c5/*"frames"*/))
+	{
+		pScript->WaitTime((int)(Period*1000.0f/60.0f)); // This should NOT be changed for PAL, as Tmr::GetTime accounts for PAL
+		return true;
+	}		
+
+	if (pParams->ContainsFlag(0xecaa3345/*"one_per_frame"*/))
+	{
+		pScript->WaitOnePerFrame((int)Period);
+		return true;
+	}	
+	
+
+	
+	pScript->WaitTime((int)Period);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*  Added for testing if statements, may be useful anyway though  */
+/******************************************************************/
+
+// @script | IsZero | added for testing if statements.  may be useful
+bool ScriptIsZero(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	
+	int Val=0;
+	pParams->GetInteger(NONAME,&Val);
+	if (Val==0)
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | CastToInteger | If a parameter with the passed name exists and is a float,
+// this will cast it down to an integer.
+// So for example, if x=3.141, after calling CastToInteger x, x will be 3.
+// @uparmopt name | The name of the parameter to be cast down
+bool ScriptCastToInteger(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 param_name=0;
+	if (!pParams->GetChecksum(NONAME,¶m_name))
+	{
+		return false;
+	}
+	
+	float v=0.0f;
+	if (!pScript->GetParams()->GetFloat(param_name,&v))
+	{
+		return false;
+	}
+
+	// Need to remove the parameter first, otherwise we will end up with both an integer
+	// and a float parameter with the same name. Components are only automatically replaced if
+	// the new component has the same name and type.
+	pScript->GetParams()->RemoveComponent(param_name);
+	pScript->GetParams()->AddInteger(param_name,(int)v);
+	return true;
+}
+
+// @script bool | StringToInteger | If a parameter with the passed name exists and is a string,
+// this will convert it to a integer.
+// So for example, if x="123", after calling StringToInteger x, x will be 123.
+// @uparmopt name | The name of the string parameter to be converted.
+bool ScriptStringToInteger(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 param_name=0;
+	if (!pParams->GetChecksum(NONAME,¶m_name))
+	{
+		return false;
+	}
+	
+	const char *p_string="";
+	if (!pScript->GetParams()->GetString(param_name,&p_string))
+	{
+		return false;
+	}
+
+	// Need to remove the parameter first, otherwise we will end up with both an integer
+	// and a string parameter with the same name. Components are only automatically replaced if
+	// the new component has the same name and type.
+	pScript->GetParams()->RemoveComponent(param_name);
+	
+	int v=0;
+	int len=strlen(p_string);
+	const char *p_ch=p_string;
+	for (int i=0; i='0' && *p_ch<='9',("\n%s\nNon-numeric char in '%s'",pScript->GetScriptInfo(),p_string));
+		v=v*10+*p_ch++-'0';
+	}
+	
+	pScript->GetParams()->AddInteger(param_name,(int)v);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | IntegerEquals | Returns true if the two values equal
+// @parmopt int | a | 0 | First value
+// @parmopt int | b | 0 | Second value
+bool ScriptIntegerEquals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int a = 0;
+	pParams->GetInteger( CRCD(0x174841bc,"a"), &a );
+	
+	int b = 0;
+	pParams->GetInteger( CRCD(0x8e411006,"b"), &b );
+	
+	return ( a == b );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | ChecksumEquals | Returns true if two checksums are equal
+// @parmopt name | a | 0 (checksum value) | First name
+// @parmopt name | b | 0 (checksum value) | Second name
+bool ScriptChecksumEquals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	uint32 a=0;
+	pParams->GetChecksum("a",&a);
+	uint32 b=0;
+	pParams->GetChecksum("b",&b);
+
+	return a==b;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | StringEquals | Returns true if two strings are equal
+// @parm string | a | First string
+// @parm string | b | Secnond string
+bool ScriptStringEquals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	const char* a;
+	pParams->GetText("a",&a,true);
+	const char* b;
+	pParams->GetText("b",&b,true);
+
+	return Script::GenerateCRC(a)==Script::GenerateCRC(b);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | ArrayContains | Returns true if the array contains an
+// element equal to the variable. 
+// Example:  ArrayContains array=some_int_array contains=4 
+// ArrayContains array=some_string_array contains="some_string"
+// @parm array | array | The array to search
+// @parm | contains | The element to search for.  Must match array type
+bool ScriptArrayContains(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	Script::CArray* pArray;
+	pParams->GetArray( "array", &pArray, true );
+
+	uint8 array_type = pArray->GetType();
+
+	for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+	{
+		switch ( array_type )
+		{
+			case ESYMBOLTYPE_INTEGER:
+			{
+				int desiredInteger;
+				pParams->GetInteger( "contains", &desiredInteger, true );
+				if ( desiredInteger == pArray->GetInt( i ) )
+					return true;
+				break;
+			}
+			case ESYMBOLTYPE_NAME:
+			{
+				uint32 desiredChecksum;
+				pParams->GetChecksum( "contains", &desiredChecksum, true );
+				if ( desiredChecksum == pArray->GetNameChecksum( i ) )
+					return true;
+				break;
+			}
+			case ESYMBOLTYPE_STRING:
+			{
+				const char* pDesiredString;
+				pParams->GetText( "contains", &pDesiredString, true );
+				// Changed to use stricmp instead of GenerateCRC because GenerateCRC sees \ and / as
+				// being the same character.
+				if (stricmp(pDesiredString, pArray->GetString( i )) == 0)
+					return true;
+				break;
+			}
+			default:
+				Dbg_MsgAssert( 0, ( "This data type is not yet supported by ArrayContains" ) );			
+				break;
+		}
+	}
+
+	// item was not found
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetArraySize | returns the number of elements in the specified structure
+// @parm | array_size | return value for array size
+bool ScriptGetArraySize(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::CArray* pArray;
+	
+	pParams->GetArray( NONAME, &pArray, Script::ASSERT );
+
+	// Add support for higher dimensional arrays
+	uint32 index = 0;
+	if(pParams->GetInteger(CRCD(0xba453b9,"index1"),(int*)&index, Script::NO_ASSERT))
+	{
+		pArray = pArray->GetArray(index);
+
+		if(pParams->GetInteger(CRCD(0x92ad0203,"index2"),(int*)&index, Script::NO_ASSERT))
+		{
+			pArray = pArray->GetArray(index);
+
+			if(pParams->GetInteger(CRCD(0xe5aa3295,"index3"),(int*)&index, Script::NO_ASSERT))
+			{
+				pArray = pArray->GetArray(index);
+			}
+		}
+	}
+
+	pScript->GetParams()->AddInteger( "array_size", pArray->GetSize() );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetArrayElement | Changes the value of an element of an array.
+// @parm name | ArrayName | The name of the array in the current script's parameters whose
+// value is to be changed.
+// @parm int | Index | The index of the array element whose value is to be changed.
+// @parm int | NewValue | The new value for the element. The type of this value must match the
+// type of the array, so it is not necessarily an integer. If the type does not match that of
+// the array it will assert.
+bool ScriptSetArrayElement(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// TODO: Most of this code should really be in the CArray class. Need to go over that
+	// class to make it easier to manipulate arrays safely. (resize, etc)
+	
+	// Get a pointer to the CArray that is to be modified.
+	// It will be in the script's parameters.
+	uint32 array_name=0;
+	pParams->GetChecksum("ArrayName",&array_name,Script::ASSERT);
+	Script::CArray *p_array=NULL;
+	pScript->GetParams()->GetArray(array_name,&p_array,Script::ASSERT);
+	
+	// Get the index of the element to be changed.
+	uint32 index=0;
+	pParams->GetInteger("Index",(int*)&index,Script::ASSERT);
+
+	// Find the new value which is to be written into the array.	
+	Script::CComponent *p_new_value=pParams->FindNamedComponentRecurse(CRCD(0x4b40630d,"NewValue"));
+	Dbg_MsgAssert(p_new_value,("\n%s\nMissing NewValue parameter in call to SetArrayElement",pScript->GetScriptInfo()));
+	// Resolve the componentn in case the new value is referring to some global.
+	Script::ResolveNameComponent(p_new_value);
+	
+	// Now make a copy of the component. This is because it may contain a pointer, eg a pointer to
+	// a struct, vector, string etc, and that entity may only exist temporarily.
+	// CopyComponent will create a new pointer which will be safe to delete later.
+	Script::CComponent *p_copy=new Script::CComponent;
+	Script::CopyComponent(p_copy,p_new_value);
+
+	// Check that the type of the new value is consistent with the type of the array.
+	if (p_array->GetType()==ESYMBOLTYPE_FLOAT && 
+		p_copy->mType==ESYMBOLTYPE_INTEGER)
+	{
+		// It's OK to write an integer value into an array of floats so don't assert in this case.
+	}
+	else
+	{
+		Dbg_MsgAssert(p_copy->mType==p_array->GetType(),("Array type of %s does not match the NewValue type of %s in call to SetArrayElement",Script::GetTypeName(p_array->GetType()),Script::GetTypeName(p_copy->mType)));
+	}
+	
+	// Write the new value into the array.
+	switch (p_array->GetType())
+	{
+	case ESYMBOLTYPE_INTEGER:
+		p_array->SetInteger(index,p_copy->mIntegerValue);
+		break;
+    case ESYMBOLTYPE_FLOAT:
+		if (p_copy->mType==ESYMBOLTYPE_FLOAT)
+		{
+			p_array->SetFloat(index,p_copy->mFloatValue);
+		}
+		else
+		{
+			Dbg_MsgAssert(p_copy->mType==ESYMBOLTYPE_INTEGER,("Eh ?"));
+			p_array->SetFloat(index,(float)p_copy->mIntegerValue);
+		}
+		break;
+    case ESYMBOLTYPE_NAME:
+		p_array->SetChecksum(index,p_copy->mChecksum);
+		break;
+	// Now for the types that are referenced by a pointer.
+	// In each of these cases, first the existing pointer in the array gets deleted.
+	// The the pointer to the array of pointers is got, and the new pointer written in.
+	// Cannot use the regular SetString, SetVector etc functions because they all assert if
+	// the existing pointer is not NULL, to catch memory leaks.
+    case ESYMBOLTYPE_STRING:
+	{
+		Script::DeleteString(p_array->GetString(index));
+		char **pp_strings=(char**)p_array->GetArrayPointer();
+		// Need to do a bounds check since not using SetString
+		Dbg_MsgAssert(indexGetSize(),("Bad index of %d sent to SetArrayElement, array size is %d",index,p_array->GetSize()));
+		pp_strings[index]=p_copy->mpString;
+		break;
+	}	
+    case ESYMBOLTYPE_LOCALSTRING:
+	{
+		Script::DeleteString(p_array->GetString(index));
+		char **pp_strings=(char**)p_array->GetArrayPointer();
+		Dbg_MsgAssert(indexGetSize(),("Bad index of %d sent to SetArrayElement, array size is %d",index,p_array->GetSize()));
+		pp_strings[index]=p_copy->mpLocalString;
+		break;
+	}	
+    case ESYMBOLTYPE_PAIR:
+	{
+		delete p_array->GetPair(index);
+		Script::CPair **pp_pairs=(Script::CPair**)p_array->GetArrayPointer();
+		Dbg_MsgAssert(indexGetSize(),("Bad index of %d sent to SetArrayElement, array size is %d",index,p_array->GetSize()));
+		pp_pairs[index]=p_copy->mpPair;
+		break;
+	}	
+    case ESYMBOLTYPE_VECTOR:
+	{
+		delete p_array->GetVector(index);
+		Script::CVector **pp_vectors=(Script::CVector**)p_array->GetArrayPointer();
+		Dbg_MsgAssert(indexGetSize(),("Bad index of %d sent to SetArrayElement, array size is %d",index,p_array->GetSize()));
+		pp_vectors[index]=p_copy->mpVector;
+		break;
+	}	
+    case ESYMBOLTYPE_STRUCTURE:
+	{
+		delete p_array->GetStructure(index);
+		Script::CStruct **pp_structs=(Script::CStruct**)p_array->GetArrayPointer();
+		Dbg_MsgAssert(indexGetSize(),("Bad index of %d sent to SetArrayElement, array size is %d",index,p_array->GetSize()));
+		pp_structs[index]=p_copy->mpStructure;
+		break;
+	}	
+    case ESYMBOLTYPE_ARRAY:
+	{
+		Script::CArray *p_old_array=p_array->GetArray(index);
+		Script::CleanUpArray(p_old_array);
+		delete p_old_array;
+		Script::CArray **pp_arrays=(Script::CArray**)p_array->GetArrayPointer();
+		Dbg_MsgAssert(indexGetSize(),("Bad index of %d sent to SetArrayElement, array size is %d",index,p_array->GetSize()));
+		pp_arrays[index]=p_copy->mpArray;
+		break;
+	}	
+	default:
+		Dbg_MsgAssert(0,("Unsupported array type of %s in SetArrayElement",Script::GetTypeName(p_array->GetType())));
+		break;		
+	}
+	
+	// Now delete the temporary component, but set any pointer in it to NULL first since the
+	// pointer has been given to the array.
+	p_copy->mUnion=0;
+	delete p_copy;
+	
+	return true;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Get2DarrayData | 
+bool ScriptGet2DArrayData(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 array_name=0;
+	pParams->GetChecksum(CRCD(0x8f61d602,"ArrayName"),&array_name,Script::ASSERT);
+	Script::CArray *p_array1=NULL;
+	if(!pScript->GetParams()->GetArray(array_name,&p_array1,Script::NO_ASSERT))
+	{
+		p_array1 = Script::GetArray( array_name,Script::ASSERT);
+	}
+
+	// Get the index of the element
+	uint32 index1=0;
+	pParams->GetInteger(CRCD(0xba453b9,"Index1"),(int*)&index1,Script::ASSERT);
+
+	// Get the index of the element
+	uint32 index2=0;
+	pParams->GetInteger(CRCD(0x92ad0203,"Index2"),(int*)&index2,Script::ASSERT);
+
+
+	Dbg_MsgAssert(index1GetSize(),("Bad index of %d sent to Get2DArrayData, array size is %d",index1,p_array1->GetSize()));
+	Script::CArray *p_array2=p_array1->GetArray(index1);
+
+	Dbg_MsgAssert(index2GetSize(),("Bad index of %d sent to Get2DArrayData, array size is %d",index2,p_array2->GetSize()));
+
+	switch (p_array2->GetType())
+	{
+	case ESYMBOLTYPE_INTEGER:
+		pScript->GetParams()->AddInteger( CRCD(0x6820459a,"val"), p_array2->GetInteger(index2) );
+		break;
+    case ESYMBOLTYPE_FLOAT:
+		pScript->GetParams()->AddFloat( CRCD(0x6820459a,"val"), p_array2->GetFloat(index2) );
+		break;
+    case ESYMBOLTYPE_NAME:
+		pScript->GetParams()->AddChecksum( CRCD(0x6820459a,"val"), p_array2->GetChecksum(index2) );
+		break;
+    case ESYMBOLTYPE_STRING:
+	{
+		pScript->GetParams()->AddString( CRCD(0x6820459a,"val"), p_array2->GetString(index2) );
+		break;
+	}	
+    case ESYMBOLTYPE_LOCALSTRING:
+	{
+		pScript->GetParams()->AddString( CRCD(0x6820459a,"val"), p_array2->GetString(index2) );
+		break;
+	}	
+    case ESYMBOLTYPE_PAIR:
+	{	
+		Script::CPair *p_pair = p_array2->GetPair(index2);
+		pScript->GetParams()->AddPair( CRCD(0x6820459a,"val"), p_pair->mX, p_pair->mY );
+		break;
+	}	
+    case ESYMBOLTYPE_VECTOR:
+	{
+		Script::CVector *p_vector = p_array2->GetVector(index2);
+		
+		pScript->GetParams()->AddVector( CRCD(0x6820459a,"val"), p_vector->mX, p_vector->mY, p_vector->mZ );
+		break;
+	}	
+    case ESYMBOLTYPE_STRUCTURE:
+	{
+		pScript->GetParams()->AddStructure( CRCD(0x6820459a,"val"), p_array2->GetStructure(index2) );
+		break;
+	}	
+    case ESYMBOLTYPE_ARRAY:
+	{
+		pScript->GetParams()->AddArray( CRCD(0x6820459a,"val"), p_array2->GetArray(index2) );
+		break;
+	}	
+	default:
+		Dbg_MsgAssert(0,("Unsupported array type of %s",Script::GetTypeName(p_array2->GetType())));
+		break;		
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Get3DarrayData | 
+bool ScriptGet3DArrayData(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 array_name=0;
+	pParams->GetChecksum(CRCD(0x8f61d602,"ArrayName"),&array_name,Script::ASSERT);
+	Script::CArray *p_array1=NULL;
+	if(!pScript->GetParams()->GetArray(array_name,&p_array1,Script::NO_ASSERT))
+	{
+		p_array1 = Script::GetArray( array_name,Script::ASSERT);
+	}
+
+	// Get the index of the element
+	uint32 index1=0;
+	pParams->GetInteger(CRCD(0xba453b9,"Index1"),(int*)&index1,Script::ASSERT);
+
+	// Get the index of the element
+	uint32 index2=0;
+	pParams->GetInteger(CRCD(0x92ad0203,"Index2"),(int*)&index2,Script::ASSERT);
+
+	// Get the index of the element
+	uint32 index3=0;
+	pParams->GetInteger(CRCD(0xe5aa3295,"Index3"),(int*)&index3,Script::ASSERT);
+
+	Dbg_MsgAssert(index1GetSize(),("Bad index of %d sent to Get3DArrayData, array size is %d",index1,p_array1->GetSize()));
+	Script::CArray *p_array2=p_array1->GetArray(index1);
+
+	Dbg_MsgAssert(index2GetSize(),("Bad index of %d sent to Get3DArrayData, array size is %d",index2,p_array2->GetSize()));
+	Script::CArray *p_array3=p_array2->GetArray(index2);
+
+	Dbg_MsgAssert(index3GetSize(),("Bad index of %d sent to Get3DArrayData, array size is %d",index3,p_array3->GetSize()));
+	
+
+	switch (p_array3->GetType())
+	{
+	case ESYMBOLTYPE_INTEGER:
+		pScript->GetParams()->AddInteger( CRCD(0x6820459a,"val"), p_array3->GetInteger(index3) );
+		break;
+    case ESYMBOLTYPE_FLOAT:
+		pScript->GetParams()->AddFloat(CRCD(0x6820459a,"val"), p_array3->GetFloat(index3) );
+		break;
+    case ESYMBOLTYPE_NAME:
+		pScript->GetParams()->AddChecksum( CRCD(0x6820459a,"val"), p_array3->GetChecksum(index3) );
+		break;
+    case ESYMBOLTYPE_STRING:
+	{
+		pScript->GetParams()->AddString( CRCD(0x6820459a,"val"), p_array3->GetString(index3) );
+		break;
+	}	
+    case ESYMBOLTYPE_LOCALSTRING:
+	{
+		pScript->GetParams()->AddString( CRCD(0x6820459a,"val"), p_array3->GetString(index3) );
+		break;
+	}	
+    case ESYMBOLTYPE_PAIR:
+	{	
+		Script::CPair *p_pair = p_array3->GetPair(index3);
+		pScript->GetParams()->AddPair( CRCD(0x6820459a,"val"), p_pair->mX, p_pair->mY );
+		break;
+	}	
+    case ESYMBOLTYPE_VECTOR:
+	{
+		Script::CVector *p_vector = p_array3->GetVector(index3);
+		
+		pScript->GetParams()->AddVector( CRCD(0x6820459a,"val"), p_vector->mX, p_vector->mY, p_vector->mZ );
+		break;
+	}	
+    case ESYMBOLTYPE_STRUCTURE:
+	{
+		pScript->GetParams()->AddStructure( CRCD(0x6820459a,"val"), p_array3->GetStructure(index3) );
+		break;
+	}	
+    case ESYMBOLTYPE_ARRAY:
+	{
+		pScript->GetParams()->AddArray( CRCD(0x6820459a,"val"), p_array3->GetArray(index3) );
+		break;
+	}	
+	default:
+		Dbg_MsgAssert(0,("Unsupported array type of %s",Script::GetTypeName(p_array3->GetType())));
+		break;		
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetNDarrayData | 
+bool ScriptGetNDArrayData(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 array_name=0;
+	pParams->GetChecksum(CRCD(0x8f61d602,"ArrayName"),&array_name,Script::ASSERT);
+	Script::CArray *p_array=NULL;
+	if(!pScript->GetParams()->GetArray(array_name,&p_array,Script::NO_ASSERT))
+	{
+		p_array = Script::GetArray( array_name,Script::ASSERT);
+	}
+
+	uint32 indices_name=CRCD(0xffc8e484,"Indices");
+	//pParams->GetChecksum("Indices",&indices_name,Script::ASSERT);
+	Script::CArray *p_indices=NULL;
+	pScript->GetParams()->GetArray(indices_name,&p_indices,Script::ASSERT);
+
+	uint32 i = 0;
+	uint32 index=0;
+	for(; i < p_indices->GetSize()-1; ++i)
+	{
+		// Get the index of the element
+		index = p_indices->GetInteger( i );
+		
+		p_array = p_array->GetArray(index);
+	}
+	index = p_indices->GetInteger( i );
+	
+	switch (p_array->GetType())
+	{
+	case ESYMBOLTYPE_INTEGER:
+		pScript->GetParams()->AddInteger( CRCD(0x6820459a,"val"), p_array->GetInteger(index) );
+		break;
+    case ESYMBOLTYPE_FLOAT:
+		pScript->GetParams()->AddFloat( CRCD(0x6820459a,"val"), p_array->GetFloat(index) );
+		break;
+    case ESYMBOLTYPE_NAME:
+		pScript->GetParams()->AddChecksum( CRCD(0x6820459a,"val"), p_array->GetChecksum(index) );
+		break;
+    case ESYMBOLTYPE_STRING:
+	{
+		pScript->GetParams()->AddString( CRCD(0x6820459a,"val"), p_array->GetString(index) );
+		break;
+	}	
+    case ESYMBOLTYPE_LOCALSTRING:
+	{
+		pScript->GetParams()->AddString( CRCD(0x6820459a,"val"), p_array->GetString(index) );
+		break;
+	}	
+    case ESYMBOLTYPE_PAIR:
+	{	
+		Script::CPair *p_pair = p_array->GetPair(index);
+		pScript->GetParams()->AddPair( CRCD(0x6820459a,"val"), p_pair->mX, p_pair->mY );
+		break;
+	}	
+    case ESYMBOLTYPE_VECTOR:
+	{
+		Script::CVector *p_vector = p_array->GetVector(index);
+		
+		pScript->GetParams()->AddVector( CRCD(0x6820459a,"val"), p_vector->mX, p_vector->mY, p_vector->mZ );
+		break;
+	}	
+    case ESYMBOLTYPE_STRUCTURE:
+	{
+		pScript->GetParams()->AddStructure( CRCD(0x6820459a,"val"), p_array->GetStructure(index) );
+		break;
+	}	
+    case ESYMBOLTYPE_ARRAY:
+	{
+		pScript->GetParams()->AddArray( CRCD(0x6820459a,"val"), p_array->GetArray(index) );
+		break;
+	}	
+	default:
+		Dbg_MsgAssert(0,("Unsupported array type of %s",Script::GetTypeName(p_array->GetType())));
+		break;		
+	}
+	return true;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AddParams | appends the given structure to the list of params
+// @uparmopt {} | structure to append to params
+bool ScriptAddParams(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AppendStructure(pParams);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RemoveComponent | removes a component from the script's list of params
+// @uparm name | name of component to remove
+bool ScriptRemoveComponent(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 name;
+	if ( !pParams->GetChecksum("name", &name, Script::NO_ASSERT) )
+	{
+		pParams->GetChecksum(NONAME, &name, Script::ASSERT);
+	}
+
+	pScript->GetParams()->RemoveComponent(name);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static uint32 s_get_bottom_up_free(bool includeFrag)
+{
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	Mem::Heap* p_heap = mem_man.GetHeap( CRCD(0xc7800b0,"BottomUpHeap") );
+	Mem::Region* p_region = p_heap->ParentRegion();
+
+	if (includeFrag)
+	{
+		return p_heap->mFreeMem.m_count + p_region->MemAvailable();
+	}
+	return p_region->MemAvailable();
+}
+
+// @script | SetScriptString | 
+bool ScriptSetScriptString(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int index=0;
+	pParams->GetInteger(CRCD(0x7f8c98fe,"index"),&index);
+	
+	const char *p_string="";
+	pParams->GetString(CRCD(0x61414d56,"string"),&p_string);
+	
+	Script::SetScriptString(index,p_string);
+	return true;
+}
+
+// @script | GetScriptString | 
+bool ScriptGetScriptString(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int index=0;
+	pParams->GetInteger(CRCD(0x7f8c98fe,"index"),&index);
+	
+	pScript->GetParams()->AddString(CRCD(0x61414d56,"string"),Script::GetScriptString(index));
+	return true;
+}
+	
+// @script | KenTest1 | old test of ken's
+bool ScriptKenTest1(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	static bool s_got_main_heap_free_edit=false;
+	static uint32 s_main_heap_free_edit=0;
+	
+	if (pParams->ContainsFlag(CRCD(0x1a4e0ef9,"Clear")))
+	{
+		s_got_main_heap_free_edit=false;
+		s_main_heap_free_edit=0;
+	}	
+	
+	if (!s_got_main_heap_free_edit)
+	{
+		s_main_heap_free_edit = s_get_bottom_up_free(true);
+		printf("Stored s_main_heap_free_edit\n");
+		s_got_main_heap_free_edit=true;
+		return true;
+	}	
+	
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+	uint32 main_heap_free_play = s_get_bottom_up_free(false);
+	
+	printf("main_heap_pad = %d\n", s_main_heap_free_edit - main_heap_free_play + 200000);
+#endif		// __NOPT_FINAL__
+	
+	return true;
+}
+
+// Used when creating default file names in the files menu.
+// It appends the passed suffix, but if the result will be a string longer than MaxChars
+// then it will move the suffix back so that the total number of chars is never more than
+// MaxChars.
+bool ScriptAppendSuffix(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	enum
+	{
+		BUFFER_SIZE=100,
+	};	
+	char p_new_text[BUFFER_SIZE];
+	
+	const char *p_text="";
+	pParams->GetString(CRCD(0xc4745838,"Text"), &p_text);
+	int original_text_length=strlen(p_text);
+	Dbg_MsgAssert(original_text_lengthGetInteger(CRCD(0xa6931595,"MaxChars"), &max_chars);
+	Dbg_MsgAssert(max_charsGetInteger(CRCD(0x4a4f7821,"suffix"),&suffix);
+	
+	char p_suffix[20];
+	sprintf(p_suffix, " %d",suffix);
+	int suffix_length=strlen(p_suffix);
+	Dbg_MsgAssert(suffix_length max_chars)
+	{
+		p_new_text[max_chars-suffix_length]=0;
+	}
+	strcat(p_new_text, p_suffix);
+	
+
+	uint32 new_text_name=0;
+	pParams->GetChecksum(CRCD(0x50060694,"NewTextName"),&new_text_name);
+	
+	pScript->GetParams()->AddString(new_text_name, p_new_text);
+	return true;
+}
+
+// @script | FindNearestRailPoint | 
+// Used by the rail editor for snapping the cursor to the nearest rail point.
+// Note that for this to work CParkGenerator::GenerateOutRailSet needs to have been called at some point first.
+// If it were not called, this would only find the nearest edited-rail point.
+bool ScriptFindNearestRailPoint(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mth::Vector pos;
+	pParams->GetVector(CRCD(0x7f261953,"Pos"),&pos,Script::ASSERT);
+	
+	float min_dist=150.0f;
+	pParams->GetFloat(CRCD(0x213395f1,"MinDist"),&min_dist);
+	float min_dist2=min_dist*min_dist;
+
+	uint32 ignore_rail_id=0;
+	pParams->GetChecksum(CRCD(0x288a6efe,"IgnoreRailId"),&ignore_rail_id);
+
+	int ignore_rail_point_index=-1;
+	pParams->GetInteger(CRCD(0xe4d305a7,"IgnoreRailPointIndex"),&ignore_rail_point_index);
+
+
+	// Find the nearest edited-rail point	
+	Mth::Vector edited_rail_nearest_point;
+	float edited_rail_nearest_dist2=100000000.0f;
+	uint32 rail_id=0;
+	int rail_point_index=0;
+	Obj::GetRailEditor()->FindNearestRailPoint(pos,
+											   &edited_rail_nearest_point, &edited_rail_nearest_dist2,
+											   &rail_id,&rail_point_index,
+											   ignore_rail_id, ignore_rail_point_index);
+	
+	// Then find the nearest rail point on the park editor level geometry.
+	// CParkGenerator::GenerateOutRailSet needs to have been called at some point first for this to
+	// find anything.
+	// Not calling GenerateOutRailSet here because it may take a while to execute.
+	Mth::Vector level_nearest_point;
+	float level_nearest_dist2=100000000.0f;
+	if (pParams->ContainsFlag(CRCD(0x9c247a94,"CheckLevelGeometry")))
+	{
+		Ed::CParkManager::sInstance()->GetGenerator()->FindNearestRailPoint(pos,&level_nearest_point,&level_nearest_dist2);
+	}	
+	
+	if (edited_rail_nearest_dist2 < min_dist2 ||
+		level_nearest_dist2 < min_dist2)
+	{
+		Mth::Vector nearest_point;
+		if (edited_rail_nearest_dist2 < level_nearest_dist2)
+		{
+			nearest_point=edited_rail_nearest_point;
+			
+			pScript->GetParams()->AddChecksum(CRCD(0xa61e7cd9,"rail_id"),rail_id);
+			pScript->GetParams()->AddInteger(CRCD(0xab3c14,"rail_point_index"),rail_point_index);
+		}
+		else
+		{
+			nearest_point=level_nearest_point;
+		}	
+		pScript->GetParams()->AddVector(CRCD(0xdf970a05,"NearestPos"),nearest_point[X],nearest_point[Y],nearest_point[Z]);
+		return true;
+	}
+	
+	return false;
+}
+
+bool ScriptGetTime(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_NGPS__
+/*
+	sceCdCLOCK rtc;
+	sceScfGetLocalTimefromRTC(&rtc);
+
+	pScript->GetParams()->AddInteger("Hours",rtc.hour);	
+	pScript->GetParams()->AddInteger("Minutes",rtc.minute);	
+	pScript->GetParams()->AddInteger("Seconds",rtc.second);	
+*/	
+#endif
+	return true;
+}
+
+bool ScriptGetDate(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_NGPS__
+/*
+	sceCdCLOCK rtc;
+	sceScfGetLocalTimefromRTC(&rtc);
+
+	pScript->GetParams()->AddInteger("Day",rtc.day);	
+	pScript->GetParams()->AddInteger("Month",rtc.month);	
+	pScript->GetParams()->AddInteger("Year",rtc.year);	
+*/	
+#endif
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetObNearestScreenCoord(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptRandomize(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mth::InitialRand(Tmr::ElapsedTime(0));
+	return true;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | KenTest2 | old test of Ken's
+bool ScriptKenTest2(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	//Ed::CParkEditor::Instance()->PlayModeGapManagerChecks();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static Tmr::Time sStartTime=0;
+bool ScriptResetTimer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	sStartTime=Tmr::ElapsedTime(0);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptTimeGreaterThan(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	float t=0.0f;
+	pParams->GetFloat(NONAME,&t);
+	
+	if (Tmr::ElapsedTime(sStartTime)>t*1000.0f)
+	{
+		return true;
+	}
+	return false;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetStartTime(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddInteger(CRCD(0xd16b61e6, "StartTime"), Tmr::GetTime());
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetElapsedTime(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int start_time;
+	pParams->GetInteger(CRCD(0xd16b61e6, "StartTime"), &start_time);
+	pScript->GetParams()->AddInteger(CRCD(0x3eb3566b, "ElapsedTime"), Tmr::ElapsedTime(start_time));
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RemoveParameter | Removes the parameter with the given name. If no such parameter
+// exists, it will do nothing.
+// @uparmopt name | The name of the parameter to be removed
+bool ScriptRemoveParameter(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 name=0;
+	pParams->GetChecksum(NONAME,&name);
+	
+	#ifdef __NOPT_ASSERT__
+	const char *p_foo=NULL;
+	Dbg_MsgAssert(!pParams->GetString(NONAME,&p_foo),("\n%s\nRemoveParameter requires a name, not a string",pScript->GetScriptInfo()));
+	#endif
+	
+	CStruct *p_params=pScript->GetParams();
+	Dbg_MsgAssert(p_params,("NULL p_params ??"));
+	p_params->RemoveComponent(name);
+	p_params->RemoveFlag(name);
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetRandomValue | Chooses a random number in a specified range. The returned value can 
+// be selected to be either an integer or a floating point value.
+// @parmopt name | name | 0 | The name to be given to the random value parameter. For example, 
+// name=foo will make the random number be assigned to a parameter called foo.
+// @parmopt float | a | 0.0 | The first value in the range.
+// @parmopt float | b | 0.0 | The second value in the range. The first value does not have to be smaller than the second.
+// @parmopt int | Resolution | 1000 | The number of possible floating point values that could be returned.
+// For example, if the range is a=1, b=2 and the resolution is set to 3, it will return either 1, 1.5 or 2
+// @flag Integer | Return an integer value in the given range, inclusive of the end values.
+// For example, if the range is a=1, b=3 it will return either 1, 2 or 3. Note that a and b need to be 
+// integers instead of floats in this case. 
+bool ScriptGetRandomValue(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 param_name=0;
+	pParams->GetChecksum(CRCD(0xa1dc81f9,"Name"),¶m_name);
+		
+	if (pParams->ContainsFlag(CRCD(0xa5f054f4,"Integer")))
+	{
+		int a=0;
+		int b=0;
+		pParams->GetInteger(CRCD(0x174841bc,"a"),&a);
+		pParams->GetInteger(CRCD(0x8e411006,"b"),&b);
+		if (a>b)
+		{
+			int t=a;
+			a=b;
+			b=t;
+		}	
+		pScript->GetParams()->AddInteger(param_name,a+Mth::Rnd(b-a+1));
+	}
+	else
+	{
+		int resolution=1000;
+		pParams->GetInteger(CRCD(0x22cf075,"Resolution"),&resolution);
+		Dbg_MsgAssert(resolution>=2,("Resolution needs to be at least 2"));
+
+		float a=0.0f;
+		float b=0.0f;
+		pParams->GetFloat(CRCD(0x174841bc,"a"),&a);
+		pParams->GetFloat(CRCD(0x8e411006,"b"),&b);
+		
+		pScript->GetParams()->AddFloat(param_name,a+Mth::Rnd(resolution)*(b-a)/(resolution-1));
+	}
+		
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetConfig | Allows various things in the config module to be changed at runtime.
+// @parmopt name | Language | | Can be one of English, French or German
+// @flag CD | Use CD for file loading
+// @flag NotCD | Use host for file loading
+// @flag GotExtraMemory | Switch on use of extra memory
+// @flag NoExtraMemory | Switch off use of extra memory
+// @flag Pal | Be in PAL mode
+// @flag NTSC | Be in NTSC mode
+// @parmopt int | FrameRate | | Frame rate to use, 50 or 60
+bool ScriptSetConfig(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 language_checksum=0;
+	pParams->GetChecksum(CRCD(0x2b248e4a,"Language"),&language_checksum);
+	
+	switch (language_checksum)
+	{
+	case 0xd37cfdff: // English
+		Config::gLanguage=Config::LANGUAGE_ENGLISH;
+		break;
+	case 0x508a31a1: // French
+		Config::gLanguage=Config::LANGUAGE_FRENCH;
+		break;
+	case 0x5dcbbf5e: // German
+		Config::gLanguage=Config::LANGUAGE_GERMAN;
+		break;
+	case 0xcbe70acb: // Spanish
+		Config::gLanguage=Config::LANGUAGE_FRENCH;
+		break;
+	case 0xa8469630: // Italian
+		Config::gLanguage=Config::LANGUAGE_GERMAN;
+		break;
+	default:
+		break;
+	}
+	
+	if (pParams->ContainsFlag(CRCD(0xba297025,"cd")))
+	{
+		if (!Config::gCD)
+		{
+			File::InstallFileSystem();
+		}
+		Config::gCD=true;
+	}	
+	if (pParams->ContainsFlag(CRCD(0x2b6d7b32,"notcd")))
+	{
+		if (Config::gCD)
+		{
+			File::UninstallFileSystem();
+		}
+		Config::gCD=false;
+	}	
+	
+	if (pParams->ContainsFlag(CRCD(0x38d23fd4,"GotExtraMemory")))
+	{
+		Config::gGotExtraMemory=true;
+	}
+	if (pParams->ContainsFlag(CRCD(0xaf11b50f,"NoExtraMemory")))		
+	{
+		Config::gGotExtraMemory=false;
+	}
+	
+	if (pParams->ContainsFlag(CRCD(0x6cad3928,"Pal")))		
+	{
+		Config::gDisplayType=Config::DISPLAY_TYPE_PAL;
+	}
+	if (pParams->ContainsFlag(CRCD(0x86137a88,"NTSC")))		
+	{
+		Config::gDisplayType=Config::DISPLAY_TYPE_NTSC;
+	}
+	
+	int frame_rate=60;
+	if (pParams->GetInteger(CRCD(0xee2bc65f,"FrameRate"),&frame_rate))
+	{
+		Config::gFPS=frame_rate;
+	}
+	
+	return true;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PrintConfig | Prints the current system configuration, whether it is CD, PAL, etc.
+bool ScriptPrintConfig(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	printf("\n");
+	printf("System Configuration:\n\n");
+	printf("Hardware       = %s\n",Config::GetHardwareName());
+	printf("Display Type   = %s, %d fps\n",Config::GetDisplayTypeName(),Config::FPS());
+	printf("Language       = %s\n",Config::GetLanguageName());
+	printf("Territory      = %s\n",Config::GetTerritoryName());
+	printf("CD             = %s\n",Config::CD()?"True":"False");
+	printf("GotExtraMemory = %s\n",Config::GotExtraMemory()?"True":"False");
+	printf("\n");
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | German | Returns true if the current language is German
+bool ScriptGerman(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Config::GetLanguage()==Config::LANGUAGE_GERMAN;
+}
+
+// @script | French | Returns true if the current language is French
+bool ScriptFrench(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Config::GetLanguage()==Config::LANGUAGE_FRENCH;
+}
+
+// @script | Spanish | Returns true if the current language is Spanish
+bool ScriptSpanish(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Config::GetLanguage()==Config::LANGUAGE_SPANISH;
+}
+
+// @script | Italian | Returns true if the current language is Italian
+bool ScriptItalian(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Config::GetLanguage()==Config::LANGUAGE_ITALIAN;
+}
+
+// @script | English | Returns true if the current language is English
+bool ScriptEnglish(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Config::GetLanguage()==Config::LANGUAGE_ENGLISH;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+#ifdef		__USE_PROFILER__			
+static Tmr::CPUCycles sStopwatchStartTime=0;
+#endif
+
+bool ScriptResetStopwatch(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef		__USE_PROFILER__			
+	sStopwatchStartTime=Tmr::GetTimeInCPUCycles();
+#endif	
+	return true;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptPrintStopwatchTime(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef		__USE_PROFILER__			
+	Tmr::CPUCycles diff=Tmr::GetTimeInCPUCycles()-sStopwatchStartTime; 
+	printf("Stopwatch time = %.3f seconds\n",(float)diff/150000000.0f);
+#endif	
+	return true;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CustomSkaterFilenameDefined | Doesn't appear to be supported
+bool ScriptCustomSkaterFilenameDefined(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	 Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile=pSkate->GetCurrentProfile();
+	Dbg_MsgAssert(pSkaterProfile,("NULL pSkaterProfile"));
+	
+	if (strcmp(pSkaterProfile->GetCASFileName(),"Unimplemented")==0)
+	{
+		return false;
+	}
+	else
+	{
+		return true;
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCustomSkaterFilename | Gets the filename for the current CAS
+bool ScriptGetCustomSkaterFilename(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile=pSkate->GetCurrentProfile();
+	Dbg_MsgAssert(pSkaterProfile,("NULL pSkaterProfile"));
+	
+	pScript->GetParams()->AddString(CRCD(0xf36c1878,"CASFileName"),pSkaterProfile->GetCASFileName());
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetCustomSkaterFilename | Sets the filename for the current CAS
+bool ScriptSetCustomSkaterFilename(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile=pSkate->GetCurrentProfile();
+	Dbg_MsgAssert(pSkaterProfile,("NULL pSkaterProfile"));
+
+	const char *p_file_name="Unimplemented";
+	pParams->GetString(NONAME,&p_file_name);
+	pSkaterProfile->SetCASFileName(p_file_name);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EditingPark | Returns true of we're in the park editor
+bool ScriptEditingPark(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#if 0
+	Ed::ParkEditor * pParkEd =  Ed::ParkEditor::Instance();
+	if (pParkEd->IsInitialized() && !pParkEd->GameGoingOrOutsideEditor())
+	{
+		// We're in the park editor, editing a park.
+		return true;
+	}
+#endif
+		
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define PARK_NAME_BUF_SIZE 128 
+static char spParkName[PARK_NAME_BUF_SIZE]={0};
+
+// @script | GetParkName | Gets the current park name
+bool ScriptGetParkName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddString(CRCD(0xcc23bddb,"ParkName"),spParkName);
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetParkName | Sets the current park name
+bool ScriptSetParkName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_name="";
+	pParams->GetString(NONAME,&p_name);
+	Dbg_MsgAssert(strlen(p_name)m_themeWasAutoSwitched;
+#endif
+	return false;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static bool MenuIsShown(uint32 id)
+{
+/*
+	 Front::MenuFactory * pMenuFactory =  Front::MenuFactory::Instance();
+	Front::MenuElement *pMenuElement=pMenuFactory->GetMenuElement(id,false); // false means don't assert
+
+	if (pMenuElement && pMenuElement->m_isShown)
+		return true;
+	else
+		return false;
+*/
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MenuIsShown | Checks array of names (pArray) is shown
+bool ScriptMenuIsShown(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// If there is an array of names, check them.		
+	Script::CArray *pArray=NULL;
+	if (pParams->GetArray(NONAME,&pArray))
+	{
+		Dbg_MsgAssert(pArray,("Eh? NULL pArray?"));
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			if (MenuIsShown(pArray->GetNameChecksum(i)))
+			{
+				return true;
+			}
+		}
+	}
+	
+	uint32 id=0;
+	if (pParams->GetChecksum(NONAME,&id))
+	{
+		if (MenuIsShown(id))
+		{
+			return true;
+		}	
+	}
+			
+	return false;			
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static bool MenuIsSelected(uint32 id)
+{
+	
+	/*
+	 Front::MenuFactory * pMenuFactory =  Front::MenuFactory::Instance();
+	Front::MenuElement *pMenuElement=pMenuFactory->GetMenuElement(id,false); // false means don't assert
+
+	if (pMenuElement && pMenuElement->m_isSelected)
+		return true;
+	else
+		return false;
+	*/
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ForEachIn | For each member of the array, run the specified script
+// @parm name | Do | The name of the script to run
+// @uparm [] | The array on which to run
+// @parmopt structure | Params | | any extra parameters that need to be
+// merged onto each of those in the array
+bool ScriptForEachIn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(CRCD(0x62ba3f6a,"Do"),&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("\n%s\nScript name missing in call to ForEachIn\n(eg, ForEachIn AnArray Do=AScript)",pScript->GetScriptInfo()));
+	
+	Script::CArray *pArray=NULL;
+	pParams->GetArray(NONAME,&pArray);
+	Dbg_MsgAssert(pArray,("\n%s\nMissing array parameter in call to ForEachIn",pScript->GetScriptInfo()));
+	Dbg_MsgAssert(pArray->GetSize()==0 || pArray->GetType()==ESYMBOLTYPE_STRUCTURE,("\n%s\nForEachIn only supports arrays of structures at the moment",pScript->GetScriptInfo()));
+	
+	
+	// Get any extra parameters that need to be merged onto each of those in the array.
+	Script::CStruct *pExtraParams=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"params"),&pExtraParams);
+	
+	if (pExtraParams)
+	{
+		// Used for holding the merged parameters.
+		Script::CStruct *pTotalParams=new Script::CStruct;
+		
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			pTotalParams->Clear();
+			
+			CStruct *p_array_struct=pArray->GetStructure(i);
+			
+			if (p_array_struct)
+			{
+				*pTotalParams+=*p_array_struct;
+			}	
+			
+			if (pExtraParams)
+			{
+				*pTotalParams+=*pExtraParams;
+			}	
+	
+			Script::RunScript(ScriptChecksum,pTotalParams,pScript->mpObject);
+		}
+		
+		// Delete the temporary structure.
+		delete pTotalParams;
+	}
+	else
+	{
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			Script::RunScript(ScriptChecksum,pArray->GetStructure(i),pScript->mpObject);
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SizeOf | 
+// Puts the size (num elements in) of the passed array into a parameter called ArraySize
+// @uparm [] | The array of which we're finding the size
+bool ScriptSizeOf(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::CArray *p_array=NULL;
+	pParams->GetArray(NONAME,&p_array);
+	
+	int size=0;
+	if (p_array)
+	{
+		size=p_array->GetSize();
+	}	
+	pScript->GetParams()->AddComponent(Script::GenerateCRC("ArraySize"),ESYMBOLTYPE_INTEGER,size);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetElement | Retrieves the array element pointed to by "Index"
+// and places it in the param called "Element"
+// @uparm [] | the array
+// @parmopt int | Index | 0 | The index value of the array element
+bool ScriptGetElement(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::CArray *p_array=NULL;
+	pParams->GetArray(NONAME,&p_array);
+
+	int index=0;
+	pParams->GetInteger("Index",&index);
+
+	pScript->GetParams()->RemoveComponent(0xbebfa1c6/*Element*/);	
+	
+	if (p_array)
+	{
+		if (index<0 || index>=(int)p_array->GetSize())
+		{
+		   #ifdef __NOPT_ASSERT__
+		   printf("\n%s\nWarning ! Index %d out of range. Array has %d elements\n",pScript->GetScriptInfo(),index,p_array->GetSize());
+		   #endif
+		   return false;
+		}
+		
+		switch (p_array->GetType())
+		{
+		case ESYMBOLTYPE_STRUCTURE:
+		{
+			Script::CStruct *p_new=new Script::CStruct;
+			p_new->AppendStructure(p_array->GetStructure(index));
+			pScript->GetParams()->AddComponent(0xbebfa1c6/*Element*/,ESYMBOLTYPE_STRUCTUREPOINTER,(int)p_new);
+			break;
+		}	
+		case ESYMBOLTYPE_ARRAY:
+			pScript->GetParams()->AddComponent(0xbebfa1c6/*Element*/,p_array->GetArray(index));
+			break;
+		case ESYMBOLTYPE_INTEGER:
+			pScript->GetParams()->AddComponent(0xbebfa1c6/*Element*/,ESYMBOLTYPE_INTEGER,p_array->GetInt(index));
+			break;
+			
+		default:
+			printf("GetElement currently only supports arrays of structures, arrays or integers ... \n(Requested type=%d)\n",p_array->GetType());
+			break;
+		}		
+	}
+	return true;
+}
+		   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetNextArrayElement | Increments the value in Index and uses it to retrieve that
+// element from the array, and puts it in Element
+// If no Index parameter exists, it will retrieve the first element of the array, and 
+// create Index and set it to zero.
+// If Index goes off the end of the array, it will remove the Index parameter and
+// any existing Element parameter, and return false.
+// Hence calling GetNextArrayElement repeatedly will cycle through the array elements.
+// @uparm [] | the array
+bool ScriptGetNextArrayElement(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::CArray *p_array=NULL;
+	pParams->GetArray(NONAME,&p_array);
+
+	int index=-1;
+	pScript->GetParams()->GetInteger(CRCD(0x7f8c98fe,"Index"),&index);
+
+	pScript->GetParams()->RemoveComponent(0x7f8c98fe/*Index*/);
+	pScript->GetParams()->RemoveComponent(0xbebfa1c6/*Element*/);
+	
+	if (!p_array)
+	{
+		return false;
+	}
+	
+	int size=p_array->GetSize();
+	
+	if (index<0)
+	{
+		// No index parameter existed, so set index to the first element of the array.
+		index=0;
+		
+		if (size==0)
+		{
+			// The array is empty though, so add nothing to the params.
+			return false;
+		}	
+	}
+	else
+	{
+		// An index existed already, so increment it.
+		++index;
+		if (index>=size)
+		{
+			// Run out of array elements!
+			return false;
+		}
+	}
+
+	pScript->GetParams()->AddInteger(CRCD(0x7f8c98fe,"Index"),index);
+
+	switch (p_array->GetType())
+	{
+	case ESYMBOLTYPE_STRUCTURE:
+		pScript->GetParams()->AddStructure(0xbebfa1c6/*Element*/,p_array->GetStructure(index));
+		break;
+	case ESYMBOLTYPE_ARRAY:
+		pScript->GetParams()->AddArray(0xbebfa1c6/*Element*/,p_array->GetArray(index));
+		break;
+	case ESYMBOLTYPE_INTEGER:
+		pScript->GetParams()->AddInteger(0xbebfa1c6/*Element*/,p_array->GetInteger(index));
+		break;
+	case ESYMBOLTYPE_FLOAT:
+		pScript->GetParams()->AddFloat(0xbebfa1c6/*Element*/,p_array->GetFloat(index));
+		break;
+	case ESYMBOLTYPE_NAME:
+		pScript->GetParams()->AddChecksum(0xbebfa1c6/*Element*/,p_array->GetChecksum(index));
+		break;
+	case ESYMBOLTYPE_STRING:
+		pScript->GetParams()->AddString(0xbebfa1c6/*Element*/,p_array->GetString(index));
+		break;
+	case ESYMBOLTYPE_LOCALSTRING:
+		pScript->GetParams()->AddLocalString(0xbebfa1c6/*Element*/,p_array->GetLocalString(index));
+		break;
+	default:
+		printf("GetNextArrayElement does not support arrays of type '%s'\n",GetTypeName(p_array->GetType()));
+		break;
+	}		
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetRandomArrayElement | Chooses a random element of the passed array, and
+// puts it in Element. It will put the index of the chosen element into a parameter called Index.
+// If the array is empty, it will remove any existing Element and Index parameter and return false.
+// @uparm [] | the array
+bool ScriptGetRandomArrayElement(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->RemoveComponent(CRCD(0xbebfa1c6,"Element"));
+	pScript->GetParams()->RemoveComponent(CRCD(0x7f8c98fe,"index"));
+
+	Script::CArray *p_array=NULL;
+	pParams->GetArray(NONAME,&p_array);
+
+	if (!p_array)
+	{
+		return false;
+	}
+	
+	int size=p_array->GetSize();
+	if (size==0)
+	{
+		return false;
+	}	
+
+	int index=Mth::Rnd(size);
+	// Wack in the index too cos it is handy to know.
+	pScript->GetParams()->AddInteger(CRCD(0x7f8c98fe,"index"),index);	
+		
+	switch (p_array->GetType())
+	{
+	case ESYMBOLTYPE_STRUCTURE:
+		pScript->GetParams()->AddStructure(CRCD(0xbebfa1c6,"Element"),p_array->GetStructure(index));
+		break;
+	case ESYMBOLTYPE_ARRAY:
+		pScript->GetParams()->AddArray(CRCD(0xbebfa1c6,"Element"),p_array->GetArray(index));
+		break;
+	case ESYMBOLTYPE_INTEGER:
+		pScript->GetParams()->AddInteger(CRCD(0xbebfa1c6,"Element"),p_array->GetInteger(index));
+		break;
+	case ESYMBOLTYPE_FLOAT:
+		pScript->GetParams()->AddFloat(CRCD(0xbebfa1c6,"Element"),p_array->GetFloat(index));
+		break;
+	case ESYMBOLTYPE_NAME:
+		pScript->GetParams()->AddChecksum(CRCD(0xbebfa1c6,"Element"),p_array->GetChecksum(index));
+		break;
+	default:
+		printf("GetRandomArrayElement does not support arrays of type '%s'\n",GetTypeName(p_array->GetType()));
+		break;
+	}		
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PermuteArray | Creates a new array equal to the passed array with its elements
+// randomly permuted. The new array is put into a parameter named using the passed NewArrayName.
+// @parm array | Array | The source array
+// @parm name | NewArrayName | The name to be given to the new array parameter.
+// @flag MakeNewFirstDifferFromOldLast | Makes the first element of the new array definitely
+// be different from the old last element. This is so that the old permutation can be
+// concatenated with the new and no consecutive elements will be the same.
+// Could be useful for playing songs in a random order with no repeats.
+// @flag PermuteSource | If this flag is specified then it will be the source array itself
+// that will get permuted, no new array will get created.
+// So the NewArrayName parameter is not required in this case, it will be ignored.
+bool ScriptPermuteArray(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::CArray *p_source_array=NULL;
+	pParams->GetArray("Array",&p_source_array);
+	
+	if (!p_source_array)
+	{
+		#ifdef __NOPT_ASSERT__
+		printf("\n%s\nPermuteArray requires an Array parameter\n",pScript->GetScriptInfo());
+		#endif
+		return false;
+	}
+
+	CArray *p_array_to_be_permuted=p_source_array;
+	bool create_new_array=!pParams->ContainsFlag("PermuteSource");
+	
+	uint32 new_array_name=0;
+	CArray *p_new_array=NULL;
+	
+	if (create_new_array)
+	{
+		pParams->GetChecksum("NewArrayName",&new_array_name);
+		if (!new_array_name)
+		{
+			#ifdef __NOPT_ASSERT__
+			printf("\n%s\nPermuteArray requires a NewArrayName parameter\n",pScript->GetScriptInfo());
+			#endif
+			return false;
+		}	
+		
+		p_new_array=new CArray;
+		CopyArray(p_new_array,p_source_array);
+		p_array_to_be_permuted=p_new_array;
+	}	
+	
+	
+	
+	int size=p_array_to_be_permuted->GetSize();
+	if (size)
+	{
+		uint32 *p_array_data=p_array_to_be_permuted->GetArrayPointer();
+		Dbg_MsgAssert(p_array_data,("NULL p_array_data ?"));
+		
+		int num_swaps=size*10;
+		
+		uint32 old_last=p_array_data[size-1];
+		
+		for (int i=0; iContainsFlag("MakeNewFirstDifferFromOldLast") && size>1 && p_array_data[0]==old_last)
+		{
+			int a=0;
+			int b=1+Mth::Rnd(size-1);
+			
+			uint32 temp=p_array_data[a];
+			p_array_data[a]=p_array_data[b];
+			p_array_data[b]=temp;
+		}
+	}
+	
+	if (create_new_array)
+	{
+		pScript->GetParams()->AddArrayPointer(new_array_name,p_new_array);
+	}
+		
+	return true;
+}
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetGammaValues( Script::CStruct *pParams, Script::CScript *pScript )
+{
+#	if defined( __PLAT_XBOX__ )
+	 Gfx::Manager * gfx_man =  Gfx::Manager::Instance();
+	float gamma_r;
+	float gamma_g;
+	float gamma_b;
+	gfx_man->GetGammaNormalized( &gamma_r, &gamma_g, &gamma_b );
+
+	pScript->GetParams()->RemoveComponent( "red" );
+	pScript->GetParams()->RemoveComponent( "green" );
+	pScript->GetParams()->RemoveComponent( "blue" );
+
+	pScript->GetParams()->AddInteger( "red", ( (int)( gamma_r * 100.0f ) ));
+	pScript->GetParams()->AddInteger( "green", ( (int)( gamma_g * 100.0f ) ));
+	pScript->GetParams()->AddInteger( "blue", ( (int)( gamma_b * 100.0f ) ));
+#	endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ApplyChangeGamma | This gets called whenever one of 
+// the color values in the gamma menu changes.
+// Called from script in gamemenu.q
+bool ScriptApplyChangeGamma( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+#	if defined( __PLAT_XBOX__ )	
+	// get the current values
+	 Gfx::Manager * gfx_man =  Gfx::Manager::Instance();
+	float gamma_r;
+	float gamma_g;
+	float gamma_b;
+	gfx_man->GetGammaNormalized( &gamma_r, &gamma_g, &gamma_b );
+
+	uint32 color;
+	pParams->GetChecksum( "color", &color, Script::ASSERT );
+
+	float change;
+	pParams->GetFloat( "change", &change, Script::ASSERT );
+	switch ( color )
+	{
+	case CRCC( 0x59ea070, "red" ):
+		if (( change > 0.0f && gamma_r < 1.0f ) || ( change < 0.0f && gamma_r > 0.0f ))
+			gamma_r += change;
+		break;
+	case CRCC( 0x2f6511de, "green" ):
+		if (( change > 0.0f && gamma_g < 1.0f ) || ( change < 0.0f && gamma_g > 0.0f ))
+			gamma_g += change;
+		break;
+	case CRCC( 0x61c9354b, "blue" ):
+		if (( change > 0.0f && gamma_b < 1.0f ) || ( change < 0.0f && gamma_b > 0.0f ))
+			gamma_b += change;
+		break;
+	default:
+		Dbg_MsgAssert( 0, ("ApplyChangeGamma called on unknown color") );
+	}
+	
+	gfx_man->SetGammaNormalized( gamma_r, gamma_g, gamma_b );
+	
+/*	// grab any new values from the params
+	int r, g, b;
+	if ( pParams->GetInteger( "red", &r, Script::NO_ASSERT ) )
+		gamma_r = (float)(r) / 100.0f;
+	if ( pParams->GetInteger( "green", &g, Script::NO_ASSERT ) )
+		gamma_g = (float)(g) / 100.0f;
+	if ( pParams->GetInteger( "blue", &b, Script::NO_ASSERT ) )
+		gamma_b = (float)(b) / 100.0f;
+*/
+
+#	endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GotParam | Checks if the script was passed the specified parameter
+// @uparm name | The name of the param to check for
+bool ScriptGotParam(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	uint32 FlagChecksum=0;
+	pParams->GetChecksum(NONAME,&FlagChecksum);
+	Dbg_MsgAssert(FlagChecksum,("GotParam command requires a flag name"));
+	
+	// Get the script's parameters, and see if they contain the aforementioned flag therein.
+	Script::CStruct *pScriptParams=pScript->GetParams();
+	Dbg_MsgAssert(pScriptParams,("NULL pScriptParams"));
+	return pScriptParams->ContainsComponentNamed(FlagChecksum);
+}
+
+Tmr::Time gButtonDebounceTime[PAD_NUMBUTTONS];  // PAD_NUMBUTTONS defined in skater.h for some reason (instead of some controller header)
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ControllerDeboune |
+// For example: 
+// Debounce X time = 2.0 
+// This will ignore the X button until either the time has elapsed,
+// or the X button has been released and subsequently re-pressed. 
+// This prevents the press of X from carrying over from one screen to 
+// the next, and removes the need to have a delay, whilst still allowing 
+// the user to actively X past things. 
+// @uparm name | the button
+// @parmopt float | time | 1.0 | 
+bool ScriptControllerDebounce( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	uint32 Checksum = 0;
+	float time=1.0f;
+	pParams->GetFloat(0x906b67ba,&time);	   // "time"
+	if ( pParams->GetChecksum( NONAME, &Checksum ) )
+	{
+		int Button = Inp::GetButtonIndex(Checksum);	
+		gButtonDebounceTime[Button] = (Tmr::Time)(Tmr::GetTime() + (time * 1000));
+	}
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void ResetDebounce( void )
+{
+	
+	int i;
+	for ( i = 0; i < PAD_NUMBUTTONS; i++ )
+	{
+		gButtonDebounceTime[ i ] = 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CheckButton(Inp::Handler< Mdl::FrontEnd >* pHandler, uint32 button)
+{
+	
+
+	if (!pHandler)
+	{
+		return false;
+	}	
+	
+	if (!pHandler->m_Input)
+	{
+		return false;
+	}	
+	
+	int whichButton = Inp::GetButtonIndex( button );
+	if ( gButtonDebounceTime[ whichButton ] > Tmr::GetTime( ) )
+	{
+		return ( false );
+	}
+	switch ( button )
+	{
+		case ( 0xbc6b118f ): // Up
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_UP] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0xe3006fc4 ): // Down
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_DOWN] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0x85981897 ): // Left
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_LEFT] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0x4b358aeb ): // Right
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_RIGHT] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0xb7231a95 ): // UpLeft
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_UP] )
+			{
+				if ( pHandler->m_Input->m_Event[Inp::Data::vA_LEFT] )
+				{
+					return ( true );
+				}
+			}
+			break;
+		case ( 0xa50950c5 ): // UpRight
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_UP] )
+			{
+				if ( pHandler->m_Input->m_Event[Inp::Data::vA_RIGHT] )
+				{
+					return ( true );
+				}
+			}
+			break;
+		case ( 0xd8847efa ): // DownLeft
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_DOWN] )
+			{
+				if ( pHandler->m_Input->m_Event[Inp::Data::vA_LEFT] )
+				{
+					return ( true );
+				}
+			}
+			break;
+		case ( 0x786b8b68 ): // DownRight
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_DOWN] )
+			{
+				if ( pHandler->m_Input->m_Event[Inp::Data::vA_RIGHT] )
+				{
+					return ( true );
+				}
+			}
+			break;
+		case ( 0x2b489a86 ): // Circle
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_CIRCLE] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0x321c9756 ): // Square
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_SQUARE] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0x7323e97c ): // X
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_X] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0x20689278 ): // Triangle
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_TRIANGLE] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0x26b0c991 ): // L1
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_L1] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0xbfb9982b ): // L2
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_L2] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0xf2f1f64e ): // R1
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_R1] )
+			{
+				return ( true );
+			}
+			break;
+		case ( 0x6bf8a7f4 ): // R2
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_R2] )
+			{
+				return ( true );
+			}
+			break;
+        case ( 0x767a45d7 ): // black
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_BLACK] )
+			{
+				return ( true );
+			}
+			break;
+        case ( 0xbd30325b ): // white
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_WHITE] )
+			{
+				return ( true );
+			}
+			break;
+        case ( 0x9d2d8850 ): // Z
+			if ( pHandler->m_Input->m_Event[Inp::Data::vA_Z] )
+			{
+				return ( true );
+			}
+			break;
+
+		default:
+			break;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ControllerPressed | returns true if the specified button is pressed
+// @uparm name | the button to check
+// @uparmopt 0 | controller index
+bool ScriptControllerPressed(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mdl::FrontEnd* pFront = Mdl::FrontEnd::Instance();
+
+	int controller;
+	uint32 button = 0;
+	if ( pParams->GetChecksum( NONAME, &button ) )
+	{
+		if ( pParams->GetInteger( NONAME, &controller, Script::NO_ASSERT ) )
+		{
+			Dbg_MsgAssert( controller < SIO::vMAX_DEVICES && controller >= 0, ( "ControllerPressed called with bad controller index" ) );
+			if ( CheckButton( pFront->GetInputHandler( controller ), button ) )
+				return true;
+			else
+				return false;
+		}
+		else
+		{
+			for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+			{
+				if ( CheckButton( pFront->GetInputHandler( i ), button ) )
+					return true;
+			}
+			return false;
+		}
+	}
+	return false;
+}
+
+// @script | GetAnalogueInfo | Reads the analogue sticks, ans returns the floats LeftX, LeftY, RightX and RightY.
+// These each range from -1 to 1.
+// @parmopt int | Controller | 0 | Controller index
+bool ScriptGetAnalogueInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mdl::FrontEnd* pFront = Mdl::FrontEnd::Instance();
+
+	int controller=0;
+	pParams->GetInteger( CRCD(0xb30d9965,"Controller"), &controller, Script::NO_ASSERT );
+	
+	Dbg_MsgAssert( controller < SIO::vMAX_DEVICES && controller >= 0, ( "ControllerPressed called with bad controller index" ) );
+	Inp::Handler< Mdl::FrontEnd > *p_handler=pFront->GetInputHandler( controller );
+	CControlPad pad;
+	pad.Update(p_handler->m_Input);
+	
+	pScript->GetParams()->AddFloat(CRCD(0x303067f1,"LeftX"),pad.m_scaled_leftX);
+	pScript->GetParams()->AddFloat(CRCD(0x47375767,"LeftY"),pad.m_scaled_leftY);
+	pScript->GetParams()->AddFloat(CRCD(0x694df774,"RightX"),pad.m_scaled_rightX);
+	pScript->GetParams()->AddFloat(CRCD(0x1e4ac7e2,"RightY"),pad.m_scaled_rightY);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSfxVolume | SetSfxVolume [100] 
+// Param: volumeLevel 
+// range: 0 to 100 (the main mixing volume, affecting all soundfx) 
+// default: 100  
+// Notes: the volume is set to 100 on startup, and can only be modified with this command
+// @uparmopt 100.0 | volume
+bool ScriptSetVolume( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	float Volume = 100.0f;
+    
+	pParams->GetFloat( NONAME, &Volume );
+
+	 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	sfx_manager->SetMainVolume( Volume );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetMusicVolume | 
+// @uparmopt 100.0 | volume level
+bool ScriptSetMusicVolume( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	float Volume = 100.0f;
+    
+	pParams->GetFloat( NONAME, &Volume );
+
+	Pcm::SetVolume( Volume );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetMusicStreamVolume | 
+// @uparmopt 100.0 | volume level
+bool ScriptSetMusicStreamVolume( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	float Volume = 100.0f;
+    
+	pParams->GetFloat( NONAME, &Volume );
+
+	Pcm::SetMusicStreamVolume( Volume );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StopMusic | 
+bool ScriptStopMusic( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	Pcm::StopMusic( );
+	return ( true );
+}
+
+#define CHECKSUM_FADE	0xb5aa125f // fade
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseMusic | 
+bool ScriptPauseMusic( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	int pausePlease = 1;
+	pParams->GetInteger( NONAME, &pausePlease );
+	Pcm::PauseMusic( pausePlease );
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseStream | 
+// @uparm 1 | 0 to unpause, 1 to pause
+bool ScriptPauseStream( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	int pausePlease = 1;
+	pParams->GetInteger( NONAME, &pausePlease );
+	Pcm::PauseStream( pausePlease );
+	return ( true );
+}
+		   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadMusicHeader | parameter is name of header/wad file (minus extension) 
+// Do this once on startup. 
+// Example:  LoadMusicHeader "music\music"
+// @uparm "string" | name of header/wad file
+bool ScriptLoadMusicHeader( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	const char *pName = NULL;
+	pParams->GetText( NONAME, &pName );
+	if ( pName )
+	{
+		// Mick:  Moved to BottomUpHeap, as it is a lot bigger now, and only loaded at startup
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+		bool done =  ( Pcm::LoadMusicHeader( pName ) );
+		Mem::Manager::sHandle().PopContext();
+		return done;
+		
+	}
+	return ( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadStreamHeader | 
+// @uparm "string" | name of header/wad file (minus extension)
+bool ScriptLoadStreamHeader( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	const char *pName = NULL;
+	pParams->GetText( NONAME, &pName );
+	if ( pName )
+	{
+		// Mick:  Moved to BottomUpHeap, as it is a lot bigger now, and only loaded at startup
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+		bool done = ( Pcm::LoadStreamHeader( pName ) );
+		Mem::Manager::sHandle().PopContext();
+		return done;
+	}
+	return ( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StreamIsAvailable | only available in the skateshop
+// @parm int | | which stream 
+bool ScriptStreamIsAvailable( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	if ( !skate_mod->GetGameMode()->IsFrontEnd() )
+	{
+		Dbg_MsgAssert( 0, ( "StreamIsAvailable script function is only valid in the skateshop." ) );
+	}
+	int whichStream;
+	if ( pParams->GetInteger( NONAME, &whichStream ) )
+	{
+		if ( whichStream >= Pcm::GetNumStreamsAvailable( ) )
+		{
+			Dbg_MsgAssert( 0, ( "\n%s\nAsking for StreamIsAvailable on stream %d, past valid range ( 0 to %d ).", pScript->GetScriptInfo( ),  whichStream, Pcm::GetNumStreamsAvailable( ) - 1 ) );
+			return ( false );
+		}
+		return ( Pcm::StreamAvailable( whichStream ) );
+	}
+	else
+	{
+		return ( Pcm::StreamAvailable( ) );
+	}
+}
+
+#define CHECKSUM_FLAG_PERM	0x389129e4	// "FLAG_PERM"
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AddMusicTrack | adds track to list - plays randomly during game 
+// Note: The songs that go into the data/music folder (and are added to the
+// CD in them 'music' directory) as well as the ambient tracks in data/ambient
+// (and in 'ambient' directory on CD) should have a filename of 8 characters
+// or less, as otherwise the names get converted upon building a CD
+// @uparmopt "string" | filename in quotes ( .wav file in data/music ) 
+// @uparmopt name | filename 
+// @flag flag_perm | to add the song to the permanent soundtrack list on startup...
+// otherwise track is a level-specific ambient type. 
+bool ScriptAddMusicTrack( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	const char *pTrackName = NULL;
+	const char *pTrackTitle = NULL;
+	// default to ambient tracks...
+	pParams->GetText( NONAME, &pTrackName );
+	pParams->GetText( "TrackTitle", &pTrackTitle );
+	int whichList = Pcm::TRACKLIST_LEVEL_SPECIFIC;
+	if ( pParams->ContainsFlag( CHECKSUM_FLAG_PERM ) )
+	{
+		whichList = Pcm::TRACKLIST_PERM;
+	}
+	
+	if ( pTrackName )
+	{
+		Pcm::AddTrackToPlaylist( pTrackName, whichList, pTrackTitle );
+	}
+	return ( true );
+}
+
+
+// @script | ChangeTrackState | Disables or enables one of the permanent music tracks.
+// @uparmopt 0 | The index of the track
+// @flag Off | Disable the specified track. If omitted, it will enable the track.
+bool ScriptChangeTrackState(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int track_num=0;
+	pParams->GetInteger(NONAME,&track_num);
+	
+	Pcm::SetTrackForbiddenStatus(track_num,pParams->ContainsFlag("Off"),Pcm::TRACKLIST_PERM);
+	return true;
+}
+	
+// @script | TrackEnabled | Returns true if the specified track in the permanent track list is enabled.
+// @uparmopt 0 | The index of the track
+bool ScriptTrackEnabled(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int track_num=0;
+	pParams->GetInteger(NONAME,&track_num);
+
+	if (Pcm::GetNumTracks(Pcm::TRACKLIST_PERM) == 0)
+	{
+		return false;
+	}	
+	
+	Dbg_MsgAssert( track_num>=0 && track_num < Pcm::GetNumTracks(Pcm::TRACKLIST_PERM),( "\n%s\nBad track number of %d sent to TrackEnabled, num tracks = %d", pScript->GetScriptInfo(), track_num, Pcm::GetNumTracks(Pcm::TRACKLIST_PERM) ));
+	
+	uint64 list1,list2;
+    Pcm::GetPlaylist(&list1, &list2);
+    
+    if (track_num<64)
+    {
+        if (list1 & (((uint64)1) << track_num))
+        {
+            return false;
+        }
+    }        
+    else
+    {
+        if (list2 & (((uint64)1) << (track_num-64)))
+        {
+            return false;
+        }
+    }
+	
+	return true;	
+}
+
+bool ScriptMusicIsPaused(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Pcm::MusicIsPaused();
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ClearMusicTrackList | 
+// @flag FLAG_PERM | optionally add FLAG_PERM to clear 
+// the permanent list, otherwise clear level specific list
+bool ScriptClearMusicTrackList( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	int whichList = Pcm::TRACKLIST_LEVEL_SPECIFIC;
+	if ( pParams->ContainsFlag( CHECKSUM_FLAG_PERM ) )
+	{
+		whichList = Pcm::TRACKLIST_PERM;
+	}
+	Pcm::ClearPlaylist( whichList );
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SkipMusicTrack | 
+// Play next track in the list ( or if in random mode, pick another random track )
+bool ScriptSkipMusicTrack( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	Pcm::SkipMusicTrack( );
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetMusicMode |
+// Play music or ambient sounds?
+// @uparm 1 | 1 for music on, 0 for music off/ ambience on
+bool ScriptSetMusicMode( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	int musicOn = 1;  // 1 for music on, 0 for music off/ambience on
+	pParams->GetInteger( NONAME, &musicOn );
+	Pcm::SetActiveTrackList( musicOn ? Pcm::TRACKLIST_PERM : Pcm::TRACKLIST_LEVEL_SPECIFIC );
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetRandomMode | 
+// Play tracks in Random mode or play tracks in order.
+bool ScriptSetRandomMode( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	int randomModeOn = 1;
+	pParams->GetInteger( NONAME, &randomModeOn );
+	Pcm::SetRandomMode( randomModeOn );
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetMusicLooping | 
+// Loops playing track.
+// @uparm 1 | 1 enables looping, 0 disables looping
+bool ScriptSetMusicLooping( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	int loopingModeOn = 1;
+	pParams->GetInteger( NONAME, &loopingModeOn );
+	Pcm::SetLoopingMode( loopingModeOn );
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCurrentTrack | 
+// Gets the index of the currently playing music track.
+bool ScriptGetCurrentTrack( Script::CStruct *pParams, Script::CScript *pScript )
+{
+    int track = Pcm::GetCurrentTrack();
+    pScript->GetParams()->AddInteger( "current_track", track );
+    return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlayMovie | Play a movie from data\movies on PC, or \movies on CD
+// @uparm "string" | movie name
+bool ScriptPlayMovie( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	const char *pMovieName;
+
+
+	if ( pParams->GetText( NONAME, &pMovieName, true ) )
+	{
+		Flx::PlayMovie( pMovieName );
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnloadAnim | 
+// @parm name | descChecksum | asset name
+// @parmopt name | refChecksum | 0 | skeleton name
+bool ScriptUnloadAnim( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// get the assman
+	Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+
+	const char* pAnimName = NULL;
+	pParams->GetText( "name", &pAnimName, Script::ASSERT );
+
+	Ass::CAsset* pAsset = ass_man->GetAssetNode( Script::GenerateCRC(pAnimName), false );
+
+	if ( pAsset )
+	{
+		ass_man->DestroyReferences( pAsset );
+		ass_man->UnloadAsset( pAsset );
+		
+//		Dbg_Message( "Unloaded asset %s", 
+//					 pAnimName );
+	}
+	else
+	{
+//		Dbg_Message( "Couldn't find asset %s to unload", 
+//					 pAnimName );
+	}
+
+
+#if 0
+	// can't do it this way, because it 
+	// deletes the reference,
+	// not the actual asset itself...
+
+	uint32 animName = 0;
+	uint32 refChecksum = 0;
+
+	pParams->GetChecksum( "descChecksum", &animName, Script::ASSERT );
+
+	//printf("Called UnloadAnim on %s\n",Script::FindChecksumName(animName));
+	
+	if ( !pParams->GetChecksum( "refChecksum", &refChecksum, Script::NO_ASSERT ) )
+	{
+		refChecksum = ass_man->GetReferenceChecksum();
+	}
+
+	// make sure the reference checksum is not 0,
+	// if a reference checksum was not specified	
+	Dbg_MsgAssert( refChecksum != 0, ( "No ref checksum" ) );
+
+	// kills the original asset
+	Ass::CAsset* pAsset = ass_man->GetAssetNode( refChecksum + animName, false );
+	if ( pAsset )
+	{
+		ass_man->DestroyReferences( pAsset );
+		ass_man->UnloadAsset( pAsset );
+#ifdef __NOPT_ASSERT__
+		Dbg_Message( "Unloaded asset %s %s", 
+					 Script::FindChecksumName( refChecksum ),
+					 Script::FindChecksumName( animName ) );
+#endif
+	}
+	else
+	{
+#ifdef __NOPT_ASSERT__
+		Dbg_Message( "Couldn't find asset %s %s to unload", 
+					 Script::FindChecksumName( refChecksum ),
+					 Script::FindChecksumName( animName ) );
+#endif
+	}
+#endif
+
+   return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadAsset | 
+// @parm string | name | asset name
+bool ScriptLoadAsset( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Ass::CAssMan * ass_man =  Ass::CAssMan::Instance();
+
+	const char* p_asset_filename;
+	if ( pParams->GetText( (const char*)"name", &p_asset_filename, false )
+		 || pParams->GetText( NONAME, &p_asset_filename, true ) )
+	{
+		// either (name="XXX") or ("XXX") is required
+	}
+
+#ifdef __USER_GARY__
+//	Dbg_Message( "*** Loading asset %s, perm=%d\n", p_asset_filename, ass_man->GetDefaultPermanent() );
+#endif
+
+//	if( !ass_man->LoadOrGetAsset( p_asset_filename, false, ass_man->GetDefaultPermanent() ) )
+	if( !ass_man->LoadOrGetAsset( p_asset_filename, false, false, ass_man->GetDefaultPermanent(), 0, NULL, pParams ) )
+	{
+		if ( Script::GetInteger( CRCD(0x25dc7904,"AssertOnMissingAssets") ) )
+		{
+			Dbg_MsgAssert( false,( "Could not load asset %s", p_asset_filename ));
+		}
+		else
+		{
+			return true;
+		}
+	}																			 
+
+	uint32 descChecksum = 0;
+	if ( pParams->GetChecksum( (const char*)"desc", &descChecksum, false )
+		 || pParams->GetChecksum( NONAME, &descChecksum, false ) )
+	{
+		// either (desc=nnn) or (nnn) is required
+		ass_man->AddRef(p_asset_filename, descChecksum, 0);	 
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadAnim | loads the specified animation
+// @parm string | name | the file name of the animation
+// @parm string | desc | description
+// @flag async | load animation asynchronously
+// returns true if a reference is generated.  false if a reference already exists
+bool ScriptLoadAnim( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Ass::CAssMan * ass_man =  Ass::CAssMan::Instance();
+
+	// an anim is just like an asset, except it has a reference added to it
+		
+	const char* p_asset_filename;
+	uint32 descChecksum = 0;
+	pParams->GetText( CRCD(0xa1dc81f9,"name"), &p_asset_filename );
+
+	pParams->GetChecksum( CRCD(0x4d20d794,"descChecksum"), &descChecksum, true );
+
+	bool async = false;
+	if( pParams->ContainsFlag( CRCD(0x90e07c79,"async") ))
+	{
+		async = true;
+	}
+
+	bool use_pip = false;
+	if( pParams->ContainsFlag( CRCD(0x23028e29,"use_pip") ))
+	{
+		use_pip = true;
+	}
+
+	return ass_man->LoadAnim( p_asset_filename, descChecksum, ass_man->GetReferenceChecksum(), async, use_pip );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadSkeleton | loads the specified skeleton
+// @parm string | name | the file name of the skeleton
+// @parm string | desc | description
+bool ScriptLoadSkeleton( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Ass::CAssMan* ass_man =  Ass::CAssMan::Instance();
+
+	const char* nameBuf;
+	pParams->GetText( (const char*)"name", &nameBuf, Script::ASSERT );
+
+	uint32 skeletonName;
+	pParams->GetChecksum( NONAME, &skeletonName, Script::ASSERT );
+	
+//    Gfx::CSkeletonData* pSkeletonData = (Gfx::CSkeletonData*)ass_man->LoadOrGetAsset(nameBuf, false, ass_man->GetDefaultPermanent());
+    Gfx::CSkeletonData* pSkeletonData = (Gfx::CSkeletonData*)ass_man->LoadOrGetAsset(nameBuf, false, false, ass_man->GetDefaultPermanent(), 0);
+	if ( !pSkeletonData )
+	{
+		Dbg_MsgAssert(0,("Failed to load skeleton %s",nameBuf));
+	}
+
+	// Now add the reference to it, if one was requested
+	// this gets combined with the "reference checksum"
+	if (!ass_man->AssetLoaded(skeletonName))
+	{
+		ass_man->AddRef(nameBuf, skeletonName, 0);	 
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAssManSetReferenceChecksum( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Ass::CAssMan * ass_man =  Ass::CAssMan::Instance();
+
+	uint32 checksum;
+	if ( !pParams->GetChecksum( NONAME, &checksum ) )
+	{
+		pParams->GetInteger( NONAME, (int*)&checksum, true );
+	}
+
+	ass_man->SetReferenceChecksum( checksum );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAssManSetDefaultPermanent( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Ass::CAssMan * ass_man =  Ass::CAssMan::Instance();
+
+	int permanent;
+	pParams->GetInteger( NONAME, &permanent, true );
+
+	ass_man->SetDefaultPermanent( permanent );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#define CHECKSUM_PITCH		0xd8604126	// "pitch"
+#define CHECKSUM_DROPOFF	0xff2020ec	// "dropoff"
+#define CHECKSUM_VOLUME		0xf6a36814	// "vol"
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadSound | 
+// @uparm "string" | The name of the sound file
+// @parmopt float | pitch | 100.0 | 
+// @parmopt float | volume | 100.0 | 
+// @parmopt float | dropoff | 0.0 | 
+// @flag PosUpdateWithDoppler | positional update with doppler
+// @flag NoReverb | reverb effect disabled for this sound
+bool ScriptLoadSound( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	const char *pSfxName;
+	if ( pParams->GetText( NONAME, &pSfxName, true ) )
+	{
+		float volume = 100.0f;
+		float pitch = 100.0f;
+		float dropoff = 0.0f;
+		int flags = 0;
+		pParams->GetFloat( CHECKSUM_PITCH, &pitch );
+		if ( pParams->GetFloat( CHECKSUM_DROPOFF, &dropoff ) )
+		{
+			dropoff = FEET_TO_INCHES( dropoff );
+		}
+		pParams->GetFloat( CHECKSUM_VOLUME, &volume );
+		
+		if ( pParams->ContainsFlag( CHECKSUM_FLAG_PERM ) )
+		{
+			flags |= SFX_FLAG_LOAD_PERM;
+		}
+		if ( pParams->ContainsFlag( 0xcf15c120 ) ) //"PosUpdateWithDoppler"
+		{
+			flags |= SFX_FLAG_POSITIONAL_UPDATE_WITH_DOPPLER;
+		}
+		else if ( pParams->ContainsFlag( CRCD(0x8b87176f,"NoReverb") ) )
+		{
+			flags |= SFX_FLAG_NO_REVERB;
+		}
+		//else if ( pParams->ContainsFlag( 0x14f9c4fe ) ) // "PosUpdate"
+		//{
+		//	flags |= SFX_FLAG_POSITIONAL_UPDATE;
+		//}
+			
+		 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+		if ( !sfx_manager->LoadSound( pSfxName, flags, dropoff, pitch, volume ) )
+		{
+//			printf( "sound %s couldn't load\n", pSfxName );
+		}
+	}
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlayStream | 
+// @uparm name | stream name
+// @parmopt float | volume | 100.0 | volume of both left and right channel in percent (overrides volL and volR)
+// @parmopt float | volL | 100.0 | volume of left channel
+// @parmopt float | volR | 100.0 | volume of right channel
+// @parmopt float | pitch | 100.0 | pitch value
+// @parmopt int | priority | 50 | priority; higher priority streams can stop lower priority streams.
+// @parmopt name | id | 0 | control ID (used instead of stream name for script control purposes)
+// @parmopt int | lipsync | 0 | set to 1 to load lipsync data
+bool ScriptPlayStream( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	uint32 streamNameChecksum = 0;
+	if ( pParams->GetChecksum( NONAME, &streamNameChecksum ) )
+	{
+		float volume = 100.0f;
+		float volL = 100.0f;
+		float volR = 100.0f;
+		float pitch = 100.0f;
+		int priority = STREAM_DEFAULT_PRIORITY;
+		uint32 id = 0;
+		int lipsync = 0;
+		pParams->GetFloat( "volL", &volL );
+		pParams->GetFloat( "volR", &volR );
+		if (pParams->GetFloat( CHECKSUM_VOLUME, &volume ))
+		{
+			volL = volume;
+			volR = volume;
+		}
+		pParams->GetFloat( CHECKSUM_PITCH, &pitch );
+		pParams->GetInteger( 0x9d5923d8, &priority ); // priority
+		pParams->GetChecksum( 0x40c698af, &id ); // id
+		pParams->GetInteger( CRCD(0xf5c3922b,"lipsync") , &lipsync );
+
+		if (lipsync == 1)
+		{
+			bool loaded = Pcm::CStreamFrameAmpManager::sLoadFrameAmp(streamNameChecksum);
+			if (!loaded)
+			{
+				Dbg_MsgAssert( 0,( "Couldn't load lipsync data for stream %s", Script::FindChecksumName(streamNameChecksum)));
+				return false;
+			}
+		}
+
+		Sfx::sVolume	vol( Sfx::VOLUME_TYPE_BASIC_2_CHANNEL );
+		vol.SetChannelVolume( 0, volL );
+		vol.SetChannelVolume( 1, volR );
+		Pcm::PlayStream( streamNameChecksum, &vol, pitch, priority, id );
+
+		return ( true );
+	}
+	Dbg_MsgAssert( 0,( "\n%s\nMust specify stream name.", pScript->GetScriptInfo( ) ));
+	return ( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StopStream | stops current stream from playing
+// @uparm name | stream name (or control ID, if one was supplied with PlayStream)
+bool ScriptStopStream( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	uint32 id;
+	if (pParams->GetChecksum( NONAME, &id ))
+	{
+		Pcm::StopStreamFromID( id );
+	}
+	else
+	{
+		Pcm::StopStreams( -1 );		// Stop all streams
+	}
+
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsStreamPlaying | Returns true if the named stream is currently playing
+// @uparm name | stream name (or control ID, if one was supplied with PlayStream)
+bool ScriptIsStreamPlaying( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 id = 0;
+    
+	if (pParams->GetChecksum( NONAME, &id ))
+	{
+		return Pcm::StreamPlaying(id);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("No stream specified"));
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetStreamParams | Set the volume or pitch of a playing stream
+// @uparm name | stream name (or control ID, if one was supplied with PlayStream)
+// @parmopt float | vol | 100.0 | volume of both left and right channel (overrides volL and volR)
+// @parmopt float | volL | 100.0 | volume of left channel
+// @parmopt float | volR | 100.0 | volume of right channel
+// @parmopt float | pitch | 100.0 | pitch
+bool ScriptSetStreamParams( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 id = 0;
+    
+	if (pParams->GetChecksum( NONAME, &id ))
+	{
+		float Volume;
+		float VolL;
+		float VolR;
+		float Pitch;
+
+		bool adjust_vol;
+
+		adjust_vol = pParams->GetFloat( CRCD(0x5e285a66,"VolL"), &VolL );
+		adjust_vol = pParams->GetFloat( CRCD(0xa4276705,"VolR"), &VolR ) || adjust_vol;
+		if (pParams->GetFloat( CRCD(0xf6a36814,"Vol"), &Volume ))
+		{
+			VolL = Volume;
+			VolR = Volume;
+			adjust_vol = true;
+		}
+
+		bool result = true;
+
+		if (adjust_vol)
+		{
+			Sfx::sVolume vol;
+			vol.SetSilent();
+			vol.SetChannelVolume( 0, VolL );
+			vol.SetChannelVolume( 1, VolR );
+
+			result = Pcm::SetStreamVolumeFromID(id, &vol);
+		}
+
+		if (pParams->GetFloat( CRCD(0xd8604126,"Pitch"), &Pitch ))
+		{
+			result = Pcm::SetStreamPitchFromID(id, Pitch) && result;
+		}
+
+		if (!result)
+		{
+			Dbg_MsgAssert(0, ("Can't find stream %s", Script::FindChecksumName(id)));
+		}
+
+		return result;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("No stream specified"));
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlayTrack | 
+// @uparm "string" | filename in quotes (no extension, file should be a 
+// .wav file in data/music)
+// @uparmopt 0 | The index of the track. For use if the name is not known.
+// @parmopt int | loop | 0 | continuously play the track if set to non-zero
+bool ScriptPlayTrack( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	const char *songName = NULL;
+	char p_calculated_track_name[MAX_TRACKNAME_STRING_LENGTH];
+    int track_num=999;
+	int loop = 0;
+	
+    if (!pParams->GetText( NONAME, &songName ))
+	{
+        pParams->GetInteger(NONAME,&track_num);
+        songName=Pcm::GetTrackName(track_num,Pcm::TRACKLIST_PERM);
+        
+		Dbg_MsgAssert(strlen("MUSIC\\VAG\\SONGS\\")+strlen(songName)GetInteger(CRCD(0x5ea0e211,"loop"), &loop);
+
+    if ( songName )
+	{
+		printf("PlayTrack songName ===================== %s\n", songName);
+        Pcm::StopMusic( );
+        Pcm::PlayTrack( songName, loop );
+        Pcm::SetCurrentTrack( track_num );
+	}
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlayMusicStream | Plays a stereo stream through the music channel
+// @uparm name | music stream name (in music.wad file)
+// @parmopt float | volume | -1.0 | volume in percent; negative value uses music volume
+bool ScriptPlayMusicStream( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	uint32 streamNameChecksum = 0;
+	if ( pParams->GetChecksum( NONAME, &streamNameChecksum ) )
+	{
+		float volume = -1.0f;
+		pParams->GetFloat( CHECKSUM_VOLUME, &volume );
+		Pcm::PlayMusicStream( streamNameChecksum, volume );
+		return ( true );
+	}
+	Dbg_MsgAssert( 0,( "\n%s\nMust specify stream name.", pScript->GetScriptInfo( ) ));
+	return ( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadStreamFrameAmp | Loads stream frame amplitude data for lipsync
+// @uparm name | stream name
+bool ScriptLoadStreamFrameAmp( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 streamNameChecksum = 0;
+	if ( pParams->GetChecksum( NONAME, &streamNameChecksum ) )
+	{
+		return Pcm::CStreamFrameAmpManager::sLoadFrameAmp(streamNameChecksum);
+	}
+	Dbg_MsgAssert( 0,( "\n%s\nMust specify stream name.", pScript->GetScriptInfo( ) ));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FreeStreamFrameAmp | Frees stream frame amplitude data for lipsync
+// @uparm name | stream name
+bool ScriptFreeStreamFrameAmp( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 streamNameChecksum = 0;
+	if ( pParams->GetChecksum( NONAME, &streamNameChecksum ) )
+	{
+		return Pcm::CStreamFrameAmpManager::sFreeFrameAmp(streamNameChecksum);
+	}
+	Dbg_MsgAssert( 0,( "\n%s\nMust specify stream name.", pScript->GetScriptInfo( ) ));
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlaySound | 
+// @uparm name | sound name
+// @parmopt float | vol | 100.0 | volume of both left and right channel (overrides volL and volR)
+// @parmopt float | volL | 100.0 | volume of left channel
+// @parmopt float | volR | 100.0 | volume of right channel
+// @parmopt float | pitch | 100.0 | 
+// @parmopt name | id | 0 | control ID (used instead of sound name for script control purposes)
+bool ScriptPlaySound(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 SoundChecksum = 0;
+	uint32 id = 0;
+	float Volume = 100.0f;
+	float VolL = 100.0f;
+	float VolR = 100.0f;
+	float Pitch = 100.0f;
+    
+	pParams->GetChecksum( NONAME, &SoundChecksum );
+	pParams->GetFloat( "VolL", &VolL );
+	pParams->GetFloat( "VolR", &VolR );
+	if (pParams->GetFloat( "Vol", &Volume ))
+	{
+		VolL = Volume;
+		VolR = Volume;
+	}
+	pParams->GetFloat( "Pitch", &Pitch );
+	pParams->GetChecksum( CRCD(0x40c698af,"id"), &id );
+
+	Dbg_MsgAssert( SoundChecksum,( "PlaySound requires the name of the sound" ));
+	
+	Sfx::sVolume vol;
+	vol.SetSilent();
+	vol.SetChannelVolume( 0, VolL );
+	vol.SetChannelVolume( 1, VolR );
+	
+	Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	if( !sfx_manager->PlaySound( SoundChecksum, &vol, Pitch, id ))
+	{
+#ifdef __NOPT_ASSERT__
+		printf( "failed to play sound %s", Script::FindChecksumName( SoundChecksum ) );
+#endif	
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StopSound | 
+// @uparm name | sound name (or control ID, if one was supplied with PlaySound)
+bool ScriptStopSound( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 id;
+	if (pParams->GetChecksum( NONAME, &id ))
+	{
+		Sfx::CSfxManager* sfx_manager = Sfx::CSfxManager::Instance();
+		bool result = sfx_manager->StopSound(id);
+
+		if (!result)
+		{
+			Dbg_Message( "Couldn't find sound %s to stop", Script::FindChecksumName( id ) );
+		}
+
+		return result;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("No sound specified"));
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StopAllSounds | 
+bool ScriptStopAllSounds(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Sfx::CSfxManager* sfx_manager = Sfx::CSfxManager::Instance();
+    sfx_manager->StopAllSounds();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSoundParams | Set the volume and pitch of a playing sound
+// @uparm name | sound name (or control ID, if one was supplied with PlaySound)
+// @parmopt float | vol | 100.0 | volume of both left and right channel (overrides volL and volR)
+// @parmopt float | volL | 100.0 | volume of left channel
+// @parmopt float | volR | 100.0 | volume of right channel
+// @parmopt float | pitch | 100.0 | pitch of sound
+bool ScriptSetSoundParams( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 id = 0;
+    
+	if (pParams->GetChecksum( NONAME, &id ))
+	{
+		float Volume;
+		float VolL = 100.0f;
+		float VolR = 100.0f;
+		float Pitch = 100.0f;
+
+		bool adjust_vol;
+
+		adjust_vol = pParams->GetFloat( CRCD(0x5e285a66,"VolL"), &VolL );
+		adjust_vol = pParams->GetFloat( CRCD(0xa4276705,"VolR"), &VolR ) || adjust_vol;
+		if (pParams->GetFloat( CRCD(0xf6a36814,"Vol"), &Volume ))
+		{
+			VolL = Volume;
+			VolR = Volume;
+			adjust_vol = true;
+		}
+
+		if (pParams->GetFloat( CRCD(0xd8604126,"Pitch"), &Pitch ) && !adjust_vol)
+		{
+			Dbg_MsgAssert(0, ("Must set volume when setting pitch"));
+
+		}
+
+		Sfx::sVolume vol;
+		vol.SetSilent();
+		vol.SetChannelVolume( 0, VolL );
+		vol.SetChannelVolume( 1, VolR );
+
+		Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+		if( !sfx_manager->SetSoundParams( id, &vol, Pitch ))
+		{
+	#ifdef __NOPT_ASSERT__
+			Dbg_MsgAssert(0, ("Can't find sound %s", Script::FindChecksumName(id)));
+	#endif // __NOPT_ASSERT__
+		}
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("No sound specified"));
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsSoundPlaying | Returns true if the named sound is currently playing
+// @uparm name | sound name (or control ID, if one was supplied with PlaySound)
+bool ScriptIsSoundPlaying( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 id = 0;
+    
+	if (pParams->GetChecksum( NONAME, &id ))
+	{
+		Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+		return sfx_manager->SoundIsPlaying(id);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("No sound specified"));
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSfxReverb | 1st param: reverbLevel 
+// default 0 (will turn reverb off) 
+// range: 0 to 100 percent 
+// 2nd param: mode (see possible values in reverb.q) 
+// default is 0 (first value in reverb.q)  
+// Notes: 2nd param (mode) only matters if 1st param (reverbLevel) is 
+// above 0. On a script Cleanup command, the global reverb is turned off.
+// @uparmopt 0.0 | Reverb (cannot be greater than 100)
+// @uparmopt name | reverb mode
+bool ScriptSetReverb( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	float reverb = 0.0f;
+	int reverbMode = 0;
+
+	// if the parameter isn't found, it should default to zero!	
+	pParams->GetFloat( NONAME, &reverb );
+	if ( reverb > 100.0f )
+	{
+		reverb = 100.0f;
+		Dbg_Message( "Reverb greater than 100.  Clipping." );
+	}
+	
+	// if the parameter isn't found, it should default to zero!	
+	pParams->GetInteger( CRCD(0x6835b854,"mode"), &reverbMode );
+	
+	 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	sfx_manager->SetReverb( reverb, reverbMode, pParams->ContainsFlag( "instant" ) );
+	
+	return ( true );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSfxDropoff | SetSfxDropoff [100] 
+// Param: dropoff distance (in feet) 
+// range: anything above zero 
+// default is 100  
+// Notes: only affects positional soundfx. Also, the global
+// dropoff distance is set back to 100 (or whatever you want me
+// to set it to) on a script Cleanup command. That way, if somebody 
+// forgets to call SetSfxDropoff at the beginning of their level, it 
+// will always be this default instead of staying at whatever level
+// the previously loaded level had it set to
+// @uparm 1.0 | Dropoff distance
+bool ScriptSetDropoff( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	float dropoffDist = DEFAULT_DROPOFF_DIST;
+
+	pParams->GetFloat( NONAME, &dropoffDist );
+	if ( dropoffDist <= 0.0f )
+	{
+		Dbg_MsgAssert( 0.0f,( "Can't have dropoff zero or less." ));
+		return ( false );
+	}
+	
+	 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	sfx_manager->SetDefaultDropoffDist( FEET_TO_INCHES( dropoffDist ) );
+	
+	return ( true );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+
+void	set_all_colors(Nx::CScene * p_scene, Image::RGBA rgb, uint32 light_group)
+{
+	Lst::HashTable< Nx::CSector > * p_sector_list = p_scene->GetSectorList();
+	
+
+	
+	if (p_sector_list)
+	{
+		p_sector_list->IterateStart();	
+		Nx::CSector *p_sector = p_sector_list->IterateNext();		
+		if (light_group)
+		{
+			while(p_sector)
+			{
+				if (p_sector->GetLightGroup() == light_group)
+				{
+					p_sector->SetColor(rgb);
+				}
+				p_sector = p_sector_list->IterateNext();
+			}
+		}
+		else
+		{
+			while(p_sector)
+			{
+				p_sector->SetColor(rgb);
+				p_sector = p_sector_list->IterateNext();
+			}
+		}
+	}
+}
+
+
+// Given a value, a target and a range, then scale the distance from the target by the amount
+// so, 
+float compress_value(float value, float target, float amount)
+{
+	float v =  target + (value-target) * amount;
+	if (v<0.0f) v = 0.0f;
+	if (v>255.0f) v = 255.0f;
+	return v;
+}
+
+void	compress_all_vertex_colors(Nx::CScene * p_scene, float target, float amount, uint32 light_group)
+{
+	Lst::HashTable< Nx::CSector > * p_sector_list = p_scene->GetSectorList();
+	
+	Image::RGBA rgb;
+	
+	
+	if (p_sector_list)
+	{
+		p_sector_list->IterateStart();	
+		Nx::CSector *p_sector = p_sector_list->IterateNext();		
+		while(p_sector)
+		{
+
+			Nx::CGeom 	*p_geom = p_sector->GetGeom();
+//			Dbg_MsgAssert(p_geom,("sector does not have geometry"))
+			
+			if (p_geom && (!light_group || light_group == p_sector->GetLightGroup()))
+			{
+				//
+				// Do renderable geometry
+				int verts = p_geom->GetNumRenderVerts();
+			
+				if (verts)
+				{
+	//				Mth::Vector	*p_verts = new Mth::Vector[verts];
+					Image::RGBA	*p_colors = new Image::RGBA[verts];
+	//				p_geom->GetRenderVerts(p_verts);
+					//p_source_geom->GetRenderColors(p_colors);
+					
+					// Note: getting the original render colors will allocate lots of memory
+					// to store all the origianl colors the firs tiem it is called
+					p_geom->GetOrigRenderColors(p_colors);
+				
+					Image::RGBA *p_color = p_colors;
+					if (target != 0.0 || amount != 0.0)
+					{
+						for (int i = 0; i < verts; i++)
+						{
+							rgb.a = p_color->a; 
+							rgb.r =  (uint8) compress_value((float) p_color->r,target,amount);						
+							rgb.g =  (uint8) compress_value((float) p_color->g,target,amount);						
+							rgb.b =  (uint8) compress_value((float) p_color->b,target,amount);						
+							*p_color = rgb;		//(*p_color & 0xff000000) | color;
+							p_color++;
+						} // end for
+					}
+			
+					// Set the colors on the actual new geom, not on the source...		
+					p_geom->SetRenderColors(p_colors);
+					
+	//				delete [] p_verts;
+					delete [] p_colors;
+				} // end if
+				else
+				{
+					// debuggery
+					//p_geom->SetColor(Image::RGBA(0,0,100,0));
+				}
+			}
+			p_sector = p_sector_list->IterateNext();
+		}
+	}
+}
+
+
+
+void	nudge_vertex_colors(Nx::CScene * p_scene, float amount)
+{
+	Lst::HashTable< Nx::CSector > * p_sector_list = p_scene->GetSectorList();
+	
+	Image::RGBA rgb;
+	
+	if (p_sector_list)
+	{
+		p_sector_list->IterateStart();	
+		Nx::CSector *p_sector = p_sector_list->IterateNext();		
+		while(p_sector)
+		{
+
+			Nx::CGeom 	*p_geom = p_sector->GetGeom();
+//			Dbg_MsgAssert(p_geom,("sector does not have geometry"))
+			
+			if (p_geom)
+			{
+				//
+				// Do renderable geometry
+				int verts = p_geom->GetNumRenderVerts();
+			
+				if (verts)
+				{
+					Mth::Vector	*p_verts = new Mth::Vector[verts];
+					Image::RGBA	*p_colors = new Image::RGBA[verts];
+					p_geom->GetRenderVerts(p_verts);
+					//p_source_geom->GetRenderColors(p_colors);
+					
+					// Note: getting the original render colors will allocate lots of memory
+					// to store all the origianl colors the firs tiem it is called
+					p_geom->GetOrigRenderColors(p_colors);
+				
+					Image::RGBA *p_color = p_colors;
+					Mth::Vector *p_vert = p_verts;
+					for (int i = 0; i < verts; i++)
+					{
+			//			CalculateVertexLighting(*p_vert, *p_color);
+						
+						rgb.a = p_color->a; 
+
+						Mth::Vector col;
+						col.Set(p_color->r, p_color->g, p_color->b);
+					
+											 
+						// create a pseudo random number based on the vertex position
+						// so non-common verts in the same position will get the same result
+						// which is the only way you can get a smooth result							 
+						int i_random =  (int)((*p_vert)[X]*16.0f) * (int)((*p_vert)[Y]*16.0f) ^ (int)((*p_vert)[Z]*16.0f);
+						float r1 = (float)(i_random & 0x7fff) / 32768.0f;
+						
+						float	nudge = 1.0f + ( 2.0f * r1 * amount ) - amount;
+						
+						col *= 	nudge;
+						if (col[X] < 0.0f) col[X] = 0.0f;
+						if (col[X] > 255.0f) col[X] = 255.0f;
+						if (col[Y] < 0.0f) col[Y] = 0.0f;
+						if (col[Y] > 255.0f) col[Y] = 255.0f;
+						if (col[Z] < 0.0f) col[Z] = 0.0f;
+						if (col[Z] > 255.0f) col[Z] = 255.0f;
+						
+						rgb.r = (uint8)col[X];
+						rgb.g = (uint8)col[Y];
+						rgb.b = (uint8)col[Z];
+						 
+						*p_color = rgb;		//(*p_color & 0xff000000) | color;
+						//*(uint32*)p_color = Mth::Rnd(32767);		//(*p_color & 0xff000000) | color;
+			
+						p_color++;
+						p_vert++;
+					} // end for
+			
+					// Set the colors on the actual new geom, not on the source...		
+					p_geom->SetRenderColors(p_colors);
+					
+					delete [] p_verts;
+					delete [] p_colors;
+				} // end if
+				else
+				{
+					// debuggery
+					//p_geom->SetColor(Image::RGBA(0,0,100,0));
+				}
+			}
+			p_sector = p_sector_list->IterateNext();
+		}
+	}
+}
+
+
+
+
+bool ScriptSetSceneColor( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	uint32 color;
+	
+	pParams->GetInteger( "color", (int*)&color, TRUE );
+	uint32 sky = color;
+	pParams->GetInteger( "sky", (int*)&sky );
+	uint32 light_group =0;
+	pParams->GetChecksum( "lightgroup", (uint32*)&light_group );	
+	Nx::CScene	*p_scene = Nx::CEngine::sGetMainScene();
+	
+	Image::RGBA rgb;
+	rgb.r = (uint8)((color)&0xff);
+	rgb.g = (uint8)((color>>8)&0xff);
+	rgb.b = (uint8)((color>>16)&0xff);
+	rgb.a = 0x80;
+	
+	Image::RGBA sky_rgb;
+	sky_rgb.r = (uint8)((sky)&0xff);
+	sky_rgb.g = (uint8)((sky>>8)&0xff);
+	sky_rgb.b = (uint8)((sky>>16)&0xff);
+	sky_rgb.a = 0x80;
+	
+	
+	if (p_scene)
+	{
+		// For main scene, just set the root color
+		#ifdef	__PLAT_NGPS__
+
+		// if doing a global or outdoor change to some color
+		// then reset all the colors first		
+		// to reset things that were set by individual SetObjectColor  commands
+		// like the Chris's best line goal in NJ
+		if 	(color != 0x808080 && (light_group == CRCD(0xe3714dc1,"outdoor") || light_group == 0))
+		{
+			Image::RGBA clear_rgb(0x80,0x80,0x80,0x80);
+			set_all_colors(p_scene, clear_rgb, light_group);						
+		}
+		
+		// if not the outdoor or default group, or if we are turing lighting off with 0x808080
+		// then set it on all nodes
+		// otherwise we just set the root color
+		if ((light_group != CRCD(0xe3714dc1,"outdoor") && light_group != 0 ) || color == 0x808080 
+			/*|| p_scene->GetID() ==CRCD(0xefc68238,"cloned")*/)
+		#endif
+		{
+			set_all_colors(p_scene, rgb, light_group);
+		}
+		
+		// Set the root color of the scene.  A PS2 specific optimization
+		// which can be ignored by other platforms.		
+		if (light_group == CRCD(0xe3714dc1,"outdoor") || light_group == 0)
+		{
+			p_scene->SetMajorityColor(rgb);
+		}
+	}
+	
+	// K: If the park editor shell scene exists, update its colours too.
+	p_scene=Nx::CEngine::sGetScene(Ed::CParkManager::Instance()->GetShellSceneID());
+	if (p_scene)
+	{
+		// Note, cut and paste from above
+		#ifdef	__PLAT_NGPS__
+		// if not the outdoor or default group, or if we are turing lighting off with 0x808080
+		// then set it on all nodes
+		// otherwise we just set the root color
+		if ((light_group != CRCD(0xe3714dc1,"outdoor") && light_group != 0 ) || color == 0x808080)
+		#endif
+		{
+			set_all_colors(p_scene, rgb, light_group);
+		}
+		
+		// Set the root color of the scene.  A PS2 specific optimization
+		// which can be ignored by other platforms.		
+		if (light_group == CRCD(0xe3714dc1,"outdoor") || light_group == 0)
+		{
+			p_scene->SetMajorityColor(rgb);
+		}
+	}
+
+	
+	p_scene = Nx::CEngine::sGetSkyScene();
+	
+	if (p_scene)
+	{
+		set_all_colors(p_scene, sky_rgb, light_group);
+	}
+	
+	// we also need to set the color of all the level objects
+	// assume they are all "outdoors"
+	if (light_group == CRCD(0xe3714dc1,"outdoor") || light_group == 0)
+	{
+		Obj::CModelComponent *p_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_MODEL ));
+		while( p_component )
+		{
+			p_component->ApplySceneLighting(rgb);
+			p_component = static_cast( p_component->GetNextSameType());
+		}
+	}	
+	
+	return true;
+}
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool ScriptCompressVC( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	float target = 0.0f;
+	pParams->GetFloat( "target", (float*)&target, TRUE );
+	float amount = 0.0f;
+	pParams->GetFloat( "percent", (float*)&amount );
+	amount = (100.0f - amount)/100.0f;
+	uint32 light_group =0;
+	pParams->GetChecksum( "lightgroup", (uint32*)&light_group );	
+
+	Nx::CScene	*p_scene = Nx::CEngine::sGetMainScene();
+	if (p_scene)
+	{
+		compress_all_vertex_colors(p_scene, target, amount, light_group);
+	}
+	p_scene = Nx::CEngine::sGetSkyScene();
+	if (p_scene)
+	{
+		compress_all_vertex_colors(p_scene, target, amount, light_group);
+	}
+	return true;
+}
+
+// @script bool | FakeLights | Adjust lighting for a node-based SceneLight.
+// @parm name | id | The name of the node, if only one needs adjusting.
+// @parm name | Prefix | The prefix of a set of nodes, if lots need adjusting.
+// @parmopt integer | Time | 0 | The time period, in frames, over which the execution of the command
+// will be spread so as not to use up too much CPU time in one frame.
+// @parmopt float | inner_radius | none | The new radius of the light in inches
+// @parmopt float | outer_radius | none | The new radius of the light in inches
+// @parmopt float | percent | none | The new intensity percentage
+// @parmopt integer | red | none | The new red component of the light color (also green, blue)
+bool ScriptFakeLights( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// If an id is supplied, we want to apply the effect to just one light.
+	uint32 id = 0; 
+	if( pParams->GetChecksum( CRCD(0x40c698af,"id"), &id ))
+	{
+		Nx::CLightManager::sFakeLight(id,pParams);
+		return true;
+	}
+
+	// Get the period in frames over which the command will execute.
+	int time_period=0;
+	pParams->GetInteger(CRCD(0x906b67ba,"time"),&time_period);
+	
+	// K: Added this bit to allow a set of nodes with a given prefix to be adjusted.
+	uint32 prefix=0;	
+    if ( pParams->GetChecksumOrStringChecksum( CRCD(0x6c4e7971,"Prefix"), &prefix ) )
+	{
+		uint16 num_nodes = 0;
+		const uint16 *p_nodes = SkateScript::GetPrefixedNodes( prefix, &num_nodes );
+		
+		Nx::CLightManager::sFakeLights(p_nodes,num_nodes,time_period,pParams);
+		return true;
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool ScriptNudgeVC( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	float amount = 0.0f;
+	pParams->GetFloat( "percent", (float*)&amount, true );
+	amount = (amount)/100.0f;
+	Nx::CScene	*p_scene = Nx::CEngine::sGetMainScene();
+	if (p_scene)
+	{
+		nudge_vertex_colors(p_scene,amount);
+	}
+	return true;
+}
+
+// Center the viewer camera on the scene
+// (Previously this would center the current camera on the scene, which had unintended results)
+void	center_camera_on_scene(Nx::CScene * p_scene, float scale, float x_rot, float y_rot, float z_rot, uint32 id=0)
+{
+
+	// get a bounding box of the world, and center the camera on that	
+	Mth::CBBox bbox;
+	
+	Lst::HashTable< Nx::CSector > * p_sector_list = p_scene->GetSectorList();
+	if (p_sector_list)
+	{
+		p_sector_list->IterateStart();	
+		Nx::CSector *p_sector = p_sector_list->IterateNext();		
+		while(p_sector)
+		{
+
+			if ((!id || id == p_sector->GetChecksum()) && p_sector->IsActive())
+			{
+				
+				Nx::CGeom 	*p_geom = p_sector->GetGeom();
+				if (p_geom)
+				{
+					//
+					// Do renderable geometry
+					int verts = p_geom->GetNumRenderVerts();
+					if (verts)
+					{
+						Mth::Vector	*p_verts = new Mth::Vector[verts];
+						p_geom->GetRenderVerts(p_verts);
+						Mth::Vector *p_vert = p_verts;
+						for (int i = 0; i < verts; i++)
+						{
+							bbox.AddPoint(*p_vert);
+							//printf ("%f,%f,%f,%f\n",p_vert[0][X],p_vert[0][Y],p_vert[0][Z],p_vert[0][W]);
+//							if (i>0)
+//							{
+//								Gfx::AddDebugLine(p_vert[0],p_vert[-1],0xffffff,100);
+//							}
+							p_vert++;
+						} // end for
+				
+						delete [] p_verts;
+					} // end if
+				}
+			}
+			p_sector = p_sector_list->IterateNext();
+		}
+	}
+
+
+	
+	
+	// Move the camera to the object, and move it back by the width of the object (min 100 ft)
+//    Gfx::Camera* pCam = Nx::CViewportManager::sGetCamera( 0 );
+
+// Mick:  Now we attempt to get the viewer camera object
+
+	Obj::CCompositeObject *p_obj = (Obj::CCompositeObject *)Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0xeb17151b,"viewer_cam"));
+	if (!p_obj)
+	{
+		return;
+	}
+	
+								 
+	Dbg_MsgAssert(GetCameraComponentFromObject(p_obj),("Viewer Camera Object Missing camera component"));
+	
+	
+	Mth::Vector min = bbox.GetMin();
+	Mth::Vector max = bbox.GetMax();
+	Mth::Vector mid = (min + max)/2.0f;
+	float	diagonal = (max - min).Length();
+	if (diagonal < 1000)
+	{
+		diagonal = 1000;
+	}
+
+
+	diagonal *= scale;
+
+	// Note we are moving the parent object, the camera component takes its pos from this
+	p_obj->GetMatrix().Ident();
+	p_obj->GetMatrix().RotateX(Mth::DegToRad(x_rot));
+	p_obj->GetMatrix().RotateY(Mth::DegToRad(y_rot));
+	p_obj->GetMatrix().RotateZ(Mth::DegToRad(z_rot));
+
+	// printf ("diagonal = %f, mid = [%f,%f,%f]\nbox = [%f,%f,%f] - [%f,%f,%f]\n",diagonal,mid[X],mid[Y],mid[Z],min[X],min[Y],min[Z],max[X],max[Y],max[Z]  );	
+
+	p_obj->SetPos(mid + scale * diagonal * p_obj->GetMatrix().GetAt());
+
+	
+}
+
+
+// @script | CenterCamera |  Center the camera on the rendered geometry, or an individual object
+// @parmopt float | scale | 0.9 | 
+// @parmopt float | X | -45 | Angle to rotate about X 
+// @parmopt float | Y | 45 | Angle to rotate about Y
+// @parmopt float | Z | 0 | Angle to rotate about X
+// @parmopt	checksum | id | 0 | checksum of and individual object to center on
+// Notes:  The Camera will be centered on the center of the bounding box
+// of all exported geometry.  It will be moved back by the size of the diagonal
+// of the bounding box, and rotated by the X,Y,Z angles
+
+bool ScriptCenterCamera( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// This is only interend to be used in the fly around modes
+	// so kill it during menus
+
+	if (!Mdl::CViewer::sGetViewMode())
+	{
+		return false;
+	}
+
+
+	float scale = 0.9f;
+	pParams->GetFloat( "scale", &scale );
+	float X = -45.0f;;
+	pParams->GetFloat( "x", &X );
+	float Y = 45.0f;
+	pParams->GetFloat( "y", &Y );
+	float Z = 0.0f;
+	pParams->GetFloat( "z", &Z );
+	uint32 id = 0; 
+	pParams->GetChecksum( "id", &id );
+
+
+
+
+	Nx::CScene	*p_scene = Nx::CEngine::sGetMainScene();
+	if (p_scene)
+	{
+		center_camera_on_scene(p_scene, scale, X, Y, Z, id);
+	}
+	return true;
+}
+
+
+
+
+
+#define CHECKSUM_MAXPITCH			0xfa3e14c5	// maxpitch
+#define CHECKSUM_MINPITCH			0x1c5ebb24	// minpitch
+#define CHECKSUM_MAXVOL				0x0693daaf	// maxvol
+#define CHECKSUM_MINVOL				0x4391992d	// minvol
+#define CHECKSUM_TERRAIN			0x3789ac4e  // terrain
+#define CHECKSUM_TABLE				0x09d670b9  // table
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadTerrainSounds | Loads all the associated sounds for a terrain type of a level.
+// @parm name | terrain | Name of terrain
+
+bool ScriptLoadTerrainSounds( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 terrain_checksum;
+
+	if (!pParams->GetChecksum(CHECKSUM_TERRAIN, &terrain_checksum))
+	{
+		printf("Expected terrain checksum\n");
+		return false;
+	}
+
+	ETerrainType terrain = Env::CTerrainManager::sGetTerrainFromChecksum(terrain_checksum);
+	Env::CTerrainManager::sLoadTerrainSounds(terrain);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Goto | jumps to the specified script
+// @Uparm name | The name of the script 
+// @parmopt structure | Params | | parameter list to pass to the new script
+bool ScriptGoto(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(NONAME,&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("Goto command requires a script name"));
+	
+	Script::CStruct *pArgs=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pArgs);
+	pScript->SetScript(ScriptChecksum,pArgs,pScript->mpObject);
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GotoPreserveParams | This has the same effect as goto, 
+// except all the parameters that were passed to the current script 
+// will get passed to the new script
+// @Uparm name | The name of the script to goto
+// @parmopt structure | Params | | additional parameters to pass to script
+bool ScriptGotoPreserveParams(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(NONAME,&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("GotoPreserveParams command requires a script name"));
+	
+	// Extract the old script parameters.
+	// Note: Need to do it this way rather than pass pScript->GetParams() to pScript->SetScript,
+	// because SetScript will clear the pScript's params before using the passed params, but
+	// since the passed params ARE the pScript's params, this will mean they will get zeroed.
+	Script::CStruct *pTemp=new Script::CStruct;
+	pTemp->AppendStructure(pScript->GetParams());
+	
+	// If any more params are specified, merge them on too.
+	Script::CStruct *pMoreParams=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pMoreParams);
+	pTemp->AppendStructure(pMoreParams);
+	
+	
+	pScript->SetScript(ScriptChecksum,pTemp,pScript->mpObject);
+	delete pTemp;
+	
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GotoRandomScript | This will abort the current script
+// and jump to a random one of the scripts specified in an array. 
+// For example: 
+// GotoRandomScript [ScriptA ScriptB ScriptC]
+// @uparm [] | Array of scripts to choose from
+
+bool ScriptGotoRandomScript(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	
+	Script::CArray *pArray=NULL;
+	pParams->GetArray(NONAME,&pArray);
+	if (pArray && pArray->GetSize())
+	{
+		switch (pArray->GetType())
+		{
+			case ESYMBOLTYPE_NAME:
+				pScript->SetScript(pArray->GetNameChecksum(Mth::Rnd(pArray->GetSize())),NULL,pScript->mpObject);				
+				break;
+			default:
+				Dbg_MsgAssert(0,("GotoRandomScript requires an array of script names."));
+				break;
+		}		
+	}
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PrintStruct | Prints the contents of the passed structure
+// @uparm structure | The structure to print
+bool ScriptPrintStruct(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::PrintContents(pParams);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static CComponent sTempComponent;
+
+static int sFormatText(CStruct *p_dest_struct, CStruct *p_format)
+{
+	#define FORMATTED_TEXT_MAX_LEN 1000
+	static char s_printf_buffer[FORMATTED_TEXT_MAX_LEN+1];
+
+	Dbg_MsgAssert(p_format,("NULL p_format"));
+	
+	uint32 string_name_checksum=0;
+	p_format->GetChecksum(CRCD(0xff0db407,"TextName"),&string_name_checksum);
+	
+	// If passed a ChecksumName, the checksum of the formatted text will
+	// be calculated and put into a parameter with that name.
+	uint32 checksum_name_checksum=0;
+	p_format->GetChecksum(CRCD(0x76fa7a8f,"ChecksumName"),&checksum_name_checksum);
+
+	int result = 1;
+	
+	const char *p_format_text="";
+	if (!p_format->GetString(NONAME,&p_format_text))
+	{
+		printf("Expected text format parameters to contain a format string\n");
+		return 0;
+	}	
+	
+	char *p_dest=s_printf_buffer;
+	const char *p_scan=p_format_text;
+	int space_left=FORMATTED_TEXT_MAX_LEN;
+	while (*p_scan)
+	{
+		Dbg_MsgAssert(space_left,("Overflowed formatted text buffer"));
+
+		if (p_scan[0]=='\\' && p_scan[1]=='%')
+		{
+			// Make \% equate to %, so that we can have % chars in the string.
+			++p_scan;
+			*p_dest++=*p_scan++;
+			--space_left;
+		}	
+		else if (*p_scan=='%')
+		{
+			++p_scan; // Skip over the %
+			
+			// Find the component in p_format which has the same name as the name following
+			// the % sign.
+			#define PARAM_NAME_MAX_LEN 50
+			char p_param_name[PARAM_NAME_MAX_LEN+1];
+			int c=0;
+			char *p_param_name_dest=p_param_name;
+
+			if (*p_scan=='%')
+			{
+				// Two percents mean that what follows is a name longer than one char.
+				++p_scan;
+				
+				while ((*p_scan>='a' && *p_scan<='z') ||
+					   (*p_scan>='A' && *p_scan<='Z') ||
+					   (*p_scan>='0' && *p_scan<='9') ||
+					   *p_scan=='_')
+				{
+					Dbg_MsgAssert(cFindNamedComponentRecurse(GenerateCRC(p_param_name));
+			
+			if (!p_unresolved_comp)
+			{
+				printf("Missing text formatting parameter '%s'\n",p_param_name);
+				return 0;
+			}	
+
+			// p_unresolved_comp may have type Name, which may resolve to some global. So use
+			// the ResolveNameComponent to resolve it.
+			// Using sTempComponent because ResolveNameComponent may modify the component by
+			// perhaps setting it's mpStructure or mpArray to equal that of some global symbol.
+			// It would be bad to modify the contents of p_unresolved_comp because it is
+			// in the passed structure, which will get cleaned up later, resulting in the deletion
+			// of the global symbol's pointer, causing horrible crashes.
+			sTempComponent.mType=p_unresolved_comp->mType;
+			sTempComponent.mUnion=p_unresolved_comp->mUnion;
+			ResolveNameComponent(&sTempComponent);
+			
+			char p_temp[100];
+			switch (sTempComponent.mType)
+			{
+				case ESYMBOLTYPE_INTEGER:
+				{
+					if (p_format->ContainsFlag(CRCD(0xf138085f,"UseCommas")))
+					{
+						strcpy(p_temp,Str::PrintThousands(sTempComponent.mIntegerValue));
+					}
+					else
+					{
+						sprintf(p_temp,"%d",sTempComponent.mIntegerValue);
+					}	
+					int len=strlen(p_temp);
+					Dbg_MsgAssert(len<=space_left,("Overflowed formatted text buffer"));
+					strcpy(p_dest,p_temp);
+					p_dest+=len;
+					space_left-=len;
+					break;
+				}	
+				case ESYMBOLTYPE_FLOAT:
+				{
+					int decimal_places=3;
+					char format_string[16];
+					p_format->GetInteger(CRCD(0xd8e2e09f,"DecimalPlaces"),&decimal_places);
+					Dbg_MsgAssert(decimal_places>=0 && decimal_places<10,("decimal_places must be less than 10"));
+					sprintf( format_string, "%%.%df", decimal_places );
+					sprintf(p_temp,format_string,sTempComponent.mFloatValue);
+					int len=strlen(p_temp);
+					Dbg_MsgAssert(len<=space_left,("Overflowed formatted text buffer"));
+					strcpy(p_dest,p_temp);
+					p_dest+=len;
+					space_left-=len;
+					break;
+				}	
+				case ESYMBOLTYPE_PAIR:
+				{
+					CPair *p_pair=sTempComponent.mpPair;
+					Dbg_MsgAssert(p_pair,("NULL p_pair"));
+					
+					sprintf(p_temp,"(%.3f,%.3f)",p_pair->mX,p_pair->mY);
+					int len=strlen(p_temp);
+					Dbg_MsgAssert(len<=space_left,("Overflowed formatted text buffer"));
+					strcpy(p_dest,p_temp);
+					p_dest+=len;
+					space_left-=len;
+					break;
+				}	
+				case ESYMBOLTYPE_VECTOR:
+				{
+					CVector *p_vector=sTempComponent.mpVector;
+					Dbg_MsgAssert(p_vector,("NULL p_vector"));
+					
+					sprintf(p_temp,"(%.3f,%.3f,%.3f)",p_vector->mX,p_vector->mY,p_vector->mZ);
+					int len=strlen(p_temp);
+					Dbg_MsgAssert(len<=space_left,("Overflowed formatted text buffer"));
+					strcpy(p_dest,p_temp);
+					p_dest+=len;
+					space_left-=len;
+					break;
+				}	
+				case ESYMBOLTYPE_NAME:
+				{
+					// we set the return result to 2, so we can assert in ScriptFormatText
+					// so we can see where in the script we are
+					result = 2;
+					sprintf(p_temp,"%s",FindChecksumName(sTempComponent.mChecksum));
+					int len=strlen(p_temp);
+					Dbg_MsgAssert(len<=space_left,("Overflowed formatted text buffer"));
+					strcpy(p_dest,p_temp);
+					p_dest+=len;
+					space_left-=len;
+					break;
+				}	
+				case ESYMBOLTYPE_STRING:
+				case ESYMBOLTYPE_LOCALSTRING:
+				{
+					const char *p_source="";
+					if (sTempComponent.mType==ESYMBOLTYPE_STRING)
+					{
+						p_source=sTempComponent.mpString;
+					}	
+					else
+					{
+						p_source=sTempComponent.mpLocalString;
+					}	
+					
+					while (*p_source)
+					{
+						Dbg_MsgAssert(space_left,("Overflowed formatted text buffer"));
+						*p_dest++=*p_source++;
+						--space_left;
+					}	
+					break;
+				}	
+				default:
+				{
+					Dbg_MsgAssert(0,("Error when formatting '%s'\n'%s' has data type '%s' which is not supported yet.\n",p_format_text,p_param_name,Script::GetTypeName(sTempComponent.mType)));
+					break;
+				}	
+			}	
+		}
+		else
+		{
+			*p_dest++=*p_scan++;
+			--space_left;
+		}	
+	}
+	*p_dest=0;
+
+	Dbg_MsgAssert(p_dest_struct,("NULL p_dest_struct"));
+	if (string_name_checksum)
+	{
+		p_dest_struct->AddString(string_name_checksum,s_printf_buffer);
+	}	
+	if (checksum_name_checksum)
+	{
+		p_dest_struct->AddChecksum(checksum_name_checksum,Script::GenerateCRC(s_printf_buffer));
+	}	
+	return result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FormatText | Fills in any % characters in the passed format string with the passed
+// parameters, and assigns the new string to a parameter with the given name.
+// For example:
+// FormatText TextName=Line1 "Hello %f %b" f="Mum" b=23
+// will create the string "Hello Mum 23" and assign it to Line1
+// The name following the % must consist of a single character. This is to allow formatting to
+// be used to append strings, eg "%sBlaa"
+// If two % signs are used, then the name that follows may consist of more than one char,
+// eg "%%foo blaa"
+//
+// Currently supported data types are integer, float, string, local string, vector and pair.
+// If any of the parameters are missing it will give an error message and will not create the
+// final string. It will not assert though.
+//
+// @parmopt name | TextName | | The name to be given to the new formatted string
+// @parmopt name | ChecksumName | | If this is specified, the checksum of the formatted text
+// will be calculated and added using ChecksumName as its name.
+// @uparm string | The source format string
+// @flag UseCommas | This will make it print any integer using commas to separate thousands for clarity.
+bool ScriptFormatText(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if (sFormatText(pScript->GetParams(),pParams) == 2)
+	{
+	#ifdef	__NOPT_ASSERT__
+//		Dbg_MsgAssert(0,("%s\nCannot use checksum names in FormatText",pScript->GetScriptInfo()));
+		printf ("%s\n### WARNING ### - Cannot use checksum names in FormatText",pScript->GetScriptInfo());
+	#endif
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool s_get_string_from_params(char* p_buffer, Script::CStruct *pParams, Script::CScript* pScript)
+{
+	Dbg_Assert( p_buffer );
+
+	const char *p_text="Default printf text";
+	if (pParams->GetText(NONAME,&p_text))
+	{
+		CStruct *p_format=new CStruct;
+		p_format->AddChecksum(GenerateCRC("TextName"),GenerateCRC("PrintfText"));
+		p_format->AppendStructure(pParams);
+		
+		if (sFormatText(p_format,p_format))
+		{
+			p_format->GetString("PrintfText",&p_text);
+		}
+		else
+		{
+			Dbg_Warning("\n%s\nError formatting text for printf",pScript->GetScriptInfo());
+			p_text="";
+		}
+
+		sprintf(p_buffer, "%s\n",p_text);
+		
+		delete p_format;   		// p_format actually contains the string we are printing, so delete it AFTER the printf
+		
+		return true;
+	}
+
+	int IntVal=0;
+	if (pParams->GetInteger(NONAME,&IntVal))
+	{
+		sprintf(p_buffer, "%d\n",IntVal);
+
+		return true;
+	}
+		
+	#ifdef __NOPT_ASSERT__
+	uint32 Checksum=0;    
+	if (pParams->GetChecksum(NONAME,&Checksum))
+	{
+		sprintf(p_buffer, "[Checksum] %s\n",Script::FindChecksumName(Checksum));
+
+		return true;
+	}
+		
+	sprintf(p_buffer, "Empty printf at %s", pScript->GetScriptInfo());
+	#endif
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Printf | Printf "Default printf text" Prints the passed 
+// text onto the EE0 output on the target manager.
+// Printf can will also print the checksum name of any passed checksum, for example:
+// printf blaa    // Will print: [Checksum] blaa
+// @uparmopt "text" | some text to print
+// @uparmopt 1 | an int to print
+// @uparmopt name | checksum to print
+// @flag UseCommas | This will make it print any integer using commas to separate thousands for clarity.
+bool ScriptPrintf(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	char buffer[1024];
+
+	s_get_string_from_params(buffer, pParams, pScript);
+	
+	printf( buffer );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptAssert |
+bool ScriptScriptAssert(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	char buffer[1024];
+
+	s_get_string_from_params(buffer, pParams, pScript);
+	
+	Dbg_MsgAssert( 0, ( "%s\nSCRIPT ASSERT:  %s", pScript->GetScriptInfo(), buffer ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PrintScriptInfo |
+bool ScriptPrintScriptInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#ifdef	__NOPT_ASSERT__
+	printf("+++ScriptInfo+++++++++++++++++++++++++\n");
+	
+	// firsts prints out the associated string,
+	// using the "printf" script function
+	ScriptPrintf(pParams, pScript);
+	
+	// then it asserts
+	printf("%s",pScript->GetScriptInfo());
+	
+	printf("++++++++++++++++++++++++++++++++++++++\n");
+	#endif	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetHackFlag | Sets special hackflag
+// @uparmopt 1 | integer flag (1 or 0)
+static bool HackFlag=false;
+bool ScriptSetHackFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	int v=1;
+	pParams->GetInteger(NONAME,&v);
+	HackFlag=v?true:false;
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | HackFlagIsSet | Returns current value of hackflag
+bool ScriptHackFlagIsSet(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return HackFlag;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PreloadModel | Preloads model
+// Example: PreloadModel name="ModelName" or name="levels/skateshop/thps4board_01/thps4board_01.mdl"
+// @parm string | name | The model name
+bool ScriptPreloadModel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char* pModelName;
+	pParams->GetText( "name", &pModelName, Script::ASSERT );
+
+	// now preload any programmatically-loaded peds
+	Nx::CModel* pDummy = Nx::CEngine::sInitModel();
+	Dbg_Assert( pDummy );
+	
+	bool forceTexDictLookup = pParams->ContainsFlag( CRCD(0x6c37fdc7,"AllowReplaceTex") );
+
+	Str::String fullModelName;
+	fullModelName = Gfx::GetModelFileName(pModelName, ".mdl");
+	pDummy->AddGeom( fullModelName.getString(), 0, true, 0, forceTexDictLookup );
+		
+	Nx::CEngine::sUninitModel( pDummy );
+	
+	return true;
+}
+		   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+// @script | LoadLevelGeometry | Loads a bsp file. Assumes the current 
+// directory is \skate3\data, so a valid string would be "levels\aus\aus.bsp". 
+// Can also have an optional "Sky="xxx.bsp" parameter for loading the 
+// sky dome with a level file. The Level and Sky parameters are optional, 
+// but you must have at least one of them (So you can load just a sky, 
+// or just a level, if you want).
+// @parmopt string | Sky | | .bsp file for sky dome
+// @parmopt string | Level | | .bsp file for level
+// @parmopt string | Pre_set | | load assets from PRE file
+bool ScriptLoadLevelGeometry(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	 File::PreMgr * pre_mgr =  File::PreMgr::Instance();
+	
+	const char*	p_level = NULL;
+	const char*	p_sky 	= NULL;
+
+	pParams->GetText( "Level",&p_level );
+	pParams->GetText( "Sky", &p_sky );
+
+	Nx::CTexDict *p_tex_dict, *p_sky_tex_dict;
+	Nx::CScene *p_scene, *p_sky_scene;
+
+	if (p_sky)
+	{
+		p_sky_tex_dict = Nx::CTexDictManager::sLoadTextureDictionary(p_sky);
+		Dbg_Assert(p_sky_tex_dict);
+		p_sky_scene = Nx::CEngine::sLoadScene(p_sky, p_sky_tex_dict, false, true);	// mark as sky that doesn't go in SuperSectors
+		Dbg_Assert(p_sky_scene);
+	}
+	
+	if (p_level)
+	{
+		//p_tex_dict = Nx::CEngine::sLoadTextures(p_level);
+		p_tex_dict = Nx::CTexDictManager::sLoadTextureDictionary(p_level);
+		Dbg_Assert(p_tex_dict);
+		p_scene = Nx::CEngine::sLoadScene(p_level, p_tex_dict);
+		Dbg_Assert(p_scene);
+		p_scene->LoadCollision(p_level);
+	}
+	
+	return true;
+}
+
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptUnloadAllLevelGeometry(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Nx::CEngine::sUnloadAllScenesAndTexDicts();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptLoadScene(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool is_dictionary = pParams->ContainsFlag(CRCD(0x3d559e7d,"is_dictionary"));  		// piece dictionary, like the park editor
+	bool is_sky = pParams->ContainsFlag(CRCD(0xb5bd28d8,"is_sky"));						// a sky dome
+	bool is_net = pParams->ContainsFlag(CRCD(0x417149c8,"is_net"));						// the _net version
+	bool add_super_sectors = ! pParams->ContainsFlag(CRCD(0x8943dbd8,"no_supersectors"));	// optionally ignore supersectors
+	
+	const char *p_scene_name = NULL;
+	pParams->GetText( CRCD(0x26861025,"scene"),&p_scene_name,true );
+	Dbg_MsgLog(("Loading Scene %s ...",p_scene_name));
+	printf ("Loading Scene %s ...",p_scene_name);
+	
+
+	// First load the texture dictionary
+	char	texture_dict_name[128];
+	sprintf(texture_dict_name,"levels\\%s\\%s%s.tex",p_scene_name,p_scene_name,is_net?"_net":"");
+
+
+	
+	#ifdef	__NOPT_ASSERT__
+	const char *p_fallback = "nj_sky";
+	// Some debug code to load the NJ sky if default_sky does not exists
+	if (stricmp(texture_dict_name,"levels\\default_sky\\default_sky.tex")==0)
+	{
+		printf ("TRYING TO LAOD DEFUALT SKY\n");
+		printf ("TRYING TO LAOD DEFUALT SKY\n");
+		printf ("TRYING TO LAOD DEFUALT SKY\n");
+		printf ("TRYING TO LAOD DEFUALT SKY\n");
+		// it's the default sky, so check if it exists
+		if (!File::Exist(texture_dict_name))
+		{
+			printf ("NO EXIST\n");
+		
+			p_scene_name = p_fallback;			
+			sprintf(texture_dict_name,"levels\\%s\\%s.tex",p_scene_name,p_scene_name);
+		}
+	}
+	#endif
+	
+	
+	Nx::CTexDict * p_tex_dict = Nx::CTexDictManager::sLoadTextureDictionary(texture_dict_name,true);
+	Dbg_MsgAssert(p_tex_dict,("ERROR loading tex dict for %s\n",texture_dict_name));
+
+	// a level ending in _sky is automatically a sky dome	
+	if (Str::StrStr(p_scene_name,"_sky"))
+	{
+		is_sky = true;
+	}
+	
+	// Skys always have no supersectors
+	if (is_sky)
+	{
+		add_super_sectors = false;		
+	}
+	
+	// and dictionaries always have no supersectors
+	if (is_dictionary)
+	{
+		add_super_sectors = false;		
+	}
+	
+	#ifdef	__NOPT_ASSERT__
+	Nx::CScene * p_scene = 
+	#endif
+	Nx::CEngine::sLoadScene(p_scene_name, p_tex_dict, add_super_sectors,is_sky,is_dictionary,is_net);	// mark as sky that doesn't go in SuperSectors
+	Dbg_MsgAssert(p_scene,("ERROR loading scene for %s\n",p_scene_name));
+	printf ("... done\n");
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAddScene(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_scene_name = NULL;
+	const char *p_add_name = NULL;
+	pParams->GetText( "scene",&p_scene_name );
+	pParams->GetText( "add",&p_add_name );
+	if (p_scene_name && p_add_name)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+
+		#ifdef	__NOPT_ASSERT__
+		Nx::CScene * p_scene =
+		#endif
+		Nx::CEngine::sAddScene(p_scene_name, p_add_name);
+		Dbg_MsgAssert(p_scene,("ERROR adding scene for %s\n",p_scene_name));
+
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// we have a seperate script command for loading collision
+// so we can control the loading of pre files
+bool ScriptLoadCollision(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	const char *p_scene_name = NULL;
+	pParams->GetText( "scene",&p_scene_name );
+	bool is_net = pParams->ContainsFlag(CRCD(0x417149c8,"is_net"));						// the _net version
+	if (p_scene_name)
+	{
+		Nx::CScene * p_scene = Nx::CEngine::sGetScene(p_scene_name);
+		Dbg_MsgAssert(p_scene,("Trying to load collision for scene: %s\n",p_scene_name));
+		Dbg_MsgLog(("Trying to load collision for scene: %s\n",p_scene_name));
+		p_scene->LoadCollision(p_scene_name, is_net);	
+	}
+	return true;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAddCollision(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_scene_name = NULL;
+	const char *p_add_name = NULL;
+	pParams->GetText( "scene",&p_scene_name );
+	pParams->GetText( "add",&p_add_name );
+	if (p_scene_name && p_add_name)
+	{
+		Nx::CScene * p_scene = Nx::CEngine::sGetScene(p_scene_name);
+		Dbg_MsgAssert(p_scene,("Trying to add collision file %s to scene: %s\n", p_add_name, p_scene_name));
+		p_scene->AddCollision(p_add_name);
+	}
+	return true;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptUnloadScene(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_scene_name = NULL;
+	bool matching_names;
+	pParams->GetText( "scene",&p_scene_name );
+
+	matching_names = false;
+	if (p_scene_name)
+	{
+		Nx::CScene *p_scene = Nx::CEngine::sGetScene(p_scene_name);
+		if (p_scene)
+		{
+			matching_names = true;
+		}
+		else
+		{
+			p_scene = Nx::CEngine::sGetMainScene();
+		}
+
+		if (p_scene)
+		{
+			Nx::CTexDict *p_tex_dict = p_scene->GetTexDict();
+
+			Nx::CEngine::sUnloadScene(p_scene);
+			// must unload dictionary after scene
+			if (p_tex_dict)
+			{
+				Nx::CTexDictManager::sUnloadTextureDictionary(p_tex_dict);
+			}
+		}
+	}
+
+	return matching_names;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptQuickReload(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_scene_name = NULL;
+	pParams->GetText( "scene",&p_scene_name );
+	if (p_scene_name)
+	{
+		Nx::CEngine::sQuickReloadGeometry(p_scene_name);
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCreateFromNode( Script::CStruct *pNode )
+{	
+//	Dbg_MsgLog(("CreateFromNodeIndex nodeIndex=%d",nodeIndex));
+	
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CRailManager *p_rail_man = skate_mod->GetRailManager();
+	Dbg_MsgAssert(p_rail_man,("Missing rail manager"));					 
+					 
+//	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+//	Dbg_MsgAssert( pNodeArray,( "No NodeArray found" ));
+
+	// We still need the node index for some things (rails, proxim)
+	// nut now we get it from the node
+	int nodeIndex = 0;
+	pNode->GetInteger(CRCD(0xe50d6573,"NodeIndex"), &nodeIndex);
+
+//	Script::CStruct *pNode=pNodeArray->GetStructure( nodeIndex );
+	Dbg_MsgAssert( pNode,( "NULL pNode" ));
+
+	// If this is a net game, don't load objects that were meant to be left out for 
+	// performance/bandwidth/gameplay reasons
+	if ( skate_mod->ShouldBeAbsentNode( pNode ) )
+	{
+		return true;
+	}
+
+#ifdef __NOPT_ASSERT__
+	Mem::Allocator* pCurrentHeapContext = Mem::Manager::sHandle().GetContextAllocator();
+	if ( !( pCurrentHeapContext == Mem::Manager::sHandle().CutsceneBottomUpHeap()
+		 || pCurrentHeapContext == Mem::Manager::sHandle().BottomUpHeap() ) )
+	{
+		// GJ:  this function used to push the bottom up heap context explicitly,
+		// but it was causing problems with the cutscene code, which
+		// wants to put stuff on the cutscene heap.  however, there's
+		// no reason to push the bottom up heap, since the bottom up heap
+		// was always the default context anyway...  i think.
+		Dbg_MsgAssert( 0, ( "Was expecting bottomupheap context" ) );
+	}
+#endif
+
+	/*
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	*/
+
+	uint32 ClassChecksum = 0;
+	pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+
+	// check for obsolete trigger objects first
+	if ( ClassChecksum == 0xe594f0a2 )	// trigger
+	{
+		uint32 checksumName = 0;
+		if ( pNode->GetChecksum( 0xa1dc81f9, &checksumName ) ) // checksum 'name'
+		{
+#ifdef __NOPT_ASSERT__
+			printf("WARNING:  Trigger objects are obsolete %s\n", Script::FindChecksumName(checksumName) );
+#endif		
+		}
+	}
+
+	bool success = true;
+
+	switch (ClassChecksum)
+	{
+		case 0xe47f1b79: //vehicle
+			Dbg_MsgLog(("Vehicle"));
+			Mem::PushMemProfile("Vehicles");
+			Obj::CreateCar( skate_mod->GetObjectManager(), pNode );
+			Mem::PopMemProfile();
+			break;
+	
+		case 0xa0dfac98:  //pedestrian
+		case 0x061a741e:  //ped
+			{
+				Dbg_MsgLog(("Pedestrian"));
+				Mem::PushMemProfile("Pedestrians");
+				Obj::CreatePed( skate_mod->GetObjectManager(), pNode );
+				Mem::PopMemProfile();
+			}
+			break;
+
+		case 0xef59c100:  //gameobject
+			{
+				Dbg_MsgLog(("gameobject"));
+				Mem::PushMemProfile("Game Objects");
+				Obj::CreateGameObj( skate_mod->GetObjectManager(), pNode );
+				Mem::PopMemProfile();
+			}
+			break;
+			
+		case 0x19b1e241: // ParticleEmitter
+			{
+				Dbg_MsgLog(("ParticleEmitter"));
+				Mem::PushMemProfile("Particle Systems");
+				Obj::CreateParticleEmitter( skate_mod->GetObjectManager(), pNode );
+				Mem::PopMemProfile();
+			}
+			break;
+		case 0x9e7d469e: // ParticleObject
+			{
+				Dbg_MsgLog(("ParticleObject"));
+				Mem::PushMemProfile("Particle Systems");
+				Obj::CreateParticleObject( skate_mod->GetObjectManager(), pNode );
+				Mem::PopMemProfile();
+			}
+			break;
+
+		case 0xb7b3bd86:  // LevelObject
+			{
+				Dbg_MsgLog(("LevelObject"));
+				// create a level object here
+				Mem::PushMemProfile("LevelObjects");
+				Obj::CreateLevelObj( skate_mod->GetObjectManager(), pNode );
+				Mem::PopMemProfile();
+				// Note, we used to turn off the original sector here
+				// this is now done to all LevelObjects in ParseNodeArray
+			}
+			break;
+
+//		case 0xe594f0a2:  // Trigger (geometry containing triggers...)
+		case 0xbf4d3536:  // LevelGeometry
+		{
+			Dbg_MsgLog(("LevelGeometry"));
+			uint32 checksumName = 0;
+			if ( pNode->GetChecksum( 0xa1dc81f9, &checksumName ) ) // checksum 'name'
+			{
+//				Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);
+				// We only want to get sectors from the main scene, as that is what the node array applies to															
+				Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				if (p_sector)
+				{
+					Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+					p_sector->SetVisibility(0xffffffff);
+					p_sector->SetActive(true);
+					p_sector->SetCollidable(true);
+
+					// If this node is an occluder, let the occlusion code know that this occluder is now enabled.
+					if( pNode->ContainsFlag( 0x4e549fe1 /*"Occluder"*/ ))
+					{
+						Nx::CEngine::sEnableOcclusionPoly( checksumName, true );
+					}
+				}
+				else
+				{
+					printf(" WARNING: sGetSector(0x%x) returned NULL (%s)\n",checksumName,Script::FindChecksumName(checksumName));
+				}
+				//Bsp::ClearWorldSectorFlag( checksumName, mSD_KILLED | mSD_NON_COLLIDABLE | mSD_INVISIBLE  | mSD_INVISIBLE2);
+			}
+			break;
+ 		}
+
+		case 0x8e6b02ad:  // railnode
+			Dbg_MsgLog(("railnode"));
+			p_rail_man->SetActive( nodeIndex, true, pNode->ContainsFlag( "FLAG_WHOLE_RAIL" ) );
+			break;
+		case CRCC(0x30c19600, "ClimbingNode"):
+			Dbg_MsgLog(("climbingnode"));
+			p_rail_man->SetActive( nodeIndex, true, pNode->ContainsFlag( "FLAG_WHOLE_RAIL" ) );
+			break;
+		case 0x8470f2e:  // proximnode
+			Dbg_MsgLog(("proximnode"));
+			Obj::Proxim_SetActive( nodeIndex, true );
+			break;			
+		case 0xd64dc7c2:  // emitterobject
+			Dbg_MsgLog(("emitterobject"));
+			Obj::CEmitterManager::sSetEmitterActive( nodeIndex, true );
+			break;			
+		default:
+			Dbg_MsgLog(("default"));
+			success = false;
+			break;
+	}
+//	Mem::Manager::sHandle().PopContext();
+	return success;
+}
+
+
+bool ScriptCreateFromNodeIndex( int nodeIndex )
+{	
+	return ScriptCreateFromNode(SkateScript::GetNode(nodeIndex));
+}
+
+
+struct MaybeKillObjectInfo
+{
+	uint32 nodeNum;
+	Obj::CObject *forbiddenObject;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void MaybeKillObject( Obj::CObject *pOb, void *pVoidData )
+{
+	
+	Dbg_MsgAssert(pOb,("NULL pOb"));
+	Dbg_MsgAssert(pVoidData,("NULL pVoidData"));
+
+	MaybeKillObjectInfo *pInfo;
+	pInfo = ( MaybeKillObjectInfo * )pVoidData;
+	
+	uint32 nodeNum = pInfo->nodeNum;
+	uint32	nodeName = SkateScript::GetNodeNameChecksum(nodeNum);
+	
+	if ( pOb->GetID() == nodeName )
+	{
+		Dbg_MsgAssert( pOb != pInfo->forbiddenObject,( "Can't issue a kill command from within the script of the object you're killing:  Use Die command." ));
+		pOb->DestroyIfUnlocked( );
+//		pOb->MarkAsDead();
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptKillFromNodeIndex( int nodeIndex, Script::CScript *pScript )
+{			
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+			
+	Obj::CRailManager *p_rail_man = skate_mod->GetRailManager();
+	Dbg_MsgAssert(p_rail_man,("Missing rail manager"));					 
+					     
+	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray,( "No NodeArray found" ));
+
+	Script::CStruct *pNode=pNodeArray->GetStructure( nodeIndex );
+	Dbg_MsgAssert( pNode,( "NULL pNode" ));
+
+	uint32 ClassChecksum = 0;
+	pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+
+
+									
+	switch (ClassChecksum)
+	{
+		case 0xe47f1b79: // vechicle
+		case 0xa0dfac98: // pedestrian
+		case 0x061a741e: // ped
+		case 0xef59c100: // gameobject
+		case 0x19b1e241: // ParticleEmitter
+		case 0x9e7d469e: // ParticleObject
+		case 0xb7b3bd86: // LevelObject
+		case 0x5b8ab877: // skater
+		{
+			MaybeKillObjectInfo info;
+			info.forbiddenObject = pScript->mpObject;
+			info.nodeNum = nodeIndex;
+			
+			skate_mod->GetObjectManager()->ProcessAllObjects( MaybeKillObject, &info );
+			break;
+		}
+
+		case CRCC(0x30c19600, "ClimbingNode"):
+			p_rail_man->SetActive( nodeIndex, false, pNode->ContainsFlag( "FLAG_WHOLE_RAIL" ) );
+			break;		
+
+		case 0x8e6b02ad:  // railnode
+			p_rail_man->SetActive( nodeIndex, false, pNode->ContainsFlag( "FLAG_WHOLE_RAIL" ) );
+			break;		
+
+		case 0x8470f2e:  // proximnode
+			Obj::Proxim_SetActive( nodeIndex, false );
+			break;			
+
+		case 0xd64dc7c2:  // emitterobject
+			Obj::CEmitterManager::sSetEmitterActive( nodeIndex, false );
+			break;			
+
+//		case 0xe594f0a2:  // Trigger (geometry containing triggers...)
+		case 0xbf4d3536:  // LevelGeometry
+		{
+			uint32 checksumName = 0;
+			if ( pNode->GetChecksum( 0xa1dc81f9, &checksumName ) ) // checksum 'name'
+			{
+//				Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);				
+				// We only want to get sectors from the main scene, as that is what the node array applies to															
+				Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+				p_sector->SetActive(false);
+				//Bsp::SetWorldSectorFlag(  checksumName, mSD_KILLED );
+
+				// If this node is an occluder, let the occlusion code know that this occluder is no longer enabled.
+				if( pNode->ContainsFlag( 0x4e549fe1 /*"Occluder"*/ ))
+				{
+					Dbg_MsgAssert(!ScriptInSplitScreenGame(NULL, NULL),
+								  ("Can't turn occlusion polys on or off during a 2 player game"));
+					Nx::CEngine::sEnableOcclusionPoly( checksumName, false );
+				}
+			}
+			break;
+ 		}
+
+		default:
+			return ( false );
+			break;
+	}
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptKillFromNodeNameChecksum( uint32 nodeNameChecksum, Script::CScript *pScript )
+{
+
+	return ( ScriptKillFromNodeIndex( SkateScript::FindNamedNode( nodeNameChecksum ), pScript ) );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool DoNodeActionFromNodeNum( int nodeNum, int action, Script::CScript *pScript, Script::CStruct *pParams )
+{
+    GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+// In net games, check if the node is flagged to be absent
+// and just ignore the command it fo	
+	if( gamenet_man->InNetMode())
+	{
+		// Rather inefficient.  We should get away from refering to nodes by index
+		if (skate_mod->ShouldBeAbsentNode( SkateScript::GetNode(nodeNum) ) )
+		{
+			// just ignore the action
+			return false;
+		}
+	}
+
+	// note rather dodgy way of determining the viewport of a script
+	// if it has an object, and tht object is the second skater, 
+	// and we have two viewports
+	// use viewport 1, otherwise default to viewport 0	
+	int viewport_number = 0;
+	if (pScript->mpObject && !gamenet_man->InNetGame() && Nx::CViewportManager::sGetNumActiveViewports() == 2)
+	{
+		Obj::CSkater *pSkater = skate_mod->GetSkater(1);
+		if (pScript->mpObject == pSkater )		
+		{
+			viewport_number = 1;
+		}
+	}
+	
+	
+    switch ( action )
+	{
+		case ( NODE_ACTION_CREATE ):
+			return ( ScriptCreateFromNodeIndex( nodeNum ) );
+		case ( NODE_ACTION_KILL ):
+			return ( ScriptKillFromNodeIndex( nodeNum, pScript ) );
+		case ( NODE_ACTION_SET_VISIBLE ):
+			return ( ScriptSetVisibilityFromNodeIndex( nodeNum, false, viewport_number ) );
+		case ( NODE_ACTION_SET_INVISIBLE ):
+			return ( ScriptSetVisibilityFromNodeIndex( nodeNum, true, viewport_number ) );
+		case ( NODE_ACTION_SET_COLOR ):
+			return ( ScriptSetColorFromNodeIndex( nodeNum, pParams ) );
+		case NODE_ACTION_SHATTER:
+		{   
+			ScriptSetVisibilityFromNodeIndex( nodeNum, false, viewport_number );
+			return ScriptShatterFromNodeIndex( nodeNum );
+		}
+		case NODE_ACTION_CHECK_IF_ALIVE:
+		{
+			return ( ScriptCheckExistenceFromNodeIndex( nodeNum ) );
+		}
+		default:
+			Dbg_MsgAssert( 0,( "Fire Matt." ));
+			return ( false );
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Used to perform actions to nodes by prefix, name checksum, or links... General version
+// (applicable to nodes that create Environmental Objects, Rails, and all types of CMovingObjects.
+bool DoNodeAction( Script::CStruct *pParams, Script::CScript *pScript, int action )
+{
+	
+	uint32 nameChecksum;
+	uint32 prefixChecksum;
+	//const char *pPrefix;
+	int i;
+	bool useCurrentLinks = false;
+	if ( pParams->ContainsFlag( 0x2e7d5ee7 ) || // checksum 'links'
+		( useCurrentLinks = pParams->ContainsFlag( 0xbceb479a ) ) ) // checksum 'currentlinks'
+	{
+		Dbg_MsgAssert(0,("%s:  DoNodeAction with links or currentlinks is deprecated",pScript->GetScriptInfo()));
+	
+		/*
+		int numLinks;
+		Script::CStruct *pNode = NULL;
+		if ( pParams->GetChecksumOrStringChecksum( 0xa1dc81f9, &nameChecksum ) ) // checksum 'name'
+		{
+			if ( useCurrentLinks )
+			{
+				Dbg_Message( "\n%s\nWarning:  The 'currentLinks' flag only works in an object script on the calling object.", pScript->GetScriptInfo( ) );
+			}
+			pNode = SkateScript::GetNode( SkateScript::FindNamedNode( nameChecksum ) );
+			Dbg_MsgAssert( pNode,( "\n%s\nNo node named %s found", pScript->GetScriptInfo( ), Script::FindChecksumName( nameChecksum ) ));
+		}
+		else
+		{
+			if ( pScript->mNode != -1 )
+			{
+				pNode = SkateScript::GetNode( pScript->mNode );
+				Dbg_MsgAssert( pNode,( "\n%s\nnode %d not found", pScript->GetScriptInfo( ), pScript->mNode ));
+			}
+			else
+			{
+				if ( pScript->mpObject )
+				{
+					if ( useCurrentLinks )
+					{
+						pNode = SkateScript::GetNode( ((Obj::CMovingObject *) pScript->mpObject.Convert())->m_current_node );
+						Dbg_MsgAssert( pNode,( "\n%s\n object's node %d is invalid", pScript->GetScriptInfo( ), ((Obj::CMovingObject *) pScript->mpObject.Convert())->m_start_node ));
+					}
+					else
+					{
+						pNode = SkateScript::GetNode( ((Obj::CMovingObject *) pScript->mpObject.Convert())->m_start_node );
+						Dbg_MsgAssert( pNode,( "\n%s\n object's node %d is invalid", pScript->GetScriptInfo( ), ((Obj::CMovingObject *) pScript->mpObject.Convert())->m_start_node ));
+					}
+				}
+				else
+				{
+					Dbg_MsgAssert( 0,( "\n%s\nNo valid nodes to be found anywherest, dumass.", pScript->GetScriptInfo( ) ));
+					return ( false );
+				}
+			}
+		}
+		Dbg_MsgAssert( pNode,( "What the fuck?  How did this motherfuckin' happen?" ));
+		numLinks = SkateScript::GetNumLinks( pNode );
+		for ( i = 0; i < numLinks; i++ )
+		{
+			if ( DoNodeActionFromNodeNum( SkateScript::GetLink( pNode, i ), action, pScript ) )
+			{
+				if ( action == NODE_ACTION_CHECK_IF_ALIVE )
+				{
+					return ( true );
+				}
+			}
+		}
+		*/
+
+		return ( true );
+	}
+    //if ( pParams->GetText( 0x6c4e7971, &pPrefix ) ) // checksum 'prefix'
+    if ( pParams->GetChecksumOrStringChecksum( 0x6c4e7971, &prefixChecksum ) ) // checksum 'prefix'
+	{
+		// Create with a prefix specified:
+		uint16 numNodes = 0;
+	  //  const uint16 *pMatchingNodes = SkateScript::GetPrefixedNodes( pPrefix, &numNodes );
+		const uint16 *pMatchingNodes = SkateScript::GetPrefixedNodes( prefixChecksum, &numNodes );
+		for ( i = 0; i < numNodes; i++ )
+		{
+			if ( DoNodeActionFromNodeNum( pMatchingNodes[ i ], action, pScript, pParams ) )
+			{
+				if ( action == NODE_ACTION_CHECK_IF_ALIVE )
+				{
+					return ( true );
+				}
+			}
+		}
+		return ( true );
+	}
+	if ( pParams->GetChecksumOrStringChecksum( 0xa1dc81f9, &nameChecksum ) ) // checksum 'name'
+	{
+		// Create with a name specified:
+		return ( DoNodeActionFromNodeNum( SkateScript::FindNamedNode( nameChecksum ), action, pScript, pParams ) );
+	}
+
+	// if they're calling "Invisible" with no parameters, intending to turn 
+	// visibility off on an object from within the object's script, they should
+	// be calling "Obj_Invisible" (or Obj_Visible, or Die, create not applicable
+	// since the object would have to have  already been created to be calling
+	// something.)
+	Dbg_Message( "\n%s\nSyntax not recognized... object script may need to call Obj_ version. (Ask Matt).", pScript->GetScriptInfo( ) );
+	return ( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptNodeExists( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 node_checksum=0;
+	pParams->GetChecksum(NONAME,&node_checksum,true);
+	
+	return SkateScript::NodeExists(node_checksum);
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCreateFromStructure( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	return ScriptCreateFromNode( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Create | creates an object node
+// @flag links | pass in to perform action to nodes by links
+// @flag currentlinks | pass in to perform action to nodes by links
+// @parmopt name | name | | The name of the node
+// @parmopt name | prefix | | Create with a prefix specified
+bool ScriptCreate( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+ #ifdef	__PLAT_NGPS__		
+//		snProfSetRange( -1, (void*)0, (void*)-1);
+//		snProfSetFlagValue(0x01);
+ #endif
+
+
+	// Flush dead objects before we create any new ones
+	ScriptFlushDeadObjects(NULL,NULL);	
+	
+	bool result = ( DoNodeAction( pParams, pScript, NODE_ACTION_CREATE ) );
+
+ #ifdef	__PLAT_NGPS__		
+//		snProfSetRange( 4, (void*)NULL, (void*)-1);
+ #endif		
+ 
+	return result;
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetObjectColor | color the object a particular color
+// @parm name | name | The name of the piece of level geometry to change color
+// @parm int | color | The color to set the object to (as a hexadecimal ABGR value)
+bool ScriptSetObjectColor( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	bool result = ( DoNodeAction( pParams, pScript, NODE_ACTION_SET_COLOR ) );
+	
+	return result;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Kill | kills an object node
+// @flag links | pass in to perform action to nodes by links
+// @flag currentlinks | pass in to perform action to nodes by links
+// @parmopt name | name | | The name of the node
+// @parmopt name | prefix | | kill with a prefix specified
+bool ScriptKill( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	return ( DoNodeAction( pParams, pScript, NODE_ACTION_KILL ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Visible | make object visible
+// @flag links | pass in to perform action to nodes by links
+// @flag currentlinks | pass in to perform action to nodes by links
+// @parmopt name | name | | The name of the node
+// @parmopt name | prefix | | act on objects with specified prefix
+bool ScriptVisible( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	return ( DoNodeAction( pParams, pScript, NODE_ACTION_SET_VISIBLE ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Invisible | make object invisible
+// @flag links | pass in to perform action to nodes by links
+// @flag currentlinks | pass in to perform action to nodes by links
+// @parmopt name | name | | The name of the node
+// @parmopt name | prefix | | act on objects with specified prefix
+bool ScriptInvisible( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	return ( DoNodeAction( pParams, pScript, NODE_ACTION_SET_INVISIBLE ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Shatter | Shatter an object
+// @parmopt float | area | 288.0 | the subdivision area above which
+// polys will be recursively subdivided. Be careful with this
+// parameter - making it too small will cause the buffer to
+// overflow and assert. The units are square inches 
+// The default value may change
+// @parmopt float | variance | 0.0 | the amount the velocity
+// of each shard can be (randomly) above the supplied velocity. 
+// So a value of 1.0 would mean a shard could travel at twice the supplied 
+// velocity  The default value may change.
+// @parmopt float | spread | 1.0 | determines the degree to which the
+// shards will fly apart. The way I figure this is to take the center
+// of the bounding box for the object, and then move backwards (spread
+// * velocity). This position is then used to figure the start velocity
+// for each shard. So the lower the spread value, the less the start
+// point moves back, and the more the shards will fly apart. (I can
+// draw a diagram if this isn't clear). 
+// The default value may change
+// @parmopt float | life | 4.0 | how long the shatter atomic will exist for
+// (seconds)  The default value may change
+// @parmopt | bounce | -10,000 | Default value may change
+// @parmopt | scale | 1.0 | Scaling factor for the shards
+// @flag use_skater_vel | Use skater velocity to determine
+// if object should shatter
+// @parm | vel_x | default velocity of shard on x-axis (in inches per second)
+// @parm | vel_y | default velocity of shard on y-axis (in inches per second) 
+// @parm | vel_z | default velocity of shard on z-axis (in inches per second)
+bool ScriptShatter( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// Need to read some params from the script here.
+	float		area		= 0.0f;
+	float		variance	= 0.0f;
+	float		spread		= 0.0f;
+	float		life		= 0.0f;
+	float		bounce		= 0.0f;
+	float		bounce_amp	= 0.0f;
+	float		scale		= 1.0f;
+	Mth::Vector	velocity;
+
+	if( pParams->ContainsFlag( 0x9f98989e /*"use_skater_vel"*/ ))
+	{
+		pParams->GetFloat( "scale", &scale );
+
+		Obj::CSkater* p_skater = static_cast ( pScript->mpObject.Convert() );
+		Dbg_Assert( p_skater );
+
+		velocity = p_skater->GetVel() * scale;
+	}
+	else
+	{
+		// Have to supply vel_x, y, and z.
+		pParams->GetFloat( "vel_x", &velocity[X], true );
+		pParams->GetFloat( "vel_y", &velocity[Y], true );
+		pParams->GetFloat( "vel_z", &velocity[Z], true );
+		velocity[W] = 0.0f;	// To prevent assert on using unitialized vectors 
+	}
+
+	pParams->GetFloat( "area",			&area );
+	pParams->GetFloat( "variance",		&variance );
+	pParams->GetFloat( "spread",		&spread );
+	pParams->GetFloat( "life",			&life );
+	pParams->GetFloat( "bounce",		&bounce );
+	pParams->GetFloat( "bounce_amp",	&bounce_amp );
+
+	//////////////////////////////////////////////////////////////////////////////////////////
+	// Mick 09/18/02:  PATCH
+	// setting the shatter area too low (generally to 1000) generates an excessive amount
+	// of polygons in the form of immediate mode primitive, which eat up the DMA buffer on the PS2
+	// So I'm going to limit the area to 2000 in the general case, and to 5000 in multi player games
+	float min_area = 2000.0f;				// single player limit
+	if (Mdl::Skate::Instance()->IsMultiplayerGame())
+	{
+		min_area = 5000.0f;
+	}
+	
+	if (area < min_area)
+	{
+		#ifdef	__NOPT_ASSERT__
+		printf("WARNING:  area %.1f too small for THPS4 PS2 engine, clamping to %.1f\n",area,min_area);
+		#endif
+		area = min_area;
+	}
+	// END PATCH
+	//////////////////////////////////////////////////////////////////////////////////////////
+
+	// Set up values for this shatter.
+	Nx::ShatterSetParams( velocity, area, variance, spread, life, bounce, bounce_amp );
+
+	return DoNodeAction( pParams, pScript, NODE_ACTION_SHATTER );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	CleanupBeforeParseNodeArray()
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	Obj::Proxim_Init();					
+	Obj::CEmitterManager::sInit();
+	skate_mod->GetRailManager()->Cleanup();		// probably not needed, as will be done in ScriptCleanup
+	skate_mod->GetTrickObjectManager()->DeleteAllTrickObjects();
+
+	// Scene lights are created during the node array parse, so clear them up here ready.
+	Nx::CLightManager::sClearSceneLights();
+	Nx::CLightManager::sClearVCLights();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ParseNodeArray | Parses the loaded node array 
+bool ScriptParseNodeArray( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mem::PushMemProfile("ParseNodeArray");
+
+	CleanupBeforeParseNodeArray();	
+
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	// Scan through the node array creating all things that need to be created.
+	Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+	Dbg_MsgAssert(pNodeArray,("No NodeArray found in ParseNodeArray"));
+
+	// Rails are now added seperately
+	Mem::PushMemProfile("Rail Nodes");
+	Obj::CRailManager *p_rail_man = skate_mod->GetRailManager();
+	Dbg_MsgAssert(p_rail_man,("Missing rail manager"));					 
+	p_rail_man->AddRailsFromNodeArray(pNodeArray );					 									  
+	Mem::PopMemProfile();
+	
+	// Nav nodes are added separately.
+#	ifdef TESTING_GUNSLINGER
+	Mem::PushMemProfile( "Nav Nodes" );
+	Obj::CNavManager* p_nav_man = skate_mod->GetNavManager();
+	Dbg_MsgAssert( p_nav_man, ( "Missing Nav Manager" ));
+	p_nav_man->AddNavsFromNodeArray( pNodeArray );
+	Mem::PopMemProfile();
+#	endif
+
+	// We only want to get sectors from the main scene, as that is what the node array applies to															
+	Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+									  
+	bool	fail = false;
+	uint32 i;	 
+	for (i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *pNode=pNodeArray->GetStructure(i);
+		Dbg_MsgAssert(pNode,("NULL pNode"));
+
+		uint32 ClassChecksum = 0;
+		pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+
+
+		// First the nodes that need some special processing
+		// for the "net game" state
+		switch (ClassChecksum)
+		{						   
+			// Note - Rails are no longer added here, but in the AddRailsFromNodeArray, above
+			
+			case 0xb7b3bd86:  // LevelObject
+			{
+				// A "LevelObject" is like "LevelGeometry", in that it is a 3DStudio Max object
+				// which is part of the scene (like, a wall or a house)
+				// However, it's going to be movable, so it has to exist in "local" coordinate
+				// space, centered around the origin.
+				// When it is exported, the original geometry is just tranlated to the origin, and left there
+				// later we will create a clone of this object, which will be be movable instance
+				// but the original is left alone. So, we always turn off the original instance
+				// of a LevelObject, before we create any instances. The creation is done via
+				// ScriptCreateFromNodeIndex, either below (if createdAtStart), 
+				// or via a script command at some indeterminate time in the future.
+				uint32 checksumName = 0;
+				pNode->GetChecksum( 0xa1dc81f9 /*Name*/, &checksumName);
+//				Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				if (p_sector)
+				{
+						p_sector->SetActive(false);
+				}
+				
+
+				break;
+			}
+			
+			case 0x8e6b02ad:  // railnode
+			{
+				pNode->RemoveComponent(CRCD(0x9d2d0915,"angles"));
+				pNode->RemoveComponent(CRCD(0x7321a8d6,"type")); 	// Type is no longer used, as "TerrainType" suffices
+				break;
+			}
+
+			case CRCC(0x30c19600, "ClimbingNode"):
+			{
+				break;
+			}
+			
+			case 0xbf4d3536:  // LevelGeometry
+			{
+				
+				////////////////////////////////////////////////////////////////////////////////////
+				// Test:  if it's a level object, then remove the angles, collisionmode and position
+				pNode->RemoveComponent(CRCD(0x2d7e583b,"collisionmode"));
+//				pNode->RemoveComponent(CRCD(0xb9d31b0a,"position"));
+//				pNode->RemoveComponent(CRCD(0x9d2d0915,"angles"));
+				////////////////////////////////////////////////////////////////////////////////////
+
+				
+				// first get the sector indicated by this level geometry
+				uint32 checksumName = 0;
+				pNode->GetChecksum( 0xa1dc81f9 /*Name*/, &checksumName);
+				if (checksumName == 0)
+				{
+					Script::PrintContents(pNode);
+					printf("FAILED BECAUSE Node %d is LevelGeometry, but has no Name=....\n",i);
+					fail = true;
+				}
+				//Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				
+				//Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+				
+				if (p_sector)
+				{
+					 
+					// The park editor takes care of it's own visibility								
+					// 
+
+					   
+					if (Ed::CParkEditor::Instance()->UsingCustomPark()) 		// is it a custom park???
+					{
+						if ( !( pNode->ContainsFlag( 0x7c2552b9/*"CreatedAtStart"*/  ) ) || skate_mod->ShouldBeAbsentNode( pNode ) )
+						{
+							// if this assertion fires, you are probably try to extend the park
+							// editor to have some sectors be not "createdatstart"
+							// which is not currently supported, as the park editor sets up it;s
+							// own visibility and "active" state for things like:
+							//   - restart nodes
+							//   - the cursor
+							//   - gap pieces
+							// so you would need to re-work it so they were correctly flagged 
+							// as no created at start, and then remove this whole "UsingCustomPark" special case
+							// This is also done in the logic around the call to ScriptCreateFromNodeIndex, below, in this function.
+							Dbg_MsgAssert(0,("Park ed levelgeom not set created at start"));
+						}					
+					}
+					else
+					{
+						// The sector will already be active and visible, since no flags will be set...
+						// but if we're not created at start, set the non-collide and invisible flags:
+						if ( !( pNode->ContainsFlag( 0x7c2552b9/*"CreatedAtStart"*/  ) ) || skate_mod->ShouldBeAbsentNode( pNode )
+							 )
+						{
+							p_sector->SetActive(false);
+						}
+						else
+						{
+							// Mick: as ParseNodeArray can get called multiple times
+							// on the same node array
+							// we need to clear any KILLED and INVIVIBLE flags
+							// that might have been set in the previous session
+							p_sector->SetVisibility(0xffffffff);
+							p_sector->SetActive(true);
+							p_sector->SetCollidable(true);
+						}
+					}
+					
+					// If it's an occluder, then we need to flag it as such
+					if (  pNode->ContainsFlag( 0x4e549fe1/*"Occluder"*/   )	)
+					{
+//						printf ("Occluding node %d, <%s>\n",i,Script::FindChecksumName(checksumName));
+						// This object is an occluder
+						// so need to flag it as so
+						p_sector->SetOccluder(true);				
+						
+						// For now, we make all occluders invisble, so we can see through them
+						//printf ("WARNING:   Occluders are being made invisible!!!!!!!\n");											
+						//printf ("WARNING:   Occluders are being made invisible!!!!!!!\n");											
+						//printf ("WARNING:   Occluders are being made invisible!!!!!!!\n");											
+						p_sector->SetActive(false);
+
+						// If this occluder is not created at start, disable it.
+						if( !pNode->ContainsFlag( 0x7c2552b9 /*"CreatedAtStart"*/  ))
+						{
+							Nx::CEngine::sEnableOcclusionPoly( checksumName, false );
+						}
+					}
+					
+					// Set the light group (usually will be "outdoor")
+					uint32	light_group = CRCD(0xe3714dc1,"outdoor");			 // default to OutDoors
+					pNode->GetChecksum(CRCD(0x7e4813a7,"lightgroup"),&light_group);
+					p_sector->SetLightGroup(light_group);
+				}
+				else
+				{
+					// Only fail in non-multiplayer games
+					// as it's quite valid for a sector to not exist in a network game
+					// as they are stripped out at SceneConv time 
+					if (!skate_mod->IsMultiplayerGame())
+					{
+						//printf("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName));
+						printf("FAILED BECAUSE: Cannot find level geometry or collision for node %d \nMaybe there are spaces in or after the name of the Max object (%s)?\nOr maybe the .QN file does not match the .SCN file checked into Perforce?\n"
+									   ,i,Script::FindChecksumName(checksumName));
+						fail = true;
+					}
+				}
+	
+				bool is_trick_object = pNode->ContainsComponentNamed( "TrickObject" );
+	
+				if (!Config::CD())
+				{
+					bool debug_graffiti = Script::GetInt( "create_all_trick_objects", false );
+					if ( debug_graffiti )
+						is_trick_object = true;
+				}		
+
+				// Only add trick objects is they are not flagged as absentinnetgames
+				if ( !skate_mod->ShouldBeAbsentNode( pNode ) && is_trick_object )
+				{
+					Mem::PushMemProfile("Trick Object Clusters");
+					// get the node name
+					uint32 checksumName;
+					pNode->GetChecksum( "name", &checksumName, true );
+	
+					uint32 clusterName = checksumName;
+					pNode->GetChecksum( "Cluster", &clusterName, false );
+					skate_mod->GetTrickObjectManager()->AddTrickCluster( clusterName );
+						
+					// add the environment object to this cluster
+					skate_mod->GetTrickObjectManager()->AddTrickObjectToCluster( checksumName, clusterName );
+					Mem::PopMemProfile();
+				}
+				break;
+			}
+			default:
+				break;
+		}	
+		
+		// The following nodes are capable of being ignored in net games		
+		
+		if ( skate_mod->ShouldBeAbsentNode( pNode ) )
+		{
+			// Don't load, as it's a net game
+		}	
+		else 
+		{
+		
+			if (ClassChecksum == 0xbf4d3536 && Ed::CParkEditor::Instance()->UsingCustomPark())  // LevelGeometry
+			{
+				// Level geometry in park editor is ignored, see above.
+			}
+			else
+			{
+				
+				if (ClassChecksum == 0x8470f2e) // checksum 'ProximNode'
+				{
+					Mem::PushMemProfile("Proximity Nodes");
+					Obj::Proxim_AddNode( i, pNode->ContainsFlag( 0x7c2552b9/*"CreatedAtStart"*/ ) );
+					Mem::PopMemProfile();
+				}
+				
+				if (ClassChecksum == 0xd64dc7c2) // checksum 'EmitterObject'
+				{
+					Mem::PushMemProfile("Emitter Objects");
+					Obj::CEmitterManager::sAddEmitter( i, pNode->ContainsFlag( 0x7c2552b9/*"CreatedAtStart"*/ ) );
+					Mem::PopMemProfile();
+				}
+
+				if ( pNode->ContainsFlag( 0x7c2552b9 /*"CreatedAtStart"*/ ) )
+				{
+					ScriptCreateFromNodeIndex( i );
+				}
+			}
+		}				
+	}	
+	
+	Dbg_MsgAssert(!fail, ("ParseNodeArray FAILED, see above for reasons labled FAILED BECAUSE \n"));
+	
+	Obj::Proxim_AddedAll();
+	p_rail_man->AddedAll();
+	Mdl::Skate::Instance()->GetGapChecklist()->FindGaps();
+
+	/*
+	// STUPID HACK
+	Ed::ParkEditor* park_ed = Ed::ParkEditor::Instance();
+	if (park_ed->IsInitialized() && park_ed->GameGoingOrOutsideEditor())
+		park_ed->MakeStuffInvisibleForGameplay();
+	*/
+	
+// Mick:
+// Level lights need to be created AFTER the node array is parsed, so that the 
+// lightgroups are set on the level geometry
+// otherwise geometry nodes that occur after this one will default to "outdoor"	
+
+	for (i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *pNode=pNodeArray->GetStructure(i);
+		Dbg_MsgAssert(pNode,("NULL pNode"));
+
+		uint32 ClassChecksum = 0;
+		pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+
+
+		// First the nodes that need some special processing
+		// for the "net game" state
+		if (ClassChecksum == CRCD(0xa0e52802,"LevelLight"))
+		{						   
+			// Will need to check a parameter here to see if the light is flagged to affect objects.
+			bool affects_objects	= !pNode->ContainsFlag( CRCD( 0xe1509e4f, "ExcludeSkater" ));
+			bool affects_geometry	= !pNode->ContainsFlag( CRCD( 0x6f050e18, "ExcludeLevel" ));
+
+			// Get the name checksum of this node, since this will be used as the link between the node and the Scene Light.
+			uint32 name_checksum = SkateScript::GetNodeNameChecksum( i );
+
+			Mth::Vector pos;
+			SkateScript::GetPosition( pNode, &pos );
+
+			// Default values.
+			float intensity		= 100.0f;
+			float inner_radius	= 300.0f;
+			float outer_radius	= 300.0f;
+
+			pNode->GetFloat( CRCD( 0x2689291c, "Brightness" ), &intensity );	
+			pNode->GetFloat( CRCD( 0x8d2c287f, "InnerRadius" ), &inner_radius );
+			pNode->GetFloat( CRCD( 0x833950cc, "OuterRadius" ), &outer_radius );
+
+			if( !pNode->ContainsFlag( CRCD( 0x7c2552b9, "CreatedAtStart" )))
+			{
+				// This light is not supposed to be active at startup.
+			}
+
+			// Lights default to pure white in the absence of any color information.
+			Image::RGBA col( 0xFF, 0xFF, 0xFF, 0x00 );
+
+			Script::CArray *p_array;
+			pNode->GetArray( CRCD( 0x99a9b716, "Color" ), &p_array );
+			if( p_array )
+			{
+				col.r = p_array->GetInteger( 0 );
+				col.g = p_array->GetInteger( 1 );
+				col.b = p_array->GetInteger( 2 );
+			}
+
+			if( affects_objects )
+			{
+				Nx::CSceneLight *p_new_light = Nx::CLightManager::sAddSceneLight();
+				if( p_new_light )
+				{
+					p_new_light->SetNameChecksum( name_checksum );
+					p_new_light->SetLightPosition( pos );
+
+					// Set the color, radius and intensity.
+					p_new_light->SetLightIntensity( intensity * 0.01f );
+					p_new_light->SetLightColor( col );
+					p_new_light->SetLightRadius( outer_radius );
+				}
+				else
+				{
+					Dbg_MsgAssert(0,("Too many Scene lights"));
+				}
+			}
+
+			if( affects_geometry )
+			{
+				Nx::CLightManager::sAddVCLight(name_checksum,pos,intensity,col,outer_radius);
+			}
+		}
+	}	
+	
+
+	Mem::PopMemProfile(/*"ParseNodeArray"*/);
+
+	return true;
+}	
+
+static char LastNodeArrayLoaded[128];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadNodeArray | Will load node array from the file specified 
+// @uparm "string" | File name to load
+bool ScriptLoadNodeArray(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mem::PushMemProfile("LoadNodeArray");
+	
+	// Remove any spawned scripts that might have been left over from the previous level.
+	Script::DeleteSpawnedScripts();
+		
+	
+	const char *pFileName=NULL;
+	pParams->GetText(NONAME,&pFileName);
+	Dbg_MsgAssert(pFileName,("LoadNodeArray requires a file name."));
+
+	strcpy(LastNodeArrayLoaded,pFileName);
+
+	// Load the QB
+	SkateScript::LoadQB(pFileName);
+	
+	// Ensure it has a node array in it
+	Script::CArray* pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+	Dbg_MsgAssert(pNodeArray,("No NodeArray found in %s",pFileName));
+
+	for ( uint32 i = 0; i < pNodeArray->GetSize(); i++ )
+	{
+		Script::CStruct* pStructure = pNodeArray->GetStructure(i);
+		pStructure->AddInteger("nodeIndex", i);
+	}
+
+	if (pParams->ContainsFlag("park_editor"))
+	{
+		// PARKED4:
+		// Nothing needs doing here, we don't preload any models in the park editor
+	}
+	else
+	{
+		// not a park editor QB, parse as normal
+//		ScriptPreloadModels( NULL, NULL );
+	}
+	Mem::PopMemProfile(/*"LoadNodeArray"*/);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ReLoadNodeArray | Will reload the last node array you loaded, 
+// without parsing it, and without deleting anything. 
+// Note, this will load directly from the .qb, so there is NO NEED to rebuild the .pre files. 
+// Using this command by itself could be used if you are just altering 
+// stuff like trigger scripts.  However, as it does not respawn anything, 
+// you have the potential to mess things up.  Give it a go though, you can 
+// always just do a Select+Circle to make sure. 
+bool ScriptReLoadNodeArray(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	
+	// We try to use the Debug heap, as we assume we will be fragmenting memory like a jackass
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+	
+	const char *pFileName=LastNodeArrayLoaded;
+	// Load the QB
+	SkateScript::LoadQB(pFileName);
+	
+	// Ensure it has a node array in it
+	Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+	
+	Dbg_MsgAssert(pNodeArray,("No NodeArray found in %s",pFileName));
+		
+	// renumber node indices
+	for ( uint32 i = 0; i < pNodeArray->GetSize(); i++ )
+	{
+		Script::CStruct* pStructure = pNodeArray->GetStructure(i);
+		pStructure->AddInteger("nodeIndex", i);
+	}
+	
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetBackgroundColor | Sets the background color. Each component 
+// is in the range 0-255.  The alpha value currently is not used. 
+// @parmopt int | r | 0 | Red component
+// @parmopt int | g | 0 | Green component
+// @parmopt int | b | 0 | Blue component
+// @parmopt int | alpha | 0 | Alpha value
+bool ScriptSetBackgroundColor(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	    
+	int Red=0;
+	int Green=0;
+	int Blue=0;
+	int Alpha=0;
+	pParams->GetInteger("r",&Red);
+	pParams->GetInteger("g",&Green);
+	pParams->GetInteger("b",&Blue);
+	pParams->GetInteger("alpha",&Alpha);
+    
+/*	
+	gfx_man->sBackgroundColor.r 	= Red;
+	gfx_man->sBackgroundColor.g = Green;
+	gfx_man->sBackgroundColor.b 	= Blue;
+	gfx_man->sBackgroundColor.a = Alpha;   
+*/
+	printf ("STUBBED ScriptSetBackgroundColor\n");	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetBSPAmbientColor | Sets the ambient color for the BSP 
+// (The environment, or the non-moving objects). 
+// NOTE:  This should be set to 0,0,0, as any other setting will result 
+// in much slowdown.  However, you can set it temporarily if you 
+// want to try out effects quickly. 
+// @parmopt int | r | 0 | Red component
+// @parmopt int | g | 0 | Green component
+// @parmopt int | b | 0 | Blue component
+// @parmopt int | alpha | 0 | Alpha value
+bool ScriptSetBSPAmbientColor(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "SetBSPAmbientColor is obsolete" );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetDFFAmbientColor | Sets the ambient color for the DFFs 
+// (The skater, cars and suchlike.).
+// @parmopt int | r | 0 | Red component
+// @parmopt int | g | 0 | Green component
+// @parmopt int | b | 0 | Blue component
+// @parmopt int | alpha | 0 | Alpha value
+bool ScriptSetDFFAmbientColor(Script::CStruct *pParams, Script::CScript *pScript)
+{			
+	Dbg_Message( "SetDFFAmbientColor is obsolete" );
+	return true;
+}
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetDFFDirectColor | Sets the directional light color 
+// for the DFFs (The skater, cars and suchlike.).   (Note:  temporary, 
+// until we get a better lighting setup). 
+// @parmopt int | r | 0 | Red component
+// @parmopt int | g | 0 | Green component
+// @parmopt int | b | 0 | Blue component
+// @parmopt int | alpha | 0 | Alpha value
+bool ScriptSetDFFDirectColor(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "SetDFFDirectColor is obsolete" );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetDynamicLightModulationFactor | A value of 0.0 means no 
+// effect (light/dark polys have no effect). 
+// A value of 1.0 means the maximum possible effect from light/dark polys 
+// These values will stay set until changed, so designers will want to
+// put these in their level startup scripts. 
+// @parm float | value | 
+// @parmopt flag | ambient | | 
+// @parmopt bool | directional | | 
+bool ScriptSetDynamicLightModulationFactor( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	
+
+	bool	ok;
+	int		index	= 0;
+	float	value	= 0.0f;
+	if( pParams->GetFloat( "value", &value ))
+	{
+		if( pParams->ContainsFlag( "ambient" ))
+		{
+			Nx::CLightManager::sSetAmbientLightModulationFactor(value);
+		}
+		else
+		{
+			ok = pParams->GetInteger( "directional", &index );
+			Nx::CLightManager::sSetDiffuseLightModulationFactor(index, value);
+		}
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetClippingDistances | Sets the clipping distances in inches. 
+// “Near” should generally be left at 12, “Far” should be whatever is 
+// appropriate for your level. Example: 
+// SetClippingDistances Near=12 Far=8000  
+// @parm float | Near |
+// @parm float | Far | 
+bool ScriptSetClippingDistances(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	float Near=Gfx::Camera::vDEFAULT_FARZ;
+	float Far=Gfx::Camera::vDEFAULT_NEARZ;
+	pParams->GetFloat("Near",&Near);
+	pParams->GetFloat("Far",&Far);
+
+	// This is a bit of a patch,
+	// cycle over the two possible cameras, and set the clipping dist
+	for (int cam = 0; cam < 2; cam ++)
+	{
+		
+		Gfx::Camera *p_cam = Nx::CViewportManager::sGetCamera(cam);
+		if (p_cam)
+		{
+			p_cam->SetNearFarClipPlanes(Near,Far);
+		}
+	}
+	//Gfx::Camera::SetNearFarClipPlanes(Near,Far);
+
+
+	// Also Gamecube uses different near and far z clip planes, and has a fog far plane independent of the
+	// camera far plane. So indicate here what the fog far plane for Gamecube should be.
+//#	ifdef __PLAT_NGC__
+//	NsRender::setFogFar( Far );
+//#	endif // __PLAT_NGC__
+
+
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetTrivialFarZClip | Sets the Z clipping distance 
+// SetTrivialFarZClip on        --- to turn it on
+// SetTrivialFarZClip off       --- to turn it off 
+// @flag on | Pass this in to set the trivial Z clipping
+// distance to true or false
+bool ScriptSetTrivialFarZClip(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	bool on = false;
+
+	if( pParams->ContainsFlag( "on" ))
+	{
+		on = true;
+	}
+
+	 Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+	gfx_manager->SetTrivialFarZClip( on );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EnableFog | 
+bool ScriptEnableFog( Script::CStruct *pParams,
+					  Script::CScript *pScript )
+{
+	Nx::CFog::sEnableFog(true);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DisableFog | 
+bool ScriptDisableFog( Script::CStruct *pParams,
+					  Script::CScript *pScript )
+{
+	Nx::CFog::sEnableFog(false);
+
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetFogDistance | Sets the "near" distance (in inches) 
+// at which fogging will begin.  (Polys closer than the near distance
+// will not be fogged.  Beyond the near distance, they will become
+// increasingly fogged until they reach maximum fogging at the far
+// clip distance -- see SetClippingDistances).
+// @parm float | distance | distance in inches
+bool ScriptSetFogDistance( Script::CStruct *pParams,
+						   Script::CScript *pScript )
+{
+	
+	float fog_dist = 0.0f;
+	pParams->GetFloat( "distance", &fog_dist );
+
+	Nx::CFog::sSetFogNearDistance(fog_dist);
+
+	// Garrett: Not sure if this is needed anymore, but leaving it there
+	// in case some other platforms need it.
+	// This is a bit of a patch,
+	// cycle over the two possible cameras, and set the clipping dist
+	for ( int cam = 0; cam < 2; cam++ )
+	{
+		Gfx::Camera *p_cam = Nx::CViewportManager::sGetCamera(cam);
+		if ( NULL != p_cam )
+		{
+			p_cam->SetFogNearPlane( fog_dist );
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetFogExponent | Sets the fog exponent.  The exponent
+// value determines the "spread" of the fog, or how far out you
+// must go before you reach maximum fog.  This component of fog
+// will probably change in the near future.
+// @parm float | exponent | fog exponent
+bool ScriptSetFogExponent( Script::CStruct *pParams,
+						   Script::CScript *pScript )
+{
+	float exponent;
+	if (!pParams->GetFloat( "exponent", &exponent ))
+	{
+		Dbg_MsgAssert(0, ("Can't find parameter 'exponent'"));
+	}
+
+	Nx::CFog::sSetFogExponent(exponent);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetFogColor | Sets the color that will be used for
+// atmospheric fogging.  Color components are in the range 0-255. 
+// The alpha component is in the range of 0-128.
+// @parmopt int | r | 255 | red component
+// @parmopt int | g | 255 | green component
+// @parmopt int | b | 255 | blue component
+// @parmopt int | a | 128 | alpha component (opacity of fog)
+bool ScriptSetFogColor( Script::CStruct *pParams,
+						Script::CScript *pScript )
+{
+	
+	int red = 255;
+	int green = 255;
+	int blue = 255;
+	int alpha = 128;
+	pParams->GetInteger( "r", &red );
+	pParams->GetInteger( "g", &green );
+	pParams->GetInteger( "b", &blue );
+	pParams->GetInteger( "a", &alpha );
+
+	Image::RGBA rgba(red, green, blue, alpha);
+
+	Nx::CFog::sSetFogRGBA( rgba );
+		
+	return true;
+}
+
+// @script | SetUVWibbleParams | Sets the UV wibble parameters of a sector
+// @parm name | sector | name of sector
+// @parm float | u_vel   | u velocity
+// @parm float | u_amp   | u amplitude
+// @parm float | u_freq  | u frequency
+// @parm float | u_phase | u phase
+// @parm float | v_vel   | v velocity
+// @parm float | v_amp   | v amplitude
+// @parm float | v_freq  | v frequency
+// @parm float | v_phase | v phase
+bool ScriptSetUVWibbleParams(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Get sector checksum
+	uint32 checksum;
+	if (!pParams->GetChecksum("sector", &checksum))
+	{
+		Dbg_MsgAssert(0, ("Can't find parameter sector"));
+	}
+
+	// Find sector
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksum);
+	Dbg_MsgAssert(p_sector, ("Can't find sector %x in world", checksum));
+
+	// Extract all the parameters
+    float u_velocity;
+    float u_amplitude;
+    float u_frequency;
+    float u_phase;
+    float v_velocity;
+    float v_amplitude;
+    float v_frequency;
+    float v_phase;
+	bool found_all;
+
+	found_all  = pParams->GetFloat("u_vel", &u_velocity);
+	found_all &= pParams->GetFloat("u_amp", &u_amplitude);
+	found_all &= pParams->GetFloat("u_freq", &u_frequency);
+	found_all &= pParams->GetFloat("u_phase", &u_phase);
+	found_all &= pParams->GetFloat("v_vel", &v_velocity);
+	found_all &= pParams->GetFloat("v_amp", &v_amplitude);
+	found_all &= pParams->GetFloat("v_freq", &v_frequency);
+	found_all &= pParams->GetFloat("v_phase", &v_phase);
+
+	Dbg_MsgAssert(found_all, ("Missing one or more of the wibble parameters.  Must fill them all out."));
+
+	p_sector->SetUVWibbleParams(u_velocity, u_amplitude, u_frequency, u_phase, v_velocity, v_amplitude, v_frequency, v_phase);
+
+	return true;
+}
+
+// @script | EnableExplicitUVWibble | Enables explicit setting of the UV wibble offsets.
+// To set the offsets, call SetUVWibbleOffsets.
+// @parm name | sector | name of sector
+bool ScriptEnableExplicitUVWibble(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Get sector checksum
+	uint32 checksum;
+	if (!pParams->GetChecksum("sector", &checksum))
+	{
+		Dbg_MsgAssert(0, ("Can't find parameter sector"));
+	}
+
+	// Find sector
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksum);
+	Dbg_MsgAssert(p_sector, ("Can't find sector %x in world", checksum));
+
+	p_sector->UseExplicitUVWibble(true);
+
+	return true;
+}
+
+// @script | DisableExplicitUVWibble | Disables explicit setting of the UV wibble offsets.
+// @parm name | sector | name of sector
+bool ScriptDisableExplicitUVWibble(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Get sector checksum
+	uint32 checksum;
+	if (!pParams->GetChecksum("sector", &checksum))
+	{
+		Dbg_MsgAssert(0, ("Can't find parameter sector"));
+	}
+
+	// Find sector
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksum);
+	Dbg_MsgAssert(p_sector, ("Can't find sector %x in world", checksum));
+
+	p_sector->UseExplicitUVWibble(false);
+
+	return true;
+}
+
+// @script | SetUVWibbleOffsets | Sets the UV wibble offsets explicitly
+// @parm name | sector | name of sector
+// @parm float | u_off | u offset
+// @parm float | v_off | v offset
+bool ScriptSetUVWibbleOffsets(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Get sector checksum
+	uint32 checksum;
+	if (!pParams->GetChecksum("sector", &checksum))
+	{
+		Dbg_MsgAssert(0, ("Can't find parameter sector"));
+	}
+
+	// Find sector
+	Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksum);
+	Dbg_MsgAssert(p_sector, ("Can't find sector %x in world", checksum));
+
+	// Extract all the parameters
+    float u_offset;
+    float v_offset;
+	bool found_all;
+
+	found_all  = pParams->GetFloat("u_off", &u_offset);
+	found_all &= pParams->GetFloat("v_off", &v_offset);
+
+	Dbg_MsgAssert(found_all, ("Missing one or more of the wibble offset parameters.  Must fill them all out."));
+
+	p_sector->SetUVWibbleOffsets(u_offset, v_offset);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetPreferenceString(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 field_id, pref_type;
+	const char* ui_string;
+	CStruct* pass_back_params;
+    
+	pass_back_params = pScript->GetParams();
+
+	pParams->GetChecksum( NO_NAME, &field_id );
+	pParams->GetChecksum( "pref_type", &pref_type );
+
+	pPreferences = Mdl::GetPreferences(pref_type); // This will assert if it cannot find the prefs
+	
+	pStructure = pPreferences->GetPreference( field_id );
+	Dbg_MsgAssert(pStructure, ("\n%s\nCan't get preference 0x%x (%s)",pScript->GetScriptInfo(),field_id, Script::FindChecksumName(field_id)));
+	
+	//pParams->AddStructure( "preference", pStructure );
+	pStructure->GetString( "ui_string", &ui_string );
+	pass_back_params->AddString( "ui_string", ui_string );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetPreferencePassword(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 field_id, pref_type;
+	const char* ui_string;
+	CStruct* pass_back_params;
+	static char	s_password_string[32];
+	uint32 i;
+    
+	pass_back_params = pScript->GetParams();
+
+	pParams->GetChecksum( NO_NAME, &field_id );
+	pParams->GetChecksum( "pref_type", &pref_type );
+
+	pPreferences = Mdl::GetPreferences(pref_type); // This will assert if it cannot find the prefs
+
+	pStructure = pPreferences->GetPreference( field_id );
+	//pParams->AddStructure( "preference", pStructure );
+	pStructure->GetString( "ui_string", &ui_string );
+	for( i = 0; i < strlen( ui_string ); i++ )
+	{
+		s_password_string[i] = '*';
+	}
+	
+	Dbg_Assert( i < 32 );
+	s_password_string[i] = '\0';
+
+	pass_back_params->AddString( "password_string", s_password_string );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetPreferenceChecksum(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 field_id, checksum, pref_type;
+	CStruct* pass_back_params;
+    
+	pass_back_params = pScript->GetParams();
+
+	pParams->GetChecksum( NO_NAME, &field_id );
+	pParams->GetChecksum( "pref_type", &pref_type );
+
+	pPreferences = Mdl::GetPreferences(pref_type); // This will assert if it cannot find the prefs
+
+	pStructure = pPreferences->GetPreference( field_id );
+	//pParams->AddStructure( "preference", pStructure );
+	pStructure->GetChecksum( "checksum", &checksum );
+	pass_back_params->AddChecksum( "checksum", checksum );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetPreference | sets the prefs to those specified in
+// the params structure
+// @parm name | field | the field id
+// @parm structure | params | 
+bool ScriptSetPreference(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	// gets either network or splitscreen prefs, as appropriate
+	Prefs::Preferences* pPreferences = Mdl::GetPreferences( pParams );
+	
+	uint32 field_id;
+	pParams->GetChecksum( "field", &field_id, true );
+
+	Script::CStruct* pSubParams;
+	pParams->GetStructure( "params", &pSubParams, true );
+	pPreferences->SetPreference( field_id, pSubParams );
+
+#ifdef __NOPT_ASSERT__
+	Script::PrintContents(pSubParams);
+#endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PreferenceEquals | returns true if the actual preference value
+// for the given field equals the test value
+// @parm name | field | the field id
+// @parm name | equals | test value
+bool ScriptPreferenceEquals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	// gets either network or splitscreen prefs, as appropriate
+	Prefs::Preferences* pPreferences = Mdl::GetPreferences( pParams );
+
+	uint32 field_id;
+	pParams->GetChecksum("field", &field_id, true);
+
+	uint32 test_value;
+	pParams->GetChecksum( "equals", &test_value, true );
+
+	Script::CStruct* pStructure = pPreferences->GetPreference( field_id );
+	Dbg_Assert(pStructure);
+
+	uint32 actual_value;
+	pStructure->GetChecksum( "checksum", &actual_value, true );
+
+	return ( test_value == actual_value );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetCamera | 
+bool ScriptResetCamera(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	// This is a bit of a patch,
+	// cycle over the two possible cameras, and reset them
+	for (int cam = 0; cam < 2; cam ++)
+	{
+		
+		Gfx::Camera *p_cam = Nx::CViewportManager::sGetCamera(cam);
+		if (p_cam)
+		{
+			printf ("TODO: ScriptResetCamera not implemented\n");
+		}
+	}
+	//Gfx::Camera::SetNearFarClipPlanes(Near,Far);
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetVolumeFromValue | sets the volume according to the slider
+// @uparm sfxvol | the volume to set (either sfxvol or cdvol)
+// @parm name | id | the slider id used to change the volume
+bool ScriptSetVolumeFromValue( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	/*
+	uint32 whichParam;
+	pParams->GetChecksum( NONAME, &whichParam, true );
+
+	uint32 slider_id;
+	pParams->GetChecksum( "id", &slider_id, true );
+
+	Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+	Front::SliderMenuElement* pSliderElement = static_cast( menu_factory->GetMenuElement( slider_id, true ) );
+	Dbg_Assert( pSliderElement );
+
+	switch ( whichParam )
+	{
+		case 0x8d6f6554: // "sfxvol"
+		{
+			 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+			sfx_manager->SetMainVolume( ( float )( 10 * pSliderElement->GetValue( ) ) );
+		}
+		break;
+
+		case 0xa5162bd2: // "cdvol"
+		{
+			// still set the main music volume even if in the front end!!!
+			Pcm::SetVolume( ( float )( 10 * pSliderElement->GetValue( ) ) );
+
+			// make sure the music is playing and shit...
+			Pcm::PauseMusic( false );
+		}
+		break;
+
+		default:
+		{
+			// unrecognized sound parameter
+			Dbg_MsgAssert( 0, ( "Unrecognized sound parameter" ) );
+		}
+		break;
+	}
+	*/
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetValueFromVolume | 
+// @uparm sfxvol | the volume you're interested in (either sfxvol, cdvol, or cutvol)
+bool ScriptGetValueFromVolume( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+
+	uint32 whichParam;
+	pParams->GetChecksum( NONAME, &whichParam, true );
+
+	switch ( whichParam )
+	{
+		case 0x8d6f6554: // "sfxvol"
+		{
+			 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+			pScript->GetParams()->AddComponent( Script::GenerateCRC( "value" ), ESYMBOLTYPE_INTEGER, ( (int) sfx_manager->GetMainVolume() ) / 10 );
+		}
+		break;
+		
+		case 0xa5162bd2: // "cdvol"
+		{
+			pScript->GetParams()->AddComponent( Script::GenerateCRC( "value" ), ESYMBOLTYPE_INTEGER, ( (int) Pcm::GetVolume() ) / 10 );
+		}
+		break;
+
+		case 0xe32f3525: // "cutvol"
+		{
+			pScript->GetParams()->AddComponent( Script::GenerateCRC( "value" ), ESYMBOLTYPE_INTEGER, ( (int) Pcm::GetMusicStreamVolume() ) / 10 );
+		}
+		break;
+
+		default:
+		{
+			// unrecognized sound parameter
+			Dbg_MsgAssert( 0, ( "Unrecognized sound parameter" ) );
+		}
+		break;
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSliderValue | 
+// @parm name | id | the slider id
+// @parm int | value | value to assign to slider
+bool ScriptSetSliderValue( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+
+	/*
+	uint32 slider_id;
+	pParams->GetChecksum( "id", &slider_id, true );
+
+	int value;
+	pParams->GetInteger( "value", &value, true );
+	
+	Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+	Front::SliderMenuElement* pSliderElement = static_cast( menu_factory->GetMenuElement( slider_id, true ) );
+	Dbg_Assert( pSliderElement );
+	pSliderElement->SetValue( value );
+	*/
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetIconTexture | 
+// @parm name | id | menuelement
+// @parm string | texture | the texture name
+bool ScriptSetIconTexture(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	/*
+	uint32 id;
+	pParams->GetChecksum("id", &id, true);
+	
+	Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+	Front::IconMenuElement* pIconElement = static_cast( menu_factory->GetMenuElement( id, true ) );
+
+	if ( pIconElement )
+	{
+		const char* textureName;
+		pParams->GetText("texture", &textureName, true);
+		pIconElement->SetTexture( textureName );
+	}
+	*/
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+// @script | SetMovementVelocity | Sets the movement (translational) 
+// speed of the camera in the viewer. The number is in terms of inches per second. 
+// @parmopt float | vel | 400 | velocity in inches per second
+bool ScriptSetMovementVelocity( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	float vel = 400;
+	pParams->GetFloat( NONAME, &vel );
+
+	Mdl::CViewer::sSetMovementVelocity( vel );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetRotateVelocity | Sets the rotational speed of the 
+// camera in the viewer. The number is in terms of degrees per second. 
+// @parmopt float | vel | 120 | Rotational speed in degrees per second
+bool ScriptSetRotateVelocity( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	float vel = 120;
+	pParams->GetFloat( NONAME, &vel );
+
+	Mdl::CViewer::sSetRotateVelocity( vel );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | OnReload | 
+// This is a script function that doesn't do anything, it is just used to indicate what should
+// happen if the script gets reloaded at runtime. It needs to be the first 
+bool ScriptOnReload(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetEngine | resets the engine
+bool ScriptResetEngine(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	 Mlp::Manager * mlp_manager =  Mlp::Manager::Instance();
+	mlp_manager->QuitLoop();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleMetrics | Toggle metrics - use before ToggleMemMetrics
+bool ScriptToggleMetrics(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	 Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+	gfx_manager->ToggleMetrics();
+    //Script::RunScript( "show_poly_count" );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleNetMetrics | 
+bool ScriptToggleNetMetrics(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	 GameNet::Manager * gamenet_manager =  GameNet::Manager::Instance();
+	gamenet_manager->ToggleMetrics();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleVRAMViewer | Toggle VRAM viewer
+bool ScriptToggleVRAMViewer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	 Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+	gfx_manager->ToggleVRAMViewer();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DumpVRAMUsage | Spits out a file detailing VRAM usage 
+// separately for all four contexts
+bool ScriptDumpVRAMUsage(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	 Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+	gfx_manager->DumpVRAMUsage();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleLightViewer | Toggle the light viewer
+bool ScriptToggleLightViewer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "ToggleLightViewer is obsolete" );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | debugrendermode | currently unsupported
+bool ScriptSetDebugRenderMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "debugrendermode is obsolete" );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | debugtoggletextureupload | currently unsupported
+bool ScriptToggleTextureUpload(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "debugtoggletextureupload is obsolete" );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | debugtoggletexturedraw | currently unsupported
+bool ScriptToggleTextureDraw(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "debugtoggletexturedraw is obsolete" );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | debugtogglepointdraw | currently unsupported
+bool ScriptTogglePointDraw(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "debugtogglepointdraw is obsolete" );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | debugrendertest1 | old function?  returns true if mbr_nsDebugTest1
+// (check cfuncs.cpp) is set
+bool ScriptRenderTest1(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+#ifdef __PLAT_NGPS__
+//	mbr_nsDebugTest1=(mbr_nsDebugTest1)?0:1;
+#endif
+	return true;
+}
+// @script | debugrendertest2 | old function?  returns true if mbr_nsDebugTest2
+// (check cfuncs.cpp) is set
+bool ScriptRenderTest2(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+#ifdef __PLAT_NGPS__
+//	mbr_nsDebugTest2=(mbr_nsDebugTest2)?0:1;
+#endif
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetViewMode | Debugging function to toggle the camera mode,
+// switching between normal, free with fixed skater, and free with moving skater
+// usually triggers with Select+X (See buttonscripts.q)
+bool ScriptSetViewMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int viewMode;
+	pParams->GetInteger( NONAME, &viewMode, Script::ASSERT );
+
+	Dbg_Assert( viewMode >= 0 && viewMode < Obj::NUM_VIEW_MODES );
+
+	s_view_mode = viewMode;
+	Mdl::CViewer::sSetViewMode( s_view_mode );
+	// Handle toggling to different cameras based on view mode
+	
+	switch (s_view_mode )
+	{
+		case Obj::GAMEPLAY_SKATER_ACTIVE:
+			SetActiveCamera(CRCD(0x967c138c,"skatercam0"),0, false);			
+			break;
+		default:
+			SetActiveCamera(CRCD(0xeb17151b,"viewer_cam"),0, true);						
+			break;		
+	}
+	
+		
+	Obj::CSkater* pSkater;
+	for ( int i = 0; i < 8; i++ )
+	{
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		pSkater = skate_mod->GetSkater(i);						   
+		if ( pSkater )			// Skater might not exist
+		{
+			pSkater->SetViewMode( (Obj::EViewMode)s_view_mode );
+		}
+	}
+	
+	// clear out the viewer object, if any
+	Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+	if ( pViewer )
+	{
+		pViewer->RemoveViewerObject();   	
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleMetricItem | ToggleMetricItem will turn on or off 
+// individual items in the metric display.
+// The “item” parameter is one of: 
+// METRIC_TIME 
+// METRIC_ARENAUSAGE             
+// METRIC_TOTALPOLYS 
+// METRIC_POLYSPROC 
+// METRIC_VERTS 
+// METRIC_RESOURCEALLOCS 
+// METRIC_TEXTUREUPLOADS 
+// METRIC_VU1 
+// METRIC_DMA1 
+// METRIC_DMA2 
+// METRIC_VBLANKS 
+// METRIC_DRAWTIME 
+// METRIC_IHANDLERTIME 
+// METRIC_SKYCACHE 
+// METRIC_VIDEOMODE 
+// METRIC_VRAMUSAGE 
+// METRIC_MEMUSED 
+// METRIC_MEMFREE 
+// METRIC_REGIONINFO 
+// @parmopt int | item | 0 | 
+bool ScriptToggleMetricItem( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	
+/*
+	uint32 metricFlags = 0;
+	pParams->GetInteger( "item", ( int * )&metricFlags );
+
+	 Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+
+	Gfx::Metrics* pMetrics = gfx_manager->GetMetrics();
+	pMetrics->ToggleMetricItem( metricFlags );
+*/
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleMemMetrics | This will replace the PS2 specific
+// metrics with a list of the all the memory heaps, together with
+// the amount of fragmentation, and the amount of free memory. Note, 
+// you have to use ToggleMetrics to actually see the metrics before
+// using ToggleMemMetrics 
+// See the online doc for a full explanation of the various heaps
+bool ScriptToggleMemMetrics( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	/* 
+	 Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+	Gfx::Metrics* pMetrics = gfx_manager->GetMetrics();
+	pMetrics->ToggleMemMetrics();
+	*/
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// @script | KillAllTextureSplats | Destroys all exisiting texture splats
+bool ScriptKillAllTextureSplats( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Nx::KillAllTextureSplats();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ProximCleanup | Destroys all proxim nodes
+bool ScriptProximCleanup(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// TT12597: Clear out the proxim node pointers in the stream and sound components
+	// new fast way, just go directly to the components, if any
+	Obj::CSoundComponent* p_sound_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_SOUND ));
+	while( p_sound_component )
+	{
+		p_sound_component->ClearProximNode();
+		
+		p_sound_component = static_cast( p_sound_component->GetNextSameType());
+	}
+
+	Obj::CStreamComponent* p_stream_component = static_cast( Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_STREAM ));
+	while( p_stream_component )
+	{
+		p_stream_component->ClearProximNode();
+		
+		p_stream_component = static_cast( p_stream_component->GetNextSameType());
+	}
+
+	Obj::Proxim_Cleanup();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Cleanup | Destroys current world, texture dictionaries, objects and railnodes.
+// @flag preserve_skaters | Set this to preserve the skaters
+bool ScriptCleanup(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	//	DumpUnwindStack( 20, 0 );
+	// Wait for any async rendering to finish before we proceed	
+	Nx::CEngine::sFinishRendering();
+ 	#ifdef	__PLAT_NGPS__
+	NxPs2::AllocateExtraDMA(false);
+	#endif
+
+	Replay::DeallocateReplayMemory();
+	
+// goalmanager stuff moved here from Skate::ChangeLevel
+    Game::CGoalManager* p_GoalManager = Game::GetGoalManager();
+    Dbg_MsgAssert( p_GoalManager, ( "couldn't get GoalManager\n" ) );
+    
+    p_GoalManager->DeactivateAllGoals();
+    p_GoalManager->RemoveAllGoals();
+
+	// PARKED4:
+	Ed::CParkEditor* p_park_editor = Ed::CParkEditor::Instance();
+	p_park_editor->Cleanup();
+	p_park_editor->DeletePlayModeGapManager();
+	
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();	
+	bool preserve;
+	bool was_spawned;
+
+	Dbg_Printf( "Performing Cleanup\n" );
+
+	Gfx::DebuggeryLines_CleanUp( );	// so we remove debug lines between levels
+
+	preserve = false;
+	if( pParams )
+	{
+		preserve = pParams->ContainsFlag("preserve_skaters");
+	}	
+
+// ********************************************************************************************
+// ********************************************************************************************
+// ********************************************************************************************
+	// A hacky way of preserving the running script, since skate's cleanup will destroy all
+	// spawned scripts.
+	was_spawned = false;
+	if( pScript )
+	{
+		was_spawned = pScript->mIsSpawned;
+		pScript->mIsSpawned = false;
+	}
+	// Delete's session objects managed by the "Skate::" code module	
+	skate_mod->Cleanup( );   
+	if( pScript )
+	{
+		pScript->mIsSpawned = was_spawned;
+	}
+	
+	// Destroy other Session Objects
+// These can be recreated, no assets are unloaded
+
+	//skate_mod->GetGoalManager()->RemoveAllGoals();	
+
+	Dbg_Printf( "Destroying imposters..." );
+	Nx::CEngine::sGetImposterManager()->Cleanup();
+
+	Dbg_Printf( "Destroying misc fx..." );
+	Nx::MiscFXCleanup();
+
+	Dbg_Printf( "Clearing node-name hash table ..." );
+	SkateScript::ClearNodeNameHashTable();
+
+	// Turn off fogging...let the next level turn it back on if it wants it
+	Nx::CFog::sEnableFog(false);
+
+	Script::KillStoppedScripts();
+
+
+	Dbg_Printf( "Cleaning up terrain soundFX manager." );
+	Env::CTerrainManager::sReset();
+	Dbg_Printf( "Done.\n" );
+
+				   
+// ********************************************************************************************
+// ********************************************************************************************
+// ********************************************************************************************
+// Actual unloading of assets starts here 
+	
+// unload sond effects (just resets the SRAM pointer)				   
+	Dbg_Printf( "Cleaning up soundFX manager." );
+	 Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	sfx_manager->CleanUp( );		// Sfx manager uses a very primitive memory management system ... check it out..
+	Dbg_Printf( "Done.\n" );
+
+// Unload object models and animations													 
+	printf ("unloading assman tables ...........\n");
+	 Ass::CAssMan * ass_manager =  Ass::CAssMan::Instance();
+	ass_manager->UnloadAllTables( );
+	printf ("done unloading assman ...........\n");
+
+// ********************************************************************************************
+// ********************************************************************************************
+// ********************************************************************************************
+	Dbg_Printf( "Unloading NodeArray .QB (level .qn file)" );
+	Script::CSymbolTableEntry *p_sym=Script::LookUpSymbol(CRCD(0xc472ecc5,"NodeArray"));
+	if (p_sym)
+	{
+		SkateScript::UnloadQB(p_sym->mSourceFileNameChecksum);
+	}	
+	
+// ********************************************************************************************
+// ********************************************************************************************
+// ********************************************************************************************
+// Unload the level specific qb.
+	// Note: It's ok if s_level_specific_qb is 0, UnloadQB just won't do anything if it can't find it.
+	#if 0
+	SkateScript::UnloadQB(s_level_specific_qb);
+	s_level_specific_qb=0;
+	#else
+	for (int qb=0;qbGetLocalSkater())
+		{
+			Obj::CAnimationComponent* pAnimComponent = GetAnimationComponentFromObject( skate_mod->GetLocalSkater() );
+			pAnimComponent->SetAnimArray( Script::GenerateCRC("animload_human") );
+		}
+		Mem::Manager::sHandle().PopContext();
+		*/
+	
+	}
+	else	
+	{
+		printf("Clean up without preserve skaters\n");
+		//skate_mod->UnloadSkaters();
+		// as we're leaving a level,
+		// clean up any additional heaps we might have allocated for the other skaters...
+		Mem::Manager::sHandle().DeleteSkaterHeaps();
+	}
+
+
+	// Unload any pip pre files that are no longer being used (eg, collision)
+	const char *p_pre_name=Pip::GetNextLoadedPre();
+	while (p_pre_name)
+	{
+		const char *p_next=Pip::GetNextLoadedPre(p_pre_name);
+		if (!Pip::PreFileIsInUse(p_pre_name))
+		{
+			Pip::UnloadPre(p_pre_name);
+		}
+		p_pre_name=p_next;
+	}	
+		
+	// unload the skater parts pre file if it is currently loaded
+	File::PreMgr* pre_mgr = File::PreMgr::Instance();
+	pre_mgr->UnloadPre( "skaterparts.pre", true );			
+	
+	// Clear away any old cameras
+	Nx::CViewportManager::sDeleteMarkedCameras();
+
+	// Clear any currently running FakeLights commands.
+	Nx::CLightManager::sClearCurrentFakeLightsCommand();
+
+	// Clear away any non-permanent heaps
+	Nx::FlushParticleTextures(false);
+	
+	
+	// cleanup pathman memory
+	Obj::CPathMan* pPathMan = Obj::CPathMan::Instance();
+	pPathMan->DeallocateObjectTrackerMemory();
+
+	if( skate_mod->ShouldAllocateNetMiscHeap())
+	{
+		Dbg_Printf( "****** LEVEL: 0x%x  0x%x 0x%x\n", skate_mod->m_requested_level, skate_mod->m_cur_level, skate_mod->m_prev_level );
+		Mem::Manager::sHandle().InitNetMiscHeap();
+	}
+	else
+	{
+		Mem::Manager::sHandle().DeleteNetMiscHeap();
+	}
+
+
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	
+	// destroy the custom heaps, if they exist
+	// (TODO:  Move this to script, since it really
+	// only applies to the skateshop)
+	mem_man.DeleteNamedHeap( Script::GenerateCRC("preview"), false );
+
+	#ifdef	__NOPT_ASSERT__
+	// sanity checks for fragmentation
+	Mem::Heap* heap = mem_man.BottomUpHeap();
+	int fragmentation = heap->mFreeMem.m_count;
+	
+	#if 1	
+	if (fragmentation > 10000)
+	{
+		// not guaranteed to do anything useful.... might crash the debugger (Mick)
+		printf ("Dumping Fragments.....\n");
+		MemView_DumpFragments(heap);
+		printf ("Done Dumping Fragments.....\n");
+	}
+	#endif
+	
+	Dbg_MsgAssert(fragmentation < 10000, ("Excessive bottom up fragmentation (%d) after cleanup",fragmentation)); 
+	
+#ifndef __PLAT_NGC__
+	heap = mem_man.TopDownHeap();
+	fragmentation = heap->mFreeMem.m_count;
+	if (fragmentation > 10000)
+	{
+		// not guaranteed to do anything useful.... might crash the debugger (Mick)
+		printf ("Dumping Fragments.....\n");
+		MemView_DumpFragments(heap);
+		printf ("Done Dumping Fragments.....\n");
+	}
+	
+	Dbg_MsgAssert(fragmentation < 10000, ("Excessive top down fragmentation (%d) after cleanup",fragmentation)); 
+#endif		// __PLAT_NGC__
+	#endif
+
+#	if defined( __PLAT_XBOX__ ) && defined( __NOPT_ASSERT__ )
+#	define AddStr(a,b)	( pstrOut += wsprintf( pstrOut, a, b ))
+#	define KB			( 1024 )
+	MEMORYSTATUS stat;
+	char strOut[1024], *pstrOut;
+
+    // Get the memory status.
+    GlobalMemoryStatus( &stat );
+
+    // Setup the output string.
+    pstrOut = strOut;
+//	AddStr( "%4d total kb of virtual memory.\n", stat.dwTotalVirtual / KB );
+//	AddStr( "%4d  free kb of virtual memory.\n", stat.dwAvailVirtual / KB );
+    AddStr( "%4d total kb of physical memory.\n", stat.dwTotalPhys / KB );
+    AddStr( "%4d  free kb of physical memory.\n", stat.dwAvailPhys / KB );
+//	AddStr( "%4d  percent of memory is in use.\n", stat.dwMemoryLoad );
+    OutputDebugString( strOut );
+#	endif
+	
+#	ifdef __PLAT_NGPS__
+	if( skate_mod->ShouldAllocateInternetHeap())
+	{
+		Mem::Manager::sHandle().InitInternetHeap();
+	}
+	else
+	{
+		Mem::Manager::sHandle().DeleteInternetHeap();
+	}
+#	endif // __PLAT_NGPS__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadQB | Loads the specified QB file.
+// @uparm "data\scripts\ken.qb" | File name of the qb to load
+// @uparmopt LevelSpecific | Whether this is the level specific script file
+bool ScriptLoadQB(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_file_name=NULL;
+	pParams->GetString(NONAME,&p_file_name);
+	Dbg_MsgAssert(p_file_name,("LoadQB requires a file name."));
+
+	if (pParams->ContainsFlag("LevelSpecific"))
+	{
+		#if 0
+		if (s_level_specific_qb)
+		{
+			// Only one level specific qb can exist currently, so make sure
+			// that the old one is removed, just in case someone did two
+			// LoadQB's in a row.
+			SkateScript::UnloadQB(s_level_specific_qb);
+		}	
+		s_level_specific_qb=Crc::GenerateCRCFromString(p_file_name);
+		#else
+		// Now allowing multiple level specific QBs, so 
+		// just look for a free slot
+		int qb;
+		for (qb=0;qbGetString(NONAME,&p_file_name);
+	Dbg_MsgAssert(p_file_name,("LoadQB requires a file name."));
+
+	// Unload the QB
+	uint32 qb_checksum=Crc::GenerateCRCFromString(p_file_name);
+	SkateScript::UnloadQB(qb_checksum);
+
+	// If it was the level specific qb, which is normally removed automatically
+	// by ScriptCleanup above.
+	// Since they removed it manually, reset s_level_specific_qb so that it is consistent.
+	#if 0	
+	if (qb_checksum==s_level_specific_qb)
+	{
+		s_level_specific_qb=0;
+	}
+	#else
+	int qb;
+	for (qb=0;qbGetInteger(CRCD(0x6835b854,"mode"),&mode);
+	Nx::CEngine::sSetRenderMode(mode);	
+
+	return true;
+}
+
+// @script | SetWireframeMode | 
+bool ScriptSetWireframeMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int mode = 0;
+	pParams->GetInteger(CRCD(0x6835b854,"mode"),&mode);
+	Nx::CEngine::sSetWireframeMode(mode);	
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DebugRenderIgnore | 
+// @parmopt int | ignore_1 | 0 | bitmask of face flags where we ignore this face if bit is 1
+// @parmopt int | ignore_0 | 0 | bitmask of face flags where we ignore this face if bit is 0
+bool ScriptDebugRenderIgnore(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	int ignore_0 = 0;	
+	int ignore_1 = 0;
+	pParams->GetInteger("ignore_1",&ignore_1);
+	pParams->GetInteger("ignore_0",&ignore_0);
+						  
+	Nx::CEngine::sSetDebugIgnore(ignore_1,ignore_0);	
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetVRAMPackContext | Currently, there are 4 possible 
+// contexts, 0 through 3, with 0 being the default. There is a script 
+// command SetVRAMPackContext which takes a number between 0 and 3. 
+// All packing handled by the intelligent packer will then only pack 
+// in blocks with a matching context.
+// @uparmopt 0 | context number (int)
+bool ScriptSetVRAMPackContext(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	int context = 0;
+	pParams->GetInteger( NONAME, &context );
+
+#	ifdef __PLAT_NGPS__
+//	RpSkyNXSetVRAMPackContext( (uint32)context );
+#	endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScreenShot | save a screenshot with specified filename
+// @parmopt string | filename | screen | place to save screenshot
+bool ScriptScreenShot(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *fileName;
+	bool ret;
+
+	ret = pParams->GetText( "filename", &fileName );
+	if ( !ret )
+		fileName = "screen";
+
+	 Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+	gfx_manager->ScreenShot( fileName );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// @script | DumpShots | dump memory card screenshots to PC
+bool ScriptDumpShots(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#ifdef	__PLAT_NGPS__
+	Gfx::Manager * gfx_manager =  Gfx::Manager::Instance();
+	gfx_manager->DumpMemcardScreeenshots( );
+	#endif
+
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IfDebugOn | returns true if debug on
+bool ScriptIfDebugOn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Dbg_MsgAssert(pScript,("NULL pScript ?"));
+	pScript->SwitchOnIfDebugging();
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IfDebugOff | returns true if debug off
+bool ScriptIfDebugOff(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Dbg_MsgAssert(pScript,("NULL pScript ?"));
+	pScript->SwitchOffIfDebugging();
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CD | returns true if CD present
+bool ScriptCD(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Config::CD();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | NotCD | returns true if CD not present
+bool ScriptNotCD(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return !Config::CD();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Bootstrap | returns true if this is a bootstrap demo
+bool ScriptBootstrap(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return Config::Bootstrap();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CasArtist | returns true if cas_artist int is set anywhere
+bool ScriptCasArtist(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool is_cas_artist = Script::GetInt( "cas_artist", false );
+
+	return is_cas_artist;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | NotCasArtist | returns true if cas_artist int is not set
+bool ScriptNotCasArtist(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return !ScriptCasArtist( pParams, pScript );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Gunslinger | returns true if a Gunslinger build
+bool ScriptGunslinger(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#	ifdef TESTING_GUNSLINGER
+	return true;
+#	else
+	return false;
+#	endif
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+enum
+{
+	ACTION_SET_FLAG,
+	ACTION_CLEAR_FLAG,
+	ACTION_QUERY_FLAG,
+	ACTION_SET_OBJECT_FLAG,    // different than the script flags.
+	ACTION_CLEAR_OBJECT_FLAG,  // different than the script flags.
+	ACTION_FLAG_EXCEPTION_BY_CHECKSUM,
+	ACTION_SEE_IF_OBJECT_EXISTS,
+};
+
+struct SActionData
+{
+	int Flag;
+	int Action;
+	bool ReturnValue;
+	bool ObjectFound;
+};
+
+struct SActionData_NameChecksum
+{
+	uint32 ObjectNameChecksum;
+	SActionData actionData;
+};
+
+struct SActionData_NodeIndex
+{
+	int NodeIndex;
+	SActionData actionData;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void DoAction( Obj::CCompositeObject* pOb, SActionData* pData )
+{
+	pData->ObjectFound = true;
+	switch ( pData->Action )
+	{
+		case ( ACTION_SET_FLAG ):
+			pOb->mScriptFlags |= ( 1 << pData->Flag );
+			pData->ReturnValue = true;
+			break;
+		case ( ACTION_CLEAR_FLAG ):
+			pOb->mScriptFlags &= ~( 1 << pData->Flag );
+			pData->ReturnValue = true;
+			break;
+		case ( ACTION_QUERY_FLAG ):
+			if ( pOb->mScriptFlags & ( 1 << pData->Flag ) )
+			{
+				pData->ReturnValue = true;
+			}
+			break;
+		case ( ACTION_SET_OBJECT_FLAG ):
+			pOb->SetFlags(((Obj::CObject *) pOb)->GetFlags() | ( pData->Flag ));	 // Note:  Flags are passed as a bitfield
+			break;
+		case ( ACTION_CLEAR_OBJECT_FLAG ):
+			pOb->SetFlags(((Obj::CObject *) pOb)->GetFlags() & ~( pData->Flag ));
+			break;
+		case ( ACTION_FLAG_EXCEPTION_BY_CHECKSUM ):
+			{
+				pOb->SelfEvent((pData->Flag));
+				pData->ReturnValue = true;
+			}
+			break;
+		case ( ACTION_SEE_IF_OBJECT_EXISTS ):
+			break;
+		default:
+			Dbg_MsgAssert( 0,( "Unknown action %d", pData->Action ));
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void Action_ByNameChecksum( Obj::CObject* pOb, void* pVoidData )
+{
+	
+	Dbg_MsgAssert(pOb,("NULL pOb"));
+	Dbg_MsgAssert(pVoidData,("NULL pVoidData"));
+	
+	SActionData_NameChecksum *pData=(SActionData_NameChecksum*)pVoidData;
+
+	// Mick - find objects based either on their node name (soon to go away)
+	// or by their ID (which is set to the node name for composite objects)	
+	if ( pOb->GetID() == pData->ObjectNameChecksum )
+	{
+		DoAction( (Obj::CCompositeObject*)pOb, &pData->actionData );
+	}
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void Action_ByNodeIndex(Obj::CObject* pOb, void* pVoidData)
+{
+	
+	Dbg_MsgAssert(pOb,("NULL pOb"));
+	Dbg_MsgAssert(pVoidData,("NULL pVoidData"));
+	
+	SActionData_NodeIndex *pData=(SActionData_NodeIndex*)pVoidData;
+
+	uint32	nodeName = SkateScript::GetNodeNameChecksum(pData->NodeIndex);
+	
+	if (pOb->GetID() == nodeName )
+	{
+		DoAction( (Obj::CCompositeObject*)pOb, &pData->actionData );
+	}	
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ClearFlag | clears a flag 
+// @uparm 1 | the flag to clear
+// @parmopt name | name | | the name of the object
+// @parmopt string | prefix | | prefix value
+bool ScriptClearFlag(Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	return ( ScriptDoAction( pParams, pScript, ACTION_CLEAR_FLAG ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SendFlag | Sets a specified flag in a named object. All
+// objects have a set of 32 flags. For example: 
+// SendFlag 14 name =TRG_Car03 ; no quotes 
+// This will search all the objects until it finds the one called
+// TRG_Car03, and will then set its flag 14. The flag value must
+// be between 0 and 31 inclusive. To aid readability, names should
+// be used for the flags, and there is a file flags.q where the
+// definitions should be put
+// @uparm 1 | the flag to set
+// @parmopt name | name | | the name of the object
+// @parmopt string | prefix | | prefix value
+bool ScriptSendFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return ( ScriptDoAction( pParams, pScript, ACTION_SET_FLAG ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | QueryFlag | you can query a flag on an object (or several
+// objects if using links parameter or prefix = "Something_" parameter
+// instead of name = Something_Unique). In the case of one object,
+// QueryFlag will return true or false depending on if the flag is set
+// or not. In the case of several objects, the function will return true
+// if any of the objects specified have that flag set, false otherwise
+// @uparm 1 | the flag to query
+// @parmopt name | name | | the name of the object
+// @parmopt string | prefix | | prefix value
+bool ScriptQueryFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return ( ScriptDoAction( pParams, pScript, ACTION_QUERY_FLAG ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SendException | use the SendException command in the same
+// way you would use the SendFlag command:  you can specify name=whatever_object
+// or links or prefix="whatever_" (just like with SendFlag). 
+// @uparm Exception | the flag 
+// @parmopt name | name | | object name
+// @parmopt string | prefix | | prefix value
+bool ScriptFlagException(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return ( ScriptDoAction( pParams, pScript, ACTION_FLAG_EXCEPTION_BY_CHECKSUM ) );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsAlive | Takes the same arguments as SendFlag etc... Returns
+// true if the object currently exists
+// @parm name | name | object name
+bool ScriptCheckIfAlive( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	return ( DoNodeAction( pParams, pScript, NODE_ACTION_CHECK_IF_ALIVE ) );
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void SetUpActionData( SActionData *pData, int flag, int action )
+{
+	
+	Dbg_MsgAssert( pData,( "Fire matt the idiot" ));
+	pData->Flag = flag;
+	pData->Action = action;
+	pData->ObjectFound = false;
+	pData->ReturnValue = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Used to perform actions to nodes by prefix, name checksum, or links -- but this version
+// only operates on the nodes that create CMovingObjects... For the general version (applicable
+// to nodes that create Environmental Objects, Rails, etc... AS WELL as CMovingObjects, see
+// DoNodeAction( )
+bool ScriptDoAction(Script::CStruct *pParams, Script::CScript *pScript, int action )
+{
+	
+	int flag = 0;
+	if (action == ACTION_FLAG_EXCEPTION_BY_CHECKSUM)
+	{
+	#ifdef __NOPT_ASSERT__
+		bool GotFlag=
+	#endif	
+		pParams->GetChecksum(NONAME,(uint32*)&flag);
+		Dbg_MsgAssert(GotFlag,("\n%s\nSendException  require a chechecksum name to be specified.\n(NOT AN INTEGER)",pScript->GetScriptInfo()));
+	}
+	else
+	{
+	#ifdef __NOPT_ASSERT__
+		bool GotFlag=
+	#endif	
+		pParams->GetInteger(NONAME,&flag);
+		Dbg_MsgAssert(GotFlag,("\n%s\nFlag commands require a flag to be specified.\n(Either an integer or a name defined to be an integer)",pScript->GetScriptInfo()));
+		Dbg_MsgAssert(flag>=0 && flag<32,("\n%s\nBad flag value of %d, value must be between 0 and 31",pScript->GetScriptInfo(),flag));
+	
+	}
+	uint32 nameChecksum;
+	const char *pPrefix;
+	int i;
+	
+	
+	bool useCurrentLinks = false;
+	if ( pParams->ContainsFlag( 0x2e7d5ee7 ) || // checksum 'links'
+		( useCurrentLinks = pParams->ContainsFlag( 0xbceb479a ) ) ) // checksum 'currentlinks'
+	{
+	
+		Dbg_MsgAssert(0,("\n%s\n'links' and 'currentlinks' are deprecated\n",pScript->GetScriptInfo()));
+	}
+	
+	if ( pParams->GetChecksum( 0xa1dc81f9, &nameChecksum) )  // 'name'
+	{
+		SActionData_NameChecksum Data;
+		Data.ObjectNameChecksum = nameChecksum;
+		SetUpActionData( &Data.actionData, flag, action );
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		skate_mod->GetObjectManager()->ProcessAllObjects(Action_ByNameChecksum,&Data);
+		Dbg_MsgAssert( Data.actionData.ObjectFound,( "\n%s\nDidn't find object specified '%s'.", pScript->GetScriptInfo( ), Script::FindChecksumName( nameChecksum ) ) );
+		return ( Data.actionData.ReturnValue );
+	}
+    if ( pParams->GetText( 0x6c4e7971, &pPrefix ) ) // checksum 'prefix'
+	{
+		// Create with a prefix specified:
+		uint16 numNodes = 0;
+		const uint16 *pMatchingNodes = SkateScript::GetPrefixedNodes( pPrefix, &numNodes );
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		SActionData_NodeIndex Data;
+		SetUpActionData( &Data.actionData, flag, action );
+		for ( i = 0; i < numNodes; i++ )
+		{
+			Data.NodeIndex = pMatchingNodes[ i ];
+			skate_mod->GetObjectManager()->ProcessAllObjects(Action_ByNodeIndex,&Data);
+		}
+		Dbg_MsgAssert( Data.actionData.ObjectFound,( "\n%s\nDidn't find any objects with prefix %s.", pScript->GetScriptInfo( ), pPrefix ));
+		return ( Data.actionData.ReturnValue );
+	}
+#if 0  // put in if ever necessary:
+	// set the flag on ourself:
+	if ( pScript->mpObject )
+	{
+		switch ( action )
+		{
+			case ( ACTION_SET_FLAG ):
+				pScript->mpObject->mScriptFlags |= ( 1 << flag );
+				return ( true );
+			case ( ACTION_CLEAR_FLAG ):
+                pScript->mpObject->mScriptFlags &= ~( 1 << flag );
+				return ( true );
+			case ( ACTION_QUERY_FLAG ):
+				return ( pScript->mpObject->mScriptFlags & ( 1 << flag );
+			default:
+				Dbg_MsgAssert( 0,( "Unknown flag action %d", action ));
+				break;
+		}
+	}
+	else
+	{
+		Dbg_MsgAssert( 0,( "\n%s\nNeed to specify an object, or call this command on a script associated with an object.", pScript->GetScriptInfo( ) ));
+	}
+#endif
+	Dbg_MsgAssert( 0,( "\n%s\nMust specify a name, links, or prefix please.", pScript->GetScriptInfo( ) ));
+	return ( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSetVisibilityFromNodeIndex( int nodeIndex, bool invisible, int viewport_number )
+{	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray,( "No NodeArray found" ));
+
+	Script::CStruct *pNode=pNodeArray->GetStructure( nodeIndex );
+	Dbg_MsgAssert( pNode,( "NULL pNode" ));
+
+	// If this is a net game, don't show/hide objects that were meant to be left out for 
+	// performance/bandwidth/gameplay reasons
+	if ( skate_mod->ShouldBeAbsentNode( pNode ) )
+	{
+		return true;
+	}
+
+	uint32 ClassChecksum = 0;
+	pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+	
+	switch (ClassChecksum)
+	{
+		case 0xe47f1b79: // vehicle
+		case 0xa0dfac98: // pedestrian
+		case 0x61a741e:  // ped
+		case 0xef59c100: // gameobject
+		case 0x5b8ab877: // skater
+		case 0x19b1e241: // ParticleEmitter	  
+		case 0x9e7d469e: // ParticleObject  
+		{
+			SActionData_NodeIndex Data;
+			Data.NodeIndex = nodeIndex;
+			if ( invisible )
+			{
+				SetUpActionData( &Data.actionData, Obj::CMovingObject::vINVISIBLE, ACTION_SET_OBJECT_FLAG );
+			}
+			else
+			{
+				SetUpActionData( &Data.actionData, Obj::CMovingObject::vINVISIBLE, ACTION_CLEAR_OBJECT_FLAG );
+			}
+			Data.NodeIndex = nodeIndex;
+			skate_mod->GetObjectManager()->ProcessAllObjects(Action_ByNodeIndex,&Data);
+			if ( !Data.actionData.ObjectFound )
+			{
+				Dbg_Message( "Vis/Invis:  Object from node %d not found.", nodeIndex );
+			}
+			break;
+		}
+		case 0xb7b3bd86:  // LevelObject
+		case 0xbf4d3536:  // LevelGeometry
+		{
+			uint32 checksumName = 0;
+			//Dbg_MsgAssert( ( "No world in viewer module..." ));
+			
+			if ( pNode->GetChecksum( 0xa1dc81f9, &checksumName ) ) // checksum 'name'
+			{
+//				Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);
+				// We only want to get sectors from the main scene, as that is what the node array applies to															
+				Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+
+				uint32 orig_flag = p_sector->GetVisibility();
+				uint32 flag = 0x1; 
+				if (viewport_number == 1)
+				{
+					flag = 0x2; 
+				}
+				if ( invisible )
+				{
+					p_sector->SetVisibility(orig_flag & ~flag);		// clear flags
+					//Bsp::SetWorldSectorFlag(  checksumName, flag );
+				}
+				else
+				{
+					p_sector->SetVisibility(orig_flag | flag);		// set flags
+					//Bsp::ClearWorldSectorFlag(  checksumName, flag );
+				}
+			}
+			break;
+ 		}
+
+//		case 0xe594f0a2:  // Trigger (geometry containing triggers...)
+		case 0x8e6b02ad:  // railnode
+		case CRCC(0x30c19600, "ClimbingNode"):
+		default:
+			return ( false );
+			break;
+	}
+	return ( true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCheckExistenceFromNodeIndex( int nodeIndex )
+{	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray,( "No NodeArray found" ));
+
+	Script::CStruct *pNode=pNodeArray->GetStructure( nodeIndex );
+	Dbg_MsgAssert( pNode,( "NULL pNode" ));
+
+	uint32 ClassChecksum = 0;
+	pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+	
+	switch (ClassChecksum)
+	{
+		case 0xe47f1b79: // vehicle
+		case 0xa0dfac98: // pedestrian
+		case 0x61a741e:  // ped
+		case 0xef59c100: // gameobject
+		case 0x5b8ab877: // skater
+		case 0xb7b3bd86: // LevelObject
+		case 0x19b1e241: // ParticleEmitter
+		case 0x9e7d469e: // ParticleObject
+		
+		{
+			SActionData_NodeIndex Data;
+			Data.NodeIndex = nodeIndex;
+			SetUpActionData( &Data.actionData, 0, ACTION_SEE_IF_OBJECT_EXISTS );
+			Data.NodeIndex = nodeIndex;
+			skate_mod->GetObjectManager()->ProcessAllObjects(Action_ByNodeIndex,&Data);
+			if ( !Data.actionData.ObjectFound )
+			{
+				return ( false );
+			}
+			return ( true );
+			break;
+		}
+		case 0xbf4d3536:  // LevelGeometry
+		{
+			uint32 checksumName = 0;
+			//Dbg_MsgAssert( ( "No world in viewer module..." ));
+			if ( pNode->GetChecksum( 0xa1dc81f9, &checksumName ) ) // checksum 'name'
+			{
+//				Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);				
+				// We only want to get sectors from the main scene, as that is what the node array applies to															
+				Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+				return (p_sector->IsActive());
+				//return ( Bsp::WorldSectorIsAlive( checksumName ) );
+			}
+			break;
+ 		}
+
+		case 0xe594f0a2:  // Trigger (geometry containing triggers...)
+			break;
+			
+		case CRCC(0x30c19600, "ClimbingNode"):
+		case 0x8e6b02ad:  // railnode
+			return ( skate_mod->GetRailManager()->IsActive( nodeIndex ) );
+			break;
+			
+		default:
+			return ( false );
+			break;
+	}
+	return ( true );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSetColorFromNodeIndex( int nodeIndex, Script::CStruct *pParams )
+{	
+
+	uint32 color = 0x808080;
+	
+	pParams->GetInteger( CRCD(0x99a9b716,"color"), (int*)&color, TRUE );
+	
+	Image::RGBA rgb;
+	rgb.r = (uint8)((color)&0xff);
+	rgb.g = (uint8)((color>>8)&0xff);
+	rgb.b = (uint8)((color>>16)&0xff);
+	rgb.a = 0x80;
+
+	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray,( "No NodeArray found" ));
+
+	Script::CStruct *pNode=pNodeArray->GetStructure( nodeIndex );
+	Dbg_MsgAssert( pNode,( "NULL pNode" ));
+
+	uint32 ClassChecksum = 0;
+	pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+	
+	switch (ClassChecksum)
+	{
+		case 0xbf4d3536:  // LevelGeometry
+		{
+			uint32 checksumName = 0;
+			//Dbg_MsgAssert( ( "No world in viewer module..." ));
+			if ( pNode->GetChecksum( 0xa1dc81f9, &checksumName ) ) // checksum 'name'
+			{
+				Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+				p_sector->SetColor(rgb);
+			
+			}
+			break;
+ 		}
+		default:
+			return ( false );
+			break;
+	}
+	return ( true );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptShatterFromNodeIndex( int nodeIndex )
+{
+	
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+// Mick:  If pre-running scripts in a net game, we might shatter a lot of glass
+// which might run out of memory
+// so don't actually shatter the sector if that's what we are doing
+
+	if( skate_mod->LaunchingQueuedScripts())
+	{
+		return true;
+	}
+
+	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray,( "No NodeArray found" ));
+
+	Script::CStruct *pNode=pNodeArray->GetStructure( nodeIndex );
+	Dbg_MsgAssert( pNode,( "NULL pNode" ));
+
+	// If this is a net game, don't show/hide objects that were meant to be left out for 
+	// performance/bandwidth/gameplay reasons
+	if ( skate_mod->ShouldBeAbsentNode( pNode ) )
+	{
+		return true;
+	}
+
+	uint32 ClassChecksum = 0;
+	pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+
+	switch( ClassChecksum )
+	{
+		case 0xb7b3bd86:  // LevelObject
+		case 0xbf4d3536:  // LevelGeometry
+		{
+			//Dbg_MsgAssert( p_world,( "No world in viewer module..." ));
+
+			uint32 checksumName = 0;
+			if( pNode->GetChecksum( 0xa1dc81f9, &checksumName )) // checksum 'name'
+			{
+//				Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);				
+				// We only want to get sectors from the main scene, as that is what the node array applies to															
+				Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+				p_sector->SetShatter(true);
+				//Bsp::ShatterWorldSector(  checksumName );
+			}
+			break;
+ 		}
+
+		default:
+		{
+			return false;
+			break;
+		}
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MakeSkaterGoto |
+// This will cause the main skater script to jump to the
+// specified script. So a script triggered from a trigger poly
+// can use this to force the main skater script to jump to a script.
+// It will choose the skater that triggered the poly in that case. 
+// 
+// A script not associated with an object can also use this, in which
+// case it will set the script of skater 0. 
+// 
+// The main skater script itself could also use it, but there would be
+// no point since it can use Goto. 
+// 
+// This should be used with care, since making the skater jump to a
+// script when he's in the middle of doing something could confuse him.
+// Eg, forcing him to jump to OnGroundAI when he's on a rail will make
+// an assert go off.
+// @uparm name | Script name
+// @parmopt structure | params | | parameters to be passed to new script
+bool ScriptMakeSkaterGoto(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	
+	Script::CStruct *pParamsToPass=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pParamsToPass);
+	
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(NONAME,&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("\n%s\nMakeSkaterGoto requires a script name, eg MakeSkaterGoto Blaa",pScript->GetScriptInfo()));
+
+	Obj::CSkater *pSkater=NULL;
+	if (pScript && pScript->mpObject && pScript->mpObject->GetType()==SKATE_TYPE_SKATER)	
+	{
+		// If the script using this has an object associated with it, and that object
+		// is a skater, then use that skater.
+		// This will happen in the case of a trigger script using this command.
+		pSkater=(Obj::CSkater*)pScript->mpObject.Convert();
+	}
+	else
+	{
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		int Skater=0;
+		if( pParams->GetInteger("Skater",&Skater))
+		{
+			pSkater = skate_mod->GetSkater(Skater);
+		}
+		else
+		{
+			pSkater = skate_mod->GetLocalSkater();
+		}
+	}
+		
+	if (pSkater && pSkater->IsLocalClient())
+	{
+		pSkater->JumpToScript(ScriptChecksum,pParamsToPass);
+	}
+		
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MakeSkaterGotoSub | interrupts whatever script the skater is doing,
+// calls new script, and then returns to the original script.
+// @uparm name | script to goto
+// @parmopt structure | Params | | parameters to pass to new script
+// @parmopt int | Skater | | 
+bool ScriptMakeSkaterGosub(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Script::CStruct *pParamsToPass=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pParamsToPass);
+	
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(NONAME,&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("\n%s\nMakeSkaterGosub requires a script name, eg MakeSkaterGosub Blaa",pScript->GetScriptInfo()));
+
+	Obj::CSkater *pSkater=NULL;
+	if (pScript && pScript->mpObject && pScript->mpObject->GetType()==SKATE_TYPE_SKATER)	
+	{
+		// If the script using this has an object associated with it, and that object
+		// is a skater, then use that skater.
+		// This will happen in the case of a trigger script using this command.
+		pSkater=(Obj::CSkater*)pScript->mpObject.Convert();
+	}
+	else
+	{
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		int Skater=0;
+		pParams->GetInteger("Skater",&Skater);
+		pSkater = skate_mod->GetSkater(Skater);						   
+	}
+		
+	if (pSkater)
+	{
+		pSkater->CallScript(ScriptChecksum,pParamsToPass);
+	}
+		
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SpawnScript | This will create & run a new script which will
+// run -in parallel- until it finishes, when it will die. The new script
+// is not associated with any object. The calling script is not affected
+// in any way. A typical use of a spawned script would be to play a sound,
+// pause, play another sound, etc, for example when an object dies. This
+// way the object itself can die straight away
+// @uparm name | the name of the script to spawn
+// @parmopt structure | Params | | Parameter structure to pass to new script
+// @parmopt name | Id | | an id to assign to the spawned script, so it can 
+// be killed by KillSpawnedScript
+// @flag NotSessionSpecific | This will cause the script to not get deleted when the current
+// level (session) ends.
+bool ScriptSpawnScript(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(NONAME,&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("\n%s\nMissing script name in SpawnScript command.",pScript->GetScriptInfo()));
+	bool net_enabled, permanent;
+	Script::CStruct *pScriptParams=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptParams);
+	
+	// The spawned script can optionally be given an Id, so that it can be deleted
+	// by KillSpawnedScript.
+	uint32 Id=0;
+	pParams->GetChecksum("Id",&Id);
+
+	uint32 CallbackScript=0;
+	pParams->GetChecksum("Callback",&CallbackScript);
+	Script::CStruct *pCallbackParams=NULL;
+	pParams->GetStructure("CallbackParams",&pCallbackParams);
+		
+	//net_enabled = 0;
+	//permanent = 0;
+	//pParams->GetInteger( "NetEnabled", &net_enabled );
+	//pParams->GetInteger( "Permanent", &permanent );
+	net_enabled = pParams->ContainsFlag( "NetEnabled" );
+	permanent = pParams->ContainsFlag( "Permanent" );
+	
+	int not_session_specific=0;
+	pParams->GetInteger( "NotSessionSpecific", ¬_session_specific);
+	
+	// copy the parent's node
+	#ifdef __NOPT_ASSERT__
+	Script::CScript *p_script=Script::SpawnScript(ScriptChecksum,pScriptParams,CallbackScript,pCallbackParams,
+													pScript->mNode,
+													Id,
+													net_enabled,
+													permanent,
+													not_session_specific); 	
+	p_script->SetCommentString("Spawned by script command SpawnScript");
+	p_script->SetOriginatingScriptInfo(pScript->GetCurrentLineNumber(),pScript->mScriptChecksum);
+	#else
+	Script::SpawnScript(ScriptChecksum,pScriptParams,CallbackScript,pCallbackParams,
+						pScript->mNode,
+						Id,
+						net_enabled,
+						permanent,
+						not_session_specific); 	
+	#endif	
+	return true;
+}	
+
+
+			
+// @script | SpawnSound | This will create & run a new script which will
+// run -in parallel- until it finishes, when it will die. The new script
+// is not associated with any object. The calling script is not affected
+// in any way. A typical use of a spawned script would be to play a sound,
+// pause, play another sound, etc, for example when an object dies. This
+// way the object itself can die straight away
+//
+// Note, this is the same as SpawnScript, except if the current script is
+// attached to an object, then we do the same as Obj_SpawnScript
+// So we get attached to the object
+//
+// @uparm name | the name of the script to spawn
+// @parmopt structure | Params | | Parameter structure to pass to new script
+// @parmopt name | Id | | an id to assign to the spawned script, so it can 
+// be killed by KillSpawnedScript
+bool ScriptSpawnSound(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	if (pScript->mpObject)
+	{
+		uint32 scriptChecksum;
+		if ( pParams->GetChecksum( NONAME, &scriptChecksum ) )
+		{
+			// The spawned script can optionally be given an Id, so that it can be deleted
+			// by KillSpawnedScript.
+			uint32 Id=0;
+			// keep the same ID as the parent if not specified...
+			Id = Script::FindSpawnedScriptID(pScript);
+			pParams->GetChecksum("Id",&Id);
+			Script::CScriptStructure *pScriptParams = NULL;
+			pParams->GetStructure( "Params", &pScriptParams );
+			#ifdef __NOPT_ASSERT__	
+			Script::CScript *p_script=pScript->mpObject->SpawnScriptPlease( scriptChecksum, pScriptParams, Id );
+			p_script->SetCommentString("Created by SpawnSound");
+			p_script->SetOriginatingScriptInfo(pScript->GetCurrentLineNumber(),pScript->mScriptChecksum);
+			#else
+			pScript->mpObject->SpawnScriptPlease( scriptChecksum, pScriptParams, Id );
+			#endif
+		}
+	}
+	else
+	{
+		return ScriptSpawnScript(pParams,pScript);		
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SpawnSkaterScript | This will create & run a new script
+// on the skater which will run in parallel until it finishes, when it
+// will die. The calling script is not affected in any way. 
+// @uparm name | the name of the script to call
+// @parmopt structure | Params | | parameter list to pass to new script
+// @parmopt name | Id | | an id to assign to the new script so it can 
+// be killed by KillSpawnedScript
+bool ScriptSpawnSkaterScript(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	// Mostly the same as the code for ScriptSpawnScript
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(NONAME,&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("\n%s\nMissing script name in SpawnSkaterScript command.",pScript->GetScriptInfo()));
+	
+	Script::CStruct *pScriptParams=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"Params"),&pScriptParams);
+	
+	// The spawned script can optionally be given an Id, so that it can be deleted
+	// by KillSpawnedScript.
+	uint32 Id=0;
+	pParams->GetChecksum("Id",&Id);
+
+	uint32 CallbackScript=0;
+	pParams->GetChecksum("Callback",&CallbackScript);
+	Script::CStruct *pCallbackParams=NULL;
+	pParams->GetStructure("CallbackParams",&pCallbackParams);
+	
+	Script::CScript *pNewScript=Script::SpawnScript(ScriptChecksum,pScriptParams,CallbackScript,pCallbackParams,pScript->mNode,Id); 	// copy the parent's node
+	#ifdef __NOPT_ASSERT__
+	pNewScript->SetCommentString("Spawned by script command SpawnSkaterScript");
+	pNewScript->SetOriginatingScriptInfo(pScript->GetCurrentLineNumber(),pScript->mScriptChecksum);
+	#endif	
+	
+	// Now set the object pointer to be the skater.
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetLocalSkater();						   
+	
+	pNewScript->mpObject=pSkater;
+	
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | KillSpawnedScript | can be passed a script name or an id
+// @parmopt name | Name | | the name of the script
+// @parmopt name | Id | | the id of the script
+bool ScriptKillSpawnedScript(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	uint32 ScriptChecksum=0;
+	if (pParams->GetChecksum("Name",&ScriptChecksum))
+	{
+		// Got a script name, so kill all spawned scripts that ran that script.
+		Script::KillSpawnedScriptsThatReferTo(ScriptChecksum);
+		return true;
+	}
+
+	uint32 Id=0;										   
+	if (pParams->GetChecksum("Id",&Id))
+	{
+		// They specified an Id, so kill all spawned scripts with this Id.
+		Script::KillSpawnedScriptsWithId(Id);
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseSkaters | This will cause all skaters to freeze.
+// They will continue where they left off when UnPauseSkaters is called. 
+// Note that this command will not pause any spawned scripts associated
+// with the skaters, because otherwise it will pause the camera animation
+// script too, which is where the command will probably be used most
+bool ScriptPauseSkaters( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Replay::WritePauseSkater();
+	
+	bool hide = pParams->ContainsFlag("hide");	
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	uint32 NumSkaters=skate_mod->GetNumSkaters();
+	for (uint32 i=0; iGetSkater(i);
+		if (pSkater && pSkater->IsLocalClient()) // Hmm, assert instead?
+		{
+			pSkater->Pause();
+			pSkater->Hide(hide);
+		}	
+	}	
+	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnPauseSkaters | opposite effect of PauseSkaters
+bool ScriptUnPauseSkaters( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Replay::WriteUnPauseSkater();
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	uint32 NumSkaters=skate_mod->GetNumSkaters();
+	for (uint32 i=0; iGetSkater(i);
+		if (pSkater)
+		{
+			pSkater->UnPause();
+			pSkater->Hide(false);
+		}	
+	}	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseSkater | this pauses an individual skater
+// @uparm 0 | skater id
+bool ScriptPauseSkater( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	bool hide = pParams->ContainsFlag("hide");	
+
+	int skater_id;
+	pParams->GetInteger( NONAME, &skater_id, true );
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetSkater(skater_id);
+	if (pSkater)
+	{
+		pSkater->Pause();
+		pSkater->Hide(hide);
+	}	
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnPauseSkater | Unpauses an individual skater
+// @uparm 0 | skater id
+bool ScriptUnPauseSkater( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+
+	int skater_id;
+	pParams->GetInteger( NONAME, &skater_id, true );
+
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Obj::CSkater *pSkater = skate_mod->GetSkater(skater_id);
+	if (pSkater)
+	{
+		pSkater->UnPause();
+		pSkater->Hide(false);
+	}	
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseGame | Similar to "PauseSkaters", it will pause
+// the skaters, and everything else in the world, yet still allow
+// you to launch messages and spawn new scripts
+bool ScriptPauseGame( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Mdl::FrontEnd * front =  Mdl::FrontEnd::Instance();
+	front->PauseGame(true);
+	// if the script was a spawned script
+	// then we want to unpause it, so we can keep running after the game has paused
+	Script::UnpauseSpawnedScript(pScript);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnPauseGame | 
+bool ScriptUnPauseGame( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Mdl::FrontEnd * front =  Mdl::FrontEnd::Instance();
+	front->PauseGame(false);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsGamePaused | 
+bool ScriptIsGamePaused( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Mdl::FrontEnd * front =  Mdl::FrontEnd::Instance();
+	if( front->GamePaused() )
+    {
+        return true;
+    }
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseObjects |
+bool ScriptPauseObjects(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Pause all composite objects
+	Obj::CCompositeObjectManager::Instance()->Pause(true);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnpauseObjects |
+bool ScriptUnPauseObjects(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Unpause all composite objects
+	Obj::CCompositeObjectManager::Instance()->Pause(false);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseSpawnedScripts |
+bool ScriptPauseSpawnedScripts(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::PauseSpawnedScripts(true);		
+
+	if ( pScript->mIsSpawned )
+	{
+		Dbg_MsgAssert( 0, ( "Can't pause spawned scripts from a spawned script" ) );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnPauseSpawnedScripts |
+bool ScriptUnPauseSpawnedScripts(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Script::PauseSpawnedScripts(false);		
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetClock | 
+bool ScriptResetClock( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	int time_limit;
+
+	// Clients instead get their time limit from the server
+	if( gamenet_man->OnServer())
+	{
+		time_limit = skate_mod->GetGameMode()->GetTimeLimit();
+		skate_mod->SetTimeLimit( time_limit );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PadsPluggedIn | Returns true if at least one pad is plugged in.
+bool ScriptPadsPluggedIn( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Mdl::FrontEnd * p_front =  Mdl::FrontEnd::Instance();
+	 return p_front->PadsPluggedIn();
+}	 
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DoFlash | Doflash just draws a big poly over the viewport
+// and then modulates the color and alpha of that poly over time. There
+// is no support for Blendmodes yet. If you are doing a player specific 
+// flash remember to call it from a skater script so that it works in 2
+// player splitscreen
+// @parmopt float | duration | 0.0 | 
+// @parmopt string | texture | | texture name
+// @parmopt float | z | 0.0 | z
+// @flag fullscreen |
+// @flag behind_panel |
+// @flag additive |
+// @flag subtractive |
+// @parmopt int | skater | | for specifying skater when not in skater script
+// @flag ignore_pause |
+// @parm int | start_r | start red index
+// @parm int | start_g | start green index
+// @parm int | start_b | start blue index
+// @parm int | start_a | start alpha value
+// @parm int | end_r | end red index
+// @parm int | end_g | end green index
+// @parm int | end_b | end blue index
+// @parm int | end_a | end alpha value
+bool ScriptDoFlash( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+
+	float		duration		= 0.0f;
+	float		z				= 0.0f;
+	uint32		flags			= 0;
+	bool		fullscreen		= false;
+	int			skater_num		= -1;
+	const char*	p_texture_name	= NULL;
+
+	pParams->GetFloat( "duration", &duration );
+	
+	if( duration > 0.0f )
+	{
+		pParams->GetText( "texture", &p_texture_name );
+
+		pParams->GetFloat( "z", &z );
+
+//		if( pParams->ContainsFlag( 0xdf6436d2 /*"fullscreen"*/ ))
+//		{
+//			flags |= Flash::FLAG_FULLSCREEN;
+//			fullscreen = true;
+//		}	
+		if( pParams->ContainsFlag( 0x70ffc649 /*"behind_panel"*/ ))
+		{
+			flags |= Nx::SCREEN_FLASH_FLAG_BEHIND_PANEL;
+		}	
+		if( pParams->ContainsFlag( 0x19c43cf6 /*"additive"*/ ))
+		{
+			flags |= Nx::SCREEN_FLASH_FLAG_ADDITIVE;
+		}	
+		else if( pParams->ContainsFlag( 0x387c9ed6 /*"subtractive"*/ ))
+		{
+			flags |= Nx::SCREEN_FLASH_FLAG_SUBTRACTIVE;
+		}	
+
+		if( !fullscreen )
+		{
+			pParams->GetInteger( "skater", &skater_num );
+			if( skater_num == -1 )
+			{
+				// No skater parameter provided, so assume running from a skater script.
+				Obj::CSkater* p_skater = static_cast ( pScript->mpObject.Convert() );
+				Dbg_Assert( p_skater );
+				skater_num = p_skater->GetSkaterNumber();
+			}
+		}
+
+		if( pParams->ContainsFlag( 0xdf6436d2 /*"ignore_pause"*/ ))
+		{
+			flags |= Nx::SCREEN_FLASH_FLAG_IGNORE_PAUSE;
+		}	
+
+		int val = 0;
+		Image::RGBA from, to;
+
+		pParams->GetInteger( "start_r",	&val );
+		from.r = val;
+		pParams->GetInteger( "start_g",	&val );
+		from.g = val;
+		pParams->GetInteger( "start_b",	&val );
+		from.b = val;
+		pParams->GetInteger( "start_a",	&val );
+		from.a = val;
+
+		pParams->GetInteger( "end_r",	&val );
+		to.r = val;
+		pParams->GetInteger( "end_g",	&val );
+		to.g = val;
+		pParams->GetInteger( "end_b",	&val );
+		to.b = val;
+		pParams->GetInteger( "end_a",	&val );
+		to.a = val;
+
+		if( fullscreen )
+		{
+		}
+		else
+		{
+			Nx::AddScreenFlash( skater_num, from, to, duration, z, flags, p_texture_name );
+
+			// Get the panel for player.
+			//HUD::PanelMgr* panel_mgr = HUD::PanelMgr::Instance();
+			//HUD::Panel* p_panel = panel_mgr->GetPanelBySkaterId( skater_num );
+			//p_panel->AddFlash( from, to, duration, z, flags, p_texture_name );
+		}
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartServer | Starts a server using the IP stored in
+// the script variable LocalIP, which you also have to define. You
+// can also define a script variable called ServerName to name your
+// server (i.e. ServerName = “MyServer” with a max of 15 characters
+bool ScriptStartServer(Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	// set the appropriate memory context
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+
+	skate_mod->StartServer();
+
+	if (gamenet_man->InInternetMode())
+	{
+		// defer loading the level/starting the game
+		// until we have connected to the matchmaker
+		//gamenet_man->RequestMatchmakerConnect();
+		
+		// SG: 9-17-02. Post the game AFTER we're in our first level
+		//gamenet_man->PostGame();
+	}
+	else if (gamenet_man->InLanMode())
+	{
+		// if we're in LAN mode, start the game immediately
+	}
+		
+	// pop the memory context
+	Mem::Manager::sHandle().PopContext();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LeaveServer | 
+bool ScriptLeaveServer(Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	skate_mod->LeaveServer();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FindServers | broadcast out to the local subnet for servers
+bool ScriptFindServers(Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	
+	//skate_mod->FindServers();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | JoinServer | Join the server in session at the given IP address and port
+// @uparm "string" | ip address
+// @uparm 1 | port
+bool ScriptJoinServer(Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+    
+	if( gamenet_man->OnServer())
+	{
+		uint32 i;
+
+		// If playing split screen, create N clients (one for each viewport
+		// otherwise, just create one client
+//		gamenet_man->SpawnClient( false, true, 0 );
+
+		uint32 numPlayers = skate_mod->GetGameMode()->GetInitialNumberOfPlayers();
+
+		for( i = 0; i < numPlayers; i++ )
+		{   
+			Net::Client* client;
+
+			client = gamenet_man->SpawnClient( false, true, true, i );
+			
+			//skate_mod->JoinServer( false, 0, 0, true, i );
+			gamenet_man->JoinServer( false, 0, 0, i );
+			skate_mod->AddNetworkMsgHandlers( client, i );
+			if( gamenet_man->InNetGame())
+			{
+				Script::RunScript( "entered_network_game" );
+			}
+		}
+	}
+	else
+	{
+		/*int max_players;
+		pParams->GetInteger( Script::GenerateCRC( "MaxPlayers" ), (int *) &max_players );
+		skate_mod->GetGameMode()->SetMaximumNumberOfPlayers( max_players );
+
+		Dbg_Printf( "**** SETTING MAX PLAYERS TO %d\n", max_players );
+		Dbg_Assert( skate_mod->GetGameMode()->GetMaximumNumberOfPlayers() == (uint32) max_players );*/
+
+#ifdef __PLAT_XBOX__
+		IN_ADDR host_addr;
+		int port = 0;
+		bool observe_only;
+
+		pParams->GetInteger( Script::GenerateCRC( "Address" ), (int *) &host_addr.s_addr );
+		pParams->GetInteger( Script::GenerateCRC( "Port" ), &port );
+		observe_only = ( gamenet_man->GetJoinMode() == GameNet::vJOIN_MODE_OBSERVE );
+	
+		if( port != 0 )
+		{
+			gamenet_man->SpawnClient( false, false, true, 0 );
+			gamenet_man->JoinServer( observe_only, (unsigned long) host_addr.s_addr, port, 0 );
+		}
+#else
+		const char *server_ip = NULL;
+		int port = 0;
+		bool observe_only;
+		
+		pParams->GetText( NONAME, &server_ip );
+		pParams->GetInteger( NONAME, &port );
+		observe_only = ( gamenet_man->GetJoinMode() == GameNet::vJOIN_MODE_OBSERVE );
+	
+		if( server_ip && ( port != 0 ))
+		{
+			gamenet_man->SpawnClient( false, false, true, 0 );
+			gamenet_man->JoinServer( observe_only, inet_addr( server_ip ), port, 0 );
+		}
+#endif
+	}
+    
+	Mem::Manager::sHandle().PopContext();
+    	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetNetworkMode | sets the network mode
+// @uparm 0 | mode
+bool ScriptSetNetworkMode( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int mode = 0;
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	mode = 0;
+	pParams->GetInteger( NONAME, &mode );
+	if( mode == 0 )
+	{
+		gamenet_man->SetNetworkMode( GameNet::vNO_NET_MODE );
+	}
+	else if( mode == 1 )
+	{
+		gamenet_man->SetNetworkMode( GameNet::vLAN_MODE );
+	}
+	else if( mode == 2 )
+	{
+		gamenet_man->SetNetworkMode( GameNet::vINTERNET_MODE );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MemViewToggle | 
+bool ScriptMemViewToggle( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	MemViewToggle();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ProfileTasks | profiles task for the number of frames specified
+// @uparm 1 | number of frames
+bool ScriptProfileTasks( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Mlp::Manager * mlp_manager =  Mlp::Manager::Instance();
+	int Val=1;
+	pParams->GetInteger(NONAME,&Val);	
+	printf ("\n\nProfiling task for %d frames:\n",Val);
+	mlp_manager->ProfileTasks(Val);
+	
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UseNetworkPreferences | 
+bool ScriptUseNetworkPreferences( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->UsePreferences();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptTestNetSetup( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Net::Manager * net_man =  Net::Manager::Instance();
+	bool properly_setup;
+	 
+	properly_setup = net_man->NetworkEnvironmentSetup();
+	if( !properly_setup )
+	{
+		if( net_man->GetLastError() == Net::vRES_ERROR_DEVICE_NOT_HOT )
+		{
+			pParams->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_not_connected" ));
+			Script::RunScript( "create_net_startup_error_dialog", pParams );
+		}
+		else if( net_man->GetLastError() == Net::vRES_ERROR_DEVICE_NOT_CONNECTED )
+		{
+			pParams->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_device_error" ));
+			Script::RunScript( "create_net_startup_error_dialog", pParams );
+		}
+		else if( net_man->GetLastError() == Net::vRES_ERROR_UNKNOWN_DEVICE )
+		{
+			pParams->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_not_detected" ));
+			Script::RunScript( "create_net_startup_error_dialog", pParams );
+		}
+		else if( net_man->GetLastError() == Net::vRES_ERROR_DHCP )
+		{
+			pParams->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_dhcp_error" ));
+			Script::RunScript( "create_net_startup_error_dialog", pParams );
+		}
+		else if( net_man->GetLastError() == Net::vRES_ERROR_DEVICE_CHANGED )
+		{
+			pParams->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_changed_device" ));
+			Script::RunScript( "create_net_startup_error_dialog", pParams );
+		}
+		else
+		{
+			pParams->AddComponent( Script::GenerateCRC( "text" ), ESYMBOLTYPE_STRING, Script::GetString( "net_error_general_error" ));
+			Script::RunScript( "create_net_startup_error_dialog", pParams );
+		}
+	}
+	else
+	{   
+		uint32 success_script;
+
+		pParams->GetChecksum( "success_script", &success_script, true );
+		Dbg_Printf( "***** SUCCESS! RUNNING SCRIPT %p\n", success_script );
+		Script::RunScript( success_script, pParams );
+		
+		/*Script::CStruct* pParams;
+
+		Script::RunScript( "dialog_box_exit" );
+		//Script::RunScript( "launch_network_select_menu" );
+		
+        pParams = new Script::CStruct;
+
+		pParams->AddChecksum( "change_gamemode", Script::GenerateCRC( "change_gamemode_net" ));
+		Script::RunScript( "launch_select_skater_menu", pParams );
+		
+		delete pParams;*/
+	}
+
+	return properly_setup;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptNeedToTestNetSetup( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Net::Manager * net_man =  Net::Manager::Instance();
+	
+	return net_man->NeedToTestNetworkEnvironment();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCanChangeDevices( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Net::Manager * net_man =  Net::Manager::Instance();
+
+	return net_man->CanChangeDevices();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ConnectToInternet | 
+bool ScriptConnectToInternet( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	uint32 success, failure;
+
+	success = 0;
+	failure = 0;
+
+	pParams->GetChecksum( CRCD(0x90ff204d,"success"), &success );
+	pParams->GetChecksum( CRCD(0xde64fc3e,"failure"), &failure );
+
+	return gamenet_man->ConnectToInternet( success, failure );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCancelConnectToInternet( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->CancelConnectToInternet();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCancelLogon( Script::CStruct *pParams, Script::CScript *pScript )
+{
+#ifdef __PLAT_XBOX__
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	gamenet_man->mpAuthMan->CancelLogon();
+	return true;
+#else
+	return true;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptIsOnline( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Net::Manager * net_man =  Net::Manager::Instance();
+
+	return net_man->IsOnline();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DisconnectFromInternet | 
+bool ScriptDisconnectFromInternet( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	uint32 callback_script = 0;
+
+	pParams->GetChecksum( CRCD(0x86068bd9,"callback"), &callback_script );
+	gamenet_man->DisconnectFromInternet( callback_script );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StopAllScripts | This will cause all scripts in existence
+// to stop (including the one calling this function). This function may
+// be useful later when doing cleanup type stuff. Note that though this
+// command will stop all scripts in existence at the moment it is called,
+// it won't prevent new scripts being run by the C-code
+bool ScriptStopAllScripts(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Script::StopAllScripts();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetMenuElementText | sets the specified menu element
+// @uparmopt "Blaa" | text to use for menu element - "Blaa" is the actual default
+bool ScriptSetMenuElementText(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	/*
+	uint32 Id=0;
+	pParams->GetChecksum("Id",&Id);
+	Dbg_MsgAssert(Id,("SetMenuElementText requires an Id"));
+	
+	const char *pText="Blaa";
+	pParams->GetText(NONAME,&pText);
+	
+	Front::MenuEvent event;
+	event.SetTypeAndTarget(Front::MenuEvent::vSETCONTENTS,Id);
+	Script::CStruct *pData = event.GetData();		
+	pData->AddComponent(Script::GenerateCRC("string"), ESYMBOLTYPE_STRING, pText);
+	 Front::MenuFactory * pMenuFactory =  Front::MenuFactory::Instance();
+	pMenuFactory->LaunchEvent(&event);
+	*/
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static bool FirstTimeThisIsCalled_Flag=true;
+// @script | FirstTimeThisIsCalled | returns true the first time this is called
+bool ScriptFirstTimeThisIsCalled(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	if (FirstTimeThisIsCalled_Flag)
+	{
+		FirstTimeThisIsCalled_Flag=false;
+		return true;
+	}
+	return false;		
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EnableActuators | 
+// @uparm 1 | 1 for enable - 0 for disable.  1 is the default
+bool	ScriptEnableActuators(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	int on = 1;	
+	pParams->GetInteger(NONAME,&on);
+	Inp::Manager * input_man =  Inp::Manager::Instance();
+	Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+	for (int i=0; i< SIO::vMAX_DEVICES; ++i)
+	{
+		if ( pSkate->mp_controller_preferences[i].VibrationOn )
+		{
+			if (on)
+			{
+				input_man->EnableActuator(i);
+			}
+			else
+			{
+				input_man->DisableActuator(i);
+			}
+		}
+	}
+
+	return true;
+}
+					  
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InNetGame | returns true if we're in a net game
+bool ScriptInNetGame(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	 
+	return gamenet_man->InNetGame();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsObserving | You can now use the "IsObserving" script
+// command to perform logic (ex. hide objects) if the player is
+// Observing a network game.
+bool ScriptIsObserving( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	GameNet::PlayerInfo* player;
+	GameNet::NewPlayerInfo* new_player;
+	Lst::Search< GameNet::NewPlayerInfo > sh;
+
+	if(( player = gamenet_man->GetLocalPlayer()))
+	{
+		return player->IsObserving();
+	}
+		
+	// If we're observing, we need to remove our skater
+	for( new_player = gamenet_man->FirstNewPlayerInfo( sh ); new_player; new_player = gamenet_man->NextNewPlayerInfo( sh ))
+	{
+		if( new_player->Flags & GameNet::PlayerInfo::mLOCAL_PLAYER )
+		{
+			if( new_player->Flags & GameNet::PlayerInfo::mOBSERVER )
+			{
+				return true;
+			}
+		}
+	}
+
+	return( gamenet_man->GetJoinMode() == GameNet::vJOIN_MODE_OBSERVE );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SkatersAreReady | true if all skaters connected to the server
+// are done loading
+bool ScriptSkatersAreReady(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	
+	// poll the server to find out whether the skaters are done loading
+	if( gamenet_man->ReadyToPlay() && 
+		( ( skate_mod->GetNumSkaters() > 0 ) ||
+		  ( ScriptIsObserving( NULL, NULL ))))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSlomo | sets slomo to specified value
+// @uparmopt 1.0 | value 
+bool ScriptSetSlomo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	float slomo = 1.0f;
+	pParams->GetFloat(NONAME,&slomo);
+	Tmr::SetSlomo(slomo);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetArenaSize | Obsolete function
+bool ScriptSetArenaSize(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_Message( "SetArenaSize is obsolete" );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetParticleSysVisibility | no longer supported.
+bool ScriptSetParticleSysVisibility( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	printf ("STUBBED: ScriptSetParticleSysVisibility\n");
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TogglePlayerNames | 
+bool ScriptTogglePlayerNames( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->TogglePlayerNames();
+	if( !gamenet_man->ShouldDrawPlayerNames())
+	{
+		Script::RunScript( "destroy_all_player_names" );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/*************************                          *****************************************/
+
+// @script | SetCurrentGameType | sets the current game type
+bool ScriptSetCurrentGameType( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	skate_mod->SetCurrentGameType();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DumpNetMessageStats | displays all net message stats
+bool ScriptDumpNetMessageStats( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Net::Conn* conn;
+	Lst::Search< Net::Conn > sh;
+	Net::App* app;
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+#ifdef __NOPT_ASSERT__
+	 Net::Manager * net_man =  Net::Manager::Instance();
+#endif		// __NOPT_ASSERT__
+	int i, j, total_size, total_num, size, num;
+	Net::Metrics* metrics;
+
+	app = gamenet_man->GetServer();
+	if( app == NULL )
+	{
+		app = gamenet_man->GetClient( 0 );
+	}
+
+	if( app == NULL )
+	{
+		return true;
+	}
+
+	i = 0;
+    for( conn = app->FirstConnection( &sh ); conn; conn = app->NextConnection( &sh ))
+	{
+		metrics = conn->GetInboundMetrics();
+		Dbg_Printf( "Conn %d inbound stats:\n", i );
+		total_size = 0;
+		total_num = 0;
+		for( j = 0; j < 256; j++ )
+		{
+			total_size += metrics->GetTotalMessageData( j );
+			total_num += metrics->GetTotalNumMessagesOfId( j );
+		}
+
+		// Guard against dbz
+		if( total_size == 0 )
+		{
+			total_size = 1;
+		}
+
+		for( j = 0; j < 256; j++ )
+		{
+			size = metrics->GetTotalMessageData( j );
+			num = metrics->GetTotalNumMessagesOfId( j );
+
+			if( num > 0 )
+			{
+#				ifdef __NOPT_ASSERT__
+				Dbg_Printf( "[%d-%s] Num: %d Size: %d Pct: %02f\n", j, net_man->GetMessageName( j ),
+						num, size, (float) size/(float) total_size );
+#				endif // __NOPT_ASSERT__
+			}
+		}
+
+		metrics = conn->GetOutboundMetrics();
+		Dbg_Printf( "Conn %d outbound stats:\n", i );
+		total_size = 0;
+		total_num = 0;
+		for( j = 0; j < 256; j++ )
+		{
+			total_size += metrics->GetTotalMessageData( j );
+			total_num += metrics->GetTotalNumMessagesOfId( j );
+		}
+
+		// Guard against dbz
+		if( total_size == 0 )
+		{
+			total_size = 1;
+		}
+
+		for( j = 0; j < 256; j++ )
+		{
+			size = metrics->GetTotalMessageData( j );
+			num = metrics->GetTotalNumMessagesOfId( j );
+
+			if( num > 0 )
+			{
+#				ifdef __NOPT_ASSERT__
+				Dbg_Printf( "[%d-%s] Num: %d Size: %d Pct: %02f\n", j, net_man->GetMessageName( j ),
+						num, size, (float) size/(float) total_size );
+#				endif // __NOPT_ASSERT__
+			}
+		}
+		i++;
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetServerMode | sets the server mode
+// @flag off | 
+bool  ScriptSetServerMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	bool off = pParams->ContainsFlag("off");
+    
+	gamenet_man->SetServerMode( !off );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | NotifyBailDone | 
+bool ScriptNotifyBailDone( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Net::Client* client;	
+
+	Obj::CSkater *pSkater=NULL;
+	if( pScript && pScript->mpObject && pScript->mpObject->GetType() == SKATE_TYPE_SKATER )	
+	{
+		// If the script using this has an object associated with it, and that object
+		// is a skater, then use that skater.
+		pSkater= (Obj::CSkater *) pScript->mpObject.Convert();
+	}
+
+	if( pSkater && pSkater->IsLocalClient())
+	{
+		client = gamenet_man->GetClient( pSkater->GetSkaterNumber());
+		if( client )
+		{
+			Net::MsgDesc msg_desc;
+
+			msg_desc.m_Id = GameNet::MSG_ID_BAIL_DONE;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+			client->EnqueueMessageToServer( &msg_desc );
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DisplayLoadingScreen | shows the loading screen 
+// @uparm "string" | name of the screen to display
+// @uparmopt time | the amount of time to display loading bar (default is 0, meaning no loading bar)
+// @flag Freeze | just freeze current screen
+// @flag Blank | clear screen to black
+bool ScriptDisplayLoadingScreen( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	const char	*pScreen = "images\\xxxxx";	
+	pParams->GetText(NONAME,&pScreen);
+	
+	Nx::CLoadScreen::sDisplay( (char*) pScreen, pParams->ContainsFlag(CRCD(0xb96e0be5,"Freeze")),pParams->ContainsFlag(CRCD(0xc3d43b9a,"blank")));
+
+	float duration;
+	if (pParams->GetFloat(NONAME,&duration))
+	{
+		Nx::CLoadScreen::sStartLoadingBar(duration);
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | HideLoadingScreen | hides the loading screen 
+bool ScriptHideLoadingScreen( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	//LoadScreen::Hide();
+	Nx::CLoadScreen::sHide();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EnterObserverMode | changes to observer mode (for net game)
+bool ScriptEnterObserverMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->RequestObserverMode();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ObserveNextSkater | 
+bool ScriptObserveNextSkater( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	skate_mod->ObserveNextSkater();
+
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AllowPause | 
+// @flag off | do not allow pause
+bool ScriptAllowPause(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	bool on = true;
+	if (pParams->ContainsFlag("off"))
+	{
+		on = false;
+	}		
+
+	printf ("WARNING: ScriptAllowPause is STUBBED\n");	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RefreshServerList | gets a new server list 
+bool ScriptRefreshServerList(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->RefreshServerList( pParams->ContainsFlag( "force_refresh" ));
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartServerList | 
+bool ScriptStartServerList(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->StartServerList();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptStartLobbyList( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	if(	gamenet_man->GetServerListState() == GameNet::vSERVER_LIST_STATE_SHUTDOWN )
+	{
+		gamenet_man->SetNextServerListState( GameNet::vSERVER_LIST_STATE_STARTING_LOBBY_LIST );
+		gamenet_man->SetServerListState( GameNet::vSERVER_LIST_STATE_INITIALIZE );
+	}
+	else
+	{
+		gamenet_man->SetServerListState( GameNet::vSERVER_LIST_STATE_STARTING_LOBBY_LIST );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StopServerList | 
+bool ScriptStopServerList(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->StopServerList();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FreeServerList | 
+bool ScriptFreeServerList(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->FreeServerList();
+	
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PauseGameFlow | suspends gameflow - should only be called from
+// gameflow.q
+bool ScriptPauseGameFlow(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	skate_mod->PauseGameFlow( true );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnPauseGameFlow | 
+bool ScriptUnpauseGameFlow(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	skate_mod->PauseGameFlow( false );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InFrontEnd | returns true if we are in the front end
+bool ScriptInFrontEnd(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	return skate_mod->GetGameMode()->IsFrontEnd();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InSplitScreenGame | returns true if we are in a split screen game
+bool ScriptInSplitScreenGame(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	return skate_mod->IsMultiplayerGame() && !gamenet_man->InNetGame() && !skate_mod->GetGameMode()->IsFrontEnd();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InMultiPlayerGame | 
+bool ScriptInMultiplayerGame(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	
+	return skate_mod->IsMultiplayerGame();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetFireballLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	int level;
+	 
+	Script::CStruct* params = pScript->GetParams();
+
+ 	level = skate_mod->GetGameMode()->GetFireballLevel();
+	params->AddInteger( CRCD(0x651533ec,"level"), level );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GameModeEquals | returns true if the current game mode matches
+// the specified game mode
+// @uparm name | game mode to check. More than one may be specified, eg GameModeEquals is_singlesession is_creategoals
+bool ScriptGameModeEquals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	 
+ 	CComponent *pComp=pParams->GetNextComponent();
+	while (pComp)
+	{
+		if (pComp->mNameChecksum==0 && pComp->mType==ESYMBOLTYPE_NAME)
+		{
+			if (skate_mod->GetGameMode()->IsTrue(pComp->mChecksum))
+			{
+				return true;
+			}
+		}
+		pComp=pParams->GetNextComponent(pComp);
+	}	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | OnServer | returns true if we are on a server
+bool ScriptOnServer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	return gamenet_man->OnServer();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCurrentLevel | returns level=load_foun  (or whatever)
+// so you can use it with LoadLevel, without going through LoadRequestedLevel
+bool ScriptGetCurrentLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	pScript->GetParams()->AddComponent( Script::GenerateCRC("level"), ESYMBOLTYPE_NAME, (int)skate_mod->m_requested_level );
+	
+	Game::CGoalManager* p_goal_manager = Game::GetGoalManager();
+	Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager ?"));
+	pScript->GetParams()->AddChecksum(CRCD(0x16a0b364,"level_structure"),p_goal_manager->GetLevelStructureName());
+	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RestartLevel | RestartLevel is just intended to be called
+// when a level is restarted it does not actually restart the level,
+// it just sets a few flags and does something with disabling the the
+// viewer log that's only related to screenshot mode 
+bool ScriptRestartLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	skate_mod->RestartLevel();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleScores | toggles game scores
+bool ScriptToggleScores(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->ToggleScores();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | XTriggered | 
+bool ScriptXTriggered(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    Dbg_MsgAssert( 0, ( "Obsolete function" ) );
+    return false;
+
+//	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+//	return skate_mod->XTriggered();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UsePad | sets the specified skater to use the pad
+// @uparmopt 0 | skater id (default is 0)
+bool ScriptUsePad(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    Dbg_MsgAssert( 0, ( "Obsolete function" ) );
+    return false;
+	
+/*
+    int skaterId = 0;
+	pParams->GetInteger( NONAME, &skaterId );
+
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	skate_mod->UsePad( skaterId );
+	return true;
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsTrue | Checks to see whether some global constant equals
+// 1 or 0.  (If it's not defined anywhere, then it returns 0.) 
+// Usage: 
+// if IsTrue run_viewer 
+//    printf "I am in viewer mode" 
+// else 
+//    printf "I am in skateshop mode" 
+// endif 
+// if IsTrue test_balls 
+// where test_balls is defined in "yourname".q. 
+//    printf "I am testing my balls here" 
+// endif 
+// @uparm name | global constant to check
+bool ScriptIsTrue(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int Integer=0;
+	pParams->GetInteger( NONAME, &Integer, false );
+	
+	return Integer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GameFlow | 
+// @uparm name | script name
+bool ScriptGameFlow(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 ScriptChecksum=0;
+	pParams->GetChecksum(NONAME,&ScriptChecksum);
+	Dbg_MsgAssert(ScriptChecksum,("\n%s\nGameFlow requires a script name, eg Gameflow Blaa",pScript->GetScriptInfo()));
+
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Dbg_AssertPtr( skate_mod->mp_gameFlow );
+	skate_mod->mp_gameFlow->Reset( ScriptChecksum );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptLastBroadcastedCheatWas(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 cheat_flag;
+
+	pParams->GetChecksum(CRCD(0xae94c183,"cheat_flag"), &cheat_flag, true );
+
+	return( s_last_broadcast_cheat == cheat_flag );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptClearCheats(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Obj::CSkaterCareer* career;
+
+	career = skate_mod->GetCareer();
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x13d5b5db,"CHEAT_ON_1")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x8adce461,"CHEAT_ON_2")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0xfddbd4f7,"CHEAT_ON_3")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x63bf4154,"CHEAT_ON_4")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x14b871c2,"CHEAT_ON_5")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x8db12078,"CHEAT_ON_6")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0xfab610ee,"CHEAT_ON_7")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x6a090d7f,"CHEAT_ON_8")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x1d0e3de9,"CHEAT_ON_9")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x37cbee45,"CHEAT_ON_10")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x40ccded3,"CHEAT_ON_11")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0xd9c58f69,"CHEAT_ON_12")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0xaec2bfff,"CHEAT_ON_13")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x30a62a5c,"CHEAT_ON_14")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x47a11aca,"CHEAT_ON_15")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0xdea84b70,"CHEAT_ON_16")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0xa9af7be6,"CHEAT_ON_17")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x39106677,"CHEAT_ON_18")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x4e1756e1,"CHEAT_ON_19")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x1ce6bd86,"CHEAT_ON_20")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x6be18d10,"CHEAT_ON_21")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0xf2e8dcaa,"CHEAT_ON_22")));
+	career->UnSetGlobalFlag( Script::GetInteger(CRCD(0x85efec3c,"CHEAT_ON_23")));
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptBroadcastCheat(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Lst::Search< Net::Conn > sh;
+	Net::Conn* conn;
+	Net::Server* server;
+	uint32 cheat_flag;
+	char on;
+
+	pParams->GetChecksum(CRCD(0xae94c183,"cheat_flag"), &cheat_flag, true );
+	Dbg_Assert( gamenet_man->OnServer());
+	Dbg_Assert( gamenet_man->InNetGame());
+	if( pParams->ContainsComponentNamed(CRCD(0xf649d637,"on")))
+	{
+		on = 1;
+	}
+	else
+	{
+		on = 0;
+	}
+
+	// Intentionally sprinkled in this function so that hackers have a more-difficult time to 
+	// nullify it
+	s_last_broadcast_cheat = cheat_flag;
+	server = gamenet_man->GetServer();
+	for( conn = server->FirstConnection( &sh ); conn; conn = server->NextConnection( &sh ))
+	{
+		if( conn->IsRemote())
+		{
+			Net::MsgDesc msg_desc;
+			GameNet::MsgToggleCheat msg;
+	
+			msg.m_Cheat = cheat_flag;
+			msg.m_On = on;
+	
+			msg_desc.m_Id = GameNet::MSG_ID_TOGGLE_CHEAT;
+			msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+			msg_desc.m_Data = &msg;
+			msg_desc.m_Length = sizeof( GameNet::MsgToggleCheat );
+			msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+		
+			server->EnqueueMessage( conn->GetHandle(), &msg_desc );
+			s_last_broadcast_cheat = cheat_flag;
+		}
+	}
+
+	s_last_broadcast_cheat = cheat_flag;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCheatAllowed(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 cheat_flag;
+
+	pParams->GetChecksum(CRCD(0xae94c183,"cheat_flag"), &cheat_flag, true );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | JoinWithPassword | joins the server with the specified password
+// @parm string | string | password string
+bool ScriptJoinWithPassword(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+    
+	const char* p_string;
+	pParams->GetText( "string", &p_string, true );
+
+    gamenet_man->ReattemptJoinWithPassword((char*) p_string );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SendChatMessage | 
+// @parm string | string | the message to send
+bool ScriptSendChatMessage(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	const char* p_string = NULL;
+	pParams->GetText( "string", &p_string );
+	if( p_string == NULL )
+	{
+		return false;
+	}
+
+#if ( ENGLISH == 0 )
+	if ( ( p_string != NULL ) && ( strlen( p_string ) > 0 ) && ( stricmp( p_string, Script::GetLocalString( "kc_str_empty" ) ) ) )
+#else
+	if ( ( p_string != NULL ) && ( strlen( p_string ) > 0 ) && ( stricmp( p_string, "--EMPTY--" ) ) )
+#endif
+	{
+		 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+		gamenet_man->SendChatMessage( (char*) p_string );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InSlapGame | true if we are in a slap or netslap game
+bool ScriptInSlapGame( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	if( ( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netslap" )) ||
+		(skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "slap" )))
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetScreenMode | Sets the way the screen will be split up into separate
+// viewports in the game. If there is more than one viewport, and multiple skaters
+// are active, then each skater will get his own viewport
+// @uparm One_Camera | the mode to use - one of the following: 
+// One_Camera 
+// Split_Vertical 
+// Split_Horizontal 
+// Split_Quarters
+bool ScriptSetScreenMode(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	Nx::ScriptSetScreenMode( pParams, pScript );
+
+	Obj::CSkater* pSkater;
+	Mdl::Score* pScore;
+	for( uint i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		pSkater = skate_mod->GetSkater(i);						   
+		if ( pSkater )			// Skater might not exist
+		{
+			pScore = pSkater->GetScoreObject();
+			pScore->RepositionMeters();
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetScreenModeFromGameMode | sets the appropriate screen mode based on the game mode
+bool ScriptSetScreenModeFromGameMode( Script::CStruct *pParams, Script::CScript *pScript )
+{           
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	Nx::ScreenMode requested_mode, current_mode;
+
+	Obj::CSkater* pSkater;
+	Mdl::Score* pScore;
+	for( uint i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		pSkater = skate_mod->GetSkater(i);						   
+		if ( pSkater && pSkater->IsLocalClient())			// Skater might not exist
+		{
+			printf ("cfuncs %d: SUTUBBBEDDDDDDDDDDDDDDDDDDDDDDDD\n",__LINE__);
+			
+			/*							
+			Gfx::Camera *p_camera = pSkater->GetSkaterCam();
+			if (p_camera)
+			{
+				Nx::CViewportManager::sSetCameraAllScreenModes(pSkater->GetHeapIndex(), p_camera);
+			}
+			*/
+		}
+	}
+	
+	// set up the appropriate screen mode
+	requested_mode = skate_mod->GetGameMode()->GetScreenMode();
+	current_mode = Nx::CViewportManager::sGetScreenMode();
+	if(! ((	( requested_mode == Nx::vSPLIT_V ) &&
+			( current_mode == Nx::vSPLIT_H )) ||
+			(( requested_mode == Nx::vSPLIT_H ) &&
+			( current_mode == Nx::vSPLIT_V ))))
+	{
+		Nx::CViewportManager::sSetScreenMode( requested_mode );
+	}
+
+	for( uint i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		pSkater = skate_mod->GetSkater(i);						   
+		if ( pSkater && pSkater->IsLocalClient())			// Skater might not exist
+		{
+			pScore = pSkater->GetScoreObject();
+			pScore->RepositionMeters();
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadPendingPlayers | 
+bool ScriptLoadPendingPlayers( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->LoadPendingPlayers();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LaunchQueuedScripts | 
+bool ScriptLaunchQueuedScripts( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->LaunchQueuedScripts();
+
+	return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// extend the current parameters to include:  string = "WOW"
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetInitialsString | gets the initials string for the player
+bool ScriptGetInitialsString( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+	Records::CGameRecords *pGameRecords=pSkate->GetGameRecords();
+	
+	pScript->GetParams()->AddComponent( Script::GenerateCRC( "string" ), ESYMBOLTYPE_STRING, pGameRecords->GetDefaultInitials()->Get() );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetInitialsString | sets the initials string for the player
+// @parm string | string | initials string
+bool ScriptSetInitialsString( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	 Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+	Records::CGameRecords *pGameRecords=pSkate->GetGameRecords();	
+	const char *pInitials = "ERR";
+	pParams->GetText("string",&pInitials);
+	pGameRecords->SetDefaultInitials(pInitials);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AttachToSkater | (DEPRECATED) Make the current script be running on the skater 
+// used for the gameflow, where we are not always sure if there is a skater there 
+// use this in place of SpawnSkaterScript in the gameflow 
+// if "AttachToSkater End" is called then will detatch it 
+// must be in mathcing pairs 
+// otherwise the gameflow gets messed up 
+// when the skater is deleted (as the gameflow gets stopped and deleted) 
+// @flag End | 
+bool	ScriptAttachToSkater(  Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+	Dbg_MsgAssert(0,("ScriptAttachToSkater should not be called, as it is old and dangerous\n"));	
+	
+	if (pParams->ContainsFlag("End"))
+	{
+		pScript->mpObject = NULL; 
+	}
+	else
+	{
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		Obj::CSkater *pSkater = skate_mod->GetLocalSkater();
+		Dbg_MsgAssert(pSkater,("AttachToSkater called before skater exists"));
+		pScript->mpObject = pSkater; 
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TryCheatString | 
+// @parm string | string | cheat string to try
+bool	ScriptTryCheatString(  Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	char  b[128];
+	const char *p;
+	pParams->GetText("string",&p,true);
+	if (strlen(p) < 6)
+	{
+		Script::RunScript("cheat_playsound_bad");
+		return false;
+	}
+	
+	sprintf(b,"12345678901234567890"); 		// just in case.....
+	sprintf(b,p);
+	
+	// (Mick)									  
+	// We want to generate a 64 bit CRC
+	// so we take 3 checksums, starting from 3 different places
+	// and add the first one to the second two
+	// and use those two numbers as a 64 bit checksum
+	// The idea here is to create a number, from which is is difficult to
+	// go back to the original string
+	// (to do this, you need a fairlry long string, otherwise, you could just try them all
+	// now we do 1 second worth of PS2 precessing the generate the key,
+	// so even on a computer 1000 times as fast, it would take ~30 years to try all six letter keys
+	// So, barring a flaw in my algorithm, should be pretty safe even with short keys 
+	
+	uint32 check0 = Script::GenerateCRC(p);	  // get checksum
+	
+	uint32 check1 = 0;	
+	for (int i = 0;i< 100000;i++)			  // a hundred thousand calls, to mess with Ken
+	{
+		sprintf (b,"%d",i+check0);			  // print a different number at the start
+		b[strlen(b)]='X';					  // link it to the rest of the string
+		char *p1 = b;
+		while (*p1)
+		{
+			check1 += 1023 * (*p1++);				  // and just add up the bytes, oh, how sweet...
+		}
+		check0 = Script::GenerateCRC(b);	  // oh, I slay myself...
+	}
+	uint32 check2 = Script::GenerateCRC(&p[strlen(p)/3]);	
+	uint32 check3 = Script::GenerateCRC(b);	   // use the final string, so no correlation with check2
+	
+//	printf ("%x,%x,%x\n",check1,check2,check3);	 // three pretty dang random looking numbers
+
+	check2 ^= check1;							 // munched into two
+	check3 ^= check1;							 // (hopefully not a fatal mistake)
+	
+//	printf ("%x,%x,%x\n",check1,check2,check3);
+	// Print it out, so we can cut and paste
+	printf ("\n{c1=%d c2=%d CheatScript=cheat_xxx },  ; %s\n\n",check2, check3,p); 
+	// then search through the cheat array to see if we have a match
+
+
+	#ifdef	__PLAT_XBOX__				 	
+	Script::CArray *pCheatArray = Script::GetArray( "Cheat_Array_Xbox" );
+	#endif
+	#ifdef	__PLAT_NGC__				 	
+	Script::CArray *pCheatArray = Script::GetArray( "Cheat_Array_Gamecube" );
+	#endif
+	#ifdef	__PLAT_NGPS__				 	
+	Script::CArray *pCheatArray = Script::GetArray( "Cheat_Array_PS2" );
+	#endif
+	
+	Dbg_MsgAssert( pCheatArray,( "No Cheat_Array found" ));
+	int cheats = pCheatArray->GetSize();
+	bool cheated = false;
+	for (int cheat = 0;cheatGetStructure(cheat);
+		Dbg_MsgAssert( pStruct,( "Cheat Array messed up" ));
+		uint32 c1,c2;
+		uint32 cheat_script;
+		pStruct->GetInteger("c1",(int*)&c1,true);
+		pStruct->GetInteger("c2",(int*)&c2,true);
+		pStruct->GetChecksum("cheatscript",&cheat_script,true);
+		if (c1 == check2 && c2 == check3)
+		{
+			Script::RunScript("cheat_playsound_good");
+			Script::RunScript(cheat_script);
+			cheated = true;
+		}
+	}
+	
+	if (!cheated)
+	{
+			Script::RunScript("cheat_playsound_bad");
+	}
+
+
+								
+	return false;								
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LevelIs | true if the level matches the specified level
+// @uparm name | level name
+bool ScriptLevelIs(  Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	
+	// GJ:  this may not work in park editor mode
+	uint32 level_name;
+	pParams->GetChecksum( NONAME, &level_name, true );
+	
+	return ( skate_mod->m_cur_level == level_name );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartNetworkLobby | 
+bool ScriptStartNetworkLobby( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	gamenet_man->StartNetworkLobby();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ObserversAllowed | 
+bool ScriptObserversAllowed( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 Net::Manager * net_man =  Net::Manager::Instance();
+	
+	// Modem games cannot host observers
+	return( net_man->GetConnectionType() != Net::vCONN_TYPE_MODEM );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | NumPlayersAllowed | 
+// @uparm name | number of players allowed
+bool ScriptNumPlayersAllowed( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	
+	uint32 checksum = 0;
+	 Net::Manager * net_man =  Net::Manager::Instance();
+
+	// Non-Modem games have no max player restriction
+	if( net_man->GetConnectionType() != Net::vCONN_TYPE_MODEM )
+	{
+		return true;
+	}
+    
+	pParams->GetChecksum( NONAME, &checksum );
+	if( checksum == Script::GenerateCRC( "num_4" ))
+	{
+		return false;
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AutoDNS | true if auto DNS
+bool ScriptAutoDNS( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 auto_dns_checksum;
+	 
+	pPreferences = gamenet_man->GetNetworkPreferences();
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("auto_dns") );
+	pStructure->GetChecksum( "Checksum", &auto_dns_checksum, true );
+	return ( auto_dns_checksum == Script::GenerateCRC( "boolean_true" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UsingDefaultMasterServers | true if using the default master
+// servers
+bool ScriptUsingDefaultMasterServers( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 default_servers;
+	 
+	pPreferences = gamenet_man->GetNetworkPreferences();
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("use_default_master_servers") );
+	pStructure->GetChecksum( "Checksum", &default_servers, true );
+	return ( default_servers == Script::GenerateCRC( "boolean_true" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UsingDHCP | true if using DHCP
+bool ScriptUsingDHCP( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	Script::CStruct* pStructure;
+	Prefs::Preferences* pPreferences;
+	uint32 ip_assignment;
+	 
+	pPreferences = gamenet_man->GetNetworkPreferences();
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("broadband_type") );
+	pStructure->GetChecksum( "Checksum", &ip_assignment, true );
+	return ( ip_assignment == Script::GenerateCRC( "ip_dhcp" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InInternetMode | 
+bool ScriptInInternetMode( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+
+	return gamenet_man->InInternetMode();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EnteringNetGame | true if we're in a network game but not 
+// read to play
+bool ScriptEnteringNetGame( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+	 return(( gamenet_man->InNetGame()) &&
+			( skate_mod->m_prev_level == Script::GenerateCRC( "Load_Skateshop" )));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DeviceChosen | true if a device type has been chosen
+bool ScriptDeviceChosen( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	uint32 checksum;
+	Prefs::Preferences* pPreferences;
+	Script::CStruct* pStructure;
+
+	pPreferences = gamenet_man->GetNetworkPreferences();
+	Dbg_Assert( pPreferences );
+
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("device_type") );
+	pStructure->GetChecksum( "checksum", &checksum, true );
+
+	return ( checksum != Script::GenerateCRC("device_none"));
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GameIsOver | true if game is over
+bool ScriptGameIsOver( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	 GameNet::Manager * gamenet_man =  GameNet::Manager::Instance();
+	
+	return gamenet_man->GameIsOver();
+}
+
+static char 	s_level_name[64];
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetLevelName | gets the level name into the above static, so it can be used for the DumpHeaps memory profile dump 
+bool ScriptSetLevelName( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	const char* pName;
+	pParams->GetText( NONAME, &pName, true );
+	sprintf(s_level_name,pName);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetPS2 | For testing the reset button handler when
+// the Sony Network adapter is loaded
+bool ScriptResetPS2( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+#	ifdef __PLAT_NGPS__
+	int stat;
+
+	Dbg_Printf( "Resetting PS2\n" );
+	
+	while( sceDevctl("dev9x:", DDIOC_OFF, NULL, 0, NULL, 0 ) < 0 );
+	// PS2 power off
+	while( !sceCdPowerOff( &stat ) || stat );
+#	endif // __PLAT_NGPS__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetHD | For testing the reset button handler when
+// the Sony Network adapter is loaded
+bool ScriptResetHD( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Dbg_Printf( "Resetting PS2\n" );
+
+#	ifdef __PLAT_NGPS__
+	int stat;
+	sceDevctl("pfs:", PDIOC_CLOSEALL, NULL, 0, NULL, 0);
+	while (sceDevctl("hdd:", HDIOC_DEV9OFF, NULL, 0, NULL, 0) < 0);
+	while (sceDevctl("dev9x:", DDIOC_OFF, NULL, 0, NULL, 0) < 0);
+    while (!sceCdPowerOff(&stat) || stat);
+#	endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PAL | returns true if we're in PAL mode
+bool ScriptPAL( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+#ifdef	PAL
+	return true;
+#else
+	return false;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Change | changes the symbol to the new value specified.  will do nothing
+// if called on the following: a symbol that doesn't already exist, a script symbol, c-function
+// or member function symbols 
+// should really only be called on strings, floats, and ints 
+// example: Change Foo=7
+// @uparm name | the name of the symbol to change
+// @uparm value | the value to assign to the symbol (make sure type is correct)
+// @flag Resolve | If specified, resolve the right hand side if it is a name of a global, so as to lose
+// the reference to that global. Ie, Change a=b Resolve where a=6 and b=3.141 will change a to be 3.141,
+// and it will remain so even if b is then changed to something else.
+bool ScriptChangeSymbolValue(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Extract the first component from pParams, which is used to define the name of the
+	// symbol to change and the new value to give to it.
+	CComponent *pComp=pParams->GetNextComponent();
+	Dbg_MsgAssert(pComp,("\n%s\nChange function requires a symbol name and new value, eg Change Foo=7",pScript->GetScriptInfo()));
+	Dbg_MsgAssert(pComp->mNameChecksum,("\n%s\nChange function requires a symbol name and new value, eg Change Foo=7",pScript->GetScriptInfo()));
+	if (!pComp)
+	{
+		return false;
+	}
+		
+	// Do a few checks on the old symbol.
+	CSymbolTableEntry *pOld=Script::LookUpSymbol(pComp->mNameChecksum);
+	
+	// Require that the old symbol must exist, otherwise continuing will create the symbol and it will never get deleted,
+	// causing fragmentation & memory leaks etc.
+	if (!pOld)
+	{
+		Dbg_MsgAssert(0,("\n%s\nTried to change the value of the non-existent symbol '%s'",pScript->GetScriptInfo(),FindChecksumName(pComp->mNameChecksum)));
+		return false;
+	}	
+	// Don't allow script symbols to be changed. If this were allowed then all existing CScripts that were running that
+	// script would have to get halted to avoid their PC's becoming invalid, which would require some extra code
+	// and seems kind of a silly thing to want to do anyway.
+	if (pOld->mType==ESYMBOLTYPE_QSCRIPT)
+	{
+		Dbg_MsgAssert(0,("\n%s\nTried to change the value of the script '%s'",pScript->GetScriptInfo(),FindChecksumName(pComp->mNameChecksum)));
+		return false;
+	}	
+	// Don't allow c-function or member function symbols to be changed, cos once changed they'll never be able to be put back.
+	if (pOld->mType==ESYMBOLTYPE_CFUNCTION)
+	{
+		Dbg_MsgAssert(0,("\n%s\nTried to change the value of the c-function '%s'",pScript->GetScriptInfo(),FindChecksumName(pComp->mNameChecksum)));
+		return false;
+	}	
+	if (pOld->mType==ESYMBOLTYPE_MEMBERFUNCTION)
+	{
+		Dbg_MsgAssert(0,("\n%s\nTried to change the value of the member function '%s'",pScript->GetScriptInfo(),FindChecksumName(pComp->mNameChecksum)));
+		return false;
+	}	
+	
+	// Hmmm, in theory other types could be changed, but they shouldn't be cos it'll cause fragmentation.
+	switch (pOld->mType)
+	{
+		case ESYMBOLTYPE_INTEGER:
+        case ESYMBOLTYPE_FLOAT:
+        case ESYMBOLTYPE_NAME:
+			break;
+		default:
+			Dbg_MsgAssert(0,("\n%s\nCannot change the value of '%s' because it has type '%s', and reallocating it will cause memory fragmentation. So there.",pScript->GetScriptInfo(),FindChecksumName(pComp->mNameChecksum),GetTypeName(pOld->mType)));
+			break;
+	}	
+	
+	// Remove the old symbol.
+	Script::CleanUpAndRemoveSymbol(pOld);
+
+	// Create it afresh.
+	CSymbolTableEntry *pNew=Script::CreateNewSymbolEntry(pComp->mNameChecksum); 
+
+	CComponent temp;
+	temp.mType=pComp->mType;
+	temp.mUnion=pComp->mUnion;
+
+	// If they are changing a value to some name, then support the option of resolving that name,
+	// in case it might be a global.
+	// For example, suppose a=6, and b=3.141
+	// 'Change a=b' will change a from 6 to the checksum b. This means it will preserve the reference to b,
+	// so in the C code if a GetFloat("a") is done it will return 3.141, but if b is changed to 28.3, 
+	// GetFloat("a") will now return 28.3, because it preserves the reference to b.
+	// However, 'Change a=b Resolve' will change a to be the float 3.141. Even if b is now changed, a has
+	// lost the reference to b and will stay as 3.141.
+	if (temp.mType==ESYMBOLTYPE_NAME)
+	{
+		if (pParams->ContainsFlag(CRCD(0x991a7bc3,"Resolve")))
+		{
+			CSymbolTableEntry *p_global=Script::Resolve(temp.mChecksum);
+			if (p_global)
+			{
+				temp.mType=p_global->mType;
+				temp.mUnion=p_global->mUnion;
+			}
+		}
+	}
+	
+	// Copy the new value into the symbol just created.
+	switch (temp.mType)
+	{
+		case ESYMBOLTYPE_INTEGER:
+            pNew->mType=ESYMBOLTYPE_INTEGER;
+            pNew->mIntegerValue=temp.mIntegerValue;
+            break;
+
+        case ESYMBOLTYPE_FLOAT:
+            pNew->mType=ESYMBOLTYPE_FLOAT;
+            pNew->mFloatValue=temp.mFloatValue;
+            break;
+
+        case ESYMBOLTYPE_NAME:
+            pNew->mType=ESYMBOLTYPE_NAME;
+            pNew->mChecksum=temp.mChecksum;
+            break;
+			
+        default:
+            Dbg_MsgAssert(0,("\n%s\nChange function does not support component type '%s'",pScript->GetScriptInfo(),GetTypeName(temp.mType)));
+            break;
+	}
+
+	// This is to prevent an assert in the CComponent destructor
+	temp.mUnion=0;
+	
+	return true;									   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DumpScripts | Prints out the names of all the currently existing scripts.
+bool ScriptDumpScripts( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	bool just_stack = pParams->ContainsFlag("just_stack");
+	
+	if (!just_stack)
+		Script::DumpScripts();
+	
+	// XXX
+	#ifdef	__NOPT_ASSERT__
+	printf("Script stack is: %s\n", pScript->GetScriptInfo());
+	#endif
+	
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TimeUp | true if time is up
+bool ScriptTimeUp( Script::CStruct *pParams, Script::CScript *pScript )
+{
+     Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+
+    return skate_mod->GetGameMode()->ShouldUseClock() && ( skate_mod->GetGameMode()->GetTimeLeft() <= 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LaunchViewer | 
+bool ScriptLaunchViewer( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::Manager * mdl_manager =  Mdl::Manager::Instance();
+	Mdl::CViewer* viewer_mod;
+	static bool initialized = false;
+	
+	if( initialized )
+	{
+		return true;
+	}
+
+	viewer_mod = new Mdl::CViewer;
+
+	mdl_manager->RegisterModule ( *viewer_mod );
+	mdl_manager->StartModule( *viewer_mod );
+	initialized = true;
+	
+	
+	// Add the parameters for the skater camera component		
+	// so it can get me as the target
+	Script::CStruct * p_cam_params = new Script::CStruct;
+	p_cam_params->AddChecksum("name",CRCD(0xeb17151b,"viewer_cam"));
+	
+	// Also create the camera components here.
+	Obj::CCompositeObject *p_cam_object = Obj::CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(
+										Script::GetArray("viewercam_composite_structure"),p_cam_params);
+	
+	delete	p_cam_params;
+	p_cam_object->SetLockOn();		// Lock it so it does not get deleted between levels
+
+
+	return true;
+}
+
+// @script | LaunchViewer | 
+bool ScriptLaunchScriptDebugger( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	#ifdef __NOPT_ASSERT__
+	static bool initialized = false;
+	
+	if( initialized )
+	{
+		return true;
+	}
+
+	Mdl::Manager::Instance()->StartModule( *Dbg::CScriptDebugger::Instance() );
+	
+	initialized = true;
+	#endif
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetViewerModel | 
+bool ScriptSetViewerModel( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+
+	s_view_mode = (int)Obj::VIEWER_SKATER_PAUSED;
+	
+	// sync the viewer up to cfuncs.cpp's version of s_view_mode
+	pViewer->sSetViewMode( s_view_mode );
+	// Handle toggling to different cameras based on view mode
+	
+	switch (s_view_mode )
+	{
+		case Obj::GAMEPLAY_SKATER_ACTIVE:
+			SetActiveCamera(CRCD(0x967c138c,"skatercam0"),0, false);			
+			break;
+		default:
+			SetActiveCamera(CRCD(0xeb17151b,"viewer_cam"),0, true);						
+			break;		
+	}
+
+
+	Obj::CSkater* pSkater;
+	for ( int i = 0; i < 8; i++ )
+	{
+		 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+		pSkater = skate_mod->GetSkater(i);						   
+		if ( pSkater )			// Skater might not exist
+		{
+			pSkater->SetViewMode( (Obj::EViewMode)s_view_mode );
+		}
+	}
+	
+	if ( pViewer )
+	{
+		pViewer->ResetCameraToViewerObject();
+		
+		Script::PrintContents(pParams);
+
+		pViewer->AddViewerObject(pParams);
+
+		/*
+		Obj::CViewerObject* pViewerObject = pViewer->GetViewerObject();
+		if ( pViewerObject )
+		{
+			pViewerObject->UnloadModel();
+			
+			pViewerObject->LoadModel( pParams );
+		}
+		*/
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetViewerAnim | 
+bool ScriptSetViewerAnim( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+
+	if ( pViewer )
+	{
+		uint32 animName;
+		pParams->GetChecksum( NONAME, &animName, true );
+
+		Obj::CViewerObject* pViewerObject = pViewer->GetViewerObject();
+		if ( pViewerObject )
+		{
+			pViewerObject->SetAnim( animName );
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptSetViewerLODDist | 
+bool ScriptSetViewerLODDist( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+
+	if ( pViewer )
+	{
+		Script::CPair thePair;
+		if ( !pParams->GetPair( NONAME, &thePair, Script::NO_ASSERT ) )
+		{
+			Dbg_Message( "Looking for pair in the format:  SetViewerLODDist (1,500.0f)" );
+			return false;
+		}
+
+		Obj::CViewerObject* pViewerObject = pViewer->GetViewerObject();
+		if ( pViewerObject )
+		{
+			GetModelComponentFromObject( pViewerObject )->SetModelLODDistance( (int)thePair.mX, thePair.mY );
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetViewerObjectID( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	// clear out the viewer object, if any
+	Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+	if ( pViewer )
+	{
+		Obj::CCompositeObject* pViewerObject = pViewer->GetViewerObject();
+		pScript->GetParams()->AddChecksum( CRCD(0x0830ecaf,"objID"), pViewerObject->GetID() );
+		pScript->GetParams()->AddChecksum( CRCD(0x52280066,"viewerObjectId"), pViewerObject->GetID() );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptReloadViewerAnim( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::CViewer* pViewer = Mdl::CViewer::sGetViewer();
+
+	if ( pViewer )
+	{
+		Obj::CViewerObject* pViewerObject = pViewer->GetViewerObject();
+		if ( pViewerObject )
+		{
+			uint32 animName;
+			const char* fileName;
+
+			if ( pParams->GetChecksum( NONAME, &animName )
+				 && pParams->GetText( NONAME, &fileName ) )
+			{
+				pViewerObject->ReloadAnim( fileName, animName );
+			}
+			else
+			{
+				Dbg_Message( "Need 2 parameters to viewer anim:  filename string, and anim checksum" );
+			}
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AddRestartsToMenu | adds all restart points to the menu
+bool ScriptAddRestartsToMenu( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	
+	Script::CStruct* p_restart_params;
+	int 	node = -1;
+	int 	entry = 0;
+	do
+	{
+		node = Obj::GetRestartNode(0, 0, node);
+		if (node != -1)
+		{
+			Script::CArray *pNodeArray=Script::GetArray(CRCD(0xc472ecc5,"NodeArray"));
+			Script::CScriptStructure *pNode=pNodeArray->GetStructure(node);
+			const char *pName;
+			if (!pNode->GetText("RestartName",&pName))
+			{
+				pName = "Unnamed restart";
+			}
+			p_restart_params = new Script::CStruct;	
+			p_restart_params->AddString("text",pName);
+			p_restart_params->AddChecksum("id",123456 + entry);
+			p_restart_params->AddChecksum("no_bg",CRCD(0x9f67b2c8,"no_bg"));
+			p_restart_params->AddChecksum("centered",CRCD(0x2a434d05,"centered"));
+			p_restart_params->AddChecksum("pad_choose_script",Script::GenerateCRC("skip_to_selected_restart"));
+			
+			// create the parameters that are passed to the X script
+			Script::CStruct *p_script_params= new Script::CStruct;
+			p_script_params->AddInteger("node_number",node);	
+			p_restart_params->AddStructure("pad_choose_params",p_script_params);			
+
+			/*if (!Script::GetInt("SimpleRestarts",false))
+			{
+				p_restart_params->AddChecksum("focus_script",Script::GenerateCRC("preview_restart"));
+				// also pass it too the Focus script
+				p_restart_params->AddStructure("focus_params",p_script_params);						
+			}*/
+			
+            // scale the restarts
+            float initial_scale;
+            pParams->GetFloat( "initial_scale", &initial_scale, Script::ASSERT );
+            p_restart_params->AddFloat( "scale", initial_scale );
+            //p_restart_params->AddChecksum( "unfocus_script", Script::GenerateCRC( "scale_down_restart" ) );
+			
+			Script::RunScript("theme_menu_add_item",p_restart_params);
+			delete p_restart_params;
+			delete p_script_params;
+
+			entry++;
+		}
+	} while (node != -1);
+
+	if (entry == 0)
+	{
+			p_restart_params = new Script::CStruct;	
+			p_restart_params->AddString("text","No restarts in level");
+			p_restart_params->AddChecksum("id",123456 + entry);
+			p_restart_params->AddChecksum("no_bg",CRCD(0x9f67b2c8,"no_bg"));
+			p_restart_params->AddChecksum("centered",CRCD(0x2a434d05,"centered"));
+			p_restart_params->AddChecksum("pad_choose_script",Script::GenerateCRC("skip_to_selected_restart"));
+			Script::RunScript("theme_menu_add_item",p_restart_params);
+			delete p_restart_params;
+	}
+
+	
+	
+	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAddWarpPointsToMenu( Script::CStruct* pParams, Script::CScript* pScript ) {
+    Script::CStruct* p_restart_params;
+    
+    // grab array of warp points
+    Script::CArray *p_Array = NULL;
+
+    pParams->GetArray( "nodes", &p_Array );
+    Dbg_MsgAssert(p_Array, ("AddWarpPointsToMenu requires an array of nodes"));
+    // better only be names
+    Dbg_MsgAssert(p_Array->GetType()==ESYMBOLTYPE_NAME,("\n%s\nnodes: Array must be of names",pScript->GetScriptInfo()));
+    
+    
+    // add nodes to menu    
+    for (uint32 i=0; iGetSize(); ++i) {
+		// Obj_MoveToNode name =  Orient NoReset
+        
+        p_restart_params = new Script::CStruct;
+        
+        Script::CScriptStructure *pNode = SkateScript::GetNode( SkateScript::FindNamedNode( p_Array->GetChecksum( i ) ) );
+        const char *pName;
+        if (!pNode->GetText("RestartName",&pName))
+        {
+            pName = "Unnamed restart";
+        }        
+        
+        p_restart_params->AddString( "text", pName );
+        p_restart_params->AddChecksum("pad_choose_script",Script::GenerateCRC("WarpSkater"));
+
+        // create the parameters that are passed to the script
+        Script::CStruct *p_script_params = new Script::CStruct;
+        p_script_params->AddChecksum( "nodename", p_Array->GetChecksum( i ) );
+        p_restart_params->AddStructure( "pad_choose_params", p_script_params );
+
+        Script::RunScript( "make_text_sub_menu_item", p_restart_params );
+        delete p_restart_params;
+
+    }
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptRunScriptOnObject | allows you to access Obj_ type functions, from a global script
+bool ScriptRunScriptOnObject( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	// searches for object by name
+
+	uint32 obj_id;
+	pParams->GetChecksum("id", &obj_id, true);
+    
+	uint32 scriptName;
+	pParams->GetChecksum(NONAME, &scriptName, true);
+
+	Script::CStruct* pSubParams = NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"params"), &pSubParams);
+
+	pScript->GetParams()->RemoveComponent(CRCD(0xa2b033fd,"UniqueID"));	
+	
+	 // this returns all objects, not just game objects and peds
+	Obj::CObject* pObject = Obj::ResolveToObject( obj_id );
+
+	if ( pObject )
+	{
+		/*
+		Script::CScript *pNewScript=Script::SpawnScript(scriptName,pSubParams,0,NULL,scriptName);
+		#ifdef __NOPT_ASSERT__
+		pNewScript->SetCommentString("Spawned by script command RunScriptOnObject");
+		pNewScript->SetOriginatingScriptInfo(pScript->GetCurrentLineNumber(),pScript->mScriptChecksum);
+		#endif	
+		
+		pNewScript->mpObject = pObject;
+		
+		pScript->GetParams()->AddChecksum("UniqueID",pNewScript->GetUniqueId());
+//		Script::RunScript( scriptName, pSubParams, obj_and_id.pObj );
+
+	*/
+	
+		if (pObject->GetScript())
+		{
+			// (Mick) If this object has a script, but it's got a NULL mpObject
+			// that means that script has been cleared in the current game loop
+			// but not got around to being deleted.
+			// it's quite safe to re-use it, we just need to set the object on it
+			if (pObject->GetScript()->mpObject == NULL)
+			{
+				pObject->GetScript()->mpObject = pObject;
+			}
+		
+			Dbg_MsgAssert(pObject->GetScript()->mpObject == pObject,("%s\nscript object %p not same as object %p",pScript->GetScriptInfo(),pObject->GetScript()->mpObject.Convert(),pObject));
+		}
+		// (Mick) Instead of spawning a script, call it on the main script
+		// Which allows us to set exceptions and event handlers
+		// It's also a lot quicker
+		// (Might be problem with parameter conflicts?)
+		pObject->CallScript(scriptName, pSubParams);
+		
+		
+		return true;
+	
+		
+	}
+	else
+	{
+		#ifdef	__NOPT_ASSERT__
+		printf( "Warning: Couldn't find object %s on which to run script %s\n", Script::FindChecksumName(obj_id), Script::FindChecksumName(scriptName) );
+		#endif
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RunScriptOnComponentType | 
+bool ScriptRunScriptOnComponentType( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 componentType;
+	pParams->GetChecksum( CRCD(0xb6015ea8,"component"), &componentType, Script::ASSERT );
+
+	uint32 target;
+	pParams->GetChecksum( CRCD(0xb990d003,"target"), &target, Script::ASSERT );
+
+	Script::CStruct* pSubParams = NULL;
+	pParams->GetStructure( CRCD(0x7031f10c,"params"), &pSubParams, Script::NO_ASSERT );
+
+	// new fast way, just go directly to the components, if any
+	Obj::CBaseComponent *p_component = Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( componentType );
+	while( p_component )
+	{
+		p_component->CallMemberFunction( target, pSubParams, pScript );
+		
+		p_component = p_component->GetNextSameType();		
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | Debounce | This will ignore the specified button
+// until either the time has elapsed, or the button has been
+// released and subsequently re-pressed. 
+// For X, this prevents the press of X from carrying over from one
+// screen to the next, and removes the need to have a delay,
+// whilst still allowing the user to actively X past things
+// @uparm X | button name
+// @parmopt float | time | 1.0 | time to wait (in seconds)
+// @parmopt int | clear | 0 | if 1, then clear the current pressed and triggered states
+	
+bool ScriptDebounce( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 ButtonChecksum=0;
+	pParams->GetChecksum(NONAME,&ButtonChecksum);
+	if (!ButtonChecksum)
+	{
+		return true;
+	}	
+	
+	float time=1.0f;
+	pParams->GetFloat(CRCD(0x906b67ba, "time"),&time);
+
+	int	clear = 0;
+	pParams->GetInteger(CRCD(0x1a4e0ef9, "clear"),&clear);
+	
+	Obj::CInputComponent* p_input_component
+		= static_cast< Obj::CInputComponent* >(Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType(CRC_INPUT));
+	while (p_input_component)
+	{
+		p_input_component->Debounce(ButtonChecksum, time, clear);
+		p_input_component = static_cast< Obj::CInputComponent* >(p_input_component->GetNextSameType());
+	}
+		
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetStatOverride | Set a global stat override for debugging
+// set it to 0 to use normal stats
+bool ScriptSetStatOverride( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	float	value = 0.0f;
+	pParams->GetFloat(NONAME,&value);
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	skate_mod->SetStatOverride(value);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleRails | Toggle the debug display of rails on and off
+bool ScriptToggleRails( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	skate_mod->SetDrawRails(!skate_mod->GetDrawRails());
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleRigidBodyDebug | Toggle the debug display of rigidbody characteristics
+bool ScriptToggleRigidBodyDebug( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Obj::CRigidBodyComponent::sToggleDrawRigidBodyDebugLines(); 
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CheckForHoles | check the scene file for holes in geometry, and visually display them
+bool ScriptCheckForHoles( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Nx::CEngine::sDebugCheckForHoles();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | OnXbox |
+bool ScriptOnXbox( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __PLAT_XBOX__
+	return true;
+#else
+	return false;
+#endif	// __PLAT_XBOX__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GotoXboxDashboard |
+bool ScriptGotoXboxDashboard( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#	ifdef __PLAT_XBOX__
+	LD_LAUNCH_DASHBOARD ld;
+    ZeroMemory( &ld, sizeof(ld) );
+
+	// In the case where we are rebooting to the memory management section of the dashboard,
+	// this value should contain the total number of blocks needed.
+	uint32 total_blocks_needed = 0;
+
+	if( pParams->ContainsFlag( 0x1592cbca /*"memory"*/ ))
+	{
+		pParams->GetInteger(CRCD(0xb204db86,"total_blocks_needed"),(int*)&total_blocks_needed);
+		
+		ld.dwReason		= XLD_LAUNCH_DASHBOARD_MEMORY;
+		ld.dwParameter1	= 'U';
+		ld.dwParameter2	= total_blocks_needed;
+	}
+	else
+	{
+		ld.dwReason = XLD_LAUNCH_DASHBOARD_MAIN_MENU;
+	}
+
+    XLaunchNewImage( NULL, PLAUNCH_DATA( &ld ) );
+#	endif	// __PLAT_XBOX__
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSystemLinkEnabled( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __PLAT_XBOX__
+	DWORD result;
+	
+	result = XNetGetEthernetLinkStatus();
+	if(( result & XNET_ETHERNET_LINK_ACTIVE ) == 0 )
+	{
+		return false;
+	}
+#endif	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Particle system functions.
+// @script | CreateParticleSystem | Creates a particle system.
+// @parm name | name | the name of the particle system. (Should be Particle_UnNamed if created from a node)
+// @parmopt int | max | 256 | maximum number of particles this system can display.
+// @parmopt name | texture | | the name of the texture to apply. No name means use flat shaded.
+// @parmopt name | emitscript | | the name of the script to call for emission.
+// @parmopt name | updatescript | | the name of the script to call to update position etc.
+// @parmopt name | blendmode | diffuse | The blendmode: blend/add/sub/modulate/brighten & 
+// fixblend/fixadd/fixsub/fixmodulate/fixbrighten & diffuse (no blend at all). Defaults to diffuse.
+// @parmopt int | fix | 0 | Fixed alpha value. 128 is normal. Range is 0-255.
+// @parmopt name | params | | Sets a parameter block for use with the created particle scripts.
+// @parmopt name | type | flat | Sets the type of particle (flat/shaded/smooth/glow). Defaults to flat.
+// @parmopt int | segments | 8 | The number of segments in a segmented particle, such as 'Glow'.
+// @parmopt float | split | 0.5 | Sets the split point in a segmented particle. 0 = split at center. 1 means split at outer edge.
+// @parmopt int | history | 1 | Number of history lists to keep. Minimum is 1 when using trail particle types.
+// @parmopt int | perm | 0 | set to 1 if particle system stays between levels (like the skaters)
+// update script to grab bone/node positions when attaching.
+//
+// 
+bool ScriptCreateParticleSystem( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name = 0;
+	pParams->GetChecksum("Name",&name);
+
+	int max = 256;
+	pParams->GetInteger("Max",&max);
+
+	int max_streams = 2;
+	pParams->GetInteger("MaxStreams",&max_streams);
+
+	uint32 texture = 0;
+	pParams->GetChecksum("Texture",&texture);
+
+	uint32 blendmode = 0x515e298e;		// Defaults to diffuse.
+	pParams->GetChecksum("blendmode",&blendmode);
+	
+	uint32 type = 0xaab555bb;		// Defaults to flat.
+	pParams->GetChecksum("type",&type);
+	
+	int fix = 0;
+	pParams->GetInteger("fix",&fix);
+
+	int segments = 8;
+	pParams->GetInteger("segments",&segments);
+
+	float split = 0.5f;
+	pParams->GetFloat("split",&split);
+
+	int history = 1;
+	pParams->GetInteger("history",&history);
+
+	int perm = 0;
+	pParams->GetInteger("perm",&perm);
+
+	// Create a particle. Will be internally added to the process list, so we don't need to do
+	// anything after it has been created.
+	Nx::CParticle * p_particle = Nx::create_particle( name, type, max, max_streams, texture, blendmode, fix, segments, split, history, perm );
+	
+	Script::CStruct* pSubParams = NULL;
+	pParams->GetStructure( "params", &pSubParams, Script::NO_ASSERT );
+
+	uint32 emitscript = 0;
+	if ( pParams->GetChecksum("emitscript",&emitscript) )
+	{
+		p_particle->set_emit_script( emitscript, pSubParams );
+	}
+
+								
+//	p_particle->SetActive( false );
+//								
+//	uint32 active = 0;
+//	if ( pParams->GetChecksum("active",&active) )
+//	{
+//		p_particle->SetActive( active );
+//	}
+	
+	uint32 updatescript = 0;
+	if ( pParams->GetChecksum("updatescript",&updatescript) )
+	{
+		p_particle->set_update_script( updatescript, pSubParams );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetScript | Sets the emission script.
+// @parm name | name | the name of the particle system.
+// @parmopt name | emitscript | | the name of the script to call for emission.
+// @parmopt name | updatescript | | the name of the script to call to update position etc.
+// @parmopt name | params | | Sets a parameter block for use with the created particle scripts.
+bool ScriptSetScript( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name = 0;
+	pParams->GetChecksum("Name",&name);
+
+	Script::CStruct* pSubParams = NULL;
+	pParams->GetStructure( "params", &pSubParams, Script::NO_ASSERT );
+
+	Nx::CParticle * p_particle = Nx::get_particle( name );
+	Dbg_MsgAssert( p_particle, ( "Couldn't find the requested particle system." ) );
+
+	uint32 emitscript = 0;
+	if ( pParams->GetChecksum("emitscript",&emitscript) )
+	{
+		p_particle->set_emit_script( emitscript, pSubParams );
+	}
+	
+	uint32 updatescript = 0;
+	if ( pParams->GetChecksum("updatescript",&updatescript) )
+	{
+		p_particle->set_update_script( updatescript, pSubParams );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DestroyParticleSystem | Destroys a particle system.
+// @parm name | name | the name of the particle system.
+// @parmopt int | ifempty | 0 | Set this to 1 if you want the particle system to be destroyed only when empty.
+bool ScriptDestroyParticleSystem( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name = 0;
+	pParams->GetChecksum("Name",&name);
+
+	int ifempty = 0;
+	pParams->GetInteger("IfEmpty",&ifempty);
+
+	if ( ifempty )
+	{
+		Nx::destroy_particle_when_empty( name );
+	}
+	else
+	{
+		Nx::destroy_particle( name );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EmptyParticleSystem | Empties the particle system immediately (sets number of particles to 0. 
+// @parm name | name | the name of the particle system.
+bool ScriptEmptyParticleSystem( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name = 0;
+	pParams->GetChecksum("Name",&name);
+
+	Nx::CParticle * p_particle = Nx::get_particle( name );
+	Dbg_MsgAssert( p_particle, ( "Couldn't find the requested particle system." ) );
+
+	p_particle->SetNumParticles( 0 );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ParticleExists | Returns whether the specified particle system exists
+// @parm name | name | the name of the particle system.
+bool ScriptParticleExists( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name = 0;
+	if ( !pParams->GetChecksum("Name",&name ) )
+	{
+		pParams->GetChecksum(NONAME,&name,Script::ASSERT);
+	}
+
+	Nx::CParticle * p_particle = Nx::get_particle( name );
+
+	if ( p_particle )
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StructureContains | true if the structure contains an element
+// or flag with the specified name
+// @parm structure | structure | the structure to check
+// @uparm name | the named param to look for
+bool ScriptStructureContains( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Script::CStruct* p_struct;
+	if ( pParams->GetStructure( "structure", &p_struct, Script::NO_ASSERT ) )
+	{	
+		// printf("got a structure\n");
+		uint32 name;
+		pParams->GetChecksum( NONAME, &name, Script::NO_ASSERT );
+		Dbg_MsgAssert( name, ( "StructureContains called without a param name" ) );
+		// printf("checking for a name of %x\n", name );
+		if ( p_struct->ContainsComponentNamed( name ) )
+		{
+			return true;
+		}
+		else
+			return p_struct->ContainsFlag( name );
+	}
+	// printf("didn't get a structure\n");
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetBonePosition | allows you to access Obj_ type functions, from a global script
+bool ScriptGetBonePosition( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mth::Vector bonePos;
+	bool success = false;
+	
+	// searches for object by m_id
+	uint32 obj_id;
+	pParams->GetChecksum( CRCD(0x40c698af,"id"), &obj_id, Script::ASSERT );
+	
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();	
+	Obj::CGeneralManager* pObjectManager = skate_mod->GetObjectManager();
+	Dbg_Assert( pObjectManager );
+
+	Obj::CObject* pObject = pObjectManager->GetObjectByID( obj_id );
+	if ( pObject )
+	{
+		Dbg_MsgAssert( static_cast( pObject ), ( "This function only works on moving objects." ) ); 
+		Obj::CMovingObject* pMovingObject = (Obj::CMovingObject*)pObject;
+
+		uint32 boneName;
+		pParams->GetChecksum( CRCD(0xcab94088,"bone"), &boneName, Script::ASSERT );
+		
+		Obj::CSkeletonComponent* p_skeleton_component = GetSkeletonComponentFromObject(pMovingObject);
+		Dbg_Assert(p_skeleton_component);
+		success = p_skeleton_component->GetBoneWorldPosition( boneName, &bonePos );
+	}
+	
+	// make sure we have somewhere to return the data
+	Dbg_Assert( pScript && pScript->GetParams() );
+	pScript->GetParams()->AddFloat( CRCD(0x7323e97c,"x"), bonePos[X] );
+	pScript->GetParams()->AddFloat( CRCD(0x424d9ea,"y"), bonePos[Y] );
+	pScript->GetParams()->AddFloat( CRCD(0x9d2d8850,"z"), bonePos[Z] );
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ShouldEmitParticles | tests whether the specified particle system is active
+bool ScriptShouldEmitParticles( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+
+	Nx::CParticle * p_particle = Nx::get_particle( name );
+
+	if ( p_particle )
+	{
+		return p_particle->IsEmitting();
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ParticlesOn | turns on the specified particle system
+bool ScriptParticlesOn( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name;
+	pParams->GetChecksum( "name", &name, Script::ASSERT );
+
+	Nx::CParticle * p_particle = Nx::get_particle( name );
+
+	if ( p_particle )
+	{
+		p_particle->SetEmitting( true );
+	}
+	else
+	{
+//		Dbg_MsgAssert( 0, ( "Couldn't find particle system to turn on" ) );
+	}
+
+	return ( p_particle != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ParticlesOff | turns off the specified particle system
+bool ScriptParticlesOff( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name;
+	pParams->GetChecksum( "name", &name, Script::ASSERT );
+
+	Nx::CParticle * p_particle = Nx::get_particle( name );
+
+	if ( p_particle )
+	{
+		p_particle->SetEmitting( false );
+	}
+	else
+	{
+//		Dbg_MsgAssert( 0, ( "Couldn't find particle system to turn off" ) );
+	}
+
+	return ( p_particle != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MangleChecksums | adds 2 checksums together and returns the result in "mangled_id"
+// @parm name | a | First checksum
+// @parm name | b | Second checksum
+// @parm name | mangled_id | The returned value for the mangled checksum
+bool ScriptMangleChecksums( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 a;
+	if ( !pParams->GetChecksum( CRCD(0x174841bc,"a"), &a, Script::NO_ASSERT ) )
+	{
+		pParams->GetInteger( CRCD(0x174841bc,"a"), (int*)&a, Script::ASSERT );
+	}
+	
+	uint32 b;
+	if ( !pParams->GetChecksum( CRCD(0x8e411006,"b"), &b, Script::NO_ASSERT ) )
+	{
+		pParams->GetInteger( CRCD(0x8e411006,"b"), (int*)&b, Script::ASSERT );
+	}
+	
+	uint32 mangled_id = a + b;
+	Dbg_Assert( pScript && pScript->GetParams() );
+	pScript->GetParams()->AddChecksum( CRCD(0xfba40626,"mangled_id"), mangled_id );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AppendSuffixToChecksum | Appends a string suffix to a checksum and returns the result in "appended_id"
+// @parm name | Base | The base checksum
+// @parm string | SuffixString | The suffix string
+bool ScriptAppendSuffixToChecksum( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 base_checksum;
+	pParams->GetChecksum(CRCD(0x3f4b019e, "Base"), &base_checksum, Script::ASSERT);
+	
+	const char* suffix_string;
+	pParams->GetString(CRCD(0x94542eea, "SuffixString"), &suffix_string, Script::ASSERT);
+	
+	pScript->GetParams()->AddChecksum(CRCD(0xafb911c6, "appended_id"), Crc::ExtendCRCWithString(base_checksum, suffix_string));
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RotateVector | rotates a vector by the specified amount
+// @parm float | x | X value of the vector to be rotated (also, the return value)
+// @parm float | y | Y value of the vector to be rotated (also, the return value)
+// @parm float | z | Z value of the vector to be rotated (also, the return value)
+// @parm float | rx | Amount to rotate on X, in degrees
+// @parm float | ry | Amount to rotate on Y, in degrees
+// @parm float | rz | Amount to rotate on Z, in degrees
+bool ScriptRotateVector( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	// get original vector:
+	Mth::Vector vec;
+	pParams->GetFloat( "x", &vec[X], Script::ASSERT );
+	pParams->GetFloat( "y", &vec[Y], Script::ASSERT );
+	pParams->GetFloat( "z", &vec[Z], Script::ASSERT );
+	vec[W] = 1.0f;
+
+	// get amount to rotate (in degrees)
+	float rx = 0.0f;
+	float ry = 0.0f;
+	float rz = 0.0f;
+	pParams->GetFloat( "rx", &rx, Script::NO_ASSERT );
+	pParams->GetFloat( "ry", &ry, Script::NO_ASSERT );
+	pParams->GetFloat( "rz", &rz, Script::NO_ASSERT );
+
+	// rotate the vector
+	Mth::Matrix mat;
+	mat.Ident();
+	mat.RotateXLocal( Mth::DegToRad(rx) );
+	mat.RotateYLocal( Mth::DegToRad(ry) );
+	mat.RotateZLocal( Mth::DegToRad(rz) );
+	vec = mat.Transform( vec );
+
+	// return the vector
+	pScript->GetParams()->AddFloat( "x", vec[X] );
+	pScript->GetParams()->AddFloat( "y", vec[Y] );
+	pScript->GetParams()->AddFloat( "z", vec[Z] );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsPS2 | Returns true if the current hardware is PS2 (proview/devkit/regular).
+bool ScriptIsPS2( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	if (( Config::GetHardware() == Config::HARDWARE_PS2 ) ||
+		( Config::GetHardware() == Config::HARDWARE_PS2_PROVIEW ) ||
+		( Config::GetHardware() == Config::HARDWARE_PS2_DEVSYSTEM ) )
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsNGC | Returns true if the current hardware is GameCube.
+bool ScriptIsNGC( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	if ( ( Config::GetHardware() == Config::HARDWARE_NGC ) )
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsXBOX | Returns true if the current hardware is Xbox.
+bool ScriptIsXBOX( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	if ( ( Config::GetHardware() == Config::HARDWARE_XBOX ) )
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsWin32 | Returns true if the current hardware is Win32.
+bool ScriptIsWIN32( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	if ( ( Config::GetHardware() == Config::HARDWARE_WIN32 ) )
+	{
+		return true;
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetPlatform | Puts the name of the current platform as a checksum into a parameter
+// called Platform. The value will be either ps2, xbox, ngc or win32
+// Handy for use in script switch statements, rather than having to have nested if's
+bool ScriptGetPlatform( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 platform=0;
+	
+	switch (Config::GetHardware())
+	{
+	case Config::HARDWARE_PS2:
+	case Config::HARDWARE_PS2_PROVIEW:
+	case Config::HARDWARE_PS2_DEVSYSTEM:
+		platform=0x988a3508; // ps2
+		break;
+	case Config::HARDWARE_NGC:
+		platform=0xbcf00d45; // ngc
+		break;
+	case Config::HARDWARE_XBOX:
+		platform=0x87d839b8; // xbox
+		break;
+	case Config::HARDWARE_WIN32:
+		platform=0x4af45e2e; // win32
+		break;
+	default:
+		break;
+	}
+	
+	pScript->GetParams()->AddChecksum("Platform",platform);		
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptIsPal( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	return Config::PAL();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PushMemProfile | push an arbitarily named memory profile (Mick's use only)
+bool ScriptPushMemProfile( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	
+	const char* pContext;
+	pParams->GetText( NONAME, &pContext, true );
+	Mem::PushMemProfile((char*)pContext);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PopMemProfile | pop a mem profile that was pushed (Mick's use only)
+bool ScriptPopMemProfile( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mem::PopMemProfile();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TogglePass | Toggle which passes are displayed
+bool ScriptTogglePass( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	#ifdef	__PLAT_NGPS__
+	#ifdef	__NOPT_ASSERT__
+	static 	int pass = 0;
+	pass++;
+
+	// Let us put a pass value in that will override the toggling value  
+	pParams->GetInteger(CRCD(0x318f2bdb,"Pass"),&pass);
+	
+	if (pass == 5)
+	{
+		pass = 0;
+	}
+	if (pass)
+	{
+		NxPs2::gPassMask1 = 0xc0; 			// 1<<6 | 1<<7  (0x40, 0x80)
+		NxPs2::gPassMask0 = (pass-1)<<6;		
+	}
+	else
+	{
+		NxPs2::gPassMask1 = 0;
+		NxPs2::gPassMask0 = 0;
+	}
+	#endif
+	#endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetScreen | Sets default screen aspect ratio and camera
+// @parm float | Aspect | aspect ratio, usually 16/9 (1.777777777) or 4/3 (1.3333333333)
+// @parm float | Angle | screen angle in degrees usally 72, or 80 for 16:9 
+// @parm int | Letterbox | 0 for non-letterbox, 1 for letterboxed.  Normally used with 4/3 aspect 
+bool	ScriptSetScreen( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	float	Aspect;
+	float	Angle;
+	
+	if (pParams->GetFloat("Aspect",&Aspect))
+	{
+		Nx::CViewportManager::sSetScreenAspect(Aspect);
+	}
+	
+	if (pParams->GetFloat("Angle",&Angle))
+	{
+		Nx::CViewportManager::sSetScreenAngle(Angle);
+	}
+	
+	int letterbox;
+	if (pParams->GetInteger("Letterbox",&letterbox))
+	{
+		Nx::CEngine::sSetLetterbox(letterbox);
+	}
+	
+	return true;	   
+	   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetUpperCaseString( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	const char* initial_string;
+	pParams->GetString( NONAME, &initial_string, Script::ASSERT );
+
+	char new_string[128];
+	strcpy( new_string, initial_string );
+
+	Str::UpperCase( new_string );
+	pScript->GetParams()->AddString( "UpperCaseString", new_string );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetGameMode( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	 Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+	uint32 gameMode = skate_mod->GetGameMode()->GetNameChecksum();
+
+	pScript->GetParams()->AddChecksum( "GameMode", gameMode );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptStartKeyboardHandler( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+	int max_length;
+
+	pParams->GetInteger( "max_length", &max_length, true );
+	front->AddKeyboardHandler( max_length );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptEnableKeyboard( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __PLAT_NGPS__
+	SIO::EnableKeyboard( true );
+#endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptDisableKeyboard( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __PLAT_NGPS__
+	SIO::EnableKeyboard( false );
+#endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptStopKeyboardHandler( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::FrontEnd* front = Mdl::FrontEnd::Instance();
+
+	front->RemoveKeyboardHandler();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CreateIndexArray | creates an array of ints, where each element
+// is equal to its own index
+// @uparm 1 | size of array
+bool ScriptCreateIndexArray( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int size;
+	pParams->GetInteger( NONAME, &size, Script::ASSERT );
+
+	if ( size < 1 )
+		Dbg_MsgAssert( 0, ( "CreateIndexArray called with size less than 1" ) );
+
+	Script::CArray *p_index_array = new Script::CArray();
+	p_index_array->SetSizeAndType( size, ESYMBOLTYPE_INTEGER );
+
+	for ( int i = 0; i < size; i++ )
+		p_index_array->SetInteger( i, i );
+
+	pScript->GetParams()->AddArray( "index_array", p_index_array );
+	Script::CleanUpArray( p_index_array );
+	delete p_index_array;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetRescaledTargetValue | 
+// @parm float | min |  
+// @parm float | max | 
+// @parm float | target | 
+// @parm float | result | 
+bool ScriptGetRescaledTargetValue( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	float min;
+	float max;
+	float target;
+	float result;
+
+	pParams->GetFloat( "min", &min, true );
+	pParams->GetFloat( "max", &max, true );
+	pParams->GetFloat( "target", &target, true );
+
+	result = ( target - min ) / ( max - min );
+
+	if ( pParams->ContainsFlag( "sin_curve" ) )
+	{
+		result = asinf( result ) * 4096.0f / ( 2.0f * Mth::PI );
+	
+		if ( pParams->ContainsFlag( "backwards" ) )
+		{
+			result = 2048.0f - result;
+		}
+	}
+
+//	Dbg_Message( "min = %f max = %f target = %f result = %f", min, max, target, result );
+
+	// this finds the scaled value between 0.0 and 1.0
+	pScript->GetParams()->AddFloat( "result", result );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MenuIsSelected | Checks names in pArray to find selected
+bool ScriptMenuIsSelected(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// If there is an array of names, check them.		
+	Script::CArray *pArray=NULL;
+	if (pParams->GetArray(NONAME,&pArray))
+	{
+		Dbg_MsgAssert(pArray,("Eh? NULL pArray?"));
+		for (uint32 i=0; iGetSize(); ++i)
+		{
+			if (MenuIsSelected(pArray->GetNameChecksum(i)))
+			{
+				return true;
+			}
+		}
+	}
+	
+	uint32 id=0;
+	if (pParams->GetChecksum(NONAME,&id))
+	{
+		if (MenuIsSelected(id))
+		{
+			return true;
+		}	
+	}
+			
+	return false;			
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetNodeName | Gets the node name of the object running this script, and puts
+// it into a parameter called NodeName.
+// If there is no such object, it will not add NodeName and will return false.
+bool ScriptGetNodeName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if (pScript->mpObject)
+	{
+		Obj::CMovingObject *p_pos_obj = static_cast(pScript->mpObject.Convert());
+		if (p_pos_obj)
+		{
+			if (p_pos_obj->GetID())
+			{
+				pScript->GetParams()->AddChecksum("NodeName",p_pos_obj->GetID());
+				return true;
+			}
+		}
+	}			
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetTesterScript | Sets and starts running the tester script. This is a script which
+// will never die and will always be having its update function called every frame.
+// Used for auto testing levels.
+// SetTesterScript may be called more than once. Any new call will stop the current tester script
+// and start the new one.
+// If the tester script hits an endscript it still won't die, it will just stick on the endscript
+// and not do anything. (This uses negligible processing time)
+// @uparm name | The name of the script to run.
+bool ScriptSetTesterScript(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 tester_script=0;
+	CStruct *p_params=NULL;
+	
+	pParams->GetChecksum(NONAME,&tester_script);
+	pParams->GetStructure(CRCD(0x7031f10c,"params"),&p_params);
+	
+	 Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+
+	Dbg_MsgAssert(pSkate->mp_gameFlow,("NULL pSkate->mp_gameFlow ??"));
+	pSkate->mp_gameFlow->SetTesterScript(tester_script, p_params);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | KillTesterScript | Kills any tester script that is running. 
+// Returns true if a tester script was running before, false otherwise.
+bool ScriptKillTesterScript(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	 Mdl::Skate * pSkate =  Mdl::Skate::Instance();
+
+	Dbg_MsgAssert(pSkate->mp_gameFlow,("NULL pSkate->mp_gameFlow ??"));
+	return pSkate->mp_gameFlow->KillTesterScript();
+}									 
+
+// Memory manager related functions
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MemPushContext | 
+// @uparm 1 | value
+bool ScriptMemPushContext( Script::CStruct *pParams, Script::CScript *pScript )
+{
+//	MemViewToggle();
+	
+	// 0 is the special case name
+	int Val=0;			   			// default to 0
+	if ( pParams->GetInteger(NONAME,&Val,false) )
+	{
+		if ( Val == 0 )
+		{
+			// is the bottom up heap
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+		}
+		else
+		{
+			Dbg_MsgAssert( 0, ( "Must specify heap name (instead of number) for MemPushContext (ex: BottomUpHeap)" ) );
+		}
+	}
+	else
+	{
+		// otherwise, look it up by name
+		uint32 whichHeap;
+		pParams->GetChecksum( NONAME, &whichHeap, true );
+		Mem::Manager& mem_man = Mem::Manager::sHandle();
+		Mem::Heap* pHeap = mem_man.GetHeap( whichHeap );
+		Dbg_Assert( pHeap );
+
+		Mem::Manager::sHandle().PushContext( pHeap );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MemPopContext |
+bool ScriptMemPopContext( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mem::Manager::sHandle().PopContext();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MemInitHeap |
+// @parm name | name | the name of heap to create
+// @parm int | size | the size of heap to create
+bool ScriptMemInitHeap(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	const char* pHeapName;
+	pParams->GetText( "name", &pHeapName, true );
+
+	int heapSize;
+	pParams->GetInteger( "size", &heapSize, true );
+
+	uint32 heapName;
+	heapName = Script::GenerateCRC( pHeapName );
+
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	Dbg_MsgAssert( !mem_man.NamedHeap(heapName, false), ( "named heap %s already exists", pHeapName ) )
+	mem_man.InitNamedHeap( heapName, heapSize, pHeapName );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MemDeleteHeap |
+// @parm name | name | the name of heap to delete
+bool ScriptMemDeleteHeap(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char* pHeapName;
+	pParams->GetText( "name", &pHeapName, true );
+
+	Mem::Manager& mem_man = Mem::Manager::sHandle();	
+	return mem_man.DeleteNamedHeap( Script::GenerateCRC( pHeapName ), false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptMemThreadSafe( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	if (pParams->ContainsFlag( Script::GenerateCRC( "off" )))
+	{
+		Mem::SetThreadSafe( false );
+	}
+	else
+	{
+		Mem::SetThreadSafe( true );
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AnalyzeHeap | analyzes specified heap
+// @uparmopt BottomUpHeap | heap to analyze
+bool ScriptAnalyzeHeap( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// GJ:  I am planning to refactor this!
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	Mem::Heap* pHeap = NULL;
+	uint32 whichHeap = Script::GenerateCRC("BottomUpHeap");
+	if ( pParams )
+	{
+		pParams->GetChecksum( NONAME, &whichHeap );
+	}
+
+	pHeap = mem_man.GetHeap( whichHeap );
+
+#ifndef __PLAT_NGC__
+	if ( pHeap )
+	{
+		MemView_AnalyzeHeap( pHeap );
+	}
+#endif		// __PLAT_NGC__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PrintMemInfo | displays mem info about specified heap
+// @uparmopt BottomUpHeap | which heap to print
+bool ScriptPrintMemInfo( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	Mem::Heap* pHeap = NULL;
+	char buf[64];
+	uint32 whichHeap = Script::GenerateCRC("BottomUpHeap");
+	if ( pParams )
+	{
+		pParams->GetChecksum( NONAME, &whichHeap );
+	}
+	
+	pHeap = mem_man.GetHeap( whichHeap );
+	strcpy( buf, mem_man.GetHeapName( whichHeap ) );
+
+	if ( pHeap )
+	{
+		printf(  "\n" );
+
+		printf( "%s:\n", buf );
+		printf( "Used %dK (%d) Peak %dK (%d)\n",
+				pHeap->mUsedMem.m_count / 1024,
+				pHeap->mUsedBlocks.m_count,
+				pHeap->mUsedMem.m_peak / 1024,
+				pHeap->mUsedBlocks.m_peak );
+		printf( "Frag %dK (%d) Peak %dK (%d)\n",
+				pHeap->mFreeMem.m_count / 1024,
+				pHeap->mFreeBlocks.m_count,
+				pHeap->mFreeMem.m_peak / 1024,
+				pHeap->mFreeBlocks.m_peak );
+
+		printf(  "\n" );
+	}
+	
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DisplayFreeMem | displays free mem info of specified heap
+bool ScriptDisplayFreeMem( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+
+	Mem::Heap* heap;
+   
+	Script::CStruct* params;
+
+	params = new Script::CStruct;
+	for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+	{		
+			Mem::Region* region = heap->ParentRegion();			
+			
+			params->Clear();
+			
+			params->AddChecksum( "id", Script::GenerateCRC( heap->GetName() ) );
+			params->AddInteger( "free_mem", region->MemAvailable() );
+			params->AddInteger( "min_free_mem", region->MinMemAvailable() );
+
+			Script::RunScript( "UpdateDisplayFreeMemory", params );
+			
+	}
+
+	delete params;
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DumpHeaps | prints out heap contents
+// @flag ExactValues | Prints the exact heap sizes in bytes.
+bool ScriptDumpHeaps( Script::CStruct *pParams, Script::CScript *pScript )
+{
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+
+	Script::DumpLastStructs();
+
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+
+#	ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+	char buf[512];
+#endif		// __PLAT_NGC__
+#	endif
+
+	Mem::Heap* heap;
+
+#	ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+	dump_open("blah.txt");
+#endif		// __PLAT_NGC__
+#	endif
+
+	if (pParams->ContainsFlag(CRCD(0xd65b9ac7,"ExactValues")))
+	{
+		printf("Name              Used    Frag    Free    Min Blocks\n");
+		printf("--------------- ------ ------- ------- ------ ------\n");
+		for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+		{		
+				Mem::Region* region = heap->ParentRegion();			
+				printf( "%12s: %8d %7d %7d %7d  %5d \n",
+						heap->GetName(),
+						heap->mUsedMem.m_count ,
+						heap->mFreeMem.m_count ,
+						region->MemAvailable() ,
+						region->MinMemAvailable() ,
+						heap->mUsedBlocks.m_count
+						);										
+		}
+	}	
+	else
+	{
+		printf("Name            Used  Frag  Free   Min  Blocks\n");
+		printf("--------------- ----- ----- ---- ------ ------\n");
+		for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+		{		
+				Mem::Region* region = heap->ParentRegion();			
+				printf( "%12s: %5dK %4dK %4dK %4dK  %5d \n",
+						heap->GetName(),
+						heap->mUsedMem.m_count / 1024,
+						heap->mFreeMem.m_count / 1024,
+						region->MemAvailable() / 1024,
+						region->MinMemAvailable() / 1024,
+						heap->mUsedBlocks.m_count
+						);										
+		}
+	}
+
+#	ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+	dump_close();
+#endif		// __PLAT_NGC__
+#	endif
+	
+	Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	printf( "\nSound mem free %d K  (%d bytes)\n", 
+		 sfx_manager->MemAvailable() / 1024, sfx_manager->MemAvailable() );
+
+#	ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+	char fileName[256];
+	int i = 0;
+	while ( TRUE )
+	{
+		// The filename is formed from the level name a number, and the free memory
+		// you should sort them by DATE, and not by filename
+		// 
+		sprintf( fileName, "dumps\\%s_dump_%02d_%s.txt",
+						   s_level_name,
+//						   __nDATE__,
+					//	   __nTIME__,
+						   i,
+						   Str::PrintThousands((mem_man.BottomUpHeap()->ParentRegion()->MemAvailable())) );
+
+		printf ("Filename = <%s>, %d chars\n",fileName,strlen(fileName));
+						   
+		if ( FALSE == File::Exist( fileName ))
+			break;
+
+		i++;
+	}
+#endif		// __PLAT_NGC__
+#	endif // __PLAT_XBOX__
+
+#	ifndef __PLAT_XBOX__
+#ifndef __PLAT_NGC__
+	if( dump_open(fileName))
+	{
+		sprintf(buf,"Name            Used  Frag  Free   Blocks\n");
+		dump_printf(buf);
+		sprintf(buf,"--------------- ----- ----- ------ ------\n");
+		dump_printf(buf);
+		for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+		{		
+				Mem::Region* region = heap->ParentRegion();			
+				sprintf(buf, "%12s: %5dK %4dK %4dK   %5d \n",
+						heap->GetName(),
+						heap->mUsedMem.m_count / 1024,
+						heap->mFreeMem.m_count / 1024,
+						region->MemAvailable() / 1024,
+						heap->mUsedBlocks.m_count
+						);										
+				dump_printf(buf);
+		}
+		sprintf( buf, "\nSound mem free %d K  (%d bytes)\n\n\n", 
+			 sfx_manager->MemAvailable() / 1024, sfx_manager->MemAvailable() );
+		dump_printf(buf);
+	
+		sprintf(buf,"Summary\n----------\n\n");
+		dump_printf(buf);
+		Mem::DumpMemProfile(3);
+		sprintf(buf,"\n\nDetails\n----------\n\n");
+		dump_printf(buf);
+		Mem::DumpMemProfile(100);
+
+		dump_close();
+	}
+#endif		// __PLAT_NGC__
+#	endif //__PLAT_XBOX__
+
+	printf("\nScript pool usage:\n");
+	printf("CStruct:           %5d of %5d, max %5d\n",CStruct::SGetNumUsedItems(),CStruct::SGetTotalItems(),CStruct::SGetMaxUsedItems());
+	printf("CComponent:        %5d of %5d, max %5d\n",CComponent::SGetNumUsedItems(),CComponent::SGetTotalItems(),CComponent::SGetMaxUsedItems());
+	printf("CSymbolTableEntry: %5d of %5d, max %5d\n",CSymbolTableEntry::SGetNumUsedItems(),CSymbolTableEntry::SGetTotalItems(),CSymbolTableEntry::SGetMaxUsedItems());
+	printf("CVector:           %5d of %5d, max %5d\n",CVector::SGetNumUsedItems(),CVector::SGetTotalItems(),CVector::SGetMaxUsedItems());
+	printf("CPair:             %5d of %5d, max %5d\n",CPair::SGetNumUsedItems(),CPair::SGetTotalItems(),CPair::SGetMaxUsedItems());
+	printf("CArray:            %5d of %5d, max %5d\n",CArray::SGetNumUsedItems(),CArray::SGetTotalItems(),CArray::SGetMaxUsedItems());
+	printf("CScript:           %5d of %5d, max %5d\n",CScript::SGetNumUsedItems(),CScript::SGetTotalItems(),CScript::SGetMaxUsedItems());
+	Mem::CCompactPool *p_hash = Mem::PoolManager::SGetPool(Mem::PoolManager::vHASH_ITEM_POOL);
+	printf("CHashItem:         %5d of %5d, max %5d\n",p_hash->GetNumUsedItems(),p_hash->GetTotalItems(),p_hash->GetMaxUsedItems());
+	printf("\n(HashItems are init in main.cpp, others are in init.cpp)\n");
+#endif		// __NOPT_FINAL__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DumpFragments | prints out heap fragments
+bool ScriptDumpFragments( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int maxFragmentation = 10000;
+	pParams->GetInteger( NONAME, &maxFragmentation, Script::NO_ASSERT );
+
+	if ( pParams->ContainsFlag( "K" ) )
+	{
+		maxFragmentation *= 1024;
+	}
+
+#ifdef __NOPT_ASSERT__
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	
+	// sanity checks for fragmentation
+	Mem::Heap* heap = mem_man.BottomUpHeap();
+	int fragmentation = heap->mFreeMem.m_count;
+	
+	if (fragmentation > maxFragmentation)
+	{
+		// not guaranteed to do anything useful.... might crash the debugger (Mick)
+		printf ("Dumping Fragments.....\n");
+		MemView_DumpFragments(heap);
+		printf ("Done Dumping Fragments.....\n");
+	}
+	
+	Dbg_MsgAssert(fragmentation < maxFragmentation, ("Excessive bottom up fragmentation (%d) after cleanup (max=%d)",fragmentation,maxFragmentation)); 
+	
+	heap = mem_man.TopDownHeap();
+	fragmentation = heap->mFreeMem.m_count;
+	if (fragmentation > maxFragmentation)
+	{
+		// not guaranteed to do anything useful.... might crash the debugger (Mick)
+		printf ("Dumping Fragments.....\n");
+		MemView_DumpFragments(heap);
+		printf ("Done Dumping Fragments.....\n");
+	}
+	
+	Dbg_MsgAssert(fragmentation < maxFragmentation, ("Excessive top down fragmentation (%d) after cleanup (max=%d)",fragmentation,maxFragmentation)); 
+#endif
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptClearStruct( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 structName;
+	pParams->GetChecksum( "struct", &structName, Script::ASSERT );
+
+	Script::CStruct* pStruct = Script::GetStructure( structName, Script::ASSERT );
+	pStruct->Clear();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptAppendStruct( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 structName;
+	pParams->GetChecksum( "struct", &structName, Script::ASSERT );
+
+	uint32 fieldName;
+	pParams->GetChecksum( "field", &fieldName, Script::ASSERT );
+	
+	Script::CStruct* pAppendParams;
+	pParams->GetStructure( "params", &pAppendParams, Script::ASSERT );
+
+	Script::CStruct* pSubStruct = new Script::CStruct;
+	pSubStruct->AppendStructure( pAppendParams );
+	
+	Script::CStruct* pStruct = Script::GetStructure( structName, Script::ASSERT );
+	pStruct->AddStructurePointer( fieldName, pSubStruct ); 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptScriptExists( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 script_name_checksum=0;
+	pParams->GetChecksum(NONAME,&script_name_checksum);
+
+	CSymbolTableEntry *p_entry=Resolve(script_name_checksum);
+	if (p_entry && p_entry->mType==ESYMBOLTYPE_QSCRIPT)
+	{
+		return true;
+	}
+	return false;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSpawnSecondControllerCheck( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	 Mlp::Manager * mlp_manager =  Mlp::Manager::Instance();
+	int not_used;
+
+	if( s_second_controller_check_task != NULL )
+	{
+		return true;
+	}
+
+	not_used = 0;
+	s_second_controller_check_task = new Tsk::Task< int > ( s_second_controller_check_code, not_used );
+	mlp_manager->AddLogicTask ( *s_second_controller_check_task );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptStopSecondControllerCheck( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Dbg_Assert( s_second_controller_check_task != NULL );
+
+	delete s_second_controller_check_task;
+	s_second_controller_check_task = NULL;
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LoadExecPS2 | 
+// @parm string | elf | elf file to load
+// if this fails on a TOOL turn off SN profiling in main.cpp
+// and check the media type in prepare_to_exit
+bool ScriptLoadExecPS2( Script::CStruct *pParams, Script::CScript *pScript )
+{
+
+#ifdef __PLAT_NGPS__
+	const char* p_string;
+	pParams->GetText( "elf", &p_string, true );
+    Sys::LoadExec( p_string );
+#endif // __PLAT_NGPS__
+	
+    return true;		// actually will never get here}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+- TODO - factor out the common functionality of ExitDemo into LoadExecPS2
+
+the LoadExecPS2() references lists some common gotchas:
+
+The total size of the character strings for the filename and all of the
+arguments must not exceed 256 bytes. 
+
+Note the following points before calling LoadExecPS2(). 
+* Terminate or delete all threads other than the thread that will 
+  execute LoadExecPS2(). 
+* Cancel all callbacks and interrupt handlers. 
+* Execute scePadEnd(). 
+* Execute sceSifExitCmd().
+*/
+
+bool ScriptExitDemo( Script::CStruct*, Script::CScript* )
+{
+#ifdef __PLAT_NGPS__
+    Sys::ExitDemo();
+#endif
+
+	return true;		// actually will never get here
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptIsArray( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Script::CArray* pTest = NULL;
+	if ( pParams->GetArray( NONAME, &pTest, Script::NO_ASSERT ) )
+		return true;
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UseUserSoundtrack | xbox only - uses a certain soundtrack
+// @uparm 1 | the soundtrack number
+bool ScriptUseUserSoundtrack( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Dbg_MsgAssert( Config::GetHardware() == Config::HARDWARE_XBOX, ( "UseUserSoundtrack should only be called on the XBox" ) );
+	
+	int soundtrack;
+	pParams->GetInteger( NONAME, &soundtrack, Script::ASSERT );
+	Pcm::UseUserSoundtrack( soundtrack );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UseStandardSoundtrack | xbox only - use the standard soundtrack
+bool ScriptUseStandardSoundtrack( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Dbg_MsgAssert( Config::GetHardware() == Config::HARDWARE_XBOX, ( "UseStandardSoundtrack should only be called on XBox" ) );
+	Pcm::UseStandardSoundtrack();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | DisableReset | ngc only - disable the reset button. If the user presses reset when disabled, a screen will be displayed until it is enabled, and it will reset.
+bool ScriptDisableReset( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __PLAT_NGC__
+	//printf("DisableReset ...\n");
+	NxNgc::EngineGlobals.disableReset = true;
+#endif		// __PLAT_NGC__ 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EnableReset | ngc only - enable the reset button. If reset was pressed while disabled, it will now reset.
+bool ScriptEnableReset( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __PLAT_NGC__
+	//printf("WARNING! EnableReset ...\n");
+
+	NxNgc::EngineGlobals.disableReset = false;
+#endif		// __PLAT_NGC__ 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetToIPL | ngc only - resets to IPL screen.
+bool ScriptResetToIPL( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __PLAT_NGC__
+	NxNgc::EngineGlobals.resetToIPL = true;
+#endif		// __PLAT_NGC__ 
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptInitAnimCompressTable( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	const char* pFileName;
+	pParams->GetText( NONAME, &pFileName, Script::ASSERT );
+
+	uint32 compressTableName;
+	pParams->GetChecksum( NONAME, &compressTableName, Script::ASSERT );
+
+	char filename[256];
+	sprintf( filename, "%s", pFileName );//, Nx::CEngine::sGetPlatformExtension() );
+
+	switch ( compressTableName )
+	{
+		case 0xc6a56fe3:	// q48
+		{
+			Gfx::InitQ48Table( filename );
+		}
+		break;
+		
+		case 0xc06ead08:	// t48
+		{
+			Gfx::InitT48Table( filename );
+		}
+		break;
+
+		default:
+			Dbg_MsgAssert( 0, ( "Unrecognized compress table name %s", Script::FindChecksumName(compressTableName) ) );
+			break;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Test_Composite is a test array of structures
+// that are the components of the composite object, and the parameters passed to it
+/*
+test_composite = [
+    {
+        component = model
+        model = "gameobjects\skate\letter_a\letter_a.mdl"
+    }
+    
+    {
+        component = exceptions
+    }
+    
+    {
+        component = bouncyphysics
+        bounce = 2
+    }
+   {
+    components = [model, exceptions, explodingphysics]
+    model = "gameobjects\skate\letter_a\letter_a.mdl"
+    bounce = 2
+   
+} 
+]
+*/
+						
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCreateCompositeObject( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	// Required array of structures containing "component="
+	Script::CArray*	p_array = NULL;
+	pParams->GetArray(CRCD(0x11b70a02,"components"),&p_array,Script::ASSERT);
+
+	// Optional extra structure of parameters that override any provided in the array	
+	Script::CStruct* p_struct = NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"params"),&p_struct);
+	
+	Obj::CCompositeObjectManager::Instance()->CreateCompositeObjectFromNode(p_array, p_struct);
+	
+	return true;
+}	  
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the id of an object, and a structure, 
+bool ScriptRefreshObject( Script::CStruct* pParams, Script::CScript* pScript )
+{
+
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AutoRail |
+// auto-generates rails based of the level's collidable geometry
+// @parmopt float | min_rail_edge_angle | 30.0 |
+// rails are created only on edges as least as sharp as this angle
+// @parmopt float | max_rail_angle_of_assent | 45.0 |
+// rails are created only on edges less steep than this angle
+// @parmopt float | min_railset_length | 36.0 |
+// autorail attempts to join contiguous edges together in order to determine the effective length of a curved rail;
+// once this is done, rails shorter than this length in inches are dropped
+// @parmopt float | min_edge_length | 0.0 |
+// edges below this length in inches are not considered for rail creation, even if they could be part of a curved rail
+// @parmopt float | max_corner_in_railset | 50.0 |
+// maximum angle between two edges for them to be connected as a curved rail
+// @parmopt float | connection_slop | 0.1 |
+// slop distance allowed between rail endpoints below which those rails will be snapped togeather
+// @parmopt float | farthest_degenerate_rail | 12.0 |
+// auto-generated rails	within this distance in inches of an old rail will not be created
+// @parmopt float | max_degenerate_rail_angle | 20.0 |
+// if the angle between an auto-generated rail and a nearly old rail is below this angle, the auto-generated rail will not be created
+// @parmopt float | max_low_curb_height | 8.0 |
+// curbs below this height in inches are not given rails; controls the low-curb collision feeler length
+// @parmopt float | vertical_feeler_length | 60.0 |
+// length in inches of the vertical collision feeler
+// @parmopt float | crossbar_feeler_length | 12.0 |
+// length in inches of the crossbar collision feeler
+// @parmopt float | crossbar_feeler_elevation | 12.0 |
+// height in inches of the crossbar collision feeler
+// @parmopt float | curb_feeler_angle_of_assent | 30.0	|
+// low-curb collision feeler radiates from the rail at this angle above the bordering faces
+// @parmopt float | feeler_increment | 36.0 |
+// collision feelers are used at each step of this distance in inches along the rail
+// @flag no_feelers |
+// turns off the checking of the volume around rails by collision feelers
+// otherwise this checking is done in order to insure that a rail is not cramped or on a low curb
+// @flag no_vertical_feeler |
+// turns off the collision feeler which radiates upward from the rail
+// @flag no_crossbar_feeler |
+// turns off the collision feeler which lies just above the rail and runs perpendicular to it
+// @flag no_low_curb_feeler |
+// turns off the collision feelers which radiate from the rail just above the bordering faces;
+// these collision feelers are used to insure rails are not created on low curbs
+// @flag overwrite |
+// deletes old rails before auto-generating rails
+bool ScriptAutoRail( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::Skate::Instance()->GetRailManager()->AutoGenerateRails(pParams);
+	return true;			  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptInside(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	Dbg_MsgAssert(Mdl::Skate::Instance()->mpProximManager->IsInsideFlagValid(), ("Inside called outside of a proxim trigger script or within a proxim trigger script with a ProximObject"));
+    return Mdl::Skate::Instance()->mpProximManager->IsInside(); 
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSetSpecialBarColors(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    int skater_num;
+    pParams->GetInteger( "skater", &skater_num, Script::ASSERT );
+    
+    Mdl::Skate * pSkate = Mdl::Skate::Instance();
+    Obj::CSkater* pSkater = pSkate->GetSkater( skater_num );
+    Mdl::Score *pScore = pSkater->GetScoreObject();
+
+    pScore->setSpecialBarColors();
+    return true;
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ScriptGetMetrics(Script::CStruct* pParams, Script::CScript* pScript)
+{
+// get various metrics into a structure for display
+// You can run this on a spawned script to see in the viewer
+
+	Script::CStruct *p_metrics = new Script::CStruct();
+	Nx::CEngine::sGetMetrics(p_metrics);
+	
+	Script::CStruct *p_script_params = pScript->GetParams(); 				   
+	p_script_params->AddStructure("Metrics",p_metrics);
+	
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	ScriptMoveNode(Script::CStruct* pParams, Script::CScript* pScript)
+{
+   	// Given a name and a position, move the node to that position
+	// This can only be applied to one node at once, as it's an absolute position
+	// So we just do everything here.
+
+	uint32	name;															
+	if ( ! pParams->GetChecksum(CRCD(0xa1dc81f9,"name"),&name) )
+	{
+		printf ("Missing Name\n");
+		return false;
+	}
+
+	int node_num = SkateScript::FindNamedNode( name, false );
+	if( node_num == -1 )
+	{
+		Dbg_Printf( "MoveNode failed. Could not find node %p in the scene\n", name );
+		return false;
+	}
+	Script::CStruct *pNode = SkateScript::GetNode( node_num );
+	
+	Mth::Vector new_pos, old_pos;
+	
+  	if ( !pParams->GetVector(CRCD(0xb9d31b0a,"position"),&new_pos)
+		 && !pParams->GetVector(CRCD(0x7f261953,"pos"),&new_pos) )
+	{
+		printf("Missing position parameter in MoveNode\n");
+		return false;
+	}
+
+	if ( !pNode->GetVector(CRCD(0xb9d31b0a,"position"),&old_pos)
+		 && !pNode->GetVector(CRCD(0x7f261953,"pos"),&old_pos) )
+	{
+		printf("node is missing position\n");
+		return false;
+	}
+		
+	// Move the original node 
+	// GJ:  i'm pretty sure that the following is incorrect,
+	// and that the Z component needs to be negated, but since we don't
+	// actually reference the "position" component after the object has
+	// been created, the bug has slipped through the cracks...  we're
+	// switching over to the new-style "pos" vector anyway
+//	pNode->AddVector(CRCD(0xb9d31b0a,"position"),new_pos[X],new_pos[Y],new_pos[Z]);
+	pNode->AddVector(CRCD(0x7f261953,"pos"),new_pos[X],new_pos[Y],new_pos[Z]);
+
+	// Now look for the node type, and handle the mode for each type of thing
+
+	Obj::CCompositeObject * p_object = (Obj::CCompositeObject *) Obj::CCompositeObjectManager::Instance()->GetObjectByID(name);
+
+	if (p_object)
+	{
+		// Might also have to set the pos of the components
+		// as they can store internal absolute positions
+		// and the pos of the object is only used for display
+		p_object->SetPos(new_pos);
+	}
+
+	uint32 ClassChecksum = 0;
+	pNode->GetChecksum( 0x12b4e660 /*"Class"*/, &ClassChecksum );
+	switch (ClassChecksum)
+	{
+		case 0xe47f1b79: //vehicle
+			break;
+	
+		case 0xa0dfac98:  //pedestrian
+		case 0x61a741e:  // ped
+//			Obj::CreatePed( skate_mod->GetObjectManager(), nodeIndex );
+			break;
+
+		case 0xef59c100:  //gameobject
+			break;
+			
+		case 0x19b1e241: // ParticleEmitter		
+			{
+				Nx::CParticle * p_particle = Nx::get_particle( name );
+				if (p_particle)
+				{
+					p_particle->Refresh();
+				}
+			}
+			break;
+
+		case 0xb7b3bd86:  // LevelObject
+			{
+			}
+			break;
+
+		case 0xbf4d3536:  // LevelGeometry
+		{
+			/*Dbg_MsgLog(("LevelGeometry"));
+			uint32 checksumName = 0;
+			if ( pNode->GetChecksum( 0xa1dc81f9, &checksumName ) ) // checksum 'name'
+			{
+//				Nx::CSector *p_sector = Nx::CEngine::sGetSector(checksumName);
+				// We only want to get sectors from the main scene, as that is what the node array applies to															
+				Nx::CScene * p_main_scene = Nx::CEngine::sGetMainScene();
+				Nx::CSector *p_sector = p_main_scene->GetSector(checksumName);
+				if (p_sector)
+				{
+					Dbg_MsgAssert(p_sector,("sGetSector(0x%x) returned NULL (%s)",checksumName,Script::FindChecksumName(checksumName)));
+					p_sector->SetWorldPosition(new_pos);
+				}
+				else
+				{
+					printf(" WARNING: sGetSector(0x%x) returned NULL (%s)\n",checksumName,Script::FindChecksumName(checksumName));
+				}
+				//Bsp::ClearWorldSectorFlag( checksumName, mSD_KILLED | mSD_NON_COLLIDABLE | mSD_INVISIBLE  | mSD_INVISIBLE2);
+			}*/
+			break;
+ 		}
+
+		case CRCC(0x30c19600, "ClimbingNode"):
+		case 0x8e6b02ad:  // railnode
+			Mdl::Skate::Instance()->GetRailManager()->MoveNode( node_num, new_pos );
+			break;			
+		case 0x8470f2e:  // proximnode
+//			Obj::Proxim_SetActive( nodeIndex, true );
+			break;			
+		default:
+			return ( false );
+			break;
+	}
+	
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+					 
+// @script | SetActiveCamera |
+// Sets the active camera to be the camera component of this object
+// @parmopt checksum | id | 0 | 
+// @parmopt integer | viewport | 0 |
+// @flag move |	  move the new camera to the position of the old camera
+bool	ScriptSetActiveCamera(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32	id=0;
+	pParams->GetChecksum(CRCD(0x40c698af,"id"),&id);
+	int	viewport=0;
+	pParams->GetInteger(CRCD(0x9fd29151,"viewport"),&viewport);
+	bool move = pParams->ContainsFlag(CRCD(0x10c1c887,"move"));
+
+	return SetActiveCamera(id,viewport, move); 	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSin(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    float x,sin;
+    pParams->GetFloat( NONAME , &x, Script::ASSERT );
+    
+    sin = sinf(Mth::DegToRad(x));
+
+    Script::CStruct *p_script_params = pScript->GetParams(); 				   
+	p_script_params->AddFloat("sin",sin);
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptCos(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    float x,cos;
+    pParams->GetFloat( NONAME , &x, Script::ASSERT );
+    
+    cos = cosf(Mth::DegToRad(x));
+
+    Script::CStruct *p_script_params = pScript->GetParams(); 				   
+	p_script_params->AddFloat("cos",cos);
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptTan(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    float x,tan;
+    pParams->GetFloat( NONAME , &x, Script::ASSERT );
+    
+    tan = tanf(Mth::DegToRad(x));
+
+    Script::CStruct *p_script_params = pScript->GetParams(); 				   
+	p_script_params->AddFloat("tan",tan);
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptASin(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    float x,asin;
+    pParams->GetFloat( NONAME , &x, Script::ASSERT );
+    
+    asin = asinf(x);
+    asin = Mth::RadToDeg(asin);
+
+    Script::CStruct *p_script_params = pScript->GetParams(); 				   
+	p_script_params->AddFloat("asin",asin);
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptACos(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    float x,acos;
+    pParams->GetFloat( NONAME , &x, Script::ASSERT );
+    
+    acos = acosf(x);
+    acos = Mth::RadToDeg(acos);
+
+    Script::CStruct *p_script_params = pScript->GetParams(); 				   
+	p_script_params->AddFloat("acos",acos);
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptATan(Script::CStruct* pParams, Script::CScript* pScript)
+{
+    float x,atan;
+    pParams->GetFloat( NONAME , &x, Script::ASSERT );
+    
+    atan = atanf(x);
+    atan = Mth::RadToDeg(atan);
+
+    Script::CStruct *p_script_params = pScript->GetParams(); 				   
+	p_script_params->AddFloat("atan",atan);
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Load tracking info from a file, and display it as immediate mode lines
+bool ScriptShowTracking(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	Gfx::DebugGfx_CleanUp();
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+
+	bool do_bonks = pParams->ContainsFlag(CRCD(0xb426bf5a,"bonks"));														   
+	bool do_grinds = pParams->ContainsFlag(CRCD(0x14240297,"grinds"));														   
+	bool do_verts = pParams->ContainsFlag(CRCD(0x969d3af6,"verts"));														   
+	bool do_lines = pParams->ContainsFlag(CRCD(0xb0fe7369,"lines"));					
+
+	// if nothing specificed, then do everything												  
+	if (!do_bonks && !do_grinds && !do_verts && !do_lines)
+	{
+		do_bonks = do_grinds = do_verts	= do_lines = true;
+	}
+														   
+	const char *p_name="";
+	pParams->GetString(NONAME,&p_name);
+	if (p_name[1] == ':')
+	{
+		// skip over any 'c:' the user might be specifying
+		// hopefully they don't try to load things off q:, or something
+		printf ("WARNING: stripped drive name off file\n");
+		p_name+=2;
+	}
+	int size=0;
+	void * p_file;
+	if (File::Exist(p_name))
+	{
+		p_file = File::LoadAlloc(p_name,&size);
+	
+		printf ("Loaded %s, size = %d, at %p\n",p_name,size,p_file);
+	
+		Mem::Manager::sHandle().PopContext();
+	}
+	else
+	{
+		printf ("Can't find file: %s\n",p_name);
+		return false;
+	}
+	
+	int bonks = 0;
+	int grinds = 0;				 
+	int verts = 0;				 
+	int lines = 0;				 
+	char *p = (char*)p_file;
+	while (p < ((char*)p_file+size))
+	{
+		if (
+			    p[0] == 0x0a
+			&&	p[1] == 'T'
+			&&	p[2] == 'r'
+			&&	p[3] == 'a'
+			&&	p[4] == 'c'
+			&&	p[5] == 'k'
+			&&	p[6] == 'i'
+			&&	p[7] == 'n'
+			&&	p[8] == 'g'
+			)
+		{
+			int type = p[9] - '0';
+			p += 11;
+			float f[6];
+			for (int i=0;i<6;i++)
+			{
+				f[i] = (float)atof(p);
+				while (*p=='-' || *p=='.' || (*p>='0' && *p<='9'))
+				{
+					p++;
+				}
+				p++;  // skip the comma (or half the EOL)
+			}
+			p--;	// back up!
+//			printf ("%d: %f,%f,%f - %f,%f,%f,%f\n",type,f[0],f[1],f[2],f[3],f[4],f[5]);
+
+			Mth::Vector start = Mth::Vector(f[0],f[1],f[2]); 
+			Mth::Vector end = Mth::Vector(f[3],f[4],f[5]);
+			
+			uint32 color = 0xff00ff;
+			switch (type)
+			{
+				case 1:
+					bonks++;
+					if (!do_bonks) goto sorry;
+					color = 0xffffff;	  // white = Bonk
+					start -= (end-start)*10;
+					break;
+				case 2:
+					grinds++;
+					if (!do_grinds) goto sorry;
+					color = 0x0000ff;	  // Red = Grind
+					//start -= (end-start)*2;
+					break;
+				case 3: 
+					verts++;
+					if (!do_verts) goto sorry;
+					color = 0xffff00;	  // Cyan = Vert
+					start -= (end-start)*2;
+					break;
+				case 4: 
+					lines++;
+					if (!do_lines) goto sorry;
+					color = 0x008000;	  // Green = Line
+					start -= (end-start)*4;
+					break;
+				default:
+					color = 0xffff;
+			}
+			Gfx::AddDebugArrow(start, end,color,color,0);
+sorry:		
+		;	
+		}
+	
+	
+		p++;
+	}
+	
+	printf ("Bonks = %d,  Grinds = %d, verts= %d, lines = %d\n",bonks, grinds, verts, lines);
+	
+	Mem::Free(p_file);	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsGrind | 
+bool ScriptIsGrind(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	return pParams->ContainsFlag(CRCD(0x255ed86f, "Grind"));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetColorBufferClear | 
+// @parmopt integer | clear | 0 |
+bool ScriptSetColorBufferClear(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	int	clear = 0;
+	pParams->GetInteger( CRCD( 0x1a4e0ef9, "clear" ), &clear );
+
+	Nx::CEngine::sSetColorBufferClear( clear > 0 );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ShowCamOffset |
+// @parm name | Name | The name of the node we want to get the offset from
+// Printfs the cam offset in EE0
+bool ScriptShowCamOffset(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	#ifdef	__NOPT_ASSERT__
+	// Given a node name
+	// then calculate a good targetOffset and positionOffset
+	// so we can mode the camera into that position later
+
+	// Some common code to either relative, or world specific
+	Mth::Matrix cam_matrix = Nx::CViewportManager::sGetActiveCamera()->GetMatrix();
+	Mth::Vector cam_pos = Nx::CViewportManager::sGetActiveCamera()->GetPos();			
+
+	// get a line segment extending fromthe camera forward (note -z)
+	Mth::Line cam_fwd;
+	cam_fwd.m_start = cam_pos;
+	cam_fwd.m_end = cam_pos - cam_matrix[Z] * 10000;
+
+	
+	uint32 name = 0;
+	if (pParams->GetChecksum(CRCD(0xa1dc81f9,"name"),&name))
+	{
+		
+		if (name != CRCD(0xc588eebc,"world"))
+		{
+			// get the node from the node array, and get the position
+			int node = SkateScript::FindNamedNode(name);
+			if (node != -1)
+			{
+				Script::CStruct *pNode = SkateScript::GetNode(node);
+				Mth::Vector	pos;
+				pNode->GetVector(CRCD(0x7f261953,"pos"),&pos);
+	
+	
+				Mth::Matrix cam_matrix = Nx::CViewportManager::sGetActiveCamera()->GetMatrix();
+				Mth::Vector cam_pos = Nx::CViewportManager::sGetActiveCamera()->GetPos();			
+	
+	
+				// get a line segment extending fromthe camera forward (note -z)
+				Mth::Line cam_fwd;
+				cam_fwd.m_start = cam_pos;
+				cam_fwd.m_end = cam_pos - cam_matrix[Z] * 10000;
+				
+				Mth::Line node_up;
+				node_up.m_start = pos;
+				node_up.m_end = pos + Mth::Vector(0,10000,0);
+	
+				Mth::Vector pa, pb;			
+				float mua,mub;
+				if (Mth::LineLineIntersect( cam_fwd, node_up, &pa, &pb, &mua, &mub, false ))
+				{
+					Gfx::AddDebugArrow(pos, pa,0xff0000,0,100);			
+	//				Gfx::AddDebugArrow(pa, pb ,0xff00  ,0,100);			
+				}
+				else
+				{
+					printf ("Can't calculate intersection, move camera a bit\n");
+				}			
+				
+				//Need to find the offset from this point to the line of sight of the camera
+				// we have  cam_pos = position of camera
+				//          pos     = position of node
+				//          pa      = position of the focus
+				
+				Mth::Vector target_offset = pa-pos;
+				Mth::Vector position_offset = cam_pos - pa;
+				
+				/*
+				printf ("\nUnrotated - Use if focussing relative to the ground\n");																									 
+				printf ("		targetOffset=(%.1f, %.1f, %.1f)\n",target_offset[X],target_offset[Y],target_offset[Z]);
+				printf ("		positionOffset=(%.1f, %.1f, %.1f)\n",position_offset[X],position_offset[Y],position_offset[Z]);
+				*/
+				
+				// Now we have to transform it by the inverse fo the matrix representing the orientation fo the node
+				Obj::CCompositeObject *p_obj = (Obj::CCompositeObject *)Obj::CCompositeObjectManager::Instance()->GetObjectByID(name);
+				if (!p_obj)
+				{
+					printf ("Can't find the oject this node represents\n");
+					return false;
+				}
+				// Get the matrix
+				Mth::Matrix obj_mat = p_obj->GetMatrix();
+				// Invert it, as we want to do the opposite of what we will do with the actual values
+				obj_mat.Invert();
+				// apply this, to get the values
+				target_offset = obj_mat.TransformAsPos(target_offset);
+				position_offset = obj_mat.TransformAsPos(position_offset);
+				
+//				printf ("\nRotated (Use if focussing relative to the object's orientation)\n");																									 
+				printf ("		targetid=%s\n",Script::FindChecksumName(name)); 
+				printf ("		targetOffset=(%.1f, %.1f, %.1f)\n",target_offset[X],target_offset[Y],target_offset[Z]);
+				printf ("		positionOffset=(%.1f, %.1f, %.1f)\n",position_offset[X],position_offset[Y],position_offset[Z]);
+	
+				return true;
+			}			
+		}
+	}
+	
+	// Looks like we want world coordinates, so print up targetOffset as a point 100 feet in front of the camera
+	// and positionoffset as the actual point of the camera
+	printf ("		targetid=world\n"); 
+	printf ("		targetOffset=(%.1f, %.1f, %.1f)\n",cam_fwd.m_end[X],cam_fwd.m_end[Y],cam_fwd.m_end[Z]);
+	printf ("		positionOffset=(%.1f, %.1f, %.1f)\n",cam_pos[X],cam_pos[Y],cam_pos[Z]);
+	
+	
+	
+	#endif
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+// @script | SetEventHandler | register this script to listen for the given event and gosub to the given script in response to the event
+// @parm name | Ex | the name of the event listened for
+// @parm script | Scr | the script to be run in response to the event
+// @parmopt name | Group | the name of this event listeners group; used to identify which listeners to clear when clearing listener groups
+// @parmopt structure | Params | this structure is added to the parameter list of the script
+// @parmopt flag | exception | set if this causes an exception, rather than a goto
+bool ScriptSetEventHandler(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 ex = 0;
+	pParams->GetChecksum(CRCD(0xf8728bec,"Ex"), &ex, true);
+	uint32 scr = 0;
+	pParams->GetChecksum(CRCD(0xa6d2d890,"Scr"), &scr, true);
+	uint32 group = CRCD(0x1ca1ff20,"default");
+	pParams->GetChecksum(CRCD(0x923fbb3a,"group"), &group);	
+	bool exception = pParams->ContainsFlag(CRCD(0x80367192,"exception"));
+	Script::CStruct *pExtraParams=NULL;
+	pParams->GetStructure(CRCD(0x7031f10c,"params"),&pExtraParams);
+	pScript->SetEventHandler(ex,scr,group,exception,pExtraParams);	
+	return true;
+}
+
+bool ScriptClearEventHandler(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 type;
+	pParams->GetChecksum(NO_NAME, &type, Script::ASSERT);
+	pScript->RemoveEventHandler(type);
+	return true;
+}
+			
+bool ScriptClearEventHandlerGroup(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 group = Obj::CEventHandlerTable::vDEFAULT_GROUP;
+	pParams->GetChecksum(NO_NAME, &group);
+	pScript->RemoveEventHandlerGroup(group);
+	return true;
+}
+		
+// @script | OnExceptionRun | run the specified script on exception
+// @uparm name | script name to run
+// can be called without a parameter to clear it
+bool ScriptOnExceptionRun(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 OnExceptionScriptChecksum = 0;
+	pParams->GetChecksum( NONAME, &OnExceptionScriptChecksum);
+	pScript->SetOnExceptionScriptChecksum(OnExceptionScriptChecksum);
+	return true;
+}
+
+bool ScriptOnExitRun(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	uint32 OnExitScriptChecksum = 0;
+	pParams->GetChecksum( NONAME, &OnExitScriptChecksum);
+	pScript->SetOnExitScriptChecksum(OnExitScriptChecksum);
+	return true;
+}
+
+
+// @script | Block | Stop execution of this script, allowing to still be woken up by events and exceptions
+bool ScriptBlock(Script::CStruct* pParams, Script::CScript* pScript)
+{
+	pScript->Block();
+	return true;
+}
+
+// @script | PrintEventHandlerTable | Prints the event handler table of the current script
+bool ScriptPrintEventHandlerTable ( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	pScript->PrintEventHandlerTable();
+	return true;
+}
+
+
+// @script | AllocateSplitScreenDMA | (PS2 only) allocated extra memory for DMA usage
+bool ScriptAllocateSplitScreenDMA ( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	#ifdef	__PLAT_NGPS__
+	
+	Nx::CEngine::sFinishRendering();
+ 	NxPs2::AllocateExtraDMA(true);
+
+	#endif
+	
+	return true;
+}
+
+
+
+// @script | AddSkaterEarly | Create and load a skater, so when we autolaunch a level, it's there.  DEBUG ONLY
+bool ScriptAddSkaterEarly( Script::CStruct* pParams, Script::CScript* pScript )
+{
+
+	if (Mdl::Skate::Instance()->GetNumSkaters() == 0)
+	{
+		Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile(0);
+		Mdl::Skate::Instance()->add_skater( pSkaterProfile, true, 0, 0);
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptPreLoadStreamDone( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 streamId;
+	pParams->GetChecksum( NONAME, &streamId, Script::ASSERT );
+	return Pcm::PreLoadStreamDone( streamId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptStartPreLoadedStream( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 streamId;
+	pParams->GetChecksum( CRCD(0x8a68ab90,"streamId"), &streamId, Script::ASSERT );
+	float vol = 100;
+	pParams->GetFloat( CRCD(0x46653221,"volume"), &vol, Script::NO_ASSERT );
+	Sfx::sVolume* p_volume = new Sfx::sVolume();
+	p_volume->SetChannelVolume( 0, vol );
+	p_volume->SetChannelVolume( 1, vol );
+	bool rc = Pcm::StartPreLoadedStream( streamId, p_volume );
+	delete p_volume;
+	return rc;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptFinishRendering( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Nx::CEngine::sFinishRendering();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace CFuncs
diff --git a/Code/Sk/Scripting/cfuncs.h b/Code/Sk/Scripting/cfuncs.h
new file mode 100644
index 0000000..eab0f54
--- /dev/null
+++ b/Code/Sk/Scripting/cfuncs.h
@@ -0,0 +1,576 @@
+//****************************************************************************
+//* MODULE:         Sk/Scripting
+//* FILENAME:       CFuncs.h
+//* OWNER:          Kendall Harrison
+//* CREATION DATE:  9/14/2000
+//****************************************************************************
+
+#ifndef	__SCRIPTING_CFUNCS_H
+#define	__SCRIPTING_CFUNCS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+// for now, include skfuncs.h, so that
+// it won't break the existing CPP files
+// that include cfuncs.h
+//#include 
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+};
+	
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace CFuncs
+{
+
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+enum {
+	TYPE_VUTESTOBJECT = 0x8daad03d,
+	VUTESTOBJECT = 0xae96a38,
+};
+
+
+bool	SetActiveCamera(uint32 id, int viewport, bool move_to_current);
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+bool ScriptDummyCommand(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptAllocateSplitScreenDMA ( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptWait(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptBlock(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintEventHandlerTable(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptFormatText(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintf(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptScriptAssert(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintScriptInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetHackFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptHackFlagIsSet(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintStruct(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnloadAllLevelGeometry(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadScene(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadCollision(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAddScene(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAddCollision(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnloadScene(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptQuickReload(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadNodeArray(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptReLoadNodeArray(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptParseNodeArray(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetBackgroundColor(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetBSPAmbientColor(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetDFFAmbientColor(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetDFFDirectColor(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetDynamicLightModulationFactor( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetClippingDistances(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetTrivialFarZClip(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEnableFog(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDisableFog(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetFogDistance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetFogExponent(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetFogColor(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetUVWibbleParams(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEnableExplicitUVWibble(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDisableExplicitUVWibble(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetUVWibbleOffsets(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetWorldSize( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMovementVelocity( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetRotateVelocity( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptOnReload(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetEngine(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleMetrics(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleVRAMViewer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetVRAMPackContext(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDumpVRAMUsage(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleLightViewer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCleanup(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptProximCleanup(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadQB(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnloadQB(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetDebugRenderMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleTextureUpload(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleTextureDraw(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptTogglePointDraw(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRenderTest1(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRenderTest2(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIsZero(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCastToInteger(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStringToInteger(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIntegerEquals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptChecksumEquals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStringEquals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptArrayContains(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetArraySize(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetArrayElement(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAddParams(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGet3DArrayData(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGet2DArrayData(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetNDArrayData(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRemoveComponent(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetScriptString(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetScriptString(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptKenTest1(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptKenTest2(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAppendSuffix(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFindNearestRailPoint(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetTime(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetDate(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetObNearestScreenCoord(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRandomize(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetTimer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptTimeGreaterThan(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetStartTime(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetElapsedTime(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRemoveParameter(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetRandomValue(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetConfig(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintConfig(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGerman(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFrench(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpanish(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptItalian(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetNodeName(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetTesterScript(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptKillTesterScript(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetStopwatch(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintStopwatchTime(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCustomSkaterFilenameDefined(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetCustomSkaterFilename(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetCustomSkaterFilename(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEditingPark(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetParkName(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetParkName(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptParkEditorThemeWasSwitched(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMenuIsShown(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMenuIsSelected(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptForEachIn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSizeOf(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetElement(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetNextArrayElement(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetRandomArrayElement(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPermuteArray(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptApplyChangeGamma( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetGammaValues( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGotParam(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPreloadModel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGoto(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGotoPreserveParams(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGotoRandomScript(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleRenderMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetRenderMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetWireframeMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDebugRenderIgnore(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptScreenShot(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDumpShots(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleMetricItem( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptToggleMemMetrics( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptKillAllTextureSplats( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPlayMovie( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadAsset( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadAnim( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadSkeleton( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUnloadAnim( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptAssManSetReferenceChecksum( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptAssManSetDefaultPermanent( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadSound( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPlaySound( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptStopSound( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptStopAllSounds( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetSoundParams( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIsSoundPlaying( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPlayStream( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptStopStream( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetStreamParams( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIsStreamPlaying( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetReverb( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadTerrainSounds( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetObjectColor( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetSceneColor( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCompressVC( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptNudgeVC( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptFakeLights( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCenterCamera( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetDropoff( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetVolume( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMusicVolume( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMusicStreamVolume( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadMusicHeader( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadStreamHeader( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptStreamIsAvailable( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPlayTrack( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPlayMusicStream( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSkipMusicTrack( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetRandomMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetCurrentTrack( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMusicMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMusicLooping( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptStopMusic( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPauseMusic( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPauseStream( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadStreamFrameAmp( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptFreeStreamFrameAmp( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptAddMusicTrack( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptChangeTrackState(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptTrackEnabled(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMusicIsPaused(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptClearMusicTrackList( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIfDebugOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIfDebugOff(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptBootstrap(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCD(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptNotCD(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCasArtist(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptNotCasArtist(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGunslinger(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDoAction(Script::CStruct *pParams, Script::CScript *pScript, int action );
+bool ScriptSendFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptClearFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptQueryFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCheckIfAlive( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCheckExistenceFromNodeIndex( int nodeIndex );
+bool ScriptFlagException(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMakeSkaterGoto(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMakeSkaterGosub(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpawnSound(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpawnScript(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpawnSkaterScript(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptKillSpawnedScript(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPauseSkaters( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUnPauseSkaters( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPauseSkater( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUnPauseSkater( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPauseGame( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUnPauseGame( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIsGamePaused( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPauseObjects( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUnPauseObjects( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPauseSpawnedScripts( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUnPauseSpawnedScripts( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptResetClock( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPadsPluggedIn( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDoFlash( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetViewMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStartServer(Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSpawnCrown(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpawnCompass(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLeaveServer(Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptFindServers(Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptJoinServer(Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetNetworkMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetServerMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCreateFromNodeIndex( int nodeIndex );
+bool ScriptKillFromNodeIndex( int nodeIndex, Script::CScript *pScript );
+bool ScriptSetColorFromNodeIndex( int nodeIndex, Script::CStruct *pParams );
+bool ScriptShatterFromNodeIndex( int nodeIndex );
+bool ScriptSetVisibilityFromNodeIndex( int nodeIndex, bool invisible, int viewport_number = 0 );
+bool ScriptNodeExists( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCreate( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCreateFromStructure( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptKill( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptVisible( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptInvisible( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptShatter( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptResetCamera(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetValueFromVolume(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetSliderValue(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetVolumeFromValue(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetIconTexture(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMemViewToggle(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPreferenceEquals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetPreference(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetPreferenceString(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetPreferencePassword(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetPreferenceChecksum(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMemPushContext(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMemPopContext(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptProfileTasks( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUseNetworkPreferences( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCanChangeDevices( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptNeedToTestNetSetup( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptTestNetSetup( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptConnectToInternet( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCancelConnectToInternet( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCancelLogon( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDisconnectFromInternet( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIsOnline( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptStopAllScripts( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMenuElementText(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFirstTimeThisIsCalled(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEnableActuators(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInNetGame(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleNetMetrics(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetSlomo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetArenaSize(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetParticleSysVisibility( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptTogglePlayerNames( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetCurrentGameType( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDumpNetMessageStats( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptNotifyBailDone( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDisplayLoadingScreen( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptHideLoadingScreen( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptObserveNextSkater( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPrintMemInfo( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDisplayFreeMem( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptAnalyzeHeap( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptMemThreadSafe( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIsSingleSession(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEnterObserverMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAllowPause(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRefreshServerList(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStartServerList(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStopServerList(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFreeServerList(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPauseGameFlow(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnpauseGameFlow(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInFrontEnd(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetFrontEndInactive(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInSplitScreenGame(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGameModeEquals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetFireballLevel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptOnServer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetCurrentLevel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRestartLevel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleScores(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIsTrue(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetLevelLoadScript(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptXTriggered(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUsePad(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptInMultiplayerGame(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleShadowType(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptGameFlow(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptControllerPressed(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetAnalogueInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptControllerDebounce( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptClearCheats(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptBroadcastCheat(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLastBroadcastedCheatWas(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCheatAllowed(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptJoinWithPassword(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSendChatMessage(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInSlapGame( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetScreenModeFromGameMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetScreenMode( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptLoadPendingPlayers( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptLaunchQueuedScripts( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIsObserving( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSkatersAreReady(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptGetInitialsString( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetInitialsString( Script::CStruct *pParams, Script::CScript *pScript );
+bool	ScriptAttachToSkater(  Script::CStruct *pParams, Script::CScript *pScript );
+bool	ScriptTryCheatString(  Script::CStruct *pParams, Script::CScript *pScript );
+bool	ScriptLevelIs(  Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptStartNetworkLobby( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptObserversAllowed( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptNumPlayersAllowed( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptAutoDNS( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUsingDefaultMasterServers( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUsingDHCP( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptInInternetMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptEnteringNetGame( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDeviceChosen( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLoadExecPS2( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptExitDemo( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptGameIsOver( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptSetLevelName( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDumpHeaps( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptDumpFragments( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptResetPS2( Script::CStruct *pParams, Script::CScript *pScript ); 
+bool ScriptResetHD( Script::CStruct *pParams, Script::CScript *pScript ); 
+bool ScriptPAL( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptEnglish( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptTimeUp( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptChangeSymbolValue(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDumpScripts( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptResetPS2( Script::CStruct *pParams, Script::CScript *pScript ); 
+bool ScriptResetHD( Script::CStruct *pParams, Script::CScript *pScript ); 
+bool ScriptPAL( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptEnglish( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptTimeUp( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptLaunchViewer( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptLaunchScriptDebugger( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetViewerModel( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetViewerAnim( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetViewerLODDist( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetViewerObjectID( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptReloadViewerAnim( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptInitHealth( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptResetScore( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAddRestartsToMenu( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAddWarpPointsToMenu( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptRunScriptOnObject( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptRunScriptOnComponentType( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptTestName( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptDebounce( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetStatOverride( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptToggleRails( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptToggleRigidBodyDebug( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptCheckForHoles( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptStartLobbyList( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptChatConnect( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptChatDisconnect( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptFillPlayerListMenu( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptOnXbox( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGotoXboxDashboard( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSystemLinkEnabled( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptCreateParticleSystem( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetScript( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptDestroyParticleSystem( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptEmptyParticleSystem( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptParticleExists( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptStructureContains( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetBonePosition( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptMangleChecksums( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAppendSuffixToChecksum( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptShouldEmitParticles( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptParticlesOn( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptParticlesOff( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptRotateVector( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptIsPS2( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptIsXBOX( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptIsNGC( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptIsWIN32( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetPlatform( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptIsPal( Script::CStruct* pParams, Script::CScript* pScript );
+
+
+bool ScriptPushMemProfile( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptPopMemProfile( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptTogglePass( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool	ScriptSetScreen( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetUpperCaseString( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetGameMode( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptStartKeyboardHandler( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptStopKeyboardHandler( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptEnableKeyboard( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptDisableKeyboard( Script::CStruct* pParams, Script::CScript* pScript );
+	 
+bool ScriptCreateIndexArray( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetRescaledTargetValue( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptMemInitHeap( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptMemDeleteHeap( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptClearStruct( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptAppendStruct( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptScriptExists( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSpawnSecondControllerCheck( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptStopSecondControllerCheck( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptIsArray( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptUseUserSoundtrack( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptUseStandardSoundtrack( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptDisableReset( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptEnableReset( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptResetToIPL( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptInitAnimCompressTable( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptCreateCompositeObject( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptAutoRail( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptInside( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSetSpecialBarColors( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetMetrics( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptMoveNode( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetActiveCamera( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptSetColorBufferClear( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetEventHandler( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptClearEventHandler(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptClearEventHandlerGroup(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptOnExceptionRun(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptOnExitRun(Script::CStruct* pParams, Script::CScript* pScript);
+
+
+
+bool ScriptSin( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptCos( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptTan( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptASin( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptACos( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptATan( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptShowTracking(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptIsGrind(Script::CStruct* pParams, Script::CScript* pScript);
+bool ScriptShowCamOffset(Script::CStruct* pParams, Script::CScript* pScript);
+
+bool ScriptAddSkaterEarly( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptPreLoadStreamDone( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptStartPreLoadedStream( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptFinishRendering( Script::CStruct* pParams, Script::CScript* pScript );
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace CFuncs
+
+#endif	// __SCRIPTING_CFUNCS_H
+
diff --git a/Code/Sk/Scripting/ftables.cpp b/Code/Sk/Scripting/ftables.cpp
new file mode 100644
index 0000000..70a9c04
--- /dev/null
+++ b/Code/Sk/Scripting/ftables.cpp
@@ -0,0 +1,2188 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Scripting                  								**
+**																			**
+**	File name:		Scripting/ftables.cpp          							**
+**																			**
+**	Created by:		09/15/00	-	ksh										**
+**																			**
+**	Description:	Lists of C-functions & class member functions callable 	**
+**                  from scripts.                                           **
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#ifdef __PLAT_NGPS__
+#include 
+#include 
+#include 
+#include 
+#endif
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+
+
+#ifdef __PLAT_NGC__
+#include 
+#endif		// __PLAT_NGC__
+			  
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Script
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+// A list of all the C functions callable from scripts.
+// The name in quotes must be the same as the way it appears in q scripts. (case insensitive)
+SCFunction CFunctionLookupTable[]=
+{
+    {"Wait",				CFuncs::ScriptWait},
+	{"WaitForEvent",		Obj::ScriptWaitForEvent},
+	{"Block",				CFuncs::ScriptBlock},
+
+    // Prints the contents of the passed structure.
+    {"PrintStruct",         CFuncs::ScriptPrintStruct},
+	
+	// Prints the script's event handler table.
+    {"PrintEventHandlerTable",	CFuncs::ScriptPrintEventHandlerTable},
+    
+    {"AllocateSplitScreenDMA",	CFuncs::ScriptAllocateSplitScreenDMA},
+										
+										
+    // Printf "Default printf text"
+    {"Printf",				CFuncs::ScriptPrintf},
+	{"ScriptAssert",		CFuncs::ScriptScriptAssert},
+	{"PrintScriptInfo",		CFuncs::ScriptPrintScriptInfo},
+	{"FormatText",			CFuncs::ScriptFormatText},
+	
+	{"SetHackFlag",			CFuncs::ScriptSetHackFlag},
+	{"HackFlagIsSet",		CFuncs::ScriptHackFlagIsSet},
+	{"SetCurrentSkaterProfile",	CFuncs::ScriptSetCurrentSkaterProfile},
+	{"CurrentSkaterProfileIs",	CFuncs::ScriptCurrentSkaterProfileIs},
+	{"AddSkaterProfile",		CFuncs::ScriptAddSkaterProfile},
+	{"AddTemporaryProfile",		CFuncs::ScriptAddTemporaryProfile},
+	{"RememberTemporaryAppearance",CFuncs::ScriptRememberTemporaryAppearance},
+	{"RestoreTemporaryAppearance",	CFuncs::ScriptRestoreTemporaryAppearance},
+	{"SyncPlayer2Profile",	CFuncs::ScriptSyncPlayer2Profile},
+	
+	{"UnloadAllLevelGeometry",	CFuncs::ScriptUnloadAllLevelGeometry},
+	
+    // LoadLevelGeometry "blaa"
+	{"QuickReload",			CFuncs::ScriptQuickReload},
+	{"UnloadScene",			CFuncs::ScriptUnloadScene},
+	{"LoadScene",			CFuncs::ScriptLoadScene},
+	{"LoadCollision",		CFuncs::ScriptLoadCollision},
+	{"AddScene",			CFuncs::ScriptAddScene},
+	{"AddCollision",		CFuncs::ScriptAddCollision},
+	{"ToggleAddScenes",		Nx::ScriptToggleAddScenes},
+	{"SetScreenBlur",		Nx::ScriptSetScreenBlur},
+    // LoadNodeArray "blaa.qb"
+    {"LoadNodeArray",		CFuncs::ScriptLoadNodeArray},
+    {"ReLoadNodeArray",		CFuncs::ScriptReLoadNodeArray},
+	{"ParseNodeArray",		CFuncs::ScriptParseNodeArray},
+    
+    // SetBackgroundColor r=0 g=0 b=0 alpha=0
+    {"SetBackgroundColor",  CFuncs::ScriptSetBackgroundColor},
+    
+    // Won't do anything yet ...   
+    // SetAmbientColor r=0 g=0 b=0 alpha=0                                         
+//    {"SetBSPAmbientColor",  CFuncs::ScriptSetBSPAmbientColor},
+//    {"SetDFFAmbientColor",  CFuncs::ScriptSetDFFAmbientColor},
+//    {"SetDFFDirectColor",  CFuncs::ScriptSetDFFDirectColor},
+//	{"SetDFFDirectRotation", Mdl::ScriptSetDirectionalLightDirection},
+//	{"SetDFFDirectActive", Mdl::ScriptSetDirectionalLightActive},
+	
+	{"SetDynamicLightModulationFactor", CFuncs::ScriptSetDynamicLightModulationFactor},
+    
+    // Sets the clipping distances.
+    // SetClippingDistances Near=100 Far=1000
+    {"SetClippingDistances",    CFuncs::ScriptSetClippingDistances},
+	{"SetTrivialFarZClip",		CFuncs::ScriptSetTrivialFarZClip},
+    
+	// Sets the default world size (for viewing dffs)
+    // SetWorldSize Size=100
+//    {"SetWorldSize",    CFuncs::ScriptSetWorldSize},
+
+    // Sets the movement( translational ) velocity for the camera in the viewer
+	{"SetMovementVelocity", CFuncs::ScriptSetMovementVelocity },
+	// Sets the rotational velocity for the camera in the viewer
+	{"SetRotateVelocity", CFuncs::ScriptSetRotateVelocity},
+
+	{"SkaterLastScoreLandedGreaterThan", CFuncs::ScriptLastScoreLandedGreaterThan},
+	{"SkaterLastScoreLandedLessThan", CFuncs::ScriptLastScoreLandedLessThan},
+	{"SkaterTotalScoreGreaterThan", CFuncs::ScriptTotalScoreGreaterThan},
+	{"AnySkaterTotalScoreAtLeast", CFuncs::ScriptAnyTotalScoreAtLeast},
+	{"OnlyOneSkaterLeft", CFuncs::ScriptOnlyOneSkaterLeft},
+	{"SkaterTotalScoreLessThan", CFuncs::ScriptTotalScoreLessThan},
+	{"SkaterCurrentScorePotGreaterThan", CFuncs::ScriptCurrentScorePotGreaterThan},
+	{"SkaterCurrentScorePotLessThan", CFuncs::ScriptCurrentScorePotLessThan},
+	{"SkaterGetScoreInfo", CFuncs::ScriptSkaterGetScoreInfo},
+
+	{"SkaterSpeedGreaterThan", CFuncs::ScriptSkaterSpeedGreaterThan},
+	{"SkaterSpeedLessThan", CFuncs::ScriptSkaterSpeedLessThan},
+
+	{"ToggleAlwaysSpecial", CFuncs::ScriptToggleAlwaysSpecial},
+
+	{"Debounce",			CFuncs::ScriptDebounce},
+	{"SetStatOverride",		CFuncs::ScriptSetStatOverride},
+	{"ToggleRails",			CFuncs::ScriptToggleRails},
+	{"ToggleRigidBodyDebug", CFuncs::ScriptToggleRigidBodyDebug},
+	{"CheckForHoles",		CFuncs::ScriptCheckForHoles},
+	
+	{"OnReload",			CFuncs::ScriptOnReload},
+	{"ResetEngine",			CFuncs::ScriptResetEngine},
+	{"ResetSkaters",		CFuncs::ScriptResetSkaters},
+	{"ToggleMetrics",		CFuncs::ScriptToggleMetrics},
+	{"ToggleVRAMViewer",	CFuncs::ScriptToggleVRAMViewer},
+	{"ToggleLightViewer",	CFuncs::ScriptToggleLightViewer},
+	{"DumpVRAMUsage",		CFuncs::ScriptDumpVRAMUsage},
+	{"SetVRAMPackContext",	CFuncs::ScriptSetVRAMPackContext},
+	{"Cleanup",	    		CFuncs::ScriptCleanup},
+	{"ProximCleanup",  		CFuncs::ScriptProximCleanup},
+	{"LoadQB",				CFuncs::ScriptLoadQB},
+	{"UnloadQB",			CFuncs::ScriptUnloadQB},
+	{"DebugRenderIgnore",	CFuncs::ScriptDebugRenderIgnore},
+	{"debugrendermode",		CFuncs::ScriptSetDebugRenderMode},
+	{"debugtoggletextureupload",		CFuncs::ScriptToggleTextureUpload},
+	{"debugtoggletexturedraw",		CFuncs::ScriptToggleTextureDraw},
+	{"debugtogglepointdraw",		CFuncs::ScriptTogglePointDraw},
+	{"debugrendertest1",			CFuncs::ScriptRenderTest1},
+	{"debugrendertest2",			CFuncs::ScriptRenderTest2},
+
+    {"IsZero",					CFuncs::ScriptIsZero},
+	{"CastToInteger",			CFuncs::ScriptCastToInteger},
+	{"StringToInteger",			CFuncs::ScriptStringToInteger},
+	{"IntegerEquals",			CFuncs::ScriptIntegerEquals},
+	{"ChecksumEquals",			CFuncs::ScriptChecksumEquals},
+	{"StringEquals",			CFuncs::ScriptStringEquals},
+	{"ArrayContains",			CFuncs::ScriptArrayContains},
+	{"GetArraySize",			CFuncs::ScriptGetArraySize},
+	{"SetArrayElement",			CFuncs::ScriptSetArrayElement},
+	{"Get3DArrayData",			CFuncs::ScriptGet3DArrayData},
+	{"Get2DArrayData",			CFuncs::ScriptGet2DArrayData},
+	{"GetNDArrayData",			CFuncs::ScriptGetNDArrayData},
+	{"AddParams",				CFuncs::ScriptAddParams},
+	{"RemoveComponent",			CFuncs::ScriptRemoveComponent},
+	{"SetScriptString",			CFuncs::ScriptSetScriptString},
+	{"GetScriptString",			CFuncs::ScriptGetScriptString},
+    {"KenTest1",				CFuncs::ScriptKenTest1},
+    {"KenTest2",				CFuncs::ScriptKenTest2},
+	{"AppendSuffix",			CFuncs::ScriptAppendSuffix},
+	{"FindNearestRailPoint",	CFuncs::ScriptFindNearestRailPoint},
+	{"GetTime",					CFuncs::ScriptGetTime},
+	{"GetDate",					CFuncs::ScriptGetDate},
+	{"GetObNearestScreenCoord",	CFuncs::ScriptGetObNearestScreenCoord},
+	{"Randomize",				CFuncs::ScriptRandomize},
+	{"RunningReplay",			Replay::ScriptRunningReplay},
+	{"PauseReplay",				Replay::ScriptPauseReplay},
+	{"UnPauseReplay",			Replay::ScriptUnPauseReplay},
+	{"DeleteDummies",			Replay::ScriptDeleteDummies},
+	{"ShowGameObjects",			Replay::ScriptShowGameObjects},
+	{"HideGameObjects",			Replay::ScriptHideGameObjects},
+	{"PlaybackReplay",			Replay::ScriptPlaybackReplay},
+	{"ReplayRecordSimpleScriptCall",	Replay::ScriptReplayRecordSimpleScriptCall},
+	{"RecordPanelMessage",		Replay::ScriptRecordPanelMessage},
+	{"SwitchToReplayRecordMode",Replay::ScriptSwitchToReplayRecordMode},
+	{"SwitchToReplayIdleMode",	Replay::ScriptSwitchToReplayIdleMode},
+	{"AllocateReplayMemory",	Replay::ScriptAllocateReplayMemory},
+	{"DeallocateReplayMemory",	Replay::ScriptDeallocateReplayMemory},
+	{"StartRecordingAfresh",				Replay::ScriptStartRecordingAfresh},
+	{"RememberLevelStructureNameForReplays",	Replay::ScriptRememberLevelStructureNameForReplays},
+	{"GetReplayLevelStructureName",	Replay::ScriptGetReplayLevelStructureName},
+	{"ClearTrickAndScoreText",		Replay::ScriptClearTrickAndScoreText},
+	{"NeedToLoadReplayBuffer",	CFuncs::ScriptNeedToLoadReplayBuffer},
+	{"LoadReplayData",			CFuncs::ScriptLoadReplayData},
+	{"ResetTimer",				CFuncs::ScriptResetTimer},
+	{"TimeGreaterThan",			CFuncs::ScriptTimeGreaterThan},
+	{"GetStartTime",			CFuncs::ScriptGetStartTime},
+	{"GetElapsedTime",          CFuncs::ScriptGetElapsedTime},
+	{"RemoveParameter",			CFuncs::ScriptRemoveParameter},
+	{"GetRandomValue",			CFuncs::ScriptGetRandomValue},
+	{"SetConfig",				CFuncs::ScriptSetConfig},
+	{"PrintConfig",				CFuncs::ScriptPrintConfig},
+	{"German",					CFuncs::ScriptGerman},
+	{"French",					CFuncs::ScriptFrench},
+	{"Spanish",					CFuncs::ScriptSpanish},
+	{"Italian",					CFuncs::ScriptItalian},
+	{"GetNodeName",				CFuncs::ScriptGetNodeName},
+	{"SetTesterScript",			CFuncs::ScriptSetTesterScript},
+	{"KillTesterScript",		CFuncs::ScriptKillTesterScript},
+	{"ResetStopwatch",			CFuncs::ScriptResetStopwatch},
+	{"PrintStopwatchTime",		CFuncs::ScriptPrintStopwatchTime},
+	{"CustomSkaterFilenameDefined",CFuncs::ScriptCustomSkaterFilenameDefined},
+	{"GetCustomSkaterFilename",	CFuncs::ScriptGetCustomSkaterFilename},
+	{"SetCustomSkaterFilename",	CFuncs::ScriptSetCustomSkaterFilename},
+	{"CurrentSkaterIsPro",		CFuncs::ScriptCurrentSkaterIsPro},
+	{"EditingPark",				CFuncs::ScriptEditingPark},
+	{"GetParkName",				CFuncs::ScriptGetParkName},
+	{"SetParkName",				CFuncs::ScriptSetParkName},
+	{"ParkEditorThemeWasSwitched",CFuncs::ScriptParkEditorThemeWasSwitched},
+	{"GetGoalsCompleted",		CFuncs::ScriptGetGoalsCompleted},
+	{"GetNextLevelRequirements",CFuncs::ScriptGetNextLevelRequirements},
+	{"MenuIsShown",				CFuncs::ScriptMenuIsShown},
+	{"MenuIsSelected",			CFuncs::ScriptMenuIsSelected},
+
+	{"GetMostRecentSave",		CFuncs::ScriptGetMostRecentSave},
+	{"GetMemCardSpaceAvailable",CFuncs::ScriptGetMemCardSpaceAvailable},
+	{"GetMemCardSpaceRequired",	CFuncs::ScriptGetMemCardSpaceRequired},
+	{"MemCardFileExists",		CFuncs::ScriptMemCardFileExists},
+	{"DeleteMemCardFile",		CFuncs::ScriptDeleteMemCardFile},
+	{"FormatCard",				CFuncs::ScriptFormatCard},
+	{"CardIsInSlot",			CFuncs::ScriptCardIsInSlot},
+	{"SectorSizeOK",			CFuncs::ScriptSectorSizeOK},
+	{"CardIsDamaged",			CFuncs::ScriptCardIsDamaged},
+	{"CardIsForeign",			CFuncs::ScriptCardIsForeign},
+	{"BadDevice",				CFuncs::ScriptBadDevice},
+	{"GetSaveInfo",				CFuncs::ScriptGetSaveInfo},
+	{"CreateTemporaryMemCardPools",CFuncs::ScriptCreateTemporaryMemCardPools},
+	{"RemoveTemporaryMemCardPools",CFuncs::ScriptRemoveTemporaryMemCardPools},
+	{"SwitchToTempPoolsIfTheyExist",CFuncs::ScriptSwitchToTempPoolsIfTheyExist},
+	{"SwitchToRegularPools",	CFuncs::ScriptSwitchToRegularPools},
+	{"CardIsFormatted",			CFuncs::ScriptCardIsFormatted},
+	{"SaveFailedDueToInsufficientSpace",CFuncs::ScriptSaveFailedDueToInsufficientSpace},
+	{"GetSummaryInfo",			CFuncs::ScriptGetSummaryInfo},
+	{"SaveToMemoryCard",		CFuncs::ScriptSaveToMemoryCard},
+	{"LoadFromMemoryCard",		CFuncs::ScriptLoadFromMemoryCard},
+	{"LoadedCustomSkater",		CFuncs::ScriptLoadedCustomSkater},
+	{"SetSectionsToApplyWhenLoading",CFuncs::ScriptSetSectionsToApplyWhenLoading},
+	{"GetMemCardDataForUpload",	CFuncs::ScriptGetMemCardDataForUpload},
+	{"ClearMemCardDataForUpload",CFuncs::ScriptClearMemCardDataForUpload},
+	{"GetMaxTHPS4FilesAllowed",	CFuncs::ScriptGetMaxTHPS4FilesAllowed},
+	{"GetMemCardDirectoryListing",CFuncs::ScriptGetMemCardDirectoryListing},
+	{"ForEachIn",				CFuncs::ScriptForEachIn},
+	{"SizeOf",					CFuncs::ScriptSizeOf},
+	{"GetElement",				CFuncs::ScriptGetElement},
+	{"GetNextArrayElement",		CFuncs::ScriptGetNextArrayElement},
+	{"GetRandomArrayElement",	CFuncs::ScriptGetRandomArrayElement},
+	{"PermuteArray",			CFuncs::ScriptPermuteArray},
+	{"CreateIndexArray",		CFuncs::ScriptCreateIndexArray},
+	{"GetGammaValues",			CFuncs::ScriptGetGammaValues},
+	{"ApplyChangeGamma",		CFuncs::ScriptApplyChangeGamma},
+	{"GotParam",				CFuncs::ScriptGotParam},
+	{"Goto",					CFuncs::ScriptGoto},
+	{"GotoPreserveParams",		CFuncs::ScriptGotoPreserveParams},
+	{"GotoRandomScript",		CFuncs::ScriptGotoRandomScript},
+
+	{"PreloadModel", CFuncs::ScriptPreloadModel},
+
+	{"LaunchPanelMessage", 		CFuncs::ScriptDummyCommand},
+	{"LaunchLocalPanelMessage", CFuncs::ScriptDummyCommand},
+	{"KillMessages", 			CFuncs::ScriptDummyCommand},
+	{"ChangeLocalMessage", 		CFuncs::ScriptDummyCommand},
+	{"LaunchConsoleMessage", 	CFuncs::ScriptDummyCommand},
+	//{"FireMenuEvent",			Front::ScriptFireMenuEvent},
+
+	{"SetViewMode",				CFuncs::ScriptSetViewMode},
+	
+	{"ToggleRenderMode",		CFuncs::ScriptToggleRenderMode},
+	{"SetRenderMode",			CFuncs::ScriptSetRenderMode},
+	{"SetWireframeMode",		CFuncs::ScriptSetWireframeMode},
+	{"ScreenShot",				CFuncs::ScriptScreenShot},
+	{"DumpShots",				CFuncs::ScriptDumpShots},
+	//{"SetStat",					CFuncs::ScriptSetStat},
+
+	{"LoadFont", 				Nx::ScriptLoadFont},
+	{"UnloadFont",				Nx::ScriptUnloadFont},
+
+	{"LoadTexture", 			Nx::ScriptLoadTexture},
+	{"UnloadTexture",			Nx::ScriptUnloadTexture},
+	{"AddTextureToVram", 		Nx::ScriptAddTextureToVram},
+	{"RemoveTextureFromVram",	Nx::ScriptRemoveTextureFromVram},
+	{"LoadFaceTextureFromProfile", Nx::ScriptLoadFaceTextureFromProfile},
+	{"Generate32BitImage",		Nx::ScriptGenerate32BitImage},
+	{"OffsetTexture",			Nx::ScriptOffsetTexture},
+    {"AdjustTextureRegion",		Nx::ScriptAdjustTextureRegion},
+	{"PullTextureToEdge",		Nx::ScriptPullTextureToEdge},
+	{"PushTextureToPoint",		Nx::ScriptPushTextureToPoint},
+	{"AdjustTextureBrightness",	Nx::ScriptAdjustTextureBrightness},
+	{"AdjustTextureHSV",		Nx::ScriptAdjustTextureHSV},
+	{"CopyTexture",				Nx::ScriptCopyTexture},
+	{"CombineTextures",			Nx::ScriptCombineTextures},
+	{"LoadParticleTexture",		Nx::ScriptLoadParticleTexture},
+	{"UnloadParticleTexture",	Nx::ScriptUnloadParticleTexture},
+	{"LoadSFPTexture",			Nx::ScriptLoadSFPTexture},
+	{"UnloadSFPTexture",		Nx::ScriptUnloadSFPTexture},
+	{"DumpTextures",			Nx::ScriptDumpTextures},
+
+	{"SetModelFaceTexturePoints", Nx::ScriptSetModelFaceTexturePoints},
+	{"SetFaceMassageTextureOverlay", Nx::ScriptSetFaceMassageTextureOverlay},
+	{"MassageFaceTexture",		Nx::ScriptMassageFaceTexture},
+	{"AdjustFaceTextureToModel", Nx::ScriptAdjustFaceTextureToModel},
+	{"CombineFaceTextureWithOverlay", Nx::ScriptCombineFaceTextureWithOverlay},
+
+	{"SetLightAmbientColor",  	Nx::ScriptSetLightAmbientColor},
+	{"SetLightDirection",		Nx::ScriptSetLightDirection},
+	{"SetLightDiffuseColor",	Nx::ScriptSetLightDiffuseColor},
+	{"PushWorldLights",			Nx::ScriptPushWorldLights},
+	{"PopWorldLights",			Nx::ScriptPopWorldLights},
+
+	{"SetColorBufferClear",		CFuncs::ScriptSetColorBufferClear},
+
+	{"SetEventHandler",			CFuncs::ScriptSetEventHandler},	   
+	{"ClearEventHandler",		CFuncs::ScriptClearEventHandler},
+	{"ClearEventHandlerGroup",	CFuncs::ScriptClearEventHandlerGroup},
+	{"OnExceptionRun",			CFuncs::ScriptOnExceptionRun},
+	{"OnExitRun",				CFuncs::ScriptOnExitRun},
+	
+	{"SetUVWibbleParams",  		CFuncs::ScriptSetUVWibbleParams},
+	{"EnableExplicitUVWibble",	CFuncs::ScriptEnableExplicitUVWibble},
+	{"DisableExplicitUVWibble",	CFuncs::ScriptDisableExplicitUVWibble},
+	{"SetUVWibbleOffsets",		CFuncs::ScriptSetUVWibbleOffsets},
+
+	{"ToggleMetricItem",		CFuncs::ScriptToggleMetricItem},
+	{"ToggleMemMetrics",		CFuncs::ScriptToggleMemMetrics},
+
+	{"KillAllTextureSplats",	CFuncs::ScriptKillAllTextureSplats},
+	
+	{"PreloadModels",			CFuncs::ScriptPreloadModels},
+	{"PreloadPedestrians",		CFuncs::ScriptPreloadPedestrians},
+	{"PreselectRandomPedestrians",		CFuncs::ScriptPreselectRandomPedestrians},
+	{"ReplaceCarTextures",		CFuncs::ScriptReplaceCarTextures},
+
+	{"LoadSound",				CFuncs::ScriptLoadSound},
+    // PlaySound "SoundName" Vol=100 Pitch=100
+	{"PlaySound",				CFuncs::ScriptPlaySound},
+	{"StopSound",				CFuncs::ScriptStopSound},
+	{"StopAllSounds",			CFuncs::ScriptStopAllSounds},
+	{"SetSoundParams",			CFuncs::ScriptSetSoundParams},
+	{"IsSoundPlaying",			CFuncs::ScriptIsSoundPlaying},
+	// 0-100 for zero to 100percent reverb level...
+	{"SetSfxReverb",   			CFuncs::ScriptSetReverb},
+	{"SetSfxVolume",   			CFuncs::ScriptSetVolume},
+	{"SetSfxDropoff",			CFuncs::ScriptSetDropoff},
+	{"LoadTerrainSounds",		CFuncs::ScriptLoadTerrainSounds},
+	{"SetObjectColor",			CFuncs::ScriptSetObjectColor},
+	{"SetSceneColor",			CFuncs::ScriptSetSceneColor},
+	{"CompressVC",				CFuncs::ScriptCompressVC},
+	{"NudgeVC",					CFuncs::ScriptNudgeVC},
+	{"FakeLights",				CFuncs::ScriptFakeLights},
+	{"CenterCamera",			CFuncs::ScriptCenterCamera},
+	// music tracks commands:
+	{"SetMusicVolume",			CFuncs::ScriptSetMusicVolume},
+	{"SetMusicStreamVolume",	CFuncs::ScriptSetMusicStreamVolume},
+	{"PlayTrack",				CFuncs::ScriptPlayTrack},
+	{"PlayMusicStream",			CFuncs::ScriptPlayMusicStream},
+	{"SkipMusicTrack", 			CFuncs::ScriptSkipMusicTrack},
+	{"PauseMusic",				CFuncs::ScriptPauseMusic},
+	{"StopMusic",				CFuncs::ScriptStopMusic},
+	{"SetMusicMode",			CFuncs::ScriptSetMusicMode},
+	{"SetRandomMode",			CFuncs::ScriptSetRandomMode},
+	{"SetMusicLooping",			CFuncs::ScriptSetMusicLooping},
+	{"LoadMusicHeader",			CFuncs::ScriptLoadMusicHeader},
+	{"LoadStreamHeader",		CFuncs::ScriptLoadStreamHeader},
+	{"StreamIsAvailable",		CFuncs::ScriptStreamIsAvailable}, // only can be used in the skateshop!
+	// tracks added to the tracklist get played randomly during the game...
+	{"AddMusicTrack",			CFuncs::ScriptAddMusicTrack},
+	{"ChangeTrackState",		CFuncs::ScriptChangeTrackState},
+    {"GetCurrentTrack",		    CFuncs::ScriptGetCurrentTrack},
+	{"TrackEnabled",			CFuncs::ScriptTrackEnabled},
+	{"MusicIsPaused",			CFuncs::ScriptMusicIsPaused},
+	{"ClearMusicTrackList",		CFuncs::ScriptClearMusicTrackList},
+	{"PlayStream",				CFuncs::ScriptPlayStream},
+	{"StopStream",				CFuncs::ScriptStopStream},
+	{"PauseStream",				CFuncs::ScriptPauseStream},
+	{"SetStreamParams",			CFuncs::ScriptSetStreamParams},
+	{"IsStreamPlaying",			CFuncs::ScriptIsStreamPlaying},
+	{"LoadStreamFrameAmp",		CFuncs::ScriptLoadStreamFrameAmp},
+	{"FreeStreamFrameAmp",		CFuncs::ScriptFreeStreamFrameAmp},
+	{"PlayMovie",				CFuncs::ScriptPlayMovie},
+	
+	{"SetGameType",				Mdl::ScriptSetGameType},
+	{"InTeamGame",				Mdl::ScriptInTeamGame},
+	{"SetNumTeams",				GameNet::Manager::ScriptSetNumTeams},
+	{"GetNumTeams",				GameNet::Manager::ScriptGetNumTeams},
+	{"GetNumPlayersOnTeam",		GameNet::Manager::ScriptGetNumPlayersOnTeam},
+	{"GetMyTeam",				GameNet::Manager::ScriptGetMyTeam},
+	{"JoinTeam",				GameNet::Manager::ScriptJoinTeam},
+	{"GetCollidingPlayerAndTeam", CFuncs::ScriptGetCollidingPlayerAndTeam},
+    {"LobbyCheckKeyboard",      CFuncs::ScriptLobbyCheckKeyboard},
+	{"TestGameType",			Mdl::ScriptTestGameType},
+	{"TestRequestedGameType",	Mdl::ScriptTestRequestedGameType},
+	{"ChangeLevel",				Mdl::ScriptChangeLevel},
+	{"LaunchLevel",				Mdl::ScriptLaunchLevel},
+	{"RequestLevel",			Mdl::ScriptRequestLevel},
+	{"Retry",					Mdl::ScriptRetry},
+	{"LaunchGame",				Mdl::ScriptLaunchGame},
+	{"ResetLevel",				Mdl::ScriptResetLevel},
+	{"FillRankingScreen",		Mdl::ScriptFillRankingScreen},
+	{"InitSkaterHeaps",			Mdl::ScriptInitSkaterHeaps},
+
+	{"NodeExists",				CFuncs::ScriptNodeExists},
+	{"Create",					CFuncs::ScriptCreate},
+	{"CreateFromStructure",		CFuncs::ScriptCreateFromStructure},
+	{"Kill",					CFuncs::ScriptKill},
+	{"Visible",					CFuncs::ScriptVisible},
+	{"Invisible",				CFuncs::ScriptInvisible},
+	{"Shatter",					CFuncs::ScriptShatter},
+
+	{"ToggleSkaterCamMode",				CFuncs::ScriptToggleSkaterCamMode},
+	{"SetSkaterCamLerpReductionTimer",	CFuncs::ScriptSetSkaterCamLerpReductionTimer},
+	
+	{"PlaySkaterCamAnim",				CFuncs::ScriptPlaySkaterCamAnim},
+	{"SetSkaterCamAnimSkippable",		CFuncs::ScriptSetSkaterCamAnimSkippable},
+	{"SetSkaterCamAnimShouldPause",		CFuncs::ScriptSetSkaterCamAnimShouldPause},
+	{"SkaterCamAnimFinished",			CFuncs::ScriptSkaterCamAnimFinished},
+	{"SkaterCamAnimHeld",				CFuncs::ScriptSkaterCamAnimHeld},
+	{"KillSkaterCamAnim",				CFuncs::ScriptKillSkaterCamAnim},
+	{"GetSkaterCamAnimParams",			CFuncs::ScriptGetSkaterCamAnimParams},
+	{"GetCurrentSkaterCamAnimName",		CFuncs::ScriptGetCurrentSkaterCamAnimName},
+
+	{"PlayMovingObjectAnim",		   	CFuncs::ScriptPlayMovingObjectAnim},
+	{"SetMovingObjectAnimSkippable",	CFuncs::ScriptSetMovingObjectAnimSkippable},
+	{"SetMovingObjectAnimShouldPause",	CFuncs::ScriptSetMovingObjectAnimShouldPause},
+	{"MovingObjectAnimFinished",		CFuncs::ScriptMovingObjectAnimFinished},
+	{"MovingObjectAnimHeld",			CFuncs::ScriptMovingObjectAnimHeld},
+	{"KillMovingObjectAnim",			CFuncs::ScriptKillMovingObjectAnim},
+
+	{"PlayCutscene",		   	CFuncs::ScriptPlayCutscene},
+	{"IsMovieQueued",		   	CFuncs::ScriptIsMovieQueued},
+	{"HasMovieStarted",		   	CFuncs::ScriptHasMovieStarted},
+	
+	// (most of the cutscene functions are actually the same
+	// as the movingobjectanim-style functions)
+	{"SetCutsceneSkippable",	CFuncs::ScriptSetSkaterCamAnimSkippable},
+	{"SetCutsceneShouldPause",	CFuncs::ScriptSetSkaterCamAnimShouldPause},
+	{"CutsceneFinished",		CFuncs::ScriptSkaterCamAnimFinished},
+	{"CutsceneHeld",			CFuncs::ScriptSkaterCamAnimHeld},
+	{"KillCutscene",			CFuncs::ScriptKillSkaterCamAnim},
+
+	{"SkaterDebugOn",			CFuncs::ScriptSkaterDebugOn},
+	{"SkaterDebugOff",			CFuncs::ScriptSkaterDebugOff},
+	{"IfDebugOn",				CFuncs::ScriptIfDebugOn},
+	{"IfDebugOff",				CFuncs::ScriptIfDebugOff},
+	{"Bootstrap",				CFuncs::ScriptBootstrap},
+	{"CD",						CFuncs::ScriptCD},
+	{"NotCD",					CFuncs::ScriptNotCD},
+	{"Gunslinger",				CFuncs::ScriptGunslinger},
+	{"CasArtist",				CFuncs::ScriptCasArtist},
+	{"NotCasArtist",			CFuncs::ScriptNotCasArtist},
+	{"SendFlag",				CFuncs::ScriptSendFlag},
+	{"ClearFlag",				CFuncs::ScriptClearFlag},
+	{"QueryFlag",				CFuncs::ScriptQueryFlag},
+	{"SendException",			CFuncs::ScriptFlagException},
+	{"IsAlive",					CFuncs::ScriptCheckIfAlive},
+
+	{"MakeSkaterGoto",			CFuncs::ScriptMakeSkaterGoto},
+	{"MakeSkaterGosub",			CFuncs::ScriptMakeSkaterGosub},
+
+	{"LaunchMenuScreen",		Mdl::ScriptLaunchMenuScreen},
+	{"UseOnePadInFrontEnd",		CFuncs::ScriptDummyCommand},
+	{"SetMenuAutoRepeatTimes",	Mdl::ScriptSetMenuAutoRepeatTimes},
+	{"SetMenuPadMappings",		Mdl::ScriptSetMenuPadMappings},
+	{"SetButtonEventMappings",	Mdl::ScriptSetButtonEventMappings},
+	{"SetAnalogStickActiveForMenus",	Mdl::ScriptSetAnalogStickActiveForMenus},
+
+	{"ControllerPressed",		CFuncs::ScriptControllerPressed},
+	{"ControllerDebounce",		CFuncs::ScriptControllerDebounce},
+	{"GetAnalogueInfo",			CFuncs::ScriptGetAnalogueInfo},
+
+	{"SetScreenMode",			CFuncs::ScriptSetScreenMode},
+	{"ScriptGetScreenMode",		Nx::ScriptGetScreenMode},
+	{"SetScreenModeFromGameMode",	CFuncs::ScriptSetScreenModeFromGameMode},
+	{"Set2DIn3DSpaceParams",		Nx::ScriptSet2DIn3DSpaceParams},
+	
+
+	{"SpawnScript",				CFuncs::ScriptSpawnScript},
+	{"SpawnSound",				CFuncs::ScriptSpawnSound},
+	{"SpawnSkaterScript",		CFuncs::ScriptSpawnSkaterScript},
+	{"KillSpawnedScript",		CFuncs::ScriptKillSpawnedScript},
+	{"PauseSkaters",			CFuncs::ScriptPauseSkaters},
+	{"UnPauseSkaters",			CFuncs::ScriptUnPauseSkaters},
+	{"PauseSkater",				CFuncs::ScriptPauseSkater},
+	{"UnPauseSkater",			CFuncs::ScriptUnPauseSkater},
+
+	{"PauseGame",				CFuncs::ScriptPauseGame},
+	{"UnPauseGame",				CFuncs::ScriptUnPauseGame},
+    {"GameIsPaused",			CFuncs::ScriptIsGamePaused},
+
+	{"PauseObjects",			CFuncs::ScriptPauseObjects},
+	{"UnPauseObjects",			CFuncs::ScriptUnPauseObjects},
+
+	{"PauseSpawnedScripts",		CFuncs::ScriptPauseSpawnedScripts},
+	{"UnPauseSpawnedScripts",	CFuncs::ScriptUnPauseSpawnedScripts},
+
+	// clock related functions
+	{"ResetClock",				CFuncs::ScriptResetClock},
+	{"PadsPluggedIn",			CFuncs::ScriptPadsPluggedIn},
+	{"DoFlash",					CFuncs::ScriptDoFlash},
+
+	{"InPreFile",				File::ScriptInPreFile},
+	{"LoadPreFile",				File::ScriptLoadPreFile},
+	{"UnloadPreFile",			File::ScriptUnloadPreFile},
+	{"IsLoadPreFinished",		File::ScriptIsLoadPreFinished},
+	{"AllLoadPreFinished",		File::ScriptAllLoadPreFinished},
+	{"WaitLoadPre",				File::ScriptWaitLoadPre},
+	{"WaitAllLoadPre",			File::ScriptWaitAllLoadPre},
+	{"LoadPipPre",				Pip::ScriptLoadPipPre},
+	{"UnLoadPipPre",			Pip::ScriptUnloadPipPre},
+	{"DumpPipPreStatus",		Pip::ScriptDumpPipPreStatus},
+	{"StartServer",				CFuncs::ScriptStartServer},
+	{"SpawnCrown",				GameNet::Manager::ScriptSpawnCrown},
+	{"StartCTFGame",			GameNet::Manager::ScriptStartCTFGame},
+	{"EndCTFGame",				GameNet::Manager::ScriptEndCTFGame},
+	{"PlayerTookFlag",			GameNet::Manager::ScriptTookFlag},
+	{"PlayerCapturedFlag",		GameNet::Manager::ScriptCapturedFlag},
+	{"PlayerRetrievedFlag",		GameNet::Manager::ScriptRetrievedFlag},
+	{"PlayerHasFlag",			GameNet::Manager::ScriptHasFlag},
+    {"TeamFlagTaken",			GameNet::Manager::ScriptTeamFlagTaken},
+	{"DisplayFlagBaseWarning",	GameNet::Manager::ScriptDisplayFlagBaseWarning},
+	{"FindServers",				CFuncs::ScriptFindServers},
+	{"FoundServers",			GameNet::Manager::ScriptFoundServers},
+	{"JoinServer",				CFuncs::ScriptJoinServer},
+	{"LeaveServer",				CFuncs::ScriptLeaveServer},
+	{"SetNetworkMode",			CFuncs::ScriptSetNetworkMode},
+	{"SetServerMode",			CFuncs::ScriptSetServerMode},
+	{"StartNetworkGame",		GameNet::Manager::ScriptStartNetworkGame},
+	{"NetworkGamePending",		GameNet::Manager::ScriptNetworkGamePending},
+	{"EndNetworkGame", 			GameNet::Manager::ScriptEndNetworkGame},
+	{"EnteredNetworkGame",		GameNet::Manager::ScriptEnteredNetworkGame},
+	{"JustStartedNetGame",		GameNet::Manager::ScriptJustStartedNetGame},
+	{"OverrideGameModeOptions",	Game::ScriptOverrideGameModeOptions},
+
+	{"ResetCamera",				CFuncs::ScriptResetCamera},
+	{"GetValueFromVolume",		CFuncs::ScriptGetValueFromVolume},
+	{"SetSliderValue",			CFuncs::ScriptSetSliderValue},
+	{"SetVolumeFromValue",		CFuncs::ScriptSetVolumeFromValue},
+	
+	{"ResetDefaultAppearance",	CFuncs::ScriptResetDefaultAppearance},
+	{"ResetDefaultTricks",		CFuncs::ScriptResetDefaultTricks},
+	{"ResetDefaultStats",		CFuncs::ScriptResetDefaultStats},
+	{"RandomizeAppearance",		CFuncs::ScriptRandomizeAppearance},
+	{"PrintCurrentAppearance",	CFuncs::ScriptPrintCurrentAppearance},
+	{"SetNeversoftSkater",		CFuncs::ScriptSetNeversoftSkater},
+	{"CurrentProfileIsLocked",	CFuncs::ScriptCurrentProfileIsLocked},
+	{"SetIconTexture",			CFuncs::ScriptSetIconTexture},
+	
+	{"MemViewToggle",           CFuncs::ScriptMemViewToggle},
+
+    {"EnableFog",				CFuncs::ScriptEnableFog},
+    {"DisableFog",				CFuncs::ScriptDisableFog},
+    {"SetFogDistance",			CFuncs::ScriptSetFogDistance},
+    {"SetFogExponent",			CFuncs::ScriptSetFogExponent},
+	{"SetFogColor",				CFuncs::ScriptSetFogColor},
+	
+	{"Change",					CFuncs::ScriptChangeSymbolValue},
+	{"PreferenceEquals",		CFuncs::ScriptPreferenceEquals},
+	{"SetUIFromPreferences",	CFuncs::ScriptSetUIFromPreferences},
+	{"SetUIFromSkaterProfile",	CFuncs::ScriptSetUIFromSkaterProfile},
+	{"SetPreference",			CFuncs::ScriptSetPreference},
+	{"SetPreferencesFromUI",	CFuncs::ScriptSetPreferencesFromUI},
+	{"GetPreferenceString",		CFuncs::ScriptGetPreferenceString},
+	{"GetPreferencePassword",	CFuncs::ScriptGetPreferencePassword},
+	{"GetPreferenceChecksum",	CFuncs::ScriptGetPreferenceChecksum},
+	
+	{"MemPushContext",			CFuncs::ScriptMemPushContext},
+	{"MemPopContext",			CFuncs::ScriptMemPopContext},
+	{"ProfileTasks",			CFuncs::ScriptProfileTasks},
+	{"UseNetworkPreferences",	CFuncs::ScriptUseNetworkPreferences},
+	{"CanChangeDevices",		CFuncs::ScriptCanChangeDevices},
+	{"NeedToTestNetSetup",		CFuncs::ScriptNeedToTestNetSetup},
+	{"TestNetSetup",			CFuncs::ScriptTestNetSetup},
+	{"StopAllScripts",			CFuncs::ScriptStopAllScripts},
+	{"SetMenuElementText",		CFuncs::ScriptSetMenuElementText},
+	{"FirstTimeThisIsCalled",	CFuncs::ScriptFirstTimeThisIsCalled},
+	{"VibrationIsOn",			CFuncs::ScriptVibrationIsOn},
+	{"VibrationOff",			CFuncs::ScriptVibrationOff},
+	{"VibrationOn",				CFuncs::ScriptVibrationOn},
+	{"VibrateController",		CFuncs::ScriptVibrateController},
+	{"EnableActuators",			CFuncs::ScriptEnableActuators},
+	{"AutoKickIsOn",			CFuncs::ScriptAutoKickIsOn},
+	{"AutoKickOn",				CFuncs::ScriptAutoKickOn},
+	{"AutoKickOff",				CFuncs::ScriptAutoKickOff},
+	{"SpinTapsAreOn",			CFuncs::ScriptSpinTapsAreOn},
+	{"SpinTapsOn",				CFuncs::ScriptSpinTapsOn},
+	{"SpinTapsOff",				CFuncs::ScriptSpinTapsOff},
+	{"IsOnline",				CFuncs::ScriptIsOnline},
+	{"ConnectToInternet",		CFuncs::ScriptConnectToInternet},
+	{"CancelConnectToInternet",	CFuncs::ScriptCancelConnectToInternet},
+	{"CancelLogon",				CFuncs::ScriptCancelLogon},
+	{"DisconnectFromInternet",	CFuncs::ScriptDisconnectFromInternet},
+	{"InNetGame",				CFuncs::ScriptInNetGame},
+	{"DumpScripts",				CFuncs::ScriptDumpScripts},
+	{"SetSlomo",				CFuncs::ScriptSetSlomo},
+	{"SetArenaSize",			CFuncs::ScriptSetArenaSize},
+	{"ToggleNetMetrics",		CFuncs::ScriptToggleNetMetrics },
+	{"GetCurrentProDisplayInfo",CFuncs::ScriptGetCurrentProDisplayInfo},
+	{"SetPlayerAppearance",		CFuncs::ScriptSetPlayerAppearance},
+	{"SetPlayerFaceTexture",	CFuncs::ScriptSetPlayerFaceTexture},
+    {"SetPlayerFaceOverlayTexture",	CFuncs::ScriptSetPlayerFaceOverlayTexture},
+	{"ClearPlayerFaceTexture",	CFuncs::ScriptClearPlayerFaceTexture},
+	{"GetPlayerFacePoints",		CFuncs::ScriptGetPlayerFacePoints},
+	{"SetPlayerFacePoints",		CFuncs::ScriptSetPlayerFacePoints},
+	{"PlayerFaceIsValid",		CFuncs::ScriptPlayerFaceIsValid},
+	{"SelectCurrentSkater",		CFuncs::ScriptSelectCurrentSkater},
+	{"SetParticleSysVisibility",CFuncs::ScriptSetParticleSysVisibility},
+	{"TogglePlayerNames",		CFuncs::ScriptTogglePlayerNames},
+	{"SetCurrentGameType",		CFuncs::ScriptSetCurrentGameType},
+	{"DumpNetMessageStats",		CFuncs::ScriptDumpNetMessageStats},
+	{"NotifyBailDone",			CFuncs::ScriptNotifyBailDone},
+	{"ClearPowerups",			CFuncs::ScriptClearPowerups},
+	{"BroadcastProjectile",		CFuncs::ScriptBroadcastProjectile},
+	{"BroadcastEnterVehicle",	CFuncs::ScriptBroadcastEnterVehicle},
+
+	// Loading screen
+	{"DisplayLoadingScreen",	CFuncs::ScriptDisplayLoadingScreen},
+	{"HideLoadingScreen",		CFuncs::ScriptHideLoadingScreen},
+
+	// Loading bar of loading screen
+	{"SetLoadingBarPos",		Nx::ScriptSetLoadingBarPos},
+	{"SetLoadingBarSize",		Nx::ScriptSetLoadingBarSize},
+	{"SetLoadingBarStartColor",	Nx::ScriptSetLoadingBarStartColor},
+	{"SetLoadingBarEndColor",	Nx::ScriptSetLoadingBarEndColor},
+	{"SetLoadingBarBorder",		Nx::ScriptSetLoadingBarBorder},
+	{"SetLoadingBarBorderColor",Nx::ScriptSetLoadingBarBorderColor},
+
+	// park editor, ya munks
+	{"SetParkEditorTimeOfDay",	Ed::ScriptSetParkEditorTimeOfDay},
+	{"GetParkEditorTimeOfDayScript",Ed::ScriptGetParkEditorTimeOfDayScript},
+	{"RebuildParkNodeArray",	Ed::ScriptRebuildParkNodeArray},
+	{"FreeUpMemoryForPlayingPark",Ed::ScriptFreeUpMemoryForPlayingPark},
+	{"CalibrateMemoryGauge",	Ed::ScriptCalibrateMemoryGauge},
+	{"GetParkEditorCursorPos",	Ed::ScriptGetParkEditorCursorPos},
+	{"SwitchToParkEditorCamera",Ed::ScriptSwitchToParkEditorCamera},
+	{"SetParkEditorState",		Ed::ScriptSetParkEditorState},
+	{"SetParkEditorPauseMode",	Ed::ScriptSetParkEditorPauseMode},
+	{"SetParkEditorMaxPlayers",	Ed::ScriptSetParkEditorMaxPlayers},
+	{"GetParkEditorMaxPlayers",	Ed::ScriptGetParkEditorMaxPlayers},
+	{"GetParkEditorMaxPlayersPossible",Ed::ScriptGetParkEditorMaxPlayersPossible},
+	{"CustomParkMode",			Ed::ScriptCustomParkMode},
+	{"CanCleanlyResizePark",	Ed::ScriptCanCleanlyResizePark},
+	#ifdef __PLAT_NGC__
+	{"WriteCompressedMapBuffer",Ed::ScriptWriteCompressedMapBuffer},
+	{"RequiresDefragment",		Ed::ScriptRequiresDefragment},
+	{"TopDownHeapTooLow",		Ed::ScriptTopDownHeapTooLow},
+	#endif
+	{"ResizePark",				Ed::ScriptResizePark},
+	{"GetCurrentParkBounds",	Ed::ScriptGetCurrentParkBounds},
+	{"CanChangeParkDimension", 	Ed::ScriptCanChangeParkDimension},
+	{"SaveParkToDisk",			Ed::ScriptSaveParkToDisk},
+	{"LoadParkFromDisk",		Ed::ScriptLoadParkFromDisk},
+	{"IsParkUnsaved",			Ed::ScriptIsParkUnsaved},
+	{"FireCustomParkGap",		Ed::ScriptFireCustomParkGap},
+	{"SetEditedParkGapInfo",	Ed::ScriptSetEditedParkGapInfo},
+	{"GetEditedParkGapName",	Ed::ScriptGetEditedParkGapName},
+	{"ParkEditorSelectionAreaTooBigToCopy",Ed::ScriptParkEditorSelectionAreaTooBigToCopy},
+	{"CopyParkEditorSelectionToClipboard",Ed::ScriptCopyParkEditorSelectionToClipboard},
+	{"SwitchParkEditorMenuPieceToMostRecentClipboard",Ed::ScriptSwitchParkEditorMenuPieceToMostRecentClipboard},
+	{"CutParkEditorAreaSelection",Ed::ScriptCutParkEditorAreaSelection},
+	{"ParkEditorAreaSelectionDeletePieces",Ed::ScriptParkEditorAreaSelectionDeletePieces},
+	{"ContinueParkEditorAreaSelection",	Ed::ScriptContinueParkEditorAreaSelection},
+	{"GetEditorTheme",			Ed::ScriptGetEditorTheme},
+	{"SetEditorTheme",			Ed::ScriptSetEditorTheme},
+	{"GetEditorMaxThemes",		Ed::ScriptGetEditorMaxThemes},
+	{"GetCustomParkName",		Ed::ScriptGetCustomParkName},
+	{"SetCustomParkName",		Ed::ScriptSetCustomParkName},
+	{"BindParkEditorToController",	Ed::ScriptBindParkEditorToController},
+	{"ParkEditorCommand",		CFuncs::ScriptDummyCommand},
+	{"ParkEditorGetGapName",	CFuncs::ScriptDummyCommand},
+	{"ParkEditorSetGapName",	CFuncs::ScriptDummyCommand},
+	{"IsCustomPark",			Ed::ScriptIsCustomPark},
+	{"IsUnsavedPark",			CFuncs::ScriptDummyCommand},
+	
+	{"ObserveNextSkater",		CFuncs::ScriptObserveNextSkater},
+
+	// Prints the current memory usage of the bottom up heap
+	{"PrintMemInfo",			CFuncs::ScriptPrintMemInfo},
+	{"DisplayFreeMem",      	CFuncs::ScriptDisplayFreeMem},
+	{"AnalyzeHeap",				CFuncs::ScriptAnalyzeHeap},
+	{"SetMemThreadSafe",		CFuncs::ScriptMemThreadSafe},
+	
+	{"CareerStartLevel",		CFuncs::ScriptCareerStartLevel},
+	{"CareerLevelIs",			CFuncs::ScriptCareerLevelIs},
+	{"CareerReset",				CFuncs::ScriptCareerReset},
+	{"SetGoal",               	CFuncs::ScriptSetGoal},        
+	{"UnSetGoal",               CFuncs::ScriptUnSetGoal},        
+	{"GetGoal",               	CFuncs::ScriptGetGoal},        
+	{"JustGotGoal",           	CFuncs::ScriptJustGotGoal},    
+	{"SetFlag",               	CFuncs::ScriptSetFlag},        
+	{"UnSetFlag",               CFuncs::ScriptUnSetFlag},        
+	{"GetFlag",               	CFuncs::ScriptGetFlag},        
+	{"JustGotFlag",           	CFuncs::ScriptJustGotFlag},
+	{"SetScoreGoal",			CFuncs::ScriptSetScoreGoal},			
+	{"ClearScoreGoals",			CFuncs::ScriptClearScoreGoals},			
+
+	{"SetGlobalFlag",           CFuncs::ScriptSetGlobalFlag},        
+	{"UnSetGlobalFlag",         CFuncs::ScriptUnSetGlobalFlag},        
+	{"GetGlobalFlag",           CFuncs::ScriptGetGlobalFlag},        
+	
+	// Career-related functions...  these refer to skater 0,
+	// so don't try to use these functions from a trick script.
+	{"ProfileEquals",			CFuncs::ScriptProfileEquals},
+	{"IsCareerMode",			CFuncs::ScriptIsCareerMode},
+	{"EndRun",					CFuncs::ScriptEndRun},
+	{"AllowPause",				CFuncs::ScriptAllowPause},
+
+	{"EnterObserverMode",		CFuncs::ScriptEnterObserverMode},
+	{"EnterSurveyorMode",		GameNet::Manager::ScriptEnterSurveyorMode},
+	{"ExitSurveyorMode",		GameNet::Manager::ScriptExitSurveyorMode},
+	
+	{"RefreshServerList",		CFuncs::ScriptRefreshServerList},
+	{"SetJoinMode",				GameNet::Manager::ScriptSetJoinMode},
+	{"SetHostMode",				GameNet::Manager::ScriptSetHostMode},
+	{"IsHost",					GameNet::Manager::ScriptIsHost},
+	{"IsAutoServing",			GameNet::Manager::ScriptIsAutoServing},
+	{"ChangeLevelPending",		GameNet::Manager::ScriptChangeLevelPending},
+	{"ToggleProSet",			GameNet::Manager::ScriptToggleProSet},
+	{"ResetProSetFlags",		GameNet::Manager::ScriptResetProSetFlags},
+		
+	// Some gameflow-related tests
+	{"PauseGameFlow",			CFuncs::ScriptPauseGameFlow},
+	{"UnpauseGameFlow",			CFuncs::ScriptUnpauseGameFlow},
+	{"InFrontEnd",				CFuncs::ScriptInFrontEnd},
+	{"GetFireballLevel",		CFuncs::ScriptGetFireballLevel},
+	{"InSplitScreenGame",		CFuncs::ScriptInSplitScreenGame},
+	{"GameModeEquals",			CFuncs::ScriptGameModeEquals},
+	{"OnServer",				CFuncs::ScriptOnServer},
+	{"ShouldEndRun",			CFuncs::ScriptShouldEndRun},
+	{"InitializeSkaters",		CFuncs::ScriptInitializeSkaters},
+	{"SkatersAreReady",			CFuncs::ScriptSkatersAreReady},
+	{"RestartLevel",			CFuncs::ScriptRestartLevel},
+	{"EndRunSelected",			CFuncs::ScriptEndRunSelected},
+	{"AllSkatersAreIdle",		CFuncs::ScriptAllSkatersAreIdle},
+	{"FirstTrickStarted",		CFuncs::ScriptFirstTrickStarted},
+	{"FirstTrickCompleted",		CFuncs::ScriptFirstTrickCompleted},
+	{"CalculateFinalScores",	CFuncs::ScriptCalculateFinalScores},
+	{"ReinsertSkaters",			CFuncs::ScriptReinsertSkaters},
+	{"UnhookSkaters",			CFuncs::ScriptUnhookSkaters},
+	{"ToggleScores",			CFuncs::ScriptToggleScores},
+	{"ApplySplitScreenOptions",	CFuncs::ScriptApplySplitScreenOptions},
+	{"ApplyToSkaterProfile",	CFuncs::ScriptApplyToSkaterProfile},
+	{"RefreshSkaterColors",		CFuncs::ScriptRefreshSkaterColors},
+	{"RefreshSkaterScale",		CFuncs::ScriptRefreshSkaterScale},
+	{"RefreshSkaterVisibility", CFuncs::ScriptRefreshSkaterVisibility},
+	{"RefreshSkaterUV", 		CFuncs::ScriptRefreshSkaterUV},
+	{"IsTrue",					CFuncs::ScriptIsTrue},
+	{"InitSkaterModel",			CFuncs::ScriptInitSkaterModel},
+	{"RefreshSkaterModel",		CFuncs::ScriptRefreshSkaterModel},
+	{"EditPlayerAppearance",	CFuncs::ScriptEditPlayerAppearance},
+	{"GetCurrentSkaterProfileIndex", CFuncs::ScriptGetCurrentSkaterProfileIndex},
+    {"GetCustomSkaterName",     CFuncs::ScriptGetCustomSkaterName},
+
+	{"GetCurrentLevel",			CFuncs::ScriptGetCurrentLevel},
+
+	{"StartCompetition",		CFuncs::ScriptStartCompetition},
+	{"EndCompetition",			CFuncs::ScriptEndCompetition},
+	{"CompetitionEnded",		CFuncs::ScriptCompetitionEnded},
+	{"StartCompetitionRun",		CFuncs::ScriptStartCompetitionRun},
+	{"EndCompetitionRun",		CFuncs::ScriptEndCompetitionRun},
+
+	{"StartHorse",				CFuncs::ScriptStartHorse},
+	{"EndHorse",				CFuncs::ScriptEndHorse},
+	{"HorseEnded",				CFuncs::ScriptHorseEnded},
+	{"HorseStatusEquals",		CFuncs::ScriptHorseStatusEquals},
+	{"StartHorseRun",			CFuncs::ScriptStartHorseRun},
+	{"EndHorseRun",				CFuncs::ScriptEndHorseRun},
+	{"SwitchHorsePlayers",		CFuncs::ScriptSwitchHorsePlayers},
+	{"GetHorseString",			CFuncs::ScriptGetHorseString},
+	{"IsCurrentHorseSkater",	CFuncs::ScriptIsCurrentHorseSkater},
+	{"ApplyToHorsePanelString", CFuncs::ScriptApplyToHorsePanelString},
+	
+	{"XTriggered",				CFuncs::ScriptXTriggered},
+	{"UsePad",					CFuncs::ScriptUsePad},
+
+	{"InMultiplayerGame",		CFuncs::ScriptInMultiplayerGame},	
+	{"IsTopJudge",				CFuncs::ScriptIsTopJudge},
+	{"PlaceIs",					CFuncs::ScriptPlaceIs},
+	{"RoundIs",					CFuncs::ScriptRoundIs},
+	
+	{"GameFlow",				CFuncs::ScriptGameFlow},	
+	{"AwardStatPoint",			CFuncs::ScriptAwardStatPoint},
+	{"AwardSpecialTrickSlot",	CFuncs::ScriptAwardSpecialTrickSlot},
+	{"UpdateSkaterStats",		CFuncs::ScriptUpdateSkaterStats},
+	
+	{"GoalsGreaterThan",        CFuncs::ScriptGoalsGreaterThan},    
+	{"GoalsEqualTo",            CFuncs::ScriptGoalsEqualTo},
+	{"MedalsGreaterThan",       CFuncs::ScriptMedalsGreaterThan},   
+	{"MedalsEqualTo",           CFuncs::ScriptMedalsEqualTo},       
+	{"StartServerList",			CFuncs::ScriptStartServerList},
+	{"StartLobbyList",			CFuncs::ScriptStartLobbyList},
+	{"NumServersInLobby",		GameNet::Manager::ScriptGetNumServersInLobby},
+#ifdef __PLAT_NGPS__
+	{"NumPlayersInLobby",		GameNet::LobbyMan::ScriptGetNumPlayersInLobby},
+	{"RejoinLobby",				GameNet::LobbyMan::ScriptRejoinLobby},
+	{"ChooseLobby",				GameNet::LobbyMan::ScriptChooseLobby},
+	{"LeaveLobby",				GameNet::LobbyMan::ScriptLeaveLobby},
+	{"LobbyConnect",			GameNet::LobbyMan::ScriptLobbyConnect},
+	{"LobbyDisconnect",			GameNet::LobbyMan::ScriptLobbyDisconnect},
+	{"FillLobbyProspectiveBuddyList",	GameNet::LobbyMan::ScriptFillLobbyProspectiveBuddyList},
+	{"StartNatNegotiation",		GameNet::LobbyMan::ScriptStartNatNegotiation},
+	{"CancelNatNegotiation",	GameNet::LobbyMan::ScriptCancelNatNegotiation},
+	
+	{"CanHostGame",             GameNet::LobbyMan::ScriptCanHostGame},
+	{"SendMessage",				GameNet::LobbyMan::ScriptSendMessage},
+	{"SetQuietMode",			GameNet::LobbyMan::ScriptSetQuietMode},
+	{"FillPlayerList",			GameNet::LobbyMan::ScriptFillPlayerList},
+	{"LoadNetConfigs",			GameNet::Manager::ScriptLoadNetConfigs},
+	{"NoNetConfigFiles",		GameNet::Manager::ScriptNoNetConfigFiles},
+	{"FillNetConfigList",		GameNet::Manager::ScriptFillNetConfigList},
+	{"ChooseNetConfig",			GameNet::Manager::ScriptChooseNetConfig},
+	{"FillPlayerListMenu",		GameNet::Manager::ScriptFillPlayerListMenu},
+	{"DownloadFace",			GameNet::ContentMan::ScriptDownloadFace},
+	{"UploadFile",				GameNet::ContentMan::ScriptUploadFile},
+	{"DownloadFile",			GameNet::ContentMan::ScriptDownloadFile},
+	{"DownloadDirectoryList",	GameNet::ContentMan::ScriptDownloadDirectoryList},
+	{"FreeDirectoryListing",	GameNet::ContentMan::ScriptFreeDirectoryListing},
+	{"FillVaultMenu",			GameNet::ContentMan::ScriptFillVaultMenu},
+	{"NextVaultCategory",		GameNet::ContentMan::ScriptNextVaultCategory},
+	{"PrevVaultCategory",		GameNet::ContentMan::ScriptPrevVaultCategory},
+	{"StatsLoggedIn",			GameNet::StatsMan::ScriptStatsLoggedIn},
+	{"StatsLogIn",				GameNet::StatsMan::ScriptStatsLogIn},
+	{"StatsLogOff",				GameNet::StatsMan::ScriptStatsLogOff},
+	{"ReportStats",				GameNet::StatsMan::ScriptReportStats},
+	{"RetrievePersonalStats",	GameNet::StatsMan::ScriptRetrievePersonalStats},
+	{"RetrieveTopStats",		GameNet::StatsMan::ScriptRetrieveTopStats},
+	{"NeedToRetrieveTopStats",	GameNet::StatsMan::ScriptNeedToRetrieveTopStats},
+	{"FillStatsArrays",			GameNet::StatsMan::ScriptFillStatsArrays},
+	{"GetRank",					GameNet::StatsMan::ScriptGetRank},
+	{"CleanUpTopStats",			GameNet::StatsMan::ScriptCleanUpTopStats},
+	{"ProfileLoggedIn",			GameNet::BuddyMan::ScriptProfileLoggedIn},
+	{"SetUniqueId",				GameNet::BuddyMan::ScriptSetUniqueId},
+	{"CreateProfile",			GameNet::BuddyMan::ScriptCreateProfile},
+	{"ProfileLogIn",			GameNet::BuddyMan::ScriptProfileLogIn},
+	{"ProfileLogOff",			GameNet::BuddyMan::ScriptProfileLogOff},
+	{"FillBuddyList",			GameNet::BuddyMan::ScriptFillBuddyList},
+	{"FillProspectiveBuddyList",GameNet::BuddyMan::ScriptFillProspectiveBuddyList},
+	{"AddBuddy",				GameNet::BuddyMan::ScriptAddBuddy},
+	{"RemoveBuddy",				GameNet::BuddyMan::ScriptRemoveBuddy},
+	{"CancelAddBuddy",			GameNet::BuddyMan::ScriptCancelAddBuddy},
+	{"JoinBuddy",				GameNet::BuddyMan::ScriptJoinBuddy},
+	{"HasBuddies",				GameNet::BuddyMan::ScriptHasBuddies},
+	{"BuddyListFull",			GameNet::BuddyMan::ScriptBuddyListFull},
+	{"SetLobbyStatus",			GameNet::BuddyMan::ScriptSetLobbyStatus},
+	{"PostGame",				GameNet::Manager::ScriptPostGame},
+	{"AuthenticateClient",		GameNet::Manager::ScriptAuthenticateClient },
+	{"WriteDNASBinary",			GameNet::Manager::ScriptWriteDNASBinary },
+#endif
+	{"ConnectedToPeer",			GameNet::Manager::ScriptConnectedToPeer },
+	{"InGroupRoom",				GameNet::Manager::ScriptInGroupRoom},
+	
+	{"ChooseServer", 			GameNet::Manager::ScriptChooseServer},
+	{"DescribeServer",			GameNet::Manager::ScriptDescribeServer},
+	{"RetrieveServerInfo",		GameNet::Manager::ScriptRetrieveServerInfo},
+	{"CancelJoinServer", 		GameNet::Manager::ScriptCancelJoinServer},
+	{"ReattemptJoinServer",		GameNet::Manager::ScriptReattemptJoinServer},
+	{"CreatePlayerOptions",     GameNet::Manager::ScriptCreatePlayerOptions},
+	{"AllPlayersAreReady",      GameNet::Manager::ScriptAllPlayersAreReady},
+	
+	{"ChooseAccount",			GameNet::Manager::ScriptChooseAccount},
+	{"RemovePlayer",			GameNet::Manager::ScriptRemovePlayer},
+	{"CancelRemovePlayer",		GameNet::Manager::ScriptCancelRemovePlayer},
+	{"KickPlayer",				GameNet::Manager::ScriptKickPlayer},
+	{"BanPlayer",				GameNet::Manager::ScriptBanPlayer},
+	{"FCFSRequestStartGame",	GameNet::Manager::ScriptFCFSRequestStartGame},
+	{"FCFSRequestChangeLevel",	GameNet::Manager::ScriptFCFSRequestChangeLevel},
+	{"FCFSRequestToggleProSet", GameNet::Manager::ScriptFCFSRequestToggleProSet},
+	{"FCFSRequestToggleGoalSelection",	GameNet::Manager::ScriptFCFSRequestToggleGoalSelection},
+	{"HasSignedDisclaimer",		GameNet::Manager::ScriptHasSignedDisclaimer},
+	
+	// "Omnigon" is code for "cheat". Just another attempt to NOT lead hackers directly to the
+	// pile of gold
+	{"ClearOmnigons",			CFuncs::ScriptClearCheats},
+	{"BroadcastOmnigon",		CFuncs::ScriptBroadcastCheat},
+	{"LastBroadcastedOmnigonWas",CFuncs::ScriptLastBroadcastedCheatWas},
+	{"OmnigonAllowed",			CFuncs::ScriptCheatAllowed},
+
+	{"JoinWithPassword",		CFuncs::ScriptJoinWithPassword},
+	{"GetRecordText",			CFuncs::ScriptGetRecordText},
+	{"UpdateRecords",			CFuncs::ScriptUpdateRecords},
+	{"SendChatMessage",			CFuncs::ScriptSendChatMessage},
+	{"InSlapGame",				CFuncs::ScriptInSlapGame},
+	{"LoadPendingPlayers",		CFuncs::ScriptLoadPendingPlayers},
+	{"DropPendingPlayers",		GameNet::Manager::ScriptDropPendingPlayers},
+	{"StopServerList",			CFuncs::ScriptStopServerList},
+	{"FreeServerList",			CFuncs::ScriptFreeServerList},
+	{"LaunchQueuedScripts",     CFuncs::ScriptLaunchQueuedScripts},
+	{"IsObserving",				CFuncs::ScriptIsObserving},
+	{"GetInitialsString",		CFuncs::ScriptGetInitialsString},
+	{"SetInitialsString",		CFuncs::ScriptSetInitialsString},
+	{"UpdateInitials",			CFuncs::ScriptUpdateInitials},
+	{"NewRecord",				CFuncs::ScriptNewRecord},
+	{"AttachToSkater",			CFuncs::ScriptAttachToSkater},
+	{"TryCheatString",			CFuncs::ScriptTryCheatString},
+	{"LevelIs",					CFuncs::ScriptLevelIs},
+	{"StartNetworkLobby",		CFuncs::ScriptStartNetworkLobby},
+	{"ObserversAllowed",		CFuncs::ScriptObserversAllowed},
+	{"NumPlayersAllowed",		CFuncs::ScriptNumPlayersAllowed},
+	{"AutoDNS",					CFuncs::ScriptAutoDNS},
+	{"UsingDefaultMasterServers",CFuncs::ScriptUsingDefaultMasterServers},
+	{"UsingDHCP",				CFuncs::ScriptUsingDHCP},
+	{"InInternetMode",			CFuncs::ScriptInInternetMode},
+	{"LoadExecPS2",				CFuncs::ScriptLoadExecPS2},
+	{"ExitDemo",				CFuncs::ScriptExitDemo},
+	{"SendGameOverToObservers", GameNet::Manager::ScriptSendGameOverToObservers},
+	{"GameIsOver", 				CFuncs::ScriptGameIsOver},
+	{"EnteringNetGame",			CFuncs::ScriptEnteringNetGame},
+	{"DeviceChosen", 			CFuncs::ScriptDeviceChosen},
+	{"SetLevelName",			CFuncs::ScriptSetLevelName},
+	{"TrickOffAllObjects",		CFuncs::ScriptTrickOffAllObjects},
+	{"DumpHeaps",				CFuncs::ScriptDumpHeaps},
+	{"DumpFragments",			CFuncs::ScriptDumpFragments},
+	{"ResetPS2",				CFuncs::ScriptResetPS2},
+	{"ResetHD",					CFuncs::ScriptResetHD},
+	{"PAL",						CFuncs::ScriptPAL},
+	{"English",					CFuncs::ScriptEnglish},
+	{"TimeUp",					CFuncs::ScriptTimeUp},
+	{"LaunchEvent",				Obj::ScriptLaunchEvent},
+	{"FireEvent",				Obj::ScriptLaunchEvent},	// LaunchEvent and FireEvent are the same
+	{"PrintEventLog",			Obj::ScriptPrintEventLog},
+	{"ObjectExists",			Obj::ScriptObjectExists},
+	{"TerminateObjectsScripts",	Obj::ScriptTerminateObjectsScripts},
+	{"AssignAlias",				Obj::ScriptAssignAlias},
+	{"SetObjectProps",			Obj::ScriptSetObjectProperties},
+	{"CreateScreenElement",		Front::ScriptCreateScreenElement},
+	{"DestroyScreenElement",	Front::ScriptDestroyScreenElement},
+	{"RunScriptOnScreenElement",	Front::ScriptRunScriptOnScreenElement},
+	{"SetScreenElementProps",	Front::ScriptSetScreenElementProps},
+	{"GetScreenElementProps",	Front::ScriptGetScreenElementProps},
+	{"DoScreenElementMorph",	Front::ScriptDoScreenElementMorph},
+	{"SetScreenElementLock",	Front::ScriptSetScreenElementLock},
+	{"ScreenElementSystemInit",		Front::ScriptScreenElementSystemInit},
+	{"TextElementConcatenate",	Front::ScriptTextElementConcatenate},
+	{"TextElementBackspace",	Front::ScriptTextElementBackspace},
+	{"GetTextElementString",	Front::ScriptGetTextElementString},
+	{"GetTextElementLength",	Front::ScriptGetTextElementLength},
+	{"GetScreenElementDims", 	Front::ScriptGetScreenElementDims},
+	{"GetScreenElementPosition", Front::ScriptGetScreenElementPosition},
+	{"LaunchViewer",			CFuncs::ScriptLaunchViewer},
+	{"LaunchScriptDebugger",	CFuncs::ScriptLaunchScriptDebugger},
+	{"SetViewerModel",			CFuncs::ScriptSetViewerModel},
+	{"SetViewerAnim",			CFuncs::ScriptSetViewerAnim},
+	{"SetViewerLODDist",		CFuncs::ScriptSetViewerLODDist},
+	{"ReloadViewerAnim",		CFuncs::ScriptReloadViewerAnim},
+	{"ReloadSkaterCamAnim",		CFuncs::ScriptReloadSkaterCamAnim},
+	{"SetScoreDegradation",		CFuncs::ScriptGameModeSetScoreDegradation},
+	{"SetScoreAccumulation",	CFuncs::ScriptGameModeSetScoreAccumulation},
+	{"ResetScore",				CFuncs::ScriptResetScore},
+	{"ResetScorePot", 			CFuncs::ScriptResetScorePot},
+	{"ResetScoreDegradation",	CFuncs::ScriptResetScoreDegradation},
+	{"UpdateScore",				CFuncs::ScriptUpdateScore},
+	{"AddRestartsToMenu",		CFuncs::ScriptAddRestartsToMenu},
+	{"AddGapsToMenu",			Obj::ScriptAddGapsToMenu},
+	{"CreateGapList",			Obj::ScriptCreateGapList},
+	{"GetLevelGapTotals",		Obj::ScriptGetLevelGapTotals},
+	{"GiveAllGaps",				Obj::ScriptGiveAllGaps},
+    {"SetTimeLimit",            Mdl::ScriptSetTimeLimit},
+	{"MenuSelectedIndexIs",		Front::ScriptMenuSelectedIndexIs},
+	{"ScreenElementExists",		Front::ScriptScreenElementExists},
+	{"SetRootScreenElement",	Front::ScriptSetRootScreenElement},
+
+	// Goal-related stuff
+	{"GoalManager_AddGoal",				Game::ScriptAddGoal},
+	{"GoalManager_RemoveGoal",			Game::ScriptRemoveGoal},
+	{"GoalManager_EditGoal",			Game::ScriptEditGoal},
+	{"GoalManager_ActivateGoal",		Game::ScriptActivateGoal},
+	{"GoalManager_DeactivateGoal",		Game::ScriptDeactivateGoal},
+	{"GoalManager_ClearLastGoal",		Game::ScriptClearLastGoal},
+	{"GoalManager_WinGoal",				Game::ScriptWinGoal},
+	{"GoalManager_LoseGoal",			Game::ScriptLoseGoal},
+	{"GoalManager_RemoveAllGoals",		Game::ScriptRemoveAllGoals},
+	{"GoalManager_DeactivateAllGoals",	Game::ScriptDeactivateAllGoals},
+	{"GoalManager_UninitializeAllGoals",Game::ScriptUninitializeAllGoals},
+	{"GoalManager_UninitializeGoal",	Game::ScriptUninitializeGoal},
+	{"GoalManager_InitializeAllGoals",	Game::ScriptInitializeAllGoals},
+	{"GoalManager_InitializeGoal",		Game::ScriptInitializeGoal},
+	{"GoalManager_GoalInitialized",		Game::ScriptGoalInitialized},
+	{"GoalManager_InitializeAllSelectedGoals", Game::ScriptInitializeAllSelectedGoals},
+	{"GoalManager_InitializeAllMinigames", Game::ScriptInitializeAllMinigames},
+	{"GoalManager_DeactivateAllMinigames", Game::ScriptDeactivateAllMinigames},
+	{"GoalManager_DeactivateCurrentGoal", Game::ScriptDeactivateCurrentGoal},
+	{"GoalManager_UpdateAllGoals",		Game::ScriptUpdateAllGoals},
+	{"GoalManager_HasActiveGoals",		Game::ScriptHasActiveGoals},
+	{"GoalManager_GoalIsActive",		Game::ScriptGoalIsActive},
+	{"GoalManager_SetGoalTimer",		Game::ScriptSetGoalTimer},
+	{"GoalManager_ZeroGoalTimer",		Game::ScriptZeroGoalTimer},
+	{"GoalManager_SetGoalFlag",			Game::ScriptSetGoalFlag},
+    {"GoalManager_HasSeenGoal",         Game::ScriptHasSeenGoal},
+    {"GoalManager_PauseAllGoals",       Game::ScriptPauseAllGoals},
+    {"GoalManager_UnPauseAllGoals",     Game::ScriptUnPauseAllGoals},
+    {"GoalManager_RestartLastGoal",     Game::ScriptRestartLastGoal},
+    // {"GoalManager_CreateGoalFlag",      Game::ScriptCreateGoalFlag},
+    {"GoalManager_AllFlagsSet",         Game::ScriptAllFlagsSet},
+	{"GoalManager_GoalFlagSet",			Game::ScriptGoalFlagSet},
+    {"GoalManager_CanRetryGoal",        Game::ScriptCanRetryGoal},      
+    {"GoalManager_GetGoalParams",       Game::ScriptGetGoalParams},
+    {"GoalManager_CanStartGoal",        Game::ScriptCanStartGoal},
+    {"GoalManager_NextHorseSpot",       Game::ScriptNextTourSpot},
+	{"GoalManager_NextTourSpot",		Game::ScriptNextTourSpot},
+    {"GoalManager_NextRaceWaypoint",    Game::ScriptNextRaceWaypoint},
+    {"GoalManager_CreateGoalName",      Game::ScriptCreateGoalName},
+	{"GoalManager_GetLevelPrefix",		Game::ScriptGetLevelPrefix},
+    {"GoalManager_SetGraffitiMode",     Game::ScriptSetGraffitiMode},
+    {"GoalManager_HasWonGoal",          Game::ScriptHasWonGoal},
+    {"GoalManager_GotCounterObject",    Game::ScriptGotCounterObject},
+    {"GoalManager_CounterGoalDone",     Game::ScriptCounterGoalDone},
+	{"GoalManager_ClearGoalPoints",		Game::ScriptClearGoalPoints},
+    {"GoalManager_HasGoalPoints",       Game::ScriptHasGoalPoints},
+    {"GoalManager_SpendGoalPoints",     Game::ScriptSpendGoalPoints},
+    {"GoalManager_GetNumberOfGoalPoints", Game::ScriptGetNumberOfGoalPoints},
+    {"GoalManager_GetCash",             Game::ScriptGetCash},
+    {"GoalManager_SpendCash",           Game::ScriptSpendCash},
+    {"GoalManager_AddCash",             Game::ScriptAddCash},
+    {"GoalManager_HasBeatenGoalWithProset", Game::ScriptHasBeatenGoalWithProset},
+    {"GoalManager_GetProsetNotPrefix",  Game::ScriptGetProsetNotPrefix},
+    {"GoalManager_LevelUnload",         Game::ScriptLevelUnload},
+    {"GoalManager_LevelLoad",           Game::ScriptLevelLoad},
+    {"GoalManager_NumGoalsBeatenInLevel",  Game::ScriptNumGoalsBeatenInLevel},
+    {"GoalManager_UnlockGoal",			Game::ScriptUnlockGoal},
+	{"GoalManager_UnlockAllGoals",      Game::ScriptUnlockAllGoals},
+	{"GoalManager_TurnPro",				Game::ScriptTurnPro},
+	{"GoalManager_CheckMinigameRecord", Game::ScriptCheckMinigameRecord},
+	{"GoalManager_SetStartTime",		Game::ScriptSetStartTime},
+	{"GoalManager_UpdateComboTimer", 	Game::ScriptUpdateComboTimer},
+	{"GoalManager_AddMinigameTime",		Game::ScriptAddMinigameTime},
+	{"GoalManager_SetStartHeight", 		Game::ScriptSetStartHeight},
+	{"GoalManager_CheckHeightRecord", 	Game::ScriptCheckHeightRecord},
+	{"GoalManager_CheckDistanceRecord",	Game::ScriptCheckDistanceRecord},
+/*	
+	{"GoalManager_ShowGoalPoints", 		Game::ScriptShowGoalPoints},
+	{"GoalManager_HideGoalPoints",		Game::ScriptHideGoalPoints},
+	{"GoalManager_ShowPoints",			Game::ScriptShowPoints},
+	{"GoalManager_HidePoints",			Game::ScriptHidePoints},
+*/
+	{"GoalManager_AddGoalPoint",		Game::ScriptAddGoalPoint},
+	{"GoalManager_PlayGoalStartStream", Game::ScriptPlayGoalStartStream},
+	{"GoalManager_PlayGoalWinStream", 	Game::ScriptPlayGoalWinStream},
+	{"GoalManager_StopCurrentStream",	Game::ScriptStopCurrentStream},
+	{"GoalManager_PlayGoalWaitStream",	Game::ScriptPlayGoalWaitStream},
+	{"GoalManager_PauseGoal",			Game::ScriptPauseGoal},
+	{"GoalManager_UnPauseGoal",			Game::ScriptUnPauseGoal},
+	{"GoalManager_PauseCompetition",	Game::ScriptPauseCompetition},
+	{"GoalManager_UnPauseCompetition",	Game::ScriptUnPauseCompetition},
+	{"GoalManager_UnBeatAllGoals",		Game::ScriptUnBeatAllGoals},
+	{"GoalManager_AddViewGoalsList",	Game::ScriptAddViewGoalsList},
+	{"GoalManager_GoalIsLocked",		Game::ScriptGoalIsLocked},
+	{"GoalManager_IsInCompetition",		Game::ScriptIsInCompetition},
+	{"GoalManager_GetGoalAnimations",	Game::ScriptGetGoalAnimations},
+	{"GoalManager_PlayGoalStream",		Game::ScriptPlayGoalStream},
+	{"GoalManager_EndRunCalled",		Game::ScriptEndRunCalled},
+	{"GoalManager_ClearEndRun",			Game::ScriptClearEndRun},
+	{"GoalManager_FinishedEndOfRun",	Game::ScriptFinishedEndOfRun},
+	{"GoalManager_StartedEndOfRun",		Game::ScriptStartedEndOfRun},
+	{"GoalManager_SetShouldDeactivateOnExpire",	Game::ScriptSetShouldDeactivateOnExpire},
+	{"GoalManager_EndBetAttempt",		Game::ScriptEndBetAttempt},
+	{"GoalManager_StartBetAttempt",		Game::ScriptStartBetAttempt},
+	{"GoalManager_WinBet",				Game::ScriptWinBet},
+	{"GoalManager_MoveBettingGuyNow",	Game::ScriptMoveBettingGuyNow},
+	{"GoalManager_BetOffered",			Game::ScriptBetOffered},
+	{"GoalManager_BetAccepted",			Game::ScriptBetAccepted},
+	{"GoalManager_BetRefused",			Game::ScriptBetRefused},
+	{"GoalManager_BetIsActive",			Game::ScriptBetIsActive},
+	{"GoalManager_AddTempSpecialTrick",	Game::ScriptAddTempSpecialTrick},
+	{"GoalManager_RemoveTempSpecialTrick", Game::ScriptRemoveTempSpecialTrick},
+	{"GoalManager_GetTrickFromKeyCombo", Game::ScriptGetTrickFromKeyCombo},
+	{"GoalManager_QuickStartGoal",		Game::ScriptQuickStartGoal},
+	{"GoalManager_AddGoalChoices",		Game::ScriptAddGoalChoices},
+	{"GoalManager_GoalIsSelected",		Game::ScriptGoalIsSelected},
+	{"GoalManager_ToggleGoalSelection",	Game::ScriptToggleGoalSelection},
+	{"GoalManager_GoalsAreSelected",	Game::ScriptGoalsAreSelected},
+	{"GoalManager_AddTime",				Game::ScriptAddTime},
+	{"GoalManager_ReplaceTrickText",	Game::ScriptReplaceTrickText},
+	// {"GoalManager_UnlockProSpecificChallenges",	Game::ScriptUnlockProSpecificChallenges},
+	// {"GoalManager_ProSpecificChallengesUnlocked", Game::ScriptProSpecificChallengesUnlocked},
+	{"GoalManager_GetNumberCollected",	Game::ScriptGetNumberCollected},
+	{"GoalManager_GetNumberOfFlags",	Game::ScriptGetNumberOfFlags},
+	{"GoalManager_IsPro",				Game::ScriptIsPro},
+	{"GoalManager_ResetGoalFlags",		Game::ScriptResetGoalFlags},
+	{"GoalManager_ColorTrickObjects",	Game::ScriptColorTrickObjects},
+	{"GoalManager_GetNumberOfTimesGoalStarted", Game::ScriptGetNumberOfTimesGoalStarted},
+	{"GoalManager_GoalExists",			Game::ScriptGoalExists},
+	{"GoalManager_GetGoalAttemptInfo",	Game::ScriptGetGoalAttemptInfo},
+	{"GoalManager_SetCanStartGoal",		Game::ScriptSetCanStartGoal},
+	{"GoalManager_GetLastGoalId",		Game::ScriptGetLastGoalId},
+	{"GoalManager_ClearTetrisTricks",	Game::ScriptClearTetrisTricks},
+	// {"GoalManager_MarkProSpecificChallengeBeaten", Game::ScriptMarkProSpecificChallengeBeaten},
+	// {"GoalManager_SkaterHasBeatenProSpecificChallenge", Game::ScriptSkaterHasBeatenProSpecificChallenge},
+	{"GoalManager_AnnounceGoalStarted",	Game::ScriptAnnounceGoalStarted},
+	{"GoalManager_SetEndRunType",		Game::ScriptSetEndRunType},
+	{"GoalManager_GetActiveGoalId",		Game::ScriptGetActiveGoalId},
+	{"GoalManager_ResetCareer",			Game::ScriptResetCareer},
+	{"GoalManager_AwardMinigameCash",	Game::ScriptAwardMinigameCash},
+	{"GoalManager_AwardAllGoalCash",	Game::ScriptAwardAllGoalCash},
+	{"GoalManager_UpdateFamilyTrees",	Game::ScriptUpdateFamilyTrees},
+	{"GoalManager_IsLeafNode",			Game::ScriptIsLeafNode},
+	{"GoalManager_IsRootNode",			Game::ScriptIsRootNode},
+	{"GoalManager_SuspendGoalPedLogic",	Game::ScriptSuspendGoalPedLogic},
+	{"GoalManager_RememberLevelStructureName", Game::ScriptRememberLevelStructureName},
+	{"GoalManager_SetDifficultyLevel",	Game::ScriptSetDifficultyLevel},
+	{"GoalManager_GetDifficultyLevel",	Game::ScriptGetDifficultyLevel},
+	// {"GoalManager_GetGoalParam",		Game::ScriptGetGoalParam},
+	{"GoalManager_RestartStage",		Game::ScriptRestartStage},
+	{"GoalManager_CanRestartStage",		Game::ScriptCanRestartStage},
+	{"GoalManager_SetGoalChaptersAndStages", Game::ScriptSetGoalChaptersAndStages},
+	{"GoalManager_AdvanceStage", 		Game::ScriptAdvanceStage},
+	{"GoalManager_GetCurrentChapterAndStage", Game::ScriptGetCurrentChapterAndStage},
+	{"GoalManager_SetCurrentChapterAndStage", Game::ScriptSetCurrentChapterAndStage},
+	{"GoalManager_FilmGoalCheckpoint",	Game::ScriptFilmGoalCheckpoint},
+	{"GoalManager_StartFilming",		Game::ScriptStartFilming},
+	{"GoalManager_GoalShouldExpire",	Game::ScriptGoalShouldExpire},
+	{"GoalManager_SetSponsor",			Game::ScriptSetSponsor},
+	{"GoalManager_GetSponsor",			Game::ScriptGetSponsor},
+	{"GoalManager_SetTeamMember",		Game::ScriptSetTeamMember},
+	{"GoalManager_KillTeamMembers",		Game::ScriptKillTeamMembers},
+    {"GoalManager_SetTeamName", 		Game::ScriptSetTeamName},
+	{"GoalManager_GetTeam",				Game::ScriptGetTeam},
+	{"GoalManager_RunLastStageScript",	Game::ScriptRunLastStageScript},
+	{"GoalManager_UnloadLastFam",		Game::ScriptUnloadLastFam},
+	{"GoalManager_StopLastStream",		Game::ScriptStopLastSream},
+	{"GoalManager_HideAllGoalPeds",		Game::ScriptHideAllGoalPeds},
+
+    {"GetCreateATrickParams",	        Game::ScriptGetCreateATrickParams},
+    {"GetCreateATrickOtherParams",      Game::ScriptGetCreateATrickOtherParams},
+    {"SetCreateATrickParams",	        Game::ScriptSetCreateATrickParams},
+    {"SetCreateATrickOtherParams",      Game::ScriptSetCreateATrickOtherParams},
+    {"SetCreateATrickRotations",        Game::ScriptSetCreateATrickRotations},
+    {"SetCreateATrickAnimations",       Game::ScriptSetCreateATrickAnimations},
+    {"GetCreateATrickRotations",        Game::ScriptGetCreateATrickRotations},
+    {"GetCreateATrickAnimations",       Game::ScriptGetCreateATrickAnimations},
+
+    {"CAT_SetNumAnims",                 Game::ScriptCAT_SetNumAnims},
+    {"CAT_GetNumAnims",                 Game::ScriptCAT_GetNumAnims},
+    {"CAT_SetAnimsDone",                Game::ScriptCAT_SetAnimsDone},
+    {"CAT_GetAnimsDone",                Game::ScriptCAT_GetAnimsDone},
+    {"CAT_SetRotsDone",                 Game::ScriptCAT_SetRotsDone},
+    {"CAT_GetRotsDone",                 Game::ScriptCAT_GetRotsDone},
+    {"CAT_SetBailDone",                 Game::ScriptCAT_SetBailDone},
+    {"CAT_GetBailDone",                 Game::ScriptCAT_GetBailDone},
+    {"CAT_SetFlipSkater",               Game::ScriptCAT_SetFlipSkater},
+    {"CAT_GetFlipSkater",               Game::ScriptCAT_GetFlipSkater},
+    {"CAT_SetHoldTime",                 Game::ScriptCAT_SetHoldTime},
+    {"CAT_GetHoldTime",                 Game::ScriptCAT_GetHoldTime},
+    {"CAT_SetTotalX",                   Game::ScriptCAT_SetTotalX},
+    {"CAT_GetTotalX",                   Game::ScriptCAT_GetTotalX},
+    {"CAT_SetTotalY",                   Game::ScriptCAT_SetTotalY},
+    {"CAT_GetTotalY",                   Game::ScriptCAT_GetTotalY},
+    {"CAT_SetTotalZ",                   Game::ScriptCAT_SetTotalZ},
+    {"CAT_GetTotalZ",                   Game::ScriptCAT_GetTotalZ},
+    
+    {"AddWarpPointsToMenu",             CFuncs::ScriptAddWarpPointsToMenu},
+	{"RunScriptOnObject",   			CFuncs::ScriptRunScriptOnObject},
+	{"RunScriptOnComponentType",		CFuncs::ScriptRunScriptOnComponentType},
+
+	// some asset manager functions
+	{"LoadAsset",						CFuncs::ScriptLoadAsset},
+    {"LoadAnim",						CFuncs::ScriptLoadAnim},
+    {"LoadSkeleton",					CFuncs::ScriptLoadSkeleton},
+    {"UnloadAnim",						CFuncs::ScriptUnloadAnim},
+	{"SetDefaultPermanent",				CFuncs::ScriptAssManSetDefaultPermanent},
+	{"SetReferenceChecksum",			CFuncs::ScriptAssManSetReferenceChecksum},
+    
+    {"SkaterIsBraking",                 CFuncs::ScriptSkaterIsBraking},
+	{"LocalSkaterExists",				CFuncs::ScriptLocalSkaterExists},
+	{"DownloadMotd",					GameNet::Manager::ScriptDownloadMotd},
+	{"AlreadyGotMotd",					GameNet::Manager::ScriptAlreadyGotMotd},
+	{"AlreadySignedIn",					GameNet::Manager::ScriptAlreadySignedIn},
+	
+	{"SignOut",							GameNet::Manager::ScriptSignOut},
+	{"JoinServerComplete",				GameNet::Manager::ScriptJoinServerComplete},
+	{"OnXbox",							CFuncs::ScriptOnXbox },
+	{"SystemLinkEnabled",				CFuncs::ScriptSystemLinkEnabled },
+	{"GotoXboxDashboard",				CFuncs::ScriptGotoXboxDashboard },
+
+	{"CreateParticleSystem",			CFuncs::ScriptCreateParticleSystem },
+	{"SetScript",						CFuncs::ScriptSetScript },
+	{"DestroyParticleSystem",			CFuncs::ScriptDestroyParticleSystem },
+	{"EmptyParticleSystem",				CFuncs::ScriptEmptyParticleSystem },
+	{"ParticleExists",					CFuncs::ScriptParticleExists },
+
+	{"WeatherUpdateGrid",				Nx::CEngine::ScriptWeatherUpdateGrid },
+	{"WeatherSetRainHeight",			Nx::CEngine::ScriptWeatherSetRainHeight },
+	{"WeatherSetRainFrames",			Nx::CEngine::ScriptWeatherSetRainFrames },
+	{"WeatherSetRainLength",			Nx::CEngine::ScriptWeatherSetRainLength },
+	{"WeatherSetRainBlendMode",			Nx::CEngine::ScriptWeatherSetRainBlendMode },
+	{"WeatherSetRainRate",				Nx::CEngine::ScriptWeatherSetRainRate },
+    {"WeatherSetRainColor",				Nx::CEngine::ScriptWeatherSetRainColor},
+
+	{"WeatherSetSplashRate",			Nx::CEngine::ScriptWeatherSetSplashRate },
+	{"WeatherSetSplashLife",			Nx::CEngine::ScriptWeatherSetSplashLife },
+	{"WeatherSetSplashSize",			Nx::CEngine::ScriptWeatherSetSplashSize },
+	{"WeatherSetSplashColor",			Nx::CEngine::ScriptWeatherSetSplashColor },
+	{"WeatherSetSplashBlendMode",		Nx::CEngine::ScriptWeatherSetSplashBlendMode },
+
+	{"WeatherSetSnowHeight",			Nx::CEngine::ScriptWeatherSetSnowHeight },
+	{"WeatherSetSnowFrames",			Nx::CEngine::ScriptWeatherSetSnowFrames },
+	{"WeatherSetSnowSize",				Nx::CEngine::ScriptWeatherSetSnowSize },
+	{"WeatherSetSnowBlendMode",			Nx::CEngine::ScriptWeatherSetSnowBlendMode },
+	{"WeatherSetSnowRate",				Nx::CEngine::ScriptWeatherSetSnowRate },
+    {"WeatherSetSnowColor",				Nx::CEngine::ScriptWeatherSetSnowColor},
+
+    {"WeatherSetSnowActive",			Nx::CEngine::ScriptWeatherSetSnowActive},
+    {"WeatherSetRainActive",			Nx::CEngine::ScriptWeatherSetRainActive},
+
+	{"StructureContains",				CFuncs::ScriptStructureContains},
+	{"GetBonePosition",					CFuncs::ScriptGetBonePosition},
+	{"ShouldEmitParticles",				CFuncs::ScriptShouldEmitParticles},
+	{"ParticlesOn",						CFuncs::ScriptParticlesOn},
+	{"ParticlesOff",					CFuncs::ScriptParticlesOff},
+	{"MangleChecksums",					CFuncs::ScriptMangleChecksums},
+	{"AppendSuffixToChecksum",			CFuncs::ScriptAppendSuffixToChecksum},
+	
+	{"RotateVector",					CFuncs::ScriptRotateVector},
+
+	{"IsPS2",							CFuncs::ScriptIsPS2},
+	{"IsNGC",							CFuncs::ScriptIsNGC},
+	{"IsXBOX",							CFuncs::ScriptIsXBOX},
+	{"IsWIN32",							CFuncs::ScriptIsWIN32},
+	{"GetPlatform",						CFuncs::ScriptGetPlatform},
+	{"IsPal",							CFuncs::ScriptIsPal},
+
+	{"PrintSkaterStats",				CFuncs::ScriptPrintSkaterStats},
+    {"PrintSkaterStats2",				CFuncs::ScriptPrintSkaterStats2},
+	{"PrintSkaterPosition",				CFuncs::ScriptPrintSkaterPosition},
+	{"GetSkaterID",						CFuncs::ScriptGetSkaterID},
+	{"GetCurrentSkaterID",				CFuncs::ScriptGetCurrentSkaterID},
+	{"GetViewerObjectID",				CFuncs::ScriptGetViewerObjectID},
+	
+	{"PushMemProfile",					CFuncs::ScriptPushMemProfile},
+	{"PopMemProfile",					CFuncs::ScriptPopMemProfile},
+	{"TogglePass",						CFuncs::ScriptTogglePass},
+	
+	{"GetStatValue",					CFuncs::ScriptGetStatValue},
+	{"GetNumStatPointsAvailable",		CFuncs::ScriptGetNumStatPointsAvailable},
+	{"UnlockSkater",					CFuncs::ScriptUnlockSkater},
+	{"SetSkaterProfileInfo",			CFuncs::ScriptSetSkaterProfileInfo},
+	{"GetSkaterProfileInfo",			CFuncs::ScriptGetSkaterProfileInfo},
+	{"GetSkaterProfileInfoByName",		CFuncs::ScriptGetSkaterProfileInfoByName},
+	{"SetSkaterProfileProperty",		CFuncs::ScriptSetSkaterProfileProperty},
+	{"SetSkaterProfileInfoByName",		CFuncs::ScriptSetSkaterProfileInfoByName},
+	{"SetScreen",						CFuncs::ScriptSetScreen},
+
+	{"GetUpperCaseString",				CFuncs::ScriptGetUpperCaseString},
+
+	{"GetGameMode",						CFuncs::ScriptGetGameMode},
+	{"StartKeyboardHandler",			CFuncs::ScriptStartKeyboardHandler},
+	{"StopKeyboardHandler",				CFuncs::ScriptStopKeyboardHandler},
+	{"EnableKeyboard",					CFuncs::ScriptEnableKeyboard},
+	{"DisableKeyboard",					CFuncs::ScriptDisableKeyboard},
+	
+	{"GetRescaledTargetValue",			CFuncs::ScriptGetRescaledTargetValue},
+	{"MemInitHeap",						CFuncs::ScriptMemInitHeap},
+	{"MemDeleteHeap",					CFuncs::ScriptMemDeleteHeap},
+	{"ClearStruct",						CFuncs::ScriptClearStruct},
+	{"AppendStruct",					CFuncs::ScriptAppendStruct},
+	{"ScriptExists",					CFuncs::ScriptScriptExists},
+	{"GetPlayerAppearancePart",			CFuncs::ScriptGetPlayerAppearancePart},
+	{"GetActualCASOptionStruct",		CFuncs::ScriptGetActualCASOptionStruct},
+	{"GetActualPlayerAppearancePart",	CFuncs::ScriptGetActualPlayerAppearancePart},
+	{"SetPlayerAppearanceColor",		CFuncs::ScriptSetPlayerAppearanceColor},
+	{"SetPlayerAppearanceScale",		CFuncs::ScriptSetPlayerAppearanceScale},
+	{"SetPlayerAppearanceUV",			CFuncs::ScriptSetPlayerAppearanceUV},
+	{"FlushDeadObjects",				CFuncs::ScriptFlushDeadObjects},
+
+	{"BindTrickToKeyCombo",				CFuncs::ScriptBindTrickToKeyCombo},
+	{"GetKeyComboBoundToTrick",			CFuncs::ScriptGetKeyComboBoundToTrick},
+	{"UpdateTrickMappings",				CFuncs::ScriptUpdateTrickMappings},
+	{"GetConfigurableTricksFromType",	CFuncs::ScriptGetConfigurableTricksFromType},
+	{"GetTrickDisplayText",				CFuncs::ScriptGetTrickDisplayText},
+	{"GetSpecialTrickInfo",				CFuncs::ScriptGetSpecialTrickInfo},
+	{"GetTrickType",					CFuncs::ScriptGetTrickType},
+	{"TrickIsLocked",					CFuncs::ScriptTrickIsLocked},
+
+	{"SpawnSecondControllerCheck",		CFuncs::ScriptSpawnSecondControllerCheck},
+	{"StopSecondControllerCheck",		CFuncs::ScriptStopSecondControllerCheck},
+
+	{"GetIndexOfItemContaining",		CFuncs::ScriptGetIndexOfItemContaining},
+	{"ForEachSkaterName",				CFuncs::ScriptForEachSkaterName},
+	{"ForEachSkaterProfile",			CFuncs::ScriptForEachSkaterProfile},
+	{"ResetAllToDefaultStats",			CFuncs::ScriptResetAllToDefaultStats},
+	{"ResetAllToDefaultProfile",		CFuncs::ScriptResetAllToDefaultProfile},
+	{"ResetToDefaultProfile",			CFuncs::ScriptResetToDefaultProfile},
+	{"GetLevelRecords",					CFuncs::ScriptGetLevelRecords},
+
+	{"ResetComboRecords",				CFuncs::ScriptResetComboRecords},
+
+	{"IsArray",							CFuncs::ScriptIsArray},
+
+	{"GetNumberOfTrickOccurrences",		CFuncs::ScriptGetNumberOfTrickOccurrences},
+
+	{"GetNumSoundtracks",				CFuncs::ScriptGetNumSoundtracks},
+	{"GetSoundtrackName",				CFuncs::ScriptGetSoundtrackName},
+	{"UseUserSoundtrack",				CFuncs::ScriptUseUserSoundtrack},
+	{"UseStandardSoundtrack",			CFuncs::ScriptUseStandardSoundtrack},
+	
+	{"DisableReset",					CFuncs::ScriptDisableReset},
+	{"EnableReset",						CFuncs::ScriptEnableReset},
+	{"ResetToIPL",						CFuncs::ScriptResetToIPL},
+
+	{"BindControllerToSkater",			CFuncs::ScriptBindControllerToSkater},
+	{"BindFrontEndToController",		CFuncs::ScriptBindFrontEndToController},
+	{"ControllerBoundToDifferentSkater",CFuncs::ScriptControllerBoundToDifferentSkater},
+	{"ControllerBoundToSkater",			CFuncs::ScriptControllerBoundToSkater},
+
+	{"GetKeyComboArrayFromTrickArray",	CFuncs::ScriptGetKeyComboArrayFromTrickArray},
+	{"InitAnimCompressTable",			CFuncs::ScriptInitAnimCompressTable},
+
+	{"FirstInputReceived",				CFuncs::ScriptFirstInputReceived},
+	{"LockCurrentSkaterProfileIndex",	CFuncs::ScriptLockCurrentSkaterProfileIndex},
+
+	{"SetSpecialTrickInfo",				CFuncs::ScriptSetSpecialTrickInfo},
+    {"GetSkaterPosition",				CFuncs::ScriptGetSkaterPosition},
+	{"GetSkaterVelocity",				CFuncs::ScriptGetSkaterVelocity},
+	{"InterpolateParameters",			CFuncs::ScriptInterpolateParameters},
+	{"GetLightCurrentColor",			Nx::ScriptGetLightCurrentColor},
+    {"DrawDirectionalLightLines",		Nx::ScriptDrawDirectionalLightLines},
+
+    {"CreateCompositeObject",			CFuncs::ScriptCreateCompositeObject},
+	
+	{"AutoRail",						CFuncs::ScriptAutoRail},
+	
+    {"Inside",           				CFuncs::ScriptInside},
+
+    {"SetSpecialBarColors",           	CFuncs::ScriptSetSpecialBarColors},
+	{"GetMetrics",						CFuncs::ScriptGetMetrics},
+	{"MoveNode",						CFuncs::ScriptMoveNode},
+	{"SetActiveCamera",					CFuncs::ScriptSetActiveCamera},
+
+    {"Sin",                           	CFuncs::ScriptSin},
+    {"Cos",                           	CFuncs::ScriptCos},
+    {"Tan",                           	CFuncs::ScriptTan},
+    {"ASin",                           	CFuncs::ScriptASin},
+    {"ACos",                           	CFuncs::ScriptACos},
+    {"ATan",                           	CFuncs::ScriptATan},
+	
+	{"ShowTracking",					CFuncs::ScriptShowTracking},
+	
+	{"IsGrind",							CFuncs::ScriptIsGrind},
+	{"ShowCamOffset",					CFuncs::ScriptShowCamOffset},
+	
+	{"PlaySkaterStream",				CFuncs::ScriptPlaySkaterStream},
+
+    {"GetTextureFromPath",              CFuncs::ScriptGetTextureFromPath},
+    {"GetVramUsage",              CFuncs::ScriptGetVramUsage},
+	{"CompositeObjectExists",			CFuncs::ScriptCompositeObjectExists},	
+	
+	{"DoNextTrick",					Obj::ScriptDoNextTrick},
+
+	{"AllocatePathManMemory",		Obj::ScriptAllocatePathManMemory},
+
+
+	{"AddSkaterEarly",					CFuncs::ScriptAddSkaterEarly},
+
+	{"PreLoadStreamDone",				CFuncs::ScriptPreLoadStreamDone},
+	{"StartPreLoadedStream",			CFuncs::ScriptStartPreLoadedStream},
+	{"FinishRendering",					CFuncs::ScriptFinishRendering},
+
+// Gamecube-specific message functions.
+#ifdef __PLAT_NGC__
+	{"Ngc_BGColor", 					Nx::ScriptNgc_BGColor},
+	{"Ngc_Message", 					Nx::ScriptNgc_Message},
+	{"Ngc_Menu", 						Nx::ScriptNgc_Menu},
+	{"Ngc_Set480P",						Nx::ScriptNgc_Set480P},
+	{"Ngc_Set480I",						Nx::ScriptNgc_Set480I},
+	{"Ngc_SetWide",						Nx::ScriptNgc_SetWide},
+	{"Ngc_SetStandard",					Nx::ScriptNgc_SetStandard},
+	{"Ngc_ReduceColors",				Nx::ScriptNgc_ReduceColors},
+	{"Ngc_Set60hz",						Nx::ScriptNgc_Set60Hz},
+	{"Ngc_Set50hz",						Nx::ScriptNgc_Set50Hz},
+#endif		// __PLAT_NGC__
+};
+
+// A list of all the member function names accessible from scripts.
+// The name in quotes must be the same as the way it appears in q scripts. (case insensitive)
+const char *ppMemberFunctionNames[]=
+{
+	// used by the asset manager (AssMan)	
+    "AnimLoaded",
+    "AssetLoaded",
+
+	// used by Gfx::Camera
+	"ChangeCameraFOV",
+
+	// used by Obj::CMovieCamera
+	"SetTargetObject",
+	"ClearTargetObject",
+	 
+	// used by Gfx::CModelAppearance
+	"SetPart",
+	"GetPart",
+	"ClearPart",
+	"SetChecksum",
+	"PartGotFlag",
+	"GotPart",
+	
+	// used by Gfx::CModelBuilder
+	"GeomModulateColor",
+	"GeomSetUVOffset",
+	"GeomAllocateUVMatrixParams",
+	"GeomReplaceTexture",
+	"ModelAddGeom",
+	"ModelHideGeom",
+	"ModelRemovePolys",
+	"ModelFinalize",
+    "ModelResetScale",
+    "ModelApplyBoneScale",
+    "ModelApplyBodyShape",
+    "ModelApplyObjectScale",
+	"ModelApplyFaceTexture",
+	"ModelRunScript",
+	"ModelClearGeom",
+	"ModelClearAllGeoms",
+	"DebugPrintAppearance",
+	"AppearanceAllowScalingCheat",
+
+	// used by Obj::CSkater
+	"GetStat",
+	"GetScriptedStat",
+	"SetCustomRestart",
+	"SkipToCustomRestart",
+	"PausePhysics",
+	"UnPausePhysics",
+	"SetDriving",
+	"UnsetDriving",
+	"GetTimeSincePhysicsSwitch",
+	"GetPreviousPhysicsStateDuration",
+	"DumpEventBuffer",
+	"AnimEquals",
+	"PlayAnim",
+	"BlendPeriodOut",
+	"LoopingAnim",
+	"AnimFinished",
+	"LeftPressed",
+	"RightPressed",
+	"UpPressed",
+	"DownPressed",
+	"Jump",
+	"Flipped",
+	"Switched",
+	"Crouched",
+	"OnGround",
+	"InAir",
+	"OnWall",
+	"OnLip",
+	"OnRail",
+	"InWallplant",
+	"FirstTimeOnThisRail",
+	"InBail",
+	"NotInBail",
+	"IsInBail",
+	"PlayBonkSound",
+	"PlayCessSound",
+	"PlayLandSound",
+	"PlayJumpSound",
+	"SpeedLessThan",
+	"SpeedGreaterThan",
+	"SpeedEquals",
+	"Braking",
+	"CanBrakeOff",
+	"CanBrakeOn",
+	"CanKick",
+	"CanKickOn",
+	"CanKickOff",
+	"ForceAutokickOn",
+	"ForceAutokickOff",
+	"RestoreAutokick",
+	"AutoKickIsOff",
+	"DoCarPlantBoost",
+	"GetAnimLength",
+    "Obj_GetAnimSpeed",
+	"WaitAnim",
+	"FrameIs",
+	"BashOn",
+	"BashOff",
+	"SetTrickName",
+	"SetTrickScore",
+	"GetSpin",
+	"Display",
+	"HandleLipOllieDirection",
+	"EnableDisplayFlip",
+	"DisableDisplayFlip",
+	"Flip",
+	"ResetSwitched",
+	"GetHeading",
+	"Rotate",
+	"RotateDisplay",
+	"CancelRotateDisplay",
+	"BoardRotate",
+	"NoSpin",
+	"CanSpin",
+	"Move",
+	"Hide",
+	"Unhide",
+	"IsHidden",
+	"Suspend",
+	"Unsuspend",
+	"Pause",
+	"Unpause",
+	"SetSpeed",
+	"GetSpeed",
+	"OrientToNormal",
+	"Held",
+	"Released",
+	"HeldLongerThan",
+	"EnableInputEvents",
+	"DisableInputEvents",
+	"HasPowerup",
+	"PickedUpPowerup",
+	
+//	"DoNextTrick",
+	"ClearTricksFrom",
+	"SetQueueTricks",
+	"ClearTrickQueue",
+	"AddTricksToQueue",
+	"UseGrindEvents",
+	"DoNextManualTrick",
+	"SetManualTricks",
+	"ClearManualTrick",
+	"SetExtraGrindTricks",
+	"ClearExtraGrindTrick",
+	"AirTimeLessThan",
+	"AirTimeGreaterThan",
+	"GetAirTime",
+	"GetAirTimeLeft",
+	"GetSlope",
+	"backwards",
+	"ClearPanel_Landed",
+	"ClearPanel_Bailed",
+	"FlipAfter",
+	"RotateAfter",
+	"BoardRotateAfter",
+	"IsFlipAfterSet",
+	"TriggerType",
+	"KillSkater",
+	"FlipAndRotate",
+	"DoBalanceTrick",
+	"StopBalanceTrick",
+	"SwitchOffBalanceMeter",
+	"SwitchOnBalanceMeter",
+	"StartBalanceTrick",
+	"StartSkitch",
+	"StopSkitch",
+	"Skitching",
+	"SetState",
+	"ClearEventBuffer",
+	"Input_Debounce",
+	"DisablePlayerInput",
+	"NetDisablePlayerInput",
+	"EnablePlayerInput",
+	"NetEnablePlayerInput",
+	"PlayerInputIsDisabled",
+	"StartGap",
+	"EndGap",
+	"StartGapTrick",
+	"CheckGapTricks",
+	"ClearGapTricks",
+	"GotSpecial",
+	"SetRailSound",
+	"LockVelocityDirection",
+	"SetRollingFriction",
+	"SetSpecialFriction",
+	"InNollie",
+	"NollieOn",
+	"NollieOff",
+	"InPressure",
+	"PressureOn",
+	"PressureOff",
+    "RunStarted",
+	"EndOfRunDone",
+	"EndOfRunStarted",
+	"Goal_EndOfRunDone",
+	"Goal_EndOfRunStarted",
+	"IsInEndOfRun",
+	"DuplicateTrigger",
+	"SparksOn",
+	"SparksOff",
+	"Vibrate",
+	"DoingTrick",
+	"GetTerrain",
+	"BailOn",
+	"BailOff",
+	"BailIsOn",
+    "FrontTruckSparks",
+	"SetFrontTruckSparks",
+	"SetRearTruckSparks",
+	
+	"RemoveXEvents",
+	"RestoreEvents",
+	"YawBetween",
+	"PitchGreaterThan",
+	"AbsolutePitchGreaterThan",
+	"RollGreaterThan",
+	"YawingLeft",
+	"YawingRight",
+	// "PitchingForward",
+	// "PitchingBackward",
+	// "RollingLeft",
+	// "RollingRight",
+	"TextureSplat",
+	"Skeleton_SpawnTextureSplat",
+	"Skeleton_SpawnCompositeObject",
+	"TweakTrick",
+	"IsLatestTrick",
+	"SetGrindTweak",
+	"Ledge",
+	"BadLedge",
+	"SkateInAble",
+	"ResetSpin",
+	"LastSpinWas",
+	"LandedFromSpine",
+	"LandedFromVert",
+	"SetLandedFromVert",
+	"ResetLandedFromVert",
+	"InVertAir",
+	"AllowLipNoGrind",
+	"ClearAllowLipNoGrind",
+	"NoRailTricks",
+	"AllowRailTricks",
+	"SetExtraTricks",
+	"KillExtraTricks",
+	"TurnToFaceVelocity",
+	"SC_SetSkaterCamOverride",
+	"SC_ClearSkaterCamOverride",
+	"SC_ShakeCamera",
+	"SC_SetMode",
+	"SetExtraPush",
+	"GetMatrixNormal",
+	"GetLastGroundPos",
+	"ShouldMongo",
+	"SetSlotTrick",
+	"ChangeProTricks",
+	"TrickOffObject",
+	"AdjustBalance",
+	"IsLocalSkater",
+	"SkaterIsNamed",
+	"GetCameraId",
+	"AddSkaterToWorld",
+	"ResetRigidBodyCollisionRadiusBoost",
+	"RemoveSkaterFromWorld",
+	"OverrideLimits",
+	"CreateSpecialItem",
+	"DestroySpecialItem",
+	"DestroyAllSpecialItems",
+	"SpecialItemExists",
+	"OverrideCancelGround",
+	"BoardIsRotated",
+	"GetSkaterNumber",
+	"LastScoreLandedGreaterThan",
+	"LastScoreLandedLessThan",
+	"TotalScoreGreaterThan",
+	"TotalScoreLessThan",
+	"GetTotalScore",
+	"CurrentScorePotGreaterThan",
+	"CurrentScorePotLessThan",
+	"GetNumberOfNonGapTricks",
+	"PlaceBeforeCamera",
+	"CancelWallpush",
+	
+	// used by Obj::CCompositeObject
+	"Obj_PrintDetails",
+	"CreateComponentFromStructure",
+
+	// used by Obj::CMovingObject
+	"Obj_PlayStream",
+	"Obj_StopStream",
+	"FollowWaypointPath",
+	"LookAtNode",
+//	"Obj_WaitForStreamAvailable",
+//	"Obj_WaitForStreamToStart",
+//	"Obj_WaitStreamFinished",
+	"Obj_ShadowOn",
+	"Obj_ShadowOff",
+	// objects would require an animated model to run these functions:
+//	"RunAnim",
+//	"CycleAnim",
+	"Obj_WaitRotate",
+	"Obj_WaitAnimFinished",
+	"AnimExists",
+	"Obj_WaitMove",
+	"Obj_WaitStop",
+	// wait functions:
+	// other functions:
+	"Obj_StopMoving",
+	"Obj_StopRotating",
+	"Obj_PlaySound",
+	"Obj_AdjustSound",
+	"Obj_StopSound",
+	"Obj_SetSound", // tell a car, for example, which sound to use for engine loop.
+	"Obj_Rotate",
+	"Obj_RotX",
+	"Obj_RotY",
+	"Obj_RotZ",
+	"Obj_Hover",
+	"Obj_StorePos",
+	"Obj_StoreNode",
+	"Obj_LookAtNodeStored",
+	"Obj_LookAtNodeLinked",
+	"Obj_LookAtPosStored",
+//	"Obj_MoveToNodeStored",
+	"Obj_MoveToPosStored",
+	"Obj_RandomPathMode",
+	"Obj_FollowPathStored",
+	"Obj_LookAtNode",
+	"Obj_LookAtPos",
+	"Obj_LookAtRelPos",
+	"Obj_LookAtObject",
+	"Obj_AngleToNearestSkaterGreaterThan",
+	"Obj_MoveToLink",
+	"Obj_GetRandomLink",
+	"Obj_MoveToNode",
+	"Obj_MoveToPos",
+	"Obj_MoveToRelPos",
+	"Obj_MoveForward",
+	"Obj_MoveLeft",
+	"Obj_IsMoving",
+	"Obj_IsRotating",
+	"Obj_SetConstantHeight",
+	"Obj_FollowPath",
+	"Obj_FollowPathLinked",
+	"Obj_FollowLeader",
+	"Obj_LockToObject",
+	"Obj_GetSpeed",
+	"Obj_GetNextObjOnPath",
+	"Obj_SetPathVelocity",
+	"Obj_SetPathAcceleration",
+	"Obj_SetPathDeceleration",
+	"Obj_SetPathMinStopVel",
+	"Obj_StopAlongPath",
+	"Obj_StartAlongPath",
+	"Obj_SetPathTurnDist",
+	"Obj_SetGroundOffset",
+	"Obj_PlayAnim",
+	"Obj_EnableAnimBlending",
+	"Obj_SetAnimCycleMode",
+	"Obj_AnimEquals",
+	"Obj_StickToGround",
+	"Obj_PathHeading",
+	"Obj_AnimComplete",
+	"Obj_AllowSkitching",
+	"Obj_GetOrientation",
+	"Obj_ReplaceTexture",
+	"Obj_ReplaceSpriteTexture",
+	"Obj_SetBodyShape",
+	"Obj_SetBoneActive",
+	"Obj_GetBonePosition",
+	"SwitchOnAtomic",
+	"SwitchOffAtomic",
+    "AtomicIsHidden",
+	"Obj_ClearGeoms",
+	"Obj_InitModel",
+	"Obj_InitModelFromProfile",
+	"Obj_HasModelLights",
+	"Obj_UpdateBrightness",
+	"Obj_EnableAmbientLight",
+	"Obj_EnableDiffuseLight",
+	"Obj_DisableAmbientLight",
+	"Obj_DisableDiffuseLight",
+	"Obj_SetLightAmbientColor",
+	"Obj_SetLightDirection",
+	"Obj_SetLightDiffuseColor",
+	"Obj_SetUVOffset",
+	"Obj_SetUVParams",
+	"Obj_SetBoundingSphere",
+	"Obj_RestoreBoundingSphere",
+	"Obj_SkipToRestart",
+
+	// procedural bone anim functions
+	"SetBoneTransActive",
+	"SetBoneTransMin",
+	"SetBoneTransMax",
+	"SetBoneTransSpeed",
+	"SetBoneTransCurrent",
+	"SetBoneRotActive",
+	"SetBoneRotMin",
+	"SetBoneRotMax",
+	"SetBoneRotSpeed",
+	"SetBoneRotCurrent",
+	"SetBoneScaleActive",
+	"SetBoneScaleMin",
+	"SetBoneScaleMax",
+	"SetBoneScaleSpeed",
+	"SetBoneScaleCurrent",
+	"AddAnimController",
+	"RemoveAnimController",
+	"GetPartialAnimParams",
+	"SetPartialAnimSpeed"
+	"IncrementPartialAnimTime",
+	"ReversePartialAnimDirection",
+	"SetWobbleDetails",
+	"Obj_Flip",
+	"Obj_AnimationFlipped",
+
+	// can be used by any Obj::CMovingObject
+	"Obj_GetId",
+	"Die",
+	"DisassociateFromObject",
+	"Obj_SetInnerRadius", // inner radius for player 'collision'
+	"Obj_SetOuterRadius", // outer radius for player 'collision'
+	"Obj_SetInnerAvoidRadius", // inner radius for player avoidance logic
+	"Obj_SetOuterAvoidRadius", // outer radius for player avoidance logic
+	"Obj_FlagSet",
+	"Obj_FlagNotSet",
+	"Obj_SetFlag",
+	"Obj_ClearFlag",
+	"Obj_SpawnScript",
+	"Obj_KillSpawnedScript",
+	"Obj_SwitchScript",
+	"Obj_Visible",
+	"Obj_Invisible",
+	"Obj_ObjectInRadius",
+	"Obj_ObjectInRect",
+	"Obj_ObjectNotInRect",
+	
+	// "Obj_VarGet",
+	// "Obj_VarSet",
+	// "Obj_VarPrintf",
+	// "Obj_VarPrint",
+	// "Obj_VarInc",
+	// "Obj_VarDec",
+	// "Obj_VarLT",
+	// "Obj_VarGT",
+	// "Obj_VarEQ",
+	
+	"Obj_GetCollision",
+	"Obj_GetDistToNode",
+	"Obj_ApplyScaling",
+	"Obj_EnableScaling",
+	"Obj_DisableScaling",
+	"Obj_SetColor",
+	"Obj_ClearColor",
+	"Obj_Jump",
+	"Obj_WaitJumpFinished",
+	"Obj_GetDistanceToObject",
+	"Obj_GetOrientationToObject",
+	"Obj_GetPosition",
+	"Obj_SetPosition",
+	"Obj_SetOrientation",
+	"Obj_ForceUpdate",
+	"Obj_GetVelocity",
+	
+	// Car member functions:
+	"EnableCarShadow",
+	 
+	// Ped member functions:
+	"Ped_SelectAvoidPoint",
+	"Ped_MoveTowardsAvoidPoint",
+	"Ped_AvoidPointReached",
+	"Ped_RememberNextWaypoint",
+	"Ped_RememberCurrentPosition",
+	"Ped_RememberStickToGround",
+	"Ped_RestoreStickToGround",
+	"Ped_BackOnOriginalPath",
+
+	// Bouncy obj member functions:
+	"BouncyObj_PlayerCollisionOn",
+	"BouncyObj_PlayerCollisionOff",
+	"BouncyObj_Go",
+	"MoveToRandomRestart",
+
+	// CRigidBodyComponent member functions:
+	"RigidBody_IgnoreSkater",
+	"RigidBody_Wake",
+	"RigidBody_Sleep",
+	"RigidBody_Kick",
+	"RigidBody_Reset",
+	"RigidBody_DisablePlayerCollision",
+	"RigidBody_EnablePlayerCollision",
+	"RigidBody_MatchVelocityTo",
+	
+	// CRibbonComponent member functions:
+	// "Ribbon_Reset",
+	
+	// CVehicleComponent member functions:
+	"Vehicle_Kick",
+	"Vehicle_Wake",
+	"Vehicle_MoveToRestart",
+	"Vehicle_PlaceBeforeCamera",
+	"Vehicle_AdjustGravity",
+	// "Vehicle_ForceBrake",
+	"Vehicle_HandbrakeActive",
+	"Vehicle_AllWheelsAreInContact",
+	"Vehicle_LostCollision",
+	"Vehicle_IsSkaterVisible",
+	
+	// CVehicleCameraComponent member functions:
+	"VehicleCamera_Reset",
+	
+
+
+
+
+	// CSkaterPhysicsControlComponent member functions:
+	"Walking",
+	"Skating",
+	"Driving",
+	"SkaterPhysicsControl_SwitchSkatingToWalking",
+	"SkaterPhysicsControl_SwitchWalkingToSkating",
+	"SetBoardMissing",
+	"UnsetBoardMissing",
+	"IsBoardMissing",
+	
+	
+	// CSkaterLoopingSoundComponent member functions:
+	"SkaterLoopingSound_TurnOn",
+	"SkaterLoopingSound_TurnOff",
+	
+	// CWalkComponent member functions:
+	"Walk_Jump",
+	"Walk_ScaleAnimSpeed",
+	"Walk_Ground",
+	"Walk_Air",
+	"Walk_Hang",
+	"Walk_Ladder",
+	"Walk_AnimWait",
+	"Walk_GetStateTime",
+	"Walk_SetDragFactor",
+	"Walk_ResetDragFactor",
+	"Walk_AnimWaitComplete",
+	"Walk_GetSpeedScale",
+    "Walk_GetHangInitAnimType",
+	"Walk_GetStateDuration",
+	"Walk_GetPreviousState",
+	"Walk_GetState",
+	"Walk_GetHangAngle",
+	
+	// "Walk_CancelTransitionalMomentum",
+	// "Walk_SuppressInAirControl",
+	
+	// CWalkCameraComponent member functions:
+	"WalkCamera_FlushRequest",
+	"WalkCamera_Reset",
+	
+	// CSkaterRunTimerComponent member functions:
+	"RunTimer_Pause",
+	"RunTimer_UnPause",
+	"RunTimer_GetFactorComplete",
+	"RunTimer_GetRunTimerControllerId",
+	"RunTimer_GetRunTimerId",
+	
+#	ifdef TESTING_GUNSLINGER
+	// CHorseComponent member functions:
+	"Horse_GetSpeedScale",
+	"Horse_Jump",
+#	endif
+
+	// CScreenElement functions:
+	"SetProps",
+	"DoMorph",
+	"GetProps",
+	
+	// CObject functions:
+//	"ClearEventHandler",
+//	"ClearEventHandlerGroup",
+	"SetTags",
+	"GetTags",
+
+	// Particle system functions.
+	"SetPos",
+	"SetSpeedRange",
+	"SetEmitRange",
+	"SetAngleSpread",
+	"SetRandomAngle",
+	"SetCircularEmit",
+	"SetForce",
+	"SetParticleSize",
+	"SetLife",
+	"SetEmitTarget",
+	"SetColor",
+	"BuildPath",
+	"EmptySystem",
+	
+	"Emit",
+	"EmitRate",
+
+    // StatsManagerComponent fuctions
+    "StatsManager_ActivateGoals",
+    "StatsManager_DeactivateGoals",
+    "StatsManager_GetGoalStatus",
+    "StatsManager_ReInit",
+    "StatsManager_Reset",
+    "StatsManager_UnlockAmGoals",
+    "StatsManager_UnlockProGoals",
+
+	// PedLogicComponent functions
+	"Ped_InitPath",
+	"Ped_SetLogicState",
+	"Ped_StartMoving",
+	"Ped_StopMoving",
+	"Ped_IsMoving",
+	"Ped_SetStickToGroundDist",
+	"Ped_SetIsGrinding",
+	"Ped_Bail",
+	"Ped_SetIsBailing",
+	"Ped_GetCurrentVelocity",
+	"Ped_StoreMaxVelocity",
+	"Ped_GetOriginalMaxVelocity",
+	"Ped_InitVertTrick",
+	"Ped_HitWaypoint",
+	"Ped_SetIsSkater",
+	"Ped_NearVertNode",
+	"Ped_PlayJumpSound",
+	"Ped_PlayLandSound",
+	"Ped_GetCurrentNodeNames",
+	
+	"FlagGoalAsWon",
+	"FindUnfinishedGoal",
+	"GoalExists",
+	"NewEditorGoal",
+	"RemovedCreatedGoal",
+	"GoalHasAllPositionsSet",
+	"SetCurrentEditorGoal",
+	"SetEditorGoalType",
+	"SetEditorGoalDescription",
+	"SetEditorGoalWinMessage",
+	"GetEditedGoalsInfo",
+	"GetNumEditedGoals",
+	"GetCurrentEditedGoalId",
+	"WriteEditedGoalNodePositions",
+	"AddEditedGoalToGoalManager",
+	"GetCurrentEditedGoalInfo",
+	"SetEditorGoalName",
+	"SetEditorPedName",
+	"MaxEditedGoalsReached",	
+	"EditGoal",
+	"NukeAllGoals",
+	"GetMaxGoalsPerLevel",
+	"SetGoalScore",
+	"SetGoalTimeLimit",
+	"SetGoalControlType",
+	"RefreshGoalCursorPosition",
+	"SetGoalSpecificParams",
+	"RemoveGoalSpecificFlag",
+	"AddKeyComboSet",
+	"GetKeyComboSet",
+	"RemoveKeyComboSet",
+	"EditedGoalAddGap",
+	"EditedGoalRemoveGap",
+	"EditedGoalGotGap",
+	"EditorCam_Initialise",
+	"EditorCam_SetCursorPos",
+	"EditorCam_SetOnlyVerticalCollisionChecks",
+
+	// Rail editor component member functions
+	"GetCursorPosition",
+	"NewRail",
+	"MaxRailsReached",
+	"AddNewPosition",
+	"ClearRailEditor",
+	"SetEditingMode",
+	"GetEditingMode",
+	"DrawDottedLine",
+	"DeleteDottedLine",
+	"UpdateRailPointPosition",
+	"HighlightRailPoint",
+	"UnHighlightAllRails",
+	"DeleteRailPoint",
+	"GetEditedRailInfo",
+	"DeleteRail",
+	"DestroyEditedRailSectors",
+	
+	"SetStartPos",
+	"SetMidPos",
+	"SetEndPos",
+	"SetStartBoxDimensions",
+	"SetMidBoxDimensions",
+	"SetEndBoxDimensions",
+	"SetUpdateScript",
+	"SetEmitRate",
+	"SetLifetime",
+	"SetMidPointPct",
+	"SetMidpointColorPct",
+	"SetStartRadius",
+	"SetMidRadius",
+	"SetEndRadius",
+	"SetStartColor",
+	"SetMidColor",
+	"SetEndColor",
+    
+};
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+int GetCFunctionLookupTableSize()
+{
+    return (sizeof(CFunctionLookupTable)/sizeof(SCFunction));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int GetNumMemberFunctions()
+{
+    return (sizeof(ppMemberFunctionNames)/sizeof(const char *));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Script
+
diff --git a/Code/Sk/Scripting/ftables.h b/Code/Sk/Scripting/ftables.h
new file mode 100644
index 0000000..03ba2c2
--- /dev/null
+++ b/Code/Sk/Scripting/ftables.h
@@ -0,0 +1,74 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Scripting												**
+**																			**
+**	File name:		ftables.h												**
+**																			**
+**      Created:                09/15/2000      -        ksh                                                                             **
+**																			**
+*****************************************************************************/
+
+#ifndef	__SCRIPTING_FTABLES_H
+#define	__SCRIPTING_FTABLES_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef	__SCRIPTING_INIT_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+namespace Script
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+extern SCFunction CFunctionLookupTable[];
+
+extern const char *ppMemberFunctionNames[];
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+int GetCFunctionLookupTableSize();
+int GetNumMemberFunctions();
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace FTables
+
+#endif	// __SCRIPTING_FTABLES_H
+
diff --git a/Code/Sk/Scripting/gs_file.cpp b/Code/Sk/Scripting/gs_file.cpp
new file mode 100644
index 0000000..e0ab901
--- /dev/null
+++ b/Code/Sk/Scripting/gs_file.cpp
@@ -0,0 +1,263 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// gs_file.cpp		KSH 7 Nov 2001
+//
+// Game specific functions for loading and unloading qb files.
+// These call the LoadQB and UnloadQB functions in the Skate namespace, but then do
+// any additional skate-specific processing required.
+// For example, if the newly loaded qb contained a NodeArray, node name prefix info
+// needs to be regenerated, etc.
+// Also, skater exceptions may need to be reloaded.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  // For Crc::GenerateCRCFromString
+#include 
+#include 
+#include 
+#include 
+#ifdef __PLAT_NGC__
+#include 
+#endif		// __PLAT_NGC__
+
+#include "string.h"
+
+#ifdef __PLAT_NGC__
+#include 
+#include "sys/ngc/p_display.h"
+#endif		// __PLAT_NGC__
+
+namespace SkateScript
+{
+using namespace Script;
+
+// The size of the temporary buffer used when parsing the qdir.txt file. Used in LoadAllStartupQBFiles()
+#define MAX_QB_FILENAME_CHARS_WITH_PATH 100
+
+#define QDIR_FILE "scripts\\qdir.txt"
+#define MAIN_QB_FILEPATH "scripts\\"
+
+void LoadAllStartupQBFiles()
+{
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+#ifdef __PLAT_NGC__
+	switch ( Config::GetLanguage() )
+	{
+		case Config::LANGUAGE_FRENCH:
+			Pip::LoadPre("qb.prf");
+			break;
+		case Config::LANGUAGE_GERMAN:
+			Pip::LoadPre("qb.prd");
+			break;
+		default:
+			Pip::LoadPre("qb.pre");
+			break;
+	}
+#else
+	Pip::LoadPre("qb.pre");
+#endif		// __PLAT_NGC__
+	Mem::Manager::sHandle().PopContext();
+
+	// Load the qdir.txt file
+	const char *p_qdir=(const char *)Pip::Load(QDIR_FILE);
+	uint32 file_size=Pip::GetFileSize(QDIR_FILE);
+	
+	UsePermanentStringHeap();
+	
+    // Load each of the files listed.
+	#define MAX_FILENAME_CHARS 200
+	char p_file_name[MAX_FILENAME_CHARS+1];
+	
+	const char *p_scan=p_qdir;
+	while (p_scan < p_qdir+file_size)
+	{
+		int c=0;
+		while (p_scan < p_qdir+file_size && *p_scan!=0x0d && *p_scan!=0x0a)
+		{
+			Dbg_MsgAssert(c= p_qdir+file_size then it
+		// still will be after incrementing p_scan so that's OK too.
+		++p_scan;
+		
+		// Skip any empty strings, which will happen when encountering 0x0d,0x0a pairs.
+		if (*p_file_name)
+		{
+			// Note: p_file_name will contain the complete path, eg c:\skate5\data\scripts\blaa.qb
+
+			// Strip off any preceding data path.			
+			char *p_data_backslash=strstr(p_file_name,"data\\");
+			if (p_data_backslash)
+			{
+				strcpy( p_file_name, p_data_backslash+5); // Safe cos it will copy backwards
+			}
+				
+			// Make sure this script isn't already loaded.
+			SkateScript::UnloadQB( Crc::GenerateCRCFromString(p_file_name) );
+
+			SkateScript::LoadQB(p_file_name,
+								// We do want assertions if duplicate symbols when loading all 
+								// the qb's on startup. (Just not when reloading a qb)
+								ASSERT_IF_DUPLICATE_SYMBOLS);
+		}	
+#ifdef __PLAT_NGC__
+		NsDisplay::doReset();
+#endif		// __PLAT_NGC__
+
+	}	
+
+	UseRegularStringHeap();
+	
+	Pip::Unload(QDIR_FILE);
+#ifdef __PLAT_NGC__
+	switch ( Config::GetLanguage() )
+	{
+		case Config::LANGUAGE_FRENCH:
+			Pip::UnloadPre("qb.prf");
+			break;
+		case Config::LANGUAGE_GERMAN:
+			Pip::UnloadPre("qb.prd");
+			break;
+		default:
+			Pip::UnloadPre("qb.pre");
+			break;
+	}
+#else
+	Pip::UnloadPre("qb.pre");
+#endif		// __PLAT_NGC__
+	
+	// A very small memory optimization ...
+	// Remove the debugger_names array since it only exists to get its checksum names registered
+	// for the script debugger. They will have got registered by now, so the array is no longer
+	// needed. Saves about 3K.
+	CSymbolTableEntry *p_debugger_names=LookUpSymbol(CRCD(0xc2f97ba7,"debugger_names"));
+	if (p_debugger_names)
+	{
+		CleanUpAndRemoveSymbol(p_debugger_names);
+	}
+}
+
+// Calls the Script::LoadQB, and then do any game-specific stuff that needs to be done when a qb is reloaded, such as 
+// generating the node name hash table & prefix info
+void LoadQB(const char *p_fileName, EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols)
+{
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+	//if (strcmp("..\\runnow.qb",p_fileName) != 0)
+	//{
+	//	printf("Loading %s\n",p_fileName); // REMOVE
+	//}
+	// Call the non-game-specific LoadQB, which open the qb and create all the symbols defined within.
+	Script::LoadQB(p_fileName, assertIfDuplicateSymbols);
+	
+	CSymbolTableEntry *p_sym=GetNextSymbolTableEntry();
+	while (p_sym)
+	{
+		if (p_sym->mGotReloaded && 
+			p_sym->mType==ESYMBOLTYPE_ARRAY &&
+			p_sym->mNameChecksum==0xc472ecc5/*NodeArray*/)
+		{
+			// This will resize the NodeArray, adding the extra nodes required by the goal editor.
+			// Note that p_sym->mpArray is still valid after doing this, because the resizing only
+			// causes a reallocation of a buffer inside the CArray, the CArray itself is not reallocated.
+			Obj::InsertGoalEditorNodes();
+			
+			CreateNodeNameHashTable();
+			GeneratePrefixInfo();
+
+			// Do a bit of node array processing.
+			int size=p_sym->mpArray->GetSize();
+			for (int i=0; impArray->GetStructure(i);
+				
+				// K: This swapping of Position for Pos used to be at a lower level in parse.cpp,
+				// but that had the problem that it was doing the swap on all structures, including those
+				// passed to commands in scripts. So I moved it here so as to only affect those structures
+				// in the node array.
+				Mth::Vector p(0.0f,0.0f,0.0f);
+				if (p_node->GetVector(CRCD(0xb9d31b0a,"Position"),&p))
+				{
+					// GJ:  The "position" currently written out 
+					// by the exporter has an incorrect Z, and there
+					// are a bunch of places in the game code which
+					// negate the Z value in order to correct it.
+					// We're currently in the process of phasing this
+					// "Position" out, in favor of the "Pos" vector, 
+					// which will have the correct Z.  For backwards
+					// compatibility, the code here removes the old
+					// "Position" vector if it exists and replaces 
+					// it with the new "Pos" vector.  Eventually, 
+					// once all the levels have been re-exported, 
+					// we can remove this kludge.
+					p_node->RemoveComponent(CRCD(0xb9d31b0a,"Position"));
+					p_node->AddVector(CRCD(0x7f261953,"Pos"),p[X],p[Y],-p[Z]);
+				}	
+			}
+			
+			// Do some path preprocessing. This adds PathNum parameters to all nodes that
+			// are on a path so that one can quickly find out what path a particular car is
+			// on without having to scan through a bunch of nodes.
+			Obj::CPathMan::Instance()->AddPathInfoToNodeArray();
+			
+			// Reset the reloaded flag, otherwise it will stick on forever.
+			p_sym->mGotReloaded=false;
+			
+			// No need to keep checking for more NodeArray's, there can be only one.
+			break;
+		}
+		p_sym=GetNextSymbolTableEntry(p_sym);
+	}		
+	
+	// Make sure checksum name lookup table is removed since it is only needed for the node array.
+	RemoveChecksumNameLookupHashTable();
+}
+
+// Calls the Script::UnloadQB and then does any game-specific stuff that needs to be done 
+// when a qb is unloaded, such as resetting the node name hash table & prefix info.
+void UnloadQB(uint32 fileNameChecksum)
+{
+	// Call the non-game-specific UnloadQB, which will delete all the symbols that came from this qb.
+	Script::UnloadQB(fileNameChecksum);
+	
+	if (!GetArray(0xc472ecc5/*NodeArray*/))
+	{
+		// If there is no node array any more then make sure the prefix info and node name lookup
+		// info is cleaned up.
+		DeletePrefixInfo();
+		ClearNodeNameHashTable();
+	}	
+}
+
+uint32 GenerateFileNameChecksum(const char *p_fileName)
+{
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+	
+	// TODO May need to process the file name a bit first, eg prefix it with the
+	// full path if it is not there already, before calculating its checksum, in order to ensure a match.
+	// We may want UnloadQB to be able to be passed a filename without the full path.
+	
+	// For the moment though, just calculate the checksum for the string as it is.
+	return Crc::GenerateCRCFromString(p_fileName);
+}
+
+void UnloadQB(const char *p_fileName)
+{
+	SkateScript::UnloadQB(GenerateFileNameChecksum(p_fileName));
+}
+
+// TODO Need to add the old 'DoIt' function which executes when a qbr is done. It does a LoadQB then
+// reloads the skater exceptions, updates the decks on the skateshop wall, etc...
+
+} // namespace SkateScript
+
diff --git a/Code/Sk/Scripting/gs_file.h b/Code/Sk/Scripting/gs_file.h
new file mode 100644
index 0000000..7b06860
--- /dev/null
+++ b/Code/Sk/Scripting/gs_file.h
@@ -0,0 +1,22 @@
+#ifndef	__SK_SCRIPTING_GS_FILE_H
+#define	__SK_SCRIPTING_GS_FILE_H
+
+#ifndef	__SCRIPTING_SCRIPTDEFS_H
+#include  // For enums
+#endif
+
+namespace SkateScript
+{
+using namespace Script;
+
+void LoadAllStartupQBFiles();
+void LoadQB(const char *p_fileName, 
+			EBoolAssertIfDuplicateSymbols assertIfDuplicateSymbols=NO_ASSERT_IF_DUPLICATE_SYMBOLS);
+void UnloadQB(uint32 fileNameChecksum);
+uint32 GenerateFileNameChecksum(const char *p_fileName);
+void UnloadQB(const char *p_fileName);
+
+} // namespace SkateScript
+
+#endif // #ifndef	__SK_SCRIPTING_GS_FILE_H
+
diff --git a/Code/Sk/Scripting/gs_init.cpp b/Code/Sk/Scripting/gs_init.cpp
new file mode 100644
index 0000000..9412d02
--- /dev/null
+++ b/Code/Sk/Scripting/gs_init.cpp
@@ -0,0 +1,128 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// gs_init.cpp		KSH 7 Nov 2001
+//
+// Game-specific script system initialisation.
+// Also calls the non-game-specific script initialisation.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace SkateScript
+{
+
+// Called once on startup.
+void Init()
+{
+	// Do the non-game-specific script system initialisation
+	Mem::PushMemProfile("Script::AllocatePools");
+	Script::AllocatePools();
+	Mem::PopMemProfile();
+	
+	
+	// Now do the game-specific script system initialisation
+	Mem::PushMemProfile("InitNodeNameHashTable");
+	InitNodeNameHashTable();
+	Mem::PopMemProfile();
+	
+
+	Mem::PushMemProfile("Registering script functions");
+	Script::RegisterCFunctions(CFunctionLookupTable,GetCFunctionLookupTableSize());
+	Script::RegisterMemberFunctions(ppMemberFunctionNames,GetNumMemberFunctions());
+	Mem::PopMemProfile();
+	
+}
+
+void Preload()
+{
+	LoadAllStartupQBFiles();
+	
+	#ifdef COUNT_USAGE
+	CSymbolTableEntry *p_sym=GetNextSymbolTableEntry();
+	while (p_sym)
+	{
+		if (p_sym->mType==ESYMBOLTYPE_QSCRIPT)
+		{
+			//printf("%s\n",Script::FindChecksumName(p_sym->mNameChecksum));
+			
+			uint8 *p_token=p_sym->mpScript;
+			// Skip over the 4-byte contents checksum.
+			p_token+=4;
+			
+			while (*p_token!=ESCRIPTTOKEN_KEYWORD_ENDSCRIPT) 
+			{
+				switch (*p_token)
+				{
+					case ESCRIPTTOKEN_NAME:
+					{
+						++p_token;
+						uint32 name_checksum=Read4Bytes(p_token).mChecksum;
+						
+						p_token+=4;
+			
+						// Look up the name to see if it is a cfunction or member function.
+						CSymbolTableEntry *p_entry=Resolve(name_checksum);
+						if (p_entry)
+						{
+							//if (name_checksum==0xb0bcbaec)
+							//printf("(%s) Type=%s\n",Script::FindChecksumName(name_checksum),Script::GetTypeName(p_entry->mType));
+						
+							if (p_entry->mType==ESYMBOLTYPE_CFUNCTION)
+							{
+								++p_entry->mUsage;
+								//printf("%s: %s\n",Script::FindChecksumName(p_sym->mNameChecksum),Script::FindChecksumName(name_checksum));
+							}
+							else if (p_entry->mType==ESYMBOLTYPE_MEMBERFUNCTION)
+							{
+								++p_entry->mUsage;
+							}	
+						}		
+						break;
+					}	
+					default:
+						p_token=SkipToken(p_token);
+						break;
+				}
+			}	
+		}
+		p_sym=GetNextSymbolTableEntry(p_sym);
+	}		
+	
+	p_sym=GetNextSymbolTableEntry();
+	while (p_sym)
+	{
+		if (p_sym->mUsage==0)
+		{
+			if (p_sym->mType==ESYMBOLTYPE_CFUNCTION)
+			{
+				printf("CFunc %s: Usage=%d\n",Script::FindChecksumName(p_sym->mNameChecksum),p_sym->mUsage);
+			}
+			if (p_sym->mType==ESYMBOLTYPE_MEMBERFUNCTION)
+			{
+				//printf("MemberFunc %s: Usage=%d\n",Script::FindChecksumName(p_sym->mNameChecksum),p_sym->mUsage);
+			}
+		}	
+		p_sym=GetNextSymbolTableEntry(p_sym);
+	}		
+	
+	printf("Finished\n");
+	while (1);
+	#endif // #ifdef COUNT_USAGE
+
+
+
+	
+	// Now that all the qb's are loaded, pre-process all the scripts.
+	//PreProcessScripts();
+}
+
+} // namespace SkateScript
+
diff --git a/Code/Sk/Scripting/gs_init.h b/Code/Sk/Scripting/gs_init.h
new file mode 100644
index 0000000..d4fe8e2
--- /dev/null
+++ b/Code/Sk/Scripting/gs_init.h
@@ -0,0 +1,12 @@
+#ifndef	__SK_SCRIPTING_GS_INIT_H
+#define	__SK_SCRIPTING_GS_INIT_H
+
+namespace SkateScript
+{
+
+void Init();
+void Preload();
+
+} // namespace SkateScript
+
+#endif // #ifndef __SK_SCRIPTING_GS_INIT_H
diff --git a/Code/Sk/Scripting/mcfuncs.cpp b/Code/Sk/Scripting/mcfuncs.cpp
new file mode 100644
index 0000000..c15fc46
--- /dev/null
+++ b/Code/Sk/Scripting/mcfuncs.cpp
@@ -0,0 +1,4897 @@
+//****************************************************************************
+//* MODULE:         Sk/Scripting
+//* FILENAME:       McFuncs.cpp
+//* OWNER:          Kendall Harrison
+//* CREATION DATE:  6/10/2002
+//****************************************************************************
+
+// Contains Mem card stuff.
+
+// TODO: Make all local variables conform to the naming convention! Currently they are a mix
+
+// start autoduck documentation
+// @DOC cfuncs
+// @module cfuncs | None
+// @subindex Scripting Database
+// @index script | cfuncs
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGPS__
+#include 
+#endif // __PLAT_NGPS__
+
+#ifdef __PLAT_NGC__
+#include "sys/ngc/p_file.h"
+#include "sys/ngc/p_buffer.h"
+#include "sys/ngc/p_dma.h"
+#include "sys/ngc/p_aram.h"
+bool g_mc_hack = false;
+uint32 g_hack_address = 0;
+extern char * g_p_buffer;
+#endif		// __PLAT_NGC__
+
+namespace CFuncs
+{
+
+using namespace Script;
+	
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// Whenever the format of one of these file types has changed such that
+// an old save will not load, increment its version number.
+/*
+// GJ:  I moved this to memcard.q, because the artists/designers sometimes
+// do things that will require a version change.
+
+#define VERSION_OPTIONSANDPROS 10
+#define VERSION_NETWORKSETTINGS 3
+#define VERSION_CAS 4
+#define VERSION_CAT 1
+#define VERSION_PARK 2
+#define VERSION_REPLAY 6
+*/
+
+// The max number of files that can appear in the file list.
+// A max is specified both to ensure we don't run out of memory in the files menu, and
+// also to limit the pause before the menu comes up because it has to open each file first
+// to extract the summary info.
+#define MAX_THPS4_FILES_ALLOWED 75
+
+// Limit the space that the summary info structure is allowed to take up when written to a buffer.
+// This is because we want to be able to quickly read in the summary info from the start of the
+// mem card file without having to read in the whole file. (Units are bytes)
+#define MAX_SUMMARY_INFO_SIZE 100
+
+// Max chars allowed in the low level card filename.
+#define MAX_CARD_FILE_NAME_CHARS 100
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+// This value gets added to the character 'a' in the last char of the 
+// low-level card filename to indicate what the type of the file is.
+enum EMemCardFileType
+{
+	MEMCARD_FILETYPE_CAREER,
+	MEMCARD_FILETYPE_CAS,
+	MEMCARD_FILETYPE_PARK,
+	MEMCARD_FILETYPE_REPLAY,
+	MEMCARD_FILETYPE_NETWORKSETTINGS,
+    MEMCARD_FILETYPE_CAT,
+    MEMCARD_FILETYPE_CREATEDGOALS,
+};
+
+// The old OptionsAndPros save file now contains the custom skater too, and is divided into 4 parts.
+// The LoadFromMemoryCard command provides the option of only applying certain parts to the game state.
+// For example, when loading a custom skater, only the CUSTOM_SKATER part is applied, so the career
+// remains unaffected.
+enum EUnifiedSaveFileSubTypes
+{
+	APPLY_GLOBAL_INFO=0,
+	APPLY_STORY=1,
+	APPLY_STORY_SKATER=2,
+	APPLY_CUSTOM_SKATER=3,
+};
+	
+#ifdef __PLAT_NGC__
+// Icons plus banner.
+#define	NGC_MEMCARD_ICON_DATA_SIZE		( 64 + ( 32 * 32 * 7 ) + ( 256 * 2 ) + ( 96 * 32 ) + ( 256 * 2 ) )
+#endif
+
+// The mem card file starts with one of these, followed by two CStruct's as written
+// out using WriteToBuffer.
+// The first CStruct is the summary info for the file and occupies no more than
+// MAX_SUMMARY_INFO_SIZE bytes.
+// The second CStruct holds all the save data.
+struct SMcFileHeader
+{
+	#ifdef __PLAT_NGC__
+	// On the GameCube the icon data is stored within the file rather than as a
+	// seperate file. So lets wack it into this structure.
+	uint8 mpIconData[NGC_MEMCARD_ICON_DATA_SIZE];
+	#endif
+	
+	#ifdef __PLAT_XBOX__
+	// I think it is a TRC requirement to use the XBox's special way of calculating
+	// a checksum. They probably have ways of telling whether we're using it or not.
+	XCALCSIG_SIGNATURE mSignature;
+	#else
+	uint32 mChecksum;
+	#endif // #ifdef __PLAT_XBOX__
+	
+	// This is so that the files menu can see if a file might be bad 
+	// given only the summary info.
+	uint32 mSummaryInfoChecksum;
+	int mSummaryInfoSize;
+
+	// This is the size of this header, the summary info structure, and
+	// the main structure, rounded up to the platforms block size.
+	// So for all file types except replays, this should match the total file size.
+	// In the case of replays, the replay data comes after that lot.
+	// mDataSize is needed to indicate the start of the replay data.
+	int mDataSize;
+	
+	int mVersion;
+};
+	
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+#ifndef __PLAT_NGC__
+static unsigned short s_ascii_special[33][2] = {
+	{0x8140, 32},		/*   */
+	{0x8149, 33},		/* ! */
+	{0x8168, 34},		/* " */
+	{0x8194, 35},		/* # */
+	{0x8190, 36},		/* $ */
+	{0x8193, 37},		/* % */
+	{0x8195, 38},		/* & */
+	{0x8166, 39},		/* ' */
+	{0x8169, 40},		/* ( */
+	{0x816a, 41},		/* ) */
+	{0x8196, 42},		/* * */
+	{0x817b, 43},		/* + */
+	{0x8143, 44},		/* , */
+	{0x817c, 45},		/* - */
+	{0x8144, 46},		/* . */
+	{0x815e, 47},		/* / */
+	{0x8146, 58},		/* : */
+	{0x8147, 59},		/* ; */
+	{0x8171, 60},		/* < */
+	{0x8181, 61},		/* = */
+	{0x8172, 62},		/* > */
+	{0x8148, 63},		/* ? */
+	{0x8197, 64},		/* @ */
+	{0x816d, 91},		/* [ */
+	{0x818f, 92},		/* \ */
+	{0x816e, 93},		/* ] */
+	{0x814f, 94},		/* ^ */
+	{0x8151, 95},		/* _ */
+	{0x8165, 96},		/* ` */
+	{0x816f, 123},		/* { */
+	{0x8162, 124},		/* | */
+	{0x8170, 125},		/* } */
+	{0x8150, 126},		/* ~ */
+};
+
+static unsigned short s_ascii_table[3][2] = {
+	{0x824f, 0x30},	/* 0-9  */
+	{0x8260, 0x41},	/* A-Z  */
+	{0x8281, 0x61},	/* a-z  */
+};
+#endif // #ifndef __PLAT_NGC__
+
+// Used by the SaveFailedDueToInsufficientSpace command.
+static bool s_insufficient_space=false;
+
+#if __USE_REPLAYS__
+static char spReplayCardFileName[MAX_CARD_FILE_NAME_CHARS+1];
+static bool sNeedToLoadReplayBuffer=false;
+#endif
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+static const char *s_generate_ascii_checksum(char *p_dest, const char *p_string, uint32 fileType=0);
+static uint32 s_determine_file_type(char c);
+#ifndef __PLAT_NGC__
+static unsigned short s_ascii_to_sjis(unsigned char ascii_code);
+#endif
+static void s_insert_global_info(CStruct *p_struct);
+static void s_insert_story_info(CStruct *p_struct);
+static void s_insert_story_skater_info(CStruct *p_struct);
+static void s_insert_custom_skater_info(CStruct *p_struct);
+static void s_insert_game_save_info(uint32 fileType, CStruct *p_struct);
+static void s_generate_summary_info(CStruct *p_summaryInfo, uint32 fileType, CStruct *p_mainData);
+
+static void s_read_global_info(CStruct *p_globalInfo, CScript *p_script);
+static void s_read_story_info(CStruct *p_storyInfo);
+static void s_read_story_skater_info(CStruct *p_storySkaterInfo, CStruct *p_customSkater);
+static void s_read_custom_skater_info(CStruct *p_customSkater);
+static void s_read_game_save_info(uint32 fileType, CStruct *p_struct, CScript *p_script);
+
+static int s_get_icon_k_required(uint32 fileType);
+static int s_calculate_total_space_used_on_card(uint32 fileType, int fileSize);
+static int s_get_platforms_block_size();
+static int s_round_up_to_platforms_block_size(int fileSize);
+static int s_get_version_number(uint32 fileType);
+static const char *s_generate_xbox_directory_name(uint32 fileType, const char *p_name);
+static void s_generate_card_directory_name(uint32 fileType, const char *p_name, char *p_card_directory_name);
+static bool s_make_xbox_dir_and_icons(	Mc::Card *p_card,
+										uint32 fileType, const char *p_name, 
+										char *p_card_file_name,
+										bool *p_insufficientSpace);
+static bool s_make_ps2_dir_and_icons(	Mc::Card *p_card,
+										uint32 fileType, const char *p_name, 
+										char *p_card_file_name,
+										bool *p_insufficientSpace);
+static bool s_insert_ngc_icon(	SMcFileHeader *p_fileHeader, 
+								Mc::Card *p_card,
+								Mc::File *p_file,
+								uint32 fileType,
+								const char *p_name);
+static uint32 sGetFixedFileSize(uint32 fileType);
+
+
+
+// When transferring data between the online vault and the mem card, this structure temporarily holds
+// the data. The LoadFromMemoryCard and SaveToMemoryCard commands can be made to load to/from this structure
+// instead, so that data can be transferred without loading it into the game.
+static Script::CStruct *spVaultData=NULL;
+static uint32 sVaultDataType=0;
+
+// Steve's code calls this after downloading the binary data from the vault.
+void SetVaultData(uint8 *p_data, uint32 type)
+{
+	if (spVaultData)
+	{
+		delete spVaultData;
+		spVaultData=NULL;
+	}
+	
+	spVaultData=new Script::CStruct;
+	Script::ReadFromBuffer(spVaultData,p_data);
+	sVaultDataType=type;
+}
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This calculates an 8 character ascii checksum of the passed string.
+// Used for generating the filename of the memory card file from the name the player has chosen.
+
+// It works by calculating a 32 bit checksum the usual way, then
+// converting this number to base 26 and using 'a' to represent 0, 'b' for 1, etc.
+// Log to base 26 of 2^32 is 6.8, which means at most 7 ascii characters will be needed, so the 
+// 8th letter in the string will actually always be a. So the last letter is used to indicate the file type.
+static const char *s_generate_ascii_checksum(char *p_dest, const char *p_string, uint32 fileType)
+{
+	Dbg_MsgAssert(p_dest,("NULL p_dest"));
+	
+	uint32 Checksum=Script::GenerateCRC(p_string);
+	for (int i=0; i<8; ++i)
+	{
+		int Rem=Checksum%26;
+		p_dest[i]='a'+Rem;
+		Checksum=(Checksum-Rem)/26;
+	}	
+	// Check that mathematics is still working ok
+	Dbg_MsgAssert(Checksum==0,("Checksum not zero ???"));
+	Dbg_MsgAssert(p_dest[7]=='a',("Last letter of ascii checksum not 'a' ???"));
+
+	switch (fileType)
+	{
+		case 0xb010f357: // OptionsAndPros
+			p_dest[7]='a'+MEMCARD_FILETYPE_CAREER;
+			break;
+		case 0xffc529f4: // Cas
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			//p_dest[7]='a'+MEMCARD_FILETYPE_CAS;
+			break;
+        case 0x61a1bc57: // CAT
+			p_dest[7]='a'+MEMCARD_FILETYPE_CAT;
+			break;
+		case 0x3bf882cc: // Park
+			p_dest[7]='a'+MEMCARD_FILETYPE_PARK;
+			break;
+		case 0x26c80b0d: // Replay
+			p_dest[7]='a'+MEMCARD_FILETYPE_REPLAY;
+			break;
+		case 0xca41692d: // NetworkSettings
+			p_dest[7]='a'+MEMCARD_FILETYPE_NETWORKSETTINGS;
+			break;
+		case 0x62896edf: // CreatedGoals
+			p_dest[7]='a'+MEMCARD_FILETYPE_CREATEDGOALS;
+			break;
+		default:
+			break;
+	}
+	
+	p_dest[8]=0;
+	return p_dest;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static uint32 s_determine_file_type(char c)
+{
+	switch (c-'a')
+	{
+	case MEMCARD_FILETYPE_CAREER:
+		return 0xb010f357; // OptionsAndPros
+		break;
+	case MEMCARD_FILETYPE_CAS:
+		return 0xffc529f4; // Cas
+		break;
+    case MEMCARD_FILETYPE_CAT:
+		return 0x61a1bc57; // CAT
+		break;
+	case MEMCARD_FILETYPE_PARK:
+		return 0x3bf882cc; // Park
+		break;
+	case MEMCARD_FILETYPE_REPLAY:	
+		return 0x26c80b0d; // Replay
+		break;
+	case MEMCARD_FILETYPE_NETWORKSETTINGS:	
+		return 0xca41692d; // NetworkSettings
+		break;
+	case MEMCARD_FILETYPE_CREATEDGOALS:	
+		return 0x62896edf; // CreatedGoals
+		break;
+	default:
+		return 0;
+		break;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+#ifndef __PLAT_NGC__
+static unsigned short s_ascii_to_sjis(unsigned char ascii_code)
+{
+	if (Config::PAL())
+	{
+		// The PS2 shell will not display underscores (it shows them as spaces)
+		// so convert them to hyphens.
+		if (ascii_code=='_')
+		{
+			ascii_code='-';
+		}	
+	}	
+
+	switch ((char)ascii_code)
+	{
+		case 'ß': ascii_code='B'; break;
+		case 'Ä': ascii_code='A'; break;
+		case 'Ü': ascii_code='U'; break;
+		case 'Ö': ascii_code='O'; break;
+		case 'à': ascii_code='a'; break;
+		case 'â': ascii_code='a'; break;
+		case 'ä': ascii_code='a'; break;
+		case 'ê': ascii_code='e'; break;
+		case 'è': ascii_code='e'; break;
+		case 'é': ascii_code='e'; break;
+		case 'ë': ascii_code='e'; break;
+		case 'ì': ascii_code='i'; break;
+		case 'î': ascii_code='i'; break;
+		case 'ï': ascii_code='i'; break;
+		case 'ô': ascii_code='o'; break;
+		case 'ò': ascii_code='o'; break;
+		case 'ö': ascii_code='o'; break;
+		case 'ù': ascii_code='u'; break;
+		case 'û': ascii_code='u'; break;
+		case 'ü': ascii_code='u'; break;
+		case 'ç': ascii_code='c'; break;
+		case 'œ': ascii_code='o'; break;
+		default: break;
+	}
+	
+	unsigned short sjis_code = 0;
+	unsigned char stmp=0;
+	unsigned char stmp2 = 0;
+
+	if((ascii_code >= 0x20) && (ascii_code <= 0x2f))
+		stmp2 = 1;
+	
+	else if((ascii_code >= 0x30) && (ascii_code <= 0x39))
+		stmp = 0;
+	
+	else if((ascii_code >= 0x3a) && (ascii_code <= 0x40))
+		stmp2 = 11;
+	
+	else if((ascii_code >= 0x41) && (ascii_code <= 0x5a))
+		stmp = 1;
+	
+	else if((ascii_code >= 0x5b) && (ascii_code <= 0x60))
+		stmp2 = 37;
+	
+	else if((ascii_code >= 0x61) && (ascii_code <= 0x7a))
+		stmp = 2;
+	
+	else if((ascii_code >= 0x7b) && (ascii_code <= 0x7e))
+		stmp2 = 63;
+	
+	else 
+	{
+		printf("bad ASCII code 0x%x\n", ascii_code);
+		return 0;
+	}
+
+	if (stmp2)
+	   	sjis_code = s_ascii_special[ascii_code - 0x20 - (stmp2 - 1)][0];
+	else
+		sjis_code = s_ascii_table[stmp][0] + ascii_code - s_ascii_table[stmp][1];
+
+	return sjis_code;
+}
+#endif		// __PLAT_NGC__
+
+static void s_insert_global_info(CStruct *p_struct)
+{
+	// Build the global options structure and insert it into p_struct.
+	
+	Script::CStruct *pOptions=new Script::CStruct;
+	
+	// Attach the split screen preferences.
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Prefs::Preferences *pPreferences = pSkate->GetSplitScreenPreferences();
+	Dbg_MsgAssert(pPreferences,("NULL split screen pPreferences"));
+	
+	// Create a new structure, append the data contained in the preferences, then insert the pointer to the
+	// new structure into pOptions.
+	Script::CStruct *pTemp=new Script::CStruct;
+	pTemp->AppendStructure(pPreferences->GetRoot());
+	pOptions->AddStructurePointer(CRCD(0xf7720c3f,"SplitScreenPreferences"),pTemp);
+
+	// Get the sound options and stick them in a structure and add that.
+	pTemp=new Script::CStruct;
+	
+	Sfx::CSfxManager * pSfxManager = Sfx::CSfxManager::Instance();
+	float MainVolume=pSfxManager->GetMainVolume();
+	pTemp->AddFloat(CRCD(0x6f016dfb,"MainVolume"),MainVolume);
+
+	float MusicVolume=Pcm::GetVolume();
+	pTemp->AddFloat(CRCD(0xabd4a575,"MusicVolume"),MusicVolume);
+	
+	//uint64 PlayListForbiddenTrackFlags=Pcm::GetPlaylist();
+    uint64 list1,list2;
+    Pcm::GetPlaylist(&list1, &list2);
+
+	uint32 PlayListForbiddenTrackFlags1=(uint32)(list1>>32);
+	uint32 PlayListForbiddenTrackFlags2=(uint32)list1;
+
+    uint32 PlayListForbiddenTrackFlags3=(uint32)(list2>>32);
+	uint32 PlayListForbiddenTrackFlags4=(uint32)list2;
+	
+	pTemp->AddInteger(CRCD(0x595d2c95,"PlayListForbiddenTrackFlags1"),PlayListForbiddenTrackFlags1);
+	pTemp->AddInteger(CRCD(0xc0547d2f,"PlayListForbiddenTrackFlags2"),PlayListForbiddenTrackFlags2);
+    pTemp->AddInteger(CRCD(0xb7534db9,"PlayListForbiddenTrackFlags3"),PlayListForbiddenTrackFlags3);
+	pTemp->AddInteger(CRCD(0x2937d81a,"PlayListForbiddenTrackFlags4"),PlayListForbiddenTrackFlags4);
+
+	// current_soundtrack only applies to XBox
+	if (Config::GetHardware()==Config::HARDWARE_XBOX)
+	{
+		pTemp->AddChecksum(CRCD(0xe1f7c4ae,"current_soundtrack"),Script::GetChecksum("current_soundtrack"));
+	}	
+	
+	if (Pcm::GetRandomMode())
+	{
+		pTemp->AddChecksum(NONAME,CRCD(0x31c71b70,"RandomMode"));
+	}	
+	
+	pOptions->AddStructurePointer(CRCD(0x89eb9738,"SoundOptions"),pTemp);
+
+	// Add the controller preferences.
+	Script::CArray *pControllerPrefs=new Script::CArray;
+	pControllerPrefs->SetSizeAndType(Mdl::Skate::vMAX_SKATERS, ESYMBOLTYPE_STRUCTURE);
+	for (int i=0; imp_controller_preferences[i].AutoKickOn)
+		{
+			pTemp->AddChecksum(NONAME,CRCD(0x1eef7085,"AutoKickOn"));
+		}	
+		if (pSkate->mp_controller_preferences[i].SpinTapsOn)
+		{
+			pTemp->AddChecksum(NONAME,CRCD(0xa483ba67,"SpinTapsOn"));
+		}	
+		if (pSkate->mp_controller_preferences[i].VibrationOn)
+		{
+			pTemp->AddChecksum(NONAME,CRCD(0x73cee124,"VibrationOn"));
+		}	
+		pControllerPrefs->SetStructure(i,pTemp);
+	}			
+	
+	pOptions->AddArrayPointer(CRCD(0x37bdb853,"ControllerPreferences"),pControllerPrefs);
+
+	// Insert the taunt stuff
+	GameNet::Manager * pGamenet = GameNet::Manager::Instance();
+	pPreferences=pGamenet->GetTauntPreferences();
+	Dbg_MsgAssert(pPreferences,("NULL taunt pPreferences"));
+	pOptions->AddStructure(CRCD(0xe62b6586,"Taunts"),pPreferences->GetRoot());
+
+	// Now insert the pointer to the newly constucted pOptions into the mem card structure.
+	p_struct->AddStructurePointer(CRCD(0x2fca0578,"Options"),pOptions);
+
+	// save the career (Game progress, flags and gap checklist)			
+	Mdl::Skate::Instance()->GetCareer()->WriteIntoStructure(p_struct);
+
+	// Add the game records.		  
+	Records::CGameRecords *pGameRecords=pSkate->GetGameRecords();
+	Dbg_MsgAssert(pGameRecords,("NULL pGameRecords"));
+	pGameRecords->WriteIntoStructure(p_struct);
+	
+	// Wack in the pro skater profile info
+	Script::CStruct *p_pros=new Script::CStruct;
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+	Dbg_MsgAssert(pPlayerProfileManager,("NULL pPlayerProfileManager"));
+	pPlayerProfileManager->AddAllProProfileInfo(p_pros);
+	p_struct->AddStructurePointer(CRCD(0xc9986baf,"Pros"),p_pros);
+	
+	// Note: Mustn't delete pOptions or the other structures created above
+	// since pointers to these have been given to p_struct.
+	// p_struct will clean them up when it gets deleted.
+}
+
+static void s_insert_story_info(CStruct *p_struct)
+{
+	// Save the goal manager parameters.
+	Game::CGoalManager* p_goal_manager=Game::GetGoalManager();
+	Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager"));
+	
+	CStruct *p_goal_manager_params=p_goal_manager->GetGoalManagerParams();
+	Dbg_MsgAssert(p_goal_manager_params,("NULL p_goal_manager_params"));
+	p_struct->AddStructure(CRCD(0xac7c2b62,"GoalManagerParams"),p_goal_manager_params);
+}
+
+static void s_insert_story_skater_info(CStruct *p_struct)
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = pSkate->GetSkater(0);
+	Dbg_Assert( pSkater );
+	pSkater->AddCATInfo(p_struct);
+	
+    // stat goal status
+    pSkater->AddStatGoalInfo(p_struct);
+    
+    // chapter status
+    pSkater->AddChapterStatusInfo(p_struct);
+}
+
+static void s_insert_custom_skater_info(CStruct *p_struct)
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+	Dbg_MsgAssert(pPlayerProfileManager,("NULL pPlayerProfileManager"));
+	
+	pPlayerProfileManager->AddCASProfileInfo(p_struct);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void s_insert_game_save_info(uint32 fileType, CStruct *p_struct)
+{
+	// WARNING !		WARNING !		WARNING !		WARNING !		WARNING !
+	// Make sure that no function in here stores any pointers to new CStructs.
+	// This is because all new CStructs (and CComponents) allocated here will be coming
+	// off a special pool, to avoid overflowing the regular pool.
+	
+	Dbg_MsgAssert(p_struct,("NULL p_struct"));
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	#ifdef	__NOPT_ASSERT__
+	#if 0 		// Actually remove it fully, as the code that uses it had been temp stubbed out,a nd we don't want to confuse the non final build into thinking it's there
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+	Dbg_MsgAssert(pPlayerProfileManager,("NULL pPlayerProfileManager"));
+	#endif
+	#endif
+	
+	switch (fileType)
+	{
+		case 0xb010f357: // OptionsAndPros
+		{
+			Script::CStruct *p_global_info=new Script::CStruct;
+			s_insert_global_info(p_global_info);
+			p_struct->AddStructurePointer(CRCD(0xf55cbd13,"GlobalInfo"),p_global_info);
+			
+			Script::CStruct *p_story=new Script::CStruct;
+			s_insert_story_info(p_story);
+			p_struct->AddStructurePointer(CRCD(0x14a9fbc7,"Story"),p_story);
+			
+			Script::CStruct *p_story_skater=new Script::CStruct;
+			s_insert_story_skater_info(p_story_skater);
+			p_struct->AddStructurePointer(CRCD(0xdf2f448,"StorySkater"),p_story_skater);
+			
+			Script::CStruct *p_custom_skater=new Script::CStruct;
+			s_insert_custom_skater_info(p_custom_skater);
+			p_struct->AddStructurePointer(CRCD(0x12bfac82,"CustomSkater"),p_custom_skater);
+			break;
+		}
+
+		case 0xca41692d: // NetworkSettings		
+		{
+			// Add the network preferences
+			GameNet::Manager * pGamenet = GameNet::Manager::Instance();
+			Prefs::Preferences *pPreferences=pGamenet->GetNetworkPreferences();
+			p_struct->AppendStructure(pPreferences->GetRoot());
+			break;
+		}	
+		
+		case 0xffc529f4: // Cas
+		{
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			/*		
+			pPlayerProfileManager->AddCASProfileInfo(p_struct);
+            
+            Obj::CSkater* pSkater = pSkate->GetSkater(0);
+            Dbg_Assert( pSkater );
+            pSkater->AddCATInfo(p_struct);
+			*/
+			break;
+		}
+
+        case 0x61a1bc57: // Cat
+		{
+			// Can only edit CAT on skater zero!
+            Obj::CSkater* pSkater = pSkate->GetSkater(0);
+            Dbg_Assert( pSkater );
+            
+            // Index is always 0 since that is the only one that can be edited directly.
+            Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[0];
+            Dbg_Assert( pCreatedTrick );
+
+            //Other params
+            p_struct->AddStructure( "other_params", pCreatedTrick->mp_other_params );
+            
+            //Rotation params
+            p_struct->AddArray( "rotation_info", pCreatedTrick->mp_rotations );
+            
+            //Animation params
+            p_struct->AddArray( "animation_info", pCreatedTrick->mp_animations );
+        
+            break;
+		}
+		
+		case 0x3bf882cc: // Park	 (Save)
+		{
+			Ed::CParkManager::Instance()->WriteIntoStructure(p_struct);
+			break;
+		}
+		
+		case 0x26c80b0d: // Replay
+		{
+#if __USE_REPLAYS__
+			Replay::AddReplayMemCardInfo(p_struct);
+#endif			
+			break;
+		}
+
+		case 0x62896edf: // CreatedGoals
+		{
+			Obj::CCompositeObject *p_obj=(Obj::CCompositeObject*)Obj::CTracker::Instance()->GetObject(CRCD(0x81f01058,"GoalEditor"));
+			Dbg_MsgAssert(p_obj,("No GoalEditor object"));
+			Obj::CGoalEditorComponent *p_goal_editor=GetGoalEditorComponentFromObject(p_obj);
+			Dbg_MsgAssert(p_goal_editor,("No goal editor component ???"));
+			p_goal_editor->WriteIntoStructure(p_struct);
+			break;
+		}
+		
+		default:
+		{
+			Dbg_MsgAssert(0,("Bad type of '%s' sent to s_insert_game_save_info",FindChecksumName(fileType)));
+			break;
+		}		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Inserts summary info for the specified file type into p_struct.
+// Summary info is a small amount of info that is written at the start of the mem card file
+// so that it can be quickly read out without having to read the whole file.
+// The info gets printed at the bottom of the files menu when the highlight is on a file.
+static void s_generate_summary_info(CStruct *p_summaryInfo, uint32 fileType, CStruct *p_mainData)
+{
+	// WARNING !		WARNING !		WARNING !		WARNING !		WARNING !
+	// Make sure that no function in here stores any pointers to new CStructs.
+	// This is because all new CStructs (and CComponents) allocated here will be coming
+	// off a special pool, to avoid overflowing the regular pool.
+	
+	Dbg_MsgAssert(p_summaryInfo,("NULL p_summaryInfo"));
+
+	if (p_mainData)
+	{
+		// Extract the summary info from the passed p_mainData.
+		// This is for when the save process is being done on data that got downloaded from the vault.
+		
+		switch (fileType)
+		{
+			case 0xb010f357: // OptionsAndPros
+			{
+				Script::CStruct *p_story=NULL;
+				p_mainData->GetStructure(CRCD(0x14a9fbc7,"Story"),&p_story,Script::ASSERT);
+				
+				Script::CStruct *p_goal_manager_params=NULL;
+				p_story->GetStructure(CRCD(0xac7c2b62,"GoalManagerParams"),&p_goal_manager_params,Script::ASSERT);
+
+				CStruct *p_more_goal_manager_params=NULL;
+				p_goal_manager_params->GetStructure(CRCD(0x23d4170a,"GoalManager_Params"),&p_more_goal_manager_params,Script::ASSERT);
+				
+				int chapter=0;
+				p_more_goal_manager_params->GetInteger(CRCD(0xf884773c,"CurrentChapter"),&chapter);
+				p_summaryInfo->AddInteger(CRCD(0xf884773c,"CurrentChapter"),chapter);
+				
+				// is_male is used by the script upload_content in net.q
+				Script::CStruct *p_custom_skater=NULL;
+				p_mainData->GetStructure(CRCD(0x12bfac82,"CustomSkater"),&p_custom_skater,Script::ASSERT);
+				Script::CStruct *p_custom=NULL;
+				p_custom_skater->GetStructure(CRCD(0xa7be964,"Custom"),&p_custom,Script::ASSERT);
+				Script::CStruct *p_info=NULL;
+				p_custom->GetStructure(CRCD(0x3476cea8,"Info"),&p_info,Script::ASSERT);
+                int is_male=0;
+				p_info->GetInteger(CRCD(0x3f813177,"is_male"),&is_male);
+				p_summaryInfo->AddInteger(CRCD(0x3f813177,"is_male"),is_male);
+                const char *p_name="";
+                p_info->GetString(CRCD(0x2ab66cb8,"display_name"),&p_name);
+				p_summaryInfo->AddString(CRCD(0xa1dc81f9,"name"),p_name);
+				break;
+			}	
+			
+			case 0xca41692d: // NetworkSettings		
+			{
+				p_summaryInfo->AddString("network_id","");
+				break;
+			}			
+			
+			case 0xffc529f4: // Cas
+			{
+				Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+				break;
+			}
+			
+            case 0x61a1bc57: // Cat
+            {
+                Script::CStruct *p_data=NULL;
+				p_mainData->GetStructure(CRCD(0x4137cb14,"other_params"),&p_data,Script::ASSERT);
+                
+                const char *p_name="";
+				p_data->GetString(CRCD(0xa1dc81f9,"name"),&p_name,Script::ASSERT);
+
+                p_summaryInfo->AddString(CRCD(0xa1dc81f9,"name"),p_name);
+                break;
+            }
+
+			case 0x26c80b0d: // Replay
+			{
+				break;
+			}
+	
+			case 0x3bf882cc: // Park
+			{
+				int num_edited_goals=0;
+				
+				Script::CArray *p_goals=NULL;
+				Script::CStruct *p_goals_struct=NULL;
+				p_mainData->GetStructure(CRCD(0xd8eb825e,"Park_editor_goals"),&p_goals_struct);
+				if (p_goals_struct)
+				{
+					p_goals_struct->GetArray(CRCD(0x38dbe1d0,"Goals"),&p_goals);
+					if (p_goals)
+					{
+						num_edited_goals=p_goals->GetSize();
+					}
+				}		
+				p_summaryInfo->AddInteger(CRCD(0xe1ec606f,"num_edited_goals"),num_edited_goals);
+				
+				int max_players=1;
+				p_mainData->GetInteger(CRCD(0xb7e39b53,"MaxPlayers"),&max_players);
+				p_summaryInfo->AddInteger(CRCD(0xb7e39b53,"MaxPlayers"),max_players);
+				
+				Script::CArray *p_map=NULL;
+				p_mainData->GetArray(CRCD(0x337c5289,"Park_editor_map"),&p_map,Script::ASSERT);
+				
+				// Used by the script upload_content in net.q
+				int num_gaps=0;
+				int num_metas=0;
+				uint16 theme=0;
+				uint32 tod_script=0;
+				int width=0;
+				int length=0;
+				Ed::CParkManager::Instance()->GetSummaryInfoFromBuffer((uint8*)p_map->GetArrayPointer(),&num_gaps,&num_metas,&theme,&tod_script,&width,&length);
+				p_summaryInfo->AddInteger(CRCD(0xe6121ed0,"num_gaps"),num_gaps);
+				p_summaryInfo->AddInteger(CRCD(0xfff3dc35,"num_pieces"),num_metas);
+				p_summaryInfo->AddInteger(CRCD(0x688a18f7,"theme"),theme);
+				p_summaryInfo->AddChecksum(CRCD(0x4c72ed98,"tod_script"),tod_script);
+				p_summaryInfo->AddInteger(CRCD(0x73e5bad0,"width"),width);
+				p_summaryInfo->AddInteger(CRCD(0xfe82614d,"length"),length);
+				break;
+			}
+				
+			case 0x62896edf: // CreatedGoals
+			{
+				int num_edited_goals=0;
+				
+				Script::CArray *p_goals=NULL;
+				p_mainData->GetArray(CRCD(0x38dbe1d0,"Goals"),&p_goals);
+				if (p_goals)
+				{
+					num_edited_goals=p_goals->GetSize();
+				}	
+				p_summaryInfo->AddInteger(CRCD(0xe1ec606f,"num_edited_goals"),num_edited_goals);
+				break;
+			}
+				
+			default:
+			{
+				Dbg_MsgAssert(0,("Bad type of '%s' sent to s_generate_summary_info",FindChecksumName(fileType)));
+				break;
+			}		
+		}		
+	}
+	else
+	{
+		// Get the summary info from the game.
+		
+		switch (fileType)
+		{
+			case 0xb010f357: // OptionsAndPros
+			{
+				Game::CGoalManager* p_goal_manager=Game::GetGoalManager();
+				Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager"));
+				
+				CStruct *p_goal_manager_params=p_goal_manager->GetGoalManagerParams();
+				Dbg_MsgAssert(p_goal_manager_params,("NULL p_goal_manager_params"));
+	
+				// Grab the summary info.
+				CStruct *p_more_goal_manager_params=NULL;
+				p_goal_manager_params->GetStructure(CRCD(0x23d4170a,"GoalManager_Params"),&p_more_goal_manager_params);
+				Dbg_MsgAssert(p_more_goal_manager_params,("No GoalManager_Params structure ??"));
+				
+				int chapter=0;
+				p_more_goal_manager_params->GetInteger(CRCD(0xf884773c,"CurrentChapter"),&chapter);
+				p_summaryInfo->AddInteger(CRCD(0xf884773c,"CurrentChapter"),chapter);
+				break;
+			}	
+			
+			case 0xca41692d: // NetworkSettings		
+			{
+				GameNet::Manager * pGamenet = GameNet::Manager::Instance();
+				Prefs::Preferences *pPreferences=pGamenet->GetNetworkPreferences();
+				CStruct *p_net_stuff=pPreferences->GetRoot();
+				
+				// Grab the summary info.
+				CStruct *p_foo=NULL;
+				p_net_stuff->GetStructure("network_id",&p_foo);
+				Dbg_MsgAssert(p_foo,("No network_id structure ?"));
+				
+				const char *p_name="";
+				p_foo->GetString("ui_string",&p_name);
+				
+				p_summaryInfo->AddString("network_id",p_name);
+				break;
+			}			
+			
+			case 0xffc529f4: // Cas
+			{
+				Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));			
+				break;
+			}
+	
+			case 0x61a1bc57: // Cat
+			{
+				// Can only edit CAT on skater zero!
+                Mdl::Skate * pSkate = Mdl::Skate::Instance();
+                Obj::CSkater* pSkater = pSkate->GetSkater(0);
+                Dbg_Assert( pSkater );
+                
+                // Index is always 0 since that is the only one that can be edited directly.
+                Game::CCreateATrick* pCreatedTrick = pSkater->m_created_trick[0];
+                Dbg_Assert( pCreatedTrick );
+    
+                const char *p_name="";
+				pCreatedTrick->mp_other_params->GetString(CRCD(0xa1dc81f9,"name"),&p_name,Script::ASSERT);
+
+                p_summaryInfo->AddString(CRCD(0xa1dc81f9,"name"),p_name);
+                break;
+			}
+			
+			case 0x26c80b0d: // Replay
+			{
+	#if __USE_REPLAYS__
+				Replay::AddReplayMemCardSummaryInfo(p_summaryInfo);
+	#endif			
+				break;
+			}
+			
+			case 0x3bf882cc: // Park
+			case 0x62896edf: // CreatedGoals
+			{
+				Obj::CCompositeObject *p_obj=(Obj::CCompositeObject*)Obj::CTracker::Instance()->GetObject(CRCD(0x81f01058,"GoalEditor"));
+				Dbg_MsgAssert(p_obj,("No GoalEditor object"));
+				Obj::CGoalEditorComponent *p_goal_editor=GetGoalEditorComponentFromObject(p_obj);
+				Dbg_MsgAssert(p_goal_editor,("No goal editor component ???"));
+				
+				p_summaryInfo->AddInteger(CRCD(0xe1ec606f,"num_edited_goals"),p_goal_editor->GetNumGoals());
+				
+				if (fileType==CRCD(0x3bf882cc,"Park"))
+				{
+					p_summaryInfo->AddInteger(CRCD(0xb7e39b53,"MaxPlayers"),Ed::CParkManager::Instance()->GetGenerator()->GetMaxPlayers());
+					
+					int num_gaps=0;
+					int num_metas=0;
+					uint16 theme=0;
+					uint32 tod_script=0;
+					int width=0;
+					int length=0;
+					Ed::CParkManager::Instance()->WriteCompressedMapBuffer();		// Ensure map buffer is correct
+					Ed::CParkManager::Instance()->GetSummaryInfoFromBuffer(Ed::CParkManager::Instance()->GetCompressedMapBuffer(),&num_gaps,&num_metas,&theme,&tod_script,&width,&length);
+					p_summaryInfo->AddInteger(CRCD(0xe6121ed0,"num_gaps"),num_gaps);
+					p_summaryInfo->AddInteger(CRCD(0xfff3dc35,"num_pieces"),num_metas);
+					p_summaryInfo->AddInteger(CRCD(0x688a18f7,"theme"),theme);
+					p_summaryInfo->AddChecksum(CRCD(0x4c72ed98,"tod_script"),tod_script);
+					p_summaryInfo->AddInteger(CRCD(0x73e5bad0,"width"),width);
+					p_summaryInfo->AddInteger(CRCD(0xfe82614d,"length"),length);
+				}	
+				break;
+			}
+				
+			default:
+			{
+				Dbg_MsgAssert(0,("Bad type of '%s' sent to s_generate_summary_info",FindChecksumName(fileType)));
+				break;
+			}		
+		}		
+	}	
+}
+
+static void s_read_global_info(CStruct *p_globalInfo, CScript *p_script)
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	
+	Script::CStruct *pOptions=NULL;
+	p_globalInfo->GetStructure(CRCD(0x2fca0578,"Options"),&pOptions);
+	Dbg_MsgAssert(pOptions,("p_globalInfo is missing Options structure"));
+	
+	// Extract the split screen preferences
+	Script::CStruct *pTemp=NULL;
+	pOptions->GetStructure(CRCD(0xf7720c3f,"SplitScreenPreferences"),&pTemp);
+	
+	Prefs::Preferences *pPreferences = pSkate->GetSplitScreenPreferences();
+	Dbg_MsgAssert(pPreferences,("NULL split screen pPreferences"));
+	pPreferences->SetRoot(pTemp);
+
+	// Get the taunt preferences
+	pTemp=NULL;
+	pOptions->GetStructure(CRCD(0xe62b6586,"Taunts"),&pTemp);
+	
+	GameNet::Manager * pGamenet = GameNet::Manager::Instance();
+	pPreferences=pGamenet->GetTauntPreferences();
+	Dbg_MsgAssert(pPreferences,("NULL taunt pPreferences"));
+	pPreferences->SetRoot(pTemp);
+
+	
+	// Get the sound options.
+	pOptions->GetStructure(CRCD(0x89eb9738,"SoundOptions"),&pTemp);
+	Sfx::CSfxManager * pSfxManager = Sfx::CSfxManager::Instance();
+	float MainVolume=0;
+	if (pTemp->GetFloat(CRCD(0x6f016dfb,"MainVolume"),&MainVolume))
+	{
+		pSfxManager->SetMainVolume(MainVolume);
+	}	
+
+	float MusicVolume=0;
+	if (pTemp->GetFloat(CRCD(0xabd4a575,"MusicVolume"),&MusicVolume))
+	{
+		Pcm::SetVolume(MusicVolume);
+	}	
+
+	uint64 PlayListForbiddenTrackFlagsA=0;
+    uint64 PlayListForbiddenTrackFlagsB=0;
+	uint32 PlayListForbiddenTrackFlags1=0;
+	uint32 PlayListForbiddenTrackFlags2=0;
+    uint32 PlayListForbiddenTrackFlags3=0;
+	uint32 PlayListForbiddenTrackFlags4=0;
+
+	if (pTemp->GetInteger(CRCD(0x595d2c95,"PlayListForbiddenTrackFlags1"),(int*)&PlayListForbiddenTrackFlags1))
+	{
+		pTemp->GetInteger(CRCD(0xc0547d2f,"PlayListForbiddenTrackFlags2"),(int*)&PlayListForbiddenTrackFlags2);
+        pTemp->GetInteger(CRCD(0xb7534db9,"PlayListForbiddenTrackFlags3"),(int*)&PlayListForbiddenTrackFlags3);
+        pTemp->GetInteger(CRCD(0x2937d81a,"PlayListForbiddenTrackFlags4"),(int*)&PlayListForbiddenTrackFlags4);
+		
+		PlayListForbiddenTrackFlagsA=(((uint64)PlayListForbiddenTrackFlags1)<<32)+PlayListForbiddenTrackFlags2;
+        PlayListForbiddenTrackFlagsB=(((uint64)PlayListForbiddenTrackFlags3)<<32)+PlayListForbiddenTrackFlags4;
+		Pcm::SetPlaylist(PlayListForbiddenTrackFlagsA, PlayListForbiddenTrackFlagsB);
+	}	
+
+	if (pTemp->ContainsFlag(CRCD(0x31c71b70,"RandomMode")))
+	{
+		Pcm::SetRandomMode(true);
+	}
+	else
+	{
+		Pcm::SetRandomMode(false);
+	}
+
+	if (Config::GetHardware()==Config::HARDWARE_XBOX)
+	{
+		uint32 current_soundtrack=0xffffffff;
+		pTemp->GetChecksum(CRCD(0xe1f7c4ae,"current_soundtrack"),¤t_soundtrack);
+		
+		Script::CSymbolTableEntry *p_sym=Script::LookUpSymbol(0xe1f7c4ae/*current_soundtrack*/);
+		if (p_sym)
+		{
+			Dbg_MsgAssert(p_sym->mType==ESYMBOLTYPE_NAME,("Expected current_soundtrack to have type checksum"));
+			p_sym->mChecksum=current_soundtrack;
+		}	
+		Script::CStruct* pTemp2 = new Script::CStruct;
+        pTemp2->AddChecksum(CRCD(0x8c465905,"trackchecksum"),current_soundtrack);
+        Script::RunScript(CRCD(0xd44400c2,"set_loaded_soundtrack"), pTemp2);
+	}	
+
+	// Extract the controller preferences
+	Script::CArray *pControllerPrefs=NULL;
+	if (pOptions->GetArray(CRCD(0x37bdb853,"ControllerPreferences"),&pControllerPrefs))
+	{
+		for (int i=0; iGetStructure(i);
+			Dbg_MsgAssert(pPrefs,("NULL pPrefs?"));
+
+			pSkate->SetVibration(i,pPrefs->ContainsFlag(CRCD(0x73cee124,"VibrationOn")));
+			pSkate->SetAutoKick(i,pPrefs->ContainsFlag(CRCD(0x1eef7085,"AutoKickOn")));
+			pSkate->SetSpinTaps(i,pPrefs->ContainsFlag(CRCD(0xa483ba67,"SpinTapsOn")));
+		}	
+	}
+	
+	// Extract the game records.		  
+	Records::CGameRecords *pGameRecords=pSkate->GetGameRecords();
+	Dbg_MsgAssert(pGameRecords,("NULL pGameRecords"));
+	pGameRecords->ReadFromStructure(p_globalInfo);
+	
+	// Extract the pro & cas skater profile info
+	Script::CStruct *p_pros=NULL;
+	p_globalInfo->GetStructure(CRCD(0xc9986baf,"Pros"),&p_pros,Script::ASSERT);
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+	pPlayerProfileManager->LoadAllProProfileInfo(p_pros);
+
+	// Extract the career info (Game progress, flags and gap checklist)			
+	Mdl::Skate::Instance()->GetCareer()->ReadFromStructure(p_globalInfo);
+	
+	// Return the LastLevelLoadScript and LastGameMode to the calling script.
+	// Needed by Zac so that after autoloading the game can automatically load up the
+	// last level that was being played when saved.
+	uint32 last_level_load_script=0;
+	uint32 last_game_mode=0;
+	CStruct *p_career=NULL;
+	int current_theme=0;
+	p_globalInfo->GetStructure(CRCD(0x4da4937b,"Career"),&p_career);
+	if (p_career)
+	{
+		p_career->GetChecksum(CRCD(0xe3335d2f,"LastLevelLoadScript"),&last_level_load_script);
+		p_career->GetChecksum(CRCD(0x2cc06f5e,"LastGameMode"),&last_game_mode);
+		// Extract current theme
+		p_career->GetInteger(CRCD(0xcc946ff3,"current_theme"),¤t_theme);
+	}
+	if (last_level_load_script)
+	{
+		p_script->GetParams()->AddChecksum(CRCD(0xe3335d2f,"LastLevelLoadScript"),last_level_load_script);
+	}
+	if (last_game_mode)
+	{
+		p_script->GetParams()->AddChecksum(CRCD(0x2cc06f5e,"LastGameMode"),last_game_mode);
+	}
+	//if (current_theme) TT7448: current_theme can be saved as zero
+	//{
+		Script::CStruct* pTemp2 = new Script::CStruct;
+		pTemp2->AddInteger(CRCD(0x3bed2cf5,"theme_num"),current_theme);
+		pTemp2->AddChecksum(CRCD(0x476cdadd,"loading_career"),0);
+		Script::RunScript( "set_current_theme", pTemp2 );
+		delete pTemp2;
+   // }
+	
+	// Now, if the skater is custom, set his cas file name.
+	Obj::CSkaterProfile* pSkaterProfile=pSkate->GetCurrentProfile();
+	if (!pSkaterProfile->IsPro())
+	{
+		const char *pCASFileName="Unimplemented";
+		p_globalInfo->GetString(CRCD(0xf36c1878,"CASFileName"),&pCASFileName);
+		pSkaterProfile->SetCASFileName(pCASFileName);
+	}
+}
+	
+static void s_read_story_info(CStruct *p_storyInfo)
+{
+	// Load in the goal manager parameters.
+	CStruct *p_loaded_goal_manager_params=NULL;
+	p_storyInfo->GetStructure(CRCD(0xac7c2b62,"GoalManagerParams"),&p_loaded_goal_manager_params);
+	Dbg_MsgAssert(p_loaded_goal_manager_params,("No goal manager params on mem card"));
+	
+	Game::CGoalManager* p_goal_manager=Game::GetGoalManager();
+	Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager"));
+	
+	p_goal_manager->ResetCareer();
+	
+	CStruct *p_goal_manager_params=p_goal_manager->GetGoalManagerParams();
+	Dbg_MsgAssert(p_goal_manager_params,("NULL p_goal_manager_params"));
+	
+	p_goal_manager_params->Clear();
+	p_goal_manager_params->AppendStructure(p_loaded_goal_manager_params);
+
+	// Refresh the goal manager with the new params.
+	p_goal_manager->LevelLoad();
+}
+
+static void s_read_story_skater_info(CStruct *p_storySkaterInfo, CStruct *p_customSkater)
+{
+	// Which skater are we loading?
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+	int index = pPlayerProfileManager->GetCurrentProfileIndex();
+	Dbg_MsgAssert(index==0,("Should not be loading story skater info into any skater other than skater 0, tried to load it into skater %d",index));
+
+	Obj::CSkater* pSkater = pSkate->GetSkater(index);
+	Dbg_Assert( pSkater );
+	pSkater->LoadCATInfo(p_storySkaterInfo);
+    pSkater->LoadStatGoalInfo(p_storySkaterInfo);
+    pSkater->LoadChapterStatusInfo(p_storySkaterInfo);
+    
+    pPlayerProfileManager->LoadCASProfileInfo(p_customSkater, true);
+}
+
+static void s_read_custom_skater_info(CStruct *p_customSkater)
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+    int index = pPlayerProfileManager->GetCurrentProfileIndex();
+    
+    if ( index == 0 )
+    {
+        // if this is player one... don't apply the stats
+        pPlayerProfileManager->LoadCASProfileInfo(p_customSkater, false);
+    }
+    else
+    {
+        // if this is any other skater, then apply the stats
+        pPlayerProfileManager->LoadCASProfileInfo(p_customSkater, true);
+    }
+    
+}
+
+uint32 s_apply_flags=0;
+bool s_did_apply_custom_skater_info=false;
+static void s_read_game_save_info(uint32 fileType, CStruct *p_struct, CScript *p_script)
+{
+	Dbg_MsgAssert(p_struct,("NULL p_struct"));
+	Dbg_MsgAssert(p_script,("NULL p_script"));
+	
+	s_did_apply_custom_skater_info=false;
+	
+	switch (fileType)
+	{
+		case 0xb010f357: // OptionsAndPros
+		{
+			Dbg_MsgAssert(s_apply_flags,("s_apply_flags is zero, need to call SetSectionsToApplyWhenLoading"));
+
+            if (s_apply_flags & (1<GetStructure(CRCD(0xf55cbd13,"GlobalInfo"),&p_global_info,Script::ASSERT);
+				s_read_global_info(p_global_info, p_script);
+			}
+			
+			if (s_apply_flags & (1<GetStructure(CRCD(0x14a9fbc7,"Story"),&p_story_info,Script::ASSERT);
+				s_read_story_info(p_story_info);
+			}
+			
+            if (s_apply_flags & (1<GetStructure(CRCD(0x12bfac82,"CustomSkater"),&p_custom_skater_info,Script::ASSERT);
+				s_read_custom_skater_info(p_custom_skater_info);
+				s_did_apply_custom_skater_info=true;
+			}
+
+			if (s_apply_flags & (1<GetStructure(CRCD(0xdf2f448,"StorySkater"),&p_story_skater_info,Script::ASSERT);
+                Script::CStruct *p_custom_skater_info=NULL;
+				p_struct->GetStructure(CRCD(0x12bfac82,"CustomSkater"),&p_custom_skater_info,Script::ASSERT);
+				s_read_story_skater_info(p_story_skater_info,p_custom_skater_info);
+			}
+			
+            // Reset the flags to that the above assert will catch cases where the scripts 
+			// have not called SetSectionsToApplyWhenLoading	
+			s_apply_flags=0;
+			break;
+		}
+		
+		case 0xca41692d: // NetworkSettings		
+		{
+			// Extract the network preferences
+			GameNet::Manager * pGamenet = GameNet::Manager::Instance();
+			Prefs::Preferences *pPreferences=pGamenet->GetNetworkPreferences();
+			Dbg_MsgAssert(pPreferences,("NULL network pPreferences"));
+			pPreferences->SetRoot(p_struct);
+			break;
+		}
+		
+		case 0xffc529f4: // Cas
+		{
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			/*
+			Mdl::Skate * pSkate = Mdl::Skate::Instance();
+			Obj::CPlayerProfileManager*	pPlayerProfileManager=pSkate->GetPlayerProfileManager();
+			//Script::PrintContents( p_struct );
+			pPlayerProfileManager->LoadCASProfileInfo(p_struct);
+
+            // Which skater are we loading?
+            int index = pPlayerProfileManager->GetCurrentProfileIndex();
+
+            Obj::CSkater* pSkater = pSkate->GetSkater(index);
+            Dbg_Assert( pSkater );
+            pSkater->LoadCATInfo(p_struct);
+			*/
+			break;
+		}
+
+        case 0x61a1bc57: // Cat
+		{
+			Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+            Script::CStruct *p_other_params;
+            Script::CArray *p_rotation_info;
+            Script::CArray *p_animation_info;
+            
+            Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+            if ( pSkater )
+            {
+                //Params
+                p_struct->GetStructure( "other_params", &p_other_params, Script::ASSERT );
+                //pSkater->m_created_trick[0]->mp_other_params = p_other_params;
+                pSkater->m_created_trick[0]->mp_other_params->AppendStructure( p_other_params );
+                
+                //Rotations
+                p_struct->GetArray( "rotation_info", &p_rotation_info, Script::ASSERT );
+                Script::CopyArray( pSkater->m_created_trick[0]->mp_rotations, p_rotation_info );
+                
+                //Animations
+                p_struct->GetArray( "animation_info", &p_animation_info, Script::ASSERT );
+                Script::CopyArray( pSkater->m_created_trick[0]->mp_animations, p_animation_info );
+            }
+			break;
+		}
+		
+		case 0x3bf882cc: // Park  (read save game info)
+		{
+			Ed::CParkManager::Instance()->ReadFromStructure(p_struct);
+			break;
+		}
+		
+		case 0x26c80b0d: // Replay
+		{
+#if __USE_REPLAYS__
+			uint32 level_structure_name=0;
+			p_struct->GetChecksum("level_structure_name",&level_structure_name);
+			Replay::SetLevelStructureName(level_structure_name);
+			
+			// Look up the load_script in the level structure, and return it to the calling
+			// script so that after calling LoadFromMemoryCard the script can call the load_script,
+			// which will load the appropriate level.
+			CStruct *p_level_structure=GetStructure(level_structure_name);
+			Dbg_MsgAssert(p_level_structure,("Could not find level structure '%s'",FindChecksumName(level_structure_name)));
+			
+			uint32 load_script=0;
+			p_level_structure->GetChecksum("load_script",&load_script);
+			p_script->GetParams()->AddChecksum("load_script",load_script);
+#endif			
+			break;
+		}
+
+		case 0x62896edf: // CreatedGoals
+		{
+			Obj::CCompositeObject *p_obj=(Obj::CCompositeObject*)Obj::CTracker::Instance()->GetObject(CRCD(0x81f01058,"GoalEditor"));
+			Dbg_MsgAssert(p_obj,("No GoalEditor object"));
+			Obj::CGoalEditorComponent *p_goal_editor=GetGoalEditorComponentFromObject(p_obj);
+			Dbg_MsgAssert(p_goal_editor,("No goal editor component ???"));
+			
+			p_goal_editor->ReadFromStructure(p_struct);
+			break;
+		}
+		
+		default:
+		{
+			Dbg_MsgAssert(0,("Bad type of '%s' sent to s_read_game_save_info",FindChecksumName(fileType)));
+			break;
+		}		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static int s_get_icon_k_required(uint32 fileType)
+{
+	switch (fileType)
+	{
+		case 0xb010f357: // OptionsAndPros
+			return Script::GetInteger(CRCD(0x4925ed9e,"OptionsProsIconSpaceRequired"));
+			break;
+		case 0xca41692d: // NetworkSettings
+			return Script::GetInteger(CRCD(0xf3431f63,"NetworkSettingsIconSpaceRequired"));
+			break;
+		case 0xffc529f4: // Cas
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));		
+			//return Script::GetInteger("CASIconSpaceRequired");
+			break;
+        case 0x61a1bc57: // Cat
+			return Script::GetInteger(CRCD(0x46b0ef40,"CATIconSpaceRequired"));
+			break;
+		case 0x3bf882cc: // Park
+			return Script::GetInteger(CRCD(0x82aaf8dc,"ParkIconSpaceRequired"));
+			break;
+		case 0x26c80b0d: // Replay
+			return Script::GetInteger(CRCD(0x7eaf934f,"ReplayIconSpaceRequired"));
+			break;
+		case 0x62896edf: // CreatedGoals
+			return Script::GetInteger(CRCD(0x3205bc07,"CreatedGoalsIconSpaceRequired"));
+			break;
+		default:
+			Dbg_MsgAssert(0,("Bad fileType of '%s' sent to s_get_icon_k_required",FindChecksumName(fileType)));
+			break;
+	}
+	
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static int s_calculate_total_space_used_on_card(uint32 fileType, int fileSize)
+{
+	int blocks=s_round_up_to_platforms_block_size(fileSize)/s_get_platforms_block_size();
+
+	switch (Config::GetHardware())
+	{
+	case Config::HARDWARE_PS2:
+	case Config::HARDWARE_PS2_PROVIEW:
+	case Config::HARDWARE_PS2_DEVSYSTEM:
+	{
+		// Add in the icon space required
+		blocks+=s_get_icon_k_required(fileType);
+		
+		// Then add in the space used up by the mem card file system.
+		blocks+=4;
+		break;
+	}
+			
+	case Config::HARDWARE_XBOX:
+	{
+		// Add in the icon space required
+		blocks+=3;
+		break;
+	}
+			
+	case Config::HARDWARE_NGC:
+		// All done, I think.
+		break;
+				
+	default:
+		break;
+	}		
+	
+	return blocks;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// This returns the block size for mem card files.
+// On some platforms, using a library function to get the file size will not
+// return the original file size, but will return the size rounded up to the next
+// multiple of the block size.
+// In order that the calculated checksum be the same on loading, the files are always
+// padded with zeros to be a multiple of the block size before the checksum is calculated.
+static int s_get_platforms_block_size()
+{
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_PS2:
+		case Config::HARDWARE_PS2_PROVIEW:
+		case Config::HARDWARE_PS2_DEVSYSTEM:
+			return 1024;
+			break;
+					
+		case Config::HARDWARE_NGC:
+			return 8192;
+			break;
+			
+		case Config::HARDWARE_XBOX:
+			return 16384;
+			break;
+		
+		default:
+			break;
+	}
+	
+	return 1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int s_round_up_to_platforms_block_size(int fileSize)
+{
+	int block_size=s_get_platforms_block_size();
+	if (fileSize%block_size)
+	{
+		return (1+fileSize/block_size)*block_size;
+	}
+	else
+	{
+		return fileSize;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static int s_get_version_number(uint32 fileType)
+{
+	switch (fileType)
+	{
+		case 0xb010f357: // OptionsAndPros
+			return Script::GetInt( "VERSION_OPTIONSANDPROS", Script::ASSERT );
+			break;
+		case 0xffc529f4: // Cas
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			//return Script::GetInt( "VERSION_CAS", Script::ASSERT );
+			break;
+        case 0x61a1bc57: // Cat
+			return Script::GetInt( "VERSION_CAT", Script::ASSERT );
+			break;
+		case 0x3bf882cc: // Park
+			return Script::GetInt( "VERSION_PARK", Script::ASSERT );
+			break;
+		case 0x26c80b0d: // Replay
+			return Script::GetInt( "VERSION_REPLAY", Script::ASSERT );
+			break;
+		case 0xca41692d: // NetworkSettings
+			return Script::GetInt( "VERSION_NETWORKSETTINGS", Script::ASSERT );
+			break;
+		case 0x62896edf: // CreatedGoals
+			return Script::GetInt( "VERSION_CREATEDGOALS", Script::ASSERT );
+			break;
+		default:
+			Dbg_MsgAssert(0,("Bad fileType of '%s' sent to s_get_version_number",FindChecksumName(fileType)));
+			break;
+	}
+	return -1;
+}
+
+static const char *s_generate_xbox_directory_name(uint32 fileType, const char *p_name)
+{
+	static char p_directory_name[100];
+	
+	switch( fileType )
+	{
+		case 0xca41692d: // NetworkSettings
+		{
+			sprintf( p_directory_name,	"%s-NetworkSettings", p_name);
+			break;
+		}
+		case 0xb010f357: // OptionsAndPros
+		{
+			sprintf( p_directory_name,	"%s-Story/Skater", p_name );
+			break;
+		}
+		case 0xffc529f4: // Cas
+		{
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			//sprintf( p_directory_name,	"%s-Skater", p_name );
+			break;
+		}
+        case 0x61a1bc57: // Cat
+		{
+			sprintf( p_directory_name,	"%s-Trick", p_name );
+			break;
+		}
+		case 0x3bf882cc: // Park
+		{
+			sprintf( p_directory_name,	"%s-Park", p_name );
+			break;
+		}
+		case 0x26c80b0d: // Replay
+		{
+			sprintf( p_directory_name,	"%s-Replay", p_name );
+			break;
+		}
+		case 0x62896edf: // CreatedGoals
+		{
+			sprintf( p_directory_name,	"%s-Goals", p_name );
+			break;
+		}
+		default:
+		{
+			Dbg_MsgAssert( 0, ( "Bad file type of '%s' for file '%s' sent to s_generate_xbox_directory_name", FindChecksumName(fileType),p_name));
+			break;
+		}		
+	}
+	Dbg_MsgAssert( strlen( p_directory_name ) < 100, ( "Oops" ));
+	
+	return p_directory_name;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// Returns false if error.
+static bool s_make_xbox_dir_and_icons(	Mc::Card *p_card,
+										uint32 fileType, const char *p_name, 
+										char *p_card_file_name,
+										bool *p_insufficientSpace)
+{
+	Dbg_MsgAssert(p_card_file_name,("NULL p_card_file_name"));
+	p_card_file_name[0]=0;
+	
+	// Generate the directory name
+	const char *p_directory_name=s_generate_xbox_directory_name(fileType,p_name);
+	
+	// Create the directory
+	if (!p_card->MakeDirectory(p_directory_name))
+	{
+		if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+		{
+			*p_insufficientSpace=true;
+		}	
+		return false;
+	}	
+
+	// Calculate the low-level file name.
+	const char *p_low_level_directory_name=p_card->ConvertDirectory(p_directory_name);
+	if (!p_low_level_directory_name)
+	{
+		return false;
+	}	
+	
+	sprintf( p_card_file_name, "/%s/%s", p_low_level_directory_name, p_low_level_directory_name );
+	Dbg_MsgAssert(strlen(p_card_file_name)Open( pFullIcoName, Mc::File::mMODE_WRITE | Mc::File::mMODE_CREATE );
+	if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+	{
+		*p_insufficientSpace=true;
+	}	
+	
+	if( pFile )
+	{   
+		uint32 CardBytesWritten = pFile->Write( pIco, FileSize );
+		if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+		{
+			*p_insufficientSpace=true;
+		}	
+		
+		if( CardBytesWritten == FileSize )
+		{
+			SavedOK = true;
+		}
+		if( !pFile->Flush())
+		{
+			SavedOK = false;
+		}	
+		if( !pFile->Close())
+		{
+			SavedOK = false;
+		}	
+		delete pFile;
+	}
+	Mem::Free( pIco );
+	return SavedOK;	
+}
+
+static void s_generate_card_directory_name(uint32 fileType, const char *p_name, char *p_card_directory_name)
+{
+	Dbg_MsgAssert(p_card_directory_name,("NULL p_card_directory_name"));
+	
+	char p_ascii_checksum[20];
+	s_generate_ascii_checksum(p_ascii_checksum,p_name,fileType);
+
+	const char *p_header=Config::GetMemCardHeader();
+	sprintf(p_card_directory_name,"/%s%s",p_header,p_ascii_checksum);
+											
+	Dbg_MsgAssert(strlen(p_card_directory_name)Open( p_icon_sys, Mc::File::mMODE_READ );
+	if (pFile)
+	{
+		// Icon exists already, hence so must the directory, so no need to create it.
+		pFile->Flush();
+		pFile->Close();
+		delete pFile;
+		pFile=NULL;
+		return true;
+	}
+	
+	// Icon does not exist hence the directory cannot either, 
+	// so create it and save out the icon.
+	if (!p_card->MakeDirectory(p_directory_name))
+	{
+		if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+		{
+			*p_insufficientSpace=true;
+		}	
+		return false;
+	}	
+	
+	
+	sceMcIconSys IconSys;
+
+	sceVu0IVECTOR bgcolor[4] = 
+	{
+		{ 0x80,    0,    0, 0 },
+		{    0, 0x80,    0, 0 },
+		{    0,    0, 0x80, 0 },
+		{ 0x80, 0x80, 0x80, 0 },
+	};
+	
+	sceVu0FVECTOR lightdir[3] = 
+	{
+		{ 0.5, 0.0, 0.0, 0.0 },
+		{ 0.0, 0.5, 0.0, 0.0 },
+		{ 0.0, 0.0, 0.5, 0.0 },
+	};
+
+	sceVu0FVECTOR lightcol[3] = 
+	{
+		{ 0.50, 0.50, 0.50, 0.00 },
+		{ 0.25, 0.25, 0.40, 0.00 },
+		{ 0.80, 0.80, 0.80, 0.00 },		
+	};
+	
+	sceVu0FVECTOR ambient = { 0.50, 0.50, 0.50, 0.00 };
+	
+	memset(&IconSys, 0, sizeof(IconSys));
+	strcpy((char*)IconSys.Head, "PS2D");
+	
+	// Convert the title from ascii to shift-jis & write it in.
+	//static char pTitle[100];
+	char pTitle[100];
+	
+	char *pSourceIconFile="";
+	char *pIcoName="";
+	#ifdef __NOPT_ASSERT__
+	uint32 ExpectedIcoSize=0;
+	#endif
+
+	int LineBreak=16;
+	
+	switch (fileType)
+	{
+		case 0xb010f357: // OptionsAndPros
+		{
+			strcpy(pTitle,Script::GetString(CRCD(0x130ab28a,"cfuncs_str_thugstoryskater")));
+			LineBreak=strlen(pTitle);
+			Dbg_MsgAssert(LineBreak<=16,("Mem card title '%s' exceeds 16 chars",pTitle));
+			Dbg_MsgAssert(pTitle[LineBreak-1]==':',("Expected mem card file name '%s' to end in ':'",pTitle));
+			strcat(pTitle,p_name);
+			
+			pIcoName="thps5.ico";
+			pSourceIconFile="memcard\\thps3.icn";
+			#ifdef __NOPT_ASSERT__
+			ExpectedIcoSize=Script::GetInt(CRCD(0x4925ed9e,"OptionsProsIconSpaceRequired"));
+			#endif
+			break;
+		}
+
+		case 0xca41692d: // NetworkSettings
+		{
+			strcpy(pTitle,Script::GetString(CRCD(0x92c497ae,"cfuncs_str_thugnetsettings")));
+			LineBreak=strlen(pTitle);
+			Dbg_MsgAssert(LineBreak<=16,("Mem card title '%s' exceeds 16 chars",pTitle));
+			Dbg_MsgAssert(pTitle[LineBreak-1]==':',("Expected mem card file name '%s' to end in ':'",pTitle));
+			strcat(pTitle,p_name);
+			
+			// For the moment use the options/pros icon, until a new icon is made.
+			// TODO: Once got a new icon file, remember to update ScriptDeleteMemCardFile too.
+			pIcoName="network.ico";
+			pSourceIconFile="memcard\\network.icn";
+			#ifdef __NOPT_ASSERT__
+			ExpectedIcoSize=Script::GetInt(CRCD(0xf3431f63,"NetworkSettingsIconSpaceRequired"));
+			#endif
+			break;
+		}
+
+		case 0xffc529f4: // Cas
+		{
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			/*
+			#if ENGLISH
+			sprintf(pTitle,"THUG Skater:%s",p_name);
+			#else
+			sprintf(pTitle,Script::GetLocalString(CRCD(0x7c2cfe2c,"cfuncs_str_thps3skater")),p_name);
+			#endif
+			LineBreak=12; // After 'THUG Skater:'
+			
+			pIcoName="cas.ico";
+			pSourceIconFile="memcard\\cas.icn";
+			#ifdef __NOPT_ASSERT__
+			ExpectedIcoSize=Script::GetInt(CRCD(0xa79ee524,"CASIconSpaceRequired"));
+			#endif
+			*/
+			break;
+		}
+
+        case 0x61a1bc57: // Cat
+		{
+			strcpy(pTitle,Script::GetString(CRCD(0x3aa2cffa,"cfuncs_str_thugtrick")));
+			LineBreak=strlen(pTitle);
+			Dbg_MsgAssert(LineBreak<=16,("Mem card title '%s' exceeds 16 chars",pTitle));
+			Dbg_MsgAssert(pTitle[LineBreak-1]==':',("Expected mem card file name '%s' to end in ':'",pTitle));
+			strcat(pTitle,p_name);
+			
+			pIcoName="tricks.ico";
+			pSourceIconFile="memcard\\tricks.icn";
+			#ifdef __NOPT_ASSERT__
+			ExpectedIcoSize=Script::GetInt(CRCD(0x46b0ef40,"CATIconSpaceRequired"));
+			#endif
+			break;
+		}
+		
+		case 0x62896edf: // CreatedGoals
+		{
+			strcpy(pTitle,Script::GetString(CRCD(0x257678cb,"cfuncs_str_thuggoals")));
+			LineBreak=strlen(pTitle);
+			Dbg_MsgAssert(LineBreak<=16,("Mem card title '%s' exceeds 16 chars",pTitle));
+			Dbg_MsgAssert(pTitle[LineBreak-1]==':',("Expected mem card file name '%s' to end in ':'",pTitle));
+			strcat(pTitle,p_name);
+			
+			pIcoName="goals.ico";
+			pSourceIconFile="memcard\\goals.icn";
+			#ifdef __NOPT_ASSERT__
+			ExpectedIcoSize=Script::GetInt(CRCD(0x3205bc07,"CreatedGoalsIconSpaceRequired"));
+			#endif
+			break;
+		}
+		
+		case 0x3bf882cc: // Park
+		{
+			strcpy(pTitle,Script::GetString(CRCD(0x2171fddc,"cfuncs_str_thugpark")));
+			LineBreak=strlen(pTitle);
+			Dbg_MsgAssert(LineBreak<=16,("Mem card title '%s' exceeds 16 chars",pTitle));
+			Dbg_MsgAssert(pTitle[LineBreak-1]==':',("Expected mem card file name '%s' to end in ':'",pTitle));
+			strcat(pTitle,p_name);
+			
+			pIcoName="parked.ico";
+			pSourceIconFile="memcard\\parked.icn";
+			#ifdef __NOPT_ASSERT__
+			ExpectedIcoSize=Script::GetInt(CRCD(0x82aaf8dc,"ParkIconSpaceRequired"));
+			#endif
+			break;
+		}
+		
+		default:
+		{
+			Dbg_MsgAssert(0,("Bad file type sent to s_create_mem_card_icon_file"));
+			break;
+		}		
+	}
+
+	// Convert the title to shift-jis and put it in IconSys.TitleName
+	int Len=strlen(pTitle);
+	Dbg_MsgAssert(Len<=32,("Title too long !!!"));
+	pTitle[32]=0; // Just to be absolutely sure on non debug builds.
+	
+	char *pTit=(char*)IconSys.TitleName;
+	for (int i=0; i>8)&0xff;
+		*pTit++=Jis&0xff;
+	}
+	*pTit++=0;
+	*pTit=0;
+
+	
+	IconSys.OffsLF = 2*LineBreak;
+	IconSys.TransRate = 0x60;
+	memcpy(IconSys.BgColor, bgcolor, sizeof(bgcolor));
+	memcpy(IconSys.LightDir, lightdir, sizeof(lightdir));
+	memcpy(IconSys.LightColor, lightcol, sizeof(lightcol));
+	memcpy(IconSys.Ambient, ambient, sizeof(ambient));
+
+
+	strcpy((char*)IconSys.FnameView, pIcoName);
+	strcpy((char*)IconSys.FnameCopy, pIcoName);
+	strcpy((char*)IconSys.FnameDel, pIcoName);
+
+	
+	// Now load the .ico file into memory
+	uint32 FileSize=0;
+	char *pIco=NULL;
+
+	File::StopStreaming();
+		
+    void *pFP = File::Open(pSourceIconFile, "rb");
+    Dbg_MsgAssert(pFP,("No %s file found.",pSourceIconFile));
+	
+    FileSize=File::GetFileSize(pFP);
+	
+	// Make sure the ico file size matches that specified in memcard.q.
+	// The +1 is because the icon.sys takes up 1K.
+	Dbg_MsgAssert(1+(((FileSize+0x3ff)&(~0x3ff)) >> 10)==ExpectedIcoSize,("%s icon size mismatch",Script::FindChecksumName(fileType)));
+	
+	// Allocate a buffer to hold the file.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	// Allocating the size rounded up to a multiple of 2048 in case file reading only
+	// reads a whole number of sectors.
+    pIco=(char*)Mem::Malloc((FileSize+2047)&~2047);
+    Dbg_MsgAssert(pIco,("Could not allocate memory for %s",pSourceIconFile));
+	Mem::Manager::sHandle().PopContext();
+
+	// Read the file into the buffer and close the file.
+#ifdef __NOPT_ASSERT__
+	long BytesRead=
+#endif	
+	File::Read(pIco,1,FileSize,pFP);
+	Dbg_MsgAssert(BytesRead<=FileSize,("Bad rwfread when loading %s",pSourceIconFile));
+    File::Close(pFP);
+	
+	
+	bool SavedOK=false;
+
+	pFile=p_card->Open( p_icon_sys, Mc::File::mMODE_WRITE | Mc::File::mMODE_CREATE );
+	if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+	{
+		*p_insufficientSpace=true;
+	}	
+	
+	if (pFile)
+	{   
+		//printf("Writing %s, %d bytes\n",pIconSys,sizeof(IconSys));
+		uint32 CardBytesWritten=pFile->Write( &IconSys, sizeof(IconSys) );
+		if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+		{
+			*p_insufficientSpace=true;
+		}	
+		
+		if (CardBytesWritten==sizeof(IconSys))
+		{
+			SavedOK=true;
+		}
+
+		if (!pFile->Flush())
+		{
+			SavedOK=false;
+		}	
+		if (!pFile->Close())
+		{
+			SavedOK=false;
+		}	
+				
+		delete pFile;
+	}
+
+	// Bail out straight away if the previous save failed so that the error code as
+	// returned by Card::GetLastError is correct.
+	if (!SavedOK)
+	{
+		Mem::Free(pIco);
+		return false;
+	}	
+
+	SavedOK=false;
+	char pFullIcoName[MAX_CARD_FILE_NAME_CHARS+1];
+	sprintf(pFullIcoName,"%s/%s",p_directory_name,pIcoName);
+	Dbg_MsgAssert(strlen(pFullIcoName)Open( pFullIcoName, Mc::File::mMODE_WRITE | Mc::File::mMODE_CREATE );
+	if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+	{
+		*p_insufficientSpace=true;
+	}	
+	
+	if (pFile)
+	{   
+		//printf("Writing %s, %d bytes\n",pFullIcoName,FileSize);
+		uint32 CardBytesWritten=pFile->Write( pIco, FileSize );
+		if (p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE)
+		{
+			*p_insufficientSpace=true;
+		}	
+		
+		if (CardBytesWritten==FileSize)
+		{
+			SavedOK=true;
+		}
+
+		if (!pFile->Flush())
+		{
+			SavedOK=false;
+		}	
+		if (!pFile->Close())
+		{
+			SavedOK=false;
+		}	
+				
+		delete pFile;
+	}
+	
+	Mem::Free(pIco);
+	return SavedOK;
+}
+#else
+static bool s_make_ps2_dir_and_icons(	Mc::Card *p_card,
+										uint32 fileType, const char *p_name, 
+										char *p_card_file_name,
+										bool *p_insufficientSpace)
+{
+	return false;
+}
+#endif // #ifdef __PLAT_NGPS__
+
+#ifdef __PLAT_NGC__
+static bool s_insert_ngc_icon(	SMcFileHeader *p_fileHeader, 
+								Mc::Card *p_card,
+								Mc::File *p_file,
+								uint32 fileType,
+								const char *p_name)
+{
+
+	Dbg_MsgAssert(p_fileHeader,("NULL p_fileHeader"));
+	Dbg_MsgAssert(p_card,("NULL p_card"));
+	Dbg_MsgAssert(p_file,("NULL p_file"));
+	Dbg_MsgAssert(p_name,("NULL p_name"));
+	
+	uint8 *p_buffer=p_fileHeader->mpIconData;
+	
+	char* p_icon_name="icons\\CreateSk8r_Icon_01.img.ngc";
+
+	switch( fileType )
+	{
+		case 0xb010f357: // OptionsAndPros
+		case 0xca41692d: // NetworkSettings	
+		{
+			break;
+		}	
+		
+		case 0xffc529f4: // Cas
+		{
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			break;
+		}
+
+        case 0x61a1bc57: // CAT
+		{
+			p_icon_name = "icons\\Trick_Icon_01.img.ngc";
+			break;
+		}
+
+		case 0x62896edf: // CreatedGoals
+		{
+			p_icon_name = "icons\\Goal_Icon_01.img.ngc";
+			break;
+		}
+		
+		case 0x3bf882cc: // Park
+		{
+			p_icon_name = "icons\\ParkEd_Icon_01.img.ngc";
+			break;
+		}
+		
+		default:
+		{
+			Dbg_MsgAssert( 0, ( "Bad file type" ));
+			break;
+		}		
+	}
+	
+	// We used to have each of the save types in the switch statement above choose a different
+	// name, one which contained the save type, ie "THUG: Skater"
+	// We then had to change them to all have the same name, namely "Tony Hawk's Underground"
+	// This is because Nintendo required that we prefix all our save names with Tony Hawk's Underground, but
+	// when we tried to append the save type as well the text was too long for the IPL screen.
+	strcpy((char*)p_buffer, Script::GetString(CRCD(0x6a6214bc,"cfuncs_str_ngc_generic_save_name")));
+	Dbg_MsgAssert(strlen((const char*)p_buffer) < 32,("p_buffer '%s' too long",p_buffer));
+	strcpy((char*)p_buffer + 32, p_name );
+
+	// Find the index of the number character that precedes the .
+	// This used to be set a hard wired value for each of the cases above, but changed to
+	// calculate it instead cos the icon file names could quite likely change.
+	int name_index=0;
+	while (p_icon_name[name_index] && p_icon_name[name_index] != '.')
+	{
+		++name_index;
+	}
+	Dbg_MsgAssert(p_icon_name[name_index],("No '.' found in icon name '%s'",p_icon_name));
+	--name_index;
+		
+
+	CARDStat stat;
+	CARDGetStatus( p_card->GetSlot(), p_file->m_file_number, &stat );
+
+	// Set up to write the banner and icon data.
+	char* next_pixel = (char*)p_buffer + 64;
+
+	// Load the banner file.
+	void *p_FH = File::Open( "icons\\thps3_bannericon_01.img.ngc", "rb" );
+	Dbg_MsgAssert( p_FH, ( "Couldn't open texture file %s\n", "icons\\thps3_bannericon_01.img.ngc" ) );
+
+	int dummy;
+	int width;
+	int height;
+	// Read header info.
+	File::Read( &dummy, sizeof( int ), 1, p_FH );		// version
+	File::Read( &dummy, sizeof( int ), 1, p_FH );		// checksum
+	File::Read( &width, sizeof( int ), 1, p_FH );
+	File::Read( &height, sizeof( int ), 1, p_FH );
+	File::Read( &dummy, sizeof( int ), 1, p_FH );		// depth
+	File::Read( &dummy, sizeof( int ), 1, p_FH );		// levels
+	File::Read( &dummy, sizeof( int ), 1, p_FH );		// rwidth
+	File::Read( &dummy, sizeof( int ), 1, p_FH );		// rheight
+	File::Read( &dummy, sizeof( int ), 1, p_FH );		// has_holes/alphamap
+
+	Dbg_MsgAssert( width == 96, ( "Bad mem card banner width" ));
+	Dbg_MsgAssert( height == 32, ( "Bad mem card banner height" ));
+
+	// Read image & palette data.
+	File::Read( next_pixel, 96 * 32, 1, p_FH );
+	next_pixel += ( 96 * 32 );
+	File::Read( next_pixel, 256 * 2, 1, p_FH );
+	next_pixel += ( 256 * 2 );
+
+	File::Close( p_FH );
+
+#	if 0
+	// Write out a dummy array representing the banner image.
+	OSReport( "unsigned short image[96*32] = {\n" );
+	for( int i = 0; i < 32; ++i )
+	{
+		for( int j = 0; j < 96; ++j )
+		{
+			OSReport( "0x%x,", ((unsigned short*)p_texture->m_pPalette )[((unsigned char*)p_texture->m_pImage )[( i * 32 ) + j]] );
+		}
+		OSReport( "\n" );
+	}
+	OSReport( "};\n" );
+#	endif
+
+	// Load the icon files.
+	for( int i = 0; i < 7; ++i )
+	{
+		p_icon_name[name_index] = '1' + i;
+
+		// Load the icon file.
+		void *p_FH = File::Open( p_icon_name, "rb" );
+		Dbg_MsgAssert( p_FH, ( "Couldn't open icon file %s\n", p_icon_name ) );
+
+		int dummy;
+		int width;
+		int height;
+		// Read header info.
+		File::Read( &dummy, sizeof( int ), 1, p_FH );		// version
+		File::Read( &dummy, sizeof( int ), 1, p_FH );		// checksum
+		File::Read( &width, sizeof( int ), 1, p_FH );
+		File::Read( &height, sizeof( int ), 1, p_FH );
+		File::Read( &dummy, sizeof( int ), 1, p_FH );		// depth
+		File::Read( &dummy, sizeof( int ), 1, p_FH );		// levels
+		File::Read( &dummy, sizeof( int ), 1, p_FH );		// rwidth
+		File::Read( &dummy, sizeof( int ), 1, p_FH );		// rheight
+		File::Read( &dummy, sizeof( int ), 1, p_FH );		// has_holes/alphamap
+
+		Dbg_MsgAssert( width == 32, ( "Bad mem card banner width" ));
+		Dbg_MsgAssert( height == 32, ( "Bad mem card banner height" ));
+
+		// Read image & palette data.
+		File::Read( next_pixel, 32 * 32, 1, p_FH );
+		next_pixel += ( 32 * 32 );
+		if( i == 6 )
+		{
+			File::Read( next_pixel, 256 * 2, 1, p_FH );
+			next_pixel += 256*2;
+		}
+
+		File::Close( p_FH );
+
+		// Icon formats.
+		CARDSetIconFormat( &stat, i, CARD_STAT_ICON_C8 ); 
+
+		// Anim speeds.
+		CARDSetIconSpeed( &stat, i, CARD_STAT_SPEED_MIDDLE ); 
+	}
+
+	Dbg_MsgAssert(next_pixel==(char*)p_buffer+NGC_MEMCARD_ICON_DATA_SIZE,("Incorrect NGC_MEMCARD_ICON_DATA_SIZE, too big by %d bytes",NGC_MEMCARD_ICON_DATA_SIZE-(next_pixel-(char*)p_buffer)));
+	stat.commentAddr	= (uint32)p_fileHeader->mpIconData-(uint32)p_fileHeader;
+	stat.iconAddr		= stat.commentAddr + 64;	// End of comment strings.
+
+	// Banner format.
+	CARDSetBannerFormat( &stat, CARD_STAT_BANNER_C8 ); 
+	CARDSetIconSpeed( &stat, 7, CARD_STAT_SPEED_END ); 
+	CARDSetIconAnim( &stat, CARD_STAT_ANIM_LOOP );
+	CARDSetStatus( p_card->GetSlot(), p_file->m_file_number, &stat );
+	return true;
+}
+#else
+static bool s_insert_ngc_icon(	SMcFileHeader *p_fileHeader, 
+								Mc::Card *p_card,
+								Mc::File *p_file,
+								uint32 fileType,
+								const char *p_name)
+{
+	return false;
+}
+#endif // #ifdef __PLAT_NGC__
+
+static bool s_first_date_is_more_recent(Script::CStruct *p_a, Script::CStruct *p_b)
+{
+	int year_a=0;
+	int month_a=0;
+	int day_a=0;
+	int hour_a=0;
+	int minutes_a=0;
+	int seconds_a=0;
+	
+	Dbg_MsgAssert(p_a,("NULL p_a"));
+	p_a->GetInteger(CRCD(0x447d8cc8,"Year"),&year_a);
+	p_a->GetInteger(CRCD(0x7149eff9,"Month"),&month_a);
+	p_a->GetInteger(CRCD(0x1a5fd66f,"Day"),&day_a);
+	p_a->GetInteger(CRCD(0x8fe1eeb1,"Hour"),&hour_a);
+	p_a->GetInteger(CRCD(0x5f94b55c,"Minutes"),&minutes_a);
+	p_a->GetInteger(CRCD(0xd029f619,"Seconds"),&seconds_a);
+
+	int year_b=0;
+	int month_b=0;
+	int day_b=0;
+	int hour_b=0;
+	int minutes_b=0;
+	int seconds_b=0;
+	
+	Dbg_MsgAssert(p_b,("NULL p_b"));
+	p_b->GetInteger(CRCD(0x447d8cc8,"Year"),&year_b);
+	p_b->GetInteger(CRCD(0x7149eff9,"Month"),&month_b);
+	p_b->GetInteger(CRCD(0x1a5fd66f,"Day"),&day_b);
+	p_b->GetInteger(CRCD(0x8fe1eeb1,"Hour"),&hour_b);
+	p_b->GetInteger(CRCD(0x5f94b55c,"Minutes"),&minutes_b);
+	p_b->GetInteger(CRCD(0xd029f619,"Seconds"),&seconds_b);
+
+	bool more_recent=false;
+	
+	if (year_a < year_b)
+	{
+	}
+	else if (year_a > year_b)
+	{
+		more_recent=true;
+	}
+	else if (month_a < month_b)
+	{
+	}
+	else if (month_a > month_b)
+	{
+		more_recent=true;
+	}
+	else if (day_a < day_b)
+	{
+	}
+	else if (day_a > day_b)
+	{
+		more_recent=true;
+	}
+	else if (hour_a < hour_b)
+	{
+	}
+	else if (hour_a > hour_b)
+	{
+		more_recent=true;
+	}
+	else if (minutes_a < minutes_b)
+	{
+	}
+	else if (minutes_a > minutes_b)
+	{
+		more_recent=true;
+	}
+	else if (seconds_a < seconds_b)
+	{
+	}
+	else if (seconds_a > seconds_b)
+	{
+		more_recent=true;
+	}
+	else
+	{
+		// Exactly the same date/time, so call it more recent.
+		more_recent=true;
+	}
+	
+	return more_recent;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				
+// @script | GetMostRecentSave | Finds the most recent save of the given type in the passed
+// directory listing. Puts the result into a structure called MostRecentSave. Any existing
+// parameter called MostRecentSave will be removed first. If there are no files, no MostRecentSave
+// parameter will be created.
+// @uparmopt [] | The directory listing. Must be an array of structures.
+// @uparmopt name | The save type. If omitted it will return the most recent save of any type.
+bool ScriptGetMostRecentSave(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->RemoveComponent("MostRecentSave");
+	
+	CArray *p_array=NULL;
+	pParams->GetArray(NONAME,&p_array);
+	
+	if (!p_array)
+	{
+		return false;
+	}	
+	
+	uint32 file_type=0;
+	pParams->GetChecksum(NONAME,&file_type);
+
+	CStruct *p_most_recent=NULL;
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		CStruct *p_struct=p_array->GetStructure(i);
+		
+		uint32 this_file_type=0;
+		p_struct->GetChecksum("file_type",&this_file_type);
+		
+		if (file_type && file_type!=this_file_type)
+		{
+			continue;
+		}
+		
+		if (p_struct->ContainsFlag("BadVersion") ||
+			p_struct->ContainsFlag("Corrupt"))
+		{
+			if (Config::GetHardware() == Config::HARDWARE_XBOX ||
+				Config::GetHardware() == Config::HARDWARE_NGC)
+			{
+				// For the XBox, do not ignore bad files, so that an error message will appear on bootup
+				// if the most recent save is bad. (TT6236)
+				// Same for NGC (TT13519)
+			}
+			else
+			{
+				// For other platforms, find the most recent good save.
+				continue;
+			}	
+		}	
+			
+		if (p_most_recent)
+		{
+			if (s_first_date_is_more_recent(p_struct, p_most_recent))
+			{
+				p_most_recent=p_struct;
+			}	
+		}	
+		else
+		{
+			p_most_recent=p_struct;
+		}	
+	}
+	
+	if (p_most_recent)
+	{
+		pScript->GetParams()->AddStructure("MostRecentSave",p_most_recent);
+        return true;
+	}
+	
+	return false;	
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				
+// @script | GetMemCardSpaceAvailable | Puts the amount of space left on the mem card
+// into the parameter SpaceAvailable. Units are K for the PS2
+bool ScriptGetMemCardSpaceAvailable(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddInteger(CRCD(0xc37c363,"SpaceAvailable"),0);
+	pScript->GetParams()->AddInteger(CRCD(0x855b2fc,"FilesLeft"),1000000);
+
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (p_card)
+	{
+		pScript->GetParams()->AddInteger(CRCD(0xc37c363,"SpaceAvailable"),p_card->GetNumFreeClusters());
+		#ifdef __PLAT_NGC__
+		pScript->GetParams()->AddInteger(CRCD(0x855b2fc,"FilesLeft"),p_card->CountFilesLeft());
+		#endif
+			
+		return true;
+	}	
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetMemCardSpaceRequired | Calculates the amount of space required to save the specified
+// file type to the memory card, and puts it into the parameter SpaceRequired.
+// Units are K for the PS2.
+// @uparm name | The save type.
+bool ScriptGetMemCardSpaceRequired(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 file_type=0;
+	pParams->GetChecksum(NONAME,&file_type);
+	
+	// s_calculate_total_space_used_on_card adds in the space used by the icons.
+	int space_required=s_calculate_total_space_used_on_card(file_type,sGetFixedFileSize(file_type));
+
+	pScript->GetParams()->AddInteger("SpaceRequired",space_required);
+	return true;
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				
+// @script | MemCardFileExists | returns true if the file already exists
+// on the card
+// @parm string | Name | the name of the file
+// @parm name | type | the type of the file
+bool ScriptMemCardFileExists(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (!p_card)
+	{
+		return false;
+	}
+		
+	const char *p_name=NULL;
+	pParams->GetText("Name",&p_name);
+	
+	uint32 file_type=0;
+	pParams->GetChecksum("Type",&file_type);
+
+	if (!p_name || !file_type)
+	{
+		return false;
+	}
+		
+	char p_card_file_name[MAX_CARD_FILE_NAME_CHARS+1];
+	p_card_file_name[0]=0;
+
+	const char *p_xbox_directory_name=NULL;
+	const char *p_xbox_converted_directory_name=NULL;
+	
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_XBOX:
+		{
+			p_xbox_directory_name=s_generate_xbox_directory_name(file_type,p_name);
+			p_xbox_converted_directory_name=p_card->ConvertDirectory(p_xbox_directory_name);
+			if (!p_xbox_converted_directory_name)
+			{
+				return false;
+			}	
+			sprintf(p_card_file_name,"/%s/%s",p_xbox_converted_directory_name,p_xbox_converted_directory_name);
+			Dbg_MsgAssert(strlen(p_card_file_name)Open( p_card_file_name, Mc::File::mMODE_READ );
+	if (pFile)
+	{
+		int file_size=pFile->Seek(0,Mc::File::BASE_END);
+		int total_size_on_card=s_calculate_total_space_used_on_card(file_type,file_size);
+		pScript->GetParams()->AddInteger("total_file_size",total_size_on_card);
+	
+		pFile->Flush();
+		pFile->Close();
+		delete pFile;
+		return true;
+	}
+	
+	if (Config::GetHardware()==Config::HARDWARE_XBOX)
+	{
+		// Better delete the directory we just created ...
+		Dbg_MsgAssert(p_xbox_directory_name,("NULL p_xbox_directory_name ?"));
+		p_card->DeleteDirectory(p_xbox_directory_name);
+	}
+	return false;		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				
+// @script | DeleteMemCardFile | Deletes the specified file from the memory card
+// @parmopt string | CardFileName | | The name of the file as stored on the memory card. If this is
+// not specified then it is derived from UserFileName and Type
+// @parmopt string | UserFileName | | The name of the file as it appears in the files menu
+// @parm name | Type | The file type (NetworkSettings, OptionsAndPros, Cas, Park, Replay)
+
+bool ScriptDeleteMemCardFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic(true);
+		Pcm::PauseStream(true);
+	}
+	
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (!p_card)
+	{
+		if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+		{
+			Pcm::PauseMusic( -1 );
+			Pcm::PauseStream( -1 );
+		}	
+		return false;
+	}
+		
+	bool delete_succeeded=true;
+	
+	const char *p_xbox_directory_name=NULL;
+	pParams->GetString("XBoxDirectoryName",&p_xbox_directory_name);
+	const char *p_full_file_name=NULL;
+	pParams->GetString("CardFileName",&p_full_file_name);
+	uint32 file_type=0;
+	pParams->GetChecksum("Type",&file_type);
+	const char *p_name="";
+	pParams->GetString("UserFileName",&p_name);
+
+	
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_NGC:
+		{
+			if (p_full_file_name)
+			{
+				if (!p_card->Delete(p_full_file_name))
+				{
+					delete_succeeded=false;
+				}	
+			}
+			else
+			{
+				char p_temp[MAX_CARD_FILE_NAME_CHARS+1];
+				
+				// If p_full_file_name is not specified then derive it from Type and UserFileName
+				
+				const char *p_header=Config::GetMemCardHeader();
+				char p_ascii_checksum[20];
+				s_generate_ascii_checksum(p_ascii_checksum,p_name,file_type);
+		
+				sprintf(p_temp,"%s%s%s%s",p_header,p_ascii_checksum,
+									      p_header,p_ascii_checksum);
+				Dbg_MsgAssert(strlen(p_temp)Delete(p_temp))
+				{
+					delete_succeeded=false;
+				}	
+			}
+			break;
+		}
+		
+		case Config::HARDWARE_XBOX:
+		{
+			if (!p_xbox_directory_name)
+			{
+				// Derive p_xbox_directory_name from the file type and user file name
+				p_xbox_directory_name=s_generate_xbox_directory_name(file_type,p_name);
+				
+			}
+			if (!p_card->DeleteDirectory( p_xbox_directory_name ))
+			{
+				delete_succeeded=false;
+			}
+			break;
+		}
+		
+		case Config::HARDWARE_PS2:
+		case Config::HARDWARE_PS2_PROVIEW:
+		case Config::HARDWARE_PS2_DEVSYSTEM:
+		{
+			char p_temp[MAX_CARD_FILE_NAME_CHARS+1];
+			
+			if (!p_full_file_name)
+			{
+				// If p_full_file_name is not specified then derive it from Type and UserFileName
+				
+				const char *p_header=Config::GetMemCardHeader();
+				char p_ascii_checksum[20];
+				s_generate_ascii_checksum(p_ascii_checksum,p_name,file_type);
+		
+				sprintf(p_temp,"/%s%s/%s%s",p_header,p_ascii_checksum,
+											p_header,p_ascii_checksum);
+				Dbg_MsgAssert(strlen(p_temp)Delete(p_full_file_name))
+			{
+				delete_succeeded=false;
+			}	
+			
+			// Delete the icon.sys file
+			char p_buf[100];
+			sprintf(p_buf,"%s/icon.sys",p_directory_name);
+			Dbg_MsgAssert(strlen(p_buf)<100,("Oops"));
+			if (!p_card->Delete(p_buf))
+			{
+				delete_succeeded=false;
+			}	
+			
+			// Delete the .ico file.
+			switch (file_type)
+			{
+				case 0xffc529f4: // Cas
+				{
+					Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+					//sprintf(p_buf,"%s/cas.ico",p_directory_name);
+					break;
+				}
+
+                case 0x61a1bc57: // CAT
+				{
+					sprintf(p_buf,"%s/tricks.ico",p_directory_name);
+					break;
+				}
+				
+				case 0x3bf882cc: // Park
+				{
+					sprintf(p_buf,"%s/parked.ico",p_directory_name);
+					break;
+				}
+				
+				case 0x26c80b0d: // Replay
+				{
+					sprintf(p_buf,"%s/replay.ico",p_directory_name);
+					break;
+				}
+				
+				case 0xb010f357: // OptionsAndPros
+				{
+					sprintf(p_buf,"%s/thps5.ico",p_directory_name);
+					break;
+				}
+				
+				case 0x62896edf: // CreatedGoals
+				{
+					sprintf(p_buf,"%s/goals.ico",p_directory_name);
+					break;
+				}
+
+				case 0xca41692d: // NetworkSettings
+				{
+					// TODO: Change this once got a new icon file for the net settings
+					sprintf(p_buf,"%s/network.ico",p_directory_name);
+					break;
+				}
+				
+				default:
+				{
+					Dbg_MsgAssert(0,("Bad file type sent to DeleteMemCardFile"));
+					break;
+				}		
+			}
+			Dbg_MsgAssert(strlen(p_buf)<100,("Oops"));
+			if (!p_card->Delete(p_buf))
+			{
+				delete_succeeded=false;
+			}	
+	
+			// Delete the directory
+			if (!p_card->DeleteDirectory(p_directory_name))
+			{
+				delete_succeeded=false;
+			}	
+			break;
+		}
+		default:
+		{
+			delete_succeeded=false;
+			break;
+		}	
+	}
+		
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic( -1 );
+		Pcm::PauseStream( -1 );
+	}
+	
+	return delete_succeeded;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FormatCard | Formats the memory card
+bool ScriptFormatCard(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Pcm::PauseMusic(true);
+	Pcm::PauseStream(true);
+	
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	bool Worked=false;
+	if (p_card)
+	{
+		Worked=p_card->Format();
+	}
+
+	Pcm::PauseMusic( -1 );
+	Pcm::PauseStream( -1 );
+	return Worked;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CardIsInSlot | returns true if the memory card is in the slot
+bool ScriptCardIsInSlot(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (p_card)
+	{
+		return true;
+	}
+	else
+	{
+		#ifdef __PLAT_NGC__
+		// On the NGC, p_card will also be NULL if the card is in the slot, but is broken.
+		if (mc_man->GotFatalError())
+		{
+			return true;
+		}	
+		
+		// On the NGC, p_card will also be NULL if something is in the slot,
+		// but gives a 'wrong device' error
+		if (mc_man->GotWrongDevice())
+		{
+			return true;
+		}	
+		#endif
+		return false;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CardIsFormatted | returns true if the memory card is formatted
+bool ScriptCardIsFormatted(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (p_card)
+	{
+		return p_card->IsFormatted();
+	}
+	else
+	{
+		// If there is no card, then I guess it isn't formatted.
+		return false;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SaveFailedDueToInsufficientSpace | 
+bool ScriptSaveFailedDueToInsufficientSpace(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return s_insufficient_space;
+}
+		
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSummaryInfo	| 
+bool ScriptGetSummaryInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 file_type=0;
+	pParams->GetChecksum(NONAME,&file_type);
+	
+	int use_vault_data=0;
+	pParams->GetInteger(CRCD(0xc78dabf6,"VaultData"),&use_vault_data);
+	
+	if (use_vault_data)
+	{
+		file_type=sVaultDataType;
+		Dbg_MsgAssert(spVaultData,("Called GetSummaryInfo on the vault data when there was no vault data"));
+		s_generate_summary_info(pScript->GetParams(), file_type, spVaultData);
+	}
+	else
+	{
+		if (file_type==0xb010f357) // OptionsAndPros
+		{
+			// Force the goal manager to refresh the goal manager params structure.
+			// Note that this cannot be done inside s_generate_summary_info, because LevelUnload()
+			// will create a structure and store a pointer to it.
+			// In other places SSwitchToNextPool() will be called before calling s_generate_summary_info,
+			// and we don't want the structure created by LevelUnload() to come off the special memcard pool.
+			Game::CGoalManager* p_goal_manager=Game::GetGoalManager();
+			Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager"));
+			p_goal_manager->LevelUnload();
+		}
+		
+		s_generate_summary_info(pScript->GetParams(), file_type, NULL);
+	}
+		
+	return true;
+}
+
+#if __USE_REPLAYS__
+static int sGetReplayDataSize(int num_dummies)
+{
+	return 	sizeof(Replay::SReplayDataHeader)+
+			num_dummies*sizeof(Replay::SSavedDummy)+
+			Replay::GetBufferSize()+
+			4; // The checksum of that lot
+}			
+#endif
+
+static uint32 sGetFixedFileSize(uint32 fileType)
+{
+	uint32 fixed_size=0;
+	
+	switch (fileType)
+	{
+		case 0xb010f357: // OptionsAndPros
+			fixed_size=Script::GetInteger(CRCD(0xe7f51ffe,"SaveSize_OptionsAndPros"),Script::ASSERT);
+			break;
+		case 0xffc529f4: // Cas
+			Dbg_MsgAssert(0,("CAS mem card files are no longer supported! Use OptionsAndPros file type instead"));
+			//fixed_size=Script::GetInteger("SaveSize_Cas",Script::ASSERT);
+			break;
+        case 0x61a1bc57: // Cat
+			fixed_size=Script::GetInteger(CRCD(0x9eecdec9,"SaveSize_Cat"),Script::ASSERT);
+			break;
+		case 0x62896edf: // CreatedGoals
+			fixed_size=Script::GetInteger(CRCD(0x3308efc0,"SaveSize_CreatedGoals"),Script::ASSERT);
+			break;
+		case 0x3bf882cc: // Park
+			fixed_size=Script::GetInteger(CRCD(0x2cb071ed,"SaveSize_Park"),Script::ASSERT);
+			break;
+		case 0x26c80b0d: // Replay
+			fixed_size=Script::GetInteger(CRCD(0x8ee3aa00,"SaveSize_Replay"),Script::ASSERT);
+			break;
+		case 0xca41692d: // NetworkSettings
+			fixed_size=Script::GetInteger(CRCD(0x651c978d,"SaveSize_NetworkSettings"),Script::ASSERT);
+			break;
+		default:
+			Dbg_MsgAssert(0,("Bad file_type of %s",Script::FindChecksumName(fileType)));
+			break;
+	}
+	fixed_size+=sizeof(SMcFileHeader);
+	fixed_size=s_round_up_to_platforms_block_size(fixed_size);
+	return fixed_size;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SaveToMemoryCard | 
+bool ScriptSaveToMemoryCard(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if (Config::GetHardware() == Config::HARDWARE_NGC)
+	{
+		// On the GameCube, the temp mem card pools only exist for the duration of this function,
+		// because they use space normally used for rendering, hence cannot exist during the mem card scripts
+		// which may execute over many frames.
+		ScriptCreateTemporaryMemCardPools(NULL,NULL);
+	}
+		
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic(true);
+		Pcm::PauseStream(true);
+	}
+	
+	s_insufficient_space=false;
+	
+	const char *p_name="";
+	pParams->GetText("Name",&p_name);
+
+	uint32 file_type=0;
+	pParams->GetChecksum("Type",&file_type);
+
+	// If the SaveVaultData flag is set, it will save the stored spVaultData, which
+	// will have been loaded from the online vault.
+	int save_vault_data=0;
+	pParams->GetInteger(CRCD(0xbb609e73,"SaveVaultData"),&save_vault_data);
+	if (save_vault_data)
+	{
+		Dbg_MsgAssert(spVaultData,("Called SaveToMemoryCard with no vault data"));
+		file_type=sVaultDataType;
+	}	
+
+
+	if (!save_vault_data && file_type==0xb010f357) // OptionsAndPros
+	{
+		// Force the goal manager to refresh the goal manager params structure.
+		// This has to be done before calling SSwitchToNextPool(), because LevelUnload()
+		// will create a structure and store a pointer to it.
+		// It is best for the special pools to be kept empty outside of GetMemCardSpaceRequired
+		// or SaveToMemoryCard, otherwise saving the game could run out of pool space.
+		Game::CGoalManager* p_goal_manager=Game::GetGoalManager();
+		Dbg_MsgAssert(p_goal_manager,("NULL p_goal_manager"));
+		p_goal_manager->LevelUnload();
+	}
+
+
+	// If the special pools exist, switch to them so that the components & structs etc get allocated off them.
+	// The special pools use the space freed by unloading the skater anims, and are for preventing memory
+	// overflows when the save size gets large.
+	Dbg_MsgAssert(CComponent::SGetCurrentPoolIndex()==0,("Bad current CComponent pool"));
+	Dbg_MsgAssert(CStruct::SGetCurrentPoolIndex()==0,("Bad current CStruct pool"));
+	Dbg_MsgAssert(CVector::SGetCurrentPoolIndex()==0,("Bad current CVector pool"));
+	bool got_special_pools=false;
+	CComponent::SSwitchToNextPool();
+	if (CComponent::SPoolExists())
+	{
+		CStruct::SSwitchToNextPool();
+		Dbg_MsgAssert(CStruct::SPoolExists(),("No special CStruct pool ?"));
+		CVector::SSwitchToNextPool();
+		Dbg_MsgAssert(CVector::SPoolExists(),("No special CVector pool ?"));
+		
+		got_special_pools=true;
+	}
+	else
+	{
+		CComponent::SSwitchToPreviousPool();
+	}	
+
+	
+	#ifdef	__NOPT_ASSERT__
+	int initial_ccomponent_num_used_items=CComponent::SGetNumUsedItems();
+	int initial_cstruct_num_used_items=CStruct::SGetNumUsedItems();
+	int initial_cvector_num_used_items=CVector::SGetNumUsedItems();
+	#endif
+
+	// WARNING !		WARNING !		WARNING !		WARNING !		WARNING !
+	// Between here and SSwitchToPreviousPool(), all CStruct's and CComponent's will be allocated
+	// off a special pool.
+	// This is to avoid overflowing the regular pools with these temporary big structures.
+	// Make sure that no function, or function that it calls, stores any pointers to new CStructs
+	// between here and SSwitchToPreviousPool().
+	// This is just to keep the special pool clean. Things will get confusing if other stuff
+	// gets allocated off it and uses up space there, cos then saving to mem card could start
+	// running out of pool again.
+
+	Script::CStruct *pMemCardStuff=new Script::CStruct;
+	if (save_vault_data)
+	{
+		pMemCardStuff->AppendStructure(spVaultData);
+	}
+	else
+	{
+		s_insert_game_save_info(file_type,pMemCardStuff);	
+	}	
+	//Script::PrintContents(pMemCardStuff);
+	
+	Script::CStruct *pSummaryInfo=new Script::CStruct;
+	s_generate_summary_info(pSummaryInfo,file_type,pMemCardStuff);	
+	
+	// Put the user filename into the summary info too.
+	pSummaryInfo->AddString("filename",p_name);
+
+	#ifdef	__NOPT_ASSERT__
+	printf("Save type = '%s'\n",Script::FindChecksumName(file_type));
+	printf("Num CComponents used by save = %d\n",CComponent::SGetNumUsedItems()-initial_ccomponent_num_used_items);
+	printf("Num CStructs used by game save = %d\n",CStruct::SGetNumUsedItems()-initial_cstruct_num_used_items);
+	printf("Num CVectors used by game save = %d\n",CVector::SGetNumUsedItems()-initial_cvector_num_used_items);
+	#endif
+
+
+	Mc::File* pFile=NULL;
+	uint8 *pTemp=NULL;
+	uint8 *p_pad=NULL;
+	uint32 pad_size=0;
+	bool SavedOK=false;
+
+	uint32 CardBytesWritten=0;
+	uint8 *p_dest=NULL;
+	SMcFileHeader *p_file_header=NULL;
+	uint32 BytesWritten=0;
+	
+	Mc::Manager * mc_man=NULL;
+	Mc::Card* p_card=NULL;
+	
+	// fixed_size is the hard-wired file size as defined in memcard.q, rounded up to
+	// the platform's block size. A file of exactly this size will be opened for writing.
+	// The code will assert if the required space is greater than this.
+	// The file sizes are required to be fixed as a TRC thing for GameCube.
+	uint32 fixed_size=sGetFixedFileSize(file_type);
+	
+	// header_and_structures_size is the exact space used by the header, summary info, and 
+	// game save info structures. It is not rounded up to the platform's block size.
+	// For all save types except replays, this comprises all the info in the file.
+	// Replay's have the replay data tagged on the end of the file instead, because there
+	// is not enough memory to keep it in the structures.
+	uint32 header_and_structures_size=0;
+
+	
+	// This is the size of the replay data, if any. Not rounded up to the platform's block size.
+	uint32 replay_data_size=0;
+
+#if __USE_REPLAYS__
+	// The big replay data has to be tagged on the end rather than being put inside pMemCardStuff
+	// since there is not enough memory to make a copy of the replay data there.
+	Replay::SReplayDataHeader replay_data_header;
+	if (file_type==0x26c80b0d/* Replay */)
+	{
+		Replay::WriteReplayDataHeader(&replay_data_header);
+		replay_data_size=sGetReplayDataSize(replay_data_header.mNumStartStateDummies);
+	}	
+#endif	
+	
+	/////////////////////////////////////////////////////////////////////////
+	//pMemCardStuff->PrintContents();
+	//printf("Space required = %d\n",pMemCardStuff->CalculateBufferSize());
+	/////////////////////////////////////////////////////////////////////////
+	
+	// Get how much space is needed to store the structures.
+	// Calculating this before opening the file so that if CalculateBufferSize fails,
+	// the file won't be left with zero size, erasing the previous save game.
+	// Passing fixed_size as the size of the temp buffer used when calculating the actual size needed.
+	uint32 structure_size=CalculateBufferSize(pMemCardStuff, fixed_size);
+	uint32 summary_info_size=CalculateBufferSize(pSummaryInfo,MAX_SUMMARY_INFO_SIZE);
+	Dbg_MsgAssert(summary_info_size<=MAX_SUMMARY_INFO_SIZE,("summary_info_size too big !!"));
+	header_and_structures_size=sizeof(SMcFileHeader)+summary_info_size+structure_size;
+
+	uint32 required_size=header_and_structures_size+replay_data_size;
+	
+	#ifdef __NOPT_ASSERT__
+	Dbg_MsgAssert(required_size <= fixed_size,("Need to update fixed file size for type '%s'\nMin required size = %d",Script::FindChecksumName(file_type),required_size));
+	printf("required_size=%d, fixed_size=%d, %d percent\n",required_size,fixed_size,(100*required_size)/fixed_size);
+	if (required_size > fixed_size)
+	{
+		goto ERROR;
+	}	
+	#endif
+
+	pad_size=fixed_size-required_size;
+	if (pad_size && Config::GetHardware() != Config::HARDWARE_NGC)
+	{
+		p_pad=(uint8*)Mem::Malloc(pad_size);
+		for (uint32 i=0; iGetCard(0,0);
+	if (!p_card)
+	{
+		goto ERROR;
+	}
+	// GameCube often crashes if try to do card operations on a bad card, so do this check first.
+	if (!p_card->IsFormatted())
+	{
+		goto ERROR;
+	}
+	
+	char p_card_file_name[MAX_CARD_FILE_NAME_CHARS+1];
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_PS2:
+		case Config::HARDWARE_PS2_PROVIEW:
+		case Config::HARDWARE_PS2_DEVSYSTEM:
+			if (!s_make_ps2_dir_and_icons(p_card,
+										  file_type,p_name,
+										  p_card_file_name,
+										  &s_insufficient_space))
+			{
+				goto ERROR;
+			}
+			break;
+			
+		case Config::HARDWARE_NGC:
+		{
+			// On the NGC, just generate the card file name.
+			// The icon cannot be created at this point because on the NGC the
+			// icon images have to be stored in the file data itself, so we have to
+			// wait until after the file is opened.
+			char p_directory_name[MAX_CARD_FILE_NAME_CHARS+1];
+			s_generate_card_directory_name(file_type,p_name,p_directory_name);
+			
+			sprintf(p_card_file_name,"%s%s",p_directory_name,p_directory_name);
+			Dbg_MsgAssert(strlen(p_card_file_name)Open( p_card_file_name, Mc::File::mMODE_WRITE | Mc::File::mMODE_CREATE, fixed_size );
+	s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+
+	if (!pFile)
+	{
+		goto ERROR;
+	}
+		
+	pTemp=(uint8*)Mem::Malloc(header_and_structures_size);
+	
+	p_file_header=(SMcFileHeader*)pTemp;
+	// Zero the whole structure to ensure that the XBox mSignature member is zeroed since
+	// it will be part of the data who's signature is calculated.
+	p_dest=(uint8*)p_file_header;
+	for (uint32 i=0; imVersion=s_get_version_number(file_type);
+	p_file_header->mSummaryInfoSize=summary_info_size;
+	p_file_header->mDataSize=header_and_structures_size;
+
+	p_dest=(uint8*)(p_file_header+1);
+
+	// Write in the summary info and calculate its checksum.
+	BytesWritten=WriteToBuffer(pSummaryInfo, p_dest, summary_info_size, Script::NO_ASSERT);
+	if (BytesWritten!=summary_info_size)
+	{
+		// Note: This assert is surrounded by an if to prevent a warning on non debug builds
+		// due to the BytesWritten variable being unused.
+		Dbg_MsgAssert(0,("BytesWritten does not equal summary_info_size ?"));
+	}	
+	p_file_header->mSummaryInfoChecksum=Crc::GenerateCRCCaseSensitive((const char *)p_dest,summary_info_size);
+	p_dest+=summary_info_size;
+
+	
+	// Write in the game save info
+	BytesWritten=WriteToBuffer(pMemCardStuff, p_dest, structure_size, Script::NO_ASSERT);
+	
+	// K: Last minute fix, 'encrypt' the network settings so that the AOL info cannot be 
+	// read. Not very secure, but it should be enough to make the strings non obvious.
+	if (file_type==0xca41692d) // NetworkSettings
+	{
+		//printf("Encrypting %d bytes ...\n",structure_size);
+		for (uint32 e=0; emSignature)!=ERROR_SUCCESS)
+	{
+		Dbg_MsgAssert(0,("XCalculateSignatureEnd failed!"));
+	}
+	#else
+	// Now calculate a checksum of all that data.
+	// It will include the contents of p_file_header in the data too, 
+	// including mChecksum itself which will be zero at this point.
+	// By zeroing mChecksum and including it in the data that is checksum'd it
+	// means that the order of the members of SMcFileHeader does not matter.
+	// Just seems kind of ugly having to have mChecksum be the first member, cos
+	// it would also mean having to take the address of the second member to get
+	// the start address of the data to checksum, bleurgh.
+	
+	p_file_header->mChecksum=Crc::GenerateCRCCaseSensitive((const char*)pTemp,header_and_structures_size);
+	#endif // #ifdef __PLAT_XBOX__
+	
+	CardBytesWritten=pFile->Write( pTemp, header_and_structures_size );
+	//printf("Wrote %d bytes\n",CardBytesWritten);
+	s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+	if (CardBytesWritten!=header_and_structures_size)
+	{
+		goto ERROR;
+	}
+
+
+#if __USE_REPLAYS__
+	// Write out the big replay buffer.
+	// It cannot be written out in one go by getting its base pointer, because on
+	// the GameCube it is stored in ARAM.
+	// The only way to access it is via the ReadFromBuffer function.
+	// Also, there is not enough memory to copy it into a big temporary buffer, 
+	// so write it out in small chunks.
+	if (file_type==0x26c80b0d/* Replay */)
+	{
+		// Initialise the checksum, which is calculated cumulatively.
+		uint32 replay_data_checksum=0xffffffff;
+		
+		// Write out the replay data header.
+		CardBytesWritten=pFile->Write( (uint8*)&replay_data_header, sizeof(Replay::SReplayDataHeader) );
+		s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+		if (CardBytesWritten!=sizeof(Replay::SReplayDataHeader))
+		{
+			goto ERROR;
+		}
+	
+		replay_data_checksum=Crc::UpdateCRC((const char *)&replay_data_header,sizeof(Replay::SReplayDataHeader),replay_data_checksum);
+
+
+		// Write out the start-state dummies.	
+		Replay::SSavedDummy saved_dummy;
+		Replay::CDummy *p_dummy=Replay::GetFirstStartStateDummy();
+		int count=0;
+		while (p_dummy)
+		{
+			p_dummy->Save(&saved_dummy);
+			
+			CardBytesWritten=pFile->Write( (uint8*)&saved_dummy, sizeof(Replay::SSavedDummy) );
+			s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+			if (CardBytesWritten!=sizeof(Replay::SSavedDummy))
+			{
+				goto ERROR;
+			}
+			replay_data_checksum=Crc::UpdateCRC((const char *)&saved_dummy,sizeof(Replay::SSavedDummy),replay_data_checksum);
+			
+			p_dummy=p_dummy->mpNext;
+			++count;
+		}
+		if (count!=replay_data_header.mNumStartStateDummies)
+		{
+			Dbg_MsgAssert(0,("Dummy count mismatch"));
+		}	
+		
+		int buf_size=Replay::GetBufferSize();
+		Dbg_MsgAssert((buf_size%REPLAY_BUFFER_CHUNK_SIZE)==0,("Replay buffer size not a multiple of REPLAY_BUFFER_CHUNK_SIZE"));
+		
+		uint8 *p_chunk=Replay::GetTempBuffer();
+		int num_chunks=buf_size/REPLAY_BUFFER_CHUNK_SIZE;
+		
+		uint32 offset=0;
+		for (int i=0; iWrite( p_chunk, REPLAY_BUFFER_CHUNK_SIZE );
+			s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+			if (CardBytesWritten!=REPLAY_BUFFER_CHUNK_SIZE)
+			{
+				goto ERROR;
+			}
+		}
+		
+		// Write out the replay data checksum
+		CardBytesWritten=pFile->Write( (uint8*)&replay_data_checksum, 4 );
+		s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+		if (CardBytesWritten!=4)
+		{
+			goto ERROR;
+		}
+	}
+#endif
+	
+	// Now write out padding so as to make sure the file has size fixed_size, otherwise
+	// there will be a discrepancy between the space that the game claims is required and
+	// the size that appears in the file list, and I'll get a ton of TT bugs.
+	if ( Config::GetHardware() != Config::HARDWARE_NGC)
+	{
+		// TODO: Fix bug in the Write function for the NGC. It always writes from offset 0,
+		// meaning that writing this padding overwrites the start of the file data.
+		// We don't actually have to write this padding on the NGC, because the resultant file size
+		// will be exactly that specified in the file Open command.
+		// But the bug will manifest itself when saving the replay data, so need to fix it ...
+		if (pad_size)
+		{
+			Dbg_MsgAssert(p_pad,("NULL p_pad ?"));			
+			CardBytesWritten=pFile->Write( p_pad, pad_size );
+			//printf("Wrote %d bytes\n",CardBytesWritten);
+			
+			s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+			if (CardBytesWritten != pad_size)
+			{
+				goto ERROR;
+			}
+		}	
+	}	
+	
+	SavedOK=true;
+	
+ERROR:	
+	if (p_pad)
+	{
+		Mem::Free(p_pad);
+	}
+	if (pTemp)
+	{
+		Mem::Free(pTemp);
+	}
+	if (pFile)
+	{
+		if (!pFile->Flush())
+		{
+			SavedOK=false;
+		}	
+		if (!pFile->Close())
+		{
+			SavedOK=false;
+		}	
+		delete pFile;
+	}	
+	
+	delete pSummaryInfo;
+	delete pMemCardStuff;
+
+	
+	if (got_special_pools)
+	{
+		Dbg_MsgAssert(CComponent::SGetNumUsedItems()==0,("Expected the special mem card CComponent pool to be empty at this point, but got %d items",CComponent::SGetNumUsedItems()));
+		Dbg_MsgAssert(CStruct::SGetNumUsedItems()==0,("Expected the special mem card CStruct pool to be empty at this point, but got %d items",CStruct::SGetNumUsedItems()));
+		Dbg_MsgAssert(CVector::SGetNumUsedItems()==0,("Expected the special mem card CVector pool to be empty at this point, but got %d items",CVector::SGetNumUsedItems()));
+		CComponent::SSwitchToPreviousPool();
+		CStruct::SSwitchToPreviousPool();
+		CVector::SSwitchToPreviousPool();
+	}
+	
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic( -1 );
+		Pcm::PauseStream( -1 );
+	}
+
+	if (Config::GetHardware() == Config::HARDWARE_NGC)
+	{
+		ScriptRemoveTemporaryMemCardPools(NULL,NULL);
+	}
+		
+	return SavedOK;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if __USE_REPLAYS__
+	#ifdef __PLAT_NGC__
+	static SMcFileHeader sFileHeader __attribute__((aligned( 32 )));
+	#else
+	static SMcFileHeader sFileHeader;
+	#endif
+#endif
+
+bool ScriptSetSectionsToApplyWhenLoading(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    printf("ScriptSetSectionsToApplyWhenLoading was called.........................\n");
+    // These flags only apply when loading type OptionsAndPros.
+	// They allow for only certain sections of the file being applied to the game state.
+	// For example, when loading a custom skater, only the CUSTOM_SKATER part must be applied.
+	s_apply_flags=0;
+	
+	if (pParams->ContainsFlag(CRCD(0x16b506c0,"ApplyCustomSkater")))
+	{
+		s_apply_flags |= 1<ContainsFlag(CRCD(0xdc7ea3ce,"ApplyStorySkater")))
+	{
+		s_apply_flags |= 1<ContainsFlag(CRCD(0x33ec233f,"ApplyStory")))
+	{
+		s_apply_flags |= 1<ContainsFlag(CRCD(0xb39d7cc4,"ApplyGeneralOptions")))
+	{
+		s_apply_flags |= 1<ContainsFlag(CRCD(0xc4e78e22,"all")))
+	{
+		s_apply_flags=0xffffffff;
+	}	
+	
+	return true;
+}
+
+// @script | LoadFromMemoryCard | Load the specified file from the memory card
+// @parm string | Name | The name of the file to load
+// @parm name | Type | The type of the file (cas, network, park, etc.)
+bool ScriptLoadFromMemoryCard(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_MsgAssert(!Config::Bootstrap(),("Can't use memory card from bootstrap demo"));
+
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic(true);
+		Pcm::PauseStream(true);
+	}
+		
+	// Create structures for holding the summary info & game save info
+	CStruct *pSummaryInfo=new CStruct;
+	CStruct *pMemCardStuff=new CStruct;
+
+	// A bunch of variable declarations, which need to be up here cos there are goto's later
+	const char *p_name="";
+	uint32 file_type=0;
+	
+	Mc::File* p_file=NULL;
+	int file_size=0;
+	uint8 *p_temp=NULL;
+	uint8 *p_stuff=NULL;
+	SMcFileHeader *p_file_header;
+	bool loaded_ok=false;
+	uint32 calculated_checksum=0;
+	uint32 stored_checksum=0;
+	bool checksum_matches=false;
+	
+	// The LoadForUpload flag is passed as an integer so that that calls to LoadFromMemoryCard
+	// in script can set LoadForUpload equal to the value of some script global for convenience.
+	int load_for_upload=0;
+	pParams->GetInteger(CRCD(0x8c6808d4,"LoadForUpload"),&load_for_upload);
+
+	#ifdef __PLAT_XBOX__
+	XCALCSIG_SIGNATURE stored_signature;
+	XCALCSIG_SIGNATURE calculated_signature;
+	HANDLE h_signature;
+	BYTE *p_a=NULL;
+	BYTE *p_b=NULL;
+	#endif	
+	
+	// Get the card.
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (!p_card)
+	{
+		goto ERROR;
+	}
+	// GameCube often crashes if try to do card operations on a bad card, so do this check first.
+	if (!p_card->IsFormatted())
+	{
+		goto ERROR;
+	}
+		
+		
+	pParams->GetText("Name",&p_name);
+	pParams->GetChecksum("Type",&file_type);
+
+	// Calculate the low-level filename
+	char p_card_file_name[MAX_CARD_FILE_NAME_CHARS+1];
+	p_card_file_name[0]=0;
+	
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_PS2:
+		case Config::HARDWARE_PS2_PROVIEW:
+		case Config::HARDWARE_PS2_DEVSYSTEM:
+		{
+			const char *p_header=Config::GetMemCardHeader();
+			char p_ascii_checksum[20];
+			s_generate_ascii_checksum(p_ascii_checksum,p_name,file_type);
+			
+			sprintf(p_card_file_name,"/%s%s/%s%s",	p_header,p_ascii_checksum,
+													p_header,p_ascii_checksum);
+			break;
+		}	
+		case Config::HARDWARE_NGC:
+		{
+			char p_directory_name[MAX_CARD_FILE_NAME_CHARS+1];
+			s_generate_card_directory_name(file_type,p_name,p_directory_name);
+			
+			sprintf(p_card_file_name,"%s%s",p_directory_name,p_directory_name);
+			break;
+		}	
+		case Config::HARDWARE_XBOX:
+		{
+			// Generate the directory name
+			const char *p_directory_name=s_generate_xbox_directory_name(file_type,p_name);
+			const char *p_low_level_directory_name=p_card->ConvertDirectory(p_directory_name);
+		
+			if (!p_low_level_directory_name)
+			{
+				goto ERROR;
+			}
+						
+			// Calculate the low-level file name.
+			sprintf( p_card_file_name, "/%s/%s", p_low_level_directory_name, p_low_level_directory_name );
+			break;
+		}	
+		default:
+		{
+			goto ERROR;
+			break;
+		}	
+	}
+	
+	Dbg_MsgAssert(strlen(p_card_file_name)Open( p_card_file_name, Mc::File::mMODE_READ );
+	if (!p_file)
+	{
+		// File could not be opened
+		goto ERROR;
+	}	
+	
+	// File opened OK
+	
+	// Check the file size.
+	file_size=p_file->Seek( 0 ,Mc::File::BASE_END);
+	// Removed the file size check, because the fixed size needed to be updated, and keeping the
+	// check would prevent existing parks from being able to be loaded.
+	//if (file_size != (int)sGetFixedFileSize(file_type))
+	//{
+	//	// Size mismatch, so consider the file corrupted.
+	//	pScript->GetParams()->AddChecksum(NONAME,GenerateCRC("CorruptedData"));
+	//	goto ERROR;
+	//}	
+	
+	// Read the file into memory
+	// Allocate a temporary buffer for reading the file into
+	p_temp=(uint8*)Mem::Malloc(file_size);
+	Dbg_MsgAssert(p_temp,("Could not allocate %d bytes for file buffer for file %s",file_size,p_card_file_name));
+
+	p_file->Seek(0,Mc::File::BASE_START);
+	if (p_file->Read(p_temp, file_size) != file_size)
+	{
+		// Some sort of read error.
+		goto ERROR;
+	}		
+	
+	// Seemed to read into memory OK
+	// Check if the data is corrupt by recalculating the checksum.
+	p_stuff=p_temp;
+	p_file_header=(SMcFileHeader*)p_stuff;
+	
+	#ifdef __PLAT_XBOX__
+	// Load p_file_header->mSignature into stored_signature
+	// then zero out p_file_header->mSignature
+	p_a=p_file_header->mSignature.Signature;
+	p_b=stored_signature.Signature;
+	for (int i=0; imChecksum;
+	
+	// Set the checksum to zero because that was what it was when
+	// the checksum was calculated.
+	p_file_header->mChecksum=0;
+	// Using p_file_header->mDataSize instead of file_size, because file_size includes the
+	// padding, which is not included in the checksum calculation on PS2 and NGC
+	if (p_file_header->mDataSize > 500000)
+	{
+		// If mDataSize itself is corrupted, then don't go trying to calculate the
+		// checksum of megabytes of data, in case that hangs the game by hitting some sort
+		// of restricted memory location.
+	}
+	else
+	{
+		calculated_checksum=Crc::GenerateCRCCaseSensitive((const char*)p_stuff,p_file_header->mDataSize);
+	
+		if (calculated_checksum==stored_checksum &&
+			p_file_header->mVersion==s_get_version_number(file_type))
+		{
+			checksum_matches=true;
+		}
+	}	
+	#endif
+
+		
+	if (checksum_matches)
+	{
+		// The data is OK, so it is safe to parse it.
+		
+		// Skip over the header.
+		p_stuff=(uint8*)(p_file_header+1);
+
+		// Skip over the summary info
+		p_stuff=ReadFromBuffer(pSummaryInfo, p_stuff);
+
+		// K: Last minute fix, 'decrypt' the network settings.
+		if (file_type==0xca41692d) // NetworkSettings
+		{
+			int num_bytes=p_file_header->mDataSize-(p_stuff-((uint8*)p_file_header));
+			//printf("Decrypting %d bytes ...\n",num_bytes);
+			for (int e=0; eGetScriptInfo()));
+			spVaultData=new Script::CStruct;
+			spVaultData->AppendStructure(pMemCardStuff);
+			sVaultDataType=file_type;
+
+            if ( file_type == CRCD(0x61a1bc57,"cat") )
+            {
+                // load Created trick in edit slot so we can get some info out of it for uploading
+                s_read_game_save_info(file_type, pMemCardStuff, pScript);
+            }
+		}
+		else
+		{
+			// process the data according to the type of file loaded.
+			s_read_game_save_info(file_type, pMemCardStuff, pScript);
+		}
+				
+		loaded_ok=true;
+	}	
+	else
+	{
+		// Corrupted data, or bad version number
+		pScript->GetParams()->AddChecksum(NONAME,GenerateCRC("CorruptedData"));
+	}	
+
+	// if the loading was successful,
+	// then run some post-load functions
+	if ( loaded_ok )
+	{
+		if (load_for_upload)
+		{
+			// If the file was loaded for upload to the net, then do not do any post processing,
+			// since the data has just been stored away for retrieval later.
+		}
+		else
+		{
+			Script::RunScript( "post_load_from_memory_card", pParams );
+		}	
+	}
+
+ERROR:	
+	// Cleanup and unpause the music.
+	if (p_file)
+	{
+		p_file->Close();
+		delete p_file;
+	}
+
+	if (p_temp)
+	{
+		Mem::Free(p_temp);
+	}
+
+	delete pSummaryInfo;
+	delete pMemCardStuff;
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic( -1 );
+		Pcm::PauseStream( -1 );
+	}
+		
+	return loaded_ok;
+}
+
+bool ScriptLoadedCustomSkater(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return s_did_apply_custom_skater_info;
+}	
+
+// Functions required for when loading a file of mem card for uploading to the net.
+bool ScriptGetMemCardDataForUpload(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_MsgAssert(spVaultData,("\n%s\nNo mem card data present",pScript->GetScriptInfo()));
+	
+	pScript->GetParams()->AddStructure(CRCD(0x6d2ab6a,"DataForUpload"),spVaultData);
+	pScript->GetParams()->AddChecksum(CRCD(0x7321a8d6,"Type"),sVaultDataType);
+	return true;
+}	
+
+bool ScriptClearMemCardDataForUpload(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if (spVaultData)
+	{
+		delete spVaultData;
+		spVaultData=NULL;
+	}	
+	sVaultDataType=0;
+	return true;
+}	
+
+bool ScriptNeedToLoadReplayBuffer(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#if __USE_REPLAYS__
+	return sNeedToLoadReplayBuffer;
+#else
+	return false;
+#endif
+}
+
+bool ScriptLoadReplayData(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#if __USE_REPLAYS__
+	Dbg_MsgAssert(!Config::Bootstrap(),("Can't use memory card from bootstrap demo"));
+
+	Pcm::PauseMusic(true);
+	Pcm::PauseStream(true);
+	
+	Dbg_MsgAssert(sNeedToLoadReplayBuffer,("Called LoadReplayBuffer when sNeedToLoadReplayBuffer is false"));
+
+	int replay_buffer_size=0;
+	Replay::SReplayDataHeader header;
+	Replay::SSavedDummy saved_dummy;
+	uint32 replay_data_checksum=0xffffffff; // Initialise the checksum, which is calculated accumulatively.
+	uint32 ch=0;	
+	bool loaded_ok=false;
+	int bytes_read=0;
+	Mc::File* p_file=NULL;
+	uint8 *p_chunk=NULL;
+	int num_chunks=0;
+	int offset=0;
+	
+	// Get the card.
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (!p_card)
+	{
+		goto ERROR;
+	}
+	
+	// GameCube often crashes if try to do card operations on a bad card, so do this check first.
+	if (!p_card->IsFormatted())
+	{
+		goto ERROR;
+	}
+
+	// Open the file.
+	p_file=p_card->Open( spReplayCardFileName, Mc::File::mMODE_READ );
+	if (!p_file)
+	{
+		// File could not be opened
+		goto ERROR;
+	}	
+	
+	// File opened OK
+
+	// Seek to the start of the replay data by reading in the header, reading out the start data
+	// size from the header, then seeking there.	
+	p_file->Seek( 0 ,Mc::File::BASE_START);
+	if (p_file->Read((uint8*)&sFileHeader, sizeof(SMcFileHeader)) != sizeof(SMcFileHeader))
+	{
+		// Some sort of read error.
+		goto ERROR;
+	}		
+	p_file->Seek(sFileHeader.mDataSize,Mc::File::BASE_START);
+
+	// Make sure the buffer does exist
+	Replay::AllocateBuffer();
+	// and that it is cleared to start with.
+	Replay::ClearBuffer();
+			
+	// Read in the header			
+	bytes_read=p_file->Read( (uint8*)&header, sizeof(Replay::SReplayDataHeader) );
+	if (bytes_read!=sizeof(Replay::SReplayDataHeader))
+	{
+		// Some sort of read error.
+		goto ERROR;
+	}
+	Replay::ReadReplayDataHeader(&header);
+	replay_data_checksum=Crc::UpdateCRC((const char *)&header,sizeof(Replay::SReplayDataHeader),replay_data_checksum);
+	
+	// Read in the dummies
+	for (int i=0; iRead( (uint8*)&saved_dummy, sizeof(Replay::SSavedDummy) );
+		if (bytes_read!=sizeof(Replay::SSavedDummy))
+		{
+			// Some sort of read error.
+			goto ERROR;
+		}
+		replay_data_checksum=Crc::UpdateCRC((const char *)&saved_dummy,sizeof(Replay::SSavedDummy),replay_data_checksum);
+		
+		Replay::CreateDummyFromSaveData(&saved_dummy);
+	}
+			
+	// Read the data in a chunk at a time.
+	replay_buffer_size=Replay::GetBufferSize();
+	Dbg_MsgAssert((replay_buffer_size%REPLAY_BUFFER_CHUNK_SIZE)==0,("Replay buffer size not a multiple of REPLAY_BUFFER_CHUNK_SIZE"));
+	
+	p_chunk=Replay::GetTempBuffer();
+	num_chunks=replay_buffer_size/REPLAY_BUFFER_CHUNK_SIZE;
+	
+	offset=0;
+	for (int i=0; iRead( p_chunk, REPLAY_BUFFER_CHUNK_SIZE );
+		if (bytes_read!=REPLAY_BUFFER_CHUNK_SIZE)
+		{
+			// Some sort of read error.
+			goto ERROR;
+		}
+		Replay::WriteIntoBuffer(p_chunk,offset,REPLAY_BUFFER_CHUNK_SIZE);
+		replay_data_checksum=Crc::UpdateCRC((const char *)p_chunk,REPLAY_BUFFER_CHUNK_SIZE,replay_data_checksum);
+		
+		offset+=REPLAY_BUFFER_CHUNK_SIZE;
+	}
+
+	// Read in and check the checksum
+	bytes_read=p_file->Read( (uint8*)&ch, 4 );
+	if (bytes_read!=4)
+	{
+		// Some sort of read error.
+		goto ERROR;
+	}
+	if (ch != replay_data_checksum)
+	{
+		goto ERROR;
+	}	
+		
+	// Hooray!	
+	loaded_ok=true;
+
+ERROR:	
+	sNeedToLoadReplayBuffer=false;
+	
+	if (p_file)
+	{
+		p_file->Close();
+		delete p_file;
+	}
+
+	Pcm::PauseMusic( -1 );
+	Pcm::PauseStream( -1 );
+	return loaded_ok;
+#else
+	return false;
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetMaxTHPS4FilesAllowed(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddInteger("MaxTHPS4FilesAllowed",MAX_THPS4_FILES_ALLOWED);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static uint8 spSummaryInfoBuffer[sizeof(SMcFileHeader)+MAX_SUMMARY_INFO_SIZE];
+bool ScriptGetMemCardDirectoryListing(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->RemoveComponent("DirectoryListing");
+	pScript->GetParams()->RemoveComponent("FilesLimitReached");
+	pScript->GetParams()->RemoveComponent("TotalTHPS4FilesOnCard");
+
+	uint32 file_type_to_list=0;
+	pParams->GetChecksum("FileType",&file_type_to_list);
+
+	// Get a directory listing of the whole card.
+	Mc::Manager * p_mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=p_mc_man->GetCard(0,0);
+	if (!p_card)
+	{
+		return true;
+	}	
+
+	Lst::Head< Mc::File > file_list;
+	p_card->GetFileList( "*", file_list );
+
+	// If the special pools exist, switch to them so that the components & structs etc get allocated off them.
+	// The special pools use the space freed by unloading the skater anims, and are for preventing memory
+	// overflows when the save size gets large.
+	Dbg_MsgAssert(CComponent::SGetCurrentPoolIndex()==0,("Bad current CComponent pool"));
+	Dbg_MsgAssert(CStruct::SGetCurrentPoolIndex()==0,("Bad current CStruct pool"));
+	bool got_special_pools=false;
+	
+	CComponent::SSwitchToNextPool();
+	if (CComponent::SPoolExists())
+	{
+		CStruct::SSwitchToNextPool();
+		Dbg_MsgAssert(CStruct::SPoolExists(),("No special CStruct pool ?"));
+		got_special_pools=true;
+	}
+	else
+	{
+		CComponent::SSwitchToPreviousPool();
+	}	
+
+	
+	int num_files_added=0;
+	CStruct *pp_structs[MAX_THPS4_FILES_ALLOWED];
+	
+	// Run through all the files (well, directory names actually) and compare their
+	// first 12 characters with the THPS4 header.	
+	const char *p_header=Config::GetMemCardHeader();
+	int header_len=strlen(p_header);
+	
+	int num_files=file_list.CountItems();
+	int total_thps4_files=0;
+	for (int i=0; iGetData();
+		
+		bool is_THPS4_file=true;
+		
+		// No need to check the file name if it's the XBox because all the files
+		// will be in our filespace.
+		if ( Config::GetHardware() != Config::HARDWARE_XBOX )
+		{
+			for (int j=0; jm_Filename[j]!=p_header[j])
+				{
+					is_THPS4_file=false;
+					break;
+				}
+			}
+		}	
+		
+		if (!is_THPS4_file)
+		{
+			continue;
+		}
+		
+		++total_thps4_files;
+		
+		// It is a THPS4 file, so compare the file type with that requested.
+		uint32 file_type=0;
+		
+		
+		switch (Config::GetHardware())
+		{
+		case Config::HARDWARE_PS2:
+		case Config::HARDWARE_PS2_PROVIEW:
+		case Config::HARDWARE_PS2_DEVSYSTEM:
+		case Config::HARDWARE_NGC:
+			// The filetype is indicated by the last letter of the 8 characters
+			// that follow the header. The other 7 contain the ascii checksum of
+			// the full file name as entered by the user.
+			file_type=s_determine_file_type(p_file->m_Filename[header_len+8-1]);
+			break;
+		case Config::HARDWARE_XBOX:
+		{
+			// TODO: Remove this ifdef somehow, needed currently because m_DisplayFilename
+			// is only defined for XBox. Shouldn't really have any platform ifdefs in this file.
+			#ifdef __PLAT_XBOX__
+			int length = strlen( p_file->m_DisplayFilename );
+			if( stricmp( &p_file->m_DisplayFilename[length - 15], "NetworkSettings" ) == 0 )
+			{
+				file_type = 0xca41692d; // NetworkSettings
+			}
+			else if( stricmp( &p_file->m_DisplayFilename[length - 6], "Skater" ) == 0 )
+			{
+				file_type = 0xb010f357; // OptionsAndPros
+			}
+			else if( stricmp( &p_file->m_DisplayFilename[length - 4], "Park" ) == 0 )
+			{
+				file_type = 0x3bf882cc; // Park
+			}
+			else if( stricmp( &p_file->m_DisplayFilename[length - 6], "Skater" ) == 0 )
+			{
+				file_type = 0xffc529f4; // Cas
+			}
+            else if( stricmp( &p_file->m_DisplayFilename[length - 5], "Trick" ) == 0 )
+			{
+				file_type = 0x61a1bc57; // CAT
+			}
+			else if( stricmp( &p_file->m_DisplayFilename[length - 6], "Replay" ) == 0 )
+			{
+				file_type = 0x26c80b0d; // Replay
+			}
+			else if( stricmp( &p_file->m_DisplayFilename[length - 5], "Goals" ) == 0 )
+			{
+				file_type = 0x62896edf; // CreatedGoals
+			}
+			#endif // #ifdef __PLAT_XBOX__
+			break;
+		}	
+		case Config::HARDWARE_WIN32:
+			break;
+		default:
+			break;
+		}
+		
+		
+		
+		if ( (file_type_to_list==0 && file_type!=0) || 
+			 file_type==file_type_to_list)
+		{
+			if (num_files_added < MAX_THPS4_FILES_ALLOWED)
+			{
+				// It's safe to add a new entry to the array.
+				CStruct *p_struct=new CStruct;
+				p_struct->AddChecksum("file_type",file_type);
+				p_struct->AddInteger("Index",num_files_added);
+
+
+				p_struct->AddInteger("Year",p_file->m_Modified.m_Year);
+				p_struct->AddInteger("Month",p_file->m_Modified.m_Month);
+				p_struct->AddInteger("Day",p_file->m_Modified.m_Day);
+				p_struct->AddInteger("Hour",p_file->m_Modified.m_Hour);
+				p_struct->AddInteger("Minutes",p_file->m_Modified.m_Minutes);
+				p_struct->AddInteger("Seconds",p_file->m_Modified.m_Seconds);
+
+				
+				char p_card_file_name[100];
+				if (Config::GetHardware()==Config::HARDWARE_NGC)
+				{
+					sprintf(p_card_file_name,"%s",p_file->m_Filename);
+				}	
+				else
+				{
+					sprintf(p_card_file_name,"/%s/%s",p_file->m_Filename,p_file->m_Filename);
+				}	
+				Dbg_MsgAssert(strlen(p_card_file_name)<100,("Oops"));
+				
+				// Store the low level file name in the structure too so that the scripts
+				// can pass it on to the file delete function. This is necessary because
+				// even though the low level file name can be derived from the file name as
+				// stored in the file itself, if the file is corrupted the name might be too,
+				// so in that case it would not be possible to delete it.
+				p_struct->AddString("actual_file_name",p_card_file_name);
+				
+				#ifdef __PLAT_XBOX__
+				p_struct->AddString("xbox_directory_name",p_file->m_DisplayFilename);
+				#endif
+
+				// Need to open the file to get the summary info out of it ...
+				Mc::File* p_mc_file=p_card->Open( p_card_file_name, Mc::File::mMODE_READ );
+				if (p_mc_file)
+				{   
+					// File opened OK, so grab the first hundred or so bytes.
+					p_mc_file->Seek( 0 ,Mc::File::BASE_START);
+					p_mc_file->Read( spSummaryInfoBuffer, sizeof(SMcFileHeader)+MAX_SUMMARY_INFO_SIZE );
+					
+					SMcFileHeader *p_file_header=(SMcFileHeader*)spSummaryInfoBuffer;
+					uint8 *p_summary_info=(uint8*)(p_file_header+1);
+					
+					// Determine whether the summary info is corrupted.
+					bool corrupt_summary_info=false;
+					if (p_file_header->mSummaryInfoSize > MAX_SUMMARY_INFO_SIZE)
+					{
+						// The mSummaryInfoSize itself is corrupted!
+						corrupt_summary_info=true;
+					}
+					else
+					{
+						uint32 calculated_summary_info_checksum=Crc::GenerateCRCCaseSensitive((const char *)p_summary_info,p_file_header->mSummaryInfoSize);
+						if (calculated_summary_info_checksum != p_file_header->mSummaryInfoChecksum)
+						{
+							corrupt_summary_info=true;
+						}
+					}		
+
+					// Calculate the total file size
+					int file_size=p_mc_file->Seek(0,Mc::File::BASE_END);
+					//printf("Directory listing: %s, size=%d\n",p_card_file_name,file_size);
+					int total_size_on_card=s_calculate_total_space_used_on_card(file_type,file_size);
+					p_struct->AddInteger("total_file_size",total_size_on_card);
+
+					
+					// Removed the file size check, because the fixed size needed to be updated, and keeping the
+					// check would prevent existing parks from being able to be loaded.
+					//if (corrupt_summary_info || file_size != (int)sGetFixedFileSize(file_type))
+					if (corrupt_summary_info)
+					{
+						p_struct->AddChecksum(NONAME,Script::GenerateCRC("Corrupt"));
+						if (Config::GetHardware()==Config::HARDWARE_NGC)
+						{
+							p_struct->AddString("filename",GetString("NGCDamagedFile"));
+						}
+						else
+						{
+							p_struct->AddString("filename",GetString("DamagedFile"));
+						}	
+					}
+					else if (p_file_header->mVersion!=s_get_version_number(file_type))
+					{
+						#ifdef __NOPT_ASSERT__
+						p_struct->AddChecksum(NONAME,Script::GenerateCRC("BadVersion"));
+						p_struct->AddString("filename",GetString("BadVersionNumber"));
+						#else
+						p_struct->AddChecksum(NONAME,Script::GenerateCRC("Corrupt"));
+						if (Config::GetHardware()==Config::HARDWARE_NGC)
+						{
+							p_struct->AddString("filename",GetString("NGCDamagedFile"));
+						}
+						else
+						{
+							p_struct->AddString("filename",GetString("DamagedFile"));
+						}	
+						#endif
+					}
+					else
+					{
+						// Extract the summary info and stick it in the structure.
+						// This summary info gets printed at the bottom of the files menu when
+						// the highlight is on the file.
+						// The summary info is actually in the main structure stored in the file,
+						// but that would require loading in the whole thing which would take a
+						// long time.
+						
+						// Have to use a temporary structure because ReadFromBuffer clears the
+						// passed structure first.
+						CStruct *p_temp=new CStruct;
+						ReadFromBuffer(p_temp,p_summary_info);
+						p_struct->AppendStructure(p_temp);
+						delete p_temp;
+					}
+					
+					p_mc_file->Close();
+					delete p_mc_file;
+				}
+				else
+				{
+					// If the file did not open at all, treat it as corrupted.
+					p_struct->AddChecksum(NONAME,Script::GenerateCRC("Corrupt"));
+					p_struct->AddString("filename",GetString("DamagedFile"));
+				}	
+					
+				pp_structs[num_files_added++]=p_struct;
+			}	
+		}
+	}	
+
+	if (got_special_pools)
+	{
+		// Note: The pools will contain stuff at this point. The script will delete them
+		// when it does its cleanup.
+		CComponent::SSwitchToPreviousPool();
+		CStruct::SSwitchToPreviousPool();
+	}
+	
+	// The file_list will probably get cleaned up when it goes out of scope, but just to be sure ...
+	file_list.DestroyAllNodes();
+
+	pScript->GetParams()->AddInteger("TotalTHPS4FilesOnCard",total_thps4_files);
+	
+	// Set the FilesLimitReached flag so that the script can determine whether to add
+	// the 'Create New' entry to the files menu.
+	if (total_thps4_files >= MAX_THPS4_FILES_ALLOWED)
+	{
+		pScript->GetParams()->AddChecksum(NONAME,0x4eec27b5/*FilesLimitReached*/);
+	}
+	
+	// If files were found, add the array to the script's params.
+	if (num_files_added)
+	{
+		// First, sort the array so that the files are definitely in date order (TT6112)
+		while (true)
+		{
+			bool did_a_swap=false;
+			
+			for (int i=0; iSetSizeAndType(num_files_added,ESYMBOLTYPE_STRUCTURE);
+		
+		for (int f=0; fSetStructure(f,pp_structs[f]);
+		}
+		
+		pScript->GetParams()->AddArrayPointer("DirectoryListing",p_directory_listing_array);
+		
+		// Note: Not deleting the pp_structs[], cos they've been given to the array.
+		// Not deleting p_directory_listing_array, because it's been given to the scripts params.
+	}
+		
+	return true;
+}
+
+// Returns true if the sector size is 8K, false otherwise.
+// Needed as part of the GameCube card check procedure to check that the card is not some weird type.
+// If it's a PS2 build, it will just return true.
+bool ScriptSectorSizeOK(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#ifdef __PLAT_NGC__
+	Spt::SingletonPtr< Mc::Manager > mc_man;
+	Mc::Card* pCard=mc_man->GetCard(0,0);
+	if (pCard)
+	{
+		if (pCard->m_sector_size==8192)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}		
+	return false;
+	#else
+	return true;
+	#endif
+}
+
+bool ScriptCardIsDamaged(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#ifdef __PLAT_NGC__
+	Spt::SingletonPtr< Mc::Manager > mc_man;
+	Mc::Card* pCard=mc_man->GetCard(0,0);
+	if (pCard)
+	{
+		return pCard->m_broken;
+	}
+	else
+	{
+		if (mc_man->GotFatalError())
+		{
+			return true;
+		}
+	}	
+	return false;
+	#else
+	return false;
+	#endif
+}
+
+bool ScriptCardIsForeign(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#ifdef __PLAT_NGC__
+	Spt::SingletonPtr< Mc::Manager > mc_man;
+	Mc::Card* pCard=mc_man->GetCard(0,0);
+	if (pCard)
+	{
+		return pCard->IsForeign();
+	}
+	return false;
+	#else
+	return false;
+	#endif
+}
+
+bool ScriptBadDevice(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	#ifdef __PLAT_NGC__
+	Spt::SingletonPtr< Mc::Manager > mc_man;
+	Mc::Card* pCard=mc_man->GetCard(0,0);
+	if (pCard)
+	{
+		return false;
+	}
+	else
+	{
+		if (mc_man->GotWrongDevice())
+		{
+			return true;
+		}
+	}	
+	return false;
+	#else
+	return false;
+	#endif
+}
+
+// For debugging, so that we can use the script debugger to view the contents of the save structure that would
+// get saved out to the mem card.
+bool ScriptGetSaveInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 file_type=0;
+	pParams->GetChecksum(CRCD(0x7321a8d6,"type"),&file_type);
+
+	Script::CStruct *p_main_structure=new Script::CStruct;
+	s_insert_game_save_info(file_type, p_main_structure);
+	uint32 structure_size=CalculateBufferSize(p_main_structure);
+	
+	Script::CStruct *p_summary_info=new Script::CStruct;
+	s_generate_summary_info(p_summary_info, file_type, p_main_structure);
+	uint32 summary_info_size=CalculateBufferSize(p_summary_info);
+
+	
+	pScript->GetParams()->AddInteger(CRCD(0x172c1344,"MainStructureSize"),structure_size);
+	pScript->GetParams()->AddInteger(CRCD(0x96635475,"SummaryInfoSize"),summary_info_size);
+	pScript->GetParams()->AddInteger(CRCD(0x6afd2f7f,"MaxSummaryInfoSize"),MAX_SUMMARY_INFO_SIZE);
+	pScript->GetParams()->AddInteger(CRCD(0xecbb8262,"TotalSize"),sizeof(SMcFileHeader)+summary_info_size+structure_size);
+	pScript->GetParams()->AddInteger(CRCD(0xb35eb1d1,"MaxTotalSize"),sGetFixedFileSize(file_type));
+	
+	pScript->GetParams()->AddStructurePointer(CRCD(0x703e60ca,"SummaryInfo"),p_summary_info);
+	pScript->GetParams()->AddStructurePointer(CRCD(0x671c9c00,"MainStructure"),p_main_structure);
+	
+	return true;
+}
+
+// It is safe to call this multiple times.
+bool ScriptCreateTemporaryMemCardPools(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// If the pools exist already, do nothing.
+	CComponent::SSwitchToNextPool();
+	if (CComponent::SPoolExists())
+	{
+		CComponent::SSwitchToPreviousPool();
+		
+		CStruct::SSwitchToNextPool();								 
+		Dbg_MsgAssert(CStruct::SPoolExists(),("Expected CStruct pool to exist"));
+		CStruct::SSwitchToPreviousPool();
+
+		CVector::SSwitchToNextPool();								 
+		Dbg_MsgAssert(CVector::SPoolExists(),("Expected CVector pool to exist"));
+		CVector::SSwitchToPreviousPool();
+		
+		return true;
+	}	
+	CComponent::SSwitchToPreviousPool();
+	
+#ifdef __PLAT_NGC__
+#define NUM_COM 16000
+#define NUM_STR 8000
+#define NUM_VEC 3000
+
+#define BUFFER_SIZE ( ( NUM_COM * 16 ) + ( NUM_STR * 8 ) + ( NUM_VEC * 16 ) )
+
+	// Time for a hack...
+	g_mc_hack = true;
+
+	g_hack_address = NsARAM::alloc( BUFFER_SIZE );
+	if ( g_hack_address )
+	{
+		NsDMA::toARAM( g_hack_address, g_p_buffer, BUFFER_SIZE );
+	}
+
+	memset( g_p_buffer, 0, BUFFER_SIZE );
+
+	NsBuffer::reset( false );
+#else
+#define NUM_COM 50000
+#define NUM_STR 50000
+#define NUM_VEC 10000
+#endif		// __PLAT_NGC__
+
+		
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+
+	Mem::PushMemProfile("Mem card CComponent");
+	Dbg_MsgAssert(CComponent::SGetCurrentPoolIndex()==0,("Bad current CComponent pool"));
+	CComponent::SSwitchToNextPool();
+	CComponent::SCreatePool(NUM_COM, "Reserve CComponent");
+	CComponent::SSwitchToPreviousPool();
+	Mem::PopMemProfile();
+										 
+	Mem::PushMemProfile("Mem card CStruct");
+	Dbg_MsgAssert(CStruct::SGetCurrentPoolIndex()==0,("Bad current CStruct pool"));
+	CStruct::SSwitchToNextPool();
+	CStruct::SCreatePool(NUM_STR, "Reserve CStruct");
+	CStruct::SSwitchToPreviousPool();
+	Mem::PopMemProfile();
+	
+	// 12 bytes each  (Actually 16)
+	Mem::PushMemProfile("Mem card CVector");
+	Dbg_MsgAssert(CVector::SGetCurrentPoolIndex()==0,("Bad current CVector pool"));
+	CVector::SSwitchToNextPool();
+	CVector::SCreatePool(NUM_VEC, "Reserve CVector");
+	CVector::SSwitchToPreviousPool();
+	Mem::PopMemProfile();
+
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+// It is safe to call this multiple times.
+bool ScriptRemoveTemporaryMemCardPools(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_MsgAssert(CComponent::SGetCurrentPoolIndex()==0,("Bad current CComponent pool"));
+	CComponent::SSwitchToNextPool();
+	CComponent::SRemovePool(); // Does nothing if the pool does not exist.
+	CComponent::SSwitchToPreviousPool();
+	
+	Dbg_MsgAssert(CStruct::SGetCurrentPoolIndex()==0,("Bad current CStruct pool"));
+	CStruct::SSwitchToNextPool();
+	CStruct::SRemovePool();
+	CStruct::SSwitchToPreviousPool();
+		
+	Dbg_MsgAssert(CVector::SGetCurrentPoolIndex()==0,("Bad current CVector pool"));
+	CVector::SSwitchToNextPool();
+	CVector::SRemovePool();
+	CVector::SSwitchToPreviousPool();
+	
+#ifdef __PLAT_NGC__
+	if ( g_hack_address )
+	{
+		NsDMA::toMRAM( g_p_buffer, g_hack_address, BUFFER_SIZE );
+	}
+	NsARAM::free( g_hack_address );
+
+	g_mc_hack = false;
+	NsBuffer::reset( true );
+#endif		// __PLAT_NGC__
+
+	return true;
+}
+
+bool ScriptSwitchToTempPoolsIfTheyExist(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Dbg_MsgAssert(CComponent::SGetCurrentPoolIndex()==0 && CStruct::SGetCurrentPoolIndex()==0 && CVector::SGetCurrentPoolIndex()==0, ("Expected current pools to be 0"));
+	CComponent::SSwitchToNextPool();
+	CStruct::SSwitchToNextPool();
+	CVector::SSwitchToNextPool();
+	if (CComponent::SPoolExists() && CStruct::SPoolExists() && CVector::SPoolExists())
+	{
+		return true;
+	}
+	CComponent::SSwitchToPreviousPool();
+	CStruct::SSwitchToPreviousPool();
+	CVector::SSwitchToPreviousPool();
+
+	return false;
+}
+
+bool ScriptSwitchToRegularPools(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if (CComponent::SGetCurrentPoolIndex()==0 && 
+		CStruct::SGetCurrentPoolIndex()==0 &&
+		CVector::SGetCurrentPoolIndex()==0)
+	{
+		return true;
+	}
+		
+	CComponent::SSwitchToPreviousPool();
+	CStruct::SSwitchToPreviousPool();
+	CVector::SSwitchToPreviousPool();
+	return true;
+}
+
+// Saves any old data file to mem card
+bool SaveDataFile(const char *p_name, uint8 *p_data, uint32 size)
+{
+	Dbg_MsgAssert(p_name,("NULL p_name"));
+	Dbg_MsgAssert(p_data,("NULL p_data"));
+
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_PS2:
+		case Config::HARDWARE_PS2_PROVIEW:
+		case Config::HARDWARE_PS2_DEVSYSTEM:
+			break;
+		default:
+			return false;
+			break;
+	}
+				
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic(true);
+		Pcm::PauseStream(true);
+	}
+	
+	Mc::File* pFile=NULL;
+	char p_card_file_name[MAX_CARD_FILE_NAME_CHARS+1];
+	const char *p_header=Config::GetMemCardHeader();
+	char p_ascii_checksum[20];
+	int suffix=0;
+	#define FULL_NAME_BUF_SIZE 100
+	char p_full_name[FULL_NAME_BUF_SIZE];
+	bool SavedOK=false;
+	uint32 CardBytesWritten=0;
+
+
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (!p_card)
+	{
+		goto ERROR;
+	}
+
+	while (true)
+	{
+		Dbg_MsgAssert(strlen(p_name)Open( p_card_file_name, Mc::File::mMODE_READ );
+		if (pFile)
+		{
+			pFile->Flush();
+			pFile->Close();
+			delete pFile;
+			pFile=NULL;
+		}
+		else
+		{
+			break;
+		}
+		++suffix;
+		Dbg_MsgAssert(suffix<1000,("Too many files!!"));
+	}		
+
+	printf ("Creating mem card file '%s'\n",p_full_name);
+	
+	if (!s_make_ps2_dir_and_icons(p_card,
+								  0xb010f357, // OptionsAndPros tee hee
+								  p_full_name,
+								  p_card_file_name,
+								  &s_insufficient_space))
+	{
+		goto ERROR;
+	}
+
+	// Open a file big enough to hold all data
+	pFile=p_card->Open( p_card_file_name, Mc::File::mMODE_WRITE | Mc::File::mMODE_CREATE, s_round_up_to_platforms_block_size(size) );
+	s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+
+	if (!pFile)
+	{
+		goto ERROR;
+	}
+
+	CardBytesWritten=pFile->Write( p_data, size );
+	s_insufficient_space=p_card->GetLastError()==Mc::Card::vINSUFFICIENT_SPACE;
+	if (CardBytesWritten!=size)
+	{
+		goto ERROR;
+	}
+
+	SavedOK=true;
+	
+ERROR:	
+	if (pFile)
+	{
+		if (!pFile->Flush())
+		{
+			SavedOK=false;
+		}	
+		if (!pFile->Close())
+		{
+			SavedOK=false;
+		}	
+		delete pFile;
+	}	
+
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic( -1 );
+		Pcm::PauseStream( -1 );
+	}
+		
+	return SavedOK;
+}
+
+
+
+// Load len bytes of any old data file to mem card to p_data
+bool LoadDataFile(const char *p_name, uint8 *p_data, uint32 size)
+{
+	Dbg_MsgAssert(p_name,("NULL p_name"));
+	Dbg_MsgAssert(p_data,("NULL p_data"));
+
+	switch (Config::GetHardware())
+	{
+		case Config::HARDWARE_PS2:
+		case Config::HARDWARE_PS2_PROVIEW:
+		case Config::HARDWARE_PS2_DEVSYSTEM:
+			break;
+		default:
+			return false;
+			break;
+	}
+				
+	if ( Config::GetHardware() != Config::HARDWARE_XBOX)
+	{
+		Pcm::PauseMusic(true);
+		Pcm::PauseStream(true);
+	}
+	
+	Mc::File* pFile=NULL;
+	char p_card_file_name[MAX_CARD_FILE_NAME_CHARS+1];
+	const char *p_header=Config::GetMemCardHeader();
+	char p_ascii_checksum[20];
+	#define FULL_NAME_BUF_SIZE 100
+	char p_full_name[FULL_NAME_BUF_SIZE];
+
+	Mc::Manager * mc_man = Mc::Manager::Instance();
+	Mc::Card* p_card=mc_man->GetCard(0,0);
+	if (!p_card)
+	{
+		return false;;
+	}
+
+	Dbg_MsgAssert(strlen(p_name)Open( p_card_file_name, Mc::File::mMODE_READ );
+	if (pFile)
+	{
+		printf ("Loading From Memory Card\n");
+		pFile->Flush();
+		pFile->Seek( 0 ,Mc::File::BASE_START);
+		pFile->Read(p_data,size);
+		pFile->Flush();
+		pFile->Close();
+		delete pFile;
+		return true;
+	}
+	return false;
+}
+
+
+
+} // namespace CFuncs
+
diff --git a/Code/Sk/Scripting/mcfuncs.h b/Code/Sk/Scripting/mcfuncs.h
new file mode 100644
index 0000000..9b030d7
--- /dev/null
+++ b/Code/Sk/Scripting/mcfuncs.h
@@ -0,0 +1,107 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Scripting												**
+**																			**
+**	File name:		mcfuncs.h												**
+**																			**
+**	Created: 		06/10/2002	-	ksh										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SCRIPTING_MCFUNCS_H
+#define	__SCRIPTING_MCFUNCS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+};
+	
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace CFuncs
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+bool ScriptGetMostRecentSave(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetMemCardSpaceAvailable(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetMemCardSpaceRequired(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptMemCardFileExists(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDeleteMemCardFile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFormatCard(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCardIsInSlot(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCardIsFormatted(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSaveFailedDueToInsufficientSpace(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetSummaryInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSaveToMemoryCard(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetSectionsToApplyWhenLoading(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadFromMemoryCard(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadedCustomSkater(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetMemCardDataForUpload(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptClearMemCardDataForUpload(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptNeedToLoadReplayBuffer(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadReplayData(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetMemCardDirectoryListing(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetMaxTHPS4FilesAllowed(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSectorSizeOK(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCardIsDamaged(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCardIsForeign(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptBadDevice(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptGetSaveInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCreateTemporaryMemCardPools(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRemoveTemporaryMemCardPools(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSwitchToTempPoolsIfTheyExist(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSwitchToRegularPools(Script::CStruct *pParams, Script::CScript *pScript);
+
+
+bool SaveDataFile(const char *p_name, uint8 *p_data, uint32 size);
+bool LoadDataFile(const char *p_name, uint8 *p_data, uint32 size);
+
+void SetVaultData(uint8 *p_data, uint32 type);
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace CFuncs
+
+#endif	// __SCRIPTING_MCFUNCS_H
+
diff --git a/Code/Sk/Scripting/nodearray.cpp b/Code/Sk/Scripting/nodearray.cpp
new file mode 100644
index 0000000..dc2a842
--- /dev/null
+++ b/Code/Sk/Scripting/nodearray.cpp
@@ -0,0 +1,1298 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// nodearray.cpp		KSH 7 Nov 2001
+//
+// Functions related to the NodeArray, such as the node name hash table,
+// prefix info for rapidly finding all the nodes with a given prefix,
+// and utility functions for getting the links and stuff.
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include  // For Crc::GenerateCRCFromString
+#include 
+#include 
+
+namespace SkateScript
+{
+using namespace Script;
+
+////////////////////////////////////////////////////////////////////
+//
+// Node name hash table stuff, for speeding up FindNamedNode
+//
+////////////////////////////////////////////////////////////////////
+
+#if	1//def __PLAT_NGC__
+
+// Gets called from SkateScript::Init in sk\scripting\init.cpp
+void InitNodeNameHashTable()
+{
+}
+
+// Resets the hash table, and deletes any extra entries created.
+// This currently gets called from the rwviewer cleanup function.
+void ClearNodeNameHashTable()
+{
+}
+
+// Given that a new NodeArray symbol has got loaded due to the loading of a qb containing it,
+// this will generate the node name hash table.
+
+// More efficient hash table.
+#define HASH_SIZE 16384
+#define HASH_MASK (HASH_SIZE-1)
+
+short node_hash[HASH_SIZE];
+
+void CreateNodeNameHashTable()
+{
+	for ( int lp = 0; lp < HASH_SIZE; lp++ )
+	{
+		node_hash[lp] = -1;
+	}
+	// Get the NodeArray
+	CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/);
+	Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray"));
+
+	// Scan through each node, getting it's name, and if it has a name add it in to the hash table.	
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		CStruct *p_node=p_node_array->GetStructure(i);
+		
+		uint32 name_checksum=0;
+		if (p_node->GetChecksum("Name",&name_checksum))
+		{
+			uint16 hash = (uint16)(name_checksum & HASH_MASK );
+
+			while ( node_hash[hash] != -1 )
+			{
+				hash++;
+				hash &= HASH_MASK;
+			}
+			node_hash[hash] = i;
+		}
+	}
+}
+	
+// Searches the node array for a node whose Name is the passed checksum.
+int FindNamedNode(uint32 checksum, bool assert)
+{
+	// Get the NodeArray
+	CArray *p_node_array=GetArray(CRCD(0xc472ecc5,"NodeArray"));
+	Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray"));
+
+	// Added 9/18/03 for THUG submission. If the node array doesn't exist, interpret that
+	// as just another condition of the node not existing
+	if( p_node_array == NULL )
+	{
+		return -1;
+	}
+
+	uint16 hash = (uint16)(checksum & HASH_MASK );
+
+	while ( node_hash[hash] != -1 )
+	{
+		CStruct *p_node=p_node_array->GetStructure(node_hash[hash]);
+		if ( assert ) Dbg_MsgAssert(p_node,("NULL p_node"));
+		
+		uint32 name_checksum=0;
+		if (p_node->GetChecksum(CRCD(0xa1dc81f9,"Name"),&name_checksum))
+		{
+			if ( name_checksum == checksum )
+			{
+				return node_hash[hash];
+			}
+		}
+
+		hash++;
+		hash &= HASH_MASK;
+	}
+
+//	// Scan through each node, getting it's name, and if it has a name add it in to the hash table.	
+//	for (uint32 i=0; iGetSize(); ++i)
+//	{
+//		CStruct *p_node=p_node_array->GetStructure(i);
+//		if ( assert ) Dbg_MsgAssert(p_node,("NULL p_node"));
+//		
+//		uint32 name_checksum=0;
+//		if (p_node->GetChecksum("Name",&name_checksum))
+//		{
+//			if ( name_checksum == checksum )
+//			{
+//				return i;
+//			}
+//		}
+//	}
+	return -1;
+}
+
+int FindNamedNode(const char *p_name)
+{
+	return FindNamedNode(Crc::GenerateCRCFromString(p_name));
+}
+
+// Added for use by the NodeExists script function
+bool NodeExists(uint32 checksum)
+{
+	return ( FindNamedNode( checksum, true ) == -1 ) ? false : true;
+
+//	// Get the NodeArray
+//	CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/);
+//	Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray"));
+//
+//	// Scan through each node, getting it's name, and if it has a name add it in to the hash table.	
+//	for (uint32 i=0; iGetSize(); ++i)
+//	{
+//		CStruct *p_node=p_node_array->GetStructure(i);
+//		Dbg_MsgAssert(p_node,("NULL p_node"));
+//		
+//		uint32 name_checksum=0;
+//		if (p_node->GetChecksum("Name",&name_checksum))
+//		{
+//			if ( name_checksum == checksum )
+//			{
+//				return true;
+//			}
+//		}
+//	}
+//	return false;
+}
+
+#else
+
+#define	__OLD_HASH_TABLE__  0
+#define NODE_NAME_HASHBITS 13
+
+#if __OLD_HASH_TABLE__
+#define NODE_NAME_HASH_SIZE ((1<mpNext;
+				Mem::Free(p_entry);
+				p_entry=p_next;
+			}
+			sp_node_name_hash_table[i].mpNext=NULL;	
+		#endif					
+		sp_node_name_hash_table[i].mNameChecksum=0;
+		sp_node_name_hash_table[i].mNodeIndex=0;
+	}	
+}
+
+// Given that a new NodeArray symbol has got loaded due to the loading of a qb containing it,
+// this will generate the node name hash table.
+void CreateNodeNameHashTable()
+{
+	Dbg_MsgAssert(s_node_name_hash_table_initialised,("Node name hash table not initialised"));
+	
+	// Make sure the old one is cleared.
+	ClearNodeNameHashTable();
+
+	// Get the NodeArray
+	CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/);
+	Dbg_MsgAssert(p_node_array,("CreateNodeNameHashTable could not find NodeArray"));
+
+	// Make sure we're using the script heap, cos the hash table is going to hang around
+	// for the duration of the level.
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ScriptHeap());
+
+	// Scan through each node, getting it's name, and if it has a name add it in to the hash table.	
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		CStruct *p_node=p_node_array->GetStructure(i);
+		Dbg_MsgAssert(p_node,("NULL p_node"));
+		
+		uint32 name_checksum=0;
+		if (p_node->GetChecksum("Name",&name_checksum))
+		{
+			// Node i has name name_checksum, so add it in to the hash table.
+			
+			// A checksum of zero is used to indicate an empty hash table entry,
+			// so check that the name checksum is not zero by chance.
+			Dbg_MsgAssert(name_checksum,("Node has zero name checksum ?"));
+			
+			SNodeNameHashEntry *p_entry=&sp_node_name_hash_table[name_checksum&((1<mNameChecksum)
+			{
+				// p_entry is already occupied, so skip to the end of the list that starts at p_entry.
+				while (p_entry->mpNext)
+				{
+					p_entry=p_entry->mpNext;
+				}
+				
+				// p_entry is now the last entry in the list.
+				
+				// Create a new entry and fill it in.
+				SNodeNameHashEntry *p_new=(SNodeNameHashEntry*)Mem::Malloc(sizeof(SNodeNameHashEntry));
+				p_new->mNameChecksum=name_checksum;
+				p_new->mNodeIndex=i;
+				p_new->mpNext=NULL;
+				
+				// Tag it onto the end of the list.
+				p_entry->mpNext=p_new;
+				
+				
+			}
+			else
+			{
+				// p_entry is free, so stick the info in there.
+				p_entry->mNameChecksum=name_checksum;
+				p_entry->mNodeIndex=i;
+				// Quick check for leaks.
+				Dbg_MsgAssert(p_entry->mpNext==NULL,("p_entry->mpNext not NULL ??"));
+				p_entry->mpNext=NULL;
+			}
+			#else
+			// just skip forward until we find an empty entry
+			while (p_entry->mNameChecksum)
+			{
+				p_entry++;
+				// note in the following assertion we leave one entry to guarentee we have
+				// a zero entry roadblock to stop searches
+				// otherwise we have a slight possibility of very obscure bugs
+				Dbg_MsgAssert(p_entry < &sp_node_name_hash_table[NODE_NAME_HASH_SIZE-1],("Hash table overflow"));
+			}
+			p_entry->mNameChecksum=name_checksum;
+			p_entry->mNodeIndex=i;
+			#endif
+	
+		}
+	}
+	Mem::Manager::sHandle().PopContext();
+}
+	
+// Searches the node array for a node whose Name is the passed checksum.
+int FindNamedNode(uint32 checksum, bool assert)
+{
+	// TODO: Fix bug where this returns 0 when a checksum of 0 is passed. It should
+	// not find a node in that case.
+	
+	Dbg_MsgAssert(s_node_name_hash_table_initialised,("Node name hash table not initialised"));
+
+	SNodeNameHashEntry *p_entry=&sp_node_name_hash_table[checksum&((1<mNameChecksum!=checksum)
+	{
+		p_entry=p_entry->mpNext;
+	}
+	
+	if (assert)
+	{
+		Dbg_MsgAssert(p_entry,("No node with name %s found.",FindChecksumName(checksum)));
+	}
+	if (!p_entry)
+	{
+		return -1;
+	}
+	#else
+	while (p_entry->mNameChecksum && p_entry->mNameChecksum!=checksum)
+	{
+		p_entry++;
+	}
+	
+	if (assert)
+	{
+		Dbg_MsgAssert(p_entry->mNameChecksum,("No node with name %s found.",FindChecksumName(checksum)));
+	}
+	
+	if (!p_entry->mNameChecksum)
+	{
+		return -1;
+	}
+	#endif		
+	
+	return p_entry->mNodeIndex;
+}
+
+int FindNamedNode(const char *p_name)
+{
+	return FindNamedNode(Crc::GenerateCRCFromString(p_name));
+}
+
+// Added for use by the NodeExists script function
+bool NodeExists(uint32 checksum)
+{
+	Dbg_MsgAssert(s_node_name_hash_table_initialised,("Node name hash table not initialised"));
+
+	SNodeNameHashEntry *p_entry=&sp_node_name_hash_table[checksum&((1<mNameChecksum!=checksum)
+	{
+		p_entry=p_entry->mpNext;
+	}
+	if (p_entry)
+	{
+		return true;
+	}
+	#else
+	while (p_entry->mNameChecksum && p_entry->mNameChecksum!=checksum)
+	{
+		p_entry=p_entry++;
+	}
+	if (p_entry->mNameChecksum)
+	{
+		return true;
+	}
+	#endif
+	
+	return false;
+}
+
+#endif		// __PLAT_NGC__
+
+////////////////////////////////////////////////////////////////////
+//
+// Node name prefix stuff.
+//
+////////////////////////////////////////////////////////////////////
+
+// Linked list stuff, used when generating the node lists.
+// These only exist temporarily in memory.
+struct STempNode
+{
+	int mNodeIndex;
+	STempNode *mpNext;
+};
+
+// MEMOPT 800K TEMP Size of temporary buffer used when generating prefix info.
+#define NUM_TEMP_NODES 120000
+static STempNode *sp_temp_nodes=NULL;
+static STempNode *sp_free_temp_nodes=NULL;
+
+// The lookup table, which exists in memory for the duration of the level.
+struct SPrefixLookup
+{
+	// Checksum of the prefix string
+	uint32 mChecksum;
+	
+	// Pointer to the list of node indices of the nodes that have this prefix.
+	union
+	{
+		// Points to somewhere inside sp_node_list_buffer
+		// pNodeList[0] contains the number of nodes.
+		uint16 *mpNodeList;
+		
+		// Points to a temporary linked list of nodes, which is used for speed when generating the prefix info.
+		// (Uses up too much memory to keep in memory all the time)
+		STempNode *mpTempNodesHeadPointer;
+	};	
+};
+
+// MEMOPT 80K PERM An array of SPrefixLookup's, one for each possible prefix.
+// This array is kept in order of checksum, smallest checksum first, to enable quick searching
+// using a binary search.
+#define MAX_PREFIX_LOOKUPS 13000
+static SPrefixLookup sp_prefix_lookups[MAX_PREFIX_LOOKUPS];
+static uint32 s_num_prefix_lookups=0;
+
+// MEMOPT 200K PERM Big array of uint16's for holding the node lists.
+#define NODE_LIST_BUFFER_SIZE 121000
+static uint32 s_node_list_buffer_used=0;
+static uint16 sp_node_list_buffer[NODE_LIST_BUFFER_SIZE];
+
+// Does a binary search of the lookup table, and returns the index of the entry 
+// that has the passed checksum.
+// If there is no matching entry, it will return the index of the entry with the next smallest
+// checksum.
+// If there is no matching entry, and the passed checksum is smaller than the smallest checksum in the
+// table, then it will return 0.
+static uint32 sFindPrefixLookup(uint32 checksum)
+{
+	Dbg_MsgAssert(s_num_prefix_lookups,("Zero s_num_prefix_lookups"));
+	uint32 bottom=0;
+	uint32 top=s_num_prefix_lookups-1;
+	uint32 middle=(bottom+top)>>1;
+	
+	while (bottom!=middle)
+	{
+		uint32 ch=sp_prefix_lookups[middle].mChecksum;
+		
+		if (ch==checksum)
+		{
+			return middle;
+		}
+		else if (checksum>1;		
+	}		
+	if (sp_prefix_lookups[top].mChecksum > checksum)
+	{
+		return bottom;
+	}	
+	return top;
+}
+
+// Creates the big temporary array of nodes.
+static void sCreateTempNodes()
+{
+	Dbg_MsgAssert(sp_temp_nodes==NULL,("sp_temp_nodes not NULL ?"));
+	sp_temp_nodes=(STempNode*)Mem::Malloc(NUM_TEMP_NODES*sizeof(STempNode));
+	Dbg_MsgAssert(sp_temp_nodes,("Could not allocate sp_temp_nodes"));
+	
+	// Link them all into a free list.
+	for (int i=0; impNext;
+	return p_new;
+}
+
+// Converts the linked lists of nodes into simple arrays, kept in the sp_node_list_buffer static array.
+// Uses up less memory. Also means the rest of the game code that calls GetPrefixInfo does not have
+// to be changed.
+static void sConvertTempNodes()
+{
+	Dbg_MsgAssert(s_node_list_buffer_used==0,("Expected s_node_list_buffer_used to be 0"));
+	uint16 *p_buf=sp_node_list_buffer;
+	
+	// For each prefix in the lookup table ...
+	for (uint32 i=0; impNext;
+		}
+		
+		// Check there is enough space to copy the node list into.
+		Dbg_MsgAssert(s_node_list_buffer_used+1+count<=NODE_LIST_BUFFER_SIZE,("Node list buffer overflow"));
+
+		// Make the prefix entry now point to the simple array.
+		sp_prefix_lookups[i].mpNodeList=p_buf;
+		
+		// First entry is the count.		
+		*p_buf++=count;
+		// Copy in all the node indices.
+		p_node=p_first;
+		while (p_node)
+		{
+			*p_buf++=p_node->mNodeIndex;
+			p_node=p_node->mpNext;
+		}
+		
+		// Update the space used. The +1 is for the count.
+		s_node_list_buffer_used+=1+count;
+	}	
+//	Dbg_MsgAssert(0,("buffer = %d\n",s_node_list_buffer_used));	
+}
+
+// Adds a new node index to a particular prefix checksum's entry in the lookup table.
+static void sAddNewPrefix(uint32 checksum, int nodeIndex)
+{
+	// sFindPrefixLookup will assert if s_num_prefix_lookups is zero, so do this
+	// as a special case for the very first one added.
+	if (s_num_prefix_lookups==0)
+	{
+		STempNode *p_new=sNewTempNode();
+		p_new->mNodeIndex=nodeIndex;
+		p_new->mpNext=NULL;
+		
+		// Stick it in at index 0. The array will be maintained as a sorted
+		// array from now on.
+		sp_prefix_lookups[0].mChecksum=checksum;
+		sp_prefix_lookups[0].mpTempNodesHeadPointer=p_new;
+		
+		++s_num_prefix_lookups;
+		// All done.
+		return;
+	}	
+	
+	// Look up the checksum, which may not be there.
+	int index=sFindPrefixLookup(checksum);
+	uint32 ch=sp_prefix_lookups[index].mChecksum;
+	// ch may or may not be checksum. If it isn't, it will either be the next smallest one in the
+	// array, or it will be the checksum at index 0 in the case of checksum being smaller than all of them.
+	
+	if (ch==checksum)
+	{
+		// This prefix is already in the array, so just have to add the new node index to
+		// its list.
+		
+		STempNode *p_new=sNewTempNode();
+		p_new->mNodeIndex=nodeIndex;
+		p_new->mpNext=sp_prefix_lookups[index].mpTempNodesHeadPointer;
+		
+		sp_prefix_lookups[index].mpTempNodesHeadPointer=p_new;
+	}
+	else
+	{
+		// The prefix is not in the table, so we have to add it.
+		
+		Dbg_MsgAssert(s_num_prefix_lookupsch)
+		{
+			// The above if will mostly be true, because usually Ch will be the next smaller checksum
+			// in the table. Hence we increment index so that all the checksums above ch get shifted up,
+			// so that the new checksum can be inserted at index, maintaining the sort order.
+			++index;
+		}
+		else
+		{
+			// The above if will be false in the case of checksum being smaller than all the current checksums
+			// in the table. In this case, all the checksums need to be shifted up and checksum inserted at the
+			// bottom, so index should be zero here.
+			Dbg_MsgAssert(index==0,("index not zero ?"));
+		}		
+
+		// Shift everything up one so that the new checksum can be inserted.
+		for (int i=s_num_prefix_lookups; i>index; --i)
+		{
+			sp_prefix_lookups[i]=sp_prefix_lookups[i-1];
+		}
+		
+		// Insert the new checksum, and make a new list for it with just one entry at the moment.
+		STempNode *p_new=sNewTempNode();
+		p_new->mNodeIndex=nodeIndex;
+		p_new->mpNext=NULL;
+		
+		sp_prefix_lookups[index].mChecksum=checksum;
+		sp_prefix_lookups[index].mpTempNodesHeadPointer=p_new;
+		
+		// Increment the count.
+		++s_num_prefix_lookups;
+	}
+}
+
+void DeletePrefixInfo()
+{
+	// Just to be sure.
+	sDeleteTempNodes();
+	
+	s_node_list_buffer_used=0;
+	s_num_prefix_lookups=0;
+}	
+
+// This is called from LoadQB if the QB contains a NodeArray.	
+void GeneratePrefixInfo()
+{
+	DeletePrefixInfo();
+	
+	// Create the temporary array of nodes used to make linked lists in sAddNewPrefix, for speed.
+	sCreateTempNodes();
+
+	// Scan through all the nodes, look up their names, and add all their possible
+	// prefixes to the lookup table.
+	CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/);
+	Dbg_MsgAssert(p_node_array,("NodeArray not found"));
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		CStruct *p_node=p_node_array->GetStructure(i);
+		Dbg_MsgAssert(p_node,("NULL p_node"));
+		uint32 name_checksum=0;
+		p_node->GetChecksum("Name",&name_checksum);
+		
+		if (name_checksum)
+		{
+			// Search the hash table for the name.
+			const char *p_name=GetChecksumNameFromLastQB(name_checksum);
+			if (p_name)
+			{
+				// Add all the possible prefixes to the lookup table.
+				int string_length=strlen(p_name);
+				for (int j=1; j<=string_length; ++j)
+				{
+					sAddNewPrefix(Crc::GenerateCRC(p_name,j),i);
+				}		
+			}	
+		}
+	}
+
+	// Move the info in the linked lists into the more memory efficient static array.
+	sConvertTempNodes();
+	// Remove the temporary stuff.
+	sDeleteTempNodes();
+
+	#ifdef __NOPT_ASSERT__
+	// Quick check to make sure the lookup table seems right.
+	if (s_num_prefix_lookups)
+	{
+		for (uint32 i=0; iGetStructure(*p_nodes);
+		Mth::Vector node_pos;
+		p_node->GetVector(CRCD(0x7f261953,"pos"),&node_pos);
+		float	node_dist = (node_pos - pos).LengthSqr();
+		//printf ("Node %d at (%.02f, %.02f, %.02f)  dist %.02f\n",*p_nodes,node_pos[X],node_pos[Y],node_pos[Z],node_dist);
+		if (node_dist < closest_dist)
+		{
+		  //  printf ("closest node %d\n",*p_nodes);
+			closest_dist = node_dist;
+			closest_node = *p_nodes;
+		}
+	
+		p_nodes++;
+		num_nodes--;
+	}
+	
+	return closest_node;				   
+				   
+}
+
+
+// return the index of the node nearest to this position that
+// matches this prefix
+int	GetNearestNodeByPrefix(const char *p_prefix, const Mth::Vector &pos)
+{
+	return GetNearestNodeByPrefix(Crc::GenerateCRCFromString(p_prefix),pos);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//
+// Utility functions for getting nodes, links, etc
+//
+////////////////////////////////////////////////////////////////////
+
+CStruct *GetNode(int nodeIndex)
+{
+	CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/);
+	Dbg_MsgAssert(p_node_array,("NodeArray not found"));
+	CStruct *p_node=p_node_array->GetStructure(nodeIndex);
+	Dbg_MsgAssert(p_node,("NULL p_node"));
+	return p_node;
+}
+
+
+// This is a DEPRECATED function
+// only in to support the rather odd manner of access in nodes via index
+uint32	GetNodeNameChecksum(int nodeIndex)
+{
+	CStruct *p_struct = GetNode(nodeIndex);
+	uint32 name = 0;
+	p_struct->GetChecksum(CRCD(0xa1dc81f9,"name"),&name);
+	return name;
+}
+
+uint32 GetNumLinks(CStruct *p_node)
+{
+	Dbg_MsgAssert(p_node,("NULL p_node"));
+	CArray *p_links=NULL;
+	p_node->GetArray(0x2e7d5ee7/*Links*/,&p_links);
+	if (p_links==NULL)
+	{
+		return 0;
+	}
+	return p_links->GetSize();
+}
+
+uint32 GetNumLinks(int nodeIndex)
+{
+	CArray *p_node_array=GetArray(0xc472ecc5/*NodeArray*/, NO_ASSERT);
+	if (p_node_array)
+	{
+		CStruct *p_node=p_node_array->GetStructure(nodeIndex);
+		Dbg_MsgAssert(p_node,("NULL p_node"));
+	
+		return GetNumLinks(p_node);
+	}	
+	return 0;
+}
+
+int GetLink(CStruct *p_node, int linkNumber)
+{
+	Dbg_MsgAssert( p_node, ( "NULL p_node" ) );
+	CArray *p_links=NULL;
+	p_node->GetArray( CRCD( 0x2e7d5ee7, "Links" ), &p_links);
+	Dbg_MsgAssert( p_links, ( "Tried to call GetLink when there are no links" ) );
+	Dbg_MsgAssert( p_links->GetSize(), ( "Tried to call GetLink when there are no links (empty Links array)" ) );
+	return p_links->GetInteger( linkNumber );
+}
+
+int GetLink(int nodeIndex, int linkNumber)
+{
+	return GetLink(GetNode(nodeIndex),linkNumber);
+}
+
+// return true if Node1 is linked to Node2
+bool IsLinkedTo(int node1, int node2)
+{
+	int n = GetNumLinks(node1);
+	for (int i = 0; iGetVector(CRCD(0xb9d31b0a,"Position"),p_vector,Script::NO_ASSERT) )
+	{
+		uint32 name = 0;
+		p_node->GetChecksum(CRCD(0xa1dc81f9,"name"),&name);
+		if (name)
+		{
+			Dbg_Message( "Warning:  'Position' deprecated! reexport, node %s",Script::FindChecksumName(name) );
+		}
+		else
+		{
+			Dbg_Message( "Warning:  'Position' deprecated! reexport" );
+		}
+	}
+	#endif
+
+	if ( p_node->GetVector(CRCD(0x7f261953,"pos"),p_vector,Script::NO_ASSERT) )
+	{
+		// no need to negate the new style vectors
+	}
+	else if ( p_node->GetVector(CRCD(0xb9d31b0a,"Position"),p_vector,Script::NO_ASSERT) )
+	{
+		(*p_vector)[Z]=-(*p_vector)[Z];
+	}
+	else
+	{
+		*p_vector = Mth::Vector(0.0f,0.0f,0.0f,1.0f);
+//		Script::PrintContents( p_node );
+//		Dbg_MsgAssert( 0, ( "No 'pos' vector found in node" ) );
+	}
+}
+
+void GetPosition(int nodeIndex, Mth::Vector *p_vector)
+{
+	Dbg_MsgAssert(p_vector,("NULL p_vector"));
+	GetPosition(GetNode(nodeIndex),p_vector);
+}
+
+void GetOrientation(CStruct *p_node, Mth::Matrix *p_matrix)
+{
+	Dbg_MsgAssert(p_node,("NULL p_node"));
+	Dbg_MsgAssert(p_matrix,("NULL p_matrix"));
+	   
+	Mth::Vector orientation_vector;
+	if ( p_node->GetVector(CRCD(0xc97f3aa9, "orientation"),&orientation_vector,Script::NO_ASSERT) )
+	{
+		// assumes a positive scalar component
+		Mth::Quat orientation_quat;
+		orientation_quat.SetVector(orientation_vector);
+		orientation_quat.SetScalar(sqrtf(1.0f - orientation_vector.LengthSqr()));
+		
+		orientation_quat.GetMatrix(*p_matrix);
+	}
+	else
+	{
+		p_matrix->Identity();
+	}
+}
+
+void GetOrientation(int nodeIndex, Mth::Vector *p_matrix)
+{
+	Dbg_MsgAssert(p_matrix,("NULL p_matrix"));
+	GetPosition(GetNode(nodeIndex),p_matrix);
+}
+
+void GetAngles(CStruct *p_node, Mth::Vector *p_vector)
+{
+	Dbg_MsgAssert(p_node,("NULL p_node"));
+	Dbg_MsgAssert(p_vector,("NULL p_vector"));
+	// Make sure they are initialised to zero, in case there is no Angles component.
+	// Angles components of (0,0,0) are ommitted to save memory.
+	p_vector->Set(); 
+	p_node->GetVector(0x9d2d0915/*Angles*/,p_vector);
+}
+
+void GetAngles(int nodeIndex, Mth::Vector *p_vector)
+{
+	Dbg_MsgAssert(p_vector,("NULL p_vector"));
+	GetAngles(GetNode(nodeIndex),p_vector);
+}
+
+CArray *GetIgnoredLightArray(CStruct *p_node)
+{
+	Dbg_MsgAssert(p_node,("NULL p_node"));
+	CArray *p_ignored_lights=NULL;
+	p_node->GetArray(0xb7b030be/*IgnoredLights*/,&p_ignored_lights);
+	return p_ignored_lights;
+}
+
+CArray *GetIgnoredLightArray(int nodeIndex)
+{
+	return GetIgnoredLightArray(GetNode(nodeIndex));
+}
+
+// Used by Ryan in the Park Editor.
+// This creates an array (called NodeArray) of Size structures, with all the entries initialised to empty structures.
+void CreateNodeArray(int size, bool hackUseFrontEndHeap)
+{
+	// copied from Script::Cleanup()
+	//KillStoppedScripts();
+	//RemoveOldTriggerScripts();
+	//RemoveSymbol(GenerateCRC("TriggerScripts"));
+	CleanUpAndRemoveSymbol(CRCD(0xc472ecc5,"NodeArray"));
+	DeletePrefixInfo();
+	ClearNodeNameHashTable();
+	
+	// Make sure it doesn't exist already.
+	// ParseQB will catch this anyway, but may as well check before getting there.
+	Dbg_MsgAssert(GetArray(CRCD(0xc472ecc5,"NodeArray"))==NULL,("Called CreateNodeArray when a NodeArray already exists"));
+
+
+	// Create a dummy QB file, and parse it the usual way.	
+	if (hackUseFrontEndHeap)
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().FrontEndHeap()); // Use the temporary heap.
+	else
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap()); // Use the temporary heap.
+	uint8 *p_dummy_qb=(uint8*)Mem::Malloc((1			// ESCRIPTTOKEN_NAME
+										 +4			// Checksum of 'NodeArray'
+										 +1			// ESCRIPTTOKEN_EQUALS
+										 +1			// ESCRIPTTOKEN_STARTARRAY
+										 +2*size	// ESCRIPTTOKEN_STARTSTRUCT,ESCRIPTTOKEN_ENDSTRUCT pairs
+										 +1			// ESCRIPTTOKEN_ENDARRAY
+										 +1			// ESCRIPTTOKEN_ENDOFFILE
+										 )*sizeof(uint8));
+	uint8 *p_qb=p_dummy_qb;
+	
+	*p_qb++=ESCRIPTTOKEN_NAME;
+	*p_qb++=0xc5; // Checksum of 'NodeArray'
+	*p_qb++=0xec;
+	*p_qb++=0x72;
+	*p_qb++=0xc4;
+	*p_qb++=ESCRIPTTOKEN_EQUALS;
+	*p_qb++=ESCRIPTTOKEN_STARTARRAY;
+	for (int i=0; imGotReloaded=false;
+
+	Obj::InsertGoalEditorNodes();
+	
+	Mem::Free(p_dummy_qb);
+	Mem::Manager::sHandle().PopContext();
+}
+
+////////////////////////////////////////////////////////////////////
+//
+// Given a script, and the checksum of a function to look for,
+// this will search through the script for any calls to that function.
+// It will recursively search through any script calls.
+// The callback function is called for each occurrence of the function
+// call found, and the parameter list for that call is also passed.
+//
+// NOTE: Don't store the passed CStruct pointer! 
+// It's a local variable in FindReferences.
+// To store the parameters create a new CStruct and use the
+// AppendStructure member function to copy in the contents of the
+// passed one.
+//
+////////////////////////////////////////////////////////////////////
+
+// Note: p_args contains the set of parameters that were passed to scriptToScanThrough. It is passed so that
+// the <,> syntax can be evaluated. This is necessary because sometimes the EndGap command is not directly
+// passed the text and score, they might get passed via the <...> syntax, for example see the
+// Gap_Gen_End script in sk4_scripts.q
+static void FindReferences(uint32 scriptToScanThrough, uint32 functionToScanFor, void (*p_callback)(Script::CStruct *, const uint8 *), Script::CStruct *p_args, int Count)
+{
+	// Don't recurse too deeply otherwise some levels take ages finding the gaps (eg, the secret level)
+	if (Count > 5)
+	{
+		return;
+	}
+		
+	Dbg_MsgAssert(p_callback,("NULL p_callback"));
+
+	#ifdef __NOPT_ASSERT__
+	// Look up the function being scanned for to see if it is a cfunction.
+    bool (*p_cfunction)(CStruct *pParams, CScript *pCScript)=NULL;
+	CSymbolTableEntry *p_function_entry=LookUpSymbol(functionToScanFor);
+	if (p_function_entry && p_function_entry->mType==ESYMBOLTYPE_CFUNCTION)
+	{
+		p_cfunction=p_function_entry->mpCFunction;
+	}	
+	Dbg_MsgAssert(p_cfunction==NULL,("Cannot use FindReferences to find cfunction calls yet ..."));
+	#endif
+
+	#ifdef NO_SCRIPT_CACHING
+	// Look up the script
+	CSymbolTableEntry *p_script_entry=LookUpSymbol(scriptToScanThrough);
+	if (!p_script_entry)
+	{
+		Dbg_Warning("Script '%s' not found in call to FindReferences",FindChecksumName(scriptToScanThrough));
+		return;
+	}
+	Dbg_MsgAssert(p_script_entry->mType==ESYMBOLTYPE_QSCRIPT,("'%s' is not a qscript",FindChecksumName(scriptToScanThrough)));
+	
+	// Get a pointer to it.
+	uint8 *p_token=p_script_entry->mpScript;
+	Dbg_MsgAssert(p_token,("NULL p_token ???"));
+	// Skip over the 4-byte contents checksum.
+	p_token+=4;
+	#else
+	
+	Script::CScriptCache *p_script_cache=Script::CScriptCache::Instance();
+	Dbg_MsgAssert(p_script_cache,("NULL p_script_cache"));
+	uint8 *p_script=p_script_cache->GetScript(scriptToScanThrough);
+	Dbg_MsgAssert(p_script,("Script %s not found in script cache",Script::FindChecksumName(scriptToScanThrough)));
+	uint8 *p_token=p_script;
+	
+	#endif
+	
+	
+	// Create a structure for holding parameters.
+	CStruct *p_params=new CStruct;
+
+	// Skip to the first line of the script.	
+	p_token=SkipToStartOfNextLine(p_token);
+
+	
+	// Now scan through each token of the script in turn, looking for any calls to functionToScanFor.
+	
+	bool finished=false;
+    while (!finished)
+    {
+        switch (*p_token)
+        {
+		case ESCRIPTTOKEN_RUNTIME_CFUNCTION:
+		{
+			// Skip over CFunction calls for now.
+			p_token+=5;
+			p_token=SkipToStartOfNextLine(p_token);
+			break;
+		}
+		
+        case ESCRIPTTOKEN_NAME:
+		case ESCRIPTTOKEN_RUNTIME_MEMBERFUNCTION:
+        {
+			// Remember the location for passing to the callback.
+			const uint8 *p_location=p_token;
+			
+            ++p_token;
+            uint32 name_checksum=Read4Bytes(p_token).mChecksum;
+            p_token+=4;
+
+			// Skip over lines of script that are setting parameters
+			if (*p_token==ESCRIPTTOKEN_EQUALS)
+			{
+				break;
+			}		
+			
+            CSymbolTableEntry *p_entry=Resolve(name_checksum);
+			if (p_entry)
+			{
+				switch (p_entry->mType)
+				{
+				case ESYMBOLTYPE_CFUNCTION:
+					Dbg_MsgAssert(p_entry->mpCFunction,("\nLine %d of %s\nNULL pCFunction",GetLineNumber(p_token),FindChecksumName(p_entry->mSourceFileNameChecksum)));
+				
+					if (name_checksum==functionToScanFor)
+					{
+						// Found the required function call! Get the params and call the callback.
+						p_params->Clear();
+						// Note: Uses p_args to look up any params enclosed in <,> that are encountered.
+						p_token=AddComponentsUntilEndOfLine(p_params,p_token,p_args);
+						
+						//printf("Callback: %s\n",FindChecksumName(scriptToScanThrough));
+						(*p_callback)(p_params,p_location);
+					}
+					else
+					{
+						p_token=SkipToStartOfNextLine(p_token);
+					}
+					break;
+					
+				case ESYMBOLTYPE_MEMBERFUNCTION:
+					if (name_checksum==functionToScanFor)
+					{
+						// Found the required function call! Get the params and call the callback.
+						p_params->Clear();
+						// Note: Uses p_args to look up any params enclosed in <,> that are encountered.
+						p_token=AddComponentsUntilEndOfLine(p_params,p_token,p_args);
+						
+						//printf("Callback: %s\n",FindChecksumName(scriptToScanThrough));
+						(*p_callback)(p_params,p_location);
+						
+						if (name_checksum==0xe5399fb2) // EndGap
+						{
+							uint32 GapScript=0;
+							if (p_params->GetChecksum("GapScript",&GapScript))
+							{
+								// Passing NULL for p_args here because it is not clear what parameters are
+								// going to be passed to the GapScript when it is run.
+								FindReferences(GapScript,functionToScanFor,p_callback,NULL,Count+1);
+							}	
+						}	
+					}
+					else
+					{
+						p_token=SkipToStartOfNextLine(p_token);
+					}
+					break;
+					
+				case ESYMBOLTYPE_QSCRIPT:
+					p_params->Clear();
+					// Note: Uses p_args to look up any params enclosed in <,> that are encountered.
+					p_token=AddComponentsUntilEndOfLine(p_params,p_token,p_args);
+					
+					if (name_checksum==functionToScanFor)
+					{
+						// Found the required function call! Get the params and call the callback.
+						//printf("Callback: %s\n",FindChecksumName(scriptToScanThrough));
+						(*p_callback)(p_params,p_location);
+					}
+					else
+					{
+						p_token=SkipToStartOfNextLine(p_token);
+					}
+					
+					// It's a q-script, so recursively search that too.
+					// Passing the just calculated p_params so that the <,> syntax within the script
+					// can be evaluated.
+					FindReferences(name_checksum,functionToScanFor,p_callback,p_params,Count+1);
+					break;
+					
+				default:
+					break;
+				}    
+			}
+            break;
+        }    
+
+        case ESCRIPTTOKEN_KEYWORD_REPEAT:
+            p_token=SkipToken(p_token);
+			// Scan over any repeat parameters.
+			p_token=SkipToStartOfNextLine(p_token);
+            break;
+
+		case ESCRIPTTOKEN_KEYWORD_IF:
+            p_token=SkipToken(p_token);
+
+			if (*p_token==ESCRIPTTOKEN_KEYWORD_NOT)
+			{
+				p_token=SkipToken(p_token);
+			}
+				
+			// If the if keyword is followed by an open parenthesis, then it must be using an
+			// expression. Often the expression may contain c-function calls, such as:
+			// if ( (GotParam Foo) | (GotParam Blaa) )
+			// This was causing a problem because this loop would skip over the open parenthesis,
+			// get to the GotParam, and then recognizing that GotParam is a cfunction it would
+			// try to skip to the next line, but then that would cause a parenthesis mismatch
+			// assert in SkipToStartOfNextLine because the open parenth had been skipped over.
+			// So to get around that, skip to the next line as soon as the open parenth is detected.
+			if (*p_token==ESCRIPTTOKEN_OPENPARENTH)
+			{
+				p_token=SkipToStartOfNextLine(p_token);
+			}	
+			break;
+			
+        case ESCRIPTTOKEN_KEYWORD_ENDSCRIPT:
+			finished=true;
+            break;
+
+        default:
+            p_token=SkipToken(p_token);
+            break;
+        }
+    }
+
+	#ifndef NO_SCRIPT_CACHING
+	p_script_cache->DecrementScriptUsage(scriptToScanThrough);
+	#endif
+	
+	// Delete the temporary p_params.
+	delete p_params;	
+}
+
+void ScanNodeScripts(uint32 componentName, uint32 functionName, void (*p_callback)(CStruct *, const uint8 *))
+{
+	Script::DisableExpressionEvaluatorErrorChecking();
+	
+	CArray *p_node_array=GetArray(0xc472ecc5); // Checksum of NodeArray
+	Dbg_MsgAssert(p_node_array,("NodeArray not found"));
+	
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		CStruct *p_node=p_node_array->GetStructure(i);
+		Dbg_MsgAssert(p_node,("NULL p_node"));
+		
+		uint32 script_checksum=0;
+		if (p_node->GetChecksum(componentName,&script_checksum))
+		{
+			FindReferences(script_checksum,functionName,p_callback,NULL,0);
+		}	
+	}
+	
+	Script::EnableExpressionEvaluatorErrorChecking();
+}
+
+} // namespace SkateScript
+
diff --git a/Code/Sk/Scripting/nodearray.h b/Code/Sk/Scripting/nodearray.h
new file mode 100644
index 0000000..3f9b5be
--- /dev/null
+++ b/Code/Sk/Scripting/nodearray.h
@@ -0,0 +1,64 @@
+#ifndef	__SK_SCRIPTING_NODEARRAY_H
+#define	__SK_SCRIPTING_NODEARRAY_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Mth
+{
+class Vector;
+class Matrix;
+}
+
+namespace Script
+{
+class CStruct;
+class CArray;
+}	
+
+namespace SkateScript
+{
+using namespace Script;
+
+void InitNodeNameHashTable();
+void ClearNodeNameHashTable();
+void CreateNodeNameHashTable();
+int FindNamedNode(uint32 checksum, bool assert=true);
+int FindNamedNode(const char *p_name);
+bool NodeExists(uint32 checksum);
+
+void DeletePrefixInfo();
+void GeneratePrefixInfo();
+const uint16 *GetPrefixedNodes(const char *p_prefix, uint16 *p_numMatches);
+const uint16 *GetPrefixedNodes(uint32 checksum, uint16 *p_numMatches);
+
+int	GetNearestNodeByPrefix(uint32 prefix, const Mth::Vector &pos);
+int	GetNearestNodeByPrefix(const char * p_prefix, const Mth::Vector &pos);
+
+
+CStruct *GetNode(int nodeIndex);
+uint32	GetNodeNameChecksum(int nodeIndex);
+uint32 GetNumLinks(CStruct *p_node);
+uint32 GetNumLinks(int nodeIndex);
+int GetLink(CStruct *p_node, int linkNumber);
+int GetLink(int nodeIndex, int linkNumber);
+bool IsLinkedTo(int node1, int node2);
+
+void GetPosition(CStruct *p_node, Mth::Vector *p_vector);
+void GetPosition(int nodeIndex, Mth::Vector *p_vector);
+void GetOrientation(CStruct *p_node, Mth::Matrix *p_matrix);
+void GetOrientation(int nodeIndex, Mth::Matrix *p_matrix);
+void GetAngles(CStruct *p_node, Mth::Vector *p_vector);
+void GetAngles(int nodeIndex, Mth::Vector *p_vector);
+
+CArray *GetIgnoredLightArray(CStruct *p_node);
+CArray *GetIgnoredLightArray(int nodeIndex);
+
+void CreateNodeArray(int size, bool hackUseFrontEndHeapYouFucker);
+void ScanNodeScripts(uint32 componentName, uint32 functionName, void (*p_callback)(CStruct *, const uint8 *));
+
+} // namespace SkateScript
+
+#endif // #ifndef	__SK_SCRIPTING_NODEARRAY_H
+
diff --git a/Code/Sk/Scripting/skfuncs.cpp b/Code/Sk/Scripting/skfuncs.cpp
new file mode 100644
index 0000000..496ef1a
--- /dev/null
+++ b/Code/Sk/Scripting/skfuncs.cpp
@@ -0,0 +1,7494 @@
+//****************************************************************************
+//* MODULE:         Sk/Scripting
+//* FILENAME:       SkFuncs.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  6/10/2002
+//****************************************************************************
+
+// start autoduck documentation
+// @DOC skfuncs
+// @module skfuncs | None
+// @subindex Scripting Database
+// @index script | skfuncs
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+							 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include  
+#include 
+#include  
+#include  
+#include 
+#include 
+
+#include 
+#include 
+ 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Front
+{
+	extern void SetScoreTHPS4(char* score_text, int skater_num);
+}
+
+namespace CFuncs
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void s_preload_ped_part( uint32 partChecksum )
+{
+	Script::CArray* pArray = Script::GetArray( partChecksum );
+	Dbg_Assert( pArray );
+
+	for ( uint32 i = 0; i < pArray->GetSize(); i++ )
+	{
+		Script::CStruct* pStruct = pArray->GetStructure( i );
+
+		int allow_random = 0;
+		pStruct->GetInteger( CRCD(0xf1e3cd22,"allow_random"), &allow_random, Script::NO_ASSERT );
+			
+		if ( allow_random )
+		{
+			const char* pMeshName;
+			if ( pStruct->GetText( CRCD(0x1e90c5a9,"mesh"), &pMeshName, false ) )
+			{
+				Nx::CModel* pDummy = Nx::CEngine::sInitModel();
+//				Dbg_Message("Preloading random ped model %s", pMeshName );
+				pDummy->AddGeom( pMeshName, 0, true );
+				Nx::CEngine::sUninitModel( pDummy );
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Score queries:
+
+static int s_get_score_from_params( Script::CStruct *pParams, Script::CScript *pScript )
+{  
+	int score;
+	if ( !pParams->GetInteger( NONAME, &score ) )
+	{
+		Dbg_MsgAssert( 0,( "\n%s\nMust provide an integer for s_get_score_from_params.", pScript->GetScriptInfo( ) ));
+		return ( 0 );
+	}
+	return ( score );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static Mdl::Score* s_get_score_struct( void )
+{
+	// find the skater, get the score landed:
+	Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();						   
+	if ( pSkater )
+	{
+		return ( pSkater->GetScoreObject() );
+	}
+	return ( NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Local function to return a career, given a flag number
+static Obj::CSkaterCareer* s_get_career( int flag, Script::CStruct *pParams )
+{
+// Now this always just returns the global career, just kept for convenience	
+	return Mdl::Skate::Instance()->GetCareer();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CurrentSkaterIsPro | Checks if the current skater is pro
+bool ScriptCurrentSkaterIsPro(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	Obj::CSkaterProfile *pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	return ( pSkaterProfile && pSkaterProfile->IsPro() );
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetGoalsCompleted | Returns goals (competitions) completed
+bool ScriptGetGoalsCompleted(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+	Dbg_MsgAssert(pCareer,("NULL pCareer"));
+	
+	int LevelNumber=0;
+	pParams->GetInteger(NONAME,&LevelNumber);
+	char pBuf[100];
+
+	sprintf(pBuf,"%d/9",pCareer->CountGoalsCompleted(LevelNumber));
+	pScript->GetParams()->AddComponent(CRCD(0xc661fe79,"GoalsCompleted"),ESYMBOLTYPE_STRING,pBuf);
+	pScript->GetParams()->AddComponent(CRCD(0xa43dc969,"BestMedal"),ESYMBOLTYPE_STRING,"---");
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetNextLevelRequirements | Gets number of goals needed for next level
+bool ScriptGetNextLevelRequirements(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+	Dbg_MsgAssert(pCareer,("NULL pCareer"));
+	
+	char pBuf[100];
+	sprintf(pBuf,"---");
+	
+	int TotalGoals=pCareer->CountTotalGoalsCompleted();
+	int TotalMedals=pCareer->CountMedals();
+	
+	Script::CArray *pUnlockRequirementsArray=Script::GetArray(CRCD(0x1d8f1a7e,"UnlockRequirements"));
+	for (uint32 i=0; iGetSize(); ++i)
+	{
+		Script::CStruct *pUnlockRequirements=pUnlockRequirementsArray->GetStructure(i);
+		
+		int NumGoalsRequired=0;
+		int NumMedalsRequired=0;
+		
+		if (pUnlockRequirements->ContainsFlag(CRCD(0x38dbe1d0,"Goals")) || pUnlockRequirements->ContainsFlag(CRCD(0x032314d1,"Goal")))
+		{
+			// Need a certain number of goals.
+			pUnlockRequirements->GetInteger(NONAME,&NumGoalsRequired);
+		}	
+				
+		if (pUnlockRequirements->ContainsFlag(CRCD(0x56907f8b,"Medals")) || pUnlockRequirements->ContainsFlag(CRCD(0x23bba846,"Medal")))
+		{
+			// Need a certain number of medals.
+			pUnlockRequirements->GetInteger(NONAME,&NumMedalsRequired);
+		}
+		
+		#if ENGLISH
+		if (TotalGoalsGetParams()->AddComponent(CRCD(0xaf2305a6,"NextLevelRequirements"),ESYMBOLTYPE_STRING,pBuf);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetCurrentSkaterProfile | 
+// @uparmopt 0 | Skater profile index - must be 0 or 1
+bool ScriptSetCurrentSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	int Profile=0;
+	pParams->GetInteger(NONAME,&Profile);
+	Dbg_MsgAssert(Profile==0 || Profile==1,("\n%s\nBad index sent to SetCurrentSkaterProfile, must be 0 or 1",pScript->GetScriptInfo()));
+	
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	pPlayerProfileManager->SetCurrentProfileIndex(Profile);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CurrentSkaterProfileIs | Checks if current skater profile is 
+// equal to index value passed in
+// @uparmopt 0 | Index value - must be 0 or 1
+bool ScriptCurrentSkaterProfileIs(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	int Profile=0;
+	pParams->GetInteger(NONAME,&Profile);
+	Dbg_MsgAssert(Profile==0 || Profile==1,("\n%s\nBad index sent to CurrentSkaterProfileIs, must be 0 or 1",pScript->GetScriptInfo()));
+	
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	
+	return pPlayerProfileManager->GetCurrentProfileIndex()==Profile;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AddSkaterProfile | Adds skater profile 
+// @parm name | name | The name of the profile
+bool ScriptAddSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	return pPlayerProfileManager->AddNewProfile( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AddTemporaryProfile | Adds temporary skater profile
+// (used for credits, to temporarily hijack the skater profile
+bool ScriptAddTemporaryProfile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 profileName;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &profileName, Script::ASSERT );
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	return pPlayerProfileManager->AddTemporaryProfile( profileName );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RememberTemporaryAppearance | 
+bool ScriptRememberTemporaryAppearance(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 profileName;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &profileName, Script::ASSERT );
+	
+	Gfx::CModelAppearance theAppearance;
+	Gfx::CModelAppearance* pAppearance = NULL;
+	
+	Obj::CPlayerProfileManager* pProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+
+	Script::CStruct* pAppearanceStruct;
+	if ( pParams->GetStructure( CRCD(0xefc8944c,"appearance_structure"), &pAppearanceStruct, Script::NO_ASSERT ) )
+	{
+		theAppearance.Load( pAppearanceStruct );
+		pAppearance = &theAppearance;
+	}
+	else
+	{
+		int playerNum;
+		pParams->GetInteger( CRCD(0x67e6859a,"player"), &playerNum, Script::ASSERT );
+
+		Obj::CSkaterProfile* pProfile = pProfileManager->GetProfile( playerNum );
+		Dbg_Assert( pProfile );
+		pAppearance = pProfile->GetAppearance();
+	}
+	Dbg_Assert( pAppearance );
+
+	Obj::CSkaterProfile* pTempProfile = pProfileManager->GetTemporaryProfile( profileName );
+	Dbg_Assert( pTempProfile );
+	Gfx::CModelAppearance* pTempAppearance = pTempProfile->GetAppearance();
+	Dbg_Assert( pTempAppearance );
+
+	if ( pParams->ContainsFlag( CRCD(0x3af36e6d,"NoFaceTexture") ) )
+	{
+		Script::CStruct* pOriginalStructure = pAppearance->GetStructure();
+		Script::CStruct* pTempStructure = pTempAppearance->GetStructure();
+		pTempStructure->Clear();
+		pTempStructure->AppendStructure( pOriginalStructure );
+	}
+	else
+	{
+		*pTempAppearance = *pAppearance;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RestoreTemporaryAppearance | 
+bool ScriptRestoreTemporaryAppearance(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 profileName;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &profileName, Script::ASSERT );
+	
+	int playerNum;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &playerNum, Script::ASSERT );
+
+	Obj::CPlayerProfileManager* pProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pProfile = pProfileManager->GetProfile( playerNum );
+	Dbg_Assert( pProfile );
+	Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	Dbg_Assert( pAppearance );
+
+	Obj::CSkaterProfile* pTempProfile = pProfileManager->GetTemporaryProfile( profileName );
+	Dbg_Assert( pTempProfile );
+	Gfx::CModelAppearance* pTempAppearance = pTempProfile->GetAppearance();
+	Dbg_Assert( pTempAppearance );
+
+	if ( pParams->ContainsFlag( CRCD(0x3af36e6d,"NoFaceTexture") ) )
+	{
+		Script::CStruct* pOriginalStructure = pAppearance->GetStructure();
+		Script::CStruct* pTempStructure = pTempAppearance->GetStructure();
+		pOriginalStructure->Clear();
+		pOriginalStructure->AppendStructure( pTempStructure );
+	}
+	else
+	{
+		*pAppearance = *pTempAppearance;
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SyncPlayer2Profile | 
+bool ScriptSyncPlayer2Profile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	pPlayerProfileManager->SyncPlayer2();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PreloadModels | 
+bool ScriptPreloadModels( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// Load all the files to get them into the asset manager, 
+	// while the PRE file is still in memory
+
+	Mem::PushMemProfile("PreLoadModels");
+  
+	// now loop through the NodeArray, looking for single-skinned models
+	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray, ( "No NodeArray found in ParseNodeArray" ) );
+
+	for ( uint32 i = 0; i < pNodeArray->GetSize(); i++ )
+	{
+		Script::CStruct* pNode = pNodeArray->GetStructure( i );
+		Dbg_MsgAssert( pNode, ( "NULL pNode" ) );
+
+		// The following nodes are capable of being ignored in net games		
+				
+		if ( Mdl::Skate::Instance()->ShouldBeAbsentNode( pNode ) )
+		{
+			// Don't load, as it's a net game
+		}	
+		else 
+		{
+			uint32 ClassChecksum = 0;
+			pNode->GetChecksum( CRCD(0x12b4e660,"Class"), &ClassChecksum );
+
+			uint32 TypeChecksum = 0;
+			pNode->GetChecksum(CRCD(0x7321a8d6,"Type"), &TypeChecksum );
+			
+			// first eliminate the flag models, if this is non-net game 
+			// On load model IF a multiplayer game OR NOT a flag model
+			if (Mdl::Skate::Instance()->IsMultiplayerGame() ||
+				TypeChecksum != CRCD(0xbebb41f0,"Flag_Red")         && 
+				TypeChecksum != CRCD(0x836284a5,"Flag_Red_Base")    &&
+				TypeChecksum != CRCD(0xc3ebe05e,"Flag_Green") 		 && 
+				TypeChecksum != CRCD(0x4f8ff239,"Flag_Green_base")	 &&
+				TypeChecksum != CRCD(0x8cca938a,"Flag_Blue")		 &&
+				TypeChecksum != CRCD(0x41e5bcf,"Flag_Blue_base")	 &&
+				TypeChecksum != CRCD(0xc2af1eb1,"Flag_Yellow")		 &&
+				TypeChecksum != CRCD(0xed3ad7fe,"Flag_Yellow_base") )
+			{
+	
+				// Handle pre-loading of models for non-net game nodes
+				switch (ClassChecksum)
+				{
+					case 0xa0dfac98: // Pedestrian
+					case 0xa71394a2: // AnimatingObject
+					case 0x19b1e241: // ParticleEmitter				
+						break;
+					case 0xe47f1b79: // Vehicle
+					case 0xef59c100: // GameObject
+						{
+							const char *pModelName = NULL;
+	
+							if ( pNode->GetText( 0x286a8d26, &pModelName ) ) // checksum 'model'
+							{
+								const char* p_model_name;
+								pNode->GetText( CRCD(0x286a8d26,"model"), &p_model_name, true );
+								if (stricmp("none",p_model_name) != 0)
+								{		   
+									Str::String fullModelName;
+									fullModelName = Gfx::GetModelFileName(p_model_name, ".mdl");
+									
+									Nx::CModel* p_dummy = Nx::CEngine::sInitModel();
+									//Dbg_Message( "Preloading model %s", fullModelName.getString() );
+	
+									//int texDictOffset = 0;
+									//pNode->GetInteger( "texDictOffset", &texDictOffset, false );
+	
+									bool forceTexDictLookup = pNode->ContainsFlag( CRCD(0x6c37fdc7,"AllowReplaceTex") );
+									
+									p_dummy->AddGeom( fullModelName.getString(), 0, true, 0, forceTexDictLookup );
+									Nx::CEngine::sUninitModel( p_dummy );
+								}
+							}
+						}
+						break;
+					default:
+						break;
+				}
+			}
+		}				
+	}
+
+	Mem::PopMemProfile(/*"PreLoadModels"*/);
+	
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PreloadPedestrians | 
+bool ScriptPreloadPedestrians( Script::CStruct *pParams, Script::CScript *pScript )
+{
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+
+	// should be skipped for net game?
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	if ( gamenet_man->InNetGame()/* || Mdl::Skate::Instance()->IsMultiplayerGame()*/)
+	{
+		return false;
+	}
+
+	if ( Script::GetInt( "NoPreloadRandomPeds", false ) )
+	{
+		// some create-a-skater artists don't want to
+		// preload their random peds
+		return false;
+	}
+
+	Mem::PushMemProfile("PreLoadPedestrians");
+
+	if (pParams->ContainsFlag(CRCD(0xf6f8e158,"no_random")))
+	{
+		// This feature used when pre-loading peds for created goals in the park editor,
+		// where we don't want loads of parts cos there isn't much memory
+	}
+	else
+	{
+		// loop through the master_editable_list, looking for
+		// randomizable parts (based on the "allow_random" flag
+		// which should have been set when preselecting ped parts.
+		s_preload_ped_part( Script::GenerateCRC("ped_m_head") );
+		s_preload_ped_part( Script::GenerateCRC("ped_m_torso") );
+		s_preload_ped_part( Script::GenerateCRC("ped_m_legs") );
+		s_preload_ped_part( Script::GenerateCRC("ped_f_head") );
+		s_preload_ped_part( Script::GenerateCRC("ped_f_torso") );
+		s_preload_ped_part( Script::GenerateCRC("ped_f_legs") );
+	}
+	
+	// should really do an examination of what parts the thing actually uses
+	// and then the levelassetlister should list those...
+
+	// loop through the NodeArray, looking for single-skinned models
+	Script::CArray *pNodeArray = Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray, ( "No NodeArray found in ParseNodeArray" ) );
+
+	for ( uint32 i = 0; i < pNodeArray->GetSize(); i++ )
+	{
+		// we need to give it a texDictOffset so that it
+		// doesn't conflict with the secret ped skaters...
+		// (make sure it's less than the cutscene tex dict range, 
+		// though, or else it would conflict with the cutscene objects)
+		int texDictOffset = Mdl::Skate::vMAX_SKATERS;
+
+        Script::CStruct* pNode = pNodeArray->GetStructure( i );
+		Dbg_MsgAssert( pNode, ( "NULL pNode" ) );
+
+		uint32 ClassChecksum = 0;
+		pNode->GetChecksum( Script::GenerateCRC("Class"), &ClassChecksum );
+
+		if ( ClassChecksum == Script::GenerateCRC("pedestrian") )
+		{
+			const char* p_model_name;
+			if ( pNode->GetText( "model", &p_model_name, false ) )
+			{
+				Str::String fullModelName;
+				fullModelName = Gfx::GetModelFileName(p_model_name, ".skin");
+
+				// preload the model!
+				Nx::CModel* pDummy = Nx::CEngine::sInitModel();
+//				Dbg_Message("Preloading single-skinned ped model %s", fullModelName.getString() );
+				pDummy->AddGeom( fullModelName.getString(), 0, true, texDictOffset );
+				
+				Nx::CEngine::sUninitModel( pDummy );
+			}
+
+			// preload the shadow if necessary
+			const char* p_shadow_model_name;
+			if ( pNode->GetText( "shadowmodel", &p_shadow_model_name, false ) )
+			{
+				Str::String fullModelName;
+				fullModelName = Gfx::GetModelFileName(p_shadow_model_name, ".mdl");
+
+				// preload the model!
+				Nx::CModel* pDummy = Nx::CEngine::sInitModel();
+//				Dbg_Message("Preloading single-skinned ped model %s", fullModelName.getString() );
+				pDummy->AddGeom( fullModelName.getString(), 0, true, texDictOffset );
+
+				Nx::CEngine::sUninitModel( pDummy );
+			}
+
+
+			// now find the non-randoms
+			uint32 profileName;
+			if ( pNode->GetChecksum( CRCD(0x7ea855f0,"profile"), &profileName, false ) )
+			{
+				Script::CStruct* pStruct = Script::GetStructure( profileName );
+				if ( pStruct )				   
+				{
+					Nx::CModel* pDummy = Nx::CEngine::sInitModel();
+					Dbg_Assert( pDummy );
+  //  				pDummy->LoadSkeleton( Script::GenerateCRC("human") );
+					Gfx::CModelAppearance theTempAppearance;
+					theTempAppearance.Load( pStruct, false );
+					Gfx::CModelBuilder theBuilder( true, texDictOffset );
+					theBuilder.BuildModel( &theTempAppearance, pDummy, NULL, Script::GenerateCRC("preload_model_from_appearance") );
+					Nx::CEngine::sUninitModel( pDummy );
+    			}
+			}
+		}
+	}
+
+//	Dbg_Message( "Preloading peds took %d ms", Tmr::ElapsedTime( baseTime ) );
+			
+	Mem::PopMemProfile(/*"PreLoadModels"*/);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PreselectRandomPedestrians | 
+// Example: PreselectRandomPedestrians part=ped_f_legs list=sch_ped_f_legs num=4
+bool ScriptPreselectRandomPedestrians( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 fullPartChecksum;
+	pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &fullPartChecksum, Script::ASSERT );
+
+	Script::CArray* pFullPartArray = NULL;
+	pFullPartArray = Script::GetArray( fullPartChecksum, Script::ASSERT );
+
+	uint32 levelName = Mdl::Skate::Instance()->m_requested_level;
+
+	Dbg_Message( "Pre-selecting %s in %s", Script::FindChecksumName(fullPartChecksum), Script::FindChecksumName(levelName) );
+
+	// go through the specified part array,
+	// and remove any "allow_random" components...
+	uint32 fullPartArraySize = pFullPartArray->GetSize();
+	for ( uint32 i = 0; i < fullPartArraySize; i++ )
+	{
+		Script::CStruct* pStruct = pFullPartArray->GetStructure( i );
+		
+		// allowed_to_pick is a temp variable used only by this function...
+		// clear it already exists...
+		pStruct->RemoveComponent( CRCD(0x355f9467,"allowed_to_pick") );
+
+		// they all default to not be able to be randomized
+		pStruct->AddInteger( CRCD(0xf1e3cd22,"allow_random"), 0 );
+
+		pStruct->AddInteger( CRCD(0x92bddfd9,"already_selected"), 0 );
+		
+		pStruct->AddInteger( CRCD(0x4b833e64,"random_index"), -1 );
+	}
+	
+	int allowableCount = 0;
+	for ( uint32 i = 0; i < fullPartArraySize; i++ )
+	{
+		Script::CStruct* pStruct = pFullPartArray->GetStructure( i );
+		
+		bool allowed_to_pick = true;
+
+		// if it's level-specific...
+		Script::CStruct* pLevelSpecificStruct;
+		if ( pStruct->ContainsComponentNamed( CRCD(0xf6f8e158,"no_random") ) )
+		{
+			allowed_to_pick = false;
+		
+			if ( pStruct->GetStructure( CRCD(0x3598bf7d,"level_specific"), &pLevelSpecificStruct, false ) )
+			{
+				// sanity check:  level_specific and no_random are mutually exclusive
+				Script::PrintContents( pStruct );
+				Dbg_MsgAssert( 0, ( "no_random should not be used with level_specific" ) );
+			}
+		}
+		else if ( pStruct->GetStructure( CRCD(0x3598bf7d,"level_specific"), &pLevelSpecificStruct, false ) )
+		{
+			Dbg_MsgAssert( !pStruct->ContainsComponentNamed(CRCD(0x94d12f97,"exclude_from_levels")), ( "level_specific and exclude_from_levels are mutually exclusive" ) )
+
+			// check to see if we're in this level...
+			allowed_to_pick = pLevelSpecificStruct->ContainsComponentNamed( levelName );
+		}
+		else if ( pStruct->GetStructure( CRCD(0x94d12f97,"exclude_from_levels"), &pLevelSpecificStruct, false ) )
+		{
+			Dbg_MsgAssert( !pStruct->ContainsComponentNamed(CRCD(0x3598bf7d,"level_specific")), ( "level_specific and exclude_from_levels are mutually exclusive" ) )
+
+			// check to see if we're in this level...
+			allowed_to_pick = !pLevelSpecificStruct->ContainsComponentNamed( levelName );
+		}
+		 
+		if ( allowed_to_pick )
+		{
+			allowableCount++;
+			pStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 1 );
+		}
+		else
+		{
+			// clear the already picked flag
+			pStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 0 );
+			
+			if ( Script::GetInt( "cas_artist", false ) )
+			{
+//				Dbg_Message("Disallowing...");
+//				Script::PrintContents(pStruct);
+			}
+		}
+	}
+
+	// Nolan's Fisherman Example:
+	// If a non-randomized profile explicitly loads up a random ped part anyway,
+	// then add that random ped asset to the pool of allowable randomized ped parts
+	Script::CArray *pNodeArray=Script::GetArray( CRCD(0xc472ecc5,"NodeArray") );
+	Dbg_MsgAssert( pNodeArray, ( "No NodeArray found in PreselectRandomPedestrians" ) );
+	for ( uint32 i = 0; i < pNodeArray->GetSize(); i++ )
+	{
+		Script::CStruct* pNode = pNodeArray->GetStructure( i );
+		Dbg_MsgAssert( pNode, ( "NULL pNode" ) );
+
+		uint32 ClassChecksum = 0;
+		pNode->GetChecksum( Script::GenerateCRC("Class"), &ClassChecksum );
+
+		if ( ClassChecksum == Script::GenerateCRC("pedestrian") )
+		{
+			// now find the non-randoms
+			uint32 profileName;
+			if ( pNode->GetChecksum( CRCD(0x7ea855f0,"profile"), &profileName, false ) )
+			{
+				Script::CStruct* pProfileStruct = Script::GetStructure( profileName );
+				if ( pProfileStruct )
+				{
+					Script::CStruct* pVirtualStruct;
+					if ( pProfileStruct->GetStructure( fullPartChecksum, &pVirtualStruct, false ) )
+					{
+						uint32 descID;
+						if ( pVirtualStruct->GetChecksum( CRCD(0x4bb2084e,"desc_id"), &descID, false ) )
+						{
+							Script::CStruct* pActualStruct = Cas::GetOptionStructure( fullPartChecksum, descID, false );
+							if ( pActualStruct && !pActualStruct->ContainsComponentNamed( CRCD(0xf6f8e158,"no_random") ) )
+							{
+								if ( Script::GetInt( "cas_artist", false ) )
+								{
+//									Dbg_Message( "Allowing to pick %s", Script::FindChecksumName( descID ) );
+								}
+								pActualStruct->AddInteger( CRCD(0xf1e3cd22,"allow_random"), 1 );
+								pActualStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 1 );
+
+								// these get lowest-priority when randomizing...
+								pActualStruct->AddInteger( CRCD(0x92bddfd9,"already_selected"), 1 );
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	// sanity check that the number specified is 
+	// greater than the actual number of random parts
+	int numToSelect = allowableCount;
+	pParams->GetInteger( CRCD(0x23bc5091,"num"), &numToSelect, Script::NO_ASSERT );
+	if ( numToSelect > allowableCount )
+	{
+		Dbg_Message( "Not enough parts in list %s to select from (need %d, have %d)!", Script::FindChecksumName(fullPartChecksum), numToSelect, allowableCount );
+		numToSelect = allowableCount;
+	}
+
+	//-------------------------------------------------------------
+	// look for a item with a universal skin tone first...  if it
+	// exists, automatically preselect it.  otherwise, make sure
+	// that at least one of each skintone is chosen, or else the
+	// skintones may not match up after part randomization.
+	Script::CStruct* pSkinTonePicks[64];
+	int skinToneCount = 0;
+
+	Cas::BuildRandomSetList( fullPartChecksum, 0, pSkinTonePicks, &skinToneCount );
+	if ( skinToneCount )
+	{
+		Script::CStruct* pStruct = pSkinTonePicks[Mth::Rnd( skinToneCount )];
+		
+		pStruct->AddInteger( CRCD(0xf1e3cd22,"allow_random"), 1 );
+		pStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 0 );
+		pStruct->AddInteger( CRCD(0x4b833e64,"random_index"), numToSelect-1 );
+		numToSelect--;
+	}
+	else
+	{
+		// this code will only kick in if all of the options have a skin-tone
+		// (random_set) associated with it 
+		Dbg_MsgAssert( numToSelect >= 3, ( "Num parts to select (%d) must be >= 3, so that ped skin tones will work (either need to increase '%s' in levels.q, or add at least one universal skintone option)", numToSelect, Script::FindChecksumName(fullPartChecksum) ) );
+		
+		Script::CStruct* pStruct;
+
+		Cas::BuildRandomSetList( fullPartChecksum, CRCD(0x94e5a308,"light"), pSkinTonePicks, &skinToneCount );
+		Dbg_MsgAssert( skinToneCount, ( "Couldn't find light-skinned version of this part %s", Script::FindChecksumName(fullPartChecksum) ) );
+		pStruct = pSkinTonePicks[Mth::Rnd( skinToneCount )];
+		pStruct->AddInteger( CRCD(0xf1e3cd22,"allow_random"), 1 );
+		pStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 0 );
+		pStruct->AddInteger( CRCD(0x4b833e64,"random_index"), numToSelect-1 );
+		numToSelect--;
+		
+		Cas::BuildRandomSetList( fullPartChecksum, CRCC(0x85aaf0d8,"tan"), pSkinTonePicks, &skinToneCount );
+		Dbg_MsgAssert( skinToneCount, ( "Couldn't find tan-skinned version of this part %s", Script::FindChecksumName(fullPartChecksum) ) );
+		pStruct = pSkinTonePicks[Mth::Rnd( skinToneCount )];
+		pStruct->AddInteger( CRCD(0xf1e3cd22,"allow_random"), 1 );
+		pStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 0 );
+		pStruct->AddInteger( CRCD(0x4b833e64,"random_index"), numToSelect-1 );
+		numToSelect--;
+		
+		Cas::BuildRandomSetList( fullPartChecksum, CRCC(0xe4834204,"dark"), pSkinTonePicks, &skinToneCount );
+		Dbg_MsgAssert( skinToneCount, ( "Couldn't find dark-skinned version of this part %s", Script::FindChecksumName(fullPartChecksum) ) );
+		pStruct = pSkinTonePicks[Mth::Rnd( skinToneCount )];
+		pStruct->AddInteger( CRCD(0xf1e3cd22,"allow_random"), 1 );
+		pStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 0 );
+		pStruct->AddInteger( CRCD(0x4b833e64,"random_index"), numToSelect-1 );
+		numToSelect--;
+	}
+	//-------------------------------------------------------------
+
+	//-------------------------------------------------------------
+	// now, add the "allow_random" flag to  of the items in the list...
+	for ( int i = 0; i < numToSelect; i++ )
+	{
+		int allowed_to_pick = 0;
+		while ( !allowed_to_pick )
+		{
+			int randomPick = Mth::Rnd( fullPartArraySize );
+			Dbg_Assert( randomPick >= 0 && randomPick < (int)fullPartArraySize );
+
+			Script::CStruct* pStruct = pFullPartArray->GetStructure( randomPick );
+			pStruct->GetInteger( CRCD(0x355f9467,"allowed_to_pick"), &allowed_to_pick, Script::ASSERT );
+			if ( allowed_to_pick )
+			{
+				pStruct->AddInteger( CRCD(0xf1e3cd22,"allow_random"), 1 );
+				pStruct->AddInteger( CRCD(0x355f9467,"allowed_to_pick"), 0 );
+				pStruct->AddInteger( CRCD(0x4b833e64,"random_index"), i );
+			}
+		}
+	}
+	//-------------------------------------------------------------
+
+	//-------------------------------------------------------------
+	// debugging code to show which items are allowed to be randomized
+#ifdef __NOPT_ASSERT__
+	int randomCount = 0;
+	
+	for ( uint32 i = 0; i < fullPartArraySize; i++ )
+	{
+		Script::CStruct* pStruct = pFullPartArray->GetStructure( i );
+		
+		int allow_random = 0;
+		pStruct->GetInteger( CRCD(0xf1e3cd22,"allow_random"), &allow_random, Script::ASSERT );
+
+		uint32 descID;
+		pStruct->GetChecksum( CRCD(0x4bb2084e,"desc_id"), &descID, Script::ASSERT );
+
+		if ( allow_random )
+		{
+			Dbg_Message( "Setting random part #%d: %s (%d)", randomCount++, Script::FindChecksumName(descID), numToSelect );
+		}
+		
+		// get rid of temp variable...
+		pStruct->RemoveComponent( CRCD(0x355f9467,"allowed_to_pick") );
+	}
+#endif // __NOPT_ASSERT__
+	//-------------------------------------------------------------
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptReplaceCarTextures( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// this is a highly-specific function used for quickly changing
+	// a car's headlight textures for time-of-day stuff
+	// (the previous implementation was too slow, 
+	// involving looping through each model component,
+	// getting the model, and attempting to replaceTexture
+	// on it...  this one targets car objects, and
+	// skips over texture dictionaries that have already
+	// been swapped)
+
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+	
+	int num_to_rebuild = 0;
+
+	const int vMAX_TEX_DICTS = 32;
+	Nx::CTexDict* tex_dicts[vMAX_TEX_DICTS];
+	int num_tex_dicts = 0;
+
+	// new fast way, just go directly to the components, if any
+	// (this is still kind of slow, so you wouldn't want to call it that often,
+	// maybe only when any hiccups can be masked out with a screen fade or something)
+	Obj::CModelComponent *p_component = (Obj::CModelComponent*)Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_MODEL );
+	while( p_component )
+	{
+		// GJ TODO:  somehow loop through all the
+		// skin assets in the asset manager
+		if ( p_component->GetObject()->GetType() == SKATE_TYPE_CAR )
+		{
+			Nx::CModel* pModel = p_component->GetModel();
+
+			bool must_rebuild = false;
+
+			for ( int i = 0; i < pModel->GetNumGeoms(); i++ )
+			{
+				Nx::CTexDict* pTexDict = pModel->GetTexDictByIndex( i );
+
+				bool found = false;
+
+				// if any tex dict in the model's list
+				// has not been processed yet, then
+				// replace it
+				for ( int j = 0; j < num_tex_dicts; j++ )
+				{
+					if ( pTexDict == tex_dicts[j] )
+					{
+						found = true;
+					}
+				}
+
+				if ( !found )
+				{
+					// mark that texture dictionary as
+					// processed, so that we don't have
+					// to reprocess future objects
+                    Dbg_MsgAssert( num_tex_dicts < vMAX_TEX_DICTS, ( "Too many texture dictionaries to search through" ) );
+					tex_dicts[num_tex_dicts] = pTexDict;
+					num_tex_dicts++;
+					
+					must_rebuild = true;
+				}
+			}
+
+			if ( must_rebuild )
+			{                
+				num_to_rebuild++;
+				p_component->CallMemberFunction( CRCD(0x83f9be15,"Obj_ReplaceTexture"), pParams, pScript );
+			}
+		}
+
+		p_component = (Obj::CModelComponent*)p_component->GetNextSameType();		
+	}
+
+//	Dbg_Message( "ReplaceCarTextures on %d items took %d ms", num_to_rebuild, Tmr::ElapsedTime( baseTime ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetUIFromPreferences | 
+// @parmopt name | field | | field name
+// @parmopt string | field | | field string
+// @parmopt name | select_if | | check if this name equals the value of
+// checksum in the field
+// @parmopt name | menu_id | | the menu id to act on if the select_if is true
+// @parmopt name | id | | the control id
+// @flag mask_password | if set, all letters are replaced with stars 
+bool ScriptSetUIFromPreferences(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// gets either network or splitscreen prefs, as appropriate
+	Prefs::Preferences* pPreferences = Mdl::GetPreferences( pParams );
+	
+	uint32 field_id;
+
+	// look for either a field checksum or string
+	if ( !pParams->GetChecksum("field", &field_id ) )
+	{
+		const char* pFieldName;
+		pParams->GetText("field", &pFieldName, true);
+		field_id = Script::GenerateCRC( pFieldName );
+	}
+	
+	uint32 checksum_value1;
+	if ( pParams->GetChecksum( "select_if", &checksum_value1 ) )
+	{	
+		Script::CStruct* pStructure = pPreferences->GetPreference( field_id );
+		Dbg_Assert(pStructure);
+		uint32 checksum_value2;
+		pStructure->GetChecksum( "checksum", &checksum_value2, true );
+
+		if ( checksum_value1 == checksum_value2 )
+		{
+			/*
+			uint32 menu_id;
+			pParams->GetChecksum( "menu_id", &menu_id, true );
+			Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+			Front::MenuElement *pMenuElement=menu_factory->GetMenuElement(menu_id);
+			Front::VerticalMenu *pMenu=static_cast(pMenuElement);
+			Dbg_MsgAssert(pMenu,("\n%s\nNo menu with id '%s' found",pScript->GetScriptInfo(),Script::FindChecksumName(menu_id)));
+
+			Script::PrintContents(pParams);
+
+			uint32 control_id;
+			pParams->GetChecksum( "id", &control_id, true );
+//			pParams->GetChecksum( "control_id", &control_id, true );
+			pMenu->AttemptSelect( control_id );
+			*/
+		}
+	}
+	else
+	{
+		uint32 control_name;
+		pParams->GetChecksum( "id", &control_name, true );
+
+		int mask_password = pParams->ContainsFlag( "mask_password" );
+	
+		// update all the elements
+		pPreferences->UpdateUIElement( control_name, field_id, mask_password );
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetUIFromSkaterProfile | 
+// @parm name | field_id | the field id
+// @parmopt name | select_if | | 
+// @parmopt name | menu_id | | the menu id
+// @parmopt name | control_id | | 
+// @parmopt name | slider_id | |
+bool ScriptSetUIFromSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	/*
+	uint32 field_id;
+	pParams->GetChecksum( "field_id", &field_id, true );
+
+
+	Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+
+	uint32 slider_id;
+	uint32 control_id;
+	uint32 checksum_value;
+	if ( pParams->GetChecksum( "select_if", &checksum_value ) )
+	{
+		if ( checksum_value == pSkaterProfile->GetChecksumValue(field_id) )
+		{
+			uint32 menu_id;
+			pParams->GetChecksum( "menu_id", &menu_id, true );
+			Front::MenuElement *pMenuElement=menu_factory->GetMenuElement(menu_id);
+			Front::VerticalMenu *pMenu=static_cast(pMenuElement);
+			Dbg_MsgAssert(pMenu,("\n%s\nNo menu with id '%s' found",pScript->GetScriptInfo(),Script::FindChecksumName(menu_id)));
+			pParams->GetChecksum( "control_id", &control_id, true );
+			pMenu->AttemptSelect( control_id );
+		}
+	}
+	else if ( pParams->GetChecksum( "slider_id", &slider_id ) )
+	{
+		// slider element
+		Front::SliderMenuElement* pSliderElement = static_cast( menu_factory->GetMenuElement( slider_id, true ) );
+		pSliderElement->SetValue( pSkaterProfile->GetUIValue(field_id) );
+	}
+	else if ( pParams->GetChecksum( "control_id", &control_id ) )
+	{		
+		Front::MenuEvent event;
+		event.SetTypeAndTarget(Front::MenuEvent::vSETCONTENTS, control_id );
+		event.GetData()->AddComponent(Script::GenerateCRC("string"), ESYMBOLTYPE_STRING, pSkaterProfile->GetUIString(field_id).getString() );
+		menu_factory->LaunchEvent(&event);
+	}
+	else
+	{
+		Dbg_Assert( 0 );
+	}
+	*/
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetPreferencesFromUI | changes field defined by string to value defined by field
+// @parm string | string | 
+// @parm string | field | 
+// @parmopt name | level_checksum | | 
+bool ScriptSetPreferencesFromUI(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// gets either network or splitscreen prefs, as appropriate
+	Prefs::Preferences* pPreferences = Mdl::GetPreferences( pParams );
+
+	const char* p_string;
+	pParams->GetText( "string", &p_string, true );
+
+	const char* pFieldName;
+	pParams->GetText("field", &pFieldName, true);
+
+	Dbg_Message( "Changing field %s to %s", pFieldName, p_string );
+	
+	// transfer data back into the preferences
+	Script::CStruct* pTempStructure = new Script::CStruct;
+	pTempStructure->AddComponent( Script::GenerateCRC("ui_string"), ESYMBOLTYPE_STRING, p_string );
+	
+	uint32 checksum;
+	checksum = 0;
+	pParams->GetChecksum( "checksum", &checksum );
+	pTempStructure->AddComponent( Script::GenerateCRC("checksum"), ESYMBOLTYPE_NAME, (int) checksum );
+
+	if( ( stricmp( pFieldName, "time_limit" ) == 0 ) ||
+		( stricmp( pFieldName, "horse_time_limit" ) == 0 ))
+	{
+		int time;
+
+		pParams->GetInteger( "time", &time );
+		Dbg_Message( "Changing time to %d", time );
+		pTempStructure->AddComponent( Script::GenerateCRC("time"), ESYMBOLTYPE_INTEGER, time );
+	}
+
+	if( stricmp( pFieldName, "target_score" ) == 0 )
+	{
+		int score;
+
+		if( pParams->GetInteger( "score", &score ) == false )
+		{
+			pParams->GetInteger( "time", &score, Script::ASSERT );
+			score = Tmr::Seconds( score );
+		}
+		
+		Dbg_Message( "Changing score to %d", score );
+		pTempStructure->AddComponent( Script::GenerateCRC("score"), ESYMBOLTYPE_INTEGER, score );
+	}
+
+
+
+	pPreferences->SetPreference( Script::GenerateCRC(pFieldName), pTempStructure );
+	delete pTempStructure;
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetAllToDefaultStats | 
+bool ScriptResetAllToDefaultStats( Script::CStruct *pParams, Script::CScript *pScript )
+{
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+
+	Script::CArray* pMasterSkaterArray = Script::GetArray( "master_skater_list", Script::ASSERT );
+
+	for ( uint32 i = 0; i < pMasterSkaterArray->GetSize(); i++ )
+	{
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		
+		Script::CStruct* pOriginalInfo = pMasterSkaterArray->GetStructure( i );
+
+		uint32 skaterName;
+		pOriginalInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &skaterName, Script::ASSERT );
+
+		Script::CArray* pStatNameArray = Script::GetArray( "stat_names" );
+		for ( uint32 j = 0; j < pStatNameArray->GetSize(); j++ )
+		{
+			Script::CStruct* pStatInfo = pStatNameArray->GetStructure(j);
+			
+			uint32 statName;
+			pStatInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &statName, Script::ASSERT );
+			
+			int statVal;
+			pOriginalInfo->GetInteger( statName, &statVal, Script::ASSERT );
+			pTempStructure->AddInteger( statName, statVal );
+		}
+
+		int pointsAvailable;
+		pOriginalInfo->GetInteger( "points_available", &pointsAvailable, Script::ASSERT );
+		pTempStructure->AddInteger( "points_available", pointsAvailable );
+
+		// now add it to the corresponding skater profile
+		Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+		uint32 numProfiles = pPlayerProfileManager->GetNumProfileTemplates();
+		for ( uint32 j = 0; j < numProfiles; j++ )
+		{
+			Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplateByIndex(j);
+			Dbg_Assert( pProfile );
+			Script::CStruct* pInfo = pProfile->GetInfo();
+
+			uint32 test;
+			pInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &test, Script::ASSERT );
+			if ( test == skaterName )
+			{
+				pInfo->AppendStructure( pTempStructure );
+				break;
+			}
+		}
+		
+		delete pTempStructure;
+	}
+
+//	Dbg_Message( "ResetAllToDefaultStats took %d ms", Tmr::ElapsedTime( baseTime ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetToDefaultProfile | 
+bool ScriptResetToDefaultProfile( Script::CStruct *pParams, Script::CScript *pScript )
+{
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+
+	uint32 resetThisSkaterName;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &resetThisSkaterName, Script::ASSERT );
+
+	Script::CArray* pMasterSkaterArray = Script::GetArray( "master_skater_list", Script::ASSERT );
+
+	for ( uint32 i = 0; i < pMasterSkaterArray->GetSize(); i++ )
+	{
+		Script::CStruct* pOriginalInfo = pMasterSkaterArray->GetStructure( i );
+		Dbg_Assert( pOriginalInfo );
+
+		uint32 skaterName;
+		pOriginalInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &skaterName, Script::ASSERT );
+
+		if ( resetThisSkaterName == skaterName )
+		{
+			Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+			Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplate( skaterName );
+			Dbg_Assert( pProfile );
+
+			uint32 partial;
+            if ( pParams->GetChecksum( CRCD(0x55f82f0b,"partial"), &partial, Script::NO_ASSERT ) )
+            {
+                // resets appearance, tricks, NOT STATS
+                pProfile->PartialReset( pOriginalInfo );
+            }
+            else
+            {
+                // resets appearance, stats, tricks
+    			pProfile->Reset( pOriginalInfo );
+            }
+		}
+	}
+
+//	Dbg_Message( "ResetAllToDefaultProfile took %d ms", Tmr::ElapsedTime( baseTime ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetAllToDefaultProfile | 
+bool ScriptResetAllToDefaultProfile( Script::CStruct *pParams, Script::CScript *pScript )
+{
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+
+	Script::CArray* pMasterSkaterArray = Script::GetArray( "master_skater_list", Script::ASSERT );
+
+	for ( uint32 i = 0; i < pMasterSkaterArray->GetSize(); i++ )
+	{
+		Script::CStruct* pOriginalInfo = pMasterSkaterArray->GetStructure( i );
+		Dbg_Assert( pOriginalInfo );
+
+		uint32 skaterName;
+		pOriginalInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &skaterName, Script::ASSERT );
+
+		Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+		Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplate( skaterName );
+		Dbg_Assert( pProfile );
+			
+		// resets appearance, stats, tricks
+		pProfile->Reset( pOriginalInfo );
+	}
+
+//	Dbg_Message( "ResetAllToDefaultProfile took %d ms", Tmr::ElapsedTime( baseTime ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ForEachSkaterName | 
+bool ScriptForEachSkaterName( Script::CStruct *pParams, Script::CScript *pScript )
+{
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+
+	uint32 scriptToRun;
+	pParams->GetChecksum( CRCD(0x62ba3f6a,"do"), &scriptToRun, Script::ASSERT );
+
+	Script::CStruct* pSubParams = NULL;
+	pParams->GetStructure( CRCD(0x7031f10c,"params"), &pSubParams, Script::NO_ASSERT );
+
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	int numProfiles = pPlayerProfileManager->GetNumProfileTemplates();
+	for ( int i = 0; i < numProfiles; i++ )
+	{
+		Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplateByIndex(i);
+		Dbg_Assert( pProfile );
+		Script::CStruct* pInfo = pProfile->GetInfo();
+
+		uint32 skaterName;
+		pInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &skaterName, Script::ASSERT );
+
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		pTempStructure->AppendStructure( pSubParams );
+		pTempStructure->AddChecksum( CRCD(0xa1dc81f9,"name"), skaterName );
+		Script::RunScript( scriptToRun, pTempStructure );
+		delete pTempStructure;
+	}
+
+//	Dbg_Message( "ScriptForEachSkaterName took %d ms", Tmr::ElapsedTime( baseTime ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ForEachSkaterProfile | 
+bool ScriptForEachSkaterProfile( Script::CStruct *pParams, Script::CScript *pScript )
+{
+//	Tmr::Time baseTime = Tmr::ElapsedTime(0);
+
+	uint32 scriptToRun;
+	pParams->GetChecksum( CRCD(0x62ba3f6a,"Do"), &scriptToRun, Script::ASSERT );
+
+	Script::CStruct* pSubParams = NULL;
+	pParams->GetStructure( CRCD(0x7031f10c,"params"), &pSubParams, Script::NO_ASSERT );
+
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	int numProfiles = pPlayerProfileManager->GetNumProfileTemplates();
+	for ( int i = 0; i < numProfiles; i++ )
+	{
+		Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplateByIndex(i);
+		Dbg_Assert( pProfile );
+		Script::CStruct* pInfo = pProfile->GetInfo();
+
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		pTempStructure->AppendStructure( pSubParams );
+		pTempStructure->AppendStructure( pInfo );
+		Script::RunScript( scriptToRun, pTempStructure );
+		delete pTempStructure;
+	}
+
+//	Dbg_Message( "ScriptForEachSkaterProfile took %d ms", Tmr::ElapsedTime( baseTime ) );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSkaterProfileInfoByName | appends the skater profile info to the
+// calling script's params. returns false if the specified profile can't be found
+// @parm name | name | the name to search for
+bool ScriptGetSkaterProfileInfoByName( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 name;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	int numProfiles = pPlayerProfileManager->GetNumProfileTemplates();
+	for ( int i = 0; i < numProfiles; i++ )
+	{
+		Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplateByIndex(i);
+		Dbg_Assert( pProfile );
+		Script::CStruct* pInfo = pProfile->GetInfo();
+
+		uint32 test;
+		pInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &test, Script::ASSERT );
+		if ( test == name )
+		{
+			pScript->GetParams()->AppendStructure( pInfo );
+			return true;
+		}
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSkaterProfileInfoByName | appends the skater profile info to the
+// calling script's params. returns false if the specified profile can't be found
+// @parm name | name | the name to search for
+// @parm structure | params | the params structure to append to the skater profile
+bool ScriptSetSkaterProfileInfoByName( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 name;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+
+	Script::CStruct* pSubParams = NULL;
+	pParams->GetStructure( "params", &pSubParams, Script::ASSERT );
+
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	int numProfiles = pPlayerProfileManager->GetNumProfileTemplates();
+	for ( int i = 0; i < numProfiles; i++ )
+	{
+		Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplateByIndex(i);
+		Dbg_Assert( pProfile );
+		Script::CStruct* pInfo = pProfile->GetInfo();
+
+		uint32 test;
+		pInfo->GetChecksum( CRCD(0xa1dc81f9,"name"), &test, Script::ASSERT );
+		if ( test == name )
+		{
+			pInfo->AppendStructure( pSubParams );
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetDefaultAppearance | 
+// @parm int | skater | the skater to reset
+bool ScriptResetDefaultAppearance( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	pSkaterProfile->ResetDefaultAppearance();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetDefaultTricks | 
+// @parm int | skater | the skater to reset
+bool ScriptResetDefaultTricks( Script::CStruct *pParams, Script::CScript *pScript )
+{	
+
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	pSkaterProfile->ResetDefaultTricks();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetDefaultStats | 
+// @parm int | skater | the skater to reset
+bool ScriptResetDefaultStats( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	pSkaterProfile->ResetDefaultStats();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RandomizeAppearance | 
+// @parm int | skater | the skater to randomize
+bool ScriptRandomizeAppearance( Script::CStruct *pParams, Script::CScript *pScript )
+{  
+	Dbg_Message( "STUB:  RandomizeAppearance" );
+
+	/*
+
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	Gfx::CModelAppearance* pSkaterAppearance = pSkaterProfile->GetAppearance();
+	Dbg_Assert( pSkaterAppearance );
+	pSkaterAppearance->Randomize();
+	 */
+
+	// Randomize: Randomizes parts, height, weight
+	// Possibly to be moved into script...
+	// Needs to take disqualifications into account
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PrintCurrentAppearance | 
+// @parm int | skater | the skater num
+bool ScriptPrintCurrentAppearance(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+
+	
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	Gfx::CModelAppearance* pAppearance;
+	pAppearance = pSkaterProfile->GetAppearance();
+	pAppearance->PrintContents(pAppearance->GetStructure());
+	
+	/*const Gfx::CFaceTexture* p_face = pAppearance->GetFaceTexture();
+	if( p_face && p_face->IsValid())
+		pAppearance->GetFaceTexture()->PrintContents();*/
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetNeversoftSkater | 
+// @parm int | skater | the skater num
+// @parm structure | appearance | current appearance structure 
+// @parm structure | info | current info structure
+bool ScriptSetNeversoftSkater(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+
+	// debug info:
+//	printf( "Set Neversoft Skater" );
+//	pParams->PrintContents();
+
+	// make sure it's a custom skater
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	Dbg_MsgAssert( !pSkaterProfile->IsPro(), ( "This function only works on a custom skater." ) );
+
+	// get the current appearance structure
+	Script::CStruct* pAppearanceStructure;
+	pParams->GetStructure( "appearance", &pAppearanceStructure, true );
+
+	// apply the desired appearance structure
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	pAppearance->Load( pAppearanceStructure );
+
+	// get the current info structure
+	Script::CStruct* pInfoStructure;
+	pParams->GetStructure( "info", &pInfoStructure, true );
+
+	// append the new info structure
+	Script::CStruct* pInfo = pSkaterProfile->GetInfo();
+	pInfo->AppendStructure( pInfoStructure );
+
+#ifdef __NOPT_ASSERT__
+	Script::PrintContents(pInfo);
+#endif
+	// TODO:  since the career is being reset,
+	// we should clear the info too (like stats and trick configs)
+	
+	pSkaterProfile->SetHeadIsLocked( true );
+
+	// reset the career as well
+//	Obj::CSkaterCareer* pCareer = pSkaterProfile->GetCareer();
+//	pCareer->Reset();
+//
+//	// reset the career flags
+//	for ( int i = 0; i < Script::GetInt( "FIRST_SHARED_GLOBAL_FLAG" ); i++ )
+//	{
+//		pCareer->UnSetGlobalFlag( i );
+//	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CurrentProfileIsLocked | returns true if this skater profile is locked
+// @parm int | skater | the skater num
+bool ScriptCurrentProfileIsLocked(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	Obj::CSkaterProfile* pSkaterProfile;
+	pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile(pParams);
+	return pSkaterProfile->IsLocked();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetSkaters | skip skaters to their restart points
+// can specify either a node number or a node name
+// the node number is ued by the "goto restart" menu
+// @parmopt int | node_number | -1 | number of a node to skip the skater to 
+// @parmopt name | node_name |  | name of a node to skip the skater to 
+bool ScriptResetSkaters(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int node = -1;
+	if (!pParams->GetInteger("node_number",&node))
+	{
+		uint32	node_name;
+		if (pParams->GetChecksum("node_name",&node_name))
+		{
+			node = SkateScript::FindNamedNode(node_name);
+		}	
+	}
+						 
+	Mdl::Skate::Instance()->ResetSkaters(node, pParams->ContainsFlag(CRCD(0xd6f06bf6, "RestartWalking")));
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSkaterProfileInfo | Appends the structure to the specified skater
+// profile info.  (Should only append the item if the item is of the same
+// type)
+// @parm int | player | the player slot to set
+// @parm structure | params | the stuff to set
+bool ScriptSetSkaterProfileInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// now append appropriate data
+	int 	playerNum;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &playerNum, Script::ASSERT );
+
+	Script::CStruct* pAppendStruct;
+	pParams->GetStructure( "params", &pAppendStruct, Script::ASSERT );
+
+	Obj::CPlayerProfileManager* pProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pProfile = pProfileManager->GetProfile( playerNum );
+	Script::CStruct* pStruct = pProfile->GetInfo();
+
+	pStruct->AppendStructure( pAppendStruct );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSkaterProfileInfo | Returns the skater profile info in the script params
+// @parm int | player | the player slot to get
+// @parm float | value | value 
+bool ScriptGetSkaterProfileInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int 	playerNum;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &playerNum, Script::ASSERT );	
+	
+	Obj::CPlayerProfileManager* pProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pProfile = pProfileManager->GetProfile( playerNum );
+	
+	Dbg_MsgAssert( pScript, ( "NULL pScript" ) );
+	Dbg_MsgAssert( pScript->GetParams(), ( "NULL pScript params" ) );
+
+	pScript->GetParams()->AppendStructure( pProfile->GetInfo() );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSkaterProfileProperty | Debugging function to set individual stats
+// @parm int | stat | the stat to set
+// @parm float | value | value 
+bool ScriptSetSkaterProfileProperty(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	int 	playerNum;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &playerNum, Script::ASSERT );
+
+	uint32 	stat;
+	int		value;
+	pParams->GetChecksum( NONAME, &stat );
+	pParams->GetInteger( NONAME, &value );
+
+	
+	Obj::CPlayerProfileManager* pProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pProfile = pProfileManager->GetProfile( playerNum );
+
+	return pProfile->SetPropertyValue( stat, value );
+
+/*	// If no skater was specified, change the stats of all local skaters
+	if( pScript->mpObject == NULL )
+	{
+		for( i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+		{
+			pSkater = Mdl::Skate::Instance()->GetSkater(i);
+			if (pSkater && pSkater->IsLocalClient())			// Skater might not exist
+			{
+				pSkater->SetStat((Obj::CSkater::EStat)stat,value);
+			}
+		}
+	}
+	else
+	{
+		pSkater = static_cast ( pScript->mpObject );
+		Dbg_Assert( pSkater );
+
+		pSkater->SetStat((Obj::CSkater::EStat)stat,value);
+	}
+	
+	return true;
+*/
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleAlwaysSpecial | ToggleAlwaysSpecial (on/off) fixes the 
+// special meter to be always full. 
+// ToggleAlwaysSpecial         ; Toggle it (useful for puttin on a button) 
+// ToggleAlwaysSpecial on      ; Sets it on 
+// ToggleAlwaysSpecial off     ; Sets it off 
+// Note that when you set it on, the special meter is immediately full.
+// However, when you turn it off, then the special meter will just decay 
+// normally, so it might not be immediately obvious that you switched it off. 
+bool ScriptToggleAlwaysSpecial(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool on = pParams->ContainsFlag("on");
+	bool off = pParams->ContainsFlag("off");
+	
+
+	for (int i=0;i<8;i++)
+	{
+		Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetSkater(i);						   
+		if (pSkater)			// Skater might not exist
+		{
+			if (on)
+			{
+				pSkater->SetAlwaysSpecial(true);
+			}
+			else if (off)
+			{
+				pSkater->SetAlwaysSpecial(false);
+			}
+			else 
+			{
+				pSkater->ToggleAlwaysSpecial();
+			}
+		}	
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterSpeedGreaterThan | Check the skater speed, as if to have a ped
+// not fall when the skater is stationary
+// @parm float | speed | Speed to check against
+bool ScriptSkaterSpeedGreaterThan( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	float speed;
+	if ( pParams->GetFloat( NONAME, &speed ) )
+	{
+		
+		Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();						   
+		if ( pSkater )
+		{
+			float skaterVel = pSkater->GetVel( ).Length( );
+			if ( skaterVel > speed)
+			{
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterSpeedLessThan | Check the skater speed, as if to have a ped
+// not fall when the skater is stationary
+// @parm float | speed | Speed to check against
+bool ScriptSkaterSpeedLessThan( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	float speed;
+	if ( pParams->GetFloat( NONAME, &speed ) )
+	{
+		
+		Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();						   
+		if ( pSkater )
+		{
+			float skaterVel = pSkater->GetVel( ).Length( );
+			if ( skaterVel < speed )
+			{
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterLastScoreLandedGreaterThan | Detects last score to trigger if script.
+// Example: if SkaterLastScoreLandedGreaterThan 2000 --do cool stuff-- endif
+// @uparm 1 | Score (int)
+bool ScriptLastScoreLandedGreaterThan( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	int score;
+	score = s_get_score_from_params( pParams, pScript );
+	Mdl::Score *pScore;
+	pScore = s_get_score_struct( );
+	if ( pScore )
+	{
+		int scorePot = pScore->GetLastScoreLanded();
+		if ( scorePot > score )
+			return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterLastScoreLandedLessThan | Detects last score to trigger if script.
+// Example: if SkaterLastScoreLandedLessThan 2000 --do cool stuff-- endif
+// @uparm 1 | Score (int)
+bool ScriptLastScoreLandedLessThan( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	int score;
+	score = s_get_score_from_params( pParams, pScript );
+	
+	Mdl::Score *pScore;
+	
+	pScore = s_get_score_struct( );
+	if ( pScore )
+	{
+		int scorePot = pScore->GetLastScoreLanded();
+		if ( scorePot < score )
+			return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | AnySkaterTotalScoreGreaterThan | Checks if any skaters' score is greater than the score paramter
+// Example: if AnySkaterTotalScoreGreaterThan 2000 --do cool stuff-- endif
+// @uparm 1 | Score (int)
+bool ScriptAnyTotalScoreAtLeast( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+	int score;
+	score = s_get_score_from_params( pParams, pScript );
+
+	if( Mdl::Skate::Instance()->GetGameMode()->IsTeamGame())
+	{
+		int i;
+		int total_score[GameNet::vMAX_TEAMS] = {0};
+
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			Mdl::Score* pScore = player->m_Skater->GetScoreObject();
+
+			total_score[player->m_Team] += pScore->GetTotalScore();
+		}
+
+		for( i = 0; i < GameNet::vMAX_TEAMS; i++ )
+		{
+			if( total_score[i] >= score )
+			{
+				return true;
+			}
+		}
+	}
+	else
+	{
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			if( player->m_Skater )
+			{
+				Mdl::Score *pScore;
+	
+				pScore = player->m_Skater->GetScoreObject();
+				int totalScore = pScore->GetTotalScore();
+				if( totalScore >= score )
+				{
+					return true;
+				}
+			}
+		}
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptOnlyOneSkaterLeft( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	
+	GameNet::PlayerInfo* player;
+	Lst::Search< GameNet::PlayerInfo > sh;
+	Lst::Search< GameNet::NewPlayerInfo > new_sh;
+	int num_participants_left;
+	
+	num_participants_left = 0;
+	if( Mdl::Skate::Instance()->GetGameMode()->IsTeamGame())
+	{
+		int i;
+		int total_score[GameNet::vMAX_TEAMS] = {0};
+
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			Mdl::Score* pScore = player->m_Skater->GetScoreObject();
+
+			total_score[player->m_Team] += pScore->GetTotalScore();
+		}
+
+		for( i = 0; i < GameNet::vMAX_TEAMS; i++ )
+		{
+			if( total_score[i] > 0 )
+			{
+				num_participants_left++;
+			}
+		}
+
+		// If we're still waiting to load players fully, this test is invalid
+		if( gamenet_man->FirstNewPlayerInfo( new_sh ))
+		{
+			return false;
+		}
+	}
+	else
+	{
+		int num_players;
+
+		num_players = 0;
+		for( player = gamenet_man->FirstPlayerInfo( sh ); player; player = gamenet_man->NextPlayerInfo( sh ))
+		{
+			if( player->m_Skater )
+			{
+				Mdl::Score *pScore;
+	
+				num_players++;
+				pScore = player->m_Skater->GetScoreObject();
+				int totalScore = pScore->GetTotalScore();
+				if( totalScore > 0 )
+				{
+					num_participants_left++;
+				}
+			}
+		}
+
+		// If we're still waiting to load players fully, this test is invalid
+		if( gamenet_man->FirstNewPlayerInfo( new_sh ))
+		{
+			return false;
+		}
+
+		if( num_players == 1 )
+		{
+			return false;
+		}
+	}
+	
+	if( gamenet_man->OnServer())
+	{
+		if(	( gamenet_man->GetHostMode() == GameNet::vHOST_MODE_AUTO_SERVE ) ||
+			( gamenet_man->GetHostMode() == GameNet::vHOST_MODE_FCFS ))
+		{
+			if( num_participants_left == 0 )
+			{
+				return true;
+			}
+		}
+	}
+	
+	return num_participants_left == 1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterTotalScoreGreaterThan | Detects last score to trigger if script.
+// Example: if SkaterTotalScoreGreaterThan 2000 --do cool stuff-- endif
+// @uparm 1 | Score (int)
+bool ScriptTotalScoreGreaterThan( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	
+	int score;
+	score = s_get_score_from_params( pParams, pScript );
+	Mdl::Score *pScore;
+	pScore = s_get_score_struct( );
+	if ( pScore )
+	{
+		int totalScore = pScore->GetTotalScore();
+		if ( totalScore > score )
+			return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterTotalScoreLessThan | Detects last score to trigger if script.
+// Example: if SkaterTotalScoreLessThan 2000 --do cool stuff-- endif
+// @uparm 1 | Score (int)
+bool ScriptTotalScoreLessThan( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	int score;
+	score = s_get_score_from_params( pParams, pScript );
+	Mdl::Score *pScore;
+	pScore = s_get_score_struct( );
+	if ( pScore )
+	{
+		int totalScore = pScore->GetTotalScore();
+		if ( totalScore < score )
+			return ( true );
+	}
+	return ( false );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterCurrentScorePotGreaterThan | Detects last score to trigger if script.
+// Example: if SkaterCurrentScorePotGreaterThan 2000 --do cool stuff-- endif
+// @uparm 1 | Score (int)
+bool ScriptCurrentScorePotGreaterThan( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	int score;
+	int scale;
+	
+	scale = 1;
+	score = s_get_score_from_params( pParams, pScript );
+	pParams->GetInteger( CRCD(0xb08c5ae8,"point_scale"), &scale );
+	score *= scale;
+
+	Mdl::Score *pScore;
+	pScore = s_get_score_struct( );
+	if ( pScore )
+	{
+		int curScore = pScore->GetScorePotValue();
+		if ( curScore > score )
+			return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script bool | SkaterCurrentScorePotLessThan | Detects last score to trigger if script.
+// Example: if SkaterCurrentScorePotLessThan 2000 --do cool stuff-- endif
+// @uparm 1 | Score (int)
+bool ScriptCurrentScorePotLessThan( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	int score;
+	score = s_get_score_from_params( pParams, pScript );
+	Mdl::Score *pScore;
+	pScore = s_get_score_struct( );
+	if ( pScore )
+	{
+		int curScore = pScore->GetScorePotValue();
+		if ( curScore < score )
+			return true;
+	}
+	return false;
+}
+
+
+// @script | SkaterGetScoreInfo | Adds the parameters ScorePot, TotalScore and LastScoreLanded to the script's parameters.
+bool ScriptSkaterGetScoreInfo( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	Mdl::Score *p_score=s_get_score_struct( );
+	
+	pScript->GetParams()->AddInteger(CRCD(0x95e84391,"ScorePot"),p_score->GetScorePotValue());
+	pScript->GetParams()->AddInteger(CRCD(0xee5b2b48,"TotalScore"),p_score->GetTotalScore());
+	pScript->GetParams()->AddInteger(CRCD(0xea60ac69,"LastScoreLanded"),p_score->GetLastScoreLanded());
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalGreaterThan | Counts the number of goals you have got.
+// @uparm 1.0 | value to check
+bool ScriptGoalsGreaterThan( Script::CStruct *pParams, Script::CScript *pScript )
+{														     
+	float num;
+	if ( pParams->GetFloat( NONAME, &num ) )
+	{
+	
+		Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();	
+		if (pCareer->CountTotalGoalsCompleted() > num)
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GoalsEqualTo | Counts the number of goals you have got. 
+// @uparm 1.0 | number of goals
+bool ScriptGoalsEqualTo( Script::CStruct *pParams, Script::CScript *pScript )
+{  
+	float num;
+	if ( pParams->GetFloat( NONAME, &num ) )
+	{
+	
+		Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();	
+		if (pCareer->CountTotalGoalsCompleted() == num)
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MedalsGreaterThan |  Counts the number of medals you have got.  This 
+// is just the number of levels you have gotten a medal in, so it will be 0 - 3.
+// @uparm 1.0 | number of medals
+bool ScriptMedalsGreaterThan( Script::CStruct *pParams, Script::CScript *pScript )
+{														     
+	float num;
+	if ( pParams->GetFloat( NONAME, &num ) )
+	{
+	
+		Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();	
+		if (pCareer->CountMedals() > num)
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | MedalsEqualTo |  Counts the number of medals you have got.  This 
+// is just the number of levels you have gotten a medal in, so it will be 0 - 3.
+// @uparm 1.0 | number of medals
+bool ScriptMedalsEqualTo( Script::CStruct *pParams, Script::CScript *pScript )
+{														  
+	float num;
+	if ( pParams->GetFloat( NONAME, &num ) )
+	{
+	
+		Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();	
+		if (pCareer->CountMedals() == num)
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ToggleSkaterCamMode | Toggles the camera mode for a given skater
+// @parm int | skater | 
+bool ScriptToggleSkaterCamMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int skater;
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater );
+
+	
+	if (!Mdl::Skate::Instance()->GetGameMode()->IsFrontEnd())
+	{
+		GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+		
+		Obj::CSkater* p_skater = NULL;
+		
+		if (skater == 0)
+		{
+			GameNet::PlayerInfo* player;
+			player = gamenet_man->GetLocalPlayer();
+			if( player )
+			{
+				p_skater = player->m_Skater;
+			}
+		}
+		else
+		{											 
+			p_skater = Mdl::Skate::Instance()->GetSkater( skater );
+		}
+		
+		if( p_skater )
+		{
+			/*
+			if ( p_skater->GetSkaterCam()->mMode < Obj::CSkaterCam::SKATERCAM_NUM_NORMAL_MODES )
+			{
+				p_skater->ToggleCamMode();
+			}
+			*/
+			GetSkaterCameraComponentFromObject(p_skater->GetCamera())->ToggleMode();
+			
+		}
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSkaterID | returns the id of the skater, so that you can run member functions on it from a global script
+// @parm int | skater | Which skater's ID you want to grab
+// The skater's id is returned in objID, check the script for examples
+
+bool ScriptGetSkaterID( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	// in this context, skater really means "viewport"
+	int skaterId;
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterId ) )
+		 pSkater = Mdl::Skate::Instance()->GetSkater( skaterId );
+	else	
+		pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+	
+	if ( pSkater && pSkater->IsLocalClient() )
+	{
+		uint32 id = pSkater->GetID();
+		Dbg_Assert( pScript && pScript->GetParams() );
+		pScript->GetParams()->AddChecksum( "objID", id );
+		// Dbg_Printf( "************** SKATER ID %d\n", id );
+		return true;
+	}
+	Dbg_MsgAssert( 0, ( "Couldn't find specified skater" ) );
+	return false;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetCurrentSkaterID( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Dbg_Assert( pScript->mpObject );
+
+	Obj::CSkater* pSkater = static_cast ( pScript->mpObject.Convert() );
+	if( pSkater && pSkater->IsLocalClient() )
+	{
+		uint32 id = pSkater->GetID();
+		Dbg_Assert( pScript && pScript->GetParams() );
+		pScript->GetParams()->AddChecksum( "objID", id );
+		//Dbg_Printf( "************** SKATER ID %d\n", id );
+		return true;
+	}
+	Dbg_MsgAssert( 0, ( "Couldn't find specified skater" ) );
+	return false;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSkaterCamLerpReductionTimer | 
+// @parmopt float | time | 0.0 | time value
+bool ScriptSetSkaterCamLerpReductionTimer( Script::CStruct *pParams, Script::CScript *pScript )
+{  
+/*	
+	Obj::CSkater* p_skater = static_cast ( pScript->mpObject.Convert() );
+	Dbg_Assert( p_skater );
+
+	Obj::CSkaterCam* p_cam = p_skater->GetSkaterCam();
+	if( p_cam == NULL )
+	{
+		return true;
+	}
+
+	float time = 0.0f;
+	pParams->GetFloat( 0x906b67ba, &time, true );  // time
+
+	p_cam->SetLerpReductionTimer( time );
+*/
+//	printf ("skfuncs %d: SUTUBBBEDDDDDDDDDDDDDDDDDDDDDDDD\n",__LINE__);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlaySkaterCamAnim | Play a camera animation for a given skater 
+// Example usage:
+// script play_photoguy_cam
+//		TRG_G_TS_PhotoGuy:Obj_GetID
+//		PlaySkaterCamAnim skater=0 name=G_TS_CameraStart targetID=objID targetOffset=(0,50,0) positionOffset=(-100, 50, -100)
+//		; camera looks at a point 50 units above the photoguy, camera is fixed to a position 50 units above, 100 units behind, and 100 units to the left
+// endscript
+// @parmopt int | skater | 0 |
+// @parmopt name | skaterId | | find the skater by id, rather than index
+// @flag stop | 
+// @parm name | name | the name of the animation
+// @flag loop | loop the animation
+// @flag focus_skater | 
+// @flag play_hold | 
+// @parmopt int | skippable | | whether the movie is skippable
+// @parmopt name | exitScript | | name of script to run when the movie finishes/is skipped through
+// @parmopt structure | exitParams | | additional parameters to be passed to the exitScript
+// @parmopt name | targetID | | m_id of the object to follow (not the node's name!  see example below) 
+// @parmopt vector | targetOffset | | offset from the specified targetID to look at (in local space, not world space)
+// @parmopt vector | positionOffset | | overrides the camera position with the specified offset from the specified targetID
+bool ScriptPlaySkaterCamAnim( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	if ( pParams->ContainsFlag( CRCD(0x0c567fa2,"use_last_camera") ) )
+	{
+		// used so that we don't go back to the skatercam
+		// when the "want to save" dialog comes up
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		skate_mod->GetMovieManager()->ApplyLastCamera();
+		return true;
+	}
+
+ 	// in this context, skater really means "viewport"
+	int skaterIndex = 0;
+	uint32 skaterId;
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetChecksum( "skaterId", &skaterId, Script::NO_ASSERT ) )
+	{
+		Obj::CObject* pObj = Obj::ResolveToObject( skaterId);
+		Dbg_MsgAssert( pObj && pObj->GetType() == SKATE_TYPE_SKATER, ( "%x is not a skater.", skaterId ) );
+		pSkater = (Obj::CSkater*)pObj;
+	}
+	else
+	{
+		pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterIndex );
+		pSkater = Mdl::Skate::Instance()->GetSkater( skaterIndex );
+	}
+	
+	if ( pSkater )
+	{
+		if ( pSkater->IsLocalClient() )
+		{
+			// The stop flag is read with highest priority.
+			if ( pParams->ContainsFlag( "stop" ) )
+			{
+				Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+				skate_mod->GetMovieManager()->ClearMovieQueue();
+				return true;
+			}
+			else
+			{
+				uint32 movieName = 0;
+				pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+				// GJ:  The camera may not be framed properly if the
+				// skater was scaled up, so the following script will
+				// shut off the scaling on the skater...  restore_skater_camera
+				// will re-enable it when the movie is done
+				Script::RunScript( CRCD(0xdf00bdff,"disable_skater_scaling") );
+
+				// start the movie with the default params...
+				Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+				skate_mod->GetMovieManager()->AddMovieToQueue( movieName, pParams, CRCD(0x66b7dd11,"skatercamanim") );
+			}
+		}
+		else
+		{
+			if ( !pSkater->IsLocalClient() )
+			{
+				printf("this isn't a local skater!\n");
+			}
+		}
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSkaterCamAnimSkippable | set the current anim for a given
+// skater as skippable
+// @parmopt int | skater | 0 | the skater
+// @parmopt name | skaterId | | the id of the skater
+// @parmopt int | name | | the name of the movie to set skippable (otherwise, it will use the currently-playing movie)
+// @parmopt int | skippable | 0 | set to anything greater than 0 to set anim to skippable
+bool ScriptSetSkaterCamAnimSkippable( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// in this context, skater really means "viewport"
+	uint32 skaterId;
+	int skaterIndex = 0;
+
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetChecksum( "skaterId", &skaterId, Script::NO_ASSERT ) )
+	{
+		Obj::CObject* pObj = Obj::ResolveToObject( skaterId);
+		Dbg_MsgAssert( pObj && pObj->GetType() == SKATE_TYPE_SKATER, ( "%x is not a CSkater", skaterId ) );
+		pSkater = (Obj::CSkater*)pObj;
+	}
+	else
+	{
+		pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterIndex );
+		pSkater = Mdl::Skate::Instance()->GetSkater( skaterIndex );
+	}
+		
+	if( pSkater && pSkater->IsLocalClient() )
+	{
+		uint32 movieName = 0;
+		pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+		int skippable = 0;
+		pParams->GetInteger( "skippable", &skippable );
+
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		skate_mod->GetMovieManager()->SetMovieSkippable( movieName, skippable );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSkaterCamAnimShouldPause | changes the pause mode of a skater cam.
+// If the pause mode is set to false, the cam will play even if the game is paused
+// @parmopt name | skaterId | | the id of the skater
+// @parmopt int | skater | 0 | the skater index
+bool ScriptSetSkaterCamAnimShouldPause( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 skaterId;
+	int skaterIndex = 0;
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetChecksum( "skaterId", &skaterId, Script::NO_ASSERT ) )
+	{
+		Obj::CObject* pObj = Obj::ResolveToObject( skaterId);
+		Dbg_MsgAssert( pObj && pObj->GetType() == SKATE_TYPE_SKATER, ( "%x is not a CSkater", skaterId ) );
+		pSkater = (Obj::CSkater*)pObj;
+	}
+	else
+	{
+		pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterIndex );
+		pSkater = Mdl::Skate::Instance()->GetSkater( skaterIndex );
+	}
+
+	if ( pSkater && pSkater->IsLocalClient() )
+	{
+		uint32 movieName = 0;
+		pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+		int should_pause = 0;
+		pParams->GetInteger( "should_pause", &should_pause );
+
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		skate_mod->GetMovieManager()->SetMoviePauseMode( movieName, should_pause );
+	}
+	return true;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCurrentSkaterCamAnimName | returns the name of the current skater cam (if any)
+bool ScriptGetCurrentSkaterCamAnimName( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	uint32 movieName = skate_mod->GetMovieManager()->GetCurrentMovieName();
+	if ( movieName != 0 )
+	{
+		pScript->GetParams()->AddChecksum( CRCD(0xa1dc81f9,"name"), movieName );
+		return true;
+	}
+
+	pScript->GetParams()->AddString( CRCD(0xa1dc81f9,"name"), "none" );
+	return false;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSkaterCamAnimParams | returns the params of the current skater cam 
+bool ScriptGetSkaterCamAnimParams( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 name = 0;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::NO_ASSERT );
+	
+	uint32 skaterId;
+	int skaterIndex = 0;
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetChecksum( "skaterId", &skaterId, Script::NO_ASSERT ) )
+	{
+		Obj::CObject* pObj = Obj::ResolveToObject( skaterId);
+		Dbg_MsgAssert( pObj && pObj->GetType() == SKATE_TYPE_SKATER, ( "%x is not a CSkater", skaterId ) );
+		pSkater = (Obj::CSkater*)pObj;
+	}
+	else
+	{
+		pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterIndex );
+		pSkater = Mdl::Skate::Instance()->GetSkater( skaterIndex );
+	}
+
+	if ( pSkater && pSkater->IsLocalClient() )
+	{
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		Script::CStruct* pMovieParams = skate_mod->GetMovieManager()->GetMovieParams( name );
+		if ( pMovieParams )
+		{
+			pScript->GetParams()->AppendStructure( pMovieParams );
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | SkaterCamAnimFinished | returns true if the anim for the
+// given skater is finished
+// @parmopt int | skater | 0 | the skater
+// @parmopt name | skaterId | | the id of the skater
+// @parmopt int | cam | 1 | the camera number 
+bool ScriptSkaterCamAnimFinished( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// in this context, skater really means "viewport"
+	uint32 skaterId;
+	int skaterIndex = 0;
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetChecksum( "skaterId", &skaterId, Script::NO_ASSERT ) )
+	{
+		Obj::CObject* pObj = Obj::ResolveToObject( skaterId);
+		Dbg_MsgAssert( pObj && pObj->GetType() == SKATE_TYPE_SKATER, ( "%x is not a CSkater", skaterId ) );
+		pSkater = (Obj::CSkater*)pObj;
+	}
+	else
+	{
+		pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterIndex );
+		pSkater = Mdl::Skate::Instance()->GetSkater( skaterIndex );
+	}
+		
+	if( pSkater && pSkater->IsLocalClient() )
+	{
+		uint32 movieName = 0;
+		pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		return skate_mod->GetMovieManager()->IsMovieComplete( movieName );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | SkaterCamAnimHeld | returns true if anim is held
+// @parmopt int | skater | 0 | the skater
+// @parmopt name | skater | | the id of the skater
+bool ScriptSkaterCamAnimHeld( Script::CStruct *pParams, Script::CScript *pScript )
+{	
+	// in this context, skater really means "viewport"
+	uint32 skaterId;
+	int skaterIndex = 0;
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetChecksum( "skaterId", &skaterId, Script::NO_ASSERT ) )
+	{
+		Obj::CObject* pObj = Obj::ResolveToObject( skaterId);
+		Dbg_MsgAssert( pObj && pObj->GetType() == SKATE_TYPE_SKATER, ( "%x is not a CSkater", skaterId ) );
+		pSkater = (Obj::CSkater*)pObj;
+	}
+	else
+	{
+		pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterIndex );
+		pSkater = Mdl::Skate::Instance()->GetSkater( skaterIndex );
+	}
+		
+	if( pSkater && pSkater->IsLocalClient() )
+	{
+		uint32 movieName = 0;
+		pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+		Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+		return skate_mod->GetMovieManager()->IsMovieHeld( movieName );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | KillSkaterCamAnim | 
+// @parmopt int | skater | 0 | the skater
+// @parmopt name | skaterId | | the id of the skater
+// @flag current | kill the current cam anim
+// @flag all | kill all cam anims
+bool ScriptKillSkaterCamAnim( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	bool success = true;
+
+	// in this context, skater really means "viewport"
+	uint32 skaterId;
+	int skaterIndex = 0;
+	Obj::CSkater* pSkater = NULL;
+	if ( pParams->GetChecksum( "skaterId", &skaterId, Script::NO_ASSERT ) )
+	{
+		Obj::CObject* pObj = Obj::ResolveToObject( skaterId);
+		Dbg_MsgAssert( pObj && pObj->GetType() == SKATE_TYPE_SKATER, ( "%x is not a CSkater", skaterId ) );
+		pSkater = (Obj::CSkater*)pObj;
+	}
+	else if( pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skaterIndex ))
+	{
+		pSkater = Mdl::Skate::Instance()->GetSkater( skaterIndex );
+	}
+	else
+	{
+		pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+	}
+		
+	if( pSkater && pSkater->IsLocalClient() )
+	{
+		if ( pParams->ContainsFlag( "current" ) )
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			success = skate_mod->GetMovieManager()->AbortCurrentMovie( false );
+		}
+		else if ( pParams->ContainsFlag( "all" ) )
+		{
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			skate_mod->GetMovieManager()->ClearMovieQueue();
+			success = true;
+		}
+		else
+		{
+			uint32 movieName = 0;
+			pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName, true );
+			Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+			skate_mod->GetMovieManager()->RemoveMovieFromQueue( movieName );
+			success = true;
+		}
+	}
+
+	// GJ:  if the movie queue is empty, it's time to restore the skater
+	// camera (this fixes SK5:TT13239 - "View goals - camera stays on
+	// view goal position instead of returning to skater when unpausing"
+	// or one of the park editor cameras
+	if ( !Mdl::Skate::Instance()->GetMovieManager()->IsRolling() )
+	{
+		if ( Ed::CParkEditor::Instance()->EditingCustomPark() )
+		{
+			Ed::CParkEditor::Instance()->SetAppropriateCamera();	
+		}
+		else
+		{
+			Script::RunScript( CRCD(0x15674315,"restore_skater_camera") );
+		}
+	}
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ReloadSkaterCamAnim | undocumented
+bool ScriptReloadSkaterCamAnim( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	if ( !Config::GotExtraMemory() )
+	{
+		Dbg_Message( "ReloadSkaterCamAnim:  This function only works with the extra debug heap.  (Otherwise, it would fragment the bottom-up heap.)" );
+		return false;
+	}
+
+	uint32 animName;
+	const char* pFileName;
+
+	if ( !pParams->GetChecksum( NONAME, &animName, false ) )
+	{
+		Dbg_Message( "ReloadSkaterCamAnim:  Missing cam anim name.\n" );
+		return false;
+	}
+
+	if ( !pParams->GetText( NONAME, &pFileName, false )	)
+	{
+		Dbg_Message( "ReloadSkaterCamAnim:  Missing cam anim filename.\n" );
+		return false;
+	}
+
+	Dbg_Message( "Reloading movie %08x with file %s...", animName, pFileName );
+
+	// clear any existing movies in the queue, just in case we were in the middle of playing the anim to be replaced
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->GetMovieManager()->ClearMovieQueue();
+
+	// reload the appropriate asset
+	Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+	
+	// on the debug heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+	
+	bool success =  ass_man->ReloadAsset( animName, pFileName, false );
+	
+	Mem::Manager::sHandle().PopContext();
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlayCutscene | Play a cutscene
+bool ScriptPlayCutscene( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	if( pParams->ContainsFlag( "stop" ))
+	{
+		Dbg_MsgAssert( 0, ( "stop parameter doesn't work with PlayCutscene" ) );
+	}
+	
+//	uint32 movieName = 0;
+//	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+	// all cutscenes are named as "cutscene"...  this will
+	// help me enforce that there is only one cutscene playing
+	// at any given time...
+	uint32 movieName = CRCD(0xf5012f52,"cutscene");
+
+	// We don't want any messages showing through the cutscene
+	Script::RunScript("destroy_goal_panel_messages");
+
+	// start the movie with the default params...
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->GetMovieManager()->AddMovieToQueue( movieName, pParams, CRCD(0xf5012f52,"cutscene") );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | HasMovieStarted | return true if the video of a movie has started 
+// (after loading data)
+bool ScriptHasMovieStarted( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	return skate_mod->GetMovieManager()->HasMovieStarted();
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsMovieQueued | return true if there are any movies in the queue,
+// regardless of whether the video has started
+bool ScriptIsMovieQueued( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	return skate_mod->GetMovieManager()->IsMovieQueued();
+}
+   
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlayMovingObjectAnim | Play an scene-based animation (earthquake, for instance)
+bool ScriptPlayMovingObjectAnim( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	if( pParams->ContainsFlag( "stop" ))
+	{
+		Dbg_MsgAssert( 0, ( "stop parameter doesn't work with PlayMovingObjectAnim" ) );
+	}
+	
+	uint32 movieName = 0;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+	Dbg_Message( "playmovingobjectanim %s", Script::FindChecksumName(movieName) );
+
+	// start the movie with the default params...
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->GetObjectAnimManager()->AddMovieToQueue( movieName, pParams, CRCD(0x12743edb,"objectanim") );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetMovingObjectAnimSkippable | set the current movie as skippable
+// @parmopt int | name | | the name of the movie to set skippable (otherwise, it will use the currently-playing movie)
+// @parmopt int | skippable | 0 | set to anything greater than 0 to set anim to skippable
+bool ScriptSetMovingObjectAnimSkippable( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 movieName = 0;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+	int skippable = 0;
+	pParams->GetInteger( "skippable", &skippable );
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->GetObjectAnimManager()->SetMovieSkippable( movieName, skippable );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetMovingObjectAnimShouldPause | changes the pause mode of a skater cam.
+// If the pause mode is set to false, the cam will play even if the game is paused
+bool ScriptSetMovingObjectAnimShouldPause( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 movieName = 0;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+	int should_pause = 0;
+	pParams->GetInteger( "should_pause", &should_pause );
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->GetObjectAnimManager()->SetMoviePauseMode( movieName, should_pause );
+	
+	return true;
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | MovingObjectAnimFinished | returns true if the anim for the
+// given skater is finished
+// @parm int | skater | the skater
+// @parmopt int | cam | 1 | the camera number 
+bool ScriptMovingObjectAnimFinished( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 movieName = 0;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	return skate_mod->GetObjectAnimManager()->IsMovieComplete( movieName );
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | MovingObjectAnimHeld | returns true if anim is held
+// @parm int | skater | the skater
+bool ScriptMovingObjectAnimHeld( Script::CStruct *pParams, Script::CScript *pScript )
+{	
+	uint32 movieName = 0;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName );
+		
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	return skate_mod->GetObjectAnimManager()->IsMovieHeld( movieName );
+}
+
+/******************************************************************/
+/*																  */
+/*                                                                */
+/******************************************************************/
+
+// @script | KillMovingObjectAnim | 
+// @parmopt int | skater | 0 | the skater
+// @flag current | kill the current cam anim
+// @flag all | kill all cam anims
+bool ScriptKillMovingObjectAnim( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CMovieManager* pObjectAnimManager = skate_mod->GetObjectAnimManager(); 
+
+	if ( pParams->ContainsFlag( "current" ) )
+	{
+		return pObjectAnimManager->AbortCurrentMovie( false );
+	}
+	else if ( pParams->ContainsFlag( "all" ) )
+	{
+		pObjectAnimManager->ClearMovieQueue();
+		return true;
+	}
+	else
+	{
+		uint32 movieName = 0;
+		pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &movieName, true );
+		pObjectAnimManager->RemoveMovieFromQueue( movieName );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ReloadMovingObjectAnim | undocumented
+bool ScriptReloadMovingObjectAnim( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	if ( !Config::GotExtraMemory() )
+	{
+		Dbg_Message( "ReloadMovingObjectAnim:  This function only works with the extra debug heap.  (Otherwise, it would fragment the bottom-up heap.)" );
+		return false;
+	}
+
+	uint32 animName;
+	const char* pFileName;
+
+	if ( !pParams->GetChecksum( NONAME, &animName, false ) )
+	{
+		Dbg_Message( "ReloadMovingObjectAnim:  Missing cam anim name.\n" );
+		return false;
+	}
+
+	if ( !pParams->GetText( NONAME, &pFileName, false )	)
+	{
+		Dbg_Message( "ReloadMovingObjectAnim:  Missing cam anim filename.\n" );
+		return false;
+	}
+
+	Dbg_Message( "Reloading movie %08x with file %s...", animName, pFileName );
+
+	// clear any existing movies in the queue, just in case we were in the middle of playing the anim to be replaced
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	skate_mod->GetObjectAnimManager()->ClearMovieQueue();
+
+	// reload the appropriate asset
+	Ass::CAssMan * ass_man = Ass::CAssMan::Instance();
+	
+	// on the debug heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+	
+	bool success = ass_man->ReloadAsset( animName, pFileName, false );
+	
+	Mem::Manager::sHandle().PopContext();
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// @script | SkaterDebugOn | sets debug to on
+bool ScriptSkaterDebugOn(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	Obj::DebugSkaterScripts=true;
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SkaterDebugOff | sets debug to off
+bool ScriptSkaterDebugOff(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	Obj::DebugSkaterScripts=false;
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | VibrationIsOn | returns true if vibration for the specified
+// player is on
+// @uparm 0 | the player 
+bool ScriptVibrationIsOn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && imp_controller_preferences[i].VibrationOn;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | VibrationOn | turns on vibration for the specified player
+// @uparm 0 | player
+bool ScriptVibrationOn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && iSetVibration(i,true);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | VibrationOff | turns off vibration for the specified player
+// @uparm 0 | the player 
+bool ScriptVibrationOff(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && iSetVibration(i,false);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AutoKickIsOn | returns true if autokick is on for the specified skater
+// @uparmopt 0 | skater
+bool ScriptAutoKickIsOn(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && imp_controller_preferences[i].AutoKickOn;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AutoKickOn | turns on autokick for the specified player
+// @uparmopt 0 | the skater
+bool ScriptAutoKickOn(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && iSetAutoKick(i,true);
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AutoKickOff | turns off autokick for the specified player
+// @uparmopt 0 | skater
+bool ScriptAutoKickOff(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && iSetAutoKick(i,false);
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SpinTapsAreOn | returns true if spin taps are on
+// @uparmopt 0 | skater
+bool ScriptSpinTapsAreOn(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && imp_controller_preferences[i].SpinTapsOn;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SpinTapsOn | turns on spin taps for the specified skater
+// @uparmopt 0 | skater
+bool ScriptSpinTapsOn(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && iSetSpinTaps(i,true);
+	return true;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SpinTapsOff | turns off spin taps for the specified skater
+// @uparmopt 0 | skater
+bool ScriptSpinTapsOff(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	int i=0;
+	pParams->GetInteger(NONAME,&i);
+	Dbg_MsgAssert(i>=0 && iSetSpinTaps(i,false);
+	return true;
+}	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCurrentProDisplayInfo | 
+bool ScriptGetCurrentProDisplayInfo(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile();
+
+	pScript->GetParams()->AddComponent( Script::GenerateCRC( "string" ), ESYMBOLTYPE_STRING, pSkaterProfile->GetDisplayName());
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetPlayerAppearance | sets the player appearance 
+// @parmopt int | player | 0 | index of player whose appearance you want to edit
+// @parmopt name | appearance_structure | | the name of the profile
+bool ScriptSetPlayerAppearance(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( player );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	
+	Script::CStruct* pAppearanceStruct;
+	pParams->GetStructure( "appearance_structure", &pAppearanceStruct, Script::ASSERT );
+	pAppearance->Load( pAppearanceStruct );
+	
+	return true;
+}
+					
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetPlayerFacePoints | Gets the player face points and puts it in the structure "current_face_points"
+// @parm int | player | 0 | index of player
+bool ScriptGetPlayerFacePoints(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( player );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	
+	Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+	Dbg_MsgAssert( pFaceTexture, ( "Appearance doesn't have a face texture1" ) );
+	Dbg_MsgAssert( pFaceTexture->IsValid(), ( "Face texture has not been initialized with valid texture data" ) );
+
+	Script::CStruct *pFacePointsStruct = NULL;
+
+	bool make_new_structure = false;
+	if (!pScript->GetParams()->GetStructure( CRCD(0x12e87395,"current_face_points"), &pFacePointsStruct ))
+	{
+		pFacePointsStruct = new Script::CStruct;
+		make_new_structure = true;
+	}
+
+	Nx::SFacePoints theFacePoints;
+	theFacePoints = pFaceTexture->GetFacePoints( );
+	if ( !Nx::SetFacePointsStruct( theFacePoints, pFacePointsStruct ) )
+	{
+		Script::PrintContents( pParams );
+		Dbg_MsgAssert( 0, ("Couldn't set face points to structure") );
+	}
+
+	if (make_new_structure)
+	{
+		pScript->GetParams()->AddStructure( CRCD(0x12e87395,"current_face_points"), pFacePointsStruct );
+		delete pFacePointsStruct;
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetPlayerFacePoints |
+// @parmopt struct | face_points | face points structure
+bool ScriptSetPlayerFacePoints(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( player );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	
+	Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+	Dbg_MsgAssert( pFaceTexture, ( "Appearance doesn't have a face texture1" ) );
+	Dbg_MsgAssert( pFaceTexture->IsValid(), ( "Face texture has not been initialized with valid texture data" ) );
+
+	Nx::SFacePoints theFacePoints;
+	Script::CStruct *pFacePointsStruct;
+	if (!pParams->GetStructure("face_points", &pFacePointsStruct))
+	{
+		pFacePointsStruct = pParams;
+	}
+	if ( !Nx::GetFacePointsStruct( theFacePoints, pFacePointsStruct ) )
+	{
+		Script::PrintContents( pParams );
+		Dbg_MsgAssert( 0, ("Couldn't read face points from structure") );
+	}
+	pFaceTexture->SetFacePoints( theFacePoints );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetPlayerFaceTexture |
+bool ScriptSetPlayerFaceTexture(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( player );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	
+	const char* pFaceName;
+	pParams->GetText( CRCD(0x7d99f28d,"texture"), &pFaceName, Script::ASSERT );
+
+	// by default, LoadFace will prepend the filename with "images\\"
+	// however, the neversoft skater images are located in "textures\\"
+	// so the following flag will tell LoadFace not to prepend the
+	// filename
+	bool fullPathIncluded = pParams->ContainsFlag( CRCD(0x54c2e57c,"FullPathIncluded") );
+
+	Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+	Dbg_MsgAssert( pFaceTexture, ( "Appearance doesn't have a face texture2" ) );
+	pFaceTexture->LoadFace( pFaceName, fullPathIncluded );
+
+	Dbg_MsgAssert( pFaceTexture->IsValid(), ( "Face texture should now be considered valid" ) );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetPlayerFaceOverlayTexture |
+bool ScriptSetPlayerFaceOverlayTexture(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( player );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	
+	const char* pOverlayTextureName;
+	pParams->GetText( CRCD(0x7d99f28d,"texture"), &pOverlayTextureName, Script::ASSERT );
+
+	Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+	Dbg_MsgAssert( pFaceTexture, ( "Appearance doesn't have a face texture3" ) );
+	Dbg_MsgAssert( pFaceTexture->IsValid(), ( "Face texture is not valid" ) );
+
+	pFaceTexture->SetOverlayTextureName( pOverlayTextureName );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ClearPlayerFaceTexture |
+bool ScriptClearPlayerFaceTexture(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef __PLAT_NGPS__
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( player );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	Dbg_Assert( pAppearance );
+
+	Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+	Dbg_MsgAssert( pFaceTexture, ( "Appearance doesn't have a face texture4" ) );
+	pFaceTexture->SetValid( false );
+#endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlayerFaceIsValid | returns whether the player has already loaded or downloaded a face
+bool ScriptPlayerFaceIsValid(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( player );
+	Gfx::CModelAppearance* pAppearance = pSkaterProfile->GetAppearance();
+	Dbg_Assert( pAppearance );
+
+	Gfx::CFaceTexture* pFaceTexture = pAppearance->GetFaceTexture();
+	//Dbg_MsgAssert( pFaceTexture, ( "Appearance doesn't have a face texture5" ) );
+    if ( pFaceTexture )
+    {
+        return pFaceTexture->IsValid();
+    }
+    else
+    {
+        //Appearance doesn't have a face texture
+        return false;
+    }
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SelectCurrentSkater | sets the current profile to the specified skater
+// @parmopt name | name | | the name of the profile
+// @uparmopt name | name of the profile
+bool ScriptSelectCurrentSkater(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	uint32 profileName;
+	if ( !pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &profileName, false ) )
+	{
+		pParams->GetChecksum( NONAME, &profileName, true );
+	}
+
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager=Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile();
+
+	// remember old checksum to see if the data has changed
+	if ( pSkaterProfile->GetSkaterNameChecksum() == profileName )
+	{
+		// no change
+		return false;
+	}
+	
+	pPlayerProfileManager->ApplyTemplateToCurrentProfile( profileName );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CareerStartLevel | Called at the start of the level, and whenever
+// the level is restarted (by a retry, for example).  This will set the level
+// number (unless you pass -1, in which case it uses the "current level"). This
+// will store the values of the career goals, and the level flags, at the start
+// of the level, so at the end of the level, we will be able to see if we have
+// just got a particular goal, and play appropriate rewards. Currently you
+// should not have to mess with this.  I've added appropriate CareerStartLevel 
+// commands in the level loading script in levels.q.  I've also added (in the
+// C++ code) what amounts to "CareerStartLevel level = -1" to the "RestartLevel"
+// function in skate.cpp.  This gets called whenever you restart a level (via
+// a retry). 
+// @parm int | level | the level to start
+bool ScriptCareerStartLevel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int level; 		
+	pParams->GetInteger("level", &level, true);  		// Note we assert if "level" is missing
+	Mdl::Skate::Instance()->GetCareer()->StartLevel(level);
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CareerLevelIs | This is true if the current level number, as specified
+// in the last call to CareerStartLevel, matches the value passed, for example: 
+// if CareerLevelIs 1 
+//   printf "Foundry!" 
+// endif
+// @uparm 1 | level to check
+bool ScriptCareerLevelIs(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int level; 		
+	pParams->GetInteger(NONAME, &level, true);	// Note we assert if the level value is missing
+	
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	return pCareer->GetLevel()==level;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetRecordText | 
+// @parmopt int | level | | level num (default is current level)
+bool ScriptGetRecordText(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int level; 	  
+	  
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	if (!pParams->GetInteger("level", &level, false))	
+	{
+		level = pCareer->GetLevel();
+	}
+			  
+	Mdl::Skate::Instance()->GetRecordsText(level);	
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UpdateRecords | 
+bool ScriptUpdateRecords(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	Mdl::Skate::Instance()->UpdateRecords();	
+	return true;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CareerReset | Resets all the career info back to as if you've
+// just started a new career.  Note that this also sets the current level
+// to 0 (as you'd not have unlocked any levels).  So if you want to use
+// this in the middle of playing a  level, then you'd better add a "StartLevel
+// level = ???" to set the correct level. See ResetLevelGoals for more
+bool ScriptCareerReset(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	pCareer->Reset();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetGoal | Sets the goal to the "got" state.  All this does
+// is set a flag, anything else, like playing sounds, should be done in script
+// @parm int | goal | the goal num
+bool ScriptSetGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int goal; 		
+	pParams->GetInteger("goal", &goal, true);  		// Note we assert if "goal" is missing
+	
+
+	
+	// if we are not in career mode, then exit  		   
+	if (!Mdl::Skate::Instance()->GetGameMode()->IsTrue( CRCD(0x1ded1ea4,"is_career") ) && !Mdl::Skate::Instance()->GetGameMode()->IsFrontEnd() )
+	{
+		return true;
+	}
+	
+	
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	pCareer->SetGoal(goal);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnSetGoal | Clears a particular goal flag
+// @parm int | goal | goal flag to clear
+bool ScriptUnSetGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int goal; 		
+	pParams->GetInteger("goal", &goal, true);  		// Note we assert if "goal" is missing
+	
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	pCareer->UnSetGoal(goal);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetGoal | Get the state of a goal.  Will be "true" if you have
+// got the goal, either in this session, or in some previous session. For
+// example:  
+// if GetGoal goal = GOAL_SKATE  ; if got SKATE, then do nothing here 
+// else 
+//   trigger skater letters 
+// endif 
+// The primary purpose of this function is the modify the functionality
+// of the startup script, to stop things from being created, and to modify
+// the intro camera. 
+// @parm int | goal | the goal to check
+bool ScriptGetGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int goal; 		
+	pParams->GetInteger("goal", &goal, true);  		// Note we assert if "goal" is missing
+	
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	return pCareer->GetGoal(goal);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | JustGotGoal | Is "true" if you got this goal in the current
+// session (still valid if the session has just ended). For example: 
+// if JustGotoGoal goal = GOAL_SKATE 
+//  LaunchPanelMessage "Yay! You got the skate letters" 
+// endif 
+// The primary purpose if to play one-time congratulatory sequences at
+// the end of the level, in the end-level script.  This flag will only
+// be true during the session when you actually get the goal
+// @parm int | goal | goal th check
+bool ScriptJustGotGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int goal; 		
+	pParams->GetInteger("goal", &goal, true);  		// Note we assert if "goal" is missing
+	
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	return pCareer->JustGotGoal(goal);   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetFlag | similar to setgoal.  there are 32 flags per level,
+// numbered 0 - 31.
+// @parm int | flag | the flag to set
+bool ScriptSetFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int flag; 		
+	pParams->GetInteger("flag", &flag, true);  		// Note we assert if "flag" is missing
+	
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	pCareer->SetFlag(flag);
+	
+	if (flag==Script::GetInt("GOAL_DECK"))
+	{
+		//Front::GetNewDeck();
+	}	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnSetFlag | similar to unsetgoal.  there are 32 flags per level,
+// numbered 0 - 31.
+// @parm int | flag | the flag to unset
+// @parmopt int | level | | level number
+bool ScriptUnSetFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int flag; 		
+	pParams->GetInteger("flag", &flag, true);  		// Note we assert if "flag" is missing
+	
+
+	int level = -1;
+	pParams->GetInteger( "level", &level, Script::NO_ASSERT );
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+
+	pCareer->UnSetFlag(flag, level);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetFlag | similar to GetGoal.  gets the state of the flag
+// @parm int | flag | the flag to get
+// @parmopt int | level | | the level number (for level specific flags).  if
+// no level number is given, it will use the current level number
+bool ScriptGetFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int flag;
+	pParams->GetInteger("flag", &flag, true);  		// Note we assert if "flag" is missing
+	
+	int level = 0;
+	pParams->GetInteger( "level", &level, Script::NO_ASSERT );
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+	if ( level )
+		return pCareer->GetFlag( flag, level );
+
+	return pCareer->GetFlag( flag );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | JustGotFlag | similar to the JustGotGoal command.  returns true
+// if we just got this flag
+// @parm int | flag | the flag to check
+bool ScriptJustGotFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int flag; 		
+	pParams->GetInteger("flag", &flag, true);  		// Note we assert if "flag" is missing
+	
+
+	Obj::CSkaterCareer* pCareer = Mdl::Skate::Instance()->GetCareer();
+	return pCareer->JustGotFlag(flag);	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetGlobalFlag | sets a global flag
+// @parm int | flag | the global flag to set
+bool ScriptSetGlobalFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef __NOPT_ASSERT__
+	if (!pParams->ContainsComponentNamed("flag"))
+	{
+		printf("Call to SetGlobalFlag has no flag (or that flag is undefined.\n%s", pScript->GetScriptInfo());
+		Dbg_Assert(false);
+	}
+#endif
+
+	int flag; 		
+	pParams->GetInteger(CRCD(0x2e0b1465, "flag"), &flag, true);  		// Note we assert if "flag" is missing
+	
+	s_get_career(flag,pParams)->SetGlobalFlag(flag);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnSetGlobalFlag | unsets a global flag
+// @parm int | flag | the flag to unset
+bool ScriptUnSetGlobalFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef __NOPT_ASSERT__
+	if (!pParams->ContainsComponentNamed("flag"))
+	{
+		printf("Call to UnSetGlobalFlag has no flag (or that flag is undefined.\n%s", pScript->GetScriptInfo());
+		Dbg_Assert(false);
+	}
+#endif
+
+	int flag; 		
+	pParams->GetInteger(CRCD(0x2e0b1465, "flag"), &flag, true);  		// Note we assert if "flag" is missing
+	
+
+	
+	s_get_career(flag,pParams)->UnSetGlobalFlag(flag);
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetGlobalFlag | gets a global flag
+// @parm int | flag | the flag to get
+bool ScriptGetGlobalFlag(Script::CStruct *pParams, Script::CScript *pScript)
+{
+#ifdef __NOPT_ASSERT__
+	if (!pParams->ContainsComponentNamed("flag"))
+	{
+		printf("Call to GetGlobalFlag has no flag (or that flag is undefined.\n%s", pScript->GetScriptInfo());
+		Dbg_Assert(false);
+	}
+#endif
+	
+	int flag; 		
+	pParams->GetInteger(CRCD(0x2e0b1465, "flag"), &flag, true);  		// Note we assert if "flag" is missing
+	
+	return Mdl::Skate::Instance()->GetCareer()->GetGlobalFlag(flag);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ProfileEquals | tells us whether the skater matches the given
+// criteria.  example: 
+// if ProfileEquals is_pro
+// @flag is_pro | check if this is a pro skater
+// @flag is_custom | check if this is a custom skater
+// @parmopt name | is_named | | check if the skater has the specified name
+// @parmopt name | stance | | check for this stance
+bool ScriptProfileEquals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// look for the specified skater (0 by default)
+	Obj::CSkater* pSkater;
+	
+	// if this function is being called through
+	// a skater script, then use the specified skater...
+	if( pScript && pScript->mpObject && pScript->mpObject->GetType() == SKATE_TYPE_SKATER )	
+	{
+		pSkater = (Obj::CSkater *) pScript->mpObject.Convert();
+		return pSkater->SkaterEquals( pParams );
+	}
+
+	if ( !Mdl::Skate::Instance()->IsMultiplayerGame() || Mdl::Skate::Instance()->GetGameMode()->IsFrontEnd() )
+	{
+		Dbg_MsgAssert( 0, ( "ProfileEquals should only be called from a skater script in a multiplayer game." ) );
+		return false;
+	}
+
+	// otherwise, get the first skater and check him
+	pSkater = Mdl::Skate::Instance()->GetSkater( 0 );
+	if( pSkater == NULL )
+	{
+		return false;
+	}
+
+	return pSkater->SkaterEquals( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsCareerMode | returns true if the current game mode is
+// career mode
+bool ScriptIsCareerMode(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+
+	return Mdl::Skate::Instance()->GetGameMode()->IsTrue( CRCD(0x1ded1ea4,"is_career") );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ClearScoreGoals | clears all the score goals
+bool ScriptClearScoreGoals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	Mdl::Skate::Instance()->ClearScoreGoals();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetScoreGoal | used to add a new score goal.  examples: 
+// SetScoreGoal  score = 1200000 goalscript = Got_SickScore goal=GOAL_SICKSCORE 
+// SetScoreGoal    score = 30000 goalscript = Got_HighScore goal=GOAL_HIGHSCORE
+// @parm int | score | the score value for this goal
+// @parm name | goalscript | the script to run 
+// @parm int | goal | goal number
+bool ScriptSetScoreGoal(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int score;
+	pParams->GetInteger("score", &score, true);
+	uint32	script;
+	pParams->GetChecksum("goalscript", &script, true);
+	int	goal;
+	pParams->GetInteger("goal", &goal, true);
+
+	
+	// Note, for now we are just using the goal number as an index into the table
+	// so long as we have enough entries, we will be fine
+	Mdl::Skate::Instance()->SetScoreGoalScore(goal,score);
+	Mdl::Skate::Instance()->SetScoreGoalScript(goal,script);
+	Mdl::Skate::Instance()->SetScoreGoalGoal(goal,goal);
+	
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EndRun | This command will end the current run (same as
+// used by the "end run" selection in the pause menu)
+bool ScriptEndRun(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	Mdl::Skate::Instance()->EndRun();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ShouldEndRun | if "end run" was selected from the menu
+// of the end conditions for this game mode have been met
+bool ScriptShouldEndRun(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	// if "end run" was selected from the menu or the
+	// end conditions for this game mode have been met
+	return ( Mdl::Skate::Instance()->EndRunSelected() || Mdl::Skate::Instance()->GetGameMode()->EndConditionsMet() );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InitializeSkaters | 
+bool ScriptInitializeSkaters(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+
+	for ( unsigned int i = 0; i < Mdl::Skate::Instance()->GetNumSkaters(); i++)
+	{
+		Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater(i);
+		Dbg_Assert( pSkater );
+		if( pSkater->IsLocalClient())
+		{
+			pSkater->Reset();
+		}
+		Mdl::Skate::Instance()->HideSkater( pSkater, false );
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EndRunSelected | true if "end run" was selected from the menu
+bool ScriptEndRunSelected(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	// if "end run" was selected from the menu
+	return Mdl::Skate::Instance()->EndRunSelected();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AllSkatersAreIdle | true if all skaters are idle
+bool ScriptAllSkatersAreIdle(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return Mdl::Skate::Instance()->SkatersAreIdle();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FirstTrickStarted | should only be called in horse mode.  
+bool ScriptFirstTrickStarted(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return Mdl::Skate::Instance()->FirstTrickStarted();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FirstTrickCompleted | should only be called in horse mode
+bool ScriptFirstTrickCompleted(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return Mdl::Skate::Instance()->FirstTrickCompleted();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CalculateFinalScores | final scores for all skaters on server
+bool ScriptCalculateFinalScores(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	
+
+	if( gamenet_man->OnServer())
+	{
+		Mdl::Skate::Instance()->SendScoreUpdates( true );
+		return true;
+	}
+
+	return gamenet_man->HaveReceivedFinalScores();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ReinsertSkaters | adds all skaters to the current world
+bool ScriptReinsertSkaters(Script::CStruct *pParams, Script::CScript *pScript)
+{
+    // For now, this is commented out, until I can get the
+    // skin model functionality over into the new CModel class.
+	
+	// synchronous, no need to pass through net msgs
+	// add the skaters back to the world
+	
+	for ( uint32 i = 0; i < Mdl::Skate::Instance()->GetNumSkaters(); i++ )
+	{   
+		Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater( i );
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterHeap(pSkater->GetHeapIndex()));
+		pSkater->AddToCurrentWorld();
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnhookSkaters | Unhook the skaters from the world, so it can be unloaded
+bool ScriptUnhookSkaters(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	for ( uint32 i = 0; i < Mdl::Skate::Instance()->GetNumSkaters(); i++ )
+	{   
+		Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater( i );
+		pSkater->RemoveFromCurrentWorld();
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ApplySplitScreenOptions | applies various split screen prefs - 
+// game type, time limit, etc.
+bool ScriptApplySplitScreenOptions(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Prefs::Preferences* pPreferences;
+
+	pPreferences = Mdl::Skate::Instance()->GetSplitScreenPreferences();
+	Dbg_Assert(pPreferences);
+
+	Script::CStruct* pStructure;
+	uint32 checksum;
+
+	// change the game type
+	pStructure = pPreferences->GetPreference( Script::GenerateCRC("game_type") );
+	Dbg_Assert(pStructure);
+	pStructure->GetChecksum( "checksum", &checksum, true );
+	Mdl::Skate::Instance()->GetGameMode()->LoadGameType( checksum );
+
+	int time_limit = pPreferences->GetPreferenceValue( Script::GenerateCRC("time_limit"), Script::GenerateCRC("time") );
+
+	if ( Mdl::Skate::Instance()->GetGameMode()->IsTrue( "is_horse" ) )
+	{
+		time_limit = pPreferences->GetPreferenceValue( Script::GenerateCRC("horse_time_limit"), Script::GenerateCRC("time") );
+		Mdl::Skate::Instance()->GetHorse()->SetWord( pPreferences->GetPreferenceString(  Script::GenerateCRC("horse_word"), Script::GenerateCRC("ui_string") ) );
+
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		pTempStructure->AddComponent( Script::GenerateCRC("default_time_limit"), ESYMBOLTYPE_INTEGER, time_limit );
+		Mdl::Skate::Instance()->GetGameMode()->OverrideOptions( pTempStructure );		
+		Mdl::Skate::Instance()->SetTimeLimit( time_limit );
+		delete pTempStructure;
+
+		//Mdl::Skate::Instance()->SetShadowMode( Gfx::vDETAILED_SHADOW );
+	}
+	else
+	{
+		//Mdl::Skate::Instance()->SetShadowMode( Gfx::vSIMPLE_SHADOW );
+	}
+	
+	/*if( Mdl::Skate::Instance()->GetGameMode()->IsTrue( "is_king" ))
+	{
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		Script::CArray* pArray = new Script::CArray;
+		Script::CopyArray(pArray,Script::GetArray("targetScoreArray"));
+		Script::CStruct* pSubStruct = pArray->GetStructure(0);
+		Dbg_Assert(pSubStruct);
+		pSubStruct->AddComponent(Script::GenerateCRC("score"),ESYMBOLTYPE_INTEGER, (int) Tmr::Seconds( time_limit ));
+		pTempStructure->AddComponent( Script::GenerateCRC("default_time_limit"), ESYMBOLTYPE_INTEGER, (int) 0 );
+		pTempStructure->AddComponent( Script::GenerateCRC("victory_conditions"), pArray );
+		Mdl::Skate::Instance()->GetGameMode()->OverrideOptions( pTempStructure );
+		Mdl::Skate::Instance()->SetTimeLimit( 0 );
+		delete pTempStructure;
+	}
+	else
+	{
+		// if the time limit is fixed, then don't override it
+		if ( !Mdl::Skate::Instance()->GetGameMode()->IsTimeLimitConfigurable() )
+		{
+			time_limit = Mdl::Skate::Instance()->GetGameMode()->GetTimeLimit();
+		}
+
+		Script::CStruct* pTempStructure = new Script::CStruct;
+		pTempStructure->AddComponent( Script::GenerateCRC("default_time_limit"), ESYMBOLTYPE_INTEGER, time_limit );
+		Mdl::Skate::Instance()->GetGameMode()->OverrideOptions( pTempStructure );		
+		Mdl::Skate::Instance()->SetTimeLimit( time_limit );
+		delete pTempStructure;
+	}
+	
+//	printf("Applying time limit\n");
+//	pTempStructure->PrintContents();
+
+	// update all the skaters' stats
+	for ( unsigned int i = 0; i < Mdl::Skate::Instance()->GetNumSkaters(); i++)
+	{
+		Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetSkater(i);
+		Dbg_Assert( pSkater );
+
+		Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile( pSkater->GetID() );
+		pSkater->UpdateStats( pSkaterProfile );
+	}*/
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartCompetition | 
+// @parm float | gold | 
+// @parm float | silver | 
+// @parm float | bronze | 
+// @parm float | gold_score |
+// @parm float | silver_score | 
+// @parm float | bronze_score | 
+// @parm float | bail | 
+bool ScriptStartCompetition(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	
+//	float bronze, float silver, float gold, float bronze_score, float silver_score, float gold_score, float bail)
+	float	gold,silver,bronze;
+	float	gold_score,silver_score,bronze_score;
+	float 	bail;
+	
+	if (!pParams->GetFloat("gold",&gold))
+	{
+		Dbg_MsgAssert(0,("\n%s\nStartCompetition missing 'gold' parameter",pScript->GetScriptInfo()));		
+	}
+	
+	if (!pParams->GetFloat("silver",&silver))
+	{
+		Dbg_MsgAssert(0,("\n%s\nStartCompetition missing 'silver' parameter",pScript->GetScriptInfo()));		
+	}
+		
+	if (!pParams->GetFloat("bronze",&bronze))
+	{
+		Dbg_MsgAssert(0,("\n%s\nStartCompetition missing 'bronze' parameter",pScript->GetScriptInfo()));		
+	}
+	if (!pParams->GetFloat("gold_score",&gold_score))
+	{
+		Dbg_MsgAssert(0,("\n%s\nStartCompetition missing 'gold_score' parameter",pScript->GetScriptInfo()));		
+	}
+	
+	if (!pParams->GetFloat("silver_score",&silver_score))
+	{
+		Dbg_MsgAssert(0,("\n%s\nStartCompetition missing 'silver_score' parameter",pScript->GetScriptInfo()));		
+	}
+		
+	if (!pParams->GetFloat("bronze_score",&bronze_score))
+	{
+		Dbg_MsgAssert(0,("\n%s\nStartCompetition missing 'bronze_score' parameter",pScript->GetScriptInfo()));		
+	}
+	if (!pParams->GetFloat("bail",&bail))
+	{
+		Dbg_MsgAssert(0,("\n%s\nStartCompetition missing 'bail' parameter",pScript->GetScriptInfo()));		
+	}
+	Script::CStruct* pExtraParams = NULL;
+	pParams->GetStructure( CRCD(0xa61dc9a4,"extra_params"), &pExtraParams, Script::NO_ASSERT );
+
+	// Need to use SkaterInfoHeap, as new strings can get created, which can fragment memory 									   
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());	
+	if ( pExtraParams )
+		Mdl::Skate::Instance()->GetCompetition()->EditParams( pExtraParams );
+	Mdl::Skate::Instance()->GetCompetition()->StartCompetition(bronze, silver, gold, bronze_score, silver_score, gold_score, bail);
+	Mem::Manager::sHandle().PopContext();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartCompetitionRun | 
+bool ScriptStartCompetitionRun(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mdl::Skate::Instance()->GetCompetition()->StartRun();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EndCompetitionRun | 
+bool ScriptEndCompetitionRun(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();						   
+	Mdl::Score *pScore=pSkater->GetScoreObject();
+	int score = pScore->GetTotalScore();
+
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+	Mdl::Skate::Instance()->GetCompetition()->EndRun(score);
+	Mdl::Skate::Instance()->GetCompetition()->SetupJudgeText();
+	Mem::Manager::sHandle().PopContext();
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsTopJudge | true if the specified judge is the top judge
+// @uparm 1 | judge num
+bool ScriptIsTopJudge(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+	int judge;
+	pParams->GetInteger(NONAME,&judge);
+
+	
+	return Mdl::Skate::Instance()->GetCompetition()->IsTopJudge(judge);
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PlaceIs | true if the comp place is equal to the specified place
+// @uparm 1 | place
+bool ScriptPlaceIs(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int place;
+	pParams->GetInteger(NONAME,&place);
+
+	
+	return Mdl::Skate::Instance()->GetCompetition()->PlaceIs(place);
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RoundIs | true if the round equals the specified round
+// @uparm 1 | round
+bool ScriptRoundIs(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int round;
+	pParams->GetInteger(NONAME,&round);
+
+	
+	return Mdl::Skate::Instance()->GetCompetition()->RoundIs(round);
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EndCompetition | 
+bool ScriptEndCompetition(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterInfoHeap());
+	Mdl::Skate::Instance()->GetCompetition()->EndCompetition();
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CompetitionEnded | true if the comp has ended
+bool ScriptCompetitionEnded(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return Mdl::Skate::Instance()->GetCompetition()->CompetitionEnded();
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartHorse |
+bool ScriptStartHorse(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mdl::Skate::Instance()->GetHorse()->Init();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EndHorse | 
+bool ScriptEndHorse(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | HorseEnded | true if horse has ended
+bool ScriptHorseEnded(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	
+	return Mdl::Skate::Instance()->GetHorse()->Ended();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | HorseStatusEquals | pass in one or more of the flags to check status
+// @flag GotLetter |
+// @flag TieScore |
+// @flag BeatScore |
+// @flag Ended |
+// @flag Idle |
+// @flag NoScoreSet |
+// @flag Terminator |
+bool ScriptHorseStatusEquals(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	return Mdl::Skate::Instance()->GetHorse()->StatusEquals( pParams );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | StartHorseRun | 
+bool ScriptStartHorseRun(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mdl::Skate::Instance()->GetHorse()->StartRun();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EndHorseRun | 
+bool ScriptEndHorseRun(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mdl::Skate::Instance()->GetHorse()->EndRun();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SwitchHorsePlayers | switches to the next valid player
+bool ScriptSwitchHorsePlayers(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Mdl::Skate::Instance()->GetHorse()->SwitchPlayers();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ApplyToHorsePanelString | 
+// @parm name | whichString | the string to apply to
+bool ScriptApplyToHorsePanelString(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 whichString, id;
+	pParams->GetChecksum( "whichString", &whichString, true );
+	pParams->GetChecksum( "id", &id, true );
+	
+	Str::String horseString = Mdl::Skate::Instance()->GetHorse()->GetString( whichString );
+
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkaterById( Mdl::Skate::Instance()->GetHorse()->GetCurrentSkaterId() );
+	Dbg_Assert( pSkater );
+	
+	Script::CStruct* pTempStructure = new Script::CStruct;
+	*pTempStructure+=*pParams;
+	pTempStructure->AddString( "text", horseString.getString() );
+	pTempStructure->AddChecksum( "id", id );
+	Script::RunScript( "create_horse_panel_message", pTempStructure, pSkater );
+	delete pTempStructure;
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// @parm name | whichString | the string to apply to
+// @parmopt int | skater | 0 | the skater we're applying to (default is 0)
+bool ScriptGetHorseString(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	uint32 whichString;
+	Script::CStruct* p_return_parms;
+	pParams->GetChecksum( "whichString", &whichString, true );
+
+	Str::String horseString = Mdl::Skate::Instance()->GetHorse()->GetString( whichString );
+
+	p_return_parms = pScript->GetParams();
+	p_return_parms->AddString( "text", horseString.getString());
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | IsCurrentHorseSkater | true if the current horse skater
+// is the same as the specified skater id
+// @uparm 1 | skater id
+bool ScriptIsCurrentHorseSkater(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int skaterId;
+	if( pParams->GetInteger( NONAME, &skaterId, false ) == false )
+	{
+		pParams->GetChecksum( NONAME, (uint32*) &skaterId, true );
+	}
+	
+	
+	return ( Mdl::Skate::Instance()->GetHorse()->GetCurrentSkaterId() == skaterId );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ApplyToSkaterProfile | 
+// @parmopt int | skater | 0 | the skater we're applying to (default is 0)
+// @parmopt name | field_id | | the field to change
+// @parmopt name | slider_id | | if the value is specified in a slider
+// @parmopt name | keyboard_id | | value specified in a string
+// @parmopt name | value | | specific value (in the absence of slider or keyboard)
+bool ScriptApplyToSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript)
+{   
+	/*
+	Obj::CSkaterProfile* pProfile = NULL;
+
+	// look for the specified skater (0 by default)
+	int skater;
+
+
+	if ( pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater ) )
+	{
+		// get the specified skater profile
+		pProfile = Mdl::Skate::Instance()->GetProfile( skater );
+	}
+	else
+	{
+		// look up the current skater profile
+		pProfile = Mdl::Skate::Instance()->GetCurrentProfile( pParams );
+	}
+
+	Dbg_MsgAssert( pProfile,( "No profile found" ));
+
+	uint32 field_id;
+	uint32 checksumValue;
+	uint32 slider_id;
+	uint32 keyboard_id;
+	pParams->GetChecksum( "field_id", &field_id, true );
+
+	// the value will be in one of two places...
+	if ( pParams->GetChecksum( "slider_id", &slider_id ) )
+	{
+		// de-reference the specified slider_id
+		Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+		Front::SliderMenuElement* pSliderElement = static_cast( menu_factory->GetMenuElement( slider_id, true ) );
+		uint32 value = pSliderElement->GetValue();
+		pProfile->SetSkaterProperty( field_id, value );
+	}
+	else if ( pParams->GetChecksum( "keyboard_id", &keyboard_id ) )
+	{
+		// de-reference the specified slider_id
+		Front::MenuFactory* menu_factory = Front::MenuFactory::Instance();
+		Front::KeyboardControl* pKeyboardControl = static_cast( menu_factory->GetMenuElement( keyboard_id, true ) );
+		Str::String myString = pKeyboardControl->GetString();
+
+		if ( strlen( myString.getString() ) )
+		{
+			printf("Setting property %s to %s\n", Script::FindChecksumName(field_id), myString.getString() );
+			pProfile->SetSkaterProperty( field_id, myString.getString() );
+		}
+		else
+		{
+			printf("No string supplied.  Ignoring change to %s\n", Script::FindChecksumName(field_id) );
+		}
+	}
+	else
+	{
+		// look for checksum value
+		pParams->GetChecksum( "value", &checksumValue, true );
+		printf("Setting %s\n", Script::FindChecksumName( checksumValue ));
+		pProfile->SetSkaterProperty( field_id, checksumValue );
+	}
+	*/
+		
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RefreshSkaterColors | this just synchs up the on-screen 
+// skater with the color in the skater profile, in case it's changed
+bool ScriptRefreshSkaterColors(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int profile = 0;
+	int skater = 0;
+	
+	pParams->GetInteger( CRCD(0x7ea855f0,"profile"), &profile, Script::ASSERT );
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater, Script::ASSERT );
+//	Dbg_Printf( "Skater %d  Profile %d\n", skater, profile );
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater(skater);
+	Dbg_Assert( pSkater );
+
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( profile );
+	Dbg_Assert( pProfile );
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pSkater );
+	pModelComponent->RefreshModel( pAppearance, Gfx::vCHECKSUM_COLOR_MODEL_FROM_APPEARANCE );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RefreshSkaterScale | this just synchs up the on-screen 
+// skater with the scale in the skater profile, in case it's changed
+bool ScriptRefreshSkaterScale(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int profile = 0;
+	int skater = 0;
+	
+	pParams->GetInteger( CRCD(0x7ea855f0,"profile"), &profile, Script::ASSERT );
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater, Script::ASSERT );
+//	Dbg_Printf( "Skater %d  Profile %d\n", skater, profile );
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater(skater);
+	Dbg_Assert( pSkater );
+
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( profile );
+	Dbg_Assert( pProfile );
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pSkater );
+	pModelComponent->RefreshModel( pAppearance, Gfx::vCHECKSUM_SCALE_MODEL_FROM_APPEARANCE );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RefreshSkaterVisibility | this just synchs up the on-screen 
+// skater with the hidegeoms in the skater profile, in case it's changed
+// (such as in the invisible man cheat
+bool ScriptRefreshSkaterVisibility(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int profile = 0;
+	int skater = 0;
+	
+	pParams->GetInteger( CRCD(0x7ea855f0,"profile"), &profile, Script::ASSERT );
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater, Script::ASSERT );
+//	Dbg_Printf( "Skater %d  Profile %d\n", skater, profile );
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater(skater);
+	Dbg_Assert( pSkater );
+
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( profile );
+	Dbg_Assert( pProfile );
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pSkater );
+	pModelComponent->RefreshModel( pAppearance, Gfx::vCHECKSUM_HIDE_MODEL_FROM_APPEARANCE );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RefreshSkaterUV | this just synchs up the on-screen 
+// skater with the UVs in the skater profile, in case it's changed
+bool ScriptRefreshSkaterUV(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int profile = 0;
+	int skater = 0;
+	
+	pParams->GetInteger( CRCD(0x7ea855f0,"profile"), &profile, Script::ASSERT );
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater, Script::ASSERT );
+//	Dbg_Printf( "Skater %d  Profile %d\n", skater, profile );
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater(skater);
+	Dbg_Assert( pSkater );
+
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( profile );
+	Dbg_Assert( pProfile );
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pSkater );
+	pModelComponent->RefreshModel( pAppearance, CRCD(0x91d6261,"set_uv_from_appearance") );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AwardStatPoint | awards a stat point to the current skater
+// @uparmopt 1 | increment value (can be negative)
+bool ScriptAwardStatPoint(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile();
+
+	int inc_val = 1;
+	pParams->GetInteger( NONAME, &inc_val, Script::NO_ASSERT );
+
+	pSkaterProfile->AwardStatPoint( inc_val );
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | AwardSpecialTrickSlot | awards a new special trick slot to the 
+// current skater profile
+// @flag all | award a trickslot to all profiles
+bool ScriptAwardSpecialTrickSlot(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	Obj::CPlayerProfileManager* pPlayerProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	if ( pParams->ContainsFlag( "all" ) )
+	{
+		int num_profiles = pPlayerProfileManager->GetNumProfileTemplates();
+		for ( int i = 0; i < num_profiles; i++ )
+		{
+			Obj::CSkaterProfile* pTempProfile = pPlayerProfileManager->GetProfileTemplateByIndex( i );
+			pTempProfile->AwardSpecialTrickSlot();
+		}
+	}
+	else
+	{
+		Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetCurrentProfile();
+		pSkaterProfile->AwardSpecialTrickSlot();
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UpdateSkaterStats | 
+bool ScriptUpdateSkaterStats(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	int player;
+	pParams->GetInteger(CRCD(0x67e6859a,"player"), &player, Script::ASSERT);	
+		
+	Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetSkater(player);
+	Dbg_Assert(pSkater);
+
+	Obj::CSkaterProfile* pSkaterProfile = Mdl::Skate::Instance()->GetProfile(player);
+	pSkater->UpdateStats(pSkaterProfile);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UpdateInitials | 
+bool ScriptUpdateInitials( Script::CStruct *pParams, Script::CScript *pScript )
+{   
+	
+	int level = Mdl::Skate::Instance()->GetCareer()->GetLevel();
+	Records::CGameRecords *pGameRecords=Mdl::Skate::Instance()->GetGameRecords();	
+	pGameRecords->GetLevelRecords(level)->UpdateInitials(pGameRecords->GetDefaultInitials());
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | NewRecord | return true if the last call to UpdateRecords made
+// there be a new record
+bool ScriptNewRecord(  Script::CStruct *pParams, Script::CScript *pScript )
+{      
+	return Mdl::Skate::Instance()->m_new_record;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TrickOffAllObjects | 
+// @uparmopt 0 | skater id (0 is default)
+bool ScriptTrickOffAllObjects( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// a cheat for getting all the graffiti objects
+
+	if (!Config::CD())
+	{	
+		int skater_id = 0;
+		pParams->GetInteger( NONAME, &skater_id );
+		Mdl::Skate::Instance()->GetTrickObjectManager()->TrickOffAllObjects( skater_id );
+	}	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetScoreDegredation | 
+// @uparm 1 | should degrade amount
+bool ScriptGameModeSetScoreDegradation( Script::CStruct* pParams, Script::CScript* pScript )
+{
+    
+	
+	int should_degrade = 1;
+
+	pParams->GetInteger( NONAME, &should_degrade );
+
+	Mdl::Skate::Instance()->GetGameMode()->SetScoreDegradation( should_degrade );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetScoreAccumulation | 
+// @uparm 1 | should accumulate amount
+bool ScriptGameModeSetScoreAccumulation( Script::CStruct* pParams, Script::CScript* pScript )
+{
+    
+	
+	int should_accumulate = 1;
+
+	pParams->GetInteger( NONAME, &should_accumulate );
+
+	Mdl::Skate::Instance()->GetGameMode()->SetScoreAccumulation( should_accumulate );
+
+	if ( should_accumulate == 0 && pParams->ContainsFlag( "freeze_score" ) )
+	{
+		Mdl::Skate::Instance()->GetGameMode()->SetScoreFrozen( true );
+	}
+	else 
+	{
+		Mdl::Skate::Instance()->GetGameMode()->SetScoreFrozen( false );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetScore |
+bool ScriptResetScore(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	
+	Dbg_Printf( "******** RESET SCORE\n" );
+
+    for (uint32 i=0;iGetNumSkaters();i++)
+	{
+		Obj::CSkater* p_Skater = skate_mod->GetSkater( i );
+		if ( p_Skater->IsLocalClient() )
+		{
+			Mdl::Score * p_Score = ( p_Skater->GetScoreObject() );
+			Dbg_Assert( p_Score );
+
+			p_Score->Reset();
+			if( ( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "netking" )) ||
+				( skate_mod->GetGameMode()->GetNameChecksum() == Script::GenerateCRC( "king" )))
+			{
+				Front::SetScoreTHPS4( "0:00", p_Skater->GetHeapIndex());
+			}
+			else
+			{
+				Front::SetScoreTHPS4( "0", p_Skater->GetHeapIndex());
+			}
+			
+		}
+	}
+	
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UpdateScore |
+bool ScriptUpdateScore(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	
+    for (uint32 i=0;iGetNumSkaters();i++)
+	{
+		Obj::CSkater* p_Skater = skate_mod->GetSkater( i );
+		if ( p_Skater->IsLocalClient() )
+		{
+			Mdl::Score * p_Score = ( p_Skater->GetScoreObject() );
+			Dbg_Assert( p_Score );
+
+			p_Score->SetTotalScore( p_Score->GetTotalScore());
+		}
+	}
+	
+    return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetScoreDegradation | doesn't seem to do anything...
+bool ScriptResetScoreDegradation( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SkaterIsBraking | returns true if the local skater is braking
+bool ScriptSkaterIsBraking( Script::CStruct* pParams, Script::CScript* pScript )
+{
+    
+    Obj::CCompositeObject *pSkater = Mdl::Skate::Instance()->GetLocalSkater();						   
+    Dbg_Assert( pSkater );
+    return pSkater->CallMemberFunction(CRCD(0x1f8bbd05, "Braking"), pParams, pScript);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LocalSkaterExists | returns true if the local skater exists
+bool ScriptLocalSkaterExists( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	
+    Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();						   
+    
+    return ( pSkater != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | InitSkaterModel | This will destroy the specified
+// skater's model and replace it with the specified model or
+// profile
+bool ScriptInitSkaterModel(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	int player_num = 0;
+	pParams->GetInteger( NONAME, &player_num );
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater(player_num);
+	Dbg_Assert( pSkater );
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterGeomHeap(pSkater->GetHeapIndex()));
+   	
+	uint32 appearance_structure_name;
+	pParams->GetChecksum( "default_appearance", &appearance_structure_name, true );
+//	Script::PrintContents( pParams );
+
+	// create the model
+	Gfx::CModelAppearance theAppearance;
+	theAppearance.Load( appearance_structure_name );
+
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pSkater );
+	pModelComponent->InitModelFromProfile( &theAppearance, false, pSkater->GetHeapIndex() );
+	
+	Mem::Manager::sHandle().PopContext();
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | RefreshSkaterModel | This will destroy the specified
+// skater's model and replace it with the specified model or
+// profile
+// If there is a composite object called "bailboard" that will be destroyed
+bool ScriptRefreshSkaterModel(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	int profile = 0;
+	int skater = 0;
+	int no_name = 0;
+	
+	
+	
+	pParams->GetInteger( NONAME, &no_name );
+	profile = no_name;
+	skater = no_name;
+	pParams->GetInteger( CRCD(0x7ea855f0,"profile"), &profile );
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater );
+	Dbg_Printf( "Skater %d  Profile %d\n", skater, profile );
+	Obj::CSkater* pSkater = Mdl::Skate::Instance()->GetSkater(skater);
+	Dbg_MsgAssert( pSkater, ( "Couldn't find specified skater" ) );
+
+
+	// (Mick) Now check for bailboard, and kill it
+	// Bit of a patch really, but a fairly safe high level one.
+	// (need a more general solution for killing model that has instances...)
+	if (pSkater->GetSkaterNumber() == 0)	// Only need it for single player
+	{
+		Obj::CCompositeObject*p_bailboard = (Obj::CCompositeObject*) Obj::CCompositeObjectManager::Instance()->GetObjectByID(CRCD(0x884ef81b,"bailboard"));
+		if (p_bailboard)
+		{
+			p_bailboard->MarkAsDead();
+			Obj::CCompositeObjectManager::Instance()->FlushDeadObjects();
+		}
+	}
+
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( profile );
+	Dbg_Assert( pProfile );
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	
+	// GJ:  rebuilding the face and texture takes
+	// a while, causes Pcm audio "panic" assert...
+	// just to be safe, i'll pause the music while
+	// the model is being rebuilt.
+	/*bool should_pause_music = !Pcm::MusicIsPaused();
+	
+	if ( should_pause_music )
+	{
+		Pcm::PauseMusic( true );
+	}*/
+
+	// create the model
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterGeomHeap(pSkater->GetHeapIndex()));
+
+	Nx::CEngine::sFinishRendering();
+   
+#ifdef __PLAT_NGC__
+	File::PreMgr* pre_mgr = File::PreMgr::Instance();
+	bool loaded = pre_mgr->InPre( "skaterparts.pre");
+	if ( !loaded )
+	{
+		pre_mgr->LoadPre( "skaterparts.pre", false);
+	}
+#endif
+	Obj::CModelComponent* pModelComponent = GetModelComponentFromObject( pSkater );
+	pModelComponent->InitModelFromProfile( pAppearance, false, pSkater->GetHeapIndex() );
+#ifdef __PLAT_NGC__
+	if ( !loaded )
+	{
+		pre_mgr->UnloadPre( "skaterparts.pre");
+	}
+#endif
+	
+	Mem::Manager::sHandle().PopContext();
+
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().SkaterHeap(pSkater->GetHeapIndex()));
+	
+	pSkater->UpdateStats( pProfile );
+
+	Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+	Dbg_Assert(pTrickComponent);
+	pTrickComponent->UpdateTrickMappings( pProfile );
+
+	pSkater->UpdateSkaterInfo( pProfile );
+
+	Mem::Manager::sHandle().PopContext();
+
+    uint32 board;
+    if ( pParams->GetChecksum( CRCD(0x41eec86b,"no_board"), &board ) )
+    {
+        // if Refreshing the skater in the CAS level,
+        // Hide the board
+        //Mdl::Skate * skate_mod =  Mdl::Skate::Instance();
+        //if ( skate_mod->m_cur_level == CRCD(0xa368b4f1,"load_cas") )
+        //{
+            // turn off board
+            Obj::CModelComponent* pModelComponent = GetModelComponentFromObject(pSkater);
+            pModelComponent->HideGeom(CRCD(0xa7a9d4b8,"board"), true, true);
+
+			// GJ THPS5: Kludge to fix TT12405: CAS - board 
+			// appears when adjusting leg tattoos
+			// generally, this function will be called from a spawned script,
+			// which gets called after the composite objects are updated
+			// for the frame...  because of this, it will take 1 frame
+			// for the hide flag to take effect, unless we explicitly update
+			// the model component here
+            pModelComponent->Update();
+        //}
+    }
+    
+	/*if ( should_pause_music )
+	{
+		// unpause the music
+		Pcm::PauseMusic( false );
+	}*/
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | EditPlayerAppearance | This runs an object script on the specified
+// appearance structure
+// @parm name | target | name of script that gets run on the player's appearance
+// @parmopt structure | targetParams | | extra parameters sent to the target script
+bool ScriptEditPlayerAppearance(Script::CStruct *pParams, Script::CScript *pScript)
+{	
+	int profile_num = 0;
+	int no_name = 0;
+	
+	pParams->GetInteger( NONAME, &no_name );
+	profile_num = no_name;
+	pParams->GetInteger( CRCD(0x7ea855f0,"profile"), &profile_num );
+	Dbg_Printf( "Profile %d\n", profile_num );
+
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile( profile_num );
+	Dbg_Assert( pProfile );
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	   	
+	uint32 target;
+	pParams->GetChecksum(CRCD(0xb990d003,"target"), &target, Script::ASSERT);
+
+	Script::CStruct* pTargetParams = NULL;
+	pParams->GetStructure(CRCD(0x46a2869c,"targetParams"), &pTargetParams, Script::NO_ASSERT);
+
+	// runs an object script on the specified appearance structure
+	Script::RunScript(target, pTargetParams, pAppearance);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCurrentSkaterProfileIndex | This returns the currently selected
+// profile (either player 0 or player 1), presumably for use by other script functions
+// @parm int | currentSkaterProfileIndex | index of currently selected profile (return value)
+bool ScriptGetCurrentSkaterProfileIndex(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	int index = pPlayerProfileManager->GetCurrentProfileIndex();
+	pScript->GetParams()->AddInteger(CRCD(0x3c64476b,"currentSkaterProfileIndex"), index);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetCustomSkaterName | 
+bool ScriptGetCustomSkaterName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+	Obj::CPlayerProfileManager*	pPlayerProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	const char* name = pPlayerProfileManager->GetProfileTemplate(CRCD(0xa7be964,"custom"))->GetDisplayName();
+	pScript->GetParams()->AddString(CRCD(0x2ab66cb8,"display_name"), name);
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetScorePot | this will reset the local skater's current score pot.
+// This currently just calls the Bail member func of the score object...
+bool ScriptResetScorePot(Script::CScriptStructure *pParams, Script::CScript *pScript)
+{
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	
+	bool useBailStyle = pParams->ContainsFlag(CRCD(0x929eb4d5, "UseBailStyle"));
+	
+    for (uint32 i=0;iGetNumSkaters();i++)
+	{
+		Obj::CSkater* p_Skater = skate_mod->GetSkater( i );
+		if ( p_Skater->IsLocalClient() )
+		{   
+			Mdl::Score *pScore=p_Skater->GetScoreObject();
+
+			Dbg_Assert( pScore );
+			pScore->Bail(!useBailStyle);
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptPrintSkaterStats | prints the specified skater's stats
+bool ScriptPrintSkaterStats( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __NOPT_ASSERT__
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+    for (uint32 i=0;iGetNumSkaters();i++)
+	{
+		printf("Skater %d stats:\n", i);
+
+		Obj::CSkater* pSkater = skate_mod->GetSkater( i );
+		
+		printf("Air    %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_AIR));		
+		printf("Run    %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_RUN));		
+		printf("Ollie  %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_OLLIE));		
+		printf("Speed  %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_SPEED));		
+		printf("Spin   %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_SPIN));		
+		printf("Flip   %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_FLIPSPEED));		
+		printf("Switch %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_SWITCH));		
+		printf("Rail   %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_RAILBALANCE));		
+		printf("Lip    %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_LIPBALANCE));		
+		printf("Manual %2.2f\n",pSkater->GetStat(Obj::CSkater::STATS_MANUAL));		
+	}
+#endif		// __NOPT_ASSERT__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ScriptPrintSkaterStats | prints the specified skater's stats
+bool ScriptPrintSkaterStats2( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __NOPT_ASSERT__
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+    for (uint32 i=0;iGetNumSkaters();i++)
+	{
+		printf("Skater %d stats:\n", i);
+
+        Obj::CPlayerProfileManager* pProfileManager = skate_mod->GetPlayerProfileManager();
+        Obj::CSkaterProfile* pProfile = pProfileManager->GetProfile( i );
+		
+		printf("Air    %i\n",pProfile->GetStatValue(CRCD(0x439f4704,"AIR")));		
+		printf("Run    %i\n",pProfile->GetStatValue(CRCD(0xaf895b3f,"RUN")));		
+		printf("Ollie  %i\n",pProfile->GetStatValue(CRCD(0x9b65d7b8,"OLLIE")));		
+		printf("Speed  %i\n",pProfile->GetStatValue(CRCD(0xf0d90109,"SPEED")));		
+		printf("Spin   %i\n",pProfile->GetStatValue(CRCD(0xedf5db70,"SPIN")));		
+		printf("Flip   %i\n",pProfile->GetStatValue(CRCD(0x6dcb497c,"FLIP_SPEED")));		
+		printf("Switch %i\n",pProfile->GetStatValue(CRCD(0x9016b4e7,"SWITCH")));		
+		printf("Rail   %i\n",pProfile->GetStatValue(CRCD(0xf73a13e3,"RAIL_BALANCE")));		
+		printf("Lip    %i\n",pProfile->GetStatValue(CRCD(0xae798769,"LIP_BALANCE")));		
+		printf("Manual %i\n",pProfile->GetStatValue(CRCD(0xb1fc0722,"MANUAL_BALANCE")));		
+	}
+#endif		// __NOPT_ASSERT__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | PrintSkaterPosition | prints the local skater's current position
+bool ScriptPrintSkaterPosition( Script::CStruct* pParams, Script::CScript* pScript )
+{
+#ifdef __NOPT_ASSERT__
+	Obj::CSkater *pSkater = Mdl::Skate::Instance()->GetLocalSkater();
+	
+	Mth::Vector pos = pSkater->GetPos();
+	float x = pos.GetX();
+	float y = pos.GetY();
+	float z = pos.GetZ();
+
+	Dbg_Message("(%f, %f, %f)", x, y, z);
+#endif		// __NOPT_ASSERT__
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSkaterPosition | prints the local skater's current position
+bool ScriptGetSkaterPosition( Script::CStruct* pParams, Script::CScript* pScript )
+{
+
+    Obj::CSkater* pSkater = static_cast< Obj::CSkater* >(pScript->mpObject.Convert());
+	
+    if (pSkater) 
+    {
+    	Mth::Vector pos = pSkater->GetPos();
+    	float x = pos.GetX();
+    	float y = pos.GetY();
+    	float z = pos.GetZ();
+    
+        pScript->GetParams()->AddInteger("x", x);
+        pScript->GetParams()->AddInteger("y", y);
+        pScript->GetParams()->AddInteger("z", z);
+    
+        return true;
+    }
+    
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSkaterPosition | prints the local skater's current position
+bool ScriptGetSkaterVelocity( Script::CStruct* pParams, Script::CScript* pScript )
+{
+
+    Obj::CSkater* pSkater = static_cast< Obj::CSkater* >(pScript->mpObject.Convert());
+	
+    if (pSkater) 
+    {
+    	Mth::Vector vel = pSkater->GetVel();
+    	float x = vel.GetX();
+    	float y = vel.GetY();
+    	float z = vel.GetZ();
+		float scale = 1.0f;
+		float skew_angle = 0.0f;
+    
+		
+		pParams->GetFloat( CRCD(0x5582fe27,"skew_angle"), &skew_angle );
+
+        pScript->GetParams()->AddInteger(CRCD(0x6187906d,"vel_x"), x);
+        pScript->GetParams()->AddInteger(CRCD(0x1680a0fb,"vel_y"), y);
+        pScript->GetParams()->AddInteger(CRCD(0x8f89f141,"vel_z"), z);
+
+		pParams->GetFloat( CRCD(0x13b9da7b,"scale"), &scale );
+
+		vel[Y] = 0.0f;
+
+		if( vel.Length() > 100.0f )
+		{
+			vel.Normalize();
+
+			if( skew_angle != 0 )
+			{
+				Mth::Matrix rot_mat;
+	
+				rot_mat.Ident();
+				rot_mat.RotateY( Mth::DegToRad( skew_angle ));
+	
+				vel = rot_mat.Transform( vel );
+			}
+
+			pScript->GetParams()->AddInteger("scaled_x", vel[X] * scale );
+			pScript->GetParams()->AddInteger("scaled_y", vel[Y] * scale );
+			pScript->GetParams()->AddInteger("scaled_z", vel[Z] * scale );
+		}
+		else
+		{
+			Mth::Vector facing;
+
+			facing = pSkater->GetMatrix()[Mth::AT];
+	
+			if( skew_angle != 0 )
+			{
+				Mth::Matrix rot_mat;
+	
+				rot_mat = pSkater->GetMatrix();
+				rot_mat.RotateYLocal( Mth::DegToRad( skew_angle ));
+	
+				facing = rot_mat[Mth::AT];
+			}
+
+			pScript->GetParams()->AddInteger(CRCD(0xbded7a76,"scaled_x"), facing[X] * scale );
+			pScript->GetParams()->AddInteger(CRCD(0xcaea4ae0,"scaled_y"), facing[Y] * scale );
+			pScript->GetParams()->AddInteger(CRCD(0x53e31b5a,"scaled_z"), facing[Z] * scale );
+		}
+    
+        return true;
+    }
+    
+    return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetStatValue | this returns the specified stat value in
+// the script's params as stat_value
+// @uparm STATS_AIR | global int value of the stat (defined in physics.q)
+// @parmopt name | skater | | the name of the profile you wish to get.  If you don't
+// provide a name, the current skater's profile will be returned
+bool ScriptGetStatValue( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 stat;
+	pParams->GetChecksum( NONAME, &stat, Script::ASSERT );
+
+	uint32 name;
+	
+	Obj::CPlayerProfileManager* pPlayerProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile = NULL;
+	if ( pParams->GetChecksum( CRCD(0x5b8ab877,"skater"), &name, Script::NO_ASSERT ) )
+		pSkaterProfile = pPlayerProfileManager->GetProfileTemplate( name );
+	else
+		pSkaterProfile = pPlayerProfileManager->GetCurrentProfile();
+	
+	Dbg_MsgAssert( pSkaterProfile, ( "Unable to get skater profile") );
+	int value = pSkaterProfile->GetStatValue( stat );
+	pScript->GetParams()->AddInteger( "stat_value", value );
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetNumStatPointsAvailable | returns the number in the scripts
+// params as points_available
+// @parm int | player | the player number to check 
+bool ScriptGetNumStatPointsAvailable( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int player;
+	pParams->GetInteger( CRCD(0x67e6859a,"player"), &player, Script::ASSERT );
+
+	
+	Obj::CPlayerProfileManager* pProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pProfile = pProfileManager->GetProfile( player );
+	int num = pProfile->GetNumStatPointsAvailable();
+	pScript->GetParams()->AddInteger( "points_available", num );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UnlockSkater | this makes locked characters accessible
+// to the player
+// @parm name | skater | the name of the locked character to unlock
+bool ScriptUnlockSkater( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 name;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+
+	Obj::CPlayerProfileManager* pPlayerProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pSkaterProfile = pPlayerProfileManager->GetProfileTemplate( name );
+	Dbg_MsgAssert( pSkaterProfile, ( "Unable to get skater profile") );
+
+	int isLocked = 1;
+	pSkaterProfile->GetInfo()->GetInteger( "is_hidden", &isLocked, Script::ASSERT );
+	if ( isLocked )
+	{
+#ifdef __NOPT_ASSERT__
+		// don't need to sync it up to the current skater profiles,
+		// because, theoretically, the player won't be able to
+		// choose him through the front end...
+		Obj::CSkaterProfile* pCurrentSkaterProfile = pPlayerProfileManager->GetCurrentProfile();
+		Dbg_MsgAssert( pCurrentSkaterProfile->GetSkaterNameChecksum() != name, 
+					   ( "Locked skaters aren't supposed to be currently selected!", 
+						 Script::FindChecksumName(name) ) );
+#endif
+		Dbg_Message( "%s was locked\n", Script::FindChecksumName(name) );
+		pSkaterProfile->GetInfo()->AddInteger( "is_hidden", 0 );
+	}
+	else
+	{
+		Dbg_Message( "%s was not locked\n", Script::FindChecksumName(name) );
+	}
+		
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetActualCASOptionStruct( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 partChecksum;
+	pParams->GetChecksum(CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT);
+	
+	uint32 descChecksum;
+	pParams->GetChecksum(CRCD(0x4bb2084e,"desc_id"), &descChecksum, Script::ASSERT);
+
+	Script::CStruct* pActualStruct = Cas::GetOptionStructure( partChecksum, descChecksum );
+
+	pScript->GetParams()->AppendStructure( pActualStruct );
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetActualPlayerAppearancePart( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int player_num = 0;
+	pParams->GetInteger(CRCD(0x67e6859a,"player"), &player_num);
+	
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile(player_num);
+	Dbg_Assert(pProfile);
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+
+	uint32 partChecksum;
+	if ( pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::NO_ASSERT ) )
+	{
+		Script::CStruct* pVirtualStruct;
+		pVirtualStruct = pAppearance->GetActualDescStructure( partChecksum );
+		if ( pVirtualStruct )
+		{
+			pScript->GetParams()->AppendStructure( pVirtualStruct );
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetPlayerAppearancePart( Script::CStruct* pParams, Script::CScript* pScript )
+{	
+	int player_num = 0;
+	pParams->GetInteger(CRCD(0x67e6859a,"player"), &player_num);
+	
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile(player_num);
+	Dbg_Assert(pProfile);
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+
+	uint32 partChecksum;
+	if ( pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::NO_ASSERT ) )
+	{
+		Script::CStruct* pVirtualStruct;
+		pVirtualStruct = pAppearance->GetVirtualDescStructure( partChecksum );
+		if ( pVirtualStruct )
+		{
+			pScript->GetParams()->AppendStructure( pVirtualStruct );
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSetPlayerAppearanceColor( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int player_num = 0;
+	pParams->GetInteger(CRCD(0x67e6859a,"player"), &player_num);
+	
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile(player_num);
+	Dbg_Assert(pProfile);
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	Dbg_Assert( pAppearance );
+
+	uint32 partChecksum;
+	pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+
+	Script::CStruct* pVirtualStruct;
+	pVirtualStruct = pAppearance->GetVirtualDescStructure( partChecksum );
+
+	if ( pVirtualStruct )
+	{
+		float h, s, v;
+		int useDefaultHSV;
+		pParams->GetFloat( CRCD(0x6e94f918,"h"), &h, Script::ASSERT );
+		pParams->GetFloat( CRCD(0xe4f130f4,"s"), &s, Script::ASSERT );
+		pParams->GetFloat( CRCD(0x949bc47b,"v"), &v, Script::ASSERT );
+		pParams->GetInteger( CRCD(0x97dbdde6,"use_default_hsv"), &useDefaultHSV, Script::ASSERT );
+
+		pVirtualStruct->AddInteger( CRCD(0x6e94f918,"h"), (int)h );
+		pVirtualStruct->AddInteger( CRCD(0xe4f130f4,"s"), (int)s );
+		pVirtualStruct->AddInteger( CRCD(0x949bc47b,"v"), (int)v );
+		pVirtualStruct->AddInteger( CRCD(0x97dbdde6,"use_default_hsv"), useDefaultHSV );
+	}
+	else
+	{
+		Dbg_Message( "Nothing to color...  (appearance doesn't contain %s)", Script::FindChecksumName(partChecksum) );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSetPlayerAppearanceScale( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int player_num = 0;
+	pParams->GetInteger(CRCD(0x67e6859a,"player"), &player_num);
+	
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile(player_num);
+	Dbg_Assert(pProfile);
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	Dbg_Assert( pAppearance );
+
+	uint32 partChecksum;
+	pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+
+	Script::CStruct* pVirtualStruct;
+	pVirtualStruct = pAppearance->GetVirtualDescStructure( partChecksum );
+
+	if ( !pVirtualStruct )
+	{
+		pVirtualStruct = new Script::CStruct;
+		Script::CStruct* pAppearanceStruct = pAppearance->GetStructure();
+		pAppearanceStruct->AddStructurePointer( partChecksum, pVirtualStruct );
+	}
+
+	float x;
+	if ( pParams->GetFloat( CRCD(0x7323e97c,"x"), &x, Script::NO_ASSERT ) )
+	{
+		pVirtualStruct->AddInteger( CRCD(0x7323e97c,"x"), (int)x );
+	}
+	
+	float y;
+	if ( pParams->GetFloat( CRCD(0x424d9ea,"y"), &y, Script::NO_ASSERT ) )
+	{
+		pVirtualStruct->AddInteger( CRCD(0x424d9ea,"y"), (int)y );
+	}
+	
+	float z;
+	if ( pParams->GetFloat( CRCD(0x9d2d8850,"z"), &z, Script::NO_ASSERT ) )
+	{
+		pVirtualStruct->AddInteger( CRCD(0x9d2d8850,"z"), (int)z );
+	}
+	
+	int useDefaultScale;
+	if ( pParams->GetInteger( CRCD(0x5a96985d,"use_default_scale"), &useDefaultScale, Script::NO_ASSERT ) )
+	{
+		pVirtualStruct->AddInteger( CRCD(0x5a96985d,"use_default_scale"), useDefaultScale );
+	}	
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptSetPlayerAppearanceUV( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int player_num = 0;
+	pParams->GetInteger(CRCD(0x67e6859a,"player"), &player_num);
+	
+	Obj::CSkaterProfile* pProfile = Mdl::Skate::Instance()->GetProfile(player_num);
+	Dbg_Assert(pProfile);
+    Gfx::CModelAppearance* pAppearance = pProfile->GetAppearance();
+	Dbg_Assert( pAppearance );
+
+	uint32 partChecksum;
+	pParams->GetChecksum( CRCD(0xb6f08f39,"part"), &partChecksum, Script::ASSERT );
+
+	Script::CStruct* pVirtualStruct;
+	pVirtualStruct = pAppearance->GetVirtualDescStructure( partChecksum );
+
+	if ( pVirtualStruct )
+	{
+		float u, v, uv_scale, uv_rot;
+		int useDefaultUV;
+		pParams->GetFloat( CRCD(0xcf6aa087,"uv_u"), &u, Script::ASSERT );
+		pParams->GetFloat( CRCD(0x5663f13d,"uv_v"), &v, Script::ASSERT );
+		pParams->GetFloat( CRCD(0x266932c8,"uv_scale"), &uv_scale, Script::ASSERT );
+		pParams->GetFloat( CRCD(0x1256b6c6,"uv_rot"), &uv_rot, Script::ASSERT );
+		pParams->GetInteger( CRCD(0x8602f6ee,"use_default_uv"), &useDefaultUV, Script::ASSERT );
+				   
+		pVirtualStruct->AddFloat( CRCD(0xcf6aa087,"uv_u"), u );
+		pVirtualStruct->AddFloat( CRCD(0x5663f13d,"uv_v"), v );
+		pVirtualStruct->AddFloat( CRCD(0x266932c8,"uv_scale"), uv_scale );
+		pVirtualStruct->AddFloat( CRCD(0x1256b6c6,"uv_rot"), uv_rot );
+		pVirtualStruct->AddInteger( CRCD(0x8602f6ee,"use_default_uv"), useDefaultUV );
+	}
+	else
+	{
+		Dbg_Message( "Nothing to wibble...  (appearance doesn't contain %s yet?)", Script::FindChecksumName(partChecksum) );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* GetNextUndisqualified( Script::CArray* pPartArray, int startIndex )
+{
+	Dbg_Assert( pPartArray );
+
+	startIndex++;
+	if ( startIndex >= (int)pPartArray->GetSize() )
+	{
+		startIndex -= pPartArray->GetSize();
+	}
+
+	for ( int i = 0; i < (int)pPartArray->GetSize(); i++ )
+	{
+		int currIndex = (i + startIndex) % pPartArray->GetSize();
+
+		Script::CStruct* pCurrStruct = pPartArray->GetStructure( currIndex );
+		
+		int disqualified;
+		pCurrStruct->GetInteger( "disqualified", &disqualified, true );
+		if ( !disqualified )
+		{
+			return pCurrStruct;
+		}
+	}
+
+	// none found...
+	return pPartArray->GetStructure( startIndex );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Script::CStruct* GetPrevUndisqualified( Script::CArray* pPartArray, int startIndex )
+{
+	Dbg_Assert( pPartArray );
+
+	startIndex--;
+	if ( startIndex < 0 )
+	{
+		startIndex += pPartArray->GetSize();
+	}
+
+	for ( int i = (int)pPartArray->GetSize(); i >= 0; i-- )
+	{
+		int currIndex = (i + startIndex) % pPartArray->GetSize();
+
+		Script::CStruct* pCurrStruct = pPartArray->GetStructure( currIndex );
+		
+		int disqualified;
+		pCurrStruct->GetInteger( "disqualified", &disqualified, true );
+		if ( !disqualified )
+		{
+			return pCurrStruct;
+		}
+	}
+
+	// none found...
+	return pPartArray->GetStructure( startIndex );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FlushDeadObjects | 
+// flush any objects that are marked as dead
+// used so we can be sure they have gone, so we can re-use the memory
+// and also avoid fragmentation
+bool ScriptFlushDeadObjects( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::Skate::Instance()->GetObjectManager()->FlushDeadObjects();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | BindTrickToKeyCombo | binds the given trick to the key combo
+// @parm name | key_combo |
+// @parm name | trick_checksum |
+// @parmopt int | update_mappings | 1 | automatically update the trick mappings.
+// This should only be 0 in a split screen game
+bool ScriptBindTrickToKeyCombo( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 key_combo;
+	pParams->GetChecksum( "key_combo", &key_combo, Script::ASSERT );
+
+	uint32 trick_checksum=0;
+	pParams->GetChecksum( "trick", &trick_checksum );
+
+	// For create-a-tricks, the trick parameter is an integer index.
+	int create_a_trick=0;
+	bool got_create_a_trick=false;
+	if (pParams->GetInteger( "trick", &create_a_trick ))
+	{
+		got_create_a_trick=true;
+	}	
+
+	Dbg_MsgAssert(got_create_a_trick || trick_checksum,("\n%s\nMissing trick parameter",pScript->GetScriptInfo()));
+	
+	bool update_mappings = true;
+	int update_param = 1;
+	pParams->GetInteger( "update_mappings", &update_param, Script::NO_ASSERT );
+	if ( update_param == 0 )
+		update_mappings = false;
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+
+	// are we binding a special trick?
+	if ( pParams->ContainsFlag( "special" ) )
+	{
+		int index;
+		if ( !pParams->GetInteger( "index", &index, Script::NO_ASSERT ) )
+        {
+            return false;
+        }
+        
+		// add a new slot and update info
+		Obj::SSpecialTrickInfo trickInfo;
+		trickInfo.m_TrickSlot = key_combo;
+		if (got_create_a_trick)
+		{
+            trickInfo.m_TrickName = create_a_trick;
+        }
+        else
+        {
+            trickInfo.m_TrickName = trick_checksum;
+        }
+		if ( pParams->ContainsFlag( CRCD(0x61a1bc57,"cat") ) )
+		{
+			trickInfo.m_isCat = true;
+		}
+        else
+        {
+			trickInfo.m_isCat = false;
+		}
+
+        pSkaterProfile->SetSpecialTrickInfo( index, trickInfo, update_mappings );
+	}
+	else
+	{
+		Script::CStruct* pTricks = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+		
+		if (got_create_a_trick)
+		{
+			// Make sure any existing checksum parameter is removed.
+			pTricks->RemoveComponent(key_combo);
+			pTricks->AddInteger( key_combo, create_a_trick );
+		}
+		else
+		{
+			pTricks->RemoveComponent(key_combo);
+            pTricks->AddChecksum( key_combo, trick_checksum );
+		}	
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// this does breadth first recursion on the structure and returns trick
+// highest in the tree (parent overwrites child).
+static uint32 s_find_trick_in_mapping( Script::CStruct* pMapping, uint32 trick_checksum, int recurse_level )
+{
+	if ( recurse_level > 10 )
+	{
+		Dbg_MsgAssert( 0, ( "GetKeyComboArrayFromTrickArray recursed too much." ) );
+		return 0;
+	}
+
+	Script::CComponent* p_comp = pMapping->GetNextComponent( NULL );
+	uint32 rv = 0;
+	while ( p_comp )
+	{
+		Script::CStruct* pTestStruct = Script::GetStructure( p_comp->mChecksum, Script::NO_ASSERT );
+		Script::CComponent* p_next = pMapping->GetNextComponent( p_comp );
+		if ( !rv && p_comp->mType == ESYMBOLTYPE_STRUCTURE )
+		{
+			// printf("recursing to level %i\n", recurse_level);
+			rv = s_find_trick_in_mapping( p_comp->mpStructure, trick_checksum, recurse_level + 1 );
+		}
+		else if ( !rv && pTestStruct )
+		{
+			// printf("recursing to level %i\n", recurse_level);
+			rv = s_find_trick_in_mapping( pTestStruct, trick_checksum, recurse_level + 1 );
+		}
+		if ( p_comp->mType == ESYMBOLTYPE_NAME && p_comp->mChecksum == trick_checksum )
+			rv = p_comp->mNameChecksum;
+
+		p_comp = p_next;
+	}
+	return rv;
+}
+
+static uint32 s_find_cat_in_mapping( Script::CStruct* pMapping, int cat_num )
+{
+	Script::CComponent* p_comp = pMapping->GetNextComponent( NULL );
+	uint32 rv = 0;
+
+    while ( p_comp )
+	{
+        Script::CComponent* p_next = pMapping->GetNextComponent( p_comp );
+        if ( p_comp->mType == ESYMBOLTYPE_INTEGER && p_comp->mIntegerValue == cat_num )
+        {
+            printf("s_find_cat_in_mapping found %i\n", p_comp->mIntegerValue );
+            rv = p_comp->mNameChecksum;
+        }
+        p_comp = p_next;
+	}
+    return rv;
+}
+
+// @script | GetKeyComboBoundToTrick | looks for any key combos that are currently
+// associated with the specified trick.  The result is stored in current_key_combo
+// @parm name | trick | trick to look for
+bool ScriptGetKeyComboBoundToTrick( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 trick_checksum = NULL;
+    int cat_num;
+    if (!pParams->GetChecksum( CRCD(0x270f56e1,"trick"), &trick_checksum, Script::NO_ASSERT) )
+    {
+        pParams->GetInteger( CRCD(0xa75b8581,"cat_num"), &cat_num, Script::ASSERT );
+    }
+	
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+
+	uint32 key_combo = 0;
+	bool found_special = false;
+	int special_index = 0;
+	
+    if (trick_checksum)
+    {
+        if ( !pParams->ContainsFlag( CRCD(0xb394c01c,"special") ) )
+    	{
+    		Script::CStruct* pTricks = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+    	
+    		uint32 test_key_combo = s_find_trick_in_mapping( pTricks, trick_checksum, 0 );
+    		
+    		// check that the mapping holds true the other way (ie, when we grab the trick
+    		// based on this key_combo, do we get the same trick?).
+    		uint32 test_trick_checksum = 0;
+    		if ( pTricks->GetChecksum( test_key_combo, &test_trick_checksum, Script::NO_ASSERT ) && test_trick_checksum == trick_checksum )
+    				key_combo = test_key_combo;
+    	}
+    	else
+    	{
+    		if ( !key_combo && pParams->ContainsFlag( CRCD(0xb394c01c,"special") ) )
+    		{
+    			// printf("searching for special trick\n");
+    			int numTricks = pSkaterProfile->GetNumSpecialTrickSlots();
+    			// printf("%i tricks to check\n", numTricks);
+    	
+    			// search through current special tricks
+    			for ( int i = 0; i < numTricks; i++ )
+    			{
+    				Obj::SSpecialTrickInfo trick_info = pSkaterProfile->GetSpecialTrickInfo( i );
+    				if ( trick_info.m_TrickName == trick_checksum )
+    				{
+    					printf("GetKeyComboBoundToTrick found a special trick\n");
+    					found_special = true;
+    					special_index = i;
+    					key_combo = trick_info.m_TrickSlot;
+    					break;
+    				}
+    			}
+    		}
+    	}
+    }
+    else
+    {
+        Script::CStruct* pTricks = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+    	
+        key_combo = s_find_cat_in_mapping( pTricks, cat_num );
+    }
+    
+	
+	if ( key_combo )
+	{
+		pScript->GetParams()->AddChecksum( "current_key_combo", key_combo );
+		if ( found_special )
+		{
+			printf("current_index: %i\n", special_index);
+			pScript->GetParams()->AddInteger( "current_index", special_index );
+		}
+		return true;
+	}	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | UpdateTrickMappings | forces the trick mappings to update
+// @parmopt int | skater | | the skater num
+// @parmopt name | skaterId | | the id of the skater
+bool ScriptUpdateTrickMappings( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+
+	Obj::CPlayerProfileManager* pProfileMan = pSkate->GetPlayerProfileManager();
+	
+	// TODO: if we ever do split screen network games, this will not work!
+	// It assumes there is only one local skater when in a net game
+	if ( gamenet_man->InNetGame() )
+	{
+		Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+		Dbg_MsgAssert( pSkater, ( "UpdateTrickMappings couldn't find a local skater" ) );
+		
+		Obj::CSkaterProfile* pProfile = pProfileMan->GetProfile( 0 );
+		
+		Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+		Dbg_Assert(pTrickComponent);
+		pTrickComponent->UpdateTrickMappings( pProfile );
+	}
+	else
+	{
+		int skater_num;
+		pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater_num, Script::ASSERT );
+		Obj::CSkater* pSkater = pSkate->GetSkater( skater_num );
+		Dbg_Assert( pSkater );
+		Obj::CSkaterProfile* pProfile = pProfileMan->GetProfile( skater_num );
+		Dbg_Assert( pProfile );
+	
+		if ( pSkater && pProfile )
+		{
+			Obj::CTrickComponent* pTrickComponent = GetTrickComponentFromObject(pSkater);
+			Dbg_Assert(pTrickComponent);
+			pTrickComponent->UpdateTrickMappings( pProfile );
+
+		}
+	}
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetConfigurableTricksFromType | returns an array of trick checksums
+// which have the type specified.  The array is stored as ConfigurableTricks
+// @parm name | type | the type to search for
+// @flag special | search for special tricks of this type
+bool ScriptGetConfigurableTricksFromType( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 type = 0;
+	pParams->GetChecksum( "type", &type, Script::NO_ASSERT );
+
+	bool find_special = pParams->ContainsFlag( "special" );
+
+	Dbg_MsgAssert( type || find_special, ( "You must specify a type or provide the special flag" ) );
+
+	uint32 trick_list[128];
+	int num_found = 0;
+    int cat_list[128];
+    int num_cats_found = 0;
+    
+    // grab some checksums
+    //uint32 createdtrick = Script::GenerateCRC( "CreatedTrick" );
+    uint32 grabtrick = Script::GenerateCRC( "GrabTrick" );
+    uint32 fliptrick = Script::GenerateCRC( "FlipTrick" );
+
+	// grab the list of all tricks
+	Script::CArray* p_all_tricks = Script::GetArray( "ConfigurableTricks", Script::ASSERT );
+	Dbg_MsgAssert( p_all_tricks->GetType() == ESYMBOLTYPE_NAME, ( "ConfigurableTricks array had wrong type\n" ) );
+
+	int size = p_all_tricks->GetSize();
+	for ( int i = 0; i < size; i++ )
+	{
+		Dbg_MsgAssert( num_found < 128, ( "Found too many tricks" ) );
+		
+		uint32 trick = p_all_tricks->GetChecksum( i );
+		Script::CStruct* p_trick = Script::GetStructure( trick, Script::NO_ASSERT );
+
+        if ( p_trick )
+		{
+			// get the type
+			uint32 current_trick_type;
+
+			// first check for optional TrickType var
+			if ( !p_trick->GetChecksum( "TrickType", ¤t_trick_type, Script::NO_ASSERT ) )
+			{
+				if ( !p_trick->GetChecksum( "Scr", ¤t_trick_type, Script::NO_ASSERT ) )
+				{
+					// assume it's a grind
+					current_trick_type = Script::GenerateCRC( "GrindTrick" );
+				}
+			}
+			
+			if ( ( find_special && !type ) || current_trick_type == type )
+			{
+				// get the params array and look for the special flag
+				Script::CStruct* p_trick_params;
+				p_trick->GetStructure( "Params", &p_trick_params, Script::ASSERT );
+
+				if ( find_special && p_trick_params->ContainsFlag( "IsSpecial" ) )
+				{
+					trick_list[num_found] = trick;
+					num_found++;
+				}
+				else if ( !find_special && !p_trick_params->ContainsFlag( "IsSpecial" ) )
+				{
+					trick_list[num_found] = trick;
+					num_found++;
+				}
+			}
+		}
+	}
+
+    // if type is grab or flip then list created tricks too!
+    if ( type == grabtrick || type == fliptrick )
+    {
+        Mdl::Skate * pSkate = Mdl::Skate::Instance();
+        Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+	    if ( pSkater )
+        {
+            int size = Game::vMAX_CREATED_TRICKS;
+            for ( int i = 1; i < size; i++ )    // start at one because 0 is just a clipboard!
+        	{
+                int full = 0;
+                if ( pSkater->m_created_trick[i]->mp_other_params->GetInteger( CRCD(0x1f802b5f,"full"), &full, Script::NO_ASSERT ) )
+                {
+                    if ( full )
+                    {
+                        cat_list[num_cats_found] = i; //trick;
+                        num_cats_found++;
+                    }
+                }
+            }
+        }
+    }
+
+	if ( num_found == 0 )
+		return false;
+
+	// create an array to return
+	Script::CArray* p_configurable_tricks = new Script::CArray();
+	p_configurable_tricks->SetSizeAndType( num_found, ESYMBOLTYPE_NAME );
+	for ( int i = 0; i < num_found; i++ )
+		p_configurable_tricks->SetChecksum( i, trick_list[i] );
+
+	// add the array
+	pScript->GetParams()->AddArrayPointer( "ConfigurableTricks", p_configurable_tricks );
+
+    if ( num_cats_found > 0 )
+    {
+        // create an array to return
+    	Script::CArray* p_configurable_cats = new Script::CArray();
+    	p_configurable_cats->SetSizeAndType(num_cats_found, ESYMBOLTYPE_INTEGER );
+    	for ( int i = 0; i < num_cats_found; i++ )
+    		p_configurable_cats->SetInteger( i, cat_list[i] );
+    
+    	// add the array
+    	pScript->GetParams()->AddArrayPointer( "ConfigurableCats", p_configurable_cats );
+    }
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | TrickIsLocked | true if the trick is associated with a skater
+// that is locked
+// @parm name | trick | the trick name
+bool ScriptTrickIsLocked( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 trick;
+	pParams->GetChecksum( "trick", &trick, Script::ASSERT );
+
+	Script::CStruct* p_trick = Script::GetStructure( trick, Script::ASSERT );
+
+	if ( p_trick )
+	{
+		Script::CStruct* p_trick_params;
+		p_trick->GetStructure( "Params", &p_trick_params, Script::ASSERT );
+
+		uint32 skater;
+		if ( p_trick_params && p_trick_params->GetChecksum( CRCD(0x5b8ab877,"skater"), &skater, Script::NO_ASSERT ) )
+		{
+			Obj::CPlayerProfileManager* pPlayerProfileManager = Mdl::Skate::Instance()->GetPlayerProfileManager();
+			
+			if ( p_trick_params->ContainsFlag( "OnlyWith" ) )
+			{
+				Obj::CSkaterProfile* pCurrentProfile = pPlayerProfileManager->GetCurrentProfile();
+				return ( pCurrentProfile->GetSkaterNameChecksum() != skater );
+			}
+			else
+			{
+				int num_profiles = pPlayerProfileManager->GetNumProfileTemplates();
+				for ( int i = 0; i < num_profiles; i++ )
+				{
+					Obj::CSkaterProfile* pProfile = pPlayerProfileManager->GetProfileTemplateByIndex( i );
+					if ( pProfile->GetSkaterNameChecksum() == skater )
+					{
+						int isLocked = 0;
+						pProfile->GetInfo()->GetInteger( "is_hidden", &isLocked, Script::NO_ASSERT );
+						return ( isLocked != 0 );
+					}
+				}
+			}
+			Dbg_MsgAssert( 0, ( "TrickIsLocked confused by skater %s in trick %s\n", Script::FindChecksumName( skater ), Script::FindChecksumName( trick ) ) );
+		}
+	}
+	else
+		Dbg_MsgAssert( 0, ( "TrickIsLocked couldn't find trick %s\n", Script::FindChecksumName( trick ) ) );
+
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetTrickDisplayText | gets the text corresponding to this trick
+// @parm name | trick | the trick name
+bool ScriptGetTrickDisplayText( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 trick;
+	pParams->GetChecksum( "trick", &trick, Script::ASSERT );
+
+	Script::CStruct* p_trick = Script::GetStructure( trick, Script::ASSERT );
+
+	if ( p_trick )
+	{
+		Script::CStruct* p_trick_params;
+		p_trick->GetStructure( "Params", &p_trick_params, Script::ASSERT );
+
+		const char* p_trick_name;
+		p_trick_params->GetLocalString( CRCD(0xa1dc81f9,"name"), &p_trick_name, Script::ASSERT );
+
+		pScript->GetParams()->AddString( "trick_display_text", p_trick_name );
+
+		// look for any double tap tricks
+		uint32 extra_trick = 0;
+		if ( !p_trick_params->GetChecksum( "ExtraTricks", &extra_trick, Script::NO_ASSERT ) )
+		{
+			// look for an array of extra tricks and grab the first one
+			Script::CArray* p_extra_tricks;
+			if ( p_trick_params->GetArray( "ExtraTricks", &p_extra_tricks, Script::NO_ASSERT ) )
+				extra_trick = p_extra_tricks->GetChecksum( 0 );
+		}
+
+		// add any extra trick info
+		if ( extra_trick )
+		{
+			// grab this extra trick
+			Script::CArray* p_temp = Script::GetArray( extra_trick, Script::ASSERT );
+			Script::CStruct* p_extra_trick = p_temp->GetStructure( 0 );
+			Dbg_Assert( p_extra_trick );
+			Script::CStruct* p_extra_trick_params;
+			p_extra_trick->GetStructure( "Params", &p_extra_trick_params, Script::ASSERT );
+			const char* p_extra_trick_string;
+			p_extra_trick_params->GetString( CRCD(0xa1dc81f9,"name"), &p_extra_trick_string, Script::ASSERT );
+			pScript->GetParams()->AddString( "extra_trick_string", p_extra_trick_string );
+			pScript->GetParams()->AddChecksum( "extra_trick_checksum", extra_trick );
+		}
+
+		return true;
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetSpecialTrickInfo | gets the special trick info for the slot
+// specified
+// @parm int | index | the special trick slot
+bool ScriptGetSpecialTrickInfo( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int index;
+	pParams->GetInteger( "index", &index, Script::ASSERT );
+
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* p_SkaterProfile = skate_mod->GetCurrentProfile();
+	Obj::SSpecialTrickInfo trick_info = p_SkaterProfile->GetSpecialTrickInfo( index );
+
+	pScript->GetParams()->AddChecksum( "special_trickslot", trick_info.m_TrickSlot );
+	
+    
+    if ( trick_info.m_isCat )
+    {
+        pScript->GetParams()->AddInteger( "special_trickname", trick_info.m_TrickName );
+    }
+    else
+    {
+        pScript->GetParams()->AddChecksum( "special_trickname", trick_info.m_TrickName );
+    }
+    
+    pScript->GetParams()->AddInteger( "isCat", trick_info.m_isCat );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetTrickType | gets the type of the specified trick and returns
+// the result in the trick_type param
+// @parm name | trick | the name of the trick
+bool ScriptGetTrickType( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 trick_checksum;
+	pParams->GetChecksum( "trick", &trick_checksum, Script::ASSERT );
+
+	Script::CStruct* p_trick = Script::GetStructure( trick_checksum, Script::ASSERT );
+
+	uint32 trick_type;
+	// first check for optional TrickType var
+	if ( !p_trick->GetChecksum( "TrickType", &trick_type, Script::NO_ASSERT ) )
+	{
+		if ( !p_trick->GetChecksum( "Scr", &trick_type, Script::NO_ASSERT ) )
+		{
+			// assume it's a grind
+			trick_type = Script::GenerateCRC( "GrindTrick" );
+		}
+	}
+	pScript->GetParams()->AddChecksum( "trick_type", trick_type );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetIndexOfItemContaining | this will search through the given
+// array (must be an array of structures) for the structure containing a param
+// the index will be placed in the calling script's params (index)
+// @parm array | array | the array to search
+// @parmopt int | index | 0 | index to start at
+// @parm name | name | name of the param to search for
+// @parm name | value | value param should have for a match
+bool ScriptGetIndexOfItemContaining( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	uint32 array_name;
+	pParams->GetChecksum( "array", &array_name, Script::ASSERT );
+	Script::CArray* p_array = Script::GetArray( array_name, Script::ASSERT );
+	Dbg_MsgAssert( p_array->GetType() == ESYMBOLTYPE_STRUCTURE, ( "This function only works on arrays of structures." ) );
+
+	uint32 item;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &item, Script::ASSERT );
+
+	int index = 0;
+	pParams->GetInteger( "index", &index, Script::NO_ASSERT );
+
+	uint32 value;
+	pParams->GetChecksum( "value", &value, Script::ASSERT );
+
+	int array_size = p_array->GetSize();
+	for ( ; index < array_size; index++ )
+	{
+		Script::CStruct* p_struct = p_array->GetStructure( index );
+		if ( p_struct->ContainsComponentNamed( item ) )
+		{
+			uint32 item_value;
+			p_struct->GetChecksum( item, &item_value, Script::ASSERT );
+			if ( item_value == value )
+			{
+				pScript->GetParams()->AddInteger( "index", index );
+				return true;
+			}
+		}
+	}
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetLevelRecords | appends the records to the calling script's params
+bool ScriptGetLevelRecords( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	
+	int levelNum;
+	if ( !pParams->GetInteger( "level", &levelNum, Script::NO_ASSERT ) )
+		levelNum = pSkate->GetCareer()->GetLevel();	
+	
+	Records::CGameRecords* pGameRecords = pSkate->GetGameRecords();
+	Records::CLevelRecords* pLevelRecords = pGameRecords->GetLevelRecords( levelNum );
+	pLevelRecords->WriteIntoStructure( pScript->GetParams() );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ResetComboRecords | resets the score object's combo records
+bool ScriptResetComboRecords( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = pSkate->GetLocalSkater();
+	if( pSkater )
+	{
+		Mdl::Score* pScore = pSkater->GetScoreObject();
+	
+		pScore->ResetComboRecords();
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetNumberOfTrickOccurrences | returns the number of times
+// the given trick appears in the skater's current score pot
+// @parm string | TrickText | trick string to look for
+bool ScriptGetNumberOfTrickOccurrences( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	const char* p_trick;
+	pParams->GetString( "TrickText", &p_trick, Script::ASSERT );
+	uint32 trick = Script::GenerateCRC( p_trick );
+
+	Mdl::Skate * skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater* pSkater = skate_mod->GetLocalSkater();
+	Mdl::Score* pScore = pSkater->GetScoreObject();
+
+	int num = pScore->GetCurrentNumberOfOccurrencesByName( trick );
+	pScript->GetParams()->AddInteger( "number_of_occurrences", num );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetNumSoundtracks | returns the number of soundtracks
+// in the numSoundtracks param
+bool ScriptGetNumSoundtracks( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Dbg_MsgAssert( Config::GetHardware() == Config::HARDWARE_XBOX, ( "GetNumSoundtracks can only be called on XBox" ) );
+	int numSoundtracks = Nx::CEngine::sGetNumSoundtracks();
+	pScript->GetParams()->AddInteger( "numSoundtracks", numSoundtracks );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetSoundtrackName( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Dbg_MsgAssert( Config::GetHardware() == Config::HARDWARE_XBOX, ( "GetNumSoundtracks can only be called on XBox" ) );
+	int soundtrack_number;
+	pParams->GetInteger( NONAME, &soundtrack_number, Script::ASSERT );
+	const char* pSoundtrackName = Nx::CEngine::sGetSoundtrackName( soundtrack_number );
+	pScript->GetParams()->AddString( "soundtrackName", pSoundtrackName );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | BindControllerToSkater | remaps the given controller to the
+// skater
+// @parm int | controller | the controller number
+// @parm int | skater_heap_index | the skater num
+bool ScriptBindControllerToSkater( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int controller;
+	pParams->GetInteger( "controller", &controller, Script::ASSERT );
+
+	int skater;
+	pParams->GetInteger( "skater_heap_index", &skater, Script::ASSERT );
+
+	printf("attempting to bind skater %i to controller %i\n", skater, controller);
+
+	Mdl::Skate *pSkate = Mdl::Skate::Instance();
+	pSkate->m_device_server_map[skater] = controller;
+
+	pSkate->UpdateSkaterInputHandlers();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptBindFrontEndToController( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int controller;
+	pParams->GetInteger( "controller", &controller, Script::ASSERT );
+
+	int front_end_pad;
+	pParams->GetInteger( "front_end_pad", &front_end_pad, Script::ASSERT );
+
+	printf("attempting to bind front end %i to controller %i\n", front_end_pad, controller);
+
+	Mdl::FrontEnd *pFrontEnd = Mdl::FrontEnd::Instance();
+	
+	// find any controllers using this front end mapping and switch
+	int current = pFrontEnd->m_device_server_map[front_end_pad];
+	for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+	{
+		if ( pFrontEnd->m_device_server_map[i] == controller )
+			pFrontEnd->m_device_server_map[i] = current;
+	}
+	pFrontEnd->m_device_server_map[front_end_pad] = controller;
+
+	pFrontEnd->UpdateInputHandlerMappings();
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | ControllerBoundToDifferentSkater | 
+// @parm int | controller |
+// @parm int | skater | 
+bool ScriptControllerBoundToDifferentSkater( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int skater;
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater, Script::ASSERT );
+
+	int controller;
+	pParams->GetInteger( "controller", &controller, Script::ASSERT );
+
+	Mdl::Skate* pSkate = Mdl::Skate::Instance();
+	
+	for ( int i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		// don't check the skater we're trying to bind
+		if ( i == skater )
+			continue;
+
+		Obj::CSkater* pSkater = pSkate->GetSkater( i );
+		if ( pSkater && pSkater->IsLocalClient() )
+		{
+			if ( pSkate->m_device_server_map[i] == controller )
+				return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptControllerBoundToSkater( Script::CStruct* pParams, Script::CScript* pScript )
+{
+	int controller;
+	pParams->GetInteger( "controller", &controller, Script::ASSERT );
+
+	int skater;
+	pParams->GetInteger( CRCD(0x5b8ab877,"skater"), &skater, Script::ASSERT );
+
+	Dbg_MsgAssert( skater >= 0 && skater < Mdl::Skate::vMAX_SKATERS, ( "Bad skater index %i passed to ControllerBoundToSkater\n", skater ) );
+	return ( Mdl::Skate::Instance()->m_device_server_map[skater] == controller );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetKeyComboArrayFromTrickArray | returns an array of key combos (KeyCombos)
+// that correspond to the passed trick array
+// @parm array | tricks | array of trick names, eg [Trick_KickFlip Trick_HeelFlip]
+bool ScriptGetKeyComboArrayFromTrickArray( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Script::CArray* pTricks;
+	pParams->GetArray( "tricks", &pTricks, Script::ASSERT );
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	Obj::CSkaterProfile* pSkaterProfile = pSkate->GetCurrentProfile();
+	Script::CStruct* pTrickMapping = pSkaterProfile->GetTrickMapping( CRCD(0xd544aa2d,"trick_mapping") );
+
+	int size = pTricks->GetSize();
+	// make a return array
+	Script::CArray* pKeyCombos = new Script::CArray();
+	pKeyCombos->SetSizeAndType( size, ESYMBOLTYPE_NAME );
+
+	for ( int i = 0; i < size; i++ )
+	{
+		uint32 trick_checksum = pTricks->GetChecksum( i );
+		
+		// search for this trick in the current mapping
+		uint32 key_combo = s_find_trick_in_mapping( pTrickMapping, trick_checksum, 0 );
+
+		if ( key_combo == 0 )
+		{
+			// look for a special trick
+			int num_specials = pSkaterProfile->GetNumSpecialTrickSlots();
+			for ( int j = 0; j < num_specials; j++ )
+			{
+				Obj::SSpecialTrickInfo trick_info = pSkaterProfile->GetSpecialTrickInfo( j );
+				if ( trick_info.m_TrickName == trick_checksum )
+				{
+					key_combo = trick_info.m_TrickSlot;
+					break;
+				}
+			}			
+		}
+
+		Dbg_MsgAssert( key_combo != 0, ( "GetKeyComboFromTrickArray found an unmpped trick - %s", Script::FindChecksumName( trick_checksum ) ) );
+		pKeyCombos->SetChecksum( i, key_combo );
+	}
+
+	pScript->GetParams()->AddArray( "KeyCombos", pKeyCombos );
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | FirstInputReceived | the game does not start checking for
+// disconnected controllers until this is called
+bool ScriptFirstInputReceived( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate::Instance()->FirstInputReceived();
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | VibrateController | 
+// @parm int | port | 
+// @parm int | actuator |
+// @parm int | percent |
+bool ScriptVibrateController( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int port;
+	pParams->GetInteger( "port", &port, Script::ASSERT );
+
+	int actuator;
+	pParams->GetInteger( "actuator", &actuator, Script::ASSERT );
+
+	int percent;
+	pParams->GetInteger( "percent", &percent, Script::ASSERT );
+	
+	SIO::Manager* sio_manager = SIO::Manager::Instance();
+
+	// TODO: this won't work if we support multitap (assumes slot 0)
+	SIO::Device* pDevice = sio_manager->GetDevice( port, 0 );
+	if ( pDevice )
+	{
+		pDevice->ActivateActuator( actuator, percent );
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | LockCurrentSkaterProfileIndex | 
+bool ScriptLockCurrentSkaterProfileIndex( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int locked = true;
+	pParams->GetInteger( NONAME, &locked, Script::ASSERT );
+
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+	Obj::CPlayerProfileManager* pProfileMan = pSkate->GetPlayerProfileManager();
+	pProfileMan->LockCurrentSkaterProfileIndex( locked );
+
+	return true;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | SetSpecialTrickInfo | 
+// @parm int | slot | 
+// @parm name | trick_name | 
+// @parm name | key_combo |
+// @flag update_mappings | 
+bool ScriptSetSpecialTrickInfo( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int slot;
+	pParams->GetInteger( "slot", &slot, Script::ASSERT );
+
+	uint32 trick_name;
+	pParams->GetChecksum( "trick_name", &trick_name, Script::ASSERT );
+	
+	uint32 key_combo;
+	pParams->GetChecksum( "key_combo", &key_combo, Script::ASSERT );
+
+	Mdl::Skate* skate_mod = Mdl::Skate::Instance();
+	Obj::CPlayerProfileManager* pPlayerProfileManager = skate_mod->GetPlayerProfileManager();
+	Obj::CSkaterProfile* pProfile = NULL;
+	pProfile = pPlayerProfileManager->GetCurrentProfile();
+
+	Dbg_MsgAssert( pProfile, ( "SetSpecialTrickInfo couldn't get a profile" ) );
+	if ( pProfile )
+	{
+		// printf("setting slot %i to %s, %s\n", slot, Script::FindChecksumName( trick_name ), Script::FindChecksumName( key_combo ) );
+		Obj::SSpecialTrickInfo trickInfo;
+		trickInfo.m_TrickName = trick_name;
+		trickInfo.m_TrickSlot = key_combo;
+		pProfile->SetSpecialTrickInfo( slot, trickInfo, false );
+		return true;
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static bool s_is_angle(Script::CArray *p_angles_array, uint32 component_name)
+{
+	if (!p_angles_array)
+	{
+		return false;
+	}
+
+	uint32 size=p_angles_array->GetSize();
+	for (uint32 i=0; iGetChecksum(i))
+		{
+			return true;
+		}
+	}
+	
+	return false;
+}
+	
+static float make_angle_in_range(float a)
+{
+	while (a < 0.0f)
+	{
+		a+=360.0f;
+	}
+	while (a > 360.0f)
+	{
+		a-=360.0f;
+	}
+	return a;
+}
+		
+// @script | InterpolateParameters | This will scan through the structure A, and any parameters in it
+// which have type int or float and such that the same named parameter exists in B and also has type int
+// or float will have their value interpolated between the two values, and the parameter put into a
+// returned structure named Interpolated.
+// Note that the returned structure will only contain the parameters that were interpolated, so if
+// structure A contained a string, that parameter will not get into Interpolated
+// @parm structure | a | The first structure
+// @parm structure | b | The second structure
+// @parmopt float | Proportion | 0.0 | A value between 0 and 1. 0 means all of structure A and none of structure B,
+// 1 means all of B and none of A. 0.5 will therefore be half way between the two.
+// @parmopt array | Ignore | | An optional array of parameter names to ignore. Parameters whose type cannot
+// be interpolated, such as strings, will automatically be ignored, but the Ignore array allows certain
+// integer or float params to be ignored too.
+// @parmopt array | Angles | | An optional array of parameters whose values are to be treated as angles.
+// So when interpolating between 10 and 350 degrees for example, instead of simply interpolating from
+// the number 10 to 350, it will go from 10 backwards through 0 and 359 to 350.
+bool ScriptInterpolateParameters( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Script::CStruct *p_new_structure=new Script::CStruct;
+	
+	Script::CStruct *p_a=NULL;
+	pParams->GetStructure(CRCD(0x174841bc,"a"),&p_a,Script::ASSERT);
+	
+	Script::CStruct *p_b=NULL;
+	pParams->GetStructure(CRCD(0x8e411006,"b"),&p_b,Script::ASSERT);
+	
+	float proportion=0.0f;
+	pParams->GetFloat(CRCD(0x404e690d,"Proportion"),&proportion);
+	
+	Script::CArray *p_ignore=NULL;
+	pParams->GetArray(CRCD(0xf277291d,"Ignore"),&p_ignore);
+	
+	Script::CArray *p_angles_array=NULL;
+	pParams->GetArray(CRCD(0x9d2d0915,"Angles"),&p_angles_array);
+	
+	Script::CComponent *p_comp = p_a->GetNextComponent();
+	while (p_comp)
+	{
+		bool ignore=false;
+		
+		if (p_ignore)
+		{
+			for (uint32 i=0; iGetSize(); ++i)
+			{
+				if (p_comp->mNameChecksum==p_ignore->GetChecksum(i))
+				{
+					ignore=true;
+					break;
+				}
+			}
+		}
+
+		if (!ignore)
+		{
+			uint32 component_name=0;
+			float value_a=0.0f;
+			bool write_integer=true;
+			
+			// See if the component in structure A is of a type that needs to be interpolated.
+			if (p_comp->mType==ESYMBOLTYPE_INTEGER)
+			{
+				component_name=p_comp->mNameChecksum;
+				value_a=(float)p_comp->mIntegerValue;
+			}
+			else if (p_comp->mType==ESYMBOLTYPE_FLOAT)
+			{
+				component_name=p_comp->mNameChecksum;
+				value_a=p_comp->mFloatValue;
+				write_integer=false;
+			}
+			
+			// If found a candidate for interpolation, see if there is a similarly named
+			// component in structure B, and if so, interpolate between the two and stick
+			// a new component of that name into the new structure.
+			if (component_name)
+			{
+				bool got_value_b=false;
+				
+				float value_b=0.0f;
+				int integer_value_b=0;
+				if (p_b->GetInteger(component_name,&integer_value_b))
+				{
+					got_value_b=true;
+					value_b=(float)integer_value_b;
+				}
+				else if (p_b->GetFloat(component_name,&value_b))
+				{
+					got_value_b=true;
+					write_integer=false;
+				}
+				
+				if (got_value_b)
+				{
+					float new_value=0.0f;
+					
+					
+					if (s_is_angle(p_angles_array,component_name))
+					{
+						float d=value_b-value_a;
+						d=make_angle_in_range(d);
+						if (d > 180.0f)
+						{
+							d=-(360.0f-d);
+						}
+						new_value=value_a+proportion*d;
+						new_value=make_angle_in_range(new_value);
+					}
+					else
+					{
+						new_value=value_a+proportion*(value_b-value_a);
+					}	
+						
+					if (write_integer)
+					{
+						p_new_structure->AddInteger(component_name,(int)new_value);
+					}
+					else
+					{
+						p_new_structure->AddFloat(component_name,new_value);
+					}	
+				}
+			}
+		}
+				
+		p_comp = pParams->GetNextComponent(p_comp);
+	}	
+	
+	pScript->GetParams()->AddStructurePointer(CRCD(0xff6f3872,"Interpolated"),p_new_structure);
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				
+// @script | PlaySkaterStream | skater member func.
+// @parm string | type | the stream type.
+// @parmopt int | num_possible | 10 | the maximum number of streams to search for
+bool ScriptPlaySkaterStream ( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Dbg_Assert(pScript->mpObject);
+	Dbg_MsgAssert(pScript->mpObject->GetType() == SKATE_TYPE_SKATER, ("PlaySkaterStream may only be called on a skater object"));
+	
+	Obj::CSkater* p_skater = static_cast< Obj::CSkater* >(pScript->mpObject.Convert());
+		
+	const char* p_type;
+	pParams->GetString( CRCD(0x7321a8d6,"type"), &p_type, Script::ASSERT );
+	
+	int max_num = 10;
+	pParams->GetInteger( CRCD(0x58492707,"num_possible"), &max_num, Script::NO_ASSERT );
+
+	Script::CArray* p_stream_indices = new Script::CArray();
+	p_stream_indices->SetSizeAndType( max_num, ESYMBOLTYPE_INTEGER );
+	// generate list of indices (start at 1)
+	for ( int i = 0; i < max_num; i++ )
+		p_stream_indices->SetInteger( i, i + 1 );
+
+	// randomize list
+	for ( int i = 0; i < max_num; i++ )
+	{
+		// grab a random index and switch with the current index
+		int random_index = Mth::Rnd( max_num );
+		int random_value = p_stream_indices->GetInteger( random_index );
+		p_stream_indices->SetInteger( random_index, p_stream_indices->GetInteger( i ) );
+		p_stream_indices->SetInteger( i, random_value );
+	}
+
+	// get the skater's first name
+	char p_first_name[128];
+	strcpy( p_first_name, p_skater->m_firstName );
+
+	// resolve to last name
+	char p_last_name[128];
+
+	if ( p_skater->m_isPro )
+	{
+		// grab the last name string from global array
+		Script::CStruct* pLastNames = Script::GetStructure( CRCD(0x5775194e,"goal_pro_last_names"), Script::ASSERT );
+		uint32 first_name_checksum = Script::GenerateCRC( p_first_name );
+		const char* p_temp_last_name;
+		pLastNames->GetString( first_name_checksum, &p_temp_last_name, Script::ASSERT );
+		Dbg_MsgAssert( strlen( p_temp_last_name ) < 128, ( "buffer overflow" ) );
+		strcpy( p_last_name, p_temp_last_name );
+	}
+	else
+	{
+		strcpy( p_last_name, "custom" );
+		if ( p_skater->m_isMale )
+		{
+			strcat( p_last_name, "m" );
+		}
+		else
+		{
+			strcat( p_last_name, "f" );
+		}
+	}
+		
+	Dbg_MsgAssert( strlen( p_last_name ) < 128, ( "buffer overflow in PlaySkaterStream: %s", p_last_name ) );
+	// find the first valid stream and play
+	char stream_name[128];
+		
+	for ( int i = 0; i < max_num; i++ )
+	{
+		Dbg_MsgAssert( strlen( p_last_name ) + strlen( p_type ) + 2 < 128, ( "buffer overflow in PlaySkaterStream: %s", stream_name ) );
+		sprintf( stream_name, "%s_%s%02i", p_last_name, p_type, p_stream_indices->GetInteger( i ) );
+		
+		// printf("figured a stream name of %s\n", stream_name);
+		if ( Pcm::StreamExists( Script::GenerateCRC( stream_name ) ) )
+		{
+			Script::CStruct* pTemp = new Script::CStruct;
+			pTemp->AddChecksum( "stream_checksum", Script::GenerateCRC( stream_name ) );
+			Script::RunScript( "skater_play_bail_stream", pTemp, pScript->mpObject );
+			delete pTemp;
+			break;
+		}
+	}
+	Script::CleanUpArray( p_stream_indices );
+	delete p_stream_indices;
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetTextureFromPath | returns the file name for a texture
+// from the end of the give path
+// @parm string | path | path of file including file name
+bool ScriptGetTextureFromPath( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	const char* p_path;
+    char new_string[32];
+    int i, t=0;
+
+	pParams->GetString( CRCD(0xf4ab74f0,"path"), &p_path, Script::ASSERT );
+
+    int length = strlen( p_path );
+
+    // find last '/' in path
+    for (i=(length); i>=0; i--)	 	// Mick: was previously "length+1", which would start at the character AFTER the string
+    {								// which on rare occasions was a '/' char, which caused obscure crashes.
+        if ( ( p_path[i] == '/' ) || ( p_path[i] == '\\' ) )
+        {
+            break;
+        }
+    }
+
+    // copy everything after '/' into texture
+    for (int p=(i+1); p<=length; p++)
+    {
+        new_string[t] = p_path[p];
+        t++;
+    }
+	
+
+	pScript->GetParams()->AddString( CRCD(0x7d99f28d,"texture"), new_string );
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | GetVramUsage | 
+bool ScriptGetVramUsage( Script::CStruct *pParams, Script::CScript *pScript )
+{
+#	ifdef __PLAT_NGPS__
+	//printf("FontVramStart = %uK \n", (NxPs2::FontVramStart/4) );
+    //printf("FontVramBase = %uK \n", (NxPs2::FontVramBase/4) );
+    printf("FontVramSize = %uK \n", (NxPs2::FontVramSize/4) );
+    printf("Font Usage = %uK \n", ( (NxPs2::FontVramBase - NxPs2::FontVramStart )/4) );
+#	endif
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// @script | CompositeObjectExists | 
+bool ScriptCompositeObjectExists ( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 name;
+	pParams->GetChecksum( CRCD(0xa1dc81f9,"name"), &name, Script::ASSERT );
+	Obj::CObject* pObj = Obj::ResolveToObject( name );
+	return ( pObj != NULL );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptClearPowerups( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int i;
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+
+	for( i = 0; i < Mdl::Skate::vMAX_SKATERS; i++ )
+	{
+		Obj::CSkater* pSkater = pSkate->GetSkater( i );
+		if( pSkater )
+		{
+			Obj::CSkaterStateComponent *p_component = (Obj::CSkaterStateComponent*)Obj::CCompositeObjectManager::Instance()->GetFirstComponentByType( CRC_SKATERSTATE );
+			p_component->ClearPowerups();
+		}
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptBroadcastProjectile( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 type;
+	Mth::Vector pos, vel;
+	int radius;
+	float scale;
+	uint32 id;
+	GameNet::Manager * gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client;
+	
+	pParams->GetChecksum( CRCD(0x830ecaf,"objID"), &id );
+	pParams->GetChecksum( CRCD(0x7321a8d6,"type"), &type );
+	pParams->GetVector( CRCD(0x7f261953,"pos"), &pos );
+	pParams->GetVector( CRCD(0xc4c809e,"vel"), &vel );
+	pParams->GetFloat( CRCD(0x13b9da7b,"scale"), &scale );
+	pParams->GetInteger( CRCD(0xc48391a5,"radius"), &radius );
+
+	client = gamenet_man->GetClient( 0 );
+	if( client )
+	{
+		Net::MsgDesc msg_desc;
+		GameNet::MsgProjectile msg;
+		
+		msg.m_Id = id;
+		msg.m_Pos = pos;
+		msg.m_Vel = vel;
+		msg.m_Radius = radius;
+		msg.m_Scale = scale;
+		msg.m_Latency = 0;
+		msg.m_Type = type;
+
+		msg_desc.m_Id = GameNet::MSG_ID_SPAWN_PROJECTILE;
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgProjectile );
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+	
+		client->EnqueueMessageToServer( &msg_desc );
+	}
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptBroadcastEnterVehicle ( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	uint32 id;
+	uint32 control_type;
+	GameNet::Manager* gamenet_man = GameNet::Manager::Instance();
+	Net::Client* client;
+	
+	pParams->GetChecksum( CRCD(0x5b24faaa, "SkaterId"), &id, Script::ASSERT );
+	pParams->GetChecksum( CRCD(0x81cff663, "control_type"), &control_type, Script::ASSERT );
+	
+	client = gamenet_man->GetClient( 0 );
+	if( client )
+	{
+		Net::MsgDesc msg_desc;
+		GameNet::MsgEnterVehicle msg;
+		
+		msg.m_Id = id;
+		msg.m_ControlType = control_type;
+
+		msg_desc.m_Id = GameNet::MSG_ID_ENTER_VEHICLE;
+		msg_desc.m_Data = &msg;
+		msg_desc.m_Length = sizeof( GameNet::MsgEnterVehicle );
+		msg_desc.m_Queue = Net::QUEUE_SEQUENCED;
+		msg_desc.m_GroupId = GameNet::vSEQ_GROUP_PLAYER_MSGS;
+	
+		client->EnqueueMessageToServer( &msg_desc );
+	}
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool ScriptGetCollidingPlayerAndTeam( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int team = -1;
+	int player_id = -1;
+    Script::CStruct* p_pass_back_params = pScript->GetParams();
+	float nearest_distance = 999999.9999f;
+	Obj::CCompositeObject* flag_obj;
+
+	flag_obj = static_cast ( pScript->mpObject.Convert() );
+	Dbg_Assert( flag_obj );
+	
+	int exclude_team;
+	pParams->GetInteger(CRCD(0x6c2a140f, "exclude_team"), &exclude_team, Script::ASSERT);
+	
+	float radius_squared;
+	pParams->GetFloat(CRCD(0xc48391a5, "radius"), &radius_squared, Script::ASSERT);
+	radius_squared = FEET_TO_INCHES(radius_squared) * FEET_TO_INCHES(radius_squared);
+	
+	GameNet::PlayerInfo* player;
+	for (uint32 i = 0; i < Mdl::Skate::Instance()->GetNumSkaters(); i++)
+	{
+		Obj::CCompositeObject* p_skater = Mdl::Skate::Instance()->GetSkater(i);
+		
+		if( p_skater )
+		{
+			player = GameNet::Manager::Instance()->GetPlayerByObjectID(p_skater->GetID());
+			if (!player) continue;
+
+			// If a player on the flag's team collides with the flag and doesn't have an enemy flag, just ignore it
+			if ((player->m_Team == exclude_team) && !player->HasCTFFlag()) continue;
+
+			// If a player already has a flag, just ignore it. They're being greedy.
+			if ((player->m_Team != exclude_team ) && player->HasCTFFlag()) continue;
+			
+			float this_dist = Mth::DistanceSqr( p_skater->m_pos, flag_obj->GetPos());
+			
+			if (this_dist < radius_squared && this_dist < nearest_distance)
+			{
+				nearest_distance = this_dist;
+				player_id = p_skater->GetID();
+			}
+		}
+	}
+
+	if (player_id > -1)
+	{
+		player = GameNet::Manager::Instance()->GetPlayerByObjectID(player_id);
+		team = player->m_Team;
+	}
+
+	p_pass_back_params->AddInteger(CRCD(0x3b1f59e0, "team"), team);
+	p_pass_back_params->AddInteger(CRCD(0x67e6859a, "player"), player_id);
+	return true;
+}
+
+bool ScriptLobbyCheckKeyboard( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	int num_chars;
+	char makes[256];
+    
+	num_chars = SIO::KeyboardRead( makes );
+
+    if( num_chars > 0 )
+	{
+		// Space brings up the chat interface
+		if( makes[0] == 32 )
+		{
+			Script::CStruct* pParams;
+			
+			// Enter and space act as "choose" only if you're not currently using the on-screen keyboard
+            pParams = new Script::CStruct;
+			pParams->AddChecksum( Script::GenerateCRC( "id" ), Script::GenerateCRC( "keyboard_anchor" ));
+            if( Obj::ScriptObjectExists( pParams, NULL ) == false )
+			{
+                Script::RunScript( "lobby_enter_kb_chat" );
+                SIO::KeyboardClear();
+			}
+            delete pParams;
+		}
+    }
+    return true;
+}
+ 
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*
+bool ScriptMarkRestarts ( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	// Green:	Player1
+	// Purple:	Multiplayer
+	// Cyan:	Horse
+	// Red:		CTF
+	// Blue:	Team
+	// Yellow:	Crown
+	
+	Script::CArray *pNodeArray = Script::GetArray(CRCD(0xc472ecc5, "NodeArray"));
+	for (int i = pNodeArray->GetSize(); i--; )
+	{
+		uint32 color = 0;
+		Mth::Vector offset;
+		
+		uint32 node_class;
+		pNodeArray->GetStructure(i)->GetChecksum(CRCD(0x12b4e660, "Class"), &node_class);
+		if (node_class == CRCD(0x1806ddf8, "Restart"))
+		{
+			Script::CArray *pRestartTypes;
+			pNodeArray->GetStructure(i)->GetArray(CRCD(0xdd304987, "restart_types"), &pRestartTypes);
+			for (int j = pRestartTypes->GetSize(); j--; )
+			{
+				switch (pRestartTypes->GetChecksum(j))
+				{
+					case CRCC(0x41639ce5, "Player1"):
+						color = MAKE_RGB(0, 255, 0);
+						offset.Set(0.0f, 0.0f, 0.0f);
+						break;
+					case CRCC(0xbae0e4da, "Multiplayer"):
+						color = MAKE_RGB(255, 0, 255);
+						offset.Set(1.0f, 0.0f, 0.0f);
+						break;
+					case CRCC(0x9d65d0e7, "Horse"):
+						color = MAKE_RGB(0, 255, 255);
+						offset.Set(0.0f, 1.0f, 0.0f);
+						break;
+					case CRCC(0xa5ad2b0b, "CTF"):
+						color = MAKE_RGB(255, 0, 0);
+						offset.Set(0.0f, 0.0f, 1.0f);
+						break;
+					case CRCC(0x3b1f59e0, "Team"):
+						color = MAKE_RGB(0, 0, 255);
+						offset.Set(1.0f, 1.0f, 0.0f);
+						break;
+					default:
+						continue;
+				}
+			}
+		}
+		else if (node_class == CRCD(0xf8565321, "GenericNode"))
+		{
+			uint32 type;
+			pNodeArray->GetStructure(i)->GetChecksum(CRCD(0x7321a8d6, "Type"), &type);
+			if (type != CRCD(0xaf86421b, "Crown")) continue;
+			
+			color = MAKE_RGB(255, 255, 0);
+			offset.Set(0.0f, 1.0f, 1.0f);
+		}
+		else
+		{
+			continue;
+		}
+		
+		Mth::Vector pos;
+		SkateScript::GetPosition(i, &pos);
+		
+		Gfx::AddDebugLine(pos, pos + 12.0f * offset + Mth::Vector(0.0f, 140.0f, 0.0f), color, 0, 0);
+	}
+	
+	return true;
+}
+*/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace CFuncs
diff --git a/Code/Sk/Scripting/skfuncs.h b/Code/Sk/Scripting/skfuncs.h
new file mode 100644
index 0000000..8d5f69f
--- /dev/null
+++ b/Code/Sk/Scripting/skfuncs.h
@@ -0,0 +1,305 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		PS2														**
+**																			**
+**	Module:			Scripting												**
+**																			**
+**	File name:		cfuncs.h												**
+**																			**
+**	Created: 		09/14/2000	-	ksh										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SCRIPTING_SKFUNCS_H
+#define	__SCRIPTING_SKFUNCS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+};
+	
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace CFuncs
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+bool ScriptCurrentSkaterIsPro(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetGoalsCompleted(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetNextLevelRequirements(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetCurrentSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCurrentSkaterProfileIs(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAddSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAddTemporaryProfile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRememberTemporaryAppearance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRestoreTemporaryAppearance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSyncPlayer2Profile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPreloadModels( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPreloadPedestrians( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPreselectRandomPedestrians( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptReplaceCarTextures( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetSkaterProfileInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetSkaterProfileInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetUIFromPreferences(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetUIFromSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetPreferencesFromUI(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetDefaultAppearance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetDefaultTricks(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetDefaultStats(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRandomizeAppearance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPrintCurrentAppearance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetNeversoftSkater(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCurrentProfileIsLocked(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetSkaters(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetSkaterProfileProperty(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleAlwaysSpecial( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSkaterSpeedGreaterThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSkaterSpeedLessThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLastScoreLandedGreaterThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLastScoreLandedLessThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptAnyTotalScoreAtLeast( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptOnlyOneSkaterLeft( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptTotalScoreGreaterThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptTotalScoreLessThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCurrentScorePotGreaterThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCurrentScorePotLessThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSkaterGetScoreInfo( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGoalsGreaterThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGoalsEqualTo( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptMedalsGreaterThan( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptMedalsEqualTo( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptToggleStats(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptToggleSkaterCamMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetSkaterID( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetCurrentSkaterID( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptResetScore( Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUpdateScore( Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptPlaySkaterCamAnim( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetSkaterCamAnimSkippable( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetSkaterCamAnimShouldPause( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSkaterCamAnimFinished( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSkaterCamAnimHeld( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptKillSkaterCamAnim( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetSkaterCamAnimParams( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetCurrentSkaterCamAnimName( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptPlayMovingObjectAnim( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPlayCutscene( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptHasMovieStarted( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptIsMovieQueued( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMovingObjectAnimSkippable( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetMovingObjectAnimShouldPause( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptMovingObjectAnimFinished( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptMovingObjectAnimHeld( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptKillMovingObjectAnim( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptSetSkaterCamLerpReductionTimer( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptReloadSkaterCamAnim( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSkaterDebugOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSkaterDebugOff(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptVibrationIsOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptVibrationOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptVibrationOff(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAutoKickIsOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAutoKickOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAutoKickOff(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpinTapsAreOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpinTapsOn(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSpinTapsOff(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetCurrentProDisplayInfo(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetPlayerAppearance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetPlayerFacePoints(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetPlayerFacePoints(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetPlayerFaceTexture(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetPlayerFaceOverlayTexture(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptClearPlayerFaceTexture(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPlayerFaceIsValid(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSelectCurrentSkater(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCareerStartLevel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCareerLevelIs(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetRecordText(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUpdateRecords(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCareerReset(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnSetGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptJustGotGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnSetFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptJustGotFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetGlobalFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnSetGlobalFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetGlobalFlag(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptProfileEquals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIsCareerMode(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptClearScoreGoals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSetScoreGoal(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEndRun(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptShouldEndRun(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptInitializeSkaters(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEndRunSelected(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAllSkatersAreIdle(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptFirstTrickStarted( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptFirstTrickCompleted( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCalculateFinalScores(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptReinsertSkaters(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnhookSkaters(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptApplySplitScreenOptions(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStartCompetition(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStartCompetitionRun(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEndCompetitionRun(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIsTopJudge(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptPlaceIs(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRoundIs(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEndCompetition(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptCompetitionEnded(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStartHorse(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEndHorse(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptHorseEnded(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptHorseStatusEquals(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStartHorseRun(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEndHorseRun(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSwitchHorsePlayers(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetHorseString(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptIsCurrentHorseSkater(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptApplyToHorsePanelString(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptApplyToSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptApplyColorToSkaterProfile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRefreshSkaterColors(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRefreshSkaterScale(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRefreshSkaterVisibility(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRefreshSkaterUV(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAwardStatPoint(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAwardSpecialTrickSlot(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUpdateSkaterStats(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUpdateInitials( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptNewRecord(  Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptTrickOffAllObjects( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGameModeSetScoreDegradation( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGameModeSetScoreAccumulation( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptResetScoreDegradation( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSkaterIsBraking( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptLocalSkaterExists( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptInitSkaterModel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRefreshSkaterModel(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptEditPlayerAppearance(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetCurrentSkaterProfileIndex(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptGetCustomSkaterName(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptResetScorePot( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptPrintSkaterStats( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptPrintSkaterStats2( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptPrintSkaterPosition( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetSkaterPosition( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetSkaterVelocity( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetStatValue( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetNumStatPointsAvailable( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptUnlockSkater( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetActualCASOptionStruct( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetActualPlayerAppearancePart( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetPlayerAppearancePart( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetPlayerAppearanceColor( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetPlayerAppearanceScale( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptSetPlayerAppearanceUV( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptFlushDeadObjects( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptBindTrickToKeyCombo( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetKeyComboBoundToTrick( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptUpdateTrickMappings( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetConfigurableTricksFromType( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptTrickIsLocked( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetTrickDisplayText( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetSpecialTrickInfo( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptGetTrickType( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetIndexOfItemContaining( Script::CStruct* pParams, Script::CScript* pScript );
+bool ScriptForEachSkaterName( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptForEachSkaterProfile( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptResetAllToDefaultStats( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptResetAllToDefaultProfile( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptResetToDefaultProfile( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetLevelRecords( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptResetComboRecords( Script::CStruct* pParams, Script::CScript* pScript );
+
+bool ScriptGetSkaterProfileInfoByName( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSetSkaterProfileInfoByName( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptGetNumberOfTrickOccurrences( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptGetNumSoundtracks( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetSoundtrackName( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptControllerBoundToDifferentSkater( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptBindControllerToSkater( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptBindFrontEndToController( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptControllerBoundToSkater( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptGetKeyComboArrayFromTrickArray( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptFirstInputReceived( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptVibrateController( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLockCurrentSkaterProfileIndex( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptSetSpecialTrickInfo( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptInterpolateParameters( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptPlaySkaterStream ( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetTextureFromPath ( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptGetVramUsage ( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptCompositeObjectExists ( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptClearPowerups( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptBroadcastProjectile( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptBroadcastEnterVehicle ( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptGetCollidingPlayerAndTeam( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptLobbyCheckKeyboard( Script::CStruct *pParams, Script::CScript *pScript );
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace CFuncs
+
+#endif	// __SCRIPTING_SKFUNCS_H
+
diff --git a/Code/Sk/heap_sizes.h b/Code/Sk/heap_sizes.h
new file mode 100644
index 0000000..8fd28a0
--- /dev/null
+++ b/Code/Sk/heap_sizes.h
@@ -0,0 +1,138 @@
+#ifndef __SK_HEAP_SIZES_H
+#define __SK_HEAP_SIZES_H
+
+// Sizes of heaps
+
+#define	EXTRA 1000000
+
+
+#ifdef __PLAT_WN32__
+
+#define	_SCRIPT_HEAP_SIZE				(1024)
+#define	SCRIPT_CACHE_HEAP_SIZE			(1024)
+#define	FRONTEND_HEAP_SIZE				(1024)
+#define	NETWORK_HEAP_qSIZE				(1024)
+#define PROFILER_HEAP_SIZE					(1024)
+#define	SKATERINFO_HEAP_SIZE			(1024)
+#define	SKATER_HEAP_SIZE				(1024)		// default size of skater heap
+#define SKATER_GEOM_HEAP_SIZE			(1024)
+#define BOOTSTRAP_FRONTEND_HEAP_SIZE	(1024)
+#define INTERNET_HEAP_SIZE				(1024)
+#define NETMISC_HEAP_SIZE				(1024)
+#define	THEME_HEAP_SIZE     			 (1024)		// theme textures heap size
+
+#else
+
+#ifdef	__NOPT_ASSERT__
+// K: On debug builds line number info is included in the qb's so add in another 500K for it.
+// On release builds the line number info is excluded by passing the nolinenumbers switch to
+// cleanass when it is called from easyburn.bat
+#define	_SCRIPT_HEAP_SIZE					(3824000+500000+130000)
+#else
+#define	_SCRIPT_HEAP_SIZE					(3824000+130000)	   
+#endif
+
+// Mick: Note the FRONTEND_HEAP_SIZE encompasses both Frontend and Net heaps.
+// as they share the same region, with Net being top down.
+#define	FRONTEND_HEAP_SIZE					(1100000)  	 	// was 800000
+
+#define NETMISC_HEAP_SIZE					(160000)
+#define	BOOTSTRAP_FRONTEND_HEAP_SIZE		(FRONTEND_HEAP_SIZE-100000)		// we have no network play in bootstrap mode  
+#define INTERNET_HEAP_SIZE					(450000)
+#define PROFILER_HEAP_SIZE					(60000)
+
+// GJ:  I temporarily increased the size
+// of the skater info heap until we can figure
+// out how the face texture pathway is going to
+// work (right now, I need a heap that wouldn't
+// get destroyed when changing levels, to store
+// 17K worth of temporary face texture data)
+//#define	SKATERINFO_HEAP_SIZE			 	(40000)
+// Mick: Increased it again for 2P
+#define	SKATERINFO_HEAP_SIZE			 	(60000+2000+20000)	// extra 2000 for skater cams +20K for 2p fragmentation concerns
+
+#define	SKATER_HEAP_SIZE				 	(120000-40000+1000-2000)	// default size of skater heap, minus 2000 since skater cams moved to skater info heap
+#define	SKATER_GEOM_HEAP_SIZE			 	(680000 - 16000)		// default size of skater heap, plus a little extra cause we were running out for E3
+#define	THEME_HEAP_SIZE     			 	(204800)		// theme textures heap size
+
+#ifdef	__PLAT_NGPS__
+#ifdef	__NOPT_ASSERT__
+// On a regular development build, the memory usage is very different
+// to a "final=" build.  Firstly there is the extra debug code, mostly assertions,
+// but also memory blocks have a 32 byte rather than 16 byte header
+// and many data structures have extra fields for debugging purposes
+// this all means that the game will not fit in 32MB, so to fix this, the
+// script heap is placed in "debug" memory (>32MB)
+// Just that alone would result in there seeming to be MORE memory avaialbe in the normal build
+// so to conteeract that, we allocate a block off the bottom_up heap,
+// The size of this block is SCRIPT_HEAP_SIZE-DEBUG_ADJUSTMENT
+// So you need to adjust DEBUG_ADJUSTMENT so the amount of free memory
+// on the main heap is the same in a development build
+// and in a "final=" cd build, for example, if you had for CD final build:
+// 
+// Name            Used  Frag  Free   Min  Blocks                                  
+// --------------- ----- ----- ---- ------ ------                                  
+//     Top Down:  3942K    0K  894K  884K   3933 
+//     BottomUp: 23275K   23K  894K  884K  10432  
+//
+// and on a regular build:
+//
+//     Top Down:  4004K    0K  579K  523K   3933   
+//     BottomUp: 22115K   23K  579K  523K  10601
+//
+// Then you see that the regular build is underreporting the actual amount of free memory
+// and you should in theory add (894-579) = 315K
+// In practice it's a good idea to not push things right to the wire, as memory
+// usage can vary in differing situation (specifically when the number of blocks vary,
+// like a large number of small allocations vs a small number of large allocations.)
+// Since the above was from a worst case situation (NJ), I feel that 200K would be appropiate
+	
+#define	DEBUG_ADJUSTMENT					(1126400 + 200000 + 300000 + 100000 + 350000 ) // (1126400)		// difference in free memory for "final=" vs debug build
+#endif // __NOPT_ASSERT__
+#endif // __PLAT_NGPS__
+
+#endif // __PLAT_WN32__
+
+#ifdef __PLAT_XBOX__
+// Just need to override some of these values - want to keep as much the same as possible tho.
+#undef	SKATER_HEAP_SIZE
+#define	SKATER_HEAP_SIZE				( 120000 - 40000 - 2000)
+
+#undef	SKATER_GEOM_HEAP_SIZE
+#define	SKATER_GEOM_HEAP_SIZE			( 480000 )
+
+#undef	THEME_HEAP_SIZE
+#define	THEME_HEAP_SIZE					( 307200 )
+
+#undef	FRONTEND_HEAP_SIZE
+#define	FRONTEND_HEAP_SIZE				( 1050000 )
+
+
+#endif // __PLAT_XBOX__
+
+#ifdef __PLAT_NGC__
+// Just need to override some of these values - want to keep as much the same as possible tho.
+#undef	SKATER_HEAP_SIZE
+#define	SKATER_HEAP_SIZE				( ( 120000 - 40000 - 2000) + 1000 )
+#undef	PROFILER_HEAP_SIZE
+#define PROFILER_HEAP_SIZE					  (40000)
+#undef	FRONTEND_HEAP_SIZE
+#define	FRONTEND_HEAP_SIZE				( 650000 )
+#undef	SKATER_GEOM_HEAP_SIZE
+#define	SKATER_GEOM_HEAP_SIZE			 (470000)		// default size of skater heap
+#undef	THEME_HEAP_SIZE
+#define	THEME_HEAP_SIZE     			 	(330000)		// theme textures heap size
+#define	SCRIPT_HEAP_SIZE (_SCRIPT_HEAP_SIZE-(300000+(1024*1024)))
+
+#undef	SKATERINFO_HEAP_SIZE
+#define	SKATERINFO_HEAP_SIZE			 	(60000+2000+20000+18000)	// extra 2000 for skater cams +20K for 2p fragmentation concerns
+
+#define	AUDIO_HEAP_SIZE			 			(310*1024)
+#else
+// All platforms except for NGC
+#define	SCRIPT_HEAP_SIZE _SCRIPT_HEAP_SIZE
+#endif // __PLAT_NGC__
+
+
+#endif // __SK_HEAP_SIZES_H
+
diff --git a/Code/Sk/language.h b/Code/Sk/language.h
new file mode 100644
index 0000000..9f7388d
--- /dev/null
+++ b/Code/Sk/language.h
@@ -0,0 +1,47 @@
+#ifndef	__SK_LANGUAGE_H
+#define	__SK_LANGUAGE_H
+
+#ifdef __PLAT_XBOX__
+#include 
+#endif // __PLAT_XBOX__
+
+// These only apply if PAL
+#define ENGLISH 1
+#define FRENCH 0
+#define GERMAN 0
+
+inline bool IsEnglish( void )
+{
+#	ifdef __PLAT_XBOX__
+	DWORD lang = XGetLanguage();
+	if( lang == XC_LANGUAGE_ENGLISH )
+		return true;
+	else
+	{
+		// For NTSC, the only language allowed is English.
+		if( XGetVideoStandard() != XC_VIDEO_STANDARD_PAL_I )
+		{
+			return true;
+		}
+
+		// Any languages other than French and German should also be considered English.
+		if(( lang != XC_LANGUAGE_GERMAN ) && ( lang != XC_LANGUAGE_FRENCH ))
+		{
+			return true;
+		}
+	}
+	return false;
+//		return true;		// For now...
+#	else
+#	if ENGLISH
+	return true;
+#	else
+	return false
+#	endif // ENGLISH
+#	endif // __PLAT_XBOX__
+}
+
+
+
+#endif
+
diff --git a/Code/Sk/ngc/crt0.s b/Code/Sk/ngc/crt0.s
new file mode 100644
index 0000000..7861cfb
--- /dev/null
+++ b/Code/Sk/ngc/crt0.s
@@ -0,0 +1,175 @@
+##
+## SN Systems C/C++ Startup Module
+##
+#	.include "defs.s"			# equates
+#
+#	.section .data
+#wdbmsg:
+#	.string	"Waiting for SN Debugger...\n"
+#	.byte	0
+#	.align	4
+#
+#	.section .init
+#	.global	__start				# the only thing we need to export
+#	
+#__start:
+#    lis     %r1, _stack_addr@h        	# set r1, r2, r13 as required by PPC EABI
+#    ori     %r1, %r1, _stack_addr@l
+#
+#    lis     %r2, _SDA2_BASE_@h			# __SDA2_BASE_ is generated by linker */
+#    ori     %r2, %r2, _SDA2_BASE_@l
+#
+#    lis     %r13, _SDA_BASE_@h			# _SDA_BASE_ is generated by linker
+#    ori     %r13, %r13, _SDA_BASE_@l
+#
+#    bl      __init_hardware				# why do we have to call this?
+#
+#    li      %r0, -1               		# leave stack space for main params and lr stash
+#    stwu    %r1, -8(%r1)               
+#    stw     0, 4(%r1)                
+#    stw     0, 0(%r1)
+#
+## Init the BSS sections to 0
+#
+#	lis		%r3,_f_bss@ha
+#	addi	%r3,%r3,_f_bss@l
+#	li		%r4,0
+#	lis		%r5,_e_bss@ha
+#	addi	%r5,%r5,_e_bss@l
+#	subf	%r5,%r3,%r5
+#	bl		memset
+#
+#	lis		%r3,_f_sbss@ha
+#	addi	%r3,%r3,_f_sbss@l
+#	li		%r4, 0
+#	lis		%r5,_e_sbss@ha
+#	addi	%r5,%r5,_e_sbss@l
+#	subf	%r5,%r3,%r5
+#	bl		memset
+#
+#	li		%r4,0
+#	lis		%r5,0x8000			# use cached address space
+#	stw		%r4,0x44(%r5)		# write it to the DBException structure in low mem
+#
+## OK, now check to see if debugging is enabled
+#	li		%r29,0				# r29 == 0  =>  debugging not enabled
+#	lis		%r6,BOOTINFO2_ADDR@ha
+#	addi	%r6,%r6,BOOTINFO2_ADDR@l
+#	lwz		%r6,0(%r6)			# get r6 = ptr to BOOTINFO2
+#	cmpi	%cr0,%r6,0
+#	beq		_skipdbg			# skip if BOOTINFO2 does not exist
+#
+#	lwz		%r3,OS_BI2_DEBUGFLAG_OFFSET(%r6)	# this will be 2 if we are debugging
+#	cmpli	%cr0,%r3,2
+#	blt		_skipdbg			# 2=>DDH, 3=>GDEV
+#
+#	bl		SNDebugInit			# can we reach it with a bl? May need an "li32 + blrl"
+#	li		%r29,1
+#
+#_skipdbg:
+#	bl		_ParseCmdLine		# must do it here (before OSInit) because it may adjust ARENAHI
+#	mr		%r30,%r3
+#	mr		%r31,%r4
+#
+#	bl      DBInit
+#	bl      OSInit				# this will make the callback to EnableMetroTRKInterrupts
+#
+##    bl      __init_user			# C++ constructors. No, now handled in call to _main() from main()
+#
+#	cmpi	%cr0,%r29,0
+#	beq		_sktr
+#
+#	lis		%r3,wdbmsg@h
+#	ori		%r3,%r3,wdbmsg@l
+#	bl		OSReport
+#
+#	mfmsr	%r5
+#	lis		%r4,(~MSR_EE)@h		# li32 r4,MSR_EE (=0xFFFF7FFF)
+#	ori		%r4,%r4,(~MSR_EE)@l
+#	and		%r4,%r5,%r4			# disable external ints (so SRR stays safe)
+#	mtmsr	%r4
+#	sync						# make sure ints are off before next bit of code
+#	ori		%r5,%r5,MSR_BE		# and set BRANCH TRACE bit (will go of after bl main)
+#	mtspr	spr_srr1,%r5
+#
+#	lis		%r4,_sktr@h
+#	ori		%r4,%r4,_sktr@l		# li32	%r4,_sktr
+#	mtspr	spr_srr0,%r4
+#	rfi
+#
+#_sktr:
+#	bl      pre_main
+#
+### Fill stack with 55.
+##
+##	lis		%r4,_stack_end@ha
+##	addi	%r4,%r4,_stack_end@l
+##	li		%r5,0x55
+##	lis		%r6,_stack_addr@ha
+##	addi	%r6,%r6,_stack_addr@l
+##	subf	%r6,%r4,%r6
+##	li		%r6,0x80
+##	subf	%r6,%r6,%r4
+##	bl		memset
+#
+#
+#
+#
+#	mr		%r3,%r30			# restore argc
+#	mr		%r4,%r31			# and argv
+#	bl      main				# so we can call main()
+#    bl      post_main
+#    b       exit
+#
+#
+## process command line
+#_ParseCmdLine:
+#	lis		%r6,BOOTINFO2_ADDR@ha
+#	addi	%r6,%r6,BOOTINFO2_ADDR@l
+#	lwz		%r5,0(%r6)			# get r6 = ptr to BOOTINFO2
+#	cmpi	%cr0,%r5,0
+#	bne		_gargs
+#
+#_noargs:
+#    li      %r3,0
+#    li      %r4,0
+#	blr
+#
+#_gargs:
+#	lwz		%r6,OS_BI2_ARGOFFSET_OFFSET(%r5)	#argument offset
+#	cmpi	%cr0,%r6,0
+#	beq		_noargs				# no arguments
+#	add		%r6,%r5,%r6			# argument start
+#
+#	lwz		%r3,0(%r6)			# get argc
+#	cmpi	%cr0,%r3,0
+#	beq		_noargs				# shouldn't happen
+#
+#	addi	%r4,%r6,4			# argv
+#	mtctr	%r3
+#_lp:
+#	addi	%r6,%r6,4
+#	lwz		%r7,0(%r6)
+#	add		%r7,%r7,%r5
+#	stw		%r7,0(%r6)
+#	bdnz	_lp
+#
+## set ARENAHI to be *below* the arguments
+#	lis		%r5,ARENAHI_ADDR@ha
+#	addi	%r5,%r5,ARENAHI_ADDR@l
+#	clrrwi	%r7,%r4,5			# align it by 32bytes
+#	stw		%r7,0(%r5)
+#
+#	blr
+#
+## Bill's hack Yuk Yuk
+#
+#	.section .data
+#	.extern read
+#	.extern pre_main
+#	.extern main
+#	.extern __mod2i
+#LinkFiddle:
+#	.long read
+#	.long __mod2i
+
diff --git a/Code/Sk/ngc/defs.s b/Code/Sk/ngc/defs.s
new file mode 100644
index 0000000..72878e8
--- /dev/null
+++ b/Code/Sk/ngc/defs.s
@@ -0,0 +1,226 @@
+# register numbers for mfspr/mtspr
+
+	.equ	spr_xer,1
+	.equ	spr_lr,8
+	.equ	spr_ctr,9
+
+	.equ	spr_upmc1,937
+	.equ	spr_upmc2,938
+	.equ	spr_upmc3,941
+	.equ	spr_upmc4,942
+
+	.equ	spr_usia,939
+
+	.equ	spr_ummcr0,936
+	.equ	spr_ummcr1,940
+
+	.equ	spr_hid0,1008
+	.equ	spr_hid1,1009
+
+	.equ	spr_pvr,287
+
+	.equ	spr_ibat0u,528
+	.equ	spr_ibat0l,529
+	.equ	spr_ibat1u,530
+	.equ	spr_ibat1l,531
+	.equ	spr_ibat2u,532
+	.equ	spr_ibat2l,533
+	.equ	spr_ibat3u,534
+	.equ	spr_ibat3l,535
+
+	.equ	spr_dbat0u,536
+	.equ	spr_dbat0l,537
+	.equ	spr_dbat1u,538
+	.equ	spr_dbat1l,539
+	.equ	spr_dbat2u,540
+	.equ	spr_dbat2l,541
+	.equ	spr_dbat3u,542
+	.equ	spr_dbat3l,543
+
+	.equ	spr_sdr1,25
+
+	.equ	spr_sprg0,272
+	.equ	spr_sprg1,273
+	.equ	spr_sprg2,274
+	.equ	spr_sprg3,275
+
+	.equ	spr_dar,19
+	.equ	spr_dsisr,18
+
+	.equ	spr_srr0,26
+	.equ	spr_srr1,27
+
+	.equ	spr_ear,282
+
+	.equ	spr_dabr,1013
+
+	.equ	spr_tbl,284
+	.equ	spr_tbu,285
+
+	.equ	spr_l2cr,1017
+
+	.equ	spr_dec,22
+
+	.equ	spr_iabr,1010
+
+	.equ	spr_pmc1,953
+	.equ	spr_pmc2,954
+	.equ	spr_pmc3,957
+	.equ	spr_pmc4,958
+
+	.equ	spr_sia,955
+
+	.equ	spr_mmcr0,952
+	.equ	spr_mmcr1,956
+
+	.equ	spr_thrm1,1020
+	.equ	spr_thrm2,1021
+	.equ	spr_thrm3,1022
+
+	.equ	spr_ictc,1019
+
+# gekko registers
+	.equ	spr_gqr0,912
+	.equ	spr_gqr1,913
+	.equ	spr_gqr2,914
+	.equ	spr_gqr3,915
+	.equ	spr_gqr4,916
+	.equ	spr_gqr5,917
+	.equ	spr_gqr6,918
+	.equ	spr_gqr7,919
+
+	.equ	spr_hid2,920
+
+	.equ	spr_wpar,921
+
+	.equ	spr_dmau,922
+	.equ	spr_dmal,923
+# end of gekko registers
+
+	.equ	MSR_POW,0x00040000	# Power Management
+	.equ	MSR_ILE,0x00010000	# Interrupt Little Endian
+	.equ	MSR_EE,0x00008000	# external interrupt
+	.equ	MSR_PR,0x00004000	# privilege level(should be 0)
+	.equ	MSR_FP,0x00002000	# floating point available
+	.equ	MSR_ME,0x00001000	# machine check enable
+	.equ	MSR_FE0,0x00000800	# floating point exception enable
+	.equ	MSR_SE,0x00000400	# single step trace enable
+	.equ	MSR_BE,0x00000200	# branch trace enable
+	.equ	MSR_FE1,0x00000100	# floating point exception enable
+	.equ	MSR_IP,0x00000040	# Exception prefix
+	.equ	MSR_IR,0x00000020	# instruction relocate
+	.equ	MSR_DR,0x00000010	# data relocate
+	.equ	MSR_PM,0x00000004	# Performance monitor marked mode
+	.equ	MSR_RI,0x00000002	# Recoverable interrupt
+	.equ	MSR_LE,0x00000001	# Little Endian
+
+	.equ	MSR_POW_BIT,13		# Power Management
+	.equ	MSR_ILE_BIT,15		# Interrupt Little Endian
+	.equ	MSR_EE_BIT,16		# external interrupt
+	.equ	MSR_PR_BIT,17		# privilege level (should be 0)
+	.equ	MSR_FP_BIT,18		# floating point available
+	.equ	MSR_ME_BIT,19		# machine check enable
+	.equ	MSR_FE0_BIT,20		# floating point exception enable
+	.equ	MSR_SE_BIT,21		# single step trace enable
+	.equ	MSR_BE_BIT,22		# branch trace enable
+	.equ	MSR_FE1_BIT,23		# floating point exception enable
+	.equ	MSR_IP_BIT,25		# Exception prefix
+	.equ	MSR_IR_BIT,26		# instruction relocate
+	.equ	MSR_DR_BIT,27		# data relocate
+	.equ	MSR_PM_BIT,29		# Performance monitor marked mode
+	.equ	MSR_RI_BIT,30		# Recoverable interrupt
+	.equ	MSR_LE_BIT,31		# Little Endian
+
+
+	.equ	HID2_LSQE,0x80000000	# L/S quantize enable
+	.equ	HID2_WPE,0x40000000	# Write pipe enable
+	.equ	HID2_PSE,0x20000000	# Paired single enable
+	.equ	HID2_LCE,0x10000000	# Locked cache enable
+
+	.equ	HID2_DCHERR,0x00800000	# ERROR: dcbz_l cache hit
+	.equ	HID2_DNCERR,0x00400000	# ERROR: DMA access to normal cache
+	.equ	HID2_DCMERR,0x00200000	# ERROR: DMA cache miss error
+	.equ	HID2_DQOERR,0x00100000	# ERROR: DMA queue overflow
+	.equ	HID2_DCHEE,0x00080000	# dcbz_l cache hit error enable
+	.equ	HID2_DNCEE,0x00040000	# DMA access to normal cache error enable
+	.equ	HID2_DCMEE,0x00020000	# DMA cache miss error error enable
+	.equ	HID2_DQOEE,0x00010000	# DMA queue overflow error enable
+
+	.equ	HID2_DMAQL_MASK,0x0F000000	# DMA queue length mask
+	.equ	HID2_DMAQL_SHIFT,24		# DMA queue shift
+
+	.equ	HID2_LSQE_BIT,0
+	.equ	HID2_WPE_BIT,1
+	.equ	HID2_PSE_BIT,2
+	.equ	HID2_LCE_BIT,3
+
+	.equ	HID2_DCHERR_BIT,8   
+	.equ	HID2_DNCERR_BIT,9
+	.equ	HID2_DCMERR_BIT,10
+	.equ	HID2_DQOERR_BIT,11
+	.equ	HID2_DCHEE_BIT,12
+	.equ	HID2_DNCEE_BIT,13
+	.equ	HID2_DCMEE_BIT,14
+	.equ	HID2_DQOEE_BIT,15
+
+#------------------------------------------
+
+	.equ	EX_SYSTEM_RESET,0
+	.equ	EX_MACHINE_CHECK,1
+	.equ	EX_DSI,2
+	.equ	EX_ISI,3
+	.equ	EX_EXTERNAL_INTERRUPT,4
+	.equ	EX_ALIGNMENT,5
+	.equ	EX_PROGRAM,6
+	.equ	EX_FLOATING_POINT,7
+	.equ	EX_DECREMENTER,8
+	.equ	EX_SYSTEM_CALL,9
+	.equ	EX_TRACE,10
+	.equ	EX_PERFORMANCE_MONITOR,11
+	.equ	EX_BREAKPOINT,12
+	.equ	EX_SYSTEM_INTERRUPT,13
+	.equ	EX_THERMAL_INTERRUPT,14
+	.equ	EX_MAX,15
+
+	.equ	REAL_EX_SYSTEM_RESET,0
+	.equ	REAL_EX_MACHINE_CHECK,1
+	.equ	REAL_EX_DSI,2
+	.equ	REAL_EX_ISI,3
+	.equ	REAL_EX_EXTERNAL_INTERRUPT,4
+	.equ	REAL_EX_ALIGNMENT,5
+	.equ	REAL_EX_PROGRAM,6
+	.equ	REAL_EX_FLOATING_POINT,7
+	.equ	REAL_EX_DECREMENTER,8
+	.equ	REAL_EX_SYSTEM_CALL,11
+	.equ	REAL_EX_TRACE,12
+	.equ	REAL_EX_PERFORMANCE_MONITOR,14
+	.equ	REAL_EX_BREAKPOINT,18
+	.equ	REAL_EX_SYSTEM_INTERRUPT,21
+	.equ	REAL_EX_THERMAL_INTERRUPT,22
+	.equ	REAL_EX_MAX,15
+
+#------------------------------------------
+
+	.equ	CMD_OK,0
+	.equ	CMD_PK_2SMALL,1
+	.equ	CMD_COMMSERR,2
+
+#-----------------------------------------
+# Boot Info2 is a 8K byte structure that is loaded to himem
+# (lower than FST) by apploader.
+
+	.equ	BOOTINFO2_ADDR,0x800000F4
+
+	.equ	OS_BI2_SIZE,0x2000      			# 8K 
+	.equ	OS_BI2_DEBUGMONSIZE_OFFSET,0x0000
+	.equ	OS_BI2_SIMMEMSIZE_OFFSET,0x0004
+	.equ	OS_BI2_ARGOFFSET_OFFSET,0x0008
+	.equ	OS_BI2_DEBUGFLAG_OFFSET,0x000c
+	.equ	OS_BI2_TRKLOCATION_OFFSET,0x0010
+	.equ	OS_BI2_TRKSIZE_OFFSET,0x0014
+	.equ	OS_BI2_COUNTRYCODE_OFFSET,0x0018
+	.equ	OS_BI2_ARGSIZE_MAX,0x1000      		# 4K
+	.equ	OS_BI2_COUNTRYCODE_JP,0
+	.equ	OS_BI2_COUNTRYCODE_US,1
+
+	.equ	ARENAHI_ADDR,0x80000034
diff --git a/Code/Sk/product_codes.h b/Code/Sk/product_codes.h
new file mode 100644
index 0000000..9dbe949
--- /dev/null
+++ b/Code/Sk/product_codes.h
@@ -0,0 +1,20 @@
+#ifndef	__SK_PRODUCT_CODES_H
+#define	__SK_PRODUCT_CODES_H
+
+namespace Config
+{
+
+// The mem card headers
+// These are also used to derive the elf name for comparison so that the language can be
+// autodetected, in sys\config\ngps\p_config.cpp
+#define NGPS_NTSC "BASLUS-20731"    // Mick: 20731 is the official THPS5 NTSC SLUS number
+#define NGPS_PAL_ENGLISH "BESLES-51848"
+#define NGPS_PAL_FRENCH "BESLES-51851"   // <<<<<<<<<<<<<<<<<<<  just guessing here.....
+#define NGPS_PAL_GERMAN "BESLES-51852"
+#define NGPS_PAL_ITALIAN "BESLES-51853"
+#define NGPS_PAL_SPANISH "BESLES-51854"
+
+};
+
+#endif
+
diff --git a/Code/Sk/template.h b/Code/Sk/template.h
new file mode 100644
index 0000000..a0a197c
--- /dev/null
+++ b/Code/Sk/template.h
@@ -0,0 +1,58 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:														**
+**																			**
+**	Module:															**
+**																			**
+**	File name:		.h											**
+**																			**
+**	Created:		xx/xx/xx	- xxx										**
+**																			**
+*****************************************************************************/
+
+#ifndef	___H
+#define	___H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							     Clase Definitions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+#endif	// ___H
\ No newline at end of file
diff --git a/Code/Sys/Config/NGC/p_config.cpp b/Code/Sys/Config/NGC/p_config.cpp
new file mode 100644
index 0000000..1d4fdde
--- /dev/null
+++ b/Code/Sys/Config/NGC/p_config.cpp
@@ -0,0 +1,78 @@
+// Config Manager stuff. KSH 20 Mar 2002
+#include 
+#include 
+#include 
+
+extern void * get_font_file_address( const char * p_filename );
+
+namespace Config
+{
+
+void Plat_Init(sint argc, char** argv)
+{
+	gHardware=HARDWARE_NGC;
+	
+	// I don't know yet how to autodetect the language for this platform, so
+	// just set it to English for the moment.
+	
+	switch ( OSGetLanguage() )
+	{
+		case OS_LANG_GERMAN:
+			gLanguage=LANGUAGE_GERMAN;
+			break;
+		case OS_LANG_FRENCH:
+			gLanguage=LANGUAGE_FRENCH;
+			{
+				// Load French font file.
+				void * p = get_font_file_address( "small" );
+				void *p_FH = File::Open( "fonts\\small_fr.fnt.ngc", "rb" );
+				if( p_FH )
+				{
+					int size = File::GetFileSize( p_FH );
+					char * p_new = new char[size];
+					File::Read( p_new, size, 1, p_FH );
+					memcpy( p, p_new, size );
+					delete p_new;
+				}
+			}
+			break;
+		case OS_LANG_SPANISH:
+		case OS_LANG_ITALIAN:
+		case OS_LANG_DUTCH:
+		case OS_LANG_ENGLISH:
+		default:
+			gLanguage=LANGUAGE_ENGLISH;
+			break;
+	}
+
+	// Autodetect these somehow ...
+	if ( VIGetTvFormat() == VI_PAL )
+	{
+		gDisplayType=DISPLAY_TYPE_PAL;
+		gFPS=50;
+	}
+	else
+	{
+		gDisplayType=DISPLAY_TYPE_NTSC;
+		gFPS=60;
+	}
+	
+	// See if we're on a devkit or not by checking the amount of memory
+	int megs = (uint32)OSGetArenaHi() - (uint32)OSGetArenaLo();
+
+	if ( megs >= ( 1024 * 1024 * 24 ) )
+	{
+		// We're on a devkit
+		gCD=false;
+	}
+	else
+	{
+		// We're on an NR-Reader
+		gCD=true;
+	}
+	
+	gpMemCardHeader="SK5";
+}
+
+} // namespace Config
+
diff --git a/Code/Sys/Config/NGPS/p_config.cpp b/Code/Sys/Config/NGPS/p_config.cpp
new file mode 100644
index 0000000..85a2b66
--- /dev/null
+++ b/Code/Sys/Config/NGPS/p_config.cpp
@@ -0,0 +1,226 @@
+// Config Manager stuff. KSH 20 Mar 2002
+#include 
+#include 
+#include 
+
+extern "C"
+{
+int snputs(const char* pszStr);
+}
+
+namespace Config
+{
+
+static char s_elf_name[64];  // will be 'local' or 'skate5'
+
+const char * GetElfName()
+{
+	return (const char *)s_elf_name;
+}
+
+static const char *sGenerateElfName(const char *p_memCardHeader)
+{
+	Dbg_MsgAssert(strlen(p_memCardHeader)==12,("Bad length for mem card header '%s'\n",p_memCardHeader));
+	static char sp_elf_name[50];
+
+	// Note: p_memCardHeader will have the form "BASLUS-20731"
+	// "BASLUS-20731" needs to map to an elf name of "cdrom:\SLUS_207.31;1"
+	
+	sprintf(sp_elf_name,"cdrom0:\\SL%cS_%c%c%c.%c%c;1",
+			p_memCardHeader[4],
+			p_memCardHeader[7],
+			p_memCardHeader[8],
+			p_memCardHeader[9],
+			p_memCardHeader[10],
+			p_memCardHeader[11]);
+	return sp_elf_name;
+}
+
+void Plat_Init(sint argc, char** argv)
+{
+	gHardware=HARDWARE_UNDEFINED;
+	gGotExtraMemory=false;
+	gCD=false;
+
+
+	
+	// must check to see if the supplied filename starts "cdrom0:\THPS4\"    SLUS_207.31;1
+
+	// if the first real argument exists and starts with a digit, then assume
+	// that we are running from a bootstrap (as we will just have been passed the language in argv[1])
+	// and preempt any other parameters...
+	// so assume running on a regular PS2, from the CD
+	if (argc > 1)
+	{
+		if (argv[1][0] >= '0' && argv[1][0] <= '9')
+		{
+			printf ("argv[1][0] is a digit, so assuming bootstrap format, using \\thps4 directory\n");
+			gBootstrap=true;
+			gSonyBootstrap = true;
+			gHardware=HARDWARE_PS2;
+			gGotExtraMemory=false;
+			gCD=true;
+
+			// the rest we could, in theory, set from the sceLibDemo calls......			
+			gLanguage=LANGUAGE_ENGLISH;
+			gTerritory=TERRITORY_UNDEFINED;
+			gDisplayType=DISPLAY_TYPE_NTSC;
+			gFPS=60;
+
+			
+			return;
+		}
+		else
+		{
+			printf ("argv[1][0] (%s) is not digit, so it's a regular boot, using root\n",argv[1]);
+		}
+	}
+	else
+	{
+		printf ("no arguments, so it's a regular boot, using root\n");
+	}
+	
+
+	
+	// If the filename ends in .elf, then extract out the name
+	// c:\skate5\build\NGPSgnu\local.elf
+	if (stricmp(argv[0]+strlen(argv[0])-4,".elf")==0)
+	{
+		char *p = argv[0]+strlen(argv[0])-5;		// letter before the .elf
+		while (*p != '\\') p--;
+		p++; // first letter of the name
+		char *q = &s_elf_name[0];
+		while (*p != '.') *q++ = *p++;
+		*q++ = 0;
+	}
+	
+
+	if (!argv[0] || stricmp(argv[0]+strlen(argv[0])-4,".elf")==0)
+	{
+		gHardware=HARDWARE_PS2_DEVSYSTEM;
+		gGotExtraMemory=true;
+		gCD=false;
+	}
+	else
+	{
+		// It's just a normal PS2
+		gHardware=HARDWARE_PS2;
+		gGotExtraMemory=false;
+		gCD=true;
+	}
+	
+	// Check command line to see if they're using ProView
+	if (snputs("Plat_Init detected ProView ...\n")!=-1)
+	{
+		gHardware=HARDWARE_PS2_PROVIEW;
+		gGotExtraMemory=false;
+		gCD=false;
+	}
+	
+	// Check command line to see if they want to force gGotExtraMemory on or off
+	if (CommandLineContainsFlag("GotExtraMemory",argc,argv))		
+	{
+		gGotExtraMemory=true;
+	}
+	if (CommandLineContainsFlag("NoExtraMemory",argc,argv))		
+	{
+		gGotExtraMemory=false;
+	}
+	
+	// Detect the language from the product code.
+	gLanguage=LANGUAGE_ENGLISH;
+	gpMemCardHeader=NGPS_NTSC;
+	if (argv[0])
+	{
+		// Doing a stricmp just in case it changes to be CDROM later or something.
+		if (stricmp(argv[0],sGenerateElfName(NGPS_NTSC))==0)
+		{
+			gLanguage=LANGUAGE_ENGLISH;
+			gpMemCardHeader=NGPS_NTSC;
+		}
+		else if (stricmp(argv[0],sGenerateElfName(NGPS_PAL_ENGLISH))==0)
+		{
+			gLanguage=LANGUAGE_ENGLISH;
+			gpMemCardHeader=NGPS_PAL_ENGLISH;
+		}
+		else if (stricmp(argv[0],sGenerateElfName(NGPS_PAL_FRENCH))==0)
+		{
+			gLanguage=LANGUAGE_FRENCH;
+			gpMemCardHeader=NGPS_PAL_FRENCH;
+		}
+		else if (stricmp(argv[0],sGenerateElfName(NGPS_PAL_GERMAN))==0)
+		{
+			gLanguage=LANGUAGE_GERMAN;
+			gpMemCardHeader=NGPS_PAL_GERMAN;
+		}
+		else if (stricmp(argv[0],sGenerateElfName(NGPS_PAL_ITALIAN))==0)
+		{
+			gLanguage=LANGUAGE_ITALIAN;
+			gpMemCardHeader=NGPS_PAL_ITALIAN;
+		}
+		else if (stricmp(argv[0],sGenerateElfName(NGPS_PAL_SPANISH))==0)
+		{
+			gLanguage=LANGUAGE_SPANISH;
+			gpMemCardHeader=NGPS_PAL_SPANISH;
+		}
+	}	
+
+	// They may want to force the language to be something else from the command line ...
+	const char *p_language=GetCommandLineParam("Language",argc,argv);
+	if (p_language)
+	{
+		if (stricmp(p_language,"English")==0)
+		{
+			gLanguage=LANGUAGE_ENGLISH;
+			gpMemCardHeader=NGPS_NTSC;
+		}
+		else if (stricmp(p_language,"French")==0)
+		{
+			gLanguage=LANGUAGE_FRENCH;
+			gpMemCardHeader=NGPS_PAL_FRENCH;
+		}
+		else if (stricmp(p_language,"German")==0)
+		{
+			gLanguage=LANGUAGE_GERMAN;
+			gpMemCardHeader=NGPS_PAL_GERMAN;
+		}
+		else if (stricmp(p_language,"Italian")==0)
+		{
+			gLanguage=LANGUAGE_ITALIAN;
+			gpMemCardHeader=NGPS_PAL_ITALIAN;
+		}
+		else if (stricmp(p_language,"Spanish")==0)
+		{
+			gLanguage=LANGUAGE_SPANISH;
+			gpMemCardHeader=NGPS_PAL_SPANISH;
+		}
+		else
+		{
+			Dbg_MsgAssert(0,("Language '%s' not supported",p_language));
+		}
+	}
+
+
+	
+	gTerritory=TERRITORY_UNDEFINED;
+
+	gDisplayType=DISPLAY_TYPE_NTSC;
+	gFPS=60;
+	
+	// Figure out if it is PAL from the product code ...
+	if (argv[0])
+	{
+		char p_temp[50];
+		strncpy(p_temp,argv[0],12);
+		p_temp[12]=0; // strncpy won't terminate
+		// Doing a stricmp just in case it changes to be CDROM later or something.
+		if (stricmp(p_temp,"cdrom0:\\SLES")==0)
+		{
+			gDisplayType=DISPLAY_TYPE_PAL;
+			gFPS=50;
+		}	
+	}
+}
+
+} // namespace Config
+
diff --git a/Code/Sys/Config/Win32/p_config.cpp b/Code/Sys/Config/Win32/p_config.cpp
new file mode 100644
index 0000000..6da7959
--- /dev/null
+++ b/Code/Sys/Config/Win32/p_config.cpp
@@ -0,0 +1,15 @@
+// Config Manager stuff. KSH 20 Mar 2002
+#include 
+
+namespace Config
+{
+
+void Plat_Init(sint argc, char** argv)
+{
+	gHardware	= HARDWARE_WIN32;
+	gLanguage	= LANGUAGE_ENGLISH;	
+	gGotExtraMemory = true;
+}
+
+} // namespace Config
+
diff --git a/Code/Sys/Config/XBox/p_config.cpp b/Code/Sys/Config/XBox/p_config.cpp
new file mode 100644
index 0000000..a46c46c
--- /dev/null
+++ b/Code/Sys/Config/XBox/p_config.cpp
@@ -0,0 +1,70 @@
+// Config Manager stuff. KSH 20 Mar 2002
+#include 
+
+namespace Config
+{
+
+void Plat_Init(sint argc, char** argv)
+{
+	gHardware	= HARDWARE_XBOX;
+	DWORD lang	= XGetLanguage();
+	if( lang == XC_LANGUAGE_ENGLISH )
+		gLanguage = LANGUAGE_ENGLISH;
+	else
+	{
+		if( XGetVideoStandard() != XC_VIDEO_STANDARD_PAL_I )
+		{
+			// For NTSC, the only language allowed is English.
+			gLanguage = LANGUAGE_ENGLISH;
+		}
+		else if( lang == XC_LANGUAGE_FRENCH )
+		{
+			gLanguage = LANGUAGE_FRENCH;
+		}
+		else if( lang == XC_LANGUAGE_GERMAN )
+		{
+			gLanguage = LANGUAGE_GERMAN;
+		}
+		else
+		{
+			// Any languages other than French and German should also be considered English.
+			gLanguage = LANGUAGE_ENGLISH;
+		}
+	}
+	
+	// Kind of meaningless, but default CD to true for Final builds, false otherwise.
+#	ifdef __NOPT_ASSERT__
+	gCD = false;
+#	else
+	gCD = true;
+#	endif
+	
+	switch( XGetVideoStandard())
+	{
+		case XC_VIDEO_STANDARD_PAL_I:
+		{
+			gDisplayType	= DISPLAY_TYPE_PAL;
+			gFPS			= 50;
+			if( XGetVideoFlags() & XC_VIDEO_FLAGS_PAL_60Hz )
+			{
+				gFPS		=	60;
+			}	
+			break;
+		}
+		case XC_VIDEO_STANDARD_NTSC_M:
+		case XC_VIDEO_STANDARD_NTSC_J:
+		{
+			gDisplayType	= DISPLAY_TYPE_NTSC;
+			gFPS			= 60;
+			break;
+		}
+		default:
+		{
+			Dbg_MsgAssert( 0, ("Unrecognized return value (%d) from XGetVideoStandard()", XGetVideoStandard()));
+			break;
+		}
+	}	
+}
+
+} // namespace Config
+
diff --git a/Code/Sys/Config/config.cpp b/Code/Sys/Config/config.cpp
new file mode 100644
index 0000000..84e92fa
--- /dev/null
+++ b/Code/Sys/Config/config.cpp
@@ -0,0 +1,265 @@
+// Config Manager stuff. KSH 20 Mar 2002
+#include 
+
+namespace Config
+{
+ELanguage gLanguage=LANGUAGE_UNDEFINED;
+ETerritory gTerritory=TERRITORY_UNDEFINED;
+EHardware gHardware=HARDWARE_UNDEFINED;
+bool gGotExtraMemory=false;
+bool gCD=false;
+bool gBootstrap=false;
+bool gSonyBootstrap=false;
+float	gMasterVolume = 100.0f; 		// precentage at which we play the overall volume
+char	gReturnTo[128];					// full path of executable we return to from a demo (second argument if first is "bootstrap")
+char	gReturnString[128];				// string that we return for demo
+
+EDisplayType gDisplayType=DISPLAY_TYPE_NTSC;
+int gFPS=60;
+
+const char *gpMemCardHeader="";
+
+// Returns true if p_string starts with p_prefix. Non-case-sensitive.
+// (don't want to use strstr cos it is case sensitive)
+static bool sIsPrefixedWith(const char *p_string, const char *p_prefix)
+{
+	if (!p_string || !p_prefix) return false;
+	
+	int string_len=strlen(p_string);
+	int prefix_len=strlen(p_prefix);
+	if (prefix_len>string_len)
+	{
+		// Can't be prefixed if shorter than the prefix.
+		return false;
+	}
+	
+	char p_buf[100];
+	Dbg_MsgAssert(string_len<100,("String too long for buffer"));
+	strcpy(p_buf,p_string);
+	p_buf[prefix_len]=0;
+	if (stricmp(p_buf,p_prefix)==0)
+	{
+		return true;
+	}
+	return false;
+}
+
+// If p_name is found to be followed by an equals in the command line, then this function
+// will return the word following the equals.
+// If p_name is not found, or equals blank, it will return NULL. 
+const char *GetCommandLineParam(const char *p_name, sint argc, char** argv)
+{
+	const char *p_found=NULL;
+	
+	for (int i=0; i 2)
+		{
+			strcpy(gReturnTo,argv[2]);
+			if (argc > 3)
+			{
+				strcpy(gReturnString,argv[3]);
+			}
+			else
+			{
+				sprintf(gReturnString,"THPS4");
+			}			
+			printf ("This bootstrap demo will return to %s passing back: %s\n",gReturnTo,gReturnString);
+		}
+	}
+	if (CommandLineContainsFlag("NotBootstrap",argc,argv))		
+	{
+		gBootstrap=false;
+	}
+	
+	if (CommandLineContainsFlag("Pal",argc,argv))		
+	{
+		gDisplayType=DISPLAY_TYPE_PAL;
+	}
+	if (CommandLineContainsFlag("NTSC",argc,argv))		
+	{
+		gDisplayType=DISPLAY_TYPE_NTSC;
+	}
+	
+	const char *p_frame_rate=GetCommandLineParam("FrameRate",argc,argv);
+	if (p_frame_rate)
+	{
+		if (strcmp(p_frame_rate,"50")==0)
+		{
+			gFPS=50;
+		}
+		if (strcmp(p_frame_rate,"60")==0)
+		{
+			gFPS=60;
+		}
+	}	
+}
+
+const char* GetMemCardHeader()
+{
+	return gpMemCardHeader;	
+}
+
+// Return the string for the directory we are running in
+// generally this will be the root
+// but when making a demo disk, then it will be the "THPS4" directory
+const char* GetDirectory()
+{
+	if (Bootstrap())
+	{
+		return	"THPS4\\";	 // generally a subdirectory off the root
+	}
+	else
+	{
+		return "";			 // nothing needed it it is the root
+	}
+}
+
+const char* GetLanguageName()
+{
+	switch (gLanguage)
+	{
+	case LANGUAGE_ENGLISH: 		return "English"; break;
+	case LANGUAGE_FRENCH: 		return "French"; break;
+	case LANGUAGE_GERMAN: 		return "German"; break;
+	case LANGUAGE_JAPANESE:		return "Japanese"; break;
+	case LANGUAGE_SPANISH:		return "Spanish"; break;
+	case LANGUAGE_ITALIAN:		return "Italian"; break;
+	case LANGUAGE_DUTCH:		return "Dutch"; break;
+	case LANGUAGE_PORTUGUESE:	return "Portuguese"; break;
+	default: 				return "Unknown language"; break;
+	}
+}
+
+const char* GetTerritoryName()
+{
+	switch (gTerritory)
+	{
+	case TERRITORY_EUROPE: 	return "Europe"; break;
+	case TERRITORY_US: 		return "US"; break;
+	case TERRITORY_ASIA: 	return "Asia"; break;
+	default: 				return "Unknown territory"; break;
+	}
+}
+
+const char* GetHardwareName()
+{
+	switch (gHardware)
+	{
+	case HARDWARE_PS2: 				return "PS2"; break;
+	case HARDWARE_PS2_PROVIEW:		return "PS2 ProView"; break;
+	case HARDWARE_PS2_DEVSYSTEM:	return "PS2 Dev System"; break;
+	case HARDWARE_XBOX:				return "XBox"; break;
+	case HARDWARE_NGC:				return "GameCube"; break;
+	case HARDWARE_WIN32:			return "Win32"; break;
+	default:						return "Unknown platform"; break;
+	}	
+}
+
+const char* GetDisplayTypeName()
+{
+	switch (gDisplayType)
+	{
+	case DISPLAY_TYPE_PAL:		return "PAL"; break;
+	case DISPLAY_TYPE_NTSC:		return "NTSC"; break;
+	default:					return "Unknown display type"; break;
+	}
+}
+
+} // namespace Config
+
diff --git a/Code/Sys/Config/config.h b/Code/Sys/Config/config.h
new file mode 100644
index 0000000..50e3e55
--- /dev/null
+++ b/Code/Sys/Config/config.h
@@ -0,0 +1,125 @@
+// Config Manager stuff. KSH 20 Mar 2002
+//
+// A bunch of inline functions for quering what type of platform the executable is running on,
+// what language is set, whether it's PAL or NTSC, etc blaa blaa.
+//
+// The first thing main() does is call the Config::Init function, which parses the command
+// line and does various things to work out what the config is. Then the inline functions just
+// return the values calculated in Init, so they're nice and fast.
+
+
+#ifndef	__CONFIG_CONFIG_H
+#define	__CONFIG_CONFIG_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Config
+{
+
+enum ELanguage
+{
+	LANGUAGE_UNDEFINED,
+	
+	// Prefixed with LANGUAGE_ so that they don't clash with the old defines that are still
+	// in the code.
+	LANGUAGE_JAPANESE,
+	LANGUAGE_ENGLISH,
+	LANGUAGE_FRENCH,
+	LANGUAGE_SPANISH,
+	LANGUAGE_GERMAN,
+	LANGUAGE_ITALIAN,
+	LANGUAGE_DUTCH,
+	LANGUAGE_PORTUGUESE,
+};
+
+enum ETerritory
+{
+	TERRITORY_UNDEFINED,
+	TERRITORY_EUROPE,
+	TERRITORY_US,
+	TERRITORY_ASIA,
+};
+
+enum EHardware
+{
+	// All prefixed with HARDWARE_ to be consistent with the language enum above ...
+	HARDWARE_UNDEFINED,
+	
+	HARDWARE_PS2,			// A normal PS2
+	HARDWARE_PS2_PROVIEW,	// A PS2 running ProView
+	HARDWARE_PS2_DEVSYSTEM,	// A dev system with lots of memory
+	
+	HARDWARE_XBOX,		// An XBox
+	HARDWARE_NGC,		// A GameCube
+	HARDWARE_WIN32,		// A PC running Win32
+};
+
+enum EDisplayType
+{
+	DISPLAY_TYPE_PAL,
+	DISPLAY_TYPE_NTSC,
+};
+
+extern EDisplayType gDisplayType;
+inline EDisplayType GetDisplayType() {return gDisplayType;}
+// Perhaps change this to just return a bool if speed is a problem ...
+inline bool PAL() {return gDisplayType==DISPLAY_TYPE_PAL;}
+inline bool NTSC() {return gDisplayType==DISPLAY_TYPE_NTSC;}
+
+extern int gFPS;
+extern inline int FPS() {return gFPS;}
+
+extern EHardware gHardware;
+inline EHardware GetHardware() {return gHardware;}
+
+extern bool gGotExtraMemory;
+inline bool GotExtraMemory() {return gGotExtraMemory;}
+
+extern ELanguage gLanguage;
+inline ELanguage GetLanguage() {return gLanguage;}
+
+extern ETerritory gTerritory;
+inline ETerritory GetTerritory() {return gTerritory;}
+
+extern bool gCD;
+inline bool CD() {return gCD;}
+
+extern	float	gMasterVolume;
+inline	float	GetMasterVolume() {return gMasterVolume;}
+inline	void 	SetMasterVolume(float vol) {gMasterVolume=vol;}
+
+extern bool gBootstrap;
+extern bool gSonyBootstrap;
+inline bool Bootstrap() {return gBootstrap;}				// true if some kind of bootstrap demo
+inline bool SonyBootstrap() {return gSonyBootstrap;}		// true if it's a Sony specific bootstrap demo (with timeout, etc)
+extern char	gReturnTo[];  
+extern char	gReturnString[];
+
+extern const char *gpMemCardHeader;
+
+const char* GetDirectory();
+
+const char* GetMemCardHeader();
+
+const char *GetCommandLineParam(const char *p_name, sint argc, char** argv);
+bool CommandLineContainsFlag(const char *p_name, sint argc, char** argv);
+
+void Init(sint argc, char** argv);
+void Plat_Init(sint argc, char** argv);
+
+// Utility functions.
+const char* GetLanguageName();
+const char* GetTerritoryName();
+const char* GetHardwareName();
+const char* GetDisplayTypeName();
+
+// Sony Specific, not needed on the others
+const char *GetElfName();
+
+
+} // namespace Config
+
+#endif	// __CONFIG_CONFIG_H
+
diff --git a/Code/Sys/File/AsyncFilesys.cpp b/Code/Sys/File/AsyncFilesys.cpp
new file mode 100644
index 0000000..5d50bec
--- /dev/null
+++ b/Code/Sys/File/AsyncFilesys.cpp
@@ -0,0 +1,854 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// AsyncFilesys.cpp		GRJ 8 Oct 2002
+//
+// Asynchronous file system
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+#include 
+
+namespace File
+{
+
+///////////////////////////////////////////////////////////////////////////////////////
+// Constants
+//
+
+const uint32	CAsyncFileHandle::MAX_FILE_SIZE = (uint32) ((uint64) (1 << 31) - 1);	// 1 Gig (Because we are using signed, we lose a bit)
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAsyncFileHandle::CAsyncFileHandle()
+{
+	init();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::init()
+{
+	// Init vars
+	mp_callback = NULL;
+	m_callback_arg0 = 0;
+	m_callback_arg1 = 0;
+	m_current_function = FUNC_IDLE;
+	m_mem_destination = (EAsyncMemoryType) DEFAULT_MEMORY_TYPE;
+	m_stream = false;
+	m_blocking = true;			// Makes it async
+	m_buffer_size = DEFAULT_BUFFER_SIZE;
+	m_priority = DEFAULT_ASYNC_PRIORITY;
+
+	m_file_size = -1;
+	m_position = -1;
+	m_busy_count = 0;
+	m_last_result = 0;
+
+	mp_pre_file = NULL;
+
+	plat_init();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAsyncFileHandle::~CAsyncFileHandle()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+volatile bool		CAsyncFileHandle::IsBusy( void )
+{
+	// Don't think we need the plat_is_busy()
+	return get_busy_count() > 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					CAsyncFileHandle::WaitForIO( void )
+{
+	do
+	{
+		// Process any pending callbacks
+		CAsyncFileLoader::s_execute_callback_list();
+	} while (IsBusy());
+
+	return m_last_result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::SetPriority( int priority )
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't set file parameters: asynchronous file handle already busy."));
+	m_current_function = FUNC_IDLE;
+
+	m_priority = priority;
+
+	plat_set_priority(priority);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::SetStream( bool stream )
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't set file parameters: asynchronous file handle already busy."));
+	m_current_function = FUNC_IDLE;
+
+	m_stream = stream;
+
+	plat_set_stream(stream);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::SetDestination( EAsyncMemoryType destination )
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't set file parameters: asynchronous file handle already busy."));
+	m_current_function = FUNC_IDLE;
+
+	m_mem_destination = destination;
+
+	plat_set_destination(destination);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::SetBufferSize( size_t buffer_size )
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't set file parameters: asynchronous file handle already busy."));
+	m_current_function = FUNC_IDLE;
+
+	m_buffer_size = buffer_size;
+
+	plat_set_buffer_size(buffer_size);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::SetBlocking( bool block )
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't set file parameters: asynchronous file handle already busy."));
+	m_current_function = FUNC_IDLE;
+
+	m_blocking = block;
+
+	plat_set_blocking(block);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::SetCallback( AsyncCallback p_callback, unsigned int arg0, unsigned int arg1)
+{
+	mp_callback = p_callback;
+	m_callback_arg0 = arg0;
+	m_callback_arg1 = arg1;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+size_t				CAsyncFileHandle::GetFileSize( void )
+{
+	if (mp_pre_file)
+	{
+		Dbg_Assert(PreMgr::sPreEnabled());
+
+        m_last_result = PreMgr::pre_get_file_size(mp_pre_file);
+
+		Dbg_Assert(PreMgr::sPreExecuteSuccess());
+		Dbg_MsgAssert(m_last_result, ("PRE file size is 0"));
+
+		return m_last_result;
+	}
+
+	// Make sure we have a valid file size first
+	while (m_file_size < 0)
+		;
+
+	return m_file_size;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+size_t				CAsyncFileHandle::Load(void *p_buffer)
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't start new load: asynchronous file handle already busy."));
+
+	m_current_function = FUNC_LOAD;
+
+	if (mp_pre_file)
+	{
+		Dbg_Assert(PreMgr::sPreEnabled());
+
+		int size = PreMgr::pre_get_file_size(mp_pre_file);
+		PreMgr::pre_fseek(mp_pre_file, SEEK_SET, 0);
+        m_last_result = PreMgr::pre_fread(p_buffer, 1, size, mp_pre_file);
+
+		Dbg_Assert(PreMgr::sPreExecuteSuccess());
+
+		inc_busy_count();
+		io_callback(m_current_function, m_last_result, 0);			// Must call this when we are done
+		return m_last_result;
+	}
+
+	return plat_load(p_buffer);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+size_t				CAsyncFileHandle::Read(void *p_buffer, size_t size, size_t count)
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't start new read: asynchronous file handle already busy."));
+
+	m_current_function = FUNC_READ;
+
+	if (mp_pre_file)
+	{
+		Dbg_Assert(PreMgr::sPreEnabled());
+
+		m_last_result = PreMgr::pre_fread(p_buffer, size, count, mp_pre_file);
+
+		Dbg_Assert(PreMgr::sPreExecuteSuccess());
+
+		inc_busy_count();
+		io_callback(m_current_function, m_last_result, 0);			// Must call this when we are done
+		return m_last_result;
+	}
+
+	return plat_read(p_buffer, size, count);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+size_t				CAsyncFileHandle::Write(void *p_buffer, size_t size, size_t count)
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't start new write: asynchronous file handle already busy."));
+
+	m_current_function = FUNC_WRITE;
+
+	if (mp_pre_file)
+	{
+		Dbg_Assert(PreMgr::sPreEnabled());
+
+		m_last_result = PreMgr::pre_fwrite(p_buffer, size, count, mp_pre_file);
+
+		Dbg_Assert(PreMgr::sPreExecuteSuccess());
+
+		inc_busy_count();
+		io_callback(m_current_function, m_last_result, 0);			// Must call this when we are done
+		return m_last_result;
+	}
+
+	return plat_write(p_buffer, size, count);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					CAsyncFileHandle::Seek(long offset, int origin)
+{
+	//Dbg_MsgAssert(!IsBusy(), ("Can't seek: asynchronous file handle already busy."));
+
+	m_current_function = FUNC_SEEK;
+
+	if (mp_pre_file)
+	{
+		Dbg_Assert(PreMgr::sPreEnabled());
+
+		m_last_result = PreMgr::pre_fseek(mp_pre_file, offset, origin);
+
+		Dbg_Assert(PreMgr::sPreExecuteSuccess());
+
+		inc_busy_count();
+		io_callback(m_current_function, m_last_result, 0);			// Must call this when we are done
+		return m_last_result;
+	}
+
+	return plat_seek(offset, origin);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::io_callback(EAsyncFunctionType function, int result, uint32 data)
+{
+	m_last_result = result;
+	CAsyncFileLoader::s_new_io_completion = true;
+
+	// Call user function, if any
+	if (mp_callback)
+	{
+		CAsyncFileLoader::s_add_callback(this, function, result);
+	} else {
+		post_io_callback();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::post_io_callback()
+{
+	// Clean up
+	Dbg_MsgAssert(get_busy_count() > 0, ("We will go into a neagtive busy with completion of function %d", m_current_function));
+	if (dec_busy_count() == 0)
+	{
+		m_current_function = FUNC_IDLE;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileHandle::open(const char *filename, bool blocking, int priority)
+{
+	Dbg_MsgAssert(!IsBusy(), ("Can't open file %s: asynchronous file handle already busy.", filename));
+	Dbg_MsgAssert(!mp_pre_file, ("Can't open file %s: already open as a PRE file.", filename));
+
+	m_priority = priority;
+	m_blocking = blocking;
+
+	m_current_function = FUNC_OPEN;
+
+    if (PreMgr::sPreEnabled())
+    {
+        mp_pre_file = PreMgr::pre_fopen(filename, "rb", true);
+        if (mp_pre_file)
+		{
+			Dbg_Assert(PreMgr::sPreExecuteSuccess());
+
+			m_blocking = true;		// Since PRE files are in memory, they will finish "instantly"
+			m_last_result = true;
+
+			inc_busy_count();
+			io_callback(m_current_function, m_last_result, 0);			// Must call this when we are done
+			return m_last_result;
+		}
+    }
+
+	return plat_open(filename);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileHandle::close()
+{
+	m_current_function = FUNC_CLOSE;
+
+	if (mp_pre_file)
+	{
+		Dbg_Assert(PreMgr::sPreEnabled());
+
+        m_last_result = PreMgr::pre_fclose(mp_pre_file, true);
+
+		Dbg_Assert(PreMgr::sPreExecuteSuccess());
+
+		inc_busy_count();
+		io_callback(m_current_function, m_last_result, 0);			// Must call this when we are done
+		return m_last_result;
+	}
+
+	return plat_close();
+}
+
+/******************************************************************/
+/*                                                                */
+/* plat stubs													  */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileHandle::plat_init()
+{
+	printf ("STUB: CAsyncFileHandle::Init\n");
+}
+
+bool				CAsyncFileHandle::plat_open(const char *filename)
+{
+	printf ("STUB: CAsyncFileHandle::Open\n");
+
+	return false;
+}
+
+bool				CAsyncFileHandle::plat_close()
+{
+	printf ("STUB: CAsyncFileHandle::Close\n");
+
+	return false;
+}
+
+volatile bool		CAsyncFileHandle::plat_is_done()
+{
+	printf ("STUB: CAsyncFileHandle::IsDone\n");
+
+	return false;
+}
+
+volatile bool		CAsyncFileHandle::plat_is_busy()
+{
+	printf ("STUB: CAsyncFileHandle::IsBusy\n");
+
+	return false;
+}
+
+bool				CAsyncFileHandle::plat_is_eof() const
+{
+	printf ("STUB: CAsyncFileHandle::IsEOF\n");
+
+	return false;
+}
+
+void				CAsyncFileHandle::plat_set_priority( int priority )
+{
+	printf ("STUB: CAsyncFileHandle::SetPriority\n");
+}
+
+void				CAsyncFileHandle::plat_set_stream( bool stream )
+{
+	printf ("STUB: CAsyncFileHandle::SetStream\n");
+}
+
+void				CAsyncFileHandle::plat_set_destination( EAsyncMemoryType destination )
+{
+	printf ("STUB: CAsyncFileHandle::SetDestination\n");
+}
+
+void				CAsyncFileHandle::plat_set_buffer_size( size_t buffer_size )
+{
+	printf ("STUB: CAsyncFileHandle::SetBufferSize\n");
+}
+
+void				CAsyncFileHandle::plat_set_blocking( bool block )
+{
+	printf ("STUB: CAsyncFileHandle::SetBlocking\n");
+}
+
+size_t				CAsyncFileHandle::plat_load(void *p_buffer)
+{
+	printf ("STUB: CAsyncFileHandle::Load\n");
+
+	return 0;
+}
+
+size_t				CAsyncFileHandle::plat_read(void *p_buffer, size_t size, size_t count)
+{
+	printf ("STUB: CAsyncFileHandle::Read\n");
+
+	return 0;
+}
+
+size_t				CAsyncFileHandle::plat_write(void *p_buffer, size_t size, size_t count)
+{
+	printf ("STUB: CAsyncFileHandle::Write\n");
+
+	return 0;
+}
+
+char *				CAsyncFileHandle::plat_get_s(char *p_buffer, int maxlen)
+{
+	printf ("STUB: CAsyncFileHandle::GetS\n");
+
+	return NULL;
+}
+
+int					CAsyncFileHandle::plat_seek(long offset, int origin)
+{
+	printf ("STUB: CAsyncFileHandle::Seek\n");
+
+	return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+CAsyncFileHandle	*	CAsyncFileLoader::s_file_handles[MAX_FILE_HANDLES];
+int						CAsyncFileLoader::s_free_handle_index = 0;
+
+volatile int			CAsyncFileLoader::s_manager_busy_count = 0;
+volatile bool			CAsyncFileLoader::s_new_io_completion = false;
+
+volatile CAsyncFileLoader::SCallback	CAsyncFileLoader::s_callback_list[2][MAX_PENDING_CALLBACKS];
+volatile int							CAsyncFileLoader::s_num_callbacks[2] = { 0, 0 };
+int										CAsyncFileLoader::s_cur_callback_list_index = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::sInit()
+{
+	s_free_handle_index = 0;
+	s_plat_init();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::sCleanup()
+{
+	s_plat_cleanup();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::sAsyncSupported()
+{
+	return s_plat_async_supported();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::sExist(const char *filename)
+{
+	return s_plat_exist(filename);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAsyncFileHandle *	CAsyncFileLoader::sOpen(const char *filename, bool blocking, int priority)
+{
+	CAsyncFileHandle *p_file_handle = s_get_file_handle();
+
+	if (p_file_handle)
+	{
+		if (!p_file_handle->open(filename, blocking, priority))
+		{
+			//Dbg_MsgAssert(0, ("Error opening Async file %s", filename));
+			s_free_file_handle(p_file_handle);
+			return NULL;
+		}
+
+		return p_file_handle;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Out of Async handles"));
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::sClose(CAsyncFileHandle *p_file_handle)
+{
+
+	bool result = p_file_handle->close();
+
+	bool free_result = s_free_file_handle(p_file_handle);
+	if ( !free_result )
+	{
+		Dbg_MsgAssert(0, ("sClose(): Can't find async handle in CAsyncFileLoader"));
+	}
+
+	return result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void				CAsyncFileLoader::sWaitForIOEvent(bool all_io_events)
+{
+	bool done = false;
+
+	while (!done)
+	{
+		printf("CAsyncFileLoader waiting for io completion: busy count %d completion %d\n", s_manager_busy_count, s_new_io_completion);
+
+		// Wait for an event
+		while (!s_new_io_completion)
+			;
+
+		printf("CAsyncFileLoader got completion: busy count %d completion %d\n", s_manager_busy_count, s_new_io_completion);
+
+		// execute callbacks
+		s_execute_callback_list();
+
+		done = true;	// assume we are done for now
+		if (all_io_events)
+		{
+			for (int i = 0; i < MAX_FILE_HANDLES; i++)
+			{
+				if (s_file_handles[i]->IsBusy())
+				{
+					printf("CAsyncFileLoader still needs to wait for file handle %d\n", i);
+					Dbg_MsgAssert(s_manager_busy_count, ("CAsyncFileLoader busy count is 0 while file handle %d is busy", i));
+					done = false;
+					break;
+				}
+			}
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::sAsyncInUse()
+{
+#if 0
+	for (int i = 0; i < MAX_FILE_HANDLES; i++)
+	{
+		if (s_file_handles[i]->IsBusy())
+		{
+			return true;
+		}
+	}
+
+	return false;
+#endif
+
+	Dbg_MsgAssert(s_manager_busy_count >= 0, ("CAsyncFileLoader busy count is negative: %d", s_manager_busy_count));
+	return s_manager_busy_count > 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAsyncFileHandle *	CAsyncFileLoader::s_get_file_handle()
+{
+	if (s_free_handle_index < MAX_FILE_HANDLES)
+	{
+		// Find a non-busy handle and exchange with busy, if necessary
+		if (s_file_handles[s_free_handle_index]->IsBusy())
+		{
+			int i;
+			for (i = s_free_handle_index + 1; i < MAX_FILE_HANDLES; i++)
+			{
+				if (!s_file_handles[i]->IsBusy())
+				{
+					CAsyncFileHandle *p_temp = s_file_handles[i];
+					s_file_handles[i] = s_file_handles[s_free_handle_index];
+					s_file_handles[s_free_handle_index] = p_temp;
+					break;
+				}
+			}
+			
+			// If we are full, wait for this one
+			if (i == MAX_FILE_HANDLES)
+			{
+				Dbg_Message("CAsyncFileLoader::sOpen(): waiting for old handle to finish up");
+				s_file_handles[s_free_handle_index]->WaitForIO();
+				Dbg_Message("CAsyncFileLoader::sOpen(): done waiting.");
+			}
+		}
+
+		s_file_handles[s_free_handle_index]->init();		// Clear out any old stuff first
+
+		return s_file_handles[s_free_handle_index++];
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::s_free_file_handle(CAsyncFileHandle *p_file_handle)
+{
+	Dbg_Assert(s_free_handle_index);
+
+	for (int i = 0; i < MAX_FILE_HANDLES; i++)
+	{
+		if (p_file_handle == s_file_handles[i])
+		{
+			// Found it, exchange it with last open handle
+			s_file_handles[i] = s_file_handles[--s_free_handle_index];
+			s_file_handles[s_free_handle_index] = p_file_handle;
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::s_add_callback(CAsyncFileHandle *p_file, EAsyncFunctionType function, int result)
+{
+	// This will be called from an interrupt
+	//scePrintf("Adding callback for handle %x\n", p_file);
+	Dbg_MsgAssert(s_num_callbacks[s_cur_callback_list_index] < MAX_PENDING_CALLBACKS, ("add_callback(): list is full"));
+
+	volatile SCallback * p_callback_entry =  &s_callback_list[s_cur_callback_list_index][s_num_callbacks[s_cur_callback_list_index]++];
+
+	p_callback_entry->mp_file_handle	= p_file;
+	p_callback_entry->m_function		= function;
+	p_callback_entry->m_result			= result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void				CAsyncFileLoader::s_update()
+{
+	s_plat_update();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::s_execute_callback_list()
+{
+	s_plat_swap_callback_list();
+
+	int list_index = s_cur_callback_list_index ^ 1;	// Look at old list
+	int num_callbacks = s_num_callbacks[list_index];
+
+	for (int i = 0; i < num_callbacks; i++)
+	{
+		volatile SCallback * p_callback_entry =  &s_callback_list[list_index][i];
+
+		CAsyncFileHandle *p_file = p_callback_entry->mp_file_handle;
+		Dbg_Assert(p_file->mp_callback);
+
+		//Dbg_Message("Executing callback for handle %x", p_file);
+
+		// Call the callback and the post callback
+		(*p_file->mp_callback)(p_file, p_callback_entry->m_function, p_callback_entry->m_result,
+							   p_file->m_callback_arg0, p_file->m_callback_arg1);
+		p_file->post_io_callback();
+	}
+
+	s_num_callbacks[list_index] = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+DefineSingletonClass( CAsyncFilePoll, "Async File System Poll module" );
+
+CAsyncFilePoll::CAsyncFilePoll()
+{
+	mp_logic_task = new Tsk::Task< CAsyncFilePoll > ( CAsyncFilePoll::s_logic_code, *this );
+}
+
+CAsyncFilePoll::~CAsyncFilePoll()
+{
+	delete mp_logic_task;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAsyncFilePoll::v_start_cb ( void )
+{
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	mlp_manager->AddLogicTask( *mp_logic_task );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAsyncFilePoll::v_stop_cb ( void )
+{
+	mp_logic_task->Remove();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void CAsyncFilePoll::s_logic_code ( const Tsk::Task< CAsyncFilePoll >& task )
+{
+	Dbg_AssertType ( &task, Tsk::Task< CAsyncFilePoll > );
+
+	CAsyncFileLoader::s_update();
+	CAsyncFileLoader::s_execute_callback_list();
+}
+
+}
+
+
+
diff --git a/Code/Sys/File/AsyncFilesys.h b/Code/Sys/File/AsyncFilesys.h
new file mode 100644
index 0000000..bcc3027
--- /dev/null
+++ b/Code/Sys/File/AsyncFilesys.h
@@ -0,0 +1,324 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			File													**
+**																			**
+**	Created:		09/24/02	-	grj										**
+**																			**
+**	File name:		core/sys/AsyncFilesys.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_FILE_ASYNC_FILESYS_H
+#define	__SYS_FILE_ASYNC_FILESYS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __GEL_MODULE_H
+#include 
+#endif
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define   	DEFAULT_ASYNC_PRIORITY	(100)
+#define   	DEFAULT_BUFFER_SIZE		(64 * 1024)
+
+namespace Pcm
+{
+	class CFileStreamInfo;
+}
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+// Forward declarations
+class CAsyncFileHandle;
+class CAsyncFileLoader;
+class CAsyncFilePoll;
+
+/////////////////////////////////////////////////////////////////////////////////////
+// The asynchronous file class
+//
+class CAsyncFileHandle
+{
+public:
+
+	// A callback is called after the completion of every async function
+	typedef void (* AsyncCallback)(CAsyncFileHandle *, EAsyncFunctionType function, int result, unsigned int arg0, unsigned int arg1);
+
+
+	// Check read status
+	volatile bool		IsDone( void );
+	volatile bool		IsBusy( void );
+	bool				IsEOF( void ) const;
+	int					WaitForIO( void );
+
+	// Change parameters
+	void				SetPriority( int priority );
+	void				SetStream( bool stream );			// Tells if loaded all at once or streamed one buffer at a time
+	void				SetDestination( EAsyncMemoryType destination );
+	void				SetBufferSize( size_t buffer_size );
+	void				SetBlocking( bool block );
+	void				SetCallback( AsyncCallback p_callback, unsigned int arg0, unsigned int arg1 );
+
+	size_t				GetFileSize( void );
+
+	// Async commands
+	size_t				Load( void *p_buffer );
+	size_t				Read( void *p_buffer, size_t size, size_t count );
+	size_t				Write( void *p_buffer, size_t size, size_t count );
+	char *				GetS( char *p_buffer, int maxlen );	// Probably don't need this one
+	int					Seek( long offset, int origin );
+
+protected:
+						CAsyncFileHandle();
+	virtual 			~CAsyncFileHandle();
+
+	void				init( void );
+
+	bool				open( const char *filename, bool blocking = true, int priority = DEFAULT_ASYNC_PRIORITY );
+	bool				close( void );
+
+	// Internal busy counter functions
+	volatile int		inc_busy_count();			// Increment and return current count
+	volatile int		dec_busy_count();			// Decrement and return current count
+	volatile int		get_busy_count();			// Just return current count
+
+	// Internal callback
+	virtual void		io_callback(EAsyncFunctionType function, int result, uint32 data);
+	void				post_io_callback();
+
+	// These could be changed by an interrupt, so they are volatile
+	volatile int		m_last_result;
+
+	// Callback
+	AsyncCallback		mp_callback;
+	unsigned int		m_callback_arg0;
+	unsigned int		m_callback_arg1;
+	EAsyncFunctionType	m_current_function;		// So callback knows what was completed
+
+	EAsyncMemoryType	m_mem_destination;
+	bool				m_stream;
+	bool				m_blocking;
+	uint32				m_buffer_size;
+	int					m_priority;
+
+	volatile int		m_file_size;
+	volatile int		m_position;
+
+	// PRE file
+	PreFile::FileHandle *mp_pre_file;
+
+	// constants
+	static const uint32	MAX_FILE_SIZE;
+
+	// platform-specific calls
+	virtual void		plat_init( void );
+
+	virtual bool		plat_open( const char *filename );
+	virtual bool		plat_close( void );
+
+	virtual volatile bool	plat_is_done( void );
+	virtual volatile bool	plat_is_busy( void );
+	virtual bool		plat_is_eof( void ) const;
+
+	virtual void		plat_set_priority( int priority );
+	virtual void		plat_set_stream( bool stream );
+	virtual void		plat_set_destination( EAsyncMemoryType destination );
+	virtual void		plat_set_buffer_size( size_t buffer_size );
+	virtual void		plat_set_blocking( bool block );
+
+	virtual size_t		plat_load( void *p_buffer );
+	virtual size_t		plat_read( void *p_buffer, size_t size, size_t count );
+	virtual size_t		plat_write( void *p_buffer, size_t size, size_t count );
+	virtual char *		plat_get_s( char *p_buffer, int maxlen );
+	virtual int			plat_seek( long offset, int origin );
+
+private:
+	volatile int		m_busy_count;		// Number of items we are waiting to finish asynchronously
+
+	// Friends
+	friend CAsyncFileLoader;
+
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+// The interface to start and end async loads
+//
+class CAsyncFileLoader
+{
+public:
+	static void					sInit();
+	static void					sCleanup();
+
+	static bool					sAsyncSupported();							// Returns true if async has been implemented on
+																			// the platform
+
+	/////////////////////////////////////////////////////////////////////
+	// The following are our versions of the ANSI file IO functions.
+	//
+	static bool					sExist( const char *filename );				// Nothing special about this function
+	static CAsyncFileHandle *	sOpen( const char *filename, bool blocking = true, int priority = DEFAULT_ASYNC_PRIORITY );
+	static bool					sClose( CAsyncFileHandle *p_file_handle );
+
+	// IO Waiting
+	static void					sWaitForIOEvent(bool all_io_events);		// Returns when we get an async result
+	static bool					sAsyncInUse();								// Slow
+
+
+protected:
+	// Constants
+	enum
+	{
+		MAX_FILE_HANDLES = 		16,
+		MAX_PENDING_CALLBACKS = 16,
+	};
+
+	// Callback data
+	struct SCallback
+	{
+		CAsyncFileHandle *				mp_file_handle;
+		EAsyncFunctionType				m_function;
+		int								m_result;
+	};
+
+	// This should actually be declared below the p-line
+	static CAsyncFileHandle *	s_file_handles[MAX_FILE_HANDLES];
+	static int					s_free_handle_index;
+
+	// Status
+	static volatile int			s_manager_busy_count;		// Is 0 when there are no outstanding async IO requests
+	static volatile bool		s_new_io_completion;		// File handle got callback since s_execute_callback_list()
+
+	// Current callback list
+	static volatile SCallback	s_callback_list[2][MAX_PENDING_CALLBACKS];
+	static volatile int			s_num_callbacks[2];
+	static int					s_cur_callback_list_index;
+
+	// File handle management
+	static CAsyncFileHandle *	s_get_file_handle();
+	static bool					s_free_file_handle(CAsyncFileHandle *p_file_handle);
+
+	// callback list functions
+	static void					s_add_callback(CAsyncFileHandle *p_file, EAsyncFunctionType function, int result);
+	static void					s_execute_callback_list();
+
+	// Increment and decrement the manager busy count.  Should only be called by CAsyncFileHandle::inc_busy_count()
+	// and CAsyncFileHandle::dec_busy_count()
+	static void					s_inc_manager_busy_count();
+	static void					s_dec_manager_busy_count();
+
+	// Primarily to allow per-frame polling for platforms that may not support system callbacks on i/o routines
+	static void					s_update();
+
+	// platform-specific calls
+	static void					s_plat_init();
+	static void					s_plat_cleanup();
+
+	static bool					s_plat_async_supported();
+
+	static bool					s_plat_exist( const char *filename );
+	static CAsyncFileHandle *	s_plat_open( const char *filename, int priority );
+	static bool					s_plat_close( CAsyncFileHandle *p_file_handle );
+
+	static void					s_plat_swap_callback_list();		// This is needed below the p-line because interrupts
+																	// need to be disabled for a bit
+	static void					s_plat_update();
+
+	// Friends
+	friend CAsyncFilePoll;
+	friend CAsyncFileHandle;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+// This defines the singleton class for the one-a-frame async calls that are needed.
+// For now, just the callbacks that have stacked up in CAsyncFileLoader are called
+//
+class CAsyncFilePoll  : public Mdl::Module
+{
+	DeclareSingletonClass( CAsyncFilePoll );
+
+			CAsyncFilePoll();
+	virtual ~CAsyncFilePoll();
+
+	void v_start_cb ( void );
+	void v_stop_cb ( void );
+	
+	static Tsk::Task< CAsyncFilePoll >::Code s_logic_code;       
+	Tsk::Task< CAsyncFilePoll > *mp_logic_task;
+};
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline void				CAsyncFileLoader::s_inc_manager_busy_count()
+{
+	s_manager_busy_count++;
+}
+
+inline void				CAsyncFileLoader::s_dec_manager_busy_count()
+{
+	s_manager_busy_count--;
+}
+
+inline volatile int		CAsyncFileHandle::inc_busy_count()
+{
+	CAsyncFileLoader::s_inc_manager_busy_count();			// Also increment manager
+	return ++m_busy_count;
+}
+
+inline volatile int		CAsyncFileHandle::dec_busy_count()
+{
+	CAsyncFileLoader::s_dec_manager_busy_count();			// Also decrement manager
+	return --m_busy_count;
+}
+
+inline volatile int		CAsyncFileHandle::get_busy_count()
+{
+	return m_busy_count;
+}
+
+} // namespace File
+
+#endif  // __SYS_FILE_ASYNC_FILESYS_H
diff --git a/Code/Sys/File/AsyncTypes.h b/Code/Sys/File/AsyncTypes.h
new file mode 100644
index 0000000..a2fa4a1
--- /dev/null
+++ b/Code/Sys/File/AsyncTypes.h
@@ -0,0 +1,91 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			File													**
+**																			**
+**	Created:		09/24/02	-	grj										**
+**																			**
+**	File name:		core/sys/AsyncFilesys.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_FILE_ASYNC_TYPES_H
+#define	__SYS_FILE_ASYNC_TYPES_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define   	DEFAULT_MEMORY_TYPE		(0)
+
+namespace File
+{
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+enum EAsyncMemoryType	// This belongs below the p-line, but need to think of a general way communicate
+{						// this above the p-line.
+#ifdef __PLAT_NGPS__
+	MEM_EE = DEFAULT_MEMORY_TYPE,
+	MEM_IOP,
+#endif // __PLAT_NGPS__
+
+#ifdef __PLAT_NGC__
+	MEM_DRAM = DEFAULT_MEMORY_TYPE,
+	MEM_ARAM,
+#endif // __PLAT_NGC__
+
+#ifdef __PLAT_XBOX__
+	MEM_MAIN = DEFAULT_MEMORY_TYPE,		// Only 1 type for XBox
+#endif //__PLAT_XBOX__
+};
+
+enum EAsyncFunctionType
+{
+	FUNC_IDLE,
+	FUNC_OPEN,
+	FUNC_LOAD,
+	FUNC_READ,
+	FUNC_WRITE,
+	FUNC_SEEK,
+	FUNC_CLOSE,
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+
+} // namespace File
+
+#endif  // __SYS_FILE_ASYNC_TYPES_H
diff --git a/Code/Sys/File/FileLibrary.cpp b/Code/Sys/File/FileLibrary.cpp
new file mode 100644
index 0000000..89fcb85
--- /dev/null
+++ b/Code/Sys/File/FileLibrary.cpp
@@ -0,0 +1,610 @@
+//****************************************************************************
+//* MODULE:         File
+//* FILENAME:       FileLibrary.cpp
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  01/21/2003
+//****************************************************************************
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#ifdef __PLAT_NGC__
+#include 
+#include 
+#include 
+#endif		// __PLAT_NGC__
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace File
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+struct SLibHeader
+{
+	uint32	versionNumber;
+	int		numFiles;
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void filesync_async_callback(File::CAsyncFileHandle*, File::EAsyncFunctionType function, int result, unsigned int arg0, unsigned int arg1)
+{
+	// Dbg_Message("Got callback from %x", arg0);
+	if (function == File::FUNC_READ)
+	{
+		CFileLibrary* p_data = (CFileLibrary*)arg0;
+		bool assertOnFail = (bool)arg1;
+
+		p_data->PostLoad( assertOnFail, result );
+	}
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+CFileLibrary::CFileLibrary()
+{
+#ifndef __PLAT_NGC__
+	mp_baseBuffer = NULL;
+	mp_fileBuffer = NULL;
+#endif		// __PLAT_NGC__
+	mp_fileHandle = NULL;
+	m_dataLoaded = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFileLibrary::~CFileLibrary()
+{
+#ifdef __PLAT_NGC__
+	NsARAM::free( m_aramOffset );
+#else
+	if ( mp_baseBuffer )
+	{
+		Mem::Free( mp_baseBuffer );
+		mp_baseBuffer = NULL;
+	}
+#endif		// __PLAT_NGC__
+	
+	if ( mp_fileHandle )
+	{
+		Dbg_MsgAssert( m_dataLoaded, ( "Can't delete CFileLibrary while it is still being loaded" ) );
+		File::CAsyncFileLoader::sClose( mp_fileHandle );
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+bool CFileLibrary::LoadFinished() const
+{
+	return m_dataLoaded;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CFileLibrary::PostLoad( bool assertOnFail, int file_size )
+{
+	// Handle end of async, if that was used
+	if ( mp_fileHandle )
+	{
+		File::CAsyncFileLoader::sClose( mp_fileHandle );
+		mp_fileHandle = NULL;
+	}
+	
+	Dbg_MsgAssert( (file_size & 0x3) == 0, ( "Size of file is not multiple of 4 (%d bytes)", file_size ) );
+
+#ifdef __PLAT_NGC__
+#define XFER_SIZE ( sizeof( SLibHeader ) + ( vMAX_LIB_FILES * sizeof( SFileInfo ) ) )
+	char buffer[ XFER_SIZE ];
+
+	NsDMA::toMRAM( buffer, m_aramOffset, XFER_SIZE );
+
+	uint8 *pFileData = (uint8*)buffer; 
+#else
+	uint8 *pFileData = (uint8*)mp_fileBuffer;
+#endif		// __PLAT_NGC__
+
+	SLibHeader* pHeader = (SLibHeader*)pFileData;
+	
+	pFileData += sizeof( SLibHeader );
+#ifndef __PLAT_NGC__
+	if ( pFileData > ( mp_fileBuffer + file_size ) )
+	{
+		// out of bounds
+		goto load_fail;
+	}
+#endif		// __PLAT_NGC__
+
+	m_numFiles = pHeader->numFiles;
+
+#ifndef __PLAT_NGC__
+	Dbg_MsgAssert( m_numFiles > 0, ( "No files found in lib mp_fileBuffer = %p", mp_fileBuffer ) );
+    Dbg_MsgAssert( m_numFiles < vMAX_LIB_FILES, ( "Too many subfiles found in lib (%d files, max=%d)", m_numFiles, vMAX_LIB_FILES ) );
+#endif		// __PLAT_NGC__
+
+	// read in table of contents information
+	memcpy( &m_fileInfo, pFileData, (sizeof(SFileInfo) * m_numFiles) );
+	pFileData += (sizeof(SFileInfo) * m_numFiles);
+#ifndef __PLAT_NGC__
+	if ( pFileData > ( mp_fileBuffer + file_size ) )
+	{
+		// out of bounds
+		goto load_fail;
+	}
+#endif		// __PLAT_NGC__
+	
+	for ( int i = 0; i < m_numFiles; i++ )
+	{
+#ifdef __PLAT_NGC__
+		m_fileOffsets[i] = (uint32)(m_aramOffset + m_fileInfo[i].fileOffset);
+		mp_filePointers[i] = NULL;
+#else
+		mp_filePointers[i] = (uint32*)(mp_fileBuffer + m_fileInfo[i].fileOffset);
+#endif		// __PLAT_NGC__
+		Dbg_Message( "File %d @ %08x %s [%d bytes]", i, m_fileInfo[i].fileNameChecksum, Script::FindChecksumName(m_fileInfo[i].fileExtensionChecksum), m_fileInfo[i].fileSize );
+	}
+	
+#ifndef __PLAT_NGC__
+	// to reduce the amount of temp memory needed, we first load the LIB file
+	// on the bottom up heap, and then copy sub-file individually onto the top
+	// down heap (as we reallocate shrink the original LIB file on the bottom up heap)
+	for ( int i = m_numFiles - 1; i >= 0; i-- )
+	{
+		int file_size = m_fileInfo[i].fileSize;
+		
+		uint32* pTempFilePointer = mp_filePointers[i];
+		
+		// GJ:  When I first wrote this class, I wanted to make it generic, 
+		// but it gradually became more cutscene-specific to fix
+		// memory issues.  For example, the following uses the
+		// "cutscene heap" to reduce temp memory overhead.  Perhaps
+		// later on, I can just store the appropriate top/bottomheap pointers
+		// during initialization
+		Dbg_MsgAssert( Mem::Manager::sHandle().CutsceneTopDownHeap(), ( "No cutscene heap?" ) ); 
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneTopDownHeap());
+		mp_filePointers[i] = (uint32*)Mem::Malloc( file_size );
+		memcpy( mp_filePointers[i], pTempFilePointer, file_size );
+		Mem::Manager::sHandle().PopContext();
+		
+#ifdef	__NOPT_ASSERT__
+		uint8* pOldBuffer = mp_baseBuffer;
+#endif
+		
+		Dbg_MsgAssert( Mem::Manager::sHandle().CutsceneBottomUpHeap(), ( "No cutscene heap?" ) ); 
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneBottomUpHeap());
+		mp_baseBuffer = (uint8*)Mem::ReallocateShrink( Mem::GetAllocSize(mp_baseBuffer) - file_size, mp_baseBuffer );
+		Dbg_MsgAssert( mp_baseBuffer == pOldBuffer, ( "Pointer was not supposed to change except for shrinking" ) );
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	// by this point, all that should be left in the original block
+	// is the header, which we don't need any more
+	Mem::Free( mp_baseBuffer );
+	mp_baseBuffer = NULL;
+	mp_fileBuffer = NULL;
+#endif		// __PLAT_NGC__
+
+	// if the data is ready to be accessed
+	m_dataLoaded = true;
+
+	return true;
+
+#ifndef __PLAT_NGC__
+load_fail:
+	Dbg_MsgAssert( 0, ( "Parsing of library failed" ) );
+	return false;
+#endif		// __PLAT_NGC__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CFileLibrary::Load( const char* p_fileName, bool assertOnFail, bool async_load )
+{
+#ifndef __PLAT_NGC__
+	// See Garrett, Garrett, or Mick for explanation.
+	
+	Dbg_MsgAssert( !async_load, ( "Cutscenes aren't supposed to be async loaded anymore" ) );
+	int file_size;
+	
+	Dbg_MsgAssert( Mem::Manager::sHandle().CutsceneBottomUpHeap(), ( "No cutscene heap?" ) ); 
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneBottomUpHeap());
+	
+	mp_baseBuffer = (uint8*)LoadAlloc( p_fileName, &file_size );
+	mp_fileBuffer = mp_baseBuffer;
+	
+	Mem::Manager::sHandle().PopContext();
+	
+	return PostLoad(assertOnFail, file_size);
+#endif
+
+#ifdef __PLAT_NGC__
+	// Make sure music is off here as we really need the memory.
+//	Pcm::StopMusic();
+
+	async_load = false;
+#else
+	// Turn off async for platforms that don't support it
+	if (!File::CAsyncFileLoader::sAsyncSupported())
+	{
+		async_load = false;
+	}
+	
+	if ( async_load )
+	{
+		// Pre-load
+		Dbg_Assert( !mp_fileHandle );
+
+		mp_fileHandle = File::CAsyncFileLoader::sOpen( p_fileName, !async_load );
+
+		// make sure the file is valid
+		if ( !mp_fileHandle )
+		{
+			Dbg_MsgAssert( assertOnFail, ( "Load of %s failed - file not found?", p_fileName ) );
+			return false;
+		}
+
+		int file_size = mp_fileHandle->GetFileSize();
+
+		if ( file_size == 0 )
+		{
+			Dbg_MsgAssert( 0, ("Library file %s size is 0 (possibly not found?)", p_fileName) );
+			File::CAsyncFileLoader::sClose( mp_fileHandle );
+			mp_fileHandle = NULL;
+			return false;
+		}
+
+		Dbg_Assert( !mp_fileBuffer );
+		Dbg_Assert( !mp_baseBuffer );
+		
+		// to reduce the amount of temp memory needed, we first load the LIB file
+		// on the bottom up heap, and then copy sub-file individually onto the top
+		// down heap (as we reallocate shrink the original LIB file on the bottom up heap)
+		Dbg_MsgAssert( Mem::Manager::sHandle().CutsceneBottomUpHeap(), ( "No cutscene heap?" ) ); 
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneBottomUpHeap());
+		mp_baseBuffer = (uint8*) Mem::Malloc( file_size + 64 );
+		mp_fileBuffer = (uint8*)(((uint32)mp_baseBuffer+63)&~63);
+		mp_fileBuffer = mp_baseBuffer;		// Garrett: De-aligning pointer since async filesystem should take care of it.		
+		Mem::Manager::sHandle().PopContext();
+
+		// Set the callback
+		mp_fileHandle->SetCallback( filesync_async_callback, (unsigned int)this, (unsigned int)assertOnFail );
+
+		// read the file in
+		mp_fileHandle->Read( mp_fileBuffer, 1, file_size );
+
+		return true;
+	}
+	else
+#endif		// __PLAT_NGC__
+	{				   
+		// open the file as a stream
+		void* pStream = File::Open( p_fileName, "rb" );
+	
+		// make sure the file is valid
+		if ( !pStream )
+		{
+			Dbg_MsgAssert( assertOnFail, ("Load of %s failed - file not found?", p_fileName) );
+			return false;
+		}
+
+		int file_size = File::GetFileSize( pStream );
+		Dbg_MsgAssert( file_size, ( "Library file size is 0" ) );
+		
+#ifndef __PLAT_NGC__
+		Dbg_Assert( !mp_fileBuffer );
+		Dbg_Assert( !mp_baseBuffer );
+#endif		// __PLAT_NGC__
+		
+#ifdef __PLAT_NGC__
+		// Allocate ARAM
+		m_aramOffset = NsARAM::alloc( file_size, NsARAM::TOPDOWN );
+
+#define BUFFER_SIZE ( 16 * 1024 )
+
+		// read the file in blocks & transfer to ARAM
+		char buffer[BUFFER_SIZE];
+		int size = file_size;
+		uint32 offset = m_aramOffset;
+
+		while ( size > BUFFER_SIZE )
+		{
+			File::Read( buffer, 1, BUFFER_SIZE, pStream );
+			NsDMA::toARAM( offset, buffer, BUFFER_SIZE );
+
+			size -= BUFFER_SIZE;
+			offset += BUFFER_SIZE;
+		}
+
+		// Read & transfer last bit.
+		if ( size )
+		{
+			File::Read( buffer, 1, size, pStream ); 
+			NsDMA::toARAM( offset, buffer, size );
+		}
+//		mp_baseBuffer = (uint8*)Mem::Malloc( file_size );
+//		mp_fileBuffer = mp_baseBuffer;
+#else
+		// to reduce the amount of temp memory needed, we first load the LIB file
+		// on the bottom up heap, and then copy sub-file individually onto the top
+		// down heap (as we reallocate shrink the original LIB file on the bottom up heap)
+		Dbg_MsgAssert( Mem::Manager::sHandle().CutsceneBottomUpHeap(), ( "No cutscene heap?" ) ); 
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneBottomUpHeap());
+		mp_baseBuffer = (uint8*)Mem::Malloc( file_size );
+		mp_fileBuffer = mp_baseBuffer;
+		Mem::Manager::sHandle().PopContext();
+
+		// read the file in
+		if ( !File::Read( mp_fileBuffer, 1, file_size, pStream ) )
+		{
+			Mem::Free( mp_fileBuffer );
+			mp_baseBuffer = NULL;
+			mp_fileBuffer = NULL;
+			File::Close( pStream );
+
+			Dbg_MsgAssert( assertOnFail, ( "Load of %s failed - bad format?", p_fileName ) );
+			return false;
+		}
+#endif		// __PLAT_NGC__
+
+		File::Close(pStream);
+
+		return PostLoad(assertOnFail, file_size);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+int CFileLibrary::GetNumFiles() const 
+{
+	return m_numFiles;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+uint32* CFileLibrary::GetFileData( int index )
+{
+	Dbg_MsgAssert( index >= 0 && index < m_numFiles, ( "Out of range file index %d", index ) );
+
+#ifdef __PLAT_NGC__
+	if ( !mp_filePointers[index] )
+	{
+		// Allocate memory & copy over.
+//		Dbg_MsgAssert( Mem::Manager::sHandle().CutsceneTopDownHeap(), ( "No cutscene heap?" ) ); 
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().CutsceneTopDownHeap());
+
+		int mem_available;
+		bool need_to_pop = false;
+		int size = m_fileInfo[index].fileSize;
+
+		Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().AudioHeap() );
+		mem_available = Mem::Manager::sHandle().Available();
+		if ( size < ( mem_available - ( 15 * 1024 ) ) )
+		{
+			need_to_pop = true;
+		}
+		else
+		{
+			Mem::Manager::sHandle().PopContext();
+			Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().FrontEndHeap() );
+			mem_available = Mem::Manager::sHandle().Available();
+			if ( size < ( mem_available - ( 40 * 1024 ) ) )
+			{
+				need_to_pop = true;
+			}
+			else
+			{
+				Mem::Manager::sHandle().PopContext();
+				Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ThemeHeap() );
+				mem_available = Mem::Manager::sHandle().Available();
+				if ( size < ( mem_available - ( 5 * 1024 ) ) )
+				{
+					need_to_pop = true;
+				}
+				else
+				{
+					Mem::Manager::sHandle().PopContext();
+					Mem::Manager::sHandle().PushContext( Mem::Manager::sHandle().ScriptHeap() );
+					mem_available = Mem::Manager::sHandle().Available();
+					if ( size < ( mem_available - ( 40 * 1024 ) ) )
+					{
+						need_to_pop = true;
+					}
+					else
+					{
+						Mem::Manager::sHandle().PopContext();
+					}
+				}
+			}
+		}
+
+		mp_filePointers[index] = (uint32*)Mem::Malloc( m_fileInfo[index].fileSize );
+		NsDMA::toMRAM( mp_filePointers[index], m_fileOffsets[index], m_fileInfo[index].fileSize );
+
+		if ( need_to_pop )
+		{
+			Mem::Manager::sHandle().PopContext();
+		}
+
+		Mem::Manager::sHandle().PopContext();
+	}
+#endif		// __PLAT_NGC__
+
+	return mp_filePointers[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+const SFileInfo* CFileLibrary::GetFileInfo( int index ) const
+{
+	Dbg_MsgAssert( index >= 0 && index < m_numFiles, ( "Out of range file index %d", index ) );
+
+	return &m_fileInfo[index];
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+const SFileInfo* CFileLibrary::GetFileInfo( uint32 name, uint32 extension, bool assertOnFail ) const
+{
+	for ( int index = 0; index < m_numFiles; index++ )
+	{
+		if ( m_fileInfo[index].fileNameChecksum == name && m_fileInfo[index].fileExtensionChecksum == extension )
+		{
+			return &m_fileInfo[index];
+		}
+	}
+
+	if ( assertOnFail )
+	{
+		Dbg_MsgAssert( 0, ( "Couldn't find file %08x %08x\n", name, extension ) );
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+uint32* CFileLibrary::GetFileData( uint32 name, uint32 extension, bool assertOnFail )
+{
+	for ( int index = 0; index < m_numFiles; index++ )
+	{
+		if ( m_fileInfo[index].fileNameChecksum == name && m_fileInfo[index].fileExtensionChecksum == extension )
+		{
+			return GetFileData( index );
+		}
+	}
+
+	if ( assertOnFail )
+	{
+		Dbg_MsgAssert( 0, ( "Couldn't find file %08x %08x\n", name, extension ) );
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+bool CFileLibrary::ClearFile( uint32 name, uint32 extension )
+{
+	bool success = false;
+
+	for ( int index = 0; index < m_numFiles; index++ )
+	{
+		if ( m_fileInfo[index].fileNameChecksum == name && m_fileInfo[index].fileExtensionChecksum == extension )
+		{
+			Mem::Free( mp_filePointers[index] );
+			mp_filePointers[index] = NULL;
+			success = true;
+		}
+	}
+
+	return success;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+bool CFileLibrary::FileExists( uint32 name, uint32 extension )
+{
+	for ( int index = 0; index < m_numFiles; index++ )
+	{
+		if ( m_fileInfo[index].fileNameChecksum == name && m_fileInfo[index].fileExtensionChecksum == extension )
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+				   
+} // namespace File
diff --git a/Code/Sys/File/FileLibrary.h b/Code/Sys/File/FileLibrary.h
new file mode 100644
index 0000000..d62ba58
--- /dev/null
+++ b/Code/Sys/File/FileLibrary.h
@@ -0,0 +1,117 @@
+//****************************************************************************
+//* MODULE:         File
+//* FILENAME:       FileLibrary.h
+//* OWNER:          Gary Jesdanun
+//* CREATION DATE:  01/21/2003
+//****************************************************************************
+
+#ifndef __SYS_FILE_FILELIBRARY_H
+#define __SYS_FILE_FILELIBRARY_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+namespace File
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+	class CAsyncFileHandle;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+struct SFileInfo
+{
+	uint32	fileOffset;
+	int		fileSize;
+	uint32	fileNameChecksum;
+	uint32	fileExtensionChecksum;
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class CFileLibrary : public Spt::Class
+{
+protected:
+	enum
+	{
+		vMAX_LIB_FILES = 128
+	};
+
+public:
+	CFileLibrary();
+	virtual 			~CFileLibrary();
+
+public:
+	bool 				Load( const char* p_fileName, bool assertOnFail, bool async_load );
+	bool				PostLoad( bool assertOnFail, int file_size );
+	bool				LoadFinished() const;
+
+public:
+	int					GetNumFiles() const;
+	uint32*				GetFileData( int index );
+	uint32*				GetFileData( uint32 name, uint32 extension, bool assertOnFail = true );
+	const SFileInfo*	GetFileInfo( int index ) const;
+	const SFileInfo*	GetFileInfo( uint32 name, uint32 extension, bool assertOnFail = true ) const;
+	bool				ClearFile( uint32 name, uint32 extension );
+	bool				FileExists( uint32 name, uint32 extension );
+
+protected:
+	CAsyncFileHandle*	mp_fileHandle;
+#ifdef __PLAT_NGC__
+	uint32				m_aramOffset;
+#else
+	uint8* 				mp_baseBuffer;
+	uint8* 				mp_fileBuffer;
+#endif		// __PLAT_NGC__
+	int					m_numFiles;
+	int					m_originalNumFiles;
+	SFileInfo			m_fileInfo[vMAX_LIB_FILES];
+#ifdef __PLAT_NGC__
+	uint32				m_fileOffsets[vMAX_LIB_FILES];
+#endif		// __PLAT_NGC__
+	uint32*				mp_filePointers[vMAX_LIB_FILES];
+	bool				m_dataLoaded;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace File
+
+#endif	// __SYS_FILE_FILELIBRARY_H
+
+
diff --git a/Code/Sys/File/PRE.cpp b/Code/Sys/File/PRE.cpp
new file mode 100644
index 0000000..49ca877
--- /dev/null
+++ b/Code/Sys/File/PRE.cpp
@@ -0,0 +1,1620 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:									 								**
+**																			**
+**	File name:																**
+**																			**
+**	Created by:		rjm														**
+**																			**
+**	Description:						 									**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC pre
+// @module pre | None
+// @subindex Scripting Database
+// @index script | pre
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifdef	__PLAT_NGPS__
+#include 
+#endif		//	__PLAT_NGPS__
+#include 
+//#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// cd shared by the music streaming stuff...  ASSERT if file access attempted
+// while music is streaming:
+#include 
+
+// script stuff
+#include  
+#include 
+
+#ifdef __PLAT_NGC__
+#include "sys/ngc/p_aram.h"
+#include "sys/ngc/p_dma.h"
+#include "sys/ngc/p_display.h"
+#ifdef __NOPT_FINAL__
+#define __PRE_ARAM__
+#endif		// __NOPT_FINAL__
+#endif
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+#define DEBUG_PRE 0
+
+#define	PRE_NAME_OFFSET 16		// was 12, but then I added an extra checksum field
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define CURRENT_PRE_VERSION	0xabcd0003			// as of 3/14/2001
+//#define CURRENT_PRE_VERSION	0xabcd0001		// until 3/14/2001
+
+#define RINGBUFFERSIZE		 4096	/* N size of ring buffer */	
+#define MATCHLIMIT		   18	/* F upper limit for match_length */
+#define THRESHOLD	2   /* encode string into position and length */
+
+#define WriteOut(x) 	{*pOut++ = x;}
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+PreMgr *PreMgr::sp_mgr = NULL;
+bool    PreMgr::s_lastExecuteSuccess = false; 
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+
+
+#define USE_BUFFER	1		 // we don't need no stinking buffer!!!!
+ 
+#if USE_BUFFER
+#ifdef	__PLAT_NGPS__
+// ring buffer is just over 4K 4096+17), 
+// so fits nicely in the PS2's 16K scratchpad 
+unsigned char	text_buf[RINGBUFFERSIZE + MATCHLIMIT - 1];	
+// Note:  if we try to use the scratchpad, like this
+// then the code actually runs slower
+// if we want to optimize this, then it should
+// be hand crafted in assembly, using 128bit registers
+//	const unsigned char * text_buf = (unsigned char*) 0x70000000;
+//	#define text_buf ((unsigned char*) 0x70000000)
+#else
+unsigned char
+		text_buf[RINGBUFFERSIZE + MATCHLIMIT - 1];	/* ring buffer of size N,
+			with extra F-1 bytes to facilitate string comparison */
+#endif
+#endif
+
+
+#ifdef __PRE_ARAM__
+#define	ReadInto(x)		if (!Len) break; Len--; x = get_byte( p_buffer ) 
+#define	ReadInto2(x)	Len--; x = get_byte( p_buffer ) 	  // version that knows Len is Ok
+#else
+#define	ReadInto(x)		if (!Len) break; Len--; x = *pIn++ 
+#define	ReadInto2(x)	Len--; x = *pIn++ 	  // version that knows Len is Ok
+#endif		// __PRE_ARAM__
+
+
+// Decode an LZSS encoded stream
+// Runs at approx 12MB/s on PS2	 without scratchpad (which slows it down in C)
+// a 32x CD would run at 4.8MB/sec, although we seem to get a lot less than this
+// with our current file system, more like 600K per seconds.....
+// Need to write a fast streaming file system....
+
+#ifdef __PRE_ARAM__
+#define INPUT_BUFFER_SIZE (16 * 1024)
+static uint32 p_aram_in;
+static int aram_len = 0;
+static int buffer_bytes = 0;
+static int buffer_offset = 0;
+static unsigned char get_byte( unsigned char * p_buffer )
+{
+	if ( buffer_bytes == 0 )
+	{
+		int toread;
+		if ( aram_len >= INPUT_BUFFER_SIZE )
+		{
+			toread = INPUT_BUFFER_SIZE;
+		}
+		else
+		{
+			toread = aram_len;
+		}
+
+		NsDMA::toMRAM( p_buffer, p_aram_in, toread );
+		p_aram_in += INPUT_BUFFER_SIZE;
+		aram_len -= INPUT_BUFFER_SIZE;
+		buffer_bytes = toread; 
+		buffer_offset = 0;
+	}
+	unsigned char bb = p_buffer[buffer_offset];
+	buffer_bytes--;
+	buffer_offset++;
+	return bb;
+}
+#endif		// __PRE_ARAM__
+
+void DecodeLZSS(unsigned char *pIn, unsigned char *pOut, int Len)	/* Just the reverse of Encode(). */
+{
+	int  i, j, k, r, c;
+//	uint64	LongWord;
+//	int bytes = 0;
+//	unsigned char *pScratch;
+	unsigned int  flags;
+
+	// Ensure we have decent values for the decode length
+	Dbg_Assert(( Len >= 0 ) && ( Len < 0x2000000 ));
+
+	if(( Len < 0 ) || ( Len >= 0x2000000 ))
+	{
+		while( 1 );
+	}
+#ifdef __PRE_ARAM__
+	unsigned char p_buffer[INPUT_BUFFER_SIZE];
+	p_aram_in = (uint32)pIn;
+	aram_len = Len;
+#endif		// __PRE_ARAM__
+
+
+//	int basetime =  (int) Tmr::ElapsedTime(0);
+//	int len = Len;
+
+//	int	OutBytes = 4;
+//	int	OutWord = 0;
+
+	#if USE_BUFFER
+	for (i = 0; i < RINGBUFFERSIZE - MATCHLIMIT; i++)
+		 text_buf[i] = ' ';
+	r = RINGBUFFERSIZE - MATCHLIMIT;
+	#else
+	r = RINGBUFFERSIZE - MATCHLIMIT;
+	#endif
+	flags = 0;
+	for ( ; ; )
+	{
+		if (((flags >>= 1) & 256) == 0)
+		{
+			ReadInto(c);
+			flags = c | 0xff00;			/* uses higher byte cleverly */
+		}										/* to count eight */
+		if (flags & 1)
+		{
+			ReadInto(c);
+			//			putc(c, outfile);
+			WriteOut(c);
+			#if USE_BUFFER
+			text_buf[r++] = c;
+			r &= (RINGBUFFERSIZE - 1);
+			#else
+			r++;
+//			r &= (RINGBUFFERSIZE - 1);	  // don't need to wrap r until it is used
+			#endif
+		}
+		else
+		{
+			ReadInto(i);			
+			ReadInto2(j);			// note, don't need to check len on this one.... 
+			
+			i |= ((j & 0xf0) << 4);						// i is 12 bit offset
+			
+			#if !USE_BUFFER
+			j = (j & 0x0f) + THRESHOLD+1;				// j is 4 bit length (above the threshold)
+			unsigned char *pStream;
+			r &= (RINGBUFFERSIZE - 1);					// wrap r around before it is used
+			pStream = pOut - r;					  		// get base of block
+			if (i>=r)										// if offset > r, then
+				pStream -= RINGBUFFERSIZE;				// it's the previous block
+			pStream += i;									// add in the offset to the base
+			r+=j;												// add size to r
+			while (j--)										// copy j bytes
+				WriteOut(*pStream++);
+			#else
+			
+			j = (j & 0x0f) + THRESHOLD;				// j is 4 bit length (above the threshold)
+			for (k = 0; k <= j; k++)					// just copy the bytes
+			{
+				c =  text_buf[(i+k) & (RINGBUFFERSIZE - 1)]; 
+				WriteOut(c);
+				text_buf[r++] = c;
+				r &= (RINGBUFFERSIZE - 1);
+			}
+			#endif
+		}
+	}
+//	int Time = (int) Tmr::ElapsedTime(basetime);
+//	if (Time > 5)
+//	{
+//		printf("decomp time is %d ms, for %d bytes,  %d bytes/second\n", Time,len, len * 1000 /Time );
+//	}
+
+}
+
+void EndOfDecodeLZSS( void )
+{
+} 
+
+
+void PreFile::s_delete_file(_File *pFile, void *pData)
+{
+	Dbg_Assert(pFile);
+	delete pFile;
+}
+
+
+
+
+PreFile::PreFile(uint8 *p_file_buffer, bool useBottomUpHeap)
+{
+	m_use_bottom_up_heap=useBottomUpHeap;
+	
+	mp_table = new Lst::StringHashTable<_File>(4);	
+
+	Dbg_AssertPtr(p_file_buffer);
+
+	mp_buffer = p_file_buffer;
+	#ifdef __NOPT_ASSERT__
+#ifdef __PRE_ARAM__
+	uint version;
+	NsDMA::toMRAM( &version, (uint32)mp_buffer + 4, 4 );
+#else
+	uint version = 	*((int *) (mp_buffer + 4));
+#endif		// __PRE_ARAM__
+	Dbg_MsgAssert(version == CURRENT_PRE_VERSION,( "PRE file version (%x) not current (%x)",version,CURRENT_PRE_VERSION));
+	#endif
+#ifdef __PRE_ARAM__
+	NsDMA::toMRAM( &m_numEntries, (uint32)mp_buffer + 8, 4 );
+#else
+	m_numEntries = *((int *)(mp_buffer + 8));
+#endif		// __PRE_ARAM__
+
+	uint8 *pEntry = mp_buffer + 12;
+	for (int i = 0; i < m_numEntries; i++)
+	{
+#ifdef __PRE_ARAM__
+		int data_size;
+		int compressed_data_size;
+		short text_size;
+		NsDMA::toMRAM( &data_size, (uint32)pEntry, 4 );
+		NsDMA::toMRAM( &compressed_data_size, (uint32)pEntry + 4, 4 );
+		NsDMA::toMRAM( &text_size, (uint32)pEntry + 8, 2 );
+#else
+		int data_size 				= *((int *) pEntry);
+		int compressed_data_size 	= *((int *) (pEntry + 4));
+		int text_size	 			= *((short *) (pEntry + 8));
+#endif		// __PRE_ARAM__
+		int actual_data_size = (compressed_data_size != 0) ? compressed_data_size : data_size;
+			
+#ifdef __PRE_ARAM__
+		char pName[1024];
+		NsDMA::toMRAM( pName, (uint32)pEntry + PRE_NAME_OFFSET, text_size );
+#else
+		char *pName = (char *) pEntry + PRE_NAME_OFFSET;
+#endif		// __PRE_ARAM__
+		uint8 *pCompressedData = pEntry + PRE_NAME_OFFSET + text_size;
+		
+		_File *pFile;
+		if (m_use_bottom_up_heap)
+		{
+			pFile = new (Mem::Manager::sHandle().BottomUpHeap()) _File;
+		}
+		else
+		{
+			pFile = new (Mem::Manager::sHandle().TopDownHeap()) _File;
+		}	
+
+		if (!mp_table->GetItem(pName)) 
+		{
+			// file is not in table, safe to add
+			mp_table->PutItem(pName, pFile);
+			
+			pFile->compressedDataSize = compressed_data_size;
+			pFile->pCompressedData = pCompressedData; 
+			pFile->pData = NULL;
+			pFile->m_position = 0;
+			pFile->m_filesize = data_size;
+		}
+		else
+			// Somehow, file is already in table, just kill it
+			// Later, I'll want to add an assert
+			delete pFile;
+		
+#		if DEBUG_PRE
+		printf("   %s, size %d\n", pName, data_size);
+#		endif
+		
+		pEntry += PRE_NAME_OFFSET + text_size + ((actual_data_size + 3) & (~3));
+	}
+
+#	if DEBUG_PRE
+	printf("Done loading PRE\n");
+#	endif
+	
+	mp_activeFile = NULL;
+	m_numOpenAsyncFiles = 0;
+}
+
+
+
+PreFile::~PreFile()
+{
+#ifdef __PRE_ARAM__
+	NsARAM::free( (uint32)mp_buffer );
+#else
+	delete mp_buffer;
+#endif		// __PRE_ARAM__
+	mp_table->HandleCallback(s_delete_file, NULL);
+	mp_table->FlushAllItems();
+
+	delete mp_table;
+
+	Dbg_MsgAssert(m_numOpenAsyncFiles == 0, ("Can't unload Pre because there are still %d async files open", m_numOpenAsyncFiles));
+}
+
+
+bool PreFile::FileExists(const char *pName)
+{
+	
+	_File *pFile = mp_table->GetItem(pName, false);
+	return (pFile != NULL);
+}
+
+// returns handle pointer
+PreFile::FileHandle *PreFile::GetContainedFile(const char *pName)
+{
+	
+	_File *pFile = mp_table->GetItem(pName, false);
+	if (!pFile) 
+		return NULL;
+	
+	PreFile::FileHandle *pHandle = pFile;
+	//// kinda roundabout, but sets mp_activeFile
+	GetContainedFileByHandle(pHandle);
+	
+#ifdef __PLAT_NGC__
+	NsDisplay::doReset();
+#endif		// __PLAT_NGC__
+
+	// do we need to fetch file data?
+	if (!mp_activeFile->pData)
+	{
+		if (mp_activeFile->compressedDataSize)
+		{
+			Mem::PushMemProfile((char*)pName);
+			if (m_use_bottom_up_heap)
+			{
+				mp_activeFile->pData = new (Mem::Manager::sHandle().BottomUpHeap()) uint8[mp_activeFile->m_filesize];		
+			}
+			else
+			{
+				mp_activeFile->pData = new (Mem::Manager::sHandle().TopDownHeap()) uint8[mp_activeFile->m_filesize];		
+			}	
+			Mem::PopMemProfile();
+			// need to uncompress data
+			DecodeLZSS(mp_activeFile->pCompressedData, mp_activeFile->pData, mp_activeFile->compressedDataSize);	
+		}
+#ifdef __PRE_ARAM__
+		else
+		{
+			// Just DMA to main RAM.
+			Mem::PushMemProfile((char*)pName);
+			if (m_use_bottom_up_heap)
+			{
+				mp_activeFile->pData = new (Mem::Manager::sHandle().BottomUpHeap()) uint8[mp_activeFile->m_filesize];
+			}
+			else
+			{
+				mp_activeFile->pData = new (Mem::Manager::sHandle().TopDownHeap()) uint8[mp_activeFile->m_filesize];
+			}	
+			Mem::PopMemProfile();
+			NsDMA::toMRAM( mp_activeFile->pData, (uint32)mp_activeFile->pCompressedData, mp_activeFile->m_filesize );
+		}
+#endif		// __PRE_ARAM__
+	}
+
+	
+
+	return pHandle;
+}
+
+
+// allocate memory and load file directly from a pre file, if it is there
+// This avoids the problem of having to have the decompressed file in memory twice
+// when we are loading directly, like with Pip::Load()
+// using this enables us to actually load network game, where there is 1MB less heap during loading
+//
+// returns a pointer to the file in memory
+// or NULL if the file is not in this pre file.
+// optional parameter p_dest, if set to anything other then NULL, then load file to this destination
+void *PreFile::LoadContainedFile(const char *pName,int *p_size, void *p_dest)
+{
+
+//	printf ("LoadContainedFile(%s\n",pName);
+	
+	_File *pFile = mp_table->GetItem(pName, false);
+	if (!pFile) 
+	{
+		return NULL;
+	}	
+	
+	*p_size = pFile->m_filesize;
+
+	// If destination was passed as NULL, then allocate memory	
+	if (!p_dest)
+	{
+		p_dest = Mem::Malloc(pFile->m_filesize);		
+	}
+	
+	// do we need to deompress file data?
+	if (!pFile->pData)
+	{
+		if (pFile->compressedDataSize)
+		{
+			// need to uncompress data
+			//DecodeLZSS(mp_activeFile->pCompressedData, mp_activeFile->pData, mp_activeFile->compressedDataSize);	
+			DecodeLZSS(pFile->pCompressedData, (uint8*)p_dest, pFile->compressedDataSize);	
+		}
+		else
+		{
+#ifdef __PRE_ARAM__
+			// Just DMA to main RAM.
+			NsDMA::toMRAM( p_dest, (uint32)pFile->pCompressedData, pFile->m_filesize );
+#else
+			memcpy(p_dest,(void*)pFile->pCompressedData,pFile->m_filesize);
+#endif		// __PRE_ARAM__
+		}
+	}
+	else
+	{
+//		printf ("Copying %d bytes from %p to %p\n",pFile->sky.SOF,p_dest,(void*)pFile->pData);
+		memcpy(p_dest,(void*)pFile->pData,pFile->m_filesize);
+	}
+	return  p_dest;
+}
+	
+
+
+uint8 *PreFile::GetContainedFileByHandle(PreFile::FileHandle *pHandle)
+{
+	mp_table->IterateStart();
+	_File *pFile = mp_table->IterateNext();
+	while(pFile)
+	{
+		uint8 *pCompressedData = pFile->pCompressedData;
+		if (pCompressedData && pFile == pHandle)
+		{
+			mp_activeFile = pFile;
+			
+			if (mp_activeFile->compressedDataSize)
+				return mp_activeFile->pData;
+			else
+				return mp_activeFile->pCompressedData;
+		}
+		pFile = mp_table->IterateNext();
+	}
+
+	return NULL;
+}
+
+
+
+void PreFile::Reset()
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	mp_activeFile->m_position = 0;
+}
+
+
+
+uint32 PreFile::Read(void *addr, uint32 count)
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	int seek_offs = mp_activeFile->m_position;
+	unsigned int limit = mp_activeFile->m_filesize - seek_offs;
+	int copy_number = (count <= limit) ? count : limit; 
+#ifdef __PRE_ARAM__
+	memcpy(addr, mp_activeFile->pData + mp_activeFile->m_position, copy_number);
+#else
+	if (mp_activeFile->compressedDataSize)
+	{
+		Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed"));
+		memcpy(addr, mp_activeFile->pData + mp_activeFile->m_position, copy_number);
+	}
+	else
+	{
+		memcpy(addr, mp_activeFile->pCompressedData + mp_activeFile->m_position, copy_number);
+	}
+#endif		// __PRE_ARAM__
+
+	mp_activeFile->m_position += copy_number;
+
+#if DEBUG_PRE
+		printf("PRE: read %d bytes from file, handle 0x%x\n", copy_number, (int) mp_activeFile->pData);
+#endif
+	return copy_number;
+}
+
+
+
+int PreFile::Eof()
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	if (mp_activeFile->m_position >= mp_activeFile->m_filesize)
+	{
+#if DEBUG_PRE
+		printf("PRE: at end of file\n");
+#endif
+		return 1;
+	}
+
+#if DEBUG_PRE
+	printf("PRE: not at end of file\n");
+#endif
+	return 0;
+}
+
+
+
+void PreFile::Open(bool async)
+{
+	if (async)
+	{
+		m_numOpenAsyncFiles++;
+	}
+}
+
+void PreFile::Close(bool async)
+{
+	
+	//Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed"));
+
+	if (mp_activeFile->pData)
+		delete mp_activeFile->pData;
+	mp_activeFile->pData = NULL;
+
+	if (async)
+	{
+		m_numOpenAsyncFiles--;
+		Dbg_MsgAssert(m_numOpenAsyncFiles >= 0, ("PreFile: m_numOpenAsyncFiles is negative after Close()"));
+	}
+}
+
+
+
+int PreFile::Seek(long offset, int origin)
+{
+	int32 old_pos = mp_activeFile->m_position;
+
+	// SEEK_CUR, SEEK_END, SEEK_SET
+	switch(origin)
+	{
+		case SEEK_CUR:
+			mp_activeFile->m_position += offset;
+			break;
+		case SEEK_END:
+			mp_activeFile->m_position = mp_activeFile->m_filesize - offset;
+			break;
+		case SEEK_SET:
+			mp_activeFile->m_position = offset;
+			break;
+		default:
+			return -1;
+	}
+
+	if (mp_activeFile->m_position < 0 || mp_activeFile->m_position > mp_activeFile->m_filesize)
+	{
+		mp_activeFile->m_position = old_pos;
+		return -1;
+	}
+
+	return 0;
+}
+
+
+
+PreMgr::PreMgr() 
+{
+	mp_table = new Lst::StringHashTable(4);
+
+	sp_mgr = this;
+
+
+	mp_activeHandle = NULL;
+	mp_activeData = NULL;
+
+	mp_activeNonPreHandle = NULL;
+
+	m_num_pending_pre_files = 0;
+}
+
+
+
+PreMgr::~PreMgr()
+{
+	delete mp_table;
+}
+
+
+
+// Returns handle
+// Not frequently called
+PreFile::FileHandle *PreMgr::getContainedFile(const char *pName)
+{
+	
+	Dbg_AssertPtr(pName);
+
+	// replace all '/' with '\'
+	char cleaned_name[128];
+	const char *pCharIn = pName;
+	char *pCharOut = cleaned_name;
+	while (1)
+	{
+		*pCharOut = *pCharIn;
+		if (*pCharIn == '\0') break;
+		if (*pCharOut == '/') *pCharOut = '\\';
+		pCharIn++;
+		pCharOut++;		
+	}
+
+	PreFile::FileHandle *pHandle = NULL;
+
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		pHandle = pPre->GetContainedFile(cleaned_name);
+		if (pHandle) 
+		{
+			mp_activePre = pPre;
+			mp_activeHandle = pHandle;
+			mp_activeData = pPre->GetContainedFileByHandle(pHandle);
+#			ifdef __PLAT_NGPS__
+			scePrintf("+++ %s is in PRE\n", cleaned_name);
+#			endif
+			return pHandle;
+		}
+		pPre = mp_table->IterateNext();
+	}
+
+#	ifdef __PLAT_NGPS__
+	scePrintf("--- %s not found in PRE\n", cleaned_name);
+#	endif
+	return NULL;
+}
+
+// returns true if the file exists in any of the pre files
+bool	PreMgr::fileExists(const char *pName)
+{
+	Dbg_AssertPtr(pName);
+	// replace all '/' with '\'
+	char cleaned_name[128];
+	const char *pCharIn = pName;
+	char *pCharOut = cleaned_name;
+	while (1)
+	{
+		*pCharOut = *pCharIn;
+		if (*pCharIn == '\0') break;
+		if (*pCharOut == '/') *pCharOut = '\\';
+		pCharIn++;
+		pCharOut++;		
+	}
+
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		if (pPre->FileExists(cleaned_name))
+		{
+			return true;
+		}
+		pPre = mp_table->IterateNext();
+	}
+	return false;
+}
+
+
+
+// returns pointer to data
+uint8 *PreMgr::getContainedFileByHandle(PreFile::FileHandle *pHandle)
+{
+	
+	Dbg_AssertPtr(pHandle);
+
+	// if we know that the file in question is not in the PRE system,
+	// then it's a regular file, don't waste time looking for it
+	if (mp_activeNonPreHandle == pHandle)
+		return NULL;
+	
+	if (mp_activeHandle == pHandle)
+		// mp_activePre will be unchanged
+		return mp_activeData;
+	
+	uint8 *pData = NULL;
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		pData = pPre->GetContainedFileByHandle(pHandle);
+		if (pData)
+		{
+			mp_activePre = pPre;
+			mp_activeHandle = pHandle;
+			mp_activeData = pData;			
+			return pData;
+		}
+		pPre = mp_table->IterateNext();
+	}
+
+	// obviously this file is not in the PRE system, mark as such
+	mp_activeNonPreHandle = pHandle;
+	return NULL;
+}
+
+
+
+// there's a wrapper around this now, so that we can do
+// some memory-context switching
+void PreMgr::loadPre(const char *pFilename, bool async, bool dont_assert, bool useBottomUpHeap)
+{
+#ifdef DVDETH
+	m_blockPreLoading = true;
+#else
+	m_blockPreLoading = (bool) Script::GetInt("block_pre_loading", false);
+#endif		// DVDETH 
+
+	if (m_blockPreLoading)
+		return;
+
+	// Turn off async for platforms that don't support it
+	if (!File::CAsyncFileLoader::sAsyncSupported())
+	{
+		async = false;
+	}
+
+#ifdef __PRE_ARAM__
+	Dbg_MsgAssert(!async, ("Async loading not implemented for ARAM transfer"));
+#endif
+
+	if( !async && Pcm::UsingCD() )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return;
+	}
+
+	// Moved this to below the Pcm::UsingCD() call as that is used (bad!!) to turn off
+	// music and streams.
+#	ifdef __PLAT_NGPS__
+//	scePrintf("Loading PRE file %s...\n", pFilename);
+#	endif
+
+	char fullname[256];
+	sprintf(fullname, "pre\\%s", pFilename);
+
+#	ifdef __PLAT_XBOX__
+	// Replace the .pre extension (if one exists) with .prx for Xbox PRE file.
+	if( strrchr( pFilename, '.' ) && ( strlen( fullname ) > 4 ))
+	{
+		fullname[strlen( fullname ) - 1] = 'x';
+	}
+#	endif
+
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+	Tmr::Time basetime = Tmr::ElapsedTime(0);
+#endif
+
+	int file_size;
+	uint8 *pFile = NULL;
+
+	// Try loading asynchronously
+	if (async)
+	{
+		Dbg_MsgAssert(m_num_pending_pre_files < MAX_NUM_ASYNC_LOADS, ("Too many async LoadPre's pending"));
+
+		CAsyncFileHandle *p_fileHandle = CAsyncFileLoader::sOpen( fullname, !async );
+		if (p_fileHandle)
+		{
+			Dbg_MsgAssert(strlen(pFilename) < MAX_COMPACT_FILE_NAME, ("Pre file name %s is greater than %d bytes", pFilename, MAX_COMPACT_FILE_NAME - 1));
+
+			// Add to pending list
+			strcpy(m_pending_pre_files[m_num_pending_pre_files].m_file_name, pFilename);
+			m_pending_pre_files[m_num_pending_pre_files++].mp_file_handle = p_fileHandle;
+
+			file_size = p_fileHandle->GetFileSize();
+			Dbg_MsgAssert(file_size, ("Pre file size is 0"));
+
+			Mem::PushMemProfile((char*)fullname);
+			if (useBottomUpHeap)
+			{
+				pFile = new (Mem::Manager::sHandle().BottomUpHeap()) uint8[file_size];
+			}
+			else
+			{
+				pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size];
+			}	
+			Mem::PopMemProfile();
+
+			// Set the callback
+			p_fileHandle->SetCallback(async_callback, (unsigned int) this, (unsigned int) pFile);
+
+			// read the file in
+			p_fileHandle->Read( pFile, 1, file_size );
+			return;
+		}
+	}
+
+	// If we got here, we didn't do an async load
+	file_size = CanFileBeLoadedQuickly( fullname );
+	if ( file_size )
+	{
+		Mem::PushMemProfile((char*)fullname);
+		if (useBottomUpHeap)
+		{
+			pFile = new (Mem::Manager::sHandle().BottomUpHeap()) uint8[file_size];
+		}
+		else
+		{
+			pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size];
+		}	
+		Mem::PopMemProfile();
+		bool fileLoaded= LoadFileQuicklyPlease( fullname, pFile );
+		if ( !fileLoaded )
+		{
+			printf( "pre file %s failed to load quickly.\n", fullname );
+			Dbg_MsgAssert( 0,( "Fire Matt - pre file didn't load quickly." ));
+		}
+	}
+	else
+	{
+			void *fp = File::Open(fullname, "rb");
+			if (!fp)
+			{
+			// always run the code below if CD build
+				if (dont_assert || Config::CD()) 
+				{
+					printf("couldn't open %s\n", fullname);
+					return;
+				}
+				Dbg_MsgAssert(0,( "couldn't open %s\n", fullname));
+			}
+
+
+#ifdef __PRE_ARAM__
+			File::Read(&file_size, 4, 1, fp);
+			Dbg_MsgAssert(file_size > 0,( "%s has incorrect file size\n", fullname));
+			if (Config::CD())
+			{
+				if (file_size <= 0) printf("%s has incorrect file size\n", fullname);
+			}	
+
+			// Stream the file into ARAM.
+			#define PRE_BUFFER_SIZE (16 * 1024)
+			char p_buffer[PRE_BUFFER_SIZE];
+
+			uint32 p_aram;
+			if (useBottomUpHeap)
+			{
+				p_aram = NsARAM::alloc( file_size, NsARAM::BOTTOMUP );
+			}
+			else
+			{
+				p_aram = NsARAM::alloc( file_size, NsARAM::TOPDOWN );
+			}	
+			if ( p_aram )
+			{
+				int adjust = 4;
+				uint32 toread = file_size;
+				uint32 current_aram_offset = p_aram;
+				while ( toread )
+				{
+					uint32 thistime = PRE_BUFFER_SIZE;
+					if ( toread < PRE_BUFFER_SIZE ) thistime = toread;
+
+					File::Read(&p_buffer[adjust], 1, thistime - adjust, fp);
+					DCFlushRange ( p_buffer, thistime );
+					NsDMA::toARAM( current_aram_offset, p_buffer, thistime );
+					toread -= thistime;
+					current_aram_offset += thistime;
+					adjust = 0;
+				}
+				File::Close(fp);
+				pFile = (uint8 *)p_aram;
+			}
+#else
+			file_size = File::GetFileSize(fp);
+		// Now allocates the .PRE file from the top of the heap, to avoid fragmentation.
+			Mem::PushMemProfile((char*)fullname);
+			if (useBottomUpHeap)
+			{
+				pFile = new (Mem::Manager::sHandle().BottomUpHeap()) uint8[file_size];
+			}
+			else
+			{
+				pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size];
+			}	
+			Mem::PopMemProfile();
+		//uint8 *pFile = new uint8[file_size];
+			File::Read(pFile, 1, file_size, fp);
+
+			int read_file_size = *((int *) pFile);
+			Dbg_MsgAssert(file_size == read_file_size,( "%s has incorrect file size: %d vs. expected %d\n", fullname, file_size, read_file_size));
+			if (Config::CD())
+			{
+				if (file_size != read_file_size) printf("%s has incorrect file size\n", fullname);
+			}
+
+			File::Close(fp);
+#endif		// __PRE_ARAM__
+		}
+
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+	printf("load time for file %s size %d is %d ms\n", pFilename, file_size, (int) Tmr::ElapsedTime(basetime));
+#endif
+
+	// the PRE file object winds up at the top of the heap, too. This is fine because
+	// it will be unloaded at the same time as the big file buffer
+	if (useBottomUpHeap)
+	{
+		if (!mp_table->PutItem(pFilename, new (Mem::Manager::sHandle().BottomUpHeap()) PreFile(pFile,useBottomUpHeap)))
+			Dbg_MsgAssert(0,( "PRE %s loaded twice", pFilename));
+	}
+	else
+	{
+		if (!mp_table->PutItem(pFilename, new (Mem::Manager::sHandle().TopDownHeap()) PreFile(pFile)))
+			Dbg_MsgAssert(0,( "PRE %s loaded twice", pFilename));
+	}		
+}
+
+// Finishes the loading sequence
+void   	PreMgr::postLoadPre(CAsyncFileHandle *p_file_handle, uint8 *pData, int size)
+{
+	// Find entry in pending list
+	for (int i = 0; i < m_num_pending_pre_files; i++)
+	{
+		if (m_pending_pre_files[i].mp_file_handle == p_file_handle)
+		{
+			// the PRE file object winds up at the top of the heap, too. This is fine because
+			// it will be unloaded at the same time as the big file buffer
+			if (!mp_table->PutItem(m_pending_pre_files[i].m_file_name, new (Mem::Manager::sHandle().TopDownHeap()) PreFile(pData)))
+			{
+				Dbg_MsgAssert(0,( "PRE %s loaded twice", m_pending_pre_files[i].m_file_name));
+			}
+
+			// Copy last one to this position
+			m_pending_pre_files[i] = m_pending_pre_files[--m_num_pending_pre_files];
+			return;
+		}
+	}
+
+	Dbg_MsgAssert(0, ("PreMgr::postLoadPre(): Can't find entry in pending Pre file list"));
+}
+
+// Handles all the async callbacks.  Makes sure we only do something on the appropriate callback
+void	PreMgr::async_callback(CAsyncFileHandle *p_file_handle, EAsyncFunctionType function,
+							   int result, unsigned int arg0, unsigned int arg1)
+{
+	if (function == File::FUNC_READ)
+	{
+		PreMgr *p_mgr = (PreMgr *) arg0;
+
+		p_mgr->postLoadPre(p_file_handle, (uint8 *) arg1, result);
+	}
+}
+
+// Returns point in string where it will fit in compact space
+char *	PreMgr::getCompactFileName(char *pName)
+{
+	int length = strlen(pName);
+
+	if (length < MAX_COMPACT_FILE_NAME)
+	{
+		return pName;
+	}
+	else
+	{
+		int offset = length - (MAX_COMPACT_FILE_NAME - 1);
+
+		return pName + offset;
+		//return (char *) ((int) pName + offset);
+	}
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+
+DefineSingletonClass(PreMgr, "PRE Manager");
+
+
+
+bool PreMgr::InPre(const char *pFilename)
+{
+	
+
+	return mp_table->GetItem( pFilename );
+}
+
+
+
+void PreMgr::LoadPre(const char *pFilename, bool async, bool dont_assert, bool useBottomUpHeap)
+{
+	// GJ:  This function is a wrapper around loadPRE(), to
+	// make sure that all allocations go on the top-down heap
+	
+	// K: Unless they want to use the bottom up heap :)
+	// Needed so that the anims pre can be put on the bottom up, so that the decompressed anims
+	// can be put on the top-down, then the pre removed without leaving a hole.
+	if (useBottomUpHeap)
+	{
+		//printf("Loading %s to bottom up heap\n",pFilename);
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+	}
+	else
+	{
+		//printf("Loading %s to top down heap\n",pFilename);
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	}	
+	loadPre(pFilename, async, dont_assert, useBottomUpHeap);
+	Mem::Manager::sHandle().PopContext();	
+}
+
+
+// This function exisits for historical reasons
+void PreMgr::LoadPrePermanently(const char *pFilename, bool async, bool dont_assert)
+{
+
+	// Load the pre file...
+	// This will go on the top-down heap by default
+	LoadPre(pFilename, async, dont_assert);
+
+}
+
+
+
+void PreMgr::UnloadPre(const char *pFilename, bool dont_assert)
+{
+#	ifdef __PLAT_NGPS__
+//	scePrintf("Unloading PRE file %s\n", pFilename);
+#	endif
+	//printf("Unloading PRE file %s\n", pFilename);
+	
+	if (m_blockPreLoading)
+		return;
+	
+	PreFile *pThePre = mp_table->GetItem(pFilename);
+	if (!pThePre)
+	{
+		if (dont_assert) return;
+#	ifndef __PLAT_NGC__
+		Dbg_MsgAssert(0,( "PRE file %s not in PRE manager", pFilename));
+#	endif
+	}
+
+	mp_table->FlushItem(pFilename);
+	delete pThePre;
+}
+
+bool PreMgr::IsLoadPreFinished(const char *pFilename)
+{
+	// If it's in the pending list, it isn't done loading
+	for (int i = 0; i < m_num_pending_pre_files; i++)
+	{
+		if (strcmp(m_pending_pre_files[i].m_file_name, pFilename) == 0)
+		{
+			return false;
+		}
+	}
+
+	Dbg_MsgAssert(InPre(pFilename), ("IsLoadPreFinished(): Can't find Pre file"));
+
+	return true;
+}
+
+bool PreMgr::AllLoadPreFinished()
+{
+	return m_num_pending_pre_files == 0;
+}
+
+void PreMgr::WaitLoadPre(const char *pFilename)
+{
+	while (!IsLoadPreFinished(pFilename))
+	{
+		// We got to call this to allow callbacks to come through
+		CAsyncFileLoader::sWaitForIOEvent(false);
+	}
+}
+
+void PreMgr::WaitAllLoadPre()
+{
+	while (!AllLoadPreFinished())
+	{
+		// We got to call this to allow callbacks to come through
+		CAsyncFileLoader::sWaitForIOEvent(false);
+	}
+}
+
+bool PreMgr::sPreEnabled()
+{
+	return sp_mgr != NULL;
+}
+
+bool PreMgr::sPreExecuteSuccess()
+{
+	return s_lastExecuteSuccess; 
+}
+
+bool PreMgr::pre_fexist(const char *name)
+{
+	
+	Dbg_MsgAssert(name,( "requesting file NULL"));	
+	
+	if (sp_mgr->fileExists(name)) 
+	{
+#		if DEBUG_PRE
+		printf("PRE: file %s exists\n", name);
+#		endif
+		s_lastExecuteSuccess = true;
+		return true;
+	}
+//	if ( Pcm::UsingCD( ) )
+//	{
+//		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+//		return false;
+//	}
+
+	return s_lastExecuteSuccess = false;
+}
+
+
+
+PreFile::FileHandle *PreMgr::pre_fopen(const char *name, const char *access, bool async)
+{
+	Dbg_MsgAssert(name,( "trying to open file NULL"));	
+
+	PreFile::FileHandle *pHandle = sp_mgr->getContainedFile(name);
+	if (pHandle)
+	{
+		sp_mgr->mp_activePre->Open(async);
+
+		// if we are going to write the file, we want to use the regular file system
+		const char *pChar = access;
+		bool am_writing = false;
+		while(*pChar)
+		{
+			if (*pChar != 'r' && *pChar != 'b')
+				am_writing = true;
+			pChar++;
+		}
+		
+		if (am_writing)
+		{
+#			ifdef __PLAT_NGPS__
+//			scePrintf("    writing file %s\n", name);
+#			endif
+
+			// am writing, so we don't need file open in PRE system
+			sp_mgr->mp_activePre->Close(async);
+		}
+		else
+		{
+			// we're reading the file from the PRE system
+#			if DEBUG_PRE
+			printf("PRE: opened file %s, handle is 0x%x\n", name, (int) pHandle);
+#			endif
+			sp_mgr->mp_activePre->Reset();
+			s_lastExecuteSuccess = true;
+			return pHandle;
+		}
+	}
+
+//	// if we get here, we are using the regular file system
+//	if ( Pcm::UsingCD( ) )
+//	{
+//		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+//		return NULL;
+//	}
+
+	s_lastExecuteSuccess = false;
+	return NULL;
+
+	//return pHandle;
+}
+
+
+
+int PreMgr::pre_fclose(PreFile::FileHandle *fptr, bool async)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling fclose with invalid file ptr"));	
+	
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+#if DEBUG_PRE
+		printf("PRE: closed file, handle 0x%x\n", (int) fptr);
+#endif
+		sp_mgr->mp_activePre->Close(async);
+		s_lastExecuteSuccess = true;
+		return 0;
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+size_t PreMgr::pre_fread(void *addr, size_t size, size_t count, PreFile::FileHandle *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling fread with invalid file ptr"));		
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		// read from a simulated file stream in PRE file
+		Dbg_AssertPtr(sp_mgr->mp_activePre);
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Read(addr, size * count);
+	}
+//	if ( Pcm::UsingCD( ) )
+//	{
+//		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+//		return 0;
+//	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+size_t  PreMgr::pre_fwrite(const void *addr, size_t size, size_t count, PreFile::FileHandle *fptr)
+{
+		
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't write to a PRE file"));
+	
+//	if ( Pcm::UsingCD( ) )
+//	{
+//		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+//		return 0;
+//	}
+
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+char *PreMgr::pre_fgets(char *buffer, int maxLen, PreFile::FileHandle *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return NULL;
+}
+
+
+
+int PreMgr::pre_fputs(const char *buffer, PreFile::FileHandle *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_feof(PreFile::FileHandle *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling feof with invalid file ptr"));		
+
+	uint8 *pData = 	sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		Dbg_AssertPtr(sp_mgr->mp_activePre);
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Eof();
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_fseek(PreFile::FileHandle *fptr, long offset, int origin)
+{
+		
+
+	uint8 *pData = 	sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Seek(offset, origin);
+	}
+
+	Dbg_MsgAssert(!pData,( "seek not supported for PRE file"));
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_fflush(PreFile::FileHandle *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "flush not supported for PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_ftell(PreFile::FileHandle *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "tell supported for PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+int	PreMgr::pre_get_file_size(PreFile::FileHandle *fptr)
+{
+	uint8 *pData = 	sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->GetFileSize();
+	}
+
+	Dbg_MsgAssert(!pData,( "get_file_size not supported for PRE file"));
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+int PreMgr::pre_get_file_position(PreFile::FileHandle *fptr)
+{
+	uint8 *pData = 	sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->GetFilePosition();
+	}
+
+	Dbg_MsgAssert(!pData,( "get_file_position not supported for PRE file"));
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+// @script | InPreFile | 
+// @uparm "string" | filename
+bool ScriptInPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	PreMgr* pre_mgr = PreMgr::Instance();
+	return pre_mgr->InPre(pFilename);
+}
+
+
+
+// @script | LoadPreFile | 
+// @uparm "string" | filename
+// @flag async | Load Asynchronously
+// @flag dont_assert | 
+// @flag use_bottom_up_heap | 
+bool ScriptLoadPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	PreMgr* pre_mgr = PreMgr::Instance();
+	pre_mgr->LoadPre(pFilename, 
+					 pParams->ContainsFlag(CRCD(0x90e07c79,"async")),
+					pParams->ContainsFlag(CRCD(0x3d92465e,"dont_assert")),
+					pParams->ContainsFlag(CRCD(0xa44c770f,"use_bottom_up_heap")));
+	return true;
+}
+
+
+
+// @script | UnloadPreFile | 
+// @flag BoardsPre | 
+// @flag dont_assert | 
+// @uparm "string" | filename
+bool ScriptUnloadPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	PreMgr* pre_mgr = PreMgr::Instance();
+	
+	if (pParams->ContainsFlag("BoardsPre"))
+	{
+		
+//		Mdl::Skate * pSkate = Mdl::Skate::Instance();
+//		pSkate->UnloadBoardPreIfPresent(pParams->ContainsFlag("dont_assert"));
+		printf ("STUBBED:  Unload BoardsPre, in ScriptUnloadPreFile\n");
+		return true;
+	}
+	
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	pre_mgr->UnloadPre(pFilename, pParams->ContainsFlag("dont_assert"));
+	return true;
+}
+
+// @script | IsLoadPreFinished | Returns true if Pre file has finished loading
+// @uparm "string" | filename
+bool ScriptIsLoadPreFinished(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	PreMgr* pre_mgr = PreMgr::Instance();
+
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	return pre_mgr->IsLoadPreFinished(pFilename);
+}
+
+// @script | AllLoadPreFinished | Returns true if all LoadPre commands have completed
+bool ScriptAllLoadPreFinished(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	PreMgr* pre_mgr = PreMgr::Instance();
+
+	return pre_mgr->AllLoadPreFinished();
+}
+
+// @script | WaitLoadPre | Waits for Pre file to finished loading
+// @uparm "string" | filename
+bool ScriptWaitLoadPre(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	PreMgr* pre_mgr = PreMgr::Instance();
+
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	pre_mgr->WaitLoadPre(pFilename);
+	return true;
+}
+
+// @script | WaitAllLoadPre | Waits for all Pre files to finished loading
+bool ScriptWaitAllLoadPre(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	PreMgr* pre_mgr = PreMgr::Instance();
+
+	pre_mgr->WaitAllLoadPre();
+	return true;
+}
+
+// if a file is in a pre, then:
+// allocate memory for the file
+// if file is uncompressed
+//   copy it down
+// else
+//   decompress
+
+void * PreMgr::LoadFile(const char *pName, int *p_size, void *p_dest)
+{
+// NOTE: THIS IS JUST CUT AND PASTE FROM Pre::fileExists
+	Dbg_AssertPtr(pName);
+	// replace all '/' with '\'
+	char cleaned_name[128];
+	const char *pCharIn = pName;
+	char *pCharOut = cleaned_name;
+	while (1)
+	{
+		*pCharOut = *pCharIn;
+		if (*pCharIn == '\0') break;
+		if (*pCharOut == '/') *pCharOut = '\\';
+		pCharIn++;
+		pCharOut++;		
+	}
+
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		
+		void *p_data = pPre->LoadContainedFile(cleaned_name,p_size, p_dest);
+		if (p_data)
+		{
+			return p_data;
+		}
+		pPre = mp_table->IterateNext();
+	}
+	return NULL;
+
+}
+
+
+
+} // namespace File
+
+
+
+
diff --git a/Code/Sys/File/PRE.h b/Code/Sys/File/PRE.h
new file mode 100644
index 0000000..79e07a2
--- /dev/null
+++ b/Code/Sys/File/PRE.h
@@ -0,0 +1,239 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2001 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:																**
+**																			**
+**	Module:									 								**
+**																			**
+**	File name:																**
+**																			**
+**	Created by:		rjm, 1/23/2001											**
+**																			**
+**	Description:						 									**
+**																			**
+*****************************************************************************/
+
+#ifndef __CORE_FILE_PRE_H
+#define __CORE_FILE_PRE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+namespace Lst
+{
+	template class StringHashTable;
+}
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}	
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace File
+{
+
+						
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+bool ScriptInPreFile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptLoadPreFile(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnloadPreFile(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptIsLoadPreFinished(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptAllLoadPreFinished(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptWaitLoadPre(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptWaitAllLoadPre(Script::CStruct *pParams, Script::CScript *pScript);
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class CAsyncFileHandle;
+class PreMgr;
+class  PreFile  : public Spt::Class
+{
+	
+
+public:
+
+	// The actual PRE file (it is public so that FileHandle can be made public).
+	struct _File
+	{
+		int				compressedDataSize;
+		uint8 *			pCompressedData;
+		uint8 *			pData;
+		int				m_position;
+		int				m_filesize;
+	};
+
+	// Just typedef a file handle type since the file itself contains all the info
+	typedef _File FileHandle;
+
+	PreFile(uint8 *p_file_buffer, bool useBottomUpHeap=false);
+	~PreFile();
+	
+	
+	
+	bool 		FileExists(const char *pName);
+	void*		LoadContainedFile(const char *pName,int *p_size, void *p_dest = NULL);
+	FileHandle*	GetContainedFile(const char *pName);
+	uint8*		GetContainedFileByHandle(FileHandle *pHandle);
+
+	void Reset();
+	uint32 Read(void *addr, uint32 count);
+	int Eof();
+	void Open(bool async);
+	void Close(bool async);
+	int Seek(long offset, int origin);
+	int TellActive( void )				{ if( mp_activeFile ){ return mp_activeFile->m_position; }else{ return 0; }}
+	int GetFileSize( void )				{ if( mp_activeFile ){ return mp_activeFile->m_filesize; }else{ return 0; }}
+	int GetFilePosition( void )			{ if( mp_activeFile ){ return mp_activeFile->m_position; }else{ return 0; }}
+
+private:
+
+	static void						s_delete_file(_File *pFile, void *pData);
+	
+	uint8 *							mp_buffer;
+	int								m_numEntries;
+	
+	// maps filenames to pointers
+	Lst::StringHashTable<_File> *	mp_table;
+
+	_File *							mp_activeFile;
+
+	int								m_numOpenAsyncFiles;
+	
+	bool							m_use_bottom_up_heap;
+};
+
+
+
+
+class  PreMgr  : public Spt::Class
+{
+	
+	DeclareSingletonClass(PreMgr);
+
+public:
+	bool InPre(const char *pFilename);
+	void LoadPre(const char *pFilename, bool async, bool dont_assert = false, bool useBottomUpHeap=false);
+	void LoadPrePermanently(const char *pFilename, bool async, bool dont_assert = false);
+	void UnloadPre(const char *pFilename, bool dont_assert = false);
+	void * LoadFile(const char *pName, int *p_size, void *p_dest = NULL);
+
+	// Async check functions
+	bool IsLoadPreFinished(const char *pFilename);
+	bool AllLoadPreFinished();
+	void WaitLoadPre(const char *pFilename);
+	void WaitAllLoadPre();
+
+	static bool sPreEnabled();
+	static bool sPreExecuteSuccess();
+	
+	static bool					pre_fexist(const char *name);
+	static PreFile::FileHandle *pre_fopen(const char *name, const char *access, bool async = false);
+	static int					pre_fclose(PreFile::FileHandle *fptr, bool async = false);
+	static size_t				pre_fread(void *addr, size_t size, size_t count, PreFile::FileHandle *fptr);
+	static size_t				pre_fwrite(const void *addr, size_t size, size_t count, PreFile::FileHandle *fptr);
+	static char *				pre_fgets(char *buffer, int maxLen, PreFile::FileHandle *fptr);
+	static int					pre_fputs(const char *buffer, PreFile::FileHandle *fptr);
+	static int					pre_feof(PreFile::FileHandle *fptr);
+	static int					pre_fseek(PreFile::FileHandle *fptr, long offset, int origin);
+	static int					pre_fflush(PreFile::FileHandle *fptr);
+	static int					pre_ftell(PreFile::FileHandle *fptr);
+	static int					pre_get_file_size(PreFile::FileHandle *fptr);
+	static int					pre_get_file_position(PreFile::FileHandle *fptr);
+
+private:
+	// Constants
+	enum
+	{
+		MAX_COMPACT_FILE_NAME = 64,				// Only store the right-most characters of the filename to save space
+		MAX_NUM_ASYNC_LOADS = 8,
+	};
+
+	// Holds data for pending async loads
+	struct SPendingAsync
+	{
+		CAsyncFileHandle *			mp_file_handle;
+		char						m_file_name[MAX_COMPACT_FILE_NAME];
+	};
+
+	PreMgr();
+	~PreMgr();
+
+	void				 loadPre(const char *pFilename, bool async, bool dont_assert = false, bool useBottomUpHeap=false);
+    void    			 postLoadPre(CAsyncFileHandle *p_file_handle, uint8 *pData, int size);
+	bool				 fileExists(const char *pName);
+	PreFile::FileHandle *getContainedFile(const char *pName);
+	uint8 *				 getContainedFileByHandle(PreFile::FileHandle *pHandle);
+
+	static char *		 getCompactFileName(char *pName);		// Returns point in string where it will fit in compact space
+
+	static void			 async_callback(CAsyncFileHandle *p_file_handle, EAsyncFunctionType function,
+										int result, unsigned int arg0, unsigned int arg1);
+
+	PreFile							*mp_activePre;
+	PreFile::FileHandle *	 		mp_activeHandle;
+	uint8 *							mp_activeData;
+	// handle of current file being accessed from regular file system, for quick check
+	void *							mp_activeNonPreHandle;
+
+	static bool						s_lastExecuteSuccess;
+	static PreMgr *					sp_mgr;
+	
+	Lst::StringHashTable *	mp_table;
+
+	bool							m_blockPreLoading;
+
+	// Async status
+	SPendingAsync					m_pending_pre_files[MAX_NUM_ASYNC_LOADS];
+	int								m_num_pending_pre_files;
+};
+
+
+} // namespace File
+
+#endif	// __CORE_FILE_PRE_H
+
+
diff --git a/Code/Sys/File/XBox/hed.cpp b/Code/Sys/File/XBox/hed.cpp
new file mode 100644
index 0000000..d295490
--- /dev/null
+++ b/Code/Sys/File/XBox/hed.cpp
@@ -0,0 +1,133 @@
+/*	Header file functionality...
+	.Hed files that describe the contents of .Wad files
+	Written by Ken, stolen by Matt*/
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include  
+
+#include 
+#include 
+
+namespace File
+{
+
+// Searches the hed file for the filename. If not found, returns NULL.
+SHed *FindFileInHed(const char *pFilename, SHed *pHed)
+{
+	Dbg_MsgAssert(pFilename,("NULL pFilename"));
+	Dbg_MsgAssert(pHed,("NULL pHed"));
+	
+	#define FILENAME_BUF_SIZE 200
+	char pBuf[FILENAME_BUF_SIZE];
+	Dbg_MsgAssert(strlen(pFilename)Offset!=0xffffffff)
+	{						  
+		if (stricmp(pBuf,pHd->pFilename)==0)
+		{
+			return pHd;
+		}
+		
+		// Get the total space occupied by the file name, which is
+		// the length +1 (to include the terminator) then rounded up
+		// to a multiple of 4.
+		int Len=strlen(pHd->pFilename)+1;
+		Len=(Len+3)&~3;
+		pHd=(SHed*)(pHd->pFilename+Len);
+	}
+	return NULL;	
+}
+
+// Searches the hed file for the filename with the same checksum. If not found, returns NULL.
+SHed *FindFileInHedUsingChecksum( uint32 checksum, SHed *pHed, bool stripPath )
+{
+	Dbg_MsgAssert(pHed,("NULL pHed"));
+	
+	// Search the hed until the file is found, or not.
+	SHed *pHd=pHed;
+	const char *pFilename;
+	while (pHd->Offset!=0xffffffff)
+	{
+		pFilename = pHd->pFilename;
+		if ( stripPath )
+		{
+			int i;
+			for ( i = strlen( pFilename ) - 2; i >= 0; i-- )
+			{
+				if ( pFilename[ i ] == '\\' )
+				{
+					pFilename = &pFilename[ i + 1 ];
+					i = -1;
+				}
+			}
+		}
+		if ( Script::GenerateCRC( pFilename ) == checksum )
+		{
+			return ( pHd );
+		}
+		// Get the total space occupied by the file name, which is
+		// the length +1 (to include the terminator) then rounded up
+		// to a multiple of 4.
+		int Len=strlen(pHd->pFilename)+1;
+		Len=(Len+3)&~3;
+		pHd=(SHed*)(pHd->pFilename+Len);
+	}
+	return NULL;	
+}
+
+
+
+SHed *LoadHed( const char *filename )
+{
+	char tempFileName[255];
+	sprintf( tempFileName, "%s.HED", filename );
+
+	void *fp = File::Open( tempFileName, "rb" );
+	if ( !fp )
+	{
+		Dbg_MsgAssert( 0,( "Couldn't find hed file %s", tempFileName ));
+		return NULL;
+	}
+	int fileSize = File::GetFileSize( fp );
+	File::Seek( fp, 0, SEEK_SET );
+	
+	SHed *pHed = (SHed*)Mem::Malloc(( fileSize + 2047 ) & ~2047 );
+	Dbg_MsgAssert( pHed,( "Failed to allocate memory for hed %s", tempFileName ));
+	
+	File::Read( pHed, 1, fileSize, fp );
+	File::Close( fp );
+	return pHed;
+}
+
+} // namespace File
diff --git a/Code/Sys/File/XBox/hed.h b/Code/Sys/File/XBox/hed.h
new file mode 100644
index 0000000..03b5435
--- /dev/null
+++ b/Code/Sys/File/XBox/hed.h
@@ -0,0 +1,30 @@
+/*	Header file functionality...
+	.Hed files that describe the contents of .Wad files
+	Written by Ken, stolen by Matt*/
+#ifndef __HED_H__
+#define __HED_H__
+
+namespace File
+{
+
+struct SHed
+{
+	// A SECTOR_SIZE aligned offset of a file within skate3.wad
+	uint32 Offset;
+	
+	// The file size, which is the raw file size, not rounded up
+	// to a multiple of SECTOR_SIZE
+	uint32 FileSize;
+	
+	// The filename, which is actually bigger than one byte, tee hee.
+	const char pFilename[1];
+};
+
+
+SHed *FindFileInHed(const char *pFilename, SHed *pHed );
+SHed *FindFileInHedUsingChecksum( uint32 checksum, SHed *pHed, bool stripPath );
+SHed *LoadHed( const char *filename );
+
+} // namespace File
+
+#endif
diff --git a/Code/Sys/File/XBox/p_AsyncFilesys.cpp b/Code/Sys/File/XBox/p_AsyncFilesys.cpp
new file mode 100644
index 0000000..431a73e
--- /dev/null
+++ b/Code/Sys/File/XBox/p_AsyncFilesys.cpp
@@ -0,0 +1,662 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// p_AsyncFilesys.cpp		GRJ 11 Oct 2002
+//
+// Asynchronous file system
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+
+#include 
+
+namespace File
+{
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static uint32 RoundToNearestSectorSize( uint32 size )
+{
+	// Round up to the nearest sector size of a DVD, which is 2048 bytes.
+	return ( size + 0x7FF ) & ~0x7FF;
+}
+
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxAsyncFileHandle::CXboxAsyncFileHandle( void )
+{
+	m_num_open_requests		= 0;
+	mp_non_aligned_buffer	= NULL;
+	mp_temp_aligned_buffer	= NULL;
+	mh_file					= INVALID_HANDLE_VALUE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CXboxAsyncFileHandle::~CXboxAsyncFileHandle()
+{
+}
+	
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxAsyncFileHandle::plat_open( const char *filename )
+{
+	m_position = 0;
+
+	// The following code duplicates that in prefopen(). At some point the filenme modification code
+	// should probably be abstracted out.
+
+	// Used for prepending the root data directory on filesystem calls.
+	char		nameConversionBuffer[256] = "d:\\data\\";
+	int			index = 8;
+	const char*	p_skip;
+
+	if(( filename[0] == 'c' ) && ( filename[1] == ':' ))
+	{
+		// Filename is of the form c:\skate4\data\foo...
+		p_skip = filename + 15;
+	}
+	else
+	{
+		// Filename is of the form foo...
+		p_skip = filename;
+	}
+
+	while( nameConversionBuffer[index] = *p_skip )
+	{
+		// Switch forward slash directory separators to the supported backslash.
+		if( nameConversionBuffer[index] == '/' )
+		{
+			nameConversionBuffer[index] = '\\';
+		}
+		++index;
+		++p_skip;
+	}
+	nameConversionBuffer[index] = 0;
+
+	// If this is a .tex file, switch to a .txx file.
+	if((( nameConversionBuffer[index - 1] ) == 'x' ) &&
+	   (( nameConversionBuffer[index - 2] ) == 'e' ) &&
+	   (( nameConversionBuffer[index - 3] ) == 't' ))
+	{
+		nameConversionBuffer[index - 2] = 'x';
+	}
+
+	// If this is a .pre file, switch to a .prx file.
+	if((( nameConversionBuffer[index - 1] ) == 'e' ) &&
+	   (( nameConversionBuffer[index - 2] ) == 'r' ) &&
+	   (( nameConversionBuffer[index - 3] ) == 'p' ))
+	{
+		nameConversionBuffer[index - 1] = 'x';
+	}
+
+	inc_busy_count();
+
+	mh_file = CreateFile(	nameConversionBuffer,							// File name
+							GENERIC_READ,									// Access mode
+							FILE_SHARE_READ,								// Share mode
+							NULL,											// SD
+							OPEN_EXISTING,									// How to create
+							FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,	// File attributes
+							NULL );											// Handle to template file
+
+	if( mh_file == INVALID_HANDLE_VALUE )
+	{
+        return false;
+	}
+
+	// Immediately obtain the file size.
+	m_file_size = ::GetFileSize( mh_file, NULL );
+	Dbg_Assert( m_file_size != -1 );
+
+	// Round to nearest sector size, since this will be required for async reads anyway.
+	m_file_size = RoundToNearestSectorSize( m_file_size );
+
+	io_callback( m_current_function, (int)mh_file, m_file_size );
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxAsyncFileHandle::io_callback( EAsyncFunctionType function, int result, uint32 data )
+{
+	// Do any special function processing
+	switch (function)
+	{
+		case FUNC_OPEN:
+		{
+			if( result < 0 )
+			{
+//				Dbg_MsgAssert( 0, ( "Error: Async open returned %d", result ));
+			}
+			else
+			{
+				m_file_size = data;
+			}
+//			ShowAsyncInfo("io_callback: Open returned handle index %d with filesize %d\n", result, data);
+			break;
+		}
+
+	case FUNC_SEEK:
+		if (result < 0)
+		{
+			Dbg_MsgAssert(0, ("Error: Async seek returned %d", result));
+		} else {
+			m_position = result;
+		}
+		break;
+
+	case FUNC_LOAD:
+	case FUNC_READ:
+		if (mp_temp_aligned_buffer && (result > 0))
+		{
+			memcpy(mp_non_aligned_buffer, mp_temp_aligned_buffer, result);
+		}
+		// don't break, continue on below
+
+	case FUNC_WRITE:
+		if (result < 0)
+		{
+			Dbg_MsgAssert(0, ("Error: Async IO returned %d", result));
+		} else {
+			m_position += result;
+			if (mp_temp_aligned_buffer)
+			{
+				delete mp_temp_aligned_buffer;
+				mp_non_aligned_buffer = NULL;
+				mp_temp_aligned_buffer = NULL;
+			}
+		}
+		break;
+
+	case FUNC_IDLE:
+		// Don't want m_busy to change or user-defined callback to be executed
+		return;
+
+	default:
+		break;
+	}
+
+	// Check to make sure request buffer will be clear
+	if (get_busy_count() > 0)
+	{
+		//Dbg_MsgAssert(m_num_open_requests == 1, ("Sill other open requests"));
+	}
+
+	// Now handle the above p-line stuff
+	CAsyncFileHandle::io_callback(function, result, data);
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxAsyncFileHandle::add_request_id( int request_id, EAsyncFunctionType function )
+{
+	if (m_num_open_requests >= NUM_REQUESTS)
+	{
+		Dbg_MsgAssert(0, ("Too many open requests for file handle"));
+		return false;
+	}
+
+	m_open_requests[m_num_open_requests].m_request_id = request_id;
+	m_open_requests[m_num_open_requests++].m_function = function;
+
+	//scePrintf("Adding request %d to entry #%d for %x with busy %d\n", request_id, (m_num_open_requests-1), this, get_busy_count());
+
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+EAsyncFunctionType	CXboxAsyncFileHandle::get_request_function( int request_id ) const
+{
+	// Find request id in list
+	for (int i = 0; i < m_num_open_requests; i++)
+	{
+		if (m_open_requests[i].m_request_id == request_id)
+		{
+			return m_open_requests[i].m_function;
+		}
+	}
+
+	// Request not found
+	Dbg_MsgAssert(0, ("Can't find request %d on %x", request_id, this));
+	return FUNC_IDLE;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxAsyncFileHandle::clear_request_id( int request_id )
+{
+	if (m_num_open_requests <= 0)
+	{
+		Dbg_MsgAssert(0, ("Can't clear request: Open request list empty"));
+		return false;
+	}
+
+	// Find request id in list
+	for (int i = 0; i < m_num_open_requests; i++)
+	{
+		if (m_open_requests[i].m_request_id == request_id)
+		{
+			// Just move end of list to here
+			m_open_requests[i] = m_open_requests[--m_num_open_requests];
+			//scePrintf("Clearing request %d from entry #%d\n", request_id, i);
+			return true;
+		}
+	}
+
+	// Request not found
+	Dbg_MsgAssert(0, ("Can't clear request: Not found"));
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxAsyncFileHandle::plat_init( void )
+{
+	m_num_open_requests		= 0;
+	mp_non_aligned_buffer	= NULL;
+	mp_temp_aligned_buffer	= NULL;
+
+	// Set up the overlapped structure.
+	m_overlapped.Offset		= 0;
+	m_overlapped.OffsetHigh	= 0;
+	m_overlapped.hEvent		= NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+volatile bool CXboxAsyncFileHandle::plat_is_done( void )
+{
+	bool is_done = false;
+
+	if( m_last_result == ERROR_IO_PENDING )
+	{
+		is_done = HasOverlappedIoCompleted( &m_overlapped );
+		if( is_done )
+		{
+			// I/O operation is complete, so GetOverlappedResult() should retuen true.
+			uint32 bt;
+			bool result	= GetOverlappedResult(	mh_file,		// handle to file, pipe, or device
+												&m_overlapped,	// overlapped structure
+												&bt,			// bytes transferred
+												false );		// wait option
+
+			Dbg_Assert( result );
+
+			m_last_result = ERROR_SUCCESS;
+
+			// Must call this when we are done.
+			io_callback( m_current_function, bt, 0 );	
+		}
+	}
+	return is_done;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+volatile bool CXboxAsyncFileHandle::plat_is_busy( void )
+{
+	return ( m_last_result == ERROR_IO_PENDING );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxAsyncFileHandle::plat_is_eof( void ) const
+{
+	return ( m_last_result == ERROR_HANDLE_EOF );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxAsyncFileHandle::plat_set_priority( int priority )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxAsyncFileHandle::plat_set_stream( bool stream )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxAsyncFileHandle::plat_set_destination( EAsyncMemoryType destination )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxAsyncFileHandle::plat_set_buffer_size( size_t buffer_size )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CXboxAsyncFileHandle::plat_set_blocking( bool block )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+size_t CXboxAsyncFileHandle::plat_load( void *p_buffer )
+{
+	// This function will just get the file size and do one big read.
+	// Set up the overlapped structure.
+	m_overlapped.Offset		= 0;
+	m_overlapped.OffsetHigh	= 0;
+	m_overlapped.hEvent		= NULL;
+
+	// And do the read.
+	return plat_read( p_buffer, 1, GetFileSize());
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+size_t CXboxAsyncFileHandle::plat_read( void *p_buffer, size_t size, size_t count )
+{
+	uint32 total_bytes = RoundToNearestSectorSize( size * count );
+
+	inc_busy_count();
+
+	bool result =  ReadFile(	mh_file,							// Handle to file
+								p_buffer,							// Data buffer
+								total_bytes,						// Number of bytes to read
+								NULL,								// Number of bytes read
+								&m_overlapped );					// Overlapped buffer
+
+	// If there was a problem, or the async. operation's still pending... 
+	if( !result ) 
+	{ 
+		// Deal with the error code. 
+		m_last_result = GetLastError();
+		switch( m_last_result ) 
+		{ 
+			case ERROR_HANDLE_EOF: 
+			{ 
+	            // We've reached the end of the file during the call to ReadFile.
+				break;
+			} 
+ 
+			case ERROR_IO_PENDING: 
+			{
+				break;
+			}
+
+			default:
+			{
+				Dbg_Assert( 0 );
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+size_t CXboxAsyncFileHandle::plat_write( void *p_buffer, size_t size, size_t count )
+{
+	Dbg_Assert( 0 );
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+char *CXboxAsyncFileHandle::plat_get_s( char *p_buffer, int maxlen )
+{
+	Dbg_Assert( 0 );
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int CXboxAsyncFileHandle::plat_seek( long offset, int origin )
+{
+	Dbg_Assert( 0 );
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CXboxAsyncFileHandle::plat_close( void )
+{
+	if( mh_file != INVALID_HANDLE_VALUE )
+	{
+		inc_busy_count();
+
+		CloseHandle( mh_file );
+		mh_file = INVALID_HANDLE_VALUE;
+
+		io_callback( m_current_function, 0, 0 );
+		return true;
+	}
+	return false;
+}
+
+	
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CAsyncFileLoader::s_plat_init( void )
+{
+	for( int i = 0; i < MAX_FILE_HANDLES; ++i )
+	{
+		if( s_file_handles[i] )
+		{
+			s_file_handles[i]->init();
+		}
+		else
+		{
+			s_file_handles[i] = new CXboxAsyncFileHandle;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CAsyncFileLoader::s_plat_cleanup( void )
+{
+	for( int i = 0; i < MAX_FILE_HANDLES; ++i )
+	{
+		if( s_file_handles[i] )
+		{
+			delete s_file_handles[i];
+			s_file_handles[i] = NULL;
+		}
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CAsyncFileLoader::s_plat_async_supported( void )
+{
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool CAsyncFileLoader::s_plat_exist( const char *filename )
+{
+	HANDLE h_file = CreateFile( filename,							// File name
+					0,												// Access mode
+					FILE_SHARE_READ,								// Share mode
+					NULL,											// SD
+					OPEN_EXISTING,									// How to create
+					FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,	// File attributes
+					NULL );											// Handle to template file
+
+	if( h_file == INVALID_HANDLE_VALUE )
+	{
+        return false;
+	}
+
+	CloseHandle( h_file );
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+CAsyncFileHandle *CAsyncFileLoader::s_plat_open( const char *filename, int priority )
+{
+	CXboxAsyncFileHandle *p_handle = new CXboxAsyncFileHandle();
+	p_handle->plat_init();
+
+	bool opened = p_handle->plat_open( filename );
+	if( !opened )
+	{
+		delete p_handle;
+		return NULL;
+	}
+
+	return p_handle;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CAsyncFileLoader::s_plat_swap_callback_list( void )
+{
+	s_cur_callback_list_index	= s_cur_callback_list_index ^ 1;	// Swap indices...
+	s_new_io_completion			= false;							// ...and clear flag.
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void CAsyncFileLoader::s_plat_update( void )
+{
+	for( int h = 0; h < MAX_FILE_HANDLES; ++h )
+	{
+		if( s_file_handles[h] )
+		{
+			if( s_file_handles[h]->IsBusy())
+			{
+				CXboxAsyncFileHandle*	p_xbox_handle = static_cast( s_file_handles[h] );
+				p_xbox_handle->plat_is_done();
+			}
+		}
+	}
+}
+
+
+}
+
+
+
diff --git a/Code/Sys/File/XBox/p_AsyncFilesys.h b/Code/Sys/File/XBox/p_AsyncFilesys.h
new file mode 100644
index 0000000..734bd65
--- /dev/null
+++ b/Code/Sys/File/XBox/p_AsyncFilesys.h
@@ -0,0 +1,135 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			File													**
+**																			**
+**	Created:		10/11/02	-	grj										**
+**																			**
+**	File name:		core/sys/p_AsyncFilesys.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_FILE_P_ASYNC_FILESYS_H
+#define	__SYS_FILE_P_ASYNC_FILESYS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+class CXboxAsyncFileHandle : public CAsyncFileHandle
+{
+public:
+
+protected:
+
+							CXboxAsyncFileHandle();
+	virtual 				~CXboxAsyncFileHandle();
+
+	static VOID CALLBACK	sAsyncFileReadTimerAPCProc( LPVOID lpArgToCompletionRoutine, DWORD dwTimerLowValue, DWORD dwTimerHighValue );
+
+private:
+	// Constants
+	enum
+	{
+		NUM_REQUESTS = 8
+	};
+
+	// Request data
+	struct SRequest
+	{
+		int					m_request_id;
+		EAsyncFunctionType	m_function;
+	};
+
+	// Callback functions
+	virtual void		io_callback(EAsyncFunctionType function, int result, uint32 data);
+
+	// Open requests
+	SRequest		  	m_open_requests[NUM_REQUESTS];
+	volatile int		m_num_open_requests;
+
+	// Non-aligned buffer IO
+	uint8 *				mp_non_aligned_buffer;
+	uint8 *				mp_temp_aligned_buffer;
+
+	// Xbox-specific file handle.
+	HANDLE				mh_file;
+	OVERLAPPED			m_overlapped;	// OVERLAPPED structure required for async reading on Xbox
+
+	bool				add_request_id( int request_id, EAsyncFunctionType function );
+	EAsyncFunctionType	get_request_function( int request_id ) const;
+	bool				clear_request_id( int request_id );
+
+	// platform-specific calls
+	virtual void			plat_init( void );
+
+	virtual bool			plat_open( const char *filename );
+	virtual bool			plat_close( void );
+
+	virtual volatile bool	plat_is_done( void );
+	virtual volatile bool	plat_is_busy( void );
+	virtual bool			plat_is_eof( void ) const;
+
+	virtual void			plat_set_priority( int priority );
+	virtual void			plat_set_stream( bool stream );
+	virtual void			plat_set_destination( EAsyncMemoryType destination );
+	virtual void			plat_set_buffer_size( size_t buffer_size );
+	virtual void			plat_set_blocking( bool block );
+
+	virtual size_t			plat_load( void *p_buffer );
+	virtual size_t			plat_read( void *p_buffer, size_t size, size_t count );
+	virtual size_t			plat_write( void *p_buffer, size_t size, size_t count );
+	virtual char *			plat_get_s( char *p_buffer, int maxlen );
+	virtual int				plat_seek( long offset, int origin );
+
+	// Friends
+	friend CAsyncFileLoader;
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace File
+
+#endif  // __SYS_FILE_P_ASYNC_FILESYS_H
diff --git a/Code/Sys/File/XBox/p_filesys.cpp b/Code/Sys/File/XBox/p_filesys.cpp
new file mode 100644
index 0000000..fb5baac
--- /dev/null
+++ b/Code/Sys/File/XBox/p_filesys.cpp
@@ -0,0 +1,591 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			File IO (File) 											**
+**																			**
+**	File name:		p_filesys.cpp											**
+**																			**
+**	Created by:		09/25/00	-	dc										**
+**																			**
+**	Description:	XBox File System										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace File
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define READBUFFERSIZE				10240
+#define	PREPEND_START_POS			8
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+BOOL OkayToUseUtilityDrive = FALSE;
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/* prefopen														  */
+/* Same as fopen() except it will prepend the data root directory */
+/* For Xbox, path is always relative to location of .xbe image	  */
+/* and default path from that location is d:\					  */
+/* So incoming files of the form:								  */
+/* c:\skate3\data\foo...	become	d:\data\foo...				  */
+/* foo...					become	d:\data\foo...				  */
+/*                                                                */
+/******************************************************************/
+static void* prefopen( const char *filename, const char *mode )
+{
+	// Used for prepending the root data directory on filesystem calls.
+	char		nameConversionBuffer[256] = "d:\\data\\";
+	int			index = PREPEND_START_POS;
+	const char*	p_skip;
+
+	if(( filename[0] == 'c' ) && ( filename[1] == ':' ))
+	{
+		// Filename is of the form c:\skate4\data\foo...
+		p_skip = filename + 15;
+	}
+	else
+	{
+		// Filename is of the form foo...
+		p_skip = filename;
+	}
+
+	while( nameConversionBuffer[index] = *p_skip )
+	{
+		// Switch forward slash directory separators to the supported backslash.
+		if( nameConversionBuffer[index] == '/' )
+		{
+			nameConversionBuffer[index] = '\\';
+		}
+		++index;
+		++p_skip;
+	}
+	nameConversionBuffer[index] = 0;
+
+	// If this is a .tex file, switch to a .txx file.
+	if((( nameConversionBuffer[index - 1] ) == 'x' ) &&
+	   (( nameConversionBuffer[index - 2] ) == 'e' ) &&
+	   (( nameConversionBuffer[index - 3] ) == 't' ))
+	{
+		nameConversionBuffer[index - 2] = 'x';
+	}
+
+	// If this is a .pre file, switch to a .prx file.
+	if(((( nameConversionBuffer[index - 1] ) == 'e' ) || (( nameConversionBuffer[index - 1] ) == 'x' )) &&
+	    (( nameConversionBuffer[index - 2] ) == 'r' ) &&
+	    (( nameConversionBuffer[index - 3] ) == 'p' ))
+	{
+#		ifdef __PAL_BUILD__
+		// Switch to a .prf, .prg or .prx file, depending on language.
+		switch( Config::GetLanguage())
+		{
+			case Config::LANGUAGE_FRENCH:
+			{
+				nameConversionBuffer[index - 1] = 'f';
+				break;
+			}
+			case Config::LANGUAGE_GERMAN:
+			{
+				nameConversionBuffer[index - 1] = 'g';
+				break;
+			}
+			default:
+			{
+				nameConversionBuffer[index - 1] = 'x';
+				break;
+			}
+		}
+#		else
+		nameConversionBuffer[index - 1] = 'x';
+#		endif // __PLAT_BUILD__
+	}
+
+	// First we try reading the file from the utility partition (z:\) on the HD, rather than the DVD.
+	HANDLE h_file = INVALID_HANDLE_VALUE;
+	if( OkayToUseUtilityDrive )
+	{
+		nameConversionBuffer[0] = 'Z';
+		h_file = CreateFile( nameConversionBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
+	}
+
+	if( h_file == INVALID_HANDLE_VALUE )
+	{
+		// Not on the utility partition, so load it from the DVD.
+		nameConversionBuffer[0] = 'D';
+		h_file = CreateFile( nameConversionBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
+
+		// Deal with various error returns.
+		if( h_file == INVALID_HANDLE_VALUE )
+		{
+			DWORD error = GetLastError();
+
+			// Need to exclude this error from the test, since screenshot and other code sometimes check to see if a file exists, and it
+			// is valid to just return the error code if it doesn't.
+			if( error != ERROR_FILE_NOT_FOUND )
+			{
+				// Catch-all error indicating a fatal problem. Can't continue at this point.
+				// The ideal solution would be a catch/throw exception mechanism, but we don't include exception handling at the moment.
+				// For now just call this NxXbox function, which is slightly messy since it means we have to include a gfx\ file.
+				printf( "FatalFileError: %x %s\n", error, nameConversionBuffer );
+				NxXbox::FatalFileError((uint32)INVALID_HANDLE_VALUE );
+			}
+			return NULL;
+		}
+		else
+		{
+			// All is well.
+			return h_file;
+		}
+	}
+	else
+	{
+		return h_file;
+	}
+}
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+static CThreadedLevelLoader *pLoader;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void InstallFileSystem( void )
+{
+#	if 0
+	// This is where we start the thread that will deal with copying commonly used data from the DVD to the utility
+	// region (z:\) on the HD.
+	pLoader = new CThreadedLevelLoader();
+
+	SLevelDesc level_descs[] = {{ "pre\\alc.prx" },
+								{ "pre\\alccol.prx" },
+								{ "pre\\alcscn.prx" },
+								{ "pre\\anims.prx" },
+								{ "pre\\bits.prx" },
+								{ "pre\\cnv.prx" },
+								{ "pre\\cnvcol.prx" },
+								{ "pre\\cnvscn.prx" },
+								{ "pre\\fonts.prx" },
+								{ "pre\\jnk.prx" },
+								{ "pre\\jnkcol.prx" },
+								{ "pre\\jnkscn.prx" },
+								{ "pre\\kon.prx" },
+								{ "pre\\koncol.prx" },
+								{ "pre\\konscn.prx" },
+								{ "pre\\lon.prx" },
+								{ "pre\\loncol.prx" },
+								{ "pre\\lonscn.prx" },
+								{ "pre\\qb.prx" },
+								{ "pre\\sch.prx" },
+								{ "pre\\schcol.prx" },
+								{ "pre\\schscn.prx" },
+								{ "pre\\sf2.prx" },
+								{ "pre\\sf2col.prx" },
+								{ "pre\\sf2scn.prx" },
+								{ "pre\\skaterparts.prx" },
+								{ "pre\\skater_sounds.prx" },
+								{ "pre\\skateshop.prx" },
+								{ "pre\\skateshopcol.prx" },
+								{ "pre\\skateshopscn.prx" },
+								{ "pre\\skeletons.prx" },
+								{ "pre\\zoo.prx" },
+								{ "pre\\zoocol.prx" },
+								{ "pre\\zooscn.prx" }};
+
+	static BYTE data_buffer[32 * 1024];
+	pLoader->Initialize( level_descs, 34, data_buffer, 32 * 1024, &OkayToUseUtilityDrive );
+	pLoader->AsyncStreamLevel( 0 );
+#	endif
+
+	// Initialise the async file system.
+	File::CAsyncFileLoader::sInit();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+long GetFileSize( void* pFP )
+{
+	Dbg_MsgAssert( pFP, ( "NULL pFP sent to GetFileSize" ));
+
+	if( PreMgr::sPreEnabled())
+    {
+		int retval = PreMgr::pre_get_file_size( (PreFile::FileHandle *) pFP );
+		if( PreMgr::sPreExecuteSuccess())
+			return retval;
+	}
+	
+	LARGE_INTEGER	li;
+	GetFileSizeEx((HANDLE)pFP, &li );
+	return (long)li.LowPart;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+long GetFilePosition( void *pFP )
+{
+	Dbg_MsgAssert( pFP, ( "NULL pFP sent to GetFilePosition" ));
+
+	if( PreMgr::sPreEnabled())
+	{
+		int retval = PreMgr::pre_get_file_position((PreFile::FileHandle*)pFP );
+		if( PreMgr::sPreExecuteSuccess())
+			return retval;
+	}
+
+	long pos = SetFilePointer(	(HANDLE)pFP,	// Handle to file
+								0,				// Bytes to move pointer
+								0,				// High bytes to move pointer
+								FILE_CURRENT );	// Starting point
+	return pos;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void InitQuickFileSystem( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint32	CanFileBeLoadedQuickly( const char* filename )
+{
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool LoadFileQuicklyPlease( const char* filename, uint8 *addr )
+{
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void StopStreaming( void )
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void UninstallFileSystem( void )
+{
+}
+
+
+
+////////////////////////////////////////////////////////////////////
+// Our versions of the ANSI file IO functions. They call
+// the PreMgr first to see if the file is in a PRE file.
+////////////////////////////////////////////////////////////////////
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Exist( const char *filename )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        bool retval = PreMgr::pre_fexist(filename);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	void *p_result = prefopen( filename, "rb" );
+	if( p_result != NULL )
+	{
+		Close( p_result );
+	}
+
+	return( p_result != NULL );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void * Open( const char *filename, const char *access )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        void * retval = PreMgr::pre_fopen(filename, access);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	return prefopen( filename, access );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int Close( void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fclose((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	CloseHandle((HANDLE)pFP );
+
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+size_t Read( void *addr, size_t size, size_t count, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        size_t retval = PreMgr::pre_fread( addr, size, count, (PreFile::FileHandle*)pFP );
+		if( PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	DWORD bytes_read;
+	if( ReadFile((HANDLE)pFP, addr, size * count, &bytes_read, NULL ))
+	{
+		// All is well.
+		return bytes_read;
+	}
+	else
+	{
+		// Read error.
+		DWORD last_error = GetLastError();
+
+		if( last_error == ERROR_HANDLE_EOF )
+		{
+			// Continue in this case.
+			return bytes_read;
+		}
+
+		NxXbox::FatalFileError( last_error );
+		return bytes_read;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Read an Integer in little endian format. Just read it directly */
+/* into memory...												  */
+/*                                                                */
+/******************************************************************/
+size_t ReadInt( void *addr, void *pFP )
+{
+	return Read( addr, 4, 1, pFP );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+size_t Write( const void *addr, size_t size, size_t count, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        size_t retval = PreMgr::pre_fwrite(addr, size, count, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+char * GetS( char *buffer, int maxlen, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        char * retval = PreMgr::pre_fgets(buffer, maxlen, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int PutS( const char *buffer, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fputs(buffer, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int Eof( void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_feof((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int Seek( void *pFP, long offset, int origin )
+{
+	if( PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fseek((PreFile::FileHandle *) pFP, offset, origin);
+		if( PreMgr::sPreExecuteSuccess())
+			return retval;
+	}
+	return SetFilePointer((HANDLE)pFP, offset, NULL, ( origin == SEEK_CUR ) ? FILE_CURRENT : (( origin == SEEK_SET ) ? FILE_BEGIN : FILE_END ));
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int Flush( void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fflush((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace File
+
+
diff --git a/Code/Sys/File/XBox/p_pre.cpp b/Code/Sys/File/XBox/p_pre.cpp
new file mode 100644
index 0000000..8e4e0bf
--- /dev/null
+++ b/Code/Sys/File/XBox/p_pre.cpp
@@ -0,0 +1,1157 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:			Xbox specific PRE module 								**
+**																			**
+**	File name:		p_pre.cpp												**
+**																			**
+**	Created by:		dc														**
+**																			**
+**	Description:						 									**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// script stuff
+#include  
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+#define DEBUG_PRE 0
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define CURRENT_PRE_VERSION	0xabcd0002			// as of 3/14/2001
+//#define CURRENT_PRE_VERSION	0xabcd0001		// until 3/14/2001
+
+#define RINGBUFFERSIZE		 4096	/* N size of ring buffer */	
+#define MATCHLIMIT		   18	/* F upper limit for match_length */
+#define THRESHOLD	2   /* encode string into position and length */
+
+#define WriteOut(x) 	{*pOut++ = x;}
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+PreMgr *PreMgr::sp_mgr = NULL;
+bool    PreMgr::s_lastExecuteSuccess = false; 
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+
+
+#define USE_BUFFER	1		 // we don't need no stinking buffer!!!!
+ 
+#if USE_BUFFER
+unsigned char	text_buf[RINGBUFFERSIZE + MATCHLIMIT - 1];	
+#endif
+
+
+#define	ReadInto(x)		if (!Len) break; Len--; x = *pIn++ 
+#define	ReadInto2(x)	Len--; x = *pIn++ 	  // version that knows Len is Ok
+
+
+// Decode an LZSS encoded stream
+// Runs at approx 12MB/s on PS2	 without scratchpad (which slows it down in C)
+// a 32x CD would run at 4.8MB/sec, although we seem to get a lot less than this
+// with our current file system, more like 600K per seconds.....
+// Need to write a fast streaming file system....
+
+void DecodeLZSS( unsigned char *pIn, unsigned char *pOut, int Len )	/* Just the reverse of Encode(). */
+{
+	int  i, j, k, r, c;
+//	uint64	LongWord;
+//	int bytes = 0;
+//	unsigned char *pScratch;
+	unsigned int  flags;
+
+
+
+//	int basetime =  (int) Tmr::ElapsedTime(0);
+//	int len = Len;
+
+//	int	OutBytes = 4;
+//	int	OutWord = 0;
+
+#	if USE_BUFFER
+	for (i = 0; i < RINGBUFFERSIZE - MATCHLIMIT; i++)
+		 text_buf[i] = ' ';
+	r = RINGBUFFERSIZE - MATCHLIMIT;
+#	else
+	r = RINGBUFFERSIZE - MATCHLIMIT;
+#	endif
+	flags = 0;
+	for ( ; ; )
+	{
+		if (((flags >>= 1) & 256) == 0)
+		{
+			ReadInto(c);
+			flags = c | 0xff00;			/* uses higher byte cleverly */
+		}										/* to count eight */
+		if (flags & 1)
+		{
+			ReadInto(c);
+			//			putc(c, outfile);
+			WriteOut(c);
+			#if USE_BUFFER
+			text_buf[r++] = c;
+			r &= (RINGBUFFERSIZE - 1);
+			#else
+			r++;
+//			r &= (RINGBUFFERSIZE - 1);	  // don't need to wrap r until it is used
+			#endif
+		}
+		else
+		{
+			ReadInto(i);			
+			ReadInto2(j);			// note, don't need to check len on this one.... 
+			
+			i |= ((j & 0xf0) << 4);						// i is 12 bit offset
+			
+			#if !USE_BUFFER
+			j = (j & 0x0f) + THRESHOLD+1;				// j is 4 bit length (above the threshold)
+			unsigned char *pStream;
+			r &= (RINGBUFFERSIZE - 1);					// wrap r around before it is used
+			pStream = pOut - r;					  		// get base of block
+			if (i>=r)										// if offset > r, then
+				pStream -= RINGBUFFERSIZE;				// it's the previous block
+			pStream += i;									// add in the offset to the base
+			r+=j;												// add size to r
+			while (j--)										// copy j bytes
+				WriteOut(*pStream++);
+			#else
+			
+			j = (j & 0x0f) + THRESHOLD;				// j is 4 bit length (above the threshold)
+			for (k = 0; k <= j; k++)					// just copy the bytes
+			{
+				c =  text_buf[(i+k) & (RINGBUFFERSIZE - 1)]; 
+				WriteOut(c);
+				text_buf[r++] = c;
+				r &= (RINGBUFFERSIZE - 1);
+			}
+			#endif
+		}
+	}
+//	int Time = (int) Tmr::ElapsedTime(basetime);
+//	if (Time > 5)
+//	{
+//		printf("decomp time is %d ms, for %d bytes,  %d bytes/second\n", Time,len, len * 1000 /Time );
+//	}
+
+}
+
+
+
+
+void PreFile::s_delete_file(_File *pFile, void *pData)
+{
+	Dbg_Assert(pFile);
+	delete pFile;
+}
+
+
+
+
+PreFile::PreFile(uint8 *p_file_buffer)
+{
+	mp_table = new Lst::StringHashTable<_File>(4);	
+
+	Dbg_AssertPtr(p_file_buffer);
+
+	mp_buffer = p_file_buffer;
+	uint version = *((int *) (mp_buffer + 4));
+	Dbg_MsgAssert(version == CURRENT_PRE_VERSION,( "PRE file version not current"));
+	m_numEntries = *((int *)(mp_buffer + 8));
+
+	uint8 *pEntry = mp_buffer + 12;
+	for (int i = 0; i < m_numEntries; i++)
+	{
+		int data_size 				= *((int *) pEntry);
+		int compressed_data_size 	= *((int *) (pEntry + 4));
+		int text_size 				= *((int *) (pEntry + 8));
+		int actual_data_size = (compressed_data_size != 0) ? compressed_data_size : data_size;
+			
+		char *pName = (char *) pEntry + 12;
+		uint8 *pCompressedData = pEntry + 12 + text_size;
+		
+		_File *pFile = new (Mem::Manager::sHandle().TopDownHeap()) _File;
+
+		if (!mp_table->GetItem(pName)) 
+		{
+			// file is not in table, safe to add
+			mp_table->PutItem(pName, pFile);
+
+		pFile->compressedDataSize = compressed_data_size;
+		pFile->pCompressedData = pCompressedData; 
+		pFile->pData = NULL;
+		pFile->sky.POS = 0;
+		pFile->sky.SOF = data_size;
+		}
+		else
+			// Somehow, file is already in table, just kill it
+			// Later, I'll want to add an assert
+			delete pFile;
+		
+#		if DEBUG_PRE
+		printf("   %s, size %d\n", pName, data_size);
+#		endif
+		
+		pEntry += 12 + text_size + ((actual_data_size + 3) & (~3));
+	}
+
+#	if DEBUG_PRE
+	printf("Done loading PRE\n");
+#	endif
+	
+	mp_activeFile = NULL;
+}
+
+
+
+PreFile::~PreFile()
+{
+	delete mp_buffer;
+	mp_table->HandleCallback(s_delete_file, NULL);
+	mp_table->FlushAllItems();
+
+	delete mp_table;
+}
+
+
+
+bool PreFile::FileExists(const char *pName)
+{
+	
+	_File *pFile = mp_table->GetItem(pName, false);
+	return (pFile != NULL);
+}
+
+
+
+// returns handle pointer
+void *PreFile::GetContainedFile(const char *pName)
+{
+
+	_File *pFile = mp_table->GetItem(pName, false);
+	if (!pFile) 
+		return NULL;
+	
+	dumbSkyFile *pHandle = &pFile->sky;
+	// kinda roundabout, but sets mp_activeFile
+	GetContainedFileByHandle(pHandle);
+	
+	// do we need to fetch file data?
+	if (!mp_activeFile->pData)
+	{
+		if (mp_activeFile->compressedDataSize)
+		{
+		mp_activeFile->pData = new (Mem::Manager::sHandle().TopDownHeap()) uint8[mp_activeFile->sky.SOF];		
+			// need to uncompress data
+			DecodeLZSS(mp_activeFile->pCompressedData, mp_activeFile->pData, mp_activeFile->compressedDataSize);	
+		}
+	}
+
+	return pHandle;
+}
+	
+
+
+uint8 *PreFile::GetContainedFileByHandle(void *pHandle)
+{
+	mp_table->IterateStart();
+	_File *pFile = mp_table->IterateNext();
+	while(pFile)
+	{
+		uint8 *pCompressedData = pFile->pCompressedData;
+		if (pCompressedData && &pFile->sky == pHandle)
+		{
+			mp_activeFile = pFile;
+			
+			if (mp_activeFile->compressedDataSize)
+			return mp_activeFile->pData;
+			else
+				return mp_activeFile->pCompressedData;
+		}
+		pFile = mp_table->IterateNext();
+	}
+
+	return NULL;
+}
+
+
+
+// allocate memory and load file directly from a pre file, if it is there
+// This avoids the problem of having to have the decompressed file in memory twice
+// when we are loading directly, like with Pip::Load()
+// using this enables us to actually load network game, where there is 1MB less heap during loading
+//
+// returns a pointer to the file in memory
+// or NULL if the file is not in this pre file.
+void *PreFile::LoadContainedFile( const char *pName, int *p_size, void *p_dest )
+{
+	// The dest parameter is used only on Ps2.
+	Dbg_Assert( p_dest == NULL );
+	
+	_File *pFile = mp_table->GetItem( pName, false );
+	if( !pFile ) 
+	{
+		return NULL;
+	}	
+	
+	*p_size = pFile->sky.SOF;
+	
+	p_dest = Mem::Malloc( pFile->sky.SOF );
+	
+	// Do we need to decompress file data?
+	if( !pFile->pData )
+	{
+		if( pFile->compressedDataSize )
+		{
+			// Need to uncompress data.
+			DecodeLZSS( pFile->pCompressedData, (uint8*)p_dest, pFile->compressedDataSize );	
+		}
+		else
+		{
+			memcpy( p_dest, (void*)pFile->pCompressedData, pFile->sky.SOF );
+		}
+	}
+	else
+	{
+		memcpy( p_dest, (void*)pFile->pData, pFile->sky.SOF );
+	}
+	return p_dest;
+}
+
+
+
+void PreFile::Reset()
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	mp_activeFile->sky.POS = 0;
+}
+
+
+
+uint32 PreFile::Read( void *addr, uint32 count )
+{
+	
+	Dbg_AssertPtr( mp_activeFile );
+
+	int seek_offs = mp_activeFile->sky.POS;
+	unsigned int limit = mp_activeFile->sky.SOF - seek_offs;
+	int copy_number = (count <= limit) ? count : limit; 
+	if (mp_activeFile->compressedDataSize)
+	{
+		Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed"));
+	memcpy( addr, mp_activeFile->pData + mp_activeFile->sky.POS, copy_number );
+	}
+	else
+	{
+		memcpy(addr, mp_activeFile->pCompressedData + mp_activeFile->sky.POS, copy_number);
+	}
+	
+	mp_activeFile->sky.POS += copy_number;
+
+#	if DEBUG_PRE
+	printf("PRE: read %d bytes from file, handle 0x%x\n", copy_number, (int) mp_activeFile->pData);
+#	endif
+	return copy_number;
+}
+
+
+
+int PreFile::Eof()
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	if (mp_activeFile->sky.POS >= mp_activeFile->sky.SOF)
+	{
+#if DEBUG_PRE
+		printf("PRE: at end of file\n");
+#endif
+		return 1;
+	}
+
+#if DEBUG_PRE
+	printf("PRE: not at end of file\n");
+#endif
+	return 0;
+}
+
+
+
+void PreFile::Close()
+{
+
+	//Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed"));
+
+	if (mp_activeFile->pData)
+	delete mp_activeFile->pData;
+	mp_activeFile->pData = NULL;
+}
+
+
+
+int PreFile::Seek(long offset, int origin)
+{
+	int32 old_pos = mp_activeFile->sky.POS;
+
+	// SEEK_CUR, SEEK_END, SEEK_SET
+	switch(origin)
+	{
+		case SEEK_CUR:
+			mp_activeFile->sky.POS += offset;
+			break;
+		case SEEK_END:
+			mp_activeFile->sky.POS = mp_activeFile->sky.SOF - offset;
+			break;
+		case SEEK_SET:
+			mp_activeFile->sky.POS = offset;
+			break;
+		default:
+			return -1;
+	}
+
+	if (mp_activeFile->sky.POS < 0 || mp_activeFile->sky.POS > mp_activeFile->sky.SOF)
+	{
+		mp_activeFile->sky.POS = old_pos;
+		return -1;
+	}
+
+	return 0;
+}
+
+
+
+PreMgr::PreMgr() 
+{
+	mp_table = new Lst::StringHashTable(4);
+
+	sp_mgr = this;
+
+
+	mp_activeHandle = NULL;
+	mp_activeData = NULL;
+
+	mp_activeNonPreHandle = NULL;
+}
+
+
+
+PreMgr::~PreMgr()
+{
+	delete mp_table;
+}
+
+
+
+// Returns handle
+// Not frequently called
+void *PreMgr::getContainedFile(const char *pName)
+{
+	
+	Dbg_AssertPtr(pName);
+
+	// replace all '/' with '\'
+	char cleaned_name[128];
+	const char *pCharIn = pName;
+	char *pCharOut = cleaned_name;
+	while (1)
+	{
+		*pCharOut = *pCharIn;
+		if (*pCharIn == '\0') break;
+		if (*pCharOut == '/') *pCharOut = '\\';
+		pCharIn++;
+		pCharOut++;		
+	}
+
+	void *pHandle = NULL;
+
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+			pHandle = pPre->GetContainedFile(cleaned_name);
+		if (pHandle) 
+		{
+			mp_activePre = pPre;
+			mp_activeHandle = pHandle;
+			mp_activeData = pPre->GetContainedFileByHandle(pHandle);
+#			ifdef __PLAT_NGPS__
+//			scePrintf("+++ %s is in PRE\n", cleaned_name);
+#			endif
+			return pHandle;
+		}
+		pPre = mp_table->IterateNext();
+	}
+
+#	ifdef __PLAT_NGPS__
+//	scePrintf("--- %s not found in PRE\n", cleaned_name);
+#	endif
+	return NULL;
+}
+
+
+
+// returns true if the file exists in any of the pre files
+bool	PreMgr::fileExists(const char *pName)
+{
+	Dbg_AssertPtr(pName);
+	// replace all '/' with '\'
+	char cleaned_name[128];
+	const char *pCharIn = pName;
+	char *pCharOut = cleaned_name;
+	while (1)
+	{
+		*pCharOut = *pCharIn;
+		if (*pCharIn == '\0') break;
+		if (*pCharOut == '/') *pCharOut = '\\';
+		pCharIn++;
+		pCharOut++;		
+	}
+
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		if (pPre->FileExists(cleaned_name))
+		{
+			return true;
+		}
+		pPre = mp_table->IterateNext();
+	}
+	return false;
+}
+
+
+
+// returns pointer to data
+uint8 *PreMgr::getContainedFileByHandle(void *pHandle)
+{
+	
+	Dbg_AssertPtr(pHandle);
+
+	// if we know that the file in question is not in the PRE system,
+	// then it's a regular file, don't waste time looking for it
+	if (mp_activeNonPreHandle == pHandle)
+		return NULL;
+	
+	if (mp_activeHandle == pHandle)
+		// mp_activePre will be unchanged
+		return mp_activeData;
+	
+	uint8 *pData = NULL;
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+			pData = pPre->GetContainedFileByHandle(pHandle);
+		if (pData)
+		{
+			mp_activePre = pPre;
+			mp_activeHandle = pHandle;
+			mp_activeData = pData;			
+			return pData;
+		}
+		pPre = mp_table->IterateNext();
+	}
+
+	// obviously this file is not in the PRE system, mark as such
+	mp_activeNonPreHandle = pHandle;
+	return NULL;
+}
+
+// there's a wrapper around this now, so that we can do
+// some memory-context switching
+void PreMgr::loadPre(const char *pFilename, bool dont_assert)
+{
+	m_blockPreLoading = (bool) Script::GetInt("block_pre_loading", false);
+
+	if (m_blockPreLoading)
+		return;
+
+	if( Pcm::UsingCD() )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return;
+	}
+
+	// Moved this to below the Pcm::UsingCD() call as that is used (bad!!) to turn off
+	// music and streams.
+#	ifdef __PLAT_NGC__
+	OSReport ( "Warning: Attempting to load pre file: %s (ignored)\n", pFilename );
+	return;
+#	endif // __PLAT_NGC__
+
+#	ifdef __PLAT_NGPS__
+//	scePrintf("Loading PRE file %s...\n", pFilename);
+#	endif
+
+	char fullname[256];
+	sprintf(fullname, "pre\\%s", pFilename);
+
+	// Replace the .pre extension with .prx for Xbox PRE file.
+	fullname[strlen( fullname ) - 1] = 'x';
+
+	// Create a lower-case version.
+	char lower[128];
+	for( unsigned int lp = 0; lp < strlen( fullname ) + 1; lp++ ) lower[lp] = tolower( fullname[lp] );
+
+	Tmr::Time basetime = Tmr::ElapsedTime(0);
+
+	int file_size;
+	uint8 *pFile = NULL;
+
+	file_size = CanFileBeLoadedQuickly( fullname );
+	if ( file_size )
+	{
+		pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size];
+		bool fileLoaded= LoadFileQuicklyPlease( fullname, pFile );
+		if ( !fileLoaded )
+		{
+			printf( "pre file %s failed to load quickly.\n", fullname );
+			Dbg_MsgAssert( 0,( "Fire Matt - pre file didn't load quickly." ));
+		}
+	}
+	else
+	{
+#ifdef __PLAT_NGC__
+		NsFile file;
+		if ( file.exist( fullname ) )
+		{
+			file.open( fullname );
+#else
+			void *fp = File::Open(fullname, "rb");
+			if (!fp)
+			{
+#endif		// __PLAT_NGC__
+			// always run the code below if CD build
+				if (dont_assert || Config::CD()) 
+				{
+					printf("couldn't open %s\n", fullname);
+					return;
+				}
+				Dbg_MsgAssert(0,( "couldn't open %s\n", fullname));
+			}
+
+
+#ifdef __PLAT_NGC__
+			file.read( &file_size, 4 );
+			file_size = nLtEn32(file_size);
+#else
+			File::Read(&file_size, 4, 1, fp);
+			Dbg_MsgAssert(file_size > 0,( "%s has incorrect file size\n", fullname));
+#endif		// __PLAT_NGC__
+			if (Config::CD())
+			{
+				if (file_size <= 0) printf("%s has incorrect file size\n", fullname);
+			}	
+
+
+		// Now allocates the .PRE file from the top of the heap, to avoid fragmentation.
+			pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size];
+		//uint8 *pFile = new uint8[file_size];
+#ifdef __PLAT_NGC__
+			file.read( pFile + 4, file_size );
+			file.close();
+#else
+			File::Read(pFile + 4, 1, file_size, fp);
+			File::Close(fp);
+#endif		// __PLAT_NGC__
+		}
+
+		printf("load time for file size %d is %d ms\n", file_size, (int) Tmr::ElapsedTime(basetime));
+
+	// the PRE file object winds up at the top of the heap, too. This is fine because
+	// it will be unloaded at the same time as the big file buffer
+		if (!mp_table->PutItem(pFilename, new (Mem::Manager::sHandle().TopDownHeap()) PreFile(pFile)))
+			Dbg_MsgAssert(0,( "PRE %s loaded twice", pFilename));
+}
+
+
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+
+DefineSingletonClass(PreMgr, "PRE Manager");
+
+
+
+bool PreMgr::InPre(const char *pFilename)
+{
+
+
+	return mp_table->GetItem( pFilename );
+}
+
+
+
+void PreMgr::LoadPre( const char *pFilename, bool dont_assert )
+{
+	// GJ:  This function is a wrapper around loadPRE(), to
+	// make sure that all allocations go on the top-down heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	loadPre(pFilename, dont_assert);
+	Mem::Manager::sHandle().PopContext();	
+}
+
+
+
+void PreMgr::LoadPrePermanently(const char *pFilename, bool dont_assert)
+{
+	// GJ:  This function is a wrapper around LoadPRE(),
+	// used for loading PRE files which will reside in memory
+	// permanently.  Essentially, we need to make sure that the
+	// data is above Renderware's resource arena.  If you didn't
+	// use this function, you'd get fragmentation when the resource
+	// arena resized itself.
+
+	// Mick: Removed reference to renderware, the old "arena" concept is gone
+
+	
+	// Load the pre file...
+	// This will go on the top-down heap by default
+	LoadPre(pFilename, dont_assert);
+
+}
+
+
+
+void PreMgr::UnloadPre(const char *pFilename, bool dont_assert)
+{
+
+//	scePrintf("Unloading PRE file %s\n", pFilename);
+	
+	if (m_blockPreLoading)
+		return;
+	
+	PreFile *pThePre = mp_table->GetItem(pFilename);
+	if (!pThePre)
+	{
+		if (dont_assert) return;
+		Dbg_MsgAssert(0,( "PRE file %s not in PRE manager", pFilename));
+	}
+
+	mp_table->FlushItem(pFilename);
+	delete pThePre;
+}
+
+
+bool PreMgr::sPreEnabled()
+{
+	return sp_mgr != NULL;
+}
+
+bool PreMgr::sPreExecuteSuccess()
+{
+	return s_lastExecuteSuccess; 
+}
+
+#	ifndef __PLAT_NGC__
+bool PreMgr::pre_fexist(const char *name)
+{
+	Dbg_MsgAssert(name,( "requesting file NULL"));	
+	
+	if( sp_mgr->fileExists( name ))
+	{
+#		if DEBUG_PRE
+		printf("PRE: file %s exists\n", name);
+#		endif
+		s_lastExecuteSuccess = true;
+		return true;
+	}
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return false;
+	}
+
+	return s_lastExecuteSuccess = false;
+}
+
+
+
+void *PreMgr::pre_fopen(const char *name, const char *access)
+{
+	Dbg_MsgAssert(name,( "trying to open file NULL" ));	
+
+	char nameConversionBuffer[256];
+
+	// Just as we do in the Xbox version of prefopen(), if this is a .tex file, switch to a .txx file.
+	int length = strlen( name );
+	strncpy( nameConversionBuffer, name, length + 1 );
+	if((( nameConversionBuffer[length - 1] ) == 'x' ) &&
+	   (( nameConversionBuffer[length - 2] ) == 'e' ) &&
+	   (( nameConversionBuffer[length - 3] ) == 't' ))
+	{
+		nameConversionBuffer[length - 2] = 'x';
+	}
+
+	void *pHandle = sp_mgr->getContainedFile( nameConversionBuffer );
+	if( pHandle )
+	{
+		// if we are going to write the file, we want to use the regular file system
+		const char *pChar = access;
+		bool am_writing = false;
+		while(*pChar)
+		{
+			if (*pChar != 'r' && *pChar != 'b')
+				am_writing = true;
+			pChar++;
+		}
+		
+		if (am_writing)
+		{
+#			ifdef __PLAT_NGPS__
+//			scePrintf("    writing file %s\n", name);
+#			endif
+
+			// am writing, so we don't need file open in PRE system
+			sp_mgr->mp_activePre->Close();
+		}
+		else
+		{
+			// we're reading the file from the PRE system
+#			if DEBUG_PRE
+			printf("PRE: opened file %s, handle is 0x%x\n", name, (int) pHandle);
+#			endif
+			sp_mgr->mp_activePre->Reset();
+			s_lastExecuteSuccess = true;
+			return pHandle;
+		}
+	}
+
+	// if we get here, we are using the regular file system
+	if( Pcm::UsingCD())
+	{
+		Dbg_MsgAssert( 0, ( "File access forbidden while PCM audio is in progress." ));
+		return NULL;
+	}
+
+	s_lastExecuteSuccess = false;
+	return NULL;
+
+	//return pHandle;
+}
+
+
+
+int PreMgr::pre_fclose(void *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling fclose with invalid file ptr"));	
+	
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+#if DEBUG_PRE
+		printf("PRE: closed file, handle 0x%x\n", (int) fptr);
+#endif
+		sp_mgr->mp_activePre->Close();
+		s_lastExecuteSuccess = true;
+		return 0;
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+size_t PreMgr::pre_fread(void *addr, size_t size, size_t count, void *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling fread with invalid file ptr"));		
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle( fptr );
+	if (pData)
+	{
+		// read from a simulated file stream in PRE file
+		Dbg_AssertPtr(sp_mgr->mp_activePre);
+
+		// There is a subtle bug in the PS2 version, in that the pre version of fread()
+		// returns the number of bytes read, rather than the number of items. This
+		// version does return the correct value.
+		size_t bytes_read = sp_mgr->mp_activePre->Read( addr, size * count );
+//		return bytes_read / size;
+		return bytes_read;
+	}
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return 0;
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+size_t  PreMgr::pre_fwrite(const void *addr, size_t size, size_t count, void *fptr)
+{
+
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't write to a PRE file"));
+	
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return 0;
+	}
+
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+char *PreMgr::pre_fgets(char *buffer, int maxLen, void *fptr)
+{
+		
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return NULL;
+}
+
+
+
+int PreMgr::pre_fputs(const char *buffer, void *fptr)
+{
+		
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_feof(void *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling feof with invalid file ptr"));		
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		Dbg_AssertPtr(sp_mgr->mp_activePre);
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Eof();
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_fseek(void *fptr, long offset, int origin)
+{
+		
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if( pData )
+	{
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Seek( offset, origin );
+	}
+
+	Dbg_MsgAssert( !pData, ( "seek not supported for PRE file" ));
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_fflush(void *fptr)
+{
+
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "flush not supported for PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_ftell(void *fptr)
+{
+
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if( pData )
+	{
+		return sp_mgr->mp_activePre->TellActive();
+	}
+
+	Dbg_MsgAssert( !pData, ( "tell supported for PRE file" ));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+#	endif		// __PLAT_NGC__
+
+
+
+// @script | InPreFile | 
+// @uparm "string" | filename
+bool ScriptInPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	Spt::SingletonPtr pre_mgr;
+	return pre_mgr->InPre(pFilename);
+}
+
+
+
+// @script | LoadPreFile | 
+// @uparm "string" | filename
+// @flag dont_assert | 
+bool ScriptLoadPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	Spt::SingletonPtr pre_mgr;
+	pre_mgr->LoadPre(pFilename, pParams->ContainsFlag("dont_assert"));
+	return true;
+}
+
+
+
+// @script | UnloadPreFile | 
+// @flag BoardsPre | 
+// @flag dont_assert | 
+// @uparm "string" | filename
+bool ScriptUnloadPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+
+
+	Spt::SingletonPtr pre_mgr;
+	
+	if (pParams->ContainsFlag("BoardsPre"))
+	{
+		
+//		Spt::SingletonPtr< Mdl::Skate >	pSkate;
+//		pSkate->UnloadBoardPreIfPresent(pParams->ContainsFlag("dont_assert"));
+		printf ("STUBBED:  Unload BoardsPre, in ScriptUnloadPreFile\n");
+		return true;
+	}
+	
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	pre_mgr->UnloadPre(pFilename, pParams->ContainsFlag("dont_assert"));
+	return true;
+}
+
+
+
+void *PreMgr::LoadFile( const char *pName, int *p_size, void *p_dest )
+{
+	// The dest parameter is used only on Ps2.
+	Dbg_Assert( p_dest == NULL );
+
+	// NOTE: THIS IS JUST CUT AND PASTE FROM Pre::fileExists
+	Dbg_AssertPtr( pName );
+
+	// Replace all '/' with '\'
+	char cleaned_name[128];
+	const char *pCharIn = pName;
+	char *pCharOut = cleaned_name;
+	while (1)
+	{
+		*pCharOut = *pCharIn;
+		if (*pCharIn == '\0') break;
+		if (*pCharOut == '/') *pCharOut = '\\';
+		pCharIn++;
+		pCharOut++;		
+	}
+
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		
+		void *p_data = pPre->LoadContainedFile(cleaned_name,p_size);
+		if (p_data)
+		{
+			return p_data;
+		}
+		pPre = mp_table->IterateNext();
+	}
+	return NULL;
+
+}
+
+
+
+} // namespace File
+
+
+
+
diff --git a/Code/Sys/File/XBox/p_streamer.cpp b/Code/Sys/File/XBox/p_streamer.cpp
new file mode 100644
index 0000000..16db1a7
--- /dev/null
+++ b/Code/Sys/File/XBox/p_streamer.cpp
@@ -0,0 +1,770 @@
+#include 
+#include 
+#include "p_streamer.h"
+
+
+//-----------------------------------------------------------------------------
+// DVD/HD sector sizes (these will never change)
+//-----------------------------------------------------------------------------
+#define HD_SECTOR_SIZE	( 512 )
+#define DVD_SECTOR_SIZE	( 2 * 1024 )
+
+const char *p_time_string = __TIME__;
+const char *p_date_string = __DATE__;
+
+//-----------------------------------------------------------------------------
+// Name: CheckDMAAlignment
+// Desc: checks the alignment of file offset, read/write size, and 
+//       target/source memory for DMA IO
+//-----------------------------------------------------------------------------
+#ifdef _DEBUG
+VOID Check_DMA_Alignment( HANDLE hFile, VOID* pBuffer, DWORD dwOffset, DWORD dwNumBytesIO )
+{
+    // assert proper target memory alignment
+    Dbg_Assert( pBuffer );
+    Dbg_Assert( (DWORD(pBuffer) & 0x00000003) == 0 );
+
+     // figure out whether this file is on the DVD or hard disk
+    BY_HANDLE_FILE_INFORMATION Info;
+    GetFileInformationByHandle( hFile, &Info );
+    DWORD dwDVDSerialNumber;
+    GetVolumeInformation( "D:\\", NULL, 0, &dwDVDSerialNumber, NULL, NULL, NULL, 0 );
+
+    // assert proper file offset and read/write size
+    if( Info.dwVolumeSerialNumber == dwDVDSerialNumber )
+    {
+        Dbg_Assert( dwOffset % DVD_SECTOR_SIZE == 0 );         
+        Dbg_Assert( dwNumBytesIO % DVD_SECTOR_SIZE == 0); 
+    }
+    else
+    {
+        Dbg_Assert( dwOffset % HD_SECTOR_SIZE == 0 );       
+        Dbg_Assert( dwNumBytesIO % HD_SECTOR_SIZE == 0 ); 
+    }
+}
+#endif
+
+
+//-----------------------------------------------------------------------------
+// Name: CLevelLoader()
+// Desc: Constructor
+//-----------------------------------------------------------------------------
+CLevelLoader::CLevelLoader()
+:   m_pSysMemData( NULL ),
+    m_dwSysMemSize( 0 ),
+    m_pLevels( NULL ),
+    m_dwNumLevels( 0 ),
+    m_pCurrentLevel( NULL ),
+    m_IOState( Idle ),
+    m_pSysMemBuffer( NULL ),
+    m_pFileSig( NULL ),
+    m_hSignature( INVALID_HANDLE_VALUE )
+{
+    // create file sig buffer
+    Dbg_Assert( m_pFileSig == NULL );
+    Dbg_Assert( XCALCSIG_SIGNATURE_SIZE + sizeof(SIG_MAGIC) <= HD_SECTOR_SIZE );
+	m_pFileSig = new BYTE[HD_SECTOR_SIZE];
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ~CLevelLoader()
+// Desc: Destructor
+//-----------------------------------------------------------------------------
+CLevelLoader::~CLevelLoader()
+{
+    // Ensure we are not being deleted in the middle of an IO op.
+	Dbg_Assert( IsIdle());
+
+    // These should be already be cleaned up.
+    Dbg_Assert( m_hSignature == INVALID_HANDLE_VALUE );
+    Dbg_Assert( m_pSysMemBuffer == NULL );
+    Dbg_Assert( m_pFileSig == NULL );
+
+    // Close all handles.
+    for( UINT i = 0; i < m_dwNumLevels; i++ )
+    {
+        CloseHandle( m_pLevels[i].hDVDFile );
+        CloseHandle( m_pLevels[i].hHDFile );
+        CloseHandle( m_pLevels[i].hSigFile );
+    }
+
+	// We don't own this memory, so don't delete it.
+    m_pSysMemData = NULL;
+
+	// Free the level state array.
+	delete [] m_pLevels;
+	m_pLevels = NULL;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: Initialize
+// Desc: Initialize loader
+//-----------------------------------------------------------------------------
+HRESULT CLevelLoader::Initialize( SLevelDesc* pDescs, DWORD dwNumLevels, BYTE* pSysMemData, DWORD dwSysMemSize, BOOL *p_signal )
+{
+	// Make sure relevant data directories are on the HD utility section.
+	CreateDirectory( "Z:\\data", NULL );
+	CreateDirectory( "Z:\\data\\pre", NULL );
+	CreateDirectory( "Z:\\data\\music", NULL );
+	CreateDirectory( "Z:\\data\\music\\wma", NULL );
+
+	// Copy signal.
+	m_pOkayToUseUtilityDrive = p_signal;
+	
+	// Initialize data.
+	Dbg_Assert( pSysMemData );
+    m_dwSysMemSize	= dwSysMemSize;
+    m_pSysMemData	= pSysMemData;
+
+	// Create level states.
+	m_dwNumLevels = dwNumLevels;
+	m_pLevels = new SLevelState[m_dwNumLevels];
+    
+	// Init level states.
+    for( UINT i = 0; i < m_dwNumLevels; i++ )
+    {
+        // Copy level desc.
+        m_pLevels[i].Desc = pDescs[i];
+
+		// Clear file handle.
+        m_pLevels[i].hDVDFile			= INVALID_HANDLE_VALUE;
+        m_pLevels[i].hHDFile			= INVALID_HANDLE_VALUE;
+        m_pLevels[i].hSigFile			= INVALID_HANDLE_VALUE;
+        m_pLevels[i].bWasPreCached		= FALSE;
+        m_pLevels[i].bWasCacheCorrupted	= FALSE;
+        m_pLevels[i].dwDVDFileSize		= 0;
+    }
+
+	return RefreshLevelStates();
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: RefreshLevelStates
+// Desc: Refreshes the level loader's level states
+//-----------------------------------------------------------------------------
+HRESULT CLevelLoader::RefreshLevelStates()
+{
+    Dbg_Assert( IsIdle());
+
+    // Init states.
+    for( UINT i = 0; i < m_dwNumLevels; i++ )
+    {   
+        m_pLevels[i].bIsPreCached = FALSE;
+        m_pLevels[i].bIsCacheCorrupted = FALSE;
+
+        m_pLevels[i].bIsOpen = FALSE;
+        m_pLevels[i].dwSysMemSize = 0;
+        m_pLevels[i].dwVidMemSize = 0;
+
+        // Close any opened handles.
+        CloseHandle( m_pLevels[i].hDVDFile );
+        CloseHandle( m_pLevels[i].hSigFile );
+        CloseHandle( m_pLevels[i].hHDFile );
+
+        m_pLevels[i].hDVDFile = INVALID_HANDLE_VALUE;
+        m_pLevels[i].hHDFile = INVALID_HANDLE_VALUE;
+        m_pLevels[i].hSigFile = INVALID_HANDLE_VALUE;
+
+    }
+    return S_OK;
+}
+
+
+
+/******************************************************************/
+/* Name: OpenLevel												  */
+/* Desc: opens a level											  */
+/******************************************************************/
+HRESULT CLevelLoader::OpenLevel( SLevelState* pLevel, DWORD dwFlags )
+{
+	// If the level has never been opened before, open in.
+    if( !pLevel->bIsOpen )
+    {
+        // Check that the level file handles are not open.
+        Dbg_Assert( pLevel->hDVDFile == INVALID_HANDLE_VALUE );
+        Dbg_Assert( pLevel->hHDFile == INVALID_HANDLE_VALUE );
+        Dbg_Assert( pLevel->hSigFile == INVALID_HANDLE_VALUE );
+
+        char szBuffer[MAX_PATH];
+        
+		// DVD File.
+        sprintf( szBuffer, "D:\\data\\%s", pLevel->Desc.szName );
+
+        // Open DVD file for reading.
+		pLevel->hDVDFile = CreateFile( szBuffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, dwFlags, NULL );
+
+		if( pLevel->hDVDFile == INVALID_HANDLE_VALUE )
+			return EndOpenLevel( pLevel, BadOpen );
+
+		// Get size of file on DVD. NOTE: This size is used to help detect corrupt cached levels. If a cached level
+		// is not the same size as its DVD counterpart, the cached level is considered corrupt.
+        pLevel->dwDVDFileSize = ::GetFileSize( pLevel->hDVDFile, NULL );
+
+		// NOTE: Both files stored on the utility drive (signature file and level) are pre-sized. This prevents the files from
+		// becoming fragmented in the FATX.  Also, Overlapped reads/writes to the hard disk are synchronous if the file system
+		// has to hit the FATX to determine local->physical cluster mapping.
+
+        // SIG File.
+        sprintf( szBuffer, "Z:\\data\\%s.sig", pLevel->Desc.szName );
+
+		// See if we have a cached sig.
+        pLevel->bIsPreCached = ( GetFileAttributes( szBuffer ) != DWORD( -1 ));
+                
+		// Open sig file for reading and writing.
+        pLevel->hSigFile = CreateFile( szBuffer,
+                                       GENERIC_READ | GENERIC_WRITE,
+                                       FILE_SHARE_READ |
+                                       FILE_SHARE_WRITE |
+                                       FILE_SHARE_DELETE,
+                                       NULL,
+                                       OPEN_ALWAYS,
+                                       dwFlags,
+                                       NULL );
+        if( pLevel->hSigFile == INVALID_HANDLE_VALUE )
+            return EndOpenLevel( pLevel, BadOpen );
+        
+		// Cache is corrupted if sig file is not the right size. NOTE: The sig file is HD_SECTOR_SIZE in size. The actual
+		// signature calculated by XCalculateSignature* is much smaller that HD_SECTOR_SIZE, but we must write at least
+		// HD_SECTOR_SIZE to use DMA on the hard disk.
+		pLevel->bIsCacheCorrupted = pLevel->bIsPreCached && ::GetFileSize( pLevel->hSigFile, NULL ) != HD_SECTOR_SIZE;
+        
+		// If the sig file is corrupted or not saved, resize it.
+        if( !pLevel->bIsPreCached || pLevel->bIsCacheCorrupted )
+        {
+			// Set file size for faster write.
+            SetFilePointer( pLevel->hSigFile, HD_SECTOR_SIZE, NULL, FILE_BEGIN );
+			SetEndOfFile( pLevel->hSigFile );
+            
+			// Clear Sig magic number.
+            BYTE apyBuffer[HD_SECTOR_SIZE];
+            *(DWORD*)(apyBuffer) = ~SIG_MAGIC;
+            SetCurrentFile( pLevel->hSigFile );
+            if( FAILED( DoIO( Write, apyBuffer, HD_SECTOR_SIZE )))
+                return EndOpenLevel( pLevel, BadSigMagicWrite );
+
+			// Wait for IO completion for sig magic writes.
+            while( HasIOCompleted() != S_OK );
+        }
+
+        
+        // CACHED file.
+        sprintf( szBuffer, "Z:\\data\\%s", pLevel->Desc.szName );
+
+		// See if we have a cached file.
+        pLevel->bIsPreCached = pLevel->bIsPreCached && ( GetFileAttributes( szBuffer ) != DWORD( -1 )); 
+        
+        // Open cached file for reading and writing.
+        pLevel->hHDFile = CreateFile( szBuffer,
+                                      GENERIC_WRITE | GENERIC_READ, 
+                                      FILE_SHARE_READ |
+                                      FILE_SHARE_WRITE |
+                                      FILE_SHARE_DELETE,
+                                      NULL,
+                                      OPEN_ALWAYS,
+                                      dwFlags,
+                                      NULL );
+
+        if( pLevel->hHDFile == INVALID_HANDLE_VALUE )
+            return EndOpenLevel( pLevel, BadOpen );
+        
+		// Cache is corrupted if cache file is not the right size
+		pLevel->bIsCacheCorrupted = pLevel->bIsPreCached && (( ::GetFileSize( pLevel->hHDFile, NULL ) != pLevel->dwDVDFileSize ) || pLevel->bIsCacheCorrupted );
+
+        // If the cache file is corrupted or not saved, resize it.
+        if( !pLevel->bIsPreCached || pLevel->bIsCacheCorrupted )
+        {
+            // Set file size for faster write.
+			DWORD rv = SetFilePointer( pLevel->hHDFile, pLevel->dwDVDFileSize, NULL, FILE_BEGIN );
+			if( rv == INVALID_SET_FILE_POINTER )
+			{
+				DWORD last_error = GetLastError();
+				printf( "Last error: %x\n", last_error );
+			}
+			SetEndOfFile( pLevel->hHDFile );
+        }
+		return EndOpenLevel( pLevel, FilesOpened );
+    }
+
+    return S_OK;
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: AsyncStreamLevel
+// Desc: Loads all the resources from the given XPR asynchronously using DMA
+//-----------------------------------------------------------------------------
+VOID CLevelLoader::AsyncStreamLevel( DWORD dwLevel )
+{
+    Dbg_Assert( IsIdle() );
+    Dbg_Assert( dwLevel < m_dwNumLevels );
+
+	// We are no longer idle.
+	m_IOState = Begin;
+
+    // set current level
+	m_pCurrentLevel		= &m_pLevels[dwLevel];
+    m_dwCurrentLevel	= dwLevel;
+
+	// Start timer.
+	m_dStartTime = Tmr::GetTime();
+}
+
+
+//-----------------------------------------------------------------------------
+// Name: StreamCurrentBundle()
+// Desc: updates the streaming state, returning S_OK when finished
+//-----------------------------------------------------------------------------
+HRESULT CLevelLoader::StreamCurrentLevel()
+{
+    HRESULT hr;
+  
+    switch(m_IOState)
+    {
+        case Begin:
+        {
+            // reset IO
+            ResetStreaming();
+
+            // if we are reading from the cache, get the signature
+            if( IsCurrentCacheGood())
+            {
+                SetCurrentFile( m_pCurrentLevel->hSigFile );
+                if( FAILED( hr = DoIO( Read, m_pFileSig, HD_SECTOR_SIZE )))
+                    return EndStreamLevel( BadRead );
+            }
+            m_IOState = LoadSig;
+        }
+
+        case LoadSig:
+        {
+            // wait for previous IO to complete
+            if( FAILED( hr = HasIOCompleted()))
+                return EndStreamLevel( hr == E_PENDING ? Pending : BadRead );
+            
+            // Reading cached file, so a signature exists
+            if( IsCurrentCacheGood())
+            {
+				// Look for sig magic number.
+				DWORD dwSig;
+				dwSig = *(DWORD*)( m_pFileSig );
+				if( dwSig != SIG_MAGIC )
+                {
+                    if( dwSig == ~SIG_MAGIC )
+                        return EndStreamLevel( NoSigMagic );
+                    else
+                        return EndStreamLevel( BadSig );
+                }
+				
+				// Check date and time.            
+				dwSig = *(((DWORD*)m_pFileSig ) + 1 );
+				if( dwSig != Crc::GenerateCRCFromString( p_date_string ))
+                {
+					return EndStreamLevel( BadSig );
+                }
+				dwSig = *(((DWORD*)m_pFileSig ) + 2 );
+				if( dwSig != Crc::GenerateCRCFromString( p_time_string ))
+                {
+					return EndStreamLevel( BadSig );
+                }
+			}
+            else
+			{
+                // Set sig magic number so it is written out.
+                *(((DWORD*)m_pFileSig )	+ 0 )	= SIG_MAGIC;
+                *(((DWORD*)m_pFileSig ) + 1 )	= Crc::GenerateCRCFromString( p_date_string );
+                *(((DWORD*)m_pFileSig ) + 2 )	= Crc::GenerateCRCFromString( p_time_string );
+			}
+
+            m_IOState = LoadSysMem;
+        }
+
+        case LoadSysMem:
+        {
+            // Wait for previous IO to complete.
+            if( FAILED( hr = HasIOCompleted()))
+                return EndStreamLevel( hr == E_PENDING ? Pending : BadRead );
+        
+            // Read initial system memory buffer.
+            if( IsCurrentCacheGood())
+			{
+				// At this point we have decided the sig file is fine, which indicates that the cached file is fine also.
+				// As such, no further work required for this file.
+//				SetCurrentFile( m_pCurrentLevel->hHDFile );
+				m_pCurrentLevel->bIsPreCached = TRUE;
+				m_pCurrentLevel->bIsCacheCorrupted = FALSE;
+				return EndStreamLevel( Finished );
+			}
+            else
+			{
+				// Set file pointer to start of HD file.
+                SetCurrentFile( m_pCurrentLevel->hHDFile );
+				
+				// Set file pointer to start of DVD file.
+                SetCurrentFile( m_pCurrentLevel->hDVDFile );
+			}
+
+			// At this point we want to loop through reading the buffer and writing it until teh file is fully written.
+			DWORD	total_bytes_read		= 0;
+			DWORD	total_bytes_written		= 0;
+			int		bytes_remaining			= m_pCurrentLevel->dwDVDFileSize;
+			for( ;; )
+			{
+				// Set the DVD file, but don't reset the file pointer.
+                SetCurrentFile( m_pCurrentLevel->hDVDFile, FALSE );
+
+//				DWORD bytes_to_transfer = ( bytes_remaining >= m_dwSysMemSize ) ? m_dwSysMemSize : bytes_remaining;
+				DWORD bytes_to_transfer = ( bytes_remaining >= (int)m_dwSysMemSize ) ? m_dwSysMemSize : m_dwSysMemSize;
+				
+				if( FAILED( DoIO( Read, m_pSysMemData, bytes_to_transfer )))
+	                return EndStreamLevel( BadRead );
+
+				total_bytes_read += bytes_to_transfer;
+
+				// Set the HD file, but don't reset the file pointer.
+				SetCurrentFile( m_pCurrentLevel->hHDFile, FALSE );
+
+				if( FAILED( DoIO( Write, m_pSysMemData, bytes_to_transfer )))
+	                return EndStreamLevel( BadWrite );
+				
+				total_bytes_written += bytes_to_transfer;
+				bytes_remaining -= bytes_to_transfer;
+				
+				if( bytes_remaining <= 0 )
+				{
+					DWORD rv = SetFilePointer( m_pCurrentLevel->hHDFile, m_pCurrentLevel->dwDVDFileSize, NULL, FILE_BEGIN );
+					if( rv == INVALID_SET_FILE_POINTER )
+					{
+						DWORD last_error = GetLastError();
+						printf( "Last error: %x\n", last_error );
+					}
+					
+					SetEndOfFile( m_pCurrentLevel->hHDFile );
+					
+					m_IOState = WriteSig;
+					break;
+				}
+			}
+        }
+
+        case WriteSig:
+        {
+            // Wait for previous IO to complete.
+            if( FAILED( hr = HasIOCompleted() ) )
+                return EndStreamLevel( hr == E_PENDING ? Pending : BadRead );
+    
+			// We are now loaded.
+			m_dLoadTime = Tmr::GetTime() - m_dStartTime;
+
+            // We write the sig file last. If the box is turned off during a cache operation, the sig file will be missing.
+            // If the box is turned off during the write of the sig file, it won't have a header or will not match the level
+            // data. In either case, the level will be re-cached.
+            SetCurrentFile( m_pCurrentLevel->hSigFile );
+            if( FAILED( hr = DoIO( Write, m_pFileSig, HD_SECTOR_SIZE) ) )
+                return EndStreamLevel( BadWrite );
+
+            m_IOState = End;
+        }
+
+        case End:
+        {
+            // Wait for previous IO to complete.
+            if( FAILED( hr = HasIOCompleted() ) )
+                return EndStreamLevel( hr == E_PENDING ? Pending : BadRead );
+    
+			// Record cache time.
+			m_dCacheTime = Tmr::GetTime() - m_dStartTime;
+
+			// We are now cached.
+            m_pCurrentLevel->bIsPreCached		= TRUE;
+            m_pCurrentLevel->bIsCacheCorrupted	= FALSE;
+
+            return EndStreamLevel( Finished );
+        }
+    }
+
+    // should never reach here
+    Dbg_Assert( FALSE );
+    return E_FAIL;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: EndOpenLevel()
+// Desc: called when IO has completed or there is an error
+//-----------------------------------------------------------------------------
+HRESULT CLevelLoader::EndOpenLevel( SLevelState* pLevel, FileOpenStatus Status)
+{
+    Dbg_Assert(pLevel);
+
+    if(Status == BadOpen || Status == BadSigMagicWrite)
+    {
+        // close all file handles
+        CloseHandle( pLevel->hDVDFile );
+        CloseHandle( pLevel->hHDFile );
+        CloseHandle( pLevel->hSigFile );
+        pLevel->hDVDFile = INVALID_HANDLE_VALUE;
+        pLevel->hHDFile = INVALID_HANDLE_VALUE;
+        pLevel->hSigFile = INVALID_HANDLE_VALUE;
+
+        return E_FAIL;
+    }
+
+    // level is now opened
+    pLevel->bIsOpen = TRUE;
+
+    return S_OK;
+}
+
+
+//-----------------------------------------------------------------------------
+// Name: EndStreamLevel()
+// Desc: called when IO has completed or there is an error
+//-----------------------------------------------------------------------------
+HRESULT CLevelLoader::EndStreamLevel( FileIOStatus Status )
+{
+    // if IO is not pending, we are finished
+    if(Status != Pending)
+    {
+        // close the signature if needed
+        if( m_hSignature != INVALID_HANDLE_VALUE )
+        {
+            XCalculateSignatureEnd( m_hSignature, NULL );
+            m_hSignature = INVALID_HANDLE_VALUE;
+        }
+    }
+
+    // determine new state
+    switch(Status)
+    {
+        case BadRead:
+        case BadHeader:
+        case BadSig:
+        case SigMismatch:
+            m_pCurrentLevel->bIsCacheCorrupted = TRUE;
+            m_IOState = Begin; // retry streaming the level
+            return E_FAIL;
+        case NoSigMagic:
+            m_pCurrentLevel->bIsPreCached = FALSE;
+            m_IOState = Begin; // retry streaming the level
+            return E_FAIL;
+        case BadWrite:  // if we have a bad write, finish anyways since the cache will be corrupt next time around
+            m_pCurrentLevel->bIsCacheCorrupted = TRUE;
+        case Finished:
+            m_IOState = Idle;  // done with IO, so we are now idle
+
+			// close all file handles
+			CloseHandle( m_pCurrentLevel->hDVDFile );
+			CloseHandle( m_pCurrentLevel->hHDFile );
+			CloseHandle( m_pCurrentLevel->hSigFile );
+			m_pCurrentLevel->hDVDFile = INVALID_HANDLE_VALUE;
+			m_pCurrentLevel->hHDFile = INVALID_HANDLE_VALUE;
+			m_pCurrentLevel->hSigFile = INVALID_HANDLE_VALUE;
+			
+			return S_OK;
+        case Pending:
+            return E_PENDING;
+    }
+
+    // should never reach here
+    Dbg_Assert( FALSE );
+    return E_FAIL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Name: CRestIO()
+// Desc: Records last state for current level and get ready for IO
+//-----------------------------------------------------------------------------
+VOID CLevelLoader::ResetStreaming()
+{
+    // record last state
+    m_pCurrentLevel->bWasPreCached = m_pCurrentLevel->bIsPreCached;
+    m_pCurrentLevel->bWasCacheCorrupted = m_pCurrentLevel->bIsCacheCorrupted;
+
+    // begin signature
+    Dbg_Assert( m_hSignature == INVALID_HANDLE_VALUE );
+    m_hSignature = XCalculateSignatureBegin( XCALCSIG_FLAG_NON_ROAMABLE );
+    Dbg_Assert( m_hSignature != INVALID_HANDLE_VALUE );
+
+    // create file sig buffer
+//    Dbg_Assert( m_pFileSig == NULL );
+//    Dbg_Assert( XCALCSIG_SIGNATURE_SIZE + sizeof(SIG_MAGIC) <= HD_SECTOR_SIZE );
+//    m_pFileSig = new BYTE[HD_SECTOR_SIZE];
+}
+
+
+
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name: IOProc
+// Desc: Thread proc for IO
+//-----------------------------------------------------------------------------
+DWORD WINAPI IOProc( LPVOID lpParameter )
+{
+    CThreadedLevelLoader* pLoader = (CThreadedLevelLoader*)lpParameter;
+    Dbg_Assert( pLoader );
+   
+	// Wait for the IO event to be signaled.
+	WaitForSingleObject( pLoader->m_hEvent, INFINITE );
+	
+	for( ;; )
+    {
+        
+        // NOTE: In the threaded version of the loader, we are able to open
+        //       files and set file sizes mid-game without noticing a severe
+        //       frame "glitch."  If you use overlapped IO, you must remember
+        //       that OpenFile, SetEndOfFile, etc. are blocking
+
+        // open the current level
+        // If we can't open files, we are in trouble, so in the release build, we just keep trying
+//        while( FAILED( pLoader->OpenLevel( pLoader->m_pCurrentLevel, FILE_FLAG_NO_BUFFERING | FILE_FLAG_SEQUENTIAL_SCAN )))
+        while( FAILED( pLoader->OpenLevel( pLoader->m_pCurrentLevel, FILE_FLAG_SEQUENTIAL_SCAN )))
+        {
+            // break in the debug build to find out what the problem is
+            Dbg_Assert( FALSE );
+        }
+    
+        // Keep trying to load and cache the level until we are successful.
+        while( FAILED( pLoader->StreamCurrentLevel()));
+
+		// Level loaded and cached.
+		++pLoader->m_dwCurrentLevel;
+		if( pLoader->m_dwCurrentLevel < pLoader->m_dwNumLevels )
+		{
+			pLoader->m_pCurrentLevel = &pLoader->m_pLevels[pLoader->m_dwCurrentLevel];
+			pLoader->m_IOState = CThreadedLevelLoader::Begin;
+		}
+		else
+		{
+			break;
+		}
+	
+	}
+
+	// If the loaded has closed the thread we are finished. Signal the boolean value passed in.
+	*( pLoader->m_pOkayToUseUtilityDrive ) = true;
+
+	ExitThread( 0 );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: CThreadedLevelLoader()
+// Desc: Constructor
+//-----------------------------------------------------------------------------
+CThreadedLevelLoader::CThreadedLevelLoader() : m_bKillThread( FALSE )
+{
+    // Create event that is used to block the thread when it is not being used for IO.
+    m_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
+
+    // Create IO thread.
+    m_hThread =  CreateThread( NULL, 0, IOProc, this, 0, NULL);     
+
+	SetCurrentFile( INVALID_HANDLE_VALUE );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: ~CThreadedLevelLoader()
+// Desc: Destructor
+//-----------------------------------------------------------------------------
+CThreadedLevelLoader::~CThreadedLevelLoader()
+{
+    Dbg_Assert( IsIdle() );
+
+    // Kill the thread.
+
+    // signal thread 
+    m_bKillThread = TRUE;
+    SetEvent( m_hEvent );
+    
+    // wait for thread to exit
+    DWORD dwExitStatus;
+    do
+    {
+        SwitchToThread();  // try to get the thread to execute
+        GetExitCodeThread( m_hThread, &dwExitStatus );
+    }
+    while( dwExitStatus == STILL_ACTIVE );
+
+    // close handle to thread and event
+    CloseHandle( m_hThread );
+    CloseHandle( m_hEvent );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: DoIO
+// Desc: wrapper for ::ReadFile and ::WriteFile that checks parameters
+//-----------------------------------------------------------------------------
+HRESULT CThreadedLevelLoader::DoIO( IOType Type, VOID* pBuffer, DWORD dwNumBytes )
+{
+    // Check DMA alignment
+#	ifdef _DEBUG
+    Check_DMA_Alignment( m_hFile, pBuffer, SetFilePointer( m_hFile, 0, NULL, FILE_CURRENT ), dwNumBytes );
+#	endif
+
+    // Do IO
+    DWORD dwNumBytesIO;
+    BOOL bSuccess;
+    if(Type == Read)
+    {
+        bSuccess = ::ReadFile( m_hFile, pBuffer, dwNumBytes, &dwNumBytesIO, NULL );
+    }
+    else
+        bSuccess = ::WriteFile( m_hFile, pBuffer, dwNumBytes, &dwNumBytesIO, NULL );
+
+	// If IO is successful and we have the number of expected bytes, return success.
+    // NOTE: If the file size is X*SECTORSIZE + Y where Y is less than SECTORSIZE, we still request SECTORSIZE bytes when reading Y
+    // in order to get DMA. The file system will return success, and the number of bytes read/written is Y.
+    if( !bSuccess || ( dwNumBytesIO != dwNumBytes && SetFilePointer( m_hFile, 0, NULL, FILE_CURRENT ) != GetFileSize( m_hFile, NULL )))
+//	if( !bSuccess )
+        return E_FAIL;
+        
+    return S_OK;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: SetCurrentFile
+// Desc: Sets the current file to be used during IO
+//-----------------------------------------------------------------------------
+VOID CThreadedLevelLoader::SetCurrentFile( HANDLE hFile, BOOL to_start )
+{
+	if(( hFile != INVALID_HANDLE_VALUE ) && to_start )
+	{
+		// Reset file pointer to beginning.
+		SetFilePointer( hFile, 0, NULL, FILE_BEGIN );
+	}
+    m_hFile = hFile;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Name: AsyncStreamLevel
+// Desc: Begins streaming of the level.  returns immediately
+//-----------------------------------------------------------------------------
+VOID CThreadedLevelLoader::AsyncStreamLevel( DWORD dwLevel )
+{
+    CLevelLoader::AsyncStreamLevel( dwLevel );
+
+	// Signal thread to start IO.
+	SetEvent( m_hEvent );
+}
diff --git a/Code/Sys/File/XBox/p_streamer.h b/Code/Sys/File/XBox/p_streamer.h
new file mode 100644
index 0000000..9533e81
--- /dev/null
+++ b/Code/Sys/File/XBox/p_streamer.h
@@ -0,0 +1,398 @@
+//-----------------------------------------------------------------------------
+// File: LevelLoader.h
+//
+// Desc: Asynchronously loads and caches level data from XPR (Xbox Packed
+// Resource) files using DMA.  
+//
+// Hist: 03.12.02 - New for May XDK release
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//-----------------------------------------------------------------------------
+#ifndef LEVEL_LOADER_H
+#define LEVEL_LOADER_H
+
+#include 
+#include 
+#include 
+#include 
+
+
+
+// NOTE: The terms "system memory" and "video memory" are used throughout the
+//       source code.  "Video memory" is continuous physically addressed
+//       write-combining memory that is used for GPU resources. "System memory"
+//       is virtually addressed cached memory that is used by the CPU.
+
+//-----------------------------------------------------------------------------
+// SIG_MAGIC
+// Marker that specifies that a signature file exists and is valid.
+// Overlapped IO may open and pre-size a file without writing to it and
+// we want to detect this without signaling a corruption error.
+//-----------------------------------------------------------------------------
+#define SIG_MAGIC 0xF0F0F0F0ul
+
+
+//-----------------------------------------------------------------------------
+// Name: SLevelDesc
+// Desc: Struct that describes a level
+//-----------------------------------------------------------------------------
+struct SLevelDesc
+{
+    const CHAR* szName;          // level is assumed to be at media\szName.xpr
+//    DWORD       dwNumResources;  // number of resources in level
+//    XBRESOURCE* pResourceTags;   // array or resource tags
+};
+
+
+//-----------------------------------------------------------------------------
+// Name: SLevelState
+// Desc: Struct to hold state about a level
+//-----------------------------------------------------------------------------
+struct SLevelState
+{
+    SLevelDesc  Desc;
+    DWORD       dwDVDFileSize;   // size of level on DVD.  used for error
+                                 // detection
+    // state of level cache
+    BOOL        bIsPreCached;            
+    BOOL        bWasPreCached;
+    BOOL        bIsCacheCorrupted;
+    BOOL        bWasCacheCorrupted;
+
+    // memory requirements for the level
+    DWORD       dwSysMemSize;       
+    DWORD       dwVidMemSize;
+
+    // level file handles
+    HANDLE      hDVDFile;       // DVD file handle
+    HANDLE      hHDFile;        // Utility region file handle
+    HANDLE      hSigFile;       // signature file handle
+
+    BOOL        bIsOpen;        // are the handles opened
+};
+
+
+//-----------------------------------------------------------------------------
+// Name: class CLevelLoader
+// Desc: Cached level base class
+//       Handles loading and caching of levels
+//-----------------------------------------------------------------------------
+class CLevelLoader 
+{
+public:
+    // Constructor/destructor
+                    CLevelLoader();
+    virtual         ~CLevelLoader();
+
+    // Initializes the class with a list of levels and memory
+    HRESULT         Initialize( SLevelDesc* pDescs, DWORD dwNumlevels, BYTE* pSysMemData, DWORD dwSysMemSize, BOOL *p_signal );
+
+    // Load the specified level asynchronously
+    virtual VOID    AsyncStreamLevel( DWORD dwlevel );
+
+    // Updates load/cache state
+    virtual VOID    Update() = 0;
+
+    // refreshes the opened and loads states of all levels
+    virtual HRESULT RefreshLevelStates();
+
+
+    // function to retrieve state of current level
+    inline DWORD   GetCurrentDVDFileSize() const;
+    inline BOOL    IsCurrentPrecached() const;        
+    inline BOOL    WasCurrentPrecached() const;       
+    inline BOOL    IsCurrentCacheCorrupted() const;  
+    inline BOOL    WasCurrentCacheCorrupted() const;  
+    inline BOOL    IsCurrentOpen() const;
+    inline BOOL    IsCurrentLoaded() const;
+    
+    // functions to retrieve state of loader
+    inline BOOL    IsCacheing() const;
+    inline BOOL    IsLoading() const;
+    inline BOOL    IsIdle() const;
+    inline DOUBLE  GetLoadTime() const;   
+    inline DOUBLE  GetCacheTime() const;  
+    
+    // functions to retrieve resource by their offset
+    inline VOID*                    GetData( DWORD dwOffset ) const;
+    inline LPDIRECT3DRESOURCE8      GetResource( DWORD dwOffset ) const;
+    inline LPDIRECT3DTEXTURE8       GetTexture( DWORD dwOffset ) const;
+    inline LPDIRECT3DCUBETEXTURE8   GetCubemap( DWORD dwOffset ) const;
+    inline LPDIRECT3DVOLUMETEXTURE8 GetVolumeTexture( DWORD dwOffset ) const;
+    inline LPDIRECT3DVERTEXBUFFER8  GetVertexBuffer( DWORD dwOffset ) const;
+    
+    // Functions to retrieve resources by their name
+    VOID*                           GetData( const CHAR* strName ) const;
+    inline LPDIRECT3DRESOURCE8      GetResource( const CHAR* strName ) const;
+    inline LPDIRECT3DTEXTURE8       GetTexture( const CHAR* strName ) const;
+    inline LPDIRECT3DCUBETEXTURE8   GetCubemap( const CHAR* strName ) const;
+    inline LPDIRECT3DVOLUMETEXTURE8 GetVolumeTexture( const CHAR* strN ) const;
+    inline LPDIRECT3DVERTEXBUFFER8  GetVertexBuffer( const CHAR* strN ) const;
+    
+protected:
+
+    // IO status
+    enum FileIOStatus
+    {
+        BadRead = 0,
+        BadWrite,
+        BadHeader,
+        BadSig,
+        SigMismatch,
+        NoSigMagic,
+        Finished,
+        Pending
+    };
+
+    // Openfile status
+    enum FileOpenStatus
+    {
+        BadOpen = 0,
+        BadSigMagicWrite,
+        FilesOpened
+    };
+
+     // type of IO requested
+    enum IOType
+    {
+        Read = 0,
+        Write,
+    };
+
+    // current step in loading the level resources
+    enum IOState
+    {
+        Begin = 0,
+        LoadSig,
+        LoadSysMem,
+        ParseHeader,
+        CalcSig,
+        LoadVidMem,
+        RegisterResources,
+        WriteSysMem,
+        WriteVidMem,
+        WriteSig,
+        End,
+        Idle
+    };
+
+
+    // opens the current level
+    HRESULT         OpenLevel( SLevelState* pState, DWORD dwFlags );
+    HRESULT         EndOpenLevel( SLevelState* pLevel, FileOpenStatus Status );
+
+    // streams the current level
+    HRESULT         StreamCurrentLevel();
+    VOID            ResetStreaming();
+    HRESULT         EndStreamLevel( FileIOStatus Status );
+
+    // is the current cache valid
+    inline BOOL     IsCurrentCacheGood();
+
+    // set the current IO file
+    virtual VOID    SetCurrentFile( HANDLE hFile, BOOL to_start = TRUE ) = 0;
+
+    // wrappers for read and write file
+    virtual HRESULT DoIO( IOType Type, VOID* pBuffer, DWORD dwNumBytes ) = 0;
+
+    // has an IO op completed
+    virtual HRESULT HasIOCompleted() = 0;
+
+
+
+    // Allocated memory for resource headers etc.
+    BYTE*           m_pSysMemData;    
+    DWORD           m_dwSysMemSize;
+
+    // level state array
+    SLevelState*    m_pLevels;
+    DWORD           m_dwNumLevels;
+    DWORD           m_dwCurrentLevel;
+
+    // current level
+    SLevelState*   m_pCurrentLevel;
+
+    // stats data
+	Tmr::Time		m_dStartTime;
+    Tmr::Time		m_dLoadTime;        
+    Tmr::Time		m_dCacheTime;
+    
+    // current IO state
+    IOState         m_IOState;
+
+    // temp variables used during load
+    BYTE*           m_pSysMemBuffer;
+    BYTE*           m_pFileSig;
+    HANDLE          m_hSignature;
+
+	BOOL			*m_pOkayToUseUtilityDrive;
+};
+
+
+//-----------------------------------------------------------------------------
+// Inlined accessors
+//-----------------------------------------------------------------------------
+inline DWORD CLevelLoader::GetCurrentDVDFileSize() const
+{
+    return m_pCurrentLevel ? m_pCurrentLevel->dwDVDFileSize: 0;
+}
+inline BOOL CLevelLoader::IsCurrentPrecached() const        
+{
+    return m_pCurrentLevel ? m_pCurrentLevel->bIsPreCached : FALSE;
+}
+inline BOOL CLevelLoader::WasCurrentPrecached() const       
+{
+    return m_pCurrentLevel ? m_pCurrentLevel->bWasPreCached : FALSE;
+}
+inline BOOL CLevelLoader::IsCurrentCacheCorrupted() const   
+{
+    return m_pCurrentLevel ? m_pCurrentLevel->bIsCacheCorrupted : FALSE;
+}
+inline BOOL CLevelLoader::WasCurrentCacheCorrupted() const  
+{
+    return m_pCurrentLevel ? m_pCurrentLevel->bWasCacheCorrupted : FALSE;
+}
+inline BOOL CLevelLoader::IsCurrentOpen() const           
+{
+    return m_pCurrentLevel ? m_pCurrentLevel->bIsOpen : FALSE;
+}
+inline BOOL CLevelLoader::IsCacheing() const
+{
+    return m_pCurrentLevel &&
+        m_IOState > RegisterResources && m_IOState < Idle;
+}
+inline BOOL CLevelLoader::IsCurrentLoaded() const
+{
+    return m_pCurrentLevel && m_IOState > RegisterResources;
+}
+inline BOOL CLevelLoader::IsLoading() const
+{
+    return m_pCurrentLevel && m_IOState < WriteSysMem;
+}
+inline DOUBLE CLevelLoader::GetLoadTime() const
+{
+    return m_dLoadTime;
+}
+inline DOUBLE CLevelLoader::GetCacheTime() const
+{
+    return m_dCacheTime;
+}
+inline BOOL CLevelLoader::IsIdle() const
+{
+    return m_IOState == Idle;
+}
+inline BOOL CLevelLoader::IsCurrentCacheGood()
+{
+    return m_pCurrentLevel ? m_pCurrentLevel->bIsPreCached
+           && !m_pCurrentLevel->bIsCacheCorrupted : FALSE;
+}
+inline VOID* CLevelLoader::GetData( DWORD dwOffset ) const
+{
+    return m_pCurrentLevel ? &m_pSysMemData[dwOffset] : NULL;
+}
+inline LPDIRECT3DRESOURCE8 CLevelLoader::GetResource( DWORD dwOffset ) const
+{
+    return (LPDIRECT3DRESOURCE8)GetData(dwOffset);
+}
+inline LPDIRECT3DTEXTURE8 CLevelLoader::GetTexture( DWORD dwOffset ) const
+{
+    return (LPDIRECT3DTEXTURE8)GetData( dwOffset );
+}
+inline LPDIRECT3DCUBETEXTURE8
+CLevelLoader::GetCubemap( DWORD dwOffset ) const
+{
+    return (LPDIRECT3DCUBETEXTURE8)GetData( dwOffset );
+}
+inline LPDIRECT3DVOLUMETEXTURE8
+CLevelLoader::GetVolumeTexture( DWORD dwOffset ) const
+{
+    return (LPDIRECT3DVOLUMETEXTURE8)GetData( dwOffset );
+}
+inline LPDIRECT3DVERTEXBUFFER8
+CLevelLoader::GetVertexBuffer( DWORD dwOffset ) const
+{
+    return (LPDIRECT3DVERTEXBUFFER8)GetData( dwOffset );
+}
+inline LPDIRECT3DRESOURCE8
+CLevelLoader::GetResource( const CHAR* strName ) const
+{
+    return (LPDIRECT3DRESOURCE8)GetData( strName );
+}
+inline LPDIRECT3DTEXTURE8
+CLevelLoader::GetTexture( const CHAR* strName ) const
+{
+    return (LPDIRECT3DTEXTURE8)GetResource( strName );
+}
+inline LPDIRECT3DCUBETEXTURE8
+CLevelLoader::GetCubemap( const CHAR* strName ) const
+{
+    return (LPDIRECT3DCUBETEXTURE8)GetResource( strName );
+}
+inline LPDIRECT3DVOLUMETEXTURE8
+CLevelLoader::GetVolumeTexture( const CHAR* strName ) const
+{
+    return (LPDIRECT3DVOLUMETEXTURE8)GetResource( strName );
+}
+inline LPDIRECT3DVERTEXBUFFER8
+CLevelLoader::GetVertexBuffer( const CHAR* strName ) const
+{
+    return (LPDIRECT3DVERTEXBUFFER8)GetResource( strName );
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Name:  IOProc
+// Desc: Thread proc for IO
+//-----------------------------------------------------------------------------
+DWORD WINAPI IOProc( LPVOID lpParameter );
+
+
+//-----------------------------------------------------------------------------
+// Name: class CThreadedLevelLoader
+// Desc: Threaded Cached resource.  Handles loading and caching of packed
+//       resources, using a thread
+//-----------------------------------------------------------------------------
+class CThreadedLevelLoader : public CLevelLoader
+{
+public:
+
+    // Constructor/destructor
+                    CThreadedLevelLoader();
+    virtual         ~CThreadedLevelLoader();
+
+    virtual VOID    AsyncStreamLevel( DWORD dwlevel );
+    virtual VOID    Update() {} // does nothing for the threaded version
+
+protected:
+
+    virtual VOID    SetCurrentFile( HANDLE hFile, BOOL to_start = TRUE );
+    virtual HRESULT DoIO( IOType Type, VOID* pBuffer, DWORD dwNumBytes );
+    virtual HRESULT HasIOCompleted() { return S_OK; }; // IO is synchronous
+                                                       // but in its own thread
+
+    
+    // current file
+    HANDLE          m_hFile;
+    
+    // thread handle and wake event
+    HANDLE          m_hEvent;
+    HANDLE          m_hThread;
+
+    // signal to kill the thread (only done when the class is destroyed)
+    BOOL            m_bKillThread;
+
+    // the IO proc is our friend
+    friend          DWORD WINAPI IOProc( LPVOID lpParameter );
+};
+
+
+
+
+#endif //LEVEL_LOADER_H
+
+
+
+
+
diff --git a/Code/Sys/File/filesys.h b/Code/Sys/File/filesys.h
new file mode 100644
index 0000000..f542abe
--- /dev/null
+++ b/Code/Sys/File/filesys.h
@@ -0,0 +1,104 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/alloc.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_FILE_FILESYS_H
+#define	__SYS_FILE_FILESYS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+void InstallFileSystem( void );
+void UninstallFileSystem( void );
+long GetFileSize(void *pFP);
+long GetFilePosition(void *pFP);
+float GetPercentageRead(void *pFP);
+void InitQuickFileSystem( void );
+void ResetQuickFileSystem( void );
+
+uint32	CanFileBeLoadedQuickly( const char *filename );
+bool LoadFileQuicklyPlease( const char *filename, uint8 *addr );
+
+void   *LoadAlloc(const char *p_fileName, int *p_filesize=NULL, void *p_dest = NULL, int maxSize=0);
+
+
+void StopStreaming( void );
+
+/////////////////////////////////////////////////////////////////////
+// The following are our versions of the ANSI file IO functions.
+//
+bool   Exist( const char *filename );
+void * Open( const char *filename, const char *access );
+int    Close( void *pFP );
+size_t Read( void *addr, size_t size, size_t count, void *pFP );
+size_t ReadInt( void *addr, void *pFP );
+size_t Write( const void *addr, size_t size, size_t count, void *pFP );
+char * GetS( char *buffer, int maxlen, void *pFP );
+int    PutS( const char *buffer, void *pFP );
+int    Eof( void *pFP );
+int    Seek( void *pFP, long offset, int origin );
+int	   Tell( void* pFP );
+int    Flush( void *pFP );
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace File
+
+#endif  // __SYS_FILE_FILESYS_H
diff --git a/Code/Sys/File/memfile.h b/Code/Sys/File/memfile.h
new file mode 100644
index 0000000..4fe57a6
--- /dev/null
+++ b/Code/Sys/File/memfile.h
@@ -0,0 +1,29 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+// memfile.h
+//
+// memory mapped file replacement functions
+// allow you to load a file to memory, and then use these MemFile:: functions
+// instead of File:: functions, to load it exactly the same as before
+// so you would replace
+// 	File::Read(
+// with
+//	MEM_Read(
+//
+// Note that since these are macros, they dont return anything
+//
+#define MEM_Read( addr, size, count, pFP )	   \
+{											   \
+	char *p = (char*)(addr);				   \
+	char *q = (char*)(pFP);					   \
+	int total = (size)*(count);				   \
+	for (;total>0;total--)					   \
+		*p++ = *q++;						   \
+	pFP = (void*) q;						   \
+}											   \
+
+#define MEM_Seek(pFP, offset, origin )	   \
+{										   \
+	pFP = (void*)((char*)(pFP) + (offset));  \
+}										   \
+
diff --git a/Code/Sys/File/ngc/hed.cpp b/Code/Sys/File/ngc/hed.cpp
new file mode 100644
index 0000000..e25cfc0
--- /dev/null
+++ b/Code/Sys/File/ngc/hed.cpp
@@ -0,0 +1,319 @@
+/*	Header file functionality...
+	.Hed files that describe the contents of .Wad files
+	Written by Ken, stolen by Matt*/
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//
+//#include 
+
+#include 
+#include 
+
+#include  
+
+#include 
+#include 
+#include 
+
+bool sLoadingHed = false;
+
+namespace File
+{
+
+static void get_checksums( const char * p_name, uint32 * p_dir, uint32 * p_file )
+{
+	char name[128];
+	char dir[128];
+	char file[128];
+	uint lp;
+
+	// Convert / to \\.
+	for ( lp = 0; lp <= strlen( p_name ); lp++ )
+	{
+		name[lp] = p_name[lp];
+		if ( name[lp] == '/' ) name[lp] = '\\';
+	}
+
+	// Find the directory name and the filename.
+	for ( lp = strlen( name ) - 1; lp >= 0; lp-- )
+	{
+		if ( name[lp] == '\\' )
+		{
+			// Found the directory end.
+			strcpy( file, &name[lp+1] );
+			name[lp] = '\0';
+			strcpy( dir, name );
+			break;
+		}
+	}
+
+	// Chop off the extension (if it exists). 
+	if ( file[strlen(file)-4] == '.' )
+	{
+		file[strlen(file)-4] = '\0';
+	}
+
+	// Create checksums.
+	if ( p_dir ) *p_dir = Script::GenerateCRC( dir );
+	if ( p_file ) *p_file = Script::GenerateCRC( file );
+}
+
+
+
+
+// Searches the hed file for the filename. If not found, returns NULL.
+SHedFile *FindFileInHed(const char *pFilename, SHed *pHed)
+{
+	if ( sLoadingHed ) return NULL;
+
+	Dbg_MsgAssert(pFilename,("NULL pFilename"));
+	Dbg_MsgAssert(pHed,("NULL pHed"));
+
+	uint32 check_dir;
+	uint32 check_file;
+
+	get_checksums( pFilename, &check_dir, &check_file );
+
+	SHed *pHd=pHed;
+	while ( pHd->numFiles != 0xffffffff )
+	{
+		if ( pHd->Checksum == check_dir )
+		{
+			SHedFile * pF = pHd->p_fileList;
+			for ( uint lp2 = 0; lp2 < pHd->numFiles; lp2++ )
+			{
+				if ( pF->Checksum == check_file )
+				{
+					return pF;
+				}
+				pF++;
+			}
+		}
+		pHd++;
+	}
+
+	return NULL;
+}
+
+// Searches the hed file for the filename with the same checksum. If not found, returns NULL.
+SHedFile *FindFileInHedUsingChecksum( uint32 checksum, SHed *pHed )
+{
+	if ( sLoadingHed ) return NULL;
+
+	Dbg_MsgAssert(pHed,("NULL pHed"));
+
+	SHed *pHd=pHed;
+	while ( pHd->numFiles != 0xffffffff )
+	{
+		SHedFile * pF = pHd->p_fileList;
+		for ( uint lp2 = 0; lp2 < pHd->numFiles; lp2++ )
+		{
+			if ( pF->Checksum == checksum )
+			{
+				return pF;
+			}
+			pF++;
+		}
+		pHd++;
+	}
+
+	return NULL;
+}
+
+//#ifdef __NOPT_CDROM__
+//
+//extern void StopStreaming( void );
+//
+//SHed *LoadHed( const char *filename )
+//{
+//	NsFile file;
+//	SHed *pHed = NULL;
+////	int HedId=-1;
+//	char hedFileName[255];
+//	char uppercaseFilename[ 255 ];
+//	int sL = strlen( filename );
+//	Dbg_MsgAssert( sL < 255,( "Filename %s too long", filename ));
+//	int i;
+//	for ( i = 0; i < sL; i++ )
+//	{
+//		uppercaseFilename[ i ] = toupper( filename[ i ] );
+//	}
+//	uppercaseFilename[ i ] = '\0';
+//	StopStreaming( );
+//	sprintf( hedFileName, "cdrom0:\\%s.HED;1", uppercaseFilename );
+//	while (HedId<0)
+//	{
+//		file.open( hedFileName );
+////		HedId=sceOpen( hedFileName, SCE_RDONLY );
+//		if (HedId<0)
+//		{
+//			printf("Retrying opening %s\n", hedFileName );
+//		}	
+//	}
+//	printf("%s opened successfully\n", uppercaseFilename );
+////	long HedSize=sceLseek(HedId, 0, SCE_SEEK_END);
+//	long HedSize=file.size();
+//	
+//	Dbg_MsgAssert(pHed==NULL,("pHed not NULL ?"));
+//	pHed=(SHed*)Mem::Malloc((HedSize+2047)&~2047);
+//
+///*
+//	char *p = (char*) pHed;  
+//	// Hmmm.....
+//	for (int i=0 ;i < (HedSize+2047)&~2047;i++)
+//	{
+//		p[i] = 0x55;
+//	}
+//*/						
+//					
+////	sceLseek(HedId, 0, SCE_SEEK_SET);
+////	sceRead(HedId,pHed,HedSize);
+////	sceClose(HedId);
+//	file.read( pHed,, HedSize );
+//	file.close();
+//	return ( pHed );
+//	return ( NULL );
+//}
+//#else
+//
+SHed *LoadHed( const char *filename )
+{
+	sLoadingHed = true;
+
+	int lp;
+	char tempFileName[255];
+	sprintf( tempFileName, "%sNGC.HED", filename );
+
+	void *fp = File::Open( tempFileName, "rb" );
+
+	int fileSize = File::GetFileSize( fp );
+	
+	uint32 *pHed = (uint32 *)new (Mem::Manager::sHandle().TopDownHeap()) uint8[fileSize];
+	Dbg_MsgAssert( pHed,( "Failed to allocate memory for hed %s", tempFileName ));
+
+	File::Read( pHed, 1, fileSize, fp );
+	File::Close( fp );
+
+	// Convert to more compact list.
+	int files = 0;
+	int dirs = 0;
+
+	// First, count how many files we have.
+	uint32 * p32 = pHed;
+	while ( p32[0] != 0xffffffff )
+	{
+		files++;
+
+		// Byte order is reversed on NGC.
+		p32[0] = ( ( p32[0] >> 24 ) & 0xff ) | ( ( p32[0] >> 8 ) & 0xff00 ) | ( ( p32[0] << 8 ) & 0xff0000 ) | ( ( p32[0] << 24 ) & 0xff000000 );
+		p32[1] = ( ( p32[1] >> 24 ) & 0xff ) | ( ( p32[1] >> 8 ) & 0xff00 ) | ( ( p32[1] << 8 ) & 0xff0000 ) | ( ( p32[1] << 24 ) & 0xff000000 );
+
+		int len = strlen( (char *)&p32[2] ) + 1;
+		len = ( len + 3 ) & ~3;
+		p32 = (uint32*)(((int)&p32[2]) + len);
+	}
+
+	// Build list of checksums.
+	uint32 *p_offset = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	uint32 *p_size = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	uint32 *dir_check = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	uint32 *file_check = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	int count = 0;
+	p32 = pHed;
+	while ( p32[0] != 0xffffffff )
+	{
+		p_offset[count] = p32[0];
+		p_size[count] = p32[1];
+		get_checksums( (char *)&p32[2], &dir_check[count], &file_check[count] );
+		count++;
+
+		int len = strlen( (char *)&p32[2] ) + 1;
+		len = ( len + 3 ) & ~3;
+		p32 = (uint32*)(((int)&p32[2]) + len);
+	}
+
+	// Second, count how many directories we have.
+	for ( lp = 0; lp < files; lp++ )
+	{
+		bool unique = true;
+		for ( int lp2 = 0; lp2 < lp; lp2++ )
+		{
+			if ( dir_check[lp] == dir_check[lp2] )
+			{
+				unique = false;
+				break;
+			}
+		}
+		if ( unique ) dirs++;
+	}
+
+	// Now, allocate final piece of memory.
+	int size = ( sizeof( SHed ) * ( dirs + 1 ) ) + ( sizeof( SHedFile ) * files );
+	SHed * p_hed = (SHed *)new uint8[size];
+
+	// Fill in directory entries.
+	int dirs_added = 0;
+	for ( lp = 0; lp < files; lp++ )
+	{
+		bool unique = true;
+		for ( int lp2 = 0; lp2 < dirs_added; lp2++ )
+		{
+			if ( p_hed[lp2].Checksum == dir_check[lp] )
+			{
+				unique = false;
+				break;
+			}
+		}
+		if ( unique )
+		{
+			p_hed[dirs_added].Checksum = dir_check[lp];
+			dirs_added++;
+		}
+	}
+
+	// Fill in file entries.
+	SHedFile * p_hed_file = (SHedFile *)&p_hed[(dirs+1)];
+	for ( lp = 0; lp < dirs; lp++ )
+	{
+		p_hed[lp].p_fileList = p_hed_file;
+		int files_this_dir = 0;
+		for ( int lp2 = 0; lp2 < files; lp2++ )
+		{
+			if ( dir_check[lp2] == p_hed[lp].Checksum )
+			{
+				p_hed_file->Offset = p_offset[lp2];
+				p_hed_file->FileSize = p_size[lp2];
+				p_hed_file->Checksum = file_check[lp2];
+				files_this_dir++;
+				p_hed_file++;
+			}
+		}
+		p_hed[lp].numFiles = files_this_dir;
+	}
+	p_hed[dirs].numFiles = 0xffffffff;
+
+	// Get rid of temp data.
+	delete file_check;
+	delete dir_check;
+	delete p_size;
+	delete p_offset;
+	delete pHed;
+
+	sLoadingHed = false;
+
+	return p_hed;
+}
+//#endif
+
+} // namespace File
+
+
diff --git a/Code/Sys/File/ngc/hed.h b/Code/Sys/File/ngc/hed.h
new file mode 100644
index 0000000..e4e1472
--- /dev/null
+++ b/Code/Sys/File/ngc/hed.h
@@ -0,0 +1,47 @@
+/*	Header file functionality...
+	.Hed files that describe the contents of .Wad files
+	Written by Ken, stolen by Matt*/
+#ifndef __HED_H__
+#define __HED_H__
+
+namespace File
+{
+
+struct SHedFile
+{
+	// A SECTOR_SIZE aligned offset of a file within skate3.wad
+	uint32 Offset;
+	
+	// The file size, which is the raw file size, not rounded up
+	// to a multiple of SECTOR_SIZE
+	uint32 FileSize;
+	
+	// Filename checksum (does not include directory).
+	uint32 Checksum;
+};
+
+struct SHed
+{
+	// Number of files in this directory.
+	uint32 numFiles;
+	
+	// Checksum of this directory.
+	uint32 Checksum;
+
+	// Pointer to File list.
+	SHedFile * p_fileList;
+
+	// The filename, which is actually bigger than one byte, tee hee.
+//	const char pFilename[1];
+};
+
+
+SHedFile *FindFileInHed(const char *pFilename, SHed *pHed );
+SHedFile *FindFileInHedUsingChecksum( uint32 checksum, SHed *pHed );
+SHed *LoadHed( const char *filename );
+
+} // namespace File
+
+#endif
+
+
diff --git a/Code/Sys/File/ngc/p_AsyncFilesys.cpp b/Code/Sys/File/ngc/p_AsyncFilesys.cpp
new file mode 100644
index 0000000..490455a
--- /dev/null
+++ b/Code/Sys/File/ngc/p_AsyncFilesys.cpp
@@ -0,0 +1,78 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// p_AsyncFilesys.cpp		GRJ 11 Oct 2002
+//
+// Asynchronous file system
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+
+#include 
+
+namespace File
+{
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void				CAsyncFileLoader::s_plat_init()
+{
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void				CAsyncFileLoader::s_plat_cleanup()
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::s_plat_async_supported()
+{
+	// Not implemented yet
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool				CAsyncFileLoader::s_plat_exist(const char *filename)
+{
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::s_plat_update(void)
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void				CAsyncFileLoader::s_plat_swap_callback_list()
+{
+}
+
+}
+
+
+
+
diff --git a/Code/Sys/File/ngc/p_asyncFilesys.h b/Code/Sys/File/ngc/p_asyncFilesys.h
new file mode 100644
index 0000000..065ff0e
--- /dev/null
+++ b/Code/Sys/File/ngc/p_asyncFilesys.h
@@ -0,0 +1,65 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			File													**
+**																			**
+**	Created:		10/11/02	-	grj										**
+**																			**
+**	File name:		core/sys/p_AsyncFilesys.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_FILE_P_ASYNC_FILESYS_H
+#define	__SYS_FILE_P_ASYNC_FILESYS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace File
+
+#endif  // __SYS_FILE_P_ASYNC_FILESYS_H
+
diff --git a/Code/Sys/File/ngc/p_filesys.cpp b/Code/Sys/File/ngc/p_filesys.cpp
new file mode 100644
index 0000000..ee23b20
--- /dev/null
+++ b/Code/Sys/File/ngc/p_filesys.cpp
@@ -0,0 +1,572 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			File IO (File) 											**
+**																			**
+**	File name:		p_filesys.cpp											**
+**																			**
+**	Created by:		09/25/00	-	dc										**
+**																			**
+**	Description:	XBox File System										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace File
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define CDROMx   // Use this define to get CDROM behavior
+
+#define RWGDFSGLOBAL(var)	(RWPLUGINOFFSET(gdfsGlobals, RwEngineInstance, gdfsModuleInfo.globalsOffset)->var)
+
+#define	PREPEND_START_POS	8
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+//#define READBUFFERSIZE (10240*5)
+
+// GJ:  I added this to track how many skyFiles are active
+// at any given time.  I am assuming that there's only one,
+// in which case I can just create one global instance
+// (previously, we were allocating it off the current heap,
+// which was causing me grief during the CAS loading process)
+//int g_fileOpenCount = 0;
+
+struct skyFile
+{
+	
+	// the following must match the dumbSkyFile struct
+	// in pre.cpp, or else pre file i/o won't work properly
+	int				gdfs;
+	int32			POS;
+	int32			SOF;
+
+	// the rest is skyFile-specific
+//	uint8			readBuffer[READBUFFERSIZE];
+	uint32		bufferPos;
+	bool			bufferValid;
+	bool			locked;			// whether the skyfile is currently in use
+	
+	// Used when CD
+	uint32			WadOffset;
+	const char		*pFilename;
+	
+	// Used when non-CD
+	#define MAX_PFILESYS_NAME_SIZE 100
+	char filename[ MAX_PFILESYS_NAME_SIZE ];
+
+	NsFile ngcFile;
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+//static	RwFileFunctions	s_old_fs;
+//static	RwInt32			s_open_files = 0;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/* prefopen														  */
+/* Same as fopen() except it will prepend the data root directory */
+/* For Xbox, path is always relative to location of .xbe image	  */
+/* and default path from that location is d:\					  */
+/* So incoming files of the form:								  */
+/* c:\skate3\data\foo...	become	d:\data\foo...				  */
+/* foo...					become	d:\data\foo...				  */
+/*                                                                */
+/******************************************************************/
+
+//static void* prefopen( const char *filename, const char *mode )
+//{
+//	// Used for prepending the root data directory on filesystem calls.
+//	char		nameConversionBuffer[256] = "d:\\data\\";
+//	int			index = PREPEND_START_POS;
+//	const char*	p_skip;
+//
+//	if(( filename[0] == 'c' ) && ( filename[1] == ':' ))
+//	{
+//		// Filename is of the form c:\skate3\data\foo...
+//		p_skip = filename + 15;
+//	}
+//	else
+//	{
+//		// Filename is of the form foo...
+//		p_skip = filename;
+//	}
+//
+//	while( nameConversionBuffer[index] = *p_skip )
+//	{
+//		// Switch forward slash directory separators to the supported backslash.
+//		if( nameConversionBuffer[index] == '/' )
+//		{
+//			nameConversionBuffer[index] = '\\';
+//		}
+//		++index;
+//		++p_skip;
+//	}
+//	nameConversionBuffer[index] = 0;
+//
+//	// Final hack - if this is a .tex file, switch to a .txx file.
+//	if((( nameConversionBuffer[index - 1] ) == 'x' ) &&
+//	   (( nameConversionBuffer[index - 2] ) == 'e' ) &&
+//	   (( nameConversionBuffer[index - 3] ) == 't' ))
+//	{
+//		nameConversionBuffer[index - 2] = 'x';
+//	}
+//
+////	return fopen( nameConversionBuffer, mode );
+//
+//	HANDLE h_file = CreateFile( nameConversionBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
+//	return ( h_file == INVALID_HANDLE_VALUE ) ? NULL : h_file;
+//}
+
+
+// At first, I assumed that only one skyfile could be open at
+// a time, but that's not true when testing streams from the
+// PC build.
+// pc builds need to have two (one for normal files, and one for streams)
+// GJ:  for THPS4, we increased this to 3, so that we can play multiple streams
+const int MAXOPENFILES = 3;
+
+// Ken note: It used to be that MAXOPENFILES was defined to be 1 when __NOPT_CDROM__OLD
+// was set. Now that __NOPT_CDROM__OLD is not used anymore and CD() is used at runtime
+// instead, I made MAXOPENFILES always 2. Then, where MAXOPENFILES used to be used in the code
+// it now uses a var set to 1 or 2 depending on CD()
+static skyFile g_skyFile[MAXOPENFILES];
+
+skyFile* lock_skyfile( void )
+{
+	// cd builds can only have one file open at any given time
+	int max_open_files=MAXOPENFILES;
+	
+	int i;
+	// this tries to find an unlocked skyfile
+	for ( i = 0; i < max_open_files; i++ )
+	{
+		if ( !g_skyFile[i].locked )
+		{
+			g_skyFile[i].locked = true;
+			g_skyFile[i].ngcFile.m_FileOpen			= 0; 
+			g_skyFile[i].ngcFile.m_sizeCached		= 0; 
+			g_skyFile[i].ngcFile.m_numCacheBytes	= 0; 
+			g_skyFile[i].ngcFile.m_seekOffset		= 0;
+			g_skyFile[i].ngcFile.m_preHandle		= NULL;
+			g_skyFile[i].ngcFile.m_unique_tag		= 0x1234ABCD;
+			return &g_skyFile[i];
+		}
+	}
+
+	Dbg_Message( "Here are the files currently open:" );
+	for ( i = 0; i < max_open_files; i++ )
+	{
+		Dbg_Message( "%s", g_skyFile[ i ].filename );
+	}	
+
+	// if we get here, that means that all the sky files were locked
+	Dbg_MsgAssert( 0, ( "Trying to open too many files simultaneously (max=%d)", max_open_files ) );
+	return NULL;
+}
+
+void unlock_skyfile( skyFile* pSkyFile )
+{
+	// cd builds can only have one file open at any given time
+	int max_open_files=MAXOPENFILES;
+	
+
+	// this tries to find the sky file that the caller is referencing
+	for ( int i = 0; i < max_open_files; i++ )
+	{
+		if ( pSkyFile == &g_skyFile[i] )
+		{
+			Dbg_MsgAssert( g_skyFile[i].locked, ( "Trying to unlock a sky file too many times" ) );
+			g_skyFile[i].locked = false;
+			return;
+		}
+	}
+
+	// if we get here, that means that the pointer didn't match one of the valid skyfiles
+	Dbg_MsgAssert( 0, ( "Unrecognized sky file" ) );
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+long GetFileSize( void* pFP )
+{
+	Dbg_MsgAssert(pFP,("NULL pFP sent to GetFileSize"));
+
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_get_file_size((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	skyFile* fp = (skyFile*)pFP;
+	return fp->ngcFile.size();
+}
+
+long GetFilePosition(void *pFP)
+{
+	
+	Dbg_MsgAssert(pFP,("NULL pFP sent to GetFilePosition"));
+
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_get_file_position((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	skyFile* fp = (skyFile*)pFP;
+	return fp->ngcFile.tell();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void InstallFileSystem( void )
+{
+/*
+	RwFileFunctions*	fs = RwOsGetFileInterface();
+	Dbg_MsgAssert( fs,( "File System not found" ));
+	
+	s_old_fs = *fs;    
+    
+	fs->rwfexist	= fexist;			
+	fs->rwfopen		= (rwFnFopen)prefopen;
+	fs->rwfclose	= (rwFnFclose)fclose;
+	fs->rwfread		= (rwFnFread)fread;
+	fs->rwfwrite	= (rwFnFwrite)fwrite;
+	fs->rwfgets		= (rwFnFgets)fgets;
+	fs->rwfputs		= (rwFnFputs)fputs;
+	fs->rwfeof		= (rwFnFeof)feof;
+	fs->rwfseek		= (rwFnFseek)fseek;
+	fs->rwfflush	= (rwFnFflush)fflush;
+	fs->rwftell		= (rwFnFtell)ftell;
+*/
+}
+
+void InitQuickFileSystem( void )
+{
+}
+
+uint32	CanFileBeLoadedQuickly( const char* filename )
+{
+	return 0;
+}
+
+bool LoadFileQuicklyPlease( const char* filename, uint8 *addr )
+{
+	return false;
+}
+
+void StopStreaming( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void UninstallFileSystem( void )
+{
+/*
+	RwFileFunctions*	fs = RwOsGetFileInterface();
+
+	Dbg_MsgAssert( fs,( "File System not found" ));
+
+	*fs = s_old_fs;		// restore old
+*/
+
+}
+
+
+
+////////////////////////////////////////////////////////////////////
+// Our versions of the ANSI file IO functions.  They call
+// the PreMgr first to see if the file is in a PRE file.
+////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////
+// Exist                                                          
+bool Exist( const char *filename )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        bool retval = PreMgr::pre_fexist(filename);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	NsFile f;
+	return f.exist( filename );
+}
+
+////////////////////////////////////////////////////////////////////
+// Open                                                          
+void * Open( const char *filename, const char *access )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        void * retval = PreMgr::pre_fopen(filename, access);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	skyFile *fp = lock_skyfile();
+	if ( fp )
+	{
+		strcpy( fp->filename, filename );
+	
+		if ( fp->ngcFile.open( filename ) )
+		{
+			fp->SOF = fp->ngcFile.size();
+	
+			return (void *)fp;
+		}
+		else
+		{
+			unlock_skyfile( fp );
+			return NULL;
+		}
+	}
+	else
+	{
+		return NULL;
+	}
+}
+
+////////////////////////////////////////////////////////////////////
+// Close                                                          
+int Close( void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fclose((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	skyFile* fp = (skyFile*)pFP;
+	fp->ngcFile.close();
+
+	unlock_skyfile( fp );
+	return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+// Read                                                          
+size_t Read( void *addr, size_t size, size_t count, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        size_t retval = PreMgr::pre_fread(addr, size, count, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	skyFile* fp = (skyFile*)pFP;
+	return fp->ngcFile.read( addr, size * count ); 
+}
+
+
+
+
+///////////////////////////////////////////////////////////////////////
+// Read an Integer in PS2 (littleendian) format
+// we just read it directly into memory...
+size_t ReadInt( void *addr, void *pFP )
+{
+	return Read(addr,4,1,pFP);	
+}
+
+
+
+
+////////////////////////////////////////////////////////////////////
+// Write                                                          
+size_t Write( const void *addr, size_t size, size_t count, void *pFP )
+{
+//    if (PreMgr::sPreEnabled())
+//    {
+//        size_t retval = PreMgr::pre_fwrite(addr, size, count, (PreFile::FileHandle *) pFP);
+//        if (PreMgr::sPreExecuteSuccess())
+//            return retval;
+//    }
+//
+////    return skyFwrite(addr, size, count, pFP);
+	return 0;
+}
+
+
+
+////////////////////////////////////////////////////////////////////
+// GetS                                                          
+char * GetS( char *buffer, int maxlen, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        char * retval = PreMgr::pre_fgets(buffer, maxlen, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	skyFile* fp = (skyFile*)pFP;
+	return fp->ngcFile.gets( buffer, maxlen ); 
+}
+
+////////////////////////////////////////////////////////////////////
+// PutS                                                          
+int PutS( const char *buffer, void *pFP )
+{
+//    if (PreMgr::sPreEnabled())
+//    {
+//        int retval = PreMgr::pre_fputs(buffer, (PreFile::FileHandle *) pFP);
+//        if (PreMgr::sPreExecuteSuccess())
+//            return retval;
+//    }
+//
+////    return skyFputs(buffer, pFP);
+	return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+// Eof                                                          
+int Eof( void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_feof((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	skyFile* fp = (skyFile*)pFP;
+	return fp->ngcFile.eof();
+}
+
+////////////////////////////////////////////////////////////////////
+// Seek                                                          
+int Seek( void *pFP, long offset, int origin )
+{
+	if( PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fseek((PreFile::FileHandle *) pFP, offset, origin);
+		if( PreMgr::sPreExecuteSuccess())
+			return retval;
+	}
+	skyFile* fp = (skyFile*)pFP;
+	switch ( origin ) {
+		case SEEK_SET:
+			fp->ngcFile.seek( NsFileSeek_Start, offset );
+			break;
+		case SEEK_END:
+			fp->ngcFile.seek( NsFileSeek_End, offset );
+			break;
+		case SEEK_CUR:
+		default:
+			fp->ngcFile.seek( NsFileSeek_Current, offset );
+			break;
+	}
+	return fp->ngcFile.tell();
+}
+
+int32	Tell( void* pFP )
+{
+	skyFile* fp = (skyFile *) pFP;
+	return fp->POS;
+}
+
+
+
+////////////////////////////////////////////////////////////////////
+// Flush                                                          
+int Flush( void *pFP )
+{
+//    if (PreMgr::sPreEnabled())
+//    {
+//        int retval = PreMgr::pre_fflush((PreFile::FileHandle *) pFP);
+//        if (PreMgr::sPreExecuteSuccess())
+//            return retval;
+//    }
+//
+////    return skyFflush(pFP);
+	return 0;
+}
+
+
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace File
+
+
+
diff --git a/Code/Sys/File/ngc/p_pre.cpp b/Code/Sys/File/ngc/p_pre.cpp
new file mode 100644
index 0000000..9729fd7
--- /dev/null
+++ b/Code/Sys/File/ngc/p_pre.cpp
@@ -0,0 +1,1038 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		skate3													**
+**																			**
+**	Module:									 								**
+**																			**
+**	File name:																**
+**																			**
+**	Created by:		rjm														**
+**																			**
+**	Description:						 									**
+**																			**
+*****************************************************************************/
+
+// start autoduck documentation
+// @DOC pre
+// @module pre | None
+// @subindex Scripting Database
+// @index script | pre
+                         
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+//#include 
+#include 
+#include 
+#include 
+#include 
+
+// cd shared by the music streaming stuff...  ASSERT if file access attempted
+// while music is streaming:
+#include 
+
+// script stuff
+#include  
+#include 
+
+#include 
+
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+#define DEBUG_PRE 0
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define CURRENT_PRE_VERSION	0xabcd0002			// as of 3/14/2001
+//#define CURRENT_PRE_VERSION	0xabcd0001		// until 3/14/2001
+
+#define RINGBUFFERSIZE		 4096	/* N size of ring buffer */	
+#define MATCHLIMIT		   18	/* F upper limit for match_length */
+#define THRESHOLD	2   /* encode string into position and length */
+
+#define WriteOut(x) 	{*pOut++ = x;}
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+PreMgr *PreMgr::sp_mgr = NULL;
+bool    PreMgr::s_lastExecuteSuccess = false; 
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+
+
+#define USE_BUFFER	1		 // we don't need no stinking buffer!!!!
+ 
+#if USE_BUFFER
+#ifdef	__PLAT_NGPS__
+// ring buffer is just over 4K 4096+17), 
+// so fits nicely in the PS2's 8K scratchpad 
+unsigned char	text_buf[RINGBUFFERSIZE + MATCHLIMIT - 1];	
+// Note:  if we try to use the scratchpad, like this
+// then the code actually runs slower
+// if we want to optimize this, then it should
+// be hand crafted in assembly, using 128bit registers
+//	const unsigned char * text_buf = (unsigned char*) 0x70000000;
+#else
+unsigned char
+		text_buf[RINGBUFFERSIZE + MATCHLIMIT - 1];	/* ring buffer of size N,
+			with extra F-1 bytes to facilitate string comparison */
+#endif
+#endif
+
+
+#define	ReadInto(x)		if (!Len) break; Len--; x = *pIn++ 
+#define	ReadInto2(x)	Len--; x = *pIn++ 	  // version that knows Len is Ok
+
+
+// Decode an LZSS encoded stream
+// Runs at approx 12MB/s on PS2	 without scratchpad (which slows it down in C)
+// a 32x CD would run at 4.8MB/sec, although we seem to get a lot less than this
+// with our current file system, more like 600K per seconds.....
+// Need to write a fast streaming file system....
+
+void DecodeLZSS(unsigned char *pIn, unsigned char *pOut, int Len)	/* Just the reverse of Encode(). */
+{
+	int  i, j, k, r, c;
+//	uint64	LongWord;
+//	int bytes = 0;
+//	unsigned char *pScratch;
+	unsigned int  flags;
+
+
+
+//	int basetime =  (int) Tmr::ElapsedTime(0);
+//	int len = Len;
+
+//	int	OutBytes = 4;
+//	int	OutWord = 0;
+
+	#if USE_BUFFER
+	for (i = 0; i < RINGBUFFERSIZE - MATCHLIMIT; i++)
+		 text_buf[i] = ' ';
+	r = RINGBUFFERSIZE - MATCHLIMIT;
+	#else
+	r = RINGBUFFERSIZE - MATCHLIMIT;
+	#endif
+	flags = 0;
+	for ( ; ; )
+	{
+		if (((flags >>= 1) & 256) == 0)
+		{
+			ReadInto(c);
+			flags = c | 0xff00;			/* uses higher byte cleverly */
+		}										/* to count eight */
+		if (flags & 1)
+		{
+			ReadInto(c);
+			//			putc(c, outfile);
+			WriteOut(c);
+			#if USE_BUFFER
+			text_buf[r++] = c;
+			r &= (RINGBUFFERSIZE - 1);
+			#else
+			r++;
+//			r &= (RINGBUFFERSIZE - 1);	  // don't need to wrap r until it is used
+			#endif
+		}
+		else
+		{
+			ReadInto(i);			
+			ReadInto2(j);			// note, don't need to check len on this one.... 
+			
+			i |= ((j & 0xf0) << 4);						// i is 12 bit offset
+			
+			#if !USE_BUFFER
+			j = (j & 0x0f) + THRESHOLD+1;				// j is 4 bit length (above the threshold)
+			unsigned char *pStream;
+			r &= (RINGBUFFERSIZE - 1);					// wrap r around before it is used
+			pStream = pOut - r;					  		// get base of block
+			if (i>=r)										// if offset > r, then
+				pStream -= RINGBUFFERSIZE;				// it's the previous block
+			pStream += i;									// add in the offset to the base
+			r+=j;												// add size to r
+			while (j--)										// copy j bytes
+				WriteOut(*pStream++);
+			#else
+			
+			j = (j & 0x0f) + THRESHOLD;				// j is 4 bit length (above the threshold)
+			for (k = 0; k <= j; k++)					// just copy the bytes
+			{
+				c =  text_buf[(i+k) & (RINGBUFFERSIZE - 1)]; 
+				WriteOut(c);
+				text_buf[r++] = c;
+				r &= (RINGBUFFERSIZE - 1);
+			}
+			#endif
+		}
+	}
+//	int Time = (int) Tmr::ElapsedTime(basetime);
+//	if (Time > 5)
+//	{
+//		printf("decomp time is %d ms, for %d bytes,  %d bytes/second\n", Time,len, len * 1000 /Time );
+//	}
+
+}
+
+
+
+
+void PreFile::s_delete_file(_File *pFile, void *pData)
+{
+	Dbg_Assert(pFile);
+	delete pFile;
+}
+
+
+
+
+PreFile::PreFile(uint8 *p_file_buffer)
+{
+	mp_table = new Lst::StringHashTable<_File>(4);	
+
+	Dbg_AssertPtr(p_file_buffer);
+
+	mp_buffer = p_file_buffer;
+	#ifdef __NOPT_ASSERT__
+	uint version = 	*((int *) (mp_buffer + 4));
+	Dbg_MsgAssert(version == CURRENT_PRE_VERSION,( "PRE file version (%x) not current (%x)",version,CURRENT_PRE_VERSION));
+	#endif
+	m_numEntries = *((int *)(mp_buffer + 8));
+
+	uint8 *pEntry = mp_buffer + 12;
+	for (int i = 0; i < m_numEntries; i++)
+	{
+		int data_size 				= *((int *) pEntry);
+		int compressed_data_size 	= *((int *) (pEntry + 4));
+		int text_size 				= *((int *) (pEntry + 8));
+		int actual_data_size = (compressed_data_size != 0) ? compressed_data_size : data_size;
+			
+		char *pName = (char *) pEntry + 12;
+		uint8 *pCompressedData = pEntry + 12 + text_size;
+		
+		_File *pFile = new (Mem::Manager::sHandle().TopDownHeap()) _File;
+
+		if (!mp_table->GetItem(pName)) 
+		{
+			// file is not in table, safe to add
+			mp_table->PutItem(pName, pFile);
+			
+			pFile->compressedDataSize = compressed_data_size;
+			pFile->pCompressedData = pCompressedData; 
+			pFile->pData = NULL;
+			pFile->sky.POS = 0;
+			pFile->sky.SOF = data_size;
+		}
+		else
+			// Somehow, file is already in table, just kill it
+			// Later, I'll want to add an assert
+			delete pFile;
+		
+#		if DEBUG_PRE
+		printf("   %s, size %d\n", pName, data_size);
+#		endif
+		
+		pEntry += 12 + text_size + ((actual_data_size + 3) & (~3));
+	}
+
+#	if DEBUG_PRE
+	printf("Done loading PRE\n");
+#	endif
+	
+	mp_activeFile = NULL;
+}
+
+
+
+PreFile::~PreFile()
+{
+	delete mp_buffer;
+	mp_table->HandleCallback(s_delete_file, NULL);
+	mp_table->FlushAllItems();
+
+	delete mp_table;
+}
+
+
+
+// returns handle pointer
+void *PreFile::GetContainedFile(const char *pName)
+{
+	
+	_File *pFile = mp_table->GetItem(pName, false);
+	if (!pFile) 
+		return NULL;
+	
+	dumbSkyFile *pHandle = &pFile->sky;
+	// kinda roundabout, but sets mp_activeFile
+	GetContainedFileByHandle(pHandle);
+	
+	// do we need to fetch file data?
+	if (!mp_activeFile->pData)
+	{
+		if (mp_activeFile->compressedDataSize)
+		{
+			mp_activeFile->pData = new (Mem::Manager::sHandle().TopDownHeap()) uint8[mp_activeFile->sky.SOF];		
+			// need to uncompress data
+			DecodeLZSS(mp_activeFile->pCompressedData, mp_activeFile->pData, mp_activeFile->compressedDataSize);	
+		}
+	}
+
+	return pHandle;
+}
+	
+
+
+uint8 *PreFile::GetContainedFileByHandle(void *pHandle)
+{
+	mp_table->IterateStart();
+	_File *pFile = mp_table->IterateNext();
+	while(pFile)
+	{
+		uint8 *pCompressedData = pFile->pCompressedData;
+		if (pCompressedData && &pFile->sky == pHandle)
+		{
+			mp_activeFile = pFile;
+			
+			if (mp_activeFile->compressedDataSize)
+				return mp_activeFile->pData;
+			else
+				return mp_activeFile->pCompressedData;
+		}
+		pFile = mp_table->IterateNext();
+	}
+
+	return NULL;
+}
+
+
+
+void PreFile::Reset()
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	mp_activeFile->sky.POS = 0;
+}
+
+
+
+uint32 PreFile::Read(void *addr, uint32 count)
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	int seek_offs = mp_activeFile->sky.POS;
+	unsigned int limit = mp_activeFile->sky.SOF - seek_offs;
+	int copy_number = (count <= limit) ? count : limit; 
+	if (mp_activeFile->compressedDataSize)
+	{
+		Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed"));
+		memcpy(addr, mp_activeFile->pData + mp_activeFile->sky.POS, copy_number);
+	}
+	else
+	{
+		memcpy(addr, mp_activeFile->pCompressedData + mp_activeFile->sky.POS, copy_number);
+	}
+
+	mp_activeFile->sky.POS += copy_number;
+
+#if DEBUG_PRE
+		printf("PRE: read %d bytes from file, handle 0x%x\n", copy_number, (int) mp_activeFile->pData);
+#endif
+	return copy_number;
+}
+
+
+
+int PreFile::Eof()
+{
+	
+	Dbg_AssertPtr(mp_activeFile);
+
+	if (mp_activeFile->sky.POS >= mp_activeFile->sky.SOF)
+	{
+#if DEBUG_PRE
+		printf("PRE: at end of file\n");
+#endif
+		return 1;
+	}
+
+#if DEBUG_PRE
+	printf("PRE: not at end of file\n");
+#endif
+	return 0;
+}
+
+
+
+void PreFile::Close()
+{
+	
+	//Dbg_MsgAssert(mp_activeFile->pData,( "file not uncompressed"));
+
+	if (mp_activeFile->pData)
+		delete mp_activeFile->pData;
+	mp_activeFile->pData = NULL;
+}
+
+
+
+int PreFile::Seek(long offset, int origin)
+{
+	int32 old_pos = mp_activeFile->sky.POS;
+
+	// SEEK_CUR, SEEK_END, SEEK_SET
+	switch(origin)
+	{
+		case SEEK_CUR:
+			mp_activeFile->sky.POS += offset;
+			break;
+		case SEEK_END:
+			mp_activeFile->sky.POS = mp_activeFile->sky.SOF - offset;
+			break;
+		case SEEK_SET:
+			mp_activeFile->sky.POS = offset;
+			break;
+		default:
+			return -1;
+	}
+
+	if (mp_activeFile->sky.POS < 0 || mp_activeFile->sky.POS > mp_activeFile->sky.SOF)
+	{
+		mp_activeFile->sky.POS = old_pos;
+		return -1;
+	}
+
+	return 0;
+}
+
+
+
+PreMgr::PreMgr() 
+{
+	mp_table = new Lst::StringHashTable(4);
+
+	sp_mgr = this;
+
+
+	mp_activeHandle = NULL;
+	mp_activeData = NULL;
+
+	mp_activeNonPreHandle = NULL;
+}
+
+
+
+PreMgr::~PreMgr()
+{
+	delete mp_table;
+}
+
+
+
+// Returns handle
+// Not frequently called
+void *PreMgr::getContainedFile(const char *pName)
+{
+	
+	Dbg_AssertPtr(pName);
+
+	// replace all '/' with '\'
+	char cleaned_name[128];
+	const char *pCharIn = pName;
+	char *pCharOut = cleaned_name;
+	while (1)
+	{
+		*pCharOut = *pCharIn;
+		if (*pCharIn == '\0') break;
+		if (*pCharOut == '/') *pCharOut = '\\';
+		pCharIn++;
+		pCharOut++;		
+	}
+
+	void *pHandle = NULL;
+
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		pHandle = pPre->GetContainedFile(cleaned_name);
+		if (pHandle) 
+		{
+			mp_activePre = pPre;
+			mp_activeHandle = pHandle;
+			mp_activeData = pPre->GetContainedFileByHandle(pHandle);
+#			ifdef __PLAT_NGPS__
+			scePrintf("+++ %s is in PRE\n", cleaned_name);
+#			endif
+			return pHandle;
+		}
+		pPre = mp_table->IterateNext();
+	}
+
+#	ifdef __PLAT_NGPS__
+	scePrintf("--- %s not found in PRE\n", cleaned_name);
+#	endif
+	return NULL;
+}
+
+
+
+// returns pointer to data
+uint8 *PreMgr::getContainedFileByHandle(void *pHandle)
+{
+	
+	Dbg_AssertPtr(pHandle);
+
+	// if we know that the file in question is not in the PRE system,
+	// then it's a regular file, don't waste time looking for it
+	if (mp_activeNonPreHandle == pHandle)
+		return NULL;
+	
+	if (mp_activeHandle == pHandle)
+		// mp_activePre will be unchanged
+		return mp_activeData;
+	
+	uint8 *pData = NULL;
+	mp_table->IterateStart();
+	PreFile *pPre = mp_table->IterateNext();
+	while(pPre)
+	{
+		pData = pPre->GetContainedFileByHandle(pHandle);
+		if (pData)
+		{
+			mp_activePre = pPre;
+			mp_activeHandle = pHandle;
+			mp_activeData = pData;			
+			return pData;
+		}
+		pPre = mp_table->IterateNext();
+	}
+
+	// obviously this file is not in the PRE system, mark as such
+	mp_activeNonPreHandle = pHandle;
+	return NULL;
+}
+
+
+
+// there's a wrapper around this now, so that we can do
+// some memory-context switching
+void PreMgr::loadPre(const char *pFilename, bool dont_assert)
+{
+	
+
+#	ifdef __PLAT_XBOX__
+	// Do nothing.
+	return;
+#	endif
+
+
+
+	m_blockPreLoading = (bool) Script::GetInt("block_pre_loading", false);
+
+	if (m_blockPreLoading)
+		return;
+
+	if( Pcm::UsingCD() )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return;
+	}
+
+	// Moved this to below the Pcm::UsingCD() call as that is used (bad!!) to turn off
+	// music and streams.
+#	ifdef __PLAT_NGPS__
+//	scePrintf("Loading PRE file %s...\n", pFilename);
+#	endif
+
+	char fullname[256];
+	sprintf(fullname, "pre\\%s", pFilename);
+
+	Tmr::Time basetime = Tmr::ElapsedTime(0);
+
+
+	int file_size;
+	uint8 *pFile = NULL;
+
+	file_size = CanFileBeLoadedQuickly( fullname );
+	if ( file_size )
+	{
+		pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size];
+		bool fileLoaded= LoadFileQuicklyPlease( fullname, pFile );
+		if ( !fileLoaded )
+		{
+			printf( "pre file %s failed to load quickly.\n", fullname );
+			Dbg_MsgAssert( 0,( "Fire Matt - pre file didn't load quickly." ));
+		}
+	}
+	else
+	{
+			void *fp = File::Open(fullname, "rb");
+			if (!fp)
+			{
+			// always run the code below if CD build
+				if (dont_assert || Config::CD()) 
+				{
+					printf("couldn't open %s\n", fullname);
+					return;
+				}
+				Dbg_MsgAssert(0,( "couldn't open %s\n", fullname));
+			}
+
+
+			File::Read(&file_size, 4, 1, fp);
+			Dbg_MsgAssert(file_size > 0,( "%s has incorrect file size\n", fullname));
+			if (Config::CD())
+			{
+				if (file_size <= 0) printf("%s has incorrect file size\n", fullname);
+			}	
+
+
+		// Now allocates the .PRE file from the top of the heap, to avoid fragmentation.
+			pFile = new (Mem::Manager::sHandle().TopDownHeap()) uint8[file_size];
+		//uint8 *pFile = new uint8[file_size];
+			File::Read(pFile + 4, 1, file_size, fp);
+			File::Close(fp);
+		}
+
+		printf("load time for file size %d is %d ms\n", file_size, (int) Tmr::ElapsedTime(basetime));
+
+	// the PRE file object winds up at the top of the heap, too. This is fine because
+	// it will be unloaded at the same time as the big file buffer
+		if (!mp_table->PutItem(pFilename, new (Mem::Manager::sHandle().TopDownHeap()) PreFile(pFile)))
+			Dbg_MsgAssert(0,( "PRE %s loaded twice", pFilename));
+}
+
+
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+
+DefineSingletonClass(PreMgr, "PRE Manager");
+
+
+
+bool PreMgr::InPre(const char *pFilename)
+{
+	
+
+	return mp_table->GetItem( pFilename );
+}
+
+
+
+void PreMgr::LoadPre(const char *pFilename, bool dont_assert)
+{
+	// GJ:  This function is a wrapper around loadPRE(), to
+	// make sure that all allocations go on the top-down heap
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	loadPre(pFilename, dont_assert);
+	Mem::Manager::sHandle().PopContext();	
+}
+
+
+
+void PreMgr::LoadPrePermanently(const char *pFilename, bool dont_assert)
+{
+	// GJ:  This function is a wrapper around LoadPRE(),
+	// used for loading PRE files which will reside in memory
+	// permanently.  Essentially, we need to make sure that the
+	// data is above Renderware's resource arena.  If you didn't
+	// use this function, you'd get fragmentation when the resource
+	// arena resized itself.
+
+	// Mick: Removed reference to renderware, the old "arena" concept is gone
+
+	
+	// Load the pre file...
+	// This will go on the top-down heap by default
+	LoadPre(pFilename, dont_assert);
+
+}
+
+
+
+void PreMgr::UnloadPre(const char *pFilename, bool dont_assert)
+{
+	
+
+#	ifdef __PLAT_NGPS__
+//	scePrintf("Unloading PRE file %s\n", pFilename);
+#	endif
+	
+	if (m_blockPreLoading)
+		return;
+	
+	PreFile *pThePre = mp_table->GetItem(pFilename);
+	if (!pThePre)
+	{
+		if (dont_assert) return;
+		Dbg_MsgAssert(0,( "PRE file %s not in PRE manager", pFilename));
+	}
+
+	mp_table->FlushItem(pFilename);
+	delete pThePre;
+}
+
+
+bool PreMgr::sPreEnabled()
+{
+	return sp_mgr != NULL;
+}
+
+bool PreMgr::sPreExecuteSuccess()
+{
+	return s_lastExecuteSuccess; 
+}
+
+bool PreMgr::pre_fexist(const char *name)
+{
+	
+	Dbg_MsgAssert(name,( "requesting file NULL"));	
+	
+	if (sp_mgr->getContainedFile(name)) 
+	{
+#		if DEBUG_PRE
+		printf("PRE: file %s exists\n", name);
+#		endif
+		s_lastExecuteSuccess = true;
+		return true;
+	}
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return false;
+	}
+
+	return s_lastExecuteSuccess = false;
+}
+
+
+
+void *PreMgr::pre_fopen(const char *name, const char *access)
+{
+		
+	Dbg_MsgAssert(name,( "trying to open file NULL"));	
+
+	void *pHandle = sp_mgr->getContainedFile(name);
+	if (pHandle)
+	{
+		// if we are going to write the file, we want to use the regular file system
+		const char *pChar = access;
+		bool am_writing = false;
+		while(*pChar)
+		{
+			if (*pChar != 'r' && *pChar != 'b')
+				am_writing = true;
+			pChar++;
+		}
+		
+		if (am_writing)
+		{
+#			ifdef __PLAT_NGPS__
+//			scePrintf("    writing file %s\n", name);
+#			endif
+
+			// am writing, so we don't need file open in PRE system
+			sp_mgr->mp_activePre->Close();
+		}
+		else
+		{
+			// we're reading the file from the PRE system
+#			if DEBUG_PRE
+			printf("PRE: opened file %s, handle is 0x%x\n", name, (int) pHandle);
+#			endif
+			sp_mgr->mp_activePre->Reset();
+			s_lastExecuteSuccess = true;
+			return pHandle;
+		}
+	}
+
+	// if we get here, we are using the regular file system
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return NULL;
+	}
+
+	s_lastExecuteSuccess = false;
+	return NULL;
+
+	//return pHandle;
+}
+
+
+
+int PreMgr::pre_fclose(void *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling fclose with invalid file ptr"));	
+	
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+#if DEBUG_PRE
+		printf("PRE: closed file, handle 0x%x\n", (int) fptr);
+#endif
+		sp_mgr->mp_activePre->Close();
+		s_lastExecuteSuccess = true;
+		return 0;
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+size_t PreMgr::pre_fread(void *addr, size_t size, size_t count, void *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling fread with invalid file ptr"));		
+
+	uint8 *pData = sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		// read from a simulated file stream in PRE file
+		Dbg_AssertPtr(sp_mgr->mp_activePre);
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Read(addr, size * count);
+	}
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return 0;
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+size_t  PreMgr::pre_fwrite(const void *addr, size_t size, size_t count, void *fptr)
+{
+		
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't write to a PRE file"));
+	
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+		return 0;
+	}
+
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+char *PreMgr::pre_fgets(char *buffer, int maxLen, void *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return NULL;
+}
+
+
+
+int PreMgr::pre_fputs(const char *buffer, void *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "can't do string ops on a PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_feof(void *fptr)
+{
+		
+	Dbg_MsgAssert(fptr,( "calling feof with invalid file ptr"));		
+
+	uint8 *pData = 	sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		Dbg_AssertPtr(sp_mgr->mp_activePre);
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Eof();
+	}
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_fseek(void *fptr, long offset, int origin)
+{
+		
+
+	uint8 *pData = 	sp_mgr->getContainedFileByHandle(fptr);
+	if (pData)
+	{
+		s_lastExecuteSuccess = true;
+		return sp_mgr->mp_activePre->Seek(offset, origin);
+	}
+
+	Dbg_MsgAssert(!pData,( "seek not supported for PRE file"));
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_fflush(void *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "flush not supported for PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+int PreMgr::pre_ftell(void *fptr)
+{
+		
+
+	#ifdef __NOPT_ASSERT__
+	uint8 *pData = 
+	#endif
+	sp_mgr->getContainedFileByHandle(fptr);
+	Dbg_MsgAssert(!pData,( "tell supported for PRE file"));
+	
+	s_lastExecuteSuccess = false;
+	return 0;
+}
+
+
+
+// @script | InPreFile | 
+// @uparm "string" | filename
+bool ScriptInPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	Spt::SingletonPtr pre_mgr;
+	return pre_mgr->InPre(pFilename);
+}
+
+
+
+// @script | LoadPreFile | 
+// @uparm "string" | filename
+// @flag dont_assert | 
+bool ScriptLoadPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	Spt::SingletonPtr pre_mgr;
+	pre_mgr->LoadPre(pFilename, pParams->ContainsFlag("dont_assert"));
+	return true;
+}
+
+
+
+// @script | UnloadPreFile | 
+// @flag BoardsPre | 
+// @flag dont_assert | 
+// @uparm "string" | filename
+bool ScriptUnloadPreFile(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	
+
+	Spt::SingletonPtr pre_mgr;
+	
+	if (pParams->ContainsFlag("BoardsPre"))
+	{
+		
+//		Spt::SingletonPtr< Mdl::Skate >	pSkate;
+//		pSkate->UnloadBoardPreIfPresent(pParams->ContainsFlag("dont_assert"));
+		printf ("STUBBED:  Unload BoardsPre, in ScriptUnloadPreFile\n");
+		return true;
+	}
+	
+	const char *pFilename;
+	pParams->GetText(NONAME, &pFilename, true);
+
+	pre_mgr->UnloadPre(pFilename, pParams->ContainsFlag("dont_assert"));
+	return true;
+}
+
+
+
+} // namespace File
+
+
+
+
+
diff --git a/Code/Sys/File/ngps/FileIO/FIleIO_IOP.h b/Code/Sys/File/ngps/FileIO/FIleIO_IOP.h
new file mode 100644
index 0000000..001d656
--- /dev/null
+++ b/Code/Sys/File/ngps/FileIO/FIleIO_IOP.h
@@ -0,0 +1,137 @@
+
+// This file is included by the IOP side only.  Make sure all
+// changes are C compatible.
+
+#ifndef	__SYS_FILE_NGPS_FILEIO_IOP_H
+#define	__SYS_FILE_NGPS_FILEIO_IOP_H
+
+#include "fileio.h"
+
+
+#if 0
+#define PRINTF(x) printf x
+#else
+#define PRINTF(x) 
+#endif
+//#define ERROR(x) printf x
+#define ERROR(A...) Kprintf(##A); while(1)
+#define xPRINTF(x) 
+
+#define BASE_priority  32
+
+#define TRUE 1
+#define FALSE 0
+
+#define FILEIO_MAX_HANDLERS		(16)
+#define FILEIO_MAX_BUFFERS		((NUM_DEVICE_TYPES - 1) * 2)		// Don't need buffer for the Unknown device
+#define FILEIO_MAX_TASKS		(20)
+
+// Function enums (for callback)
+typedef enum
+{
+	FILEIO_FUNCTION_IDLE = 0,					// No function is being waited on
+	FILEIO_FUNCTION_OPEN,
+	FILEIO_FUNCTION_SEEK,
+	FILEIO_FUNCTION_READ,
+	FILEIO_FUNCTION_WRITE,
+} EFileIOFunction;
+
+// Device types
+typedef enum
+{
+	DEVICE_CD = 0,
+	DEVICE_HOST,
+	DEVICE_UNKNOWN,		// This is always the last device type (also where all requests start)
+	NUM_DEVICE_TYPES,
+} EDeviceType;
+
+typedef enum
+{
+	MEM_EE = 0,
+	MEM_IOP,
+} EMemoryType;
+
+// FileIOHandle Flags
+#define	FILEIO_HANDLE_BUSY		(0x0001)		// Handle currently busy
+#define	FILEIO_HANDLE_RESERVED	(0x0002)		// Handle currently allocated to someone
+#define	FILEIO_HANDLE_PREV_DMA	(0x0004)		// Already sent out DMA info (meaning m_last_dma_id is valid)
+#define	FILEIO_HANDLE_NOWAIT	(0x0008)		// Last function called finished immediately
+#define	FILEIO_HANDLE_EE_MEMORY	(0x0010)		// Destionion is EE memory
+
+// File handle
+typedef struct
+{
+	// General data
+	volatile int	m_flags;
+//	void *			mp_buffer[2];		// This is only needed for EE transfers
+//	int				m_buffer_index;		// This is only needed for EE transfers
+	void *			mp_dest_buffer;		// Could be either an EE or IOP address
+	int				m_buffer_size;
+	EDeviceType		m_device_type;
+	int				m_host_fd;			// File descriptor for host IO
+	int				m_priority;
+	int				m_stream;
+	int				m_start_sector;
+	int				m_cur_sector;
+	int				m_cur_position;
+	int				m_file_size;
+	int				m_open_request_id;
+	SFileIORequest *mp_blocked_request;	// If handle is marked BUSY but waiting for the device, this is the request that
+										// will execute when the device finally becomes free.
+
+	// Current IO variables
+	EFileIOFunction	m_cur_function;
+	int				m_bytes_to_process;
+	int				m_bytes_processing;
+	unsigned short	m_non_aligned_start_bytes;
+	unsigned short	m_non_aligned_end_bytes;
+	int				m_request_id;
+	int				m_return_value;
+	int				m_return_data;
+	unsigned int	m_last_dma_id;
+} SFileIOHandle;
+
+// Request task
+typedef struct FileIOTask
+{
+	SFileIORequestEntry 	m_entry;
+	SFileIOHandle *			mp_file_handle;
+	struct FileIOTask *		mp_cont;	// Continued packets for the same request
+	struct FileIOTask *		mp_next;
+} SFileIOTask;
+
+// Device status
+typedef struct
+{
+	volatile int	m_in_use;
+	volatile int	m_waiting_callback;
+	SFileIOHandle *	mp_file_handle;
+	void *			mp_buffer[2];		// This is only needed for EE transfers
+	int				m_buffer_index;		// This is only needed for EE transfers
+	int				m_cur_sector;
+	int				m_cur_position;
+	SFileIOTask *	mp_task_list;
+} SFileIODevice;
+
+
+// Request state
+typedef enum
+{
+	REQUEST_OPEN,			// Still waiting to be started
+	REQUEST_PROCESSING,		// Currently executing
+	REQUEST_APPEND,			// Needs appending to a current request
+	REQUEST_COMPLETE,		// Done
+} ERequestState;
+
+// Request Status
+typedef struct
+{
+	int				m_request_id;
+	ERequestState	m_request_state;
+	SFileIOHandle *	mp_file_handle;
+	int				m_return_value;
+	int				m_return_data;
+} SRequestStatus;
+
+#endif	// __SYS_FILE_NGPS_FILEIO_IOP_H
+
diff --git a/Code/Sys/File/ngps/FileIO/FileIO.h b/Code/Sys/File/ngps/FileIO/FileIO.h
new file mode 100644
index 0000000..45cdc12
--- /dev/null
+++ b/Code/Sys/File/ngps/FileIO/FileIO.h
@@ -0,0 +1,192 @@
+// This file is included by both the EE and IOP side.  Make sure all
+// changes are C compatible.
+
+#ifndef	__SYS_FILE_NGPS_FILEIO_H
+#define	__SYS_FILE_NGPS_FILEIO_H
+
+#include 
+
+#ifndef SECTOR_SIZE
+#define SECTOR_SIZE							( 2048 )
+#endif // SECTOR_SIZE
+
+// Make sure these don't conflict with the Stream IRX
+#define FILEIO_REQUEST_COMMAND				(1)
+#define FILEIO_RESULT_COMMAND				(1)
+#define FILEIO_NUM_COMMAND_HANDLERS 		(0x10)
+
+#define FILEIO_TEMP_HANDLE_FLAG				(0x80000000)
+
+#define FILEIO_MAX_FILENAME_SIZE			(80)
+
+#define FILEIO_BUFFER_SECTORS				(8)
+#define FILEIO_BUFFER_SIZE					(FILEIO_BUFFER_SECTORS * SECTOR_SIZE)
+
+#define FILEIO_CACHE_BLOCK_SIZE				(64)								// Cache block size for EE
+#define FILEIO_CACHE_BLOCK_NONALIGN_MASK	(FILEIO_CACHE_BLOCK_SIZE - 1)		// Masks out the non-aligned address bits
+#define FILEIO_CACHE_BLOCK_ALIGN_MASK		(~FILEIO_CACHE_BLOCK_NONALIGN_MASK)	// Masks out the aligned address bits
+
+// The FileIO commands
+typedef enum
+{
+	FILEIO_INIT = 0,
+	FILEIO_OPEN,
+	FILEIO_CLOSE,
+	FILEIO_READ,
+	FILEIO_WRITE,
+	FILEIO_SEEK,
+	FILEIO_SET,
+	FILEIO_CONT,
+} FileIOCommand;
+
+typedef enum
+{
+	FILEIO_VAR_PRIORITY,
+	FILEIO_VAR_BUFFER_SIZE,
+	FILEIO_VAR_STREAM,
+	FILEIO_VAR_DESTINATION,
+} FileIOVariable;
+
+typedef enum
+{
+	FILEIO_HOSTOPEN_FALSE = 0,
+	FILEIO_HOSTOPEN_TRUE,
+	FILEIO_HOSTOPEN_EXTEND_NAME,
+} FileIOHostOpen;
+
+////////////////////////////////////
+// Command parameters
+// All structures sizes + 4 bytes (for command itself) must be 16-byte aligned
+//
+
+// General Parameters
+typedef struct
+{
+	int				m_param[3];
+} SGeneralParam;
+
+// Init Parameters
+typedef struct
+{
+	int				m_priority;
+	int				m_buffer_size;
+	short			m_memory_dest;
+	short			m_init_cd_device;
+} SInitParam;
+
+// Open Parameters
+typedef struct
+{
+	int				m_sector_number;
+	int				m_file_size;
+} SRawFileName;
+
+typedef struct
+{
+	int				m_buffer_size;
+	int				m_priority;
+
+	// Unions must be named in C
+	union
+	{
+		char		 m_filename[FILEIO_MAX_FILENAME_SIZE];
+		SRawFileName m_raw_name;
+	} m_file;
+
+	short			m_attributes;
+	unsigned char	m_host_read;
+	unsigned char	m_memory_dest;
+} SOpenParam;
+
+// Close Parameters
+typedef struct
+{
+	unsigned int	m_file_handle;
+	int				m_pad[2];
+} SCloseParam;
+
+// Read/Write Parameters
+typedef struct
+{
+	unsigned int	m_file_handle;
+	void *			m_buffer;
+	size_t			m_size;
+	unsigned short	m_non_aligned_start_bytes;
+	unsigned short	m_non_aligned_end_bytes;
+	int				m_pad[3];
+} SRWParam;
+
+// Seek Parameters
+typedef struct
+{
+	unsigned int	m_file_handle;
+	int				m_offset;
+	int				m_origin;
+} SSeekParam;
+
+// Set Parameters
+typedef struct
+{
+	unsigned int	m_file_handle;
+	FileIOVariable	m_variable;
+	int				m_value;
+} SSetParam;
+
+// Parameter Union
+typedef union
+{
+	SGeneralParam	m_general;
+	SInitParam		m_init;
+	SOpenParam		m_open;
+	SCloseParam		m_close;
+	SRWParam		m_rw;
+	SSeekParam		m_seek;
+	SSetParam		m_set;
+} SFileIOParam;
+
+// Make sure this doesn't break the alignment of SSifCmdFileIOReqPacket
+typedef struct
+{
+	// Add union first so it aligns correctly
+	SFileIOParam		m_param;
+	FileIOCommand		m_command;
+} SFileIORequest;
+
+
+////////////////////////////////////
+// Other structures
+// 
+
+// SifCmd structures ( keeping them 128-bit aligned )
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	SFileIORequest	m_request;
+} SFileIORequestPacket;
+
+// Packet that is sent back to EE after a request is made
+typedef struct
+{
+	sceSifCmdHdr	m_header;
+	int				m_return_value;			// Return value
+	int				m_done;					// Indicates request completed
+	int				m_byte_transfer;		// Number of bytes copied in secondary DMA
+	int				m_return_data;			// Additional return data
+
+	// Non-aligned data buffer (for beginning and end of buffers that don't cover a whole cache block)
+	int				m_non_aligned_data_size;
+	unsigned char	m_non_aligned_data[FILEIO_CACHE_BLOCK_SIZE + 12];	// Need the additional 12 bytes to align this packet
+} SFileIOResultPacket;
+
+// Stores the request
+typedef struct FileIORequestEntry
+{
+	SFileIORequest				m_request;
+	int							m_request_id;
+} SFileIORequestEntry;
+
+#endif	// __SYS_FILE_NGPS_FILEIO_H
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
diff --git a/Code/Sys/File/ngps/FileIO/Makefile b/Code/Sys/File/ngps/FileIO/Makefile
new file mode 100644
index 0000000..9c939a9
--- /dev/null
+++ b/Code/Sys/File/ngps/FileIO/Makefile
@@ -0,0 +1,37 @@
+ifeq ($(wildcard PathDefs),)
+PathDefs:
+	iop-path-setup
+	make all
+else
+include PathDefs
+endif
+
+TOPDIR = /usr/local/sce
+INCOPT = -I$(TOPDIR)/common/include -I$(TOPDIR)/iop/install/include
+
+CFLAGS  = $(INCOPT) -I. -Wall -G0 -g -D__LANGUAGE_C
+ASFLAGS = $(INCOPT) -G0
+RM          = /bin/rm -f
+
+COMPILE.s = $(CC) -xassembler-with-cpp $(ASFLAGS) $(CPPFLAGS) $(TARGET_MACH) -c
+
+#----------- customize section --------------
+PROGNAME = fileio
+
+OBJS     = start.o command.o
+#ILIBS	= -ilb=$(TOP)lib/iop.ilb 
+ILIBS	=
+LIBI	= /usr/local/sce/iop/install/lib
+
+#----------- rules --------------
+all:	$(PROGNAME).irx
+
+clean:
+	rm -f *.o $(PROGNAME).irx *.obj *.map
+
+$(PROGNAME).irx: $(OBJS)
+	$(LINK.o)  -o $@ \
+		$(OBJS) -L../../../lib -L./ -L$(LIBI) -ilb=cdvdman.ilb
+
+start.o:	fileio.h fileio_iop.h
+command.o:	fileio.h fileio_iop.h
diff --git a/Code/Sys/File/ngps/FileIO/PathDefs b/Code/Sys/File/ngps/FileIO/PathDefs
new file mode 100644
index 0000000..6d730f4
--- /dev/null
+++ b/Code/Sys/File/ngps/FileIO/PathDefs
@@ -0,0 +1,16 @@
+AR	 = snarl.exe
+AS	 = ps2cc -iop
+CC	 = ps2cc -iop
+GCC	 = ps2cc -iop
+LD	 = ps2cc -iop
+NM	 = C:/usr/local/sce/iop/gcc/bin/iop-nm.exe
+SIZE	 = iop-size.exe
+STRIP	 = C:/usr/local/sce/iop/gcc/bin/iop-strip.exe
+RANLIB	 = C:/usr/local/sce/iop/gcc/bin/iop-ranlib.exe
+OBJCOPY	 = C:/usr/local/sce/iop/gcc/bin/iop-objcopy.exe
+OBJDUMP	 = C:/usr/local/sce/iop/gcc/bin/iop-objdump.exe
+IFIXUP	 = iopfixup.exe
+ILBGEN	 = C:/usr/local/sce/iop/gcc/bin/ioplibgen.exe
+ILBLD	 = C:/usr/local/sce/iop/gcc/mipsel-scei-elfl/bin/ioplibld.exe
+ILBDUMP	 = C:/usr/local/sce/iop/gcc/mipsel-scei-elfl/bin/ioplibdump.exe
+BIN2OBJ	 = bin2elf.exe
diff --git a/Code/Sys/File/ngps/FileIO/command.c b/Code/Sys/File/ngps/FileIO/command.c
new file mode 100644
index 0000000..637d8e4
--- /dev/null
+++ b/Code/Sys/File/ngps/FileIO/command.c
@@ -0,0 +1,1617 @@
+/* FileIO -- Converted from Sony samples -- Garrett Oct 2002 */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "fileio.h"
+#include "fileio_iop.h"
+
+// ================================================================
+
+#define MAX(a,b)  (((a) > (b)) ? (a) : (b))
+#define MIN(a,b)  (((a) < (b)) ? (a) : (b))
+
+// FileIO prototypes
+static int FileIO_GetDescriptor(int memory_destination, int buffer_size);
+static int FileIO_FindFileHandleIndex(volatile unsigned int *p_file_handle_index);
+
+static void FileIO_Init(int priority, int buffer_size, short memory_destination, short init_cd_device);
+static void FileIO_Open(int sector_num, int file_size, int memory_destination, int buffer_size, int priority, int attributes);
+static void FileIO_HostOpen(char *p_filename, int memory_destination, int buffer_size, int priority, int attributes,
+							FileIOHostOpen open_type, SFileIORequest *p_cont_request);
+static void FileIO_Close(volatile unsigned int *p_file_handle_index);
+static void FileIO_Read(SFileIORequest *p_request, volatile unsigned int *p_file_handle_index, void *p_buffer, int size, int non_aligned_start_bytes, int non_aligned_end_bytes);
+static void FileIO_Write(SFileIORequest *p_request, volatile unsigned int *p_file_handle_index, void *p_buffer, int size, int non_aligned_start_bytes, int non_aligned_end_bytes);
+static void FileIO_Seek(volatile unsigned int *p_file_handle_index, int offset, int origin);
+static void FileIO_Set(volatile unsigned int *p_file_handle_index, FileIOVariable variable, int value);
+
+// Debug info
+#define SHOW_FILEIO_INFO	0
+
+#if SHOW_FILEIO_INFO
+#define ShowFileIOInfo(A...) Kprintf(##A)
+#else
+#define ShowFileIOInfo(A...) 
+#endif
+
+unsigned int gThid = 0;
+unsigned int gSem = 0;
+unsigned int gCmdSem = 0;
+
+// Defaults
+int gDefaultPriority;
+int gDefaultBufferSize;
+int gDefaultMemoryDestination;
+
+// File handles and buffers
+SFileIOHandle	gFileHandle[FILEIO_MAX_HANDLERS];
+unsigned char	gFileBuffer[FILEIO_MAX_BUFFERS][FILEIO_BUFFER_SIZE];
+unsigned char *	gFileBufferList[FILEIO_MAX_BUFFERS];
+
+int gFreeFileBufferIndex;
+
+// Device status var
+static SFileIODevice sFileIODevices[NUM_DEVICE_TYPES];
+
+static SFileIOResultPacket s_result_packet;
+
+static SRequestStatus s_cur_command_status;
+
+static void ServiceCurrentFileHandle(int resend);
+static int  StartCdRead(int start_sector, int num_sectors, void *p_buffer);
+static void SendNowaitResult();
+
+static int	GetResultPacketSize(SFileIOResultPacket *p_packet);
+
+static void AppendTask(SFileIODevice *p_device, SFileIOTask *p_task);
+static void InsertTask(SFileIODevice *p_device, SFileIOTask *p_task);
+static void RemoveTask(SFileIODevice *p_device, SFileIOTask *p_task);
+static int  AppendContRequest(SFileIOTask *p_task);
+
+
+//
+// Callback
+static void cd_callback(int arg)
+{
+	void *p_ee_addr = NULL, *p_iop_addr = NULL;
+	int block_size = 0;
+	SFileIODevice *p_device = &sFileIODevices[DEVICE_CD];
+	SFileIOHandle *p_file = p_device->mp_file_handle;
+	int resend = FALSE;
+
+	// Lame hack to make sure we issued the sceCd call, not some legacy code
+	if (!p_device->m_in_use)
+	{
+		ShowFileIOInfo("Got CD callback even though we didn't issue a command\n");
+		return;
+	}
+
+	// Wait for previous transfer to finish before we try to read any more (maybe we shouldn't do this in a callback)
+	if (p_file->m_flags & FILEIO_HANDLE_PREV_DMA)
+	{
+		while(sceSifDmaStat(p_file->m_last_dma_id) >= 0)
+			;
+	}
+
+	// Clear out data transfer size
+	s_result_packet.m_non_aligned_data_size = 0;
+
+	switch (p_file->m_cur_function)
+	{
+	case FILEIO_FUNCTION_READ:
+		// Check that we got the correct arg back.  Re-send command if not.
+		if ((arg != SCECdFuncRead) && (arg != SCECdFuncSeek))
+		{
+			resend = TRUE;
+			break;
+		}
+
+		// We probably won't set this for an IOP memory operation
+		s_result_packet.m_byte_transfer = p_file->m_bytes_processing;
+
+		// Figure out if we are done before calling ServiceCurrentFileHandle()
+		s_result_packet.m_done = (p_file->m_bytes_to_process == 0);
+		s_result_packet.m_header.opt = p_file->m_request_id;
+		s_result_packet.m_return_value = p_file->m_return_value;
+		s_result_packet.m_return_data = p_file->m_return_data;
+
+		// Do we need to transfer to EE?
+		if (p_file->m_flags & FILEIO_HANDLE_EE_MEMORY)
+		{
+			p_iop_addr = p_device->mp_buffer[p_device->m_buffer_index];
+			p_device->m_buffer_index ^= 1;
+
+			p_ee_addr = p_file->mp_dest_buffer;
+			block_size = p_file->m_bytes_processing;
+
+			// Check non-alignment
+			if (p_file->m_non_aligned_start_bytes)
+			{
+				s_result_packet.m_non_aligned_data_size = p_file->m_non_aligned_start_bytes;
+				memcpy(s_result_packet.m_non_aligned_data, p_iop_addr, p_file->m_non_aligned_start_bytes);
+
+				p_file->m_non_aligned_start_bytes = 0;
+				//if (s_result_packet.m_done && p_file->m_non_aligned_end_bytes)
+				//{
+				//	Kprintf("Have both non-aligned data at beginning and end of buffer\n");
+				//}
+			}
+			else if (s_result_packet.m_done && p_file->m_non_aligned_end_bytes)
+			{
+				s_result_packet.m_non_aligned_data_size = p_file->m_non_aligned_end_bytes;
+				memcpy(s_result_packet.m_non_aligned_data, p_iop_addr + (block_size - p_file->m_non_aligned_end_bytes), p_file->m_non_aligned_end_bytes);
+			}
+		}
+
+		// Advance dest pointer
+		p_file->mp_dest_buffer = p_file->mp_dest_buffer + block_size;
+
+		break;
+
+	case FILEIO_FUNCTION_OPEN:
+	case FILEIO_FUNCTION_SEEK:
+		s_result_packet.m_byte_transfer = 0;
+		s_result_packet.m_done = TRUE;
+		s_result_packet.m_return_value = p_file->m_return_value;
+		s_result_packet.m_return_data = p_file->m_return_data;
+		s_result_packet.m_header.opt = s_cur_command_status.m_request_id;
+		p_file->m_cur_function = FILEIO_FUNCTION_IDLE;
+		break;
+
+	default:
+		// Something went wrong here
+		ShowFileIOInfo("Got cd callback from an unexpected function: %d\n", p_file->m_cur_function);
+		s_result_packet.m_byte_transfer = 0;
+		s_result_packet.m_done = TRUE;
+		s_result_packet.m_return_value = -1;
+		s_result_packet.m_return_data = -1;
+		s_result_packet.m_header.opt = s_cur_command_status.m_request_id;
+		break;
+
+	}
+
+	if (!resend)
+	{
+		p_file->m_flags |= FILEIO_HANDLE_PREV_DMA;
+		p_file->m_last_dma_id = isceSifSendCmd(FILEIO_RESULT_COMMAND, &s_result_packet, GetResultPacketSize(&s_result_packet), p_iop_addr, p_ee_addr, block_size);
+	}
+	ShowFileIOInfo("FileIO cd callback: sent result %d back from %x to %x of size %d with data %x %x %x %x and arg %x error %d resend %d\n", s_result_packet.m_return_value, p_iop_addr, p_ee_addr, block_size,
+				   ((unsigned int *) p_iop_addr)[0],
+				   ((unsigned int *) p_iop_addr)[1],
+				   ((unsigned int *) p_iop_addr)[2],
+				   ((unsigned int *) p_iop_addr)[3],
+				   arg, sceCdGetError(), resend );
+
+	// Check to see if we need to send out more CD commands
+	ServiceCurrentFileHandle(resend);
+
+	// Wake up the dispatch thread
+	iSignalSema(gCmdSem);
+}
+
+// Can be called from both from outside and within a CD callback
+static void ServiceCurrentFileHandle(int resend)
+{
+	int sectors_to_read;
+	SFileIODevice *p_device = &sFileIODevices[DEVICE_CD];
+	SFileIOHandle *p_file = p_device->mp_file_handle;
+
+	// Read parameters: need to save in case of resend
+	static int cur_sector;
+	static int num_sectors;
+	static void *p_local_buffer;
+
+	if (resend)
+	{
+		ShowFileIOInfo("ServiceCurrentFileHandle %x: Resending read of %d sectors starting at %d to %x with %d bytes left\n", p_file, num_sectors, cur_sector, p_local_buffer, p_file->m_bytes_to_process);
+
+		StartCdRead(cur_sector, num_sectors, p_local_buffer);
+
+		return;
+	}
+
+	// Right now, this only does something with reads
+	if (p_file->m_cur_function != FILEIO_FUNCTION_READ)
+	{
+		// We're done
+		p_file->m_flags &= ~FILEIO_HANDLE_BUSY;
+		p_device->m_in_use = FALSE;
+
+		return;
+	}
+
+	sectors_to_read = (p_file->m_bytes_to_process + SECTOR_SIZE - 1) / SECTOR_SIZE;
+
+	// Check if we need to seek first
+	if (p_file->m_cur_sector != p_device->m_cur_sector)
+	{
+		p_device->m_cur_sector = p_file->m_cur_sector;
+		p_device->m_cur_position = p_file->m_cur_position;
+
+		// We don't need to seek since the read does it for us.
+		//sceCdSeek(p_file->m_cur_sector);
+		//ShowFileIOInfo("ServiceCurrentFileHandle %x: Seeking to sector %d\n", p_file, p_file->m_cur_sector);
+		//return;
+	}
+
+	if (sectors_to_read)
+	{
+		cur_sector = p_file->m_cur_sector;
+
+		if (p_file->m_flags & FILEIO_HANDLE_EE_MEMORY)
+		{
+			p_local_buffer = p_device->mp_buffer[p_device->m_buffer_index];
+			num_sectors = MIN(FILEIO_BUFFER_SECTORS, sectors_to_read);
+		}
+		else
+		{
+			// Note that we don't handle the last sector properly, which most likely won't be
+			// filled (and would overwrite the output buffer).
+			p_local_buffer = p_file->mp_dest_buffer;
+			num_sectors = sectors_to_read;
+		}
+
+		// Advance the file stats before the read call, since, in theory, the read could call the
+		// callback immediately
+		p_file->m_cur_sector += num_sectors;
+		p_file->m_cur_position += num_sectors * SECTOR_SIZE;
+		p_file->m_bytes_processing = MIN(num_sectors * SECTOR_SIZE, p_file->m_bytes_to_process);
+		p_file->m_bytes_to_process = MAX(p_file->m_bytes_to_process - p_file->m_bytes_processing, 0);
+
+		ShowFileIOInfo("ServiceCurrentFileHandle %x: Reading %d sectors starting at %d to %x with %d bytes left\n", p_file, num_sectors, cur_sector, p_local_buffer, p_file->m_bytes_to_process);
+		p_device->m_cur_sector = p_file->m_cur_sector;
+		p_device->m_cur_position = p_file->m_cur_position;
+
+		StartCdRead(cur_sector, num_sectors, p_local_buffer);
+	} else {
+		// We're done
+		p_file->m_flags &= ~FILEIO_HANDLE_BUSY;
+		p_device->m_in_use = FALSE;
+
+		// Send message to EE that we are done
+	}
+}
+
+#define NUM_WAIT_CLICKS 1000
+
+static int StartCdRead(int start_sector, int num_sectors, void *p_buffer)
+{
+	int result;
+	sceCdRMode mode;
+	int retries = 0;
+	static int wait_read = 0;
+
+	if (wait_read)
+	{
+		ShowFileIOInfo("Got callback while waiting to resend\n");
+		return 0;
+	}
+
+	wait_read = 1;
+
+	mode.trycount = 255;
+	mode.spindlctrl = SCECdSpinNom;
+	mode.datapattern = SCECdSecS2048;
+	mode.pad = 0;
+
+	do
+	{
+		//if (sceCdSync( 1 ))
+		//	ShowFileIOInfo("Sync isn't ready before the CD read\n");
+
+		result = sceCdRead(start_sector, num_sectors, p_buffer, &mode);
+
+		if (result == 0)
+		{
+			int i, j = 0;
+
+			Kprintf("sceCdRead() didn't start.  Returned %d with error %d.  Retrying...\n", result, sceCdGetError());
+
+			sceCdSync( 0 );
+
+			do
+			{
+				for ( i = 0; i < NUM_WAIT_CLICKS; i++ )
+					;
+				if ( j++ > NUM_WAIT_CLICKS )
+				{
+					j = 0;
+				}
+			} while ( j != 0 );
+
+            while(sceCdDiskReady(0)!=SCECdComplete)
+				;
+			//sceCdSeek(0);
+			//sceCdSync( 0 );
+			//result = 1;
+			retries++;
+		}
+	} while ((result == 0) && (retries <= 15));		// Need to eventually abort this if retries get too high
+
+	wait_read = 0;
+
+	return result;
+}
+
+// Returns packet size of result, rounded up to a 16-byte chunk
+static int GetResultPacketSize(SFileIOResultPacket *p_packet)
+{
+	unsigned int base_size = ((unsigned int) &(p_packet->m_non_aligned_data[0]) - (unsigned int) p_packet);
+
+	return ((base_size + p_packet->m_non_aligned_data_size) + 0xf) & ~0xf;
+}
+
+////////////////////////////////
+// Tries to find the real file handle index from the open request ID
+static int FileIO_FindFileHandleIndex(volatile unsigned int *p_file_handle_index)
+{
+	int fd;
+	int open_request_id = (int) (*p_file_handle_index & ~FILEIO_TEMP_HANDLE_FLAG);
+
+	for (fd = 0; fd < FILEIO_MAX_HANDLERS; fd++)
+	{
+		if (gFileHandle[fd].m_open_request_id == open_request_id)
+		{
+			ShowFileIOInfo("FileIO: converting open request id %d to file handle index %d\n", open_request_id, fd);
+			*p_file_handle_index = fd;
+			return TRUE;
+		}
+	}
+
+	ShowFileIOInfo("FileIO: can't convert open request id %d to file handle index\n", open_request_id);
+	return FALSE;
+}
+
+////////////////////////////////
+// Init
+static void FileIO_Init(int priority, int buffer_size, short memory_destination, short init_cd_device)
+{
+	int detected_disk_type;
+	int i;
+
+	printf("Starting FileIO_Init\n");
+
+	sFileIODevices[DEVICE_CD].m_in_use = FALSE;
+	sFileIODevices[DEVICE_CD].mp_file_handle = NULL;
+
+	// Init drive
+	if (init_cd_device)
+	{
+#if 0		// Doesn't seem to be necessary, since we already do this on the EE
+		sceCdInit(SCECdINIT);
+		sceCdMmode(SCECdDVD);
+
+		// find the disk type, this might be different from what we intialized to 
+		printf("FileIO: sceCdGetDiskType   ");
+		detected_disk_type= sceCdGetDiskType();
+		switch(detected_disk_type)
+		{
+			case SCECdIllgalMedia:
+			printf("Disk Type= IllgalMedia\n"); break;
+			case SCECdPS2DVD:
+			printf("Disk Type= PlayStation2 DVD\n"); break;
+			case SCECdPS2CD:
+			printf("Disk Type= PlayStation2 CD\n"); break;
+			case SCECdPS2CDDA:
+			printf("Disk Type= PlayStation2 CD with CDDA\n"); break;
+			case SCECdPSCD:
+			printf("Disk Type= PlayStation CD\n"); break;
+			case SCECdPSCDDA:
+			printf("Disk Type= PlayStation CD with CDDA\n"); break;
+			case SCECdDVDV:
+			printf("Disk Type= DVD video\n"); break;
+			case SCECdCDDA:
+			printf("Disk Type= CD-DA\n"); break;
+			case SCECdDETCT:
+			printf("Working\n"); break;
+			case SCECdNODISC: 
+			printf("Disk Type= No Disc\n"); break;
+			default:
+			printf("Disk Type= OTHER DISK\n"); break;
+		}
+
+		// If disk type has changed to a CD, then need to re-initialize it							   
+		if (detected_disk_type == SCECdPS2CD)
+		{
+			#if __DVD_ONLY__
+			printf( "*** ERROR - CD Detected, needs DVD.\n" );
+			#else
+			printf( "reinitializing for Ps2CD\n" );
+			sceCdMmode(SCECdCD);
+			printf( "done reinitializing\n" );
+			#endif		
+		}
+#endif
+
+		// Set up callback
+		sceCdCallback(cd_callback);
+	}
+
+	// Set default parameters
+	gDefaultPriority			= priority;
+	gDefaultBufferSize			= buffer_size;
+	gDefaultMemoryDestination	= memory_destination;
+
+	// Init the file handles and buffers
+	for (i = 0; i < FILEIO_MAX_HANDLERS; i++)
+	{
+		gFileHandle[i].m_flags			= 0;		// Neither busy nor reserved
+		//gFileHandle[i].mp_buffer[0]		= NULL;
+		//gFileHandle[i].mp_buffer[1]		= NULL;
+		//gFileHandle[i].m_buffer_index  	= -1;
+		gFileHandle[i].mp_dest_buffer	= NULL;
+		gFileHandle[i].m_device_type	= DEVICE_UNKNOWN;
+		gFileHandle[i].m_buffer_size	= buffer_size;
+		gFileHandle[i].m_host_fd		= -1;
+		gFileHandle[i].m_priority		= priority;
+		gFileHandle[i].m_stream			= FALSE;
+		gFileHandle[i].m_start_sector	= 0;
+		gFileHandle[i].m_cur_sector		= 0;
+		gFileHandle[i].m_cur_position	= 0;
+		gFileHandle[i].m_file_size		= 0;
+		gFileHandle[i].m_open_request_id= -1;
+	}
+
+	for (i = 0; i < FILEIO_MAX_BUFFERS; i++)
+	{
+		gFileBufferList[i] = gFileBuffer[i];
+	}
+
+	gFreeFileBufferIndex = 0;
+
+	// Allocate device buffers
+	for (i = 0; i < DEVICE_UNKNOWN; i++)
+	{
+		if ((gFreeFileBufferIndex + 1) < FILEIO_MAX_BUFFERS)
+		{
+			// Use buffer size in the future
+			sFileIODevices[i].mp_buffer[0] = gFileBufferList[gFreeFileBufferIndex++];
+			sFileIODevices[i].mp_buffer[1] = gFileBufferList[gFreeFileBufferIndex++];
+			sFileIODevices[i].m_buffer_index = 0;
+		}
+		else
+		{
+			// Can't allocate
+			printf("Out of fileio memory buffers\n");
+			s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+			s_cur_command_status.m_return_value = -1;
+			return;
+		}
+	}
+
+	printf("Done FileIO_Init\n");
+
+	s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+	s_cur_command_status.m_return_value = TRUE;
+	return;
+}
+
+////////////////////////////////
+// GetDescriptor
+static int FileIO_GetDescriptor(int memory_destination, int buffer_size)
+{
+	int i;
+
+	for (i = 0; i < FILEIO_MAX_HANDLERS; i++)
+	{
+		if (!(gFileHandle[i].m_flags & FILEIO_HANDLE_RESERVED))
+		{
+			gFileHandle[i].m_buffer_size = buffer_size;
+
+			if (memory_destination == MEM_EE)
+			{
+				gFileHandle[i].m_flags |= FILEIO_HANDLE_EE_MEMORY;
+#if 0
+				if ((gFreeFileBufferIndex + 1) < FILEIO_MAX_BUFFERS)
+				{
+					// Use buffer size in the future
+					gFileHandle[i].mp_buffer[0] = gFileBufferList[gFreeFileBufferIndex++];
+					gFileHandle[i].mp_buffer[1] = gFileBufferList[gFreeFileBufferIndex++];
+					gFileHandle[i].m_buffer_index = 0;
+				}
+				else
+				{
+					// Can't open
+					printf("Out of fileio memory buffers\n");
+					return -1;
+				}
+#endif
+			}
+			else
+			{
+				gFileHandle[i].m_flags &= ~FILEIO_HANDLE_EE_MEMORY;
+#if 0
+				gFileHandle[i].mp_buffer[0] = NULL;
+				gFileHandle[i].mp_buffer[1] = NULL;
+				gFileHandle[i].m_buffer_index = -1;
+#endif
+			}
+
+			gFileHandle[i].m_flags |= FILEIO_HANDLE_RESERVED;
+
+			return i;
+		}
+	}
+
+	// Couldn't find a free descriptor
+	printf("Out of fileio file descriptors\n");
+	return -1;
+
+}
+
+////////////////////////////////
+// Open
+static void FileIO_Open(int sector_num, int file_size, int memory_destination, int buffer_size, int priority, int attributes)
+{
+	int fd = FileIO_GetDescriptor(memory_destination, buffer_size);
+
+	if (fd >= 0)
+	{
+		// Init the handle itself
+		gFileHandle[fd].m_device_type = DEVICE_CD;
+		gFileHandle[fd].mp_dest_buffer = NULL;
+		gFileHandle[fd].m_host_fd = -1;
+		gFileHandle[fd].m_priority = priority;
+		gFileHandle[fd].m_stream = FALSE;
+		gFileHandle[fd].m_start_sector = sector_num;
+		gFileHandle[fd].m_cur_sector = sector_num;
+		gFileHandle[fd].m_cur_position = 0;
+		gFileHandle[fd].m_file_size = file_size;
+		gFileHandle[fd].m_open_request_id= s_cur_command_status.m_request_id;
+		gFileHandle[fd].mp_blocked_request = NULL;
+		gFileHandle[fd].m_flags &= ~FILEIO_HANDLE_BUSY;
+		gFileHandle[fd].m_cur_function = FILEIO_FUNCTION_IDLE;
+
+		// Don't do seek, just return
+		gFileHandle[fd].m_return_value = fd;
+		gFileHandle[fd].m_return_data = file_size;
+		s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+		s_cur_command_status.mp_file_handle = &(gFileHandle[fd]);
+		s_cur_command_status.m_return_value = fd;
+		s_cur_command_status.m_return_data = file_size;
+		ShowFileIOInfo("Opened file using handle %d\n", fd);
+	} else {
+		s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+		s_cur_command_status.m_return_value = -1;
+		return;
+	}
+}
+
+////////////////////////////////
+// HostOpen
+static void FileIO_HostOpen(char *p_filename, int memory_destination, int buffer_size, int priority, int attributes,
+							FileIOHostOpen open_type, SFileIORequest *p_cont_request)
+{
+	int fd, host_fd;
+	char full_filename[FILEIO_MAX_FILENAME_SIZE * 2];
+
+	strcpy(full_filename, p_filename);
+	if (open_type == FILEIO_HOSTOPEN_EXTEND_NAME)
+	{
+		if (p_cont_request)
+		{
+			strcat(full_filename, p_cont_request->m_param.m_open.m_file.m_filename);
+			ShowFileIOInfo("FileIO HostOpen: opening extended filename %s\n", full_filename);
+		}
+		else
+		{
+			ShowFileIOInfo("FileIO HostOpen: waiting for rest of filename for %s\n", p_filename);
+			s_cur_command_status.m_request_state = REQUEST_OPEN;
+			return;
+		}
+	}
+
+	host_fd = open(full_filename, O_RDONLY);
+	if (host_fd >= 0)
+	{
+		// Find file size
+		int file_size = lseek(host_fd, 0, SEEK_END);
+		lseek(host_fd, 0, SEEK_SET);
+
+		fd = FileIO_GetDescriptor(memory_destination, buffer_size);
+
+		if (fd >= 0)
+		{
+			// Init the handle itself
+			gFileHandle[fd].m_device_type = DEVICE_HOST;
+			gFileHandle[fd].mp_dest_buffer = NULL;
+			gFileHandle[fd].m_priority = priority;
+			gFileHandle[fd].m_stream = FALSE;
+			gFileHandle[fd].m_start_sector = -1;
+			gFileHandle[fd].m_cur_sector = -1;
+			gFileHandle[fd].m_cur_position = 0;
+			gFileHandle[fd].m_file_size = file_size;
+			gFileHandle[fd].m_open_request_id= s_cur_command_status.m_request_id;
+			gFileHandle[fd].mp_blocked_request = NULL;
+			gFileHandle[fd].m_flags &= ~FILEIO_HANDLE_BUSY;
+			gFileHandle[fd].m_cur_function = FILEIO_FUNCTION_IDLE;
+
+			// Do a open
+			gFileHandle[fd].m_host_fd = host_fd;
+
+			gFileHandle[fd].m_return_value = fd;
+			gFileHandle[fd].m_return_data = file_size;
+
+			s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+			s_cur_command_status.mp_file_handle = &(gFileHandle[fd]);
+			s_cur_command_status.m_return_value = fd;
+			s_cur_command_status.m_return_data = file_size;
+			return;
+		} else {
+			printf("Can't get free descriptor for host open of %s\n", full_filename);
+			close(host_fd);
+		}
+	} else {
+		printf("Can't open file %s on host\n", full_filename);
+	}
+
+	s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+	s_cur_command_status.m_return_value = -1;
+	return;
+}
+
+////////////////////////////////
+// Close
+static void FileIO_Close(volatile unsigned int *p_file_handle_index)
+{
+	SFileIOHandle *p_file;
+	//int i, j;
+
+	// See if we have the correct index, and if we can't get it, return
+	if ((*p_file_handle_index & FILEIO_TEMP_HANDLE_FLAG) && !FileIO_FindFileHandleIndex(p_file_handle_index))
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	p_file = &(gFileHandle[*p_file_handle_index]);
+
+	if (p_file->m_flags & FILEIO_HANDLE_RESERVED)
+	{
+		s_cur_command_status.mp_file_handle = p_file;
+
+		// Can't close if we're busy
+		if (p_file->m_flags & FILEIO_HANDLE_BUSY)
+		{
+			s_cur_command_status.m_request_state = REQUEST_OPEN;
+			return;
+		}
+
+		// Check if a host handle
+		if (p_file->m_host_fd >= 0)
+		{
+			close(p_file->m_host_fd);
+		}
+
+#if 0
+		// Check if buffer needs freeing
+		if (p_file->mp_buffer[0])
+		{
+			for (i = 0; i < FILEIO_MAX_BUFFERS; i++)
+			{
+				for (j = 0; j < 2; j++)
+				{
+					if (p_file->mp_buffer[j] == gFileBufferList[i])
+					{
+						// Found it, exchange it with last used buffer
+						gFileBufferList[i] = gFileBufferList[--gFreeFileBufferIndex];
+						gFileBufferList[gFreeFileBufferIndex] = p_file->mp_buffer[j];
+						p_file->mp_buffer[j] = NULL;
+					}
+				}
+			}
+			p_file->m_buffer_index = -1;
+		}
+#endif
+
+		p_file->m_flags &= ~FILEIO_HANDLE_RESERVED;
+
+		ShowFileIOInfo("CLOSE: handle %d open and not busy\n", *p_file_handle_index);
+		s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+		s_cur_command_status.m_return_value = TRUE;
+		return;
+	}
+
+	// Handle wasn't open
+	ShowFileIOInfo("CLOSE: handle %d not open\n", *p_file_handle_index);
+	s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+	s_cur_command_status.m_return_value = FALSE;
+	return;
+}
+
+////////////////////////////////
+// Read
+static void FileIO_Read(SFileIORequest *p_request, volatile unsigned int *p_file_handle_index, void *p_buffer, int size, int non_aligned_start_bytes, int non_aligned_end_bytes)
+{
+	SFileIOHandle *p_file;
+	int host_read;
+	int bytes_to_read;
+
+	// See if we have the correct index, and if we can't get it, return
+	if ((*p_file_handle_index & FILEIO_TEMP_HANDLE_FLAG) && !FileIO_FindFileHandleIndex(p_file_handle_index))
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	p_file = &(gFileHandle[*p_file_handle_index]);
+	host_read = (p_file->m_host_fd >= 0);
+
+	s_cur_command_status.mp_file_handle = p_file;
+
+	// Can't add additional read if we're already busy
+	if ((p_file->m_flags & FILEIO_HANDLE_BUSY) && !(p_file->mp_blocked_request && (p_file->mp_blocked_request == p_request)))
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	// Check to make sure device is free
+	if (host_read)
+	{
+		// Nothing to check for now
+	}
+	else
+	{
+		if (sFileIODevices[DEVICE_CD].m_in_use)
+		{
+			// Mark file handle as blocked
+			p_file->mp_blocked_request = p_request;
+			p_file->m_flags |= FILEIO_HANDLE_BUSY;
+
+			s_cur_command_status.m_request_state = REQUEST_OPEN;
+			return;
+		}
+		else
+		{
+			// Clear blocked status
+			p_file->mp_blocked_request = NULL;
+			p_file->m_flags &= ~FILEIO_HANDLE_BUSY;
+		}
+	}
+
+	// We won't support non sector aligned reads now
+	if (!host_read && (p_file->m_cur_position & (SECTOR_SIZE - 1)))
+	{
+		printf("Can't read from position %d because it isn't on a sector boundary\n", p_file->m_cur_position);
+		p_file->m_cur_function = FILEIO_FUNCTION_IDLE;
+		s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+		s_cur_command_status.m_return_value = -1;
+		return;
+	}
+
+	p_file->m_bytes_to_process = MIN(size, p_file->m_file_size - p_file->m_cur_position);
+	p_file->m_bytes_processing = 0;
+	p_file->m_non_aligned_start_bytes = non_aligned_start_bytes;
+	p_file->m_non_aligned_end_bytes = non_aligned_end_bytes;
+	p_file->m_request_id = s_cur_command_status.m_request_id;
+
+	// Set destination buffer (EE or IOP address) and size
+	p_file->mp_dest_buffer = p_buffer;
+	bytes_to_read = p_file->m_bytes_to_process;
+
+	// Do the actual read
+	if (host_read)
+	{
+		SFileIODevice *p_device = &sFileIODevices[DEVICE_HOST];
+
+		// We should really be looking for the old one and making sure it is done
+		p_file->m_flags &= ~FILEIO_HANDLE_PREV_DMA;
+
+		// Just do the read here until we set up a separate thread
+		while (p_file->m_bytes_to_process)
+		{
+			void *p_ee_addr = NULL, *p_iop_addr = NULL;
+			int block_size = 0;
+			int num_bytes;
+			void *p_local_buffer;
+
+			if (p_file->m_flags & FILEIO_HANDLE_EE_MEMORY)
+			{
+				p_local_buffer = p_device->mp_buffer[p_device->m_buffer_index];
+				num_bytes = MIN(FILEIO_BUFFER_SIZE, p_file->m_bytes_to_process);
+			}
+			else
+			{
+				p_local_buffer = p_file->mp_dest_buffer;
+				num_bytes = p_file->m_bytes_to_process;
+			}
+
+			// Advance the file stats before the read call, since, in theory, the read could call the
+			// callback immediately
+			p_file->m_cur_position += num_bytes;
+			p_file->m_bytes_processing = num_bytes;
+			p_file->m_bytes_to_process = p_file->m_bytes_to_process - p_file->m_bytes_processing;
+
+			ShowFileIOInfo("Host read %x: Reading %d bytes to %x with %d bytes left\n", p_file, num_bytes, p_local_buffer, p_file->m_bytes_to_process);
+			read(p_file->m_host_fd, p_local_buffer, num_bytes);
+
+			// Wait for previous transfer to finish before we try to read any more (maybe we shouldn't do this in a callback)
+			if (p_file->m_flags & FILEIO_HANDLE_PREV_DMA)
+			{
+				while(sceSifDmaStat(p_file->m_last_dma_id) >= 0)
+					;
+			}
+
+			// We probably won't set this for an IOP memory operation
+			s_result_packet.m_byte_transfer = p_file->m_bytes_processing;
+
+			// Figure out if we are done before calling ServiceCurrentFileHandle()
+			s_result_packet.m_done = (p_file->m_bytes_to_process == 0);
+			s_result_packet.m_header.opt = p_file->m_request_id;
+			s_result_packet.m_return_value = bytes_to_read;
+			s_result_packet.m_non_aligned_data_size = 0;
+
+			// Do we need to transfer to EE?
+			if (p_file->m_flags & FILEIO_HANDLE_EE_MEMORY)
+			{
+				p_iop_addr = p_local_buffer;
+				p_device->m_buffer_index ^= 1;
+
+				p_ee_addr = p_file->mp_dest_buffer;
+				block_size = p_file->m_bytes_processing;
+
+				// Check non-alignment
+				if (p_file->m_non_aligned_start_bytes)
+				{
+					s_result_packet.m_non_aligned_data_size = p_file->m_non_aligned_start_bytes;
+					memcpy(s_result_packet.m_non_aligned_data, p_iop_addr, p_file->m_non_aligned_start_bytes);
+
+					p_file->m_non_aligned_start_bytes = 0;
+				}
+				else if (s_result_packet.m_done && p_file->m_non_aligned_end_bytes)
+				{
+					s_result_packet.m_non_aligned_data_size = p_file->m_non_aligned_end_bytes;
+					memcpy(s_result_packet.m_non_aligned_data, p_iop_addr + (block_size - p_file->m_non_aligned_end_bytes), p_file->m_non_aligned_end_bytes);
+				}
+			}
+
+			// Advance dest pointer
+			p_file->mp_dest_buffer = p_file->mp_dest_buffer + block_size;
+
+			p_file->m_flags |= FILEIO_HANDLE_PREV_DMA;
+			p_file->m_last_dma_id = sceSifSendCmd(FILEIO_RESULT_COMMAND, &s_result_packet, GetResultPacketSize(&s_result_packet), p_iop_addr, p_ee_addr, block_size);
+			ShowFileIOInfo("FileIO host read: sent result %d back from %x to %x of size %d\n", s_result_packet.m_return_value, p_iop_addr, p_ee_addr, block_size);
+		}
+
+		s_cur_command_status.m_request_state = REQUEST_PROCESSING;		// Don't want to send another result packet
+		p_file->m_return_value = bytes_to_read;
+	}
+	else
+	{
+		// We should really be looking for the old one and making sure it is done
+		p_file->m_flags &= ~FILEIO_HANDLE_PREV_DMA;
+
+		// Mark that we are using the CD
+		p_file->m_flags |= FILEIO_HANDLE_BUSY;
+		sFileIODevices[DEVICE_CD].m_in_use = TRUE;
+		sFileIODevices[DEVICE_CD].mp_file_handle = p_file;
+
+		p_file->m_cur_function = FILEIO_FUNCTION_READ;
+		p_file->m_return_value = p_file->m_bytes_to_process;
+		s_cur_command_status.m_request_state = REQUEST_PROCESSING;
+
+		ServiceCurrentFileHandle(FALSE);
+	}
+
+	return;
+}
+
+////////////////////////////////
+// Write
+static void FileIO_Write(SFileIORequest *p_request, volatile unsigned int *p_file_handle_index, void *p_buffer, int size, int non_aligned_start_bytes, int non_aligned_end_bytes)
+{
+	SFileIOHandle *p_file;
+
+	// See if we have the correct index, and if we can't get it, return
+	if ((*p_file_handle_index & FILEIO_TEMP_HANDLE_FLAG) && !FileIO_FindFileHandleIndex(p_file_handle_index))
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	p_file = &(gFileHandle[*p_file_handle_index]);
+
+	s_cur_command_status.mp_file_handle = p_file;
+
+	// Can't write if we're already busy
+	if ((p_file->m_flags & FILEIO_HANDLE_BUSY) && !(p_file->mp_blocked_request && (p_file->mp_blocked_request == p_request)))
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+	s_cur_command_status.m_return_value = -1;
+	return;
+}
+
+////////////////////////////////
+// Seek
+static void FileIO_Seek(volatile unsigned int *p_file_handle_index, int offset, int origin)
+{
+	int new_position;
+	SFileIOHandle *p_file;
+
+	// See if we have the correct index, and if we can't get it, return
+	if ((*p_file_handle_index & FILEIO_TEMP_HANDLE_FLAG) && !FileIO_FindFileHandleIndex(p_file_handle_index))
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	p_file = &(gFileHandle[*p_file_handle_index]);
+
+	s_cur_command_status.mp_file_handle = p_file;
+
+	// Can't start seek if we're already busy
+	if (p_file->m_flags & FILEIO_HANDLE_BUSY)
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	// Do the adjustments
+	switch (origin)
+	{
+	case SEEK_CUR:
+		new_position = p_file->m_cur_position + offset;
+		break;
+
+	case SEEK_SET:
+		new_position = offset;
+		break;
+
+	case SEEK_END:
+		new_position = p_file->m_file_size - offset;
+		break;
+
+	default:
+		p_file->m_cur_function = FILEIO_FUNCTION_IDLE;
+		s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+		s_cur_command_status.m_return_value = -1;
+		return;
+	}
+
+	// Check if before beginning
+	if (new_position < 0)
+	{
+		new_position = 0;
+	}
+
+	// Check if past EOF
+	if (new_position > p_file->m_file_size)
+	{
+		new_position = p_file->m_file_size;
+	}
+
+	if (p_file->m_host_fd >= 0)
+	{
+		// Device ready
+		p_file->m_cur_position = new_position;
+
+		// Do the seek
+		lseek(p_file->m_host_fd, p_file->m_cur_position, SEEK_SET);
+
+		p_file->m_return_value = p_file->m_cur_position;
+		s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+		s_cur_command_status.m_return_value = p_file->m_cur_position;
+	}
+	else
+	{
+		// Change position
+		p_file->m_cur_position = new_position;
+
+		// Also set the new current sector
+		p_file->m_cur_sector = p_file->m_start_sector + (p_file->m_cur_position / SECTOR_SIZE);
+
+		p_file->m_return_value = p_file->m_cur_position;
+		s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+		s_cur_command_status.m_return_value = p_file->m_cur_position;
+	}
+
+	return;
+}
+
+static void FileIO_Set(volatile unsigned int *p_file_handle_index, FileIOVariable variable, int value)
+{
+	SFileIOHandle *p_file;
+
+	// See if we have the correct index, and if we can't get it, return
+	if ((*p_file_handle_index & FILEIO_TEMP_HANDLE_FLAG) && !FileIO_FindFileHandleIndex(p_file_handle_index))
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	p_file = &(gFileHandle[*p_file_handle_index]);
+
+	s_cur_command_status.mp_file_handle = p_file;
+
+	// Can't change parameters if we're already busy
+	if (p_file->m_flags & FILEIO_HANDLE_BUSY)
+	{
+		s_cur_command_status.m_request_state = REQUEST_OPEN;
+		return;
+	}
+
+	s_cur_command_status.m_request_state = REQUEST_COMPLETE;
+
+	switch (variable)
+	{
+	case FILEIO_VAR_PRIORITY:
+		p_file->m_priority = value;
+		break;
+
+	case FILEIO_VAR_BUFFER_SIZE:
+		p_file->m_buffer_size = value;
+		break;
+
+	case FILEIO_VAR_STREAM:
+		p_file->m_stream = value;
+		break;
+
+	case FILEIO_VAR_DESTINATION:
+		// Not sure how to handle this one yet, since buffers may need to be acquired or freed
+		if (value == MEM_EE)
+		{
+			p_file->m_flags |= FILEIO_HANDLE_EE_MEMORY;
+		}
+		else
+		{
+			p_file->m_flags &= ~FILEIO_HANDLE_EE_MEMORY;
+		}
+		break;
+
+	default:
+		s_cur_command_status.m_return_value = FALSE;
+		return;
+	}
+
+	s_cur_command_status.m_return_value = TRUE;
+	return;
+}
+
+// EzADPCM_INIT
+int AdpcmInit( int pIopBuf )
+{
+#if 0
+	int i;
+	if ( gEECommand & PCMSTATUS_INITIALIZED )
+	{
+		return ( 0 );
+	}
+
+    if ( gSem == 0 )
+	{
+		struct SemaParam sem;
+		sem.initCount = 0;
+		sem.maxCount = 1;
+		sem.attr = AT_THFIFO;
+		gSem = CreateSema (&sem);
+    }
+    if (gThid == 0)
+	{
+		struct ThreadParam param;
+		param.attr         = TH_C;
+		param.entry        = RefreshStreams;
+		param.initPriority = BASE_priority-3;
+		param.stackSize    = 0x800; // was 800
+		param.option = 0;
+		gThid = CreateThread (¶m);
+		printf( "EzADPCM: create thread ID= %d\n", gThid );
+		StartThread (gThid, (u_long) NULL);
+    }
+
+	memset( gStreamInfo, 0, sizeof( struct StreamInfo ) * NUM_STREAMS );
+	memset( &gMusicInfo, 0, sizeof( struct StreamInfo ) );
+	
+	gpIopBuf = pIopBuf;
+	gEECommand |= PCMSTATUS_INITIALIZED;
+	//printf( "PCM Irx iop buf %d\n", gpIopBuf );
+	//Dbug_Printf( "PCM irx is initialized\n" );
+#endif
+    return gThid;
+}
+
+// EzADPCM_QUIT
+void AdpcmQuit( void )
+{
+    DeleteThread (gThid);
+    gThid = 0;
+#if 0
+    DeleteSema (gSem);
+    gSem = 0;
+#endif
+    return;
+}
+
+/* internal */
+int RefreshStreams( int status )
+{
+	//int i;
+
+    while ( 1 )
+	{
+		WaitSema(gSem);
+#if 0
+		for ( i = 0; i < NUM_STREAMS; i++ )
+		{
+			CheckStream( i );
+		}
+		CheckMusic( );
+
+		for ( i = 0; i < NUM_STREAMS; i++ )
+		{
+			while ( gStreamInfo[ i ].update )
+			{
+				gStreamInfo[ i ].update--;
+				UpdateStream( i );
+			}
+		}
+		while ( gMusicInfo.update )
+		{
+			gMusicInfo.update--;
+			UpdateMusic( );
+		}
+		if ( gMusicInfo.volumeSet )
+		{
+			gMusicInfo.volumeSet = 0;
+			AdpcmSetMusicVolumeDirect( gMusicInfo.volume );
+		}
+		for ( i = 0; i < NUM_STREAMS; i++ )
+		{
+			if ( gStreamInfo[ i ].volumeSet )
+			{
+				gStreamInfo[ i ].volumeSet = 0;
+				AdpcmSetStreamVolumeDirect( gStreamInfo[ i ].volume, i );
+			}
+		}
+#endif
+    }
+    return 0;
+}
+
+static ERequestState dispatch(volatile SFileIOTask *p_request_task)
+{ 
+	volatile SFileIORequestEntry *p_request_entry = &(p_request_task->m_entry);
+	volatile SFileIORequest *p_request = &(p_request_entry->m_request);
+	SFileIORequest *p_cont_request = (p_request_task->mp_cont) ? (&p_request_task->mp_cont->m_entry.m_request) : NULL;
+
+	// Assume command will need to be completed asynchronously
+	s_cur_command_status.m_request_state = REQUEST_OPEN;
+	s_cur_command_status.m_request_id = p_request_entry->m_request_id;
+	s_cur_command_status.mp_file_handle = NULL;
+
+    switch ( p_request->m_command )
+	{
+	case FILEIO_INIT:
+		FileIO_Init(p_request->m_param.m_init.m_priority,
+					p_request->m_param.m_init.m_buffer_size,
+					p_request->m_param.m_init.m_memory_dest,
+					p_request->m_param.m_init.m_init_cd_device);
+		break;
+		
+	case FILEIO_OPEN:
+		if (p_request->m_param.m_open.m_host_read)
+		{
+			FileIO_HostOpen((char *) p_request->m_param.m_open.m_file.m_filename,
+							p_request->m_param.m_open.m_memory_dest,
+							p_request->m_param.m_open.m_buffer_size,
+							p_request->m_param.m_open.m_priority,
+							p_request->m_param.m_open.m_attributes,
+							p_request->m_param.m_open.m_host_read,
+							p_cont_request);
+		} else {
+			FileIO_Open(p_request->m_param.m_open.m_file.m_raw_name.m_sector_number, 
+						p_request->m_param.m_open.m_file.m_raw_name.m_file_size,
+						p_request->m_param.m_open.m_memory_dest,
+						p_request->m_param.m_open.m_buffer_size,
+						p_request->m_param.m_open.m_priority,
+						p_request->m_param.m_open.m_attributes);
+		}
+		break;
+
+	case FILEIO_CLOSE:
+		FileIO_Close(&p_request->m_param.m_close.m_file_handle);
+		break;
+
+	case FILEIO_READ:
+		FileIO_Read((SFileIORequest *) p_request, &p_request->m_param.m_rw.m_file_handle, p_request->m_param.m_rw.m_buffer,
+					p_request->m_param.m_rw.m_size, p_request->m_param.m_rw.m_non_aligned_start_bytes,
+					p_request->m_param.m_rw.m_non_aligned_end_bytes);
+		break;
+
+	case FILEIO_WRITE:
+		FileIO_Write((SFileIORequest *) p_request, &p_request->m_param.m_rw.m_file_handle, p_request->m_param.m_rw.m_buffer,
+					 p_request->m_param.m_rw.m_size, p_request->m_param.m_rw.m_non_aligned_start_bytes,
+					 p_request->m_param.m_rw.m_non_aligned_end_bytes);
+		break;
+
+	case FILEIO_SEEK:
+		FileIO_Seek(&p_request->m_param.m_seek.m_file_handle, p_request->m_param.m_seek.m_offset,
+					p_request->m_param.m_seek.m_origin);
+		break;
+
+	case FILEIO_SET:
+		FileIO_Set(&p_request->m_param.m_set.m_file_handle, p_request->m_param.m_set.m_variable,
+				   p_request->m_param.m_set.m_value);
+		break;
+
+	case FILEIO_CONT:
+		s_cur_command_status.m_request_state = REQUEST_APPEND;
+		break;
+
+	default:
+		ERROR ("FileIO driver error: unknown command %d \n", p_request->m_command);
+		s_cur_command_status.m_return_value = -1;
+		break;
+    }
+
+	// Copy file handle in case we need it later
+	p_request_task->mp_file_handle = s_cur_command_status.mp_file_handle;
+
+	switch (s_cur_command_status.m_request_state)
+	{
+	case REQUEST_OPEN:
+		// This isn't an error or warning anymore: it's now normal
+		//ERROR (("FileIO driver error: request %d should not end in a open request state\n", p_request_entry->m_request_id));
+		//printf("FileIO driver warning: request %d with command %d ended in a open request state\n", p_request_entry->m_request_id, p_request->m_command);
+		break;
+
+	case REQUEST_PROCESSING:
+		break;
+
+	case REQUEST_APPEND:
+		break;
+
+	case REQUEST_COMPLETE:
+		// Send result back to EE now if command completed
+		SendNowaitResult();
+		break;
+	}
+
+    return s_cur_command_status.m_request_state;
+}
+
+
+static sceSifCmdData cmdbuffer[FILEIO_NUM_COMMAND_HANDLERS];
+
+#define NUM_FILEIO_REQUESTS	(16)
+
+// Note these can be changed in an interrupt
+static volatile SFileIORequestEntry sFileIORequestArray[NUM_FILEIO_REQUESTS];
+static volatile int sFirstFileIORequest;		// Interrupt only reads this value.
+static volatile	int sFreeFileIORequest;			// This is the main variable that changes in the interrupt
+
+// The task entries
+static SFileIOTask sFileIOTaskArray[FILEIO_MAX_TASKS];
+static SFileIOTask *spTaskFreeList = NULL;
+
+static void request_callback(void *p, void *q);
+
+int fileio_command_loop( void )
+{
+	int oldStat;
+	int i;
+	EDeviceType cur_device;
+
+	// Create semaphore to signal command came in
+	struct SemaParam sem;
+	sem.initCount = 0;
+	sem.maxCount = 1;
+	sem.attr = AT_THFIFO;
+	gCmdSem = CreateSema(&sem);
+
+	// Init the stream queue
+	sFirstFileIORequest = 0;
+	sFreeFileIORequest = 0;
+
+	// Init free task list
+	spTaskFreeList = NULL;
+	for (i = 0; i < FILEIO_MAX_TASKS; i++)
+	{
+		sFileIOTaskArray[i].mp_file_handle = NULL;
+
+		// Add to beginning of list
+		sFileIOTaskArray[i].mp_next = spTaskFreeList;
+		spTaskFreeList = &sFileIOTaskArray[i];
+	}
+
+	// Init devices
+	for (i = 0; i < NUM_DEVICE_TYPES; i++)
+	{
+		sFileIODevices[i].m_in_use = FALSE;
+		sFileIODevices[i].m_waiting_callback = FALSE;
+		sFileIODevices[i].mp_file_handle = NULL;
+		sFileIODevices[i].mp_task_list = NULL;
+		sFileIODevices[i].m_cur_position = -1;
+		sFileIODevices[i].m_cur_sector = -1;
+		sFileIODevices[i].mp_buffer[0] = NULL;
+		sFileIODevices[i].mp_buffer[1] = NULL;
+		sFileIODevices[i].m_buffer_index = -1;
+	}
+
+	sceSifInitRpc(0);
+
+	// set local buffer & functions
+	CpuSuspendIntr(&oldStat);
+
+	// SIFCMD
+	sceSifSetCmdBuffer( &cmdbuffer[0], FILEIO_NUM_COMMAND_HANDLERS);
+
+	sceSifAddCmdHandler(FILEIO_REQUEST_COMMAND, (void *) request_callback, NULL );
+
+	CpuResumeIntr(oldStat);
+
+	// The loop
+	while (1) {
+		//ShowFileIOInfo("waiting for command semaphore\n");
+		WaitSema(gCmdSem);		// Get signal from callback
+		//ShowFileIOInfo("got command semaphore\n");
+
+		// Move new requests into schedule list
+		// Note that FreeStreamRequest can change in the interrupt at any time
+		// Also, FirstStreamRequest is examined in the interrupt, but just to make sure we don't overflow the buffer
+		while (sFreeFileIORequest != sFirstFileIORequest)
+		{
+			volatile SFileIORequestEntry *p_request = &(sFileIORequestArray[sFirstFileIORequest]);
+			SFileIOTask *p_new_task;
+			ShowFileIOInfo("FileIO: got request id %d with command %x\n", p_request->m_request_id, p_request->m_request.m_command);
+
+			// Get a new task entry
+			p_new_task = spTaskFreeList;
+			spTaskFreeList = p_new_task->mp_next;
+			p_new_task->mp_next = NULL;
+
+			// Copy data
+			p_new_task->m_entry = *p_request;
+			p_new_task->mp_file_handle = NULL;
+			p_new_task->mp_cont = NULL;
+
+			// And put onto unknown device task list
+			AppendTask(&sFileIODevices[DEVICE_UNKNOWN], p_new_task);
+
+			// Increment request index; Note that interrupt can look at this
+			sFirstFileIORequest = (sFirstFileIORequest + 1) % NUM_FILEIO_REQUESTS;
+		}
+
+		// Process device tasks
+		for (cur_device = DEVICE_CD; cur_device < NUM_DEVICE_TYPES; cur_device++)
+		{
+			SFileIODevice *p_device = &sFileIODevices[cur_device];
+			SFileIOTask *p_task;
+
+			// Skip if busy
+			if (p_device->m_in_use)
+			{
+				continue;
+			}
+
+			p_task = p_device->mp_task_list;
+			while (p_task)
+			{
+				ERequestState ret;
+				SFileIOTask *p_dispatch_task;
+
+				p_dispatch_task = p_task;
+
+				ShowFileIOInfo("FileIO: doing request id %d with command %x\n", p_dispatch_task->m_entry.m_request_id, p_dispatch_task->m_entry.m_request.m_command);
+				ret = dispatch(p_dispatch_task);
+
+				p_task = p_task->mp_next;
+
+				switch (ret)
+				{
+				case REQUEST_OPEN:
+					// Move to other device
+					if (p_dispatch_task->mp_file_handle && (p_dispatch_task->mp_file_handle->m_device_type != cur_device))
+					{
+						RemoveTask(p_device, p_dispatch_task);
+						InsertTask(&sFileIODevices[p_dispatch_task->mp_file_handle->m_device_type], p_dispatch_task);
+					}
+					break;
+
+				case REQUEST_APPEND:
+					// Add the request to an existing request
+					if (AppendContRequest(p_dispatch_task))
+					{
+						ShowFileIOInfo("***************** FileIO: appended request id %d of command %x\n", p_dispatch_task->m_entry.m_request_id, p_dispatch_task->m_entry.m_request.m_command);
+						RemoveTask(p_device, p_dispatch_task);
+						SignalSema(gCmdSem);		// So the other task can be retried
+					}
+					break;
+
+				case REQUEST_PROCESSING:
+				case REQUEST_COMPLETE:
+					RemoveTask(p_device, p_dispatch_task);
+					if (p_dispatch_task->mp_cont)			// Check if we have a continuation request
+					{
+						p_dispatch_task->mp_cont->mp_next = spTaskFreeList;
+						spTaskFreeList = p_dispatch_task->mp_cont;
+						p_dispatch_task->mp_cont = NULL;
+					}
+					p_dispatch_task->mp_next = spTaskFreeList;
+					spTaskFreeList = p_dispatch_task;
+
+					break;
+				}
+			}
+		}
+	}
+
+    return 0;
+}
+
+static void SendNowaitResult()
+{
+	static SFileIOResultPacket fileIOResult;
+
+	static unsigned int last_cmd_id = 0;
+	static int did_dma_send = FALSE;
+
+	// Send the result back
+	if (did_dma_send && last_cmd_id >= 0)	// Wait for previous send to complete (it should already be done, though)
+	{
+	   while(sceSifDmaStat(last_cmd_id) >= 0)
+		   Kprintf("Waiting for DMA\n");
+	}
+
+	// Gotta copy the id into SStreamRequest
+	fileIOResult.m_header.opt = s_cur_command_status.m_request_id;	// Copy id
+	fileIOResult.m_return_value = s_cur_command_status.m_return_value;
+	fileIOResult.m_return_data = s_cur_command_status.m_return_data;
+	fileIOResult.m_done = TRUE;
+	fileIOResult.m_non_aligned_data_size = 0;
+	did_dma_send = TRUE;
+	last_cmd_id = sceSifSendCmd(FILEIO_RESULT_COMMAND, &fileIOResult, GetResultPacketSize(&fileIOResult), 0, 0, 0);
+	ShowFileIOInfo("FileIO: sent nowait result id %d back\n", s_cur_command_status.m_request_id);
+}
+
+static void request_callback(void *p, void *q)
+{
+	SFileIORequestPacket *h = (SFileIORequestPacket *) p;
+
+	// Check to make sure we can add
+	int nextFreeReq = (sFreeFileIORequest + 1) % NUM_FILEIO_REQUESTS;
+	if (nextFreeReq == sFirstFileIORequest)
+	{
+		// We can't allow a request to be ignored.  We must abort
+		ERROR(("******************************** FileIO driver error: too many requests ********************************\n" ));
+	}
+
+	ShowFileIOInfo("FileIO: received request id %d with command %x\n", h->m_header.opt, h->m_request.m_command);
+
+	// Copy the request into the reuest queue
+	sFileIORequestArray[sFreeFileIORequest].m_request = h->m_request;
+	sFileIORequestArray[sFreeFileIORequest].m_request_id = h->m_header.opt;
+	sFreeFileIORequest = nextFreeReq;
+
+	// And wake up the dispatch thread
+	iSignalSema(gCmdSem);
+}
+
+// Adds p_task to the end of the device task list
+static void AppendTask(SFileIODevice *p_device, SFileIOTask *p_task)
+{
+	SFileIOTask *p_task_list = p_device->mp_task_list;
+	if (p_task_list)
+	{
+		// Find end of list
+		while (p_task_list->mp_next)
+		{
+			p_task_list = p_task_list->mp_next;
+		}
+
+		p_task->mp_next = p_task_list->mp_next;
+		p_task_list->mp_next = p_task;
+	}
+	else
+	{
+		// Empty list
+		p_device->mp_task_list = p_task;
+		p_task->mp_next = NULL;
+	}
+}
+
+#define GetPriority(x) ((x) ? (x)->m_priority : gDefaultPriority)
+
+// Inserts p_task into the device task list sorted by priority
+static void InsertTask(SFileIODevice *p_device, SFileIOTask *p_task)
+{
+	SFileIOTask *p_task_node = p_device->mp_task_list;
+	SFileIOTask *p_prev_node = NULL;
+
+	int priority = GetPriority(p_task->mp_file_handle);
+
+	// Find point in list
+	while (p_task_node)
+	{
+		if (priority < GetPriority(p_task_node->mp_file_handle))
+		{
+			break;
+		}
+		p_prev_node = p_task_node;
+		p_task_node = p_task_node->mp_next;
+	}
+
+	// Insert
+	if (p_prev_node)
+	{
+		p_task->mp_next = p_task_node;
+		p_prev_node->mp_next = p_task;
+	}
+	else
+	{
+		// Head of list
+		p_task->mp_next = p_device->mp_task_list;
+		p_device->mp_task_list = p_task;
+	}
+}
+
+// Removes p_task from the device task list
+static void RemoveTask(SFileIODevice *p_device, SFileIOTask *p_task)
+{
+	SFileIOTask *p_task_node = p_device->mp_task_list;
+
+	if (p_task == p_task_node)
+	{
+		// Head of list
+		p_device->mp_task_list = p_task->mp_next;
+	}
+	else
+	{
+		// Find item in list
+		while (p_task_node->mp_next)
+		{
+			if (p_task == p_task_node->mp_next)
+			{
+				break;
+			}
+			p_task_node = p_task_node->mp_next;
+		}
+
+		// Remove
+		p_task_node->mp_next = p_task->mp_next;
+	}
+	p_task->mp_next = NULL;
+}
+
+static int AppendContRequest(SFileIOTask *p_task)
+{
+	EDeviceType cur_device;
+
+	// Process device tasks
+	for (cur_device = DEVICE_CD; cur_device < NUM_DEVICE_TYPES; cur_device++)
+	{
+		SFileIODevice *p_device = &sFileIODevices[cur_device];
+		SFileIOTask *p_task_node;
+
+		p_task_node = p_device->mp_task_list;
+
+		// Find item in list
+		while (p_task_node)
+		{
+			if (p_task->m_entry.m_request_id == p_task_node->m_entry.m_request_id && (p_task != p_task_node))
+			{
+				p_task_node->mp_cont = p_task;
+				return TRUE;
+			}
+
+			p_task_node = p_task_node->mp_next;
+		}
+	}
+
+	return FALSE;
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
diff --git a/Code/Sys/File/ngps/FileIO/start.c b/Code/Sys/File/ngps/FileIO/start.c
new file mode 100644
index 0000000..805afd1
--- /dev/null
+++ b/Code/Sys/File/ngps/FileIO/start.c
@@ -0,0 +1,47 @@
+/* FileIO -- Converted from Sony samples -- Garrett Oct 2002 */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "fileio.h"
+#include "fileio_iop.h"
+
+ModuleInfo Module = {"fileio_driver", 0x0112};
+
+// in command.c
+extern int fileio_command_loop (void);
+
+int start()
+{
+    struct ThreadParam param;
+    int th;
+
+    if (!sceSifCheckInit())
+	{
+		sceSifInit();
+	}
+    sceSifInitRpc(0);
+
+    printf("FileIO driver version 0.1\n");
+
+    param.attr         = TH_C;
+    param.entry        = fileio_command_loop;
+    param.initPriority = BASE_priority - 2;
+    param.stackSize    = 0x800;
+    param.option       = 0;
+    th = CreateThread(¶m);
+    if (th > 0) {
+		StartThread(th, 0);
+    } else {
+		return 1;
+    }
+
+	return 0;
+}
+
+/* ----------------------------------------------------------------
+ *	End on File
+ * ---------------------------------------------------------------- */
diff --git a/Code/Sys/File/ngps/hed.cpp b/Code/Sys/File/ngps/hed.cpp
new file mode 100644
index 0000000..7b3599e
--- /dev/null
+++ b/Code/Sys/File/ngps/hed.cpp
@@ -0,0 +1,392 @@
+/*	Header file functionality...
+	.Hed files that describe the contents of .Wad files
+	Written by Ken, stolen by Matt*/
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+
+#include 
+#include 
+
+#include  
+
+#include 
+#include 
+#include 
+
+namespace File
+{
+
+static void get_checksums( const char * p_name, uint32 * p_dir, uint32 * p_file )
+{
+	char name[128];
+	char dir[128];
+	char file[128];
+	uint lp;
+
+	// Convert / to \\.
+	for ( lp = 0; lp <= strlen( p_name ); lp++ )
+	{
+		name[lp] = p_name[lp];
+		if ( name[lp] == '/' ) name[lp] = '\\';
+	}
+
+	// Find the directory name and the filename.
+	for ( lp = strlen( name ) - 1; lp >= 0; lp-- )
+	{
+		if ( name[lp] == '\\' )
+		{
+			// Found the directory end.
+			strcpy( file, &name[lp+1] );
+			name[lp] = '\0';
+			if ( name[0]=='\\' )
+			{
+				strcpy( dir, &name[1] );
+			}
+			else
+			{
+				strcpy( dir, name );
+			}
+			break;
+		}
+	}
+
+	// Create checksums.
+	if ( p_dir ) *p_dir = Script::GenerateCRC( dir );
+	if ( p_file ) *p_file = Script::GenerateCRC( file );
+}
+
+
+// Searches the hed file for the filename. If not found, returns NULL.
+SHedFile *FindFileInHed(const char *pFilename, SHed *pHed)
+{
+	Dbg_MsgAssert(pFilename,("NULL pFilename"));
+	Dbg_MsgAssert(pHed,("NULL pHed"));
+
+	uint32 check_dir;
+	uint32 check_file;
+
+	get_checksums( pFilename, &check_dir, &check_file );
+
+	SHed *pHd=pHed;
+	while ( pHd->numFiles != 0xffffffff )
+	{
+		if ( pHd->Checksum == check_dir )
+		{
+			SHedFile * pF = pHd->p_fileList;
+			for ( uint lp2 = 0; lp2 < pHd->numFiles; lp2++ )
+			{
+				if ( pF->Checksum == check_file )
+				{
+					return pF;
+				}
+				pF++;
+			}
+		}
+		pHd++;
+	}
+
+	return NULL;
+}
+
+// Searches the hed file for the filename with the same checksum. If not found, returns NULL.
+SHedFile *FindFileInHedUsingChecksum( uint32 checksum, SHed *pHed )
+{
+	// Garrett: This function can take over .1 ms to execute when there are over 2000 entries.  We may want to make a better
+	// search for this.
+	//uint64 start_time = Tmr::GetTimeInUSeconds();	
+	//int iterations = 0;
+
+	Dbg_MsgAssert(pHed,("NULL pHed"));
+
+	SHed *pHd=pHed;
+	while ( pHd->numFiles != 0xffffffff )
+	{
+		SHedFile * pF = pHd->p_fileList;
+		for ( uint lp2 = 0; lp2 < pHd->numFiles; lp2++ )
+		{
+			//iterations++;
+			if ( pF->Checksum == checksum )
+			{
+				//uint64 end_time = Tmr::GetTimeInUSeconds();	
+				//if ((end_time - start_time) > 100)
+				//	Dbg_Message("FindNameFromChecksum Found Iterations %d Time %d", iterations, (end_time - start_time));
+				return pF;
+			}
+			pF++;
+		}
+		pHd++;
+	}
+
+	//uint64 end_time = Tmr::GetTimeInUSeconds();	
+	//if ((end_time - start_time) > 100)
+	//	Dbg_Message("FindNameFromChecksum NotFound Iterations %d Time %d", iterations, (end_time - start_time));
+	return NULL;
+}
+
+extern void StopStreaming( void );
+
+#ifdef	__NOPT_ASSERT__
+// Keep track of the hed buffers so the leak code doesn't think they are leaks
+#define MAX_HED_BUFFERS 3
+uint32 *sp_hed_buffers[MAX_HED_BUFFERS] = { (uint32 *) 16, (uint32 *) 16, (uint32 *) 16 };
+int s_num_hed_buffers = 0;
+#endif //	__NOPT_ASSERT__
+
+SHed *LoadHed( const char *filename, bool force_cd, bool no_wad )
+{
+	int lp;
+	uint32 *pHed = NULL;
+
+	if (Config::CD() || force_cd)
+	{
+		int HedId=-1;
+		char hedFileName[255];
+		char uppercaseFilename[ 255 ];
+		int sL = strlen( filename );
+		Dbg_MsgAssert( sL < 255,( "Filename %s too long", filename ));
+		int i;
+		for ( i = 0; i < sL; i++ )
+		{
+			uppercaseFilename[ i ] = toupper( filename[ i ] );
+		}
+		uppercaseFilename[ i ] = '\0';
+		StopStreaming( );
+		sprintf( hedFileName, "cdrom0:\\%s%s.HED;1", Config::GetDirectory(),uppercaseFilename );
+		int retry = 0;
+		while (HedId<0)
+		{
+			HedId=sceOpen( hedFileName, SCE_RDONLY );
+			if (HedId<0)
+			{
+				printf("Retrying opening %s\n", hedFileName );
+				retry++;
+				if (retry == 10)
+				{
+					return NULL;					
+				}
+			}	
+		}
+		printf("%s opened successfully\n", uppercaseFilename );
+		long HedSize=sceLseek(HedId, 0, SCE_SEEK_END);
+		
+		Dbg_MsgAssert(pHed==NULL,("pHed not NULL ?"));
+		if (no_wad)
+		{
+			// We'll want to keep this around
+			pHed = new (Mem::Manager::sHandle().DebugHeap()) uint32[((HedSize+2047)&~2047) >> 2];
+		}
+		else
+		{
+			pHed=(uint32*)Mem::Malloc((HedSize+2047)&~2047);
+		}
+		Dbg_MsgAssert(pHed,("pHed NULL ?"));
+
+	/*
+		char *p = (char*) pHed;  
+		// Hmmm.....
+		for (int i=0 ;i < (HedSize+2047)&~2047;i++)
+		{
+			p[i] = 0x55;
+		}
+	*/						
+		// 9/5/03 - Garrett: Burn fix.  Added retry code to see if HED file actually written.
+		// If it cannot, it frees the buffer and returns NULL.
+		int read_bytes = 0;
+		retry = 0;
+		while (read_bytes != HedSize)
+		{
+			sceLseek(HedId, 0, SCE_SEEK_SET);
+			read_bytes = sceRead(HedId,pHed,HedSize);
+			if (read_bytes != HedSize)
+			{
+				printf("READ ERROR: Retrying read of %s\n", hedFileName );
+				retry++;
+				if (retry == 4)
+				{
+					printf("READ ERROR: aborting read of %s\n", hedFileName );
+					delete pHed;
+					return NULL;					
+				}
+			}	
+		}
+
+		sceClose(HedId);
+	}
+	else
+	{
+		char tempFileName[ 255];
+
+		// Make filename
+		if (no_wad)
+		{
+			sprintf( tempFileName, "host:%shost.HED", filename );
+		}
+		else
+		{
+			sprintf( tempFileName, "host:%s.HED", filename );
+		}
+	
+		void *fp = File::Open( tempFileName, "rb" );
+		if ( !fp )
+		{
+// Mick:   Don't assert, as we want to be able to run without a .HED on the disk, for music
+//			Dbg_MsgAssert( 0,( "Couldn't find hed file %s", tempFileName ));
+			printf( "failed to find %s\n", tempFileName );
+			return ( NULL );
+		}
+		int fileSize = File::GetFileSize( fp );
+		File::Seek( fp, 0, SEEK_SET );
+		
+		if (no_wad)
+		{
+			// We'll want to keep this around
+			pHed = new (Mem::Manager::sHandle().DebugHeap()) uint32[((fileSize+2047)&~2047) >> 2];
+
+#ifdef	__NOPT_ASSERT__
+			if (s_num_hed_buffers < MAX_HED_BUFFERS)
+			{
+				sp_hed_buffers[s_num_hed_buffers++] = pHed;
+			}
+#endif //	__NOPT_ASSERT__
+		}
+		else
+		{
+			pHed=(uint32*)Mem::Malloc((fileSize+2047)&~2047);
+		}
+		Dbg_MsgAssert( pHed,( "Failed to allocate memory for hed %s", tempFileName ));
+		
+		File::Read( pHed, 1, fileSize, fp );
+		File::Close( fp );
+	}
+
+	// Convert to more compact list.
+	int files = 0;
+	int dirs = 0;
+
+	// First, count how many files we have.
+	uint32 * p32 = pHed;
+	while ( p32[0] != 0xffffffff )
+	{
+		files++;
+
+		int len = strlen( (char *)&p32[2] ) + 1;
+		len = ( len + 3 ) & ~3;
+		p32 = (uint32*)(((int)&p32[2]) + len);
+	}
+
+	// Build list of checksums.
+	uint32 *p_offset = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	uint32 *p_size = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	uint32 *dir_check = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	uint32 *file_check = new (Mem::Manager::sHandle().TopDownHeap()) uint32[files];
+	char **p_filename = new (Mem::Manager::sHandle().TopDownHeap()) (char *)[files];
+	int count = 0;
+	p32 = pHed;
+	while ( p32[0] != 0xffffffff )
+	{
+		p_offset[count] = p32[0];
+		p_size[count] = p32[1];
+		p_filename[count] = (char *)&p32[2];
+		get_checksums( (char *)&p32[2], &dir_check[count], &file_check[count] );
+		count++;
+
+		int len = strlen( (char *)&p32[2] ) + 1;
+		len = ( len + 3 ) & ~3;
+		p32 = (uint32*)(((int)&p32[2]) + len);
+	}
+
+	// Second, count how many directories we have.
+	for ( lp = 0; lp < files; lp++ )
+	{
+		bool unique = true;
+		for ( int lp2 = 0; lp2 < lp; lp2++ )
+		{
+			if ( dir_check[lp] == dir_check[lp2] )
+			{
+				unique = false;
+				break;
+			}
+		}
+		if ( unique ) dirs++;
+	}
+
+	// Now, allocate final piece of memory.
+	int size = ( sizeof( SHed ) * ( dirs + 1 ) ) + ( sizeof( SHedFile ) * files );
+	SHed * p_hed = (SHed *)new uint8[size];
+
+	// Fill in directory entries.
+	int dirs_added = 0;
+	for ( lp = 0; lp < files; lp++ )
+	{
+		bool unique = true;
+		for ( int lp2 = 0; lp2 < dirs_added; lp2++ )
+		{
+			if ( p_hed[lp2].Checksum == dir_check[lp] )
+			{
+				unique = false;
+				break;
+			}
+		}
+		if ( unique )
+		{
+			p_hed[dirs_added].Checksum = dir_check[lp];
+			dirs_added++;
+		}
+	}
+
+	// Fill in file entries.
+	SHedFile * p_hed_file = (SHedFile *)&p_hed[(dirs+1)];
+	for ( lp = 0; lp < dirs; lp++ )
+	{
+		p_hed[lp].p_fileList = p_hed_file;
+		int files_this_dir = 0;
+		for ( int lp2 = 0; lp2 < files; lp2++ )
+		{
+			if ( dir_check[lp2] == p_hed[lp].Checksum )
+			{
+				p_hed_file->FileSize = p_size[lp2];
+				if (no_wad)
+				{
+					p_hed_file->p_filename = p_filename[lp2];
+					p_hed_file->FileSize |= SHedFile::mNO_WAD;		// This marks that we are using the no_wad version
+				}
+				else
+				{
+					p_hed_file->Offset = p_offset[lp2];
+				}
+				p_hed_file->Checksum = file_check[lp2];
+				files_this_dir++;
+				p_hed_file++;
+			}
+		}
+		p_hed[lp].numFiles = files_this_dir;
+	}
+	p_hed[dirs].numFiles = 0xffffffff;
+
+	// Get rid of temp data.
+	delete p_filename;
+	delete file_check;
+	delete dir_check;
+	delete p_size;
+	delete p_offset;
+	if (!no_wad)
+	{
+		delete pHed;
+	}
+
+	return p_hed;
+}
+
+} // namespace File
diff --git a/Code/Sys/File/ngps/hed.h b/Code/Sys/File/ngps/hed.h
new file mode 100644
index 0000000..ae1881f
--- /dev/null
+++ b/Code/Sys/File/ngps/hed.h
@@ -0,0 +1,58 @@
+/*	Header file functionality...
+	.Hed files that describe the contents of .Wad files
+	Written by Ken, stolen by Matt*/
+#ifndef __HED_H__
+#define __HED_H__
+
+namespace File
+{
+
+struct SHedFile
+{
+	// Constants
+	enum
+	{
+		mNO_WAD = 0x80000000
+	};
+
+	bool		HasNoWad() const { return FileSize & mNO_WAD; }
+	uint32		GetFileSize() const { return FileSize & (~mNO_WAD); }
+
+	union
+	{
+		uint32 Offset; 			// A SECTOR_SIZE aligned offset of a file within skate3.wad
+		char * p_filename; 		// The filename itself (for no_wad heds)
+	};
+
+	// The file size, which is the raw file size, not rounded up
+	// to a multiple of SECTOR_SIZE.  Highest bit is set if no wad
+	// file is associated with it.
+	uint32 FileSize;
+
+	// Filename checksum (does not include directory).
+	uint32 Checksum;
+};
+
+struct SHed
+{
+	// Number of files in this directory.
+	uint32 numFiles;
+	
+	// Checksum of this directory.
+	uint32 Checksum;
+
+	// Pointer to File list.
+	SHedFile * p_fileList;
+
+	// The filename, which is actually bigger than one byte, tee hee.
+//	const char pFilename[1];
+};
+
+
+SHedFile *FindFileInHed(const char *pFilename, SHed *pHed );
+SHedFile *FindFileInHedUsingChecksum( uint32 checksum, SHed *pHed );
+SHed *LoadHed( const char *filename, bool force_cd = false, bool no_wad = false );
+
+} // namespace File
+
+#endif
diff --git a/Code/Sys/File/ngps/p_AsyncFilesys.cpp b/Code/Sys/File/ngps/p_AsyncFilesys.cpp
new file mode 100644
index 0000000..188157f
--- /dev/null
+++ b/Code/Sys/File/ngps/p_AsyncFilesys.cpp
@@ -0,0 +1,1290 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// p_AsyncFilesys.cpp		GRJ 11 Oct 2002
+//
+// Asynchronous file system
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+namespace File
+{
+
+// Debug info
+#define SHOW_ASYNC_INFO	0
+
+#if SHOW_ASYNC_INFO
+#define ShowAsyncInfo(A...) scePrintf(##A)
+#else
+#define ShowAsyncInfo(A...) 
+#endif
+
+extern SHed *gpHed;		// Made global for p_AsyncFilesystem
+extern unsigned int gWadLSN;
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2AsyncFileHandle::CPs2AsyncFileHandle()
+{
+	m_file_handle_index = -1;
+	m_num_open_requests = 0;
+	mp_non_aligned_buffer = NULL;
+	mp_temp_aligned_buffer = NULL;
+	mp_temp_aligned_buffer_base = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CPs2AsyncFileHandle::~CPs2AsyncFileHandle()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::io_callback(EAsyncFunctionType function, int result, uint32 data)
+{
+	SFileIOResultPacket *p_packet = (SFileIOResultPacket *) data;
+	int return_data = p_packet->m_return_data;
+
+	// Do any special function processing
+	switch (function)
+	{
+	case FUNC_OPEN:
+		m_file_handle_index = result;		// Save IOP file handle index
+		if (result < 0)
+		{
+			//Dbg_MsgAssert(0, ("Error: Async open returned %d", result));
+			m_file_size = 0;
+		} else {
+			m_file_size = return_data;
+			ShowAsyncInfo("io_callback: Open returned handle index %d with filesize %d\n", result, return_data);
+		}
+		break;
+
+	case FUNC_SEEK:
+		if (result < 0)
+		{
+			Dbg_MsgAssert(0, ("Error: Async seek returned %d", result));
+		} else {
+			m_position = result;
+		}
+		break;
+
+	case FUNC_LOAD:
+	case FUNC_READ:
+		if (mp_temp_aligned_buffer && (result > 0))
+		{
+			ShowAsyncInfo("Copying from %x to %x of size %d", mp_temp_aligned_buffer, mp_non_aligned_buffer, result);
+			memcpy(mp_non_aligned_buffer, mp_temp_aligned_buffer, result);
+		}
+
+		// don't break, continue on below
+
+	case FUNC_WRITE:
+		if (result < 0)
+		{
+			Dbg_MsgAssert(0, ("Error: Async IO returned %d", result));
+		} else {
+			m_position += result;
+			if (mp_temp_aligned_buffer)
+			{
+				CPs2AsyncFileLoader::sIAddBufferToFreeList(mp_temp_aligned_buffer_base);
+				mp_non_aligned_buffer = NULL;
+				mp_temp_aligned_buffer = NULL;
+				mp_temp_aligned_buffer_base = NULL;
+			}
+		}
+		break;
+
+	case FUNC_CLOSE:
+		if (get_busy_count() > 1)
+		{
+			scePrintf("********* ERROR: Still other %d requests open after close completed on handle %x and request id %d\n", get_busy_count() - 1, this, p_packet->m_header.opt);
+			//while (1)
+			//	;
+		}
+		break;
+
+	case FUNC_IDLE:
+		// Don't want m_busy to change or user-defined callback to be executed
+		return;
+
+	default:
+		break;
+	}
+
+	// Check to make sure request buffer will be clear
+	if (get_busy_count() > 0)
+	{
+		//Dbg_MsgAssert(m_num_open_requests == 1, ("Sill other open requests"));
+	}
+
+	// Now handle the above p-line stuff
+	CAsyncFileHandle::io_callback(function, result, return_data);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::non_cache_aligned_data_copy(SFileIOResultPacket *p_result, bool last_packet)
+{
+	Dbg_Assert(p_result->m_non_aligned_data_size > 0);
+	
+	int request_id = p_result->m_header.opt;
+	const SRequest *p_request = get_request(request_id);
+	Dbg_Assert(p_request);
+	Dbg_Assert(p_request->mp_buffer);
+
+	// Check if it goes at the start or end
+	if (last_packet)
+	{
+		// end of buffer
+		memcpy((void *) ((uint32) p_request->mp_buffer + (p_request->m_buffer_size - p_result->m_non_aligned_data_size)),
+			   p_result->m_non_aligned_data, p_result->m_non_aligned_data_size);
+	}
+	else
+	{
+		// beginning of buffer
+		memcpy(p_request->mp_buffer, p_result->m_non_aligned_data, p_result->m_non_aligned_data_size);
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CPs2AsyncFileHandle::add_request_id(int request_id, EAsyncFunctionType function, SFileIORequestPacket *p_packet)
+{
+	if (m_num_open_requests >= NUM_REQUESTS)
+	{
+		Dbg_MsgAssert(0, ("Too many open requests for file handle"));
+		return false;
+	}
+
+	// We must disable interrupts here so that the IO interrupts don't change things underneath us.
+	DI();
+
+	// Save buffer info if it is a read
+	if (p_packet->m_request.m_command == FILEIO_READ)
+	{
+		m_open_requests[m_num_open_requests].mp_buffer = (uint8 *) p_packet->m_request.m_param.m_rw.m_buffer;
+		m_open_requests[m_num_open_requests].m_buffer_size = p_packet->m_request.m_param.m_rw.m_size;
+	}
+	else
+	{
+		m_open_requests[m_num_open_requests].mp_buffer = NULL;
+		m_open_requests[m_num_open_requests].m_buffer_size = 0;
+	}
+
+	m_open_requests[m_num_open_requests].m_request_id = request_id;
+	m_open_requests[m_num_open_requests++].m_function = function;
+
+	// And re-enable interrupts
+	EI();
+
+	//scePrintf("Adding request %d to entry #%d for %x with busy %d\n", request_id, (m_num_open_requests-1), this, get_busy_count());
+
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+EAsyncFunctionType	CPs2AsyncFileHandle::get_request_function(int request_id) const
+{
+	// Find request id in list
+	for (int i = 0; i < m_num_open_requests; i++)
+	{
+		if (m_open_requests[i].m_request_id == request_id)
+		{
+			return m_open_requests[i].m_function;
+		}
+	}
+
+	// Request not found
+	Dbg_MsgAssert(0, ("Can't find request %d on %x", request_id, this));
+	return FUNC_IDLE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const CPs2AsyncFileHandle::SRequest *	CPs2AsyncFileHandle::get_request(int request_id) const
+{
+	// Find request id in list
+	for (int i = 0; i < m_num_open_requests; i++)
+	{
+		if (m_open_requests[i].m_request_id == request_id)
+		{
+			return &(m_open_requests[i]);
+		}
+	}
+
+	// Request not found
+	Dbg_MsgAssert(0, ("Can't find request %d on %x", request_id, this));
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CPs2AsyncFileHandle::clear_request_id(int request_id)
+{
+	if (m_num_open_requests <= 0)
+	{
+		Dbg_MsgAssert(0, ("Can't clear request: Open request list empty"));
+		return false;
+	}
+
+	// Find request id in list
+	for (int i = 0; i < m_num_open_requests; i++)
+	{
+		if (m_open_requests[i].m_request_id == request_id)
+		{
+			// Just move end of list to here
+			m_open_requests[i] = m_open_requests[--m_num_open_requests];
+			//scePrintf("Clearing request %d from entry #%d\n", request_id, i);
+			return true;
+		}
+	}
+
+	// Request not found
+	Dbg_MsgAssert(0, ("Can't clear request: Not found"));
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::plat_init()
+{
+	m_file_handle_index = -1;
+	m_num_open_requests = 0;
+	mp_non_aligned_buffer = NULL;
+	mp_temp_aligned_buffer = NULL;
+	mp_temp_aligned_buffer_base = NULL;
+}
+
+volatile bool		CPs2AsyncFileHandle::plat_is_done()
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	return false;
+}
+
+volatile bool		CPs2AsyncFileHandle::plat_is_busy()
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	return false;
+}
+
+bool				CPs2AsyncFileHandle::plat_is_eof() const
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::plat_set_priority( int priority )
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	// Now send the packet
+	SFileIORequestPacket set_packet __attribute__ ((aligned(16)));
+
+	set_packet.m_request.m_command						= FILEIO_SET;
+	set_packet.m_request.m_param.m_set.m_file_handle	= m_file_handle_index;
+	set_packet.m_request.m_param.m_set.m_variable		= FILEIO_VAR_PRIORITY;
+	set_packet.m_request.m_param.m_set.m_value			= priority;
+
+	CFileIOManager::sSendCommand(&set_packet, this, true);
+
+	if (m_blocking)
+	{
+		WaitForIO();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::plat_set_stream( bool stream )
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	// Now send the packet
+	SFileIORequestPacket set_packet __attribute__ ((aligned(16)));
+
+	set_packet.m_request.m_command						= FILEIO_SET;
+	set_packet.m_request.m_param.m_set.m_file_handle	= m_file_handle_index;
+	set_packet.m_request.m_param.m_set.m_variable		= FILEIO_VAR_STREAM;
+	set_packet.m_request.m_param.m_set.m_value			= stream;
+
+	CFileIOManager::sSendCommand(&set_packet, this, true);
+
+	if (m_blocking)
+	{
+		WaitForIO();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::plat_set_destination( EAsyncMemoryType destination )
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	// Now send the packet
+	SFileIORequestPacket set_packet __attribute__ ((aligned(16)));
+
+	set_packet.m_request.m_command						= FILEIO_SET;
+	set_packet.m_request.m_param.m_set.m_file_handle	= m_file_handle_index;
+	set_packet.m_request.m_param.m_set.m_variable		= FILEIO_VAR_DESTINATION;
+	set_packet.m_request.m_param.m_set.m_value			= destination;
+
+	CFileIOManager::sSendCommand(&set_packet, this, true);
+
+	if (m_blocking)
+	{
+		WaitForIO();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::plat_set_buffer_size( size_t buffer_size )
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	// Now send the packet
+	SFileIORequestPacket set_packet __attribute__ ((aligned(16)));
+
+	set_packet.m_request.m_command						= FILEIO_SET;
+	set_packet.m_request.m_param.m_set.m_file_handle	= m_file_handle_index;
+	set_packet.m_request.m_param.m_set.m_variable		= FILEIO_VAR_BUFFER_SIZE;
+	set_packet.m_request.m_param.m_set.m_value			= buffer_size;
+
+	CFileIOManager::sSendCommand(&set_packet, this, true);
+
+	if (m_blocking)
+	{
+		WaitForIO();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileHandle::plat_set_blocking( bool block )
+{
+	// IOP doesn't need to be notified
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+size_t				CPs2AsyncFileHandle::plat_load(void *p_buffer)
+{
+	// This function will just get the file size and do one big read
+	// It is not truly asynchronous now because of the seek wait
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	if (m_position != 0)
+	{
+		ShowAsyncInfo("plat_load: Doing seek.\n");
+		// Now put at the beginning
+		Seek(0, SEEK_SET);
+		WaitForIO();
+
+		m_current_function = FUNC_LOAD;
+	}
+
+	ShowAsyncInfo("plat_load: Reading into buffer %x.\n", p_buffer);
+	// And do the read
+	return plat_read(p_buffer, 1, GetFileSize());
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+size_t				CPs2AsyncFileHandle::plat_read(void *p_buffer, size_t size, size_t count)
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+	//Dbg_MsgAssert((((uint32) p_buffer) & 0xF) == 0, ("Buffer %x needs to be quadword aligned", p_buffer));
+
+	// Now send the packet
+	SFileIORequestPacket read_packet __attribute__ ((aligned(16)));
+
+	uint32 non_aligned_cache_start_bytes = 0;
+	uint32 non_aligned_cache_end_bytes = 0;
+
+	// Read in non-aligned bytes first (this is a temp hack)
+	if (m_mem_destination == MEM_EE)
+	{
+		// only look for 16-byte alignment here
+		bool non_aligned_bytes = ((uint32) p_buffer) & 0xF;
+
+		// Also check to see if we aren't cache aligned on either end of a small buffer, since we assume the
+		// cache alignment problem is at the end of a small buffer.
+		int non_aligned_cache_bytes = ((uint32) p_buffer) & FILEIO_CACHE_BLOCK_NONALIGN_MASK;
+		if (non_aligned_cache_bytes)
+		{
+			non_aligned_cache_bytes = FILEIO_CACHE_BLOCK_SIZE - non_aligned_cache_bytes;
+		}
+		//bool non_aligned_cache_size = ((size * count) - non_aligned_cache_bytes) & FILEIO_CACHE_BLOCK_NONALIGN_MASK;
+
+		if (non_aligned_bytes || ((non_aligned_cache_bytes /*|| non_aligned_cache_size*/) && (size * count) < FILEIO_BUFFER_SIZE))
+		{
+			Dbg_MsgAssert(!mp_temp_aligned_buffer_base, ("Handling two small reads in succession not implemented yet"));
+
+			int aligned_size = ((size * count) + (2 * (FILEIO_CACHE_BLOCK_SIZE - 1))) & FILEIO_CACHE_BLOCK_ALIGN_MASK;
+			mp_non_aligned_buffer = (uint8 *) p_buffer;
+			mp_temp_aligned_buffer_base = CPs2AsyncFileLoader::sGetBuffer(aligned_size);
+			mp_temp_aligned_buffer = (uint8 *) (((uint32) mp_temp_aligned_buffer_base + (FILEIO_CACHE_BLOCK_SIZE - 1)) & FILEIO_CACHE_BLOCK_ALIGN_MASK);
+			p_buffer = mp_temp_aligned_buffer;
+		}
+		else
+		{
+			// Now check for cache-alignment problems that will be handled with the messages
+			non_aligned_cache_start_bytes = (((uint32) p_buffer) & FILEIO_CACHE_BLOCK_NONALIGN_MASK);
+			if (non_aligned_cache_start_bytes)
+			{
+				non_aligned_cache_start_bytes = FILEIO_CACHE_BLOCK_SIZE - non_aligned_cache_start_bytes;
+			}
+			non_aligned_cache_end_bytes = ((size * count) - non_aligned_cache_start_bytes) & FILEIO_CACHE_BLOCK_NONALIGN_MASK;
+		}
+	}
+
+	read_packet.m_request.m_command								 = FILEIO_READ;
+	read_packet.m_request.m_param.m_rw.m_file_handle			 = m_file_handle_index;
+	read_packet.m_request.m_param.m_rw.m_buffer					 = p_buffer;
+	read_packet.m_request.m_param.m_rw.m_size					 = size * count;
+	read_packet.m_request.m_param.m_rw.m_non_aligned_start_bytes = non_aligned_cache_start_bytes;
+	read_packet.m_request.m_param.m_rw.m_non_aligned_end_bytes	 = non_aligned_cache_end_bytes;
+
+	// Mark as busy before sending command
+	inc_busy_count();
+
+	// Garrett (4/18/03): According to the Sony Newsgroups, a FlushCache is not needed if only 1 packet is sent.
+	// Only the additional packet needs the flush.  I think this was helping the issue of non-64 byte aligned
+	// read buffers.  We should try taking this out after the buffers are aligned.
+	FlushCache(WRITEBACK_DCACHE);		// This one is needed, but I think invalidating the cache will work, instead
+
+	CFileIOManager::sSendCommand(&read_packet, this, true);
+
+	Dbg_Assert(((non_aligned_cache_start_bytes == 0) || (non_aligned_cache_end_bytes == 0)) || ((size * count) >= FILEIO_BUFFER_SIZE));
+
+	if (m_blocking)
+	{
+		return WaitForIO();
+	} else {
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+size_t				CPs2AsyncFileHandle::plat_write(void *p_buffer, size_t size, size_t count)
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+	Dbg_MsgAssert((((uint32) p_buffer) & 0xF) == 0, ("Buffer %x needs to be quadword aligned", p_buffer));
+
+	// Now send the packet
+	SFileIORequestPacket write_packet __attribute__ ((aligned(16)));
+
+	write_packet.m_request.m_command					= FILEIO_WRITE;
+	write_packet.m_request.m_param.m_rw.m_file_handle	= m_file_handle_index;
+	write_packet.m_request.m_param.m_rw.m_buffer		= p_buffer;
+	write_packet.m_request.m_param.m_rw.m_size			= size * count;
+
+	// Mark as busy before sending command
+	inc_busy_count();
+
+	FlushCache(WRITEBACK_DCACHE);		// This one is needed, but I think invalidating the cache will work, instead
+
+	int req_id = CFileIOManager::sSendCommand(&write_packet, this, true);
+
+	// Wait for result
+	CFileIOManager::sWaitRequestCompletion(req_id);
+
+	return CFileIOManager::sGetRequestResult(write_packet.m_header.opt);
+}
+
+char *				CPs2AsyncFileHandle::plat_get_s(char *p_buffer, int maxlen)
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					CPs2AsyncFileHandle::plat_seek(long offset, int origin)
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	// Now send the packet
+	SFileIORequestPacket seek_packet __attribute__ ((aligned(16)));
+
+	seek_packet.m_request.m_command						= FILEIO_SEEK;
+	seek_packet.m_request.m_param.m_seek.m_file_handle	= m_file_handle_index;
+	seek_packet.m_request.m_param.m_seek.m_offset		= offset;
+	seek_packet.m_request.m_param.m_seek.m_origin		= origin;
+
+	// Mark as busy before sending command
+	inc_busy_count();
+
+	CFileIOManager::sSendCommand(&seek_packet, this, true);
+
+	if (m_blocking)
+	{
+		return WaitForIO();
+	} else {
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CPs2AsyncFileHandle::plat_open(const char *filename)
+{
+	// Init vars
+	m_position = 0;
+
+	// Now send the packet
+	const char *p_continued_filename = NULL;
+	SFileIORequestPacket open_packet __attribute__ ((aligned(16)));
+
+	open_packet.m_request.m_command											= FILEIO_OPEN;
+	if (Config::CD())
+	{
+		if ( !gpHed )
+		{
+			return ( false );
+		}
+		SHedFile *tempHed = FindFileInHed( filename, gpHed );
+		if ( !tempHed )
+		{
+			//Dbg_MsgAssert( 0,( "File %s cannot be found in Hed file.", filename ));
+			Dbg_Message( "File %s cannot be found in Hed file.", filename );
+			return ( false );
+		}
+
+		m_file_size = tempHed->GetFileSize();
+
+		open_packet.m_request.m_param.m_open.m_file.m_raw_name.m_sector_number	= gWadLSN + (tempHed->Offset / SECTOR_SIZE);
+		open_packet.m_request.m_param.m_open.m_file.m_raw_name.m_file_size		= m_file_size;
+		open_packet.m_request.m_param.m_open.m_host_read 						= FILEIO_HOSTOPEN_FALSE;
+
+		ShowAsyncInfo("Sending open command for file %s of size %d and offset %d at sector %d\n", filename, m_file_size, tempHed->Offset, gWadLSN + (tempHed->Offset / SECTOR_SIZE));
+	} else {
+		//Dbg_MsgAssert(strlen(filename) < FILEIO_MAX_FILENAME_SIZE, ("Can't open file %s: name longer than %d chars", filename, FILEIO_MAX_FILENAME_SIZE - 1));
+
+		// Copy filename and check if we need to insert a "host0:" into the name
+		uint chars_copied = FILEIO_MAX_FILENAME_SIZE - 1;
+		if (strchr(filename, ':'))
+		{
+			strncpy(open_packet.m_request.m_param.m_open.m_file.m_filename, filename, chars_copied);
+		} else {
+			//Dbg_MsgAssert(strlen(filename) < (FILEIO_MAX_FILENAME_SIZE - 5), ("Can't open file %s: name longer than %d chars", filename, FILEIO_MAX_FILENAME_SIZE - 6));
+			chars_copied -= 5;
+			strcpy(open_packet.m_request.m_param.m_open.m_file.m_filename, "host:");
+			strncpy(&open_packet.m_request.m_param.m_open.m_file.m_filename[5], filename, chars_copied);
+		}
+
+		// Check for long filename
+		if (chars_copied < strlen(filename))
+		{
+			p_continued_filename = &(filename[chars_copied]);
+			open_packet.m_request.m_param.m_open.m_host_read = FILEIO_HOSTOPEN_EXTEND_NAME;
+		}
+		else
+		{
+			open_packet.m_request.m_param.m_open.m_host_read = FILEIO_HOSTOPEN_TRUE;
+		}
+
+
+		ShowAsyncInfo("Sending host open command for file %s\n", filename);
+	}
+	open_packet.m_request.m_param.m_open.m_memory_dest 						= m_mem_destination;
+	open_packet.m_request.m_param.m_open.m_buffer_size 						= m_buffer_size;
+	open_packet.m_request.m_param.m_open.m_priority 						= m_priority;
+	open_packet.m_request.m_param.m_open.m_attributes						= 0;		// need to define this
+
+	// Mark as busy before sending command
+	inc_busy_count();
+
+	uint request_id = CFileIOManager::sSendCommand(&open_packet, this, true);
+
+	if (p_continued_filename)
+	{
+		Dbg_MsgAssert(strlen(p_continued_filename) < FILEIO_MAX_FILENAME_SIZE, ("Can't open file %s: name longer than %d chars", filename, (FILEIO_MAX_FILENAME_SIZE - 1) * 2));
+		strcpy(open_packet.m_request.m_param.m_open.m_file.m_filename, p_continued_filename);
+
+		open_packet.m_request.m_command	= FILEIO_CONT;
+
+		#ifdef	__NOPT_ASSERT__
+		uint cont_request_id = 
+		#endif
+		CFileIOManager::sSendCommand(&open_packet, this, true, request_id);
+		Dbg_Assert(cont_request_id == request_id);
+	}
+
+	if (m_blocking)
+	{
+		return WaitForIO() >= 0;
+	} else {
+		// Copy request ID into file handle to use as temp file handle
+		m_file_handle_index = (int) (FILEIO_TEMP_HANDLE_FLAG | request_id);
+		return true;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CPs2AsyncFileHandle::raw_open(unsigned int lsn, bool blocking, int priority)
+{
+	Dbg_MsgAssert(!IsBusy(), ("Can't open raw file %d: asynchronous file handle already busy.", lsn));
+	Dbg_MsgAssert(!mp_pre_file, ("Can't open raw file %d: already open as a PRE file.", lsn));
+
+	// Make sure we are using the CD
+	if (!Config::CD())
+	{
+		Dbg_MsgAssert(0, ("Raw Open not supported in host mode"));
+		return false;
+	}
+
+	m_priority = priority;
+	m_blocking = blocking;
+
+	m_current_function = FUNC_OPEN;
+
+	// Init vars
+	m_position = 0;
+
+	// Now send the packet
+	SFileIORequestPacket open_packet __attribute__ ((aligned(16)));
+
+	// Make it the max file size since a the WADs don't keep track of how big they are
+	m_file_size = MAX_FILE_SIZE;
+
+	open_packet.m_request.m_command											= FILEIO_OPEN;
+	open_packet.m_request.m_param.m_open.m_file.m_raw_name.m_sector_number	= lsn;
+	open_packet.m_request.m_param.m_open.m_file.m_raw_name.m_file_size		= m_file_size;
+	open_packet.m_request.m_param.m_open.m_host_read 						= FILEIO_HOSTOPEN_FALSE;
+	open_packet.m_request.m_param.m_open.m_memory_dest 						= m_mem_destination;
+	open_packet.m_request.m_param.m_open.m_buffer_size 						= m_buffer_size;
+	open_packet.m_request.m_param.m_open.m_priority 						= m_priority;
+	open_packet.m_request.m_param.m_open.m_attributes						= 0;		// need to define this
+
+	ShowAsyncInfo("Sending raw open command for file of size %d at sector %d\n", m_file_size, lsn);
+
+	// Mark as busy before sending command
+	inc_busy_count();
+
+	uint request_id = CFileIOManager::sSendCommand(&open_packet, this, true);
+
+	if (m_blocking)
+	{
+		return WaitForIO() >= 0;
+	} else {
+		// Copy request ID into file handle to use as temp file handle
+		m_file_handle_index = (int) (FILEIO_TEMP_HANDLE_FLAG | request_id);
+		return true;
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CPs2AsyncFileHandle::plat_close()
+{
+	//Dbg_MsgAssert(m_file_handle_index != -1, ("Invalid IOP file handle index: %d", m_file_handle_index));
+
+	// Now send the packet
+	SFileIORequestPacket close_packet __attribute__ ((aligned(16)));
+
+	close_packet.m_request.m_command						= FILEIO_CLOSE;
+	close_packet.m_request.m_param.m_close.m_file_handle	= m_file_handle_index;
+
+	// Mark as busy before sending command
+	inc_busy_count();
+
+	CFileIOManager::sSendCommand(&close_packet, this, true);
+
+	m_file_handle_index = -1;
+
+	if (m_blocking)
+	{
+		return WaitForIO();
+	} else {
+		return 0;
+	}
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+CPs2AsyncFileLoader::SAlignBuffer	CPs2AsyncFileLoader::s_buffer_list[BUFFER_LIST_SIZE];
+//volatile int		CPs2AsyncFileLoader::s_free_buffer_list_size = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CAsyncFileHandle *	CPs2AsyncFileLoader::sRawOpen( unsigned int lsn, bool blocking, int priority )
+{
+	CPs2AsyncFileHandle *p_file_handle = static_cast(s_get_file_handle());
+
+	if (p_file_handle)
+	{
+		if (!p_file_handle->raw_open(lsn, blocking, priority))
+		{
+			//Dbg_MsgAssert(0, ("Error opening Async file %s", filename));
+			s_free_file_handle(p_file_handle);
+			return NULL;
+		}
+
+		return p_file_handle;
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Out of Async handles"));
+		return NULL;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint8 *				CPs2AsyncFileLoader::sGetBuffer( uint size )
+{
+#if 0
+	if (size > ALIGN_BUFFER_SIZE)
+	{
+		Dbg_Message("sGetBuffer() is trying to allocate %d.  Max buffer size is %d", size, ALIGN_BUFFER_SIZE);
+		Dbg_MsgAssert(0, ("sGetBuffer() is trying to allocate %d.  Max buffer size is %d", size, ALIGN_BUFFER_SIZE));
+	}
+
+	if (s_free_buffer_list_size == 0)
+	{
+		Dbg_Message("sGetBuffer() out of buffers");
+		Dbg_MsgAssert(0, ("sGetBuffer() out of buffers"));
+	}
+#endif
+
+	//Dbg_Message("***** Getting async buffer of size %d", size);
+
+	volatile uint8 *p_buffer = NULL;
+	// Make sure interrupts are off
+	DI();
+	for (int i = 0; i < BUFFER_LIST_SIZE; i++)
+	{
+		if ((s_buffer_list[i].m_size >= size) && !s_buffer_list[i].m_used)
+		{
+			p_buffer = s_buffer_list[i].mp_buffer;
+			s_buffer_list[i].m_used = true;
+		}
+	}
+	EI();
+
+	Dbg_MsgAssert(p_buffer, ("Couldn't allocate align buffer of size %d", size));
+
+	return (uint8 *) p_buffer;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileLoader::sAddBufferToFreeList( uint8 *p_dealloc_buffer )
+{
+	// Make sure interrupts are off
+	DI();
+
+	sIAddBufferToFreeList(p_dealloc_buffer);
+
+	EI();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileLoader::sIAddBufferToFreeList( uint8 *p_dealloc_buffer )
+{
+	//scePrintf("***** Freeing async buffer\n");
+
+#if 0 //def __NOPT_ASSERT__
+	if (s_free_buffer_list_size >= FREE_LIST_SIZE)
+	{
+		scePrintf("CPs2AsyncFileLoader::sIAddBufferToFreeList(): Free list full.  Tell Garrett.\n");
+		while (s_free_buffer_list_size >= FREE_LIST_SIZE)		// We are basically stuck here
+			;
+	}
+#endif // __NOPT_ASSERT__
+
+	for (int i = 0; i < BUFFER_LIST_SIZE; i++)
+	{
+		if (s_buffer_list[i].mp_buffer == p_dealloc_buffer)
+		{
+#ifdef __NOPT_ASSERT__
+			if (!s_buffer_list[i].m_used)
+			{
+				scePrintf("CPs2AsyncFileLoader::sIAddBufferToFreeList(): Freeing a buffer that is already free.  Tell Garrett.\n");
+				while (1)
+					;
+			}
+#endif // __NOPT_ASSERT__
+
+			s_buffer_list[i].m_used = false;
+			return;
+		}
+	}
+
+#ifdef __NOPT_ASSERT__
+	scePrintf("CPs2AsyncFileLoader::sIAddBufferToFreeList(): Pointer wrong value.  Tell Garrett.\n");
+	while (1)
+		;
+#endif // __NOPT_ASSERT__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileLoader::sAllocateFreeListBuffers( )
+{
+	int size_chunk = (ALIGN_MAX_BUFFER_SIZE - ALIGN_MIN_BUFFER_SIZE) / (BUFFER_LIST_SIZE - 1);
+	for (int i = 0; i < BUFFER_LIST_SIZE; i++)
+	{
+		int buffer_size = ALIGN_MIN_BUFFER_SIZE + (size_chunk * i);
+		s_buffer_list[i].mp_buffer = new uint8[buffer_size];
+		s_buffer_list[i].m_size = buffer_size;
+		s_buffer_list[i].m_used = false;
+		
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CPs2AsyncFileLoader::sDeallocateFreeListBuffers( )
+{
+	Dbg_Assert(0);		// We shouldn't be calling this now
+
+#if 0 
+	// Make sure interrupts are off
+	DI();
+
+	for (int i = 0; i < s_free_buffer_list_size; i++)
+	{
+		delete [] s_free_buffer_list[i];
+		s_free_buffer_list[i] = NULL;
+	}
+	s_free_buffer_list_size = 0;
+
+	EI();
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+sceSifCmdData	CFileIOManager::s_cmdbuffer[FILEIO_NUM_COMMAND_HANDLERS];
+
+int				CFileIOManager::s_cur_request_id = 0;
+
+CFileIOManager::SOpenRequest	CFileIOManager::s_open_requests[NUM_REQUESTS];
+int								CFileIOManager::s_free_request_index = 0;
+
+/******************************************************************/
+/*                                                                */
+/*   CALLBACK                                                     */
+/*                                                                */
+/******************************************************************/
+
+void 				CFileIOManager::s_result_callback(void *p, void *q)
+{
+	SFileIOResultPacket *h = (SFileIOResultPacket *) p;
+	int request_id = h->m_header.opt;
+
+	SOpenRequest *p_request = s_find_request(request_id);
+	Dbg_MsgAssert(p_request, ("Can't find request id # %d", request_id));
+
+	p_request->m_result = h->m_return_value;
+	p_request->m_done = h->m_done;
+
+	//scePrintf( "Got IOP request %d result value %d back with done = %d\n", request_id, p_request->m_result, p_request->m_done );
+
+	// Call file handle callback if request is completed
+	if (p_request->mp_cur_file)
+	{
+		// Check if we got any non-aligned data back
+		if (h->m_non_aligned_data_size > 0)
+		{
+			p_request->mp_cur_file->non_cache_aligned_data_copy(h, p_request->m_done);
+		}
+
+		if (p_request->m_done)
+		{
+			//p_request->mp_cur_file->io_callback(p_request->mp_cur_file->get_request_function(request_id), p_request->m_result, h->m_return_data);
+			p_request->mp_cur_file->io_callback(p_request->mp_cur_file->get_request_function(request_id), p_request->m_result, (uint32) h);
+			p_request->mp_cur_file->clear_request_id(request_id);
+		}
+	}
+
+	ShowAsyncInfo( "Got IOP request %d result value %d back with done = %d\n", request_id, p_request->m_result, p_request->m_done );
+	
+	ExitHandler();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CFileIOManager::SOpenRequest *		CFileIOManager::s_find_request(int request_id)
+{
+	// Start at the last used index, probably the one used
+	int index = s_prev_open_index(s_free_request_index);
+
+	SOpenRequest *p_start_request = &(s_open_requests[index]);
+	SOpenRequest *p_request = p_start_request;
+
+	do
+	{
+		if (p_request->m_id == request_id)
+		{
+			return p_request;
+		}
+		
+		index = s_prev_open_index(index);
+		p_request = &(s_open_requests[index]);
+	} while (p_request != p_start_request);
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CFileIOManager::sInit()
+{
+	// Communicate with the IOP side
+    sceSifInitRpc(0);
+	DI();
+
+	ShowAsyncInfo( "Setting up Sif Cmd with FileIO IRX...\n" );
+	sceSifSetCmdBuffer(&s_cmdbuffer[0], FILEIO_NUM_COMMAND_HANDLERS);
+	sceSifAddCmdHandler(FILEIO_RESULT_COMMAND, s_result_callback, NULL);
+
+	EI();
+
+	// Clear out pending requests
+	for (int i = 0; i < NUM_REQUESTS; i++)
+	{
+		s_open_requests[i].m_done = true;
+		s_open_requests[i].m_id = -1;
+		s_open_requests[i].m_result = -1;
+	}
+	s_free_request_index = 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					CFileIOManager::sSendCommand(SFileIORequestPacket *p_packet, CPs2AsyncFileHandle *p_file, bool wait_for_send, int continuation_request)
+{
+	SOpenRequest *p_request;
+
+	if (continuation_request >= 0)
+	{
+		p_request = s_find_request(continuation_request);
+		Dbg_MsgAssert(p_request, ("Can't find open request %d to continue", continuation_request));
+
+		// Just in case they didn't wait before
+		sWaitSendCompletion(p_request->m_id);
+	}
+	else
+	{
+		p_request = &(s_open_requests[s_free_request_index]);
+		Dbg_MsgAssert(p_request->m_done, ("Too many open requests"));
+		s_free_request_index = s_next_open_index(s_free_request_index);
+
+		int request_id = s_cur_request_id++;
+
+		p_request->m_done		= false;		// Callback will change it to true
+		p_request->m_id			= request_id;
+		p_request->mp_cur_file	= p_file;
+		if (p_file)
+		{
+			p_file->add_request_id(request_id, p_file->m_current_function, p_packet);
+		}
+
+		p_packet->m_header.opt = request_id;
+	}
+
+	p_request->m_sif_cmd_id = sceSifSendCmd(FILEIO_REQUEST_COMMAND, p_packet, sizeof(SFileIORequestPacket), 0, 0, 0);
+
+	if (wait_for_send)
+	{
+		sWaitSendCompletion(p_request->m_id);
+	}
+
+	return p_request->m_id;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CFileIOManager::sWaitSendCompletion(int request_id)
+{
+	SOpenRequest *p_request = s_find_request(request_id);
+	Dbg_MsgAssert(p_request, ("Can't find request id # %d", request_id));
+
+	while(sceSifDmaStat(p_request->m_sif_cmd_id) >= 0)
+		;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CFileIOManager::sWaitRequestCompletion(int request_id)
+{
+	SOpenRequest *p_request = s_find_request(request_id);
+	Dbg_MsgAssert(p_request, ("Can't find request id # %d", request_id));
+
+	while (!p_request->CommandDone())
+		;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+volatile bool		CFileIOManager::sCommandDone(int request_id)
+{
+	SOpenRequest *p_request = s_find_request(request_id);
+	Dbg_MsgAssert(p_request, ("Can't find request id # %d", request_id));
+
+	return p_request->m_done;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int					CFileIOManager::sGetRequestResult(int request_id)
+{
+	SOpenRequest *p_request = s_find_request(request_id);
+	Dbg_MsgAssert(p_request, ("Can't find request id # %d", request_id));
+
+	return p_request->m_result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::s_plat_init()
+{
+	for (int i = 0; i < MAX_FILE_HANDLES; i++)
+	{
+		if (s_file_handles[i])
+		{
+			s_file_handles[i]->init();
+		}
+		else
+		{
+			s_file_handles[i] = new CPs2AsyncFileHandle;
+		}
+	}
+
+	// Allocate EE align buffers
+	CPs2AsyncFileLoader::sAllocateFreeListBuffers();
+
+	// Init the FileIO Manager
+	CFileIOManager::sInit();
+
+	// Now send the first packet
+	SFileIORequestPacket init_packet __attribute__ ((aligned(16)));
+
+	init_packet.m_request.m_command						= FILEIO_INIT;
+	init_packet.m_request.m_param.m_init.m_buffer_size	= DEFAULT_BUFFER_SIZE;
+	init_packet.m_request.m_param.m_init.m_priority		= DEFAULT_ASYNC_PRIORITY;
+	init_packet.m_request.m_param.m_init.m_memory_dest	= DEFAULT_MEMORY_TYPE;
+	init_packet.m_request.m_param.m_init.m_init_cd_device = Config::CD();
+
+	int req_id = CFileIOManager::sSendCommand(&init_packet, NULL, true);
+
+	Dbg_Message("Sending first init command to fileio.irx: waiting for completion");
+
+	// Wait for result (no async inits)
+	CFileIOManager::sWaitRequestCompletion(req_id);
+
+	Dbg_Message("Sent first init command to fileio.irx.");
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::s_plat_cleanup()
+{
+	for (int i = 0; i < MAX_FILE_HANDLES; i++)
+	{
+		if (s_file_handles[i])
+		{
+			delete s_file_handles[i];
+
+			s_file_handles[i] = NULL;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::s_plat_async_supported()
+{
+	// Works on the PS2
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool				CAsyncFileLoader::s_plat_exist(const char *filename)
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::s_plat_update(void)
+{
+	//// Free any buffers let go during an interrupt
+	//CPs2AsyncFileLoader::sDeallocateFreeListBuffers();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void				CAsyncFileLoader::s_plat_swap_callback_list()
+{
+	// We must disable interrupts here so that the IO interrupts don't change things underneath us.
+	DI();
+
+	s_cur_callback_list_index = s_cur_callback_list_index ^ 1;	// Swap Indexes
+	s_new_io_completion = false;								// And clear flag
+
+#ifdef __NOPT_ASSERT__
+	bool new_list_clear = s_num_callbacks[s_cur_callback_list_index] == 0;
+#endif // __NOPT_ASSERT__
+
+	// And re-enable interrupts
+	EI();
+
+	Dbg_MsgAssert(new_list_clear, ("Async IO swapped callback list isn't empty"));
+}
+
+}
+
+
+
diff --git a/Code/Sys/File/ngps/p_AsyncFilesys.h b/Code/Sys/File/ngps/p_AsyncFilesys.h
new file mode 100644
index 0000000..6b58c2c
--- /dev/null
+++ b/Code/Sys/File/ngps/p_AsyncFilesys.h
@@ -0,0 +1,256 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			File													**
+**																			**
+**	Created:		10/11/02	-	grj										**
+**																			**
+**	File name:		core/sys/p_AsyncFilesys.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_FILE_P_ASYNC_FILESYS_H
+#define	__SYS_FILE_P_ASYNC_FILESYS_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+// Forward declarations
+class CFileIOManager;
+class CPs2AsyncFileLoader;
+
+/////////////////////////////////////////////////////////////////////////////////////
+// The asynchronous file class
+//
+class CPs2AsyncFileHandle : public CAsyncFileHandle
+{
+public:
+
+protected:
+
+						CPs2AsyncFileHandle();
+	virtual 			~CPs2AsyncFileHandle();
+
+private:
+	// Constants
+	enum
+	{
+		NUM_REQUESTS = 16
+	};
+
+	// Request data
+	struct SRequest
+	{
+		int					m_request_id;
+		EAsyncFunctionType	m_function;
+		uint8 *				mp_buffer;
+		int					m_buffer_size;
+	};
+
+	// Callback functions
+	virtual void		io_callback(EAsyncFunctionType function, int result, uint32 data);
+
+	volatile int		m_file_handle_index;
+
+	// Open requests
+	SRequest		  	m_open_requests[NUM_REQUESTS];
+	volatile int		m_num_open_requests;
+
+	// Non-aligned buffer IO
+	uint8 *				mp_non_aligned_buffer;
+	uint8 *				mp_temp_aligned_buffer;
+	uint8 *				mp_temp_aligned_buffer_base;
+
+	bool				add_request_id(int request_id, EAsyncFunctionType function, SFileIORequestPacket *p_packet);
+	EAsyncFunctionType	get_request_function(int request_id) const;
+	const SRequest *	get_request(int request_id) const;
+	bool				clear_request_id(int request_id);
+
+	// PS2 only IO calls
+	bool				raw_open(unsigned int lsn, bool blocking, int priority);
+
+	// PS2 internal calls
+	void				non_cache_aligned_data_copy(SFileIOResultPacket *p_result, bool last_packet);
+
+	// platform-specific calls
+	virtual void		plat_init( void );
+
+	virtual bool		plat_open( const char *filename );
+	virtual bool		plat_close( void );
+
+	virtual volatile bool	plat_is_done( void );
+	virtual volatile bool	plat_is_busy( void );
+	virtual bool		plat_is_eof( void ) const;
+
+	virtual void		plat_set_priority( int priority );
+	virtual void		plat_set_stream( bool stream );
+	virtual void		plat_set_destination( EAsyncMemoryType destination );
+	virtual void		plat_set_buffer_size( size_t buffer_size );
+	virtual void		plat_set_blocking( bool block );
+
+	virtual size_t		plat_load( void *p_buffer );
+	virtual size_t		plat_read( void *p_buffer, size_t size, size_t count );
+	virtual size_t		plat_write( void *p_buffer, size_t size, size_t count );
+	virtual char *		plat_get_s( char *p_buffer, int maxlen );
+	virtual int			plat_seek( long offset, int origin );
+
+	// Friends
+	friend CAsyncFileLoader;
+	friend CPs2AsyncFileLoader;
+	friend CFileIOManager;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+// The interface to start and end async loads
+//
+class CPs2AsyncFileLoader : public CAsyncFileLoader
+{
+public:
+	// Opens a "file" by specifying the starting logical sector number of a file
+	static CAsyncFileHandle *	sRawOpen( unsigned int lsn, bool blocking = true, int priority = DEFAULT_ASYNC_PRIORITY );
+
+	// Deallocation of buffers cannot be done in interrupt mode.  The FileLoader update function will need to
+	// clear them out.
+	static void					sAllocateFreeListBuffers( );
+	static void					sDeallocateFreeListBuffers( );
+
+	static uint8 *				sGetBuffer( uint size );								// thread only
+	static void					sAddBufferToFreeList( uint8 *p_dealloc_buffer );		// thread only
+	static void					sIAddBufferToFreeList( uint8 *p_dealloc_buffer );		// interrupt only
+
+private:
+	// Constants
+	enum
+	{
+		ALIGN_MAX_BUFFER_SIZE = 17000,
+		ALIGN_MIN_BUFFER_SIZE = 4096,
+		BUFFER_LIST_SIZE = 2
+	};
+
+	// Buffer struct
+	struct SAlignBuffer
+	{
+		uint					m_size;
+		volatile bool			m_used;
+		volatile uint8 *		mp_buffer;
+	};
+
+	static SAlignBuffer			s_buffer_list[BUFFER_LIST_SIZE];
+//	static volatile int			s_free_buffer_list_size;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+// File IO manager that communicates with the IOP module
+//
+class CFileIOManager
+{
+public:
+	static void					sInit();
+
+	// IOP command functions
+	static int					sSendCommand(SFileIORequestPacket *p_packet, CPs2AsyncFileHandle *p_file = NULL, bool wait_for_send = false,
+											 int continuation_request = -1);
+	static void					sWaitSendCompletion(int request_id);
+	static void					sWaitRequestCompletion(int request_id);
+
+	static volatile bool		sCommandDone(int request_id);
+
+	static int					sGetRequestResult(int request_id);
+
+private:
+	// Constants
+	enum
+	{
+		NUM_REQUESTS = 64
+	};
+
+	struct SOpenRequest
+	{
+		inline volatile bool	CommandDone();
+
+		CPs2AsyncFileHandle *	mp_cur_file;
+		int						m_id;
+		int						m_sif_cmd_id;
+		volatile bool 			m_done;
+		volatile int 			m_result;
+	};
+
+	static void 				s_result_callback(void *p, void *q);
+
+	static int					s_prev_open_index(int index) { return (index > 0) ? index - 1 : NUM_REQUESTS - 1; }
+	static int					s_next_open_index(int index) { return (index + 1) % NUM_REQUESTS; }
+	static SOpenRequest *		s_find_request(int request_id);
+
+	// Data for the actual SifCmd transfers
+	static sceSifCmdData 		s_cmdbuffer[FILEIO_NUM_COMMAND_HANDLERS];
+
+	static int 					s_cur_request_id;
+
+	// Open requests
+	static SOpenRequest			s_open_requests[NUM_REQUESTS];
+	static int					s_free_request_index;
+};
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline volatile bool		CFileIOManager::SOpenRequest::CommandDone()
+{
+	return m_done;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace File
+
+#endif  // __SYS_FILE_P_ASYNC_FILESYS_H
diff --git a/Code/Sys/File/ngps/p_filesys.cpp b/Code/Sys/File/ngps/p_filesys.cpp
new file mode 100644
index 0000000..d023b31
--- /dev/null
+++ b/Code/Sys/File/ngps/p_filesys.cpp
@@ -0,0 +1,1878 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			File IO (File) 											**
+**																			**
+**	File name:		p_filesys.cpp											**
+**																			**
+**	Created by:		03/20/00	-	mjb										**
+**																			**
+**	Description:	PS2 File System											**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#ifdef	PAL
+#define	__DVD_ONLY__ 0				// 0 = DVD or CD,  1 = Just DVD (for copy protection)
+#else
+#define	__DVD_ONLY__ 0				// 0 = DVD or CD,  1 = Just DVD (for copy protection)
+#endif
+
+#define ASYNC_QUICK_FILESYS 1
+#define ASYNC_HOST_FILESYS	0
+#define DISABLE_QUICK_FILESYSTEM	0
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace File
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#ifndef SECTOR_SIZE
+#define SECTOR_SIZE 2048
+#endif // SECTOR_SIZE
+
+
+
+// MEMOPT: If we run out of IOP memory again, it is possible that we could use the pcm buffer
+// (The one that is TOTAL_IOP_BUFFER_SIZE_NEEDED) for the file i/o, since probably music or 
+// streams will not need to be playing when a file is loading.
+// That would free up 2048*48=98304 bytes!!
+#define FILESYS_NUM_SECTORS_IN_BUFFER			48
+#define FILESYS_STREAM_BUFFER_NUM_PARTITIONS	3
+#define FILESYS_IOP_BUFFER_SIZE					( SECTOR_SIZE * FILESYS_NUM_SECTORS_IN_BUFFER )
+#define FILESYS_NUM_SECTORS_PER_READ			16
+#define	FILESYS_CD_READ_SIZE					( SECTOR_SIZE * FILESYS_NUM_SECTORS_PER_READ )
+#define IOP_TO_EE_BUFFER_ALLIGNMENT				64
+
+// Static globals:
+static void				*gFilesysIOPStreamBuffer = NULL;
+//static char				*gIOPToEEBuffer;
+	   unsigned int		gWadLSN = 0;          // Made global for p_AsyncFilesystem
+static bool				gQuickFileSystemInitialzed = false;
+static bool				gStreaming = false;
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+#define READBUFFERSIZE (10240*5)
+
+// GJ:  I added this to track how many skyFiles are active
+// at any given time.  I am assuming that there's only one,
+// in which case I can just create one global instance
+// (previously, we were allocating it off the current heap,
+// which was causing me grief during the CAS loading process)
+//int g_fileOpenCount = 0;
+
+struct skyFile
+{
+	
+	// the following must match the dumbSkyFile struct
+	// in pre.cpp, or else pre file i/o won't work properly
+	int				gdfs;
+	int32			POS;
+	int32			SOF;
+
+	// the rest is skyFile-specific
+	uint8			readBuffer[READBUFFERSIZE];
+	uint32		bufferPos;
+	bool			bufferValid;
+	bool			locked;			// whether the skyfile is currently in use
+	
+	// Used when CD
+	uint32			WadOffset;
+	const char		*pFilename;
+	
+	// Used when non-CD
+	#define MAX_PFILESYS_NAME_SIZE 255
+	char filename[ MAX_PFILESYS_NAME_SIZE ];
+#if ASYNC_HOST_FILESYS
+	CAsyncFileHandle *p_async_file;
+#endif
+};
+
+// At first, I assumed that only one skyfile could be open at
+// a time, but that's not true when testing streams from the
+// PC build.
+// pc builds need to have two (one for normal files, and one for streams)
+// GJ:  for THPS4, we increased this to 3, so that we can play multiple streams
+const int MAXOPENFILES = 3;
+
+// Ken note: It used to be that MAXOPENFILES was defined to be 1 when __NOPT_CDROM__OLD
+// was set. Now that __NOPT_CDROM__OLD is not used anymore and CD() is used at runtime
+// instead, I made MAXOPENFILES always 2. Then, where MAXOPENFILES used to be used in the code
+// it now uses a var set to 1 or 2 depending on CD()
+static skyFile g_skyFile[MAXOPENFILES];
+
+skyFile* lock_skyfile( void )
+{
+	// cd builds can only have one file open at any given time
+	int max_open_files=Config::CD() ? 1:MAXOPENFILES;
+	
+	int i;
+	// this tries to find an unlocked skyfile
+	for ( i = 0; i < max_open_files; i++ )
+	{
+		if ( !g_skyFile[i].locked )
+		{
+			g_skyFile[i].locked = true;
+			return &g_skyFile[i];
+		}
+	}
+
+	if (!Config::CD())
+	{
+		Dbg_Message( "Here are the files currently open:" );
+		for ( i = 0; i < max_open_files; i++ )
+		{
+			Dbg_Message( "%s", g_skyFile[ i ].filename );
+		}
+	}	
+
+	// if we get here, that means that all the sky files were locked
+	Dbg_MsgAssert( 0, ( "Trying to open too many files simultaneously (max=%d)", max_open_files ) );
+	return NULL;
+}
+
+void unlock_skyfile( skyFile* pSkyFile )
+{
+	// cd builds can only have one file open at any given time
+	int max_open_files=Config::CD() ? 1:MAXOPENFILES;
+	
+
+	// this tries to find the sky file that the caller is referencing
+	for ( int i = 0; i < max_open_files; i++ )
+	{
+		if ( pSkyFile == &g_skyFile[i] )
+		{
+			Dbg_MsgAssert( g_skyFile[i].locked, ( "Trying to unlock a sky file too many times" ) );
+			g_skyFile[i].locked = false;
+			return;
+		}
+	}
+
+	// if we get here, that means that the pointer didn't match one of the valid skyfiles
+	Dbg_MsgAssert( 0, ( "Unrecognized sky file" ) );
+}
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static	int32			s_open_files = 0;
+
+// The header file (SKATE4.hed) used for looking up where files are within
+// SKATE4.wad
+SHed *gpHed=NULL;		// Made global for p_AsyncFilesystem
+static int WadId=-1;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/* skyTransMode                                                   */
+/*                                                                */
+/* Attempt to convert a mode string to an open mode				  */ 
+/*                                                                */
+/* On entry  : access mode										  */
+/* On exit   : integer mode										  */
+/*                                                                */
+/******************************************************************/
+
+static int skyTransMode(const char *access)
+{
+	
+
+	int mode;
+	char *r;
+	char *w;
+	char *a;
+	char *plus;
+	char *n;
+	char *d;
+
+	/* I add a couple of new characters for now:
+	   n non-blocking mode
+	   d no write back d cache */
+
+	r = strrchr(access, (int)'r');
+	w = strrchr(access, (int)'w');
+	a = strrchr(access, (int)'a');
+	plus = strrchr(access, (int)'+');
+	n = strrchr(access, (int)'n');
+	d = strrchr(access, (int)'d');
+
+	if (plus)
+		mode = SCE_RDWR;
+	else if (r)
+		mode = SCE_RDONLY;
+	else if (w)
+		mode = SCE_WRONLY;
+	else if (a)
+		mode = SCE_WRONLY;
+	else
+		return(0);
+
+	/* later we will test for SCE_CREAT & !SCE_TRUNC as a seek to end of file */
+	if (w)
+		mode |= SCE_CREAT | SCE_TRUNC;
+
+	if (a)
+		mode |= SCE_CREAT;
+
+	if (n)
+		mode |= SCE_NOWAIT;
+
+	if (d)
+		Dbg_MsgAssert( 0,( "Hmm... gotta find out what SCE_NOWBDC was in previous library." ));
+//		mode |= SCE_NOWBDC;
+
+	return(mode);
+}
+
+void StartStreaming( uint32 lsn )
+{
+	Dbg_MsgAssert( gFilesysIOPStreamBuffer,( "IOP stream buffer not initialized" ));
+
+	sceCdSync(0);			// Make sure there aren't any non-blocking CD reads happening
+	Pcm::StopStreams( );	// Garrett: I found streamed sounds can skip when using the quick load
+//	if ( Pcm::UsingCD( ) )
+//	{
+//		Dbg_MsgAssert( 0,( "File access forbidden while PCM audio is in progress." ));
+//	}
+	sceCdRMode mode;
+	mode.trycount = 255;
+	mode.spindlctrl = SCECdSpinNom;
+    mode.datapattern = SCECdSecS2048;
+	mode.pad = 0;
+	while ( !( gStreaming = sceCdStStart( lsn, &mode ) ) )
+	{
+		printf( "aah ha! would have caused the crash!!!\n" );
+		sceCdStop();
+		sceCdSync(0);
+	}
+}
+
+void StopStreaming( void )
+{
+	if (!Config::CD())
+	{
+		return;
+	}
+		
+	if ( gStreaming )
+	{
+		while ( !sceCdStStop( ) )
+		{
+			printf( "sceCdStStop failed\n" );
+		}
+	}
+	gStreaming = false;
+}
+
+
+uint32 CanFileBeLoadedQuickly( const char *filename )
+{
+#if DISABLE_QUICK_FILESYSTEM
+	return 0;
+#endif
+
+	if (!Config::CD())
+	{
+		return 0;
+	}
+		
+	if ( !gQuickFileSystemInitialzed )
+	{
+		return ( 0 );
+	}
+    if ( !gpHed )
+	{
+		return ( 0 );
+	}
+	SHedFile *tempHed = FindFileInHed( filename, gpHed );
+	if ( tempHed )
+		return ( tempHed->GetFileSize() );
+	return ( 0 );
+}
+
+#if ASYNC_QUICK_FILESYS == 0
+static void HandleCDStreamingError( uint32 lsn )
+{
+	
+	int err;
+	err = sceCdGetError( );
+	printf( "CD Error %d\n", err );
+	// let's just stop everything, and try again fresh and new:
+	sceCdBreak( );
+	sceCdSync( 0 );
+	StartStreaming( lsn );
+/*	switch ( err )
+	{
+		case ( SCECdErNO ):
+			break;
+		case ( SCECdErTRMOPN ):
+			break;
+		case ( ):
+			break;
+		case ( ):
+			break;
+		case ( ):
+			break;
+		default:
+			break;
+	}*/
+}
+#endif // ASYNC_QUICK_FILESYS == 0
+
+bool LoadFileQuicklyPlease( const char *filename, uint8 *addr )
+{
+	if (!Config::CD())
+	{
+		return false;
+	}
+
+	if ( !gQuickFileSystemInitialzed )
+	{
+		return ( false );
+	}
+		
+#if ASYNC_QUICK_FILESYS
+
+	CAsyncFileHandle *p_handle = CAsyncFileLoader::sOpen(filename, false);
+	if (p_handle)
+	{
+		p_handle->Load(addr);
+		p_handle->WaitForIO();
+		CAsyncFileLoader::sClose(p_handle);
+	}
+	else
+	{
+		Dbg_MsgAssert(0, ("Didn't get handle pointer from async filesys for file %s", filename));
+		return false;
+	}
+
+#else
+	Dbg_MsgAssert( gFilesysIOPStreamBuffer,( "IOP stream buffer not initialized" ));
+
+	if ( !gpHed )
+	{
+		return ( false );
+	}
+
+	SHedFile *tempHed = FindFileInHed( filename, gpHed );
+	if ( !tempHed )
+	{
+		Dbg_MsgAssert( 0,( "File cannot be loaded quickly." ));
+		return ( false );
+	}
+	uint32 bytesRequired = tempHed->GetFileSize();
+//	uint32 offsetWithinBuffer = tempHed->Offset & ( SECTOR_SIZE - 1 );
+	if ( tempHed->Offset & ( SECTOR_SIZE - 1 ) )
+	{
+		int i;
+		for ( i = 0; i != -1; i++ )
+		{
+			printf( "fix wad so all files start on 2048 boundary.\n" );
+		}
+	}
+
+	if (CAsyncFileLoader::sAsyncInUse())
+	{
+		CAsyncFileLoader::sWaitForIOEvent(true);
+		Dbg_Message("************************ Can't do a normal read when async filesystem is in use");
+		//Dbg_MsgAssert(0, ("Can't do a normal read when async filesystem is in use"));
+	}
+
+	uint32 lsn = gWadLSN;
+	lsn += tempHed->Offset / SECTOR_SIZE;
+
+	uint8 *pEEBuffer = new uint8[ FILESYS_CD_READ_SIZE + IOP_TO_EE_BUFFER_ALLIGNMENT ];
+
+	uint8 *pData = pEEBuffer;
+	pData = ( uint8 * )( ( ( int )pEEBuffer + ( IOP_TO_EE_BUFFER_ALLIGNMENT - 1 ) ) & ~( IOP_TO_EE_BUFFER_ALLIGNMENT - 1 ) );
+
+	uint32 numSectorsToRead;
+	uint32 err;
+	uint32 numBytesToCopy;
+	uint32 i;
+	uint32 *pAI;
+	uint32 *pDI;
+	if ( gStreaming )
+	{
+		if ( !sceCdStSeek( lsn ) )
+		{
+			StartStreaming( lsn );
+		}
+	}
+	else
+	{
+		StartStreaming( lsn );
+	}
+	while ( bytesRequired )
+	{
+		if ( /*offsetWithinBuffer +*/ bytesRequired < FILESYS_CD_READ_SIZE )
+		{
+			numBytesToCopy = bytesRequired;
+			numSectorsToRead = ( ( /*offsetWithinBuffer +*/ bytesRequired ) / SECTOR_SIZE );
+			if ( bytesRequired & ( SECTOR_SIZE - 1 ) )
+			 numSectorsToRead++;
+		}
+		else
+		{
+			numBytesToCopy = FILESYS_CD_READ_SIZE;// - offsetWithinBuffer;
+			numSectorsToRead = FILESYS_NUM_SECTORS_PER_READ;
+		}
+		
+		// read in a chunk at a time ( 50 sectors? 100 sectors? )
+		uint32 sectorsRead;
+		sectorsRead = sceCdStRead( numSectorsToRead, ( uint32 * )pData, STMBLK, &err );
+		
+		int numErrors = 0;
+		while ( sectorsRead != numSectorsToRead )
+		{
+			printf("QuickLoad: Could only read %d of the %d sectors for file %s\n", sectorsRead, numSectorsToRead, filename);
+			sceCdStSeek( lsn );
+			sectorsRead = sceCdStRead( numSectorsToRead, ( uint32 * )pData, STMBLK, &err );
+			if ( numErrors++ >= 12 )
+			{
+				// figure out what the problem is using sceCDGetError( ) and either try to
+				// recover or put up an error screen or something?
+				HandleCDStreamingError( lsn );
+				numErrors = 0;
+			}
+		}
+
+		pAI = ( uint32 * )( addr );
+		pDI = ( uint32 * )( pData );
+		// copy the data from the buffer to the given address:
+		for ( i = 0; i < ( numBytesToCopy >> 2 ); i++ )
+		{
+			*pAI++ = *pDI++;
+		}
+		for ( i = 0; i < ( numBytesToCopy & 3 ); i++ )
+		{
+			//addr[ 1 + ( numBytesToCopy & ~3 ) + i ] = pData[ 1 + ( numBytesToCopy & ~3 ) + i ];
+			addr[  ( numBytesToCopy & ~3 ) + i ] = pData[ ( numBytesToCopy & ~3 ) + i ];
+		}
+		
+		addr += numBytesToCopy;
+		bytesRequired -= numBytesToCopy;
+		
+		lsn += numSectorsToRead;
+//		offsetWithinBuffer = 0;
+	}
+//	sceCdStPause( );
+
+// Mick: 8/28/02 - I added the next line
+// the aim is to stop streaming now, so we don't have to do the extra call to
+// sceCdStSeek  for the next call to this functions
+// The speeds up the loading of skaters by 20%
+	StopStreaming();
+
+	delete pEEBuffer;
+#endif // ASYNC_QUICK_FILESYS
+
+	return ( true );
+}
+
+static void* cdFopen(const char *fname, const char *access)
+{
+	
+	
+	SHedFile *pHd=FindFileInHed(fname, gpHed);
+	if (!pHd)
+	{
+		return NULL;
+	}
+
+	skyFile *fp = lock_skyfile();
+
+	if (!fp)
+	{
+		return (NULL);
+	}
+
+	int mode = skyTransMode(access);
+	if (!mode)
+	{
+		unlock_skyfile( fp );
+		return (NULL);
+	}
+
+	fp->pFilename=fname;
+	fp->WadOffset=pHd->Offset;					
+	fp->SOF=pHd->FileSize;
+	fp->POS=0;
+	fp->gdfs=0;
+#if ASYNC_HOST_FILESYS
+	fp->p_async_file = NULL;
+#endif
+	/* Initialise the buffer to show nothing buffered */
+	fp->bufferPos = READBUFFERSIZE;
+	
+	/* SCE_CREAT & !SCE_TRUNC mean seek to end of file */
+	//if (!((mode & SCE_CREAT) && !(mode & SCE_TRUNC)))
+	//{
+	//	fp->POS = fp->SOF;
+	//}
+	
+	s_open_files++;
+
+	return ((void *)fp);
+}
+
+static int cdFclose( void* fptr )
+{
+	
+	
+	skyFile* fp = (skyFile*)fptr;
+
+	Dbg_AssertPtr( fptr );
+
+
+	if ( fp && s_open_files )
+	{
+		unlock_skyfile( fp );
+        
+		s_open_files--;
+
+		return 0;
+	}
+
+	return -1;
+}
+
+static bool cdFexist( const char* name )
+{
+	
+	if (FindFileInHed(name, gpHed))
+	{
+		return true;
+	}	
+	else
+	{
+		return false;
+	}	
+}
+
+static size_t cdFread(void *addr, size_t size, size_t count, void *fptr)
+{
+	
+	skyFile     *fp = (skyFile *)fptr;
+	size_t      numBytesToRead = size * count;
+	int         bytesRead, bytesRead2;
+
+	bytesRead = 0;
+
+	StopStreaming( );
+
+//	char * after = (char*)addr + numBytesToRead; 							   
+
+															  
+
+	/* Trim number of bytes for the size of the file */
+	if ((fp->POS + (int32)numBytesToRead) > fp->SOF)
+	{
+		numBytesToRead = fp->SOF - fp->POS;
+	}
+
+	
+	/* First try and use the buffer */
+	if ((fp->bufferPos < READBUFFERSIZE) &&
+		(bytesRead < (int32)numBytesToRead))
+	{
+		/* Pull from the buffer */
+		if (numBytesToRead < (READBUFFERSIZE-fp->bufferPos))
+		{
+			/* Can satisfy entirely from buffer */
+			bytesRead = numBytesToRead;
+		}
+		else
+		{
+			/* Pull as much as possible from the buffer */
+			bytesRead = READBUFFERSIZE-fp->bufferPos;
+		}
+
+		/* Copy it */
+		memcpy(addr, &fp->readBuffer[fp->bufferPos], bytesRead);
+
+		/* Update target address and source address */
+		addr = (void *)((uint8 *)addr + bytesRead);
+		fp->bufferPos += bytesRead;
+		fp->POS += bytesRead;
+	}
+
+	/* If next bit is bigger than a buffer, read it directly and ignore the
+	 * buffer.
+	 */
+	if ((numBytesToRead-bytesRead) > 0)
+	{
+		if ((numBytesToRead-bytesRead) >= READBUFFERSIZE)
+		{
+			bytesRead2 = (numBytesToRead-bytesRead);
+			//bytesRead2 = sceRead(fp->gdfs, addr, bytesRead2);
+			Dbg_MsgAssert(WadId>=0,("Bad WadId"));	
+			//Dbg_Message("Seeking to LSN %d", fp->WadOffset+fp->POS);
+			sceLseek(WadId, fp->WadOffset+fp->POS, SCE_SEEK_SET);
+			bytesRead2=sceRead(WadId,addr,bytesRead2);
+			
+			if (bytesRead2 < 0)
+			{
+				bytesRead2 = 0;
+			}
+		}
+		else
+		{
+			/* Go via the buffer */
+			//sceRead(fp->gdfs, fp->readBuffer, READBUFFERSIZE);
+			Dbg_MsgAssert(WadId>=0,("Bad WadId"));	
+			//Dbg_Message("Seeking to LSN %d", fp->WadOffset+fp->POS);
+			sceLseek(WadId, fp->WadOffset+fp->POS, SCE_SEEK_SET);
+			sceRead(WadId,fp->readBuffer, READBUFFERSIZE);
+
+			
+			bytesRead2 = (numBytesToRead-bytesRead);
+			memcpy(addr, fp->readBuffer, bytesRead2);
+			fp->bufferPos = bytesRead2;
+		}
+		fp->POS += bytesRead2;
+		bytesRead += bytesRead2;
+	}
+
+	return (bytesRead/size);
+}
+
+static size_t cdFwrite( const void *addr, size_t size, size_t count, void *fptr )
+{
+	
+	skyFile  *fp = (skyFile *)fptr;
+	Dbg_AssertPtr( fptr );
+	
+	fp->POS+=size*count;			 
+	fp->SOF=fp->POS;
+	return count;
+}
+
+static int cdFseek(void *fptr, long offset, int origin)
+{
+	
+
+	skyFile      *fp = (skyFile *)fptr;
+	int32      oldFPos, bufStart;
+	bool       noBuffer = FALSE;
+
+	Dbg_AssertPtr( fptr );
+
+	oldFPos = fp->POS;
+	bufStart = oldFPos - fp->bufferPos;
+	if (fp->bufferPos == READBUFFERSIZE) noBuffer = TRUE;
+	fp->bufferPos = READBUFFERSIZE;
+
+	switch (origin)
+	{
+		case SEEK_CUR:
+		{            
+			/* Does the seek stay in the buffer */
+			if ((oldFPos + offset >= bufStart) &&
+				(oldFPos + offset < bufStart+READBUFFERSIZE))
+			{
+				fp->bufferPos = (oldFPos + offset) - bufStart;
+				fp->POS += offset;
+			}
+			else
+			{
+				fp->POS+=offset;
+				if (fp->POS<0 || fp->POS>fp->SOF)
+				{
+					fp->POS=-1;
+					Dbg_MsgAssert(0,("Bad offset sent to cdFseek (SEEK_CUR) offset=%ld pos=%d size=%d",offset,fp->POS-offset,fp->SOF));
+				}	
+			}
+			break;
+		}
+		case SEEK_END:
+		{
+			fp->POS=fp->SOF-offset;
+			if (fp->POS<0 || fp->POS>fp->SOF)
+			{
+				fp->POS=-1;
+				Dbg_MsgAssert(0,("Bad offset sent to cdFseek (SEEK_END)"));			
+			}	
+			break;
+		}
+		case SEEK_SET:
+		{
+			fp->POS=offset;
+			if (fp->POS<0 || fp->POS>fp->SOF)
+			{
+				fp->POS=-1;
+				Dbg_MsgAssert(0,("Bad offset sent to cdFseek (SEEK_SET)"));			
+			}	
+			break;
+		}
+		default:
+		{
+			return (-1);
+		}
+	}
+
+	if (noBuffer)
+		fp->bufferPos = READBUFFERSIZE;
+
+	if (fp->POS == -1)
+	{
+		/* This may not be valid */
+		fp->POS = oldFPos;
+		fp->bufferPos = READBUFFERSIZE;
+		return (-1);
+	}
+
+	return (0);
+}
+
+static char * cdFgets(char *buffer, int maxLen, void *fptr)
+{
+	
+	Dbg_MsgAssert(0,("fgets not done yet"));
+	return NULL;
+}
+
+static int cdFputs( const char *buffer, void *fptr)
+{
+	
+	Dbg_MsgAssert(0,("Cannot fputs to CD"));
+	return 0;
+}
+
+static int cdFeof( void* fptr )
+{
+	
+
+	skyFile  *fp = (skyFile*)fptr;
+
+	Dbg_AssertPtr( fptr );
+
+	return ( fp->POS >= fp->SOF) ;
+}
+
+static int cdFflush( void* )
+{
+	
+	
+	return 0;
+}
+
+
+
+/******************************************************************/
+/* skyFopen                                                       */
+/*                                                                */
+/* On entry   : Filename, access mode                             */
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void* skyFopen(const char *fname, const char *access)
+{
+	
+
+	skyFile*	fp;
+	int         mode;
+
+	StopStreaming( );
+
+	/* Allocate structure for holding info */
+	fp = lock_skyfile();
+
+	if (!fp)
+	{
+		return (NULL);
+	}
+
+	mode = skyTransMode(access);
+	if (!mode)
+	{
+		return (NULL);
+	}
+
+#if ASYNC_HOST_FILESYS
+	fp->p_async_file = CAsyncFileLoader::sOpen(fname, true);
+
+	if (!fp->p_async_file)
+	{
+		unlock_skyfile( fp );
+		return (NULL);
+	}
+
+	// Get file size (will come up with better way)
+	fp->SOF = fp->p_async_file->GetFileSize();
+	fp->POS = 0;
+	//fp->p_async_file->Seek(0, SEEK_END);
+	//fp->SOF = fp->p_async_file->WaitForIO();
+	//fp->p_async_file->Seek(0, SEEK_SET);
+	//fp->POS = fp->p_async_file->WaitForIO();
+#else
+	char      name[256];
+	char*		nameptr;
+
+	/* First manipulate the filename into a Sky friendly name */
+	if (strchr(fname, ':'))
+	{
+		strncpy(name, fname, 255);
+	}
+	else
+	{
+#ifdef SCE_11
+		strcpy(name, "sim:");
+		strncpy(&name[4], fname, 251);
+#else
+		strcpy(name, "host:");
+		strncpy(&name[5], fname, 250);
+#endif       
+	}
+	/* force null termination */
+	name[255] = 0;
+
+	nameptr = name;
+	while(*nameptr)
+	{
+		if (*nameptr == '\\') *nameptr = '/';
+		nameptr++;
+	}
+
+	fp->gdfs = sceOpen(name, mode);
+
+	if (fp->gdfs < 0)
+	{
+		unlock_skyfile( fp );
+		return (NULL);
+	}
+
+	/* We seek to the end of the file to get size */
+	fp->SOF = fp->POS = sceLseek(fp->gdfs, 0, SCE_SEEK_END);
+	if (fp->SOF< 0)
+	{
+		sceClose(fp->gdfs);
+		unlock_skyfile( fp );
+		return (NULL);
+	}
+	/* SCE_CREAT & !SCE_TRUNC mean seek to end of file */
+	if (!((mode & SCE_CREAT) && !(mode & SCE_TRUNC)))
+	{
+		fp->POS = sceLseek(fp->gdfs, 0, SCE_SEEK_SET);
+
+		if (fp->POS < 0)
+		{
+			sceClose(fp->gdfs);
+			unlock_skyfile( fp );
+			
+			return (NULL);
+		}
+	}
+#endif
+
+	/* Initialise the buffer to show nothing buffered */
+	fp->bufferPos = READBUFFERSIZE;
+	strncpy( fp->filename, fname, MAX_PFILESYS_NAME_SIZE );
+	s_open_files++;
+
+	return ((void *)fp);
+}
+
+
+/******************************************************************/
+/* skyFclose                                                      */
+/*                                                                */
+/******************************************************************/
+
+static int skyFclose( void* fptr )
+{
+	
+
+	skyFile* fp = (skyFile*)fptr;
+
+	Dbg_AssertPtr( fptr );
+
+	if ( fp && s_open_files )
+	{
+#if ASYNC_HOST_FILESYS
+		CAsyncFileLoader::sClose(fp->p_async_file);
+		fp->p_async_file = NULL;
+#else
+		sceClose( fp->gdfs );
+#endif
+		unlock_skyfile( fp );
+        
+		s_open_files--;
+
+		return 0;
+	}
+
+	return -1;
+}
+
+/******************************************************************/
+/* skyFexist                                                      */
+/*                                                                */
+/******************************************************************/
+
+static bool skyFexist( const char* name )
+{
+	
+
+	void* res = File::Open( name, "r" );
+
+	if ( res )
+	{
+		File::Close( res );
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+/******************************************************************/
+/* skyFread                                                       */
+/*                                                                */
+/* On entry : Address to read to, block size, block count, file   */
+/* On exit  : Number of bytes read                                */
+/*                                                                */
+/******************************************************************/
+
+static size_t skyFread(void *addr, size_t size, size_t count, void *fptr)
+{
+	
+	
+	skyFile     *fp = (skyFile *)fptr;
+	size_t      numBytesToRead = size * count;
+	int         bytesRead = 0;
+
+	StopStreaming( );
+
+	/* Trim number of bytes for the size of the file */
+	if ((fp->POS + (int32)numBytesToRead) > fp->SOF)
+	{
+		numBytesToRead = fp->SOF - fp->POS;
+	}
+
+#if ASYNC_HOST_FILESYS
+	fp->p_async_file->Read(addr, size, count);
+	bytesRead = fp->p_async_file->WaitForIO();
+	fp->POS += bytesRead;
+#else
+	int bytesRead2;
+
+	/* First try and use the buffer */
+	if ((fp->bufferPos < READBUFFERSIZE) &&
+		(bytesRead < (int32)numBytesToRead))
+	{
+		/* Pull from the buffer */
+		if (numBytesToRead < (READBUFFERSIZE-fp->bufferPos))
+		{
+			/* Can satisfy entirely from buffer */
+			bytesRead = numBytesToRead;
+		}
+		else
+		{
+			/* Pull as much as possible from the buffer */
+			bytesRead = READBUFFERSIZE-fp->bufferPos;
+		}
+
+		/* Copy it */
+		memcpy(addr, &fp->readBuffer[fp->bufferPos], bytesRead);
+
+		/* Update target address and source address */
+		addr = (void *)((uint8 *)addr + bytesRead);
+		fp->bufferPos += bytesRead;
+		fp->POS += bytesRead;
+	}
+
+	/* If next bit is bigger than a buffer, read it directly and ignore the
+	 * buffer.
+	 */
+	if ((numBytesToRead-bytesRead) > 0)
+	{
+		if ((numBytesToRead-bytesRead) >= READBUFFERSIZE)
+		{
+			bytesRead2 = (numBytesToRead-bytesRead);
+			bytesRead2 = sceRead(fp->gdfs, addr, bytesRead2);
+			if (bytesRead2 < 0)
+			{
+				bytesRead2 = 0;
+			}
+		}
+		else
+		{
+			/* Go via the buffer */
+			sceRead(fp->gdfs, fp->readBuffer, READBUFFERSIZE);
+			bytesRead2 = (numBytesToRead-bytesRead);
+			memcpy(addr, fp->readBuffer, bytesRead2);
+			fp->bufferPos = bytesRead2;
+		}
+		fp->POS += bytesRead2;
+		bytesRead += bytesRead2;
+	}
+#endif
+
+	return (bytesRead/size);
+}
+
+/******************************************************************/
+/* skyFwrite                                                      */
+/*                                                                */
+/* On entry : Address to write from, block size, block count, file*/
+/* On exit  : Number of bytes written                             */
+/*                                                                */
+/******************************************************************/
+
+static size_t skyFwrite( const void *addr, size_t size, size_t count, void *fptr )
+{
+	
+
+	int         bytesWritten;
+	skyFile*	fp = (skyFile*)fptr;
+	int32     numBytesToWrite = size * count;
+
+	Dbg_AssertPtr( addr );
+	Dbg_AssertPtr( fptr );
+
+	bytesWritten = sceWrite( fp->gdfs, (void*)addr, numBytesToWrite );
+
+	if (bytesWritten != -1)
+	{
+		fp->POS += bytesWritten;
+		if (fp->POS > fp->SOF)
+			fp->SOF = fp->POS;
+		return (size>0?bytesWritten/size:0);
+	}
+	return (0);
+}
+
+/******************************************************************/
+/* skyFseek                                                       */
+/*                                                                */
+/* On entry   : file to seek in, offset, how to seek	  		  */
+/* On exit    : success/failure                                   */
+/*                                                                */
+/******************************************************************/
+
+static int skyFseek(void *fptr, long offset, int origin)
+{
+	
+
+	skyFile      *fp = (skyFile *)fptr;
+
+	Dbg_AssertPtr( fptr );
+
+	StopStreaming( );
+
+#if ASYNC_HOST_FILESYS
+	fp->p_async_file->Seek(offset, origin);
+	fp->POS = fp->p_async_file->WaitForIO();
+#else
+	int32      oldFPos, bufStart;
+	bool       noBuffer = FALSE;
+
+	oldFPos = fp->POS;
+	bufStart = oldFPos - fp->bufferPos;
+	if (fp->bufferPos == READBUFFERSIZE) noBuffer = TRUE;
+	fp->bufferPos = READBUFFERSIZE;
+
+	switch (origin)
+	{
+		case SEEK_CUR:
+		{            
+			/* Does the seek stay in the buffer */
+			if ((oldFPos + offset >= bufStart) &&
+				(oldFPos + offset < bufStart+READBUFFERSIZE))
+			{
+				fp->bufferPos = (oldFPos + offset) - bufStart;
+				fp->POS += offset;
+			}
+			else
+			{
+				fp->POS = sceLseek(fp->gdfs, oldFPos + offset, SCE_SEEK_SET);
+			}
+			break;
+		}
+		case SEEK_END:
+		{
+			fp->POS = sceLseek(fp->gdfs, offset, SCE_SEEK_END);
+			break;
+		}
+		case SEEK_SET:
+		{
+			fp->POS = sceLseek(fp->gdfs, offset, SCE_SEEK_SET);
+			break;
+		}
+		default:
+		{
+			return (-1);
+		}
+	}
+
+	if (noBuffer)
+		fp->bufferPos = READBUFFERSIZE;
+
+	if (fp->POS == -1)
+	{
+		/* This may not be valid */
+		fp->POS = oldFPos;
+		fp->bufferPos = READBUFFERSIZE;
+		return (-1);
+	}
+#endif
+	return (0);
+}
+
+/******************************************************************/
+/* skyFgets                                                       */
+/*                                                                */
+/* On entry   : Buffer to read into, max chars to read, file	  */
+/* On exit    : Non negative value on success                     */
+/*                                                                */
+/******************************************************************/
+
+static char * skyFgets(char *buffer, int maxLen, void *fptr)
+{
+	
+
+	skyFile            *fp = (skyFile *) fptr;
+	int32             i;
+	int32             numBytesRead;
+
+	Dbg_AssertPtr( buffer );
+	Dbg_AssertPtr( fptr );
+
+	i = 0;
+
+	numBytesRead = skyFread(buffer, 1, maxLen - 1, fp);
+
+	if (numBytesRead == 0)
+	{
+		return (NULL);
+	}
+
+	while (i < numBytesRead)
+	{
+		if (buffer[i] == '\n')
+		{
+			i++;
+
+			buffer[i] = '\0';
+
+			/* the file pointer needs */
+			/* to be reset as skyFread */
+			/* will have overshot the */
+			/* first new line         */
+
+			i -= numBytesRead;
+			skyFseek(fp, i, SEEK_CUR);
+
+			return (buffer);
+		}
+		else if ( buffer[i] == 0x0D )
+		{
+			if ((i < (numBytesRead - 1)) && (buffer[i + 1] == '\n'))
+			{
+				memcpy(&buffer[i], &buffer[i + 1], (numBytesRead - i - 1));
+				numBytesRead--;
+			}
+			else
+				i++;
+		}
+		else
+			i++;
+	}
+
+	/*
+	 * Don't return NULL because we could have read maxLen bytes
+	 * without finding a \n
+	 */
+	return (buffer);
+}
+
+/******************************************************************/
+/* skyFputs                                                       */
+/*                                                                */
+/* On entry   : Buffer to write from, file to write to            */
+/* On exit    : Non negative value on success                     */
+/*                                                                */
+/******************************************************************/
+
+static int skyFputs( const char *buffer, void *fptr)
+{
+	
+
+	skyFile   *fp = (skyFile *)fptr;
+	int i, j;
+
+	Dbg_AssertPtr( buffer );
+	Dbg_AssertPtr( fptr );
+
+	i = strlen(buffer);
+	j = sceWrite(fp->gdfs, (void*)buffer, i);
+
+	if (j != -1)
+	{
+		fp->POS += j;
+		if (fp->POS > fp->SOF)
+			fp->SOF = fp->POS;
+	}
+	if ((j == -1) || (i != j))
+	{
+		return (EOF);
+	}
+	return (j);
+}
+
+/******************************************************************/
+/* skyFeof                                                        */
+/*                                                                */
+/* On entry   : File to test for eof                              */
+/* On exit    : Non zero if end of file reached                   */
+/*                                                                */
+/******************************************************************/
+
+static int skyFeof( void* fptr )
+{
+	
+
+	skyFile  *fp = (skyFile*)fptr;
+
+	Dbg_AssertPtr( fptr );
+
+	return ( fp->POS >= fp->SOF) ;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static int skyFflush( void* )
+{
+	
+
+	return 0;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+long GetFileSize(void *pFP)
+{
+	
+	Dbg_MsgAssert(pFP,("NULL pFP sent to GetFileSize"));
+
+#if ASYNC_HOST_FILESYS
+	skyFile      *fp = (skyFile *)pFP;
+	if (!Config::CD() && fp->p_async_file)
+	{
+		return fp->p_async_file->GetFileSize();
+	}
+#endif
+
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_get_file_size((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	return ((skyFile*)pFP)->SOF;
+}
+
+long GetFilePosition(void *pFP)
+{
+	
+	Dbg_MsgAssert(pFP,("NULL pFP sent to GetFilePosition"));
+
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_get_file_position((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	return ((skyFile*)pFP)->POS;
+}
+
+float GetPercentageRead(void *pFP)
+{
+	Dbg_MsgAssert(pFP,("NULL pFP sent to GetPercentageRead"));
+
+
+    if (PreMgr::sPreEnabled())
+    {
+        int position = PreMgr::pre_get_file_position((PreFile::FileHandle *) pFP);
+        int size = PreMgr::pre_get_file_size((PreFile::FileHandle *) pFP);
+
+        if (PreMgr::sPreExecuteSuccess())
+			return 100.0 * position / size;
+    }
+
+	return 100.0*((skyFile*)pFP)->POS/((skyFile*)pFP)->SOF;
+}
+	
+void InstallFileSystem( void )
+{
+	// cd builds can only have one file open at any given time
+	int max_open_files=Config::CD() ? 1:MAXOPENFILES;
+
+
+	// initialize the locks on the sky files
+	for ( int i = 0; i < max_open_files; i++ )
+	{
+		g_skyFile[i].locked = false;
+	}
+	
+    
+	if (Config::CD())
+	{
+		int	disk_type = SCECdDVD;
+	
+		// Initialise the CD
+		printf("Initialising CD ...\n");
+	//	int err1 = 
+		sceCdInit(SCECdINIT);
+		
+		//int err2 = sceCdMmode(SCECdDVD);
+	//	int err2 = 
+		sceCdMmode(disk_type);
+	
+	
+		// find the disk type, this might be different from what we intialized to 
+		printf(" sceCdGetDiskType   ");
+		int detected_disk_type= sceCdGetDiskType();
+		switch(detected_disk_type)
+		{
+			case SCECdIllgalMedia:
+			printf("Disk Type= IllgalMedia\n"); break;
+			case SCECdPS2DVD:
+			printf("Disk Type= PlayStation2 DVD\n"); break;
+			case SCECdPS2CD:
+			printf("Disk Type= PlayStation2 CD\n"); break;
+			case SCECdPS2CDDA:
+			printf("Disk Type= PlayStation2 CD with CDDA\n"); break;
+			case SCECdPSCD:
+			printf("Disk Type= PlayStation CD\n"); break;
+			case SCECdPSCDDA:
+			printf("Disk Type= PlayStation CD with CDDA\n"); break;
+			case SCECdDVDV:
+			printf("Disk Type= DVD video\n"); break;
+			case SCECdCDDA:
+			printf("Disk Type= CD-DA\n"); break;
+			case SCECdDETCT:
+			printf("Working\n"); break;
+			case SCECdNODISC: 
+			printf("Disk Type= No Disc\n"); break;
+			default:
+			printf("Disk Type= OTHER DISK\n"); break;
+		}
+		
+		// If disk type has changed to a CD, then need to re-initialize it							   
+		if (detected_disk_type == SCECdPS2CD)
+		{
+			#if __DVD_ONLY__
+			printf( "*** ERROR - CD Detected, needs DVD.\n" );
+			#else
+			printf( "reinitializing for Ps2CD\n" );
+			disk_type = SCECdCD;
+			sceCdMmode(disk_type);
+			printf( "done reinitializing\n" );
+			#endif		
+		}
+		
+	
+	//	printf("'tis done. errs = %d, %d\n",err1,err2);
+		
+	
+	
+						   
+						   
+		// This next bit is essential for when making a bootable disc, ie one that will
+		// boot on the actual PS rather than just the dev system.
+	//#ifdef __NOPT_BOOTABLE__
+		// K: Commented out __NOPT_BOOTABLE__, since it is set when __NOPT_CDROM__OLD is set.
+	
+		/* Reboot IOP, replace default modules  */
+		char  path[128];
+//		sprintf(path,"cdrom0:\\%sIOP\\IOPRP260.IMG;1",Config::GetDirectory()); 
+//		sprintf(path,"host0:\\SKATE5\\DATA\\IOPMODULES\\DNAS280.IMG;1");
+		
+		
+		sprintf(path,"cdrom0:\\%sIOP\\DNAS280.IMG;1",Config::GetDirectory()); 
+		while ( !sceSifRebootIop((const char*) path) ); /* (Important) Unlimited retries */
+		while( !sceSifSyncIop() );
+		
+		
+		/* Reinitialize */
+		sceSifInitRpc(0);
+		sceCdInit(SCECdINIT);
+		
+	//    sceCdMmode(SCECdDVD);   /* Media: CD-ROM */
+		sceCdMmode(disk_type);   /* Media: CD-ROM */
+		
+		sceFsReset();
+	//#endif // __NOPT_BOOTABLE__
+
+		printf("Opening hed file ...\n");
+		// Open the hed file and load it into memory.
+		gpHed = LoadHed( "SKATE5");
+		
+	/*	
+		printf ("Size = %d, adjusted = %d\n",HedSize,(HedSize+2047)&~204);
+	
+		for (int i = HedSize; i< (HedSize+2047)&~204; i++)
+		{
+			if (p[i] != 0x55)
+			{
+				printf ("Overrun ? %d = %2x\n",i,p[i]);
+				break;
+			}
+		}
+	*/
+		printf("Opening wad file ...\n");												 	
+		// Open the wad and keep it open for future reference.	
+		Dbg_MsgAssert(WadId==-1,("WadId not -1 ?"));					   
+		while (WadId<0)
+		{
+			char  path[128];
+			sprintf(path,"cdrom0:\\%sSKATE5.WAD;1",Config::GetDirectory()); 
+			WadId=sceOpen(path,SCE_RDONLY);
+			if (WadId<0)
+			{
+				printf("Retrying opening SKATE5.wad ...\n");
+			}
+		}		
+		printf( "opened successfully\n" );
+	}   
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void UninstallFileSystem( void )
+{
+	if (Config::CD())
+	{
+		// Free the hed file buffer.
+		Dbg_MsgAssert(gpHed,("NULL gpHed ?"));
+		Mem::Free(gpHed);
+		gpHed=NULL;
+		
+		Dbg_MsgAssert(WadId>=0,("Bad WadId"));
+		sceClose(WadId);
+		WadId=-1;
+	
+		// free IOP memory:
+		if ( gFilesysIOPStreamBuffer )
+		{
+			sceSifFreeIopHeap( gFilesysIOPStreamBuffer ) ;
+			gFilesysIOPStreamBuffer = 0;
+		}
+	}
+}
+
+void InitQuickFileSystem( void )
+{
+
+	// get the LSN to allow us to figure out where every other file is...
+	sceCdlFILE wadInfo;
+	char  path[128];
+	sprintf(path,"\\%sSKATE5.WAD;1",Config::GetDirectory()); 
+	while ( !sceCdSearchFile( &wadInfo, path ) )
+	{
+		printf("Retrying finding SKATE5.wad ...\n");
+	}
+	//printf( "after search file\n" );
+	gWadLSN = wadInfo.lsn;
+
+#if DISABLE_QUICK_FILESYSTEM
+	return;
+#endif
+
+#if ASYNC_QUICK_FILESYS == 0
+	// allocate our streaming buffer (iop side) for files:
+	//printf( "before alloc heap\n" );	
+
+	gFilesysIOPStreamBuffer = sceSifAllocIopHeap( FILESYS_IOP_BUFFER_SIZE );
+	
+	//printf( "after alloc heap: buffer %x of size %d\n", gFilesysIOPStreamBuffer, FILESYS_IOP_BUFFER_SIZE );	
+
+	Dbg_MsgAssert( gFilesysIOPStreamBuffer,( "IOP stream buffer allocation failed." ));
+
+	//printf( "before stream init\n" );	
+	// initialize streaming capabilities and shit...
+	while ( !sceCdStInit( FILESYS_NUM_SECTORS_IN_BUFFER, FILESYS_STREAM_BUFFER_NUM_PARTITIONS, (unsigned int)gFilesysIOPStreamBuffer ) )
+	{
+		printf( "trying to init CD stream\n" );
+	}
+	//printf( "after stream init\n" );
+
+	StartStreaming( gWadLSN );
+#endif
+
+	gQuickFileSystemInitialzed = true;
+
+	//printf( "after StartStreaming()\n" );
+} // end of InitQuickFileSystem( )	
+
+
+// We need to execute this after we have used the Cd Stream calls in other routines
+void ResetQuickFileSystem( void )
+{
+#if ASYNC_QUICK_FILESYS == 0
+	Dbg_MsgAssert( gFilesysIOPStreamBuffer,( "IOP stream buffer not initialized" ));
+
+	// initialize streaming capabilities and shit...
+	while ( !sceCdStInit( FILESYS_NUM_SECTORS_IN_BUFFER, FILESYS_STREAM_BUFFER_NUM_PARTITIONS, (unsigned int)gFilesysIOPStreamBuffer ) )
+	{
+		printf( "trying to init CD stream\n" );
+	}
+#endif
+}
+
+////////////////////////////////////////////////////////////////////
+// Our versions of the ANSI file IO functions.  They call
+// the PreMgr first to see if the file is in a PRE file.
+////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////
+// Exist                                                          
+bool Exist( const char *filename )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        bool retval = PreMgr::pre_fexist(filename);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFexist(filename);
+	}
+	else
+	{
+		return skyFexist(filename);
+	}	
+}
+
+////////////////////////////////////////////////////////////////////
+// Open                                                          
+void * Open( const char *filename, const char *access )
+{
+	bool use_pre = true;
+#if ASYNC_HOST_FILESYS
+	use_pre = Config::CD();
+#endif
+
+	// Don't use pre files if writing to disk (eg when writing parks)
+	if (access[0]=='w')
+	{
+		use_pre=false;
+	}
+		
+	if (Script::GetInt(CRCD(0xe99935c2,"show_filenames"),false))
+	{
+		printf (".... Open %s\n",filename);
+	}
+
+
+    if (use_pre && PreMgr::sPreEnabled())
+    {
+        void * retval = PreMgr::pre_fopen(filename, access);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFopen(filename, access);
+	}
+	else
+	{
+		return skyFopen(filename, access);
+	}	
+}
+
+////////////////////////////////////////////////////////////////////
+// Close                                                          
+int Close( void *pFP )
+{
+	bool use_pre = true;
+#if ASYNC_HOST_FILESYS
+	use_pre = Config::CD();
+#endif
+
+    if (use_pre && PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fclose((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFclose(pFP);
+	}
+	else
+	{
+		return skyFclose(pFP);
+	}	
+}
+
+////////////////////////////////////////////////////////////////////
+// Read                                                          
+size_t Read( void *addr, size_t size, size_t count, void *pFP )
+{
+	bool use_pre = true;
+#if ASYNC_HOST_FILESYS
+	use_pre = Config::CD();
+#endif
+
+    if (use_pre && PreMgr::sPreEnabled())
+    {
+        size_t retval = PreMgr::pre_fread(addr, size, count, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (CAsyncFileLoader::sAsyncInUse())
+	{
+		CAsyncFileLoader::sWaitForIOEvent(true);
+		Dbg_Message("************************ Can't do a normal read when async filesystem is in use");
+		//Dbg_MsgAssert(0, ("Can't do a normal read when async filesystem is in use"));
+	}
+
+	if (Config::CD())
+	{
+		return cdFread(addr, size, count, pFP);
+	}
+	else
+	{
+		return skyFread(addr, size, count, pFP);
+	}	
+}
+
+///////////////////////////////////////////////////////////////////////
+// Read an Integer in PS2 (littleendian) format
+// we just read it directly into memory...
+size_t ReadInt( void *addr, void *pFP )
+{
+	return Read(addr,4,1,pFP);	
+}
+
+////////////////////////////////////////////////////////////////////
+// Write                                                          
+size_t Write( const void *addr, size_t size, size_t count, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        size_t retval = PreMgr::pre_fwrite(addr, size, count, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFwrite(addr, size, count, pFP);
+	}
+	else
+	{
+		return skyFwrite(addr, size, count, pFP);
+	}	
+}
+
+////////////////////////////////////////////////////////////////////
+// GetS                                                          
+char * GetS( char *buffer, int maxlen, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        char * retval = PreMgr::pre_fgets(buffer, maxlen, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFgets(buffer, maxlen, pFP);
+	}
+	else
+	{
+		return skyFgets(buffer, maxlen, pFP);
+	}	
+}
+
+////////////////////////////////////////////////////////////////////
+// PutS                                                          
+int PutS( const char *buffer, void *pFP )
+{
+    if (PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fputs(buffer, (PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFputs(buffer, pFP);
+	}	
+	else
+	{
+		return skyFputs(buffer, pFP);
+	}	
+}
+
+////////////////////////////////////////////////////////////////////
+// Eof                                                          
+int Eof( void *pFP )
+{
+	bool use_pre = true;
+#if ASYNC_HOST_FILESYS
+	use_pre = Config::CD();
+#endif
+
+    if (use_pre && PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_feof((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFeof(pFP);
+	}
+	else
+	{
+		return skyFeof(pFP);
+	}	
+}
+
+////////////////////////////////////////////////////////////////////
+// Seek                                                          
+int Seek( void *pFP, long offset, int origin )
+{
+	bool use_pre = true;
+#if ASYNC_HOST_FILESYS
+	use_pre = Config::CD();
+#endif
+
+    if (use_pre && PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fseek((PreFile::FileHandle *) pFP, offset, origin);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFseek(pFP, offset, origin);
+	}
+	else
+	{
+		return skyFseek(pFP, offset, origin);
+	}	
+}
+
+int32	Tell( void* pFP )
+{
+	skyFile* fp = (skyFile *) pFP;
+	return fp->POS;
+}
+
+////////////////////////////////////////////////////////////////////
+// Flush                                                          
+int Flush( void *pFP )
+{
+	bool use_pre = true;
+#if ASYNC_HOST_FILESYS
+	use_pre = Config::CD();
+#endif
+
+    if (use_pre && PreMgr::sPreEnabled())
+    {
+        int retval = PreMgr::pre_fflush((PreFile::FileHandle *) pFP);
+        if (PreMgr::sPreExecuteSuccess())
+            return retval;
+    }
+
+	if (Config::CD())
+	{
+		return cdFflush(pFP);
+	}
+	else
+	{
+		return skyFflush(pFP);
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace File
+
+
diff --git a/Code/Sys/File/pip.cpp b/Code/Sys/File/pip.cpp
new file mode 100644
index 0000000..447cbcc
--- /dev/null
+++ b/Code/Sys/File/pip.cpp
@@ -0,0 +1,1022 @@
+///////////////////////////////////////////////////////////////////////////////////////
+//
+// pip.cpp		KSH 6 Feb 2002
+//
+// Pre-in-Place stuff
+//
+///////////////////////////////////////////////////////////////////////////////////////
+
+// start autoduck documentation
+// @DOC pip
+// @module pip | None
+// @subindex Scripting Database
+// @index script | pip
+
+// TODO
+// Use Shrink to shrink the memory block to get rid of the 2048 padding at the end.
+// (This will only be effective when the bottom-up heap is being used though)
+
+// TODO but non-essential:
+// Make Unload work if passed the pointer.
+// Make GetFileSize work if passed the pointer.
+
+// 11Dec02 JCB - Andre wanted me to use tolower() instead of strncmpi()
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+namespace Pip
+{
+
+#define IN_PLACE_DECOMPRESSION_MARGIN 3072
+
+
+enum EQuadAligned
+{
+	NOT_QUAD_WORD_ALIGNED=0,
+	QUAD_WORD_ALIGNED
+};
+
+
+#define CURRENT_PRE_VERSION	0xabcd0003			// as of 3/14/2001 (and as of 2/12/2002)
+
+struct SPreHeader
+{
+	int		mSize;
+	int		mVersion;
+	int		mNumFiles;
+};
+
+
+struct SPreContained
+{
+	uint32	mDataSize;
+	uint32	mCompressedSize;
+	uint16	mNameSize;
+
+	// In makepre.cpp, mNameSize is stored in 4 bytes. When the pre is in memory though, I'm
+	// borrowing the two high bytes to use as a usage indicator to indicate whether this
+	// contained file is 'open', ie has had Load called on it. The count value is the number
+	// of times the file has been opened using Load. Gets decremented when Unload is called.
+	// (The LoadPre function checks that they are all zero to start with when a new pre is loaded)
+	uint16  mUsage;
+
+	// Mick - added space for a checksum of mpName
+	// as otherwise we spend over five seconds at boot up in re-calculating checksums n^2 times	
+	uint32 	mChecksum;
+	
+	// Keep mpName the last member of this struct, cos I calculate the space used by the other
+	// members by subtracting &p->mDataSize from p->mpName.
+	// mpName will probably not move anyway, cos this strcture is part of the pre file format.
+	char	mpName[1];
+};
+
+
+
+static SPreContained *sSkipToNextPreContained(SPreContained *p_preContained, EQuadAligned quadWordAlignedData=QUAD_WORD_ALIGNED);
+static SPreHeader 	 *sSkipOverPreName(const char *p_pre_name);
+static SPreContained *sSeeIfFileIsInAnyPre(uint32 fileNameCRC);
+#ifdef __NOPT_ASSERT__
+static const char 	 *sGetPreName(SPreContained *p_contained_file);
+#endif
+
+#define MAX_PRE_FILES 100
+// char* because each pre is prefixed with the name of the pre.
+static char *spp_pre_files[MAX_PRE_FILES];
+
+struct SUnPreedFile
+{
+	uint8 *mpFileData;
+	uint32 mFileNameChecksum;
+	uint32 mFileSize;
+	uint32 mUsage;
+};
+
+#define MAX_UNPREED_FILES 200
+static SUnPreedFile sp_unpreed_files[MAX_UNPREED_FILES];
+
+// This class only exists so that I can declare an instance of it and hence
+// use its constructor to initialise the above arrays.
+// Saves me having to call an init function from somewhere early on in the code,
+// which might get moved around later and cause problems.
+// This way, the arrays are guaranteed to have got initialised even before main() is
+// called. I think ...
+class CInit
+{
+public:
+	CInit()
+	{
+		int i;
+
+		for (i=0; imDataSize;
+	if (p_preContained->mCompressedSize)
+	{
+		total_data_size=p_preContained->mCompressedSize;
+	}
+	total_data_size=(total_data_size+3)&~3;
+
+//	printf ("p_preContained = %p\n, total_data = %d, name = (%p) %s",p_preContained,total_data_size,p_preContained->mpName,p_preContained->mpName);
+	
+	uint32 p_next=(uint32)p_preContained->mpName;
+	p_next+=p_preContained->mNameSize;
+	if (quadWordAlignedData)
+	{
+		p_next=(p_next+15)&~15;
+	}
+	p_next+=total_data_size;
+
+	return (SPreContained*)p_next;
+}
+
+static SPreHeader *sSkipOverPreName(const char *p_pre_name)
+{
+	Dbg_MsgAssert(p_pre_name,("NULL p_pre_name"));
+
+	int len=strlen(p_pre_name)+1; // +1 for terminator
+	len=(len+15)&~15;	// Round up to a multiple of 16
+	return (SPreHeader*)(p_pre_name+len);
+}
+
+// Loads a pre file into memory. The name must have no path since the pre is
+// assumed to be in the data\pre directory.
+// Ie, a valid name would be "alc.pre"
+void LoadPre(const char *p_preFileName)
+{
+	Dbg_MsgAssert(p_preFileName,("NULL p_preFileName"));
+
+	// Check to see if the pre is already loaded, and return without doing anything if it is.
+	for (int i=0; ioriginal_file_size ?"));
+		#else
+		File::Read(p_old_file_data+name_size, 1, original_file_size, p_file);
+		#endif
+
+		File::Close(p_file);
+	}
+
+
+	// Calculate the new buffer size required.
+	// Note that even if no decompression is required, the new buffer will probably need to
+	// be bigger than the old, because the contained files need to be moved so that they all start
+	// on 16 byte boundaries. This is required by the collision code for example.
+
+	uint32 new_pre_buffer_size=name_size;
+	new_pre_buffer_size+=sizeof(SPreHeader);
+
+	SPreHeader *p_pre_header=sSkipOverPreName(p_old_file_data);
+	uint32 num_files=p_pre_header->mNumFiles;
+	SPreContained *p_contained=(SPreContained*)(p_pre_header+1);
+	for (uint32 f=0; fmUsage==0,("The file %s in %s has mUsage=%d ??",p_contained->mpName,p_preFileName,p_contained->mUsage));
+
+		new_pre_buffer_size+=(uint32)p_contained->mpName-(uint32)p_contained;
+		new_pre_buffer_size+=p_contained->mNameSize;
+
+		new_pre_buffer_size=(new_pre_buffer_size+15)&~15;
+
+		new_pre_buffer_size+=(p_contained->mDataSize+3)&~3;
+
+		p_contained=sSkipToNextPreContained(p_contained,NOT_QUAD_WORD_ALIGNED);
+	}
+
+	// Need to add a small margin to prevent decompressed data overtaking the compressed data.
+	new_pre_buffer_size+=IN_PLACE_DECOMPRESSION_MARGIN;
+
+	// At this point we have:
+	//
+	// original_file_size	The exact size of the original pre file. Will be a multiple of 4.
+	//
+	// name_size			The size of the pre name, rounded up to a multiple of 4
+	//
+	// old_pre_buffer_size	The size of the memory buffer pointed to by p_old_file_data
+	//						Equals name_size +  (original_file_size, rounded up to a multiple of 2048)
+	//
+	// new_pre_buffer_size	The required size of the new buffer, to contain the decompressed pre,
+	//						with all the files moved to be at 16 byte boundaries.
+	//
+
+	#ifdef __NOPT_ASSERT__
+	printf("Decompressing and rearranging pre file ...\n");
+	#endif
+
+	// Note: It does not matter if new_pre_buffer_size is not a multiple of 2048 any more.
+	// old_pre_buffer_size was only a multiple of 2048 to ensure that the file loading
+	// did not overrun the end of the buffer. I think file loading may only load a whole
+	// number of sectors.
+
+
+	// Reallocate the buffer.
+	char *p_new_file_data=NULL;
+	if (Mem::Manager::sHandle().GetContextDirection()==Mem::Allocator::vTOP_DOWN)
+	{
+		// If using the top-down heap expand the buffer downwards ...
+
+		// p_new_file_data will become a pointer lower down in memory than p_old_file_data.
+		// The old data pointed to by p_old_file_data will still be there.
+		// The memory between p_new_file_data and p_old_file_data is all free to use. Hoorah!
+		p_new_file_data=(char*)Mem::ReallocateDown(new_pre_buffer_size,p_old_file_data);
+	}
+	else
+	{
+		// If using the top-down heap expand the buffer upwards ...
+		p_new_file_data=(char*)Mem::ReallocateUp(new_pre_buffer_size,p_old_file_data);
+		Dbg_MsgAssert(p_new_file_data,("ReallocateUp failed!"));
+
+		// Now need to move the data up so that it can be decompressed into the gap below.
+		uint32 *p_source=(uint32*)(p_old_file_data+old_pre_buffer_size);
+		uint32 *p_dest=(uint32*)(p_new_file_data+new_pre_buffer_size);
+		// Loading backwards cos the destination overlaps the source.
+
+		// Note: Did some timing tests to see if this was was slow, but it's not really.
+		// To load all the pre files in the game, one after the other, takes an average of
+		// 32.406 seconds when the top down heap is used. When using the bottom up, which
+		// necessitates doing this copy, it takes 33.518 seconds.
+		// So per pre file that isn't much. (There's about 60 pre files)
+		Dbg_MsgAssert((old_pre_buffer_size&3)==0,("old_pre_buffer_size not a multiple of 4 ?"));
+		uint32 num_longs=old_pre_buffer_size/4;
+		for (uint32 i=0; imNumFiles=p_source_header->mNumFiles;
+	p_dest_header->mVersion=p_source_header->mVersion;
+
+	// Copy down each of the contained files, decompressing those that need it.
+	SPreContained *p_source_contained=(SPreContained*)(p_source_header+1);
+	SPreContained *p_dest_contained=(SPreContained*)(p_dest_header+1);
+	for (uint32 f=0; fmDataSize=p_source_contained->mDataSize;
+		p_dest_contained->mChecksum=p_source_contained->mChecksum;
+		p_dest_contained->mCompressedSize=0; // The new file will not be compressed.
+		p_dest_contained->mUsage=0;
+		p_dest_contained->mNameSize=p_source_contained->mNameSize;
+		for (int i=0; imNameSize; ++i)
+		{
+			p_dest_contained->mpName[i]=p_source_contained->mpName[i];
+		}
+
+		// Pre-calculate p_next_source_contained because decompression may (and often will)
+		// cause the new data to overwrite the contents of p_source_contained.
+		SPreContained *p_next_source_contained=sSkipToNextPreContained(p_source_contained,NOT_QUAD_WORD_ALIGNED);
+
+		uint8 *p_source=(uint8*)(p_source_contained->mpName+p_source_contained->mNameSize);
+		uint8 *p_dest=(uint8*)(p_dest_contained->mpName+p_dest_contained->mNameSize);
+		p_dest=(uint8*)( ((uint32)p_dest+15)&~15 );
+
+		if (p_source_contained->mCompressedSize)
+		{
+			uint32 num_bytes_decompressed=p_dest_contained->mDataSize;
+			uint8 *p_end=DecodeLZSS(p_source,p_dest,p_source_contained->mCompressedSize);
+			Dbg_MsgAssert(p_end==p_dest+num_bytes_decompressed,("Eh? DecodeLZSS wrote %d bytes, expected it to write %d",p_end-p_dest,num_bytes_decompressed));
+
+			// For neatness, write zero's into the pad bytes at the end, otherwise they'll
+			// be uninitialised data.
+			while (num_bytes_decompressed & 3)
+			{
+				*p_end++=0;
+				++num_bytes_decompressed;
+			}
+		}
+		else
+		{
+			// Uncompressed, so just copy the data down.
+			uint32 *p_source_long=(uint32*)p_source;
+			uint32 *p_dest_long=(uint32*)p_dest;
+			// mDataSize is not necessarily a multiple of 4, but the actual data will be
+			// padded at the end so that it does occupy a whole number of long words.
+			// So the +3 is to ensure that n is rounded up to the next whole number of longs.
+			uint32 n=(p_source_contained->mDataSize+3)/4;
+
+			for (uint32 i=0; imSize=(uint32)p_dest_contained-(uint32)p_dest_header;
+
+	//printf("Wasted space = %d\n",new_pre_buffer_size-((uint32)p_dest_contained-(uint32)p_new_file_data));
+
+	spp_pre_files[spare_index]=p_new_file_data;
+	#ifdef __NOPT_ASSERT__
+	printf("Done\n");
+	#endif
+}
+
+// Removes the specified pre from memory. The name must have no path since the pre is
+// assumed to be in the data\pre directory.
+// Ie, a valid name would be "alc.pre"
+//
+// Won't do anything if the pre is gone already.
+// Asserts if any of the files in the pre are still 'open' in that they haven't had Unload called.
+bool UnloadPre(const char *p_preFileName)
+{
+	bool success = false;
+
+	Dbg_MsgAssert(p_preFileName,("NULL p_preFileName"));
+
+	for (int i=0; imNumFiles;
+				SPreContained *p_contained=(SPreContained*)(p_pre_header+1);
+				for (int f=0; fmUsage,("Tried to unload %s when the file %s contained within it was still open! (mUsage=%d)",spp_pre_files[i],p_contained->mpName,p_contained->mUsage));
+					p_contained=sSkipToNextPreContained(p_contained);
+				}
+				#endif
+
+				// Delete it.
+				Mem::Free(spp_pre_files[i]);
+				spp_pre_files[i]=NULL;
+
+				// we've successfully unloaded a pre
+				success = true;
+			}
+		}
+	}
+
+	// Not found, so nothing to do.
+	return success;
+}
+
+// Searches each of the loaded pre files for the passed contained file.
+// Returns NULL if not found.
+static SPreContained *sSeeIfFileIsInAnyPre(uint32 fileNameCRC)
+{
+	for (int i=0; imNumFiles;
+			SPreContained *p_contained=(SPreContained*)(p_pre_header+1);
+			for (int f=0; fmpName) == p_contained->mChecksum,
+				//("Checksum for %s (%x) not %x",p_contained->mpName,Crc::GenerateCRCFromString(p_contained->mpName),p_contained->mChecksum));
+				//if ( Crc::GenerateCRCFromString(p_contained->mpName) == fileNameCRC )
+				if ( p_contained->mChecksum == fileNameCRC )
+				{
+					return p_contained;
+				}
+				p_contained=sSkipToNextPreContained(p_contained);
+			}
+		}
+	}
+	return NULL;
+}
+
+#ifdef __NOPT_ASSERT__
+// Finds which pre the passed file is contained in, and returns a pointer to the pre name.
+// Returns "Unknown" if not found anywhere.
+static const char *sGetPreName(SPreContained *p_contained_file)
+{
+	for (int i=0; imNumFiles;
+			SPreContained *p_contained=(SPreContained*)(p_pre_header+1);
+			for (int f=0; fmUsage;
+
+		Dbg_MsgAssert(p_contained_file->mCompressedSize==0,("The file '%s' is stored compressed in %s !",p_fileName,sGetPreName(p_contained_file)));
+
+		uint32 p_data=(uint32)p_contained_file->mpName+p_contained_file->mNameSize;
+		p_data=(p_data+15)&~15;
+		return (void*)p_data;
+	}
+
+	// Next, see if it is one of the unpreed files that is already loaded.
+	for (int i=0; impFileData=p_file_data;
+	p_new_unpreed_file->mFileSize=file_size;
+
+	p_new_unpreed_file->mFileNameChecksum=filename_checksum;
+	p_new_unpreed_file->mUsage=1;
+
+	return p_file_data;
+}
+
+void Unload(uint32 fileNameCRC)
+{
+	// See if it is one of the unpreed files.
+	#ifdef __NOPT_ASSERT__
+	bool is_an_unpreed_file=false;
+	#endif
+	for (int i=0; impName,sGetPreName(p_contained_file)));
+
+		// Decrement the usage.
+		if (p_contained_file->mUsage)
+		{
+			--p_contained_file->mUsage;
+		}
+	}
+}
+
+// If the file is unpreed, this will decrement the usage and free the memory specially allocated
+// for it if the usage has reached zero.
+// If the file is in a loaded pre, it will decrement that files usage in the pre.
+// If the file is both of the above, it will assert.
+// If it is neither, it won't do anything.
+void Unload(const char *p_fileName)
+{
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+
+	Unload( Crc::GenerateCRCFromString(p_fileName) );
+}
+
+uint32 GetFileSize(uint32 fileNameCRC)
+{
+	// See if it is one of the unpreed files.
+	#ifdef __NOPT_ASSERT__
+	bool is_an_unpreed_file=false;
+	#endif
+	for (int i=0; impName,sGetPreName(p_contained_file)));
+		Dbg_MsgAssert(p_contained_file->mDataSize,("Zero mDataSize ??"));
+		return p_contained_file->mDataSize;
+	}
+
+//	Dbg_MsgAssert(0,("File '%s' not found loaded anywhere",p_fileName));
+	return 0;
+}
+
+uint32 GetFileSize(const char *p_fileName)
+{
+	Dbg_MsgAssert(p_fileName,("NULL p_fileName"));
+
+	uint32 file_size = GetFileSize( Crc::GenerateCRCFromString(p_fileName) );
+
+	if ( file_size == 0 )
+	{
+		Dbg_MsgAssert(0,("File '%s' not found loaded anywhere",p_fileName));
+	}
+
+	return file_size;
+}
+
+const char *GetNextLoadedPre(const char *p_pre_name)
+{
+	if (p_pre_name==NULL)
+	{
+		for (int i=0; imNumFiles;
+				SPreContained *p_contained=(SPreContained*)(p_pre_header+1);
+				for (int f=0; fmUsage)
+					{
+						return true;
+					}
+					p_contained=sSkipToNextPreContained(p_contained);
+				}
+			}
+		}
+	}
+
+	return false;
+}
+
+// @script | LoadPipPre |
+// @uparm "string" | filename
+// @parmopt name | heap | 0 (checksum value) | Which heap to use.
+// Possible values are TopDown or BottomUp
+// If no heap is specified it will use whatever the current heap is.
+bool ScriptLoadPipPre(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_filename;
+	pParams->GetString(NONAME, &p_filename, Script::ASSERT);
+
+	uint32 chosen_heap=0;
+	pParams->GetChecksum("Heap",&chosen_heap);
+	switch (chosen_heap)
+	{
+		case 0:
+			chosen_heap = 0;
+			// Use whatever the current heap is.
+			break;
+		case 0x477fc6de: // TopDown
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+			break;
+		case 0xc80bf12d: // BottomUp
+			Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+			break;
+		default:
+			Dbg_Warning("Heap '%s' not supported by LoadPipPre, using current heap instead.",Script::FindChecksumName(chosen_heap));
+			chosen_heap=0;
+			break;
+	}
+
+	LoadPre(p_filename);
+
+	if (chosen_heap)
+	{
+		Mem::Manager::sHandle().PopContext();
+	}
+
+	return true;
+}
+
+// @script | UnLoadPipPre |
+// @uparm "string" | filename
+bool ScriptUnloadPipPre(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	const char *p_filename;
+	pParams->GetString(NONAME, &p_filename, Script::ASSERT);
+
+	return UnloadPre(p_filename);
+}
+
+// @script | DumpPipPreStatus | Prints the status of the currently loaded pre files.
+// @flag ShowPreedFiles | Lists all the files contained within each pre, together with their usage value.
+// @flag ShowUnPreedFiles | Lists all the files currently open which were not in a pre,
+// so were individually opened.
+// @flag ShowOnlyOpenFiles | When listing the files in each pre, only display those whose usage is
+// greater than zero.
+bool ScriptDumpPipPreStatus(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	bool show_preed_files=pParams->ContainsFlag("ShowPreedFiles");
+	bool show_unpreed_files=pParams->ContainsFlag("ShowUnPreedFiles");
+	bool show_only_open_files=pParams->ContainsFlag("ShowOnlyOpenFiles");
+
+	printf("Currently loaded pre files:\n");
+
+	for (int i=0; imNumFiles;
+				SPreContained *p_contained=(SPreContained*)(p_pre_header+1);
+				for (int f=0; fmUsage)
+					{
+						printf("Usage:%d  File: %s\n",p_contained->mUsage,p_contained->mpName);
+						// Delay so that printf does not break when there are lots of files.
+						for (volatile int pp=0; pp<100000; ++pp);
+					}
+
+					p_contained=sSkipToNextPreContained(p_contained);
+				}
+			}
+		}
+	}
+
+	if (show_unpreed_files)
+	{
+		printf("Currently loaded un-preed files:\n");
+
+		for (int i=0; iLoadFile(p_fileName,&file_size, p_dest);
+	if (!p_file_data)
+	{
+		// nope, so try loading "Quickly" (basically if we are on a CD)
+		file_size = File::CanFileBeLoadedQuickly( p_fileName );
+		if (file_size)
+		{
+			// It can be loaded quickly.
+			if (!p_dest)
+			{
+				p_file_data=(uint8*)Mem::Malloc((file_size+2047)&~2047);
+			}
+			else
+			{
+				p_file_data = (uint8*)p_dest;
+			}
+
+			bool fileLoaded = File::LoadFileQuicklyPlease( p_fileName, p_file_data );
+			if ( !fileLoaded )
+			{
+				Dbg_MsgAssert( 0,( "File %s failed to load quickly.\n", p_fileName));
+				if (!p_dest)
+				{
+					Mem::Free(p_file_data);
+				}
+				p_file_data=NULL;
+			}
+		}
+		else
+		{
+			// can't load quickly, so probably loading from the PC
+			// Open the file & get its file size
+
+			void *p_file = File::Open(p_fileName, "rb");
+			if (!p_file)
+			{
+				Dbg_MsgAssert(0,("Could not open file '%s'",p_fileName));
+			}
+
+			file_size=File::GetFileSize(p_file);
+			if (!file_size)
+			{
+				Dbg_MsgAssert(0,("Zero file size for file %s",p_fileName));
+			}
+
+			if (!p_dest)
+			{
+				// Allocate memory.
+				// Just to be safe, allocate a buffer of size file_size rounded up to the
+				// next multiple of 2048, cos maybe loading a file off CD will always load
+				// whole numbers of sectors.
+				// Haven't checked that though.
+				p_file_data=(uint8*)Mem::Malloc((file_size+2047)&~2047);
+				Dbg_MsgAssert(p_file_data,("Could not allocate memory for file %s",p_fileName));
+			}
+			else
+			{
+				p_file_data = (uint8*)p_dest;
+			}
+			// Load the file into memory then close the file.
+			#ifdef __NOPT_ASSERT__
+			long bytes_read=File::Read(p_file_data, 1, file_size, p_file);
+			Dbg_MsgAssert(bytes_read<=file_size,("bytes_read>file_size ?"));
+			#else
+			File::Read(p_file_data, 1, file_size, p_file);
+			#endif
+
+			File::Close(p_file);
+		}
+		// Shrink memory back down to accurate usage - saves 43K total in the school!!!
+		if (!p_dest && p_file_data)
+		{
+			Mem::ReallocateShrink(file_size,p_file_data);
+		}
+	}
+	if (p_filesize)
+	{
+		*p_filesize = file_size;
+	}
+	// If we specified a destination, then make sure we did not overflow it
+	if (p_dest)
+	{
+		Dbg_MsgAssert(((file_size + 2047)&~2047) < maxSize,("file size (%d) overflows buffer (%d) for %s\n",file_size,maxSize,p_fileName));
+	}
+	return (void *)p_file_data;
+}
+
+
+}
+
+
+
diff --git a/Code/Sys/File/pip.h b/Code/Sys/File/pip.h
new file mode 100644
index 0000000..a1eeaf1
--- /dev/null
+++ b/Code/Sys/File/pip.h
@@ -0,0 +1,40 @@
+#ifndef	__FILE_PIP_H
+#define	__FILE_PIP_H
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}	
+
+namespace Pip
+{
+void	LoadPre(const char *p_preFileName);
+bool	UnloadPre(const char *p_preFileName);
+
+void*	Load(const char *p_fileName);
+void	Unload(const char *p_fileName);
+uint32	GetFileSize(const char *p_fileName);
+
+// GJ:  sometimes it's useful to do this
+// by checksum, so that we don't have to keep 
+// the full filename string hanging around
+void	Unload(uint32 fileNameCRC);
+uint32	GetFileSize(uint32 fileNameCRC);
+
+const char *GetNextLoadedPre(const char *p_pre_name=NULL);
+bool PreFileIsInUse(const char *p_pre_name);
+
+bool ScriptLoadPipPre(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptUnloadPipPre(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDumpPipPreStatus(Script::CStruct *pParams, Script::CScript *pScript);
+
+
+}
+
+#endif
+
diff --git a/Code/Sys/McMan.h b/Code/Sys/McMan.h
new file mode 100644
index 0000000..6bc4454
--- /dev/null
+++ b/Code/Sys/McMan.h
@@ -0,0 +1,297 @@
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS														**
+**																			**
+**	Module:			MemCard  (Mc)											**
+**																			**
+**	File name:		sys/mcman.h												**
+**																			**
+**	Created: 		03/06/2001	-	spg										**
+**																			**
+*****************************************************************************/
+
+#ifndef __SYS_MCMAN_H
+#define __SYS_MCMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#ifdef __PLAT_NGC__
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mc
+{
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class Card;
+
+class DateTime
+{
+public:
+	unsigned char 	m_Seconds;
+	unsigned char	m_Minutes;
+	unsigned char 	m_Hour;
+	unsigned char 	m_Day;
+	unsigned char 	m_Month;
+	unsigned short	m_Year;
+};
+
+class File : public Lst::Node< File >
+{
+public:
+	enum
+	{
+		mMODE_READ		= 0x0001,
+		mMODE_WRITE		= 0x0002,
+		mMODE_CREATE	= 0x0004,
+	};
+
+	enum FilePointerBase
+	{
+		BASE_START,
+		BASE_CURRENT,
+		BASE_END,
+	};
+
+	enum
+	{
+		mATTRIB_READABLE		= 0x0001,
+		mATTRIB_WRITEABLE		= 0x0002,
+		mATTRIB_EXECUTABLE		= 0x0004,
+		mATTRIB_DUP_PROHIBITED	= 0x0008,
+		mATTRIB_DIRECTORY		= 0x0010,
+		mATTRIB_CLOSED			= 0x0020,
+		mATTRIB_PDA_APP			= 0x0040,
+		mATTRIB_PS1_FILE		= 0x0080,
+	};
+	enum
+	{
+		vMAX_FILENAME_LEN = 31,
+		vMAX_DISPLAY_FILENAME_LEN	= 63
+	};
+
+					File( int fd, Card* card );
+					~File();
+					
+		
+	char			m_Filename[vMAX_FILENAME_LEN+1];
+	DateTime		m_Created;	
+	DateTime		m_Modified;
+	unsigned int	m_Size;
+	unsigned short	m_Attribs;
+
+	bool			Close( void );
+	int				Write( void* data, int len );
+	int				Read( void* data, int len );
+	int				Seek( int offset, FilePointerBase base );
+	bool			Flush( void );
+
+#	ifdef __PLAT_XBOX__
+	char			m_DisplayFilename[vMAX_DISPLAY_FILENAME_LEN+1];
+#	endif
+
+#	ifdef __PLAT_NGC__
+	CARDFileInfo	m_file_info;
+	int				m_file_number;		// Set when opened.
+	
+	bool			m_need_to_flush;
+	int				m_write_offset;
+	uint8		   *mp_write_buffer;
+	int				m_read_offset;
+	uint8		   *mp_read_buffer;
+#	endif
+
+private:
+	int				m_fd;
+	Card*			m_card;
+};
+
+class  Card  : public Spt::Class
+{
+public:
+	friend class Manager;
+
+	enum
+	{
+		vERROR_FORMATTED,
+		vERROR_UNFORMATTED,
+		vINSUFFICIENT_SPACE,
+		vINVALID_PATH,
+		vNO_CARD,
+		vACCESS_ERROR,
+		vSUBDIR_NOT_EMPTY,
+		vTOO_MANY_OPEN_FILES,
+		vUNKNOWN,
+	};
+
+	enum
+	{
+		vDEV_NONE,
+		vDEV_PS1_CARD,
+		vDEV_PS2_CARD,
+		vDEV_POCKET_STATION,
+		vDEV_XBOX_CARD,
+		vDEV_XBOX_HARD_DRIVE,
+		vDEV_GC_CARD,
+	};
+
+	enum
+	{
+		vMAX_FILES_PER_DIRECTORY = 32,
+	};
+
+	bool			MakeDirectory( const char* dir_name );
+	bool			DeleteDirectory( const char* dir_name );
+	const char	   *ConvertDirectory( const char* dir_name );
+	
+	bool			ChangeDirectory( const char* dir_name );
+	
+	File*			Open( const char* filename, int mode, int size = 0 );	// Size used by NGC for file creation.
+	
+	bool			Delete( const char* filename );
+	bool			Rename( const char* old_name, const char* new_name );
+
+	bool			Format( void );
+	bool			Unformat( void );
+	bool			IsFormatted( void );
+	
+	int				GetSlot( void )			{ return m_slot; }
+	int				GetDeviceType( void );
+	int				GetNumFreeClusters( void );
+	int				GetNumFreeEntries( const char* path );
+
+	void			SetError( int error ) { m_last_error = error; }
+	int				GetLastError( void ) { return m_last_error; }
+
+	bool			GetFileList( const char* mask, Lst::Head< File > &file_list );
+
+#	ifdef __PLAT_NGC__
+	int				m_mem_size;				// Overall memory size of unit.
+	int				m_sector_size;			// Sector size of unit.
+	char*			mp_work_area;			// Used also as a flag: == NULL means not yet mounted.
+	bool			m_broken;
+	int 			CountFilesLeft();			// Required to check if there are enough files left to allow a game save.
+	bool			IsForeign( void );
+	bool			IsBadDevice( void );
+#	endif
+
+#	ifdef __PLAT_XBOX__
+	void			SetAsHardDrive();
+#	endif // #ifdef __PLAT_XBOX__
+
+private:
+	int				m_port;
+	int				m_slot;
+	int				m_last_error;
+
+#	ifdef __PLAT_XBOX__
+	char			m_mounted_drive_letter;	// Used also as a flag: == 0 means not yet mounted.
+	enum
+	{
+		vDIRECTORY_NAME_BUF_SIZE=64
+	};	
+#	endif // #ifdef __PLAT_XBOX__
+};
+
+class  Manager  : public Spt::Class
+{
+
+public :
+	enum
+	{
+#		ifdef __PLAT_XBOX__
+		vMAX_PORT = 4,			// Four controllers...
+		vMAX_SLOT = 2,			// ...each with 2 slots for memory cards.
+#		elif defined __PLAT_NGC__
+		vMAX_PORT = 1,			// Just plug them into the console itself, so 1 port...
+		vMAX_SLOT = 1,			// ...with 2 slots for memory cards.
+#		else
+		vMAX_PORT = 2,
+		vMAX_SLOT = 4,
+#		endif // #ifdef __PLAT_XBOX__
+	};
+
+	int				GetMaxSlots( int port );
+	Card*			GetCard( int port, int slot );
+	Card*			GetCardEx( int port, int slot )	{ return &( m_card[port][slot] ); }
+#	ifdef __PLAT_NGC__
+	void			SetFatalError( bool error )		{ m_hasFatalError = error; }
+	bool			GotFatalError( void )			{ return m_hasFatalError; }
+	void			SetWrongDevice( bool error )	{ m_wrongDevice = error; }
+	bool			GotWrongDevice( void )			{ return m_wrongDevice; }
+#	endif // #ifdef __PLAT_NGC__
+
+private :
+					~Manager ( void );
+					Manager ( void );
+
+					Card			m_card[vMAX_PORT][vMAX_SLOT];
+#	ifdef __PLAT_NGC__
+	bool			m_hasFatalError;
+	bool			m_wrongDevice;
+#	endif // #ifdef __PLAT_NGC__
+
+#	ifdef __PLAT_XBOX__
+	Card m_hard_drive;
+#	endif // #ifdef __PLAT_XBOX__
+	
+	DeclareSingletonClass( Manager );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mc
+
+#endif	// __GEL_MCMAN_H
+
diff --git a/Code/Sys/Mem/CompactPool.cpp b/Code/Sys/Mem/CompactPool.cpp
new file mode 100644
index 0000000..cf05468
--- /dev/null
+++ b/Code/Sys/Mem/CompactPool.cpp
@@ -0,0 +1,195 @@
+#include 
+#include 
+
+
+#ifdef __DEBUG_COMPACTPOOL__
+//#define __REALLY_DEBUG_COMPACTPOOL__
+
+
+#endif
+
+#ifdef	__NOPT_ASSERT__
+#define	REPORT_ON		0		// change this to the address of a black you want to watch
+#endif
+
+#ifdef __PLAT_NGC__
+extern bool g_mc_hack;
+#include "sys/ngc/p_buffer.h"
+#endif		// __PLAT_NGC__
+
+namespace Mem
+{
+
+
+CCompactPool::CCompactPool(int item_size, int desired_num_items, char *name)
+{
+	m_totalItems = desired_num_items;
+	m_itemSize = item_size;
+	Dbg_MsgAssert(m_itemSize >= 4, ("item size too small (%d)", m_itemSize));
+
+#ifdef __PLAT_NGC__
+	if ( g_mc_hack )
+	{
+		mp_buffer = (uint8*)NsBuffer::alloc( m_totalItems * m_itemSize );
+	}
+	else
+#endif		// __PLAT_NGC__
+	{
+		mp_buffer = new uint8[m_totalItems * m_itemSize];
+	}
+	mp_buffer_end = mp_buffer + m_totalItems * m_itemSize;
+
+	m_currentUsedItems = 0;
+	m_maxUsedItems = 0;
+
+	// set up free list
+	mp_freeList = (uint32 *) mp_buffer;
+	uint8 *pItem = mp_buffer;
+	for (int i = 0; i < m_totalItems; i++)
+	{
+		if (i < m_totalItems - 1)
+			*((uint32 **) pItem) = (uint32 *) (pItem + m_itemSize);
+		else
+			*((uint32 **) pItem) = NULL;
+		pItem += m_itemSize;
+	}
+
+	if (name)
+		strcpy(m_name, name);
+	else
+		strcpy(m_name, "unnamed");
+
+#ifdef __REALLY_DEBUG_COMPACTPOOL__
+
+	m_maxUsedItems = 0;
+
+	int total_marker_blocks = (m_totalItems + 31) >> 5;
+	mp_used_marker_tab = new uint32[total_marker_blocks];
+	//printf("ZOOPY: %d marker blocks, marker tab at 0x%x\n", total_marker_blocks, mp_used_marker_tab);
+	for (int b = 0; b < total_marker_blocks; b++)
+		mp_used_marker_tab[b] = 0;
+#endif
+}
+
+
+
+
+CCompactPool::~CCompactPool()
+{
+	Dbg_MsgAssert(!m_currentUsedItems, ("pool still contains items"));
+
+#ifdef __PLAT_NGC__
+	if ( !g_mc_hack )
+#endif		// __PLAT_NGC__
+	{
+		delete mp_buffer;
+	}
+
+#ifdef __REALLY_DEBUG_COMPACTPOOL__
+	Ryan("Freeing pool %s, max items used in this pool: %d\n", m_name, m_maxUsedItems);
+	delete [] mp_used_marker_tab;
+	mp_used_marker_tab = NULL;
+	m_maxUsedItems = 0;
+#endif
+}
+
+   
+bool CCompactPool::IsInPool(void *p)
+{
+	if (p>=mp_buffer && p m_maxUsedItems)
+		{
+			m_maxUsedItems = m_currentUsedItems;
+		}
+#ifdef __REALLY_DEBUG_COMPACTPOOL__		
+
+		uint32 marker_num = ((uint32) mp_freeList - (uint32) mp_buffer) / m_itemSize;
+		uint32 marker_tab_entry = marker_num >> 5;
+		uint32 marker_tab_bit = marker_num - (marker_tab_entry << 5);
+		//printf("ZOOPY: allocating using entry %d, bit %d, marker tab at 0x%x\n", marker_tab_entry, marker_tab_bit, mp_used_marker_tab);
+		Dbg_MsgAssert(mp_used_marker_tab, ("max used items %d", m_maxUsedItems));
+		mp_used_marker_tab[marker_tab_entry] |= (1<= 0 && pool_item < m_totalItems, ("item (%d) out of range (%d)", pool_item, m_totalItems));
+	Dbg_Assert(!(((uint32) pFreeItem) & 3));
+	
+	uint32 marker_tab_entry = pool_item >> 5;
+	uint32 marker_tab_bit = pool_item - (marker_tab_entry << 5);
+	//printf("ZOOPY: freeing using entry %d, bit %d, marker tab at 0x%x\n", marker_tab_entry, marker_tab_bit, mp_used_marker_tab);
+	Dbg_Assert(mp_used_marker_tab);
+	Dbg_MsgAssert(mp_used_marker_tab[marker_tab_entry] & (1<= 0 && pool_item < m_totalItems, ("next item (%d) out of range (%d)", pool_item, m_totalItems));
+	}
+#endif
+*/
+}
+
+
+
+} // namespace Mem
+
+
+
diff --git a/Code/Sys/Mem/CompactPool.h b/Code/Sys/Mem/CompactPool.h
new file mode 100644
index 0000000..5a10527
--- /dev/null
+++ b/Code/Sys/Mem/CompactPool.h
@@ -0,0 +1,137 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		
+**																			**
+**	Module:			
+**																			**
+**	File name:		
+**																			**
+**	Created by:		8/2/2001 - rjm							                **
+**																			**
+**	Description:	
+**																			**
+*****************************************************************************/
+
+#ifndef __SYS_MEM_COMPACTPOOL_H
+#define __SYS_MEM_COMPACTPOOL_H
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+#ifdef __NOPT_ASSERT__
+#define __DEBUG_COMPACTPOOL__
+#endif
+
+
+namespace Mem
+{
+
+						
+
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+class CCompactPool
+{
+public:
+						CCompactPool(int item_size, int desired_num_items, char *name = NULL);
+						~CCompactPool();
+
+	void *				Allocate();
+	void 				Free(void *pFreeItem);
+	int					GetMaxUsedItems() {return m_maxUsedItems;}
+	int					GetNumUsedItems() {return m_currentUsedItems;}
+	int					GetTotalItems() {return m_totalItems;}
+	char *				GetName() {return m_name;}
+	bool				IsInPool(void *p);
+	
+private:
+	uint8 *				mp_buffer;
+	uint8 *				mp_buffer_end;
+	int					m_totalItems; // that we have room for
+	int					m_itemSize;
+
+	int					m_currentUsedItems;
+
+	uint32 *			mp_freeList;
+
+	char				m_name[64];
+	int					m_maxUsedItems;
+#ifdef __DEBUG_COMPACTPOOL__
+	uint32 *			mp_used_marker_tab;
+	
+#endif
+
+};
+
+
+
+
+/*
+template
+class StaticCCompactPool
+{
+public:
+	static void			SAllocPool(int size, char *name = NULL) {sp_pool = new CCompactPool<_V>(size, name);}
+	static void			SFreePool()	{delete sp_pool;}
+	static _V*			SCreate() {return sp_pool->Create();}
+	static void			SFree(_V *pItem) {sp_pool->Free(pItem);}
+
+	static int			SGetNumUsedItems() {return sp_pool->GetNumUsedItems();}
+
+protected:
+	static CCompactPool<_V> *	sp_pool;
+};
+*/
+
+
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+}
+
+#endif	// __SYS_MEM_CCOMPACTPOOL_H
+
+
diff --git a/Code/Sys/Mem/PoolManager.cpp b/Code/Sys/Mem/PoolManager.cpp
new file mode 100644
index 0000000..9c5d7da
--- /dev/null
+++ b/Code/Sys/Mem/PoolManager.cpp
@@ -0,0 +1,91 @@
+#include 
+#include 
+
+//DefinePoolableClass(Lst::HashItem)
+
+
+namespace Mem 
+{
+
+
+Mem::CCompactPool *		PoolManager::sp_pool[PoolManager::vTOTAL_POOLS] =
+{
+	NULL
+};
+
+
+
+
+void PoolManager::SSetupPool(int poolId, int numItems)
+{
+	Dbg_MsgAssert(poolId >= 0 && poolId < vTOTAL_POOLS, ("%d not a valid pool", poolId));
+	Dbg_MsgAssert(!sp_pool[poolId], ("pool %d already exists", poolId));
+
+	if (poolId == vHASH_ITEM_POOL)
+		sp_pool[poolId] = new Mem::CCompactPool(s_get_item_size(poolId), numItems, "HashItem");
+}
+
+
+
+
+void PoolManager::SDestroyPool(int poolId)
+{
+	Dbg_MsgAssert(poolId >= 0 && poolId < vTOTAL_POOLS, ("%d not a valid pool", poolId));
+	Dbg_MsgAssert(sp_pool[poolId], ("pool %d doesn't exist", poolId));
+	
+	delete sp_pool[poolId];
+	sp_pool[poolId] = NULL;
+}
+
+
+
+
+void *PoolManager::SCreateItem(int poolId)
+{
+	Dbg_MsgAssert(poolId >= 0 && poolId < vTOTAL_POOLS, ("%d not a valid pool", poolId));
+	
+	if (sp_pool[poolId])
+	{
+		return sp_pool[poolId]->Allocate();
+	}
+
+	return new char[s_get_item_size(poolId)];
+}
+
+
+
+
+void PoolManager::SFreeItem(int poolId, void *pItem)
+{
+	Dbg_MsgAssert(poolId >= 0 && poolId < vTOTAL_POOLS, ("%d not a valid pool", poolId));
+	
+	if (sp_pool[poolId])
+	{
+		sp_pool[poolId]->Free(pItem);
+		return;
+	}
+
+	delete [] (char *)pItem;
+}
+
+// Provide access to the pools for debugging (like reporting usage)
+Mem::CCompactPool * PoolManager::SGetPool(int poolId)
+{
+	return sp_pool[poolId];	
+}
+
+
+
+
+int PoolManager::s_get_item_size(int poolId)
+{
+	if (poolId == vHASH_ITEM_POOL)
+		return sizeof(Lst::HashItem);
+	return 0;
+}
+
+
+
+
+}
+
diff --git a/Code/Sys/Mem/PoolManager.h b/Code/Sys/Mem/PoolManager.h
new file mode 100644
index 0000000..a9d3d45
--- /dev/null
+++ b/Code/Sys/Mem/PoolManager.h
@@ -0,0 +1,45 @@
+#ifndef __SYS_MEM_POOLMANAGER_H
+#define __SYS_MEM_POOLMANAGER_H
+
+#include 
+
+namespace Mem
+{
+
+
+/*
+	Used in cases where one can't simply make a class poolable
+	by inheriting from CPoolable, for example, where we want
+	every HashItem class instance to come off the same
+	pool.
+*/
+
+class PoolManager
+{
+public:
+
+	enum
+	{
+		vHASH_ITEM_POOL =	0,
+		vTOTAL_POOLS	=	1,
+	};
+
+	static void				SSetupPool(int poolId, int numItems);
+	static void				SDestroyPool(int poolId);
+
+	static void *			SCreateItem(int poolId);
+	static void				SFreeItem(int poolId, void *pItem);
+	static Mem::CCompactPool * 	SGetPool(int poolId);
+	
+
+private:
+
+	static int				s_get_item_size(int poolId);
+
+	static Mem::CCompactPool *sp_pool[vTOTAL_POOLS];
+};
+
+}
+
+#endif
+
diff --git a/Code/Sys/Mem/Poolable.cpp b/Code/Sys/Mem/Poolable.cpp
new file mode 100644
index 0000000..8ec7651
--- /dev/null
+++ b/Code/Sys/Mem/Poolable.cpp
@@ -0,0 +1,31 @@
+#include 
+#include 
+
+namespace Mem
+{
+
+	int gHeapPools = false;			// set to true to use debug heap instead of pools
+
+
+CCompactPool *CPoolable::sp_pool[POOL_STACK_SIZE] = {NULL,NULL};									
+bool CPoolable::s_internallyCreatedPool[POOL_STACK_SIZE] = {false,false};
+int CPoolable::s_currentPool=0;						
+
+
+PoolTest::PoolTest()
+{
+	printf("created PoolTest object\n");
+}
+
+
+
+
+PoolTest::~PoolTest()
+{
+	printf("~PoolTest()\n");
+}
+
+
+
+
+}
diff --git a/Code/Sys/Mem/Poolable.h b/Code/Sys/Mem/Poolable.h
new file mode 100644
index 0000000..94dc133
--- /dev/null
+++ b/Code/Sys/Mem/Poolable.h
@@ -0,0 +1,243 @@
+/*
+	MODULE DESCRIPTION
+	
+	Poolable.h, RJM, 10/18/2001
+*/
+
+#ifndef __SYS_MEM_POOLABLE_H
+#define __SYS_MEM_POOLABLE_H
+
+#include 
+#include 
+
+#define DefinePoolableClass(_T)										\
+namespace Mem														\
+{																	\
+	Mem::CCompactPool *Mem::CPoolable< _T >::sp_pool[POOL_STACK_SIZE] = {NULL,NULL};		\
+	bool Mem::CPoolable< _T >::s_internallyCreatedPool[POOL_STACK_SIZE] = {false,false};	\
+	int Mem::CPoolable< _T >::s_currentPool=0;						\
+}																	\
+
+
+namespace Mem
+{
+
+
+	extern int gHeapPools;
+
+//class CCompactPool;
+
+
+template 
+class CPoolable
+{
+	enum EPoolStackSize
+	{
+		POOL_STACK_SIZE=2
+	};
+		
+public:
+	static void							SCreatePool(int num_items, char *name);
+	static void							SAttachPool(CCompactPool *pPool);
+	static void							SRemovePool();
+	static int							SGetMaxUsedItems();
+	static int							SGetNumUsedItems();
+	static int							SGetTotalItems();
+							  													
+	static void							SPrintInfo();
+	
+	static void							SSwitchToNextPool();
+	static void							SSwitchToPreviousPool();
+	
+	static int							SGetCurrentPoolIndex();
+	static bool							SPoolExists();
+	
+	void * operator new (size_t size);										  	
+	void operator delete (void * pMem);									   		
+protected:																		
+	static CCompactPool *				sp_pool[POOL_STACK_SIZE];
+	static bool	  						s_internallyCreatedPool[POOL_STACK_SIZE];  	
+	static int							s_currentPool;
+};
+
+
+
+
+class PoolTest : public CPoolable
+{
+
+public:
+	PoolTest();
+	~PoolTest();
+
+	int m_monkey;
+};
+
+
+
+
+template
+void *CPoolable<_T>::operator new (size_t size)							
+{																
+	Dbg_Assert(size == sizeof(_T));								
+																
+	//printf("in CPoolable operator new\n");
+
+	#ifdef	__NOPT_ASSERT__
+	if (gHeapPools)
+	{
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
+		void *p = ::new char[size];								
+		Mem::Manager::sHandle().PopContext();
+		return p;
+	}
+	#endif
+	
+	if (sp_pool[s_currentPool])	 
+	{
+		return sp_pool[s_currentPool]->Allocate();								
+	}
+
+	return ::new char[size];								
+}																
+																
+
+
+
+template
+void CPoolable<_T>::operator delete (void * pMem)							
+{																
+	Dbg_Assert(pMem);  
+	#ifdef	__NOPT_ASSERT__
+	if (gHeapPools)
+	{
+		delete [] (char *)pMem;											
+		return;
+	}
+	#endif
+	
+	// pMem contains no info as to which pool it was allocated from, because that would
+	// use up too much memory.
+	// So query the pools to see if it is in them.
+	
+	CCompactPool *p_pool=sp_pool[s_currentPool];
+	// Check if it is in the current pool, which it will be most of the time.
+	if (p_pool && p_pool->IsInPool(pMem))
+	{
+		p_pool->Free(pMem);
+	}
+	else
+	{
+		// Otherwise try the other one, which exists for saving games to mem card.
+		Dbg_MsgAssert(POOL_STACK_SIZE==2,("Only two pool supported at the moment"));
+		p_pool=sp_pool[s_currentPool ^ 1];
+		if (p_pool && p_pool->IsInPool(pMem))
+		{
+			p_pool->Free(pMem);
+		}
+		else
+		{
+			delete [] (char *)pMem;											
+		}
+	}		
+}																
+																
+
+
+
+template
+void CPoolable<_T>::SCreatePool(int num_items, char *name)					
+{																
+	Dbg_Assert(!sp_pool[s_currentPool]);										
+	sp_pool[s_currentPool] = new CCompactPool(sizeof(_T), num_items, name);		
+	s_internallyCreatedPool[s_currentPool] = true;
+}																
+																
+template
+bool CPoolable<_T>::SPoolExists()
+{																
+	if (sp_pool[s_currentPool])
+	{
+		return true;
+	}
+	return false;
+}		
+ 
+
+template
+void CPoolable<_T>::SAttachPool(CCompactPool *pPool)					
+{																
+	Dbg_Assert(!sp_pool[s_currentPool]);										
+	Dbg_Assert(pPool);										
+	sp_pool[s_currentPool] = pPool;		
+	s_internallyCreatedPool[s_currentPool] = false;
+}																
+																
+
+
+
+template
+void CPoolable<_T>::SRemovePool()											
+{																
+	if (sp_pool[s_currentPool])												
+	{															
+		if (s_internallyCreatedPool[s_currentPool])
+			delete sp_pool[s_currentPool];											
+		sp_pool[s_currentPool] = NULL;											
+	}															
+}																
+
+template
+int CPoolable<_T>::SGetCurrentPoolIndex()
+{
+	return s_currentPool;
+}
+
+template
+void CPoolable<_T>::SSwitchToNextPool()
+{
+	Dbg_MsgAssert(s_currentPool
+void CPoolable<_T>::SSwitchToPreviousPool()
+{
+	Dbg_MsgAssert(s_currentPool,("Called SSwitchToPreviousPool with no previous pool"));
+	--s_currentPool;
+}
+
+template
+int CPoolable<_T>::SGetMaxUsedItems()											
+{
+	return sp_pool[s_currentPool]->GetMaxUsedItems();
+}
+
+
+template
+int CPoolable<_T>::SGetNumUsedItems()											
+{
+	return sp_pool[s_currentPool]->GetNumUsedItems();
+}
+
+template
+int CPoolable<_T>::SGetTotalItems()											
+{
+	return sp_pool[s_currentPool]->GetTotalItems();
+}
+
+
+
+
+template
+void CPoolable<_T>::SPrintInfo()											
+{
+	printf("pool is at %p\n", sp_pool[s_currentPool]);
+}
+
+
+
+
+}
+
+#endif
diff --git a/Code/Sys/Mem/alloc.cpp b/Code/Sys/Mem/alloc.cpp
new file mode 100644
index 0000000..9b195c4
--- /dev/null
+++ b/Code/Sys/Mem/alloc.cpp
@@ -0,0 +1,441 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			Memory (Mem) 											**
+**																			**
+**	File name:		alloc.cpp												**
+**																			**
+**	Created by:		03/20/00	-	mjb										**
+**																			**
+**	Description:	Abstract Allocator class								**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include "memman.h"
+#include "alloc.h"
+
+#ifdef __PLAT_NGC__
+#ifdef __EFFICIENT__
+#include 
+#endif		// __EFFICIENT__
+#endif		// __PLAT_NGC__
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+uint		Allocator::s_current_id = 1;
+#ifdef __EFFICIENT__
+uint		Allocator::s_align_stack[16] = { 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+uint		Allocator::s_align_stack_index = 0;
+const uint	Allocator::BlockHeader::sSize = sizeof(BlockHeader); 
+#else
+#ifdef __PLAT_NGC__
+const uint	Allocator::BlockHeader::sSize = (uint)nAlignUpBy( sizeof(BlockHeader), 5 ); 
+#else
+const uint	Allocator::BlockHeader::sSize = (uint)nAlignUpBy( sizeof(BlockHeader), 4 ); 
+#endif		// __PLAT_NGC__
+#endif
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+Allocator::Context::Context( void ) 
+: m_node( this ), mp_free_list( NULL ) 
+{
+	
+
+#ifdef __LINKED_LIST_HEAP__
+	mp_used_list = NULL;
+#endif
+
+	Dbg_AssertType( &m_node, Lst::Node< Context > );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Allocator::Context::~Context( void ) 
+{
+	
+
+#ifdef __NOPT_DEBUG__
+	Dbg_MsgAssert( mp_used_list == NULL,( "Memory Leak" ));
+	Dbg_MsgAssert( mp_free_list == NULL,( "Corrupt Free List" ));
+	Dbg_AssertType( &m_node, Lst::Node< Context > );
+#endif
+}
+
+
+bool Allocator::s_valid( void* pAddr )
+{
+	BlockHeader* p_bheader = BlockHeader::sRead( pAddr ); 
+	return (p_bheader->mId == vALLOC_MAGIC);
+}
+
+size_t Allocator::s_get_alloc_size( void* pAddr )
+{
+	BlockHeader* p_bheader = BlockHeader::sRead( pAddr ); 
+	return (p_bheader->mSize);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Allocator* Allocator::s_free( void* pAddr )
+{   
+	BlockHeader* p_bheader = BlockHeader::sRead( pAddr ); 
+	Allocator* p_ret = p_bheader->mpAlloc;
+
+#if 0
+	
+
+	Mem::Manager& mem_man = Mem::Manager::sHandle();	
+	bool found = false;
+	for (Mem::Heap* heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+	{		
+		if (heap == p_ret) found = true;
+	}
+	Dbg_MsgAssert(found,("Double dealloc of %p, allocator = %p?", pAddr, p_ret));
+#endif
+
+	Dbg_MsgAssert(p_bheader->mId != 0xDEADDEAD,("Freeing Block Twice!\n"));
+	Dbg_MsgAssert(p_bheader->mId == vALLOC_MAGIC,("Freeing Corrupt Block\n"));
+	
+	p_bheader->mpAlloc->free( p_bheader );
+	p_bheader->mId = 0xDEADDEAD;						// clear Handle ID to DEAD value
+
+	return p_ret;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Allocator::s_set_id( void* pAddr )
+{
+	
+
+	BlockHeader* p_bheader = (BlockHeader*)( ((uint)pAddr) - BlockHeader::sSize );
+#ifdef __EFFICIENT__
+	p_bheader = (BlockHeader*)(((uint)p_bheader) - p_bheader->mPadBytes);
+#endif		// __EFFICIENT__
+	Dbg_AssertType( p_bheader, BlockHeader );
+	Dbg_AssertType( p_bheader->mpAlloc, Allocator );
+	
+	p_bheader->mId = vALLOC_MAGIC;		//	s_current_id++;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Allocator::BlockHeader* Allocator::BlockHeader::sRead( void* pAddr )
+{
+	BlockHeader* p_ret = (BlockHeader*)(((uint)pAddr) - BlockHeader::sSize);
+#ifdef __EFFICIENT__
+	p_ret = (BlockHeader*)(((uint)p_ret) - p_ret->mPadBytes);
+#endif		// __EFFICIENT__
+	
+	Dbg_AssertType( p_ret, BlockHeader );
+	Dbg_AssertType( p_ret->mpAlloc, Allocator );
+	
+	return p_ret;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Allocator::Allocator( Region* region, Direction dir, char *p_name )
+: mp_region( region ), m_dir( dir ), mp_name(p_name)
+{
+	
+	
+	mp_region->RegisterAllocator( this );
+	
+	m_context_stack.AddToHead( &m_initial_context.m_node );
+	mp_context = &m_initial_context;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Allocator::~Allocator( void )
+{
+	
+
+	Dbg_MsgAssert( !m_context_stack.IsEmpty(),( "Heap stack underflow" ));
+
+	mp_region->UnregisterAllocator( this ); 
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+uint	Allocator::sGetId( void* pAddr )
+{
+	
+
+#ifdef __EFFICIENT__
+	if ( pAddr == NULL )
+#else
+	if (( pAddr == NULL ) || !nAligned( pAddr ))
+#endif		// __EFFICIENT__
+	{
+		return 0;
+	}
+
+	BlockHeader* p_bheader = (BlockHeader*)( ((uint)pAddr) - BlockHeader::sSize );
+#ifdef __EFFICIENT__
+	p_bheader = (BlockHeader*)(((uint)p_bheader) - p_bheader->mPadBytes);
+#endif		// __EFFICIENT__
+
+	return p_bheader->mId;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Allocator::PushContext( void )
+{
+	
+
+	Context* new_context = new Context;
+	m_context_stack.AddToHead( &new_context->m_node );
+	mp_context = new_context;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Allocator::PopContext( void )
+{
+	
+
+	Dbg_MsgAssert( !m_context_stack.IsEmpty(),( "Heap stack underflow" ));
+
+#ifdef __NOPT_DEBUG__
+	dump_free_list();
+	dump_used_list();
+#endif
+
+	delete mp_context;
+		
+	Lst::Search< Context > sh;
+	mp_context = sh.FirstItem( m_context_stack );
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+#ifdef __NOPT_DEBUG__
+
+void 	Allocator::dump_used_list( void )
+{
+	
+#ifndef __PLAT_NGC__
+	BlockHeader*	p_header = mp_context->mp_used_list;
+	
+	Dbg_Message( "USED LIST for Heap (%p)", this );
+
+	while ( p_header )
+	{
+		Dbg_AssertType( p_header, BlockHeader );
+		Dbg_Message ( "%p  %x   (%p)", (void*)((uint)p_header + BlockHeader::sSize ), 
+										p_header->mSize, p_header->mp_next_used );
+		p_header = p_header->mp_next_used;
+	}
+
+	Dbg_Message( "----------------------" );
+#endif		// __PLAT_NGC__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	Allocator::dump_free_list( void )
+{
+	
+	
+	BlockHeader*	p_header = mp_context->mp_free_list;
+	
+	Dbg_Message( "FREE LIST for Heap (%p)", this );
+	
+	while ( p_header )
+	{
+		Dbg_AssertType( p_header, BlockHeader );
+		Dbg_Message ( "%p  %x   (%p)", (void*)((uint)p_header + BlockHeader::sSize ), 
+										p_header->mSize, p_header->mpNext );
+		p_header = p_header->mpNext;
+	}
+
+	Dbg_Message( "----------------------" );
+}
+
+#endif
+
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+// find the block that contains this pointer
+// return NULL if not found   
+// also includes the header area of the block  							   
+void * Allocator::find_block(void *p)
+{
+
+	BlockHeader*	p_header = mp_context->mp_used_list;
+	
+	while ( p_header )
+	{
+		
+		void * p_start = (void*)((uint)p_header + BlockHeader::sSize);
+		int size = p_header->mSize;
+		void * p_end = (void*)((int)p_start + size);
+
+		if (p >= p_start && p mp_next_used;
+	}
+	return NULL;	
+}
+
+Allocator::BlockHeader* Allocator::first_block()
+{
+
+	BlockHeader*	p_header = mp_context->mp_used_list;
+	
+	return p_header;	
+}
+
+
+
+
+#endif
+
+bool  SameContext(void *p_mem,  Mem::Allocator *p_alloc)
+{
+	Allocator::BlockHeader* p_bheader = Allocator::BlockHeader::sRead( p_mem ); 
+	return (p_bheader->mpAlloc == p_alloc);
+}
+
+
+#ifdef __EFFICIENT__
+void Allocator::PushAlign( int align )
+{
+	s_align_stack_index++;
+
+	switch ( align )
+	{
+		case 4:
+			s_align_stack[s_align_stack_index] = 2;
+			break;
+		case 8:
+			s_align_stack[s_align_stack_index] = 3;
+			break;
+		case 16:
+			s_align_stack[s_align_stack_index] = 4;
+			break;
+		case 32:
+			s_align_stack[s_align_stack_index] = 5;
+			break;
+		case 64:
+			s_align_stack[s_align_stack_index] = 6;
+			break;
+		case 128:
+			s_align_stack[s_align_stack_index] = 7;
+			break;
+		default:
+			Dbg_MsgAssert( false, ( "Illegal alignment (%d) specified. Must be 4, 8, 16, 32, 64 or 128.\n", align ) );
+			break;
+	}
+}
+
+void Allocator::PopAlign( void )
+{
+	s_align_stack_index--;
+}
+
+uint Allocator::GetAlign( void )
+{
+	return s_align_stack[s_align_stack_index];
+}
+#endif		// __EFFICIENT__
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
diff --git a/Code/Sys/Mem/alloc.h b/Code/Sys/Mem/alloc.h
new file mode 100644
index 0000000..66bd33b
--- /dev/null
+++ b/Code/Sys/Mem/alloc.h
@@ -0,0 +1,478 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/alloc.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_ALLOC_H
+#define	__SYS_MEM_ALLOC_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+
+#ifdef __PLAT_NGC__
+#define __EFFICIENT__		// Comment in for efficient allocation.
+#endif		// __PLAT_NGC__
+
+//#ifdef	__PLAT_NGPS__											  
+#define	__LINKED_LIST_HEAP__    
+//#else
+//#ifdef __NOPT_DEBUG__
+//#define	__LINKED_LIST_HEAP__    
+//#endif
+//#endif
+
+// Mick: on the CD build, we don't need any memory tracking
+// this saves us at least 300K as we have over 30,000 individual allocations... crazy
+// K: This used to be #ifdef __NOPT_CDROM__, changed cos the nopt_cdrom define is now gone.
+#ifndef	__NOPT_ASSERT__
+#undef	__LINKED_LIST_HEAP__
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+const	uint	vALLOC_MAGIC = 0xCF801FE1;			// just a random magic number, to indicate a block is valid
+
+class	CMemProfile;
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class Region;
+
+class  Record  : public Spt::Class
+{
+	
+
+public:
+			Record( void ) : m_count(0), m_peak(0) {}
+
+			int		m_count;
+			int		m_peak;
+
+			Record&		operator+=( int src );
+			Record&		operator-=( int src );
+
+	const 	Record		operator++( int src );
+	const 	Record		operator--( int src );
+			Record&		operator++( void );
+			Record&		operator--( void );
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Allocator  : public Spt::Class				// abstract base class 			
+{
+	
+
+	friend class Manager;
+	friend class Region;
+
+public :
+
+	enum Direction
+	{		
+		vBOTTOM_UP	= +1,
+		vTOP_DOWN	= -1
+	};
+
+	static		uint 			sGetId( void* pAddr );
+		
+				Region*			ParentRegion( void );
+
+				Record			mUsedBlocks;
+				Record			mFreeBlocks;
+				Record			mUsedMem;
+				Record			mFreeMem;
+
+
+	virtual		void			PushContext( void );
+	virtual		void			PopContext( void );
+
+#ifdef __EFFICIENT__
+				void			PushAlign( int align );
+				void			PopAlign( void );
+				uint			GetAlign( void );
+#endif		// __EFFICIENT__
+
+#ifdef __NOPT_DEBUG__
+				void			dump_free_list ( void );
+				void			dump_used_list ( void );
+#endif
+
+				char *	GetName() {return mp_name;}
+				Direction GetDirection() {return m_dir;}
+
+/// sorry, need to access this from elsewhere, for debugging... (MICK)
+//protected :
+
+	class  BlockHeader  //: public Spt::Class
+	{
+		
+	
+	public :
+
+#ifdef __NOPT_MEM_DEBUG__
+
+				void				DbgAllocateBlock( void );
+				void				DbgFreeBlock( void );
+				void				DbgDump( void );
+
+#endif // __NOPT_MEM_DEBUG__
+
+									BlockHeader( Allocator* pAlloc, size_t size );
+									~BlockHeader( void );
+
+		static	BlockHeader* 		sRead( void* pMem );
+		
+				union
+				{
+					Allocator*		mpAlloc;
+					BlockHeader*	mpNext;
+				};
+
+#ifndef __EFFICIENT__
+				uint				mSize;
+#endif
+				uint				mId;
+#ifdef __EFFICIENT__
+#ifdef __NOPT_ASSERT__
+				CMemProfile	*		mp_profile;
+#endif		// __NOPT_ASSERT__
+#else
+				CMemProfile	*		mp_profile;
+#endif __EFFICIENT__
+
+
+		static const uint			sSize; // Aligned Block Header size
+
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+				BlockHeader*		mp_prev_used;
+				BlockHeader*		mp_next_used;
+				
+				void *				mp_debug_data;		// added for tracking callstacks for each allocation
+
+#endif // __NOPT_DEBUG__
+
+#ifdef __EFFICIENT__
+				uint				mSize:24;
+				uint				mPadBytes:8;		// Must be last byte in structure.
+#endif
+	};
+
+#ifdef __LINKED_LIST_HEAP__
+				void * 				find_block(void *p);
+				BlockHeader* 		first_block();
+#endif
+	
+    
+	class  Context  : public Spt::Class
+	{
+		
+	public:
+								Context( void );
+								~Context( void );
+	
+		Lst::Node< Context >	m_node;
+		BlockHeader*			mp_free_list;
+	
+	#ifdef	__LINKED_LIST_HEAP__    
+		BlockHeader*			mp_used_list;
+	#endif
+	
+	};
+
+									Allocator( Region* region, Direction dir, char *p_name );
+	virtual							~Allocator( void );
+	
+			Region*					mp_region;
+			Direction				m_dir;
+			void*					mp_top;
+			Context*				mp_context;
+
+			char *					mp_name;		// debugging name, for checking
+
+			Lst::Head< Context >	m_context_stack;
+
+private :
+
+	static  bool 			s_valid( void* pAddr );
+	static	size_t			s_get_alloc_size( void* pAddr );
+	static	Allocator* 		s_free( void* pAddr );
+	static	void 			s_set_id( void* pAddr );
+	virtual void*			allocate( size_t size, bool assert_on_fail ) = 0;
+	virtual int				available(  ) {Dbg_MsgAssert(0,("available() not defined for this allocator!")); return 0;}
+	virtual void*			reallocate_down( size_t newSize, void* pOld ) {Dbg_MsgAssert(0,("reallocate_down not defined for this allocator!")); return NULL;}
+	virtual void*			reallocate_up( size_t newSize, void* pOld ) {Dbg_MsgAssert(0,("reallocate_up not defined for this allocator!")); return NULL;}
+	virtual void*			reallocate_shrink( size_t newSize, void* pOld ) {Dbg_MsgAssert(0,("reallocate_shrink not defined for this allocator!")); return NULL;}
+	virtual	void			free( BlockHeader* pAddr ) = 0;
+
+	static	uint			s_current_id;
+			Context			m_initial_context;
+#ifdef __EFFICIENT__
+	static uint				s_align_stack[16];
+	static uint				s_align_stack_index;
+#endif		// __EFFICIENT__
+};                  		
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline Record& Record::operator++( void )			// prefix
+{
+	
+
+	*this += 1;
+	return	*this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Record Record::operator++( int )	// postfix
+{
+	
+	
+	Record ret = *this;
+	++( *this );
+
+	return ret;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Record& Record::operator--( void )		// prefix
+{
+	
+
+	*this -= 1;
+	return	*this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline const Record Record::operator--( int )	// postfix
+{
+	
+	
+	Record ret = *this;
+	--( *this );
+
+	return ret;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Record& Record::operator+=( int src )
+{
+	
+
+	m_count += src;
+	
+	if( m_count > m_peak )
+	{
+		m_peak = m_count;
+	}
+	
+	return *this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Record& Record::operator-=( int src )
+{
+	
+	
+	m_count -= src;
+
+	Dbg_MsgAssert(( m_count >= 0 ), ( "Count Underflow" ));
+	
+	return *this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Region* Allocator::ParentRegion( void )
+{
+	
+
+	return mp_region; 
+}
+	
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __EFFICIENT__
+inline Allocator::BlockHeader::BlockHeader( Allocator* pAlloc, size_t size )
+: mpAlloc( pAlloc ), mSize( size ), mPadBytes( 0 )
+#else
+inline Allocator::BlockHeader::BlockHeader( Allocator* pAlloc, size_t size )
+: mpAlloc( pAlloc ), mSize( size )
+#endif	// __EFFICIENT__
+{
+	
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+	mp_next_used = NULL;
+	mp_prev_used = NULL;
+	mp_debug_data  = NULL;
+
+#endif
+#ifdef __NOPT_ASSERT__
+	mp_profile = NULL;
+#endif		// __NOPT_ASSERT__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline Allocator::BlockHeader::~BlockHeader( void )
+{
+	
+
+#ifdef	__LINKED_LIST_HEAP__    
+	Dbg_MsgAssert( mp_prev_used == NULL, ( "internal error" ));
+	Dbg_MsgAssert( mp_next_used == NULL, ( "internal error" ));
+#endif
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#ifdef __NOPT_MEM_DEBUG__
+
+
+inline void	Allocator::BlockHeader::DbgAllocateBlock( void )
+{	
+	
+
+	uint64* ptr = (uint64*)((uint)this + sSize );
+
+	for ( uint i = 0; i < mSize; i += 8 )
+	{
+		*ptr++ = vALLOCATED_BLOCK;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void	Allocator::BlockHeader::DbgFreeBlock( void )
+{
+	
+	
+	uint64* ptr = (uint64*)((uint)this + sSize ); 	
+
+	for ( uint i = 0; i < mSize; i += 8 )
+	{
+		*ptr++ = vFREE_BLOCK;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void	Allocator::BlockHeader::DbgDump( void )
+{
+	
+#ifdef __NOPT_FULL_DEBUG__
+	Dbg_Message ( "\nBlockHeader [%p]\nmpAlloc/Next:%p\nSize %d\nstamp %x\n", this, mpAlloc, mSize, classStamp );
+#else
+	Dbg_Message ( "\nBlockHeader [%p]\nmpAlloc/Next:%p\nSize %d\n", this, mpAlloc, mSize );
+#endif
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#endif // __NOPT_MEM_DEBUG__
+
+bool  SameContext(void *p_mem,  Allocator *p_alloc);
+
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_ALLOC_H
diff --git a/Code/Sys/Mem/handle.h b/Code/Sys/Mem/handle.h
new file mode 100644
index 0000000..3cc47f3
--- /dev/null
+++ b/Code/Sys/Mem/handle.h
@@ -0,0 +1,187 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/handle.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_HANDLE_H
+#define	__SYS_MEM_HANDLE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+nTemplateBaseClass( _T, Handle )
+{
+	
+
+	
+public :
+
+								Handle( _T* ptr = NULL );
+								~Handle( void );
+
+	template < class _NewT >						                    // template copy contructor
+								Handle( Handle< _NewT >& rhs ); 		// needed to support inheritance correctly
+	
+	template < class _NewT > 	
+		Handle< _T >&			operator = ( Handle< _NewT >& rhs );	// template assignment operator
+		Handle< _T >&			operator = ( _T* ptr );
+
+		_T*						GetPointer( void ) const;
+		
+protected :
+
+		_T*						m_ptr;
+		uint					m_id;
+	
+};
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+template < class _T > inline   
+Handle< _T >::Handle< _T >( _T* ptr ) 
+: m_ptr ( ptr ), m_id ( Allocator::sGetId( ptr ))
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ ))
+// This version to fix Visual C++ compiler lack of ANSI C++ compliance.
+#else
+template < class _T > template < class _NewT > inline
+Handle< _T >::Handle< _T >( Handle< _NewT >& rhs )
+: m_ptr ( rhs.m_ptr ), m_id ( rhs.m_id )
+{
+	
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline	
+Handle< _T >::~Handle( void )
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ ))
+// This version to fix Visual C++ compiler lack of ANSI C++ compliance.
+#else
+ template < class _T > template < class _NewT > inline
+Handle< _T >&		Handle< _T >::operator = ( Handle< _NewT >& rhs ) 
+{
+	
+
+	m_ptr = rhs.m_ptr;
+	m_id  = rhs.m_id;
+
+	return *this;	
+}
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+Handle< _T >&		Handle< _T >::operator = ( _T* ptr ) 
+{
+	
+
+	m_ptr = ptr;
+	m_id  = Allocator::sGetId( ptr );
+
+	return *this;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+_T*		Handle< _T >::GetPointer ( void ) const 
+{
+	
+
+	if (( m_ptr ) && ( m_id == Allocator::sGetId( m_ptr )))
+	{
+		Dbg_AssertType( m_ptr, _T );
+		return m_ptr;
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_HANDLE_H								
diff --git a/Code/Sys/Mem/heap.cpp b/Code/Sys/Mem/heap.cpp
new file mode 100644
index 0000000..0dc4897
--- /dev/null
+++ b/Code/Sys/Mem/heap.cpp
@@ -0,0 +1,1155 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			Memory (Mem) 											**
+**																			**
+**	File name:		heap.cpp												**
+**																			**
+**	Maintained by:	Mick													**
+**																			**
+**	Description:	Heap class												**
+**																			**
+*****************************************************************************/
+
+
+// (Mick) undefine this line if you want the full PS2 callstack reporting on allocations
+// and the trashing of memory
+// Also you might want to set gHeapPools in Poolable.cpp
+// which makes the pools use the debug heap, so this debuggin applies to them.
+#define __PURE_HEAP__			// No debugging
+
+//////////////////////////////////////////////////////
+// Memory Debugging Trick:
+// dump the call stack for allocations and de-allocation of 
+// a particular block 
+// (maybe extend this to any block that encompasses this address????)  							   
+#define		REPORT_ON		0			// would be somethng like 0x3a2b50, if you want it to work 
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include "memman.h"
+#include "heap.h"
+#include 
+
+#define DUMP_HEAP 0			// Change to 1 to have a handy text dump in memory of the heap status.
+							// Use the debugger to see it - look at gMemStatus, 64 bytes per line.
+
+#define NUM_ADDRESS 0		// Change this to 8 to have a handy list of addresses to check.
+							// If set to 0, no extra code/data will be generated.
+							// Remember to change to 8, compile, then get the addresses in question
+							// as the act of adding this code/data will change your addresses.
+#if NUM_ADDRESS > 0
+uint32 gAddress[NUM_ADDRESS] =
+{
+	0x81828561,
+	0x818284c3,
+	0x81827621,
+	0x818275e3,
+	0x81827541,
+	0x81826523,
+	0x818264e1,
+	0x81826443
+};
+
+int g128 = 0;
+int g32 = 0;
+
+static void check_address( void * p, int size )
+{
+	for ( int lp = 0; lp < 8; lp++ )
+	{
+		if ( gAddress[lp] == ((uint32)p) ) {
+			Dbg_Message( "We found the address we're looking for: 0x%08x (%d)\n", (uint32)p, size );
+		}
+	}
+
+	if ( size == 32 )
+	{
+		g32++;
+	}
+	if ( size == 128 )
+	{
+		g128++;
+	}
+}
+
+#else
+#define check_address(a,b)
+#endif
+
+#if DUMP_HEAP == 1
+#ifdef __PLAT_XBOX__
+static SIZE_T	peakUsage = 64 * 1024 * 1024;	// 64mb.
+char gMemStatus[64*28];
+#else
+char gMemStatus[64*16];
+#endif
+
+void dump_heap( void )
+{
+
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+
+	sprintf(&gMemStatus[64*0], "Name            Used   Frag   Free   Blocks                    \n");
+	sprintf(&gMemStatus[64*1], "--------------- ------ ------ ------ ------                    \n");
+	Mem::Heap* heap;
+	int line = 2;
+	for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+	{		
+			Mem::Region* region = heap->ParentRegion();			
+			sprintf( &gMemStatus[64*line], "%12s: %5dK %5dK %5dK   %5d \n",
+					heap->GetName(),
+					heap->mUsedMem.m_count / 1024,
+					heap->mFreeMem.m_count / 1024,
+					region->MemAvailable() / 1024,
+					heap->mUsedBlocks.m_count
+					);
+			line++;
+	}
+
+#	ifdef __PLAT_XBOX__
+	MEMORYSTATUS stat;
+    GlobalMemoryStatus( &stat );
+    sprintf( &gMemStatus[64 * line++], "%4d  free kb of physical memory.\n", stat.dwAvailPhys / 1024 );
+
+	if( stat.dwAvailPhys < peakUsage )
+		peakUsage = stat.dwAvailPhys;
+
+    sprintf( &gMemStatus[64 * line++], "%4d  free kb at peak physical memory usage.\n", peakUsage / 1024 );
+#	endif
+
+
+
+}
+#endif		// DUMP_HEAP == 1
+	
+namespace Script
+{
+	class	CStruct;
+	class	CScript;
+}
+
+namespace CFuncs
+{
+bool ScriptDumpHeaps( Script::CStruct *pParams, Script::CScript *pScript );
+}
+
+#ifdef	__PLAT_NGPS__
+#include 
+#elif defined( __PLAT_NGC__ )
+#include 
+#endif
+
+
+
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#ifndef	__NOPT_ASSERT__
+#define	__PURE_HEAP__
+#endif
+
+
+#ifndef	__PURE_HEAP__		
+#define	__TRASH_BLOCKS__				// define this to trash contents of alloc and free, regardless of build				
+#endif
+
+
+						
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+inline Mem::Allocator::BlockHeader*	Heap::next_addr( Allocator::BlockHeader* pHeader )
+{
+	
+	
+	Dbg_AssertType( pHeader, BlockHeader );
+
+	return (BlockHeader*)( (uint)pHeader + BlockHeader::sSize + pHeader->mSize );
+}
+
+
+#ifdef	__TRASH_BLOCKS__
+
+//static const uint64 vTRASH_ALLOCATED_BLOCK = 0x0101010101010101;
+static const uint64 vTRASH_ALLOCATED_BLOCK = 0x5555555555555555LL; 	// grey
+static const uint64 vTRASH_FREE_BLOCK 	 =   0xbbbbbbbbbbbbbbbbLL;	// white
+//static const uint64 vTRASH_FREE_BLOCK 	 =       0x3f3f003f3f003f3f;	// ??? ??? (with 0 so string are terminated)
+								
+
+inline void	Trash_AllocateBlock( Mem::Allocator::BlockHeader* pBlock )
+{	
+#ifdef __EFFICIENT__
+	memset( (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize ), vTRASH_ALLOCATED_BLOCK & 0xff, pBlock->mSize );
+#else
+	uint64* ptr = (uint64*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize );
+
+	for ( uint i = 0; i < pBlock->mSize; i += 8 )
+	{
+		*ptr++ = vTRASH_ALLOCATED_BLOCK;
+	}
+#endif		// __EFFICIENT__
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+inline void	Trash_FreeBlock( Mem::Allocator::BlockHeader* pBlock )
+{
+#ifdef __EFFICIENT__
+	memset( (void*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize ), vTRASH_FREE_BLOCK & 0xff, pBlock->mSize );
+#else
+	uint64* ptr = (uint64*)((uint)pBlock + Mem::Allocator::BlockHeader::sSize ); 	
+
+	for ( uint i = 0; i < pBlock->mSize; i += 8 )
+	{
+		*ptr++ = vTRASH_FREE_BLOCK;
+	}
+#endif		// __EFFICIENT__
+}
+
+inline void	Trash_FreeBlockHeader( Mem::Allocator::BlockHeader* pBlock )
+{
+#ifdef __EFFICIENT__
+	memset( (void*)((uint)pBlock), vTRASH_FREE_BLOCK & 0xff, Mem::Allocator::BlockHeader::sSize );
+#else
+	uint64* ptr = (uint64*)((uint)pBlock); 	
+
+	for ( uint i = 0; i < Mem::Allocator::BlockHeader::sSize; i += 8 )
+	{
+		*ptr++ = vTRASH_FREE_BLOCK;
+	}
+#endif		// __EFFICIENT__
+}
+
+
+#endif
+
+				
+				
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Heap::free ( BlockHeader* pFreeBlock )
+{
+#if DUMP_HEAP == 1
+	dump_heap();
+#endif		// DUMP_HEAP == 1
+	
+#ifdef __EFFICIENT__
+	pFreeBlock->mSize += pFreeBlock->mPadBytes;
+	pFreeBlock->mPadBytes = 0;
+#endif		// __EFFICIENT__
+	
+	Dbg_AssertType ( pFreeBlock, BlockHeader );
+
+	#ifdef	__NOPT_ASSERT__
+	#ifdef	__PLAT_NGPS__
+	// find what feeded a particular block
+	if ( ((uint)pFreeBlock + BlockHeader::sSize) == REPORT_ON)
+	{
+		printf ("Freeing 0x%x",REPORT_ON);
+		DumpUnwindStack(20,NULL);
+	}
+	#endif
+	#endif
+			  
+			  
+	BlockHeader*	p_before = NULL;
+	BlockHeader*	p_2before = NULL;
+	BlockHeader*	p_after = mp_context->mp_free_list;
+
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+	MemView_Free((void*)pFreeBlock);
+#endif
+#endif
+	FreeMemProfile(pFreeBlock);
+
+	// mp_next_used and mp_prev_used are a doble linked null ternimated list
+
+	// unlink next and prev
+	if ( pFreeBlock->mp_next_used )
+	{
+		pFreeBlock->mp_next_used->mp_prev_used = pFreeBlock->mp_prev_used;
+	}
+	if ( pFreeBlock->mp_prev_used )
+	{
+		pFreeBlock->mp_prev_used->mp_next_used = pFreeBlock->mp_next_used;
+	}
+	// unlink if it's the head of the list
+	if ( mp_context->mp_used_list == pFreeBlock )
+	{
+		mp_context->mp_used_list = pFreeBlock->mp_next_used;
+	}
+
+	pFreeBlock->mp_prev_used = NULL;
+	pFreeBlock->mp_next_used = NULL;
+
+#endif
+
+// we trash the free block before we merge it with anything
+// otherwise we end us trashing multi-megabytes
+// when memory is fragmented (which it often is, in the middle of things)
+	#ifdef	__TRASH_BLOCKS__
+	Trash_FreeBlock ( pFreeBlock ); 
+	#endif
+
+	mFreeBlocks++;
+	mFreeMem += pFreeBlock->mSize;
+
+	mUsedBlocks--;
+	mUsedMem -= pFreeBlock->mSize;
+
+	// p_after starts at the head of the free list
+	// and traverses it until p_after is the block after this 
+	// block
+	while ( p_after && ( (uint)p_after < (uint)pFreeBlock ))
+	{		
+		p_2before = p_before;
+		p_before = p_after;
+		p_after = p_after->mpNext;
+	}
+
+	pFreeBlock->mpNext = p_after; 						// insert pFreeBlock before p_after
+	
+
+	if ( p_after )
+	{
+		if ( next_addr( pFreeBlock ) == p_after ) 		// p_after joins pFreeBlock
+		{
+			pFreeBlock->mpNext = p_after->mpNext;
+			pFreeBlock->mSize += p_after->mSize + BlockHeader::sSize;
+			
+									  
+			p_after->~BlockHeader();
+			#ifdef	__TRASH_BLOCKS__
+			Trash_FreeBlockHeader ( p_after ); 
+			#endif
+			mFreeBlocks--;
+			mFreeMem += BlockHeader::sSize;
+			mUsedMem -= BlockHeader::sSize;
+		}
+	}
+	
+	
+	if ( p_before )
+	{
+		if ( next_addr( p_before ) == pFreeBlock )		// pFreeBlock joins p_before
+		{	
+			p_before->mSize += pFreeBlock->mSize + BlockHeader::sSize;
+			p_before->mpNext = pFreeBlock->mpNext;
+
+
+
+			pFreeBlock->~BlockHeader();
+			
+			#ifdef	__TRASH_BLOCKS__
+			Trash_FreeBlockHeader ( pFreeBlock ); 
+			#endif
+
+			
+			mFreeBlocks--;
+			mFreeMem += BlockHeader::sSize;
+			mUsedMem -= BlockHeader::sSize;
+			
+			pFreeBlock = p_before;
+			p_before = p_2before;
+		}
+		else											// insert pFreeBlock after p_before
+		{							   				
+			p_before->mpNext = pFreeBlock;
+		}
+	}
+	else
+	{
+		mp_context->mp_free_list = pFreeBlock;
+	}
+
+	MemDbg_FreeBlock ( pFreeBlock ); 
+	
+
+														// reclaim free space in region
+
+	if ( ( m_dir == vBOTTOM_UP ) && ( !p_after ) && 
+		 ( mp_top == (void*)((uint)pFreeBlock + pFreeBlock->mSize + BlockHeader::sSize )))
+	{
+		if( p_before )
+		{
+			p_before->mpNext = NULL;
+		}
+		else
+		{
+			mp_context->mp_free_list = NULL;
+		}
+
+
+
+		mp_top = (void*)((uint)mp_top - ( pFreeBlock->mSize + BlockHeader::sSize) * m_dir );
+		pFreeBlock->~BlockHeader();
+		mFreeBlocks--;
+		mFreeMem -= pFreeBlock->mSize;
+		mUsedMem -= BlockHeader::sSize;
+		#ifdef	__TRASH_BLOCKS__
+		Trash_FreeBlockHeader ( pFreeBlock ); 
+		#endif
+	}
+	else if (( m_dir == vTOP_DOWN ) && ( !p_before ) &&
+			 ( mp_top == (void*)((uint)pFreeBlock )))
+	{		 
+	
+	
+		mp_top = (void*)((uint)mp_top - ( pFreeBlock->mSize + BlockHeader::sSize) * m_dir );
+		mp_context->mp_free_list = pFreeBlock->mpNext;
+		pFreeBlock->~BlockHeader();
+		
+		
+		mFreeBlocks--;
+		mFreeMem -= pFreeBlock->mSize;
+		mUsedMem -= BlockHeader::sSize;
+		#ifdef	__TRASH_BLOCKS__
+		Trash_FreeBlockHeader ( pFreeBlock ); 
+		#endif
+	}
+	
+#if 0
+	Mem::Manager& mem_man = Mem::Manager::sHandle();
+	Mem::Heap* heap = mem_man.BottomUpHeap();
+	Mem::Region* region = heap->ParentRegion();
+	if (heap->mFreeMem.m_count > 3200*1000)
+	{
+		printf ("\nBottomUp Fragmentation %7dK, in %5d Blocks\n",heap->mFreeMem.m_count / 1024, heap->mFreeBlocks.m_count);
+		DumpUnwindStack(20,NULL);
+		MemView_DumpFragments(heap);		
+		Dbg_MsgAssert(0,("Frag"));
+	}
+#endif
+	
+	
+	
+}
+
+
+// Returns the size in bytes of either the largest free fragmented block,
+// or the free space in the region whichever is larger
+int Heap::LargestFreeBlock()
+{
+	
+	uint32 size = 0;
+	BlockHeader* p_header = mp_context->mp_free_list;
+	while ( p_header ) 
+	{
+		if ( p_header->mSize >= size )
+		{
+			size = p_header->mSize;
+		}
+		p_header = p_header->mpNext;
+	}
+	uint32 region_size = mp_region->MemAvailable();
+	if (region_size > size)
+	{	
+		size = region_size;
+	}
+	return size;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Note the block returned might be bigger than asked for
+// by 16 or 32 bytes.
+// memory system calls MUST account for this 
+void*	Heap::allocate( size_t size, bool assert_on_fail )
+{
+#ifdef	__PLAT_NGPS__
+//	if (size > 10*1024)
+//	{
+//		printf ("\nAllocating %d\n",size);
+//		printf ("%s\n",Mem::Manager::sHandle().GetContextName());
+//	}
+	#endif
+	
+	Dbg_MsgAssert(size >0, ("Trying to allocate Zero bytes\n"));
+	/*
+	if (size == 0)
+	{
+		static int zeros=0;
+		printf("(%d) Trying to allocate Zero bytes\n", zeros++);
+		DumpUnwindStack(20,NULL);
+	}
+	*/	
+	
+	
+#if DUMP_HEAP == 1
+	dump_heap();
+#endif		// DUMP_HEAP == 1
+
+	BlockHeader* p_header = mp_context->mp_free_list;
+	BlockHeader* p_freeblock = NULL;
+	BlockHeader* p_last = NULL;
+	BlockHeader* p_prev = NULL;
+
+//	printf ("Trying to allocate %d bytes, current free list = %p\n",size,p_header);							 
+							 
+//	if (size == 8688) while (1);		
+
+#ifdef __EFFICIENT__
+	int align = 1 << GetAlign();
+	int offset_bytes;
+	size = (uint)nAlignUpBy( size, 2 );	// all allocations aligned by 4 bytes
+#else
+#ifdef __PLAT_NGC__
+	size = (uint)nAlignUpBy( size, 5 );	// all allocations aligned by 16 bytes
+#else
+	size = (uint)nAlignUpBy( size, 4 );	// all allocations aligned by 16 bytes
+#endif
+#endif
+	while ( p_header ) 	// find smallest free block large enough to fulfill request
+	{
+		Dbg_AssertType ( p_header, BlockHeader );
+
+#ifdef __EFFICIENT__
+		int off = ( ( ( (int)p_header + BlockHeader::sSize ) + ( align - 1 ) ) & -align ) - ( (int)p_header + BlockHeader::sSize );
+		uint adjusted_size = size + off;
+		if ( p_header->mSize >= adjusted_size )
+#else
+		if ( p_header->mSize >= size )
+#endif
+		{
+			if (( p_freeblock == NULL ) || ( p_freeblock->mSize > p_header->mSize ))
+			{
+				p_prev = p_last;
+				p_freeblock = p_header;
+			}
+		}
+
+		p_last = p_header;
+		p_header = p_header->mpNext;
+	}
+
+	if ( p_freeblock )	// found a free block large enough
+	{	
+#ifdef __EFFICIENT__
+		offset_bytes = ( ( ( (int)p_freeblock + BlockHeader::sSize ) + ( align - 1 ) ) & -align ) - ( (int)p_freeblock + BlockHeader::sSize );
+#endif
+		if ( p_prev )
+		{
+			p_prev->mpNext = p_freeblock->mpNext;
+		}
+		else
+		{
+			mp_context->mp_free_list = p_freeblock->mpNext;
+		}
+
+		p_freeblock->mpAlloc = this;
+
+		mFreeBlocks--;
+		mUsedBlocks++;
+		mFreeMem -= p_freeblock->mSize;
+		mUsedMem += p_freeblock->mSize;
+
+#ifdef __EFFICIENT__
+		BlockHeader*	p_leftover 	= (BlockHeader*)((uint)p_freeblock + BlockHeader::sSize + size + offset_bytes );
+		int				new_size 	= p_freeblock->mSize - size - BlockHeader::sSize - offset_bytes;
+#else
+		BlockHeader*	p_leftover 	= (BlockHeader*)((uint)p_freeblock + BlockHeader::sSize + size );
+		int				new_size 	= p_freeblock->mSize - size - BlockHeader::sSize;
+#endif		// __EFFICIENT__
+		
+		if ( new_size > 0 )	// create new free node for left-over memory
+		{
+			p_freeblock->mSize = size;
+			new ((void*)p_leftover) BlockHeader( this, new_size );			
+			mUsedBlocks++;
+			free( p_leftover );
+		}
+		else
+		{
+			// new_size is 0, which implies that this block is (size) ... (size + BlockHeader::Size)
+			// which means we'll be allocating a bit more than we need
+			// but as (size) is not reference again, then that's not a problem
+#ifdef __EFFICIENT__
+			p_freeblock->mSize -= offset_bytes;
+#endif		// __EFFICIENT__
+		}
+
+	}
+	else				// request extra space from region
+	{
+		p_freeblock = (BlockHeader*)mp_region->Allocate( this, size + BlockHeader::sSize, assert_on_fail );
+
+#ifdef __EFFICIENT__
+		if ( m_dir == vBOTTOM_UP )
+		{
+			offset_bytes = ( ( ( (int)p_freeblock + BlockHeader::sSize ) + ( align - 1 ) ) & -align ) - ( (int)p_freeblock + BlockHeader::sSize );
+			// Allocate, but keep the original pointer.
+			mp_region->Allocate( this, offset_bytes, assert_on_fail );
+		}
+		else
+		{
+			offset_bytes = ( (int)p_freeblock + BlockHeader::sSize ) - ( ( (int)p_freeblock + BlockHeader::sSize ) & -align );
+			// Allocate, increase size, not offset bytes.
+			if ( offset_bytes )
+			{
+				p_freeblock = (BlockHeader*)mp_region->Allocate( this, offset_bytes, assert_on_fail );
+				size += offset_bytes;
+				offset_bytes = 0;
+			}
+		}
+#endif
+		if ( p_freeblock )
+		{
+			new ((void*)p_freeblock) BlockHeader( this, size );
+
+			mUsedBlocks++;
+#ifdef __EFFICIENT__
+			mUsedMem += size + BlockHeader::sSize + offset_bytes;
+#else			
+			mUsedMem += size + BlockHeader::sSize;
+#endif		// __EFFICIENT__
+		}
+	}
+
+	if ( p_freeblock )
+	{		
+		MemDbg_AllocateBlock ( p_freeblock );
+
+		#ifdef	__TRASH_BLOCKS__
+		Trash_AllocateBlock ( p_freeblock ); 
+		#endif
+
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+		if( mp_context->mp_used_list )
+		{
+			p_freeblock->mp_next_used = mp_context->mp_used_list;
+			mp_context->mp_used_list->mp_prev_used = p_freeblock;
+		}
+		else
+		{
+			p_freeblock->mp_next_used = NULL;	 	
+		}	
+		p_freeblock->mp_prev_used = NULL;	
+
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+		MemView_Alloc((void*)p_freeblock);
+#endif
+#endif
+		mp_context->mp_used_list = p_freeblock; 
+#endif
+		AllocMemProfile(p_freeblock);
+
+		#ifdef	__NOPT_ASSERT__
+		#ifdef	__PLAT_NGPS__
+		// find what allocated a particular block
+		if ( ((uint)p_freeblock + BlockHeader::sSize) == REPORT_ON)
+		{
+			printf ("allocating 0x%x",REPORT_ON);
+			DumpUnwindStack(20,NULL);
+		}
+		#endif
+		#endif
+
+#ifdef __EFFICIENT__
+		check_address( (void*)((uint)p_freeblock + BlockHeader::sSize + offset_bytes), size );
+
+		// Fill padding bytes with bytes offset value.
+		p_freeblock->mPadBytes = offset_bytes;
+		uint8 * p8 = (uint8*)((uint)p_freeblock + BlockHeader::sSize);
+		for ( int lp = 0; lp < offset_bytes; lp++ )
+		{
+			*p8++ = offset_bytes;
+		}
+
+		return (void*)((uint)p_freeblock + BlockHeader::sSize + offset_bytes);
+#else
+		check_address( (void*)((uint)p_freeblock + BlockHeader::sSize), size );
+
+		return (void*)((uint)p_freeblock + BlockHeader::sSize);
+#endif		// __EFFICIENT__
+	}
+
+// Mick: heap allocations currently coded to ALWAYS assert on fail
+//	if ( assert_on_fail )
+	{
+		Dbg_MsgAssert ( false,( "Failed to allocate %d bytes", size ));
+
+
+	}
+
+//	Mem::Heap* heap = Mem::Manager::sHandle().BottomUpHeap();
+//	printf ("Dumping Fragments.....\n");
+//	MemView_DumpFragments(heap);
+	
+
+	printf ("----------------------------------\n");
+	printf ("failed to allocate %d bytes\n", size); 	
+
+#ifdef __PLAT_NGC__
+#ifndef __NOPT_FINAL__
+	{
+		Mem::Manager& mem_man = Mem::Manager::sHandle();
+
+		printf ("MEM CONTEXT: %s\n",Mem::Manager::sHandle().GetContextName());
+		printf("Name            Used  Frag  Free   Blocks\n");
+		printf("--------------- ----- ----- ------ ------\n");
+		Mem::Heap* heap;
+		for (heap = mem_man.FirstHeap(); heap != NULL; heap = mem_man.NextHeap(heap))
+		{		
+				Mem::Region* region = heap->ParentRegion();			
+				printf( "%12s: %6dK %6dK %6dK   %6d \n",
+						heap->GetName(),
+						heap->mUsedMem.m_count / 1024,
+						heap->mFreeMem.m_count / 1024,
+						region->MemAvailable() / 1024,
+						heap->mUsedBlocks.m_count
+						);										
+		}
+	}
+#endif
+#endif		// __PLAT_NGC__ 
+
+#ifndef __PLAT_WN32__
+	CFuncs::ScriptDumpHeaps(NULL,NULL);
+#endif
+
+	return NULL;
+
+}
+
+int	Heap::available()
+{
+	return LargestFreeBlock();
+}
+
+// Ken addition, for use by pip.cpp when it needs to reallocate the block of memory used
+// by a pre file that has just been loaded in, so that the pre can be decompressed.
+//
+// This function will return a pointer to a new block of memory which includes the original
+// block, but such that the returned pointer is lower down in memory than the original.
+// The original contents of the memory block will be unchanged.
+// This function is most likely to work if used on the top-down heap.
+// It will assert if it does not work.
+void* Heap::reallocate_down( size_t newSize, void *pOld )
+{
+	// What this function is going to do is allocate a new block with size equal to 
+	// the difference between newSize and the size of pOld, then see if the new block 
+	// is directly below the passed pOld.
+	// If it is, then it will merge the two blocks together.
+	// If it is not, it will assert.
+	
+	Dbg_MsgAssert(pOld,("NULL pOld sent to reallocate_down !"));
+	BlockHeader* p_old_block = BlockHeader::sRead( pOld ); 
+	
+#ifdef __EFFICIENT__
+	newSize = (uint)nAlignUpBy( newSize, 2 );	// all allocations aligned by 4 bytes
+#else
+#ifdef __PLAT_NGC__
+	newSize = (uint)nAlignUpBy( newSize, 5 );	// all allocations aligned by 32 bytes
+#else
+	newSize = (uint)nAlignUpBy( newSize, 4 );	// all allocations aligned by 16 bytes
+#endif
+#endif		// __EFFICIENT__
+	
+	Dbg_MsgAssert(p_old_block->mSize < newSize,("Tried to reallocate a block that was already big enough, old size=%d, requested size=%d",p_old_block->mSize,newSize));
+	
+	// When bobbling together the new and old blocks, we will gain the space used by the old
+	// block header, so we don't really need to include that space in the new allocation request.
+	// But, if newSize is exactly a blockheader size bigger than the old, then that would cause
+	// allocate to be called on a size of zero.
+	// So allocate a blockheader size more than necessary to avoid this.
+	void *p_new=allocate(newSize-p_old_block->mSize,true);
+	Dbg_MsgAssert(p_new,("allocate failed!"));
+	
+	// Got the new block, so now check that it is directly below the old.	
+	BlockHeader* p_new_block=BlockHeader::sRead( p_new );
+	Dbg_MsgAssert( (BlockHeader*)(((uint)p_new)+p_new_block->mSize) == p_old_block,("reallocate_down failed! New block is not directly below the old."));
+	
+	// Got this far, so the new block is right below the old.
+	// Now we have to remove the old block header so as to bobble the two blocks together.
+
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__	  
+	// Both blocks need to be freed from the memory tracker
+	// as together they take up all the space of the new block, whcih will be added below  
+	MemView_Free((void*)p_old_block); 
+	MemView_Free((void*)p_new_block); 
+#endif
+#endif
+
+	// mp_next_used and mp_prev_used are a doble linked null ternimated list
+
+	// unlink next and prev
+	if ( p_old_block->mp_next_used )
+	{
+		p_old_block->mp_next_used->mp_prev_used = p_old_block->mp_prev_used;
+	}
+	if ( p_old_block->mp_prev_used )
+	{
+		p_old_block->mp_prev_used->mp_next_used = p_old_block->mp_next_used;
+	}
+	// unlink if it's the head of the list
+	if ( mp_context->mp_used_list == p_old_block )
+	{
+		mp_context->mp_used_list = p_old_block->mp_next_used;
+	}
+
+	p_old_block->mp_prev_used = NULL;
+	p_old_block->mp_next_used = NULL;
+#endif
+// Free both blocks, so we can just add one later
+	FreeMemProfile(p_old_block);
+	FreeMemProfile(p_new_block);
+
+#ifdef __EFFICIENT__
+	newSize += p_old_block->mPadBytes;
+#endif		// __EFFICIENT__
+
+	p_old_block->~BlockHeader();	
+	#ifdef	__TRASH_BLOCKS__
+	Trash_FreeBlockHeader ( p_old_block ); 
+	#endif
+	
+	// The memory occupied by the old block header has gone from being used as a BlockHeader, 
+	// to being used as part of a used memory block, so mUsedMem does not need to change.
+	// The number of used blocks has changed though, since two are being merged into one.
+	--mUsedBlocks;
+	// Nothing has changed regarding free blocks, so mFreeBlocks and mFreeMem stay the same.
+	
+	
+	// That's p_old_block cleaned up, so now modify p_new_block's size.
+	p_new_block->mSize=newSize+BlockHeader::sSize;
+
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+	MemView_Alloc((void*)p_new_block);
+#endif
+#endif
+	AllocMemProfile(p_new_block);
+
+	
+#ifdef __EFFICIENT__
+	return (void*)((uint)p_new_block + BlockHeader::sSize + p_new_block->mPadBytes);
+#else
+	return (void*)((uint)p_new_block + BlockHeader::sSize);
+#endif		// __EFFICIENT__
+}
+
+// This will make the passed memory block bigger.
+// The original contents of the memory block will be unchanged.
+// This function is most likely to work if used on the bottom-up heap.
+// It will assert if it does not work.
+void *Heap::reallocate_up( size_t newSize, void *pOld )
+{
+	// What this function is going to do is allocate a new block with size equal to 
+	// the difference between newSize and the size of pOld, then see if the new block 
+	// is directly above the passed pOld.
+	// If it is, then it will merge the two blocks together.
+	// If it is not, it will assert.
+	
+	Dbg_MsgAssert(pOld,("NULL pOld sent to reallocate_up !"));
+	BlockHeader* p_old_block = BlockHeader::sRead( pOld ); 
+	
+#ifdef __EFFICIENT__
+	newSize = (uint)nAlignUpBy( newSize, 2 );	// all allocations aligned by 4 bytes
+#else
+#ifdef __PLAT_NGC__
+	newSize = (uint)nAlignUpBy( newSize, 5 );	// all allocations aligned by 32 bytes
+#else
+	newSize = (uint)nAlignUpBy( newSize, 4 );	// all allocations aligned by 16 bytes
+#endif
+#endif		// __EFFICIENT__
+	
+	if (p_old_block->mSize >= newSize)
+	{
+		// The current block is already big enough, so nothing to do.
+		return pOld;
+	}
+		
+	// When bobbling together the new and old blocks, we will gain the space used by the old
+	// block header, so we don't really need to include that space in the new allocation request.
+	// But, if newSize is exactly a blockheader size bigger than the old, then that would cause
+	// allocate to be called on a size of zero.
+	// So allocate a blockheader size more than necessary to avoid this.
+	void *p_new=allocate(newSize-p_old_block->mSize,true);
+	Dbg_MsgAssert(p_new,("allocate failed!"));
+	
+	// Got the new block, so now check that it is directly above the old.	
+	BlockHeader* p_new_block=BlockHeader::sRead( p_new );
+	if ( (BlockHeader*)(((uint)pOld)+p_old_block->mSize) != p_new_block)
+	{
+		// It isn't!
+		free(p_new_block);
+		return NULL;
+	}	
+	
+	// Got this far, so the new block is right above the old.
+	// Now we have to remove the new block header so as to bobble the two blocks together.
+
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+// remove both blocks from memory tracking, ready to add the new block
+// that encompasses both of them
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+	MemView_Free((void*)p_old_block); 
+	MemView_Free((void*)p_new_block); 
+#endif
+#endif
+	// mp_next_used and mp_prev_used are a doble linked null ternimated list
+
+	// unlink next and prev
+	if ( p_new_block->mp_next_used )
+	{
+		p_new_block->mp_next_used->mp_prev_used = p_new_block->mp_prev_used;
+	}
+	if ( p_new_block->mp_prev_used )
+	{
+		p_new_block->mp_prev_used->mp_next_used = p_new_block->mp_next_used;
+	}
+	// unlink if it's the head of the list
+	if ( mp_context->mp_used_list == p_new_block )
+	{
+		mp_context->mp_used_list = p_new_block->mp_next_used;
+	}
+
+	p_new_block->mp_prev_used = NULL;
+	p_new_block->mp_next_used = NULL;
+#endif
+	FreeMemProfile(p_old_block);
+	FreeMemProfile(p_new_block);
+
+	// (Mick) Since the alloc function might return a bigger block than was asked for
+	// we need to account for this in calculating the size of the new block
+	// we can get the size of the new block before we trash the header
+	int new_block_size = p_new_block->mSize;
+
+	p_new_block->~BlockHeader();	
+	#ifdef	__TRASH_BLOCKS__
+	Trash_FreeBlockHeader ( p_new_block ); 
+	#endif
+	
+	// The memory occupied by the new block header has gone from being used as a BlockHeader, 
+	// to being used as part of a used memory block, so mUsedMem does not need to change.
+	// The number of used blocks has changed though, since two are being merged into one.
+	--mUsedBlocks;
+	// Nothing has changed regarding free blocks, so mFreeBlocks and mFreeMem stay the same.
+
+#ifdef	__NOPT_ASSERT__
+	if (pOld == (void*)REPORT_ON)
+	{
+		printf ("%p: realloced up from %d to %d (actually %d) bytes\n", p_old_block, p_old_block->mSize, newSize, new_block_size + BlockHeader::sSize);  	
+	}
+#endif
+	
+	
+	// That's p_new_block cleaned up, so now modify p_old_block's size.
+//	p_old_block->mSize=newSize+BlockHeader::sSize;		 // Old method, did not account for alloc returning oversized blocks
+	p_old_block->mSize += new_block_size + BlockHeader::sSize;	  // (Mick) New method, uses the actual size of the new block (plus blockheader)
+
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+	MemView_Alloc((void*)p_old_block); 
+#endif
+#endif
+	AllocMemProfile(p_old_block);
+
+
+	
+	return pOld;
+}
+
+void *Heap::reallocate_shrink( size_t newSize, void *pOld )
+{
+	// This will shrink the passed block by creating a new free block out of the remaining
+	// space, if there is enough room to make a new free block. (at least 32 bytes needed)
+	// It creates the new free block by first creating a new used block, then freeing that.
+	// That way, all the merging of free blocks will be taken care of.
+		
+	Dbg_MsgAssert(pOld,("NULL pOld sent to reallocate_up !"));
+	BlockHeader* p_old_block = BlockHeader::sRead( pOld ); 
+
+
+	Dbg_MsgAssert(p_old_block->mpAlloc == this,("Shrinking block in wrong context"));
+
+
+	
+	//printf("Block size before shrinking = %d\n",p_old_block->mSize);
+	
+	Dbg_MsgAssert(newSize<=p_old_block->mSize,("Larger size sent to reallocate_shrink:\nold size=%d  requested new size=%d",p_old_block->mSize,newSize));
+	uint32 size_diff=p_old_block->mSize-newSize;
+	
+	// We can only shrink down by a number of bytes that is divisible be 16 (4 on ngc), so
+	// that the resulting block size for the old block remains a multiple of 16 (4)
+#ifdef __EFFICIENT__
+	size_diff&=~((1<<2)-1);
+#else
+	#ifdef __PLAT_NGC__
+	size_diff&=~((1<<5)-1);
+	#else
+	size_diff&=~((1<<4)-1);
+	#endif
+#endif		// __EFFICIENT__
+	
+	// If the amount the block is being shrunk by is not enough to hold a block header
+	// with a bit left over, then we can't make a new free block with it, so return without
+	// doing anything.
+	if (size_diff<=BlockHeader::sSize)
+	{
+		return pOld;
+	}	
+	
+	// Calculate a pointer to the new block.
+	BlockHeader *p_new_block=(BlockHeader*)((uint32)pOld+p_old_block->mSize-size_diff);
+
+	#ifdef	__PLAT_NGPS__
+	Dbg_MsgAssert(((int)p_new_block&0xf) == 0,("p_new_block odd (%p), pOld = %p, p_old_block = %p, p_old_block->mSize = %d, size_diff = %d",
+										p_new_block, pOld, p_old_block->mSize, size_diff));
+	#endif
+	
+	// Fill in new block header
+	p_new_block->mSize=size_diff-BlockHeader::sSize;
+#ifdef __EFFICIENT__
+	p_new_block->mPadBytes = 0;
+#endif		// __EFFICIENT__ 
+	/////////////////////////////////////////////////////////////////////
+	// Do all the stuff one needs to do when creating a new used block.
+	// I just happily cut-and-pasted this lot from ::allocate
+	p_new_block->mpAlloc = this;
+	MemDbg_AllocateBlock ( p_new_block );
+	#ifdef	__TRASH_BLOCKS__
+	Trash_AllocateBlock ( p_new_block ); 
+	#endif
+
+#ifdef	__LINKED_LIST_HEAP__    
+
+	if( mp_context->mp_used_list )
+	{
+		p_new_block->mp_next_used = mp_context->mp_used_list;
+		mp_context->mp_used_list->mp_prev_used = p_new_block;
+	}
+	else
+	{
+		p_new_block->mp_next_used = NULL;	 	
+	}	
+	p_new_block->mp_prev_used = NULL;	
+
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+	MemView_Alloc((void*)p_new_block);
+#endif
+#endif
+	mp_context->mp_used_list = p_new_block; 
+#endif
+	AllocMemProfile(p_new_block);
+	/////////////////////////////////////////////////////////////////////
+	
+	// mUsedMem does not change, since we've made a new used block out of memory
+	// that was already being used.
+	// The number of used blocks has increased by one though.
+	++mUsedBlocks;
+	// Nothing has changed regarding mFreeBlocks or mFreeMem yet. That will all be handled
+	// when the block gets freed.
+	
+	// Free the block just created.
+	free(p_new_block);
+		
+
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+	MemView_Free((void*)p_old_block);	   // free at old size
+#endif
+#endif
+	FreeMemProfile(p_old_block);
+
+	// Update the size of the old block.
+	p_old_block->mSize-=size_diff;
+	
+#ifdef	__PLAT_NGPS__
+#ifndef	__PURE_HEAP__		
+	MemView_Alloc((void*)p_old_block);	   // re-register at new size
+#endif
+#endif
+	AllocMemProfile(p_old_block);
+
+	
+	//printf("New block size after shrinking = %d\n",p_old_block->mSize);
+	return pOld;
+}
+
+/****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Heap::Heap( Region* region, Direction dir, char *p_name )
+: Allocator( region, dir, p_name )
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
diff --git a/Code/Sys/Mem/heap.h b/Code/Sys/Mem/heap.h
new file mode 100644
index 0000000..2f842e1
--- /dev/null
+++ b/Code/Sys/Mem/heap.h
@@ -0,0 +1,100 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/heap.h										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_HEAP_H
+#define	__SYS_MEM_HEAP_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include "alloc.h"
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class Region;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+class  Heap : public  Allocator 			
+{
+	
+	
+	friend class Manager;
+
+public :
+		
+								Heap( Region* region, Direction dir = vBOTTOM_UP, char* p_name = "unknown heap" );
+
+	int							LargestFreeBlock();
+	
+private :
+	
+	virtual		void*			allocate( size_t size, bool assert_on_fail );
+	virtual		void			free( BlockHeader* pHeader );
+
+	virtual		int				available();
+	virtual		void* 			reallocate_down( size_t new_size, void *pOld );
+	virtual		void*			reallocate_up( size_t newSize, void *pOld );
+	virtual		void*			reallocate_shrink( size_t newSize, void *pOld );
+	
+				
+				BlockHeader*	next_addr( BlockHeader* pHeader );
+};
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_HEAP_H
diff --git a/Code/Sys/Mem/memdbg.h b/Code/Sys/Mem/memdbg.h
new file mode 100644
index 0000000..4dfb4f9
--- /dev/null
+++ b/Code/Sys/Mem/memdbg.h
@@ -0,0 +1,79 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Management (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		sys/mem/memdbg.h										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_MEMDBG_H
+#define	__SYS_MEM_MEMDBG_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#if defined( __PLAT_WN32__ ) || defined( __PLAT_XBOX__ )
+static const uint64 vALLOCATED_BLOCK = 0x0101010101010101i64;
+static const uint64 vFREE_BLOCK 	 = 0x0303030303030303i64;
+#else
+static const uint64 vALLOCATED_BLOCK = 0x0101010101010101LL;
+static const uint64 vFREE_BLOCK 	 = 0x0303030303030303LL;
+#endif	// __PLAT_WN32__
+
+#ifdef __NOPT_MEM_DEBUG__
+	
+#define MemDbg_AllocateBlock(_header)	_header->DbgAllocateBlock()
+#define MemDbg_FreeBlock(_header)		_header->DbgFreeBlock()
+
+#else // !__NOPT_MEM_DEBUG__
+
+#define MemDbg_AllocateBlock(_header)
+#define MemDbg_FreeBlock(_header)
+
+#endif
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+   
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_MEMDBG_H								
diff --git a/Code/Sys/Mem/memman.cpp b/Code/Sys/Mem/memman.cpp
new file mode 100644
index 0000000..80b7c03
--- /dev/null
+++ b/Code/Sys/Mem/memman.cpp
@@ -0,0 +1,1722 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			Memory (Mem) 											**
+**																			**
+**	File name:		memman.cpp												**
+**																			**
+**	Created by:		03/20/00	-	mjb										**
+**																			**
+**	Description:	Memory manager											**
+**																			**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "memman.h"
+#include 
+#include 	// for memory profiling, to see if we ahve the extra memory
+#include "heap.h"
+#include "alloc.h"
+#include 
+#ifdef __PLAT_XBOX__
+#include 
+#endif
+#ifdef __PLAT_NGC__
+#include 
+#endif
+
+#include 
+
+
+#ifdef	__PLAT_NGPS__
+
+static	bool	s_use_semaphore = false;
+
+void WaitSemaMaybe(int sema)
+{
+	if (s_use_semaphore)
+	{
+		WaitSema(sema);
+	}
+}
+
+
+void SignalSemaMaybe(int sema) 
+{
+	if (s_use_semaphore)
+	{
+		SignalSema(sema);
+	}
+}
+
+#endif
+
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+#if defined ( __PLAT_NGPS__ )
+extern char _code_start[]					__attribute__((section(".text")));
+extern char _code_end[]						__attribute__((section(".text")));
+extern char _data_end[]						__attribute__((section(".text")));
+extern char _mem_start[]					__attribute__((section(".mem_block")));
+extern char _mem_end[]						__attribute__((section(".mem_block")));
+extern char _std_mem_end[]					__attribute__((section(".mem_block")));
+extern char _debug_heap_start[]				__attribute__((section(".mem_block")));
+extern char _script_heap_start[]			__attribute__((section(".mem_block")));
+#else
+char* 	_code_start;
+char* 	_code_end;
+char* 	_data_end;
+char* 	_mem_start;
+char* 	_mem_end;
+char*	_std_mem_end;
+char *	_debug_heap_start;
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+char 			Manager::s_manager_buffer[sizeof(Manager)];
+char 			Manager::s_region_buffer[sizeof(Region)];
+char 			Manager::s_script_region_buffer[sizeof(Region)];
+char 			Manager::s_top_heap_buffer[sizeof(Heap)];
+char 			Manager::s_bot_heap_buffer[sizeof(Heap)];
+char 			Manager::s_debug_heap_buffer[sizeof(Heap)];
+Manager*		Manager::sp_instance = NULL;
+#ifdef __PLAT_NGPS__
+static	int		s_context_semaphore;
+#endif
+char			Manager::s_debug_region_buffer[sizeof(Region)];
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::Manager( void )
+{
+	
+	
+	m_current_id = 0;
+	mp_process_man = NULL;
+	
+#	if defined ( __PLAT_XBOX__ )
+	// Just grab 33mb of main memory.
+	_mem_start		= (char*)XPhysicalAlloc(	33 * 1024 * 1024,				// size of region
+												MAXULONG_PTR,					// base physical address
+												0,								// memory alignment
+												PAGE_READWRITE );				// memory protection and type
+
+	_mem_end		= _mem_start + ( 33 * 1024 * 1024 );
+	_std_mem_end	= _mem_end;
+#	elif defined ( __PLAT_WN32__ )
+	// Nasty hack for WN32 for now - just grab 38mb of main memory via a malloc.
+	_mem_start	= (char *)malloc( 38 * 1024 * 1024 );
+	_mem_end	= _mem_start + ( 38 * 1024 * 1024 );
+	_std_mem_end = _mem_end;
+
+#	endif 
+#	if defined ( __PLAT_NGC__ )
+	// Fill in what we know.
+	_code_start		= (char *)0xfadebabe;		// Junk addresses.
+	_code_end		= (char *)0xfacced00;
+	_data_end		= (char *)0xcaccfacc;
+	_mem_start		= &((char *)OSGetArenaLo())[8192];  // Real, actual useful addresses.
+	_mem_end		= (char *)OSGetArenaHi();
+	_std_mem_end	= (char *)OSGetArenaHi();
+#	endif
+
+	// create root memory region
+	mp_region 	= new ((void*)s_region_buffer) Region( nAlignUp( _mem_start ), nAlignDown( _std_mem_end ) );
+
+	// create default heaps
+	mp_top_heap = new ((void*)s_top_heap_buffer) Heap( mp_region, Heap::vTOP_DOWN, "Top Down" );
+	mp_bot_heap = new ((void*)s_bot_heap_buffer) Heap( mp_region, Heap::vBOTTOM_UP, "BottomUp" );
+
+	m_heap_list[0] = mp_top_heap;
+	m_heap_list[1] = mp_bot_heap;
+	
+	m_num_heaps = 2;
+
+#	if !defined( __PLAT_NGC__ ) || ( defined( __PLAT_NGC__ ) && !defined( __NOPT_FINAL__ ) )
+	uint codesize = ((uint)(_code_end) - (uint)(_code_start))/1024;
+	uint datasize = ((uint)(_data_end) - (uint)(_code_end))/1024;
+	printf ( "code [%p - %p] (%dK) + data [%p - %p] (%dK) = %dK \n",
+			 _code_start, _code_end, codesize, 
+			 _code_end, _data_end, datasize,
+			 codesize + datasize );
+#endif
+			 
+	m_pushed_context_count = 0;
+	mp_internet_region = NULL;
+	mp_net_misc_region = NULL;
+
+	mp_cutscene_region = NULL;
+	mp_cutscene_bottom_heap = NULL;
+	mp_cutscene_top_heap = NULL;
+
+#ifdef __PLAT_NGPS__
+	// Create a semaphore to prevent threads from re-entering push/pop memory contexts
+	struct SemaParam params;
+
+	params.initCount = 1;
+	params.maxCount = 10;
+
+	s_context_semaphore = CreateSema( ¶ms );			 
+#endif
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+	
+
+	mp_top_heap->~Heap();
+	mp_bot_heap->~Heap();
+	mp_region->~Region();
+
+#ifdef __PLAT_NGPS__
+	DeleteSema( s_context_semaphore );
+#endif
+}
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void*	Manager::New( size_t size, bool assert_on_fail, Allocator* pAlloc )
+{   
+#ifdef __PLAT_NGPS__
+	WaitSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	if ( !pAlloc ) 					// set to 'default' allocator
+	{
+		pAlloc = mp_context->mp_alloc;
+	}
+
+	void* p_ret = pAlloc->allocate( size, assert_on_fail );
+	
+	if ( p_ret )	// if allocation was successful
+	{
+		Allocator::s_set_id( p_ret );		// stamp ID; used by Mem::Handle
+	}
+
+#if 0 
+	if ( mp_process_man )
+	{
+		mp_process_man->AllocEv( p_ret, size, pAlloc );
+	}
+#endif
+
+#ifdef __PLAT_NGPS__
+	SignalSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	return p_ret;
+}
+
+
+// Returns the amount of memory avaialbe in the current context
+// currently this is only valid for Heaps
+int		Manager::Available()
+{
+	return mp_context->mp_alloc->available();
+}
+
+// Ken addition, for use by pip.cpp when it needs to reallocate the block of memory used
+// by a pre file that has just been loaded in, so that the pre can be decompressed.
+void*	Manager::ReallocateDown( size_t newSize, void *pOld, Allocator* pAlloc )
+{   
+#ifdef __PLAT_NGPS__
+	WaitSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	if ( !pAlloc ) 					// set to 'default' allocator
+	{
+		pAlloc = mp_context->mp_alloc;
+	}
+
+	void* p_ret = pAlloc->reallocate_down( newSize, pOld );
+	
+	if ( p_ret )	// if allocation was successful
+	{
+		Allocator::s_set_id( p_ret );		// stamp ID; used by Mem::Handle
+	}
+
+#ifdef __PLAT_NGPS__
+	SignalSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	return p_ret;
+}
+
+// Ken addition, for use by pip.cpp when it needs to reallocate the block of memory used
+// by a pre file that has just been loaded in, so that the pre can be decompressed.
+// This is for when the bottoms-up heap is being used.
+void *Manager::ReallocateUp( size_t newSize, void *pOld, Allocator* pAlloc )
+{   
+#ifdef __PLAT_NGPS__
+	WaitSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	if ( !pAlloc ) 					// set to 'default' allocator
+	{
+		pAlloc = mp_context->mp_alloc;
+	}
+
+	void* p_ret = pAlloc->reallocate_up( newSize, pOld );
+	
+	if ( p_ret )	// if allocation was successful
+	{
+		Allocator::s_set_id( p_ret );		// stamp ID; used by Mem::Handle
+	}
+
+#ifdef __PLAT_NGPS__
+	SignalSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	return p_ret;
+}
+
+void *Manager::ReallocateShrink( size_t newSize, void *pOld, Allocator* pAlloc )
+{   
+#ifdef __PLAT_NGPS__
+	WaitSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	if ( !pAlloc ) 					// set to 'default' allocator
+	{
+		pAlloc = mp_context->mp_alloc;
+	}
+
+	void* p_ret = pAlloc->reallocate_shrink( newSize, pOld );
+	
+	if ( p_ret )	// if allocation was successful
+	{
+		Allocator::s_set_id( p_ret );		// stamp ID; used by Mem::Handle
+	}
+
+#ifdef __PLAT_NGPS__
+	SignalSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	return p_ret;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::PushMemoryMarker( uint32 uiID )
+{
+}
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::PopMemoryMarker( uint32 uiID )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void	Manager::Delete( void* pAddr )
+{
+#ifdef __PLAT_NGPS__
+	WaitSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	if( pAddr != NULL )
+	{
+		Allocator::s_free( pAddr );
+
+#if 0
+		// 000810 JAB: Modified s_free to return the allocator.
+		if (mp_process_man)
+		{
+			mp_process_man->FreeEv( pAddr, p_alloc );
+		}
+#endif
+
+	}
+
+#ifdef __PLAT_NGPS__
+	SignalSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+bool	Manager::Valid( void* pAddr )
+{
+	if( pAddr != NULL )
+	{
+		return Allocator::s_valid( pAddr );
+	}
+	else
+	{
+		return false;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+size_t	Manager::GetAllocSize( void* pAddr )
+{
+	if( pAddr != NULL )
+	{
+		return Allocator::s_get_alloc_size( pAddr );
+	}
+	else
+	{
+		Dbg_MsgAssert( 0, ( "Trying to get the size of an invalid block" ) );
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::PushContext( Allocator* alloc )
+{
+	
+	Dbg_AssertType ( alloc, Allocator );
+
+#ifdef __PLAT_NGPS__
+	WaitSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+//	printf ("Pushed context %d to %s\n",m_pushed_context_count, alloc->GetName());
+//	DumpUnwindStack(20,0);
+
+	Dbg_MsgAssert(m_pushed_context_count < vMAX_CONTEXT-1,("Pushed too many contexts"));
+	mp_context = &m_contexts[m_pushed_context_count];	
+	mp_context->mp_alloc = alloc;	
+	m_pushed_context_count++;
+
+#ifdef __PLAT_NGPS__
+	SignalSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::PopContext( void )
+{
+	
+
+#ifdef __PLAT_NGPS__
+	WaitSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+
+	Dbg_MsgAssert( m_pushed_context_count,( "Heap stack underflow" ));
+	
+	m_pushed_context_count--;
+	m_contexts[m_pushed_context_count].mp_alloc = (Mem::Allocator*)-1;	
+	if (m_pushed_context_count)
+	{
+		mp_context = &m_contexts[m_pushed_context_count-1];		
+	}
+	else
+	{
+		mp_context = NULL;	 // stack has now been emptied
+	}
+
+#ifdef __PLAT_NGPS__
+	SignalSemaMaybe( s_context_semaphore );
+#endif // __PLAT_NGPS__
+}
+
+//GJ:  Context number doesn't seem to be used...
+//Originally, it was used so that you could access the heaps from script,
+//but now you can access the heaps using names, so the numbers aren't needed
+//any more
+//int Manager::GetContextNumber()
+//{   
+//	return mp_context->mp_alloc->GetNumber();		
+//}
+
+char * Manager::GetContextName()
+{
+	
+	return mp_context->mp_alloc->GetName();		
+}
+
+Allocator* Manager::GetContextAllocator()
+{
+	
+	return mp_context->mp_alloc;		
+}
+
+// Added by Ken, so that pip.cpp knows whether to try and expand a memory block up or down.
+Allocator::Direction Manager::GetContextDirection()
+{
+	
+	return mp_context->mp_alloc->GetDirection();		
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+const char* Manager::GetHeapName( uint32 whichHeap )
+{
+	// first look through the named heaps...
+	CNamedHeapInfo* pInfo = find_named_heap_info( whichHeap );
+	if ( pInfo )
+	{
+		return pInfo->mp_heap_name;
+	}
+	
+	switch ( whichHeap )
+	{
+		case 0xc7800b0:		// bottomupheap
+			return "BottomUpHeap";
+			break;
+		case 0x8fdb68af:		// topdownheap
+			return "TopDownHeap";
+			break;
+		case 0x62f3a0f3:		// frontendheap
+			return "FrontEndHeap";
+			break;
+		case 0xe3551d2e:		// networkheap
+			return "NetworkHeap";
+			break;
+		case 0x96d29d93:		// netmischeap
+			return "NetMiscHeap";
+			break;
+		case 0xfcd5166b:		// internettopdownheap
+			return "InternetTopDownHeap";
+			break;
+		case 0x90020867:		// internetbottomupheap
+			return "InternetBottomUpHeap";
+			break;
+		case 0xfa33d9b:		// scriptheap
+			return "ScriptHeap";
+			break;
+		case 0x70cb0238:		// debugheap
+			return "DebugHeap";
+			break;
+		case 0xc3909393:		// skaterheap0
+			return "Skater0";
+			break;
+		case 0xb497a305:		// skaterheap1
+			return "Skater1";
+			break;
+		case 0x2d9ef2bf:		// skaterheap2
+			return "Skater2";
+			break;
+		case 0x5a99c229:		// skaterheap3
+			return "Skater3";
+			break;
+		case 0x572a9f4c:		// skatergeomheap0
+			return "SkaterGeom0";
+			break;
+		case 0x202dafda:		// skatergeomheap1
+			return "SkaterGeom1";
+			break;
+		case 0xb924fe60:		// skatergeomheap2
+			return "SkaterGeom2";
+			break;
+		case 0xce23cef6:		// skatergeomheap3
+			return "SkaterGeom3";
+			break;
+		case 0x8682d24:		// skaterinfoheap
+			return "SkaterInfo";
+			break;
+        case 0x17ecb880:		// themeheap
+			return "ThemeHeap";
+            break;
+        case 0x7ed56b49:		// CutsceneBottomUpHeap
+			return "CutsceneBottomUp";
+            break;
+        case 0x25d71469:		// CutsceneTopDownHeap
+			return "CutsceneTopDown";
+            break;
+		default:	// unrecognized heap
+			Dbg_Assert ( 0 );
+	}
+
+	return "Unrecognized Heap";
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Heap* Manager::GetHeap( uint32 whichHeap )
+{
+	Heap* pHeap = NULL;
+
+	// first look through the named heaps...
+	CNamedHeapInfo* pInfo = find_named_heap_info( whichHeap );
+	if ( pInfo )
+	{
+		return pInfo->mp_heap;
+	}
+
+	switch ( whichHeap )
+	{
+		case 0xc7800b0:		// bottomupheap
+			pHeap = BottomUpHeap();
+			break;
+		case 0x8fdb68af:		// topdownheap
+			pHeap = TopDownHeap();
+			break;
+		case 0x62f3a0f3:		// frontendheap
+			pHeap = FrontEndHeap();
+			break;
+		case 0xe3551d2e:		// networkheap
+			pHeap = NetworkHeap();
+			break;
+		case 0x96d29d93:		// netmischeap
+			pHeap = NetMiscHeap();
+			break;
+		case 0xfcd5166b:		// internettopdownheap
+			pHeap = InternetTopDownHeap();
+			break;
+		case 0x90020867:		// internetbottomupheap
+			pHeap = InternetBottomUpHeap();
+			break;
+		case 0xfa33d9b:		// scriptheap
+			pHeap = ScriptHeap();
+			break;
+		case 0x70cb0238:		// debugheap
+			pHeap = DebugHeap();
+			break;
+		case 0xc3909393:		// skaterheap0
+			pHeap = SkaterHeap(0);
+			break;
+		case 0xb497a305:		// skaterheap1
+			pHeap = SkaterHeap(1);
+			break;
+		case 0x2d9ef2bf:		// skaterheap2
+			pHeap = SkaterHeap(2);
+			break;
+		case 0x5a99c229:		// skaterheap3
+			pHeap = SkaterHeap(3);
+			break;
+		case 0x572a9f4c:		// skatergeomheap0
+			pHeap = SkaterGeomHeap(0);
+			break;
+		case 0x202dafda:		// skatergeomheap1
+			pHeap = SkaterGeomHeap(1);
+		break;
+		case 0xb924fe60:		// skatergeomheap2
+			pHeap = SkaterGeomHeap(2);
+		break;
+		case 0xce23cef6:		// skatergeomheap3
+			pHeap = SkaterGeomHeap(3);
+		break;
+		case 0x8682d24:		// skaterinfoheap
+			pHeap = SkaterInfoHeap();
+		break;
+        case 0x17ecb880:		// themeheap
+			pHeap = ThemeHeap();
+		break;
+        case 0x7ed56b49:		// CutsceneBottomUpHeap
+			pHeap = CutsceneBottomUpHeap();
+		break;
+        case 0x25d71469:		// CutsceneTopDownHeap
+			pHeap = CutsceneTopDownHeap();
+		break;
+		default:	// unrecognized heap
+			Dbg_MsgAssert ( 0, ( "Unrecognized heap %08x", whichHeap ) );
+	}
+
+	return pHeap;
+}
+
+
+Mem::Heap *Manager::CreateHeap( Region* region, Mem::Allocator::Direction dir, char* p_name)
+{
+	Mem::Heap * pHeap = new Mem::Heap(region, dir, p_name);
+	// At this point we can maintain the heap list 
+	m_heap_list[m_num_heaps++] = pHeap;
+	return pHeap;
+}
+
+void Manager::RemoveHeap(Mem::Heap *pHeap)
+{
+
+#ifndef __PLAT_WN32__
+	#ifdef	__NOPT_ASSERT__			 
+	// Check first to see if there is something on the heap before it is deleted
+	// Deleting a heap with stuff on it might indicate an error
+	if (pHeap->mUsedBlocks.m_count)
+	{
+		printf ("Deleting a heap %s with %d used blocks still on it\n",pHeap->mp_name,pHeap->mUsedBlocks.m_count);
+		#ifndef __PLAT_NGC__
+		printf ("\n\nDumping Heap\n");
+		MemView_DumpHeap(pHeap);
+		printf ("\n\nAnalyzing Heap\n");
+		MemView_AnalyzeHeap(pHeap);
+		Dbg_MsgAssert(0, ("Deleting heap <%s> with %d used blocks still on it\n",pHeap->mp_name,pHeap->mUsedBlocks.m_count));
+		
+		#endif		// __PLAT_NGC__
+	}
+	#endif
+#endif
+
+	// remove from list of heaps
+	for (int i=0;iPushContext( sp_instance->mp_bot_heap );
+		#else
+		// but with assertions there is not enough room, so put it on the debug heap
+		if (Config::GotExtraMemory())
+		{
+			sp_instance->PushContext( sp_instance->mp_debug_heap );			
+		}
+		else
+		{
+			// running on a regular PS2, allow them to try, but will probably fail later with out of memory
+			sp_instance->PushContext( sp_instance->mp_bot_heap );
+		}
+		#endif		
+		
+		mp_net_misc_region = new Mem::AllocRegion( (NETMISC_HEAP_SIZE) );	
+		mp_net_misc_heap = CreateHeap( mp_net_misc_region, Mem::Allocator::vBOTTOM_UP, "NetMisc" );
+
+		sp_instance->PopContext();
+	}
+}
+
+void Manager::DeleteNetMiscHeap()
+{
+	if( mp_net_misc_region )
+	{
+		RemoveHeap( mp_net_misc_heap );
+		delete mp_net_misc_region;
+		mp_net_misc_region = NULL;
+	}
+}
+
+void Manager::InitInternetHeap()
+{
+	if( mp_internet_region == NULL )
+	{
+
+		//#ifndef	__NOPT_ASSERT__
+		#if	1		// always allocate internet heap normally
+		// normally the internet heap just goes on the top down heap
+		sp_instance->PushContext( sp_instance->mp_bot_heap );
+		#else
+		// but with assertions there is not enough room, so put it on the debug heap
+		if (Config::GotExtraMemory())
+		{
+			sp_instance->PushContext( sp_instance->mp_debug_heap );			
+		}
+		else
+		{
+			// running on a regular PS2, allow them to try, but will probably fail later with out of memory
+			sp_instance->PushContext( sp_instance->mp_bot_heap );
+		}
+		#endif		
+		
+		mp_internet_region = new Mem::AllocRegion( (INTERNET_HEAP_SIZE) );	
+		mp_internet_top_heap = CreateHeap( mp_internet_region, Mem::Allocator::vTOP_DOWN, "InternetTopDown" );
+		mp_internet_bottom_heap = CreateHeap( mp_internet_region, Mem::Allocator::vBOTTOM_UP, "InternetBottomUp" );
+
+		sp_instance->PopContext();
+	}
+}
+
+void Manager::DeleteInternetHeap()
+{
+	if( mp_internet_region )
+	{
+		RemoveHeap( mp_internet_top_heap );
+		RemoveHeap( mp_internet_bottom_heap );
+		delete mp_internet_region;
+		mp_internet_region = NULL;
+	}
+}
+
+void Manager::InitCutsceneHeap( int heap_size )
+{
+	// Note that it will create the cutscene heap on the current mem context...
+
+	if ( mp_cutscene_region == NULL )
+	{
+		// put it on the top-down heap, because it's used only temporarily
+//		sp_instance->PushContext( sp_instance->mp_top_heap );
+		
+		mp_cutscene_region = new Mem::AllocRegion( heap_size );
+
+		Dbg_MsgAssert( mp_cutscene_bottom_heap == NULL, ( "CutsceneBottomUpHeap already exists" ) );
+		mp_cutscene_bottom_heap = CreateHeap( mp_cutscene_region, Mem::Allocator::vBOTTOM_UP, "CutsceneBottomUp" );
+		
+		Dbg_MsgAssert( mp_cutscene_top_heap == NULL, ( "CutsceneTopDownHeap already exists" ) );
+		mp_cutscene_top_heap = CreateHeap( mp_cutscene_region, Mem::Allocator::vTOP_DOWN, "CutsceneTopDown" );
+
+//		sp_instance->PopContext();
+	}
+}
+
+void Manager::DeleteCutsceneHeap()
+{
+	if ( mp_cutscene_region )
+	{
+		RemoveHeap( mp_cutscene_bottom_heap );
+		mp_cutscene_bottom_heap = NULL;
+		
+		RemoveHeap( mp_cutscene_top_heap );
+		mp_cutscene_top_heap = NULL;
+
+		delete mp_cutscene_region;
+		mp_cutscene_region = NULL;
+	}
+}
+
+
+void Manager::InitDebugHeap()
+{
+	#ifdef	__PLAT_NGPS__
+	// The Debug heap is allocated directly from debug memory (>32MB on PS2)
+	// as such, it should only ever be used on the TOOL (T10K) debug stations, or equivalents on other platforms 
+	mp_debug_region 	= new ((void*)s_debug_region_buffer) Region( nAlignUp( _debug_heap_start ), nAlignDown( _debug_heap_start+DEBUG_HEAP_SIZE ) );
+	mp_debug_heap = CreateHeap( mp_debug_region, Mem::Allocator::vBOTTOM_UP, "debug" );
+	#endif
+}
+
+void Manager::InitSkaterHeaps(int players)
+{
+	printf ("Init Skater Heaps\n");
+	   
+	// some game modes specify 8 players, however 4 of those
+	// are observers ??? 
+	// anyway, for now, just don't allow them to create more heaps than the max
+	// it will assert later if you try to access a heap that has not been created							   
+	if (players > NUM_SKATER_HEAPS)
+	{
+		players = NUM_SKATER_HEAPS;
+	}
+	
+	// Initialize the other skater heaps
+	for (int heap = NUM_PERM_SKATER_HEAPS; heap < players; heap++)
+	{
+		Dbg_MsgAssert(!mp_skater_region[heap],( "Skater region %d is still active!!!\n",heap));
+		Dbg_MsgAssert(!mp_skater_heap[heap],( "Skater heap %d is still active!!!\n",heap));
+		mp_skater_region[heap] = new Mem::AllocRegion( (SKATER_HEAP_SIZE) );	
+		printf ("EXTRA: allocated mp_skater_region at %p\n",mp_skater_region[heap]);
+		mp_skater_heap[heap] = CreateHeap( mp_skater_region[heap], Mem::Allocator::vBOTTOM_UP, "skaterX" );
+		printf ("EXTRA: Setup mp_skater_heap at %p\n",mp_skater_heap[heap]);
+	
+		Dbg_MsgAssert(!mp_skater_geom_region[heap],( "Skater geom region %d is still active!!!\n",heap));
+		Dbg_MsgAssert(!mp_skater_geom_heap[heap],( "Skater geom heap %d is still active!!!\n",heap));
+		mp_skater_geom_region[heap] = new Mem::AllocRegion( (SKATER_GEOM_HEAP_SIZE) );	
+		printf ("EXTRA: allocated mp_skater_region at %p\n",mp_skater_geom_region[heap]);
+		mp_skater_geom_heap[heap] = CreateHeap( mp_skater_geom_region[heap], Mem::Allocator::vBOTTOM_UP, "skaterGeomX" );
+		printf ("EXTRA: Setup mp_skater_geom_heap at %p\n",mp_skater_geom_heap[heap]);
+	}
+	
+	printf ("END Init Skater Heaps\n");
+}
+
+// Delete the temporary skater heaps
+void Manager::DeleteSkaterHeaps()
+{
+	for (int i=NUM_PERM_SKATER_HEAPS;iPushContext( sp_instance->mp_bot_heap );		// make bottom-up heap default
+    
+//		sp_instance->InitOtherHeaps();							
+							
+
+	}
+	else
+	{
+		Dbg_Warning( "Already Initialized!" );
+	}
+}
+
+// K: Separated this out from sSetUp because this needs to be called from main(), after
+// the config manager has initialised, because it must only be called if extra memory is
+// available, and we only know that after Config::Init has been called.
+// sSetUp is called from pre_main, and the config manager cannot be called from there 
+// because it needs the command line args.
+void Manager::sSetUpDebugHeap( void )
+{
+	if ( sp_instance )
+	{
+		sp_instance->InitDebugHeap();
+	}
+	else
+	{
+		Dbg_MsgAssert(0,("Called sSetUpDebugHeap when mem manager not initialized!"));
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::sCloseDown( void )
+{
+	
+
+	if ( sp_instance )
+	{
+		
+#ifndef __PLAT_WN32__
+		sp_instance->DeleteOtherHeaps();							
+#endif		
+		sp_instance->PopContext();
+
+		sp_instance->~Manager();
+		sp_instance = NULL;
+	}
+	else
+	{
+		Dbg_Warning( "Not Initialized!" );
+	}
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+
+void Manager::RegisterPcsMemMan( Pcs::Manager* pReg ) 
+{
+	
+
+	Dbg_Assert( mp_process_man == NULL );	// should not initialize twice.
+
+	mp_process_man = pReg;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+CNamedHeapInfo* Manager::find_named_heap_info( uint32 name )
+{
+	for ( int i = 0; i < NUM_NAMED_HEAPS; i++ )
+	{
+		if ( m_named_heap_info[i].m_used && m_named_heap_info[i].m_name == name )
+		{
+			return &m_named_heap_info[i];
+		}
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Heap* Manager::NamedHeap( uint32 name, bool assertOnFail )
+{
+	CNamedHeapInfo* pInfo = find_named_heap_info( name );
+
+	if ( pInfo )
+	{
+		return pInfo->mp_heap;
+	}
+		
+	Dbg_MsgAssert( !assertOnFail, ( "Couldn't find named heap %08x", name ) );
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::InitNamedHeap( uint32 name, uint32 size, const char* pHeapName )
+{
+	CNamedHeapInfo* pNamedHeapInfo = NULL;
+
+	for ( int i = 0; i < NUM_NAMED_HEAPS; i++ )
+	{
+		if ( !m_named_heap_info[i].m_used )
+		{
+			pNamedHeapInfo = &m_named_heap_info[i];
+			break;
+		}
+	}
+
+	Dbg_MsgAssert( pNamedHeapInfo, ( "No more free named heaps (Increase NUM_NAMED_HEAPS from %d)!", NUM_NAMED_HEAPS ) );
+
+	// set the correct name
+	pNamedHeapInfo->m_name = name;
+	Dbg_MsgAssert( strlen(pHeapName) < CNamedHeapInfo::vMAX_HEAP_NAME_LEN, ( "Heap name %s is too long", pHeapName ) );
+	strcpy( pNamedHeapInfo->mp_heap_name, pHeapName );
+	
+	Dbg_MsgAssert(!pNamedHeapInfo->mp_region, ( "Named region is still active" ) );
+	pNamedHeapInfo->mp_region =	new Mem::AllocRegion( size );	
+	Dbg_Message( "EXTRA: Allocated pNamedHeapInfo->mp_region at %p", pNamedHeapInfo->mp_region );
+	
+	Dbg_MsgAssert(!pNamedHeapInfo->mp_heap, ("Named heap is still active"));
+	pNamedHeapInfo->mp_heap = CreateHeap( pNamedHeapInfo->mp_region, 
+										  Mem::Allocator::vBOTTOM_UP, 
+										  pNamedHeapInfo->mp_heap_name );
+	Dbg_Message( "EXTRA: Setup pNamedHeapInfo->mp_region at %p", pNamedHeapInfo->mp_heap );
+
+	pNamedHeapInfo->m_used = true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Manager::DeleteNamedHeap( uint32 name, bool assertOnFail )
+{
+	CNamedHeapInfo* pInfo = find_named_heap_info( name );
+
+	if ( pInfo )
+	{
+		Dbg_Message( "Deleting named heap %s", pInfo->mp_heap_name );
+		Dbg_Assert( pInfo->mp_region );
+		Dbg_Assert( pInfo->mp_heap );
+		RemoveHeap( pInfo->mp_heap );
+		delete pInfo->mp_region;
+		pInfo->mp_region = NULL;
+		pInfo->mp_heap = NULL;
+		pInfo->m_name = 0;
+		pInfo->m_used = false;
+		return true;
+	}
+	else
+	{
+		Dbg_MsgAssert( !assertOnFail, ( "Couldn't find named heap %08x to delete", name ) );
+	}
+	
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void*	Malloc( size_t size )
+{
+	
+	
+	void *v =  Mem::Manager::sHandle().New( size, true );
+	
+	return v;
+}
+
+int		Available()
+{
+	return Mem::Manager::sHandle().Available(); 
+}
+
+void* ReallocateDown( size_t newSize, void *pOld )
+{
+	return Mem::Manager::sHandle().ReallocateDown(newSize,pOld);
+}
+
+void* ReallocateUp( size_t newSize, void *pOld )
+{
+	return Mem::Manager::sHandle().ReallocateUp(newSize,pOld);
+}
+
+void* ReallocateShrink( size_t newSize, void *pOld )
+{
+	return Mem::Manager::sHandle().ReallocateShrink(newSize,pOld);
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Free( void* pAddr )
+{
+	
+
+	Mem::Manager::sHandle().Delete( pAddr );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Valid( void* pAddr )
+{	
+	return Mem::Manager::sHandle().Valid( pAddr );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+size_t	GetAllocSize( void* pAddr )
+{
+	return Mem::Manager::sHandle().GetAllocSize( pAddr );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void*	Realloc( void* mem, size_t newSize )
+{
+	/* should really add resize functions to do this more efficiently */
+
+	void* ptr = NULL;
+
+	if ( newSize )
+	{   
+//		Mem::Manager::sHandle().PushContext(Manager::sHandle().TopDownHeap());
+		ptr = Mem::Manager::sHandle().New( newSize, true );
+//		Mem::Manager::sHandle().PopContext();	
+	}
+
+	if ( mem )
+	{
+		if ( ptr )
+		{
+			memmove ( ptr, mem, newSize ); 
+		}
+
+		Mem::Manager::sHandle().Delete( mem );
+	}
+	
+	return ptr;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void*	Calloc( size_t numObj, size_t sizeObj )
+{
+	
+	
+	return Mem::Manager::sHandle().New(( numObj * sizeObj ), true );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+// enable multiple threads to access the memory manager at once
+void	SetThreadSafe(bool safe)
+{
+	#ifdef	__PLAT_NGPS__
+		s_use_semaphore = safe;	
+	#endif
+}
+
+bool	IsThreadSafe( void )
+{
+#ifdef	__PLAT_NGPS__
+	return s_use_semaphore;
+#else
+	return false;
+#endif
+}
+
+#ifdef __NOPT_ASSERT__
+
+#define	MAX_PROFILE_NAME 64
+#define	MAX_NUM_PROFILES  5000
+#define	MAX_LEVELS	  32
+
+class	CMemProfile
+{		  
+public:
+	char	m_type[MAX_PROFILE_NAME];
+	int		m_blocks;
+	int		m_size;
+	int		m_depth;
+	int		m_count;		// number of pushes of same nam, same depth context
+	int		m_link;			// link forward to profile that has merged with this profile
+};
+
+
+//static		CMemProfile				s_profile_list[MAX_NUM_PROFILES];		// a flat array of all profiles
+static		CMemProfile			*	s_profile_list;		// a dynamically allocated flat array of all profiles
+	
+static		CMemProfile	*			s_profile_stack[MAX_LEVELS];		// stack of pushed profiles 
+	
+static		int						s_profile_count = 0;
+	
+static		CMemProfile*			sp_current_profile = NULL;							// current entry in profile list (will need to pop back....
+static		int						s_profile_stack_depth = 0;						// depth in stack (index into m_profile_stack[])
+static		int						s_next_profile = 0;								// index into array
+
+//static 		int						s_launched = false;		// true after we've laucnhed the level once
+
+static		bool					s_active = true;
+
+void	PushMemProfile(char *p_type)
+{
+	if( s_active )
+	{
+	
+		if (!s_profile_list)
+		{
+			if (Config::GotExtraMemory())
+			{
+				Mem::Manager::sHandle().PushContext(Manager::sHandle().DebugHeap());
+				s_profile_list = (CMemProfile *)Mem::Malloc(MAX_NUM_PROFILES *sizeof(CMemProfile));
+				Mem::Manager::sHandle().PopContext();
+				
+			}
+			else
+			{
+				s_active = false;			// no extra memory, or maybe it's an X-Box....
+				return;
+			}
+		}
+		
+		if (s_next_profile >= MAX_NUM_PROFILES)
+		{
+			s_active = false;
+			return;
+		}
+
+/*		
+		if (!strcmp("LaunchLevel",p_type))
+		{
+			if (s_launched)
+			{
+				s_active = false;
+				return;
+			}
+			s_launched = true;
+		}
+*/
+							  
+		// if we have a current profile, then push it
+		if (sp_current_profile)
+		{
+			Dbg_MsgAssert(s_profile_stack_depth < MAX_LEVELS,("mem profile stack overflow, unmatched push?"));
+			s_profile_stack[s_profile_stack_depth++] = sp_current_profile;
+		}
+		
+		// get a new profile from the list
+		Dbg_MsgAssert(s_next_profile < MAX_NUM_PROFILES,("mem profile heap overflow, too many pushes"));
+		sp_current_profile = &s_profile_list[s_next_profile++];
+
+
+
+
+		// just copy over the memory containing the name												  
+		char *p = &(sp_current_profile->m_type[0]);
+		char *q = (char*) p_type;
+		for (int i=0;im_blocks = 0;
+		sp_current_profile->m_size = 0;
+		sp_current_profile->m_depth = s_profile_stack_depth;
+		sp_current_profile->m_count = 1;
+		sp_current_profile->m_link = 0;
+
+		// Then, search back through the list to see if there 
+		// are any entries att he same level that have this same string
+		// if so, then zero the old instance, and add the size and count to this one
+		// there should only be one, as any other one would already be zeroed
+		int	check = s_next_profile-2;
+		while (check>0
+				&& s_profile_list[check].m_depth == s_profile_stack_depth)
+		{
+			if (s_profile_list[check].m_depth == s_profile_stack_depth)
+			{
+				// same depth
+				if (strcmp(s_profile_list[check].m_type,p_type) == 0)
+				{
+					// same string, so bring this one forward to add to this
+					sp_current_profile->m_blocks += s_profile_list[check].m_blocks;
+					sp_current_profile->m_size += s_profile_list[check].m_size;
+					sp_current_profile->m_count += s_profile_list[check].m_count;
+					// and zero out the old one					
+					s_profile_list[check].m_blocks = 0;
+					s_profile_list[check].m_size = 0;
+					// Patch in the index of the new profile,
+					// so late deletions can be accounted for
+					s_profile_list[check].m_link = s_profile_stack_depth - 1;
+					break;
+				}
+			}
+			check--;
+		}
+
+
+	}
+}
+
+
+/******************************************************************/
+
+void	PopMemProfile()
+{
+	if( s_active)
+	{
+		Dbg_MsgAssert(sp_current_profile,("Popped one more memory profile than we pushed"));
+
+		// set time on the current profile		
+//		sp_current_profile->m_end_time = Tmr::GetTimeInUSeconds();
+		
+		if ( ! s_profile_stack_depth)
+		{
+			// nothing left on stack, so set current profile to NULL
+			sp_current_profile = NULL;
+			// update the count of valid profiles
+			s_profile_count = s_next_profile;
+		}
+		else
+		{
+			// get the last profile pushed on the stack
+			sp_current_profile = s_profile_stack[--s_profile_stack_depth]; 
+		}
+	}
+}
+
+
+// given an index in the the mem profile list
+// then get the total size of all allocations
+// at this level, or below
+// up the next entry at the same depth, or the end of the list
+int		total_size(int n)
+{
+	
+	int size = s_profile_list[n].m_size;
+	int depth = s_profile_list[n].m_depth;
+	n++;
+	while (n < s_next_profile && s_profile_list[n].m_depth > depth)
+	{
+		size += s_profile_list[n].m_size;
+		n++;
+	}
+	return size;
+					
+}
+
+#ifndef __PLAT_WN32__
+// dump the memory profile in a tree format, like
+//
+//  level stuff     100000
+//     peds           5000
+//     cars          23000
+//    other          72000
+//  skater stuff    ...... 
+void	DumpMemProfile(int level, char *p_type)
+{
+	char buf[512];
+	if( s_active )
+	{
+	
+
+
+		printf ("\nDumping Memory Profile\n");
+		printf ("There are %d mem profile contexts\n",s_next_profile);
+		for (int i=0;i%10s____",Str::PrintThousands(total_size(i)));
+					dump_printf(buf);
+					if (s_profile_list[i].m_count == 1)
+					{
+						sprintf (buf,"%s\n",s_profile_list[i].m_type);
+					}
+					else
+					{
+						sprintf (buf,"%s (%d) (avg: %s)\n",s_profile_list[i].m_type,s_profile_list[i].m_count,Str::PrintThousands(total_size(i)/s_profile_list[i].m_count));
+					}
+					dump_printf(buf);
+					if (s_profile_list[i].m_depth < level && s_profile_list[i].m_size && total_size(i) != s_profile_list[i].m_size)
+					{
+						sprintf (buf,"%2d: ",s_profile_list[i].m_depth+1);
+						dump_printf(buf);
+						for (int tab2 = 0;tab2 < s_profile_list[i].m_depth+1;tab2++)
+						{
+							sprintf(buf,"   ");
+							dump_printf(buf);
+						}
+						sprintf (buf,">%10s____",Str::PrintThousands(s_profile_list[i].m_size));
+						dump_printf(buf);
+						sprintf (buf,"Untracked %s\n",s_profile_list[i].m_type);			
+						dump_printf(buf);
+						
+					}
+				}
+			}
+		}
+	}
+	else
+	{
+		printf ("Mem Profiler not active, probably overflowed, try restarting...\n");
+	}
+}
+#endif// __PLAT_WN32__
+void	AllocMemProfile(Allocator::BlockHeader* p_block)
+{
+	if( s_active )
+	{
+		if (sp_current_profile)
+		{
+			sp_current_profile->m_blocks++;
+			
+			// If it's on the debug heap, then set size to zero to avoid confusion
+			if (Mem::Manager::sHandle().GetContextAllocator() != Mem::Manager::sHandle().DebugHeap())
+			{
+				sp_current_profile->m_size += p_block->mSize;
+			}
+			
+			p_block->mp_profile = sp_current_profile;
+		}
+	}
+}
+
+void	FreeMemProfile(Allocator::BlockHeader* p_block)
+{
+	if( s_active )
+	{
+		
+		CMemProfile	*	p_profile  = p_block->mp_profile;						  
+		if (p_profile)
+		{
+//			Dbg_MsgAssert(p_profile->m_blocks,("mutli-block freed out of context"));
+			// Skip over any that have been combined, until we find the final combined block
+			while (p_profile->m_link != 0)
+			{
+				p_profile = &s_profile_list[p_profile->m_link];
+			}
+			p_profile->m_blocks--;
+			p_profile->m_size -= p_block->mSize;
+			p_profile = NULL;
+		}
+	}
+}
+
+#else
+
+void	PushMemProfile(char *p_type)
+{
+}
+
+void	PopMemProfile()
+{
+}
+
+#ifndef __PLAT_WN32__
+void	DumpMemProfile(int level, char *p_type)
+{
+}
+#endif// __PLAT_WN32__
+
+void	AllocMemProfile(Allocator::BlockHeader* p_block)
+{
+}
+
+void	FreeMemProfile(Allocator::BlockHeader* p_block)
+{
+}
+
+#endif __NOPT_ASSERT__
+
+
+} // namespace Mem
+
diff --git a/Code/Sys/Mem/memman.h b/Code/Sys/Mem/memman.h
new file mode 100644
index 0000000..b70d376
--- /dev/null
+++ b/Code/Sys/Mem/memman.h
@@ -0,0 +1,361 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/memman.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_MEMMAN_H
+#define	__SYS_MEM_MEMMAN_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+#include 
+#include "heap.h"
+#include "alloc.h"
+//#ifndef __PLAT_XBOX__
+
+#ifndef	__SYS_MEM_MEMPTR_H
+#	include "memptr.h"
+#endif
+//#endif
+#include "handle.h"
+
+#if 0
+#ifdef __PLAT_WN32__
+#include "mem/wn32/p_memman.h"
+#else
+#ifdef __PLAT_NGPS__
+#include "mem/ngps/p_memman.h"
+#else
+#ifdef __PLAT_NGC__
+#include "mem/ngc/p_memman.h"
+#else
+#error Unsupported Platform
+#endif
+#endif
+#endif
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+// redefine the standard memory library functions, so they give us errors
+
+
+#define	NUM_PERM_SKATER_HEAPS	1
+#define	NUM_SKATER_HEAPS		8
+#define NUM_NAMED_HEAPS			4
+
+#define		DEBUG_HEAP_SIZE				32767*1024		// 1K short of 32MB
+
+		
+namespace Pcs
+{
+	class Manager;
+}
+
+namespace Mem
+{
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class CNamedHeapInfo
+{
+public:
+	enum
+	{
+		vMAX_HEAP_NAME_LEN = 128
+	};
+
+	CNamedHeapInfo()
+	{
+		mp_region = NULL;
+		mp_heap = NULL;
+		m_name = 0;
+		m_used = false;
+	}
+
+public:
+	Region*		mp_region;
+	Heap*		mp_heap;
+	uint32		m_name;
+	char		mp_heap_name[vMAX_HEAP_NAME_LEN];
+	bool		m_used;
+};
+
+class Manager : public Spt::Class			
+{
+	
+public:
+
+	enum
+	{
+		vMAX_CONTEXT = 16,
+		vMAX_HEAPS = 32
+	};
+
+	void						PushContext( Allocator* alloc );
+	void						PopContext( void );
+
+	void*						New( size_t size, bool assert_on_fail = true, Allocator* pAlloc = NULL );
+	int							Available();
+	void*						ReallocateDown( size_t newSize, void *pOld, Allocator* pAlloc = NULL );
+	void*						ReallocateUp( size_t newSize, void *pOld, Allocator* pAlloc = NULL );
+	void*						ReallocateShrink( size_t newSize, void *pOld, Allocator* pAlloc = NULL );
+	void						Delete( void* pAddr );
+	bool						Valid( void* pAddr );
+	size_t						GetAllocSize( void* pAddr );
+								
+	Heap*						TopDownHeap( void ) 		{ return mp_top_heap; }
+	Heap*						BottomUpHeap( void )  		{ return mp_bot_heap; }
+	Heap*						FrontEndHeap( void ) 		{ return mp_frontend_heap; }
+	Heap*						ScriptHeap( void ) 			{ return mp_script_heap; }
+	Heap*						NetworkHeap( void ) 		{ return mp_network_heap; }
+	Heap*						NetMiscHeap( void ) 		{ return mp_net_misc_heap; }
+	Heap*						ProfilerHeap( void ) 		{ return mp_profiler_heap; }
+	Heap*						DebugHeap( void ) 			{ return mp_debug_heap; }
+	Heap*						SkaterHeap( int n ) 		{ return mp_skater_heap[n]; }
+	Heap*						SkaterInfoHeap( ) 			{ return mp_skater_info_heap; }
+	Heap*						SkaterGeomHeap( int n ) 	{ return mp_skater_geom_heap[n]; }
+	Heap*						InternetTopDownHeap( void )	{ return mp_internet_top_heap; }
+	Heap*						InternetBottomUpHeap( void ){ return mp_internet_bottom_heap; }
+    Heap*						ThemeHeap( void )           { return mp_theme_heap; }
+	Heap*						CutsceneTopDownHeap( void )	{ return mp_cutscene_top_heap; }
+	Heap*						CutsceneBottomUpHeap( void ){ return mp_cutscene_bottom_heap; }
+#ifdef __PLAT_NGC__
+	Heap*						AudioHeap( void )			{ return mp_audio_heap; }
+#endif	
+	Heap*						NamedHeap( uint32 name, bool assertOnFail = true );
+
+	void						RegisterPcsMemMan( Pcs::Manager* );
+
+	/* Global versions for all memory allocations */
+	void						PushMemoryMarker( uint32 uiID );
+	void						PopMemoryMarker( uint32 uiID );
+
+	static void					sSetUp( void );
+	static void					sSetUpDebugHeap( void );
+	
+	static void					sCloseDown( void );
+	static Manager&				sHandle( void );
+
+	
+	void 						InitOtherHeaps();
+	void 						DeleteOtherHeaps();
+
+	void						InitInternetHeap();
+	void						DeleteInternetHeap();
+
+	void						InitNetMiscHeap();
+	void						DeleteNetMiscHeap();
+
+	void						InitCutsceneHeap(int heap_size);
+	void						DeleteCutsceneHeap();
+
+	void						InitDebugHeap();
+
+	void 						InitSkaterHeaps(int players);
+	void 						DeleteSkaterHeaps();
+
+	void						InitNamedHeap(uint32 name, uint32 size, const char* pHeapName);
+	bool						DeleteNamedHeap(uint32 name, bool assertOnFail = true);
+	
+	const char*					GetHeapName( uint32 whichHeap );
+	Heap *						GetHeap( uint32 whichHeap );
+
+	Heap *						CreateHeap( Region* region, Mem::Allocator::Direction dir /*= Mem::Allocator::vBOTTOM_UP*/, char* p_name );//= "unknown heap" );
+	void						RemoveHeap( Heap * pHeap); 
+	Heap *						FirstHeap();
+	Heap *						NextHeap(Heap * pHeap);
+	
+
+//	int 						GetContextNumber();
+	char *						GetContextName();
+	Allocator::Direction		GetContextDirection();
+	Allocator*					GetContextAllocator();
+
+
+								~Manager( void );
+private :
+								Manager( void );								   
+																 
+	Mem::Heap *					m_heap_list[vMAX_HEAPS]; 
+	int							m_num_heaps;   							
+
+	static char 				s_manager_buffer[];
+	static char 				s_region_buffer[];
+	static char 				s_debug_region_buffer[];
+	static char 				s_script_region_buffer[];
+	static char 				s_profiler_region_buffer[];
+	static char 				s_debug_heap_buffer[];
+	static char 				s_top_heap_buffer[];
+	static char 				s_bot_heap_buffer[];
+	static bool					s_initialized;
+	static Manager*				sp_instance;
+		
+	class MemManContext
+	{
+	
+		friend class Manager;
+	
+	private:
+		Allocator*						mp_alloc;
+	};
+
+
+	Ptr< MemManContext > 		mp_context;
+
+	// Mick: Contexts are now statically allocated off this 
+	// array, rather than off the heap, as that was causing fragmentation
+	// in rare but crash-worthy circumstances			
+	MemManContext				m_contexts[vMAX_CONTEXT];
+	int							m_pushed_context_count;
+
+
+	Region*						mp_region;
+	Heap*						mp_top_heap;
+	Heap*						mp_bot_heap;
+	
+	Region*						mp_frontend_region;
+	Heap*						mp_frontend_heap;
+
+	Region*						mp_script_region;
+	Heap*						mp_script_heap;
+
+	Region*						mp_network_region;
+	Heap*						mp_network_heap;
+
+	Region*						mp_net_misc_region;
+	Heap*						mp_net_misc_heap;
+
+	Region*						mp_internet_region;
+	Heap*						mp_internet_top_heap;
+	Heap*						mp_internet_bottom_heap;
+
+	Region*						mp_cutscene_region;
+	Heap*						mp_cutscene_top_heap;
+	Heap*						mp_cutscene_bottom_heap;
+
+#ifdef __PLAT_NGC__
+	Region*						mp_audio_region;
+	Heap*						mp_audio_heap;
+#endif		// __PLAT_NGC__
+
+	Region*						mp_debug_region;
+	Heap*						mp_debug_heap;
+
+	Region*						mp_profiler_region;
+	Heap*						mp_profiler_heap;
+
+	Region*						mp_skater_region[NUM_SKATER_HEAPS];
+	Heap*						mp_skater_heap[NUM_SKATER_HEAPS];
+	
+	Region*						mp_skater_geom_region[NUM_SKATER_HEAPS];
+	Heap*						mp_skater_geom_heap[NUM_SKATER_HEAPS];
+
+	CNamedHeapInfo				m_named_heap_info[NUM_NAMED_HEAPS];
+
+	Region*						mp_skater_info_region;
+	Heap*						mp_skater_info_heap;
+
+    Region*						mp_theme_region;
+	Heap*						mp_theme_heap;
+
+	Pcs::Manager*				mp_process_man;
+	uint						m_current_id;
+
+protected:
+	CNamedHeapInfo*				find_named_heap_info( uint32 name );
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+extern "C"
+{
+
+int		Available();
+void*	Malloc( size_t size );
+void	Free( void* mem );
+bool	Valid( void* pAddr );
+size_t	GetAllocSize( void* pAddr );
+void*	ReallocateDown( size_t newSize, void *pOld );
+void*	ReallocateUp( size_t newSize, void *pOld );
+void*	ReallocateShrink( size_t newSize, void *pOld );
+void*	Realloc( void* mem, size_t newSize );
+void*	Calloc( size_t numObj, size_t sizeObj );
+}
+
+void	PushMemProfile(char *p_type);
+void	PopMemProfile();
+void	DumpMemProfile(int level, char *p_type=NULL);
+void	AllocMemProfile(Allocator::BlockHeader* p_block);
+void	FreeMemProfile(Allocator::BlockHeader* p_block);
+
+void	SetThreadSafe(bool safe);
+bool	IsThreadSafe( void );
+
+
+
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline Manager&	Manager::sHandle( void )
+{
+	
+
+	Dbg_AssertType( sp_instance, Manager );
+
+	return *sp_instance;
+}
+
+
+} // namespace Mem
+
+
+// Some debug functions
+bool dump_open(const char *name);
+void dump_printf(char *p);
+void dump_close();
+extern int dumping_printfs;
+
+
+
+#endif  // __SYS_MEM_MEMMAN_H
diff --git a/Code/Sys/Mem/memptr.h b/Code/Sys/Mem/memptr.h
new file mode 100644
index 0000000..82710d1
--- /dev/null
+++ b/Code/Sys/Mem/memptr.h
@@ -0,0 +1,591 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/memptr.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_MEMPTR_H
+#define	__SYS_MEM_MEMPTR_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#include 
+
+#include 
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+nTemplateBaseClass( _T, PtrToConst )
+{
+	
+public :
+
+								PtrToConst( const _T* ptr = NULL );
+								~PtrToConst( void );
+
+#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ ))
+
+								PtrToConst( const PtrToConst< _T >& rhs );
+		PtrToConst< _T >&		operator= ( const PtrToConst< _T >& rhs );
+
+#else
+
+	template < class _NewT >						                    		// template copy contructor
+								PtrToConst( const PtrToConst< _NewT >& rhs ); 	// needed to support inheritance correctly
+
+	template < class _NewT > 	
+		PtrToConst< _T >&		operator = ( const PtrToConst< _NewT >& rhs );	// template assignment operator
+
+#endif		
+		PtrToConst< _T >&		operator = ( const _T* ptr );
+
+		PtrToConst< _T >&		operator++ ( void ); 							// ++ptr   
+		const PtrToConst< _T >	operator++ ( int ); 							// ptr++   
+		PtrToConst< _T >&		operator-- ( void );							// --ptr
+		const PtrToConst< _T >	operator-- ( int );								// ptr--
+		PtrToConst< _T >&		operator+= ( int val );
+		PtrToConst< _T >&		operator-= ( int val );
+
+		bool					operator ! () const;                    		// operator! - use to test for null
+		
+		const _T&				operator * () const;
+		const _T*				operator -> () const;																	
+		const _T*				Addr( void ) const;								// Retrieve 'dumb' pointer
+		
+protected :
+
+		union
+		{
+			const	_T*		m_const_ptr;
+					_T*		m_ptr;
+		};
+	
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+nTemplateSubClass( _T, Ptr, PtrToConst< _T > )
+{
+
+	
+public :
+								Ptr( const _T* ptr = NULL );
+								~Ptr( void );
+
+#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ ))
+
+								Ptr( const Ptr< _T >& rhs );
+		Ptr< _T >&				operator= ( const Ptr< _T >& rhs );
+
+#else
+	template < class _NewT >										 
+								Ptr( const Ptr< _NewT >& rhs );	
+
+	template < class _NewT > 	
+		Ptr< _T >&				operator = ( const Ptr< _NewT >& rhs );			// template assignment operator
+#endif		
+		Ptr< _T >&				operator = ( const _T* ptr );
+		_T&						operator * ( void ) const; 
+		_T*						operator -> ( void ) const;
+		_T*						Addr( void ) const;
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/* PtrToConst< _T >                                               */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline   
+PtrToConst< _T >::PtrToConst< _T >( const _T* ptr ) 
+: m_const_ptr ( ptr )
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline	
+PtrToConst< _T >::~PtrToConst( void )
+{
+	
+
+}
+
+#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline	
+PtrToConst< _T >::PtrToConst( const PtrToConst< _T >& rhs )
+: m_const_ptr ( rhs.Addr() )
+{
+	
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline	
+PtrToConst< _T >&		PtrToConst< _T >::operator= ( const PtrToConst< _T >& rhs )
+{
+	
+	
+	m_const_ptr = rhs.Addr();
+	return *this;	
+}
+
+#else
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+template < class _T > template < class _NewT > inline
+PtrToConst< _T >::PtrToConst< _T >( const PtrToConst< _NewT >& rhs )
+: m_const_ptr ( rhs.Addr() )
+{
+	
+
+	Dbg_MsgAssert( false, ( "Microsoft VC++ sucks - don't do this (yet)" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > template < class _NewT > inline
+PtrToConst< _T >&		PtrToConst< _T >::operator = ( const PtrToConst< _NewT >& rhs ) 
+{
+	
+
+	Dbg_MsgAssert( false, ( "Microsoft VC++ sucks - don't do this (yet)" ));
+
+	m_const_ptr = rhs.Addr();
+	return *this;	
+}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+PtrToConst< _T >&		PtrToConst< _T >::operator = ( const _T* ptr ) 
+{
+	
+
+	m_const_ptr = ptr;
+	return *this;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+const _T&		PtrToConst< _T >::operator * ( void ) const 
+{
+	
+	
+	Dbg_AssertType( m_const_ptr, _T );
+	
+	return *m_const_ptr;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+PtrToConst< _T >&	PtrToConst< _T >::operator+= ( int val )
+{
+	
+
+	m_const_ptr += val;
+	Dbg_AssertType( m_const_ptr, _T );
+
+	return *this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+PtrToConst< _T >&	PtrToConst< _T >::operator-= ( int val )
+{
+	
+
+	m_const_ptr -= val;
+
+	Dbg_AssertType( m_const_ptr, _T );
+	
+	return *this;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+PtrToConst< _T >&	PtrToConst< _T >::operator++ ( void )
+{
+	
+
+	*this += 1;
+
+	return *this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+const PtrToConst< _T >	PtrToConst< _T >::operator++ ( int )
+{
+	
+	
+	PtrToConst< _T > old = *this;
+
+	++(*this);
+
+	return old;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+PtrToConst< _T >&	PtrToConst< _T >::operator-- ( void )
+{
+	
+
+	Dbg_AssertType( m_const_ptr, _T );
+
+	*this -= 1;
+
+	return *this;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+const PtrToConst< _T >	PtrToConst< _T >::operator-- ( int )
+{
+	
+
+	Dbg_AssertType( m_const_ptr, _T );
+	
+	PtrToConst< _T > old = *this;
+
+	--(*this);
+
+	return old;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+const _T*		PtrToConst< _T >::operator -> ( void ) const 
+{
+	
+
+	Dbg_AssertType( m_const_ptr, _T );
+	
+	return m_const_ptr;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+bool		PtrToConst< _T >::operator ! ( void ) const 
+{
+	
+
+	return ( m_const_ptr == NULL );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+const _T*		PtrToConst< _T >::Addr ( void ) const 
+{
+	
+
+	return m_const_ptr;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+const PtrToConst< _T > operator+ ( const PtrToConst< _T >& lhs, int rhs )
+{
+	
+	
+	PtrToConst< _T >	ret = lhs;
+	ret += rhs;
+	
+	return ret;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+const PtrToConst< _T > operator- ( const PtrToConst< _T >& lhs, int rhs )
+{
+	
+	
+	PtrToConst< _T >	ret = lhs;
+	ret -= rhs;
+	
+	return ret;
+}
+
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Ptr< _T >                                                      */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline   
+Ptr< _T >::Ptr( const _T* ptr )
+: PtrToConst< _T >( ptr )
+{
+	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline	
+Ptr< _T >::~Ptr( void )
+{
+	
+}
+
+#if ( defined ( __PLAT_XBOX__ ) || defined ( __PLAT_WN32__ ))
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline	
+Ptr< _T >::Ptr( const Ptr< _T >& rhs )
+: PtrToConst< _T >( rhs )
+{
+	
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline	
+Ptr< _T >&		Ptr< _T >::operator= ( const Ptr< _T >& rhs )
+{
+	
+	
+	m_const_ptr = rhs.Addr();
+	return *this;	
+}
+
+#else
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+template < class _T > template < class _NewT > inline
+Ptr< _T >::Ptr( const Ptr< _NewT >& rhs )	
+: PtrToConst< _T >( rhs )
+{
+	
+
+	Dbg_MsgAssert( false, ( "Microsoft VC++ sucks - don't do this (yet)" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > template < class _NewT > inline
+Ptr< _T >&		Ptr< _T >::operator= ( const Ptr< _NewT >& rhs ) 
+{
+	
+
+	Dbg_MsgAssert( false, ( "Microsoft VC++ sucks - don't do this (yet)" ));
+
+	m_const_ptr = rhs.Addr();
+	return *this;	
+}
+
+#endif
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+Ptr< _T >&		Ptr< _T >::operator= ( const _T* ptr ) 
+{
+	
+
+	m_const_ptr = ptr;
+	return *this;	
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+_T&		Ptr< _T >::operator * ( void ) const 
+{
+	
+
+	Dbg_AssertType( m_ptr, _T );
+
+	return *m_ptr;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+_T*		Ptr< _T >::operator -> ( void ) const 
+{
+	
+	
+	Dbg_AssertType( m_ptr, _T );
+	
+	return m_ptr;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+template < class _T > inline
+_T*		Ptr< _T >::Addr ( void ) const 
+{
+	
+
+	return m_ptr;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_MEMPTR_H								
diff --git a/Code/Sys/Mem/memtest.cpp b/Code/Sys/Mem/memtest.cpp
new file mode 100644
index 0000000..32a37e0
--- /dev/null
+++ b/Code/Sys/Mem/memtest.cpp
@@ -0,0 +1,263 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			Memory (Mem) 											**
+**																			**
+**	File name:		memtest.cpp												**
+**																			**
+**	Created by:		03/20/00	-	mjb										**
+**																			**
+**	Description:	Memory test code										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include "heap.h"
+#include "pool.h"
+#include "pile.h"
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+class  TestA  : public Spt::Class
+{
+	
+
+public :
+			TestA ( uint val = 999 ) : value (val ) { }
+	uint	GetValue( void ) const  { return value; }
+
+protected :
+
+	uint	value;
+
+};
+
+class  TestB : public  TestA 
+{
+	
+
+public :
+			TestB ( uint val = 666 ) { value = val; }
+
+};
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void Test( void )
+{
+	
+
+	Dbg_Message( "Testing Memory sub-system" );
+
+	Dbg_Message ( "Allocate 1M region from Mem::Manager's default BottomUp Heap" );
+	
+	Mem::AllocRegion*	region = new Mem::AllocRegion( 1024 * 1024 );
+	
+	Dbg_Message ( "Create Bottom-Up Heap and Top-Down Pile within region" );
+
+	Mem::Heap*			heap = new Mem::Heap( region, Mem::Allocator::vBOTTOM_UP );
+	Mem::Pile*			pile = new Mem::Pile( region, Mem::Allocator::vTOP_DOWN );
+
+	Dbg_Message ( "Set current allocator to Mem::Manager's default TopDown Heap" );
+
+	Manager::sHandle().PushContext( Manager::sHandle().TopDownHeap() );
+
+	Dbg_Message ( "Allocate another 1M region" );
+
+	Mem::AllocRegion*	region2 = new Mem::AllocRegion( 1024 * 1024 );
+	Dbg_Message ( "Create Bottom-Up Pool (16x1K slots) and Top-Down Heap within region" );
+
+	Mem::Pool*			pool = new Mem::Pool( region2, 1024, 16 );
+	Mem::Heap*			heap2 = new Mem::Heap( region2, Mem::Allocator::vTOP_DOWN );
+	
+	Manager::sHandle().PushContext( pile );
+
+//	char*	test1( new char[100] );
+//	Mem::Ptr	test1( new char[100] );
+	
+	Mem::Ptr< TestB >	testB = new TestB[10];
+						
+
+	Dbg_Message ( "Test B = %p, value = %d", testB.Addr(), testB->GetValue() );
+#if 0
+	Mem::Manager::sInstance().Delete(testB.Addr());
+	uint	a = testB->GetValue();
+#endif
+
+
+	
+//	Mem::Ptr< TestA >	ctestA = testB;
+//	Mem::PtrToConst< TestA >	ctestA = testB;
+	
+
+
+
+
+//	Mem::Ptr< TestA >	testA( testB );
+
+//	Mem::Ptr< TestA >	testA;
+//	testA = testB;
+
+#if 0 // poor little XBOX - can't handleit!
+	Mem::Ptr< TestA >	testA = testB;
+	testA+=9;	 // 10 doesn't assert properly - why???
+#endif
+
+//	Mem::PtrToConst< TestA >	ctestA( testB );
+
+
+//	Mem::PtrToConst< TestA >	ctestA;
+//	ctestA = testB;
+	
+//	Mem::PtrToConst< TestA >	ctestA = testB;	 // gives warning
+	
+//	Mem::PtrToConst< TestB >	ctestB = testB;
+//	Mem::PtrToConst< TestA >	ctestA = ctestB;	
+	
+	
+//	ctestB = testB;
+//	ctestB->GetValue();
+//	TestB	ttestB = *ctestB++;
+	
+#if 0
+	Mem::PtrToConst< TestB >	stestB1 = (ctestB + 1);
+
+	Mem::PtrToConst< TestB >	stestB10 = (ctestB + 10);
+#endif
+
+
+//	Mem::Ptr< int >		intPtr = new int;
+//	int		b = *intPtr;
+
+	char*	test2[17];
+
+	for( uint i = 0; i < 16; i++ )
+	{
+		test2[i] = new (pool) char[1024];
+		Dbg_Message ( "%x  should be valid address", test2[i] );
+	}
+
+	test2[16] = new (pool, false) char[1024];
+	Dbg_Message ( " %x  should be NULL address ( no free slots )", test2[16] ); 
+
+	delete test2[0];
+	delete test2[1];
+
+	char* test3 = new char;
+	
+	Manager::sHandle().PushContext( heap );
+
+	char* test4 = new char;
+
+	Manager::sHandle().PushContext( heap2 );
+	
+	Manager::sHandle().PopContext();
+	Manager::sHandle().PopContext();
+	Manager::sHandle().PopContext();
+
+	delete test3;
+	delete test4;
+//	delete test4;
+
+	delete heap;
+	delete pile;
+	delete pool;
+	delete heap2;
+
+
+/*
+	char*  tmp1 = new char;
+	int*  tmp2 = new int;
+	short*  tmp3 = new short;
+	delete tmp1;
+	delete tmp3;
+	delete tmp2;
+*/
+
+	TestA*	testa = new TestA;
+	Mem::Handle< TestA > test_handle = testa;
+
+	TestA*	gooda = test_handle.GetPointer();
+	Dbg_AssertType( testa, TestA );
+	
+	Dbg_MsgAssert(( gooda == testa ),( "Handles pointer not valid" ));
+
+	if( gooda != testa )
+	{
+		printf("Error!!!!!   Handles pointer not valid\n" );
+	}
+
+	Dbg_AssertType( gooda, TestA );
+
+	delete testa;
+//	testa = new TestA;
+
+	gooda = test_handle.GetPointer();	
+	Dbg_MsgAssert(( gooda == NULL ),( "Handles pointer not NULL" ));
+
+	if( gooda )
+	{
+		printf("Error!!!!!   Handles pointer not NULL\n" );
+	}
+
+	Dbg_MsgAssert(( false ),("DONE" ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
diff --git a/Code/Sys/Mem/memtest.h b/Code/Sys/Mem/memtest.h
new file mode 100644
index 0000000..c85a918
--- /dev/null
+++ b/Code/Sys/Mem/memtest.h
@@ -0,0 +1,67 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/memtest.h									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_MEMTEST_H
+#define	__SYS_MEM_MEMTEST_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+void Test( void );
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_MEMTEST_H
diff --git a/Code/Sys/Mem/pile.cpp b/Code/Sys/Mem/pile.cpp
new file mode 100644
index 0000000..7931ec1
--- /dev/null
+++ b/Code/Sys/Mem/pile.cpp
@@ -0,0 +1,203 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			Memory (Mem) 											**
+**																			**
+**	File name:		pile.cpp												**
+**																			**
+**	Created by:		03/20/00	-	mjb										**
+**																			**
+**	Description:	Pile class												**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include "pile.h"
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+void*	Pile::allocate( size_t size, bool assert_on_fail )
+{
+	
+		
+#ifdef __PLAT_NGC__
+	size = (uint)nAlignUpBy( size, 5 );
+#else
+	size = (uint)nAlignUpBy( size, 4 );
+#endif
+	
+	BlockHeader* p_header = (BlockHeader*)mp_region->Allocate( this, size + BlockHeader::sSize, assert_on_fail );
+
+	if ( p_header )
+	{
+		new ((void*)p_header) BlockHeader( this, size ); 
+
+		Dbg_Code 
+		(
+			m_alloc_count++;
+		)
+	}
+	else if ( assert_on_fail )
+	{
+		Dbg_MsgAssert ( false,( "Failed to allocate %d bytes", size ));
+	}
+
+	MemDbg_AllocateBlock ( p_header );	
+	
+	return (void*)((uint)p_header + BlockHeader::sSize);
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Pile::free( BlockHeader* pHeader )
+{
+	
+	
+	MemDbg_FreeBlock ( pHeader );	
+
+	pHeader->~BlockHeader();
+
+	Dbg_Code 
+	(
+		Dbg_MsgAssert(( m_alloc_count > 0 ),( "Internal Error - freed too many blocks!!" ));
+		m_alloc_count--;
+	)
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Pile::Pile( Region* region, Direction dir )
+: Allocator( region, dir, "Pile" )
+{
+	
+	
+	mp_base = mp_top;
+
+	Dbg_Code
+	(
+		m_depth = 0;
+	)
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Pile::~Pile( void )
+{
+	
+
+	Dbg_Code
+	(
+		Dbg_MsgAssert(( m_depth == 0 ),( "Context Stack not empty (%d items)", m_depth ));
+	
+		// warn if there are still allocations.
+		Dbg_MsgAssert(( m_alloc_count == 0 ),( "Pile still contains allocations" ));
+	)
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void 	Pile::PushContext( void )
+{
+	
+
+	void** p_top = (void**)mp_top;
+	*p_top = mp_base;
+	mp_base = p_top;
+	mp_top = p_top + m_dir;
+	
+	Dbg_Code
+	(
+		m_depth++;
+	)
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Pile::PopContext( void )
+{
+	
+		
+	Dbg_Code
+	(
+		Dbg_MsgAssert( m_depth > 0,( "Context stack underflow" ));
+		m_depth--;
+	)
+
+	mp_top = mp_base;
+	mp_base = *(void**)mp_top;
+	mp_top = (void*)( (void**)(mp_top) + m_dir );
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
diff --git a/Code/Sys/Mem/pile.h b/Code/Sys/Mem/pile.h
new file mode 100644
index 0000000..ef59a9c
--- /dev/null
+++ b/Code/Sys/Mem/pile.h
@@ -0,0 +1,102 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/pile.h										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_PILE_H
+#define	__SYS_MEM_PILE_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include "alloc.h"
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class Region;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+class  Pile : public  Allocator 			
+{
+	
+	
+	friend class Manager;
+
+public :
+		
+							Pile( Region* region, Direction dir = vBOTTOM_UP );
+	virtual					~Pile( void );
+	
+	virtual		void		PushContext( void );
+	virtual		void		PopContext( void );
+
+private :
+	
+	virtual		void*		allocate( size_t size, bool assert_on_fail );
+	virtual		void		free( BlockHeader* pHeader );
+	
+	void*					mp_base;
+	
+	Dbg_Code
+	(
+		sint				m_depth;
+		sint				m_alloc_count;
+	)
+};
+
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_PILE_H
diff --git a/Code/Sys/Mem/pool.cpp b/Code/Sys/Mem/pool.cpp
new file mode 100644
index 0000000..b6f28f9
--- /dev/null
+++ b/Code/Sys/Mem/pool.cpp
@@ -0,0 +1,209 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			Memory (Mem) 											**
+**																			**
+**	File name:		pool.cpp												**
+**																			**
+**	Created by:		03/29/00	-	mjb										**
+**																			**
+**	Description:	Memory Pool class										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include "memman.h"
+#include "pool.h"
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+void Pool::dump_free_list( void )
+{
+	
+	
+	BlockHeader*	p_header = mp_free_list;
+
+	while ( p_header )
+	{
+		Dbg_AssertType( p_header, BlockHeader );
+		Dbg_Message ( "%p  %d   (%p)", (void*)((uint)p_header + sizeof( BlockHeader )), 
+										p_header->mSize, p_header->mpNext );
+		p_header = p_header->mpNext;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void*	Pool::allocate( size_t size, bool assert_on_fail )
+{
+	
+		
+	Dbg_MsgAssert ( size <= m_size,( "Failed to allocate: Size (%x) > Pool Element (%x)", size, m_size ));
+
+	if ( mp_free_list )
+	{
+		Dbg_AssertType( mp_free_list, BlockHeader );
+		
+		BlockHeader* p_header = mp_free_list;
+		
+		mp_free_list = p_header->mpNext;
+		p_header->mpAlloc = this;
+
+		MemDbg_AllocateBlock ( p_header );	
+
+		return (void*)((uint)p_header + BlockHeader::sSize);
+	}
+
+	if ( assert_on_fail )
+	{
+		Dbg_MsgAssert ( false,( "Failed to allocate %d bytes, no free slots", size ));
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Pool::free( BlockHeader* pFreeBlock )
+{
+	
+	
+	Dbg_AssertType( pFreeBlock, BlockHeader );
+
+	MemDbg_FreeBlock ( pFreeBlock );	
+	   
+	pFreeBlock->mpNext = mp_free_list;
+	mp_free_list = pFreeBlock;
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Pool::Pool( Region* region, size_t size, uint count, Direction dir )
+: Allocator( region, dir, "Pool" ), m_count( count), m_size( size )
+{
+	
+
+	if ( !nAlignedBy( m_size, 4 ))
+	{
+#ifdef __PLAT_NGC__
+		Dbg_Warning ( "size %x must be 32 byte aligned ( setting to %x )", m_size, nAlignUpBy( (void*)m_size, 5 ));
+		m_size = (size_t)nAlignUpBy( m_size, 5 );
+#else
+		Dbg_Warning ( "size %x must be 16 byte aligned ( setting to %x )", m_size, nAlignUpBy( (void*)m_size, 4 ));
+		m_size = (size_t)nAlignUpBy( m_size, 4 );
+#endif
+	}
+
+	BlockHeader* p_freeblock = (BlockHeader*)mp_region->Allocate( this, ( m_size + BlockHeader::sSize ) * m_count );
+	
+	Dbg_MsgAssert ( p_freeblock,( "Failed to allocate Memory Pool from Region" ));
+
+	mp_free_list = p_freeblock;
+
+	for ( uint i = 0; i < ( m_count - 1 ); i++ )
+	{
+		new ((void*)p_freeblock) BlockHeader( this, m_size );		
+		
+		BlockHeader* p_nextblock = (BlockHeader*)( (uint)p_freeblock + BlockHeader::sSize + m_size ); 
+		p_freeblock->mpNext = p_nextblock;
+		p_freeblock = p_nextblock;
+	}
+
+	new ((void*)p_freeblock) BlockHeader( this, m_size );		
+	p_freeblock->mpNext = NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Pool::~Pool( void )
+{
+	
+
+	Dbg_Code  // warn if there are still allocated slots.
+	(
+		uint free_list_count = 0;
+
+		for ( BlockHeader* p_free_list = mp_free_list; p_free_list; p_free_list = p_free_list->mpNext )
+		{
+			free_list_count++;
+		}
+
+		Dbg_MsgAssert(( free_list_count == m_count ),( "Pool still contains allocated slots" ));
+	)
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
diff --git a/Code/Sys/Mem/pool.h b/Code/Sys/Mem/pool.h
new file mode 100644
index 0000000..ef090c2
--- /dev/null
+++ b/Code/Sys/Mem/pool.h
@@ -0,0 +1,95 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		core/sys/mem/pool.h										**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_POOL_H
+#define	__SYS_MEM_POOL_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include "alloc.h"
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class Region;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Pool : public  Allocator 			
+{
+	
+	
+	friend class Manager;
+
+public :
+		
+								Pool( Region* region, size_t size, uint count, Direction dir = vBOTTOM_UP );
+	virtual						~Pool( void );
+	
+private :
+	
+	virtual		void*			allocate( size_t size, bool assert_on_fail );
+	virtual		void			free( BlockHeader* pHeader );
+				void			dump_free_list( void );
+
+				BlockHeader*	mp_free_list;
+				uint			m_count;
+				size_t			m_size;
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_POOL_H
diff --git a/Code/Sys/Mem/region.cpp b/Code/Sys/Mem/region.cpp
new file mode 100644
index 0000000..2f870c6
--- /dev/null
+++ b/Code/Sys/Mem/region.cpp
@@ -0,0 +1,310 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		System Library											**
+**																			**
+**	Module:			Memory (Mem) 											**
+**																			**
+**	File name:		region.cpp												**
+**																			**
+**	Created by:		03/20/00	-	mjb										**
+**																			**
+**	Description:	Region Class											**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include "alloc.h"
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+	
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+Region::~Region( void )
+{
+	
+
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	Region::init( void* pStart, void* pEnd )
+{
+	
+
+	Dbg_AssertPtr( pStart );
+
+	mp_start	= pStart;
+	mp_end		= pEnd;
+
+	m_min_free  = (int)pEnd - (int)pStart;
+
+	m_alloc[vBOTTOM_UP] = 
+	m_alloc[vTOP_DOWN] 	= NULL;
+
+	Dbg_Notify( "Region(%p) created %p -> %p ", this, mp_start, mp_end );
+
+	if (!Config::CD())
+	{
+#ifdef __CLEAR_MEMORY__
+		uint64* ptr = (uint64*)mp_start; 
+		uint 	size = (uint)mp_end - (uint)mp_start;
+	
+		for ( uint i = 0; i < size; i += 8 )
+		{
+			*ptr++ = vFREE_BLOCK;
+		}
+#endif
+	}
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void		Region::RegisterAllocator( Allocator* pAlloc )
+{
+	
+
+	int index = ( pAlloc->m_dir & 2 ) >> 1;
+
+	Dbg_MsgAssert ( m_alloc[index] == NULL,( "Attempt to register multiple Allocators" ));
+
+	m_alloc[index] = pAlloc;
+
+	pAlloc->mp_top = ( pAlloc->m_dir == Allocator::vBOTTOM_UP ) ? mp_start : mp_end;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Region::UnregisterAllocator( Allocator* pAlloc )
+{
+	
+	
+	int index = ( pAlloc->m_dir & 2 ) >> 1;
+	
+	Dbg_MsgAssert ( m_alloc[index] == pAlloc,( "Allocator not currently registered" ));
+
+	m_alloc[index] = NULL;
+}
+	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Givne an allocator pAlloc, then allocate some space from this region
+// for this allocator
+// account for the m_dir flag in the allocator, which will be -1 or +1
+
+void*		Region::Allocate( Allocator* pAlloc, size_t size, bool assert_on_fail )
+{
+	
+	
+	Dbg_AssertType ( pAlloc, Allocator );
+
+	// Get index = 0 if BottomUp
+	//     index = 1 if TopDown
+	int index = ( pAlloc->m_dir & 2 ) >> 1;
+	Dbg_MsgAssert ( m_alloc[index] == pAlloc,( "Allocator not currently registered" ));
+
+	// mp_top of an allocator is the "Top" of the heap relative to what direction it is going in
+	// hence for a BottomUp heap, the mp_top will be the byte after the highest byte
+	// and for a TopDown heap, the mp_top will be the lowest byte	
+	
+	// here we get a new p_top for the allocator in pAlloc
+	// then we just need to test it
+	void* p_top = (void*) (( (uint)pAlloc->mp_top ) + ( size * pAlloc->m_dir ) );
+	
+	if( Allocator* p_alloc = m_alloc[index^1] )	  // If other allocator exists
+	{
+		if ( index == vBOTTOM_UP )				  // we are bottom up, other allocater is top down
+		{
+			if ( p_top > p_alloc->mp_top )		 // our new top is above the other allocator's base
+			{
+				if( assert_on_fail )			 // so fail
+				{
+					Dbg_MsgAssert( false,( "Failed to Allocate %d bytes", size ));
+				}
+				Dbg_Warning( "Failed to Allocate %d bytes", size ); 
+				return NULL;
+			}
+		}
+		else	// we are top down, other allocator is bottom up
+		{
+			if ( p_top < p_alloc->mp_top )			// new top is below the other allocator's top
+			{
+				if( assert_on_fail )				// so fail
+				{
+					Dbg_MsgAssert( false,( "Failed to Allocate %d bytes", size ));
+				}
+				Dbg_Warning( "Failed to Allocate %d bytes", size ); 
+				return NULL;
+			}
+		}
+	}
+	else
+	{
+		// here there is no other allocator
+		if ( index == vBOTTOM_UP )				// for a single bottom up heap
+		{
+			if ( p_top > mp_end )  				// if new p_top goes past end of region
+			{
+				if( assert_on_fail )			// then fail
+				{
+					Dbg_MsgAssert( false,( "Failed to Allocate %d bytes", size ));
+				}
+				Dbg_Warning( "Failed to Allocate %d bytes", size ); 
+				return NULL;
+			}
+		}
+		else	// single top down	heap
+		{
+			if ( p_top < mp_start )		 // p_top goes below start of region
+			{
+				if( assert_on_fail )
+				{
+					Dbg_MsgAssert( false,( "Failed to Allocate %d bytes", size ));
+				}
+				Dbg_Warning( "Failed to Allocate %d bytes", size ); 
+				return NULL;
+			}
+		}
+	}
+
+	// If it's vBOTTOM_UP then
+	//    return pAlloc->mp_top, which is the old end of the heap, and the start of the new block
+	// else
+	// 	  return p_top, which is the base of the new block (when heap growing downwards)
+
+	void* p_ret = ( index == vBOTTOM_UP ) ? pAlloc->mp_top : p_top;
+
+	// store new "top" of heap
+	pAlloc->mp_top = p_top;
+
+	int free = MemAvailable();
+	if (free < m_min_free)
+	{
+		m_min_free = free;
+	}
+
+
+	// and return the start of the new memory
+	return p_ret;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Region::Region( void* pStart, void* pEnd )
+{
+	
+		
+	init( pStart, pEnd );
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+AllocRegion::AllocRegion( size_t size )
+{
+	
+
+	size = (size_t) nAlignDown( size );
+	
+	mp_start = new char[size];
+	
+	init( mp_start, (void*)( (uint)mp_start + size ));
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+AllocRegion::~AllocRegion( void )
+{
+	
+
+	delete[] (char *)mp_start;
+}
+					
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Region::MemAvailable( void )
+{
+	
+		
+	int bot = (int)((m_alloc[0]) ? m_alloc[0]->mp_top : mp_start);
+	int top = (int)((m_alloc[1]) ? m_alloc[1]->mp_top : mp_end);
+
+	return ((int)top - (int)bot);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
diff --git a/Code/Sys/Mem/region.h b/Code/Sys/Mem/region.h
new file mode 100644
index 0000000..258df70
--- /dev/null
+++ b/Code/Sys/Mem/region.h
@@ -0,0 +1,144 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment	                        **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		Sys Library												**
+**																			**
+**	Module:			Memory Manager (Mem)									**
+**																			**
+**	Created:		03/20/00	-	mjb										**
+**																			**
+**	File name:		/sys/mem/region.h   									**
+**																			**
+*****************************************************************************/
+
+#ifndef	__SYS_MEM_REGION_H
+#define	__SYS_MEM_REGION_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Mem
+{
+
+
+
+/*****************************************************************************
+**							     Type Defines								**
+*****************************************************************************/
+
+class Allocator;
+
+class  Region  : public Spt::Class			
+{
+	
+
+	friend class Manager;
+
+public :
+		
+	void*		StartAddr ( void ) const { return mp_start; }
+	void*		EndAddr ( void ) const { return mp_end; }
+
+	void		RegisterAllocator( Allocator* alloc );		
+	void		UnregisterAllocator( Allocator* alloc );
+	void*		Allocate( Allocator* pAlloc, size_t size, bool assert_on_fail = true );
+	int			MemAvailable( void );
+	int			MinMemAvailable( void ) {return m_min_free;}
+	int			TotalSize( void );
+
+	virtual		~Region();
+
+protected :
+				Region( void ){};
+				Region( void* pStart, void* pEnd );
+
+	void		init ( void* pStart, void* pEnd );
+	void*		mp_start;
+
+
+	int			m_min_free;		// minimum amount of free memory
+
+private :
+	
+	enum
+	{
+		vBOTTOM_UP,
+		vTOP_DOWN,
+		vMAX_ALLOCS
+	};
+
+	void*		mp_end;
+
+	Allocator*	m_alloc[vMAX_ALLOCS];
+
+};
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  AllocRegion : public  Region 			
+{
+	
+
+	friend class Manager;
+
+public :
+				AllocRegion( size_t size );
+	virtual		~AllocRegion( void );
+
+private :
+
+
+};
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+inline int Region::TotalSize( void )
+{
+	
+		
+	return ((int)mp_end - (int)mp_start);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mem
+
+#endif  // __SYS_MEM_REGION_H
diff --git a/Code/Sys/MemCard/NGPS/p_McMan.cpp b/Code/Sys/MemCard/NGPS/p_McMan.cpp
new file mode 100644
index 0000000..9d68ab4
--- /dev/null
+++ b/Code/Sys/MemCard/NGPS/p_McMan.cpp
@@ -0,0 +1,768 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS														**
+**																			**
+**	Module:			Mc			 											**
+**																			**
+**	File name:		memcard.cpp												**
+**																			**
+**	Created by:		03/06/01	-	spg										**
+**																			**
+**	Description:	Memcard - platform-specific implementations				**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+
+namespace Mc
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "MemCard Manager" );
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+Manager::Manager( void )
+{
+	
+
+	int result;
+	int i, j;
+
+	result = sceMcInit();
+	Dbg_MsgAssert( result == 0,( "Failed to initialize memory card system\n" ));
+
+	for( i = 0; i < vMAX_PORT; i++ )
+	{
+		for( j = 0; j < vMAX_SLOT; j++ )
+		{
+			m_card[i][j].m_port = i;
+			m_card[i][j].m_slot = j;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+int		Manager::GetMaxSlots( int port )
+{
+	int result;
+
+   	result = sceMcGetSlotMax( port );
+	if( result >= 0 )
+	{
+		return result;
+	}
+	else
+	{
+		return 0;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Card*	Manager::GetCard( int port, int slot )
+{
+	
+	int type;
+
+	Dbg_Assert( port < vMAX_PORT );
+	Dbg_Assert( slot < vMAX_SLOT );
+	
+	Card* card = &m_card[port][slot];
+
+	type = card->GetDeviceType();
+	if(	( type == Card::vDEV_PS1_CARD ) ||
+		( type == Card::vDEV_PS2_CARD ))
+	{
+		return card;
+	}
+    
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		wait_for_async_call_to_finish( Card* card )
+{
+	int ret_val;
+
+	
+
+	sceMcSync( 0, NULL, &ret_val );
+	if( ret_val < 0 )
+	{   
+		switch( ret_val )
+		{
+			case -1:
+				Dbg_Printf( "Switched to formated card\n" );
+				card->SetError( Card::vERROR_FORMATTED );
+				break;
+			case -2:
+				Dbg_Printf( "Error: Unformatted Card\n" );
+				card->SetError( Card::vERROR_UNFORMATTED );
+				break;
+			case -3:
+				Dbg_Printf( "Error: Insufficient Space\n" );
+				card->SetError( Card::vINSUFFICIENT_SPACE );
+				break;
+			case -4:
+				Dbg_Printf( "Error: Invalid Path\n" );
+				card->SetError( Card::vINVALID_PATH );
+				break;
+			case -5:
+				Dbg_Printf( "Error: Access Error\n" );
+				card->SetError( Card::vACCESS_ERROR );
+				break;
+			case -6:
+				Dbg_Printf( "Error: Subdir not empty\n" );
+				card->SetError( Card::vSUBDIR_NOT_EMPTY );
+				break;
+			case -7:
+				Dbg_Printf( "Error: Too many open files\n" );
+				card->SetError( Card::vTOO_MANY_OPEN_FILES );
+				break;
+			case -10:
+				Dbg_Printf( "Error: No card\n" );
+				card->SetError( Card::vNO_CARD );
+				break;
+			default:
+				card->SetError( Card::vUNKNOWN );
+				break;
+		}
+	}
+
+	return ret_val;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Card::MakeDirectory( const char* dir_name )
+{
+	int result;
+	 
+	
+
+	result = sceMcMkdir( m_port, m_slot, dir_name );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+    
+	int ErrorCode=wait_for_async_call_to_finish( this );
+	if (ErrorCode==-4)
+	{
+		// If, the directory exists already, don't make it an error
+		ErrorCode=0;
+	}	
+	
+	return( ErrorCode == 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Card::DeleteDirectory( const char* dir_name )
+{
+	// On the PS2, the Delete() function used for files can also
+	// be used to delete a directory.
+	return Delete(dir_name);
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const char *Card::ConvertDirectory( const char* dir_name )
+{
+	return NULL;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool 	Card::ChangeDirectory( const char* dir_name )
+{
+	int result;
+
+	result = sceMcChdir( m_port, m_slot, dir_name, NULL );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+    
+	return( wait_for_async_call_to_finish( this ) == 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Card::Format( void )
+{
+	int result;
+
+	result = sceMcFormat( m_port, m_slot );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+    
+	return( wait_for_async_call_to_finish( this ) == 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Card::Unformat( void )
+{
+	int result;
+
+	result = sceMcUnformat( m_port, m_slot );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+    
+	return( wait_for_async_call_to_finish( this ) == 0 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Card::IsFormatted( void )
+{
+	int formatted;
+	int result;
+
+    result = sceMcGetInfo( m_port, m_slot, NULL, NULL, &formatted );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+    
+	result = wait_for_async_call_to_finish( this );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+
+	return( formatted == 1 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Card::GetDeviceType( void )
+{
+	int type;
+	int result;
+
+    result = sceMcGetInfo( m_port, m_slot, &type, NULL, NULL );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return vDEV_NONE;
+	}
+    
+	result = wait_for_async_call_to_finish( this );
+	if(	result != 0 )
+	{
+		if(	( GetLastError() != vERROR_UNFORMATTED ) &&
+			( GetLastError() != vERROR_FORMATTED ))
+		{
+			SetError( vUNKNOWN );
+			return vDEV_NONE;
+		}
+	}
+
+	switch( type )
+	{
+		case 1:
+			return vDEV_PS1_CARD;
+		case 2:
+			return vDEV_PS2_CARD;
+		case 3:
+			return vDEV_POCKET_STATION;
+		default:
+			return vDEV_NONE;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Card::GetNumFreeClusters( void )
+{
+	int free_clusters;
+	int result;
+
+    result = sceMcGetInfo( m_port, m_slot, NULL, &free_clusters, NULL );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return 0;
+	}
+    
+	result = wait_for_async_call_to_finish( this );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return 0;
+	}
+
+	return free_clusters;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		Card::GetNumFreeEntries( const char* path )
+{
+	int result;
+
+    result = sceMcGetEntSpace( m_port, m_slot, path );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return 0;
+	}
+    
+	return( wait_for_async_call_to_finish( this ));	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Card::Delete( const char* filename )
+{
+	int result;
+
+	result = sceMcDelete( m_port, m_slot, filename );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+    
+	int ErrorCode=wait_for_async_call_to_finish( this );
+	if (ErrorCode==-4)
+	{
+		// The the error was that the file already did not exist, don't report this as an error.
+		ErrorCode=0;
+	}
+		
+	return( ErrorCode==0 );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	Card::Rename( const char* old_name, const char* new_name )
+{
+	int result;
+
+	result = sceMcRename( m_port, m_slot, old_name, new_name );
+	if( result != 0 )
+	{
+		SetError( vUNKNOWN );
+		return false;
+	}
+    
+	return( wait_for_async_call_to_finish( this ) == 0 );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+File*	Card::Open( const char* filename, int mode, int size )
+{
+	int result, fd;
+	int open_mode;
+	File* file;
+
+	
+
+	//Dbg_MsgAssert( strlen( filename ) < 31,( "Filename exceeds max length\n" ));
+
+	file = NULL;
+	open_mode = 0;
+	if( mode & File::mMODE_READ )
+	{
+		open_mode |= SCE_RDONLY;
+	}
+	if( mode & File::mMODE_WRITE )
+	{
+		open_mode |= SCE_WRONLY;
+	}
+	if( mode & File::mMODE_CREATE )
+	{
+		open_mode |= SCE_CREAT;
+	}
+	result = sceMcOpen( m_port, m_slot, filename, open_mode );
+	if( result != 0 )
+	{
+		Dbg_Printf( "Unknown Error\n" );
+		SetError( vUNKNOWN );
+		return NULL;
+	}
+    
+	fd = wait_for_async_call_to_finish( this );
+	if( fd >= 0 )
+	{
+		file = new File( fd, this );
+	}
+	return file;	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Ken: This used to be a local variable in Card::GetFileList, but sceMcGetDir requires that
+// this buffer be 64 byte aligned, and occasionally it would not be, causing the contents of
+// file_table to be undefined after the call to sceMcGetDir.
+// Unfortunately, Sony did not make sceMcGetDir return an error code in this case, the blighters.
+//
+static sceMcTblGetDir file_table[Card::vMAX_FILES_PER_DIRECTORY];   	
+
+bool	Card::GetFileList( const char* mask, Lst::Head< File > &file_list )
+{
+	
+	int result, i;
+		
+	Dbg_MsgAssert( mask != NULL,( "Mask must be a valid search string. You may use wildcards\n" ));
+
+	Dbg_MsgAssert( (((uint32)file_table)&63)==0, ("file_table not 64 byte aligned"));
+	
+	
+	int CallNumber=0;
+	while (true)
+	{
+		result = sceMcGetDir( m_port, m_slot, mask, CallNumber, vMAX_FILES_PER_DIRECTORY, file_table );
+		if( result != 0 )
+		{
+			SetError( vUNKNOWN );
+			return 0;
+		}
+		
+		int NumFilesReturned = wait_for_async_call_to_finish( this );
+		for( i = 0; i < NumFilesReturned; i++ )
+		{
+			File *new_file;
+
+			new_file = new File( 0, this );
+
+			strcpy( new_file->m_Filename, (char *) file_table[i].EntryName );
+			new_file->m_Size = file_table[i].FileSizeByte;
+			
+			new_file->m_Attribs = 0;
+			if( file_table[i].AttrFile & sceMcFileAttrReadable )
+			{
+				new_file->m_Attribs |= File::mATTRIB_READABLE;
+			}
+			if( file_table[i].AttrFile & sceMcFileAttrWriteable )
+			{
+				new_file->m_Attribs |= File::mATTRIB_WRITEABLE;
+			}
+			if( file_table[i].AttrFile & sceMcFileAttrExecutable )
+			{
+				new_file->m_Attribs |= File::mATTRIB_EXECUTABLE;
+			}
+			if( file_table[i].AttrFile & sceMcFileAttrDupProhibit )
+			{
+				new_file->m_Attribs |= File::mATTRIB_DUP_PROHIBITED;
+			}
+			if( file_table[i].AttrFile & sceMcFileAttrSubdir )
+			{
+				new_file->m_Attribs |= File::mATTRIB_DIRECTORY;
+			}
+			if( file_table[i].AttrFile & sceMcFileAttrClosed )
+			{
+				new_file->m_Attribs |= File::mATTRIB_CLOSED;
+			}
+			if( file_table[i].AttrFile & sceMcFileAttrPDAExec )
+			{
+				new_file->m_Attribs |= File::mATTRIB_PDA_APP;
+			}
+			if( file_table[i].AttrFile & sceMcFileAttrPS1 )
+			{
+				new_file->m_Attribs |= File::mATTRIB_PS1_FILE;
+			}
+			
+			sceCdCLOCK myClock;
+			myClock.day=itob(file_table[i]._Modify.Day);
+			myClock.hour=itob(file_table[i]._Modify.Hour);
+			myClock.minute=itob(file_table[i]._Modify.Min);
+			myClock.month=itob(file_table[i]._Modify.Month);
+			myClock.second=itob(file_table[i]._Modify.Sec);
+			myClock.year=itob(file_table[i]._Modify.Year%100);
+			sceScfGetLocalTimefromRTC(&myClock);
+			new_file->m_Modified.m_Seconds 	= btoi(myClock.second);
+			new_file->m_Modified.m_Minutes 	= btoi(myClock.minute);
+			new_file->m_Modified.m_Hour 	= btoi(myClock.hour);
+			new_file->m_Modified.m_Day 		= btoi(myClock.day);
+			new_file->m_Modified.m_Month 	= btoi(myClock.month);
+			new_file->m_Modified.m_Year 	= btoi(myClock.year)+2000;
+			
+			myClock.day=itob(file_table[i]._Create.Day);
+			myClock.hour=itob(file_table[i]._Create.Hour);
+			myClock.minute=itob(file_table[i]._Create.Min);
+			myClock.month=itob(file_table[i]._Create.Month);
+			myClock.second=itob(file_table[i]._Create.Sec);
+			myClock.year=itob(file_table[i]._Create.Year%100);
+			sceScfGetLocalTimefromRTC(&myClock);
+			new_file->m_Created.m_Seconds 	= btoi(myClock.second);
+			new_file->m_Created.m_Minutes 	= btoi(myClock.minute);
+			new_file->m_Created.m_Hour 		= btoi(myClock.hour);  
+			new_file->m_Created.m_Day 		= btoi(myClock.day);   
+			new_file->m_Created.m_Month 	= btoi(myClock.month); 
+			new_file->m_Created.m_Year 		= btoi(myClock.year)+2000;  
+
+			file_list.AddToTail( new_file );
+		}
+		
+		if (NumFilesReturned ( this ), m_fd( fd ), m_card( card )
+{
+}
+		
+File::~File()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool	File::Close( void )
+{
+	int result;
+
+	result = sceMcClose( m_fd );
+	if( result != 0 )
+	{
+		m_card->SetError( Card::vUNKNOWN );
+		return 0;
+	}
+    
+	return( wait_for_async_call_to_finish( m_card ) == 0 );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		File::Seek( int offset, FilePointerBase base )
+{
+	int result;
+	int mode;
+
+	
+
+	switch( base )
+	{
+		case BASE_START:
+			mode = 0;
+			break;
+		case BASE_CURRENT:
+			mode = 1;
+			break;
+		case BASE_END:
+			mode = 2;
+			break;
+		default:
+			mode = 1;
+			Dbg_MsgAssert( 0,( "Invalid FilePointerBase\n" ));
+			break;
+	}
+
+	result = sceMcSeek( m_fd, offset, mode );
+	if( result != 0 )
+	{
+		m_card->SetError( Card::vUNKNOWN );
+		return 0;
+	}
+    
+	return( wait_for_async_call_to_finish( m_card ));	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool 	File::Flush( void )
+{
+	int result;
+
+	result = sceMcFlush( m_fd );
+	if( result != 0 )
+	{
+		m_card->SetError( Card::vUNKNOWN );
+		return false;
+	}
+    
+	return( wait_for_async_call_to_finish( m_card ) == 0 );	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		File::Write( void* data, int len )
+{
+	int result;
+
+	result = sceMcWrite( m_fd, data, len );
+	if( result != 0 )
+	{
+		m_card->SetError( Card::vUNKNOWN );
+		return 0;
+	}
+    
+	return( wait_for_async_call_to_finish( m_card ));	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int		File::Read( void* buff, int len )
+{
+	int result;
+
+	
+
+	result = sceMcRead( m_fd, buff, len );
+	if( result != 0 )
+	{
+		Dbg_Printf( "Error: Could not read data\n" );
+		m_card->SetError( Card::vUNKNOWN );
+		return 0;
+	}
+    
+	return( wait_for_async_call_to_finish( m_card ));	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mc
+
+
+
+
diff --git a/Code/Sys/MemCard/XBox/p_McMan.cpp b/Code/Sys/MemCard/XBox/p_McMan.cpp
new file mode 100644
index 0000000..f50f0c1
--- /dev/null
+++ b/Code/Sys/MemCard/XBox/p_McMan.cpp
@@ -0,0 +1,1031 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS														**
+**																			**
+**	Module:			Mc			 											**
+**																			**
+**	File name:		memcard.cpp												**
+**																			**
+**	Created by:		03/06/01	-	spg										**
+**																			**
+**	Description:	Memcard - platform-specific implementations				**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mc
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define MAX_FILENAME_LENGTH	128
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "MemCard Manager" );
+
+static char cardFilenameBuffer[MAX_FILENAME_LENGTH];
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+Manager::Manager( void )
+{
+	int i, j;
+
+	for( i = 0; i < vMAX_PORT; i++ )
+	{
+		for( j = 0; j < vMAX_SLOT; j++ )
+		{
+			m_card[i][j].m_port = i;
+			m_card[i][j].m_slot = j;
+		}
+	}
+	
+	m_hard_drive.SetAsHardDrive();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+int	Manager::GetMaxSlots( int port )
+{
+	return vMAX_SLOT;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Card* Manager::GetCard( int port, int slot )
+{
+	// Ignore port and slot, since on the XBox we're only using the hard drive.
+	return &m_hard_drive;
+}
+
+// Skate3 code, disabled for now.
+#	if 0
+Card* Manager::GetHardDrive( )
+{
+	return &m_hard_drive;
+}
+
+const char* Manager::GetLastCardName( int port, int slot )
+{
+	Dbg_Assert( port < vMAX_PORT );
+	Dbg_Assert( slot < vMAX_SLOT );
+	
+	Card *p_card = &m_card[port][slot];
+	return p_card->GetLastPersonalizedName();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const char *Card::GetPersonalizedName()
+{
+	// Only get the name if the card is mounted and is not the hard drive.
+	if (m_mounted_drive_letter && m_mounted_drive_letter!='u')
+	{
+		sprintf(mp_personalized_name,"");
+		
+		WCHAR p_personalized_name[MAX_MUNAME+10];
+		DWORD rv=XMUNameFromDriveLetter(m_mounted_drive_letter,p_personalized_name,MAX_MUNAME);
+		if (rv==ERROR_SUCCESS)
+		{
+			// Instead of doing a wsprintfA( mp_personalized_name, "%ls", p_personalized_name );
+			// which will terminate as soon as it hits a bad char, convert each WCHAR one at a 
+			// time so that all good characters come across.
+			char *p_dest=mp_personalized_name;
+			int count=0;
+			WCHAR p_temp[2]; // A buffer for holding one WCHAR at a time for sending to wsprintfA
+			p_temp[1]=0;
+			const WCHAR *p_scan=p_personalized_name;
+			while (*p_scan) // WCHAR strings are terminated by a 0 just like normal strings, except its a 2byte 0.
+			{
+				p_temp[0]=*p_scan++;
+				
+				char p_one_char[10];
+				wsprintfA( p_one_char, "%ls", p_temp);
+				// p_one_char now contains a one char string.
+				
+				if (count=75)
+	{
+		return true;
+	}	
+	return false;
+}
+#	endif
+	
+
+// Note: dir_name must no longer start with a backslash, it should just be "Career2-Career" etc.
+bool Card::MakeDirectory( const char* dir_name )
+{
+	char	output_dir[vDIRECTORY_NAME_BUF_SIZE];
+	WCHAR	input_name[vDIRECTORY_NAME_BUF_SIZE];
+
+	wsprintfW( input_name, L"%hs", dir_name );
+	DWORD rv = XCreateSaveGame(	"u:\\",				// Root of device on which to create the save game.
+								input_name,			// Name of save game (effectively directory name).
+								OPEN_ALWAYS,		// Open disposition.
+								0,					// Creation flags.
+								output_dir,			// String to take resultant directory name buffer.
+								vDIRECTORY_NAME_BUF_SIZE );	// Size of directory name buffer.
+
+	if( rv == ERROR_SUCCESS )
+	{
+		return true;
+	}
+
+	if (rv==ERROR_DISK_FULL)
+	{
+		m_last_error=vINSUFFICIENT_SPACE;
+	}
+		
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const char *Card::ConvertDirectory( const char* dir_name )
+{
+	static char	output_dir[vDIRECTORY_NAME_BUF_SIZE];
+	WCHAR	input_name[vDIRECTORY_NAME_BUF_SIZE];
+
+	wsprintfW( input_name, L"%hs", dir_name );
+	DWORD rv = XCreateSaveGame(	"u:\\",				// Root of device on which to create the save game.
+								input_name,			// Name of save game (effectively directory name).
+								OPEN_EXISTING,		// Open disposition.
+								0,					// Creation flags.
+								output_dir,			// String to take resultant directory name buffer.
+								vDIRECTORY_NAME_BUF_SIZE );	// Size of directory name buffer.
+
+	if( rv == ERROR_SUCCESS )
+	{
+		// Remove the initial "u:\" and the final "\"
+		strcpy(output_dir,output_dir+3);
+		output_dir[strlen(output_dir)-1]=0;
+		
+		return output_dir;
+	}
+
+	return NULL;
+}	
+
+// Skate3 code, disabled for now.
+#if 0
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::ConvertDirectory( const char* dir_name, char* output_name )
+{
+	// K: This used to assert.
+	if (m_mounted_drive_letter==0)
+	{
+		return false;
+	}	
+
+	// Seems incoming filenames are of the form /foo etc.
+	cardFilenameBuffer[0] = m_mounted_drive_letter;
+	cardFilenameBuffer[1] = ':';
+	cardFilenameBuffer[2] = '\\';
+	cardFilenameBuffer[3] = 0;
+
+	++dir_name;
+	int index = 4;
+	while( cardFilenameBuffer[index] = *dir_name )
+	{
+		// Switch forward slash directory separators to the supported backslash.
+		if( cardFilenameBuffer[index] == '/' )
+		{
+			cardFilenameBuffer[index] = '\\';
+		}
+		++index;
+		++dir_name;
+	}
+
+	char	output_dir[64];
+	WCHAR	input_name[64];
+
+	wsprintfW( input_name, L"%hs", &cardFilenameBuffer[4] );
+	DWORD rv = XCreateSaveGame(	cardFilenameBuffer,	// Root of device on which to create the save game.
+								input_name,			// Name of save game (effectively directory name).
+								OPEN_EXISTING,		// Open disposition.
+								0,					// Creation flags.
+								output_dir,			// String to take resultant directory name buffer.
+								64 );				// Size of directory name buffer.
+
+	if( rv == ERROR_SUCCESS )
+	{
+		// Copy over output directory, stripping leading drive, colon and backslash, and removing trailing backslash.
+		strcpy( output_name, &output_dir[3] );
+		output_name[strlen( output_name ) - 1] = 0;
+		return true;
+	}
+
+	return false;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::DeleteDirectory( const char* dir_name )
+{
+	Dbg_Assert( m_mounted_drive_letter != 0 );
+
+	// Seems incoming filenames are of the form /foo etc.
+	cardFilenameBuffer[0] = m_mounted_drive_letter;
+	cardFilenameBuffer[1] = ':';
+	cardFilenameBuffer[2] = '\\';
+	cardFilenameBuffer[3] = 0;
+
+	++dir_name;
+	int index = 4;
+	while( cardFilenameBuffer[index] = *dir_name )
+	{
+		// Switch forward slash directory separators to the supported backslash.
+		if( cardFilenameBuffer[index] == '/' )
+		{
+			cardFilenameBuffer[index] = '\\';
+		}
+		++index;
+		++dir_name;
+	}
+
+	WCHAR	input_name[64];
+	wsprintfW( input_name, L"%hs", &cardFilenameBuffer[4] );
+	DWORD rv = XDeleteSaveGame(	cardFilenameBuffer,			// Root of device on which to create the save game.
+								input_name );
+
+	if( rv == ERROR_SUCCESS )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+#endif
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+// Given the name of a file, this will delete it's directory, which
+// will result in the deletion of the directory, the file, and the icon.
+// The name must not be preceded with a backslash, ie should be "Career12-Career" for example.
+bool Card::DeleteDirectory( const char* dir_name )
+{
+	WCHAR	input_name[64];
+	wsprintfW( input_name, L"%hs", dir_name );
+	DWORD rv = XDeleteSaveGame(	"u:\\", input_name );
+
+	if( rv == ERROR_SUCCESS )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::ChangeDirectory( const char* dir_name )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::Format( void )
+{
+	// Not supported from within an Xbox title.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::Unformat( void )
+{
+	// Not supported from within an Xbox title.
+	return false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::IsFormatted( void )
+{
+	// Must be formatted to have got this far on an Xbox title.
+	return true;
+}
+
+
+void Card::SetAsHardDrive()
+{
+	m_mounted_drive_letter='u';
+}
+
+// Skate3 code, disabled for now.
+#if 0
+bool Card::IsHardDrive()
+{
+	return m_mounted_drive_letter=='u';
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Card::UnMount( void )
+{
+	// Can't unmount the hard drive.
+	if( m_mounted_drive_letter=='u' )
+	{
+		return;
+	}	
+	
+    m_mount_error=false;
+	m_mount_failed_due_to_card_full=false;
+	m_mount_failed_due_to_card_unformatted=false;
+	
+	if( m_mounted_drive_letter )
+	{
+		DWORD rv = XUnmountMU(m_port, ( m_slot == 0 ) ? XDEVICE_TOP_SLOT : XDEVICE_BOTTOM_SLOT);
+		if (rv != ERROR_SUCCESS)
+		{
+			m_mount_error=true;
+			if (rv==ERROR_DISK_FULL)
+			{
+				m_mount_failed_due_to_card_full=true;
+			}	
+			if (rv==ERROR_UNRECOGNIZED_VOLUME)	
+			{
+				m_mount_failed_due_to_card_unformatted=true;
+			}
+		}    
+		m_mounted_drive_letter=0;
+	}
+}
+	
+#endif
+
+
+int	Card::GetDeviceType( void )
+{
+	return vDEV_XBOX_HARD_DRIVE;
+}
+
+
+// Skate3 code, disabled for now.
+#if 0
+
+bool Card::GetMountError()
+{
+    return m_mount_error;
+}
+
+bool Card::MountFailedDueToCardFull()
+{
+	return m_mount_failed_due_to_card_full;
+}	
+
+bool Card::MountFailedDueToCardUnformatted()
+{
+	return m_mount_failed_due_to_card_unformatted;
+}	
+
+#endif
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int	Card::GetNumFreeClusters( void )
+{
+	if( m_mounted_drive_letter )
+	{
+		char p_drive[20];
+		strcpy(p_drive,"z:\\");
+		
+		ULARGE_INTEGER	uliFreeAvail;
+		ULARGE_INTEGER	uliTotal;
+
+		p_drive[0] = m_mounted_drive_letter;
+		BOOL br		= GetDiskFreeSpaceEx( p_drive, &uliFreeAvail, &uliTotal, NULL );
+		if( br )
+		{
+			// Each increment of HighPart represents 2^32 bytes, which is (2^32)/16384=262144 blocks.
+			return uliFreeAvail.HighPart*262144 + uliFreeAvail.LowPart/16384;
+		}
+		else
+		{
+			return 0;
+		}
+	}
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int	Card::GetNumFreeEntries( const char* path )
+{
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::Delete( const char* filename )
+{
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::Rename( const char* old_name, const char* new_name )
+{
+	return true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+File* Card::Open( const char* filename, int mode, int size )
+{
+	Dbg_Assert( m_mounted_drive_letter != 0 );
+
+	m_last_error=0;
+	
+	File*	p_file		= NULL;
+	HANDLE	handle;
+
+	// Seems incoming filenames are of the form /foo/bar etc.
+	cardFilenameBuffer[0] = m_mounted_drive_letter;
+	cardFilenameBuffer[1] = ':';
+
+	int index = 2;
+	while( cardFilenameBuffer[index] = *filename )
+	{
+		// Switch forward slash directory separators to the supported backslash.
+		if( cardFilenameBuffer[index] == '/' )
+		{
+			cardFilenameBuffer[index] = '\\';
+		}
+		++index;
+		++filename;
+	}
+
+	DWORD dwDesiredAccess;
+	DWORD dwCreationDisposition;
+
+	switch( mode )
+	{
+		case File::mMODE_READ:
+		{
+			dwDesiredAccess			= GENERIC_READ;
+			dwCreationDisposition	= OPEN_EXISTING;
+			break;
+		}
+
+		case File::mMODE_WRITE:
+		{
+			dwDesiredAccess			= GENERIC_WRITE;
+			dwCreationDisposition	= OPEN_EXISTING;
+			break;
+		}
+
+		case ( File::mMODE_WRITE | File::mMODE_CREATE ):
+		{
+			dwDesiredAccess			= GENERIC_WRITE;
+			dwCreationDisposition	= OPEN_ALWAYS;
+			break;
+		}
+
+		case File::mMODE_CREATE:
+		{
+			dwDesiredAccess			= GENERIC_WRITE;
+			dwCreationDisposition	= CREATE_NEW;
+			break;
+		}
+
+		case ( File::mMODE_READ | File::mMODE_WRITE ):
+		{
+			dwDesiredAccess	= GENERIC_READ | GENERIC_WRITE;
+			dwCreationDisposition	= OPEN_EXISTING;
+			break;
+		}
+
+		default:
+		{
+			Dbg_Assert( 0 );
+			return NULL;
+		}
+	}
+
+	handle = CreateFile(	cardFilenameBuffer,							// file name
+							dwDesiredAccess,							// access mode
+							0,											// share mode
+							NULL,										// security attributes
+							dwCreationDisposition,						// how to create
+							FILE_ATTRIBUTE_NORMAL,						// file attributes and flags
+							NULL );				                        // handle to template file
+
+	if( handle != INVALID_HANDLE_VALUE )
+	{
+		p_file = new File( (int)handle, this );
+		
+		WIN32_FILE_ATTRIBUTE_DATA file_attribute_data;
+		if (GetFileAttributesEx(cardFilenameBuffer,GetFileExInfoStandard,&file_attribute_data))
+		{
+//			Skate3 code, disabled for now.
+#			if 0
+			p_file->m_file_time=file_attribute_data.ftLastWriteTime;
+#			endif
+		}	
+		
+		return p_file;
+	}
+	else
+	{
+		if (GetLastError()==ERROR_DISK_FULL)
+		{
+			m_last_error=vINSUFFICIENT_SPACE;
+		}
+	}	
+
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::GetFileList( const char* mask, Lst::Head< File > &file_list )
+{
+	HANDLE			handle;
+	XGAME_FIND_DATA	find_data;
+
+	cardFilenameBuffer[0] = m_mounted_drive_letter;
+	cardFilenameBuffer[1] = ':';
+	cardFilenameBuffer[2] = '\\';
+
+	cardFilenameBuffer[3] = 0;
+	if(( handle = XFindFirstSaveGame( cardFilenameBuffer, &find_data )) == INVALID_HANDLE_VALUE )
+	{
+		return true;
+	}
+
+	do
+	{
+		File* new_file = new File( 0, this );
+
+		// Skip copying the drive stuff, just copy the directory, and strip the trailing '\'.
+		strcpy( new_file->m_Filename, (char*)&find_data.szSaveGameDirectory[3] );
+		new_file->m_Filename[strlen( new_file->m_Filename ) - 1] = 0;
+
+		wsprintfA( new_file->m_DisplayFilename, "%ls", find_data.szSaveGameName );
+		
+		FILETIME local_file_time;
+		FileTimeToLocalFileTime(&find_data.wfd.ftLastWriteTime,&local_file_time);
+		SYSTEMTIME system_file_time;
+		FileTimeToSystemTime(&local_file_time,&system_file_time);
+		
+		new_file->m_Modified.m_Year=system_file_time.wYear;
+		new_file->m_Modified.m_Month=system_file_time.wMonth;
+		new_file->m_Modified.m_Day=system_file_time.wDay;
+		new_file->m_Modified.m_Hour=system_file_time.wHour;
+		new_file->m_Modified.m_Minutes=system_file_time.wMinute;
+		new_file->m_Modified.m_Seconds=system_file_time.wSecond;
+		
+		new_file->m_Size	= find_data.wfd.nFileSizeLow;
+		new_file->m_Attribs	= 0;
+		file_list.AddToTail( new_file );
+	}
+	while( XFindNextSaveGame( handle, &find_data ));
+	XFindClose( handle );
+	return true;
+}
+
+
+File::File( int fd, Card* card ) : Lst::Node< File > ( this ), m_fd( fd ), m_card( card )
+{
+}
+		
+File::~File()
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int File::Seek( int offset, FilePointerBase base )
+{
+	Dbg_Assert( m_fd != 0 );
+
+	DWORD dwMoveMethod;
+
+	switch( base )
+	{
+		case BASE_START:
+			dwMoveMethod = FILE_BEGIN;
+			break;
+		case BASE_CURRENT:
+			dwMoveMethod = FILE_CURRENT;
+			break;
+		case BASE_END:
+			dwMoveMethod = FILE_END;
+			break;
+		default:
+			dwMoveMethod = FILE_END;
+			Dbg_MsgAssert( 0,( "Invalid FilePointerBase\n" ));
+			break;
+	}
+
+
+	DWORD result = SetFilePointer(	(HANDLE)m_fd,		// handle to file
+									offset,				// bytes to move pointer
+									NULL,				// high-order bytes to move pointer
+									dwMoveMethod );		// starting point
+
+	return result;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool File::Flush( void )
+{
+	Dbg_Assert( m_fd != 0 );
+
+	FlushFileBuffers((HANDLE)m_fd );
+
+	// The FlushFileBuffers() is pretty strict about what types of files wmay be flushed,
+	// whereas the PS2 equivalent doesn't really care. Just return a positive response always,
+	// no critical stuff predicated on this return anway.
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	File::Write( void* data, int len )
+{
+	Dbg_Assert( m_fd != 0 );
+
+//	Skate3 code, disabled for now.
+#	if 0
+	m_not_enough_space_to_write_file=false;
+#	endif
+
+	DWORD bytes_written;
+	BOOL rv = WriteFile(	(HANDLE)m_fd,		// handle to file
+							data,				// data buffer
+							len,				// number of bytes to write
+							&bytes_written,		// number of bytes written
+							NULL );				// overlapped buffer
+							
+//	Skate3 code, disabled for now.
+#	if 0
+	if (rv==ERROR_NOT_ENOUGH_MEMORY)
+	{
+		m_not_enough_space_to_write_file=true;
+	}
+	if (GetLastError()==ERROR_DISK_FULL)
+	{
+		m_not_enough_space_to_write_file=true;
+	}
+#	endif
+		
+	if( rv )
+	{
+		return (int)bytes_written;
+	}
+
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	File::Read( void* buff, int len )
+{
+	Dbg_Assert( m_fd != 0 );
+
+	DWORD bytes_read;
+	BOOL rv = ReadFile(	(HANDLE)m_fd,			// handle to file
+						buff,					// data buffer
+						len,					// number of bytes to read
+						&bytes_read,			// number of bytes read
+						NULL );					// overlapped buffer
+	if( rv )
+	{
+		return (int)bytes_read;
+	}
+
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool File::Close( void )
+{
+	Dbg_Assert( m_fd != 0 );
+
+	return CloseHandle((HANDLE)m_fd );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mc
+
+
+
+
diff --git a/Code/Sys/MemCard/ngc/p_McMan.cpp b/Code/Sys/MemCard/ngc/p_McMan.cpp
new file mode 100644
index 0000000..34d3c5d
--- /dev/null
+++ b/Code/Sys/MemCard/ngc/p_McMan.cpp
@@ -0,0 +1,1072 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment.			                **
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS														**
+**																			**
+**	Module:			Mc			 											**
+**																			**
+**	File name:		memcard.cpp												**
+**																			**
+**	Created by:		03/06/01	-	spg										**
+**																			**
+**	Description:	Memcard - platform-specific implementations				**
+**																			**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Mc
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+												
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define MAX_FILENAME_LENGTH	CARD_FILENAME_MAX
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "MemCard Manager" );
+
+static char cardFilenameBuffer[MAX_FILENAME_LENGTH];
+
+static bool initCalled = false;
+
+// Implicit assumption here is that vMAX_PORT == 1, which is always true for Gamecube.
+static char cardWorkArea[Manager::vMAX_SLOT][CARD_WORKAREA_SIZE] __attribute__((aligned( 32 )));
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Functions								**
+*****************************************************************************/
+
+static void detachCallback( s32 chan, s32 result )
+{
+	OSReport( "card %d detached: result = %d\n", chan, result );
+
+	Spt::SingletonPtr< Mc::Manager > mc_man;
+
+	// Don't want to call GetCard() here, since that will go off and try to verify the card...
+	Card* p_card = mc_man->GetCardEx( 0, chan );
+	if( p_card && p_card->mp_work_area )
+	{
+//		delete[] p_card->mp_work_area;
+		p_card->mp_work_area	= NULL;
+	}
+}
+
+
+
+Manager::Manager( void )
+{
+	if( !initCalled )
+	{
+		initCalled = true;
+		CARDInit();
+	}
+
+	for( int i = 0; i < vMAX_PORT; i++ )
+	{
+		for( int j = 0; j < vMAX_SLOT; j++ )
+		{
+			m_card[i][j].m_port = i;
+			m_card[i][j].m_slot = j;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+}
+
+/*****************************************************************************
+**							  Public Functions								**
+*****************************************************************************/
+
+int	Manager::GetMaxSlots( int port )
+{
+	return vMAX_SLOT;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Card* Manager::GetCard( int port, int slot )
+{
+	Dbg_Assert( port < vMAX_PORT );
+	Dbg_Assert( slot < vMAX_SLOT );
+	
+	// Disable the reset button. Scripts will also do this by calling the script command
+	// DisableReset at appropriate points.
+	// The scripts may disable reset a bit earlier, eg when a 'checking card' message is on
+	// screen. They do that to prevent lots of TT bugs because if the reset button is enabled
+	// during the message it will seem like you can reset during card access, even though the
+	// card is not really being accessed then.
+	// It is done here too as a guarantee.
+	//printf("GetCard is disabling reset ...\n");
+	//NxNgc::EngineGlobals.disableReset = true;
+	
+	Card*	card	= &m_card[port][slot];
+	int		type	= card->GetDeviceType();
+
+	if( type == Card::vDEV_GC_CARD )
+	{
+		return card;
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Card::MakeDirectory( const char* dir_name )
+{
+//	Dbg_Assert( m_mounted_drive_letter != 0 );
+//
+//	// Seems incoming filenames are of the form /foo etc.
+//
+//	cardFilenameBuffer[0] = m_mounted_drive_letter;
+//	cardFilenameBuffer[1] = ':';
+//
+//	int index = 2;
+//	while( cardFilenameBuffer[index] = *dir_name )
+//	{
+//		// Switch forward slash directory separators to the supported backslash.
+//		if( cardFilenameBuffer[index] == '/' )
+//		{
+//			cardFilenameBuffer[index] = '\\';
+//		}
+//		++index;
+//		++dir_name;
+//	}
+//
+//	if( CreateDirectory( cardFilenameBuffer, NULL ))
+//	{
+//		return true;
+//	}
+
+	// Directory structure is not supported on Gamecube memory cards.
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::DeleteDirectory( const char* dir_name )
+{
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+const char *Card::ConvertDirectory( const char* dir_name )
+{
+	return NULL;
+}	
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Card::ChangeDirectory( const char* dir_name )
+{
+	// Directory structure is not supported on Gamecube memory cards.
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::Format( void )
+{
+	if( mp_work_area )
+	{
+		s32 format_result = CARDFormat( m_slot );
+		
+		if( format_result != CARD_RESULT_READY )
+		{
+			// If the format fails, then we need to do a CARDMount again, otherwise	
+			// CARDCheck will always return CARD_RESULT_NOCARD, even if a good card is inserted. (TT4975)
+			// Force a CARDMount by deleting the mp_work_area buffer and calling GetDeviceType.
+			if( mp_work_area )
+			{
+	//			delete [] mp_work_area;
+				mp_work_area = NULL;
+			}
+			GetDeviceType();
+			
+			m_broken=true;
+		}
+		
+		if( format_result == CARD_RESULT_READY )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::Unformat( void )
+{
+	// Not supported from within a GameCube title.
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::IsFormatted( void )
+{
+	if( mp_work_area )
+	{
+		s32 check_result = CARDCheck( m_slot );
+		if (check_result==CARD_RESULT_IOERROR)
+		{
+			m_broken=true;
+		}	
+		if( check_result == CARD_RESULT_READY )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::IsForeign( void )
+{
+	if( mp_work_area )
+	{
+		u16 encode;
+		CARDGetEncoding(m_slot,&encode);
+		if (encode==CARD_ENCODE_SJIS)
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool Card::IsBadDevice( void )
+{
+	if( mp_work_area )
+	{
+		s32 check_result = CARDCheck( m_slot );
+		if( check_result == CARD_RESULT_WRONGDEVICE )
+		{
+			return true;
+		}
+	}
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Card::GetDeviceType( void )
+{
+	// See whether this card is present.
+	s32	mem_size;
+	s32	sector_size;
+	s32	probe_result;
+	s32 loop_iterations	= 0;
+
+	// Cleat fatal error tracking variable.
+	Spt::SingletonPtr< Mc::Manager > mc_man;
+	mc_man->SetFatalError( false );
+	mc_man->SetWrongDevice( false );
+
+	do
+	{
+		// This would appear to be a problem with the 0.93 dev hardware.
+		probe_result = CARDProbeEx( m_slot, &mem_size, §or_size );
+
+		// CARDProbeEx() will return BUSY if the system is still checking the card, so safe to quit on a NOCARD.
+		if( probe_result == CARD_RESULT_NOCARD )
+		{
+			break;
+		}
+
+		if( probe_result == CARD_RESULT_WRONGDEVICE )
+		{
+			mc_man->SetWrongDevice( true );
+			break;
+		}	
+
+//		// Check for reset being pushed.
+//		if( NsDisplay::shouldReset())
+//		{
+//			// Prepare the game side of things for reset.
+//			Spt::SingletonPtr< Mlp::Manager > mlp_manager;
+//			//mlp_manager->PrepareReset();
+//
+//			// Prepare the system side of things for reset.
+//			NsDisplay::doReset();
+//		}
+	}
+	while(( probe_result != CARD_RESULT_READY ) && ( ++loop_iterations < 1000000 ));
+
+	switch( probe_result )
+	{
+		case CARD_RESULT_BUSY:
+		case CARD_RESULT_NOCARD:
+		case CARD_RESULT_READY:
+		case CARD_RESULT_FATAL_ERROR:
+		{
+			// If this card is not mounted, mount it now.
+			if( mp_work_area == NULL )
+			{
+				m_broken=false;
+				
+//				mp_work_area = new char[CARD_WORKAREA_SIZE];
+				mp_work_area = &( cardWorkArea[m_slot][0] );
+				Dbg_MsgAssert( (((uint32)mp_work_area)&0x1f)==0, ("mp_work_area not 32 byte aligned"));
+				
+				s32 mount_result = CARDMount( m_slot, mp_work_area, detachCallback );
+				
+				if (mount_result == CARD_RESULT_BROKEN)
+				{
+					s32 check_result = CARDCheck( m_slot );
+					//if (check_result == CARD_RESULT_BROKEN || check_result==CARD_RESULT_IOERROR)
+					if (check_result==CARD_RESULT_IOERROR)
+					{
+						m_broken=true;
+					}	
+				}	
+				
+				if(( mount_result == CARD_RESULT_READY ) || ( mount_result == CARD_RESULT_BROKEN ) || ( mount_result == CARD_RESULT_ENCODING ))
+				{
+					// Mounted. Required to call CARDCheck() at this stage.
+					s32 check_result = CARDCheck( m_slot );
+					
+					if(( check_result == CARD_RESULT_READY ) || ( check_result == CARD_RESULT_BROKEN ) || ( mount_result == CARD_RESULT_ENCODING ))
+					{
+						m_mem_size		= mem_size;
+						m_sector_size	= sector_size;
+
+						return vDEV_GC_CARD;
+					}
+					else
+					{
+//						delete [] mp_work_area;
+						mp_work_area = NULL;
+					}
+				}
+				else
+				{
+					if(( mount_result == CARD_RESULT_IOERROR ) || ( mount_result == CARD_RESULT_FATAL_ERROR ))
+					{
+						// Set Manager tracking variable.
+						mc_man->SetFatalError( true );
+					}
+					else if (mount_result==CARD_RESULT_WRONGDEVICE)
+					{
+						mc_man->SetWrongDevice( true );
+					}	
+//					delete [] mp_work_area;
+					mp_work_area = NULL;
+				}
+			}			
+			else
+			{
+				return vDEV_GC_CARD;
+			}
+			break;
+		}
+
+		case CARD_RESULT_WRONGDEVICE:
+		default:
+		{
+			// No valid card, or can't be processed at this time.
+			break;
+		}
+	}	
+
+	return 0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Card::GetNumFreeClusters( void )
+{
+	s32 bytes_free;
+	s32 files_free;
+	s32 free_result = CARDFreeBlocks( m_slot, &bytes_free, &files_free );
+	if( free_result == CARD_RESULT_READY )
+	{
+		return bytes_free / 8192;
+	}
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	Card::GetNumFreeEntries( const char* path )
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Card::Delete( const char* filename )
+{
+	// Seems incoming filenames are of the form /foo/bar etc. Since there is no
+	// directory structure on the Gamecube memory card, just convert to a single filename
+	// by removing '\' and '/'.
+	int index = 0;
+	while(( cardFilenameBuffer[index] = *filename ))
+	{
+		Dbg_Assert( index < MAX_FILENAME_LENGTH );
+
+		// Switch forward slash directory separators to the supported backslash.
+		if(( cardFilenameBuffer[index] == '/' ) || ( cardFilenameBuffer[index] == '\\' ) || ( cardFilenameBuffer[index] == '.' ))
+		{
+		}
+		else
+		{
+			++index;
+		}
+		++filename;
+	}
+
+	s32 delete_result = CARDDelete( m_slot, cardFilenameBuffer );
+	if( delete_result == CARD_RESULT_IOERROR )
+	{
+		m_broken=true;
+	}
+
+	if( delete_result == CARD_RESULT_READY )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Card::Rename( const char* old_name, const char* new_name )
+{
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+File* Card::Open( const char* filename, int mode, int size )
+{
+	Dbg_Assert( mp_work_area != 0 );
+
+	// Create the file here, it may be deleted if the open fails.
+	File* p_file = new File( 0, this );
+
+	// Seems incoming filenames are of the form /foo/bar etc. Since there is no
+	// directory structure on the Gamecube memory card, just convert to a single filename
+	// by removing '\' and '/'.
+	int index = 0;
+	while(( cardFilenameBuffer[index] = *filename ))
+	{
+		Dbg_MsgAssert( index < MAX_FILENAME_LENGTH, ( "Filename: %s too long\n", filename ));
+
+		// Switch forward slash directory separators to the supported backslash.
+		if(( cardFilenameBuffer[index] == '/' ) || ( cardFilenameBuffer[index] == '\\' ) || ( cardFilenameBuffer[index] == '.' ))
+		{
+		}
+		else
+		{
+			++index;
+		}
+		++filename;
+	}
+
+	// If we might be creating the file, ensure the size is aligned to a multiple of the sector size.
+	if( mode & File::mMODE_CREATE )
+	{
+		Dbg_Assert( m_sector_size > 0 );
+		size = ( size + ( m_sector_size - 1 )) & ~( m_sector_size - 1 );
+	}
+
+	s32 open_result;
+	switch( mode )
+	{
+		case File::mMODE_READ:
+		{
+			open_result = CARDOpen( m_slot, cardFilenameBuffer, &p_file->m_file_info );
+			break;
+		}
+
+		case File::mMODE_WRITE:
+		{
+			open_result = CARDOpen( m_slot, cardFilenameBuffer, &p_file->m_file_info );
+			break;
+		}
+
+		case ( File::mMODE_WRITE | File::mMODE_CREATE ):
+		{
+			open_result = CARDOpen( m_slot, cardFilenameBuffer, &p_file->m_file_info );
+			if( open_result == CARD_RESULT_NOFILE )
+			{
+				open_result = CARDCreate( m_slot, cardFilenameBuffer, size, &p_file->m_file_info );
+			}
+			break;
+		}
+
+		case File::mMODE_CREATE:
+		{
+			open_result = CARDCreate( m_slot, cardFilenameBuffer, size, &p_file->m_file_info );
+			break;
+		}
+
+		case ( File::mMODE_READ | File::mMODE_WRITE ):
+		{
+			open_result = CARDOpen( m_slot, cardFilenameBuffer, &p_file->m_file_info );
+			break;
+		}
+
+		default:
+		{
+			Dbg_Assert( 0 );
+			open_result = CARD_RESULT_FATAL_ERROR;
+			break;
+		}
+	}
+
+	if (open_result == CARD_RESULT_INSSPACE )
+	{
+		SetError(vINSUFFICIENT_SPACE);
+	}
+		
+	if( open_result == CARD_RESULT_READY )
+	{
+		// Set the file number.
+		p_file->m_file_number = CARDGetFileNo( &p_file->m_file_info );
+		return p_file;
+	}
+
+	delete p_file;
+	return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void s_convert_time(uint32 seconds, DateTime *p_dateTime)
+{
+	// seconds is the number of seconds since 01/01/2000 midnight
+	
+	OSCalendarTime ct;
+
+	OSTicksToCalendarTime(OSSecondsToTicks((uint64)seconds),&ct);
+	p_dateTime->m_Seconds = ct.sec;
+	p_dateTime->m_Minutes = ct.min;
+	p_dateTime->m_Hour = ct.hour;
+	p_dateTime->m_Day = ct.mday;
+	// The month value in OSCalendarTime starts at 0. May as well make it consistent
+	// with the other platforms by adding 1. It doesn't really matter whether 1 is added
+	// or not though cos the date is only used to determine the most recent save.
+	p_dateTime->m_Month = ct.mon+1;
+	p_dateTime->m_Year = ct.year;
+}
+
+bool Card::GetFileList( const char* mask, Lst::Head< File > &file_list )
+{
+	for( int i = 0; i < 127; ++i )
+	{
+		CARDStat stat;
+		s32	result = CARDGetStatus( 0, i, &stat );
+		if( result == CARD_RESULT_READY )
+		{
+			File* new_file;
+
+			new_file = new File( 0, this );
+			strcpy( new_file->m_Filename, (char*)stat.fileName );
+			new_file->m_Size = stat.length;
+			
+			new_file->m_Attribs = 0;
+			new_file->m_Attribs |= File::mATTRIB_READABLE;
+			new_file->m_Attribs |= File::mATTRIB_WRITEABLE;
+
+			s_convert_time(stat.time,&new_file->m_Modified);
+			
+//			if( file_table[i].AttrFile & sceMcFileAttrExecutable )
+//			{
+//				new_file->m_Attribs |= File::mATTRIB_EXECUTABLE;
+//			}
+//			if( file_table[i].AttrFile & sceMcFileAttrDupProhibit )
+//			{
+//				new_file->m_Attribs |= File::mATTRIB_DUP_PROHIBITED;
+//			}
+//			if( file_table[i].AttrFile & sceMcFileAttrSubdir )
+//			{
+//				new_file->m_Attribs |= File::mATTRIB_DIRECTORY;
+//			}
+//			if( file_table[i].AttrFile & sceMcFileAttrClosed )
+//			{
+//				new_file->m_Attribs |= File::mATTRIB_CLOSED;
+//			}
+//			if( file_table[i].AttrFile & sceMcFileAttrPDAExec )
+//			{
+//				new_file->m_Attribs |= File::mATTRIB_PDA_APP;
+//			}
+//			if( file_table[i].AttrFile & sceMcFileAttrPS1 )
+//			{
+//				new_file->m_Attribs |= File::mATTRIB_PS1_FILE;
+//			}
+//			new_file->m_Created.m_Seconds = file_table[i]._Create.Sec;
+//			new_file->m_Created.m_Minutes = file_table[i]._Create.Min;
+//			new_file->m_Created.m_Hour = file_table[i]._Create.Hour;
+//			new_file->m_Created.m_Day = file_table[i]._Create.Day;
+//			new_file->m_Created.m_Month = file_table[i]._Create.Month;
+//			new_file->m_Created.m_Year = file_table[i]._Create.Year;
+
+//			new_file->m_Modified.m_Seconds = file_table[i]._Modify.Sec;
+//			new_file->m_Modified.m_Minutes = file_table[i]._Modify.Min;
+//			new_file->m_Modified.m_Hour = file_table[i]._Modify.Hour;
+//			new_file->m_Modified.m_Day = file_table[i]._Modify.Day;
+//			new_file->m_Modified.m_Month = file_table[i]._Modify.Month;
+//			new_file->m_Modified.m_Year = file_table[i]._Modify.Year;
+
+			file_list.AddToTail( new_file );
+		}
+	}
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Card::CountFilesLeft()
+{
+	int NumFiles=0;
+	for( int i = 0; i < 127; ++i )
+	{
+		CARDStat stat;
+		s32	result = CARDGetStatus( 0, i, &stat );
+		if( result == CARD_RESULT_READY )
+		{
+			++NumFiles;
+		}
+	}
+	return 127-NumFiles;
+}
+
+File::File( int fd, Card* card ) : Lst::Node< File > ( this ), m_fd( fd ), m_card( card )
+{
+	m_need_to_flush=false;
+	m_write_offset=0;
+	mp_write_buffer=NULL;
+	
+	m_read_offset=0;
+	mp_read_buffer=NULL;
+}
+		
+File::~File()
+{
+	Dbg_MsgAssert(!m_need_to_flush,("Closed mem card file when it needed flushing"));
+	
+	if (mp_write_buffer)
+	{
+		Mem::Free(mp_write_buffer);
+		mp_write_buffer=NULL;
+	}	
+	if (mp_read_buffer)
+	{
+		Mem::Free(mp_read_buffer);
+		mp_read_buffer=NULL;
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int File::Seek( int offset, FilePointerBase base )
+{
+	m_read_offset=offset;
+	// Force a reload of the read buffer by deleting it.
+	if (mp_read_buffer)
+	{
+		Mem::Free(mp_read_buffer);
+		mp_read_buffer=NULL;
+	}
+		
+	int result = 0;
+
+	switch( base )
+	{
+		case BASE_START:
+			break;
+		case BASE_CURRENT:
+			Dbg_MsgAssert( 0, ( "Not supported" ));
+			break;
+		case BASE_END:
+		{
+			CARDStat	status;
+			s32			status_result = CARDGetStatus( m_card->GetSlot(), m_file_number, &status );
+			if( status_result == CARD_RESULT_READY )
+			{
+				result = status.length - offset;
+			}
+			break;
+		}
+		default:
+			Dbg_MsgAssert( 0,( "Invalid FilePointerBase\n" ));
+			return 0;
+			break;
+	}
+
+	return result;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool File::Flush( void )
+{
+	// The FlushFileBuffers() is pretty strict about what types of files wmay be flushed,
+	// whereas the PS2 equivalent doesn't really care. Just return a positive response always,
+	// no critical stuff predicated on this return anway.
+	
+	if (!m_need_to_flush)
+	{
+		return true;
+	}
+	
+	int num_bytes_to_write=m_write_offset%m_card->m_sector_size;
+	Dbg_MsgAssert(num_bytes_to_write,("Got zero num_bytes_to_write in File::Flush"));
+	
+	Dbg_MsgAssert(mp_write_buffer,("NULL mp_write_buffer"));
+	memset(mp_write_buffer+num_bytes_to_write,0,m_card->m_sector_size-num_bytes_to_write);
+	
+	int file_offset=m_write_offset-num_bytes_to_write;
+	
+	Dbg_MsgAssert(file_offset%m_card->m_sector_size == 0,("Bad file_offset"));
+	s32 write_result = CARDWrite( &m_file_info, mp_write_buffer, m_card->m_sector_size, file_offset );
+	if ( write_result != CARD_RESULT_READY )
+	{
+		return false;
+	}
+	
+	m_need_to_flush=false;
+	m_write_offset=0;
+	if (mp_write_buffer)
+	{
+		Mem::Free(mp_write_buffer);
+		mp_write_buffer=NULL;
+	}	
+	
+	return true;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int	File::Write( void* data, int len )
+{
+	Dbg_MsgAssert(data,("NULL data"));
+	Dbg_Assert( len > 0 );
+	Dbg_Assert( m_card->m_sector_size > 0 );
+
+	if (!mp_write_buffer)
+	{
+		// Grab a sector-sized chunk of memory, ensuring it is 32-byte aligned.
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+		Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+		mp_write_buffer = (uint8*)Mem::Malloc(m_card->m_sector_size);
+		Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+		Mem::Manager::sHandle().PopContext();
+	}
+	Dbg_MsgAssert(mp_write_buffer,("NULL mp_write_buffer"));
+	
+	// Ensure the buffer address is 32 byte aligned.
+	Dbg_MsgAssert((((unsigned int)mp_write_buffer ) & 31 ) == 0, ( "file write buffer must be 32 byte aligned" ));
+
+	m_need_to_flush=false;
+
+	int	bytes_remaining_to_write = len;
+	unsigned char*	p_source_buffer	= (unsigned char*)data;
+
+	int write_buffer_sector_offset=m_write_offset%m_card->m_sector_size;
+	int write_buffer_left=m_card->m_sector_size - write_buffer_sector_offset;
+		
+	if (bytes_remaining_to_write < write_buffer_left)
+	{
+		memcpy( mp_write_buffer+write_buffer_sector_offset, p_source_buffer, bytes_remaining_to_write);
+		m_write_offset+=bytes_remaining_to_write;
+		m_need_to_flush=true;
+		return len;
+	}
+
+	int file_offset=m_write_offset-write_buffer_sector_offset;
+
+	if (write_buffer_left)
+	{
+		Dbg_MsgAssert(write_buffer_sector_offset+write_buffer_left==m_card->m_sector_size,("Oops"));
+		memcpy( mp_write_buffer+write_buffer_sector_offset, p_source_buffer, write_buffer_left);
+		m_write_offset+=write_buffer_left;
+		bytes_remaining_to_write-=write_buffer_left;
+		p_source_buffer+=write_buffer_left;
+		
+		Dbg_MsgAssert(file_offset%m_card->m_sector_size == 0,("Bad file_offset"));
+		s32 write_result = CARDWrite( &m_file_info, mp_write_buffer, m_card->m_sector_size, file_offset );
+		if ( write_result != CARD_RESULT_READY )
+		{
+			return 0;
+		}
+		file_offset+=m_card->m_sector_size;
+		m_need_to_flush=false;
+	}
+
+	while (bytes_remaining_to_write)
+	{
+		if (bytes_remaining_to_write >= m_card->m_sector_size)
+		{
+			Dbg_MsgAssert(m_write_offset%m_card->m_sector_size == 0,("Oops"));
+			
+			memcpy( mp_write_buffer, p_source_buffer, m_card->m_sector_size);
+			m_write_offset+=m_card->m_sector_size;
+			bytes_remaining_to_write-=m_card->m_sector_size;
+			p_source_buffer+=m_card->m_sector_size;
+			
+			Dbg_MsgAssert(file_offset%m_card->m_sector_size == 0,("Bad file_offset"));
+			s32 write_result = CARDWrite( &m_file_info, mp_write_buffer, m_card->m_sector_size, file_offset );
+			if ( write_result != CARD_RESULT_READY )
+			{
+				return 0;
+			}
+			file_offset+=m_card->m_sector_size;
+			m_need_to_flush=false;
+		}
+		else
+		{
+			memcpy( mp_write_buffer, p_source_buffer, bytes_remaining_to_write);
+			memset( mp_write_buffer+bytes_remaining_to_write, 0, m_card->m_sector_size-bytes_remaining_to_write);
+			
+			m_write_offset+=bytes_remaining_to_write;
+			bytes_remaining_to_write=0;
+			m_need_to_flush=true;
+		}
+	}
+	return len;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+int	File::Read( void* buff, int len )
+{
+	Dbg_Assert( len > 0 );
+	Dbg_Assert( m_card->m_sector_size > 0 );
+
+	int read_buffer_sector_offset=m_read_offset%m_card->m_sector_size;
+	int read_buffer_left=m_card->m_sector_size - read_buffer_sector_offset;
+
+	if (!mp_read_buffer)
+	{
+		// Similar process to write, in that we read 1 sector at a time. So grab a sector-sized chunk of memory,
+		// ensuring it is 32-byte aligned.
+		Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().BottomUpHeap());
+		Mem::Manager::sHandle().BottomUpHeap()->PushAlign( 32 );
+		mp_read_buffer = (uint8*)Mem::Malloc(m_card->m_sector_size);
+		Mem::Manager::sHandle().BottomUpHeap()->PopAlign();
+		Mem::Manager::sHandle().PopContext();
+		
+		Dbg_MsgAssert(mp_read_buffer,("NULL mp_read_buffer"));
+		
+		// Ensure the buffer address is 32 byte aligned.
+		Dbg_MsgAssert((((unsigned int)mp_read_buffer ) & 31 ) == 0, ( "file read buffer must be 32 byte aligned" ));
+		
+		// Also ensure the buffer is a multiple of CARD_READ_SIZE (should be, since this is smaller than the smallest sector).
+		Dbg_MsgAssert(( m_card->m_sector_size & ( CARD_READ_SIZE - 1 )) == 0, ( "read buffer must be multiple of CARD_READ_SIZE" ));
+		
+
+		int file_offset=m_read_offset-read_buffer_sector_offset;
+		Dbg_MsgAssert(file_offset%m_card->m_sector_size == 0,("Bad file_offset"));
+		s32 read_result = CARDRead( &m_file_info, mp_read_buffer, m_card->m_sector_size, file_offset);
+		if (read_result != CARD_RESULT_READY)
+		{
+			return 0;
+		}	
+	}
+	else
+	{
+		if (read_buffer_sector_offset==0)
+		{
+			Dbg_MsgAssert(m_read_offset%m_card->m_sector_size == 0,("Bad m_read_offset"));
+			s32 read_result = CARDRead( &m_file_info, mp_read_buffer, m_card->m_sector_size, m_read_offset);
+			if (read_result != CARD_RESULT_READY)
+			{
+				return 0;
+			}	
+		}	
+	}
+
+
+	int	bytes_remaining_to_read	= len;
+	unsigned char*	p_dest_buffer	= (unsigned char*)buff;
+
+	while (true)
+	{
+		if (bytes_remaining_to_read < read_buffer_left)
+		{
+			if (bytes_remaining_to_read)
+			{
+				// The required data is all in the buffer already so copy it out and return.
+				memcpy(p_dest_buffer,mp_read_buffer+read_buffer_sector_offset,bytes_remaining_to_read);
+				m_read_offset+=bytes_remaining_to_read;
+			}	
+			return len;
+		}
+			
+		// Copy out what is left in the buffer
+		memcpy(p_dest_buffer,mp_read_buffer+read_buffer_sector_offset,read_buffer_left);
+		p_dest_buffer+=read_buffer_left;
+		m_read_offset+=read_buffer_left;
+		bytes_remaining_to_read-=read_buffer_left;
+		
+		if (bytes_remaining_to_read==0)
+		{
+			return len;
+		}
+		
+		
+		Dbg_MsgAssert(m_read_offset%m_card->m_sector_size == 0,("Bad m_read_offset"));
+		s32 read_result = CARDRead( &m_file_info, mp_read_buffer, m_card->m_sector_size, m_read_offset);
+		if (read_result != CARD_RESULT_READY)
+		{
+			return 0;
+		}	
+			
+		read_buffer_left=m_card->m_sector_size;
+		read_buffer_sector_offset=0;
+	}
+			
+	return 0;	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+bool File::Close( void )
+{
+	s32 close_result = CARDClose( &m_file_info );
+	if( close_result == CARD_RESULT_READY )
+	{
+		return true;
+	}
+
+	return false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Mc
+
+
+
+
+
diff --git a/Code/Sys/Profiler.cpp b/Code/Sys/Profiler.cpp
new file mode 100644
index 0000000..419ef7e
--- /dev/null
+++ b/Code/Sys/Profiler.cpp
@@ -0,0 +1,434 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 2002 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SysLib (System Library)									**
+**																			**
+**	Module:			Sys					 									**
+**																			**
+**	File name:		profiler.cpp											**
+**																			**
+**	Description:	Profiler class											**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#ifndef __PLAT_WN32__
+#include 
+#endif // __PLAT_WN32__
+
+#ifdef	__PLAT_NGPS__	
+#include 
+#endif
+#ifdef	__PLAT_NGC__	
+#include 
+#include 
+#include 
+#endif
+
+
+
+#ifdef		__USE_PROFILER__
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace Sys
+{
+
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+Tmr::MicroSeconds	Profiler::s_base_time[ 2 ];
+uint32				Profiler::s_default_color = 0x00ff0000;
+int					Profiler::s_flip = 0;
+#ifdef	__PLAT_NGC__	
+bool				Profiler::s_enabled = true;
+bool				Profiler::s_active = true;
+#else
+bool				Profiler::s_enabled = false;
+bool				Profiler::s_active = false;
+#endif
+uint				Profiler::s_vblanks;
+float				Profiler::s_width;
+float				Profiler::s_height;
+float 				Profiler::s_scale;
+float				Profiler::s_left_x;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+Profiler* CPUProfiler;		  
+Profiler* VUProfiler;		  
+Profiler* DMAProfiler;		  
+
+
+/*****************************************************************************
+**							Private Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+
+Profiler::Profiler( uint line )
+{
+	m_top_y = s_height * line;
+	start_new_frame();			// just ensure everything points to something reasonable
+}
+
+/******************************************************************/
+
+Profiler::~Profiler( void )
+{
+}
+
+/******************************************************************/
+
+void			box(int x0,int y0, int x1, int y1, uint32 color)
+{
+#ifdef	__PLAT_NGPS__	
+	NxPs2::BeginLines2D(0x80000000 + color);
+	NxPs2::DrawLine2D(x0,y0,0, x1,y0,0);
+	NxPs2::DrawLine2D(x1,y0,0, x1,y1,0);
+	NxPs2::DrawLine2D(x1,y1,0, x0,y1,0);
+	NxPs2::DrawLine2D(x0,y1,0, x0,y0,0);
+	NxPs2::EndLines2D();
+#endif
+
+
+
+#ifdef	__PLAT_NGC__	
+//	NsPrim::line ( (float)x0, (float)y0, (float)x1, (float)y0, (GXColor){(color>>0)&0xff,(color>>8)&0xff,(color>>16)&0xff,255} );
+//	NsPrim::line ( (float)x1, (float)y0, (float)x1, (float)y1, (GXColor){(color>>0)&0xff,(color>>8)&0xff,(color>>16)&0xff,255} );
+//	NsPrim::line ( (float)x1, (float)y1, (float)x0, (float)y1, (GXColor){(color>>0)&0xff,(color>>8)&0xff,(color>>16)&0xff,255} );
+//	NsPrim::line ( (float)x0, (float)y1, (float)x0, (float)y0, (GXColor){(color>>0)&0xff,(color>>8)&0xff,(color>>16)&0xff,255} );
+
+	GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){(color>>0)&0xff,(color>>8)&0xff,(color>>16)&0xff,255} );
+
+	// Send coordinates.
+	GX::Begin( GX_LINESTRIP, GX_VTXFMT0, 5 ); 
+		GX::Position3f32( (float)x0, (float)y0, -1.0f );
+		GX::Position3f32( (float)x1, (float)y0, -1.0f );
+		GX::Position3f32( (float)x1, (float)y1, -1.0f );
+		GX::Position3f32( (float)x0, (float)y1, -1.0f );
+		GX::Position3f32( (float)x0, (float)y0, -1.0f );
+	GX::End();
+
+#endif
+
+
+}
+
+void			line(int x0,int y0, int x1, int y1, uint32 color)
+{
+#ifdef	__PLAT_NGPS__	
+	NxPs2::BeginLines2D(0x80000000 + color);
+	NxPs2::DrawLine2D(x0,y0,0, x1,y1,0);
+	NxPs2::EndLines2D();
+#endif
+
+
+#ifdef	__PLAT_NGC__	
+//	NsPrim::line ( (float)x0, (float)y0, (float)x1, (float)y1, (GXColor){(color>>0)&0xff,(color>>8)&0xff,(color>>16)&0xff,255} );
+
+	GX::SetChanAmbColor( GX_COLOR0A0, (GXColor){(color>>0)&0xff,(color>>8)&0xff,(color>>16)&0xff,255} );
+
+	// Send coordinates.
+	GX::Begin( GX_LINES, GX_VTXFMT0, 2 ); 
+		GX::Position3f32( (float)x0, (float)y0, -1.0f );
+		GX::Position3f32( (float)x1, (float)y1, -1.0f );
+	GX::End();
+
+#endif
+
+}
+
+/******************************************************************/
+
+void			box(float x0,float y0, float x1, float y1, uint32 color)
+{
+	box ((int)x0,(int)y0,(int)x1,(int)y1, color);
+}
+
+
+/******************************************************************/
+
+void Profiler::start_new_frame( void )
+{
+	mp_current_context_list 	= m_context_list[ s_flip ];
+	mpp_current_context_stack 	= m_context_stack[ s_flip ];
+	mp_current_context 			= NULL;								// nothing has been pushed yet
+	mp_current_context_count	= &m_context_count[ s_flip ];
+	m_context_stack_depth		= 0;								// nothing on stack
+	m_next_context				= 0;								// start of list (index into mp_current_context_list)
+	m_acc						= false;
+	mp_acc_context				= NULL;
+}
+
+/******************************************************************/
+
+void Profiler::render()
+{
+	if ( s_active )
+	{
+		/* We draw from the buffer that's not being updated this frame */
+		int drawFlip = s_flip ^ 1;
+		
+		SProfileContext *p_context = m_context_list[ drawFlip ];
+		Tmr::MicroSeconds	base_time = s_base_time[ drawFlip ];
+		int n = m_context_count[ drawFlip ];
+		while ( n--)
+		{
+			float startx 	= ( (uint32)(p_context->m_start_time - base_time) ) * s_scale;
+			float endx 		= ( (uint32)(p_context->m_end_time - base_time) ) * s_scale;
+			float top_y 	= m_top_y +( p_context->m_depth)* (s_height+2);
+			box((s_left_x+startx),(top_y),  (s_left_x+endx),( top_y + s_height) , p_context->m_color);   
+			p_context++;
+		}
+	}
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+void Profiler::PushContext( uint32 color, bool acc )
+{
+	
+	if( s_active )
+	{
+		if ( acc && mp_acc_context )
+		{
+			// Want to accumulate to last context.
+			m_acc_time = Tmr::GetTimeInUSeconds(); 
+			m_acc_first = false;
+		}
+		else
+		{
+			// if we have a current context, then push it
+			if (mp_current_context)
+			{
+				Dbg_MsgAssert(m_context_stack_depth < PROF_MAX_LEVELS,("Context stack overflow"));
+				mpp_current_context_stack[m_context_stack_depth++] = mp_current_context;
+			}
+
+			// get a new context from the list
+			Dbg_MsgAssert(m_next_context < PROF_MAX_CONTEXTS,("Context heap overflow"));
+			mp_current_context = &mp_current_context_list[m_next_context++];
+			// and fill it in with the start time, depth and color
+			// (the end time is filled in by the Pop) 
+			mp_current_context->m_start_time = Tmr::GetTimeInUSeconds();
+			mp_current_context->m_depth = m_context_stack_depth;
+			mp_current_context->m_color = color;
+			if ( acc )
+			{
+				mp_acc_context = mp_current_context;
+				m_acc_first = true;
+			}
+		}
+		m_acc = acc;
+	}
+}
+
+
+void Profiler::PushContext( uint8 r, uint8 g, uint8 b, bool acc )
+{
+	uint32 color = (b<<16)+(g<<8)+(r);
+	PushContext(color, acc);
+}
+
+/******************************************************************/
+
+void Profiler::PopContext( void )
+{
+	if( s_active)
+	{
+		if ( m_acc && !m_acc_first )
+		{
+			mp_acc_context->m_end_time += Tmr::GetTimeInUSeconds() - m_acc_time;
+		}
+		else
+		{
+			// set time on the current context		
+			Dbg_MsgAssert(mp_current_context,("Popped one more profiler context than we pushed"));
+
+			mp_current_context->m_end_time = Tmr::GetTimeInUSeconds();
+
+			if ( ! m_context_stack_depth)
+			{
+				// nothing left on stack, so set current context to NULL
+				mp_current_context = NULL;
+				// update the count of valid contexts
+				*mp_current_context_count = m_next_context;
+			}
+			else
+			{
+				// get the last context pushed on the stack
+				mp_current_context = mpp_current_context_stack[--m_context_stack_depth]; 
+			}
+		}
+	}
+}
+
+/******************************************************************/
+
+void Profiler::sSetUp( uint vblanks )
+{
+	
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().ProfilerHeap());
+	
+	s_width = 640 * 0.8;
+	s_height = 448 * 0.01;
+	s_left_x = 640 * 0.1;
+	s_vblanks = vblanks;
+	s_scale = ( s_width * 60 ) / ( 1000000 * s_vblanks );
+
+	VUProfiler  =  new Profiler( 84 );
+	DMAProfiler =  new Profiler( 86 );
+	CPUProfiler =  new Profiler( 88 );
+	
+#ifdef	__PLAT_NGC__	
+	s_active = true;
+	s_enabled = true;
+#else
+	s_active = false;
+	s_enabled = false;
+#endif
+	
+	Mem::Manager::sHandle().PopContext();
+	
+}
+
+/******************************************************************/
+    
+void Profiler::sCloseDown( void )
+{
+	delete VUProfiler;
+	delete DMAProfiler;
+	delete CPUProfiler;
+	VUProfiler = NULL;
+	DMAProfiler = NULL;
+	CPUProfiler = NULL;
+}
+
+/******************************************************************/
+    
+void Profiler::sRender( void )
+{
+	if (s_active && s_enabled)
+	{
+#ifdef	__PLAT_NGC__	
+		NsCamera cam;
+		cam.orthographic( 0, 0, 640, 448 );
+		cam.begin();
+		GX::SetZMode( GX_FALSE, GX_ALWAYS, GX_TRUE );
+
+		GX::SetTexChanTevIndCull( 0, 1, 1, 0, GX_CULL_NONE );
+		GX::SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR0A0, GX_TEXCOORD0, GX_TEXMAP_NULL, GX_COLOR_NULL);
+
+		GX::SetTevAlphaInOpSwap( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_RASA,
+											   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV,
+											   GX_TEV_SWAP0, GX_TEV_SWAP0 );
+
+		GX::SetTevColorInOp( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC,
+										   GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
+
+		GX::SetBlendMode ( GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_CLEAR, GX_TRUE, GX_FALSE, GX_FALSE );
+		GX::SetChanCtrl( GX_COLOR0A0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE );
+		GX::SetChanMatColor( GX_COLOR0A0, (GXColor){255,255,255,255} );
+
+		GX::SetVtxDesc( 1, GX_VA_POS, GX_DIRECT );
+#endif	// __PLAT_NGC__	
+
+		for (uint x = 0; xrender();
+		DMAProfiler->render();
+		CPUProfiler->render();
+	
+#ifdef	__PLAT_NGC__	
+		cam.end();
+#endif	// __PLAT_NGC__	
+	}
+}
+
+/******************************************************************/
+
+void Profiler::sStartFrame( void )
+{
+	
+
+	s_active = s_enabled;
+	s_flip ^= 1;
+	s_base_time[ s_flip ] = Tmr::GetTimeInUSeconds();
+	CPUProfiler->start_new_frame();
+	VUProfiler->start_new_frame();
+	DMAProfiler->start_new_frame();
+}
+/******************************************************************/
+
+void Profiler::sEndFrame( void )
+{
+
+}
+
+/******************************************************************/
+
+void Profiler::sEnable( void )
+{
+	s_enabled = true;
+}
+
+
+/******************************************************************/
+
+void Profiler::sDisable( void )
+{
+	s_enabled = false;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void		Render_Profiler()			// easy function to allow it to be rendered from simpler code 
+{
+	Profiler::sRender();
+}
+    
+} // namespace Sys
+				  
+
+#endif	//		__USE_PROFILER__
+
+
diff --git a/Code/Sys/Profiler.h b/Code/Sys/Profiler.h
new file mode 100644
index 0000000..191a87d
--- /dev/null
+++ b/Code/Sys/Profiler.h
@@ -0,0 +1,192 @@
+
+/*****************************************************************************
+**																			**
+**					   	  Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 2000 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SysLib (System Library)									**
+**																			**
+**	Module:			Sys  													**
+**																			**
+**	File name:		sys/profiler.h											**
+**																			**
+**	Created: 		11/15/00	-	mjb										**
+**																			**
+*****************************************************************************/
+
+#ifndef __SYS_PROFILER_H
+#define __SYS_PROFILER_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+#include 
+#include 
+
+#ifndef	__SYS_MEM_MEMPTR_H
+#include 
+#endif
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#ifndef __PLAT_WN32__
+
+#ifndef		__NOPT_FINAL__
+#ifdef		__PLAT_NGPS__	
+#define		__USE_PROFILER__			// use this one to turn profiling on/off
+#endif		//	__PLAT_NGPS__	
+
+
+#define	PROF_MAX_LEVELS		16		 			// depth of profiler stack
+#define	PROF_MAX_CONTEXTS	512				// number of push/pops possible
+
+namespace Sys
+{
+
+
+void		Render_Profiler();			// easy function to allow it to be rendered from simpler code 
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+// a profiler context represtens a single atomic measurement of time
+// a simgle block of color on the profiler bar, at a particular level
+struct  SProfileContext 
+{
+	uint32							m_color;				// color of this context
+	int								m_depth;				// level in the stack
+	Tmr::MicroSeconds				m_start_time;			// when it started
+	Tmr::MicroSeconds				m_end_time;				// when it ended
+};
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+class  Profiler  : public Spt::Class
+{
+	
+
+public:
+
+
+#ifdef		__USE_PROFILER__
+	void						PushContext( uint8 r, uint8 g, uint8 b, bool acc = false );
+	void						PushContext( uint32 rgba, bool acc = false);
+	void						PopContext( void );
+		
+	static void					sSetUp( uint vblanks );
+	static void					sCloseDown( void );
+	static void					sRender( void );
+
+	static void					sEnable( void );
+	static void					sDisable( void );
+	static void					sStartFrame( void );
+	static void					sEndFrame( void );
+#else
+	inline void					PushContext( uint8 r,
+											 uint8 g,
+											 uint8 b,
+											 bool acc ){}
+	inline void					PushContext( uint32 rgba,
+											 bool acc ){}
+	inline void					PopContext( void ){}
+		
+	inline  static void						sSetUp( uint vblanks ){}
+	inline  static  void					sCloseDown( void ){}
+	inline  static  void					sRender( void ){}
+
+	inline  static  void					sEnable( void ){}
+	inline  static  void					sDisable( void ){}
+	inline  static  void					sStartFrame( void ){}
+	inline  static  void					sEndFrame( void ){}
+#endif	
+
+								~Profiler( void );		 
+
+private:
+#ifdef		__USE_PROFILER__
+								Profiler( uint line );
+								
+	void						render( void );
+	void						start_new_frame( void );
+	SProfileContext				m_context_list[ 2 ][PROF_MAX_CONTEXTS];		// a flat array of all contexts
+	SProfileContext*			mp_current_context_list;					// pointer to one of the above, for speed
+	
+	SProfileContext	*			m_context_stack[ 2 ][PROF_MAX_LEVELS];		// stack of pushed contexts (usually max 3 deep)
+	SProfileContext**			mpp_current_context_stack;					// pointer to one of the above, for speed
+	
+	int							m_context_count[2];
+	int	*						mp_current_context_count;
+	
+	SProfileContext*			mp_current_context;							// current entry in context list (will need to pop back....
+	int							m_context_stack_depth;						// depth in stack (index into m_context_stack[])
+	int							m_next_context;								// index into array
+	
+	float						m_top_y;								// screen coord of top of profiler
+	
+	static Tmr::MicroSeconds	s_base_time[2];
+	static int					s_flip;
+	static bool					s_enabled;
+	static bool					s_active;
+
+
+	static uint					s_vblanks;
+	static float				s_width;
+	static float				s_height;
+	static float 				s_scale;
+	
+	static float				s_left_x;
+
+	static uint32				s_default_color;
+	static Profiler*			s_cpu_profiler;
+
+	bool						m_acc;					// Accumulation?
+	bool						m_acc_first;
+	Tmr::MicroSeconds			m_acc_time;				// Accumulation time.
+	SProfileContext*			mp_acc_context;
+#endif
+};
+
+/*****************************s************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+extern Profiler*	CPUProfiler;
+extern Profiler*	VUProfiler;
+extern Profiler*	DMAProfiler;
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+
+} // namespace Sys
+
+#endif
+#endif	// __PLAT_WN32__
+#endif // __SYS_PROFILER_H
+
diff --git a/Code/Sys/Replay/NGC/p_replay.cpp b/Code/Sys/Replay/NGC/p_replay.cpp
new file mode 100644
index 0000000..c262694
--- /dev/null
+++ b/Code/Sys/Replay/NGC/p_replay.cpp
@@ -0,0 +1,147 @@
+#if 0
+#include 
+#include 
+#include 
+
+#define MAX_BUFFER (20*1024)
+#define MAX_FILL_BUFFER (8*1024)
+
+namespace Replay
+{
+
+// Must be a multiple of REPLAY_BUFFER_CHUNK_SIZE
+#define REPLAY_BUFFER_SIZE 1048576
+//#define REPLAY_BUFFER_SIZE ( REPLAY_BUFFER_CHUNK_SIZE * 96 )
+//#define REPLAY_BUFFER_SIZE (20480*4)
+
+static uint32 sp_buffer=0;
+
+// Allocates the big buffer for storing replays. Never gets deallocated. (Currently)
+void AllocateBuffer()
+{
+	if (sp_buffer)
+	{
+		return;
+	}	
+	
+//	sp_buffer=(uint8*)Mem::Malloc(REPLAY_BUFFER_SIZE);
+	sp_buffer = NsARAM::alloc( REPLAY_BUFFER_SIZE );
+	Dbg_MsgAssert(sp_buffer,("Could not allocate replay buffer"));
+}
+
+void DeallocateBuffer()
+{
+	if (sp_buffer)
+	{
+		NsARAM::free( sp_buffer );
+		sp_buffer=0;
+	}
+}
+
+uint32 GetBufferSize()
+{
+	return REPLAY_BUFFER_SIZE;
+}
+
+bool BufferAllocated()
+{
+	if (sp_buffer)
+	{
+		return true;
+	}
+	return false;
+}
+	
+// Reads numBytes out of the buffer starting at offset bufferOffset, into p_dest.
+// Asserts if there are not enough source bytes.
+void ReadFromBuffer(uint8 *p_dest, int bufferOffset, int numBytes)
+{
+//	OSReport( "READ: 0x%08x - %d bytes\n", bufferOffset, numBytes );
+	Dbg_MsgAssert(sp_buffer,("Replay buffer has not been allocated"));
+	Dbg_MsgAssert(p_dest,("NULL p_dest sent to Replay::Read()"));
+	Dbg_MsgAssert(bufferOffset>=0 && bufferOffset MAX_BUFFER )
+	{
+		OSReport( "Transfer too large: %d - max = %d\n", aligned_size, MAX_BUFFER );
+		while (1 == 1);
+	}
+
+	NsDMA::toMRAM( buffer, sp_buffer+aligned_off, aligned_size );
+	memcpy( p_dest, &buffer[bufferOffset-aligned_off], numBytes );
+}
+
+// Writes numBytes into the buffer. Asserts if there is not enough space.
+void WriteIntoBuffer(uint8 *p_source, int bufferOffset, int numBytes)
+{
+//	OSReport( "WRITE: 0x%08x - %d bytes\n", bufferOffset, numBytes );
+
+	Dbg_MsgAssert(sp_buffer,("Replay buffer has not been allocated"));
+	Dbg_MsgAssert(p_source,("NULL p_source sent to Replay::Write()"));
+	Dbg_MsgAssert(bufferOffset>=0 && bufferOffset MAX_BUFFER )
+	{
+		OSReport( "Transfer too large: %d - max = %d\n", aligned_size, MAX_BUFFER );
+		while (1 == 1);
+	}
+
+	NsDMA::toMRAM( buffer, sp_buffer+aligned_off, aligned_size );
+	memcpy( &buffer[bufferOffset-aligned_off], p_source, numBytes );
+	NsDMA::toARAM( sp_buffer+aligned_off, buffer, aligned_size );
+}
+
+void FillBuffer(int bufferOffset, int numBytes, uint8 value)
+{
+//	OSReport( "FILL: 0x%08x - %d bytes, 0x%02x\n", bufferOffset, numBytes, value );
+	Dbg_MsgAssert(sp_buffer,("Replay buffer has not been allocated"));
+	Dbg_MsgAssert(bufferOffset>=0 && bufferOffset MAX_FILL_BUFFER )
+//		{
+//			NsDMA::toARAM( sp_buffer+bufferOffset+off, buffer, MAX_FILL_BUFFER );
+//			size -= MAX_FILL_BUFFER;
+//			off += MAX_FILL_BUFFER;
+//		}
+//	}
+//	else
+	{
+		while ( size > MAX_FILL_BUFFER )
+		{
+			WriteIntoBuffer( buffer, bufferOffset+off, MAX_FILL_BUFFER );
+			size -= MAX_FILL_BUFFER;
+			off += MAX_FILL_BUFFER;
+		}
+		if ( size ) WriteIntoBuffer( buffer, bufferOffset+off, size ); 
+	}
+
+}
+
+}  // namespace Replay
+
+#endif
diff --git a/Code/Sys/Replay/NGPS/p_replay.cpp b/Code/Sys/Replay/NGPS/p_replay.cpp
new file mode 100644
index 0000000..d212795
--- /dev/null
+++ b/Code/Sys/Replay/NGPS/p_replay.cpp
@@ -0,0 +1,81 @@
+#include 
+#if 0
+namespace Replay
+{
+
+// Must be a multiple of REPLAY_BUFFER_CHUNK_SIZE
+//#define REPLAY_BUFFER_SIZE 1048576
+#define REPLAY_BUFFER_SIZE 200704			// Mick - changed back to this, as the old levels are running out of memory
+//#define REPLAY_BUFFER_SIZE (20480*4)
+
+static uint8 *sp_buffer=NULL;
+
+// Allocates the big buffer for storing replays. Never gets deallocated. (Currently)
+void AllocateBuffer()
+{
+	if (sp_buffer)
+	{
+		return;
+	}	
+	
+	sp_buffer=(uint8*)Mem::Malloc(REPLAY_BUFFER_SIZE);
+	Dbg_MsgAssert(sp_buffer,("Could not allocate replay buffer"));
+}
+
+void DeallocateBuffer()
+{
+	if (sp_buffer)
+	{
+		Mem::Free(sp_buffer);
+		sp_buffer=NULL;
+	}
+}
+
+uint32 GetBufferSize()
+{
+	return REPLAY_BUFFER_SIZE;
+}
+
+bool BufferAllocated()
+{
+	if (sp_buffer)
+	{
+		return true;
+	}
+	return false;
+}
+	
+// Reads numBytes out of the buffer starting at offset bufferOffset, into p_dest.
+// Asserts if there are not enough source bytes.
+void ReadFromBuffer(uint8 *p_dest, int bufferOffset, int numBytes)
+{
+	Dbg_MsgAssert(sp_buffer,("Replay buffer has not been allocated"));
+	Dbg_MsgAssert(p_dest,("NULL p_dest sent to Replay::Read()"));
+	Dbg_MsgAssert(bufferOffset>=0 && bufferOffset=0 && bufferOffset=0 && bufferOffset
+
+namespace Replay
+{
+
+// Must be a multiple of REPLAY_BUFFER_CHUNK_SIZE
+#define REPLAY_BUFFER_SIZE 200704
+
+static uint8 *sp_buffer=NULL;
+
+// Allocates the big buffer for storing replays. Never gets deallocated. (Currently)
+void AllocateBuffer()
+{
+	if (sp_buffer)
+	{
+		return;
+	}	
+	
+	sp_buffer=(uint8*)Mem::Malloc(REPLAY_BUFFER_SIZE);
+	Dbg_MsgAssert(sp_buffer,("Could not allocate replay buffer"));
+}
+
+void DeallocateBuffer()
+{
+	if (sp_buffer)
+	{
+		Mem::Free(sp_buffer);
+		sp_buffer=NULL;
+	}
+}
+
+uint32 GetBufferSize()
+{
+	return REPLAY_BUFFER_SIZE;
+}
+
+bool BufferAllocated()
+{
+	if (sp_buffer)
+	{
+		return true;
+	}
+	return false;
+}
+	
+// Reads numBytes out of the buffer starting at offset bufferOffset, into p_dest.
+// Asserts if there are not enough source bytes.
+void ReadFromBuffer(uint8 *p_dest, int bufferOffset, int numBytes)
+{
+	Dbg_MsgAssert(sp_buffer,("Replay buffer has not been allocated"));
+	Dbg_MsgAssert(p_dest,("NULL p_dest sent to Replay::Read()"));
+	Dbg_MsgAssert(bufferOffset>=0 && bufferOffset=0 && bufferOffset=0 && bufferOffset
+
+#if 0
+
+namespace Replay
+{
+
+// Must be a multiple of REPLAY_BUFFER_CHUNK_SIZE
+#define REPLAY_BUFFER_SIZE 1048576
+
+static uint8 *sp_buffer=NULL;
+
+// Allocates the big buffer for storing replays. Never gets deallocated. (Currently)
+void AllocateBuffer()
+{
+	if (sp_buffer)
+	{
+		return;
+	}	
+	
+	sp_buffer=(uint8*)Mem::Malloc(REPLAY_BUFFER_SIZE);
+	Dbg_MsgAssert(sp_buffer,("Could not allocate replay buffer"));
+}
+
+void DeallocateBuffer()
+{
+	if (sp_buffer)
+	{
+		Mem::Free(sp_buffer);
+		sp_buffer=NULL;
+	}
+}
+
+uint32 GetBufferSize()
+{
+	return REPLAY_BUFFER_SIZE;
+}
+
+bool BufferAllocated()
+{
+	if (sp_buffer)
+	{
+		return true;
+	}
+	return false;
+}
+	
+// Reads numBytes out of the buffer starting at offset bufferOffset, into p_dest.
+// Asserts if there are not enough source bytes.
+void ReadFromBuffer(uint8 *p_dest, int bufferOffset, int numBytes)
+{
+	Dbg_MsgAssert(sp_buffer,("Replay buffer has not been allocated"));
+	Dbg_MsgAssert(p_dest,("NULL p_dest sent to Replay::Read()"));
+	Dbg_MsgAssert(bufferOffset>=0 && bufferOffset=0 && bufferOffset=0 && bufferOffset
+
+#if __USE_REPLAYS__
+
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+// disabling replays while we refactor
+#define DISABLE_REPLAYS
+//#define CHECK_TOKEN_USAGE
+
+namespace CFuncs
+{
+	bool CheckButton(Inp::Handler< Mdl::FrontEnd >* pHandler, uint32 button);
+}	
+
+namespace Replay
+{
+
+DefineSingletonClass( Manager, "Replay Manager" )
+
+// These are hard coded rather than wasting space in the replay data. It would waste quite
+// a bit of space since there are lots of hovering cash icons. They all appear to use the same
+// parameters anyway.
+#define HOVER_ROTATE_SPEED 250.0f
+#define HOVER_AMPLITUDE 10.0f
+#define HOVER_PERIOD 1000
+#define HOVER_PERIOD_RND 100
+
+static uint32 sLevelStructureName=0;
+
+static uint32 sNextPlaybackFrameOffset=0;
+static bool sBufferFilling=true;
+static bool sReachedEndOfReplay=false;
+static bool sReplayPaused=false;
+static bool sNeedToInitialiseVibration=false;
+static bool sTrickTextGotCleared=false;
+
+static uint32 sBigBufferStartOffset=0;
+static uint32 sBigBufferEndOffset=0;
+
+// This is a small buffer where the replay info for one frame is stored.
+// It is then appended to the big cyclic buffer by sWriteFrameBufferToBigBuffer
+#define FRAME_BUFFER_SIZE 20480
+static uint8 spFrameBuffer[FRAME_BUFFER_SIZE];
+static uint8 *spFrameBufferPos=NULL;
+
+enum EReplayMode
+{
+	NONE,
+	RECORD,
+	PLAYBACK
+};
+static EReplayMode sMode=NONE;
+	
+enum EPointerType
+{
+	UNDEFINED,
+	CMOVINGOBJECT,
+	CSCREENELEMENT,
+};
+
+enum ESpecialIDs
+{
+	ID_SKATER=0,
+	ID_CAMERA=1,
+};
+	
+enum EAnimStartEndType
+{
+	START_TO_END,
+	END_TO_START,
+	SPECIFIC_START_END_TIMES
+};
+
+enum EReplayToken
+{
+	BLANK=0,
+	FRAME_START,
+	CREATE_OBJECT,
+	KILL_OBJECT,
+	OBJECT_ID,
+	MODEL_NAME,
+	SKELETON_NAME,
+	PROFILE_NAME,
+	SECTOR_NAME,
+	ANIM_SCRIPT_NAME,
+	SET_SCALE,
+	SET_POSITION,
+	SET_POSITION_ANGLES,	
+	SET_VELOCITY,
+	SET_ANGLE_X,
+	SET_ANGLE_Y,
+	SET_ANGLE_Z,
+	SET_ANGULAR_VELOCITY_X,
+	SET_ANGULAR_VELOCITY_Y,
+	SET_ANGULAR_VELOCITY_Z,
+	
+	PRIMARY_ANIM_CONTROLLER_CHANGES,
+	DEGENERATE_ANIM_CONTROLLER_CHANGES,
+	
+	PLAY_POSITIONAL_SOUND_EFFECT,
+	PLAY_SKATER_SOUND_EFFECT,
+	PLAY_SOUND,
+	
+	PLAY_LOOPING_SOUND,
+	STOP_LOOPING_SOUND,
+	PITCH_MIN,
+	PITCH_MAX,
+	
+	SCREEN_BLUR,
+	SCREEN_FLASH,
+	
+	SPARKS_ON,
+	SPARKS_OFF,
+	
+	TRICK_TEXT,
+	TRICK_TEXT_PULSE,
+	TRICK_TEXT_COUNTDOWN,
+	TRICK_TEXT_LANDED,
+	TRICK_TEXT_BAIL,
+	
+	SCORE_POT_TEXT,
+	
+	PANEL_MESSAGE,
+	
+	SET_ATOMIC_STATES,
+		
+	MANUAL_METER_ON,
+	MANUAL_METER_OFF,
+	BALANCE_METER_ON,
+	BALANCE_METER_OFF,
+	
+	FLIP,
+	UNFLIP,
+	
+	MODEL_ACTIVE,
+	MODEL_INACTIVE,
+	
+	PAD_VIBRATION,
+	PLAY_POSITIONAL_STREAM,
+	PLAY_STREAM,
+	STOP_STREAM,
+	
+	SET_CAR_ROTATION_X,
+	SET_CAR_ROTATION_X_VEL,
+	SET_WHEEL_ROTATION_X,
+	SET_WHEEL_ROTATION_X_VEL,
+	SET_WHEEL_ROTATION_Y,
+	SET_WHEEL_ROTATION_Y_VEL,
+	
+	HOVERING,
+	
+	SECTOR_ACTIVE,
+	SECTOR_INACTIVE,
+
+	SECTOR_VISIBLE,
+	SECTOR_INVISIBLE,
+	
+	SHATTER_PARAMS,
+	SHATTER_ON,
+	SHATTER_OFF,
+
+	TEXTURE_SPLAT,
+	
+	SCRIPT_CALL,
+	SKATER_SCRIPT_CALL,
+	
+	PAUSE_SKATER,
+	UNPAUSE_SKATER,
+		
+	// Keep the total number of tokens less than 256, cos they're stored in a byte.
+	NUM_REPLAY_TOKEN_TYPES
+};
+
+class CPosTracker
+{
+	float m_calculated_last;
+	float m_vel;
+	float m_actual_last;
+	
+public:
+	CPosTracker();
+	~CPosTracker();
+	
+	void WriteChanges(float newVal, EReplayToken setToken, EReplayToken velToken, float tolerance=0.005f);
+	float GetActualLast() {return m_actual_last;}
+};
+
+class CSkaterTrackingInfo
+{
+public:
+	CSkaterTrackingInfo();
+	~CSkaterTrackingInfo();
+
+	void Reset();
+
+    enum
+    {
+        NUM_DEGENERATE_ANIMS = 3
+    };
+    Gfx::CAnimChannel m_degenerateControllers[NUM_DEGENERATE_ANIMS];
+	
+	bool m_playing_looping_sound;
+	uint32 m_looping_sound_checksum;
+	float m_pitch_min;
+	float m_pitch_max;
+};
+
+CSkaterTrackingInfo::CSkaterTrackingInfo()
+{
+	Reset();
+}
+
+CSkaterTrackingInfo::~CSkaterTrackingInfo()
+{
+}
+
+void CSkaterTrackingInfo::Reset()
+{
+	for (int i=0; i
+{
+public:	
+	CTrackingInfo();
+	~CTrackingInfo();
+
+	CTrackingInfo *mpNext;
+	CTrackingInfo *mpPrevious;
+	
+	void SetMovingObject(Obj::CMovingObject *p_ob);
+	void SetSkaterCamera();
+
+	void RecordPositionAndAngleChanges(Mth::Vector &actual_pos, Mth::Vector &actual_angles, bool exact=false);
+
+	uint32 m_id;
+	EPointerType mPointerType;
+	Obj::CSmtPtr mpMovingObject;
+		
+	uint32 mFlags;
+	float mScale;
+	
+	Mth::Vector mReplayVel;
+	Mth::Vector mReplayLastPos;
+	Mth::Vector mActualLastPos;
+
+	CPosTracker mTrackerAngleX;
+	CPosTracker mTrackerAngleY;
+	CPosTracker mTrackerAngleZ;
+
+	Mth::Matrix mLastMatrix;
+		
+	CPosTracker mTrackerCarRotationX;
+	CPosTracker mTrackerWheelRotationX;
+	CPosTracker mTrackerWheelRotationY;
+
+	Gfx::CAnimChannel m_primaryController;
+	
+	// Made this static because only the skater needs these members, and there is only one skater.
+	// Need to keep the CTrackingInfo class as small as possible because a pool of 300 of them exists.
+	static CSkaterTrackingInfo sSkaterTrackingInfo;
+	
+	static int sScreenBlurTracker;
+	
+};	
+CSkaterTrackingInfo CTrackingInfo::sSkaterTrackingInfo;
+int CTrackingInfo::sScreenBlurTracker=0;
+
+
+static CTrackingInfo *spTrackingInfoHead=NULL;
+static Lst::HashTable sTrackingInfoHashTable(8);
+
+static CDummy *spStartStateDummies=NULL;
+static CDummy *spReplayDummies=NULL;
+
+static Lst::HashTable sObjectStartStateHashTable(8);
+static Lst::HashTable sReplayDummysHashTable(8);
+
+static SGlobalStates sStartState;
+static SGlobalStates sCurrentState;
+}
+
+DefinePoolableClass(Replay::CTrackingInfo);
+
+namespace Replay
+{
+
+#define MAX_PANEL_MESSAGE_SIZE 200
+
+static void sDeleteObjectTrackingInfo();
+static Lst::HashTable *sGetDummyHashTable(EDummyList whichList);
+static CDummy *sGetDummyListHeadPointer(EDummyList whichList);
+static void sSetDummyListHeadPointer(EDummyList whichList, CDummy *p_dummy);
+static int sCountStartStateDummies();
+static void sStopReplayDummyLoopingSounds();
+static uint32 sReadAnimControllerChanges(uint32 offset, Gfx::CAnimChannel *p_anim_controller);
+static uint32 sReadFrame(uint32 offset, bool *p_nothingFollows, EDummyList whichList);
+static void sMakeEnoughSpace(uint32 frameSize);
+static void sWriteFrameBufferToBigBuffer();
+static void sWriteSingleToken(EReplayToken token);
+static void sWriteUint8(uint8 v);
+static void sWriteUint32(EReplayToken token, uint32 v);
+static void sWriteUint32(uint32 v);
+static void sWriteFloat(EReplayToken token, float f);
+static void sWriteFloat(float f);
+static void sWriteVector(EReplayToken token, Mth::Vector &v);
+//static void sWriteString(EReplayToken token, const char *p_string);
+//static uint8 sWriteCAnimChannelChanges(const Gfx::CAnimChannel *p_a, const Gfx::CAnimChannel *p_b);
+static bool sIsFlipped(Obj::CMovingObject *p_movingObject);
+static void sRecordSkaterCamera(CTrackingInfo *p_info);
+static void sRecordCMovingObject(CTrackingInfo *p_info);
+static void sRecord();
+static void sPlayback(bool display=true);
+static void sEnsureTrackerExists( Obj::CObject *p_ob, void *p_data );
+static void sEnsureCameraTrackerExists();
+static void sHideForReplayPlayback( Obj::CObject *p_ob, void *p_data );
+static void sRestoreAfterReplayPlayback( Obj::CObject *p_ob, void *p_data );
+static void sDeleteDummies(EDummyList whichList);
+static void sUnpauseCertainScreenElements();
+static void sSwitchOffVibrations();
+static void sClearLastPanelMessage();
+static void sClearTrickAndScoreText();
+static Obj::CSkater *sGetSkater();
+static Mdl::Score *sGetSkaterScoreObject();
+#ifdef CHECK_TOKEN_USAGE
+static const char *sGetTokenName(EReplayToken token);
+#endif
+
+SGlobalStates::SGlobalStates()
+{
+	Reset();
+}
+
+SGlobalStates::~SGlobalStates()
+{
+}
+
+void SGlobalStates::Reset()
+{
+	mBalanceMeterStatus=false;
+	mBalanceMeterValue=0.0f;
+
+	mManualMeterStatus=false;
+	mManualMeterValue=0.0f;
+	
+	int i;
+	for (i=0; i tolerance)
+	{
+		m_calculated_last=newVal;
+		sWriteFloat(setToken,newVal);
+		
+		m_vel=newVal-m_actual_last;
+		sWriteFloat(velToken,m_vel);
+	}
+	else
+	{
+		m_calculated_last=predicted;
+	}	
+    m_actual_last=newVal;
+}
+
+CTrackingInfo::CTrackingInfo()
+{
+	m_id=0xffffffff;		
+	mPointerType=UNDEFINED;
+	mpMovingObject=NULL;
+	mFlags=0;
+	mScale=1.0f;
+			
+	mReplayVel.Set();
+	mReplayLastPos.Set();
+	mActualLastPos.Set();
+
+	// Making sure this won't match the object's matrix on the first frame so that
+	// changes will get recorded for the first frame.
+	mLastMatrix[0][0]=0.0f;	
+
+	m_primaryController.Reset();
+	
+	mpNext=spTrackingInfoHead;
+	mpPrevious=NULL;
+	if (mpNext)
+	{
+		mpNext->mpPrevious=this;
+	}
+	spTrackingInfoHead=this;
+}
+
+CTrackingInfo::~CTrackingInfo()
+{
+	if (mpPrevious) mpPrevious->mpNext=mpNext;
+	if (mpNext) mpNext->mpPrevious=mpPrevious;
+	if (!mpPrevious) spTrackingInfoHead=mpNext;
+	
+	sTrackingInfoHashTable.FlushItem((uint32)m_id);
+}
+
+void CTrackingInfo::SetMovingObject(Obj::CMovingObject *p_ob)
+{
+	mPointerType=CMOVINGOBJECT;
+	mpMovingObject=p_ob;
+	m_id=p_ob->GetID();
+	
+	// Use p_ob as the hash key so that the CTrackingInfo for any given CMovingObject
+	// can be quickly looked up.
+	sTrackingInfoHashTable.PutItem((uint32)m_id,this);
+}	
+
+void CTrackingInfo::SetSkaterCamera()
+{
+	Dbg_MsgAssert(mPointerType==UNDEFINED,("Expected mPointerType to be UNDEFINED"));
+	Dbg_MsgAssert(mpMovingObject==NULL,("Expected mpMovingObject to be NULL"));
+	mpMovingObject=NULL;
+	
+	m_id=ID_CAMERA;
+	sTrackingInfoHashTable.PutItem((uint32)m_id,this);
+}
+
+void CTrackingInfo::RecordPositionAndAngleChanges(Mth::Vector &actual_pos, Mth::Vector &actual_angles, bool exact)
+{
+	if (exact)
+	{
+		sWriteSingleToken(SET_POSITION_ANGLES);
+		sWriteFloat(actual_pos[X]);
+		sWriteFloat(actual_pos[Y]);
+		sWriteFloat(actual_pos[Z]);
+		sWriteFloat(actual_angles[X]);
+		sWriteFloat(actual_angles[Y]);
+		sWriteFloat(actual_angles[Z]);
+		return;
+	}
+		
+	// Compare the actual position with that predicted by the last
+	// position and velocity
+	Mth::Vector predicted_pos=mReplayLastPos+mReplayVel;
+	Mth::Vector d=actual_pos-predicted_pos;
+	
+	if (fabs(d[X]) > 0.1f || fabs(d[Y]) > 0.1f || fabs(d[Z]) > 0.1f)
+	{
+		mReplayLastPos=actual_pos;
+		sWriteVector(SET_POSITION,actual_pos);
+
+		
+		mReplayVel=actual_pos-mActualLastPos;
+		sWriteVector(SET_VELOCITY,mReplayVel);
+	}
+	else
+	{
+		mReplayLastPos=predicted_pos;
+	}	
+    mActualLastPos=actual_pos;
+
+
+	// Do the angles.
+	mTrackerAngleX.WriteChanges(actual_angles[X],SET_ANGLE_X,SET_ANGULAR_VELOCITY_X);
+	mTrackerAngleY.WriteChanges(actual_angles[Y],SET_ANGLE_Y,SET_ANGULAR_VELOCITY_Y);
+	mTrackerAngleZ.WriteChanges(actual_angles[Z],SET_ANGLE_Z,SET_ANGULAR_VELOCITY_Z);
+}
+
+static void sDeleteObjectTrackingInfo()
+{
+	CTrackingInfo *p_info=spTrackingInfoHead;
+	while (p_info)
+	{
+		CTrackingInfo *p_next=p_info->mpNext;
+		delete p_info;
+		p_info=p_next;
+	}	
+}
+
+static Lst::HashTable *sGetDummyHashTable(EDummyList whichList)
+{
+	if (whichList==START_STATE_DUMMY_LIST)
+	{
+		return &sObjectStartStateHashTable;
+	}
+	else
+	{
+		return &sReplayDummysHashTable;
+	}
+}
+		
+CDummy *GetFirstStartStateDummy()
+{
+	return spStartStateDummies;
+}
+	
+static CDummy *sGetDummyListHeadPointer(EDummyList whichList)
+{
+	if (whichList==START_STATE_DUMMY_LIST)
+	{
+		return spStartStateDummies;
+	}
+	else	
+	{
+		return spReplayDummies;
+	}
+}
+
+static void sSetDummyListHeadPointer(EDummyList whichList, CDummy *p_dummy)
+{
+	if (whichList==START_STATE_DUMMY_LIST)
+	{
+		spStartStateDummies=p_dummy;
+	}
+	else
+	{
+		spReplayDummies=p_dummy;
+	}
+}
+		
+static int sCountStartStateDummies()
+{
+	int n=0;
+	CDummy *p_dummy=spStartStateDummies;
+	while (p_dummy)
+	{
+		++n;
+		p_dummy=p_dummy->mpNext;
+	}
+	return n;	
+}
+
+#ifdef CHECK_TOKEN_USAGE
+static const char *sGetTokenName(EReplayToken token)
+{
+	switch (token)
+	{
+	case BLANK                               :return "BLANK"; break;
+	case FRAME_START                         :return "FRAME_START"; break;                       
+	case CREATE_OBJECT                       :return "CREATE_OBJECT"; break;                     
+	case KILL_OBJECT                         :return "KILL_OBJECT"; break;                       
+	case OBJECT_ID                           :return "OBJECT_ID"; break;                         
+	case MODEL_NAME                          :return "MODEL_NAME"; break;                        
+	case SKELETON_NAME                       :return "SKELETON_NAME"; break;                     
+	case PROFILE_NAME                        :return "PROFILE_NAME"; break;                      
+	case SECTOR_NAME                         :return "SECTOR_NAME"; break;                       
+	case ANIM_SCRIPT_NAME                    :return "ANIM_SCRIPT_NAME"; break;                  
+	case SET_POSITION                        :return "SET_POSITION"; break;                      
+	case SET_POSITION_ANGLES	             :return "SET_POSITION_ANGLES"; break;               
+	case SET_VELOCITY                        :return "SET_VELOCITY"; break;                      
+	case SET_ANGLE_X                         :return "SET_ANGLE_X"; break;                       
+	case SET_ANGLE_Y                         :return "SET_ANGLE_Y"; break;                       
+	case SET_ANGLE_Z                         :return "SET_ANGLE_Z"; break;                       
+	case SET_ANGULAR_VELOCITY_X              :return "SET_ANGULAR_VELOCITY_X"; break;            
+	case SET_ANGULAR_VELOCITY_Y              :return "SET_ANGULAR_VELOCITY_Y"; break;            
+	case SET_ANGULAR_VELOCITY_Z              :return "SET_ANGULAR_VELOCITY_Z"; break;            
+	case PRIMARY_ANIM_CONTROLLER_CHANGES     :return "PRIMARY_ANIM_CONTROLLER_CHANGES"; break;   
+	case DEGENERATE_ANIM_CONTROLLER_CHANGES  :return "DEGENERATE_ANIM_CONTROLLER_CHANGES"; break;
+	case PLAY_POSITIONAL_SOUND_EFFECT        :return "PLAY_POSITIONAL_SOUND_EFFECT"; break;      
+	case PLAY_SKATER_SOUND_EFFECT            :return "PLAY_SKATER_SOUND_EFFECT"; break;          
+	case PLAY_SOUND							 :return "PLAY_SOUND"; break;
+	case PLAY_LOOPING_SOUND					 :return "PLAY_LOOPING_SOUND"; break;
+	case STOP_LOOPING_SOUND					 :return "STOP_LOOPING_SOUND"; break;
+	case PITCH_MIN							 :return "PITCH_MIN"; break;
+	case PITCH_MAX							 :return "PITCH_MAX"; break;
+	case SCREEN_BLUR                         :return "SCREEN_BLUR"; break;                       
+	case SCREEN_FLASH						 :return "SCREEN_FLASH"; break;
+	case SPARKS_ON                           :return "SPARKS_ON"; break;                         
+	case SPARKS_OFF                          :return "SPARKS_OFF"; break;                        
+	case TRICK_TEXT                          :return "TRICK_TEXT"; break;                        
+	case TRICK_TEXT_PULSE                    :return "TRICK_TEXT_PULSE"; break;                  
+	case TRICK_TEXT_COUNTDOWN                :return "TRICK_TEXT_COUNTDOWN"; break;              
+	case TRICK_TEXT_LANDED                   :return "TRICK_TEXT_LANDED"; break;                 
+	case TRICK_TEXT_BAIL                     :return "TRICK_TEXT_BAIL"; break;                   
+	case SCORE_POT_TEXT                      :return "SCORE_POT_TEXT"; break;                    
+	case PANEL_MESSAGE                       :return "PANEL_MESSAGE"; break;                     
+	case SET_ATOMIC_STATES                   :return "SET_ATOMIC_STATES"; break;                  
+	case MANUAL_METER_ON                     :return "MANUAL_METER_ON"; break;                   
+	case MANUAL_METER_OFF                    :return "MANUAL_METER_OFF"; break;                  
+	case BALANCE_METER_ON                    :return "BALANCE_METER_ON"; break;                  
+	case BALANCE_METER_OFF                   :return "BALANCE_METER_OFF"; break;                 
+	case FLIP                                :return "FLIP"; break;                              
+	case UNFLIP                              :return "UNFLIP"; break;                            
+	case MODEL_ACTIVE                        :return "MODEL_ACTIVE"; break;                              
+	case MODEL_INACTIVE                      :return "MODEL_INACTIVE"; break;                              
+	case PAD_VIBRATION                       :return "PAD_VIBRATION"; break;                     
+	case PLAY_POSITIONAL_STREAM              :return "PLAY_POSITIONAL_STREAM"; break;            
+	case PLAY_STREAM                         :return "PLAY_STREAM"; break;                       
+	case STOP_STREAM                         :return "STOP_STREAM"; break;                       
+	case SET_CAR_ROTATION_X                  :return "SET_CAR_ROTATION_X"; break;                
+	case SET_CAR_ROTATION_X_VEL              :return "SET_CAR_ROTATION_X_VEL"; break;            
+	case SET_WHEEL_ROTATION_X                :return "SET_WHEEL_ROTATION_X"; break;              
+	case SET_WHEEL_ROTATION_X_VEL            :return "SET_WHEEL_ROTATION_X_VEL"; break;          
+	case SET_WHEEL_ROTATION_Y                :return "SET_WHEEL_ROTATION_Y"; break;              
+	case SET_WHEEL_ROTATION_Y_VEL            :return "SET_WHEEL_ROTATION_Y_VEL"; break;          
+	case HOVERING							 :return "HOVERING"; break;          
+	case SECTOR_ACTIVE						 :return "SECTOR_ACTIVE"; break;          
+	case SECTOR_INACTIVE					 :return "SECTOR_INACTIVE"; break;          
+	case SECTOR_VISIBLE						 :return "SECTOR_VISIBLE"; break;          
+	case SECTOR_INVISIBLE					 :return "SECTOR_INVISIBLE"; break;          
+	case SHATTER_PARAMS					 	 :return "SHATTER_PARAMS"; break;          
+	case SHATTER_ON					 		 :return "SHATTER_ON"; break;          
+	case SHATTER_OFF				 		 :return "SHATTER_OFF"; break;          
+	case TEXTURE_SPLAT						 :return "TEXTURE_SPLAT"; break;
+	case SCRIPT_CALL						 :return "SCRIPT_CALL"; break;
+	case SKATER_SCRIPT_CALL					 :return "SKATER_SCRIPT_CALL"; break;
+	case PAUSE_SKATER						 :return "PAUSE_SKATER"; break;
+	case UNPAUSE_SKATER						 :return "UNPAUSE_SKATER"; break;
+	default									 :return "UNKNOWN"; break;
+	}
+}
+#endif
+
+// When the game is in paused mode, which it will be when running a replay, certain screen
+// elements such as the balance meters will not update properly.
+// This function will unpause them so that they work during the replay.
+static void sUnpauseCertainScreenElements()
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	Front::CSpriteElement *p_balance_meter = (Front::CSpriteElement *) p_manager->GetElement(0xa4db8a4b + sGetSkater()->GetHeapIndex()).Convert(); // "the_balance_meter"
+	Dbg_MsgAssert(p_balance_meter,("NULL p_balance_meter"));
+	Front::CSpriteElement *p_balance_arrow = (Front::CSpriteElement *) p_balance_meter->GetFirstChild().Convert();
+	
+	p_balance_meter->SetMorphPausedState(false);
+	p_balance_arrow->SetMorphPausedState(false);
+}
+
+static void sSwitchOffVibrations()
+{
+	Obj::CSkater *p_skater=sGetSkater();
+	for (int i=0; iGetDevice()->ActivateActuator(i,0);
+	}	
+}
+
+static Obj::CSkater *sGetSkater()
+{
+	Mdl::Skate * p_skate_mod = Mdl::Skate::Instance();
+	Obj::CSkater *p_skater = p_skate_mod->GetSkater(0);
+	Dbg_MsgAssert(p_skater,("NULL p_skater"));
+	return p_skater;
+}
+	
+static Mdl::Score *sGetSkaterScoreObject()
+{
+	Mdl::Score *p_score=sGetSkater()->GetScoreObject();
+	Dbg_MsgAssert(p_score,("NULL p_score"));
+	return p_score;
+}
+
+static void sDeleteDummies(EDummyList whichList)
+{
+	CDummy *p_dummy=sGetDummyListHeadPointer(whichList);
+	while (p_dummy)
+	{
+		CDummy *p_next=p_dummy->mpNext;
+		delete p_dummy;
+		p_dummy=p_next;
+	}	
+	sGetDummyHashTable(whichList)->FlushAllItems();
+	Dbg_MsgAssert(spReplayDummies==NULL,("Hey! spReplayDummies not NULL !!!"));
+}
+
+bool ScriptDeleteDummies( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	sDeleteDummies(PLAYBACK_DUMMY_LIST);
+	return true;
+}
+	
+CDummy::CDummy(EDummyList whichList, uint32 id)
+{
+	m_list=whichList;
+	SetID(id);
+
+	mpNext=sGetDummyListHeadPointer(m_list);
+	mpPrevious=NULL;
+	if (mpNext)
+	{
+		mpNext->mpPrevious=this;
+	}	
+	sSetDummyListHeadPointer(m_list,this);
+	sGetDummyHashTable(whichList)->PutItem(m_id,this);
+	
+	//m_type = SKATE_TYPE_REPLAY_DUMMY;
+
+	mAtomicStates=0xffffffff;
+
+	mFlags=0;
+	
+	// Add a bit of randomness so that groups of hovering things slowly
+	// go out of phase with each other rather than hovering in unison.
+	mHoverPeriod=HOVER_PERIOD+Mth::Rnd(2*HOVER_PERIOD_RND+1)-HOVER_PERIOD_RND;
+	Dbg_MsgAssert(mHoverPeriod>0,("Bad mHoverPeriod"));
+	
+	m_car_rotation_x=0.0f;
+	m_car_rotation_x_vel=0.0f;
+	m_wheel_rotation_x=0.0f;
+	m_wheel_rotation_x_vel=0.0f;
+	m_wheel_rotation_y=0.0f;
+	m_wheel_rotation_y_vel=0.0f;
+
+	mAnimScriptName=0;
+	mSectorName=0;
+	mScale=1.0f;
+
+	m_looping_sound_id=0;
+	m_looping_sound_checksum=0;
+	m_pitch_min=50.0f;
+	m_pitch_max=120.0f;
+			
+	m_is_displayed=true;
+				
+	// Note: CDummy is derived from CMovingObject which is derived from Spt::Class, so we
+	// get the autozeroing of the members.
+}
+
+CDummy::~CDummy()
+{
+	if (mp_rendered_model && m_id!=0)
+	{
+		// Must call sUninitModel rather than just delete it.
+		Nx::CEngine::sUninitModel(mp_rendered_model);
+    }
+
+    if (mp_skeletonComponent)
+    {
+        delete mp_skeletonComponent;
+		mp_skeletonComponent = NULL;
+    }
+	
+	if (mpPrevious) mpPrevious->mpNext=mpNext;
+	if (mpNext) mpNext->mpPrevious=mpPrevious;
+	if (mpPrevious==NULL)
+	{
+		sSetDummyListHeadPointer(m_list,mpNext);
+	}	
+	
+	sGetDummyHashTable(m_list)->FlushItem(m_id);
+	
+	if (mp_skater_camera)
+	{
+		delete mp_skater_camera;
+//		Obj::CSkater *p_skater=sGetSkater();
+
+		// Replay has finished, so we'd want to switch the camera back to the skater camera
+		// can't do it the old way, need to use
+		
+		printf ("STUBBED:  replay.cpp, line %d  -------- not resetting skater camera\n",__LINE__);											
+		//Nx::CViewportManager::sSetCamera( /*m_skater_number*/0, p_skater->GetSkaterCam() );			
+	}	
+}
+
+CDummy& CDummy::operator=( const CDummy& rhs )
+{
+#if 0
+	Dbg_MsgAssert(strlen(rhs.mpModelName)<=MAX_MODEL_NAME_CHARS,("rhs mpModelName too long ?"));
+	strcpy(mpModelName,rhs.mpModelName);
+	mSkeletonName=rhs.mSkeletonName;
+	mProfileName=rhs.mProfileName;
+	mAnimScriptName=rhs.mAnimScriptName;
+	mSectorName=rhs.mSectorName;
+	mScale=rhs.mScale;
+	
+	mFlags=rhs.mFlags;
+	
+	mHoverPeriod=rhs.mHoverPeriod;
+	
+	// Don't really need to copy m_id and m_pos here, because they are members of
+	// CMovingObject so will get copied by the CMovingObject default assignement operator, I think.
+	// I feel more comfortable doing it here too though.
+	m_id=rhs.m_id;
+	m_pos=rhs.m_pos;
+	m_type=rhs.m_type;
+	
+	m_vel=rhs.m_vel;
+	m_angles=rhs.m_angles;
+	m_ang_vel=rhs.m_ang_vel;
+
+	// This is because for the skater dummy the speed for calculating the looping sound
+	// volume is calculated using m_old_pos. If it were left equal to 0,0,0 it would cause the
+	// looping sound to be played at max volume for the first frame, because it would think
+	// the skater was moving very fast.
+	m_old_pos=m_pos;
+
+	m_car_rotation_x		=rhs.m_car_rotation_x;
+	m_car_rotation_x_vel    =rhs.m_car_rotation_x_vel;
+	m_wheel_rotation_x      =rhs.m_wheel_rotation_x;
+	m_wheel_rotation_x_vel  =rhs.m_wheel_rotation_x_vel;
+	m_wheel_rotation_y      =rhs.m_wheel_rotation_y;
+	m_wheel_rotation_y_vel  =rhs.m_wheel_rotation_y_vel;
+	
+	m_primaryController=rhs.m_primaryController;
+	
+	for (int i=0; impModelName,mpModelName);
+	
+	p_saved_dummy->m_id				=	m_id;
+	p_saved_dummy->m_type			=	m_type;
+	p_saved_dummy->mSkeletonName	=	mSkeletonName;
+	p_saved_dummy->mProfileName		=	mProfileName;
+	p_saved_dummy->mAnimScriptName	=	mAnimScriptName;
+	p_saved_dummy->mSectorName		=	mSectorName;
+	p_saved_dummy->mScale			=	mScale;
+	p_saved_dummy->mFlags			=	mFlags;
+	// Note: The mHoverPeriod is not saved out. No need, it does not need to be restored exactly,
+	// and the CDummy constructor generates a new random value for it.
+	p_saved_dummy->m_pos			=	m_pos;
+	p_saved_dummy->m_vel			=	m_vel;
+	p_saved_dummy->m_angles			=	m_angles;
+	p_saved_dummy->m_ang_vel		=	m_ang_vel;
+	p_saved_dummy->m_car_rotation_x       = m_car_rotation_x;      
+	p_saved_dummy->m_car_rotation_x_vel   = m_car_rotation_x_vel;  
+	p_saved_dummy->m_wheel_rotation_x     = m_wheel_rotation_x;    
+	p_saved_dummy->m_wheel_rotation_x_vel = m_wheel_rotation_x_vel;
+	p_saved_dummy->m_wheel_rotation_y     = m_wheel_rotation_y;    
+	p_saved_dummy->m_wheel_rotation_y_vel = m_wheel_rotation_y_vel;
+	
+	p_saved_dummy->m_primaryController		=	m_primaryController;
+	
+	for (int i=0; im_degenerateControllers[i]=m_degenerateControllers[i];
+	}
+	
+	p_saved_dummy->mAtomicStates		= mAtomicStates;
+
+	// Note: m_looping_sound_id is not saved, since this is a unique id for the instance of the sound.
+	p_saved_dummy->m_looping_sound_checksum = m_looping_sound_checksum;
+	p_saved_dummy->m_pitch_min = m_pitch_min;
+	p_saved_dummy->m_pitch_max = m_pitch_max;
+#endif
+}
+
+void CDummy::UpdateLoopingSound()
+{
+	if (!m_looping_sound_checksum)
+	{
+		return;
+	}
+		
+	float percent_of_max_speed=100.0f;
+	float maxSpeed = 1100.0f;
+	Mth::Vector v=m_pos-m_old_pos;
+	v[Y]=0.0f;
+	float speed = v.Length( ) * 60.0f;
+	if ( fabsf( speed ) < maxSpeed )
+	{
+		percent_of_max_speed=( 100.0f * speed ) / maxSpeed;
+	}
+
+	Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	
+	if (!m_looping_sound_id)
+	{
+		m_looping_sound_id = sfx_manager->PlaySound( m_looping_sound_checksum, 0, 0 );
+	}
+		
+	Sfx::sVolume vol;
+	sfx_manager->SetVolumeFromPos( &vol, m_pos, sfx_manager->GetDropoffDist( m_looping_sound_checksum ) );
+	
+	vol.PercentageAdjustment( percent_of_max_speed );
+
+	float pitch = PERCENT( m_pitch_max - m_pitch_min, percent_of_max_speed );
+	pitch += m_pitch_min;
+	
+	sfx_manager->UpdateLoopingSound(m_looping_sound_id,&vol,pitch);
+}		
+
+void CDummy::Update()
+{
+#if 0
+	if (mFlags&DUMMY_FLAG_HOVERING)
+	{
+		m_angles[Y]+=DEGREES_TO_RADIANS(HOVER_ROTATE_SPEED)/60.0f;
+	}
+	else
+	{
+		m_angles+=m_ang_vel;
+		m_pos+=m_vel;
+	
+		if (m_type==SKATE_TYPE_CAR)
+		{
+			m_car_rotation_x	+= m_car_rotation_x_vel; 
+			m_wheel_rotation_x	+= m_wheel_rotation_x_vel;
+			m_wheel_rotation_y	+= m_wheel_rotation_y_vel;
+		}
+	
+		if (m_list==PLAYBACK_DUMMY_LIST)
+		{
+			Mth::Matrix mat(m_angles[X],m_angles[Y],m_angles[Z]);
+			Obj::CSkater *p_skater=sGetSkater();
+			
+			if (m_id==ID_SKATER)
+			{
+				p_skater->UpdateShadow(m_pos,mat);
+				
+				// Update the skater's pos and display matrix so that the sparks follow the dummy.
+				// The skater's RestoreAfterReplayPlayback function will restore the skater's
+				// original position when the replay ends.
+				p_skater->SetActualDisplayMatrix(mat);
+				p_skater->m_pos=m_pos;
+				
+				UpdateLoopingSound();
+			}	
+			else if (m_id==ID_CAMERA)
+			{
+				if (!mp_skater_camera)
+				{
+					mp_skater_camera=new Gfx::Camera;
+					
+					//mp_skater_camera=new Obj::CSkaterCam(0);
+					//mp_skater_camera->SetMode( Obj::CSkaterCam::SKATERCAM_MODE_NORMAL_MEDIUM, 0.0f );
+					//mp_skater_camera->SetSkater(p_skater);
+					
+					Nx::CViewportManager::sSetCamera( /*m_skater_number*/0, mp_skater_camera );			
+				}		
+				
+				//mp_skater_camera->Update();
+				m_pos[W]=1.0f;
+				mp_skater_camera->SetPos(m_pos);
+				mp_skater_camera->SetMatrix(mat);
+			}
+		}
+	}	
+	
+	m_old_pos=m_pos;
+#endif
+}
+
+void CDummy::CreateModel()
+{
+#if 0
+	Dbg_MsgAssert(mp_rendered_model==NULL,("Called CreateModel() when model already exists"));
+
+	if (m_id==ID_SKATER)
+	{
+		Obj::CSkater *p_skater=sGetSkater();
+		mp_rendered_model=p_skater->GetModel();
+		// This switches off any bouncing boobs on Jenna initially.
+		Dbg_Assert( p_skater->GetSkeleton() );
+		p_skater->GetSkeleton()->SetProceduralBoneTransActive( 0x47c76c69/*breast_cloth_zz*/, 0 );
+	}
+	else
+	{
+		if (mpModelName[0]==0 && mProfileName==0 && mSectorName==0)
+		{
+			m_is_displayed=false;
+			return;
+		}
+	
+		mp_rendered_model = Nx::CEngine::sInitModel();
+		Dbg_MsgAssert(mp_rendered_model,("sInitModel() returned NULL"));
+		Mth::Vector scale(mScale,mScale,mScale);
+		mp_rendered_model->SetScale(scale);
+
+		switch (m_type)
+		{
+			case SKATE_TYPE_CAR:
+				if (mSkeletonName)
+				{
+					this->LoadSkeleton(mSkeletonName);
+				}
+				if (mpModelName[0])
+				{
+					mp_rendered_model->AddGeom(mpModelName, 0, true );
+				}		
+				break;
+			case SKATE_TYPE_PED:	
+				if (mSkeletonName)
+				{
+					this->LoadSkeleton(mSkeletonName);
+					
+					if (mProfileName)
+					{
+						Gfx::CModelAppearance thePedAppearance;
+						thePedAppearance.Load( mProfileName );
+						
+						Gfx::CModelBuilder theBuilder( true, 0 );
+						theBuilder.BuildModel( &thePedAppearance, mp_rendered_model );
+					}
+					else
+					{
+						Str::String fullModelName;
+						fullModelName = Gfx::GetModelFileName(mpModelName, ".skin");
+					
+						mp_rendered_model->AddGeom(fullModelName.getString(), 0, true );
+					}
+				}
+				break;
+			case SKATE_TYPE_GAME_OBJ:	
+			case SKATE_TYPE_BOUNCY_OBJ:	
+				if (mSectorName)
+				{
+					Nx::CSector *p_sector = Nx::CEngine::sGetSector(mSectorName);
+					
+					if ( p_sector )
+					{
+						// need to clone the source, not the instance?
+						Nx::CGeom* pGeom = p_sector->GetGeom();
+						if( pGeom )
+						{
+							Nx::CGeom* pClonedGeom = pGeom->Clone( true );
+							pClonedGeom->SetActive(true);
+							mp_rendered_model->AddGeom( pClonedGeom, 0 );
+						}
+					}	
+				}
+				else if (mSkeletonName)
+				{
+					this->LoadSkeleton(mSkeletonName);
+					
+					Ass::CAssMan*	ass_manager = Ass::CAssMan::Instance();
+					ass_manager->SetReferenceChecksum( mAnimScriptName );
+					Script::RunScript( mAnimScriptName );
+					
+					Str::String fullModelName;
+					fullModelName = Gfx::GetModelFileName(mpModelName, ".skin");
+				
+					mp_rendered_model->AddGeom(fullModelName.getString(), 0, true );
+				}
+				else if (mpModelName[0])
+				{
+					Str::String fullModelName;
+					fullModelName = Gfx::GetModelFileName(mpModelName, ".mdl");
+					
+					mp_rendered_model->AddGeom(fullModelName.getString(), 0, true );
+				}				
+				break;
+			default:
+				break;
+		}		
+	}	
+	
+	// Now initialise the atomic states according to the states stored in the dummy.
+	Dbg_MsgAssert(mp_rendered_model,("NULL mp_rendered_model"));
+	mp_rendered_model->SetGeomActiveMask(mAtomicStates);
+
+	mp_rendered_model->SetActive(mFlags & DUMMY_FLAG_ACTIVE);
+	
+	// And initialise the flipped status
+	Gfx::CSkeleton* p_skeleton=this->GetSkeleton();
+	if (m_id==ID_SKATER)
+	{
+		Obj::CSkater *p_skater=sGetSkater();
+		p_skeleton = p_skater->GetSkeleton();
+	}
+#endif
+
+#if 0
+	if (p_skeleton)
+	{
+		p_skeleton->FlipAnimation( 0, mFlags&DUMMY_FLAG_FLIPPED, 0, false );
+	}
+#endif
+}
+
+void CDummy::DisplayModel()
+{
+	if (mp_rendered_model && mp_rendered_model->GetActive())
+	{
+		Mth::Matrix display_matrix(m_angles[X],m_angles[Y],m_angles[Z]);
+		display_matrix[Mth::POS] = m_pos;
+		display_matrix[Mth::POS][W] = 1.0f;
+
+		bool should_animate=true;
+		if (!mSkeletonName)
+		{
+			should_animate=false;
+		}	
+		
+		switch (m_type)
+		{
+			case SKATE_TYPE_CAR:
+			{
+				if (!mp_rendered_model->GetHierarchy())
+				{
+					break;
+				}	
+				// This updates the rotating wheels.
+				Obj::CalculateCarHierarchyMatrices( GetSkeleton(),
+                                                    mp_rendered_model,
+													m_car_rotation_x,
+													m_wheel_rotation_x,
+													m_wheel_rotation_y);
+				break;
+			}	
+			case SKATE_TYPE_PED:
+			case SKATE_TYPE_SKATER:
+			case SKATE_TYPE_GAME_OBJ:
+				if (should_animate)
+				{
+//                    if ( mp_skeletonComponent )
+//                    {
+//                        mp_skeletonComponent->SetNeutralPose( mAnimScriptName+0x1ca1ff20/*default*/ );
+//                    }
+					
+#if 0
+					int degeneratingCount = 0;
+					for ( int i = 0; i < NUM_DEGENERATE_ANIMS; i++ )
+					{
+						if ( m_degenerateControllers[i].GetStatus() != Gfx::ANIM_STATUS_INACTIVE )
+						{
+							degeneratingCount++;
+						}
+					}
+					
+					if ( degeneratingCount )
+					{
+						// animation has blending...
+						
+						uint32 degenerate_anim_name0,degenerate_anim_name1,degenerate_anim_name2;
+						float degenerate_anim_time0,degenerate_anim_time1,degenerate_anim_time2;
+						float degenerate_anim_blend_value0,degenerate_anim_blend_value1,degenerate_anim_blend_value2;
+						
+						m_degenerateControllers[0].GetNameTimeAndBlendValue(°enerate_anim_name0,°enerate_anim_time0,°enerate_anim_blend_value0);
+						m_degenerateControllers[1].GetNameTimeAndBlendValue(°enerate_anim_name1,°enerate_anim_time1,°enerate_anim_blend_value1);
+						m_degenerateControllers[2].GetNameTimeAndBlendValue(°enerate_anim_name2,°enerate_anim_time2,°enerate_anim_blend_value2);
+						
+						if (degenerate_anim_name0) degenerate_anim_name0+=mAnimScriptName;
+						if (degenerate_anim_name1) degenerate_anim_name1+=mAnimScriptName;
+						if (degenerate_anim_name2) degenerate_anim_name2+=mAnimScriptName;
+						
+						#if 0
+						// disabled replays of skeletons until the transition is complete
+                        if ( mp_skeletonComponent )
+                        {
+                            mp_skeletonComponent->Update( 
+                                mAnimScriptName+m_primaryController.GetAnimName(), m_primaryController.GetCurrentAnimTime(),
+                                degenerate_anim_name0,degenerate_anim_time0,degenerate_anim_blend_value0,
+                                degenerate_anim_name1,degenerate_anim_time1,degenerate_anim_blend_value1,
+                                degenerate_anim_name2,degenerate_anim_time2,degenerate_anim_blend_value2 );                        }
+						#endif
+					}
+					else
+					{
+						#if 0
+						// disabled replays of skeletons until the transition is complete
+						if ( mp_skeletonComponent )
+                        {
+                            mp_skeletonComponent->Update( mAnimScriptName+m_primaryController.GetAnimName(),
+								m_primaryController.GetCurrentAnimTime() );
+                        }
+						#endif
+					}
+#endif
+				
+				}	
+				break;
+			default:
+				break;
+		}		
+
+
+		if (mFlags&DUMMY_FLAG_HOVERING)
+		{
+			uint32 t=Tmr::ElapsedTime(0)%mHoverPeriod;
+			display_matrix[Mth::POS][Y]+=HOVER_AMPLITUDE*sinf(t*2*3.141592653f/mHoverPeriod);
+		}
+		
+		mp_rendered_model->Render(&display_matrix,!should_animate,GetSkeleton());
+	}	
+}
+
+void CDummy::UpdateMutedSounds()
+{
+	GetSoundComponent()->UpdateMutedSounds();
+}	
+
+static void sStopReplayDummyLoopingSounds()
+{
+	Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+	
+	CDummy *p_dummy=spReplayDummies;
+	while (p_dummy)
+	{
+		if (p_dummy->m_looping_sound_id)
+		{
+			sfx_manager->StopSound( p_dummy->m_looping_sound_id );
+			p_dummy->m_looping_sound_id=0;
+		}	
+		
+		p_dummy=p_dummy->mpNext;
+	}
+}
+		
+void ClearBuffer()
+{
+	if (BufferAllocated())
+	{
+		FillBuffer(0,GetBufferSize(),BLANK);
+	}	
+	sBigBufferStartOffset=0;
+	sBigBufferEndOffset=0;
+	sBufferFilling=true;
+}
+
+static uint8 spBufferChunk[REPLAY_BUFFER_CHUNK_SIZE];
+uint8 *GetTempBuffer()
+{
+	return spBufferChunk;
+}
+	
+void WriteReplayDataHeader(SReplayDataHeader *p_header)
+{
+	Dbg_MsgAssert(p_header,("NULL p_header"));
+	p_header->mBufferStartOffset=sBigBufferStartOffset;
+	p_header->mBufferEndOffset=sBigBufferEndOffset;
+	p_header->mNumStartStateDummies=sCountStartStateDummies();
+	p_header->mStartState=sStartState;
+}
+
+void ReadReplayDataHeader(const SReplayDataHeader *p_header)
+{
+	Dbg_MsgAssert(p_header,("NULL p_header"));
+	sBigBufferStartOffset=p_header->mBufferStartOffset;
+	sBigBufferEndOffset=p_header->mBufferEndOffset;
+	sStartState=p_header->mStartState;
+	Nx::CEngine::sReadSectorStatusBitfield(sStartState.mpSectorStatus,SECTOR_STATUS_BUFFER_SIZE);
+}
+	
+void CreateDummyFromSaveData(SSavedDummy *p_saved_dummy)
+{
+#if 0
+	Dbg_MsgAssert(p_saved_dummy,("NULL p_saved_dummy"));
+	
+	CDummy *p_dummy=new CDummy(START_STATE_DUMMY_LIST,p_saved_dummy->m_id);
+	p_dummy->SetType(p_saved_dummy->m_type);
+	
+	Dbg_MsgAssert(strlen(p_saved_dummy->mpModelName)<=MAX_MODEL_NAME_CHARS,("p_saved_dummy mpModelName too long ?"));
+	strcpy(p_dummy->mpModelName,p_saved_dummy->mpModelName);
+	
+	p_dummy->mSkeletonName	=	p_saved_dummy->mSkeletonName;
+	p_dummy->mProfileName	=	p_saved_dummy->mProfileName;
+	p_dummy->mAnimScriptName=	p_saved_dummy->mAnimScriptName;
+	p_dummy->mSectorName	=	p_saved_dummy->mSectorName;
+	p_dummy->mScale			=	p_saved_dummy->mScale;
+	p_dummy->mFlags			=	p_saved_dummy->mFlags;
+	// Note: The mHoverPeriod is not restored. No need, it does not need to be restored exactly,
+	// and the CDummy constructor will have generated a new random value for it.
+	p_dummy->m_pos			=	p_saved_dummy->m_pos;
+	p_dummy->m_vel			=	p_saved_dummy->m_vel;
+	p_dummy->m_angles		=	p_saved_dummy->m_angles;
+	p_dummy->m_ang_vel		=	p_saved_dummy->m_ang_vel;
+	p_dummy->m_car_rotation_x       = p_saved_dummy->m_car_rotation_x;      
+	p_dummy->m_car_rotation_x_vel   = p_saved_dummy->m_car_rotation_x_vel;  
+	p_dummy->m_wheel_rotation_x     = p_saved_dummy->m_wheel_rotation_x;    
+	p_dummy->m_wheel_rotation_x_vel = p_saved_dummy->m_wheel_rotation_x_vel;
+	p_dummy->m_wheel_rotation_y     = p_saved_dummy->m_wheel_rotation_y;    
+	p_dummy->m_wheel_rotation_y_vel = p_saved_dummy->m_wheel_rotation_y_vel;
+	
+	p_dummy->m_primaryController	=	p_saved_dummy->m_primaryController;
+	
+	for (int i=0; im_degenerateControllers[i]=p_saved_dummy->m_degenerateControllers[i];
+	}
+	
+	p_dummy->mAtomicStates			= p_saved_dummy->mAtomicStates;
+
+	// The looping sound id is not copied in, since it is a unique id for the instance of the sound.
+	p_dummy->m_looping_sound_id=0;
+	p_dummy->m_looping_sound_checksum = p_saved_dummy->m_looping_sound_checksum;
+	p_dummy->m_pitch_min = p_saved_dummy->m_pitch_min;
+	p_dummy->m_pitch_max = p_saved_dummy->m_pitch_max;
+	
+	// This is because for the skater dummy the speed for calculating the looping sound
+	// volume is calculated using m_old_pos. If it were left equal to 0,0,0 it would cause the
+	// looping sound to be played at max volume for the first frame, because it would think
+	// the skater was moving very fast.
+	p_dummy->m_old_pos=p_dummy->m_pos;
+#endif
+}
+
+static bool sPoolsCreated=false;
+//char p_foo[sizeof(CTrackingInfo)/0];
+void CreatePools()
+{
+	if (!sPoolsCreated)
+	{
+		// MEMOPT: Make this pool smaller
+		CTrackingInfo::SCreatePool(300, "CTrackingInfo");
+		sPoolsCreated=true;
+	}	
+}
+
+void RemovePools()
+{
+	if (sPoolsCreated)
+	{
+		CTrackingInfo::SRemovePool();
+		sPoolsCreated=false;
+	}	
+}
+
+bool ScriptReplayRecordSimpleScriptCall(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	// Do nothing if not in record mode.
+	if (sMode!=RECORD)
+	{
+		return true;
+	}
+	
+	uint32 script_name=0;
+	pParams->GetChecksum("scriptname",&script_name);
+	
+	if (pParams->ContainsFlag("skaterscript"))
+	{
+		sWriteUint32(SKATER_SCRIPT_CALL,script_name);
+	}
+	else
+	{
+		sWriteUint32(SCRIPT_CALL,script_name);
+	}
+	return true;
+}
+
+static uint32 sLastRecordedPanelMessageID=0;
+bool ScriptRecordPanelMessage(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if (sMode!=RECORD)
+	{
+		return true;
+	}
+
+	// Remember what the last panel message id was so that it can be killed before
+	// replaying the replay.
+	//sLastRecordedPanelMessageID=0;
+	//pParams->GetChecksum("id",&sLastRecordedPanelMessageID);
+	
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	sLastRecordedPanelMessageID = p_manager->ResolveComplexID(pParams, "id");
+	
+	
+	
+	uint8 p_buf[MAX_PANEL_MESSAGE_SIZE];
+	int size=Script::WriteToBuffer(pParams,p_buf,MAX_PANEL_MESSAGE_SIZE,Script::NO_ASSERT);
+	if (!size)
+	{
+		Script::PrintContents(pParams);
+		Dbg_MsgAssert(0,("Panel message structure too big"));
+		return true;
+	}	
+	
+	sWriteUint32(PANEL_MESSAGE,size);
+	
+	Dbg_MsgAssert(spFrameBufferPos+size <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	memcpy(spFrameBufferPos,p_buf,size);
+	spFrameBufferPos+=size;
+	
+	return true;
+}
+	
+// Call this to allocate the resources needed to store a replay.
+// May be called repeatedly.
+bool ScriptAllocateReplayMemory(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	return true;
+	Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().TopDownHeap());
+	AllocateBuffer();
+	ClearBuffer();
+	CreatePools();
+	Mem::Manager::sHandle().PopContext();
+	sMode=RECORD;	
+	
+	CTrackingInfo::sSkaterTrackingInfo.Reset();
+	Nx::CEngine::sInitReplayStartState();
+	return true;
+}
+
+void DeallocateReplayMemory()
+{
+	sDeleteDummies(START_STATE_DUMMY_LIST);
+	sDeleteDummies(PLAYBACK_DUMMY_LIST);
+	Dbg_MsgAssert(spReplayDummies==NULL,("Hey! spReplayDummies not NULL !!!"));
+	sDeleteObjectTrackingInfo();
+	ClearBuffer();
+	DeallocateBuffer();
+	RemovePools();
+	sStartState.Reset();
+	sCurrentState.Reset();
+	sMode=NONE;
+}
+
+// Call this to free up the resources used to store a replay.
+// May be called repeatedly.
+bool ScriptDeallocateReplayMemory(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	DeallocateReplayMemory();
+	return true;
+}	
+
+// If in record mode, this will clear what has been recorded and start afresh.
+void StartRecordingAfresh()
+{
+	Dbg_MsgAssert(!Mdl::Skate::Instance()->IsMultiplayerGame(),("Tried to start recording replay in multiplayer game"));
+	sMode=RECORD;
+
+	sDeleteDummies(START_STATE_DUMMY_LIST);
+	sDeleteDummies(PLAYBACK_DUMMY_LIST);
+	Dbg_MsgAssert(spReplayDummies==NULL,("Hey! spReplayDummies not NULL !!!"));
+	sDeleteObjectTrackingInfo();
+	ClearBuffer();
+	sStartState.Reset();
+	sCurrentState.Reset();
+	CTrackingInfo::sSkaterTrackingInfo.Reset();
+	Nx::CEngine::sInitReplayStartState();
+}
+
+bool ScriptStartRecordingAfresh(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	if (Mdl::Skate::Instance()->IsMultiplayerGame())
+	{
+		return false;
+	}	
+	StartRecordingAfresh();
+	return true;
+}	
+
+static uint32 sReadAnimControllerChanges(uint32 offset, Gfx::CAnimChannel *p_anim_controller)
+{
+	#define MAX_CHANGES 18
+	uint8 p_buf[5*MAX_CHANGES];
+	
+	uint8 num_changes;
+	ReadFromBuffer(&num_changes,offset,1);
+	++offset;
+	Dbg_MsgAssert(num_changes<=MAX_CHANGES,("Too many anim controller changes in one instruction"));
+	
+	ReadFromBuffer(p_buf,offset,5*num_changes);
+	offset+=5*num_changes;
+	
+	uint32 *p_dest=(uint32*)p_anim_controller;
+	uint8 *p_foo=p_buf;
+	for (int i=0; i *p_dummy_table=sGetDummyHashTable(whichList);
+	*p_nothingFollows=false;
+	CDummy *p_dummy=NULL;
+	bool end=false;
+	while (!end)
+	{
+		if (offset==GetBufferSize())
+		{
+			*p_nothingFollows=true;
+			break;
+		}	
+		Dbg_MsgAssert(offsetSetShatter(token==SHATTER_ON);
+				}	
+			
+				break;
+			}
+
+			
+			case SCRIPT_CALL:	
+			case SKATER_SCRIPT_CALL: // Used to record BloodParticlesOn & off
+			{
+				// Must not call the script if recording.
+				if (sMode==RECORD)
+				{
+					offset+=1+4;
+					break;
+				}
+				
+				++offset;
+
+				uint32 script_name;
+				ReadFromBuffer((uint8*)&script_name,offset,4);
+				script_name=Script::Read4Bytes((uint8*)&script_name).mUInt;
+				offset+=4;
+				
+				if (token==SCRIPT_CALL)
+				{
+					Script::RunScript(script_name);
+				}
+				else
+				{
+					Script::RunScript( script_name, NULL, p_skater );
+				}
+				break;
+			}
+			
+			case PAD_VIBRATION:
+			{
+				++offset;
+				uint8 actuator;
+				ReadFromBuffer(&actuator,offset,1);
+				++offset;
+				Dbg_MsgAssert(actuatorGetDevice()->ActivateActuator(actuator,percent);
+				}	
+				break;
+			}
+				
+			// This command is needed because when the goal cutscenes play in mid goal
+			// they do a PauseSkaters command, which in-game pauses the vibrations.
+			// Need to do that in the replay too, otherwise in the 4 angry seals goal in sf
+			// the pad vibrates during the camera anims, cos the skater is usually in a grind then.
+			case PAUSE_SKATER:	
+			case UNPAUSE_SKATER:	
+			{
+				++offset;
+				
+				if (whichList==START_STATE_DUMMY_LIST)
+				{
+					sStartState.mSkaterPaused=(token==PAUSE_SKATER);
+				}
+				else
+				{
+					sCurrentState.mSkaterPaused=(token==PAUSE_SKATER);
+					if (sCurrentState.mSkaterPaused)
+					{
+						// Switch off the vibrations.
+						for (int i=0; iGetDevice()->ActivateActuator(i,0);
+						}	
+					}
+					else
+					{
+						// Restore the vibrations.
+						for (int i=0; iGetDevice()->ActivateActuator(i,sCurrentState.mActuatorStrength[i]);
+						}	
+					}
+				}
+				break;
+			}
+				
+			case MANUAL_METER_ON:
+			{
+				++offset;
+				float value;
+				ReadFromBuffer((uint8*)&value,offset,4);
+				value=Script::Read4Bytes((uint8*)&value).mFloat;
+				offset+=4;
+				
+				if (whichList==START_STATE_DUMMY_LIST)
+				{
+					sStartState.mManualMeterStatus=true;
+					sStartState.mManualMeterValue=value;
+				}
+				else
+				{
+					sCurrentState.mManualMeterStatus=true;
+					sCurrentState.mManualMeterValue=value;
+					sGetSkaterScoreObject()->SetManualMeter(true,value);
+				}
+				break;
+			}
+			
+			case MANUAL_METER_OFF:
+			{
+				++offset;
+					
+				if (whichList==START_STATE_DUMMY_LIST)
+				{
+					sStartState.mManualMeterStatus=false;
+					sStartState.mManualMeterValue=0.0f;
+				}
+				else
+				{
+					sCurrentState.mManualMeterStatus=false;
+					sCurrentState.mManualMeterValue=0.0f;
+					sGetSkaterScoreObject()->SetManualMeter(false);
+				}
+				break;
+			}
+
+			case BALANCE_METER_ON:
+			{
+				++offset;
+				float value;
+				ReadFromBuffer((uint8*)&value,offset,4);
+				value=Script::Read4Bytes((uint8*)&value).mFloat;
+				offset+=4;
+				
+				if (whichList==START_STATE_DUMMY_LIST)
+				{
+					sStartState.mBalanceMeterStatus=true;
+					sStartState.mBalanceMeterValue=value;
+				}
+				else
+				{
+					sCurrentState.mBalanceMeterStatus=true;
+					sCurrentState.mBalanceMeterValue=value;
+					sGetSkaterScoreObject()->SetBalanceMeter(true,value);
+				}
+				break;
+			}
+			
+			case BALANCE_METER_OFF:
+			{
+				++offset;
+					
+				if (whichList==START_STATE_DUMMY_LIST)
+				{
+					sStartState.mBalanceMeterStatus=false;
+					sStartState.mBalanceMeterValue=0.0f;
+				}
+				else
+				{
+					sCurrentState.mBalanceMeterStatus=false;
+					sCurrentState.mBalanceMeterValue=0.0f;
+					sGetSkaterScoreObject()->SetBalanceMeter(false);
+				}
+				break;
+			}
+			
+#if 0
+			case FLIP:	
+			case UNFLIP:	
+			{
+				++offset;
+				
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				if (token==FLIP)
+				{
+					p_dummy->mFlags|=DUMMY_FLAG_FLIPPED;
+				}
+				else
+				{
+					p_dummy->mFlags&=~DUMMY_FLAG_FLIPPED;
+				}
+				
+				if (whichList==PLAYBACK_DUMMY_LIST && p_dummy->mp_rendered_model)
+				{
+					Gfx::CSkeleton* p_skeleton=p_dummy->GetSkeleton();
+					if (p_skeleton)
+					{
+						p_skeleton->FlipAnimation( 0, p_dummy->mFlags&DUMMY_FLAG_FLIPPED, 0, false );
+					}
+				}		
+				break;
+			}
+#endif
+
+			case MODEL_ACTIVE:	
+			case MODEL_INACTIVE:	
+			{
+				++offset;
+				
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				if (token==MODEL_ACTIVE)
+				{
+					p_dummy->mFlags|=DUMMY_FLAG_ACTIVE;
+				}
+				else
+				{
+					p_dummy->mFlags&=~DUMMY_FLAG_ACTIVE;
+				}
+				
+				if (whichList==PLAYBACK_DUMMY_LIST && p_dummy->mp_rendered_model)
+				{
+					p_dummy->mp_rendered_model->SetActive(p_dummy->mFlags&DUMMY_FLAG_ACTIVE);
+				}		
+				break;
+			}
+				
+			case SPARKS_ON:
+			{
+				++offset;
+				// Must not do sparks if recording
+				if (sMode==RECORD)
+				{
+					break;
+				}
+				
+				Script::RunScript( "sparks_on", NULL, p_skater );
+				break;
+			}
+
+			case SPARKS_OFF:
+			{
+				++offset;
+				if (sMode==RECORD)
+				{
+					break;
+				}
+				
+				Script::RunScript( "sparks_off", NULL, p_skater );
+				break;
+			}
+			
+			case PANEL_MESSAGE:
+			{
+				++offset;
+				int size;
+				ReadFromBuffer((uint8*)&size,offset,4);
+				size=Script::Read4Bytes((uint8*)&size).mInt;
+				offset+=4;
+				
+				uint8 p_buf[MAX_PANEL_MESSAGE_SIZE];
+				Dbg_MsgAssert(size<=MAX_PANEL_MESSAGE_SIZE,("Panel message size too big in replay"));
+				ReadFromBuffer(p_buf,offset,size);
+				offset+=size;
+
+				if (sMode==RECORD)
+				{
+					break;
+				}
+				
+				Script::CStruct *p_struct=new Script::CStruct;
+				Script::ReadFromBuffer(p_struct,p_buf);
+				Script::RunScript("Create_Panel_Message",p_struct);
+				delete p_struct;
+				break;
+			}
+					
+			case SCORE_POT_TEXT:
+			{
+				++offset;
+				uint8 len;
+				ReadFromBuffer(&len,offset,1);
+				++offset;
+				char p_text[100];
+				if (len<100)
+				{
+					ReadFromBuffer((uint8*)p_text,offset,len);
+					p_text[len]=0;
+				}
+				else
+				{
+					p_text[0]=0;
+				}
+				offset+=len;
+
+				if (sMode==RECORD)
+				{
+					break;
+				}
+				
+				Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+				
+				int index = p_skater->GetHeapIndex();
+
+				Front::CTextElement *p_score_pot_text = (Front::CTextElement *) p_manager->GetElement(0xf4d3a70e + index ).Convert(); // "the_score_pot_text"
+				if (p_score_pot_text)
+				{
+					p_score_pot_text->SetText(p_text);
+				}
+				break;
+			}
+			
+			case TRICK_TEXT:
+			{
+				++offset;
+				uint8 num_strings;
+				ReadFromBuffer(&num_strings,offset,1);
+				++offset;
+
+				const char *pp_text[TRICK_LIMIT];
+				int num_strings_loaded=0;
+				char *p_dest=spTrickText;
+				for (uint8 i=0; iGetHeapIndex();
+					
+					Front::CTextBlockElement *p_text_block = (Front::CTextBlockElement *) p_manager->GetElement(0x44727dae/*the_trick_text*/ + index ).Convert();
+					if (p_text_block)
+					{
+						p_text_block->SetText(pp_text, num_strings_loaded);
+					}
+					sTrickTextGotCleared=false;
+					break;
+				}
+			}
+
+			case TRICK_TEXT_PULSE:
+			case TRICK_TEXT_COUNTDOWN:
+			case TRICK_TEXT_LANDED:
+			case TRICK_TEXT_BAIL:
+			{
+				++offset;
+				if (sMode==RECORD)
+				{
+					break;
+				}
+				
+				if (sTrickTextGotCleared)
+				{
+					break;
+				}
+					
+				int index=p_skater->GetHeapIndex();
+				
+				Mdl::Score *p_score=p_skater->GetScoreObject();
+				
+				switch (token)
+				{
+					case TRICK_TEXT_PULSE:
+						p_score->TrickTextPulse(index);
+						break;
+					case TRICK_TEXT_COUNTDOWN:
+						p_score->TrickTextCountdown(index);
+						break;
+					case TRICK_TEXT_LANDED:
+						p_score->TrickTextLanded(index);
+						break;
+					case TRICK_TEXT_BAIL:
+						p_score->TrickTextBail(index);
+						break;
+					default:
+						break;
+				}				
+				
+				break;
+			}
+			
+			case SET_ATOMIC_STATES:							
+			{
+				++offset;
+				uint32 id;
+				ReadFromBuffer((uint8*)&id,offset,4);
+				id=Script::Read4Bytes((uint8*)&id).mUInt;
+				offset+=4;
+				
+				uint32 atomic_states;
+				ReadFromBuffer((uint8*)&atomic_states,offset,4);
+				atomic_states=Script::Read4Bytes((uint8*)&atomic_states).mUInt;
+				offset+=4;
+
+				p_dummy=p_dummy_table->GetItem(id);
+				if (p_dummy)
+				{
+					// Record the atomic state change in the dummy too, so that the initial states
+					// can be set at the start of replay playback.
+					p_dummy->mAtomicStates=atomic_states;
+				
+					if (p_dummy->mp_rendered_model)
+					{
+						p_dummy->mp_rendered_model->SetGeomActiveMask(atomic_states);
+					}				
+				}	
+				break;
+			}
+			
+			case PLAY_STREAM:
+			{
+				// Must not play sounds if recording
+				if (sMode==RECORD)
+				{
+					offset+=1+4*5;
+					break;
+				}
+				
+				++offset;
+				uint32 checksum;
+//				float volume_l, volume_r;
+				float vol;
+				float pitch;
+
+				int priority;
+				ReadFromBuffer((uint8*)&checksum,offset,4);
+				checksum=Script::Read4Bytes((uint8*)&checksum).mUInt;
+				offset+=4;
+
+				uint32 volume_type;
+				ReadFromBuffer((uint8*)&volume_type,offset,4);
+				volume_type=Script::Read4Bytes((uint8*)&volume_type).mInt;
+				offset+=4;
+
+				Sfx::sVolume vol_struct((Sfx::EVolumeType)volume_type );
+
+				ReadFromBuffer((uint8*)&vol,offset,4);
+				vol=Script::Read4Bytes((uint8*)&vol).mFloat;
+				offset+=4;
+				vol_struct.SetChannelVolume( 0, vol );
+
+				ReadFromBuffer((uint8*)&vol,offset,4);
+				vol=Script::Read4Bytes((uint8*)&vol).mFloat;
+				offset+=4;
+				vol_struct.SetChannelVolume( 1, vol );
+
+				// Read channels 2, 3 and 4 if a 5 channel sound type.
+				if( vol_struct.GetVolumeType() == Sfx::VOLUME_TYPE_5_CHANNEL_DOLBY5_1 )
+				{
+					ReadFromBuffer((uint8*)&vol,offset,4);
+					vol=Script::Read4Bytes((uint8*)&vol).mFloat;
+					offset+=4;
+					vol_struct.SetChannelVolume( 2, vol );
+
+					ReadFromBuffer((uint8*)&vol,offset,4);
+					vol=Script::Read4Bytes((uint8*)&vol).mFloat;
+					offset+=4;
+					vol_struct.SetChannelVolume( 3, vol );
+
+					ReadFromBuffer((uint8*)&vol,offset,4);
+					vol=Script::Read4Bytes((uint8*)&vol).mFloat;
+					offset+=4;
+					vol_struct.SetChannelVolume( 4, vol );
+				}
+
+				ReadFromBuffer((uint8*)&pitch,offset,4);
+				pitch=Script::Read4Bytes((uint8*)&pitch).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&priority,offset,4);
+				priority=Script::Read4Bytes((uint8*)&priority).mInt;
+				offset+=4;
+
+				Pcm::PlayStream(checksum,&vol_struct,pitch,priority);
+				break;
+			}
+
+			case STOP_STREAM:
+			{
+				// Must not stop streams if recording
+				if (sMode==RECORD)
+				{
+					offset+=1+4;
+					break;
+				}
+				
+				++offset;
+				int channel;
+				ReadFromBuffer((uint8*)&channel,offset,4);
+				channel=Script::Read4Bytes((uint8*)&channel).mInt;
+				offset+=4;
+				
+				Pcm::StopStreams(channel);
+				break;
+			}
+						
+			case PLAY_POSITIONAL_STREAM:
+			{
+				// Must not play sounds if recording
+				if (sMode==RECORD)
+				{
+					offset+=1+4*7;
+					break;
+				}
+				
+				++offset;
+				uint32 id, stream_name;
+				float volume, pitch, drop_off;
+				int priority, use_pos_info;
+				ReadFromBuffer((uint8*)&id,offset,4);
+				id=Script::Read4Bytes((uint8*)&id).mUInt;
+				offset+=4;
+				ReadFromBuffer((uint8*)&stream_name,offset,4);
+				stream_name=Script::Read4Bytes((uint8*)&stream_name).mUInt;
+				offset+=4;
+				ReadFromBuffer((uint8*)&drop_off,offset,4);
+				drop_off=Script::Read4Bytes((uint8*)&drop_off).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&volume,offset,4);
+				volume=Script::Read4Bytes((uint8*)&volume).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&pitch,offset,4);
+				pitch=Script::Read4Bytes((uint8*)&pitch).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&priority,offset,4);
+				priority=Script::Read4Bytes((uint8*)&priority).mInt;
+				offset+=4;
+				ReadFromBuffer((uint8*)&use_pos_info,offset,4);
+				use_pos_info=Script::Read4Bytes((uint8*)&use_pos_info).mInt;
+				offset+=4;
+
+				p_dummy=p_dummy_table->GetItem(id);
+				if (p_dummy)
+				{
+					printf ("STUBBED replay.cpp %d\n",__LINE__);
+//					Pcm::PlayStreamFromObject(p_dummy,stream_name,drop_off,volume,pitch,channel,use_pos_info);
+				}
+				
+				break;
+			}
+			
+			case PLAY_POSITIONAL_SOUND_EFFECT:				
+			{
+				// Must not play sounds if recording
+				if (sMode==RECORD)
+				{
+					offset+=1+4*5;
+					break;
+				}
+				
+				++offset;
+				uint32 id, sound_name;
+				float volume, pitch, drop_off_dist;
+				ReadFromBuffer((uint8*)&id,offset,4);
+				id=Script::Read4Bytes((uint8*)&id).mUInt;
+				offset+=4;
+				ReadFromBuffer((uint8*)&sound_name,offset,4);
+				sound_name=Script::Read4Bytes((uint8*)&sound_name).mUInt;
+				offset+=4;
+				ReadFromBuffer((uint8*)&volume,offset,4);
+				volume=Script::Read4Bytes((uint8*)&volume).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&pitch,offset,4);
+				pitch=Script::Read4Bytes((uint8*)&pitch).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&drop_off_dist,offset,4);
+				drop_off_dist=Script::Read4Bytes((uint8*)&drop_off_dist).mFloat;
+				offset+=4;
+
+				p_dummy=p_dummy_table->GetItem(id);
+				if (p_dummy)
+				{
+					Sfx::CSfxManager * p_sfx_manager = Sfx::CSfxManager::Instance();
+					Sfx::SoundUpdateInfo soundUpdateInfo;
+					soundUpdateInfo.volume = volume;
+					soundUpdateInfo.pitch = pitch;
+					soundUpdateInfo.dropoffDist = drop_off_dist;
+
+					// These next two parameters need to be added to the replay code
+					soundUpdateInfo.dropoffFunction = DROPOFF_FUNC_STANDARD;
+					bool noPosUpdate = false;
+					p_sfx_manager->PlaySoundWithPos( sound_name, &soundUpdateInfo, GetSoundComponentFromObject( p_dummy ), noPosUpdate );
+				}
+				
+				break;
+			}
+				
+			case PLAY_SKATER_SOUND_EFFECT:				
+			{
+				// Must not play sounds if recording
+				if (sMode==RECORD)
+				{
+					offset+=1+4*6;
+					break;
+				}
+				
+				++offset;
+				
+				int which_array, surface_flag;
+				Mth::Vector pos;
+				float vol_percent;
+				
+				ReadFromBuffer((uint8*)&which_array,offset,4);
+				which_array=Script::Read4Bytes((uint8*)&which_array).mInt;
+				offset+=4;
+				ReadFromBuffer((uint8*)&surface_flag,offset,4);
+				surface_flag=Script::Read4Bytes((uint8*)&surface_flag).mInt;
+				offset+=4;
+				ReadFromBuffer((uint8*)&pos[X],offset,4);
+				pos[X]=Script::Read4Bytes((uint8*)&pos[X]).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&pos[Y],offset,4);
+				pos[Y]=Script::Read4Bytes((uint8*)&pos[Y]).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&pos[Z],offset,4);
+				pos[Z]=Script::Read4Bytes((uint8*)&pos[Z]).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&vol_percent,offset,4);
+				vol_percent=Script::Read4Bytes((uint8*)&vol_percent).mFloat;
+				offset+=4;
+
+				// NOTE: need to record sound pitch and choice for use here
+				Env::CTerrainManager::sPlaySound((Env::ETerrainActionType) which_array, (ETerrainType) surface_flag,pos,vol_percent,100.0f,0.0f,false);
+				break;
+			}
+				
+			case PLAY_SOUND:
+			{
+				// Must not play sounds if recording
+				if (sMode==RECORD)
+				{
+					offset+=1+4*4;
+					break;
+				}
+				
+				++offset;
+				
+				uint32 checksum;
+				ReadFromBuffer((uint8*)&checksum,offset,4);
+				checksum=Script::Read4Bytes((uint8*)&checksum).mUInt;
+				offset+=4;
+				
+				float vol_l,vol_r;
+				ReadFromBuffer((uint8*)&vol_l,offset,4);
+				vol_l=Script::Read4Bytes((uint8*)&vol_l).mFloat;
+				offset+=4;
+				ReadFromBuffer((uint8*)&vol_r,offset,4);
+				vol_r=Script::Read4Bytes((uint8*)&vol_r).mFloat;
+				offset+=4;
+
+				float pitch;
+				ReadFromBuffer((uint8*)&pitch,offset,4);
+				pitch=Script::Read4Bytes((uint8*)&pitch).mFloat;
+				offset+=4;
+				
+				Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+
+				// Dave note 10/17/02, the replay code should store all 5 channels where appropriate.
+				Sfx::sVolume vol;
+				vol.SetSilent();
+				vol.SetChannelVolume( 0, vol_l );
+				vol.SetChannelVolume( 1, vol_r );
+//				sfx_manager->PlaySound(checksum,vol_l,vol_r,pitch);
+				sfx_manager->PlaySound(checksum,&vol,pitch);
+				break;
+			}
+			
+			case PLAY_LOOPING_SOUND:
+			{
+				++offset;
+				
+				uint32 checksum;
+				ReadFromBuffer((uint8*)&checksum,offset,4);
+				checksum=Script::Read4Bytes((uint8*)&checksum).mUInt;
+				offset+=4;
+
+				p_dummy->m_looping_sound_checksum=checksum;
+					
+				if (whichList==PLAYBACK_DUMMY_LIST)
+				{
+					Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+					
+					Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+					if (p_dummy->m_looping_sound_id)
+					{
+						sfx_manager->StopSound( p_dummy->m_looping_sound_id );
+					}	
+					p_dummy->m_looping_sound_id = sfx_manager->PlaySound( checksum, 0, 0 );
+				}
+				else
+				{
+					// This bit will execute if the list is the start-state dummy list.
+					// m_looping_sound_id isn't used in that case, but may as well set it to zero.
+					p_dummy->m_looping_sound_id=0;
+				}	
+					
+				break;
+			}
+
+			case STOP_LOOPING_SOUND:
+			{
+				++offset;
+				
+				if (whichList==PLAYBACK_DUMMY_LIST)
+				{
+					Sfx::CSfxManager * sfx_manager =  Sfx::CSfxManager::Instance();
+					
+					Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+					if (p_dummy->m_looping_sound_id)
+					{
+						sfx_manager->StopSound( p_dummy->m_looping_sound_id );
+					}	
+				}	
+				
+				p_dummy->m_looping_sound_id=0;
+				p_dummy->m_looping_sound_checksum=0;
+				break;
+			}
+
+			case PITCH_MIN:
+			case PITCH_MAX:
+			{
+				++offset;
+				
+				float pitch;
+				ReadFromBuffer((uint8*)&pitch,offset,4);
+				pitch=Script::Read4Bytes((uint8*)&pitch).mFloat;
+				offset+=4;
+
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				if (token==PITCH_MIN)
+				{
+					p_dummy->m_pitch_min=pitch;
+				}
+				else
+				{
+					p_dummy->m_pitch_max=pitch;
+				}
+				break;
+			}
+				
+			case OBJECT_ID:
+			{
+				++offset;
+				uint32 id;
+				ReadFromBuffer((uint8*)&id,offset,4);
+				id=Script::Read4Bytes((uint8*)&id).mUInt;
+				
+				p_dummy=p_dummy_table->GetItem(id);
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?  id=%d",id));
+				
+				offset+=4;
+				break;
+			}
+
+			case CREATE_OBJECT:
+			{
+				++offset;
+				uint32 id;
+				uint8 type;
+				ReadFromBuffer((uint8*)&id,offset,4);
+				id=Script::Read4Bytes((uint8*)&id).mUInt;
+				offset+=4;
+				ReadFromBuffer(&type,offset,1);
+				++offset;
+				
+				p_dummy=p_dummy_table->GetItem(id);
+				if (!p_dummy)
+				{
+					p_dummy=new CDummy(whichList,id);
+					p_dummy->SetType(type);
+				}
+				else
+				{
+					Dbg_MsgAssert(0,("Got CREATE_OBJECT when dummy already exists ?"));
+				}	
+				
+				break;
+			}
+			
+			case KILL_OBJECT:
+			{
+				++offset;
+				uint32 id;
+				ReadFromBuffer((uint8*)&id,offset,4);
+				id=Script::Read4Bytes((uint8*)&id).mUInt;
+				
+				p_dummy=p_dummy_table->GetItem(id);
+				if (p_dummy)
+				{
+					delete p_dummy;
+					p_dummy=NULL;
+				}	
+				
+				offset+=4;
+				break;
+			}
+			
+#if 0
+			case MODEL_NAME:
+			{
+				++offset;
+				uint8 len;
+				ReadFromBuffer(&len,offset,1);
+				++offset;
+				
+				Dbg_MsgAssert(len<=MAX_MODEL_NAME_CHARS,("Too many chars in model name"));
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)p_dummy->mpModelName,offset,len);
+				// The string has no terminating 0 in the big buffer, so wack one on.
+				p_dummy->mpModelName[len]=0;
+				offset+=len;
+				break;
+			}
+#endif
+			case SKELETON_NAME:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->mSkeletonName,offset,4);
+				p_dummy->mSkeletonName=Script::Read4Bytes((uint8*)&p_dummy->mSkeletonName).mUInt;
+				offset+=4;
+				break;
+			}
+			case PROFILE_NAME:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->mProfileName,offset,4);
+				p_dummy->mProfileName=Script::Read4Bytes((uint8*)&p_dummy->mProfileName).mUInt;
+				offset+=4;
+				break;
+			}
+			case SECTOR_NAME:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->mSectorName,offset,4);
+				p_dummy->mSectorName=Script::Read4Bytes((uint8*)&p_dummy->mSectorName).mUInt;
+				offset+=4;
+				break;
+			}
+			case ANIM_SCRIPT_NAME:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->mAnimScriptName,offset,4);
+				p_dummy->mAnimScriptName=Script::Read4Bytes((uint8*)&p_dummy->mAnimScriptName).mUInt;
+				offset+=4;
+				break;
+			}
+			case SET_SCALE:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->mScale,offset,4);
+				p_dummy->mScale=Script::Read4Bytes((uint8*)&p_dummy->mScale).mFloat;
+				if (p_dummy->mp_rendered_model)
+				{
+					Mth::Vector s(p_dummy->mScale,p_dummy->mScale,p_dummy->mScale);
+					p_dummy->mp_rendered_model->SetScale(s);
+				}	
+				offset+=4;
+				break;
+			}	
+			case SET_CAR_ROTATION_X:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_car_rotation_x,offset,4);
+				p_dummy->m_car_rotation_x=Script::Read4Bytes((uint8*)&p_dummy->m_car_rotation_x).mFloat;
+				p_dummy->m_car_rotation_x_vel=0.0f;
+				offset+=4;
+				break;
+			}	
+			case SET_WHEEL_ROTATION_X:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_wheel_rotation_x,offset,4);
+				p_dummy->m_wheel_rotation_x=Script::Read4Bytes((uint8*)&p_dummy->m_wheel_rotation_x).mFloat;
+				p_dummy->m_wheel_rotation_x_vel=0.0f;
+				offset+=4;
+				break;
+			}	
+			case SET_WHEEL_ROTATION_Y:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_wheel_rotation_y,offset,4);
+				p_dummy->m_wheel_rotation_y=Script::Read4Bytes((uint8*)&p_dummy->m_wheel_rotation_y).mFloat;
+				p_dummy->m_wheel_rotation_y_vel=0.0f;
+				offset+=4;
+				break;
+			}	
+			case SET_CAR_ROTATION_X_VEL:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_car_rotation_x_vel,offset,4);
+				p_dummy->m_car_rotation_x_vel=Script::Read4Bytes((uint8*)&p_dummy->m_car_rotation_x_vel).mFloat;
+				offset+=4;
+				break;
+			}	
+			case SET_WHEEL_ROTATION_X_VEL:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_wheel_rotation_x_vel,offset,4);
+				p_dummy->m_wheel_rotation_x_vel=Script::Read4Bytes((uint8*)&p_dummy->m_wheel_rotation_x_vel).mFloat;
+				offset+=4;
+				break;
+			}	
+			case SET_WHEEL_ROTATION_Y_VEL:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_wheel_rotation_y_vel,offset,4);
+				p_dummy->m_wheel_rotation_y_vel=Script::Read4Bytes((uint8*)&p_dummy->m_wheel_rotation_y_vel).mFloat;
+				offset+=4;
+				break;
+			}	
+			
+			case SET_ANGLE_X:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_angles[X],offset,4);
+				p_dummy->m_angles[X]=Script::Read4Bytes((uint8*)&p_dummy->m_angles[X]).mFloat;
+				p_dummy->m_ang_vel[X]=0.0f;
+				offset+=4;
+				break;
+			}	
+			case SET_ANGLE_Y:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_angles[Y],offset,4);
+				p_dummy->m_angles[Y]=Script::Read4Bytes((uint8*)&p_dummy->m_angles[Y]).mFloat;
+				p_dummy->m_ang_vel[Y]=0.0f;
+				offset+=4;
+				break;
+			}	
+			case SET_ANGLE_Z:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_angles[Z],offset,4);
+				p_dummy->m_angles[Z]=Script::Read4Bytes((uint8*)&p_dummy->m_angles[Z]).mFloat;
+				p_dummy->m_ang_vel[Z]=0.0f;
+				offset+=4;
+				break;
+			}	
+			case SET_ANGULAR_VELOCITY_X:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_ang_vel[X],offset,4);
+				p_dummy->m_ang_vel[X]=Script::Read4Bytes((uint8*)&p_dummy->m_ang_vel[X]).mFloat;
+				offset+=4;
+				break;
+			}	
+			case SET_ANGULAR_VELOCITY_Y:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_ang_vel[Y],offset,4);
+				p_dummy->m_ang_vel[Y]=Script::Read4Bytes((uint8*)&p_dummy->m_ang_vel[Y]).mFloat;
+				offset+=4;
+				break;
+			}	
+			case SET_ANGULAR_VELOCITY_Z:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_ang_vel[Z],offset,4);
+				p_dummy->m_ang_vel[Z]=Script::Read4Bytes((uint8*)&p_dummy->m_ang_vel[Z]).mFloat;
+				offset+=4;
+				break;
+			}	
+				
+			case SET_POSITION:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_pos[X],offset,4*3);
+				p_dummy->m_pos[X]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[X]).mFloat;
+				p_dummy->m_pos[Y]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[Y]).mFloat;
+				p_dummy->m_pos[Z]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[Z]).mFloat;
+				p_dummy->m_vel.Set();
+				
+				offset+=4*3;
+				break;
+			}	
+			
+			case SET_POSITION_ANGLES:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				
+				ReadFromBuffer((uint8*)&p_dummy->m_pos[X],offset,4*3);
+				p_dummy->m_pos[X]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[X]).mFloat;
+				p_dummy->m_pos[Y]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[Y]).mFloat;
+				p_dummy->m_pos[Z]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[Z]).mFloat;
+				offset+=4*3;
+
+				ReadFromBuffer((uint8*)&p_dummy->m_angles[X],offset,4*3);
+				p_dummy->m_angles[X]=Script::Read4Bytes((uint8*)&p_dummy->m_angles[X]).mFloat;
+				p_dummy->m_angles[Y]=Script::Read4Bytes((uint8*)&p_dummy->m_angles[Y]).mFloat;
+				p_dummy->m_angles[Z]=Script::Read4Bytes((uint8*)&p_dummy->m_angles[Z]).mFloat;
+				offset+=4*3;
+				
+				p_dummy->m_vel.Set();
+				p_dummy->m_ang_vel.Set();
+				break;
+			}	
+			
+			case SET_VELOCITY:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				ReadFromBuffer((uint8*)&p_dummy->m_vel[X],offset,4*3);
+				p_dummy->m_vel[X]=Script::Read4Bytes((uint8*)&p_dummy->m_vel[X]).mFloat;
+				p_dummy->m_vel[Y]=Script::Read4Bytes((uint8*)&p_dummy->m_vel[Y]).mFloat;
+				p_dummy->m_vel[Z]=Script::Read4Bytes((uint8*)&p_dummy->m_vel[Z]).mFloat;
+				offset+=4*3;
+				break;
+			}	
+			case PRIMARY_ANIM_CONTROLLER_CHANGES:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				offset=sReadAnimControllerChanges(offset,&p_dummy->m_primaryController);
+				break;
+			}	
+			case DEGENERATE_ANIM_CONTROLLER_CHANGES:
+			{
+				++offset;
+				uint8 index=0;
+				ReadFromBuffer(&index,offset,1);
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				Dbg_MsgAssert(p_dummy->GetID()==ID_SKATER,("Got degenerate anim changes for a non-skater ?"));
+				offset=sReadAnimControllerChanges(offset,&p_dummy->m_degenerateControllers[index]);
+				break;
+			}	
+			case HOVERING:
+			{
+				++offset;
+				Dbg_MsgAssert(p_dummy,("NULL p_dummy ?"));
+				p_dummy->mFlags|=DUMMY_FLAG_HOVERING;
+				
+				ReadFromBuffer((uint8*)&p_dummy->m_pos[X],offset,4*3);
+				p_dummy->m_pos[X]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[X]).mFloat;
+				p_dummy->m_pos[Y]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[Y]).mFloat;
+				p_dummy->m_pos[Z]=Script::Read4Bytes((uint8*)&p_dummy->m_pos[Z]).mFloat;
+				offset+=4*3;
+				
+				break;
+			}	
+			
+			case SECTOR_ACTIVE:
+			case SECTOR_INACTIVE:
+			{
+				++offset;
+				uint32 sector_name;
+				ReadFromBuffer((uint8*)§or_name,offset,4);
+				sector_name=Script::Read4Bytes((uint8*)§or_name).mUInt;
+				offset+=4;
+				
+				Nx::CSector *p_sector = Nx::CEngine::sGetSector(sector_name);
+				if (p_sector)
+				{
+					if (sMode==RECORD)
+					{
+						// If recording, apply to the start state.
+						p_sector->SetActiveAtReplayStart(token==SECTOR_ACTIVE);
+					}
+					else
+					{
+						// Otherwise apply to the current world.
+						p_sector->SetActive(token==SECTOR_ACTIVE);
+					}
+				}
+				break;
+			}	
+
+			case SECTOR_VISIBLE:
+			case SECTOR_INVISIBLE:
+			{
+				++offset;
+				uint32 sector_name;
+				ReadFromBuffer((uint8*)§or_name,offset,4);
+				sector_name=Script::Read4Bytes((uint8*)§or_name).mUInt;
+				offset+=4;
+				
+				Nx::CSector *p_sector = Nx::CEngine::sGetSector(sector_name);
+				if (p_sector)
+				{
+					if (sMode==RECORD)
+					{
+						// If recording, apply to the start state.
+						p_sector->SetVisibleAtReplayStart(token==SECTOR_VISIBLE);
+					}
+					else
+					{
+						// Otherwise apply to the current world.
+						p_sector->SetVisibility(token==SECTOR_VISIBLE ?0xff:0x00);
+					}
+				}
+				break;
+			}	
+			
+			default:
+				Dbg_MsgAssert(0,("Unsupported token type of %d in sReadFrame",token));
+				break;
+		}
+	}	
+
+	// Update all the dummy objects
+	p_dummy=sGetDummyListHeadPointer(whichList);
+	while (p_dummy)
+	{
+		if (whichList==PLAYBACK_DUMMY_LIST && !p_dummy->mp_rendered_model && p_dummy->m_is_displayed)
+		{
+			p_dummy->CreateModel();
+		}
+		
+		p_dummy->Update();		
+		p_dummy=p_dummy->mpNext;
+	}	
+
+
+
+	#ifdef CHECK_TOKEN_USAGE
+	sTokenCount[OBJECT_ID]=0;
+	int biggest=0;
+	int worst_token=-1;
+	for (int i=0; ibiggest)
+		{
+			biggest=sTokenCount[i];
+			worst_token=i;
+		}	
+	}	
+	printf("Worst token = %s\n",sGetTokenName((EReplayToken)worst_token));
+	#endif
+
+
+
+
+	return offset;
+}
+
+// After calling this, sBigBufferEndOffset will be guaranteed to point to enough contiguous space
+// to hold frameSize bytes.
+static void sMakeEnoughSpace(uint32 frameSize)
+{
+	// This loop makes one modification to the buffer each time around, and breaks out once
+	// enough space has been freed.
+	while (true)
+	{
+		if (sBigBufferStartOffset < sBigBufferEndOffset)
+		{
+			Dbg_MsgAssert(sBigBufferStartOffset==0,("sBigBufferStartOffset not zero ?"));
+			
+			// We know that all the space from sBigBufferEndOffset to the end of the buffer
+			// is free to use, so check if that space is big enough.
+			if (GetBufferSize()-sBigBufferEndOffset >= frameSize)
+			{
+				// It is!
+				break;
+			}
+			else
+			{
+				// Wrap the end offset around to the start of the buffer, so that next time
+				// around this loop frames at the start will start getting chomped up to make space.
+				sBigBufferEndOffset=0;
+				
+				sBufferFilling=false;
+			}	
+		}
+		else if (sBigBufferStartOffset > sBigBufferEndOffset)
+		{
+			// The space between end and start is free to use, so check if it is big enough. 
+			if (sBigBufferStartOffset-sBigBufferEndOffset >= frameSize)
+			{
+				// It is!
+				break;
+			}
+			else
+			{
+				// Skip the start offset over the frame it is pointing to.
+				bool nothing_follows=false;
+				uint32 old_start=sBigBufferStartOffset;
+				sBigBufferStartOffset=sReadFrame(sBigBufferStartOffset,¬hing_follows,START_STATE_DUMMY_LIST);
+				if (sBigBufferStartOffset==old_start)
+				{
+					// The start offset did not move, meaning it is pointing to empty space.
+					// The only time this should happen is when the buffer is empty, in which
+					// case the start and end offset should both be zero. So assert.
+					Dbg_MsgAssert(0,("Start offset points to empty space, even though it is greater than end offset ?"));
+					
+					// Just in case this does ever happen on a release build, reset the buffer so that
+					// it does not hang. All that will happen is that the replay will be lost.
+					sDeleteDummies(START_STATE_DUMMY_LIST);
+					sDeleteDummies(PLAYBACK_DUMMY_LIST);
+					Dbg_MsgAssert(spReplayDummies==NULL,("Hey! spReplayDummies not NULL !!!"));
+					sDeleteObjectTrackingInfo();
+					ClearBuffer();
+				}
+				else
+				{
+					// Fill the frame just skipped over with BLANK's
+					FillBuffer(old_start,sBigBufferStartOffset-old_start,BLANK);
+					// Wrap around if necessary.
+					if (nothing_follows)
+					{
+						sBigBufferStartOffset=0;
+					}	
+				}	
+			}
+		}
+		else
+		{
+			// sBigBufferStartOffset equals sBigBufferEndOffset
+			bool nothing_follows=false;
+			uint32 old_start=sBigBufferStartOffset;
+			sBigBufferStartOffset=sReadFrame(sBigBufferStartOffset,¬hing_follows,START_STATE_DUMMY_LIST);
+			if (sBigBufferStartOffset==old_start)
+			{
+				// If the start offset did not move, it must be pointing to empty space.
+				// The only time the start offset should point to empty space is when the 
+				// buffer is totally empty, in which case the start offset would be expected to be zero.
+				// So check that.
+				Dbg_MsgAssert(sBigBufferStartOffset==0,("Expected sBigBufferStartOffset to be zero"));
+				// Check that the frame size is not bigger than the entire buffer.
+				Dbg_MsgAssert(GetBufferSize() >= frameSize,("Frame size %d too big for replay buffer",frameSize));
+				// There is enough space, so break out.
+				break;
+			}
+			else
+			{
+				// Fill the frame just skipped over with BLANK's
+				FillBuffer(old_start,sBigBufferStartOffset-old_start,BLANK);
+				// Wrap around if necessary.
+				if (nothing_follows)
+				{
+					sBigBufferStartOffset=0;
+				}	
+			}	
+		}
+	}	
+}
+
+static void sWriteFrameBufferToBigBuffer()
+{
+	uint32 frame_size=spFrameBufferPos-spFrameBuffer;
+
+	sMakeEnoughSpace(frame_size);
+
+	// sBigBufferEndOffset is now guaranteed to point to enough contiguous space to
+	// hold frame_size bytes, so write in the frame data.
+	WriteIntoBuffer(spFrameBuffer,sBigBufferEndOffset,frame_size);
+	sBigBufferEndOffset+=frame_size;
+	// Wrap around if necessary.
+	if (sBigBufferEndOffset==GetBufferSize())
+	{
+		sBigBufferEndOffset=0;
+	}	
+}
+
+static void sWriteSingleToken(EReplayToken token)
+{
+	Dbg_MsgAssert(!Mdl::Skate::Instance()->IsMultiplayerGame(),("Replay Active in Multiplayer game!"));
+	Dbg_MsgAssert(spFrameBufferPos+1 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	*spFrameBufferPos++=token;
+}
+
+/*
+static void sWriteUint8(EReplayToken token, uint8 v)
+{
+	Dbg_MsgAssert(spFrameBufferPos+2 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	*spFrameBufferPos++=token;
+	*spFrameBufferPos++=v;
+}
+*/
+
+static void sWriteUint8(uint8 v)
+{
+	Dbg_MsgAssert(spFrameBufferPos+1 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	*spFrameBufferPos++=v;
+}
+
+static void sWriteUint32(EReplayToken token, uint32 v)
+{
+	Dbg_MsgAssert(spFrameBufferPos+5 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	*spFrameBufferPos++=token;
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, v);
+}
+
+static void sWriteUint32(uint32 v)
+{
+	Dbg_MsgAssert(spFrameBufferPos+4 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, v);
+}
+
+static void sWriteFloat(EReplayToken token, float f)
+{
+	Dbg_MsgAssert(spFrameBufferPos+5 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	*spFrameBufferPos++=token;
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, f);
+}
+
+static void sWriteFloat(float f)
+{
+	Dbg_MsgAssert(spFrameBufferPos+4 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, f);
+}
+
+/*
+static void sWrite2Floats(EReplayToken token, float a, float b)
+{
+	Dbg_MsgAssert(spFrameBufferPos+9 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	*spFrameBufferPos++=token;
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, a);
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, b);
+}
+*/
+
+static void sWriteVector(EReplayToken token, Mth::Vector &v)
+{
+	Dbg_MsgAssert(spFrameBufferPos+13 <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	*spFrameBufferPos++=token;
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, v[X]);
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, v[Y]);
+	spFrameBufferPos=Script::Write4Bytes(spFrameBufferPos, v[Z]);
+}
+
+#if 0
+static void sWriteString(EReplayToken token, const char *p_string)
+{
+	Dbg_MsgAssert(p_string,("NULL p_string"));
+	int len=strlen(p_string);
+	Dbg_MsgAssert(len<256,("String length too big, '%s'",p_string));
+	Dbg_MsgAssert(spFrameBufferPos+2+len <= spFrameBuffer+FRAME_BUFFER_SIZE,("Replay frame buffer overflow"));
+	
+	*spFrameBufferPos++=token;
+	*spFrameBufferPos++=len;
+	for (int i=0; iGetVolumeType());
+	sWriteFloat(p_volume->GetChannelVolume(0));
+	sWriteFloat(p_volume->GetChannelVolume(1));
+	if( p_volume->GetVolumeType() == Sfx::VOLUME_TYPE_5_CHANNEL_DOLBY5_1 )
+	{
+		sWriteFloat(p_volume->GetChannelVolume(2));
+		sWriteFloat(p_volume->GetChannelVolume(3));
+		sWriteFloat(p_volume->GetChannelVolume(4));
+	}
+
+	sWriteFloat(pitch);
+	sWriteUint32(priority);
+}
+
+void WriteStopStream(int channel)
+{
+	if (sMode!=RECORD)
+	{
+		return;
+	}
+	sWriteSingleToken(STOP_STREAM);
+	sWriteUint32(channel);
+}
+
+void WritePositionalStream(uint32 dummyId, uint32 streamNameChecksum, float dropoff, float volume, float pitch, int priority, int use_pos_info)
+{
+	if (sMode!=RECORD)
+	{
+		return;
+	}	
+	sWriteSingleToken(PLAY_POSITIONAL_STREAM);
+	sWriteUint32(dummyId);
+	sWriteUint32(streamNameChecksum);
+	sWriteFloat(dropoff);
+	sWriteFloat(volume);
+	sWriteFloat(pitch);
+	sWriteUint32(priority);
+	sWriteUint32(use_pos_info);
+}	
+
+void WritePositionalSoundEffect(uint32 dummyId, uint32 soundName, float volume, float pitch, float dropOffDist)
+{
+	if (sMode!=RECORD)
+	{
+		return;
+	}	
+	sWriteSingleToken(PLAY_POSITIONAL_SOUND_EFFECT);
+	sWriteUint32(dummyId);
+	sWriteUint32(soundName);
+	sWriteFloat(volume);
+	sWriteFloat(pitch);
+	sWriteFloat(dropOffDist);
+}	
+
+// Called from CSk3SfxManager::PlaySound, takes the same parameters, except for 'propogate' (sic)
+// which I guess will always be false, since no replays in multiplayer.
+void WriteSkaterSoundEffect(int whichArray, int surfaceFlag, const Mth::Vector &pos, 
+							float volPercent)
+{
+	if (sMode!=RECORD)
+	{
+		return;
+	}	
+	sWriteSingleToken(PLAY_SKATER_SOUND_EFFECT);
+	sWriteUint32(whichArray);
+	sWriteUint32(surfaceFlag);
+	sWriteFloat(pos[X]);
+	sWriteFloat(pos[Y]);
+	sWriteFloat(pos[Z]);
+	sWriteFloat(volPercent);
+}
+
+void WritePlaySound(uint32 checksum, float volL, float volR, float pitch)
+{
+	if (sMode!=RECORD)
+	{
+		return;
+	}	
+	sWriteSingleToken(PLAY_SOUND);
+	sWriteUint32(checksum);
+	sWriteFloat(volL);
+	sWriteFloat(volR);
+	sWriteFloat(pitch);
+}
+
+static void sRecordSkaterCamera(CTrackingInfo *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	
+	if (p_info->mFlags & TRACKING_INFO_FLAG_OBJECT_CREATED)
+	{
+		sWriteUint32(CREATE_OBJECT,ID_CAMERA);
+		sWriteUint8(SKATE_TYPE_UNDEFINED);
+		p_info->mFlags &= ~TRACKING_INFO_FLAG_OBJECT_CREATED;
+	}	
+	else
+	{
+		// Write in the object Id
+		sWriteUint32(OBJECT_ID,ID_CAMERA);
+	}	
+	
+	Obj::CSkater *p_skater=sGetSkater();
+	Gfx::Camera *p_skater_camera=p_skater->GetActiveCamera();
+	Dbg_MsgAssert(p_skater_camera,("NULL p_skater_camera"));
+	
+	Mth::Vector actual_angles;
+	p_skater_camera->GetMatrix().GetEulers(actual_angles);
+	p_info->RecordPositionAndAngleChanges(p_skater_camera->GetPos(),actual_angles,true);
+}
+
+static bool sIsFlipped(Obj::CMovingObject *p_movingObject)
+{
+	Dbg_MsgAssert(p_movingObject,("NULL p_movingObject"));
+	
+	if (p_movingObject->GetType()==SKATE_TYPE_SKATER)
+	{
+		return GetSkaterCorePhysicsComponentFromObject(p_movingObject)->GetFlag(Obj::FLIPPED);
+	}
+	else
+	{
+		Dbg_MsgAssert( 0, ( "Flipped-ness has been moved to animation component" ) );
+		return false;
+//		return p_movingObject->IsFlipped();
+	}
+}
+
+static void sRecordCMovingObject(CTrackingInfo *p_info)
+{
+	Dbg_MsgAssert(p_info,("NULL p_info"));
+	Dbg_MsgAssert(p_info->mPointerType==CMOVINGOBJECT,("Not a moving object?"));
+	
+	if (!p_info->mpMovingObject)
+	{
+		sWriteUint32(KILL_OBJECT,p_info->m_id);
+		delete p_info;
+		return;
+	}	
+	
+	Obj::CMovingObject *p_moving_object=p_info->mpMovingObject;
+	Nx::CModel* p_model=p_moving_object->GetModel();
+
+	uint8 *p_start=spFrameBufferPos;
+	
+	if (p_info->mFlags & TRACKING_INFO_FLAG_OBJECT_CREATED)
+	{
+		sWriteUint32(CREATE_OBJECT,p_moving_object->GetID());
+		sWriteUint8(p_moving_object->GetType());
+		p_info->mFlags &= ~TRACKING_INFO_FLAG_OBJECT_CREATED;
+
+		Obj::CAnimationComponent* pAnimComponent = GetAnimationComponentFromObject( p_moving_object );
+        if ( pAnimComponent )
+        {
+            sWriteUint32(ANIM_SCRIPT_NAME,pAnimComponent->GetAnimScriptName());
+        }
+        else
+        {
+            sWriteUint32(ANIM_SCRIPT_NAME,0);
+        }
+
+		switch (p_moving_object->GetType())
+		{
+			case SKATE_TYPE_SKATER:
+				sWriteUint32(SKELETON_NAME,0x5a9d2a0a/*Human*/);
+				break;
+			case SKATE_TYPE_CAR:
+			{
+				/*
+				Obj::CCar *p_car=(Obj::CCar*)p_moving_object;
+				Obj::CModelRestorationInfo *p_model_restoration_info=&p_car->m_model_restoration_info;
+				
+				sWriteUint32(SKELETON_NAME,p_model_restoration_info->mSkeletonName);
+				sWriteString(MODEL_NAME,p_model_restoration_info->GetModelName());
+				*/
+				break;
+			}	
+			case SKATE_TYPE_PED:
+			{
+				/*
+				Obj::CPed *p_ped=(Obj::CPed*)p_moving_object;
+				Obj::CModelRestorationInfo *p_model_restoration_info=&p_ped->m_model_restoration_info;
+				
+				sWriteUint32(SKELETON_NAME,p_model_restoration_info->mSkeletonName);
+				if (p_model_restoration_info->mProfileName)
+				{
+					sWriteUint32(PROFILE_NAME,p_model_restoration_info->mProfileName);
+				}
+				else
+				{
+					sWriteString(MODEL_NAME,p_model_restoration_info->GetModelName());
+				}
+				*/
+				break;
+			}	
+			case SKATE_TYPE_GAME_OBJ:
+			{
+				/*
+				Obj::CGameObj *p_game_ob=(Obj::CGameObj*)p_moving_object;
+				Obj::CModelRestorationInfo *p_model_restoration_info=&p_game_ob->m_model_restoration_info;
+			 
+				sWriteUint32(SECTOR_NAME,p_model_restoration_info->mSectorName);
+				sWriteUint32(SKELETON_NAME,p_model_restoration_info->mSkeletonName);
+				sWriteString(MODEL_NAME,p_model_restoration_info->GetModelName());
+				*/
+				break;
+			}
+			/*
+			// CBouncyObj has been replaced by CComposite object
+			case SKATE_TYPE_BOUNCY_OBJ:
+			{
+				Obj::CBouncyObj *p_bouncy_ob=(Obj::CBouncyObj*)p_moving_object;
+				Obj::CModelRestorationInfo *p_model_restoration_info=&p_bouncy_ob->m_model_restoration_info;
+				
+				sWriteUint32(SECTOR_NAME,p_model_restoration_info->mSectorName);
+				break;
+			}
+			*/
+			default:
+				printf("Created object, type=%d\n",p_moving_object->GetType());
+				break;
+		}		
+
+		p_info->mFlags &= ~TRACKING_INFO_FLAG_ACTIVE;
+		p_info->mFlags &= ~TRACKING_INFO_FLAG_FLIPPED;
+		p_info->mFlags &= ~TRACKING_INFO_FLAG_HOVERING;
+		
+		// Write out a SET_ATOMIC_STATES token to initialise the atomic states of the
+		// dummy when it gets created later.
+		if (p_model)
+		{
+			sWriteUint32(SET_ATOMIC_STATES,p_moving_object->GetID());
+			sWriteUint32(p_model->GetGeomActiveMask());
+		}	
+	}	
+	else
+	{
+		// Write in the object Id
+		sWriteUint32(OBJECT_ID,p_moving_object->GetID());
+	}	
+
+	if (p_model && p_model->GetScale().GetX() != p_info->mScale)
+	{
+		// Some peds do use non 1 scale, eg the gorilla in the tram in the zoo
+		// And the shark in sf2
+		sWriteFloat(SET_SCALE,p_model->GetScale().GetX());
+		p_info->mScale=p_model->GetScale().GetX();
+	}	
+
+	bool hovering=false;
+	Obj::CMotionComponent *p_motion_component=GetMotionComponentFromObject(p_moving_object);
+	if (p_motion_component)
+	{
+		hovering = p_motion_component->IsHovering();
+	}
+
+	
+	
+	if (hovering)
+	{
+		if (p_info->mFlags & TRACKING_INFO_FLAG_HOVERING)
+		{
+		}
+		else
+		{
+			p_info->mFlags |= TRACKING_INFO_FLAG_HOVERING;
+			Mth::Vector pos;
+			p_motion_component->GetHoverOrgPos(&pos);
+			
+			sWriteSingleToken(HOVERING);
+			sWriteFloat(pos[X]);
+			sWriteFloat(pos[Y]);
+			sWriteFloat(pos[Z]);
+		}	
+	}
+	else
+	{
+		if (p_info->mFlags & TRACKING_INFO_FLAG_HOVERING)
+		{
+			// Need to support this? I don't think anything ever goes from hovering to not hovering?
+		}
+	}
+						
+	bool flipped=sIsFlipped(p_moving_object);
+	if (flipped)
+	{
+		if (p_info->mFlags & TRACKING_INFO_FLAG_FLIPPED)
+		{
+		}
+		else
+		{
+			p_info->mFlags |= TRACKING_INFO_FLAG_FLIPPED;
+			sWriteSingleToken(FLIP);
+		}	
+	}
+	else
+	{
+		if (p_info->mFlags & TRACKING_INFO_FLAG_FLIPPED)
+		{
+			p_info->mFlags &= ~TRACKING_INFO_FLAG_FLIPPED;
+			sWriteSingleToken(UNFLIP);
+		}
+	}
+
+	bool active=false;
+	if (p_model)
+	{
+		active=p_model->GetActive();
+	}
+	if (active)
+	{
+		if (p_info->mFlags & TRACKING_INFO_FLAG_ACTIVE)
+		{
+		}
+		else
+		{
+			p_info->mFlags |= TRACKING_INFO_FLAG_ACTIVE;
+			sWriteSingleToken(MODEL_ACTIVE);
+		}	
+	}
+	else
+	{
+		if (p_info->mFlags & TRACKING_INFO_FLAG_ACTIVE)
+		{
+			p_info->mFlags &= ~TRACKING_INFO_FLAG_ACTIVE;
+			sWriteSingleToken(MODEL_INACTIVE);
+		}
+	}
+	
+	//Mth::Matrix objects_display_matrix=p_moving_object->GetDisplayMatrix();
+	//objects_display_matrix[Mth::POS]=p_moving_object->GetPos();
+	//Mth::Vector actual_pos=objects_display_matrix[Mth::POS];
+	
+	if (!hovering)
+	{
+		Mth::Vector actual_pos=p_moving_object->GetPos();
+		Mth::Vector actual_angles;
+		
+		//if (p_moving_object->IsSpecialItem())
+		//{
+		//	printf("Recording exact pos of special item %x\n",p_moving_object->GetID());
+		//}
+			
+		// GJ:  Need to reimplement special-case special item code
+		// because the current implementation conflicts with our
+		// new CCOmpositeObject model.
+		if (p_info->m_id==ID_SKATER )
+//		if (p_info->m_id==ID_SKATER || p_moving_object->IsSpecialItem())
+		{
+			p_moving_object->GetDisplayMatrix().GetEulers(actual_angles);
+			p_info->RecordPositionAndAngleChanges(actual_pos,actual_angles,true);
+		}
+		else
+		{
+			if (p_moving_object->GetDisplayMatrix() != p_info->mLastMatrix)
+			{
+				p_moving_object->GetDisplayMatrix().GetEulers(actual_angles);
+				p_info->mLastMatrix=p_moving_object->GetDisplayMatrix();
+			}
+			else
+			{
+				actual_angles[X]=p_info->mTrackerAngleX.GetActualLast();
+				actual_angles[Y]=p_info->mTrackerAngleY.GetActualLast();
+				actual_angles[Z]=p_info->mTrackerAngleZ.GetActualLast();
+			}	
+			p_info->RecordPositionAndAngleChanges(actual_pos,actual_angles);
+		}	
+	}
+		
+	// Do the animation stuff ...
+#if 0
+	uint8 *p_num_changes=NULL;
+	uint8 num_changes=0;
+	
+	Obj::CAnimationComponent* pAnimComponent = GetAnimationComponentFromObject( p_moving_object );
+	if ( pAnimComponent && pAnimComponent->HasAnims() )
+	{
+		sWriteSingleToken(PRIMARY_ANIM_CONTROLLER_CHANGES);
+		p_num_changes=spFrameBufferPos;
+		++spFrameBufferPos;
+		num_changes=sWriteCAnimChannelChanges(&p_info->m_primaryController,pAnimComponent->GetPrimaryController());
+		if (!num_changes)
+		{
+			spFrameBufferPos-=2;
+		}
+		else
+		{
+			p_info->m_primaryController=*pAnimComponent->GetPrimaryController();
+			*p_num_changes=num_changes;
+		}
+	}	
+	
+	if (p_info->m_id==ID_SKATER)
+	{
+		for (int i=0; isSkaterTrackingInfo.m_degenerateControllers[i],pAnimComponent->GetDegenerateController(i));
+            }
+            else
+            {
+                num_changes=0;
+            }
+			if (!num_changes)
+			{
+				spFrameBufferPos-=3;
+			}	
+			else
+			{
+				p_info->sSkaterTrackingInfo.m_degenerateControllers[i]=*pAnimComponent->GetDegenerateController(i);
+				*p_num_changes=num_changes;
+			}
+		}
+		
+		// Check for skater looping sound changes
+		uint32 skater_looping_sound_id=0;
+		uint32 skater_looping_sound_checksum=0;
+		float skater_pitch_min;
+		float skater_pitch_max;
+		Obj::CSkater *p_skater=(Obj::CSkater*)p_moving_object;
+		
+		// NOTE: this information has moved to CSkaterLoopingSoundComponent
+		p_skater->GetLoopingSoundInfo(	&skater_looping_sound_id,
+										&skater_looping_sound_checksum,
+										&skater_pitch_min,
+										&skater_pitch_max);
+		if (skater_looping_sound_id)
+		{
+			// Skater is playing a looping sound
+			if (CTrackingInfo::sSkaterTrackingInfo.m_playing_looping_sound)
+			{
+				// Tracker already knows the skater was playing a looping sound, but
+				// perhaps the name of the sound has changed.
+				if (CTrackingInfo::sSkaterTrackingInfo.m_looping_sound_checksum != skater_looping_sound_checksum)
+				{
+					sWriteUint32(PLAY_LOOPING_SOUND,skater_looping_sound_checksum);
+					CTrackingInfo::sSkaterTrackingInfo.m_looping_sound_checksum = skater_looping_sound_checksum;
+				}
+			}	
+			else
+			{
+				sWriteUint32(PLAY_LOOPING_SOUND,skater_looping_sound_checksum);
+				CTrackingInfo::sSkaterTrackingInfo.m_looping_sound_checksum = skater_looping_sound_checksum;
+				CTrackingInfo::sSkaterTrackingInfo.m_playing_looping_sound=true;
+			}	
+			
+			if (skater_pitch_min != CTrackingInfo::sSkaterTrackingInfo.m_pitch_min)
+			{
+				sWriteFloat(PITCH_MIN,skater_pitch_min);
+				CTrackingInfo::sSkaterTrackingInfo.m_pitch_min = skater_pitch_min;
+			}	
+			if (skater_pitch_max != CTrackingInfo::sSkaterTrackingInfo.m_pitch_max)
+			{
+				sWriteFloat(PITCH_MAX,skater_pitch_max);
+				CTrackingInfo::sSkaterTrackingInfo.m_pitch_max = skater_pitch_max;
+			}	
+		}
+		else
+		{
+			if (CTrackingInfo::sSkaterTrackingInfo.m_playing_looping_sound)
+			{
+				sWriteSingleToken(STOP_LOOPING_SOUND);
+				CTrackingInfo::sSkaterTrackingInfo.m_playing_looping_sound=false;
+				CTrackingInfo::sSkaterTrackingInfo.m_looping_sound_checksum=0;
+			}
+		}		
+	}	
+#endif
+
+	if (p_moving_object->GetType()==SKATE_TYPE_CAR)
+	{
+		float car_rotation_x, wheel_rotation_x, wheel_rotation_y;
+		((Obj::CCar*)p_moving_object)->GetRotationValues(&car_rotation_x,&wheel_rotation_x,&wheel_rotation_y);
+		
+		p_info->mTrackerCarRotationX.WriteChanges(car_rotation_x,SET_CAR_ROTATION_X,SET_CAR_ROTATION_X_VEL);
+		p_info->mTrackerWheelRotationX.WriteChanges(wheel_rotation_x,SET_WHEEL_ROTATION_X,SET_WHEEL_ROTATION_X_VEL);
+		p_info->mTrackerWheelRotationY.WriteChanges(wheel_rotation_y,SET_WHEEL_ROTATION_Y,SET_WHEEL_ROTATION_Y_VEL);
+	}
+	
+	// If no changes at all got written in for this object, then remove the object Id.
+	// This is an important space optimization, because if this redundant info were not
+	// removed then each frame would be several hundred bytes bigger.
+	if (*p_start==OBJECT_ID && spFrameBufferPos-p_start==5)
+	{
+		spFrameBufferPos=p_start;
+	}	
+}
+
+//static int record_frame=0;
+static void sRecord()
+{
+	CTrackingInfo *p_info=spTrackingInfoHead;
+	while (p_info)
+	{
+		CTrackingInfo *p_next=p_info->mpNext;
+
+		if (p_info->m_id==ID_CAMERA)
+		{
+			sRecordSkaterCamera(p_info);
+		}
+		else
+		{
+			switch (p_info->mPointerType)
+			{
+				case CMOVINGOBJECT:
+					sRecordCMovingObject(p_info);
+					break;
+				default:
+					break;
+			}
+		}
+									
+		p_info=p_next;
+	}	
+
+	int current_blur=Nx::CEngine::sGetScreenBlur();
+	if (current_blur != CTrackingInfo::sScreenBlurTracker)
+	{
+		sWriteSingleToken(SCREEN_BLUR);
+		sWriteUint32(current_blur);
+		CTrackingInfo::sScreenBlurTracker=current_blur;
+	}	
+	
+	// REMOVE
+	//printf("Size=%d\n",spFrameBufferPos-spFrameBuffer);
+	
+	sWriteFrameBufferToBigBuffer();
+}
+
+//static int playback_frame=0;
+static void sPlayback(bool display)
+{
+	// If reached the end of the replay, do nothing.
+	if (sReachedEndOfReplay)
+	{
+		return;
+	}	
+	if (sReplayPaused)
+	{
+		return;
+	}	
+
+	Obj::CSkater *p_skater=sGetSkater();
+	// NGC must not replay vibrations, TRC requirement.
+	if (Config::GetHardware() != Config::HARDWARE_NGC)
+	{
+		if (sNeedToInitialiseVibration)
+		{
+			if (!sCurrentState.mSkaterPaused)
+			{
+				for (int i=0; iGetDevice()->ActivateActuator(i,sCurrentState.mActuatorStrength[i]);
+				}	
+			}	
+			sNeedToInitialiseVibration=false;
+		}
+	}
+		
+	// Read the next frame
+	bool nothing_follows=false;
+	sNextPlaybackFrameOffset=sReadFrame(sNextPlaybackFrameOffset,¬hing_follows,PLAYBACK_DUMMY_LIST);
+
+	// The process of reading the frame will have updated all the dummy's.
+	// Now display them & do other misc stuff like update sounds.
+	CDummy *p_dummy=spReplayDummies;
+	while (p_dummy)
+	{
+		p_dummy->UpdateMutedSounds();
+		if (display)
+		{
+			p_dummy->DisplayModel();
+		}	
+		p_dummy=p_dummy->mpNext;
+	}
+
+
+	// Wrap around if reached the end of the replay buffer.
+	if (sNextPlaybackFrameOffset==GetBufferSize())
+	{
+		sNextPlaybackFrameOffset=0;
+	}
+	
+	// It is possible that the frame could be followed by some space filled with
+	// BLANK, so skip over it until either a new frame is hit or we get back
+	// to the start frame.
+	while (true)
+	{
+		uint8 token;
+		ReadFromBuffer(&token,sNextPlaybackFrameOffset,1);
+		
+		if (token != BLANK)
+		{
+			// Hit a new frame
+			Dbg_MsgAssert(token==FRAME_START,("Expected token to be FRAME_START"));
+			break;
+		}
+		
+		// sBufferFilling is a flag used to indicate that the buffer is filling up for the
+		// first time and has not cycled around yet.
+		// Knowing this means we don't need to read each individual byte, since we know everything
+		// will be blank till the end of the buffer, so just set sNextPlaybackFrameOffset to 0
+		// straight away.
+		// This is a speed optimization for the GameCube, since it has a 4meg replay buffer, and
+		// it takes several seconds so read all those blanks.
+		if (sBufferFilling)
+		{
+			Dbg_MsgAssert(sBigBufferStartOffset==0,("Expected sBigBufferStartOffset to be zero when the buffer is filling up?"));
+			sNextPlaybackFrameOffset=0;
+		}
+		
+		if (sNextPlaybackFrameOffset==sBigBufferStartOffset)
+		{
+			// Reached the start again.
+			break;
+		}
+			
+		++sNextPlaybackFrameOffset;
+		if (sNextPlaybackFrameOffset==GetBufferSize())
+		{
+			sNextPlaybackFrameOffset=0;
+		}
+	}					
+		
+	// If we've got back to the start, then that is the end of the replay,
+	// so kill all the dummys and restore everything the way it was.
+	if (sNextPlaybackFrameOffset==sBigBufferStartOffset)
+	{
+		// Switch off any vibrations being done by the replay, so that they do not continue
+		// while the end-replay menu is on screen.
+		// Note that any vibrations that were being done in-game before the replay was run will
+		// automatically get restored when the game is un-paused, so we do not need to restore them here.
+		sSwitchOffVibrations();
+
+		sStopReplayDummyLoopingSounds();
+		
+		// Now that the replay has ended, the player may want to save it to mem card.
+		// So make sure that the start-state contains the correct active/visible status of all the sectors.
+		Nx::CEngine::sWriteSectorStatusBitfield(sStartState.mpSectorStatus,SECTOR_STATUS_BUFFER_SIZE);
+		
+		Script::RunScript("EndOfReplay");
+		sReachedEndOfReplay=true;
+	}	
+}
+
+static void sEnsureTrackerExists( Obj::CObject *p_ob, void *p_data )
+{
+	Dbg_MsgAssert(p_ob,("NULL p_ob"));
+
+	CTrackingInfo *p_info=sTrackingInfoHashTable.GetItem((uint32)p_ob->GetID());
+	if (!p_info)
+	{
+		int type=p_ob->GetType();
+		Dbg_MsgAssert(!(p_ob->GetID()==0 && type!=SKATE_TYPE_SKATER),("Non-skater object has an id of 0, type=%d\n",type));
+		
+		if (type==SKATE_TYPE_CAR ||
+			type==SKATE_TYPE_PED ||
+			type==SKATE_TYPE_BOUNCY_OBJ ||
+			type==SKATE_TYPE_SKATER ||
+			type==SKATE_TYPE_GAME_OBJ)
+		{
+			p_info=new CTrackingInfo;
+			p_info->SetMovingObject((Obj::CMovingObject*)p_ob);
+			p_info->mFlags |= TRACKING_INFO_FLAG_OBJECT_CREATED;
+		}	
+	}	
+}
+
+static void sEnsureCameraTrackerExists()
+{
+	CTrackingInfo *p_info=sTrackingInfoHashTable.GetItem((uint32)ID_CAMERA);
+	if (!p_info)
+	{
+		p_info=new CTrackingInfo;
+		p_info->SetSkaterCamera();
+		p_info->mFlags |= TRACKING_INFO_FLAG_OBJECT_CREATED;
+	}	
+}
+
+static void sHideForReplayPlayback( Obj::CObject *p_ob, void *p_data )
+{
+	Dbg_MsgAssert(p_ob,("NULL p_ob"));
+	p_ob->HideForReplayPlayback();
+}
+
+static void sRestoreAfterReplayPlayback( Obj::CObject *p_ob, void *p_data )
+{
+	Dbg_MsgAssert(p_ob,("NULL p_ob"));
+	p_ob->RestoreAfterReplayPlayback();
+}
+
+static void sClearLastPanelMessage()
+{
+	Script::CStruct *p_params=new Script::CStruct;
+	p_params->AddChecksum("id",sLastRecordedPanelMessageID);
+	
+	Script::RunScript("kill_panel_message_if_it_exists",p_params);
+	delete p_params;
+}
+
+static void sClearTrickAndScoreText()
+{
+	Front::CScreenElementManager* p_manager = Front::CScreenElementManager::Instance();
+	int index=sGetSkater()->GetHeapIndex();
+	Front::CTextBlockElement *p_text_block = (Front::CTextBlockElement *) p_manager->GetElement(0x44727dae/*the_trick_text*/ + index ).Convert();
+	if (p_text_block)
+	{
+		p_text_block->SetText(NULL, 0);
+	}
+	Front::CTextElement *p_score_pot_text = (Front::CTextElement *) p_manager->GetElement(0xf4d3a70e + index ).Convert(); // "the_score_pot_text"
+	if (p_score_pot_text)
+	{
+		p_score_pot_text->SetText("");
+	}
+	sTrickTextGotCleared=true;
+}
+
+bool ScriptClearTrickAndScoreText(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	sClearTrickAndScoreText();
+	return true;
+}
+
+bool ScriptPlaybackReplay(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	sClearTrickAndScoreText();
+	sClearLastPanelMessage();
+	Nx::MiscFXCleanup();
+	Nx::CEngine::sSetScreenBlur(0);
+	
+	// Check that there are no replay dummy's in existence at the moment.
+	Dbg_MsgAssert(spReplayDummies==NULL,("Expected spReplayDummies to be NULL")); 
+	sReplayDummysHashTable.IterateStart();
+	Dbg_MsgAssert(sReplayDummysHashTable.IterateNext()==NULL,("Expected sReplayDummysHashTable to be empty")); 
+
+	// Create the replay dummy's by making copies of the start-state dummy's.
+	CDummy *p_source_dummy=spStartStateDummies;
+	while (p_source_dummy)
+	{
+		CDummy *p_new_dummy=new CDummy(PLAYBACK_DUMMY_LIST,p_source_dummy->GetID());
+		// Make a copy of the contents of the CDummy.
+		// The assignement operator for CDummy is overloaded.
+		*p_new_dummy=*p_source_dummy;
+		
+		p_source_dummy=p_source_dummy->mpNext;
+	}	
+
+	Nx::CEngine::sPrepareSectorsForReplayPlayback(pParams->ContainsFlag("store"));
+	
+	sCurrentState=sStartState;
+	sNextPlaybackFrameOffset=sBigBufferStartOffset;
+	sLastTrickTextChecksum=0;
+	sMode=PLAYBACK;
+	sReachedEndOfReplay=false;
+	sReplayPaused=false;
+	
+	// NGC must not replay vibrations, TRC requirement.
+	if (Config::GetHardware()==Config::HARDWARE_NGC)
+	{
+		sSwitchOffVibrations();
+	}
+		
+	sNeedToInitialiseVibration=true;
+	sUnpauseCertainScreenElements();
+	
+	return true;
+}
+
+bool ScriptHideGameObjects( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	pSkate->GetObjectManager()->ProcessAllObjects( sHideForReplayPlayback, NULL );
+	return true;
+}
+
+bool ScriptShowGameObjects( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	Mdl::Skate * pSkate = Mdl::Skate::Instance();
+	pSkate->GetObjectManager()->ProcessAllObjects( sRestoreAfterReplayPlayback, NULL );
+	
+	// Make sure that the balance meters are not left on if the replay was quit during a balance.
+	sGetSkaterScoreObject()->SetManualMeter(false);
+	sGetSkaterScoreObject()->SetBalanceMeter(false);
+	
+	// Restore the active/visible state of the world sectors.
+	Nx::CEngine::sRestoreSectorsAfterReplayPlayback();
+	return true;
+}
+
+bool ScriptSwitchToReplayRecordMode( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	if (Mdl::Skate::Instance()->IsMultiplayerGame())
+	{
+		return false;
+	}	
+	sMode=RECORD;
+	return true;
+}
+
+bool ScriptSwitchToReplayIdleMode( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	if (Mdl::Skate::Instance()->IsMultiplayerGame())
+	{
+		return false;
+	}	
+	sMode=NONE;
+	return true;
+}
+
+bool RunningReplay()
+{
+	return sMode==PLAYBACK;
+}
+
+bool ScriptRunningReplay( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	return RunningReplay();
+}
+	
+bool ScriptPauseReplay( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	sReplayPaused=true;	
+	
+	// Switch off any vibration while the replay is paused
+	sSwitchOffVibrations();
+	
+	sStopReplayDummyLoopingSounds();
+	
+	// Then set this flag so that the sPlayback function will switch the vibration back
+	// on if need be once the replay is unpaused.
+	sNeedToInitialiseVibration=true;
+	return true;
+}
+
+bool ScriptUnPauseReplay( Script::CStruct *pParams, Script::CScript *pScript )
+{
+	sReplayPaused=false;	
+	sUnpauseCertainScreenElements();
+	return true;
+}
+	
+bool Paused()
+{
+	return sReplayPaused;
+}
+	
+void AddReplayMemCardInfo(Script::CStruct *p_struct)
+{
+	Dbg_MsgAssert(p_struct,("NULL p_struct"));
+	p_struct->AddChecksum("level_structure_name",sLevelStructureName);
+}
+
+void AddReplayMemCardSummaryInfo(Script::CStruct *p_struct)
+{
+	Dbg_MsgAssert(p_struct,("NULL p_struct"));
+	
+	Script::CStruct *p_level_def=Script::GetStructure(sLevelStructureName);
+	Dbg_MsgAssert(p_level_def,("Could not find level def '%s'",Script::FindChecksumName(sLevelStructureName)));
+	
+	const char *p_level_name="";
+	p_level_def->GetString("Name",&p_level_name);
+	
+	p_struct->AddString("LevelName",p_level_name);
+}
+
+bool ScriptRememberLevelStructureNameForReplays(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pParams->GetChecksum("level_structure_name",&sLevelStructureName,Script::ASSERT);
+	return true;
+}
+
+void SetLevelStructureName(uint32 level_structure_name)
+{
+	sLevelStructureName=level_structure_name;
+}
+
+bool ScriptGetReplayLevelStructureName(Script::CStruct *pParams, Script::CScript *pScript)
+{
+	pScript->GetParams()->AddChecksum("level_structure_name",sLevelStructureName);
+	return true;
+}
+
+Manager::Manager()
+{
+	Dbg_MsgAssert(NUM_REPLAY_TOKEN_TYPES<=256,("Too many token types !!"));
+	
+	mp_start_frame_task = new Tsk::Task< Manager > ( s_start_frame_code, *this,
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_REPLAY_START_FRAME );
+
+	mp_end_frame_task = new Tsk::Task< Manager > ( s_end_frame_code, *this,
+										Tsk::BaseTask::Node::vLOGIC_TASK_PRIORITY_REPLAY_END_FRAME );
+										
+	Mlp::Manager * mlp_manager = Mlp::Manager::Instance();
+	mlp_manager->AddLogicTask( *mp_start_frame_task );
+	mlp_manager->AddLogicTask( *mp_end_frame_task );
+}
+
+Manager::~Manager()
+{
+	delete mp_start_frame_task;
+	delete mp_end_frame_task;
+}
+
+void Manager::s_start_frame_code( const Tsk::Task< Manager >& task )
+{
+	//Manager& man = task.GetData();
+	spFrameBufferPos=spFrameBuffer;
+	sWriteUint32(FRAME_START,Tmr::GetTime());
+}
+
+/*
+static Tmr::CPUCycles sStartTime;
+static int s_num_times=0;
+static int s_time_index=0;
+#define MAX_TIMES 60
+static Tmr::CPUCycles spTimes[MAX_TIMES];
+
+void NewTime(Tmr::CPUCycles t)
+{
+	spTimes[s_time_index++]=t;
+	if (s_time_index>=MAX_TIMES)
+	{
+		s_time_index=0;
+	}
+	if (s_num_times& task )
+{
+	//Manager& man = task.GetData();
+	#ifdef DISABLE_REPLAYS
+	return;
+	#endif
+
+	if (Mdl::Skate::Instance()->IsMultiplayerGame())
+	{
+		return;
+	}	
+	
+	switch (sMode)
+	{
+		case RECORD:
+		{
+			//sStartTime=Tmr::GetTimeInCPUCycles();
+
+			sEnsureCameraTrackerExists();
+					
+			Mdl::FrontEnd* p_front = Mdl::FrontEnd::Instance();
+			if (!p_front->GamePaused())
+			{
+				Mdl::Skate * p_skate = Mdl::Skate::Instance();
+				p_skate->GetObjectManager()->ProcessAllObjects( sEnsureTrackerExists, NULL );
+				
+				sRecord();
+			}	
+			
+			//Tmr::CPUCycles t_record_time=Tmr::GetTimeInCPUCycles()-sStartTime;
+			//NewTime(t_record_time);
+			break;
+		}	
+		case PLAYBACK:
+		{
+			bool left=false;
+			bool right=false;
+			Mdl::FrontEnd* pFront = Mdl::FrontEnd::Instance();
+	
+			for ( int i = 0; i < SIO::vMAX_DEVICES; i++ )
+			{
+				if ( CFuncs::CheckButton( pFront->GetInputHandler( i ), 0x85981897 ) ) // left
+				{
+					left=true;
+				}	
+				if ( CFuncs::CheckButton( pFront->GetInputHandler( i ), 0x4b358aeb ) ) // right
+				{
+					right=true;
+				}	
+			}
+			
+			if (left)
+			{
+				if (Tmr::GetVblanks()&1)
+				{
+					sPlayback();
+				}	
+			}
+			else if (right)
+			{
+				sPlayback(false);
+				sPlayback();
+			}	
+			else
+			{
+				sPlayback();
+			}	
+			break;
+		}	
+		default:
+			break;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Gfx::CSkeleton* CDummy::GetSkeleton()
+{
+	if ( mp_skeletonComponent )
+	{
+		return mp_skeletonComponent->GetSkeleton();
+	}
+
+	return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool CDummy::LoadSkeleton( uint32 skeletonName )
+{
+    // temporarily moved this function from CModel,
+    // in order to make it easier to split off
+    // skeleton into its own component.
+
+	Gfx::CSkeletonData* pSkeletonData = (Gfx::CSkeletonData*) Ass::CAssMan::Instance()->GetAsset( skeletonName, false );
+
+	if ( !pSkeletonData )
+	{
+		Dbg_MsgAssert( 0, ("Unrecognized skeleton %s. (Is skeleton.q up to date?)", Script::FindChecksumName(skeletonName)) );
+	}
+    
+	Dbg_MsgAssert( mp_skeletonComponent == NULL, ( "Model already has skeleton component.  Possible memory leak?" ) );    
+	mp_skeletonComponent = new Obj::CSkeletonComponent;
+
+    Script::CStruct* pTempStructure = new Script::CStruct;
+    pTempStructure->AddChecksum( CRCD(0x222756d5,"skeleton"), skeletonName );
+    
+    mp_skeletonComponent->InitFromStructure( pTempStructure );
+    delete pTempStructure;
+
+#ifdef __NOPT_ASSERT__
+	Gfx::CSkeleton* pSkeleton = mp_skeletonComponent->GetSkeleton();
+    
+    Dbg_Assert( pSkeleton );
+    Dbg_Assert( pSkeleton->GetNumBones() > 0 );
+#endif		// __NOPT_ASSERT__
+	
+	return true;
+//    return mp_rendered_model->SetSkeleton( mp_skeletonComponent->GetSkeleton() );
+}
+
+}  // namespace Replay
+
+#endif
diff --git a/Code/Sys/Replay/replay.h b/Code/Sys/Replay/replay.h
new file mode 100644
index 0000000..fc0fab7
--- /dev/null
+++ b/Code/Sys/Replay/replay.h
@@ -0,0 +1,402 @@
+#ifndef __SYS_REPLAY_H
+#define __SYS_REPLAY_H
+
+#define	__USE_REPLAYS__ 0
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#ifndef __CORE_DEFINES_H
+#include 
+#endif
+
+#ifndef __CORE_TASK_H
+#include 
+#endif
+
+#ifndef __CORE_MATH_VECTOR_H
+#include 
+#endif
+
+#ifndef __GFX_ANIMCONTROLLER_H
+#include 
+#endif
+
+#ifndef	__GFX_NXMODEL_H__
+#include 
+#endif
+
+#ifndef __GEL_OBJECT_H
+#include 
+#endif
+
+#ifndef __GEL_SOUNDFX_H
+#include 
+#endif
+
+#ifndef __GEL_COMPONENTS_VIBRATIONCOMPONENT_H
+#include 
+#endif
+
+#ifndef __OBJECTS_SKATER_H
+#include 
+#endif
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace Obj
+{
+	class CObject;
+	class CSkeletonComponent;
+}
+
+namespace Script
+{
+	class CStruct;
+	class CScript;
+}
+	
+namespace Replay
+{
+
+class  Manager  : public Spt::Class
+{
+	Tsk::Task< Manager > *mp_start_frame_task;
+	static	Tsk::Task< Manager >::Code 	s_start_frame_code;
+
+	Tsk::Task< Manager > *mp_end_frame_task;
+	static	Tsk::Task< Manager >::Code 	s_end_frame_code;
+
+public:
+	Manager();
+	~Manager();
+	
+private:	
+	DeclareSingletonClass( Manager );
+};
+
+// 128 allows for a max of 4096 sectors
+#define SECTOR_STATUS_BUFFER_SIZE 128
+struct SGlobalStates
+{
+public:
+	SGlobalStates();
+	~SGlobalStates();
+
+	void Reset();
+	// Warning! If any new members get added or removed, update VERSION_REPLAY in mcfuncs.cpp
+	// Also update the Reset function.
+	bool mManualMeterStatus;
+	float mManualMeterValue;
+	bool mBalanceMeterStatus;
+	float mBalanceMeterValue;
+	
+	int mActuatorStrength[Obj::CVibrationComponent::vVB_NUM_ACTUATORS];
+	uint32 mpSectorStatus[SECTOR_STATUS_BUFFER_SIZE];
+	bool mSkaterPaused;
+};
+
+struct SReplayDataHeader
+{
+	uint32 mBufferStartOffset;
+	uint32 mBufferEndOffset;
+	int mNumStartStateDummies;
+	SGlobalStates mStartState;
+};
+
+enum
+{
+	NUM_DEGENERATE_ANIMS=3
+};	
+
+enum
+{
+	DUMMY_FLAG_FLIPPED		= (1<<0),
+	DUMMY_FLAG_HOVERING		= (1<<1),
+	DUMMY_FLAG_ACTIVE		= (1<<2),
+	// Keep the number of these <= 32
+};
+	
+struct SSavedDummy
+{
+	uint32 m_id;
+	int	m_type;
+//	char mpModelName[MAX_MODEL_NAME_CHARS+1];	
+	uint32 mSkeletonName;
+	uint32 mProfileName;
+	uint32 mAnimScriptName;
+	uint32 mSectorName;
+	uint32 mFlags;
+	float  mScale;
+	
+	Mth::Vector m_pos;
+	Mth::Vector m_vel;
+	Mth::Vector m_angles;
+	Mth::Vector m_ang_vel;
+	float m_car_rotation_x;
+	float m_car_rotation_x_vel;
+	float m_wheel_rotation_x;
+	float m_wheel_rotation_x_vel;
+	float m_wheel_rotation_y;
+	float m_wheel_rotation_y_vel;
+
+	Gfx::CAnimChannel	m_primaryController;
+
+    Gfx::CAnimChannel   m_degenerateControllers[NUM_DEGENERATE_ANIMS];
+	uint32 mAtomicStates;
+	// These only apply to the skater.
+	uint32 m_looping_sound_checksum;
+	float m_pitch_min;
+	float m_pitch_max;
+};
+
+enum EDummyList
+{
+	START_STATE_DUMMY_LIST,
+	PLAYBACK_DUMMY_LIST,
+};
+
+class CDummy : public Obj::CMovingObject
+{
+public:	
+	CDummy(EDummyList whichList, uint32 id);
+	virtual ~CDummy(void);
+
+	// If any new members are added to CDummy, remember to update the following:
+	// CDummy constructor, initialise new member
+	// assignement operator, copy new member.
+	// CDummy::Save function, save new member, and add to SSavedDummy, and update mem card version number
+	// Replay::CreateDummyFromSaveData function, copy in new member.
+	// Update VERSION_REPLAY in memcard.q
+	CDummy& operator=( const CDummy& rhs );
+
+	EDummyList m_list;
+	CDummy *mpNext;
+	CDummy *mpPrevious;
+	
+	void Update();
+	void CreateModel();
+	void DisplayModel();
+	void UpdateMutedSounds();
+	void UpdateLoopingSound();
+	
+	void Save(SSavedDummy *p_saved_dummy);
+
+//	char mpModelName[MAX_MODEL_NAME_CHARS+1];	
+	uint32 mSkeletonName;
+	uint32 mProfileName;
+	uint32 mAnimScriptName;
+	uint32 mSectorName;
+
+	bool m_is_displayed;
+	
+	float mScale;
+	
+	uint32 mFlags;
+
+	int mHoverPeriod;
+	
+	Mth::Vector m_vel;
+	Mth::Vector m_angles;
+	Mth::Vector m_ang_vel;
+
+	float m_car_rotation_x;
+	float m_car_rotation_x_vel;
+	float m_wheel_rotation_x;
+	float m_wheel_rotation_x_vel;
+	float m_wheel_rotation_y;
+	float m_wheel_rotation_y_vel;
+	
+	Gfx::CAnimChannel 	m_primaryController;
+
+    Gfx::CAnimChannel    m_degenerateControllers[NUM_DEGENERATE_ANIMS];
+	
+    Nx::CModel *mp_rendered_model;
+	Gfx::Camera *mp_skater_camera;
+	//Obj::CSkaterCam *mp_skater_camera;
+	
+	uint32 mAtomicStates;
+	
+	uint32 m_looping_sound_id;
+	uint32 m_looping_sound_checksum;
+	float m_pitch_min;
+	float m_pitch_max;
+
+    // TEMP
+    bool LoadSkeleton( uint32 skeletonChecksum );
+    Gfx::CSkeleton* GetSkeleton();
+    Obj::CSkeletonComponent* mp_skeletonComponent;
+};
+
+// The bigger this is the better, since it will speed up saving of replays to mem card.
+// A static buffer of this size exists though, so don't make it too big.
+#define REPLAY_BUFFER_CHUNK_SIZE 2048
+
+
+#if __USE_REPLAYS__  
+
+// These functions have platform specific versions.
+// On the GameCube for example, the buffer is in ARAM.
+void AllocateBuffer();
+void DeallocateBuffer();
+bool BufferAllocated();
+uint32 GetBufferSize();
+void ReadFromBuffer(uint8 *p_dest, int bufferOffset, int numBytes);
+void WriteIntoBuffer(uint8 *p_source, int bufferOffset, int numBytes);
+void FillBuffer(int bufferOffset, int numBytes, uint8 value);
+void WriteReplayDataHeader(SReplayDataHeader *p_header);
+void ReadReplayDataHeader(const SReplayDataHeader *p_header);
+void CreateDummyFromSaveData(SSavedDummy *p_saved_dummy);
+CDummy *GetFirstStartStateDummy();
+
+void ClearBuffer();
+uint8 *GetTempBuffer();
+void CreatePools();
+void RemovePools();
+void DeallocateReplayMemory();
+bool ScriptDeleteDummies( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptHideGameObjects( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptShowGameObjects( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptRunningReplay( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptPauseReplay( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptUnPauseReplay( Script::CStruct *pParams, Script::CScript *pScript );
+
+bool ScriptAllocateReplayMemory(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptDeallocateReplayMemory(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptStartRecordingAfresh(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptRememberLevelStructureNameForReplays(Script::CStruct *pParams, Script::CScript *pScript);
+void SetLevelStructureName(uint32 level_structure_name);
+bool ScriptGetReplayLevelStructureName(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptPlaybackReplay(Script::CStruct *pParams, Script::CScript *pScript);
+
+bool ScriptReplayRecordSimpleScriptCall(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptRecordPanelMessage(Script::CStruct *pParams, Script::CScript *pScript);
+bool ScriptSwitchToReplayRecordMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptSwitchToReplayIdleMode( Script::CStruct *pParams, Script::CScript *pScript );
+bool ScriptClearTrickAndScoreText(Script::CStruct *pParams, Script::CScript *pScript);
+
+void WritePadVibration(int actuator, int percent);
+void WritePauseSkater();
+void WriteUnPauseSkater();
+void WriteScreenFlash(int viewport, Image::RGBA from, Image::RGBA to, float duration, float z, uint32 flags);
+void WriteShatter(uint32 sectorName, bool on);
+void WriteShatterParams(Mth::Vector& velocity, float area_test, float velocity_variance, float spread_factor, float lifetime, float bounce, float bounce_amplitude);
+void WriteTextureSplat(Mth::Vector& splat_start, Mth::Vector& splat_end, float size, float lifetime, const char *p_texture_name, uint32 trail );
+void WriteSectorActiveStatus(uint32 sectorName, bool active);
+void WriteSectorVisibleStatus(uint32 sectorName, bool visible);
+void WriteManualMeter(bool state, float value);
+void WriteBalanceMeter(bool state, float value);
+void WriteSparksOn();
+void WriteSparksOff();
+void WriteScorePotText(const char *p_text);
+void WriteTrickText(const char **pp_text, int numStrings);
+void WriteTrickTextPulse();
+void WriteTrickTextCountdown();
+void WriteTrickTextLanded();
+void WriteTrickTextBail();
+void WriteSetAtomicStates(uint32 id, uint32 mask);
+void WritePlayStream(uint32 checksum, Sfx::sVolume *p_volume, float pitch, int priority);
+void WriteStopStream(int channel);
+void WritePositionalStream(uint32 dummyId, uint32 streamNameChecksum, float dropoff, float volume, float pitch, int priority, int use_pos_info);
+void WritePositionalSoundEffect(uint32 dummyId, uint32 soundName, float volume, float pitch, float dropOffDist);
+void WriteSkaterSoundEffect(int whichArray, int surfaceFlag, const Mth::Vector &pos, 
+							float volPercent);
+void WritePlaySound(uint32 checksum, float volL, float volR, float pitch);
+void PrepareForReplayPlayback(bool hideObjects);
+bool RunningReplay();
+bool Paused();
+void AddReplayMemCardInfo(Script::CStruct *p_struct);
+void AddReplayMemCardSummaryInfo(Script::CStruct *p_struct);
+SGlobalStates *GetStartState();
+
+#else
+
+// These functions have platform specific versions.
+// On the GameCube for example, the buffer is in ARAM.
+inline void AllocateBuffer(){}
+inline void DeallocateBuffer(){}
+//bool BufferAllocated(){}
+//uint32 GetBufferSize(){}
+inline void ReadFromBuffer(uint8 *p_dest, int bufferOffset, int numBytes){}
+inline void WriteIntoBuffer(uint8 *p_source, int bufferOffset, int numBytes){}
+inline void FillBuffer(int bufferOffset, int numBytes, uint8 value){}
+inline void WriteReplayDataHeader(SReplayDataHeader *p_header){}
+inline void ReadReplayDataHeader(const SReplayDataHeader *p_header){}
+inline void CreateDummyFromSaveData(SSavedDummy *p_saved_dummy){}
+//CDummy *GetFirstStartStateDummy(){}
+
+inline void ClearBuffer(){}
+//uint8 *GetTempBuffer(){}
+inline void CreatePools(){}
+inline void RemovePools(){}
+inline void DeallocateReplayMemory(){}
+inline bool ScriptDeleteDummies( Script::CStruct *pParams, Script::CScript *pScript ){return true;}
+inline bool ScriptHideGameObjects( Script::CStruct *pParams, Script::CScript *pScript ){return true;}
+inline bool ScriptShowGameObjects( Script::CStruct *pParams, Script::CScript *pScript ){return true;}
+inline bool ScriptRunningReplay( Script::CStruct *pParams, Script::CScript *pScript ){return false;}
+inline bool ScriptPauseReplay( Script::CStruct *pParams, Script::CScript *pScript ){return true;}
+inline bool ScriptUnPauseReplay( Script::CStruct *pParams, Script::CScript *pScript ){return true;}
+
+inline bool ScriptAllocateReplayMemory(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+inline bool ScriptDeallocateReplayMemory(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+inline bool ScriptStartRecordingAfresh(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+
+inline bool ScriptRememberLevelStructureNameForReplays(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+inline void SetLevelStructureName(uint32 level_structure_name){}
+inline bool ScriptGetReplayLevelStructureName(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+
+inline bool ScriptPlaybackReplay(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+
+inline bool ScriptReplayRecordSimpleScriptCall(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+inline bool ScriptRecordPanelMessage(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+inline bool ScriptSwitchToReplayRecordMode( Script::CStruct *pParams, Script::CScript *pScript ){return true;}
+inline bool ScriptSwitchToReplayIdleMode( Script::CStruct *pParams, Script::CScript *pScript ){return true;}
+inline bool ScriptClearTrickAndScoreText(Script::CStruct *pParams, Script::CScript *pScript){return true;}
+
+inline void WritePadVibration(int actuator, int percent){}
+inline void WritePauseSkater(){}
+inline void WriteUnPauseSkater(){}
+inline void WriteScreenFlash(int viewport, Image::RGBA from, Image::RGBA to, float duration, float z, uint32 flags){}
+inline void WriteShatter(uint32 sectorName, bool on){}
+inline void WriteShatterParams(Mth::Vector& velocity, float area_test, float velocity_variance, float spread_factor, float lifetime, float bounce, float bounce_amplitude){}
+inline void WriteTextureSplat(Mth::Vector& splat_start, Mth::Vector& splat_end, float size, float lifetime, const char *p_texture_name, uint32 trail ){}
+inline void WriteSectorActiveStatus(uint32 sectorName, bool active){}
+inline void WriteSectorVisibleStatus(uint32 sectorName, bool visible){}
+inline void WriteManualMeter(bool state, float value){}
+inline void WriteBalanceMeter(bool state, float value){}
+inline void WriteSparksOn(){}
+inline void WriteSparksOff(){}
+inline void WriteScorePotText(const char *p_text){}
+inline void WriteTrickText(const char **pp_text, int numStrings){}
+inline void WriteTrickTextPulse(){}
+inline void WriteTrickTextCountdown(){}
+inline void WriteTrickTextLanded(){}
+inline void WriteTrickTextBail(){}
+inline void WriteSetAtomicStates(uint32 id, uint32 mask){}
+inline void WritePlayStream(uint32 checksum, Sfx::sVolume *p_volume, float pitch, int priority){}
+inline void WriteStopStream(int channel){}
+inline void WritePositionalStream(uint32 dummyId, uint32 streamNameChecksum, float dropoff, float volume, float pitch, int priority, int use_pos_info){}
+inline void WritePositionalSoundEffect(uint32 dummyId, uint32 soundName, float volume, float pitch, float dropOffDist){}
+inline void WriteSkaterSoundEffect(int whichArray, int surfaceFlag, const Mth::Vector &pos, 
+							float volPercent){}
+inline void WritePlaySound(uint32 checksum, float volL, float volR, float pitch){}
+inline void PrepareForReplayPlayback(bool hideObjects){}
+inline bool RunningReplay(){return false;}
+inline bool Paused(){return false;}
+inline void AddReplayMemCardInfo(Script::CStruct *p_struct){}
+inline void AddReplayMemCardSummaryInfo(Script::CStruct *p_struct){}
+//SGlobalStates *GetStartState(){}
+#endif
+
+} // namespace Replay
+
+#endif	// __SYS_REPLAY_H
+
+
+
+
diff --git a/Code/Sys/SIO/NGPS/p_keyboard.cpp b/Code/Sys/SIO/NGPS/p_keyboard.cpp
new file mode 100644
index 0000000..beceb8c
--- /dev/null
+++ b/Code/Sys/SIO/NGPS/p_keyboard.cpp
@@ -0,0 +1,463 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		keyboard.cpp											**
+**																			**
+**	Created:		03/08/2001	-	gj										**
+**																			**
+**	Description:	USB Keyboard interface									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace SIO
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+#define USBKEYBD_ARG "keybd=8\0debug=1"
+
+static	unsigned char *s_old_status = NULL;
+static	USBKBINFO_t	s_info;
+static	u_char 		s_location[7];
+static	USBKBDATA_t s_kdata;
+static	int			s_max_connect;
+static	char		s_makes[32];
+static	int			s_num_makes;
+static  bool		s_active = true;
+static  bool		s_enabled = true;
+
+static 	Tsk::Task< int >*		s_kb_read_logic_task;
+
+enum
+{
+	vKEYCODE_F1		= 	0x803A,
+	vKEYCODE_F2		= 	0x803B,
+	vKEYCODE_F3		= 	0x803C,
+	vKEYCODE_F4		= 	0x803D,
+	vKEYCODE_RIGHT	=	0x804F,
+	vKEYCODE_LEFT	=	0x8050,
+	vKEYCODE_DOWN	=	0x8051,
+	vKEYCODE_UP		=	0x8052,
+	vKEYCODE_ESC	= 	0x8029,
+};
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+static void s_init_newkeyboard( int i );
+static int s_capture_input( int i, char* makes );
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void	kb_read_logic_code ( const Tsk::Task< int >& task )
+{
+	int i, result;
+
+	if (!s_active || !s_enabled)
+	{
+		return;
+	}
+    
+	s_num_makes = 0;
+
+    result = sceUsbKbGetInfo( &s_info );
+	if( result != USBKB_OK ) 
+	{
+		Dbg_Printf( "Error%d : sceUsbKbGetInfo\n", result );
+		return;
+	}
+	sceUsbKbSync( USBKB_WAIT, &result );
+	if( result != USBKB_OK ) 
+	{
+		Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+		return;
+	}
+  
+    // reading keyboard
+	for( i = 0; i < s_max_connect; i++)  
+	{
+		if( s_info.status[i] == 0 ) 
+		{ 
+			continue; 
+		}  // not connected
+
+		if( s_old_status[i] == 0 ) 
+		{
+			Dbg_Printf( "New keyboard %d is connected\n", i );
+			s_init_newkeyboard(i);
+		}
+
+		result = sceUsbKbRead( i, &s_kdata );
+		if( result != USBKB_OK ) 
+		{
+			Dbg_Printf( "Error%d : sceUsbKbRead\n", result );
+			continue;
+		}
+		sceUsbKbSync( USBKB_WAIT, &result );
+		if( result != USBKB_OK ) 
+		{
+			Dbg_Printf( "Error%d : sceUsbKbSync\n",result);
+			continue;
+		}
+
+		if( s_kdata.len == 0 ) 
+		{
+			continue;
+		}
+
+		result = sceUsbKbGetLocation( i, s_location );
+		if( result != USBKB_OK ) 
+		{
+			Dbg_Printf( "Error%d : sceUsbKbGetLocation\n", result );
+			continue;
+		}
+		
+		sceUsbKbSync( USBKB_WAIT, &result );
+		if( result != USBKB_OK ) 
+		{
+			Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+			continue;
+		}
+		
+		s_num_makes = s_capture_input( i, s_makes );
+	}
+  
+	for( i = 0; i < s_max_connect; i++ ) 
+	{ 
+		s_old_status[i] = s_info.status[i]; 
+	}
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardInit(void)
+{
+	
+	
+	int result;
+    char *option = USBKEYBD_ARG;
+	int i;
+	Mlp::Manager * mlp_man = Mlp::Manager::Instance();
+	
+
+	//sceSifInitRpc(0);
+	
+	if (Config::CD())
+	{
+		char  path[128];
+		sprintf(path,"cdrom0:\\%sIOP\\USBKB.IRX",Config::GetDirectory()); 
+
+		result = sceSifLoadModule(
+				   path,
+					sizeof( USBKEYBD_ARG ) + 1, option );
+	}
+	else
+	{
+		result = sceSifLoadModule(
+				   "host0:IOPModules/usbkb.irx",
+					sizeof( USBKEYBD_ARG ) + 1, option );
+	}				
+	
+	if( result < 0 )
+    {
+        Dbg_MsgAssert( 0,( "EE:Can't load module usbkb.irx\n" ));
+		return -1;
+	}
+
+    result = sceUsbKbInit( &s_max_connect );
+    if( result == USBKB_NG ) 
+	{
+		Dbg_Printf( "Initialize error\n" );
+		return -1;
+	}
+
+    s_old_status = new unsigned char[ s_max_connect ];
+    for( i = 0; i < s_max_connect; i++ ) 
+	{ 
+		s_old_status[i] = 0; 
+	}
+
+	int arg;
+	// Just send an int because Tsk::Task is a template and needs some sort of
+	// specific data type. So I just chose int and pass an int even though I don't use it
+	s_kb_read_logic_task = new Tsk::Task< int > ( kb_read_logic_code, arg );
+	mlp_man->AddSystemTask( *s_kb_read_logic_task );
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardDeinit(void)
+{
+	delete s_kb_read_logic_task;
+	delete s_old_status;
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardRead( char* makes )
+{
+	int i;
+	for( i = 0; i < s_num_makes; i++ )
+	{
+		makes[i] = s_makes[i];
+	}
+
+	return s_num_makes;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void KeyboardClear( void )
+{
+	s_num_makes = 0;
+}
+
+
+void SetKeyboardActive(bool active)
+{
+	s_active = active;
+}
+
+void EnableKeyboard( bool enable )
+{
+	s_enabled = enable;
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static void s_init_newkeyboard( int i )
+{       
+	int result;
+	
+	result = sceUsbKbSetLEDStatus( i, USBKB_LED_NUM_LOCK );
+	if( result != USBKB_OK ) 
+	{
+		Dbg_Printf( "Error%d : sceUsbKbSetLEDStatus\n", result );
+	} 
+	else 
+	{
+		sceUsbKbSync(USBKB_WAIT,&result);
+		if( result != USBKB_OK ) 
+		{
+			Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+		}
+	}
+	
+	result = sceUsbKbSetLEDMode( i, USBKB_LED_MODE_AUTO1 );
+	if( result != USBKB_OK ) 
+	{
+		Dbg_Printf( "Error%d : sceUsbKbSetLEDMode\n", result );
+	} 
+	else 
+	{
+		sceUsbKbSync( USBKB_WAIT, &result );
+		if( result != USBKB_OK ) 
+		{
+			Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+		}
+	}
+	
+	sceUsbKbSetRepeat( i, 30, 2 );
+	sceUsbKbSetCodeType( i, USBKB_CODETYPE_ASCII );
+	sceUsbKbSetArrangement( i, USBKB_ARRANGEMENT_101 );//USBKB_ARRANGEMENT_106 );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+static int s_capture_input( int i, char* makes )
+{
+	u_short kcode;
+	int j, index;
+	
+	//Dbg_Printf( "usbkeybd%d : ", i );
+	
+	//Dbg_Printf( "port=" );
+	//for( k = 0; k < 7 && s_location[k] != 0; k++ )
+	//{
+		//Dbg_Printf( "%s%d", ((k)? ",": "" ), s_location[k] );
+	//}
+	  
+	
+	//Dbg_Printf( " : LED[%02X] ", s_kdata.led );
+	
+	//Dbg_Printf( "MKEY[%02X] ", s_kdata.mkey );
+	
+	//Dbg_Printf( "KEY[" );
+	
+	index = 0;
+	for( j=0; j < s_kdata.len; j++ ) 
+	{
+		kcode = s_kdata.keycode[j];
+		if( kcode & USBKB_RAWDAT ) 
+		{
+			//Dbg_Printf( "%04X ", kcode );
+			switch( kcode )
+			{   
+				case vKEYCODE_F1:
+					makes[index++] = vKB_F1;
+					break;
+				case vKEYCODE_F2:
+					makes[index++] = vKB_F2;
+					break;
+				case vKEYCODE_F3:
+					makes[index++] = vKB_F3;
+					break;
+				case vKEYCODE_F4:
+					makes[index++] = vKB_F4;
+					break;
+				case vKEYCODE_RIGHT:
+					makes[index++] = vKB_RIGHT;
+					break;
+				case vKEYCODE_LEFT:
+					makes[index++] = vKB_LEFT;
+					break;
+				case vKEYCODE_DOWN:
+					makes[index++] = vKB_DOWN;
+					break;
+				case vKEYCODE_UP:
+					makes[index++] = vKB_UP;
+					break;
+				case vKEYCODE_ESC:
+					makes[index++] = vKB_ESCAPE;
+					break;
+			}
+			continue;
+		}
+		if( kcode & USBKB_KEYPAD ) 
+		{
+			// 10 key
+			if((kcode & 0x00ff) == '\n')
+			{ 
+				//Dbg_Printf( "(\\n)" ); 
+				makes[index++] = vKB_ENTER;
+				continue;
+			}
+			//Dbg_Printf( "(%c) ", kcode & ~USBKB_KEYPAD );
+			makes[index++] = kcode & ~USBKB_KEYPAD;
+			continue;
+		}
+		// Normal key
+		if((kcode & 0x00ff) == '\0' ) 
+		{ 
+			//Dbg_Printf( "\'\\0\'" ); 
+			continue;
+		}
+		if((kcode & 0x00ff) == '\n' ) 
+		{ 
+			//Dbg_Printf( "\'\\n\'" ); 
+			makes[index++] = vKB_ENTER;
+			continue;
+		}
+		if((kcode & 0x00ff) == '\t' ) 
+		{ 
+			//Dbg_Printf( "\'\\t\'" ); 
+			continue;
+		}
+		if((kcode & 0x00ff) == '\b' ) 
+		{ 
+			//Dbg_Printf( "\'\\b\'" ); 
+			makes[index++] = vKB_BACKSPACE;
+			continue;
+		}
+		//Dbg_Printf( "\'%c\' ", kcode );
+		makes[index++] = kcode;
+	}
+	//Dbg_Printf( "]\n" ); 
+
+	return index;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SIO
diff --git a/Code/Sys/SIO/XBox/p_keyboard.cpp b/Code/Sys/SIO/XBox/p_keyboard.cpp
new file mode 100644
index 0000000..5a79d65
--- /dev/null
+++ b/Code/Sys/SIO/XBox/p_keyboard.cpp
@@ -0,0 +1,115 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		keyboard.cpp											**
+**																			**
+**	Created:		03/08/2001	-	gj										**
+**																			**
+**	Description:	USB Keyboard interface									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace SIO
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardInit(void)
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardDeinit(void)
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardRead( char* makes )
+{
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void KeyboardClear( void )
+{
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SIO
diff --git a/Code/Sys/SIO/XBox/p_siodev.cpp b/Code/Sys/SIO/XBox/p_siodev.cpp
new file mode 100644
index 0000000..9b6f949
--- /dev/null
+++ b/Code/Sys/SIO/XBox/p_siodev.cpp
@@ -0,0 +1,760 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		siodev.cpp												**
+**																			**
+**	Created:		05/26/2000	-	spg										**
+**																			**
+**	Description:	Generic input device									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+         
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+//#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace SIO
+{
+
+
+// Maps Xbox thumbstick values in the range [-32767, 32767] to PS2 thumstick values in the range [0, 255].
+#define XboxThumbToPS2Thumb( v )	(( v + 32767 ) / 256 )
+
+// K: Index of the last pad to have a button pressed. Used when a level is chosen in the skateshop.
+// The pad used to control the player is whatever pad was used to choose the level.
+int gLastPadPressed=0;
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+static void set_xbox_actuators( HANDLE handle, unsigned short left_motor, unsigned short right_motor )
+{
+	// Want this to be static since otherwise it would potentially go out of scope and be written to.
+	static XINPUT_FEEDBACK input_feedback[32];
+	static int next_index			= 0;
+	
+	// The Ps2 left motor is the high frequency motor, the right motor is a simple on/off low frequency motor.
+	// On the Xbox it is the reverse (although the left motor has more control than simple on/off).
+	
+	input_feedback[next_index].Header.dwStatus			= 0;
+	input_feedback[next_index].Header.hEvent			= NULL;
+	input_feedback[next_index].Rumble.wLeftMotorSpeed	= right_motor;
+	input_feedback[next_index].Rumble.wRightMotorSpeed	= left_motor;
+
+	DWORD status = XInputSetState( handle, &input_feedback[next_index] );
+	Dbg_Assert(( status == ERROR_IO_PENDING ) || ( status == ERROR_SUCCESS ) || ( status == ERROR_DEVICE_NOT_CONNECTED ));
+
+	// Cycle array member.
+	if( ++next_index >= 32 )
+	{
+		next_index = 0;
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+    
+void Device::process( void )
+{
+	m_plugged_in = false;
+
+    switch( m_state )
+    {
+    case vIDLE:
+        break;
+
+    case vBUSY:
+        wait();
+        break;
+
+    case vACQUIRING:
+        acquisition_pending();
+        break;
+
+    case vACQUIRED:
+        read_data();
+        break;
+
+    default:
+        break;
+
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::wait( void )
+{
+    Dbg_MsgAssert(( m_next_state >= 0 ) && ( m_next_state <= vNUM_STATES ),( "No next state set for wait state" ));
+
+	if ( get_status() != vNOTREADY )
+	{
+		m_state = m_next_state;
+		m_next_state = vIDLE;
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::read_data ( void )
+{
+	XINPUT_STATE	xis;
+	HRESULT			hr;
+
+	m_plugged_in = false;
+
+	if( m_data.m_handle == NULL )
+	{
+		return;
+	}
+
+	hr = XInputGetState( m_data.m_handle, &xis );
+	if( hr != ERROR_SUCCESS )
+	{
+		XInputClose( m_data.m_handle );
+		m_data.m_handle	= NULL;
+		m_data.m_valid	= false;
+		Unacquire();
+		Acquire();
+		return;
+	}
+
+	m_data.m_valid	= true;
+	m_plugged_in	= true;
+
+	// Convert this data back into PS2-style 'raw' data for a DUALSHOCK2.
+	m_data.m_control_data[0] = 0;						// 'Valid' info flag.
+	m_data.m_control_data[1] = ( 0x07 << 4 ) | 16;		// DUALSHOCK2 id + data length.
+
+	m_data.m_control_data[2] = 0xFF;					// Turn off all buttons by default.
+	m_data.m_control_data[3] = 0xFF;					// Turn off all buttons by default.
+
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_START ) ? ( 1 << 3 ) : 0;		// XBox 'Start' = PS2 'Start'.
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_BACK ) ? ( 1 << 0 ) : 0;		// XBox 'Back' = PS2 'Select'.
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB ) ? ( 1 << 1 ) : 0;	// Xbox 'Left Stick Button' = PS2 'Left Stick Button'.
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB ) ? ( 1 << 2 ): 0;	// Xbox 'Right Stick Button' = PS2 'Right Stick Button'.
+
+	// TRC 1.4-1-26 to eliminate crosstalk must disregard values below 0x20.
+	m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_Y] >= 0x20 ) ? ( 1 << 4 ) : 0;	// XBox 'Y' = PS2 'Triangle'.
+	m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_B] >= 0x20 ) ? ( 1 << 5 ) : 0;	// XBox 'B' = PS2 'Circle'.
+	m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_A] >= 0x20 ) ? ( 1 << 6 ) : 0;	// XBox 'A' = PS2 'X'.
+	m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_X] >= 0x20 ) ? ( 1 << 7 ) : 0;	// XBox 'X' = PS2 'Square'.
+		
+	if( Ed::CParkEditor::Instance()->EditingCustomPark())
+	{
+		// In the Park Editor, black and white buttons are L1 and L2, triggers are R1 and R2... unless the gap name keyboard is active :(
+		bool							keyboard_active		= false;
+		Front::CScreenElementManager	*p_screen_elem_man	= Front::CScreenElementManager::Instance();
+		if( p_screen_elem_man )
+		{
+			Front::CScreenElement		*p_keyboard			= p_screen_elem_man->GetElement( Script::GenerateCRC( "keyboard_anchor" ) , Front::CScreenElementManager::DONT_ASSERT );
+			if( p_keyboard )
+			{
+				keyboard_active	= true;
+			}
+		}
+		
+		if( keyboard_active	)
+		{
+			m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] > 0x20 ) ? ( 1 << 1 ) : 0;		// XBox  'Right Trigger'= PS2 'R2'.
+		}
+		else
+		{
+			m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] > 0x20 ) ? ( 1 << 2 ) : 0;		// XBox  'Right Trigger'= PS2 'L1'.
+		}
+		
+		m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] > 0x20 ) ? ( 1 << 0 ) : 0;			// XBox 'Left Trigger' = PS2 'L2'.
+		// New! Black and white are no longer mapped to R2 and L2 but are treated as seperate buttons
+		//m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_WHITE] > 0x20 ) ? ( 1 << 1 ) : 0;					// XBox 'White' = PS2 'R2'.
+		//m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_BLACK] > 0x20 ) ? ( 1 << 3 ) : 0;					// XBox 'Black' = PS2 'R1'.
+	}
+	else
+	{
+		// New! Black and white are no longer mapped to R2 and L2 but are treated as seperate buttons
+
+		// Outside of the Park Editor, either black or white buttons act as L1 and R1...
+		//m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_WHITE] > 0x20 ) ? (( 1 << 2 ) | ( 1 << 3 )) : 0;	// XBox 'White' = PS2 'L1' + PS2 'R1'.
+//		m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_BLACK] > 0x20 ) ? (( 1 << 2 ) | ( 1 << 3 )) : 0;	// XBox 'Black'= PS2 'L1' + PS2 'R1'.
+		//m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_BLACK] > 0x20 ) ? (( 1 << 0 ) | ( 1 << 1 )) : 0;	// XBox 'Black'= PS2 'L2' + PS2 'R2'.
+		
+		// ...triggers function as both L1/L2 and R1/R2.		
+		m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] > 128 ) ? ( 1 << 0 ) : 0;				// XBox 'Left Trigger pressed down > halfway' = PS2 'L2'.
+		m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] > 128 ) ? ( 1 << 1 ) : 0;			// XBox 'Right Trigger pressed down > halfway' = PS2 'R2'.
+
+		m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] > 0x20 ) ? ( 1 << 2 ) : 0;				// XBox 'Left Trigger' = PS2 'L1'.
+		m_data.m_control_data[3] ^= ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] > 0x20 ) ? ( 1 << 3 ) : 0;				// XBox 'Right Trigger' = PS2 'R1'.
+	}
+
+	// Make analog buttons full depression if pressed.
+	// Xbox analog buttons return analog value in range [0, 255].
+	m_data.m_control_data[12] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_Y] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_Y] : 0;							// XBox 'Y' = PS2 Analog 'Triangle'.
+	m_data.m_control_data[13] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_B] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_B] : 0;							// XBox 'B' = PS2 Analog 'Circle'.
+	m_data.m_control_data[14] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_A] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_A] : 0;							// XBox 'A' = PS2 Analog 'X'.
+	m_data.m_control_data[15] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_X] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_X] : 0;							// XBox 'X' = PS2 'Square'.
+	m_data.m_control_data[16] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_LEFT_TRIGGER] : 0;	// XBox 'Left Trigger' = PS2 Analog 'L1'.
+	m_data.m_control_data[17] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_RIGHT_TRIGGER] : 0;	// XBox 'Right Trigger' = PS2 Analog 'R1'.
+	// Black and white are no longer mapped to R2 and L2, but are treated as separate buttons.
+	//m_data.m_control_data[18] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_WHITE] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_WHITE] : 0;					// XBox 'White' = PS2 Analog 'L2'.
+	//m_data.m_control_data[19] = ( xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_BLACK] > 0x20 ) ? xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_BLACK] : 0;					// XBox 'Black' = PS2 Analog 'R2'.
+	m_data.m_control_data[18]=0;
+	m_data.m_control_data[19]=0;
+
+	// Zero out the d-pad pressure values.
+	m_data.m_control_data[8]	= 0x00;
+	m_data.m_control_data[9]	= 0x00;
+	m_data.m_control_data[10]	= 0x00;
+	m_data.m_control_data[11]	= 0x00;
+
+	// Handle 8 position d-pad.
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) ? ( 1 << 4 ) : 0;		// XBox 'DPad Up' = PS2 'DPad Up'.
+	m_data.m_control_data[10] = ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP ) ? 0xFF : 0;			// XBox 'DPad Up' = PS2 'DPad Up'.
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT ) ? ( 1 << 5 ) : 0;	// XBox 'DPad Right' = PS2 'DPad Right'.
+	m_data.m_control_data[8] = ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT ) ? 0xFF : 0;			// XBox 'DPad Right' = PS2 'DPad Right'.
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) ? ( 1 << 6 ) : 0;	// XBox 'DPad Down' = PS2 'DPad Down'.
+	m_data.m_control_data[11] = ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN ) ? 0xFF : 0;			// XBox 'DPad Down' = PS2 'DPad Down'.
+	m_data.m_control_data[2] ^= ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) ? ( 1 << 7 ) : 0;	// XBox 'DPad Left' = PS2 'DPad Left'.
+	m_data.m_control_data[9] = ( xis.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT ) ? 0xFF : 0;			// XBox 'DPad Left' = PS2 'DPad Left'.
+
+	// Xbox thumbsticks return analog value in range [-32767, 32767].
+	m_data.m_control_data[4] = XboxThumbToPS2Thumb( xis.Gamepad.sThumbRX );	// Analog stick right (X direction).
+	m_data.m_control_data[5] = XboxThumbToPS2Thumb( -xis.Gamepad.sThumbRY );	// Analog stick right (Y direction).
+	m_data.m_control_data[6] = XboxThumbToPS2Thumb( xis.Gamepad.sThumbLX );	// Analog stick left (X direction).
+	m_data.m_control_data[7] = XboxThumbToPS2Thumb( -xis.Gamepad.sThumbLY );	// Analog stick left (Y direction).
+
+	// K: Use m_control_data[20] to store the state of the black & white buttons.
+	m_data.m_control_data[20]=0;
+	if (xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_BLACK] > 0x20)
+	{
+		m_data.m_control_data[20] |= (1<<0);
+	}	
+	if (xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_WHITE] > 0x20)
+	{
+		m_data.m_control_data[20] |= (1<<1);
+	}	
+
+	
+	uint32 buttons = 0xFFFF ^ (( m_data.m_control_data[2] << 8 ) | m_data.m_control_data[3] );
+
+	// Skate3 specific code, removed for now.
+	if( buttons )
+	{
+		m_pressed = true;	
+	}
+	else
+	{
+		m_pressed = false;
+	}	
+		
+
+	if(( xis.Gamepad.wButtons & XINPUT_GAMEPAD_START) || xis.Gamepad.bAnalogButtons[XINPUT_GAMEPAD_A] >= 0x20 )
+	{
+		m_start_or_a_pressed = true;
+	}
+	else
+	{
+		m_start_or_a_pressed = false;
+	}	
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Device::IsPluggedIn( void )
+{
+	return m_plugged_in;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::acquisition_pending( void )
+{
+    int status;
+
+	if( m_data.m_handle == NULL )
+	{
+		++m_unplugged_counter;
+
+		// Retry to connect every second or so.
+		if(( m_unplugged_counter & 0x3F ) == m_unplugged_retry )
+		{
+			m_data.m_handle = XInputOpen( XDEVICE_TYPE_GAMEPAD, m_data.m_port, XDEVICE_NO_SLOT, NULL );
+		}
+		
+		if( m_data.m_handle == NULL )
+		{
+			return;
+		}
+	}
+
+    status = get_status();
+                        
+    if(( status == vCONNECTED ) || ( status == vREADY ))
+    {
+        // Sucessful.  Now query the controller for capabilities.
+		m_unplugged_counter = 0;
+        query_capabilities();
+    }
+        
+	// failed to acquire controller
+	// stay in this state and continue to try to acquire it or prompt the user if it is mandatory
+	// that they have a controller in   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::query_capabilities( void )
+{   
+	// Currently assumes standard XBox controller.
+    int id = vANALOG_CTRL;
+    
+    m_data.m_type = id;
+
+	switch( id )
+	{   
+		case vANALOG_CTRL:
+		{
+			m_data.m_caps.SetMask( mANALOG_BUTTONS );
+
+			m_data.m_num_actuators		= 2;
+			m_data.m_actuator_max[0]	= 255;
+			m_data.m_actuator_max[1]	= 255;
+			m_data.m_caps.SetMask( mACTUATORS );
+
+			m_state = vBUSY;
+			m_next_state = vACQUIRED;
+			return;
+		}
+
+		default:
+		{
+			Dbg_Message( "Detected Controller of unknown type in %d:%d", m_data.m_port, m_data.m_slot );
+			break;
+		}
+	}
+    m_state = vACQUIRED;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Device::get_status( void )
+{
+	if( m_data.m_handle )
+	{
+		XINPUT_STATE state;
+		if( XInputGetState( m_data.m_handle, &state ) == ERROR_SUCCESS )
+		{
+			return vREADY;
+		}
+		else
+		{
+			// Could do more checking here of the return value.
+			return vDISCONNECTED;
+		}
+	}
+
+	return vDISCONNECTED;
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Device::Device ( int index, int port, int slot )
+{
+	m_node = new Lst::Node< Device > ( this );
+	Dbg_AssertType ( m_node, Lst::Node< Device > );
+
+    Dbg_Assert( port < vMAX_PORT );
+    Dbg_Assert( slot < vMAX_SLOT );
+
+    m_state = vIDLE;
+    m_next_state = vIDLE;
+    m_index = index;
+
+    // Initialize device
+    m_data.m_port = port;
+    m_data.m_slot = slot;
+    m_data.m_caps.ClearAll();
+    m_data.m_num_actuators = 0;
+    m_data.m_valid = false;
+
+    memset( m_data.m_actuator_direct, 0, ACTUATOR_BUFFER_LENGTH );
+    memset( m_data.m_actuator_align, 0xFF, ACTUATOR_BUFFER_LENGTH );
+    m_data.m_actuator_align[0] = 0;
+    m_data.m_actuator_align[1] = 1;
+
+    memset( m_data.m_control_data, 0, CTRL_BUFFER_LENGTH );
+//	m_data.m_prealbuffer = Mem::Malloc( scePadDmaBufferMax * sizeof( uint128) + 63 );
+	m_data.m_dma_buff =  (unsigned char*)((((uint32)m_data.m_prealbuffer)+63)&~63);
+	
+	// Random retry for unplugged controllers.
+	m_unplugged_retry = rand() & 0x3F;
+
+//	memset( m_data.m_dma_buff, 0, sizeof( uint128 ) * scePadDmaBufferMax );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Device::~Device ( void )
+{
+	Dbg_AssertType ( m_node, Lst::Node< Device > );
+//	Mem::Free( m_data.m_prealbuffer );
+
+	XInputClose( m_data.m_handle );
+
+	delete m_node;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::Acquire( void )
+{
+    Dbg_Message( "Acquiring controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+
+    if( m_state == vIDLE )
+    {
+		// Acquire device handle.
+		m_data.m_handle = XInputOpen( XDEVICE_TYPE_GAMEPAD, m_data.m_port, XDEVICE_NO_SLOT, NULL );
+
+//		if( m_data.m_handle )
+		{
+			// Store capabilites of the device
+//			XINPUT_CAPABILITIES caps;
+//			XInputGetCapabilities( m_data.m_handle, &caps );
+
+//			memset( m_data.m_control_data, 0, CTRL_BUFFER_LENGTH );
+			m_data.m_valid = false;
+			m_state = vACQUIRING;
+			return;
+		}
+	}
+	Dbg_Warning( "failed to open controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void Device::Unacquire ( void )
+{
+    Dbg_Message( "Unacquiring controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+
+    if( m_state == vACQUIRED )
+    {
+		m_data.m_handle = NULL;
+    }
+
+    m_state = vIDLE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Device::ActivateActuator( int act_num, int percent )
+{
+	// Do nothing if the actuators are disabled.
+	if( m_data.m_actuators_disabled )
+	{
+		return;
+	}	
+    
+    // First, make sure we're in a ready state and our controller has actuators
+	read_data();
+    if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+    {
+        if(( act_num >= 0 ) && ( act_num < m_data.m_num_actuators ))
+        {
+			unsigned short left_motor, right_motor;           
+			float act_strength						= ((float)percent * m_data.m_actuator_max[act_num] ) * 0.01f;
+			m_data.m_actuator_direct[act_num]		= (unsigned char)act_strength;
+
+			// Scale the values from [0,255] to [0,65535].
+			if( act_num == 0 )
+			{
+				left_motor	= (unsigned short)( act_strength * 256.0f );
+				right_motor	= (unsigned short)m_data.m_actuator_direct[1] * 256;
+			}
+			else
+			{
+				left_motor	= (unsigned short)m_data.m_actuator_direct[0] * 256;
+				right_motor	= (unsigned short)( act_strength * 256.0f );
+			}
+
+			set_xbox_actuators( m_data.m_handle, left_motor, right_motor );
+        }
+    }
+}
+
+/******************************************************************/
+/* These disable or enable pad vibration.
+/******************************************************************/
+void Device::DisableActuators()
+{
+	// If disabled already do nothing.
+	if( m_data.m_actuators_disabled )
+	{
+		return;
+	}
+		
+	// Run through all the actuators and make sure they're off.
+	if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+    {
+		for( int i = 0; i < m_data.m_num_actuators; ++i )
+		{
+			// Switch it off.
+			m_data.m_actuator_direct[i] = 0;
+		}	
+		set_xbox_actuators( m_data.m_handle, 0, 0 );
+	}	
+	
+	// Set the flag.
+	m_data.m_actuators_disabled = true;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Device::EnableActuators( void )
+{
+	m_data.m_actuators_disabled = false;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Device::ResetActuators( void )
+{
+	if( m_data.m_actuators_disabled )
+	{
+		// If disabled, then should be off anyway but toggle the states to make sure.
+		EnableActuators();
+		DisableActuators();
+	}
+	else
+	{
+		// If enabled, then we disable them, which switched them off and then enable them again (in the off position).
+		DisableActuators();
+		EnableActuators();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/* Ken: This gets called for each pad device when the game gets   */
+/* paused. It remembers whether the pad was vibrating & switches  */
+/* off vibration.
+/*                                                                */
+/******************************************************************/
+
+void Device::Pause( void )
+{
+	// If paused already do nothing.
+	if( !m_data.m_paused_ref.InUse())
+	{		
+	    // First, make sure we're in a ready state and our controller has actuators
+	    if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+		{
+			for( int i = 0; i
+
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+extern int KeyboardInit(void);
+       
+namespace SIO
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Serial IO Manager" );
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+void Manager::process_devices( const Tsk::Task< Manager::DeviceList >& task )
+{
+	Device*					device;
+	Lst::Search< Device >	    sh;
+	Manager::DeviceList&	device_list = task.GetData();
+
+	device = sh.FirstItem ( device_list );
+
+	while ( device )
+	{
+		Dbg_AssertType ( device, Device );
+
+		device->process();
+		device = sh.NextItem();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Device* Manager::create_device( int index, int port, int slot )
+{
+    Device *device;
+        
+    device = new Device( index, port, slot );
+
+    return device;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Manager::Manager ( void )
+{
+    int i, index;
+
+	XDEVICE_PREALLOC_TYPE xdpt[] = {{ XDEVICE_TYPE_GAMEPAD,			4 },
+									{ XDEVICE_TYPE_MEMORY_UNIT,		1 }};
+
+	// Initialize the peripherals.
+	XInitDevices( sizeof( xdpt ) / sizeof( XDEVICE_PREALLOC_TYPE ), xdpt );
+
+	// Create the keyboard queue.
+//	XInputDebugInitKeyboardQueue( &xdkp );
+
+    m_process_devices_task = new Tsk::Task< DeviceList > ( Manager::process_devices, m_devices );
+    
+	// Pause briefly here to give the system time to enumerate the attached devices.
+	Sleep( 1000 );
+
+    index = 0;
+    for( i = 0; i < SIO::vMAX_PORT; i++ )
+    {
+	    Device* p_device;
+
+		if(( p_device = create_device( index, i, 0 )))
+		{
+			m_devices.AddToTail( p_device->m_node );
+			p_device->Acquire();
+			index++;
+		}
+    }
+    
+	if( !Pcm::NoMusicPlease())
+	{
+		Pcm::Init();
+	}
+
+	Dbg_Message( "Initialized Controller lib\n" );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Manager::~Manager( void )
+{
+    Device*					device;
+    Device*					next;
+    Lst::Search< Device >	sh;
+    
+    device = sh.FirstItem ( m_devices );
+    
+    while ( device )
+    {
+        Dbg_AssertType ( device, Device );
+    
+        next = sh.NextItem();
+    
+        delete device;
+        device = next;
+    }
+
+    delete m_process_devices_task;
+
+#	if KEYBOARD_ON
+	// Initialize the keyboard.
+	KeyboardDeinit();
+#	endif
+
+	Dbg_Message( "Shut down IOP Controller Lib\n" );
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Manager::ProcessDevices( void )
+{
+	Device*					device;
+	Lst::Search< Device >	sh;
+	Manager::DeviceList&	device_list = m_devices;
+
+	device = sh.FirstItem( device_list );
+
+	while( device )
+	{
+		Dbg_AssertType ( device, Device );
+
+		device->process();
+		device = sh.NextItem();
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Device* Manager::GetDevice( int port, int slot )
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    device = sh.FirstItem ( m_devices );
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+        if( ( device->GetPort() == port ) &&
+            ( device->GetSlot() == slot ))
+        {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+Device* Manager::GetDeviceByIndex( int index )
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    device = sh.FirstItem ( m_devices );
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+        if( device->GetIndex() == index )
+        {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Manager::Pause()
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem( m_devices ); device; device = sh.NextItem())
+    {
+		device->Pause();
+    }
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Manager::UnPause( void )
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem( m_devices ); device; device = sh.NextItem())
+    {
+		device->UnPause();
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SIO
diff --git a/Code/Sys/SIO/keyboard.h b/Code/Sys/SIO/keyboard.h
new file mode 100644
index 0000000..dda83db
--- /dev/null
+++ b/Code/Sys/SIO/keyboard.h
@@ -0,0 +1,94 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		keyboard.h												**
+**																			**
+**	Created:		03/08/2001	-	gj										**
+**																			**
+**	Description:	USB Keyboard interface									**
+**																			**
+*****************************************************************************/
+
+
+#ifndef __SYS_KEYBOARD_H
+#define __SYS_KEYBOARD_H
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+namespace SIO
+{
+
+	
+
+#define KEYBOARD_ON	(1)
+			
+enum
+{
+	vKB_LEFT	= 20,	// start it high so as not to interfere with button/pad enums 
+	vKB_RIGHT,
+	vKB_UP,
+	vKB_DOWN,
+	vKB_BACKSPACE,
+	vKB_ENTER,
+	vKB_ESCAPE,
+	vKB_F1,
+	vKB_F2,
+	vKB_F3,
+	vKB_F4
+};
+
+/*****************************************************************************
+**							Class Definitions								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							 Private Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Public Declarations							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Public Prototypes							**
+*****************************************************************************/
+
+int KeyboardInit(void);
+int KeyboardDeinit(void);
+int KeyboardRead( char* makes );
+void KeyboardClear( void );
+void SetKeyboardActive(bool active);
+void EnableKeyboard( bool enable );
+
+/*****************************************************************************
+**								Inline Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SIO
+
+#endif	// __SYS_KEYBOARD__H
+
diff --git a/Code/Sys/SIO/ngc/p_keyboard.cpp b/Code/Sys/SIO/ngc/p_keyboard.cpp
new file mode 100644
index 0000000..5a832f5
--- /dev/null
+++ b/Code/Sys/SIO/ngc/p_keyboard.cpp
@@ -0,0 +1,390 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		keyboard.cpp											**
+**																			**
+**	Created:		03/08/2001	-	gj										**
+**																			**
+**	Description:	USB Keyboard interface									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+
+//#include 
+//#include 
+//#include 
+//#include 
+//#include 
+//
+//#include 
+extern "C"
+{
+int sceUsbKbInit(int *max_connect) { return 0; }
+int sceUsbKbGetInfo(int *info) { return 0; }
+int sceUsbKbRead(unsigned int no,int *data) { return 0; }
+int sceUsbKbGetLocation(int no,unsigned char *location) { return 0; }
+int sceUsbKbSetLEDStatus(int no, unsigned char led) { return 0; }
+int sceUsbKbSetLEDMode(int no, int mode) { return 0; }
+int sceUsbKbSetRepeat(int no, int sta_time, int interval) { return 0; }
+int sceUsbKbSetCodeType(int no, int type) { return 0; }
+int sceUsbKbSetArrangement(int no, int arrange) { return 0; }
+int sceUsbKbSync(int mode, int *result) { return 0; }
+}
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace SIO
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+#define USBKEYBD_ARG "keybd=8\0debug=1"
+
+static	unsigned char *s_old_status = NULL;
+//static	int	s_info;
+//static	unsigned char 		s_location[7];
+//static	int s_kdata;
+static	int			s_max_connect;
+
+enum
+{
+	vKEYCODE_RIGHT	=	0x804F,
+	vKEYCODE_LEFT	=	0x8050,
+	vKEYCODE_DOWN	=	0x8051,
+	vKEYCODE_UP		=	0x8052,
+	vKEYCODE_ESC	= 	0x8029,
+};
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+//static void s_init_newkeyboard( int i );
+//static int s_capture_input( int i, char* makes );
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardInit(void)
+{
+	int result;
+//    char *option = USBKEYBD_ARG;
+	int i;
+
+	//sceSifInitRpc(0);
+	
+//#ifdef __NOPT_CDROM__OLD
+//	result = sceSifLoadModule(
+//               "cdrom0:\\USBKB.IRX",
+//                sizeof( USBKEYBD_ARG ) + 1, option );
+//#else
+//	result = sceSifLoadModule(
+//               "host0:IOPModules/usbkb.irx",
+//                sizeof( USBKEYBD_ARG ) + 1, option );
+//#endif
+	
+//	if( result < 0 )
+    {
+        Dbg_MsgAssert( 0,( "EE:Can't load module usbkb.irx\n" ));
+		return -1;
+	}
+
+    result = sceUsbKbInit( &s_max_connect );
+//    if( result == USBKB_NG ) 
+	{
+		Dbg_Printf( "Initialize error\n" );
+		return -1;
+	}
+
+    s_old_status = new unsigned char[ s_max_connect ];
+    for( i = 0; i < s_max_connect; i++ ) 
+	{ 
+		s_old_status[i] = 0; 
+	}
+
+	return 0;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int KeyboardRead( char* makes )
+{
+//	int result;
+//	int i, num_strokes;
+//
+//	// get keyboard info
+//	result = sceUsbKbGetInfo( &s_info );
+////	if( result != USBKB_OK ) 
+//	{
+//		Dbg_Printf( "Error%d : sceUsbKbGetInfo\n", result );
+//		return 0;
+//	}
+////	sceUsbKbSync( USBKB_WAIT, &result );
+////	if( result != USBKB_OK ) 
+//	{
+//		Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+//		return 0;
+//	}
+//  
+//	num_strokes = 0;
+//	// reading keyboard
+//	for( i = 0; i < s_max_connect; i++)  
+//	{
+////		if( s_info.status[i] == 0 ) 
+//		{ 
+//			continue; 
+//		}  // not connected
+//
+//		if( s_old_status[i] == 0 ) 
+//		{
+//			Dbg_Printf( "New keyboard %d is connected\n", i );
+//			s_init_newkeyboard(i);
+//		}
+//
+//		result = sceUsbKbRead( i, &s_kdata );
+////		if( result != USBKB_OK ) 
+//		{
+//			Dbg_Printf( "Error%d : sceUsbKbRead\n", result );
+//			continue;
+//		}
+////		sceUsbKbSync( USBKB_WAIT, &result );
+////		if( result != USBKB_OK ) 
+//		{
+//			Dbg_Printf( "Error%d : sceUsbKbSync\n",result);
+//			continue;
+//		}
+//
+////		if( s_kdata.len == 0 ) 
+//		{
+//			continue;
+//		}
+//
+////		result = sceUsbKbGetLocation( i, s_location );
+////		if( result != USBKB_OK ) 
+//		{
+//			Dbg_Printf( "Error%d : sceUsbKbGetLocation\n", result );
+//			continue;
+//		}
+//		
+////		sceUsbKbSync( USBKB_WAIT, &result );
+////		if( result != USBKB_OK ) 
+//		{
+//			Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+//			continue;
+//		}
+//		
+//		num_strokes = s_capture_input( i, makes );
+//	}
+//  
+//	for( i = 0; i < s_max_connect; i++ ) 
+//	{ 
+////		s_old_status[i] = s_info.status[i]; 
+//	}
+//
+//	return num_strokes;
+	return 0;
+}
+
+void KeyboardClear( void )
+{
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//static void s_init_newkeyboard( int i )
+//{       
+//	int result;
+//	
+//	result = sceUsbKbSetLEDStatus( i, USBKB_LED_NUM_LOCK );
+//	if( result != USBKB_OK ) 
+//	{
+//		Dbg_Printf( "Error%d : sceUsbKbSetLEDStatus\n", result );
+//	} 
+//	else 
+//	{
+//		sceUsbKbSync(USBKB_WAIT,&result);
+//		if( result != USBKB_OK ) 
+//		{
+//			Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+//		}
+//	}
+//	
+//	result = sceUsbKbSetLEDMode( i, USBKB_LED_MODE_AUTO1 );
+//	if( result != USBKB_OK ) 
+//	{
+//		Dbg_Printf( "Error%d : sceUsbKbSetLEDMode\n", result );
+//	} 
+//	else 
+//	{
+//		sceUsbKbSync( USBKB_WAIT, &result );
+//		if( result != USBKB_OK ) 
+//		{
+//			Dbg_Printf( "Error%d : sceUsbKbSync\n", result );
+//		}
+//	}
+//	
+//	sceUsbKbSetRepeat( i, 30, 2 );
+//	sceUsbKbSetCodeType( i, USBKB_CODETYPE_ASCII );
+//	sceUsbKbSetArrangement( i, USBKB_ARRANGEMENT_106 );
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+//static int s_capture_input( int i, char* makes )
+//{
+//	unsigned short kcode;
+//	int j, k, index;
+//	
+//	//Dbg_Printf( "usbkeybd%d : ", i );
+//	
+//	//Dbg_Printf( "port=" );
+//	for( k = 0; k < 7 && s_location[k] != 0; k++ )
+//	{
+//		//Dbg_Printf( "%s%d", ((k)? ",": "" ), s_location[k] );
+//	}
+//	  
+//	
+//	//Dbg_Printf( " : LED[%02X] ", s_kdata.led );
+//	
+//	//Dbg_Printf( "MKEY[%02X] ", s_kdata.mkey );
+//	
+//	//Dbg_Printf( "KEY[" );
+//	
+//	index = 0;
+//	for( j=0; j < s_kdata.len; j++ ) 
+//	{
+//		kcode = s_kdata.keycode[j];
+//		if( kcode & USBKB_RAWDAT ) 
+//		{
+//			//Dbg_Printf( "%04X ", kcode );
+//			switch( kcode )
+//			{   
+//				case vKEYCODE_RIGHT:
+//					makes[index++] = vKB_RIGHT;
+//					break;
+//				case vKEYCODE_LEFT:
+//					makes[index++] = vKB_LEFT;
+//					break;
+//				case vKEYCODE_DOWN:
+//					makes[index++] = vKB_DOWN;
+//					break;
+//				case vKEYCODE_UP:
+//					makes[index++] = vKB_UP;
+//					break;
+//				case vKEYCODE_ESC:
+//					makes[index++] = vKB_ESCAPE;
+//					break;
+//			}
+//			continue;
+//		}
+//		if( kcode & USBKB_KEYPAD ) 
+//		{
+//			// 10 key
+//			if((kcode & 0x00ff) == '\n')
+//			{ 
+//				//Dbg_Printf( "(\\n)" ); 
+//				makes[index++] = vKB_ENTER;
+//				continue;
+//			}
+//			//Dbg_Printf( "(%c) ", kcode & ~USBKB_KEYPAD );
+//			makes[index++] = kcode & ~USBKB_KEYPAD;
+//			continue;
+//		}
+//		// Normal key
+//		if((kcode & 0x00ff) == '\0' ) 
+//		{ 
+//			//Dbg_Printf( "\'\\0\'" ); 
+//			continue;
+//		}
+//		if((kcode & 0x00ff) == '\n' ) 
+//		{ 
+//			//Dbg_Printf( "\'\\n\'" ); 
+//			makes[index++] = vKB_ENTER;
+//			continue;
+//		}
+//		if((kcode & 0x00ff) == '\t' ) 
+//		{ 
+//			//Dbg_Printf( "\'\\t\'" ); 
+//			continue;
+//		}
+//		if((kcode & 0x00ff) == '\b' ) 
+//		{ 
+//			//Dbg_Printf( "\'\\b\'" ); 
+//			makes[index++] = vKB_BACKSPACE;
+//			continue;
+//		}
+//		//Dbg_Printf( "\'%c\' ", kcode );
+//		makes[index++] = kcode;
+//	}
+//	//Dbg_Printf( "]\n" ); 
+//
+//	return index;
+//	return 0;
+//}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SIO
diff --git a/Code/Sys/SIO/ngc/siodev.cpp b/Code/Sys/SIO/ngc/siodev.cpp
new file mode 100644
index 0000000..764824a
--- /dev/null
+++ b/Code/Sys/SIO/ngc/siodev.cpp
@@ -0,0 +1,719 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		siodev.cpp												**
+**																			**
+**	Created:		05/26/2000	-	spg										**
+**																			**
+**	Description:	Generic input device									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+         
+#include 
+#include 
+#include 
+//#include 
+
+#include 
+#include "sys/ngc/p_hwpad.h"
+
+// PJR - Doh!
+extern PADStatus padData[PAD_MAX_CONTROLLERS]; // game pad state
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace SIO
+{
+  
+
+// Maps Xbox thumbstick values in the range [-32767, 32767] to PS2 thumstick values in the range [0, 255].
+#define XboxThumbToPS2Thumb( v )	(( v + 32767 ) / 256 )
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+    
+void Device::process( void )
+{
+    switch( m_state )
+    {
+
+    case vIDLE:
+        break;
+
+    case vBUSY:
+        wait();
+        break;
+
+    case vACQUIRING:
+        acquisition_pending();
+        break;
+
+    case vACQUIRED:
+        read_data();
+        break;
+
+    default:
+        break;
+
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::wait( void )
+{
+    Dbg_MsgAssert(( m_next_state >= 0 ) && ( m_next_state <= vNUM_STATES ),( "No next state set for wait state" ));
+
+	if ( get_status() != vNOTREADY )
+	{
+		m_state = m_next_state;
+		m_next_state = vIDLE;
+	}
+}
+
+
+//int gButton_Option = 0;
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Device::read_data( void )
+{
+	PADStatus * p;
+
+	// Need to check here for the controller becoming detached.
+	if( padData[m_data.m_port].err == PAD_ERR_NO_CONTROLLER )
+	{
+		m_plugged_in	= false;
+		m_data.m_valid	= false;
+		Unacquire();
+		Acquire();
+		return;
+	}
+
+	m_data.m_valid	= true;
+	m_plugged_in	= true;
+
+	p = &padData[m_data.m_port];
+
+	// Convert this data back into PS2-style 'raw' data for a DUALSHOCK2.
+	m_data.m_control_data[0] = 0;						// 'Valid' info flag.
+	m_data.m_control_data[1] = ( 0x07 << 4 ) | 16;		// DUALSHOCK2 id + data length.
+
+	m_data.m_control_data[2] = 0xFF;					// Turn off all buttons by default.
+	m_data.m_control_data[3] = 0xFF;					// Turn off all buttons by default.
+
+	m_data.m_control_data[2] ^= ( ( p->button & PAD_BUTTON_START ) && !( p->button & PAD_TRIGGER_Z ) ) ? ( 1 << 3 ) : 0;	// Gamecube 'Start' = PS2 'Start' (only if Z is not also pressed).
+
+	#if 0 // Dan: none of the Z-shifting crap
+	// Gamecube 'Z' = PS2 'Select'.
+	// Directly mapping to select is disabled.
+
+	// Gamecube 'Z' plus 'L' = PS2 'Right Stick Button'.
+	// Gamecube 'Z' plus 'R' = PS2 'shift' (this allows the camera mode shifting).
+	if( p->button & PAD_TRIGGER_Z )
+	{
+		// with Z pressed, triggers are L2/R2
+		
+		// GameCube 'Z' plus 'L' = PS2 'Right Stick Button'. 
+		if( p->button & PAD_TRIGGER_L )
+		{
+			m_data.m_control_data[2] ^= ( 1 << 1 );
+		} else
+
+		// GameCube 'Z' plus 'R' = PS2 'Right Stick Button'. 
+		if( p->button & PAD_TRIGGER_R )
+		{
+			m_data.m_control_data[2] ^= ( 1 << 2 );
+		} else
+
+//		// Z button + START is PS2 'Select'.
+//		if( p->button & PAD_BUTTON_START )
+//		{
+//			m_data.m_control_data[2] ^= ( 1 << 0 );
+//		}
+		// Z button without L & R is PS2 L1+R1.
+		{
+			m_data.m_control_data[3] ^= ( 1 << 2 ) | ( 1 << 3 );
+			m_data.m_control_data[2] ^= ( 1 << 0 );
+		}
+	}
+	#endif
+
+	// Temp hack to get fly-around working.
+	if( p->button & PAD_TRIGGER_Z )
+	{
+		m_data.m_control_data[2] ^= ( 1 << 0 );
+	}
+
+	m_data.m_control_data[3] ^= ( p->button & PAD_BUTTON_Y ) ? ( 1 << 4 ) : 0;		// Gamecube 'Y' = PS2 'Triangle'.
+	m_data.m_control_data[3] ^= ( p->button & PAD_BUTTON_X ) ? ( 1 << 5 ) : 0;		// Gamecibe 'X' = PS2 'Circle'.
+	m_data.m_control_data[3] ^= ( p->button & PAD_BUTTON_A ) ? ( 1 << 6 ) : 0;		// Gamecube 'A' = PS2 'X'.
+	m_data.m_control_data[3] ^= ( p->button & PAD_BUTTON_B ) ? ( 1 << 7 ) : 0;		// Gamecube 'B' = PS2 'Square'.
+
+	// Dan: none of this analog crap
+	// m_data.m_control_data[3] ^= ( p->triggerLeft  > 128 ) ? ( 1 << 2 ) : 0;		// XBox 'Left Trigger pressed down > halfway' = PS2 'L1'.
+	// m_data.m_control_data[3] ^= ( p->triggerRight > 128 ) ? ( 1 << 3 ) : 0;		// XBox 'Right Trigger pressed down > halfway' = PS2 'R1'.
+	// m_data.m_control_data[3] ^= ( p->triggerLeft  > 16 ) ? ( 1 << 0 ) : 0;		// XBox 'Left Trigger' = PS2 'L2'.
+	// m_data.m_control_data[3] ^= ( p->triggerRight > 16 ) ? ( 1 << 1 ) : 0;		// XBox 'Right Trigger' = PS2 'R2'.
+	
+	m_data.m_control_data[3] ^= ( p->triggerLeft  > 128 ) ? ( 1 << 2 ) : 0;			// Gamecube 'Left Trigger = PS2 'L1'.
+	m_data.m_control_data[3] ^= ( p->triggerRight > 128 ) ? ( 1 << 3 ) : 0;			// Gamecube 'Right Trigger = PS2 'R1'.
+	
+	// Dan: Store the state of Z in m_control_data[20] above Xbox's black and white buttons.
+	m_data.m_control_data[20]=0;
+	if (p->button & PAD_TRIGGER_Z)
+	{
+		m_data.m_control_data[20] |= (1<<2);
+	}	
+
+	// Make analog buttons full depression if pressed.
+	// Ngc analog buttons return analog value in range [0, 255].
+	m_data.m_control_data[12] = ( p->button & PAD_BUTTON_Y ) ? 255 : 0;				// Gamecube 'Y' = PS2 Analog 'Triangle'.
+	m_data.m_control_data[13] = ( p->button & PAD_BUTTON_X ) ? 255 : 0;				// Gamecube 'X' = PS2 Analog 'Circle'.
+	m_data.m_control_data[14] = p->analogA;											// Gamecube 'A' = PS2 Analog 'X'.
+	m_data.m_control_data[15] = p->analogB;											// Gamecube 'B' = PS2 'Square'.
+	
+	m_data.m_control_data[16] = ( p->triggerLeft  > 128 ) ? 255 : 0;				// Gamecube 'Left Trigger' = PS2 Analog 'L1'.
+	m_data.m_control_data[17] = ( p->triggerRight > 128 ) ? 255 : 0;				// Gamecube 'Right Trigger' = PS2 Analog 'R1'.
+	m_data.m_control_data[18] = 0;													// Gamecube no 'L2'
+	m_data.m_control_data[19] = 0;													// Gamecube no 'R2'
+
+	// Zero out the d-pad pressure values.
+	m_data.m_control_data[8]	= 0x00;
+	m_data.m_control_data[9]	= 0x00;
+	m_data.m_control_data[10]	= 0x00;
+	m_data.m_control_data[11]	= 0x00;
+
+	// Handle 8 position d-pad.
+	m_data.m_control_data[2] ^= ( p->button & PAD_BUTTON_UP ) ? ( 1 << 4 ) : 0;		// Gamecube 'DPad Up' = PS2 'DPad Up'.
+	m_data.m_control_data[10] = ( p->button & PAD_BUTTON_UP ) ? 0xFF : 0;			// Gamecube 'DPad Up' = PS2 'DPad Up'.
+	m_data.m_control_data[2] ^= ( p->button & PAD_BUTTON_RIGHT ) ? ( 1 << 5 ) : 0;	// Gamecube 'DPad Right' = PS2 'DPad Right'.
+	m_data.m_control_data[8] = ( p->button & PAD_BUTTON_RIGHT ) ? 0xFF : 0;			// Gamecube 'DPad Right' = PS2 'DPad Right'.
+	m_data.m_control_data[2] ^= ( p->button & PAD_BUTTON_DOWN ) ? ( 1 << 6 ) : 0;	// Gamecube 'DPad Down' = PS2 'DPad Down'.
+	m_data.m_control_data[11] = ( p->button & PAD_BUTTON_DOWN ) ? 0xFF : 0;			// Gamecube 'DPad Down' = PS2 'DPad Down'.
+	m_data.m_control_data[2] ^= ( p->button & PAD_BUTTON_LEFT ) ? ( 1 << 7 ) : 0;	// Gamecube 'DPad Left' = PS2 'DPad Left'.
+	m_data.m_control_data[9] = ( p->button & PAD_BUTTON_LEFT ) ? 0xFF : 0;			// Gamecube 'DPad Left' = PS2 'DPad Left'.
+
+	// Gamecube thumbsticks return analog value in range [-128, 127].
+	int stx = (int)(((float)p->stickX) * 2.0f );
+	int sty = (int)(((float)p->stickY) * 2.0f );
+	if ( stx < -128 ) stx = -128;
+	if ( stx > 127 ) stx = 127;
+	if ( sty < -128 ) sty = -128;
+	if ( sty > 127 ) sty = 127;
+
+	int subx = (int)(((float)p->substickX) * 2.0f );
+	int suby = (int)(((float)p->substickY) * 2.0f );
+	if ( subx < -128 ) subx = -128;
+	if ( subx > 127 ) subx = 127;
+	if ( suby < -128 ) suby = -128;
+	if ( suby > 127 ) suby = 127;
+
+	m_data.m_control_data[4] = (unsigned char)(subx+128);		// Analog stick right (X direction).
+	m_data.m_control_data[5] = 255 - (unsigned char)(suby+128);	// Analog stick right (Y direction).
+	m_data.m_control_data[6] = (unsigned char)(stx+128);		// Analog stick left (X direction).
+	m_data.m_control_data[7] = 255 - (unsigned char)(sty+128);	// Analog stick left (Y direction).
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Device::IsPluggedIn( void )
+{
+	return m_plugged_in;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::acquisition_pending( void )
+{
+    int status;
+
+    status = get_status();
+                        
+    if(( status == vCONNECTED ) || ( status == vREADY ))
+    {
+        // sucessful.  Now query the controller for capabilities
+        query_capabilities();
+    }
+        
+	// failed to acquire controller
+	// stay in this state and continue to try to acquire it or prompt the user if it is mandatory
+	// that they have a controller in   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::query_capabilities( void )
+{   
+	// Currently assumes standard Gamecube controller.
+    int id = vANALOG_CTRL;
+    
+    m_data.m_type = id;
+
+	switch( id )
+	{   
+		case vANALOG_CTRL:
+		{
+			m_data.m_caps.SetMask( mANALOG_BUTTONS );
+
+			// GameCube actually only has 1, but we need to track 2 since PS2 and Xbox have 2,
+			// and incoming requests could be for either.
+			m_data.m_num_actuators = 2;
+			m_data.m_caps.SetMask( mACTUATORS );
+			m_data.m_actuator_max[0] = 1;
+			m_data.m_actuator_max[1] = 1;
+
+			m_state = vBUSY;
+			m_next_state = vACQUIRED;
+			return;
+		}
+
+		default:
+		{
+			Dbg_Message( "Detected Controller of unknown type in %d:%d", m_data.m_port, m_data.m_slot );
+			break;
+		}
+	}
+    m_state = vACQUIRED;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Device::get_status( void )
+{
+	if( m_data.m_port < 4 )
+	{
+		if( padData[m_data.m_port].err == PAD_ERR_NONE )
+		{
+			return vREADY;
+		}
+		else
+		{
+			// Could do more checking here of the return value.
+			return vDISCONNECTED;
+		}
+	}
+	return vDISCONNECTED;
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Device::Device ( int index, int port, int slot )
+{
+	m_node = new Lst::Node< Device > ( this );
+	Dbg_AssertType ( m_node, Lst::Node< Device > );
+
+    Dbg_Assert( port < vMAX_PORT );
+    Dbg_Assert( slot < vMAX_SLOT );
+
+    m_state = vIDLE;
+    m_next_state = vIDLE;
+    m_index = index;
+
+    // Initialize device
+    m_data.m_port = port;
+    m_data.m_slot = slot;
+    m_data.m_caps.ClearAll();
+    m_data.m_num_actuators = 0;
+    m_data.m_valid = false;
+
+    memset( m_data.m_actuator_direct, 0, ACTUATOR_BUFFER_LENGTH );
+    memset( m_data.m_actuator_align, 0xFF, ACTUATOR_BUFFER_LENGTH );
+    m_data.m_actuator_align[0] = 0;
+    m_data.m_actuator_align[1] = 1;
+
+    memset( m_data.m_control_data, 0, CTRL_BUFFER_LENGTH );
+//	m_data.m_prealbuffer = Mem::Malloc( scePadDmaBufferMax * sizeof( uint128) + 63 );
+	m_data.m_dma_buff =  (unsigned char*)((((uint32)m_data.m_prealbuffer)+63)&~63);
+	
+//    memset( m_data.m_dma_buff, 0, sizeof( uint128 ) * scePadDmaBufferMax );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Device::~Device ( void )
+{
+	Dbg_AssertType ( m_node, Lst::Node< Device > );
+//	Mem::Free( m_data.m_prealbuffer );
+
+//	XInputClose( m_data.m_handle );
+
+	delete m_node;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::Acquire( void )
+{
+    Dbg_Message( "Acquiring controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+
+    if( m_state == vIDLE )
+    {
+		if( m_data.m_port < 4 )
+		{
+			// Acquire device handle.
+//			m_data.m_handle = XInputOpen( XDEVICE_TYPE_GAMEPAD, m_data.m_port, XDEVICE_NO_SLOT, NULL );
+//
+//			if( m_data.m_handle )
+			{
+				// Store capabilites of the device
+//				XINPUT_CAPABILITIES caps;
+//				XInputGetCapabilities( m_data.m_handle, &caps );
+
+//				memset( m_data.m_control_data, 0, CTRL_BUFFER_LENGTH );
+				m_data.m_valid = false;
+				m_state = vACQUIRING;
+				return;
+			}
+		}
+	}
+	Dbg_Warning( "failed to open controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void Device::Unacquire ( void )
+{
+    Dbg_Message( "Unacquiring controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+
+    if( m_state == vACQUIRED )
+    {
+//		if( scePadPortClose( m_data.m_port, m_data.m_slot ) != 1 )
+//		{
+//			Dbg_Warning( "failed to close controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+//		}
+    }
+
+    m_state = vIDLE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Device::ActivateActuator( int act_num, int percent )
+{
+	// Do nothing if the actuators are disabled.
+	if( m_data.m_actuators_disabled )
+	{
+		return;
+	}	
+	
+    float act_strength;
+    
+    // First, make sure we're in a ready state and our controller has actuators
+    if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+    {
+		// Rumble regardless. The problem here is that incoming rumble requests can be for motors
+		// other than 0 - PS2 and Xbox have 2 motors. So, if the request is not for motor0, if it 
+		if(( act_num >= 0 ) && ( act_num < m_data.m_num_actuators ))
+        {
+//			act_strength = ((float) percent * m_data.m_actuator_max[act_num] ) / 100.0f;
+			act_strength = percent;
+
+            // for lack of a rounding function, perform this check here
+//			if( m_data.m_actuator_max[act_num] == 1 )
+//			{
+//				if( act_strength > 0.0f )
+//				{
+//					m_data.m_actuator_direct[act_num] = 1;
+//				}
+//				else
+//				{
+//					m_data.m_actuator_direct[act_num] = 0;
+//				}
+//			}
+//			else
+//			{
+//				m_data.m_actuator_direct[act_num] = (unsigned char) act_strength;
+//			}
+			m_data.m_actuator_direct[act_num] = ( act_strength > 0.0f ) ? 1 : 0;
+            
+			// If either tracked motor is on, rumble the single motor, otherwise turn it off.
+			PADControlMotor( m_data.m_port, (( m_data.m_actuator_direct[0] > 0 ) || ( m_data.m_actuator_direct[1] > 0 )) ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP );
+        }
+    }
+}
+
+/******************************************************************/
+/* These disable or enable pad vibration.
+/******************************************************************/
+void Device::DisableActuators()
+{
+	// If disabled already do nothing.
+	if (m_data.m_actuators_disabled)
+	{
+		return;
+	}
+		
+	// Run through all the actuators and make sure they're off.
+    if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+    {
+		for (int i=0; i 0 ) || ( m_data.m_actuator_direct[1] > 0 )) ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP );
+
+//				scePadSetActDirect( m_data.m_port, m_data.m_slot, m_data.m_actuator_direct );
+			}	
+		}
+	}
+}
+
+
+
+// Switches off vibration, and also zeros the saved
+// vibration levels too so that unpausing will not 
+// switch them on again for goodness sake.         
+void Device::StopAllVibrationIncludingSaved()
+{
+	// First, make sure we're in a ready state and our controller has actuators
+	if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+	{
+		for (int i=0; i 0 ) || ( m_data.m_actuator_direct[1] > 0 )) ? PAD_MOTOR_RUMBLE : PAD_MOTOR_STOP );
+
+//		scePadSetActDirect( m_data.m_port, m_data.m_slot, m_data.m_actuator_direct );
+	}	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::ActivatePressureSensitiveMode( void )
+{
+    if( m_data.m_caps.TestMask( mANALOG_BUTTONS ))
+    {
+//		if( scePadEnterPressMode( m_data.m_port, m_data.m_slot ) == 1 )
+		{
+			m_state = vBUSY;
+			m_next_state = vACQUIRED;
+		}
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::DeactivatePressureSensitiveMode( void )
+{
+    if( m_data.m_caps.TestMask( mANALOG_BUTTONS ))
+    {
+//		if( scePadExitPressMode( m_data.m_port, m_data.m_slot ) == 1 )
+		{
+			m_state = vBUSY;
+			m_next_state = vACQUIRED;
+		}
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+} // namespace SIO
+
diff --git a/Code/Sys/SIO/ngc/sioman.cpp b/Code/Sys/SIO/ngc/sioman.cpp
new file mode 100644
index 0000000..0766dcc
--- /dev/null
+++ b/Code/Sys/SIO/ngc/sioman.cpp
@@ -0,0 +1,309 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		sioman.cpp												**
+**																			**
+**	Created:		05/26/2000	-	spg										**
+**																			**
+**	Description:	Serial IO Manager										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+
+#include 
+#include 
+//#include 
+
+#include 
+//#include 
+//#include 
+#include 
+
+#include 
+#include "sys/ngc/p_hwpad.h"
+
+	PADStatus padData[PAD_MAX_CONTROLLERS]; // game pad state
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+namespace SIO
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Serial IO Manager" );
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+void Manager::process_devices( const Tsk::Task< Manager::DeviceList >& task )
+{
+//	Dbg_AssertType ( task, Tsk::Task< INP_MANAGER::DEVICE_LIST > );
+
+	Device*					device;
+	Lst::Search< Device >	    sh;
+	Manager::DeviceList&	device_list = task.GetData();
+
+	device = sh.FirstItem ( device_list );
+
+	// Deal with hot-switching controllers.
+	u32 reset_mask = 0;
+	if( padData[0].err == PAD_ERR_NO_CONTROLLER )
+	{
+		reset_mask |= PAD_CHAN0_BIT;
+	}
+	if( padData[1].err == PAD_ERR_NO_CONTROLLER )
+	{
+		reset_mask |= PAD_CHAN1_BIT;
+	}
+	if( reset_mask )
+	{
+		PADReset( reset_mask );
+	}
+
+	while ( device )
+	{
+		Dbg_AssertType ( device, Device );
+
+		device->process();
+		device = sh.NextItem();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Device* Manager::create_device( int index, int port, int slot )
+{
+    Device *device;
+        
+    device = new Device( index, port, slot );
+
+    return device;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::Manager ( void )
+{
+    int i, j, index;
+
+//	XDEVICE_PREALLOC_TYPE xdpt[] = {{ XDEVICE_TYPE_GAMEPAD,			4 },
+//									{ XDEVICE_TYPE_MEMORY_UNIT,		8 }};
+////									{ XDEVICE_TYPE_DEBUG_KEYBOARD,	1 }};
+//
+//	// Initialize the peripherals.
+//	XInitDevices( sizeof( xdpt ) / sizeof( XDEVICE_PREALLOC_TYPE ), xdpt );
+
+	// Create the keyboard queue.
+//	XInputDebugInitKeyboardQueue( &xdkp );
+
+//#	if KEYBOARD_ON
+//	// Initialize the keyboard.
+//	KeyboardInit();
+//#	endif
+
+    m_process_devices_task = new Tsk::Task< DeviceList > ( Manager::process_devices, m_devices );
+    
+	// Pause briefly here to give the system time to enumerate the attached devices.
+//	Sleep( 500 );
+
+    index = 0;
+    for( i = 0; i < vMAX_PORT; i++ )
+    {
+//		for( j = 0; j < scePadGetSlotMax ( i ); j++ )
+		for( j = 0; j < 1; j++ )
+		{
+		    Device* p_device;
+
+			if(( p_device = create_device ( index, i, j )))
+			{
+				m_devices.AddToTail( p_device->m_node );
+				p_device->Acquire();
+				index++;
+			}
+		}
+    }
+    
+#	if !NO_MUSIC_PLEASE
+	// Moved this since the pcm stuff for ngc needs the sfx stuff initialised first.
+//	Pcm::Init();
+#	endif
+
+    Dbg_Message( "Initialized IOP controller lib\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+    Device*					device;
+    Device*					next;
+    Lst::Search< Device >	sh;
+    
+    device = sh.FirstItem ( m_devices );
+    
+    while ( device )
+    {
+        Dbg_AssertType ( device, Device );
+    
+        next = sh.NextItem();
+    
+        delete device;
+        device = next;
+    }
+
+    delete m_process_devices_task;
+
+	Dbg_Message( "Shut down IOP Controller Lib\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Device* Manager::GetDevice( int port, int slot )
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    device = sh.FirstItem ( m_devices );
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+        if( ( device->GetPort() == port ) &&
+            ( device->GetSlot() == slot ))
+        {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Device* Manager::GetDeviceByIndex( int index )
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    device = sh.FirstItem ( m_devices );
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+        if( device->GetIndex() == index )
+        {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::Pause()
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem( m_devices ); device; device = sh.NextItem())
+    {
+		device->Pause();
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Manager::UnPause( void )
+{
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem( m_devices ); device; device = sh.NextItem())
+    {
+		device->UnPause();
+    }
+}
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SIO
+
+
+
diff --git a/Code/Sys/SIO/siodev.cpp b/Code/Sys/SIO/siodev.cpp
new file mode 100644
index 0000000..1655dcf
--- /dev/null
+++ b/Code/Sys/SIO/siodev.cpp
@@ -0,0 +1,703 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment			                **
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			SYS (SYS_) 												**
+**																			**
+**	File name:		siodev.cpp												**
+**																			**
+**	Created:		05/26/2000	-	spg										**
+**																			**
+**	Description:	Generic input device									**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+         
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								DBG Defines									**
+*****************************************************************************/
+
+namespace SIO
+{
+  
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+    
+void Device::process( void )
+{
+    
+
+    switch( m_state )
+    {
+
+    case vIDLE:
+        break;
+
+    case vBUSY:
+        wait();
+        break;
+
+    case vACQUIRING:
+        acquisition_pending();
+        break;
+
+	case vPRESS_MODE_ON_COMPLETE:
+		m_data.m_button_mode = vANALOG;
+		m_state = vACQUIRED;
+		break;
+
+	case vPRESS_MODE_OFF_COMPLETE:
+		m_data.m_button_mode = vDIGITAL;
+		m_state = vACQUIRED;
+		break;
+	
+    case vACQUIRED:
+        read_data();
+        break;
+
+    default:
+        break;
+
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::wait( void )
+{
+    
+
+    Dbg_MsgAssert(( m_next_state >= 0 ) && ( m_next_state <= vNUM_STATES ),( "No next state set for wait state" ));
+
+    if( scePadGetReqState( m_data.m_port, m_data.m_slot ) != scePadReqStateBusy )
+    {
+        if( get_status() != vNOTREADY )
+        {
+            m_state = m_next_state;
+            m_next_state = vIDLE;
+        }
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::read_data ( void )
+{
+    
+
+    int state;
+
+    state = scePadGetState( m_data.m_port, m_data.m_slot );
+
+    switch ( state )
+    {
+    case scePadStateFindCTP1:
+    case scePadStateStable:
+        if( scePadRead( m_data.m_port, m_data.m_slot, m_data.m_control_data ) > 0 )
+        {
+            m_data.m_valid = true;
+			m_plugged_in=true;
+        }
+        else
+        {
+            m_data.m_valid = false;
+        }
+        break;
+
+    case scePadStateDiscon:
+        // We lost connection to the controller. Try to reacquire it
+        // Also, display message if it is required.
+		m_plugged_in=false;
+        Unacquire();
+        Acquire();
+        break;
+    }                  
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+bool Device::IsPluggedIn()
+{
+    
+	return m_plugged_in;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::acquisition_pending( void )
+{
+    
+
+    int status;
+
+    status = get_status();
+                        
+    if(( status == vCONNECTED ) || ( status == vREADY ))
+    {
+        // sucessful.  Now query the controller for capabilities
+        query_capabilities();
+    }
+        
+	// failed to acquire controller
+	// stay in this state and continue to try to acquire it or prompt the user if it is mandatory
+	// that they have a controller in   
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::query_capabilities( void )
+{   
+    
+
+    int id, ex_id;
+
+    // Get controller ID
+    id = scePadInfoMode( m_data.m_port, m_data.m_slot, InfoModeCurID, 0 );
+    
+    if( id == 0 )
+    {
+        Dbg_Warning( "Unsupported controller or controller disconnected" );
+        Unacquire();
+        return;
+    }
+
+    // Get extended ID, if one exists
+    ex_id = scePadInfoMode( m_data.m_port, m_data.m_slot, InfoModeCurExID, 0 );
+    // if we have a valid extended ID, use it instead
+    if( ex_id > 0 )
+    { 
+        id = ex_id;
+    }
+    
+    m_data.m_type = id;
+	m_data.m_button_mode = vDIGITAL;
+
+    switch( id )
+    {   
+    case vNEGI_COM:
+        Dbg_Message( "Detected NeGi-CON Controller in %d:%d", m_data.m_port, m_data.m_slot );
+        break;
+
+    case vKONAMI_GUN:
+        Dbg_Message( "Detected GunCON(Konami) Controller in %d:%d", m_data.m_port, m_data.m_slot );
+        break;
+
+    case vDIGITAL_CTRL:
+        Dbg_Message( "Detected Digital Controller in %d:%d", m_data.m_port, m_data.m_slot );
+        // Switch to Analog if it's supported
+        if( scePadInfoMode( m_data.m_port, m_data.m_slot, InfoModeCurExID, 0 ) != 0 )
+        {   
+            if( scePadSetMainMode( m_data.m_port, m_data.m_slot, 1, 3 ) == 0 )
+            {
+                Dbg_Warning( "scePadSetMainMode not sent properly" );
+            }
+			// Return so that we stay in this state and re-query as an analog controller
+			return;
+        }
+        
+        break;
+
+    case vJOYSTICK:
+        Dbg_Message( "Detected Analog Joystick in %d:%d", m_data.m_port, m_data.m_slot );
+        break;
+
+    case vNAMCO_GUN:
+        Dbg_Message( "Detected GunCON(NAMCO) Controller in %d:%d", m_data.m_port, m_data.m_slot );
+        break;
+
+
+    case vANALOG_CTRL:
+        {
+            int i, j;
+
+            Dbg_Message( "Detected Analog Controller in %d:%d", m_data.m_port, m_data.m_slot );
+            m_data.m_caps.SetMask( mANALOG );
+            // check if it's a dual shock 2
+			if( scePadInfoPressMode( m_data.m_port, m_data.m_slot ) == 1 )
+			{
+				m_data.m_caps.SetMask( mANALOG_BUTTONS );
+			}
+
+            // check for actuator support
+            if(( m_data.m_num_actuators = scePadInfoAct( m_data.m_port, m_data.m_slot, -1, 0 )) > 0 )
+            {
+                m_data.m_caps.SetMask( mACTUATORS );
+
+                for( i = 0; i < m_data.m_num_actuators; i++ )
+                {
+                    int power;
+
+                    power = scePadInfoAct( m_data.m_port, m_data.m_slot, i, InfoActSize );
+                    if( power == 0 )
+                    {
+                        m_data.m_actuator_max[i] = 1;
+                    }
+                    else
+                    {
+                        m_data.m_actuator_max[i] = 255;
+                    }
+	            }
+
+                for( i = 0; i < m_data.m_num_actuators; i++ )
+                {
+                    m_data.m_actuator_align[ i ] = i;
+                }
+
+                for( j = i; j < 6; j++ )
+                {
+                    m_data.m_actuator_align[ j ] = 0xFF;
+                }
+                
+                if( scePadSetActAlign( m_data.m_port, m_data.m_slot, m_data.m_actuator_align ) == 0 )
+                {
+                    Dbg_Warning( "scePadSetActAlign not sent properly" );
+                }
+
+                m_state = vBUSY;
+                m_next_state = vACQUIRED;
+                return;
+            }
+            break;
+        }
+        
+    case vFISHING_CTRL:
+        Dbg_Message( "Detected TSURI-CON(fishing) Controller in %d:%d", m_data.m_port, m_data.m_slot );
+        break;
+        
+    case vJOG_CTRL:
+        Dbg_Message( "Detected JOG-CON Controller in %d:%d", m_data.m_port, m_data.m_slot );
+        break;
+
+    default:
+        Dbg_Message( "Detected Controller of unknown type in %d:%d", m_data.m_port, m_data.m_slot );
+        break;
+    }
+
+    m_state = vACQUIRED;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+int Device::get_status( void )
+{
+    
+
+    int state;
+
+    state = scePadGetState( m_data.m_port, m_data.m_slot );
+    switch ( state )
+    {
+        
+    case scePadStateFindCTP1:
+    	return vREADY;
+		
+	case scePadStateStable:   
+        return vCONNECTED;
+
+    case scePadStateError:
+    case scePadStateDiscon:
+	case scePadStateClosed:
+		m_plugged_in = false;
+        // problems with controller connection
+		return vDISCONNECTED;
+
+    case scePadStateFindPad:
+    case scePadStateExecCmd:
+		return vNOTREADY;
+
+    default:
+        Dbg_MsgAssert( 0,( "Unhandled Controller Pad State" ));
+        return vREADY;
+    }                 
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+Device::Device ( int index, int port, int slot )
+{
+	m_node = new Lst::Node< Device > ( this );
+	Dbg_AssertType ( m_node, Lst::Node< Device > );
+
+    Dbg_Assert( port < vMAX_PORT );
+    Dbg_Assert( slot < vMAX_SLOT );
+
+    m_state = vIDLE;
+    m_next_state = vIDLE;
+    m_index = index;
+
+    // Initialize device
+    m_data.m_port = port;
+    m_data.m_slot = slot;
+    m_data.m_caps.ClearAll();
+    m_data.m_num_actuators = 0;
+	m_data.m_button_mode = vDIGITAL;
+    m_data.m_valid = false;
+
+	m_plugged_in = true;
+    
+    
+    memset( m_data.m_actuator_direct, 0, ACTUATOR_BUFFER_LENGTH );
+    memset( m_data.m_actuator_align, 0xFF, ACTUATOR_BUFFER_LENGTH );
+    m_data.m_actuator_align[0] = 0;
+    m_data.m_actuator_align[1] = 1;
+
+    memset( m_data.m_control_data, 0, CTRL_BUFFER_LENGTH );
+	m_data.m_prealbuffer = Mem::Malloc( scePadDmaBufferMax * sizeof( uint128) + 63 );
+	m_data.m_dma_buff =  (unsigned char*)((((uint32)m_data.m_prealbuffer)+63)&~63);
+	
+    memset( m_data.m_dma_buff, 0, sizeof( uint128 ) * scePadDmaBufferMax );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+Device::~Device ( void )
+{
+	
+
+	Dbg_AssertType ( m_node, Lst::Node< Device > );
+	Mem::Free( m_data.m_prealbuffer );
+	delete m_node;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::Acquire ( void )
+{
+    
+    
+    Dbg_Message( "Acquiring controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+
+    if( m_state == vIDLE )
+    {
+        if( scePadPortOpen( m_data.m_port, m_data.m_slot, (uint128*)m_data.m_dma_buff ) == 1 )
+        {   
+            memset( m_data.m_control_data, 0, CTRL_BUFFER_LENGTH );
+            
+            m_data.m_valid = false;
+            m_state = vACQUIRING;
+        }
+        else
+        {
+            Dbg_Warning( "failed to open controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+        }
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+	
+void Device::Unacquire ( void )
+{
+    
+
+    Dbg_Message( "Unacquiring controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+
+    if( m_state == vACQUIRED )
+    {
+        if( scePadPortClose( m_data.m_port, m_data.m_slot ) != 1 )
+        {
+            Dbg_Warning( "failed to close controller port %d slot %d\n", m_data.m_port, m_data.m_slot );
+        }
+    }
+
+    m_state = vIDLE;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+void Device::ActivateActuator( int act_num, int percent )
+{
+
+
+	
+	// Do nothing if the actuators are disabled.
+	if (m_data.m_actuators_disabled)
+	{
+		return;
+	}	
+	
+    float act_strength;
+    
+    // First, make sure we're in a ready state and our controller has actuators
+    if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+    {
+        if(( act_num >= 0 ) && ( act_num < m_data.m_num_actuators ))
+        {
+            act_strength = ((float) percent * m_data.m_actuator_max[act_num] ) / 100.0f;
+            // for lack of a rounding function, perform this check here
+            if( m_data.m_actuator_max[act_num] == 1 )
+            {
+                if( act_strength > 0.001f )
+                {
+                    m_data.m_actuator_direct[act_num] = 1;
+                }
+                else
+                {
+                    m_data.m_actuator_direct[act_num] = 0;
+                }
+            }
+            else
+            {
+                m_data.m_actuator_direct[act_num] = (unsigned char) act_strength;
+            }
+            
+            scePadSetActDirect( m_data.m_port, m_data.m_slot, m_data.m_actuator_direct );
+        }
+    }
+}
+
+/******************************************************************/
+/* These disable or enable pad vibration.
+/******************************************************************/
+void Device::DisableActuators()
+{
+    
+
+	// If disabled already do nothing.
+	if (m_data.m_actuators_disabled)
+	{
+		return;
+	}
+		
+	// Run through all the actuators and make sure they're off.
+    if(( m_state == vACQUIRED ) && ( m_data.m_caps.TestMask( mACTUATORS )))
+    {
+		for (int i=0; i
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+//#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#include 
+
+
+/*****************************************************************************
+**								DBG Information								**
+*****************************************************************************/
+
+
+       
+namespace SIO
+{
+
+
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+DefineSingletonClass( Manager, "Serial IO Manager" );
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+#define CD_TEST 0
+#if CD_TEST
+static void cd_test()
+{
+	int i;
+	int	disk_type = SCECdDVD;
+
+	sceCdInit(SCECdINIT);
+	sceCdMmode(disk_type);
+	printf(" sceCdGetDiskType   ");
+	int detected_disk_type= sceCdGetDiskType();
+	switch(detected_disk_type)
+	{
+		case SCECdIllgalMedia:
+		printf("Disk Type= IllgalMedia\n"); break;
+		case SCECdPS2DVD:
+		printf("Disk Type= PlayStation2 DVD\n"); break;
+		case SCECdPS2CD:
+		printf("Disk Type= PlayStation2 CD\n"); break;
+		case SCECdPS2CDDA:
+		printf("Disk Type= PlayStation2 CD with CDDA\n"); break;
+		case SCECdPSCD:
+		printf("Disk Type= PlayStation CD\n"); break;
+		case SCECdPSCDDA:
+		printf("Disk Type= PlayStation CD with CDDA\n"); break;
+		case SCECdDVDV:
+		printf("Disk Type= DVD video\n"); break;
+		case SCECdCDDA:
+		printf("Disk Type= CD-DA\n"); break;
+		case SCECdDETCT:
+		printf("Working\n"); break;
+		case SCECdNODISC: 
+		printf("Disk Type= No Disc\n"); break;
+		default:
+		printf("Disk Type= OTHER DISK\n"); break;
+	}
+
+	// If disk type has changed to a CD, then need to re-initialize it							   
+	if ((detected_disk_type == SCECdPS2CD) || (detected_disk_type == SCECdPSCD))
+	{
+		disk_type = SCECdCD;
+		sceCdMmode(disk_type);
+	}
+	// This next bit is essential for when making a bootable disc, ie one that will
+	// boot on the actual PS rather than just the dev system.
+	// K: Commented out __NOPT_BOOTABLE__, since it is set when __NOPT_CDROM__OLD is set.
+
+	/* Reboot IOP, replace default modules  */
+	char  path[128];
+	
+	// THIS CODE IS NOT USED - JUST A TEST FUNCTION!!!!!
+	sprintf(path,"host0:\\SKATE5\\DATA\\IOPMODULES\\DNAS280.IMG");	   // ALSO NEED TO CHANGE In p_filesys.cpp
+	
+	
+	while ( !sceDNAS2NetSifRebootIop(path) ); /* (Important) Unlimited retries */
+	while( !sceSifSyncIop() );
+
+	/* Reinitialize */
+	sceSifInitRpc(0);
+	sceCdInit(SCECdINIT);
+
+	sceCdMmode(disk_type);   /* Media: CD-ROM */
+
+	sceFsReset();
+
+	int fd = sceDopen("cdrom0:");
+	if (fd >= 0)
+	{
+		struct sce_dirent dir_entry;
+		Dbg_Message("Reading directory");
+		while (sceDread(fd, &dir_entry) > 0)
+		{
+			Dbg_Message("Found entry %s", dir_entry.d_name);
+		}
+		sceDclose(fd);
+	}
+	else
+	{
+		// Couldn't open directory, try raw read
+		uint32 buffer[512 + 16];
+		for (i = 0; i < 512; i++)
+		{
+			buffer[i] = 0xDEADBEEF;
+		}
+
+		sceCdRMode mode;
+		mode.trycount = 255;
+		mode.spindlctrl = SCECdSpinNom;
+		mode.datapattern = SCECdSecS2048;
+		mode.pad = 0;
+
+		int ret_val = sceCdRead(0, 1, buffer, &mode);
+		if (ret_val == 1)
+		{
+			sceCdSync(0);
+			Dbg_Message("Read Sector, here is the dump (make sure you don't see 0xDEADBEEF in data)");
+
+			// Read data, print it
+			for (i = 0; i < 512; i += 4)
+			{
+				Dbg_Message("0x%08x 0x%08x 0x%08x 0x%08x", buffer[i], buffer[i + 1], buffer[i + 2], buffer[i + 3]);
+			}
+		}
+		else
+		{
+			Dbg_Message("Couldn't Read Sector. Error = %x", sceCdGetError());
+		}
+	}
+	Dbg_Assert(0);
+}
+#endif
+
+void		Manager::process_devices( const Tsk::Task< Manager::DeviceList >& task )
+{
+	
+//	Dbg_AssertType ( task, Tsk::Task< INP_MANAGER::DEVICE_LIST > );
+
+	Device*					device;
+	Lst::Search< Device >	    sh;
+	Manager::DeviceList&	device_list = task.GetData();
+
+	device = sh.FirstItem ( device_list );
+
+	while ( device )
+	{
+		Dbg_AssertType ( device, Device );
+
+		device->process();
+		device = sh.NextItem();
+	}
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Device* Manager::create_device( int index, int port, int slot )
+{
+    
+
+    Device *device;
+        
+    device = new Device( index, port, slot );
+
+    return device;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+#define MAX_IRX_NAME_CHARS 100
+static char pIRXNameBuf[MAX_IRX_NAME_CHARS+1];
+
+// Loads an IRX file. The name must not have the .irx extension, it gets added by the function.
+// Returns 0 if successful, -1 if error. (But it will assert in debug mode)
+int LoadIRX(const char *pName, int num_args, char* args, bool assert_on_fail)
+{
+    
+	
+	File::StopStreaming( );
+	if ( Pcm::UsingCD( ) )
+	{
+		Dbg_MsgAssert( 0,( "Using CD returned TRUE." ));
+		return ( -1 );
+	}
+
+	// Start the name with the appropriate thingy.
+	if (Config::CD())
+	{
+		Dbg_MsgAssert(strlen("cdrom0:\\xxxxxxxxxxxxxxxxxIOP\\")+strlen(pName)+strlen(".IRX")<=MAX_IRX_NAME_CHARS,("String in pName is too long."));
+		char  path[128];
+		sprintf(path,"cdrom0:\\%sIOP\\",Config::GetDirectory());
+		strcpy(pIRXNameBuf,path);
+	}
+	else
+	{
+		Dbg_MsgAssert(strlen("host0:IOPModules/")+strlen(pName)+strlen(".IRX")<=MAX_IRX_NAME_CHARS,("String in pName is too long."));
+		strcpy(pIRXNameBuf,"host0:IOPModules/");
+	}	
+	
+	// Append the name in upper case.
+	char *pDest=pIRXNameBuf+strlen(pIRXNameBuf);
+	const char *pSource=pName;
+	while (*pSource)
+	{
+		char ch=*pSource++;
+		if (ch>='a' && ch<='z')
+		{
+			ch='A'+ch-'a';
+		}	
+		*pDest++=ch;
+	}
+	*pDest=0;
+	
+	// Append the extension.
+	strcat(pIRXNameBuf,".IRX");
+	
+	int result;
+	if(( result = sceSifLoadModule(pIRXNameBuf, num_args, args )) < 0 )
+	{
+		Dbg_MsgAssert( !assert_on_fail,( "Can't load module %s. Result: %d\n",pIRXNameBuf, result ));
+		
+		return result;
+	}   
+	return 0;		
+}
+
+Manager::Manager( void )
+{
+    
+
+    int i, j, index;
+    Device *device;
+    
+    sceSifInitRpc(0);
+
+#if CD_TEST
+	cd_test();
+#endif
+
+	LoadIRX("sio2man");
+#ifdef MULTI_TAP_SUPPORT
+	LoadIRX("mtapman");
+#endif // MULTI_TAP_SUPPORT
+	LoadIRX("mcman");
+	LoadIRX("mcserv");
+	LoadIRX("padman");
+	LoadIRX("cdvdstm");
+    
+	// Moved the loading of the SN Stack module here from net.cpp because it seems to use some
+	// temporary memory upon startup that causes it to exceed the IOP memory limit if loaded
+	// last.
+#ifdef __NOPT_DEBUG__
+	LoadIRX( "SNSTKDBG" );
+#else
+	LoadIRX( "SNSTKREL" );
+#endif
+
+	if (!Sfx::NoSoundPlease() || !Pcm::NoMusicPlease() || !Pcm::StreamsDisabled())
+	{
+		LoadIRX("libsd");	// sound chip SPU2 lib
+		LoadIRX("sdrdrv");	// sound driver lib?
+	}
+
+	// Async file driver
+	LoadIRX("fileio");
+	// Init the new async stuff
+	File::CAsyncFileLoader::sInit();
+
+	if (!Sfx::NoSoundPlease() || !Pcm::NoMusicPlease() || !Pcm::StreamsDisabled())
+	{
+		LoadIRX("ezpcm");	// streaming music...
+	}
+
+	LoadIRX("usbd");		// usb driver
+
+	#define ICON_FILE	"foo"	//"mc0:BASLUS-20731/NAAPROIA/SYS_NET.ICO"// "foo"
+	#define ICON_SYS_FILE	"bar"	//"mc0:BASLUS-20731/NAAPROIA/icon.sys"// "bar"
+
+	static char netcnfArgs[] = "icon="ICON_FILE"\0iconsys="ICON_SYS_FILE;
+
+	LoadIRX("netcnf", sizeof(netcnfArgs), netcnfArgs );
+
+#if ( KEYBOARD_ON )
+	// initialize the keyboard
+	KeyboardInit();
+#endif
+
+	// initialise IOP memory ( used for CD filesystem and for music ).
+	if( sceSifInitIopHeap( ) < 0 )
+	{
+		Dbg_MsgAssert( 0,( "Failed to init IOP Heap\n"));
+	}
+
+    if( scePadInit( 0 ) != 1 )
+    {
+        Dbg_MsgAssert( false,( "failed to init IOP controller lib\n" ));
+    }
+                         
+#ifdef MULTI_TAP_SUPPORT
+    sceMtapInit();
+	sceMtapPortOpen( 0 );
+    if( sceMtapGetConnection( 0 ) != 1 )
+	{
+		sceMtapPortClose( 0 );
+	}
+#endif // MULTI_TAP_SUPPORT
+
+    m_process_devices_task = new Tsk::Task< DeviceList > ( Manager::process_devices, m_devices );
+    
+    index = 0;
+    for( i = 0; i < vMAX_PORT; i++ )
+    {
+       for( j = 0; j < scePadGetSlotMax ( i ); j++ )
+       {
+           if(( device = create_device ( index, i, j )))
+           {
+               m_devices.AddToTail( device->m_node );
+               device->Acquire();
+               index++;
+           }
+       }
+    }
+	
+	if (!Sfx::NoSoundPlease())
+	{
+		Sfx::PS2Sfx_InitCold( );
+	}
+	
+	if (!Pcm::NoMusicPlease() || !Pcm::StreamsDisabled())
+	{
+		Pcm::Init( );
+	}
+
+	if (Config::CD())
+	{
+		File::InitQuickFileSystem( );
+	}	
+    Dbg_Message( "Initialized IOP controller lib\n" );
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Manager::~Manager( void )
+{
+    
+
+    Device*					device;
+    Device*					next;
+    Lst::Search< Device >	sh;
+    
+    device = sh.FirstItem ( m_devices );
+    
+    while ( device )
+    {
+        Dbg_AssertType ( device, Device );
+    
+        next = sh.NextItem();
+    
+        delete device;
+        device = next;
+    }
+
+    delete m_process_devices_task;
+
+    if( scePadEnd() == 1 )
+    {
+        Dbg_Message( "Shut down IOP Controller Lib\n" );
+    }
+    else
+    {
+        Dbg_MsgAssert( false,( "Failed to shut down IOP Controller Lib\n" ));
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Device* Manager::GetDevice( int port, int slot )
+{
+    
+
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+        if( ( device->GetPort() == port ) &&
+            ( device->GetSlot() == slot ))
+        {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+Device* Manager::GetDeviceByIndex( int index )
+{
+    
+
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+        if( device->GetIndex() == index )
+        {
+            return device;
+        }
+    }
+    
+    return NULL;
+}
+
+/******************************************************************/
+/*                                                                */
+/*  Ken: Runs through all the devices pausing each one.           */
+/*                                                                */
+/******************************************************************/
+
+void Manager::Pause()
+{
+    
+
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+		device->Pause();
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*  Ken: Runs through all the devices unpausing each one.         */
+/*                                                                */
+/******************************************************************/
+
+void Manager::UnPause()
+{
+    
+
+    Device*					device;
+    Lst::Search< Device >	sh;
+    
+    for( device = sh.FirstItem ( m_devices ); device;
+            device = sh.NextItem ())
+    {
+		device->UnPause();
+    }
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace SIO
diff --git a/Code/Sys/Win32/p_timer.cpp b/Code/Sys/Win32/p_timer.cpp
new file mode 100644
index 0000000..c6b0571
--- /dev/null
+++ b/Code/Sys/Win32/p_timer.cpp
@@ -0,0 +1,120 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			Timer (TMR)			 									**
+**																			**
+**	File name:		ngps/p_timer.cpp										**
+**																			**
+**	Created by:		09/25/00	-	dc										**
+**																			**
+**	Description:	XBox System Timer										**
+**																			**
+*****************************************************************************/
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace Tmr 
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+//static clock_t			start_count;
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************
+
+Manager::Manager ( void )
+{
+	Dbg_MemberFunction;
+	
+	// Set the start count.
+	start_count = timeGetTime();	
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************
+
+Manager::~Manager ( void )
+{
+	Dbg_MemberFunction;    
+}
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+ 
+Time GetTime ( void )
+{
+	return timeGetTime();
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+ 
+MicroSeconds GetTimeInUSeconds( void )
+{
+	return timeGetTime() * 1000;
+}
+
+float FrameLength()
+{
+	return 16;
+}
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+
+} // namespace Tmr
diff --git a/Code/Sys/XBox/p_timer.cpp b/Code/Sys/XBox/p_timer.cpp
new file mode 100644
index 0000000..cff00ee
--- /dev/null
+++ b/Code/Sys/XBox/p_timer.cpp
@@ -0,0 +1,498 @@
+/*****************************************************************************
+**																			**
+**			              Neversoft Entertainment							**
+**																		   	**
+**				   Copyright (C) 1999 - All Rights Reserved				   	**
+**																			**
+******************************************************************************
+**																			**
+**	Project:		SYS Library												**
+**																			**
+**	Module:			Timer (TMR)			 									**
+**																			**
+**	File name:		ngps/p_timer.cpp										**
+**																			**
+**	Created by:		01/13/00	-	mjb										**
+**																			**
+**	Description:	NGPS System Timer										**
+**																			**
+*****************************************************************************/
+
+
+
+/*****************************************************************************
+**							  	  Includes									**
+*****************************************************************************/
+
+#include 
+#include 
+#include 
+#include 
+#include 
+
+/*****************************************************************************
+**							  DBG Information								**
+*****************************************************************************/
+
+namespace Tmr 
+{
+
+/*****************************************************************************
+**								  Externals									**
+*****************************************************************************/
+
+/*****************************************************************************
+**								   Defines									**
+*****************************************************************************/
+
+#define vHIREZ_CMP 15000
+
+/*****************************************************************************
+**								Private Types								**
+*****************************************************************************/
+
+/*****************************************************************************
+**								 Private Data								**
+*****************************************************************************/
+
+static volatile uint64	s_count;
+
+#define	vSMOOTH_N  4	
+
+static volatile uint64	s_vblank = 0;
+static volatile uint64	s_total_vblanks = 0;
+static float			s_slomo = 1.0f;
+static uint64			s_render_frame = 0;	
+static uint64			s_stored_vblank = 0;
+static volatile bool	s_vsync_handler_installed = false;
+static volatile bool	s_swap_handler_installed = false;
+
+static clock_t			high_count;
+static clock_t			start_count;
+static __int64			high_count_high_precision;
+static __int64			start_count_high_precision;
+
+static uint64 GetGameVblanks();
+
+struct STimerInfo
+{
+	float	render_length;
+	double	uncapped_render_length;
+	int		render_buffer[vSMOOTH_N];
+	uint64	render_vblank;
+	uint64	render_last_vblank;	
+	int		render_index;
+};
+
+static STimerInfo gTimerInfo;
+static STimerInfo gStoredTimerInfo;
+static float xrate = 60.0f;
+
+static void InitTimerInfo( void )
+{
+	static bool xrate_set = false;
+
+	if( !xrate_set )
+	{
+		xrate		= (float)Config::FPS();
+		xrate_set	= true;
+	}
+
+	gTimerInfo.render_length			= 0.01666666f;		// defualt to 1/60th
+	gTimerInfo.uncapped_render_length	= 0.01666666f;		// defualt to 1/60th
+	for( int i = 0; i < vSMOOTH_N; i++ )
+	{
+		gTimerInfo.render_buffer[ i ] = 1;
+	}
+	gTimerInfo.render_vblank			= 0;	
+	gTimerInfo.render_last_vblank		= 0;
+	gTimerInfo.render_index				= 0;
+
+	gStoredTimerInfo					= gTimerInfo;
+
+	s_stored_vblank						= 0;
+	s_vblank							= 0;
+}
+
+//#ifdef __NOPT_PROFILE__
+
+//Sys::ProfileData	DMAProfileData;
+//Sys::ProfileData	VU1ProfileData;
+
+//#endif // __NOPT_PROFILE__
+
+/*****************************************************************************
+**								 Public Data								**
+*****************************************************************************/
+
+/*****************************************************************************
+**							  Private Prototypes							**
+*****************************************************************************/
+
+/*****************************************************************************
+**							   Private Functions							**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+__int64 __declspec( naked ) __stdcall getCycles( void )
+{
+	_asm
+	{
+		rdtsc
+		ret
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void __cdecl s_vsync_handler( D3DVBLANKDATA *pData )
+{
+	// inc vblanks    
+	s_vblank++;
+	s_total_vblanks++;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static void __cdecl s_swap_callback_handler( D3DSWAPDATA *pData )
+{
+	// This callback function will be called when a Swap or Present is encountered by the GPU, in order to provide information as to when
+	// Swap or Present calls are actually handled by the GPU. Swap and Present are like all other rendering functions in that they are enqueued
+	// in the push-buffer and so the handling of the actual command is not synchronous with the actual function call.
+	// Even if the Swap or Present is called by the title before a vertical blank, it may not be handled by the GPU until after the vertical blank
+	// because the GPU still has to process all of the commands in the push-buffer that are before the Swap or Present.
+	++s_render_frame;
+	
+	int diff;
+	gTimerInfo.render_last_vblank	= gTimerInfo.render_vblank;
+	gTimerInfo.render_vblank		= pData->SwapVBlank;
+	diff = (int)( gTimerInfo.render_vblank - gTimerInfo.render_last_vblank );
+
+	gTimerInfo.render_buffer[gTimerInfo.render_index++]	= diff;
+
+	// Wrap the index around.	
+	if( gTimerInfo.render_index == vSMOOTH_N )
+	{
+		gTimerInfo.render_index = 0;		
+	}
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Init( void )
+{
+	InitTimerInfo();
+	
+	s_count						= 0;
+	start_count					= clock();
+	high_count					= (clock_t)0;
+	start_count_high_precision	= getCycles();
+	high_count_high_precision	= (__int64)0;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void DeInit( void )
+{
+}
+
+
+
+/*****************************************************************************
+**							   Public Functions								**
+*****************************************************************************/
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void InstallVSyncHandlers( void )
+{
+	if( !s_vsync_handler_installed )
+	{
+		// Install the vertical blank callback handler.
+		D3DDevice_SetVerticalBlankCallback( s_vsync_handler );
+		s_vsync_handler_installed = true;
+	}
+
+	if( !s_swap_handler_installed )
+	{
+		// Install the swap callback handler at this point.
+		D3DDevice_SetSwapCallback( s_swap_callback_handler );
+		s_swap_handler_installed = true;
+	}
+}
+
+
+
+// when pausing the game, call this to store the current state of OncePerRender( ) (only essential in replay mode)
+void StoreTimerInfo( void )
+{
+}
+
+
+
+void RecallTimerInfo( void )
+{
+}
+
+
+
+// Call this function once per rendering loop, to increment the 
+// m_render_frame variable
+// This function should be synchronized in some way to the vblank, so that it is called 
+// directly after the vblank that rendering starts on
+void OncePerRender( void )
+{
+#	ifdef STOPWATCH_STUFF
+	Script::IncrementStopwatchFrameIndices();
+	Script::DumpFunctionTimes();
+	Script::ResetFunctionCounts();	
+#	endif
+
+	int total			= 0;
+	int uncapped_total	= 0;
+
+	for( int i = 0; i < vSMOOTH_N; ++i )
+	{
+		int diff = gTimerInfo.render_buffer[i];
+		uncapped_total += diff;
+
+		// Handle very bad values.
+		if( diff > 10 || diff < 0 )		
+		{
+			diff = 1;
+		}
+
+		// Clamp to 4.
+		if( diff > 4 )
+		{
+			diff = 4;
+		}		
+		total += diff;
+	}
+
+	gTimerInfo.render_length = (float)total / (float)vSMOOTH_N;
+
+	if( gTimerInfo.render_length < 1.0f )
+	{
+		gTimerInfo.render_length = 1.0f;
+	}
+
+	gTimerInfo.uncapped_render_length = (double)uncapped_total/(double)vSMOOTH_N; 
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint64 GetRenderFrame( void )
+{
+	return s_render_frame;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void Vblank( void )
+{
+	s_total_vblanks++;
+	s_vblank++;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+static uint64 GetGameVblanks( void )
+{
+	return s_vblank;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+uint64 GetVblanks( void )
+{
+	return s_total_vblanks;
+}
+
+
+
+// call "StartLogic" when all the logic functions are going to be called  
+//void Manager::StartLogic()
+//{
+//}
+
+// returns the PROJECTED length of the next frame, for use in logic calculations
+// done by averaging the frame lengths of the last N frames (default N = 4)
+// at 60fps (NTSC), this would return 1/60
+// at 50fps (PAL), this would return 1/50		   
+// complications arise when the framerate changes
+// we want the physics to move smoothly and consistently at all framerates
+// however, all we know is the number of ticks the last frames took
+// we also need to ensure that the total of all the "FrameLength" calls will
+// reflect the actual time that has passed
+//				 |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |   |    |   
+// say we have   1    1    1    .    2    1    1    1    .    2    1    .    2    1    1    1   .    2 
+//    (10x1 + 4x2  = 18 frames
+//
+//
+// now, let's work in 1/60ths of a second for now....
+// let's take the average of the last 4 frames, as the logic time we will return for this frame
+//
+//               1    1    1         1.25 1.25 1.25 1.25     1.25 1.25      1.5  1.5  1.25 1.25     1.25
+//
+// the reason we do the averaging is to smooth out discontinuities
+// if we simply use the length of the previous frame, then if we were to be alternating between 1 and 2 frames
+// then the logic would move 2 on frames of length 1, and move 1 on frames of length 2
+// this would essentially make the skater visually move alternately .5x and 2x his actual speed
+// this looks incredibly jerky, as there is a 4x difference per frame   
+// by smoothing it out, you would move 1.5 on every frame, giving relative motion of 0.6666 and 1.3333, 
+// this is still jerky, but half as much as the unsmoothed version.
+float FrameLength( void )
+{
+	return gTimerInfo.render_length / xrate * GetSlomo();
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+float GetSlomo( void )
+{
+	return s_slomo;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void SetSlomo( float slomo )
+{
+	s_slomo = slomo;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+double UncappedFrameLength()
+{
+	return gTimerInfo.uncapped_render_length / xrate;
+}
+
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+void VSync( void )
+{
+	uint64 now = GetVblanks();
+	while( now == GetVblanks());	
+}
+ 
+
+
+/******************************************************************/
+/*                                                                */
+/*                                                                */
+/******************************************************************/
+MicroSeconds GetTimeInUSeconds( void )
+{
+	high_count_high_precision = ( getCycles() - start_count_high_precision ) / 733;
+	return static_cast